blob: 223708897e8e9f2cad8de6466e3fee5b61ae696a [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 Delvareec5ce552005-04-26 22:09:43 +0200440 printk(KERN_INFO "smsc47m1: 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 Delvare60917802006-10-08 22:00:44 +0200444 printk(KERN_INFO "smsc47m1: Found SMSC "
445 "LPC47M10x/LPC47M112/LPC47M13x\n");
Jean Delvare51f2cca2007-05-08 17:22:00 +0200446 sio_data->type = smsc47m1;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200447 break;
448 case 0x5F:
Jean Delvareec5ce552005-04-26 22:09:43 +0200449 printk(KERN_INFO "smsc47m1: Found SMSC LPC47M14x\n");
Jean Delvare51f2cca2007-05-08 17:22:00 +0200450 sio_data->type = smsc47m1;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200451 break;
452 case 0x60:
Jean Delvareb890a072005-10-26 22:21:24 +0200453 printk(KERN_INFO "smsc47m1: Found SMSC "
454 "LPC47M15x/LPC47M192/LPC47M997\n");
Jean Delvare51f2cca2007-05-08 17:22:00 +0200455 sio_data->type = smsc47m1;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200456 break;
457 case 0x6B:
458 printk(KERN_INFO "smsc47m1: Found SMSC LPC47M292\n");
Jean Delvare51f2cca2007-05-08 17:22:00 +0200459 sio_data->type = smsc47m2;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200460 break;
461 default:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700462 superio_exit();
463 return -ENODEV;
464 }
465
466 superio_select();
Jean Delvare2d8672c2005-07-19 23:56:35 +0200467 *addr = (superio_inb(SUPERIO_REG_BASE) << 8)
468 | superio_inb(SUPERIO_REG_BASE + 1);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700469 val = superio_inb(SUPERIO_REG_ACT);
Jean Delvare2d8672c2005-07-19 23:56:35 +0200470 if (*addr == 0 || (val & 0x01) == 0) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700471 printk(KERN_INFO "smsc47m1: Device is disabled, will not use\n");
472 superio_exit();
473 return -ENODEV;
474 }
475
476 superio_exit();
477 return 0;
478}
479
Jean Delvare51f2cca2007-05-08 17:22:00 +0200480static int __devinit smsc47m1_probe(struct platform_device *pdev)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700481{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200482 struct device *dev = &pdev->dev;
483 struct smsc47m1_sio_data *sio_data = dev->platform_data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700484 struct smsc47m1_data *data;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200485 struct resource *res;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700486 int err = 0;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200487 int fan1, fan2, fan3, pwm1, pwm2, pwm3;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700488
Jean Delvare51f2cca2007-05-08 17:22:00 +0200489 static const char *names[] = {
490 "smsc47m1",
491 "smsc47m2",
492 };
493
494 res = platform_get_resource(pdev, IORESOURCE_IO, 0);
495 if (!request_region(res->start, SMSC_EXTENT, DRVNAME)) {
496 dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
497 (unsigned long)res->start,
498 (unsigned long)res->end);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700499 return -EBUSY;
500 }
501
Deepak Saxenaba9c2e82005-10-17 23:08:32 +0200502 if (!(data = kzalloc(sizeof(struct smsc47m1_data), GFP_KERNEL))) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700503 err = -ENOMEM;
504 goto error_release;
505 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700506
Jean Delvare51f2cca2007-05-08 17:22:00 +0200507 data->addr = res->start;
508 data->type = sio_data->type;
509 data->name = names[sio_data->type];
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100510 mutex_init(&data->update_lock);
Jean Delvare51f2cca2007-05-08 17:22:00 +0200511 platform_set_drvdata(pdev, data);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700512
513 /* If no function is properly configured, there's no point in
514 actually registering the chip. */
Jean Delvare51f2cca2007-05-08 17:22:00 +0200515 pwm1 = (smsc47m1_read_value(data, SMSC47M1_REG_PPIN(0)) & 0x05)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700516 == 0x04;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200517 pwm2 = (smsc47m1_read_value(data, SMSC47M1_REG_PPIN(1)) & 0x05)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700518 == 0x04;
Jean Delvare8eccbb62007-05-08 17:21:59 +0200519 if (data->type == smsc47m2) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200520 fan1 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN1)
Jean Delvare8eccbb62007-05-08 17:21:59 +0200521 & 0x0d) == 0x09;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200522 fan2 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN2)
Jean Delvare8eccbb62007-05-08 17:21:59 +0200523 & 0x0d) == 0x09;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200524 fan3 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN3)
Jean Delvare8eccbb62007-05-08 17:21:59 +0200525 & 0x0d) == 0x0d;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200526 pwm3 = (smsc47m1_read_value(data, SMSC47M2_REG_PPIN3)
Jean Delvare8eccbb62007-05-08 17:21:59 +0200527 & 0x0d) == 0x08;
528 } else {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200529 fan1 = (smsc47m1_read_value(data, SMSC47M1_REG_TPIN(0))
Jean Delvare8eccbb62007-05-08 17:21:59 +0200530 & 0x05) == 0x05;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200531 fan2 = (smsc47m1_read_value(data, SMSC47M1_REG_TPIN(1))
Jean Delvare8eccbb62007-05-08 17:21:59 +0200532 & 0x05) == 0x05;
533 fan3 = 0;
534 pwm3 = 0;
535 }
536 if (!(fan1 || fan2 || fan3 || pwm1 || pwm2 || pwm3)) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200537 dev_warn(dev, "Device not configured, will not use\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700538 err = -ENODEV;
539 goto error_free;
540 }
541
Linus Torvalds1da177e2005-04-16 15:20:36 -0700542 /* Some values (fan min, clock dividers, pwm registers) may be
543 needed before any update is triggered, so we better read them
544 at least once here. We don't usually do it that way, but in
545 this particular case, manually reading 5 registers out of 8
546 doesn't make much sense and we're better using the existing
547 function. */
Jean Delvare51f2cca2007-05-08 17:22:00 +0200548 smsc47m1_update_device(dev, 1);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700549
Mark M. Hoffman943b0832005-07-15 21:39:18 -0400550 /* Register sysfs hooks */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700551 if (fan1) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200552 if ((err = device_create_file(dev, &dev_attr_fan1_input))
553 || (err = device_create_file(dev, &dev_attr_fan1_min))
554 || (err = device_create_file(dev, &dev_attr_fan1_div)))
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200555 goto error_remove_files;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700556 } else
Jean Delvare51f2cca2007-05-08 17:22:00 +0200557 dev_dbg(dev, "Fan 1 not enabled by hardware, skipping\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700558
559 if (fan2) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200560 if ((err = device_create_file(dev, &dev_attr_fan2_input))
561 || (err = device_create_file(dev, &dev_attr_fan2_min))
562 || (err = device_create_file(dev, &dev_attr_fan2_div)))
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200563 goto error_remove_files;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700564 } else
Jean Delvare51f2cca2007-05-08 17:22:00 +0200565 dev_dbg(dev, "Fan 2 not enabled by hardware, skipping\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700566
Jean Delvare8eccbb62007-05-08 17:21:59 +0200567 if (fan3) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200568 if ((err = device_create_file(dev, &dev_attr_fan3_input))
569 || (err = device_create_file(dev, &dev_attr_fan3_min))
570 || (err = device_create_file(dev, &dev_attr_fan3_div)))
Jean Delvare8eccbb62007-05-08 17:21:59 +0200571 goto error_remove_files;
572 } else
Jean Delvare51f2cca2007-05-08 17:22:00 +0200573 dev_dbg(dev, "Fan 3 not enabled by hardware, skipping\n");
Jean Delvare8eccbb62007-05-08 17:21:59 +0200574
Linus Torvalds1da177e2005-04-16 15:20:36 -0700575 if (pwm1) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200576 if ((err = device_create_file(dev, &dev_attr_pwm1))
577 || (err = device_create_file(dev, &dev_attr_pwm1_enable)))
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200578 goto error_remove_files;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700579 } else
Jean Delvare51f2cca2007-05-08 17:22:00 +0200580 dev_dbg(dev, "PWM 1 not enabled by hardware, skipping\n");
Jean Delvare8eccbb62007-05-08 17:21:59 +0200581
Linus Torvalds1da177e2005-04-16 15:20:36 -0700582 if (pwm2) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200583 if ((err = device_create_file(dev, &dev_attr_pwm2))
584 || (err = device_create_file(dev, &dev_attr_pwm2_enable)))
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200585 goto error_remove_files;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700586 } else
Jean Delvare51f2cca2007-05-08 17:22:00 +0200587 dev_dbg(dev, "PWM 2 not enabled by hardware, skipping\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700588
Jean Delvare8eccbb62007-05-08 17:21:59 +0200589 if (pwm3) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200590 if ((err = device_create_file(dev, &dev_attr_pwm3))
591 || (err = device_create_file(dev, &dev_attr_pwm3_enable)))
Jean Delvare8eccbb62007-05-08 17:21:59 +0200592 goto error_remove_files;
593 } else
Jean Delvare51f2cca2007-05-08 17:22:00 +0200594 dev_dbg(dev, "PWM 3 not enabled by hardware, skipping\n");
Jean Delvare8eccbb62007-05-08 17:21:59 +0200595
Jean Delvare51f2cca2007-05-08 17:22:00 +0200596 if ((err = device_create_file(dev, &dev_attr_alarms)))
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200597 goto error_remove_files;
598
Jean Delvare51f2cca2007-05-08 17:22:00 +0200599 data->class_dev = hwmon_device_register(dev);
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200600 if (IS_ERR(data->class_dev)) {
601 err = PTR_ERR(data->class_dev);
602 goto error_remove_files;
603 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700604
605 return 0;
606
Jean Delvarece8c6ce12006-09-24 21:25:12 +0200607error_remove_files:
Jean Delvare51f2cca2007-05-08 17:22:00 +0200608 sysfs_remove_group(&dev->kobj, &smsc47m1_group);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700609error_free:
Alexey Dobriyan1f57ff82005-08-26 01:49:14 +0400610 kfree(data);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700611error_release:
Jean Delvare51f2cca2007-05-08 17:22:00 +0200612 release_region(res->start, SMSC_EXTENT);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700613 return err;
614}
615
Jean Delvare51f2cca2007-05-08 17:22:00 +0200616static int __devexit smsc47m1_remove(struct platform_device *pdev)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700617{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200618 struct smsc47m1_data *data = platform_get_drvdata(pdev);
619 struct resource *res;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700620
Jean Delvare51f2cca2007-05-08 17:22:00 +0200621 platform_set_drvdata(pdev, NULL);
Mark M. Hoffman943b0832005-07-15 21:39:18 -0400622 hwmon_device_unregister(data->class_dev);
Jean Delvare51f2cca2007-05-08 17:22:00 +0200623 sysfs_remove_group(&pdev->dev.kobj, &smsc47m1_group);
Mark M. Hoffman943b0832005-07-15 21:39:18 -0400624
Jean Delvare51f2cca2007-05-08 17:22:00 +0200625 res = platform_get_resource(pdev, IORESOURCE_IO, 0);
626 release_region(res->start, SMSC_EXTENT);
Mark M. Hoffman943b0832005-07-15 21:39:18 -0400627 kfree(data);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700628
629 return 0;
630}
631
Linus Torvalds1da177e2005-04-16 15:20:36 -0700632static struct smsc47m1_data *smsc47m1_update_device(struct device *dev,
633 int init)
634{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200635 struct smsc47m1_data *data = dev_get_drvdata(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700636
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100637 mutex_lock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700638
639 if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || init) {
Jean Delvare8eccbb62007-05-08 17:21:59 +0200640 int i, fan_nr;
641 fan_nr = data->type == smsc47m2 ? 3 : 2;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700642
Jean Delvare8eccbb62007-05-08 17:21:59 +0200643 for (i = 0; i < fan_nr; i++) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200644 data->fan[i] = smsc47m1_read_value(data,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200645 SMSC47M1_REG_FAN[i]);
Jean Delvare51f2cca2007-05-08 17:22:00 +0200646 data->fan_preload[i] = smsc47m1_read_value(data,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200647 SMSC47M1_REG_FAN_PRELOAD[i]);
Jean Delvare51f2cca2007-05-08 17:22:00 +0200648 data->pwm[i] = smsc47m1_read_value(data,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200649 SMSC47M1_REG_PWM[i]);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700650 }
651
Jean Delvare51f2cca2007-05-08 17:22:00 +0200652 i = smsc47m1_read_value(data, SMSC47M1_REG_FANDIV);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700653 data->fan_div[0] = (i >> 4) & 0x03;
654 data->fan_div[1] = i >> 6;
655
Jean Delvare51f2cca2007-05-08 17:22:00 +0200656 data->alarms = smsc47m1_read_value(data,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700657 SMSC47M1_REG_ALARM) >> 6;
658 /* Clear alarms if needed */
659 if (data->alarms)
Jean Delvare51f2cca2007-05-08 17:22:00 +0200660 smsc47m1_write_value(data, SMSC47M1_REG_ALARM, 0xC0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700661
Jean Delvare8eccbb62007-05-08 17:21:59 +0200662 if (fan_nr >= 3) {
Jean Delvare51f2cca2007-05-08 17:22:00 +0200663 data->fan_div[2] = (smsc47m1_read_value(data,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200664 SMSC47M2_REG_FANDIV3) >> 4) & 0x03;
Jean Delvare51f2cca2007-05-08 17:22:00 +0200665 data->alarms |= (smsc47m1_read_value(data,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200666 SMSC47M2_REG_ALARM6) & 0x40) >> 4;
667 /* Clear alarm if needed */
668 if (data->alarms & 0x04)
Jean Delvare51f2cca2007-05-08 17:22:00 +0200669 smsc47m1_write_value(data,
Jean Delvare8eccbb62007-05-08 17:21:59 +0200670 SMSC47M2_REG_ALARM6,
671 0x40);
672 }
673
Linus Torvalds1da177e2005-04-16 15:20:36 -0700674 data->last_updated = jiffies;
675 }
676
Ingo Molnar9a61bf62006-01-18 23:19:26 +0100677 mutex_unlock(&data->update_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700678 return data;
679}
680
Jean Delvare51f2cca2007-05-08 17:22:00 +0200681static int __init smsc47m1_device_add(unsigned short address,
682 const struct smsc47m1_sio_data *sio_data)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700683{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200684 struct resource res = {
685 .start = address,
686 .end = address + SMSC_EXTENT - 1,
687 .name = DRVNAME,
688 .flags = IORESOURCE_IO,
689 };
690 int err;
691
692 pdev = platform_device_alloc(DRVNAME, address);
693 if (!pdev) {
694 err = -ENOMEM;
695 printk(KERN_ERR DRVNAME ": Device allocation failed\n");
696 goto exit;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700697 }
698
Jean Delvare51f2cca2007-05-08 17:22:00 +0200699 err = platform_device_add_resources(pdev, &res, 1);
700 if (err) {
701 printk(KERN_ERR DRVNAME ": Device resource addition failed "
702 "(%d)\n", err);
703 goto exit_device_put;
704 }
705
706 pdev->dev.platform_data = kmalloc(sizeof(struct smsc47m1_sio_data),
707 GFP_KERNEL);
708 if (!pdev->dev.platform_data) {
709 err = -ENOMEM;
710 printk(KERN_ERR DRVNAME ": Platform data allocation failed\n");
711 goto exit_device_put;
712 }
713 memcpy(pdev->dev.platform_data, sio_data,
714 sizeof(struct smsc47m1_sio_data));
715
716 err = platform_device_add(pdev);
717 if (err) {
718 printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
719 err);
720 goto exit_device_put;
721 }
722
723 return 0;
724
725exit_device_put:
726 platform_device_put(pdev);
727exit:
728 return err;
729}
730
731static int __init sm_smsc47m1_init(void)
732{
733 int err;
734 unsigned short address;
735 struct smsc47m1_sio_data sio_data;
736
737 if (smsc47m1_find(&address, &sio_data))
738 return -ENODEV;
739
740 err = platform_driver_register(&smsc47m1_driver);
741 if (err)
742 goto exit;
743
744 /* Sets global pdev as a side effect */
745 err = smsc47m1_device_add(address, &sio_data);
746 if (err)
747 goto exit_driver;
748
749 return 0;
750
751exit_driver:
752 platform_driver_unregister(&smsc47m1_driver);
753exit:
754 return err;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700755}
756
757static void __exit sm_smsc47m1_exit(void)
758{
Jean Delvare51f2cca2007-05-08 17:22:00 +0200759 platform_device_unregister(pdev);
760 platform_driver_unregister(&smsc47m1_driver);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700761}
762
763MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>");
764MODULE_DESCRIPTION("SMSC LPC47M1xx fan sensors driver");
765MODULE_LICENSE("GPL");
766
767module_init(sm_smsc47m1_init);
768module_exit(sm_smsc47m1_exit);