1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Support for OLPC XO-1.5 System Control Interrupts (SCI) |
4 | * |
5 | * Copyright (C) 2009-2010 One Laptop per Child |
6 | */ |
7 | |
8 | #include <linux/device.h> |
9 | #include <linux/slab.h> |
10 | #include <linux/workqueue.h> |
11 | #include <linux/power_supply.h> |
12 | #include <linux/olpc-ec.h> |
13 | |
14 | #include <linux/acpi.h> |
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 | |
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 opportunistically |
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 | { |
38 | acpi_status status; |
39 | |
40 | status = acpi_execute_simple_method(NULL, method: "\\_SB.PCI0.LID.LIDW" , arg: wake_on_close); |
41 | if (ACPI_FAILURE(status)) { |
42 | pr_warn(PFX "failed to set lid behavior\n" ); |
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, fmt: "%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); |
75 | |
76 | static void battery_status_changed(void) |
77 | { |
78 | struct power_supply *psy = power_supply_get_by_name(name: "olpc_battery" ); |
79 | |
80 | if (psy) { |
81 | power_supply_changed(psy); |
82 | power_supply_put(psy); |
83 | } |
84 | } |
85 | |
86 | static void ac_status_changed(void) |
87 | { |
88 | struct power_supply *psy = power_supply_get_by_name(name: "olpc_ac" ); |
89 | |
90 | if (psy) { |
91 | power_supply_changed(psy); |
92 | power_supply_put(psy); |
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(sci_value: &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(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; |
142 | int r; |
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(handle: device->handle, pathname: "_GPE" , NULL, data: &tmp); |
152 | if (ACPI_FAILURE(status)) |
153 | return -EINVAL; |
154 | |
155 | xo15_sci_gpe = tmp; |
156 | status = acpi_install_gpe_handler(NULL, gpe_number: xo15_sci_gpe, |
157 | ACPI_GPE_EDGE_TRIGGERED, |
158 | address: xo15_sci_gpe_handler, context: device); |
159 | if (ACPI_FAILURE(status)) |
160 | return -ENODEV; |
161 | |
162 | dev_info(&device->dev, "Initialized, GPE = 0x%lx\n" , xo15_sci_gpe); |
163 | |
164 | r = sysfs_create_file(kobj: &device->dev.kobj, attr: &lid_wake_on_close_attr.attr); |
165 | if (r) |
166 | goto err_sysfs; |
167 | |
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, gpe_number: xo15_sci_gpe); |
173 | |
174 | /* Enable wake-on-EC */ |
175 | if (device->wakeup.flags.valid) |
176 | device_init_wakeup(dev: &device->dev, enable: true); |
177 | |
178 | return 0; |
179 | |
180 | err_sysfs: |
181 | acpi_remove_gpe_handler(NULL, gpe_number: xo15_sci_gpe, address: xo15_sci_gpe_handler); |
182 | cancel_work_sync(work: &sci_work); |
183 | return r; |
184 | } |
185 | |
186 | static void xo15_sci_remove(struct acpi_device *device) |
187 | { |
188 | acpi_disable_gpe(NULL, gpe_number: xo15_sci_gpe); |
189 | acpi_remove_gpe_handler(NULL, gpe_number: xo15_sci_gpe, address: xo15_sci_gpe_handler); |
190 | cancel_work_sync(work: &sci_work); |
191 | sysfs_remove_file(kobj: &device->dev.kobj, attr: &lid_wake_on_close_attr.attr); |
192 | } |
193 | |
194 | #ifdef CONFIG_PM_SLEEP |
195 | static int xo15_sci_resume(struct device *dev) |
196 | { |
197 | /* Enable all EC events */ |
198 | olpc_ec_mask_write(EC_SCI_SRC_ALL); |
199 | |
200 | /* Power/battery status might have changed */ |
201 | battery_status_changed(); |
202 | ac_status_changed(); |
203 | |
204 | return 0; |
205 | } |
206 | #endif |
207 | |
208 | static SIMPLE_DEV_PM_OPS(xo15_sci_pm, NULL, xo15_sci_resume); |
209 | |
210 | static const struct acpi_device_id xo15_sci_device_ids[] = { |
211 | {"XO15EC" , 0}, |
212 | {"" , 0}, |
213 | }; |
214 | |
215 | static struct acpi_driver xo15_sci_drv = { |
216 | .name = DRV_NAME, |
217 | .class = XO15_SCI_CLASS, |
218 | .ids = xo15_sci_device_ids, |
219 | .ops = { |
220 | .add = xo15_sci_add, |
221 | .remove = xo15_sci_remove, |
222 | }, |
223 | .drv.pm = &xo15_sci_pm, |
224 | }; |
225 | |
226 | static int __init xo15_sci_init(void) |
227 | { |
228 | return acpi_bus_register_driver(driver: &xo15_sci_drv); |
229 | } |
230 | device_initcall(xo15_sci_init); |
231 | |