/* * Read/write DS3231M RTC to/from the sysclock. * * This currently doesn't try to maintain any kind of subsecond * accuracy. */ #include #include "common.h" #include "console.h" #include "io.h" #include "systime.h" static inline uint32_t i2c_wait(void) { waitfor(I2C_IRQ); } static void i2c_send(uint8_t byte, uint8_t ctl) { i2c_wait(); I2C_WDATA = (byte << 8) | I2C_NAK | ctl; } static bool i2c_acked(void) { i2c_wait(); return !(I2C_RDATA & I2C_NAK); } static uint8_t i2c_recv(uint8_t ctl) { uint32_t rdata; i2c_wait(); I2C_WDATA = (~0xff) | ctl; i2c_wait(); return I2C_RDATA_DATA; } #define RTC_REGS 19 /* Total RTC registers */ #define RTC_TIME_REGS 7 /* RTC registers with time info */ #define RTC_ADDR 0x68 #define RTC_WCMD ((RTC_ADDR << 1)+0) #define RTC_RCMD ((RTC_ADDR << 1)+1) static unsigned int unbcd(uint8_t v) { return (v & 0x0f) + (v >> 4) * 10; } static uint8_t tobcd(unsigned int v) { uint8_t q; v %= 100; return ((v/10) << 4) + (v%10); } static int i2c_start_rtc(void) { int sda_retry_count = 16; i2c_set_speed(400); /* SDA held low? */ while (!(I2C_RDATA & I2C_SDA)) { i2c_send(0xff, I2C_DUMMY); if (!sda_retry_count--) { con_printf("RTC: I2C SDA stuck low\n"); return -1; } } i2c_send(RTC_WCMD, 0); if (!i2c_acked()) { con_printf("No RTC detected at I2C address 0x%02x\n", RTC_ADDR); i2c_send(0xff, I2C_P); return -1; } return 0; } static void con_time(const char *what, struct tms tms) { con_printf("RTC time %s: %04u-%02u-%02u %02u:%02u:%02u\n", what, tms.tm_year + 1980, tms.tm_mon, tms.tm_mday, tms.tm_hour, tms.tm_min, tms_sec(tms)); } void read_rtc(void) { uint8_t rtc_regs[RTC_TIME_REGS]; int i; struct tms tms; if (i2c_start_rtc()) return; i2c_send(0, I2C_SR); /* Starting register */ i2c_send(RTC_RCMD, 0); for (i = 0; i < RTC_TIME_REGS-1; i++) rtc_regs[i] = i2c_recv(0); rtc_regs[i] = i2c_recv(I2C_NAK | I2C_P); /* Convert to struct tms and set systime */ memset(&tms, 0, sizeof tms); unsigned int sec = unbcd(rtc_regs[0]); tms.tm_2sec = sec >> 1; tms.hold.tm_1sec = sec & 1; tms.hold.tm_tick = 0x4000; /* Without more info, assume mid-second */ tms.tm_min = unbcd(rtc_regs[1]); unsigned int hour; if (rtc_regs[2] & 0x40) { /* AM/PM mode - this shouldn't happen */ hour = unbcd(rtc_regs[2] & 0x1f); if (hour > 11) hour -= 12; if (rtc_regs[2] & 0x20) hour += 12; } else { /* 24-hour mode */ hour = unbcd(rtc_regs[2]); } tms.tm_hour = hour; tms.tm_mday = unbcd(rtc_regs[4]); tms.tm_mon = unbcd(rtc_regs[5] & 0x1f); tms.tm_year = unbcd(rtc_regs[6]) + (rtc_regs[5] & 0x80 ? 100 : 0) + 20; set_systime(tms); #ifdef TEST con_printf("RTC register content:\n"); for (i = 0; i < RTC_REGS; i++) con_printf(" %02x", rtc_regs[i]); #endif con_time("read", tms); } volatile bool do_write_rtc; void write_rtc(void) { uint8_t rtc_regs[RTC_TIME_REGS]; int i; struct tms tms; int sda_retry_count = 16; tms = get_systime(); rtc_regs[0] = tobcd(tms_sec(tms)); rtc_regs[1] = tobcd(tms.tm_min); rtc_regs[2] = tobcd(tms.tm_hour); rtc_regs[3] = 0; /* Day of week, unused */ rtc_regs[4] = tobcd(tms.tm_mday); rtc_regs[5] = tobcd(tms.tm_mon); rtc_regs[6] = tobcd(tms.tm_year - 20); if (i2c_start_rtc()) return; i2c_send(0, 0); /* Starting register */ for (i = 0; i < RTC_TIME_REGS-1; i++) i2c_send(rtc_regs[i], 0); i2c_send(rtc_regs[i], I2C_P); con_time("set", tms); do_write_rtc = false; }