Root/
Source at commit 7a4fc34 created 12 years 7 months ago. By Xiangfu Liu, ben nanonote: forward patches to linux-3.0 | |
---|---|
1 | From b02e39f1ba2310fedf212d2a6e5d99c4a14fc297 Mon Sep 17 00:00:00 2001 |
2 | From: Lars-Peter Clausen <lars@metafoo.de> |
3 | Date: Wed, 12 May 2010 14:22:36 +0200 |
4 | Subject: [PATCH 06/29] Add n516 lpc driver |
5 | |
6 | --- |
7 | drivers/misc/Kconfig | 8 + |
8 | drivers/misc/Makefile | 1 + |
9 | drivers/misc/n516-lpc.c | 471 +++++++++++++++++++++++++++++++++++++++++++++++ |
10 | 3 files changed, 480 insertions(+), 0 deletions(-) |
11 | create mode 100644 drivers/misc/n516-lpc.c |
12 | |
13 | diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig |
14 | index 4e349cd..aabc92e 100644 |
15 | --- a/drivers/misc/Kconfig |
16 | +++ b/drivers/misc/Kconfig |
17 | @@ -490,6 +490,14 @@ config PCH_PHUB |
18 | To compile this driver as a module, choose M here: the module will |
19 | be called pch_phub. |
20 | |
21 | +config N516_LPC |
22 | + tristate "N516 keys & power controller" |
23 | + depends on I2C |
24 | + depends on INPUT |
25 | + depends on POWER_SUPPLY |
26 | + help |
27 | + N516 keyboard & power controller driver |
28 | + |
29 | source "drivers/misc/c2port/Kconfig" |
30 | source "drivers/misc/eeprom/Kconfig" |
31 | source "drivers/misc/cb710/Kconfig" |
32 | diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile |
33 | index 5f03172..905b807 100644 |
34 | --- a/drivers/misc/Makefile |
35 | +++ b/drivers/misc/Makefile |
36 | @@ -46,3 +46,4 @@ obj-y += ti-st/ |
37 | obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o |
38 | obj-y += lis3lv02d/ |
39 | obj-y += carma/ |
40 | +obj-$(CONFIG_N516_LPC) += n516-lpc.o |
41 | diff --git a/drivers/misc/n516-lpc.c b/drivers/misc/n516-lpc.c |
42 | new file mode 100644 |
43 | index 0000000..2b7a5b3 |
44 | --- /dev/null |
45 | +++ b/drivers/misc/n516-lpc.c |
46 | @@ -0,0 +1,471 @@ |
47 | +#include <linux/module.h> |
48 | +#include <linux/version.h> |
49 | +#include <linux/init.h> |
50 | +#include <linux/fs.h> |
51 | +#include <linux/interrupt.h> |
52 | +#include <linux/irq.h> |
53 | +#include <linux/sched.h> |
54 | +#include <linux/pm.h> |
55 | +#include <linux/sysctl.h> |
56 | +#include <linux/proc_fs.h> |
57 | +#include <linux/delay.h> |
58 | +#include <linux/platform_device.h> |
59 | +#include <linux/input.h> |
60 | +#include <linux/power_supply.h> |
61 | +#include <linux/suspend.h> |
62 | + |
63 | +#include <linux/i2c.h> |
64 | + |
65 | +#include <asm/mach-jz4740/irq.h> |
66 | +#include <asm/mach-jz4740/gpio.h> |
67 | +#include <asm/mach-jz4740/board-n516.h> |
68 | + |
69 | +static int batt_level=0; |
70 | +module_param(batt_level, int, 0); |
71 | + |
72 | +struct n516_lpc_chip { |
73 | + struct i2c_client *i2c_client; |
74 | + struct input_dev *input; |
75 | + unsigned int battery_level; |
76 | + unsigned int suspending:1, can_sleep:1; |
77 | +}; |
78 | + |
79 | +static struct n516_lpc_chip *the_lpc; |
80 | + |
81 | +struct i2c_device_id n516_lpc_i2c_ids[] = { |
82 | + {"LPC524", 0}, |
83 | + {}, |
84 | +}; |
85 | + |
86 | +MODULE_DEVICE_TABLE(i2c, n516_lpc_i2c_ids); |
87 | + |
88 | +static const unsigned short normal_i2c[] = I2C_ADDRS(0x54); |
89 | + |
90 | +static const unsigned int n516_lpc_keymap[] = { |
91 | + [0x01] = KEY_4, |
92 | + [0x02] = KEY_3, |
93 | + [0x03] = KEY_2, |
94 | + [0x04] = KEY_1, |
95 | + [0x05] = KEY_0, |
96 | + [0x07] = KEY_9, |
97 | + [0x08] = KEY_8, |
98 | + [0x09] = KEY_7, |
99 | + [0x0a] = KEY_6, |
100 | + [0x0b] = KEY_5, |
101 | + [0x0d] = KEY_PLAYPAUSE, |
102 | + [0x0e] = KEY_MENU, |
103 | + [0x0f] = KEY_SEARCH, |
104 | + [0x10] = KEY_DIRECTION, |
105 | + [0x11] = KEY_SPACE, |
106 | + [0x13] = KEY_ENTER, |
107 | + [0x14] = KEY_UP, |
108 | + [0x15] = KEY_DOWN, |
109 | + [0x16] = KEY_RIGHT, |
110 | + [0x17] = KEY_LEFT, |
111 | + [0x19] = KEY_PAGEDOWN, |
112 | + [0x1a] = KEY_PAGEUP, |
113 | + [0x1c] = KEY_POWER, |
114 | + [0x1d] = KEY_ESC, |
115 | + [0x1e] = KEY_SLEEP, |
116 | + [0x1f] = KEY_WAKEUP, |
117 | +}; |
118 | + |
119 | +static const unsigned int batt_charge[] = {0, 7, 20, 45, 65, 80, 100}; |
120 | +#define MAX_BAT_LEVEL 6 |
121 | + |
122 | +static inline int n516_bat_charging(void) |
123 | +{ |
124 | + return !gpio_get_value(GPIO_CHARG_STAT_N); |
125 | +} |
126 | + |
127 | +static int n516_bat_get_status(struct power_supply *b) |
128 | +{ |
129 | + if (power_supply_am_i_supplied(b)) { |
130 | + if (n516_bat_charging()) |
131 | + return POWER_SUPPLY_STATUS_CHARGING; |
132 | + else |
133 | + return POWER_SUPPLY_STATUS_FULL; |
134 | + } else { |
135 | + return POWER_SUPPLY_STATUS_DISCHARGING; |
136 | + } |
137 | +} |
138 | + |
139 | +static int n516_bat_get_charge(struct power_supply *b) |
140 | +{ |
141 | + return batt_charge[the_lpc->battery_level]; |
142 | +} |
143 | + |
144 | +static int n516_bat_get_property(struct power_supply *b, |
145 | + enum power_supply_property psp, |
146 | + union power_supply_propval *val) |
147 | +{ |
148 | + switch (psp) { |
149 | + case POWER_SUPPLY_PROP_STATUS: |
150 | + val->intval = n516_bat_get_status(b); |
151 | + break; |
152 | + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: |
153 | + val->intval = 100; |
154 | + break; |
155 | + case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN: |
156 | + val->intval = 0; |
157 | + break; |
158 | + case POWER_SUPPLY_PROP_CHARGE_NOW: |
159 | + val->intval = n516_bat_get_charge(b); |
160 | + break; |
161 | + default: |
162 | + return -EINVAL; |
163 | + } |
164 | + return 0; |
165 | +} |
166 | + |
167 | +static void n516_bat_power_changed(struct power_supply *p) |
168 | +{ |
169 | + if (power_supply_am_i_supplied(p) && !n516_bat_charging()) |
170 | + the_lpc->battery_level = MAX_BAT_LEVEL; |
171 | + |
172 | + power_supply_changed(p); |
173 | +} |
174 | + |
175 | +static enum power_supply_property n516_bat_properties[] = { |
176 | + POWER_SUPPLY_PROP_STATUS, |
177 | + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, |
178 | + POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN, |
179 | + POWER_SUPPLY_PROP_CHARGE_NOW, |
180 | +}; |
181 | + |
182 | +static struct power_supply n516_battery = { |
183 | + .name = "n516-battery", |
184 | + .get_property = n516_bat_get_property, |
185 | + .properties = n516_bat_properties, |
186 | + .num_properties = ARRAY_SIZE(n516_bat_properties), |
187 | + .external_power_changed = n516_bat_power_changed, |
188 | +}; |
189 | + |
190 | +static irqreturn_t n516_bat_charge_irq(int irq, void *dev) |
191 | +{ |
192 | + struct power_supply *psy = dev; |
193 | + |
194 | + dev_dbg(psy->dev, "Battery charging IRQ\n"); |
195 | + |
196 | + if (power_supply_am_i_supplied(psy) && !n516_bat_charging()) |
197 | + the_lpc->battery_level = MAX_BAT_LEVEL; |
198 | + |
199 | + power_supply_changed(psy); |
200 | + |
201 | + return IRQ_HANDLED; |
202 | +} |
203 | + |
204 | +static int n516_lpc_send_message(struct n516_lpc_chip *chip, unsigned char val) |
205 | +{ |
206 | + struct i2c_client *client = chip->i2c_client; |
207 | + struct i2c_msg msg = {client->addr, client->flags, 1, &val}; |
208 | + int ret = 0; |
209 | + |
210 | + ret = i2c_transfer(client->adapter, &msg, 1); |
211 | + return ret > 0 ? 0 : ret; |
212 | +} |
213 | + |
214 | +static void n516_key_event(struct n516_lpc_chip *chip, unsigned char keycode) |
215 | +{ |
216 | + struct i2c_client *client = chip->i2c_client; |
217 | + bool long_press = false; |
218 | + |
219 | + if (keycode & 0x40) { |
220 | + keycode &= ~0x40; |
221 | + long_press = true; |
222 | + } |
223 | + |
224 | + dev_dbg(&client->dev, "keycode: 0x%02x, long_press: 0x%02x\n", keycode, (unsigned int)long_press); |
225 | + |
226 | + if (keycode >= ARRAY_SIZE(n516_lpc_keymap) || n516_lpc_keymap[keycode] == 0) |
227 | + return; |
228 | + |
229 | + if (long_press) |
230 | + input_report_key(chip->input, KEY_LEFTALT, 1); |
231 | + |
232 | + input_report_key(chip->input, n516_lpc_keymap[keycode], 1); |
233 | + input_sync(chip->input); |
234 | + input_report_key(chip->input, n516_lpc_keymap[keycode], 0); |
235 | + |
236 | + if (long_press) |
237 | + input_report_key(chip->input, KEY_LEFTALT, 0); |
238 | + input_sync(chip->input); |
239 | +} |
240 | + |
241 | +static void n516_battery_event(struct n516_lpc_chip *chip, unsigned char battery_level) |
242 | +{ |
243 | + if (battery_level != chip->battery_level) { |
244 | + chip->battery_level = battery_level; |
245 | + power_supply_changed(&n516_battery); |
246 | + } |
247 | +} |
248 | + |
249 | +static irqreturn_t n516_lpc_irq_thread(int irq, void *devid) |
250 | +{ |
251 | + struct n516_lpc_chip *chip = (struct n516_lpc_chip*)devid; |
252 | + int ret; |
253 | + unsigned char raw_msg; |
254 | + struct i2c_client *client = chip->i2c_client; |
255 | + struct i2c_msg msg = {client->addr, client->flags | I2C_M_RD, 1, &raw_msg}; |
256 | + |
257 | + if (client->dev.power.status >= DPM_OFF) |
258 | + return IRQ_HANDLED; |
259 | + |
260 | + ret = i2c_transfer(client->adapter, &msg, 1); |
261 | + if (ret != 1) { |
262 | + dev_dbg(&client->dev, "I2C error: %d\n", ret); |
263 | + return IRQ_HANDLED; |
264 | + } |
265 | + |
266 | + dev_dbg(&client->dev, "msg: 0x%02x\n", raw_msg); |
267 | + |
268 | + /* Ack wakeup event */ |
269 | + if ((raw_msg & ~0x40) < ARRAY_SIZE(n516_lpc_keymap)) |
270 | + n516_key_event(chip, raw_msg); |
271 | + else if ((raw_msg >= 0x81) && (raw_msg <= 0x87)) |
272 | + n516_battery_event(chip, raw_msg - 0x81); |
273 | + else if (raw_msg == 0x7e) |
274 | + n516_lpc_send_message(chip, 0x00); |
275 | + else |
276 | + dev_warn(&client->dev, "Unknown message: %x\n", raw_msg); |
277 | + |
278 | + if (chip->suspending) |
279 | + chip->can_sleep = 0; |
280 | + |
281 | + return IRQ_HANDLED; |
282 | +} |
283 | + |
284 | +static void n516_lpc_power_off(void) |
285 | +{ |
286 | + struct i2c_client *client = the_lpc->i2c_client; |
287 | + unsigned char val = 0x01; |
288 | + struct i2c_msg msg = {client->addr, client->flags, 1, &val}; |
289 | + |
290 | + printk("Issue LPC POWEROFF command...\n"); |
291 | + while (1) |
292 | + i2c_transfer(client->adapter, &msg, 1); |
293 | +} |
294 | + |
295 | +static int n516_lpc_detect(struct i2c_client *client, struct i2c_board_info *info) |
296 | +{ |
297 | + return 0; |
298 | +} |
299 | + |
300 | +static int n516_lpc_suspend_notifier(struct notifier_block *nb, |
301 | + unsigned long event, |
302 | + void *dummy) |
303 | +{ |
304 | + switch(event) { |
305 | + case PM_SUSPEND_PREPARE: |
306 | + the_lpc->suspending = 1; |
307 | + the_lpc->can_sleep = 1; |
308 | + break; |
309 | + case PM_POST_SUSPEND: |
310 | + the_lpc->suspending = 0; |
311 | + the_lpc->can_sleep = 1; |
312 | + break; |
313 | + default: |
314 | + return NOTIFY_DONE; |
315 | + } |
316 | + return NOTIFY_OK; |
317 | +} |
318 | + |
319 | +static struct notifier_block n516_lpc_notif_block = { |
320 | + .notifier_call = n516_lpc_suspend_notifier, |
321 | +}; |
322 | + |
323 | +static int __devinit n516_lpc_probe(struct i2c_client *client, const struct i2c_device_id *id) |
324 | +{ |
325 | + struct n516_lpc_chip *chip; |
326 | + struct input_dev *input; |
327 | + int ret = 0; |
328 | + int i; |
329 | + |
330 | + chip = kzalloc(sizeof(*chip), GFP_KERNEL); |
331 | + if (!chip) |
332 | + return -ENOMEM; |
333 | + |
334 | + the_lpc = chip; |
335 | + chip->i2c_client = client; |
336 | + if ((batt_level > 0) && (batt_level < ARRAY_SIZE(batt_charge))) |
337 | + chip->battery_level = batt_level; |
338 | + else |
339 | + chip->battery_level = 1; |
340 | + |
341 | + i2c_set_clientdata(client, chip); |
342 | + |
343 | + ret = gpio_request(GPIO_LPC_INT, "LPC interrupt request"); |
344 | + if (ret) { |
345 | + dev_err(&client->dev, "Unable to reguest LPC INT GPIO\n"); |
346 | + goto err_gpio_req_lpcint; |
347 | + } |
348 | + |
349 | + ret = gpio_request(GPIO_CHARG_STAT_N, "LPC charging status"); |
350 | + if (ret) { |
351 | + dev_err(&client->dev, "Unable to reguest CHARG STAT GPIO\n"); |
352 | + goto err_gpio_req_chargstat; |
353 | + } |
354 | + |
355 | + /* Enter normal mode */ |
356 | + n516_lpc_send_message(chip, 0x2); |
357 | + |
358 | + input = input_allocate_device(); |
359 | + if (!input) { |
360 | + dev_err(&client->dev, "Unable to allocate input device\n"); |
361 | + ret = -ENOMEM; |
362 | + goto err_input_alloc; |
363 | + } |
364 | + |
365 | + chip->input = input; |
366 | + |
367 | + __set_bit(EV_KEY, input->evbit); |
368 | + |
369 | + for (i = 0; i < ARRAY_SIZE(n516_lpc_keymap); i++) |
370 | + __set_bit(n516_lpc_keymap[i], input->keybit); |
371 | + |
372 | + __set_bit(KEY_LEFTALT, input->keybit); |
373 | + |
374 | + input->name = "n516-keys"; |
375 | + input->phys = "n516-keys/input0"; |
376 | + input->dev.parent = &client->dev; |
377 | + input->id.bustype = BUS_I2C; |
378 | + input->id.vendor = 0x0001; |
379 | + input->id.product = 0x0001; |
380 | + input->id.version = 0x0100; |
381 | + |
382 | + ret = input_register_device(input); |
383 | + if (ret < 0) { |
384 | + dev_err(&client->dev, "Unable to register input device\n"); |
385 | + goto err_input_register; |
386 | + } |
387 | + |
388 | + ret = power_supply_register(NULL, &n516_battery); |
389 | + if (ret) { |
390 | + dev_err(&client->dev, "Unable to register N516 battery\n"); |
391 | + goto err_bat_reg; |
392 | + } |
393 | + |
394 | + ret = request_threaded_irq(gpio_to_irq(GPIO_LPC_INT), NULL, |
395 | + n516_lpc_irq_thread, |
396 | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, |
397 | + "lpc", chip); |
398 | + if (ret) { |
399 | + dev_err(&client->dev, "request_irq failed: %d\n", ret); |
400 | + goto err_request_lpc_irq; |
401 | + } |
402 | + |
403 | + ret = request_irq(gpio_to_irq(GPIO_CHARG_STAT_N), n516_bat_charge_irq, |
404 | + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, |
405 | + "battery charging", &n516_battery); |
406 | + if (ret) { |
407 | + dev_err(&client->dev, "Unable to claim battery charging IRQ\n"); |
408 | + goto err_request_chrg_irq; |
409 | + } |
410 | + |
411 | + pm_power_off = n516_lpc_power_off; |
412 | + ret = register_pm_notifier(&n516_lpc_notif_block); |
413 | + if (ret) { |
414 | + dev_err(&client->dev, "Unable to register PM notify block\n"); |
415 | + goto err_reg_pm_notifier; |
416 | + } |
417 | + |
418 | + device_init_wakeup(&client->dev, 1); |
419 | + |
420 | + return 0; |
421 | + |
422 | + unregister_pm_notifier(&n516_lpc_notif_block); |
423 | +err_reg_pm_notifier: |
424 | + free_irq(gpio_to_irq(GPIO_CHARG_STAT_N), &n516_battery); |
425 | +err_request_chrg_irq: |
426 | + free_irq(gpio_to_irq(GPIO_LPC_INT), chip); |
427 | +err_request_lpc_irq: |
428 | + power_supply_unregister(&n516_battery); |
429 | +err_bat_reg: |
430 | + input_unregister_device(input); |
431 | +err_input_register: |
432 | + input_free_device(input); |
433 | +err_input_alloc: |
434 | + gpio_free(GPIO_CHARG_STAT_N); |
435 | +err_gpio_req_chargstat: |
436 | + gpio_free(GPIO_LPC_INT); |
437 | +err_gpio_req_lpcint: |
438 | + i2c_set_clientdata(client, NULL); |
439 | + kfree(chip); |
440 | + |
441 | + return ret; |
442 | +} |
443 | + |
444 | +static int __devexit n516_lpc_remove(struct i2c_client *client) |
445 | +{ |
446 | + struct n516_lpc_chip *chip = i2c_get_clientdata(client); |
447 | + |
448 | + unregister_pm_notifier(&n516_lpc_notif_block); |
449 | + pm_power_off = NULL; |
450 | + free_irq(gpio_to_irq(GPIO_CHARG_STAT_N), &n516_battery); |
451 | + free_irq(gpio_to_irq(GPIO_LPC_INT), chip); |
452 | + power_supply_unregister(&n516_battery); |
453 | + input_unregister_device(chip->input); |
454 | + gpio_free(GPIO_CHARG_STAT_N); |
455 | + gpio_free(GPIO_LPC_INT); |
456 | + i2c_set_clientdata(client, NULL); |
457 | + kfree(chip); |
458 | + |
459 | + return 0; |
460 | +} |
461 | + |
462 | +#if CONFIG_PM |
463 | +static int n516_lpc_suspend(struct i2c_client *client, pm_message_t msg) |
464 | +{ |
465 | + if (!the_lpc->can_sleep) |
466 | + return -EBUSY; |
467 | + |
468 | + if (device_may_wakeup(&client->dev)) |
469 | + enable_irq_wake(gpio_to_irq(GPIO_LPC_INT)); |
470 | + |
471 | + return 0; |
472 | +} |
473 | + |
474 | +static int n516_lpc_resume(struct i2c_client *client) |
475 | +{ |
476 | + if (device_may_wakeup(&client->dev)) |
477 | + disable_irq_wake(gpio_to_irq(GPIO_LPC_INT)); |
478 | + |
479 | + return 0; |
480 | +} |
481 | +#else |
482 | +#define n516_lpc_suspend NULL |
483 | +#define n516_lpc_resume NULL |
484 | +#endif |
485 | + |
486 | + |
487 | +static struct i2c_driver n516_lpc_driver = { |
488 | + .class = I2C_CLASS_HWMON, |
489 | + .driver = { |
490 | + .name = "n516-keys", |
491 | + .owner = THIS_MODULE, |
492 | + }, |
493 | + .probe = n516_lpc_probe, |
494 | + .remove = __devexit_p(n516_lpc_remove), |
495 | + .detect = n516_lpc_detect, |
496 | + .id_table = n516_lpc_i2c_ids, |
497 | + .address_list = normal_i2c, |
498 | + .suspend = n516_lpc_suspend, |
499 | + .resume = n516_lpc_resume, |
500 | +}; |
501 | + |
502 | +static int __init n516_lpc_init(void) |
503 | +{ |
504 | + return i2c_add_driver(&n516_lpc_driver); |
505 | +} |
506 | +module_init(n516_lpc_init); |
507 | + |
508 | +static void __exit n516_lpc_exit(void) |
509 | +{ |
510 | + i2c_del_driver(&n516_lpc_driver); |
511 | +} |
512 | +module_exit(n516_lpc_exit); |
513 | + |
514 | +MODULE_AUTHOR("Yauhen Kharuzhy"); |
515 | +MODULE_LICENSE("GPL"); |
516 | +MODULE_DESCRIPTION("Keys and power controller driver for N516"); |
517 | +MODULE_ALIAS("platform:n516-keys"); |
518 | -- |
519 | 1.7.4.1 |
520 | |
521 |