Re: [PATCH 2/2] iio: temperature: Add support for AMS AS6200

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

 



On Sun, Nov 26, 2023 at 05:57:26PM +0000, Jonathan Cameron wrote:
> On Sat, 11 Nov 2023 17:05:02 -0500
> Abdel Alkuor <alkuor@xxxxxxxxx> wrote:
> 
> > as6200 is a high accuracy temperature sensor of 0.0625C with a range
> > between -40 to 125 Celsius degrees. The driver implements the alert
> > trigger event in comparator mode where an alert would trigger
> > when n number of consecutive current temperature is above the upper
> > temp limit, and the alert is only cleared when the n number of
> > consecutive current temperature is below the lower temp limit.
> > 
> Hi Abdel,
> 
> Comments inline.  Sorry it took so long for you get a review.  I've been
> travelling + snowed under since returning.
> Seems like a very feature rich driver whilst still being nice and short :)
>
Hi Jonathan,

No worries. I figured as I'm following you on LinkedIn :-) I hope you
enjoyed your travel.

Thank you for your time and the thorough review. I'll address your
comments in v2.
> > The driver supports the following:
> > - show available sampling frequencey
> > - read/write sampling frequency
> > - read raw temperature
> > - read scaling factor
> > - read/write consective faults to trigger/clear an alert
> > - show available consecutive faults
> > - buffer trigger
> > - temperature alert event trigger
> > 
> > https://ams.com/documents/20143/36005/AS6200_DS000449_4-00.pdf
> 
> Use a formal tag in the tag block for this.
> 
> > 
> Datasheet: https://ams.com/documents/20143/36005/AS6200_DS000449_4-00.pdf
Will add it.
> > Signed-off-by: Abdel Alkuor <alkuor@xxxxxxxxx>
> 
> 
> 
> > ---
> >  drivers/iio/temperature/Kconfig  |   9 +
> >  drivers/iio/temperature/Makefile |   1 +
> >  drivers/iio/temperature/as6200.c | 507 +++++++++++++++++++++++++++++++
> >  3 files changed, 517 insertions(+)
> >  create mode 100644 drivers/iio/temperature/as6200.c
> > 
> > diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig
> > index ed384f33e0c7..f32691744952 100644
> > --- a/drivers/iio/temperature/Kconfig
> > +++ b/drivers/iio/temperature/Kconfig
> > @@ -157,5 +157,14 @@ config MAX31865
> >  
> >  	  This driver can also be build as a module. If so, the module
> >  	  will be called max31865.
> blank line.  Also should be in alphabetical order, not down the end of the file.
> 
> Appending to the end causes a lot more trouble for merging multiple series than
> alphabetical order, which is one reason we always try to keep these files in order.
> 
> > +config AS6200
> > +       tristate "AS6200 temperature sensor"
> > +       depends on I2C
> > +       help
> > +         If you say yes here you get support for AS6200
> > +         temperature sensor chip connected via I2C.
> > +
> > +         This driver can also be built as a module.  If so, the module
> > +         will be called as6200.
> >  
> >  endmenu
> > diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile
> > index dfec8c6d3019..48f647c273c1 100644
> > --- a/drivers/iio/temperature/Makefile
> > +++ b/drivers/iio/temperature/Makefile
> > @@ -17,3 +17,4 @@ obj-$(CONFIG_TMP007) += tmp007.o
> >  obj-$(CONFIG_TMP117) += tmp117.o
> >  obj-$(CONFIG_TSYS01) += tsys01.o
> >  obj-$(CONFIG_TSYS02D) += tsys02d.o
> > +obj-$(CONFIG_AS6200) += as6200.o
> Alphabetical order.
> 
Will be fixed in v2.
> > diff --git a/drivers/iio/temperature/as6200.c b/drivers/iio/temperature/as6200.c
> > new file mode 100644
> > index 000000000000..a18c5be0a229
> > --- /dev/null
> > +++ b/drivers/iio/temperature/as6200.c
> > @@ -0,0 +1,507 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Driver for AMS AS6200 Temperature sensor
> > + *
> > + * Auther: Abdel Alkuor <alkuor@xxxxxxxxx>
> Spell check comments Author.
> > + */
> > +
> > +#include <linux/i2c.h>
> 
> Alphabetical order, but it's common to have the iio/*
> headers as a separate block afterwards given this is an IIO driver.
> 
Will be addressed in v2.
> > +#include <linux/module.h>
> > +#include <linux/regmap.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/iio/iio.h>
> > +#include <linux/iio/events.h>
> > +#include <linux/iio/trigger.h>
> > +#include <linux/iio/buffer.h>
> > +#include <linux/iio/triggered_buffer.h>
> > +#include <linux/iio/trigger_consumer.h>
> > +#include <linux/iio/sysfs.h>
> > +#include <linux/device.h>
> > +#include <linux/bitfield.h>
> > +#include <linux/kstrtox.h>
> > +
> > +#define AS_TVAL_REG	0x0
> 
> Probably want a more specific prefix.
> AS6200_TVAL_REG etc
> 
Sounds good. I'll prefix AS6200 for the defines.
> > +#define AS_CONFIG_REG	0x1
> > +#define AS_TLOW_REG	0x2
> > +#define AS_THIGH_REG	0x3
> > +
> > +/* AS_CONFIG_REG */
> The register and field naming should make it clear enough which register
> these are in.  It think that is true here so you don't need the comment.
> Excess comments are just opportunities for code rot so keep them for when
> they add significant value.
> 
Will be removed in v2.
> > +#define AS_CONFIG_AL	BIT(5)
> > +#define AS_CONFIG_CR	GENMASK(7, 6)
> > +#define AS_CONFIG_SM	BIT(8)
> > +#define AS_CONFIG_IM	BIT(9)
> > +#define AS_CONFIG_POL	BIT(10)
> > +#define AS_CONFIG_CF	GENMASK(12, 11)
> > +
> > +#define AS_TEMP_SCALE		62500
> 
> This isn't a magic number, it's an actual quantity. As such, just put the
> number inline in the code rather than use a define that just makes it harder
> to review.
>
Understood. Will be removed in v2
> > +
> > +struct as6200_freq {
> > +	int val;
> > +	int val2;
> > +};
> 
> I don't mind the structure, but not sure it adds much over a simple 2d array of
> integers given the ordering of val and val2 is fairly natural anyway.
>
Cool. I'll use a 2d array instead.
> > +
> > +struct as6200 {
> > +	struct device *dev;
> > +	struct regmap *regmap;
> > +	struct mutex lock;
> All locks need a comment to explain what data they are protecting.
> 
A comment will be added in v2.
> > +	struct iio_dev *indio_dev;
> 
> You may have noticed this is almost never done in IIO drivers. It normally
> indicates a problem with the architecture.  There is a natural way to go
> from iio_dev to iio_priv() so we shouldn't nee to go the other way.
> 
This makes a lot of sense, not sure how I missed that. Will use iio_priv instead.
> If you did need to have a remove() (which you don't - see later) then
> should by the struct iio_dev * in the i2c_clientdata not the
> iio_priv() structure.
>
No need, remove() will be removed in v2 as you suggested below.
> > +
> > +	unsigned int data[3];
> > +};
> > +
> > +static const struct as6200_freq as6200_samp_freq[4] = {
> > +	{0, 250000},
> 
> Trivial: Slight preference for more spaces { 0, 2500000 },
> 
> > +	{1, 0},
> > +	{4, 0},
> > +	{8, 0},
> > +};
> > +
Will added spaces in v2.
> > +static const struct iio_event_spec as6200_temp_event[] = {
> > +	{
> > +		.type = IIO_EV_TYPE_THRESH,
> > +		.dir = IIO_EV_DIR_RISING,
> > +		.mask_separate = BIT(IIO_EV_INFO_VALUE)
> > +	},
> > +	{
> > +		.type = IIO_EV_TYPE_THRESH,
> > +		.dir = IIO_EV_DIR_FALLING,
> > +		.mask_separate = BIT(IIO_EV_INFO_VALUE)
> > +	},
> > +};
> > +
> > +static const struct iio_chan_spec as6200_temp_channels[] = {
> 
> There aren't any other channels so as6200_channels is enough.
>
Will be renamed in v2.
> > +	{
> > +		.type = IIO_TEMP,
> > +		.indexed = 0,
> That's the 'obvious' default so don't provide it.
> > +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> > +				      BIT(IIO_CHAN_INFO_SCALE) |
> > +				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
> > +		.scan_index = 0,
> 
> With .indexed =0, this isn't used so no need to specify it here.
>
Will be addressed in v2.
> > +		.scan_type = {
> > +			.sign = 's',
> > +			.realbits = 12,
> > +			.storagebits = 16,
> > +			.shift = 4,
> > +		},
> > +		.event_spec = as6200_temp_event,
> > +		.num_event_specs = ARRAY_SIZE(as6200_temp_event),
> > +	},
> > +	IIO_CHAN_SOFT_TIMESTAMP(1),
> > +};
> > +
> > +static const struct regmap_config as6200_regmap_config = {
> > +	.reg_bits = 8,
> > +	.val_bits = 16,
> > +	.max_register = 0x7F,
> > +};
> > +
> > +static int
> > +as6200_modify_config_reg(struct as6200 *as, unsigned int mask, unsigned int val)
> > +{
> > +	int ret;
> > +	unsigned int reg;
> > +
> > +	ret = regmap_read(as->regmap, AS_CONFIG_REG, &reg);
> > +	if (ret)
> > +		return ret;
> > +
> > +	reg &= ~mask;
> > +	reg |= val << (ffs(mask) - 1);
> > +
> > +	return regmap_write(as->regmap, AS_CONFIG_REG, reg);
> 
> regmap has functions to handle a read modify write cycle as simple as this.
> Use those instead of inventing your own.  regmap_update_bits() for example
> 
> They also hold the regmap lock over the whole cycle which can simplify locking
> requirements on a driver using it.
> 
Sure, I'll use regmap_update_bits instead.
> 
> > +}
> > +
> > +static int
> > +as6200_read_config_reg(struct as6200 *as, unsigned int mask, unsigned int *val)
> 
> All the masks passed to this are constant, so it would be better to just
> put something like
> 
> 	ret = regmap_read(as->regmap, AS_CONFIG_REG, &reg);
> 	if (ret)
> 		return ret;
> 
> 	x = FIELD_GET(reg, MASK);
> 
> inline and get rid of this function entirely.
>
Sounds good.
> > +{
> > +	int ret;
> > +	unsigned int reg;
> > +
> > +	ret = regmap_read(as->regmap, AS_CONFIG_REG, &reg);
> > +	if (ret)
> > +		return ret;
> > +
> > +	*val = (reg & mask) >> (ffs(mask) - 1);
> > +
> > +	return 0;
> > +}
> > +
> 
> > +
> > +static int as6200_read_event_value(struct iio_dev *indio_dev,
> > +			 const struct iio_chan_spec *chan,
> > +			 enum iio_event_type type,
> > +			 enum iio_event_direction dir,
> > +			 enum iio_event_info info,
> > +			 int *val, int *val2)
> > +{
> > +	struct as6200 *as = iio_device_get_drvdata(indio_dev);
> > +	unsigned int reg;
> > +	int ret;
> > +	unsigned int temp;
> > +
> > +	switch (dir) {
> > +	case IIO_EV_DIR_FALLING:
> > +		reg = AS_TLOW_REG;
> > +		break;
> > +	case IIO_EV_DIR_RISING:
> > +		reg = AS_THIGH_REG;
> > +		break;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	ret = regmap_read(as->regmap, reg, &temp);
> > +	if (ret)
> > +		return ret;
> > +
> > +	*val = sign_extend32(temp >> 4, 11);
> 
> That shift looks like it should be handled via a FIELD_GET()
> an appropriate mask.
>
Will add a temp mask to handle this.
> > +
> > +	return IIO_VAL_INT;
> > +}
> > +
> > +static int as6200_write_event_value(struct iio_dev *indio_dev,
> > +			  const struct iio_chan_spec *chan,
> > +			  enum iio_event_type type,
> > +			  enum iio_event_direction dir,
> > +			  enum iio_event_info info,
> > +			  int val, int val2)
> > +{
> > +	struct as6200 *as = iio_device_get_drvdata(indio_dev);
> 
> Why? You have the iio_dev so
> 	struct as6000 *as = iio_priv(indio_dev);
> 
Yup, I misunderstood the use of iio_device_get_drvdata. Will use
iio_priv instead in v2.
> > +	unsigned int temp;
> > +	unsigned int reg;
> > +
> > +	/*
> > +	 * range without applying the scaling
> > +	 * factor of 0.06250
> > +	 */
> > +	if (val > 2000 || val < -640)
> > +		return -EINVAL;
> > +
> > +	temp = (val & 0xfff) << 4;
> 
> FIELD_PREP() with appropriately defined _MASK
> 
Will be addressed in v2.
> > +
> > +	switch (dir) {
> > +	case IIO_EV_DIR_FALLING:
> > +		reg = AS_TLOW_REG;
> > +		break;
> > +	case IIO_EV_DIR_RISING:
> > +		reg = AS_THIGH_REG;
> > +		break;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	return regmap_write(as->regmap, reg, temp);
> > +}
> > +
> > +static irqreturn_t as6200_event_handler(int irq, void *private)
> > +{
> > +	struct iio_dev *indio_dev = private;
> > +	struct as6200 *as = iio_device_get_drvdata(indio_dev);
> As above.
> > +	unsigned int alert;
> > +	enum iio_event_direction dir;
> > +	int ret;
> > +
> > +	mutex_lock(&as->lock);
> 	guard(mutex)(&as->lock);
> 
> then you don't need to do any manual unlocking.
> 
This is pretty cool. I've never known this exists. Now, I know
and I'll use it :)
> > +
> > +	ret = as6200_read_config_reg(as, AS_CONFIG_AL, &alert);
> > +	if (ret) {
> > +		mutex_unlock(&as->lock);
> > +		return IRQ_NONE;
> > +	}
> > +
> > +	dir = alert ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING;
> > +
> > +	iio_push_event(indio_dev,
> > +		       IIO_EVENT_CODE(IIO_TEMP, 0, 0,
> > +				      dir,
> > +				      IIO_EV_TYPE_THRESH,
> > +				      0, 0, 0),
> > +		       iio_get_time_ns(indio_dev));
> > +
> > +	mutex_unlock(&as->lock);
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static irqreturn_t as6200_trigger_handler(int irq, void *private)
> > +{
> > +	struct iio_poll_func *pf = private;
> > +	struct iio_dev *indio_dev = pf->indio_dev;
> > +	struct as6200 *as = iio_device_get_drvdata(indio_dev);
> > +	int ret;
> > +
> > +	mutex_lock(&as->lock);
> 
> 	scoped_guard(mutex, &as->lock) {
> 		ret = regmap_read(as->regmap, AS_TVAL_REG, &as->data[0]);
> 		if (ret)
> 			break;
> 
> 		iio_push_to_buffers_with_timestamp(indio_dev, as->data,
> 						   iio_get_time_ns(indio_dev));
> 
> However, data isn't big enough. It should contain enough space for the data + 
> a naturally aligned s64 (so 16 bytes).  This interface is deeply unintuitive
> but we have been stuck with it for a long time now :(
> 
I agree, the data index for each element kinda depend on the alignment.
So from user space, a user needs to be aware of such alignment to
process each data element properly.
> Also, I'm not sure why we can't just have data on the stack and if we do,
> is there a need for the lock?
>
This is a good point. Simply, an oversight from my side. I'll remove the
lock and put the data on the stack.
> 	}
> 
> 	iio_trigger_notify_done()....
> 
> 	
> > +
> > +	ret = regmap_read(as->regmap, AS_TVAL_REG, &as->data[0]);
> > +	if (!ret)
> > +		iio_push_to_buffers_with_timestamp(indio_dev, as->data,
> > +						   iio_get_time_ns(indio_dev));
> > +
> > +	mutex_unlock(&as->lock);
> > +
> > +	iio_trigger_notify_done(indio_dev->trig);
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> 
> > +
> > +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("0.25 1 4 8");
> > +static IIO_CONST_ATTR(avail_consecutive_faults, "1 2 4 6");
> > +static IIO_DEVICE_ATTR_RW(consecutive_faults, 0);
> > +
> > +static struct attribute *as6200_event_attributes[] = {
> > +	&iio_const_attr_avail_consecutive_faults.dev_attr.attr,
> > +	&iio_dev_attr_consecutive_faults.dev_attr.attr,
> Custom ABI, so should be some documentation.
> 
> I'm guessing a bit, but sounds like the standard ABI period
> to me.
> https://elixir.bootlin.com/linux/latest/source/Documentation/ABI/testing/sysfs-bus-iio#L1163
> 
> However you will need to take into account current sampling frequency
> and provide this in seconds as per the documentation.
>
Sounds good. I'll add it under period with the proper conversions.
> It's absolutely fine to have available change with the sampling
> frequency.
> 
> > +	NULL,
> > +};
> > +
> > +static struct attribute *as6200_attributes[] = {
> > +	&iio_const_attr_sampling_frequency_available.dev_attr.attr,
> 
> For this one, use read_avail() and appropriate bitmaps.
> That both reduces manual handling of attributes and allows in kernel
> consumers to potentially access this parameter.
>
Will be addressed in v2.
> > +	NULL,
> > +};
> > +
> > +static const struct attribute_group as6200_attribute_group = {
> > +	.attrs = as6200_attributes,
> > +};
> > +
> > +static const struct attribute_group as6200_event_attribute_group = {
> > +	.attrs = as6200_event_attributes,
> > +};
> > +
> > +static const struct iio_info as6200_temp_info = {
> > +	.event_attrs = &as6200_event_attribute_group,
> > +	.attrs = &as6200_attribute_group,
> > +	.read_raw = &as6200_read_raw,
> > +	.write_raw = &as6200_write_raw,
> > +	.read_event_value = &as6200_read_event_value,
> > +	.write_event_value = &as6200_write_event_value,
> > +};
> > +
> > +static int as6200_probe(struct i2c_client *client)
> > +{
> > +	struct as6200 *as;
> > +	struct iio_dev *indio_dev;
> > +	int ret;
> > +
> > +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
> > +		return -EINVAL;
> > +
> > +	indio_dev = devm_iio_device_alloc(&client->dev, 0);
> > +	if (!indio_dev)
> > +		return -ENOMEM;
> > +
> > +	as = devm_kzalloc(&client->dev, sizeof(*as), GFP_KERNEL);
> > +	if (IS_ERR(as))
> > +		return PTR_ERR(as);
> > +
> > +	as->regmap = devm_regmap_init_i2c(client, &as6200_regmap_config);
> > +	if (IS_ERR(as->regmap))
> > +		return PTR_ERR(as->regmap);
> > +
> > +	mutex_init(&as->lock);
> > +
> > +	as->dev = &client->dev;
> > +	as->indio_dev = indio_dev;
> > +
> > +	iio_device_set_drvdata(indio_dev, as);
> > +
> > +	indio_dev->modes = INDIO_DIRECT_MODE;
> > +	indio_dev->channels = as6200_temp_channels;
> > +	indio_dev->num_channels = ARRAY_SIZE(as6200_temp_channels);
> > +	indio_dev->name = client->name;
> 
> Encode the string directly here. The different firmware types fill client->name
> in via various fragile means. Things are safer if we don't rely on that.
>
Will encode the string directly in v2.
> > +	indio_dev->info = &as6200_temp_info;
> > +
> > +	ret = devm_iio_triggered_buffer_setup(as->dev, indio_dev,
> > +					      NULL,
> > +					      as6200_trigger_handler,
> > +					      NULL);
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (client->irq) {
> > +		ret = devm_request_threaded_irq(as->dev,
> > +						client->irq,
> > +						NULL,
> > +						&as6200_event_handler,
> > +						IRQF_ONESHOT,
> > +						client->name,
> > +						indio_dev);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	i2c_set_clientdata(client, as);
> > +
> > +	return iio_device_register(indio_dev);
> 
> As only thing in remove is iio_device_unregister() use
> 	return devm_iio_device_register(indio_dev);
> and drop remove() callback entirely.
>
Sounds good.
> > +}
> > +
> > +static void as6200_remove(struct i2c_client *client)
> > +{
> > +	struct as6200 *as = i2c_get_clientdata(client);
> > +
> > +	iio_device_unregister(as->indio_dev);
> > +}
> 
> ...
> 
will be dropped in v2.
> > +
> > +static const struct dev_pm_ops as6200_pm_ops = {
> > +	SET_SYSTEM_SLEEP_PM_OPS(as6200_suspend, as6200_resume)
> > +};
> > +
> > +static const struct i2c_device_id as6200_id_table[] = {
> > +	{ "as6200" },
> > +	{ },
> As below.
> 
> > +};
> > +MODULE_DEVICE_TABLE(i2c, as6200_id_table);
> > +
> > +static const struct of_device_id as6200_of_match[] = {
> > +	{ .compatible = "ams,as6200" },
> > +	{ },
> 
> No point in a , after a 'NULL' like terminator as we will 
> never add anything after it.
>
Understood.
> > +};
> > +MODULE_DEVICE_TABLE(of, as6200_of_match);
> > +
> > +static struct i2c_driver as6200_driver = {
> > +	.driver = {
> > +		.name = "as6200",
> > +		.pm = &as6200_pm_ops,
> 		.pm = pm_sleep_ptr(&as6200_pm_ops),
> 
> see what that function is doing to understand why (all about cleaning up
> unnecessary code if the PM config doesn't require it)
> 
Will be added in v2.
> > +		.of_match_table = as6200_of_match,
> > +	},
> > +	.probe = as6200_probe,
> > +	.remove = as6200_remove,
> > +	.id_table = as6200_id_table,
> > +};
> > +module_i2c_driver(as6200_driver);
> > +
> > +MODULE_AUTHOR("Abdel Alkuor <alkuor@xxxxxxxxx");
> > +MODULE_DESCRIPTION("AMS AS6200 Temperature Sensor");
> > +MODULE_LICENSE("GPL");
> 




[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux