Support for Texas Instruments OPT4060 RGBW Color sensor.
Subsystems (3)
v9 cover Support for Texas Instruments OPT4060 RGBW Color sensor.
This patch series adds support for Texas Instruments OPT4060 RGBW Color sensor using the i2c interface.
The driver exposes raw adc values for red, green, blue and clear. The illuminance is exposed as a calculated value in lux, the calculation uses the wide spectrum green channel as base. The driver supports scale values for red, green and blue. The raw values are scaled so that for a particular test light source, typically white, the measurement intensity is the same across the different color channels. Integration time can be configured through sysfs as well. The OPT4060 sensor supports both rising and falling threshold interrupts. These interrupts are exposed as IIO events. The driver also implements an IIO triggered buffer with a trigger for conversion ready interrupts.
Changes in v9:
- Implemented mitigation for race conditions between buffers, sysfs read and events. This resulted in fairly big changes around opt4060_trigger_one_shot(...), opt4060_trigger_set_state(...) and opt4060_event_set_state(...). Some functions are renamed and moved within the file. The general concept is that direct mode has to be claimed for removing continuous sampling and irq.
- Fixed some typos.
- Changed in_intensity_xxx_scale to return factors instead of scaled raw values.
- Documentation: Adapted opt4060.rst to scaled factors instead of scaled raw values.
- Link to V8: https://lore.kernel.org/lkml/[email protected]/
Changes in v8:
- Documentation: Fixed new line warning in opt4060.rst
- dt-bindings: Re-added "Reviewed-by: Krzysztof Kozlowski <[email protected]>"
- Link to V7: https://lore.kernel.org/lkml/[email protected]/
Changes in v7:
- Calculation for scaled values changed to remove normalization.
- Fixed alignment in opt4060_write_ev_period(...).
- Fixed alignment in opt4060_read_ev_period(...).
- Updates state to bool in opt4060_write_event_config(...).
- dt-bindings: Define vdd-supply as required.
- dt-bindings: Removed description of vdd-supply.
- dt-bindings: Removed "Reviewed-by: Krzysztof Kozlowski <[email protected]>" due to changes.
- Documentation: Added missing _scale parameters in Documentation/ABI/testing/sysfs-bus-iio
- Documentation: Updated opt4060.rst with description of scaled values without normalization.
- Cover letter: Description adapted to the new implementation of scaled values.
- Link to V6: https://lore.kernel.org/lkml/[email protected]/
Changes in v6:
- Modified the driver to work with any trigger. Verified using iio-trig-sysfs.
- Fixed return in opt4060_trigger_set_state(...).
- Break added in opt4060_read_ev_period(...).
- Struct and variable declaration chnages in opt4060_trigger_handler(...).
- Events changed to modified and not unmodified.
- Moved variable declaration from case statement in opt4060_read_event(...), and opt4060_write_event(...), found by test robot.
- Init_completion() moved to a place related to IRQ.
- Documentation: Added opt4060.rst with description of calculations.
- Ducumentation: Added missing _raw parameters in Documentation/ABI/testing/sysfs-bus-iio
- Link to V5: https://lore.kernel.org/lkml/[email protected]/
Changes in v5:
- Cover letter: Description adapted to new channel and trigger setup.
- Trigger for threshold removed.
- Channel setup modified according to email discussion.
- Switched to aligned_s64 from linux-next.
- Endianness in buffer changed to IIO_CPU.
- opt4060_read_raw_value(...) changed to avoid endian issue plus early return.
- opt4060_read_chan_value(...) split of in several for new channel setup.
- Improved return values in multiple places.
- The preenable callback removed, functionality moved to set_state.
- Irq setup removed from opt4060_irq_thread(...).
- Flipped logic with early return in opt4060_trigger_one_shot(...).
- devm_add_action_or_reset(...) moved to after opt4060_load_defaults(...).
- Documentation: Added sysfs-bus-iio-light-opt4060 with channel descriptions.
- Added hard coded name indio_dev->name.
- Modified opt4060_volatile_reg(...) to make test robot happy.
- Link to V4: https://lore.kernel.org/lkml/[email protected]/
Changes in v4:
- Fix for a warning found by test robot in opt4060_write_event(...).
- Correction of early return in opt4060_write_event(...) and opt4060_read_event(...), missed from review of version 2.
- Correction of timeout for sample conversion.
- Correction of bug when changing integration time with buffer enabled.
- Link to V3: https://lore.kernel.org/lkml/[email protected]/
Changes in v3:
- Cover letter: Removed lux from description.
- OPT_4060_DRV_NAME define removed.
- Corrected alignment for struct opt4060_buffer.
- Added description of the CRC calculation.
- Cleaned variable declaration in several places.
- Added a path for the non-irq case in opt4060_read_chan_value(...).
- Added a description of processed values.
- Use of regmap_clear_bits in opt4060_power_down(...).
- Switched to IIO_INTENSITY instead of IIO_LIGHT.
- Correction of channel index in IIO_UNMOD_EVENT_CODE, found by test robot.
- Added iio_chan_spec for the non-irq case without events.
- Fixed braces in a few if-else statements.
- Refactoring with early returns in a few places to reduce indentation.
- Replaced for_each_set_bit with iio_for_each_active_channel.
- Removed various too obvious comments.
- Fixed various other code style problems.
- Link to V2: https://lore.kernel.org/lkml/[email protected]/
Changes in v2:
- dt-bindings: Removed incorrect allOf.
- dt-bindings: Changed to generic node name.
- Correction in opt4060_trigger_one_shot(...) for continuous mode.
- Correction in opt4060_power_down(...), wrong register was read.
- Corrected usage of active_scan_mask in opt4060_trigger_handler(...).
- Clean-up of various comments.
- Link to V1: https://lore.kernel.org/lkml/[email protected]/
Per-Daniel Olsson (2):
dt-bindings: iio: light: Document TI OPT4060 RGBW sensor
iio: light: Add support for TI OPT4060 color sensor
Documentation/ABI/testing/sysfs-bus-iio | 7 + .../bindings/iio/light/ti,opt4060.yaml | 51 + Documentation/iio/index.rst | 1 + Documentation/iio/opt4060.rst | 61 + drivers/iio/light/Kconfig | 13 + drivers/iio/light/Makefile | 1 + drivers/iio/light/opt4060.c | 1306 +++++++++++++++++ 7 files changed, 1440 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/ti,opt4060.yaml create mode 100644 Documentation/iio/opt4060.rst create mode 100644 drivers/iio/light/opt4060.c
--
2.39.5
PATCH v9 1/2 dt-bindings: iio: light: Document TI OPT4060 RGBW sensor
Add devicetree bindings for the OPT4060 RGBW color sensor.
--- .../bindings/iio/light/ti,opt4060.yaml | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/ti,opt4060.yaml
Documentation/devicetree/bindings/iio/light/ti,opt4060.yaml new file +51 -0
@@ -0,0 +1,51 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/light/ti,opt4060.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments OPT4060 RGBW Color Sensor + +maintainers: + - Per-Daniel Olsson <[email protected]> + +description: + Texas Instrument RGBW high resolution color sensor over I2C. + https://www.ti.com/lit/gpn/opt4060 + +properties: + compatible: + enum: + - ti,opt4060 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + vdd-supply: true + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + i2c { + #address-cells = <1>; + #size-cells = <0>; + + light-sensor@44 { + compatible = "ti,opt4060"; + reg = <0x44>; + vdd-supply = <&vdd_reg>; + interrupt-parent = <&gpio5>; + interrupts = <13 IRQ_TYPE_EDGE_FALLING>; + }; + }; +...
Signature · 1 line
2.39.5
PATCH v9 2/2 iio: light: Add support for TI OPT4060 color sensor
In reply to Per-Daniel OlssonAdd support for Texas Instruments OPT4060 RGBW Color sensor.
--- Documentation/ABI/testing/sysfs-bus-iio | 7 + Documentation/iio/index.rst | 1 + Documentation/iio/opt4060.rst | 61 ++ drivers/iio/light/Kconfig | 13 + drivers/iio/light/Makefile | 1 + drivers/iio/light/opt4060.c | 1306 +++++++++++++++++++++++ 6 files changed, 1389 insertions(+) create mode 100644 Documentation/iio/opt4060.rst create mode 100644 drivers/iio/light/opt4060.c
Documentation/ABI/testing/sysfs-bus-iio +7 -0
@@ -508,6 +508,9 @@ What: /sys/bus/iio/devices/iio:deviceX/in_angl_scale What: /sys/bus/iio/devices/iio:deviceX/in_intensity_x_scale What: /sys/bus/iio/devices/iio:deviceX/in_intensity_y_scale What: /sys/bus/iio/devices/iio:deviceX/in_intensity_z_scale +What: /sys/bus/iio/devices/iio:deviceX/in_intensity_red_scale +What: /sys/bus/iio/devices/iio:deviceX/in_intensity_green_scale +What: /sys/bus/iio/devices/iio:deviceX/in_intensity_blue_scale What: /sys/bus/iio/devices/iio:deviceX/in_concentration_co2_scale KernelVersion: 2.6.35 Contact: [email protected]
@@ -1633,6 +1636,10 @@ What: /sys/.../iio:deviceX/in_intensityY_uv_raw What: /sys/.../iio:deviceX/in_intensityY_uva_raw What: /sys/.../iio:deviceX/in_intensityY_uvb_raw What: /sys/.../iio:deviceX/in_intensityY_duv_raw +What: /sys/.../iio:deviceX/in_intensity_red_raw +What: /sys/.../iio:deviceX/in_intensity_green_raw +What: /sys/.../iio:deviceX/in_intensity_blue_raw +What: /sys/.../iio:deviceX/in_intensity_clear_raw KernelVersion: 3.4 Contact: [email protected] Description:
Documentation/iio/index.rst +1 -0
@@ -29,3 +29,4 @@ Industrial I/O Kernel Drivers adxl380 bno055 ep93xx_adc + opt4060
Documentation/iio/opt4060.rst new file +61 -0
61 lines not on this page · raw message
drivers/iio/light/Kconfig +13 -0
@@ -475,6 +475,19 @@ config OPT4001 If built as a dynamically linked module, it will be called opt4001. +config OPT4060 + tristate "Texas Instruments OPT4060 RGBW Color Sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say Y or M here, you get support for Texas Instruments + OPT4060 RGBW Color Sensor. + + If built as a dynamically linked module, it will be called + opt4060. + config PA12203001 tristate "TXC PA12203001 light and proximity sensor" depends on I2C
drivers/iio/light/Makefile +1 -0
@@ -42,6 +42,7 @@ obj-$(CONFIG_MAX44009) += max44009.o obj-$(CONFIG_NOA1305) += noa1305.o obj-$(CONFIG_OPT3001) += opt3001.o obj-$(CONFIG_OPT4001) += opt4001.o +obj-$(CONFIG_OPT4060) += opt4060.o obj-$(CONFIG_PA12203001) += pa12203001.o obj-$(CONFIG_ROHM_BU27034) += rohm-bu27034.o obj-$(CONFIG_RPR0521) += rpr0521.o
drivers/iio/light/opt4060.c new file +1306 -0
1,306 lines not on this page · raw message
Signature · 1 line
2.39.5
Jonathan Cameron
maintainerRe: [PATCH v9 2/2] iio: light: Add support for TI OPT4060 color sensor
Quoted 3 lines · On Wed, 11 Dec 2024 15:04:09 +0100 Per-Daniel Olsson…
Add support for Texas Instruments OPT4060 RGBW Color sensor.
Signed-off-by Per-Daniel Olsson <[email protected]>
Hi Per-Daniel,
I think this is nearly there, but still some races or at least places I can't currently convince myself aren't races.
I remember long ago being a wimp and failing to implement a similar dance in the max1363 ADC driver. That manages one more complexity in that in it's continuous mode if events are enabled, the data fields move. It still only supports one of events, sysfs read back or buffered output, not any combination. Maybe if I can find the hardware I'll revisit that one day.
Thanks,
Jonathan
Quoted 5 lines
drivers/iio/light/opt4060.c new file +0
-0
drivers/iio/light/opt4060.c line 134
Quoted 9 lines
drivers/iio/light/opt4060.c new file +7
-0+struct opt4060_chip { +struct regmap *regmap; +struct device *dev; +struct iio_trigger *trig; +u8 int_time; +int irq; +struct mutex irq_setting_lock;
General rule is all locks need a comment on what data they protect even when they have a nice specific name. Bit advantage is it makes it clear what they are not designed to protect!
drivers/iio/light/opt4060.c line 214
Quoted 8 lines
drivers/iio/light/opt4060.c new file +7
-0+struct completion completion; +bool thresh_event_lo_active; +bool thresh_event_hi_active; +}; +static void opt4060_claim_irq_setting_lock (struct opt4060_chip *chip) +{ +if (chip->irq)
I'm struggling to see why you'd be messing with irqs if you don't have any? I can't see a path in which chip->irq isn't set (which makes sense!) and you get here.
So I think you can just move the mutex inline which avoids the mess of lockdep warnings etc and let's you use guard() to simplify things.
Quoted 21 lines
drivers/iio/light/opt4060.c new file +21
-0+mutex_lock (&chip->irq_setting_lock); +} + +static void opt4060_release_irq_setting_lock (struct opt4060_chip *chip) +{ +if (chip->irq) +mutex_unlock (&chip->irq_setting_lock); +} + +static int opt4060_set_int_state (struct opt4060_chip *chip,u32 state) +{ +int ret; +unsigned int regval; + +opt4060_claim_irq_setting_lock (chip); + regval =FIELD_PREP (OPT4060_INT_CTRL_INT_CFG , state); + ret =regmap_update_bits (chip->regmap,OPT4060_INT_CTRL , +OPT4060_INT_CTRL_INT_CFG , regval); +if (ret) +dev_err (chip->dev,"Failed to set interrupt config\n" ); +opt4060_release_irq_setting_lock (chip);
drivers/iio/light/opt4060.c line 245
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+return ret; +} + +static int opt4060_set_continuous_mode (struct opt4060_chip *chip, +bool continuous) +{ +unsigned int reg; +int ret; + + ret =regmap_read (chip->regmap,OPT4060_CTRL , ®);
You could use a regmap_update_bits() call to simplify this like you do for the int_state above.
Quoted 31 lines
drivers/iio/light/opt4060.c new file +31
-0+if (ret <0 ) { +dev_err (chip->dev,"Failed to read ctrl register\n" ); +return ret; + } + reg &= ~OPT4060_CTRL_OPER_MODE_MASK ; +if (continuous) + reg |=FIELD_PREP (OPT4060_CTRL_OPER_MODE_MASK , +OPT4060_CTRL_OPER_MODE_CONTINUOUS ); +else + reg |=FIELD_PREP (OPT4060_CTRL_OPER_MODE_MASK , +OPT4060_CTRL_OPER_MODE_ONE_SHOT ); + +/* Trigger a new conversions by writing to CRTL register. */ + ret =regmap_write (chip->regmap,OPT4060_CTRL , reg); +if (ret) +dev_err (chip->dev,"Failed to set ctrl register\n" ); +return ret; +} + +static bool opt4060_event_active (struct opt4060_chip *chip) +{ +return chip->thresh_event_lo_active || chip->thresh_event_hi_active; +} + +static int opt4060_set_state_common (struct opt4060_chip *chip, +bool continuous_sampling, +bool continuous_irq,bool direct_mode) +{ +int ret =0 ; + +/* It is important to setup irq before sampling to avoid missing samples. */
drivers/iio/light/opt4060.c line 286
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+if (continuous_irq || !direct_mode) + ret =opt4060_set_int_state (chip,OPT4060_INT_CTRL_ALL_CH ); +else if (direct_mode) + ret =opt4060_set_int_state (chip,OPT4060_INT_CTRL_THRESHOLD ); +if (ret) { +dev_err (chip->dev,"Failed to set irq state.\n" ); +return ret; + } + +if (continuous_sampling || !direct_mode ||opt4060_event_active (chip))
I think there may also a race around the event active check. You could have one event direction being enabled concurrently with the other being disabled. I'm not sure it matters but worth checking.
Side effect of either claiming direct or buffered mode is that only one caller can do it at a time, so that would close this race as well. Having said that, it's an implementation detail of the core (be it one that has been there a long time) so you should really have your own driver specific locking scheme prevent that.
Quoted 15 lines
drivers/iio/light/opt4060.c new file +15
-0+ ret =opt4060_set_continuous_mode (chip, true); + else if (direct_mode) + ret =opt4060_set_continuous_mode (chip, false); +if (ret) +dev_err (chip->dev,"Failed to set sampling state.\n" ); +return ret; +} + +/* +* Function for setting the driver state for sampling and irq. When disabling +* continuous sampling or irq, the IIO direct mode must be claimed to prevent +* races with buffer enabling/disabling. In the case when the direct mode is +* not possible to claim, the function will keep continuous mode. All +* functions, sysfs read, events and buffer, work in continuous mode. +*/
drivers/iio/light/opt4060.c line 311
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+static int opt4060_set_driver_state (struct iio_dev *indio_dev, +bool continuous_sampling, +bool continuous_irq) +{ +struct opt4060_chip *chip =iio_priv (indio_dev); +bool direct_mode = false; +int ret =0 ; + +if (!iio_device_claim_direct_mode (indio_dev)) + direct_mode = true;
Hmm. I'm dubious about this pattern. Why is it fine if the driver leaves buffered mode right here? I was expecting this to do the dance with claiming either direct mode or buffered mode. (with the retry loop). Direct mode that you pass into the next call may well be false when it should be true.
Even if you can reason why that isn't a problem (and there are worse dances where it switches mode multiple times during your call of the next function to consider) I think it is easier to reason about if we know it is definitely not changing state until we release it.
Quoted 9 lines
drivers/iio/light/opt4060.c new file +9
-0+ + ret =opt4060_set_state_common (chip, continuous_sampling, + continuous_irq, direct_mode); + +if (direct_mode) +iio_device_release_direct_mode (indio_dev); +return ret; +} +
drivers/iio/light/opt4060.c line 330
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+/* +* This function is called in direct mode from the framework. +*/ +static int opt4060_trigger_set_state (struct iio_trigger *trig,bool state) +{ +struct iio_dev *indio_dev =iio_trigger_get_drvdata (trig); +struct opt4060_chip *chip =iio_priv (indio_dev); +int ret =0 ; + +return ret =opt4060_set_state_common (chip, state, state, true);
return opt_set_state_common() is probably the intent.
Quoted 18 lines
drivers/iio/light/opt4060.c new file +17
-0+} +static int opt4060_trigger_new_samples (struct iio_dev *indio_dev) +{ +struct opt4060_chip *chip =iio_priv (indio_dev); +int ret; + +/* +* The conversion time should be 500us startup time plus the integration time +* times the number of channels. An exact timeout isn't critical, it's better +* not to get incorrect errors in the log. Setting the timeout to double the +* theoretical time plus and extra 100ms margin. +*/ +unsigned int timeout_us = (500 +OPT4060_NUM_CHANS * + opt4060_int_time_reg[chip->int_time][0 ]) *2 +100000 ; + +/* Setting the state in one shot mode with irq on each sample. */ + ret =opt4060_set_driver_state (indio_dev, false, true);
drivers/iio/light/opt4060.c line 385
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+if (ret) +return ret; + +if (chip->irq) { +reinit_completion (&chip->completion); +opt4060_claim_irq_setting_lock (chip); +if (wait_for_completion_timeout (&chip->completion, +usecs_to_jiffies (timeout_us)) ==0 ) { +dev_err (chip->dev,"Completion timed out.\n" ); +opt4060_release_irq_setting_lock (chip);
This is where exposing the lock directly will simplify things as you can just use a guard.
Quoted 6 lines
drivers/iio/light/opt4060.c new file +6
-0+return -ETIME ; + } +opt4060_release_irq_setting_lock (chip); + }else { +unsigned int ready; +
drivers/iio/light/opt4060.c line 401
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+ ret =regmap_read_poll_timeout (chip->regmap,OPT4060_RES_CTRL , + ready, (ready &OPT4060_RES_CTRL_CONV_READY ), +1000 , timeout_us); +if (ret) +dev_err (chip->dev,"Conversion ready did not finish within timeout.\n" ); + } +/* Setting the state in one shot mode with irq on thresholds. */ + ret =opt4060_set_driver_state (indio_dev, false, false); + +return ret;
return opt4060_...
drivers/iio/light/opt4060.c line 653
Quoted 9 lines
drivers/iio/light/opt4060.c new file +8
-0+} +static int opt4060_write_raw_get_fmt (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +long mask) +{ +switch (mask) { +case IIO_CHAN_INFO_INT_TIME : +return IIO_VAL_INT_PLUS_MICRO ;
IIRC That's the default, so you don't need to provide write_raw_get_fmt, though no harm in doing so I guess.
Quoted 4 lines
drivers/iio/light/opt4060.c new file +4
-0+default : +return -EINVAL ; + } +}
drivers/iio/light/opt4060.c line 776
Quoted 10 lines
drivers/iio/light/opt4060.c new file +8
-0+static int opt4060_get_channel_sel (struct opt4060_chip *chip,int *ch_sel) +{ +int ret; +u32 regval; + + ret =regmap_read (chip->regmap,OPT4060_INT_CTRL , ®val); +if (ret) +dev_err (chip->dev,"Failed to get channel selection.\n" );
if you have garbage, not sure it's valid to update ch_sel.
Quoted 18 lines
drivers/iio/light/opt4060.c new file +16
-0+ *ch_sel =FIELD_GET (OPT4060_INT_CTRL_THRESH_SEL , regval); +return ret; +} + +static int opt4060_write_event_config (struct iio_dev *indio_dev, +const struct iio_chan_spec *chan, +enum iio_event_type type, +enum iio_event_direction dir,bool state) +{ +int ch_sel, ch_idx = chan->scan_index; +struct opt4060_chip *chip =iio_priv (indio_dev); +int ret; + +if (chan->type !=IIO_INTENSITY ) +return -EINVAL ; +if (type !=IIO_EV_TYPE_THRESH )
drivers/iio/light/opt4060.c line 948
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+return -EINVAL ; + + ret =opt4060_get_channel_sel (chip, &ch_sel); +if (ret) +return ret; + +if (state) { +/* Only one channel can be active at the same time */ +if ((chip->thresh_event_lo_active || + chip->thresh_event_hi_active) && (ch_idx != ch_sel))
That's a bit nasty to ready. I'd use a slightly long line and get the || pair on the first line.
Hmm. We've never made rules on this but some devices to fifo type selection if they have limitations on events enabled at the same time. With hindsight I think this scheme of just saying no is probably more user friendly.
Quoted 9 lines
drivers/iio/light/opt4060.c new file +9
-0+return -EBUSY ; +if (dir ==IIO_EV_DIR_FALLING ) + chip->thresh_event_lo_active = true; +else if (dir ==IIO_EV_DIR_RISING ) + chip->thresh_event_hi_active = true; + ret =opt4060_set_channel_sel (chip, ch_idx); +if (ret) +return ret; + }else {
drivers/iio/light/opt4060.c line 967
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+if (ch_idx == ch_sel) { +if (dir ==IIO_EV_DIR_FALLING ) + chip->thresh_event_lo_active = false; +else if (dir ==IIO_EV_DIR_RISING ) + chip->thresh_event_hi_active = false; + } + } + +return opt4060_set_driver_state (indio_dev, chip->thresh_event_hi_active | + chip->thresh_event_lo_active, false);
Maybe wrap it to have the | pair on lines with nothing else. They are a little bit burried otherwise.
return opt4060_set_driver_state(indio_dev, chip->thresh_event_hi_active | chip->thresh_event_lo_active, false);
drivers/iio/light/opt4060.c line 979
Quoted 12 lines
drivers/iio/light/opt4060.c new file +12
-0+} + +static const struct iio_info opt4060_info = { + .read_raw = opt4060_read_raw, + .write_raw = opt4060_write_raw, + .write_raw_get_fmt = opt4060_write_raw_get_fmt, + .read_avail = opt4060_read_available, + .read_event_value = opt4060_read_event, + .write_event_value = opt4060_write_event, + .read_event_config = opt4060_read_event_config, + .write_event_config = opt4060_write_event_config, +};
Given you have option for no irq it is probably worth picking a version of this
info structure with all the event callbacks removed. Technically it isn't
required but it does harden the code (by crashing horribly if you call them ;)
drivers/iio/light/opt4060.c line 1061
Quoted 13 lines
drivers/iio/light/opt4060.c new file +13
-0+static irqreturn_t opt4060_trigger_handler (int irq,void *p) +{ +struct iio_poll_func *pf = p; +struct iio_dev *idev = pf->indio_dev; +struct opt4060_chip *chip =iio_priv (idev); +struct { +u32 chan[OPT4060_NUM_CHANS ]; +aligned_s64 ts; + } raw; +int i =0 ; +int chan, ret; + +/* If the trigger is coming for a different driver, a new sample is needed.*/
from a different driver?
Quoted 69 lines
drivers/iio/light/opt4060.c new file +69
-0+if (iio_trigger_validate_own_device (idev->trig, idev)) +opt4060_trigger_new_samples (idev); + +memset (&raw,0 ,sizeof (raw)); + +iio_for_each_active_channel (idev, chan) { +if (chan ==OPT4060_ILLUM ) + ret =opt4060_calc_illuminance (chip, &raw.chan[i++]); +else + ret =opt4060_read_raw_value (chip, + idev->channels[chan].address, + &raw.chan[i++]); +if (ret) { +dev_err (chip->dev,"Reading channel data failed\n" ); + goto err_read; + } + } + +iio_push_to_buffers_with_timestamp (idev, &raw, pf->timestamp); +err_read: +iio_trigger_notify_done (idev->trig); +return IRQ_HANDLED ; +} + +static irqreturn_t opt4060_irq_thread (int irq,void *private) +{ +struct iio_dev *idev = private; +struct opt4060_chip *chip =iio_priv (idev); +int ret, dummy; +unsigned int int_res; + + ret =regmap_read (chip->regmap,OPT4060_RES_CTRL , &int_res); +if (ret <0 ) { +dev_err (chip->dev,"Failed to read interrupt reasons.\n" ); +return IRQ_NONE ; + } + +/* Read OPT4060_CTRL to clear interrupt */ + ret =regmap_read (chip->regmap,OPT4060_CTRL , &dummy); +if (ret <0 ) { +dev_err (chip->dev,"Failed to clear interrupt\n" ); +return IRQ_NONE ; + } + +/* Handle events */ +if (int_res & (OPT4060_RES_CTRL_FLAG_H |OPT4060_RES_CTRL_FLAG_L )) { +u64 code; +int chan =0 ; + + ret =opt4060_get_channel_sel (chip, &chan); +if (ret) { +dev_err (chip->dev,"Failed to read threshold channel.\n" ); +return IRQ_NONE ; + } + +/* Check if the interrupt is from the lower threshold */ +if (int_res &OPT4060_RES_CTRL_FLAG_L ) { + code =IIO_MOD_EVENT_CODE (IIO_INTENSITY , + chan, + idev->channels[chan].channel2, +IIO_EV_TYPE_THRESH , +IIO_EV_DIR_FALLING ); +iio_push_event (idev, code,iio_get_time_ns (idev)); + } +/* Check if the interrupt is from the upper threshold */ +if (int_res &OPT4060_RES_CTRL_FLAG_H ) { + code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, + chan, + idev->channels[chan].channel2,
drivers/iio/light/opt4060.c line 1140
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+IIO_EV_TYPE_THRESH , + IIO_EV_DIR_RISING); +iio_push_event (idev, code,iio_get_time_ns (idev)); + } + } + +/* Handle conversion ready */ +if (int_res &OPT4060_RES_CTRL_CONV_READY ) { +/* Signal completion for potentially waiting reads */ +complete (&chip->completion);
That looks problematic as you haven't necessarily reset the completion if the buffer is enabled. So you probably need a flag or something similar to say a sysfs read has been requested.
Quoted 10 lines
drivers/iio/light/opt4060.c new file +9
-0+ +/* Handle data ready triggers */ +if (iio_buffer_enabled (idev)) +iio_trigger_poll_nested (chip->trig); + } +return IRQ_HANDLED ; +} +static int opt4060_setup_trigger (struct opt4060_chip *chip,struct iio_dev *idev) +{
drivers/iio/light/opt4060.c line 1173
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+struct iio_trigger *data_trigger; +char *name; +int ret; + + data_trigger =devm_iio_trigger_alloc (chip->dev,"%s-data-ready-dev%d" , + idev->name,iio_device_id (idev)); +if (!data_trigger) +return -ENOMEM ; + +/* The data trigger allows for sample capture on each new conversion ready interrupt. */
Make that a multiline comment.
Quoted 5 lines
drivers/iio/light/opt4060.c new file +5
-0+ chip->trig = data_trigger; + data_trigger->ops = &opt4060_trigger_ops; +iio_trigger_set_drvdata (data_trigger, idev); + ret =devm_iio_trigger_register (chip->dev, data_trigger); +if (ret)
drivers/iio/light/opt4060.c line 1188
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+return dev_err_probe (chip->dev, ret, +"Data ready trigger registration failed\n" ); + + name =devm_kasprintf (chip->dev,GFP_KERNEL ,"%s-opt4060" , +dev_name (chip->dev)); +if (!name) +return dev_err_probe (chip->dev, -ENOMEM ,"Failed to alloc chip name\n" ); + + ret = devm_request_threaded_irq(chip->dev, chip->irq, NULL, opt4060_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
That's unusual for a trigger type interrupt and seems likely to give a lot of spurious interrupts. Even if the pulse is short, some interrupt controllers will hang on to the bonus edge and trigger again when you reenable the interrupt.
If intent is to use this for events, then I think you can configure it to latched window mode and one edge type and it will all work.
drivers/iio/light/opt4060.c line 1195
Quoted 7 lines
drivers/iio/light/opt4060.c new file +7
-0+ IRQF_ONESHOT, name, idev); +if (ret) +return dev_err_probe (chip->dev, ret,"Could not request IRQ\n" ); + +init_completion (&chip->completion); + +mutex_init (&chip->irq_setting_lock);
Trivial and I might not even bother changing it, but slightly preference for
ret = devm_mutex_init(...) if (ret) return ret;
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+ + ret =regmap_write_bits (chip->regmap,OPT4060_INT_CTRL , +OPT4060_INT_CTRL_OUTPUT , +OPT4060_INT_CTRL_OUTPUT ); +if (ret) +return dev_err_probe (chip->dev, ret, +"Failed to set interrupt as output\n" ); + +return 0 ; +}
Re: [PATCH v9 2/2] iio: light: Add support for TI OPT4060 color sensor
Quoted 19 lines · Jonathan Cameron
Quoted 3 lines · On Wed, 11 Dec 2024 15:04:09 +0100 Per-Daniel Olsson…
Add support for Texas Instruments OPT4060 RGBW Color sensor.
Signed-off-by Per-Daniel Olsson <[email protected]>Hi Per-Daniel,
I think this is nearly there, but still some races or at least places I can't currently convince myself aren't races.
I remember long ago being a wimp and failing to implement a similar dance in the max1363 ADC driver. That manages one more complexity in that in it's continuous mode if events are enabled, the data fields move. It still only supports one of events, sysfs read back or buffered output, not any combination. Maybe if I can find the hardware I'll revisit that one day.
Quoted 3 lines
Thanks,
Jonathan
Hi Jonathan,
Thank you for your comments. Also thank you for sharing your story about max1363, sounds like a tricky piece of silicon. Great to hear that you think it can be worth the time to implement concurrent use cases, I was a little worried that I was pushing that part a little too far... :)
I have added comments below.
Thanks (and happy holidays if I don't hear back from you before),
Per-Daniel
Quoted 8 lines
Quoted 6 lines
drivers/iio/light/opt4060.c new file +0
-0
Quoted 11 lines
drivers/iio/light/opt4060.c line 134
Quoted 7 lines
drivers/iio/light/opt4060.c new file +7
-0+struct opt4060_chip { +struct regmap *regmap; +struct device *dev; +struct iio_trigger *trig; +u8 int_time; +int irq; +struct mutex irq_setting_lock;General rule is all locks need a comment on what data they protect even when they have a nice specific name. Bit advantage is it makes it clear what they are not designed to protect!
Done in v10.
Quoted 6 lines
Quoted 5 lines
drivers/iio/light/opt4060.c new file +4
-0+struct completion completion; +bool thresh_event_lo_active; +bool thresh_event_hi_active; +};
Quoted 6 lines
drivers/iio/light/opt4060.c line 214
Quoted 3 lines
drivers/iio/light/opt4060.c new file +3
-0+static void opt4060_claim_irq_setting_lock (struct opt4060_chip *chip) +{ +if (chip->irq)I'm struggling to see why you'd be messing with irqs if you don't have any? I can't see a path in which
chip->irqisn't set (which makes sense!) and you get here.
Good point, fixed in v10.
Quoted 30 lines
So I think you can just move the mutex inline which avoids the mess of lockdep warnings etc and let's you use
guard()to simplify things.Quoted 26 lines
drivers/iio/light/opt4060.c new file +26
-0+mutex_lock (&chip->irq_setting_lock); +} + +static void opt4060_release_irq_setting_lock (struct opt4060_chip *chip) +{ +if (chip->irq) +mutex_unlock (&chip->irq_setting_lock); +} + +static int opt4060_set_int_state (struct opt4060_chip *chip,u32 state) +{ +int ret; +unsigned int regval; + +opt4060_claim_irq_setting_lock (chip); + regval =FIELD_PREP (OPT4060_INT_CTRL_INT_CFG , state); + ret =regmap_update_bits (chip->regmap,OPT4060_INT_CTRL , +OPT4060_INT_CTRL_INT_CFG , regval); +if (ret) +dev_err (chip->dev,"Failed to set interrupt config\n" ); +opt4060_release_irq_setting_lock (chip); +return ret; +} + +static int opt4060_set_continuous_mode (struct opt4060_chip *chip, +bool continuous)
Quoted 8 lines
drivers/iio/light/opt4060.c line 245
Quoted 5 lines
drivers/iio/light/opt4060.c new file +5
-0+{ +unsigned int reg; +int ret; + + ret =regmap_read (chip->regmap,OPT4060_CTRL , ®);You could use a
regmap_update_bits()call to simplify this like you do for theint_stateabove.
That register is a little special. It requires a real write sometimes even is the data hasn't changed, so regmap_update_bits won't work. This is needed for triggering one shot mode. I have renamed the function in v10 since it don't just set continuous mode but also one shot. I have also changed the comment in the code.
Quoted 39 lines
Quoted 39 lines
drivers/iio/light/opt4060.c new file +37
-0+if (ret <0 ) { +dev_err (chip->dev,"Failed to read ctrl register\n" ); +return ret; + } + reg &= ~OPT4060_CTRL_OPER_MODE_MASK ; +if (continuous) + reg |=FIELD_PREP (OPT4060_CTRL_OPER_MODE_MASK , +OPT4060_CTRL_OPER_MODE_CONTINUOUS ); +else + reg |=FIELD_PREP (OPT4060_CTRL_OPER_MODE_MASK , +OPT4060_CTRL_OPER_MODE_ONE_SHOT ); + +/* Trigger a new conversions by writing to CRTL register. */ + ret =regmap_write (chip->regmap,OPT4060_CTRL , reg); +if (ret) +dev_err (chip->dev,"Failed to set ctrl register\n" ); +return ret; +} + +static bool opt4060_event_active (struct opt4060_chip *chip) +{ +return chip->thresh_event_lo_active || chip->thresh_event_hi_active; +} + +static int opt4060_set_state_common (struct opt4060_chip *chip, +bool continuous_sampling, +bool continuous_irq,bool direct_mode) +{ +int ret =0 ; + +/* It is important to setup irq before sampling to avoid missing samples. */ +if (continuous_irq || !direct_mode) + ret =opt4060_set_int_state (chip,OPT4060_INT_CTRL_ALL_CH ); +else if (direct_mode) + ret =opt4060_set_int_state (chip,OPT4060_INT_CTRL_THRESHOLD ); +if (ret) { +dev_err (chip->dev,"Failed to set irq state.\n" );
Quoted 8 lines
drivers/iio/light/opt4060.c line 286
Quoted 4 lines
drivers/iio/light/opt4060.c new file +4
-0+return ret; + } + +if (continuous_sampling || !direct_mode ||opt4060_event_active (chip))I think there may also a race around the event active check. You could have one event direction being enabled concurrently with the other being disabled. I'm not sure it matters but worth checking.
I think you might be correct even if I haven't been able to trigger the case in my tests. I have added a guard for this in v10. If opt4060_write_event_config() is called from several callers at the same time, the whole sequence all the way down to setting sampling and irq will be synchronized with the mutex/guard.
Quoted 6 lines
Side effect of either claiming direct or buffered mode is that only one caller can do it at a time, so that would close this race as well. Having said that, it's an implementation detail of the core (be it one that has been there a long time) so you should really have your own driver specific locking scheme prevent that.
I have implemented the "dance" in v10 and it seems to work well in my tests.
Quoted 34 lines
Quoted 17 lines
drivers/iio/light/opt4060.c new file +15
-0+ ret =opt4060_set_continuous_mode (chip, true); + else if (direct_mode) + ret =opt4060_set_continuous_mode (chip, false); +if (ret) +dev_err (chip->dev,"Failed to set sampling state.\n" ); +return ret; +} + +/* +* Function for setting the driver state for sampling and irq. When disabling +* continuous sampling or irq, the IIO direct mode must be claimed to prevent +* races with buffer enabling/disabling. In the case when the direct mode is +* not possible to claim, the function will keep continuous mode. All +* functions, sysfs read, events and buffer, work in continuous mode. +*/ drivers/iio/light/opt4060.c line 311
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+static int opt4060_set_driver_state (struct iio_dev *indio_dev, +bool continuous_sampling, +bool continuous_irq) +{ +struct opt4060_chip *chip =iio_priv (indio_dev); +bool direct_mode = false; +int ret =0 ; + +if (!iio_device_claim_direct_mode (indio_dev)) + direct_mode = true;Hmm. I'm dubious about this pattern. Why is it fine if the driver leaves buffered mode right here? I was expecting this to do the dance with claiming either direct mode or buffered mode. (with the retry loop). Direct mode that you pass into the next call may well be false when it should be true.
Quoted 5 lines
Even if you can reason why that isn't a problem (and there are worse dances where it switches mode multiple times during your call of the next function to consider) I think it is easier to reason about if we know it is definitely not changing state until we release it.
"dance" implemented in v10.
Quoted 46 lines
Quoted 10 lines
drivers/iio/light/opt4060.c new file +9
-0+ + ret =opt4060_set_state_common (chip, continuous_sampling, + continuous_irq, direct_mode); + +if (direct_mode) +iio_device_release_direct_mode (indio_dev); +return ret; +} +drivers/iio/light/opt4060.c line 330
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+/* +* This function is called in direct mode from the framework. +*/ +static int opt4060_trigger_set_state (struct iio_trigger *trig,bool state) +{ +struct iio_dev *indio_dev =iio_trigger_get_drvdata (trig); +struct opt4060_chip *chip =iio_priv (indio_dev); +int ret =0 ; + +return ret =opt4060_set_state_common (chip, state, state, true);return
opt_set_state_common()is probably the intent.Quoted 23 lines
drivers/iio/light/opt4060.c new file +22
-0+} +static int opt4060_trigger_new_samples (struct iio_dev *indio_dev) +{ +struct opt4060_chip *chip =iio_priv (indio_dev); +int ret; + +/* +* The conversion time should be 500us startup time plus the integration time +* times the number of channels. An exact timeout isn't critical, it's better +* not to get incorrect errors in the log. Setting the timeout to double the +* theoretical time plus and extra 100ms margin. +*/ +unsigned int timeout_us = (500 +OPT4060_NUM_CHANS * + opt4060_int_time_reg[chip->int_time][0 ]) *2 +100000 ; + +/* Setting the state in one shot mode with irq on each sample. */ + ret =opt4060_set_driver_state (indio_dev, false, true); +if (ret) +return ret; + +if (chip->irq) { +reinit_completion (&chip->completion);
Quoted 8 lines
drivers/iio/light/opt4060.c line 385
Quoted 5 lines
drivers/iio/light/opt4060.c new file +5
-0+opt4060_claim_irq_setting_lock (chip); +if (wait_for_completion_timeout (&chip->completion, +usecs_to_jiffies (timeout_us)) ==0 ) { +dev_err (chip->dev,"Completion timed out.\n" ); +opt4060_release_irq_setting_lock (chip);This is where exposing the lock directly will simplify things as you can just use a guard.
Fixed with guard in v10.
Quoted 22 lines
Quoted 7 lines
drivers/iio/light/opt4060.c new file +6
-0+return -ETIME ; + } +opt4060_release_irq_setting_lock (chip); + }else { +unsigned int ready; +drivers/iio/light/opt4060.c line 401
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+ ret =regmap_read_poll_timeout (chip->regmap,OPT4060_RES_CTRL , + ready, (ready &OPT4060_RES_CTRL_CONV_READY ), +1000 , timeout_us); +if (ret) +dev_err (chip->dev,"Conversion ready did not finish within timeout.\n" ); + } +/* Setting the state in one shot mode with irq on thresholds. */ + ret =opt4060_set_driver_state (indio_dev, false, false); + +return ret;return opt4060_...Quoted 1 line
+}
Quoted 9 lines
drivers/iio/light/opt4060.c line 653
Quoted 7 lines
drivers/iio/light/opt4060.c new file +7
-0+static int opt4060_write_raw_get_fmt (struct iio_dev *indio_dev, +struct iio_chan_spec const *chan, +long mask) +{ +switch (mask) { +case IIO_CHAN_INFO_INT_TIME : +return IIO_VAL_INT_PLUS_MICRO ;IIRC That's the default, so you don't need to provide
write_raw_get_fmt, though no harm in doing so I guess.
Ok, wasn't aware of that being the default.
Quoted 41 lines
Quoted 5 lines
drivers/iio/light/opt4060.c new file +4
-0+default : +return -EINVAL ; + } +}drivers/iio/light/opt4060.c line 776
Quoted 10 lines
drivers/iio/light/opt4060.c new file +8
-0+static int opt4060_get_channel_sel (struct opt4060_chip *chip,int *ch_sel) +{ +int ret; +u32 regval; + + ret =regmap_read (chip->regmap,OPT4060_INT_CTRL , ®val); +if (ret) +dev_err (chip->dev,"Failed to get channel selection.\n" );if you have garbage, not sure it's valid to update
ch_sel.Quoted 23 lines
drivers/iio/light/opt4060.c new file +21
-0+ *ch_sel =FIELD_GET (OPT4060_INT_CTRL_THRESH_SEL , regval); +return ret; +} + +static int opt4060_write_event_config (struct iio_dev *indio_dev, +const struct iio_chan_spec *chan, +enum iio_event_type type, +enum iio_event_direction dir,bool state) +{ +int ch_sel, ch_idx = chan->scan_index; +struct opt4060_chip *chip =iio_priv (indio_dev); +int ret; + +if (chan->type !=IIO_INTENSITY ) +return -EINVAL ; +if (type !=IIO_EV_TYPE_THRESH ) +return -EINVAL ; + + ret =opt4060_get_channel_sel (chip, &ch_sel); +if (ret) +return ret;
Quoted 8 lines
drivers/iio/light/opt4060.c line 948
Quoted 5 lines
drivers/iio/light/opt4060.c new file +5
-0+ +if (state) { +/* Only one channel can be active at the same time */ +if ((chip->thresh_event_lo_active || + chip->thresh_event_hi_active) && (ch_idx != ch_sel))That's a bit nasty to ready. I'd use a slightly long line and get the || pair on the first line.
Fixed in v10.
Quoted 5 lines
Hmm. We've never made rules on this but some devices to fifo type selection if they have limitations on events enabled at the same time. With hindsight I think this scheme of just saying no is probably more user friendly.
Ok.
Quoted 14 lines
Quoted 14 lines
drivers/iio/light/opt4060.c new file +13
-0+return -EBUSY ; +if (dir ==IIO_EV_DIR_FALLING ) + chip->thresh_event_lo_active = true; +else if (dir ==IIO_EV_DIR_RISING ) + chip->thresh_event_hi_active = true; + ret =opt4060_set_channel_sel (chip, ch_idx); +if (ret) +return ret; + }else { +if (ch_idx == ch_sel) { +if (dir ==IIO_EV_DIR_FALLING ) + chip->thresh_event_lo_active = false; +else if (dir ==IIO_EV_DIR_RISING )
Quoted 8 lines
drivers/iio/light/opt4060.c line 967
Quoted 6 lines
drivers/iio/light/opt4060.c new file +6
-0+ chip->thresh_event_hi_active = false; + } + } + +return opt4060_set_driver_state (indio_dev, chip->thresh_event_hi_active | + chip->thresh_event_lo_active, false);Maybe wrap it to have the | pair on lines with nothing else. They are a little bit burried otherwise.
Fixed in v10.
Quoted 5 lines
return opt4060_set_driver_state(indio_dev, chip->thresh_event_hi_active | chip->thresh_event_lo_active, false);
Quoted 17 lines
drivers/iio/light/opt4060.c line 979
Quoted 12 lines
drivers/iio/light/opt4060.c new file +12
-0+} + +static const struct iio_info opt4060_info = { + .read_raw = opt4060_read_raw, + .write_raw = opt4060_write_raw, + .write_raw_get_fmt = opt4060_write_raw_get_fmt, + .read_avail = opt4060_read_available, + .read_event_value = opt4060_read_event, + .write_event_value = opt4060_write_event, + .read_event_config = opt4060_read_event_config, + .write_event_config = opt4060_write_event_config, +};Given you have option for no irq it is probably worth picking a version of this
info structure with all the event callbacks removed. Technically it isn't
required but it does harden the code (by crashing horribly if you call them ;)
Good point, I have added a separate version without those callbacks.
Quoted 17 lines
Quoted 5 lines
drivers/iio/light/opt4060.c new file +3
-0+static irqreturn_t opt4060_trigger_handler (int irq,void *p) +{ +struct iio_poll_func *pf = p;drivers/iio/light/opt4060.c line 1061
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+struct iio_dev *idev = pf->indio_dev; +struct opt4060_chip *chip =iio_priv (idev); +struct { +u32 chan[OPT4060_NUM_CHANS ]; +aligned_s64 ts; + } raw; +int i =0 ; +int chan, ret; + +/* If the trigger is coming for a different driver, a new sample is needed.*/ from a different driver?
I have tried to clarify this comment in v10. When an external trigger such as iio_sysfs_trigger is used, the sensor will not be running in continuous mode and a new sample must be triggered.
Quoted 76 lines
Quoted 76 lines
drivers/iio/light/opt4060.c new file +75
-0+if (iio_trigger_validate_own_device (idev->trig, idev)) +opt4060_trigger_new_samples (idev); + +memset (&raw,0 ,sizeof (raw)); + +iio_for_each_active_channel (idev, chan) { +if (chan ==OPT4060_ILLUM ) + ret =opt4060_calc_illuminance (chip, &raw.chan[i++]); +else + ret =opt4060_read_raw_value (chip, + idev->channels[chan].address, + &raw.chan[i++]); +if (ret) { +dev_err (chip->dev,"Reading channel data failed\n" ); + goto err_read; + } + } + +iio_push_to_buffers_with_timestamp (idev, &raw, pf->timestamp); +err_read: +iio_trigger_notify_done (idev->trig); +return IRQ_HANDLED ; +} + +static irqreturn_t opt4060_irq_thread (int irq,void *private) +{ +struct iio_dev *idev = private; +struct opt4060_chip *chip =iio_priv (idev); +int ret, dummy; +unsigned int int_res; + + ret =regmap_read (chip->regmap,OPT4060_RES_CTRL , &int_res); +if (ret <0 ) { +dev_err (chip->dev,"Failed to read interrupt reasons.\n" ); +return IRQ_NONE ; + } + +/* Read OPT4060_CTRL to clear interrupt */ + ret =regmap_read (chip->regmap,OPT4060_CTRL , &dummy); +if (ret <0 ) { +dev_err (chip->dev,"Failed to clear interrupt\n" ); +return IRQ_NONE ; + } + +/* Handle events */ +if (int_res & (OPT4060_RES_CTRL_FLAG_H |OPT4060_RES_CTRL_FLAG_L )) { +u64 code; +int chan =0 ; + + ret =opt4060_get_channel_sel (chip, &chan); +if (ret) { +dev_err (chip->dev,"Failed to read threshold channel.\n" ); +return IRQ_NONE ; + } + +/* Check if the interrupt is from the lower threshold */ +if (int_res &OPT4060_RES_CTRL_FLAG_L ) { + code =IIO_MOD_EVENT_CODE (IIO_INTENSITY , + chan, + idev->channels[chan].channel2, +IIO_EV_TYPE_THRESH , +IIO_EV_DIR_FALLING ); +iio_push_event (idev, code,iio_get_time_ns (idev)); + } +/* Check if the interrupt is from the upper threshold */ +if (int_res &OPT4060_RES_CTRL_FLAG_H ) { + code =IIO_MOD_EVENT_CODE (IIO_INTENSITY , + chan, + idev->channels[chan].channel2, +IIO_EV_TYPE_THRESH , +IIO_EV_DIR_RISING ); +iio_push_event (idev, code,iio_get_time_ns (idev)); + } + } +
Quoted 8 lines
drivers/iio/light/opt4060.c line 1140
Quoted 4 lines
drivers/iio/light/opt4060.c new file +4
-0+/* Handle conversion ready */ +if (int_res &OPT4060_RES_CTRL_CONV_READY ) { +/* Signal completion for potentially waiting reads */ +complete (&chip->completion);That looks problematic as you haven't necessarily reset the completion if the buffer is enabled. So you probably need a flag or something similar to say a sysfs read has been requested.
The completion is only used when triggering a new sample. The code will call reinit_completion() which will set the internal counter to zero. The code will then call wait_for_completion_timeout() which will wait for the counter to increase. The call to complete() here will increase the counter each time it's called but since reinit_completion() is called every time before waiting, I don't think this is an issue.
Quoted 10 lines
Quoted 9 lines
drivers/iio/light/opt4060.c new file +7
-0+ +/* Handle data ready triggers */ +if (iio_buffer_enabled (idev)) +iio_trigger_poll_nested (chip->trig); + } +return IRQ_HANDLED ; +}
Quoted 14 lines
drivers/iio/light/opt4060.c line 1173
Quoted 12 lines
drivers/iio/light/opt4060.c new file +12
-0+static int opt4060_setup_trigger (struct opt4060_chip *chip,struct iio_dev *idev) +{ +struct iio_trigger *data_trigger; +char *name; +int ret; + + data_trigger =devm_iio_trigger_alloc (chip->dev,"%s-data-ready-dev%d" , + idev->name,iio_device_id (idev)); +if (!data_trigger) +return -ENOMEM ; + +/* The data trigger allows for sample capture on each new conversion ready interrupt. */ Make that a multiline comment.
Done in v10.
Quoted 20 lines
Quoted 6 lines
drivers/iio/light/opt4060.c new file +5
-0+ chip->trig = data_trigger; + data_trigger->ops = &opt4060_trigger_ops; +iio_trigger_set_drvdata (data_trigger, idev); + ret =devm_iio_trigger_register (chip->dev, data_trigger); +if (ret)drivers/iio/light/opt4060.c line 1188
Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+return dev_err_probe (chip->dev, ret, +"Data ready trigger registration failed\n" ); + + name =devm_kasprintf (chip->dev,GFP_KERNEL ,"%s-opt4060" , +dev_name (chip->dev)); +if (!name) +return dev_err_probe (chip->dev, -ENOMEM ,"Failed to alloc chip name\n" ); + + ret = devm_request_threaded_irq(chip->dev, chip->irq, NULL, opt4060_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |That's unusual for a trigger type interrupt and seems likely to give a lot of spurious interrupts. Even if the pulse is short, some interrupt controllers will hang on to the bonus edge and trigger again when you reenable the interrupt.
Quoted 2 lines
If intent is to use this for events, then I think you can configure it to latched window mode and one edge type and it will all work.
Removed IRQF_TRIGGER_RISING in v10, don't know how both ended up there...
Quoted 24 lines
drivers/iio/light/opt4060.c line 1195
Quoted 8 lines
drivers/iio/light/opt4060.c new file +7
-0+ IRQF_ONESHOT, name, idev); +if (ret) +return dev_err_probe (chip->dev, ret,"Could not request IRQ\n" ); + +init_completion (&chip->completion); + +mutex_init (&chip->irq_setting_lock);Trivial and I might not even bother changing it, but slightly preference for
ret = devm_mutex_init(...) if (ret) return ret;Quoted 10 lines
drivers/iio/light/opt4060.c new file +10
-0+ + ret =regmap_write_bits (chip->regmap,OPT4060_INT_CTRL , +OPT4060_INT_CTRL_OUTPUT , +OPT4060_INT_CTRL_OUTPUT ); +if (ret) +return dev_err_probe (chip->dev, ret, +"Failed to set interrupt as output\n" ); + +return 0 ; +}