#include #include #include "ktiva.h" #include "ksync.h" #include "kfile.h" #include "krtc.h" ///////////////////////////////////////////////////////////////////////////// #define _BCD2BIN(b) ((int)(((b) >> 4) * 10 + ((b) & 0x0F))) #define _BIN2BCD(b) ((unsigned char)(((b) / 10) << 4 | ((b) % 10))) #define _IS_12_HOUR_FORMAT_FLAG (unsigned char)(1 << 6) #define _IS_PM_FLAG (unsigned char)(1 << 5) #define _IS_12_HOUR_FORMAT(reg) !!((reg) & _IS_12_HOUR_FORMAT_FLAG) #define _IS_PM(reg) !!((reg) & _IS_PM_FLAG) #define _RTC_STOP_YIELD_DELAY 10 #define _RTC_STOP_MAX_YIELD_DELAY 1000 ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// static RTCTypes g_rtcType = RTCT_Unknown; ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // https://ww1.microchip.com/downloads/en/devicedoc/20005010f.pdf // MCP7940 #define _I2C_SLV_ADDR_MCP7940 0x6f #define _IS_MCP7940_OSC_RUN(reg) (!!(reg & 0x20)) // OSCRUN: 1 = Oscillator is enabled and running // 0 = Oscillator has stopped or has been disabled #define _MCP7940_SRAM_ADDRESS 0x20 #define _MCP7940_SRAM_SIZE 64 #define _MCP7940_TEST_I2C_BUFFER_SIZE 28 ///////////////////////////////////////////////////////////////////////////// static int _MCP7940_get_date_time(struct file *pf, struct tm *ptm) { int ret; unsigned char rtc[7]; if(!(ret = TivaCmdGetI2C(pf, _I2C_SLV_ADDR_MCP7940, 0, sizeof(rtc), rtc, sizeof(rtc)))) { if(_IS_MCP7940_OSC_RUN(rtc[3])) { memset(ptm, 0, sizeof(struct tm)); ptm->tm_year = _BCD2BIN(rtc[6]) + 100; ptm->tm_mon = _BCD2BIN(rtc[5] & 0x1F) - 1; ptm->tm_mday = _BCD2BIN(rtc[4] & 0x3F); if(_IS_12_HOUR_FORMAT(rtc[2])) // 12h format ptm->tm_hour = _BCD2BIN(rtc[2] & 0x1F) + (_IS_PM(rtc[2]) ? 12 : 0); else // 24h format (default) ptm->tm_hour = _BCD2BIN(rtc[2] & 0x3F); ptm->tm_min = _BCD2BIN(rtc[1] & 0x7F); ptm->tm_sec = _BCD2BIN(rtc[0] & 0x7F); ret = 0; } else { KALERT("%s: Oscillator not running!\n", __FUNCTION__); ret = -EIO; } } else { KALERT("%s: TivaCmdGetI2C failed (%d)\n", __FUNCTION__, ret); ret = -EIO; } return ret; } ///////////////////////////////////////////////////////////////////////////// static int _MCP7940_set_date_time(struct file *pf, const struct tm *ptm) { bool bOscRun; unsigned int nMsSleep = 0; int ret, y, m; unsigned char rtc[9], reg = 0; ///////////////////////////////////////////////////////////////////////// // Initiate a stop of the Oscillator by clearing register 0. if((ret = TivaCmdSetI2C(pf, _I2C_SLV_ADDR_MCP7940, 0, ®, 1))) { KALERT("%s: TivaCmdSetI2C failed (%d)\n", __FUNCTION__, ret); return -EIO; } ///////////////////////////////////////////////////////////////////////// // Meanwhile prepare the clock data that will be written to the RTC registers KALERT("Setting RTC to %04ld-%02d-%02dT%02d:%02d:%02d UTC\n", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec); y = ptm->tm_year - 100; m = ptm->tm_mon + 1; rtc[0] = _BIN2BCD(ptm->tm_sec) | 0x80; // Write seconds and start Oscillator. This register will be written seperately at last! rtc[1] = _BIN2BCD(ptm->tm_min); rtc[2] = _BIN2BCD(ptm->tm_hour); // uses 24h format rtc[3] = (ptm->tm_wday + 1) | 0x08; // MCP7940 day of week is in range 1-7, struct tm is in range 0-6 (start with Sunday). // Setting VBATEN (0x08) enables external battery backup supply. rtc[4] = _BIN2BCD(ptm->tm_mday); rtc[5] = _BIN2BCD(m); rtc[6] = _BIN2BCD(y); rtc[7] = 0x80; // Set OUT bit in control register. rtc[8] = 0; // Clear OSCTRIM explicitly. Disables Digital Trimming. ///////////////////////////////////////////////////////////////////////// // Wait for the oscillator to stop. This is indicated by OSCRUN = 0 in register 3. do { if((ret = TivaCmdGetI2C(pf, _I2C_SLV_ADDR_MCP7940, 3, 1, ®, 1))) // read register 3 { KALERT("%s: TivaCmdGetI2C failed (%d)\n", __FUNCTION__, ret); return -EIO; } if((bOscRun = _IS_MCP7940_OSC_RUN(reg))) // if oscillator has not stopped yet { ksync_sleep_ms(_RTC_STOP_YIELD_DELAY); // yield to another thread for _RTC_STOP_YIELD_DELAY ms nMsSleep += _RTC_STOP_YIELD_DELAY; // and sum up the delay to the total yield time. if(nMsSleep >= _RTC_STOP_MAX_YIELD_DELAY) // if the total yield time has reached _RTC_STOP_MAX_YIELD_DELAY, return a timeout error. { KALERT("%s: Timeout while trying to stop RTC Oscillator!\n", __FUNCTION__); return -ETIMEDOUT; } } } while(bOscRun); ///////////////////////////////////////////////////////////////////////// // write registers 1-8 (Minutes to Year and control/status and trim registers) if((ret = TivaCmdSetI2C(pf, _I2C_SLV_ADDR_MCP7940, 1, &rtc[1], sizeof(rtc) - 1))) { KALERT("%s: TivaCmdSetI2C failed (%d)\n", __FUNCTION__, ret); return -EIO; } ///////////////////////////////////////////////////////////////////////// // write register 0 (Seconds and Start oscillator) if((ret = TivaCmdSetI2C(pf, _I2C_SLV_ADDR_MCP7940, 0, rtc, 1))) { KALERT("%s: TivaCmdSetI2C failed (%d)\n", __FUNCTION__, ret); return -EIO; } return 0; // RTC should now be set and running } ///////////////////////////////////////////////////////////////////////////// static int _MCP7940_test_i2c(struct file *pf) { size_t i; int ret; unsigned char write[_MCP7940_TEST_I2C_BUFFER_SIZE], read[_MCP7940_TEST_I2C_BUFFER_SIZE]; for(i = 0; i < sizeof(write); ++i) { write[i] = i; read[i] = 0xFF; } if((ret = TivaCmdSetI2C(pf, _I2C_SLV_ADDR_MCP7940, _MCP7940_SRAM_ADDRESS, write, sizeof(write)))) { KALERT("%s: TivaCmdSetI2C failed (%d)\n", __FUNCTION__, ret); return -EIO; } if((ret = TivaCmdGetI2C(pf, _I2C_SLV_ADDR_MCP7940, _MCP7940_SRAM_ADDRESS, sizeof(read), read, sizeof(read)))) { KALERT("%s: TivaCmdGetI2C failed (%d)\n", __FUNCTION__, ret); return -EIO; } if((ret = memcmp(write, read, sizeof(read)))) { KALERT("%s: R/W Data mismatch!\n", __FUNCTION__); ret = -EPROTO; } return ret; } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // https://datasheets.maximintegrated.com/en/ds/DS3231-DS3231S.pdf // DS3231 #define _I2C_SLV_ADDR_DS3231 0x68 #define _IS_DS3231_OSC_RUN(reg) (!(reg & 0x80)) // OSF (Oscillator Stop Flag): // 1 indicates that the oscillator either is stopped or was stopped for some period ///////////////////////////////////////////////////////////////////////////// static int _DS3231_get_date_time(struct file *pf, struct tm *ptm) { int ret; unsigned char rtc[16]; if(!(ret = TivaCmdGetI2C(pf, _I2C_SLV_ADDR_DS3231, 0, sizeof(rtc), rtc, sizeof(rtc)))) { if(_IS_DS3231_OSC_RUN(rtc[15])) { memset(ptm, 0, sizeof(struct tm)); ptm->tm_year = _BCD2BIN(rtc[6]) + 100; ptm->tm_mon = _BCD2BIN(rtc[5] & 0x1F) - 1; ptm->tm_mday = _BCD2BIN(rtc[4] & 0x3F); if(_IS_12_HOUR_FORMAT(rtc[2])) // 12h format ptm->tm_hour = _BCD2BIN(rtc[2] & 0x1F) + (_IS_PM(rtc[2]) ? 12 : 0); else // 24h format (default) ptm->tm_hour = _BCD2BIN(rtc[2] & 0x3F); ptm->tm_min = _BCD2BIN(rtc[1] & 0x7F); ptm->tm_sec = _BCD2BIN(rtc[0] & 0x7F); ret = 0; } else { KALERT("%s: Oscillator not running!\n", __FUNCTION__); ret = -EIO; } } else { KALERT("%s: TivaCmdGetI2C failed (%d)\n", __FUNCTION__, ret); ret = -EIO; } return ret; } ///////////////////////////////////////////////////////////////////////////// static int _DS3231_set_date_time(struct file *pf, const struct tm *ptm) { int ret, y, m; unsigned char rtc[7], reg = 0; ///////////////////////////////////////////////////////////////////////// // Prepare the clock data to be written to the RTC registers KALERT("Setting RTC to %04ld-%02d-%02dT%02d:%02d:%02d UTC\n", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec); y = ptm->tm_year - 100; m = ptm->tm_mon + 1; rtc[0] = _BIN2BCD(ptm->tm_sec); // seconds rtc[1] = _BIN2BCD(ptm->tm_min); // minutes rtc[2] = _BIN2BCD(ptm->tm_hour); // hour, uses 24h format rtc[3] = (ptm->tm_wday + 1); // DS3231 day of week is in range 1-7, struct tm is in range 0-6 (start with Sunday). rtc[4] = _BIN2BCD(ptm->tm_mday); // day rtc[5] = _BIN2BCD(m); // month rtc[6] = _BIN2BCD(y); // year ///////////////////////////////////////////////////////////////////////// // write registers 0-6 (Seconds to Year) // The countdown chain is reset whenever the seconds register is written (0h). Once the countdown chain is // reset, to avoid rollover issues the remaining time and date registers must be written within 1 second. if((ret = TivaCmdSetI2C(pf, _I2C_SLV_ADDR_DS3231, 0, rtc, sizeof(rtc)))) { KALERT("%s: TivaCmdSetI2C failed (%d)\n", __FUNCTION__, ret); return -EIO; } ///////////////////////////////////////////////////////////////////////// // read/write register 15 (Status Register), to clear the OSF flag. if(!(ret = TivaCmdGetI2C(pf, _I2C_SLV_ADDR_DS3231, 15, 1, ®, 1))) // read register 15 { reg &= 0x7F; // clear OSF bit. if((ret = TivaCmdSetI2C(pf, _I2C_SLV_ADDR_DS3231, 15, ®, 1))) // write register 15 { KALERT("%s: TivaCmdSetI2C failed (%d)\n", __FUNCTION__, ret); return -EIO; } } else { KALERT("%s: TivaCmdGetI2C failed (%d)\n", __FUNCTION__, ret); return -EIO; } return 0; // RTC should now be set and running } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// int krtc_init(void) { int i, ret = 0; struct file *pfSpiDev = NULL; g_rtcType = RTCT_Unknown; if((pfSpiDev = kf_open_locked(_SPI_DEVICE, O_RDWR, 0))) { if(KSpiInit(pfSpiDev)) { unsigned char rtc[7]; if(!(ret = TivaCmdGetI2C(pfSpiDev, _I2C_SLV_ADDR_MCP7940, 0, sizeof(rtc), rtc, sizeof(rtc)))) // MCP7940 { for(i = 0; i < 7; ++i) { if(rtc[i] != 0xFF) { g_rtcType = RTCT_MCP7940; break; } } if(g_rtcType == RTCT_Unknown) { if(!(ret = TivaCmdGetI2C(pfSpiDev, _I2C_SLV_ADDR_DS3231, 0, sizeof(rtc), rtc, sizeof(rtc)))) // DS3231 { for(i = 0; i < 7; ++i) { if(rtc[i] != 0xFF) { g_rtcType = RTCT_DS3231; break; } } } else { KALERT("%s: TivaCmdGetI2C failed (%d)\n", __FUNCTION__, ret); ret = -EIO; } } } else { KALERT("%s: TivaCmdGetI2C failed (%d)\n", __FUNCTION__, ret); ret = -EIO; } } else { KALERT("%s: KSpiInit failed\n", __FUNCTION__); ret = -EIO; } kf_close(pfSpiDev); } else { KALERT("%s: kf_open_locked failed\n", __FUNCTION__); ret = -EIO; } if(g_rtcType == RTCT_Unknown) { KALERT("%s: Unable to determine RTC-Type!\n", __FUNCTION__); ret = -ENODEV; } return ret; } ///////////////////////////////////////////////////////////////////////////// RTCTypes krtc_get_type(void) { return g_rtcType; } ///////////////////////////////////////////////////////////////////////////// int krtc_get_date_time(struct tm *ptm) { int ret; struct file *pfSpiDev = NULL; if(!KRTC_IS_VALID_RTC_TYPE(g_rtcType)) { KALERT("%s: Invalid RTC-Type!\n", __FUNCTION__); return -ENODEV; } if((pfSpiDev = kf_open_locked(_SPI_DEVICE, O_RDWR, 0))) { if(KSpiInit(pfSpiDev)) { switch(g_rtcType) { case RTCT_MCP7940: if((ret = _MCP7940_get_date_time(pfSpiDev, ptm))) KALERT("%s: _MCP7940_get_date_time failed!\n", __FUNCTION__); break; case RTCT_DS3231: if((ret = _DS3231_get_date_time(pfSpiDev, ptm))) KALERT("%s: _DS3231_get_date_time failed!\n", __FUNCTION__); break; default: break; } } else { KALERT("%s: KSpiInit failed\n", __FUNCTION__); ret = -EIO; } kf_close(pfSpiDev); } else { KALERT("%s: kf_open_locked failed\n", __FUNCTION__); ret = -ENODEV; } return ret; } ///////////////////////////////////////////////////////////////////////////// int krtc_set_date_time(const struct tm *ptm) { int ret; struct file *pfSpiDev = NULL; // unsigned long jStart = jiffies, jDiff; if(!KRTC_IS_VALID_RTC_TYPE(g_rtcType)) { KALERT("%s: Invalid RTC-Type!\n", __FUNCTION__); return -ENODEV; } if((pfSpiDev = kf_open_locked(_SPI_DEVICE, O_RDWR, 0))) { if(KSpiInit(pfSpiDev)) { switch(g_rtcType) { case RTCT_MCP7940: // jDiff = jiffies - jStart; // KALERT("%s: prep _MCP7940_set_date_time took %u us.\n", __FUNCTION__, jiffies_to_usecs(jDiff)); // jStart = jiffies; if((ret = _MCP7940_set_date_time(pfSpiDev, ptm))) KALERT("%s: _MCP7940_set_date_time failed!\n", __FUNCTION__); // jDiff = jiffies - jStart; // KALERT("%s: call _MCP7940_set_date_time took %u us.\n", __FUNCTION__, jiffies_to_usecs(jDiff)); break; case RTCT_DS3231: if((ret = _DS3231_set_date_time(pfSpiDev, ptm))) KALERT("%s: _DS3231_set_date_time failed!\n", __FUNCTION__); break; default: break; } } else { KALERT("%s: KSpiInit failed\n", __FUNCTION__); ret = -EIO; } kf_close(pfSpiDev); } else { KALERT("%s: kf_open_locked failed\n", __FUNCTION__); ret = -ENODEV; } return ret; } ///////////////////////////////////////////////////////////////////////////// int krtc_test_i2c(void) { int ret; struct file *pfSpiDev = NULL; if(g_rtcType != RTCT_MCP7940) { if(g_rtcType == RTCT_DS3231) { KALERT("%s: I2C-Test not implemented in DS3231!\n", __FUNCTION__); return -ENOTSUPP; } else { KALERT("%s: Invalid RTC-Type!\n", __FUNCTION__); return -ENODEV; } } if((pfSpiDev = kf_open_locked(_SPI_DEVICE, O_RDWR, 0))) { if(KSpiInit(pfSpiDev)) { if((ret = _MCP7940_test_i2c(pfSpiDev))) KALERT("%s: _MCP7940_test_i2c failed!\n", __FUNCTION__); } else { KALERT("%s: KSpiInit failed\n", __FUNCTION__); ret = -EIO; } kf_close(pfSpiDev); } else { KALERT("%s: kf_open_locked failed\n", __FUNCTION__); ret = -ENODEV; } return ret; }