Hello Carlos, On 03/11/2023 09:51:05-0300, Carlos Menin wrote: > +struct pcf85053a { > + struct rtc_device *rtc; > + struct regmap *regmap; > + struct regmap *regmap_nvmem; > +}; > + > +struct pcf85053a_config { > + struct regmap_config regmap; > + struct regmap_config regmap_nvmem; > +}; > + > +static int pcf85053a_read_offset(struct device *dev, long *offset) > +{ > + struct pcf85053a *pcf85053a = dev_get_drvdata(dev); > + long val; > + u32 reg_offset, reg_oscillator; > + int ret; > + > + ret = regmap_read(pcf85053a->regmap, REG_OFFSET, ®_offset); > + if (ret) > + return -EIO; Why do you change the error returned by regmap? > + > + ret = regmap_read(pcf85053a->regmap, REG_OSCILLATOR, ®_oscillator); > + if (ret) > + return -EIO; > + > + val = sign_extend32(reg_offset, 7); > + > + if (reg_oscillator & REG_OSC_OFFM) > + *offset = val * OFFSET_STEP1; > + else > + *offset = val * OFFSET_STEP0; > + > + return 0; > +} > + > +static int pcf85053a_set_offset(struct device *dev, long offset) > +{ > + struct pcf85053a *pcf85053a = dev_get_drvdata(dev); > + s8 mode0, mode1, reg_offset; > + unsigned int ret, error0, error1; > + > + if (offset > OFFSET_STEP0 * 127) > + return -ERANGE; > + if (offset < OFFSET_STEP0 * -128) > + return -ERANGE; > + > + ret = regmap_set_bits(pcf85053a->regmap, REG_ACCESS, REG_ACCESS_XCLK); > + if (ret) > + return -EIO; > + > + mode0 = DIV_ROUND_CLOSEST(offset, OFFSET_STEP0); > + mode1 = DIV_ROUND_CLOSEST(offset, OFFSET_STEP1); > + > + error0 = abs(offset - (mode0 * OFFSET_STEP0)); > + error1 = abs(offset - (mode1 * OFFSET_STEP1)); > + if (error0 < error1) { > + reg_offset = mode0; > + ret = regmap_clear_bits(pcf85053a->regmap, REG_OSCILLATOR, > + REG_OSC_OFFM); > + } else { > + reg_offset = mode1; > + ret = regmap_set_bits(pcf85053a->regmap, REG_OSCILLATOR, > + REG_OSC_OFFM); > + } > + if (ret) > + return -EIO; > + > + ret = regmap_write(pcf85053a->regmap, REG_OFFSET, reg_offset); > + > + return ret; > +} > + > +static int pcf85053a_rtc_check_reliability(struct device *dev, u8 status_reg) > +{ > + int ret = 0; > + > + if (status_reg & REG_STATUS_CIF) { > + dev_warn(dev, "tamper detected," > + " date/time is not reliable\n"); You should not split strings. Also, I don't think most of the messages are actually useful as the end user doesn't have any specific action after seeing it. You should probably drop them. I don't think CIF means the time is not correct anymore. > + ret = -EINVAL; > + } > + > + if (status_reg & REG_STATUS_OF) { > + dev_warn(dev, "oscillator fail detected," > + " date/time is not reliable.\n"); > + ret = -EINVAL; > + } > + > + if (status_reg & REG_STATUS_RTCF) { > + dev_warn(dev, "power loss detected," > + " date/time is not reliable.\n"); > + ret = -EINVAL; > + } > + > + return ret; > +} > + > +static int pcf85053a_rtc_read_time(struct device *dev, struct rtc_time *tm) > +{ > + struct pcf85053a *pcf85053a = dev_get_drvdata(dev); > + u8 buf[REG_STATUS + 1]; > + int ret, len = sizeof(buf); > + > + ret = regmap_bulk_read(pcf85053a->regmap, REG_SECS, buf, len); > + if (ret) { > + dev_err(dev, "%s: error %d\n", __func__, ret); > + return ret; > + } > + > + ret = pcf85053a_rtc_check_reliability(dev, buf[REG_STATUS]); > + if (ret) > + return ret; > + > + tm->tm_year = buf[REG_YEARS]; > + /* adjust for 1900 base of rtc_time */ > + tm->tm_year += 100; > + > + tm->tm_wday = (buf[REG_WEEKDAYS] - 1) & 7; /* 1 - 7 */ > + tm->tm_sec = buf[REG_SECS]; > + tm->tm_min = buf[REG_MINUTES]; > + tm->tm_hour = buf[REG_HOURS]; > + tm->tm_mday = buf[REG_DAYS]; > + tm->tm_mon = buf[REG_MONTHS] - 1; /* 1 - 12 */ Those comments are not useful. > + > + return 0; > +} > + > +static ssize_t attr_flag_clear(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count, > + u8 reg, u8 flag) > +{ > + struct pcf85053a *pcf85053a = dev_get_drvdata(dev->parent); > + int ret; > + > + (void)attr; > + (void)buf; > + (void)count; > + > + ret = regmap_clear_bits(pcf85053a->regmap, reg, flag); > + if (ret) > + return -EIO; > + > + return count; > +} > + > +static ssize_t attr_flag_read(struct device *dev, > + struct device_attribute *attr, > + char *buf, > + u8 reg, u8 flag) > +{ > + struct pcf85053a *pcf85053a = dev_get_drvdata(dev->parent); > + unsigned int status, val; > + int ret; > + > + (void)attr; > + ret = regmap_read(pcf85053a->regmap, reg, &status); > + if (ret) > + return -EIO; > + > + val = (status & flag) != 0; > + > + return sprintf(buf, "%u\n", val); > +} > + > +/* flags that can be read or written to be cleared */ > +#define PCF85053A_ATTR_FLAG_RWC(name, reg, flag) \ > + static ssize_t name ## _store( \ > + struct device *dev, \ > + struct device_attribute *attr, \ > + const char *buf, \ > + size_t count) \ > + { \ > + return attr_flag_clear(dev, attr, buf, count, \ > + REG_ ## reg, REG_ ## reg ## _ ## flag); \ > + } \ > + static ssize_t name ## _show( \ > + struct device *dev, \ > + struct device_attribute *attr, \ > + char *buf) \ > + { \ > + return attr_flag_read(dev, attr, buf, \ > + REG_ ## reg, REG_ ## reg ## _ ## flag); \ > + } \ > + static DEVICE_ATTR_RW(name) > + > +PCF85053A_ATTR_FLAG_RWC(rtc_fail, STATUS, RTCF); > +PCF85053A_ATTR_FLAG_RWC(oscillator_fail, STATUS, OF); > +PCF85053A_ATTR_FLAG_RWC(rtc_clear, STATUS, CIF); > + > +static struct attribute *pcf85053a_attrs_flags[] = { > + &dev_attr_rtc_fail.attr, > + &dev_attr_oscillator_fail.attr, > + &dev_attr_rtc_clear.attr, > + 0, > +}; Don't add undocumented sysfs files. Also, You must not allow userspace to clear those flags without setting the time properly. > +static void pcf85053a_set_drive_control(struct device *dev, u8 *reg_ctrl) > +{ > + int ret; > + const char *val; > + u8 regval; > + > + ret = of_property_read_string(dev->of_node, "nxp,quartz-drive-control", > + &val); This property should rather be "nxp,quartz-drive". > + if (ret) { > + dev_warn(dev, "failed to read nxp,quartz-drive-control property," > + " assuming 'normal' drive"); > + val = "normal"; > + } > + > + if (!strcmp(val, "normal")) { > + regval = 0; > + } else if (!strcmp(val, "low")) { > + regval = 1; > + } else if (!strcmp(val, "high")) { > + regval = 2; > + } else { > + dev_warn(dev, "invalid nxp,quartz-drive-control value: %s," > + " assuming 'normal' drive", val); > + regval = 0; > + } > + > + *reg_ctrl |= (regval << 2); 2 needs a define, what about using FIELD_PREP? > +} > + > +static void pcf85053a_set_low_jitter(struct device *dev, u8 *reg_ctrl) > +{ > + bool val; > + u8 regval; > + > + val = of_property_read_bool(dev->of_node, "nxp,low-jitter-mode"); Bool properties don't work well with RTC because with this, there is now way to enable the normal mode. > + > + regval = val ? 1 : 0; > + *reg_ctrl |= (regval << 4); 4 also needs a define > +} > + > +static void pcf85053a_set_clk_inverted(struct device *dev, u8 *reg_ctrl) > +{ > + bool val; > + u8 regval; > + > + val = of_property_read_bool(dev->of_node, "nxp,clk-inverted"); > + > + regval = val ? 1 : 0; > + *reg_ctrl |= (regval << 7); Ditto > +} > + > +static int pcf85053a_probe(struct i2c_client *client) > +{ > + int ret; > + struct pcf85053a *pcf85053a; > + const struct pcf85053a_config *config = &pcf85053a_config; > + u8 reg_ctrl; > + > + pcf85053a = devm_kzalloc(&client->dev, sizeof(*pcf85053a), GFP_KERNEL); > + if (!pcf85053a) { > + dev_err(&client->dev, "failed to allocate device: no memory"); > + return -ENOMEM; > + } > + > + pcf85053a->regmap = devm_regmap_init_i2c(client, &config->regmap); > + if (IS_ERR(pcf85053a->regmap)) { > + dev_err(&client->dev, "failed to allocate regmap: %ld\n", > + PTR_ERR(pcf85053a->regmap)); > + return PTR_ERR(pcf85053a->regmap); > + } > + > + i2c_set_clientdata(client, pcf85053a); > + > + pcf85053a->rtc = devm_rtc_allocate_device(&client->dev); > + if (IS_ERR(pcf85053a->rtc)) { > + dev_err(&client->dev, "failed to allocate rtc: %ld\n", > + PTR_ERR(pcf85053a->rtc)); > + return PTR_ERR(pcf85053a->rtc); > + } > + > + pcf85053a->rtc->ops = &pcf85053a_rtc_ops; > + pcf85053a->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; > + pcf85053a->rtc->range_max = RTC_TIMESTAMP_END_2099; > + > + reg_ctrl = REG_CTRL_DM | REG_CTRL_HF | REG_CTRL_CIE; CIE enables an interrupt but you never use interrupts. > + pcf85053a_set_load_capacitance(&client->dev, ®_ctrl); > + pcf85053a_set_drive_control(&client->dev, ®_ctrl); > + pcf85053a_set_low_jitter(&client->dev, ®_ctrl); > + pcf85053a_set_clk_inverted(&client->dev, ®_ctrl); > + > + ret = regmap_write(pcf85053a->regmap, REG_CTRL, reg_ctrl); > + if (ret) { > + dev_err(&client->dev, "failed to configure rtc: %d\n", ret); > + return ret; > + } > + > + ret = rtc_add_group(pcf85053a->rtc, &pcf85053a_attr_group); > + if (ret) { > + dev_err(&client->dev, "failed to add sysfs entry: %d\n", ret); > + return ret; > + } > + > + ret = devm_rtc_register_device(pcf85053a->rtc); > + if (ret) { > + dev_err(&client->dev, "failed to register rtc: %d\n", ret); > + return ret; > + } > + > + ret = pcf85053a_add_nvmem(client, pcf85053a); > + if (ret) { > + dev_err(&client->dev, "failed to register nvmem: %d\n", ret); > + return ret; probe must not fail after devm_rtc_register_device > + } > + > + ret = pcf85053a_hwmon_register(&client->dev, client->name); > + if (ret) > + dev_err(&client->dev, "failed to register hwmon: %d\n", ret); > + > + return ret; > +} > + > +static const __maybe_unused struct of_device_id dev_ids[] = { > + { .compatible = "nxp,pcf85053a", .data = &pcf85053a_config }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, dev_ids); > + > +static struct i2c_driver pcf85053a_driver = { > + .driver = { > + .name = "pcf85053a", > + .of_match_table = of_match_ptr(dev_ids), > + }, > + .probe_new = &pcf85053a_probe, > +}; > + > +module_i2c_driver(pcf85053a_driver); > + > +MODULE_AUTHOR("Carlos Menin <menin@xxxxxxxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("PCF85053A I2C RTC driver"); > +MODULE_LICENSE("GPL"); > -- > 2.34.1 > -- Alexandre Belloni, co-owner and COO, Bootlin Embedded Linux and Kernel engineering https://bootlin.com