From 7c5bc7e87cc43a45fd34987d0f48637ad4881eb0 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Mon, 23 Jan 2017 21:25:16 +0530 Subject: [PATCH] iio:temperature:tmp007: Add irq and threshold events support This patch adds ALERT irq and limit threshold events support for TI TMP007 - 16 bit IR thermopile sensor with integrated math engine. Following threshold events are supported: 1. TObj high limit 2. TObj low limit 3. TDie high limit 4. TDie low limit Signed-off-by: Manivannan Sadhasivam Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/tmp007.c | 277 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 265 insertions(+), 12 deletions(-) diff --git a/drivers/iio/temperature/tmp007.c b/drivers/iio/temperature/tmp007.c index f04d0d1f6ac8..0615324d054c 100644 --- a/drivers/iio/temperature/tmp007.c +++ b/drivers/iio/temperature/tmp007.c @@ -11,9 +11,10 @@ * * (7-bit I2C slave address (0x40 - 0x47), changeable via ADR pins) * - * Note: This driver assumes that the sensor has been calibrated beforehand - * - * TODO: ALERT irq, limit threshold events + * Note: + * 1. This driver assumes that the sensor has been calibrated beforehand + * 2. Limit threshold events are enabled at the start + * 3. Operating mode: INT * */ @@ -24,25 +25,38 @@ #include #include #include +#include +#include #include #include +#include #define TMP007_TDIE 0x01 #define TMP007_CONFIG 0x02 #define TMP007_TOBJECT 0x03 #define TMP007_STATUS 0x04 #define TMP007_STATUS_MASK 0x05 +#define TMP007_TOBJ_HIGH_LIMIT 0x06 +#define TMP007_TOBJ_LOW_LIMIT 0x07 +#define TMP007_TDIE_HIGH_LIMIT 0x08 +#define TMP007_TDIE_LOW_LIMIT 0x09 #define TMP007_MANUFACTURER_ID 0x1e #define TMP007_DEVICE_ID 0x1f #define TMP007_CONFIG_CONV_EN BIT(12) -#define TMP007_CONFIG_COMP_EN BIT(5) #define TMP007_CONFIG_TC_EN BIT(6) #define TMP007_CONFIG_CR_MASK GENMASK(11, 9) +#define TMP007_CONFIG_ALERT_EN BIT(8) #define TMP007_CONFIG_CR_SHIFT 9 +/* Status register flags */ +#define TMP007_STATUS_ALERT BIT(15) #define TMP007_STATUS_CONV_READY BIT(14) +#define TMP007_STATUS_OHF BIT(13) +#define TMP007_STATUS_OLF BIT(12) +#define TMP007_STATUS_LHF BIT(11) +#define TMP007_STATUS_LLF BIT(10) #define TMP007_STATUS_DATA_VALID BIT(9) #define TMP007_MANUFACTURER_MAGIC 0x5449 @@ -52,7 +66,9 @@ struct tmp007_data { struct i2c_client *client; + struct mutex lock; u16 config; + u16 status_mask; }; static const int tmp007_avgs[5][2] = { {4, 0}, {2, 0}, {1, 0}, @@ -156,6 +172,188 @@ static int tmp007_write_raw(struct iio_dev *indio_dev, return -EINVAL; } +static irqreturn_t tmp007_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct tmp007_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_word_swapped(data->client, TMP007_STATUS); + if ((ret < 0) || !(ret & (TMP007_STATUS_OHF | TMP007_STATUS_OLF | + TMP007_STATUS_LHF | TMP007_STATUS_LLF))) + return IRQ_NONE; + + if (ret & TMP007_STATUS_OHF) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_MOD_TEMP_OBJECT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + if (ret & TMP007_STATUS_OLF) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_MOD_TEMP_OBJECT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + + if (ret & TMP007_STATUS_LHF) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_MOD_TEMP_AMBIENT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + if (ret & TMP007_STATUS_LLF) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_MOD_TEMP_AMBIENT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static int tmp007_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 tmp007_data *data = iio_priv(indio_dev); + unsigned int status_mask; + int ret; + + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: + if (dir == IIO_EV_DIR_RISING) + status_mask = TMP007_STATUS_LHF; + else + status_mask = TMP007_STATUS_LLF; + break; + case IIO_MOD_TEMP_OBJECT: + if (dir == IIO_EV_DIR_RISING) + status_mask = TMP007_STATUS_OHF; + else + status_mask = TMP007_STATUS_OLF; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->lock); + ret = i2c_smbus_read_word_swapped(data->client, TMP007_STATUS_MASK); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + if (state) + ret |= status_mask; + else + ret &= ~status_mask; + + return i2c_smbus_write_word_swapped(data->client, TMP007_STATUS_MASK, + data->status_mask = ret); +} + +static int tmp007_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 tmp007_data *data = iio_priv(indio_dev); + unsigned int mask; + + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: + if (dir == IIO_EV_DIR_RISING) + mask = TMP007_STATUS_LHF; + else + mask = TMP007_STATUS_LLF; + break; + case IIO_MOD_TEMP_OBJECT: + if (dir == IIO_EV_DIR_RISING) + mask = TMP007_STATUS_OHF; + else + mask = TMP007_STATUS_OLF; + break; + default: + return -EINVAL; + } + + return !!(data->status_mask & mask); +} + +static int tmp007_read_thresh(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 tmp007_data *data = iio_priv(indio_dev); + int ret; + u8 reg; + + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: /* LSB: 0.5 degree Celsius */ + if (dir == IIO_EV_DIR_RISING) + reg = TMP007_TDIE_HIGH_LIMIT; + else + reg = TMP007_TDIE_LOW_LIMIT; + break; + case IIO_MOD_TEMP_OBJECT: + if (dir == IIO_EV_DIR_RISING) + reg = TMP007_TOBJ_HIGH_LIMIT; + else + reg = TMP007_TOBJ_LOW_LIMIT; + break; + default: + return -EINVAL; + } + + ret = i2c_smbus_read_word_swapped(data->client, reg); + if (ret < 0) + return ret; + + /* Shift length 7 bits = 6(15:6) + 1(0.5 LSB) */ + *val = sign_extend32(ret, 15) >> 7; + + return IIO_VAL_INT; +} + +static int tmp007_write_thresh(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 tmp007_data *data = iio_priv(indio_dev); + u8 reg; + + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: + if (dir == IIO_EV_DIR_RISING) + reg = TMP007_TDIE_HIGH_LIMIT; + else + reg = TMP007_TDIE_LOW_LIMIT; + break; + case IIO_MOD_TEMP_OBJECT: + if (dir == IIO_EV_DIR_RISING) + reg = TMP007_TOBJ_HIGH_LIMIT; + else + reg = TMP007_TOBJ_LOW_LIMIT; + break; + default: + return -EINVAL; + } + + /* Full scale threshold value is +/- 256 degree Celsius */ + if (val < -256 || val > 255) + return -EINVAL; + + /* Shift length 7 bits = 6(15:6) + 1(0.5 LSB) */ + return i2c_smbus_write_word_swapped(data->client, reg, (val << 7)); +} + static IIO_CONST_ATTR(sampling_frequency_available, "4 2 1 0.5 0.25"); static struct attribute *tmp007_attributes[] = { @@ -167,6 +365,36 @@ static const struct attribute_group tmp007_attribute_group = { .attrs = tmp007_attributes, }; +static const struct iio_event_spec tmp007_obj_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .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_event_spec tmp007_die_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .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 tmp007_channels[] = { { .type = IIO_TEMP, @@ -175,6 +403,8 @@ static const struct iio_chan_spec tmp007_channels[] = { .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = tmp007_die_event, + .num_event_specs = ARRAY_SIZE(tmp007_die_event), }, { .type = IIO_TEMP, @@ -183,12 +413,18 @@ static const struct iio_chan_spec tmp007_channels[] = { .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = tmp007_obj_event, + .num_event_specs = ARRAY_SIZE(tmp007_obj_event), } }; static const struct iio_info tmp007_info = { .read_raw = tmp007_read_raw, .write_raw = tmp007_write_raw, + .read_event_config = tmp007_read_event_config, + .write_event_config = tmp007_write_event_config, + .read_event_value = tmp007_read_thresh, + .write_event_value = tmp007_write_thresh, .attrs = &tmp007_attribute_group, .driver_module = THIS_MODULE, }; @@ -214,7 +450,6 @@ static int tmp007_probe(struct i2c_client *client, struct tmp007_data *data; struct iio_dev *indio_dev; int ret; - u16 status; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) return -EOPNOTSUPP; @@ -231,6 +466,7 @@ static int tmp007_probe(struct i2c_client *client, data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); data->client = client; + mutex_init(&data->lock); indio_dev->dev.parent = &client->dev; indio_dev->name = "tmp007"; @@ -243,7 +479,7 @@ static int tmp007_probe(struct i2c_client *client, /* * Set Configuration register: * 1. Conversion ON - * 2. Comparator mode + * 2. ALERT enable * 3. Transient correction enable */ @@ -252,7 +488,7 @@ static int tmp007_probe(struct i2c_client *client, return ret; data->config = ret; - data->config |= (TMP007_CONFIG_CONV_EN | TMP007_CONFIG_COMP_EN | TMP007_CONFIG_TC_EN); + data->config |= (TMP007_CONFIG_CONV_EN | TMP007_CONFIG_ALERT_EN | TMP007_CONFIG_TC_EN); ret = i2c_smbus_write_word_swapped(data->client, TMP007_CONFIG, data->config); @@ -260,22 +496,39 @@ static int tmp007_probe(struct i2c_client *client, return ret; /* + * Only the following flags can activate ALERT pin. Data conversion/validity flags + * flags can still be polled for getting temperature data + * * Set Status Mask register: - * 1. Conversion ready enable - * 2. Data valid enable + * 1. Object temperature high limit enable + * 2. Object temperature low limit enable + * 3. TDIE temperature high limit enable + * 4. TDIE temperature low limit enable */ ret = i2c_smbus_read_word_swapped(data->client, TMP007_STATUS_MASK); if (ret < 0) goto error_powerdown; - status = ret; - status |= (TMP007_STATUS_CONV_READY | TMP007_STATUS_DATA_VALID); + data->status_mask = ret; + data->status_mask |= (TMP007_STATUS_OHF | TMP007_STATUS_OLF + | TMP007_STATUS_LHF | TMP007_STATUS_LLF); - ret = i2c_smbus_write_word_swapped(data->client, TMP007_STATUS_MASK, status); + ret = i2c_smbus_write_word_swapped(data->client, TMP007_STATUS_MASK, data->status_mask); if (ret < 0) goto error_powerdown; + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, tmp007_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + tmp007_id->name, indio_dev); + if (ret) { + dev_err(&client->dev, "irq request error %d\n", -ret); + goto error_powerdown; + } + } + return iio_device_register(indio_dev); error_powerdown: -- 2.11.0