blob: 46e0aaf26f660d645a4a6b42c8fd342f3ec8dc53 [file] [log] [blame]
From f5bee334b0a56b8c69f5d82b1ebf513d94e6ede0 Mon Sep 17 00:00:00 2001
From: Avery Yang <avery.zl.yang@fii-na.corp-partner.google.com>
Date: Tue, 5 Nov 2024 19:26:02 +0800
Subject: [PATCH] hwmon mp2981 driver
Signed-off-by: Avery Yang <avery.zl.yang@fii-na.corp-partner.google.com>
---
drivers/hwmon/Kconfig | 10 ++
drivers/hwmon/Makefile | 1 +
drivers/hwmon/mp2981.c | 212 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 223 insertions(+)
create mode 100644 drivers/hwmon/mp2981.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index d48268e9cb63..136b37e1d50b 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1386,6 +1386,16 @@ config SENSORS_LM95245
This driver can also be built as a module. If so, the module
will be called lm95245.
+config SENSORS_MP2981
+ tristate "Monolithic Power System MP2981"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for MP2981 temperature sensor.
+
+ This driver can also be built as a module. If so, the module
+ will be called mp2981.
+
config SENSORS_PC87360
tristate "National Semiconductor PC87360 family"
depends on !PPC
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index bfe3f3c919a4..b8e8df35cb98 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -144,6 +144,7 @@ obj-$(CONFIG_SENSORS_MAX31760) += max31760.o
obj-$(CONFIG_SENSORS_MAX31790) += max31790.o
obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o
+obj-$(CONFIG_SENSORS_MP2981) += mp2981.o
obj-$(CONFIG_SENSORS_TC654) += tc654.o
obj-$(CONFIG_SENSORS_TPS23861) += tps23861.o
obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
diff --git a/drivers/hwmon/mp2981.c b/drivers/hwmon/mp2981.c
new file mode 100644
index 000000000000..6dd9c97c3fea
--- /dev/null
+++ b/drivers/hwmon/mp2981.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for MPS Digital LLC Controllers(MP2981)
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+
+/* MP2981 telemetry register */
+#define MP2981_VIN_REG 0x88
+#define MP2981_VOUT_REG 0x8B
+#define MP2981_IOUT_REG 0x8C
+#define MP2981_TEMP_REG 0x8D
+#define MP2981_POUT_REG 0x96
+
+#define MP2981_VIN_UINT 125
+#define MP2981_VOUT_UINT 625
+#define MP2981_VOUT_DIV 10
+#define MP2981_IOUT_UINT 500
+#define MP2981_TEMP_UINT 1000
+#define MP2981_POUT_UINT 1000000
+
+/* Each client has this additional data */
+struct mp2981_data {
+ struct i2c_client *client;
+ /* lock for preventing concurrency issue */
+ struct mutex lock;
+};
+
+static umode_t mp2981_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_temp:
+ case hwmon_in:
+ case hwmon_curr:
+ case hwmon_power:
+ return 0444;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int mp2981_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ int ret;
+ struct mp2981_data *data;
+
+ data = dev_get_drvdata(dev);
+ if (!data) {
+ dev_err(dev, "failed to get driver data\n");
+ return -ENOMEM;
+ }
+
+ mutex_lock(&data->lock);
+
+ switch (type) {
+ case hwmon_in:
+ ret = i2c_smbus_write_byte_data(data->client, 0, 0);
+ if (ret < 0) {
+ dev_err(dev, "failed to change page, errno = %d\n", ret);
+ break;
+ }
+
+ ret = i2c_smbus_read_word_data(data->client, channel == 0 ?
+ MP2981_VIN_REG : MP2981_VOUT_REG);
+ if (ret < 0) {
+ dev_err(dev, "failed to read %s, errno = %d\n", channel == 0 ?
+ "vin" : "vout", ret);
+ break;
+ }
+
+ *val = channel == 0 ? (ret & GENMASK(9, 0)) * MP2981_VIN_UINT :
+ DIV_ROUND_CLOSEST((ret & GENMASK(8, 0)) * MP2981_VOUT_UINT,
+ MP2981_VOUT_DIV);
+ break;
+ case hwmon_temp:
+ ret = i2c_smbus_write_byte_data(data->client, 0, 0);
+ if (ret < 0) {
+ dev_err(dev, "failed to change page, errno = %d\n", ret);
+ break;
+ }
+
+ ret = i2c_smbus_read_byte_data(data->client, MP2981_TEMP_REG);
+ if (ret < 0) {
+ dev_err(dev, "failed to read temp, errno = %d\n", ret);
+ break;
+ }
+
+ *val = ret * MP2981_TEMP_UINT;
+ break;
+ case hwmon_curr:
+ ret = i2c_smbus_write_byte_data(data->client, 0, 0);
+ if (ret < 0) {
+ dev_err(dev, "failed to change page, errno = %d\n", ret);
+ break;
+ }
+
+ ret = i2c_smbus_read_word_data(data->client, MP2981_IOUT_REG);
+ if (ret < 0) {
+ dev_err(dev, "failed to read iout, errno = %d\n", ret);
+ break;
+ }
+
+ *val = (ret & GENMASK(9, 0)) * MP2981_IOUT_UINT;
+ break;
+ case hwmon_power:
+ ret = i2c_smbus_write_byte_data(data->client, 0, 0);
+ if (ret < 0) {
+ dev_err(dev, "failed to change page, errno = %d\n", ret);
+ break;
+ }
+
+ ret = i2c_smbus_read_word_data(data->client, MP2981_POUT_REG);
+ if (ret < 0) {
+ dev_err(dev, "failed to read pout, errno = %d\n", ret);
+ break;
+ }
+
+ *val = (ret & GENMASK(10, 0)) * MP2981_POUT_UINT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static const struct hwmon_channel_info *mp2981_info[] = {
+ HWMON_CHANNEL_INFO(in, HWMON_I_INPUT, HWMON_I_INPUT),
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+ HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT),
+ HWMON_CHANNEL_INFO(power, HWMON_P_INPUT),
+ NULL
+};
+
+static const struct hwmon_ops mp2981_hwmon_ops = {
+ .is_visible = mp2981_is_visible,
+ .read = mp2981_read,
+};
+
+static const struct hwmon_chip_info mp2981_chip_info = {
+ .ops = &mp2981_hwmon_ops,
+ .info = mp2981_info,
+};
+
+static int mp2981_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct device *hwmon_dev;
+ struct mp2981_data *data;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_WORD_DATA)) {
+ dev_err(dev, "check failed, smbus byte and/or word data not supported!\n");
+ return -ENODEV;
+ }
+
+ data = devm_kzalloc(dev, sizeof(struct mp2981_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ mutex_init(&data->lock);
+ data->client = client;
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ data, &mp2981_chip_info,
+ NULL);
+ if (IS_ERR(hwmon_dev)) {
+ dev_err(dev, "unable to register mp2981 hwmon device\n");
+ return PTR_ERR(hwmon_dev);
+ }
+
+ dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name);
+
+ return 0;
+}
+
+static const struct i2c_device_id mp2981_ids[] = {
+ {"mp2981", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, mp2981_ids);
+
+static const struct of_device_id __maybe_unused mp2981_of_match[] = {
+ {.compatible = "mps,mp2981"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mp2981_of_match);
+
+static struct i2c_driver mp2981_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "mp2981",
+ .of_match_table = mp2981_of_match,
+ },
+ .probe_new = mp2981_probe,
+ .id_table = mp2981_ids,
+};
+module_i2c_driver(mp2981_driver);
+
+MODULE_AUTHOR("Noah Wang <Noah.Wang@monolithicpower.com>");
+MODULE_DESCRIPTION("MP2981 driver");
+MODULE_LICENSE("GPL");
--
2.34.1