diff options
author | Tiberiu Breana <tiberiu.a.breana@intel.com> | 2015-04-27 18:34:01 +0300 |
---|---|---|
committer | Jonathan Cameron <jic23@kernel.org> | 2015-05-10 20:31:42 +0100 |
commit | 3dd477acbdd1f147f432a742afc5521168341461 (patch) | |
tree | f41d2da9611023b7fa876f3e7274b08df9c47e63 /drivers/iio | |
parent | be9e6229d67696f94c2f2331a2e207beff9bfc7f (diff) |
iio: light: Add threshold interrupt support for STK3310
Added interrupt support for proximity threshold events
to the stk3310 driver.
Signed-off-by: Tiberiu Breana <tiberiu.a.breana@intel.com>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
Diffstat (limited to 'drivers/iio')
-rw-r--r-- | drivers/iio/light/stk3310.c | 255 |
1 files changed, 253 insertions, 2 deletions
diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c index 0f8c450fc4a2..e79b9d89b024 100644 --- a/drivers/iio/light/stk3310.c +++ b/drivers/iio/light/stk3310.c @@ -12,15 +12,22 @@ #include <linux/acpi.h> #include <linux/i2c.h> +#include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/regmap.h> +#include <linux/gpio/consumer.h> +#include <linux/iio/events.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> #define STK3310_REG_STATE 0x00 #define STK3310_REG_PSCTRL 0x01 #define STK3310_REG_ALSCTRL 0x02 +#define STK3310_REG_INT 0x04 +#define STK3310_REG_THDH_PS 0x06 +#define STK3310_REG_THDL_PS 0x08 +#define STK3310_REG_FLAG 0x10 #define STK3310_REG_PS_DATA_MSB 0x11 #define STK3310_REG_PS_DATA_LSB 0x12 #define STK3310_REG_ALS_DATA_MSB 0x13 @@ -34,10 +41,14 @@ #define STK3310_CHIP_ID_VAL 0x13 #define STK3311_CHIP_ID_VAL 0x1D +#define STK3310_PSINT_EN 0x01 #define STK3310_PS_MAX_VAL 0xFFFF +#define STK3310_THRESH_MAX 0xFFFF #define STK3310_DRIVER_NAME "stk3310" #define STK3310_REGMAP_NAME "stk3310_regmap" +#define STK3310_EVENT "stk3310_event" +#define STK3310_GPIO "stk3310_gpio" #define STK3310_SCALE_AVAILABLE "6.4 1.6 0.4 0.1" @@ -67,7 +78,12 @@ static const struct reg_field stk3310_reg_field_als_it = REG_FIELD(STK3310_REG_ALSCTRL, 0, 3); static const struct reg_field stk3310_reg_field_ps_it = REG_FIELD(STK3310_REG_PSCTRL, 0, 3); - +static const struct reg_field stk3310_reg_field_int_ps = + REG_FIELD(STK3310_REG_INT, 0, 2); +static const struct reg_field stk3310_reg_field_flag_psint = + REG_FIELD(STK3310_REG_FLAG, 4, 4); +static const struct reg_field stk3310_reg_field_flag_nf = + REG_FIELD(STK3310_REG_FLAG, 0, 0); /* * Maximum PS values with regard to scale. Used to export the 'inverse' * PS value (high values for far objects, low values for near objects). @@ -96,12 +112,33 @@ struct stk3310_data { struct mutex lock; bool als_enabled; bool ps_enabled; + u64 timestamp; struct regmap *regmap; struct regmap_field *reg_state; struct regmap_field *reg_als_gain; struct regmap_field *reg_ps_gain; struct regmap_field *reg_als_it; struct regmap_field *reg_ps_it; + struct regmap_field *reg_int_ps; + struct regmap_field *reg_flag_psint; + struct regmap_field *reg_flag_nf; +}; + +static const struct iio_event_spec stk3310_events[] = { + /* Proximity event */ + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + /* Out-of-proximity event */ + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, }; static const struct iio_chan_spec stk3310_channels[] = { @@ -118,6 +155,8 @@ static const struct iio_chan_spec stk3310_channels[] = { BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = stk3310_events, + .num_event_specs = ARRAY_SIZE(stk3310_events), } }; @@ -156,6 +195,118 @@ static int stk3310_get_index(const int table[][2], int table_size, return -EINVAL; } +static int stk3310_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; + u16 buf; + int ret; + unsigned int index; + struct stk3310_data *data = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + /* + * Only proximity interrupts are implemented at the moment. + * Since we're inverting proximity values, the sensor's 'high' + * threshold will become our 'low' threshold, associated with + * 'near' events. Similarly, the sensor's 'low' threshold will + * be our 'high' threshold, associated with 'far' events. + */ + if (dir == IIO_EV_DIR_RISING) + reg = STK3310_REG_THDL_PS; + else if (dir == IIO_EV_DIR_FALLING) + reg = STK3310_REG_THDH_PS; + else + return -EINVAL; + + mutex_lock(&data->lock); + ret = regmap_bulk_read(data->regmap, reg, &buf, 2); + mutex_unlock(&data->lock); + if (ret < 0) { + dev_err(&data->client->dev, "register read failed\n"); + return ret; + } + regmap_field_read(data->reg_ps_gain, &index); + *val = swab16(stk3310_ps_max[index] - buf); + + return IIO_VAL_INT; +} + +static int stk3310_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; + u16 buf; + int ret; + unsigned int index; + struct stk3310_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + regmap_field_read(data->reg_ps_gain, &index); + if (val > stk3310_ps_max[index]) + return -EINVAL; + + if (dir == IIO_EV_DIR_RISING) + reg = STK3310_REG_THDL_PS; + else if (dir == IIO_EV_DIR_FALLING) + reg = STK3310_REG_THDH_PS; + else + return -EINVAL; + + buf = swab16(stk3310_ps_max[index] - val); + ret = regmap_bulk_write(data->regmap, reg, &buf, 2); + if (ret < 0) + dev_err(&client->dev, "failed to set PS threshold!\n"); + + return ret; +} + +static int stk3310_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + unsigned int event_val; + struct stk3310_data *data = iio_priv(indio_dev); + + regmap_field_read(data->reg_int_ps, &event_val); + + return event_val; +} + +static int stk3310_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) +{ + int ret; + struct stk3310_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + if (state < 0 || state > 7) + return -EINVAL; + + /* Set INT_PS value */ + mutex_lock(&data->lock); + ret = regmap_field_write(data->reg_int_ps, state); + if (ret < 0) + dev_err(&client->dev, "failed to set interrupt mode\n"); + mutex_unlock(&data->lock); + + return ret; +} + static int stk3310_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -266,6 +417,10 @@ static const struct iio_info stk3310_info = { .read_raw = stk3310_read_raw, .write_raw = stk3310_write_raw, .attrs = &stk3310_attribute_group, + .read_event_value = stk3310_read_event, + .write_event_value = stk3310_write_event, + .read_event_config = stk3310_read_event_config, + .write_event_config = stk3310_write_event_config, }; static int stk3310_set_state(struct stk3310_data *data, u8 state) @@ -308,8 +463,43 @@ static int stk3310_init(struct iio_dev *indio_dev) state = STK3310_STATE_EN_ALS | STK3310_STATE_EN_PS; ret = stk3310_set_state(data, state); - if (ret < 0) + if (ret < 0) { dev_err(&client->dev, "failed to enable sensor"); + return ret; + } + + /* Enable PS interrupts */ + ret = regmap_field_write(data->reg_int_ps, STK3310_PSINT_EN); + if (ret < 0) + dev_err(&client->dev, "failed to enable interrupts!\n"); + + return ret; +} + +static int stk3310_gpio_probe(struct i2c_client *client) +{ + struct device *dev; + struct gpio_desc *gpio; + int ret; + + if (!client) + return -EINVAL; + + dev = &client->dev; + + /* gpio interrupt pin */ + gpio = devm_gpiod_get_index(dev, STK3310_GPIO, 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; + + ret = gpiod_to_irq(gpio); + dev_dbg(dev, "GPIO resource, no:%d irq:%d\n", desc_to_gpio(gpio), ret); return ret; } @@ -321,6 +511,7 @@ static bool stk3310_is_volatile_reg(struct device *dev, unsigned int reg) case STK3310_REG_ALS_DATA_LSB: case STK3310_REG_PS_DATA_LSB: case STK3310_REG_PS_DATA_MSB: + case STK3310_REG_FLAG: return true; default: return false; @@ -354,10 +545,55 @@ static int stk3310_regmap_init(struct stk3310_data *data) STK3310_REGFIELD(ps_gain); STK3310_REGFIELD(als_it); STK3310_REGFIELD(ps_it); + STK3310_REGFIELD(int_ps); + STK3310_REGFIELD(flag_psint); + STK3310_REGFIELD(flag_nf); return 0; } +static irqreturn_t stk3310_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct stk3310_data *data = iio_priv(indio_dev); + + data->timestamp = iio_get_time_ns(); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t stk3310_irq_event_handler(int irq, void *private) +{ + int ret; + unsigned int dir; + u64 event; + + struct iio_dev *indio_dev = private; + struct stk3310_data *data = iio_priv(indio_dev); + + /* Read FLAG_NF to figure out what threshold has been met. */ + mutex_lock(&data->lock); + ret = regmap_field_read(data->reg_flag_nf, &dir); + if (ret < 0) { + dev_err(&data->client->dev, "register read failed\n"); + mutex_unlock(&data->lock); + return ret; + } + event = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, + IIO_EV_TYPE_THRESH, + (dir ? IIO_EV_DIR_RISING : + IIO_EV_DIR_FALLING)); + iio_push_event(indio_dev, event, data->timestamp); + + /* Reset the interrupt flag */ + ret = regmap_field_write(data->reg_flag_psint, 0); + if (ret < 0) + dev_err(&data->client->dev, "failed to reset interrupts\n"); + mutex_unlock(&data->lock); + + return IRQ_HANDLED; +} + static int stk3310_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -397,6 +633,21 @@ static int stk3310_probe(struct i2c_client *client, stk3310_set_state(data, STK3310_STATE_STANDBY); } + if (client->irq <= 0) + client->irq = stk3310_gpio_probe(client); + + if (client->irq >= 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + stk3310_irq_handler, + stk3310_irq_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + STK3310_EVENT, indio_dev); + if (ret < 0) + dev_err(&client->dev, "request irq %d failed\n", + client->irq); + } + return ret; } |