[PATCH 1/2] hwmon: Add support for TI TPS389008

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



TI TPS38900x are voltage monitor IC's.

All device variants offer an I2C interface and depending on the
part number, x monitor inputs.

Support chips by the driver are TPS389008, TPS389006 and TPS389004.

The driver adds support for reading the 8-bit ADC value of any of
the VMON inputs. By default the inputs are disabled and have a scaling
factor of 1x. They need to be enabled in the device tree, or using the
sysfs attribute from user space.

Signed-off-by: Flaviu Nistor <flaviu.nistor@xxxxxxxxx>
---
 Documentation/hwmon/index.rst     |   1 +
 Documentation/hwmon/tps389008.rst |  56 ++++
 MAINTAINERS                       |   8 +
 drivers/hwmon/Kconfig             |  10 +
 drivers/hwmon/Makefile            |   1 +
 drivers/hwmon/tps389008.c         | 466 ++++++++++++++++++++++++++++++
 6 files changed, 542 insertions(+)
 create mode 100644 Documentation/hwmon/tps389008.rst
 create mode 100644 drivers/hwmon/tps389008.c

diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 874f8fd26325..b04643d14972 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -239,6 +239,7 @@ Hardware Monitoring Kernel Drivers
    tmp513
    tps23861
    tps25990
+   tps389008
    tps40422
    tps53679
    tps546d24
diff --git a/Documentation/hwmon/tps389008.rst b/Documentation/hwmon/tps389008.rst
new file mode 100644
index 000000000000..6e1166165ac4
--- /dev/null
+++ b/Documentation/hwmon/tps389008.rst
@@ -0,0 +1,56 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver tps389008
+======================
+
+Supported chips:
+
+  * TI TPS389008, TPS389006, TPS389004
+
+    Prefix: 'tps389008'
+
+    Addresses scanned: -
+
+    Datasheet: https://www.ti.com/lit/ds/symlink/tps389006.pdf?ts=1741000787840&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252FTPS389006
+
+Author:
+
+  - Flaviu Nistor <flaviu.nistor@xxxxxxxxx>
+
+Description
+-----------
+
+This driver implements support for TI TPS389008, TPS389006 and TPS389004 voltage monitor chips.
+The driver supports only the chips that have default values based on datasheet and not OTP NVM settings.
+Monitored voltages can be read out via an internal ADC with one register per input channel.
+Measured voltage is expressed in mV per LSB.
+The measurement voltage ranges depends on the scaling factor used as following:
+
+  - 1x scaling: 200 to 1475 mV (8-bit resolution)
+  - 4x scaling: 800 to 5900 mV (8-bit resolution)
+
+The scaling factor is 1 by default for all channels.
+
+All input VMON channel are disabled by default, and they can be enabled via the dts (during probe)
+or using the provided sysfs attribute from user space.
+
+The device communicates with the I2C protocol and uses the I2C address 0x30 by default.
+
+
+Known Issues
+------------
+
+The driver does not support usage of alarms and setting of thresholds (for the alarms).
+
+sysfs-Interface
+---------------
+
+The following list includes the sysfs attributes that the driver will provide for each added input channel:
+
+=============================== ======= ========================================
+Name                            Perm    Description
+=============================== ======= ========================================
+in[12345678]_input:             RO      Voltage channel input
+in[12345678]_label:             RO      Voltage channel label
+in[12345678]_enable:            RW      Voltage channel enable controls
+=============================== ======= ========================================
diff --git a/MAINTAINERS b/MAINTAINERS
index 16f51eb6ebe8..fbf07f26d933 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23510,6 +23510,14 @@ F:	Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml
 F:	Documentation/hwmon/tps23861.rst
 F:	drivers/hwmon/tps23861.c
 
+TEXAS INSTRUMENTS TPS389008 VMON DRIVER
+M:	Flaviu Nistor <flaviu.nistor@xxxxxxxxx>
+L:	linux-hwmon@xxxxxxxxxxxxxxx
+S:	Maintained
+F:	Documentation/devicetree/bindings/hwmon/ti,tps389008.yaml
+F:	Documentation/hwmon/tps389008.rst
+F:	drivers/hwmon/tps389008.c
+
 TEXAS INSTRUMENTS' DAC7612 DAC DRIVER
 M:	Ricardo Ribalda <ribalda@xxxxxxxxxx>
 L:	linux-iio@xxxxxxxxxxxxxxx
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 4cbaba15d86e..5562eea4d0bb 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1340,6 +1340,16 @@ config SENSORS_TPS23861
 	  This driver can also be built as a module. If so, the module
 	  will be called tps23861.
 
+config SENSORS_TPS389008
+	tristate "TI TPS389008 VMON Driver"
+	depends on I2C
+	help
+	  This driver provides support for voltage monitoring for the Texas
+	  Instruments TPS389008, TPS389006 and TPS389004 chips.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called tps389008.
+
 config SENSORS_MENF21BMC_HWMON
 	tristate "MEN 14F021P00 BMC Hardware Monitoring"
 	depends on MFD_MENF21BMC
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b7ef0f0562d3..6eb0b7696239 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -164,6 +164,7 @@ obj-$(CONFIG_SENSORS_MC34VR500)	+= mc34vr500.o
 obj-$(CONFIG_SENSORS_MCP3021)	+= mcp3021.o
 obj-$(CONFIG_SENSORS_TC654)	+= tc654.o
 obj-$(CONFIG_SENSORS_TPS23861)	+= tps23861.o
+obj-$(CONFIG_SENSORS_TPS389008) += tps389008.o
 obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
 obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
 obj-$(CONFIG_SENSORS_MR75203)	+= mr75203.o
diff --git a/drivers/hwmon/tps389008.c b/drivers/hwmon/tps389008.c
new file mode 100644
index 000000000000..6ee6c3b58747
--- /dev/null
+++ b/drivers/hwmon/tps389008.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * tps389008.c Support for the TI TPS389008 Voltage Monitor
+ *
+ * Part numbers supported:
+ * TPS389006, TPS389008
+ *
+ * Author: Flaviu Nistor <flaviu.nistor@xxxxxxxxx>
+ *
+ * Datasheet and application notes:
+ * https://www.ti.com/
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+
+#define TPS389008_NUM_CHANNELS      8
+#define TPS389008_1LSB              5
+#define RANGE_OFFSET                200
+#define BANK0                       0
+#define BANK1                       1
+/*BANK0 REGISTERS*/
+#define MON_LVL			            0x40
+#define BANK_SEL                    0xF0
+/*BANK1 REGISTERS
+ * use 0x100 to signal that address is part of BANK1
+ */
+#define MON_CH_EN                   0x11E
+
+struct tps389008_input {
+	const char *label;
+	int vrange_mult_mv;
+	bool enabled;
+	bool disconnected;
+};
+
+struct tps389008_data {
+	struct device *hwmon;
+	struct i2c_client *client;
+	struct tps389008_input input[TPS389008_NUM_CHANNELS];
+	struct mutex dev_access_lock; /* device access lock */
+	const char *name;
+	int current_bank;
+};
+
+enum tps_chan_addr {
+	TPS_CHANNEL_0 = 0,
+	TPS_CHANNEL_1,
+	TPS_CHANNEL_2,
+	TPS_CHANNEL_3,
+	TPS_CHANNEL_4,
+	TPS_CHANNEL_5,
+	TPS_CHANNEL_6,
+	TPS_CHANNEL_7,
+	TPS_CHANNEL_8
+};
+
+static int tps389008_read_string(struct device *dev, enum hwmon_sensor_types type,
+	u32 attr, int channel, const char **str)
+{
+	struct tps389008_data *tps = dev_get_drvdata(dev);
+	int index;
+
+	index = channel - 1;
+	*str = tps->input[index].label;
+
+	return 0;
+}
+
+static umode_t tps389008_is_visible(const void *data, enum hwmon_sensor_types type,
+				  u32 attr, int channel)
+{
+	const struct tps389008_data *tps = data;
+	const struct tps389008_input *input = NULL;
+	int index;
+
+	/* channel numbering starts from 1, but index from 0*/
+	index = channel - 1;
+
+	/* in0_ or disconnected channels should be ignored*/
+	if (channel == 0 ||
+		(tps->input[index].disconnected ||
+		 tps->input[index].vrange_mult_mv == 0)) {
+		return 0;
+	}
+	switch (attr) {
+	case hwmon_in_input:
+		return 0444;
+	case hwmon_in_label:
+		input = &tps->input[index];
+		/* Hide label node if label is not provided */
+		return (input && input->label) ? 0444 : 0;
+	case hwmon_in_enable:
+		return 0644;
+	default:
+		return 0;
+	}
+}
+
+static int change_bank(struct tps389008_data *data, u8 bank)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(data->client, BANK_SEL, bank);
+	if (ret < 0) {
+		dev_err(&data->client->dev,
+			"change to bank%d failed with error code: %d\n", bank, ret);
+		return ret;
+	}
+
+	return ret;
+}
+
+static int tps389008_write_reg(struct tps389008_data *data, u16 reg, u8 val)
+{
+	int ret;
+	u8 bank;
+
+	bank  = (reg & 0x100) >> 8;
+
+	if (bank != (u8)data->current_bank) {
+		change_bank(data, bank);
+		data->current_bank = bank;
+	}
+
+	ret = i2c_smbus_write_byte_data(data->client, reg, val);
+	if (ret < 0) {
+		dev_err(&data->client->dev,
+				"I2C write failed at address: 0x%X with error code: %d\n",
+				reg, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int tps389008_read_reg(struct tps389008_data *data, u16 reg, u16 *val)
+{
+	int read_byte;
+	u8 bank;
+
+	bank  = (reg & 0x100) >> 8;
+
+	if (bank != (u8)data->current_bank) {
+		change_bank(data, bank);
+		data->current_bank = bank;
+	}
+
+	read_byte = i2c_smbus_read_byte_data(data->client, reg);
+	if (read_byte < 0) {
+		dev_err(&data->client->dev,
+			"I2C read failed at address: 0x%X with error code: %d\n", reg, read_byte);
+		return read_byte;
+	}
+
+	*val = read_byte;
+
+	return 0;
+}
+
+static int tps389008_get_in_val(struct tps389008_data *data, u16 reg, int channel, long *val)
+{
+	u16 reg_val;
+	int ret;
+	int index;
+
+	/* channel numbering starts from 1, but index from 0*/
+	index = channel - 1;
+
+	ret = tps389008_read_reg(data, reg, &reg_val);
+	if (ret)
+		return ret;
+
+	*val = ((reg_val * TPS389008_1LSB + RANGE_OFFSET) * data->input[index].vrange_mult_mv);
+
+	return 0;
+}
+
+static int disable_input(struct tps389008_data *data, u8 channel)
+{
+	u16 reg_val;
+	int ret;
+	int index;
+
+	index = channel - 1;
+
+	ret = tps389008_read_reg(data, MON_CH_EN, &reg_val);
+	if (ret)
+		return ret;
+
+	reg_val = reg_val & ~(1 << index);
+
+	ret = tps389008_write_reg(data, MON_CH_EN, reg_val);
+
+	return ret;
+}
+
+static int enable_input(struct tps389008_data *data, u8 channel)
+{
+	u16 reg_val;
+	int ret;
+	int index;
+
+	index = channel - 1;
+
+	ret = tps389008_read_reg(data, MON_CH_EN, &reg_val);
+	if (ret)
+		return ret;
+
+	reg_val = reg_val | (1 << index);
+
+	ret = tps389008_write_reg(data, MON_CH_EN, reg_val);
+
+	return ret;
+}
+
+static int tps389008_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+			int channel, long val)
+{
+	struct tps389008_data *data = dev_get_drvdata(dev);
+	int index;
+	int ret;
+
+	/* channel numbering starts from 1, but index from 0*/
+	index = channel - 1;
+
+	guard(mutex)(&data->dev_access_lock);
+
+	if (attr == hwmon_in_input || attr == hwmon_in_label) {
+		dev_warn(dev, "Write to READ ONLY resource\n");
+		return -EOPNOTSUPP;
+	}
+	if (attr == hwmon_in_enable) {
+		if (val == 0) {
+			data->input[index].enabled = val;
+			ret = disable_input(data, channel);
+			return ret;
+		} else if (val == 1) {
+			data->input[index].enabled = val;
+			ret = enable_input(data, channel);
+			return ret;
+		}
+		dev_err(dev, "invalid value %ld\n", val);
+		return -EINVAL;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int tps389008_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+			int channel, long *val)
+{
+
+	struct tps389008_data *data = dev_get_drvdata(dev);
+	int ret;
+	int index;
+
+	/* channel numbering starts from 1, but index from 0*/
+	index = channel - 1;
+
+	guard(mutex)(&data->dev_access_lock);
+
+	switch (attr) {
+	case hwmon_in_input:
+		ret = tps389008_get_in_val(data, MON_LVL + index, channel, val);
+		if (ret)
+			dev_err(dev,
+				"Reading the ADC value for channel %d failed with error code: %d\n",
+				channel, ret);
+		break;
+	case hwmon_in_enable:
+		*val = data->input[index].enabled;
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct hwmon_channel_info *tps389008_info[] = {
+	HWMON_CHANNEL_INFO(in,
+					/* 0: dummy, skipped in is_visible */
+					HWMON_I_INPUT,
+					/* 1-8: input voltage Channels */
+					HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL,
+					HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL,
+					HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL,
+					HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL,
+					HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL,
+					HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL,
+					HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL,
+					HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL),
+	NULL
+};
+
+static const struct hwmon_ops tps389008_hwmon_ops = {
+	.is_visible = tps389008_is_visible,
+	.read_string = tps389008_read_string,
+	.read = tps389008_read,
+	.write = tps389008_write,
+};
+
+static const struct hwmon_chip_info tps389008_chip_info = {
+	.ops = &tps389008_hwmon_ops,
+	.info = tps389008_info,
+};
+
+
+static int tps389008_probe_child_from_dt(struct device *dev,
+	struct device_node *child,
+	struct tps389008_data *tps)
+{
+	struct tps389008_input *input;
+	u32 val;
+	int ret;
+
+	ret = of_property_read_u32(child, "reg", &val);
+	if (ret) {
+		dev_err(dev, "missing reg property of %pOFn\n", child);
+		return ret;
+	}
+	if (val < 1 || val > TPS_CHANNEL_8) {
+		dev_err(dev, "invalid reg %d of %pOFn\n", val, child);
+		return -EINVAL;
+	}
+
+	/* remember that children nodes starts from 1, but we have the input start index 0.*/
+	input = &tps->input[val-1];
+
+	ret = (int)of_property_read_bool(child, "ti,vrange-mult-4x");
+	if (ret)
+		input->vrange_mult_mv = 4;
+	else
+		input->vrange_mult_mv = 1;
+
+	ret = (int)of_property_read_bool(child, "ti,vmon-enable");
+	/* missing optional property. Default enable the channel*/
+	if (ret)
+		input->enabled = true;
+
+	/* Log the disconnected channel input */
+	if (!of_device_is_available(child)) {
+		input->disconnected = true;
+		return 0;
+	}
+
+	/* Save the connected input label if available */
+	of_property_read_string(child, "label", &input->label);
+
+	return 0;
+}
+
+static int tps389008_probe_from_dt(struct device *dev, struct tps389008_data *tps)
+{
+	const struct device_node *np = dev->of_node;
+	struct device_node *child;
+	int ret;
+
+	for_each_child_of_node(np, child) {
+		ret = tps389008_probe_child_from_dt(dev, child, tps);
+		if (ret) {
+			of_node_put(child);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int tps389008_probe(struct i2c_client *client)
+{
+	struct tps389008_data *data;
+	struct device *dev = &client->dev;
+	int ret, i;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+		return -EOPNOTSUPP;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->dev_access_lock);
+	data->client = client;
+
+	ret = tps389008_probe_from_dt(dev, data);
+	if (ret) {
+		dev_err(dev, "Unable to probe from device tree\n");
+		return ret;
+	}
+
+	data->current_bank = 0;
+
+	/* Enable channels if they are enabled */
+	for (i = 0; i < TPS389008_NUM_CHANNELS; i++) {
+		if (data->input[i].enabled) {
+			enable_input(data, i+1);
+			dev_dbg(dev, "VMON input %d is enabled\n", (i+1));
+		}
+	}
+
+	data->hwmon = devm_hwmon_device_register_with_info(dev, client->name,
+							   data, &tps389008_chip_info,
+							   NULL);
+
+	if (IS_ERR(data->hwmon))
+		return dev_err_probe(dev, PTR_ERR(data->hwmon),
+					 "Failed to register hwmon device tps389008\n");
+
+	dev_info(dev, "hwmon device tps389008 probed successfully\n");
+
+	return 0;
+}
+
+static void tps389008_remove(struct i2c_client *client)
+{
+	struct tps389008_data *tps = dev_get_drvdata(&client->dev);
+
+	hwmon_device_unregister(tps->hwmon);
+
+	mutex_destroy(&tps->dev_access_lock);
+}
+
+static const struct i2c_device_id tps389008_ids[] = {
+	{ "tps389008" },
+	{ "tps389006" },
+	{ "tps389004" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tps389008_ids);
+
+static const struct of_device_id tps389008_of_match[] = {
+	{ .compatible = "ti,tps389008", },
+	{ .compatible = "ti,tps389006", },
+	{ .compatible = "ti,tps389004", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, tps389008_of_match);
+
+static struct i2c_driver tps389008_i2c_driver = {
+	.driver = {
+		.name = "tps389008",
+		.of_match_table = tps389008_of_match,
+	},
+	.probe = tps389008_probe,
+	.remove = tps389008_remove,
+	.id_table = tps389008_ids,
+};
+
+module_i2c_driver(tps389008_i2c_driver);
+
+MODULE_AUTHOR("Flaviu Nistor <flaviu.nistor@xxxxxxxxx>");
+MODULE_DESCRIPTION("TI TPS389008 voltage monitor driver");
+MODULE_LICENSE("GPL");
-- 
2.43.0





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux