diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-04-16 00:37:32 +0300 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-04-16 00:37:32 +0300 |
| commit | 46576fa32908043975471bd26fe833a7d8015b35 (patch) | |
| tree | 8be9416e2c8445b7f9406725450f9121c9a5035e /drivers | |
| parent | 405f6584d7d0fc46534fd370e374630283dffe60 (diff) | |
| parent | fb447217c59a13b2fff22d94de2498c185cd9032 (diff) | |
| download | linux-46576fa32908043975471bd26fe833a7d8015b35.tar.xz | |
Merge tag 'hwmon-for-v7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging
Pull hwmon updates from Guenter Roeck:
"New drivers:
- Lenovo Yoga/Legion fan monitoring (yogafan)
- LattePanda Sigma EC
- Infineon XDP720 eFuse
- Microchip MCP998X
New device support:
- TI INA234
- Infineon XDPE1A2G5B/7B
- Renesas RAA228942 and RAA228943 (isl68137)
- Delta Q54SN120A1 and Q54SW120A7 (pmbus)
- TI TMP110 and TMP113 (tmp102)
- Sony APS-379 (pmbus)
- ITE IT8689E (it87)
- ASUS ROG STRIX Z790-H, X470-F, and CROSSHAIR X670E (asus-ec-sensors)
- GPD Win 5 (gpd-fan)
Modernization and Cleanups:
- Convert asus_atk0110 and acpi_power_meter ACPI drivers to platform
drivers
- Remove i2c_match_id() usage in many PMBus drivers
- Use guard() for mutex protection in pmbus_core
- Replace sprintf() with sysfs_emit() in ads7871, emc1403, max6650,
ads7828, max31722, and tc74
- Various markup and documentation improvements for yogafan and
ltc4282
Bug fixes:
- Fix use-after-free and missing usb_kill_urb on disconnect in powerz
driver
- Avoid cacheline sharing for DMA buffer in powerz driver
- Fix integer overflow in power calculation on 32-bit in isl28022
driver
- Fix bugs in pt5161l_read_block_data()
- Propagate SPI errors and fix incorrect error codes in ads7871
driver
- Fix i2c_smbus_write_byte_data wrapper argument type in max31785
driver
Device tree bindings:
- Convert npcm750-pwm-fan to DT schema
- Add bindings for Infineon XDP720, Microchip MCP998X, Sony APS-379,
Renesas RAA228942/3, Delta Q54SN120A1/7, XDPE1A2G5B/7B, Aosong
AHT10/20, DHT20, and TI INA234
- Adapt moortec,mr75203 bindings for T-Head TH1520"
* tag 'hwmon-for-v7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (82 commits)
hwmon: (ina233) Don't check for specific errors when parsing properties
hwmon: (isl28022) Don't check for specific errors when parsing properties
hwmon: (pmbus/tps25990) Don't check for specific errors when parsing properties
hwmon: (nct6683) Add customer ID for ASRock B650I Lightning WiFi
hwmon:(pmbus/xdp720) Add support for efuse xdp720
dt-bindings: hwmon/pmbus: Add Infineon XDP720
hwmon: add support for MCP998X
dt-bindings: hwmon: add support for MCP998X
hwmon: (powerz) Avoid cacheline sharing for DMA buffer
hwmon: (isl28022) Fix integer overflow in power calculation on 32-bit
hwmon: (pt5161l) Fix bugs in pt5161l_read_block_data()
hwmon: (powerz) Fix missing usb_kill_urb() on signal interrupt
hwmon: (powerz) Fix use-after-free on USB disconnect
hwmon: pmbus: Add support for Sony APS-379
dt-bindings: trivial-devices: Add sony,aps-379
hwmon: (yogafan) various markup improvements
hwmon: (sparx5) Make it selectable for ARCH_LAN969X
hwmon: (tmp102) add support for update interval
hwmon: (yogafan) fix markup warning
hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring
...
Diffstat (limited to 'drivers')
53 files changed, 2856 insertions, 2092 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 328867242cb3..14e4cea48acc 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -457,32 +457,6 @@ config SENSORS_ATXP1 This driver can also be built as a module. If so, the module will be called 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. - - This driver can also be built as a module. If so, the module will be - called bt1-pvt. - -config SENSORS_BT1_PVT_ALARMS - bool "Enable Baikal-T1 PVT sensor alarms" - depends on SENSORS_BT1_PVT - help - Baikal-T1 PVT IP-block provides threshold registers for each - supported sensor. But the corresponding interrupts might be - generated by the thresholds comparator only in synchronization with - a data conversion. Additionally there is only one sensor data can - be converted at a time. All of these makes the interface impossible - to be used for the hwmon alarms implementation without periodic - switch between the PVT sensors. By default the data conversion is - performed on demand from the user-space. If this config is enabled - the data conversion will be periodically performed and the data will be - saved in the internal driver cache. - config SENSORS_CGBC tristate "Congatec Board Controller Sensors" depends on MFD_CGBC @@ -632,7 +606,7 @@ config SENSORS_I5K_AMB config SENSORS_SPARX5 tristate "Sparx5 SoC temperature sensor" - depends on ARCH_SPARX5 || COMPILE_TEST + depends on ARCH_SPARX5 || ARCH_LAN969X || COMPILE_TEST help If you say yes here you get support for temperature monitoring with the Microchip Sparx5 SoC. @@ -801,7 +775,6 @@ config SENSORS_G762 config SENSORS_GPIO_FAN tristate "GPIO fan" - depends on OF_GPIO depends on GPIOLIB || COMPILE_TEST depends on THERMAL || THERMAL=n help @@ -936,8 +909,8 @@ config SENSORS_IT87 If you say yes here you get support for ITE IT8705F, IT8712F, IT8716F, IT8718F, IT8720F, IT8721F, IT8726F, IT8728F, IT8732F, IT8758E, IT8771E, IT8772E, IT8781F, IT8782F, IT8783E/F, IT8786E, IT8790E, - IT8603E, IT8620E, IT8623E, and IT8628E sensor chips, and the SiS950 - clone. + IT8603E, IT8620E, IT8623E, IT8628E, and IT8689E sensor chips, and + the SiS950 clone. This driver can also be built as a module. If so, the module will be called it87. @@ -990,6 +963,23 @@ config SENSORS_LAN966X This driver can also be built as a module. If so, the module will be called lan966x-hwmon. +config SENSORS_LATTEPANDA_SIGMA_EC + tristate "LattePanda Sigma EC hardware monitoring" + depends on X86 + depends on DMI + depends on HAS_IOPORT + help + If you say yes here you get support for the hardware monitoring + features of the Embedded Controller on LattePanda Sigma + single-board computers, including CPU fan speed (RPM) and + board and CPU temperatures. + + The driver reads the EC directly via ACPI EC I/O ports and + uses DMI matching to ensure it only loads on supported hardware. + + This driver can also be built as a module. If so, the module + will be called lattepanda-sigma-ec. + config SENSORS_LENOVO_EC tristate "Sensor reader for Lenovo ThinkStations" depends on X86 @@ -1388,6 +1378,17 @@ config SENSORS_MCP3021 This driver can also be built as a module. If so, the module will be called mcp3021. +config SENSORS_MCP9982 + tristate "Microchip Technology MCP9982 driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to include support for Microchip Technology's MCP998X/33 + and MCP998XD/33D Multichannel Automotive Temperature Monitor Family. + + This driver can also be built as a module. If so, the module + will be called mcp9982. + config SENSORS_MLXREG_FAN tristate "Mellanox FAN driver" depends on MELLANOX_PLATFORM @@ -2273,7 +2274,7 @@ config SENSORS_INA2XX select REGMAP_I2C help If you say yes here you get support for INA219, INA220, INA226, - INA230, INA231, INA260, and SY24655 power monitor chips. + INA230, INA231, INA234, INA260, and SY24655 power monitor chips. The INA2xx driver is configured for the default configuration of the part as described in the datasheet. @@ -2362,8 +2363,8 @@ config SENSORS_TMP102 depends on I2C select REGMAP_I2C help - If you say yes here you get support for Texas Instruments TMP102 - sensor chips. + If you say yes here you get support for Texas Instruments TMP102, + TMP110 and TMP113 sensor chips. This driver can also be built as a module. If so, the module will be called tmp102. @@ -2661,6 +2662,18 @@ config SENSORS_XGENE If you say yes here you get support for the temperature and power sensors for APM X-Gene SoC. +config SENSORS_YOGAFAN + tristate "Lenovo Yoga Fan Hardware Monitoring" + depends on ACPI && HWMON && DMI + help + If you say yes here you get support for fan speed monitoring + on Lenovo Yoga, Legion, IdeaPad, Slim and LOQ laptops. + The driver interfaces with the Embedded Controller via ACPI + and uses a Rate-Limited Lag filter to smooth RPM readings. + + This driver can also be built as a module. If so, the module + will be called yogafan. + config SENSORS_INTEL_M10_BMC_HWMON tristate "Intel MAX10 BMC Hardware Monitoring" depends on MFD_INTEL_M10_BMC_CORE diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5833c807c688..4788996aa137 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -58,7 +58,6 @@ obj-$(CONFIG_SENSORS_ASPEED_G6) += aspeed-g6-pwm-tach.o obj-$(CONFIG_SENSORS_ASUS_ROG_RYUJIN) += asus_rog_ryujin.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o -obj-$(CONFIG_SENSORS_BT1_PVT) += bt1-pvt.o obj-$(CONFIG_SENSORS_CGBC) += cgbc-hwmon.o obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o @@ -114,6 +113,7 @@ obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o obj-$(CONFIG_SENSORS_KBATT) += kbatt.o obj-$(CONFIG_SENSORS_KFAN) += kfan.o obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o +obj-$(CONFIG_SENSORS_LATTEPANDA_SIGMA_EC) += lattepanda-sigma-ec.o obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o @@ -170,6 +170,7 @@ obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o obj-$(CONFIG_SENSORS_MC33XS2410) += mc33xs2410_hwmon.o obj-$(CONFIG_SENSORS_MC34VR500) += mc34vr500.o obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o +obj-$(CONFIG_SENSORS_MCP9982) += mcp9982.o obj-$(CONFIG_SENSORS_TC654) += tc654.o obj-$(CONFIG_SENSORS_TPS23861) += tps23861.o obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o @@ -245,6 +246,7 @@ obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o +obj-$(CONFIG_SENSORS_YOGAFAN) += yogafan.o obj-$(CONFIG_SENSORS_OCC) += occ/ obj-$(CONFIG_SENSORS_PECI) += peci/ diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 1e3fab5f7946..be7f702dcde9 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -18,6 +18,7 @@ #include <linux/time.h> #include <linux/err.h> #include <linux/acpi.h> +#include <linux/platform_device.h> #define ACPI_POWER_METER_NAME "power_meter" #define ACPI_POWER_METER_DEVICE_NAME "Power Meter" @@ -814,16 +815,12 @@ end: } /* Handle ACPI event notifications */ -static void acpi_power_meter_notify(struct acpi_device *device, u32 event) +static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data) { - struct acpi_power_meter_resource *resource; + struct device *dev = data; + struct acpi_power_meter_resource *resource = dev_get_drvdata(dev); int res; - if (!device || !acpi_driver_data(device)) - return; - - resource = acpi_driver_data(device); - guard(mutex)(&acpi_notify_lock); switch (event) { @@ -837,43 +834,43 @@ static void acpi_power_meter_notify(struct acpi_device *device, u32 event) remove_domain_devices(resource); res = read_capabilities(resource); if (res) - dev_err_once(&device->dev, "read capabilities failed.\n"); + dev_err_once(dev, "read capabilities failed.\n"); res = read_domain_devices(resource); if (res && res != -ENODEV) - dev_err_once(&device->dev, "read domain devices failed.\n"); + dev_err_once(dev, "read domain devices failed.\n"); mutex_unlock(&resource->lock); resource->hwmon_dev = - hwmon_device_register_with_info(&device->dev, + hwmon_device_register_with_info(dev, ACPI_POWER_METER_NAME, resource, &power_meter_chip_info, power_extra_groups); if (IS_ERR(resource->hwmon_dev)) - dev_err_once(&device->dev, "register hwmon device failed.\n"); + dev_err_once(dev, "register hwmon device failed.\n"); break; case METER_NOTIFY_TRIP: - sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME); + sysfs_notify(&dev->kobj, NULL, POWER_AVERAGE_NAME); break; case METER_NOTIFY_CAP: mutex_lock(&resource->lock); res = update_cap(resource); if (res) - dev_err_once(&device->dev, "update cap failed when capping value is changed.\n"); + dev_err_once(dev, "update cap failed when capping value is changed.\n"); mutex_unlock(&resource->lock); - sysfs_notify(&device->dev.kobj, NULL, POWER_CAP_NAME); + sysfs_notify(&dev->kobj, NULL, POWER_CAP_NAME); break; case METER_NOTIFY_INTERVAL: - sysfs_notify(&device->dev.kobj, NULL, POWER_AVG_INTERVAL_NAME); + sysfs_notify(&dev->kobj, NULL, POWER_AVG_INTERVAL_NAME); break; case METER_NOTIFY_CAPPING: mutex_lock(&resource->lock); resource->power_alarm = true; mutex_unlock(&resource->lock); - sysfs_notify(&device->dev.kobj, NULL, POWER_ALARM_NAME); - dev_info(&device->dev, "Capping in progress.\n"); + sysfs_notify(&dev->kobj, NULL, POWER_ALARM_NAME); + dev_info(dev, "Capping in progress.\n"); break; default: WARN(1, "Unexpected event %d\n", event); @@ -881,16 +878,15 @@ static void acpi_power_meter_notify(struct acpi_device *device, u32 event) } acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, - dev_name(&device->dev), event, 0); + dev_name(&resource->acpi_dev->dev), + event, 0); } -static int acpi_power_meter_add(struct acpi_device *device) +static int acpi_power_meter_probe(struct platform_device *pdev) { - int res; + struct acpi_device *device = ACPI_COMPANION(&pdev->dev); struct acpi_power_meter_resource *resource; - - if (!device) - return -EINVAL; + int res; resource = kzalloc_obj(*resource); if (!resource) @@ -901,7 +897,8 @@ static int acpi_power_meter_add(struct acpi_device *device) mutex_init(&resource->lock); strscpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); - device->driver_data = resource; + + platform_set_drvdata(pdev, resource); #if IS_REACHABLE(CONFIG_ACPI_IPMI) /* @@ -914,7 +911,7 @@ static int acpi_power_meter_add(struct acpi_device *device) struct acpi_device *ipi_device = acpi_dev_get_first_match_dev("IPI0001", NULL, -1); if (ipi_device && acpi_wait_for_acpi_ipmi()) - dev_warn(&device->dev, "Waiting for ACPI IPMI timeout"); + dev_warn(&pdev->dev, "Waiting for ACPI IPMI timeout"); acpi_dev_put(ipi_device); } #endif @@ -932,7 +929,7 @@ static int acpi_power_meter_add(struct acpi_device *device) goto exit_free_capability; resource->hwmon_dev = - hwmon_device_register_with_info(&device->dev, + hwmon_device_register_with_info(&pdev->dev, ACPI_POWER_METER_NAME, resource, &power_meter_chip_info, power_extra_groups); @@ -941,9 +938,16 @@ static int acpi_power_meter_add(struct acpi_device *device) goto exit_remove; } + res = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + acpi_power_meter_notify, &pdev->dev); + if (res) + goto exit_hwmon; + res = 0; goto exit; +exit_hwmon: + hwmon_device_unregister(resource->hwmon_dev); exit_remove: remove_domain_devices(resource); exit_free_capability: @@ -954,14 +958,13 @@ exit: return res; } -static void acpi_power_meter_remove(struct acpi_device *device) +static void acpi_power_meter_remove(struct platform_device *pdev) { - struct acpi_power_meter_resource *resource; + struct acpi_power_meter_resource *resource = platform_get_drvdata(pdev); - if (!device || !acpi_driver_data(device)) - return; + acpi_dev_remove_notify_handler(resource->acpi_dev, ACPI_DEVICE_NOTIFY, + acpi_power_meter_notify); - resource = acpi_driver_data(device); if (!IS_ERR(resource->hwmon_dev)) hwmon_device_unregister(resource->hwmon_dev); @@ -973,14 +976,7 @@ static void acpi_power_meter_remove(struct acpi_device *device) static int acpi_power_meter_resume(struct device *dev) { - struct acpi_power_meter_resource *resource; - - if (!dev) - return -EINVAL; - - resource = acpi_driver_data(to_acpi_device(dev)); - if (!resource) - return -EINVAL; + struct acpi_power_meter_resource *resource = dev_get_drvdata(dev); free_capabilities(resource); read_capabilities(resource); @@ -991,16 +987,14 @@ static int acpi_power_meter_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(acpi_power_meter_pm, NULL, acpi_power_meter_resume); -static struct acpi_driver acpi_power_meter_driver = { - .name = "power_meter", - .class = ACPI_POWER_METER_CLASS, - .ids = power_meter_ids, - .ops = { - .add = acpi_power_meter_add, - .remove = acpi_power_meter_remove, - .notify = acpi_power_meter_notify, - }, - .drv.pm = pm_sleep_ptr(&acpi_power_meter_pm), +static struct platform_driver acpi_power_meter_driver = { + .probe = acpi_power_meter_probe, + .remove = acpi_power_meter_remove, + .driver = { + .name = "acpi-power-meter", + .acpi_match_table = power_meter_ids, + .pm = &acpi_power_meter_pm, + }, }; /* Module init/exit routines */ @@ -1029,7 +1023,7 @@ static int __init acpi_power_meter_init(void) dmi_check_system(pm_dmi_table); - result = acpi_bus_register_driver(&acpi_power_meter_driver); + result = platform_driver_register(&acpi_power_meter_driver); if (result < 0) return result; @@ -1038,7 +1032,7 @@ static int __init acpi_power_meter_init(void) static void __exit acpi_power_meter_exit(void) { - acpi_bus_unregister_driver(&acpi_power_meter_driver); + platform_driver_unregister(&acpi_power_meter_driver); } MODULE_AUTHOR("Darrick J. Wong <darrick.wong@oracle.com>"); diff --git a/drivers/hwmon/ads7828.c b/drivers/hwmon/ads7828.c index 436637264056..7f43565ca284 100644 --- a/drivers/hwmon/ads7828.c +++ b/drivers/hwmon/ads7828.c @@ -62,8 +62,8 @@ static ssize_t ads7828_in_show(struct device *dev, if (err < 0) return err; - return sprintf(buf, "%d\n", - DIV_ROUND_CLOSEST(regval * data->lsb_resol, 1000)); + return sysfs_emit(buf, "%d\n", + DIV_ROUND_CLOSEST(regval * data->lsb_resol, 1000)); } static SENSOR_DEVICE_ATTR_RO(in0_input, ads7828_in, 0); diff --git a/drivers/hwmon/ads7871.c b/drivers/hwmon/ads7871.c index 5434c37969d7..9bfdf9e6bcd7 100644 --- a/drivers/hwmon/ads7871.c +++ b/drivers/hwmon/ads7871.c @@ -104,10 +104,14 @@ static ssize_t voltage_show(struct device *dev, struct device_attribute *da, */ /*MUX_M3_BM forces single ended*/ /*This is also where the gain of the PGA would be set*/ - ads7871_write_reg8(spi, REG_GAIN_MUX, - (MUX_CNV_BM | MUX_M3_BM | channel)); + ret = ads7871_write_reg8(spi, REG_GAIN_MUX, + (MUX_CNV_BM | MUX_M3_BM | channel)); + if (ret < 0) + return ret; ret = ads7871_read_reg8(spi, REG_GAIN_MUX); + if (ret < 0) + return ret; mux_cnv = ((ret & MUX_CNV_BM) >> MUX_CNV_BV); /* * on 400MHz arm9 platform the conversion @@ -116,18 +120,22 @@ static ssize_t voltage_show(struct device *dev, struct device_attribute *da, while ((i < 2) && mux_cnv) { i++; ret = ads7871_read_reg8(spi, REG_GAIN_MUX); + if (ret < 0) + return ret; mux_cnv = ((ret & MUX_CNV_BM) >> MUX_CNV_BV); msleep_interruptible(1); } if (mux_cnv == 0) { val = ads7871_read_reg16(spi, REG_LS_BYTE); + if (val < 0) + return val; /*result in volts*10000 = (val/8192)*2.5*10000*/ val = ((val >> 2) * 25000) / 8192; - return sprintf(buf, "%d\n", val); - } else { - return -1; + return sysfs_emit(buf, "%d\n", val); } + + return -ETIMEDOUT; } static SENSOR_DEVICE_ATTR_RO(in0_input, voltage, 0); diff --git a/drivers/hwmon/aht10.c b/drivers/hwmon/aht10.c index 4ce019d2cc80..66955395d058 100644 --- a/drivers/hwmon/aht10.c +++ b/drivers/hwmon/aht10.c @@ -62,6 +62,15 @@ static const struct i2c_device_id aht10_id[] = { }; MODULE_DEVICE_TABLE(i2c, aht10_id); +static const struct of_device_id aht10_of_match[] = { + { .compatible = "aosong,aht10", .data = (void *)aht10 }, + { .compatible = "aosong,aht20", .data = (void *)aht20 }, + { .compatible = "aosong,dht20", .data = (void *)dht20 }, + {} +}; + +MODULE_DEVICE_TABLE(of, aht10_of_match); + /** * struct aht10_data - All the data required to operate an AHT10/AHT20 chip * @client: the i2c client associated with the AHT10/AHT20 @@ -377,6 +386,7 @@ static int aht10_probe(struct i2c_client *client) static struct i2c_driver aht10_driver = { .driver = { .name = "aht10", + .of_match_table = aht10_of_match, }, .probe = aht10_probe, .id_table = aht10_id, diff --git a/drivers/hwmon/aspeed-g6-pwm-tach.c b/drivers/hwmon/aspeed-g6-pwm-tach.c index 44e1ecba205d..4f6e6d440dd4 100644 --- a/drivers/hwmon/aspeed-g6-pwm-tach.c +++ b/drivers/hwmon/aspeed-g6-pwm-tach.c @@ -517,13 +517,6 @@ static int aspeed_pwm_tach_probe(struct platform_device *pdev) return 0; } -static void aspeed_pwm_tach_remove(struct platform_device *pdev) -{ - struct aspeed_pwm_tach_data *priv = platform_get_drvdata(pdev); - - reset_control_assert(priv->reset); -} - static const struct of_device_id aspeed_pwm_tach_match[] = { { .compatible = "aspeed,ast2600-pwm-tach", @@ -537,7 +530,6 @@ MODULE_DEVICE_TABLE(of, aspeed_pwm_tach_match); static struct platform_driver aspeed_pwm_tach_driver = { .probe = aspeed_pwm_tach_probe, - .remove = aspeed_pwm_tach_remove, .driver = { .name = "aspeed-g6-pwm-tach", .of_match_table = aspeed_pwm_tach_match, diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index adedaf0db10e..b5d97a27f80d 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -456,6 +456,15 @@ static const struct ec_board_info board_info_crosshair_viii_impact = { .family = family_amd_500_series, }; +static const struct ec_board_info board_info_crosshair_x670e_extreme = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_WATER_IN | + SENSOR_TEMP_WATER_OUT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, + .family = family_amd_600_series, +}; + static const struct ec_board_info board_info_crosshair_x670e_gene = { .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_T_SENSOR | @@ -626,6 +635,14 @@ static const struct ec_board_info board_info_strix_b850_i_gaming_wifi = { .family = family_amd_800_series, }; +static const struct ec_board_info board_info_strix_x470_f_gaming = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_400_series, +}; + static const struct ec_board_info board_info_strix_x470_i_gaming = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | @@ -750,6 +767,12 @@ static const struct ec_board_info board_info_strix_z790_e_gaming_wifi_ii = { .family = family_intel_700_series, }; +static const struct ec_board_info board_info_strix_z790_h_gaming_wifi = { + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0, + .family = family_intel_700_series, +}; + static const struct ec_board_info board_info_strix_z790_i_gaming_wifi = { .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_2 | SENSOR_TEMP_VRM, @@ -825,6 +848,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_crosshair_viii_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT", &board_info_crosshair_viii_impact), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E EXTREME", + &board_info_crosshair_x670e_extreme), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E GENE", &board_info_crosshair_x670e_gene), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO", @@ -845,6 +870,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_b650e_i_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B850-I GAMING WIFI", &board_info_strix_b850_i_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-F GAMING", + &board_info_strix_x470_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-I GAMING", &board_info_strix_x470_i_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING", @@ -877,6 +904,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_z690_e_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-E GAMING WIFI II", &board_info_strix_z790_e_gaming_wifi_ii), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-H GAMING WIFI", + &board_info_strix_z790_h_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-I GAMING WIFI", &board_info_strix_z790_i_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME", diff --git a/drivers/hwmon/asus_atk0110.c b/drivers/hwmon/asus_atk0110.c index c80350e499e9..5688ff5f7c28 100644 --- a/drivers/hwmon/asus_atk0110.c +++ b/drivers/hwmon/asus_atk0110.c @@ -17,6 +17,7 @@ #include <linux/jiffies.h> #include <linux/err.h> #include <linux/acpi.h> +#include <linux/platform_device.h> #include <linux/string_choices.h> #define ATK_HID "ATK0110" @@ -107,7 +108,7 @@ enum atk_pack_member { struct atk_data { struct device *hwmon_dev; acpi_handle atk_handle; - struct acpi_device *acpi_dev; + struct device *dev; bool old_interface; @@ -187,18 +188,17 @@ struct atk_acpi_input_buf { u32 param2; }; -static int atk_add(struct acpi_device *device); -static void atk_remove(struct acpi_device *device); +static int atk_probe(struct platform_device *pdev); +static void atk_remove(struct platform_device *pdev); static void atk_print_sensor(struct atk_data *data, union acpi_object *obj); static int atk_read_value(struct atk_sensor_data *sensor, u64 *value); -static struct acpi_driver atk_driver = { - .name = ATK_HID, - .class = "hwmon", - .ids = atk_ids, - .ops = { - .add = atk_add, - .remove = atk_remove, +static struct platform_driver atk_driver = { + .probe = atk_probe, + .remove = atk_remove, + .driver = { + .name = ATK_HID, + .acpi_match_table = atk_ids, }, }; @@ -327,7 +327,7 @@ static union acpi_object *atk_get_pack_member(struct atk_data *data, */ static int validate_hwmon_pack(struct atk_data *data, union acpi_object *obj) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *tmp; bool old_if = data->old_interface; int const expected_size = old_if ? _HWMON_OLD_PACK_SIZE : @@ -422,7 +422,7 @@ static char const *atk_sensor_type(union acpi_object *flags) static void atk_print_sensor(struct atk_data *data, union acpi_object *obj) { #ifdef DEBUG - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *flags; union acpi_object *name; union acpi_object *limit1; @@ -449,7 +449,7 @@ static void atk_print_sensor(struct atk_data *data, union acpi_object *obj) static int atk_read_value_old(struct atk_sensor_data *sensor, u64 *value) { struct atk_data *data = sensor->data; - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; struct acpi_object_list params; union acpi_object id; acpi_status status; @@ -487,7 +487,7 @@ static int atk_read_value_old(struct atk_sensor_data *sensor, u64 *value) static union acpi_object *atk_ggrp(struct atk_data *data, u16 mux) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; struct acpi_buffer buf; acpi_status ret; struct acpi_object_list params; @@ -523,7 +523,7 @@ static union acpi_object *atk_ggrp(struct atk_data *data, u16 mux) static union acpi_object *atk_gitm(struct atk_data *data, u64 id) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; struct atk_acpi_input_buf buf; union acpi_object tmp; struct acpi_object_list params; @@ -565,7 +565,7 @@ static union acpi_object *atk_gitm(struct atk_data *data, u64 id) static union acpi_object *atk_sitm(struct atk_data *data, struct atk_acpi_input_buf *buf) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; struct acpi_object_list params; union acpi_object tmp; struct acpi_buffer ret; @@ -602,7 +602,7 @@ static union acpi_object *atk_sitm(struct atk_data *data, static int atk_read_value_new(struct atk_sensor_data *sensor, u64 *value) { struct atk_data *data = sensor->data; - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *obj; struct atk_acpi_ret_buffer *buf; int err = 0; @@ -819,7 +819,7 @@ static void atk_debugfs_cleanup(struct atk_data *data) static int atk_add_sensor(struct atk_data *data, union acpi_object *obj) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *flags; union acpi_object *name; union acpi_object *limit1; @@ -937,7 +937,7 @@ static int atk_add_sensor(struct atk_data *data, union acpi_object *obj) static int atk_enumerate_old_hwmon(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; struct acpi_buffer buf; union acpi_object *pack; acpi_status status; @@ -1012,7 +1012,7 @@ static int atk_enumerate_old_hwmon(struct atk_data *data) static int atk_ec_present(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *pack; union acpi_object *ec; int ret; @@ -1058,7 +1058,7 @@ static int atk_ec_present(struct atk_data *data) static int atk_ec_enabled(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *obj; struct atk_acpi_ret_buffer *buf; int err; @@ -1084,7 +1084,7 @@ static int atk_ec_enabled(struct atk_data *data) static int atk_ec_ctl(struct atk_data *data, int enable) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *obj; struct atk_acpi_input_buf sitm; struct atk_acpi_ret_buffer *ec_ret; @@ -1113,7 +1113,7 @@ static int atk_ec_ctl(struct atk_data *data, int enable) static int atk_enumerate_new_hwmon(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; union acpi_object *pack; int err; int i; @@ -1155,7 +1155,7 @@ static int atk_enumerate_new_hwmon(struct atk_data *data) static int atk_init_attribute_groups(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; struct atk_sensor_data *s; struct attribute **attrs; int i = 0; @@ -1181,7 +1181,7 @@ static int atk_init_attribute_groups(struct atk_data *data) static int atk_register_hwmon(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; dev_dbg(dev, "registering hwmon device\n"); data->hwmon_dev = hwmon_device_register_with_groups(dev, "atk0110", @@ -1193,7 +1193,7 @@ static int atk_register_hwmon(struct atk_data *data) static int atk_probe_if(struct atk_data *data) { - struct device *dev = &data->acpi_dev->dev; + struct device *dev = data->dev; acpi_handle ret; acpi_status status; int err = 0; @@ -1266,7 +1266,7 @@ static int atk_probe_if(struct atk_data *data) return err; } -static int atk_add(struct acpi_device *device) +static int atk_probe(struct platform_device *pdev) { acpi_status ret; int err; @@ -1274,14 +1274,14 @@ static int atk_add(struct acpi_device *device) union acpi_object *obj; struct atk_data *data; - dev_dbg(&device->dev, "adding...\n"); + dev_dbg(&pdev->dev, "adding...\n"); - data = devm_kzalloc(&device->dev, sizeof(*data), GFP_KERNEL); + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - data->acpi_dev = device; - data->atk_handle = device->handle; + data->dev = &pdev->dev; + data->atk_handle = ACPI_HANDLE(&pdev->dev); INIT_LIST_HEAD(&data->sensor_list); data->disable_ec = false; @@ -1289,13 +1289,13 @@ static int atk_add(struct acpi_device *device) ret = acpi_evaluate_object_typed(data->atk_handle, BOARD_ID, NULL, &buf, ACPI_TYPE_PACKAGE); if (ret != AE_OK) { - dev_dbg(&device->dev, "atk: method MBIF not found\n"); + dev_dbg(&pdev->dev, "atk: method MBIF not found\n"); } else { obj = buf.pointer; if (obj->package.count >= 2) { union acpi_object *id = &obj->package.elements[1]; if (id->type == ACPI_TYPE_STRING) - dev_dbg(&device->dev, "board ID = %s\n", + dev_dbg(&pdev->dev, "board ID = %s\n", id->string.pointer); } ACPI_FREE(buf.pointer); @@ -1303,21 +1303,21 @@ static int atk_add(struct acpi_device *device) err = atk_probe_if(data); if (err) { - dev_err(&device->dev, "No usable hwmon interface detected\n"); + dev_err(&pdev->dev, "No usable hwmon interface detected\n"); goto out; } if (data->old_interface) { - dev_dbg(&device->dev, "Using old hwmon interface\n"); + dev_dbg(&pdev->dev, "Using old hwmon interface\n"); err = atk_enumerate_old_hwmon(data); } else { - dev_dbg(&device->dev, "Using new hwmon interface\n"); + dev_dbg(&pdev->dev, "Using new hwmon interface\n"); err = atk_enumerate_new_hwmon(data); } if (err < 0) goto out; if (err == 0) { - dev_info(&device->dev, + dev_info(&pdev->dev, "No usable sensor detected, bailing out\n"); err = -ENODEV; goto out; @@ -1332,7 +1332,8 @@ static int atk_add(struct acpi_device *device) atk_debugfs_init(data); - device->driver_data = data; + platform_set_drvdata(pdev, data); + return 0; out: if (data->disable_ec) @@ -1340,12 +1341,11 @@ out: return err; } -static void atk_remove(struct acpi_device *device) +static void atk_remove(struct platform_device *pdev) { - struct atk_data *data = device->driver_data; - dev_dbg(&device->dev, "removing...\n"); + struct atk_data *data = platform_get_drvdata(pdev); - device->driver_data = NULL; + dev_dbg(&pdev->dev, "removing...\n"); atk_debugfs_cleanup(data); @@ -1353,7 +1353,7 @@ static void atk_remove(struct acpi_device *device) if (data->disable_ec) { if (atk_ec_ctl(data, 0)) - dev_err(&device->dev, "Failed to disable EC\n"); + dev_err(&pdev->dev, "Failed to disable EC\n"); } } @@ -1370,16 +1370,16 @@ static int __init atk0110_init(void) if (dmi_check_system(atk_force_new_if)) new_if = true; - ret = acpi_bus_register_driver(&atk_driver); + ret = platform_driver_register(&atk_driver); if (ret) - pr_info("acpi_bus_register_driver failed: %d\n", ret); + pr_info("platform_driver_register failed: %d\n", ret); return ret; } static void __exit atk0110_exit(void) { - acpi_bus_unregister_driver(&atk_driver); + platform_driver_unregister(&atk_driver); } module_init(atk0110_init); diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c deleted file mode 100644 index b77ebac2e0ce..000000000000 --- a/drivers/hwmon/bt1-pvt.c +++ /dev/null @@ -1,1171 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC - * - * Authors: - * Maxim Kaurkin <maxim.kaurkin@baikalelectronics.ru> - * Serge Semin <Sergey.Semin@baikalelectronics.ru> - * - * Baikal-T1 Process, Voltage, Temperature sensor driver - */ - -#include <linux/bitfield.h> -#include <linux/bitops.h> -#include <linux/clk.h> -#include <linux/completion.h> -#include <linux/delay.h> -#include <linux/device.h> -#include <linux/hwmon-sysfs.h> -#include <linux/hwmon.h> -#include <linux/interrupt.h> -#include <linux/io.h> -#include <linux/kernel.h> -#include <linux/ktime.h> -#include <linux/limits.h> -#include <linux/module.h> -#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> - -#include "bt1-pvt.h" - -/* - * For the sake of the code simplification we created the sensors info table - * with the sensor names, activation modes, threshold registers base address - * and the thresholds bit fields. - */ -static const struct pvt_sensor_info pvt_info[] = { - PVT_SENSOR_INFO(0, "CPU Core Temperature", hwmon_temp, TEMP, TTHRES), - PVT_SENSOR_INFO(0, "CPU Core Voltage", hwmon_in, VOLT, VTHRES), - PVT_SENSOR_INFO(1, "CPU Core Low-Vt", hwmon_in, LVT, LTHRES), - PVT_SENSOR_INFO(2, "CPU Core High-Vt", hwmon_in, HVT, HTHRES), - PVT_SENSOR_INFO(3, "CPU Core Standard-Vt", hwmon_in, SVT, STHRES), -}; - -/* - * 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 __maybe_unused 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} - } -}; - -/* - * Similar alterations are performed for the voltage conversion equations. - * The original formulae are: - * N = 1.8658e3*V - 1.1572e3, - * V = (N + 1.1572e3) / 1.8658e3, - * where V = [0.620, 1.168] V and N = [0, 1023]. - * After the optimization they looks as follows: - * N = (18658e-3*V - 11572) / 10, - * V = N * 10^5 / 18658 + 11572 * 10^4 / 18658. - */ -static const struct polynomial __maybe_unused poly_volt_to_N = { - .total_divider = 10, - .terms = { - {1, 18658, 1000, 1}, - {0, -11572, 1, 1} - } -}; - -static const struct polynomial poly_N_to_volt = { - .total_divider = 10, - .terms = { - {1, 100000, 18658, 1}, - {0, 115720000, 1, 18658} - } -}; - -static inline u32 pvt_update(void __iomem *reg, u32 mask, u32 data) -{ - u32 old; - - old = readl_relaxed(reg); - writel((old & ~mask) | (data & mask), reg); - - return old & mask; -} - -/* - * Baikal-T1 PVT mode can be updated only when the controller is disabled. - * So first we disable it, then set the new mode together with the controller - * getting back enabled. The same concerns the temperature trim and - * measurements timeout. If it is necessary the interface mutex is supposed - * to be locked at the time the operations are performed. - */ -static inline void pvt_set_mode(struct pvt_hwmon *pvt, u32 mode) -{ - u32 old; - - mode = FIELD_PREP(PVT_CTRL_MODE_MASK, mode); - - old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_MODE_MASK | PVT_CTRL_EN, - mode | old); -} - -static inline u32 pvt_calc_trim(long temp) -{ - temp = clamp_val(temp, 0, PVT_TRIM_TEMP); - - return DIV_ROUND_UP(temp, PVT_TRIM_STEP); -} - -static inline void pvt_set_trim(struct pvt_hwmon *pvt, u32 trim) -{ - u32 old; - - trim = FIELD_PREP(PVT_CTRL_TRIM_MASK, trim); - - old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_TRIM_MASK | PVT_CTRL_EN, - trim | old); -} - -static inline void pvt_set_tout(struct pvt_hwmon *pvt, u32 tout) -{ - u32 old; - - old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - writel(tout, pvt->regs + PVT_TTIMEOUT); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, old); -} - -/* - * This driver can optionally provide the hwmon alarms for each sensor the PVT - * controller supports. The alarms functionality is made compile-time - * configurable due to the hardware interface implementation peculiarity - * described further in this comment. So in case if alarms are unnecessary in - * your system design it's recommended to have them disabled to prevent the PVT - * IRQs being periodically raised to get the data cache/alarms status up to - * date. - * - * Baikal-T1 PVT embedded controller is based on the Analog Bits PVT sensor, - * but is equipped with a dedicated control wrapper. It exposes the PVT - * sub-block registers space via the APB3 bus. In addition the wrapper provides - * a common interrupt vector of the sensors conversion completion events and - * threshold value alarms. Alas the wrapper interface hasn't been fully thought - * through. There is only one sensor can be activated at a time, for which the - * thresholds comparator is enabled right after the data conversion is - * completed. Due to this if alarms need to be implemented for all available - * sensors we can't just set the thresholds and enable the interrupts. We need - * to enable the sensors one after another and let the controller to detect - * the alarms by itself at each conversion. This also makes pointless to handle - * the alarms interrupts, since in occasion they happen synchronously with - * data conversion completion. The best driver design would be to have the - * completion interrupts enabled only and keep the converted value in the - * driver data cache. This solution is implemented if hwmon alarms are enabled - * in this driver. In case if the alarms are disabled, the conversion is - * performed on demand at the time a sensors input file is read. - */ - -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - -#define pvt_hard_isr NULL - -static irqreturn_t pvt_soft_isr(int irq, void *data) -{ - const struct pvt_sensor_info *info; - struct pvt_hwmon *pvt = data; - struct pvt_cache *cache; - u32 val, thres_sts, old; - - /* - * DVALID bit will be cleared by reading the data. We need to save the - * status before the next conversion happens. Threshold events will be - * handled a bit later. - */ - thres_sts = readl(pvt->regs + PVT_RAW_INTR_STAT); - - /* - * Then lets recharge the PVT interface with the next sampling mode. - * Lock the interface mutex to serialize trim, timeouts and alarm - * thresholds settings. - */ - cache = &pvt->cache[pvt->sensor]; - info = &pvt_info[pvt->sensor]; - pvt->sensor = (pvt->sensor == PVT_SENSOR_LAST) ? - PVT_SENSOR_FIRST : (pvt->sensor + 1); - - /* - * For some reason we have to mask the interrupt before changing the - * mode, otherwise sometimes the temperature mode doesn't get - * activated even though the actual mode in the ctrl register - * corresponds to one. Then we read the data. By doing so we also - * recharge the data conversion. After this the mode corresponding - * to the next sensor in the row is set. Finally we enable the - * interrupts back. - */ - mutex_lock(&pvt->iface_mtx); - - old = pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, - PVT_INTR_DVALID); - - val = readl(pvt->regs + PVT_DATA); - - pvt_set_mode(pvt, pvt_info[pvt->sensor].mode); - - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, old); - - mutex_unlock(&pvt->iface_mtx); - - /* - * We can now update the data cache with data just retrieved from the - * sensor. Lock write-seqlock to make sure the reader has a coherent - * data. - */ - write_seqlock(&cache->data_seqlock); - - cache->data = FIELD_GET(PVT_DATA_DATA_MASK, val); - - write_sequnlock(&cache->data_seqlock); - - /* - * While PVT core is doing the next mode data conversion, we'll check - * whether the alarms were triggered for the current sensor. Note that - * according to the documentation only one threshold IRQ status can be - * set at a time, that's why if-else statement is utilized. - */ - if ((thres_sts & info->thres_sts_lo) ^ cache->thres_sts_lo) { - WRITE_ONCE(cache->thres_sts_lo, thres_sts & info->thres_sts_lo); - hwmon_notify_event(pvt->hwmon, info->type, info->attr_min_alarm, - info->channel); - } else if ((thres_sts & info->thres_sts_hi) ^ cache->thres_sts_hi) { - WRITE_ONCE(cache->thres_sts_hi, thres_sts & info->thres_sts_hi); - hwmon_notify_event(pvt->hwmon, info->type, info->attr_max_alarm, - info->channel); - } - - return IRQ_HANDLED; -} - -static inline umode_t pvt_limit_is_visible(enum pvt_sensor_type type) -{ - return 0644; -} - -static inline umode_t pvt_alarm_is_visible(enum pvt_sensor_type type) -{ - return 0444; -} - -static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - long *val) -{ - struct pvt_cache *cache = &pvt->cache[type]; - unsigned int seq; - u32 data; - - do { - seq = read_seqbegin(&cache->data_seqlock); - data = cache->data; - } while (read_seqretry(&cache->data_seqlock, seq)); - - if (type == PVT_TEMP) - *val = polynomial_calc(&poly_N_to_temp, data); - else - *val = polynomial_calc(&poly_N_to_volt, data); - - return 0; -} - -static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - bool is_low, long *val) -{ - u32 data; - - /* No need in serialization, since it is just read from MMIO. */ - data = readl(pvt->regs + pvt_info[type].thres_base); - - if (is_low) - data = FIELD_GET(PVT_THRES_LO_MASK, data); - else - data = FIELD_GET(PVT_THRES_HI_MASK, data); - - if (type == PVT_TEMP) - *val = polynomial_calc(&poly_N_to_temp, data); - else - *val = polynomial_calc(&poly_N_to_volt, data); - - return 0; -} - -static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - bool is_low, long val) -{ - u32 data, limit, mask; - int ret; - - if (type == PVT_TEMP) { - val = clamp(val, PVT_TEMP_MIN, PVT_TEMP_MAX); - data = polynomial_calc(&poly_temp_to_N, val); - } else { - val = clamp(val, PVT_VOLT_MIN, PVT_VOLT_MAX); - data = polynomial_calc(&poly_volt_to_N, val); - } - - /* Serialize limit update, since a part of the register is changed. */ - ret = mutex_lock_interruptible(&pvt->iface_mtx); - if (ret) - return ret; - - /* Make sure the upper and lower ranges don't intersect. */ - limit = readl(pvt->regs + pvt_info[type].thres_base); - if (is_low) { - limit = FIELD_GET(PVT_THRES_HI_MASK, limit); - data = clamp_val(data, PVT_DATA_MIN, limit); - data = FIELD_PREP(PVT_THRES_LO_MASK, data); - mask = PVT_THRES_LO_MASK; - } else { - limit = FIELD_GET(PVT_THRES_LO_MASK, limit); - data = clamp_val(data, limit, PVT_DATA_MAX); - data = FIELD_PREP(PVT_THRES_HI_MASK, data); - mask = PVT_THRES_HI_MASK; - } - - pvt_update(pvt->regs + pvt_info[type].thres_base, mask, data); - - mutex_unlock(&pvt->iface_mtx); - - return 0; -} - -static int pvt_read_alarm(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - bool is_low, long *val) -{ - if (is_low) - *val = !!READ_ONCE(pvt->cache[type].thres_sts_lo); - else - *val = !!READ_ONCE(pvt->cache[type].thres_sts_hi); - - return 0; -} - -static const struct hwmon_channel_info * const pvt_channel_info[] = { - HWMON_CHANNEL_INFO(chip, - HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), - HWMON_CHANNEL_INFO(temp, - HWMON_T_INPUT | HWMON_T_TYPE | HWMON_T_LABEL | - HWMON_T_MIN | HWMON_T_MIN_ALARM | - HWMON_T_MAX | HWMON_T_MAX_ALARM | - HWMON_T_OFFSET), - HWMON_CHANNEL_INFO(in, - HWMON_I_INPUT | HWMON_I_LABEL | - HWMON_I_MIN | HWMON_I_MIN_ALARM | - HWMON_I_MAX | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LABEL | - HWMON_I_MIN | HWMON_I_MIN_ALARM | - HWMON_I_MAX | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LABEL | - HWMON_I_MIN | HWMON_I_MIN_ALARM | - HWMON_I_MAX | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LABEL | - HWMON_I_MIN | HWMON_I_MIN_ALARM | - HWMON_I_MAX | HWMON_I_MAX_ALARM), - NULL -}; - -#else /* !CONFIG_SENSORS_BT1_PVT_ALARMS */ - -static irqreturn_t pvt_hard_isr(int irq, void *data) -{ - struct pvt_hwmon *pvt = data; - struct pvt_cache *cache; - u32 val; - - /* - * Mask the DVALID interrupt so after exiting from the handler a - * repeated conversion wouldn't happen. - */ - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, - PVT_INTR_DVALID); - - /* - * Nothing special for alarm-less driver. Just read the data, update - * the cache and notify a waiter of this event. - */ - val = readl(pvt->regs + PVT_DATA); - if (!(val & PVT_DATA_VALID)) { - dev_err(pvt->dev, "Got IRQ when data isn't valid\n"); - return IRQ_HANDLED; - } - - cache = &pvt->cache[pvt->sensor]; - - WRITE_ONCE(cache->data, FIELD_GET(PVT_DATA_DATA_MASK, val)); - - complete(&cache->conversion); - - return IRQ_HANDLED; -} - -#define pvt_soft_isr NULL - -static inline umode_t pvt_limit_is_visible(enum pvt_sensor_type type) -{ - return 0; -} - -static inline umode_t pvt_alarm_is_visible(enum pvt_sensor_type type) -{ - return 0; -} - -static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - long *val) -{ - struct pvt_cache *cache = &pvt->cache[type]; - unsigned long timeout; - u32 data; - int ret; - - /* - * Lock PVT conversion interface until data cache is updated. The - * data read procedure is following: set the requested PVT sensor - * mode, enable IRQ and conversion, wait until conversion is finished, - * then disable conversion and IRQ, and read the cached data. - */ - ret = mutex_lock_interruptible(&pvt->iface_mtx); - if (ret) - return ret; - - pvt->sensor = type; - pvt_set_mode(pvt, pvt_info[type].mode); - - /* - * Unmask the DVALID interrupt and enable the sensors conversions. - * Do the reverse procedure when conversion is done. - */ - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, 0); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN); - - /* - * Wait with timeout since in case if the sensor is suddenly powered - * down the request won't be completed and the caller will hang up on - * this procedure until the power is back up again. Multiply the - * timeout by the factor of two to prevent a false timeout. - */ - timeout = 2 * usecs_to_jiffies(ktime_to_us(pvt->timeout)); - ret = wait_for_completion_timeout(&cache->conversion, timeout); - - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, - PVT_INTR_DVALID); - - data = READ_ONCE(cache->data); - - mutex_unlock(&pvt->iface_mtx); - - if (!ret) - return -ETIMEDOUT; - - if (type == PVT_TEMP) - *val = polynomial_calc(&poly_N_to_temp, data); - else - *val = polynomial_calc(&poly_N_to_volt, data); - - return 0; -} - -static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - bool is_low, long *val) -{ - return -EOPNOTSUPP; -} - -static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - bool is_low, long val) -{ - return -EOPNOTSUPP; -} - -static int pvt_read_alarm(struct pvt_hwmon *pvt, enum pvt_sensor_type type, - bool is_low, long *val) -{ - return -EOPNOTSUPP; -} - -static const struct hwmon_channel_info * const pvt_channel_info[] = { - HWMON_CHANNEL_INFO(chip, - HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), - HWMON_CHANNEL_INFO(temp, - HWMON_T_INPUT | HWMON_T_TYPE | HWMON_T_LABEL | - HWMON_T_OFFSET), - HWMON_CHANNEL_INFO(in, - HWMON_I_INPUT | HWMON_I_LABEL, - HWMON_I_INPUT | HWMON_I_LABEL, - HWMON_I_INPUT | HWMON_I_LABEL, - HWMON_I_INPUT | HWMON_I_LABEL), - NULL -}; - -#endif /* !CONFIG_SENSORS_BT1_PVT_ALARMS */ - -static inline bool pvt_hwmon_channel_is_valid(enum hwmon_sensor_types type, - int ch) -{ - switch (type) { - case hwmon_temp: - if (ch < 0 || ch >= PVT_TEMP_CHS) - return false; - break; - case hwmon_in: - if (ch < 0 || ch >= PVT_VOLT_CHS) - return false; - break; - default: - break; - } - - /* The rest of the types are independent from the channel number. */ - return true; -} - -static umode_t pvt_hwmon_is_visible(const void *data, - enum hwmon_sensor_types type, - u32 attr, int ch) -{ - if (!pvt_hwmon_channel_is_valid(type, ch)) - return 0; - - switch (type) { - case hwmon_chip: - switch (attr) { - case hwmon_chip_update_interval: - return 0644; - } - break; - case hwmon_temp: - switch (attr) { - case hwmon_temp_input: - case hwmon_temp_type: - case hwmon_temp_label: - return 0444; - case hwmon_temp_min: - case hwmon_temp_max: - return pvt_limit_is_visible(ch); - case hwmon_temp_min_alarm: - case hwmon_temp_max_alarm: - return pvt_alarm_is_visible(ch); - case hwmon_temp_offset: - return 0644; - } - break; - case hwmon_in: - switch (attr) { - case hwmon_in_input: - case hwmon_in_label: - return 0444; - case hwmon_in_min: - case hwmon_in_max: - return pvt_limit_is_visible(PVT_VOLT + ch); - case hwmon_in_min_alarm: - case hwmon_in_max_alarm: - return pvt_alarm_is_visible(PVT_VOLT + ch); - } - break; - default: - break; - } - - return 0; -} - -static int pvt_read_trim(struct pvt_hwmon *pvt, long *val) -{ - u32 data; - - data = readl(pvt->regs + PVT_CTRL); - *val = FIELD_GET(PVT_CTRL_TRIM_MASK, data) * PVT_TRIM_STEP; - - return 0; -} - -static int pvt_write_trim(struct pvt_hwmon *pvt, long val) -{ - u32 trim; - int ret; - - /* - * Serialize trim update, since a part of the register is changed and - * the controller is supposed to be disabled during this operation. - */ - ret = mutex_lock_interruptible(&pvt->iface_mtx); - if (ret) - return ret; - - trim = pvt_calc_trim(val); - pvt_set_trim(pvt, trim); - - mutex_unlock(&pvt->iface_mtx); - - return 0; -} - -static int pvt_read_timeout(struct pvt_hwmon *pvt, long *val) -{ - int ret; - - ret = mutex_lock_interruptible(&pvt->iface_mtx); - if (ret) - return ret; - - /* Return the result in msec as hwmon sysfs interface requires. */ - *val = ktime_to_ms(pvt->timeout); - - mutex_unlock(&pvt->iface_mtx); - - return 0; -} - -static int pvt_write_timeout(struct pvt_hwmon *pvt, long val) -{ - unsigned long rate; - ktime_t kt, cache; - u32 data; - int ret; - - rate = clk_get_rate(pvt->clks[PVT_CLOCK_REF].clk); - if (!rate) - return -ENODEV; - - /* - * If alarms are enabled, the requested timeout must be divided - * between all available sensors to have the requested delay - * applicable to each individual sensor. - */ - cache = kt = ms_to_ktime(val); -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - kt = ktime_divns(kt, PVT_SENSORS_NUM); -#endif - - /* - * Subtract a constant lag, which always persists due to the limited - * PVT sampling rate. Make sure the timeout is not negative. - */ - kt = ktime_sub_ns(kt, PVT_TOUT_MIN); - if (ktime_to_ns(kt) < 0) - kt = ktime_set(0, 0); - - /* - * Finally recalculate the timeout in terms of the reference clock - * period. - */ - data = ktime_divns(kt * rate, NSEC_PER_SEC); - - /* - * Update the measurements delay, but lock the interface first, since - * we have to disable PVT in order to have the new delay actually - * updated. - */ - ret = mutex_lock_interruptible(&pvt->iface_mtx); - if (ret) - return ret; - - pvt_set_tout(pvt, data); - pvt->timeout = cache; - - mutex_unlock(&pvt->iface_mtx); - - return 0; -} - -static int pvt_hwmon_read(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int ch, long *val) -{ - struct pvt_hwmon *pvt = dev_get_drvdata(dev); - - if (!pvt_hwmon_channel_is_valid(type, ch)) - return -EINVAL; - - switch (type) { - case hwmon_chip: - switch (attr) { - case hwmon_chip_update_interval: - return pvt_read_timeout(pvt, val); - } - break; - case hwmon_temp: - switch (attr) { - case hwmon_temp_input: - return pvt_read_data(pvt, ch, val); - case hwmon_temp_type: - *val = 1; - return 0; - case hwmon_temp_min: - return pvt_read_limit(pvt, ch, true, val); - case hwmon_temp_max: - return pvt_read_limit(pvt, ch, false, val); - case hwmon_temp_min_alarm: - return pvt_read_alarm(pvt, ch, true, val); - case hwmon_temp_max_alarm: - return pvt_read_alarm(pvt, ch, false, val); - case hwmon_temp_offset: - return pvt_read_trim(pvt, val); - } - break; - case hwmon_in: - switch (attr) { - case hwmon_in_input: - return pvt_read_data(pvt, PVT_VOLT + ch, val); - case hwmon_in_min: - return pvt_read_limit(pvt, PVT_VOLT + ch, true, val); - case hwmon_in_max: - return pvt_read_limit(pvt, PVT_VOLT + ch, false, val); - case hwmon_in_min_alarm: - return pvt_read_alarm(pvt, PVT_VOLT + ch, true, val); - case hwmon_in_max_alarm: - return pvt_read_alarm(pvt, PVT_VOLT + ch, false, val); - } - break; - default: - break; - } - - return -EOPNOTSUPP; -} - -static int pvt_hwmon_read_string(struct device *dev, - enum hwmon_sensor_types type, - u32 attr, int ch, const char **str) -{ - if (!pvt_hwmon_channel_is_valid(type, ch)) - return -EINVAL; - - switch (type) { - case hwmon_temp: - switch (attr) { - case hwmon_temp_label: - *str = pvt_info[ch].label; - return 0; - } - break; - case hwmon_in: - switch (attr) { - case hwmon_in_label: - *str = pvt_info[PVT_VOLT + ch].label; - return 0; - } - break; - default: - break; - } - - return -EOPNOTSUPP; -} - -static int pvt_hwmon_write(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int ch, long val) -{ - struct pvt_hwmon *pvt = dev_get_drvdata(dev); - - if (!pvt_hwmon_channel_is_valid(type, ch)) - return -EINVAL; - - switch (type) { - case hwmon_chip: - switch (attr) { - case hwmon_chip_update_interval: - return pvt_write_timeout(pvt, val); - } - break; - case hwmon_temp: - switch (attr) { - case hwmon_temp_min: - return pvt_write_limit(pvt, ch, true, val); - case hwmon_temp_max: - return pvt_write_limit(pvt, ch, false, val); - case hwmon_temp_offset: - return pvt_write_trim(pvt, val); - } - break; - case hwmon_in: - switch (attr) { - case hwmon_in_min: - return pvt_write_limit(pvt, PVT_VOLT + ch, true, val); - case hwmon_in_max: - return pvt_write_limit(pvt, PVT_VOLT + ch, false, val); - } - break; - default: - break; - } - - return -EOPNOTSUPP; -} - -static const struct hwmon_ops pvt_hwmon_ops = { - .is_visible = pvt_hwmon_is_visible, - .read = pvt_hwmon_read, - .read_string = pvt_hwmon_read_string, - .write = pvt_hwmon_write -}; - -static const struct hwmon_chip_info pvt_hwmon_info = { - .ops = &pvt_hwmon_ops, - .info = pvt_channel_info -}; - -static void pvt_clear_data(void *data) -{ - struct pvt_hwmon *pvt = data; -#if !defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - int idx; - - for (idx = 0; idx < PVT_SENSORS_NUM; ++idx) - complete_all(&pvt->cache[idx].conversion); -#endif - - mutex_destroy(&pvt->iface_mtx); -} - -static struct pvt_hwmon *pvt_create_data(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct pvt_hwmon *pvt; - int ret, idx; - - pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL); - if (!pvt) - return ERR_PTR(-ENOMEM); - - ret = devm_add_action(dev, pvt_clear_data, pvt); - if (ret) { - dev_err(dev, "Can't add PVT data clear action\n"); - return ERR_PTR(ret); - } - - pvt->dev = dev; - pvt->sensor = PVT_SENSOR_FIRST; - mutex_init(&pvt->iface_mtx); - -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - for (idx = 0; idx < PVT_SENSORS_NUM; ++idx) - seqlock_init(&pvt->cache[idx].data_seqlock); -#else - for (idx = 0; idx < PVT_SENSORS_NUM; ++idx) - init_completion(&pvt->cache[idx].conversion); -#endif - - return pvt; -} - -static int pvt_request_regs(struct pvt_hwmon *pvt) -{ - struct platform_device *pdev = to_platform_device(pvt->dev); - - pvt->regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(pvt->regs)) - return PTR_ERR(pvt->regs); - - return 0; -} - -static void pvt_disable_clks(void *data) -{ - struct pvt_hwmon *pvt = data; - - clk_bulk_disable_unprepare(PVT_CLOCK_NUM, pvt->clks); -} - -static int pvt_request_clks(struct pvt_hwmon *pvt) -{ - int ret; - - pvt->clks[PVT_CLOCK_APB].id = "pclk"; - pvt->clks[PVT_CLOCK_REF].id = "ref"; - - ret = devm_clk_bulk_get(pvt->dev, PVT_CLOCK_NUM, pvt->clks); - if (ret) { - dev_err(pvt->dev, "Couldn't get PVT clocks descriptors\n"); - return ret; - } - - ret = clk_bulk_prepare_enable(PVT_CLOCK_NUM, pvt->clks); - if (ret) { - dev_err(pvt->dev, "Couldn't enable the PVT clocks\n"); - return ret; - } - - ret = devm_add_action_or_reset(pvt->dev, pvt_disable_clks, pvt); - if (ret) { - dev_err(pvt->dev, "Can't add PVT clocks disable action\n"); - return ret; - } - - return 0; -} - -static int pvt_check_pwr(struct pvt_hwmon *pvt) -{ - unsigned long tout; - int ret = 0; - u32 data; - - /* - * Test out the sensor conversion functionality. If it is not done on - * time then the domain must have been unpowered and we won't be able - * to use the device later in this driver. - * Note If the power source is lost during the normal driver work the - * data read procedure will either return -ETIMEDOUT (for the - * alarm-less driver configuration) or just stop the repeated - * conversion. In the later case alas we won't be able to detect the - * problem. - */ - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN); - pvt_set_tout(pvt, 0); - readl(pvt->regs + PVT_DATA); - - tout = PVT_TOUT_MIN / NSEC_PER_USEC; - usleep_range(tout, 2 * tout); - - data = readl(pvt->regs + PVT_DATA); - if (!(data & PVT_DATA_VALID)) { - ret = -ENODEV; - dev_err(pvt->dev, "Sensor is powered down\n"); - } - - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - - return ret; -} - -static int pvt_init_iface(struct pvt_hwmon *pvt) -{ - unsigned long rate; - u32 trim, temp; - - rate = clk_get_rate(pvt->clks[PVT_CLOCK_REF].clk); - if (!rate) { - dev_err(pvt->dev, "Invalid reference clock rate\n"); - return -ENODEV; - } - - /* - * Make sure all interrupts and controller are disabled so not to - * accidentally have ISR executed before the driver data is fully - * initialized. Clear the IRQ status as well. - */ - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - readl(pvt->regs + PVT_CLR_INTR); - readl(pvt->regs + PVT_DATA); - - /* Setup default sensor mode, timeout and temperature trim. */ - pvt_set_mode(pvt, pvt_info[pvt->sensor].mode); - pvt_set_tout(pvt, PVT_TOUT_DEF); - - /* - * Preserve the current ref-clock based delay (Ttotal) between the - * sensors data samples in the driver data so not to recalculate it - * each time on the data requests and timeout reads. It consists of the - * delay introduced by the internal ref-clock timer (N / Fclk) and the - * constant timeout caused by each conversion latency (Tmin): - * Ttotal = N / Fclk + Tmin - * If alarms are enabled the sensors are polled one after another and - * in order to get the next measurement of a particular sensor the - * caller will have to wait for at most until all the others are - * polled. In that case the formulae will look a bit different: - * Ttotal = 5 * (N / Fclk + Tmin) - */ -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - pvt->timeout = ktime_set(PVT_SENSORS_NUM * PVT_TOUT_DEF, 0); - pvt->timeout = ktime_divns(pvt->timeout, rate); - pvt->timeout = ktime_add_ns(pvt->timeout, PVT_SENSORS_NUM * PVT_TOUT_MIN); -#else - pvt->timeout = ktime_set(PVT_TOUT_DEF, 0); - pvt->timeout = ktime_divns(pvt->timeout, rate); - pvt->timeout = ktime_add_ns(pvt->timeout, PVT_TOUT_MIN); -#endif - - trim = PVT_TRIM_DEF; - if (!of_property_read_u32(pvt->dev->of_node, - "baikal,pvt-temp-offset-millicelsius", &temp)) - trim = pvt_calc_trim(temp); - - pvt_set_trim(pvt, trim); - - return 0; -} - -static int pvt_request_irq(struct pvt_hwmon *pvt) -{ - struct platform_device *pdev = to_platform_device(pvt->dev); - int ret; - - pvt->irq = platform_get_irq(pdev, 0); - if (pvt->irq < 0) - return pvt->irq; - - ret = devm_request_threaded_irq(pvt->dev, pvt->irq, - pvt_hard_isr, pvt_soft_isr, -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - IRQF_SHARED | IRQF_TRIGGER_HIGH | - IRQF_ONESHOT, -#else - IRQF_SHARED | IRQF_TRIGGER_HIGH, -#endif - "pvt", pvt); - if (ret) { - dev_err(pvt->dev, "Couldn't request PVT IRQ\n"); - return ret; - } - - return 0; -} - -static int pvt_create_hwmon(struct pvt_hwmon *pvt) -{ - pvt->hwmon = devm_hwmon_device_register_with_info(pvt->dev, "pvt", pvt, - &pvt_hwmon_info, NULL); - if (IS_ERR(pvt->hwmon)) { - dev_err(pvt->dev, "Couldn't create hwmon device\n"); - return PTR_ERR(pvt->hwmon); - } - - return 0; -} - -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - -static void pvt_disable_iface(void *data) -{ - struct pvt_hwmon *pvt = data; - - mutex_lock(&pvt->iface_mtx); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0); - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, - PVT_INTR_DVALID); - mutex_unlock(&pvt->iface_mtx); -} - -static int pvt_enable_iface(struct pvt_hwmon *pvt) -{ - int ret; - - ret = devm_add_action(pvt->dev, pvt_disable_iface, pvt); - if (ret) { - dev_err(pvt->dev, "Can't add PVT disable interface action\n"); - return ret; - } - - /* - * Enable sensors data conversion and IRQ. We need to lock the - * interface mutex since hwmon has just been created and the - * corresponding sysfs files are accessible from user-space, - * which theoretically may cause races. - */ - mutex_lock(&pvt->iface_mtx); - pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, 0); - pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN); - mutex_unlock(&pvt->iface_mtx); - - return 0; -} - -#else /* !CONFIG_SENSORS_BT1_PVT_ALARMS */ - -static int pvt_enable_iface(struct pvt_hwmon *pvt) -{ - return 0; -} - -#endif /* !CONFIG_SENSORS_BT1_PVT_ALARMS */ - -static int pvt_probe(struct platform_device *pdev) -{ - struct pvt_hwmon *pvt; - int ret; - - pvt = pvt_create_data(pdev); - if (IS_ERR(pvt)) - return PTR_ERR(pvt); - - ret = pvt_request_regs(pvt); - if (ret) - return ret; - - ret = pvt_request_clks(pvt); - if (ret) - return ret; - - ret = pvt_check_pwr(pvt); - if (ret) - return ret; - - ret = pvt_init_iface(pvt); - if (ret) - return ret; - - ret = pvt_request_irq(pvt); - if (ret) - return ret; - - ret = pvt_create_hwmon(pvt); - if (ret) - return ret; - - ret = pvt_enable_iface(pvt); - if (ret) - return ret; - - return 0; -} - -static const struct of_device_id pvt_of_match[] = { - { .compatible = "baikal,bt1-pvt" }, - { } -}; -MODULE_DEVICE_TABLE(of, pvt_of_match); - -static struct platform_driver pvt_driver = { - .probe = pvt_probe, - .driver = { - .name = "bt1-pvt", - .of_match_table = pvt_of_match - } -}; -module_platform_driver(pvt_driver); - -MODULE_AUTHOR("Maxim Kaurkin <maxim.kaurkin@baikalelectronics.ru>"); -MODULE_DESCRIPTION("Baikal-T1 PVT driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/bt1-pvt.h b/drivers/hwmon/bt1-pvt.h deleted file mode 100644 index 93b8dd5e7c94..000000000000 --- a/drivers/hwmon/bt1-pvt.h +++ /dev/null @@ -1,247 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC - * - * Baikal-T1 Process, Voltage, Temperature sensor driver - */ -#ifndef __HWMON_BT1_PVT_H__ -#define __HWMON_BT1_PVT_H__ - -#include <linux/completion.h> -#include <linux/hwmon.h> -#include <linux/kernel.h> -#include <linux/ktime.h> -#include <linux/mutex.h> -#include <linux/seqlock.h> - -/* Baikal-T1 PVT registers and their bitfields */ -#define PVT_CTRL 0x00 -#define PVT_CTRL_EN BIT(0) -#define PVT_CTRL_MODE_FLD 1 -#define PVT_CTRL_MODE_MASK GENMASK(3, PVT_CTRL_MODE_FLD) -#define PVT_CTRL_MODE_TEMP 0x0 -#define PVT_CTRL_MODE_VOLT 0x1 -#define PVT_CTRL_MODE_LVT 0x2 -#define PVT_CTRL_MODE_HVT 0x4 -#define PVT_CTRL_MODE_SVT 0x6 -#define PVT_CTRL_TRIM_FLD 4 -#define PVT_CTRL_TRIM_MASK GENMASK(8, PVT_CTRL_TRIM_FLD) -#define PVT_DATA 0x04 -#define PVT_DATA_VALID BIT(10) -#define PVT_DATA_DATA_FLD 0 -#define PVT_DATA_DATA_MASK GENMASK(9, PVT_DATA_DATA_FLD) -#define PVT_TTHRES 0x08 -#define PVT_VTHRES 0x0C -#define PVT_LTHRES 0x10 -#define PVT_HTHRES 0x14 -#define PVT_STHRES 0x18 -#define PVT_THRES_LO_FLD 0 -#define PVT_THRES_LO_MASK GENMASK(9, PVT_THRES_LO_FLD) -#define PVT_THRES_HI_FLD 10 -#define PVT_THRES_HI_MASK GENMASK(19, PVT_THRES_HI_FLD) -#define PVT_TTIMEOUT 0x1C -#define PVT_INTR_STAT 0x20 -#define PVT_INTR_MASK 0x24 -#define PVT_RAW_INTR_STAT 0x28 -#define PVT_INTR_DVALID BIT(0) -#define PVT_INTR_TTHRES_LO BIT(1) -#define PVT_INTR_TTHRES_HI BIT(2) -#define PVT_INTR_VTHRES_LO BIT(3) -#define PVT_INTR_VTHRES_HI BIT(4) -#define PVT_INTR_LTHRES_LO BIT(5) -#define PVT_INTR_LTHRES_HI BIT(6) -#define PVT_INTR_HTHRES_LO BIT(7) -#define PVT_INTR_HTHRES_HI BIT(8) -#define PVT_INTR_STHRES_LO BIT(9) -#define PVT_INTR_STHRES_HI BIT(10) -#define PVT_INTR_ALL GENMASK(10, 0) -#define PVT_CLR_INTR 0x2C - -/* - * PVT sensors-related limits and default values - * @PVT_TEMP_MIN: Minimal temperature in millidegrees of Celsius. - * @PVT_TEMP_MAX: Maximal temperature in millidegrees of Celsius. - * @PVT_TEMP_CHS: Number of temperature hwmon channels. - * @PVT_VOLT_MIN: Minimal voltage in mV. - * @PVT_VOLT_MAX: Maximal voltage in mV. - * @PVT_VOLT_CHS: Number of voltage hwmon channels. - * @PVT_DATA_MIN: Minimal PVT raw data value. - * @PVT_DATA_MAX: Maximal PVT raw data value. - * @PVT_TRIM_MIN: Minimal temperature sensor trim value. - * @PVT_TRIM_MAX: Maximal temperature sensor trim value. - * @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value - * when one is determined for Baikal-T1 SoC). - * @PVT_TRIM_TEMP: Maximum temperature encoded by the trim factor. - * @PVT_TRIM_STEP: Temperature stride corresponding to the trim value. - * @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds. - * @PVT_TOUT_DEF: Default data measurements timeout. In case if alarms are - * activated the PVT IRQ is enabled to be raised after each - * conversion in order to have the thresholds checked and the - * converted value cached. Too frequent conversions may cause - * the system CPU overload. Lets set the 50ms delay between - * them by default to prevent this. - */ -#define PVT_TEMP_MIN -48380L -#define PVT_TEMP_MAX 147438L -#define PVT_TEMP_CHS 1 -#define PVT_VOLT_MIN 620L -#define PVT_VOLT_MAX 1168L -#define PVT_VOLT_CHS 4 -#define PVT_DATA_MIN 0 -#define PVT_DATA_MAX (PVT_DATA_DATA_MASK >> PVT_DATA_DATA_FLD) -#define PVT_TRIM_MIN 0 -#define PVT_TRIM_MAX (PVT_CTRL_TRIM_MASK >> PVT_CTRL_TRIM_FLD) -#define PVT_TRIM_TEMP 7130 -#define PVT_TRIM_STEP (PVT_TRIM_TEMP / PVT_TRIM_MAX) -#define PVT_TRIM_DEF 0 -#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000) -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) -# define PVT_TOUT_DEF 60000 -#else -# define PVT_TOUT_DEF 0 -#endif - -/* - * enum pvt_sensor_type - Baikal-T1 PVT sensor types (correspond to each PVT - * sampling mode) - * @PVT_SENSOR*: helpers to traverse the sensors in loops. - * @PVT_TEMP: PVT Temperature sensor. - * @PVT_VOLT: PVT Voltage sensor. - * @PVT_LVT: PVT Low-Voltage threshold sensor. - * @PVT_HVT: PVT High-Voltage threshold sensor. - * @PVT_SVT: PVT Standard-Voltage threshold sensor. - */ -enum pvt_sensor_type { - PVT_SENSOR_FIRST, - PVT_TEMP = PVT_SENSOR_FIRST, - PVT_VOLT, - PVT_LVT, - PVT_HVT, - PVT_SVT, - PVT_SENSOR_LAST = PVT_SVT, - PVT_SENSORS_NUM -}; - -/* - * enum pvt_clock_type - Baikal-T1 PVT clocks. - * @PVT_CLOCK_APB: APB clock. - * @PVT_CLOCK_REF: PVT reference clock. - */ -enum pvt_clock_type { - PVT_CLOCK_APB, - PVT_CLOCK_REF, - PVT_CLOCK_NUM -}; - -/* - * struct pvt_sensor_info - Baikal-T1 PVT sensor informational structure - * @channel: Sensor channel ID. - * @label: hwmon sensor label. - * @mode: PVT mode corresponding to the channel. - * @thres_base: upper and lower threshold values of the sensor. - * @thres_sts_lo: low threshold status bitfield. - * @thres_sts_hi: high threshold status bitfield. - * @type: Sensor type. - * @attr_min_alarm: Min alarm attribute ID. - * @attr_min_alarm: Max alarm attribute ID. - */ -struct pvt_sensor_info { - int channel; - const char *label; - u32 mode; - unsigned long thres_base; - u32 thres_sts_lo; - u32 thres_sts_hi; - enum hwmon_sensor_types type; - u32 attr_min_alarm; - u32 attr_max_alarm; -}; - -#define PVT_SENSOR_INFO(_ch, _label, _type, _mode, _thres) \ - { \ - .channel = _ch, \ - .label = _label, \ - .mode = PVT_CTRL_MODE_ ##_mode, \ - .thres_base = PVT_ ##_thres, \ - .thres_sts_lo = PVT_INTR_ ##_thres## _LO, \ - .thres_sts_hi = PVT_INTR_ ##_thres## _HI, \ - .type = _type, \ - .attr_min_alarm = _type## _min, \ - .attr_max_alarm = _type## _max, \ - } - -/* - * struct pvt_cache - PVT sensors data cache - * @data: data cache in raw format. - * @thres_sts_lo: low threshold status saved on the previous data conversion. - * @thres_sts_hi: high threshold status saved on the previous data conversion. - * @data_seqlock: cached data seq-lock. - * @conversion: data conversion completion. - */ -struct pvt_cache { - u32 data; -#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) - seqlock_t data_seqlock; - u32 thres_sts_lo; - u32 thres_sts_hi; -#else - struct completion conversion; -#endif -}; - -/* - * struct pvt_hwmon - Baikal-T1 PVT private data - * @dev: device structure of the PVT platform device. - * @hwmon: hwmon device structure. - * @regs: pointer to the Baikal-T1 PVT registers region. - * @irq: PVT events IRQ number. - * @clks: Array of the PVT clocks descriptor (APB/ref clocks). - * @ref_clk: Pointer to the reference clocks descriptor. - * @iface_mtx: Generic interface mutex (used to lock the alarm registers - * when the alarms enabled, or the data conversion interface - * if alarms are disabled). - * @sensor: current PVT sensor the data conversion is being performed for. - * @cache: data cache descriptor. - * @timeout: conversion timeout cache. - */ -struct pvt_hwmon { - struct device *dev; - struct device *hwmon; - - void __iomem *regs; - int irq; - - struct clk_bulk_data clks[PVT_CLOCK_NUM]; - - struct mutex iface_mtx; - enum pvt_sensor_type sensor; - struct pvt_cache cache[PVT_SENSORS_NUM]; - ktime_t timeout; -}; - -/* - * struct pvt_poly_term - a term descriptor of the PVT data translation - * 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 pvt_poly_term { - unsigned int deg; - long coef; - long divider; - long divider_leftover; -}; - -/* - * struct pvt_poly - PVT data translation polynomial descriptor - * @total_divider: total data divider. - * @terms: polynomial terms up to a free one. - */ -struct pvt_poly { - long total_divider; - struct pvt_poly_term terms[]; -}; - -#endif /* __HWMON_BT1_PVT_H__ */ diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c index ccce948a4306..964a8cb278f1 100644 --- a/drivers/hwmon/emc1403.c +++ b/drivers/hwmon/emc1403.c @@ -40,7 +40,7 @@ static ssize_t power_state_show(struct device *dev, struct device_attribute *att retval = regmap_read(data->regmap, 0x03, &val); if (retval < 0) return retval; - return sprintf(buf, "%d\n", !!(val & BIT(6))); + return sysfs_emit(buf, "%d\n", !!(val & BIT(6))); } static ssize_t power_state_store(struct device *dev, struct device_attribute *attr, diff --git a/drivers/hwmon/gpd-fan.c b/drivers/hwmon/gpd-fan.c index 1729729b135f..80de5f20781e 100644 --- a/drivers/hwmon/gpd-fan.c +++ b/drivers/hwmon/gpd-fan.c @@ -210,6 +210,14 @@ static const struct dmi_system_id dmi_table[] = { .driver_data = &gpd_duo_drvdata, }, { + // GPD Win 5 with AMD AI MAX 395 + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1618-05"), + }, + .driver_data = &gpd_duo_drvdata, + }, + { // GPD Pocket 4 .matches = { DMI_MATCH(DMI_SYS_VENDOR, "GPD"), diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 9695dca62a7e..6812d1fd7c28 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -505,6 +505,7 @@ static bool is_string_attr(enum hwmon_sensor_types type, u32 attr) (type == hwmon_curr && attr == hwmon_curr_label) || (type == hwmon_power && attr == hwmon_power_label) || (type == hwmon_energy && attr == hwmon_energy_label) || + (type == hwmon_energy64 && attr == hwmon_energy_label) || (type == hwmon_humidity && attr == hwmon_humidity_label) || (type == hwmon_fan && attr == hwmon_fan_label); } diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c index bd7b3380d847..a116f1600e81 100644 --- a/drivers/hwmon/ina209.c +++ b/drivers/hwmon/ina209.c @@ -27,8 +27,6 @@ #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> -#include <linux/platform_data/ina2xx.h> - /* register definitions */ #define INA209_CONFIGURATION 0x00 #define INA209_STATUS 0x01 @@ -487,7 +485,6 @@ static void ina209_restore_conf(struct i2c_client *client, static int ina209_init_client(struct i2c_client *client, struct ina209_data *data) { - struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev); u32 shunt; int reg; @@ -501,12 +498,8 @@ static int ina209_init_client(struct i2c_client *client, return reg; data->config_orig = reg; - if (pdata) { - if (pdata->shunt_uohms <= 0) - return -EINVAL; - shunt = pdata->shunt_uohms; - } else if (!of_property_read_u32(client->dev.of_node, "shunt-resistor", - &shunt)) { + if (!of_property_read_u32(client->dev.of_node, "shunt-resistor", + &shunt)) { if (shunt == 0) return -EINVAL; } else { diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 69ac0468dee4..613ffb622b7c 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -1,22 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Driver for Texas Instruments INA219, INA226 power monitor chips - * - * INA219: - * Zero Drift Bi-Directional Current/Power Monitor with I2C Interface - * Datasheet: https://www.ti.com/product/ina219 - * - * INA220: - * Bi-Directional Current/Power Monitor with I2C Interface - * Datasheet: https://www.ti.com/product/ina220 - * - * INA226: - * Bi-Directional Current/Power Monitor with I2C Interface - * Datasheet: https://www.ti.com/product/ina226 - * - * INA230: - * Bi-directional Current/Power Monitor with I2C Interface - * Datasheet: https://www.ti.com/product/ina230 + * Driver for Texas Instruments INA219, INA226 and register-layout compatible + * current/power monitor chips with I2C Interface * * Copyright (C) 2012 Lothar Felten <lothar.felten@gmail.com> * Thanks to Jan Volkering @@ -49,7 +34,6 @@ /* INA226 register definitions */ #define INA226_MASK_ENABLE 0x06 #define INA226_ALERT_LIMIT 0x07 -#define INA226_DIE_ID 0xFF /* SY24655 register definitions */ #define SY24655_EIN 0x0A @@ -135,18 +119,27 @@ static const struct regmap_config ina2xx_regmap_config = { .writeable_reg = ina2xx_writeable_reg, }; -enum ina2xx_ids { ina219, ina226, ina260, sy24655 }; +enum ina2xx_ids { + ina219, + ina226, + ina234, + ina260, + sy24655 +}; struct ina2xx_config { u16 config_default; bool has_alerts; /* chip supports alerts and limits */ bool has_ishunt; /* chip has internal shunt resistor */ - bool has_power_average; /* chip has internal shunt resistor */ + bool has_power_average; /* chip supports average power */ + bool has_update_interval; int calibration_value; int shunt_div; + int shunt_voltage_shift; int bus_voltage_shift; int bus_voltage_lsb; /* uV */ int power_lsb_factor; + int current_shift; }; struct ina2xx_data { @@ -165,44 +158,70 @@ static const struct ina2xx_config ina2xx_config[] = { .config_default = INA219_CONFIG_DEFAULT, .calibration_value = 4096, .shunt_div = 100, + .shunt_voltage_shift = 0, .bus_voltage_shift = 3, .bus_voltage_lsb = 4000, .power_lsb_factor = 20, .has_alerts = false, .has_ishunt = false, .has_power_average = false, + .current_shift = 0, + .has_update_interval = false, }, [ina226] = { .config_default = INA226_CONFIG_DEFAULT, .calibration_value = 2048, .shunt_div = 400, + .shunt_voltage_shift = 0, .bus_voltage_shift = 0, .bus_voltage_lsb = 1250, .power_lsb_factor = 25, .has_alerts = true, .has_ishunt = false, .has_power_average = false, + .current_shift = 0, + .has_update_interval = true, + }, + [ina234] = { + .config_default = INA226_CONFIG_DEFAULT, + .calibration_value = 2048, + .shunt_div = 25, /* 2.5 µV/LSB raw ADC reading from INA2XX_SHUNT_VOLTAGE */ + .shunt_voltage_shift = 4, + .bus_voltage_shift = 4, + .bus_voltage_lsb = 25600, + .power_lsb_factor = 32, + .has_alerts = true, + .has_ishunt = false, + .has_power_average = false, + .current_shift = 4, + .has_update_interval = true, }, [ina260] = { .config_default = INA260_CONFIG_DEFAULT, .shunt_div = 400, + .shunt_voltage_shift = 0, .bus_voltage_shift = 0, .bus_voltage_lsb = 1250, .power_lsb_factor = 8, .has_alerts = true, .has_ishunt = true, .has_power_average = false, + .current_shift = 0, + .has_update_interval = true, }, [sy24655] = { .config_default = SY24655_CONFIG_DEFAULT, .calibration_value = 4096, .shunt_div = 400, + .shunt_voltage_shift = 0, .bus_voltage_shift = 0, .bus_voltage_lsb = 1250, .power_lsb_factor = 25, .has_alerts = true, .has_ishunt = false, .has_power_average = true, + .current_shift = 0, + .has_update_interval = false, }, }; @@ -255,7 +274,8 @@ static int ina2xx_get_value(struct ina2xx_data *data, u8 reg, switch (reg) { case INA2XX_SHUNT_VOLTAGE: /* signed register */ - val = DIV_ROUND_CLOSEST((s16)regval, data->config->shunt_div); + val = (s16)regval >> data->config->shunt_voltage_shift; + val = DIV_ROUND_CLOSEST(val, data->config->shunt_div); break; case INA2XX_BUS_VOLTAGE: val = (regval >> data->config->bus_voltage_shift) * @@ -267,7 +287,8 @@ static int ina2xx_get_value(struct ina2xx_data *data, u8 reg, break; case INA2XX_CURRENT: /* signed register, result in mA */ - val = (s16)regval * data->current_lsb_uA; + val = ((s16)regval >> data->config->current_shift) * + data->current_lsb_uA; val = DIV_ROUND_CLOSEST(val, 1000); break; case INA2XX_CALIBRATION: @@ -361,6 +382,7 @@ static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val) case INA2XX_SHUNT_VOLTAGE: val = clamp_val(val, 0, SHRT_MAX * data->config->shunt_div); val *= data->config->shunt_div; + val <<= data->config->shunt_voltage_shift; return clamp_val(val, 0, SHRT_MAX); case INA2XX_BUS_VOLTAGE: val = clamp_val(val, 0, 200000); @@ -375,6 +397,7 @@ static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val) val = clamp_val(val, INT_MIN / 1000, INT_MAX / 1000); /* signed register, result in mA */ val = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb_uA); + val <<= data->config->current_shift; return clamp_val(val, SHRT_MIN, SHRT_MAX); default: /* programmer goofed */ @@ -706,7 +729,7 @@ static umode_t ina2xx_is_visible(const void *_data, enum hwmon_sensor_types type const struct ina2xx_data *data = _data; bool has_alerts = data->config->has_alerts; bool has_power_average = data->config->has_power_average; - enum ina2xx_ids chip = data->chip; + bool has_update_interval = data->config->has_update_interval; switch (type) { case hwmon_in: @@ -768,7 +791,7 @@ static umode_t ina2xx_is_visible(const void *_data, enum hwmon_sensor_types type case hwmon_chip: switch (attr) { case hwmon_chip_update_interval: - if (chip == ina226 || chip == ina260) + if (has_update_interval) return 0644; break; default: @@ -982,6 +1005,7 @@ static const struct i2c_device_id ina2xx_id[] = { { "ina226", ina226 }, { "ina230", ina226 }, { "ina231", ina226 }, + { "ina234", ina234 }, { "ina260", ina260 }, { "sy24655", sy24655 }, { } @@ -1014,6 +1038,10 @@ static const struct of_device_id __maybe_unused ina2xx_of_match[] = { .data = (void *)ina226 }, { + .compatible = "ti,ina234", + .data = (void *)ina234 + }, + { .compatible = "ti,ina260", .data = (void *)ina260 }, diff --git a/drivers/hwmon/isl28022.c b/drivers/hwmon/isl28022.c index c2e559dde63f..96fcfbfff48f 100644 --- a/drivers/hwmon/isl28022.c +++ b/drivers/hwmon/isl28022.c @@ -9,6 +9,7 @@ #include <linux/err.h> #include <linux/hwmon.h> #include <linux/i2c.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/regmap.h> @@ -185,8 +186,8 @@ static int isl28022_read_power(struct device *dev, u32 attr, long *val) ISL28022_REG_POWER, ®val); if (err < 0) return err; - *val = ((51200000L * ((long)data->gain)) / - (long)data->shunt) * (long)regval; + *val = min(div_u64(51200000ULL * data->gain * regval, + data->shunt), LONG_MAX); break; default: return -EOPNOTSUPP; @@ -337,21 +338,28 @@ DEFINE_SHOW_ATTRIBUTE(shunt_voltage); */ static int isl28022_read_properties(struct device *dev, struct isl28022_data *data) { + const char *propname; u32 val; int err; - err = device_property_read_u32(dev, "shunt-resistor-micro-ohms", &val); - if (err == -EINVAL) + propname = "shunt-resistor-micro-ohms"; + if (device_property_present(dev, propname)) { + err = device_property_read_u32(dev, propname, &val); + if (err) + return err; + } else { val = 10000; - else if (err < 0) - return err; + } data->shunt = val; - err = device_property_read_u32(dev, "renesas,shunt-range-microvolt", &val); - if (err == -EINVAL) + propname = "renesas,shunt-range-microvolt"; + if (device_property_present(dev, propname)) { + err = device_property_read_u32(dev, propname, &val); + if (err) + return err; + } else { val = 320000; - else if (err < 0) - return err; + } switch (val) { case 40000: @@ -375,20 +383,19 @@ static int isl28022_read_properties(struct device *dev, struct isl28022_data *da goto shunt_invalid; break; default: - return dev_err_probe(dev, -EINVAL, - "renesas,shunt-range-microvolt invalid value %d\n", - val); + return dev_err_probe(dev, -EINVAL, "%s invalid value %u\n", propname, val); } - err = device_property_read_u32(dev, "renesas,average-samples", &val); - if (err == -EINVAL) + propname = "renesas,average-samples"; + if (device_property_present(dev, propname)) { + err = device_property_read_u32(dev, propname, &val); + if (err) + return err; + } else { val = 1; - else if (err < 0) - return err; + } if (val > 128 || hweight32(val) != 1) - return dev_err_probe(dev, -EINVAL, - "renesas,average-samples invalid value %d\n", - val); + return dev_err_probe(dev, -EINVAL, "%s invalid value %u\n", propname, val); data->average = val; diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index 5cfb98a0512f..5fd310662ee4 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -16,6 +16,7 @@ * IT8622E Super I/O chip w/LPC interface * IT8623E Super I/O chip w/LPC interface * IT8628E Super I/O chip w/LPC interface + * IT8689E Super I/O chip w/LPC interface * IT8705F Super I/O chip w/LPC interface * IT8712F Super I/O chip w/LPC interface * IT8716F Super I/O chip w/LPC interface @@ -64,7 +65,7 @@ enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8732, it8771, it8772, it8781, it8782, it8783, it8786, it8790, - it8792, it8603, it8620, it8622, it8628, it87952 }; + it8792, it8603, it8620, it8622, it8628, it8689, it87952 }; static struct platform_device *it87_pdev[2]; @@ -162,6 +163,7 @@ static inline void superio_exit(int ioreg, bool noexit) #define IT8622E_DEVID 0x8622 #define IT8623E_DEVID 0x8623 #define IT8628E_DEVID 0x8628 +#define IT8689E_DEVID 0x8689 #define IT87952E_DEVID 0x8695 /* Logical device 4 (Environmental Monitor) registers */ @@ -502,6 +504,15 @@ static const struct it87_devices it87_devices[] = { | FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF, .peci_mask = 0x07, }, + [it8689] = { + .name = "it8689", + .model = "IT8689E", + .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_SIX_FANS | FEAT_IN7_INTERNAL + | FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_AVCC3 + | FEAT_FANCTL_ONOFF, + .smbus_bitmap = BIT(1) | BIT(2), + }, [it87952] = { .name = "it87952", .model = "IT87952E", @@ -2785,6 +2796,9 @@ static int __init it87_find(int sioaddr, unsigned short *address, case IT8628E_DEVID: sio_data->type = it8628; break; + case IT8689E_DEVID: + sio_data->type = it8689; + break; case IT87952E_DEVID: sio_data->type = it87952; break; @@ -3002,6 +3016,51 @@ static int __init it87_find(int sioaddr, unsigned short *address, sio_data->beep_pin = superio_inb(sioaddr, IT87_SIO_BEEP_PIN_REG) & 0x3f; + } else if (sio_data->type == it8689) { + int reg; + + superio_select(sioaddr, GPIO); + + /* Check for pwm5 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO1_REG); + if (reg & BIT(6)) + sio_data->skip_pwm |= BIT(4); + + /* Check for fan4, fan5 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO2_REG); + if (!(reg & BIT(5))) + sio_data->skip_fan |= BIT(3); + if (!(reg & BIT(4))) + sio_data->skip_fan |= BIT(4); + + /* Check for pwm3, fan3 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO3_REG); + if (reg & BIT(6)) + sio_data->skip_pwm |= BIT(2); + if (reg & BIT(7)) + sio_data->skip_fan |= BIT(2); + + /* Check for pwm4 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO4_REG); + if (reg & BIT(2)) + sio_data->skip_pwm |= BIT(3); + + /* Check for pwm2, fan2 */ + reg = superio_inb(sioaddr, IT87_SIO_GPIO5_REG); + if (reg & BIT(1)) + sio_data->skip_pwm |= BIT(1); + if (reg & BIT(2)) + sio_data->skip_fan |= BIT(1); + /* Check for pwm6, fan6 */ + if (!(reg & BIT(7))) { + sio_data->skip_pwm |= BIT(5); + sio_data->skip_fan |= BIT(5); + } + + /* in9 (AVCC3) is always internal, no PINX2 check needed */ + + sio_data->beep_pin = superio_inb(sioaddr, + IT87_SIO_BEEP_PIN_REG) & 0x3f; } else if (sio_data->type == it8622) { int reg; diff --git a/drivers/hwmon/lattepanda-sigma-ec.c b/drivers/hwmon/lattepanda-sigma-ec.c new file mode 100644 index 000000000000..f06097422201 --- /dev/null +++ b/drivers/hwmon/lattepanda-sigma-ec.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for LattePanda Sigma EC. + * + * The LattePanda Sigma is an x86 SBC made by DFRobot with an ITE IT8613E + * Embedded Controller that manages a CPU fan and thermal sensors. + * + * The BIOS declares the ACPI Embedded Controller (PNP0C09) with _STA + * returning 0 and provides only stub ECRD/ECWT methods that return Zero + * for all registers. Since the kernel's ACPI EC subsystem never initializes, + * ec_read() is not available and direct port I/O to the standard ACPI EC + * ports (0x62/0x66) is used instead. + * + * Because ACPI never initializes the EC, there is no concurrent firmware + * access to these ports, and no ACPI Global Lock or namespace mutex is + * required. The hwmon with_info API serializes all sysfs callbacks, + * so no additional driver-level locking is needed. + * + * The EC register map was discovered by dumping all 256 registers, + * identifying those that change in real-time, and validating by physically + * stopping the fan and observing the RPM register drop to zero. The map + * has been verified on BIOS version 5.27; other versions may differ. + * + * Copyright (c) 2026 Mariano Abad <weimaraner@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#define DRIVER_NAME "lattepanda_sigma_ec" + +/* EC I/O ports (standard ACPI EC interface) */ +#define EC_DATA_PORT 0x62 +#define EC_CMD_PORT 0x66 /* also status port */ + +/* EC commands */ +#define EC_CMD_READ 0x80 + +/* EC status register bits */ +#define EC_STATUS_OBF 0x01 /* Output Buffer Full */ +#define EC_STATUS_IBF 0x02 /* Input Buffer Full */ + +/* EC register offsets for LattePanda Sigma (BIOS 5.27) */ +#define EC_REG_FAN_RPM_HI 0x2E +#define EC_REG_FAN_RPM_LO 0x2F +#define EC_REG_TEMP_BOARD 0x60 +#define EC_REG_TEMP_CPU 0x70 +#define EC_REG_FAN_DUTY 0x93 + +/* + * EC polling uses udelay() because the EC typically responds within a + * few microseconds. The kernel's own ACPI EC driver (drivers/acpi/ec.c) + * likewise uses udelay() for busy-polling with a per-poll delay of 550us. + * + * usleep_range() was tested but caused EC protocol failures: the EC + * clears its status flags within microseconds, and sleeping for 50-100us + * between polls allowed the flags to transition past the expected state. + * + * The worst-case total busy-wait of 25ms covers EC recovery after errors. + * In practice the EC responds within 10us so the loop exits immediately. + */ +#define EC_TIMEOUT_US 25000 +#define EC_POLL_US 1 + +static bool force; +module_param(force, bool, 0444); +MODULE_PARM_DESC(force, + "Force loading on untested BIOS versions (default: false)"); + +static struct platform_device *lps_ec_pdev; + +static int ec_wait_ibf_clear(void) +{ + int i; + + for (i = 0; i < EC_TIMEOUT_US; i++) { + if (!(inb(EC_CMD_PORT) & EC_STATUS_IBF)) + return 0; + udelay(EC_POLL_US); + } + return -ETIMEDOUT; +} + +static int ec_wait_obf_set(void) +{ + int i; + + for (i = 0; i < EC_TIMEOUT_US; i++) { + if (inb(EC_CMD_PORT) & EC_STATUS_OBF) + return 0; + udelay(EC_POLL_US); + } + return -ETIMEDOUT; +} + +static int ec_read_reg(u8 reg, u8 *val) +{ + int ret; + + ret = ec_wait_ibf_clear(); + if (ret) + return ret; + + outb(EC_CMD_READ, EC_CMD_PORT); + + ret = ec_wait_ibf_clear(); + if (ret) + return ret; + + outb(reg, EC_DATA_PORT); + + ret = ec_wait_obf_set(); + if (ret) + return ret; + + *val = inb(EC_DATA_PORT); + return 0; +} + +/* + * Read a 16-bit big-endian value from two consecutive EC registers. + * + * The EC may update the register pair between reading the high and low + * bytes, which could produce a corrupted value if the high byte rolls + * over (e.g., 0x0100 -> 0x00FF read as 0x01FF). Guard against this by + * re-reading the high byte after reading the low byte. If the high byte + * changed, re-read the low byte to get a consistent pair. + * See also lm90_read16() which uses the same approach. + */ +static int ec_read_reg16(u8 reg_hi, u8 reg_lo, u16 *val) +{ + int ret; + u8 oldh, newh, lo; + + ret = ec_read_reg(reg_hi, &oldh); + if (ret) + return ret; + + ret = ec_read_reg(reg_lo, &lo); + if (ret) + return ret; + + ret = ec_read_reg(reg_hi, &newh); + if (ret) + return ret; + + if (oldh != newh) { + ret = ec_read_reg(reg_lo, &lo); + if (ret) + return ret; + } + + *val = ((u16)newh << 8) | lo; + return 0; +} + +static int +lps_ec_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, + const char **str) +{ + switch (type) { + case hwmon_fan: + *str = "CPU Fan"; + return 0; + case hwmon_temp: + *str = channel == 0 ? "Board Temp" : "CPU Temp"; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static umode_t +lps_ec_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_fan: + if (attr == hwmon_fan_input || attr == hwmon_fan_label) + return 0444; + break; + case hwmon_temp: + if (attr == hwmon_temp_input || attr == hwmon_temp_label) + return 0444; + break; + default: + break; + } + return 0; +} + +static int +lps_ec_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + u16 rpm; + u8 v; + int ret; + + switch (type) { + case hwmon_fan: + if (attr != hwmon_fan_input) + return -EOPNOTSUPP; + ret = ec_read_reg16(EC_REG_FAN_RPM_HI, + EC_REG_FAN_RPM_LO, &rpm); + if (ret) + return ret; + *val = rpm; + return 0; + + case hwmon_temp: + if (attr != hwmon_temp_input) + return -EOPNOTSUPP; + ret = ec_read_reg(channel == 0 ? EC_REG_TEMP_BOARD + : EC_REG_TEMP_CPU, + &v); + if (ret) + return ret; + /* EC reports unsigned 8-bit temperature in degrees Celsius */ + *val = (unsigned long)v * 1000; + return 0; + + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_channel_info * const lps_ec_info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + NULL +}; + +static const struct hwmon_ops lps_ec_ops = { + .is_visible = lps_ec_is_visible, + .read = lps_ec_read, + .read_string = lps_ec_read_string, +}; + +static const struct hwmon_chip_info lps_ec_chip_info = { + .ops = &lps_ec_ops, + .info = lps_ec_info, +}; + +static int lps_ec_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *hwmon; + u8 test; + int ret; + + if (!devm_request_region(dev, EC_DATA_PORT, 1, DRIVER_NAME)) + return dev_err_probe(dev, -EBUSY, + "Failed to request EC data port 0x%x\n", + EC_DATA_PORT); + + if (!devm_request_region(dev, EC_CMD_PORT, 1, DRIVER_NAME)) + return dev_err_probe(dev, -EBUSY, + "Failed to request EC cmd port 0x%x\n", + EC_CMD_PORT); + + /* Sanity check: verify EC is responsive */ + ret = ec_read_reg(EC_REG_FAN_DUTY, &test); + if (ret) + return dev_err_probe(dev, ret, + "EC not responding on ports 0x%x/0x%x\n", + EC_DATA_PORT, EC_CMD_PORT); + + hwmon = devm_hwmon_device_register_with_info(dev, DRIVER_NAME, NULL, + &lps_ec_chip_info, NULL); + if (IS_ERR(hwmon)) + return dev_err_probe(dev, PTR_ERR(hwmon), + "Failed to register hwmon device\n"); + + dev_info(dev, "EC hwmon registered (fan duty: %u%%)\n", test); + return 0; +} + +/* DMI table with strict BIOS version match (override with force=1) */ +static const struct dmi_system_id lps_ec_dmi_table[] = { + { + .ident = "LattePanda Sigma", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LattePanda"), + DMI_MATCH(DMI_PRODUCT_NAME, "LattePanda Sigma"), + DMI_MATCH(DMI_BIOS_VERSION, "5.27"), + }, + }, + { } /* terminator */ +}; +MODULE_DEVICE_TABLE(dmi, lps_ec_dmi_table); + +/* Loose table (vendor + product only) for use with force=1 */ +static const struct dmi_system_id lps_ec_dmi_table_force[] = { + { + .ident = "LattePanda Sigma", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LattePanda"), + DMI_MATCH(DMI_PRODUCT_NAME, "LattePanda Sigma"), + }, + }, + { } /* terminator */ +}; + +static struct platform_driver lps_ec_driver = { + .probe = lps_ec_probe, + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int __init lps_ec_init(void) +{ + int ret; + + if (!dmi_check_system(lps_ec_dmi_table)) { + if (!force || !dmi_check_system(lps_ec_dmi_table_force)) + return -ENODEV; + pr_warn("%s: BIOS version not verified, loading due to force=1\n", + DRIVER_NAME); + } + + ret = platform_driver_register(&lps_ec_driver); + if (ret) + return ret; + + lps_ec_pdev = platform_device_register_simple(DRIVER_NAME, -1, + NULL, 0); + if (IS_ERR(lps_ec_pdev)) { + platform_driver_unregister(&lps_ec_driver); + return PTR_ERR(lps_ec_pdev); + } + + return 0; +} + +static void __exit lps_ec_exit(void) +{ + platform_device_unregister(lps_ec_pdev); + platform_driver_unregister(&lps_ec_driver); +} + +module_init(lps_ec_init); +module_exit(lps_ec_exit); + +MODULE_AUTHOR("Mariano Abad <weimaraner@gmail.com>"); +MODULE_DESCRIPTION("Hardware monitoring driver for LattePanda Sigma EC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index eda93a8c23c9..f1a1e5b888f6 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -108,6 +108,7 @@ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c, #define PCT2075_REG_IDLE 0x04 struct lm75_data { + const char *label; struct regmap *regmap; u16 orig_conf; u8 resolution; /* In bits, 9 to 16 */ @@ -363,6 +364,16 @@ static irqreturn_t lm75_alarm_handler(int irq, void *private) return IRQ_HANDLED; } +static int lm75_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct lm75_data *data = dev_get_drvdata(dev); + + *str = data->label; + + return 0; +} + static int lm75_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { @@ -534,6 +545,9 @@ static umode_t lm75_is_visible(const void *data, enum hwmon_sensor_types type, switch (attr) { case hwmon_temp_input: return 0444; + case hwmon_temp_label: + /* Hide label node if label is not provided */ + return config_data->label ? 0444 : 0; case hwmon_temp_max: case hwmon_temp_max_hyst: return 0644; @@ -553,13 +567,14 @@ static const struct hwmon_channel_info * const lm75_info[] = { HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), HWMON_CHANNEL_INFO(temp, - HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MAX | HWMON_T_MAX_HYST | HWMON_T_ALARM), NULL }; static const struct hwmon_ops lm75_hwmon_ops = { .is_visible = lm75_is_visible, + .read_string = lm75_read_string, .read = lm75_read, .write = lm75_write, }; @@ -721,6 +736,9 @@ static int lm75_generic_probe(struct device *dev, const char *name, /* needed by custom regmap callbacks */ dev_set_drvdata(dev, data); + /* Save the connected input label if available */ + device_property_read_string(dev, "label", &data->label); + data->kind = kind; data->regmap = regmap; diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c index db6534e67991..b9084424160d 100644 --- a/drivers/hwmon/ltc4282.c +++ b/drivers/hwmon/ltc4282.c @@ -1328,15 +1328,16 @@ static int ltc4282_setup(struct ltc4282_state *st, struct device *dev) if (ret) return ret; + /* default to 1 milli-ohm so we can probe without FW properties */ + st->rsense = 1 * (NANO / MILLI); ret = device_property_read_u32(dev, "adi,rsense-nano-ohms", &st->rsense); - if (ret) - return dev_err_probe(dev, ret, - "Failed to read adi,rsense-nano-ohms\n"); - if (st->rsense < CENTI) - return dev_err_probe(dev, -EINVAL, - "adi,rsense-nano-ohms too small (< %lu)\n", - CENTI); + if (!ret) { + if (st->rsense < CENTI) + return dev_err_probe(dev, -EINVAL, + "adi,rsense-nano-ohms too small (< %lu)\n", + CENTI); + } /* * The resolution for rsense is tenths of micro (eg: 62.5 uOhm) which diff --git a/drivers/hwmon/max31722.c b/drivers/hwmon/max31722.c index 9a31ef388396..6c5c86c75c36 100644 --- a/drivers/hwmon/max31722.c +++ b/drivers/hwmon/max31722.c @@ -11,6 +11,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/spi/spi.h> +#include <linux/sysfs.h> #define MAX31722_REG_CFG 0x00 #define MAX31722_REG_TEMP_LSB 0x01 @@ -56,7 +57,7 @@ static ssize_t max31722_temp_show(struct device *dev, if (ret < 0) return ret; /* Keep 12 bits and multiply by the scale of 62.5 millidegrees/bit. */ - return sprintf(buf, "%d\n", (s16)le16_to_cpu(ret) * 125 / 32); + return sysfs_emit(buf, "%d\n", (s16)le16_to_cpu(ret) * 125 / 32); } static SENSOR_DEVICE_ATTR_RO(temp1_input, max31722_temp, 0); diff --git a/drivers/hwmon/max6650.c b/drivers/hwmon/max6650.c index 9649c6611d5f..56b8157885bb 100644 --- a/drivers/hwmon/max6650.c +++ b/drivers/hwmon/max6650.c @@ -27,6 +27,7 @@ #include <linux/hwmon-sysfs.h> #include <linux/err.h> #include <linux/of.h> +#include <linux/sysfs.h> #include <linux/thermal.h> /* @@ -312,7 +313,7 @@ static ssize_t alarm_show(struct device *dev, mutex_unlock(&data->update_lock); } - return sprintf(buf, "%d\n", alarm); + return sysfs_emit(buf, "%d\n", alarm); } static SENSOR_DEVICE_ATTR_RO(gpio1_alarm, alarm, MAX6650_ALRM_GPIO1); diff --git a/drivers/hwmon/mcp9982.c b/drivers/hwmon/mcp9982.c new file mode 100644 index 000000000000..26c69e3388ab --- /dev/null +++ b/drivers/hwmon/mcp9982.c @@ -0,0 +1,998 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HWMON driver for MCP998X/33 and MCP998XD/33D Multichannel Automotive + * Temperature Monitor Family + * + * Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries + * + * Author: Victor Duicu <victor.duicu@microchip.com> + * + * Datasheet can be found here: + * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/bits.h> +#include <linux/byteorder/generic.h> +#include <linux/delay.h> +#include <linux/device/devres.h> +#include <linux/device.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/i2c.h> +#include <linux/math.h> +#include <linux/minmax.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/time64.h> +#include <linux/util_macros.h> + +/* MCP9982 Registers */ +#define MCP9982_HIGH_BYTE_ADDR(index) (2 * (index)) +#define MCP9982_ONE_SHOT_ADDR 0x0A +#define MCP9982_INTERNAL_HIGH_LIMIT_ADDR 0x0B +#define MCP9982_INTERNAL_LOW_LIMIT_ADDR 0x0C +#define MCP9982_EXT_HIGH_LIMIT_ADDR(index) (4 * ((index) - 1) + 0x0D) +#define MCP9982_EXT_LOW_LIMIT_ADDR(index) (4 * ((index) - 1) + 0x0F) +#define MCP9982_THERM_LIMIT_ADDR(index) ((index) + 0x1D) +#define MCP9982_CFG_ADDR 0x22 +#define MCP9982_CONV_ADDR 0x24 +#define MCP9982_HYS_ADDR 0x25 +#define MCP9982_CONSEC_ALRT_ADDR 0x26 +#define MCP9982_ALRT_CFG_ADDR 0x27 +#define MCP9982_RUNNING_AVG_ADDR 0x28 +#define MCP9982_HOTTEST_CFG_ADDR 0x29 +#define MCP9982_STATUS_ADDR 0x2A +#define MCP9982_EXT_FAULT_STATUS_ADDR 0x2B +#define MCP9982_HIGH_LIMIT_STATUS_ADDR 0x2C +#define MCP9982_LOW_LIMIT_STATUS_ADDR 0x2D +#define MCP9982_THERM_LIMIT_STATUS_ADDR 0x2E +#define MCP9982_HOTTEST_HIGH_BYTE_ADDR 0x2F +#define MCP9982_HOTTEST_LOW_BYTE_ADDR 0x30 +#define MCP9982_HOTTEST_STATUS_ADDR 0x31 +#define MCP9982_THERM_SHTDWN_CFG_ADDR 0x32 +#define MCP9982_HRDW_THERM_SHTDWN_LIMIT_ADDR 0x33 +#define MCP9982_EXT_BETA_CFG_ADDR(index) ((index) + 0x33) +#define MCP9982_EXT_IDEAL_ADDR(index) ((index) + 0x35) + +/* MCP9982 Bits */ +#define MCP9982_CFG_MSKAL BIT(7) +#define MCP9982_CFG_RS BIT(6) +#define MCP9982_CFG_ATTHM BIT(5) +#define MCP9982_CFG_RECD12 BIT(4) +#define MCP9982_CFG_RECD34 BIT(3) +#define MCP9982_CFG_RANGE BIT(2) +#define MCP9982_CFG_DA_ENA BIT(1) +#define MCP9982_CFG_APDD BIT(0) + +#define MCP9982_STATUS_BUSY BIT(5) + +/* Constants and default values */ +#define MCP9982_MAX_NUM_CHANNELS 5 +#define MCP9982_BETA_AUTODETECT 16 +#define MCP9982_IDEALITY_DEFAULT 18 +#define MCP9982_OFFSET 64 +#define MCP9982_DEFAULT_CONSEC_ALRT_VAL 112 +#define MCP9982_DEFAULT_HYS_VAL 10 +#define MCP9982_DEFAULT_CONV_VAL 6 +#define MCP9982_WAKE_UP_TIME_US 125000 +#define MCP9982_WAKE_UP_TIME_MAX_US 130000 +#define MCP9982_HIGH_LIMIT_DEFAULT 85000 +#define MCP9982_LOW_LIMIT_DEFAULT 0 + +static const struct hwmon_channel_info * const mcp9985_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN | + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM | + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | + HWMON_T_CRIT_HYST, + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN | + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM | + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | + HWMON_T_CRIT_HYST, + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN | + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM | + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | + HWMON_T_CRIT_HYST, + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN | + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM | + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | + HWMON_T_CRIT_HYST, + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN | + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM | + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM | + HWMON_T_CRIT_HYST), + HWMON_CHANNEL_INFO(chip, + HWMON_C_UPDATE_INTERVAL), + NULL +}; + +/** + * struct mcp9982_features - features of a mcp9982 instance + * @name: chip's name + * @phys_channels: number of physical channels supported by the chip + * @hw_thermal_shutdown: presence of hardware thermal shutdown circuitry + * @allow_apdd: whether the chip supports enabling APDD + * @has_recd34: whether the chip has the channels that are affected by recd34 + */ +struct mcp9982_features { + const char *name; + u8 phys_channels; + bool hw_thermal_shutdown; + bool allow_apdd; + bool has_recd34; +}; + +static const struct mcp9982_features mcp9933_chip_config = { + .name = "mcp9933", + .phys_channels = 3, + .hw_thermal_shutdown = false, + .allow_apdd = true, + .has_recd34 = false, +}; + +static const struct mcp9982_features mcp9933d_chip_config = { + .name = "mcp9933d", + .phys_channels = 3, + .hw_thermal_shutdown = true, + .allow_apdd = true, + .has_recd34 = false, +}; + +static const struct mcp9982_features mcp9982_chip_config = { + .name = "mcp9982", + .phys_channels = 2, + .hw_thermal_shutdown = false, + .allow_apdd = false, + .has_recd34 = false, +}; + +static const struct mcp9982_features mcp9982d_chip_config = { + .name = "mcp9982d", + .phys_channels = 2, + .hw_thermal_shutdown = true, + .allow_apdd = false, + .has_recd34 = false, +}; + +static const struct mcp9982_features mcp9983_chip_config = { + .name = "mcp9983", + .phys_channels = 3, + .hw_thermal_shutdown = false, + .allow_apdd = false, + .has_recd34 = true, +}; + +static const struct mcp9982_features mcp9983d_chip_config = { + .name = "mcp9983d", + .phys_channels = 3, + .hw_thermal_shutdown = true, + .allow_apdd = false, + .has_recd34 = true, +}; + +static const struct mcp9982_features mcp9984_chip_config = { + .name = "mcp9984", + .phys_channels = 4, + .hw_thermal_shutdown = false, + .allow_apdd = true, + .has_recd34 = true, +}; + +static const struct mcp9982_features mcp9984d_chip_config = { + .name = "mcp9984d", + .phys_channels = 4, + .hw_thermal_shutdown = true, + .allow_apdd = true, + .has_recd34 = true, +}; + +static const struct mcp9982_features mcp9985_chip_config = { + .name = "mcp9985", + .phys_channels = 5, + .hw_thermal_shutdown = false, + .allow_apdd = true, + .has_recd34 = true, +}; + +static const struct mcp9982_features mcp9985d_chip_config = { + .name = "mcp9985d", + .phys_channels = 5, + .hw_thermal_shutdown = true, + .allow_apdd = true, + .has_recd34 = true, +}; + +static const unsigned int mcp9982_update_interval[11] = { + 16000, 8000, 4000, 2000, 1000, 500, 250, 125, 64, 32, 16 +}; + +/* MCP9982 regmap configuration */ +static const struct regmap_range mcp9982_regmap_wr_ranges[] = { + regmap_reg_range(MCP9982_ONE_SHOT_ADDR, MCP9982_CFG_ADDR), + regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_HOTTEST_CFG_ADDR), + regmap_reg_range(MCP9982_THERM_SHTDWN_CFG_ADDR, MCP9982_THERM_SHTDWN_CFG_ADDR), + regmap_reg_range(MCP9982_EXT_BETA_CFG_ADDR(1), MCP9982_EXT_IDEAL_ADDR(4)), +}; + +static const struct regmap_access_table mcp9982_regmap_wr_table = { + .yes_ranges = mcp9982_regmap_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_wr_ranges), +}; + +static const struct regmap_range mcp9982_regmap_rd_ranges[] = { + regmap_reg_range(MCP9982_HIGH_BYTE_ADDR(0), MCP9982_CFG_ADDR), + regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_EXT_IDEAL_ADDR(4)), +}; + +static const struct regmap_access_table mcp9982_regmap_rd_table = { + .yes_ranges = mcp9982_regmap_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_rd_ranges), +}; + +static bool mcp9982_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MCP9982_ONE_SHOT_ADDR: + case MCP9982_INTERNAL_HIGH_LIMIT_ADDR: + case MCP9982_INTERNAL_LOW_LIMIT_ADDR: + case MCP9982_EXT_LOW_LIMIT_ADDR(1): + case MCP9982_EXT_LOW_LIMIT_ADDR(1) + 1: + case MCP9982_EXT_LOW_LIMIT_ADDR(2): + case MCP9982_EXT_LOW_LIMIT_ADDR(2) + 1: + case MCP9982_EXT_LOW_LIMIT_ADDR(3): + case MCP9982_EXT_LOW_LIMIT_ADDR(3) + 1: + case MCP9982_EXT_LOW_LIMIT_ADDR(4): + case MCP9982_EXT_LOW_LIMIT_ADDR(4) + 1: + case MCP9982_EXT_HIGH_LIMIT_ADDR(1): + case MCP9982_EXT_HIGH_LIMIT_ADDR(1) + 1: + case MCP9982_EXT_HIGH_LIMIT_ADDR(2): + case MCP9982_EXT_HIGH_LIMIT_ADDR(2) + 1: + case MCP9982_EXT_HIGH_LIMIT_ADDR(3): + case MCP9982_EXT_HIGH_LIMIT_ADDR(3) + 1: + case MCP9982_EXT_HIGH_LIMIT_ADDR(4): + case MCP9982_EXT_HIGH_LIMIT_ADDR(4) + 1: + case MCP9982_THERM_LIMIT_ADDR(0): + case MCP9982_THERM_LIMIT_ADDR(1): + case MCP9982_THERM_LIMIT_ADDR(2): + case MCP9982_THERM_LIMIT_ADDR(3): + case MCP9982_THERM_LIMIT_ADDR(4): + case MCP9982_CFG_ADDR: + case MCP9982_CONV_ADDR: + case MCP9982_HYS_ADDR: + case MCP9982_CONSEC_ALRT_ADDR: + case MCP9982_ALRT_CFG_ADDR: + case MCP9982_RUNNING_AVG_ADDR: + case MCP9982_HOTTEST_CFG_ADDR: + case MCP9982_THERM_SHTDWN_CFG_ADDR: + return false; + default: + return true; + } +} + +static const struct regmap_config mcp9982_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &mcp9982_regmap_rd_table, + .wr_table = &mcp9982_regmap_wr_table, + .volatile_reg = mcp9982_is_volatile_reg, + .max_register = MCP9982_EXT_IDEAL_ADDR(4), + .cache_type = REGCACHE_MAPLE, +}; + +/** + * struct mcp9982_priv - information about chip parameters + * @regmap: device register map + * @chip: pointer to structure holding chip features + * @labels: labels of the channels + * @interval_idx: index representing the current update interval + * @enabled_channel_mask: mask containing which channels should be enabled + * @num_channels: number of active physical channels + * @recd34_enable: state of Resistance Error Correction(REC) on channels 3 and 4 + * @recd12_enable: state of Resistance Error Correction(REC) on channels 1 and 2 + * @apdd_enable: state of anti-parallel diode mode + * @run_state: chip is in Run state, otherwise is in Standby state + */ +struct mcp9982_priv { + struct regmap *regmap; + const struct mcp9982_features *chip; + const char *labels[MCP9982_MAX_NUM_CHANNELS]; + unsigned int interval_idx; + unsigned long enabled_channel_mask; + u8 num_channels; + bool recd34_enable; + bool recd12_enable; + bool apdd_enable; + bool run_state; +}; + +static int mcp9982_read_limit(struct mcp9982_priv *priv, u8 address, long *val) +{ + unsigned int limit, reg_high, reg_low; + int ret; + + switch (address) { + case MCP9982_INTERNAL_HIGH_LIMIT_ADDR: + case MCP9982_INTERNAL_LOW_LIMIT_ADDR: + case MCP9982_THERM_LIMIT_ADDR(0): + case MCP9982_THERM_LIMIT_ADDR(1): + case MCP9982_THERM_LIMIT_ADDR(2): + case MCP9982_THERM_LIMIT_ADDR(3): + case MCP9982_THERM_LIMIT_ADDR(4): + ret = regmap_read(priv->regmap, address, &limit); + if (ret) + return ret; + + *val = ((int)limit - MCP9982_OFFSET) * 1000; + + return 0; + case MCP9982_EXT_HIGH_LIMIT_ADDR(1): + case MCP9982_EXT_HIGH_LIMIT_ADDR(2): + case MCP9982_EXT_HIGH_LIMIT_ADDR(3): + case MCP9982_EXT_HIGH_LIMIT_ADDR(4): + case MCP9982_EXT_LOW_LIMIT_ADDR(1): + case MCP9982_EXT_LOW_LIMIT_ADDR(2): + case MCP9982_EXT_LOW_LIMIT_ADDR(3): + case MCP9982_EXT_LOW_LIMIT_ADDR(4): + /* + * In order to keep consistency with reading temperature memory region we will use + * single byte I2C read. + */ + ret = regmap_read(priv->regmap, address, ®_high); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, address + 1, ®_low); + if (ret) + return ret; + + *val = ((reg_high << 8) + reg_low) >> 5; + *val = (*val - (MCP9982_OFFSET << 3)) * 125; + + return 0; + default: + return -EINVAL; + } +} + +static int mcp9982_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long *val) +{ + struct mcp9982_priv *priv = dev_get_drvdata(dev); + unsigned int reg_high, reg_low, hyst, reg_status; + int ret; + u8 addr; + + /* + * In Standby State the conversion cycle must be initated manually in + * order to read fresh temperature values and the status of the alarms. + */ + if (!priv->run_state) { + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_max_alarm: + case hwmon_temp_min_alarm: + case hwmon_temp_crit_alarm: + ret = regmap_write(priv->regmap, MCP9982_ONE_SHOT_ADDR, 1); + if (ret) + return ret; + /* + * When the device is in Standby mode, 125 ms need + * to pass from writing in One Shot register before + * the conversion cycle begins. + */ + usleep_range(MCP9982_WAKE_UP_TIME_US, MCP9982_WAKE_UP_TIME_MAX_US); + ret = regmap_read_poll_timeout + (priv->regmap, MCP9982_STATUS_ADDR, + reg_status, !(reg_status & MCP9982_STATUS_BUSY), + MCP9982_WAKE_UP_TIME_US, + MCP9982_WAKE_UP_TIME_US * 10); + break; + } + break; + default: + break; + } + } + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + /* + * The only areas of memory that support SMBus block read are 80h->89h + * (temperature memory block) and 90h->97h(status memory block). + * In this context the read operation uses SMBus protocol and the first + * value returned will be the number of addresses that can be read. + * Temperature memory block is 10 bytes long and status memory block is 8 + * bytes long. + * + * Depending on the read instruction used, the chip behaves differently: + * - regmap_bulk_read() when applied to the temperature memory block + * (80h->89h), the chip replies with SMBus block read, including count, + * additionally to the high and the low bytes. This function cannot be + * applied on the memory region 00h->09h(memory area which does not support + * block reads, returns wrong data) unless use_single_read is set in + * regmap_config. + * + * - regmap_multi_reg_read() when applied to the 00h->09h area uses I2C + * and returns only the high and low temperature bytes. When applied to + * the temperature memory block (80h->89h) returns the count till the end of + * the temperature memory block(aka SMBus count). + * + * - i2c_smbus_read_block_data() is not supported by all drivers. + * + * In order to keep consistency with reading limit memory region we will + * use single byte I2C read. + * + * Low register is latched when high temperature register is read. + */ + ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel), ®_high); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel) + 1, + ®_low); + if (ret) + return ret; + + *val = ((reg_high << 8) + reg_low) >> 5; + *val = (*val - (MCP9982_OFFSET << 3)) * 125; + + return 0; + case hwmon_temp_max: + if (channel) + addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel); + else + addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR; + + return mcp9982_read_limit(priv, addr, val); + case hwmon_temp_max_alarm: + *val = regmap_test_bits(priv->regmap, MCP9982_HIGH_LIMIT_STATUS_ADDR, + BIT(channel)); + if (*val < 0) + return *val; + + return 0; + case hwmon_temp_max_hyst: + if (channel) + addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel); + else + addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR; + ret = mcp9982_read_limit(priv, addr, val); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst); + if (ret) + return ret; + + *val -= hyst * 1000; + + return 0; + case hwmon_temp_min: + if (channel) + addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel); + else + addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR; + + return mcp9982_read_limit(priv, addr, val); + case hwmon_temp_min_alarm: + *val = regmap_test_bits(priv->regmap, MCP9982_LOW_LIMIT_STATUS_ADDR, + BIT(channel)); + if (*val < 0) + return *val; + + return 0; + case hwmon_temp_crit: + return mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val); + case hwmon_temp_crit_alarm: + *val = regmap_test_bits(priv->regmap, MCP9982_THERM_LIMIT_STATUS_ADDR, + BIT(channel)); + if (*val < 0) + return *val; + + return 0; + case hwmon_temp_crit_hyst: + ret = mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst); + if (ret) + return ret; + + *val -= hyst * 1000; + + return 0; + default: + return -EINVAL; + } + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + *val = mcp9982_update_interval[priv->interval_idx]; + return 0; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mcp9982_read_label(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct mcp9982_priv *priv = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + *str = priv->labels[channel]; + return 0; + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int mcp9982_write_limit(struct mcp9982_priv *priv, u8 address, long val) +{ + int ret; + unsigned int regh, regl; + + switch (address) { + case MCP9982_INTERNAL_HIGH_LIMIT_ADDR: + case MCP9982_INTERNAL_LOW_LIMIT_ADDR: + case MCP9982_THERM_LIMIT_ADDR(0): + case MCP9982_THERM_LIMIT_ADDR(1): + case MCP9982_THERM_LIMIT_ADDR(2): + case MCP9982_THERM_LIMIT_ADDR(3): + case MCP9982_THERM_LIMIT_ADDR(4): + regh = DIV_ROUND_CLOSEST(val, 1000); + regh = clamp_val(regh, 0, 255); + + return regmap_write(priv->regmap, address, regh); + case MCP9982_EXT_HIGH_LIMIT_ADDR(1): + case MCP9982_EXT_HIGH_LIMIT_ADDR(2): + case MCP9982_EXT_HIGH_LIMIT_ADDR(3): + case MCP9982_EXT_HIGH_LIMIT_ADDR(4): + case MCP9982_EXT_LOW_LIMIT_ADDR(1): + case MCP9982_EXT_LOW_LIMIT_ADDR(2): + case MCP9982_EXT_LOW_LIMIT_ADDR(3): + case MCP9982_EXT_LOW_LIMIT_ADDR(4): + val = DIV_ROUND_CLOSEST(val, 125); + regh = (val >> 3) & 0xff; + regl = (val & 0x07) << 5; + /* Block writing is not supported by the chip. */ + ret = regmap_write(priv->regmap, address, regh); + if (ret) + return ret; + + return regmap_write(priv->regmap, address + 1, regl); + default: + return -EINVAL; + } +} + +static int mcp9982_write_hyst(struct mcp9982_priv *priv, int channel, long val) +{ + int hyst, ret; + int limit; + + val = DIV_ROUND_CLOSEST(val, 1000); + val = clamp_val(val, 0, 255); + + /* Therm register is 8 bits and so it keeps only the integer part of the temperature. */ + ret = regmap_read(priv->regmap, MCP9982_THERM_LIMIT_ADDR(channel), &limit); + if (ret) + return ret; + + hyst = clamp_val(limit - val, 0, 255); + + return regmap_write(priv->regmap, MCP9982_HYS_ADDR, hyst); +} + +static int mcp9982_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long val) +{ + struct mcp9982_priv *priv = dev_get_drvdata(dev); + unsigned int idx; + u8 addr; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + + /* + * For MCP998XD and MCP9933D update interval + * can't be longer than 1 second. + */ + if (priv->chip->hw_thermal_shutdown) + val = clamp_val(val, 0, 1000); + + idx = find_closest_descending(val, mcp9982_update_interval, + ARRAY_SIZE(mcp9982_update_interval)); + priv->interval_idx = idx; + + return regmap_write(priv->regmap, MCP9982_CONV_ADDR, idx); + default: + return -EINVAL; + } + case hwmon_temp: + val = clamp_val(val, -64000, 191875); + val = val + (MCP9982_OFFSET * 1000); + switch (attr) { + case hwmon_temp_max: + if (channel) + addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel); + else + addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR; + + return mcp9982_write_limit(priv, addr, val); + case hwmon_temp_min: + if (channel) + addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel); + else + addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR; + + return mcp9982_write_limit(priv, addr, val); + case hwmon_temp_crit: + return mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val); + case hwmon_temp_crit_hyst: + return mcp9982_write_hyst(priv, channel, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static umode_t mcp9982_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct mcp9982_priv *priv = _data; + + if (!test_bit(channel, &priv->enabled_channel_mask)) + return 0; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + if (priv->labels[channel]) + return 0444; + else + return 0; + case hwmon_temp_input: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_max_hyst: + case hwmon_temp_crit_alarm: + return 0444; + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + case hwmon_temp_crit_hyst: + return 0644; + default: + return 0; + } + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + default: + return 0; + } + default: + return 0; + } +} + +static const struct hwmon_ops mcp9982_hwmon_ops = { + .is_visible = mcp9982_is_visible, + .read = mcp9982_read, + .read_string = mcp9982_read_label, + .write = mcp9982_write, +}; + +static int mcp9982_init(struct device *dev, struct mcp9982_priv *priv) +{ + long high_limit, low_limit; + unsigned int i; + int ret; + u8 val; + + /* Chips 82/83 and 82D/83D do not support anti-parallel diode mode. */ + if (!priv->chip->allow_apdd && priv->apdd_enable == 1) + return dev_err_probe(dev, -EINVAL, "Incorrect setting of APDD.\n"); + + /* Chips with "D" work only in Run state. */ + if (priv->chip->hw_thermal_shutdown && !priv->run_state) + return dev_err_probe(dev, -EINVAL, "Incorrect setting of Power State.\n"); + + /* All chips with "D" in the name must have RECD12 enabled. */ + if (priv->chip->hw_thermal_shutdown && !priv->recd12_enable) + return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD12.\n"); + /* Chips 83D/84D/85D must have RECD34 enabled. */ + if (priv->chip->hw_thermal_shutdown) + if ((priv->chip->has_recd34 && !priv->recd34_enable)) + return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD34.\n"); + + /* + * Set default values in registers. + * APDD, RECD12 and RECD34 are active on 0. + */ + val = FIELD_PREP(MCP9982_CFG_MSKAL, 1) | + FIELD_PREP(MCP9982_CFG_RS, !priv->run_state) | + FIELD_PREP(MCP9982_CFG_ATTHM, 1) | + FIELD_PREP(MCP9982_CFG_RECD12, !priv->recd12_enable) | + FIELD_PREP(MCP9982_CFG_RECD34, !priv->recd34_enable) | + FIELD_PREP(MCP9982_CFG_RANGE, 1) | FIELD_PREP(MCP9982_CFG_DA_ENA, 0) | + FIELD_PREP(MCP9982_CFG_APDD, !priv->apdd_enable); + + ret = regmap_write(priv->regmap, MCP9982_CFG_ADDR, val); + if (ret) + return ret; + + /* + * Read initial value from register. + * The convert register utilises only 4 out of 8 bits. + * Numerical values 0->10 set their respective update intervals, + * while numerical values 11->15 default to 1 second. + */ + ret = regmap_read(priv->regmap, MCP9982_CONV_ADDR, &priv->interval_idx); + if (ret) + return ret; + if (priv->interval_idx >= 11) + priv->interval_idx = 4; + + ret = regmap_write(priv->regmap, MCP9982_HYS_ADDR, MCP9982_DEFAULT_HYS_VAL); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, MCP9982_CONSEC_ALRT_ADDR, MCP9982_DEFAULT_CONSEC_ALRT_VAL); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, MCP9982_ALRT_CFG_ADDR, 0); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, MCP9982_RUNNING_AVG_ADDR, 0); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, MCP9982_HOTTEST_CFG_ADDR, 0); + if (ret) + return ret; + + /* + * Only external channels 1 and 2 support beta compensation. + * Set beta auto-detection. + */ + for (i = 1; i < 3; i++) + if (test_bit(i, &priv->enabled_channel_mask)) { + ret = regmap_write(priv->regmap, MCP9982_EXT_BETA_CFG_ADDR(i), + MCP9982_BETA_AUTODETECT); + if (ret) + return ret; + } + + high_limit = MCP9982_HIGH_LIMIT_DEFAULT + (MCP9982_OFFSET * 1000); + low_limit = MCP9982_LOW_LIMIT_DEFAULT + (MCP9982_OFFSET * 1000); + + /* Set default values for internal channel limits. */ + if (test_bit(0, &priv->enabled_channel_mask)) { + ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_HIGH_LIMIT_ADDR, high_limit); + if (ret) + return ret; + + ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_LOW_LIMIT_ADDR, low_limit); + if (ret) + return ret; + + ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(0), high_limit); + if (ret) + return ret; + } + + /* Set ideality factor and limits to default for external channels. */ + for (i = 1; i < MCP9982_MAX_NUM_CHANNELS; i++) + if (test_bit(i, &priv->enabled_channel_mask)) { + ret = regmap_write(priv->regmap, MCP9982_EXT_IDEAL_ADDR(i), + MCP9982_IDEALITY_DEFAULT); + if (ret) + return ret; + + ret = mcp9982_write_limit(priv, MCP9982_EXT_HIGH_LIMIT_ADDR(i), high_limit); + if (ret) + return ret; + + ret = mcp9982_write_limit(priv, MCP9982_EXT_LOW_LIMIT_ADDR(i), low_limit); + if (ret) + return ret; + + ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(i), high_limit); + if (ret) + return ret; + } + + return 0; +} + +static int mcp9982_parse_fw_config(struct device *dev, int device_nr_channels) +{ + struct mcp9982_priv *priv = dev_get_drvdata(dev); + unsigned int reg_nr; + int ret; + + /* Initialise internal channel( which is always present ). */ + priv->labels[0] = "internal diode"; + priv->enabled_channel_mask = 1; + + /* Default values to work on systems without devicetree or firmware nodes. */ + if (!dev_fwnode(dev)) { + priv->num_channels = device_nr_channels; + priv->enabled_channel_mask = BIT(priv->num_channels) - 1; + priv->apdd_enable = false; + priv->recd12_enable = true; + priv->recd34_enable = true; + priv->run_state = true; + return 0; + } + + priv->apdd_enable = + device_property_read_bool(dev, "microchip,enable-anti-parallel"); + + priv->recd12_enable = + device_property_read_bool(dev, "microchip,parasitic-res-on-channel1-2"); + + priv->recd34_enable = + device_property_read_bool(dev, "microchip,parasitic-res-on-channel3-4"); + + priv->run_state = + device_property_read_bool(dev, "microchip,power-state"); + + priv->num_channels = device_get_child_node_count(dev) + 1; + + if (priv->num_channels > device_nr_channels) + return dev_err_probe(dev, -EINVAL, + "More channels than the chip supports.\n"); + + /* Read information about the external channels. */ + device_for_each_named_child_node_scoped(dev, child, "channel") { + reg_nr = 0; + ret = fwnode_property_read_u32(child, "reg", ®_nr); + if (ret || !reg_nr || reg_nr >= device_nr_channels) + return dev_err_probe(dev, -EINVAL, + "Channel reg is incorrectly set.\n"); + + fwnode_property_read_string(child, "label", &priv->labels[reg_nr]); + set_bit(reg_nr, &priv->enabled_channel_mask); + } + + return 0; +} + +static const struct hwmon_chip_info mcp998x_chip_info = { + .ops = &mcp9982_hwmon_ops, + .info = mcp9985_info, +}; + +static int mcp9982_probe(struct i2c_client *client) +{ + const struct mcp9982_features *chip; + struct device *dev = &client->dev; + struct mcp9982_priv *priv; + struct device *hwmon_dev; + int ret; + + priv = devm_kzalloc(dev, sizeof(struct mcp9982_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = devm_regmap_init_i2c(client, &mcp9982_regmap_config); + + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "Cannot initialize register map.\n"); + + dev_set_drvdata(dev, priv); + + chip = i2c_get_match_data(client); + if (!chip) + return -EINVAL; + priv->chip = chip; + + ret = mcp9982_parse_fw_config(dev, chip->phys_channels); + if (ret) + return ret; + + ret = mcp9982_init(dev, priv); + if (ret) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, chip->name, priv, + &mcp998x_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id mcp9982_id[] = { + { .name = "mcp9933", .driver_data = (kernel_ulong_t)&mcp9933_chip_config }, + { .name = "mcp9933d", .driver_data = (kernel_ulong_t)&mcp9933d_chip_config }, + { .name = "mcp9982", .driver_data = (kernel_ulong_t)&mcp9982_chip_config }, + { .name = "mcp9982d", .driver_data = (kernel_ulong_t)&mcp9982d_chip_config }, + { .name = "mcp9983", .driver_data = (kernel_ulong_t)&mcp9983_chip_config }, + { .name = "mcp9983d", .driver_data = (kernel_ulong_t)&mcp9983d_chip_config }, + { .name = "mcp9984", .driver_data = (kernel_ulong_t)&mcp9984_chip_config }, + { .name = "mcp9984d", .driver_data = (kernel_ulong_t)&mcp9984d_chip_config }, + { .name = "mcp9985", .driver_data = (kernel_ulong_t)&mcp9985_chip_config }, + { .name = "mcp9985d", .driver_data = (kernel_ulong_t)&mcp9985d_chip_config }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp9982_id); + +static const struct of_device_id mcp9982_of_match[] = { + { + .compatible = "microchip,mcp9933", + .data = &mcp9933_chip_config, + }, { + .compatible = "microchip,mcp9933d", + .data = &mcp9933d_chip_config, + }, { + .compatible = "microchip,mcp9982", + .data = &mcp9982_chip_config, + }, { + .compatible = "microchip,mcp9982d", + .data = &mcp9982d_chip_config, + }, { + .compatible = "microchip,mcp9983", + .data = &mcp9983_chip_config, + }, { + .compatible = "microchip,mcp9983d", + .data = &mcp9983d_chip_config, + }, { + .compatible = "microchip,mcp9984", + .data = &mcp9984_chip_config, + }, { + .compatible = "microchip,mcp9984d", + .data = &mcp9984d_chip_config, + }, { + .compatible = "microchip,mcp9985", + .data = &mcp9985_chip_config, + }, { + .compatible = "microchip,mcp9985d", + .data = &mcp9985d_chip_config, + }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp9982_of_match); + +static struct i2c_driver mcp9982_driver = { + .driver = { + .name = "mcp9982", + .of_match_table = mcp9982_of_match, + }, + .probe = mcp9982_probe, + .id_table = mcp9982_id, +}; +module_i2c_driver(mcp9982_driver); + +MODULE_AUTHOR("Victor Duicu <victor.duicu@microchip.com>"); +MODULE_DESCRIPTION("MCP998X/33 and MCP998XD/33D Multichannel Automotive Temperature Monitor Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c index 4a8380414038..0581770380cc 100644 --- a/drivers/hwmon/nct6683.c +++ b/drivers/hwmon/nct6683.c @@ -182,6 +182,7 @@ superio_exit(int ioreg) #define NCT6683_CUSTOMER_ID_ASROCK3 0x1631 #define NCT6683_CUSTOMER_ID_ASROCK4 0x163e #define NCT6683_CUSTOMER_ID_ASROCK5 0x1621 +#define NCT6683_CUSTOMER_ID_ASROCK6 0x1633 #define NCT6683_REG_BUILD_YEAR 0x604 #define NCT6683_REG_BUILD_MONTH 0x605 @@ -1245,6 +1246,8 @@ static int nct6683_probe(struct platform_device *pdev) break; case NCT6683_CUSTOMER_ID_ASROCK5: break; + case NCT6683_CUSTOMER_ID_ASROCK6: + break; default: if (!force) return -ENODEV; diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c index 555029dfe713..1975399ac440 100644 --- a/drivers/hwmon/nct6775-platform.c +++ b/drivers/hwmon/nct6775-platform.c @@ -1159,6 +1159,7 @@ static const char * const asus_wmi_boards[] = { "Pro A520M-C", "Pro A520M-C II", "Pro B550M-C", + "Pro WS W480-ACE", "Pro WS X570-ACE", "ProArt B550-CREATOR", "ProArt X570-CREATOR WIFI", @@ -1258,6 +1259,7 @@ static const char * const asus_wmi_boards[] = { "TUF Z390-PRO GAMING", "TUF Z390M-PRO GAMING", "TUF Z390M-PRO GAMING (WI-FI)", + "W480/SYS", "WS Z390 PRO", "Z490-GUNDAM (WI-FI)", }; @@ -1270,6 +1272,7 @@ static const char * const asus_msi_boards[] = { "EX-B760M-V5 D4", "EX-H510M-V3", "EX-H610M-V3 D4", + "G15CE", "G15CF", "PRIME A620M-A", "PRIME B560-PLUS", @@ -1320,6 +1323,8 @@ static const char * const asus_msi_boards[] = { "PRIME X670-P", "PRIME X670-P WIFI", "PRIME X670E-PRO WIFI", + "PRIME X870-P", + "PRIME X870-P WIFI", "PRIME Z590-A", "PRIME Z590-P", "PRIME Z590-P WIFI", @@ -1362,11 +1367,18 @@ static const char * const asus_msi_boards[] = { "ProArt B660-CREATOR D4", "ProArt B760-CREATOR D4", "ProArt X670E-CREATOR WIFI", + "ProArt X870E-CREATOR WIFI", "ProArt Z690-CREATOR WIFI", "ProArt Z790-CREATOR WIFI", "ROG CROSSHAIR X670E EXTREME", "ROG CROSSHAIR X670E GENE", "ROG CROSSHAIR X670E HERO", + "ROG CROSSHAIR X870E APEX", + "ROG CROSSHAIR X870E DARK HERO", + "ROG CROSSHAIR X870E EXTREME", + "ROG CROSSHAIR X870E GLACIAL", + "ROG CROSSHAIR X870E HERO", + "ROG CROSSHAIR X870E HERO BTF", "ROG MAXIMUS XIII APEX", "ROG MAXIMUS XIII EXTREME", "ROG MAXIMUS XIII EXTREME GLACIAL", @@ -1404,6 +1416,11 @@ static const char * const asus_msi_boards[] = { "ROG STRIX X670E-E GAMING WIFI", "ROG STRIX X670E-F GAMING WIFI", "ROG STRIX X670E-I GAMING WIFI", + "ROG STRIX X870-A GAMING WIFI", + "ROG STRIX X870-F GAMING WIFI", + "ROG STRIX X870-I GAMING WIFI", + "ROG STRIX X870E-E GAMING WIFI", + "ROG STRIX X870E-E GAMING WIFI7 R2", "ROG STRIX X870E-H GAMING WIFI7", "ROG STRIX Z590-A GAMING WIFI", "ROG STRIX Z590-A GAMING WIFI II", @@ -1451,6 +1468,9 @@ static const char * const asus_msi_boards[] = { "TUF GAMING H770-PRO WIFI", "TUF GAMING X670E-PLUS", "TUF GAMING X670E-PLUS WIFI", + "TUF GAMING X870-PLUS WIFI", + "TUF GAMING X870-PRO WIFI7 W NEO", + "TUF GAMING X870E-PLUS WIFI7", "TUF GAMING Z590-PLUS", "TUF GAMING Z590-PLUS WIFI", "TUF GAMING Z690-PLUS", @@ -1460,6 +1480,9 @@ static const char * const asus_msi_boards[] = { "TUF GAMING Z790-PLUS D4", "TUF GAMING Z790-PLUS WIFI", "TUF GAMING Z790-PLUS WIFI D4", + "X870 AYW GAMING WIFI W", + "X870 MAX GAMING WIFI7", + "X870 MAX GAMING WIFI7 W", "Z590 WIFI GUNDAM EDITION", }; diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index fc1273abe357..8f4bff375ecb 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -77,6 +77,15 @@ config SENSORS_ADP1050_REGULATOR µModule regulators that can provide microprocessor power from 54V power distribution architecture. +config SENSORS_APS_379 + tristate "Sony APS-379 Power Supplies" + help + If you say yes here you get hardware monitoring support for Sony + APS-379 Power Supplies. + + This driver can also be built as a module. If so, the module will + be called aps-379. + config SENSORS_BEL_PFE tristate "Bel PFE Compatible Power Supplies" help @@ -702,6 +711,15 @@ config SENSORS_XDP710 This driver can also be built as a module. If so, the module will be called xdp710. +config SENSORS_XDP720 + tristate "Infineon XDP720 family" + help + If you say yes here you get hardware monitoring support for Infineon + XDP720. + + This driver can also be built as a module. If so, the module will + be called xdp720. + config SENSORS_XDPE152 tristate "Infineon XDPE152 family" help @@ -711,6 +729,15 @@ config SENSORS_XDPE152 This driver can also be built as a module. If so, the module will be called xdpe152c4. +config SENSORS_XDPE1A2G7B + tristate "Infineon XDPE1A2G7B" + help + If you say yes here you get hardware monitoring support for Infineon + XDPE1A2G5B and XDPE1A2G7B. + + This driver can also be built as a module. If so, the module will + be called xdpe1a2g7b. + config SENSORS_XDPE122 tristate "Infineon XDPE122 family" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index d6c86924f887..7129b62bc00f 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SENSORS_ACBEL_FSG032) += acbel-fsg032.o obj-$(CONFIG_SENSORS_ADM1266) += adm1266.o obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o obj-$(CONFIG_SENSORS_ADP1050) += adp1050.o +obj-$(CONFIG_SENSORS_APS_379) += aps-379.o obj-$(CONFIG_SENSORS_BEL_PFE) += bel-pfe.o obj-$(CONFIG_SENSORS_BPA_RS600) += bpa-rs600.o obj-$(CONFIG_SENSORS_DELTA_AHE50DC_FAN) += delta-ahe50dc-fan.o @@ -68,8 +69,10 @@ obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o obj-$(CONFIG_SENSORS_XDP710) += xdp710.o +obj-$(CONFIG_SENSORS_XDP720) += xdp720.o obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o obj-$(CONFIG_SENSORS_XDPE152) += xdpe152c4.o +obj-$(CONFIG_SENSORS_XDPE1A2G7B) += xdpe1a2g7b.o obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o obj-$(CONFIG_SENSORS_PIM4328) += pim4328.o obj-$(CONFIG_SENSORS_CRPS) += crps.o diff --git a/drivers/hwmon/pmbus/aps-379.c b/drivers/hwmon/pmbus/aps-379.c new file mode 100644 index 000000000000..7d46cd647e20 --- /dev/null +++ b/drivers/hwmon/pmbus/aps-379.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Sony APS-379 Power Supplies + * + * Copyright 2026 Allied Telesis Labs + */ + +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pmbus.h> +#include "pmbus.h" + +/* + * The VOUT format used by the chip is linear11, not linear16. Provide a hard + * coded VOUT_MODE that says VOUT is in linear mode with a fixed exponent of + * 2^-4. + */ +#define APS_379_VOUT_MODE ((u8)(-4 & 0x1f)) + +static int aps_379_read_byte_data(struct i2c_client *client, int page, int reg) +{ + switch (reg) { + case PMBUS_VOUT_MODE: + return APS_379_VOUT_MODE; + default: + return -ENODATA; + } +} + +/* + * The APS-379 uses linear11 format instead of linear16. We've reported the exponent + * via the PMBUS_VOUT_MODE so we just return the mantissa here. + */ +static int aps_379_read_vout(struct i2c_client *client) +{ + int ret; + + ret = pmbus_read_word_data(client, 0, 0xff, PMBUS_READ_VOUT); + if (ret < 0) + return ret; + + return clamp_val(sign_extend32(ret & 0x7ff, 10), 0, 0x3ff); +} + +static int aps_379_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + switch (reg) { + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_VOUT_OV_WARN_LIMIT: + case PMBUS_VOUT_UV_FAULT_LIMIT: + case PMBUS_VOUT_OV_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_IOUT_UC_FAULT_LIMIT: + case PMBUS_UT_WARN_LIMIT: + case PMBUS_UT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_PIN_OP_WARN_LIMIT: + case PMBUS_POUT_OP_WARN_LIMIT: + case PMBUS_MFR_IIN_MAX: + case PMBUS_MFR_PIN_MAX: + case PMBUS_MFR_VOUT_MIN: + case PMBUS_MFR_VOUT_MAX: + case PMBUS_MFR_IOUT_MAX: + case PMBUS_MFR_POUT_MAX: + case PMBUS_MFR_MAX_TEMP_1: + /* These commands return data but it is invalid/un-documented */ + return -ENXIO; + case PMBUS_IOUT_OC_FAULT_LIMIT: + /* + * The standard requires this to be a value in Amps but it's + * actually a percentage of the rated output (123A for + * 110-240Vac, 110A for 90-100Vac) which we don't know. Ignore + * it rather than guessing. + */ + return -ENXIO; + case PMBUS_READ_VOUT: + return aps_379_read_vout(client); + default: + return -ENODATA; + } +} + +static struct pmbus_driver_info aps_379_info = { + .pages = 1, + .format[PSC_VOLTAGE_OUT] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_FAN] = linear, + .func[0] = PMBUS_HAVE_VOUT | + PMBUS_HAVE_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | + PMBUS_HAVE_TEMP | + PMBUS_HAVE_FAN12, + .read_byte_data = aps_379_read_byte_data, + .read_word_data = aps_379_read_word_data, +}; + +static const struct i2c_device_id aps_379_id[] = { + { "aps-379", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, aps_379_id); + +static int aps_379_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = { 0 }; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_READ_WORD_DATA + | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) { + dev_err(dev, "Failed to read Manufacturer Model\n"); + return ret; + } + + if (strncasecmp(buf, aps_379_id[0].name, strlen(aps_379_id[0].name)) != 0) { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf); + return -ENODEV; + } + + return pmbus_do_probe(client, &aps_379_info); +} + +static const struct of_device_id __maybe_unused aps_379_of_match[] = { + { .compatible = "sony,aps-379" }, + {}, +}; +MODULE_DEVICE_TABLE(of, aps_379_of_match); + +static struct i2c_driver aps_379_driver = { + .driver = { + .name = "aps-379", + .of_match_table = of_match_ptr(aps_379_of_match), + }, + .probe = aps_379_probe, + .id_table = aps_379_id, +}; + +module_i2c_driver(aps_379_driver); + +MODULE_AUTHOR("Chris Packham"); +MODULE_DESCRIPTION("PMBus driver for Sony APS-379"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); diff --git a/drivers/hwmon/pmbus/bel-pfe.c b/drivers/hwmon/pmbus/bel-pfe.c index ddf9d9a2958c..6499556f735b 100644 --- a/drivers/hwmon/pmbus/bel-pfe.c +++ b/drivers/hwmon/pmbus/bel-pfe.c @@ -88,13 +88,10 @@ static struct pmbus_driver_info pfe_driver_info[] = { }, }; -static const struct i2c_device_id pfe_device_id[]; - static int pfe_pmbus_probe(struct i2c_client *client) { - int model; + int model = (uintptr_t)i2c_get_match_data(client); - model = (int)i2c_match_id(pfe_device_id, client)->driver_data; client->dev.platform_data = &pfe_plat_data; /* diff --git a/drivers/hwmon/pmbus/fsp-3y.c b/drivers/hwmon/pmbus/fsp-3y.c index a4dc09e2ef75..cad4d2330003 100644 --- a/drivers/hwmon/pmbus/fsp-3y.c +++ b/drivers/hwmon/pmbus/fsp-3y.c @@ -222,12 +222,6 @@ static int fsp3y_detect(struct i2c_client *client) return -ENODEV; } -static const struct i2c_device_id fsp3y_id[] = { - {"ym2151e", ym2151e}, - {"yh5151e", yh5151e}, - { } -}; - static int fsp3y_probe(struct i2c_client *client) { struct fsp3y_data *data; @@ -242,7 +236,7 @@ static int fsp3y_probe(struct i2c_client *client) if (data->chip < 0) return data->chip; - id = i2c_match_id(fsp3y_id, client); + id = i2c_client_get_device_id(client); if (data->chip != id->driver_data) dev_warn(&client->dev, "Device mismatch: Configured %s (%d), detected %d\n", id->name, (int)id->driver_data, data->chip); @@ -276,6 +270,11 @@ static int fsp3y_probe(struct i2c_client *client) return pmbus_do_probe(client, &data->info); } +static const struct i2c_device_id fsp3y_id[] = { + {"ym2151e", ym2151e}, + {"yh5151e", yh5151e}, + { } +}; MODULE_DEVICE_TABLE(i2c, fsp3y_id); static struct i2c_driver fsp3y_driver = { diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index d05ef7a968a9..6c7256d997f4 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -58,7 +58,7 @@ enum { CFFPS_DEBUGFS_NUM_ENTRIES }; -enum versions { cffps1, cffps2, cffps_unknown }; +enum versions { cffps_unknown, cffps1, cffps2 }; struct ibm_cffps { enum versions version; @@ -482,19 +482,9 @@ MODULE_DEVICE_TABLE(i2c, ibm_cffps_id); static int ibm_cffps_probe(struct i2c_client *client) { int i, rc; - enum versions vs = cffps_unknown; + enum versions vs = (uintptr_t)i2c_get_match_data(client); struct dentry *debugfs; struct ibm_cffps *psu; - const void *md = of_device_get_match_data(&client->dev); - const struct i2c_device_id *id; - - if (md) { - vs = (uintptr_t)md; - } else { - id = i2c_match_id(ibm_cffps_id, client); - if (id) - vs = (enum versions)id->driver_data; - } if (vs == cffps_unknown) { u16 ccin_revision = 0; @@ -534,7 +524,7 @@ static int ibm_cffps_probe(struct i2c_client *client) } /* Set the client name to include the version number. */ - snprintf(client->name, I2C_NAME_SIZE, "cffps%d", vs + 1); + snprintf(client->name, I2C_NAME_SIZE, "cffps%d", vs); } client->dev.platform_data = &ibm_cffps_pdata; diff --git a/drivers/hwmon/pmbus/ina233.c b/drivers/hwmon/pmbus/ina233.c index 7aebd854763a..652087589c55 100644 --- a/drivers/hwmon/pmbus/ina233.c +++ b/drivers/hwmon/pmbus/ina233.c @@ -85,6 +85,7 @@ static int ina233_read_word_data(struct i2c_client *client, int page, static int ina233_probe(struct i2c_client *client) { struct device *dev = &client->dev; + const char *propname; int ret, m, R; u32 rshunt; u32 max_current; @@ -114,27 +115,28 @@ static int ina233_probe(struct i2c_client *client) /* If INA233 skips current/power, shunt-resistor and current-lsb aren't needed. */ /* read rshunt value (uOhm) */ - ret = device_property_read_u32(dev, "shunt-resistor", &rshunt); - if (ret) { - if (ret != -EINVAL) - return dev_err_probe(dev, ret, "Shunt resistor property read fail.\n"); + propname = "shunt-resistor"; + if (device_property_present(dev, propname)) { + ret = device_property_read_u32(dev, propname, &rshunt); + if (ret) + return dev_err_probe(dev, ret, "%s property read fail.\n", propname); + } else { rshunt = INA233_RSHUNT_DEFAULT; } if (!rshunt) - return dev_err_probe(dev, -EINVAL, - "Shunt resistor cannot be zero.\n"); + return dev_err_probe(dev, -EINVAL, "%s cannot be zero.\n", propname); /* read Maximum expected current value (uA) */ - ret = device_property_read_u32(dev, "ti,maximum-expected-current-microamp", &max_current); - if (ret) { - if (ret != -EINVAL) - return dev_err_probe(dev, ret, - "Maximum expected current property read fail.\n"); + propname = "ti,maximum-expected-current-microamp"; + if (device_property_present(dev, propname)) { + ret = device_property_read_u32(dev, propname, &max_current); + if (ret) + return dev_err_probe(dev, ret, "%s property read fail.\n", propname); + } else { max_current = INA233_MAX_CURRENT_DEFAULT; } if (max_current < 32768) - return dev_err_probe(dev, -EINVAL, - "Maximum expected current cannot less then 32768.\n"); + return dev_err_probe(dev, -EINVAL, "%s cannot be less than 32768.\n", propname); /* Calculate Current_LSB according to the spec formula */ current_lsb = max_current / 32768; diff --git a/drivers/hwmon/pmbus/isl68137.c b/drivers/hwmon/pmbus/isl68137.c index 3e3a887aad05..21d047b577a4 100644 --- a/drivers/hwmon/pmbus/isl68137.c +++ b/drivers/hwmon/pmbus/isl68137.c @@ -23,52 +23,6 @@ #define RAA_DMPVR2_READ_VMON 0xc8 #define MAX_CHANNELS 4 -enum chips { - isl68137, - isl68220, - isl68221, - isl68222, - isl68223, - isl68224, - isl68225, - isl68226, - isl68227, - isl68229, - isl68233, - isl68239, - isl69222, - isl69223, - isl69224, - isl69225, - isl69227, - isl69228, - isl69234, - isl69236, - isl69239, - isl69242, - isl69243, - isl69247, - isl69248, - isl69254, - isl69255, - isl69256, - isl69259, - isl69260, - isl69268, - isl69269, - isl69298, - raa228000, - raa228004, - raa228006, - raa228228, - raa228244, - raa228246, - raa229001, - raa229004, - raa229141, - raa229621, -}; - enum variants { raa_dmpvr1_2rail, raa_dmpvr2_1rail, @@ -90,8 +44,6 @@ struct isl68137_data { #define to_isl68137_data(x) container_of(x, struct isl68137_data, info) -static const struct i2c_device_id raa_dmpvr_id[]; - static ssize_t isl68137_avs_enable_show_page(struct i2c_client *client, int page, char *buf) @@ -393,7 +345,7 @@ static int isl68137_probe(struct i2c_client *client) memcpy(&data->info, &raa_dmpvr_info, sizeof(data->info)); info = &data->info; - switch (i2c_match_id(raa_dmpvr_id, client)->driver_data) { + switch ((uintptr_t)i2c_get_match_data(client)) { case raa_dmpvr1_2rail: info->pages = 2; info->R[PSC_VOLTAGE_IN] = 3; @@ -498,6 +450,8 @@ static const struct i2c_device_id raa_dmpvr_id[] = { {"raa228228", raa_dmpvr2_2rail_nontc}, {"raa228244", raa_dmpvr2_2rail_nontc}, {"raa228246", raa_dmpvr2_2rail_nontc}, + {"raa228942", raa_dmpvr2_2rail_nontc}, + {"raa228943", raa_dmpvr2_2rail_nontc}, {"raa229001", raa_dmpvr2_2rail}, {"raa229004", raa_dmpvr2_2rail}, {"raa229141", raa_dmpvr2_2rail_pmbus}, diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c index 8f5be520a15d..d69a5e675e80 100644 --- a/drivers/hwmon/pmbus/ltc2978.c +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -733,7 +733,7 @@ static int ltc2978_probe(struct i2c_client *client) return chip_id; data->id = chip_id; - id = i2c_match_id(ltc2978_id, client); + id = i2c_client_get_device_id(client); if (data->id != id->driver_data) dev_warn(&client->dev, "Device mismatch: Configured %s (%d), detected %d\n", diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c index d696e506aafb..36dc13424d92 100644 --- a/drivers/hwmon/pmbus/max16601.c +++ b/drivers/hwmon/pmbus/max16601.c @@ -318,7 +318,7 @@ static int max16601_probe(struct i2c_client *client) if (chip_id < 0) return chip_id; - id = i2c_match_id(max16601_id, client); + id = i2c_client_get_device_id(client); if (chip_id != id->driver_data) dev_warn(&client->dev, "Device mismatch: Configured %s (%d), detected %d\n", diff --git a/drivers/hwmon/pmbus/max20730.c b/drivers/hwmon/pmbus/max20730.c index 95869d198ecf..fe03164788df 100644 --- a/drivers/hwmon/pmbus/max20730.c +++ b/drivers/hwmon/pmbus/max20730.c @@ -715,10 +715,7 @@ static int max20730_probe(struct i2c_client *client) return -ENODEV; } - if (client->dev.of_node) - chip_id = (uintptr_t)of_device_get_match_data(dev); - else - chip_id = i2c_match_id(max20730_id, client)->driver_data; + chip_id = (uintptr_t)i2c_get_match_data(client); data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index 1f94d38a1637..3caa76bcbeb5 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c @@ -31,8 +31,6 @@ struct max31785_data { struct pmbus_driver_info info; }; -#define to_max31785_data(x) container_of(x, struct max31785_data, info) - /* * MAX31785 Driver Workaround * @@ -40,9 +38,8 @@ struct max31785_data { * These issues are not indicated by the device itself, except for occasional * NACK responses during master transactions. No error bits are set in STATUS_BYTE. * - * To address this, we introduce a delay of 250us between consecutive accesses - * to the fan controller. This delay helps mitigate communication problems by - * allowing sufficient time between accesses. + * Keep minimal local delay handling for raw pre-probe SMBus accesses. + * Normal PMBus-mediated accesses use pmbus_driver_info.access_delay instead. */ static inline void max31785_wait(const struct max31785_data *data) { @@ -54,89 +51,40 @@ static inline void max31785_wait(const struct max31785_data *data) } static int max31785_i2c_write_byte_data(struct i2c_client *client, - struct max31785_data *driver_data, - int command, u16 data) + struct max31785_data *data, + int command, u8 value) { int rc; - max31785_wait(driver_data); - rc = i2c_smbus_write_byte_data(client, command, data); - driver_data->access = ktime_get(); + max31785_wait(data); + rc = i2c_smbus_write_byte_data(client, command, value); + data->access = ktime_get(); return rc; } static int max31785_i2c_read_word_data(struct i2c_client *client, - struct max31785_data *driver_data, + struct max31785_data *data, int command) { int rc; - max31785_wait(driver_data); + max31785_wait(data); rc = i2c_smbus_read_word_data(client, command); - driver_data->access = ktime_get(); - return rc; -} - -static int _max31785_read_byte_data(struct i2c_client *client, - struct max31785_data *driver_data, - int page, int command) -{ - int rc; - - max31785_wait(driver_data); - rc = pmbus_read_byte_data(client, page, command); - driver_data->access = ktime_get(); - return rc; -} - -static int _max31785_write_byte_data(struct i2c_client *client, - struct max31785_data *driver_data, - int page, int command, u16 data) -{ - int rc; - - max31785_wait(driver_data); - rc = pmbus_write_byte_data(client, page, command, data); - driver_data->access = ktime_get(); - return rc; -} - -static int _max31785_read_word_data(struct i2c_client *client, - struct max31785_data *driver_data, - int page, int phase, int command) -{ - int rc; - - max31785_wait(driver_data); - rc = pmbus_read_word_data(client, page, phase, command); - driver_data->access = ktime_get(); - return rc; -} - -static int _max31785_write_word_data(struct i2c_client *client, - struct max31785_data *driver_data, - int page, int command, u16 data) -{ - int rc; - - max31785_wait(driver_data); - rc = pmbus_write_word_data(client, page, command, data); - driver_data->access = ktime_get(); + data->access = ktime_get(); return rc; } static int max31785_read_byte_data(struct i2c_client *client, int page, int reg) { - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct max31785_data *driver_data = to_max31785_data(info); - switch (reg) { case PMBUS_VOUT_MODE: return -ENOTSUPP; case PMBUS_FAN_CONFIG_12: - return _max31785_read_byte_data(client, driver_data, - page - MAX31785_NR_PAGES, - reg); + if (page < MAX31785_NR_PAGES) + return -ENODATA; + return pmbus_read_byte_data(client, + page - MAX31785_NR_PAGES, + reg); } return -ENODATA; @@ -178,14 +126,28 @@ static int max31785_read_long_data(struct i2c_client *client, int page, if (rc < 0) return rc; + /* + * Ensure the raw transfer is properly spaced from the + * preceding PMBus transaction. + */ + pmbus_wait(client); + rc = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); - if (rc < 0) - return rc; + + /* + * Update PMBus core timing state for the raw transfer, even on error. + * Pass 0 as the operation mask since this is a raw read, intentionally + * neither PMBUS_OP_WRITE nor PMBUS_OP_PAGE_CHANGE. + */ + pmbus_update_ts(client, 0); + + if (rc != ARRAY_SIZE(msg)) + return rc < 0 ? rc : -EIO; *data = (rspbuf[0] << (0 * 8)) | (rspbuf[1] << (1 * 8)) | (rspbuf[2] << (2 * 8)) | (rspbuf[3] << (3 * 8)); - return rc; + return 0; } static int max31785_get_pwm(struct i2c_client *client, int page) @@ -203,19 +165,16 @@ static int max31785_get_pwm(struct i2c_client *client, int page) return rv; } -static int max31785_get_pwm_mode(struct i2c_client *client, - struct max31785_data *driver_data, int page) +static int max31785_get_pwm_mode(struct i2c_client *client, int page) { int config; int command; - config = _max31785_read_byte_data(client, driver_data, page, - PMBUS_FAN_CONFIG_12); + config = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12); if (config < 0) return config; - command = _max31785_read_word_data(client, driver_data, page, 0xff, - PMBUS_FAN_COMMAND_1); + command = pmbus_read_word_data(client, page, 0xff, PMBUS_FAN_COMMAND_1); if (command < 0) return command; @@ -233,8 +192,6 @@ static int max31785_get_pwm_mode(struct i2c_client *client, static int max31785_read_word_data(struct i2c_client *client, int page, int phase, int reg) { - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct max31785_data *driver_data = to_max31785_data(info); u32 val; int rv; @@ -263,7 +220,7 @@ static int max31785_read_word_data(struct i2c_client *client, int page, rv = max31785_get_pwm(client, page); break; case PMBUS_VIRT_PWM_ENABLE_1: - rv = max31785_get_pwm_mode(client, driver_data, page); + rv = max31785_get_pwm_mode(client, page); break; default: rv = -ENODATA; @@ -294,35 +251,7 @@ static inline u32 max31785_scale_pwm(u32 sensor_val) return (sensor_val * 100) / 255; } -static int max31785_update_fan(struct i2c_client *client, - struct max31785_data *driver_data, int page, - u8 config, u8 mask, u16 command) -{ - int from, rv; - u8 to; - - from = _max31785_read_byte_data(client, driver_data, page, - PMBUS_FAN_CONFIG_12); - if (from < 0) - return from; - - to = (from & ~mask) | (config & mask); - - if (to != from) { - rv = _max31785_write_byte_data(client, driver_data, page, - PMBUS_FAN_CONFIG_12, to); - if (rv < 0) - return rv; - } - - rv = _max31785_write_word_data(client, driver_data, page, - PMBUS_FAN_COMMAND_1, command); - - return rv; -} - -static int max31785_pwm_enable(struct i2c_client *client, - struct max31785_data *driver_data, int page, +static int max31785_pwm_enable(struct i2c_client *client, int page, u16 word) { int config = 0; @@ -351,23 +280,20 @@ static int max31785_pwm_enable(struct i2c_client *client, return -EINVAL; } - return max31785_update_fan(client, driver_data, page, config, - PB_FAN_1_RPM, rate); + return pmbus_update_fan(client, page, 0, config, + PB_FAN_1_RPM, rate); } static int max31785_write_word_data(struct i2c_client *client, int page, int reg, u16 word) { - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct max31785_data *driver_data = to_max31785_data(info); - switch (reg) { case PMBUS_VIRT_PWM_1: - return max31785_update_fan(client, driver_data, page, 0, - PB_FAN_1_RPM, - max31785_scale_pwm(word)); + return pmbus_update_fan(client, page, 0, 0, + PB_FAN_1_RPM, + max31785_scale_pwm(word)); case PMBUS_VIRT_PWM_ENABLE_1: - return max31785_pwm_enable(client, driver_data, page, word); + return max31785_pwm_enable(client, page, word); default: break; } @@ -391,6 +317,7 @@ static const struct pmbus_driver_info max31785_info = { .read_byte_data = max31785_read_byte_data, .read_word_data = max31785_read_word_data, .write_byte = max31785_write_byte, + .access_delay = MAX31785_WAIT_DELAY_US, /* RPM */ .format[PSC_FAN] = direct, @@ -438,29 +365,29 @@ static const struct pmbus_driver_info max31785_info = { }; static int max31785_configure_dual_tach(struct i2c_client *client, - struct pmbus_driver_info *info) + struct max31785_data *data) { + struct pmbus_driver_info *info = &data->info; int ret; int i; - struct max31785_data *driver_data = to_max31785_data(info); for (i = 0; i < MAX31785_NR_FAN_PAGES; i++) { - ret = max31785_i2c_write_byte_data(client, driver_data, + ret = max31785_i2c_write_byte_data(client, data, PMBUS_PAGE, i); if (ret < 0) return ret; - ret = max31785_i2c_read_word_data(client, driver_data, + ret = max31785_i2c_read_word_data(client, data, MFR_FAN_CONFIG); if (ret < 0) return ret; if (ret & MFR_FAN_CONFIG_DUAL_TACH) { - int virtual = MAX31785_NR_PAGES + i; + int vpage = MAX31785_NR_PAGES + i; - info->pages = virtual + 1; - info->func[virtual] |= PMBUS_HAVE_FAN12; - info->func[virtual] |= PMBUS_PAGE_VIRTUAL; + info->pages = vpage + 1; + info->func[vpage] |= PMBUS_HAVE_FAN12; + info->func[vpage] |= PMBUS_PAGE_VIRTUAL; } } @@ -471,7 +398,7 @@ static int max31785_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct pmbus_driver_info *info; - struct max31785_data *driver_data; + struct max31785_data *data; bool dual_tach = false; int ret; @@ -480,20 +407,20 @@ static int max31785_probe(struct i2c_client *client) I2C_FUNC_SMBUS_WORD_DATA)) return -ENODEV; - driver_data = devm_kzalloc(dev, sizeof(struct max31785_data), GFP_KERNEL); - if (!driver_data) + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) return -ENOMEM; - info = &driver_data->info; - driver_data->access = ktime_get(); + data->access = ktime_get(); + info = &data->info; *info = max31785_info; - ret = max31785_i2c_write_byte_data(client, driver_data, - PMBUS_PAGE, 255); + ret = max31785_i2c_write_byte_data(client, data, + PMBUS_PAGE, 0xff); if (ret < 0) return ret; - ret = i2c_smbus_read_word_data(client, MFR_REVISION); + ret = max31785_i2c_read_word_data(client, data, MFR_REVISION); if (ret < 0) return ret; @@ -509,11 +436,13 @@ static int max31785_probe(struct i2c_client *client) } if (dual_tach) { - ret = max31785_configure_dual_tach(client, info); + ret = max31785_configure_dual_tach(client, data); if (ret < 0) return ret; } + max31785_wait(data); + return pmbus_do_probe(client, info); } diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index 8ea4e68d4e9d..cc96bb22f8f5 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -71,8 +71,6 @@ struct max34440_data { #define to_max34440_data(x) container_of(x, struct max34440_data, info) -static const struct i2c_device_id max34440_id[]; - static int max34440_read_word_data(struct i2c_client *client, int page, int phase, int reg) { @@ -628,7 +626,7 @@ static int max34440_probe(struct i2c_client *client) GFP_KERNEL); if (!data) return -ENOMEM; - data->id = i2c_match_id(max34440_id, client)->driver_data; + data->id = (uintptr_t)i2c_get_match_data(client); data->info = max34440_info[data->id]; data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT; data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT; diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c index 920cd5408141..d1844c7a51ee 100644 --- a/drivers/hwmon/pmbus/pmbus.c +++ b/drivers/hwmon/pmbus/pmbus.c @@ -20,8 +20,6 @@ struct pmbus_device_info { u32 flags; }; -static const struct i2c_device_id pmbus_id[]; - /* * Find sensor groups and status registers on each page. */ @@ -174,7 +172,7 @@ static int pmbus_probe(struct i2c_client *client) if (!info) return -ENOMEM; - device_info = (struct pmbus_device_info *)i2c_match_id(pmbus_id, client)->driver_data; + device_info = (struct pmbus_device_info *)i2c_get_match_data(client); if (device_info->flags) { pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data), GFP_KERNEL); diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index d2e9bfb5320f..23e3eda58870 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -10,6 +10,7 @@ #define PMBUS_H #include <linux/bitops.h> +#include <linux/cleanup.h> #include <linux/regulator/driver.h> /* @@ -416,7 +417,7 @@ enum pmbus_sensor_classes { #define PMBUS_PAGE_VIRTUAL BIT(31) /* Page is virtual */ enum pmbus_data_format { linear = 0, ieee754, direct, vid }; -enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv }; +enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv, nvidia195mv }; /* PMBus revision identifiers */ #define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ @@ -424,6 +425,10 @@ enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv }; #define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ #define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ +/* Operation type flags for pmbus_update_ts */ +#define PMBUS_OP_WRITE BIT(0) +#define PMBUS_OP_PAGE_CHANGE BIT(1) + struct pmbus_driver_info { int pages; /* Total number of pages */ u8 phases[PMBUS_PAGES]; /* Number of phases per page */ @@ -541,6 +546,8 @@ int pmbus_regulator_init_cb(struct regulator_dev *rdev, void pmbus_clear_cache(struct i2c_client *client); void pmbus_set_update(struct i2c_client *client, u8 reg, bool update); +void pmbus_wait(struct i2c_client *client); +void pmbus_update_ts(struct i2c_client *client, int op); int pmbus_set_page(struct i2c_client *client, int page, int phase); int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg); @@ -563,7 +570,11 @@ int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id, int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id, enum pmbus_fan_mode mode); int pmbus_lock_interruptible(struct i2c_client *client); +void pmbus_lock(struct i2c_client *client); void pmbus_unlock(struct i2c_client *client); + +DEFINE_GUARD(pmbus_lock, struct i2c_client *, pmbus_lock(_T), pmbus_unlock(_T)) + int pmbus_update_fan(struct i2c_client *client, int page, int id, u8 config, u8 mask, u16 command); struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client); diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 572be3ebc03d..e8fdd799c71c 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -37,8 +37,7 @@ * The type of operation used for picking the delay between * successive pmbus operations. */ -#define PMBUS_OP_WRITE BIT(0) -#define PMBUS_OP_PAGE_CHANGE BIT(1) +/* PMBUS_OP_WRITE and PMBUS_OP_PAGE_CHANGE are defined in pmbus.h */ static int wp = -1; module_param(wp, int, 0444); @@ -179,7 +178,7 @@ void pmbus_set_update(struct i2c_client *client, u8 reg, bool update) EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS"); /* Some chips need a delay between accesses. */ -static void pmbus_wait(struct i2c_client *client) +void pmbus_wait(struct i2c_client *client) { struct pmbus_data *data = i2c_get_clientdata(client); s64 delay = ktime_us_delta(data->next_access_backoff, ktime_get()); @@ -187,9 +186,10 @@ static void pmbus_wait(struct i2c_client *client) if (delay > 0) fsleep(delay); } +EXPORT_SYMBOL_NS_GPL(pmbus_wait, "PMBUS"); /* Sets the last operation timestamp for pmbus_wait */ -static void pmbus_update_ts(struct i2c_client *client, int op) +void pmbus_update_ts(struct i2c_client *client, int op) { struct pmbus_data *data = i2c_get_clientdata(client); const struct pmbus_driver_info *info = data->info; @@ -203,6 +203,7 @@ static void pmbus_update_ts(struct i2c_client *client, int op) if (delay > 0) data->next_access_backoff = ktime_add_us(ktime_get(), delay); } +EXPORT_SYMBOL_NS_GPL(pmbus_update_ts, "PMBUS"); int pmbus_set_page(struct i2c_client *client, int page, int phase) { @@ -891,6 +892,10 @@ static s64 pmbus_reg2data_vid(struct pmbus_data *data, if (val >= 0x0 && val <= 0xd8) rv = DIV_ROUND_CLOSEST(155000 - val * 625, 100); break; + case nvidia195mv: + if (val >= 0x01) + rv = 195 + (val - 1) * 5; /* VID step is 5mv */ + break; } return rv; } @@ -1156,12 +1161,11 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, int ret, status; u16 regval; - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); + status = pmbus_get_status(client, page, reg); - if (status < 0) { - ret = status; - goto unlock; - } + if (status < 0) + return status; if (s1) pmbus_update_sensor_data(client, s1); @@ -1173,7 +1177,7 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, if (data->revision >= PMBUS_REV_12) { ret = _pmbus_write_byte_data(client, page, reg, regval); if (ret) - goto unlock; + return ret; } else { pmbus_clear_fault_page(client, page); } @@ -1181,14 +1185,10 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, if (s1 && s2) { s64 v1, v2; - if (s1->data < 0) { - ret = s1->data; - goto unlock; - } - if (s2->data < 0) { - ret = s2->data; - goto unlock; - } + if (s1->data < 0) + return s1->data; + if (s2->data < 0) + return s2->data; v1 = pmbus_reg2data(data, s1); v2 = pmbus_reg2data(data, s2); @@ -1196,8 +1196,6 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, } else { ret = !!regval; } -unlock: - mutex_unlock(&data->update_lock); return ret; } @@ -1227,16 +1225,16 @@ static ssize_t pmbus_show_sensor(struct device *dev, struct i2c_client *client = to_i2c_client(dev->parent); struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); struct pmbus_data *data = i2c_get_clientdata(client); - ssize_t ret; + s64 val; - mutex_lock(&data->update_lock); - pmbus_update_sensor_data(client, sensor); - if (sensor->data < 0) - ret = sensor->data; - else - ret = sysfs_emit(buf, "%lld\n", pmbus_reg2data(data, sensor)); - mutex_unlock(&data->update_lock); - return ret; + scoped_guard(pmbus_lock, client) { + pmbus_update_sensor_data(client, sensor); + if (sensor->data < 0) + return sensor->data; + val = pmbus_reg2data(data, sensor); + } + + return sysfs_emit(buf, "%lld\n", val); } static ssize_t pmbus_set_sensor(struct device *dev, @@ -1246,7 +1244,6 @@ static ssize_t pmbus_set_sensor(struct device *dev, struct i2c_client *client = to_i2c_client(dev->parent); struct pmbus_data *data = i2c_get_clientdata(client); struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); - ssize_t rv = count; s64 val; int ret; u16 regval; @@ -1254,15 +1251,15 @@ static ssize_t pmbus_set_sensor(struct device *dev, if (kstrtos64(buf, 10, &val) < 0) return -EINVAL; - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); + regval = pmbus_data2reg(data, sensor, val); ret = _pmbus_write_word_data(client, sensor->page, sensor->reg, regval); if (ret < 0) - rv = ret; - else - sensor->data = -ENODATA; - mutex_unlock(&data->update_lock); - return rv; + return ret; + + sensor->data = -ENODATA; + return count; } static ssize_t pmbus_show_label(struct device *dev, @@ -1364,7 +1361,7 @@ static int pmbus_thermal_get_temp(struct thermal_zone_device *tz, int *temp) struct pmbus_data *pmbus_data = tdata->pmbus_data; struct i2c_client *client = to_i2c_client(pmbus_data->dev); struct device *dev = pmbus_data->hwmon_dev; - int ret = 0; + int _temp; if (!dev) { /* May not even get to hwmon yet */ @@ -1372,15 +1369,15 @@ static int pmbus_thermal_get_temp(struct thermal_zone_device *tz, int *temp) return 0; } - mutex_lock(&pmbus_data->update_lock); - pmbus_update_sensor_data(client, sensor); - if (sensor->data < 0) - ret = sensor->data; - else - *temp = (int)pmbus_reg2data(pmbus_data, sensor); - mutex_unlock(&pmbus_data->update_lock); + scoped_guard(pmbus_lock, client) { + pmbus_update_sensor_data(client, sensor); + if (sensor->data < 0) + return sensor->data; + _temp = (int)pmbus_reg2data(pmbus_data, sensor); + } - return ret; + *temp = _temp; + return 0; } static const struct thermal_zone_device_ops pmbus_thermal_ops = { @@ -2412,13 +2409,12 @@ static ssize_t pmbus_show_samples(struct device *dev, int val; struct i2c_client *client = to_i2c_client(dev->parent); struct pmbus_samples_reg *reg = to_samples_reg(devattr); - struct pmbus_data *data = i2c_get_clientdata(client); - mutex_lock(&data->update_lock); - val = _pmbus_read_word_data(client, reg->page, 0xff, reg->attr->reg); - mutex_unlock(&data->update_lock); - if (val < 0) - return val; + scoped_guard(pmbus_lock, client) { + val = _pmbus_read_word_data(client, reg->page, 0xff, reg->attr->reg); + if (val < 0) + return val; + } return sysfs_emit(buf, "%d\n", val); } @@ -2431,14 +2427,13 @@ static ssize_t pmbus_set_samples(struct device *dev, long val; struct i2c_client *client = to_i2c_client(dev->parent); struct pmbus_samples_reg *reg = to_samples_reg(devattr); - struct pmbus_data *data = i2c_get_clientdata(client); if (kstrtol(buf, 0, &val) < 0) return -EINVAL; - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); + ret = _pmbus_write_word_data(client, reg->page, reg->attr->reg, val); - mutex_unlock(&data->update_lock); return ret ? : count; } @@ -2950,14 +2945,9 @@ static int _pmbus_is_enabled(struct i2c_client *client, u8 page) static int __maybe_unused pmbus_is_enabled(struct i2c_client *client, u8 page) { - struct pmbus_data *data = i2c_get_clientdata(client); - int ret; - - mutex_lock(&data->update_lock); - ret = _pmbus_is_enabled(client, page); - mutex_unlock(&data->update_lock); + guard(pmbus_lock)(client); - return ret; + return _pmbus_is_enabled(client, page); } #define to_dev_attr(_dev_attr) \ @@ -2987,14 +2977,13 @@ static void pmbus_notify(struct pmbus_data *data, int page, int reg, int flags) } } -static int _pmbus_get_flags(struct pmbus_data *data, u8 page, unsigned int *flags, +static int _pmbus_get_flags(struct i2c_client *client, u8 page, unsigned int *flags, unsigned int *event, bool notify) { + struct pmbus_data *data = i2c_get_clientdata(client); int i, status; const struct pmbus_status_category *cat; const struct pmbus_status_assoc *bit; - struct device *dev = data->dev; - struct i2c_client *client = to_i2c_client(dev); int func = data->info->func[page]; *flags = 0; @@ -3070,16 +3059,12 @@ static int _pmbus_get_flags(struct pmbus_data *data, u8 page, unsigned int *flag return 0; } -static int __maybe_unused pmbus_get_flags(struct pmbus_data *data, u8 page, unsigned int *flags, +static int __maybe_unused pmbus_get_flags(struct i2c_client *client, u8 page, unsigned int *flags, unsigned int *event, bool notify) { - int ret; + guard(pmbus_lock)(client); - mutex_lock(&data->update_lock); - ret = _pmbus_get_flags(data, page, flags, event, notify); - mutex_unlock(&data->update_lock); - - return ret; + return _pmbus_get_flags(client, page, flags, event, notify); } #if IS_ENABLED(CONFIG_REGULATOR) @@ -3095,17 +3080,13 @@ static int _pmbus_regulator_on_off(struct regulator_dev *rdev, bool enable) { struct device *dev = rdev_get_dev(rdev); struct i2c_client *client = to_i2c_client(dev->parent); - struct pmbus_data *data = i2c_get_clientdata(client); u8 page = rdev_get_id(rdev); - int ret; - mutex_lock(&data->update_lock); - ret = pmbus_update_byte_data(client, page, PMBUS_OPERATION, - PB_OPERATION_CONTROL_ON, - enable ? PB_OPERATION_CONTROL_ON : 0); - mutex_unlock(&data->update_lock); + guard(pmbus_lock)(client); - return ret; + return pmbus_update_byte_data(client, page, PMBUS_OPERATION, + PB_OPERATION_CONTROL_ON, + enable ? PB_OPERATION_CONTROL_ON : 0); } static int pmbus_regulator_enable(struct regulator_dev *rdev) @@ -3122,54 +3103,41 @@ static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned { struct device *dev = rdev_get_dev(rdev); struct i2c_client *client = to_i2c_client(dev->parent); - struct pmbus_data *data = i2c_get_clientdata(client); int event; - return pmbus_get_flags(data, rdev_get_id(rdev), flags, &event, false); + return pmbus_get_flags(client, rdev_get_id(rdev), flags, &event, false); } static int pmbus_regulator_get_status(struct regulator_dev *rdev) { struct device *dev = rdev_get_dev(rdev); struct i2c_client *client = to_i2c_client(dev->parent); - struct pmbus_data *data = i2c_get_clientdata(client); u8 page = rdev_get_id(rdev); int status, ret; int event; - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); + status = pmbus_get_status(client, page, PMBUS_STATUS_WORD); - if (status < 0) { - ret = status; - goto unlock; - } + if (status < 0) + return status; - if (status & PB_STATUS_OFF) { - ret = REGULATOR_STATUS_OFF; - goto unlock; - } + if (status & PB_STATUS_OFF) + return REGULATOR_STATUS_OFF; /* If regulator is ON & reports power good then return ON */ - if (!(status & PB_STATUS_POWER_GOOD_N)) { - ret = REGULATOR_STATUS_ON; - goto unlock; - } + if (!(status & PB_STATUS_POWER_GOOD_N)) + return REGULATOR_STATUS_ON; - ret = _pmbus_get_flags(data, rdev_get_id(rdev), &status, &event, false); + ret = _pmbus_get_flags(client, rdev_get_id(rdev), &status, &event, false); if (ret) - goto unlock; + return ret; if (status & (REGULATOR_ERROR_UNDER_VOLTAGE | REGULATOR_ERROR_OVER_CURRENT | - REGULATOR_ERROR_REGULATION_OUT | REGULATOR_ERROR_FAIL | REGULATOR_ERROR_OVER_TEMP)) { - ret = REGULATOR_STATUS_ERROR; - goto unlock; - } + REGULATOR_ERROR_REGULATION_OUT | REGULATOR_ERROR_FAIL | REGULATOR_ERROR_OVER_TEMP)) + return REGULATOR_STATUS_ERROR; - ret = REGULATOR_STATUS_UNDEFINED; - -unlock: - mutex_unlock(&data->update_lock); - return ret; + return REGULATOR_STATUS_UNDEFINED; } static int pmbus_regulator_get_low_margin(struct i2c_client *client, int page) @@ -3234,19 +3202,16 @@ static int pmbus_regulator_get_voltage(struct regulator_dev *rdev) .class = PSC_VOLTAGE_OUT, .convert = true, }; - int ret; + int voltage; - mutex_lock(&data->update_lock); - s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT); - if (s.data < 0) { - ret = s.data; - goto unlock; + scoped_guard(pmbus_lock, client) { + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT); + if (s.data < 0) + return s.data; + voltage = (int)pmbus_reg2data(data, &s); } - ret = (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */ -unlock: - mutex_unlock(&data->update_lock); - return ret; + return voltage * 1000; /* unit is uV */ } static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv, @@ -3263,22 +3228,18 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv, }; int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */ int low, high; - int ret; *selector = 0; - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); + low = pmbus_regulator_get_low_margin(client, s.page); - if (low < 0) { - ret = low; - goto unlock; - } + if (low < 0) + return low; high = pmbus_regulator_get_high_margin(client, s.page); - if (high < 0) { - ret = high; - goto unlock; - } + if (high < 0) + return high; /* Make sure we are within margins */ if (low > val) @@ -3288,10 +3249,7 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv, val = pmbus_data2reg(data, &s, val); - ret = _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val); -unlock: - mutex_unlock(&data->update_lock); - return ret; + return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val); } static int pmbus_regulator_list_voltage(struct regulator_dev *rdev, @@ -3301,7 +3259,6 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev, struct i2c_client *client = to_i2c_client(dev->parent); struct pmbus_data *data = i2c_get_clientdata(client); int val, low, high; - int ret; if (data->flags & PMBUS_VOUT_PROTECTED) return 0; @@ -3314,29 +3271,20 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev, val = DIV_ROUND_CLOSEST(rdev->desc->min_uV + (rdev->desc->uV_step * selector), 1000); /* convert to mV */ - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); low = pmbus_regulator_get_low_margin(client, rdev_get_id(rdev)); - if (low < 0) { - ret = low; - goto unlock; - } + if (low < 0) + return low; high = pmbus_regulator_get_high_margin(client, rdev_get_id(rdev)); - if (high < 0) { - ret = high; - goto unlock; - } + if (high < 0) + return high; - if (val >= low && val <= high) { - ret = val * 1000; /* unit is uV */ - goto unlock; - } + if (val >= low && val <= high) + return val * 1000; /* unit is uV */ - ret = 0; -unlock: - mutex_unlock(&data->update_lock); - return ret; + return 0; } const struct regulator_ops pmbus_regulator_ops = { @@ -3472,16 +3420,16 @@ static irqreturn_t pmbus_fault_handler(int irq, void *pdata) struct i2c_client *client = to_i2c_client(data->dev); int i, status, event; - mutex_lock(&data->update_lock); + guard(pmbus_lock)(client); + for (i = 0; i < data->info->pages; i++) { - _pmbus_get_flags(data, i, &status, &event, true); + _pmbus_get_flags(client, i, &status, &event, true); if (event) pmbus_regulator_notify(data, i, event); } pmbus_clear_faults(client); - mutex_unlock(&data->update_lock); return IRQ_HANDLED; } @@ -3537,15 +3485,13 @@ static struct dentry *pmbus_debugfs_dir; /* pmbus debugfs directory */ static int pmbus_debugfs_get(void *data, u64 *val) { - int rc; struct pmbus_debugfs_entry *entry = data; - struct pmbus_data *pdata = i2c_get_clientdata(entry->client); + struct i2c_client *client = entry->client; + int rc; - rc = mutex_lock_interruptible(&pdata->update_lock); - if (rc) - return rc; - rc = _pmbus_read_byte_data(entry->client, entry->page, entry->reg); - mutex_unlock(&pdata->update_lock); + guard(pmbus_lock)(client); + + rc = _pmbus_read_byte_data(client, entry->page, entry->reg); if (rc < 0) return rc; @@ -3558,15 +3504,14 @@ DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL, static int pmbus_debugfs_get_status(void *data, u64 *val) { - int rc; struct pmbus_debugfs_entry *entry = data; - struct pmbus_data *pdata = i2c_get_clientdata(entry->client); + struct i2c_client *client = entry->client; + struct pmbus_data *pdata = i2c_get_clientdata(client); + int rc; - rc = mutex_lock_interruptible(&pdata->update_lock); - if (rc) - return rc; - rc = pdata->read_status(entry->client, entry->page); - mutex_unlock(&pdata->update_lock); + guard(pmbus_lock)(client); + + rc = pdata->read_status(client, entry->page); if (rc < 0) return rc; @@ -3582,17 +3527,14 @@ static ssize_t pmbus_debugfs_block_read(struct file *file, char __user *buf, { int rc; struct pmbus_debugfs_entry *entry = file->private_data; - struct pmbus_data *pdata = i2c_get_clientdata(entry->client); + struct i2c_client *client = entry->client; char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; - rc = mutex_lock_interruptible(&pdata->update_lock); - if (rc) - return rc; - rc = pmbus_read_block_data(entry->client, entry->page, entry->reg, - data); - mutex_unlock(&pdata->update_lock); - if (rc < 0) - return rc; + scoped_guard(pmbus_lock, client) { + rc = pmbus_read_block_data(client, entry->page, entry->reg, data); + if (rc < 0) + return rc; + } /* Add newline at the end of a read data */ data[rc] = '\n'; @@ -3871,6 +3813,14 @@ struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client) } EXPORT_SYMBOL_NS_GPL(pmbus_get_debugfs_dir, "PMBUS"); +void pmbus_lock(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); +} +EXPORT_SYMBOL_NS_GPL(pmbus_lock, "PMBUS"); + int pmbus_lock_interruptible(struct i2c_client *client) { struct pmbus_data *data = i2c_get_clientdata(client); diff --git a/drivers/hwmon/pmbus/q54sj108a2.c b/drivers/hwmon/pmbus/q54sj108a2.c index d5d60a9af8c5..a368cfa9d45a 100644 --- a/drivers/hwmon/pmbus/q54sj108a2.c +++ b/drivers/hwmon/pmbus/q54sj108a2.c @@ -271,6 +271,8 @@ static const struct file_operations q54sj108a2_fops = { static const struct i2c_device_id q54sj108a2_id[] = { { "q54sj108a2", q54sj108a2 }, + { "q54sn120a1", q54sj108a2 }, + { "q54sw120a7", q54sj108a2 }, { }, }; @@ -280,6 +282,7 @@ static int q54sj108a2_probe(struct i2c_client *client) { struct device *dev = &client->dev; u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + const struct i2c_device_id *mid; enum chips chip_id; int ret, i; struct dentry *debugfs; @@ -292,10 +295,7 @@ static int q54sj108a2_probe(struct i2c_client *client) I2C_FUNC_SMBUS_BLOCK_DATA)) return -ENODEV; - if (client->dev.of_node) - chip_id = (enum chips)(unsigned long)of_device_get_match_data(dev); - else - chip_id = i2c_match_id(q54sj108a2_id, client)->driver_data; + chip_id = (enum chips)(uintptr_t)i2c_get_match_data(client); ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); if (ret < 0) { @@ -316,8 +316,12 @@ static int q54sj108a2_probe(struct i2c_client *client) dev_err(dev, "Failed to read Manufacturer Model\n"); return ret; } - if (ret != 14 || strncmp(buf, "Q54SJ108A2", 10)) { - buf[ret] = '\0'; + buf[ret] = '\0'; + for (mid = q54sj108a2_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, buf, strlen(mid->name))) + break; + } + if (!mid->name[0]) { dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf); return -ENODEV; } @@ -327,7 +331,10 @@ static int q54sj108a2_probe(struct i2c_client *client) dev_err(dev, "Failed to read Manufacturer Revision\n"); return ret; } - if (ret != 4 || buf[0] != 'S') { + /* + * accept manufacturer revision with optional NUL byte + */ + if (!(ret == 4 || ret == 5) || buf[0] != 'S') { buf[ret] = '\0'; dev_err(dev, "Unsupported Manufacturer Revision '%s'\n", buf); return -ENODEV; @@ -404,6 +411,8 @@ static int q54sj108a2_probe(struct i2c_client *client) static const struct of_device_id q54sj108a2_of_match[] = { { .compatible = "delta,q54sj108a2", .data = (void *)q54sj108a2 }, + { .compatible = "delta,q54sn120a1", .data = (void *)q54sj108a2 }, + { .compatible = "delta,q54sw120a7", .data = (void *)q54sj108a2 }, { }, }; diff --git a/drivers/hwmon/pmbus/tps25990.c b/drivers/hwmon/pmbus/tps25990.c index c13edd7e1abf..05c6288ecafc 100644 --- a/drivers/hwmon/pmbus/tps25990.c +++ b/drivers/hwmon/pmbus/tps25990.c @@ -402,12 +402,18 @@ static int tps25990_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct pmbus_driver_info *info; - u32 rimon = TPS25990_DEFAULT_RIMON; + const char *propname; + u32 rimon; int ret; - ret = device_property_read_u32(dev, "ti,rimon-micro-ohms", &rimon); - if (ret < 0 && ret != -EINVAL) - return dev_err_probe(dev, ret, "failed to get rimon\n"); + propname = "ti,rimon-micro-ohms"; + if (device_property_present(dev, propname)) { + ret = device_property_read_u32(dev, propname, &rimon); + if (ret) + return dev_err_probe(dev, ret, "failed to get %s\n", propname); + } else { + rimon = TPS25990_DEFAULT_RIMON; + } info = devm_kmemdup(dev, &tps25990_base_info, sizeof(*info), GFP_KERNEL); if (!info) diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c index 249974c13aa3..94258e8cfd90 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -253,10 +253,7 @@ static int tps53679_probe(struct i2c_client *client) struct pmbus_driver_info *info; enum chips chip_id; - if (dev->of_node) - chip_id = (uintptr_t)of_device_get_match_data(dev); - else - chip_id = i2c_match_id(tps53679_id, client)->driver_data; + chip_id = (uintptr_t)i2c_get_match_data(client); info = devm_kmemdup(dev, &tps53679_info, sizeof(*info), GFP_KERNEL); if (!info) diff --git a/drivers/hwmon/pmbus/xdp720.c b/drivers/hwmon/pmbus/xdp720.c new file mode 100644 index 000000000000..8729a771f216 --- /dev/null +++ b/drivers/hwmon/pmbus/xdp720.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for Infineon XDP720 Digital eFuse Controller + * + * Copyright (c) 2026 Infineon Technologies. All rights reserved. + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/of_device.h> +#include <linux/bitops.h> +#include <linux/math64.h> +#include "pmbus.h" + +/* + * The IMON resistor required to generate the system overcurrent protection. + * Arbitrary default Rimon value: 2k Ohm + */ +#define XDP720_DEFAULT_RIMON 2000000000 /* 2k ohm */ +#define XDP720_TELEMETRY_AVG 0xE9 + +static struct pmbus_driver_info xdp720_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .format[PSC_TEMPERATURE] = direct, + + .m[PSC_VOLTAGE_IN] = 4653, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -2, + .m[PSC_VOLTAGE_OUT] = 4653, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -2, + /* + * Current and Power measurement depends on the RIMON (kOhm) and + * GIMON(microA/A) values. + */ + .m[PSC_CURRENT_OUT] = 24668, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = -4, + .m[PSC_POWER] = 4486, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = -1, + .m[PSC_TEMPERATURE] = 54, + .b[PSC_TEMPERATURE] = 22521, + .R[PSC_TEMPERATURE] = -1, + + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_PIN | + PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_STATUS_TEMP, +}; + +static int xdp720_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + int ret; + u32 rimon; + int gimon; + + info = devm_kmemdup(&client->dev, &xdp720_info, sizeof(*info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = devm_regulator_get_enable(&client->dev, "vdd-vin"); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to enable vdd-vin supply\n"); + + ret = i2c_smbus_read_word_data(client, XDP720_TELEMETRY_AVG); + if (ret < 0) { + dev_err(&client->dev, "Can't get TELEMETRY_AVG\n"); + return ret; + } + + ret >>= 10; /* 10th bit of TELEMETRY_AVG REG for GIMON Value */ + ret &= GENMASK(0, 0); + if (ret == 1) + gimon = 18200; /* output gain 18.2 microA/A */ + else + gimon = 9100; /* output gain 9.1 microA/A */ + + if (of_property_read_u32(client->dev.of_node, + "infineon,rimon-micro-ohms", &rimon)) + rimon = XDP720_DEFAULT_RIMON; /* Default if not set via DT */ + if (rimon == 0) + return -EINVAL; + + /* Adapt the current and power scale for each instance */ + info->m[PSC_CURRENT_OUT] = DIV64_U64_ROUND_CLOSEST((u64) + info->m[PSC_CURRENT_OUT] * rimon * gimon, 1000000000000ULL); + info->m[PSC_POWER] = DIV64_U64_ROUND_CLOSEST((u64) + info->m[PSC_POWER] * rimon * gimon, 1000000000000000ULL); + + return pmbus_do_probe(client, info); +} + +static const struct of_device_id xdp720_of_match[] = { + { .compatible = "infineon,xdp720" }, + {} +}; +MODULE_DEVICE_TABLE(of, xdp720_of_match); + +static const struct i2c_device_id xdp720_id[] = { + { "xdp720" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, xdp720_id); + +static struct i2c_driver xdp720_driver = { + .driver = { + .name = "xdp720", + .of_match_table = xdp720_of_match, + }, + .probe = xdp720_probe, + .id_table = xdp720_id, +}; + +module_i2c_driver(xdp720_driver); + +MODULE_AUTHOR("Ashish Yadav <ashish.yadav@infineon.com>"); +MODULE_DESCRIPTION("PMBus driver for Infineon XDP720 Digital eFuse Controller"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); diff --git a/drivers/hwmon/pmbus/xdpe1a2g7b.c b/drivers/hwmon/pmbus/xdpe1a2g7b.c new file mode 100644 index 000000000000..1755e3522ede --- /dev/null +++ b/drivers/hwmon/pmbus/xdpe1a2g7b.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for Infineon Multi-phase Digital XDPE1A2G5B + * and XDPE1A2G7B Controllers + * + * Copyright (c) 2026 Infineon Technologies. All rights reserved. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include "pmbus.h" + +#define XDPE1A2G7B_PAGE_NUM 2 +#define XDPE1A2G7B_NVIDIA_195MV 0x1E /* NVIDIA mode 1.95mV, VID step is 5mV */ + +static int xdpe1a2g7b_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + u8 vout_params; + int vout_mode; + + /* + * XDPE1A2G5B and XDPE1A2G7B support both Linear and NVIDIA PWM VID data + * formats via VOUT_MODE. Note that the device pages/loops are not fully + * independent: configuration is shared, so programming each page/loop + * separately is not supported. + */ + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode < 0) + return vout_mode; + + switch (vout_mode >> 5) { + case 0: + info->format[PSC_VOLTAGE_OUT] = linear; + return 0; + case 1: + info->format[PSC_VOLTAGE_OUT] = vid; + vout_params = vout_mode & GENMASK(4, 0); + /* Check for VID Code Type */ + switch (vout_params) { + case XDPE1A2G7B_NVIDIA_195MV: + /* VID vrm_version for PAGE0 and PAGE1 */ + info->vrm_version[0] = nvidia195mv; + info->vrm_version[1] = nvidia195mv; + break; + default: + return -EINVAL; + } + break; + default: + return -ENODEV; + } + + return 0; +} + +static struct pmbus_driver_info xdpe1a2g7b_info = { + .pages = XDPE1A2G7B_PAGE_NUM, + .identify = xdpe1a2g7b_identify, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_STATUS_INPUT, +}; + +static int xdpe1a2g7b_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + + info = devm_kmemdup(&client->dev, &xdpe1a2g7b_info, sizeof(*info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id xdpe1a2g7b_id[] = { + { "xdpe1a2g5b" }, + { "xdpe1a2g7b" }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, xdpe1a2g7b_id); + +static const struct of_device_id __maybe_unused xdpe1a2g7b_of_match[] = { + { .compatible = "infineon,xdpe1a2g5b" }, + { .compatible = "infineon,xdpe1a2g7b" }, + {} +}; + +MODULE_DEVICE_TABLE(of, xdpe1a2g7b_of_match); + +static struct i2c_driver xdpe1a2g7b_driver = { + .driver = { + .name = "xdpe1a2g7b", + .of_match_table = of_match_ptr(xdpe1a2g7b_of_match), + }, + .probe = xdpe1a2g7b_probe, + .id_table = xdpe1a2g7b_id, +}; + +module_i2c_driver(xdpe1a2g7b_driver); + +MODULE_AUTHOR("Ashish Yadav <ashish.yadav@infineon.com>"); +MODULE_DESCRIPTION("PMBus driver for Infineon XDPE1A2G5B/7B"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); diff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index 4e663d5b4e33..6e1359144cab 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -6,6 +6,7 @@ #include <linux/completion.h> #include <linux/device.h> +#include <linux/dma-mapping.h> #include <linux/hwmon.h> #include <linux/module.h> #include <linux/mutex.h> @@ -33,7 +34,9 @@ struct powerz_sensor_data { } __packed; struct powerz_priv { - char transfer_buffer[64]; /* first member to satisfy DMA alignment */ + __dma_from_device_group_begin(); + char transfer_buffer[64]; + __dma_from_device_group_end(); struct mutex mutex; struct completion completion; struct urb *urb; @@ -106,8 +109,12 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; + if (!priv->urb) + return -ENODEV; + priv->status = -ETIMEDOUT; reinit_completion(&priv->completion); @@ -124,8 +131,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } @@ -224,6 +237,8 @@ static int powerz_probe(struct usb_interface *intf, mutex_init(&priv->mutex); init_completion(&priv->completion); + usb_set_intfdata(intf, priv); + hwmon_dev = devm_hwmon_device_register_with_info(parent, DRIVER_NAME, priv, &powerz_chip_info, NULL); @@ -232,8 +247,6 @@ static int powerz_probe(struct usb_interface *intf, return PTR_ERR(hwmon_dev); } - usb_set_intfdata(intf, priv); - return 0; } @@ -244,6 +257,7 @@ static void powerz_disconnect(struct usb_interface *intf) mutex_lock(&priv->mutex); usb_kill_urb(priv->urb); usb_free_urb(priv->urb); + priv->urb = NULL; mutex_unlock(&priv->mutex); } diff --git a/drivers/hwmon/pt5161l.c b/drivers/hwmon/pt5161l.c index 20e3cfa625f1..89d4da8aa4c0 100644 --- a/drivers/hwmon/pt5161l.c +++ b/drivers/hwmon/pt5161l.c @@ -121,7 +121,7 @@ static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address, int ret, tries; u8 remain_len = len; u8 curr_len; - u8 wbuf[16], rbuf[24]; + u8 wbuf[16], rbuf[I2C_SMBUS_BLOCK_MAX]; u8 cmd = 0x08; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */ u8 config = 0x00; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */ @@ -151,7 +151,7 @@ static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address, break; } if (tries >= 3) - return ret; + return ret < 0 ? ret : -EIO; memcpy(val, rbuf, curr_len); val += curr_len; diff --git a/drivers/hwmon/tc74.c b/drivers/hwmon/tc74.c index 9984373a25fb..7fb7b50ad1ad 100644 --- a/drivers/hwmon/tc74.c +++ b/drivers/hwmon/tc74.c @@ -92,7 +92,7 @@ static ssize_t temp_input_show(struct device *dev, if (ret) return ret; - return sprintf(buf, "%d\n", data->temp_input * 1000); + return sysfs_emit(buf, "%d\n", data->temp_input * 1000); } static SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0); diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c index 5b10c395a84d..3aa1a3fbeaa9 100644 --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -50,11 +50,16 @@ #define CONVERSION_TIME_MS 35 /* in milli-seconds */ +#define NUM_SAMPLE_TIMES 4 +#define DEFAULT_SAMPLE_TIME_MS 250 +static const unsigned int *sample_times = (const unsigned int []){ 125, 250, 1000, 4000 }; + struct tmp102 { const char *label; struct regmap *regmap; u16 config_orig; unsigned long ready_time; + u16 sample_time; }; /* convert left adjusted 13-bit TMP102 register value to milliCelsius */ @@ -79,8 +84,20 @@ static int tmp102_read_string(struct device *dev, enum hwmon_sensor_types type, return 0; } -static int tmp102_read(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, long *temp) +static int tmp102_read_chip(struct device *dev, u32 attr, long *val) +{ + struct tmp102 *tmp102 = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_chip_update_interval: + *val = tmp102->sample_time; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int tmp102_read_temp(struct device *dev, u32 attr, long *val) { struct tmp102 *tmp102 = dev_get_drvdata(dev); unsigned int regval; @@ -108,13 +125,54 @@ static int tmp102_read(struct device *dev, enum hwmon_sensor_types type, err = regmap_read(tmp102->regmap, reg, ®val); if (err < 0) return err; - *temp = tmp102_reg_to_mC(regval); + + *val = tmp102_reg_to_mC(regval); return 0; } -static int tmp102_write(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, long temp) +static int tmp102_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return tmp102_read_chip(dev, attr, val); + case hwmon_temp: + return tmp102_read_temp(dev, attr, val); + default: + return -EOPNOTSUPP; + } +} + +static int tmp102_update_interval(struct device *dev, long val) +{ + struct tmp102 *tmp102 = dev_get_drvdata(dev); + u8 index; + s32 err; + + index = find_closest(val, sample_times, NUM_SAMPLE_TIMES); + + err = regmap_update_bits(tmp102->regmap, TMP102_CONF_REG, + (TMP102_CONF_CR1 | TMP102_CONF_CR0), (3 - index) << 6); + if (err < 0) + return err; + tmp102->sample_time = sample_times[index]; + + return 0; +} + +static int tmp102_write_chip(struct device *dev, u32 attr, long val) +{ + switch (attr) { + case hwmon_chip_update_interval: + return tmp102_update_interval(dev, val); + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int tmp102_write_temp(struct device *dev, u32 attr, long val) { struct tmp102 *tmp102 = dev_get_drvdata(dev); int reg; @@ -130,8 +188,22 @@ static int tmp102_write(struct device *dev, enum hwmon_sensor_types type, return -EOPNOTSUPP; } - temp = clamp_val(temp, -256000, 255000); - return regmap_write(tmp102->regmap, reg, tmp102_mC_to_reg(temp)); + val = clamp_val(val, -256000, 255000); + return regmap_write(tmp102->regmap, reg, tmp102_mC_to_reg(val)); +} + +static int tmp102_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_chip: + return tmp102_write_chip(dev, attr, val); + case hwmon_temp: + return tmp102_write_temp(dev, attr, val); + default: + return -EOPNOTSUPP; + } + return 0; } static umode_t tmp102_is_visible(const void *data, enum hwmon_sensor_types type, @@ -139,27 +211,39 @@ static umode_t tmp102_is_visible(const void *data, enum hwmon_sensor_types type, { const struct tmp102 *tmp102 = data; - if (type != hwmon_temp) - return 0; - - switch (attr) { - case hwmon_temp_input: - return 0444; - case hwmon_temp_label: - if (tmp102->label) + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + default: + break; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: return 0444; - return 0; - case hwmon_temp_max_hyst: - case hwmon_temp_max: - return 0644; + case hwmon_temp_label: + if (tmp102->label) + return 0444; + return 0; + case hwmon_temp_max_hyst: + case hwmon_temp_max: + return 0644; + default: + break; + } + break; default: - return 0; + break; } + return 0; } static const struct hwmon_channel_info * const tmp102_info[] = { HWMON_CHANNEL_INFO(chip, - HWMON_C_REGISTER_TZ), + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MAX | HWMON_T_MAX_HYST), NULL @@ -237,6 +321,8 @@ static int tmp102_probe(struct i2c_client *client) if (IS_ERR(tmp102->regmap)) return PTR_ERR(tmp102->regmap); + tmp102->sample_time = DEFAULT_SAMPLE_TIME_MS; + err = regmap_read(tmp102->regmap, TMP102_CONF_REG, ®val); if (err < 0) { dev_err(dev, "error reading config register\n"); diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c new file mode 100644 index 000000000000..605cc928f21f --- /dev/null +++ b/drivers/hwmon/yogafan.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** + * yoga_fan.c - Lenovo Yoga/Legion Fan Hardware Monitoring Driver + * + * Provides fan speed monitoring for Lenovo Yoga, Legion, and IdeaPad + * laptops by interfacing with the Embedded Controller (EC) via ACPI. + * + * The driver implements a passive discrete-time first-order lag filter + * with slew-rate limiting (RLLag). This addresses low-resolution + * tachometer sampling in the EC by smoothing RPM readings based on + * the time delta (dt) between userspace requests, ensuring physical + * consistency without background task overhead or race conditions. + * The filter implements multirate filtering with autoreset in case + * of large sampling time. + * + * Copyright (C) 2021-2026 Sergio Melas <sergiomelas@gmail.com> + */ +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/ktime.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/math64.h> + +/* Driver Configuration Constants */ +#define DRVNAME "yogafan" +#define MAX_FANS 8 + +/* Filter Configuration Constants */ +#define TAU_MS 1000 /* Time constant for the first-order lag (ms) */ +#define MAX_SLEW_RPM_S 1500 /* Maximum allowed change in RPM per second */ +#define MAX_SAMPLING 5000 /* Maximum allowed Ts for reset (ms) */ +#define MIN_SAMPLING 100 /* Minimum interval between filter updates (ms) */ + +/* RPM Sanitation Constants */ +#define RPM_FLOOR_LIMIT 50 /* Snap filtered value to 0 if raw is 0 */ + +struct yogafan_config { + int multiplier; + int fan_count; + const char *paths[2]; +}; + +struct yoga_fan_data { + acpi_handle active_handles[MAX_FANS]; + long filtered_val[MAX_FANS]; + ktime_t last_sample[MAX_FANS]; + int multiplier; + int fan_count; +}; + +/* Specific configurations mapped via DMI */ +static const struct yogafan_config yoga_8bit_fans_cfg = { + .multiplier = 100, + .fan_count = 1, + .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", NULL } +}; + +static const struct yogafan_config ideapad_8bit_fan0_cfg = { + .multiplier = 100, + .fan_count = 1, + .paths = { "\\_SB.PCI0.LPC0.EC0.FAN0", NULL } +}; + +static const struct yogafan_config legion_16bit_dual_cfg = { + .multiplier = 1, + .fan_count = 2, + .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } +}; + +static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm) +{ + ktime_t now = ktime_get_boottime(); + s64 dt_ms = ktime_to_ms(ktime_sub(now, data->last_sample[idx])); + long delta, step, limit, alpha; + s64 temp_num; + + if (raw_rpm < RPM_FLOOR_LIMIT) { + data->filtered_val[idx] = 0; + data->last_sample[idx] = now; + return; + } + + if (data->last_sample[idx] == 0 || dt_ms > MAX_SAMPLING) { + data->filtered_val[idx] = raw_rpm; + data->last_sample[idx] = now; + return; + } + + if (dt_ms < MIN_SAMPLING) + return; + + delta = raw_rpm - data->filtered_val[idx]; + if (delta == 0) { + data->last_sample[idx] = now; + return; + } + + temp_num = dt_ms << 12; + alpha = (long)div64_s64(temp_num, (s64)(TAU_MS + dt_ms)); + step = (delta * alpha) >> 12; + + if (step == 0 && delta != 0) + step = (delta > 0) ? 1 : -1; + + limit = (MAX_SLEW_RPM_S * (long)dt_ms) / 1000; + if (limit < 1) + limit = 1; + + if (step > limit) + step = limit; + else if (step < -limit) + step = -limit; + + data->filtered_val[idx] += step; + data->last_sample[idx] = now; +} + +static int yoga_fan_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct yoga_fan_data *data = dev_get_drvdata(dev); + unsigned long long raw_acpi; + acpi_status status; + + if (type != hwmon_fan || attr != hwmon_fan_input) + return -EOPNOTSUPP; + + status = acpi_evaluate_integer(data->active_handles[channel], NULL, NULL, &raw_acpi); + if (ACPI_FAILURE(status)) + return -EIO; + + apply_rllag_filter(data, channel, (long)raw_acpi * data->multiplier); + *val = data->filtered_val[channel]; + + return 0; +} + +static umode_t yoga_fan_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct yoga_fan_data *fan_data = data; + + if (type == hwmon_fan && channel < fan_data->fan_count) + return 0444; + + return 0; +} + +static const struct hwmon_ops yoga_fan_hwmon_ops = { + .is_visible = yoga_fan_is_visible, + .read = yoga_fan_read, +}; + +static const struct hwmon_channel_info *yoga_fan_info[] = { + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT, HWMON_F_INPUT, + HWMON_F_INPUT, HWMON_F_INPUT, + HWMON_F_INPUT, HWMON_F_INPUT, + HWMON_F_INPUT, HWMON_F_INPUT), + NULL +}; + +static const struct hwmon_chip_info yoga_fan_chip_info = { + .ops = &yoga_fan_hwmon_ops, + .info = yoga_fan_info, +}; + +static const struct dmi_system_id yogafan_quirks[] = { + { + .ident = "Lenovo Yoga", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga"), + }, + .driver_data = (void *)&yoga_8bit_fans_cfg, + }, + { + .ident = "Lenovo Legion", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Legion"), + }, + .driver_data = (void *)&legion_16bit_dual_cfg, + }, + { + .ident = "Lenovo IdeaPad", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad"), + }, + .driver_data = (void *)&ideapad_8bit_fan0_cfg, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, yogafan_quirks); + +static int yoga_fan_probe(struct platform_device *pdev) +{ + const struct dmi_system_id *dmi_id; + const struct yogafan_config *cfg; + struct yoga_fan_data *data; + struct device *hwmon_dev; + int i; + + dmi_id = dmi_first_match(yogafan_quirks); + if (!dmi_id) + return -ENODEV; + + cfg = dmi_id->driver_data; + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->multiplier = cfg->multiplier; + + for (i = 0; i < cfg->fan_count; i++) { + acpi_status status; + + status = acpi_get_handle(NULL, (char *)cfg->paths[i], + &data->active_handles[data->fan_count]); + if (ACPI_SUCCESS(status)) + data->fan_count++; + } + + if (data->fan_count == 0) + return -ENODEV; + + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, DRVNAME, + data, &yoga_fan_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct platform_driver yoga_fan_driver = { + .driver = { .name = DRVNAME }, + .probe = yoga_fan_probe, +}; + +static struct platform_device *yoga_fan_device; + +static int __init yoga_fan_init(void) +{ + int ret; + + if (!dmi_check_system(yogafan_quirks)) + return -ENODEV; + + ret = platform_driver_register(&yoga_fan_driver); + if (ret) + return ret; + + yoga_fan_device = platform_device_register_simple(DRVNAME, -1, NULL, 0); + if (IS_ERR(yoga_fan_device)) { + platform_driver_unregister(&yoga_fan_driver); + return PTR_ERR(yoga_fan_device); + } + return 0; +} + +static void __exit yoga_fan_exit(void) +{ + platform_device_unregister(yoga_fan_device); + platform_driver_unregister(&yoga_fan_driver); +} + +module_init(yoga_fan_init); +module_exit(yoga_fan_exit); + +MODULE_AUTHOR("Sergio Melas <sergiomelas@gmail.com>"); +MODULE_DESCRIPTION("Lenovo Yoga/Legion Fan Monitor Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c index 857e1b69d6cd..c6cded508738 100644 --- a/drivers/iio/adc/ina2xx-adc.c +++ b/drivers/iio/adc/ina2xx-adc.c @@ -33,8 +33,6 @@ #include <linux/sched/task.h> #include <linux/util_macros.h> -#include <linux/platform_data/ina2xx.h> - /* INA2XX registers definition */ #define INA2XX_CONFIG 0x00 #define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */ @@ -980,16 +978,8 @@ static int ina2xx_probe(struct i2c_client *client) mutex_init(&chip->state_lock); - if (of_property_read_u32(client->dev.of_node, - "shunt-resistor", &val) < 0) { - struct ina2xx_platform_data *pdata = - dev_get_platdata(&client->dev); - - if (pdata) - val = pdata->shunt_uohms; - else - val = INA2XX_RSHUNT_DEFAULT; - } + if (of_property_read_u32(client->dev.of_node, "shunt-resistor", &val) < 0) + val = INA2XX_RSHUNT_DEFAULT; ret = set_shunt_resistor(chip, val); if (ret) |
