diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 8b655e5d6b68b9..84a5339e1d6fa6 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -262,6 +262,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 00000000000000..39889c06ef0c94 --- /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 + - Lars Randers + +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 is in microseconds, with a permitted range of 0 to 8128. + +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 c2c6d79275c6eb..cbb23168d11faa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22983,6 +22983,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 14e4cea48acc47..194c8116fd018d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -930,6 +930,18 @@ 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_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_POWERZ tristate "ChargerLAB POWER-Z USB-C tester" depends on USB diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 982ee2c6f9deb6..e2a060dea83519 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -194,6 +194,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_PT5161L) += pt5161l.o diff --git a/drivers/hwmon/tvs-mpfs.c b/drivers/hwmon/tvs-mpfs.c new file mode 100644 index 00000000000000..30d7baa808c4aa --- /dev/null +++ b/drivers/hwmon/tvs-mpfs.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Author: Lars Randers + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 2 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 + +/* 273.5 in 11.4 fixed-point notation */ +#define MPFS_TVS_K_TO_C 0x1112 + +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 -EINVAL; + + 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 -EINVAL; + + 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 -EINVAL; + + 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; + + switch (channel) { + case SENSOR_V2P5: + if (val > 1) + return -EINVAL; + + 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: + if (val > 1) + return -EINVAL; + + 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: + if (val > 1) + return -EINVAL; + + 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 -EINVAL; + + 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) + 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); + //TODO: Guenter, update_interval, as implemented here, expects values + //in microseconds. Is that okay? Most drivers (all that I checked tbh) + //use milliseconds, but this is an 8-bit value so using ms will have + //almost no granularity at all. + *val = FIELD_GET(MPFS_TVS_INTERVAL_MASK, tmp) * MPFS_TVS_INTERVAL_SCALE; + + return 0; +} + +static int mpfs_tvs_interval_write(struct mpfs_tvs *data, u32 attr, long val) +{ + unsigned long temp = val; + + if (attr != hwmon_chip_update_interval) + return -EOPNOTSUPP; + + temp /= MPFS_TVS_INTERVAL_SCALE; + + /* + * The value is 8 bits wide, but 255 is described as + * "255= Do single set of transfers when scoverride set" + * but there's no scoverride bit in the tvs register region. + * Ban using 255 since its behaviour is suspect. + */ + if (temp > 254) + return -EINVAL; + + 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"); + + 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"); + + regmap_write(data->regmap, MPFS_TVS_CTRL, MPFS_TVS_CTRL_ENABLE_ALL); + + 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 "); +MODULE_DESCRIPTION("PolarFire SoC temperature & voltage sensor driver"); +MODULE_LICENSE("GPL");