Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+187
View File
@@ -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
@@ -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
@@ -0,0 +1,293 @@
// 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,
unsigned int 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,
unsigned int 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,
unsigned int 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(&indio_dev->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 = devm_iio_device_register(indio_dev->dev.parent, 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);
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");
@@ -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");
File diff suppressed because it is too large Load Diff
@@ -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");
@@ -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[] = {
{ "max31856", 0 },
{ }
};
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");
@@ -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[] = {
{ "max31865", 0 },
{ }
};
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");
@@ -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[] = {
{"max6675", MAX6675},
{"max31855", MAX31855},
{"max31855k", MAX31855K},
{"max31855j", MAX31855J},
{"max31855n", MAX31855N},
{"max31855s", MAX31855S},
{"max31855t", MAX31855T},
{"max31855e", MAX31855E},
{"max31855r", 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");
@@ -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");
@@ -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");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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");
@@ -0,0 +1,583 @@
// 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) {
dev_err(&client->dev, "irq request error %d\n", -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");
@@ -0,0 +1,230 @@
// 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/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
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 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;
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 },
{ }
};
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 },
{ }
};
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");
@@ -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");
@@ -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");