brintos

brintos / linux-shallow public Read only

0
0
Text · 15.5 KiB · 143fb79 Raw
603 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * HWMON driver for Lenovo ThinkStation based workstations4 * via the embedded controller registers5 *6 * Copyright (C) 2024 David Ober (Lenovo) <dober@lenovo.com>7 *8 * EC provides:9 * - CPU temperature10 * - DIMM temperature11 * - Chassis zone temperatures12 * - CPU fan RPM13 * - DIMM fan RPM14 * - Chassis fans RPM15 */16 17#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt18 19#include <linux/acpi.h>20#include <linux/bits.h>21#include <linux/delay.h>22#include <linux/device.h>23#include <linux/dmi.h>24#include <linux/err.h>25#include <linux/hwmon.h>26#include <linux/io.h>27#include <linux/ioport.h>28#include <linux/module.h>29#include <linux/mutex.h>30#include <linux/platform_device.h>31#include <linux/types.h>32#include <linux/units.h>33 34#define MCHP_SING_IDX			0x000035#define MCHP_EMI0_APPLICATION_ID	0x090C36#define MCHP_EMI0_EC_ADDRESS		0x090237#define MCHP_EMI0_EC_DATA_BYTE0		0x090438#define MCHP_EMI0_EC_DATA_BYTE1		0x090539#define MCHP_EMI0_EC_DATA_BYTE2		0x090640#define MCHP_EMI0_EC_DATA_BYTE3		0x090741#define IO_REGION_START			0x090042#define IO_REGION_LENGTH		0xD43 44static inline u845get_ec_reg(unsigned char page, unsigned char index)46{47	u8 onebyte;48	unsigned short m_index;49	unsigned short phy_index = page * 256 + index;50 51	outb_p(0x01, MCHP_EMI0_APPLICATION_ID);52 53	m_index = phy_index & GENMASK(14, 2);54	outw_p(m_index, MCHP_EMI0_EC_ADDRESS);55 56	onebyte = inb_p(MCHP_EMI0_EC_DATA_BYTE0 + (phy_index & GENMASK(1, 0)));57 58	outb_p(0x01, MCHP_EMI0_APPLICATION_ID);  /* write 0x01 again to clean */59	return onebyte;60}61 62enum systems {63	LENOVO_PX,64	LENOVO_P7,65	LENOVO_P5,66	LENOVO_P8,67};68 69static int px_temp_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};70 71static const char * const lenovo_px_ec_temp_label[] = {72	"CPU1",73	"CPU2",74	"R_DIMM1",75	"L_DIMM1",76	"R_DIMM2",77	"L_DIMM2",78	"PCH",79	"M2_R",80	"M2_Z1R",81	"M2_Z2R",82	"PCI_Z1",83	"PCI_Z2",84	"PCI_Z3",85	"PCI_Z4",86	"AMB",87};88 89static int gen_temp_map[] = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};90 91static const char * const lenovo_gen_ec_temp_label[] = {92	"CPU1",93	"R_DIMM",94	"L_DIMM",95	"PCH",96	"M2_R",97	"M2_Z1R",98	"M2_Z2R",99	"PCI_Z1",100	"PCI_Z2",101	"PCI_Z3",102	"PCI_Z4",103	"AMB",104};105 106static int px_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};107 108static const char * const px_ec_fan_label[] = {109	"CPU1_Fan",110	"CPU2_Fan",111	"Front_Fan1-1",112	"Front_Fan1-2",113	"Front_Fan2",114	"Front_Fan3",115	"MEM_Fan1",116	"MEM_Fan2",117	"Rear_Fan1",118	"Rear_Fan2",119	"Flex_Bay_Fan1",120	"Flex_Bay_Fan2",121	"Flex_Bay_Fan2",122	"PSU_HDD_Fan",123	"PSU1_Fan",124	"PSU2_Fan",125};126 127static int p7_fan_map[] = {0, 2, 3, 4, 5, 6, 7, 8, 10, 11, 14};128 129static const char * const p7_ec_fan_label[] = {130	"CPU1_Fan",131	"HP_CPU_Fan1",132	"HP_CPU_Fan2",133	"PCIE1_4_Fan",134	"PCIE5_7_Fan",135	"MEM_Fan1",136	"MEM_Fan2",137	"Rear_Fan1",138	"BCB_Fan",139	"Flex_Bay_Fan",140	"PSU_Fan",141};142 143static int p5_fan_map[] = {0, 5, 6, 7, 8, 10, 11, 14};144 145static const char * const p5_ec_fan_label[] = {146	"CPU_Fan",147	"HDD_Fan",148	"Duct_Fan1",149	"MEM_Fan",150	"Rear_Fan",151	"Front_Fan",152	"Flex_Bay_Fan",153	"PSU_Fan",154};155 156static int p8_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14};157 158static const char * const p8_ec_fan_label[] = {159	"CPU1_Fan",160	"CPU2_Fan",161	"HP_CPU_Fan1",162	"HP_CPU_Fan2",163	"PCIE1_4_Fan",164	"PCIE5_7_Fan",165	"DIMM1_Fan1",166	"DIMM1_Fan2",167	"DIMM2_Fan1",168	"DIMM2_Fan2",169	"Rear_Fan",170	"HDD_Bay_Fan",171	"Flex_Bay_Fan",172	"PSU_Fan",173};174 175struct ec_sensors_data {176	struct mutex mec_mutex; /* lock for sensor data access */177	const char *const *fan_labels;178	const char *const *temp_labels;179	const int *fan_map;180	const int *temp_map;181};182 183static int184lenovo_ec_do_read_temp(struct ec_sensors_data *data, u32 attr, int channel, long *val)185{186	u8 lsb;187 188	switch (attr) {189	case hwmon_temp_input:190		mutex_lock(&data->mec_mutex);191		lsb = get_ec_reg(2, 0x81 + channel);192		mutex_unlock(&data->mec_mutex);193		if (lsb <= 0x40)194			return -ENODATA;195		*val = (lsb - 0x40) * 1000;196		return 0;197	default:198		return -EOPNOTSUPP;199	}200}201 202static int203lenovo_ec_do_read_fan(struct ec_sensors_data *data, u32 attr, int channel, long *val)204{205	u8 lsb, msb;206 207	channel *= 2;208	switch (attr) {209	case hwmon_fan_input:210		mutex_lock(&data->mec_mutex);211		lsb = get_ec_reg(4, 0x20 + channel);212		msb = get_ec_reg(4, 0x21 + channel);213		mutex_unlock(&data->mec_mutex);214		*val = (msb << 8) + lsb;215		return 0;216	case hwmon_fan_max:217		mutex_lock(&data->mec_mutex);218		lsb = get_ec_reg(4, 0x40 + channel);219		msb = get_ec_reg(4, 0x41 + channel);220		mutex_unlock(&data->mec_mutex);221		*val = (msb << 8) + lsb;222		return 0;223	default:224		return -EOPNOTSUPP;225	}226}227 228static int229lenovo_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,230			    u32 attr, int channel, const char **str)231{232	struct ec_sensors_data *state = dev_get_drvdata(dev);233 234	switch (type) {235	case hwmon_temp:236		*str = state->temp_labels[channel];237		return 0;238	case hwmon_fan:239		*str = state->fan_labels[channel];240		return 0;241	default:242		return -EOPNOTSUPP;243	}244}245 246static int247lenovo_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,248		     u32 attr, int channel, long *val)249{250	struct ec_sensors_data *data = dev_get_drvdata(dev);251 252	switch (type) {253	case hwmon_temp:254		return lenovo_ec_do_read_temp(data, attr, data->temp_map[channel], val);255	case hwmon_fan:256		return lenovo_ec_do_read_fan(data, attr, data->fan_map[channel], val);257	default:258		return -EOPNOTSUPP;259	}260}261 262static umode_t263lenovo_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,264			   u32 attr, int channel)265{266	switch (type) {267	case hwmon_temp:268		if (attr == hwmon_temp_input || attr == hwmon_temp_label)269			return 0444;270		return 0;271	case hwmon_fan:272		if (attr == hwmon_fan_input || attr == hwmon_fan_max || attr == hwmon_fan_label)273			return 0444;274		return 0;275	default:276		return 0;277	}278}279 280static const struct hwmon_channel_info *lenovo_ec_hwmon_info_px[] = {281	HWMON_CHANNEL_INFO(temp,282			   HWMON_T_INPUT | HWMON_T_LABEL,283			   HWMON_T_INPUT | HWMON_T_LABEL,284			   HWMON_T_INPUT | HWMON_T_LABEL,285			   HWMON_T_INPUT | HWMON_T_LABEL,286			   HWMON_T_INPUT | HWMON_T_LABEL,287			   HWMON_T_INPUT | HWMON_T_LABEL,288			   HWMON_T_INPUT | HWMON_T_LABEL,289			   HWMON_T_INPUT | HWMON_T_LABEL,290			   HWMON_T_INPUT | HWMON_T_LABEL,291			   HWMON_T_INPUT | HWMON_T_LABEL,292			   HWMON_T_INPUT | HWMON_T_LABEL,293			   HWMON_T_INPUT | HWMON_T_LABEL,294			   HWMON_T_INPUT | HWMON_T_LABEL,295			   HWMON_T_INPUT | HWMON_T_LABEL,296			   HWMON_T_INPUT | HWMON_T_LABEL),297	HWMON_CHANNEL_INFO(fan,298			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,299			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,300			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,301			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,302			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,303			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,304			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,305			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,306			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,307			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,308			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,309			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,310			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,311			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,312			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,313			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX),314	NULL315};316 317static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p8[] = {318	HWMON_CHANNEL_INFO(temp,319			   HWMON_T_INPUT | HWMON_T_LABEL,320			   HWMON_T_INPUT | HWMON_T_LABEL,321			   HWMON_T_INPUT | HWMON_T_LABEL,322			   HWMON_T_INPUT | HWMON_T_LABEL,323			   HWMON_T_INPUT | HWMON_T_LABEL,324			   HWMON_T_INPUT | HWMON_T_LABEL,325			   HWMON_T_INPUT | HWMON_T_LABEL,326			   HWMON_T_INPUT | HWMON_T_LABEL,327			   HWMON_T_INPUT | HWMON_T_LABEL,328			   HWMON_T_INPUT | HWMON_T_LABEL,329			   HWMON_T_INPUT | HWMON_T_LABEL,330			   HWMON_T_INPUT | HWMON_T_LABEL),331	HWMON_CHANNEL_INFO(fan,332			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,333			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,334			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,335			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,336			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,337			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,338			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,339			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,340			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,341			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,342			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,343			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,344			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,345			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX),346	NULL347};348 349static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p7[] = {350	HWMON_CHANNEL_INFO(temp,351			   HWMON_T_INPUT | HWMON_T_LABEL,352			   HWMON_T_INPUT | HWMON_T_LABEL,353			   HWMON_T_INPUT | HWMON_T_LABEL,354			   HWMON_T_INPUT | HWMON_T_LABEL,355			   HWMON_T_INPUT | HWMON_T_LABEL,356			   HWMON_T_INPUT | HWMON_T_LABEL,357			   HWMON_T_INPUT | HWMON_T_LABEL,358			   HWMON_T_INPUT | HWMON_T_LABEL,359			   HWMON_T_INPUT | HWMON_T_LABEL,360			   HWMON_T_INPUT | HWMON_T_LABEL,361			   HWMON_T_INPUT | HWMON_T_LABEL,362			   HWMON_T_INPUT | HWMON_T_LABEL),363	HWMON_CHANNEL_INFO(fan,364			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,365			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,366			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,367			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,368			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,369			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,370			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,371			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,372			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,373			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,374			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX),375	NULL376};377 378static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p5[] = {379	HWMON_CHANNEL_INFO(temp,380			   HWMON_T_INPUT | HWMON_T_LABEL,381			   HWMON_T_INPUT | HWMON_T_LABEL,382			   HWMON_T_INPUT | HWMON_T_LABEL,383			   HWMON_T_INPUT | HWMON_T_LABEL,384			   HWMON_T_INPUT | HWMON_T_LABEL,385			   HWMON_T_INPUT | HWMON_T_LABEL,386			   HWMON_T_INPUT | HWMON_T_LABEL,387			   HWMON_T_INPUT | HWMON_T_LABEL,388			   HWMON_T_INPUT | HWMON_T_LABEL,389			   HWMON_T_INPUT | HWMON_T_LABEL,390			   HWMON_T_INPUT | HWMON_T_LABEL,391			   HWMON_T_INPUT | HWMON_T_LABEL),392	HWMON_CHANNEL_INFO(fan,393			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,394			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,395			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,396			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,397			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,398			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,399			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX,400			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX),401	NULL402};403 404static const struct hwmon_ops lenovo_ec_hwmon_ops = {405	.is_visible = lenovo_ec_hwmon_is_visible,406	.read = lenovo_ec_hwmon_read,407	.read_string = lenovo_ec_hwmon_read_string,408};409 410static struct hwmon_chip_info lenovo_ec_chip_info = {411	.ops = &lenovo_ec_hwmon_ops,412};413 414static const struct dmi_system_id thinkstation_dmi_table[] = {415	{416		.ident = "LENOVO_PX",417		.driver_data = (void *)(long)LENOVO_PX,418		.matches = {419			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),420			DMI_MATCH(DMI_PRODUCT_NAME, "30EU"),421		},422	},423	{424		.ident = "LENOVO_PX",425		.driver_data = (void *)(long)LENOVO_PX,426		.matches = {427			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),428			DMI_MATCH(DMI_PRODUCT_NAME, "30EV"),429		},430	},431	{432		.ident = "LENOVO_P7",433		.driver_data = (void *)(long)LENOVO_P7,434		.matches = {435			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),436			DMI_MATCH(DMI_PRODUCT_NAME, "30F2"),437		},438	},439	{440		.ident = "LENOVO_P7",441		.driver_data = (void *)(long)LENOVO_P7,442		.matches = {443			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),444			DMI_MATCH(DMI_PRODUCT_NAME, "30F3"),445		},446	},447	{448		.ident = "LENOVO_P5",449		.driver_data = (void *)(long)LENOVO_P5,450		.matches = {451			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),452			DMI_MATCH(DMI_PRODUCT_NAME, "30G9"),453		},454	},455	{456		.ident = "LENOVO_P5",457		.driver_data = (void *)(long)LENOVO_P5,458		.matches = {459			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),460			DMI_MATCH(DMI_PRODUCT_NAME, "30GA"),461		},462	},463	{464		.ident = "LENOVO_P8",465		.driver_data = (void *)(long)LENOVO_P8,466		.matches = {467			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),468			DMI_MATCH(DMI_PRODUCT_NAME, "30HH"),469		},470	},471	{472		.ident = "LENOVO_P8",473		.driver_data = (void *)(long)LENOVO_P8,474		.matches = {475			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),476			DMI_MATCH(DMI_PRODUCT_NAME, "30HJ"),477		},478	},479	{}480};481MODULE_DEVICE_TABLE(dmi, thinkstation_dmi_table);482 483static int lenovo_ec_probe(struct platform_device *pdev)484{485	struct device *hwdev;486	struct ec_sensors_data *ec_data;487	const struct hwmon_chip_info *chip_info;488	struct device *dev = &pdev->dev;489	const struct dmi_system_id *dmi_id;490	int app_id;491 492	ec_data = devm_kzalloc(dev, sizeof(struct ec_sensors_data), GFP_KERNEL);493	if (!ec_data)494		return -ENOMEM;495 496	if (!request_region(IO_REGION_START, IO_REGION_LENGTH, "LNV-WKS")) {497		pr_err(":request fail\n");498		return -EIO;499	}500 501	dev_set_drvdata(dev, ec_data);502 503	chip_info = &lenovo_ec_chip_info;504 505	mutex_init(&ec_data->mec_mutex);506 507	mutex_lock(&ec_data->mec_mutex);508	app_id = inb_p(MCHP_EMI0_APPLICATION_ID);509	if (app_id) /* check EMI Application ID Value */510		outb_p(app_id, MCHP_EMI0_APPLICATION_ID); /* set EMI Application ID to 0 */511	outw_p(MCHP_SING_IDX, MCHP_EMI0_EC_ADDRESS);512	mutex_unlock(&ec_data->mec_mutex);513 514	if ((inb_p(MCHP_EMI0_EC_DATA_BYTE0) != 'M') &&515	    (inb_p(MCHP_EMI0_EC_DATA_BYTE1) != 'C') &&516	    (inb_p(MCHP_EMI0_EC_DATA_BYTE2) != 'H') &&517	    (inb_p(MCHP_EMI0_EC_DATA_BYTE3) != 'P')) {518		release_region(IO_REGION_START, IO_REGION_LENGTH);519		return -ENODEV;520	}521 522	dmi_id = dmi_first_match(thinkstation_dmi_table);523 524	switch ((long)dmi_id->driver_data) {525	case 0:526		ec_data->fan_labels = px_ec_fan_label;527		ec_data->temp_labels = lenovo_px_ec_temp_label;528		ec_data->fan_map = px_fan_map;529		ec_data->temp_map = px_temp_map;530		lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_px;531		break;532	case 1:533		ec_data->fan_labels = p7_ec_fan_label;534		ec_data->temp_labels = lenovo_gen_ec_temp_label;535		ec_data->fan_map = p7_fan_map;536		ec_data->temp_map = gen_temp_map;537		lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p7;538		break;539	case 2:540		ec_data->fan_labels = p5_ec_fan_label;541		ec_data->temp_labels = lenovo_gen_ec_temp_label;542		ec_data->fan_map = p5_fan_map;543		ec_data->temp_map = gen_temp_map;544		lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p5;545		break;546	case 3:547		ec_data->fan_labels = p8_ec_fan_label;548		ec_data->temp_labels = lenovo_gen_ec_temp_label;549		ec_data->fan_map = p8_fan_map;550		ec_data->temp_map = gen_temp_map;551		lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p8;552		break;553	default:554		release_region(IO_REGION_START, IO_REGION_LENGTH);555		return -ENODEV;556	}557 558	hwdev = devm_hwmon_device_register_with_info(dev, "lenovo_ec",559						     ec_data,560						     chip_info, NULL);561 562	return PTR_ERR_OR_ZERO(hwdev);563}564 565static struct platform_driver lenovo_ec_sensors_platform_driver = {566	.driver = {567		.name	= "lenovo-ec-sensors",568	},569	.probe = lenovo_ec_probe,570};571 572static struct platform_device *lenovo_ec_sensors_platform_device;573 574static int __init lenovo_ec_init(void)575{576	if (!dmi_check_system(thinkstation_dmi_table))577		return -ENODEV;578 579	lenovo_ec_sensors_platform_device =580		platform_create_bundle(&lenovo_ec_sensors_platform_driver,581				       lenovo_ec_probe, NULL, 0, NULL, 0);582 583	if (IS_ERR(lenovo_ec_sensors_platform_device)) {584		release_region(IO_REGION_START, IO_REGION_LENGTH);585		return PTR_ERR(lenovo_ec_sensors_platform_device);586	}587 588	return 0;589}590module_init(lenovo_ec_init);591 592static void __exit lenovo_ec_exit(void)593{594	release_region(IO_REGION_START, IO_REGION_LENGTH);595	platform_device_unregister(lenovo_ec_sensors_platform_device);596	platform_driver_unregister(&lenovo_ec_sensors_platform_driver);597}598module_exit(lenovo_ec_exit);599 600MODULE_AUTHOR("David Ober <dober@lenovo.com>");601MODULE_DESCRIPTION("HWMON driver for sensors accessible via EC in LENOVO motherboards");602MODULE_LICENSE("GPL");603