On Thu, Mar 28, 2024 at 08:12:50AM -0400, David Ober wrote: > This addition adds in the ability for the system to scan > the EC chip in the Lenovo ThinkStation systems to get the > current fan RPM speeds the Maximum speed value for each > fan also provides the CPU, DIMM other thermal statuses > > Signed-off-by: David Ober <dober6023@xxxxxxxxx> Applied, after change indicated below. Guenter > --- > Changes in V5 > added FMT for err messages > removed macros as suggested > used GENMASK as suggested > moved request region after memory allocation for local data > updated includes list > changed byte read/wrtie to word where applicable > --- > --- > drivers/hwmon/Kconfig | 10 + > drivers/hwmon/Makefile | 1 + > drivers/hwmon/lenovo-ec-sensors.c | 605 ++++++++++++++++++++++++++++++ > 3 files changed, 616 insertions(+) > create mode 100644 drivers/hwmon/lenovo-ec-sensors.c > > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig > index ec38c8892158..a4bb403bd4ad 100644 > --- a/drivers/hwmon/Kconfig > +++ b/drivers/hwmon/Kconfig > @@ -862,6 +862,16 @@ config SENSORS_LAN966X > This driver can also be built as a module. If so, the module > will be called lan966x-hwmon. > > +config SENSORS_LENOVO_EC > + tristate "Sensor reader for Lenovo ThinkStations" > + depends on X86 > + help > + If you say yes here you get support for LENOVO > + EC Sensor data on newer ThinkStation systems > + > + This driver can also be built as a module. If so, the module > + will be called lenovo_ec_sensors. > + > config SENSORS_LINEAGE > tristate "Lineage Compact Power Line Power Entry Module" > depends on I2C > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile > index 4ac9452b5430..aa3c2dc390ec 100644 > --- a/drivers/hwmon/Makefile > +++ b/drivers/hwmon/Makefile > @@ -104,6 +104,7 @@ obj-$(CONFIG_SENSORS_JC42) += jc42.o > obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o > obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o > obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o > +obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o > obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o > obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o > obj-$(CONFIG_SENSORS_LM63) += lm63.o > diff --git a/drivers/hwmon/lenovo-ec-sensors.c b/drivers/hwmon/lenovo-ec-sensors.c > new file mode 100644 > index 000000000000..8571b983c726 > --- /dev/null > +++ b/drivers/hwmon/lenovo-ec-sensors.c > @@ -0,0 +1,605 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * HWMON driver for Lenovo ThinkStation based workstations > + * via the embedded controller registers > + * > + * Copyright (C) 2024 David Ober (Lenovo) <dober@xxxxxxxxxx> > + * > + * EC provides: > + * - CPU temperature > + * - DIMM temperature > + * - Chassis zone temperatures > + * - CPU fan RPM > + * - DIMM fan RPM > + * - Chassis fans RPM > + */ > + > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt > + > +#include <linux/acpi.h> > +#include <linux/bits.h> > +#include <linux/delay.h> > +#include <linux/device.h> > +#include <linux/dmi.h> > +#include <linux/err.h> > +#include <linux/hwmon.h> > +#include <linux/io.h> > +#include <linux/ioport.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/platform_device.h> > +#include <linux/types.h> > +#include <linux/units.h> > + > +#define MCHP_SING_IDX 0x0000 > +#define MCHP_EMI0_APPLICATION_ID 0x090C > +#define MCHP_EMI0_EC_ADDRESS 0x0902 > +#define MCHP_EMI0_EC_DATA_BYTE0 0x0904 > +#define MCHP_EMI0_EC_DATA_BYTE1 0x0905 > +#define MCHP_EMI0_EC_DATA_BYTE2 0x0906 > +#define MCHP_EMI0_EC_DATA_BYTE3 0x0907 > +#define IO_REGION_START 0x0900 > +#define IO_REGION_LENGTH 0xD > + > +static inline u8 > +get_ec_reg(unsigned char page, unsigned char index) > +{ > + u8 onebyte; > + unsigned short m_index; > + unsigned short phy_index = page * 256 + index; > + > + outb_p(0x01, MCHP_EMI0_APPLICATION_ID); > + > + m_index = phy_index & GENMASK(14, 2); > + outw_p(m_index, MCHP_EMI0_EC_ADDRESS); > + > + onebyte = inb_p(MCHP_EMI0_EC_DATA_BYTE0 + (phy_index & GENMASK(1, 0))); > + > + outb_p(0x01, MCHP_EMI0_APPLICATION_ID); /* write 0x01 again to clean */ > + return onebyte; > +} > + > +enum systems { > + LENOVO_PX, > + LENOVO_P7, > + LENOVO_P5, > + LENOVO_P8, > +}; > + > +static int px_temp_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; > + > +static const char * const lenovo_px_ec_temp_label[] = { > + "CPU1", > + "CPU2", > + "R_DIMM1", > + "L_DIMM1", > + "R_DIMM2", > + "L_DIMM2", > + "PCH", > + "M2_R", > + "M2_Z1R", > + "M2_Z2R", > + "PCI_Z1", > + "PCI_Z2", > + "PCI_Z3", > + "PCI_Z4", > + "AMB", > +}; > + > +static int gen_temp_map[] = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; > + > +static const char * const lenovo_gen_ec_temp_label[] = { > + "CPU1", > + "R_DIMM", > + "L_DIMM", > + "PCH", > + "M2_R", > + "M2_Z1R", > + "M2_Z2R", > + "PCI_Z1", > + "PCI_Z2", > + "PCI_Z3", > + "PCI_Z4", > + "AMB", > +}; > + > +static int px_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; > + > +static const char * const px_ec_fan_label[] = { > + "CPU1_Fan", > + "CPU2_Fan", > + "Front_Fan1-1", > + "Front_Fan1-2", > + "Front_Fan2", > + "Front_Fan3", > + "MEM_Fan1", > + "MEM_Fan2", > + "Rear_Fan1", > + "Rear_Fan2", > + "Flex_Bay_Fan1", > + "Flex_Bay_Fan2", > + "Flex_Bay_Fan2", > + "PSU_HDD_Fan", > + "PSU1_Fan", > + "PSU2_Fan", > +}; > + > +static int p7_fan_map[] = {0, 2, 3, 4, 5, 6, 7, 8, 10, 11, 14}; > + > +static const char * const p7_ec_fan_label[] = { > + "CPU1_Fan", > + "HP_CPU_Fan1", > + "HP_CPU_Fan2", > + "PCIE1_4_Fan", > + "PCIE5_7_Fan", > + "MEM_Fan1", > + "MEM_Fan2", > + "Rear_Fan1", > + "BCB_Fan", > + "Flex_Bay_Fan", > + "PSU_Fan", > +}; > + > +static int p5_fan_map[] = {0, 5, 6, 7, 8, 10, 11, 14}; > + > +static const char * const p5_ec_fan_label[] = { > + "CPU_Fan", > + "HDD_Fan", > + "Duct_Fan1", > + "MEM_Fan", > + "Rear_Fan", > + "Front_Fan", > + "Flex_Bay_Fan", > + "PSU_Fan", > +}; > + > +static int p8_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14}; > + > +static const char * const p8_ec_fan_label[] = { > + "CPU1_Fan", > + "CPU2_Fan", > + "HP_CPU_Fan1", > + "HP_CPU_Fan2", > + "PCIE1_4_Fan", > + "PCIE5_7_Fan", > + "DIMM1_Fan1", > + "DIMM1_Fan2", > + "DIMM2_Fan1", > + "DIMM2_Fan2", > + "Rear_Fan", > + "HDD_Bay_Fan", > + "Flex_Bay_Fan", > + "PSU_Fan", > +}; > + > +struct ec_sensors_data { > + struct mutex mec_mutex; /* lock for sensor data access */ > + const char *const *fan_labels; > + const char *const *temp_labels; > + const int *fan_map; > + const int *temp_map; > +}; > + > +static int > +lenovo_ec_do_read_temp(struct ec_sensors_data *data, u32 attr, int channel, long *val) > +{ > + u8 lsb; > + > + switch (attr) { > + case hwmon_temp_input: > + mutex_lock(&data->mec_mutex); > + lsb = get_ec_reg(2, 0x81 + channel); > + mutex_unlock(&data->mec_mutex); > + if (lsb <= 0x40) > + return -ENODATA; > + *val = (lsb - 0x40) * 1000; > + return 0; > + default: > + return -EOPNOTSUPP; > + } > +} > + > +static int > +lenovo_ec_do_read_fan(struct ec_sensors_data *data, u32 attr, int channel, long *val) > +{ > + u8 lsb, msb; > + > + channel *= 2; > + switch (attr) { > + case hwmon_fan_input: > + mutex_lock(&data->mec_mutex); > + lsb = get_ec_reg(4, 0x20 + channel); > + msb = get_ec_reg(4, 0x21 + channel); > + mutex_unlock(&data->mec_mutex); > + *val = (msb << 8) + lsb; > + return 0; > + case hwmon_fan_max: > + mutex_lock(&data->mec_mutex); > + lsb = get_ec_reg(4, 0x40 + channel); > + msb = get_ec_reg(4, 0x41 + channel); > + mutex_unlock(&data->mec_mutex); > + *val = (msb << 8) + lsb; > + return 0; > + case hwmon_fan_min: > + case hwmon_fan_div: > + case hwmon_fan_alarm: The above case statements are pointless. Removed while applying. > + default: > + return -EOPNOTSUPP; > + } > +} > + > +static int > +lenovo_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, const char **str) > +{ > + struct ec_sensors_data *state = dev_get_drvdata(dev); > + > + switch (type) { > + case hwmon_temp: > + *str = state->temp_labels[channel]; > + return 0; > + case hwmon_fan: > + *str = state->fan_labels[channel]; > + return 0; > + default: > + return -EOPNOTSUPP; > + } > +} > + > +static int > +lenovo_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long *val) > +{ > + struct ec_sensors_data *data = dev_get_drvdata(dev); > + > + switch (type) { > + case hwmon_temp: > + return lenovo_ec_do_read_temp(data, attr, data->temp_map[channel], val); > + case hwmon_fan: > + return lenovo_ec_do_read_fan(data, attr, data->fan_map[channel], val); > + default: > + return -EOPNOTSUPP; > + } > +} > + > +static umode_t > +lenovo_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, > + u32 attr, int channel) > +{ > + switch (type) { > + case hwmon_temp: > + if (attr == hwmon_temp_input || attr == hwmon_temp_label) > + return 0444; > + return 0; > + case hwmon_fan: > + if (attr == hwmon_fan_input || attr == hwmon_fan_max || attr == hwmon_fan_label) > + return 0444; > + return 0; > + default: > + return 0; > + } > +} > + > +static const struct hwmon_channel_info *lenovo_ec_hwmon_info_px[] = { > + HWMON_CHANNEL_INFO(temp, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL), > + HWMON_CHANNEL_INFO(fan, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX), > + NULL > +}; > + > +static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p8[] = { > + HWMON_CHANNEL_INFO(temp, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL), > + HWMON_CHANNEL_INFO(fan, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX), > + NULL > +}; > + > +static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p7[] = { > + HWMON_CHANNEL_INFO(temp, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL), > + HWMON_CHANNEL_INFO(fan, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX), > + NULL > +}; > + > +static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p5[] = { > + HWMON_CHANNEL_INFO(temp, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL), > + HWMON_CHANNEL_INFO(fan, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, > + HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX), > + NULL > +}; > + > +static const struct hwmon_ops lenovo_ec_hwmon_ops = { > + .is_visible = lenovo_ec_hwmon_is_visible, > + .read = lenovo_ec_hwmon_read, > + .read_string = lenovo_ec_hwmon_read_string, > +}; > + > +static struct hwmon_chip_info lenovo_ec_chip_info = { > + .ops = &lenovo_ec_hwmon_ops, > +}; > + > +static const struct dmi_system_id thinkstation_dmi_table[] = { > + { > + .ident = "LENOVO_PX", > + .driver_data = (void *)(long)LENOVO_PX, > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "30EU"), > + }, > + }, > + { > + .ident = "LENOVO_PX", > + .driver_data = (void *)(long)LENOVO_PX, > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "30EV"), > + }, > + }, > + { > + .ident = "LENOVO_P7", > + .driver_data = (void *)(long)LENOVO_P7, > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "30F2"), > + }, > + }, > + { > + .ident = "LENOVO_P7", > + .driver_data = (void *)(long)LENOVO_P7, > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "30F3"), > + }, > + }, > + { > + .ident = "LENOVO_P5", > + .driver_data = (void *)(long)LENOVO_P5, > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "30G9"), > + }, > + }, > + { > + .ident = "LENOVO_P5", > + .driver_data = (void *)(long)LENOVO_P5, > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "30GA"), > + }, > + }, > + { > + .ident = "LENOVO_P8", > + .driver_data = (void *)(long)LENOVO_P8, > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "30HH"), > + }, > + }, > + { > + .ident = "LENOVO_P8", > + .driver_data = (void *)(long)LENOVO_P8, > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "30HJ"), > + }, > + }, > + {} > +}; > +MODULE_DEVICE_TABLE(dmi, thinkstation_dmi_table); > + > +static int lenovo_ec_probe(struct platform_device *pdev) > +{ > + struct device *hwdev; > + struct ec_sensors_data *ec_data; > + const struct hwmon_chip_info *chip_info; > + struct device *dev = &pdev->dev; > + const struct dmi_system_id *dmi_id; > + int app_id; > + > + ec_data = devm_kzalloc(dev, sizeof(struct ec_sensors_data), GFP_KERNEL); > + if (!ec_data) > + return -ENOMEM; > + > + if (!request_region(IO_REGION_START, IO_REGION_LENGTH, "LNV-WKS")) { > + pr_err(":request fail\n"); > + return -EIO; > + } > + > + dev_set_drvdata(dev, ec_data); > + > + chip_info = &lenovo_ec_chip_info; > + > + mutex_init(&ec_data->mec_mutex); > + > + mutex_lock(&ec_data->mec_mutex); > + app_id = inb_p(MCHP_EMI0_APPLICATION_ID); > + if (app_id) /* check EMI Application ID Value */ > + outb_p(app_id, MCHP_EMI0_APPLICATION_ID); /* set EMI Application ID to 0 */ > + outw_p(MCHP_SING_IDX, MCHP_EMI0_EC_ADDRESS); > + mutex_unlock(&ec_data->mec_mutex); > + > + if ((inb_p(MCHP_EMI0_EC_DATA_BYTE0) != 'M') && > + (inb_p(MCHP_EMI0_EC_DATA_BYTE1) != 'C') && > + (inb_p(MCHP_EMI0_EC_DATA_BYTE2) != 'H') && > + (inb_p(MCHP_EMI0_EC_DATA_BYTE3) != 'P')) { > + release_region(IO_REGION_START, IO_REGION_LENGTH); > + return -ENODEV; > + } > + > + dmi_id = dmi_first_match(thinkstation_dmi_table); > + > + switch ((long)dmi_id->driver_data) { > + case 0: > + ec_data->fan_labels = px_ec_fan_label; > + ec_data->temp_labels = lenovo_px_ec_temp_label; > + ec_data->fan_map = px_fan_map; > + ec_data->temp_map = px_temp_map; > + lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_px; > + break; > + case 1: > + ec_data->fan_labels = p7_ec_fan_label; > + ec_data->temp_labels = lenovo_gen_ec_temp_label; > + ec_data->fan_map = p7_fan_map; > + ec_data->temp_map = gen_temp_map; > + lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p7; > + break; > + case 2: > + ec_data->fan_labels = p5_ec_fan_label; > + ec_data->temp_labels = lenovo_gen_ec_temp_label; > + ec_data->fan_map = p5_fan_map; > + ec_data->temp_map = gen_temp_map; > + lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p5; > + break; > + case 3: > + ec_data->fan_labels = p8_ec_fan_label; > + ec_data->temp_labels = lenovo_gen_ec_temp_label; > + ec_data->fan_map = p8_fan_map; > + ec_data->temp_map = gen_temp_map; > + lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p8; > + break; > + default: > + release_region(IO_REGION_START, IO_REGION_LENGTH); > + return -ENODEV; > + } > + > + hwdev = devm_hwmon_device_register_with_info(dev, "lenovo_ec", > + ec_data, > + chip_info, NULL); > + > + return PTR_ERR_OR_ZERO(hwdev); > +} > + > +static struct platform_driver lenovo_ec_sensors_platform_driver = { > + .driver = { > + .name = "lenovo-ec-sensors", > + }, > + .probe = lenovo_ec_probe, > +}; > + > +static struct platform_device *lenovo_ec_sensors_platform_device; > + > +static int __init lenovo_ec_init(void) > +{ > + if (!dmi_check_system(thinkstation_dmi_table)) > + return -ENODEV; > + > + lenovo_ec_sensors_platform_device = > + platform_create_bundle(&lenovo_ec_sensors_platform_driver, > + lenovo_ec_probe, NULL, 0, NULL, 0); > + > + if (IS_ERR(lenovo_ec_sensors_platform_device)) { > + release_region(IO_REGION_START, IO_REGION_LENGTH); > + return PTR_ERR(lenovo_ec_sensors_platform_device); > + } > + > + return 0; > +} > +module_init(lenovo_ec_init); > + > +static void __exit lenovo_ec_exit(void) > +{ > + release_region(IO_REGION_START, IO_REGION_LENGTH); > + platform_device_unregister(lenovo_ec_sensors_platform_device); > + platform_driver_unregister(&lenovo_ec_sensors_platform_driver); > +} > +module_exit(lenovo_ec_exit); > + > +MODULE_AUTHOR("David Ober <dober@xxxxxxxxxx>"); > +MODULE_DESCRIPTION("HWMON driver for sensors accesssible via EC in LENOVO motherboards"); > +MODULE_LICENSE("GPL");