xref: /linux/arch/x86/platform/olpc/olpc-xo15-sci.c (revision c7fdfd2bee1cf1448e5244da1a734e680f634b02)
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/string.h>
11 #include <linux/workqueue.h>
12 #include <linux/platform_device.h>
13 #include <linux/power_supply.h>
14 #include <linux/olpc-ec.h>
15 
16 #include <linux/acpi.h>
17 #include <asm/olpc.h>
18 
19 #define DRV_NAME			"olpc-xo15-sci"
20 #define PFX				DRV_NAME ": "
21 #define XO15_SCI_CLASS			DRV_NAME
22 #define XO15_SCI_DEVICE_NAME		"OLPC XO-1.5 SCI"
23 
24 static unsigned long			xo15_sci_gpe;
25 static bool				lid_wake_on_close;
26 
27 /*
28  * The normal ACPI LID wakeup behavior is wake-on-open, but not
29  * wake-on-close. This is implemented as standard by the XO-1.5 DSDT.
30  *
31  * We provide here a sysfs attribute that will additionally enable
32  * wake-on-close behavior. This is useful (e.g.) when we opportunistically
33  * suspend with the display running; if the lid is then closed, we want to
34  * wake up to turn the display off.
35  *
36  * This is controlled through a custom method in the XO-1.5 DSDT.
37  */
38 static int set_lid_wake_behavior(bool wake_on_close)
39 {
40 	acpi_status status;
41 
42 	status = acpi_execute_simple_method(NULL, "\\_SB.PCI0.LID.LIDW", wake_on_close);
43 	if (ACPI_FAILURE(status)) {
44 		pr_warn(PFX "failed to set lid behavior\n");
45 		return 1;
46 	}
47 
48 	lid_wake_on_close = wake_on_close;
49 
50 	return 0;
51 }
52 
53 static ssize_t
54 lid_wake_on_close_show(struct kobject *s, struct kobj_attribute *attr, char *buf)
55 {
56 	return sprintf(buf, "%u\n", lid_wake_on_close);
57 }
58 
59 static ssize_t lid_wake_on_close_store(struct kobject *s,
60 				       struct kobj_attribute *attr,
61 				       const char *buf, size_t n)
62 {
63 	unsigned int val;
64 
65 	if (sscanf(buf, "%u", &val) != 1)
66 		return -EINVAL;
67 
68 	set_lid_wake_behavior(!!val);
69 
70 	return n;
71 }
72 
73 static struct kobj_attribute lid_wake_on_close_attr =
74 	__ATTR(lid_wake_on_close, 0644,
75 	       lid_wake_on_close_show,
76 	       lid_wake_on_close_store);
77 
78 static void battery_status_changed(void)
79 {
80 	struct power_supply *psy = power_supply_get_by_name("olpc_battery");
81 
82 	if (psy) {
83 		power_supply_changed(psy);
84 		power_supply_put(psy);
85 	}
86 }
87 
88 static void ac_status_changed(void)
89 {
90 	struct power_supply *psy = power_supply_get_by_name("olpc_ac");
91 
92 	if (psy) {
93 		power_supply_changed(psy);
94 		power_supply_put(psy);
95 	}
96 }
97 
98 static void process_sci_queue(void)
99 {
100 	u16 data;
101 	int r;
102 
103 	do {
104 		r = olpc_ec_sci_query(&data);
105 		if (r || !data)
106 			break;
107 
108 		pr_debug(PFX "SCI 0x%x received\n", data);
109 
110 		switch (data) {
111 		case EC_SCI_SRC_BATERR:
112 		case EC_SCI_SRC_BATSOC:
113 		case EC_SCI_SRC_BATTERY:
114 		case EC_SCI_SRC_BATCRIT:
115 			battery_status_changed();
116 			break;
117 		case EC_SCI_SRC_ACPWR:
118 			ac_status_changed();
119 			break;
120 		}
121 	} while (data);
122 
123 	if (r)
124 		pr_err(PFX "Failed to clear SCI queue");
125 }
126 
127 static void process_sci_queue_work(struct work_struct *work)
128 {
129 	process_sci_queue();
130 }
131 
132 static DECLARE_WORK(sci_work, process_sci_queue_work);
133 
134 static u32 xo15_sci_gpe_handler(acpi_handle gpe_device, u32 gpe, void *context)
135 {
136 	schedule_work(&sci_work);
137 	return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE;
138 }
139 
140 static int xo15_sci_probe(struct platform_device *pdev)
141 {
142 	struct acpi_device *device;
143 	unsigned long long tmp;
144 	acpi_status status;
145 	int r;
146 
147 	device = ACPI_COMPANION(&pdev->dev);
148 	if (!device)
149 		return -ENODEV;
150 
151 	strscpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME);
152 	strscpy(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(&pdev->dev, "Initialized, GPE = 0x%lx\n", xo15_sci_gpe);
167 
168 	r = sysfs_create_file(&device->dev.kobj, &lid_wake_on_close_attr.attr);
169 	if (r)
170 		goto err_sysfs;
171 
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)
180 		device_init_wakeup(&pdev->dev, true);
181 
182 	return 0;
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;
188 }
189 
190 static void xo15_sci_remove(struct platform_device *pdev)
191 {
192 	struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
193 
194 	device_init_wakeup(&pdev->dev, false);
195 	acpi_disable_gpe(NULL, xo15_sci_gpe);
196 	acpi_remove_gpe_handler(NULL, xo15_sci_gpe, xo15_sci_gpe_handler);
197 	cancel_work_sync(&sci_work);
198 	sysfs_remove_file(&device->dev.kobj, &lid_wake_on_close_attr.attr);
199 }
200 
201 #ifdef CONFIG_PM_SLEEP
202 static int xo15_sci_resume(struct device *dev)
203 {
204 	/* Enable all EC events */
205 	olpc_ec_mask_write(EC_SCI_SRC_ALL);
206 
207 	/* Power/battery status might have changed */
208 	battery_status_changed();
209 	ac_status_changed();
210 
211 	return 0;
212 }
213 #endif
214 
215 static SIMPLE_DEV_PM_OPS(xo15_sci_pm, NULL, xo15_sci_resume);
216 
217 static const struct acpi_device_id xo15_sci_device_ids[] = {
218 	{"XO15EC", 0},
219 	{"", 0},
220 };
221 
222 static struct platform_driver xo15_sci_drv = {
223 	.probe = xo15_sci_probe,
224 	.remove = xo15_sci_remove,
225 	.driver = {
226 		.name = DRV_NAME,
227 		.acpi_match_table = xo15_sci_device_ids,
228 		.pm = &xo15_sci_pm,
229 	},
230 };
231 
232 static int __init xo15_sci_init(void)
233 {
234 	return platform_driver_register(&xo15_sci_drv);
235 }
236 device_initcall(xo15_sci_init);
237