| // 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@lenovo.com> |
| * |
| * 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; |
| 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@lenovo.com>"); |
| MODULE_DESCRIPTION("HWMON driver for sensors accessible via EC in LENOVO motherboards"); |
| MODULE_LICENSE("GPL"); |