Message ID | 20220401214032.3738095-1-michael@walle.cc |
---|---|
Headers | show |
Series | hwmon: add lan9668 driver | expand |
Hi, Am 2022-04-01 23:40, schrieb Michael Walle: > Add a temperature and fan controller driver for the Microchip LAN9668 > SoC. > > The temperature sensor uses a polynomial to calculate the actual > temperature. Fortunately, the bt1-pvt already has such a calculation. > It seems that the LAN9668 uses the same Analog Bits sensor as the > BT1 although with a different characteristic. To be able to reuse the > code move it to lib/ as it seems pretty generic to calculate any > polynomial using integers only, which might also be used by other parts > of the kernel. Another option might be to move the code to > hwmon-poly.c, > I'm not sure. Thoughts? > > I also plan on submitting patches to add temperature sensor support for > the GPYxxx and LAN8814 PHYs which also use polynomial_calc(). > > The last two patches adds the actual driver and the dt-binding for it. > > changes since v3: > - validate input frequency in lan966x_hwmon_write_pwm_freq() > - enable sensor before registering hwmon device > - automatically disable sensor when driver is removed > - set the required clock devider in case someone changed the > hardware default before the driver is loaded > - remove extra empty lines > > changes since v2: > - strip unwanted copy pasta.. oops > - use "select REGMAP" instead of "depends on" > > changes since v1: > - add doc string to polynomial_calc(), moved the comment > into the function. > - add missing "select POLYNOMIAL" to the bt1_pvt driver > Kconfig symbol > - add hwmon driver documentation > - cache sys_clk rate during probe > - add missing ERR_CAST() > - adapted comment for the PPS->RPM calculation > - add temporary variable in lan966x_hwmon_read_pwm_freq() > > Michael Walle (4): > lib: add generic polynomial calculation > hwmon: (bt1-pvt) use generic polynomial functions > dt-bindings: hwmon: add Microchip LAN966x bindings > hwmon: add driver for the Microchip LAN966x SoC Any news here? Or did I miss anything? -michael
On 4/18/22 10:44, Michael Walle wrote: > Hi, > > Am 2022-04-01 23:40, schrieb Michael Walle: >> Add a temperature and fan controller driver for the Microchip LAN9668 SoC. >> >> The temperature sensor uses a polynomial to calculate the actual >> temperature. Fortunately, the bt1-pvt already has such a calculation. >> It seems that the LAN9668 uses the same Analog Bits sensor as the >> BT1 although with a different characteristic. To be able to reuse the >> code move it to lib/ as it seems pretty generic to calculate any >> polynomial using integers only, which might also be used by other parts >> of the kernel. Another option might be to move the code to hwmon-poly.c, >> I'm not sure. Thoughts? >> >> I also plan on submitting patches to add temperature sensor support for >> the GPYxxx and LAN8814 PHYs which also use polynomial_calc(). >> >> The last two patches adds the actual driver and the dt-binding for it. >> >> changes since v3: >> - validate input frequency in lan966x_hwmon_write_pwm_freq() >> - enable sensor before registering hwmon device >> - automatically disable sensor when driver is removed >> - set the required clock devider in case someone changed the >> hardware default before the driver is loaded >> - remove extra empty lines >> >> changes since v2: >> - strip unwanted copy pasta.. oops >> - use "select REGMAP" instead of "depends on" >> >> changes since v1: >> - add doc string to polynomial_calc(), moved the comment >> into the function. >> - add missing "select POLYNOMIAL" to the bt1_pvt driver >> Kconfig symbol >> - add hwmon driver documentation >> - cache sys_clk rate during probe >> - add missing ERR_CAST() >> - adapted comment for the PPS->RPM calculation >> - add temporary variable in lan966x_hwmon_read_pwm_freq() >> >> Michael Walle (4): >> lib: add generic polynomial calculation >> hwmon: (bt1-pvt) use generic polynomial functions >> dt-bindings: hwmon: add Microchip LAN966x bindings >> hwmon: add driver for the Microchip LAN966x SoC > > Any news here? Or did I miss anything? > > -michael Just way behind with everything Guenter
On 4/1/22 14:40, Michael Walle wrote: > Add support for the temperatur sensor and the fan controller on the > Microchip LAN966x SoC. Apparently, an Analog Bits PVT sensor is used > which can measure temperature and process voltages. But only a forumlae > for the temperature sensor is known. Additionally, the SoC support a fan > tacho input as well as a PWM signal to control the fan. > > Signed-off-by: Michael Walle <michael@walle.cc> For my reference: Reviewed-by: Guenter Roeck <linux@roeck-us.net> I'll apply as soon as the devicetree file has been approved. Guenter > --- > Just in case someone is curious why the I state the datasheet is wrong > on the PWM frequency: The actual PWM frequency was verified by measuring > the output with an oscilloscope. Also you can write a '0' as the PWM > frequency which would result in div-by-zero; but what happens is that > the output frequency will be half the frequency of a setting of '1'. > > Documentation/hwmon/lan966x.rst | 40 +++ > drivers/hwmon/Kconfig | 12 + > drivers/hwmon/Makefile | 1 + > drivers/hwmon/lan966x-hwmon.c | 418 ++++++++++++++++++++++++++++++++ > 4 files changed, 471 insertions(+) > create mode 100644 Documentation/hwmon/lan966x.rst > create mode 100644 drivers/hwmon/lan966x-hwmon.c > > diff --git a/Documentation/hwmon/lan966x.rst b/Documentation/hwmon/lan966x.rst > new file mode 100644 > index 000000000000..1d1724afa5d2 > --- /dev/null > +++ b/Documentation/hwmon/lan966x.rst > @@ -0,0 +1,40 @@ > +.. SPDX-License-Identifier: GPL-2.0 > + > +Kernel driver lan966x-hwmon > +=========================== > + > +Supported chips: > + > + * Microchip LAN9668 (sensor in SoC) > + > + Prefix: 'lan9668-hwmon' > + > + Datasheet: https://microchip-ung.github.io/lan9668_reginfo > + > +Authors: > + > + Michael Walle <michael@walle.cc> > + > +Description > +----------- > + > +This driver implements support for the Microchip LAN9668 on-chip > +temperature sensor as well as its fan controller. It provides one > +temperature sensor and one fan controller. The temperature range > +of the sensor is specified from -40 to +125 degrees Celsius and > +its accuracy is +/- 5 degrees Celsius. The fan controller has a > +tacho input and a PWM output with a customizable PWM output > +frequency ranging from ~20Hz to ~650kHz. > + > +No alarms are supported by the SoC. > + > +The driver exports temperature values, fan tacho input and PWM > +settings via the following sysfs files: > + > +**temp1_input** > + > +**fan1_input** > + > +**pwm1** > + > +**pwm1_freq** > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig > index be9773270e53..052b37b78919 100644 > --- a/drivers/hwmon/Kconfig > +++ b/drivers/hwmon/Kconfig > @@ -815,6 +815,18 @@ config SENSORS_POWR1220 > This driver can also be built as a module. If so, the module > will be called powr1220. > > +config SENSORS_LAN966X > + tristate "Microchip LAN966x Hardware Monitoring" > + depends on SOC_LAN966 || COMPILE_TEST > + select REGMAP > + select POLYNOMIAL > + help > + If you say yes here you get support for temperature monitoring > + on the Microchip LAN966x SoC. > + > + This driver can also be built as a module. If so, the module > + will be called lan966x-hwmon. > + > config SENSORS_LINEAGE > tristate "Lineage Compact Power Line Power Entry Module" > depends on I2C > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile > index 8a03289e2aa4..51ca6956f8b7 100644 > --- a/drivers/hwmon/Makefile > +++ b/drivers/hwmon/Makefile > @@ -100,6 +100,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o > obj-$(CONFIG_SENSORS_JC42) += jc42.o > obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o > obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o > +obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o > obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o > obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o > obj-$(CONFIG_SENSORS_LM63) += lm63.o > diff --git a/drivers/hwmon/lan966x-hwmon.c b/drivers/hwmon/lan966x-hwmon.c > new file mode 100644 > index 000000000000..f41df053ac31 > --- /dev/null > +++ b/drivers/hwmon/lan966x-hwmon.c > @@ -0,0 +1,418 @@ > +// SPDX-License-Identifier: GPL-2.0-only > + > +#include <linux/bitfield.h> > +#include <linux/clk.h> > +#include <linux/hwmon.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/mod_devicetable.h> > +#include <linux/platform_device.h> > +#include <linux/polynomial.h> > +#include <linux/regmap.h> > + > +/* > + * The original translation formulae of the temperature (in degrees of Celsius) > + * are as follows: > + * > + * T = -3.4627e-11*(N^4) + 1.1023e-7*(N^3) + -1.9165e-4*(N^2) + > + * 3.0604e-1*(N^1) + -5.6197e1 > + * > + * where [-56.197, 136.402]C and N = [0, 1023]. > + * > + * They must be accordingly altered to be suitable for the integer arithmetics. > + * The technique is called 'factor redistribution', which just makes sure the > + * multiplications and divisions are made so to have a result of the operations > + * within the integer numbers limit. In addition we need to translate the > + * formulae to accept millidegrees of Celsius. Here what it looks like after > + * the alterations: > + * > + * T = -34627e-12*(N^4) + 110230e-9*(N^3) + -191650e-6*(N^2) + > + * 306040e-3*(N^1) + -56197 > + * > + * where T = [-56197, 136402]mC and N = [0, 1023]. > + */ > + > +static const struct polynomial poly_N_to_temp = { > + .terms = { > + {4, -34627, 1000, 1}, > + {3, 110230, 1000, 1}, > + {2, -191650, 1000, 1}, > + {1, 306040, 1000, 1}, > + {0, -56197, 1, 1} > + } > +}; > + > +#define PVT_SENSOR_CTRL 0x0 /* unused */ > +#define PVT_SENSOR_CFG 0x4 > +#define SENSOR_CFG_CLK_CFG GENMASK(27, 20) > +#define SENSOR_CFG_TRIM_VAL GENMASK(13, 9) > +#define SENSOR_CFG_SAMPLE_ENA BIT(8) > +#define SENSOR_CFG_START_CAPTURE BIT(7) > +#define SENSOR_CFG_CONTINIOUS_MODE BIT(6) > +#define SENSOR_CFG_PSAMPLE_ENA GENMASK(1, 0) > +#define PVT_SENSOR_STAT 0x8 > +#define SENSOR_STAT_DATA_VALID BIT(10) > +#define SENSOR_STAT_DATA GENMASK(9, 0) > + > +#define FAN_CFG 0x0 > +#define FAN_CFG_DUTY_CYCLE GENMASK(23, 16) > +#define INV_POL BIT(3) > +#define GATE_ENA BIT(2) > +#define PWM_OPEN_COL_ENA BIT(1) > +#define FAN_STAT_CFG BIT(0) > +#define FAN_PWM_FREQ 0x4 > +#define FAN_PWM_CYC_10US GENMASK(25, 15) > +#define FAN_PWM_FREQ_FREQ GENMASK(14, 0) > +#define FAN_CNT 0xc > +#define FAN_CNT_DATA GENMASK(15, 0) > + > +#define LAN966X_PVT_CLK 1200000 /* 1.2 MHz */ > + > +struct lan966x_hwmon { > + struct regmap *regmap_pvt; > + struct regmap *regmap_fan; > + struct clk *clk; > + unsigned long clk_rate; > +}; > + > +static int lan966x_hwmon_read_temp(struct device *dev, long *val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + unsigned int data; > + int ret; > + > + ret = regmap_read(hwmon->regmap_pvt, PVT_SENSOR_STAT, &data); > + if (ret < 0) > + return ret; > + > + if (!(data & SENSOR_STAT_DATA_VALID)) > + return -ENODATA; > + > + *val = polynomial_calc(&poly_N_to_temp, > + FIELD_GET(SENSOR_STAT_DATA, data)); > + > + return 0; > +} > + > +static int lan966x_hwmon_read_fan(struct device *dev, long *val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + unsigned int data; > + int ret; > + > + ret = regmap_read(hwmon->regmap_fan, FAN_CNT, &data); > + if (ret < 0) > + return ret; > + > + /* > + * Data is given in pulses per second. Assume two pulses > + * per revolution. > + */ > + *val = FIELD_GET(FAN_CNT_DATA, data) * 60 / 2; > + > + return 0; > +} > + > +static int lan966x_hwmon_read_pwm(struct device *dev, long *val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + unsigned int data; > + int ret; > + > + ret = regmap_read(hwmon->regmap_fan, FAN_CFG, &data); > + if (ret < 0) > + return ret; > + > + *val = FIELD_GET(FAN_CFG_DUTY_CYCLE, data); > + > + return 0; > +} > + > +static int lan966x_hwmon_read_pwm_freq(struct device *dev, long *val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + unsigned long tmp; > + unsigned int data; > + int ret; > + > + ret = regmap_read(hwmon->regmap_fan, FAN_PWM_FREQ, &data); > + if (ret < 0) > + return ret; > + > + /* > + * Datasheet says it is sys_clk / 256 / pwm_freq. But in reality > + * it is sys_clk / 256 / (pwm_freq + 1). > + */ > + data = FIELD_GET(FAN_PWM_FREQ_FREQ, data) + 1; > + tmp = DIV_ROUND_CLOSEST(hwmon->clk_rate, 256); > + *val = DIV_ROUND_CLOSEST(tmp, data); > + > + return 0; > +} > + > +static int lan966x_hwmon_read(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long *val) > +{ > + switch (type) { > + case hwmon_temp: > + return lan966x_hwmon_read_temp(dev, val); > + case hwmon_fan: > + return lan966x_hwmon_read_fan(dev, val); > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + return lan966x_hwmon_read_pwm(dev, val); > + case hwmon_pwm_freq: > + return lan966x_hwmon_read_pwm_freq(dev, val); > + default: > + return -EOPNOTSUPP; > + } > + default: > + return -EOPNOTSUPP; > + } > +} > + > +static int lan966x_hwmon_write_pwm(struct device *dev, long val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + > + if (val < 0 || val > 255) > + return -EINVAL; > + > + return regmap_update_bits(hwmon->regmap_fan, FAN_CFG, > + FAN_CFG_DUTY_CYCLE, > + FIELD_PREP(FAN_CFG_DUTY_CYCLE, val)); > +} > + > +static int lan966x_hwmon_write_pwm_freq(struct device *dev, long val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + > + if (val <= 0) > + return -EINVAL; > + > + val = DIV_ROUND_CLOSEST(hwmon->clk_rate, val); > + val = DIV_ROUND_CLOSEST(val, 256) - 1; > + val = clamp_val(val, 0, FAN_PWM_FREQ_FREQ); > + > + return regmap_update_bits(hwmon->regmap_fan, FAN_PWM_FREQ, > + FAN_PWM_FREQ_FREQ, > + FIELD_PREP(FAN_PWM_FREQ_FREQ, val)); > +} > + > +static int lan966x_hwmon_write(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long val) > +{ > + switch (type) { > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + return lan966x_hwmon_write_pwm(dev, val); > + case hwmon_pwm_freq: > + return lan966x_hwmon_write_pwm_freq(dev, val); > + default: > + return -EOPNOTSUPP; > + } > + default: > + return -EOPNOTSUPP; > + } > +} > + > +static umode_t lan966x_hwmon_is_visible(const void *data, > + enum hwmon_sensor_types type, > + u32 attr, int channel) > +{ > + umode_t mode = 0; > + > + switch (type) { > + case hwmon_temp: > + switch (attr) { > + case hwmon_temp_input: > + mode = 0444; > + break; > + default: > + break; > + } > + break; > + case hwmon_fan: > + switch (attr) { > + case hwmon_fan_input: > + mode = 0444; > + break; > + default: > + break; > + } > + break; > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + case hwmon_pwm_freq: > + mode = 0644; > + break; > + default: > + break; > + } > + break; > + default: > + break; > + } > + > + return mode; > +} > + > +static const struct hwmon_channel_info *lan966x_hwmon_info[] = { > + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), > + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), > + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), > + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_FREQ), > + NULL > +}; > + > +static const struct hwmon_ops lan966x_hwmon_ops = { > + .is_visible = lan966x_hwmon_is_visible, > + .read = lan966x_hwmon_read, > + .write = lan966x_hwmon_write, > +}; > + > +static const struct hwmon_chip_info lan966x_hwmon_chip_info = { > + .ops = &lan966x_hwmon_ops, > + .info = lan966x_hwmon_info, > +}; > + > +static void lan966x_hwmon_disable(void *data) > +{ > + struct lan966x_hwmon *hwmon = data; > + > + regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG, > + SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE, > + 0); > +} > + > +static int lan966x_hwmon_enable(struct device *dev, > + struct lan966x_hwmon *hwmon) > +{ > + unsigned int mask = SENSOR_CFG_CLK_CFG | > + SENSOR_CFG_SAMPLE_ENA | > + SENSOR_CFG_START_CAPTURE | > + SENSOR_CFG_CONTINIOUS_MODE | > + SENSOR_CFG_PSAMPLE_ENA; > + unsigned int val; > + unsigned int div; > + int ret; > + > + /* enable continuous mode */ > + val = SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE; > + > + /* set PVT clock to be between 1.15 and 1.25 MHz */ > + div = DIV_ROUND_CLOSEST(hwmon->clk_rate, LAN966X_PVT_CLK); > + val |= FIELD_PREP(SENSOR_CFG_CLK_CFG, div); > + > + ret = regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG, > + mask, val); > + if (ret) > + return ret; > + > + return devm_add_action_or_reset(dev, lan966x_hwmon_disable, hwmon); > +} > + > +static struct regmap *lan966x_init_regmap(struct platform_device *pdev, > + const char *name) > +{ > + struct regmap_config regmap_config = { > + .reg_bits = 32, > + .reg_stride = 4, > + .val_bits = 32, > + }; > + void __iomem *base; > + > + base = devm_platform_ioremap_resource_byname(pdev, name); > + if (IS_ERR(base)) > + return ERR_CAST(base); > + > + regmap_config.name = name; > + > + return devm_regmap_init_mmio(&pdev->dev, base, ®map_config); > +} > + > +static void lan966x_clk_disable(void *data) > +{ > + struct lan966x_hwmon *hwmon = data; > + > + clk_disable_unprepare(hwmon->clk); > +} > + > +static int lan966x_clk_enable(struct device *dev, struct lan966x_hwmon *hwmon) > +{ > + int ret; > + > + ret = clk_prepare_enable(hwmon->clk); > + if (ret) > + return ret; > + > + return devm_add_action_or_reset(dev, lan966x_clk_disable, hwmon); > +} > + > +static int lan966x_hwmon_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct lan966x_hwmon *hwmon; > + struct device *hwmon_dev; > + int ret; > + > + hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); > + if (!hwmon) > + return -ENOMEM; > + > + hwmon->clk = devm_clk_get(dev, NULL); > + if (IS_ERR(hwmon->clk)) > + return dev_err_probe(dev, PTR_ERR(hwmon->clk), > + "failed to get clock\n"); > + > + ret = lan966x_clk_enable(dev, hwmon); > + if (ret) > + return dev_err_probe(dev, ret, "failed to enable clock\n"); > + > + hwmon->clk_rate = clk_get_rate(hwmon->clk); > + > + hwmon->regmap_pvt = lan966x_init_regmap(pdev, "pvt"); > + if (IS_ERR(hwmon->regmap_pvt)) > + return dev_err_probe(dev, PTR_ERR(hwmon->regmap_pvt), > + "failed to get regmap for PVT registers\n"); > + > + hwmon->regmap_fan = lan966x_init_regmap(pdev, "fan"); > + if (IS_ERR(hwmon->regmap_fan)) > + return dev_err_probe(dev, PTR_ERR(hwmon->regmap_fan), > + "failed to get regmap for fan registers\n"); > + > + ret = lan966x_hwmon_enable(dev, hwmon); > + if (ret) > + return dev_err_probe(dev, ret, "failed to enable sensor\n"); > + > + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, > + "lan966x_hwmon", hwmon, > + &lan966x_hwmon_chip_info, NULL); > + if (IS_ERR(hwmon_dev)) > + return dev_err_probe(dev, PTR_ERR(hwmon_dev), > + "failed to register hwmon device\n"); > + > + return 0; > +} > + > +static const struct of_device_id lan966x_hwmon_of_match[] = { > + { .compatible = "microchip,lan9668-hwmon" }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, lan966x_hwmon_of_match); > + > +static struct platform_driver lan966x_hwmon_driver = { > + .probe = lan966x_hwmon_probe, > + .driver = { > + .name = "lan966x-hwmon", > + .of_match_table = lan966x_hwmon_of_match, > + }, > +}; > +module_platform_driver(lan966x_hwmon_driver); > + > +MODULE_DESCRIPTION("LAN966x Hardware Monitoring Driver"); > +MODULE_AUTHOR("Michael Walle <michael@walle.cc>"); > +MODULE_LICENSE("GPL");
Am 2022-04-24 18:38, schrieb Guenter Roeck: > On 4/1/22 14:40, Michael Walle wrote: >> Add support for the temperatur sensor and the fan controller on the >> Microchip LAN966x SoC. Apparently, an Analog Bits PVT sensor is used >> which can measure temperature and process voltages. But only a >> forumlae >> for the temperature sensor is known. Additionally, the SoC support a >> fan >> tacho input as well as a PWM signal to control the fan. >> >> Signed-off-by: Michael Walle <michael@walle.cc> > > For my reference: > > Reviewed-by: Guenter Roeck <linux@roeck-us.net> > > I'll apply as soon as the devicetree file has been approved. Thanks! Could you please make a stable branch with this series the hwmon_sanatize_names() series [1] so I can ask the netdev guys to merge it? -michael [1] https://lore.kernel.org/linux-hwmon/20220405092452.4033674-1-michael@walle.cc/
On Fri, Apr 01, 2022 at 11:40:29PM +0200, Michael Walle wrote: > Some temperature and voltage sensors use a polynomial to convert between > raw data points and actual temperature or voltage. The polynomial is > usually the result of a curve fitting of the diode characteristic. > > The BT1 PVT hwmon driver already uses such a polynonmial calculation > which is rather generic. Move it to lib/ so other drivers can reuse it. > > Signed-off-by: Michael Walle <michael@walle.cc> > Reviewed-by: Guenter Roeck <linux@roeck-us.net> Applied to hwmon-next. Thanks, Guenter > --- > include/linux/polynomial.h | 35 ++++++++++++ > lib/Kconfig | 3 ++ > lib/Makefile | 2 + > lib/polynomial.c | 108 +++++++++++++++++++++++++++++++++++++ > 4 files changed, 148 insertions(+) > create mode 100644 include/linux/polynomial.h > create mode 100644 lib/polynomial.c > > diff --git a/include/linux/polynomial.h b/include/linux/polynomial.h > new file mode 100644 > index 000000000000..9e074a0bb6fa > --- /dev/null > +++ b/include/linux/polynomial.h > @@ -0,0 +1,35 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC > + */ > + > +#ifndef _POLYNOMIAL_H > +#define _POLYNOMIAL_H > + > +/* > + * struct polynomial_term - one term descriptor of a polynomial > + * @deg: degree of the term. > + * @coef: multiplication factor of the term. > + * @divider: distributed divider per each degree. > + * @divider_leftover: divider leftover, which couldn't be redistributed. > + */ > +struct polynomial_term { > + unsigned int deg; > + long coef; > + long divider; > + long divider_leftover; > +}; > + > +/* > + * struct polynomial - a polynomial descriptor > + * @total_divider: total data divider. > + * @terms: polynomial terms, last term must have degree of 0 > + */ > +struct polynomial { > + long total_divider; > + struct polynomial_term terms[]; > +}; > + > +long polynomial_calc(const struct polynomial *poly, long data); > + > +#endif > diff --git a/lib/Kconfig b/lib/Kconfig > index 087e06b4cdfd..6a843639814f 100644 > --- a/lib/Kconfig > +++ b/lib/Kconfig > @@ -737,3 +737,6 @@ config PLDMFW > > config ASN1_ENCODER > tristate > + > +config POLYNOMIAL > + tristate > diff --git a/lib/Makefile b/lib/Makefile > index 6b9ffc1bd1ee..89fcae891361 100644 > --- a/lib/Makefile > +++ b/lib/Makefile > @@ -263,6 +263,8 @@ obj-$(CONFIG_MEMREGION) += memregion.o > obj-$(CONFIG_STMP_DEVICE) += stmp_device.o > obj-$(CONFIG_IRQ_POLL) += irq_poll.o > > +obj-$(CONFIG_POLYNOMIAL) += polynomial.o > + > # stackdepot.c should not be instrumented or call instrumented functions. > # Prevent the compiler from calling builtins like memcmp() or bcmp() from this > # file. > diff --git a/lib/polynomial.c b/lib/polynomial.c > new file mode 100644 > index 000000000000..66d383445fec > --- /dev/null > +++ b/lib/polynomial.c > @@ -0,0 +1,108 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Generic polynomial calculation using integer coefficients. > + * > + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC > + * > + * Authors: > + * Maxim Kaurkin <maxim.kaurkin@baikalelectronics.ru> > + * Serge Semin <Sergey.Semin@baikalelectronics.ru> > + * > + */ > + > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/polynomial.h> > + > +/* > + * Originally this was part of drivers/hwmon/bt1-pvt.c. > + * There the following conversion is used and should serve as an example here: > + * > + * The original translation formulae of the temperature (in degrees of Celsius) > + * to PVT data and vice-versa are following: > + * > + * N = 1.8322e-8*(T^4) + 2.343e-5*(T^3) + 8.7018e-3*(T^2) + 3.9269*(T^1) + > + * 1.7204e2 > + * T = -1.6743e-11*(N^4) + 8.1542e-8*(N^3) + -1.8201e-4*(N^2) + > + * 3.1020e-1*(N^1) - 4.838e1 > + * > + * where T = [-48.380, 147.438]C and N = [0, 1023]. > + * > + * They must be accordingly altered to be suitable for the integer arithmetics. > + * The technique is called 'factor redistribution', which just makes sure the > + * multiplications and divisions are made so to have a result of the operations > + * within the integer numbers limit. In addition we need to translate the > + * formulae to accept millidegrees of Celsius. Here what they look like after > + * the alterations: > + * > + * N = (18322e-20*(T^4) + 2343e-13*(T^3) + 87018e-9*(T^2) + 39269e-3*T + > + * 17204e2) / 1e4 > + * T = -16743e-12*(D^4) + 81542e-9*(D^3) - 182010e-6*(D^2) + 310200e-3*D - > + * 48380 > + * where T = [-48380, 147438] mC and N = [0, 1023]. > + * > + * static const struct polynomial poly_temp_to_N = { > + * .total_divider = 10000, > + * .terms = { > + * {4, 18322, 10000, 10000}, > + * {3, 2343, 10000, 10}, > + * {2, 87018, 10000, 10}, > + * {1, 39269, 1000, 1}, > + * {0, 1720400, 1, 1} > + * } > + * }; > + * > + * static const struct polynomial poly_N_to_temp = { > + * .total_divider = 1, > + * .terms = { > + * {4, -16743, 1000, 1}, > + * {3, 81542, 1000, 1}, > + * {2, -182010, 1000, 1}, > + * {1, 310200, 1000, 1}, > + * {0, -48380, 1, 1} > + * } > + * }; > + */ > + > +/** > + * polynomial_calc - calculate a polynomial using integer arithmetic > + * > + * @poly: pointer to the descriptor of the polynomial > + * @data: input value of the polynimal > + * > + * Calculate the result of a polynomial using only integer arithmetic. For > + * this to work without too much loss of precision the coefficients has to > + * be altered. This is called factor redistribution. > + * > + * Returns the result of the polynomial calculation. > + */ > +long polynomial_calc(const struct polynomial *poly, long data) > +{ > + const struct polynomial_term *term = poly->terms; > + long total_divider = poly->total_divider ?: 1; > + long tmp, ret = 0; > + int deg; > + > + /* > + * Here is the polynomial calculation function, which performs the > + * redistributed terms calculations. It's pretty straightforward. > + * We walk over each degree term up to the free one, and perform > + * the redistributed multiplication of the term coefficient, its > + * divider (as for the rationale fraction representation), data > + * power and the rational fraction divider leftover. Then all of > + * this is collected in a total sum variable, which value is > + * normalized by the total divider before being returned. > + */ > + do { > + tmp = term->coef; > + for (deg = 0; deg < term->deg; ++deg) > + tmp = mult_frac(tmp, data, term->divider); > + ret += tmp / term->divider_leftover; > + } while ((term++)->deg); > + > + return ret / total_divider; > +} > +EXPORT_SYMBOL_GPL(polynomial_calc); > + > +MODULE_DESCRIPTION("Generic polynomial calculations"); > +MODULE_LICENSE("GPL");
On Fri, Apr 01, 2022 at 11:40:30PM +0200, Michael Walle wrote: > The polynomial calculation function was moved into lib/ to be able to > reuse it. Move over to this one. > > Signed-off-by: Michael Walle <michael@walle.cc> > Reviewed-by: Guenter Roeck <linux@roeck-us.net> Applied to hwmon-next. Thanks, Guenter > --- > drivers/hwmon/Kconfig | 1 + > drivers/hwmon/bt1-pvt.c | 50 +++++++++++------------------------------ > 2 files changed, 14 insertions(+), 37 deletions(-) > > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig > index 68a8a27ab3b7..be9773270e53 100644 > --- a/drivers/hwmon/Kconfig > +++ b/drivers/hwmon/Kconfig > @@ -415,6 +415,7 @@ config SENSORS_ATXP1 > config SENSORS_BT1_PVT > tristate "Baikal-T1 Process, Voltage, Temperature sensor driver" > depends on MIPS_BAIKAL_T1 || COMPILE_TEST > + select POLYNOMIAL > help > If you say yes here you get support for Baikal-T1 PVT sensor > embedded into the SoC. > diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c > index 74ce5211eb75..21ab172774ec 100644 > --- a/drivers/hwmon/bt1-pvt.c > +++ b/drivers/hwmon/bt1-pvt.c > @@ -26,6 +26,7 @@ > #include <linux/mutex.h> > #include <linux/of.h> > #include <linux/platform_device.h> > +#include <linux/polynomial.h> > #include <linux/seqlock.h> > #include <linux/sysfs.h> > #include <linux/types.h> > @@ -65,7 +66,7 @@ static const struct pvt_sensor_info pvt_info[] = { > * 48380, > * where T = [-48380, 147438] mC and N = [0, 1023]. > */ > -static const struct pvt_poly __maybe_unused poly_temp_to_N = { > +static const struct polynomial __maybe_unused poly_temp_to_N = { > .total_divider = 10000, > .terms = { > {4, 18322, 10000, 10000}, > @@ -76,7 +77,7 @@ static const struct pvt_poly __maybe_unused poly_temp_to_N = { > } > }; > > -static const struct pvt_poly poly_N_to_temp = { > +static const struct polynomial poly_N_to_temp = { > .total_divider = 1, > .terms = { > {4, -16743, 1000, 1}, > @@ -97,7 +98,7 @@ static const struct pvt_poly poly_N_to_temp = { > * N = (18658e-3*V - 11572) / 10, > * V = N * 10^5 / 18658 + 11572 * 10^4 / 18658. > */ > -static const struct pvt_poly __maybe_unused poly_volt_to_N = { > +static const struct polynomial __maybe_unused poly_volt_to_N = { > .total_divider = 10, > .terms = { > {1, 18658, 1000, 1}, > @@ -105,7 +106,7 @@ static const struct pvt_poly __maybe_unused poly_volt_to_N = { > } > }; > > -static const struct pvt_poly poly_N_to_volt = { > +static const struct polynomial poly_N_to_volt = { > .total_divider = 10, > .terms = { > {1, 100000, 18658, 1}, > @@ -113,31 +114,6 @@ static const struct pvt_poly poly_N_to_volt = { > } > }; > > -/* > - * Here is the polynomial calculation function, which performs the > - * redistributed terms calculations. It's pretty straightforward. We walk > - * over each degree term up to the free one, and perform the redistributed > - * multiplication of the term coefficient, its divider (as for the rationale > - * fraction representation), data power and the rational fraction divider > - * leftover. Then all of this is collected in a total sum variable, which > - * value is normalized by the total divider before being returned. > - */ > -static long pvt_calc_poly(const struct pvt_poly *poly, long data) > -{ > - const struct pvt_poly_term *term = poly->terms; > - long tmp, ret = 0; > - int deg; > - > - do { > - tmp = term->coef; > - for (deg = 0; deg < term->deg; ++deg) > - tmp = mult_frac(tmp, data, term->divider); > - ret += tmp / term->divider_leftover; > - } while ((term++)->deg); > - > - return ret / poly->total_divider; > -} > - > static inline u32 pvt_update(void __iomem *reg, u32 mask, u32 data) > { > u32 old; > @@ -324,9 +300,9 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, > } while (read_seqretry(&cache->data_seqlock, seq)); > > if (type == PVT_TEMP) > - *val = pvt_calc_poly(&poly_N_to_temp, data); > + *val = polynomial_calc(&poly_N_to_temp, data); > else > - *val = pvt_calc_poly(&poly_N_to_volt, data); > + *val = polynomial_calc(&poly_N_to_volt, data); > > return 0; > } > @@ -345,9 +321,9 @@ static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, > data = FIELD_GET(PVT_THRES_HI_MASK, data); > > if (type == PVT_TEMP) > - *val = pvt_calc_poly(&poly_N_to_temp, data); > + *val = polynomial_calc(&poly_N_to_temp, data); > else > - *val = pvt_calc_poly(&poly_N_to_volt, data); > + *val = polynomial_calc(&poly_N_to_volt, data); > > return 0; > } > @@ -360,10 +336,10 @@ static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, > > if (type == PVT_TEMP) { > val = clamp(val, PVT_TEMP_MIN, PVT_TEMP_MAX); > - data = pvt_calc_poly(&poly_temp_to_N, val); > + data = polynomial_calc(&poly_temp_to_N, val); > } else { > val = clamp(val, PVT_VOLT_MIN, PVT_VOLT_MAX); > - data = pvt_calc_poly(&poly_volt_to_N, val); > + data = polynomial_calc(&poly_volt_to_N, val); > } > > /* Serialize limit update, since a part of the register is changed. */ > @@ -522,9 +498,9 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, > return -ETIMEDOUT; > > if (type == PVT_TEMP) > - *val = pvt_calc_poly(&poly_N_to_temp, data); > + *val = polynomial_calc(&poly_N_to_temp, data); > else > - *val = pvt_calc_poly(&poly_N_to_volt, data); > + *val = polynomial_calc(&poly_N_to_volt, data); > > return 0; > }
On Fri, Apr 01, 2022 at 11:40:32PM +0200, Michael Walle wrote: > Add support for the temperatur sensor and the fan controller on the > Microchip LAN966x SoC. Apparently, an Analog Bits PVT sensor is used > which can measure temperature and process voltages. But only a forumlae > for the temperature sensor is known. Additionally, the SoC support a fan > tacho input as well as a PWM signal to control the fan. > > Signed-off-by: Michael Walle <michael@walle.cc> > Reviewed-by: Guenter Roeck <linux@roeck-us.net> Applied to hwmon-next. Thanks, Guenter > --- > Just in case someone is curious why the I state the datasheet is wrong > on the PWM frequency: The actual PWM frequency was verified by measuring > the output with an oscilloscope. Also you can write a '0' as the PWM > frequency which would result in div-by-zero; but what happens is that > the output frequency will be half the frequency of a setting of '1'. > > Documentation/hwmon/lan966x.rst | 40 +++ > drivers/hwmon/Kconfig | 12 + > drivers/hwmon/Makefile | 1 + > drivers/hwmon/lan966x-hwmon.c | 418 ++++++++++++++++++++++++++++++++ > 4 files changed, 471 insertions(+) > create mode 100644 Documentation/hwmon/lan966x.rst > create mode 100644 drivers/hwmon/lan966x-hwmon.c > > diff --git a/Documentation/hwmon/lan966x.rst b/Documentation/hwmon/lan966x.rst > new file mode 100644 > index 000000000000..1d1724afa5d2 > --- /dev/null > +++ b/Documentation/hwmon/lan966x.rst > @@ -0,0 +1,40 @@ > +.. SPDX-License-Identifier: GPL-2.0 > + > +Kernel driver lan966x-hwmon > +=========================== > + > +Supported chips: > + > + * Microchip LAN9668 (sensor in SoC) > + > + Prefix: 'lan9668-hwmon' > + > + Datasheet: https://microchip-ung.github.io/lan9668_reginfo > + > +Authors: > + > + Michael Walle <michael@walle.cc> > + > +Description > +----------- > + > +This driver implements support for the Microchip LAN9668 on-chip > +temperature sensor as well as its fan controller. It provides one > +temperature sensor and one fan controller. The temperature range > +of the sensor is specified from -40 to +125 degrees Celsius and > +its accuracy is +/- 5 degrees Celsius. The fan controller has a > +tacho input and a PWM output with a customizable PWM output > +frequency ranging from ~20Hz to ~650kHz. > + > +No alarms are supported by the SoC. > + > +The driver exports temperature values, fan tacho input and PWM > +settings via the following sysfs files: > + > +**temp1_input** > + > +**fan1_input** > + > +**pwm1** > + > +**pwm1_freq** > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig > index be9773270e53..052b37b78919 100644 > --- a/drivers/hwmon/Kconfig > +++ b/drivers/hwmon/Kconfig > @@ -815,6 +815,18 @@ config SENSORS_POWR1220 > This driver can also be built as a module. If so, the module > will be called powr1220. > > +config SENSORS_LAN966X > + tristate "Microchip LAN966x Hardware Monitoring" > + depends on SOC_LAN966 || COMPILE_TEST > + select REGMAP > + select POLYNOMIAL > + help > + If you say yes here you get support for temperature monitoring > + on the Microchip LAN966x SoC. > + > + This driver can also be built as a module. If so, the module > + will be called lan966x-hwmon. > + > config SENSORS_LINEAGE > tristate "Lineage Compact Power Line Power Entry Module" > depends on I2C > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile > index 8a03289e2aa4..51ca6956f8b7 100644 > --- a/drivers/hwmon/Makefile > +++ b/drivers/hwmon/Makefile > @@ -100,6 +100,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o > obj-$(CONFIG_SENSORS_JC42) += jc42.o > obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o > obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o > +obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o > obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o > obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o > obj-$(CONFIG_SENSORS_LM63) += lm63.o > diff --git a/drivers/hwmon/lan966x-hwmon.c b/drivers/hwmon/lan966x-hwmon.c > new file mode 100644 > index 000000000000..f41df053ac31 > --- /dev/null > +++ b/drivers/hwmon/lan966x-hwmon.c > @@ -0,0 +1,418 @@ > +// SPDX-License-Identifier: GPL-2.0-only > + > +#include <linux/bitfield.h> > +#include <linux/clk.h> > +#include <linux/hwmon.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/mod_devicetable.h> > +#include <linux/platform_device.h> > +#include <linux/polynomial.h> > +#include <linux/regmap.h> > + > +/* > + * The original translation formulae of the temperature (in degrees of Celsius) > + * are as follows: > + * > + * T = -3.4627e-11*(N^4) + 1.1023e-7*(N^3) + -1.9165e-4*(N^2) + > + * 3.0604e-1*(N^1) + -5.6197e1 > + * > + * where [-56.197, 136.402]C and N = [0, 1023]. > + * > + * They must be accordingly altered to be suitable for the integer arithmetics. > + * The technique is called 'factor redistribution', which just makes sure the > + * multiplications and divisions are made so to have a result of the operations > + * within the integer numbers limit. In addition we need to translate the > + * formulae to accept millidegrees of Celsius. Here what it looks like after > + * the alterations: > + * > + * T = -34627e-12*(N^4) + 110230e-9*(N^3) + -191650e-6*(N^2) + > + * 306040e-3*(N^1) + -56197 > + * > + * where T = [-56197, 136402]mC and N = [0, 1023]. > + */ > + > +static const struct polynomial poly_N_to_temp = { > + .terms = { > + {4, -34627, 1000, 1}, > + {3, 110230, 1000, 1}, > + {2, -191650, 1000, 1}, > + {1, 306040, 1000, 1}, > + {0, -56197, 1, 1} > + } > +}; > + > +#define PVT_SENSOR_CTRL 0x0 /* unused */ > +#define PVT_SENSOR_CFG 0x4 > +#define SENSOR_CFG_CLK_CFG GENMASK(27, 20) > +#define SENSOR_CFG_TRIM_VAL GENMASK(13, 9) > +#define SENSOR_CFG_SAMPLE_ENA BIT(8) > +#define SENSOR_CFG_START_CAPTURE BIT(7) > +#define SENSOR_CFG_CONTINIOUS_MODE BIT(6) > +#define SENSOR_CFG_PSAMPLE_ENA GENMASK(1, 0) > +#define PVT_SENSOR_STAT 0x8 > +#define SENSOR_STAT_DATA_VALID BIT(10) > +#define SENSOR_STAT_DATA GENMASK(9, 0) > + > +#define FAN_CFG 0x0 > +#define FAN_CFG_DUTY_CYCLE GENMASK(23, 16) > +#define INV_POL BIT(3) > +#define GATE_ENA BIT(2) > +#define PWM_OPEN_COL_ENA BIT(1) > +#define FAN_STAT_CFG BIT(0) > +#define FAN_PWM_FREQ 0x4 > +#define FAN_PWM_CYC_10US GENMASK(25, 15) > +#define FAN_PWM_FREQ_FREQ GENMASK(14, 0) > +#define FAN_CNT 0xc > +#define FAN_CNT_DATA GENMASK(15, 0) > + > +#define LAN966X_PVT_CLK 1200000 /* 1.2 MHz */ > + > +struct lan966x_hwmon { > + struct regmap *regmap_pvt; > + struct regmap *regmap_fan; > + struct clk *clk; > + unsigned long clk_rate; > +}; > + > +static int lan966x_hwmon_read_temp(struct device *dev, long *val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + unsigned int data; > + int ret; > + > + ret = regmap_read(hwmon->regmap_pvt, PVT_SENSOR_STAT, &data); > + if (ret < 0) > + return ret; > + > + if (!(data & SENSOR_STAT_DATA_VALID)) > + return -ENODATA; > + > + *val = polynomial_calc(&poly_N_to_temp, > + FIELD_GET(SENSOR_STAT_DATA, data)); > + > + return 0; > +} > + > +static int lan966x_hwmon_read_fan(struct device *dev, long *val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + unsigned int data; > + int ret; > + > + ret = regmap_read(hwmon->regmap_fan, FAN_CNT, &data); > + if (ret < 0) > + return ret; > + > + /* > + * Data is given in pulses per second. Assume two pulses > + * per revolution. > + */ > + *val = FIELD_GET(FAN_CNT_DATA, data) * 60 / 2; > + > + return 0; > +} > + > +static int lan966x_hwmon_read_pwm(struct device *dev, long *val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + unsigned int data; > + int ret; > + > + ret = regmap_read(hwmon->regmap_fan, FAN_CFG, &data); > + if (ret < 0) > + return ret; > + > + *val = FIELD_GET(FAN_CFG_DUTY_CYCLE, data); > + > + return 0; > +} > + > +static int lan966x_hwmon_read_pwm_freq(struct device *dev, long *val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + unsigned long tmp; > + unsigned int data; > + int ret; > + > + ret = regmap_read(hwmon->regmap_fan, FAN_PWM_FREQ, &data); > + if (ret < 0) > + return ret; > + > + /* > + * Datasheet says it is sys_clk / 256 / pwm_freq. But in reality > + * it is sys_clk / 256 / (pwm_freq + 1). > + */ > + data = FIELD_GET(FAN_PWM_FREQ_FREQ, data) + 1; > + tmp = DIV_ROUND_CLOSEST(hwmon->clk_rate, 256); > + *val = DIV_ROUND_CLOSEST(tmp, data); > + > + return 0; > +} > + > +static int lan966x_hwmon_read(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long *val) > +{ > + switch (type) { > + case hwmon_temp: > + return lan966x_hwmon_read_temp(dev, val); > + case hwmon_fan: > + return lan966x_hwmon_read_fan(dev, val); > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + return lan966x_hwmon_read_pwm(dev, val); > + case hwmon_pwm_freq: > + return lan966x_hwmon_read_pwm_freq(dev, val); > + default: > + return -EOPNOTSUPP; > + } > + default: > + return -EOPNOTSUPP; > + } > +} > + > +static int lan966x_hwmon_write_pwm(struct device *dev, long val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + > + if (val < 0 || val > 255) > + return -EINVAL; > + > + return regmap_update_bits(hwmon->regmap_fan, FAN_CFG, > + FAN_CFG_DUTY_CYCLE, > + FIELD_PREP(FAN_CFG_DUTY_CYCLE, val)); > +} > + > +static int lan966x_hwmon_write_pwm_freq(struct device *dev, long val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + > + if (val <= 0) > + return -EINVAL; > + > + val = DIV_ROUND_CLOSEST(hwmon->clk_rate, val); > + val = DIV_ROUND_CLOSEST(val, 256) - 1; > + val = clamp_val(val, 0, FAN_PWM_FREQ_FREQ); > + > + return regmap_update_bits(hwmon->regmap_fan, FAN_PWM_FREQ, > + FAN_PWM_FREQ_FREQ, > + FIELD_PREP(FAN_PWM_FREQ_FREQ, val)); > +} > + > +static int lan966x_hwmon_write(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long val) > +{ > + switch (type) { > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + return lan966x_hwmon_write_pwm(dev, val); > + case hwmon_pwm_freq: > + return lan966x_hwmon_write_pwm_freq(dev, val); > + default: > + return -EOPNOTSUPP; > + } > + default: > + return -EOPNOTSUPP; > + } > +} > + > +static umode_t lan966x_hwmon_is_visible(const void *data, > + enum hwmon_sensor_types type, > + u32 attr, int channel) > +{ > + umode_t mode = 0; > + > + switch (type) { > + case hwmon_temp: > + switch (attr) { > + case hwmon_temp_input: > + mode = 0444; > + break; > + default: > + break; > + } > + break; > + case hwmon_fan: > + switch (attr) { > + case hwmon_fan_input: > + mode = 0444; > + break; > + default: > + break; > + } > + break; > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + case hwmon_pwm_freq: > + mode = 0644; > + break; > + default: > + break; > + } > + break; > + default: > + break; > + } > + > + return mode; > +} > + > +static const struct hwmon_channel_info *lan966x_hwmon_info[] = { > + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), > + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), > + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), > + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_FREQ), > + NULL > +}; > + > +static const struct hwmon_ops lan966x_hwmon_ops = { > + .is_visible = lan966x_hwmon_is_visible, > + .read = lan966x_hwmon_read, > + .write = lan966x_hwmon_write, > +}; > + > +static const struct hwmon_chip_info lan966x_hwmon_chip_info = { > + .ops = &lan966x_hwmon_ops, > + .info = lan966x_hwmon_info, > +}; > + > +static void lan966x_hwmon_disable(void *data) > +{ > + struct lan966x_hwmon *hwmon = data; > + > + regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG, > + SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE, > + 0); > +} > + > +static int lan966x_hwmon_enable(struct device *dev, > + struct lan966x_hwmon *hwmon) > +{ > + unsigned int mask = SENSOR_CFG_CLK_CFG | > + SENSOR_CFG_SAMPLE_ENA | > + SENSOR_CFG_START_CAPTURE | > + SENSOR_CFG_CONTINIOUS_MODE | > + SENSOR_CFG_PSAMPLE_ENA; > + unsigned int val; > + unsigned int div; > + int ret; > + > + /* enable continuous mode */ > + val = SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE; > + > + /* set PVT clock to be between 1.15 and 1.25 MHz */ > + div = DIV_ROUND_CLOSEST(hwmon->clk_rate, LAN966X_PVT_CLK); > + val |= FIELD_PREP(SENSOR_CFG_CLK_CFG, div); > + > + ret = regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG, > + mask, val); > + if (ret) > + return ret; > + > + return devm_add_action_or_reset(dev, lan966x_hwmon_disable, hwmon); > +} > + > +static struct regmap *lan966x_init_regmap(struct platform_device *pdev, > + const char *name) > +{ > + struct regmap_config regmap_config = { > + .reg_bits = 32, > + .reg_stride = 4, > + .val_bits = 32, > + }; > + void __iomem *base; > + > + base = devm_platform_ioremap_resource_byname(pdev, name); > + if (IS_ERR(base)) > + return ERR_CAST(base); > + > + regmap_config.name = name; > + > + return devm_regmap_init_mmio(&pdev->dev, base, ®map_config); > +} > + > +static void lan966x_clk_disable(void *data) > +{ > + struct lan966x_hwmon *hwmon = data; > + > + clk_disable_unprepare(hwmon->clk); > +} > + > +static int lan966x_clk_enable(struct device *dev, struct lan966x_hwmon *hwmon) > +{ > + int ret; > + > + ret = clk_prepare_enable(hwmon->clk); > + if (ret) > + return ret; > + > + return devm_add_action_or_reset(dev, lan966x_clk_disable, hwmon); > +} > + > +static int lan966x_hwmon_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct lan966x_hwmon *hwmon; > + struct device *hwmon_dev; > + int ret; > + > + hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); > + if (!hwmon) > + return -ENOMEM; > + > + hwmon->clk = devm_clk_get(dev, NULL); > + if (IS_ERR(hwmon->clk)) > + return dev_err_probe(dev, PTR_ERR(hwmon->clk), > + "failed to get clock\n"); > + > + ret = lan966x_clk_enable(dev, hwmon); > + if (ret) > + return dev_err_probe(dev, ret, "failed to enable clock\n"); > + > + hwmon->clk_rate = clk_get_rate(hwmon->clk); > + > + hwmon->regmap_pvt = lan966x_init_regmap(pdev, "pvt"); > + if (IS_ERR(hwmon->regmap_pvt)) > + return dev_err_probe(dev, PTR_ERR(hwmon->regmap_pvt), > + "failed to get regmap for PVT registers\n"); > + > + hwmon->regmap_fan = lan966x_init_regmap(pdev, "fan"); > + if (IS_ERR(hwmon->regmap_fan)) > + return dev_err_probe(dev, PTR_ERR(hwmon->regmap_fan), > + "failed to get regmap for fan registers\n"); > + > + ret = lan966x_hwmon_enable(dev, hwmon); > + if (ret) > + return dev_err_probe(dev, ret, "failed to enable sensor\n"); > + > + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, > + "lan966x_hwmon", hwmon, > + &lan966x_hwmon_chip_info, NULL); > + if (IS_ERR(hwmon_dev)) > + return dev_err_probe(dev, PTR_ERR(hwmon_dev), > + "failed to register hwmon device\n"); > + > + return 0; > +} > + > +static const struct of_device_id lan966x_hwmon_of_match[] = { > + { .compatible = "microchip,lan9668-hwmon" }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, lan966x_hwmon_of_match); > + > +static struct platform_driver lan966x_hwmon_driver = { > + .probe = lan966x_hwmon_probe, > + .driver = { > + .name = "lan966x-hwmon", > + .of_match_table = lan966x_hwmon_of_match, > + }, > +}; > +module_platform_driver(lan966x_hwmon_driver); > + > +MODULE_DESCRIPTION("LAN966x Hardware Monitoring Driver"); > +MODULE_AUTHOR("Michael Walle <michael@walle.cc>"); > +MODULE_LICENSE("GPL");