smsc47b397.c 9.9 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>
39
#include <linux/acpi.h>
L
Linus Torvalds 已提交
40 41
#include <asm/io.h>

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

46 47 48
static struct platform_device *pdev;

#define DRVNAME "smsc47b397"
L
Linus Torvalds 已提交
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 99

/* 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 {
100 101
	unsigned short addr;
	const char *name;
102
	struct device *hwmon_dev;
103
	struct mutex lock;
L
Linus Torvalds 已提交
104

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

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

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

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

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

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

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

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

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

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

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

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

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

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

173 174 175 176
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 已提交
177 178 179 180 181

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

187 188
static ssize_t show_fan(struct device *dev, struct device_attribute
			*devattr, char *buf)
L
Linus Torvalds 已提交
189
{
190 191 192
	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 已提交
193
}
194 195 196 197
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 已提交
198

199 200 201 202 203 204 205 206
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);

207
static struct attribute *smsc47b397_attributes[] = {
208 209 210 211 212 213 214 215
	&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,
216

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

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

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

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

	return 0;
}

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

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

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

257 258
	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
	if (!request_region(res->start, SMSC_EXTENT,
259
			    smsc47b397_driver.driver.name)) {
260 261 262
		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 已提交
263 264 265
		return -EBUSY;
	}

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

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

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

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

L
Linus Torvalds 已提交
286 287
	return 0;

288
error_remove:
289
	sysfs_remove_group(&dev->kobj, &smsc47b397_group);
L
Linus Torvalds 已提交
290
error_free:
291
	kfree(data);
L
Linus Torvalds 已提交
292
error_release:
293 294 295 296 297 298 299 300 301 302 303 304 305 306
	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;

307 308 309 310
	err = acpi_check_resource_conflict(&res);
	if (err)
		goto exit;

311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
	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 已提交
337 338 339
	return err;
}

340
static int __init smsc47b397_find(unsigned short *addr)
L
Linus Torvalds 已提交
341 342
{
	u8 id, rev;
343
	char *name;
L
Linus Torvalds 已提交
344 345

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

348 349 350 351 352 353 354 355 356 357 358 359
	switch(id) {
	case 0x81:
		name = "SCH5307-NS";
		break;
	case 0x6f:
		name = "LPC47B397-NC";
		break;
	case 0x85:
	case 0x8c:
		name = "SCH5317";
		break;
	default:
L
Linus Torvalds 已提交
360 361 362 363 364 365 366 367 368 369
		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);

370
	printk(KERN_INFO DRVNAME ": found SMSC %s "
371
		"(base address 0x%04x, revision %u)\n",
372
		name, *addr, rev);
L
Linus Torvalds 已提交
373 374 375 376 377 378 379

	superio_exit();
	return 0;
}

static int __init smsc47b397_init(void)
{
380
	unsigned short address;
L
Linus Torvalds 已提交
381 382
	int ret;

383
	if ((ret = smsc47b397_find(&address)))
L
Linus Torvalds 已提交
384 385
		return ret;

386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
	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 已提交
401 402 403 404
}

static void __exit smsc47b397_exit(void)
{
405 406
	platform_device_unregister(pdev);
	platform_driver_unregister(&smsc47b397_driver);
L
Linus Torvalds 已提交
407 408 409 410 411 412 413 414
}

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

module_init(smsc47b397_init);
module_exit(smsc47b397_exit);