diff options
Diffstat (limited to 'drivers/rtc/pcf85063.c')
| -rw-r--r-- | drivers/rtc/pcf85063.c | 134 |
1 files changed, 126 insertions, 8 deletions
diff --git a/drivers/rtc/pcf85063.c b/drivers/rtc/pcf85063.c index 737d4547aca..421c42c0b81 100644 --- a/drivers/rtc/pcf85063.c +++ b/drivers/rtc/pcf85063.c @@ -11,11 +11,33 @@ #include <dm/device_compat.h> #define PCF85063_REG_CTRL1 0x00 /* status */ -#define PCF85063_REG_CTRL1_SR 0x58 +#define PCF85063_REG_CTRL1_CAP_SEL BIT(0) +#define PCF85063_REG_CTRL1_STOP BIT(5) +#define PCF85063_REG_CTRL1_EXT_TEST BIT(7) +#define PCF85063_REG_CTRL1_SWR 0x58 /* Software reset command */ + +#define PCF85063_REG_CTRL2 0x01 +#define PCF85063_CTRL2_AF BIT(6) +#define PCF85063_CTRL2_AIE BIT(7) + +#define PCF85063_REG_OFFSET 0x02 +#define PCF85063_OFFSET_SIGN_BIT 6 /* 2's complement sign bit */ +#define PCF85063_OFFSET_MODE BIT(7) +#define PCF85063_OFFSET_STEP0 4340 +#define PCF85063_OFFSET_STEP1 4069 + +#define PCF85063_REG_CLKO_F_MASK 0x07 /* frequency mask */ +#define PCF85063_REG_CLKO_F_32768HZ 0x00 +#define PCF85063_REG_CLKO_F_OFF 0x07 + +#define PCF85063_REG_RAM 0x03 #define PCF85063_REG_SC 0x04 /* datetime */ #define PCF85063_REG_SC_OS 0x80 +#define PCF85063_REG_ALM_S 0x0b +#define PCF85063_AEN BIT(7) + static int pcf85063_get_time(struct udevice *dev, struct rtc_time *tm) { u8 regs[7]; @@ -35,7 +57,9 @@ static int pcf85063_get_time(struct udevice *dev, struct rtc_time *tm) tm->tm_hour = bcd2bin(regs[2] & 0x3f); tm->tm_mday = bcd2bin(regs[3] & 0x3f); tm->tm_wday = regs[4] & 0x07; - tm->tm_mon = bcd2bin(regs[5] & 0x1f) - 1; + /* rtc register and rtc_time spec uses 1 - 12 */ + tm->tm_mon = bcd2bin(regs[5] & 0x1f); + /* adjust rtc_time (years since 0) to match register spec */ tm->tm_year = bcd2bin(regs[6]) + 2000; return 0; @@ -44,40 +68,102 @@ static int pcf85063_get_time(struct udevice *dev, struct rtc_time *tm) static int pcf85063_set_time(struct udevice *dev, const struct rtc_time *tm) { u8 regs[7]; + int rc; if (tm->tm_year < 2000 || tm->tm_year > 2099) { dev_err(dev, "Year must be between 2000 and 2099.\n"); return -EINVAL; } - regs[0] = bin2bcd(tm->tm_sec); + /* + * to accurately set the time, reset the divider chain and keep it in + * reset state until all time/date registers are written + */ + rc = dm_i2c_reg_clrset(dev, PCF85063_REG_CTRL1, + PCF85063_REG_CTRL1_EXT_TEST | + PCF85063_REG_CTRL1_STOP, + PCF85063_REG_CTRL1_STOP); + + if (rc) + return rc; + + /* hours, minutes and seconds */ + regs[0] = bin2bcd(tm->tm_sec) & (~PCF85063_REG_SC_OS); + regs[1] = bin2bcd(tm->tm_min); regs[2] = bin2bcd(tm->tm_hour); + + /* Day of month, 1 - 31 */ regs[3] = bin2bcd(tm->tm_mday); - regs[4] = tm->tm_wday; - regs[5] = bin2bcd(tm->tm_mon + 1); + + /* Day of week 0 - 6 */ + regs[4] = tm->tm_wday & 0x07; + + /* rtc register and rtc_time spec uses 1 - 12 */ + regs[5] = bin2bcd(tm->tm_mon); + /* adjust register to match rtc_time spec */ regs[6] = bin2bcd(tm->tm_year % 100); - return dm_i2c_write(dev, PCF85063_REG_SC, regs, sizeof(regs)); + rc = dm_i2c_write(dev, PCF85063_REG_SC, regs, sizeof(regs)); + if (rc) + return rc; + + /* + * Write the control register as a separate action since the size of + * the register space is different between the PCF85063TP and + * PCF85063A devices. The rollover point can not be used. + */ + return dm_i2c_reg_clrset(dev, PCF85063_REG_CTRL1, + PCF85063_REG_CTRL1_STOP, 0); } static int pcf85063_reset(struct udevice *dev) { - return dm_i2c_reg_write(dev, PCF85063_REG_CTRL1, PCF85063_REG_CTRL1_SR); + return dm_i2c_reg_write(dev, PCF85063_REG_CTRL1, PCF85063_REG_CTRL1_SWR); } static int pcf85063_read(struct udevice *dev, unsigned int offset, u8 *buf, unsigned int len) { + if (offset + len > dev->driver_data) + return -EINVAL; + return dm_i2c_read(dev, offset, buf, len); } static int pcf85063_write(struct udevice *dev, unsigned int offset, const u8 *buf, unsigned int len) { + if (offset + len > dev->driver_data) + return -EINVAL; + return dm_i2c_write(dev, offset, buf, len); } +static int pcf85063_load_capacitance(struct udevice *dev) +{ + u32 load = 7000; + u8 reg = 0; + + if (ofnode_read_u32(dev_ofnode(dev), "quartz-load-femtofarads", &load)) + return 0; + + switch (load) { + default: + dev_warn(dev, "Unknown quartz-load-femtofarads value: %d. Assuming 7000", + load); + fallthrough; + case 7000: + break; + case 12500: + reg = PCF85063_REG_CTRL1_CAP_SEL; + break; + } + + return dm_i2c_reg_clrset(dev, PCF85063_REG_CTRL1, + PCF85063_REG_CTRL1_CAP_SEL, reg); +} + static const struct rtc_ops pcf85063_rtc_ops = { .get = pcf85063_get_time, .set = pcf85063_set_time, @@ -88,13 +174,45 @@ static const struct rtc_ops pcf85063_rtc_ops = { static int pcf85063_probe(struct udevice *dev) { + u8 tmp; + int err; + i2c_set_chip_flags(dev, DM_I2C_CHIP_RD_ADDRESS | DM_I2C_CHIP_WR_ADDRESS); + err = dm_i2c_read(dev, PCF85063_REG_SC, &tmp, sizeof(tmp)); + if (err) { + dev_err(dev, "RTC chip is not present\n"); + return err; + } + + /* + * If a Power loss is detected, SW reset the device. + * From PCF85063A datasheet: + * There is a low probability that some devices will have corruption + * of the registers after the automatic power-on reset... + */ + if (tmp & PCF85063_REG_SC_OS) { + dev_warn(dev, "POR issue detected, sending a SW reset\n"); + err = dm_i2c_reg_clrset(dev, PCF85063_REG_CTRL1, + 0xff, PCF85063_REG_CTRL1_SWR); + if (err < 0) + dev_warn(dev, "SW reset failed, trying to continue\n"); + } + + err = pcf85063_load_capacitance(dev); + if (err < 0) + dev_warn(dev, "failed to set xtal load capacitance: %d", + err); + return 0; } static const struct udevice_id pcf85063_of_id[] = { - { .compatible = "microcrystal,rv8263" }, + { .compatible = "microcrystal,rv8263", .data = 0x12 }, + { .compatible = "nxp,pcf85063", .data = 0xb }, + { .compatible = "nxp,pcf85063a", .data = 0x12 }, + { .compatible = "nxp,pcf85063tp", .data = 0xb }, + { .compatible = "nxp,pcf85073a", .data = 0x12 }, { } }; |
