On Sat, 23 Dec 2023 11:46:14 +0100
Javier Carrasco <[email protected]> wrote:
> The AMS AS7331 is a UV light sensor with three channels: UVA, UVB and
> UVC (also known as deep UV and referenced as DUV in the iio core).
> Its internal structure and forming blocks are practically identical to
> the ones the AS73211 contains: API, internal DAC, I2C interface and
> registers, measurement modes, number of channels and pinout.
>
> The only difference between them is the photodiodes used to acquire
> light, which means that only some modifications are required to add
> support for the AS7331 in the existing driver.
>
> The temperature channel is identical for both devices and only the
> channel modifiers of the IIO_INTENSITY channels need to account for the
> device type.
>
> The scale values have been obtained from the chapter "7.5 Transfer
> Function" of the official datasheet[1] for the configuration chosen as
> basis (Nclk = 1024 and GAIN = 1). Those values keep the units from the
> datasheet (nW/cm^2) because no additional upscaling is required to work
> with integers as opposed to the scale values for the AS73211. Actually
> if the same upscaling is used, their values will not fit in 4-byte
> integers without affecting its sign.
>
> Instead, the AS7331-specific function to retrieve the intensity scales
> returns decimal values as listed in the datasheet for every
> combination of GAIN and Nclk, keeping the unit as nW/cm^2.
> To achieve that, a fractional value is returned.
> The AS73211 scales use nW/m^2 units to work with integers that fit in
> a 4-byte integer, and in that case there is no need to modify the value
> type.
No need, but in general it's a nice to have if it works well with
IIO_VAL_FRACTIONAL because in kernel users (there aren't really any
for light sensors) can handle the maths better if they need to apply
other scalings etc.
>
> Add a new device-specific data structure to account for the device
> differences: channel types and scale of LSB per channel.
A may not be worth doing it in this case, but usual approach to refactoring
a driver to allow support of additional devices is to do it in two steps.
1) Refactor with no new support - so should be no operational changes.
2) Add the new device support.
>
> [1] https://ams.com/documents/20143/9106314/AS7331_DS001047_4-00.pdf
>
> Signed-off-by: Javier Carrasco <[email protected]>
...
> diff --git a/drivers/iio/light/as73211.c b/drivers/iio/light/as73211.c
> index ec97a3a46839..d53a0ae5255a 100644
> --- a/drivers/iio/light/as73211.c
> +++ b/drivers/iio/light/as73211.c
...
>
> +static const struct iio_chan_spec as7331_channels[] = {
> + {
> + .type = IIO_TEMP,
> + .info_mask_separate =
> + BIT(IIO_CHAN_INFO_RAW) |
> + BIT(IIO_CHAN_INFO_OFFSET) |
> + BIT(IIO_CHAN_INFO_SCALE),
> + .address = AS73211_OUT_TEMP,
> + .scan_index = AS73211_SCAN_INDEX_TEMP,
> + .scan_type = {
> + .sign = 'u',
> + .realbits = 16,
> + .storagebits = 16,
> + .endianness = IIO_LE,
> + }
> + },
> + AS73211_COLOR_CHANNEL(LIGHT_UVA, AS73211_SCAN_INDEX_X, AS73211_OUT_MRES1),
> + AS73211_COLOR_CHANNEL(LIGHT_UVB, AS73211_SCAN_INDEX_Y, AS73211_OUT_MRES2),
> + AS73211_COLOR_CHANNEL(LIGHT_DUV, AS73211_SCAN_INDEX_Z, AS73211_OUT_MRES3),
> + IIO_CHAN_SOFT_TIMESTAMP(AS73211_SCAN_INDEX_TS),
> +};
> +
> static unsigned int as73211_integration_time_1024cyc(struct as73211_data *data)
> {
> /*
> @@ -316,6 +361,50 @@ static int as73211_req_data(struct as73211_data *data)
> return 0;
> }
>
> +static int as73211_intensity_scale(struct as73211_data *data, int chan, int *val, int *val2)
> +{
> + unsigned int scale;
> +
> + switch (chan) {
> + case IIO_MOD_X:
> + scale = AS73211_SCALE_X;
> + break;
> + case IIO_MOD_Y:
> + scale = AS73211_SCALE_Y;
> + break;
> + case IIO_MOD_Z:
> + scale = AS73211_SCALE_Z;
> + break;
> + default:
> + return -EINVAL;
> + }
> + scale /= as73211_gain(data);
> + scale /= as73211_integration_time_1024cyc(data);
> + *val = scale;
> +
> + return IIO_VAL_INT;
Obviously it's really a question about the original code but why not
use IIO_VAL_FRACTIONAL here as well as below? Superficially looks
like it should work in a similar fashion.
If not, perhaps a comment here somewhere?
> +}
> +
> +static int as7331_intensity_scale(struct as73211_data *data, int chan, int *val, int *val2)
> +{
> + switch (chan) {
> + case IIO_MOD_LIGHT_UVA:
> + *val = AS7331_SCALE_UVA;
> + break;
> + case IIO_MOD_LIGHT_UVB:
> + *val = AS7331_SCALE_UVB;
> + break;
> + case IIO_MOD_LIGHT_DUV:
> + *val = AS7331_SCALE_UVC;
> + break;
> + default:
> + return -EINVAL;
> + }
> + *val2 = as73211_gain(data) * as73211_integration_time_1024cyc(data);
> +
> + return IIO_VAL_FRACTIONAL;
> +}
> +
> static int as73211_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
> int *val, int *val2, long mask)
> {
> @@ -355,30 +444,12 @@ static int as73211_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec cons
> *val2 = AS73211_SCALE_TEMP_MICRO;
> return IIO_VAL_INT_PLUS_MICRO;
>
> - case IIO_INTENSITY: {
> - unsigned int scale;
> -
> - switch (chan->channel2) {
> - case IIO_MOD_X:
> - scale = AS73211_SCALE_X;
> - break;
> - case IIO_MOD_Y:
> - scale = AS73211_SCALE_Y;
> - break;
> - case IIO_MOD_Z:
> - scale = AS73211_SCALE_Z;
> - break;
> - default:
> - return -EINVAL;
> - }
> - scale /= as73211_gain(data);
> - scale /= as73211_integration_time_1024cyc(data);
> - *val = scale;
> - return IIO_VAL_INT;
> + case IIO_INTENSITY:
> + return data->spec_dev->intensity_scale(data, chan->channel2, val, val2);
Where it doesn't hurt readability, I'd prefer we stayed as close to 80 chars or below
as reasonably possible. So here wrap so val, val2); is on the next line.
>
> default:
> return -EINVAL;
> - }}
> + }
>
> case IIO_CHAN_INFO_SAMP_FREQ:
> /* f_samp is configured in CREG3 in powers of 2 (x 1.024 MHz) */
> @@ -676,13 +747,20 @@ static int as73211_probe(struct i2c_client *client)
> i2c_set_clientdata(client, indio_dev);
> data->client = client;
>
> + if (dev_fwnode(dev))
> + data->spec_dev = device_get_match_data(dev);
> + else
> + data->spec_dev = i2c_get_match_data(client);
Take a look at how i2c_get_match_data() is defined...
https://elixir.bootlin.com/linux/latest/source/drivers/i2c/i2c-core-base.c#L117
and in particular what it calls first..
> + if (!data->spec_dev)
> + return -EINVAL;
> +
> mutex_init(&data->mutex);
> init_completion(&data->completion);
>
> indio_dev->info = &as73211_info;
> indio_dev->name = AS73211_DRV_NAME;
> - indio_dev->channels = as73211_channels;
> - indio_dev->num_channels = ARRAY_SIZE(as73211_channels);
> + indio_dev->channels = data->spec_dev->channel;
> + indio_dev->num_channels = data->spec_dev->num_channels;
> indio_dev->modes = INDIO_DIRECT_MODE;
>
On 26.12.23 17:14, Jonathan Cameron wrote:
>> Add a new device-specific data structure to account for the device
>> differences: channel types and scale of LSB per channel.
> A may not be worth doing it in this case, but usual approach to refactoring
> a driver to allow support of additional devices is to do it in two steps.
> 1) Refactor with no new support - so should be no operational changes.
> 2) Add the new device support.
>
I considered that in the first place, but the "refactoring" was so
simple that the modification was just adding a pointer to an empty
struct (you don't know what is chip-specific until you have another
chip) and the patch alone had no real value (otherwise it could be
applied to all drivers that only support one device, just in case).
As you said, it may not be worth it in this case, but thank you for the
clarification.
>> +static int as73211_intensity_scale(struct as73211_data *data, int chan, int *val, int *val2)
>> +{
>> + unsigned int scale;
>> +
>> + switch (chan) {
>> + case IIO_MOD_X:
>> + scale = AS73211_SCALE_X;
>> + break;
>> + case IIO_MOD_Y:
>> + scale = AS73211_SCALE_Y;
>> + break;
>> + case IIO_MOD_Z:
>> + scale = AS73211_SCALE_Z;
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> + scale /= as73211_gain(data);
>> + scale /= as73211_integration_time_1024cyc(data);
>> + *val = scale;
>> +
>> + return IIO_VAL_INT;
>
> Obviously it's really a question about the original code but why not
> use IIO_VAL_FRACTIONAL here as well as below? Superficially looks
> like it should work in a similar fashion.
>
> If not, perhaps a comment here somewhere?
>
You are right, the use of IIO_VAL_INT comes from the original
implementation. I did not modify that because the expected precision
(according to the datasheet is 3 decimal places) is guaranteed with the
use of nW/m^2 instead of nW/cm^2 (the units used in the datasheet).
I think the best approach would have been using IIO_VAL_FRACTIONAL and
the units provided in the datasheet, but changing units now could cause
problems to current users. We could still use IIO_VAL_FRACTIONAL unless
that might affect current users in any way. Otherwise I will add a
comment as suggested.
>> @@ -355,30 +444,12 @@ static int as73211_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec cons
>> *val2 = AS73211_SCALE_TEMP_MICRO;
>> return IIO_VAL_INT_PLUS_MICRO;
>>
>> - case IIO_INTENSITY: {
>> - unsigned int scale;
>> -
>> - switch (chan->channel2) {
>> - case IIO_MOD_X:
>> - scale = AS73211_SCALE_X;
>> - break;
>> - case IIO_MOD_Y:
>> - scale = AS73211_SCALE_Y;
>> - break;
>> - case IIO_MOD_Z:
>> - scale = AS73211_SCALE_Z;
>> - break;
>> - default:
>> - return -EINVAL;
>> - }
>> - scale /= as73211_gain(data);
>> - scale /= as73211_integration_time_1024cyc(data);
>> - *val = scale;
>> - return IIO_VAL_INT;
>> + case IIO_INTENSITY:
>> + return data->spec_dev->intensity_scale(data, chan->channel2, val, val2);
> Where it doesn't hurt readability, I'd prefer we stayed as close to 80 chars or below
> as reasonably possible. So here wrap so val, val2); is on the next line.
>
In order to meet the 80-char rule, three lines will be required
(wrapping val, val2 is not enough; chan->channel2 must have its own
line). It looks a bit weird, but I have nothing against it.
On the other hand, the original code did not always follow the 80-char
rule (up to 99 chars per line are used), so using two lines with a first
one of 84 chars could be an option.
>> + if (dev_fwnode(dev))
>> + data->spec_dev = device_get_match_data(dev);
>> + else
>> + data->spec_dev = i2c_get_match_data(client);
>
> Take a look at how i2c_get_match_data() is defined...
> https://elixir.bootlin.com/linux/latest/source/drivers/i2c/i2c-core-base.c#L117
> and in particular what it calls first..
>
Oops! I missed that one. I will simplify the code to a simple call to
i2c_get_match_data() and error check:
data->spec_dev = i2c_get_match_data(client);
if (!data->spec_dev)
return -EINVAL;
>
Thanks for your review and best regards,
Javier Carrasco
...
>
> >> +static int as73211_intensity_scale(struct as73211_data *data, int chan, int *val, int *val2)
> >> +{
> >> + unsigned int scale;
> >> +
> >> + switch (chan) {
> >> + case IIO_MOD_X:
> >> + scale = AS73211_SCALE_X;
> >> + break;
> >> + case IIO_MOD_Y:
> >> + scale = AS73211_SCALE_Y;
> >> + break;
> >> + case IIO_MOD_Z:
> >> + scale = AS73211_SCALE_Z;
> >> + break;
> >> + default:
> >> + return -EINVAL;
> >> + }
> >> + scale /= as73211_gain(data);
> >> + scale /= as73211_integration_time_1024cyc(data);
> >> + *val = scale;
> >> +
> >> + return IIO_VAL_INT;
> >
> > Obviously it's really a question about the original code but why not
> > use IIO_VAL_FRACTIONAL here as well as below? Superficially looks
> > like it should work in a similar fashion.
> >
> > If not, perhaps a comment here somewhere?
> >
> You are right, the use of IIO_VAL_INT comes from the original
> implementation. I did not modify that because the expected precision
> (according to the datasheet is 3 decimal places) is guaranteed with the
> use of nW/m^2 instead of nW/cm^2 (the units used in the datasheet).
>
> I think the best approach would have been using IIO_VAL_FRACTIONAL and
> the units provided in the datasheet, but changing units now could cause
> problems to current users. We could still use IIO_VAL_FRACTIONAL unless
> that might affect current users in any way. Otherwise I will add a
> comment as suggested.
It's possible we'd get slightly better precision from IIO_VAL_FRACTIONAL
as the string formatter does 64 bit maths and will print far too many
decimal places (matters for high precision ADCs where the rounding
errors are otherwise a problem).
I'd be surprised if anyone noticed as this is read only anyway.
So as far as I'm concerned switch to IIO_VAL_FRACTIONAL but keeping
the same units for this would be a good change. Perhaps doesn't belong
in this patch however.
>
> >> @@ -355,30 +444,12 @@ static int as73211_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec cons
> >> *val2 = AS73211_SCALE_TEMP_MICRO;
> >> return IIO_VAL_INT_PLUS_MICRO;
> >>
> >> - case IIO_INTENSITY: {
> >> - unsigned int scale;
> >> -
> >> - switch (chan->channel2) {
> >> - case IIO_MOD_X:
> >> - scale = AS73211_SCALE_X;
> >> - break;
> >> - case IIO_MOD_Y:
> >> - scale = AS73211_SCALE_Y;
> >> - break;
> >> - case IIO_MOD_Z:
> >> - scale = AS73211_SCALE_Z;
> >> - break;
> >> - default:
> >> - return -EINVAL;
> >> - }
> >> - scale /= as73211_gain(data);
> >> - scale /= as73211_integration_time_1024cyc(data);
> >> - *val = scale;
> >> - return IIO_VAL_INT;
> >> + case IIO_INTENSITY:
> >> + return data->spec_dev->intensity_scale(data, chan->channel2, val, val2);
> > Where it doesn't hurt readability, I'd prefer we stayed as close to 80 chars or below
> > as reasonably possible. So here wrap so val, val2); is on the next line.
> >
> In order to meet the 80-char rule, three lines will be required
> (wrapping val, val2 is not enough; chan->channel2 must have its own
> line). It looks a bit weird, but I have nothing against it.
>
> On the other hand, the original code did not always follow the 80-char
> rule (up to 99 chars per line are used), so using two lines with a first
> one of 84 chars could be an option.
Up to you. I'd be fine with 84 chars.
Jonathan