Re: [PATCH v2 1/2] rtc: add pcf85053a

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Nov 03, 2023 at 03:28:22PM +0100, Alexandre Belloni wrote:
> 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, &reg_offset);
> > +	if (ret)
> > +		return -EIO;
> 
> Why do you change the error returned by regmap?
> 

I am not sure why I did that, should just return ret. Thanks for noting.

> > +
> > +	ret = regmap_read(pcf85053a->regmap, REG_OSCILLATOR, &reg_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.
> 

About the usefullness of the message, do this apply to this CIF related
message or also to the other two flags?

> I don't think CIF means the time is not correct anymore.
> 

You are right, it only clears the SRAM. I will remove this flag from
this check.

> > +		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.
> 

I Will improve them to explain why I am offsetting the register value.

> > +
> > +	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.
> 

Should the flags be cleared only by setting the time, or is there
another acceptable method? What would be the correct way to let
userspace read those flags?

> > +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.
> 

Wouldn't the absence of the property enable normal mode? Or I am missing
something?

> > +
> > +	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, &reg_ctrl);
> > +	pcf85053a_set_drive_control(&client->dev, &reg_ctrl);
> > +	pcf85053a_set_low_jitter(&client->dev, &reg_ctrl);
> > +	pcf85053a_set_clk_inverted(&client->dev, &reg_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

Thanks for reviewing! I will implement changes to improve all noted
points in the next version of the patch.

Carlos




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux