[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250113221905.ruv3w3k4w53hvf2b@antoni-VivoBook-ASUSLaptop-X512FAY-K512FA>
Date: Mon, 13 Jan 2025 23:19:05 +0100
From: Antoni Pokusinski <apokusinski01@...il.com>
To: Andy Shevchenko <andy.shevchenko@...il.com>
Cc: jic23@...nel.org, lars@...afoo.de, robh@...nel.org, krzk+dt@...nel.org,
conor+dt@...nel.org, andrej.skvortzov@...il.com,
neil.armstrong@...aro.org, icenowy@...c.io, megi@....cz,
danila@...xyga.com, javier.carrasco.cruz@...il.com, andy@...nel.org,
linux-kernel@...r.kernel.org, linux-iio@...r.kernel.org,
devicetree@...r.kernel.org
Subject: Re: [PATCH v3 2/2] iio: magnetometer: si7210: add driver for Si7210
Hi Andy,
Thanks for the review. I'm currently implementing some changes in the
driver according to the review, however I have some doubts regarding
removal of the `i2c_client` from `si7210_data`.
Kind regards,
Antoni
On Sun, Jan 12, 2025 at 04:18:46PM +0200, Andy Shevchenko wrote:
> On Sun, Jan 12, 2025 at 12:45 PM Antoni Pokusinski
> <apokusinski01@...il.com> wrote:
> >
> > Silicon Labs Si7210 is an I2C Hall effect magnetic position and
> > temperature sensor. The driver supports the following functionalities:
> > * reading the temperature measurements
> > * reading the magnetic field measurements in a single-shot mode
> > * choosing the magnetic field measurement scale (20 or 200 mT)
>
> ...
>
> Many header inclusions are being missed.
>
> + array_size.h
>
> > +#include <linux/bitfield.h>
>
> + bits.h (it's even mentioned in the top comment of bitfield.h)
>
> + cleanup.h
> + err.h
>
> > +#include <linux/i2c.h>
> > +#include <linux/iio/iio.h>
> > +#include <linux/math64.h>
> > +#include <linux/mutex.h>
> > +#include <linux/regmap.h>
> > +#include <linux/regulator/consumer.h>
>
> + types.h
>
>
> + asm/byteorder.h (or is it already available as linux/byteorder.h?),
> but it seems that what you actually wanted is linux/unaligned.h (see
> below why).
>
> ...
>
> > +static const unsigned int a20_otp_regs[A_REGS_COUNT] = {
> > + SI7210_OTPREG_A0_20, SI7210_OTPREG_A1_20, SI7210_OTPREG_A2_20,
> > + SI7210_OTPREG_A3_20, SI7210_OTPREG_A4_20, SI7210_OTPREG_A5_20
>
> Please, leave trailing comma(s) when it's clearly not a terminator entry.
>
> > +};
> > +
> > +static const unsigned int a200_otp_regs[A_REGS_COUNT] = {
> > + SI7210_OTPREG_A0_200, SI7210_OTPREG_A1_200, SI7210_OTPREG_A2_200,
> > + SI7210_OTPREG_A3_200, SI7210_OTPREG_A4_200, SI7210_OTPREG_A5_200
>
> Ditto.
>
> > +};
>
> ...
>
> > +struct si7210_data {
> > + struct i2c_client *client;
>
> Do we really need a room for that? Isn't it derivable from the below
> regmap? Also note the frequency of use of client vs. regmap. The
> result in the object file can be much better if regmap becomes the
> first member here. Check it (with bloat-o-meter, for example).
>
I used arm-linux-nm and the bloat-o-meter to compare the sizes and it
turned out that the version which contains the `i2c_client` has
slightly smaller size actually. Here are the results:
$ ./scripts/bloat-o-meter -p arm-linux- ./old_si7210.ko ./new_si7210.ko
add/remove: 0/0 grow/shrink: 1/0 up/down: 4/0 (4)
Function old new delta
si7210_probe 556 560 +4
Total: Before=4021, After=4025, chg +0.10%
Here is the diff (shortened for better readability) between
the old_si7210.ko (uses `si7210_data->i2c_client`) and
new_si7210.ko (does not use `si7210_data->i2c_client`):
struct si7210_data {
- struct i2c_client *client;
struct regmap *regmap;
...
static int si7210_device_wake(struct si7210_data *data)
{
+ struct device *dev = regmap_get_device(data->regmap);
int ret;
- ret = i2c_smbus_read_byte(data->client);
+ ret = i2c_smbus_read_byte(to_i2c_client(dev));
...
static int si7210_probe(struct i2c_client *client)
data = iio_priv(indio_dev);
- data->client = client;
Hence, I guess that it's actually better to leave the `i2c_client` as it is.
> > + struct regmap *regmap;
> > + struct regulator *vdd;
> > + struct mutex fetch_lock; /* lock for a single measurement fetch */
> > + s8 temp_offset;
> > + s8 temp_gain;
> > + s8 scale_20_a[A_REGS_COUNT];
> > + s8 scale_200_a[A_REGS_COUNT];
> > + u8 curr_scale;
> > +};
> > +
> > +static const struct iio_chan_spec si7210_channels[] = {
> > + {
> > + .type = IIO_MAGN,
> > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> > + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET),
> > + },
> > + {
> > + .type = IIO_TEMP,
> > + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
> > + }
>
> Leave trailing comma.
>
> > +};
> > +
> > +static int si7210_fetch_measurement(struct si7210_data *data,
> > + struct iio_chan_spec const *chan,
> > + __be16 *buf)
> > +{
> > + u8 dspsigsel = chan->type == IIO_MAGN ? 0 : 1;
> > + int ret, result;
>
> Why is the result signed? I believe even regmap APIs have it unsigned
> in the prototypes.
> Ah, it's even worse... See below.
>
> > + guard(mutex)(&data->fetch_lock);
> > +
> > + ret = regmap_update_bits(data->regmap, SI7210_REG_DSPSIGSEL,
> > + SI7210_MASK_DSPSIGSEL, dspsigsel);
> > + if (ret < 0)
> > + return ret;
> > +
> > + ret = regmap_update_bits(data->regmap, SI7210_REG_POWER_CTRL,
> > + SI7210_MASK_ONEBURST | SI7210_MASK_STOP,
> > + SI7210_MASK_ONEBURST & ~SI7210_MASK_STOP);
> > + if (ret < 0)
> > + return ret;
> > +
> > + /* Read the contents of the registers containing the result: DSPSIGM, DSPSIGL */
> > + ret = regmap_bulk_read(data->regmap, SI7210_REG_DSPSIGM, &result, 2);
>
> I stumbled over this...
>
> > + if (ret < 0)
> > + return ret;
> > +
> > + *buf = cpu_to_be16(result);
>
> ...and this piece and I think you got it wrong. What you should do is
> just supply a buf with sizeof one element.
>
> ret = ..., buf, sizeof(buf[0]));
>
> Otherwise this needs a very good comment explaining what the heck is done here.
>
> > + return 0;
> > +}
> > +
> > +static int si7210_read_raw(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan,
> > + int *val, int *val2, long mask)
> > +{
> > + struct si7210_data *data = iio_priv(indio_dev);
> > + long long temp;
> > + __be16 dspsig;
> > + int ret;
> > +
> > + switch (mask) {
> > + case IIO_CHAN_INFO_RAW:
> > + ret = si7210_fetch_measurement(data, chan, &dspsig);
>
> Oh, but why...
>
> > + if (ret < 0)
>
> ...then the ' < 0' part? What is the positive ret meaning?
>
> > + return ret;
> > +
> > + *val = dspsig & GENMASK(14, 0);
> > + return IIO_VAL_INT;
> > + case IIO_CHAN_INFO_SCALE:
> > + *val = 0;
> > + if (data->curr_scale == 20)
> > + *val2 = 1250;
> > + else /* data->curr_scale == 200 */
> > + *val2 = 12500;
> > + return IIO_VAL_INT_PLUS_MICRO;
> > + case IIO_CHAN_INFO_OFFSET:
> > + *val = -16384;
> > + return IIO_VAL_INT;
> > + case IIO_CHAN_INFO_PROCESSED:
> > + ret = si7210_fetch_measurement(data, chan, &dspsig);
> > + if (ret < 0)
> > + return ret;
> > +
> > + temp = FIELD_GET(GENMASK(14, 3), dspsig);
> > + temp = div_s64(-383 * temp * temp, 100) + 160940 * temp - 279800000;
>
> HECTO/CENTI, but I think in this case it's not needed as it is most
> likely in alignment with the datasheet.
>
> > + temp = (1 + (data->temp_gain / 2048)) * temp + (1000000 / 16) * data->temp_offset;
>
> But here MICRO? MEGA? would make sense to show the scale.
>
> > + ret = regulator_get_voltage(data->vdd);
> > + if (ret < 0)
> > + return ret;
>
> > + temp -= 222 * div_s64(ret, 1000);
>
> This is conversion from uV to mV IIUC, so replacing it with MILLI
> would make it harder to understand I suppose.
>
> > + *val = div_s64(temp, 1000);
>
> MILLI?
>
> > + return IIO_VAL_INT;
> > + default:
> > + return -EINVAL;
> > + }
> > +}
>
> ...
>
> > +static int si7210_read_otpreg_val(struct si7210_data *data, unsigned int otpreg, u8 *val)
> > +{
> > + int ret;
> > + unsigned int otpdata;
> > +
> > + ret = regmap_write(data->regmap, SI7210_REG_OTP_ADDR, otpreg);
> > + if (ret < 0)
> > + return ret;
> > +
> > + ret = regmap_update_bits(data->regmap, SI7210_REG_OTP_CTRL,
> > + SI7210_MASK_OTP_READ_EN, SI7210_MASK_OTP_READ_EN);
> > + if (ret < 0)
> > + return ret;
>
> > + ret = regmap_read(data->regmap, SI7210_REG_OTP_DATA, &otpdata);
> > + if (ret < 0)
>
> What are those ' < 0' parts for in many cases? Does it mean we ignore
> positive output? Why is it so?
>
The regmap functions return value <=0 so I decided to handle errors
in the 'if (ret < 0)' way.
But in the next version I'll change that to a simpler 'if (ret)'
wherever possible
> > + return ret;
> > +
> > + *val = (u8)otpdata;
>
> Why casting?
>
> > + return 0;
> > +}
>
> ...
>
> > + /*
> > + * According to the datasheet, the primary method to wake up a
> > + * device is to send an empty write. However this is not feasible
> > + * using current API so we use the other method i.e. read a single
>
> the current
>
> > + * byte. The device should respond with 0xFF.
> > + */
>
> > +
>
> Unneeded blank line, and TBH, the comment sounds like it should be
> rather for the entire function.
>
> > + int ret;
> > +
> > + ret = i2c_smbus_read_byte(data->client);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (ret != 0xFF)
> > + return -EIO;
> > +
> > + return 0;
>
> Btw, is this the only reason for having the client member in the
> private structure? If so, you can derive it from regmap.
>
> ...
>
> > +static int si7210_probe(struct i2c_client *client)
> > +{
> > + struct si7210_data *data;
> > + struct iio_dev *indio_dev;
> > + int ret;
> > +
> > + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
> > + if (!indio_dev)
> > + return -ENOMEM;
> > +
> > + data = iio_priv(indio_dev);
>
> > + data->client = client;
>
> (Almost) useless?
>
> > + mutex_init(&data->fetch_lock);
>
> Why not devm_mutex_init()?
>
> > + data->regmap = devm_regmap_init_i2c(client, &si7210_regmap_conf);
> > + if (IS_ERR(data->regmap))
> > + return dev_err_probe(&client->dev, PTR_ERR(data->regmap),
> > + "failed to register regmap\n");
> > +
> > + data->vdd = devm_regulator_get(&client->dev, "vdd");
> > + if (IS_ERR(data->vdd))
> > + return dev_err_probe(&client->dev, PTR_ERR(data->vdd),
> > + "failed to get VDD regulator\n");
> > +
> > + ret = regulator_enable(data->vdd);
> > + if (ret)
> > + return ret;
> > +
> > + indio_dev->name = dev_name(&client->dev);
> > + indio_dev->modes = INDIO_DIRECT_MODE;
> > + indio_dev->info = &si7210_info;
> > + indio_dev->channels = si7210_channels;
> > + indio_dev->num_channels = ARRAY_SIZE(si7210_channels);
> > +
> > + ret = si7210_device_init(data);
> > + if (ret)
> > + return dev_err_probe(&client->dev, ret,
> > + "device initialization failed\n");
> > +
> > + return devm_iio_device_register(&client->dev, indio_dev);
> > +}
>
> ...
>
> > +static struct i2c_driver si7210_driver = {
> > + .driver = {
> > + .name = "si7210",
> > + .of_match_table = si7210_dt_ids,
> > + },
> > + .probe = si7210_probe,
> > + .id_table = si7210_id,
> > +};
>
> > +
>
> Wrong place of this blank line...
>
> > +module_i2c_driver(si7210_driver);
>
> ...should be here.
>
> > +MODULE_AUTHOR("Antoni Pokusinski <apokusinski01@...il.com>");
> > +MODULE_DESCRIPTION("Silicon Labs Si7210 Hall Effect sensor I2C driver");
> > +MODULE_LICENSE("GPL");
>
> --
> With Best Regards,
> Andy Shevchenko
Powered by blists - more mailing lists