[PATCH 2/2] iio: temperature: Add support for AMS AS6200

From: Abdel Alkuor
Date: Sat Nov 11 2023 - 17:06:36 EST


as6200 is a high accuracy temperature sensor of 0.0625C with a range
between -40 to 125 Celsius degrees. The driver implements the alert
trigger event in comparator mode where an alert would trigger
when n number of consecutive current temperature is above the upper
temp limit, and the alert is only cleared when the n number of
consecutive current temperature is below the lower temp limit.

The driver supports the following:
- show available sampling frequencey
- read/write sampling frequency
- read raw temperature
- read scaling factor
- read/write consective faults to trigger/clear an alert
- show available consecutive faults
- buffer trigger
- temperature alert event trigger

https://ams.com/documents/20143/36005/AS6200_DS000449_4-00.pdf

Signed-off-by: Abdel Alkuor <alkuor@xxxxxxxxx>
---
drivers/iio/temperature/Kconfig | 9 +
drivers/iio/temperature/Makefile | 1 +
drivers/iio/temperature/as6200.c | 507 +++++++++++++++++++++++++++++++
3 files changed, 517 insertions(+)
create mode 100644 drivers/iio/temperature/as6200.c

diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig
index ed384f33e0c7..f32691744952 100644
--- a/drivers/iio/temperature/Kconfig
+++ b/drivers/iio/temperature/Kconfig
@@ -157,5 +157,14 @@ config MAX31865

This driver can also be build as a module. If so, the module
will be called max31865.
+config AS6200
+ tristate "AS6200 temperature sensor"
+ depends on I2C
+ help
+ If you say yes here you get support for AS6200
+ temperature sensor chip connected via I2C.
+
+ This driver can also be built as a module. If so, the module
+ will be called as6200.

endmenu
diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile
index dfec8c6d3019..48f647c273c1 100644
--- a/drivers/iio/temperature/Makefile
+++ b/drivers/iio/temperature/Makefile
@@ -17,3 +17,4 @@ obj-$(CONFIG_TMP007) += tmp007.o
obj-$(CONFIG_TMP117) += tmp117.o
obj-$(CONFIG_TSYS01) += tsys01.o
obj-$(CONFIG_TSYS02D) += tsys02d.o
+obj-$(CONFIG_AS6200) += as6200.o
diff --git a/drivers/iio/temperature/as6200.c b/drivers/iio/temperature/as6200.c
new file mode 100644
index 000000000000..a18c5be0a229
--- /dev/null
+++ b/drivers/iio/temperature/as6200.c
@@ -0,0 +1,507 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for AMS AS6200 Temperature sensor
+ *
+ * Auther: Abdel Alkuor <alkuor@xxxxxxxxx>
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/interrupt.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/events.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/sysfs.h>
+#include <linux/device.h>
+#include <linux/bitfield.h>
+#include <linux/kstrtox.h>
+
+#define AS_TVAL_REG 0x0
+#define AS_CONFIG_REG 0x1
+#define AS_TLOW_REG 0x2
+#define AS_THIGH_REG 0x3
+
+/* AS_CONFIG_REG */
+#define AS_CONFIG_AL BIT(5)
+#define AS_CONFIG_CR GENMASK(7, 6)
+#define AS_CONFIG_SM BIT(8)
+#define AS_CONFIG_IM BIT(9)
+#define AS_CONFIG_POL BIT(10)
+#define AS_CONFIG_CF GENMASK(12, 11)
+
+#define AS_TEMP_SCALE 62500
+
+struct as6200_freq {
+ int val;
+ int val2;
+};
+
+struct as6200 {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex lock;
+ struct iio_dev *indio_dev;
+
+ unsigned int data[3];
+};
+
+static const struct as6200_freq as6200_samp_freq[4] = {
+ {0, 250000},
+ {1, 0},
+ {4, 0},
+ {8, 0},
+};
+
+static const struct iio_event_spec as6200_temp_event[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE)
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE)
+ },
+};
+
+static const struct iio_chan_spec as6200_temp_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .indexed = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 12,
+ .storagebits = 16,
+ .shift = 4,
+ },
+ .event_spec = as6200_temp_event,
+ .num_event_specs = ARRAY_SIZE(as6200_temp_event),
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct regmap_config as6200_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = 0x7F,
+};
+
+static int
+as6200_modify_config_reg(struct as6200 *as, unsigned int mask, unsigned int val)
+{
+ int ret;
+ unsigned int reg;
+
+ ret = regmap_read(as->regmap, AS_CONFIG_REG, &reg);
+ if (ret)
+ return ret;
+
+ reg &= ~mask;
+ reg |= val << (ffs(mask) - 1);
+
+ return regmap_write(as->regmap, AS_CONFIG_REG, reg);
+}
+
+static int
+as6200_read_config_reg(struct as6200 *as, unsigned int mask, unsigned int *val)
+{
+ int ret;
+ unsigned int reg;
+
+ ret = regmap_read(as->regmap, AS_CONFIG_REG, &reg);
+ if (ret)
+ return ret;
+
+ *val = (reg & mask) >> (ffs(mask) - 1);
+
+ return 0;
+}
+
+static int as6200_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long mask)
+{
+ struct as6200 *as = iio_device_get_drvdata(indio_dev);
+ unsigned int reg;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = regmap_read(as->regmap, AS_TVAL_REG, &reg);
+ if (ret)
+ return ret;
+
+ *val = sign_extend32(reg >> 4, 11);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = AS_TEMP_SCALE;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = as6200_read_config_reg(as, AS_CONFIG_CR, &reg);
+ if (ret)
+ return ret;
+
+ *val = as6200_samp_freq[reg].val;
+ *val2 = as6200_samp_freq[reg].val2;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int as6200_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct as6200 *as = iio_device_get_drvdata(indio_dev);
+ int i;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ for (i = 0; i < ARRAY_SIZE(as6200_samp_freq); i++) {
+ if (val == as6200_samp_freq[i].val &&
+ val2 == as6200_samp_freq[i].val2)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(as6200_samp_freq))
+ return -EINVAL;
+
+ return as6200_modify_config_reg(as, AS_CONFIG_CR, i);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int as6200_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 as6200 *as = iio_device_get_drvdata(indio_dev);
+ unsigned int reg;
+ int ret;
+ unsigned int temp;
+
+ switch (dir) {
+ case IIO_EV_DIR_FALLING:
+ reg = AS_TLOW_REG;
+ break;
+ case IIO_EV_DIR_RISING:
+ reg = AS_THIGH_REG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_read(as->regmap, reg, &temp);
+ if (ret)
+ return ret;
+
+ *val = sign_extend32(temp >> 4, 11);
+
+ return IIO_VAL_INT;
+}
+
+static int as6200_write_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 as6200 *as = iio_device_get_drvdata(indio_dev);
+ unsigned int temp;
+ unsigned int reg;
+
+ /*
+ * range without applying the scaling
+ * factor of 0.06250
+ */
+ if (val > 2000 || val < -640)
+ return -EINVAL;
+
+ temp = (val & 0xfff) << 4;
+
+ switch (dir) {
+ case IIO_EV_DIR_FALLING:
+ reg = AS_TLOW_REG;
+ break;
+ case IIO_EV_DIR_RISING:
+ reg = AS_THIGH_REG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_write(as->regmap, reg, temp);
+}
+
+static irqreturn_t as6200_event_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct as6200 *as = iio_device_get_drvdata(indio_dev);
+ unsigned int alert;
+ enum iio_event_direction dir;
+ int ret;
+
+ mutex_lock(&as->lock);
+
+ ret = as6200_read_config_reg(as, AS_CONFIG_AL, &alert);
+ if (ret) {
+ mutex_unlock(&as->lock);
+ return IRQ_NONE;
+ }
+
+ dir = alert ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING;
+
+ iio_push_event(indio_dev,
+ IIO_EVENT_CODE(IIO_TEMP, 0, 0,
+ dir,
+ IIO_EV_TYPE_THRESH,
+ 0, 0, 0),
+ iio_get_time_ns(indio_dev));
+
+ mutex_unlock(&as->lock);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t as6200_trigger_handler(int irq, void *private)
+{
+ struct iio_poll_func *pf = private;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct as6200 *as = iio_device_get_drvdata(indio_dev);
+ int ret;
+
+ mutex_lock(&as->lock);
+
+ ret = regmap_read(as->regmap, AS_TVAL_REG, &as->data[0]);
+ if (!ret)
+ iio_push_to_buffers_with_timestamp(indio_dev, as->data,
+ iio_get_time_ns(indio_dev));
+
+ mutex_unlock(&as->lock);
+
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t
+consecutive_faults_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct as6200 *as = iio_device_get_drvdata(indio_dev);
+ unsigned int cf;
+ int ret;
+
+ ret = as6200_read_config_reg(as, AS_CONFIG_CF, &cf);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%u\n", cf ? cf * 2 : 1);
+}
+
+static ssize_t
+consecutive_faults_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct as6200 *as = iio_device_get_drvdata(indio_dev);
+ unsigned int cf;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &cf);
+ if (ret)
+ return ret;
+
+ switch (cf) {
+ case 1:
+ cf = 0;
+ break;
+ case 2:
+ case 4:
+ case 6:
+ cf /= 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = as6200_modify_config_reg(as, AS_CONFIG_CF, cf);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("0.25 1 4 8");
+static IIO_CONST_ATTR(avail_consecutive_faults, "1 2 4 6");
+static IIO_DEVICE_ATTR_RW(consecutive_faults, 0);
+
+static struct attribute *as6200_event_attributes[] = {
+ &iio_const_attr_avail_consecutive_faults.dev_attr.attr,
+ &iio_dev_attr_consecutive_faults.dev_attr.attr,
+ NULL,
+};
+
+static struct attribute *as6200_attributes[] = {
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group as6200_attribute_group = {
+ .attrs = as6200_attributes,
+};
+
+static const struct attribute_group as6200_event_attribute_group = {
+ .attrs = as6200_event_attributes,
+};
+
+static const struct iio_info as6200_temp_info = {
+ .event_attrs = &as6200_event_attribute_group,
+ .attrs = &as6200_attribute_group,
+ .read_raw = &as6200_read_raw,
+ .write_raw = &as6200_write_raw,
+ .read_event_value = &as6200_read_event_value,
+ .write_event_value = &as6200_write_event_value,
+};
+
+static int as6200_probe(struct i2c_client *client)
+{
+ struct as6200 *as;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -EINVAL;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, 0);
+ if (!indio_dev)
+ return -ENOMEM;
+
+ as = devm_kzalloc(&client->dev, sizeof(*as), GFP_KERNEL);
+ if (IS_ERR(as))
+ return PTR_ERR(as);
+
+ as->regmap = devm_regmap_init_i2c(client, &as6200_regmap_config);
+ if (IS_ERR(as->regmap))
+ return PTR_ERR(as->regmap);
+
+ mutex_init(&as->lock);
+
+ as->dev = &client->dev;
+ as->indio_dev = indio_dev;
+
+ iio_device_set_drvdata(indio_dev, as);
+
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = as6200_temp_channels;
+ indio_dev->num_channels = ARRAY_SIZE(as6200_temp_channels);
+ indio_dev->name = client->name;
+ indio_dev->info = &as6200_temp_info;
+
+ ret = devm_iio_triggered_buffer_setup(as->dev, indio_dev,
+ NULL,
+ as6200_trigger_handler,
+ NULL);
+ if (ret)
+ return ret;
+
+ if (client->irq) {
+ ret = devm_request_threaded_irq(as->dev,
+ client->irq,
+ NULL,
+ &as6200_event_handler,
+ IRQF_ONESHOT,
+ client->name,
+ indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ i2c_set_clientdata(client, as);
+
+ return iio_device_register(indio_dev);
+}
+
+static void as6200_remove(struct i2c_client *client)
+{
+ struct as6200 *as = i2c_get_clientdata(client);
+
+ iio_device_unregister(as->indio_dev);
+}
+
+static int __maybe_unused as6200_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct as6200 *as = i2c_get_clientdata(client);
+
+ if (client->irq)
+ disable_irq(client->irq);
+
+ return as6200_modify_config_reg(as, AS_CONFIG_SM, 1);
+}
+
+static int __maybe_unused as6200_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct as6200 *as = i2c_get_clientdata(client);
+
+ if (client->irq)
+ enable_irq(client->irq);
+
+ return as6200_modify_config_reg(as, AS_CONFIG_SM, 0);
+}
+
+static const struct dev_pm_ops as6200_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(as6200_suspend, as6200_resume)
+};
+
+static const struct i2c_device_id as6200_id_table[] = {
+ { "as6200" },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, as6200_id_table);
+
+static const struct of_device_id as6200_of_match[] = {
+ { .compatible = "ams,as6200" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, as6200_of_match);
+
+static struct i2c_driver as6200_driver = {
+ .driver = {
+ .name = "as6200",
+ .pm = &as6200_pm_ops,
+ .of_match_table = as6200_of_match,
+ },
+ .probe = as6200_probe,
+ .remove = as6200_remove,
+ .id_table = as6200_id_table,
+};
+module_i2c_driver(as6200_driver);
+
+MODULE_AUTHOR("Abdel Alkuor <alkuor@xxxxxxxxx");
+MODULE_DESCRIPTION("AMS AS6200 Temperature Sensor");
+MODULE_LICENSE("GPL");
--
2.34.1