On Sun, 21 Jan 2024 15:47:34 +1030
Subhajit Ghosh <[email protected]> wrote:
> Driver support for Avago (Broadcom) APDS9306 Ambient Light Sensor.
> It has two channels - ALS and CLEAR. The ALS (Ambient Light Sensor)
> channel approximates the response of the human-eye providing direct
> read out where the output count is proportional to ambient light levels.
> It is internally temperature compensated and rejects 50Hz and 60Hz flicker
> caused by artificial light sources. Hardware interrupt configuration is
> optional. It is a low power device with 20 bit resolution and has
> configurable adaptive interrupt mode and interrupt persistence mode.
> The device also features inbuilt hardware gain, multiple integration time
> selection options and sampling frequency selection options.
>
> This driver also uses the IIO GTS (Gain Time Scale) Helpers Namespace for
> Scales, Gains and Integration time implementation.
>
> Signed-off-by: Subhajit Ghosh <[email protected]>
> ---
> v2 -> v5:
Why did you jump to v5? Some internal or private reviews perhaps?
Better for those tracking on the list if you just used v3.
> - Removed scale attribute for Intensity channel:
> Link: https://lore.kernel.org/all/20231204095108.22f89718@jic23-huawei/
>
> - Dropped caching of hardware gain, repeat rate and integration time and
> updated code as per earlier reviews.
> Link: https://lore.kernel.org/lkml/20231028142944.7e210eb6@jic23-huawei/
..
A few, mostly very minor comments inline to add to Christophe's review.
Thanks,
Jonathan
> diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c
> new file mode 100644
> index 000000000000..8ed5899050ed
> --- /dev/null
> +++ b/drivers/iio/light/apds9306.c
> @@ -0,0 +1,1315 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * APDS-9306/APDS-9306-065 Ambient Light Sensor
> + * I2C Address: 0x52
> + * Datasheet: https://docs.broadcom.com/doc/AV02-4755EN
> + *
> + * Copyright (C) 2023 Subhajit Ghosh <[email protected]>
Given you are still changing it, feel free to include 2024!
> + */
..
> +static const int apds9306_repeat_rate_freq[][2] = {
> + {40, 0},
> + {20, 0},
> + {10, 0},
> + {5, 0},
> + {2, 0},
> + {1, 0},
> + {0, 500000},
Prefer
{ 40, 0 },
etc and whilst I don't really like forcing alignment like this, if you
are going to do it be consistent. The last 50000 is one space too far to the
left I think.
> +};
> +static_assert(ARRAY_SIZE(apds9306_repeat_rate_freq) ==
> + APDS9306_NUM_REPEAT_RATES);
> +
> +static const int apds9306_repeat_rate_period[] = {
> + 25000, 50000, 100000, 200000, 500000, 1000000, 2000000,
> +};
> +static_assert(ARRAY_SIZE(apds9306_repeat_rate_period) ==
> + APDS9306_NUM_REPEAT_RATES);
> +
> +/**
> + * struct apds9306_data - apds9306 private data and registers definitions
> + *
> + * @dev: Pointer to the device structure
> + * @gts: IIO Gain Time Scale structure
> + * @mutex: Lock for protecting register access, adc reads and power
ADC. I guess the double comment is to keep checkpatch happy?
Just ignore it being dumb as you have a comment up here and put all the info
here rather than splitting it up like this.
> + * @regmap: Regmap structure pointer
> + * @regfield_sw_reset: Reg: MAIN_CTRL, Field: SW_Reset
> + * @regfield_en: Reg: MAIN_CTRL, Field: ALS_EN
> + * @regfield_intg_time: Reg: ALS_MEAS_RATE, Field: ALS Resolution/Bit Width
> + * @regfield_repeat_rate: Reg: ALS_MEAS_RATE, Field: ALS Measurement Rate
> + * @regfield_gain: Reg: ALS_GAIN, Field: ALS Gain Range
> + * @regfield_int_src: Reg: INT_CFG, Field: ALS Interrupt Source
> + * @regfield_int_thresh_var_en: Reg: INT_CFG, Field: ALS Var Interrupt Mode
> + * @regfield_int_en: Reg: INT_CFG, Field: ALS Interrupt Enable
> + * @regfield_int_persist_val: Reg: INT_PERSISTENCE, Field: ALS_PERSIST
> + * @regfield_int_thresh_var_val: Reg: ALS_THRSH_VAR, Field: ALS_THRES_VAR
> + * @nlux_per_count: nano lux per ADC count for a particular model
> + * @read_data_available: Flag set by IRQ handler for ADC data available
> + */
> +struct apds9306_data {
> + struct device *dev;
> + struct iio_gts gts;
> + /*
> + * Protects device settings changes where some calculations are required
> + * before or after setting or getting the raw settings values from regmap
> + * writes or reads respectively.
> + */
> + struct mutex mutex;
> +
> + struct regmap *regmap;
> + struct regmap_field *regfield_sw_reset;
> + struct regmap_field *regfield_en;
> + struct regmap_field *regfield_intg_time;
> + struct regmap_field *regfield_repeat_rate;
> + struct regmap_field *regfield_gain;
> + struct regmap_field *regfield_int_src;
> + struct regmap_field *regfield_int_thresh_var_en;
> + struct regmap_field *regfield_int_en;
> + struct regmap_field *regfield_int_persist_val;
> + struct regmap_field *regfield_int_thresh_var_val;
> +
> + int nlux_per_count;
> + int read_data_available;
> +};
> +
> +static struct iio_event_spec apds9306_event_spec_als[] = {
> + {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_RISING,
> + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE),
> + }, {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_FALLING,
> + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE),
> + }, {
> + .type = IIO_EV_TYPE_THRESH,
> + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD),
> + }, {
> + .type = IIO_EV_TYPE_THRESH_ADAPTIVE,
> + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) |
> + BIT(IIO_EV_INFO_ENABLE),
> + }, {
> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
What's the intent of this final entry?
The type will default to IIO_EV_TYPE_THRESH anyway but if that
the intent you should specify it. There isn't an 'obvious'
default for type in the same way there sort of is for dir
(as it's either direction).
> + },
> +};
> +
> +
> +static int apds9306_runtime_power_on(struct device *dev)
> +{
> + int ret;
> +
> + ret = pm_runtime_resume_and_get(dev);
> + if (ret < 0)
> + dev_err(dev, "runtime resume failed: %d\n", ret);
> +
> + return ret;
> +}
> +
> +static int apds9306_runtime_power_off(struct device *dev)
> +{
> + pm_runtime_mark_last_busy(dev);
> + pm_runtime_put_autosuspend(dev);
> +
> + return 0;
> +}
I'm not entirely convinced these two wrappers are worthwhile given they
aren't that common used and locally obscure what is going on when
it could be apparent at the few call sites.
> +static int apds9306_read_event_config(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir)
> +{
> + struct apds9306_data *data = iio_priv(indio_dev);
> + int int_en, int_ch, ret;
> +
> + guard(mutex)(&data->mutex);
> +
> + switch (type) {
> + case IIO_EV_TYPE_THRESH:
> + ret = regmap_field_read(data->regfield_int_src, &int_ch);
int_ch is a not particularly informative name.
event_ch_is_light perhaps?
> + if (ret)
> + return ret;
> + ret = regmap_field_read(data->regfield_int_en, &int_en);
> + if (ret)
> + return ret;
> + if (chan->type == IIO_LIGHT)
> + return int_en & int_ch;
> + else if (chan->type == IIO_INTENSITY)
> + return int_en & !int_ch;
> + return -EINVAL;
> + case IIO_EV_TYPE_THRESH_ADAPTIVE:
> + ret = regmap_field_read(data->regfield_int_thresh_var_en, &int_en);
> + if (ret)
> + return ret;
> + return int_en;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int apds9306_write_event_config(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + int state)
> +{
> + struct apds9306_data *data = iio_priv(indio_dev);
> + int ret, val;
> +
> + state = !!state;
> +
> + guard(mutex)(&data->mutex);
> +
> + switch (type) {
> + case IIO_EV_TYPE_THRESH:
> + /*
> + * If interrupt is enabled, the channel is set before enabling
> + * the interrupt. In case of disable, no need to switch
> + * channels. In case of different channel is selected while
> + * interrupt in on, just change the channel.
> + */
> + if (state) {
> + if (chan->type == IIO_LIGHT)
> + val = 1;
> + else if (chan->type == IIO_INTENSITY)
> + val = 0;
> + else
> + return -EINVAL;
Blank line here and similar.
> + ret = regmap_field_write(data->regfield_int_src, val);
> + if (ret)
> + return ret;
> + }
> + ret = regmap_field_read(data->regfield_int_en, &val);
> + if (ret)
> + return ret;
> + if (val == state)
> + return 0;
Blank line. Basically add one whenever a block of related code ends.
> + ret = regmap_field_write(data->regfield_int_en, state);
> + if (ret)
> + return ret;
> + if (state)
> + return apds9306_runtime_power_on(data->dev);
> + return apds9306_runtime_power_off(data->dev);
> + case IIO_EV_TYPE_THRESH_ADAPTIVE:
> + return regmap_field_write(data->regfield_int_thresh_var_en, state);
> + default:
> + return -EINVAL;
> + }
> +}
>
.
> +static void apds9306_powerdown(void *ptr)
> +{
> + struct apds9306_data *data = (struct apds9306_data *)ptr;
> + int ret;
> +
> + ret = regmap_field_write(data->regfield_int_thresh_var_en, 0);
> + if (ret)
> + return;
blank line here ideally.
> + ret = regmap_field_write(data->regfield_int_en, 0);
> + if (ret)
> + return;
> +
> + apds9306_power_state(data, false);
> +}
..
> +
> +static int apds9306_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct apds9306_data *data;
> + struct iio_dev *indio_dev;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + data = iio_priv(indio_dev);
> +
> + mutex_init(&data->mutex);
> +
> + data->regmap = devm_regmap_init_i2c(client, &apds9306_regmap);
> + if (IS_ERR(data->regmap))
> + return dev_err_probe(dev, PTR_ERR(data->regmap),
> + "regmap initialization failed\n");
> +
> + data->dev = dev;
> + i2c_set_clientdata(client, indio_dev);
> +
> + ret = apds9306_regfield_init(data);
> + if (ret)
> + return dev_err_probe(dev, ret, "regfield initialization failed\n");
> +
> + ret = devm_regulator_get_enable(dev, "vdd");
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to enable regulator\n");
> +
> + indio_dev->name = "apds9306";
> + indio_dev->modes = INDIO_DIRECT_MODE;
> + if (client->irq) {
> + indio_dev->info = &apds9306_info;
> + indio_dev->channels = apds9306_channels_with_events;
> + indio_dev->num_channels =
> + ARRAY_SIZE(apds9306_channels_with_events);
> + ret = devm_request_threaded_irq(dev, client->irq, NULL,
> + apds9306_irq_handler, IRQF_ONESHOT,
> + "apds9306_event", indio_dev);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to assign interrupt.\n");
> + } else {
> + indio_dev->info = &apds9306_info_no_events;
> + indio_dev->channels = apds9306_channels_without_events;
> + indio_dev->num_channels = ARRAY_SIZE(apds9306_channels_without_events);
> + }
> +
> + ret = apds9306_pm_init(data);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed pm init\n");
> +
> + ret = apds9306_device_init(data);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to init device\n");
> +
> + ret = devm_add_action_or_reset(dev, apds9306_powerdown, data);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to add action or reset\n");
> +
> + ret = devm_iio_device_register(dev, indio_dev);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed iio device registration\n");
> +
> + pm_runtime_put_autosuspend(dev);
Where is the matching get? I don't recall any of the pm functions
leaving us with the reference count raised except for the where it is
called out in the function name.
The runtime pm reference counters are protected against underflowing so this
probably just has no impact. Still good to only have it if necessary and if
you do need the power to be on until this point, force it to do so by
an appropriate pm_runtime_get().
> +
> + return 0;
> +}
On 22/1/24 01:53, Jonathan Cameron wrote:
> On Sun, 21 Jan 2024 15:47:34 +1030
> Subhajit Ghosh <[email protected]> wrote:
>
>> Driver support for Avago (Broadcom) APDS9306 Ambient Light Sensor.
>> It has two channels - ALS and CLEAR. The ALS (Ambient Light Sensor)
>> channel approximates the response of the human-eye providing direct
>> read out where the output count is proportional to ambient light levels.
>> It is internally temperature compensated and rejects 50Hz and 60Hz flicker
>> caused by artificial light sources. Hardware interrupt configuration is
>> optional. It is a low power device with 20 bit resolution and has
>> configurable adaptive interrupt mode and interrupt persistence mode.
>> The device also features inbuilt hardware gain, multiple integration time
>> selection options and sampling frequency selection options.
>>
>> This driver also uses the IIO GTS (Gain Time Scale) Helpers Namespace for
>> Scales, Gains and Integration time implementation.
>>
>> Signed-off-by: Subhajit Ghosh <[email protected]>
>> ---
>> v2 -> v5:
>
> Why did you jump to v5? Some internal or private reviews perhaps?
> Better for those tracking on the list if you just used v3.
Wish I had someone to review my code before sending it to kernel community!
I do this in my own time.
v5 was suggested by you. Now I understand that Suggested-by: tag has to be used :)
https://lore.kernel.org/all/20231028143631.2545f93e@jic23-huawei/
>
>
>> - Removed scale attribute for Intensity channel:
>> Link: https://lore.kernel.org/all/20231204095108.22f89718@jic23-huawei/
>>
>> - Dropped caching of hardware gain, repeat rate and integration time and
>> updated code as per earlier reviews.
>> Link: https://lore.kernel.org/lkml/20231028142944.7e210eb6@jic23-huawei/
>
> ...
>
> A few, mostly very minor comments inline to add to Christophe's review.
>
> Thanks,
>
> Jonathan
>
>> diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c
>> new file mode 100644
>> index 000000000000..8ed5899050ed
>> --- /dev/null
>> +++ b/drivers/iio/light/apds9306.c
>> @@ -0,0 +1,1315 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * APDS-9306/APDS-9306-065 Ambient Light Sensor
>> + * I2C Address: 0x52
>> + * Datasheet: https://docs.broadcom.com/doc/AV02-4755EN
>> + *
>> + * Copyright (C) 2023 Subhajit Ghosh <[email protected]>
>
> Given you are still changing it, feel free to include 2024!
I sincerely hope that I don't have to update it to 2025!
>
>> + */
> ...
>> +static const int apds9306_repeat_rate_freq[][2] = {
>> + {40, 0},
>> + {20, 0},
>> + {10, 0},
>> + {5, 0},
>> + {2, 0},
>> + {1, 0},
>> + {0, 500000},
> Prefer
> { 40, 0 },
> etc and whilst I don't really like forcing alignment like this, if you
> are going to do it be consistent. The last 50000 is one space too far to the
> left I think.
>
>
>> +};
>> +static_assert(ARRAY_SIZE(apds9306_repeat_rate_freq) ==
>> + APDS9306_NUM_REPEAT_RATES);
>> +
>> +static const int apds9306_repeat_rate_period[] = {
>> + 25000, 50000, 100000, 200000, 500000, 1000000, 2000000,
>> +};
>> +static_assert(ARRAY_SIZE(apds9306_repeat_rate_period) ==
>> + APDS9306_NUM_REPEAT_RATES);
>> +
>> +/**
>> + * struct apds9306_data - apds9306 private data and registers definitions
>> + *
>> + * @dev: Pointer to the device structure
>> + * @gts: IIO Gain Time Scale structure
>> + * @mutex: Lock for protecting register access, adc reads and power
>
> ADC. I guess the double comment is to keep checkpatch happy?
>
> Just ignore it being dumb as you have a comment up here and put all the info
> here rather than splitting it up like this.
>
>> + * @regmap: Regmap structure pointer
>> + * @regfield_sw_reset: Reg: MAIN_CTRL, Field: SW_Reset
>> + * @regfield_en: Reg: MAIN_CTRL, Field: ALS_EN
>> + * @regfield_intg_time: Reg: ALS_MEAS_RATE, Field: ALS Resolution/Bit Width
>> + * @regfield_repeat_rate: Reg: ALS_MEAS_RATE, Field: ALS Measurement Rate
>> + * @regfield_gain: Reg: ALS_GAIN, Field: ALS Gain Range
>> + * @regfield_int_src: Reg: INT_CFG, Field: ALS Interrupt Source
>> + * @regfield_int_thresh_var_en: Reg: INT_CFG, Field: ALS Var Interrupt Mode
>> + * @regfield_int_en: Reg: INT_CFG, Field: ALS Interrupt Enable
>> + * @regfield_int_persist_val: Reg: INT_PERSISTENCE, Field: ALS_PERSIST
>> + * @regfield_int_thresh_var_val: Reg: ALS_THRSH_VAR, Field: ALS_THRES_VAR
>> + * @nlux_per_count: nano lux per ADC count for a particular model
>> + * @read_data_available: Flag set by IRQ handler for ADC data available
>> + */
>> +struct apds9306_data {
>> + struct device *dev;
>> + struct iio_gts gts;
>> + /*
>> + * Protects device settings changes where some calculations are required
>> + * before or after setting or getting the raw settings values from regmap
>> + * writes or reads respectively.
>> + */
>> + struct mutex mutex;
>> +
>> + struct regmap *regmap;
>> + struct regmap_field *regfield_sw_reset;
>> + struct regmap_field *regfield_en;
>> + struct regmap_field *regfield_intg_time;
>> + struct regmap_field *regfield_repeat_rate;
>> + struct regmap_field *regfield_gain;
>> + struct regmap_field *regfield_int_src;
>> + struct regmap_field *regfield_int_thresh_var_en;
>> + struct regmap_field *regfield_int_en;
>> + struct regmap_field *regfield_int_persist_val;
>> + struct regmap_field *regfield_int_thresh_var_val;
>> +
>> + int nlux_per_count;
>> + int read_data_available;
>> +};
>
>> +
>> +static struct iio_event_spec apds9306_event_spec_als[] = {
>> + {
>> + .type = IIO_EV_TYPE_THRESH,
>> + .dir = IIO_EV_DIR_RISING,
>> + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE),
>> + }, {
>> + .type = IIO_EV_TYPE_THRESH,
>> + .dir = IIO_EV_DIR_FALLING,
>> + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE),
>> + }, {
>> + .type = IIO_EV_TYPE_THRESH,
>> + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD),
>> + }, {
>> + .type = IIO_EV_TYPE_THRESH_ADAPTIVE,
>> + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) |
>> + BIT(IIO_EV_INFO_ENABLE),
>> + }, {
>> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
>
> What's the intent of this final entry?
> The type will default to IIO_EV_TYPE_THRESH anyway but if that
> the intent you should specify it. There isn't an 'obvious'
> default for type in the same way there sort of is for dir
> (as it's either direction).
Understood, let me experiment and see the ABI difference, if any and get back to you.
>
>> + },
>> +};
>
>> +
>
>> +
>> +static int apds9306_runtime_power_on(struct device *dev)
>> +{
>> + int ret;
>> +
>> + ret = pm_runtime_resume_and_get(dev);
>> + if (ret < 0)
>> + dev_err(dev, "runtime resume failed: %d\n", ret);
>> +
>> + return ret;
>> +}
>> +
>> +static int apds9306_runtime_power_off(struct device *dev)
>> +{
>> + pm_runtime_mark_last_busy(dev);
>> + pm_runtime_put_autosuspend(dev);
>> +
>> + return 0;
>> +}
>
> I'm not entirely convinced these two wrappers are worthwhile given they
> aren't that common used and locally obscure what is going on when
> it could be apparent at the few call sites.
The above was suggested by Andy.
https://lore.kernel.org/linux-devicetree/[email protected]/
Apologies for my ignorance - "it could be apparent at the few call sites" -
I did not understand the above statement. Can you please elaborate?
>
>
>
>> +static int apds9306_read_event_config(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan,
>> + enum iio_event_type type,
>> + enum iio_event_direction dir)
>> +{
>> + struct apds9306_data *data = iio_priv(indio_dev);
>> + int int_en, int_ch, ret;
>> +
>> + guard(mutex)(&data->mutex);
>> +
>> + switch (type) {
>> + case IIO_EV_TYPE_THRESH:
>> + ret = regmap_field_read(data->regfield_int_src, &int_ch);
>
> int_ch is a not particularly informative name.
>
> event_ch_is_light perhaps?
>
>> + if (ret)
>> + return ret;
>> + ret = regmap_field_read(data->regfield_int_en, &int_en);
>> + if (ret)
>> + return ret;
>> + if (chan->type == IIO_LIGHT)
>> + return int_en & int_ch;
>> + else if (chan->type == IIO_INTENSITY)
>> + return int_en & !int_ch;
>> + return -EINVAL;
>> + case IIO_EV_TYPE_THRESH_ADAPTIVE:
>> + ret = regmap_field_read(data->regfield_int_thresh_var_en, &int_en);
>> + if (ret)
>> + return ret;
>> + return int_en;
>> + default:
>> + return -EINVAL;
>> + }
>> +}
>> +
>> +static int apds9306_write_event_config(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan,
>> + enum iio_event_type type,
>> + enum iio_event_direction dir,
>> + int state)
>> +{
>> + struct apds9306_data *data = iio_priv(indio_dev);
>> + int ret, val;
>> +
>> + state = !!state;
>> +
>> + guard(mutex)(&data->mutex);
>> +
>> + switch (type) {
>> + case IIO_EV_TYPE_THRESH:
>> + /*
>> + * If interrupt is enabled, the channel is set before enabling
>> + * the interrupt. In case of disable, no need to switch
>> + * channels. In case of different channel is selected while
>> + * interrupt in on, just change the channel.
>> + */
>> + if (state) {
>> + if (chan->type == IIO_LIGHT)
>> + val = 1;
>> + else if (chan->type == IIO_INTENSITY)
>> + val = 0;
>> + else
>> + return -EINVAL;
>
> Blank line here and similar.
>
>> + ret = regmap_field_write(data->regfield_int_src, val);
>> + if (ret)
>> + return ret;
>> + }
>> + ret = regmap_field_read(data->regfield_int_en, &val);
>> + if (ret)
>> + return ret;
>> + if (val == state)
>> + return 0;
>
> Blank line. Basically add one whenever a block of related code ends.
>
>> + ret = regmap_field_write(data->regfield_int_en, state);
>> + if (ret)
>> + return ret;
>> + if (state)
>> + return apds9306_runtime_power_on(data->dev);
>> + return apds9306_runtime_power_off(data->dev);
>> + case IIO_EV_TYPE_THRESH_ADAPTIVE:
>> + return regmap_field_write(data->regfield_int_thresh_var_en, state);
>> + default:
>> + return -EINVAL;
>> + }
>> +}
>>
>
> ..
>
>> +static void apds9306_powerdown(void *ptr)
>> +{
>> + struct apds9306_data *data = (struct apds9306_data *)ptr;
>> + int ret;
>> +
>> + ret = regmap_field_write(data->regfield_int_thresh_var_en, 0);
>> + if (ret)
>> + return;
>
> blank line here ideally.
>
>> + ret = regmap_field_write(data->regfield_int_en, 0);
>> + if (ret)
>> + return;
>> +
>> + apds9306_power_state(data, false);
>> +}
>
> ...
>
>> +
>> +static int apds9306_probe(struct i2c_client *client)
>> +{
>> + struct device *dev = &client->dev;
>> + struct apds9306_data *data;
>> + struct iio_dev *indio_dev;
>> + int ret;
>> +
>> + indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
>> + if (!indio_dev)
>> + return -ENOMEM;
>> +
>> + data = iio_priv(indio_dev);
>> +
>> + mutex_init(&data->mutex);
>> +
>> + data->regmap = devm_regmap_init_i2c(client, &apds9306_regmap);
>> + if (IS_ERR(data->regmap))
>> + return dev_err_probe(dev, PTR_ERR(data->regmap),
>> + "regmap initialization failed\n");
>> +
>> + data->dev = dev;
>> + i2c_set_clientdata(client, indio_dev);
>> +
>> + ret = apds9306_regfield_init(data);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "regfield initialization failed\n");
>> +
>> + ret = devm_regulator_get_enable(dev, "vdd");
>> + if (ret)
>> + return dev_err_probe(dev, ret, "Failed to enable regulator\n");
>> +
>> + indio_dev->name = "apds9306";
>> + indio_dev->modes = INDIO_DIRECT_MODE;
>> + if (client->irq) {
>> + indio_dev->info = &apds9306_info;
>> + indio_dev->channels = apds9306_channels_with_events;
>> + indio_dev->num_channels =
>> + ARRAY_SIZE(apds9306_channels_with_events);
>> + ret = devm_request_threaded_irq(dev, client->irq, NULL,
>> + apds9306_irq_handler, IRQF_ONESHOT,
>> + "apds9306_event", indio_dev);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "failed to assign interrupt.\n");
>> + } else {
>> + indio_dev->info = &apds9306_info_no_events;
>> + indio_dev->channels = apds9306_channels_without_events;
>> + indio_dev->num_channels = ARRAY_SIZE(apds9306_channels_without_events);
>> + }
>> +
>> + ret = apds9306_pm_init(data);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "failed pm init\n");
>> +
>> + ret = apds9306_device_init(data);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "failed to init device\n");
>> +
>> + ret = devm_add_action_or_reset(dev, apds9306_powerdown, data);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "failed to add action or reset\n");
>> +
>> + ret = devm_iio_device_register(dev, indio_dev);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "failed iio device registration\n");
>> +
>> + pm_runtime_put_autosuspend(dev);
>
> Where is the matching get? I don't recall any of the pm functions
> leaving us with the reference count raised except for the where it is
> called out in the function name.
>
I blindly copy pasted your below suggestion.
https://lore.kernel.org/all/20231028162025.4259f1cc@jic23-huawei/
"this lot of runtime pm stuff isn't initializing the device, so I don't
see it as making sense in here. I'd push it out to the caller with
the power up before init and the autosuspend etc after.
I'll note that I'd expect to see a a pm_runtime_put_autosuspend()
at the end of probe to put device to sleep soon after loading."
> The runtime pm reference counters are protected against underflowing so this
> probably just has no impact. Still good to only have it if necessary and if
> you do need the power to be on until this point, force it to do so by
> an appropriate pm_runtime_get().
I will use a pm_runtime_get() in the apds9306_pm_init() function above.
>
>
>> +
>> + return 0;
>> +}
>
Thank you for your review.
Regards,
Subhajit Ghosh
Hi Jonathan,
>>
>>> +
>>> +static struct iio_event_spec apds9306_event_spec_als[] = {
>>> + {
>>> + .type = IIO_EV_TYPE_THRESH,
>>> + .dir = IIO_EV_DIR_RISING,
>>> + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE),
>>> + }, {
>>> + .type = IIO_EV_TYPE_THRESH,
>>> + .dir = IIO_EV_DIR_FALLING,
>>> + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE),
>>> + }, {
>>> + .type = IIO_EV_TYPE_THRESH,
>>> + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD),
>>> + }, {
>>> + .type = IIO_EV_TYPE_THRESH_ADAPTIVE,
>>> + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) |
>>> + BIT(IIO_EV_INFO_ENABLE),
>>> + }, {
>>> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
>>
>> What's the intent of this final entry?
>> The type will default to IIO_EV_TYPE_THRESH anyway but if that
>> the intent you should specify it. There isn't an 'obvious'
>> default for type in the same way there sort of is for dir
>> (as it's either direction).
> Understood, let me experiment and see the ABI difference, if any and get back to you.
>
This device has two channels - ALS and CLEAR. One interrupt enable option and
one Channel selection option (Clear or ALS). According to our previous discussions:
https://lore.kernel.org/all/20230415183543.6d5e3392@jic23-huawei/
the event_spec was updated to have two interrupt enable attributes - one for CLEAR and
one for ALS. (Intensity channel and Illuminance channel)
If I remove the final entry I am getting only one enable option (intensity channel):
/sys/bus/iio/devices/iio:device0/
|-- events
| |-- in_intensity_clear_thresh_either_en
| |-- thresh_adaptive_either_en
| |-- thresh_adaptive_either_value
| |-- thresh_adaptive_either_values_available
| |-- thresh_either_period
| |-- thresh_either_period_available
| |-- thresh_falling_value
| `-- thresh_rising_value
The last entry gives be the following event attributes, enable attributes for both
intensity and illuminance channels:
/sys/bus/iio/devices/iio:device0/
|-- events
| |-- in_illuminance_thresh_either_en
| |-- in_intensity_clear_thresh_either_en
| |-- thresh_adaptive_either_en
| |-- thresh_adaptive_either_value
| |-- thresh_adaptive_either_values_available
| |-- thresh_either_period
| |-- thresh_either_period_available
| |-- thresh_falling_value
| `-- thresh_rising_value
Please let me know if this sounds ok to you.
Regards,
Subhajit Ghosh
On Sun, 4 Feb 2024 21:53:55 +1030
Subhajit Ghosh <[email protected]> wrote:
> Hi Jonathan,
> >>
> >>> +
> >>> +static struct iio_event_spec apds9306_event_spec_als[] = {
> >>> + {
> >>> + .type = IIO_EV_TYPE_THRESH,
> >>> + .dir = IIO_EV_DIR_RISING,
> >>> + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE),
> >>> + }, {
> >>> + .type = IIO_EV_TYPE_THRESH,
> >>> + .dir = IIO_EV_DIR_FALLING,
> >>> + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE),
> >>> + }, {
> >>> + .type = IIO_EV_TYPE_THRESH,
> >>> + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD),
> >>> + }, {
> >>> + .type = IIO_EV_TYPE_THRESH_ADAPTIVE,
> >>> + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) |
> >>> + BIT(IIO_EV_INFO_ENABLE),
> >>> + }, {
> >>> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> >>
> >> What's the intent of this final entry?
> >> The type will default to IIO_EV_TYPE_THRESH anyway but if that
> >> the intent you should specify it. There isn't an 'obvious'
> >> default for type in the same way there sort of is for dir
> >> (as it's either direction).
> > Understood, let me experiment and see the ABI difference, if any and get back to you.
> >
> This device has two channels - ALS and CLEAR. One interrupt enable option and
> one Channel selection option (Clear or ALS). According to our previous discussions:
> https://lore.kernel.org/all/20230415183543.6d5e3392@jic23-huawei/
> the event_spec was updated to have two interrupt enable attributes - one for CLEAR and
> one for ALS. (Intensity channel and Illuminance channel)
>
> If I remove the final entry I am getting only one enable option (intensity channel):
> /sys/bus/iio/devices/iio:device0/
> |-- events
> | |-- in_intensity_clear_thresh_either_en
> | |-- thresh_adaptive_either_en
> | |-- thresh_adaptive_either_value
> | |-- thresh_adaptive_either_values_available
> | |-- thresh_either_period
> | |-- thresh_either_period_available
> | |-- thresh_falling_value
> | `-- thresh_rising_value
>
> The last entry gives be the following event attributes, enable attributes for both
> intensity and illuminance channels:
> /sys/bus/iio/devices/iio:device0/
> |-- events
> | |-- in_illuminance_thresh_either_en
> | |-- in_intensity_clear_thresh_either_en
> | |-- thresh_adaptive_either_en
> | |-- thresh_adaptive_either_value
> | |-- thresh_adaptive_either_values_available
> | |-- thresh_either_period
> | |-- thresh_either_period_available
> | |-- thresh_falling_value
> | `-- thresh_rising_value
>
> Please let me know if this sounds ok to you.
Looks like coincidence of enum values being 0.
It's really
{
.type = IIO_EV_TYPE_THRESH, /* Value 0 */
.dir = IIO_EV_DIR_EITHER, /* value 0 */
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
Dropping 'defaults' for these things is fine if they are the obvious default
or other parameters mean they aren't used, but that isn't the case here so
please be explicit for all the values that are used.
You can put this final mask a few lines earlier as the other fields match
anyway.
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_EITHER,
.mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD),
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
},..
>
> Regards,
> Subhajit Ghosh