tizen 2.4 release
[profile/mobile/platform/kernel/linux-3.10-sc7730.git] / arch / x86 / platform / olpc / olpc-xo15-sci.c
1 /*
2  * Support for OLPC XO-1.5 System Control Interrupts (SCI)
3  *
4  * Copyright (C) 2009-2010 One Laptop per Child
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  */
11
12 #include <linux/device.h>
13 #include <linux/slab.h>
14 #include <linux/workqueue.h>
15 #include <linux/power_supply.h>
16 #include <linux/olpc-ec.h>
17
18 #include <acpi/acpi_bus.h>
19 #include <acpi/acpi_drivers.h>
20 #include <asm/olpc.h>
21
22 #define DRV_NAME                        "olpc-xo15-sci"
23 #define PFX                             DRV_NAME ": "
24 #define XO15_SCI_CLASS                  DRV_NAME
25 #define XO15_SCI_DEVICE_NAME            "OLPC XO-1.5 SCI"
26
27 static unsigned long                    xo15_sci_gpe;
28 static bool                             lid_wake_on_close;
29
30 /*
31  * The normal ACPI LID wakeup behavior is wake-on-open, but not
32  * wake-on-close. This is implemented as standard by the XO-1.5 DSDT.
33  *
34  * We provide here a sysfs attribute that will additionally enable
35  * wake-on-close behavior. This is useful (e.g.) when we oportunistically
36  * suspend with the display running; if the lid is then closed, we want to
37  * wake up to turn the display off.
38  *
39  * This is controlled through a custom method in the XO-1.5 DSDT.
40  */
41 static int set_lid_wake_behavior(bool wake_on_close)
42 {
43         struct acpi_object_list arg_list;
44         union acpi_object arg;
45         acpi_status status;
46
47         arg_list.count          = 1;
48         arg_list.pointer        = &arg;
49         arg.type                = ACPI_TYPE_INTEGER;
50         arg.integer.value       = wake_on_close;
51
52         status = acpi_evaluate_object(NULL, "\\_SB.PCI0.LID.LIDW", &arg_list, NULL);
53         if (ACPI_FAILURE(status)) {
54                 pr_warning(PFX "failed to set lid behavior\n");
55                 return 1;
56         }
57
58         lid_wake_on_close = wake_on_close;
59
60         return 0;
61 }
62
63 static ssize_t
64 lid_wake_on_close_show(struct kobject *s, struct kobj_attribute *attr, char *buf)
65 {
66         return sprintf(buf, "%u\n", lid_wake_on_close);
67 }
68
69 static ssize_t lid_wake_on_close_store(struct kobject *s,
70                                        struct kobj_attribute *attr,
71                                        const char *buf, size_t n)
72 {
73         unsigned int val;
74
75         if (sscanf(buf, "%u", &val) != 1)
76                 return -EINVAL;
77
78         set_lid_wake_behavior(!!val);
79
80         return n;
81 }
82
83 static struct kobj_attribute lid_wake_on_close_attr =
84         __ATTR(lid_wake_on_close, 0644,
85                lid_wake_on_close_show,
86                lid_wake_on_close_store);
87
88 static void battery_status_changed(void)
89 {
90         struct power_supply *psy = power_supply_get_by_name("olpc-battery");
91
92         if (psy) {
93                 power_supply_changed(psy);
94                 put_device(psy->dev);
95         }
96 }
97
98 static void ac_status_changed(void)
99 {
100         struct power_supply *psy = power_supply_get_by_name("olpc-ac");
101
102         if (psy) {
103                 power_supply_changed(psy);
104                 put_device(psy->dev);
105         }
106 }
107
108 static void process_sci_queue(void)
109 {
110         u16 data;
111         int r;
112
113         do {
114                 r = olpc_ec_sci_query(&data);
115                 if (r || !data)
116                         break;
117
118                 pr_debug(PFX "SCI 0x%x received\n", data);
119
120                 switch (data) {
121                 case EC_SCI_SRC_BATERR:
122                 case EC_SCI_SRC_BATSOC:
123                 case EC_SCI_SRC_BATTERY:
124                 case EC_SCI_SRC_BATCRIT:
125                         battery_status_changed();
126                         break;
127                 case EC_SCI_SRC_ACPWR:
128                         ac_status_changed();
129                         break;
130                 }
131         } while (data);
132
133         if (r)
134                 pr_err(PFX "Failed to clear SCI queue");
135 }
136
137 static void process_sci_queue_work(struct work_struct *work)
138 {
139         process_sci_queue();
140 }
141
142 static DECLARE_WORK(sci_work, process_sci_queue_work);
143
144 static u32 xo15_sci_gpe_handler(acpi_handle gpe_device, u32 gpe, void *context)
145 {
146         schedule_work(&sci_work);
147         return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE;
148 }
149
150 static int xo15_sci_add(struct acpi_device *device)
151 {
152         unsigned long long tmp;
153         acpi_status status;
154         int r;
155
156         if (!device)
157                 return -EINVAL;
158
159         strcpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME);
160         strcpy(acpi_device_class(device), XO15_SCI_CLASS);
161
162         /* Get GPE bit assignment (EC events). */
163         status = acpi_evaluate_integer(device->handle, "_GPE", NULL, &tmp);
164         if (ACPI_FAILURE(status))
165                 return -EINVAL;
166
167         xo15_sci_gpe = tmp;
168         status = acpi_install_gpe_handler(NULL, xo15_sci_gpe,
169                                           ACPI_GPE_EDGE_TRIGGERED,
170                                           xo15_sci_gpe_handler, device);
171         if (ACPI_FAILURE(status))
172                 return -ENODEV;
173
174         dev_info(&device->dev, "Initialized, GPE = 0x%lx\n", xo15_sci_gpe);
175
176         r = sysfs_create_file(&device->dev.kobj, &lid_wake_on_close_attr.attr);
177         if (r)
178                 goto err_sysfs;
179
180         /* Flush queue, and enable all SCI events */
181         process_sci_queue();
182         olpc_ec_mask_write(EC_SCI_SRC_ALL);
183
184         acpi_enable_gpe(NULL, xo15_sci_gpe);
185
186         /* Enable wake-on-EC */
187         if (device->wakeup.flags.valid)
188                 device_init_wakeup(&device->dev, true);
189
190         return 0;
191
192 err_sysfs:
193         acpi_remove_gpe_handler(NULL, xo15_sci_gpe, xo15_sci_gpe_handler);
194         cancel_work_sync(&sci_work);
195         return r;
196 }
197
198 static int xo15_sci_remove(struct acpi_device *device)
199 {
200         acpi_disable_gpe(NULL, xo15_sci_gpe);
201         acpi_remove_gpe_handler(NULL, xo15_sci_gpe, xo15_sci_gpe_handler);
202         cancel_work_sync(&sci_work);
203         sysfs_remove_file(&device->dev.kobj, &lid_wake_on_close_attr.attr);
204         return 0;
205 }
206
207 static int xo15_sci_resume(struct device *dev)
208 {
209         /* Enable all EC events */
210         olpc_ec_mask_write(EC_SCI_SRC_ALL);
211
212         /* Power/battery status might have changed */
213         battery_status_changed();
214         ac_status_changed();
215
216         return 0;
217 }
218
219 static SIMPLE_DEV_PM_OPS(xo15_sci_pm, NULL, xo15_sci_resume);
220
221 static const struct acpi_device_id xo15_sci_device_ids[] = {
222         {"XO15EC", 0},
223         {"", 0},
224 };
225
226 static struct acpi_driver xo15_sci_drv = {
227         .name = DRV_NAME,
228         .class = XO15_SCI_CLASS,
229         .ids = xo15_sci_device_ids,
230         .ops = {
231                 .add = xo15_sci_add,
232                 .remove = xo15_sci_remove,
233         },
234         .drv.pm = &xo15_sci_pm,
235 };
236
237 static int __init xo15_sci_init(void)
238 {
239         return acpi_bus_register_driver(&xo15_sci_drv);
240 }
241 device_initcall(xo15_sci_init);