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