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

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

 



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?

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

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, &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




[Index of Archives]     [Linux Sound]     [ALSA Users]     [ALSA Devel]     [Linux Audio Users]     [Linux Media]     [Kernel]     [Gimp]     [Yosemite News]     [Linux Media]

  Powered by Linux