summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/hwmon/index.rst1
-rw-r--r--Documentation/hwmon/tvs-mpfs.rst53
-rw-r--r--MAINTAINERS1
-rw-r--r--drivers/hwmon/Kconfig13
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/tvs-mpfs.c390
6 files changed, 459 insertions, 0 deletions
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 74d67fa21d19..226789376217 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -273,6 +273,7 @@ Hardware Monitoring Kernel Drivers
tps53679
tps546d24
tsc1641
+ tvs-mpfs
twl4030-madc-hwmon
ucd9000
ucd9200
diff --git a/Documentation/hwmon/tvs-mpfs.rst b/Documentation/hwmon/tvs-mpfs.rst
new file mode 100644
index 000000000000..1035812f363a
--- /dev/null
+++ b/Documentation/hwmon/tvs-mpfs.rst
@@ -0,0 +1,53 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver tvs-mpfs
+======================
+
+Supported chips:
+
+ * PolarFire SoC
+
+Authors:
+
+ - Conor Dooley <conor.dooley@microchip.com>
+ - Lars Randers <lranders@mail.dk>
+
+Description
+-----------
+
+This driver implements support for the temperature and voltage sensors on
+PolarFire SoC. The temperature reports how hot the die is, and the voltages are
+the SoC's 1.05, 1.8 and 2.5 volt rails respectively.
+
+
+Usage Notes
+-----------
+
+update_interval has a permitted range of 0 to 8 milliseconds.
+
+Temperatures are read in millidegrees Celsius, but the hardware measures in
+degrees Kelvin, storing the result as 11.4 fixed point data, for a maximum
+value of 2047.9375 degrees Kelvin.
+
+Voltages are read in millivolts. The hardware measures in millivolts, storing
+the value as 12.3 fixed point data, for a maximum of 4095.875 millivolts.
+The minimum value reportable by the driver is 0 volts, although the hardware
+is capable of measuring negative values.
+
+Sysfs entries
+-------------
+
+The following attributes are supported. update_interval is read-write, as are
+the enables. All other attributes are read only.
+
+======================= ====================================================
+temp1_label Fixed name for channel.
+temp1_input Measured temperature for channel.
+temp1_enable Enable/disable for channel.
+
+in[0-2]_label Fixed name for channel.
+in[0-2]_input Measured voltage for channel.
+in[0-2]_enable Enable/disable for channel.
+
+update_interval The interval at which the chip will update readings.
+======================= ====================================================
diff --git a/MAINTAINERS b/MAINTAINERS
index 8014b9f8253e..0c612a1e5920 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23259,6 +23259,7 @@ F: drivers/char/hw_random/mpfs-rng.c
F: drivers/clk/microchip/clk-mpfs*.c
F: drivers/firmware/microchip/mpfs-auto-update.c
F: drivers/gpio/gpio-mpfs.c
+F: drivers/hwmon/tvs-mpfs.c
F: drivers/i2c/busses/i2c-microchip-corei2c.c
F: drivers/mailbox/mailbox-mpfs.c
F: drivers/pci/controller/plda/pcie-microchip-host.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2bfbcc033d59..b2e445b5536e 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -942,6 +942,19 @@ config SENSORS_JC42
This driver can also be built as a module. If so, the module
will be called jc42.
+config SENSORS_POLARFIRE_SOC_TVS
+ tristate "PolarFire SoC (MPFS) temperature and voltage sensor"
+ depends on POLARFIRE_SOC_SYSCONS || COMPILE_TEST
+ depends on MFD_SYSCON
+ 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_POWERZ
tristate "ChargerLAB POWER-Z USB-C tester"
depends on USB
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 63effc0ab8d1..caea1f55222d 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -197,6 +197,7 @@ obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
+obj-$(CONFIG_SENSORS_POLARFIRE_SOC_TVS) += tvs-mpfs.o
obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o
diff --git a/drivers/hwmon/tvs-mpfs.c b/drivers/hwmon/tvs-mpfs.c
new file mode 100644
index 000000000000..aad00676518b
--- /dev/null
+++ b/drivers/hwmon/tvs-mpfs.c
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Author: Lars Randers <lranders@mail.dk>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/err.h>
+#include <linux/freezer.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/kthread.h>
+#include <linux/math.h>
+#include <linux/mfd/syscon.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MPFS_TVS_CTRL 0x08
+#define MPFS_TVS_OUTPUT0 0x24
+#define MPFS_TVS_OUTPUT1 0x28
+
+#define MPFS_TVS_CTRL_TEMP_VALID BIT(19)
+#define MPFS_TVS_CTRL_V2P5_VALID BIT(18)
+#define MPFS_TVS_CTRL_V1P8_VALID BIT(17)
+#define MPFS_TVS_CTRL_V1P05_VALID BIT(16)
+
+#define MPFS_TVS_CTRL_TEMP_ENABLE BIT(3)
+#define MPFS_TVS_CTRL_V2P5_ENABLE BIT(2)
+#define MPFS_TVS_CTRL_V1P8_ENABLE BIT(1)
+#define MPFS_TVS_CTRL_V1P05_ENABLE BIT(0)
+#define MPFS_TVS_CTRL_ENABLE_ALL GENMASK(3, 0)
+
+/*
+ * For all of these the value in millivolts is stored in 16 bits, with an upper
+ * sign bit and a lower 3 bits of decimal. These masks discard the sign bit and
+ * decimal places, because if Linux is running these voltages cannot be negative
+ * and so avoid having to convert to two's complement.
+ */
+#define MPFS_OUTPUT0_V1P8_MASK GENMASK(30, 19)
+#define MPFS_OUTPUT0_V1P05_MASK GENMASK(14, 3)
+#define MPFS_OUTPUT1_V2P5_MASK GENMASK(14, 3)
+
+/*
+ * The register map claims that the temperature is stored in bits 31:16, but
+ * application note "AN4682: PolarFire FPGA Temperature and Voltage Sensor"
+ * says that 31 is reserved. Temperature is in kelvin, so what's probably a
+ * sign bit has no value anyway.
+ */
+#define MPFS_OUTPUT1_TEMP_MASK GENMASK(30, 16)
+
+#define MPFS_TVS_INTERVAL_MASK GENMASK(15, 8)
+#define MPFS_TVS_INTERVAL_OFFSET 8
+/* The interval register is in increments of 32 us */
+#define MPFS_TVS_INTERVAL_SCALE 32
+/* with 254 usable increments of 32 us available, 8 ms is the integer limit */
+#define MPFS_TVS_INTERVAL_MAX_MS 8
+
+/* 273.1875 in 11.4 fixed-point notation */
+#define MPFS_TVS_K_TO_C 0x1113
+
+enum mpfs_tvs_sensors {
+ SENSOR_V1P05 = 0,
+ SENSOR_V1P8,
+ SENSOR_V2P5,
+};
+
+static const char * const mpfs_tvs_voltage_labels[] = { "1P05", "1P8", "2P5" };
+
+struct mpfs_tvs {
+ struct regmap *regmap;
+};
+
+static int mpfs_tvs_voltage_read(struct mpfs_tvs *data, u32 attr,
+ int channel, long *val)
+{
+ u32 tmp, control;
+
+ if (attr != hwmon_in_input && attr != hwmon_in_enable)
+ return -EOPNOTSUPP;
+
+ regmap_read(data->regmap, MPFS_TVS_CTRL, &control);
+
+ switch (channel) {
+ case SENSOR_V2P5:
+ if (attr == hwmon_in_enable) {
+ *val = FIELD_GET(MPFS_TVS_CTRL_V2P5_ENABLE, control);
+ break;
+ }
+
+ if (!(control & MPFS_TVS_CTRL_V2P5_VALID))
+ return -ENODATA;
+
+ regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp);
+ *val = FIELD_GET(MPFS_OUTPUT1_V2P5_MASK, tmp);
+ break;
+ case SENSOR_V1P8:
+ if (attr == hwmon_in_enable) {
+ *val = FIELD_GET(MPFS_TVS_CTRL_V1P8_ENABLE, control);
+ break;
+ }
+
+ if (!(control & MPFS_TVS_CTRL_V1P8_VALID))
+ return -ENODATA;
+
+ regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp);
+ *val = FIELD_GET(MPFS_OUTPUT0_V1P8_MASK, tmp);
+ break;
+ case SENSOR_V1P05:
+ if (attr == hwmon_in_enable) {
+ *val = FIELD_GET(MPFS_TVS_CTRL_V1P05_ENABLE, control);
+ break;
+ }
+
+ if (!(control & MPFS_TVS_CTRL_V1P05_VALID))
+ return -ENODATA;
+
+ regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp);
+ *val = FIELD_GET(MPFS_OUTPUT0_V1P05_MASK, tmp);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int mpfs_tvs_voltage_write(struct mpfs_tvs *data, u32 attr,
+ int channel, long val)
+{
+ u32 tmp;
+
+ if (attr != hwmon_in_enable)
+ return -EOPNOTSUPP;
+
+ if (val > 1 || val < 0)
+ return -EINVAL;
+
+ switch (channel) {
+ case SENSOR_V2P5:
+ tmp = FIELD_PREP(MPFS_TVS_CTRL_V2P5_ENABLE, val);
+ regmap_update_bits(data->regmap, MPFS_TVS_CTRL,
+ MPFS_TVS_CTRL_V2P5_ENABLE, tmp);
+ break;
+ case SENSOR_V1P8:
+ tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P8_ENABLE, val);
+ regmap_update_bits(data->regmap, MPFS_TVS_CTRL,
+ MPFS_TVS_CTRL_V1P8_ENABLE, tmp);
+ break;
+ case SENSOR_V1P05:
+ tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P05_ENABLE, val);
+ regmap_update_bits(data->regmap, MPFS_TVS_CTRL,
+ MPFS_TVS_CTRL_V1P05_ENABLE, tmp);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int mpfs_tvs_temp_read(struct mpfs_tvs *data, u32 attr, long *val)
+{
+ u32 tmp, control;
+
+ if (attr != hwmon_temp_input && attr != hwmon_temp_enable)
+ return -EOPNOTSUPP;
+
+ regmap_read(data->regmap, MPFS_TVS_CTRL, &control);
+
+ if (attr == hwmon_temp_enable) {
+ *val = FIELD_GET(MPFS_TVS_CTRL_TEMP_ENABLE, control);
+ return 0;
+ }
+
+ if (!(control & MPFS_TVS_CTRL_TEMP_VALID))
+ return -ENODATA;
+
+ regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp);
+ *val = FIELD_GET(MPFS_OUTPUT1_TEMP_MASK, tmp);
+ *val -= MPFS_TVS_K_TO_C;
+ *val = (1000 * *val) >> 4; /* fixed point (11.4) to millidegrees */
+
+ return 0;
+}
+
+static int mpfs_tvs_temp_write(struct mpfs_tvs *data, u32 attr, long val)
+{
+ u32 tmp;
+
+ if (attr != hwmon_temp_enable)
+ return -EOPNOTSUPP;
+
+ if (val > 1 || val < 0)
+ return -EINVAL;
+
+ tmp = FIELD_PREP(MPFS_TVS_CTRL_TEMP_ENABLE, val);
+ regmap_update_bits(data->regmap, MPFS_TVS_CTRL,
+ MPFS_TVS_CTRL_TEMP_ENABLE, tmp);
+
+ return 0;
+}
+
+static int mpfs_tvs_interval_read(struct mpfs_tvs *data, u32 attr, long *val)
+{
+ u32 tmp;
+
+ if (attr != hwmon_chip_update_interval)
+ return -EOPNOTSUPP;
+
+ regmap_read(data->regmap, MPFS_TVS_CTRL, &tmp);
+ *val = FIELD_GET(MPFS_TVS_INTERVAL_MASK, tmp);
+ *val *= MPFS_TVS_INTERVAL_SCALE;
+ *val = roundup(*val, 1000);
+ *val /= 1000;
+
+ return 0;
+}
+
+static int mpfs_tvs_interval_write(struct mpfs_tvs *data, u32 attr, long val)
+{
+ long temp = val;
+
+ if (attr != hwmon_chip_update_interval)
+ return -EOPNOTSUPP;
+
+ temp = clamp(temp, 0, MPFS_TVS_INTERVAL_MAX_MS);
+
+ temp *= 1000;
+ temp /= MPFS_TVS_INTERVAL_SCALE;
+
+ temp <<= MPFS_TVS_INTERVAL_OFFSET;
+ regmap_update_bits(data->regmap, MPFS_TVS_CTRL,
+ MPFS_TVS_INTERVAL_MASK, temp);
+
+ 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_enable:
+ return 0644;
+ case hwmon_temp_input:
+ case hwmon_temp_label:
+ return 0444;
+ default:
+ return 0;
+ }
+ }
+
+ if (type == hwmon_in) {
+ switch (attr) {
+ case hwmon_in_enable:
+ return 0644;
+ 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, val);
+ case hwmon_in:
+ return mpfs_tvs_voltage_read(data, attr, channel, val);
+ case hwmon_chip:
+ return mpfs_tvs_interval_read(data, attr, 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_temp:
+ return mpfs_tvs_temp_write(data, attr, val);
+ case hwmon_in:
+ return mpfs_tvs_voltage_write(data, attr, channel, val);
+ case hwmon_chip:
+ return mpfs_tvs_interval_write(data, attr, 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 = "Die 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_LABEL | HWMON_T_ENABLE),
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE,
+ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE,
+ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE),
+ NULL
+};
+
+static const struct hwmon_chip_info mpfs_tvs_chip_info = {
+ .ops = &mpfs_tvs_ops,
+ .info = mpfs_tvs_info,
+};
+
+static int mpfs_tvs_probe(struct platform_device *pdev)
+{
+ struct device *hwmon_dev;
+ struct mpfs_tvs *data;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->regmap = device_node_to_regmap(pdev->dev.parent->of_node);
+ if (IS_ERR(data->regmap))
+ return dev_err_probe(&pdev->dev, PTR_ERR(data->regmap),
+ "Failed to find syscon regmap\n");
+
+ /*
+ * It's an MMIO regmap with no resources, there's nothing that can fail
+ * and return an error
+ */
+ regmap_write(data->regmap, MPFS_TVS_CTRL, MPFS_TVS_CTRL_ENABLE_ALL);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "mpfs_tvs",
+ data,
+ &mpfs_tvs_chip_info,
+ NULL);
+ if (IS_ERR(hwmon_dev))
+ return dev_err_probe(&pdev->dev, PTR_ERR(hwmon_dev),
+ "hwmon device registration failed.\n");
+
+ return 0;
+}
+
+static struct platform_driver mpfs_tvs_driver = {
+ .probe = mpfs_tvs_probe,
+ .driver = {
+ .name = "mpfs-tvs",
+ },
+};
+module_platform_driver(mpfs_tvs_driver);
+
+MODULE_AUTHOR("Lars Randers <lranders@mail.dk>");
+MODULE_DESCRIPTION("PolarFire SoC temperature & voltage sensor driver");
+MODULE_LICENSE("GPL");