Blame view

drivers/hwmon/smsc47b397.c 8.11 KB
74ba9207e   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-or-later
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
2
  /*
2a52dd667   Guenter Roeck   hwmon: (smsc47b39...
3
4
5
6
7
8
9
10
11
12
   * 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>
7c81c60f3   Jean Delvare   Update Jean Delva...
13
   * Copyright (C) 2004 Jean Delvare <jdelvare@suse.de>
2a52dd667   Guenter Roeck   hwmon: (smsc47b39...
14
   */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
15

bf1a85eff   Joe Perches   hwmon: (smsc47b39...
16
  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
17
18
19
20
  #include <linux/module.h>
  #include <linux/slab.h>
  #include <linux/ioport.h>
  #include <linux/jiffies.h>
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
21
  #include <linux/platform_device.h>
943b0830c   Mark M. Hoffman   [PATCH] I2C hwmon...
22
  #include <linux/hwmon.h>
3659a0178   Jean Delvare   hwmon/smsc47b397:...
23
  #include <linux/hwmon-sysfs.h>
943b0830c   Mark M. Hoffman   [PATCH] I2C hwmon...
24
  #include <linux/err.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
25
  #include <linux/init.h>
9a61bf630   Ingo Molnar   [PATCH] hwmon: Se...
26
  #include <linux/mutex.h>
b9acb64a3   Jean Delvare   hwmon: Check for ...
27
  #include <linux/acpi.h>
6055fae8a   H Hartley Sweeten   hwmon: Include <l...
28
  #include <linux/io.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
29

67b671bce   Jean Delvare   hwmon: Let the us...
30
31
32
  static unsigned short force_id;
  module_param(force_id, ushort, 0);
  MODULE_PARM_DESC(force_id, "Override the detected device ID");
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
33
34
35
  static struct platform_device *pdev;
  
  #define DRVNAME "smsc47b397"
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
  
  /* 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);
  }
8c0826756   Guenter Roeck   hwmon: (smsc47b39...
59
  static inline int superio_enter(void)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
60
  {
8c0826756   Guenter Roeck   hwmon: (smsc47b39...
61
62
  	if (!request_muxed_region(REG, 2, DRVNAME))
  		return -EBUSY;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
63
  	outb(0x55, REG);
8c0826756   Guenter Roeck   hwmon: (smsc47b39...
64
  	return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
65
66
67
68
69
  }
  
  static inline void superio_exit(void)
  {
  	outb(0xAA, REG);
8c0826756   Guenter Roeck   hwmon: (smsc47b39...
70
  	release_region(REG, 2);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
  }
  
  #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 {
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
90
  	unsigned short addr;
9a61bf630   Ingo Molnar   [PATCH] hwmon: Se...
91
  	struct mutex lock;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
92

9a61bf630   Ingo Molnar   [PATCH] hwmon: Se...
93
  	struct mutex update_lock;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
94
95
96
97
98
99
100
  	unsigned long last_updated; /* in jiffies */
  	int valid;
  
  	/* register values */
  	u16 fan[4];
  	u8 temp[4];
  };
371f2e083   Jean Delvare   hwmon: (smsc47b39...
101
  static int smsc47b397_read_value(struct smsc47b397_data *data, u8 reg)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
102
  {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
103
  	int res;
9a61bf630   Ingo Molnar   [PATCH] hwmon: Se...
104
  	mutex_lock(&data->lock);
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
105
106
  	outb(reg, data->addr);
  	res = inb_p(data->addr + 1);
9a61bf630   Ingo Molnar   [PATCH] hwmon: Se...
107
  	mutex_unlock(&data->lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
108
109
110
111
112
  	return res;
  }
  
  static struct smsc47b397_data *smsc47b397_update_device(struct device *dev)
  {
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
113
  	struct smsc47b397_data *data = dev_get_drvdata(dev);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
114
  	int i;
9a61bf630   Ingo Molnar   [PATCH] hwmon: Se...
115
  	mutex_lock(&data->update_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
116
117
  
  	if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
118
119
  		dev_dbg(dev, "starting device update...
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
120
121
122
  
  		/* 4 temperature inputs, 4 fan inputs */
  		for (i = 0; i < 4; i++) {
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
123
  			data->temp[i] = smsc47b397_read_value(data,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
124
125
126
  					SMSC47B397_REG_TEMP(i));
  
  			/* must read LSB first */
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
127
  			data->fan[i]  = smsc47b397_read_value(data,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
128
  					SMSC47B397_REG_FAN_LSB(i));
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
129
  			data->fan[i] |= smsc47b397_read_value(data,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
130
131
132
133
134
  					SMSC47B397_REG_FAN_MSB(i)) << 8;
  		}
  
  		data->last_updated = jiffies;
  		data->valid = 1;
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
135
136
  		dev_dbg(dev, "... device update complete
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
137
  	}
9a61bf630   Ingo Molnar   [PATCH] hwmon: Se...
138
  	mutex_unlock(&data->update_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
139
140
141
  
  	return data;
  }
2a52dd667   Guenter Roeck   hwmon: (smsc47b39...
142
143
144
145
  /*
   * TEMP: 0.001C/bit (-128C to +127C)
   * REG: 1C/bit, two's complement
   */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
146
147
148
149
  static int temp_from_reg(u8 reg)
  {
  	return (s8)reg * 1000;
  }
8721bdecd   Guenter Roeck   hwmon: (smsc47b39...
150
151
  static ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
  			 char *buf)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
152
  {
3659a0178   Jean Delvare   hwmon/smsc47b397:...
153
  	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
154
  	struct smsc47b397_data *data = smsc47b397_update_device(dev);
3659a0178   Jean Delvare   hwmon/smsc47b397:...
155
156
  	return sprintf(buf, "%d
  ", temp_from_reg(data->temp[attr->index]));
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
157
  }
8721bdecd   Guenter Roeck   hwmon: (smsc47b39...
158
159
160
161
  static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0);
  static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1);
  static SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 2);
  static SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 3);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
162

2a52dd667   Guenter Roeck   hwmon: (smsc47b39...
163
164
165
166
  /*
   * FAN: 1 RPM/bit
   * REG: count of 90kHz pulses / revolution
   */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
167
168
  static int fan_from_reg(u16 reg)
  {
90205c6cb   Jean Delvare   hwmon/smsc47b397:...
169
170
  	if (reg == 0 || reg == 0xffff)
  		return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
171
172
  	return 90000 * 60 / reg;
  }
8721bdecd   Guenter Roeck   hwmon: (smsc47b39...
173
174
  static ssize_t fan_show(struct device *dev, struct device_attribute *devattr,
  			char *buf)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
175
  {
3659a0178   Jean Delvare   hwmon/smsc47b397:...
176
177
178
179
  	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
  	struct smsc47b397_data *data = smsc47b397_update_device(dev);
  	return sprintf(buf, "%d
  ", fan_from_reg(data->fan[attr->index]));
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
180
  }
8721bdecd   Guenter Roeck   hwmon: (smsc47b39...
181
182
183
184
  static SENSOR_DEVICE_ATTR_RO(fan1_input, fan, 0);
  static SENSOR_DEVICE_ATTR_RO(fan2_input, fan, 1);
  static SENSOR_DEVICE_ATTR_RO(fan3_input, fan, 2);
  static SENSOR_DEVICE_ATTR_RO(fan4_input, fan, 3);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
185

9b993e366   Axel Lin   hwmon: (smsc47b39...
186
  static struct attribute *smsc47b397_attrs[] = {
3659a0178   Jean Delvare   hwmon/smsc47b397:...
187
188
189
190
191
192
193
194
  	&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,
c1685f61b   Mark M. Hoffman   hwmon: Fix unchec...
195
196
197
  
  	NULL
  };
9b993e366   Axel Lin   hwmon: (smsc47b39...
198
  ATTRIBUTE_GROUPS(smsc47b397);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
199

292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
200
  static int smsc47b397_probe(struct platform_device *pdev);
2d8672c5a   Jean Delvare   [PATCH] I2C: Sepa...
201

292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
202
  static struct platform_driver smsc47b397_driver = {
cdaf79349   Laurent Riffard   [PATCH] i2c: Drop...
203
  	.driver = {
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
204
  		.name	= DRVNAME,
cdaf79349   Laurent Riffard   [PATCH] i2c: Drop...
205
  	},
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
206
  	.probe		= smsc47b397_probe,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
207
  };
6c931ae1c   Bill Pemberton   hwmon: remove use...
208
  static int smsc47b397_probe(struct platform_device *pdev)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
209
  {
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
210
  	struct device *dev = &pdev->dev;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
211
  	struct smsc47b397_data *data;
9b993e366   Axel Lin   hwmon: (smsc47b39...
212
  	struct device *hwmon_dev;
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
213
  	struct resource *res;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
214

292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
215
  	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
e43d5fefa   Guenter Roeck   hwmon: (smsc47b39...
216
217
  	if (!devm_request_region(dev, res->start, SMSC_EXTENT,
  				 smsc47b397_driver.driver.name)) {
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
218
219
220
221
  		dev_err(dev, "Region 0x%lx-0x%lx already in use!
  ",
  			(unsigned long)res->start,
  			(unsigned long)res->start + SMSC_EXTENT - 1);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
222
223
  		return -EBUSY;
  	}
e43d5fefa   Guenter Roeck   hwmon: (smsc47b39...
224
225
226
  	data = devm_kzalloc(dev, sizeof(struct smsc47b397_data), GFP_KERNEL);
  	if (!data)
  		return -ENOMEM;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
227

292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
228
  	data->addr = res->start;
9a61bf630   Ingo Molnar   [PATCH] hwmon: Se...
229
  	mutex_init(&data->lock);
9a61bf630   Ingo Molnar   [PATCH] hwmon: Se...
230
  	mutex_init(&data->update_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
231

9b993e366   Axel Lin   hwmon: (smsc47b39...
232
233
234
235
  	hwmon_dev = devm_hwmon_device_register_with_groups(dev, "smsc47b397",
  							   data,
  							   smsc47b397_groups);
  	return PTR_ERR_OR_ZERO(hwmon_dev);
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
236
237
238
239
240
241
242
243
244
245
246
  }
  
  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;
b9acb64a3   Jean Delvare   hwmon: Check for ...
247
248
249
  	err = acpi_check_resource_conflict(&res);
  	if (err)
  		goto exit;
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
250
251
252
  	pdev = platform_device_alloc(DRVNAME, address);
  	if (!pdev) {
  		err = -ENOMEM;
bf1a85eff   Joe Perches   hwmon: (smsc47b39...
253
254
  		pr_err("Device allocation failed
  ");
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
255
256
257
258
259
  		goto exit;
  	}
  
  	err = platform_device_add_resources(pdev, &res, 1);
  	if (err) {
bf1a85eff   Joe Perches   hwmon: (smsc47b39...
260
261
  		pr_err("Device resource addition failed (%d)
  ", err);
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
262
263
264
265
266
  		goto exit_device_put;
  	}
  
  	err = platform_device_add(pdev);
  	if (err) {
bf1a85eff   Joe Perches   hwmon: (smsc47b39...
267
268
  		pr_err("Device addition failed (%d)
  ", err);
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
269
270
271
272
273
274
275
276
  		goto exit_device_put;
  	}
  
  	return 0;
  
  exit_device_put:
  	platform_device_put(pdev);
  exit:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
277
278
  	return err;
  }
8528e07ed   Guenter Roeck   hwmon: (smsc47b39...
279
  static int __init smsc47b397_find(void)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
280
281
  {
  	u8 id, rev;
809307768   Craig Kelley   hwmon: (smsc47b39...
282
  	char *name;
8528e07ed   Guenter Roeck   hwmon: (smsc47b39...
283
  	unsigned short addr;
8c0826756   Guenter Roeck   hwmon: (smsc47b39...
284
285
286
287
288
  	int err;
  
  	err = superio_enter();
  	if (err)
  		return err;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
289

67b671bce   Jean Delvare   hwmon: Let the us...
290
  	id = force_id ? force_id : superio_inb(SUPERIO_REG_DEVID);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
291

371f2e083   Jean Delvare   hwmon: (smsc47b39...
292
  	switch (id) {
809307768   Craig Kelley   hwmon: (smsc47b39...
293
294
295
296
297
298
299
300
301
302
303
  	case 0x81:
  		name = "SCH5307-NS";
  		break;
  	case 0x6f:
  		name = "LPC47B397-NC";
  		break;
  	case 0x85:
  	case 0x8c:
  		name = "SCH5317";
  		break;
  	default:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
304
305
306
307
308
309
310
  		superio_exit();
  		return -ENODEV;
  	}
  
  	rev = superio_inb(SUPERIO_REG_DEVREV);
  
  	superio_select(SUPERIO_REG_LD8);
8528e07ed   Guenter Roeck   hwmon: (smsc47b39...
311
  	addr = (superio_inb(SUPERIO_REG_BASE_MSB) << 8)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
312
  		 |  superio_inb(SUPERIO_REG_BASE_LSB);
bf1a85eff   Joe Perches   hwmon: (smsc47b39...
313
314
  	pr_info("found SMSC %s (base address 0x%04x, revision %u)
  ",
8528e07ed   Guenter Roeck   hwmon: (smsc47b39...
315
  		name, addr, rev);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
316
317
  
  	superio_exit();
8528e07ed   Guenter Roeck   hwmon: (smsc47b39...
318
  	return addr;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
319
320
321
322
  }
  
  static int __init smsc47b397_init(void)
  {
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
323
  	unsigned short address;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
324
  	int ret;
8528e07ed   Guenter Roeck   hwmon: (smsc47b39...
325
326
  	ret = smsc47b397_find();
  	if (ret < 0)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
327
  		return ret;
8528e07ed   Guenter Roeck   hwmon: (smsc47b39...
328
  	address = ret;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
329

292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
  	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;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
345
346
347
348
  }
  
  static void __exit smsc47b397_exit(void)
  {
292fc1a5f   Jean Delvare   hwmon/smsc47b397:...
349
350
  	platform_device_unregister(pdev);
  	platform_driver_unregister(&smsc47b397_driver);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
351
352
353
354
355
356
357
358
  }
  
  MODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>");
  MODULE_DESCRIPTION("SMSC LPC47B397 driver");
  MODULE_LICENSE("GPL");
  
  module_init(smsc47b397_init);
  module_exit(smsc47b397_exit);