smsc47b397.c 9.7 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
/*
    smsc47b397.c - Part of lm_sensors, Linux kernel modules
			for hardware monitoring

    Supports the SMSC LPC47B397-NC Super-I/O chip.

    Author/Maintainer: Mark M. Hoffman <mhoffman@lightlink.com>
	Copyright (C) 2004 Utilitek Systems, Inc.

    derived in part from smsc47m1.c:
	Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
	Copyright (C) 2004 Jean Delvare <khali@linux-fr.org>

    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.
*/

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

41 42 43 44
static unsigned short force_id;
module_param(force_id, ushort, 0);
MODULE_PARM_DESC(force_id, "Override the detected device ID");

45 46 47
static struct platform_device *pdev;

#define DRVNAME "smsc47b397"
L
Linus Torvalds 已提交
48 49 50 51 52 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 91 92 93 94 95 96 97 98

/* 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);
}

/* select superio logical device */
static inline void superio_select(int ld)
{
	superio_outb(0x07, ld);
}

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

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

#define SUPERIO_REG_DEVID	0x20
#define SUPERIO_REG_DEVREV	0x21
#define SUPERIO_REG_BASE_MSB	0x60
#define SUPERIO_REG_BASE_LSB	0x61
#define SUPERIO_REG_LD8		0x08

#define SMSC_EXTENT		0x02

/* 0 <= nr <= 3 */
static u8 smsc47b397_reg_temp[] = {0x25, 0x26, 0x27, 0x80};
#define SMSC47B397_REG_TEMP(nr)	(smsc47b397_reg_temp[(nr)])

/* 0 <= nr <= 3 */
#define SMSC47B397_REG_FAN_LSB(nr) (0x28 + 2 * (nr))
#define SMSC47B397_REG_FAN_MSB(nr) (0x29 + 2 * (nr))

struct smsc47b397_data {
99 100
	unsigned short addr;
	const char *name;
101
	struct device *hwmon_dev;
102
	struct mutex lock;
L
Linus Torvalds 已提交
103

104
	struct mutex update_lock;
L
Linus Torvalds 已提交
105 106 107 108 109 110 111 112
	unsigned long last_updated; /* in jiffies */
	int valid;

	/* register values */
	u16 fan[4];
	u8 temp[4];
};

113
static int smsc47b397_read_value(struct smsc47b397_data* data, u8 reg)
L
Linus Torvalds 已提交
114 115 116
{
	int res;

117
	mutex_lock(&data->lock);
118 119
	outb(reg, data->addr);
	res = inb_p(data->addr + 1);
120
	mutex_unlock(&data->lock);
L
Linus Torvalds 已提交
121 122 123 124 125
	return res;
}

static struct smsc47b397_data *smsc47b397_update_device(struct device *dev)
{
126
	struct smsc47b397_data *data = dev_get_drvdata(dev);
L
Linus Torvalds 已提交
127 128
	int i;

129
	mutex_lock(&data->update_lock);
L
Linus Torvalds 已提交
130 131

	if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
132
		dev_dbg(dev, "starting device update...\n");
L
Linus Torvalds 已提交
133 134 135

		/* 4 temperature inputs, 4 fan inputs */
		for (i = 0; i < 4; i++) {
136
			data->temp[i] = smsc47b397_read_value(data,
L
Linus Torvalds 已提交
137 138 139
					SMSC47B397_REG_TEMP(i));

			/* must read LSB first */
140
			data->fan[i]  = smsc47b397_read_value(data,
L
Linus Torvalds 已提交
141
					SMSC47B397_REG_FAN_LSB(i));
142
			data->fan[i] |= smsc47b397_read_value(data,
L
Linus Torvalds 已提交
143 144 145 146 147 148
					SMSC47B397_REG_FAN_MSB(i)) << 8;
		}

		data->last_updated = jiffies;
		data->valid = 1;

149
		dev_dbg(dev, "... device update complete\n");
L
Linus Torvalds 已提交
150 151
	}

152
	mutex_unlock(&data->update_lock);
L
Linus Torvalds 已提交
153 154 155 156 157 158 159 160 161 162 163

	return data;
}

/* TEMP: 0.001C/bit (-128C to +127C)
   REG: 1C/bit, two's complement */
static int temp_from_reg(u8 reg)
{
	return (s8)reg * 1000;
}

164 165
static ssize_t show_temp(struct device *dev, struct device_attribute
			 *devattr, char *buf)
L
Linus Torvalds 已提交
166
{
167
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
L
Linus Torvalds 已提交
168
	struct smsc47b397_data *data = smsc47b397_update_device(dev);
169
	return sprintf(buf, "%d\n", temp_from_reg(data->temp[attr->index]));
L
Linus Torvalds 已提交
170 171
}

172 173 174 175
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1);
static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2);
static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3);
L
Linus Torvalds 已提交
176 177 178 179 180

/* FAN: 1 RPM/bit
   REG: count of 90kHz pulses / revolution */
static int fan_from_reg(u16 reg)
{
181 182
	if (reg == 0 || reg == 0xffff)
		return 0;
L
Linus Torvalds 已提交
183 184 185
	return 90000 * 60 / reg;
}

186 187
static ssize_t show_fan(struct device *dev, struct device_attribute
			*devattr, char *buf)
L
Linus Torvalds 已提交
188
{
189 190 191
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
	struct smsc47b397_data *data = smsc47b397_update_device(dev);
	return sprintf(buf, "%d\n", fan_from_reg(data->fan[attr->index]));
L
Linus Torvalds 已提交
192
}
193 194 195 196
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1);
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2);
static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3);
L
Linus Torvalds 已提交
197

198 199 200 201 202 203 204 205
static ssize_t show_name(struct device *dev, struct device_attribute
			 *devattr, char *buf)
{
	struct smsc47b397_data *data = dev_get_drvdata(dev);
	return sprintf(buf, "%s\n", data->name);
}
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);

206
static struct attribute *smsc47b397_attributes[] = {
207 208 209 210 211 212 213 214
	&sensor_dev_attr_temp1_input.dev_attr.attr,
	&sensor_dev_attr_temp2_input.dev_attr.attr,
	&sensor_dev_attr_temp3_input.dev_attr.attr,
	&sensor_dev_attr_temp4_input.dev_attr.attr,
	&sensor_dev_attr_fan1_input.dev_attr.attr,
	&sensor_dev_attr_fan2_input.dev_attr.attr,
	&sensor_dev_attr_fan3_input.dev_attr.attr,
	&sensor_dev_attr_fan4_input.dev_attr.attr,
215

216
	&dev_attr_name.attr,
217 218 219 220 221 222
	NULL
};

static const struct attribute_group smsc47b397_group = {
	.attrs = smsc47b397_attributes,
};
L
Linus Torvalds 已提交
223

224
static int __devexit smsc47b397_remove(struct platform_device *pdev)
L
Linus Torvalds 已提交
225
{
226 227
	struct smsc47b397_data *data = platform_get_drvdata(pdev);
	struct resource *res;
L
Linus Torvalds 已提交
228

229
	hwmon_device_unregister(data->hwmon_dev);
230 231 232
	sysfs_remove_group(&pdev->dev.kobj, &smsc47b397_group);
	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
	release_region(res->start, SMSC_EXTENT);
233
	kfree(data);
L
Linus Torvalds 已提交
234 235 236 237

	return 0;
}

238
static int smsc47b397_probe(struct platform_device *pdev);
239

240
static struct platform_driver smsc47b397_driver = {
241
	.driver = {
J
Jean Delvare 已提交
242
		.owner	= THIS_MODULE,
243
		.name	= DRVNAME,
244
	},
245 246
	.probe		= smsc47b397_probe,
	.remove		= __devexit_p(smsc47b397_remove),
L
Linus Torvalds 已提交
247 248
};

249
static int __devinit smsc47b397_probe(struct platform_device *pdev)
L
Linus Torvalds 已提交
250
{
251
	struct device *dev = &pdev->dev;
L
Linus Torvalds 已提交
252
	struct smsc47b397_data *data;
253
	struct resource *res;
L
Linus Torvalds 已提交
254 255
	int err = 0;

256 257
	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
	if (!request_region(res->start, SMSC_EXTENT,
258
			    smsc47b397_driver.driver.name)) {
259 260 261
		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
			(unsigned long)res->start,
			(unsigned long)res->start + SMSC_EXTENT - 1);
L
Linus Torvalds 已提交
262 263 264
		return -EBUSY;
	}

D
Deepak Saxena 已提交
265
	if (!(data = kzalloc(sizeof(struct smsc47b397_data), GFP_KERNEL))) {
L
Linus Torvalds 已提交
266 267 268 269
		err = -ENOMEM;
		goto error_release;
	}

270 271
	data->addr = res->start;
	data->name = "smsc47b397";
272 273
	mutex_init(&data->lock);
	mutex_init(&data->update_lock);
274
	platform_set_drvdata(pdev, data);
L
Linus Torvalds 已提交
275

276
	if ((err = sysfs_create_group(&dev->kobj, &smsc47b397_group)))
L
Linus Torvalds 已提交
277 278
		goto error_free;

279 280 281
	data->hwmon_dev = hwmon_device_register(dev);
	if (IS_ERR(data->hwmon_dev)) {
		err = PTR_ERR(data->hwmon_dev);
282
		goto error_remove;
283 284
	}

L
Linus Torvalds 已提交
285 286
	return 0;

287
error_remove:
288
	sysfs_remove_group(&dev->kobj, &smsc47b397_group);
L
Linus Torvalds 已提交
289
error_free:
290
	kfree(data);
L
Linus Torvalds 已提交
291
error_release:
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
	release_region(res->start, SMSC_EXTENT);
	return err;
}

static int __init smsc47b397_device_add(unsigned short address)
{
	struct resource res = {
		.start	= address,
		.end	= address + SMSC_EXTENT - 1,
		.name	= DRVNAME,
		.flags	= IORESOURCE_IO,
	};
	int err;

	pdev = platform_device_alloc(DRVNAME, address);
	if (!pdev) {
		err = -ENOMEM;
		printk(KERN_ERR DRVNAME ": Device allocation failed\n");
		goto exit;
	}

	err = platform_device_add_resources(pdev, &res, 1);
	if (err) {
		printk(KERN_ERR DRVNAME ": Device resource addition failed "
		       "(%d)\n", err);
		goto exit_device_put;
	}

	err = platform_device_add(pdev);
	if (err) {
		printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
		       err);
		goto exit_device_put;
	}

	return 0;

exit_device_put:
	platform_device_put(pdev);
exit:
L
Linus Torvalds 已提交
332 333 334
	return err;
}

335
static int __init smsc47b397_find(unsigned short *addr)
L
Linus Torvalds 已提交
336 337 338 339
{
	u8 id, rev;

	superio_enter();
340
	id = force_id ? force_id : superio_inb(SUPERIO_REG_DEVID);
L
Linus Torvalds 已提交
341

342
	if ((id != 0x6f) && (id != 0x81) && (id != 0x85)) {
L
Linus Torvalds 已提交
343 344 345 346 347 348 349 350 351 352
		superio_exit();
		return -ENODEV;
	}

	rev = superio_inb(SUPERIO_REG_DEVREV);

	superio_select(SUPERIO_REG_LD8);
	*addr = (superio_inb(SUPERIO_REG_BASE_MSB) << 8)
		 |  superio_inb(SUPERIO_REG_BASE_LSB);

353
	printk(KERN_INFO DRVNAME ": found SMSC %s "
354
		"(base address 0x%04x, revision %u)\n",
355 356
		id == 0x81 ? "SCH5307-NS" : id == 0x85 ? "SCH5317" :
	       "LPC47B397-NC", *addr, rev);
L
Linus Torvalds 已提交
357 358 359 360 361 362 363

	superio_exit();
	return 0;
}

static int __init smsc47b397_init(void)
{
364
	unsigned short address;
L
Linus Torvalds 已提交
365 366
	int ret;

367
	if ((ret = smsc47b397_find(&address)))
L
Linus Torvalds 已提交
368 369
		return ret;

370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
	ret = platform_driver_register(&smsc47b397_driver);
	if (ret)
		goto exit;

	/* Sets global pdev as a side effect */
	ret = smsc47b397_device_add(address);
	if (ret)
		goto exit_driver;

	return 0;

exit_driver:
	platform_driver_unregister(&smsc47b397_driver);
exit:
	return ret;
L
Linus Torvalds 已提交
385 386 387 388
}

static void __exit smsc47b397_exit(void)
{
389 390
	platform_device_unregister(pdev);
	platform_driver_unregister(&smsc47b397_driver);
L
Linus Torvalds 已提交
391 392 393 394 395 396 397 398
}

MODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>");
MODULE_DESCRIPTION("SMSC LPC47B397 driver");
MODULE_LICENSE("GPL");

module_init(smsc47b397_init);
module_exit(smsc47b397_exit);