On Fri, 06 Dec 2024 11:09:57 -0500 Mikael Gonella-Bolduc via B4 Relay <devnull+mgonellabolduc.dimonoff.com@xxxxxxxxxx> wrote: > From: Mikael Gonella-Bolduc <mgonellabolduc@xxxxxxxxxxxx> > > APDS9160 is a combination of ALS and proximity sensors. > > This patch add supports for: > - Intensity clear data and illuminance data > - Proximity data > - Gain control, rate control > - Event thresholds > > Signed-off-by: Mikael Gonella-Bolduc <mgonellabolduc@xxxxxxxxxxxx> Hi Mikael, As the bots noted, the maintainers entry has the wrong vendor prefix, or the binding does. Also the issue with missing include of linux/bitfield.h Unused gain table is less obvious. Not sure what intent was on that one. Given the discussion with Matti about how to do the gain control, please add some description here of the outcome. The control scheme is not particularly obvious and is the key bit we should be reviewing. It feels like you've applied the feedback on v1 to the light channel but it is equally applicable to proximity channels when they are just measures of reflected light intensity. Various other minor things inline. Thanks, Jonathan > --- > MAINTAINERS | 7 + > drivers/iio/light/Kconfig | 11 + > drivers/iio/light/Makefile | 1 + > drivers/iio/light/apds9160.c | 1548 ++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 1567 insertions(+) > > diff --git a/MAINTAINERS b/MAINTAINERS > index e69d1632c382fe0e366f7bb20e72ee0c9e91e30b..99903578e2a31dd946bd5f6722eb2aabc349a090 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -3714,6 +3714,13 @@ S: Maintained > F: Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml > F: drivers/iio/light/apds9306.c > > +AVAGO APDS9160 AMBIENT LIGHT SENSOR AND PROXIMITY DRIVER > +M: Mikael Gonella-Bolduc <mgonellabolduc@xxxxxxxxxxxx> > +L: linux-iio@xxxxxxxxxxxxxxx > +S: Maintained > +F: Documentation/devicetree/bindings/iio/light/avago,apds9160.yaml > +F: drivers/iio/light/apds9160.c > + > AVIA HX711 ANALOG DIGITAL CONVERTER IIO DRIVER > M: Andreas Klinger <ak@xxxxxxxxxxxxx> > L: linux-iio@xxxxxxxxxxxxxxx > diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig > index 29ffa84919273d64b8464ab8bbf59661b0102f97..419360661d53973eda71d7d986ff7fd481c7aa2c 100644 > --- a/drivers/iio/light/Kconfig > +++ b/drivers/iio/light/Kconfig > @@ -63,6 +63,17 @@ config AL3320A > To compile this driver as a module, choose M here: the > module will be called al3320a. > > +config APDS9160 > + tristate "APDS9160 combined als and proximity sensor" > + depends on I2C > + select REGMAP_I2C > + help > + Say Y here if you want to build support for a Broadcom APDS9160 > + combined ambient light and proximity sensor. > + > + To compile this driver as a module, choose M here: the > + module will be called apds9160. > + > config APDS9300 > tristate "APDS9300 ambient light sensor" > depends on I2C > diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile > index f14a3744271242f04fe7849b47308397ac2c9939..4a22043acdbbbed2b696a3225e4024654d5d9339 100644 > --- a/drivers/iio/light/Makefile > +++ b/drivers/iio/light/Makefile > @@ -9,6 +9,7 @@ obj-$(CONFIG_ADJD_S311) += adjd_s311.o > obj-$(CONFIG_ADUX1020) += adux1020.o > obj-$(CONFIG_AL3010) += al3010.o > obj-$(CONFIG_AL3320A) += al3320a.o > +obj-$(CONFIG_APDS9160) += apds9160.o > obj-$(CONFIG_APDS9300) += apds9300.o > obj-$(CONFIG_APDS9306) += apds9306.o > obj-$(CONFIG_APDS9960) += apds9960.o > diff --git a/drivers/iio/light/apds9160.c b/drivers/iio/light/apds9160.c > new file mode 100644 > index 0000000000000000000000000000000000000000..1cc639c31ebfef7254bf967e56a25a75f99ab05c > --- /dev/null > +++ b/drivers/iio/light/apds9160.c > @@ -0,0 +1,1548 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * APDS9160 sensor driver. > + * Chip is combined proximity and ambient light sensor. > + * Author: 2024 Mikael Gonella-Bolduc <m.gonella.bolduc@xxxxxxxxx> > + */ > + > +#include <linux/bits.h> > +#include <linux/cleanup.h> > +#include <linux/delay.h> > +#include <linux/err.h> > +#include <linux/i2c.h> > +#include <linux/interrupt.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/regmap.h> > +#include <linux/regulator/consumer.h> > +#include <linux/types.h> > +#include <linux/units.h> > + > +#include <linux/iio/iio.h> > +#include <linux/iio/events.h> > +#include <linux/iio/sysfs.h> > + > +#include <linux/unaligned.h> > + > +#define APDS9160_REGMAP_NAME "apds9160_regmap" > +#define APDS9160_REG_CNT 37 > + > +/* Main control register */ > +#define APDS9160_REG_CTRL 0x00 > +#define APDS9160_CTRL_SWRESET BIT(4) /* 1: Activate reset */ > +#define APDS9160_CTRL_MODE_RGB BIT(2) /* 0: ALS & IR, 1: RGB & IR */ > +#define APDS9160_CTRL_EN_ALS BIT(1) /* 1: ALS active */ > +#define APDS9160_CTLR_EN_PS BIT(0) /* 1: PS active */ > + > +/* Status register */ > +#define APDS9160_SR_LS_INT BIT(4) > +#define APDS9160_SR_LS_NEW_DATA BIT(3) > +#define APDS9160_SR_PS_INT BIT(1) > +#define APDS9160_SR_PS_NEW_DATA BIT(0) > + > +/* Interrupt configuration registers */ > +#define APDS9160_REG_INT_CFG 0x19 > +#define APDS9160_REG_INT_PST 0x1A > +#define APDS9160_INT_CFG_EN_LS BIT(2) /* LS int enable */ > +#define APDS9160_INT_CFG_EN_PS BIT(0) /* PS int enable */ > + > +/* Proximity registers */ > +#define APDS9160_REG_PS_LED 0x01 > +#define APDS9160_REG_PS_PULSES 0x02 > +#define APDS9160_REG_PS_MEAS_RATE 0x03 > +#define APDS9160_REG_PS_THRES_HI_LSB 0x1B > +#define APDS9160_REG_PS_THRES_HI_MSB 0x1C > +#define APDS9160_REG_PS_THRES_LO_LSB 0x1D > +#define APDS9160_REG_PS_THRES_LO_MSB 0x1E > +#define APDS9160_REG_PS_DATA_LSB 0x08 > +#define APDS9160_REG_PS_DATA_MSB 0x09 > +#define APDS9160_REG_PS_CAN_LEVEL_DIG_LSB 0x1F > +#define APDS9160_REG_PS_CAN_LEVEL_DIG_MSB 0x20 > +#define APDS9160_REG_PS_CAN_LEVEL_ANA_DUR 0x21 > +#define APDS9160_REG_PS_CAN_LEVEL_ANA_CURRENT 0x22 > + > +/* Light sensor registers */ > +#define APDS9160_REG_LS_MEAS_RATE 0x04 > +#define APDS9160_REG_LS_GAIN 0x05 > +#define APDS9160_REG_LS_DATA_CLEAR_LSB 0x0A > +#define APDS9160_REG_LS_DATA_CLEAR 0x0B > +#define APDS9160_REG_LS_DATA_CLEAR_MSB 0x0C > +#define APDS9160_REG_LS_DATA_ALS_LSB 0x0D > +#define APDS9160_REG_LS_DATA_ALS 0x0E > +#define APDS9160_REG_LS_DATA_ALS_MSB 0x0F > +#define APDS9160_REG_LS_THRES_UP_LSB 0x24 > +#define APDS9160_REG_LS_THRES_UP 0x25 > +#define APDS9160_REG_LS_THRES_UP_MSB 0x26 > +#define APDS9160_REG_LS_THRES_LO_LSB 0x27 > +#define APDS9160_REG_LS_THRES_LO 0x28 > +#define APDS9160_REG_LS_THRES_LO_MSB 0x29 > +#define APDS9160_REG_LS_THRES_VAR 0x2A > + > +/* Part identification number register */ > +#define APDS9160_REG_ID 0x06 > + > +/* Status register */ > +#define APDS9160_REG_SR 0x07 > +#define APDS9160_SR_DATA_ALS BIT(3) > +#define APDS9160_SR_DATA_PS BIT(0) > + > +/* Supported ID:s */ > +#define APDS9160_PART_ID_0 0x03 > + > +#define APDS9160_PS_THRES_MAX 0x7FF > +#define APDS9160_LS_THRES_MAX 0xFFFFF > +#define APDS9160_CMD_LS_RESOLUTION_25MS 0x04 > +#define APDS9160_CMD_LS_RESOLUTION_50MS 0x03 > +#define APDS9160_CMD_LS_RESOLUTION_100MS 0x02 > +#define APDS9160_CMD_LS_RESOLUTION_200MS 0x01 > +#define APDS9160_PS_DATA_MASK 0x7FF > + > +#define APDS9160_DEFAULT_LS_GAIN 3 > +#define APDS9160_DEFAULT_LS_RATE 100 > +#define APDS9160_DEFAULT_PS_RATE 100 > +#define APDS9160_DEFAULT_PS_CANCELLATION_LEVEL 0 > +#define APDS9160_DEFAULT_PS_ANALOG_CANCELLATION 0 > +#define APDS9160_DEFAULT_PS_GAIN 1 > +#define APDS9160_DEFAULT_PS_CURRENT 100 > +#define APDS9160_DEFAULT_PS_RESOLUTION_11BITS 0x03 > + > +static const struct reg_default apds9160_reg_defaults[] = { > + { APDS9160_REG_CTRL, 0x00 }, /* Sensors disabled by default */ > + { APDS9160_REG_PS_LED, 0x33 }, /* 60 kHz frequency, 100 mA */ > + { APDS9160_REG_PS_PULSES, 0x08 }, /* 8 pulses */ > + { APDS9160_REG_PS_MEAS_RATE, 0x05 }, /* 100ms */ > + { APDS9160_REG_LS_MEAS_RATE, 0x22 }, /* 100ms */ > + { APDS9160_REG_LS_GAIN, 0x01 }, /* 3x */ > + { APDS9160_REG_INT_CFG, 0x10 }, /* Interrupts disabled */ > + { APDS9160_REG_INT_PST, 0x00 }, > + { APDS9160_REG_PS_THRES_HI_LSB, 0xFF }, > + { APDS9160_REG_PS_THRES_HI_MSB, 0x07 }, > + { APDS9160_REG_PS_THRES_LO_LSB, 0x00 }, > + { APDS9160_REG_PS_THRES_LO_MSB, 0x00 }, > + { APDS9160_REG_PS_CAN_LEVEL_DIG_LSB, 0x00 }, > + { APDS9160_REG_PS_CAN_LEVEL_DIG_MSB, 0x00 }, > + { APDS9160_REG_PS_CAN_LEVEL_ANA_DUR, 0x00 }, > + { APDS9160_REG_PS_CAN_LEVEL_ANA_CURRENT, 0x00 }, > + { APDS9160_REG_LS_THRES_UP_LSB, 0xFF }, > + { APDS9160_REG_LS_THRES_UP, 0xFF }, > + { APDS9160_REG_LS_THRES_UP_MSB, 0x0F }, > + { APDS9160_REG_LS_THRES_LO_LSB, 0x00 }, > + { APDS9160_REG_LS_THRES_LO, 0x00 }, > + { APDS9160_REG_LS_THRES_LO_MSB, 0x00 }, > + { APDS9160_REG_LS_THRES_VAR, 0x00 }, > +}; > + > +static const struct regmap_range apds9160_readable_ranges[] = { > + regmap_reg_range(APDS9160_REG_CTRL, APDS9160_REG_LS_THRES_VAR), > +}; > + > +static const struct regmap_access_table apds9160_readable_table = { > + .yes_ranges = apds9160_readable_ranges, > + .n_yes_ranges = ARRAY_SIZE(apds9160_readable_ranges), > +}; > + > +static const struct regmap_range apds9160_writeable_ranges[] = { > + regmap_reg_range(APDS9160_REG_CTRL, APDS9160_REG_LS_GAIN), > + regmap_reg_range(APDS9160_REG_INT_CFG, APDS9160_REG_LS_THRES_VAR), > +}; > + > +static const struct regmap_access_table apds9160_writeable_table = { > + .yes_ranges = apds9160_writeable_ranges, > + .n_yes_ranges = ARRAY_SIZE(apds9160_writeable_ranges), > +}; > + > +static const struct regmap_range apds9160_volatile_ranges[] = { > + regmap_reg_range(APDS9160_REG_SR, APDS9160_REG_LS_DATA_ALS_MSB), > +}; > + > +static const struct regmap_access_table apds9160_volatile_table = { > + .yes_ranges = apds9160_volatile_ranges, > + .n_yes_ranges = ARRAY_SIZE(apds9160_volatile_ranges), > +}; > + > +static const struct regmap_config apds9160_regmap_config = { > + .name = APDS9160_REGMAP_NAME, > + .reg_bits = 8, > + .val_bits = 8, > + .use_single_read = true, > + .use_single_write = true, > + > + .rd_table = &apds9160_readable_table, > + .wr_table = &apds9160_writeable_table, > + .volatile_table = &apds9160_volatile_table, > + > + .reg_defaults = apds9160_reg_defaults, > + .num_reg_defaults = ARRAY_SIZE(apds9160_reg_defaults), > + .max_register = APDS9160_REG_CNT, > + .cache_type = REGCACHE_RBTREE, > +}; > + > +static const struct iio_event_spec apds9160_event_spec[] = { > + { > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_RISING, > + .mask_separate = BIT(IIO_EV_INFO_VALUE), > + }, > + { > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_FALLING, > + .mask_separate = BIT(IIO_EV_INFO_VALUE), > + }, > + { > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_EITHER, > + .mask_separate = BIT(IIO_EV_INFO_ENABLE), > + }, > +}; > + > +static const struct iio_chan_spec apds9160_channels[] = { > + { > + /* Proximity sensor channel */ > + .type = IIO_PROXIMITY, > + .address = APDS9160_REG_PS_DATA_LSB, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | > + BIT(IIO_CHAN_INFO_CALIBSCALE) | > + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | > + BIT(IIO_CHAN_INFO_CALIBBIAS), Unusual to have integration time control on a light sensor but not in proximity mode. > + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) | > + BIT(IIO_CHAN_INFO_HARDWAREGAIN), > + .event_spec = apds9160_event_spec, > + .num_event_specs = ARRAY_SIZE(apds9160_event_spec), As below. Give IRQ is optional (which is good btw) you should not register the events if we have no way to actually get them. So unless I'm missing something you should have a second channel array with no events used form an iio_info structure with no event callbacks. > + }, > + { > + /* Proximity sensor led current */ > + .type = IIO_CURRENT, > + .output = 1, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS), > + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), > + }, > + { > + /* Illuminance */ > + .type = IIO_LIGHT, > + .address = APDS9160_REG_LS_DATA_ALS_LSB, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME) | > + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | > + BIT(IIO_CHAN_INFO_SCALE), > + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | > + BIT(IIO_CHAN_INFO_SCALE), > + .event_spec = apds9160_event_spec, > + .num_event_specs = ARRAY_SIZE(apds9160_event_spec), > + }, > + { > + /* Clear channel */ > + .type = IIO_INTENSITY, > + .address = APDS9160_REG_LS_DATA_CLEAR_LSB, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), > + .channel2 = IIO_MOD_LIGHT_CLEAR, > + .modified = 1, > + }, > +}; > + > +/* Attributes */ Random mixture of stuff somewhat related to attributes. I'd drop the comment as to my eyes it isn't really helpful. > +static const int apds9160_als_rate_avail[] = { > + 25, 50, 100, 200 > +}; > + > +/* It is very close to kernel-doc. I'd just make it compliant by adding the structure name and short description. > + * @itime: Integration time in ms > + * @gain: Gain multiplier > + * @scale1: lux/count resolution > + * @scale2: micro lux/count > + */ > +struct apds9160_scale { > + int itime; > + int gain; > + int scale1; > + int scale2; > +}; > + > +/* > + * Scale mapping extracted from datasheet > + */ Single line comment would be fine here. > +/* > + * This parameter works in conjunction with the cancellation pulse duration > + * The value determines the current used for crosstalk cancellation > + * B4-B5: The coarse value defines cancellation current in steps of 60nA > + * B0-B3: The fine value adjusts the cancellation current in steps of 2.4nA > + */ > +static int apds9160_set_ps_cancellation_current(struct apds9160_chip *data, > + int val) > +{ > + int ret; > + > + if (val < 0 || val > 0x3F) > + return -EINVAL; > + > + ret = regmap_write(data->regmap, APDS9160_REG_PS_CAN_LEVEL_ANA_CURRENT, > + val); > + if (!ret) For consistency, prefer the error cases out of line even if it adds a line or two of code. It just makes things a tiny bit easier to review when people are reading lots of code if everything is the same way around. Apply same logic in other similar cases. if (ret) return ret; data->ps... return 0; > + data->ps_cancellation_current = val; > + > + return ret; > +} > +static int apds9160_set_als_gain(struct apds9160_chip *data, int gain) > +{ > + int ret = -EINVAL; > + int idx; > + > + for (idx = 0; idx < ARRAY_SIZE(apds9160_als_gain_map); idx++) { > + if (gain == apds9160_als_gain_map[idx][0]) { > + ret = regmap_field_write(data->reg_als_gain, > + apds9160_als_gain_map[idx][1]); > + if (ret) > + return ret; > + > + data->als_hwgain = gain; > + break; As below. Early return simplifies this as the return below is then in error case only. Can also drag ret into this local scope. Make sure to apply to other similar cases. > + } > + } > + > + return ret; > +} > + > +static int apds9160_set_als_scale(struct apds9160_chip *data, int val, int val2) > +{ > + int ret = -EINVAL; > + int idx; > + > + for (idx = 0; idx < ARRAY_SIZE(apds9160_als_scale_map); idx++) { > + if (apds9160_als_scale_map[idx].itime == data->als_itime && > + apds9160_als_scale_map[idx].scale1 == val && > + apds9160_als_scale_map[idx].scale2 == val2) { > + ret = apds9160_set_als_gain(data, > + apds9160_als_scale_map[idx].gain); > + if (ret) > + return ret; > + > + data->als_scale1 = val; > + data->als_scale2 = val2; > + break; As below. Might as well return. > + } > + } > + > + return ret; return -EINVAL; as with the early return above, can't be anything else. > +} > +/* > + * Setting the integration time ajusts resolution, rate, scale and gain > + */ > +static int apds9160_set_als_int_time(struct apds9160_chip *data, int val) > +{ > + int ret = -EINVAL; > + int idx; > + > + ret = apds9160_set_als_rate(data, val); > + if (ret) > + return ret; > + > + /* Match resolution register with rate */ > + ret = apds9160_set_als_resolution(data, val); > + if (ret) > + return ret; > + > + data->als_itime = val; > + > + /* Set the scale minimum gain */ > + for (idx = 0; idx < ARRAY_SIZE(apds9160_als_scale_map); idx++) { > + if (data->als_itime == apds9160_als_scale_map[idx].itime) { Consider flipping it for shorter lines. if (data->als_itime != adps9160_als_scale_map[idx].itime) continue; return apds9160... > + ret = apds9160_set_als_scale(data, > + apds9160_als_scale_map[idx].scale1, > + apds9160_als_scale_map[idx].scale2); > + break; Might as well return here. > + } > + } > + > + return ret; return -EINVAL; Right now ret = 0 in this path after not matching which is not I think the intent. > +} > + > +static int apds9160_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int *val, > + int *val2, long mask) > +{ > + struct apds9160_chip *data = iio_priv(indio_dev); > + int ret = -EINVAL; > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + switch (chan->type) { > + case IIO_PROXIMITY: { > + __le16 buf; > + > + ret = regmap_bulk_read(data->regmap, chan->address, > + &buf, 2); > + if (ret) > + return ret; > + > + ret = IIO_VAL_INT; > + *val = le16_to_cpu(buf); > + /* Remove overflow bits from result */ > + *val = FIELD_GET(APDS9160_PS_DATA_MASK, *val); As below. Return if there is nothing else to do rather than make the reader go looking for end of the nested switch. Also return IIO_VAL_INT; is easier to follow than checking that the ret value was set in all good paths. > + } break; > + case IIO_LIGHT: > + case IIO_INTENSITY: { > + u8 buf[3]; > + > + ret = regmap_bulk_read(data->regmap, chan->address, > + &buf, 3); > + if (ret) > + return ret; > + > + ret = IIO_VAL_INT; > + *val = get_unaligned_le24(buf); > + } break; > + case IIO_CURRENT: > + ret = IIO_VAL_INT; > + *val = data->ps_current; > + break; > + default: > + break; > + } > + break; > + case IIO_CHAN_INFO_HARDWAREGAIN: > + switch (chan->type) { > + case IIO_LIGHT: > + ret = IIO_VAL_INT; > + *val = data->als_hwgain; > + break; > + case IIO_PROXIMITY: > + ret = IIO_VAL_INT; > + *val = data->ps_gain; > + break; > + default: > + break; > + } > + break; > + case IIO_CHAN_INFO_INT_TIME: > + switch (chan->type) { > + case IIO_LIGHT: > + ret = IIO_VAL_INT; > + *val = data->als_itime; > + break; > + default: > + break; > + } > + break; > + case IIO_CHAN_INFO_SAMP_FREQ: > + switch (chan->type) { > + case IIO_PROXIMITY: > + ret = IIO_VAL_INT; > + *val = data->ps_rate; > + break; > + default: > + break; > + } > + break; > + case IIO_CHAN_INFO_CALIBSCALE: > + switch (chan->type) { > + case IIO_PROXIMITY: > + ret = IIO_VAL_INT; > + *val = data->ps_cancellation_level; > + break; > + default: > + break; > + } > + break; > + case IIO_CHAN_INFO_CALIBBIAS: > + switch (chan->type) { > + case IIO_PROXIMITY: > + ret = IIO_VAL_INT; > + *val = data->ps_cancellation_analog; > + break; > + case IIO_CURRENT: > + ret = IIO_VAL_INT; > + *val = data->ps_cancellation_current; > + default: > + break; > + } > + break; > + case IIO_CHAN_INFO_SCALE: > + switch (chan->type) { > + case IIO_LIGHT: > + ret = IIO_VAL_INT_PLUS_MICRO; > + *val = data->als_scale1; > + *val2 = data->als_scale2; > + break; > + default: > + break; > + } > + break; > + default: > + break; > + } > + > + return ret; > +}; > + > +static inline int apds9160_get_thres_reg(const struct iio_chan_spec *chan, > + enum iio_event_direction dir, u8 *reg) > +{ > + switch (dir) { > + case IIO_EV_DIR_RISING: > + switch (chan->type) { > + case IIO_PROXIMITY: > + *reg = APDS9160_REG_PS_THRES_HI_LSB; > + break; > + case IIO_LIGHT: > + *reg = APDS9160_REG_LS_THRES_UP_LSB; > + break; > + default: > + return -EINVAL; > + } > + break; > + case IIO_EV_DIR_FALLING: > + switch (chan->type) { > + case IIO_PROXIMITY: > + *reg = APDS9160_REG_PS_THRES_LO_LSB; > + break; return in each leg to make the flow simpler to follow. Then drop all unneeded breaks and the final return 0. > + case IIO_LIGHT: > + *reg = APDS9160_REG_LS_THRES_LO_LSB; > + break; > + default: > + return -EINVAL; > + } > + break; > + default: > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int apds9160_read_event(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + enum iio_event_type type, > + enum iio_event_direction dir, > + enum iio_event_info info, int *val, int *val2) > +{ > + u8 reg; > + Drop this blank line. > + int ret = 0; Always set, so don't set it here. > + struct apds9160_chip *data = iio_priv(indio_dev); > + > + if (info != IIO_EV_INFO_VALUE) > + return -EINVAL; > + > + ret = apds9160_get_thres_reg(chan, dir, ®); > + if (ret < 0) > + return ret; > + > + switch (chan->type) { > + case IIO_PROXIMITY: { > + __le16 buf; > + > + ret = regmap_bulk_read(data->regmap, reg, &buf, 2); > + if (ret < 0) > + return ret; > + *val = le16_to_cpu(buf); > + return IIO_VAL_INT; > + } break; > + case IIO_LIGHT: { > + u8 buf[3]; > + > + ret = regmap_bulk_read(data->regmap, reg, &buf, 3); > + if (ret < 0) > + return ret; > + *val = get_unaligned_le24(buf); > + return IIO_VAL_INT; > + } break; Already returned. The compiler better be able to figure that out! So this break is unreachable code. Check for others of these. > + default: > + return -EINVAL; > + } > +} > + > +static int apds9160_write_event(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, > + enum iio_event_type type, > + enum iio_event_direction dir, > + enum iio_event_info info, int val, int val2) > +{ > + u8 reg; > + int ret = 0; Set in all paths where it is used, so don't set it here. > + struct apds9160_chip *data = iio_priv(indio_dev); > + > + if (info != IIO_EV_INFO_VALUE) > + return -EINVAL; > + > + ret = apds9160_get_thres_reg(chan, dir, ®); > + if (ret < 0) > + return ret; > + > + switch (chan->type) { > + case IIO_PROXIMITY: { > + if (val < 0 || val > APDS9160_PS_THRES_MAX) > + return -EINVAL; > + __le16 buf; > + > + buf = cpu_to_le16(val); > + return regmap_bulk_write(data->regmap, reg, &buf, 2); > + } > + case IIO_LIGHT: { > + if (val < 0 || val > APDS9160_LS_THRES_MAX) > + return -EINVAL; > + __le32 buf = 0; No need to initialise. Also, we still mostly respect old style c formtting, so that __le32 buf belongs before the checks on val. The only exceptions for this are when autocleanup is in use where the declaration location matters for correctness + various things so wrapped up in macros it's not obvious they are creating local variables. > + > + buf = cpu_to_le32(val); as with the above get... It is 3 bytes not 4 so use a u8 buf[3] and put_unaligned_le24() rather than a padded valued. > + return regmap_bulk_write(data->regmap, reg, &buf, 3); > + } > + default: > + return -EINVAL; > + } > +} > + > +static int apds9160_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 apds9160_chip *data = iio_priv(indio_dev); > + > + switch (chan->type) { > + case IIO_PROXIMITY: > + return data->ps_int; > + case IIO_LIGHT: > + return data->als_int; > + default: > + return -EINVAL; > + } > + > + return 0; Can't get here so remove this return. If that changes in future the compiler will complain and hence any bugs are easy to spot. In some circumstances it will complain about unreachable code with what you have here. > +} > + > +static int apds9160_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) > +{ > + struct apds9160_chip *data = iio_priv(indio_dev); > + int ret; > + > + switch (chan->type) { > + case IIO_PROXIMITY: > + ret = regmap_field_write(data->reg_int_ps, state); > + if (ret) > + return ret; > + data->ps_int = state; > + break; Easier to return 0 here > + case IIO_LIGHT: > + ret = regmap_field_write(data->reg_int_als, state); > + if (ret) > + return ret; > + data->als_int = state; > + break; and here. > + default: > + return -EINVAL; > + } > + and drop this one. > + return 0; > +} > + > +static int apds9160_detect(struct apds9160_chip *chip) > +{ > + struct i2c_client *client = chip->client; > + int ret; > + u32 val; > + > + ret = regmap_read(chip->regmap, APDS9160_REG_ID, &val); > + if (ret < 0) { > + dev_err(&client->dev, "ID read failed\n"); > + return ret; > + } > + > + if (val != APDS9160_PART_ID_0) > + dev_info(&client->dev, "Unsupported part id %u\n", val); > + > + return ret; I'd return 0 to make it explicit that this is always hit in the non error path only. Also makes it easier for the compiler to see that ret is never positive. > +} > + > +static void apds9160_disable(void *chip) > +{ > + struct apds9160_chip *data = (struct apds9160_chip *)chip; No need (in C spec) to ever explicitly cast from void * to other pointer types, so drop that case and any similar ones. > + int ret; > + > + ret = regmap_field_write(data->reg_enable_als, 0); > + if (ret) > + return; > + > + regmap_field_write(data->reg_enable_ps, 0); > +} > + > +static int apds9160_chip_init(struct apds9160_chip *chip) > +{ > + int ret; > + > + /* Write default values to interrupt register */ > + ret = regmap_field_write(chip->reg_int_ps, 0); > + chip->ps_int = 0; > + if (ret) > + return ret; > + > + ret = regmap_field_write(chip->reg_int_als, 0); > + chip->als_int = 0; > + if (ret) > + return ret; > + > + /* Write default values to control register */ > + ret = regmap_field_write(chip->reg_enable_als, 1); > + if (ret) > + return ret; > + > + ret = regmap_field_write(chip->reg_enable_ps, 1); > + if (ret) > + return ret; > + > + /* Write other default values */ > + ret = regmap_field_write(chip->reg_ps_resolution, > + APDS9160_DEFAULT_PS_RESOLUTION_11BITS); > + if (ret) > + return ret; > + > + /* Write default values to configuration registers */ > + ret = apds9160_set_ps_current(chip, APDS9160_DEFAULT_PS_CURRENT); > + if (ret) > + return ret; > + > + apds9160_set_ps_rate(chip, APDS9160_DEFAULT_PS_RATE); > + if (ret) > + return ret; > + > + apds9160_set_als_int_time(chip, APDS9160_DEFAULT_LS_RATE); > + if (ret) > + return ret; > + > + ret = apds9160_set_als_scale(chip, > + apds9160_100ms_avail[0][0], > + apds9160_100ms_avail[0][1]); > + if (ret) > + return ret; > + > + ret = apds9160_set_ps_gain(chip, APDS9160_DEFAULT_PS_GAIN); > + if (ret) > + return ret; > + > + ret = apds9160_set_ps_analog_cancellation( > + chip, APDS9160_DEFAULT_PS_ANALOG_CANCELLATION); > + if (ret) > + return ret; > + > + ret = apds9160_set_ps_cancellation_level( > + chip, APDS9160_DEFAULT_PS_CANCELLATION_LEVEL); > + if (ret) > + return ret; > + > + return devm_add_action_or_reset(&chip->client->dev, apds9160_disable, > + (void *)chip); No need to cast explicitly from any pointer type to a void *. > +} > +static const struct iio_info apds9160_info = { > + .read_avail = apds9160_read_avail, > + .read_raw = apds9160_read_raw, > + .write_raw = apds9160_write_raw, > + .write_raw_get_fmt = apds9160_write_raw_get_fmt, > + .read_event_value = apds9160_read_event, > + .write_event_value = apds9160_write_event, > + .read_event_config = apds9160_read_event_config, > + .write_event_config = apds9160_write_event_config, > +}; > + > +static int apds9160_probe(struct i2c_client *client) > +{ > + struct device *dev = &client->dev; > + struct apds9160_chip *chip; > + struct iio_dev *indio_dev; > + int ret; > + > + indio_dev = devm_iio_device_alloc(dev, sizeof(*chip)); > + if (!indio_dev) > + return -ENOMEM; > + > + ret = devm_regulator_get_enable(dev, "vdd"); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to enable vdd supply\n"); > + > + indio_dev->info = &apds9160_info; > + indio_dev->name = "apds9160"; > + indio_dev->channels = apds9160_channels; > + indio_dev->num_channels = ARRAY_SIZE(apds9160_channels); > + indio_dev->modes = INDIO_DIRECT_MODE; > + > + chip = iio_priv(indio_dev); > + i2c_set_clientdata(client, indio_dev); I'm not immediately able to find where this is used. If it's not then don't set it. > + chip->client = client; > + chip->regmap = devm_regmap_init_i2c(client, &apds9160_regmap_config); > + if (IS_ERR(chip->regmap)) > + return dev_err_probe(dev, PTR_ERR(chip->regmap), > + "regmap initialization failed.\n"); > + > + chip->client = client; > + mutex_init(&chip->lock); > + > + ret = apds9160_detect(chip); > + if (ret < 0) > + return dev_err_probe(dev, ret, "apds9160 not found\n"); > + > + ret = apds9160_regfield_init(chip); > + if (ret) > + return ret; > + > + ret = apds9160_chip_init(chip); > + if (ret) > + return ret; > + > + if (client->irq > 0) { As the interrupt is optional, we should hide all the event related elements if there is no interrupt to signal them. Normal way to do this is two iio_info structures that you pick between depending on availability of the irq. > + ret = devm_request_threaded_irq(dev, client->irq, NULL, > + apds9160_irq_handler, > + IRQF_ONESHOT, "apds9160_event", > + indio_dev); > + if (ret) { > + return dev_err_probe(dev, ret, > + "request irq (%d) failed\n", > + client->irq); > + } > + } > + > + ret = devm_iio_device_register(dev, indio_dev); > + if (ret) > + return dev_err_probe(dev, ret, > + "failed iio device registration\n"); > + > + return ret; > +} > + > +static const struct of_device_id apds9160_of_match[] = { > + { .compatible = "brcm,apds9160" }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, apds9160_of_match); > + > +static const struct i2c_device_id apds9160_id[] = { { "apds9160", 0 }, { } }; Hmm. Not sure why I missed this in v1. Format in a style that makes it easy to extend. static const struct i2c_device_id apds9160_id[] = { { "apds9160", 0 }, { } }; > +MODULE_DEVICE_TABLE(i2c, apds9160_id);