Re: [PATCH] iio:light: Add Avago APDS9930 ALS support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 19/03/15 16:43, Cristina Ciocan wrote:
> Hello Peter,
> 
> The APDS 9300 device is similar to the ALS component of the APDS
> 9930, although there are differences (Lux computation is different,
> gain handling is not the same). Moreover, the APDS 9930 can also
> support proximity, feature which the 9300 device does not offer. I
> have not yet managed to properly test the proximity implementation
> and this is the reason why I have only sent upstream the ALS
> implementation. Therefore, wouldn't the code get too complicated to
> mix the 2 devices in one driver ?
Certainly as a list of differences go, that one is pretty short.
Obviously it depends on how difficult those are to handle, but
sounds like picking between a couple of driver specific function pointer
structures in probe then carrying two additional functions (for the
ambient light sensor side of things).

It's always easy to not enable stuff for chip variants so the proximity
side should be fine as well.

I'd give a unified driver a quick go and see how much code you actually
save.  The big advantage of a unified driver is you get double the testing
of the unified code paths so any bugs get ironed out quickly as long
as the differences are cleanly handled!

Lots of examples of how to do this in similar drivers so take a look.

Jonathan
> 
> Thank you for the review, I will send a second version to fix style
> issues.
> 
> CMD_REG_SETUP clears the type bits and also sets them to
> transaction_type. This is because the transaction type can vary, thus
> we need to clear & set it accordingly (it differs when R/W bytes vs
> words).
> 
> Kind regards, Cristina
> 
> On Thu, Mar 19, 2015 at 3:41 PM, Peter Meerwald <pmeerw@xxxxxxxxxx <mailto:pmeerw@xxxxxxxxxx>> wrote:
> 
>     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 <mailto: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 <http://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 <http://coefs.ga>) != 0)
>     > +             data->__coefs.ga <http://coefs.ga> = defaults.ga <http://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 <mailto:cristina.ciocan@xxxxxxxxx>>");
>     > +MODULE_DESCRIPTION("APDS-9930 ALS sensor IIO driver");
>     > +MODULE_LICENSE("GPL v2");
>     >
> 
>     --
> 
>     Peter Meerwald
>     +43-664-2444418 <tel:%2B43-664-2444418> (mobile)
>     --
>     To unsubscribe from this list: send the line "unsubscribe linux-iio" in
>     the body of a message to majordomo@xxxxxxxxxxxxxxx <mailto:majordomo@xxxxxxxxxxxxxxx>
>     More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
> 
> 
> 
> -- 
>    Cristina Ciocan   

--
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




[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux