blob: d509468a58a2fcdbd24d8277eab496e37c6b4ea5 [file] [log] [blame]
Stelian Pop7f09c432007-01-13 23:04:31 +01001/*
2 * ACPI Sony Notebook Control Driver (SNC)
3 *
4 * Copyright (C) 2004-2005 Stelian Pop <stelian@popies.net>
5 *
6 * Parts of this driver inspired from asus_acpi.c and ibm_acpi.c
7 * which are copyrighted by their respective authors.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 *
23 */
24
25#include <linux/kernel.h>
26#include <linux/module.h>
27#include <linux/moduleparam.h>
28#include <linux/init.h>
29#include <linux/types.h>
Alessandro Guido50f62af2007-01-13 23:04:34 +010030#include <linux/backlight.h>
31#include <linux/err.h>
Stelian Pop7f09c432007-01-13 23:04:31 +010032#include <acpi/acpi_drivers.h>
33#include <acpi/acpi_bus.h>
34#include <asm/uaccess.h>
35
36#define ACPI_SNC_CLASS "sony"
37#define ACPI_SNC_HID "SNY5001"
Alessandro Guido50f62af2007-01-13 23:04:34 +010038#define ACPI_SNC_DRIVER_NAME "ACPI Sony Notebook Control Driver v0.3"
39
40/* the device uses 1-based values, while the backlight subsystem uses
41 0-based values */
42#define SONY_MAX_BRIGHTNESS 8
Stelian Pop7f09c432007-01-13 23:04:31 +010043
44#define LOG_PFX KERN_WARNING "sony_acpi: "
45
46MODULE_AUTHOR("Stelian Pop");
47MODULE_DESCRIPTION(ACPI_SNC_DRIVER_NAME);
48MODULE_LICENSE("GPL");
49
50static int debug;
51module_param(debug, int, 0);
52MODULE_PARM_DESC(debug, "set this to 1 (and RTFM) if you want to help "
53 "the development of this driver");
54
Stelian Pop7f09c432007-01-13 23:04:31 +010055static acpi_handle sony_acpi_handle;
56static struct proc_dir_entry *sony_acpi_dir;
57
Alessandro Guido50f62af2007-01-13 23:04:34 +010058static int sony_backlight_update_status(struct backlight_device *bd);
59static int sony_backlight_get_brightness(struct backlight_device *bd);
60static struct backlight_device *sony_backlight_device;
61static struct backlight_properties sony_backlight_properties = {
62 .owner = THIS_MODULE,
63 .update_status = sony_backlight_update_status,
64 .get_brightness = sony_backlight_get_brightness,
65 .max_brightness = SONY_MAX_BRIGHTNESS - 1,
66};
67
Stelian Pop7f09c432007-01-13 23:04:31 +010068static struct sony_acpi_value {
69 char *name; /* name of the entry */
70 struct proc_dir_entry *proc; /* /proc entry */
71 char *acpiget;/* name of the ACPI get function */
72 char *acpiset;/* name of the ACPI get function */
73 int min; /* minimum allowed value or -1 */
74 int max; /* maximum allowed value or -1 */
Andrew Morton3f4f4612007-01-13 23:04:32 +010075 int value; /* current setting */
76 int valid; /* Has ever been set */
Stelian Pop7f09c432007-01-13 23:04:31 +010077 int debug; /* active only in debug mode ? */
78} sony_acpi_values[] = {
79 {
Stelian Pop7f09c432007-01-13 23:04:31 +010080 .name = "brightness_default",
81 .acpiget = "GPBR",
82 .acpiset = "SPBR",
83 .min = 1,
Alessandro Guido50f62af2007-01-13 23:04:34 +010084 .max = SONY_MAX_BRIGHTNESS,
Stelian Pop7f09c432007-01-13 23:04:31 +010085 .debug = 0,
86 },
87 {
88 .name = "fnkey",
89 .acpiget = "GHKE",
90 .debug = 0,
91 },
92 {
93 .name = "cdpower",
94 .acpiget = "GCDP",
95 .acpiset = "SCDP",
96 .min = -1,
97 .max = -1,
98 .debug = 0,
99 },
100 {
101 .name = "PID",
102 .acpiget = "GPID",
103 .debug = 1,
104 },
105 {
106 .name = "CTR",
107 .acpiget = "GCTR",
108 .acpiset = "SCTR",
109 .min = -1,
110 .max = -1,
111 .debug = 1,
112 },
113 {
114 .name = "PCR",
115 .acpiget = "GPCR",
116 .acpiset = "SPCR",
117 .min = -1,
118 .max = -1,
119 .debug = 1,
120 },
121 {
122 .name = "CMI",
123 .acpiget = "GCMI",
124 .acpiset = "SCMI",
125 .min = -1,
126 .max = -1,
127 .debug = 1,
128 },
129 {
130 .name = NULL,
131 }
132};
133
134static int acpi_callgetfunc(acpi_handle handle, char *name, int *result)
135{
136 struct acpi_buffer output;
137 union acpi_object out_obj;
138 acpi_status status;
139
140 output.length = sizeof(out_obj);
141 output.pointer = &out_obj;
142
143 status = acpi_evaluate_object(handle, name, NULL, &output);
144 if ((status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER)) {
145 *result = out_obj.integer.value;
146 return 0;
147 }
148
149 printk(LOG_PFX "acpi_callreadfunc failed\n");
150
151 return -1;
152}
153
154static int acpi_callsetfunc(acpi_handle handle, char *name, int value,
155 int *result)
156{
157 struct acpi_object_list params;
158 union acpi_object in_obj;
159 struct acpi_buffer output;
160 union acpi_object out_obj;
161 acpi_status status;
162
163 params.count = 1;
164 params.pointer = &in_obj;
165 in_obj.type = ACPI_TYPE_INTEGER;
166 in_obj.integer.value = value;
167
168 output.length = sizeof(out_obj);
169 output.pointer = &out_obj;
170
171 status = acpi_evaluate_object(handle, name, &params, &output);
172 if (status == AE_OK) {
173 if (result != NULL) {
174 if (out_obj.type != ACPI_TYPE_INTEGER) {
175 printk(LOG_PFX "acpi_evaluate_object bad "
176 "return type\n");
177 return -1;
178 }
179 *result = out_obj.integer.value;
180 }
181 return 0;
182 }
183
184 printk(LOG_PFX "acpi_evaluate_object failed\n");
185
186 return -1;
187}
188
189static int parse_buffer(const char __user *buffer, unsigned long count,
190 int *val) {
191 char s[32];
192 int ret;
193
194 if (count > 31)
195 return -EINVAL;
196 if (copy_from_user(s, buffer, count))
197 return -EFAULT;
198 s[count] = '\0';
199 ret = simple_strtoul(s, NULL, 10);
200 *val = ret;
201 return 0;
202}
203
204static int sony_acpi_read(char* page, char** start, off_t off, int count,
205 int* eof, void *data)
206{
207 struct sony_acpi_value *item = data;
208 int value;
209
210 if (!item->acpiget)
211 return -EIO;
212
213 if (acpi_callgetfunc(sony_acpi_handle, item->acpiget, &value) < 0)
214 return -EIO;
215
216 return sprintf(page, "%d\n", value);
217}
218
219static int sony_acpi_write(struct file *file, const char __user *buffer,
220 unsigned long count, void *data)
221{
222 struct sony_acpi_value *item = data;
223 int result;
224 int value;
225
226 if (!item->acpiset)
227 return -EIO;
228
229 if ((result = parse_buffer(buffer, count, &value)) < 0)
230 return result;
231
232 if (item->min != -1 && value < item->min)
233 return -EINVAL;
234 if (item->max != -1 && value > item->max)
235 return -EINVAL;
236
237 if (acpi_callsetfunc(sony_acpi_handle, item->acpiset, value, NULL) < 0)
238 return -EIO;
Andrew Morton3f4f4612007-01-13 23:04:32 +0100239 item->value = value;
240 item->valid = 1;
Stelian Pop7f09c432007-01-13 23:04:31 +0100241 return count;
242}
243
Andrew Mortonfac35062007-01-13 23:04:33 +0100244static int sony_acpi_resume(struct acpi_device *device)
Andrew Morton3f4f4612007-01-13 23:04:32 +0100245{
246 struct sony_acpi_value *item;
247
248 for (item = sony_acpi_values; item->name; item++) {
249 int ret;
250
251 if (!item->valid)
252 continue;
253 ret = acpi_callsetfunc(sony_acpi_handle, item->acpiset,
254 item->value, NULL);
255 if (ret < 0) {
256 printk("%s: %d\n", __FUNCTION__, ret);
257 break;
258 }
259 }
260 return 0;
261}
262
Stelian Pop7f09c432007-01-13 23:04:31 +0100263static void sony_acpi_notify(acpi_handle handle, u32 event, void *data)
264{
265 printk(LOG_PFX "sony_acpi_notify\n");
266}
267
268static acpi_status sony_walk_callback(acpi_handle handle, u32 level,
269 void *context, void **return_value)
270{
271 struct acpi_namespace_node *node;
272 union acpi_operand_object *operand;
273
274 node = (struct acpi_namespace_node *) handle;
275 operand = (union acpi_operand_object *) node->object;
276
277 printk(LOG_PFX "method: name: %4.4s, args %X\n", node->name.ascii,
278 (u32) operand->method.param_count);
279
280 return AE_OK;
281}
282
283static int sony_acpi_add(struct acpi_device *device)
284{
285 acpi_status status;
286 int result;
Alessandro Guido50f62af2007-01-13 23:04:34 +0100287 acpi_handle handle;
Stelian Pop7f09c432007-01-13 23:04:31 +0100288 struct sony_acpi_value *item;
289
290 sony_acpi_handle = device->handle;
291
292 acpi_driver_data(device) = NULL;
293 acpi_device_dir(device) = sony_acpi_dir;
294
295 if (debug) {
296 status = acpi_walk_namespace(ACPI_TYPE_METHOD, sony_acpi_handle,
297 1, sony_walk_callback, NULL, NULL);
298 if (ACPI_FAILURE(status)) {
299 printk(LOG_PFX "unable to walk acpi resources\n");
300 result = -ENODEV;
301 goto outwalk;
302 }
303
304 status = acpi_install_notify_handler(sony_acpi_handle,
305 ACPI_DEVICE_NOTIFY,
306 sony_acpi_notify,
307 NULL);
308 if (ACPI_FAILURE(status)) {
309 printk(LOG_PFX "unable to install notify handler\n");
310 result = -ENODEV;
311 goto outnotify;
312 }
313 }
314
Alessandro Guido50f62af2007-01-13 23:04:34 +0100315 if (ACPI_SUCCESS(acpi_get_handle(sony_acpi_handle, "GBRT", &handle))) {
316 sony_backlight_device = backlight_device_register("sony", NULL,
317 &sony_backlight_properties);
318 if (IS_ERR(sony_backlight_device)) {
319 printk(LOG_PFX "unable to register backlight device\n");
320 }
321 }
Stelian Pop7f09c432007-01-13 23:04:31 +0100322
Alessandro Guido50f62af2007-01-13 23:04:34 +0100323 for (item = sony_acpi_values; item->name; ++item) {
Stelian Pop7f09c432007-01-13 23:04:31 +0100324 if (!debug && item->debug)
325 continue;
326
327 if (item->acpiget &&
328 ACPI_FAILURE(acpi_get_handle(sony_acpi_handle,
329 item->acpiget, &handle)))
330 continue;
331
332 if (item->acpiset &&
333 ACPI_FAILURE(acpi_get_handle(sony_acpi_handle,
334 item->acpiset, &handle)))
335 continue;
336
337 item->proc = create_proc_entry(item->name, 0600,
338 acpi_device_dir(device));
339 if (!item->proc) {
340 printk(LOG_PFX "unable to create proc entry\n");
341 result = -EIO;
342 goto outproc;
343 }
344
345 item->proc->read_proc = sony_acpi_read;
346 item->proc->write_proc = sony_acpi_write;
347 item->proc->data = item;
348 item->proc->owner = THIS_MODULE;
349 }
350
351 printk(KERN_INFO ACPI_SNC_DRIVER_NAME " successfully installed\n");
352
353 return 0;
354
355outproc:
356 if (debug) {
357 status = acpi_remove_notify_handler(sony_acpi_handle,
358 ACPI_DEVICE_NOTIFY,
359 sony_acpi_notify);
360 if (ACPI_FAILURE(status))
361 printk(LOG_PFX "unable to remove notify handler\n");
362 }
363outnotify:
364 for (item = sony_acpi_values; item->name; ++item)
365 if (item->proc)
366 remove_proc_entry(item->name, acpi_device_dir(device));
367outwalk:
368 return result;
369}
370
Stelian Pop7f09c432007-01-13 23:04:31 +0100371static int sony_acpi_remove(struct acpi_device *device, int type)
372{
373 acpi_status status;
374 struct sony_acpi_value *item;
375
Alessandro Guido50f62af2007-01-13 23:04:34 +0100376 if (sony_backlight_device)
377 backlight_device_unregister(sony_backlight_device);
378
Stelian Pop7f09c432007-01-13 23:04:31 +0100379 if (debug) {
380 status = acpi_remove_notify_handler(sony_acpi_handle,
381 ACPI_DEVICE_NOTIFY,
382 sony_acpi_notify);
383 if (ACPI_FAILURE(status))
384 printk(LOG_PFX "unable to remove notify handler\n");
385 }
386
387 for (item = sony_acpi_values; item->name; ++item)
388 if (item->proc)
389 remove_proc_entry(item->name, acpi_device_dir(device));
390
391 printk(KERN_INFO ACPI_SNC_DRIVER_NAME " successfully removed\n");
392
393 return 0;
394}
395
Alessandro Guido50f62af2007-01-13 23:04:34 +0100396static int sony_backlight_update_status(struct backlight_device *bd)
397{
398 return acpi_callsetfunc(sony_acpi_handle, "SBRT",
399 bd->props->brightness + 1,
400 NULL);
401}
402
403static int sony_backlight_get_brightness(struct backlight_device *bd)
404{
405 int value;
406
407 if (acpi_callgetfunc(sony_acpi_handle, "GBRT", &value))
408 return 0;
409 /* brightness levels are 1-based, while backlight ones are 0-based */
410 return value - 1;
411}
412
Andrew Morton3f4f4612007-01-13 23:04:32 +0100413static struct acpi_driver sony_acpi_driver = {
414 .name = ACPI_SNC_DRIVER_NAME,
415 .class = ACPI_SNC_CLASS,
416 .ids = ACPI_SNC_HID,
417 .ops = {
418 .add = sony_acpi_add,
419 .remove = sony_acpi_remove,
420 .resume = sony_acpi_resume,
421 },
422};
423
Stelian Pop7f09c432007-01-13 23:04:31 +0100424static int __init sony_acpi_init(void)
425{
426 int result;
427
428 sony_acpi_dir = proc_mkdir("sony", acpi_root_dir);
429 if (!sony_acpi_dir) {
430 printk(LOG_PFX "unable to create /proc entry\n");
431 return -ENODEV;
432 }
433 sony_acpi_dir->owner = THIS_MODULE;
434
435 result = acpi_bus_register_driver(&sony_acpi_driver);
436 if (result < 0) {
437 remove_proc_entry("sony", acpi_root_dir);
438 return -ENODEV;
439 }
440 return 0;
441}
442
443
444static void __exit sony_acpi_exit(void)
445{
446 acpi_bus_unregister_driver(&sony_acpi_driver);
447 remove_proc_entry("sony", acpi_root_dir);
448}
449
450module_init(sony_acpi_init);
451module_exit(sony_acpi_exit);