[PATCH 2/2] hwmon: Add AMS AS6200 temperature sensor

From: Abdel Alkuor
Date: Sat Dec 16 2023 - 11:41:39 EST


as6200 is a temperature sensor with 0.0625°C resolution and a range between
-40°C to 125°C.

By default, the driver configures as6200 as following:
- Converstion rate: 8 Hz
- Conversion mode: continuous
- Consecutive fault counts: 6 samples
- Alert state: high polarity
- Alert mode: comparator mode

Interrupt is supported for the alert pin.

Datasheet: https://ams.com/documents/20143/36005/AS6200_DS000449_4-00.pdf
Signed-off-by: Abdel Alkuor <alkuor@xxxxxxxxx>
---
Documentation/hwmon/as6200.rst | 50 +++++++
drivers/hwmon/Kconfig | 10 ++
drivers/hwmon/Makefile | 1 +
drivers/hwmon/as6200.c | 249 +++++++++++++++++++++++++++++++++
4 files changed, 310 insertions(+)
create mode 100644 Documentation/hwmon/as6200.rst
create mode 100644 drivers/hwmon/as6200.c

diff --git a/Documentation/hwmon/as6200.rst b/Documentation/hwmon/as6200.rst
new file mode 100644
index 000000000000..f6156c2105ff
--- /dev/null
+++ b/Documentation/hwmon/as6200.rst
@@ -0,0 +1,50 @@
+Kernel driver as6200
+====================
+
+Supported chips:
+
+ * AMS OSRAM AS6200
+
+ Prefix: 'as6200'
+
+ Addresses scanned: none
+
+ Datasheet: https://ams.com/documents/20143/36005/AS6200_DS000449_4-00.pdf
+
+Author:
+
+ Abdel Alkuor <alkuor@xxxxxxxxx>
+
+Description
+-----------
+The AS6200 IC is a temperature sensor with a range between -40°C and 125°C.
+It supports ultra-low power consumption (low operation and quiescent current)
+which makes it ideally suited for mobile/battery powered applications.
+Additionally the AS6200 temperature sensor system also features an alert
+functionality, which triggers an interrupt to protect devices from excessive
+temperatures.
+
+The sensor has an accuracy of ±0.4°C between 0°C and 60°C and has an accuracy
+of 1.0°C from -40°C to +125°C, with Resolution of 0.0625°C. The sensor
+supports conversion rate of 0.25, 1, 4, 8 Hz, and consecutive fault counts of
+1, 2, 4, 6 samples which triggers/clears an alert when high/low temperature
+is detected respectively. Two alert modes are supported, interrupt mode and
+comparator mode.
+
+Configuration Notes
+-------------------
+By default as6200 driver reads the temperature continuously at 8Hz.
+Consecutive faults is set to 6 samples with true polarity which an
+alert is set when the current temperaute goes above high temperature
+theshold and is cleared when it falls below the low temperature threshold.
+Alert is configured in comparator mode.
+
+Interrupt is supported for the alert where user space is notified
+when alert is set/cleared.
+
+sysfs-Interface
+---------------
+temp#_input temperature input (read-only)
+temp#_alert alert flag (read-only)
+temp#_max temperature maximum setpoint (read/write)
+temp#_max_hyst hysteresis for temperature maximum (read/write)
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index cf27523eed5a..f6edcbf1b7cf 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -287,6 +287,16 @@ config SENSORS_AS370
This driver can also be built as a module. If so, the module
will be called as370-hwmon.

+config SENSORS_AS6200
+ tristate "AMS AS6200 temperature sensor"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for AMS AS6200
+ temperature sensor.
+
+ This driver can also be built as a module. If so, the module
+ will be called as6200.

config SENSORS_ASC7621
tristate "Andigilog aSC7621"
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index e84bd9685b5c..11fe2b7a80a9 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_SENSORS_AQUACOMPUTER_D5NEXT) += aquacomputer_d5next.o
obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o
obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
obj-$(CONFIG_SENSORS_AS370) += as370-hwmon.o
+obj-$(CONFIG_SENSORS_AS6200) += as6200.o
obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
diff --git a/drivers/hwmon/as6200.c b/drivers/hwmon/as6200.c
new file mode 100644
index 000000000000..173f86e48ee1
--- /dev/null
+++ b/drivers/hwmon/as6200.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for AMS AS6200 Temperature sensor
+ *
+ * Author: Abdel Alkuor <alkuor@xxxxxxxxx>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include <linux/hwmon.h>
+
+#define AS6200_TVAL_REG 0x0
+#define AS6200_CONFIG_REG 0x1
+#define AS6200_TLOW_REG 0x2
+#define AS6200_THIGH_REG 0x3
+
+#define AS6200_CONFIG_AL BIT(5)
+#define AS6200_CONFIG_CR GENMASK(7, 6)
+#define AS6200_CONFIG_SM BIT(8)
+#define AS6200_CONFIG_IM BIT(9)
+#define AS6200_CONFIG_POL BIT(10)
+#define AS6200_CONFIG_CF GENMASK(12, 11)
+
+#define AS6200_TEMP_MASK GENMASK(15, 4)
+#define AS6200_DEFAULT_CONFIG (AS6200_CONFIG_CR |\
+ AS6200_CONFIG_CF |\
+ AS6200_CONFIG_POL)
+
+static const struct regmap_config as6200_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = AS6200_THIGH_REG,
+};
+
+static irqreturn_t as6200_event_handler(int irq, void *private)
+{
+ struct device *hwmon_dev = private;
+
+ hwmon_notify_event(hwmon_dev, hwmon_temp, hwmon_temp_alarm, 0);
+ return IRQ_HANDLED;
+}
+
+static int as6200_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct regmap *regmap = dev_get_drvdata(dev);
+ unsigned int regval;
+ unsigned int reg;
+ s32 temp;
+ int ret;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ reg = AS6200_TVAL_REG;
+ break;
+ case hwmon_temp_max_hyst:
+ reg = AS6200_TLOW_REG;
+ break;
+ case hwmon_temp_max:
+ reg = AS6200_THIGH_REG;
+ break;
+ case hwmon_temp_alarm:
+ reg = AS6200_CONFIG_REG;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ ret = regmap_read(regmap, reg, &regval);
+ if (ret)
+ return ret;
+
+ if (reg == AS6200_CONFIG_REG) {
+ *val = FIELD_GET(AS6200_CONFIG_AL, regval);
+ } else {
+ temp = sign_extend32(FIELD_GET(AS6200_TEMP_MASK, regval), 11);
+ *val = DIV_ROUND_CLOSEST(temp * 625, 10);
+ }
+
+ return 0;
+}
+
+static int as6200_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct regmap *regmap = dev_get_drvdata(dev);
+ int reg;
+
+ switch (attr) {
+ case hwmon_temp_max_hyst:
+ reg = AS6200_TLOW_REG;
+ break;
+ case hwmon_temp_max:
+ reg = AS6200_THIGH_REG;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ val = clamp_val(val, -40000, 125000) * 16 / 1000;
+ return regmap_write(regmap, reg, FIELD_PREP(AS6200_TEMP_MASK, val));
+}
+
+static umode_t as6200_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ if (type != hwmon_temp)
+ return 0;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ case hwmon_temp_alarm:
+ return 0444;
+ case hwmon_temp_max_hyst:
+ case hwmon_temp_max:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_ops as6200_hwmon_ops = {
+ .is_visible = as6200_is_visible,
+ .read = as6200_read,
+ .write = as6200_write,
+};
+
+static const struct hwmon_channel_info * const as6200_info[] = {
+ HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_MAX |
+ HWMON_T_MAX_HYST | HWMON_T_ALARM),
+ NULL
+};
+
+struct hwmon_chip_info as6200_chip_info = {
+ .ops = &as6200_hwmon_ops,
+ .info = as6200_info
+};
+
+static int as6200_probe(struct i2c_client *client)
+{
+ struct regmap *regmap;
+ struct device *hwmon_dev;
+ struct device *dev = &client->dev;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -EINVAL;
+
+ regmap = devm_regmap_init_i2c(client, &as6200_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Could not get and enable regulator %d\n",
+ ret);
+
+ ret = regmap_write(regmap, AS6200_CONFIG_REG, AS6200_DEFAULT_CONFIG);
+ if (ret)
+ return ret;
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, "as6200",
+ regmap,
+ &as6200_chip_info,
+ NULL);
+ if (IS_ERR(hwmon_dev))
+ return PTR_ERR(hwmon_dev);
+
+ if (client->irq) {
+ ret = devm_request_threaded_irq(dev,
+ client->irq,
+ NULL,
+ &as6200_event_handler,
+ IRQF_ONESHOT,
+ client->name,
+ hwmon_dev);
+ if (ret)
+ return ret;
+ }
+
+ i2c_set_clientdata(client, regmap);
+
+ return 0;
+}
+
+static int __maybe_unused as6200_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct regmap *regmap = i2c_get_clientdata(client);
+
+ if (client->irq)
+ disable_irq(client->irq);
+
+ return regmap_update_bits(regmap, AS6200_CONFIG_REG,
+ AS6200_CONFIG_SM, AS6200_CONFIG_SM);
+}
+
+static int __maybe_unused as6200_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct regmap *regmap = i2c_get_clientdata(client);
+ int ret;
+
+ ret = regmap_update_bits(regmap, AS6200_CONFIG_REG, AS6200_CONFIG_SM, 0);
+ if (ret)
+ return ret;
+
+ if (client->irq)
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(as6200_pm_ops, as6200_suspend, as6200_resume);
+
+static const struct i2c_device_id as6200_id_table[] = {
+ { "as6200", 0 },
+ { }
+};
+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 = pm_sleep_ptr(&as6200_pm_ops),
+ .of_match_table = as6200_of_match,
+ },
+ .probe = as6200_probe,
+ .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