[RFC PATCH 02/11] hwmon: add a driver for the temp/voltage sensor on PolarFire SoC

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

 



From: Lars Randers <lranders@xxxxxxx>

Signed-off-by: Lars Randers <lranders@xxxxxxx>
Signed-off-by: Conor Dooley <conor.dooley@xxxxxxxxxxxxx>
---
 drivers/hwmon/Kconfig          |  12 ++
 drivers/hwmon/Makefile         |   1 +
 drivers/hwmon/tvs-mpfs.c       | 379 +++++++++++++++++++++++++++++++++
 drivers/mailbox/mailbox-mpfs.c |   1 +
 4 files changed, 393 insertions(+)
 create mode 100644 drivers/hwmon/tvs-mpfs.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index b60fe2e58ad6..2d2bed68dcad 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2295,6 +2295,18 @@ config SENSORS_TMP513
 	  This driver can also be built as a module. If so, the module
 	  will be called tmp513.
 
+config SENSORS_TVS_MPFS
+	tristate "PolarFire SoC (MPFS) temperature and voltage sensor"
+	depends on POLARFIRE_SOC_MAILBOX
+	help
+	  This driver adds support for the PolarFire SoC (MPFS) Temperature and
+	  Voltage Sensor.
+
+	  To compile this driver as a module, choose M here. the
+	  module will be called tvs-mpfs.
+
+	  If unsure, say N.
+
 config SENSORS_VEXPRESS
 	tristate "Versatile Express"
 	depends on VEXPRESS_CONFIG
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b1c7056c37db..7f44c2567008 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -220,6 +220,7 @@ obj-$(CONFIG_SENSORS_TMP401)	+= tmp401.o
 obj-$(CONFIG_SENSORS_TMP421)	+= tmp421.o
 obj-$(CONFIG_SENSORS_TMP464)	+= tmp464.o
 obj-$(CONFIG_SENSORS_TMP513)	+= tmp513.o
+obj-$(CONFIG_SENSORS_TVS_MPFS)  += tvs-mpfs.o
 obj-$(CONFIG_SENSORS_VEXPRESS)	+= vexpress-hwmon.o
 obj-$(CONFIG_SENSORS_VIA_CPUTEMP)+= via-cputemp.o
 obj-$(CONFIG_SENSORS_VIA686A)	+= via686a.o
diff --git a/drivers/hwmon/tvs-mpfs.c b/drivers/hwmon/tvs-mpfs.c
new file mode 100644
index 000000000000..6f117d0b7db6
--- /dev/null
+++ b/drivers/hwmon/tvs-mpfs.c
@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *
+ * Author: Lars Randers <lranders@xxxxxxx>
+ *
+ */
+
+#include <linux/err.h>
+#include <linux/freezer.h>
+#include <linux/kthread.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define PFSOC_CONTROL_SCB_TVS_CONTROL 0x08
+#define PFSOC_CONTROL_SCB_TVS_OUTPUT0 0x24
+#define PFSOC_CONTROL_SCB_TVS_OUTPUT1 0x28
+
+#define CTRL_POWEROFF BIT(5)
+#define CTRL_ABORT    BIT(4)
+#define CTRL_TEMP     BIT(3)
+#define CTRL_2P5      BIT(2)
+#define CTRL_1P8      BIT(1)
+#define CTRL_1P05     BIT(0)
+
+#define OUTPUT0_U1P8_MASK GENMASK(30, 16)
+#define OUTPUT0_U1P8_OFF  16
+#define OUTPUT0_U1P0_MASK GENMASK(14, 0)
+#define OUTPUT0_U1P0_OFF  0
+#define OUTPUT1_TEMP_MASK GENMASK(31, 16)
+#define OUTPUT1_TEMP_OFF  16
+#define OUTPUT1_U2P5_MASK GENMASK(14, 0)
+#define OUTPUT1_U2P5_OFF  0
+
+#define MPFS_TVS_MIN_POLL_INTERVAL_IN_MILLIS 2000
+
+/* The following constant is 273.5 in (16.4) fixedpoint notation */
+#define MPFS_TVS_MIN_TEMP_IN_K 0x1112
+
+typedef struct {
+	long min;
+	long actual;
+	long max;
+} mpfs_tvs_sensor_t;
+
+typedef enum {
+	SN_V1P05 = 0,
+	SN_V1P8,
+	SN_V2P5,
+	SN_TEMP,
+
+	SN_MAX
+} mpfs_tvs_sn_t;
+
+static const char *mpfs_tvs_voltage_labels[] = { "U1P05", "U1P8", "U2P5" };
+
+struct mpfs_tvs {
+	struct device *dev;
+	struct device *hwmon_dev;
+	struct task_struct *poll_task;
+	struct regmap *regmap;
+	bool kthread_running;
+	long update_interval;	/* in milli-seconds */
+	mpfs_tvs_sensor_t sensors[SN_MAX];
+};
+
+static int mpfs_tvs_update_sensors(struct mpfs_tvs *data) {
+	u32 temp;
+	u32 work;
+	int ret;
+
+	ret = regmap_read(data->regmap, PFSOC_CONTROL_SCB_TVS_OUTPUT1, &temp);
+	if (ret)
+		return ret;
+
+	work = temp;
+
+	temp = (temp & OUTPUT1_TEMP_MASK) >> OUTPUT1_TEMP_OFF;
+	temp = clamp_val(temp, MPFS_TVS_MIN_TEMP_IN_K, INT_MAX);
+	temp = temp - MPFS_TVS_MIN_TEMP_IN_K; /* Kelvin to Celsius */
+	temp = (1000 * temp) >> 4; /* fixed point (10.4) to millicentigrade */
+	data->sensors[SN_TEMP].actual = temp;
+	data->sensors[SN_TEMP].max =
+		max(data->sensors[SN_TEMP].actual, data->sensors[SN_TEMP].max);
+	data->sensors[SN_TEMP].min =
+		min(data->sensors[SN_TEMP].min, data->sensors[SN_TEMP].actual);
+
+	work &= OUTPUT1_U2P5_MASK;
+	/* fixed point (11.3) adjust; value is already millivolts */
+	work  = (1 * work) >> 3;
+	data->sensors[SN_V2P5].actual = work;
+	data->sensors[SN_V2P5].max =
+		max(data->sensors[SN_V2P5].actual, data->sensors[SN_V2P5].max);
+	data->sensors[SN_V2P5].min =
+		min(data->sensors[SN_V2P5].min, data->sensors[SN_V2P5].actual);
+
+	ret = regmap_read(data->regmap, PFSOC_CONTROL_SCB_TVS_OUTPUT0, &temp);
+	if (ret)
+		return ret;
+
+	work = temp;
+	temp = (OUTPUT0_U1P8_MASK & temp) >> OUTPUT0_U1P8_OFF;
+	/* fixed point (11.3) adjust; value is already millivolts */
+	temp = (1 * temp) >> 3;
+	data->sensors[SN_V1P8].actual = temp;
+	data->sensors[SN_V1P8].max =
+		max(data->sensors[SN_V1P8].actual, data->sensors[SN_V1P8].max);
+	data->sensors[SN_V2P5].min =
+		min(data->sensors[SN_V1P8].min, data->sensors[SN_V1P8].actual);
+
+	work &= OUTPUT0_U1P0_MASK;
+	 /* fixed point (11.3) adjust; value is already millivolts */
+	work  = (1 * work) >> 3;
+	data->sensors[SN_V1P05].actual = work;
+	data->sensors[SN_V1P05].max =
+		max(data->sensors[SN_V1P05].actual, data->sensors[SN_V1P05].max);
+	data->sensors[SN_V1P05].min =
+		min(data->sensors[SN_V1P05].min, data->sensors[SN_V1P05].actual);
+
+	return 0;
+}
+
+
+static int mpfs_tvs_chip_read(struct mpfs_tvs *data, long *val)
+{
+	*val = data->update_interval;
+	return 0;
+}
+
+static int mpfs_tvs_temp_read(struct mpfs_tvs *data, u32 attr,
+			      int channel, long *val)
+{
+	switch(attr) {
+	case hwmon_temp_input:
+		*val = data->sensors[SN_TEMP].actual;
+		break;
+
+	case hwmon_temp_max:
+		*val = data->sensors[SN_TEMP].max;
+		break;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+	return 0;
+}
+
+static int mpfs_tvs_voltage_read(struct mpfs_tvs *data, u32 attr,
+				 int channel, long *val)
+{
+	dev_dbg(data->dev, "read voltage chan %d\n", channel);
+	switch(attr) {
+	case hwmon_in_input:
+		*val = data->sensors[channel].actual;
+		break;
+
+	case hwmon_in_max:
+		*val = data->sensors[channel].max;
+		break;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+	return 0;
+}
+
+
+static ssize_t mpfs_tvs_interval_write(struct mpfs_tvs *data, long val)
+{
+	data->update_interval =
+		clamp_val(val, MPFS_TVS_MIN_POLL_INTERVAL_IN_MILLIS, INT_MAX);
+	return 0;
+}
+
+
+static umode_t mpfs_tvs_is_visible(const void *data,
+				   enum hwmon_sensor_types type,
+				   u32 attr, int channel)
+{
+	if(type == hwmon_chip && attr == hwmon_chip_update_interval)
+		return 0644;
+
+	if(type == hwmon_temp) {
+		switch(attr) {
+		case hwmon_temp_input:
+		case hwmon_temp_max:
+		case hwmon_temp_label:
+			return 0444;
+
+		default:
+			return 0;
+		}
+	} else if(type == hwmon_in) {
+		switch(attr) {
+		case hwmon_in_input:
+		case hwmon_in_label:
+			return 0444;
+
+		default:
+			return 0;
+		}
+	}
+	return 0;
+}
+
+static int mpfs_tvs_read(struct device *dev, enum hwmon_sensor_types type,
+			 u32 attr, int channel, long *val)
+{
+	struct mpfs_tvs *data = dev_get_drvdata(dev);
+
+	switch(type) {
+	case hwmon_temp:
+		return mpfs_tvs_temp_read(data, attr, channel, val);
+	case hwmon_in:
+		return mpfs_tvs_voltage_read(data, attr, channel, val);
+	case hwmon_chip:
+		return mpfs_tvs_chip_read(data, val);
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int mpfs_tvs_write(struct device *dev, enum hwmon_sensor_types type,
+			  u32 attr, int channel, long val)
+{
+	struct mpfs_tvs *data = dev_get_drvdata(dev);
+
+	switch(type) {
+	case hwmon_chip:
+		return mpfs_tvs_interval_write(data, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int mpfs_tvs_read_labels(struct device *dev,
+				enum hwmon_sensor_types type,
+				u32 attr, int channel,
+				const char **str)
+{
+	switch(type) {
+	case hwmon_temp:
+		*str = "CPU Temp";
+		return 0;
+	case hwmon_in:
+		*str = mpfs_tvs_voltage_labels[channel];
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+
+static const struct hwmon_ops mpfs_tvs_ops = {
+	.is_visible = mpfs_tvs_is_visible,
+	.read_string = mpfs_tvs_read_labels,
+	.read = mpfs_tvs_read,
+	.write = mpfs_tvs_write,
+};
+
+static const struct hwmon_channel_info *mpfs_tvs_info[] = {
+	HWMON_CHANNEL_INFO(chip,
+			   HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL),
+	HWMON_CHANNEL_INFO(temp,
+			   HWMON_T_INPUT | HWMON_T_MIN |
+			   HWMON_T_MAX | HWMON_T_LABEL),
+	HWMON_CHANNEL_INFO(in,
+			   HWMON_I_INPUT | HWMON_I_LABEL,
+			   HWMON_I_INPUT | HWMON_I_LABEL,
+			   HWMON_I_INPUT | HWMON_I_LABEL),
+	NULL
+};
+
+static const struct hwmon_chip_info mpfs_tvs_chip_info = {
+	.ops = &mpfs_tvs_ops,
+	.info = mpfs_tvs_info,
+};
+
+
+static int mpfs_tvs_poll_task(void *ptr)
+{
+	struct mpfs_tvs *data = ptr;
+	int ret = 0;
+
+	data->kthread_running = true;
+
+	set_freezable();
+
+	while(!kthread_should_stop()) {
+		schedule_timeout_interruptible(data->update_interval);
+		try_to_freeze();
+		ret = mpfs_tvs_update_sensors(data);
+		if(ret)
+			break;
+	}
+
+	data->kthread_running = false;
+	return ret;
+}
+
+static int mpfs_tvs_probe(struct platform_device *pdev)
+{
+	struct device *hwmon_dev;
+	struct mpfs_tvs *data;
+	struct task_struct *task;
+	int err;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if(!data)
+		return -ENOMEM;
+
+	data->dev = &pdev->dev;
+
+	data->regmap = syscon_node_to_regmap(data->dev->of_node->parent);
+
+	data->kthread_running = false;
+
+	hwmon_dev = devm_hwmon_device_register_with_info(data->dev, "mpfs_tvs",
+							 data,
+							 &mpfs_tvs_chip_info,
+							 NULL);
+
+	if(IS_ERR(hwmon_dev)) {
+		err = PTR_ERR(hwmon_dev);
+		dev_err(data->dev, "Class registration failed (%d)\n", err);
+		return err;
+	}
+
+	/* enable HW sensor */
+	err = regmap_write(data->regmap, PFSOC_CONTROL_SCB_TVS_CONTROL,
+			   CTRL_1P05 | CTRL_1P8 | CTRL_2P5 | CTRL_TEMP);
+
+	data->hwmon_dev = hwmon_dev;
+	data->sensors[SN_TEMP].max = 0;
+	data->sensors[SN_V1P05].min =
+		data->sensors[SN_V1P8].min =
+		data->sensors[SN_V2P5].min = 20000;
+	data->sensors[SN_V1P05].max =
+		data->sensors[SN_V1P8].max =
+		data->sensors[SN_V2P5].max = 0;
+	data->update_interval = MPFS_TVS_MIN_POLL_INTERVAL_IN_MILLIS;
+	mpfs_tvs_update_sensors(data);
+
+	task = kthread_run(mpfs_tvs_poll_task, data, "tvs-mpfs-kthread");
+	if (IS_ERR(task)) {
+		err = PTR_ERR(task);
+		dev_err(data->dev, "Unable to run kthread err %d\n", err);
+		return err;
+	}
+
+	data->poll_task = task;
+
+	dev_info(data->dev, "Registered MPFS TVS auxiliary driver\n");
+	return 0;
+}
+
+static const struct of_device_id mpfs_tvs_of_match[] = {
+	{ .compatible = "microchip,mpfs-tvs", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, mpfs_tvs_of_match);
+
+static struct platform_driver mpfs_tvs_driver = {
+	.probe		= mpfs_tvs_probe,
+	.driver = {
+		.name = "mpfs-tvs",
+		.of_match_table = mpfs_tvs_of_match,
+	},
+};
+module_platform_driver(mpfs_tvs_driver);
+
+MODULE_AUTHOR("Lars Randers <lranders@xxxxxxx>");
+MODULE_DESCRIPTION("PolarFire SoC temperature & voltage sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mailbox/mailbox-mpfs.c b/drivers/mailbox/mailbox-mpfs.c
index 20ee283a04cc..0fd83bdd4cee 100644
--- a/drivers/mailbox/mailbox-mpfs.c
+++ b/drivers/mailbox/mailbox-mpfs.c
@@ -262,6 +262,7 @@ static int mpfs_mbox_probe(struct platform_device *pdev)
 		dev_err(&pdev->dev, "Registering MPFS mailbox controller failed\n");
 		return ret;
 	}
+
 	dev_info(&pdev->dev, "Registered MPFS mailbox controller driver\n");
 
 	return 0;
-- 
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