summaryrefslogtreecommitdiffstats
path: root/drivers/iio/temperature
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio/temperature')
-rw-r--r--drivers/iio/temperature/Kconfig187
-rw-r--r--drivers/iio/temperature/Makefile21
-rw-r--r--drivers/iio/temperature/hid-sensor-temperature.c294
-rw-r--r--drivers/iio/temperature/iqs620at-temp.c98
-rw-r--r--drivers/iio/temperature/ltc2983.c2106
-rw-r--r--drivers/iio/temperature/max30208.c251
-rw-r--r--drivers/iio/temperature/max31856.c488
-rw-r--r--drivers/iio/temperature/max31865.c350
-rw-r--r--drivers/iio/temperature/maxim_thermocouple.c304
-rw-r--r--drivers/iio/temperature/mcp9600.c579
-rw-r--r--drivers/iio/temperature/mlx90614.c782
-rw-r--r--drivers/iio/temperature/mlx90632.c1343
-rw-r--r--drivers/iio/temperature/mlx90635.c1093
-rw-r--r--drivers/iio/temperature/tmp006.c411
-rw-r--r--drivers/iio/temperature/tmp007.c581
-rw-r--r--drivers/iio/temperature/tmp117.c243
-rw-r--r--drivers/iio/temperature/tsys01.c234
-rw-r--r--drivers/iio/temperature/tsys02d.c190
18 files changed, 9555 insertions, 0 deletions
diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig
new file mode 100644
index 000000000..9328b2250
--- /dev/null
+++ b/drivers/iio/temperature/Kconfig
@@ -0,0 +1,187 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Temperature sensor drivers
+#
+menu "Temperature sensors"
+
+config IQS620AT_TEMP
+ tristate "Azoteq IQS620AT temperature sensor"
+ depends on MFD_IQS62X || COMPILE_TEST
+ help
+ Say Y here if you want to build support for the Azoteq IQS620AT
+ temperature sensor.
+
+ To compile this driver as a module, choose M here: the module
+ will be called iqs620at-temp.
+
+config LTC2983
+ tristate "Analog Devices Multi-Sensor Digital Temperature Measurement System"
+ depends on SPI
+ select REGMAP_SPI
+ help
+ Say yes here to build support for the LTC2983 Multi-Sensor
+ high accuracy digital temperature measurement system.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ltc2983.
+
+config MAXIM_THERMOCOUPLE
+ tristate "Maxim thermocouple sensors"
+ depends on SPI
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ If you say yes here you get support for the Maxim series of
+ thermocouple sensors connected via SPI.
+
+ Supported sensors:
+ * MAX6675
+ * MAX31855
+
+ This driver can also be built as a module. If so, the module will
+ be called maxim_thermocouple.
+
+config HID_SENSOR_TEMP
+ tristate "HID Environmental temperature sensor"
+ depends on HID_SENSOR_HUB
+ select IIO_BUFFER
+ select HID_SENSOR_IIO_COMMON
+ select HID_SENSOR_IIO_TRIGGER
+ help
+ Say yes here to build support for the HID SENSOR
+ temperature driver
+
+ To compile this driver as a module, choose M here: the module
+ will be called hid-sensor-temperature.
+
+config MLX90614
+ tristate "MLX90614 contact-less infrared sensor"
+ depends on I2C
+ help
+ If you say yes here you get support for the Melexis
+ MLX90614 contact-less infrared sensor connected with I2C.
+
+ This driver can also be built as a module. If so, the module will
+ be called mlx90614.
+
+config MLX90632
+ tristate "MLX90632 contact-less infrared sensor with medical accuracy"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the Melexis
+ MLX90632 contact-less infrared sensor with medical accuracy
+ connected with I2C.
+
+ This driver can also be built as a module. If so, the module will
+ be called mlx90632.
+
+config MLX90635
+ tristate "MLX90635 contact-less infrared sensor with medical accuracy"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the Melexis
+ MLX90635 contact-less infrared sensor with medical accuracy
+ connected with I2C.
+
+ This driver can also be built as a module. If so, the module will
+ be called mlx90635.
+
+config TMP006
+ tristate "TMP006 infrared thermopile sensor"
+ depends on I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ If you say yes here you get support for the Texas Instruments
+ TMP006 infrared thermopile sensor.
+
+ This driver can also be built as a module. If so, the module will
+ be called tmp006.
+
+config TMP007
+ tristate "TMP007 infrared thermopile sensor with Integrated Math Engine"
+ depends on I2C
+ help
+ If you say yes here you get support for the Texas Instruments
+ TMP007 infrared thermopile sensor with Integrated Math Engine.
+
+ This driver can also be built as a module. If so, the module will
+ be called tmp007.
+
+config TMP117
+ tristate "TMP117 Digital temperature sensor with integrated NV memory"
+ depends on I2C
+ help
+ If you say yes here you get support for the Texas Instruments
+ TMP117 Digital temperature sensor with integrated NV memory.
+
+ This driver can also be built as a module. If so, the module will
+ be called tmp117.
+
+config TSYS01
+ tristate "Measurement Specialties TSYS01 temperature sensor using I2C bus connection"
+ depends on I2C
+ select IIO_MS_SENSORS_I2C
+ help
+ If you say yes here you get support for the Measurement Specialties
+ TSYS01 I2C temperature sensor.
+
+ This driver can also be built as a module. If so, the module will
+ be called tsys01.
+
+config TSYS02D
+ tristate "Measurement Specialties TSYS02D temperature sensor"
+ depends on I2C
+ select IIO_MS_SENSORS_I2C
+ help
+ If you say yes here you get support for the Measurement Specialties
+ TSYS02D temperature sensor.
+
+ This driver can also be built as a module. If so, the module will
+ be called tsys02d.
+
+config MAX30208
+ tristate "Maxim MAX30208 digital temperature sensor"
+ depends on I2C
+ help
+ If you say yes here you get support for Maxim MAX30208
+ digital temperature sensor connected via I2C.
+
+ This driver can also be built as a module. If so, the module
+ will be called max30208.
+
+config MAX31856
+ tristate "MAX31856 thermocouple sensor"
+ depends on SPI
+ help
+ If you say yes here you get support for MAX31856
+ thermocouple sensor chip connected via SPI.
+
+ This driver can also be built as a module. If so, the module
+ will be called max31856.
+
+config MAX31865
+ tristate "MAX31865 RTD to Digital converter"
+ depends on SPI
+ help
+ If you say yes here you get support for MAX31865
+ thermocouple sensor chip connected via SPI.
+
+ This driver can also be build as a module. If so, the module
+ will be called max31865.
+
+config MCP9600
+ tristate "MCP9600 and similar thermocouple EMF converters"
+ depends on I2C
+ help
+ If you say yes here you get support for...
+ - MCP9600
+ - MCP9601
+ ...thermocouple EMF converters connected via I2C.
+
+ This driver can also be built as a module. If so, the module
+ will be called mcp9600.
+
+endmenu
diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile
new file mode 100644
index 000000000..0850bf691
--- /dev/null
+++ b/drivers/iio/temperature/Makefile
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for industrial I/O temperature drivers
+#
+
+obj-$(CONFIG_IQS620AT_TEMP) += iqs620at-temp.o
+obj-$(CONFIG_LTC2983) += ltc2983.o
+obj-$(CONFIG_HID_SENSOR_TEMP) += hid-sensor-temperature.o
+obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o
+obj-$(CONFIG_MAX30208) += max30208.o
+obj-$(CONFIG_MAX31856) += max31856.o
+obj-$(CONFIG_MAX31865) += max31865.o
+obj-$(CONFIG_MCP9600) += mcp9600.o
+obj-$(CONFIG_MLX90614) += mlx90614.o
+obj-$(CONFIG_MLX90632) += mlx90632.o
+obj-$(CONFIG_MLX90635) += mlx90635.o
+obj-$(CONFIG_TMP006) += tmp006.o
+obj-$(CONFIG_TMP007) += tmp007.o
+obj-$(CONFIG_TMP117) += tmp117.o
+obj-$(CONFIG_TSYS01) += tsys01.o
+obj-$(CONFIG_TSYS02D) += tsys02d.o
diff --git a/drivers/iio/temperature/hid-sensor-temperature.c b/drivers/iio/temperature/hid-sensor-temperature.c
new file mode 100644
index 000000000..5e3262e46
--- /dev/null
+++ b/drivers/iio/temperature/hid-sensor-temperature.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HID Sensors Driver
+ * Copyright (c) 2017, Intel Corporation.
+ */
+#include <linux/device.h>
+#include <linux/hid-sensor-hub.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "../common/hid-sensors/hid-sensor-trigger.h"
+
+struct temperature_state {
+ struct hid_sensor_common common_attributes;
+ struct hid_sensor_hub_attribute_info temperature_attr;
+ struct {
+ s32 temperature_data;
+ aligned_s64 timestamp;
+ } scan;
+ int scale_pre_decml;
+ int scale_post_decml;
+ int scale_precision;
+ int value_offset;
+};
+
+static const u32 temperature_sensitivity_addresses[] = {
+ HID_USAGE_SENSOR_DATA_ENVIRONMENTAL_TEMPERATURE,
+};
+
+/* Channel definitions */
+static const struct iio_chan_spec temperature_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+/* Adjust channel real bits based on report descriptor */
+static void temperature_adjust_channel_bit_mask(struct iio_chan_spec *channels,
+ int channel, int size)
+{
+ channels[channel].scan_type.sign = 's';
+ /* Real storage bits will change based on the report desc. */
+ channels[channel].scan_type.realbits = size * 8;
+ /* Maximum size of a sample to capture is s32 */
+ channels[channel].scan_type.storagebits = sizeof(s32) * 8;
+}
+
+static int temperature_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct temperature_state *temp_st = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (chan->type != IIO_TEMP)
+ return -EINVAL;
+ hid_sensor_power_state(
+ &temp_st->common_attributes, true);
+ *val = sensor_hub_input_attr_get_raw_value(
+ temp_st->common_attributes.hsdev,
+ HID_USAGE_SENSOR_TEMPERATURE,
+ HID_USAGE_SENSOR_DATA_ENVIRONMENTAL_TEMPERATURE,
+ temp_st->temperature_attr.report_id,
+ SENSOR_HUB_SYNC,
+ temp_st->temperature_attr.logical_minimum < 0);
+ hid_sensor_power_state(
+ &temp_st->common_attributes,
+ false);
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = temp_st->scale_pre_decml;
+ *val2 = temp_st->scale_post_decml;
+ return temp_st->scale_precision;
+
+ case IIO_CHAN_INFO_OFFSET:
+ *val = temp_st->value_offset;
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return hid_sensor_read_samp_freq_value(
+ &temp_st->common_attributes, val, val2);
+
+ case IIO_CHAN_INFO_HYSTERESIS:
+ return hid_sensor_read_raw_hyst_value(
+ &temp_st->common_attributes, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int temperature_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct temperature_state *temp_st = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return hid_sensor_write_samp_freq_value(
+ &temp_st->common_attributes, val, val2);
+ case IIO_CHAN_INFO_HYSTERESIS:
+ return hid_sensor_write_raw_hyst_value(
+ &temp_st->common_attributes, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info temperature_info = {
+ .read_raw = &temperature_read_raw,
+ .write_raw = &temperature_write_raw,
+};
+
+/* Callback handler to send event after all samples are received and captured */
+static int temperature_proc_event(struct hid_sensor_hub_device *hsdev,
+ u32 usage_id, void *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct temperature_state *temp_st = iio_priv(indio_dev);
+
+ if (atomic_read(&temp_st->common_attributes.data_ready))
+ iio_push_to_buffers_with_ts(indio_dev, &temp_st->scan,
+ sizeof(temp_st->scan),
+ iio_get_time_ns(indio_dev));
+
+ return 0;
+}
+
+/* Capture samples in local storage */
+static int temperature_capture_sample(struct hid_sensor_hub_device *hsdev,
+ u32 usage_id,
+ size_t raw_len, char *raw_data,
+ void *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct temperature_state *temp_st = iio_priv(indio_dev);
+
+ switch (usage_id) {
+ case HID_USAGE_SENSOR_DATA_ENVIRONMENTAL_TEMPERATURE:
+ temp_st->scan.temperature_data = *(s32 *)raw_data;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+/* Parse report which is specific to an usage id*/
+static int temperature_parse_report(struct platform_device *pdev,
+ struct hid_sensor_hub_device *hsdev,
+ struct iio_chan_spec *channels,
+ u32 usage_id,
+ struct temperature_state *st)
+{
+ int ret;
+
+ ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT,
+ usage_id,
+ HID_USAGE_SENSOR_DATA_ENVIRONMENTAL_TEMPERATURE,
+ &st->temperature_attr);
+ if (ret < 0)
+ return ret;
+
+ temperature_adjust_channel_bit_mask(channels, 0, st->temperature_attr.size);
+
+ st->scale_precision = hid_sensor_format_scale(
+ HID_USAGE_SENSOR_TEMPERATURE,
+ &st->temperature_attr,
+ &st->scale_pre_decml, &st->scale_post_decml);
+
+ return ret;
+}
+
+static struct hid_sensor_hub_callbacks temperature_callbacks = {
+ .send_event = &temperature_proc_event,
+ .capture_sample = &temperature_capture_sample,
+};
+
+/* Function to initialize the processing for usage id */
+static int hid_temperature_probe(struct platform_device *pdev)
+{
+ static const char *name = "temperature";
+ struct iio_dev *indio_dev;
+ struct temperature_state *temp_st;
+ struct iio_chan_spec *temp_chans;
+ struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev);
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*temp_st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ temp_st = iio_priv(indio_dev);
+ temp_st->common_attributes.hsdev = hsdev;
+ temp_st->common_attributes.pdev = pdev;
+
+ ret = hid_sensor_parse_common_attributes(hsdev,
+ HID_USAGE_SENSOR_TEMPERATURE,
+ &temp_st->common_attributes,
+ temperature_sensitivity_addresses,
+ ARRAY_SIZE(temperature_sensitivity_addresses));
+ if (ret)
+ return ret;
+
+ temp_chans = devm_kmemdup(&pdev->dev, temperature_channels,
+ sizeof(temperature_channels), GFP_KERNEL);
+ if (!temp_chans)
+ return -ENOMEM;
+
+ ret = temperature_parse_report(pdev, hsdev, temp_chans,
+ HID_USAGE_SENSOR_TEMPERATURE, temp_st);
+ if (ret)
+ return ret;
+
+ indio_dev->channels = temp_chans;
+ indio_dev->num_channels = ARRAY_SIZE(temperature_channels);
+ indio_dev->info = &temperature_info;
+ indio_dev->name = name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ atomic_set(&temp_st->common_attributes.data_ready, 0);
+
+ ret = hid_sensor_setup_trigger(indio_dev, name,
+ &temp_st->common_attributes);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ temperature_callbacks.pdev = pdev;
+ ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_TEMPERATURE,
+ &temperature_callbacks);
+ if (ret)
+ goto error_remove_trigger;
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto error_remove_callback;
+
+ return ret;
+
+error_remove_callback:
+ sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_TEMPERATURE);
+error_remove_trigger:
+ hid_sensor_remove_trigger(indio_dev, &temp_st->common_attributes);
+ return ret;
+}
+
+/* Function to deinitialize the processing for usage id */
+static void hid_temperature_remove(struct platform_device *pdev)
+{
+ struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev);
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct temperature_state *temp_st = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_TEMPERATURE);
+ hid_sensor_remove_trigger(indio_dev, &temp_st->common_attributes);
+}
+
+static const struct platform_device_id hid_temperature_ids[] = {
+ {
+ /* Format: HID-SENSOR-usage_id_in_hex_lowercase */
+ .name = "HID-SENSOR-200033",
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, hid_temperature_ids);
+
+static struct platform_driver hid_temperature_platform_driver = {
+ .id_table = hid_temperature_ids,
+ .driver = {
+ .name = "temperature-sensor",
+ .pm = &hid_sensor_pm_ops,
+ },
+ .probe = hid_temperature_probe,
+ .remove = hid_temperature_remove,
+};
+module_platform_driver(hid_temperature_platform_driver);
+
+MODULE_DESCRIPTION("HID Environmental temperature sensor");
+MODULE_AUTHOR("Song Hongyan <hongyan.song@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_HID");
diff --git a/drivers/iio/temperature/iqs620at-temp.c b/drivers/iio/temperature/iqs620at-temp.c
new file mode 100644
index 000000000..e2f878d57
--- /dev/null
+++ b/drivers/iio/temperature/iqs620at-temp.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Azoteq IQS620AT Temperature Sensor
+ *
+ * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
+ */
+
+#include <linux/device.h>
+#include <linux/iio/iio.h>
+#include <linux/kernel.h>
+#include <linux/mfd/iqs62x.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define IQS620_TEMP_UI_OUT 0x1A
+
+#define IQS620_TEMP_SCALE 1000
+#define IQS620_TEMP_OFFSET (-100)
+#define IQS620_TEMP_OFFSET_V3 (-40)
+
+static int iqs620_temp_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct iqs62x_core *iqs62x = iio_device_get_drvdata(indio_dev);
+ int ret;
+ __le16 val_buf;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = regmap_raw_read(iqs62x->regmap, IQS620_TEMP_UI_OUT,
+ &val_buf, sizeof(val_buf));
+ if (ret)
+ return ret;
+
+ *val = le16_to_cpu(val_buf);
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = IQS620_TEMP_SCALE;
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_OFFSET:
+ *val = iqs62x->hw_num < IQS620_HW_NUM_V3 ? IQS620_TEMP_OFFSET
+ : IQS620_TEMP_OFFSET_V3;
+ return IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info iqs620_temp_info = {
+ .read_raw = &iqs620_temp_read_raw,
+};
+
+static const struct iio_chan_spec iqs620_temp_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET),
+ },
+};
+
+static int iqs620_temp_probe(struct platform_device *pdev)
+{
+ struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, 0);
+ if (!indio_dev)
+ return -ENOMEM;
+
+ iio_device_set_drvdata(indio_dev, iqs62x);
+
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = iqs620_temp_channels;
+ indio_dev->num_channels = ARRAY_SIZE(iqs620_temp_channels);
+ indio_dev->name = iqs62x->dev_desc->dev_name;
+ indio_dev->info = &iqs620_temp_info;
+
+ return devm_iio_device_register(&pdev->dev, indio_dev);
+}
+
+static struct platform_driver iqs620_temp_platform_driver = {
+ .driver = {
+ .name = "iqs620at-temp",
+ },
+ .probe = iqs620_temp_probe,
+};
+module_platform_driver(iqs620_temp_platform_driver);
+
+MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
+MODULE_DESCRIPTION("Azoteq IQS620AT Temperature Sensor");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:iqs620at-temp");
diff --git a/drivers/iio/temperature/ltc2983.c b/drivers/iio/temperature/ltc2983.c
new file mode 100644
index 000000000..6efb5252a
--- /dev/null
+++ b/drivers/iio/temperature/ltc2983.c
@@ -0,0 +1,2106 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Analog Devices LTC2983 Multi-Sensor Digital Temperature Measurement System
+ * driver
+ *
+ * Copyright 2019 Analog Devices Inc.
+ */
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <asm/byteorder.h>
+#include <linux/unaligned.h>
+
+/* register map */
+#define LTC2983_STATUS_REG 0x0000
+#define LTC2983_TEMP_RES_START_REG 0x0010
+#define LTC2983_TEMP_RES_END_REG 0x005F
+#define ADT7604_RES_RES_START_REG 0x0060
+#define ADT7604_RES_RES_END_REG 0x00AF
+#define LTC2983_EEPROM_KEY_REG 0x00B0
+#define LTC2983_EEPROM_READ_STATUS_REG 0x00D0
+#define LTC2983_GLOBAL_CONFIG_REG 0x00F0
+#define LTC2983_MULT_CHANNEL_START_REG 0x00F4
+#define LTC2983_MULT_CHANNEL_END_REG 0x00F7
+#define LTC2986_EEPROM_STATUS_REG 0x00F9
+#define LTC2983_MUX_CONFIG_REG 0x00FF
+#define LTC2983_CHAN_ASSIGN_START_REG 0x0200
+#define LTC2983_CHAN_ASSIGN_END_REG 0x024F
+#define LTC2983_CUST_SENS_TBL_START_REG 0x0250
+#define LTC2983_CUST_SENS_TBL_END_REG 0x03CF
+
+#define LTC2983_DIFFERENTIAL_CHAN_MIN 2
+#define LTC2983_MIN_CHANNELS_NR 1
+#define LTC2983_SLEEP 0x97
+#define LTC2983_CUSTOM_STEINHART_SIZE 24
+#define LTC2983_CUSTOM_SENSOR_ENTRY_SZ 6
+#define LTC2983_CUSTOM_STEINHART_ENTRY_SZ 4
+
+#define LTC2983_EEPROM_KEY 0xA53C0F5A
+#define LTC2983_EEPROM_WRITE_CMD 0x15
+#define LTC2983_EEPROM_READ_CMD 0x16
+#define LTC2983_EEPROM_STATUS_FAILURE_MASK GENMASK(3, 1)
+#define LTC2983_EEPROM_READ_FAILURE_MASK GENMASK(7, 0)
+
+#define LTC2983_EEPROM_WRITE_TIME_MS 2600
+#define LTC2983_EEPROM_READ_TIME_MS 20
+
+#define LTC2983_CHAN_ASSIGN_ADDR(chan) \
+ ((((chan) - 1) * 4) + LTC2983_CHAN_ASSIGN_START_REG)
+#define LTC2983_RESULT_ADDR(chan, base) \
+ ((((chan) - 1) * 4) + (base))
+#define LTC2983_THERMOCOUPLE_DIFF_MASK BIT(3)
+#define LTC2983_THERMOCOUPLE_SGL(x) \
+ FIELD_PREP(LTC2983_THERMOCOUPLE_DIFF_MASK, x)
+#define LTC2983_THERMOCOUPLE_OC_CURR_MASK GENMASK(1, 0)
+#define LTC2983_THERMOCOUPLE_OC_CURR(x) \
+ FIELD_PREP(LTC2983_THERMOCOUPLE_OC_CURR_MASK, x)
+#define LTC2983_THERMOCOUPLE_OC_CHECK_MASK BIT(2)
+#define LTC2983_THERMOCOUPLE_OC_CHECK(x) \
+ FIELD_PREP(LTC2983_THERMOCOUPLE_OC_CHECK_MASK, x)
+
+#define LTC2983_THERMISTOR_DIFF_MASK BIT(2)
+#define LTC2983_THERMISTOR_SGL(x) \
+ FIELD_PREP(LTC2983_THERMISTOR_DIFF_MASK, x)
+#define LTC2983_THERMISTOR_R_SHARE_MASK BIT(1)
+#define LTC2983_THERMISTOR_R_SHARE(x) \
+ FIELD_PREP(LTC2983_THERMISTOR_R_SHARE_MASK, x)
+#define LTC2983_THERMISTOR_C_ROTATE_MASK BIT(0)
+#define LTC2983_THERMISTOR_C_ROTATE(x) \
+ FIELD_PREP(LTC2983_THERMISTOR_C_ROTATE_MASK, x)
+
+#define LTC2983_DIODE_DIFF_MASK BIT(2)
+#define LTC2983_DIODE_SGL(x) \
+ FIELD_PREP(LTC2983_DIODE_DIFF_MASK, x)
+#define LTC2983_DIODE_3_CONV_CYCLE_MASK BIT(1)
+#define LTC2983_DIODE_3_CONV_CYCLE(x) \
+ FIELD_PREP(LTC2983_DIODE_3_CONV_CYCLE_MASK, x)
+#define LTC2983_DIODE_AVERAGE_ON_MASK BIT(0)
+#define LTC2983_DIODE_AVERAGE_ON(x) \
+ FIELD_PREP(LTC2983_DIODE_AVERAGE_ON_MASK, x)
+
+#define LTC2983_RTD_4_WIRE_MASK BIT(3)
+#define LTC2983_RTD_ROTATION_MASK BIT(1)
+#define LTC2983_RTD_C_ROTATE(x) \
+ FIELD_PREP(LTC2983_RTD_ROTATION_MASK, x)
+#define LTC2983_RTD_KELVIN_R_SENSE_MASK GENMASK(3, 2)
+#define LTC2983_RTD_N_WIRES_MASK GENMASK(3, 2)
+#define LTC2983_RTD_N_WIRES(x) \
+ FIELD_PREP(LTC2983_RTD_N_WIRES_MASK, x)
+#define LTC2983_RTD_R_SHARE_MASK BIT(0)
+#define LTC2983_RTD_R_SHARE(x) \
+ FIELD_PREP(LTC2983_RTD_R_SHARE_MASK, 1)
+
+#define LTC2983_COMMON_HARD_FAULT_MASK GENMASK(31, 30)
+#define LTC2983_COMMON_SOFT_FAULT_MASK GENMASK(27, 25)
+
+#define LTC2983_STATUS_START_MASK BIT(7)
+#define LTC2983_STATUS_START(x) FIELD_PREP(LTC2983_STATUS_START_MASK, x)
+#define LTC2983_STATUS_UP_MASK GENMASK(7, 6)
+#define LTC2983_STATUS_UP(reg) FIELD_GET(LTC2983_STATUS_UP_MASK, reg)
+
+#define LTC2983_STATUS_CHAN_SEL_MASK GENMASK(4, 0)
+#define LTC2983_STATUS_CHAN_SEL(x) \
+ FIELD_PREP(LTC2983_STATUS_CHAN_SEL_MASK, x)
+
+#define LTC2983_TEMP_UNITS_MASK BIT(2)
+#define LTC2983_TEMP_UNITS(x) FIELD_PREP(LTC2983_TEMP_UNITS_MASK, x)
+
+#define LTC2983_NOTCH_FREQ_MASK GENMASK(1, 0)
+#define LTC2983_NOTCH_FREQ(x) FIELD_PREP(LTC2983_NOTCH_FREQ_MASK, x)
+
+#define LTC2983_RES_VALID_MASK BIT(24)
+#define LTC2983_DATA_MASK GENMASK(23, 0)
+#define LTC2983_DATA_SIGN_BIT 23
+
+#define LTC2983_CHAN_TYPE_MASK GENMASK(31, 27)
+#define LTC2983_CHAN_TYPE(x) FIELD_PREP(LTC2983_CHAN_TYPE_MASK, x)
+
+/* cold junction for thermocouples and rsense for rtd's and thermistor's */
+#define LTC2983_CHAN_ASSIGN_MASK GENMASK(26, 22)
+#define LTC2983_CHAN_ASSIGN(x) FIELD_PREP(LTC2983_CHAN_ASSIGN_MASK, x)
+
+#define LTC2983_CUSTOM_LEN_MASK GENMASK(5, 0)
+#define LTC2983_CUSTOM_LEN(x) FIELD_PREP(LTC2983_CUSTOM_LEN_MASK, x)
+
+#define LTC2983_CUSTOM_ADDR_MASK GENMASK(11, 6)
+#define LTC2983_CUSTOM_ADDR(x) FIELD_PREP(LTC2983_CUSTOM_ADDR_MASK, x)
+
+#define LTC2983_THERMOCOUPLE_CFG_MASK GENMASK(21, 18)
+#define LTC2983_THERMOCOUPLE_CFG(x) \
+ FIELD_PREP(LTC2983_THERMOCOUPLE_CFG_MASK, x)
+#define LTC2983_THERMOCOUPLE_HARD_FAULT_MASK GENMASK(31, 29)
+#define LTC2983_THERMOCOUPLE_SOFT_FAULT_MASK GENMASK(28, 25)
+
+#define LTC2983_RTD_CFG_MASK GENMASK(21, 18)
+#define LTC2983_RTD_CFG(x) FIELD_PREP(LTC2983_RTD_CFG_MASK, x)
+#define LTC2983_RTD_EXC_CURRENT_MASK GENMASK(17, 14)
+#define LTC2983_RTD_EXC_CURRENT(x) \
+ FIELD_PREP(LTC2983_RTD_EXC_CURRENT_MASK, x)
+#define LTC2983_RTD_CURVE_MASK GENMASK(13, 12)
+#define LTC2983_RTD_CURVE(x) FIELD_PREP(LTC2983_RTD_CURVE_MASK, x)
+
+#define LTC2983_THERMISTOR_CFG_MASK GENMASK(21, 19)
+#define LTC2983_THERMISTOR_CFG(x) \
+ FIELD_PREP(LTC2983_THERMISTOR_CFG_MASK, x)
+#define LTC2983_THERMISTOR_EXC_CURRENT_MASK GENMASK(18, 15)
+#define LTC2983_THERMISTOR_EXC_CURRENT(x) \
+ FIELD_PREP(LTC2983_THERMISTOR_EXC_CURRENT_MASK, x)
+
+#define LTC2983_DIODE_CFG_MASK GENMASK(26, 24)
+#define LTC2983_DIODE_CFG(x) FIELD_PREP(LTC2983_DIODE_CFG_MASK, x)
+#define LTC2983_DIODE_EXC_CURRENT_MASK GENMASK(23, 22)
+#define LTC2983_DIODE_EXC_CURRENT(x) \
+ FIELD_PREP(LTC2983_DIODE_EXC_CURRENT_MASK, x)
+#define LTC2983_DIODE_IDEAL_FACTOR_MASK GENMASK(21, 0)
+#define LTC2983_DIODE_IDEAL_FACTOR(x) \
+ FIELD_PREP(LTC2983_DIODE_IDEAL_FACTOR_MASK, x)
+
+#define LTC2983_R_SENSE_VAL_MASK GENMASK(26, 0)
+#define LTC2983_R_SENSE_VAL(x) FIELD_PREP(LTC2983_R_SENSE_VAL_MASK, x)
+
+#define LTC2983_ADC_SINGLE_ENDED_MASK BIT(26)
+#define LTC2983_ADC_SINGLE_ENDED(x) \
+ FIELD_PREP(LTC2983_ADC_SINGLE_ENDED_MASK, x)
+
+enum {
+ LTC2983_SENSOR_THERMOCOUPLE = 1,
+ LTC2983_SENSOR_THERMOCOUPLE_CUSTOM = 9,
+ LTC2983_SENSOR_RTD = 10,
+ LTC2983_SENSOR_RTD_CUSTOM = 18,
+ LTC2983_SENSOR_THERMISTOR = 19,
+ LTC2983_SENSOR_THERMISTOR_STEINHART = 26,
+ LTC2983_SENSOR_THERMISTOR_CUSTOM = 27,
+ LTC2983_SENSOR_DIODE = 28,
+ LTC2983_SENSOR_SENSE_RESISTOR = 29,
+ LTC2983_SENSOR_DIRECT_ADC = 30,
+ LTC2983_SENSOR_ACTIVE_TEMP = 31,
+ /* Sensor types for some parts only; map to RTD_CUSTOM/THERMISTOR_CUSTOM in HW */
+ LTC2983_SENSOR_COPPER_TRACE = 32,
+ LTC2983_SENSOR_LEAK_DETECTOR = 33,
+ LTC2983_SENSOR_NUM
+};
+
+/* Bitmask of sensor types supported by LTC2983/LTC2984 and derivatives */
+#define LTC2983_COMMON_SENSORS \
+ (GENMASK_ULL(LTC2983_SENSOR_THERMOCOUPLE_CUSTOM, LTC2983_SENSOR_THERMOCOUPLE) | \
+ GENMASK_ULL(LTC2983_SENSOR_RTD_CUSTOM, LTC2983_SENSOR_RTD) | \
+ GENMASK_ULL(LTC2983_SENSOR_THERMISTOR_CUSTOM, LTC2983_SENSOR_THERMISTOR) | \
+ BIT_ULL(LTC2983_SENSOR_DIODE) | \
+ BIT_ULL(LTC2983_SENSOR_SENSE_RESISTOR) | \
+ BIT_ULL(LTC2983_SENSOR_DIRECT_ADC))
+
+/* Bitmask of sensor types supported by ADT7604 */
+#define ADT7604_SENSORS \
+ (GENMASK_ULL(LTC2983_SENSOR_RTD_CUSTOM - 1, LTC2983_SENSOR_RTD) | \
+ GENMASK_ULL(LTC2983_SENSOR_THERMISTOR_CUSTOM - 1, LTC2983_SENSOR_THERMISTOR) | \
+ BIT_ULL(LTC2983_SENSOR_SENSE_RESISTOR) | \
+ BIT_ULL(LTC2983_SENSOR_COPPER_TRACE) | \
+ BIT_ULL(LTC2983_SENSOR_LEAK_DETECTOR))
+
+#define to_thermocouple(_sensor) \
+ container_of(_sensor, struct ltc2983_thermocouple, sensor)
+
+#define to_rtd(_sensor) \
+ container_of(_sensor, struct ltc2983_rtd, sensor)
+
+#define to_copper_trace(_sensor) \
+ container_of(_sensor, struct ltc2983_copper_trace, sensor)
+
+#define to_thermistor(_sensor) \
+ container_of(_sensor, struct ltc2983_thermistor, sensor)
+
+#define to_leak_detector(_sensor) \
+ container_of(_sensor, struct ltc2983_leak_detector, sensor)
+
+#define to_diode(_sensor) \
+ container_of(_sensor, struct ltc2983_diode, sensor)
+
+#define to_rsense(_sensor) \
+ container_of(_sensor, struct ltc2983_rsense, sensor)
+
+#define to_adc(_sensor) \
+ container_of(_sensor, struct ltc2983_adc, sensor)
+
+#define to_temp(_sensor) \
+ container_of(_sensor, struct ltc2983_temp, sensor)
+
+struct ltc2983_chip_info {
+ const char *name;
+ unsigned int max_channels_nr;
+ u64 supported_sensors;
+ bool has_eeprom;
+};
+
+struct ltc2983_data {
+ const struct ltc2983_chip_info *info;
+ struct regmap *regmap;
+ struct spi_device *spi;
+ struct mutex lock;
+ struct completion completion;
+ struct iio_chan_spec *iio_chan;
+ struct ltc2983_sensor **sensors;
+ u32 mux_delay_config;
+ u32 filter_notch_freq;
+ u16 custom_table_size;
+ u8 num_channels;
+ u8 iio_channels;
+ /*
+ * DMA (thus cache coherency maintenance) may require the
+ * transfer buffers to live in their own cache lines.
+ * Holds the converted temperature
+ */
+ __be32 temp __aligned(IIO_DMA_MINALIGN);
+ __be32 chan_val;
+ __be32 eeprom_key;
+};
+
+struct ltc2983_sensor {
+ int (*fault_handler)(const struct ltc2983_data *st, const u32 result);
+ int (*assign_chan)(struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor);
+ /* specifies the sensor channel */
+ u32 chan;
+ /* sensor type */
+ u32 type;
+ /* number of IIO channels this sensor produces */
+ u8 n_iio_chan;
+};
+
+struct ltc2983_custom_sensor {
+ /* raw table sensor data */
+ void *table;
+ size_t size;
+ /* address offset */
+ s8 offset;
+ bool is_steinhart;
+};
+
+struct ltc2983_thermocouple {
+ struct ltc2983_sensor sensor;
+ struct ltc2983_custom_sensor *custom;
+ u32 sensor_config;
+ u32 cold_junction_chan;
+};
+
+struct ltc2983_rtd {
+ struct ltc2983_sensor sensor;
+ struct ltc2983_custom_sensor *custom;
+ u32 sensor_config;
+ u32 r_sense_chan;
+ u32 excitation_current;
+ u32 rtd_curve;
+};
+
+struct ltc2983_copper_trace {
+ struct ltc2983_sensor sensor;
+ struct ltc2983_custom_sensor *custom;
+ u32 r_sense_chan;
+ u32 excitation_current;
+ /* selects the <1Ω variant: bits 17:0 of the channel word are zeroed,
+ * disabling excitation current and custom table fields (ADT7604
+ * datasheet Table 26)
+ */
+ bool is_sub_ohm;
+};
+
+struct ltc2983_leak_detector {
+ struct ltc2983_sensor sensor;
+ struct ltc2983_custom_sensor *custom;
+ u32 r_sense_chan;
+ u32 excitation_current;
+};
+
+struct ltc2983_thermistor {
+ struct ltc2983_sensor sensor;
+ struct ltc2983_custom_sensor *custom;
+ u32 sensor_config;
+ u32 r_sense_chan;
+ u32 excitation_current;
+};
+
+struct ltc2983_diode {
+ struct ltc2983_sensor sensor;
+ u32 sensor_config;
+ u32 excitation_current;
+ u32 ideal_factor_value;
+};
+
+struct ltc2983_rsense {
+ struct ltc2983_sensor sensor;
+ u32 r_sense_val;
+};
+
+struct ltc2983_adc {
+ struct ltc2983_sensor sensor;
+ bool single_ended;
+};
+
+struct ltc2983_temp {
+ struct ltc2983_sensor sensor;
+ struct ltc2983_custom_sensor *custom;
+ bool single_ended;
+};
+
+/*
+ * Convert to Q format numbers. These number's are integers where
+ * the number of integer and fractional bits are specified. The resolution
+ * is given by 1/@resolution and tell us the number of fractional bits. For
+ * instance a resolution of 2^-10 means we have 10 fractional bits.
+ */
+static u32 __convert_to_raw(const u64 val, const u32 resolution)
+{
+ u64 __res = val * resolution;
+
+ /* all values are multiplied by 1000000 to remove the fraction */
+ do_div(__res, 1000000);
+
+ return __res;
+}
+
+static u32 __convert_to_raw_sign(const u64 val, const u32 resolution)
+{
+ s64 __res = -(s32)val;
+
+ __res = __convert_to_raw(__res, resolution);
+
+ return (u32)-__res;
+}
+
+static int __ltc2983_fault_handler(const struct ltc2983_data *st,
+ const u32 result, const u32 hard_mask,
+ const u32 soft_mask)
+{
+ const struct device *dev = &st->spi->dev;
+
+ if (result & hard_mask) {
+ dev_err(dev, "Invalid conversion: Sensor HARD fault\n");
+ return -EIO;
+ } else if (result & soft_mask) {
+ /* just print a warning */
+ dev_warn(dev, "Suspicious conversion: Sensor SOFT fault\n");
+ }
+
+ return 0;
+}
+
+static int __ltc2983_chan_assign_common(struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor,
+ u32 chan_val)
+{
+ struct device *dev = &st->spi->dev;
+ u32 reg = LTC2983_CHAN_ASSIGN_ADDR(sensor->chan);
+ u32 hw_type = sensor->type;
+
+ if (hw_type == LTC2983_SENSOR_COPPER_TRACE)
+ hw_type = LTC2983_SENSOR_RTD_CUSTOM;
+ else if (hw_type == LTC2983_SENSOR_LEAK_DETECTOR)
+ hw_type = LTC2983_SENSOR_THERMISTOR_CUSTOM;
+
+ chan_val |= LTC2983_CHAN_TYPE(hw_type);
+ dev_dbg(dev, "Assign reg:0x%04X, val:0x%08X\n", reg, chan_val);
+ st->chan_val = cpu_to_be32(chan_val);
+ return regmap_bulk_write(st->regmap, reg, &st->chan_val,
+ sizeof(st->chan_val));
+}
+
+static int __ltc2983_chan_custom_sensor_assign(struct ltc2983_data *st,
+ struct ltc2983_custom_sensor *custom,
+ u32 *chan_val)
+{
+ u32 reg;
+ u8 mult = custom->is_steinhart ? LTC2983_CUSTOM_STEINHART_ENTRY_SZ :
+ LTC2983_CUSTOM_SENSOR_ENTRY_SZ;
+ const struct device *dev = &st->spi->dev;
+ /*
+ * custom->size holds the raw size of the table. However, when
+ * configuring the sensor channel, we must write the number of
+ * entries of the table minus 1. For steinhart sensors 0 is written
+ * since the size is constant!
+ */
+ const u8 len = custom->is_steinhart ? 0 :
+ (custom->size / LTC2983_CUSTOM_SENSOR_ENTRY_SZ) - 1;
+ /*
+ * Check if the offset was assigned already. It should be for steinhart
+ * sensors. When coming from sleep, it should be assigned for all.
+ */
+ if (custom->offset < 0) {
+ /*
+ * This needs to be done again here because, from the moment
+ * when this test was done (successfully) for this custom
+ * sensor, a steinhart sensor might have been added changing
+ * custom_table_size...
+ */
+ if (st->custom_table_size + custom->size >
+ (LTC2983_CUST_SENS_TBL_END_REG -
+ LTC2983_CUST_SENS_TBL_START_REG) + 1) {
+ dev_err(dev,
+ "Not space left(%d) for new custom sensor(%zu)",
+ st->custom_table_size,
+ custom->size);
+ return -EINVAL;
+ }
+
+ custom->offset = st->custom_table_size /
+ LTC2983_CUSTOM_SENSOR_ENTRY_SZ;
+ st->custom_table_size += custom->size;
+ }
+
+ reg = (custom->offset * mult) + LTC2983_CUST_SENS_TBL_START_REG;
+
+ *chan_val |= LTC2983_CUSTOM_LEN(len);
+ *chan_val |= LTC2983_CUSTOM_ADDR(custom->offset);
+ dev_dbg(dev, "Assign custom sensor, reg:0x%04X, off:%d, sz:%zu",
+ reg, custom->offset,
+ custom->size);
+ /* write custom sensor table */
+ return regmap_bulk_write(st->regmap, reg, custom->table, custom->size);
+}
+
+static struct ltc2983_custom_sensor *
+__ltc2983_custom_sensor_new(struct ltc2983_data *st, const struct fwnode_handle *fn,
+ const char *propname, const bool is_steinhart,
+ const u32 resolution, const bool has_signed)
+{
+ struct ltc2983_custom_sensor *new_custom;
+ struct device *dev = &st->spi->dev;
+ /*
+ * For custom steinhart, the full u32 is taken. For all the others
+ * the MSB is discarded.
+ */
+ const u8 n_size = is_steinhart ? 4 : 3;
+ u8 index, n_entries;
+ int ret;
+
+ if (is_steinhart)
+ n_entries = fwnode_property_count_u32(fn, propname);
+ else
+ n_entries = fwnode_property_count_u64(fn, propname);
+ /* n_entries must be an even number */
+ if (!n_entries || (n_entries % 2) != 0)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Number of entries either 0 or not even\n");
+
+ new_custom = devm_kzalloc(dev, sizeof(*new_custom), GFP_KERNEL);
+ if (!new_custom)
+ return ERR_PTR(-ENOMEM);
+
+ new_custom->size = n_entries * n_size;
+ /* check Steinhart size */
+ if (is_steinhart && new_custom->size != LTC2983_CUSTOM_STEINHART_SIZE)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Steinhart sensors size(%zu) must be %u\n",
+ new_custom->size, LTC2983_CUSTOM_STEINHART_SIZE);
+
+ /* Check space on the table. */
+ if (st->custom_table_size + new_custom->size >
+ (LTC2983_CUST_SENS_TBL_END_REG - LTC2983_CUST_SENS_TBL_START_REG) + 1)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "No space left(%d) for new custom sensor(%zu)\n",
+ st->custom_table_size, new_custom->size);
+
+ /* allocate the table */
+ if (is_steinhart)
+ new_custom->table = devm_kcalloc(dev, n_entries, sizeof(u32), GFP_KERNEL);
+ else
+ new_custom->table = devm_kcalloc(dev, n_entries, sizeof(u64), GFP_KERNEL);
+ if (!new_custom->table)
+ return ERR_PTR(-ENOMEM);
+
+ /*
+ * Steinhart sensors are configured with raw values in the firmware
+ * node. For the other sensors we must convert the value to raw.
+ * The odd index's correspond to temperatures and always have 1/1024
+ * of resolution. Temperatures also come in Kelvin, so signed values
+ * are not possible.
+ */
+ if (is_steinhart) {
+ ret = fwnode_property_read_u32_array(fn, propname, new_custom->table, n_entries);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ cpu_to_be32_array(new_custom->table, new_custom->table, n_entries);
+ } else {
+ ret = fwnode_property_read_u64_array(fn, propname, new_custom->table, n_entries);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ for (index = 0; index < n_entries; index++) {
+ u64 temp = ((u64 *)new_custom->table)[index];
+
+ /*
+ * Users specify plain coverage percentage (0-100). Convert
+ * to µK so __convert_to_raw() produces the correct hardware
+ * encoding: P + 273.15 K.
+ */
+ if ((index % 2) != 0 && !strcmp(propname, "adi,custom-leak-detector"))
+ temp = temp * 1000000 + 273150000;
+
+ if ((index % 2) != 0)
+ temp = __convert_to_raw(temp, 1024);
+ else if (has_signed && (s64)temp < 0)
+ temp = __convert_to_raw_sign(temp, resolution);
+ else
+ temp = __convert_to_raw(temp, resolution);
+
+ put_unaligned_be24(temp, new_custom->table + index * 3);
+ }
+ }
+
+ new_custom->is_steinhart = is_steinhart;
+ /*
+ * This is done to first add all the steinhart sensors to the table,
+ * in order to maximize the table usage. If we mix adding steinhart
+ * with the other sensors, we might have to do some roundup to make
+ * sure that sensor_addr - 0x250(start address) is a multiple of 4
+ * (for steinhart), and a multiple of 6 for all the other sensors.
+ * Since we have const 24 bytes for steinhart sensors and 24 is
+ * also a multiple of 6, we guarantee that the first non-steinhart
+ * sensor will sit in a correct address without the need of filling
+ * addresses.
+ */
+ if (is_steinhart) {
+ new_custom->offset = st->custom_table_size /
+ LTC2983_CUSTOM_STEINHART_ENTRY_SZ;
+ st->custom_table_size += new_custom->size;
+ } else {
+ /* mark as unset. This is checked later on the assign phase */
+ new_custom->offset = -1;
+ }
+
+ return new_custom;
+}
+
+static int ltc2983_thermocouple_fault_handler(const struct ltc2983_data *st,
+ const u32 result)
+{
+ return __ltc2983_fault_handler(st, result,
+ LTC2983_THERMOCOUPLE_HARD_FAULT_MASK,
+ LTC2983_THERMOCOUPLE_SOFT_FAULT_MASK);
+}
+
+static int ltc2983_common_fault_handler(const struct ltc2983_data *st,
+ const u32 result)
+{
+ return __ltc2983_fault_handler(st, result,
+ LTC2983_COMMON_HARD_FAULT_MASK,
+ LTC2983_COMMON_SOFT_FAULT_MASK);
+}
+
+static int ltc2983_thermocouple_assign_chan(struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct ltc2983_thermocouple *thermo = to_thermocouple(sensor);
+ u32 chan_val;
+
+ chan_val = LTC2983_CHAN_ASSIGN(thermo->cold_junction_chan);
+ chan_val |= LTC2983_THERMOCOUPLE_CFG(thermo->sensor_config);
+
+ if (thermo->custom) {
+ int ret;
+
+ ret = __ltc2983_chan_custom_sensor_assign(st, thermo->custom,
+ &chan_val);
+ if (ret)
+ return ret;
+ }
+ return __ltc2983_chan_assign_common(st, sensor, chan_val);
+}
+
+static int ltc2983_rtd_assign_chan(struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct ltc2983_rtd *rtd = to_rtd(sensor);
+ u32 chan_val;
+
+ chan_val = LTC2983_CHAN_ASSIGN(rtd->r_sense_chan);
+ chan_val |= LTC2983_RTD_CFG(rtd->sensor_config);
+ chan_val |= LTC2983_RTD_EXC_CURRENT(rtd->excitation_current);
+ chan_val |= LTC2983_RTD_CURVE(rtd->rtd_curve);
+
+ if (rtd->custom) {
+ int ret;
+
+ ret = __ltc2983_chan_custom_sensor_assign(st, rtd->custom,
+ &chan_val);
+ if (ret)
+ return ret;
+ }
+ return __ltc2983_chan_assign_common(st, sensor, chan_val);
+}
+
+static int ltc2983_copper_trace_assign_chan(struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct ltc2983_copper_trace *ct = to_copper_trace(sensor);
+ u32 chan_val;
+
+ chan_val = LTC2983_CHAN_ASSIGN(ct->r_sense_chan);
+ /* Sensor config bits 21:18 must be 0b1001 (ADT7604 datasheet Table 26) */
+ chan_val |= LTC2983_RTD_CFG(0x9);
+
+ if (ct->is_sub_ohm) {
+ chan_val &= ~GENMASK(17, 0);
+ } else {
+ int ret;
+
+ chan_val |= LTC2983_RTD_EXC_CURRENT(ct->excitation_current);
+ ret = __ltc2983_chan_custom_sensor_assign(st, ct->custom,
+ &chan_val);
+ if (ret)
+ return ret;
+ }
+
+ return __ltc2983_chan_assign_common(st, sensor, chan_val);
+}
+
+static int ltc2983_thermistor_assign_chan(struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct ltc2983_thermistor *thermistor = to_thermistor(sensor);
+ u32 chan_val;
+
+ chan_val = LTC2983_CHAN_ASSIGN(thermistor->r_sense_chan);
+ chan_val |= LTC2983_THERMISTOR_CFG(thermistor->sensor_config);
+ chan_val |=
+ LTC2983_THERMISTOR_EXC_CURRENT(thermistor->excitation_current);
+
+ if (thermistor->custom) {
+ int ret;
+
+ ret = __ltc2983_chan_custom_sensor_assign(st,
+ thermistor->custom,
+ &chan_val);
+ if (ret)
+ return ret;
+ }
+ return __ltc2983_chan_assign_common(st, sensor, chan_val);
+}
+
+static int ltc2983_leak_detector_assign_chan(struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct ltc2983_leak_detector *ld = to_leak_detector(sensor);
+ u32 chan_val;
+ int ret;
+
+ chan_val = LTC2983_CHAN_ASSIGN(ld->r_sense_chan);
+ /* bits 21:19 must be 0b001 (ADT7604 datasheet Table 38) */
+ chan_val |= LTC2983_THERMISTOR_CFG(1);
+ chan_val |= LTC2983_THERMISTOR_EXC_CURRENT(ld->excitation_current);
+
+ ret = __ltc2983_chan_custom_sensor_assign(st, ld->custom, &chan_val);
+ if (ret)
+ return ret;
+
+ return __ltc2983_chan_assign_common(st, sensor, chan_val);
+}
+
+static int ltc2983_diode_assign_chan(struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct ltc2983_diode *diode = to_diode(sensor);
+ u32 chan_val;
+
+ chan_val = LTC2983_DIODE_CFG(diode->sensor_config);
+ chan_val |= LTC2983_DIODE_EXC_CURRENT(diode->excitation_current);
+ chan_val |= LTC2983_DIODE_IDEAL_FACTOR(diode->ideal_factor_value);
+
+ return __ltc2983_chan_assign_common(st, sensor, chan_val);
+}
+
+static int ltc2983_r_sense_assign_chan(struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct ltc2983_rsense *rsense = to_rsense(sensor);
+ u32 chan_val;
+
+ chan_val = LTC2983_R_SENSE_VAL(rsense->r_sense_val);
+
+ return __ltc2983_chan_assign_common(st, sensor, chan_val);
+}
+
+static int ltc2983_adc_assign_chan(struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct ltc2983_adc *adc = to_adc(sensor);
+ u32 chan_val;
+
+ chan_val = LTC2983_ADC_SINGLE_ENDED(adc->single_ended);
+
+ return __ltc2983_chan_assign_common(st, sensor, chan_val);
+}
+
+static int ltc2983_temp_assign_chan(struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct ltc2983_temp *temp = to_temp(sensor);
+ u32 chan_val;
+ int ret;
+
+ chan_val = LTC2983_ADC_SINGLE_ENDED(temp->single_ended);
+
+ ret = __ltc2983_chan_custom_sensor_assign(st, temp->custom, &chan_val);
+ if (ret)
+ return ret;
+
+ return __ltc2983_chan_assign_common(st, sensor, chan_val);
+}
+
+static struct ltc2983_sensor *
+ltc2983_thermocouple_new(const struct fwnode_handle *child, struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct device *dev = &st->spi->dev;
+ struct ltc2983_thermocouple *thermo;
+ u32 oc_current;
+ int ret;
+
+ thermo = devm_kzalloc(dev, sizeof(*thermo), GFP_KERNEL);
+ if (!thermo)
+ return ERR_PTR(-ENOMEM);
+
+ if (fwnode_property_read_bool(child, "adi,single-ended"))
+ thermo->sensor_config = LTC2983_THERMOCOUPLE_SGL(1);
+
+ if (fwnode_property_present(child, "adi,sensor-oc-current-microamp")) {
+ ret = fwnode_property_read_u32(child,
+ "adi,sensor-oc-current-microamp",
+ &oc_current);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "Failed to read adi,sensor-oc-current-microamp\n");
+
+ switch (oc_current) {
+ case 10:
+ thermo->sensor_config |=
+ LTC2983_THERMOCOUPLE_OC_CURR(0);
+ break;
+ case 100:
+ thermo->sensor_config |=
+ LTC2983_THERMOCOUPLE_OC_CURR(1);
+ break;
+ case 500:
+ thermo->sensor_config |=
+ LTC2983_THERMOCOUPLE_OC_CURR(2);
+ break;
+ case 1000:
+ thermo->sensor_config |=
+ LTC2983_THERMOCOUPLE_OC_CURR(3);
+ break;
+ default:
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid open circuit current:%u\n",
+ oc_current);
+ }
+
+ thermo->sensor_config |= LTC2983_THERMOCOUPLE_OC_CHECK(1);
+ }
+ /* validate channel index */
+ if (!(thermo->sensor_config & LTC2983_THERMOCOUPLE_DIFF_MASK) &&
+ sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid channel %d for differential thermocouple\n",
+ sensor->chan);
+
+ struct fwnode_handle *ref __free(fwnode_handle) =
+ fwnode_find_reference(child, "adi,cold-junction-handle", 0);
+ if (IS_ERR(ref)) {
+ ref = NULL;
+ } else {
+ ret = fwnode_property_read_u32(ref, "reg", &thermo->cold_junction_chan);
+ if (ret)
+ /*
+ * This would be caught later but we can just return
+ * the error right away.
+ */
+ return dev_err_ptr_probe(dev, ret,
+ "Property reg must be given\n");
+ }
+
+ /* check custom sensor */
+ if (sensor->type == LTC2983_SENSOR_THERMOCOUPLE_CUSTOM) {
+ const char *propname = "adi,custom-thermocouple";
+
+ thermo->custom = __ltc2983_custom_sensor_new(st, child,
+ propname, false,
+ 16384, true);
+ if (IS_ERR(thermo->custom))
+ return ERR_CAST(thermo->custom);
+ }
+
+ /* set common parameters */
+ thermo->sensor.fault_handler = ltc2983_thermocouple_fault_handler;
+ thermo->sensor.assign_chan = ltc2983_thermocouple_assign_chan;
+
+ return &thermo->sensor;
+}
+
+static struct ltc2983_sensor *
+ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct ltc2983_rtd *rtd;
+ int ret = 0;
+ struct device *dev = &st->spi->dev;
+ u32 excitation_current = 0, n_wires = 2;
+
+ rtd = devm_kzalloc(dev, sizeof(*rtd), GFP_KERNEL);
+ if (!rtd)
+ return ERR_PTR(-ENOMEM);
+
+ struct fwnode_handle *ref __free(fwnode_handle) =
+ fwnode_find_reference(child, "adi,rsense-handle", 0);
+ if (IS_ERR(ref))
+ return dev_err_cast_probe(dev, ref,
+ "Property adi,rsense-handle missing or invalid\n");
+
+ ret = fwnode_property_read_u32(ref, "reg", &rtd->r_sense_chan);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "Property reg must be given\n");
+
+ if (fwnode_property_present(child, "adi,number-of-wires")) {
+ ret = fwnode_property_read_u32(child, "adi,number-of-wires", &n_wires);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "Failed to read adi,number-of-wires\n");
+
+ switch (n_wires) {
+ case 2:
+ rtd->sensor_config = LTC2983_RTD_N_WIRES(0);
+ break;
+ case 3:
+ rtd->sensor_config = LTC2983_RTD_N_WIRES(1);
+ break;
+ case 4:
+ rtd->sensor_config = LTC2983_RTD_N_WIRES(2);
+ break;
+ case 5:
+ /* 4 wires, Kelvin Rsense */
+ rtd->sensor_config = LTC2983_RTD_N_WIRES(3);
+ break;
+ default:
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid number of wires:%u\n",
+ n_wires);
+ }
+ }
+
+ if (fwnode_property_read_bool(child, "adi,rsense-share")) {
+ /* Current rotation is only available with rsense sharing */
+ if (fwnode_property_read_bool(child, "adi,current-rotate")) {
+ if (n_wires == 2 || n_wires == 3)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Rotation not allowed for 2/3 Wire RTDs\n");
+
+ rtd->sensor_config |= LTC2983_RTD_C_ROTATE(1);
+ } else {
+ rtd->sensor_config |= LTC2983_RTD_R_SHARE(1);
+ }
+ }
+ /*
+ * rtd channel indexes are a bit more complicated to validate.
+ * For 4wire RTD with rotation, the channel selection cannot be
+ * >=19 since the channel + 1 is used in this configuration.
+ * For 4wire RTDs with kelvin rsense, the rsense channel cannot be
+ * <=1 since channel - 1 and channel - 2 are used.
+ */
+ if (rtd->sensor_config & LTC2983_RTD_4_WIRE_MASK) {
+ /* 4-wire */
+ u8 min = LTC2983_DIFFERENTIAL_CHAN_MIN,
+ max = st->info->max_channels_nr;
+
+ if (rtd->sensor_config & LTC2983_RTD_ROTATION_MASK)
+ max = st->info->max_channels_nr - 1;
+
+ if (((rtd->sensor_config & LTC2983_RTD_KELVIN_R_SENSE_MASK)
+ == LTC2983_RTD_KELVIN_R_SENSE_MASK) &&
+ (rtd->r_sense_chan <= min))
+ /* kelvin rsense*/
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid channel %d for kelvin rsense\n",
+ rtd->r_sense_chan);
+
+ if (sensor->chan < min || sensor->chan > max)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid channel %d for RTD config\n",
+ sensor->chan);
+ } else {
+ /* same as differential case */
+ if (sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid channel %d for RTD\n",
+ sensor->chan);
+ }
+
+ /* check custom sensor */
+ if (sensor->type == LTC2983_SENSOR_RTD_CUSTOM) {
+ rtd->custom = __ltc2983_custom_sensor_new(st, child,
+ "adi,custom-rtd",
+ false, 2048, false);
+ if (IS_ERR(rtd->custom))
+ return ERR_CAST(rtd->custom);
+ }
+
+ /* set common parameters */
+ rtd->sensor.fault_handler = ltc2983_common_fault_handler;
+ rtd->sensor.assign_chan = ltc2983_rtd_assign_chan;
+
+ if (fwnode_property_present(child, "adi,excitation-current-microamp")) {
+ ret = fwnode_property_read_u32(child, "adi,excitation-current-microamp",
+ &excitation_current);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "Failed to read adi,excitation-current-microamp\n");
+
+ switch (excitation_current) {
+ case 5:
+ rtd->excitation_current = 0x01;
+ break;
+ case 10:
+ rtd->excitation_current = 0x02;
+ break;
+ case 25:
+ rtd->excitation_current = 0x03;
+ break;
+ case 50:
+ rtd->excitation_current = 0x04;
+ break;
+ case 100:
+ rtd->excitation_current = 0x05;
+ break;
+ case 250:
+ rtd->excitation_current = 0x06;
+ break;
+ case 500:
+ rtd->excitation_current = 0x07;
+ break;
+ case 1000:
+ rtd->excitation_current = 0x08;
+ break;
+ default:
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid value for excitation current(%u)\n",
+ excitation_current);
+ }
+ } else {
+ /* default to 5uA */
+ rtd->excitation_current = 1;
+ }
+
+ if (fwnode_property_present(child, "adi,rtd-curve")) {
+ ret = fwnode_property_read_u32(child, "adi,rtd-curve", &rtd->rtd_curve);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "Failed to read adi,rtd-curve\n");
+ }
+
+ return &rtd->sensor;
+}
+
+static struct ltc2983_sensor *
+ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct ltc2983_thermistor *thermistor;
+ struct device *dev = &st->spi->dev;
+ u32 excitation_current = 0;
+ int ret = 0;
+
+ thermistor = devm_kzalloc(dev, sizeof(*thermistor), GFP_KERNEL);
+ if (!thermistor)
+ return ERR_PTR(-ENOMEM);
+
+ struct fwnode_handle *ref __free(fwnode_handle) =
+ fwnode_find_reference(child, "adi,rsense-handle", 0);
+ if (IS_ERR(ref))
+ return dev_err_cast_probe(dev, ref,
+ "Property adi,rsense-handle missing or invalid\n");
+
+ ret = fwnode_property_read_u32(ref, "reg", &thermistor->r_sense_chan);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "rsense channel must be configured...\n");
+
+ if (fwnode_property_read_bool(child, "adi,single-ended")) {
+ thermistor->sensor_config = LTC2983_THERMISTOR_SGL(1);
+ } else if (fwnode_property_read_bool(child, "adi,rsense-share")) {
+ /* rotation is only possible if sharing rsense */
+ if (fwnode_property_read_bool(child, "adi,current-rotate"))
+ thermistor->sensor_config =
+ LTC2983_THERMISTOR_C_ROTATE(1);
+ else
+ thermistor->sensor_config =
+ LTC2983_THERMISTOR_R_SHARE(1);
+ }
+ /* validate channel index */
+ if (!(thermistor->sensor_config & LTC2983_THERMISTOR_DIFF_MASK) &&
+ sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid channel %d for differential thermistor\n",
+ sensor->chan);
+
+ /* check custom sensor */
+ if (sensor->type >= LTC2983_SENSOR_THERMISTOR_STEINHART) {
+ bool steinhart = false;
+ const char *propname;
+
+ if (sensor->type == LTC2983_SENSOR_THERMISTOR_STEINHART) {
+ steinhart = true;
+ propname = "adi,custom-steinhart";
+ } else {
+ propname = "adi,custom-thermistor";
+ }
+
+ thermistor->custom = __ltc2983_custom_sensor_new(st, child,
+ propname,
+ steinhart,
+ 64, false);
+ if (IS_ERR(thermistor->custom))
+ return ERR_CAST(thermistor->custom);
+ }
+ /* set common parameters */
+ thermistor->sensor.fault_handler = ltc2983_common_fault_handler;
+ thermistor->sensor.assign_chan = ltc2983_thermistor_assign_chan;
+
+ if (fwnode_property_present(child, "adi,excitation-current-nanoamp")) {
+ ret = fwnode_property_read_u32(child, "adi,excitation-current-nanoamp",
+ &excitation_current);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "Failed to read adi,excitation-current-nanoamp\n");
+
+ switch (excitation_current) {
+ case 0:
+ /* auto range */
+ if (sensor->type >= LTC2983_SENSOR_THERMISTOR_STEINHART)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Auto Range not allowed for custom sensors\n");
+
+ thermistor->excitation_current = 0x0c;
+ break;
+ case 250:
+ thermistor->excitation_current = 0x01;
+ break;
+ case 500:
+ thermistor->excitation_current = 0x02;
+ break;
+ case 1000:
+ thermistor->excitation_current = 0x03;
+ break;
+ case 5000:
+ thermistor->excitation_current = 0x04;
+ break;
+ case 10000:
+ thermistor->excitation_current = 0x05;
+ break;
+ case 25000:
+ thermistor->excitation_current = 0x06;
+ break;
+ case 50000:
+ thermistor->excitation_current = 0x07;
+ break;
+ case 100000:
+ thermistor->excitation_current = 0x08;
+ break;
+ case 250000:
+ thermistor->excitation_current = 0x09;
+ break;
+ case 500000:
+ thermistor->excitation_current = 0x0a;
+ break;
+ case 1000000:
+ thermistor->excitation_current = 0x0b;
+ break;
+ default:
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid value for excitation current(%u)\n",
+ excitation_current);
+ }
+ } else {
+ /* Auto range is not allowed for custom sensors */
+ if (sensor->type >= LTC2983_SENSOR_THERMISTOR_STEINHART)
+ /* default to 1uA */
+ thermistor->excitation_current = 0x03;
+ else
+ /* default to auto-range */
+ thermistor->excitation_current = 0x0c;
+ }
+
+ return &thermistor->sensor;
+}
+
+static struct ltc2983_sensor *
+ltc2983_copper_trace_new(const struct fwnode_handle *child, struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct device *dev = &st->spi->dev;
+ struct ltc2983_copper_trace *ct;
+ int ret;
+
+ if (sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid channel %d for copper trace\n",
+ sensor->chan);
+
+ ct = devm_kzalloc(dev, sizeof(*ct), GFP_KERNEL);
+ if (!ct)
+ return ERR_PTR(-ENOMEM);
+
+ struct fwnode_handle *ref __free(fwnode_handle) =
+ fwnode_find_reference(child, "adi,rsense-handle", 0);
+ if (IS_ERR(ref))
+ return dev_err_cast_probe(dev, ref,
+ "Property adi,rsense-handle missing or invalid\n");
+
+ ret = fwnode_property_read_u32(ref, "reg", &ct->r_sense_chan);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret, "Property reg must be given\n");
+
+ ct->is_sub_ohm = fwnode_property_read_bool(child, "adi,copper-trace-sub-ohm");
+
+ if (ct->is_sub_ohm && fwnode_property_present(child, "adi,custom-copper-trace"))
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "sub-ohm copper trace cannot have a custom table\n");
+
+ if (!ct->is_sub_ohm) {
+ u32 excitation_current = 0;
+
+ if (!fwnode_property_present(child, "adi,custom-copper-trace"))
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "adi,custom-copper-trace is required for >1 ohm copper trace\n");
+
+ ct->custom = __ltc2983_custom_sensor_new(st, child, "adi,custom-copper-trace",
+ false, 2048, false);
+ if (IS_ERR(ct->custom))
+ return ERR_CAST(ct->custom);
+
+ if (fwnode_property_present(child, "adi,excitation-current-microamp")) {
+ ret = fwnode_property_read_u32(child, "adi,excitation-current-microamp",
+ &excitation_current);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "Failed to read adi,excitation-current-microamp\n");
+
+ switch (excitation_current) {
+ case 5:
+ ct->excitation_current = 0x01;
+ break;
+ case 10:
+ ct->excitation_current = 0x02;
+ break;
+ case 25:
+ ct->excitation_current = 0x03;
+ break;
+ case 50:
+ ct->excitation_current = 0x04;
+ break;
+ case 100:
+ ct->excitation_current = 0x05;
+ break;
+ case 250:
+ ct->excitation_current = 0x06;
+ break;
+ case 500:
+ ct->excitation_current = 0x07;
+ break;
+ case 1000:
+ ct->excitation_current = 0x08;
+ break;
+ default:
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid value for excitation current(%u)\n",
+ excitation_current);
+ }
+ } else {
+ /* default to 1mA per datasheet recommendation for copper trace */
+ ct->excitation_current = 0x08;
+ }
+ }
+
+ ct->sensor.fault_handler = ltc2983_common_fault_handler;
+ ct->sensor.assign_chan = ltc2983_copper_trace_assign_chan;
+ if (ct->is_sub_ohm)
+ ct->sensor.n_iio_chan = 1;
+ else
+ ct->sensor.n_iio_chan = 2;
+
+ return &ct->sensor;
+}
+
+static struct ltc2983_sensor *
+ltc2983_leak_detector_new(const struct fwnode_handle *child, struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct device *dev = &st->spi->dev;
+ struct ltc2983_leak_detector *ld;
+ int ret;
+ u32 excitation_current = 0;
+
+ if (sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid channel %d for leak detector\n",
+ sensor->chan);
+
+ ld = devm_kzalloc(dev, sizeof(*ld), GFP_KERNEL);
+ if (!ld)
+ return ERR_PTR(-ENOMEM);
+
+ struct fwnode_handle *ref __free(fwnode_handle) =
+ fwnode_find_reference(child, "adi,rsense-handle", 0);
+ if (IS_ERR(ref))
+ return dev_err_cast_probe(dev, ref,
+ "Property adi,rsense-handle missing or invalid\n");
+
+ ret = fwnode_property_read_u32(ref, "reg", &ld->r_sense_chan);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "rsense channel must be configured\n");
+
+ if (!fwnode_property_present(child, "adi,custom-leak-detector"))
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "adi,custom-leak-detector is required for leak detectors\n");
+
+ ld->custom = __ltc2983_custom_sensor_new(st, child, "adi,custom-leak-detector",
+ false, 16, false);
+ if (IS_ERR(ld->custom))
+ return ERR_CAST(ld->custom);
+
+ ret = fwnode_property_read_u32(child, "adi,excitation-current-nanoamp",
+ &excitation_current);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "adi,excitation-current-nanoamp is required for leak detectors\n");
+
+ switch (excitation_current) {
+ case 250:
+ ld->excitation_current = 0x01;
+ break;
+ case 500:
+ ld->excitation_current = 0x02;
+ break;
+ case 1000:
+ ld->excitation_current = 0x03;
+ break;
+ case 5000:
+ ld->excitation_current = 0x04;
+ break;
+ case 10000:
+ ld->excitation_current = 0x05;
+ break;
+ case 25000:
+ ld->excitation_current = 0x06;
+ break;
+ case 50000:
+ ld->excitation_current = 0x07;
+ break;
+ case 100000:
+ ld->excitation_current = 0x08;
+ break;
+ case 250000:
+ ld->excitation_current = 0x09;
+ break;
+ case 500000:
+ ld->excitation_current = 0x0a;
+ break;
+ case 1000000:
+ ld->excitation_current = 0x0b;
+ break;
+ default:
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid value for excitation current(%u)\n",
+ excitation_current);
+ }
+
+ ld->sensor.fault_handler = ltc2983_common_fault_handler;
+ ld->sensor.assign_chan = ltc2983_leak_detector_assign_chan;
+ ld->sensor.n_iio_chan = 2;
+
+ return &ld->sensor;
+}
+
+static struct ltc2983_sensor *
+ltc2983_diode_new(const struct fwnode_handle *child, const struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct device *dev = &st->spi->dev;
+ struct ltc2983_diode *diode;
+ u32 temp = 0, excitation_current = 0;
+ int ret;
+
+ diode = devm_kzalloc(dev, sizeof(*diode), GFP_KERNEL);
+ if (!diode)
+ return ERR_PTR(-ENOMEM);
+
+ if (fwnode_property_read_bool(child, "adi,single-ended"))
+ diode->sensor_config = LTC2983_DIODE_SGL(1);
+
+ if (fwnode_property_read_bool(child, "adi,three-conversion-cycles"))
+ diode->sensor_config |= LTC2983_DIODE_3_CONV_CYCLE(1);
+
+ if (fwnode_property_read_bool(child, "adi,average-on"))
+ diode->sensor_config |= LTC2983_DIODE_AVERAGE_ON(1);
+
+ /* validate channel index */
+ if (!(diode->sensor_config & LTC2983_DIODE_DIFF_MASK) &&
+ sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid channel %d for differential diode\n",
+ sensor->chan);
+
+ /* set common parameters */
+ diode->sensor.fault_handler = ltc2983_common_fault_handler;
+ diode->sensor.assign_chan = ltc2983_diode_assign_chan;
+
+ if (fwnode_property_present(child, "adi,excitation-current-microamp")) {
+ ret = fwnode_property_read_u32(child, "adi,excitation-current-microamp",
+ &excitation_current);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "Failed to read adi,excitation-current-microamp\n");
+
+ switch (excitation_current) {
+ case 10:
+ diode->excitation_current = 0x00;
+ break;
+ case 20:
+ diode->excitation_current = 0x01;
+ break;
+ case 40:
+ diode->excitation_current = 0x02;
+ break;
+ case 80:
+ diode->excitation_current = 0x03;
+ break;
+ default:
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid value for excitation current(%u)\n",
+ excitation_current);
+ }
+ }
+
+ if (fwnode_property_present(child, "adi,ideal-factor-value")) {
+ ret = fwnode_property_read_u32(child, "adi,ideal-factor-value", &temp);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret,
+ "Failed to read adi,ideal-factor-value\n");
+ }
+
+ /* 2^20 resolution */
+ diode->ideal_factor_value = __convert_to_raw(temp, 1048576);
+
+ return &diode->sensor;
+}
+
+static struct ltc2983_sensor *ltc2983_r_sense_new(struct fwnode_handle *child,
+ struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct device *dev = &st->spi->dev;
+ struct ltc2983_rsense *rsense;
+ int ret;
+ u32 temp;
+
+ rsense = devm_kzalloc(dev, sizeof(*rsense), GFP_KERNEL);
+ if (!rsense)
+ return ERR_PTR(-ENOMEM);
+
+ /* validate channel index */
+ if (sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid channel %d for r_sense\n",
+ sensor->chan);
+
+ ret = fwnode_property_read_u32(child, "adi,rsense-val-milli-ohms", &temp);
+ if (ret)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Property adi,rsense-val-milli-ohms missing\n");
+ /*
+ * Times 1000 because we have milli-ohms and __convert_to_raw
+ * expects scales of 1000000 which are used for all other
+ * properties.
+ * 2^10 resolution
+ */
+ rsense->r_sense_val = __convert_to_raw((u64)temp * 1000, 1024);
+
+ /* set common parameters */
+ rsense->sensor.assign_chan = ltc2983_r_sense_assign_chan;
+
+ return &rsense->sensor;
+}
+
+static struct ltc2983_sensor *ltc2983_adc_new(struct fwnode_handle *child,
+ struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct device *dev = &st->spi->dev;
+ struct ltc2983_adc *adc;
+
+ adc = devm_kzalloc(dev, sizeof(*adc), GFP_KERNEL);
+ if (!adc)
+ return ERR_PTR(-ENOMEM);
+
+ if (fwnode_property_read_bool(child, "adi,single-ended"))
+ adc->single_ended = true;
+
+ if (!adc->single_ended && sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid channel %d for differential ADC\n",
+ sensor->chan);
+
+ /* set common parameters */
+ adc->sensor.assign_chan = ltc2983_adc_assign_chan;
+ adc->sensor.fault_handler = ltc2983_common_fault_handler;
+
+ return &adc->sensor;
+}
+
+static struct ltc2983_sensor *ltc2983_temp_new(struct fwnode_handle *child,
+ struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor)
+{
+ struct device *dev = &st->spi->dev;
+ struct ltc2983_temp *temp;
+
+ temp = devm_kzalloc(dev, sizeof(*temp), GFP_KERNEL);
+ if (!temp)
+ return ERR_PTR(-ENOMEM);
+
+ if (fwnode_property_read_bool(child, "adi,single-ended"))
+ temp->single_ended = true;
+
+ if (!temp->single_ended && sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN)
+ return dev_err_ptr_probe(dev, -EINVAL,
+ "Invalid channel %d for differential temp\n",
+ sensor->chan);
+
+ temp->custom = __ltc2983_custom_sensor_new(st, child, "adi,custom-temp",
+ false, 4096, true);
+ if (IS_ERR(temp->custom))
+ return ERR_CAST(temp->custom);
+
+ /* set common parameters */
+ temp->sensor.assign_chan = ltc2983_temp_assign_chan;
+ temp->sensor.fault_handler = ltc2983_common_fault_handler;
+
+ return &temp->sensor;
+}
+
+static int ltc2983_chan_read(struct ltc2983_data *st,
+ const struct ltc2983_sensor *sensor,
+ u32 base_reg, int *val)
+{
+ struct device *dev = &st->spi->dev;
+ u32 start_conversion = 0;
+ int ret;
+ unsigned long time;
+
+ start_conversion = LTC2983_STATUS_START(true);
+ start_conversion |= LTC2983_STATUS_CHAN_SEL(sensor->chan);
+ dev_dbg(dev, "Start conversion on channel:%d, status:%02X\n",
+ sensor->chan, start_conversion);
+ reinit_completion(&st->completion);
+ /* start conversion */
+ ret = regmap_write(st->regmap, LTC2983_STATUS_REG, start_conversion);
+ if (ret)
+ return ret;
+ /*
+ * wait for conversion to complete.
+ * 300 ms should be more than enough to complete the conversion.
+ * Depending on the sensor configuration, there are 2/3 conversions
+ * cycles of 82ms.
+ */
+ time = wait_for_completion_timeout(&st->completion,
+ msecs_to_jiffies(300));
+ if (!time) {
+ dev_warn(dev, "Conversion timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ /* read the converted data */
+ ret = regmap_bulk_read(st->regmap, LTC2983_RESULT_ADDR(sensor->chan, base_reg),
+ &st->temp, sizeof(st->temp));
+ if (ret)
+ return ret;
+
+ *val = __be32_to_cpu(st->temp);
+
+ if (base_reg == ADT7604_RES_RES_START_REG) {
+ /*
+ * Resistance result register gives a plain unsigned value,
+ * D31 is always 0, no valid bit, no fault bits. Read bits[30:0]
+ * directly — the temperature result format does not apply here.
+ */
+ *val &= GENMASK(30, 0);
+ return 0;
+ }
+
+ if (!(LTC2983_RES_VALID_MASK & *val)) {
+ dev_err(dev, "Invalid conversion detected\n");
+ return -EIO;
+ }
+
+ ret = sensor->fault_handler(st, *val);
+ if (ret)
+ return ret;
+
+ *val = sign_extend32((*val) & LTC2983_DATA_MASK, LTC2983_DATA_SIGN_BIT);
+ return 0;
+}
+
+static int ltc2983_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct ltc2983_data *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ int ret;
+
+ /* sanity check */
+ if (chan->address >= st->num_channels) {
+ dev_err(dev, "Invalid channel address: %ld\n", chan->address);
+ return -EINVAL;
+ }
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&st->lock);
+ switch (chan->type) {
+ case IIO_RESISTANCE:
+ ret = ltc2983_chan_read(st, st->sensors[chan->address],
+ ADT7604_RES_RES_START_REG, val);
+ break;
+ default:
+ ret = ltc2983_chan_read(st, st->sensors[chan->address],
+ LTC2983_TEMP_RES_START_REG, val);
+ break;
+ }
+ mutex_unlock(&st->lock);
+ return ret ?: IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_TEMP:
+ /* value in milli degrees */
+ *val = 1000;
+ /* 2^10 */
+ *val2 = 1024;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_VOLTAGE:
+ /* value in millivolt */
+ *val = 1000;
+ /* 2^21 */
+ *val2 = 2097152;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_RESISTANCE:
+ case IIO_COVERAGE:
+ /* value in ohm/percent */
+ *val = 1;
+ /* 2^10 */
+ *val2 = 1024;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int ltc2983_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg,
+ unsigned int writeval,
+ unsigned int *readval)
+{
+ struct ltc2983_data *st = iio_priv(indio_dev);
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static irqreturn_t ltc2983_irq_handler(int irq, void *data)
+{
+ struct ltc2983_data *st = data;
+
+ complete(&st->completion);
+ return IRQ_HANDLED;
+}
+
+#define LTC2983_CHAN(__type, index, __address) ({ \
+ struct iio_chan_spec __chan = { \
+ .type = __type, \
+ .indexed = 1, \
+ .channel = index, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .address = __address, \
+ }; \
+ __chan; \
+})
+
+static int ltc2983_parse_fw(struct ltc2983_data *st)
+{
+ struct device *dev = &st->spi->dev;
+ int ret, chan = 0, channel_avail_mask = 0;
+
+ device_property_read_u32(dev, "adi,mux-delay-config-us", &st->mux_delay_config);
+
+ device_property_read_u32(dev, "adi,filter-notch-freq", &st->filter_notch_freq);
+
+ st->num_channels = device_get_child_node_count(dev);
+ if (!st->num_channels)
+ return dev_err_probe(dev, -EINVAL,
+ "At least one channel must be given!\n");
+
+ st->sensors = devm_kcalloc(dev, st->num_channels, sizeof(*st->sensors),
+ GFP_KERNEL);
+ if (!st->sensors)
+ return -ENOMEM;
+
+ st->iio_channels = 0;
+ device_for_each_child_node_scoped(dev, child) {
+ struct ltc2983_sensor sensor;
+
+ ret = fwnode_property_read_u32(child, "reg", &sensor.chan);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "reg property must given for child nodes\n");
+
+ /* check if we have a valid channel */
+ if (sensor.chan < LTC2983_MIN_CHANNELS_NR ||
+ sensor.chan > st->info->max_channels_nr)
+ return dev_err_probe(dev, -EINVAL,
+ "channel:%d must be from %u to %u\n",
+ sensor.chan,
+ LTC2983_MIN_CHANNELS_NR,
+ st->info->max_channels_nr);
+
+ if (channel_avail_mask & BIT(sensor.chan))
+ return dev_err_probe(dev, -EINVAL,
+ "channel:%d already in use\n",
+ sensor.chan);
+
+ ret = fwnode_property_read_u32(child, "adi,sensor-type", &sensor.type);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "adi,sensor-type property must given for child nodes\n");
+
+ if (sensor.type >= LTC2983_SENSOR_NUM ||
+ !(st->info->supported_sensors & BIT_ULL(sensor.type)))
+ return dev_err_probe(dev, -EINVAL,
+ "sensor type %d not supported on %s\n",
+ sensor.type, st->info->name);
+
+ dev_dbg(dev, "Create new sensor, type %u, channel %u",
+ sensor.type, sensor.chan);
+
+ if (sensor.type >= LTC2983_SENSOR_THERMOCOUPLE &&
+ sensor.type <= LTC2983_SENSOR_THERMOCOUPLE_CUSTOM) {
+ st->sensors[chan] = ltc2983_thermocouple_new(child, st,
+ &sensor);
+ } else if (sensor.type >= LTC2983_SENSOR_RTD &&
+ sensor.type <= LTC2983_SENSOR_RTD_CUSTOM) {
+ st->sensors[chan] = ltc2983_rtd_new(child, st, &sensor);
+ } else if (sensor.type >= LTC2983_SENSOR_THERMISTOR &&
+ sensor.type <= LTC2983_SENSOR_THERMISTOR_CUSTOM) {
+ st->sensors[chan] = ltc2983_thermistor_new(child, st,
+ &sensor);
+ } else if (sensor.type == LTC2983_SENSOR_DIODE) {
+ st->sensors[chan] = ltc2983_diode_new(child, st,
+ &sensor);
+ } else if (sensor.type == LTC2983_SENSOR_SENSE_RESISTOR) {
+ st->sensors[chan] = ltc2983_r_sense_new(child, st,
+ &sensor);
+ } else if (sensor.type == LTC2983_SENSOR_DIRECT_ADC) {
+ st->sensors[chan] = ltc2983_adc_new(child, st, &sensor);
+ } else if (sensor.type == LTC2983_SENSOR_ACTIVE_TEMP) {
+ st->sensors[chan] = ltc2983_temp_new(child, st, &sensor);
+ } else if (sensor.type == LTC2983_SENSOR_COPPER_TRACE) {
+ st->sensors[chan] = ltc2983_copper_trace_new(child, st, &sensor);
+ } else if (sensor.type == LTC2983_SENSOR_LEAK_DETECTOR) {
+ st->sensors[chan] = ltc2983_leak_detector_new(child, st, &sensor);
+ } else {
+ return dev_err_probe(dev, -EINVAL,
+ "Unknown sensor type %d\n",
+ sensor.type);
+ }
+
+ if (IS_ERR(st->sensors[chan]))
+ return dev_err_probe(dev, PTR_ERR(st->sensors[chan]),
+ "Failed to create sensor\n");
+
+ /* set generic sensor parameters */
+ st->sensors[chan]->chan = sensor.chan;
+ st->sensors[chan]->type = sensor.type;
+
+ /*
+ * Dedicated functions set n_iio_chan themselves; for all other
+ * sensor types rsense produces 0 channels, everything else 1.
+ */
+ if (!st->sensors[chan]->n_iio_chan) {
+ if (sensor.type != LTC2983_SENSOR_SENSE_RESISTOR)
+ st->sensors[chan]->n_iio_chan = 1;
+ }
+ st->iio_channels += st->sensors[chan]->n_iio_chan;
+
+ channel_avail_mask |= BIT(sensor.chan);
+ chan++;
+ }
+
+ return 0;
+}
+
+static int ltc2983_eeprom_cmd(struct ltc2983_data *st, unsigned int cmd,
+ unsigned int wait_time, unsigned int status_reg,
+ unsigned long status_fail_mask)
+{
+ struct device *dev = &st->spi->dev;
+ unsigned long time;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_bulk_write(st->regmap, LTC2983_EEPROM_KEY_REG,
+ &st->eeprom_key, sizeof(st->eeprom_key));
+ if (ret)
+ return ret;
+
+ reinit_completion(&st->completion);
+
+ ret = regmap_write(st->regmap, LTC2983_STATUS_REG,
+ LTC2983_STATUS_START(true) | cmd);
+ if (ret)
+ return ret;
+
+ time = wait_for_completion_timeout(&st->completion,
+ msecs_to_jiffies(wait_time));
+ if (!time)
+ return dev_err_probe(dev, -ETIMEDOUT,
+ "EEPROM command timed out\n");
+
+ ret = regmap_read(st->regmap, status_reg, &val);
+ if (ret)
+ return ret;
+
+ if (val & status_fail_mask)
+ return dev_err_probe(dev, -EINVAL,
+ "EEPROM command failed: 0x%02X\n", val);
+
+ return 0;
+}
+
+static int ltc2983_setup(struct ltc2983_data *st, bool assign_iio)
+{
+ struct device *dev = &st->spi->dev;
+ u32 iio_chan_t = 0, iio_chan_v = 0, iio_chan_r = 0, iio_chan_c = 0;
+ u32 chan, iio_idx = 0, status;
+ int ret;
+
+ /* make sure the device is up: start bit (7) is 0 and done bit (6) is 1 */
+ ret = regmap_read_poll_timeout(st->regmap, LTC2983_STATUS_REG, status,
+ LTC2983_STATUS_UP(status) == 1, 25000,
+ 25000 * 10);
+ if (ret)
+ return dev_err_probe(dev, ret, "Device startup timed out\n");
+
+ ret = regmap_update_bits(st->regmap, LTC2983_GLOBAL_CONFIG_REG,
+ LTC2983_NOTCH_FREQ_MASK,
+ LTC2983_NOTCH_FREQ(st->filter_notch_freq));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, LTC2983_MUX_CONFIG_REG,
+ st->mux_delay_config);
+ if (ret)
+ return ret;
+
+ if (st->info->has_eeprom && !assign_iio) {
+ ret = ltc2983_eeprom_cmd(st, LTC2983_EEPROM_READ_CMD,
+ LTC2983_EEPROM_READ_TIME_MS,
+ LTC2983_EEPROM_READ_STATUS_REG,
+ LTC2983_EEPROM_READ_FAILURE_MASK);
+ if (!ret)
+ return 0;
+ }
+
+ for (chan = 0; chan < st->num_channels; chan++) {
+ u32 chan_type = 0, *iio_chan;
+
+ ret = st->sensors[chan]->assign_chan(st, st->sensors[chan]);
+ if (ret)
+ return ret;
+ /*
+ * The assign_iio flag is necessary for when the device is
+ * coming out of sleep. In that case, we just need to
+ * re-configure the device channels.
+ * We also don't assign iio channels for rsense.
+ */
+ if (st->sensors[chan]->type == LTC2983_SENSOR_SENSE_RESISTOR ||
+ !assign_iio)
+ continue;
+
+ /* assign iio channel */
+ switch (st->sensors[chan]->type) {
+ case LTC2983_SENSOR_COPPER_TRACE:
+ if (st->sensors[chan]->n_iio_chan == 1) {
+ /* sub-ohm copper traces produce only a resistance result */
+ st->iio_chan[iio_idx++] =
+ LTC2983_CHAN(IIO_RESISTANCE, iio_chan_r++, chan);
+ } else {
+ st->iio_chan[iio_idx++] =
+ LTC2983_CHAN(IIO_TEMP, iio_chan_t++, chan);
+ st->iio_chan[iio_idx++] =
+ LTC2983_CHAN(IIO_RESISTANCE, iio_chan_r++, chan);
+ }
+ continue;
+ case LTC2983_SENSOR_LEAK_DETECTOR:
+ st->iio_chan[iio_idx++] =
+ LTC2983_CHAN(IIO_COVERAGE, iio_chan_c++, chan);
+ st->iio_chan[iio_idx++] =
+ LTC2983_CHAN(IIO_RESISTANCE, iio_chan_r++, chan);
+ continue;
+ case LTC2983_SENSOR_DIRECT_ADC:
+ chan_type = IIO_VOLTAGE;
+ iio_chan = &iio_chan_v;
+ break;
+ default:
+ chan_type = IIO_TEMP;
+ iio_chan = &iio_chan_t;
+ break;
+ }
+
+ /*
+ * add chan as the iio .address so that, we can directly
+ * reference the sensor given the iio_chan_spec
+ */
+ st->iio_chan[iio_idx++] = LTC2983_CHAN(chan_type, (*iio_chan)++,
+ chan);
+ }
+
+ return 0;
+}
+
+static const struct regmap_range ltc2983_reg_ranges[] = {
+ regmap_reg_range(LTC2983_STATUS_REG, LTC2983_STATUS_REG),
+ regmap_reg_range(LTC2983_TEMP_RES_START_REG, LTC2983_TEMP_RES_END_REG),
+ regmap_reg_range(ADT7604_RES_RES_START_REG, ADT7604_RES_RES_END_REG),
+ regmap_reg_range(LTC2983_EEPROM_KEY_REG, LTC2983_EEPROM_KEY_REG),
+ regmap_reg_range(LTC2983_EEPROM_READ_STATUS_REG,
+ LTC2983_EEPROM_READ_STATUS_REG),
+ regmap_reg_range(LTC2983_GLOBAL_CONFIG_REG, LTC2983_GLOBAL_CONFIG_REG),
+ regmap_reg_range(LTC2983_MULT_CHANNEL_START_REG,
+ LTC2983_MULT_CHANNEL_END_REG),
+ regmap_reg_range(LTC2986_EEPROM_STATUS_REG, LTC2986_EEPROM_STATUS_REG),
+ regmap_reg_range(LTC2983_MUX_CONFIG_REG, LTC2983_MUX_CONFIG_REG),
+ regmap_reg_range(LTC2983_CHAN_ASSIGN_START_REG,
+ LTC2983_CHAN_ASSIGN_END_REG),
+ regmap_reg_range(LTC2983_CUST_SENS_TBL_START_REG,
+ LTC2983_CUST_SENS_TBL_END_REG),
+};
+
+static const struct regmap_access_table ltc2983_reg_table = {
+ .yes_ranges = ltc2983_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(ltc2983_reg_ranges),
+};
+
+/*
+ * The reg_bits are actually 12 but the device needs the first *complete*
+ * byte for the command (R/W).
+ */
+static const struct regmap_config ltc2983_regmap_config = {
+ .reg_bits = 24,
+ .val_bits = 8,
+ .wr_table = &ltc2983_reg_table,
+ .rd_table = &ltc2983_reg_table,
+ .read_flag_mask = GENMASK(1, 0),
+ .write_flag_mask = BIT(1),
+};
+
+static const struct iio_info ltc2983_iio_info = {
+ .read_raw = ltc2983_read_raw,
+ .debugfs_reg_access = ltc2983_reg_access,
+};
+
+static int ltc2983_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct ltc2983_data *st;
+ struct iio_dev *indio_dev;
+ struct gpio_desc *gpio;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ st->info = spi_get_device_match_data(spi);
+ if (!st->info)
+ return -ENODEV;
+
+ st->regmap = devm_regmap_init_spi(spi, &ltc2983_regmap_config);
+ if (IS_ERR(st->regmap))
+ return dev_err_probe(dev, PTR_ERR(st->regmap),
+ "Failed to initialize regmap\n");
+
+ mutex_init(&st->lock);
+ init_completion(&st->completion);
+ st->spi = spi;
+ st->eeprom_key = cpu_to_be32(LTC2983_EEPROM_KEY);
+ spi_set_drvdata(spi, st);
+
+ ret = ltc2983_parse_fw(st);
+ if (ret)
+ return ret;
+
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return ret;
+
+ gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(gpio))
+ return PTR_ERR(gpio);
+
+ if (gpio) {
+ /* bring the device out of reset */
+ usleep_range(1000, 1200);
+ gpiod_set_value_cansleep(gpio, 0);
+ }
+
+ st->iio_chan = devm_kzalloc(dev,
+ st->iio_channels * sizeof(*st->iio_chan),
+ GFP_KERNEL);
+ if (!st->iio_chan)
+ return -ENOMEM;
+
+ ret = ltc2983_setup(st, true);
+ if (ret)
+ return ret;
+
+ ret = devm_request_irq(dev, spi->irq, ltc2983_irq_handler,
+ IRQF_TRIGGER_RISING, st->info->name, st);
+ if (ret)
+ return ret;
+
+ if (st->info->has_eeprom) {
+ ret = ltc2983_eeprom_cmd(st, LTC2983_EEPROM_WRITE_CMD,
+ LTC2983_EEPROM_WRITE_TIME_MS,
+ LTC2986_EEPROM_STATUS_REG,
+ LTC2983_EEPROM_STATUS_FAILURE_MASK);
+ if (ret)
+ return ret;
+ }
+
+ indio_dev->name = st->info->name;
+ indio_dev->num_channels = st->iio_channels;
+ indio_dev->channels = st->iio_chan;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &ltc2983_iio_info;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static int ltc2983_resume(struct device *dev)
+{
+ struct ltc2983_data *st = spi_get_drvdata(to_spi_device(dev));
+ int dummy;
+
+ /* dummy read to bring the device out of sleep */
+ regmap_read(st->regmap, LTC2983_STATUS_REG, &dummy);
+ /* we need to re-assign the channels */
+ return ltc2983_setup(st, false);
+}
+
+static int ltc2983_suspend(struct device *dev)
+{
+ struct ltc2983_data *st = spi_get_drvdata(to_spi_device(dev));
+
+ return regmap_write(st->regmap, LTC2983_STATUS_REG, LTC2983_SLEEP);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(ltc2983_pm_ops, ltc2983_suspend,
+ ltc2983_resume);
+
+static const struct ltc2983_chip_info adt7604_chip_info_data = {
+ .name = "adt7604",
+ .max_channels_nr = 20,
+ .has_eeprom = true,
+ .supported_sensors = ADT7604_SENSORS,
+};
+
+static const struct ltc2983_chip_info ltc2983_chip_info_data = {
+ .name = "ltc2983",
+ .max_channels_nr = 20,
+ .supported_sensors = LTC2983_COMMON_SENSORS,
+};
+
+static const struct ltc2983_chip_info ltc2984_chip_info_data = {
+ .name = "ltc2984",
+ .max_channels_nr = 20,
+ .has_eeprom = true,
+ .supported_sensors = LTC2983_COMMON_SENSORS,
+};
+
+static const struct ltc2983_chip_info ltc2986_chip_info_data = {
+ .name = "ltc2986",
+ .max_channels_nr = 10,
+ .has_eeprom = true,
+ .supported_sensors = LTC2983_COMMON_SENSORS | BIT_ULL(LTC2983_SENSOR_ACTIVE_TEMP),
+};
+
+static const struct ltc2983_chip_info ltm2985_chip_info_data = {
+ .name = "ltm2985",
+ .max_channels_nr = 10,
+ .has_eeprom = true,
+ .supported_sensors = LTC2983_COMMON_SENSORS | BIT_ULL(LTC2983_SENSOR_ACTIVE_TEMP),
+};
+
+static const struct spi_device_id ltc2983_id_table[] = {
+ { .name = "adt7604", .driver_data = (kernel_ulong_t)&adt7604_chip_info_data },
+ { .name = "ltc2983", .driver_data = (kernel_ulong_t)&ltc2983_chip_info_data },
+ { .name = "ltc2984", .driver_data = (kernel_ulong_t)&ltc2984_chip_info_data },
+ { .name = "ltc2986", .driver_data = (kernel_ulong_t)&ltc2986_chip_info_data },
+ { .name = "ltm2985", .driver_data = (kernel_ulong_t)&ltm2985_chip_info_data },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ltc2983_id_table);
+
+static const struct of_device_id ltc2983_of_match[] = {
+ { .compatible = "adi,adt7604", .data = &adt7604_chip_info_data },
+ { .compatible = "adi,ltc2983", .data = &ltc2983_chip_info_data },
+ { .compatible = "adi,ltc2984", .data = &ltc2984_chip_info_data },
+ { .compatible = "adi,ltc2986", .data = &ltc2986_chip_info_data },
+ { .compatible = "adi,ltm2985", .data = &ltm2985_chip_info_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ltc2983_of_match);
+
+static struct spi_driver ltc2983_driver = {
+ .driver = {
+ .name = "ltc2983",
+ .of_match_table = ltc2983_of_match,
+ .pm = pm_sleep_ptr(&ltc2983_pm_ops),
+ },
+ .probe = ltc2983_probe,
+ .id_table = ltc2983_id_table,
+};
+
+module_spi_driver(ltc2983_driver);
+
+MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("Analog Devices LTC2983 SPI Temperature sensors");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/temperature/max30208.c b/drivers/iio/temperature/max30208.c
new file mode 100644
index 000000000..6172abc6d
--- /dev/null
+++ b/drivers/iio/temperature/max30208.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) Rajat Khandelwal <rajat.khandelwal@linux.intel.com>
+ *
+ * Maxim MAX30208 digital temperature sensor with 0.1°C accuracy
+ * (7-bit I2C slave address (0x50 - 0x53))
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/types.h>
+
+#define MAX30208_STATUS 0x00
+#define MAX30208_STATUS_TEMP_RDY BIT(0)
+#define MAX30208_INT_ENABLE 0x01
+#define MAX30208_INT_ENABLE_TEMP_RDY BIT(0)
+
+#define MAX30208_FIFO_OVF_CNTR 0x06
+#define MAX30208_FIFO_DATA_CNTR 0x07
+#define MAX30208_FIFO_DATA 0x08
+
+#define MAX30208_FIFO_CONFIG 0x0a
+#define MAX30208_FIFO_CONFIG_RO BIT(1)
+
+#define MAX30208_SYSTEM_CTRL 0x0c
+#define MAX30208_SYSTEM_CTRL_RESET 0x01
+
+#define MAX30208_TEMP_SENSOR_SETUP 0x14
+#define MAX30208_TEMP_SENSOR_SETUP_CONV BIT(0)
+
+struct max30208_data {
+ struct i2c_client *client;
+ struct mutex lock; /* Lock to prevent concurrent reads of temperature readings */
+};
+
+static const struct iio_chan_spec max30208_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ },
+};
+
+/**
+ * max30208_request() - Request a reading
+ * @data: Struct comprising member elements of the device
+ *
+ * Requests a reading from the device and waits until the conversion is ready.
+ */
+static int max30208_request(struct max30208_data *data)
+{
+ /*
+ * Sensor can take up to 500 ms to respond so execute a total of
+ * 10 retries to give the device sufficient time.
+ */
+ int retries = 10;
+ u8 regval;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, MAX30208_TEMP_SENSOR_SETUP);
+ if (ret < 0)
+ return ret;
+
+ regval = ret | MAX30208_TEMP_SENSOR_SETUP_CONV;
+
+ ret = i2c_smbus_write_byte_data(data->client, MAX30208_TEMP_SENSOR_SETUP, regval);
+ if (ret)
+ return ret;
+
+ while (retries--) {
+ ret = i2c_smbus_read_byte_data(data->client, MAX30208_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (ret & MAX30208_STATUS_TEMP_RDY)
+ return 0;
+
+ msleep(50);
+ }
+ dev_err(&data->client->dev, "Temperature conversion failed\n");
+
+ return -ETIMEDOUT;
+}
+
+static int max30208_update_temp(struct max30208_data *data)
+{
+ u8 data_count;
+ int ret;
+
+ mutex_lock(&data->lock);
+
+ ret = max30208_request(data);
+ if (ret)
+ goto unlock;
+
+ ret = i2c_smbus_read_byte_data(data->client, MAX30208_FIFO_OVF_CNTR);
+ if (ret < 0)
+ goto unlock;
+ else if (!ret) {
+ ret = i2c_smbus_read_byte_data(data->client, MAX30208_FIFO_DATA_CNTR);
+ if (ret < 0)
+ goto unlock;
+
+ data_count = ret;
+ } else
+ data_count = 1;
+
+ while (data_count) {
+ ret = i2c_smbus_read_word_swapped(data->client, MAX30208_FIFO_DATA);
+ if (ret < 0)
+ goto unlock;
+
+ data_count--;
+ }
+
+unlock:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+/**
+ * max30208_config_setup() - Set up FIFO configuration register
+ * @data: Struct comprising member elements of the device
+ *
+ * Sets the rollover bit to '1' to enable overwriting FIFO during overflow.
+ */
+static int max30208_config_setup(struct max30208_data *data)
+{
+ u8 regval;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, MAX30208_FIFO_CONFIG);
+ if (ret < 0)
+ return ret;
+
+ regval = ret | MAX30208_FIFO_CONFIG_RO;
+
+ ret = i2c_smbus_write_byte_data(data->client, MAX30208_FIFO_CONFIG, regval);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int max30208_read(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct max30208_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = max30208_update_temp(data);
+ if (ret < 0)
+ return ret;
+
+ *val = sign_extend32(ret, 15);
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = 5;
+ return IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info max30208_info = {
+ .read_raw = max30208_read,
+};
+
+static int max30208_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ struct max30208_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = i2c;
+ mutex_init(&data->lock);
+
+ indio_dev->name = "max30208";
+ indio_dev->channels = max30208_channels;
+ indio_dev->num_channels = ARRAY_SIZE(max30208_channels);
+ indio_dev->info = &max30208_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = i2c_smbus_write_byte_data(data->client, MAX30208_SYSTEM_CTRL,
+ MAX30208_SYSTEM_CTRL_RESET);
+ if (ret) {
+ dev_err(dev, "Failure in performing reset\n");
+ return ret;
+ }
+
+ msleep(50);
+
+ ret = max30208_config_setup(data);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret) {
+ dev_err(dev, "Failed to register IIO device\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id max30208_id_table[] = {
+ { .name = "max30208" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max30208_id_table);
+
+static const struct acpi_device_id max30208_acpi_match[] = {
+ { "MAX30208" },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, max30208_acpi_match);
+
+static const struct of_device_id max30208_of_match[] = {
+ { .compatible = "maxim,max30208" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max30208_of_match);
+
+static struct i2c_driver max30208_driver = {
+ .driver = {
+ .name = "max30208",
+ .of_match_table = max30208_of_match,
+ .acpi_match_table = max30208_acpi_match,
+ },
+ .probe = max30208_probe,
+ .id_table = max30208_id_table,
+};
+module_i2c_driver(max30208_driver);
+
+MODULE_AUTHOR("Rajat Khandelwal <rajat.khandelwal@linux.intel.com>");
+MODULE_DESCRIPTION("Maxim MAX30208 digital temperature sensor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/temperature/max31856.c b/drivers/iio/temperature/max31856.c
new file mode 100644
index 000000000..061f6fe6d
--- /dev/null
+++ b/drivers/iio/temperature/max31856.c
@@ -0,0 +1,488 @@
+// SPDX-License-Identifier: GPL-2.0
+/* max31856.c
+ *
+ * Maxim MAX31856 thermocouple sensor driver
+ *
+ * Copyright (C) 2018-2019 Rockwell Collins
+ */
+
+#include <linux/ctype.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/property.h>
+#include <linux/spi/spi.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/util_macros.h>
+#include <linux/unaligned.h>
+#include <dt-bindings/iio/temperature/thermocouple.h>
+/*
+ * The MSB of the register value determines whether the following byte will
+ * be written or read. If it is 0, one or more byte reads will follow.
+ */
+#define MAX31856_RD_WR_BIT BIT(7)
+
+#define MAX31856_CR0_AUTOCONVERT BIT(7)
+#define MAX31856_CR0_1SHOT BIT(6)
+#define MAX31856_CR0_OCFAULT BIT(4)
+#define MAX31856_CR0_OCFAULT_MASK GENMASK(5, 4)
+#define MAX31856_CR0_FILTER_50HZ BIT(0)
+#define MAX31856_AVERAGING_MASK GENMASK(6, 4)
+#define MAX31856_AVERAGING_SHIFT 4
+#define MAX31856_TC_TYPE_MASK GENMASK(3, 0)
+#define MAX31856_FAULT_OVUV BIT(1)
+#define MAX31856_FAULT_OPEN BIT(0)
+
+/* The MAX31856 registers */
+#define MAX31856_CR0_REG 0x00
+#define MAX31856_CR1_REG 0x01
+#define MAX31856_MASK_REG 0x02
+#define MAX31856_CJHF_REG 0x03
+#define MAX31856_CJLF_REG 0x04
+#define MAX31856_LTHFTH_REG 0x05
+#define MAX31856_LTHFTL_REG 0x06
+#define MAX31856_LTLFTH_REG 0x07
+#define MAX31856_LTLFTL_REG 0x08
+#define MAX31856_CJTO_REG 0x09
+#define MAX31856_CJTH_REG 0x0A
+#define MAX31856_CJTL_REG 0x0B
+#define MAX31856_LTCBH_REG 0x0C
+#define MAX31856_LTCBM_REG 0x0D
+#define MAX31856_LTCBL_REG 0x0E
+#define MAX31856_SR_REG 0x0F
+
+static const struct iio_chan_spec max31856_channels[] = {
+ { /* Thermocouple Temperature */
+ .type = IIO_TEMP,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_THERMOCOUPLE_TYPE),
+ .info_mask_shared_by_type =
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO)
+ },
+ { /* Cold Junction Temperature */
+ .type = IIO_TEMP,
+ .channel2 = IIO_MOD_TEMP_AMBIENT,
+ .modified = 1,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_type =
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO)
+ },
+};
+
+struct max31856_data {
+ struct spi_device *spi;
+ u32 thermocouple_type;
+ bool filter_50hz;
+ int averaging;
+};
+
+static const char max31856_tc_types[] = {
+ 'B', 'E', 'J', 'K', 'N', 'R', 'S', 'T'
+};
+
+static int max31856_read(struct max31856_data *data, u8 reg,
+ u8 val[], unsigned int read_size)
+{
+ return spi_write_then_read(data->spi, &reg, 1, val, read_size);
+}
+
+static int max31856_write(struct max31856_data *data, u8 reg,
+ unsigned int val)
+{
+ u8 buf[2];
+
+ buf[0] = reg | (MAX31856_RD_WR_BIT);
+ buf[1] = val;
+
+ return spi_write(data->spi, buf, 2);
+}
+
+static int max31856_init(struct max31856_data *data)
+{
+ int ret;
+ u8 reg_cr0_val, reg_cr1_val;
+
+ /* Start by changing to Off mode before making changes as
+ * some settings are recommended to be set only when the device
+ * is off
+ */
+ ret = max31856_read(data, MAX31856_CR0_REG, &reg_cr0_val, 1);
+ if (ret)
+ return ret;
+
+ reg_cr0_val &= ~MAX31856_CR0_AUTOCONVERT;
+ ret = max31856_write(data, MAX31856_CR0_REG, reg_cr0_val);
+ if (ret)
+ return ret;
+
+ /* Set thermocouple type based on dts property */
+ ret = max31856_read(data, MAX31856_CR1_REG, &reg_cr1_val, 1);
+ if (ret)
+ return ret;
+
+ reg_cr1_val &= ~MAX31856_TC_TYPE_MASK;
+ reg_cr1_val |= data->thermocouple_type;
+
+ reg_cr1_val &= ~MAX31856_AVERAGING_MASK;
+ reg_cr1_val |= data->averaging << MAX31856_AVERAGING_SHIFT;
+
+ ret = max31856_write(data, MAX31856_CR1_REG, reg_cr1_val);
+ if (ret)
+ return ret;
+
+ /*
+ * Enable Open circuit fault detection
+ * Read datasheet for more information: Table 4.
+ * Value 01 means : Enabled (Once every 16 conversions)
+ */
+ reg_cr0_val &= ~MAX31856_CR0_OCFAULT_MASK;
+ reg_cr0_val |= MAX31856_CR0_OCFAULT;
+
+ /* Set Auto Conversion Mode */
+ reg_cr0_val &= ~MAX31856_CR0_1SHOT;
+ reg_cr0_val |= MAX31856_CR0_AUTOCONVERT;
+
+ if (data->filter_50hz)
+ reg_cr0_val |= MAX31856_CR0_FILTER_50HZ;
+ else
+ reg_cr0_val &= ~MAX31856_CR0_FILTER_50HZ;
+
+ return max31856_write(data, MAX31856_CR0_REG, reg_cr0_val);
+}
+
+static int max31856_thermocouple_read(struct max31856_data *data,
+ struct iio_chan_spec const *chan,
+ int *val)
+{
+ int ret, offset_cjto;
+ u8 reg_val[3];
+
+ switch (chan->channel2) {
+ case IIO_NO_MOD:
+ /*
+ * Multibyte Read
+ * MAX31856_LTCBH_REG, MAX31856_LTCBM_REG, MAX31856_LTCBL_REG
+ */
+ ret = max31856_read(data, MAX31856_LTCBH_REG, reg_val, 3);
+ if (ret)
+ return ret;
+ /* Skip last 5 dead bits of LTCBL */
+ *val = get_unaligned_be24(&reg_val[0]) >> 5;
+ /* Check 7th bit of LTCBH reg. value for sign*/
+ if (reg_val[0] & 0x80)
+ *val -= 0x80000;
+ break;
+
+ case IIO_MOD_TEMP_AMBIENT:
+ /*
+ * Multibyte Read
+ * MAX31856_CJTO_REG, MAX31856_CJTH_REG, MAX31856_CJTL_REG
+ */
+ ret = max31856_read(data, MAX31856_CJTO_REG, reg_val, 3);
+ if (ret)
+ return ret;
+ /* Get Cold Junction Temp. offset register value */
+ offset_cjto = reg_val[0];
+ /* Get CJTH and CJTL value and skip last 2 dead bits of CJTL */
+ *val = get_unaligned_be16(&reg_val[1]) >> 2;
+ /* As per datasheet add offset into CJTH and CJTL */
+ *val += offset_cjto;
+ /* Check 7th bit of CJTH reg. value for sign */
+ if (reg_val[1] & 0x80)
+ *val -= 0x4000;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ ret = max31856_read(data, MAX31856_SR_REG, reg_val, 1);
+ if (ret)
+ return ret;
+ /* Check for over/under voltage or open circuit fault */
+ if (reg_val[0] & (MAX31856_FAULT_OVUV | MAX31856_FAULT_OPEN))
+ return -EIO;
+
+ return ret;
+}
+
+static int max31856_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct max31856_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = max31856_thermocouple_read(data, chan, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->channel2) {
+ case IIO_MOD_TEMP_AMBIENT:
+ /* Cold junction Temp. Data resolution is 0.015625 */
+ *val = 15;
+ *val2 = 625000; /* 1000 * 0.015625 */
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ default:
+ /* Thermocouple Temp. Data resolution is 0.0078125 */
+ *val = 7;
+ *val2 = 812500; /* 1000 * 0.0078125) */
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+ break;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ *val = 1 << data->averaging;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_THERMOCOUPLE_TYPE:
+ *val = max31856_tc_types[data->thermocouple_type];
+ return IIO_VAL_CHAR;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int max31856_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_THERMOCOUPLE_TYPE:
+ return IIO_VAL_CHAR;
+ default:
+ return IIO_VAL_INT;
+ }
+}
+
+static int max31856_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct max31856_data *data = iio_priv(indio_dev);
+ int msb;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ if (val > 16 || val < 1)
+ return -EINVAL;
+ msb = fls(val) - 1;
+ /* Round up to next 2pow if needed */
+ if (BIT(msb) < val)
+ msb++;
+
+ data->averaging = msb;
+ max31856_init(data);
+ break;
+ case IIO_CHAN_INFO_THERMOCOUPLE_TYPE:
+ {
+ int tc_type = -1;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(max31856_tc_types); i++) {
+ if (max31856_tc_types[i] == toupper(val)) {
+ tc_type = i;
+ break;
+ }
+ }
+ if (tc_type < 0)
+ return -EINVAL;
+
+ data->thermocouple_type = tc_type;
+ max31856_init(data);
+ break;
+ }
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static ssize_t show_fault(struct device *dev, u8 faultbit, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct max31856_data *data = iio_priv(indio_dev);
+ u8 reg_val;
+ int ret;
+ bool fault;
+
+ ret = max31856_read(data, MAX31856_SR_REG, &reg_val, 1);
+ if (ret)
+ return ret;
+
+ fault = reg_val & faultbit;
+
+ return sysfs_emit(buf, "%d\n", fault);
+}
+
+static ssize_t show_fault_ovuv(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return show_fault(dev, MAX31856_FAULT_OVUV, buf);
+}
+
+static ssize_t show_fault_oc(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return show_fault(dev, MAX31856_FAULT_OPEN, buf);
+}
+
+static ssize_t show_filter(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct max31856_data *data = iio_priv(indio_dev);
+
+ return sysfs_emit(buf, "%d\n", data->filter_50hz ? 50 : 60);
+}
+
+static ssize_t set_filter(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct max31856_data *data = iio_priv(indio_dev);
+ unsigned int freq;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &freq);
+ if (ret)
+ return ret;
+
+ switch (freq) {
+ case 50:
+ data->filter_50hz = true;
+ break;
+ case 60:
+ data->filter_50hz = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ max31856_init(data);
+ return len;
+}
+
+static IIO_DEVICE_ATTR(fault_ovuv, 0444, show_fault_ovuv, NULL, 0);
+static IIO_DEVICE_ATTR(fault_oc, 0444, show_fault_oc, NULL, 0);
+static IIO_DEVICE_ATTR(in_temp_filter_notch_center_frequency, 0644,
+ show_filter, set_filter, 0);
+
+static struct attribute *max31856_attributes[] = {
+ &iio_dev_attr_fault_ovuv.dev_attr.attr,
+ &iio_dev_attr_fault_oc.dev_attr.attr,
+ &iio_dev_attr_in_temp_filter_notch_center_frequency.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group max31856_group = {
+ .attrs = max31856_attributes,
+};
+
+static const struct iio_info max31856_info = {
+ .read_raw = max31856_read_raw,
+ .write_raw = max31856_write_raw,
+ .write_raw_get_fmt = max31856_write_raw_get_fmt,
+ .attrs = &max31856_group,
+};
+
+static int max31856_probe(struct spi_device *spi)
+{
+ const struct spi_device_id *id = spi_get_device_id(spi);
+ struct iio_dev *indio_dev;
+ struct max31856_data *data;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->spi = spi;
+ data->filter_50hz = false;
+
+ spi_set_drvdata(spi, indio_dev);
+
+ indio_dev->info = &max31856_info;
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = max31856_channels;
+ indio_dev->num_channels = ARRAY_SIZE(max31856_channels);
+
+ ret = device_property_read_u32(&spi->dev, "thermocouple-type", &data->thermocouple_type);
+ if (ret) {
+ dev_info(&spi->dev,
+ "Could not read thermocouple type DT property, configuring as a K-Type\n");
+ data->thermocouple_type = THERMOCOUPLE_TYPE_K;
+ }
+
+ /*
+ * no need to translate values as the supported types
+ * have the same value as the #defines
+ */
+ switch (data->thermocouple_type) {
+ case THERMOCOUPLE_TYPE_B:
+ case THERMOCOUPLE_TYPE_E:
+ case THERMOCOUPLE_TYPE_J:
+ case THERMOCOUPLE_TYPE_K:
+ case THERMOCOUPLE_TYPE_N:
+ case THERMOCOUPLE_TYPE_R:
+ case THERMOCOUPLE_TYPE_S:
+ case THERMOCOUPLE_TYPE_T:
+ break;
+ default:
+ dev_err(&spi->dev,
+ "error: thermocouple-type %u not supported by max31856\n"
+ , data->thermocouple_type);
+ return -EINVAL;
+ }
+
+ ret = max31856_init(data);
+ if (ret) {
+ dev_err(&spi->dev, "error: Failed to configure max31856\n");
+ return ret;
+ }
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct spi_device_id max31856_id[] = {
+ { .name = "max31856" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, max31856_id);
+
+static const struct of_device_id max31856_of_match[] = {
+ { .compatible = "maxim,max31856" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max31856_of_match);
+
+static struct spi_driver max31856_driver = {
+ .driver = {
+ .name = "max31856",
+ .of_match_table = max31856_of_match,
+ },
+ .probe = max31856_probe,
+ .id_table = max31856_id,
+};
+module_spi_driver(max31856_driver);
+
+MODULE_AUTHOR("Paresh Chaudhary <paresh.chaudhary@rockwellcollins.com>");
+MODULE_AUTHOR("Patrick Havelange <patrick.havelange@essensium.com>");
+MODULE_DESCRIPTION("Maxim MAX31856 thermocouple sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/temperature/max31865.c b/drivers/iio/temperature/max31865.c
new file mode 100644
index 000000000..f9c93f95d
--- /dev/null
+++ b/drivers/iio/temperature/max31865.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) Linumiz 2021
+ *
+ * max31865.c - Maxim MAX31865 RTD-to-Digital Converter sensor driver
+ *
+ * Author: Navin Sankar Velliangiri <navin@linumiz.com>
+ */
+
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/property.h>
+#include <linux/spi/spi.h>
+#include <linux/unaligned.h>
+
+/*
+ * The MSB of the register value determines whether the following byte will
+ * be written or read. If it is 0, read will follow and if it is 1, write
+ * will follow.
+ */
+#define MAX31865_RD_WR_BIT BIT(7)
+
+#define MAX31865_CFG_VBIAS BIT(7)
+#define MAX31865_CFG_1SHOT BIT(5)
+#define MAX31865_3WIRE_RTD BIT(4)
+#define MAX31865_FAULT_STATUS_CLEAR BIT(1)
+#define MAX31865_FILTER_50HZ BIT(0)
+
+/* The MAX31865 registers */
+#define MAX31865_CFG_REG 0x00
+#define MAX31865_RTD_MSB 0x01
+#define MAX31865_FAULT_STATUS 0x07
+
+#define MAX31865_FAULT_OVUV BIT(2)
+
+static const char max31865_show_samp_freq[] = "50 60";
+
+static const struct iio_chan_spec max31865_channels[] = {
+ { /* RTD Temperature */
+ .type = IIO_TEMP,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)
+ },
+};
+
+struct max31865_data {
+ struct spi_device *spi;
+ struct mutex lock;
+ bool filter_50hz;
+ bool three_wire;
+ u8 buf[2] __aligned(IIO_DMA_MINALIGN);
+};
+
+static int max31865_read(struct max31865_data *data, u8 reg,
+ unsigned int read_size)
+{
+ return spi_write_then_read(data->spi, &reg, 1, data->buf, read_size);
+}
+
+static int max31865_write(struct max31865_data *data, size_t len)
+{
+ return spi_write(data->spi, data->buf, len);
+}
+
+static int enable_bias(struct max31865_data *data)
+{
+ u8 cfg;
+ int ret;
+
+ ret = max31865_read(data, MAX31865_CFG_REG, 1);
+ if (ret)
+ return ret;
+
+ cfg = data->buf[0];
+
+ data->buf[0] = MAX31865_CFG_REG | MAX31865_RD_WR_BIT;
+ data->buf[1] = cfg | MAX31865_CFG_VBIAS;
+
+ return max31865_write(data, 2);
+}
+
+static int disable_bias(struct max31865_data *data)
+{
+ u8 cfg;
+ int ret;
+
+ ret = max31865_read(data, MAX31865_CFG_REG, 1);
+ if (ret)
+ return ret;
+
+ cfg = data->buf[0];
+ cfg &= ~MAX31865_CFG_VBIAS;
+
+ data->buf[0] = MAX31865_CFG_REG | MAX31865_RD_WR_BIT;
+ data->buf[1] = cfg;
+
+ return max31865_write(data, 2);
+}
+
+static int max31865_rtd_read(struct max31865_data *data, int *val)
+{
+ u8 reg;
+ int ret;
+
+ /* Enable BIAS to start the conversion */
+ ret = enable_bias(data);
+ if (ret)
+ return ret;
+
+ /* wait 10.5ms before initiating the conversion */
+ msleep(11);
+
+ ret = max31865_read(data, MAX31865_CFG_REG, 1);
+ if (ret)
+ return ret;
+
+ reg = data->buf[0];
+ reg |= MAX31865_CFG_1SHOT | MAX31865_FAULT_STATUS_CLEAR;
+ data->buf[0] = MAX31865_CFG_REG | MAX31865_RD_WR_BIT;
+ data->buf[1] = reg;
+
+ ret = max31865_write(data, 2);
+ if (ret)
+ return ret;
+
+ if (data->filter_50hz) {
+ /* 50Hz filter mode requires 62.5ms to complete */
+ msleep(63);
+ } else {
+ /* 60Hz filter mode requires 52ms to complete */
+ msleep(52);
+ }
+
+ ret = max31865_read(data, MAX31865_RTD_MSB, 2);
+ if (ret)
+ return ret;
+
+ *val = get_unaligned_be16(&data->buf) >> 1;
+
+ return disable_bias(data);
+}
+
+static int max31865_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct max31865_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&data->lock);
+ ret = max31865_rtd_read(data, val);
+ mutex_unlock(&data->lock);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ /* Temp. Data resolution is 0.03125 degree centigrade */
+ *val = 31;
+ *val2 = 250000; /* 1000 * 0.03125 */
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int max31865_init(struct max31865_data *data)
+{
+ u8 cfg;
+ int ret;
+
+ ret = max31865_read(data, MAX31865_CFG_REG, 1);
+ if (ret)
+ return ret;
+
+ cfg = data->buf[0];
+
+ if (data->three_wire)
+ /* 3-wire RTD connection */
+ cfg |= MAX31865_3WIRE_RTD;
+
+ if (data->filter_50hz)
+ /* 50Hz noise rejection filter */
+ cfg |= MAX31865_FILTER_50HZ;
+
+ data->buf[0] = MAX31865_CFG_REG | MAX31865_RD_WR_BIT;
+ data->buf[1] = cfg;
+
+ return max31865_write(data, 2);
+}
+
+static ssize_t show_fault(struct device *dev, u8 faultbit, char *buf)
+{
+ int ret;
+ bool fault;
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct max31865_data *data = iio_priv(indio_dev);
+
+ ret = max31865_read(data, MAX31865_FAULT_STATUS, 1);
+ if (ret)
+ return ret;
+
+ fault = data->buf[0] & faultbit;
+
+ return sysfs_emit(buf, "%d\n", fault);
+}
+
+static ssize_t show_fault_ovuv(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return show_fault(dev, MAX31865_FAULT_OVUV, buf);
+}
+
+static ssize_t show_filter(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct max31865_data *data = iio_priv(indio_dev);
+
+ return sysfs_emit(buf, "%d\n", data->filter_50hz ? 50 : 60);
+}
+
+static ssize_t set_filter(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct max31865_data *data = iio_priv(indio_dev);
+ unsigned int freq;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &freq);
+ if (ret)
+ return ret;
+
+ switch (freq) {
+ case 50:
+ data->filter_50hz = true;
+ break;
+ case 60:
+ data->filter_50hz = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mutex_lock(&data->lock);
+ ret = max31865_init(data);
+ mutex_unlock(&data->lock);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(max31865_show_samp_freq);
+static IIO_DEVICE_ATTR(fault_ovuv, 0444, show_fault_ovuv, NULL, 0);
+static IIO_DEVICE_ATTR(in_filter_notch_center_frequency, 0644,
+ show_filter, set_filter, 0);
+
+static struct attribute *max31865_attributes[] = {
+ &iio_dev_attr_fault_ovuv.dev_attr.attr,
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ &iio_dev_attr_in_filter_notch_center_frequency.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group max31865_group = {
+ .attrs = max31865_attributes,
+};
+
+static const struct iio_info max31865_info = {
+ .read_raw = max31865_read_raw,
+ .attrs = &max31865_group,
+};
+
+static int max31865_probe(struct spi_device *spi)
+{
+ const struct spi_device_id *id = spi_get_device_id(spi);
+ struct iio_dev *indio_dev;
+ struct max31865_data *data;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->spi = spi;
+ data->filter_50hz = false;
+ mutex_init(&data->lock);
+
+ indio_dev->info = &max31865_info;
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = max31865_channels;
+ indio_dev->num_channels = ARRAY_SIZE(max31865_channels);
+
+ if (device_property_read_bool(&spi->dev, "maxim,3-wire")) {
+ /* select 3 wire */
+ data->three_wire = 1;
+ } else {
+ /* select 2 or 4 wire */
+ data->three_wire = 0;
+ }
+
+ ret = max31865_init(data);
+ if (ret) {
+ dev_err(&spi->dev, "error: Failed to configure max31865\n");
+ return ret;
+ }
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct spi_device_id max31865_id[] = {
+ { .name = "max31865" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, max31865_id);
+
+static const struct of_device_id max31865_of_match[] = {
+ { .compatible = "maxim,max31865" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max31865_of_match);
+
+static struct spi_driver max31865_driver = {
+ .driver = {
+ .name = "max31865",
+ .of_match_table = max31865_of_match,
+ },
+ .probe = max31865_probe,
+ .id_table = max31865_id,
+};
+module_spi_driver(max31865_driver);
+
+MODULE_AUTHOR("Navin Sankar Velliangiri <navin@linumiz.com>");
+MODULE_DESCRIPTION("Maxim MAX31865 RTD-to-Digital Converter sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/temperature/maxim_thermocouple.c b/drivers/iio/temperature/maxim_thermocouple.c
new file mode 100644
index 000000000..5f216e238
--- /dev/null
+++ b/drivers/iio/temperature/maxim_thermocouple.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * maxim_thermocouple.c - Support for Maxim thermocouple chips
+ *
+ * Copyright (C) 2016-2018 Matt Ranostay
+ * Author: <matt.ranostay@konsulko.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+#define MAXIM_THERMOCOUPLE_DRV_NAME "maxim_thermocouple"
+
+enum {
+ MAX6675,
+ MAX31855,
+ MAX31855K,
+ MAX31855J,
+ MAX31855N,
+ MAX31855S,
+ MAX31855T,
+ MAX31855E,
+ MAX31855R,
+};
+
+static const char maxim_tc_types[] = {
+ 'K', '?', 'K', 'J', 'N', 'S', 'T', 'E', 'R'
+};
+
+static const struct iio_chan_spec max6675_channels[] = {
+ { /* thermocouple temperature */
+ .type = IIO_TEMP,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_THERMOCOUPLE_TYPE),
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 13,
+ .storagebits = 16,
+ .shift = 3,
+ .endianness = IIO_BE,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct iio_chan_spec max31855_channels[] = {
+ { /* thermocouple temperature */
+ .type = IIO_TEMP,
+ .address = 2,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_THERMOCOUPLE_TYPE),
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 14,
+ .storagebits = 16,
+ .shift = 2,
+ .endianness = IIO_BE,
+ },
+ },
+ { /* cold junction temperature */
+ .type = IIO_TEMP,
+ .address = 0,
+ .channel2 = IIO_MOD_TEMP_AMBIENT,
+ .modified = 1,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = 1,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 12,
+ .storagebits = 16,
+ .shift = 4,
+ .endianness = IIO_BE,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(2),
+};
+
+static const unsigned long max31855_scan_masks[] = {0x3, 0};
+
+struct maxim_thermocouple_chip {
+ const struct iio_chan_spec *channels;
+ const unsigned long *scan_masks;
+ u8 num_channels;
+ u8 read_size;
+
+ /* bit-check for valid input */
+ u32 status_bit;
+};
+
+static const struct maxim_thermocouple_chip maxim_thermocouple_chips[] = {
+ [MAX6675] = {
+ .channels = max6675_channels,
+ .num_channels = ARRAY_SIZE(max6675_channels),
+ .read_size = 2,
+ .status_bit = BIT(2),
+ },
+ [MAX31855] = {
+ .channels = max31855_channels,
+ .num_channels = ARRAY_SIZE(max31855_channels),
+ .read_size = 4,
+ .scan_masks = max31855_scan_masks,
+ .status_bit = BIT(16),
+ },
+};
+
+struct maxim_thermocouple_data {
+ struct spi_device *spi;
+ const struct maxim_thermocouple_chip *chip;
+ char tc_type;
+ /* Buffer for reading up to 2 hardware channels. */
+ struct {
+ union {
+ __be16 raw16;
+ __be32 raw32;
+ __be16 raw[2];
+ };
+ aligned_s64 timestamp;
+ } buffer __aligned(IIO_DMA_MINALIGN);
+};
+
+static int maxim_thermocouple_read(struct maxim_thermocouple_data *data,
+ struct iio_chan_spec const *chan, int *val)
+{
+ unsigned int storage_bytes = data->chip->read_size;
+ unsigned int shift = chan->scan_type.shift + (chan->address * 8);
+ int ret;
+
+ switch (storage_bytes) {
+ case 2:
+ ret = spi_read(data->spi, &data->buffer.raw16, storage_bytes);
+ *val = be16_to_cpu(data->buffer.raw16);
+ break;
+ case 4:
+ ret = spi_read(data->spi, &data->buffer.raw32, storage_bytes);
+ *val = be32_to_cpu(data->buffer.raw32);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ /* check to be sure this is a valid reading */
+ if (*val & data->chip->status_bit)
+ return -EINVAL;
+
+ *val = sign_extend32(*val >> shift, chan->scan_type.realbits - 1);
+
+ return 0;
+}
+
+static irqreturn_t maxim_thermocouple_trigger_handler(int irq, void *private)
+{
+ struct iio_poll_func *pf = private;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct maxim_thermocouple_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = spi_read(data->spi, data->buffer.raw, data->chip->read_size);
+ if (!ret) {
+ iio_push_to_buffers_with_ts(indio_dev, &data->buffer,
+ sizeof(data->buffer),
+ iio_get_time_ns(indio_dev));
+ }
+
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int maxim_thermocouple_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct maxim_thermocouple_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = maxim_thermocouple_read(data, chan, val);
+ iio_device_release_direct(indio_dev);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->channel2) {
+ case IIO_MOD_TEMP_AMBIENT:
+ *val = 62;
+ *val2 = 500000; /* 1000 * 0.0625 */
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ *val = 250; /* 1000 * 0.25 */
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_THERMOCOUPLE_TYPE:
+ *val = data->tc_type;
+ return IIO_VAL_CHAR;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info maxim_thermocouple_info = {
+ .read_raw = maxim_thermocouple_read_raw,
+};
+
+static int maxim_thermocouple_probe(struct spi_device *spi)
+{
+ const struct spi_device_id *id = spi_get_device_id(spi);
+ struct iio_dev *indio_dev;
+ struct maxim_thermocouple_data *data;
+ const int chip_type = (id->driver_data == MAX6675) ? MAX6675 : MAX31855;
+ const struct maxim_thermocouple_chip *chip =
+ &maxim_thermocouple_chips[chip_type];
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ indio_dev->info = &maxim_thermocouple_info;
+ indio_dev->name = MAXIM_THERMOCOUPLE_DRV_NAME;
+ indio_dev->channels = chip->channels;
+ indio_dev->available_scan_masks = chip->scan_masks;
+ indio_dev->num_channels = chip->num_channels;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ data = iio_priv(indio_dev);
+ data->spi = spi;
+ data->chip = chip;
+ data->tc_type = maxim_tc_types[id->driver_data];
+
+ ret = devm_iio_triggered_buffer_setup(&spi->dev,
+ indio_dev, NULL,
+ maxim_thermocouple_trigger_handler, NULL);
+ if (ret)
+ return ret;
+
+ if (id->driver_data == MAX31855)
+ dev_warn(&spi->dev, "generic max31855 ID is deprecated\nplease use more specific part type");
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct spi_device_id maxim_thermocouple_id[] = {
+ { .name = "max6675", .driver_data = MAX6675 },
+ { .name = "max31855", .driver_data = MAX31855 },
+ { .name = "max31855k", .driver_data = MAX31855K },
+ { .name = "max31855j", .driver_data = MAX31855J },
+ { .name = "max31855n", .driver_data = MAX31855N },
+ { .name = "max31855s", .driver_data = MAX31855S },
+ { .name = "max31855t", .driver_data = MAX31855T },
+ { .name = "max31855e", .driver_data = MAX31855E },
+ { .name = "max31855r", .driver_data = MAX31855R },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, maxim_thermocouple_id);
+
+static const struct of_device_id maxim_thermocouple_of_match[] = {
+ { .compatible = "maxim,max6675" },
+ { .compatible = "maxim,max31855" },
+ { .compatible = "maxim,max31855k" },
+ { .compatible = "maxim,max31855j" },
+ { .compatible = "maxim,max31855n" },
+ { .compatible = "maxim,max31855s" },
+ { .compatible = "maxim,max31855t" },
+ { .compatible = "maxim,max31855e" },
+ { .compatible = "maxim,max31855r" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, maxim_thermocouple_of_match);
+
+static struct spi_driver maxim_thermocouple_driver = {
+ .driver = {
+ .name = MAXIM_THERMOCOUPLE_DRV_NAME,
+ .of_match_table = maxim_thermocouple_of_match,
+ },
+ .probe = maxim_thermocouple_probe,
+ .id_table = maxim_thermocouple_id,
+};
+module_spi_driver(maxim_thermocouple_driver);
+
+MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
+MODULE_DESCRIPTION("Maxim thermocouple sensors");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/temperature/mcp9600.c b/drivers/iio/temperature/mcp9600.c
new file mode 100644
index 000000000..b0ff0c7b4
--- /dev/null
+++ b/drivers/iio/temperature/mcp9600.c
@@ -0,0 +1,579 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * mcp9600.c - Support for Microchip MCP9600 thermocouple EMF converter
+ *
+ * Copyright (c) 2022 Andrew Hepp
+ * Author: <andrew.hepp@ahepp.dev>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+
+#include <dt-bindings/iio/temperature/thermocouple.h>
+
+/* MCP9600 registers */
+#define MCP9600_HOT_JUNCTION 0x00
+#define MCP9600_COLD_JUNCTION 0x02
+#define MCP9600_STATUS 0x04
+#define MCP9600_STATUS_ALERT(x) BIT(x)
+#define MCP9600_SENSOR_CFG 0x05
+#define MCP9600_SENSOR_TYPE_MASK GENMASK(6, 4)
+#define MCP9600_ALERT_CFG1 0x08
+#define MCP9600_ALERT_CFG(x) (MCP9600_ALERT_CFG1 + (x - 1))
+#define MCP9600_ALERT_CFG_ENABLE BIT(0)
+#define MCP9600_ALERT_CFG_ACTIVE_HIGH BIT(2)
+#define MCP9600_ALERT_CFG_FALLING BIT(3)
+#define MCP9600_ALERT_CFG_COLD_JUNCTION BIT(4)
+#define MCP9600_ALERT_HYSTERESIS1 0x0c
+#define MCP9600_ALERT_HYSTERESIS(x) (MCP9600_ALERT_HYSTERESIS1 + (x - 1))
+#define MCP9600_ALERT_LIMIT1 0x10
+#define MCP9600_ALERT_LIMIT(x) (MCP9600_ALERT_LIMIT1 + (x - 1))
+#define MCP9600_ALERT_LIMIT_MASK GENMASK(15, 2)
+#define MCP9600_DEVICE_ID 0x20
+
+/* MCP9600 device id value */
+#define MCP9600_DEVICE_ID_MCP9600 0x40
+#define MCP9600_DEVICE_ID_MCP9601 0x41
+
+#define MCP9600_ALERT_COUNT 4
+
+#define MCP9600_MIN_TEMP_HOT_JUNCTION_MICRO -200000000
+#define MCP9600_MAX_TEMP_HOT_JUNCTION_MICRO 1800000000
+
+#define MCP9600_MIN_TEMP_COLD_JUNCTION_MICRO -40000000
+#define MCP9600_MAX_TEMP_COLD_JUNCTION_MICRO 125000000
+
+enum mcp9600_alert {
+ MCP9600_ALERT1,
+ MCP9600_ALERT2,
+ MCP9600_ALERT3,
+ MCP9600_ALERT4
+};
+
+static const char * const mcp9600_alert_name[MCP9600_ALERT_COUNT] = {
+ [MCP9600_ALERT1] = "alert1",
+ [MCP9600_ALERT2] = "alert2",
+ [MCP9600_ALERT3] = "alert3",
+ [MCP9600_ALERT4] = "alert4",
+};
+
+/* Map between dt-bindings enum and the chip's type value */
+static const unsigned int mcp9600_type_map[] = {
+ [THERMOCOUPLE_TYPE_K] = 0,
+ [THERMOCOUPLE_TYPE_J] = 1,
+ [THERMOCOUPLE_TYPE_T] = 2,
+ [THERMOCOUPLE_TYPE_N] = 3,
+ [THERMOCOUPLE_TYPE_S] = 4,
+ [THERMOCOUPLE_TYPE_E] = 5,
+ [THERMOCOUPLE_TYPE_B] = 6,
+ [THERMOCOUPLE_TYPE_R] = 7,
+};
+
+/* Map thermocouple type to a char for iio info in sysfs */
+static const int mcp9600_tc_types[] = {
+ [THERMOCOUPLE_TYPE_K] = 'K',
+ [THERMOCOUPLE_TYPE_J] = 'J',
+ [THERMOCOUPLE_TYPE_T] = 'T',
+ [THERMOCOUPLE_TYPE_N] = 'N',
+ [THERMOCOUPLE_TYPE_S] = 'S',
+ [THERMOCOUPLE_TYPE_E] = 'E',
+ [THERMOCOUPLE_TYPE_B] = 'B',
+ [THERMOCOUPLE_TYPE_R] = 'R',
+};
+
+static const struct iio_event_spec mcp9600_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_HYSTERESIS),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_HYSTERESIS),
+ },
+};
+
+struct mcp_chip_info {
+ u8 chip_id;
+ const char *chip_name;
+};
+
+struct mcp9600_data {
+ struct i2c_client *client;
+ u32 thermocouple_type;
+};
+
+static int mcp9600_config(struct mcp9600_data *data)
+{
+ struct i2c_client *client = data->client;
+ int ret;
+ u8 cfg;
+
+ cfg = FIELD_PREP(MCP9600_SENSOR_TYPE_MASK,
+ mcp9600_type_map[data->thermocouple_type]);
+
+ ret = i2c_smbus_write_byte_data(client, MCP9600_SENSOR_CFG, cfg);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to set sensor configuration\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+#define MCP9600_CHANNELS(hj_num_ev, hj_ev_spec_off, cj_num_ev, cj_ev_spec_off) \
+ { \
+ { \
+ .type = IIO_TEMP, \
+ .address = MCP9600_HOT_JUNCTION, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_THERMOCOUPLE_TYPE) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .event_spec = &mcp9600_events[hj_ev_spec_off], \
+ .num_event_specs = hj_num_ev, \
+ }, \
+ { \
+ .type = IIO_TEMP, \
+ .address = MCP9600_COLD_JUNCTION, \
+ .channel2 = IIO_MOD_TEMP_AMBIENT, \
+ .modified = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .event_spec = &mcp9600_events[cj_ev_spec_off], \
+ .num_event_specs = cj_num_ev, \
+ }, \
+ }
+
+static const struct iio_chan_spec mcp9600_channels[][2] = {
+ MCP9600_CHANNELS(0, 0, 0, 0), /* Alerts: - - - - */
+ MCP9600_CHANNELS(1, 0, 0, 0), /* Alerts: 1 - - - */
+ MCP9600_CHANNELS(1, 1, 0, 0), /* Alerts: - 2 - - */
+ MCP9600_CHANNELS(2, 0, 0, 0), /* Alerts: 1 2 - - */
+ MCP9600_CHANNELS(0, 0, 1, 0), /* Alerts: - - 3 - */
+ MCP9600_CHANNELS(1, 0, 1, 0), /* Alerts: 1 - 3 - */
+ MCP9600_CHANNELS(1, 1, 1, 0), /* Alerts: - 2 3 - */
+ MCP9600_CHANNELS(2, 0, 1, 0), /* Alerts: 1 2 3 - */
+ MCP9600_CHANNELS(0, 0, 1, 1), /* Alerts: - - - 4 */
+ MCP9600_CHANNELS(1, 0, 1, 1), /* Alerts: 1 - - 4 */
+ MCP9600_CHANNELS(1, 1, 1, 1), /* Alerts: - 2 - 4 */
+ MCP9600_CHANNELS(2, 0, 1, 1), /* Alerts: 1 2 - 4 */
+ MCP9600_CHANNELS(0, 0, 2, 0), /* Alerts: - - 3 4 */
+ MCP9600_CHANNELS(1, 0, 2, 0), /* Alerts: 1 - 3 4 */
+ MCP9600_CHANNELS(1, 1, 2, 0), /* Alerts: - 2 3 4 */
+ MCP9600_CHANNELS(2, 0, 2, 0), /* Alerts: 1 2 3 4 */
+};
+
+static int mcp9600_read(struct mcp9600_data *data,
+ struct iio_chan_spec const *chan, int *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_word_swapped(data->client, chan->address);
+
+ if (ret < 0)
+ return ret;
+
+ *val = sign_extend32(ret, 15);
+
+ return 0;
+}
+
+static int mcp9600_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct mcp9600_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = mcp9600_read(data, chan, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 62;
+ *val2 = 500000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_THERMOCOUPLE_TYPE:
+ *val = mcp9600_tc_types[data->thermocouple_type];
+ return IIO_VAL_CHAR;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mcp9600_get_alert_index(int channel2, enum iio_event_direction dir)
+{
+ if (channel2 == IIO_MOD_TEMP_AMBIENT) {
+ if (dir == IIO_EV_DIR_RISING)
+ return MCP9600_ALERT3;
+ else
+ return MCP9600_ALERT4;
+ } else {
+ if (dir == IIO_EV_DIR_RISING)
+ return MCP9600_ALERT1;
+ else
+ return MCP9600_ALERT2;
+ }
+}
+
+static int mcp9600_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 mcp9600_data *data = iio_priv(indio_dev);
+ struct i2c_client *client = data->client;
+ int i, ret;
+
+ i = mcp9600_get_alert_index(chan->channel2, dir);
+ ret = i2c_smbus_read_byte_data(client, MCP9600_ALERT_CFG(i + 1));
+ if (ret < 0)
+ return ret;
+
+ return FIELD_GET(MCP9600_ALERT_CFG_ENABLE, ret);
+}
+
+static int mcp9600_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct mcp9600_data *data = iio_priv(indio_dev);
+ struct i2c_client *client = data->client;
+ int i, ret;
+
+ i = mcp9600_get_alert_index(chan->channel2, dir);
+ ret = i2c_smbus_read_byte_data(client, MCP9600_ALERT_CFG(i + 1));
+ if (ret < 0)
+ return ret;
+
+ if (state)
+ ret |= MCP9600_ALERT_CFG_ENABLE;
+ else
+ ret &= ~MCP9600_ALERT_CFG_ENABLE;
+
+ return i2c_smbus_write_byte_data(client, MCP9600_ALERT_CFG(i + 1), ret);
+}
+
+static int mcp9600_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 mcp9600_data *data = iio_priv(indio_dev);
+ struct i2c_client *client = data->client;
+ s32 ret;
+ int i;
+
+ i = mcp9600_get_alert_index(chan->channel2, dir);
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = i2c_smbus_read_word_swapped(client, MCP9600_ALERT_LIMIT(i + 1));
+ if (ret < 0)
+ return ret;
+ /*
+ * Temperature is stored in two’s complement format in
+ * bits(15:2), LSB is 0.25 degree celsius.
+ */
+ *val = FIELD_GET_SIGNED(MCP9600_ALERT_LIMIT_MASK, ret);
+ *val2 = 4;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_INFO_HYSTERESIS:
+ ret = i2c_smbus_read_byte_data(client, MCP9600_ALERT_HYSTERESIS(i + 1));
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mcp9600_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 mcp9600_data *data = iio_priv(indio_dev);
+ struct i2c_client *client = data->client;
+ int s_val, i;
+ s16 thresh;
+ u8 hyst;
+
+ i = mcp9600_get_alert_index(chan->channel2, dir);
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ /* Scale value to include decimal part into calculations */
+ s_val = (val < 0) ? ((val * 1000000) - val2) :
+ ((val * 1000000) + val2);
+ if (chan->channel2 == IIO_MOD_TEMP_AMBIENT) {
+ s_val = max(s_val, MCP9600_MIN_TEMP_COLD_JUNCTION_MICRO);
+ s_val = min(s_val, MCP9600_MAX_TEMP_COLD_JUNCTION_MICRO);
+ } else {
+ s_val = max(s_val, MCP9600_MIN_TEMP_HOT_JUNCTION_MICRO);
+ s_val = min(s_val, MCP9600_MAX_TEMP_HOT_JUNCTION_MICRO);
+ }
+
+ /*
+ * Shift length 4 bits = 2(15:2) + 2(0.25 LSB), temperature is
+ * stored in two’s complement format.
+ */
+ thresh = (s16)(s_val / (1000000 >> 4));
+ return i2c_smbus_write_word_swapped(client,
+ MCP9600_ALERT_LIMIT(i + 1),
+ thresh);
+ case IIO_EV_INFO_HYSTERESIS:
+ hyst = min(abs(val), 255);
+ return i2c_smbus_write_byte_data(client,
+ MCP9600_ALERT_HYSTERESIS(i + 1),
+ hyst);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info mcp9600_info = {
+ .read_raw = mcp9600_read_raw,
+ .read_event_config = mcp9600_read_event_config,
+ .write_event_config = mcp9600_write_event_config,
+ .read_event_value = mcp9600_read_thresh,
+ .write_event_value = mcp9600_write_thresh,
+};
+
+static irqreturn_t mcp9600_alert_handler(void *private,
+ enum mcp9600_alert alert,
+ enum iio_modifier mod,
+ enum iio_event_direction dir)
+{
+ struct iio_dev *indio_dev = private;
+ struct mcp9600_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, MCP9600_STATUS);
+ if (ret < 0)
+ return IRQ_HANDLED;
+
+ if (!(ret & MCP9600_STATUS_ALERT(alert)))
+ return IRQ_NONE;
+
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_TEMP, 0, mod, IIO_EV_TYPE_THRESH,
+ dir),
+ iio_get_time_ns(indio_dev));
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mcp9600_alert1_handler(int irq, void *private)
+{
+ return mcp9600_alert_handler(private, MCP9600_ALERT1, IIO_NO_MOD,
+ IIO_EV_DIR_RISING);
+}
+
+static irqreturn_t mcp9600_alert2_handler(int irq, void *private)
+{
+ return mcp9600_alert_handler(private, MCP9600_ALERT2, IIO_NO_MOD,
+ IIO_EV_DIR_FALLING);
+}
+
+static irqreturn_t mcp9600_alert3_handler(int irq, void *private)
+{
+ return mcp9600_alert_handler(private, MCP9600_ALERT3,
+ IIO_MOD_TEMP_AMBIENT, IIO_EV_DIR_RISING);
+}
+
+static irqreturn_t mcp9600_alert4_handler(int irq, void *private)
+{
+ return mcp9600_alert_handler(private, MCP9600_ALERT4,
+ IIO_MOD_TEMP_AMBIENT, IIO_EV_DIR_FALLING);
+}
+
+static irqreturn_t (*mcp9600_alert_handler_func[MCP9600_ALERT_COUNT]) (int, void *) = {
+ mcp9600_alert1_handler,
+ mcp9600_alert2_handler,
+ mcp9600_alert3_handler,
+ mcp9600_alert4_handler,
+};
+
+static int mcp9600_probe_alerts(struct iio_dev *indio_dev)
+{
+ struct mcp9600_data *data = iio_priv(indio_dev);
+ struct i2c_client *client = data->client;
+ struct device *dev = &client->dev;
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ unsigned int irq_type;
+ int ret, irq, i;
+ u8 val, ch_sel;
+
+ /*
+ * alert1: hot junction, rising temperature
+ * alert2: hot junction, falling temperature
+ * alert3: cold junction, rising temperature
+ * alert4: cold junction, falling temperature
+ */
+ ch_sel = 0;
+ for (i = 0; i < MCP9600_ALERT_COUNT; i++) {
+ irq = fwnode_irq_get_byname(fwnode, mcp9600_alert_name[i]);
+ if (irq <= 0)
+ continue;
+
+ val = 0;
+ irq_type = irq_get_trigger_type(irq);
+ if (irq_type == IRQ_TYPE_EDGE_RISING)
+ val |= MCP9600_ALERT_CFG_ACTIVE_HIGH;
+
+ if (i == MCP9600_ALERT2 || i == MCP9600_ALERT4)
+ val |= MCP9600_ALERT_CFG_FALLING;
+
+ if (i == MCP9600_ALERT3 || i == MCP9600_ALERT4)
+ val |= MCP9600_ALERT_CFG_COLD_JUNCTION;
+
+ ret = i2c_smbus_write_byte_data(client,
+ MCP9600_ALERT_CFG(i + 1),
+ val);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ mcp9600_alert_handler_func[i],
+ IRQF_ONESHOT, "mcp9600",
+ indio_dev);
+ if (ret)
+ return ret;
+
+ ch_sel |= BIT(i);
+ }
+
+ return ch_sel;
+}
+
+static int mcp9600_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ const struct mcp_chip_info *chip_info;
+ struct iio_dev *indio_dev;
+ struct mcp9600_data *data;
+ int ch_sel, dev_id, ret;
+
+ chip_info = i2c_get_match_data(client);
+ if (!chip_info)
+ return dev_err_probe(dev, -ENODEV,
+ "No chip-info found for device\n");
+
+ dev_id = i2c_smbus_read_byte_data(client, MCP9600_DEVICE_ID);
+ if (dev_id < 0)
+ return dev_err_probe(dev, dev_id, "Failed to read device ID\n");
+
+ switch (dev_id) {
+ case MCP9600_DEVICE_ID_MCP9600:
+ case MCP9600_DEVICE_ID_MCP9601:
+ if (dev_id != chip_info->chip_id)
+ dev_warn(dev,
+ "Expected id %02x, but device responded with %02x\n",
+ chip_info->chip_id, dev_id);
+ break;
+
+ default:
+ dev_warn(dev, "Unknown id %x, using %x\n", dev_id,
+ chip_info->chip_id);
+ }
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+
+ /* Accept type from dt with default of Type-K. */
+ data->thermocouple_type = THERMOCOUPLE_TYPE_K;
+ ret = device_property_read_u32(dev, "thermocouple-type",
+ &data->thermocouple_type);
+ if (ret && ret != -EINVAL)
+ return dev_err_probe(dev, ret,
+ "Error reading thermocouple-type property\n");
+
+ if (data->thermocouple_type >= ARRAY_SIZE(mcp9600_type_map))
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid thermocouple-type property %u.\n",
+ data->thermocouple_type);
+
+ /* Set initial config. */
+ ret = mcp9600_config(data);
+ if (ret)
+ return ret;
+
+ ch_sel = mcp9600_probe_alerts(indio_dev);
+ if (ch_sel < 0)
+ return ch_sel;
+
+ indio_dev->info = &mcp9600_info;
+ indio_dev->name = chip_info->chip_name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = mcp9600_channels[ch_sel];
+ indio_dev->num_channels = ARRAY_SIZE(mcp9600_channels[ch_sel]);
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct mcp_chip_info mcp9600_chip_info = {
+ .chip_id = MCP9600_DEVICE_ID_MCP9600,
+ .chip_name = "mcp9600",
+};
+
+static const struct mcp_chip_info mcp9601_chip_info = {
+ .chip_id = MCP9600_DEVICE_ID_MCP9601,
+ .chip_name = "mcp9601",
+};
+
+static const struct i2c_device_id mcp9600_id[] = {
+ { .name = "mcp9600", .driver_data = (kernel_ulong_t)&mcp9600_chip_info },
+ { .name = "mcp9601", .driver_data = (kernel_ulong_t)&mcp9601_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mcp9600_id);
+
+static const struct of_device_id mcp9600_of_match[] = {
+ { .compatible = "microchip,mcp9600", .data = &mcp9600_chip_info },
+ { .compatible = "microchip,mcp9601", .data = &mcp9601_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mcp9600_of_match);
+
+static struct i2c_driver mcp9600_driver = {
+ .driver = {
+ .name = "mcp9600",
+ .of_match_table = mcp9600_of_match,
+ },
+ .probe = mcp9600_probe,
+ .id_table = mcp9600_id
+};
+module_i2c_driver(mcp9600_driver);
+
+MODULE_AUTHOR("Dimitri Fedrau <dima.fedrau@gmail.com>");
+MODULE_AUTHOR("Andrew Hepp <andrew.hepp@ahepp.dev>");
+MODULE_DESCRIPTION("Microchip MCP9600 thermocouple EMF converter driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/temperature/mlx90614.c b/drivers/iio/temperature/mlx90614.c
new file mode 100644
index 000000000..27d6ab5f5
--- /dev/null
+++ b/drivers/iio/temperature/mlx90614.c
@@ -0,0 +1,782 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * mlx90614.c - Support for Melexis MLX90614/MLX90615 contactless IR temperature sensor
+ *
+ * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net>
+ * Copyright (c) 2015 Essensium NV
+ * Copyright (c) 2015 Melexis
+ *
+ * Driver for the Melexis MLX90614/MLX90615 I2C 16-bit IR thermopile sensor
+ *
+ * MLX90614 - 17-bit ADC + MLX90302 DSP
+ * MLX90615 - 16-bit ADC + MLX90325 DSP
+ *
+ * (7-bit I2C slave address 0x5a, 100KHz bus speed only!)
+ *
+ * To wake up from sleep mode, the SDA line must be held low while SCL is high
+ * for at least 33ms. This is achieved with an extra GPIO that can be connected
+ * directly to the SDA line. In normal operation, the GPIO is set as input and
+ * will not interfere in I2C communication. While the GPIO is driven low, the
+ * i2c adapter is locked since it cannot be used by other clients. The SCL line
+ * always has a pull-up so we do not need an extra GPIO to drive it high. If
+ * the "wakeup" GPIO is not given, power management will be disabled.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define MLX90614_OP_RAM 0x00
+#define MLX90614_OP_EEPROM 0x20
+#define MLX90614_OP_SLEEP 0xff
+
+#define MLX90615_OP_EEPROM 0x10
+#define MLX90615_OP_RAM 0x20
+#define MLX90615_OP_SLEEP 0xc6
+
+/* Control bits in configuration register */
+#define MLX90614_CONFIG_IIR_SHIFT 0 /* IIR coefficient */
+#define MLX90614_CONFIG_IIR_MASK (0x7 << MLX90614_CONFIG_IIR_SHIFT)
+#define MLX90614_CONFIG_DUAL_SHIFT 6 /* single (0) or dual (1) IR sensor */
+#define MLX90614_CONFIG_DUAL_MASK (1 << MLX90614_CONFIG_DUAL_SHIFT)
+#define MLX90614_CONFIG_FIR_SHIFT 8 /* FIR coefficient */
+#define MLX90614_CONFIG_FIR_MASK (0x7 << MLX90614_CONFIG_FIR_SHIFT)
+
+#define MLX90615_CONFIG_IIR_SHIFT 12 /* IIR coefficient */
+#define MLX90615_CONFIG_IIR_MASK (0x7 << MLX90615_CONFIG_IIR_SHIFT)
+
+/* Timings (in ms) */
+#define MLX90614_TIMING_EEPROM 20 /* time for EEPROM write/erase to complete */
+#define MLX90614_TIMING_WAKEUP 34 /* time to hold SDA low for wake-up */
+#define MLX90614_TIMING_STARTUP 250 /* time before first data after wake-up */
+
+#define MLX90615_TIMING_WAKEUP 22 /* time to hold SCL low for wake-up */
+
+#define MLX90614_AUTOSLEEP_DELAY 5000 /* default autosleep delay */
+
+/* Magic constants */
+#define MLX90614_CONST_OFFSET_DEC -13657 /* decimal part of the Kelvin offset */
+#define MLX90614_CONST_OFFSET_REM 500000 /* remainder of offset (273.15*50) */
+#define MLX90614_CONST_SCALE 20 /* Scale in milliKelvin (0.02 * 1000) */
+#define MLX90614_CONST_FIR 0x7 /* Fixed value for FIR part of low pass filter */
+
+struct mlx_chip_info {
+ /* EEPROM offsets with 16-bit data, MSB first */
+ /* emissivity correction coefficient */
+ u8 op_eeprom_emissivity;
+ u8 op_eeprom_config1;
+ /* RAM offsets with 16-bit data, MSB first */
+ /* ambient temperature */
+ u8 op_ram_ta;
+ /* object 1 temperature */
+ u8 op_ram_tobj1;
+ /* object 2 temperature */
+ u8 op_ram_tobj2;
+ u8 op_sleep;
+ /* support for two input channels (MLX90614 only) */
+ u8 dual_channel;
+ u8 wakeup_delay_ms;
+ u16 emissivity_max;
+ u16 fir_config_mask;
+ u16 iir_config_mask;
+ int iir_valid_offset;
+ u16 iir_values[8];
+ int iir_freqs[8][2];
+};
+
+struct mlx90614_data {
+ struct i2c_client *client;
+ struct mutex lock; /* for EEPROM access only */
+ struct gpio_desc *wakeup_gpio; /* NULL to disable sleep/wake-up */
+ const struct mlx_chip_info *chip_info; /* Chip hardware details */
+ unsigned long ready_timestamp; /* in jiffies */
+};
+
+/*
+ * Erase an address and write word.
+ * The mutex must be locked before calling.
+ */
+static s32 mlx90614_write_word(const struct i2c_client *client, u8 command,
+ u16 value)
+{
+ /*
+ * Note: The mlx90614 requires a PEC on writing but does not send us a
+ * valid PEC on reading. Hence, we cannot set I2C_CLIENT_PEC in
+ * i2c_client.flags. As a workaround, we use i2c_smbus_xfer here.
+ */
+ union i2c_smbus_data data;
+ s32 ret;
+
+ dev_dbg(&client->dev, "Writing 0x%x to address 0x%x", value, command);
+
+ data.word = 0x0000; /* erase command */
+ ret = i2c_smbus_xfer(client->adapter, client->addr,
+ client->flags | I2C_CLIENT_PEC,
+ I2C_SMBUS_WRITE, command,
+ I2C_SMBUS_WORD_DATA, &data);
+ if (ret < 0)
+ return ret;
+
+ msleep(MLX90614_TIMING_EEPROM);
+
+ data.word = value; /* actual write */
+ ret = i2c_smbus_xfer(client->adapter, client->addr,
+ client->flags | I2C_CLIENT_PEC,
+ I2C_SMBUS_WRITE, command,
+ I2C_SMBUS_WORD_DATA, &data);
+
+ msleep(MLX90614_TIMING_EEPROM);
+
+ return ret;
+}
+
+/*
+ * Find the IIR value inside iir_values array and return its position
+ * which is equivalent to the bit value in sensor register
+ */
+static inline s32 mlx90614_iir_search(const struct i2c_client *client,
+ int value)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct mlx90614_data *data = iio_priv(indio_dev);
+ const struct mlx_chip_info *chip_info = data->chip_info;
+ int i;
+ s32 ret;
+
+ for (i = chip_info->iir_valid_offset;
+ i < ARRAY_SIZE(chip_info->iir_values);
+ i++) {
+ if (value == chip_info->iir_values[i])
+ break;
+ }
+
+ if (i == ARRAY_SIZE(chip_info->iir_values))
+ return -EINVAL;
+
+ /*
+ * CONFIG register values must not be changed so
+ * we must read them before we actually write
+ * changes
+ */
+ ret = i2c_smbus_read_word_data(client, chip_info->op_eeprom_config1);
+ if (ret < 0)
+ return ret;
+
+ /* Modify FIR on parts which have configurable FIR filter */
+ if (chip_info->fir_config_mask) {
+ ret &= ~chip_info->fir_config_mask;
+ ret |= field_prep(chip_info->fir_config_mask, MLX90614_CONST_FIR);
+ }
+
+ ret &= ~chip_info->iir_config_mask;
+ ret |= field_prep(chip_info->iir_config_mask, i);
+
+ /* Write changed values */
+ ret = mlx90614_write_word(client, chip_info->op_eeprom_config1, ret);
+ return ret;
+}
+
+#ifdef CONFIG_PM
+/*
+ * If @startup is true, make sure MLX90614_TIMING_STARTUP ms have elapsed since
+ * the last wake-up. This is normally only needed to get a valid temperature
+ * reading. EEPROM access does not need such delay.
+ * Return 0 on success, <0 on error.
+ */
+static int mlx90614_power_get(struct mlx90614_data *data, bool startup)
+{
+ unsigned long now;
+ int ret;
+
+ if (!data->wakeup_gpio)
+ return 0;
+
+ ret = pm_runtime_resume_and_get(&data->client->dev);
+ if (ret < 0)
+ return ret;
+
+ if (startup) {
+ now = jiffies;
+ if (time_before(now, data->ready_timestamp) &&
+ msleep_interruptible(jiffies_to_msecs(
+ data->ready_timestamp - now)) != 0) {
+ pm_runtime_put_autosuspend(&data->client->dev);
+ return -EINTR;
+ }
+ }
+
+ return 0;
+}
+
+static void mlx90614_power_put(struct mlx90614_data *data)
+{
+ if (!data->wakeup_gpio)
+ return;
+
+ pm_runtime_put_autosuspend(&data->client->dev);
+}
+#else
+static inline int mlx90614_power_get(struct mlx90614_data *data, bool startup)
+{
+ return 0;
+}
+
+static inline void mlx90614_power_put(struct mlx90614_data *data)
+{
+}
+#endif
+
+static int mlx90614_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ struct mlx90614_data *data = iio_priv(indio_dev);
+ const struct mlx_chip_info *chip_info = data->chip_info;
+ u8 cmd, idx;
+ s32 ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW: /* 0.02K / LSB */
+ switch (channel->channel2) {
+ case IIO_MOD_TEMP_AMBIENT:
+ cmd = chip_info->op_ram_ta;
+ break;
+ case IIO_MOD_TEMP_OBJECT:
+ if (chip_info->dual_channel && channel->channel)
+ return -EINVAL;
+
+ switch (channel->channel) {
+ case 0:
+ cmd = chip_info->op_ram_tobj1;
+ break;
+ case 1:
+ cmd = chip_info->op_ram_tobj2;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = mlx90614_power_get(data, true);
+ if (ret < 0)
+ return ret;
+ ret = i2c_smbus_read_word_data(data->client, cmd);
+ mlx90614_power_put(data);
+
+ if (ret < 0)
+ return ret;
+
+ /* MSB is an error flag */
+ if (ret & 0x8000)
+ return -EIO;
+
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = MLX90614_CONST_OFFSET_DEC;
+ *val2 = MLX90614_CONST_OFFSET_REM;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SCALE:
+ *val = MLX90614_CONST_SCALE;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBEMISSIVITY: /* 1/emissivity_max / LSB */
+ ret = mlx90614_power_get(data, false);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&data->lock);
+ ret = i2c_smbus_read_word_data(data->client,
+ chip_info->op_eeprom_emissivity);
+ mutex_unlock(&data->lock);
+ mlx90614_power_put(data);
+
+ if (ret < 0)
+ return ret;
+
+ if (ret == chip_info->emissivity_max) {
+ *val = 1;
+ *val2 = 0;
+ } else {
+ *val = 0;
+ *val2 = ret * NSEC_PER_SEC / chip_info->emissivity_max;
+ }
+ return IIO_VAL_INT_PLUS_NANO;
+ /* IIR setting with FIR=1024 (MLX90614) or FIR=65536 (MLX90615) */
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ ret = mlx90614_power_get(data, false);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&data->lock);
+ ret = i2c_smbus_read_word_data(data->client,
+ chip_info->op_eeprom_config1);
+ mutex_unlock(&data->lock);
+ mlx90614_power_put(data);
+
+ if (ret < 0)
+ return ret;
+
+ idx = field_get(chip_info->iir_config_mask, ret) -
+ chip_info->iir_valid_offset;
+
+ *val = chip_info->iir_values[idx] / 100;
+ *val2 = (chip_info->iir_values[idx] % 100) * 10000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mlx90614_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int val,
+ int val2, long mask)
+{
+ struct mlx90614_data *data = iio_priv(indio_dev);
+ const struct mlx_chip_info *chip_info = data->chip_info;
+ s32 ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_CALIBEMISSIVITY: /* 1/emissivity_max / LSB */
+ if (val < 0 || val2 < 0 || val > 1 || (val == 1 && val2 != 0))
+ return -EINVAL;
+ val = val * chip_info->emissivity_max +
+ val2 * chip_info->emissivity_max / NSEC_PER_SEC;
+
+ ret = mlx90614_power_get(data, false);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&data->lock);
+ ret = mlx90614_write_word(data->client,
+ chip_info->op_eeprom_emissivity, val);
+ mutex_unlock(&data->lock);
+ mlx90614_power_put(data);
+
+ return ret;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: /* IIR Filter setting */
+ if (val < 0 || val2 < 0)
+ return -EINVAL;
+
+ ret = mlx90614_power_get(data, false);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&data->lock);
+ ret = mlx90614_iir_search(data->client,
+ val * 100 + val2 / 10000);
+ mutex_unlock(&data->lock);
+ mlx90614_power_put(data);
+
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mlx90614_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_CALIBEMISSIVITY:
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mlx90614_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct mlx90614_data *data = iio_priv(indio_dev);
+ const struct mlx_chip_info *chip_info = data->chip_info;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ *vals = (int *)chip_info->iir_freqs;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = 2 * (ARRAY_SIZE(chip_info->iir_freqs) -
+ chip_info->iir_valid_offset);
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_chan_spec mlx90614_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .modified = 1,
+ .channel2 = IIO_MOD_TEMP_AMBIENT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+ {
+ .type = IIO_TEMP,
+ .modified = 1,
+ .channel2 = IIO_MOD_TEMP_OBJECT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_CALIBEMISSIVITY) |
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY),
+ .info_mask_separate_available =
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+ {
+ .type = IIO_TEMP,
+ .indexed = 1,
+ .modified = 1,
+ .channel = 1,
+ .channel2 = IIO_MOD_TEMP_OBJECT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_CALIBEMISSIVITY) |
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY),
+ .info_mask_separate_available =
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+};
+
+static const struct iio_info mlx90614_info = {
+ .read_raw = mlx90614_read_raw,
+ .write_raw = mlx90614_write_raw,
+ .write_raw_get_fmt = mlx90614_write_raw_get_fmt,
+ .read_avail = mlx90614_read_avail,
+};
+
+#ifdef CONFIG_PM
+static int mlx90614_sleep(struct mlx90614_data *data)
+{
+ const struct mlx_chip_info *chip_info = data->chip_info;
+ s32 ret;
+
+ if (!data->wakeup_gpio) {
+ dev_dbg(&data->client->dev, "Sleep disabled");
+ return -ENOSYS;
+ }
+
+ dev_dbg(&data->client->dev, "Requesting sleep");
+
+ mutex_lock(&data->lock);
+ ret = i2c_smbus_xfer(data->client->adapter, data->client->addr,
+ data->client->flags | I2C_CLIENT_PEC,
+ I2C_SMBUS_WRITE, chip_info->op_sleep,
+ I2C_SMBUS_BYTE, NULL);
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static int mlx90614_wakeup(struct mlx90614_data *data)
+{
+ const struct mlx_chip_info *chip_info = data->chip_info;
+
+ if (!data->wakeup_gpio) {
+ dev_dbg(&data->client->dev, "Wake-up disabled");
+ return -ENOSYS;
+ }
+
+ dev_dbg(&data->client->dev, "Requesting wake-up");
+
+ i2c_lock_bus(data->client->adapter, I2C_LOCK_ROOT_ADAPTER);
+ gpiod_direction_output(data->wakeup_gpio, 0);
+ msleep(chip_info->wakeup_delay_ms);
+ gpiod_direction_input(data->wakeup_gpio);
+ i2c_unlock_bus(data->client->adapter, I2C_LOCK_ROOT_ADAPTER);
+
+ data->ready_timestamp = jiffies +
+ msecs_to_jiffies(MLX90614_TIMING_STARTUP);
+
+ /*
+ * Quirk: the i2c controller may get confused right after the
+ * wake-up signal has been sent. As a workaround, do a dummy read.
+ * If the read fails, the controller will probably be reset so that
+ * further reads will work.
+ */
+ i2c_smbus_read_word_data(data->client, chip_info->op_eeprom_config1);
+
+ return 0;
+}
+
+/* Return wake-up GPIO or NULL if sleep functionality should be disabled. */
+static struct gpio_desc *mlx90614_probe_wakeup(struct i2c_client *client)
+{
+ struct gpio_desc *gpio;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WRITE_BYTE)) {
+ dev_info(&client->dev,
+ "i2c adapter does not support SMBUS_WRITE_BYTE, sleep disabled");
+ return NULL;
+ }
+
+ gpio = devm_gpiod_get_optional(&client->dev, "wakeup", GPIOD_IN);
+
+ if (IS_ERR(gpio)) {
+ dev_warn(&client->dev,
+ "gpio acquisition failed with error %ld, sleep disabled",
+ PTR_ERR(gpio));
+ return NULL;
+ } else if (!gpio) {
+ dev_info(&client->dev,
+ "wakeup-gpio not found, sleep disabled");
+ }
+
+ return gpio;
+}
+#else
+static inline int mlx90614_sleep(struct mlx90614_data *data)
+{
+ return -ENOSYS;
+}
+static inline int mlx90614_wakeup(struct mlx90614_data *data)
+{
+ return -ENOSYS;
+}
+static inline struct gpio_desc *mlx90614_probe_wakeup(struct i2c_client *client)
+{
+ return NULL;
+}
+#endif
+
+/* Return 0 for single sensor, 1 for dual sensor, <0 on error. */
+static int mlx90614_probe_num_ir_sensors(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct mlx90614_data *data = iio_priv(indio_dev);
+ const struct mlx_chip_info *chip_info = data->chip_info;
+ s32 ret;
+
+ if (chip_info->dual_channel)
+ return 0;
+
+ ret = i2c_smbus_read_word_data(client, chip_info->op_eeprom_config1);
+
+ if (ret < 0)
+ return ret;
+
+ return (ret & MLX90614_CONFIG_DUAL_MASK) ? 1 : 0;
+}
+
+static int mlx90614_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct iio_dev *indio_dev;
+ struct mlx90614_data *data;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA))
+ return -EOPNOTSUPP;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+ mutex_init(&data->lock);
+ data->wakeup_gpio = mlx90614_probe_wakeup(client);
+ data->chip_info = i2c_get_match_data(client);
+
+ mlx90614_wakeup(data);
+
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &mlx90614_info;
+
+ ret = mlx90614_probe_num_ir_sensors(client);
+ switch (ret) {
+ case 0:
+ dev_dbg(&client->dev, "Found single sensor");
+ indio_dev->channels = mlx90614_channels;
+ indio_dev->num_channels = 2;
+ break;
+ case 1:
+ dev_dbg(&client->dev, "Found dual sensor");
+ indio_dev->channels = mlx90614_channels;
+ indio_dev->num_channels = 3;
+ break;
+ default:
+ return ret;
+ }
+
+ if (data->wakeup_gpio) {
+ pm_runtime_set_autosuspend_delay(&client->dev,
+ MLX90614_AUTOSLEEP_DELAY);
+ pm_runtime_use_autosuspend(&client->dev);
+ pm_runtime_set_active(&client->dev);
+ pm_runtime_enable(&client->dev);
+ }
+
+ return iio_device_register(indio_dev);
+}
+
+static void mlx90614_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct mlx90614_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ if (data->wakeup_gpio) {
+ pm_runtime_disable(&client->dev);
+ if (!pm_runtime_status_suspended(&client->dev))
+ mlx90614_sleep(data);
+ pm_runtime_set_suspended(&client->dev);
+ }
+}
+
+static const struct mlx_chip_info mlx90614_chip_info = {
+ .op_eeprom_emissivity = MLX90614_OP_EEPROM | 0x04,
+ .op_eeprom_config1 = MLX90614_OP_EEPROM | 0x05,
+ .op_ram_ta = MLX90614_OP_RAM | 0x06,
+ .op_ram_tobj1 = MLX90614_OP_RAM | 0x07,
+ .op_ram_tobj2 = MLX90614_OP_RAM | 0x08,
+ .op_sleep = MLX90614_OP_SLEEP,
+ .dual_channel = true,
+ .wakeup_delay_ms = MLX90614_TIMING_WAKEUP,
+ .emissivity_max = 65535,
+ .fir_config_mask = MLX90614_CONFIG_FIR_MASK,
+ .iir_config_mask = MLX90614_CONFIG_IIR_MASK,
+ .iir_valid_offset = 0,
+ .iir_values = { 77, 31, 20, 15, 723, 153, 110, 86 },
+ .iir_freqs = {
+ { 0, 150000 }, /* 13% ~= 0.15 Hz */
+ { 0, 200000 }, /* 17% ~= 0.20 Hz */
+ { 0, 310000 }, /* 25% ~= 0.31 Hz */
+ { 0, 770000 }, /* 50% ~= 0.77 Hz */
+ { 0, 860000 }, /* 57% ~= 0.86 Hz */
+ { 1, 100000 }, /* 67% ~= 1.10 Hz */
+ { 1, 530000 }, /* 80% ~= 1.53 Hz */
+ { 7, 230000 } /* 100% ~= 7.23 Hz */
+ },
+};
+
+static const struct mlx_chip_info mlx90615_chip_info = {
+ .op_eeprom_emissivity = MLX90615_OP_EEPROM | 0x03,
+ .op_eeprom_config1 = MLX90615_OP_EEPROM | 0x02,
+ .op_ram_ta = MLX90615_OP_RAM | 0x06,
+ .op_ram_tobj1 = MLX90615_OP_RAM | 0x07,
+ .op_ram_tobj2 = MLX90615_OP_RAM | 0x08,
+ .op_sleep = MLX90615_OP_SLEEP,
+ .dual_channel = false,
+ .wakeup_delay_ms = MLX90615_TIMING_WAKEUP,
+ .emissivity_max = 16383,
+ .fir_config_mask = 0, /* MLX90615 FIR is fixed */
+ .iir_config_mask = MLX90615_CONFIG_IIR_MASK,
+ /* IIR value 0 is FORBIDDEN COMBINATION on MLX90615 */
+ .iir_valid_offset = 1,
+ .iir_values = { 500, 50, 30, 20, 15, 13, 10 },
+ .iir_freqs = {
+ { 0, 100000 }, /* 14% ~= 0.10 Hz */
+ { 0, 130000 }, /* 17% ~= 0.13 Hz */
+ { 0, 150000 }, /* 20% ~= 0.15 Hz */
+ { 0, 200000 }, /* 25% ~= 0.20 Hz */
+ { 0, 300000 }, /* 33% ~= 0.30 Hz */
+ { 0, 500000 }, /* 50% ~= 0.50 Hz */
+ { 5, 000000 }, /* 100% ~= 5.00 Hz */
+ },
+};
+
+static const struct i2c_device_id mlx90614_id[] = {
+ { .name = "mlx90614", .driver_data = (kernel_ulong_t)&mlx90614_chip_info },
+ { .name = "mlx90615", .driver_data = (kernel_ulong_t)&mlx90615_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mlx90614_id);
+
+static const struct of_device_id mlx90614_of_match[] = {
+ { .compatible = "melexis,mlx90614", .data = &mlx90614_chip_info },
+ { .compatible = "melexis,mlx90615", .data = &mlx90615_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mlx90614_of_match);
+
+static int mlx90614_pm_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mlx90614_data *data = iio_priv(indio_dev);
+
+ if (data->wakeup_gpio && pm_runtime_active(dev))
+ return mlx90614_sleep(data);
+
+ return 0;
+}
+
+static int mlx90614_pm_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mlx90614_data *data = iio_priv(indio_dev);
+ int err;
+
+ if (data->wakeup_gpio) {
+ err = mlx90614_wakeup(data);
+ if (err < 0)
+ return err;
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ }
+
+ return 0;
+}
+
+static int mlx90614_pm_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mlx90614_data *data = iio_priv(indio_dev);
+
+ return mlx90614_sleep(data);
+}
+
+static int mlx90614_pm_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mlx90614_data *data = iio_priv(indio_dev);
+
+ return mlx90614_wakeup(data);
+}
+
+static const struct dev_pm_ops mlx90614_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(mlx90614_pm_suspend, mlx90614_pm_resume)
+ RUNTIME_PM_OPS(mlx90614_pm_runtime_suspend,
+ mlx90614_pm_runtime_resume, NULL)
+};
+
+static struct i2c_driver mlx90614_driver = {
+ .driver = {
+ .name = "mlx90614",
+ .of_match_table = mlx90614_of_match,
+ .pm = pm_ptr(&mlx90614_pm_ops),
+ },
+ .probe = mlx90614_probe,
+ .remove = mlx90614_remove,
+ .id_table = mlx90614_id,
+};
+module_i2c_driver(mlx90614_driver);
+
+MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
+MODULE_AUTHOR("Vianney le Clément de Saint-Marcq <vianney.leclement@essensium.com>");
+MODULE_AUTHOR("Crt Mori <cmo@melexis.com>");
+MODULE_DESCRIPTION("Melexis MLX90614 contactless IR temperature sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/temperature/mlx90632.c b/drivers/iio/temperature/mlx90632.c
new file mode 100644
index 000000000..2fde48f33
--- /dev/null
+++ b/drivers/iio/temperature/mlx90632.c
@@ -0,0 +1,1343 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mlx90632.c - Melexis MLX90632 contactless IR temperature sensor
+ *
+ * Copyright (c) 2017 Melexis <cmo@melexis.com>
+ *
+ * Driver for the Melexis MLX90632 I2C 16-bit IR thermopile sensor
+ */
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/math64.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+/* Memory sections addresses */
+#define MLX90632_ADDR_RAM 0x4000 /* Start address of ram */
+#define MLX90632_ADDR_EEPROM 0x2480 /* Start address of user eeprom */
+
+/* EEPROM addresses - used at startup */
+#define MLX90632_EE_CTRL 0x24d4 /* Control register initial value */
+#define MLX90632_EE_I2C_ADDR 0x24d5 /* I2C address register initial value */
+#define MLX90632_EE_VERSION 0x240b /* EEPROM version reg address */
+#define MLX90632_EE_P_R 0x240c /* P_R calibration register 32bit */
+#define MLX90632_EE_P_G 0x240e /* P_G calibration register 32bit */
+#define MLX90632_EE_P_T 0x2410 /* P_T calibration register 32bit */
+#define MLX90632_EE_P_O 0x2412 /* P_O calibration register 32bit */
+#define MLX90632_EE_Aa 0x2414 /* Aa calibration register 32bit */
+#define MLX90632_EE_Ab 0x2416 /* Ab calibration register 32bit */
+#define MLX90632_EE_Ba 0x2418 /* Ba calibration register 32bit */
+#define MLX90632_EE_Bb 0x241a /* Bb calibration register 32bit */
+#define MLX90632_EE_Ca 0x241c /* Ca calibration register 32bit */
+#define MLX90632_EE_Cb 0x241e /* Cb calibration register 32bit */
+#define MLX90632_EE_Da 0x2420 /* Da calibration register 32bit */
+#define MLX90632_EE_Db 0x2422 /* Db calibration register 32bit */
+#define MLX90632_EE_Ea 0x2424 /* Ea calibration register 32bit */
+#define MLX90632_EE_Eb 0x2426 /* Eb calibration register 32bit */
+#define MLX90632_EE_Fa 0x2428 /* Fa calibration register 32bit */
+#define MLX90632_EE_Fb 0x242a /* Fb calibration register 32bit */
+#define MLX90632_EE_Ga 0x242c /* Ga calibration register 32bit */
+
+#define MLX90632_EE_Gb 0x242e /* Gb calibration register 16bit */
+#define MLX90632_EE_Ka 0x242f /* Ka calibration register 16bit */
+
+#define MLX90632_EE_Ha 0x2481 /* Ha customer calib value reg 16bit */
+#define MLX90632_EE_Hb 0x2482 /* Hb customer calib value reg 16bit */
+
+#define MLX90632_EE_MEDICAL_MEAS1 0x24E1 /* Medical measurement 1 16bit */
+#define MLX90632_EE_MEDICAL_MEAS2 0x24E2 /* Medical measurement 2 16bit */
+#define MLX90632_EE_EXTENDED_MEAS1 0x24F1 /* Extended measurement 1 16bit */
+#define MLX90632_EE_EXTENDED_MEAS2 0x24F2 /* Extended measurement 2 16bit */
+#define MLX90632_EE_EXTENDED_MEAS3 0x24F3 /* Extended measurement 3 16bit */
+
+/* Register addresses - volatile */
+#define MLX90632_REG_I2C_ADDR 0x3000 /* Chip I2C address register */
+
+/* Control register address - volatile */
+#define MLX90632_REG_CONTROL 0x3001 /* Control Register address */
+#define MLX90632_CFG_PWR_MASK GENMASK(2, 1) /* PowerMode Mask */
+#define MLX90632_CFG_MTYP_MASK GENMASK(8, 4) /* Meas select Mask */
+#define MLX90632_CFG_SOB_MASK BIT(11)
+
+/* PowerModes statuses */
+#define MLX90632_PWR_STATUS(ctrl_val) (ctrl_val << 1)
+#define MLX90632_PWR_STATUS_HALT MLX90632_PWR_STATUS(0) /* hold */
+#define MLX90632_PWR_STATUS_SLEEP_STEP MLX90632_PWR_STATUS(1) /* sleep step */
+#define MLX90632_PWR_STATUS_STEP MLX90632_PWR_STATUS(2) /* step */
+#define MLX90632_PWR_STATUS_CONTINUOUS MLX90632_PWR_STATUS(3) /* continuous */
+
+#define MLX90632_EE_RR GENMASK(10, 8) /* Only Refresh Rate bits */
+#define MLX90632_REFRESH_RATE(ee_val) FIELD_GET(MLX90632_EE_RR, ee_val)
+ /* Extract Refresh Rate from ee register */
+#define MLX90632_REFRESH_RATE_STATUS(refresh_rate) (refresh_rate << 8)
+
+/* Measurement types */
+#define MLX90632_MTYP_MEDICAL 0
+#define MLX90632_MTYP_EXTENDED 17
+
+/* Measurement type select*/
+#define MLX90632_MTYP_STATUS(ctrl_val) (ctrl_val << 4)
+#define MLX90632_MTYP_STATUS_MEDICAL MLX90632_MTYP_STATUS(MLX90632_MTYP_MEDICAL)
+#define MLX90632_MTYP_STATUS_EXTENDED MLX90632_MTYP_STATUS(MLX90632_MTYP_EXTENDED)
+
+/* I2C command register - volatile */
+#define MLX90632_REG_I2C_CMD 0x3005 /* I2C command Register address */
+
+/* Device status register - volatile */
+#define MLX90632_REG_STATUS 0x3fff /* Device status register */
+#define MLX90632_STAT_BUSY BIT(10) /* Device busy indicator */
+#define MLX90632_STAT_EE_BUSY BIT(9) /* EEPROM busy indicator */
+#define MLX90632_STAT_BRST BIT(8) /* Brown out reset indicator */
+#define MLX90632_STAT_CYCLE_POS GENMASK(6, 2) /* Data position */
+#define MLX90632_STAT_DATA_RDY BIT(0) /* Data ready indicator */
+
+/* RAM_MEAS address-es for each channel */
+#define MLX90632_RAM_1(meas_num) (MLX90632_ADDR_RAM + 3 * meas_num)
+#define MLX90632_RAM_2(meas_num) (MLX90632_ADDR_RAM + 3 * meas_num + 1)
+#define MLX90632_RAM_3(meas_num) (MLX90632_ADDR_RAM + 3 * meas_num + 2)
+
+/* Name important RAM_MEAS channels */
+#define MLX90632_RAM_DSP5_EXTENDED_AMBIENT_1 MLX90632_RAM_3(17)
+#define MLX90632_RAM_DSP5_EXTENDED_AMBIENT_2 MLX90632_RAM_3(18)
+#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_1 MLX90632_RAM_1(17)
+#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_2 MLX90632_RAM_2(17)
+#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_3 MLX90632_RAM_1(18)
+#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_4 MLX90632_RAM_2(18)
+#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_5 MLX90632_RAM_1(19)
+#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_6 MLX90632_RAM_2(19)
+
+/* Magic constants */
+#define MLX90632_ID_MEDICAL 0x0105 /* EEPROM DSPv5 Medical device id */
+#define MLX90632_ID_CONSUMER 0x0205 /* EEPROM DSPv5 Consumer device id */
+#define MLX90632_ID_EXTENDED 0x0505 /* EEPROM DSPv5 Extended range device id */
+#define MLX90632_ID_MASK GENMASK(14, 0) /* DSP version and device ID in EE_VERSION */
+#define MLX90632_DSP_VERSION 5 /* DSP version */
+#define MLX90632_DSP_MASK GENMASK(7, 0) /* DSP version in EE_VERSION */
+#define MLX90632_RESET_CMD 0x0006 /* Reset sensor (address or global) */
+#define MLX90632_REF_12 12LL /* ResCtrlRef value of Ch 1 or Ch 2 */
+#define MLX90632_REF_3 12LL /* ResCtrlRef value of Channel 3 */
+#define MLX90632_MAX_MEAS_NUM 31 /* Maximum measurements in list */
+#define MLX90632_SLEEP_DELAY_MS 6000 /* Autosleep delay */
+#define MLX90632_EXTENDED_LIMIT 27000 /* Extended mode raw value limit */
+#define MLX90632_MEAS_MAX_TIME 2000 /* Max measurement time in ms for the lowest refresh rate */
+
+/**
+ * struct mlx90632_data - private data for the MLX90632 device
+ * @client: I2C client of the device
+ * @lock: Internal mutex for multiple reads for single measurement
+ * @regmap: Regmap of the device
+ * @emissivity: Object emissivity from 0 to 1000 where 1000 = 1.
+ * @mtyp: Measurement type physical sensor configuration for extended range
+ * calculations
+ * @object_ambient_temperature: Ambient temperature at object (might differ of
+ * the ambient temperature of sensor.
+ * @regulator: Regulator of the device
+ * @powerstatus: Current POWER status of the device
+ * @interaction_ts: Timestamp of the last temperature read that is used
+ * for power management in jiffies
+ */
+struct mlx90632_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ struct regmap *regmap;
+ u16 emissivity;
+ u8 mtyp;
+ u32 object_ambient_temperature;
+ struct regulator *regulator;
+ int powerstatus;
+ unsigned long interaction_ts;
+};
+
+static const struct regmap_range mlx90632_volatile_reg_range[] = {
+ regmap_reg_range(MLX90632_REG_I2C_ADDR, MLX90632_REG_CONTROL),
+ regmap_reg_range(MLX90632_REG_I2C_CMD, MLX90632_REG_I2C_CMD),
+ regmap_reg_range(MLX90632_REG_STATUS, MLX90632_REG_STATUS),
+ regmap_reg_range(MLX90632_RAM_1(0),
+ MLX90632_RAM_3(MLX90632_MAX_MEAS_NUM)),
+};
+
+static const struct regmap_access_table mlx90632_volatile_regs_tbl = {
+ .yes_ranges = mlx90632_volatile_reg_range,
+ .n_yes_ranges = ARRAY_SIZE(mlx90632_volatile_reg_range),
+};
+
+static const struct regmap_range mlx90632_read_reg_range[] = {
+ regmap_reg_range(MLX90632_EE_VERSION, MLX90632_EE_Ka),
+ regmap_reg_range(MLX90632_EE_CTRL, MLX90632_EE_I2C_ADDR),
+ regmap_reg_range(MLX90632_EE_Ha, MLX90632_EE_Hb),
+ regmap_reg_range(MLX90632_EE_MEDICAL_MEAS1, MLX90632_EE_MEDICAL_MEAS2),
+ regmap_reg_range(MLX90632_EE_EXTENDED_MEAS1, MLX90632_EE_EXTENDED_MEAS3),
+ regmap_reg_range(MLX90632_REG_I2C_ADDR, MLX90632_REG_CONTROL),
+ regmap_reg_range(MLX90632_REG_I2C_CMD, MLX90632_REG_I2C_CMD),
+ regmap_reg_range(MLX90632_REG_STATUS, MLX90632_REG_STATUS),
+ regmap_reg_range(MLX90632_RAM_1(0),
+ MLX90632_RAM_3(MLX90632_MAX_MEAS_NUM)),
+};
+
+static const struct regmap_access_table mlx90632_readable_regs_tbl = {
+ .yes_ranges = mlx90632_read_reg_range,
+ .n_yes_ranges = ARRAY_SIZE(mlx90632_read_reg_range),
+};
+
+static const struct regmap_range mlx90632_no_write_reg_range[] = {
+ regmap_reg_range(MLX90632_EE_VERSION, MLX90632_EE_Ka),
+ regmap_reg_range(MLX90632_RAM_1(0),
+ MLX90632_RAM_3(MLX90632_MAX_MEAS_NUM)),
+};
+
+static const struct regmap_access_table mlx90632_writeable_regs_tbl = {
+ .no_ranges = mlx90632_no_write_reg_range,
+ .n_no_ranges = ARRAY_SIZE(mlx90632_no_write_reg_range),
+};
+
+static const struct regmap_config mlx90632_regmap = {
+ .reg_bits = 16,
+ .val_bits = 16,
+
+ .volatile_table = &mlx90632_volatile_regs_tbl,
+ .rd_table = &mlx90632_readable_regs_tbl,
+ .wr_table = &mlx90632_writeable_regs_tbl,
+
+ .use_single_read = true,
+ .use_single_write = true,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int mlx90632_pwr_set_sleep_step(struct regmap *regmap)
+{
+ struct mlx90632_data *data =
+ iio_priv(dev_get_drvdata(regmap_get_device(regmap)));
+ int ret;
+
+ if (data->powerstatus == MLX90632_PWR_STATUS_SLEEP_STEP)
+ return 0;
+
+ ret = regmap_write_bits(regmap, MLX90632_REG_CONTROL, MLX90632_CFG_PWR_MASK,
+ MLX90632_PWR_STATUS_SLEEP_STEP);
+ if (ret < 0)
+ return ret;
+
+ data->powerstatus = MLX90632_PWR_STATUS_SLEEP_STEP;
+ return 0;
+}
+
+static int mlx90632_pwr_continuous(struct regmap *regmap)
+{
+ struct mlx90632_data *data =
+ iio_priv(dev_get_drvdata(regmap_get_device(regmap)));
+ int ret;
+
+ if (data->powerstatus == MLX90632_PWR_STATUS_CONTINUOUS)
+ return 0;
+
+ ret = regmap_write_bits(regmap, MLX90632_REG_CONTROL, MLX90632_CFG_PWR_MASK,
+ MLX90632_PWR_STATUS_CONTINUOUS);
+ if (ret < 0)
+ return ret;
+
+ data->powerstatus = MLX90632_PWR_STATUS_CONTINUOUS;
+ return 0;
+}
+
+/**
+ * mlx90632_reset_delay() - Give the mlx90632 some time to reset properly
+ * If this is not done, the following I2C command(s) will not be accepted.
+ */
+static void mlx90632_reset_delay(void)
+{
+ usleep_range(150, 200);
+}
+
+static int mlx90632_get_measurement_time(struct regmap *regmap, u16 meas)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(regmap, meas, &reg);
+ if (ret < 0)
+ return ret;
+
+ return MLX90632_MEAS_MAX_TIME >> FIELD_GET(MLX90632_EE_RR, reg);
+}
+
+static int mlx90632_calculate_dataset_ready_time(struct mlx90632_data *data)
+{
+ unsigned int refresh_time;
+ int ret;
+
+ if (data->mtyp == MLX90632_MTYP_MEDICAL) {
+ ret = mlx90632_get_measurement_time(data->regmap,
+ MLX90632_EE_MEDICAL_MEAS1);
+ if (ret < 0)
+ return ret;
+
+ refresh_time = ret;
+
+ ret = mlx90632_get_measurement_time(data->regmap,
+ MLX90632_EE_MEDICAL_MEAS2);
+ if (ret < 0)
+ return ret;
+
+ refresh_time += ret;
+ } else {
+ ret = mlx90632_get_measurement_time(data->regmap,
+ MLX90632_EE_EXTENDED_MEAS1);
+ if (ret < 0)
+ return ret;
+
+ refresh_time = ret;
+
+ ret = mlx90632_get_measurement_time(data->regmap,
+ MLX90632_EE_EXTENDED_MEAS2);
+ if (ret < 0)
+ return ret;
+
+ refresh_time += ret;
+
+ ret = mlx90632_get_measurement_time(data->regmap,
+ MLX90632_EE_EXTENDED_MEAS3);
+ if (ret < 0)
+ return ret;
+
+ refresh_time += ret;
+ }
+
+ return refresh_time;
+}
+
+/**
+ * mlx90632_perform_measurement() - Trigger and retrieve current measurement cycle
+ * @data: pointer to mlx90632_data object containing regmap information
+ *
+ * Perform a measurement and return latest measurement cycle position reported
+ * by sensor. This is a blocking function for 500ms, as that is default sensor
+ * refresh rate.
+ */
+static int mlx90632_perform_measurement(struct mlx90632_data *data)
+{
+ unsigned int reg_status;
+ int ret;
+
+ ret = regmap_clear_bits(data->regmap, MLX90632_REG_STATUS,
+ MLX90632_STAT_DATA_RDY);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read_poll_timeout(data->regmap, MLX90632_REG_STATUS, reg_status,
+ !(reg_status & MLX90632_STAT_DATA_RDY), 10000,
+ 100 * 10000);
+
+ if (ret < 0) {
+ dev_err(&data->client->dev, "data not ready");
+ return -ETIMEDOUT;
+ }
+
+ return (reg_status & MLX90632_STAT_CYCLE_POS) >> 2;
+}
+
+/**
+ * mlx90632_perform_measurement_burst() - Trigger and retrieve current measurement
+ * cycle in step sleep mode
+ * @data: pointer to mlx90632_data object containing regmap information
+ *
+ * Perform a measurement and return 2 as measurement cycle position reported
+ * by sensor. This is a blocking function for amount dependent on the sensor
+ * refresh rate.
+ */
+static int mlx90632_perform_measurement_burst(struct mlx90632_data *data)
+{
+ unsigned int reg_status;
+ int ret;
+
+ ret = regmap_write_bits(data->regmap, MLX90632_REG_CONTROL,
+ MLX90632_CFG_SOB_MASK, MLX90632_CFG_SOB_MASK);
+ if (ret < 0)
+ return ret;
+
+ ret = mlx90632_calculate_dataset_ready_time(data);
+ if (ret < 0)
+ return ret;
+
+ msleep(ret); /* Wait minimum time for dataset to be ready */
+
+ ret = regmap_read_poll_timeout(data->regmap, MLX90632_REG_STATUS,
+ reg_status,
+ (reg_status & MLX90632_STAT_BUSY) == 0,
+ 10000, 100 * 10000);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "data not ready");
+ return -ETIMEDOUT;
+ }
+
+ return 2;
+}
+
+static int mlx90632_set_meas_type(struct mlx90632_data *data, u8 type)
+{
+ int current_powerstatus;
+ int ret;
+
+ if (data->mtyp == type)
+ return 0;
+
+ current_powerstatus = data->powerstatus;
+ ret = mlx90632_pwr_continuous(data->regmap);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(data->regmap, MLX90632_REG_I2C_CMD, MLX90632_RESET_CMD);
+ if (ret < 0)
+ return ret;
+
+ mlx90632_reset_delay();
+
+ ret = regmap_update_bits(data->regmap, MLX90632_REG_CONTROL,
+ (MLX90632_CFG_MTYP_MASK | MLX90632_CFG_PWR_MASK),
+ (MLX90632_MTYP_STATUS(type) | MLX90632_PWR_STATUS_HALT));
+ if (ret < 0)
+ return ret;
+
+ data->mtyp = type;
+ data->powerstatus = MLX90632_PWR_STATUS_HALT;
+
+ if (current_powerstatus == MLX90632_PWR_STATUS_SLEEP_STEP)
+ return mlx90632_pwr_set_sleep_step(data->regmap);
+
+ return mlx90632_pwr_continuous(data->regmap);
+}
+
+static int mlx90632_channel_new_select(int perform_ret, uint8_t *channel_new,
+ uint8_t *channel_old)
+{
+ switch (perform_ret) {
+ case 1:
+ *channel_new = 1;
+ *channel_old = 2;
+ break;
+ case 2:
+ *channel_new = 2;
+ *channel_old = 1;
+ break;
+ default:
+ return -ECHRNG;
+ }
+
+ return 0;
+}
+
+static int mlx90632_read_ambient_raw(struct regmap *regmap,
+ s16 *ambient_new_raw, s16 *ambient_old_raw)
+{
+ unsigned int read_tmp;
+ int ret;
+
+ ret = regmap_read(regmap, MLX90632_RAM_3(1), &read_tmp);
+ if (ret < 0)
+ return ret;
+ *ambient_new_raw = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90632_RAM_3(2), &read_tmp);
+ if (ret < 0)
+ return ret;
+ *ambient_old_raw = (s16)read_tmp;
+
+ return ret;
+}
+
+static int mlx90632_read_object_raw(struct regmap *regmap,
+ int perform_measurement_ret,
+ s16 *object_new_raw, s16 *object_old_raw)
+{
+ unsigned int read_tmp;
+ u8 channel_old = 0;
+ u8 channel = 0;
+ s16 read;
+ int ret;
+
+ ret = mlx90632_channel_new_select(perform_measurement_ret, &channel,
+ &channel_old);
+ if (ret != 0)
+ return ret;
+
+ ret = regmap_read(regmap, MLX90632_RAM_2(channel), &read_tmp);
+ if (ret < 0)
+ return ret;
+
+ read = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90632_RAM_1(channel), &read_tmp);
+ if (ret < 0)
+ return ret;
+ *object_new_raw = (read + (s16)read_tmp) / 2;
+
+ ret = regmap_read(regmap, MLX90632_RAM_2(channel_old), &read_tmp);
+ if (ret < 0)
+ return ret;
+ read = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90632_RAM_1(channel_old), &read_tmp);
+ if (ret < 0)
+ return ret;
+ *object_old_raw = (read + (s16)read_tmp) / 2;
+
+ return ret;
+}
+
+static int mlx90632_read_all_channel(struct mlx90632_data *data,
+ s16 *ambient_new_raw, s16 *ambient_old_raw,
+ s16 *object_new_raw, s16 *object_old_raw)
+{
+ s32 measurement;
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = mlx90632_set_meas_type(data, MLX90632_MTYP_MEDICAL);
+ if (ret < 0)
+ goto read_unlock;
+
+ switch (data->powerstatus) {
+ case MLX90632_PWR_STATUS_CONTINUOUS:
+ ret = mlx90632_perform_measurement(data);
+ if (ret < 0)
+ goto read_unlock;
+
+ break;
+ case MLX90632_PWR_STATUS_SLEEP_STEP:
+ ret = mlx90632_perform_measurement_burst(data);
+ if (ret < 0)
+ goto read_unlock;
+
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ goto read_unlock;
+ }
+
+ measurement = ret; /* If we came here ret holds the measurement position */
+
+ ret = mlx90632_read_ambient_raw(data->regmap, ambient_new_raw,
+ ambient_old_raw);
+ if (ret < 0)
+ goto read_unlock;
+
+ ret = mlx90632_read_object_raw(data->regmap, measurement,
+ object_new_raw, object_old_raw);
+read_unlock:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int mlx90632_read_ambient_raw_extended(struct regmap *regmap,
+ s16 *ambient_new_raw, s16 *ambient_old_raw)
+{
+ unsigned int read_tmp;
+ int ret;
+
+ ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_AMBIENT_1, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *ambient_new_raw = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_AMBIENT_2, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *ambient_old_raw = (s16)read_tmp;
+
+ return 0;
+}
+
+static int mlx90632_read_object_raw_extended(struct regmap *regmap, s16 *object_new_raw)
+{
+ unsigned int read_tmp;
+ s32 read;
+ int ret;
+
+ ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_1, &read_tmp);
+ if (ret < 0)
+ return ret;
+ read = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_2, &read_tmp);
+ if (ret < 0)
+ return ret;
+ read = read - (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_3, &read_tmp);
+ if (ret < 0)
+ return ret;
+ read = read - (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_4, &read_tmp);
+ if (ret < 0)
+ return ret;
+ read = (read + (s16)read_tmp) / 2;
+
+ ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_5, &read_tmp);
+ if (ret < 0)
+ return ret;
+ read = read + (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_6, &read_tmp);
+ if (ret < 0)
+ return ret;
+ read = read + (s16)read_tmp;
+
+ if (read > S16_MAX || read < S16_MIN)
+ return -ERANGE;
+
+ *object_new_raw = read;
+
+ return 0;
+}
+
+static int mlx90632_read_all_channel_extended(struct mlx90632_data *data, s16 *object_new_raw,
+ s16 *ambient_new_raw, s16 *ambient_old_raw)
+{
+ s32 ret, meas;
+
+ mutex_lock(&data->lock);
+ ret = mlx90632_set_meas_type(data, MLX90632_MTYP_EXTENDED);
+ if (ret < 0)
+ goto read_unlock;
+
+ switch (data->powerstatus) {
+ case MLX90632_PWR_STATUS_CONTINUOUS:
+ ret = read_poll_timeout(mlx90632_perform_measurement, meas, meas == 19,
+ 50000, 800000, false, data);
+ if (ret)
+ goto read_unlock;
+ break;
+ case MLX90632_PWR_STATUS_SLEEP_STEP:
+ ret = mlx90632_perform_measurement_burst(data);
+ if (ret < 0)
+ goto read_unlock;
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ goto read_unlock;
+ }
+
+ ret = mlx90632_read_object_raw_extended(data->regmap, object_new_raw);
+ if (ret < 0)
+ goto read_unlock;
+
+ ret = mlx90632_read_ambient_raw_extended(data->regmap, ambient_new_raw, ambient_old_raw);
+
+read_unlock:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int mlx90632_read_ee_register(struct regmap *regmap, u16 reg_lsb,
+ s32 *reg_value)
+{
+ unsigned int read;
+ u32 value;
+ int ret;
+
+ ret = regmap_read(regmap, reg_lsb, &read);
+ if (ret < 0)
+ return ret;
+
+ value = read;
+
+ ret = regmap_read(regmap, reg_lsb + 1, &read);
+ if (ret < 0)
+ return ret;
+
+ *reg_value = (read << 16) | (value & 0xffff);
+
+ return 0;
+}
+
+static s64 mlx90632_preprocess_temp_amb(s16 ambient_new_raw,
+ s16 ambient_old_raw, s16 Gb)
+{
+ s64 VR_Ta, kGb, tmp;
+
+ kGb = ((s64)Gb * 1000LL) >> 10ULL;
+ VR_Ta = (s64)ambient_old_raw * 1000000LL +
+ kGb * div64_s64(((s64)ambient_new_raw * 1000LL),
+ (MLX90632_REF_3));
+ tmp = div64_s64(
+ div64_s64(((s64)ambient_new_raw * 1000000000000LL),
+ (MLX90632_REF_3)), VR_Ta);
+ return div64_s64(tmp << 19ULL, 1000LL);
+}
+
+static s64 mlx90632_preprocess_temp_obj(s16 object_new_raw, s16 object_old_raw,
+ s16 ambient_new_raw,
+ s16 ambient_old_raw, s16 Ka)
+{
+ s64 VR_IR, kKa, tmp;
+
+ kKa = ((s64)Ka * 1000LL) >> 10ULL;
+ VR_IR = (s64)ambient_old_raw * 1000000LL +
+ kKa * div64_s64(((s64)ambient_new_raw * 1000LL),
+ (MLX90632_REF_3));
+ tmp = div64_s64(
+ div64_s64(((s64)((object_new_raw + object_old_raw) / 2)
+ * 1000000000000LL), (MLX90632_REF_12)),
+ VR_IR);
+ return div64_s64((tmp << 19ULL), 1000LL);
+}
+
+static s64 mlx90632_preprocess_temp_obj_extended(s16 object_new_raw, s16 ambient_new_raw,
+ s16 ambient_old_raw, s16 Ka)
+{
+ s64 VR_IR, kKa, tmp;
+
+ kKa = ((s64)Ka * 1000LL) >> 10ULL;
+ VR_IR = (s64)ambient_old_raw * 1000000LL +
+ kKa * div64_s64((s64)ambient_new_raw * 1000LL,
+ MLX90632_REF_3);
+ tmp = div64_s64(
+ div64_s64((s64) object_new_raw * 1000000000000LL, MLX90632_REF_12),
+ VR_IR);
+ return div64_s64(tmp << 19ULL, 1000LL);
+}
+
+static s32 mlx90632_calc_temp_ambient(s16 ambient_new_raw, s16 ambient_old_raw,
+ s32 P_T, s32 P_R, s32 P_G, s32 P_O, s16 Gb)
+{
+ s64 Asub, Bsub, Ablock, Bblock, Cblock, AMB, sum;
+
+ AMB = mlx90632_preprocess_temp_amb(ambient_new_raw, ambient_old_raw,
+ Gb);
+ Asub = ((s64)P_T * 10000000000LL) >> 44ULL;
+ Bsub = AMB - (((s64)P_R * 1000LL) >> 8ULL);
+ Ablock = Asub * (Bsub * Bsub);
+ Bblock = (div64_s64(Bsub * 10000000LL, P_G)) << 20ULL;
+ Cblock = ((s64)P_O * 10000000000LL) >> 8ULL;
+
+ sum = div64_s64(Ablock, 1000000LL) + Bblock + Cblock;
+
+ return div64_s64(sum, 10000000LL);
+}
+
+static s32 mlx90632_calc_temp_object_iteration(s32 prev_object_temp, s64 object,
+ s64 TAdut, s64 TAdut4, s32 Fa, s32 Fb,
+ s32 Ga, s16 Ha, s16 Hb,
+ u16 emissivity)
+{
+ s64 calcedKsTO, calcedKsTA, ir_Alpha, Alpha_corr;
+ s64 Ha_customer, Hb_customer;
+
+ Ha_customer = ((s64)Ha * 1000000LL) >> 14ULL;
+ Hb_customer = ((s64)Hb * 100) >> 10ULL;
+
+ calcedKsTO = ((s64)((s64)Ga * (prev_object_temp - 25 * 1000LL)
+ * 1000LL)) >> 36LL;
+ calcedKsTA = ((s64)(Fb * (TAdut - 25 * 1000000LL))) >> 36LL;
+ Alpha_corr = div64_s64((((s64)(Fa * 10000000000LL) >> 46LL)
+ * Ha_customer), 1000LL);
+ Alpha_corr *= ((s64)(1 * 1000000LL + calcedKsTO + calcedKsTA));
+ Alpha_corr = emissivity * div64_s64(Alpha_corr, 100000LL);
+ Alpha_corr = div64_s64(Alpha_corr, 1000LL);
+ ir_Alpha = div64_s64((s64)object * 10000000LL, Alpha_corr);
+
+ return (int_sqrt64(int_sqrt64(ir_Alpha * 1000000000000LL + TAdut4))
+ - 27315 - Hb_customer) * 10;
+}
+
+static s64 mlx90632_calc_ta4(s64 TAdut, s64 scale)
+{
+ return (div64_s64(TAdut, scale) + 27315) *
+ (div64_s64(TAdut, scale) + 27315) *
+ (div64_s64(TAdut, scale) + 27315) *
+ (div64_s64(TAdut, scale) + 27315);
+}
+
+static s32 mlx90632_calc_temp_object(s64 object, s64 ambient, s32 Ea, s32 Eb,
+ s32 Fa, s32 Fb, s32 Ga, s16 Ha, s16 Hb,
+ u16 tmp_emi)
+{
+ s64 kTA, kTA0, TAdut, TAdut4;
+ s64 temp = 25000;
+ s8 i;
+
+ kTA = (Ea * 1000LL) >> 16LL;
+ kTA0 = (Eb * 1000LL) >> 8LL;
+ TAdut = div64_s64(((ambient - kTA0) * 1000000LL), kTA) + 25 * 1000000LL;
+ TAdut4 = mlx90632_calc_ta4(TAdut, 10000LL);
+
+ /* Iterations of calculation as described in datasheet */
+ for (i = 0; i < 5; ++i) {
+ temp = mlx90632_calc_temp_object_iteration(temp, object, TAdut, TAdut4,
+ Fa, Fb, Ga, Ha, Hb,
+ tmp_emi);
+ }
+ return temp;
+}
+
+static s32 mlx90632_calc_temp_object_extended(s64 object, s64 ambient, s64 reflected,
+ s32 Ea, s32 Eb, s32 Fa, s32 Fb, s32 Ga,
+ s16 Ha, s16 Hb, u16 tmp_emi)
+{
+ s64 kTA, kTA0, TAdut, TAdut4, Tr4, TaTr4;
+ s64 temp = 25000;
+ s8 i;
+
+ kTA = (Ea * 1000LL) >> 16LL;
+ kTA0 = (Eb * 1000LL) >> 8LL;
+ TAdut = div64_s64((ambient - kTA0) * 1000000LL, kTA) + 25 * 1000000LL;
+ Tr4 = mlx90632_calc_ta4(reflected, 10);
+ TAdut4 = mlx90632_calc_ta4(TAdut, 10000LL);
+ TaTr4 = Tr4 - div64_s64(Tr4 - TAdut4, tmp_emi) * 1000;
+
+ /* Iterations of calculation as described in datasheet */
+ for (i = 0; i < 5; ++i) {
+ temp = mlx90632_calc_temp_object_iteration(temp, object, TAdut, TaTr4,
+ Fa / 2, Fb, Ga, Ha, Hb,
+ tmp_emi);
+ }
+
+ return temp;
+}
+
+static int mlx90632_calc_object_dsp105(struct mlx90632_data *data, int *val)
+{
+ s16 ambient_new_raw, ambient_old_raw, object_new_raw, object_old_raw;
+ s32 Ea, Eb, Fa, Fb, Ga;
+ unsigned int read_tmp;
+ s64 object, ambient;
+ s16 Ha, Hb, Gb, Ka;
+ int ret;
+
+ ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_Ea, &Ea);
+ if (ret < 0)
+ return ret;
+ ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_Eb, &Eb);
+ if (ret < 0)
+ return ret;
+ ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_Fa, &Fa);
+ if (ret < 0)
+ return ret;
+ ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_Fb, &Fb);
+ if (ret < 0)
+ return ret;
+ ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_Ga, &Ga);
+ if (ret < 0)
+ return ret;
+ ret = regmap_read(data->regmap, MLX90632_EE_Ha, &read_tmp);
+ if (ret < 0)
+ return ret;
+ Ha = (s16)read_tmp;
+ ret = regmap_read(data->regmap, MLX90632_EE_Hb, &read_tmp);
+ if (ret < 0)
+ return ret;
+ Hb = (s16)read_tmp;
+ ret = regmap_read(data->regmap, MLX90632_EE_Gb, &read_tmp);
+ if (ret < 0)
+ return ret;
+ Gb = (s16)read_tmp;
+ ret = regmap_read(data->regmap, MLX90632_EE_Ka, &read_tmp);
+ if (ret < 0)
+ return ret;
+ Ka = (s16)read_tmp;
+
+ ret = mlx90632_read_all_channel(data,
+ &ambient_new_raw, &ambient_old_raw,
+ &object_new_raw, &object_old_raw);
+ if (ret < 0)
+ return ret;
+
+ if (object_new_raw > MLX90632_EXTENDED_LIMIT &&
+ data->mtyp == MLX90632_MTYP_EXTENDED) {
+ ret = mlx90632_read_all_channel_extended(data, &object_new_raw,
+ &ambient_new_raw, &ambient_old_raw);
+ if (ret < 0)
+ return ret;
+
+ /* Use extended mode calculations */
+ ambient = mlx90632_preprocess_temp_amb(ambient_new_raw,
+ ambient_old_raw, Gb);
+ object = mlx90632_preprocess_temp_obj_extended(object_new_raw,
+ ambient_new_raw,
+ ambient_old_raw, Ka);
+ *val = mlx90632_calc_temp_object_extended(object, ambient,
+ data->object_ambient_temperature,
+ Ea, Eb, Fa, Fb, Ga,
+ Ha, Hb, data->emissivity);
+ return 0;
+ }
+
+ ambient = mlx90632_preprocess_temp_amb(ambient_new_raw,
+ ambient_old_raw, Gb);
+ object = mlx90632_preprocess_temp_obj(object_new_raw,
+ object_old_raw,
+ ambient_new_raw,
+ ambient_old_raw, Ka);
+
+ *val = mlx90632_calc_temp_object(object, ambient, Ea, Eb, Fa, Fb, Ga,
+ Ha, Hb, data->emissivity);
+ return 0;
+}
+
+static int mlx90632_calc_ambient_dsp105(struct mlx90632_data *data, int *val)
+{
+ s16 ambient_new_raw, ambient_old_raw;
+ unsigned int read_tmp;
+ s32 PT, PR, PG, PO;
+ int ret;
+ s16 Gb;
+
+ ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_P_R, &PR);
+ if (ret < 0)
+ return ret;
+ ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_P_G, &PG);
+ if (ret < 0)
+ return ret;
+ ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_P_T, &PT);
+ if (ret < 0)
+ return ret;
+ ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_P_O, &PO);
+ if (ret < 0)
+ return ret;
+ ret = regmap_read(data->regmap, MLX90632_EE_Gb, &read_tmp);
+ if (ret < 0)
+ return ret;
+ Gb = (s16)read_tmp;
+
+ ret = mlx90632_read_ambient_raw(data->regmap, &ambient_new_raw,
+ &ambient_old_raw);
+ if (ret < 0)
+ return ret;
+ *val = mlx90632_calc_temp_ambient(ambient_new_raw, ambient_old_raw,
+ PT, PR, PG, PO, Gb);
+ return ret;
+}
+
+static int mlx90632_get_refresh_rate(struct mlx90632_data *data,
+ int *refresh_rate)
+{
+ unsigned int meas1;
+ int ret;
+
+ ret = regmap_read(data->regmap, MLX90632_EE_MEDICAL_MEAS1, &meas1);
+ if (ret < 0)
+ return ret;
+
+ *refresh_rate = MLX90632_REFRESH_RATE(meas1);
+
+ return ret;
+}
+
+static const int mlx90632_freqs[][2] = {
+ {0, 500000},
+ {1, 0},
+ {2, 0},
+ {4, 0},
+ {8, 0},
+ {16, 0},
+ {32, 0},
+ {64, 0}
+};
+
+/**
+ * mlx90632_pm_interraction_wakeup() - Measure time between user interactions to change powermode
+ * @data: pointer to mlx90632_data object containing interaction_ts information
+ *
+ * Switch to continuous mode when interaction is faster than MLX90632_MEAS_MAX_TIME. Update the
+ * interaction_ts for each function call with the jiffies to enable measurement between function
+ * calls. Initial value of the interaction_ts needs to be set before this function call.
+ */
+static int mlx90632_pm_interraction_wakeup(struct mlx90632_data *data)
+{
+ unsigned long now;
+ int ret;
+
+ now = jiffies;
+ if (time_in_range(now, data->interaction_ts,
+ data->interaction_ts +
+ msecs_to_jiffies(MLX90632_MEAS_MAX_TIME + 100))) {
+ if (data->powerstatus == MLX90632_PWR_STATUS_SLEEP_STEP) {
+ ret = mlx90632_pwr_continuous(data->regmap);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ data->interaction_ts = now;
+
+ return 0;
+}
+
+static int mlx90632_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ struct mlx90632_data *data = iio_priv(indio_dev);
+ int ret;
+ int cr;
+
+ pm_runtime_get_sync(&data->client->dev);
+ ret = mlx90632_pm_interraction_wakeup(data);
+ if (ret < 0)
+ goto mlx90632_read_raw_pm;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (channel->channel2) {
+ case IIO_MOD_TEMP_AMBIENT:
+ ret = mlx90632_calc_ambient_dsp105(data, val);
+ if (ret < 0)
+ goto mlx90632_read_raw_pm;
+
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_MOD_TEMP_OBJECT:
+ ret = mlx90632_calc_object_dsp105(data, val);
+ if (ret < 0)
+ goto mlx90632_read_raw_pm;
+
+ ret = IIO_VAL_INT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ break;
+ case IIO_CHAN_INFO_CALIBEMISSIVITY:
+ if (data->emissivity == 1000) {
+ *val = 1;
+ *val2 = 0;
+ } else {
+ *val = 0;
+ *val2 = data->emissivity * 1000;
+ }
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ case IIO_CHAN_INFO_CALIBAMBIENT:
+ *val = data->object_ambient_temperature;
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = mlx90632_get_refresh_rate(data, &cr);
+ if (ret < 0)
+ goto mlx90632_read_raw_pm;
+
+ *val = mlx90632_freqs[cr][0];
+ *val2 = mlx90632_freqs[cr][1];
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+mlx90632_read_raw_pm:
+ pm_runtime_put_autosuspend(&data->client->dev);
+ return ret;
+}
+
+static int mlx90632_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int val,
+ int val2, long mask)
+{
+ struct mlx90632_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_CALIBEMISSIVITY:
+ /* Confirm we are within 0 and 1.0 */
+ if (val < 0 || val2 < 0 || val > 1 ||
+ (val == 1 && val2 != 0))
+ return -EINVAL;
+ data->emissivity = val * 1000 + val2 / 1000;
+ return 0;
+ case IIO_CHAN_INFO_CALIBAMBIENT:
+ data->object_ambient_temperature = val;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mlx90632_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = (int *)mlx90632_freqs;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = 2 * ARRAY_SIZE(mlx90632_freqs);
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_chan_spec mlx90632_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .modified = 1,
+ .channel2 = IIO_MOD_TEMP_AMBIENT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ },
+ {
+ .type = IIO_TEMP,
+ .modified = 1,
+ .channel2 = IIO_MOD_TEMP_OBJECT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_CALIBEMISSIVITY) | BIT(IIO_CHAN_INFO_CALIBAMBIENT),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ },
+};
+
+static const struct iio_info mlx90632_info = {
+ .read_raw = mlx90632_read_raw,
+ .write_raw = mlx90632_write_raw,
+ .read_avail = mlx90632_read_avail,
+};
+
+static void mlx90632_sleep(void *_data)
+{
+ struct mlx90632_data *data = _data;
+
+ mlx90632_pwr_set_sleep_step(data->regmap);
+}
+
+static int mlx90632_suspend(struct mlx90632_data *data)
+{
+ regcache_mark_dirty(data->regmap);
+
+ dev_dbg(&data->client->dev, "Requesting suspend");
+ return mlx90632_pwr_set_sleep_step(data->regmap);
+}
+
+static int mlx90632_wakeup(struct mlx90632_data *data)
+{
+ int ret;
+
+ ret = regcache_sync(data->regmap);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Failed to sync regmap registers: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(&data->client->dev, "Requesting wake-up\n");
+ return mlx90632_pwr_continuous(data->regmap);
+}
+
+static void mlx90632_disable_regulator(void *_data)
+{
+ struct mlx90632_data *data = _data;
+ int ret;
+
+ ret = regulator_disable(data->regulator);
+ if (ret < 0)
+ dev_err(regmap_get_device(data->regmap),
+ "Failed to disable power regulator: %d\n", ret);
+}
+
+static int mlx90632_enable_regulator(struct mlx90632_data *data)
+{
+ int ret;
+
+ ret = regulator_enable(data->regulator);
+ if (ret < 0) {
+ dev_err(regmap_get_device(data->regmap), "Failed to enable power regulator!\n");
+ return ret;
+ }
+
+ mlx90632_reset_delay();
+
+ return ret;
+}
+
+static int mlx90632_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct mlx90632_data *mlx90632;
+ struct iio_dev *indio_dev;
+ struct regmap *regmap;
+ unsigned int read;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mlx90632));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ regmap = devm_regmap_init_i2c(client, &mlx90632_regmap);
+ if (IS_ERR(regmap)) {
+ ret = PTR_ERR(regmap);
+ dev_err(&client->dev, "Failed to allocate regmap: %d\n", ret);
+ return ret;
+ }
+
+ mlx90632 = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ mlx90632->client = client;
+ mlx90632->regmap = regmap;
+ mlx90632->mtyp = MLX90632_MTYP_MEDICAL;
+ mlx90632->powerstatus = MLX90632_PWR_STATUS_HALT;
+
+ mutex_init(&mlx90632->lock);
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &mlx90632_info;
+ indio_dev->channels = mlx90632_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mlx90632_channels);
+
+ mlx90632->regulator = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(mlx90632->regulator))
+ return dev_err_probe(&client->dev, PTR_ERR(mlx90632->regulator),
+ "failed to get vdd regulator");
+
+ ret = mlx90632_enable_regulator(mlx90632);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(&client->dev, mlx90632_disable_regulator,
+ mlx90632);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to setup regulator cleanup action %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = mlx90632_wakeup(mlx90632);
+ if (ret < 0) {
+ dev_err(&client->dev, "Wakeup failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(&client->dev, mlx90632_sleep, mlx90632);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to setup low power cleanup action %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = regmap_read(mlx90632->regmap, MLX90632_EE_VERSION, &read);
+ if (ret < 0) {
+ dev_err(&client->dev, "read of version failed: %d\n", ret);
+ return ret;
+ }
+ read = read & MLX90632_ID_MASK;
+ if (read == MLX90632_ID_MEDICAL) {
+ dev_dbg(&client->dev,
+ "Detected Medical EEPROM calibration %x\n", read);
+ } else if (read == MLX90632_ID_CONSUMER) {
+ dev_dbg(&client->dev,
+ "Detected Consumer EEPROM calibration %x\n", read);
+ } else if (read == MLX90632_ID_EXTENDED) {
+ dev_dbg(&client->dev,
+ "Detected Extended range EEPROM calibration %x\n", read);
+ mlx90632->mtyp = MLX90632_MTYP_EXTENDED;
+ } else if ((read & MLX90632_DSP_MASK) == MLX90632_DSP_VERSION) {
+ dev_dbg(&client->dev,
+ "Detected Unknown EEPROM calibration %x\n", read);
+ } else {
+ dev_err(&client->dev,
+ "Wrong DSP version %x (expected %x)\n",
+ read, MLX90632_DSP_VERSION);
+ return -EPROTONOSUPPORT;
+ }
+
+ mlx90632->emissivity = 1000;
+ mlx90632->object_ambient_temperature = 25000; /* 25 degrees milliCelsius */
+ mlx90632->interaction_ts = jiffies; /* Set initial value */
+
+ pm_runtime_get_noresume(&client->dev);
+ pm_runtime_set_active(&client->dev);
+
+ ret = devm_pm_runtime_enable(&client->dev);
+ if (ret)
+ return ret;
+
+ pm_runtime_set_autosuspend_delay(&client->dev, MLX90632_SLEEP_DELAY_MS);
+ pm_runtime_use_autosuspend(&client->dev);
+ pm_runtime_put_autosuspend(&client->dev);
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id mlx90632_id[] = {
+ { .name = "mlx90632" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mlx90632_id);
+
+static const struct of_device_id mlx90632_of_match[] = {
+ { .compatible = "melexis,mlx90632" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mlx90632_of_match);
+
+static int mlx90632_pm_suspend(struct device *dev)
+{
+ struct mlx90632_data *data = iio_priv(dev_get_drvdata(dev));
+ int ret;
+
+ ret = mlx90632_suspend(data);
+ if (ret < 0)
+ return ret;
+
+ ret = regulator_disable(data->regulator);
+ if (ret < 0)
+ dev_err(regmap_get_device(data->regmap),
+ "Failed to disable power regulator: %d\n", ret);
+
+ return ret;
+}
+
+static int mlx90632_pm_resume(struct device *dev)
+{
+ struct mlx90632_data *data = iio_priv(dev_get_drvdata(dev));
+ int ret;
+
+ ret = mlx90632_enable_regulator(data);
+ if (ret < 0)
+ return ret;
+
+ return mlx90632_wakeup(data);
+}
+
+static int mlx90632_pm_runtime_suspend(struct device *dev)
+{
+ struct mlx90632_data *data = iio_priv(dev_get_drvdata(dev));
+
+ return mlx90632_pwr_set_sleep_step(data->regmap);
+}
+
+static const struct dev_pm_ops mlx90632_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(mlx90632_pm_suspend, mlx90632_pm_resume)
+ RUNTIME_PM_OPS(mlx90632_pm_runtime_suspend, NULL, NULL)
+};
+
+static struct i2c_driver mlx90632_driver = {
+ .driver = {
+ .name = "mlx90632",
+ .of_match_table = mlx90632_of_match,
+ .pm = pm_ptr(&mlx90632_pm_ops),
+ },
+ .probe = mlx90632_probe,
+ .id_table = mlx90632_id,
+};
+module_i2c_driver(mlx90632_driver);
+
+MODULE_AUTHOR("Crt Mori <cmo@melexis.com>");
+MODULE_DESCRIPTION("Melexis MLX90632 contactless Infra Red temperature sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/temperature/mlx90635.c b/drivers/iio/temperature/mlx90635.c
new file mode 100644
index 000000000..0f31879a4
--- /dev/null
+++ b/drivers/iio/temperature/mlx90635.c
@@ -0,0 +1,1093 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mlx90635.c - Melexis MLX90635 contactless IR temperature sensor
+ *
+ * Copyright (c) 2023 Melexis <cmo@melexis.com>
+ *
+ * Driver for the Melexis MLX90635 I2C 16-bit IR thermopile sensor
+ */
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/math64.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/iio/iio.h>
+
+/* Memory sections addresses */
+#define MLX90635_ADDR_RAM 0x0000 /* Start address of ram */
+#define MLX90635_ADDR_EEPROM 0x0018 /* Start address of user eeprom */
+
+/* EEPROM addresses - used at startup */
+#define MLX90635_EE_I2C_CFG 0x0018 /* I2C address register initial value */
+#define MLX90635_EE_CTRL1 0x001A /* Control register1 initial value */
+#define MLX90635_EE_CTRL2 0x001C /* Control register2 initial value */
+
+#define MLX90635_EE_Ha 0x001E /* Ha customer calib value reg 16bit */
+#define MLX90635_EE_Hb 0x0020 /* Hb customer calib value reg 16bit */
+#define MLX90635_EE_Fa 0x0026 /* Fa calibration register 32bit */
+#define MLX90635_EE_FASCALE 0x002A /* Scaling coefficient for Fa register 16bit */
+#define MLX90635_EE_Ga 0x002C /* Ga calibration register 16bit */
+#define MLX90635_EE_Fb 0x002E /* Fb calibration register 16bit */
+#define MLX90635_EE_Ea 0x0030 /* Ea calibration register 32bit */
+#define MLX90635_EE_Eb 0x0034 /* Eb calibration register 32bit */
+#define MLX90635_EE_P_G 0x0038 /* P_G calibration register 16bit */
+#define MLX90635_EE_P_O 0x003A /* P_O calibration register 16bit */
+#define MLX90635_EE_Aa 0x003C /* Aa calibration register 16bit */
+#define MLX90635_EE_VERSION 0x003E /* Version bits 4:7 and 12:15 */
+#define MLX90635_EE_Gb 0x0040 /* Gb calibration register 16bit */
+
+/* Device status register - volatile */
+#define MLX90635_REG_STATUS 0x0000
+#define MLX90635_STAT_BUSY BIT(6) /* Device busy indicator */
+#define MLX90635_STAT_BRST BIT(5) /* Brown out reset indicator */
+#define MLX90635_STAT_CYCLE_POS GENMASK(4, 2) /* Data position */
+#define MLX90635_STAT_END_CONV BIT(1) /* End of conversion indicator */
+#define MLX90635_STAT_DATA_RDY BIT(0) /* Data ready indicator */
+
+/* EEPROM control register address - volatile */
+#define MLX90635_REG_EE 0x000C
+#define MLX90635_EE_ACTIVE BIT(4) /* Power-on EEPROM */
+#define MLX90635_EE_BUSY_MASK BIT(15)
+
+#define MLX90635_REG_CMD 0x0010 /* Command register address */
+
+/* Control register1 address - volatile */
+#define MLX90635_REG_CTRL1 0x0014
+#define MLX90635_CTRL1_REFRESH_RATE_MASK GENMASK(2, 0)
+#define MLX90635_CTRL1_RES_CTRL_MASK GENMASK(4, 3)
+#define MLX90635_CTRL1_TABLE_MASK BIT(15) /* Table select */
+
+/* Control register2 address - volatile */
+#define MLX90635_REG_CTRL2 0x0016
+#define MLX90635_CTRL2_BURST_CNT_MASK GENMASK(10, 6) /* Burst count */
+#define MLX90635_CTRL2_MODE_MASK GENMASK(12, 11) /* Power mode */
+#define MLX90635_CTRL2_SOB_MASK BIT(15)
+
+/* PowerModes statuses */
+#define MLX90635_PWR_STATUS_HALT 0
+#define MLX90635_PWR_STATUS_SLEEP_STEP 1
+#define MLX90635_PWR_STATUS_STEP 2
+#define MLX90635_PWR_STATUS_CONTINUOUS 3
+
+/* Measurement data addresses */
+#define MLX90635_RESULT_1 0x0002
+#define MLX90635_RESULT_2 0x0004
+#define MLX90635_RESULT_3 0x0006
+#define MLX90635_RESULT_4 0x0008
+#define MLX90635_RESULT_5 0x000A
+
+/* Timings (ms) */
+#define MLX90635_TIMING_RST_MIN 200 /* Minimum time after addressed reset command */
+#define MLX90635_TIMING_RST_MAX 250 /* Maximum time after addressed reset command */
+#define MLX90635_TIMING_POLLING 10000 /* Time between bit polling*/
+#define MLX90635_TIMING_EE_ACTIVE_MIN 100 /* Minimum time after activating the EEPROM for read */
+#define MLX90635_TIMING_EE_ACTIVE_MAX 150 /* Maximum time after activating the EEPROM for read */
+
+/* Magic constants */
+#define MLX90635_ID_DSPv1 0x01 /* EEPROM DSP version */
+#define MLX90635_RESET_CMD 0x0006 /* Reset sensor (address or global) */
+#define MLX90635_MAX_MEAS_NUM 31 /* Maximum number of measurements in list */
+#define MLX90635_PTAT_DIV 12 /* Used to divide the PTAT value in pre-processing */
+#define MLX90635_IR_DIV 24 /* Used to divide the IR value in pre-processing */
+#define MLX90635_SLEEP_DELAY_MS 6000 /* Autosleep delay */
+#define MLX90635_MEAS_MAX_TIME 2000 /* Max measurement time in ms for the lowest refresh rate */
+#define MLX90635_READ_RETRIES 100 /* Number of read retries before quitting with timeout error */
+#define MLX90635_VERSION_MASK (GENMASK(15, 12) | GENMASK(7, 4))
+#define MLX90635_DSP_VERSION(reg) (((reg & GENMASK(14, 12)) >> 9) | ((reg & GENMASK(6, 4)) >> 4))
+#define MLX90635_DSP_FIXED BIT(15)
+
+
+/**
+ * struct mlx90635_data - private data for the MLX90635 device
+ * @client: I2C client of the device
+ * @lock: Internal mutex because multiple reads are needed for single triggered
+ * measurement to ensure data consistency
+ * @regmap: Regmap of the device registers
+ * @regmap_ee: Regmap of the device EEPROM which can be cached
+ * @emissivity: Object emissivity from 0 to 1000 where 1000 = 1
+ * @regulator: Regulator of the device
+ * @powerstatus: Current POWER status of the device
+ * @interaction_ts: Timestamp of the last temperature read that is used
+ * for power management in jiffies
+ */
+struct mlx90635_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ struct regmap *regmap;
+ struct regmap *regmap_ee;
+ u16 emissivity;
+ struct regulator *regulator;
+ int powerstatus;
+ unsigned long interaction_ts;
+};
+
+static const struct regmap_range mlx90635_volatile_reg_range[] = {
+ regmap_reg_range(MLX90635_REG_STATUS, MLX90635_REG_STATUS),
+ regmap_reg_range(MLX90635_RESULT_1, MLX90635_RESULT_5),
+ regmap_reg_range(MLX90635_REG_EE, MLX90635_REG_EE),
+ regmap_reg_range(MLX90635_REG_CMD, MLX90635_REG_CMD),
+ regmap_reg_range(MLX90635_REG_CTRL1, MLX90635_REG_CTRL2),
+};
+
+static const struct regmap_access_table mlx90635_volatile_regs_tbl = {
+ .yes_ranges = mlx90635_volatile_reg_range,
+ .n_yes_ranges = ARRAY_SIZE(mlx90635_volatile_reg_range),
+};
+
+static const struct regmap_range mlx90635_read_reg_range[] = {
+ regmap_reg_range(MLX90635_REG_STATUS, MLX90635_REG_STATUS),
+ regmap_reg_range(MLX90635_RESULT_1, MLX90635_RESULT_5),
+ regmap_reg_range(MLX90635_REG_EE, MLX90635_REG_EE),
+ regmap_reg_range(MLX90635_REG_CMD, MLX90635_REG_CMD),
+ regmap_reg_range(MLX90635_REG_CTRL1, MLX90635_REG_CTRL2),
+};
+
+static const struct regmap_access_table mlx90635_readable_regs_tbl = {
+ .yes_ranges = mlx90635_read_reg_range,
+ .n_yes_ranges = ARRAY_SIZE(mlx90635_read_reg_range),
+};
+
+static const struct regmap_range mlx90635_no_write_reg_range[] = {
+ regmap_reg_range(MLX90635_RESULT_1, MLX90635_RESULT_5),
+};
+
+static const struct regmap_access_table mlx90635_writeable_regs_tbl = {
+ .no_ranges = mlx90635_no_write_reg_range,
+ .n_no_ranges = ARRAY_SIZE(mlx90635_no_write_reg_range),
+};
+
+static const struct regmap_config mlx90635_regmap = {
+ .name = "mlx90635-registers",
+ .reg_stride = 1,
+ .reg_bits = 16,
+ .val_bits = 16,
+
+ .volatile_table = &mlx90635_volatile_regs_tbl,
+ .rd_table = &mlx90635_readable_regs_tbl,
+ .wr_table = &mlx90635_writeable_regs_tbl,
+
+ .use_single_read = true,
+ .use_single_write = true,
+ .can_multi_write = false,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static const struct regmap_range mlx90635_read_ee_range[] = {
+ regmap_reg_range(MLX90635_EE_I2C_CFG, MLX90635_EE_CTRL2),
+ regmap_reg_range(MLX90635_EE_Ha, MLX90635_EE_Gb),
+};
+
+static const struct regmap_access_table mlx90635_readable_ees_tbl = {
+ .yes_ranges = mlx90635_read_ee_range,
+ .n_yes_ranges = ARRAY_SIZE(mlx90635_read_ee_range),
+};
+
+static const struct regmap_range mlx90635_no_write_ee_range[] = {
+ regmap_reg_range(MLX90635_ADDR_EEPROM, MLX90635_EE_Gb),
+};
+
+static const struct regmap_access_table mlx90635_writeable_ees_tbl = {
+ .no_ranges = mlx90635_no_write_ee_range,
+ .n_no_ranges = ARRAY_SIZE(mlx90635_no_write_ee_range),
+};
+
+static const struct regmap_config mlx90635_regmap_ee = {
+ .name = "mlx90635-eeprom",
+ .reg_stride = 1,
+ .reg_bits = 16,
+ .val_bits = 16,
+
+ .volatile_table = NULL,
+ .rd_table = &mlx90635_readable_ees_tbl,
+ .wr_table = &mlx90635_writeable_ees_tbl,
+
+ .use_single_read = true,
+ .use_single_write = true,
+ .can_multi_write = false,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+/**
+ * mlx90635_reset_delay() - Give the mlx90635 some time to reset properly
+ * If this is not done, the following I2C command(s) will not be accepted.
+ */
+static void mlx90635_reset_delay(void)
+{
+ usleep_range(MLX90635_TIMING_RST_MIN, MLX90635_TIMING_RST_MAX);
+}
+
+static int mlx90635_pwr_sleep_step(struct mlx90635_data *data)
+{
+ int ret;
+
+ if (data->powerstatus == MLX90635_PWR_STATUS_SLEEP_STEP)
+ return 0;
+
+ ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL2, MLX90635_CTRL2_MODE_MASK,
+ FIELD_PREP(MLX90635_CTRL2_MODE_MASK, MLX90635_PWR_STATUS_SLEEP_STEP));
+ if (ret < 0)
+ return ret;
+
+ data->powerstatus = MLX90635_PWR_STATUS_SLEEP_STEP;
+ return 0;
+}
+
+static int mlx90635_pwr_continuous(struct mlx90635_data *data)
+{
+ int ret;
+
+ if (data->powerstatus == MLX90635_PWR_STATUS_CONTINUOUS)
+ return 0;
+
+ ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL2, MLX90635_CTRL2_MODE_MASK,
+ FIELD_PREP(MLX90635_CTRL2_MODE_MASK, MLX90635_PWR_STATUS_CONTINUOUS));
+ if (ret < 0)
+ return ret;
+
+ data->powerstatus = MLX90635_PWR_STATUS_CONTINUOUS;
+ return 0;
+}
+
+static int mlx90635_read_ee_register(struct regmap *regmap, u16 reg_lsb,
+ s32 *reg_value)
+{
+ unsigned int read;
+ u32 value;
+ int ret;
+
+ ret = regmap_read(regmap, reg_lsb + 2, &read);
+ if (ret < 0)
+ return ret;
+
+ value = read;
+
+ ret = regmap_read(regmap, reg_lsb, &read);
+ if (ret < 0)
+ return ret;
+
+ *reg_value = (read << 16) | (value & 0xffff);
+
+ return 0;
+}
+
+static int mlx90635_read_ee_ambient(struct regmap *regmap, s16 *PG, s16 *PO, s16 *Gb)
+{
+ unsigned int read_tmp;
+ int ret;
+
+ ret = regmap_read(regmap, MLX90635_EE_P_O, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *PO = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_P_G, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *PG = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_Gb, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Gb = (u16)read_tmp;
+
+ return 0;
+}
+
+static int mlx90635_read_ee_object(struct regmap *regmap, u32 *Ea, u32 *Eb, u32 *Fa, s16 *Fb,
+ s16 *Ga, s16 *Gb, s16 *Ha, s16 *Hb, u16 *Fa_scale)
+{
+ unsigned int read_tmp;
+ int ret;
+
+ ret = mlx90635_read_ee_register(regmap, MLX90635_EE_Ea, Ea);
+ if (ret < 0)
+ return ret;
+
+ ret = mlx90635_read_ee_register(regmap, MLX90635_EE_Eb, Eb);
+ if (ret < 0)
+ return ret;
+
+ ret = mlx90635_read_ee_register(regmap, MLX90635_EE_Fa, Fa);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, MLX90635_EE_Ha, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Ha = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_Hb, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Hb = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_Ga, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Ga = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_Gb, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Gb = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_Fb, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Fb = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_FASCALE, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Fa_scale = (u16)read_tmp;
+
+ return 0;
+}
+
+static int mlx90635_calculate_dataset_ready_time(struct mlx90635_data *data, int *refresh_time)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(data->regmap, MLX90635_REG_CTRL1, &reg);
+ if (ret < 0)
+ return ret;
+
+ *refresh_time = 2 * (MLX90635_MEAS_MAX_TIME >> FIELD_GET(MLX90635_CTRL1_REFRESH_RATE_MASK, reg)) + 80;
+
+ return 0;
+}
+
+static int mlx90635_perform_measurement_burst(struct mlx90635_data *data)
+{
+ unsigned int reg_status;
+ int refresh_time;
+ int ret;
+
+ ret = regmap_write_bits(data->regmap, MLX90635_REG_STATUS,
+ MLX90635_STAT_END_CONV, MLX90635_STAT_END_CONV);
+ if (ret < 0)
+ return ret;
+
+ ret = mlx90635_calculate_dataset_ready_time(data, &refresh_time);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL2,
+ FIELD_PREP(MLX90635_CTRL2_SOB_MASK, 1),
+ FIELD_PREP(MLX90635_CTRL2_SOB_MASK, 1));
+ if (ret < 0)
+ return ret;
+
+ msleep(refresh_time); /* Wait minimum time for dataset to be ready */
+
+ ret = regmap_read_poll_timeout(data->regmap, MLX90635_REG_STATUS, reg_status,
+ (!(reg_status & MLX90635_STAT_END_CONV)) == 0,
+ MLX90635_TIMING_POLLING, MLX90635_READ_RETRIES * 10000);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "data not ready");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int mlx90635_read_ambient_raw(struct regmap *regmap,
+ s16 *ambient_new_raw, s16 *ambient_old_raw)
+{
+ unsigned int read_tmp;
+ int ret;
+
+ ret = regmap_read(regmap, MLX90635_RESULT_2, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *ambient_new_raw = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_RESULT_3, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *ambient_old_raw = (s16)read_tmp;
+
+ return 0;
+}
+
+static int mlx90635_read_object_raw(struct regmap *regmap, s16 *object_raw)
+{
+ unsigned int read_tmp;
+ s16 read;
+ int ret;
+
+ ret = regmap_read(regmap, MLX90635_RESULT_1, &read_tmp);
+ if (ret < 0)
+ return ret;
+
+ read = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_RESULT_4, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *object_raw = (read - (s16)read_tmp) / 2;
+
+ return 0;
+}
+
+static int mlx90635_read_all_channel(struct mlx90635_data *data,
+ s16 *ambient_new_raw, s16 *ambient_old_raw,
+ s16 *object_raw)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ if (data->powerstatus == MLX90635_PWR_STATUS_SLEEP_STEP) {
+ /* Trigger measurement in Sleep Step mode */
+ ret = mlx90635_perform_measurement_burst(data);
+ if (ret < 0)
+ goto read_unlock;
+ }
+
+ ret = mlx90635_read_ambient_raw(data->regmap, ambient_new_raw,
+ ambient_old_raw);
+ if (ret < 0)
+ goto read_unlock;
+
+ ret = mlx90635_read_object_raw(data->regmap, object_raw);
+read_unlock:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static s64 mlx90635_preprocess_temp_amb(s16 ambient_new_raw,
+ s16 ambient_old_raw, s16 Gb)
+{
+ s64 VR_Ta, kGb, tmp;
+
+ kGb = ((s64)Gb * 1000LL) >> 10ULL;
+ VR_Ta = (s64)ambient_old_raw * 1000000LL +
+ kGb * div64_s64(((s64)ambient_new_raw * 1000LL),
+ (MLX90635_PTAT_DIV));
+ tmp = div64_s64(
+ div64_s64(((s64)ambient_new_raw * 1000000000000LL),
+ (MLX90635_PTAT_DIV)), VR_Ta);
+ return div64_s64(tmp << 19ULL, 1000LL);
+}
+
+static s64 mlx90635_preprocess_temp_obj(s16 object_raw,
+ s16 ambient_new_raw,
+ s16 ambient_old_raw, s16 Gb)
+{
+ s64 VR_IR, kGb, tmp;
+
+ kGb = ((s64)Gb * 1000LL) >> 10ULL;
+ VR_IR = (s64)ambient_old_raw * 1000000LL +
+ kGb * (div64_s64((s64)ambient_new_raw * 1000LL,
+ MLX90635_PTAT_DIV));
+ tmp = div64_s64(
+ div64_s64((s64)(object_raw * 1000000LL),
+ MLX90635_IR_DIV) * 1000000LL,
+ VR_IR);
+ return div64_s64((tmp << 19ULL), 1000LL);
+}
+
+static s32 mlx90635_calc_temp_ambient(s16 ambient_new_raw, s16 ambient_old_raw,
+ u16 P_G, u16 P_O, s16 Gb)
+{
+ s64 kPG, kPO, AMB;
+
+ AMB = mlx90635_preprocess_temp_amb(ambient_new_raw, ambient_old_raw,
+ Gb);
+ kPG = ((s64)P_G * 1000000LL) >> 9ULL;
+ kPO = AMB - (((s64)P_O * 1000LL) >> 1ULL);
+
+ return 30 * 1000LL + div64_s64(kPO * 1000000LL, kPG);
+}
+
+static s32 mlx90635_calc_temp_object_iteration(s32 prev_object_temp, s64 object,
+ s64 TAdut, s64 TAdut4, s16 Ga,
+ u32 Fa, u16 Fa_scale, s16 Fb,
+ s16 Ha, s16 Hb, u16 emissivity)
+{
+ s64 calcedGa, calcedGb, calcedFa, Alpha_corr;
+ s64 Ha_customer, Hb_customer;
+
+ Ha_customer = ((s64)Ha * 1000000LL) >> 14ULL;
+ Hb_customer = ((s64)Hb * 100) >> 10ULL;
+
+ calcedGa = ((s64)((s64)Ga * (prev_object_temp - 35 * 1000LL)
+ * 1000LL)) >> 24LL;
+ calcedGb = ((s64)(Fb * (TAdut - 30 * 1000000LL))) >> 24LL;
+
+ Alpha_corr = ((s64)((s64)Fa * Ha_customer * 10000LL) >> Fa_scale);
+ Alpha_corr *= ((s64)(1 * 1000000LL + calcedGa + calcedGb));
+
+ Alpha_corr = div64_s64(Alpha_corr, 1000LL);
+ Alpha_corr *= emissivity;
+ Alpha_corr = div64_s64(Alpha_corr, 100LL);
+ calcedFa = div64_s64((s64)object * 100000000000LL, Alpha_corr);
+
+ return (int_sqrt64(int_sqrt64(calcedFa * 100000000LL + TAdut4))
+ - 27315 - Hb_customer) * 10;
+}
+
+static s64 mlx90635_calc_ta4(s64 TAdut, s64 scale)
+{
+ return (div64_s64(TAdut, scale) + 27315) *
+ (div64_s64(TAdut, scale) + 27315) *
+ (div64_s64(TAdut, scale) + 27315) *
+ (div64_s64(TAdut, scale) + 27315);
+}
+
+static s32 mlx90635_calc_temp_object(s64 object, s64 ambient, u32 Ea, u32 Eb,
+ s16 Ga, u32 Fa, u16 Fa_scale, s16 Fb, s16 Ha, s16 Hb,
+ u16 tmp_emi)
+{
+ s64 kTA, kTA0, TAdut, TAdut4;
+ s64 temp = 35000;
+ s8 i;
+
+ kTA = (Ea * 1000LL) >> 16LL;
+ kTA0 = (Eb * 1000LL) >> 8LL;
+ TAdut = div64_s64(((ambient - kTA0) * 1000000LL), kTA) + 30 * 1000000LL;
+ TAdut4 = mlx90635_calc_ta4(TAdut, 10000LL);
+
+ /* Iterations of calculation as described in datasheet */
+ for (i = 0; i < 5; ++i) {
+ temp = mlx90635_calc_temp_object_iteration(temp, object, TAdut, TAdut4,
+ Ga, Fa, Fa_scale, Fb, Ha, Hb,
+ tmp_emi);
+ }
+ return temp;
+}
+
+static int mlx90635_calc_object(struct mlx90635_data *data, int *val)
+{
+ s16 ambient_new_raw, ambient_old_raw, object_raw;
+ s16 Fb, Ga, Gb, Ha, Hb;
+ s64 object, ambient;
+ u32 Ea, Eb, Fa;
+ u16 Fa_scale;
+ int ret;
+
+ ret = mlx90635_read_ee_object(data->regmap_ee, &Ea, &Eb, &Fa, &Fb, &Ga, &Gb, &Ha, &Hb, &Fa_scale);
+ if (ret < 0)
+ return ret;
+
+ ret = mlx90635_read_all_channel(data,
+ &ambient_new_raw, &ambient_old_raw,
+ &object_raw);
+ if (ret < 0)
+ return ret;
+
+ ambient = mlx90635_preprocess_temp_amb(ambient_new_raw,
+ ambient_old_raw, Gb);
+ object = mlx90635_preprocess_temp_obj(object_raw,
+ ambient_new_raw,
+ ambient_old_raw, Gb);
+
+ *val = mlx90635_calc_temp_object(object, ambient, Ea, Eb, Ga, Fa, Fa_scale, Fb,
+ Ha, Hb, data->emissivity);
+ return 0;
+}
+
+static int mlx90635_calc_ambient(struct mlx90635_data *data, int *val)
+{
+ s16 ambient_new_raw, ambient_old_raw;
+ s16 PG, PO, Gb;
+ int ret;
+
+ ret = mlx90635_read_ee_ambient(data->regmap_ee, &PG, &PO, &Gb);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&data->lock);
+ if (data->powerstatus == MLX90635_PWR_STATUS_SLEEP_STEP) {
+ ret = mlx90635_perform_measurement_burst(data);
+ if (ret < 0)
+ goto read_ambient_unlock;
+ }
+
+ ret = mlx90635_read_ambient_raw(data->regmap, &ambient_new_raw,
+ &ambient_old_raw);
+read_ambient_unlock:
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ return ret;
+
+ *val = mlx90635_calc_temp_ambient(ambient_new_raw, ambient_old_raw,
+ PG, PO, Gb);
+ return ret;
+}
+
+static int mlx90635_get_refresh_rate(struct mlx90635_data *data,
+ unsigned int *refresh_rate)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(data->regmap, MLX90635_REG_CTRL1, &reg);
+ if (ret < 0)
+ return ret;
+
+ *refresh_rate = FIELD_GET(MLX90635_CTRL1_REFRESH_RATE_MASK, reg);
+
+ return 0;
+}
+
+static const struct {
+ int val;
+ int val2;
+} mlx90635_freqs[] = {
+ { 0, 200000 },
+ { 0, 500000 },
+ { 0, 900000 },
+ { 1, 700000 },
+ { 3, 0 },
+ { 4, 800000 },
+ { 6, 900000 },
+ { 8, 900000 }
+};
+
+/**
+ * mlx90635_pm_interaction_wakeup() - Measure time between user interactions to change powermode
+ * @data: pointer to mlx90635_data object containing interaction_ts information
+ *
+ * Switch to continuous mode when interaction is faster than MLX90635_MEAS_MAX_TIME. Update the
+ * interaction_ts for each function call with the jiffies to enable measurement between function
+ * calls. Initial value of the interaction_ts needs to be set before this function call.
+ */
+static int mlx90635_pm_interaction_wakeup(struct mlx90635_data *data)
+{
+ unsigned long now;
+ int ret;
+
+ now = jiffies;
+ if (time_in_range(now, data->interaction_ts,
+ data->interaction_ts +
+ msecs_to_jiffies(MLX90635_MEAS_MAX_TIME + 100))) {
+ ret = mlx90635_pwr_continuous(data);
+ if (ret < 0)
+ return ret;
+ }
+
+ data->interaction_ts = now;
+
+ return 0;
+}
+
+static int mlx90635_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ struct mlx90635_data *data = iio_priv(indio_dev);
+ int ret;
+ int cr;
+
+ pm_runtime_get_sync(&data->client->dev);
+ ret = mlx90635_pm_interaction_wakeup(data);
+ if (ret < 0)
+ goto mlx90635_read_raw_pm;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (channel->channel2) {
+ case IIO_MOD_TEMP_AMBIENT:
+ ret = mlx90635_calc_ambient(data, val);
+ if (ret < 0)
+ goto mlx90635_read_raw_pm;
+
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_MOD_TEMP_OBJECT:
+ ret = mlx90635_calc_object(data, val);
+ if (ret < 0)
+ goto mlx90635_read_raw_pm;
+
+ ret = IIO_VAL_INT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ break;
+ case IIO_CHAN_INFO_CALIBEMISSIVITY:
+ if (data->emissivity == 1000) {
+ *val = 1;
+ *val2 = 0;
+ } else {
+ *val = 0;
+ *val2 = data->emissivity * 1000;
+ }
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = mlx90635_get_refresh_rate(data, &cr);
+ if (ret < 0)
+ goto mlx90635_read_raw_pm;
+
+ *val = mlx90635_freqs[cr].val;
+ *val2 = mlx90635_freqs[cr].val2;
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+mlx90635_read_raw_pm:
+ pm_runtime_put_autosuspend(&data->client->dev);
+ return ret;
+}
+
+static int mlx90635_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int val,
+ int val2, long mask)
+{
+ struct mlx90635_data *data = iio_priv(indio_dev);
+ int ret;
+ int i;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_CALIBEMISSIVITY:
+ /* Confirm we are within 0 and 1.0 */
+ if (val < 0 || val2 < 0 || val > 1 ||
+ (val == 1 && val2 != 0))
+ return -EINVAL;
+ data->emissivity = val * 1000 + val2 / 1000;
+ return 0;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ for (i = 0; i < ARRAY_SIZE(mlx90635_freqs); i++) {
+ if (val == mlx90635_freqs[i].val &&
+ val2 == mlx90635_freqs[i].val2)
+ break;
+ }
+ if (i == ARRAY_SIZE(mlx90635_freqs))
+ return -EINVAL;
+
+ ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL1,
+ MLX90635_CTRL1_REFRESH_RATE_MASK, i);
+
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mlx90635_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = (int *)mlx90635_freqs;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = 2 * ARRAY_SIZE(mlx90635_freqs);
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_chan_spec mlx90635_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .modified = 1,
+ .channel2 = IIO_MOD_TEMP_AMBIENT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ },
+ {
+ .type = IIO_TEMP,
+ .modified = 1,
+ .channel2 = IIO_MOD_TEMP_OBJECT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_CALIBEMISSIVITY),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ },
+};
+
+static const struct iio_info mlx90635_info = {
+ .read_raw = mlx90635_read_raw,
+ .write_raw = mlx90635_write_raw,
+ .read_avail = mlx90635_read_avail,
+};
+
+static void mlx90635_sleep(void *_data)
+{
+ struct mlx90635_data *data = _data;
+
+ mlx90635_pwr_sleep_step(data);
+}
+
+static int mlx90635_suspend(struct mlx90635_data *data)
+{
+ return mlx90635_pwr_sleep_step(data);
+}
+
+static int mlx90635_wakeup(struct mlx90635_data *data)
+{
+ s16 Fb, Ga, Gb, Ha, Hb, PG, PO;
+ unsigned int dsp_version;
+ u32 Ea, Eb, Fa;
+ u16 Fa_scale;
+ int ret;
+
+ regcache_cache_bypass(data->regmap_ee, false);
+ regcache_cache_only(data->regmap_ee, false);
+ regcache_cache_only(data->regmap, false);
+
+ ret = mlx90635_pwr_continuous(data);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Switch to continuous mode failed\n");
+ return ret;
+ }
+ ret = regmap_write_bits(data->regmap, MLX90635_REG_EE,
+ MLX90635_EE_ACTIVE, MLX90635_EE_ACTIVE);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Powering EEPROM failed\n");
+ return ret;
+ }
+ usleep_range(MLX90635_TIMING_EE_ACTIVE_MIN, MLX90635_TIMING_EE_ACTIVE_MAX);
+
+ regcache_mark_dirty(data->regmap_ee);
+
+ ret = regcache_sync(data->regmap_ee);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Failed to sync cache: %d\n", ret);
+ return ret;
+ }
+
+ ret = mlx90635_read_ee_ambient(data->regmap_ee, &PG, &PO, &Gb);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Failed to read to cache Ambient coefficients EEPROM region: %d\n", ret);
+ return ret;
+ }
+
+ ret = mlx90635_read_ee_object(data->regmap_ee, &Ea, &Eb, &Fa, &Fb, &Ga, &Gb, &Ha, &Hb, &Fa_scale);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Failed to read to cache Object coefficients EEPROM region: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_read(data->regmap_ee, MLX90635_EE_VERSION, &dsp_version);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Failed to read to cache of EEPROM version: %d\n", ret);
+ return ret;
+ }
+
+ regcache_cache_only(data->regmap_ee, true);
+
+ return ret;
+}
+
+static void mlx90635_disable_regulator(void *_data)
+{
+ struct mlx90635_data *data = _data;
+ int ret;
+
+ ret = regulator_disable(data->regulator);
+ if (ret < 0)
+ dev_err(regmap_get_device(data->regmap),
+ "Failed to disable power regulator: %d\n", ret);
+}
+
+static int mlx90635_enable_regulator(struct mlx90635_data *data)
+{
+ int ret;
+
+ ret = regulator_enable(data->regulator);
+ if (ret < 0) {
+ dev_err(regmap_get_device(data->regmap), "Failed to enable power regulator!\n");
+ return ret;
+ }
+
+ mlx90635_reset_delay();
+
+ return ret;
+}
+
+static int mlx90635_probe(struct i2c_client *client)
+{
+ struct mlx90635_data *mlx90635;
+ struct iio_dev *indio_dev;
+ unsigned int dsp_version;
+ struct regmap *regmap;
+ struct regmap *regmap_ee;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mlx90635));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ regmap = devm_regmap_init_i2c(client, &mlx90635_regmap);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&client->dev, PTR_ERR(regmap),
+ "failed to allocate regmap\n");
+
+ regmap_ee = devm_regmap_init_i2c(client, &mlx90635_regmap_ee);
+ if (IS_ERR(regmap_ee))
+ return dev_err_probe(&client->dev, PTR_ERR(regmap_ee),
+ "failed to allocate EEPROM regmap\n");
+
+ mlx90635 = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ mlx90635->client = client;
+ mlx90635->regmap = regmap;
+ mlx90635->regmap_ee = regmap_ee;
+ mlx90635->powerstatus = MLX90635_PWR_STATUS_SLEEP_STEP;
+
+ mutex_init(&mlx90635->lock);
+ indio_dev->name = "mlx90635";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &mlx90635_info;
+ indio_dev->channels = mlx90635_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mlx90635_channels);
+
+ mlx90635->regulator = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(mlx90635->regulator))
+ return dev_err_probe(&client->dev, PTR_ERR(mlx90635->regulator),
+ "failed to get vdd regulator");
+
+ ret = mlx90635_enable_regulator(mlx90635);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(&client->dev, mlx90635_disable_regulator,
+ mlx90635);
+ if (ret < 0)
+ return ret;
+
+ ret = mlx90635_wakeup(mlx90635);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret, "wakeup failed\n");
+
+ ret = devm_add_action_or_reset(&client->dev, mlx90635_sleep, mlx90635);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(mlx90635->regmap_ee, MLX90635_EE_VERSION, &dsp_version);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret, "read of version failed\n");
+
+ dsp_version = dsp_version & MLX90635_VERSION_MASK;
+
+ if (FIELD_GET(MLX90635_DSP_FIXED, dsp_version)) {
+ if (MLX90635_DSP_VERSION(dsp_version) == MLX90635_ID_DSPv1) {
+ dev_dbg(&client->dev,
+ "Detected DSP v1 calibration %x\n", dsp_version);
+ } else {
+ dev_dbg(&client->dev,
+ "Detected Unknown EEPROM calibration %lx\n",
+ MLX90635_DSP_VERSION(dsp_version));
+ }
+ } else {
+ return dev_err_probe(&client->dev, -EPROTONOSUPPORT,
+ "Wrong fixed top bit %x (expected 0x8X0X)\n",
+ dsp_version);
+ }
+
+ mlx90635->emissivity = 1000;
+ mlx90635->interaction_ts = jiffies; /* Set initial value */
+
+ pm_runtime_get_noresume(&client->dev);
+ pm_runtime_set_active(&client->dev);
+
+ ret = devm_pm_runtime_enable(&client->dev);
+ if (ret)
+ return dev_err_probe(&client->dev, ret,
+ "failed to enable powermanagement\n");
+
+ pm_runtime_set_autosuspend_delay(&client->dev, MLX90635_SLEEP_DELAY_MS);
+ pm_runtime_use_autosuspend(&client->dev);
+ pm_runtime_put_autosuspend(&client->dev);
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id mlx90635_id[] = {
+ { .name = "mlx90635" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mlx90635_id);
+
+static const struct of_device_id mlx90635_of_match[] = {
+ { .compatible = "melexis,mlx90635" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mlx90635_of_match);
+
+static int mlx90635_pm_suspend(struct device *dev)
+{
+ struct mlx90635_data *data = iio_priv(dev_get_drvdata(dev));
+ int ret;
+
+ ret = mlx90635_suspend(data);
+ if (ret < 0)
+ return ret;
+
+ ret = regulator_disable(data->regulator);
+ if (ret < 0)
+ dev_err(regmap_get_device(data->regmap),
+ "Failed to disable power regulator: %d\n", ret);
+
+ return ret;
+}
+
+static int mlx90635_pm_resume(struct device *dev)
+{
+ struct mlx90635_data *data = iio_priv(dev_get_drvdata(dev));
+ int ret;
+
+ ret = mlx90635_enable_regulator(data);
+ if (ret < 0)
+ return ret;
+
+ return mlx90635_wakeup(data);
+}
+
+static int mlx90635_pm_runtime_suspend(struct device *dev)
+{
+ struct mlx90635_data *data = iio_priv(dev_get_drvdata(dev));
+
+ return mlx90635_pwr_sleep_step(data);
+}
+
+static const struct dev_pm_ops mlx90635_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(mlx90635_pm_suspend, mlx90635_pm_resume)
+ RUNTIME_PM_OPS(mlx90635_pm_runtime_suspend, NULL, NULL)
+};
+
+static struct i2c_driver mlx90635_driver = {
+ .driver = {
+ .name = "mlx90635",
+ .of_match_table = mlx90635_of_match,
+ .pm = pm_ptr(&mlx90635_pm_ops),
+ },
+ .probe = mlx90635_probe,
+ .id_table = mlx90635_id,
+};
+module_i2c_driver(mlx90635_driver);
+
+MODULE_AUTHOR("Crt Mori <cmo@melexis.com>");
+MODULE_DESCRIPTION("Melexis MLX90635 contactless Infra Red temperature sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/temperature/tmp006.c b/drivers/iio/temperature/tmp006.c
new file mode 100644
index 000000000..d9f6449ec
--- /dev/null
+++ b/drivers/iio/temperature/tmp006.c
@@ -0,0 +1,411 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tmp006.c - Support for TI TMP006 IR thermopile sensor
+ *
+ * Copyright (c) 2013 Peter Meerwald <pmeerw@pmeerw.net>
+ *
+ * Driver for the Texas Instruments I2C 16-bit IR thermopile sensor
+ *
+ * (7-bit I2C slave address 0x40, changeable via ADR pins)
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+#define TMP006_VOBJECT 0x00
+#define TMP006_TAMBIENT 0x01
+#define TMP006_CONFIG 0x02
+#define TMP006_MANUFACTURER_ID 0xfe
+#define TMP006_DEVICE_ID 0xff
+
+#define TMP006_TAMBIENT_SHIFT 2
+
+#define TMP006_CONFIG_RESET BIT(15)
+#define TMP006_CONFIG_DRDY_EN BIT(8)
+#define TMP006_CONFIG_DRDY BIT(7)
+
+#define TMP006_CONFIG_MOD_MASK GENMASK(14, 12)
+
+#define TMP006_CONFIG_CR_MASK GENMASK(11, 9)
+#define TMP006_CONFIG_CR_SHIFT 9
+
+#define TMP006_MANUFACTURER_MAGIC 0x5449
+#define TMP006_DEVICE_MAGIC 0x0067
+
+struct tmp006_data {
+ struct i2c_client *client;
+ u16 config;
+ struct iio_trigger *drdy_trig;
+};
+
+static int tmp006_read_measurement(struct tmp006_data *data, u8 reg)
+{
+ s32 ret;
+ int tries = 50;
+
+ while (tries-- > 0) {
+ ret = i2c_smbus_read_word_swapped(data->client,
+ TMP006_CONFIG);
+ if (ret < 0)
+ return ret;
+ if (ret & TMP006_CONFIG_DRDY)
+ break;
+ msleep(100);
+ }
+
+ if (tries < 0)
+ return -EIO;
+
+ return i2c_smbus_read_word_swapped(data->client, reg);
+}
+
+static const int tmp006_freqs[5][2] = { {4, 0}, {2, 0}, {1, 0},
+ {0, 500000}, {0, 250000} };
+
+static int tmp006_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ struct tmp006_data *data = iio_priv(indio_dev);
+ s32 ret;
+ int cr;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (channel->type == IIO_VOLTAGE) {
+ /* LSB is 156.25 nV */
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = tmp006_read_measurement(data, TMP006_VOBJECT);
+ iio_device_release_direct(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ *val = sign_extend32(ret, 15);
+ } else if (channel->type == IIO_TEMP) {
+ /* LSB is 0.03125 degrees Celsius */
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = tmp006_read_measurement(data, TMP006_TAMBIENT);
+ iio_device_release_direct(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ *val = sign_extend32(ret, 15) >> TMP006_TAMBIENT_SHIFT;
+ } else {
+ break;
+ }
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ if (channel->type == IIO_VOLTAGE) {
+ *val = 0;
+ *val2 = 156250;
+ } else if (channel->type == IIO_TEMP) {
+ *val = 31;
+ *val2 = 250000;
+ } else {
+ break;
+ }
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ cr = (data->config & TMP006_CONFIG_CR_MASK)
+ >> TMP006_CONFIG_CR_SHIFT;
+ *val = tmp006_freqs[cr][0];
+ *val2 = tmp006_freqs[cr][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int tmp006_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct tmp006_data *data = iio_priv(indio_dev);
+ int ret, i;
+
+ if (mask != IIO_CHAN_INFO_SAMP_FREQ)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(tmp006_freqs); i++)
+ if ((val == tmp006_freqs[i][0]) &&
+ (val2 == tmp006_freqs[i][1])) {
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ data->config &= ~TMP006_CONFIG_CR_MASK;
+ data->config |= i << TMP006_CONFIG_CR_SHIFT;
+
+ ret = i2c_smbus_write_word_swapped(data->client,
+ TMP006_CONFIG,
+ data->config);
+
+ iio_device_release_direct(indio_dev);
+ return ret;
+ }
+ return -EINVAL;
+}
+
+static IIO_CONST_ATTR(sampling_frequency_available, "4 2 1 0.5 0.25");
+
+static struct attribute *tmp006_attributes[] = {
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group tmp006_attribute_group = {
+ .attrs = tmp006_attributes,
+};
+
+static const struct iio_chan_spec tmp006_channels[] = {
+ {
+ .type = IIO_VOLTAGE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_BE,
+ }
+ },
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .scan_index = 1,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 14,
+ .storagebits = 16,
+ .shift = TMP006_TAMBIENT_SHIFT,
+ .endianness = IIO_BE,
+ }
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(2),
+};
+
+static const struct iio_info tmp006_info = {
+ .read_raw = tmp006_read_raw,
+ .write_raw = tmp006_write_raw,
+ .attrs = &tmp006_attribute_group,
+};
+
+static bool tmp006_check_identification(struct i2c_client *client)
+{
+ int mid, did;
+
+ mid = i2c_smbus_read_word_swapped(client, TMP006_MANUFACTURER_ID);
+ if (mid < 0)
+ return false;
+
+ did = i2c_smbus_read_word_swapped(client, TMP006_DEVICE_ID);
+ if (did < 0)
+ return false;
+
+ return mid == TMP006_MANUFACTURER_MAGIC && did == TMP006_DEVICE_MAGIC;
+}
+
+static int tmp006_power(struct device *dev, bool up)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct tmp006_data *data = iio_priv(indio_dev);
+
+ if (up)
+ data->config |= TMP006_CONFIG_MOD_MASK;
+ else
+ data->config &= ~TMP006_CONFIG_MOD_MASK;
+
+ return i2c_smbus_write_word_swapped(data->client, TMP006_CONFIG,
+ data->config);
+}
+
+static void tmp006_powerdown_cleanup(void *dev)
+{
+ tmp006_power(dev, false);
+}
+
+static irqreturn_t tmp006_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct tmp006_data *data = iio_priv(indio_dev);
+ struct {
+ s16 channels[2];
+ aligned_s64 ts;
+ } scan = { };
+ s32 ret;
+
+ ret = i2c_smbus_read_word_data(data->client, TMP006_VOBJECT);
+ if (ret < 0)
+ goto err;
+ scan.channels[0] = ret;
+
+ ret = i2c_smbus_read_word_data(data->client, TMP006_TAMBIENT);
+ if (ret < 0)
+ goto err;
+ scan.channels[1] = ret;
+
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
+ iio_get_time_ns(indio_dev));
+err:
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
+static int tmp006_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct tmp006_data *data = iio_priv(indio_dev);
+
+ if (state)
+ data->config |= TMP006_CONFIG_DRDY_EN;
+ else
+ data->config &= ~TMP006_CONFIG_DRDY_EN;
+
+ return i2c_smbus_write_word_swapped(data->client, TMP006_CONFIG,
+ data->config);
+}
+
+static const struct iio_trigger_ops tmp006_trigger_ops = {
+ .set_trigger_state = tmp006_set_trigger_state,
+};
+
+static const unsigned long tmp006_scan_masks[] = { 0x3, 0 };
+
+static int tmp006_probe(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev;
+ struct tmp006_data *data;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA))
+ return -EOPNOTSUPP;
+
+ if (!tmp006_check_identification(client)) {
+ dev_err(&client->dev, "no TMP006 sensor\n");
+ return -ENODEV;
+ }
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+
+ indio_dev->name = dev_name(&client->dev);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &tmp006_info;
+
+ indio_dev->channels = tmp006_channels;
+ indio_dev->num_channels = ARRAY_SIZE(tmp006_channels);
+ indio_dev->available_scan_masks = tmp006_scan_masks;
+
+ ret = i2c_smbus_read_word_swapped(data->client, TMP006_CONFIG);
+ if (ret < 0)
+ return ret;
+ data->config = ret;
+
+ if ((ret & TMP006_CONFIG_MOD_MASK) != TMP006_CONFIG_MOD_MASK) {
+ ret = tmp006_power(&client->dev, true);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(&client->dev, tmp006_powerdown_cleanup,
+ &client->dev);
+ if (ret < 0)
+ return ret;
+
+ if (client->irq > 0) {
+ data->drdy_trig = devm_iio_trigger_alloc(&client->dev,
+ "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!data->drdy_trig)
+ return -ENOMEM;
+
+ data->drdy_trig->ops = &tmp006_trigger_ops;
+ iio_trigger_set_drvdata(data->drdy_trig, indio_dev);
+ ret = devm_iio_trigger_register(&client->dev, data->drdy_trig);
+ if (ret)
+ return ret;
+
+ indio_dev->trig = iio_trigger_get(data->drdy_trig);
+
+ ret = devm_request_irq(&client->dev, client->irq,
+ iio_trigger_generic_data_rdy_poll,
+ IRQF_NO_THREAD, "tmp006_irq",
+ data->drdy_trig);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL,
+ tmp006_trigger_handler, NULL);
+ if (ret < 0)
+ return ret;
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static int tmp006_suspend(struct device *dev)
+{
+ return tmp006_power(dev, false);
+}
+
+static int tmp006_resume(struct device *dev)
+{
+ return tmp006_power(dev, true);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(tmp006_pm_ops, tmp006_suspend, tmp006_resume);
+
+static const struct of_device_id tmp006_of_match[] = {
+ { .compatible = "ti,tmp006" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tmp006_of_match);
+
+static const struct i2c_device_id tmp006_id[] = {
+ { .name = "tmp006" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tmp006_id);
+
+static struct i2c_driver tmp006_driver = {
+ .driver = {
+ .name = "tmp006",
+ .of_match_table = tmp006_of_match,
+ .pm = pm_sleep_ptr(&tmp006_pm_ops),
+ },
+ .probe = tmp006_probe,
+ .id_table = tmp006_id,
+};
+module_i2c_driver(tmp006_driver);
+
+MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("TI TMP006 IR thermopile sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/temperature/tmp007.c b/drivers/iio/temperature/tmp007.c
new file mode 100644
index 000000000..4e743424f
--- /dev/null
+++ b/drivers/iio/temperature/tmp007.c
@@ -0,0 +1,581 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tmp007.c - Support for TI TMP007 IR thermopile sensor with integrated math engine
+ *
+ * Copyright (c) 2017 Manivannan Sadhasivam <manivannanece23@gmail.com>
+ *
+ * Driver for the Texas Instruments I2C 16-bit IR thermopile sensor
+ *
+ * (7-bit I2C slave address (0x40 - 0x47), changeable via ADR pins)
+ *
+ * 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
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+
+#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_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
+#define TMP007_DEVICE_MAGIC 0x0078
+
+#define TMP007_TEMP_SHIFT 2
+
+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},
+ {0, 500000}, {0, 250000} };
+
+static int tmp007_read_temperature(struct tmp007_data *data, u8 reg)
+{
+ s32 ret;
+ int tries = 50;
+
+ while (tries-- > 0) {
+ ret = i2c_smbus_read_word_swapped(data->client,
+ TMP007_STATUS);
+ if (ret < 0)
+ return ret;
+ if ((ret & TMP007_STATUS_CONV_READY) &&
+ !(ret & TMP007_STATUS_DATA_VALID))
+ break;
+ msleep(100);
+ }
+
+ if (tries < 0)
+ return -EIO;
+
+ return i2c_smbus_read_word_swapped(data->client, reg);
+}
+
+static int tmp007_powerdown(struct tmp007_data *data)
+{
+ return i2c_smbus_write_word_swapped(data->client, TMP007_CONFIG,
+ data->config & ~TMP007_CONFIG_CONV_EN);
+}
+
+static int tmp007_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ struct tmp007_data *data = iio_priv(indio_dev);
+ s32 ret;
+ int conv_rate;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ switch (channel->channel2) {
+ case IIO_MOD_TEMP_AMBIENT: /* LSB: 0.03125 degree Celsius */
+ ret = i2c_smbus_read_word_swapped(data->client, TMP007_TDIE);
+ if (ret < 0)
+ return ret;
+ break;
+ case IIO_MOD_TEMP_OBJECT:
+ ret = tmp007_read_temperature(data, TMP007_TOBJECT);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *val = sign_extend32(ret, 15) >> TMP007_TEMP_SHIFT;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 31;
+ *val2 = 250000;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ conv_rate = (data->config & TMP007_CONFIG_CR_MASK)
+ >> TMP007_CONFIG_CR_SHIFT;
+ *val = tmp007_avgs[conv_rate][0];
+ *val2 = tmp007_avgs[conv_rate][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int tmp007_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int val,
+ int val2, long mask)
+{
+ struct tmp007_data *data = iio_priv(indio_dev);
+ int i;
+ u16 tmp;
+
+ if (mask == IIO_CHAN_INFO_SAMP_FREQ) {
+ for (i = 0; i < ARRAY_SIZE(tmp007_avgs); i++) {
+ if ((val == tmp007_avgs[i][0]) &&
+ (val2 == tmp007_avgs[i][1])) {
+ tmp = data->config & ~TMP007_CONFIG_CR_MASK;
+ tmp |= (i << TMP007_CONFIG_CR_SHIFT);
+
+ return i2c_smbus_write_word_swapped(data->client,
+ TMP007_CONFIG,
+ data->config = tmp);
+ }
+ }
+ }
+
+ 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, bool 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[] = {
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ NULL
+};
+
+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,
+ .modified = 1,
+ .channel2 = IIO_MOD_TEMP_AMBIENT,
+ .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,
+ .modified = 1,
+ .channel2 = IIO_MOD_TEMP_OBJECT,
+ .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,
+};
+
+static bool tmp007_identify(struct i2c_client *client)
+{
+ int manf_id, dev_id;
+
+ manf_id = i2c_smbus_read_word_swapped(client, TMP007_MANUFACTURER_ID);
+ if (manf_id < 0)
+ return false;
+
+ dev_id = i2c_smbus_read_word_swapped(client, TMP007_DEVICE_ID);
+ if (dev_id < 0)
+ return false;
+
+ return (manf_id == TMP007_MANUFACTURER_MAGIC && dev_id == TMP007_DEVICE_MAGIC);
+}
+
+static void tmp007_powerdown_action_cb(void *priv)
+{
+ struct tmp007_data *data = priv;
+
+ tmp007_powerdown(data);
+}
+
+static int tmp007_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *tmp007_id = i2c_client_get_device_id(client);
+ struct tmp007_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA))
+ return -EOPNOTSUPP;
+
+ if (!tmp007_identify(client)) {
+ dev_err(&client->dev, "TMP007 not found\n");
+ return -ENODEV;
+ }
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+ mutex_init(&data->lock);
+
+ indio_dev->name = "tmp007";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &tmp007_info;
+
+ indio_dev->channels = tmp007_channels;
+ indio_dev->num_channels = ARRAY_SIZE(tmp007_channels);
+
+ /*
+ * Set Configuration register:
+ * 1. Conversion ON
+ * 2. ALERT enable
+ * 3. Transient correction enable
+ */
+
+ ret = i2c_smbus_read_word_swapped(data->client, TMP007_CONFIG);
+ if (ret < 0)
+ return ret;
+
+ data->config = ret;
+ 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);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(&client->dev, tmp007_powerdown_action_cb, data);
+ if (ret)
+ 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. 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)
+ return ret;
+
+ 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, data->status_mask);
+ if (ret < 0)
+ return ret;
+
+ 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)
+ return ret;
+ }
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static int tmp007_suspend(struct device *dev)
+{
+ struct tmp007_data *data = iio_priv(i2c_get_clientdata(
+ to_i2c_client(dev)));
+
+ return tmp007_powerdown(data);
+}
+
+static int tmp007_resume(struct device *dev)
+{
+ struct tmp007_data *data = iio_priv(i2c_get_clientdata(
+ to_i2c_client(dev)));
+
+ return i2c_smbus_write_word_swapped(data->client, TMP007_CONFIG,
+ data->config | TMP007_CONFIG_CONV_EN);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(tmp007_pm_ops, tmp007_suspend, tmp007_resume);
+
+static const struct of_device_id tmp007_of_match[] = {
+ { .compatible = "ti,tmp007", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tmp007_of_match);
+
+static const struct i2c_device_id tmp007_id[] = {
+ { .name = "tmp007" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tmp007_id);
+
+static struct i2c_driver tmp007_driver = {
+ .driver = {
+ .name = "tmp007",
+ .of_match_table = tmp007_of_match,
+ .pm = pm_sleep_ptr(&tmp007_pm_ops),
+ },
+ .probe = tmp007_probe,
+ .id_table = tmp007_id,
+};
+module_i2c_driver(tmp007_driver);
+
+MODULE_AUTHOR("Manivannan Sadhasivam <manivannanece23@gmail.com>");
+MODULE_DESCRIPTION("TI TMP007 IR thermopile sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/temperature/tmp117.c b/drivers/iio/temperature/tmp117.c
new file mode 100644
index 000000000..74cb8d62b
--- /dev/null
+++ b/drivers/iio/temperature/tmp117.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Digital temperature sensor with integrated Non-volatile memory
+ * Copyright (c) 2021 Puranjay Mohan <puranjay12@gmail.com>
+ *
+ * Driver for the Texas Instruments TMP117 Temperature Sensor
+ * (7-bit I2C slave address (0x48 - 0x4B), changeable via ADD pins)
+ *
+ * Note: This driver assumes that the sensor has been calibrated beforehand.
+ */
+
+#include <linux/array_size.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/iio/iio.h>
+
+#define TMP117_REG_TEMP 0x0
+#define TMP117_REG_CFGR 0x1
+#define TMP117_REG_HIGH_LIM 0x2
+#define TMP117_REG_LOW_LIM 0x3
+#define TMP117_REG_EEPROM_UL 0x4
+#define TMP117_REG_EEPROM1 0x5
+#define TMP117_REG_EEPROM2 0x6
+#define TMP117_REG_TEMP_OFFSET 0x7
+#define TMP117_REG_EEPROM3 0x8
+#define TMP117_REG_DEVICE_ID 0xF
+
+#define TMP117_RESOLUTION_10UC 78125
+#define MICRODEGREE_PER_10MILLIDEGREE 10000
+
+#define TMP116_DEVICE_ID 0x1116
+#define TMP117_DEVICE_ID 0x0117
+#define TMP119_DEVICE_ID 0x2117
+
+struct tmp117_data {
+ struct i2c_client *client;
+ s16 calibbias;
+};
+
+struct tmp11x_info {
+ const char *name;
+ struct iio_chan_spec const *channels;
+ int num_channels;
+};
+
+static int tmp117_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ struct tmp117_data *data = iio_priv(indio_dev);
+ s32 ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = i2c_smbus_read_word_swapped(data->client,
+ TMP117_REG_TEMP);
+ if (ret < 0)
+ return ret;
+ *val = sign_extend32(ret, 15);
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_CALIBBIAS:
+ ret = i2c_smbus_read_word_swapped(data->client,
+ TMP117_REG_TEMP_OFFSET);
+ if (ret < 0)
+ return ret;
+ *val = sign_extend32(ret, 15);
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ /*
+ * Conversion from 10s of uC to mC
+ * as IIO reports temperature in mC
+ */
+ *val = TMP117_RESOLUTION_10UC / MICRODEGREE_PER_10MILLIDEGREE;
+ *val2 = (TMP117_RESOLUTION_10UC %
+ MICRODEGREE_PER_10MILLIDEGREE) * 100;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int tmp117_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec
+ const *channel, int val, int val2, long mask)
+{
+ struct tmp117_data *data = iio_priv(indio_dev);
+ s16 off;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_CALIBBIAS:
+ off = clamp_t(int, val, S16_MIN, S16_MAX);
+ if (off == data->calibbias)
+ return 0;
+ data->calibbias = off;
+ return i2c_smbus_write_word_swapped(data->client,
+ TMP117_REG_TEMP_OFFSET, off);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_chan_spec tmp117_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+};
+
+static const struct iio_chan_spec tmp116_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ },
+};
+
+static const struct tmp11x_info tmp116_channels_info = {
+ .name = "tmp116",
+ .channels = tmp116_channels,
+ .num_channels = ARRAY_SIZE(tmp116_channels)
+};
+
+static const struct tmp11x_info tmp117_channels_info = {
+ .name = "tmp117",
+ .channels = tmp117_channels,
+ .num_channels = ARRAY_SIZE(tmp117_channels)
+};
+
+static const struct tmp11x_info tmp119_channels_info = {
+ .name = "tmp119",
+ .channels = tmp117_channels,
+ .num_channels = ARRAY_SIZE(tmp117_channels)
+};
+
+static const struct iio_info tmp117_info = {
+ .read_raw = tmp117_read_raw,
+ .write_raw = tmp117_write_raw,
+};
+
+static int tmp117_probe(struct i2c_client *client)
+{
+ const struct tmp11x_info *match_data;
+ struct tmp117_data *data;
+ struct iio_dev *indio_dev;
+ int dev_id;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA))
+ return -EOPNOTSUPP;
+
+ ret = devm_regulator_get_enable(&client->dev, "vcc");
+ if (ret)
+ return ret;
+
+ fsleep(1500);
+
+ dev_id = i2c_smbus_read_word_swapped(client, TMP117_REG_DEVICE_ID);
+ if (dev_id < 0)
+ return dev_id;
+
+ switch (dev_id) {
+ case TMP116_DEVICE_ID:
+ match_data = &tmp116_channels_info;
+ break;
+ case TMP117_DEVICE_ID:
+ match_data = &tmp117_channels_info;
+ break;
+ case TMP119_DEVICE_ID:
+ match_data = &tmp119_channels_info;
+ break;
+ default:
+ dev_info(&client->dev,
+ "Unknown device id (0x%x), use fallback compatible\n",
+ dev_id);
+ match_data = i2c_get_match_data(client);
+ }
+
+ if (!match_data)
+ return dev_err_probe(&client->dev, -ENODEV,
+ "Failed to identify unsupported device\n");
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ data->calibbias = 0;
+
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &tmp117_info;
+ indio_dev->channels = match_data->channels;
+ indio_dev->num_channels = match_data->num_channels;
+ indio_dev->name = match_data->name;
+
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct of_device_id tmp117_of_match[] = {
+ { .compatible = "ti,tmp116", .data = &tmp116_channels_info },
+ { .compatible = "ti,tmp117", .data = &tmp117_channels_info },
+ { .compatible = "ti,tmp119", .data = &tmp119_channels_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tmp117_of_match);
+
+static const struct i2c_device_id tmp117_id[] = {
+ { .name = "tmp116", .driver_data = (kernel_ulong_t)&tmp116_channels_info },
+ { .name = "tmp117", .driver_data = (kernel_ulong_t)&tmp117_channels_info },
+ { .name = "tmp119", .driver_data = (kernel_ulong_t)&tmp119_channels_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tmp117_id);
+
+static struct i2c_driver tmp117_driver = {
+ .driver = {
+ .name = "tmp117",
+ .of_match_table = tmp117_of_match,
+ },
+ .probe = tmp117_probe,
+ .id_table = tmp117_id,
+};
+module_i2c_driver(tmp117_driver);
+
+MODULE_AUTHOR("Puranjay Mohan <puranjay12@gmail.com>");
+MODULE_DESCRIPTION("TI TMP117 Temperature sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/temperature/tsys01.c b/drivers/iio/temperature/tsys01.c
new file mode 100644
index 000000000..28b3ce022
--- /dev/null
+++ b/drivers/iio/temperature/tsys01.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tsys01.c - Support for Measurement-Specialties tsys01 temperature sensor
+ *
+ * Copyright (c) 2015 Measurement-Specialties
+ *
+ * Datasheet:
+ * http://www.meas-spec.com/downloads/TSYS01_Digital_Temperature_Sensor.pdf
+ */
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/stat.h>
+#include "../common/ms_sensors/ms_sensors_i2c.h"
+
+/* TSYS01 Commands */
+#define TSYS01_RESET 0x1E
+#define TSYS01_CONVERSION_START 0x48
+#define TSYS01_ADC_READ 0x00
+#define TSYS01_PROM_READ 0xA0
+
+#define TSYS01_PROM_WORDS_NB 8
+
+struct tsys01_dev {
+ void *client;
+ struct mutex lock; /* lock during conversion */
+
+ int (*reset)(void *cli, u8 cmd, unsigned int delay);
+ int (*convert_and_read)(void *cli, u8 conv, u8 rd,
+ unsigned int delay, u32 *adc);
+ int (*read_prom_word)(void *cli, int cmd, u16 *word);
+
+ u16 prom[TSYS01_PROM_WORDS_NB];
+};
+
+/* Multiplication coefficients for temperature computation */
+static const int coeff_mul[] = { -1500000, 1000000, -2000000,
+ 4000000, -2000000 };
+
+static int tsys01_read_temperature(struct iio_dev *indio_dev,
+ s32 *temperature)
+{
+ int ret, i;
+ u32 adc;
+ s64 temp = 0;
+ struct tsys01_dev *dev_data = iio_priv(indio_dev);
+
+ mutex_lock(&dev_data->lock);
+ ret = dev_data->convert_and_read(dev_data->client,
+ TSYS01_CONVERSION_START,
+ TSYS01_ADC_READ, 9000, &adc);
+ mutex_unlock(&dev_data->lock);
+ if (ret)
+ return ret;
+
+ adc >>= 8;
+
+ /* Temperature algorithm */
+ for (i = 4; i > 0; i--) {
+ temp += coeff_mul[i] *
+ (s64)dev_data->prom[5 - i];
+ temp *= (s64)adc;
+ temp = div64_s64(temp, 100000);
+ }
+ temp *= 10;
+ temp += coeff_mul[0] * (s64)dev_data->prom[5];
+ temp = div64_s64(temp, 100000);
+
+ *temperature = temp;
+
+ return 0;
+}
+
+static int tsys01_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ int ret;
+ s32 temperature;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (channel->type) {
+ case IIO_TEMP: /* in milli °C */
+ ret = tsys01_read_temperature(indio_dev, &temperature);
+ if (ret)
+ return ret;
+ *val = temperature;
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_chan_spec tsys01_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_PROCESSED),
+ }
+};
+
+static const struct iio_info tsys01_info = {
+ .read_raw = tsys01_read_raw,
+};
+
+static bool tsys01_crc_valid(u16 *n_prom)
+{
+ u8 cnt;
+ u8 sum = 0;
+
+ for (cnt = 0; cnt < TSYS01_PROM_WORDS_NB; cnt++)
+ sum += ((n_prom[cnt] >> 8) + (n_prom[cnt] & 0xFF));
+
+ return (sum == 0);
+}
+
+static int tsys01_read_prom(struct iio_dev *indio_dev)
+{
+ int i, ret;
+ struct tsys01_dev *dev_data = iio_priv(indio_dev);
+ char buf[7 * TSYS01_PROM_WORDS_NB + 1];
+ char *ptr = buf;
+
+ for (i = 0; i < TSYS01_PROM_WORDS_NB; i++) {
+ ret = dev_data->read_prom_word(dev_data->client,
+ TSYS01_PROM_READ + (i << 1),
+ &dev_data->prom[i]);
+ if (ret)
+ return ret;
+
+ ret = sprintf(ptr, "0x%04x ", dev_data->prom[i]);
+ ptr += ret;
+ }
+
+ if (!tsys01_crc_valid(dev_data->prom)) {
+ dev_err(&indio_dev->dev, "prom crc check error\n");
+ return -ENODEV;
+ }
+ *ptr = 0;
+ dev_info(&indio_dev->dev, "PROM coefficients : %s\n", buf);
+
+ return 0;
+}
+
+static int tsys01_probe(struct iio_dev *indio_dev, struct device *dev)
+{
+ int ret;
+ struct tsys01_dev *dev_data = iio_priv(indio_dev);
+
+ mutex_init(&dev_data->lock);
+
+ indio_dev->info = &tsys01_info;
+ indio_dev->name = dev->driver->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = tsys01_channels;
+ indio_dev->num_channels = ARRAY_SIZE(tsys01_channels);
+
+ ret = dev_data->reset(dev_data->client, TSYS01_RESET, 3000);
+ if (ret)
+ return ret;
+
+ ret = tsys01_read_prom(indio_dev);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static int tsys01_i2c_probe(struct i2c_client *client)
+{
+ struct tsys01_dev *dev_data;
+ struct iio_dev *indio_dev;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WORD_DATA |
+ I2C_FUNC_SMBUS_WRITE_BYTE |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+ dev_err(&client->dev,
+ "Adapter does not support some i2c transaction\n");
+ return -EOPNOTSUPP;
+ }
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ dev_data = iio_priv(indio_dev);
+ dev_data->client = client;
+ dev_data->reset = ms_sensors_reset;
+ dev_data->read_prom_word = ms_sensors_read_prom_word;
+ dev_data->convert_and_read = ms_sensors_convert_and_read;
+
+ i2c_set_clientdata(client, indio_dev);
+
+ return tsys01_probe(indio_dev, &client->dev);
+}
+
+static const struct i2c_device_id tsys01_id[] = {
+ { .name = "tsys01" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tsys01_id);
+
+static const struct of_device_id tsys01_of_match[] = {
+ { .compatible = "meas,tsys01", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tsys01_of_match);
+
+static struct i2c_driver tsys01_driver = {
+ .probe = tsys01_i2c_probe,
+ .id_table = tsys01_id,
+ .driver = {
+ .name = "tsys01",
+ .of_match_table = tsys01_of_match,
+ },
+};
+
+module_i2c_driver(tsys01_driver);
+
+MODULE_DESCRIPTION("Measurement-Specialties tsys01 temperature driver");
+MODULE_AUTHOR("William Markezana <william.markezana@meas-spec.com>");
+MODULE_AUTHOR("Ludovic Tancerel <ludovic.tancerel@maplehightech.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_MEAS_SPEC_SENSORS");
diff --git a/drivers/iio/temperature/tsys02d.c b/drivers/iio/temperature/tsys02d.c
new file mode 100644
index 000000000..3ef723474
--- /dev/null
+++ b/drivers/iio/temperature/tsys02d.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tsys02d.c - Support for Measurement-Specialties tsys02d temperature sensor
+ *
+ * Copyright (c) 2015 Measurement-Specialties
+ *
+ * (7-bit I2C slave address 0x40)
+ *
+ * Datasheet:
+ * http://www.meas-spec.com/downloads/Digital_Sensor_TSYS02D.pdf
+ */
+
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/stat.h>
+#include <linux/module.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#include "../common/ms_sensors/ms_sensors_i2c.h"
+
+#define TSYS02D_RESET 0xFE
+
+static const int tsys02d_samp_freq[4] = { 20, 40, 70, 140 };
+/* String copy of the above const for readability purpose */
+static const char tsys02d_show_samp_freq[] = "20 40 70 140";
+
+static int tsys02d_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ int ret;
+ s32 temperature;
+ struct ms_ht_dev *dev_data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (channel->type) {
+ case IIO_TEMP: /* in milli °C */
+ ret = ms_sensors_ht_read_temperature(dev_data,
+ &temperature);
+ if (ret)
+ return ret;
+ *val = temperature;
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = tsys02d_samp_freq[dev_data->res_index];
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int tsys02d_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct ms_ht_dev *dev_data = iio_priv(indio_dev);
+ int i, ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ i = ARRAY_SIZE(tsys02d_samp_freq);
+ while (i-- > 0)
+ if (val == tsys02d_samp_freq[i])
+ break;
+ if (i < 0)
+ return -EINVAL;
+ mutex_lock(&dev_data->lock);
+ dev_data->res_index = i;
+ ret = ms_sensors_write_resolution(dev_data, i);
+ mutex_unlock(&dev_data->lock);
+
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_chan_spec tsys02d_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_PROCESSED),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ }
+};
+
+static ssize_t tsys02_read_battery_low(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct ms_ht_dev *dev_data = iio_priv(indio_dev);
+
+ return ms_sensors_show_battery_low(dev_data, buf);
+}
+
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(tsys02d_show_samp_freq);
+static IIO_DEVICE_ATTR(battery_low, S_IRUGO,
+ tsys02_read_battery_low, NULL, 0);
+
+static struct attribute *tsys02d_attributes[] = {
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ &iio_dev_attr_battery_low.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group tsys02d_attribute_group = {
+ .attrs = tsys02d_attributes,
+};
+
+static const struct iio_info tsys02d_info = {
+ .read_raw = tsys02d_read_raw,
+ .write_raw = tsys02d_write_raw,
+ .attrs = &tsys02d_attribute_group,
+};
+
+static int tsys02d_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct ms_ht_dev *dev_data;
+ struct iio_dev *indio_dev;
+ int ret;
+ u64 serial_number;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
+ I2C_FUNC_SMBUS_WRITE_BYTE |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+ dev_err(&client->dev,
+ "Adapter does not support some i2c transaction\n");
+ return -EOPNOTSUPP;
+ }
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ dev_data = iio_priv(indio_dev);
+ dev_data->client = client;
+ dev_data->res_index = 0;
+ mutex_init(&dev_data->lock);
+
+ indio_dev->info = &tsys02d_info;
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = tsys02d_channels;
+ indio_dev->num_channels = ARRAY_SIZE(tsys02d_channels);
+
+ i2c_set_clientdata(client, indio_dev);
+
+ ret = ms_sensors_reset(client, TSYS02D_RESET, 15000);
+ if (ret)
+ return ret;
+
+ ret = ms_sensors_read_serial(client, &serial_number);
+ if (ret)
+ return ret;
+ dev_info(&client->dev, "Serial number : %llx", serial_number);
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id tsys02d_id[] = {
+ { .name = "tsys02d" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tsys02d_id);
+
+static struct i2c_driver tsys02d_driver = {
+ .probe = tsys02d_probe,
+ .id_table = tsys02d_id,
+ .driver = {
+ .name = "tsys02d",
+ },
+};
+
+module_i2c_driver(tsys02d_driver);
+
+MODULE_DESCRIPTION("Measurement-Specialties tsys02d temperature driver");
+MODULE_AUTHOR("William Markezana <william.markezana@meas-spec.com>");
+MODULE_AUTHOR("Ludovic Tancerel <ludovic.tancerel@maplehightech.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_MEAS_SPEC_SENSORS");