All of lore.kernel.org
 help / color / mirror / Atom feed
From: Abdel Alkuor <alkuor@gmail.com>
To: Jonathan Cameron <jic23@kernel.org>,
	Lars-Peter Clausen <lars@metafoo.de>
Cc: Abdel Alkuor <alkuor@gmail.com>,
	linux-kernel@vger.kernel.org, linux-iio@vger.kernel.org
Subject: [PATCH 2/2] iio: temperature: Add support for AMS AS6200
Date: Sat, 11 Nov 2023 17:05:02 -0500	[thread overview]
Message-ID: <cbde9aa61905a0fae6573d707b4c9c4fc4598fa5.1699740057.git.alkuor@gmail.com> (raw)
In-Reply-To: <7e108db19cd9ad19b6413f65099280c010325a91.1699740057.git.alkuor@gmail.com>

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@gmail.com>
---
 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@gmail.com>
+ */
+
+#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@gmail.com");
+MODULE_DESCRIPTION("AMS AS6200 Temperature Sensor");
+MODULE_LICENSE("GPL");
-- 
2.34.1


  reply	other threads:[~2023-11-11 22:06 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-11-11 22:05 [PATCH 1/2] dt-bindings: iio: temperature: Add AMS AS6200 Abdel Alkuor
2023-11-11 22:05 ` Abdel Alkuor [this message]
2023-11-26 17:57   ` [PATCH 2/2] iio: temperature: Add support for " Jonathan Cameron
2023-11-30  3:55     ` Abdel Alkuor
2023-11-12  7:43 ` [PATCH 1/2] dt-bindings: iio: temperature: Add " Krzysztof Kozlowski
2023-11-12 14:12   ` Abdel Alkuor
2023-11-26 17:30 ` Jonathan Cameron
2023-11-30  0:39   ` Abdel Alkuor

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cbde9aa61905a0fae6573d707b4c9c4fc4598fa5.1699740057.git.alkuor@gmail.com \
    --to=alkuor@gmail.com \
    --cc=jic23@kernel.org \
    --cc=lars@metafoo.de \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.