blob: 074f7f4719f3f32fa9dbca40cf39d90d9a3b2e66 [file] [log] [blame]
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001/*
2 * drivers/hwmon/applesmc.c - driver for Apple's SMC (accelerometer, temperature
3 * sensors, fan control, keyboard backlight control) used in Intel-based Apple
4 * computers.
5 *
6 * Copyright (C) 2007 Nicolas Boichat <nicolas@boichat.ch>
7 *
8 * Based on hdaps.c driver:
9 * Copyright (C) 2005 Robert Love <rml@novell.com>
10 * Copyright (C) 2005 Jesper Juhl <jesper.juhl@gmail.com>
11 *
12 * Fan control based on smcFanControl:
13 * Copyright (C) 2006 Hendrik Holtmann <holtmann@mac.com>
14 *
15 * This program is free software; you can redistribute it and/or modify it
16 * under the terms of the GNU General Public License v2 as published by the
17 * Free Software Foundation.
18 *
19 * This program is distributed in the hope that it will be useful, but WITHOUT
20 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
22 * more details.
23 *
24 * You should have received a copy of the GNU General Public License along with
25 * this program; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
27 */
28
29#include <linux/delay.h>
30#include <linux/platform_device.h>
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -040031#include <linux/input-polldev.h>
Nicolas Boichat6f2fad72007-05-08 00:24:52 -070032#include <linux/kernel.h>
33#include <linux/module.h>
34#include <linux/timer.h>
35#include <linux/dmi.h>
36#include <linux/mutex.h>
37#include <linux/hwmon-sysfs.h>
38#include <asm/io.h>
39#include <linux/leds.h>
40#include <linux/hwmon.h>
41#include <linux/workqueue.h>
42
43/* data port used by Apple SMC */
44#define APPLESMC_DATA_PORT 0x300
45/* command/status port used by Apple SMC */
46#define APPLESMC_CMD_PORT 0x304
47
48#define APPLESMC_NR_PORTS 32 /* 0x300-0x31f */
49
50#define APPLESMC_MAX_DATA_LENGTH 32
51
Henrik Rydberg8c9398d2008-10-18 20:27:43 -070052#define APPLESMC_MIN_WAIT 0x0040
53#define APPLESMC_MAX_WAIT 0x8000
54
Nicolas Boichat6f2fad72007-05-08 00:24:52 -070055#define APPLESMC_STATUS_MASK 0x0f
56#define APPLESMC_READ_CMD 0x10
57#define APPLESMC_WRITE_CMD 0x11
58#define APPLESMC_GET_KEY_BY_INDEX_CMD 0x12
59#define APPLESMC_GET_KEY_TYPE_CMD 0x13
60
61#define KEY_COUNT_KEY "#KEY" /* r-o ui32 */
62
Henrik Rydberg8bd1a122008-10-18 20:27:39 -070063#define LIGHT_SENSOR_LEFT_KEY "ALV0" /* r-o {alv (6-10 bytes) */
64#define LIGHT_SENSOR_RIGHT_KEY "ALV1" /* r-o {alv (6-10 bytes) */
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -040065#define BACKLIGHT_KEY "LKSB" /* w-o {lkb (2 bytes) */
Nicolas Boichat6f2fad72007-05-08 00:24:52 -070066
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -040067#define CLAMSHELL_KEY "MSLD" /* r-o ui8 (unused) */
Nicolas Boichat6f2fad72007-05-08 00:24:52 -070068
69#define MOTION_SENSOR_X_KEY "MO_X" /* r-o sp78 (2 bytes) */
70#define MOTION_SENSOR_Y_KEY "MO_Y" /* r-o sp78 (2 bytes) */
71#define MOTION_SENSOR_Z_KEY "MO_Z" /* r-o sp78 (2 bytes) */
72#define MOTION_SENSOR_KEY "MOCN" /* r/w ui16 */
73
74#define FANS_COUNT "FNum" /* r-o ui8 */
75#define FANS_MANUAL "FS! " /* r-w ui16 */
76#define FAN_ACTUAL_SPEED "F0Ac" /* r-o fpe2 (2 bytes) */
77#define FAN_MIN_SPEED "F0Mn" /* r-o fpe2 (2 bytes) */
78#define FAN_MAX_SPEED "F0Mx" /* r-o fpe2 (2 bytes) */
79#define FAN_SAFE_SPEED "F0Sf" /* r-o fpe2 (2 bytes) */
80#define FAN_TARGET_SPEED "F0Tg" /* r-w fpe2 (2 bytes) */
81#define FAN_POSITION "F0ID" /* r-o char[16] */
82
83/*
84 * Temperature sensors keys (sp78 - 2 bytes).
Nicolas Boichat6f2fad72007-05-08 00:24:52 -070085 */
René Rebe8de57702007-10-16 14:19:20 -070086static const char* temperature_sensors_sets[][36] = {
Martin Szulecki1bed24b2007-07-09 11:41:36 -070087/* Set 0: Macbook Pro */
Nicolas Boichat6f2fad72007-05-08 00:24:52 -070088 { "TA0P", "TB0T", "TC0D", "TC0P", "TG0H", "TG0P", "TG0T", "Th0H",
89 "Th1H", "Tm0P", "Ts0P", "Ts1P", NULL },
Riki Oktariantocd19ba12008-02-04 23:41:58 -080090/* Set 1: Macbook2 set */
91 { "TB0T", "TC0D", "TC0P", "TM0P", "TN0P", "TN1P", "TTF0", "Th0H",
92 "Th0S", "Th1H", NULL },
93/* Set 2: Macbook set */
Martin Szulecki1bed24b2007-07-09 11:41:36 -070094 { "TB0T", "TC0D", "TC0P", "TM0P", "TN0P", "TN1P", "Th0H", "Th0S",
95 "Th1H", "Ts0P", NULL },
Riki Oktariantocd19ba12008-02-04 23:41:58 -080096/* Set 3: Macmini set */
René Rebe8de57702007-10-16 14:19:20 -070097 { "TC0D", "TC0P", NULL },
Riki Oktariantocd19ba12008-02-04 23:41:58 -080098/* Set 4: Mac Pro (2 x Quad-Core) */
René Rebe8de57702007-10-16 14:19:20 -070099 { "TA0P", "TCAG", "TCAH", "TCBG", "TCBH", "TC0C", "TC0D", "TC0P",
100 "TC1C", "TC1D", "TC2C", "TC2D", "TC3C", "TC3D", "THTG", "TH0P",
101 "TH1P", "TH2P", "TH3P", "TMAP", "TMAS", "TMBS", "TM0P", "TM0S",
102 "TM1P", "TM1S", "TM2P", "TM2S", "TM3S", "TM8P", "TM8S", "TM9P",
103 "TM9S", "TN0H", "TS0C", NULL },
Roberto De Ioris9f86f282008-08-15 00:40:30 -0700104/* Set 5: iMac */
105 { "TC0D", "TA0P", "TG0P", "TG0D", "TG0H", "TH0P", "Tm0P", "TO0P",
106 "Tp0C", NULL },
Guilherme M. Schroederf91a79f2008-08-15 00:40:32 -0700107/* Set 6: Macbook3 set */
108 { "TB0T", "TC0D", "TC0P", "TM0P", "TN0P", "TTF0", "TW0P", "Th0H",
109 "Th0S", "Th1H", NULL },
Henrik Rydbergf5274c92008-10-18 20:27:40 -0700110/* Set 7: Macbook Air */
111 { "TB0T", "TB1S", "TB1T", "TB2S", "TB2T", "TC0D", "TC0P", "TCFP",
112 "TTF0", "TW0P", "Th0H", "Tp0P", "TpFP", "Ts0P", "Ts0S", NULL },
Henrik Rydbergd7549902008-10-18 20:27:41 -0700113/* Set 8: Macbook Pro 4,1 (Penryn) */
114 { "TB0T", "TC0D", "TC0P", "TG0D", "TG0H", "TTF0", "TW0P", "Th0H",
115 "Th1H", "Th2H", "Tm0P", "Ts0P", NULL },
Henrik Rydberg07e8dbd2008-10-18 20:27:42 -0700116/* Set 9: Macbook Pro 3,1 (Santa Rosa) */
117 { "TALP", "TB0T", "TC0D", "TC0P", "TG0D", "TG0H", "TTF0", "TW0P",
118 "Th0H", "Th1H", "Th2H", "Tm0P", "Ts0P", NULL },
Henrik Rydberg6e3530f2008-11-06 12:53:19 -0800119/* Set 10: iMac 5,1 */
120 { "TA0P", "TC0D", "TC0P", "TG0D", "TH0P", "TO0P", "Tm0P", NULL },
Henrik Rydberg181209a12008-11-06 12:53:20 -0800121/* Set 11: Macbook 5,1 */
122 { "TB0T", "TB1T", "TB2T", "TB3T", "TC0D", "TC0P", "TN0D", "TN0P",
123 "TTF0", "Th0H", "Th1H", "ThFH", "Ts0P", "Ts0S", NULL },
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700124};
125
126/* List of keys used to read/write fan speeds */
127static const char* fan_speed_keys[] = {
128 FAN_ACTUAL_SPEED,
129 FAN_MIN_SPEED,
130 FAN_MAX_SPEED,
131 FAN_SAFE_SPEED,
132 FAN_TARGET_SPEED
133};
134
135#define INIT_TIMEOUT_MSECS 5000 /* wait up to 5s for device init ... */
136#define INIT_WAIT_MSECS 50 /* ... in 50ms increments */
137
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -0400138#define APPLESMC_POLL_INTERVAL 50 /* msecs */
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700139#define APPLESMC_INPUT_FUZZ 4 /* input event threshold */
140#define APPLESMC_INPUT_FLAT 4
141
142#define SENSOR_X 0
143#define SENSOR_Y 1
144#define SENSOR_Z 2
145
146/* Structure to be passed to DMI_MATCH function */
147struct dmi_match_data {
148/* Indicates whether this computer has an accelerometer. */
149 int accelerometer;
150/* Indicates whether this computer has light sensors and keyboard backlight. */
151 int light;
152/* Indicates which temperature sensors set to use. */
153 int temperature_set;
154};
155
156static const int debug;
157static struct platform_device *pdev;
158static s16 rest_x;
159static s16 rest_y;
Tony Jones1beeffe2007-08-20 13:46:20 -0700160static struct device *hwmon_dev;
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -0400161static struct input_polled_dev *applesmc_idev;
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700162
163/* Indicates whether this computer has an accelerometer. */
164static unsigned int applesmc_accelerometer;
165
166/* Indicates whether this computer has light sensors and keyboard backlight. */
167static unsigned int applesmc_light;
168
169/* Indicates which temperature sensors set to use. */
170static unsigned int applesmc_temperature_set;
171
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -0400172static DEFINE_MUTEX(applesmc_lock);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700173
174/*
175 * Last index written to key_at_index sysfs file, and value to use for all other
176 * key_at_index_* sysfs files.
177 */
178static unsigned int key_at_index;
179
180static struct workqueue_struct *applesmc_led_wq;
181
182/*
Henrik Rydberg8c9398d2008-10-18 20:27:43 -0700183 * __wait_status - Wait up to 32ms for the status port to get a certain value
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700184 * (masked with 0x0f), returning zero if the value is obtained. Callers must
185 * hold applesmc_lock.
186 */
187static int __wait_status(u8 val)
188{
Henrik Rydberg8c9398d2008-10-18 20:27:43 -0700189 int us;
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700190
191 val = val & APPLESMC_STATUS_MASK;
192
Henrik Rydberg8c9398d2008-10-18 20:27:43 -0700193 for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
194 udelay(us);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700195 if ((inb(APPLESMC_CMD_PORT) & APPLESMC_STATUS_MASK) == val) {
196 if (debug)
197 printk(KERN_DEBUG
Henrik Rydberg8c9398d2008-10-18 20:27:43 -0700198 "Waited %d us for status %x\n",
199 2 * us - APPLESMC_MIN_WAIT, val);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700200 return 0;
201 }
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700202 }
203
204 printk(KERN_WARNING "applesmc: wait status failed: %x != %x\n",
205 val, inb(APPLESMC_CMD_PORT));
206
207 return -EIO;
208}
209
210/*
Henrik Rydberg84d2d7f2008-10-18 20:27:38 -0700211 * special treatment of command port - on newer macbooks, it seems necessary
212 * to resend the command byte before polling the status again. Callers must
213 * hold applesmc_lock.
214 */
215static int send_command(u8 cmd)
216{
Henrik Rydberg8c9398d2008-10-18 20:27:43 -0700217 int us;
218 for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
Henrik Rydberg84d2d7f2008-10-18 20:27:38 -0700219 outb(cmd, APPLESMC_CMD_PORT);
Henrik Rydberg8c9398d2008-10-18 20:27:43 -0700220 udelay(us);
Henrik Rydberg84d2d7f2008-10-18 20:27:38 -0700221 if ((inb(APPLESMC_CMD_PORT) & APPLESMC_STATUS_MASK) == 0x0c)
222 return 0;
Henrik Rydberg84d2d7f2008-10-18 20:27:38 -0700223 }
224 printk(KERN_WARNING "applesmc: command failed: %x -> %x\n",
225 cmd, inb(APPLESMC_CMD_PORT));
226 return -EIO;
227}
228
229/*
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700230 * applesmc_read_key - reads len bytes from a given key, and put them in buffer.
231 * Returns zero on success or a negative error on failure. Callers must
232 * hold applesmc_lock.
233 */
234static int applesmc_read_key(const char* key, u8* buffer, u8 len)
235{
236 int i;
237
238 if (len > APPLESMC_MAX_DATA_LENGTH) {
239 printk(KERN_ERR "applesmc_read_key: cannot read more than "
240 "%d bytes\n", APPLESMC_MAX_DATA_LENGTH);
241 return -EINVAL;
242 }
243
Henrik Rydberg84d2d7f2008-10-18 20:27:38 -0700244 if (send_command(APPLESMC_READ_CMD))
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700245 return -EIO;
246
247 for (i = 0; i < 4; i++) {
248 outb(key[i], APPLESMC_DATA_PORT);
249 if (__wait_status(0x04))
250 return -EIO;
251 }
252 if (debug)
253 printk(KERN_DEBUG "<%s", key);
254
255 outb(len, APPLESMC_DATA_PORT);
256 if (debug)
257 printk(KERN_DEBUG ">%x", len);
258
259 for (i = 0; i < len; i++) {
260 if (__wait_status(0x05))
261 return -EIO;
262 buffer[i] = inb(APPLESMC_DATA_PORT);
263 if (debug)
264 printk(KERN_DEBUG "<%x", buffer[i]);
265 }
266 if (debug)
267 printk(KERN_DEBUG "\n");
268
269 return 0;
270}
271
272/*
273 * applesmc_write_key - writes len bytes from buffer to a given key.
274 * Returns zero on success or a negative error on failure. Callers must
275 * hold applesmc_lock.
276 */
277static int applesmc_write_key(const char* key, u8* buffer, u8 len)
278{
279 int i;
280
281 if (len > APPLESMC_MAX_DATA_LENGTH) {
282 printk(KERN_ERR "applesmc_write_key: cannot write more than "
283 "%d bytes\n", APPLESMC_MAX_DATA_LENGTH);
284 return -EINVAL;
285 }
286
Henrik Rydberg84d2d7f2008-10-18 20:27:38 -0700287 if (send_command(APPLESMC_WRITE_CMD))
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700288 return -EIO;
289
290 for (i = 0; i < 4; i++) {
291 outb(key[i], APPLESMC_DATA_PORT);
292 if (__wait_status(0x04))
293 return -EIO;
294 }
295
296 outb(len, APPLESMC_DATA_PORT);
297
298 for (i = 0; i < len; i++) {
299 if (__wait_status(0x04))
300 return -EIO;
301 outb(buffer[i], APPLESMC_DATA_PORT);
302 }
303
304 return 0;
305}
306
307/*
308 * applesmc_get_key_at_index - get key at index, and put the result in key
309 * (char[6]). Returns zero on success or a negative error on failure. Callers
310 * must hold applesmc_lock.
311 */
312static int applesmc_get_key_at_index(int index, char* key)
313{
314 int i;
315 u8 readkey[4];
316 readkey[0] = index >> 24;
317 readkey[1] = index >> 16;
318 readkey[2] = index >> 8;
319 readkey[3] = index;
320
Henrik Rydberg84d2d7f2008-10-18 20:27:38 -0700321 if (send_command(APPLESMC_GET_KEY_BY_INDEX_CMD))
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700322 return -EIO;
323
324 for (i = 0; i < 4; i++) {
325 outb(readkey[i], APPLESMC_DATA_PORT);
326 if (__wait_status(0x04))
327 return -EIO;
328 }
329
330 outb(4, APPLESMC_DATA_PORT);
331
332 for (i = 0; i < 4; i++) {
333 if (__wait_status(0x05))
334 return -EIO;
335 key[i] = inb(APPLESMC_DATA_PORT);
336 }
337 key[4] = 0;
338
339 return 0;
340}
341
342/*
343 * applesmc_get_key_type - get key type, and put the result in type (char[6]).
344 * Returns zero on success or a negative error on failure. Callers must
345 * hold applesmc_lock.
346 */
347static int applesmc_get_key_type(char* key, char* type)
348{
349 int i;
350
Henrik Rydberg84d2d7f2008-10-18 20:27:38 -0700351 if (send_command(APPLESMC_GET_KEY_TYPE_CMD))
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700352 return -EIO;
353
354 for (i = 0; i < 4; i++) {
355 outb(key[i], APPLESMC_DATA_PORT);
356 if (__wait_status(0x04))
357 return -EIO;
358 }
359
Henrik Rydberg05224092008-10-18 20:27:35 -0700360 outb(6, APPLESMC_DATA_PORT);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700361
362 for (i = 0; i < 6; i++) {
363 if (__wait_status(0x05))
364 return -EIO;
365 type[i] = inb(APPLESMC_DATA_PORT);
366 }
367 type[5] = 0;
368
369 return 0;
370}
371
372/*
373 * applesmc_read_motion_sensor - Read motion sensor (X, Y or Z). Callers must
374 * hold applesmc_lock.
375 */
376static int applesmc_read_motion_sensor(int index, s16* value)
377{
378 u8 buffer[2];
379 int ret;
380
381 switch (index) {
382 case SENSOR_X:
383 ret = applesmc_read_key(MOTION_SENSOR_X_KEY, buffer, 2);
384 break;
385 case SENSOR_Y:
386 ret = applesmc_read_key(MOTION_SENSOR_Y_KEY, buffer, 2);
387 break;
388 case SENSOR_Z:
389 ret = applesmc_read_key(MOTION_SENSOR_Z_KEY, buffer, 2);
390 break;
391 default:
392 ret = -EINVAL;
393 }
394
395 *value = ((s16)buffer[0] << 8) | buffer[1];
396
397 return ret;
398}
399
400/*
401 * applesmc_device_init - initialize the accelerometer. Returns zero on success
402 * and negative error code on failure. Can sleep.
403 */
404static int applesmc_device_init(void)
405{
406 int total, ret = -ENXIO;
407 u8 buffer[2];
408
409 if (!applesmc_accelerometer)
410 return 0;
411
412 mutex_lock(&applesmc_lock);
413
414 for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
415 if (debug)
416 printk(KERN_DEBUG "applesmc try %d\n", total);
417 if (!applesmc_read_key(MOTION_SENSOR_KEY, buffer, 2) &&
418 (buffer[0] != 0x00 || buffer[1] != 0x00)) {
419 if (total == INIT_TIMEOUT_MSECS) {
420 printk(KERN_DEBUG "applesmc: device has"
421 " already been initialized"
422 " (0x%02x, 0x%02x).\n",
423 buffer[0], buffer[1]);
424 } else {
425 printk(KERN_DEBUG "applesmc: device"
426 " successfully initialized"
427 " (0x%02x, 0x%02x).\n",
428 buffer[0], buffer[1]);
429 }
430 ret = 0;
431 goto out;
432 }
433 buffer[0] = 0xe0;
434 buffer[1] = 0x00;
435 applesmc_write_key(MOTION_SENSOR_KEY, buffer, 2);
436 msleep(INIT_WAIT_MSECS);
437 }
438
439 printk(KERN_WARNING "applesmc: failed to init the device\n");
440
441out:
442 mutex_unlock(&applesmc_lock);
443 return ret;
444}
445
446/*
447 * applesmc_get_fan_count - get the number of fans. Callers must NOT hold
448 * applesmc_lock.
449 */
450static int applesmc_get_fan_count(void)
451{
452 int ret;
453 u8 buffer[1];
454
455 mutex_lock(&applesmc_lock);
456
457 ret = applesmc_read_key(FANS_COUNT, buffer, 1);
458
459 mutex_unlock(&applesmc_lock);
460 if (ret)
461 return ret;
462 else
463 return buffer[0];
464}
465
466/* Device model stuff */
467static int applesmc_probe(struct platform_device *dev)
468{
469 int ret;
470
471 ret = applesmc_device_init();
472 if (ret)
473 return ret;
474
475 printk(KERN_INFO "applesmc: device successfully initialized.\n");
476 return 0;
477}
478
479static int applesmc_resume(struct platform_device *dev)
480{
481 return applesmc_device_init();
482}
483
484static struct platform_driver applesmc_driver = {
485 .probe = applesmc_probe,
486 .resume = applesmc_resume,
487 .driver = {
488 .name = "applesmc",
489 .owner = THIS_MODULE,
490 },
491};
492
493/*
494 * applesmc_calibrate - Set our "resting" values. Callers must
495 * hold applesmc_lock.
496 */
497static void applesmc_calibrate(void)
498{
499 applesmc_read_motion_sensor(SENSOR_X, &rest_x);
500 applesmc_read_motion_sensor(SENSOR_Y, &rest_y);
501 rest_x = -rest_x;
502}
503
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -0400504static void applesmc_idev_poll(struct input_polled_dev *dev)
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700505{
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -0400506 struct input_dev *idev = dev->input;
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700507 s16 x, y;
508
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -0400509 mutex_lock(&applesmc_lock);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700510
511 if (applesmc_read_motion_sensor(SENSOR_X, &x))
512 goto out;
513 if (applesmc_read_motion_sensor(SENSOR_Y, &y))
514 goto out;
515
516 x = -x;
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -0400517 input_report_abs(idev, ABS_X, x - rest_x);
518 input_report_abs(idev, ABS_Y, y - rest_y);
519 input_sync(idev);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700520
521out:
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700522 mutex_unlock(&applesmc_lock);
523}
524
525/* Sysfs Files */
526
Nicolas Boichatfa744192007-05-23 13:58:13 -0700527static ssize_t applesmc_name_show(struct device *dev,
528 struct device_attribute *attr, char *buf)
529{
530 return snprintf(buf, PAGE_SIZE, "applesmc\n");
531}
532
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700533static ssize_t applesmc_position_show(struct device *dev,
534 struct device_attribute *attr, char *buf)
535{
536 int ret;
537 s16 x, y, z;
538
539 mutex_lock(&applesmc_lock);
540
541 ret = applesmc_read_motion_sensor(SENSOR_X, &x);
542 if (ret)
543 goto out;
544 ret = applesmc_read_motion_sensor(SENSOR_Y, &y);
545 if (ret)
546 goto out;
547 ret = applesmc_read_motion_sensor(SENSOR_Z, &z);
548 if (ret)
549 goto out;
550
551out:
552 mutex_unlock(&applesmc_lock);
553 if (ret)
554 return ret;
555 else
556 return snprintf(buf, PAGE_SIZE, "(%d,%d,%d)\n", x, y, z);
557}
558
559static ssize_t applesmc_light_show(struct device *dev,
560 struct device_attribute *attr, char *sysfsbuf)
561{
Henrik Rydberg8bd1a122008-10-18 20:27:39 -0700562 static int data_length;
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700563 int ret;
564 u8 left = 0, right = 0;
Henrik Rydberg8bd1a122008-10-18 20:27:39 -0700565 u8 buffer[10], query[6];
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700566
567 mutex_lock(&applesmc_lock);
568
Henrik Rydberg8bd1a122008-10-18 20:27:39 -0700569 if (!data_length) {
570 ret = applesmc_get_key_type(LIGHT_SENSOR_LEFT_KEY, query);
571 if (ret)
572 goto out;
573 data_length = clamp_val(query[0], 0, 10);
574 printk(KERN_INFO "applesmc: light sensor data length set to "
575 "%d\n", data_length);
576 }
577
578 ret = applesmc_read_key(LIGHT_SENSOR_LEFT_KEY, buffer, data_length);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700579 left = buffer[2];
580 if (ret)
581 goto out;
Henrik Rydberg8bd1a122008-10-18 20:27:39 -0700582 ret = applesmc_read_key(LIGHT_SENSOR_RIGHT_KEY, buffer, data_length);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700583 right = buffer[2];
584
585out:
586 mutex_unlock(&applesmc_lock);
587 if (ret)
588 return ret;
589 else
590 return snprintf(sysfsbuf, PAGE_SIZE, "(%d,%d)\n", left, right);
591}
592
593/* Displays degree Celsius * 1000 */
594static ssize_t applesmc_show_temperature(struct device *dev,
595 struct device_attribute *devattr, char *sysfsbuf)
596{
597 int ret;
598 u8 buffer[2];
599 unsigned int temp;
600 struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
601 const char* key =
602 temperature_sensors_sets[applesmc_temperature_set][attr->index];
603
604 mutex_lock(&applesmc_lock);
605
606 ret = applesmc_read_key(key, buffer, 2);
607 temp = buffer[0]*1000;
608 temp += (buffer[1] >> 6) * 250;
609
610 mutex_unlock(&applesmc_lock);
611
612 if (ret)
613 return ret;
614 else
615 return snprintf(sysfsbuf, PAGE_SIZE, "%u\n", temp);
616}
617
618static ssize_t applesmc_show_fan_speed(struct device *dev,
619 struct device_attribute *attr, char *sysfsbuf)
620{
621 int ret;
622 unsigned int speed = 0;
623 char newkey[5];
624 u8 buffer[2];
625 struct sensor_device_attribute_2 *sensor_attr =
626 to_sensor_dev_attr_2(attr);
627
628 newkey[0] = fan_speed_keys[sensor_attr->nr][0];
629 newkey[1] = '0' + sensor_attr->index;
630 newkey[2] = fan_speed_keys[sensor_attr->nr][2];
631 newkey[3] = fan_speed_keys[sensor_attr->nr][3];
632 newkey[4] = 0;
633
634 mutex_lock(&applesmc_lock);
635
636 ret = applesmc_read_key(newkey, buffer, 2);
637 speed = ((buffer[0] << 8 | buffer[1]) >> 2);
638
639 mutex_unlock(&applesmc_lock);
640 if (ret)
641 return ret;
642 else
643 return snprintf(sysfsbuf, PAGE_SIZE, "%u\n", speed);
644}
645
646static ssize_t applesmc_store_fan_speed(struct device *dev,
647 struct device_attribute *attr,
648 const char *sysfsbuf, size_t count)
649{
650 int ret;
651 u32 speed;
652 char newkey[5];
653 u8 buffer[2];
654 struct sensor_device_attribute_2 *sensor_attr =
655 to_sensor_dev_attr_2(attr);
656
657 speed = simple_strtoul(sysfsbuf, NULL, 10);
658
659 if (speed > 0x4000) /* Bigger than a 14-bit value */
660 return -EINVAL;
661
662 newkey[0] = fan_speed_keys[sensor_attr->nr][0];
663 newkey[1] = '0' + sensor_attr->index;
664 newkey[2] = fan_speed_keys[sensor_attr->nr][2];
665 newkey[3] = fan_speed_keys[sensor_attr->nr][3];
666 newkey[4] = 0;
667
668 mutex_lock(&applesmc_lock);
669
670 buffer[0] = (speed >> 6) & 0xff;
671 buffer[1] = (speed << 2) & 0xff;
672 ret = applesmc_write_key(newkey, buffer, 2);
673
674 mutex_unlock(&applesmc_lock);
675 if (ret)
676 return ret;
677 else
678 return count;
679}
680
681static ssize_t applesmc_show_fan_manual(struct device *dev,
682 struct device_attribute *devattr, char *sysfsbuf)
683{
684 int ret;
685 u16 manual = 0;
686 u8 buffer[2];
687 struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
688
689 mutex_lock(&applesmc_lock);
690
691 ret = applesmc_read_key(FANS_MANUAL, buffer, 2);
692 manual = ((buffer[0] << 8 | buffer[1]) >> attr->index) & 0x01;
693
694 mutex_unlock(&applesmc_lock);
695 if (ret)
696 return ret;
697 else
698 return snprintf(sysfsbuf, PAGE_SIZE, "%d\n", manual);
699}
700
701static ssize_t applesmc_store_fan_manual(struct device *dev,
702 struct device_attribute *devattr,
703 const char *sysfsbuf, size_t count)
704{
705 int ret;
706 u8 buffer[2];
707 u32 input;
708 u16 val;
709 struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
710
711 input = simple_strtoul(sysfsbuf, NULL, 10);
712
713 mutex_lock(&applesmc_lock);
714
715 ret = applesmc_read_key(FANS_MANUAL, buffer, 2);
716 val = (buffer[0] << 8 | buffer[1]);
717 if (ret)
718 goto out;
719
720 if (input)
721 val = val | (0x01 << attr->index);
722 else
723 val = val & ~(0x01 << attr->index);
724
725 buffer[0] = (val >> 8) & 0xFF;
726 buffer[1] = val & 0xFF;
727
728 ret = applesmc_write_key(FANS_MANUAL, buffer, 2);
729
730out:
731 mutex_unlock(&applesmc_lock);
732 if (ret)
733 return ret;
734 else
735 return count;
736}
737
738static ssize_t applesmc_show_fan_position(struct device *dev,
739 struct device_attribute *attr, char *sysfsbuf)
740{
741 int ret;
742 char newkey[5];
743 u8 buffer[17];
744 struct sensor_device_attribute_2 *sensor_attr =
745 to_sensor_dev_attr_2(attr);
746
747 newkey[0] = FAN_POSITION[0];
748 newkey[1] = '0' + sensor_attr->index;
749 newkey[2] = FAN_POSITION[2];
750 newkey[3] = FAN_POSITION[3];
751 newkey[4] = 0;
752
753 mutex_lock(&applesmc_lock);
754
755 ret = applesmc_read_key(newkey, buffer, 16);
756 buffer[16] = 0;
757
758 mutex_unlock(&applesmc_lock);
759 if (ret)
760 return ret;
761 else
762 return snprintf(sysfsbuf, PAGE_SIZE, "%s\n", buffer+4);
763}
764
765static ssize_t applesmc_calibrate_show(struct device *dev,
766 struct device_attribute *attr, char *sysfsbuf)
767{
768 return snprintf(sysfsbuf, PAGE_SIZE, "(%d,%d)\n", rest_x, rest_y);
769}
770
771static ssize_t applesmc_calibrate_store(struct device *dev,
772 struct device_attribute *attr, const char *sysfsbuf, size_t count)
773{
774 mutex_lock(&applesmc_lock);
775 applesmc_calibrate();
776 mutex_unlock(&applesmc_lock);
777
778 return count;
779}
780
781/* Store the next backlight value to be written by the work */
782static unsigned int backlight_value;
783
784static void applesmc_backlight_set(struct work_struct *work)
785{
786 u8 buffer[2];
787
788 mutex_lock(&applesmc_lock);
789 buffer[0] = backlight_value;
790 buffer[1] = 0x00;
791 applesmc_write_key(BACKLIGHT_KEY, buffer, 2);
792 mutex_unlock(&applesmc_lock);
793}
794static DECLARE_WORK(backlight_work, &applesmc_backlight_set);
795
796static void applesmc_brightness_set(struct led_classdev *led_cdev,
797 enum led_brightness value)
798{
799 int ret;
800
801 backlight_value = value;
802 ret = queue_work(applesmc_led_wq, &backlight_work);
803
804 if (debug && (!ret))
805 printk(KERN_DEBUG "applesmc: work was already on the queue.\n");
806}
807
808static ssize_t applesmc_key_count_show(struct device *dev,
809 struct device_attribute *attr, char *sysfsbuf)
810{
811 int ret;
812 u8 buffer[4];
813 u32 count;
814
815 mutex_lock(&applesmc_lock);
816
817 ret = applesmc_read_key(KEY_COUNT_KEY, buffer, 4);
818 count = ((u32)buffer[0]<<24) + ((u32)buffer[1]<<16) +
819 ((u32)buffer[2]<<8) + buffer[3];
820
821 mutex_unlock(&applesmc_lock);
822 if (ret)
823 return ret;
824 else
825 return snprintf(sysfsbuf, PAGE_SIZE, "%d\n", count);
826}
827
828static ssize_t applesmc_key_at_index_read_show(struct device *dev,
829 struct device_attribute *attr, char *sysfsbuf)
830{
831 char key[5];
832 char info[6];
833 int ret;
834
835 mutex_lock(&applesmc_lock);
836
837 ret = applesmc_get_key_at_index(key_at_index, key);
838
839 if (ret || !key[0]) {
840 mutex_unlock(&applesmc_lock);
841
842 return -EINVAL;
843 }
844
845 ret = applesmc_get_key_type(key, info);
846
847 if (ret) {
848 mutex_unlock(&applesmc_lock);
849
850 return ret;
851 }
852
853 /*
854 * info[0] maximum value (APPLESMC_MAX_DATA_LENGTH) is much lower than
855 * PAGE_SIZE, so we don't need any checks before writing to sysfsbuf.
856 */
857 ret = applesmc_read_key(key, sysfsbuf, info[0]);
858
859 mutex_unlock(&applesmc_lock);
860
861 if (!ret) {
862 return info[0];
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -0400863 } else {
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700864 return ret;
865 }
866}
867
868static ssize_t applesmc_key_at_index_data_length_show(struct device *dev,
869 struct device_attribute *attr, char *sysfsbuf)
870{
871 char key[5];
872 char info[6];
873 int ret;
874
875 mutex_lock(&applesmc_lock);
876
877 ret = applesmc_get_key_at_index(key_at_index, key);
878
879 if (ret || !key[0]) {
880 mutex_unlock(&applesmc_lock);
881
882 return -EINVAL;
883 }
884
885 ret = applesmc_get_key_type(key, info);
886
887 mutex_unlock(&applesmc_lock);
888
889 if (!ret)
890 return snprintf(sysfsbuf, PAGE_SIZE, "%d\n", info[0]);
891 else
892 return ret;
893}
894
895static ssize_t applesmc_key_at_index_type_show(struct device *dev,
896 struct device_attribute *attr, char *sysfsbuf)
897{
898 char key[5];
899 char info[6];
900 int ret;
901
902 mutex_lock(&applesmc_lock);
903
904 ret = applesmc_get_key_at_index(key_at_index, key);
905
906 if (ret || !key[0]) {
907 mutex_unlock(&applesmc_lock);
908
909 return -EINVAL;
910 }
911
912 ret = applesmc_get_key_type(key, info);
913
914 mutex_unlock(&applesmc_lock);
915
916 if (!ret)
917 return snprintf(sysfsbuf, PAGE_SIZE, "%s\n", info+1);
918 else
919 return ret;
920}
921
922static ssize_t applesmc_key_at_index_name_show(struct device *dev,
923 struct device_attribute *attr, char *sysfsbuf)
924{
925 char key[5];
926 int ret;
927
928 mutex_lock(&applesmc_lock);
929
930 ret = applesmc_get_key_at_index(key_at_index, key);
931
932 mutex_unlock(&applesmc_lock);
933
934 if (!ret && key[0])
935 return snprintf(sysfsbuf, PAGE_SIZE, "%s\n", key);
936 else
937 return -EINVAL;
938}
939
940static ssize_t applesmc_key_at_index_show(struct device *dev,
941 struct device_attribute *attr, char *sysfsbuf)
942{
943 return snprintf(sysfsbuf, PAGE_SIZE, "%d\n", key_at_index);
944}
945
946static ssize_t applesmc_key_at_index_store(struct device *dev,
947 struct device_attribute *attr, const char *sysfsbuf, size_t count)
948{
949 mutex_lock(&applesmc_lock);
950
951 key_at_index = simple_strtoul(sysfsbuf, NULL, 10);
952
953 mutex_unlock(&applesmc_lock);
954
955 return count;
956}
957
958static struct led_classdev applesmc_backlight = {
Richard Purdie6c152be2007-10-31 15:00:07 +0100959 .name = "smc::kbd_backlight",
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700960 .default_trigger = "nand-disk",
961 .brightness_set = applesmc_brightness_set,
962};
963
Nicolas Boichatfa744192007-05-23 13:58:13 -0700964static DEVICE_ATTR(name, 0444, applesmc_name_show, NULL);
965
Nicolas Boichat6f2fad72007-05-08 00:24:52 -0700966static DEVICE_ATTR(position, 0444, applesmc_position_show, NULL);
967static DEVICE_ATTR(calibrate, 0644,
968 applesmc_calibrate_show, applesmc_calibrate_store);
969
970static struct attribute *accelerometer_attributes[] = {
971 &dev_attr_position.attr,
972 &dev_attr_calibrate.attr,
973 NULL
974};
975
976static const struct attribute_group accelerometer_attributes_group =
977 { .attrs = accelerometer_attributes };
978
979static DEVICE_ATTR(light, 0444, applesmc_light_show, NULL);
980
981static DEVICE_ATTR(key_count, 0444, applesmc_key_count_show, NULL);
982static DEVICE_ATTR(key_at_index, 0644,
983 applesmc_key_at_index_show, applesmc_key_at_index_store);
984static DEVICE_ATTR(key_at_index_name, 0444,
985 applesmc_key_at_index_name_show, NULL);
986static DEVICE_ATTR(key_at_index_type, 0444,
987 applesmc_key_at_index_type_show, NULL);
988static DEVICE_ATTR(key_at_index_data_length, 0444,
989 applesmc_key_at_index_data_length_show, NULL);
990static DEVICE_ATTR(key_at_index_data, 0444,
991 applesmc_key_at_index_read_show, NULL);
992
993static struct attribute *key_enumeration_attributes[] = {
994 &dev_attr_key_count.attr,
995 &dev_attr_key_at_index.attr,
996 &dev_attr_key_at_index_name.attr,
997 &dev_attr_key_at_index_type.attr,
998 &dev_attr_key_at_index_data_length.attr,
999 &dev_attr_key_at_index_data.attr,
1000 NULL
1001};
1002
1003static const struct attribute_group key_enumeration_group =
1004 { .attrs = key_enumeration_attributes };
1005
1006/*
1007 * Macro defining SENSOR_DEVICE_ATTR for a fan sysfs entries.
1008 * - show actual speed
1009 * - show/store minimum speed
1010 * - show maximum speed
1011 * - show safe speed
1012 * - show/store target speed
1013 * - show/store manual mode
1014 */
1015#define sysfs_fan_speeds_offset(offset) \
1016static SENSOR_DEVICE_ATTR_2(fan##offset##_input, S_IRUGO, \
1017 applesmc_show_fan_speed, NULL, 0, offset-1); \
1018\
1019static SENSOR_DEVICE_ATTR_2(fan##offset##_min, S_IRUGO | S_IWUSR, \
1020 applesmc_show_fan_speed, applesmc_store_fan_speed, 1, offset-1); \
1021\
1022static SENSOR_DEVICE_ATTR_2(fan##offset##_max, S_IRUGO, \
1023 applesmc_show_fan_speed, NULL, 2, offset-1); \
1024\
1025static SENSOR_DEVICE_ATTR_2(fan##offset##_safe, S_IRUGO, \
1026 applesmc_show_fan_speed, NULL, 3, offset-1); \
1027\
1028static SENSOR_DEVICE_ATTR_2(fan##offset##_output, S_IRUGO | S_IWUSR, \
1029 applesmc_show_fan_speed, applesmc_store_fan_speed, 4, offset-1); \
1030\
1031static SENSOR_DEVICE_ATTR(fan##offset##_manual, S_IRUGO | S_IWUSR, \
1032 applesmc_show_fan_manual, applesmc_store_fan_manual, offset-1); \
1033\
Jean Delvareda4e8ca2007-05-08 20:27:05 -07001034static SENSOR_DEVICE_ATTR(fan##offset##_label, S_IRUGO, \
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001035 applesmc_show_fan_position, NULL, offset-1); \
1036\
1037static struct attribute *fan##offset##_attributes[] = { \
1038 &sensor_dev_attr_fan##offset##_input.dev_attr.attr, \
1039 &sensor_dev_attr_fan##offset##_min.dev_attr.attr, \
1040 &sensor_dev_attr_fan##offset##_max.dev_attr.attr, \
1041 &sensor_dev_attr_fan##offset##_safe.dev_attr.attr, \
1042 &sensor_dev_attr_fan##offset##_output.dev_attr.attr, \
1043 &sensor_dev_attr_fan##offset##_manual.dev_attr.attr, \
Jean Delvareda4e8ca2007-05-08 20:27:05 -07001044 &sensor_dev_attr_fan##offset##_label.dev_attr.attr, \
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001045 NULL \
1046};
1047
1048/*
1049 * Create the needed functions for each fan using the macro defined above
René Rebe8de57702007-10-16 14:19:20 -07001050 * (4 fans are supported)
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001051 */
1052sysfs_fan_speeds_offset(1);
1053sysfs_fan_speeds_offset(2);
René Rebe8de57702007-10-16 14:19:20 -07001054sysfs_fan_speeds_offset(3);
1055sysfs_fan_speeds_offset(4);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001056
1057static const struct attribute_group fan_attribute_groups[] = {
1058 { .attrs = fan1_attributes },
René Rebe8de57702007-10-16 14:19:20 -07001059 { .attrs = fan2_attributes },
1060 { .attrs = fan3_attributes },
1061 { .attrs = fan4_attributes },
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001062};
1063
1064/*
1065 * Temperature sensors sysfs entries.
1066 */
1067static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO,
1068 applesmc_show_temperature, NULL, 0);
1069static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO,
1070 applesmc_show_temperature, NULL, 1);
1071static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO,
1072 applesmc_show_temperature, NULL, 2);
1073static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO,
1074 applesmc_show_temperature, NULL, 3);
1075static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO,
1076 applesmc_show_temperature, NULL, 4);
1077static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO,
1078 applesmc_show_temperature, NULL, 5);
1079static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO,
1080 applesmc_show_temperature, NULL, 6);
1081static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO,
1082 applesmc_show_temperature, NULL, 7);
1083static SENSOR_DEVICE_ATTR(temp9_input, S_IRUGO,
1084 applesmc_show_temperature, NULL, 8);
1085static SENSOR_DEVICE_ATTR(temp10_input, S_IRUGO,
1086 applesmc_show_temperature, NULL, 9);
1087static SENSOR_DEVICE_ATTR(temp11_input, S_IRUGO,
1088 applesmc_show_temperature, NULL, 10);
1089static SENSOR_DEVICE_ATTR(temp12_input, S_IRUGO,
1090 applesmc_show_temperature, NULL, 11);
René Rebe8de57702007-10-16 14:19:20 -07001091static SENSOR_DEVICE_ATTR(temp13_input, S_IRUGO,
1092 applesmc_show_temperature, NULL, 12);
1093static SENSOR_DEVICE_ATTR(temp14_input, S_IRUGO,
1094 applesmc_show_temperature, NULL, 13);
1095static SENSOR_DEVICE_ATTR(temp15_input, S_IRUGO,
1096 applesmc_show_temperature, NULL, 14);
1097static SENSOR_DEVICE_ATTR(temp16_input, S_IRUGO,
1098 applesmc_show_temperature, NULL, 15);
1099static SENSOR_DEVICE_ATTR(temp17_input, S_IRUGO,
1100 applesmc_show_temperature, NULL, 16);
1101static SENSOR_DEVICE_ATTR(temp18_input, S_IRUGO,
1102 applesmc_show_temperature, NULL, 17);
1103static SENSOR_DEVICE_ATTR(temp19_input, S_IRUGO,
1104 applesmc_show_temperature, NULL, 18);
1105static SENSOR_DEVICE_ATTR(temp20_input, S_IRUGO,
1106 applesmc_show_temperature, NULL, 19);
1107static SENSOR_DEVICE_ATTR(temp21_input, S_IRUGO,
1108 applesmc_show_temperature, NULL, 20);
1109static SENSOR_DEVICE_ATTR(temp22_input, S_IRUGO,
1110 applesmc_show_temperature, NULL, 21);
1111static SENSOR_DEVICE_ATTR(temp23_input, S_IRUGO,
1112 applesmc_show_temperature, NULL, 22);
1113static SENSOR_DEVICE_ATTR(temp24_input, S_IRUGO,
1114 applesmc_show_temperature, NULL, 23);
1115static SENSOR_DEVICE_ATTR(temp25_input, S_IRUGO,
1116 applesmc_show_temperature, NULL, 24);
1117static SENSOR_DEVICE_ATTR(temp26_input, S_IRUGO,
1118 applesmc_show_temperature, NULL, 25);
1119static SENSOR_DEVICE_ATTR(temp27_input, S_IRUGO,
1120 applesmc_show_temperature, NULL, 26);
1121static SENSOR_DEVICE_ATTR(temp28_input, S_IRUGO,
1122 applesmc_show_temperature, NULL, 27);
1123static SENSOR_DEVICE_ATTR(temp29_input, S_IRUGO,
1124 applesmc_show_temperature, NULL, 28);
1125static SENSOR_DEVICE_ATTR(temp30_input, S_IRUGO,
1126 applesmc_show_temperature, NULL, 29);
1127static SENSOR_DEVICE_ATTR(temp31_input, S_IRUGO,
1128 applesmc_show_temperature, NULL, 30);
1129static SENSOR_DEVICE_ATTR(temp32_input, S_IRUGO,
1130 applesmc_show_temperature, NULL, 31);
1131static SENSOR_DEVICE_ATTR(temp33_input, S_IRUGO,
1132 applesmc_show_temperature, NULL, 32);
1133static SENSOR_DEVICE_ATTR(temp34_input, S_IRUGO,
1134 applesmc_show_temperature, NULL, 33);
1135static SENSOR_DEVICE_ATTR(temp35_input, S_IRUGO,
1136 applesmc_show_temperature, NULL, 34);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001137
1138static struct attribute *temperature_attributes[] = {
1139 &sensor_dev_attr_temp1_input.dev_attr.attr,
1140 &sensor_dev_attr_temp2_input.dev_attr.attr,
1141 &sensor_dev_attr_temp3_input.dev_attr.attr,
1142 &sensor_dev_attr_temp4_input.dev_attr.attr,
1143 &sensor_dev_attr_temp5_input.dev_attr.attr,
1144 &sensor_dev_attr_temp6_input.dev_attr.attr,
1145 &sensor_dev_attr_temp7_input.dev_attr.attr,
1146 &sensor_dev_attr_temp8_input.dev_attr.attr,
1147 &sensor_dev_attr_temp9_input.dev_attr.attr,
1148 &sensor_dev_attr_temp10_input.dev_attr.attr,
1149 &sensor_dev_attr_temp11_input.dev_attr.attr,
1150 &sensor_dev_attr_temp12_input.dev_attr.attr,
René Rebe8de57702007-10-16 14:19:20 -07001151 &sensor_dev_attr_temp13_input.dev_attr.attr,
1152 &sensor_dev_attr_temp14_input.dev_attr.attr,
1153 &sensor_dev_attr_temp15_input.dev_attr.attr,
1154 &sensor_dev_attr_temp16_input.dev_attr.attr,
1155 &sensor_dev_attr_temp17_input.dev_attr.attr,
1156 &sensor_dev_attr_temp18_input.dev_attr.attr,
1157 &sensor_dev_attr_temp19_input.dev_attr.attr,
1158 &sensor_dev_attr_temp20_input.dev_attr.attr,
1159 &sensor_dev_attr_temp21_input.dev_attr.attr,
1160 &sensor_dev_attr_temp22_input.dev_attr.attr,
1161 &sensor_dev_attr_temp23_input.dev_attr.attr,
1162 &sensor_dev_attr_temp24_input.dev_attr.attr,
1163 &sensor_dev_attr_temp25_input.dev_attr.attr,
1164 &sensor_dev_attr_temp26_input.dev_attr.attr,
1165 &sensor_dev_attr_temp27_input.dev_attr.attr,
1166 &sensor_dev_attr_temp28_input.dev_attr.attr,
1167 &sensor_dev_attr_temp29_input.dev_attr.attr,
1168 &sensor_dev_attr_temp30_input.dev_attr.attr,
1169 &sensor_dev_attr_temp31_input.dev_attr.attr,
1170 &sensor_dev_attr_temp32_input.dev_attr.attr,
1171 &sensor_dev_attr_temp33_input.dev_attr.attr,
1172 &sensor_dev_attr_temp34_input.dev_attr.attr,
1173 &sensor_dev_attr_temp35_input.dev_attr.attr,
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001174 NULL
1175};
1176
1177static const struct attribute_group temperature_attributes_group =
1178 { .attrs = temperature_attributes };
1179
1180/* Module stuff */
1181
1182/*
1183 * applesmc_dmi_match - found a match. return one, short-circuiting the hunt.
1184 */
Jeff Garzik18552562007-10-03 15:15:40 -04001185static int applesmc_dmi_match(const struct dmi_system_id *id)
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001186{
1187 int i = 0;
1188 struct dmi_match_data* dmi_data = id->driver_data;
1189 printk(KERN_INFO "applesmc: %s detected:\n", id->ident);
1190 applesmc_accelerometer = dmi_data->accelerometer;
1191 printk(KERN_INFO "applesmc: - Model %s accelerometer\n",
1192 applesmc_accelerometer ? "with" : "without");
1193 applesmc_light = dmi_data->light;
1194 printk(KERN_INFO "applesmc: - Model %s light sensors and backlight\n",
1195 applesmc_light ? "with" : "without");
1196
1197 applesmc_temperature_set = dmi_data->temperature_set;
1198 while (temperature_sensors_sets[applesmc_temperature_set][i] != NULL)
1199 i++;
1200 printk(KERN_INFO "applesmc: - Model with %d temperature sensors\n", i);
1201 return 1;
1202}
1203
1204/* Create accelerometer ressources */
1205static int applesmc_create_accelerometer(void)
1206{
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -04001207 struct input_dev *idev;
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001208 int ret;
1209
1210 ret = sysfs_create_group(&pdev->dev.kobj,
1211 &accelerometer_attributes_group);
1212 if (ret)
1213 goto out;
1214
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -04001215 applesmc_idev = input_allocate_polled_device();
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001216 if (!applesmc_idev) {
1217 ret = -ENOMEM;
1218 goto out_sysfs;
1219 }
1220
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -04001221 applesmc_idev->poll = applesmc_idev_poll;
1222 applesmc_idev->poll_interval = APPLESMC_POLL_INTERVAL;
1223
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001224 /* initial calibrate for the input device */
1225 applesmc_calibrate();
1226
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -04001227 /* initialize the input device */
1228 idev = applesmc_idev->input;
1229 idev->name = "applesmc";
1230 idev->id.bustype = BUS_HOST;
1231 idev->dev.parent = &pdev->dev;
Jiri Slaby7b19ada2007-10-18 23:40:32 -07001232 idev->evbit[0] = BIT_MASK(EV_ABS);
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -04001233 input_set_abs_params(idev, ABS_X,
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001234 -256, 256, APPLESMC_INPUT_FUZZ, APPLESMC_INPUT_FLAT);
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -04001235 input_set_abs_params(idev, ABS_Y,
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001236 -256, 256, APPLESMC_INPUT_FUZZ, APPLESMC_INPUT_FLAT);
1237
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -04001238 ret = input_register_polled_device(applesmc_idev);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001239 if (ret)
1240 goto out_idev;
1241
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001242 return 0;
1243
1244out_idev:
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -04001245 input_free_polled_device(applesmc_idev);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001246
1247out_sysfs:
1248 sysfs_remove_group(&pdev->dev.kobj, &accelerometer_attributes_group);
1249
1250out:
1251 printk(KERN_WARNING "applesmc: driver init failed (ret=%d)!\n", ret);
1252 return ret;
1253}
1254
1255/* Release all ressources used by the accelerometer */
1256static void applesmc_release_accelerometer(void)
1257{
Dmitry Torokhovd5cf2b92007-09-26 00:01:35 -04001258 input_unregister_polled_device(applesmc_idev);
1259 input_free_polled_device(applesmc_idev);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001260 sysfs_remove_group(&pdev->dev.kobj, &accelerometer_attributes_group);
1261}
1262
1263static __initdata struct dmi_match_data applesmc_dmi_data[] = {
1264/* MacBook Pro: accelerometer, backlight and temperature set 0 */
1265 { .accelerometer = 1, .light = 1, .temperature_set = 0 },
Riki Oktariantocd19ba12008-02-04 23:41:58 -08001266/* MacBook2: accelerometer and temperature set 1 */
Martin Szulecki1bed24b2007-07-09 11:41:36 -07001267 { .accelerometer = 1, .light = 0, .temperature_set = 1 },
Riki Oktariantocd19ba12008-02-04 23:41:58 -08001268/* MacBook: accelerometer and temperature set 2 */
1269 { .accelerometer = 1, .light = 0, .temperature_set = 2 },
1270/* MacMini: temperature set 3 */
René Rebe8de57702007-10-16 14:19:20 -07001271 { .accelerometer = 0, .light = 0, .temperature_set = 3 },
Riki Oktariantocd19ba12008-02-04 23:41:58 -08001272/* MacPro: temperature set 4 */
1273 { .accelerometer = 0, .light = 0, .temperature_set = 4 },
Roberto De Ioris9f86f282008-08-15 00:40:30 -07001274/* iMac: temperature set 5 */
1275 { .accelerometer = 0, .light = 0, .temperature_set = 5 },
Guilherme M. Schroederf91a79f2008-08-15 00:40:32 -07001276/* MacBook3: accelerometer and temperature set 6 */
1277 { .accelerometer = 1, .light = 0, .temperature_set = 6 },
Henrik Rydbergf5274c92008-10-18 20:27:40 -07001278/* MacBook Air: accelerometer, backlight and temperature set 7 */
1279 { .accelerometer = 1, .light = 1, .temperature_set = 7 },
Henrik Rydbergd7549902008-10-18 20:27:41 -07001280/* MacBook Pro 4: accelerometer, backlight and temperature set 8 */
1281 { .accelerometer = 1, .light = 1, .temperature_set = 8 },
Henrik Rydberg07e8dbd2008-10-18 20:27:42 -07001282/* MacBook Pro 3: accelerometer, backlight and temperature set 9 */
1283 { .accelerometer = 1, .light = 1, .temperature_set = 9 },
Henrik Rydberg6e3530f2008-11-06 12:53:19 -08001284/* iMac 5: light sensor only, temperature set 10 */
1285 { .accelerometer = 0, .light = 0, .temperature_set = 10 },
Henrik Rydberg181209a12008-11-06 12:53:20 -08001286/* MacBook 5: accelerometer, backlight and temperature set 11 */
1287 { .accelerometer = 1, .light = 1, .temperature_set = 11 },
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001288};
1289
1290/* Note that DMI_MATCH(...,"MacBook") will match "MacBookPro1,1".
1291 * So we need to put "Apple MacBook Pro" before "Apple MacBook". */
1292static __initdata struct dmi_system_id applesmc_whitelist[] = {
Henrik Rydbergf5274c92008-10-18 20:27:40 -07001293 { applesmc_dmi_match, "Apple MacBook Air", {
1294 DMI_MATCH(DMI_BOARD_VENDOR, "Apple"),
1295 DMI_MATCH(DMI_PRODUCT_NAME, "MacBookAir") },
Andrew Morton7b5e3cb2008-10-18 20:27:41 -07001296 &applesmc_dmi_data[7]},
Henrik Rydbergd7549902008-10-18 20:27:41 -07001297 { applesmc_dmi_match, "Apple MacBook Pro 4", {
1298 DMI_MATCH(DMI_BOARD_VENDOR, "Apple"),
1299 DMI_MATCH(DMI_PRODUCT_NAME, "MacBookPro4") },
1300 &applesmc_dmi_data[8]},
Henrik Rydberg07e8dbd2008-10-18 20:27:42 -07001301 { applesmc_dmi_match, "Apple MacBook Pro 3", {
1302 DMI_MATCH(DMI_BOARD_VENDOR, "Apple"),
1303 DMI_MATCH(DMI_PRODUCT_NAME, "MacBookPro3") },
1304 &applesmc_dmi_data[9]},
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001305 { applesmc_dmi_match, "Apple MacBook Pro", {
1306 DMI_MATCH(DMI_BOARD_VENDOR,"Apple"),
1307 DMI_MATCH(DMI_PRODUCT_NAME,"MacBookPro") },
Andrew Morton7b5e3cb2008-10-18 20:27:41 -07001308 &applesmc_dmi_data[0]},
Guilherme M. Schroederf91a79f2008-08-15 00:40:32 -07001309 { applesmc_dmi_match, "Apple MacBook (v2)", {
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001310 DMI_MATCH(DMI_BOARD_VENDOR,"Apple"),
Riki Oktariantocd19ba12008-02-04 23:41:58 -08001311 DMI_MATCH(DMI_PRODUCT_NAME,"MacBook2") },
Andrew Morton7b5e3cb2008-10-18 20:27:41 -07001312 &applesmc_dmi_data[1]},
Guilherme M. Schroederf91a79f2008-08-15 00:40:32 -07001313 { applesmc_dmi_match, "Apple MacBook (v3)", {
1314 DMI_MATCH(DMI_BOARD_VENDOR,"Apple"),
1315 DMI_MATCH(DMI_PRODUCT_NAME,"MacBook3") },
Andrew Morton7b5e3cb2008-10-18 20:27:41 -07001316 &applesmc_dmi_data[6]},
Henrik Rydberg181209a12008-11-06 12:53:20 -08001317 { applesmc_dmi_match, "Apple MacBook 5", {
1318 DMI_MATCH(DMI_BOARD_VENDOR, "Apple"),
1319 DMI_MATCH(DMI_PRODUCT_NAME, "MacBook5") },
1320 &applesmc_dmi_data[11]},
Riki Oktariantocd19ba12008-02-04 23:41:58 -08001321 { applesmc_dmi_match, "Apple MacBook", {
1322 DMI_MATCH(DMI_BOARD_VENDOR,"Apple"),
1323 DMI_MATCH(DMI_PRODUCT_NAME,"MacBook") },
Andrew Morton7b5e3cb2008-10-18 20:27:41 -07001324 &applesmc_dmi_data[2]},
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001325 { applesmc_dmi_match, "Apple Macmini", {
1326 DMI_MATCH(DMI_BOARD_VENDOR,"Apple"),
1327 DMI_MATCH(DMI_PRODUCT_NAME,"Macmini") },
Andrew Morton7b5e3cb2008-10-18 20:27:41 -07001328 &applesmc_dmi_data[3]},
René Rebe8de57702007-10-16 14:19:20 -07001329 { applesmc_dmi_match, "Apple MacPro2", {
1330 DMI_MATCH(DMI_BOARD_VENDOR,"Apple"),
1331 DMI_MATCH(DMI_PRODUCT_NAME,"MacPro2") },
Andrew Morton7b5e3cb2008-10-18 20:27:41 -07001332 &applesmc_dmi_data[4]},
Henrik Rydberg6e3530f2008-11-06 12:53:19 -08001333 { applesmc_dmi_match, "Apple iMac 5", {
1334 DMI_MATCH(DMI_BOARD_VENDOR, "Apple"),
1335 DMI_MATCH(DMI_PRODUCT_NAME, "iMac5") },
1336 &applesmc_dmi_data[10]},
Roberto De Ioris9f86f282008-08-15 00:40:30 -07001337 { applesmc_dmi_match, "Apple iMac", {
1338 DMI_MATCH(DMI_BOARD_VENDOR,"Apple"),
1339 DMI_MATCH(DMI_PRODUCT_NAME,"iMac") },
Andrew Morton7b5e3cb2008-10-18 20:27:41 -07001340 &applesmc_dmi_data[5]},
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001341 { .ident = NULL }
1342};
1343
1344static int __init applesmc_init(void)
1345{
1346 int ret;
1347 int count;
1348 int i;
1349
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001350 if (!dmi_check_system(applesmc_whitelist)) {
1351 printk(KERN_WARNING "applesmc: supported laptop not found!\n");
1352 ret = -ENODEV;
1353 goto out;
1354 }
1355
1356 if (!request_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS,
1357 "applesmc")) {
1358 ret = -ENXIO;
1359 goto out;
1360 }
1361
1362 ret = platform_driver_register(&applesmc_driver);
1363 if (ret)
1364 goto out_region;
1365
Jean Delvareddfbf2a2007-05-08 20:27:04 -07001366 pdev = platform_device_register_simple("applesmc", APPLESMC_DATA_PORT,
1367 NULL, 0);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001368 if (IS_ERR(pdev)) {
1369 ret = PTR_ERR(pdev);
1370 goto out_driver;
1371 }
1372
Nicolas Boichatfa744192007-05-23 13:58:13 -07001373 ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_name.attr);
Nicolas Boichat6996abf2007-05-27 22:17:43 +02001374 if (ret)
1375 goto out_device;
Nicolas Boichatfa744192007-05-23 13:58:13 -07001376
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001377 /* Create key enumeration sysfs files */
1378 ret = sysfs_create_group(&pdev->dev.kobj, &key_enumeration_group);
1379 if (ret)
Nicolas Boichat6996abf2007-05-27 22:17:43 +02001380 goto out_name;
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001381
1382 /* create fan files */
1383 count = applesmc_get_fan_count();
1384 if (count < 0) {
1385 printk(KERN_ERR "applesmc: Cannot get the number of fans.\n");
1386 } else {
1387 printk(KERN_INFO "applesmc: %d fans found.\n", count);
1388
1389 switch (count) {
1390 default:
René Rebe8de57702007-10-16 14:19:20 -07001391 printk(KERN_WARNING "applesmc: More than 4 fans found,"
1392 " but at most 4 fans are supported"
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001393 " by the driver.\n");
René Rebe8de57702007-10-16 14:19:20 -07001394 case 4:
1395 ret = sysfs_create_group(&pdev->dev.kobj,
1396 &fan_attribute_groups[3]);
1397 if (ret)
1398 goto out_key_enumeration;
1399 case 3:
1400 ret = sysfs_create_group(&pdev->dev.kobj,
1401 &fan_attribute_groups[2]);
1402 if (ret)
1403 goto out_key_enumeration;
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001404 case 2:
1405 ret = sysfs_create_group(&pdev->dev.kobj,
1406 &fan_attribute_groups[1]);
1407 if (ret)
1408 goto out_key_enumeration;
1409 case 1:
1410 ret = sysfs_create_group(&pdev->dev.kobj,
1411 &fan_attribute_groups[0]);
1412 if (ret)
1413 goto out_fan_1;
1414 case 0:
1415 ;
1416 }
1417 }
1418
1419 for (i = 0;
1420 temperature_sensors_sets[applesmc_temperature_set][i] != NULL;
1421 i++) {
1422 if (temperature_attributes[i] == NULL) {
1423 printk(KERN_ERR "applesmc: More temperature sensors "
1424 "in temperature_sensors_sets (at least %i)"
1425 "than available sysfs files in "
1426 "temperature_attributes (%i), please report "
1427 "this bug.\n", i, i-1);
1428 goto out_temperature;
1429 }
1430 ret = sysfs_create_file(&pdev->dev.kobj,
1431 temperature_attributes[i]);
1432 if (ret)
1433 goto out_temperature;
1434 }
1435
1436 if (applesmc_accelerometer) {
1437 ret = applesmc_create_accelerometer();
1438 if (ret)
1439 goto out_temperature;
1440 }
1441
1442 if (applesmc_light) {
1443 /* Add light sensor file */
1444 ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_light.attr);
1445 if (ret)
1446 goto out_accelerometer;
1447
1448 /* Create the workqueue */
1449 applesmc_led_wq = create_singlethread_workqueue("applesmc-led");
1450 if (!applesmc_led_wq) {
1451 ret = -ENOMEM;
1452 goto out_light_sysfs;
1453 }
1454
1455 /* register as a led device */
1456 ret = led_classdev_register(&pdev->dev, &applesmc_backlight);
1457 if (ret < 0)
1458 goto out_light_wq;
1459 }
1460
Tony Jones1beeffe2007-08-20 13:46:20 -07001461 hwmon_dev = hwmon_device_register(&pdev->dev);
1462 if (IS_ERR(hwmon_dev)) {
1463 ret = PTR_ERR(hwmon_dev);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001464 goto out_light_ledclass;
1465 }
1466
1467 printk(KERN_INFO "applesmc: driver successfully loaded.\n");
1468
1469 return 0;
1470
1471out_light_ledclass:
1472 if (applesmc_light)
1473 led_classdev_unregister(&applesmc_backlight);
1474out_light_wq:
1475 if (applesmc_light)
1476 destroy_workqueue(applesmc_led_wq);
1477out_light_sysfs:
1478 if (applesmc_light)
1479 sysfs_remove_file(&pdev->dev.kobj, &dev_attr_light.attr);
1480out_accelerometer:
1481 if (applesmc_accelerometer)
1482 applesmc_release_accelerometer();
1483out_temperature:
1484 sysfs_remove_group(&pdev->dev.kobj, &temperature_attributes_group);
1485 sysfs_remove_group(&pdev->dev.kobj, &fan_attribute_groups[0]);
1486out_fan_1:
1487 sysfs_remove_group(&pdev->dev.kobj, &fan_attribute_groups[1]);
1488out_key_enumeration:
1489 sysfs_remove_group(&pdev->dev.kobj, &key_enumeration_group);
Nicolas Boichat6996abf2007-05-27 22:17:43 +02001490out_name:
1491 sysfs_remove_file(&pdev->dev.kobj, &dev_attr_name.attr);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001492out_device:
1493 platform_device_unregister(pdev);
1494out_driver:
1495 platform_driver_unregister(&applesmc_driver);
1496out_region:
1497 release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS);
1498out:
1499 printk(KERN_WARNING "applesmc: driver init failed (ret=%d)!\n", ret);
1500 return ret;
1501}
1502
1503static void __exit applesmc_exit(void)
1504{
Tony Jones1beeffe2007-08-20 13:46:20 -07001505 hwmon_device_unregister(hwmon_dev);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001506 if (applesmc_light) {
1507 led_classdev_unregister(&applesmc_backlight);
1508 destroy_workqueue(applesmc_led_wq);
1509 sysfs_remove_file(&pdev->dev.kobj, &dev_attr_light.attr);
1510 }
1511 if (applesmc_accelerometer)
1512 applesmc_release_accelerometer();
1513 sysfs_remove_group(&pdev->dev.kobj, &temperature_attributes_group);
1514 sysfs_remove_group(&pdev->dev.kobj, &fan_attribute_groups[0]);
1515 sysfs_remove_group(&pdev->dev.kobj, &fan_attribute_groups[1]);
1516 sysfs_remove_group(&pdev->dev.kobj, &key_enumeration_group);
Nicolas Boichat6996abf2007-05-27 22:17:43 +02001517 sysfs_remove_file(&pdev->dev.kobj, &dev_attr_name.attr);
Nicolas Boichat6f2fad72007-05-08 00:24:52 -07001518 platform_device_unregister(pdev);
1519 platform_driver_unregister(&applesmc_driver);
1520 release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS);
1521
1522 printk(KERN_INFO "applesmc: driver unloaded.\n");
1523}
1524
1525module_init(applesmc_init);
1526module_exit(applesmc_exit);
1527
1528MODULE_AUTHOR("Nicolas Boichat");
1529MODULE_DESCRIPTION("Apple SMC");
1530MODULE_LICENSE("GPL v2");