blob: 7c16c1c80ef1b12549050f1fc67c007206a24316 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 smsc47m1.c - Part of lm_sensors, Linux kernel modules
3 for hardware monitoring
4
Jean Delvare60917802006-10-08 22:00:44 +02005 Supports the SMSC LPC47B27x, LPC47M10x, LPC47M112, LPC47M13x,
Jean Delvare8eccbb62007-05-08 17:21:59 +02006 LPC47M14x, LPC47M15x, LPC47M192, LPC47M292 and LPC47M997
7 Super-I/O chips.
Linus Torvalds1da177e2005-04-16 15:20:36 -07008
9 Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
Jean Delvare8eccbb62007-05-08 17:21:59 +020010 Copyright (C) 2004-2007 Jean Delvare <khali@linux-fr.org>
Linus Torvalds1da177e2005-04-16 15:20:36 -070011 Ported to Linux 2.6 by Gabriele Gorla <gorlik@yahoo.com>
12 and Jean Delvare
13
14 This program is free software; you can redistribute it and/or modify
15 it under the terms of the GNU General Public License as published by
16 the Free Software Foundation; either version 2 of the License, or
17 (at your option) any later version.
18
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with this program; if not, write to the Free Software
26 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27*/
28
29#include <linux/module.h>
30#include <linux/slab.h>
31#include <linux/ioport.h>
32#include <linux/jiffies.h>
Jean Delvare51f2cca2007-05-08 17:22:00 +020033#include <linux/platform_device.h>
Mark M. Hoffman943b0832005-07-15 21:39:18 -040034#include <linux/hwmon.h>
35#include <linux/err.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070036#include <linux/init.h>
Ingo Molnar9a61bf62006-01-18 23:19:26 +010037#include <linux/mutex.h>
Jean Delvarece8c6ce12006-09-24 21:25:12 +020038#include <linux/sysfs.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070039#include <asm/io.h>
40
Jean Delvare51f2cca2007-05-08 17:22:00 +020041static struct platform_device *pdev;
42
43#define DRVNAME "smsc47m1"
Jean Delvare8eccbb62007-05-08 17:21:59 +020044enum chips { smsc47m1, smsc47m2 };
Linus Torvalds1da177e2005-04-16 15:20:36 -070045
46/* Super-I/0 registers and commands */
47
48#define REG 0x2e /* The register to read/write */
49#define VAL 0x2f /* The value to read/write */
50
51static inline void
52superio_outb(int reg, int val)
53{
54 outb(reg, REG);
55 outb(val, VAL);
56}
57
58static inline int
59superio_inb(int reg)
60{
61 outb(reg, REG);
62 return inb(VAL);
63}
64
65/* logical device for fans is 0x0A */
66#define superio_select() superio_outb(0x07, 0x0A)
67
68static inline void
69superio_enter(void)
70{
71 outb(0x55, REG);
72}
73
74static inline void
75superio_exit(void)
76{
77 outb(0xAA, REG);
78}
79
80#define SUPERIO_REG_ACT 0x30
81#define SUPERIO_REG_BASE 0x60
82#define SUPERIO_REG_DEVID 0x20
83
84/* Logical device registers */
85
86#define SMSC_EXTENT 0x80
87
88/* nr is 0 or 1 in the macros below */
89#define SMSC47M1_REG_ALARM 0x04
90#define SMSC47M1_REG_TPIN(nr) (0x34 - (nr))
91#define SMSC47M1_REG_PPIN(nr) (0x36 - (nr))
Linus Torvalds1da177e2005-04-16 15:20:36 -070092#define SMSC47M1_REG_FANDIV 0x58
Jean Delvare8eccbb62007-05-08 17:21:59 +020093
94static const u8 SMSC47M1_REG_FAN[3] = { 0x59, 0x5a, 0x6b };
95static const u8 SMSC47M1_REG_FAN_PRELOAD[3] = { 0x5b, 0x5c, 0x6c };
96static const u8 SMSC47M1_REG_PWM[3] = { 0x56, 0x57, 0x69 };
97
98#define SMSC47M2_REG_ALARM6 0x09
99#define SMSC47M2_REG_TPIN1 0x38
100#define SMSC47M2_REG_TPIN2 0x37
101#define SMSC47M2_REG_TPIN3 0x2d
102#define SMSC47M2_REG_PPIN3 0x2c
103#define SMSC47M2_REG_FANDIV3 0x6a
Linus Torvalds1da177e2005-04-16 15:20:36 -0700104
105#define MIN_FROM_REG(reg,div) ((reg)>=192 ? 0 : \
106 983040/((192-(reg))*(div)))
107#define FAN_FROM_REG(reg,div,preload) ((reg)<=(preload) || (reg)==255 ? 0 : \
108 983040/(((reg)-(preload))*(div)))
109#define DIV_FROM_REG(reg) (1 << (reg))
110#define PWM_FROM_REG(reg) (((reg) & 0x7E) << 1)
111#define PWM_EN_FROM_REG(reg) ((~(reg)) & 0x01)
112#define PWM_TO_REG(reg) (((reg) >> 1) & 0x7E)
113
114struct smsc47m1_data {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200115 unsigned short addr;
116 const char *name;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200117 enum chips type;
Mark M. Hoffman943b0832005-07-15 21:39:18 -0400118 struct class_device *class_dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700119
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100120 struct mutex update_lock;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700121 unsigned long last_updated; /* In jiffies */
122
Jean Delvare8eccbb62007-05-08 17:21:59 +0200123 u8 fan[3]; /* Register value */
124 u8 fan_preload[3]; /* Register value */
125 u8 fan_div[3]; /* Register encoding, shifted right */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700126 u8 alarms; /* Register encoding */
Jean Delvare8eccbb62007-05-08 17:21:59 +0200127 u8 pwm[3]; /* Register value (bit 0 is disable) */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700128};
129
Jean Delvare51f2cca2007-05-08 17:22:00 +0200130struct smsc47m1_sio_data {
131 enum chips type;
132};
Linus Torvalds1da177e2005-04-16 15:20:36 -0700133
Jean Delvare51f2cca2007-05-08 17:22:00 +0200134
135static int smsc47m1_probe(struct platform_device *pdev);
136static int smsc47m1_remove(struct platform_device *pdev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700137static struct smsc47m1_data *smsc47m1_update_device(struct device *dev,
138 int init);
139
Jean Delvare51f2cca2007-05-08 17:22:00 +0200140static inline int smsc47m1_read_value(struct smsc47m1_data *data, u8 reg)
Jean Delvare94e183f2007-05-08 17:21:59 +0200141{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200142 return inb_p(data->addr + reg);
Jean Delvare94e183f2007-05-08 17:21:59 +0200143}
144
Jean Delvare51f2cca2007-05-08 17:22:00 +0200145static inline void smsc47m1_write_value(struct smsc47m1_data *data, u8 reg,
Jean Delvare94e183f2007-05-08 17:21:59 +0200146 u8 value)
147{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200148 outb_p(value, data->addr + reg);
Jean Delvare94e183f2007-05-08 17:21:59 +0200149}
Linus Torvalds1da177e2005-04-16 15:20:36 -0700150
Jean Delvare51f2cca2007-05-08 17:22:00 +0200151static struct platform_driver smsc47m1_driver = {
Laurent Riffardcdaf7932005-11-26 20:37:41 +0100152 .driver = {
Jean Delvare87218842006-09-03 22:36:14 +0200153 .owner = THIS_MODULE,
Jean Delvare51f2cca2007-05-08 17:22:00 +0200154 .name = DRVNAME,
Laurent Riffardcdaf7932005-11-26 20:37:41 +0100155 },
Jean Delvare51f2cca2007-05-08 17:22:00 +0200156 .probe = smsc47m1_probe,
157 .remove = __devexit_p(smsc47m1_remove),
Linus Torvalds1da177e2005-04-16 15:20:36 -0700158};
159
160/* nr is 0 or 1 in the callback functions below */
161
162static ssize_t get_fan(struct device *dev, char *buf, int nr)
163{
164 struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
165 /* This chip (stupidly) stops monitoring fan speed if PWM is
166 enabled and duty cycle is 0%. This is fine if the monitoring
167 and control concern the same fan, but troublesome if they are
168 not (which could as well happen). */
169 int rpm = (data->pwm[nr] & 0x7F) == 0x00 ? 0 :
170 FAN_FROM_REG(data->fan[nr],
171 DIV_FROM_REG(data->fan_div[nr]),
172 data->fan_preload[nr]);
173 return sprintf(buf, "%d\n", rpm);
174}
175
176static ssize_t get_fan_min(struct device *dev, char *buf, int nr)
177{
178 struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
179 int rpm = MIN_FROM_REG(data->fan_preload[nr],
180 DIV_FROM_REG(data->fan_div[nr]));
181 return sprintf(buf, "%d\n", rpm);
182}
183
184static ssize_t get_fan_div(struct device *dev, char *buf, int nr)
185{
186 struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
187 return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr]));
188}
189
190static ssize_t get_pwm(struct device *dev, char *buf, int nr)
191{
192 struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
193 return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[nr]));
194}
195
196static ssize_t get_pwm_en(struct device *dev, char *buf, int nr)
197{
198 struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
199 return sprintf(buf, "%d\n", PWM_EN_FROM_REG(data->pwm[nr]));
200}
201
Yani Ioannoua5099cf2005-05-17 06:42:25 -0400202static ssize_t get_alarms(struct device *dev, struct device_attribute *attr, char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700203{
204 struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
205 return sprintf(buf, "%d\n", data->alarms);
206}
207
208static ssize_t set_fan_min(struct device *dev, const char *buf,
209 size_t count, int nr)
210{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200211 struct smsc47m1_data *data = dev_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700212 long rpmdiv, val = simple_strtol(buf, NULL, 10);
213
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100214 mutex_lock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700215 rpmdiv = val * DIV_FROM_REG(data->fan_div[nr]);
216
217 if (983040 > 192 * rpmdiv || 2 * rpmdiv > 983040) {
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100218 mutex_unlock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700219 return -EINVAL;
220 }
221
222 data->fan_preload[nr] = 192 - ((983040 + rpmdiv / 2) / rpmdiv);
Jean Delvare51f2cca2007-05-08 17:22:00 +0200223 smsc47m1_write_value(data, SMSC47M1_REG_FAN_PRELOAD[nr],
Linus Torvalds1da177e2005-04-16 15:20:36 -0700224 data->fan_preload[nr]);
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100225 mutex_unlock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700226
227 return count;
228}
229
230/* Note: we save and restore the fan minimum here, because its value is
231 determined in part by the fan clock divider. This follows the principle
Andreas Mohrd6e05ed2006-06-26 18:35:02 +0200232 of least surprise; the user doesn't expect the fan minimum to change just
Linus Torvalds1da177e2005-04-16 15:20:36 -0700233 because the divider changed. */
234static ssize_t set_fan_div(struct device *dev, const char *buf,
235 size_t count, int nr)
236{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200237 struct smsc47m1_data *data = dev_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700238
239 long new_div = simple_strtol(buf, NULL, 10), tmp;
240 u8 old_div = DIV_FROM_REG(data->fan_div[nr]);
241
242 if (new_div == old_div) /* No change */
243 return count;
244
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100245 mutex_lock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700246 switch (new_div) {
247 case 1: data->fan_div[nr] = 0; break;
248 case 2: data->fan_div[nr] = 1; break;
249 case 4: data->fan_div[nr] = 2; break;
250 case 8: data->fan_div[nr] = 3; break;
251 default:
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100252 mutex_unlock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700253 return -EINVAL;
254 }
255
Jean Delvare8eccbb62007-05-08 17:21:59 +0200256 switch (nr) {
257 case 0:
258 case 1:
Jean Delvare51f2cca2007-05-08 17:22:00 +0200259 tmp = smsc47m1_read_value(data, SMSC47M1_REG_FANDIV)
Jean Delvare8eccbb62007-05-08 17:21:59 +0200260 & ~(0x03 << (4 + 2 * nr));
261 tmp |= data->fan_div[nr] << (4 + 2 * nr);
Jean Delvare51f2cca2007-05-08 17:22:00 +0200262 smsc47m1_write_value(data, SMSC47M1_REG_FANDIV, tmp);
Jean Delvare8eccbb62007-05-08 17:21:59 +0200263 break;
264 case 2:
Jean Delvare51f2cca2007-05-08 17:22:00 +0200265 tmp = smsc47m1_read_value(data, SMSC47M2_REG_FANDIV3) & 0xCF;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200266 tmp |= data->fan_div[2] << 4;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200267 smsc47m1_write_value(data, SMSC47M2_REG_FANDIV3, tmp);
Jean Delvare8eccbb62007-05-08 17:21:59 +0200268 break;
269 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700270
271 /* Preserve fan min */
272 tmp = 192 - (old_div * (192 - data->fan_preload[nr])
273 + new_div / 2) / new_div;
274 data->fan_preload[nr] = SENSORS_LIMIT(tmp, 0, 191);
Jean Delvare51f2cca2007-05-08 17:22:00 +0200275 smsc47m1_write_value(data, SMSC47M1_REG_FAN_PRELOAD[nr],
Linus Torvalds1da177e2005-04-16 15:20:36 -0700276 data->fan_preload[nr]);
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100277 mutex_unlock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700278
279 return count;
280}
281
282static ssize_t set_pwm(struct device *dev, const char *buf,
283 size_t count, int nr)
284{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200285 struct smsc47m1_data *data = dev_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700286
287 long val = simple_strtol(buf, NULL, 10);
288
289 if (val < 0 || val > 255)
290 return -EINVAL;
291
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100292 mutex_lock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700293 data->pwm[nr] &= 0x81; /* Preserve additional bits */
294 data->pwm[nr] |= PWM_TO_REG(val);
Jean Delvare51f2cca2007-05-08 17:22:00 +0200295 smsc47m1_write_value(data, SMSC47M1_REG_PWM[nr],
Linus Torvalds1da177e2005-04-16 15:20:36 -0700296 data->pwm[nr]);
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100297 mutex_unlock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700298
299 return count;
300}
301
302static ssize_t set_pwm_en(struct device *dev, const char *buf,
303 size_t count, int nr)
304{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200305 struct smsc47m1_data *data = dev_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700306
307 long val = simple_strtol(buf, NULL, 10);
308
309 if (val != 0 && val != 1)
310 return -EINVAL;
311
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100312 mutex_lock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700313 data->pwm[nr] &= 0xFE; /* preserve the other bits */
314 data->pwm[nr] |= !val;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200315 smsc47m1_write_value(data, SMSC47M1_REG_PWM[nr],
Linus Torvalds1da177e2005-04-16 15:20:36 -0700316 data->pwm[nr]);
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100317 mutex_unlock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700318
319 return count;
320}
321
322#define fan_present(offset) \
Yani Ioannoua5099cf2005-05-17 06:42:25 -0400323static ssize_t get_fan##offset (struct device *dev, struct device_attribute *attr, char *buf) \
Linus Torvalds1da177e2005-04-16 15:20:36 -0700324{ \
325 return get_fan(dev, buf, offset - 1); \
326} \
Yani Ioannoua5099cf2005-05-17 06:42:25 -0400327static ssize_t get_fan##offset##_min (struct device *dev, struct device_attribute *attr, char *buf) \
Linus Torvalds1da177e2005-04-16 15:20:36 -0700328{ \
329 return get_fan_min(dev, buf, offset - 1); \
330} \
Yani Ioannoua5099cf2005-05-17 06:42:25 -0400331static ssize_t set_fan##offset##_min (struct device *dev, struct device_attribute *attr, \
Linus Torvalds1da177e2005-04-16 15:20:36 -0700332 const char *buf, size_t count) \
333{ \
334 return set_fan_min(dev, buf, count, offset - 1); \
335} \
Yani Ioannoua5099cf2005-05-17 06:42:25 -0400336static ssize_t get_fan##offset##_div (struct device *dev, struct device_attribute *attr, char *buf) \
Linus Torvalds1da177e2005-04-16 15:20:36 -0700337{ \
338 return get_fan_div(dev, buf, offset - 1); \
339} \
Yani Ioannoua5099cf2005-05-17 06:42:25 -0400340static ssize_t set_fan##offset##_div (struct device *dev, struct device_attribute *attr, \
Linus Torvalds1da177e2005-04-16 15:20:36 -0700341 const char *buf, size_t count) \
342{ \
343 return set_fan_div(dev, buf, count, offset - 1); \
344} \
Yani Ioannoua5099cf2005-05-17 06:42:25 -0400345static ssize_t get_pwm##offset (struct device *dev, struct device_attribute *attr, char *buf) \
Linus Torvalds1da177e2005-04-16 15:20:36 -0700346{ \
347 return get_pwm(dev, buf, offset - 1); \
348} \
Yani Ioannoua5099cf2005-05-17 06:42:25 -0400349static ssize_t set_pwm##offset (struct device *dev, struct device_attribute *attr, \
Linus Torvalds1da177e2005-04-16 15:20:36 -0700350 const char *buf, size_t count) \
351{ \
352 return set_pwm(dev, buf, count, offset - 1); \
353} \
Yani Ioannoua5099cf2005-05-17 06:42:25 -0400354static ssize_t get_pwm##offset##_en (struct device *dev, struct device_attribute *attr, char *buf) \
Linus Torvalds1da177e2005-04-16 15:20:36 -0700355{ \
356 return get_pwm_en(dev, buf, offset - 1); \
357} \
Yani Ioannoua5099cf2005-05-17 06:42:25 -0400358static ssize_t set_pwm##offset##_en (struct device *dev, struct device_attribute *attr, \
Linus Torvalds1da177e2005-04-16 15:20:36 -0700359 const char *buf, size_t count) \
360{ \
361 return set_pwm_en(dev, buf, count, offset - 1); \
362} \
363static DEVICE_ATTR(fan##offset##_input, S_IRUGO, get_fan##offset, \
364 NULL); \
365static DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \
366 get_fan##offset##_min, set_fan##offset##_min); \
367static DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR, \
368 get_fan##offset##_div, set_fan##offset##_div); \
369static DEVICE_ATTR(pwm##offset, S_IRUGO | S_IWUSR, \
370 get_pwm##offset, set_pwm##offset); \
371static DEVICE_ATTR(pwm##offset##_enable, S_IRUGO | S_IWUSR, \
372 get_pwm##offset##_en, set_pwm##offset##_en);
373
374fan_present(1);
375fan_present(2);
Jean Delvare8eccbb62007-05-08 17:21:59 +0200376fan_present(3);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700377
378static DEVICE_ATTR(alarms, S_IRUGO, get_alarms, NULL);
379
Jean Delvare51f2cca2007-05-08 17:22:00 +0200380static ssize_t show_name(struct device *dev, struct device_attribute
381 *devattr, char *buf)
382{
383 struct smsc47m1_data *data = dev_get_drvdata(dev);
384
385 return sprintf(buf, "%s\n", data->name);
386}
387static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
388
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200389/* Almost all sysfs files may or may not be created depending on the chip
390 setup so we create them individually. It is still convenient to define a
391 group to remove them all at once. */
392static struct attribute *smsc47m1_attributes[] = {
393 &dev_attr_fan1_input.attr,
394 &dev_attr_fan1_min.attr,
395 &dev_attr_fan1_div.attr,
396 &dev_attr_fan2_input.attr,
397 &dev_attr_fan2_min.attr,
398 &dev_attr_fan2_div.attr,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200399 &dev_attr_fan3_input.attr,
400 &dev_attr_fan3_min.attr,
401 &dev_attr_fan3_div.attr,
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200402
403 &dev_attr_pwm1.attr,
404 &dev_attr_pwm1_enable.attr,
405 &dev_attr_pwm2.attr,
406 &dev_attr_pwm2_enable.attr,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200407 &dev_attr_pwm3.attr,
408 &dev_attr_pwm3_enable.attr,
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200409
410 &dev_attr_alarms.attr,
Jean Delvare51f2cca2007-05-08 17:22:00 +0200411 &dev_attr_name.attr,
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200412 NULL
413};
414
415static const struct attribute_group smsc47m1_group = {
416 .attrs = smsc47m1_attributes,
417};
418
Jean Delvare51f2cca2007-05-08 17:22:00 +0200419static int __init smsc47m1_find(unsigned short *addr,
420 struct smsc47m1_sio_data *sio_data)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700421{
422 u8 val;
423
424 superio_enter();
Jean Delvare51f2cca2007-05-08 17:22:00 +0200425 val = superio_inb(SUPERIO_REG_DEVID);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700426
427 /*
Jean Delvare60917802006-10-08 22:00:44 +0200428 * SMSC LPC47M10x/LPC47M112/LPC47M13x (device id 0x59), LPC47M14x
429 * (device id 0x5F) and LPC47B27x (device id 0x51) have fan control.
Linus Torvalds1da177e2005-04-16 15:20:36 -0700430 * The LPC47M15x and LPC47M192 chips "with hardware monitoring block"
Jean Delvareec5ce552005-04-26 22:09:43 +0200431 * can do much more besides (device id 0x60).
Jean Delvareb890a072005-10-26 22:21:24 +0200432 * The LPC47M997 is undocumented, but seems to be compatible with
433 * the LPC47M192, and has the same device id.
Jean Delvare8eccbb62007-05-08 17:21:59 +0200434 * The LPC47M292 (device id 0x6B) is somewhat compatible, but it
435 * supports a 3rd fan, and the pin configuration registers are
436 * unfortunately different.
Linus Torvalds1da177e2005-04-16 15:20:36 -0700437 */
Jean Delvare51f2cca2007-05-08 17:22:00 +0200438 switch (val) {
Jean Delvare8eccbb62007-05-08 17:21:59 +0200439 case 0x51:
Jean Delvare620100c2007-05-08 17:22:00 +0200440 pr_info(DRVNAME ": Found SMSC LPC47B27x\n");
Jean Delvare51f2cca2007-05-08 17:22:00 +0200441 sio_data->type = smsc47m1;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200442 break;
443 case 0x59:
Jean Delvare620100c2007-05-08 17:22:00 +0200444 pr_info(DRVNAME ": Found SMSC LPC47M10x/LPC47M112/LPC47M13x\n");
Jean Delvare51f2cca2007-05-08 17:22:00 +0200445 sio_data->type = smsc47m1;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200446 break;
447 case 0x5F:
Jean Delvare620100c2007-05-08 17:22:00 +0200448 pr_info(DRVNAME ": Found SMSC LPC47M14x\n");
Jean Delvare51f2cca2007-05-08 17:22:00 +0200449 sio_data->type = smsc47m1;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200450 break;
451 case 0x60:
Jean Delvare620100c2007-05-08 17:22:00 +0200452 pr_info(DRVNAME ": Found SMSC LPC47M15x/LPC47M192/LPC47M997\n");
Jean Delvare51f2cca2007-05-08 17:22:00 +0200453 sio_data->type = smsc47m1;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200454 break;
455 case 0x6B:
Jean Delvare620100c2007-05-08 17:22:00 +0200456 pr_info(DRVNAME ": Found SMSC LPC47M292\n");
Jean Delvare51f2cca2007-05-08 17:22:00 +0200457 sio_data->type = smsc47m2;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200458 break;
459 default:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700460 superio_exit();
461 return -ENODEV;
462 }
463
464 superio_select();
Jean Delvare2d8672c2005-07-19 23:56:35 +0200465 *addr = (superio_inb(SUPERIO_REG_BASE) << 8)
466 | superio_inb(SUPERIO_REG_BASE + 1);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700467 val = superio_inb(SUPERIO_REG_ACT);
Jean Delvare2d8672c2005-07-19 23:56:35 +0200468 if (*addr == 0 || (val & 0x01) == 0) {
Jean Delvare620100c2007-05-08 17:22:00 +0200469 pr_info(DRVNAME ": Device is disabled, will not use\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700470 superio_exit();
471 return -ENODEV;
472 }
473
474 superio_exit();
475 return 0;
476}
477
Jean Delvare51f2cca2007-05-08 17:22:00 +0200478static int __devinit smsc47m1_probe(struct platform_device *pdev)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700479{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200480 struct device *dev = &pdev->dev;
481 struct smsc47m1_sio_data *sio_data = dev->platform_data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700482 struct smsc47m1_data *data;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200483 struct resource *res;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700484 int err = 0;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200485 int fan1, fan2, fan3, pwm1, pwm2, pwm3;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700486
Jean Delvare51f2cca2007-05-08 17:22:00 +0200487 static const char *names[] = {
488 "smsc47m1",
489 "smsc47m2",
490 };
491
492 res = platform_get_resource(pdev, IORESOURCE_IO, 0);
493 if (!request_region(res->start, SMSC_EXTENT, DRVNAME)) {
494 dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
495 (unsigned long)res->start,
496 (unsigned long)res->end);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700497 return -EBUSY;
498 }
499
Deepak Saxenaba9c2e82005-10-17 23:08:32 +0200500 if (!(data = kzalloc(sizeof(struct smsc47m1_data), GFP_KERNEL))) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700501 err = -ENOMEM;
502 goto error_release;
503 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700504
Jean Delvare51f2cca2007-05-08 17:22:00 +0200505 data->addr = res->start;
506 data->type = sio_data->type;
507 data->name = names[sio_data->type];
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100508 mutex_init(&data->update_lock);
Jean Delvare51f2cca2007-05-08 17:22:00 +0200509 platform_set_drvdata(pdev, data);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700510
511 /* If no function is properly configured, there's no point in
512 actually registering the chip. */
Jean Delvare51f2cca2007-05-08 17:22:00 +0200513 pwm1 = (smsc47m1_read_value(data, SMSC47M1_REG_PPIN(0)) & 0x05)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700514 == 0x04;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200515 pwm2 = (smsc47m1_read_value(data, SMSC47M1_REG_PPIN(1)) & 0x05)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700516 == 0x04;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200517 if (data->type == smsc47m2) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200518 fan1 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN1)
Jean Delvare8eccbb62007-05-08 17:21:59 +0200519 & 0x0d) == 0x09;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200520 fan2 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN2)
Jean Delvare8eccbb62007-05-08 17:21:59 +0200521 & 0x0d) == 0x09;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200522 fan3 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN3)
Jean Delvare8eccbb62007-05-08 17:21:59 +0200523 & 0x0d) == 0x0d;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200524 pwm3 = (smsc47m1_read_value(data, SMSC47M2_REG_PPIN3)
Jean Delvare8eccbb62007-05-08 17:21:59 +0200525 & 0x0d) == 0x08;
526 } else {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200527 fan1 = (smsc47m1_read_value(data, SMSC47M1_REG_TPIN(0))
Jean Delvare8eccbb62007-05-08 17:21:59 +0200528 & 0x05) == 0x05;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200529 fan2 = (smsc47m1_read_value(data, SMSC47M1_REG_TPIN(1))
Jean Delvare8eccbb62007-05-08 17:21:59 +0200530 & 0x05) == 0x05;
531 fan3 = 0;
532 pwm3 = 0;
533 }
534 if (!(fan1 || fan2 || fan3 || pwm1 || pwm2 || pwm3)) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200535 dev_warn(dev, "Device not configured, will not use\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700536 err = -ENODEV;
537 goto error_free;
538 }
539
Linus Torvalds1da177e2005-04-16 15:20:36 -0700540 /* Some values (fan min, clock dividers, pwm registers) may be
541 needed before any update is triggered, so we better read them
542 at least once here. We don't usually do it that way, but in
543 this particular case, manually reading 5 registers out of 8
544 doesn't make much sense and we're better using the existing
545 function. */
Jean Delvare51f2cca2007-05-08 17:22:00 +0200546 smsc47m1_update_device(dev, 1);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700547
Mark M. Hoffman943b0832005-07-15 21:39:18 -0400548 /* Register sysfs hooks */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700549 if (fan1) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200550 if ((err = device_create_file(dev, &dev_attr_fan1_input))
551 || (err = device_create_file(dev, &dev_attr_fan1_min))
552 || (err = device_create_file(dev, &dev_attr_fan1_div)))
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200553 goto error_remove_files;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700554 } else
Jean Delvare51f2cca2007-05-08 17:22:00 +0200555 dev_dbg(dev, "Fan 1 not enabled by hardware, skipping\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700556
557 if (fan2) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200558 if ((err = device_create_file(dev, &dev_attr_fan2_input))
559 || (err = device_create_file(dev, &dev_attr_fan2_min))
560 || (err = device_create_file(dev, &dev_attr_fan2_div)))
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200561 goto error_remove_files;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700562 } else
Jean Delvare51f2cca2007-05-08 17:22:00 +0200563 dev_dbg(dev, "Fan 2 not enabled by hardware, skipping\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700564
Jean Delvare8eccbb62007-05-08 17:21:59 +0200565 if (fan3) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200566 if ((err = device_create_file(dev, &dev_attr_fan3_input))
567 || (err = device_create_file(dev, &dev_attr_fan3_min))
568 || (err = device_create_file(dev, &dev_attr_fan3_div)))
Jean Delvare8eccbb62007-05-08 17:21:59 +0200569 goto error_remove_files;
570 } else
Jean Delvare51f2cca2007-05-08 17:22:00 +0200571 dev_dbg(dev, "Fan 3 not enabled by hardware, skipping\n");
Jean Delvare8eccbb62007-05-08 17:21:59 +0200572
Linus Torvalds1da177e2005-04-16 15:20:36 -0700573 if (pwm1) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200574 if ((err = device_create_file(dev, &dev_attr_pwm1))
575 || (err = device_create_file(dev, &dev_attr_pwm1_enable)))
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200576 goto error_remove_files;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700577 } else
Jean Delvare51f2cca2007-05-08 17:22:00 +0200578 dev_dbg(dev, "PWM 1 not enabled by hardware, skipping\n");
Jean Delvare8eccbb62007-05-08 17:21:59 +0200579
Linus Torvalds1da177e2005-04-16 15:20:36 -0700580 if (pwm2) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200581 if ((err = device_create_file(dev, &dev_attr_pwm2))
582 || (err = device_create_file(dev, &dev_attr_pwm2_enable)))
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200583 goto error_remove_files;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700584 } else
Jean Delvare51f2cca2007-05-08 17:22:00 +0200585 dev_dbg(dev, "PWM 2 not enabled by hardware, skipping\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700586
Jean Delvare8eccbb62007-05-08 17:21:59 +0200587 if (pwm3) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200588 if ((err = device_create_file(dev, &dev_attr_pwm3))
589 || (err = device_create_file(dev, &dev_attr_pwm3_enable)))
Jean Delvare8eccbb62007-05-08 17:21:59 +0200590 goto error_remove_files;
591 } else
Jean Delvare51f2cca2007-05-08 17:22:00 +0200592 dev_dbg(dev, "PWM 3 not enabled by hardware, skipping\n");
Jean Delvare8eccbb62007-05-08 17:21:59 +0200593
Jean Delvare51f2cca2007-05-08 17:22:00 +0200594 if ((err = device_create_file(dev, &dev_attr_alarms)))
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200595 goto error_remove_files;
596
Jean Delvare51f2cca2007-05-08 17:22:00 +0200597 data->class_dev = hwmon_device_register(dev);
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200598 if (IS_ERR(data->class_dev)) {
599 err = PTR_ERR(data->class_dev);
600 goto error_remove_files;
601 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700602
603 return 0;
604
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200605error_remove_files:
Jean Delvare51f2cca2007-05-08 17:22:00 +0200606 sysfs_remove_group(&dev->kobj, &smsc47m1_group);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700607error_free:
Alexey Dobriyan1f57ff82005-08-26 01:49:14 +0400608 kfree(data);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700609error_release:
Jean Delvare51f2cca2007-05-08 17:22:00 +0200610 release_region(res->start, SMSC_EXTENT);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700611 return err;
612}
613
Jean Delvare51f2cca2007-05-08 17:22:00 +0200614static int __devexit smsc47m1_remove(struct platform_device *pdev)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700615{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200616 struct smsc47m1_data *data = platform_get_drvdata(pdev);
617 struct resource *res;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700618
Jean Delvare51f2cca2007-05-08 17:22:00 +0200619 platform_set_drvdata(pdev, NULL);
Mark M. Hoffman943b0832005-07-15 21:39:18 -0400620 hwmon_device_unregister(data->class_dev);
Jean Delvare51f2cca2007-05-08 17:22:00 +0200621 sysfs_remove_group(&pdev->dev.kobj, &smsc47m1_group);
Mark M. Hoffman943b0832005-07-15 21:39:18 -0400622
Jean Delvare51f2cca2007-05-08 17:22:00 +0200623 res = platform_get_resource(pdev, IORESOURCE_IO, 0);
624 release_region(res->start, SMSC_EXTENT);
Mark M. Hoffman943b0832005-07-15 21:39:18 -0400625 kfree(data);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700626
627 return 0;
628}
629
Linus Torvalds1da177e2005-04-16 15:20:36 -0700630static struct smsc47m1_data *smsc47m1_update_device(struct device *dev,
631 int init)
632{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200633 struct smsc47m1_data *data = dev_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700634
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100635 mutex_lock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700636
637 if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || init) {
Jean Delvare8eccbb62007-05-08 17:21:59 +0200638 int i, fan_nr;
639 fan_nr = data->type == smsc47m2 ? 3 : 2;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700640
Jean Delvare8eccbb62007-05-08 17:21:59 +0200641 for (i = 0; i < fan_nr; i++) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200642 data->fan[i] = smsc47m1_read_value(data,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200643 SMSC47M1_REG_FAN[i]);
Jean Delvare51f2cca2007-05-08 17:22:00 +0200644 data->fan_preload[i] = smsc47m1_read_value(data,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200645 SMSC47M1_REG_FAN_PRELOAD[i]);
Jean Delvare51f2cca2007-05-08 17:22:00 +0200646 data->pwm[i] = smsc47m1_read_value(data,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200647 SMSC47M1_REG_PWM[i]);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700648 }
649
Jean Delvare51f2cca2007-05-08 17:22:00 +0200650 i = smsc47m1_read_value(data, SMSC47M1_REG_FANDIV);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700651 data->fan_div[0] = (i >> 4) & 0x03;
652 data->fan_div[1] = i >> 6;
653
Jean Delvare51f2cca2007-05-08 17:22:00 +0200654 data->alarms = smsc47m1_read_value(data,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700655 SMSC47M1_REG_ALARM) >> 6;
656 /* Clear alarms if needed */
657 if (data->alarms)
Jean Delvare51f2cca2007-05-08 17:22:00 +0200658 smsc47m1_write_value(data, SMSC47M1_REG_ALARM, 0xC0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700659
Jean Delvare8eccbb62007-05-08 17:21:59 +0200660 if (fan_nr >= 3) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200661 data->fan_div[2] = (smsc47m1_read_value(data,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200662 SMSC47M2_REG_FANDIV3) >> 4) & 0x03;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200663 data->alarms |= (smsc47m1_read_value(data,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200664 SMSC47M2_REG_ALARM6) & 0x40) >> 4;
665 /* Clear alarm if needed */
666 if (data->alarms & 0x04)
Jean Delvare51f2cca2007-05-08 17:22:00 +0200667 smsc47m1_write_value(data,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200668 SMSC47M2_REG_ALARM6,
669 0x40);
670 }
671
Linus Torvalds1da177e2005-04-16 15:20:36 -0700672 data->last_updated = jiffies;
673 }
674
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100675 mutex_unlock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700676 return data;
677}
678
Jean Delvare51f2cca2007-05-08 17:22:00 +0200679static int __init smsc47m1_device_add(unsigned short address,
680 const struct smsc47m1_sio_data *sio_data)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700681{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200682 struct resource res = {
683 .start = address,
684 .end = address + SMSC_EXTENT - 1,
685 .name = DRVNAME,
686 .flags = IORESOURCE_IO,
687 };
688 int err;
689
690 pdev = platform_device_alloc(DRVNAME, address);
691 if (!pdev) {
692 err = -ENOMEM;
693 printk(KERN_ERR DRVNAME ": Device allocation failed\n");
694 goto exit;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700695 }
696
Jean Delvare51f2cca2007-05-08 17:22:00 +0200697 err = platform_device_add_resources(pdev, &res, 1);
698 if (err) {
699 printk(KERN_ERR DRVNAME ": Device resource addition failed "
700 "(%d)\n", err);
701 goto exit_device_put;
702 }
703
704 pdev->dev.platform_data = kmalloc(sizeof(struct smsc47m1_sio_data),
705 GFP_KERNEL);
706 if (!pdev->dev.platform_data) {
707 err = -ENOMEM;
708 printk(KERN_ERR DRVNAME ": Platform data allocation failed\n");
709 goto exit_device_put;
710 }
711 memcpy(pdev->dev.platform_data, sio_data,
712 sizeof(struct smsc47m1_sio_data));
713
714 err = platform_device_add(pdev);
715 if (err) {
716 printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
717 err);
718 goto exit_device_put;
719 }
720
721 return 0;
722
723exit_device_put:
724 platform_device_put(pdev);
725exit:
726 return err;
727}
728
729static int __init sm_smsc47m1_init(void)
730{
731 int err;
732 unsigned short address;
733 struct smsc47m1_sio_data sio_data;
734
735 if (smsc47m1_find(&address, &sio_data))
736 return -ENODEV;
737
738 err = platform_driver_register(&smsc47m1_driver);
739 if (err)
740 goto exit;
741
742 /* Sets global pdev as a side effect */
743 err = smsc47m1_device_add(address, &sio_data);
744 if (err)
745 goto exit_driver;
746
747 return 0;
748
749exit_driver:
750 platform_driver_unregister(&smsc47m1_driver);
751exit:
752 return err;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700753}
754
755static void __exit sm_smsc47m1_exit(void)
756{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200757 platform_device_unregister(pdev);
758 platform_driver_unregister(&smsc47m1_driver);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700759}
760
761MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>");
762MODULE_DESCRIPTION("SMSC LPC47M1xx fan sensors driver");
763MODULE_LICENSE("GPL");
764
765module_init(sm_smsc47m1_init);
766module_exit(sm_smsc47m1_exit);