diff options
Diffstat (limited to 'drivers/hwmon')
31 files changed, 2240 insertions, 410 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index cf27523eed..a608264da8 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -512,6 +512,7 @@ config SENSORS_DS1621 config SENSORS_DELL_SMM tristate "Dell laptop SMM BIOS hwmon driver" + depends on ACPI_WMI depends on X86 imply THERMAL help @@ -663,6 +664,16 @@ config SENSORS_FTSTEUTATES This driver can also be built as a module. If so, the module will be called ftsteutates. +config SENSORS_GIGABYTE_WATERFORCE + tristate "Gigabyte Waterforce X240/X280/X360 AIO CPU coolers" + depends on USB_HID + help + If you say yes here you get support for hardware monitoring for the + Gigabyte Waterforce X240/X280/X360 all-in-one CPU liquid coolers. + + This driver can also be built as a module. If so, the module + will be called gigabyte_waterforce. + config SENSORS_GL518SM tristate "Genesys Logic GL518SM" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index e84bd9685b..47be39af5c 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -80,6 +80,7 @@ obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o obj-$(CONFIG_SENSORS_FTSTEUTATES) += ftsteutates.o obj-$(CONFIG_SENSORS_G760A) += g760a.o obj-$(CONFIG_SENSORS_G762) += g762.o +obj-$(CONFIG_SENSORS_GIGABYTE_WATERFORCE) += gigabyte_waterforce.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c index 4fdd2e1242..2efe97f8d0 100644 --- a/drivers/hwmon/aquacomputer_d5next.c +++ b/drivers/hwmon/aquacomputer_d5next.c @@ -1476,8 +1476,6 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 return 0; } -#ifdef CONFIG_DEBUG_FS - static int serial_number_show(struct seq_file *seqf, void *unused) { struct aqc_data *priv = seqf->private; @@ -1527,14 +1525,6 @@ static void aqc_debugfs_init(struct aqc_data *priv) debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops); } -#else - -static void aqc_debugfs_init(struct aqc_data *priv) -{ -} - -#endif - static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct aqc_data *priv; diff --git a/drivers/hwmon/aspeed-pwm-tacho.c b/drivers/hwmon/aspeed-pwm-tacho.c index b2ae2176f1..4acc1858d8 100644 --- a/drivers/hwmon/aspeed-pwm-tacho.c +++ b/drivers/hwmon/aspeed-pwm-tacho.c @@ -166,6 +166,8 @@ #define MAX_CDEV_NAME_LEN 16 +#define MAX_ASPEED_FAN_TACH_CHANNELS 16 + struct aspeed_cooling_device { char name[16]; struct aspeed_pwm_tacho_data *priv; @@ -181,7 +183,7 @@ struct aspeed_pwm_tacho_data { struct reset_control *rst; unsigned long clk_freq; bool pwm_present[8]; - bool fan_tach_present[16]; + bool fan_tach_present[MAX_ASPEED_FAN_TACH_CHANNELS]; u8 type_pwm_clock_unit[3]; u8 type_pwm_clock_division_h[3]; u8 type_pwm_clock_division_l[3]; @@ -190,7 +192,7 @@ struct aspeed_pwm_tacho_data { u16 type_fan_tach_unit[3]; u8 pwm_port_type[8]; u8 pwm_port_fan_ctrl[8]; - u8 fan_tach_ch_source[16]; + u8 fan_tach_ch_source[MAX_ASPEED_FAN_TACH_CHANNELS]; struct aspeed_cooling_device *cdev[8]; const struct attribute_group *groups[3]; /* protects access to shared ASPEED_PTCR_RESULT */ @@ -743,20 +745,27 @@ static void aspeed_create_pwm_port(struct aspeed_pwm_tacho_data *priv, aspeed_set_pwm_port_fan_ctrl(priv, pwm_port, INIT_FAN_CTRL); } -static void aspeed_create_fan_tach_channel(struct aspeed_pwm_tacho_data *priv, - u8 *fan_tach_ch, - int count, - u8 pwm_source) +static int aspeed_create_fan_tach_channel(struct device *dev, + struct aspeed_pwm_tacho_data *priv, + u8 *fan_tach_ch, + int count, + u8 pwm_source) { u8 val, index; for (val = 0; val < count; val++) { index = fan_tach_ch[val]; + if (index >= MAX_ASPEED_FAN_TACH_CHANNELS) { + dev_err(dev, "Invalid Fan Tach input channel %u\n.", index); + return -EINVAL; + } aspeed_set_fan_tach_ch_enable(priv->regmap, index, true); priv->fan_tach_present[index] = true; priv->fan_tach_ch_source[index] = pwm_source; aspeed_set_fan_tach_ch_source(priv->regmap, index, pwm_source); } + + return 0; } static int @@ -880,7 +889,10 @@ static int aspeed_create_fan(struct device *dev, fan_tach_ch, count); if (ret) return ret; - aspeed_create_fan_tach_channel(priv, fan_tach_ch, count, pwm_port); + + ret = aspeed_create_fan_tach_channel(dev, priv, fan_tach_ch, count, pwm_port); + if (ret) + return ret; return 0; } diff --git a/drivers/hwmon/corsair-cpro.c b/drivers/hwmon/corsair-cpro.c index 463ab4296e..a284a02839 100644 --- a/drivers/hwmon/corsair-cpro.c +++ b/drivers/hwmon/corsair-cpro.c @@ -524,7 +524,7 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret) goto out_hw_close; ccp->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsaircpro", - ccp, &ccp_chip_info, 0); + ccp, &ccp_chip_info, NULL); if (IS_ERR(ccp->hwmon_dev)) { ret = PTR_ERR(ccp->hwmon_dev); goto out_hw_close; diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 44aaf9b919..6d8c0f328b 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -12,6 +12,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/acpi.h> #include <linux/capability.h> #include <linux/cpu.h> #include <linux/ctype.h> @@ -34,8 +35,10 @@ #include <linux/thermal.h> #include <linux/types.h> #include <linux/uaccess.h> +#include <linux/wmi.h> #include <linux/i8k.h> +#include <asm/unaligned.h> #define I8K_SMM_FN_STATUS 0x0025 #define I8K_SMM_POWER_STATUS 0x0069 @@ -66,9 +69,26 @@ #define I8K_POWER_AC 0x05 #define I8K_POWER_BATTERY 0x01 +#define DELL_SMM_WMI_GUID "F1DDEE52-063C-4784-A11E-8A06684B9B01" +#define DELL_SMM_LEGACY_EXECUTE 0x1 + #define DELL_SMM_NO_TEMP 10 #define DELL_SMM_NO_FANS 3 +struct smm_regs { + unsigned int eax; + unsigned int ebx; + unsigned int ecx; + unsigned int edx; + unsigned int esi; + unsigned int edi; +}; + +struct dell_smm_ops { + struct device *smm_dev; + int (*smm_call)(struct device *smm_dev, struct smm_regs *regs); +}; + struct dell_smm_data { struct mutex i8k_mutex; /* lock for sensors writes */ char bios_version[4]; @@ -76,14 +96,11 @@ struct dell_smm_data { uint i8k_fan_mult; uint i8k_pwm_mult; uint i8k_fan_max; - bool disallow_fan_type_call; - bool disallow_fan_support; - unsigned int manual_fan; - unsigned int auto_fan; int temp_type[DELL_SMM_NO_TEMP]; bool fan[DELL_SMM_NO_FANS]; int fan_type[DELL_SMM_NO_FANS]; int *fan_nominal_speed[DELL_SMM_NO_FANS]; + const struct dell_smm_ops *ops; }; struct dell_smm_cooling_data { @@ -123,14 +140,9 @@ static uint fan_max; module_param(fan_max, uint, 0); MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)"); -struct smm_regs { - unsigned int eax; - unsigned int ebx; - unsigned int ecx; - unsigned int edx; - unsigned int esi; - unsigned int edi; -}; +static bool disallow_fan_type_call, disallow_fan_support; + +static unsigned int manual_fan, auto_fan; static const char * const temp_labels[] = { "CPU", @@ -171,12 +183,8 @@ static inline const char __init *i8k_get_dmi_data(int field) */ static int i8k_smm_func(void *par) { - ktime_t calltime = ktime_get(); struct smm_regs *regs = par; - int eax = regs->eax; - int ebx = regs->ebx; unsigned char carry; - long long duration; /* SMM requires CPU 0 */ if (smp_processor_id() != 0) @@ -193,14 +201,7 @@ static int i8k_smm_func(void *par) "+S" (regs->esi), "+D" (regs->edi)); - duration = ktime_us_delta(ktime_get(), calltime); - pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x carry: %d (took %7lld usecs)\n", - eax, ebx, regs->eax & 0xffff, carry, duration); - - if (duration > DELL_SMM_MAX_DURATION) - pr_warn_once("SMM call took %lld usecs!\n", duration); - - if (carry || (regs->eax & 0xffff) == 0xffff || regs->eax == eax) + if (carry) return -EINVAL; return 0; @@ -209,7 +210,7 @@ static int i8k_smm_func(void *par) /* * Call the System Management Mode BIOS. */ -static int i8k_smm(struct smm_regs *regs) +static int i8k_smm_call(struct device *dummy, struct smm_regs *regs) { int ret; @@ -220,6 +221,134 @@ static int i8k_smm(struct smm_regs *regs) return ret; } +static const struct dell_smm_ops i8k_smm_ops = { + .smm_call = i8k_smm_call, +}; + +/* + * Call the System Management Mode BIOS over WMI. + */ +static ssize_t wmi_parse_register(u8 *buffer, u32 length, unsigned int *reg) +{ + __le32 value; + u32 reg_size; + + if (length <= sizeof(reg_size)) + return -ENODATA; + + reg_size = get_unaligned_le32(buffer); + if (!reg_size || reg_size > sizeof(value)) + return -ENOMSG; + + if (length < sizeof(reg_size) + reg_size) + return -ENODATA; + + memcpy_and_pad(&value, sizeof(value), buffer + sizeof(reg_size), reg_size, 0); + *reg = le32_to_cpu(value); + + return reg_size + sizeof(reg_size); +} + +static int wmi_parse_response(u8 *buffer, u32 length, struct smm_regs *regs) +{ + unsigned int *registers[] = { + ®s->eax, + ®s->ebx, + ®s->ecx, + ®s->edx + }; + u32 offset = 0; + ssize_t ret; + int i; + + for (i = 0; i < ARRAY_SIZE(registers); i++) { + if (offset >= length) + return -ENODATA; + + ret = wmi_parse_register(buffer + offset, length - offset, registers[i]); + if (ret < 0) + return ret; + + offset += ret; + } + + if (offset != length) + return -ENOMSG; + + return 0; +} + +static int wmi_smm_call(struct device *dev, struct smm_regs *regs) +{ + struct wmi_device *wdev = container_of(dev, struct wmi_device, dev); + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 wmi_payload[] = { + sizeof(regs->eax), + regs->eax, + sizeof(regs->ebx), + regs->ebx, + sizeof(regs->ecx), + regs->ecx, + sizeof(regs->edx), + regs->edx + }; + const struct acpi_buffer in = { + .length = sizeof(wmi_payload), + .pointer = &wmi_payload, + }; + union acpi_object *obj; + acpi_status status; + int ret; + + status = wmidev_evaluate_method(wdev, 0x0, DELL_SMM_LEGACY_EXECUTE, &in, &out); + if (ACPI_FAILURE(status)) + return -EIO; + + obj = out.pointer; + if (!obj) + return -ENODATA; + + if (obj->type != ACPI_TYPE_BUFFER) { + ret = -ENOMSG; + + goto err_free; + } + + ret = wmi_parse_response(obj->buffer.pointer, obj->buffer.length, regs); + +err_free: + kfree(obj); + + return ret; +} + +static int dell_smm_call(const struct dell_smm_ops *ops, struct smm_regs *regs) +{ + unsigned int eax = regs->eax; + unsigned int ebx = regs->ebx; + long long duration; + ktime_t calltime; + int ret; + + calltime = ktime_get(); + ret = ops->smm_call(ops->smm_dev, regs); + duration = ktime_us_delta(ktime_get(), calltime); + + pr_debug("SMM(0x%.4x 0x%.4x) = 0x%.4x status: %d (took %7lld usecs)\n", + eax, ebx, regs->eax & 0xffff, ret, duration); + + if (duration > DELL_SMM_MAX_DURATION) + pr_warn_once("SMM call took %lld usecs!\n", duration); + + if (ret < 0) + return ret; + + if ((regs->eax & 0xffff) == 0xffff || regs->eax == eax) + return -EINVAL; + + return 0; +} + /* * Read the fan status. */ @@ -230,10 +359,10 @@ static int i8k_get_fan_status(const struct dell_smm_data *data, u8 fan) .ebx = fan, }; - if (data->disallow_fan_support) + if (disallow_fan_support) return -EINVAL; - return i8k_smm(®s) ? : regs.eax & 0xff; + return dell_smm_call(data->ops, ®s) ? : regs.eax & 0xff; } /* @@ -246,10 +375,10 @@ static int i8k_get_fan_speed(const struct dell_smm_data *data, u8 fan) .ebx = fan, }; - if (data->disallow_fan_support) + if (disallow_fan_support) return -EINVAL; - return i8k_smm(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; + return dell_smm_call(data->ops, ®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; } /* @@ -262,10 +391,10 @@ static int _i8k_get_fan_type(const struct dell_smm_data *data, u8 fan) .ebx = fan, }; - if (data->disallow_fan_support || data->disallow_fan_type_call) + if (disallow_fan_support || disallow_fan_type_call) return -EINVAL; - return i8k_smm(®s) ? : regs.eax & 0xff; + return dell_smm_call(data->ops, ®s) ? : regs.eax & 0xff; } static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan) @@ -280,17 +409,17 @@ static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan) /* * Read the fan nominal rpm for specific fan speed. */ -static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed) +static int i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed) { struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, .ebx = fan | (speed << 8), }; - if (data->disallow_fan_support) + if (disallow_fan_support) return -EINVAL; - return i8k_smm(®s) ? : (regs.eax & 0xffff); + return dell_smm_call(data->ops, ®s) ? : (regs.eax & 0xffff); } /* @@ -300,11 +429,11 @@ static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enabl { struct smm_regs regs = { }; - if (data->disallow_fan_support) + if (disallow_fan_support) return -EINVAL; - regs.eax = enable ? data->auto_fan : data->manual_fan; - return i8k_smm(®s); + regs.eax = enable ? auto_fan : manual_fan; + return dell_smm_call(data->ops, ®s); } /* @@ -314,41 +443,41 @@ static int i8k_set_fan(const struct dell_smm_data *data, u8 fan, int speed) { struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, }; - if (data->disallow_fan_support) + if (disallow_fan_support) return -EINVAL; speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed); regs.ebx = fan | (speed << 8); - return i8k_smm(®s); + return dell_smm_call(data->ops, ®s); } -static int __init i8k_get_temp_type(u8 sensor) +static int i8k_get_temp_type(const struct dell_smm_data *data, u8 sensor) { struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, .ebx = sensor, }; - return i8k_smm(®s) ? : regs.eax & 0xff; + return dell_smm_call(data->ops, ®s) ? : regs.eax & 0xff; } /* * Read the cpu temperature. */ -static int _i8k_get_temp(u8 sensor) +static int _i8k_get_temp(const struct dell_smm_data *data, u8 sensor) { struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP, .ebx = sensor, }; - return i8k_smm(®s) ? : regs.eax & 0xff; + return dell_smm_call(data->ops, ®s) ? : regs.eax & 0xff; } -static int i8k_get_temp(u8 sensor) +static int i8k_get_temp(const struct dell_smm_data *data, u8 sensor) { - int temp = _i8k_get_temp(sensor); + int temp = _i8k_get_temp(data, sensor); /* * Sometimes the temperature sensor returns 0x99, which is out of range. @@ -359,7 +488,7 @@ static int i8k_get_temp(u8 sensor) */ if (temp == 0x99) { msleep(100); - temp = _i8k_get_temp(sensor); + temp = _i8k_get_temp(data, sensor); } /* * Return -ENODATA for all invalid temperatures. @@ -375,12 +504,12 @@ static int i8k_get_temp(u8 sensor) return temp; } -static int __init i8k_get_dell_signature(int req_fn) +static int dell_smm_get_signature(const struct dell_smm_ops *ops, int req_fn) { struct smm_regs regs = { .eax = req_fn, }; int rc; - rc = i8k_smm(®s); + rc = dell_smm_call(ops, ®s); if (rc < 0) return rc; @@ -392,12 +521,12 @@ static int __init i8k_get_dell_signature(int req_fn) /* * Read the Fn key status. */ -static int i8k_get_fn_status(void) +static int i8k_get_fn_status(const struct dell_smm_data *data) { struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, }; int rc; - rc = i8k_smm(®s); + rc = dell_smm_call(data->ops, ®s); if (rc < 0) return rc; @@ -416,12 +545,12 @@ static int i8k_get_fn_status(void) /* * Read the power status. */ -static int i8k_get_power_status(void) +static int i8k_get_power_status(const struct dell_smm_data *data) { struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, }; int rc; - rc = i8k_smm(®s); + rc = dell_smm_call(data->ops, ®s); if (rc < 0) return rc; @@ -464,15 +593,15 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) return 0; case I8K_FN_STATUS: - val = i8k_get_fn_status(); + val = i8k_get_fn_status(data); break; case I8K_POWER_STATUS: - val = i8k_get_power_status(); + val = i8k_get_power_status(data); break; case I8K_GET_TEMP: - val = i8k_get_temp(0); + val = i8k_get_temp(data, 0); break; case I8K_GET_SPEED: @@ -539,14 +668,14 @@ static int i8k_proc_show(struct seq_file *seq, void *offset) int fn_key, cpu_temp, ac_power; int left_fan, right_fan, left_speed, right_speed; - cpu_temp = i8k_get_temp(0); /* 11100 µs */ + cpu_temp = i8k_get_temp(data, 0); /* 11100 µs */ left_fan = i8k_get_fan_status(data, I8K_FAN_LEFT); /* 580 µs */ right_fan = i8k_get_fan_status(data, I8K_FAN_RIGHT); /* 580 µs */ left_speed = i8k_get_fan_speed(data, I8K_FAN_LEFT); /* 580 µs */ right_speed = i8k_get_fan_speed(data, I8K_FAN_RIGHT); /* 580 µs */ - fn_key = i8k_get_fn_status(); /* 750 µs */ + fn_key = i8k_get_fn_status(data); /* 750 µs */ if (power_status) - ac_power = i8k_get_power_status(); /* 14700 µs */ + ac_power = i8k_get_power_status(data); /* 14700 µs */ else ac_power = -1; @@ -597,6 +726,11 @@ static void __init i8k_init_procfs(struct device *dev) { struct dell_smm_data *data = dev_get_drvdata(dev); + strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), + sizeof(data->bios_version)); + strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), + sizeof(data->bios_machineid)); + /* Only register exit function if creation was successful */ if (proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data)) devm_add_action_or_reset(dev, i8k_exit_procfs, NULL); @@ -665,7 +799,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types switch (attr) { case hwmon_temp_input: /* _i8k_get_temp() is fine since we do not care about the actual value */ - if (data->temp_type[channel] >= 0 || _i8k_get_temp(channel) >= 0) + if (data->temp_type[channel] >= 0 || _i8k_get_temp(data, channel) >= 0) return 0444; break; @@ -679,7 +813,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types } break; case hwmon_fan: - if (data->disallow_fan_support) + if (disallow_fan_support) break; switch (attr) { @@ -689,7 +823,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types break; case hwmon_fan_label: - if (data->fan[channel] && !data->disallow_fan_type_call) + if (data->fan[channel] && !disallow_fan_type_call) return 0444; break; @@ -705,7 +839,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types } break; case hwmon_pwm: - if (data->disallow_fan_support) + if (disallow_fan_support) break; switch (attr) { @@ -715,7 +849,7 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types break; case hwmon_pwm_enable: - if (data->auto_fan) + if (auto_fan) /* * There is no command for retrieve the current status * from BIOS, and userspace/firmware itself can change @@ -747,7 +881,7 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a case hwmon_temp: switch (attr) { case hwmon_temp_input: - ret = i8k_get_temp(channel); + ret = i8k_get_temp(data, channel); if (ret < 0) return ret; @@ -955,7 +1089,7 @@ static const struct hwmon_chip_info dell_smm_chip_info = { .info = dell_smm_info, }; -static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num) +static int dell_smm_init_cdev(struct device *dev, u8 fan_num) { struct dell_smm_data *data = dev_get_drvdata(dev); struct thermal_cooling_device *cdev; @@ -986,7 +1120,7 @@ static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num) return ret; } -static int __init dell_smm_init_hwmon(struct device *dev) +static int dell_smm_init_hwmon(struct device *dev) { struct dell_smm_data *data = dev_get_drvdata(dev); struct device *dell_smm_hwmon_dev; @@ -994,7 +1128,7 @@ static int __init dell_smm_init_hwmon(struct device *dev) u8 i; for (i = 0; i < DELL_SMM_NO_TEMP; i++) { - data->temp_type[i] = i8k_get_temp_type(i); + data->temp_type[i] = i8k_get_temp_type(data, i); if (data->temp_type[i] < 0) continue; @@ -1052,41 +1186,25 @@ static int __init dell_smm_init_hwmon(struct device *dev) return PTR_ERR_OR_ZERO(dell_smm_hwmon_dev); } -struct i8k_config_data { - uint fan_mult; - uint fan_max; -}; +static int dell_smm_init_data(struct device *dev, const struct dell_smm_ops *ops) +{ + struct dell_smm_data *data; -enum i8k_configs { - DELL_LATITUDE_D520, - DELL_PRECISION_490, - DELL_STUDIO, - DELL_XPS, -}; + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; -/* - * Only use for machines which need some special configuration - * in order to work correctly (e.g. if autoconfig fails on this machines). - */ + mutex_init(&data->i8k_mutex); + dev_set_drvdata(dev, data); -static const struct i8k_config_data i8k_config_data[] __initconst = { - [DELL_LATITUDE_D520] = { - .fan_mult = 1, - .fan_max = I8K_FAN_TURBO, - }, - [DELL_PRECISION_490] = { - .fan_mult = 1, - .fan_max = I8K_FAN_TURBO, - }, - [DELL_STUDIO] = { - .fan_mult = 1, - .fan_max = I8K_FAN_HIGH, - }, - [DELL_XPS] = { - .fan_mult = 1, - .fan_max = I8K_FAN_HIGH, - }, -}; + data->ops = ops; + /* All options must not be 0 */ + data->i8k_fan_mult = fan_mult ? : I8K_FAN_MULT; + data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH; + data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max); + + return 0; +} static const struct dmi_system_id i8k_dmi_table[] __initconst = { { @@ -1118,14 +1236,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = { }, }, { - .ident = "Dell Latitude D520", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"), - }, - .driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520], - }, - { .ident = "Dell Latitude 2", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), @@ -1147,15 +1257,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = { }, }, { - .ident = "Dell Precision 490", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, - "Precision WorkStation 490"), - }, - .driver_data = (void *)&i8k_config_data[DELL_PRECISION_490], - }, - { .ident = "Dell Precision", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), @@ -1175,7 +1276,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), DMI_MATCH(DMI_PRODUCT_NAME, "Studio"), }, - .driver_data = (void *)&i8k_config_data[DELL_STUDIO], }, { .ident = "Dell XPS M140", @@ -1183,7 +1283,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"), }, - .driver_data = (void *)&i8k_config_data[DELL_XPS], }, { .ident = "Dell XPS", @@ -1198,6 +1297,78 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = { MODULE_DEVICE_TABLE(dmi, i8k_dmi_table); /* + * Only use for machines which need some special configuration + * in order to work correctly (e.g. if autoconfig fails on this machines). + */ +struct i8k_config_data { + uint fan_mult; + uint fan_max; +}; + +enum i8k_configs { + DELL_LATITUDE_D520, + DELL_PRECISION_490, + DELL_STUDIO, + DELL_XPS, +}; + +static const struct i8k_config_data i8k_config_data[] __initconst = { + [DELL_LATITUDE_D520] = { + .fan_mult = 1, + .fan_max = I8K_FAN_TURBO, + }, + [DELL_PRECISION_490] = { + .fan_mult = 1, + .fan_max = I8K_FAN_TURBO, + }, + [DELL_STUDIO] = { + .fan_mult = 1, + .fan_max = I8K_FAN_HIGH, + }, + [DELL_XPS] = { + .fan_mult = 1, + .fan_max = I8K_FAN_HIGH, + }, +}; + +static const struct dmi_system_id i8k_config_dmi_table[] __initconst = { + { + .ident = "Dell Latitude D520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"), + }, + .driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520], + }, + { + .ident = "Dell Precision 490", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, + "Precision WorkStation 490"), + }, + .driver_data = (void *)&i8k_config_data[DELL_PRECISION_490], + }, + { + .ident = "Dell Studio", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Studio"), + }, + .driver_data = (void *)&i8k_config_data[DELL_STUDIO], + }, + { + .ident = "Dell XPS M140", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"), + }, + .driver_data = (void *)&i8k_config_data[DELL_XPS], + }, + { } +}; + +/* * On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed * randomly going up and down due to bug in Dell SMM or BIOS. Here is blacklist * of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call. @@ -1338,119 +1509,174 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = { }, .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], }, + { + .ident = "Dell Optiplex 7000", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7000"), + }, + .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], + }, { } }; +/* + * Legacy SMM backend driver. + */ static int __init dell_smm_probe(struct platform_device *pdev) { - struct dell_smm_data *data; - const struct dmi_system_id *id, *fan_control; int ret; - data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL); - if (!data) + ret = dell_smm_init_data(&pdev->dev, &i8k_smm_ops); + if (ret < 0) + return ret; + + ret = dell_smm_init_hwmon(&pdev->dev); + if (ret) + return ret; + + i8k_init_procfs(&pdev->dev); + + return 0; +} + +static struct platform_driver dell_smm_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, +}; + +static struct platform_device *dell_smm_device; + +/* + * WMI SMM backend driver. + */ +static int dell_smm_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct dell_smm_ops *ops; + int ret; + + ops = devm_kzalloc(&wdev->dev, sizeof(*ops), GFP_KERNEL); + if (!ops) return -ENOMEM; - mutex_init(&data->i8k_mutex); - platform_set_drvdata(pdev, data); + ops->smm_call = wmi_smm_call; + ops->smm_dev = &wdev->dev; + + if (dell_smm_get_signature(ops, I8K_SMM_GET_DELL_SIG1) && + dell_smm_get_signature(ops, I8K_SMM_GET_DELL_SIG2)) + return -ENODEV; + + ret = dell_smm_init_data(&wdev->dev, ops); + if (ret < 0) + return ret; + + return dell_smm_init_hwmon(&wdev->dev); +} + +static const struct wmi_device_id dell_smm_wmi_id_table[] = { + { DELL_SMM_WMI_GUID, NULL }, + { } +}; +MODULE_DEVICE_TABLE(wmi, dell_smm_wmi_id_table); + +static struct wmi_driver dell_smm_wmi_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = dell_smm_wmi_id_table, + .probe = dell_smm_wmi_probe, +}; + +/* + * Probe for the presence of a supported laptop. + */ +static void __init dell_smm_init_dmi(void) +{ + struct i8k_fan_control_data *control; + struct i8k_config_data *config; + const struct dmi_system_id *id; if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) { if (!force) { - dev_notice(&pdev->dev, "Disabling fan support due to BIOS bugs\n"); - data->disallow_fan_support = true; + pr_notice("Disabling fan support due to BIOS bugs\n"); + disallow_fan_support = true; } else { - dev_warn(&pdev->dev, "Enabling fan support despite BIOS bugs\n"); + pr_warn("Enabling fan support despite BIOS bugs\n"); } } if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) { if (!force) { - dev_notice(&pdev->dev, "Disabling fan type call due to BIOS bugs\n"); - data->disallow_fan_type_call = true; + pr_notice("Disabling fan type call due to BIOS bugs\n"); + disallow_fan_type_call = true; } else { - dev_warn(&pdev->dev, "Enabling fan type call despite BIOS bugs\n"); + pr_warn("Enabling fan type call despite BIOS bugs\n"); } } - strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), - sizeof(data->bios_version)); - strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), - sizeof(data->bios_machineid)); - /* - * Set fan multiplier and maximal fan speed from dmi config - * Values specified in module parameters override values from dmi + * Set fan multiplier and maximal fan speed from DMI config. + * Values specified in module parameters override values from DMI. */ - id = dmi_first_match(i8k_dmi_table); + id = dmi_first_match(i8k_config_dmi_table); if (id && id->driver_data) { - const struct i8k_config_data *conf = id->driver_data; + config = id->driver_data; + if (!fan_mult && config->fan_mult) + fan_mult = config->fan_mult; - if (!fan_mult && conf->fan_mult) - fan_mult = conf->fan_mult; - - if (!fan_max && conf->fan_max) - fan_max = conf->fan_max; + if (!fan_max && config->fan_max) + fan_max = config->fan_max; } - /* All options must not be 0 */ - data->i8k_fan_mult = fan_mult ? : I8K_FAN_MULT; - data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH; - data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max); - - fan_control = dmi_first_match(i8k_whitelist_fan_control); - if (fan_control && fan_control->driver_data) { - const struct i8k_fan_control_data *control = fan_control->driver_data; + id = dmi_first_match(i8k_whitelist_fan_control); + if (id && id->driver_data) { + control = id->driver_data; + manual_fan = control->manual_fan; + auto_fan = control->auto_fan; - data->manual_fan = control->manual_fan; - data->auto_fan = control->auto_fan; - dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n"); + pr_info("Enabling support for setting automatic/manual fan control\n"); } - - ret = dell_smm_init_hwmon(&pdev->dev); - if (ret) - return ret; - - i8k_init_procfs(&pdev->dev); - - return 0; } -static struct platform_driver dell_smm_driver = { - .driver = { - .name = KBUILD_MODNAME, - }, -}; - -static struct platform_device *dell_smm_device; - -/* - * Probe for the presence of a supported laptop. - */ -static int __init i8k_init(void) +static int __init dell_smm_legacy_check(void) { - /* - * Get DMI information - */ if (!dmi_check_system(i8k_dmi_table)) { if (!ignore_dmi && !force) return -ENODEV; - pr_info("not running on a supported Dell system.\n"); + pr_info("Probing for legacy SMM handler on unsupported machine\n"); pr_info("vendor=%s, model=%s, version=%s\n", i8k_get_dmi_data(DMI_SYS_VENDOR), i8k_get_dmi_data(DMI_PRODUCT_NAME), i8k_get_dmi_data(DMI_BIOS_VERSION)); } - /* - * Get SMM Dell signature - */ - if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) && - i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) { + if (dell_smm_get_signature(&i8k_smm_ops, I8K_SMM_GET_DELL_SIG1) && + dell_smm_get_signature(&i8k_smm_ops, I8K_SMM_GET_DELL_SIG2)) { if (!force) return -ENODEV; - pr_err("Unable to get Dell SMM signature\n"); + pr_warn("Forcing legacy SMM calls on a possibly incompatible machine\n"); + } + + return 0; +} + +static int __init i8k_init(void) +{ + int ret; + + dell_smm_init_dmi(); + + ret = dell_smm_legacy_check(); + if (ret < 0) { + /* + * On modern machines, SMM communication happens over WMI, meaning + * the SMM handler might not react to legacy SMM calls. + */ + return wmi_driver_register(&dell_smm_wmi_driver); } dell_smm_device = platform_create_bundle(&dell_smm_driver, dell_smm_probe, NULL, 0, NULL, @@ -1461,8 +1687,12 @@ static int __init i8k_init(void) static void __exit i8k_exit(void) { - platform_device_unregister(dell_smm_device); - platform_driver_unregister(&dell_smm_driver); + if (dell_smm_device) { + platform_device_unregister(dell_smm_device); + platform_driver_unregister(&dell_smm_driver); + } else { + wmi_driver_unregister(&dell_smm_wmi_driver); + } } module_init(i8k_init); diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c index bb7c859e79..1332e4ac07 100644 --- a/drivers/hwmon/emc1403.c +++ b/drivers/hwmon/emc1403.c @@ -346,6 +346,9 @@ static int emc1403_detect(struct i2c_client *client, case 0x27: strscpy(info->type, "emc1424", I2C_NAME_SIZE); break; + case 0x60: + strscpy(info->type, "emc1442", I2C_NAME_SIZE); + break; default: return -ENODEV; } @@ -430,7 +433,7 @@ static int emc1403_probe(struct i2c_client *client) } static const unsigned short emc1403_address_list[] = { - 0x18, 0x1c, 0x29, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END + 0x18, 0x1c, 0x29, 0x3c, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END }; /* Last digit of chip name indicates number of channels */ @@ -444,6 +447,7 @@ static const struct i2c_device_id emc1403_idtable[] = { { "emc1422", emc1402 }, { "emc1423", emc1403 }, { "emc1424", emc1404 }, + { "emc1442", emc1402 }, { } }; MODULE_DEVICE_TABLE(i2c, emc1403_idtable); diff --git a/drivers/hwmon/gigabyte_waterforce.c b/drivers/hwmon/gigabyte_waterforce.c new file mode 100644 index 0000000000..8129d7b3ce --- /dev/null +++ b/drivers/hwmon/gigabyte_waterforce.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * hwmon driver for Gigabyte AORUS Waterforce AIO CPU coolers: X240, X280 and X360. + * + * Copyright 2023 Aleksa Savic <savicaleksa83@gmail.com> + */ + +#include <linux/debugfs.h> +#include <linux/hid.h> +#include <linux/hwmon.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <asm/unaligned.h> + +#define DRIVER_NAME "gigabyte_waterforce" + +#define USB_VENDOR_ID_GIGABYTE 0x1044 +#define USB_PRODUCT_ID_WATERFORCE 0x7a4d /* Gigabyte AORUS WATERFORCE X240, X280 and X360 */ + +#define STATUS_VALIDITY (2 * 1000) /* ms */ +#define MAX_REPORT_LENGTH 6144 + +#define WATERFORCE_TEMP_SENSOR 0xD +#define WATERFORCE_FAN_SPEED 0x02 +#define WATERFORCE_PUMP_SPEED 0x05 +#define WATERFORCE_FAN_DUTY 0x08 +#define WATERFORCE_PUMP_DUTY 0x09 + +/* Control commands, inner offsets and lengths */ +static const u8 get_status_cmd[] = { 0x99, 0xDA }; + +#define FIRMWARE_VER_START_OFFSET_1 2 +#define FIRMWARE_VER_START_OFFSET_2 3 +static const u8 get_firmware_ver_cmd[] = { 0x99, 0xD6 }; + +/* Command lengths */ +#define GET_STATUS_CMD_LENGTH 2 +#define GET_FIRMWARE_VER_CMD_LENGTH 2 + +static const char *const waterforce_temp_label[] = { + "Coolant temp" +}; + +static const char *const waterforce_speed_label[] = { + "Fan speed", + "Pump speed" +}; + +struct waterforce_data { + struct hid_device *hdev; + struct device *hwmon_dev; + struct dentry *debugfs; + /* For locking access to buffer */ + struct mutex buffer_lock; + /* For queueing multiple readers */ + struct mutex status_report_request_mutex; + /* For reinitializing the completion below */ + spinlock_t status_report_request_lock; + struct completion status_report_received; + struct completion fw_version_processed; + + /* Sensor data */ + s32 temp_input[1]; + u16 speed_input[2]; /* Fan and pump speed in RPM */ + u8 duty_input[2]; /* Fan and pump duty in 0-100% */ + + u8 *buffer; + int firmware_version; + unsigned long updated; /* jiffies */ +}; + +static umode_t waterforce_is_visible(const void *data, + enum hwmon_sensor_types type, u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + case hwmon_temp_input: + return 0444; + default: + break; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_label: + case hwmon_fan_input: + return 0444; + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + return 0444; + default: + break; + } + break; + default: + break; + } + + return 0; +} + +/* Writes the command to the device with the rest of the report filled with zeroes */ +static int waterforce_write_expanded(struct waterforce_data *priv, const u8 *cmd, int cmd_length) +{ + int ret; + + mutex_lock(&priv->buffer_lock); + + memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00); + ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH); + + mutex_unlock(&priv->buffer_lock); + return ret; +} + +static int waterforce_get_status(struct waterforce_data *priv) +{ + int ret = mutex_lock_interruptible(&priv->status_report_request_mutex); + + if (ret < 0) + return ret; + + if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) { + /* Data is up to date */ + goto unlock_and_return; + } + + /* + * Disable raw event parsing for a moment to safely reinitialize the + * completion. Reinit is done because hidraw could have triggered + * the raw event parsing and marked the priv->status_report_received + * completion as done. + */ + spin_lock_bh(&priv->status_report_request_lock); + reinit_completion(&priv->status_report_received); + spin_unlock_bh(&priv->status_report_request_lock); + + /* Send command for getting status */ + ret = waterforce_write_expanded(priv, get_status_cmd, GET_STATUS_CMD_LENGTH); + if (ret < 0) + goto unlock_and_return; + + ret = wait_for_completion_interruptible_timeout(&priv->status_report_received, + msecs_to_jiffies(STATUS_VALIDITY)); + if (ret == 0) + ret = -ETIMEDOUT; + +unlock_and_return: + mutex_unlock(&priv->status_report_request_mutex); + if (ret < 0) + return ret; + + return 0; +} + +static int waterforce_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct waterforce_data *priv = dev_get_drvdata(dev); + int ret = waterforce_get_status(priv); + + if (ret < 0) + return ret; + + switch (type) { + case hwmon_temp: + *val = priv->temp_input[channel]; + break; + case hwmon_fan: + *val = priv->speed_input[channel]; + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + *val = DIV_ROUND_CLOSEST(priv->duty_input[channel] * 255, 100); + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; /* unreachable */ + } + + return 0; +} + +static int waterforce_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + *str = waterforce_temp_label[channel]; + break; + case hwmon_fan: + *str = waterforce_speed_label[channel]; + break; + default: + return -EOPNOTSUPP; /* unreachable */ + } + + return 0; +} + +static int waterforce_get_fw_ver(struct hid_device *hdev) +{ + struct waterforce_data *priv = hid_get_drvdata(hdev); + int ret; + + ret = waterforce_write_expanded(priv, get_firmware_ver_cmd, GET_FIRMWARE_VER_CMD_LENGTH); + if (ret < 0) + return ret; + + ret = wait_for_completion_interruptible_timeout(&priv->fw_version_processed, + msecs_to_jiffies(STATUS_VALIDITY)); + if (ret == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + return 0; +} + +static const struct hwmon_ops waterforce_hwmon_ops = { + .is_visible = waterforce_is_visible, + .read = waterforce_read, + .read_string = waterforce_read_string +}; + +static const struct hwmon_channel_info *waterforce_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT), + NULL +}; + +static const struct hwmon_chip_info waterforce_chip_info = { + .ops = &waterforce_hwmon_ops, + .info = waterforce_info, +}; + +static int waterforce_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, + int size) +{ + struct waterforce_data *priv = hid_get_drvdata(hdev); + + if (data[0] == get_firmware_ver_cmd[0] && data[1] == get_firmware_ver_cmd[1]) { + /* Received a firmware version report */ + priv->firmware_version = + data[FIRMWARE_VER_START_OFFSET_1] * 10 + data[FIRMWARE_VER_START_OFFSET_2]; + + if (!completion_done(&priv->fw_version_processed)) + complete_all(&priv->fw_version_processed); + return 0; + } + + if (data[0] != get_status_cmd[0] || data[1] != get_status_cmd[1]) + return 0; + + priv->temp_input[0] = data[WATERFORCE_TEMP_SENSOR] * 1000; + priv->speed_input[0] = get_unaligned_le16(data + WATERFORCE_FAN_SPEED); + priv->speed_input[1] = get_unaligned_le16(data + WATERFORCE_PUMP_SPEED); + priv->duty_input[0] = data[WATERFORCE_FAN_DUTY]; + priv->duty_input[1] = data[WATERFORCE_PUMP_DUTY]; + + spin_lock(&priv->status_report_request_lock); + if (!completion_done(&priv->status_report_received)) + complete_all(&priv->status_report_received); + spin_unlock(&priv->status_report_request_lock); + + priv->updated = jiffies; + + return 0; +} + +static int firmware_version_show(struct seq_file *seqf, void *unused) +{ + struct waterforce_data *priv = seqf->private; + + seq_printf(seqf, "%u\n", priv->firmware_version); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(firmware_version); + +static void waterforce_debugfs_init(struct waterforce_data *priv) +{ + char name[64]; + + if (!priv->firmware_version) + return; /* There's nothing to show in debugfs */ + + scnprintf(name, sizeof(name), "%s-%s", DRIVER_NAME, dev_name(&priv->hdev->dev)); + + priv->debugfs = debugfs_create_dir(name, NULL); + debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops); +} + +static int waterforce_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct waterforce_data *priv; + int ret; + + priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->hdev = hdev; + hid_set_drvdata(hdev, priv); + + /* + * Initialize priv->updated to STATUS_VALIDITY seconds in the past, making + * the initial empty data invalid for waterforce_read() without the need for + * a special case there. + */ + priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "hid parse failed with %d\n", ret); + return ret; + } + + /* + * Enable hidraw so existing user-space tools can continue to work. + */ + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "hid hw start failed with %d\n", ret); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "hid hw open failed with %d\n", ret); + goto fail_and_stop; + } + + priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL); + if (!priv->buffer) { + ret = -ENOMEM; + goto fail_and_close; + } + + mutex_init(&priv->status_report_request_mutex); + mutex_init(&priv->buffer_lock); + spin_lock_init(&priv->status_report_request_lock); + init_completion(&priv->status_report_received); + init_completion(&priv->fw_version_processed); + + hid_device_io_start(hdev); + ret = waterforce_get_fw_ver(hdev); + if (ret < 0) + hid_warn(hdev, "fw version request failed with %d\n", ret); + + priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "waterforce", + priv, &waterforce_chip_info, NULL); + if (IS_ERR(priv->hwmon_dev)) { + ret = PTR_ERR(priv->hwmon_dev); + hid_err(hdev, "hwmon registration failed with %d\n", ret); + goto fail_and_close; + } + + waterforce_debugfs_init(priv); + + return 0; + +fail_and_close: + hid_hw_close(hdev); +fail_and_stop: + hid_hw_stop(hdev); + return ret; +} + +static void waterforce_remove(struct hid_device *hdev) +{ + struct waterforce_data *priv = hid_get_drvdata(hdev); + + debugfs_remove_recursive(priv->debugfs); + hwmon_device_unregister(priv->hwmon_dev); + + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static const struct hid_device_id waterforce_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_GIGABYTE, USB_PRODUCT_ID_WATERFORCE) }, + { } +}; + +MODULE_DEVICE_TABLE(hid, waterforce_table); + +static struct hid_driver waterforce_driver = { + .name = "waterforce", + .id_table = waterforce_table, + .probe = waterforce_probe, + .remove = waterforce_remove, + .raw_event = waterforce_raw_event, +}; + +static int __init waterforce_init(void) +{ + return hid_register_driver(&waterforce_driver); +} + +static void __exit waterforce_exit(void) +{ + hid_unregister_driver(&waterforce_driver); +} + +/* When compiled into the kernel, initialize after the HID bus */ +late_initcall(waterforce_init); +module_exit(waterforce_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>"); +MODULE_DESCRIPTION("Hwmon driver for Gigabyte AORUS Waterforce AIO coolers"); diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index bae0becfa2..8092312c0a 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -455,6 +455,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) switch (boot_cpu_data.x86_model) { case 0x0 ... 0x1: /* Zen3 SP3/TR */ + case 0x8: /* Zen3 TR Chagall */ case 0x21: /* Zen3 Ryzen Desktop */ case 0x50 ... 0x5f: /* Green Sardine */ data->ccd_offset = 0x154; diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 5b2ea05c95..e007507185 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -7,11 +7,11 @@ #include <linux/module.h> #include <linux/init.h> +#include <linux/interrupt.h> #include <linux/slab.h> #include <linux/jiffies.h> #include <linux/i2c.h> #include <linux/hwmon.h> -#include <linux/hwmon-sysfs.h> #include <linux/err.h> #include <linux/of.h> #include <linux/regmap.h> @@ -25,6 +25,7 @@ enum lm75_type { /* keep sorted in alphabetical order */ adt75, + as6200, at30ts74, ds1775, ds75, @@ -55,6 +56,7 @@ enum lm75_type { /* keep sorted in alphabetical order */ /** * struct lm75_params - lm75 configuration parameters. + * @config_reg_16bits: Configure register size is 2 bytes. * @set_mask: Bits to set in configuration register when configuring * the chip. * @clr_mask: Bits to clear in configuration register when configuring @@ -75,17 +77,20 @@ enum lm75_type { /* keep sorted in alphabetical order */ * @sample_times: All the possible sample times to be set. Mandatory if * num_sample_times is larger than 1. If set, number of * entries must match num_sample_times. + * @alarm: Alarm bit is supported. */ struct lm75_params { - u8 set_mask; - u8 clr_mask; + bool config_reg_16bits; + u16 set_mask; + u16 clr_mask; u8 default_resolution; u8 resolution_limits; const u8 *resolutions; unsigned int default_sample_time; u8 num_sample_times; const unsigned int *sample_times; + bool alarm; }; /* Addresses scanned */ @@ -104,8 +109,8 @@ struct lm75_data { struct i2c_client *client; struct regmap *regmap; struct regulator *vs; - u8 orig_conf; - u8 current_conf; + u16 orig_conf; + u16 current_conf; u8 resolution; /* In bits, 9 to 16 */ unsigned int sample_time; /* In ms */ enum lm75_type kind; @@ -128,6 +133,15 @@ static const struct lm75_params device_params[] = { .default_resolution = 12, .default_sample_time = MSEC_PER_SEC / 10, }, + [as6200] = { + .config_reg_16bits = true, + .set_mask = 0x94C0, /* 8 sample/s, 4 CF, positive polarity */ + .default_resolution = 12, + .default_sample_time = 125, + .num_sample_times = 4, + .sample_times = (unsigned int []){ 125, 250, 1000, 4000 }, + .alarm = true, + }, [at30ts74] = { .set_mask = 3 << 5, /* 12-bit mode*/ .default_resolution = 12, @@ -255,8 +269,9 @@ static const struct lm75_params device_params[] = { .resolutions = (u8 []) {9, 10, 11, 12 }, }, [tmp112] = { - .set_mask = 3 << 5, /* 8 samples / second */ - .clr_mask = 1 << 7, /* no one-shot mode*/ + .config_reg_16bits = true, + .set_mask = 0x60C0, /* 12-bit mode, 8 samples / second */ + .clr_mask = 1 << 15, /* no one-shot mode*/ .default_resolution = 12, .default_sample_time = 125, .num_sample_times = 4, @@ -317,20 +332,23 @@ static inline long lm75_reg_to_mc(s16 temp, u8 resolution) return ((temp >> (16 - resolution)) * 1000) >> (resolution - 8); } -static int lm75_write_config(struct lm75_data *data, u8 set_mask, - u8 clr_mask) +static int lm75_write_config(struct lm75_data *data, u16 set_mask, + u16 clr_mask) { - u8 value; + unsigned int value; - clr_mask |= LM75_SHUTDOWN; + clr_mask |= LM75_SHUTDOWN << (8 * data->params->config_reg_16bits); value = data->current_conf & ~clr_mask; value |= set_mask; if (data->current_conf != value) { s32 err; - - err = i2c_smbus_write_byte_data(data->client, LM75_REG_CONF, - value); + if (data->params->config_reg_16bits) + err = regmap_write(data->regmap, LM75_REG_CONF, value); + else + err = i2c_smbus_write_byte_data(data->client, + LM75_REG_CONF, + value); if (err) return err; data->current_conf = value; @@ -338,6 +356,27 @@ static int lm75_write_config(struct lm75_data *data, u8 set_mask, return 0; } +static int lm75_read_config(struct lm75_data *data) +{ + int ret; + unsigned int status; + + if (data->params->config_reg_16bits) { + ret = regmap_read(data->regmap, LM75_REG_CONF, &status); + return ret ? ret : status; + } + + return i2c_smbus_read_byte_data(data->client, LM75_REG_CONF); +} + +static irqreturn_t lm75_alarm_handler(int irq, void *private) +{ + struct device *hwmon_dev = private; + + hwmon_notify_event(hwmon_dev, hwmon_temp, hwmon_temp_alarm, 0); + return IRQ_HANDLED; +} + static int lm75_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { @@ -366,6 +405,9 @@ static int lm75_read(struct device *dev, enum hwmon_sensor_types type, case hwmon_temp_max_hyst: reg = LM75_REG_HYST; break; + case hwmon_temp_alarm: + reg = LM75_REG_CONF; + break; default: return -EINVAL; } @@ -373,7 +415,17 @@ static int lm75_read(struct device *dev, enum hwmon_sensor_types type, if (err < 0) return err; - *val = lm75_reg_to_mc(regval, data->resolution); + if (attr == hwmon_temp_alarm) { + switch (data->kind) { + case as6200: + *val = (regval >> 5) & 0x1; + break; + default: + return -EINVAL; + } + } else { + *val = lm75_reg_to_mc(regval, data->resolution); + } break; default: return -EINVAL; @@ -436,6 +488,7 @@ static int lm75_update_interval(struct device *dev, long val) data->resolution = data->params->resolutions[index]; break; case tmp112: + case as6200: err = regmap_read(data->regmap, LM75_REG_CONF, ®); if (err < 0) return err; @@ -503,6 +556,10 @@ static umode_t lm75_is_visible(const void *data, enum hwmon_sensor_types type, case hwmon_temp_max: case hwmon_temp_max_hyst: return 0644; + case hwmon_temp_alarm: + if (config_data->params->alarm) + return 0444; + break; } break; default: @@ -515,7 +572,8 @@ static const struct hwmon_channel_info * const lm75_info[] = { HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), HWMON_CHANNEL_INFO(temp, - HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST), + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_ALARM), NULL }; @@ -623,7 +681,7 @@ static int lm75_probe(struct i2c_client *client) return err; /* Cache original configuration */ - status = i2c_smbus_read_byte_data(client, LM75_REG_CONF); + status = lm75_read_config(data); if (status < 0) { dev_dbg(dev, "Can't read config? %d\n", status); return status; @@ -646,6 +704,23 @@ static int lm75_probe(struct i2c_client *client) if (IS_ERR(hwmon_dev)) return PTR_ERR(hwmon_dev); + if (client->irq) { + if (data->params->alarm) { + err = devm_request_threaded_irq(dev, + client->irq, + NULL, + &lm75_alarm_handler, + IRQF_ONESHOT, + client->name, + hwmon_dev); + if (err) + return err; + } else { + /* alarm is only supported for chips with alarm bit */ + dev_err(dev, "alarm interrupt is not supported\n"); + } + } + dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name); return 0; @@ -653,6 +728,7 @@ static int lm75_probe(struct i2c_client *client) static const struct i2c_device_id lm75_ids[] = { { "adt75", adt75, }, + { "as6200", as6200, }, { "at30ts74", at30ts74, }, { "ds1775", ds1775, }, { "ds75", ds75, }, @@ -690,6 +766,10 @@ static const struct of_device_id __maybe_unused lm75_of_match[] = { .data = (void *)adt75 }, { + .compatible = "ams,as6200", + .data = (void *)as6200 + }, + { .compatible = "atmel,at30ts74", .data = (void *)at30ts74 }, diff --git a/drivers/hwmon/ltc2991.c b/drivers/hwmon/ltc2991.c index fc53fdcb2b..80a6e391f2 100644 --- a/drivers/hwmon/ltc2991.c +++ b/drivers/hwmon/ltc2991.c @@ -54,7 +54,6 @@ #define LTC2991_VCC_CH_NR 0 struct ltc2991_state { - struct device *dev; struct regmap *regmap; u32 r_sense_uohm[LTC2991_MAX_CHANNEL]; bool temp_en[LTC2991_MAX_CHANNEL]; @@ -283,19 +282,19 @@ static const struct regmap_config ltc2991_regmap_config = { .max_register = 0x1D, }; -static int ltc2991_init(struct ltc2991_state *st) +static int ltc2991_init(struct ltc2991_state *st, struct device *dev) { struct fwnode_handle *child; int ret; u32 val, addr; u8 v5_v8_reg_data = 0, v1_v4_reg_data = 0; - ret = devm_regulator_get_enable(st->dev, "vcc"); + ret = devm_regulator_get_enable(dev, "vcc"); if (ret) - return dev_err_probe(st->dev, ret, + return dev_err_probe(dev, ret, "failed to enable regulator\n"); - device_for_each_child_node(st->dev, child) { + device_for_each_child_node(dev, child) { ret = fwnode_property_read_u32(child, "reg", &addr); if (ret < 0) { fwnode_handle_put(child); @@ -312,7 +311,7 @@ static int ltc2991_init(struct ltc2991_state *st) &val); if (!ret) { if (!val) - return dev_err_probe(st->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "shunt resistor value cannot be zero\n"); st->r_sense_uohm[addr] = val; @@ -361,18 +360,18 @@ static int ltc2991_init(struct ltc2991_state *st) ret = regmap_write(st->regmap, LTC2991_V5_V8_CTRL, v5_v8_reg_data); if (ret) - return dev_err_probe(st->dev, ret, + return dev_err_probe(dev, ret, "Error: Failed to set V5-V8 CTRL reg.\n"); ret = regmap_write(st->regmap, LTC2991_V1_V4_CTRL, v1_v4_reg_data); if (ret) - return dev_err_probe(st->dev, ret, + return dev_err_probe(dev, ret, "Error: Failed to set V1-V4 CTRL reg.\n"); ret = regmap_write(st->regmap, LTC2991_PWM_TH_LSB_T_INT, LTC2991_REPEAT_ACQ_EN); if (ret) - return dev_err_probe(st->dev, ret, + return dev_err_probe(dev, ret, "Error: Failed to set continuous mode.\n"); /* Enable all channels and trigger conversions */ @@ -392,12 +391,11 @@ static int ltc2991_i2c_probe(struct i2c_client *client) if (!st) return -ENOMEM; - st->dev = &client->dev; st->regmap = devm_regmap_init_i2c(client, <c2991_regmap_config); if (IS_ERR(st->regmap)) return PTR_ERR(st->regmap); - ret = ltc2991_init(st); + ret = ltc2991_init(st, &client->dev); if (ret) return ret; diff --git a/drivers/hwmon/max31827.c b/drivers/hwmon/max31827.c index a1ce651456..4a8c3e37c5 100644 --- a/drivers/hwmon/max31827.c +++ b/drivers/hwmon/max31827.c @@ -11,6 +11,7 @@ #include <linux/hwmon.h> #include <linux/i2c.h> #include <linux/mutex.h> +#include <linux/of_device.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> @@ -23,15 +24,30 @@ #define MAX31827_CONFIGURATION_1SHOT_MASK BIT(0) #define MAX31827_CONFIGURATION_CNV_RATE_MASK GENMASK(3, 1) +#define MAX31827_CONFIGURATION_TIMEOUT_MASK BIT(5) +#define MAX31827_CONFIGURATION_RESOLUTION_MASK GENMASK(7, 6) +#define MAX31827_CONFIGURATION_ALRM_POL_MASK BIT(8) +#define MAX31827_CONFIGURATION_COMP_INT_MASK BIT(9) +#define MAX31827_CONFIGURATION_FLT_Q_MASK GENMASK(11, 10) #define MAX31827_CONFIGURATION_U_TEMP_STAT_MASK BIT(14) #define MAX31827_CONFIGURATION_O_TEMP_STAT_MASK BIT(15) +#define MAX31827_ALRM_POL_LOW 0x0 +#define MAX31827_ALRM_POL_HIGH 0x1 +#define MAX31827_FLT_Q_1 0x0 +#define MAX31827_FLT_Q_4 0x2 + +#define MAX31827_8_BIT_CNV_TIME 9 +#define MAX31827_9_BIT_CNV_TIME 18 +#define MAX31827_10_BIT_CNV_TIME 35 #define MAX31827_12_BIT_CNV_TIME 140 #define MAX31827_16_BIT_TO_M_DGR(x) (sign_extend32(x, 15) * 1000 / 16) #define MAX31827_M_DGR_TO_16_BIT(x) (((x) << 4) / 1000) #define MAX31827_DEVICE_ENABLE(x) ((x) ? 0xA : 0x0) +enum chips { max31827 = 1, max31828, max31829 }; + enum max31827_cnv { MAX31827_CNV_1_DIV_64_HZ = 1, MAX31827_CNV_1_DIV_32_HZ, @@ -52,6 +68,27 @@ static const u16 max31827_conversions[] = { [MAX31827_CNV_8_HZ] = 125, }; +enum max31827_resolution { + MAX31827_RES_8_BIT = 0, + MAX31827_RES_9_BIT, + MAX31827_RES_10_BIT, + MAX31827_RES_12_BIT, +}; + +static const u16 max31827_resolutions[] = { + [MAX31827_RES_8_BIT] = 1000, + [MAX31827_RES_9_BIT] = 500, + [MAX31827_RES_10_BIT] = 250, + [MAX31827_RES_12_BIT] = 62, +}; + +static const u16 max31827_conv_times[] = { + [MAX31827_RES_8_BIT] = MAX31827_8_BIT_CNV_TIME, + [MAX31827_RES_9_BIT] = MAX31827_9_BIT_CNV_TIME, + [MAX31827_RES_10_BIT] = MAX31827_10_BIT_CNV_TIME, + [MAX31827_RES_12_BIT] = MAX31827_12_BIT_CNV_TIME, +}; + struct max31827_state { /* * Prevent simultaneous access to the i2c client. @@ -59,6 +96,8 @@ struct max31827_state { struct mutex lock; struct regmap *regmap; bool enable; + unsigned int resolution; + unsigned int update_interval; }; static const struct regmap_config max31827_regmap = { @@ -68,16 +107,16 @@ static const struct regmap_config max31827_regmap = { }; static int shutdown_write(struct max31827_state *st, unsigned int reg, - unsigned int val) + unsigned int mask, unsigned int val) { unsigned int cfg; unsigned int cnv_rate; int ret; /* - * Before the Temperature Threshold Alarm and Alarm Hysteresis Threshold - * register values are changed over I2C, the part must be in shutdown - * mode. + * Before the Temperature Threshold Alarm, Alarm Hysteresis Threshold + * and Resolution bits from Configuration register are changed over I2C, + * the part must be in shutdown mode. * * Mutex is used to ensure, that some other process doesn't change the * configuration register. @@ -85,7 +124,10 @@ static int shutdown_write(struct max31827_state *st, unsigned int reg, mutex_lock(&st->lock); if (!st->enable) { - ret = regmap_write(st->regmap, reg, val); + if (!mask) + ret = regmap_write(st->regmap, reg, val); + else + ret = regmap_update_bits(st->regmap, reg, mask, val); goto unlock; } @@ -100,7 +142,11 @@ static int shutdown_write(struct max31827_state *st, unsigned int reg, if (ret) goto unlock; - ret = regmap_write(st->regmap, reg, val); + if (!mask) + ret = regmap_write(st->regmap, reg, val); + else + ret = regmap_update_bits(st->regmap, reg, mask, val); + if (ret) goto unlock; @@ -118,7 +164,7 @@ static int write_alarm_val(struct max31827_state *st, unsigned int reg, { val = MAX31827_M_DGR_TO_16_BIT(val); - return shutdown_write(st, reg, val); + return shutdown_write(st, reg, 0, val); } static umode_t max31827_is_visible(const void *state, @@ -188,9 +234,18 @@ static int max31827_read(struct device *dev, enum hwmon_sensor_types type, mutex_unlock(&st->lock); return ret; } - - msleep(MAX31827_12_BIT_CNV_TIME); + msleep(max31827_conv_times[st->resolution]); } + + /* + * For 12-bit resolution the conversion time is 140 ms, + * thus an additional 15 ms is needed to complete the + * conversion: 125 ms + 15 ms = 140 ms + */ + if (max31827_resolutions[st->resolution] == 12 && + st->update_interval == 125) + usleep_range(15000, 20000); + ret = regmap_read(st->regmap, MAX31827_T_REG, &uval); mutex_unlock(&st->lock); @@ -341,17 +396,20 @@ static int max31827_write(struct device *dev, enum hwmon_sensor_types type, val < max31827_conversions[res]) res++; - if (res == ARRAY_SIZE(max31827_conversions) || - val != max31827_conversions[res]) - return -EINVAL; + if (res == ARRAY_SIZE(max31827_conversions)) + res = ARRAY_SIZE(max31827_conversions) - 1; res = FIELD_PREP(MAX31827_CONFIGURATION_CNV_RATE_MASK, res); - return regmap_update_bits(st->regmap, - MAX31827_CONFIGURATION_REG, - MAX31827_CONFIGURATION_CNV_RATE_MASK, - res); + ret = regmap_update_bits(st->regmap, + MAX31827_CONFIGURATION_REG, + MAX31827_CONFIGURATION_CNV_RATE_MASK, + res); + if (ret) + return ret; + + st->update_interval = val; } break; @@ -359,17 +417,165 @@ static int max31827_write(struct device *dev, enum hwmon_sensor_types type, return -EOPNOTSUPP; } - return -EOPNOTSUPP; + return 0; +} + +static ssize_t temp1_resolution_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct max31827_state *st = dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, MAX31827_CONFIGURATION_REG, &val); + if (ret) + return ret; + + val = FIELD_GET(MAX31827_CONFIGURATION_RESOLUTION_MASK, val); + + return scnprintf(buf, PAGE_SIZE, "%u\n", max31827_resolutions[val]); } -static int max31827_init_client(struct max31827_state *st) +static ssize_t temp1_resolution_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) { + struct max31827_state *st = dev_get_drvdata(dev); + unsigned int idx = 0; + unsigned int val; + int ret; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + /* + * Convert the desired resolution into register + * bits. idx is already initialized with 0. + * + * This was inspired by lm73 driver. + */ + while (idx < ARRAY_SIZE(max31827_resolutions) && + val < max31827_resolutions[idx]) + idx++; + + if (idx == ARRAY_SIZE(max31827_resolutions)) + idx = ARRAY_SIZE(max31827_resolutions) - 1; + + st->resolution = idx; + + ret = shutdown_write(st, MAX31827_CONFIGURATION_REG, + MAX31827_CONFIGURATION_RESOLUTION_MASK, + FIELD_PREP(MAX31827_CONFIGURATION_RESOLUTION_MASK, + idx)); + + return ret ? ret : count; +} + +static DEVICE_ATTR_RW(temp1_resolution); + +static struct attribute *max31827_attrs[] = { + &dev_attr_temp1_resolution.attr, + NULL +}; +ATTRIBUTE_GROUPS(max31827); + +static const struct i2c_device_id max31827_i2c_ids[] = { + { "max31827", max31827 }, + { "max31828", max31828 }, + { "max31829", max31829 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max31827_i2c_ids); + +static int max31827_init_client(struct max31827_state *st, + struct device *dev) +{ + struct fwnode_handle *fwnode; + unsigned int res = 0; + u32 data, lsb_idx; + enum chips type; + bool prop; + int ret; + + fwnode = dev_fwnode(dev); + st->enable = true; + res |= MAX31827_DEVICE_ENABLE(1); - return regmap_update_bits(st->regmap, MAX31827_CONFIGURATION_REG, - MAX31827_CONFIGURATION_1SHOT_MASK | - MAX31827_CONFIGURATION_CNV_RATE_MASK, - MAX31827_DEVICE_ENABLE(1)); + res |= MAX31827_CONFIGURATION_RESOLUTION_MASK; + + prop = fwnode_property_read_bool(fwnode, "adi,comp-int"); + res |= FIELD_PREP(MAX31827_CONFIGURATION_COMP_INT_MASK, prop); + + prop = fwnode_property_read_bool(fwnode, "adi,timeout-enable"); + res |= FIELD_PREP(MAX31827_CONFIGURATION_TIMEOUT_MASK, !prop); + + type = (enum chips)(uintptr_t)device_get_match_data(dev); + + if (fwnode_property_present(fwnode, "adi,alarm-pol")) { + ret = fwnode_property_read_u32(fwnode, "adi,alarm-pol", &data); + if (ret) + return ret; + + res |= FIELD_PREP(MAX31827_CONFIGURATION_ALRM_POL_MASK, !!data); + } else { + /* + * Set default value. + */ + switch (type) { + case max31827: + case max31828: + res |= FIELD_PREP(MAX31827_CONFIGURATION_ALRM_POL_MASK, + MAX31827_ALRM_POL_LOW); + break; + case max31829: + res |= FIELD_PREP(MAX31827_CONFIGURATION_ALRM_POL_MASK, + MAX31827_ALRM_POL_HIGH); + break; + default: + return -EOPNOTSUPP; + } + } + + if (fwnode_property_present(fwnode, "adi,fault-q")) { + ret = fwnode_property_read_u32(fwnode, "adi,fault-q", &data); + if (ret) + return ret; + + /* + * Convert the desired fault queue into register bits. + */ + if (data != 0) + lsb_idx = __ffs(data); + + if (hweight32(data) != 1 || lsb_idx > 4) { + dev_err(dev, "Invalid data in adi,fault-q\n"); + return -EINVAL; + } + + res |= FIELD_PREP(MAX31827_CONFIGURATION_FLT_Q_MASK, lsb_idx); + } else { + /* + * Set default value. + */ + switch (type) { + case max31827: + res |= FIELD_PREP(MAX31827_CONFIGURATION_FLT_Q_MASK, + MAX31827_FLT_Q_1); + break; + case max31828: + case max31829: + res |= FIELD_PREP(MAX31827_CONFIGURATION_FLT_Q_MASK, + MAX31827_FLT_Q_4); + break; + default: + return -EOPNOTSUPP; + } + } + + return regmap_write(st->regmap, MAX31827_CONFIGURATION_REG, res); } static const struct hwmon_channel_info *max31827_info[] = { @@ -417,25 +623,30 @@ static int max31827_probe(struct i2c_client *client) if (err) return dev_err_probe(dev, err, "failed to enable regulator\n"); - err = max31827_init_client(st); + err = max31827_init_client(st, dev); if (err) return err; hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, st, &max31827_chip_info, - NULL); + max31827_groups); return PTR_ERR_OR_ZERO(hwmon_dev); } -static const struct i2c_device_id max31827_i2c_ids[] = { - { "max31827", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, max31827_i2c_ids); - static const struct of_device_id max31827_of_match[] = { - { .compatible = "adi,max31827" }, + { + .compatible = "adi,max31827", + .data = (void *)max31827 + }, + { + .compatible = "adi,max31828", + .data = (void *)max31828 + }, + { + .compatible = "adi,max31829", + .data = (void *)max31829 + }, { } }; MODULE_DEVICE_TABLE(of, max31827_of_match); diff --git a/drivers/hwmon/max6650.c b/drivers/hwmon/max6650.c index cc8428a304..9649c6611d 100644 --- a/drivers/hwmon/max6650.c +++ b/drivers/hwmon/max6650.c @@ -26,7 +26,7 @@ #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> #include <linux/err.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/thermal.h> /* @@ -763,8 +763,6 @@ static int max6650_probe(struct i2c_client *client) { struct thermal_cooling_device *cooling_dev; struct device *dev = &client->dev; - const struct of_device_id *of_id = - of_match_device(of_match_ptr(max6650_dt_match), dev); struct max6650_data *data; struct device *hwmon_dev; int err; @@ -776,8 +774,8 @@ static int max6650_probe(struct i2c_client *client) data->client = client; i2c_set_clientdata(client, data); mutex_init(&data->update_lock); - data->nr_fans = of_id ? (int)(uintptr_t)of_id->data : - i2c_match_id(max6650_id, client)->driver_data; + + data->nr_fans = (uintptr_t)i2c_get_match_data(client); /* * Initialize the max6650 chip diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c index f3bf2e4701..9fbab8f023 100644 --- a/drivers/hwmon/nct6775-core.c +++ b/drivers/hwmon/nct6775-core.c @@ -63,19 +63,19 @@ /* used to set data->name = nct6775_device_names[data->sio_kind] */ static const char * const nct6775_device_names[] = { - "nct6106", - "nct6116", - "nct6775", - "nct6776", - "nct6779", - "nct6791", - "nct6792", - "nct6793", - "nct6795", - "nct6796", - "nct6797", - "nct6798", - "nct6799", + [nct6106] = "nct6106", + [nct6116] = "nct6116", + [nct6775] = "nct6775", + [nct6776] = "nct6776", + [nct6779] = "nct6779", + [nct6791] = "nct6791", + [nct6792] = "nct6792", + [nct6793] = "nct6793", + [nct6795] = "nct6795", + [nct6796] = "nct6796", + [nct6797] = "nct6797", + [nct6798] = "nct6798", + [nct6799] = "nct6799", }; /* Common and NCT6775 specific data */ @@ -767,9 +767,9 @@ static const u16 NCT6106_REG_FAN_MIN[] = { 0xe0, 0xe2, 0xe4 }; static const u16 NCT6106_REG_FAN_PULSES[] = { 0xf6, 0xf6, 0xf6 }; static const u16 NCT6106_FAN_PULSE_SHIFT[] = { 0, 2, 4 }; -static const u8 NCT6106_REG_PWM_MODE[] = { 0xf3, 0xf3, 0xf3 }; -static const u8 NCT6106_PWM_MODE_MASK[] = { 0x01, 0x02, 0x04 }; -static const u16 NCT6106_REG_PWM_READ[] = { 0x4a, 0x4b, 0x4c }; +static const u8 NCT6106_REG_PWM_MODE[] = { 0xf3, 0xf3, 0xf3, 0, 0 }; +static const u8 NCT6106_PWM_MODE_MASK[] = { 0x01, 0x02, 0x04, 0, 0 }; +static const u16 NCT6106_REG_PWM_READ[] = { 0x4a, 0x4b, 0x4c, 0xd8, 0xd9 }; static const u16 NCT6106_REG_FAN_MODE[] = { 0x113, 0x123, 0x133 }; static const u16 NCT6106_REG_TEMP_SOURCE[] = { 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5 }; @@ -3604,7 +3604,7 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data, break; case nct6116: data->in_num = 9; - data->pwm_num = 3; + data->pwm_num = 5; data->auto_pwm_num = 4; data->temp_fixed_num = 3; data->num_temp_alarms = 3; diff --git a/drivers/hwmon/nct6775-i2c.c b/drivers/hwmon/nct6775-i2c.c index 87a4fc78c5..aff69fa504 100644 --- a/drivers/hwmon/nct6775-i2c.c +++ b/drivers/hwmon/nct6775-i2c.c @@ -21,7 +21,7 @@ #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> #include <linux/err.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/regmap.h> #include "nct6775.h" @@ -155,23 +155,13 @@ static const struct regmap_config nct6775_i2c_regmap_config = { static int nct6775_i2c_probe(struct i2c_client *client) { struct nct6775_data *data; - const struct of_device_id *of_id; - const struct i2c_device_id *i2c_id; struct device *dev = &client->dev; - of_id = of_match_device(nct6775_i2c_of_match, dev); - i2c_id = i2c_match_id(nct6775_i2c_id, client); - - if (of_id && (unsigned long)of_id->data != i2c_id->driver_data) - dev_notice(dev, "Device mismatch: %s in device tree, %s detected\n", - of_id->name, i2c_id->name); - data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - data->kind = i2c_id->driver_data; - + data->kind = (enum kinds)(uintptr_t)i2c_get_match_data(client); data->read_only = true; data->driver_data = client; data->driver_init = nct6775_i2c_probe_init; diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c index 0adeeab7ee..9aa4dcf4a6 100644 --- a/drivers/hwmon/nct6775-platform.c +++ b/drivers/hwmon/nct6775-platform.c @@ -23,19 +23,19 @@ enum sensor_access { access_direct, access_asuswmi }; static const char * const nct6775_sio_names[] __initconst = { - "NCT6106D", - "NCT6116D", - "NCT6775F", - "NCT6776D/F", - "NCT6779D", - "NCT6791D", - "NCT6792D", - "NCT6793D", - "NCT6795D", - "NCT6796D", - "NCT6797D", - "NCT6798D", - "NCT6796D-S/NCT6799D-R", + [nct6106] = "NCT6106D", + [nct6116] = "NCT6116D", + [nct6775] = "NCT6775F", + [nct6776] = "NCT6776D/F", + [nct6779] = "NCT6779D", + [nct6791] = "NCT6791D", + [nct6792] = "NCT6792D", + [nct6793] = "NCT6793D", + [nct6795] = "NCT6795D", + [nct6796] = "NCT6796D", + [nct6797] = "NCT6797D", + [nct6798] = "NCT6798D", + [nct6799] = "NCT6796D-S/NCT6799D-R", }; static unsigned short force_id; diff --git a/drivers/hwmon/nct6775.h b/drivers/hwmon/nct6775.h index 296eff99d0..d31e7a0302 100644 --- a/drivers/hwmon/nct6775.h +++ b/drivers/hwmon/nct6775.h @@ -4,7 +4,7 @@ #include <linux/types.h> -enum kinds { nct6106, nct6116, nct6775, nct6776, nct6779, nct6791, nct6792, +enum kinds { nct6106 = 1, nct6116, nct6775, nct6776, nct6779, nct6791, nct6792, nct6793, nct6795, nct6796, nct6797, nct6798, nct6799 }; enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 }; diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c index 4702e4edc6..904816abb7 100644 --- a/drivers/hwmon/npcm750-pwm-fan.c +++ b/drivers/hwmon/npcm750-pwm-fan.c @@ -46,9 +46,9 @@ #define NPCM7XX_PWM_CTRL_CH3_EN_BIT BIT(16) /* Define the maximum PWM channel number */ -#define NPCM7XX_PWM_MAX_CHN_NUM 8 +#define NPCM7XX_PWM_MAX_CHN_NUM 12 #define NPCM7XX_PWM_MAX_CHN_NUM_IN_A_MODULE 4 -#define NPCM7XX_PWM_MAX_MODULES 2 +#define NPCM7XX_PWM_MAX_MODULES 3 /* Define the Counter Register, value = 100 for match 100% */ #define NPCM7XX_PWM_COUNTER_DEFAULT_NUM 255 @@ -171,6 +171,10 @@ #define FAN_PREPARE_TO_GET_FIRST_CAPTURE 0x01 #define FAN_ENOUGH_SAMPLE 0x02 +struct npcm_hwmon_info { + u32 pwm_max_channel; +}; + struct npcm7xx_fan_dev { u8 fan_st_flg; u8 fan_pls_per_rev; @@ -191,6 +195,7 @@ struct npcm7xx_cooling_device { struct npcm7xx_pwm_fan_data { void __iomem *pwm_base; void __iomem *fan_base; + int pwm_modules; unsigned long pwm_clk_freq; unsigned long fan_clk_freq; struct clk *pwm_clk; @@ -204,6 +209,7 @@ struct npcm7xx_pwm_fan_data { struct timer_list fan_timer; struct npcm7xx_fan_dev fan_dev[NPCM7XX_FAN_MAX_CHN_NUM]; struct npcm7xx_cooling_device *cdev[NPCM7XX_PWM_MAX_CHN_NUM]; + const struct npcm_hwmon_info *info; u8 fan_select; }; @@ -542,7 +548,7 @@ static umode_t npcm7xx_pwm_is_visible(const void *_data, u32 attr, int channel) { const struct npcm7xx_pwm_fan_data *data = _data; - if (!data->pwm_present[channel]) + if (!data->pwm_present[channel] || channel >= data->info->pwm_max_channel) return 0; switch (attr) { @@ -638,6 +644,10 @@ static const struct hwmon_channel_info * const npcm7xx_info[] = { HWMON_PWM_INPUT, HWMON_PWM_INPUT, HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, HWMON_PWM_INPUT), HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, @@ -670,6 +680,14 @@ static const struct hwmon_chip_info npcm7xx_chip_info = { .info = npcm7xx_info, }; +static const struct npcm_hwmon_info npxm7xx_hwmon_info = { + .pwm_max_channel = 8, +}; + +static const struct npcm_hwmon_info npxm8xx_hwmon_info = { + .pwm_max_channel = 12, +}; + static u32 npcm7xx_pwm_init(struct npcm7xx_pwm_fan_data *data) { int m, ch; @@ -693,7 +711,7 @@ static u32 npcm7xx_pwm_init(struct npcm7xx_pwm_fan_data *data) /* Setting PWM Prescale Register value register to both modules */ prescale_val |= (prescale_val << NPCM7XX_PWM_PRESCALE_SHIFT_CH01); - for (m = 0; m < NPCM7XX_PWM_MAX_MODULES ; m++) { + for (m = 0; m < data->pwm_modules; m++) { iowrite32(prescale_val, NPCM7XX_PWM_REG_PR(data->pwm_base, m)); iowrite32(NPCM7XX_PWM_PRESCALE2_DEFAULT, NPCM7XX_PWM_REG_CSR(data->pwm_base, m)); @@ -925,6 +943,12 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) if (!data) return -ENOMEM; + data->info = device_get_match_data(dev); + if (!data->info) + return -EINVAL; + + data->pwm_modules = data->info->pwm_max_channel / NPCM7XX_PWM_MAX_CHN_NUM_IN_A_MODULE; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm"); if (!res) { dev_err(dev, "pwm resource not found\n"); @@ -962,7 +986,7 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) output_freq = npcm7xx_pwm_init(data); npcm7xx_fan_init(data); - for (cnt = 0; cnt < NPCM7XX_PWM_MAX_MODULES ; cnt++) + for (cnt = 0; cnt < data->pwm_modules; cnt++) mutex_init(&data->pwm_lock[cnt]); for (i = 0; i < NPCM7XX_FAN_MAX_MODULE; i++) { @@ -1017,7 +1041,8 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) } static const struct of_device_id of_pwm_fan_match_table[] = { - { .compatible = "nuvoton,npcm750-pwm-fan", }, + { .compatible = "nuvoton,npcm750-pwm-fan", .data = &npxm7xx_hwmon_info}, + { .compatible = "nuvoton,npcm845-pwm-fan", .data = &npxm8xx_hwmon_info}, {}, }; MODULE_DEVICE_TABLE(of, of_pwm_fan_match_table); diff --git a/drivers/hwmon/pc87360.c b/drivers/hwmon/pc87360.c index 926ea1fe13..9e9681b2e8 100644 --- a/drivers/hwmon/pc87360.c +++ b/drivers/hwmon/pc87360.c @@ -323,7 +323,11 @@ static struct pc87360_data *pc87360_update_device(struct device *dev) } /* Voltages */ - for (i = 0; i < data->innr; i++) { + /* + * The min() below does not have any practical meaning and is + * only needed to silence a warning observed with gcc 12+. + */ + for (i = 0; i < min(data->innr, ARRAY_SIZE(data->in)); i++) { data->in_status[i] = pc87360_read_value(data, LD_IN, i, PC87365_REG_IN_STATUS); /* Clear bits */ diff --git a/drivers/hwmon/peci/dimmtemp.c b/drivers/hwmon/peci/dimmtemp.c index 5ca4d04e4b..4a72e97124 100644 --- a/drivers/hwmon/peci/dimmtemp.c +++ b/drivers/hwmon/peci/dimmtemp.c @@ -47,7 +47,7 @@ #define GET_TEMP_MAX(x) (((x) & DIMM_TEMP_MAX) >> 8) #define GET_TEMP_CRIT(x) (((x) & DIMM_TEMP_CRIT) >> 16) -#define NO_DIMM_RETRY_COUNT_MAX 5 +#define NO_DIMM_RETRY_COUNT_MAX 120 struct peci_dimmtemp; diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index b4e93bd583..294808f524 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -227,6 +227,16 @@ config SENSORS_LTC3815 This driver can also be built as a module. If so, the module will be called ltc3815. +config SENSORS_LTC4286 + bool "Analog Devices LTC4286" + help + LTC4286 is an integrated solution for hot swap applications that + allows a board to be safely inserted and removed from a live + backplane. + This chip could be used to monitor voltage, current, ...etc. + If you say yes here you get hardware monitoring support for Analog + Devices LTC4286. + config SENSORS_MAX15301 tristate "Maxim MAX15301" help @@ -299,6 +309,15 @@ config SENSORS_MAX8688 This driver can also be built as a module. If so, the module will be called max8688. +config SENSORS_MP2856 + tristate "MPS MP2856" + help + If you say yes here you get hardware monitoring support for MPS + MP2856 MP2857 Dual Loop Digital Multi-Phase Controller. + + This driver can also be built as a module. If so, the module will + be called mp2856. + config SENSORS_MP2888 tristate "MPS MP2888" help @@ -333,6 +352,15 @@ config SENSORS_MP5023 This driver can also be built as a module. If so, the module will be called mp5023. +config SENSORS_MP5990 + tristate "MPS MP5990" + help + If you say yes here you get hardware monitoring support for MPS + MP5990. + + This driver can also be built as a module. If so, the module will + be called mp5990. + config SENSORS_MPQ7932_REGULATOR bool "Regulator support for MPQ7932" depends on SENSORS_MPQ7932 && REGULATOR diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 84ee960a6c..cf8a767445 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_SENSORS_LM25066) += lm25066.o obj-$(CONFIG_SENSORS_LT7182S) += lt7182s.o obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o +obj-$(CONFIG_SENSORS_LTC4286) += ltc4286.o obj-$(CONFIG_SENSORS_MAX15301) += max15301.o obj-$(CONFIG_SENSORS_MAX16064) += max16064.o obj-$(CONFIG_SENSORS_MAX16601) += max16601.o @@ -32,9 +33,11 @@ obj-$(CONFIG_SENSORS_MAX20751) += max20751.o obj-$(CONFIG_SENSORS_MAX31785) += max31785.o obj-$(CONFIG_SENSORS_MAX34440) += max34440.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o +obj-$(CONFIG_SENSORS_MP2856) += mp2856.o obj-$(CONFIG_SENSORS_MP2888) += mp2888.o obj-$(CONFIG_SENSORS_MP2975) += mp2975.o obj-$(CONFIG_SENSORS_MP5023) += mp5023.o +obj-$(CONFIG_SENSORS_MP5990) += mp5990.o obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index 929fa6d34e..3a20df5a43 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -14,10 +14,10 @@ #include <linux/slab.h> #include <linux/i2c.h> #include <linux/log2.h> -#include <linux/of_device.h> +#include <linux/of.h> #include "pmbus.h" -enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i }; +enum chips { lm25056 = 1, lm25066, lm5064, lm5066, lm5066i }; #define LM25066_READ_VAUX 0xd0 #define LM25066_MFR_READ_IIN 0xd1 @@ -468,8 +468,6 @@ static int lm25066_probe(struct i2c_client *client) struct lm25066_data *data; struct pmbus_driver_info *info; const struct __coeff *coeff; - const struct of_device_id *of_id; - const struct i2c_device_id *i2c_id; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA)) @@ -484,14 +482,8 @@ static int lm25066_probe(struct i2c_client *client) if (config < 0) return config; - i2c_id = i2c_match_id(lm25066_id, client); + data->id = (enum chips)(unsigned long)i2c_get_match_data(client); - of_id = of_match_device(lm25066_of_match, &client->dev); - if (of_id && (unsigned long)of_id->data != i2c_id->driver_data) - dev_notice(&client->dev, "Device mismatch: %s in device tree, %s detected\n", - of_id->name, i2c_id->name); - - data->id = i2c_id->driver_data; info = &data->info; info->pages = 1; diff --git a/drivers/hwmon/pmbus/ltc4286.c b/drivers/hwmon/pmbus/ltc4286.c new file mode 100644 index 0000000000..9e7ceeb7e7 --- /dev/null +++ b/drivers/hwmon/pmbus/ltc4286.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pmbus.h> +#include "pmbus.h" + +/* LTC4286 register */ +#define LTC4286_MFR_CONFIG1 0xF2 + +/* LTC4286 configuration */ +#define VRANGE_SELECT_BIT BIT(1) + +#define LTC4286_MFR_ID_SIZE 3 + +/* + * Initialize the MBR as default settings which is referred to LTC4286 datasheet + * (March 22, 2022 version) table 3 page 16 + */ +static struct pmbus_driver_info ltc4286_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 32, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 1, + .m[PSC_VOLTAGE_OUT] = 32, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 1, + .m[PSC_CURRENT_OUT] = 1024, + .b[PSC_CURRENT_OUT] = 0, + /* + * The rsense value used in MBR formula in LTC4286 datasheet should be ohm unit. + * However, the rsense value that user input is micro ohm. + * Thus, the MBR setting which involves rsense should be shifted by 6 digits. + */ + .R[PSC_CURRENT_OUT] = 3 - 6, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + /* + * The rsense value used in MBR formula in LTC4286 datasheet should be ohm unit. + * However, the rsense value that user input is micro ohm. + * Thus, the MBR setting which involves rsense should be shifted by 6 digits. + */ + .R[PSC_POWER] = 4 - 6, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 273, + .R[PSC_TEMPERATURE] = 0, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP, +}; + +static const struct i2c_device_id ltc4286_id[] = { + { "ltc4286", 0 }, + { "ltc4287", 1 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ltc4286_id); + +static int ltc4286_probe(struct i2c_client *client) +{ + int ret; + const struct i2c_device_id *mid; + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + struct pmbus_driver_info *info; + u32 rsense; + int vrange_nval, vrange_oval; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); + if (ret < 0) { + return dev_err_probe(&client->dev, ret, + "Failed to read manufacturer id\n"); + } + + /* + * Refer to ltc4286 datasheet page 20 + * the manufacturer id is LTC + */ + if (ret != LTC4286_MFR_ID_SIZE || + strncmp(block_buffer, "LTC", LTC4286_MFR_ID_SIZE)) { + return dev_err_probe(&client->dev, -ENODEV, + "Manufacturer id mismatch\n"); + } + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); + if (ret < 0) { + return dev_err_probe(&client->dev, ret, + "Failed to read manufacturer model\n"); + } + + for (mid = ltc4286_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) + break; + } + if (!mid->name[0]) + return dev_err_probe(&client->dev, -ENODEV, + "Unsupported device\n"); + + if (of_property_read_u32(client->dev.of_node, + "shunt-resistor-micro-ohms", &rsense)) + rsense = 300; /* 0.3 mOhm if not set via DT */ + + if (rsense == 0) + return -EINVAL; + + /* Check for the latter MBR value won't overflow */ + if (rsense > (INT_MAX / 1024)) + return -EINVAL; + + info = devm_kmemdup(&client->dev, <c4286_info, sizeof(*info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + /* Check MFR1 CONFIG register bit 1 VRANGE_SELECT before driver loading */ + vrange_oval = i2c_smbus_read_word_data(client, LTC4286_MFR_CONFIG1); + if (vrange_oval < 0) + return dev_err_probe(&client->dev, vrange_oval, + "Failed to read manufacturer configuration one\n"); + vrange_nval = vrange_oval; + + if (device_property_read_bool(&client->dev, "adi,vrange-low-enable")) { + vrange_nval &= + ~VRANGE_SELECT_BIT; /* VRANGE_SELECT = 0, 25.6 volts */ + + info->m[PSC_VOLTAGE_IN] = 128; + info->m[PSC_VOLTAGE_OUT] = 128; + info->m[PSC_POWER] = 4 * rsense; + } else { + vrange_nval |= + VRANGE_SELECT_BIT; /* VRANGE_SELECT = 1, 102.4 volts */ + + info->m[PSC_POWER] = rsense; + } + if (vrange_nval != vrange_oval) { + /* Set MFR1 CONFIG register bit 1 VRANGE_SELECT */ + ret = i2c_smbus_write_word_data(client, LTC4286_MFR_CONFIG1, + vrange_nval); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to set vrange\n"); + } + + info->m[PSC_CURRENT_OUT] = 1024 * rsense; + + return pmbus_do_probe(client, info); +} + +static const struct of_device_id ltc4286_of_match[] = { + { .compatible = "lltc,ltc4286" }, + { .compatible = "lltc,ltc4287" }, + {} +}; + +static struct i2c_driver ltc4286_driver = { + .driver = { + .name = "ltc4286", + .of_match_table = ltc4286_of_match, + }, + .probe = ltc4286_probe, + .id_table = ltc4286_id, +}; + +module_i2c_driver(ltc4286_driver); + +MODULE_AUTHOR("Delphine CC Chiu <Delphine_CC_Chiu@wiwynn.com>"); +MODULE_DESCRIPTION("PMBUS driver for LTC4286 and compatibles"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/mp2856.c b/drivers/hwmon/pmbus/mp2856.c new file mode 100644 index 0000000000..6969350f5d --- /dev/null +++ b/drivers/hwmon/pmbus/mp2856.c @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for MPS2856/2857 + * Monolithic Power Systems VR Controllers + * + * Copyright (C) 2023 Quanta Computer lnc. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pmbus.h> +#include "pmbus.h" + +/* Vendor specific registers. */ +#define MP2856_MFR_VR_MULTI_CONFIG_R1 0x0d +#define MP2856_MFR_VR_MULTI_CONFIG_R2 0x1d + +#define MP2856_MUL1_BOOT_SR_R2 0x10 +#define MP2856_VR_ACTIVE BIT(15) + +#define MP2856_MFR_VR_CONFIG2 0x5e +#define MP2856_VOUT_MODE BIT(11) + +#define MP2856_MFR_VR_CONFIG1 0x68 +#define MP2856_DRMOS_KCS GENMASK(13, 12) + +#define MP2856_MFR_READ_CS1_2_R1 0x82 +#define MP2856_MFR_READ_CS3_4_R1 0x83 +#define MP2856_MFR_READ_CS5_6_R1 0x84 +#define MP2856_MFR_READ_CS7_8_R1 0x85 +#define MP2856_MFR_READ_CS9_10_R1 0x86 +#define MP2856_MFR_READ_CS11_12_R1 0x87 + +#define MP2856_MFR_READ_CS1_2_R2 0x85 +#define MP2856_MFR_READ_CS3_4_R2 0x86 +#define MP2856_MFR_READ_CS5_6_R2 0x87 + +#define MP2856_MAX_PHASE_RAIL1 8 +#define MP2856_MAX_PHASE_RAIL2 4 + +#define MP2857_MAX_PHASE_RAIL1 12 +#define MP2857_MAX_PHASE_RAIL2 4 + +#define MP2856_PAGE_NUM 2 + +enum chips { mp2856 = 1, mp2857 }; + +static const int mp2856_max_phases[][MP2856_PAGE_NUM] = { + [mp2856] = { MP2856_MAX_PHASE_RAIL1, MP2856_MAX_PHASE_RAIL2 }, + [mp2857] = { MP2857_MAX_PHASE_RAIL1, MP2857_MAX_PHASE_RAIL2 }, +}; + +static const struct i2c_device_id mp2856_id[] = { + {"mp2856", mp2856}, + {"mp2857", mp2857}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, mp2856_id); + +struct mp2856_data { + struct pmbus_driver_info info; + int vout_format[MP2856_PAGE_NUM]; + int curr_sense_gain[MP2856_PAGE_NUM]; + int max_phases[MP2856_PAGE_NUM]; + enum chips chip_id; +}; + +#define to_mp2856_data(x) container_of(x, struct mp2856_data, info) + +#define MAX_LIN_MANTISSA (1023 * 1000) +#define MIN_LIN_MANTISSA (511 * 1000) + +static u16 val2linear11(s64 val) +{ + s16 exponent = 0, mantissa; + bool negative = false; + + if (val == 0) + return 0; + + if (val < 0) { + negative = true; + val = -val; + } + + /* Reduce large mantissa until it fits into 10 bit */ + while (val >= MAX_LIN_MANTISSA && exponent < 15) { + exponent++; + val >>= 1; + } + /* Increase small mantissa to improve precision */ + while (val < MIN_LIN_MANTISSA && exponent > -15) { + exponent--; + val <<= 1; + } + + /* Convert mantissa from milli-units to units */ + mantissa = clamp_val(DIV_ROUND_CLOSEST_ULL(val, 1000), 0, 0x3ff); + + /* restore sign */ + if (negative) + mantissa = -mantissa; + + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + +static int +mp2856_read_word_helper(struct i2c_client *client, int page, int phase, u8 reg, + u16 mask) +{ + int ret = pmbus_read_word_data(client, page, phase, reg); + + return (ret > 0) ? ret & mask : ret; +} + +static int +mp2856_read_vout(struct i2c_client *client, struct mp2856_data *data, int page, + int phase, u8 reg) +{ + int ret; + + ret = mp2856_read_word_helper(client, page, phase, reg, + GENMASK(9, 0)); + if (ret < 0) + return ret; + + /* convert vout result to direct format */ + ret = (data->vout_format[page] == vid) ? + ((ret + 49) * 5) : ((ret * 1000) >> 8); + + return ret; +} + +static int +mp2856_read_phase(struct i2c_client *client, struct mp2856_data *data, + int page, int phase, u8 reg) +{ + int ret; + int val; + + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + if (!((phase + 1) % MP2856_PAGE_NUM)) + ret >>= 8; + ret &= 0xff; + + /* + * Output value is calculated as: (READ_CSx * 12.5mV - 1.23V) / (Kcs * Rcs) + */ + val = (ret * 125) - 12300; + + return val2linear11(val); +} + +static int +mp2856_read_phases(struct i2c_client *client, struct mp2856_data *data, + int page, int phase) +{ + int ret; + + if (page == 0) { + switch (phase) { + case 0 ... 1: + ret = mp2856_read_phase(client, data, page, phase, + MP2856_MFR_READ_CS1_2_R1); + break; + case 2 ... 3: + ret = mp2856_read_phase(client, data, page, phase, + MP2856_MFR_READ_CS3_4_R1); + break; + case 4 ... 5: + ret = mp2856_read_phase(client, data, page, phase, + MP2856_MFR_READ_CS5_6_R1); + break; + case 6 ... 7: + ret = mp2856_read_phase(client, data, page, phase, + MP2856_MFR_READ_CS7_8_R1); + break; + default: + return -ENODATA; + } + } else { + switch (phase) { + case 0 ... 1: + ret = mp2856_read_phase(client, data, page, phase, + MP2856_MFR_READ_CS1_2_R2); + break; + case 2 ... 3: + ret = mp2856_read_phase(client, data, page, phase, + MP2856_MFR_READ_CS1_2_R2); + break; + default: + return -ENODATA; + } + } + return ret; +} + +static int +mp2856_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct mp2856_data *data = to_mp2856_data(info); + int ret; + + switch (reg) { + case PMBUS_READ_VOUT: + ret = mp2856_read_vout(client, data, page, phase, reg); + break; + case PMBUS_READ_IOUT: + if (phase != 0xff) + ret = mp2856_read_phases(client, data, page, phase); + else + ret = pmbus_read_word_data(client, page, phase, reg); + break; + default: + return -ENODATA; + } + + return ret; +} + +static int +mp2856_read_byte_data(struct i2c_client *client, int page, int reg) +{ + switch (reg) { + case PMBUS_VOUT_MODE: + /* Enforce VOUT direct format. */ + return PB_VOUT_MODE_DIRECT; + default: + return -ENODATA; + } +} + +static int +mp2856_identify_multiphase(struct i2c_client *client, u8 reg, u8 max_phase, + u16 mask) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) + return ret; + + ret &= mask; + return (ret >= max_phase) ? max_phase : ret; +} + +static int +mp2856_identify_multiphase_rail1(struct i2c_client *client, + struct mp2856_data *data) +{ + int ret, i; + + ret = mp2856_identify_multiphase(client, MP2856_MFR_VR_MULTI_CONFIG_R1, + MP2856_MAX_PHASE_RAIL1, GENMASK(3, 0)); + if (ret < 0) + return ret; + + data->info.phases[0] = (ret > data->max_phases[0]) ? + data->max_phases[0] : ret; + + for (i = 0 ; i < data->info.phases[0]; i++) + data->info.pfunc[i] |= PMBUS_HAVE_IOUT; + + return 0; +} + +static int +mp2856_identify_multiphase_rail2(struct i2c_client *client, + struct mp2856_data *data) +{ + int ret, i; + + ret = mp2856_identify_multiphase(client, MP2856_MFR_VR_MULTI_CONFIG_R2, + MP2856_MAX_PHASE_RAIL2, GENMASK(2, 0)); + if (ret < 0) + return ret; + + data->info.phases[1] = (ret > data->max_phases[1]) ? + data->max_phases[1] : ret; + + for (i = 0 ; i < data->info.phases[0]; i++) + data->info.pfunc[i] |= PMBUS_HAVE_IOUT; + + return 0; +} + +static int +mp2856_current_sense_gain_get(struct i2c_client *client, + struct mp2856_data *data) +{ + int i, ret; + + /* + * Obtain DrMOS current sense gain of power stage from the register + * MP2856_MFR_VR_CONFIG1, bits 13-12. The value is selected as below: + * 00b - 5µA/A, 01b - 8.5µA/A, 10b - 9.7µA/A, 11b - 10µA/A. Other + * values are invalid. + */ + for (i = 0 ; i < data->info.pages; i++) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + if (ret < 0) + return ret; + ret = i2c_smbus_read_word_data(client, + MP2856_MFR_VR_CONFIG1); + if (ret < 0) + return ret; + + switch ((ret & MP2856_DRMOS_KCS) >> 12) { + case 0: + data->curr_sense_gain[i] = 50; + break; + case 1: + data->curr_sense_gain[i] = 85; + break; + case 2: + data->curr_sense_gain[i] = 97; + break; + default: + data->curr_sense_gain[i] = 100; + break; + } + } + return 0; +} + +static int +mp2856_identify_vout_format(struct i2c_client *client, + struct mp2856_data *data) +{ + int i, ret; + + for (i = 0; i < data->info.pages; i++) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MP2856_MFR_VR_CONFIG2); + if (ret < 0) + return ret; + + data->vout_format[i] = (ret & MP2856_VOUT_MODE) ? linear : vid; + } + return 0; +} + +static bool +mp2856_is_rail2_active(struct i2c_client *client) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); + if (ret < 0) + return true; + + ret = i2c_smbus_read_word_data(client, MP2856_MUL1_BOOT_SR_R2); + if (ret < 0) + return true; + + return (ret & MP2856_VR_ACTIVE) ? true : false; +} + +static struct pmbus_driver_info mp2856_info = { + .pages = MP2856_PAGE_NUM, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .m[PSC_VOLTAGE_OUT] = 1, + .R[PSC_VOLTAGE_OUT] = 3, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP, + .read_byte_data = mp2856_read_byte_data, + .read_word_data = mp2856_read_word_data, +}; + +static int mp2856_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + struct mp2856_data *data; + int ret; + + data = devm_kzalloc(&client->dev, sizeof(struct mp2856_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->chip_id = (enum chips)(uintptr_t)i2c_get_match_data(client); + + memcpy(data->max_phases, mp2856_max_phases[data->chip_id], + sizeof(data->max_phases)); + + memcpy(&data->info, &mp2856_info, sizeof(*info)); + info = &data->info; + + /* Identify multiphase configuration. */ + ret = mp2856_identify_multiphase_rail1(client, data); + if (ret < 0) + return ret; + + if (mp2856_is_rail2_active(client)) { + ret = mp2856_identify_multiphase_rail2(client, data); + if (ret < 0) + return ret; + } else { + /* rail2 is not active */ + info->pages = 1; + } + + /* Obtain current sense gain of power stage. */ + ret = mp2856_current_sense_gain_get(client, data); + if (ret) + return ret; + + /* Identify vout format. */ + ret = mp2856_identify_vout_format(client, data); + if (ret) + return ret; + + /* set the device to page 0 */ + i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + + return pmbus_do_probe(client, info); +} + +static const struct of_device_id __maybe_unused mp2856_of_match[] = { + {.compatible = "mps,mp2856", .data = (void *)mp2856}, + {.compatible = "mps,mp2857", .data = (void *)mp2857}, + {} +}; +MODULE_DEVICE_TABLE(of, mp2856_of_match); + +static struct i2c_driver mp2856_driver = { + .driver = { + .name = "mp2856", + .of_match_table = mp2856_of_match, + }, + .probe = mp2856_probe, + .id_table = mp2856_id, +}; + +module_i2c_driver(mp2856_driver); + +MODULE_AUTHOR("Peter Yin <peter.yin@quantatw.com>"); +MODULE_DESCRIPTION("PMBus driver for MPS MP2856/MP2857 device"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c index b9bb469e2d..e5fa10b3b8 100644 --- a/drivers/hwmon/pmbus/mp2975.c +++ b/drivers/hwmon/pmbus/mp2975.c @@ -126,6 +126,21 @@ static const struct regulator_desc __maybe_unused mp2975_reg_desc[] = { #define to_mp2975_data(x) container_of(x, struct mp2975_data, info) +static int mp2975_read_byte_data(struct i2c_client *client, int page, int reg) +{ + switch (reg) { + case PMBUS_VOUT_MODE: + /* + * Report direct format as configured by MFR_DC_LOOP_CTRL. + * Unlike on MP2971/MP2973 the reported VOUT_MODE isn't automatically + * internally updated, but always reads as PB_VOUT_MODE_VID. + */ + return PB_VOUT_MODE_DIRECT; + default: + return -ENODATA; + } +} + static int mp2975_read_word_helper(struct i2c_client *client, int page, int phase, u8 reg, u16 mask) @@ -869,6 +884,7 @@ static struct pmbus_driver_info mp2975_info = { PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | PMBUS_PHASE_VIRTUAL, + .read_byte_data = mp2975_read_byte_data, .read_word_data = mp2975_read_word_data, #if IS_ENABLED(CONFIG_SENSORS_MP2975_REGULATOR) .num_regulators = 1, diff --git a/drivers/hwmon/pmbus/mp5990.c b/drivers/hwmon/pmbus/mp5990.c new file mode 100644 index 0000000000..1dfbab25a0 --- /dev/null +++ b/drivers/hwmon/pmbus/mp5990.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for MPS MP5990 Hot-Swap Controller + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include "pmbus.h" + +#define MP5990_EFUSE_CFG (0xC4) +#define MP5990_VOUT_FORMAT BIT(9) + +struct mp5990_data { + struct pmbus_driver_info info; + u8 vout_mode; + u8 vout_linear_exponent; +}; + +#define to_mp5990_data(x) container_of(x, struct mp5990_data, info) + +static int mp5990_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct mp5990_data *data = to_mp5990_data(info); + + switch (reg) { + case PMBUS_VOUT_MODE: + if (data->vout_mode == linear) { + /* + * The VOUT format used by the chip is linear11, + * not linear16. Report that VOUT is in linear mode + * and return exponent value extracted while probing + * the chip. + */ + return data->vout_linear_exponent; + } + + /* + * The datasheet does not support the VOUT command, + * but the device responds with a default value of 0x17. + * In the standard, 0x17 represents linear mode. + * Therefore, we should report that VOUT is in direct + * format when the chip is configured for it. + */ + return PB_VOUT_MODE_DIRECT; + + default: + return -ENODATA; + } +} + +static int mp5990_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct mp5990_data *data = to_mp5990_data(info); + int ret; + s32 mantissa; + + switch (reg) { + case PMBUS_READ_VOUT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + /* + * Because the VOUT format used by the chip is linear11 and not + * linear16, we disregard bits[15:11]. The exponent is reported + * as part of the VOUT_MODE command. + */ + if (data->vout_mode == linear) { + mantissa = ((s16)((ret & 0x7ff) << 5)) >> 5; + ret = mantissa; + } + break; + default: + return -ENODATA; + } + + return ret; +} + +static struct pmbus_driver_info mp5990_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 32, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 0, + .m[PSC_VOLTAGE_OUT] = 32, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 0, + .m[PSC_CURRENT_OUT] = 16, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 0, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 0, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + .func[0] = + PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_PIN | + PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = mp5990_read_byte_data, + .read_word_data = mp5990_read_word_data, +}; + +static int mp5990_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + struct mp5990_data *data; + int ret; + + data = devm_kzalloc(&client->dev, sizeof(struct mp5990_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + memcpy(&data->info, &mp5990_info, sizeof(*info)); + info = &data->info; + + /* Read Vout Config */ + ret = i2c_smbus_read_word_data(client, MP5990_EFUSE_CFG); + if (ret < 0) { + dev_err(&client->dev, "Can't get vout mode."); + return ret; + } + + /* + * EFUSE_CFG (0xC4) bit9=1 is linear mode, bit=0 is direct mode. + */ + if (ret & MP5990_VOUT_FORMAT) { + data->vout_mode = linear; + data->info.format[PSC_VOLTAGE_IN] = linear; + data->info.format[PSC_VOLTAGE_OUT] = linear; + data->info.format[PSC_CURRENT_OUT] = linear; + data->info.format[PSC_POWER] = linear; + ret = i2c_smbus_read_word_data(client, PMBUS_READ_VOUT); + if (ret < 0) { + dev_err(&client->dev, "Can't get vout exponent."); + return ret; + } + data->vout_linear_exponent = (u8)((ret >> 11) & 0x1f); + } else { + data->vout_mode = direct; + } + return pmbus_do_probe(client, info); +} + +static const struct of_device_id mp5990_of_match[] = { + { .compatible = "mps,mp5990" }, + {} +}; + +static const struct i2c_device_id mp5990_id[] = { + {"mp5990", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, mp5990_id); + +static struct i2c_driver mp5990_driver = { + .driver = { + .name = "mp5990", + .of_match_table = mp5990_of_match, + }, + .probe = mp5990_probe, + .id_table = mp5990_id, +}; +module_i2c_driver(mp5990_driver); + +MODULE_AUTHOR("Peter Yin <peter.yin@quantatw.com>"); +MODULE_DESCRIPTION("PMBus driver for MP5990 HSC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index 6e4516c2ab..b67bc9e833 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -151,7 +151,7 @@ static int pwm_fan_power_on(struct pwm_fan_ctx *ctx) } state->enabled = true; - ret = pwm_apply_state(ctx->pwm, state); + ret = pwm_apply_might_sleep(ctx->pwm, state); if (ret) { dev_err(ctx->dev, "failed to enable PWM\n"); goto disable_regulator; @@ -181,7 +181,7 @@ static int pwm_fan_power_off(struct pwm_fan_ctx *ctx) state->enabled = false; state->duty_cycle = 0; - ret = pwm_apply_state(ctx->pwm, state); + ret = pwm_apply_might_sleep(ctx->pwm, state); if (ret) { dev_err(ctx->dev, "failed to disable PWM\n"); return ret; @@ -207,7 +207,7 @@ static int __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm) period = state->period; state->duty_cycle = DIV_ROUND_UP(pwm * (period - 1), MAX_PWM); - ret = pwm_apply_state(ctx->pwm, state); + ret = pwm_apply_might_sleep(ctx->pwm, state); if (ret) return ret; ret = pwm_fan_power_on(ctx); @@ -278,7 +278,7 @@ static int pwm_fan_update_enable(struct pwm_fan_ctx *ctx, long val) state, &enable_regulator); - pwm_apply_state(ctx->pwm, state); + pwm_apply_might_sleep(ctx->pwm, state); pwm_fan_switch_power(ctx, enable_regulator); pwm_fan_update_state(ctx, 0); } diff --git a/drivers/hwmon/sht4x.c b/drivers/hwmon/sht4x.c index 7ee7974104..4883755d4b 100644 --- a/drivers/hwmon/sht4x.c +++ b/drivers/hwmon/sht4x.c @@ -49,6 +49,7 @@ DECLARE_CRC8_TABLE(sht4x_crc8_table); * struct sht4x_data - All the data required to operate an SHT4X chip * @client: the i2c client associated with the SHT4X * @lock: a mutex that is used to prevent parallel access to the i2c client + * @valid: validity of fields below * @update_interval: the minimum poll interval * @last_updated: the previous time that the SHT4X was polled * @temperature: the latest temperature value received from the SHT4X @@ -66,7 +67,7 @@ struct sht4x_data { /** * sht4x_read_values() - read and parse the raw data from the SHT4X - * @sht4x_data: the struct sht4x_data to use for the lock + * @data: the struct sht4x_data to use for the lock * Return: 0 if successful, -ERRNO if not */ static int sht4x_read_values(struct sht4x_data *data) diff --git a/drivers/hwmon/smsc47m1.c b/drivers/hwmon/smsc47m1.c index 37531b5c82..0d46edbcb1 100644 --- a/drivers/hwmon/smsc47m1.c +++ b/drivers/hwmon/smsc47m1.c @@ -33,7 +33,7 @@ static unsigned short force_id; module_param(force_id, ushort, 0); MODULE_PARM_DESC(force_id, "Override the detected device ID"); -static struct platform_device *pdev; +static struct platform_device *smsc47m1_pdev; #define DRVNAME "smsc47m1" enum chips { smsc47m1, smsc47m2 }; @@ -840,70 +840,57 @@ error_remove_files: return err; } -static int __exit smsc47m1_remove(struct platform_device *pdev) +static void __exit smsc47m1_remove(struct platform_device *pdev) { struct smsc47m1_data *data = platform_get_drvdata(pdev); hwmon_device_unregister(data->hwmon_dev); smsc47m1_remove_files(&pdev->dev); - - return 0; } -static struct platform_driver smsc47m1_driver = { +/* + * smsc47m1_remove() lives in .exit.text. For drivers registered via + * module_platform_driver_probe() this ok because they cannot get unbound at + * runtime. The driver needs to be marked with __refdata, otherwise modpost + * triggers a section mismatch warning. + */ +static struct platform_driver smsc47m1_driver __refdata = { .driver = { .name = DRVNAME, }, - .remove = __exit_p(smsc47m1_remove), + .remove_new = __exit_p(smsc47m1_remove), }; static int __init smsc47m1_device_add(unsigned short address, const struct smsc47m1_sio_data *sio_data) { - struct resource res = { + const struct resource res = { .start = address, .end = address + SMSC_EXTENT - 1, .name = DRVNAME, .flags = IORESOURCE_IO, }; + const struct platform_device_info pdevinfo = { + .name = DRVNAME, + .id = address, + .res = &res, + .num_res = 1, + .data = sio_data, + .size_data = sizeof(struct smsc47m1_sio_data), + }; int err; err = smsc47m1_handle_resources(address, sio_data->type, CHECK, NULL); if (err) - goto exit; + return err; - pdev = platform_device_alloc(DRVNAME, address); - if (!pdev) { - err = -ENOMEM; + smsc47m1_pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(smsc47m1_pdev)) { pr_err("Device allocation failed\n"); - goto exit; - } - - err = platform_device_add_resources(pdev, &res, 1); - if (err) { - pr_err("Device resource addition failed (%d)\n", err); - goto exit_device_put; - } - - err = platform_device_add_data(pdev, sio_data, - sizeof(struct smsc47m1_sio_data)); - if (err) { - pr_err("Platform data allocation failed\n"); - goto exit_device_put; - } - - err = platform_device_add(pdev); - if (err) { - pr_err("Device addition failed (%d)\n", err); - goto exit_device_put; + return PTR_ERR(smsc47m1_pdev); } return 0; - -exit_device_put: - platform_device_put(pdev); -exit: - return err; } static int __init sm_smsc47m1_init(void) @@ -917,7 +904,7 @@ static int __init sm_smsc47m1_init(void) return err; address = err; - /* Sets global pdev as a side effect */ + /* Sets global smsc47m1_pdev as a side effect */ err = smsc47m1_device_add(address, &sio_data); if (err) return err; @@ -929,7 +916,7 @@ static int __init sm_smsc47m1_init(void) return 0; exit_device: - platform_device_unregister(pdev); + platform_device_unregister(smsc47m1_pdev); smsc47m1_restore(&sio_data); return err; } @@ -937,8 +924,8 @@ exit_device: static void __exit sm_smsc47m1_exit(void) { platform_driver_unregister(&smsc47m1_driver); - smsc47m1_restore(dev_get_platdata(&pdev->dev)); - platform_device_unregister(pdev); + smsc47m1_restore(dev_get_platdata(&smsc47m1_pdev->dev)); + platform_device_unregister(smsc47m1_pdev); } MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>"); diff --git a/drivers/hwmon/tmp513.c b/drivers/hwmon/tmp513.c index 8a7cf08733..ea6f4416c1 100644 --- a/drivers/hwmon/tmp513.c +++ b/drivers/hwmon/tmp513.c @@ -19,15 +19,20 @@ * the Free Software Foundation; version 2 of the License. */ +#include <linux/bitops.h> +#include <linux/bug.h> +#include <linux/device.h> #include <linux/err.h> #include <linux/hwmon.h> #include <linux/i2c.h> #include <linux/init.h> -#include <linux/kernel.h> +#include <linux/math.h> #include <linux/module.h> +#include <linux/property.h> #include <linux/regmap.h> #include <linux/slab.h> -#include <linux/util_macros.h> +#include <linux/types.h> +#include <linux/units.h> // Common register definition #define TMP51X_SHUNT_CONFIG 0x00 @@ -97,8 +102,8 @@ #define TMP51X_REMOTE_TEMP_LIMIT_2_POS 8 #define TMP513_REMOTE_TEMP_LIMIT_3_POS 7 -#define TMP51X_VBUS_RANGE_32V 32000000 -#define TMP51X_VBUS_RANGE_16V 16000000 +#define TMP51X_VBUS_RANGE_32V (32 * MICRO) +#define TMP51X_VBUS_RANGE_16V (16 * MICRO) // Max and Min value #define MAX_BUS_VOLTAGE_32_LIMIT 32764 @@ -200,7 +205,7 @@ static int tmp51x_get_value(struct tmp51x_data *data, u8 reg, u8 pos, * on the pga gain setting. 1lsb = 10uV */ *val = sign_extend32(regval, 17 - tmp51x_get_pga_shift(data)); - *val = DIV_ROUND_CLOSEST(*val * 10000, data->shunt_uohms); + *val = DIV_ROUND_CLOSEST(*val * 10 * MILLI, data->shunt_uohms); break; case TMP51X_BUS_VOLTAGE_RESULT: case TMP51X_BUS_VOLTAGE_H_LIMIT: @@ -216,7 +221,7 @@ static int tmp51x_get_value(struct tmp51x_data *data, u8 reg, u8 pos, case TMP51X_BUS_CURRENT_RESULT: // Current = (ShuntVoltage * CalibrationRegister) / 4096 *val = sign_extend32(regval, 16) * data->curr_lsb_ua; - *val = DIV_ROUND_CLOSEST(*val, 1000); + *val = DIV_ROUND_CLOSEST(*val, MILLI); break; case TMP51X_LOCAL_TEMP_RESULT: case TMP51X_REMOTE_TEMP_RESULT_1: @@ -256,7 +261,7 @@ static int tmp51x_set_value(struct tmp51x_data *data, u8 reg, long val) * The user enter current value and we convert it to * voltage. 1lsb = 10uV */ - val = DIV_ROUND_CLOSEST(val * data->shunt_uohms, 10000); + val = DIV_ROUND_CLOSEST(val * data->shunt_uohms, 10 * MILLI); max_val = U16_MAX >> tmp51x_get_pga_shift(data); regval = clamp_val(val, -max_val, max_val); break; @@ -546,18 +551,16 @@ static int tmp51x_calibrate(struct tmp51x_data *data) if (data->shunt_uohms == 0) return regmap_write(data->regmap, TMP51X_SHUNT_CALIBRATION, 0); - max_curr_ma = DIV_ROUND_CLOSEST_ULL(vshunt_max * 1000 * 1000, - data->shunt_uohms); + max_curr_ma = DIV_ROUND_CLOSEST_ULL(vshunt_max * MICRO, data->shunt_uohms); /* * Calculate the minimal bit resolution for the current and the power. * Those values will be used during register interpretation. */ - data->curr_lsb_ua = DIV_ROUND_CLOSEST_ULL(max_curr_ma * 1000, 32767); + data->curr_lsb_ua = DIV_ROUND_CLOSEST_ULL(max_curr_ma * MILLI, 32767); data->pwr_lsb_uw = 20 * data->curr_lsb_ua; - div = DIV_ROUND_CLOSEST_ULL(data->curr_lsb_ua * data->shunt_uohms, - 1000 * 1000); + div = DIV_ROUND_CLOSEST_ULL(data->curr_lsb_ua * data->shunt_uohms, MICRO); return regmap_write(data->regmap, TMP51X_SHUNT_CALIBRATION, DIV_ROUND_CLOSEST(40960, div)); @@ -626,9 +629,9 @@ static int tmp51x_vbus_range_to_reg(struct device *dev, } else if (data->vbus_range_uvolt == TMP51X_VBUS_RANGE_16V) { data->shunt_config &= ~TMP51X_BUS_VOLTAGE_MASK; } else { - dev_err(dev, "ti,bus-range-microvolt is invalid: %u\n", - data->vbus_range_uvolt); - return -EINVAL; + return dev_err_probe(dev, -EINVAL, + "ti,bus-range-microvolt is invalid: %u\n", + data->vbus_range_uvolt); } return 0; } @@ -644,8 +647,8 @@ static int tmp51x_pga_gain_to_reg(struct device *dev, struct tmp51x_data *data) } else if (data->pga_gain == 1) { data->shunt_config |= CURRENT_SENSE_VOLTAGE_40_MASK; } else { - dev_err(dev, "ti,pga-gain is invalid: %u\n", data->pga_gain); - return -EINVAL; + return dev_err_probe(dev, -EINVAL, + "ti,pga-gain is invalid: %u\n", data->pga_gain); } return 0; } @@ -674,10 +677,10 @@ static int tmp51x_read_properties(struct device *dev, struct tmp51x_data *data) data->max_channels - 1); // Check if shunt value is compatible with pga-gain - if (data->shunt_uohms > data->pga_gain * 40 * 1000 * 1000) { - dev_err(dev, "shunt-resistor: %u too big for pga_gain: %u\n", - data->shunt_uohms, data->pga_gain); - return -EINVAL; + if (data->shunt_uohms > data->pga_gain * 40 * MICRO) { + return dev_err_probe(dev, -EINVAL, + "shunt-resistor: %u too big for pga_gain: %u\n", + data->shunt_uohms, data->pga_gain); } return 0; @@ -717,22 +720,17 @@ static int tmp51x_probe(struct i2c_client *client) data->max_channels = (uintptr_t)i2c_get_match_data(client); ret = tmp51x_configure(dev, data); - if (ret < 0) { - dev_err(dev, "error configuring the device: %d\n", ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "error configuring the device\n"); data->regmap = devm_regmap_init_i2c(client, &tmp51x_regmap_config); - if (IS_ERR(data->regmap)) { - dev_err(dev, "failed to allocate register map\n"); - return PTR_ERR(data->regmap); - } + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "failed to allocate register map\n"); ret = tmp51x_init(data); - if (ret < 0) { - dev_err(dev, "error configuring the device: %d\n", ret); - return -ENODEV; - } + if (ret < 0) + return dev_err_probe(dev, ret, "error configuring the device\n"); hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, |