This patch adds a driver for NXP PCT2075 temperature sensors, connected via I2C. The datasheet for this part is here: https://www.nxp.com/docs/en/data-sheet/PCT2075.pdf All hardware configuration options are accessible via DT properites. Signed-off-by: Daniel Mack <daniel@xxxxxxxxxx> --- drivers/iio/temperature/Kconfig | 11 ++ drivers/iio/temperature/Makefile | 1 + drivers/iio/temperature/pct2075.c | 307 ++++++++++++++++++++++++++++++ 3 files changed, 319 insertions(+) create mode 100644 drivers/iio/temperature/pct2075.c diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig index c185cbee25c7..20aeb4c764b7 100644 --- a/drivers/iio/temperature/Kconfig +++ b/drivers/iio/temperature/Kconfig @@ -55,6 +55,17 @@ config MLX90632 This driver can also be built as a module. If so, the module will be called mlx90632. +config PCT2075 + tristate "NXP PCT2075 temperature sensor" + depends on I2C + help + If you say yes here you get support for the NXP + NCP2075 I2C connected Fm+ digital temperature sensor and + thermal watchdog. + + This driver can also be built as a module. If so, the module will + be called pct2075. + config TMP006 tristate "TMP006 infrared thermopile sensor" depends on I2C diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile index baca4776ca0d..7fad51b8be4f 100644 --- a/drivers/iio/temperature/Makefile +++ b/drivers/iio/temperature/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o obj-$(CONFIG_MAX31856) += max31856.o obj-$(CONFIG_MLX90614) += mlx90614.o obj-$(CONFIG_MLX90632) += mlx90632.o +obj-$(CONFIG_PCT2075) += pct2075.o obj-$(CONFIG_TMP006) += tmp006.o obj-$(CONFIG_TMP007) += tmp007.o obj-$(CONFIG_TSYS01) += tsys01.o diff --git a/drivers/iio/temperature/pct2075.c b/drivers/iio/temperature/pct2075.c new file mode 100644 index 000000000000..e2a092079905 --- /dev/null +++ b/drivers/iio/temperature/pct2075.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> + +#define PCT2075_REG_CONF 1 +#define PCT2075_CONF_OS_F_QUEUE(val) (((val) & 0x3) << 3) +#define PCT2075_CONF_OS_ACTIVE_HIGH BIT(2) +#define PCT2075_CONF_OS_COMP_INT BIT(1) +#define PCT2075_CONF_SHUTDOWN BIT(0) + +#define PCT2075_REG_TEMP 0 +#define PCT2075_REG_THYST 2 +#define PCT2075_REG_TOS 3 +#define PCT2075_REG_TIDLE 4 + +struct pct2075_data { + struct i2c_client *client; + struct regulator *regulator; + u8 reg_conf; + u8 reg_tidle; + u16 reg_thyst; + u16 reg_tos; +}; + +static const struct iio_chan_spec pct2075_channel = { + .type = IIO_TEMP, + .channel = IIO_MOD_TEMP_AMBIENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), +}; + +static int pct2075_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct pct2075_data *pct2075 = iio_priv(indio_dev); + int ret, v; + + ret = i2c_smbus_read_word_swapped(pct2075->client, + PCT2075_REG_TEMP); + if (ret < 0) + return ret; + + v = sign_extend32(ret >> 5, 10) * 125; + *val = v / 1000; + *val2 = (v % 1000) * 1000; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int pct2075_sync(struct pct2075_data *pct2075) +{ + struct i2c_client *client = pct2075->client; + struct device *dev = &client->dev; + int ret; + + ret = i2c_smbus_write_byte_data(client, PCT2075_REG_CONF, + pct2075->reg_conf); + if (ret < 0) { + dev_err(dev, "Cannot write CONF register: %d\n", ret); + return ret; + } + + ret = i2c_smbus_write_byte_data(client, PCT2075_REG_TIDLE, + pct2075->reg_tidle); + if (ret < 0) { + dev_err(dev, "Cannot write TIDLE register: %d\n", ret); + return ret; + } + + ret = i2c_smbus_write_word_swapped(client, PCT2075_REG_TOS, + pct2075->reg_tos); + if (ret < 0) { + dev_err(dev, "Cannot write TOS register: %d\n", ret); + return ret; + } + + ret = i2c_smbus_write_word_swapped(client, PCT2075_REG_THYST, + pct2075->reg_thyst); + if (ret < 0) { + dev_err(dev, "Cannot write THYST register: %d\n", ret); + return ret; + } + + return 0; +} + +static void pct2075_of_parse_temperature(struct device *dev, + u16 *out, const char *name) +{ + int ret; + s32 tmp; + + ret = of_property_read_s32(dev->of_node, name, &tmp); + if (ret != 0) + return; + + if (tmp < -55000 || tmp > 125000 || tmp % 500 != 0) { + dev_err(dev, "Unsupported value for %s", name); + return; + } + + *out = ((u16) (tmp / 500)) << 7; +} + +static const struct iio_info pct2075_info = { + .read_raw = pct2075_read_raw, +}; + +static int pct2075_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct pct2075_data *pct2075; + struct iio_dev *indio_dev; + u32 tmp; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*pct2075)); + if (!indio_dev) { + dev_err(&client->dev, "Failed to allocate device\n"); + return -ENOMEM; + } + + pct2075 = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + pct2075->client = client; + + indio_dev->dev.parent = dev; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &pct2075_info; + indio_dev->channels = &pct2075_channel; + indio_dev->num_channels = 1; + + pct2075->regulator = devm_regulator_get_optional(dev, "vcc"); + if (IS_ERR(pct2075->regulator)) { + ret = PTR_ERR(pct2075->regulator); + if (ret == -EPROBE_DEFER) + return ret; + + pct2075->regulator = NULL; + } + + if (pct2075->regulator) { + ret = regulator_enable(pct2075->regulator); + if (ret < 0) { + dev_err(dev, "Cannot enable regulator: %d\n", ret); + return ret; + } + } + + /* Read hardware defaults */ + ret = i2c_smbus_read_word_swapped(client, PCT2075_REG_TOS); + if (ret < 0) { + dev_err(dev, "Cannot read TOS register: %d\n", ret); + return ret; + } + pct2075->reg_tos = ret; + + ret = i2c_smbus_read_word_swapped(client, PCT2075_REG_THYST); + if (ret < 0) { + dev_err(dev, "Cannot read THYST register: %d\n", ret); + return ret; + } + pct2075->reg_thyst = ret; + + ret = i2c_smbus_read_byte_data(client, PCT2075_REG_TIDLE); + if (ret < 0) { + dev_err(dev, "Cannot read TIDLE register: %d\n", ret); + return ret; + } + pct2075->reg_tidle = ret; + + /* Parse DT properties */ + ret = of_property_read_u32(dev->of_node, "nxp,os-fault-queue", &tmp); + if (ret == 0) { + switch (tmp) { + case 1: + pct2075->reg_conf |= PCT2075_CONF_OS_F_QUEUE(0); + break; + case 2: + pct2075->reg_conf |= PCT2075_CONF_OS_F_QUEUE(1); + break; + case 4: + pct2075->reg_conf |= PCT2075_CONF_OS_F_QUEUE(2); + break; + case 6: + pct2075->reg_conf |= PCT2075_CONF_OS_F_QUEUE(3); + break; + default: + dev_err(dev, "Unsupported value for nxp,os-fault-queue"); + } + } + + if (of_property_read_bool(dev->of_node, "nxp,os-active-high")) + pct2075->reg_conf |= PCT2075_CONF_OS_ACTIVE_HIGH; + + if (of_property_read_bool(dev->of_node, "nxp,os-mode-interrupt")) + pct2075->reg_conf |= PCT2075_CONF_OS_COMP_INT; + + ret = of_property_read_u32(dev->of_node, "nxp,sample-period-ms", &tmp); + if (ret == 0) { + if (tmp % 100 == 0 && tmp <= 3100) + pct2075->reg_tidle = tmp / 100; + else + dev_err(dev, "Unsupported value for nxp,sample-period-ms"); + } + + pct2075_of_parse_temperature(dev, &pct2075->reg_tos, + "nxp,overtemperature-shutdown-millicelsius"); + pct2075_of_parse_temperature(dev, &pct2075->reg_thyst, + "nxp,hysteresis-millicelsius"); + + ret = pct2075_sync(pct2075); + if (ret < 0) + return ret; + + pm_runtime_disable(dev); + ret = pm_runtime_set_active(dev); + if (ret < 0) + return ret; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(dev, 10); + pm_runtime_use_autosuspend(dev); + + return iio_device_register(indio_dev); +} + +static int pct2075_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct pct2075_data *pct2075 = iio_priv(indio_dev); + struct device *dev = &client->dev; + + i2c_smbus_write_byte_data(client, PCT2075_REG_CONF, + PCT2075_CONF_SHUTDOWN); + + if (pct2075->regulator) + regulator_disable(pct2075->regulator); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + + return 0; +} + +static const struct i2c_device_id pct2075_id[] = { + { "pct2075", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pct2075_id); + +static const struct of_device_id pct2075_of_match[] = { + { .compatible = "nxp,pct2075" }, + { } +}; +MODULE_DEVICE_TABLE(of, pct2075_of_match); + +static int __maybe_unused pct2075_pm_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct pct2075_data *pct2075 = iio_priv(indio_dev); + + return i2c_smbus_write_byte_data(pct2075->client, PCT2075_REG_CONF, + PCT2075_CONF_SHUTDOWN); +} + +static int __maybe_unused pct2075_pm_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct pct2075_data *pct2075 = iio_priv(indio_dev); + + return pct2075_sync(pct2075); +} + +static UNIVERSAL_DEV_PM_OPS(pct2075_pm_ops, pct2075_pm_suspend, + pct2075_pm_resume, NULL); + +static struct i2c_driver pct2075_driver = { + .driver = { + .name = "pct2075", + .of_match_table = pct2075_of_match, + .pm = &pct2075_pm_ops, + }, + .probe = pct2075_probe, + .remove = pct2075_remove, + .id_table = pct2075_id, +}; +module_i2c_driver(pct2075_driver); + +MODULE_AUTHOR("Daniel Mack <daniel@xxxxxxxxxx>"); +MODULE_DESCRIPTION("NXP PCT2075 temperature sensor driver"); +MODULE_LICENSE("GPL v2"); -- 2.21.0