1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * HWMON driver for Lenovo ThinkStation based workstations
4 * via the embedded controller registers
5 *
6 * Copyright (C) 2024 David Ober (Lenovo) <dober@lenovo.com>
7 *
8 * EC provides:
9 * - CPU temperature
10 * - DIMM temperature
11 * - Chassis zone temperatures
12 * - CPU fan RPM
13 * - DIMM fan RPM
14 * - Chassis fans RPM
15 */
16
17#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
18
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			0x0000
35#define MCHP_EMI0_APPLICATION_ID	0x090C
36#define MCHP_EMI0_EC_ADDRESS		0x0902
37#define MCHP_EMI0_EC_DATA_BYTE0		0x0904
38#define MCHP_EMI0_EC_DATA_BYTE1		0x0905
39#define MCHP_EMI0_EC_DATA_BYTE2		0x0906
40#define MCHP_EMI0_EC_DATA_BYTE3		0x0907
41#define IO_REGION_START			0x0900
42#define IO_REGION_LENGTH		0xD
43
44static inline u8
45get_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 int
184lenovo_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 int
203lenovo_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 int
229lenovo_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 int
247lenovo_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_t
263lenovo_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	NULL
315};
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	NULL
347};
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	NULL
376};
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	NULL
402};
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