diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-06-01 21:33:40 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-06-01 21:33:40 +0300 |
commit | 129b9a5c40582cb0dc00aa5b58d1d1bcc93d98a7 (patch) | |
tree | a12a8d9f356d1c52bb27bb3bdaebd8b8483119df /drivers | |
parent | b6f91ab6a2bac8580026fc4a5d4724f0b9eeb11f (diff) | |
parent | 87976ce2825d9f1ca2e70ee7d38dec490ad5a6e2 (diff) | |
download | linux-129b9a5c40582cb0dc00aa5b58d1d1bcc93d98a7.tar.xz |
Merge tag 'hwmon-for-v5.8' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging
Pull hwmon updates from Guenter Roeck:
"Infrastructure:
- Add notification support
New drivers:
- Baikal-T1 PVT sensor driver
- amd_energy driver to report energy counters
- Driver for Maxim MAX16601
- Gateworks System Controller
Various:
- applesmc: avoid overlong udelay()
- dell-smm: Use one DMI match for all XPS models
- ina2xx: Implement alert functions
- lm70: Add support for ACPI
- lm75: Fix coding-style warnings
- lm90: Add max6654 support to lm90 driver
- nct7802: Replace container_of() API
- nct7904: Set default timeout
- nct7904: Add watchdog function
- pmbus: Improve initialization of 'currpage' and 'currphase'"
* tag 'hwmon-for-v5.8' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (24 commits)
hwmon: Add Baikal-T1 PVT sensor driver
hwmon: Add notification support
dt-bindings: hwmon: Add Baikal-T1 PVT sensor binding
hwmon: (applesmc) avoid overlong udelay()
hwmon: (nct7904) Set default timeout
hwmon: (amd_energy) Missing platform_driver_unregister() on error in amd_energy_init()
MAINTAINERS: add entry for AMD energy driver
hwmon: (amd_energy) Add documentation
hwmon: Add amd_energy driver to report energy counters
hwmon: (nct7802) Replace container_of() API
hwmon: (lm90) Add max6654 support to lm90 driver
hwmon : (nct6775) Use kobj_to_dev() API
hwmon: (pmbus) Driver for Maxim MAX16601
hwmon: (pmbus) Improve initialization of 'currpage' and 'currphase'
hwmon: (adt7411) update contact email
hwmon: (lm75) Fix all coding-style warnings on lm75 driver
hwmon: Reduce indentation level in __hwmon_device_register()
hwmon: (ina2xx) Implement alert functions
hwmon: (lm70) Add support for ACPI
hwmon: (dell-smm) Use one DMI match for all XPS models
...
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/hwmon/Kconfig | 59 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 3 | ||||
-rw-r--r-- | drivers/hwmon/adt7411.c | 3 | ||||
-rw-r--r-- | drivers/hwmon/amd_energy.c | 408 | ||||
-rw-r--r-- | drivers/hwmon/applesmc.c | 12 | ||||
-rw-r--r-- | drivers/hwmon/bt1-pvt.c | 1146 | ||||
-rw-r--r-- | drivers/hwmon/bt1-pvt.h | 244 | ||||
-rw-r--r-- | drivers/hwmon/dell-smm-hwmon.c | 26 | ||||
-rw-r--r-- | drivers/hwmon/gsc-hwmon.c | 390 | ||||
-rw-r--r-- | drivers/hwmon/hwmon.c | 136 | ||||
-rw-r--r-- | drivers/hwmon/ina2xx.c | 183 | ||||
-rw-r--r-- | drivers/hwmon/lm70.c | 47 | ||||
-rw-r--r-- | drivers/hwmon/lm75.c | 8 | ||||
-rw-r--r-- | drivers/hwmon/lm75.h | 31 | ||||
-rw-r--r-- | drivers/hwmon/lm90.c | 45 | ||||
-rw-r--r-- | drivers/hwmon/nct6775.c | 10 | ||||
-rw-r--r-- | drivers/hwmon/nct7802.c | 6 | ||||
-rw-r--r-- | drivers/hwmon/nct7904.c | 138 | ||||
-rw-r--r-- | drivers/hwmon/pmbus/Kconfig | 9 | ||||
-rw-r--r-- | drivers/hwmon/pmbus/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/pmbus/max16601.c | 314 | ||||
-rw-r--r-- | drivers/hwmon/pmbus/pmbus_core.c | 8 | ||||
-rw-r--r-- | drivers/mfd/Kconfig | 15 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/gateworks-gsc.c | 277 |
25 files changed, 3416 insertions, 104 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 4c62f900bf7e..288ae9f63588 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -324,6 +324,16 @@ config SENSORS_FAM15H_POWER This driver can also be built as a module. If so, the module will be called fam15h_power. +config SENSORS_AMD_ENERGY + tristate "AMD RAPL MSR based Energy driver" + depends on X86 + help + If you say yes here you get support for core and package energy + sensors, based on RAPL MSR for AMD family 17h and above CPUs. + + This driver can also be built as a module. If so, the module + will be called as amd_energy. + config SENSORS_APPLESMC tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" depends on INPUT && X86 @@ -404,6 +414,31 @@ 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 + 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_DRIVETEMP tristate "Hard disk drives with temperature sensors" depends on SCSI && ATA @@ -523,6 +558,15 @@ config SENSORS_F75375S This driver can also be built as a module. If so, the module will be called f75375s. +config SENSORS_GSC + tristate "Gateworks System Controller ADC" + depends on MFD_GATEWORKS_GSC + help + Support for the Gateworks System Controller A/D converters. + + To compile this driver as a module, choose M here: + the module will be called gsc-hwmon. + config SENSORS_MC13783_ADC tristate "Freescale MC13783/MC13892 ADC" depends on MFD_MC13XXX @@ -1198,10 +1242,11 @@ config SENSORS_LM90 help If you say yes here you get support for National Semiconductor LM90, LM86, LM89 and LM99, Analog Devices ADM1032, ADT7461, and ADT7461A, - Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6657, MAX6658, MAX6659, - MAX6680, MAX6681, MAX6692, MAX6695, MAX6696, ON Semiconductor NCT1008, - Winbond/Nuvoton W83L771W/G/AWG/ASG, Philips SA56004, GMT G781, and - Texas Instruments TMP451 sensor chips. + Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6654, MAX6657, MAX6658, + MAX6659, MAX6680, MAX6681, MAX6692, MAX6695, MAX6696, + ON Semiconductor NCT1008, Winbond/Nuvoton W83L771W/G/AWG/ASG, + Philips SA56004, GMT G781, and Texas Instruments TMP451 + sensor chips. This driver can also be built as a module. If so, the module will be called lm90. @@ -1340,10 +1385,12 @@ config SENSORS_NCT7802 config SENSORS_NCT7904 tristate "Nuvoton NCT7904" - depends on I2C + depends on I2C && WATCHDOG + select WATCHDOG_CORE help If you say yes here you get support for the Nuvoton NCT7904 - hardware monitoring chip, including manual fan speed control. + hardware monitoring chip, including manual fan speed control + and support for the integrated watchdog. This driver can also be built as a module. If so, the module will be called nct7904. diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index b0b9c8e57176..3e32c21f5efe 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o +obj-$(CONFIG_SENSORS_AMD_ENERGY) += amd_energy.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o @@ -53,6 +54,7 @@ obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.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_CORETEMP) += coretemp.o obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o @@ -74,6 +76,7 @@ obj-$(CONFIG_SENSORS_G760A) += g760a.o obj-$(CONFIG_SENSORS_G762) += g762.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o +obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o diff --git a/drivers/hwmon/adt7411.c b/drivers/hwmon/adt7411.c index c7010b91bc13..5a839cc2ed1c 100644 --- a/drivers/hwmon/adt7411.c +++ b/drivers/hwmon/adt7411.c @@ -716,7 +716,6 @@ static struct i2c_driver adt7411_driver = { module_i2c_driver(adt7411_driver); -MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de> and " - "Wolfram Sang <w.sang@pengutronix.de>"); +MODULE_AUTHOR("Sascha Hauer, Wolfram Sang <kernel@pengutronix.de>"); MODULE_DESCRIPTION("ADT7411 driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/amd_energy.c b/drivers/hwmon/amd_energy.c new file mode 100644 index 000000000000..e95b7426106e --- /dev/null +++ b/drivers/hwmon/amd_energy.c @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Copyright (C) 2020 Advanced Micro Devices, Inc. + */ +#include <asm/cpu_device_id.h> + +#include <linux/bits.h> +#include <linux/cpu.h> +#include <linux/cpumask.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/hwmon.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/processor.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/topology.h> +#include <linux/types.h> + +#define DRVNAME "amd_energy" + +#define ENERGY_PWR_UNIT_MSR 0xC0010299 +#define ENERGY_CORE_MSR 0xC001029A +#define ENERGY_PKG_MSR 0xC001029B + +#define AMD_ENERGY_UNIT_MASK 0x01F00 +#define AMD_ENERGY_MASK 0xFFFFFFFF + +struct sensor_accumulator { + u64 energy_ctr; + u64 prev_value; + char label[10]; +}; + +struct amd_energy_data { + struct hwmon_channel_info energy_info; + const struct hwmon_channel_info *info[2]; + struct hwmon_chip_info chip; + struct task_struct *wrap_accumulate; + /* Lock around the accumulator */ + struct mutex lock; + /* An accumulator for each core and socket */ + struct sensor_accumulator *accums; + /* Energy Status Units */ + u64 energy_units; + int nr_cpus; + int nr_socks; + int core_id; +}; + +static int amd_energy_read_labels(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, + const char **str) +{ + struct amd_energy_data *data = dev_get_drvdata(dev); + + *str = data->accums[channel].label; + return 0; +} + +static void get_energy_units(struct amd_energy_data *data) +{ + u64 rapl_units; + + rdmsrl_safe(ENERGY_PWR_UNIT_MSR, &rapl_units); + data->energy_units = (rapl_units & AMD_ENERGY_UNIT_MASK) >> 8; +} + +static void accumulate_socket_delta(struct amd_energy_data *data, + int sock, int cpu) +{ + struct sensor_accumulator *s_accum; + u64 input; + + mutex_lock(&data->lock); + rdmsrl_safe_on_cpu(cpu, ENERGY_PKG_MSR, &input); + input &= AMD_ENERGY_MASK; + + s_accum = &data->accums[data->nr_cpus + sock]; + if (input >= s_accum->prev_value) + s_accum->energy_ctr += + input - s_accum->prev_value; + else + s_accum->energy_ctr += UINT_MAX - + s_accum->prev_value + input; + + s_accum->prev_value = input; + mutex_unlock(&data->lock); +} + +static void accumulate_core_delta(struct amd_energy_data *data) +{ + struct sensor_accumulator *c_accum; + u64 input; + int cpu; + + mutex_lock(&data->lock); + if (data->core_id >= data->nr_cpus) + data->core_id = 0; + + cpu = data->core_id; + + if (!cpu_online(cpu)) + goto out; + + rdmsrl_safe_on_cpu(cpu, ENERGY_CORE_MSR, &input); + input &= AMD_ENERGY_MASK; + + c_accum = &data->accums[cpu]; + + if (input >= c_accum->prev_value) + c_accum->energy_ctr += + input - c_accum->prev_value; + else + c_accum->energy_ctr += UINT_MAX - + c_accum->prev_value + input; + + c_accum->prev_value = input; + +out: + data->core_id++; + mutex_unlock(&data->lock); +} + +static void read_accumulate(struct amd_energy_data *data) +{ + int sock; + + for (sock = 0; sock < data->nr_socks; sock++) { + int cpu; + + cpu = cpumask_first_and(cpu_online_mask, + cpumask_of_node(sock)); + + accumulate_socket_delta(data, sock, cpu); + } + + accumulate_core_delta(data); +} + +static void amd_add_delta(struct amd_energy_data *data, int ch, + int cpu, long *val, bool is_core) +{ + struct sensor_accumulator *s_accum, *c_accum; + u64 input; + + mutex_lock(&data->lock); + if (!is_core) { + rdmsrl_safe_on_cpu(cpu, ENERGY_PKG_MSR, &input); + input &= AMD_ENERGY_MASK; + + s_accum = &data->accums[ch]; + if (input >= s_accum->prev_value) + input += s_accum->energy_ctr - + s_accum->prev_value; + else + input += UINT_MAX - s_accum->prev_value + + s_accum->energy_ctr; + } else { + rdmsrl_safe_on_cpu(cpu, ENERGY_CORE_MSR, &input); + input &= AMD_ENERGY_MASK; + + c_accum = &data->accums[ch]; + if (input >= c_accum->prev_value) + input += c_accum->energy_ctr - + c_accum->prev_value; + else + input += UINT_MAX - c_accum->prev_value + + c_accum->energy_ctr; + } + + /* Energy consumed = (1/(2^ESU) * RAW * 1000000UL) μJoules */ + *val = div64_ul(input * 1000000UL, BIT(data->energy_units)); + + mutex_unlock(&data->lock); +} + +static int amd_energy_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct amd_energy_data *data = dev_get_drvdata(dev); + int cpu; + + if (channel >= data->nr_cpus) { + cpu = cpumask_first_and(cpu_online_mask, + cpumask_of_node + (channel - data->nr_cpus)); + amd_add_delta(data, channel, cpu, val, false); + } else { + cpu = channel; + if (!cpu_online(cpu)) + return -ENODEV; + + amd_add_delta(data, channel, cpu, val, true); + } + + return 0; +} + +static umode_t amd_energy_is_visible(const void *_data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return 0444; +} + +static int energy_accumulator(void *p) +{ + struct amd_energy_data *data = (struct amd_energy_data *)p; + + while (!kthread_should_stop()) { + /* + * Ignoring the conditions such as + * cpu being offline or rdmsr failure + */ + read_accumulate(data); + + set_current_state(TASK_INTERRUPTIBLE); + if (kthread_should_stop()) + break; + + /* + * On a 240W system, with default resolution the + * Socket Energy status register may wrap around in + * 2^32*15.3 e-6/240 = 273.8041 secs (~4.5 mins) + * + * let us accumulate for every 100secs + */ + schedule_timeout(msecs_to_jiffies(100000)); + } + return 0; +} + +static const struct hwmon_ops amd_energy_ops = { + .is_visible = amd_energy_is_visible, + .read = amd_energy_read, + .read_string = amd_energy_read_labels, +}; + +static int amd_create_sensor(struct device *dev, + struct amd_energy_data *data, + u8 type, u32 config) +{ + struct hwmon_channel_info *info = &data->energy_info; + struct sensor_accumulator *accums; + int i, num_siblings, cpus, sockets; + u32 *s_config; + + /* Identify the number of siblings per core */ + num_siblings = ((cpuid_ebx(0x8000001e) >> 8) & 0xff) + 1; + + sockets = num_possible_nodes(); + + /* + * Energy counter register is accessed at core level. + * Hence, filterout the siblings. + */ + cpus = num_present_cpus() / num_siblings; + + s_config = devm_kcalloc(dev, cpus + sockets, + sizeof(u32), GFP_KERNEL); + if (!s_config) + return -ENOMEM; + + accums = devm_kcalloc(dev, cpus + sockets, + sizeof(struct sensor_accumulator), + GFP_KERNEL); + if (!accums) + return -ENOMEM; + + info->type = type; + info->config = s_config; + + data->nr_cpus = cpus; + data->nr_socks = sockets; + data->accums = accums; + + for (i = 0; i < cpus + sockets; i++) { + s_config[i] = config; + if (i < cpus) + scnprintf(accums[i].label, 10, + "Ecore%03u", i); + else + scnprintf(accums[i].label, 10, + "Esocket%u", (i - cpus)); + } + + return 0; +} + +static int amd_energy_probe(struct platform_device *pdev) +{ + struct device *hwmon_dev; + struct amd_energy_data *data; + struct device *dev = &pdev->dev; + + data = devm_kzalloc(dev, + sizeof(struct amd_energy_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->chip.ops = &amd_energy_ops; + data->chip.info = data->info; + + dev_set_drvdata(dev, data); + /* Populate per-core energy reporting */ + data->info[0] = &data->energy_info; + amd_create_sensor(dev, data, hwmon_energy, + HWMON_E_INPUT | HWMON_E_LABEL); + + mutex_init(&data->lock); + get_energy_units(data); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, DRVNAME, + data, + &data->chip, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + data->wrap_accumulate = kthread_run(energy_accumulator, data, + "%s", dev_name(hwmon_dev)); + if (IS_ERR(data->wrap_accumulate)) + return PTR_ERR(data->wrap_accumulate); + + return PTR_ERR_OR_ZERO(data->wrap_accumulate); +} + +static int amd_energy_remove(struct platform_device *pdev) +{ + struct amd_energy_data *data = dev_get_drvdata(&pdev->dev); + + if (data && data->wrap_accumulate) + kthread_stop(data->wrap_accumulate); + + return 0; +} + +static const struct platform_device_id amd_energy_ids[] = { + { .name = DRVNAME, }, + {} +}; +MODULE_DEVICE_TABLE(platform, amd_energy_ids); + +static struct platform_driver amd_energy_driver = { + .probe = amd_energy_probe, + .remove = amd_energy_remove, + .id_table = amd_energy_ids, + .driver = { + .name = DRVNAME, + }, +}; + +static struct platform_device *amd_energy_platdev; + +static const struct x86_cpu_id cpu_ids[] __initconst = { + X86_MATCH_VENDOR_FAM(AMD, 0x17, NULL), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, cpu_ids); + +static int __init amd_energy_init(void) +{ + int ret; + + if (!x86_match_cpu(cpu_ids)) + return -ENODEV; + + ret = platform_driver_register(&amd_energy_driver); + if (ret) + return ret; + + amd_energy_platdev = platform_device_alloc(DRVNAME, 0); + if (!amd_energy_platdev) { + platform_driver_unregister(&amd_energy_driver); + return -ENOMEM; + } + + ret = platform_device_add(amd_energy_platdev); + if (ret) { + platform_device_put(amd_energy_platdev); + platform_driver_unregister(&amd_energy_driver); + return ret; + } + + return ret; +} + +static void __exit amd_energy_exit(void) +{ + platform_device_unregister(amd_energy_platdev); + platform_driver_unregister(&amd_energy_driver); +} + +module_init(amd_energy_init); +module_exit(amd_energy_exit); + +MODULE_DESCRIPTION("Driver for AMD Energy reporting from RAPL MSR via HWMON interface"); +MODULE_AUTHOR("Naveen Krishna Chatradhi <nchatrad@amd.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index ec93b8d673f5..316618409315 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -156,14 +156,19 @@ static struct workqueue_struct *applesmc_led_wq; */ static int wait_read(void) { + unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC; u8 status; int us; + for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) { - udelay(us); + usleep_range(us, us * 16); status = inb(APPLESMC_CMD_PORT); /* read: wait for smc to settle */ if (status & 0x01) return 0; + /* timeout: give up */ + if (time_after(jiffies, end)) + break; } pr_warn("wait_read() fail: 0x%02x\n", status); @@ -178,10 +183,11 @@ static int send_byte(u8 cmd, u16 port) { u8 status; int us; + unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC; outb(cmd, port); for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) { - udelay(us); + usleep_range(us, us * 16); status = inb(APPLESMC_CMD_PORT); /* write: wait for smc to settle */ if (status & 0x02) @@ -190,7 +196,7 @@ static int send_byte(u8 cmd, u16 port) if (status & 0x04) return 0; /* timeout: give up */ - if (us << 1 == APPLESMC_MAX_WAIT) + if (time_after(jiffies, end)) break; /* busy: long wait and resend */ udelay(APPLESMC_RETRY_WAIT); diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c new file mode 100644 index 000000000000..1a9772fb1f73 --- /dev/null +++ b/drivers/hwmon/bt1-pvt.c @@ -0,0 +1,1146 @@ +// 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/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/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 pvt_poly 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 pvt_poly 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 pvt_poly poly_volt_to_N = { + .total_divider = 10, + .terms = { + {1, 18658, 1000, 1}, + {0, -11572, 1, 1} + } +}; + +static const struct pvt_poly poly_N_to_volt = { + .total_divider = 10, + .terms = { + {1, 100000, 18658, 1}, + {0, 115720000, 1, 18658} + } +}; + +/* + * Here is the polynomial calculation function, which performs the + * redistributed terms calculations. It's pretty straightforward. We walk + * over each degree term up to the free one, and perform the redistributed + * multiplication of the term coefficient, its divider (as for the rationale + * fraction representation), data power and the rational fraction divider + * leftover. Then all of this is collected in a total sum variable, which + * value is normalized by the total divider before being returned. + */ +static long pvt_calc_poly(const struct pvt_poly *poly, long data) +{ + const struct pvt_poly_term *term = poly->terms; + long tmp, ret = 0; + int deg; + + do { + tmp = term->coef; + for (deg = 0; deg < term->deg; ++deg) + tmp = mult_frac(tmp, data, term->divider); + ret += tmp / term->divider_leftover; + } while ((term++)->deg); + + return ret / poly->total_divider; +} + +static inline u32 pvt_update(void __iomem *reg, u32 mask, u32 data) +{ + u32 old; + + 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; +} + +inline umode_t pvt_limit_is_visible(enum pvt_sensor_type type) +{ + return 0644; +} + +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 = pvt_calc_poly(&poly_N_to_temp, data); + else + *val = pvt_calc_poly(&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 = pvt_calc_poly(&poly_N_to_temp, data); + else + *val = pvt_calc_poly(&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 = pvt_calc_poly(&poly_temp_to_N, val); + } else { + val = clamp(val, PVT_VOLT_MIN, PVT_VOLT_MAX); + data = pvt_calc_poly(&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 *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 + +inline umode_t pvt_limit_is_visible(enum pvt_sensor_type type) +{ + return 0; +} + +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]; + 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_for_completion(&cache->conversion); + + 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 (type == PVT_TEMP) + *val = pvt_calc_poly(&poly_N_to_temp, data); + else + *val = pvt_calc_poly(&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 *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) +{ + unsigned long rate; + ktime_t kt; + u32 data; + + rate = clk_get_rate(pvt->clks[PVT_CLOCK_REF].clk); + if (!rate) + return -ENODEV; + + /* + * Don't bother with mutex here, since we just read data from MMIO. + * We also have to scale the ticks timeout up to compensate the + * ms-ns-data translations. + */ + data = readl(pvt->regs + PVT_TTIMEOUT) + 1; + + /* + * Calculate ref-clock based delay (Ttotal) between two consecutive + * data samples of the same sensor. So we first must calculate the + * delay introduced by the internal ref-clock timer (Tref * Fclk). + * Then add the constant timeout cuased by each conversion latency + * (Tmin). The basic formulae for each conversion is following: + * Ttotal = Tref * Fclk + Tmin + * Note if alarms are enabled the sensors are polled one after + * another, so in order to have the delay being applicable for each + * sensor the requested value must be equally redistirbuted. + */ +#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS) + kt = ktime_set(PVT_SENSORS_NUM * (u64)data, 0); + kt = ktime_divns(kt, rate); + kt = ktime_add_ns(kt, PVT_SENSORS_NUM * PVT_TOUT_MIN); +#else + kt = ktime_set(data, 0); + kt = ktime_divns(kt, rate); + kt = ktime_add_ns(kt, PVT_TOUT_MIN); +#endif + + /* Return the result in msec as hwmon sysfs interface requires. */ + *val = ktime_to_ms(kt); + + return 0; +} + +static int pvt_write_timeout(struct pvt_hwmon *pvt, long val) +{ + unsigned long rate; + ktime_t kt; + 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. + */ + 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); + + 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); + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(pvt->dev, "Couldn't find PVT memresource\n"); + return -EINVAL; + } + + pvt->regs = devm_ioremap_resource(pvt->dev, res); + if (IS_ERR(pvt->regs)) { + dev_err(pvt->dev, "Couldn't map PVT registers\n"); + 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 void pvt_init_iface(struct pvt_hwmon *pvt) +{ + u32 trim, temp; + + /* + * 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); + + 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); +} + +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; + + pvt_init_iface(pvt); + + 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 new file mode 100644 index 000000000000..5eac73e94885 --- /dev/null +++ b/drivers/hwmon/bt1-pvt.h @@ -0,0 +1,244 @@ +/* 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/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. + */ +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]; +}; + +/* + * 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/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index ab719d372b0d..16be012a95ed 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1073,13 +1073,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = { }, }, { - .ident = "Dell XPS421", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"), - }, - }, - { .ident = "Dell Studio", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), @@ -1088,14 +1081,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = { .driver_data = (void *)&i8k_config_data[DELL_STUDIO], }, { - .ident = "Dell XPS 13", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"), - }, - .driver_data = (void *)&i8k_config_data[DELL_XPS], - }, - { .ident = "Dell XPS M140", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), @@ -1104,17 +1089,10 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = { .driver_data = (void *)&i8k_config_data[DELL_XPS], }, { - .ident = "Dell XPS 15 9560", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "XPS 15 9560"), - }, - }, - { - .ident = "Dell XPS 15 9570", + .ident = "Dell XPS", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "XPS 15 9570"), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS"), }, }, { } diff --git a/drivers/hwmon/gsc-hwmon.c b/drivers/hwmon/gsc-hwmon.c new file mode 100644 index 000000000000..2137bc65829d --- /dev/null +++ b/drivers/hwmon/gsc-hwmon.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Gateworks System Controller Hardware Monitor module + * + * Copyright (C) 2020 Gateworks Corporation + */ +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/mfd/gsc.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include <linux/platform_data/gsc_hwmon.h> + +#define GSC_HWMON_MAX_TEMP_CH 16 +#define GSC_HWMON_MAX_IN_CH 16 + +#define GSC_HWMON_RESOLUTION 12 +#define GSC_HWMON_VREF 2500 + +struct gsc_hwmon_data { + struct gsc_dev *gsc; + struct gsc_hwmon_platform_data *pdata; + struct regmap *regmap; + const struct gsc_hwmon_channel *temp_ch[GSC_HWMON_MAX_TEMP_CH]; + const struct gsc_hwmon_channel *in_ch[GSC_HWMON_MAX_IN_CH]; + u32 temp_config[GSC_HWMON_MAX_TEMP_CH + 1]; + u32 in_config[GSC_HWMON_MAX_IN_CH + 1]; + struct hwmon_channel_info temp_info; + struct hwmon_channel_info in_info; + const struct hwmon_channel_info *info[3]; + struct hwmon_chip_info chip; +}; + +static struct regmap_bus gsc_hwmon_regmap_bus = { + .reg_read = gsc_read, + .reg_write = gsc_write, +}; + +static const struct regmap_config gsc_hwmon_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_NONE, +}; + +static ssize_t pwm_auto_point_temp_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + u8 reg = hwmon->pdata->fan_base + (2 * attr->index); + u8 regs[2]; + int ret; + + ret = regmap_bulk_read(hwmon->regmap, reg, regs, 2); + if (ret) + return ret; + + ret = regs[0] | regs[1] << 8; + return sprintf(buf, "%d\n", ret * 10); +} + +static ssize_t pwm_auto_point_temp_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + u8 reg = hwmon->pdata->fan_base + (2 * attr->index); + u8 regs[2]; + long temp; + int err; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, 0, 10000); + temp = DIV_ROUND_CLOSEST(temp, 10); + + regs[0] = temp & 0xff; + regs[1] = (temp >> 8) & 0xff; + err = regmap_bulk_write(hwmon->regmap, reg, regs, 2); + if (err) + return err; + + return count; +} + +static ssize_t pwm_auto_point_pwm_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%d\n", 255 * (50 + (attr->index * 10)) / 100); +} + +static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point1_pwm, pwm_auto_point_pwm, 0); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, pwm_auto_point_temp, 0); + +static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point2_pwm, pwm_auto_point_pwm, 1); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, pwm_auto_point_temp, 1); + +static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point3_pwm, pwm_auto_point_pwm, 2); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_auto_point_temp, 2); + +static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point4_pwm, pwm_auto_point_pwm, 3); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_auto_point_temp, 3); + +static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point5_pwm, pwm_auto_point_pwm, 4); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_auto_point_temp, 4); + +static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point6_pwm, pwm_auto_point_pwm, 5); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point6_temp, pwm_auto_point_temp, 5); + +static struct attribute *gsc_hwmon_attributes[] = { + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr, + NULL +}; + +static const struct attribute_group gsc_hwmon_group = { + .attrs = gsc_hwmon_attributes, +}; +__ATTRIBUTE_GROUPS(gsc_hwmon); + +static int +gsc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev); + const struct gsc_hwmon_channel *ch; + int sz, ret; + long tmp; + u8 buf[3]; + + switch (type) { + case hwmon_in: + ch = hwmon->in_ch[channel]; + break; + case hwmon_temp: + ch = hwmon->temp_ch[channel]; + break; + default: + return -EOPNOTSUPP; + } + + sz = (ch->mode == mode_voltage) ? 3 : 2; + ret = regmap_bulk_read(hwmon->regmap, ch->reg, buf, sz); + if (ret) + return ret; + + tmp = 0; + while (sz-- > 0) + tmp |= (buf[sz] << (8 * sz)); + + switch (ch->mode) { + case mode_temperature: + if (tmp > 0x8000) + tmp -= 0xffff; + break; + case mode_voltage_raw: + tmp = clamp_val(tmp, 0, BIT(GSC_HWMON_RESOLUTION)); + /* scale based on ref voltage and ADC resolution */ + tmp *= GSC_HWMON_VREF; + tmp >>= GSC_HWMON_RESOLUTION; + /* scale based on optional voltage divider */ + if (ch->vdiv[0] && ch->vdiv[1]) { + tmp *= (ch->vdiv[0] + ch->vdiv[1]); + tmp /= ch->vdiv[1]; + } + /* adjust by uV offset */ + tmp += ch->mvoffset; + break; + case mode_voltage: + /* no adjustment needed */ + break; + } + + *val = tmp; + + return 0; +} + +static int +gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **buf) +{ + struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev); + + switch (type) { + case hwmon_in: + *buf = hwmon->in_ch[channel]->name; + break; + case hwmon_temp: + *buf = hwmon->temp_ch[channel]->name; + break; + default: + return -ENOTSUPP; + } + + return 0; +} + +static umode_t +gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr, + int ch) +{ + return 0444; +} + +static const struct hwmon_ops gsc_hwmon_ops = { + .is_visible = gsc_hwmon_is_visible, + .read = gsc_hwmon_read, + .read_string = gsc_hwmon_read_string, +}; + +static struct gsc_hwmon_platform_data * +gsc_hwmon_get_devtree_pdata(struct device *dev) +{ + struct gsc_hwmon_platform_data *pdata; + struct gsc_hwmon_channel *ch; + struct fwnode_handle *child; + struct device_node *fan; + int nchannels; + + nchannels = device_get_child_node_count(dev); + if (nchannels == 0) + return ERR_PTR(-ENODEV); + + pdata = devm_kzalloc(dev, + sizeof(*pdata) + nchannels * sizeof(*ch), + GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + ch = (struct gsc_hwmon_channel *)(pdata + 1); + pdata->channels = ch; + pdata->nchannels = nchannels; + + /* fan controller base address */ + fan = of_find_compatible_node(dev->parent->of_node, NULL, "gw,gsc-fan"); + if (fan && of_property_read_u32(fan, "reg", &pdata->fan_base)) { + dev_err(dev, "fan node without base\n"); + return ERR_PTR(-EINVAL); + } + + /* allocate structures for channels and count instances of each type */ + device_for_each_child_node(dev, child) { + if (fwnode_property_read_string(child, "label", &ch->name)) { + dev_err(dev, "channel without label\n"); + fwnode_handle_put(child); + return ERR_PTR(-EINVAL); + } + if (fwnode_property_read_u32(child, "reg", &ch->reg)) { + dev_err(dev, "channel without reg\n"); + fwnode_handle_put(child); + return ERR_PTR(-EINVAL); + } + if (fwnode_property_read_u32(child, "gw,mode", &ch->mode)) { + dev_err(dev, "channel without mode\n"); + fwnode_handle_put(child); + return ERR_PTR(-EINVAL); + } + if (ch->mode > mode_max) { + dev_err(dev, "invalid channel mode\n"); + fwnode_handle_put(child); + return ERR_PTR(-EINVAL); + } + + if (!fwnode_property_read_u32(child, + "gw,voltage-offset-microvolt", + &ch->mvoffset)) + ch->mvoffset /= 1000; + fwnode_property_read_u32_array(child, + "gw,voltage-divider-ohms", + ch->vdiv, ARRAY_SIZE(ch->vdiv)); + ch++; + } + + return pdata; +} + +static int gsc_hwmon_probe(struct platform_device *pdev) +{ + struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct device *hwmon_dev; + struct gsc_hwmon_platform_data *pdata = dev_get_platdata(dev); + struct gsc_hwmon_data *hwmon; + const struct attribute_group **groups; + int i, i_in, i_temp; + + if (!pdata) { + pdata = gsc_hwmon_get_devtree_pdata(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } + + hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + hwmon->gsc = gsc; + hwmon->pdata = pdata; + + hwmon->regmap = devm_regmap_init(dev, &gsc_hwmon_regmap_bus, + gsc->i2c_hwmon, + &gsc_hwmon_regmap_config); + if (IS_ERR(hwmon->regmap)) + return PTR_ERR(hwmon->regmap); + + for (i = 0, i_in = 0, i_temp = 0; i < hwmon->pdata->nchannels; i++) { + const struct gsc_hwmon_channel *ch = &pdata->channels[i]; + + switch (ch->mode) { + case mode_temperature: + if (i_temp == GSC_HWMON_MAX_TEMP_CH) { + dev_err(gsc->dev, "too many temp channels\n"); + return -EINVAL; + } + hwmon->temp_ch[i_temp] = ch; + hwmon->temp_config[i_temp] = HWMON_T_INPUT | + HWMON_T_LABEL; + i_temp++; + break; + case mode_voltage: + case mode_voltage_raw: + if (i_in == GSC_HWMON_MAX_IN_CH) { + dev_err(gsc->dev, "too many input channels\n"); + return -EINVAL; + } + hwmon->in_ch[i_in] = ch; + hwmon->in_config[i_in] = + HWMON_I_INPUT | HWMON_I_LABEL; + i_in++; + break; + default: + dev_err(gsc->dev, "invalid mode: %d\n", ch->mode); + return -EINVAL; + } + } + + /* setup config structures */ + hwmon->chip.ops = &gsc_hwmon_ops; + hwmon->chip.info = hwmon->info; + hwmon->info[0] = &hwmon->temp_info; + hwmon->info[1] = &hwmon->in_info; + hwmon->temp_info.type = hwmon_temp; + hwmon->temp_info.config = hwmon->temp_config; + hwmon->in_info.type = hwmon_in; + hwmon->in_info.config = hwmon->in_config; + + groups = pdata->fan_base ? gsc_hwmon_groups : NULL; + hwmon_dev = devm_hwmon_device_register_with_info(dev, + KBUILD_MODNAME, hwmon, + &hwmon->chip, groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct of_device_id gsc_hwmon_of_match[] = { + { .compatible = "gw,gsc-adc", }, + {} +}; + +static struct platform_driver gsc_hwmon_driver = { + .driver = { + .name = "gsc-hwmon", + .of_match_table = gsc_hwmon_of_match, + }, + .probe = gsc_hwmon_probe, +}; + +module_platform_driver(gsc_hwmon_driver); + +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>"); +MODULE_DESCRIPTION("GSC hardware monitor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 6a30fb453f7a..3f596a5328da 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -15,6 +15,7 @@ #include <linux/gfp.h> #include <linux/hwmon.h> #include <linux/idr.h> +#include <linux/list.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/slab.h> @@ -31,7 +32,7 @@ struct hwmon_device { const char *name; struct device dev; const struct hwmon_chip_info *chip; - + struct list_head tzdata; struct attribute_group group; const struct attribute_group **groups; }; @@ -55,12 +56,12 @@ struct hwmon_device_attribute { /* * Thermal zone information - * In addition to the reference to the hwmon device, - * also provides the sensor index. */ struct hwmon_thermal_data { + struct list_head node; /* hwmon tzdata list entry */ struct device *dev; /* Reference to hwmon device */ int index; /* sensor index */ + struct thermal_zone_device *tzd;/* thermal zone device */ }; static ssize_t @@ -156,10 +157,17 @@ static const struct thermal_zone_of_device_ops hwmon_thermal_ops = { .get_temp = hwmon_thermal_get_temp, }; +static void hwmon_thermal_remove_sensor(void *data) +{ + list_del(data); +} + static int hwmon_thermal_add_sensor(struct device *dev, int index) { + struct hwmon_device *hwdev = to_hwmon_device(dev); struct hwmon_thermal_data *tdata; struct thermal_zone_device *tzd; + int err; tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL); if (!tdata) @@ -177,13 +185,68 @@ static int hwmon_thermal_add_sensor(struct device *dev, int index) if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV)) return PTR_ERR(tzd); + err = devm_add_action(dev, hwmon_thermal_remove_sensor, &tdata->node); + if (err) + return err; + + tdata->tzd = tzd; + list_add(&tdata->node, &hwdev->tzdata); + return 0; } + +static int hwmon_thermal_register_sensors(struct device *dev) +{ + struct hwmon_device *hwdev = to_hwmon_device(dev); + const struct hwmon_chip_info *chip = hwdev->chip; + const struct hwmon_channel_info **info = chip->info; + void *drvdata = dev_get_drvdata(dev); + int i; + + for (i = 1; info[i]; i++) { + int j; + + if (info[i]->type != hwmon_temp) + continue; + + for (j = 0; info[i]->config[j]; j++) { + int err; + + if (!(info[i]->config[j] & HWMON_T_INPUT) || + !chip->ops->is_visible(drvdata, hwmon_temp, + hwmon_temp_input, j)) + continue; + + err = hwmon_thermal_add_sensor(dev, j); + if (err) + return err; + } + } + + return 0; +} + +static void hwmon_thermal_notify(struct device *dev, int index) +{ + struct hwmon_device *hwdev = to_hwmon_device(dev); + struct hwmon_thermal_data *tzdata; + + list_for_each_entry(tzdata, &hwdev->tzdata, node) { + if (tzdata->index == index) { + thermal_zone_device_update(tzdata->tzd, + THERMAL_EVENT_UNSPECIFIED); + } + } +} + #else -static int hwmon_thermal_add_sensor(struct device *dev, int index) +static int hwmon_thermal_register_sensors(struct device *dev) { return 0; } + +static void hwmon_thermal_notify(struct device *dev, int index) { } + #endif /* IS_REACHABLE(CONFIG_THERMAL) && ... */ static int hwmon_attr_base(enum hwmon_sensor_types type) @@ -511,6 +574,35 @@ static const int __templates_size[] = { [hwmon_intrusion] = ARRAY_SIZE(hwmon_intrusion_attr_templates), }; +int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + char sattr[MAX_SYSFS_ATTR_NAME_LENGTH]; + const char * const *templates; + const char *template; + int base; + + if (type >= ARRAY_SIZE(__templates)) + return -EINVAL; + if (attr >= __templates_size[type]) + return -EINVAL; + + templates = __templates[type]; + template = templates[attr]; + + base = hwmon_attr_base(type); + + scnprintf(sattr, MAX_SYSFS_ATTR_NAME_LENGTH, template, base + channel); + sysfs_notify(&dev->kobj, NULL, sattr); + kobject_uevent(&dev->kobj, KOBJ_CHANGE); + + if (type == hwmon_temp) + hwmon_thermal_notify(dev, channel); + + return 0; +} +EXPORT_SYMBOL_GPL(hwmon_notify_event); + static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info) { int i, n; @@ -596,7 +688,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, { struct hwmon_device *hwdev; struct device *hdev; - int i, j, err, id; + int i, err, id; /* Complain about invalid characters in hwmon name attribute */ if (name && (!strlen(name) || strpbrk(name, "-* \t\n"))) @@ -661,33 +753,19 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, if (err) goto free_hwmon; + INIT_LIST_HEAD(&hwdev->tzdata); + if (dev && dev->of_node && chip && chip->ops->read && chip->info[0]->type == hwmon_chip && (chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) { - const struct hwmon_channel_info **info = chip->info; - - for (i = 1; info[i]; i++) { - if (info[i]->type != hwmon_temp) - continue; - - for (j = 0; info[i]->config[j]; j++) { - if (!chip->ops->is_visible(drvdata, hwmon_temp, - hwmon_temp_input, j)) - continue; - if (info[i]->config[j] & HWMON_T_INPUT) { - err = hwmon_thermal_add_sensor(hdev, j); - if (err) { - device_unregister(hdev); - /* - * Don't worry about hwdev; - * hwmon_dev_release(), called - * from device_unregister(), - * will free it. - */ - goto ida_remove; - } - } - } + err = hwmon_thermal_register_sensors(hdev); + if (err) { + device_unregister(hdev); + /* + * Don't worry about hwdev; hwmon_dev_release(), called + * from device_unregister(), will free it. + */ + goto ida_remove; } } diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index e9e78c0b7212..55d474ec7c35 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -74,6 +74,17 @@ #define INA226_READ_AVG(reg) (((reg) & INA226_AVG_RD_MASK) >> 9) #define INA226_SHIFT_AVG(val) ((val) << 9) +/* bit number of alert functions in Mask/Enable Register */ +#define INA226_SHUNT_OVER_VOLTAGE_BIT 15 +#define INA226_SHUNT_UNDER_VOLTAGE_BIT 14 +#define INA226_BUS_OVER_VOLTAGE_BIT 13 +#define INA226_BUS_UNDER_VOLTAGE_BIT 12 +#define INA226_POWER_OVER_LIMIT_BIT 11 + +/* bit mask for alert config bits of Mask/Enable Register */ +#define INA226_ALERT_CONFIG_MASK 0xFC00 +#define INA226_ALERT_FUNCTION_FLAG BIT(4) + /* common attrs, ina226 attrs and NULL */ #define INA2XX_MAX_ATTRIBUTE_GROUPS 3 @@ -303,6 +314,145 @@ static ssize_t ina2xx_value_show(struct device *dev, ina2xx_get_value(data, attr->index, regval)); } +static int ina226_reg_to_alert(struct ina2xx_data *data, u8 bit, u16 regval) +{ + int reg; + + switch (bit) { + case INA226_SHUNT_OVER_VOLTAGE_BIT: + case INA226_SHUNT_UNDER_VOLTAGE_BIT: + reg = INA2XX_SHUNT_VOLTAGE; + break; + case INA226_BUS_OVER_VOLTAGE_BIT: + case INA226_BUS_UNDER_VOLTAGE_BIT: + reg = INA2XX_BUS_VOLTAGE; + break; + case INA226_POWER_OVER_LIMIT_BIT: + reg = INA2XX_POWER; + break; + default: + /* programmer goofed */ + WARN_ON_ONCE(1); + return 0; + } + + return ina2xx_get_value(data, reg, regval); +} + +/* + * Turns alert limit values into register values. + * Opposite of the formula in ina2xx_get_value(). + */ +static s16 ina226_alert_to_reg(struct ina2xx_data *data, u8 bit, int val) +{ + switch (bit) { + case INA226_SHUNT_OVER_VOLTAGE_BIT: + case INA226_SHUNT_UNDER_VOLTAGE_BIT: + val *= data->config->shunt_div; + return clamp_val(val, SHRT_MIN, SHRT_MAX); + case INA226_BUS_OVER_VOLTAGE_BIT: + case INA226_BUS_UNDER_VOLTAGE_BIT: + val = (val * 1000) << data->config->bus_voltage_shift; + val = DIV_ROUND_CLOSEST(val, data->config->bus_voltage_lsb); + return clamp_val(val, 0, SHRT_MAX); + case INA226_POWER_OVER_LIMIT_BIT: + val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW); + return clamp_val(val, 0, USHRT_MAX); + default: + /* programmer goofed */ + WARN_ON_ONCE(1); + return 0; + } +} + +static ssize_t ina226_alert_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina2xx_data *data = dev_get_drvdata(dev); + int regval; + int val = 0; + int ret; + + mutex_lock(&data->config_lock); + ret = regmap_read(data->regmap, INA226_MASK_ENABLE, ®val); + if (ret) + goto abort; + + if (regval & BIT(attr->index)) { + ret = regmap_read(data->regmap, INA226_ALERT_LIMIT, ®val); + if (ret) + goto abort; + val = ina226_reg_to_alert(data, attr->index, regval); + } + + ret = snprintf(buf, PAGE_SIZE, "%d\n", val); +abort: + mutex_unlock(&data->config_lock); + return ret; +} + +static ssize_t ina226_alert_store(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina2xx_data *data = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) + return ret; + + /* + * Clear all alerts first to avoid accidentally triggering ALERT pin + * due to register write sequence. Then, only enable the alert + * if the value is non-zero. + */ + mutex_lock(&data->config_lock); + ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE, + INA226_ALERT_CONFIG_MASK, 0); + if (ret < 0) + goto abort; + + ret = regmap_write(data->regmap, INA226_ALERT_LIMIT, + ina226_alert_to_reg(data, attr->index, val)); + if (ret < 0) + goto abort; + + if (val != 0) { + ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE, + INA226_ALERT_CONFIG_MASK, + BIT(attr->index)); + if (ret < 0) + goto abort; + } + + ret = count; +abort: + mutex_unlock(&data->config_lock); + return ret; +} + +static ssize_t ina226_alarm_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina2xx_data *data = dev_get_drvdata(dev); + int regval; + int alarm = 0; + int ret; + + ret = regmap_read(data->regmap, INA226_MASK_ENABLE, ®val); + if (ret) + return ret; + + alarm = (regval & BIT(attr->index)) && + (regval & INA226_ALERT_FUNCTION_FLAG); + return snprintf(buf, PAGE_SIZE, "%d\n", alarm); +} + /* * In order to keep calibration register value fixed, the product * of current_lsb and shunt_resistor should also be fixed and equal @@ -392,15 +542,38 @@ static ssize_t ina226_interval_show(struct device *dev, /* shunt voltage */ static SENSOR_DEVICE_ATTR_RO(in0_input, ina2xx_value, INA2XX_SHUNT_VOLTAGE); +/* shunt voltage over/under voltage alert setting and alarm */ +static SENSOR_DEVICE_ATTR_RW(in0_crit, ina226_alert, + INA226_SHUNT_OVER_VOLTAGE_BIT); +static SENSOR_DEVICE_ATTR_RW(in0_lcrit, ina226_alert, + INA226_SHUNT_UNDER_VOLTAGE_BIT); +static SENSOR_DEVICE_ATTR_RO(in0_crit_alarm, ina226_alarm, + INA226_SHUNT_OVER_VOLTAGE_BIT); +static SENSOR_DEVICE_ATTR_RO(in0_lcrit_alarm, ina226_alarm, + INA226_SHUNT_UNDER_VOLTAGE_BIT); /* bus voltage */ static SENSOR_DEVICE_ATTR_RO(in1_input, ina2xx_value, INA2XX_BUS_VOLTAGE); +/* bus voltage over/under voltage alert setting and alarm */ +static SENSOR_DEVICE_ATTR_RW(in1_crit, ina226_alert, + INA226_BUS_OVER_VOLTAGE_BIT); +static SENSOR_DEVICE_ATTR_RW(in1_lcrit, ina226_alert, + INA226_BUS_UNDER_VOLTAGE_BIT); +static SENSOR_DEVICE_ATTR_RO(in1_crit_alarm, ina226_alarm, + INA226_BUS_OVER_VOLTAGE_BIT); +static SENSOR_DEVICE_ATTR_RO(in1_lcrit_alarm, ina226_alarm, + INA226_BUS_UNDER_VOLTAGE_BIT); /* calculated current */ static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT); /* calculated power */ static SENSOR_DEVICE_ATTR_RO(power1_input, ina2xx_value, INA2XX_POWER); +/* over-limit power alert setting and alarm */ +static SENSOR_DEVICE_ATTR_RW(power1_crit, ina226_alert, + INA226_POWER_OVER_LIMIT_BIT); +static SENSOR_DEVICE_ATTR_RO(power1_crit_alarm, ina226_alarm, + INA226_POWER_OVER_LIMIT_BIT); /* shunt resistance */ static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ina2xx_shunt, INA2XX_CALIBRATION); @@ -423,6 +596,16 @@ static const struct attribute_group ina2xx_group = { }; static struct attribute *ina226_attrs[] = { + &sensor_dev_attr_in0_crit.dev_attr.attr, + &sensor_dev_attr_in0_lcrit.dev_attr.attr, + &sensor_dev_attr_in0_crit_alarm.dev_attr.attr, + &sensor_dev_attr_in0_lcrit_alarm.dev_attr.attr, + &sensor_dev_attr_in1_crit.dev_attr.attr, + &sensor_dev_attr_in1_lcrit.dev_attr.attr, + &sensor_dev_attr_in1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_in1_lcrit_alarm.dev_attr.attr, + &sensor_dev_attr_power1_crit.dev_attr.attr, + &sensor_dev_attr_power1_crit_alarm.dev_attr.attr, &sensor_dev_attr_update_interval.dev_attr.attr, NULL, }; diff --git a/drivers/hwmon/lm70.c b/drivers/hwmon/lm70.c index 4122e59f0bb4..ae2b84263a44 100644 --- a/drivers/hwmon/lm70.c +++ b/drivers/hwmon/lm70.c @@ -25,7 +25,7 @@ #include <linux/spi/spi.h> #include <linux/slab.h> #include <linux/of_device.h> - +#include <linux/acpi.h> #define DRVNAME "lm70" @@ -148,18 +148,50 @@ static const struct of_device_id lm70_of_ids[] = { MODULE_DEVICE_TABLE(of, lm70_of_ids); #endif +#ifdef CONFIG_ACPI +static const struct acpi_device_id lm70_acpi_ids[] = { + { + .id = "LM000070", + .driver_data = LM70_CHIP_LM70, + }, + { + .id = "TMP00121", + .driver_data = LM70_CHIP_TMP121, + }, + { + .id = "LM000071", + .driver_data = LM70_CHIP_LM71, + }, + { + .id = "LM000074", + .driver_data = LM70_CHIP_LM74, + }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, lm70_acpi_ids); +#endif + static int lm70_probe(struct spi_device *spi) { - const struct of_device_id *match; + const struct of_device_id *of_match; struct device *hwmon_dev; struct lm70 *p_lm70; int chip; - match = of_match_device(lm70_of_ids, &spi->dev); - if (match) - chip = (int)(uintptr_t)match->data; - else - chip = spi_get_device_id(spi)->driver_data; + of_match = of_match_device(lm70_of_ids, &spi->dev); + if (of_match) + chip = (int)(uintptr_t)of_match->data; + else { +#ifdef CONFIG_ACPI + const struct acpi_device_id *acpi_match; + + acpi_match = acpi_match_device(lm70_acpi_ids, &spi->dev); + if (acpi_match) + chip = (int)(uintptr_t)acpi_match->driver_data; + else +#endif + chip = spi_get_device_id(spi)->driver_data; + } /* signaling is SPI_MODE_0 */ if (spi->mode & (SPI_CPOL | SPI_CPHA)) @@ -195,6 +227,7 @@ static struct spi_driver lm70_driver = { .driver = { .name = "lm70", .of_match_table = of_match_ptr(lm70_of_ids), + .acpi_match_table = ACPI_PTR(lm70_acpi_ids), }, .id_table = lm70_ids, .probe = lm70_probe, diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 5e6392294c03..ba0be48aeadd 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -797,8 +797,10 @@ static int lm75_detect(struct i2c_client *new_client, /* First check for LM75A */ if (i2c_smbus_read_byte_data(new_client, 7) == LM75A_ID) { - /* LM75A returns 0xff on unused registers so - just to be sure we check for that too. */ + /* + * LM75A returns 0xff on unused registers so + * just to be sure we check for that too. + */ if (i2c_smbus_read_byte_data(new_client, 4) != 0xff || i2c_smbus_read_byte_data(new_client, 5) != 0xff || i2c_smbus_read_byte_data(new_client, 6) != 0xff) @@ -849,6 +851,7 @@ static int lm75_suspend(struct device *dev) { int status; struct i2c_client *client = to_i2c_client(dev); + status = i2c_smbus_read_byte_data(client, LM75_REG_CONF); if (status < 0) { dev_dbg(&client->dev, "Can't read config? %d\n", status); @@ -863,6 +866,7 @@ static int lm75_resume(struct device *dev) { int status; struct i2c_client *client = to_i2c_client(dev); + status = i2c_smbus_read_byte_data(client, LM75_REG_CONF); if (status < 0) { dev_dbg(&client->dev, "Can't read config? %d\n", status); diff --git a/drivers/hwmon/lm75.h b/drivers/hwmon/lm75.h index b614e6328566..a398171162a8 100644 --- a/drivers/hwmon/lm75.h +++ b/drivers/hwmon/lm75.h @@ -1,17 +1,15 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* - lm75.h - Part of lm_sensors, Linux kernel modules for hardware - monitoring - Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com> - -*/ + * lm75.h - Part of lm_sensors, Linux kernel modules for hardware monitoring + * Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com> + */ /* - This file contains common code for encoding/decoding LM75 type - temperature readings, which are emulated by many of the chips - we support. As the user is unlikely to load more than one driver - which contains this code, we don't worry about the wasted space. -*/ + * This file contains common code for encoding/decoding LM75 type + * temperature readings, which are emulated by many of the chips + * we support. As the user is unlikely to load more than one driver + * which contains this code, we don't worry about the wasted space. + */ #include <linux/kernel.h> @@ -20,18 +18,23 @@ #define LM75_TEMP_MAX 125000 #define LM75_SHUTDOWN 0x01 -/* TEMP: 0.001C/bit (-55C to +125C) - REG: (0.5C/bit, two's complement) << 7 */ +/* + * TEMP: 0.001C/bit (-55C to +125C) + * REG: (0.5C/bit, two's complement) << 7 + */ static inline u16 LM75_TEMP_TO_REG(long temp) { int ntemp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX); + ntemp += (ntemp < 0 ? -250 : 250); return (u16)((ntemp / 500) << 7); } static inline int LM75_TEMP_FROM_REG(u16 reg) { - /* use integer division instead of equivalent right shift to - guarantee arithmetic shift and preserve the sign */ + /* + * use integer division instead of equivalent right shift to + * guarantee arithmetic shift and preserve the sign + */ return ((s16)reg / 128) * 500; } diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 9b3c9f390ef8..7bdc664af55b 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -35,6 +35,14 @@ * explicitly as max6659, or if its address is not 0x4c. * These chips lack the remote temperature offset feature. * + * This driver also supports the MAX6654 chip made by Maxim. This chip can + * be at 9 different addresses, similar to MAX6680/MAX6681. The MAX6654 is + * otherwise similar to MAX6657/MAX6658/MAX6659. Extended range is available + * by setting the configuration register accordingly, and is done during + * initialization. Extended precision is only available at conversion rates + * of 1 Hz and slower. Note that extended precision is not enabled by + * default, as this driver initializes all chips to 2 Hz by design. + * * This driver also supports the MAX6646, MAX6647, MAX6648, MAX6649 and * MAX6692 chips made by Maxim. These are again similar to the LM86, * but they use unsigned temperature values and can report temperatures @@ -94,8 +102,8 @@ * have address 0x4d. * MAX6647 has address 0x4e. * MAX6659 can have address 0x4c, 0x4d or 0x4e. - * MAX6680 and MAX6681 can have address 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, - * 0x4c, 0x4d or 0x4e. + * MAX6654, MAX6680, and MAX6681 can have address 0x18, 0x19, 0x1a, 0x29, + * 0x2a, 0x2b, 0x4c, 0x4d or 0x4e. * SA56004 can have address 0x48 through 0x4F. */ @@ -104,7 +112,7 @@ static const unsigned short normal_i2c[] = { 0x4d, 0x4e, 0x4f, I2C_CLIENT_END }; enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680, - max6646, w83l771, max6696, sa56004, g781, tmp451 }; + max6646, w83l771, max6696, sa56004, g781, tmp451, max6654 }; /* * The LM90 registers @@ -145,7 +153,7 @@ enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680, #define LM90_REG_R_TCRIT_HYST 0x21 #define LM90_REG_W_TCRIT_HYST 0x21 -/* MAX6646/6647/6649/6657/6658/6659/6695/6696 registers */ +/* MAX6646/6647/6649/6654/6657/6658/6659/6695/6696 registers */ #define MAX6657_REG_R_LOCAL_TEMPL 0x11 #define MAX6696_REG_R_STATUS2 0x12 @@ -209,6 +217,7 @@ static const struct i2c_device_id lm90_id[] = { { "max6646", max6646 }, { "max6647", max6646 }, { "max6649", max6646 }, + { "max6654", max6654 }, { "max6657", max6657 }, { "max6658", max6657 }, { "max6659", max6659 }, @@ -270,6 +279,10 @@ static const struct of_device_id __maybe_unused lm90_of_match[] = { .data = (void *)max6646 }, { + .compatible = "dallas,max6654", + .data = (void *)max6654 + }, + { .compatible = "dallas,max6657", .data = (void *)max6657 }, @@ -367,6 +380,11 @@ static const struct lm90_params lm90_params[] = { .max_convrate = 6, .reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL, }, + [max6654] = { + .alert_alarms = 0x7c, + .max_convrate = 7, + .reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL, + }, [max6657] = { .flags = LM90_PAUSE_FOR_CONFIG, .alert_alarms = 0x7c, @@ -1557,6 +1575,16 @@ static int lm90_detect(struct i2c_client *client, && (config1 & 0x3f) == 0x00 && convrate <= 0x07) { name = "max6646"; + } else + /* + * The chip_id of the MAX6654 holds the revision of the chip. + * The lowest 3 bits of the config1 register are unused and + * should return zero when read. + */ + if (chip_id == 0x08 + && (config1 & 0x07) == 0x00 + && convrate <= 0x07) { + name = "max6654"; } } else if (address == 0x4C @@ -1661,6 +1689,15 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) config |= 0x18; /* + * Put MAX6654 into extended range (0x20, extend minimum range from + * 0 degrees to -64 degrees). Note that extended resolution is not + * possible on the MAX6654 unless conversion rate is set to 1 Hz or + * slower, which is intentionally not done by default. + */ + if (data->kind == max6654) + config |= 0x20; + + /* * Select external channel 0 for max6695/96 */ if (data->kind == max6696) diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 7efa6bfef060..e7e1ddc1d631 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -2047,7 +2047,7 @@ store_temp_beep(struct device *dev, struct device_attribute *attr, static umode_t nct6775_in_is_visible(struct kobject *kobj, struct attribute *attr, int index) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct nct6775_data *data = dev_get_drvdata(dev); int in = index / 5; /* voltage index */ @@ -2253,7 +2253,7 @@ store_fan_pulses(struct device *dev, struct device_attribute *attr, static umode_t nct6775_fan_is_visible(struct kobject *kobj, struct attribute *attr, int index) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct nct6775_data *data = dev_get_drvdata(dev); int fan = index / 6; /* fan index */ int nr = index % 6; /* attribute index */ @@ -2440,7 +2440,7 @@ store_temp_type(struct device *dev, struct device_attribute *attr, static umode_t nct6775_temp_is_visible(struct kobject *kobj, struct attribute *attr, int index) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct nct6775_data *data = dev_get_drvdata(dev); int temp = index / 10; /* temp index */ int nr = index % 10; /* attribute index */ @@ -3257,7 +3257,7 @@ store_auto_temp(struct device *dev, struct device_attribute *attr, static umode_t nct6775_pwm_is_visible(struct kobject *kobj, struct attribute *attr, int index) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct nct6775_data *data = dev_get_drvdata(dev); int pwm = index / 36; /* pwm index */ int nr = index % 36; /* attribute index */ @@ -3459,7 +3459,7 @@ static SENSOR_DEVICE_ATTR(beep_enable, S_IWUSR | S_IRUGO, show_beep, static umode_t nct6775_other_is_visible(struct kobject *kobj, struct attribute *attr, int index) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct nct6775_data *data = dev_get_drvdata(dev); if (index == 0 && !data->have_vid) diff --git a/drivers/hwmon/nct7802.c b/drivers/hwmon/nct7802.c index 2e97e56c72c7..570df8eb5272 100644 --- a/drivers/hwmon/nct7802.c +++ b/drivers/hwmon/nct7802.c @@ -679,7 +679,7 @@ static struct attribute *nct7802_temp_attrs[] = { static umode_t nct7802_temp_is_visible(struct kobject *kobj, struct attribute *attr, int index) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct nct7802_data *data = dev_get_drvdata(dev); unsigned int reg; int err; @@ -778,7 +778,7 @@ static struct attribute *nct7802_in_attrs[] = { static umode_t nct7802_in_is_visible(struct kobject *kobj, struct attribute *attr, int index) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct nct7802_data *data = dev_get_drvdata(dev); unsigned int reg; int err; @@ -853,7 +853,7 @@ static struct attribute *nct7802_fan_attrs[] = { static umode_t nct7802_fan_is_visible(struct kobject *kobj, struct attribute *attr, int index) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct nct7802_data *data = dev_get_drvdata(dev); int fan = index / 4; /* 4 attributes per fan */ unsigned int reg; diff --git a/drivers/hwmon/nct7904.c b/drivers/hwmon/nct7904.c index a7eb10d2a053..b0425694f702 100644 --- a/drivers/hwmon/nct7904.c +++ b/drivers/hwmon/nct7904.c @@ -8,6 +8,9 @@ * Copyright (c) 2019 Advantech * Author: Amy.Shih <amy.shih@advantech.com.tw> * + * Copyright (c) 2020 Advantech + * Author: Yuechao Zhao <yuechao.zhao@advantech.com.cn> + * * Supports the following chips: * * Chip #vin #fan #pwm #temp #dts chip ID @@ -20,6 +23,7 @@ #include <linux/i2c.h> #include <linux/mutex.h> #include <linux/hwmon.h> +#include <linux/watchdog.h> #define VENDOR_ID_REG 0x7A /* Any bank */ #define NUVOTON_ID 0x50 @@ -88,18 +92,42 @@ #define FANCTL1_FMR_REG 0x00 /* Bank 3; 1 reg per channel */ #define FANCTL1_OUT_REG 0x10 /* Bank 3; 1 reg per channel */ +#define WDT_LOCK_REG 0xE0 /* W/O Lock Watchdog Register */ +#define WDT_EN_REG 0xE1 /* R/O Watchdog Enable Register */ +#define WDT_STS_REG 0xE2 /* R/O Watchdog Status Register */ +#define WDT_TIMER_REG 0xE3 /* R/W Watchdog Timer Register */ +#define WDT_SOFT_EN 0x55 /* Enable soft watchdog timer */ +#define WDT_SOFT_DIS 0xAA /* Disable soft watchdog timer */ + #define VOLT_MONITOR_MODE 0x0 #define THERMAL_DIODE_MODE 0x1 #define THERMISTOR_MODE 0x3 #define ENABLE_TSI BIT(1) +#define WATCHDOG_TIMEOUT 1 /* 1 minute default timeout */ + +/*The timeout range is 1-255 minutes*/ +#define MIN_TIMEOUT (1 * 60) +#define MAX_TIMEOUT (255 * 60) + +static int timeout; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in minutes. 1 <= timeout <= 255, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) "."); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + static const unsigned short normal_i2c[] = { 0x2d, 0x2e, I2C_CLIENT_END }; struct nct7904_data { struct i2c_client *client; + struct watchdog_device wdt; struct mutex bank_lock; int bank_sel; u32 fanin_mask; @@ -892,6 +920,95 @@ static const struct hwmon_chip_info nct7904_chip_info = { .info = nct7904_info, }; +/* + * Watchdog Function + */ +static int nct7904_wdt_start(struct watchdog_device *wdt) +{ + struct nct7904_data *data = watchdog_get_drvdata(wdt); + + /* Enable soft watchdog timer */ + return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_EN); +} + +static int nct7904_wdt_stop(struct watchdog_device *wdt) +{ + struct nct7904_data *data = watchdog_get_drvdata(wdt); + + return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_DIS); +} + +static int nct7904_wdt_set_timeout(struct watchdog_device *wdt, + unsigned int timeout) +{ + struct nct7904_data *data = watchdog_get_drvdata(wdt); + /* + * The NCT7904 is very special in watchdog function. + * Its minimum unit is minutes. And wdt->timeout needs + * to match the actual timeout selected. So, this needs + * to be: wdt->timeout = timeout / 60 * 60. + * For example, if the user configures a timeout of + * 119 seconds, the actual timeout will be 60 seconds. + * So, wdt->timeout must then be set to 60 seconds. + */ + wdt->timeout = timeout / 60 * 60; + + return nct7904_write_reg(data, BANK_0, WDT_TIMER_REG, + wdt->timeout / 60); +} + +static int nct7904_wdt_ping(struct watchdog_device *wdt) +{ + /* + * Note: + * NCT7904 does not support refreshing WDT_TIMER_REG register when + * the watchdog is active. Please disable watchdog before feeding + * the watchdog and enable it again. + */ + struct nct7904_data *data = watchdog_get_drvdata(wdt); + int ret; + + /* Disable soft watchdog timer */ + ret = nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_DIS); + if (ret < 0) + return ret; + + /* feed watchdog */ + ret = nct7904_write_reg(data, BANK_0, WDT_TIMER_REG, wdt->timeout / 60); + if (ret < 0) + return ret; + + /* Enable soft watchdog timer */ + return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_EN); +} + +static unsigned int nct7904_wdt_get_timeleft(struct watchdog_device *wdt) +{ + struct nct7904_data *data = watchdog_get_drvdata(wdt); + int ret; + + ret = nct7904_read_reg(data, BANK_0, WDT_TIMER_REG); + if (ret < 0) + return 0; + + return ret * 60; +} + +static const struct watchdog_info nct7904_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .identity = "nct7904 watchdog", +}; + +static const struct watchdog_ops nct7904_wdt_ops = { + .owner = THIS_MODULE, + .start = nct7904_wdt_start, + .stop = nct7904_wdt_stop, + .ping = nct7904_wdt_ping, + .set_timeout = nct7904_wdt_set_timeout, + .get_timeleft = nct7904_wdt_get_timeleft, +}; + static int nct7904_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -1022,7 +1139,26 @@ static int nct7904_probe(struct i2c_client *client, hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &nct7904_chip_info, NULL); - return PTR_ERR_OR_ZERO(hwmon_dev); + ret = PTR_ERR_OR_ZERO(hwmon_dev); + if (ret) + return ret; + + /* Watchdog initialization */ + data->wdt.ops = &nct7904_wdt_ops; + data->wdt.info = &nct7904_wdt_info; + + data->wdt.timeout = WATCHDOG_TIMEOUT * 60; /* Set default timeout */ + data->wdt.min_timeout = MIN_TIMEOUT; + data->wdt.max_timeout = MAX_TIMEOUT; + data->wdt.parent = &client->dev; + + watchdog_init_timeout(&data->wdt, timeout * 60, &client->dev); + watchdog_set_nowayout(&data->wdt, nowayout); + watchdog_set_drvdata(&data->wdt, data); + + watchdog_stop_on_unregister(&data->wdt); + + return devm_watchdog_register_device(dev, &data->wdt); } static const struct i2c_device_id nct7904_id[] = { diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index de12a565006d..a337195b1c39 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -146,6 +146,15 @@ config SENSORS_MAX16064 This driver can also be built as a module. If so, the module will be called max16064. +config SENSORS_MAX16601 + tristate "Maxim MAX16601" + help + If you say yes here you get hardware monitoring support for Maxim + MAX16601. + + This driver can also be built as a module. If so, the module will + be called max16601. + config SENSORS_MAX20730 tristate "Maxim MAX20730, MAX20734, MAX20743" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 5feb45806123..c4b15db996ad 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_LM25066) += lm25066.o obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o obj-$(CONFIG_SENSORS_MAX16064) += max16064.o +obj-$(CONFIG_SENSORS_MAX16601) += max16601.o obj-$(CONFIG_SENSORS_MAX20730) += max20730.o obj-$(CONFIG_SENSORS_MAX20751) += max20751.o obj-$(CONFIG_SENSORS_MAX31785) += max31785.o diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c new file mode 100644 index 000000000000..51cdfaf9023c --- /dev/null +++ b/drivers/hwmon/pmbus/max16601.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Hardware monitoring driver for Maxim MAX16601 + * + * Implementation notes: + * + * Ths chip supports two rails, VCORE and VSA. Telemetry information for the + * two rails is reported in two subsequent I2C addresses. The driver + * instantiates a dummy I2C client at the second I2C address to report + * information for the VSA rail in a single instance of the driver. + * Telemetry for the VSA rail is reported to the PMBus core in PMBus page 2. + * + * The chip reports input current using two separate methods. The input current + * reported with the standard READ_IIN command is derived from the output + * current. The first method is reported to the PMBus core with PMBus page 0, + * the second method is reported with PMBus page 1. + * + * The chip supports reading per-phase temperatures and per-phase input/output + * currents for VCORE. Telemetry is reported in vendor specific registers. + * The driver translates the vendor specific register values to PMBus standard + * register values and reports per-phase information in PMBus page 0. + * + * Copyright 2019, 2020 Google LLC. + */ + +#include <linux/bits.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#include "pmbus.h" + +#define REG_SETPT_DVID 0xd1 +#define DAC_10MV_MODE BIT(4) +#define REG_IOUT_AVG_PK 0xee +#define REG_IIN_SENSOR 0xf1 +#define REG_TOTAL_INPUT_POWER 0xf2 +#define REG_PHASE_ID 0xf3 +#define CORE_RAIL_INDICATOR BIT(7) +#define REG_PHASE_REPORTING 0xf4 + +struct max16601_data { + struct pmbus_driver_info info; + struct i2c_client *vsa; + int iout_avg_pkg; +}; + +#define to_max16601_data(x) container_of(x, struct max16601_data, info) + +static int max16601_read_byte(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max16601_data *data = to_max16601_data(info); + + if (page > 0) { + if (page == 2) /* VSA */ + return i2c_smbus_read_byte_data(data->vsa, reg); + return -EOPNOTSUPP; + } + return -ENODATA; +} + +static int max16601_read_word(struct i2c_client *client, int page, int phase, + int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max16601_data *data = to_max16601_data(info); + u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + int ret; + + switch (page) { + case 0: /* VCORE */ + if (phase == 0xff) + return -ENODATA; + switch (reg) { + case PMBUS_READ_IIN: + case PMBUS_READ_IOUT: + case PMBUS_READ_TEMPERATURE_1: + ret = i2c_smbus_write_byte_data(client, REG_PHASE_ID, + phase); + if (ret) + return ret; + ret = i2c_smbus_read_block_data(client, + REG_PHASE_REPORTING, + buf); + if (ret < 0) + return ret; + if (ret < 6) + return -EIO; + switch (reg) { + case PMBUS_READ_TEMPERATURE_1: + return buf[1] << 8 | buf[0]; + case PMBUS_READ_IOUT: + return buf[3] << 8 | buf[2]; + case PMBUS_READ_IIN: + return buf[5] << 8 | buf[4]; + default: + break; + } + } + return -EOPNOTSUPP; + case 1: /* VCORE, read IIN/PIN from sensor element */ + switch (reg) { + case PMBUS_READ_IIN: + return i2c_smbus_read_word_data(client, REG_IIN_SENSOR); + case PMBUS_READ_PIN: + return i2c_smbus_read_word_data(client, + REG_TOTAL_INPUT_POWER); + default: + break; + } + return -EOPNOTSUPP; + case 2: /* VSA */ + switch (reg) { + case PMBUS_VIRT_READ_IOUT_MAX: + ret = i2c_smbus_read_word_data(data->vsa, + REG_IOUT_AVG_PK); + if (ret < 0) + return ret; + if (sign_extend32(ret, 10) > + sign_extend32(data->iout_avg_pkg, 10)) + data->iout_avg_pkg = ret; + return data->iout_avg_pkg; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + return 0; + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_READ_IIN: + case PMBUS_READ_IOUT: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_STATUS_WORD: + return i2c_smbus_read_word_data(data->vsa, reg); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int max16601_write_byte(struct i2c_client *client, int page, u8 reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max16601_data *data = to_max16601_data(info); + + if (page == 2) { + if (reg == PMBUS_CLEAR_FAULTS) + return i2c_smbus_write_byte(data->vsa, reg); + return -EOPNOTSUPP; + } + return -ENODATA; +} + +static int max16601_write_word(struct i2c_client *client, int page, int reg, + u16 value) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max16601_data *data = to_max16601_data(info); + + switch (page) { + case 0: /* VCORE */ + return -ENODATA; + case 1: /* VCORE IIN/PIN from sensor element */ + default: + return -EOPNOTSUPP; + case 2: /* VSA */ + switch (reg) { + case PMBUS_VIRT_RESET_IOUT_HISTORY: + data->iout_avg_pkg = 0xfc00; + return 0; + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + return i2c_smbus_write_word_data(data->vsa, reg, value); + default: + return -EOPNOTSUPP; + } + } +} + +static int max16601_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int reg; + + reg = i2c_smbus_read_byte_data(client, REG_SETPT_DVID); + if (reg < 0) + return reg; + if (reg & DAC_10MV_MODE) + info->vrm_version[0] = vr13; + else + info->vrm_version[0] = vr12; + + return 0; +} + +static struct pmbus_driver_info max16601_info = { + .pages = 3, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = vid, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | + PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT | PMBUS_PAGE_VIRTUAL | PMBUS_PHASE_VIRTUAL, + .func[1] = PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | PMBUS_PAGE_VIRTUAL, + .func[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_PAGE_VIRTUAL, + .phases[0] = 8, + .pfunc[0] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, + .pfunc[1] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT, + .pfunc[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, + .pfunc[3] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT, + .pfunc[4] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, + .pfunc[5] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT, + .pfunc[6] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, + .pfunc[7] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT, + .identify = max16601_identify, + .read_byte_data = max16601_read_byte, + .read_word_data = max16601_read_word, + .write_byte = max16601_write_byte, + .write_word_data = max16601_write_word, +}; + +static void max16601_remove(void *_data) +{ + struct max16601_data *data = _data; + + i2c_unregister_device(data->vsa); +} + +static int max16601_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + struct max16601_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA | + I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); + if (ret < 0) + return -ENODEV; + + /* PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" */ + if (ret < 11 || strncmp(buf, "MAX16601", 8)) { + buf[ret] = '\0'; + dev_err(dev, "Unsupported chip '%s'\n", buf); + return -ENODEV; + } + + ret = i2c_smbus_read_byte_data(client, REG_PHASE_ID); + if (ret < 0) + return ret; + if (!(ret & CORE_RAIL_INDICATOR)) { + dev_err(dev, + "Driver must be instantiated on CORE rail I2C address\n"); + return -ENODEV; + } + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->iout_avg_pkg = 0xfc00; + data->vsa = i2c_new_dummy_device(client->adapter, client->addr + 1); + if (IS_ERR(data->vsa)) { + dev_err(dev, "Failed to register VSA client\n"); + return PTR_ERR(data->vsa); + } + ret = devm_add_action_or_reset(dev, max16601_remove, data); + if (ret) + return ret; + + data->info = max16601_info; + + return pmbus_do_probe(client, id, &data->info); +} + +static const struct i2c_device_id max16601_id[] = { + {"max16601", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max16601_id); + +static struct i2c_driver max16601_driver = { + .driver = { + .name = "max16601", + }, + .probe = max16601_probe, + .remove = pmbus_do_remove, + .id_table = max16601_id, +}; + +module_i2c_driver(max16601_driver); + +MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX16601"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 8d321bf7d15b..a420877ba533 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -109,8 +109,8 @@ struct pmbus_data { bool has_status_word; /* device uses STATUS_WORD register */ int (*read_status)(struct i2c_client *client, int page); - u8 currpage; - u8 currphase; /* current phase, 0xff for all */ + s16 currpage; /* current page, -1 for unknown/unset */ + s16 currphase; /* current phase, 0xff for all, -1 for unknown/unset */ }; struct pmbus_debugfs_entry { @@ -2529,8 +2529,8 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, if (pdata) data->flags = pdata->flags; data->info = info; - data->currpage = 0xff; - data->currphase = 0xfe; + data->currpage = -1; + data->currphase = -1; ret = pmbus_init_common(client, data, info); if (ret < 0) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 0a59249198d3..05448e78e5b8 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -407,6 +407,21 @@ config MFD_EXYNOS_LPASS Select this option to enable support for Samsung Exynos Low Power Audio Subsystem. +config MFD_GATEWORKS_GSC + tristate "Gateworks System Controller" + depends on (I2C && OF) + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + Enable support for the Gateworks System Controller (GSC) found + on Gateworks Single Board Computers supporting system functions + such as push-button monitor, multiple ADC's for voltage and + temperature monitoring, fan controller and watchdog monitor. + This driver provides common support for accessing the device. + Additional drivers must be enabled in order to use the + functionality of the device. + config MFD_MC13XXX tristate depends on (SPI_MASTER || I2C) diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index f935d10cbf0f..ed433aed7010 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.o obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o +obj-$(CONFIG_MFD_GATEWORKS_GSC) += gateworks-gsc.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o diff --git a/drivers/mfd/gateworks-gsc.c b/drivers/mfd/gateworks-gsc.c new file mode 100644 index 000000000000..576da62fbb0c --- /dev/null +++ b/drivers/mfd/gateworks-gsc.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Gateworks System Controller (GSC) is a multi-function + * device designed for use in Gateworks Single Board Computers. + * The control interface is I2C, with an interrupt. The device supports + * system functions such as push-button monitoring, multiple ADC's for + * voltage and temperature monitoring, fan controller and watchdog monitor. + * + * Copyright (C) 2020 Gateworks Corporation + */ + +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mfd/gsc.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <asm/unaligned.h> + +/* + * The GSC suffers from an errata where occasionally during + * ADC cycles the chip can NAK I2C transactions. To ensure we have reliable + * register access we place retries around register access. + */ +#define I2C_RETRIES 3 + +int gsc_write(void *context, unsigned int reg, unsigned int val) +{ + struct i2c_client *client = context; + int retry, ret; + + for (retry = 0; retry < I2C_RETRIES; retry++) { + ret = i2c_smbus_write_byte_data(client, reg, val); + /* + * -EAGAIN returned when the i2c host controller is busy + * -EIO returned when i2c device is busy + */ + if (ret != -EAGAIN && ret != -EIO) + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(gsc_write); + +int gsc_read(void *context, unsigned int reg, unsigned int *val) +{ + struct i2c_client *client = context; + int retry, ret; + + for (retry = 0; retry < I2C_RETRIES; retry++) { + ret = i2c_smbus_read_byte_data(client, reg); + /* + * -EAGAIN returned when the i2c host controller is busy + * -EIO returned when i2c device is busy + */ + if (ret != -EAGAIN && ret != -EIO) + break; + } + *val = ret & 0xff; + + return 0; +} +EXPORT_SYMBOL_GPL(gsc_read); + +/* + * gsc_powerdown - API to use GSC to power down board for a specific time + * + * secs - number of seconds to remain powered off + */ +static int gsc_powerdown(struct gsc_dev *gsc, unsigned long secs) +{ + int ret; + unsigned char regs[4]; + + dev_info(&gsc->i2c->dev, "GSC powerdown for %ld seconds\n", + secs); + + put_unaligned_le32(secs, regs); + ret = regmap_bulk_write(gsc->regmap, GSC_TIME_ADD, regs, 4); + if (ret) + return ret; + + ret = regmap_update_bits(gsc->regmap, GSC_CTRL_1, + BIT(GSC_CTRL_1_SLEEP_ADD), + BIT(GSC_CTRL_1_SLEEP_ADD)); + if (ret) + return ret; + + ret = regmap_update_bits(gsc->regmap, GSC_CTRL_1, + BIT(GSC_CTRL_1_SLEEP_ACTIVATE) | + BIT(GSC_CTRL_1_SLEEP_ENABLE), + BIT(GSC_CTRL_1_SLEEP_ACTIVATE) | + BIT(GSC_CTRL_1_SLEEP_ENABLE)); + + + return ret; +} + +static ssize_t gsc_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct gsc_dev *gsc = dev_get_drvdata(dev); + const char *name = attr->attr.name; + int rz = 0; + + if (strcasecmp(name, "fw_version") == 0) + rz = sprintf(buf, "%d\n", gsc->fwver); + else if (strcasecmp(name, "fw_crc") == 0) + rz = sprintf(buf, "0x%04x\n", gsc->fwcrc); + else + dev_err(dev, "invalid command: '%s'\n", name); + + return rz; +} + +static ssize_t gsc_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gsc_dev *gsc = dev_get_drvdata(dev); + const char *name = attr->attr.name; + long value; + + if (strcasecmp(name, "powerdown") == 0) { + if (kstrtol(buf, 0, &value) == 0) + gsc_powerdown(gsc, value); + } else { + dev_err(dev, "invalid command: '%s\n", name); + } + + return count; +} + +static struct device_attribute attr_fwver = + __ATTR(fw_version, 0440, gsc_show, NULL); +static struct device_attribute attr_fwcrc = + __ATTR(fw_crc, 0440, gsc_show, NULL); +static struct device_attribute attr_pwrdown = + __ATTR(powerdown, 0220, NULL, gsc_store); + +static struct attribute *gsc_attrs[] = { + &attr_fwver.attr, + &attr_fwcrc.attr, + &attr_pwrdown.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = gsc_attrs, +}; + +static const struct of_device_id gsc_of_match[] = { + { .compatible = "gw,gsc", }, + { } +}; +MODULE_DEVICE_TABLE(of, gsc_of_match); + +static struct regmap_bus gsc_regmap_bus = { + .reg_read = gsc_read, + .reg_write = gsc_write, +}; + +static const struct regmap_config gsc_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_NONE, + .max_register = GSC_WP, +}; + +static const struct regmap_irq gsc_irqs[] = { + REGMAP_IRQ_REG(GSC_IRQ_PB, 0, BIT(GSC_IRQ_PB)), + REGMAP_IRQ_REG(GSC_IRQ_KEY_ERASED, 0, BIT(GSC_IRQ_KEY_ERASED)), + REGMAP_IRQ_REG(GSC_IRQ_EEPROM_WP, 0, BIT(GSC_IRQ_EEPROM_WP)), + REGMAP_IRQ_REG(GSC_IRQ_RESV, 0, BIT(GSC_IRQ_RESV)), + REGMAP_IRQ_REG(GSC_IRQ_GPIO, 0, BIT(GSC_IRQ_GPIO)), + REGMAP_IRQ_REG(GSC_IRQ_TAMPER, 0, BIT(GSC_IRQ_TAMPER)), + REGMAP_IRQ_REG(GSC_IRQ_WDT_TIMEOUT, 0, BIT(GSC_IRQ_WDT_TIMEOUT)), + REGMAP_IRQ_REG(GSC_IRQ_SWITCH_HOLD, 0, BIT(GSC_IRQ_SWITCH_HOLD)), +}; + +static const struct regmap_irq_chip gsc_irq_chip = { + .name = "gateworks-gsc", + .irqs = gsc_irqs, + .num_irqs = ARRAY_SIZE(gsc_irqs), + .num_regs = 1, + .status_base = GSC_IRQ_STATUS, + .mask_base = GSC_IRQ_ENABLE, + .mask_invert = true, + .ack_base = GSC_IRQ_STATUS, + .ack_invert = true, +}; + +static int gsc_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct gsc_dev *gsc; + struct regmap_irq_chip_data *irq_data; + int ret; + unsigned int reg; + + gsc = devm_kzalloc(dev, sizeof(*gsc), GFP_KERNEL); + if (!gsc) + return -ENOMEM; + + gsc->dev = &client->dev; + gsc->i2c = client; + i2c_set_clientdata(client, gsc); + + gsc->regmap = devm_regmap_init(dev, &gsc_regmap_bus, client, + &gsc_regmap_config); + if (IS_ERR(gsc->regmap)) + return PTR_ERR(gsc->regmap); + + if (regmap_read(gsc->regmap, GSC_FW_VER, ®)) + return -EIO; + gsc->fwver = reg; + + regmap_read(gsc->regmap, GSC_FW_CRC, ®); + gsc->fwcrc = reg; + regmap_read(gsc->regmap, GSC_FW_CRC + 1, ®); + gsc->fwcrc |= reg << 8; + + gsc->i2c_hwmon = devm_i2c_new_dummy_device(dev, client->adapter, + GSC_HWMON); + if (IS_ERR(gsc->i2c_hwmon)) { + dev_err(dev, "Failed to allocate I2C device for HWMON\n"); + return PTR_ERR(gsc->i2c_hwmon); + } + + ret = devm_regmap_add_irq_chip(dev, gsc->regmap, client->irq, + IRQF_ONESHOT | IRQF_SHARED | + IRQF_TRIGGER_FALLING, 0, + &gsc_irq_chip, &irq_data); + if (ret) + return ret; + + dev_info(dev, "Gateworks System Controller v%d: fw 0x%04x\n", + gsc->fwver, gsc->fwcrc); + + ret = sysfs_create_group(&dev->kobj, &attr_group); + if (ret) + dev_err(dev, "failed to create sysfs attrs\n"); + + ret = devm_of_platform_populate(dev); + if (ret) { + sysfs_remove_group(&dev->kobj, &attr_group); + return ret; + } + + return 0; +} + +static int gsc_remove(struct i2c_client *client) +{ + sysfs_remove_group(&client->dev.kobj, &attr_group); + + return 0; +} + +static struct i2c_driver gsc_driver = { + .driver = { + .name = "gateworks-gsc", + .of_match_table = gsc_of_match, + }, + .probe_new = gsc_probe, + .remove = gsc_remove, +}; +module_i2c_driver(gsc_driver); + +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>"); +MODULE_DESCRIPTION("I2C Core interface for GSC"); +MODULE_LICENSE("GPL v2"); |