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