aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorJean Delvare <khali@linux-fr.org>2006-12-12 12:18:29 -0500
committerJean Delvare <khali@arrakis.delvare>2006-12-12 12:18:29 -0500
commitba224e2c4f0a706714ccb483b0c21d32f5994f67 (patch)
tree572d75a19909ec69a3298135a1646dceffbc584f /drivers
parent8e9afcbbdef71aeeb510732f4f8d5ac3de863df0 (diff)
hwmon: New PC87427 hardware monitoring driver
This is a new hardware monitoring driver for the National Semiconductor PC87427 Super-I/O chip. It only supports fan speed monitoring for now, while the chip can do much more. Thanks to Amir Habibi at Candelis for setting up a test system, and to Michael Kress for testing several iterations of this driver. Signed-off-by: Jean Delvare <khali@linux-fr.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/hwmon/Kconfig13
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/pc87427.c627
3 files changed, 641 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 39ab2fc128cf..e379ac41c09c 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -353,6 +353,19 @@ config SENSORS_PC87360
353 This driver can also be built as a module. If so, the module 353 This driver can also be built as a module. If so, the module
354 will be called pc87360. 354 will be called pc87360.
355 355
356config SENSORS_PC87427
357 tristate "National Semiconductor PC87427"
358 depends on HWMON && EXPERIMENTAL
359 help
360 If you say yes here you get access to the hardware monitoring
361 functions of the National Semiconductor PC87427 Super-I/O chip.
362 The chip has two distinct logical devices, one for fan speed
363 monitoring and control, and one for voltage and temperature
364 monitoring. Only fan speed monitoring is supported right now.
365
366 This driver can also be built as a module. If so, the module
367 will be called pc87427.
368
356config SENSORS_SIS5595 369config SENSORS_SIS5595
357 tristate "Silicon Integrated Systems Corp. SiS5595" 370 tristate "Silicon Integrated Systems Corp. SiS5595"
358 depends on HWMON && I2C && PCI && EXPERIMENTAL 371 depends on HWMON && I2C && PCI && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index af01cc64f7d2..95f3fc561186 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_SENSORS_LM90) += lm90.o
41obj-$(CONFIG_SENSORS_LM92) += lm92.o 41obj-$(CONFIG_SENSORS_LM92) += lm92.o
42obj-$(CONFIG_SENSORS_MAX1619) += max1619.o 42obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
43obj-$(CONFIG_SENSORS_PC87360) += pc87360.o 43obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
44obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
44obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o 45obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
45obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o 46obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
46obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o 47obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
diff --git a/drivers/hwmon/pc87427.c b/drivers/hwmon/pc87427.c
new file mode 100644
index 000000000000..affa21a5ccfd
--- /dev/null
+++ b/drivers/hwmon/pc87427.c
@@ -0,0 +1,627 @@
1/*
2 * pc87427.c - hardware monitoring driver for the
3 * National Semiconductor PC87427 Super-I/O chip
4 * Copyright (C) 2006 Jean Delvare <khali@linux-fr.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * Supports the following chips:
16 *
17 * Chip #vin #fan #pwm #temp devid
18 * PC87427 - 8 - - 0xF2
19 *
20 * This driver assumes that no more than one chip is present.
21 * Only fan inputs are supported so far, although the chip can do much more.
22 */
23
24#include <linux/module.h>
25#include <linux/init.h>
26#include <linux/slab.h>
27#include <linux/jiffies.h>
28#include <linux/platform_device.h>
29#include <linux/hwmon.h>
30#include <linux/hwmon-sysfs.h>
31#include <linux/err.h>
32#include <linux/mutex.h>
33#include <linux/sysfs.h>
34#include <asm/io.h>
35
36static struct platform_device *pdev;
37
38#define DRVNAME "pc87427"
39
40/* The lock mutex protects both the I/O accesses (needed because the
41 device is using banked registers) and the register cache (needed to keep
42 the data in the registers and the cache in sync at any time). */
43struct pc87427_data {
44 struct class_device *class_dev;
45 struct mutex lock;
46 int address[2];
47 const char *name;
48
49 unsigned long last_updated; /* in jiffies */
50 u8 fan_enabled; /* bit vector */
51 u16 fan[8]; /* register values */
52 u16 fan_min[8]; /* register values */
53 u8 fan_status[8]; /* register values */
54};
55
56/*
57 * Super-I/O registers and operations
58 */
59
60#define SIOREG_LDSEL 0x07 /* Logical device select */
61#define SIOREG_DEVID 0x20 /* Device ID */
62#define SIOREG_ACT 0x30 /* Device activation */
63#define SIOREG_MAP 0x50 /* I/O or memory mapping */
64#define SIOREG_IOBASE 0x60 /* I/O base address */
65
66static const u8 logdev[2] = { 0x09, 0x14 };
67static const char *logdev_str[2] = { DRVNAME " FMC", DRVNAME " HMC" };
68#define LD_FAN 0
69#define LD_IN 1
70#define LD_TEMP 1
71
72static inline void superio_outb(int sioaddr, int reg, int val)
73{
74 outb(reg, sioaddr);
75 outb(val, sioaddr + 1);
76}
77
78static inline int superio_inb(int sioaddr, int reg)
79{
80 outb(reg, sioaddr);
81 return inb(sioaddr + 1);
82}
83
84static inline void superio_exit(int sioaddr)
85{
86 outb(0x02, sioaddr);
87 outb(0x02, sioaddr + 1);
88}
89
90/*
91 * Logical devices
92 */
93
94#define REGION_LENGTH 32
95#define PC87427_REG_BANK 0x0f
96#define BANK_FM(nr) (nr)
97#define BANK_FT(nr) (0x08 + (nr))
98#define BANK_FC(nr) (0x10 + (nr) * 2)
99
100/*
101 * I/O access functions
102 */
103
104/* ldi is the logical device index */
105static inline int pc87427_read8(struct pc87427_data *data, u8 ldi, u8 reg)
106{
107 return inb(data->address[ldi] + reg);
108}
109
110/* Must be called with data->lock held, except during init */
111static inline int pc87427_read8_bank(struct pc87427_data *data, u8 ldi,
112 u8 bank, u8 reg)
113{
114 outb(bank, data->address[ldi] + PC87427_REG_BANK);
115 return inb(data->address[ldi] + reg);
116}
117
118/* Must be called with data->lock held, except during init */
119static inline void pc87427_write8_bank(struct pc87427_data *data, u8 ldi,
120 u8 bank, u8 reg, u8 value)
121{
122 outb(bank, data->address[ldi] + PC87427_REG_BANK);
123 outb(value, data->address[ldi] + reg);
124}
125
126/*
127 * Fan registers and conversions
128 */
129
130/* fan data registers are 16-bit wide */
131#define PC87427_REG_FAN 0x12
132#define PC87427_REG_FAN_MIN 0x14
133#define PC87427_REG_FAN_STATUS 0x10
134
135#define FAN_STATUS_STALL (1 << 3)
136#define FAN_STATUS_LOSPD (1 << 1)
137#define FAN_STATUS_MONEN (1 << 0)
138
139/* Dedicated function to read all registers related to a given fan input.
140 This saves us quite a few locks and bank selections.
141 Must be called with data->lock held.
142 nr is from 0 to 7 */
143static void pc87427_readall_fan(struct pc87427_data *data, u8 nr)
144{
145 int iobase = data->address[LD_FAN];
146
147 outb(BANK_FM(nr), iobase + PC87427_REG_BANK);
148 data->fan[nr] = inw(iobase + PC87427_REG_FAN);
149 data->fan_min[nr] = inw(iobase + PC87427_REG_FAN_MIN);
150 data->fan_status[nr] = inb(iobase + PC87427_REG_FAN_STATUS);
151 /* Clear fan alarm bits */
152 outb(data->fan_status[nr], iobase + PC87427_REG_FAN_STATUS);
153}
154
155/* The 2 LSB of fan speed registers are used for something different.
156 The actual 2 LSB of the measurements are not available. */
157static inline unsigned long fan_from_reg(u16 reg)
158{
159 reg &= 0xfffc;
160 if (reg == 0x0000 || reg == 0xfffc)
161 return 0;
162 return 5400000UL / reg;
163}
164
165/* The 2 LSB of the fan speed limit registers are not significant. */
166static inline u16 fan_to_reg(unsigned long val)
167{
168 if (val < 83UL)
169 return 0xffff;
170 if (val >= 1350000UL)
171 return 0x0004;
172 return ((1350000UL + val / 2) / val) << 2;
173}
174
175/*
176 * Data interface
177 */
178
179static struct pc87427_data *pc87427_update_device(struct device *dev)
180{
181 struct pc87427_data *data = dev_get_drvdata(dev);
182 int i;
183
184 mutex_lock(&data->lock);
185 if (!time_after(jiffies, data->last_updated + HZ)
186 && data->last_updated)
187 goto done;
188
189 /* Fans */
190 for (i = 0; i < 8; i++) {
191 if (!(data->fan_enabled & (1 << i)))
192 continue;
193 pc87427_readall_fan(data, i);
194 }
195 data->last_updated = jiffies;
196
197done:
198 mutex_unlock(&data->lock);
199 return data;
200}
201
202static ssize_t show_fan_input(struct device *dev, struct device_attribute
203 *devattr, char *buf)
204{
205 struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
206 struct pc87427_data *data = pc87427_update_device(dev);
207 int nr = attr->index;
208
209 return sprintf(buf, "%lu\n", fan_from_reg(data->fan[nr]));
210}
211
212static ssize_t show_fan_min(struct device *dev, struct device_attribute
213 *devattr, char *buf)
214{
215 struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
216 struct pc87427_data *data = pc87427_update_device(dev);
217 int nr = attr->index;
218
219 return sprintf(buf, "%lu\n", fan_from_reg(data->fan_min[nr]));
220}
221
222static ssize_t show_fan_alarm(struct device *dev, struct device_attribute
223 *devattr, char *buf)
224{
225 struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
226 struct pc87427_data *data = pc87427_update_device(dev);
227 int nr = attr->index;
228
229 return sprintf(buf, "%d\n", !!(data->fan_status[nr]
230 & FAN_STATUS_LOSPD));
231}
232
233static ssize_t show_fan_fault(struct device *dev, struct device_attribute
234 *devattr, char *buf)
235{
236 struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
237 struct pc87427_data *data = pc87427_update_device(dev);
238 int nr = attr->index;
239
240 return sprintf(buf, "%d\n", !!(data->fan_status[nr]
241 & FAN_STATUS_STALL));
242}
243
244static ssize_t set_fan_min(struct device *dev, struct device_attribute
245 *devattr, const char *buf, size_t count)
246{
247 struct pc87427_data *data = dev_get_drvdata(dev);
248 struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
249 int nr = attr->index;
250 unsigned long val = simple_strtoul(buf, NULL, 10);
251 int iobase = data->address[LD_FAN];
252
253 mutex_lock(&data->lock);
254 outb(BANK_FM(nr), iobase + PC87427_REG_BANK);
255 /* The low speed limit registers are read-only while monitoring
256 is enabled, so we have to disable monitoring, then change the
257 limit, and finally enable monitoring again. */
258 outb(0, iobase + PC87427_REG_FAN_STATUS);
259 data->fan_min[nr] = fan_to_reg(val);
260 outw(data->fan_min[nr], iobase + PC87427_REG_FAN_MIN);
261 outb(FAN_STATUS_MONEN, iobase + PC87427_REG_FAN_STATUS);
262 mutex_unlock(&data->lock);
263
264 return count;
265}
266
267static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input, NULL, 0);
268static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input, NULL, 1);
269static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan_input, NULL, 2);
270static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan_input, NULL, 3);
271static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, show_fan_input, NULL, 4);
272static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, show_fan_input, NULL, 5);
273static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, show_fan_input, NULL, 6);
274static SENSOR_DEVICE_ATTR(fan8_input, S_IRUGO, show_fan_input, NULL, 7);
275
276static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO,
277 show_fan_min, set_fan_min, 0);
278static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO,
279 show_fan_min, set_fan_min, 1);
280static SENSOR_DEVICE_ATTR(fan3_min, S_IWUSR | S_IRUGO,
281 show_fan_min, set_fan_min, 2);
282static SENSOR_DEVICE_ATTR(fan4_min, S_IWUSR | S_IRUGO,
283 show_fan_min, set_fan_min, 3);
284static SENSOR_DEVICE_ATTR(fan5_min, S_IWUSR | S_IRUGO,
285 show_fan_min, set_fan_min, 4);
286static SENSOR_DEVICE_ATTR(fan6_min, S_IWUSR | S_IRUGO,
287 show_fan_min, set_fan_min, 5);
288static SENSOR_DEVICE_ATTR(fan7_min, S_IWUSR | S_IRUGO,
289 show_fan_min, set_fan_min, 6);
290static SENSOR_DEVICE_ATTR(fan8_min, S_IWUSR | S_IRUGO,
291 show_fan_min, set_fan_min, 7);
292
293static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0);
294static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1);
295static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2);
296static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3);
297static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO, show_fan_alarm, NULL, 4);
298static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO, show_fan_alarm, NULL, 5);
299static SENSOR_DEVICE_ATTR(fan7_alarm, S_IRUGO, show_fan_alarm, NULL, 6);
300static SENSOR_DEVICE_ATTR(fan8_alarm, S_IRUGO, show_fan_alarm, NULL, 7);
301
302static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_fan_fault, NULL, 0);
303static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_fan_fault, NULL, 1);
304static SENSOR_DEVICE_ATTR(fan3_fault, S_IRUGO, show_fan_fault, NULL, 2);
305static SENSOR_DEVICE_ATTR(fan4_fault, S_IRUGO, show_fan_fault, NULL, 3);
306static SENSOR_DEVICE_ATTR(fan5_fault, S_IRUGO, show_fan_fault, NULL, 4);
307static SENSOR_DEVICE_ATTR(fan6_fault, S_IRUGO, show_fan_fault, NULL, 5);
308static SENSOR_DEVICE_ATTR(fan7_fault, S_IRUGO, show_fan_fault, NULL, 6);
309static SENSOR_DEVICE_ATTR(fan8_fault, S_IRUGO, show_fan_fault, NULL, 7);
310
311static struct attribute *pc87427_attributes_fan[8][5] = {
312 {
313 &sensor_dev_attr_fan1_input.dev_attr.attr,
314 &sensor_dev_attr_fan1_min.dev_attr.attr,
315 &sensor_dev_attr_fan1_alarm.dev_attr.attr,
316 &sensor_dev_attr_fan1_fault.dev_attr.attr,
317 NULL
318 }, {
319 &sensor_dev_attr_fan2_input.dev_attr.attr,
320 &sensor_dev_attr_fan2_min.dev_attr.attr,
321 &sensor_dev_attr_fan2_alarm.dev_attr.attr,
322 &sensor_dev_attr_fan2_fault.dev_attr.attr,
323 NULL
324 }, {
325 &sensor_dev_attr_fan3_input.dev_attr.attr,
326 &sensor_dev_attr_fan3_min.dev_attr.attr,
327 &sensor_dev_attr_fan3_alarm.dev_attr.attr,
328 &sensor_dev_attr_fan3_fault.dev_attr.attr,
329 NULL
330 }, {
331 &sensor_dev_attr_fan4_input.dev_attr.attr,
332 &sensor_dev_attr_fan4_min.dev_attr.attr,
333 &sensor_dev_attr_fan4_alarm.dev_attr.attr,
334 &sensor_dev_attr_fan4_fault.dev_attr.attr,
335 NULL
336 }, {
337 &sensor_dev_attr_fan5_input.dev_attr.attr,
338 &sensor_dev_attr_fan5_min.dev_attr.attr,
339 &sensor_dev_attr_fan5_alarm.dev_attr.attr,
340 &sensor_dev_attr_fan5_fault.dev_attr.attr,
341 NULL
342 }, {
343 &sensor_dev_attr_fan6_input.dev_attr.attr,
344 &sensor_dev_attr_fan6_min.dev_attr.attr,
345 &sensor_dev_attr_fan6_alarm.dev_attr.attr,
346 &sensor_dev_attr_fan6_fault.dev_attr.attr,
347 NULL
348 }, {
349 &sensor_dev_attr_fan7_input.dev_attr.attr,
350 &sensor_dev_attr_fan7_min.dev_attr.attr,
351 &sensor_dev_attr_fan7_alarm.dev_attr.attr,
352 &sensor_dev_attr_fan7_fault.dev_attr.attr,
353 NULL
354 }, {
355 &sensor_dev_attr_fan8_input.dev_attr.attr,
356 &sensor_dev_attr_fan8_min.dev_attr.attr,
357 &sensor_dev_attr_fan8_alarm.dev_attr.attr,
358 &sensor_dev_attr_fan8_fault.dev_attr.attr,
359 NULL
360 }
361};
362
363static const struct attribute_group pc87427_group_fan[8] = {
364 { .attrs = pc87427_attributes_fan[0] },
365 { .attrs = pc87427_attributes_fan[1] },
366 { .attrs = pc87427_attributes_fan[2] },
367 { .attrs = pc87427_attributes_fan[3] },
368 { .attrs = pc87427_attributes_fan[4] },
369 { .attrs = pc87427_attributes_fan[5] },
370 { .attrs = pc87427_attributes_fan[6] },
371 { .attrs = pc87427_attributes_fan[7] },
372};
373
374static ssize_t show_name(struct device *dev, struct device_attribute
375 *devattr, char *buf)
376{
377 struct pc87427_data *data = dev_get_drvdata(dev);
378
379 return sprintf(buf, "%s\n", data->name);
380}
381static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
382
383
384/*
385 * Device detection, attach and detach
386 */
387
388static void __devinit pc87427_init_device(struct device *dev)
389{
390 struct pc87427_data *data = dev_get_drvdata(dev);
391 int i;
392 u8 reg;
393
394 /* The FMC module should be ready */
395 reg = pc87427_read8(data, LD_FAN, PC87427_REG_BANK);
396 if (!(reg & 0x80))
397 dev_warn(dev, "FMC module not ready!\n");
398
399 /* Check which fans are enabled */
400 for (i = 0; i < 8; i++) {
401 reg = pc87427_read8_bank(data, LD_FAN, BANK_FM(i),
402 PC87427_REG_FAN_STATUS);
403 if (reg & FAN_STATUS_MONEN)
404 data->fan_enabled |= (1 << i);
405 }
406
407 if (!data->fan_enabled) {
408 dev_dbg(dev, "Enabling all fan inputs\n");
409 for (i = 0; i < 8; i++)
410 pc87427_write8_bank(data, LD_FAN, BANK_FM(i),
411 PC87427_REG_FAN_STATUS,
412 FAN_STATUS_MONEN);
413 data->fan_enabled = 0xff;
414 }
415}
416
417static int __devinit pc87427_probe(struct platform_device *pdev)
418{
419 struct pc87427_data *data;
420 struct resource *res;
421 int i, err;
422
423 if (!(data = kzalloc(sizeof(struct pc87427_data), GFP_KERNEL))) {
424 err = -ENOMEM;
425 printk(KERN_ERR DRVNAME ": Out of memory\n");
426 goto exit;
427 }
428
429 /* This will need to be revisited when we add support for
430 temperature and voltage monitoring. */
431 res = platform_get_resource(pdev, IORESOURCE_IO, 0);
432 data->address[0] = res->start;
433
434 mutex_init(&data->lock);
435 data->name = "pc87427";
436 platform_set_drvdata(pdev, data);
437 pc87427_init_device(&pdev->dev);
438
439 /* Register sysfs hooks */
440 if ((err = device_create_file(&pdev->dev, &dev_attr_name)))
441 goto exit_kfree;
442 for (i = 0; i < 8; i++) {
443 if (!(data->fan_enabled & (1 << i)))
444 continue;
445 if ((err = sysfs_create_group(&pdev->dev.kobj,
446 &pc87427_group_fan[i])))
447 goto exit_remove_files;
448 }
449
450 data->class_dev = hwmon_device_register(&pdev->dev);
451 if (IS_ERR(data->class_dev)) {
452 err = PTR_ERR(data->class_dev);
453 dev_err(&pdev->dev, "Class registration failed (%d)\n", err);
454 goto exit_remove_files;
455 }
456
457 return 0;
458
459exit_remove_files:
460 for (i = 0; i < 8; i++) {
461 if (!(data->fan_enabled & (1 << i)))
462 continue;
463 sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]);
464 }
465exit_kfree:
466 platform_set_drvdata(pdev, NULL);
467 kfree(data);
468exit:
469 return err;
470}
471
472static int __devexit pc87427_remove(struct platform_device *pdev)
473{
474 struct pc87427_data *data = platform_get_drvdata(pdev);
475 int i;
476
477 platform_set_drvdata(pdev, NULL);
478 hwmon_device_unregister(data->class_dev);
479 device_remove_file(&pdev->dev, &dev_attr_name);
480 for (i = 0; i < 8; i++) {
481 if (!(data->fan_enabled & (1 << i)))
482 continue;
483 sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]);
484 }
485 kfree(data);
486
487 return 0;
488}
489
490
491static struct platform_driver pc87427_driver = {
492 .driver = {
493 .owner = THIS_MODULE,
494 .name = DRVNAME,
495 },
496 .probe = pc87427_probe,
497 .remove = __devexit_p(pc87427_remove),
498};
499
500static int __init pc87427_device_add(unsigned short address)
501{
502 struct resource res = {
503 .start = address,
504 .end = address + REGION_LENGTH - 1,
505 .name = logdev_str[0],
506 .flags = IORESOURCE_IO,
507 };
508 int err;
509
510 pdev = platform_device_alloc(DRVNAME, address);
511 if (!pdev) {
512 err = -ENOMEM;
513 printk(KERN_ERR DRVNAME ": Device allocation failed\n");
514 goto exit;
515 }
516
517 err = platform_device_add_resources(pdev, &res, 1);
518 if (err) {
519 printk(KERN_ERR DRVNAME ": Device resource addition failed "
520 "(%d)\n", err);
521 goto exit_device_put;
522 }
523
524 err = platform_device_add(pdev);
525 if (err) {
526 printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
527 err);
528 goto exit_device_put;
529 }
530
531 return 0;
532
533exit_device_put:
534 platform_device_put(pdev);
535exit:
536 return err;
537}
538
539static int __init pc87427_find(int sioaddr, unsigned short *address)
540{
541 u16 val;
542 int i, err = 0;
543
544 /* Identify device */
545 val = superio_inb(sioaddr, SIOREG_DEVID);
546 if (val != 0xf2) { /* PC87427 */
547 err = -ENODEV;
548 goto exit;
549 }
550
551 for (i = 0; i < 2; i++) {
552 address[i] = 0;
553 /* Select logical device */
554 superio_outb(sioaddr, SIOREG_LDSEL, logdev[i]);
555
556 val = superio_inb(sioaddr, SIOREG_ACT);
557 if (!(val & 0x01)) {
558 printk(KERN_INFO DRVNAME ": Logical device 0x%02x "
559 "not activated\n", logdev[i]);
560 continue;
561 }
562
563 val = superio_inb(sioaddr, SIOREG_MAP);
564 if (val & 0x01) {
565 printk(KERN_WARNING DRVNAME ": Logical device 0x%02x "
566 "is memory-mapped, can't use\n", logdev[i]);
567 continue;
568 }
569
570 val = (superio_inb(sioaddr, SIOREG_IOBASE) << 8)
571 | superio_inb(sioaddr, SIOREG_IOBASE + 1);
572 if (!val) {
573 printk(KERN_INFO DRVNAME ": I/O base address not set "
574 "for logical device 0x%02x\n", logdev[i]);
575 continue;
576 }
577 address[i] = val;
578 }
579
580exit:
581 superio_exit(sioaddr);
582 return err;
583}
584
585static int __init pc87427_init(void)
586{
587 int err;
588 unsigned short address[2];
589
590 if (pc87427_find(0x2e, address)
591 && pc87427_find(0x4e, address))
592 return -ENODEV;
593
594 /* For now the driver only handles fans so we only care about the
595 first address. */
596 if (!address[0])
597 return -ENODEV;
598
599 err = platform_driver_register(&pc87427_driver);
600 if (err)
601 goto exit;
602
603 /* Sets global pdev as a side effect */
604 err = pc87427_device_add(address[0]);
605 if (err)
606 goto exit_driver;
607
608 return 0;
609
610exit_driver:
611 platform_driver_unregister(&pc87427_driver);
612exit:
613 return err;
614}
615
616static void __exit pc87427_exit(void)
617{
618 platform_device_unregister(pdev);
619 platform_driver_unregister(&pc87427_driver);
620}
621
622MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>");
623MODULE_DESCRIPTION("PC87427 hardware monitoring driver");
624MODULE_LICENSE("GPL");
625
626module_init(pc87427_init);
627module_exit(pc87427_exit);