Hello Cristina, > Added IIO driver for Avago's APDS 9930 proximity and ALS sensor. The current > implementation is for ALS functionality. > > It offers ACPI/DT support, irq and event handling, raw readings. can this driver be implemented by extending the apds9300 driver? the structure seems similar? some general comments: * not all specifiers are prefixed with apds9930_ or APDS9930_ * the spelling of APDS9930 is APDS-9930 or APDS 9930 sometimes * is the CMD_REG_SETUP macro necessary, i.e. is there a need to mask out bits first, are they ever set? * style of multi-line comments regards, p. > Datasheet for this device can be found at > http://www.avagotech.com/docs/AV02-3190EN. > > Signed-off-by: Cristina Ciocan <cristina.ciocan@xxxxxxxxx> > --- > drivers/iio/light/Kconfig | 10 + > drivers/iio/light/Makefile | 1 + > drivers/iio/light/apds9930.c | 912 +++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 923 insertions(+) > create mode 100644 drivers/iio/light/apds9930.c > > diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig > index cd937d9..abeca21 100644 > --- a/drivers/iio/light/Kconfig > +++ b/drivers/iio/light/Kconfig > @@ -37,6 +37,16 @@ config APDS9300 > To compile this driver as a module, choose M here: the > module will be called apds9300. > > +config APDS9930 > + tristate "APDS9930 ambient light sensor" > + depends on I2C > + help > + Say Y here if you want to build a driver for the Avago APDS9930 > + ambient light sensor. > + > + To compile this driver as a module, choose M here: the > + module will be called apds9930. > + > config CM32181 > depends on I2C > tristate "CM32181 driver" > diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile > index ad7c30f..535d289 100644 > --- a/drivers/iio/light/Makefile > +++ b/drivers/iio/light/Makefile > @@ -6,6 +6,7 @@ > obj-$(CONFIG_ADJD_S311) += adjd_s311.o > obj-$(CONFIG_AL3320A) += al3320a.o > obj-$(CONFIG_APDS9300) += apds9300.o > +obj-$(CONFIG_APDS9930) += apds9930.o > obj-$(CONFIG_CM32181) += cm32181.o > obj-$(CONFIG_CM3232) += cm3232.o > obj-$(CONFIG_CM3323) += cm3323.o > diff --git a/drivers/iio/light/apds9930.c b/drivers/iio/light/apds9930.c > new file mode 100644 > index 0000000..47b53d1 > --- /dev/null > +++ b/drivers/iio/light/apds9930.c > @@ -0,0 +1,912 @@ > +/* > + * This is a driver for Avago APDS 9930 ALS sensor chip. It > + * is inspired from drivers/misc/apds990x.c to use IIO. > + * > + * The I2C slave address for this device is 0x39. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + */ > + > +#include <linux/module.h> > +#include <linux/i2c.h> > +#include <linux/pm.h> > +#include <linux/mutex.h> > +#include <linux/kernel.h> > +#include <linux/mutex.h> > +#include <linux/interrupt.h> > +#include <linux/acpi.h> > +#include <linux/delay.h> > +#include <linux/property.h> > + > +#include <linux/iio/types.h> > +#include <linux/iio/iio.h> > +#include <linux/iio/events.h> > + > +#include <linux/gpio/consumer.h> > + > +#define APDS9930_DRIVER_NAME "apds9930" > +#define APDS9930_IRQ_NAME "apds9930_irq" > +#define APDS9930_GPIO_NAME "apds9930_gpio" > + > +#define APDS9930_INIT_SLEEP 5 /* sleep for 5 ms before issuing commands */ > + > +/* I2C command register - fields and possible field values */ > +/* CMD bit value must be set to 1 when addressing command register */ > +#define APDS9930_CMD_SELECT 0x80 > +/* TYPE possible values */ > +#define APDS9930_CMD_TYPE_RB 0 /* Repeated byte */ > +#define APDS9930_CMD_TYPE_AUTO_INC BIT(5) /* Auto-increment */ > +#define APDS9930_CMD_TYPE_SPECIAL_FUNC GENMASK(6, 5) /* Special function */ > +/* ADDR possible values */ > +#define APDS9930_CMD_TYPE_ALS 0x6 /* ALS interrupt clear */ > +/* Clear masks */ > +#define APDS9930_CLEAR_CMD_TYPE_MASK GENMASK(4, 0) > +/* Shortcut to clear and set CMD and TYPE fields */ > +#define CMD_REG_SETUP(reg, transaction_type) { \ > + reg &= APDS9930_CLEAR_CMD_TYPE_MASK; \ > + reg |= APDS9930_CMD_SELECT | transaction_type; \ > +} > + > +/* Register set (rw = read/write, r = read, w = write) */ > +#define APDS9930_ENABLE_REG 0x00 /* rw-Enable of states and interrupts */ > +#define APDS9930_ATIME_REG 0x01 /* rw-ALS ADC time*/ > +#define APDS9930_WTIME_REG 0x03 /* rw-Wait time */ > +#define APDS9930_AILTL_REG 0x04 /* rw-ALS interrupt low threshold low > + * byte > + */ > +#define APDS9930_AIHTL_REG 0x06 /* rw-ALS interrupt high threshold low > + * byte > + */ > +#define APDS9930_PERS_REG 0x0C /* rw-Interrupt persistence filters */ > +#define APDS9930_CONFIG_REG 0x0D /* rw-Configuration */ > +#define APDS9930_CONTROL_REG 0x0F /* rw-Gain control register */ > +#define APDS9930_ID_REG 0x12 /* r-Device ID */ > +#define APDS9930_STATUS_REG 0x13 /* r-Device status */ > +#define APDS9930_CDATAL_REG 0x14 /* r-Ch0 ADC low data register */ > +#define APDS9930_IRDATAL_REG 0x16 /* r-Ch1 ADC low data register */ > + > +/* Useful bits per register */ > + > +/* Enable register */ > +#define APDS9930_AIEN BIT(4) /* ALS interrupt enable */ > +/* Persistence register */ > +#define APDS9930_APERS_SHIFT 0 /* Interrupt persistence */ > +/* Configuration register */ > +#define APDS9930_AGL_SHIFT 2 /* ALS gain level */ > +/* Control register */ > +#define APDS9930_AGAIN_SHIFT 0 /* ALS gain control */ > +/* Device ID - possible values */ > +#define APDS9930_ID 0x39 > +/* Status register */ > +#define APDS9930_AINT BIT(4) /* ALS interrupt */ > + > +/* Default values (already shifted) for registers content */ > +#define APDS9930_DISABLE_ALL 0 /* Disable and powerdown */ > +#define APDS9930_ENABLE_ALL 0x13 /* Set all ALS bits and power on */ > +#define APDS9930_DEF_ATIME 0xdb /* 101 ms */ > +#define APDS9930_DEF_WTIME 0xff /* 2.7 ms - min wait time */ > +#define APDS9930_DEF_AGAIN 2 /* 16 x ALS gain */ > +#define APDS9930_DEF_APERS 0 /* Each over/underflow triggers an > + * interrupt > + */ > + > +/* > + * Interrupt threshold defaults - setting low to maximum will trigger a first > + * interrupt to allow us to read the data registers status and properly set a > + * threshold value to match the current environment. > + * > + * The default high threshold is set only for consistency, since the registers > + * are checked against in order: first if the value of CDATA is above LOW; if > + * so, trigger interrupt and do not check the HIGH threshold. > + */ > +#define APDS9930_DEF_ALS_THRESH_LOW 0xffff > +#define APDS9930_DEF_ALS_THRESH_HIGH 0x0 > + > +/* Default, open air, coefficients (for Lux computation) */ > +#define DEF_DF 52 /* Device factor */ > +/* > + * Material-depending coefficients (set to open air values). They are scaled for > + * computation purposes by 100. > + */ > +#define DEF_LUX_DIVISION_SCALE 10000 > +#define DEF_GA 49 > +#define DEF_B 186 > +#define DEF_C 74 > +#define DEF_D 129 > + > +/* Possible ALS gain values (with AGL cleared) */ > +static const int again_values[] = {1, 8, 16, 120}; > +#define APDS9930_MAX_AGAIN_INDEX 3 > +#define APDS9930_AGL_DIVISION_SCALE 6 > + > +/* > + * Percentages from the maximum CH0 value that indicate the recorded CH0 data is > + * too low or too high - for AGAIN adapting purposes. > + */ > +#define APDS9930_CH0_HIGH_PERCENTAGE 90 > +#define APDS9930_CH0_LOW_PERCENTAGE 10 > + > +/* > + * With how much (in percentages) must the CH0 value differ from one step to > + * another in order to consider a significant change in light and trigger an > + * interrupt. > + */ > +#define APDS9930_ALS_HYSTERESIS 20 > + > +/* > + * Number of consecutive out-of-range measurements needed to trigger a gain > + * change. We count them and not change the gain each time the CH0 data is > + * out-of-range because it takes a couple of cycles for system to adapt to new > + * gain values. > + */ > +#define APDS9930_MAX_COUNTS_FOR_GAIN_CHANGE 2 > + > +/* DT or ACPI device properties names */ > +#define GA_PROP "intel,ga" > +#define COEF_B_PROP "intel,coeff-B" > +#define COEF_C_PROP "intel,coeff-B" > +#define COEF_D_PROP "intel,coeff-D" > +#define DF_PROP "intel,df" > +#define AGAIN_PROP "intel,als-gain" > +#define ATIME_PROP "intel,atime" > + > +struct apds9930_coefficients { > + /* GA, B, C and D coefficients are scaled by 100 for computational > + purposes. */ > + int ga; > + int coef_a; /* This is 1, but needs to be set to a scaled value, thus if > + we decide to change the scale, this coefficient must also > + be changed. */ > + int coef_b; > + int coef_c; > + int coef_d; > + int df; > +}; > + > +struct apds9930_device_data { > + struct apds9930_coefficients coefs; > + > + u8 again; /* AGAIN value (index in again_values[]) */ > + u8 atime; /* ALS integration time */ > +}; > + > +struct apds9930_threshold { > + u16 low; > + u16 high; > +}; > + > +struct apds9930_data { > + struct i2c_client *client; > + struct mutex mutex; /* Protect access to device data */ > + > + struct apds9930_device_data device_data; > + > +#define __coefs device_data.coefs > +#define __again device_data.again > +#define __atime device_data.atime > + > + u8 alsit; > + u16 ch0_max; /* Maximum possible Ch0 data value */ > + bool agl_enabled; > + int again_change; /* Number of consecutive situations in > + * which the gain needed to be changed > + */ > + bool als_intr_state; > + struct apds9930_threshold als_thresh; > +}; > + > +/* > + * Writes data to the register; the next i2c_write call will write to the same > + * register. > + */ > +static int apds9930_write_byte(struct i2c_client *client, u8 reg, u8 data) > +{ > + CMD_REG_SETUP(reg, APDS9930_CMD_TYPE_RB); > + > + return i2c_smbus_write_byte_data(client, reg, data); > +} > + > +/* > + * Reads data from a register; the next i2c_read call will read from the same > + * address. > + */ > +static int apds9930_read_byte(struct i2c_client *client, u8 reg, u8 *data) > +{ > + int ret; > + > + CMD_REG_SETUP(reg, APDS9930_CMD_TYPE_RB); > + ret = i2c_smbus_read_byte_data(client, reg); > + *data = ret; > + > + return ret; > +} > + > +/* Writes 2 bytes at the given address */ > +static int apds9930_write_word(struct i2c_client *client, u8 reg, u16 data) > +{ > + CMD_REG_SETUP(reg, APDS9930_CMD_TYPE_AUTO_INC); > + > + return i2c_smbus_write_word_data(client, reg, data); > +} > + > +/* Reads 2 bytes from the given address */ > +static int apds9930_read_word(struct i2c_client *client, u8 reg, u16 *data) > +{ > + int ret; > + > + CMD_REG_SETUP(reg, APDS9930_CMD_TYPE_AUTO_INC); > + ret = i2c_smbus_read_word_data(client, reg); > + *data = ret; > + > + return ret; > +} > + > +/* ALSIT = 2.73ms * (256 - ATIME), 2.73 = 5591/(2^11) */ > +static inline u8 atime_to_alsit(u8 atime_val) > +{ > + return (u8)(((256 - (u32)atime_val) * 5591) >> 11); > +} > + > +/* ATIME = 256 - ALSIT/2.73ms, 1/2.73 = 375/(2^10) */ > +static inline u8 alsit_to_atime(u8 alsit_val) > +{ > + return (u8)(256 - (((u32)alsit_val * 375) >> 10)); > +} > + > +/* Computes maximum Ch0 data when atime is the given one. */ > +static inline u16 apds9930_compute_max_ch0(u8 atime_val) > +{ > + return 1024 * (256 - (u32)atime_val) - 1; > +} > + > +/* Computes the ALS gain value for the next step and updates the current one */ > +static void apds9930_update_again(struct apds9930_data *data, u16 ch0) > +{ > + int current_index, next_index, err; > + > + /* Compute the value for the next measurement */ > + current_index = data->__again; > + next_index = data->__again; > + > + /* CH0 data too high, try to lower the ALS gain if possible */ > + if (ch0 >= (data->ch0_max * APDS9930_CH0_HIGH_PERCENTAGE) / 100) { > + if (data->again_change + 1 < > + APDS9930_MAX_COUNTS_FOR_GAIN_CHANGE) > + data->again_change++; > + else { > + data->again_change = 0; > + > + if (next_index == 0 && !(data->agl_enabled)) { > + err = apds9930_write_byte( > + data->client, > + APDS9930_CONFIG_REG, > + 1 << APDS9930_AGL_SHIFT); > + if (!err) > + data->agl_enabled = true; > + } else if (next_index > 0) { > + next_index--; > + } > + } > + } > + > + /* CH0 data too low, try to increase the ALS gain if possible */ > + else if (ch0 <= (data->ch0_max * APDS9930_CH0_LOW_PERCENTAGE) / 100) { > + if (data->again_change + 1 < > + APDS9930_MAX_COUNTS_FOR_GAIN_CHANGE) > + data->again_change++; > + else { > + data->again_change = 0; > + if (data->agl_enabled) { > + err = apds9930_write_byte(data->client, > + APDS9930_CONFIG_REG, > + 0); > + if (!err) > + data->agl_enabled = false; > + } else if (next_index < APDS9930_MAX_AGAIN_INDEX) { > + next_index++; > + } > + } > + } > + > + if (next_index != current_index) { > + /* Update data's index value */ > + data->__again = next_index; > + > + /* Update AGAIN for the next reading */ > + apds9930_write_byte(data->client, APDS9930_CONTROL_REG, > + data->__again << APDS9930_AGAIN_SHIFT); > + } > +} > + > +/* > + * Update thresholds so as to generate interrupts when the CH0 data changes > + * significantly since the last update; significantly is quantifies by the > + * hysteresis factor: if the ALS state changes with more than hysteresis %, > + * update the thresholds. > + */ > +static void apds9930_update_als_thresholds(struct apds9930_data *data, u16 ch0) > +{ > + data->als_thresh.low = (ch0*(100 - APDS9930_ALS_HYSTERESIS))/100; > + data->als_thresh.high = min((ch0*(100 + APDS9930_ALS_HYSTERESIS))/100, > + (int)data->ch0_max); > + > + apds9930_write_word(data->client, APDS9930_AILTL_REG, > + data->als_thresh.low); > + apds9930_write_word(data->client, APDS9930_AIHTL_REG, > + data->als_thresh.high); > +} > + > +/* Having the channel 0 and channels 1's data, compute the Lux value */ > +static int apds9930_compute_lux(struct apds9930_data *data, u16 ch0, u16 ch1) > +{ > + long int iac1, iac2, alsit_val, again_val, tmp_iac; > + unsigned long int iac, lux; > + struct apds9930_coefficients cf; > + > + /* Lux equation */ > + cf = data->__coefs; > + iac1 = cf.coef_a * ch0 - cf.coef_b * ch1; > + iac2 = cf.coef_c * ch0 - cf.coef_d * ch1; > + tmp_iac = max(iac1, iac2); > + iac = (tmp_iac < 0) ? 0 : (unsigned long)tmp_iac; > + alsit_val = (int)(data->alsit); > + again_val = again_values[data->__again]; > + lux = (iac * cf.ga * cf.df) / (alsit_val * again_val * > + DEF_LUX_DIVISION_SCALE); > + > + return data->agl_enabled ? (APDS9930_AGL_DIVISION_SCALE * lux) : lux; > +} > + > +static int apds9930_enable_all(struct apds9930_data *data) > +{ > + int ret; > + > + mutex_lock(&data->mutex); > + ret = apds9930_write_byte(data->client, APDS9930_ENABLE_REG, > + APDS9930_ENABLE_ALL); > + if (ret < 0) > + goto err; > + > + data->als_intr_state = true; > +err: > + mutex_unlock(&data->mutex); > + > + return ret; > +} > + > +static int apds9930_disable_all(struct apds9930_data *data) > +{ > + int ret; > + > + mutex_lock(&data->mutex); > + ret = apds9930_write_byte(data->client, APDS9930_ENABLE_REG, > + APDS9930_DISABLE_ALL); > + if (ret < 0) > + goto err; > + > + data->als_intr_state = false; > +err: > + mutex_unlock(&data->mutex); > + > + return ret; > +} > + > +static int apds9930_chip_detect(struct apds9930_data *data) > +{ > + int ret; > + u8 id; > + > + ret = apds9930_read_byte(data->client, APDS9930_ID_REG, &id); > + if (ret < 0) > + return ret; > + > + if (id != APDS9930_ID) > + ret = -EINVAL; > + > + return ret; > +} > + > +/* > + * Set the coefficients to those specified in properties if they exist, > + * otherwise to default values. > + */ > +static void apds9930_set_device_data(struct apds9930_data *data) > +{ > + struct apds9930_coefficients defaults = { > + .ga = DEF_GA, > + .coef_a = 100, > + .coef_b = DEF_B, > + .coef_c = DEF_C, > + .coef_d = DEF_D, > + .df = DEF_DF, > + }; > + struct device *dev = &data->client->dev; > + > + /* > + * Look for device properties and set them to property value or to > + * default. > + */ > + if (device_property_read_u32(dev, GA_PROP, &data->__coefs.ga) != 0) > + data->__coefs.ga = defaults.ga; > + > + if (device_property_read_u32(dev, DF_PROP, &data->__coefs.df) != 0) > + data->__coefs.df = defaults.df; > + > + if (device_property_read_u32(dev, COEF_B_PROP, > + &data->__coefs.coef_b) != 0 || > + device_property_read_u32(dev, COEF_C_PROP, > + &data->__coefs.coef_c) != 0 || > + device_property_read_u32(dev, COEF_D_PROP, > + &data->__coefs.coef_d) != 0) { > + data->__coefs.coef_b = defaults.coef_b; > + data->__coefs.coef_c = defaults.coef_c; > + data->__coefs.coef_d = defaults.coef_d; > + } > + data->__coefs.coef_a = defaults.coef_a; > + > + if (device_property_read_u32(dev, ATIME_PROP, > + (int *)&data->__atime) != 0) > + data->__atime = APDS9930_DEF_ATIME; > + > + if (device_property_read_u32(dev, AGAIN_PROP, > + (int *)&data->__again) != 0) > + data->__again = APDS9930_DEF_AGAIN; > + > + /* > + * AGAIN can only have the following values (as found in the register): > + * 0, 1, 2, 3. Check if we receive invalid data and fall back to > + * defaults if that is the case. > + */ > + if (data->__again > APDS9930_MAX_AGAIN_INDEX) > + data->__again = APDS9930_DEF_AGAIN; > +} > + > +static void apds9930_chip_data_init(struct apds9930_data *data) > +{ > + apds9930_set_device_data(data); > + > + /* Init data to default values */ > + data->alsit = atime_to_alsit(data->__atime); > + data->ch0_max = apds9930_compute_max_ch0(APDS9930_DEF_ATIME); > + data->agl_enabled = false; > + data->again_change = 0; > + data->als_intr_state = false; > + data->als_thresh.low = APDS9930_DEF_ALS_THRESH_LOW; > + data->als_thresh.high = APDS9930_DEF_ALS_THRESH_HIGH; > +} > + > +/* Basic chip initialization, as described in the datasheet */ > +static int apds9930_chip_registers_init(struct apds9930_data *data) > +{ > + struct i2c_client *client = data->client; > + int ret; > + > + /* Disable and powerdown device */ > + ret = apds9930_disable_all(data); > + if (ret < 0) > + return ret; > + > + /* Set timing registers default values (minimum) */ > + ret = apds9930_write_byte(client, APDS9930_ATIME_REG, > + APDS9930_DEF_ATIME); > + if (ret < 0) > + return ret; > + ret = apds9930_write_byte(client, APDS9930_WTIME_REG, > + APDS9930_DEF_WTIME); > + if (ret < 0) > + return ret; > + > + /* Reset the configuration register (do not wait long) */ > + ret = apds9930_write_byte(client, APDS9930_CONFIG_REG, 0); > + if (ret < 0) > + return ret; > + > + /* Gain selection setting */ > + ret = apds9930_write_byte(client, APDS9930_CONTROL_REG, > + data->__again << APDS9930_AGAIN_SHIFT); > + if (ret < 0) > + return ret; > + > + /* Interrupt threshold default settings */ > + ret = apds9930_write_word(client, APDS9930_AILTL_REG, > + APDS9930_DEF_ALS_THRESH_LOW); > + if (ret < 0) > + return ret; > + ret = apds9930_write_word(client, APDS9930_AIHTL_REG, > + APDS9930_DEF_ALS_THRESH_HIGH); > + > + /* Set ALS persistence filter to default values */ > + ret = apds9930_write_byte(client, APDS9930_PERS_REG, > + APDS9930_DEF_APERS << APDS9930_APERS_SHIFT); > + if (ret < 0) > + return ret; > + > + /* Power the device back on */ > + return apds9930_enable_all(data); > +} > + > +static int apds9930_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int *val, > + int *val2, long mask) > +{ > + struct apds9930_data *data = iio_priv(indio_dev); > + struct i2c_client *client = data->client; > + int ret; > + u16 ch0, ch1, ch; > + > + mutex_lock(&data->mutex); > + switch (chan->type) { > + case IIO_LIGHT: > + /* Get Lux value */ > + ret = apds9930_read_word(client, APDS9930_CDATAL_REG, > + &ch0); > + if (ret < 0) > + break; > + ret = apds9930_read_word(client, APDS9930_IRDATAL_REG, > + &ch1); > + if (ret < 0) > + break; > + > + /* Compute Lux value */ > + *val = apds9930_compute_lux(data, ch0, ch1); > + apds9930_update_again(data, ch0); > + ret = IIO_VAL_INT; > + break; > + case IIO_INTENSITY: > + /* Get ch0 or ch1 raw value */ > + ret = apds9930_read_word(client, chan->channel ? > + APDS9930_IRDATAL_REG : > + APDS9930_CDATAL_REG, > + &ch); > + if (ret < 0) > + break; > + > + *val = (int)ch; > + ret = IIO_VAL_INT; > + break; > + default: > + ret = -EINVAL; > + break; > + } > + mutex_unlock(&data->mutex); > + > + return ret; > +} > + > +/* Event handling functions */ > +static int apds9930_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 apds9930_data *data = iio_priv(indio_dev); > + > + switch (chan->type) { > + case IIO_INTENSITY: > + return data->als_intr_state; > + default: > + return -EINVAL; > + } > +} > + > +static int apds9930_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 apds9930_data *data = iio_priv(indio_dev); > + struct i2c_client *client = data->client; > + u8 enable_reg; > + int ret; > + > + if (chan->type != IIO_INTENSITY) > + return -EINVAL; > + > + mutex_lock(&data->mutex); > + > + ret = apds9930_read_byte(client, APDS9930_ENABLE_REG, &enable_reg); > + if (ret < 0) > + goto err; > + > + if (state) > + enable_reg |= APDS9930_AIEN; /* enable */ > + else > + enable_reg &= ~APDS9930_AIEN; /* disable */ > + > + ret = apds9930_write_byte(client, APDS9930_ENABLE_REG, enable_reg); > + if (ret == 0) > + data->als_intr_state = (bool)state; > +err: > + mutex_unlock(&data->mutex); > + > + return ret; > +} > + > +static int apds9930_read_event_value(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) > +{ > + struct apds9930_data *data = iio_priv(indio_dev); > + struct apds9930_threshold thresh; > + > + switch (chan->type) { > + case IIO_INTENSITY: > + thresh = data->als_thresh; > + break; > + default: > + return -EINVAL; > + } > + > + switch (dir) { > + case IIO_EV_DIR_RISING: > + *val = (int)(thresh.high); > + break; > + case IIO_EV_DIR_FALLING: > + *val = (int)(thresh.low); > + break; > + default: > + return -EINVAL; > + } > + > + return IIO_VAL_INT; > +} > + > +/* IIO device specific data structures */ > +static const struct iio_info apds9930_info = { > + .driver_module = THIS_MODULE, > + .read_raw = apds9930_read_raw, > + .read_event_config = apds9930_read_event_config, > + .write_event_config = apds9930_write_event_config, > + .read_event_value = apds9930_read_event_value, > +}; > + > +static const struct iio_event_spec apds9930_als_event_spec[] = { > + { > + /* ALS Ch0 interrupt "above threshold" event */ > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_RISING, > + .mask_separate = BIT(IIO_EV_INFO_VALUE) | > + BIT(IIO_EV_INFO_ENABLE), > + }, { > + /* ALS Ch0 interrupt "below threshold" event */ > + .type = IIO_EV_TYPE_THRESH, > + .dir = IIO_EV_DIR_FALLING, > + .mask_separate = BIT(IIO_EV_INFO_VALUE) | > + BIT(IIO_EV_INFO_ENABLE), > + }, > +}; > + > +static const struct iio_chan_spec apds9930_channels[] = { > + /* ALS channels */ > + { > + /* Lux (processed Ch0 + Ch1) */ > + .type = IIO_LIGHT, > + .channel = 0, > + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), > + }, { > + /* > + * Ch0 photodiode (visible light + infrared); threshold > + * triggered event > + */ > + .type = IIO_INTENSITY, > + .channel = 0, > + .modified = true, > + .channel2 = IIO_MOD_LIGHT_BOTH, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), > + .event_spec = apds9930_als_event_spec, > + .num_event_specs = ARRAY_SIZE(apds9930_als_event_spec), > + }, { > + /* Ch1 photodiode (infrared) */ > + .type = IIO_INTENSITY, > + .channel = 1, > + .modified = true, > + .channel2 = IIO_MOD_LIGHT_IR, > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), > + } > +}; > + > +/* Determine what has triggered the interrupt and clear it accordingly */ > +static int apds9930_interrupt_clear(struct apds9930_data *data, u8 intr_status) > +{ > + u8 reg = 0x00; > + > + if (intr_status & APDS9930_AINT) { > + CMD_REG_SETUP(reg, APDS9930_CMD_TYPE_SPECIAL_FUNC); > + reg |= APDS9930_CMD_TYPE_ALS; > + > + return i2c_smbus_read_byte_data(data->client, reg); > + } > + > + return -EINVAL; > +} > + > +/* ALS interrupt handler */ > +static irqreturn_t apds9930_irq_handler(int irq, void *private_data) > +{ > + struct iio_dev *indio_dev = private_data; > + struct apds9930_data *data = iio_priv(indio_dev); > + u8 status, enable_reg; > + u16 ch0; > + int ret; > + > + /* Disable ALS function while processing data */ > + ret = apds9930_read_byte(data->client, APDS9930_ENABLE_REG, > + &enable_reg); > + if (ret < 0) > + return IRQ_HANDLED; > + ret = apds9930_write_byte(data->client, APDS9930_ENABLE_REG, 1); > + if (ret < 0) > + return IRQ_HANDLED; > + > + /* Read status register to see what caused the interrupt */ > + ret = apds9930_read_byte(data->client, APDS9930_STATUS_REG, &status); > + if (ret < 0) > + goto err; > + > + ret = apds9930_read_word(data->client, APDS9930_CDATAL_REG, &ch0); > + if (ret < 0) > + goto err; > + > + apds9930_update_again(data, ch0); > + > + /* Push event to userspace */ > + if (status & APDS9930_AINT) { > + /* Clear interrupt */ > + apds9930_interrupt_clear(data, status); > + > + iio_push_event(indio_dev, > + IIO_MOD_EVENT_CODE(IIO_INTENSITY, > + 0, > + IIO_MOD_LIGHT_BOTH, > + IIO_EV_TYPE_THRESH, > + IIO_EV_DIR_EITHER), > + iio_get_time_ns()); > + > + /* Update ALS thresholds to environment */ > + apds9930_update_als_thresholds(data, ch0); > + } > + > +err: > + /* Re-enable ALS converter */ > + apds9930_write_byte(data->client, APDS9930_ENABLE_REG, enable_reg); > + > + return IRQ_HANDLED; > +} > + > +static int apds9930_gpio_probe(struct i2c_client *client, > + struct apds9930_data *data) > +{ > + struct device *dev; > + struct gpio_desc *gpio; > + int ret; > + > + if (!client) > + return -EINVAL; > + > + dev = &client->dev; > + > + gpio = devm_gpiod_get_index(dev, APDS9930_GPIO_NAME, 0); > + if (IS_ERR(gpio)) { > + dev_err(dev, "ACPI GPIO get index failed\n"); > + return PTR_ERR(gpio); > + } > + > + ret = gpiod_direction_input(gpio); > + if (ret) > + return ret; > + > + return gpiod_to_irq(gpio); > +} > + > +static int apds9930_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct apds9930_data *data; > + struct iio_dev *indio_dev; > + int ret; > + > + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); > + if (!indio_dev) > + return -ENOMEM; > + > + data = iio_priv(indio_dev); > + i2c_set_clientdata(client, indio_dev); > + data->client = client; > + > + mutex_init(&data->mutex); > + > + /* Recommended wait time after power on. */ > + msleep(APDS9930_INIT_SLEEP); > + > + /* Check if the chip is the one we are expecting */ > + ret = apds9930_chip_detect(data); > + if (ret < 0) > + goto err; > + > + apds9930_chip_data_init(data); > + ret = apds9930_chip_registers_init(data); > + if (ret < 0) > + goto err; > + > + indio_dev->dev.parent = &client->dev; > + indio_dev->name = APDS9930_DRIVER_NAME; > + indio_dev->modes = INDIO_DIRECT_MODE; > + indio_dev->channels = apds9930_channels; > + indio_dev->num_channels = ARRAY_SIZE(apds9930_channels); > + indio_dev->info = &apds9930_info; > + > + if (client->irq <= 0) > + client->irq = apds9930_gpio_probe(client, data); > + > + if (client->irq > 0) { > + ret = devm_request_threaded_irq(&client->dev, > + client->irq, > + NULL, > + apds9930_irq_handler, > + IRQF_TRIGGER_FALLING | > + IRQF_ONESHOT, > + APDS9930_IRQ_NAME, > + indio_dev); > + if (ret < 0) > + goto err; > + } > + > + ret = iio_device_register(indio_dev); > + if (ret < 0) > + goto err; > + > + return 0; > +err: > + apds9930_disable_all(data); > + > + return ret; > +} > + > +static int apds9930_remove(struct i2c_client *client) > +{ > + struct iio_dev *indio_dev = i2c_get_clientdata(client); > + struct apds9930_data *data = iio_priv(indio_dev); > + int ret; > + > + /* Disable interrupts */ > + ret = apds9930_disable_all(data); > + > + iio_device_unregister(indio_dev); > + > + return ret; > +} > + > +static const struct acpi_device_id apds9930_acpi_table[] = { > + {"APDS9930", 0}, > + {} > +}; > +MODULE_DEVICE_TABLE(acpi, apds9930_acpi_table); > + > +static const struct i2c_device_id apds9930_ids_table[] = { > + {"apds9930", 0}, > + {} > +}; > +MODULE_DEVICE_TABLE(i2c, apds9930_ids_table); > + > +static struct i2c_driver apds9930_iio_driver = { > + .driver = { > + .owner = THIS_MODULE, > + .name = APDS9930_DRIVER_NAME, > + .acpi_match_table = ACPI_PTR(apds9930_acpi_table), > + }, > + .probe = apds9930_probe, > + .remove = apds9930_remove, > + .id_table = apds9930_ids_table, > +}; > +module_i2c_driver(apds9930_iio_driver); > + > +MODULE_AUTHOR("Cristina Ciocan <cristina.ciocan@xxxxxxxxx>"); > +MODULE_DESCRIPTION("APDS-9930 ALS sensor IIO driver"); > +MODULE_LICENSE("GPL v2"); > -- Peter Meerwald +43-664-2444418 (mobile) -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html