smsc47m1.c 24.7 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4
/*
    smsc47m1.c - Part of lm_sensors, Linux kernel modules
                 for hardware monitoring

5
    Supports the SMSC LPC47B27x, LPC47M10x, LPC47M112, LPC47M13x,
6 7
    LPC47M14x, LPC47M15x, LPC47M192, LPC47M292 and LPC47M997
    Super-I/O chips.
L
Linus Torvalds 已提交
8 9

    Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
10
    Copyright (C) 2004-2007 Jean Delvare <khali@linux-fr.org>
L
Linus Torvalds 已提交
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
    Ported to Linux 2.6 by Gabriele Gorla <gorlik@yahoo.com>
                        and Jean Delvare

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

29 30
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

L
Linus Torvalds 已提交
31 32 33 34
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/jiffies.h>
35
#include <linux/platform_device.h>
36
#include <linux/hwmon.h>
37
#include <linux/hwmon-sysfs.h>
38
#include <linux/err.h>
L
Linus Torvalds 已提交
39
#include <linux/init.h>
40
#include <linux/mutex.h>
41
#include <linux/sysfs.h>
42
#include <linux/acpi.h>
43
#include <linux/io.h>
L
Linus Torvalds 已提交
44

45 46 47 48
static unsigned short force_id;
module_param(force_id, ushort, 0);
MODULE_PARM_DESC(force_id, "Override the detected device ID");

49 50 51
static struct platform_device *pdev;

#define DRVNAME "smsc47m1"
52
enum chips { smsc47m1, smsc47m2 };
L
Linus Torvalds 已提交
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

/* Super-I/0 registers and commands */

#define	REG	0x2e	/* The register to read/write */
#define	VAL	0x2f	/* The value to read/write */

static inline void
superio_outb(int reg, int val)
{
	outb(reg, REG);
	outb(val, VAL);
}

static inline int
superio_inb(int reg)
{
	outb(reg, REG);
	return inb(VAL);
}

/* logical device for fans is 0x0A */
#define superio_select() superio_outb(0x07, 0x0A)

static inline void
superio_enter(void)
{
	outb(0x55, REG);
}

static inline void
superio_exit(void)
{
	outb(0xAA, REG);
}

#define SUPERIO_REG_ACT		0x30
#define SUPERIO_REG_BASE	0x60
#define SUPERIO_REG_DEVID	0x20
91
#define SUPERIO_REG_DEVREV	0x21
L
Linus Torvalds 已提交
92 93 94 95 96 97 98 99 100 101

/* Logical device registers */

#define SMSC_EXTENT		0x80

/* nr is 0 or 1 in the macros below */
#define SMSC47M1_REG_ALARM		0x04
#define SMSC47M1_REG_TPIN(nr)		(0x34 - (nr))
#define SMSC47M1_REG_PPIN(nr)		(0x36 - (nr))
#define SMSC47M1_REG_FANDIV		0x58
102 103 104 105 106 107 108 109 110 111 112

static const u8 SMSC47M1_REG_FAN[3]		= { 0x59, 0x5a, 0x6b };
static const u8 SMSC47M1_REG_FAN_PRELOAD[3]	= { 0x5b, 0x5c, 0x6c };
static const u8 SMSC47M1_REG_PWM[3]		= { 0x56, 0x57, 0x69 };

#define SMSC47M2_REG_ALARM6		0x09
#define SMSC47M2_REG_TPIN1		0x38
#define SMSC47M2_REG_TPIN2		0x37
#define SMSC47M2_REG_TPIN3		0x2d
#define SMSC47M2_REG_PPIN3		0x2c
#define SMSC47M2_REG_FANDIV3		0x6a
L
Linus Torvalds 已提交
113 114 115 116 117 118 119 120 121 122 123

#define MIN_FROM_REG(reg,div)		((reg)>=192 ? 0 : \
					 983040/((192-(reg))*(div)))
#define FAN_FROM_REG(reg,div,preload)	((reg)<=(preload) || (reg)==255 ? 0 : \
					 983040/(((reg)-(preload))*(div)))
#define DIV_FROM_REG(reg)		(1 << (reg))
#define PWM_FROM_REG(reg)		(((reg) & 0x7E) << 1)
#define PWM_EN_FROM_REG(reg)		((~(reg)) & 0x01)
#define PWM_TO_REG(reg)			(((reg) >> 1) & 0x7E)

struct smsc47m1_data {
124 125
	unsigned short addr;
	const char *name;
126
	enum chips type;
127
	struct device *hwmon_dev;
L
Linus Torvalds 已提交
128

129
	struct mutex update_lock;
L
Linus Torvalds 已提交
130 131
	unsigned long last_updated;	/* In jiffies */

132 133 134
	u8 fan[3];		/* Register value */
	u8 fan_preload[3];	/* Register value */
	u8 fan_div[3];		/* Register encoding, shifted right */
L
Linus Torvalds 已提交
135
	u8 alarms;		/* Register encoding */
136
	u8 pwm[3];		/* Register value (bit 0 is disable) */
L
Linus Torvalds 已提交
137 138
};

139 140
struct smsc47m1_sio_data {
	enum chips type;
141
	u8 activate;		/* Remember initial device state */
142
};
L
Linus Torvalds 已提交
143

144

145
static int __exit smsc47m1_remove(struct platform_device *pdev);
L
Linus Torvalds 已提交
146 147 148
static struct smsc47m1_data *smsc47m1_update_device(struct device *dev,
		int init);

149
static inline int smsc47m1_read_value(struct smsc47m1_data *data, u8 reg)
150
{
151
	return inb_p(data->addr + reg);
152 153
}

154
static inline void smsc47m1_write_value(struct smsc47m1_data *data, u8 reg,
155 156
		u8 value)
{
157
	outb_p(value, data->addr + reg);
158
}
L
Linus Torvalds 已提交
159

160
static struct platform_driver smsc47m1_driver = {
161
	.driver = {
J
Jean Delvare 已提交
162
		.owner	= THIS_MODULE,
163
		.name	= DRVNAME,
164
	},
165
	.remove		= __exit_p(smsc47m1_remove),
L
Linus Torvalds 已提交
166 167
};

168 169
static ssize_t get_fan(struct device *dev, struct device_attribute
		       *devattr, char *buf)
L
Linus Torvalds 已提交
170
{
171
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
L
Linus Torvalds 已提交
172
	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
173
	int nr = attr->index;
L
Linus Torvalds 已提交
174 175 176 177 178 179 180 181 182 183 184
	/* This chip (stupidly) stops monitoring fan speed if PWM is
	   enabled and duty cycle is 0%. This is fine if the monitoring
	   and control concern the same fan, but troublesome if they are
	   not (which could as well happen). */
	int rpm = (data->pwm[nr] & 0x7F) == 0x00 ? 0 :
		  FAN_FROM_REG(data->fan[nr],
			       DIV_FROM_REG(data->fan_div[nr]),
			       data->fan_preload[nr]);
	return sprintf(buf, "%d\n", rpm);
}

185 186
static ssize_t get_fan_min(struct device *dev, struct device_attribute
			   *devattr, char *buf)
L
Linus Torvalds 已提交
187
{
188
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
L
Linus Torvalds 已提交
189
	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
190
	int nr = attr->index;
L
Linus Torvalds 已提交
191 192 193 194 195
	int rpm = MIN_FROM_REG(data->fan_preload[nr],
			       DIV_FROM_REG(data->fan_div[nr]));
	return sprintf(buf, "%d\n", rpm);
}

196 197
static ssize_t get_fan_div(struct device *dev, struct device_attribute
			   *devattr, char *buf)
L
Linus Torvalds 已提交
198
{
199
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
L
Linus Torvalds 已提交
200
	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
201
	return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[attr->index]));
L
Linus Torvalds 已提交
202 203
}

204 205 206 207 208 209 210 211
static ssize_t get_fan_alarm(struct device *dev, struct device_attribute
			     *devattr, char *buf)
{
	int bitnr = to_sensor_dev_attr(devattr)->index;
	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
	return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1);
}

212 213
static ssize_t get_pwm(struct device *dev, struct device_attribute
		       *devattr, char *buf)
L
Linus Torvalds 已提交
214
{
215
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
L
Linus Torvalds 已提交
216
	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
217
	return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[attr->index]));
L
Linus Torvalds 已提交
218 219
}

220 221
static ssize_t get_pwm_en(struct device *dev, struct device_attribute
			  *devattr, char *buf)
L
Linus Torvalds 已提交
222
{
223
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
L
Linus Torvalds 已提交
224
	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
225
	return sprintf(buf, "%d\n", PWM_EN_FROM_REG(data->pwm[attr->index]));
L
Linus Torvalds 已提交
226 227
}

228 229
static ssize_t get_alarms(struct device *dev, struct device_attribute
			  *devattr, char *buf)
L
Linus Torvalds 已提交
230 231 232 233 234
{
	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
	return sprintf(buf, "%d\n", data->alarms);
}

235 236
static ssize_t set_fan_min(struct device *dev, struct device_attribute
			   *devattr, const char *buf, size_t count)
L
Linus Torvalds 已提交
237
{
238
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
239
	struct smsc47m1_data *data = dev_get_drvdata(dev);
240
	int nr = attr->index;
L
Linus Torvalds 已提交
241 242
	long rpmdiv, val = simple_strtol(buf, NULL, 10);

243
	mutex_lock(&data->update_lock);
L
Linus Torvalds 已提交
244 245 246
	rpmdiv = val * DIV_FROM_REG(data->fan_div[nr]);

	if (983040 > 192 * rpmdiv || 2 * rpmdiv > 983040) {
247
		mutex_unlock(&data->update_lock);
L
Linus Torvalds 已提交
248 249 250 251
		return -EINVAL;
	}

	data->fan_preload[nr] = 192 - ((983040 + rpmdiv / 2) / rpmdiv);
252
	smsc47m1_write_value(data, SMSC47M1_REG_FAN_PRELOAD[nr],
L
Linus Torvalds 已提交
253
			     data->fan_preload[nr]);
254
	mutex_unlock(&data->update_lock);
L
Linus Torvalds 已提交
255 256 257 258 259 260

	return count;
}

/* Note: we save and restore the fan minimum here, because its value is
   determined in part by the fan clock divider.  This follows the principle
A
Andreas Mohr 已提交
261
   of least surprise; the user doesn't expect the fan minimum to change just
L
Linus Torvalds 已提交
262
   because the divider changed. */
263 264
static ssize_t set_fan_div(struct device *dev, struct device_attribute
			   *devattr, const char *buf, size_t count)
L
Linus Torvalds 已提交
265
{
266
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
267
	struct smsc47m1_data *data = dev_get_drvdata(dev);
268
	int nr = attr->index;
L
Linus Torvalds 已提交
269 270 271 272 273 274
	long new_div = simple_strtol(buf, NULL, 10), tmp;
	u8 old_div = DIV_FROM_REG(data->fan_div[nr]);

	if (new_div == old_div) /* No change */
		return count;

275
	mutex_lock(&data->update_lock);
L
Linus Torvalds 已提交
276 277 278 279 280 281
	switch (new_div) {
	case 1: data->fan_div[nr] = 0; break;
	case 2: data->fan_div[nr] = 1; break;
	case 4: data->fan_div[nr] = 2; break;
	case 8: data->fan_div[nr] = 3; break;
	default:
282
		mutex_unlock(&data->update_lock);
L
Linus Torvalds 已提交
283 284 285
		return -EINVAL;
	}

286 287 288
	switch (nr) {
	case 0:
	case 1:
289
		tmp = smsc47m1_read_value(data, SMSC47M1_REG_FANDIV)
290 291
		      & ~(0x03 << (4 + 2 * nr));
		tmp |= data->fan_div[nr] << (4 + 2 * nr);
292
		smsc47m1_write_value(data, SMSC47M1_REG_FANDIV, tmp);
293 294
		break;
	case 2:
295
		tmp = smsc47m1_read_value(data, SMSC47M2_REG_FANDIV3) & 0xCF;
296
		tmp |= data->fan_div[2] << 4;
297
		smsc47m1_write_value(data, SMSC47M2_REG_FANDIV3, tmp);
298 299
		break;
	}
L
Linus Torvalds 已提交
300 301 302 303 304

	/* Preserve fan min */
	tmp = 192 - (old_div * (192 - data->fan_preload[nr])
		     + new_div / 2) / new_div;
	data->fan_preload[nr] = SENSORS_LIMIT(tmp, 0, 191);
305
	smsc47m1_write_value(data, SMSC47M1_REG_FAN_PRELOAD[nr],
L
Linus Torvalds 已提交
306
			     data->fan_preload[nr]);
307
	mutex_unlock(&data->update_lock);
L
Linus Torvalds 已提交
308 309 310 311

	return count;
}

312 313
static ssize_t set_pwm(struct device *dev, struct device_attribute
		       *devattr, const char *buf, size_t count)
L
Linus Torvalds 已提交
314
{
315
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
316
	struct smsc47m1_data *data = dev_get_drvdata(dev);
317
	int nr = attr->index;
L
Linus Torvalds 已提交
318 319 320 321 322
	long val = simple_strtol(buf, NULL, 10);

	if (val < 0 || val > 255)
		return -EINVAL;

323
	mutex_lock(&data->update_lock);
L
Linus Torvalds 已提交
324 325
	data->pwm[nr] &= 0x81; /* Preserve additional bits */
	data->pwm[nr] |= PWM_TO_REG(val);
326
	smsc47m1_write_value(data, SMSC47M1_REG_PWM[nr],
L
Linus Torvalds 已提交
327
			     data->pwm[nr]);
328
	mutex_unlock(&data->update_lock);
L
Linus Torvalds 已提交
329 330 331 332

	return count;
}

333 334
static ssize_t set_pwm_en(struct device *dev, struct device_attribute
			  *devattr, const char *buf, size_t count)
L
Linus Torvalds 已提交
335
{
336
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
337
	struct smsc47m1_data *data = dev_get_drvdata(dev);
338
	int nr = attr->index;
L
Linus Torvalds 已提交
339 340 341 342 343
	long val = simple_strtol(buf, NULL, 10);
	
	if (val != 0 && val != 1)
		return -EINVAL;

344
	mutex_lock(&data->update_lock);
L
Linus Torvalds 已提交
345 346
	data->pwm[nr] &= 0xFE; /* preserve the other bits */
	data->pwm[nr] |= !val;
347
	smsc47m1_write_value(data, SMSC47M1_REG_PWM[nr],
L
Linus Torvalds 已提交
348
			     data->pwm[nr]);
349
	mutex_unlock(&data->update_lock);
L
Linus Torvalds 已提交
350 351 352 353 354

	return count;
}

#define fan_present(offset)						\
355 356 357 358 359 360
static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, get_fan,	\
		NULL, offset - 1);					\
static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR,		\
		get_fan_min, set_fan_min, offset - 1);			\
static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR,		\
		get_fan_div, set_fan_div, offset - 1);			\
361 362
static SENSOR_DEVICE_ATTR(fan##offset##_alarm, S_IRUGO, get_fan_alarm,	\
		NULL, offset - 1);					\
363 364 365 366
static SENSOR_DEVICE_ATTR(pwm##offset, S_IRUGO | S_IWUSR,		\
		get_pwm, set_pwm, offset - 1);				\
static SENSOR_DEVICE_ATTR(pwm##offset##_enable, S_IRUGO | S_IWUSR,	\
		get_pwm_en, set_pwm_en, offset - 1)
L
Linus Torvalds 已提交
367 368 369

fan_present(1);
fan_present(2);
370
fan_present(3);
L
Linus Torvalds 已提交
371 372 373

static DEVICE_ATTR(alarms, S_IRUGO, get_alarms, NULL);

374 375 376 377 378 379 380 381 382
static ssize_t show_name(struct device *dev, struct device_attribute
			 *devattr, char *buf)
{
	struct smsc47m1_data *data = dev_get_drvdata(dev);

	return sprintf(buf, "%s\n", data->name);
}
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);

383 384 385 386
/* Almost all sysfs files may or may not be created depending on the chip
   setup so we create them individually. It is still convenient to define a
   group to remove them all at once. */
static struct attribute *smsc47m1_attributes[] = {
387 388 389
	&sensor_dev_attr_fan1_input.dev_attr.attr,
	&sensor_dev_attr_fan1_min.dev_attr.attr,
	&sensor_dev_attr_fan1_div.dev_attr.attr,
390
	&sensor_dev_attr_fan1_alarm.dev_attr.attr,
391 392 393
	&sensor_dev_attr_fan2_input.dev_attr.attr,
	&sensor_dev_attr_fan2_min.dev_attr.attr,
	&sensor_dev_attr_fan2_div.dev_attr.attr,
394
	&sensor_dev_attr_fan2_alarm.dev_attr.attr,
395 396 397
	&sensor_dev_attr_fan3_input.dev_attr.attr,
	&sensor_dev_attr_fan3_min.dev_attr.attr,
	&sensor_dev_attr_fan3_div.dev_attr.attr,
398
	&sensor_dev_attr_fan3_alarm.dev_attr.attr,
399 400 401 402 403 404 405

	&sensor_dev_attr_pwm1.dev_attr.attr,
	&sensor_dev_attr_pwm1_enable.dev_attr.attr,
	&sensor_dev_attr_pwm2.dev_attr.attr,
	&sensor_dev_attr_pwm2_enable.dev_attr.attr,
	&sensor_dev_attr_pwm3.dev_attr.attr,
	&sensor_dev_attr_pwm3_enable.dev_attr.attr,
406 407

	&dev_attr_alarms.attr,
408
	&dev_attr_name.attr,
409 410 411 412 413 414 415
	NULL
};

static const struct attribute_group smsc47m1_group = {
	.attrs = smsc47m1_attributes,
};

416 417
static int __init smsc47m1_find(unsigned short *addr,
				struct smsc47m1_sio_data *sio_data)
L
Linus Torvalds 已提交
418 419 420 421
{
	u8 val;

	superio_enter();
422
	val = force_id ? force_id : superio_inb(SUPERIO_REG_DEVID);
L
Linus Torvalds 已提交
423 424

	/*
425 426
	 * SMSC LPC47M10x/LPC47M112/LPC47M13x (device id 0x59), LPC47M14x
	 * (device id 0x5F) and LPC47B27x (device id 0x51) have fan control.
L
Linus Torvalds 已提交
427
	 * The LPC47M15x and LPC47M192 chips "with hardware monitoring block"
428
	 * can do much more besides (device id 0x60).
429 430
	 * The LPC47M997 is undocumented, but seems to be compatible with
	 * the LPC47M192, and has the same device id.
431 432 433
	 * The LPC47M292 (device id 0x6B) is somewhat compatible, but it
	 * supports a 3rd fan, and the pin configuration registers are
	 * unfortunately different.
434 435 436
	 * The LPC47M233 has the same device id (0x6B) but is not compatible.
	 * We check the high bit of the device revision register to
	 * differentiate them.
L
Linus Torvalds 已提交
437
	 */
438
	switch (val) {
439
	case 0x51:
440
		pr_info("Found SMSC LPC47B27x\n");
441
		sio_data->type = smsc47m1;
442 443
		break;
	case 0x59:
444
		pr_info("Found SMSC LPC47M10x/LPC47M112/LPC47M13x\n");
445
		sio_data->type = smsc47m1;
446 447
		break;
	case 0x5F:
448
		pr_info("Found SMSC LPC47M14x\n");
449
		sio_data->type = smsc47m1;
450 451
		break;
	case 0x60:
452
		pr_info("Found SMSC LPC47M15x/LPC47M192/LPC47M997\n");
453
		sio_data->type = smsc47m1;
454 455
		break;
	case 0x6B:
456
		if (superio_inb(SUPERIO_REG_DEVREV) & 0x80) {
457
			pr_debug("Found SMSC LPC47M233, unsupported\n");
458 459 460 461
			superio_exit();
			return -ENODEV;
		}

462
		pr_info("Found SMSC LPC47M292\n");
463
		sio_data->type = smsc47m2;
464 465
		break;
	default:
L
Linus Torvalds 已提交
466 467 468 469 470
		superio_exit();
		return -ENODEV;
	}

	superio_select();
471 472
	*addr = (superio_inb(SUPERIO_REG_BASE) << 8)
	      |  superio_inb(SUPERIO_REG_BASE + 1);
473
	if (*addr == 0) {
474
		pr_info("Device address not set, will not use\n");
L
Linus Torvalds 已提交
475 476 477 478
		superio_exit();
		return -ENODEV;
	}

479 480 481 482
	/* Enable only if address is set (needed at least on the
	 * Compaq Presario S4000NX) */
	sio_data->activate = superio_inb(SUPERIO_REG_ACT);
	if ((sio_data->activate & 0x01) == 0) {
483
		pr_info("Enabling device\n");
484 485 486
		superio_outb(SUPERIO_REG_ACT, sio_data->activate | 0x01);
	}

L
Linus Torvalds 已提交
487 488 489 490
	superio_exit();
	return 0;
}

491
/* Restore device to its initial state */
492
static void smsc47m1_restore(const struct smsc47m1_sio_data *sio_data)
493 494 495 496 497
{
	if ((sio_data->activate & 0x01) == 0) {
		superio_enter();
		superio_select();

498
		pr_info("Disabling device\n");
499 500 501 502 503 504
		superio_outb(SUPERIO_REG_ACT, sio_data->activate);

		superio_exit();
	}
}

505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
#define CHECK		1
#define REQUEST		2
#define RELEASE		3

/*
 * This function can be used to:
 *  - test for resource conflicts with ACPI
 *  - request the resources
 *  - release the resources
 * We only allocate the I/O ports we really need, to minimize the risk of
 * conflicts with ACPI or with other drivers.
 */
static int smsc47m1_handle_resources(unsigned short address, enum chips type,
				     int action, struct device *dev)
{
	static const u8 ports_m1[] = {
		/* register, region length */
		0x04, 1,
		0x33, 4,
		0x56, 7,
	};

	static const u8 ports_m2[] = {
		/* register, region length */
		0x04, 1,
		0x09, 1,
		0x2c, 2,
		0x35, 4,
		0x56, 7,
		0x69, 4,
	};

	int i, ports_size, err;
	const u8 *ports;

	switch (type) {
	case smsc47m1:
	default:
		ports = ports_m1;
		ports_size = ARRAY_SIZE(ports_m1);
		break;
	case smsc47m2:
		ports = ports_m2;
		ports_size = ARRAY_SIZE(ports_m2);
		break;
	}

	for (i = 0; i + 1 < ports_size; i += 2) {
		unsigned short start = address + ports[i];
		unsigned short len = ports[i + 1];

		switch (action) {
		case CHECK:
			/* Only check for conflicts */
			err = acpi_check_region(start, len, DRVNAME);
			if (err)
				return err;
			break;
		case REQUEST:
			/* Request the resources */
			if (!request_region(start, len, DRVNAME)) {
				dev_err(dev, "Region 0x%hx-0x%hx already in "
					"use!\n", start, start + len);

				/* Undo all requests */
				for (i -= 2; i >= 0; i -= 2)
					release_region(address + ports[i],
						       ports[i + 1]);
				return -EBUSY;
			}
			break;
		case RELEASE:
			/* Release the resources */
			release_region(start, len);
			break;
		}
	}

	return 0;
}

586
static int __init smsc47m1_probe(struct platform_device *pdev)
L
Linus Torvalds 已提交
587
{
588 589
	struct device *dev = &pdev->dev;
	struct smsc47m1_sio_data *sio_data = dev->platform_data;
L
Linus Torvalds 已提交
590
	struct smsc47m1_data *data;
591
	struct resource *res;
592
	int err;
593
	int fan1, fan2, fan3, pwm1, pwm2, pwm3;
L
Linus Torvalds 已提交
594

595 596 597 598 599 600
	static const char *names[] = {
		"smsc47m1",
		"smsc47m2",
	};

	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
601 602 603 604
	err = smsc47m1_handle_resources(res->start, sio_data->type,
					REQUEST, dev);
	if (err < 0)
		return err;
L
Linus Torvalds 已提交
605

D
Deepak Saxena 已提交
606
	if (!(data = kzalloc(sizeof(struct smsc47m1_data), GFP_KERNEL))) {
L
Linus Torvalds 已提交
607 608 609 610
		err = -ENOMEM;
		goto error_release;
	}

611 612 613
	data->addr = res->start;
	data->type = sio_data->type;
	data->name = names[sio_data->type];
614
	mutex_init(&data->update_lock);
615
	platform_set_drvdata(pdev, data);
L
Linus Torvalds 已提交
616 617 618

	/* If no function is properly configured, there's no point in
	   actually registering the chip. */
619
	pwm1 = (smsc47m1_read_value(data, SMSC47M1_REG_PPIN(0)) & 0x05)
L
Linus Torvalds 已提交
620
	       == 0x04;
621
	pwm2 = (smsc47m1_read_value(data, SMSC47M1_REG_PPIN(1)) & 0x05)
L
Linus Torvalds 已提交
622
	       == 0x04;
623
	if (data->type == smsc47m2) {
624
		fan1 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN1)
625
			& 0x0d) == 0x09;
626
		fan2 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN2)
627
			& 0x0d) == 0x09;
628
		fan3 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN3)
629
			& 0x0d) == 0x0d;
630
		pwm3 = (smsc47m1_read_value(data, SMSC47M2_REG_PPIN3)
631 632
			& 0x0d) == 0x08;
	} else {
633
		fan1 = (smsc47m1_read_value(data, SMSC47M1_REG_TPIN(0))
634
			& 0x05) == 0x05;
635
		fan2 = (smsc47m1_read_value(data, SMSC47M1_REG_TPIN(1))
636 637 638 639 640
			& 0x05) == 0x05;
		fan3 = 0;
		pwm3 = 0;
	}
	if (!(fan1 || fan2 || fan3 || pwm1 || pwm2 || pwm3)) {
641
		dev_warn(dev, "Device not configured, will not use\n");
L
Linus Torvalds 已提交
642 643 644 645 646 647 648 649 650 651
		err = -ENODEV;
		goto error_free;
	}

	/* Some values (fan min, clock dividers, pwm registers) may be
	   needed before any update is triggered, so we better read them
	   at least once here. We don't usually do it that way, but in
	   this particular case, manually reading 5 registers out of 8
	   doesn't make much sense and we're better using the existing
	   function. */
652
	smsc47m1_update_device(dev, 1);
L
Linus Torvalds 已提交
653

654
	/* Register sysfs hooks */
L
Linus Torvalds 已提交
655
	if (fan1) {
656 657 658 659 660
		if ((err = device_create_file(dev,
				&sensor_dev_attr_fan1_input.dev_attr))
		 || (err = device_create_file(dev,
				&sensor_dev_attr_fan1_min.dev_attr))
		 || (err = device_create_file(dev,
661 662 663
				&sensor_dev_attr_fan1_div.dev_attr))
		 || (err = device_create_file(dev,
				&sensor_dev_attr_fan1_alarm.dev_attr)))
664
			goto error_remove_files;
L
Linus Torvalds 已提交
665
	} else
666
		dev_dbg(dev, "Fan 1 not enabled by hardware, skipping\n");
L
Linus Torvalds 已提交
667 668

	if (fan2) {
669 670 671 672 673
		if ((err = device_create_file(dev,
				&sensor_dev_attr_fan2_input.dev_attr))
		 || (err = device_create_file(dev,
				&sensor_dev_attr_fan2_min.dev_attr))
		 || (err = device_create_file(dev,
674 675 676
				&sensor_dev_attr_fan2_div.dev_attr))
		 || (err = device_create_file(dev,
				&sensor_dev_attr_fan2_alarm.dev_attr)))
677
			goto error_remove_files;
L
Linus Torvalds 已提交
678
	} else
679
		dev_dbg(dev, "Fan 2 not enabled by hardware, skipping\n");
L
Linus Torvalds 已提交
680

681
	if (fan3) {
682 683 684 685 686
		if ((err = device_create_file(dev,
				&sensor_dev_attr_fan3_input.dev_attr))
		 || (err = device_create_file(dev,
				&sensor_dev_attr_fan3_min.dev_attr))
		 || (err = device_create_file(dev,
687 688 689
				&sensor_dev_attr_fan3_div.dev_attr))
		 || (err = device_create_file(dev,
				&sensor_dev_attr_fan3_alarm.dev_attr)))
690
			goto error_remove_files;
691
	} else if (data->type == smsc47m2)
692
		dev_dbg(dev, "Fan 3 not enabled by hardware, skipping\n");
693

L
Linus Torvalds 已提交
694
	if (pwm1) {
695 696 697 698
		if ((err = device_create_file(dev,
				&sensor_dev_attr_pwm1.dev_attr))
		 || (err = device_create_file(dev,
				&sensor_dev_attr_pwm1_enable.dev_attr)))
699
			goto error_remove_files;
L
Linus Torvalds 已提交
700
	} else
701
		dev_dbg(dev, "PWM 1 not enabled by hardware, skipping\n");
702

L
Linus Torvalds 已提交
703
	if (pwm2) {
704 705 706 707
		if ((err = device_create_file(dev,
				&sensor_dev_attr_pwm2.dev_attr))
		 || (err = device_create_file(dev,
				&sensor_dev_attr_pwm2_enable.dev_attr)))
708
			goto error_remove_files;
L
Linus Torvalds 已提交
709
	} else
710
		dev_dbg(dev, "PWM 2 not enabled by hardware, skipping\n");
L
Linus Torvalds 已提交
711

712
	if (pwm3) {
713 714 715 716
		if ((err = device_create_file(dev,
				&sensor_dev_attr_pwm3.dev_attr))
		 || (err = device_create_file(dev,
				&sensor_dev_attr_pwm3_enable.dev_attr)))
717
			goto error_remove_files;
718
	} else if (data->type == smsc47m2)
719
		dev_dbg(dev, "PWM 3 not enabled by hardware, skipping\n");
720

721
	if ((err = device_create_file(dev, &dev_attr_alarms)))
722
		goto error_remove_files;
723 724
	if ((err = device_create_file(dev, &dev_attr_name)))
		goto error_remove_files;
725

726 727 728
	data->hwmon_dev = hwmon_device_register(dev);
	if (IS_ERR(data->hwmon_dev)) {
		err = PTR_ERR(data->hwmon_dev);
729 730
		goto error_remove_files;
	}
L
Linus Torvalds 已提交
731 732 733

	return 0;

734
error_remove_files:
735
	sysfs_remove_group(&dev->kobj, &smsc47m1_group);
L
Linus Torvalds 已提交
736
error_free:
737
	platform_set_drvdata(pdev, NULL);
738
	kfree(data);
L
Linus Torvalds 已提交
739
error_release:
740
	smsc47m1_handle_resources(res->start, sio_data->type, RELEASE, dev);
L
Linus Torvalds 已提交
741 742 743
	return err;
}

744
static int __exit smsc47m1_remove(struct platform_device *pdev)
L
Linus Torvalds 已提交
745
{
746 747
	struct smsc47m1_data *data = platform_get_drvdata(pdev);
	struct resource *res;
L
Linus Torvalds 已提交
748

749
	hwmon_device_unregister(data->hwmon_dev);
750
	sysfs_remove_group(&pdev->dev.kobj, &smsc47m1_group);
L
Linus Torvalds 已提交
751

752
	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
753
	smsc47m1_handle_resources(res->start, data->type, RELEASE, &pdev->dev);
754
	platform_set_drvdata(pdev, NULL);
755
	kfree(data);
L
Linus Torvalds 已提交
756 757 758 759 760 761 762

	return 0;
}

static struct smsc47m1_data *smsc47m1_update_device(struct device *dev,
		int init)
{
763
	struct smsc47m1_data *data = dev_get_drvdata(dev);
L
Linus Torvalds 已提交
764

765
	mutex_lock(&data->update_lock);
L
Linus Torvalds 已提交
766 767

	if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || init) {
768 769
		int i, fan_nr;
		fan_nr = data->type == smsc47m2 ? 3 : 2;
L
Linus Torvalds 已提交
770

771
		for (i = 0; i < fan_nr; i++) {
772
			data->fan[i] = smsc47m1_read_value(data,
773
				       SMSC47M1_REG_FAN[i]);
774
			data->fan_preload[i] = smsc47m1_read_value(data,
775
					       SMSC47M1_REG_FAN_PRELOAD[i]);
776
			data->pwm[i] = smsc47m1_read_value(data,
777
				       SMSC47M1_REG_PWM[i]);
L
Linus Torvalds 已提交
778 779
		}

780
		i = smsc47m1_read_value(data, SMSC47M1_REG_FANDIV);
L
Linus Torvalds 已提交
781 782 783
		data->fan_div[0] = (i >> 4) & 0x03;
		data->fan_div[1] = i >> 6;

784
		data->alarms = smsc47m1_read_value(data,
L
Linus Torvalds 已提交
785 786 787
			       SMSC47M1_REG_ALARM) >> 6;
		/* Clear alarms if needed */
		if (data->alarms)
788
			smsc47m1_write_value(data, SMSC47M1_REG_ALARM, 0xC0);
L
Linus Torvalds 已提交
789

790
		if (fan_nr >= 3) {
791
			data->fan_div[2] = (smsc47m1_read_value(data,
792
					    SMSC47M2_REG_FANDIV3) >> 4) & 0x03;
793
			data->alarms |= (smsc47m1_read_value(data,
794 795 796
					 SMSC47M2_REG_ALARM6) & 0x40) >> 4;
			/* Clear alarm if needed */
			if (data->alarms & 0x04)
797
				smsc47m1_write_value(data,
798 799 800 801
						     SMSC47M2_REG_ALARM6,
						     0x40);
		}

L
Linus Torvalds 已提交
802 803 804
		data->last_updated = jiffies;
	}

805
	mutex_unlock(&data->update_lock);
L
Linus Torvalds 已提交
806 807 808
	return data;
}

809 810 811 812 813 814 815 816 817 818 819
static int __init smsc47m1_device_add(unsigned short address,
				      const struct smsc47m1_sio_data *sio_data)
{
	struct resource res = {
		.start	= address,
		.end	= address + SMSC_EXTENT - 1,
		.name	= DRVNAME,
		.flags	= IORESOURCE_IO,
	};
	int err;

820
	err = smsc47m1_handle_resources(address, sio_data->type, CHECK, NULL);
821 822 823
	if (err)
		goto exit;

824 825 826
	pdev = platform_device_alloc(DRVNAME, address);
	if (!pdev) {
		err = -ENOMEM;
827
		pr_err("Device allocation failed\n");
828 829 830 831 832
		goto exit;
	}

	err = platform_device_add_resources(pdev, &res, 1);
	if (err) {
833
		pr_err("Device resource addition failed (%d)\n", err);
834 835 836
		goto exit_device_put;
	}

837 838 839
	err = platform_device_add_data(pdev, sio_data,
				       sizeof(struct smsc47m1_sio_data));
	if (err) {
840
		pr_err("Platform data allocation failed\n");
841 842 843 844 845
		goto exit_device_put;
	}

	err = platform_device_add(pdev);
	if (err) {
846
		pr_err("Device addition failed (%d)\n", err);
847 848 849 850 851 852 853 854 855 856 857
		goto exit_device_put;
	}

	return 0;

exit_device_put:
	platform_device_put(pdev);
exit:
	return err;
}

L
Linus Torvalds 已提交
858 859
static int __init sm_smsc47m1_init(void)
{
860 861 862 863 864
	int err;
	unsigned short address;
	struct smsc47m1_sio_data sio_data;

	if (smsc47m1_find(&address, &sio_data))
L
Linus Torvalds 已提交
865 866
		return -ENODEV;

867 868
	/* Sets global pdev as a side effect */
	err = smsc47m1_device_add(address, &sio_data);
869 870 871
	if (err)
		goto exit;

872
	err = platform_driver_probe(&smsc47m1_driver, smsc47m1_probe);
873
	if (err)
874
		goto exit_device;
875 876 877

	return 0;

878 879
exit_device:
	platform_device_unregister(pdev);
880
	smsc47m1_restore(&sio_data);
881 882
exit:
	return err;
L
Linus Torvalds 已提交
883 884 885 886
}

static void __exit sm_smsc47m1_exit(void)
{
887
	platform_driver_unregister(&smsc47m1_driver);
888
	smsc47m1_restore(pdev->dev.platform_data);
889
	platform_device_unregister(pdev);
L
Linus Torvalds 已提交
890 891 892 893 894 895 896 897
}

MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>");
MODULE_DESCRIPTION("SMSC LPC47M1xx fan sensors driver");
MODULE_LICENSE("GPL");

module_init(sm_smsc47m1_init);
module_exit(sm_smsc47m1_exit);