smsc47b397.c 9.6 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
static struct platform_device *pdev;

#define DRVNAME "smsc47b397"
L
Linus Torvalds 已提交
44 45 46 47 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

/* 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 {
95 96
	unsigned short addr;
	const char *name;
97
	struct class_device *class_dev;
98
	struct mutex lock;
L
Linus Torvalds 已提交
99

100
	struct mutex update_lock;
L
Linus Torvalds 已提交
101 102 103 104 105 106 107 108
	unsigned long last_updated; /* in jiffies */
	int valid;

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

109
static int smsc47b397_read_value(struct smsc47b397_data* data, u8 reg)
L
Linus Torvalds 已提交
110 111 112
{
	int res;

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

static struct smsc47b397_data *smsc47b397_update_device(struct device *dev)
{
122
	struct smsc47b397_data *data = dev_get_drvdata(dev);
L
Linus Torvalds 已提交
123 124
	int i;

125
	mutex_lock(&data->update_lock);
L
Linus Torvalds 已提交
126 127

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

		/* 4 temperature inputs, 4 fan inputs */
		for (i = 0; i < 4; i++) {
132
			data->temp[i] = smsc47b397_read_value(data,
L
Linus Torvalds 已提交
133 134 135
					SMSC47B397_REG_TEMP(i));

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

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

145
		dev_dbg(dev, "... device update complete\n");
L
Linus Torvalds 已提交
146 147
	}

148
	mutex_unlock(&data->update_lock);
L
Linus Torvalds 已提交
149 150 151 152 153 154 155 156 157 158 159

	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;
}

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

168 169 170 171
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 已提交
172 173 174 175 176

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

182 183
static ssize_t show_fan(struct device *dev, struct device_attribute
			*devattr, char *buf)
L
Linus Torvalds 已提交
184
{
185 186 187
	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 已提交
188
}
189 190 191 192
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 已提交
193

194 195 196 197 198 199 200 201
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);

202
static struct attribute *smsc47b397_attributes[] = {
203 204 205 206 207 208 209 210
	&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,
211

212
	&dev_attr_name.attr,
213 214 215 216 217 218
	NULL
};

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

220
static int __devexit smsc47b397_remove(struct platform_device *pdev)
L
Linus Torvalds 已提交
221
{
222 223
	struct smsc47b397_data *data = platform_get_drvdata(pdev);
	struct resource *res;
L
Linus Torvalds 已提交
224

225
	hwmon_device_unregister(data->class_dev);
226 227 228
	sysfs_remove_group(&pdev->dev.kobj, &smsc47b397_group);
	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
	release_region(res->start, SMSC_EXTENT);
229
	kfree(data);
L
Linus Torvalds 已提交
230 231 232 233

	return 0;
}

234
static int smsc47b397_probe(struct platform_device *pdev);
235

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

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

252 253
	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
	if (!request_region(res->start, SMSC_EXTENT,
254
			    smsc47b397_driver.driver.name)) {
255 256 257
		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 已提交
258 259 260
		return -EBUSY;
	}

D
Deepak Saxena 已提交
261
	if (!(data = kzalloc(sizeof(struct smsc47b397_data), GFP_KERNEL))) {
L
Linus Torvalds 已提交
262 263 264 265
		err = -ENOMEM;
		goto error_release;
	}

266 267
	data->addr = res->start;
	data->name = "smsc47b397";
268 269
	mutex_init(&data->lock);
	mutex_init(&data->update_lock);
270
	platform_set_drvdata(pdev, data);
L
Linus Torvalds 已提交
271

272
	if ((err = sysfs_create_group(&dev->kobj, &smsc47b397_group)))
L
Linus Torvalds 已提交
273 274
		goto error_free;

275
	data->class_dev = hwmon_device_register(dev);
276 277
	if (IS_ERR(data->class_dev)) {
		err = PTR_ERR(data->class_dev);
278
		goto error_remove;
279 280
	}

L
Linus Torvalds 已提交
281 282
	return 0;

283
error_remove:
284
	sysfs_remove_group(&dev->kobj, &smsc47b397_group);
L
Linus Torvalds 已提交
285
error_free:
286
	kfree(data);
L
Linus Torvalds 已提交
287
error_release:
288 289 290 291 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
	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 已提交
328 329 330
	return err;
}

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

	superio_enter();
	id = superio_inb(SUPERIO_REG_DEVID);

338
	if ((id != 0x6f) && (id != 0x81) && (id != 0x85)) {
L
Linus Torvalds 已提交
339 340 341 342 343 344 345 346 347 348
		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);

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

	superio_exit();
	return 0;
}

static int __init smsc47b397_init(void)
{
360
	unsigned short address;
L
Linus Torvalds 已提交
361 362
	int ret;

363
	if ((ret = smsc47b397_find(&address)))
L
Linus Torvalds 已提交
364 365
		return ret;

366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
	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 已提交
381 382 383 384
}

static void __exit smsc47b397_exit(void)
{
385 386
	platform_device_unregister(pdev);
	platform_driver_unregister(&smsc47b397_driver);
L
Linus Torvalds 已提交
387 388 389 390 391 392 393 394
}

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

module_init(smsc47b397_init);
module_exit(smsc47b397_exit);