1fc00bc8aSMaximilian Luz // SPDX-License-Identifier: GPL-2.0+
2fc00bc8aSMaximilian Luz /*
3fc00bc8aSMaximilian Luz  * Driver for the Surface ACPI Notify (SAN) interface/shim.
4fc00bc8aSMaximilian Luz  *
5fc00bc8aSMaximilian Luz  * Translates communication from ACPI to Surface System Aggregator Module
6fc00bc8aSMaximilian Luz  * (SSAM/SAM) requests and back, specifically SAM-over-SSH. Translates SSAM
7fc00bc8aSMaximilian Luz  * events back to ACPI notifications. Allows handling of discrete GPU
8fc00bc8aSMaximilian Luz  * notifications sent from ACPI via the SAN interface by providing them to any
9fc00bc8aSMaximilian Luz  * registered external driver.
10fc00bc8aSMaximilian Luz  *
11221756e6SMaximilian Luz  * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
12fc00bc8aSMaximilian Luz  */
13fc00bc8aSMaximilian Luz 
14fc00bc8aSMaximilian Luz #include <asm/unaligned.h>
15fc00bc8aSMaximilian Luz #include <linux/acpi.h>
16fc00bc8aSMaximilian Luz #include <linux/delay.h>
17fc00bc8aSMaximilian Luz #include <linux/jiffies.h>
18fc00bc8aSMaximilian Luz #include <linux/kernel.h>
19fc00bc8aSMaximilian Luz #include <linux/module.h>
20fc00bc8aSMaximilian Luz #include <linux/notifier.h>
21fc00bc8aSMaximilian Luz #include <linux/platform_device.h>
22fc00bc8aSMaximilian Luz #include <linux/rwsem.h>
23fc00bc8aSMaximilian Luz 
24fc00bc8aSMaximilian Luz #include <linux/surface_aggregator/controller.h>
25fc00bc8aSMaximilian Luz #include <linux/surface_acpi_notify.h>
26fc00bc8aSMaximilian Luz 
27fc00bc8aSMaximilian Luz struct san_data {
28fc00bc8aSMaximilian Luz 	struct device *dev;
29fc00bc8aSMaximilian Luz 	struct ssam_controller *ctrl;
30fc00bc8aSMaximilian Luz 
31fc00bc8aSMaximilian Luz 	struct acpi_connection_info info;
32fc00bc8aSMaximilian Luz 
33fc00bc8aSMaximilian Luz 	struct ssam_event_notifier nf_bat;
34fc00bc8aSMaximilian Luz 	struct ssam_event_notifier nf_tmp;
35fc00bc8aSMaximilian Luz };
36fc00bc8aSMaximilian Luz 
37fc00bc8aSMaximilian Luz #define to_san_data(ptr, member) \
38fc00bc8aSMaximilian Luz 	container_of(ptr, struct san_data, member)
39fc00bc8aSMaximilian Luz 
4031a1e4a5STetsuo Handa static struct workqueue_struct *san_wq;
41fc00bc8aSMaximilian Luz 
42fc00bc8aSMaximilian Luz /* -- dGPU notifier interface. ---------------------------------------------- */
43fc00bc8aSMaximilian Luz 
44fc00bc8aSMaximilian Luz struct san_rqsg_if {
45fc00bc8aSMaximilian Luz 	struct rw_semaphore lock;
46fc00bc8aSMaximilian Luz 	struct device *dev;
47fc00bc8aSMaximilian Luz 	struct blocking_notifier_head nh;
48fc00bc8aSMaximilian Luz };
49fc00bc8aSMaximilian Luz 
50fc00bc8aSMaximilian Luz static struct san_rqsg_if san_rqsg_if = {
51fc00bc8aSMaximilian Luz 	.lock = __RWSEM_INITIALIZER(san_rqsg_if.lock),
52fc00bc8aSMaximilian Luz 	.dev = NULL,
53fc00bc8aSMaximilian Luz 	.nh = BLOCKING_NOTIFIER_INIT(san_rqsg_if.nh),
54fc00bc8aSMaximilian Luz };
55fc00bc8aSMaximilian Luz 
san_set_rqsg_interface_device(struct device * dev)56fc00bc8aSMaximilian Luz static int san_set_rqsg_interface_device(struct device *dev)
57fc00bc8aSMaximilian Luz {
58fc00bc8aSMaximilian Luz 	int status = 0;
59fc00bc8aSMaximilian Luz 
60fc00bc8aSMaximilian Luz 	down_write(&san_rqsg_if.lock);
61fc00bc8aSMaximilian Luz 	if (!san_rqsg_if.dev && dev)
62fc00bc8aSMaximilian Luz 		san_rqsg_if.dev = dev;
63fc00bc8aSMaximilian Luz 	else
64fc00bc8aSMaximilian Luz 		status = -EBUSY;
65fc00bc8aSMaximilian Luz 	up_write(&san_rqsg_if.lock);
66fc00bc8aSMaximilian Luz 
67fc00bc8aSMaximilian Luz 	return status;
68fc00bc8aSMaximilian Luz }
69fc00bc8aSMaximilian Luz 
70fc00bc8aSMaximilian Luz /**
71fc00bc8aSMaximilian Luz  * san_client_link() - Link client as consumer to SAN device.
72fc00bc8aSMaximilian Luz  * @client: The client to link.
73fc00bc8aSMaximilian Luz  *
74fc00bc8aSMaximilian Luz  * Sets up a device link between the provided client device as consumer and
75fc00bc8aSMaximilian Luz  * the SAN device as provider. This function can be used to ensure that the
76fc00bc8aSMaximilian Luz  * SAN interface has been set up and will be set up for as long as the driver
77fc00bc8aSMaximilian Luz  * of the client device is bound. This guarantees that, during that time, all
78fc00bc8aSMaximilian Luz  * dGPU events will be received by any registered notifier.
79fc00bc8aSMaximilian Luz  *
80fc00bc8aSMaximilian Luz  * The link will be automatically removed once the client device's driver is
81fc00bc8aSMaximilian Luz  * unbound.
82fc00bc8aSMaximilian Luz  *
83fc00bc8aSMaximilian Luz  * Return: Returns zero on success, %-ENXIO if the SAN interface has not been
84fc00bc8aSMaximilian Luz  * set up yet, and %-ENOMEM if device link creation failed.
85fc00bc8aSMaximilian Luz  */
san_client_link(struct device * client)86fc00bc8aSMaximilian Luz int san_client_link(struct device *client)
87fc00bc8aSMaximilian Luz {
88fc00bc8aSMaximilian Luz 	const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER;
89fc00bc8aSMaximilian Luz 	struct device_link *link;
90fc00bc8aSMaximilian Luz 
91fc00bc8aSMaximilian Luz 	down_read(&san_rqsg_if.lock);
92fc00bc8aSMaximilian Luz 
93fc00bc8aSMaximilian Luz 	if (!san_rqsg_if.dev) {
94fc00bc8aSMaximilian Luz 		up_read(&san_rqsg_if.lock);
95fc00bc8aSMaximilian Luz 		return -ENXIO;
96fc00bc8aSMaximilian Luz 	}
97fc00bc8aSMaximilian Luz 
98fc00bc8aSMaximilian Luz 	link = device_link_add(client, san_rqsg_if.dev, flags);
99fc00bc8aSMaximilian Luz 	if (!link) {
100fc00bc8aSMaximilian Luz 		up_read(&san_rqsg_if.lock);
101fc00bc8aSMaximilian Luz 		return -ENOMEM;
102fc00bc8aSMaximilian Luz 	}
103fc00bc8aSMaximilian Luz 
104fc00bc8aSMaximilian Luz 	if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) {
105fc00bc8aSMaximilian Luz 		up_read(&san_rqsg_if.lock);
106fc00bc8aSMaximilian Luz 		return -ENXIO;
107fc00bc8aSMaximilian Luz 	}
108fc00bc8aSMaximilian Luz 
109fc00bc8aSMaximilian Luz 	up_read(&san_rqsg_if.lock);
110fc00bc8aSMaximilian Luz 	return 0;
111fc00bc8aSMaximilian Luz }
112fc00bc8aSMaximilian Luz EXPORT_SYMBOL_GPL(san_client_link);
113fc00bc8aSMaximilian Luz 
114fc00bc8aSMaximilian Luz /**
115fc00bc8aSMaximilian Luz  * san_dgpu_notifier_register() - Register a SAN dGPU notifier.
116fc00bc8aSMaximilian Luz  * @nb: The notifier-block to register.
117fc00bc8aSMaximilian Luz  *
118fc00bc8aSMaximilian Luz  * Registers a SAN dGPU notifier, receiving any new SAN dGPU events sent from
119fc00bc8aSMaximilian Luz  * ACPI. The registered notifier will be called with &struct san_dgpu_event
120fc00bc8aSMaximilian Luz  * as notifier data and the command ID of that event as notifier action.
121fc00bc8aSMaximilian Luz  */
san_dgpu_notifier_register(struct notifier_block * nb)122fc00bc8aSMaximilian Luz int san_dgpu_notifier_register(struct notifier_block *nb)
123fc00bc8aSMaximilian Luz {
124fc00bc8aSMaximilian Luz 	return blocking_notifier_chain_register(&san_rqsg_if.nh, nb);
125fc00bc8aSMaximilian Luz }
126fc00bc8aSMaximilian Luz EXPORT_SYMBOL_GPL(san_dgpu_notifier_register);
127fc00bc8aSMaximilian Luz 
128fc00bc8aSMaximilian Luz /**
129fc00bc8aSMaximilian Luz  * san_dgpu_notifier_unregister() - Unregister a SAN dGPU notifier.
130fc00bc8aSMaximilian Luz  * @nb: The notifier-block to unregister.
131fc00bc8aSMaximilian Luz  */
san_dgpu_notifier_unregister(struct notifier_block * nb)132fc00bc8aSMaximilian Luz int san_dgpu_notifier_unregister(struct notifier_block *nb)
133fc00bc8aSMaximilian Luz {
134fc00bc8aSMaximilian Luz 	return blocking_notifier_chain_unregister(&san_rqsg_if.nh, nb);
135fc00bc8aSMaximilian Luz }
136fc00bc8aSMaximilian Luz EXPORT_SYMBOL_GPL(san_dgpu_notifier_unregister);
137fc00bc8aSMaximilian Luz 
san_dgpu_notifier_call(struct san_dgpu_event * evt)138fc00bc8aSMaximilian Luz static int san_dgpu_notifier_call(struct san_dgpu_event *evt)
139fc00bc8aSMaximilian Luz {
140fc00bc8aSMaximilian Luz 	int ret;
141fc00bc8aSMaximilian Luz 
142fc00bc8aSMaximilian Luz 	ret = blocking_notifier_call_chain(&san_rqsg_if.nh, evt->command, evt);
143fc00bc8aSMaximilian Luz 	return notifier_to_errno(ret);
144fc00bc8aSMaximilian Luz }
145fc00bc8aSMaximilian Luz 
146fc00bc8aSMaximilian Luz 
147fc00bc8aSMaximilian Luz /* -- ACPI _DSM event relay. ------------------------------------------------ */
148fc00bc8aSMaximilian Luz 
149fc00bc8aSMaximilian Luz #define SAN_DSM_REVISION	0
150fc00bc8aSMaximilian Luz 
151fc00bc8aSMaximilian Luz /* 93b666c5-70c6-469f-a215-3d487c91ab3c */
152fc00bc8aSMaximilian Luz static const guid_t SAN_DSM_UUID =
153fc00bc8aSMaximilian Luz 	GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d,
154fc00bc8aSMaximilian Luz 		  0x48, 0x7c, 0x91, 0xab, 0x3c);
155fc00bc8aSMaximilian Luz 
156fc00bc8aSMaximilian Luz enum san_dsm_event_fn {
157fc00bc8aSMaximilian Luz 	SAN_DSM_EVENT_FN_BAT1_STAT = 0x03,
158fc00bc8aSMaximilian Luz 	SAN_DSM_EVENT_FN_BAT1_INFO = 0x04,
159fc00bc8aSMaximilian Luz 	SAN_DSM_EVENT_FN_ADP1_STAT = 0x05,
160fc00bc8aSMaximilian Luz 	SAN_DSM_EVENT_FN_ADP1_INFO = 0x06,
161fc00bc8aSMaximilian Luz 	SAN_DSM_EVENT_FN_BAT2_STAT = 0x07,
162fc00bc8aSMaximilian Luz 	SAN_DSM_EVENT_FN_BAT2_INFO = 0x08,
163fc00bc8aSMaximilian Luz 	SAN_DSM_EVENT_FN_THERMAL   = 0x09,
164fc00bc8aSMaximilian Luz 	SAN_DSM_EVENT_FN_DPTF      = 0x0a,
165fc00bc8aSMaximilian Luz };
166fc00bc8aSMaximilian Luz 
167fc00bc8aSMaximilian Luz enum sam_event_cid_bat {
168fc00bc8aSMaximilian Luz 	SAM_EVENT_CID_BAT_BIX  = 0x15,
169fc00bc8aSMaximilian Luz 	SAM_EVENT_CID_BAT_BST  = 0x16,
170fc00bc8aSMaximilian Luz 	SAM_EVENT_CID_BAT_ADP  = 0x17,
171fc00bc8aSMaximilian Luz 	SAM_EVENT_CID_BAT_PROT = 0x18,
172fc00bc8aSMaximilian Luz 	SAM_EVENT_CID_BAT_DPTF = 0x4f,
173fc00bc8aSMaximilian Luz };
174fc00bc8aSMaximilian Luz 
175fc00bc8aSMaximilian Luz enum sam_event_cid_tmp {
176fc00bc8aSMaximilian Luz 	SAM_EVENT_CID_TMP_TRIP = 0x0b,
177fc00bc8aSMaximilian Luz };
178fc00bc8aSMaximilian Luz 
179fc00bc8aSMaximilian Luz struct san_event_work {
180fc00bc8aSMaximilian Luz 	struct delayed_work work;
181fc00bc8aSMaximilian Luz 	struct device *dev;
182fc00bc8aSMaximilian Luz 	struct ssam_event event;	/* must be last */
183fc00bc8aSMaximilian Luz };
184fc00bc8aSMaximilian Luz 
san_acpi_notify_event(struct device * dev,u64 func,union acpi_object * param)185fc00bc8aSMaximilian Luz static int san_acpi_notify_event(struct device *dev, u64 func,
186fc00bc8aSMaximilian Luz 				 union acpi_object *param)
187fc00bc8aSMaximilian Luz {
188fc00bc8aSMaximilian Luz 	acpi_handle san = ACPI_HANDLE(dev);
189fc00bc8aSMaximilian Luz 	union acpi_object *obj;
190fc00bc8aSMaximilian Luz 	int status = 0;
191fc00bc8aSMaximilian Luz 
192d26cbdd2SColin Ian King 	if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, BIT_ULL(func)))
193fc00bc8aSMaximilian Luz 		return 0;
194fc00bc8aSMaximilian Luz 
195fc00bc8aSMaximilian Luz 	dev_dbg(dev, "notify event %#04llx\n", func);
196fc00bc8aSMaximilian Luz 
197fc00bc8aSMaximilian Luz 	obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION,
198fc00bc8aSMaximilian Luz 				      func, param, ACPI_TYPE_BUFFER);
199fc00bc8aSMaximilian Luz 	if (!obj)
200fc00bc8aSMaximilian Luz 		return -EFAULT;
201fc00bc8aSMaximilian Luz 
202fc00bc8aSMaximilian Luz 	if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) {
203fc00bc8aSMaximilian Luz 		dev_err(dev, "got unexpected result from _DSM\n");
204fc00bc8aSMaximilian Luz 		status = -EPROTO;
205fc00bc8aSMaximilian Luz 	}
206fc00bc8aSMaximilian Luz 
207fc00bc8aSMaximilian Luz 	ACPI_FREE(obj);
208fc00bc8aSMaximilian Luz 	return status;
209fc00bc8aSMaximilian Luz }
210fc00bc8aSMaximilian Luz 
san_evt_bat_adp(struct device * dev,const struct ssam_event * event)211fc00bc8aSMaximilian Luz static int san_evt_bat_adp(struct device *dev, const struct ssam_event *event)
212fc00bc8aSMaximilian Luz {
213fc00bc8aSMaximilian Luz 	int status;
214fc00bc8aSMaximilian Luz 
215fc00bc8aSMaximilian Luz 	status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_ADP1_STAT, NULL);
216fc00bc8aSMaximilian Luz 	if (status)
217fc00bc8aSMaximilian Luz 		return status;
218fc00bc8aSMaximilian Luz 
219fc00bc8aSMaximilian Luz 	/*
220fc00bc8aSMaximilian Luz 	 * Ensure that the battery states get updated correctly. When the
221fc00bc8aSMaximilian Luz 	 * battery is fully charged and an adapter is plugged in, it sometimes
222fc00bc8aSMaximilian Luz 	 * is not updated correctly, instead showing it as charging.
223fc00bc8aSMaximilian Luz 	 * Explicitly trigger battery updates to fix this.
224fc00bc8aSMaximilian Luz 	 */
225fc00bc8aSMaximilian Luz 
226fc00bc8aSMaximilian Luz 	status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT1_STAT, NULL);
227fc00bc8aSMaximilian Luz 	if (status)
228fc00bc8aSMaximilian Luz 		return status;
229fc00bc8aSMaximilian Luz 
230fc00bc8aSMaximilian Luz 	return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT2_STAT, NULL);
231fc00bc8aSMaximilian Luz }
232fc00bc8aSMaximilian Luz 
san_evt_bat_bix(struct device * dev,const struct ssam_event * event)233fc00bc8aSMaximilian Luz static int san_evt_bat_bix(struct device *dev, const struct ssam_event *event)
234fc00bc8aSMaximilian Luz {
235fc00bc8aSMaximilian Luz 	enum san_dsm_event_fn fn;
236fc00bc8aSMaximilian Luz 
237fc00bc8aSMaximilian Luz 	if (event->instance_id == 0x02)
238fc00bc8aSMaximilian Luz 		fn = SAN_DSM_EVENT_FN_BAT2_INFO;
239fc00bc8aSMaximilian Luz 	else
240fc00bc8aSMaximilian Luz 		fn = SAN_DSM_EVENT_FN_BAT1_INFO;
241fc00bc8aSMaximilian Luz 
242fc00bc8aSMaximilian Luz 	return san_acpi_notify_event(dev, fn, NULL);
243fc00bc8aSMaximilian Luz }
244fc00bc8aSMaximilian Luz 
san_evt_bat_bst(struct device * dev,const struct ssam_event * event)245fc00bc8aSMaximilian Luz static int san_evt_bat_bst(struct device *dev, const struct ssam_event *event)
246fc00bc8aSMaximilian Luz {
247fc00bc8aSMaximilian Luz 	enum san_dsm_event_fn fn;
248fc00bc8aSMaximilian Luz 
249fc00bc8aSMaximilian Luz 	if (event->instance_id == 0x02)
250fc00bc8aSMaximilian Luz 		fn = SAN_DSM_EVENT_FN_BAT2_STAT;
251fc00bc8aSMaximilian Luz 	else
252fc00bc8aSMaximilian Luz 		fn = SAN_DSM_EVENT_FN_BAT1_STAT;
253fc00bc8aSMaximilian Luz 
254fc00bc8aSMaximilian Luz 	return san_acpi_notify_event(dev, fn, NULL);
255fc00bc8aSMaximilian Luz }
256fc00bc8aSMaximilian Luz 
san_evt_bat_dptf(struct device * dev,const struct ssam_event * event)257fc00bc8aSMaximilian Luz static int san_evt_bat_dptf(struct device *dev, const struct ssam_event *event)
258fc00bc8aSMaximilian Luz {
259fc00bc8aSMaximilian Luz 	union acpi_object payload;
260fc00bc8aSMaximilian Luz 
261fc00bc8aSMaximilian Luz 	/*
262fc00bc8aSMaximilian Luz 	 * The Surface ACPI expects a buffer and not a package. It specifically
263fc00bc8aSMaximilian Luz 	 * checks for ObjectType (Arg3) == 0x03. This will cause a warning in
264fc00bc8aSMaximilian Luz 	 * acpica/nsarguments.c, but that warning can be safely ignored.
265fc00bc8aSMaximilian Luz 	 */
266fc00bc8aSMaximilian Luz 	payload.type = ACPI_TYPE_BUFFER;
267fc00bc8aSMaximilian Luz 	payload.buffer.length = event->length;
268fc00bc8aSMaximilian Luz 	payload.buffer.pointer = (u8 *)&event->data[0];
269fc00bc8aSMaximilian Luz 
270fc00bc8aSMaximilian Luz 	return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_DPTF, &payload);
271fc00bc8aSMaximilian Luz }
272fc00bc8aSMaximilian Luz 
san_evt_bat_delay(u8 cid)273fc00bc8aSMaximilian Luz static unsigned long san_evt_bat_delay(u8 cid)
274fc00bc8aSMaximilian Luz {
275fc00bc8aSMaximilian Luz 	switch (cid) {
276fc00bc8aSMaximilian Luz 	case SAM_EVENT_CID_BAT_ADP:
277fc00bc8aSMaximilian Luz 		/*
278fc00bc8aSMaximilian Luz 		 * Wait for battery state to update before signaling adapter
279fc00bc8aSMaximilian Luz 		 * change.
280fc00bc8aSMaximilian Luz 		 */
281fc00bc8aSMaximilian Luz 		return msecs_to_jiffies(5000);
282fc00bc8aSMaximilian Luz 
283fc00bc8aSMaximilian Luz 	case SAM_EVENT_CID_BAT_BST:
284fc00bc8aSMaximilian Luz 		/* Ensure we do not miss anything important due to caching. */
285fc00bc8aSMaximilian Luz 		return msecs_to_jiffies(2000);
286fc00bc8aSMaximilian Luz 
287fc00bc8aSMaximilian Luz 	default:
288fc00bc8aSMaximilian Luz 		return 0;
289fc00bc8aSMaximilian Luz 	}
290fc00bc8aSMaximilian Luz }
291fc00bc8aSMaximilian Luz 
san_evt_bat(const struct ssam_event * event,struct device * dev)292fc00bc8aSMaximilian Luz static bool san_evt_bat(const struct ssam_event *event, struct device *dev)
293fc00bc8aSMaximilian Luz {
294fc00bc8aSMaximilian Luz 	int status;
295fc00bc8aSMaximilian Luz 
296fc00bc8aSMaximilian Luz 	switch (event->command_id) {
297fc00bc8aSMaximilian Luz 	case SAM_EVENT_CID_BAT_BIX:
298fc00bc8aSMaximilian Luz 		status = san_evt_bat_bix(dev, event);
299fc00bc8aSMaximilian Luz 		break;
300fc00bc8aSMaximilian Luz 
301fc00bc8aSMaximilian Luz 	case SAM_EVENT_CID_BAT_BST:
302fc00bc8aSMaximilian Luz 		status = san_evt_bat_bst(dev, event);
303fc00bc8aSMaximilian Luz 		break;
304fc00bc8aSMaximilian Luz 
305fc00bc8aSMaximilian Luz 	case SAM_EVENT_CID_BAT_ADP:
306fc00bc8aSMaximilian Luz 		status = san_evt_bat_adp(dev, event);
307fc00bc8aSMaximilian Luz 		break;
308fc00bc8aSMaximilian Luz 
309fc00bc8aSMaximilian Luz 	case SAM_EVENT_CID_BAT_PROT:
310fc00bc8aSMaximilian Luz 		/*
311fc00bc8aSMaximilian Luz 		 * TODO: Implement support for battery protection status change
312fc00bc8aSMaximilian Luz 		 *       event.
313fc00bc8aSMaximilian Luz 		 */
314fc00bc8aSMaximilian Luz 		return true;
315fc00bc8aSMaximilian Luz 
316fc00bc8aSMaximilian Luz 	case SAM_EVENT_CID_BAT_DPTF:
317fc00bc8aSMaximilian Luz 		status = san_evt_bat_dptf(dev, event);
318fc00bc8aSMaximilian Luz 		break;
319fc00bc8aSMaximilian Luz 
320fc00bc8aSMaximilian Luz 	default:
321fc00bc8aSMaximilian Luz 		return false;
322fc00bc8aSMaximilian Luz 	}
323fc00bc8aSMaximilian Luz 
324fc00bc8aSMaximilian Luz 	if (status) {
325fc00bc8aSMaximilian Luz 		dev_err(dev, "error handling power event (cid = %#04x)\n",
326fc00bc8aSMaximilian Luz 			event->command_id);
327fc00bc8aSMaximilian Luz 	}
328fc00bc8aSMaximilian Luz 
329fc00bc8aSMaximilian Luz 	return true;
330fc00bc8aSMaximilian Luz }
331fc00bc8aSMaximilian Luz 
san_evt_bat_workfn(struct work_struct * work)332fc00bc8aSMaximilian Luz static void san_evt_bat_workfn(struct work_struct *work)
333fc00bc8aSMaximilian Luz {
334fc00bc8aSMaximilian Luz 	struct san_event_work *ev;
335fc00bc8aSMaximilian Luz 
336fc00bc8aSMaximilian Luz 	ev = container_of(work, struct san_event_work, work.work);
337fc00bc8aSMaximilian Luz 	san_evt_bat(&ev->event, ev->dev);
338fc00bc8aSMaximilian Luz 	kfree(ev);
339fc00bc8aSMaximilian Luz }
340fc00bc8aSMaximilian Luz 
san_evt_bat_nf(struct ssam_event_notifier * nf,const struct ssam_event * event)341fc00bc8aSMaximilian Luz static u32 san_evt_bat_nf(struct ssam_event_notifier *nf,
342fc00bc8aSMaximilian Luz 			  const struct ssam_event *event)
343fc00bc8aSMaximilian Luz {
344fc00bc8aSMaximilian Luz 	struct san_data *d = to_san_data(nf, nf_bat);
345fc00bc8aSMaximilian Luz 	struct san_event_work *work;
346fc00bc8aSMaximilian Luz 	unsigned long delay = san_evt_bat_delay(event->command_id);
347fc00bc8aSMaximilian Luz 
348fc00bc8aSMaximilian Luz 	if (delay == 0)
349fc00bc8aSMaximilian Luz 		return san_evt_bat(event, d->dev) ? SSAM_NOTIF_HANDLED : 0;
350fc00bc8aSMaximilian Luz 
351fc00bc8aSMaximilian Luz 	work = kzalloc(sizeof(*work) + event->length, GFP_KERNEL);
352fc00bc8aSMaximilian Luz 	if (!work)
353fc00bc8aSMaximilian Luz 		return ssam_notifier_from_errno(-ENOMEM);
354fc00bc8aSMaximilian Luz 
355fc00bc8aSMaximilian Luz 	INIT_DELAYED_WORK(&work->work, san_evt_bat_workfn);
356fc00bc8aSMaximilian Luz 	work->dev = d->dev;
357fc00bc8aSMaximilian Luz 
358a776bf77SKees Cook 	work->event = *event;
359a776bf77SKees Cook 	memcpy(work->event.data, event->data, event->length);
360fc00bc8aSMaximilian Luz 
36131a1e4a5STetsuo Handa 	queue_delayed_work(san_wq, &work->work, delay);
362fc00bc8aSMaximilian Luz 	return SSAM_NOTIF_HANDLED;
363fc00bc8aSMaximilian Luz }
364fc00bc8aSMaximilian Luz 
san_evt_tmp_trip(struct device * dev,const struct ssam_event * event)365fc00bc8aSMaximilian Luz static int san_evt_tmp_trip(struct device *dev, const struct ssam_event *event)
366fc00bc8aSMaximilian Luz {
367fc00bc8aSMaximilian Luz 	union acpi_object param;
368fc00bc8aSMaximilian Luz 
369fc00bc8aSMaximilian Luz 	/*
370fc00bc8aSMaximilian Luz 	 * The Surface ACPI expects an integer and not a package. This will
371fc00bc8aSMaximilian Luz 	 * cause a warning in acpica/nsarguments.c, but that warning can be
372fc00bc8aSMaximilian Luz 	 * safely ignored.
373fc00bc8aSMaximilian Luz 	 */
374fc00bc8aSMaximilian Luz 	param.type = ACPI_TYPE_INTEGER;
375fc00bc8aSMaximilian Luz 	param.integer.value = event->instance_id;
376fc00bc8aSMaximilian Luz 
377fc00bc8aSMaximilian Luz 	return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_THERMAL, &param);
378fc00bc8aSMaximilian Luz }
379fc00bc8aSMaximilian Luz 
san_evt_tmp(const struct ssam_event * event,struct device * dev)380fc00bc8aSMaximilian Luz static bool san_evt_tmp(const struct ssam_event *event, struct device *dev)
381fc00bc8aSMaximilian Luz {
382fc00bc8aSMaximilian Luz 	int status;
383fc00bc8aSMaximilian Luz 
384fc00bc8aSMaximilian Luz 	switch (event->command_id) {
385fc00bc8aSMaximilian Luz 	case SAM_EVENT_CID_TMP_TRIP:
386fc00bc8aSMaximilian Luz 		status = san_evt_tmp_trip(dev, event);
387fc00bc8aSMaximilian Luz 		break;
388fc00bc8aSMaximilian Luz 
389fc00bc8aSMaximilian Luz 	default:
390fc00bc8aSMaximilian Luz 		return false;
391fc00bc8aSMaximilian Luz 	}
392fc00bc8aSMaximilian Luz 
393fc00bc8aSMaximilian Luz 	if (status) {
394fc00bc8aSMaximilian Luz 		dev_err(dev, "error handling thermal event (cid = %#04x)\n",
395fc00bc8aSMaximilian Luz 			event->command_id);
396fc00bc8aSMaximilian Luz 	}
397fc00bc8aSMaximilian Luz 
398fc00bc8aSMaximilian Luz 	return true;
399fc00bc8aSMaximilian Luz }
400fc00bc8aSMaximilian Luz 
san_evt_tmp_nf(struct ssam_event_notifier * nf,const struct ssam_event * event)401fc00bc8aSMaximilian Luz static u32 san_evt_tmp_nf(struct ssam_event_notifier *nf,
402fc00bc8aSMaximilian Luz 			  const struct ssam_event *event)
403fc00bc8aSMaximilian Luz {
404fc00bc8aSMaximilian Luz 	struct san_data *d = to_san_data(nf, nf_tmp);
405fc00bc8aSMaximilian Luz 
406fc00bc8aSMaximilian Luz 	return san_evt_tmp(event, d->dev) ? SSAM_NOTIF_HANDLED : 0;
407fc00bc8aSMaximilian Luz }
408fc00bc8aSMaximilian Luz 
409fc00bc8aSMaximilian Luz 
410fc00bc8aSMaximilian Luz /* -- ACPI GSB OperationRegion handler -------------------------------------- */
411fc00bc8aSMaximilian Luz 
412fc00bc8aSMaximilian Luz struct gsb_data_in {
413fc00bc8aSMaximilian Luz 	u8 cv;
414fc00bc8aSMaximilian Luz } __packed;
415fc00bc8aSMaximilian Luz 
416fc00bc8aSMaximilian Luz struct gsb_data_rqsx {
417fc00bc8aSMaximilian Luz 	u8 cv;				/* Command value (san_gsb_request_cv). */
418fc00bc8aSMaximilian Luz 	u8 tc;				/* Target category. */
419fc00bc8aSMaximilian Luz 	u8 tid;				/* Target ID. */
420fc00bc8aSMaximilian Luz 	u8 iid;				/* Instance ID. */
421fc00bc8aSMaximilian Luz 	u8 snc;				/* Expect-response-flag. */
422fc00bc8aSMaximilian Luz 	u8 cid;				/* Command ID. */
423fc00bc8aSMaximilian Luz 	u16 cdl;			/* Payload length. */
424fc00bc8aSMaximilian Luz 	u8 pld[];			/* Payload. */
425fc00bc8aSMaximilian Luz } __packed;
426fc00bc8aSMaximilian Luz 
427fc00bc8aSMaximilian Luz struct gsb_data_etwl {
428fc00bc8aSMaximilian Luz 	u8 cv;				/* Command value (should be 0x02). */
429fc00bc8aSMaximilian Luz 	u8 etw3;			/* Unknown. */
430fc00bc8aSMaximilian Luz 	u8 etw4;			/* Unknown. */
431fc00bc8aSMaximilian Luz 	u8 msg[];			/* Error message (ASCIIZ). */
432fc00bc8aSMaximilian Luz } __packed;
433fc00bc8aSMaximilian Luz 
434fc00bc8aSMaximilian Luz struct gsb_data_out {
435fc00bc8aSMaximilian Luz 	u8 status;			/* _SSH communication status. */
436fc00bc8aSMaximilian Luz 	u8 len;				/* _SSH payload length. */
437fc00bc8aSMaximilian Luz 	u8 pld[];			/* _SSH payload. */
438fc00bc8aSMaximilian Luz } __packed;
439fc00bc8aSMaximilian Luz 
440fc00bc8aSMaximilian Luz union gsb_buffer_data {
441fc00bc8aSMaximilian Luz 	struct gsb_data_in   in;	/* Common input. */
442fc00bc8aSMaximilian Luz 	struct gsb_data_rqsx rqsx;	/* RQSX input. */
443fc00bc8aSMaximilian Luz 	struct gsb_data_etwl etwl;	/* ETWL input. */
444fc00bc8aSMaximilian Luz 	struct gsb_data_out  out;	/* Output. */
445fc00bc8aSMaximilian Luz };
446fc00bc8aSMaximilian Luz 
447fc00bc8aSMaximilian Luz struct gsb_buffer {
448fc00bc8aSMaximilian Luz 	u8 status;			/* GSB AttribRawProcess status. */
449fc00bc8aSMaximilian Luz 	u8 len;				/* GSB AttribRawProcess length. */
450fc00bc8aSMaximilian Luz 	union gsb_buffer_data data;
451fc00bc8aSMaximilian Luz } __packed;
452fc00bc8aSMaximilian Luz 
453fc00bc8aSMaximilian Luz #define SAN_GSB_MAX_RQSX_PAYLOAD  (U8_MAX - 2 - sizeof(struct gsb_data_rqsx))
454fc00bc8aSMaximilian Luz #define SAN_GSB_MAX_RESPONSE	  (U8_MAX - 2 - sizeof(struct gsb_data_out))
455fc00bc8aSMaximilian Luz 
456fc00bc8aSMaximilian Luz #define SAN_GSB_COMMAND		0
457fc00bc8aSMaximilian Luz 
458fc00bc8aSMaximilian Luz enum san_gsb_request_cv {
459fc00bc8aSMaximilian Luz 	SAN_GSB_REQUEST_CV_RQST = 0x01,
460fc00bc8aSMaximilian Luz 	SAN_GSB_REQUEST_CV_ETWL = 0x02,
461fc00bc8aSMaximilian Luz 	SAN_GSB_REQUEST_CV_RQSG = 0x03,
462fc00bc8aSMaximilian Luz };
463fc00bc8aSMaximilian Luz 
464fc00bc8aSMaximilian Luz #define SAN_REQUEST_NUM_TRIES	5
465fc00bc8aSMaximilian Luz 
san_etwl(struct san_data * d,struct gsb_buffer * b)466fc00bc8aSMaximilian Luz static acpi_status san_etwl(struct san_data *d, struct gsb_buffer *b)
467fc00bc8aSMaximilian Luz {
468fc00bc8aSMaximilian Luz 	struct gsb_data_etwl *etwl = &b->data.etwl;
469fc00bc8aSMaximilian Luz 
470fc00bc8aSMaximilian Luz 	if (b->len < sizeof(struct gsb_data_etwl)) {
471fc00bc8aSMaximilian Luz 		dev_err(d->dev, "invalid ETWL package (len = %d)\n", b->len);
472fc00bc8aSMaximilian Luz 		return AE_OK;
473fc00bc8aSMaximilian Luz 	}
474fc00bc8aSMaximilian Luz 
475fc00bc8aSMaximilian Luz 	dev_err(d->dev, "ETWL(%#04x, %#04x): %.*s\n", etwl->etw3, etwl->etw4,
476fc00bc8aSMaximilian Luz 		(unsigned int)(b->len - sizeof(struct gsb_data_etwl)),
477fc00bc8aSMaximilian Luz 		(char *)etwl->msg);
478fc00bc8aSMaximilian Luz 
479fc00bc8aSMaximilian Luz 	/* Indicate success. */
480fc00bc8aSMaximilian Luz 	b->status = 0x00;
481fc00bc8aSMaximilian Luz 	b->len = 0x00;
482fc00bc8aSMaximilian Luz 
483fc00bc8aSMaximilian Luz 	return AE_OK;
484fc00bc8aSMaximilian Luz }
485fc00bc8aSMaximilian Luz 
486fc00bc8aSMaximilian Luz static
san_validate_rqsx(struct device * dev,const char * type,struct gsb_buffer * b)487fc00bc8aSMaximilian Luz struct gsb_data_rqsx *san_validate_rqsx(struct device *dev, const char *type,
488fc00bc8aSMaximilian Luz 					struct gsb_buffer *b)
489fc00bc8aSMaximilian Luz {
490fc00bc8aSMaximilian Luz 	struct gsb_data_rqsx *rqsx = &b->data.rqsx;
491fc00bc8aSMaximilian Luz 
492fc00bc8aSMaximilian Luz 	if (b->len < sizeof(struct gsb_data_rqsx)) {
493fc00bc8aSMaximilian Luz 		dev_err(dev, "invalid %s package (len = %d)\n", type, b->len);
494fc00bc8aSMaximilian Luz 		return NULL;
495fc00bc8aSMaximilian Luz 	}
496fc00bc8aSMaximilian Luz 
497fc00bc8aSMaximilian Luz 	if (get_unaligned(&rqsx->cdl) != b->len - sizeof(struct gsb_data_rqsx)) {
498fc00bc8aSMaximilian Luz 		dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n",
499fc00bc8aSMaximilian Luz 			type, b->len, get_unaligned(&rqsx->cdl));
500fc00bc8aSMaximilian Luz 		return NULL;
501fc00bc8aSMaximilian Luz 	}
502fc00bc8aSMaximilian Luz 
503fc00bc8aSMaximilian Luz 	if (get_unaligned(&rqsx->cdl) > SAN_GSB_MAX_RQSX_PAYLOAD) {
504fc00bc8aSMaximilian Luz 		dev_err(dev, "payload for %s package too large (cdl = %d)\n",
505fc00bc8aSMaximilian Luz 			type, get_unaligned(&rqsx->cdl));
506fc00bc8aSMaximilian Luz 		return NULL;
507fc00bc8aSMaximilian Luz 	}
508fc00bc8aSMaximilian Luz 
509fc00bc8aSMaximilian Luz 	return rqsx;
510fc00bc8aSMaximilian Luz }
511fc00bc8aSMaximilian Luz 
gsb_rqsx_response_error(struct gsb_buffer * gsb,int status)512fc00bc8aSMaximilian Luz static void gsb_rqsx_response_error(struct gsb_buffer *gsb, int status)
513fc00bc8aSMaximilian Luz {
514fc00bc8aSMaximilian Luz 	gsb->status = 0x00;
515fc00bc8aSMaximilian Luz 	gsb->len = 0x02;
516fc00bc8aSMaximilian Luz 	gsb->data.out.status = (u8)(-status);
517fc00bc8aSMaximilian Luz 	gsb->data.out.len = 0x00;
518fc00bc8aSMaximilian Luz }
519fc00bc8aSMaximilian Luz 
gsb_rqsx_response_success(struct gsb_buffer * gsb,u8 * ptr,size_t len)520fc00bc8aSMaximilian Luz static void gsb_rqsx_response_success(struct gsb_buffer *gsb, u8 *ptr, size_t len)
521fc00bc8aSMaximilian Luz {
522fc00bc8aSMaximilian Luz 	gsb->status = 0x00;
523fc00bc8aSMaximilian Luz 	gsb->len = len + 2;
524fc00bc8aSMaximilian Luz 	gsb->data.out.status = 0x00;
525fc00bc8aSMaximilian Luz 	gsb->data.out.len = len;
526fc00bc8aSMaximilian Luz 
527fc00bc8aSMaximilian Luz 	if (len)
528fc00bc8aSMaximilian Luz 		memcpy(&gsb->data.out.pld[0], ptr, len);
529fc00bc8aSMaximilian Luz }
530fc00bc8aSMaximilian Luz 
san_rqst_fixup_suspended(struct san_data * d,struct ssam_request * rqst,struct gsb_buffer * gsb)531fc00bc8aSMaximilian Luz static acpi_status san_rqst_fixup_suspended(struct san_data *d,
532fc00bc8aSMaximilian Luz 					    struct ssam_request *rqst,
533fc00bc8aSMaximilian Luz 					    struct gsb_buffer *gsb)
534fc00bc8aSMaximilian Luz {
535fc00bc8aSMaximilian Luz 	if (rqst->target_category == SSAM_SSH_TC_BAS && rqst->command_id == 0x0D) {
536fc00bc8aSMaximilian Luz 		u8 base_state = 1;
537fc00bc8aSMaximilian Luz 
538fc00bc8aSMaximilian Luz 		/* Base state quirk:
539fc00bc8aSMaximilian Luz 		 * The base state may be queried from ACPI when the EC is still
540fc00bc8aSMaximilian Luz 		 * suspended. In this case it will return '-EPERM'. This query
541fc00bc8aSMaximilian Luz 		 * will only be triggered from the ACPI lid GPE interrupt, thus
542fc00bc8aSMaximilian Luz 		 * we are either in laptop or studio mode (base status 0x01 or
543fc00bc8aSMaximilian Luz 		 * 0x02). Furthermore, we will only get here if the device (and
544fc00bc8aSMaximilian Luz 		 * EC) have been suspended.
545fc00bc8aSMaximilian Luz 		 *
546fc00bc8aSMaximilian Luz 		 * We now assume that the device is in laptop mode (0x01). This
547fc00bc8aSMaximilian Luz 		 * has the drawback that it will wake the device when unfolding
548fc00bc8aSMaximilian Luz 		 * it in studio mode, but it also allows us to avoid actively
549fc00bc8aSMaximilian Luz 		 * waiting for the EC to wake up, which may incur a notable
550fc00bc8aSMaximilian Luz 		 * delay.
551fc00bc8aSMaximilian Luz 		 */
552fc00bc8aSMaximilian Luz 
553fc00bc8aSMaximilian Luz 		dev_dbg(d->dev, "rqst: fixup: base-state quirk\n");
554fc00bc8aSMaximilian Luz 
555fc00bc8aSMaximilian Luz 		gsb_rqsx_response_success(gsb, &base_state, sizeof(base_state));
556fc00bc8aSMaximilian Luz 		return AE_OK;
557fc00bc8aSMaximilian Luz 	}
558fc00bc8aSMaximilian Luz 
559fc00bc8aSMaximilian Luz 	gsb_rqsx_response_error(gsb, -ENXIO);
560fc00bc8aSMaximilian Luz 	return AE_OK;
561fc00bc8aSMaximilian Luz }
562fc00bc8aSMaximilian Luz 
san_rqst(struct san_data * d,struct gsb_buffer * buffer)563fc00bc8aSMaximilian Luz static acpi_status san_rqst(struct san_data *d, struct gsb_buffer *buffer)
564fc00bc8aSMaximilian Luz {
565fc00bc8aSMaximilian Luz 	u8 rspbuf[SAN_GSB_MAX_RESPONSE];
566fc00bc8aSMaximilian Luz 	struct gsb_data_rqsx *gsb_rqst;
567fc00bc8aSMaximilian Luz 	struct ssam_request rqst;
568fc00bc8aSMaximilian Luz 	struct ssam_response rsp;
569fc00bc8aSMaximilian Luz 	int status = 0;
570fc00bc8aSMaximilian Luz 
571fc00bc8aSMaximilian Luz 	gsb_rqst = san_validate_rqsx(d->dev, "RQST", buffer);
572fc00bc8aSMaximilian Luz 	if (!gsb_rqst)
573fc00bc8aSMaximilian Luz 		return AE_OK;
574fc00bc8aSMaximilian Luz 
575fc00bc8aSMaximilian Luz 	rqst.target_category = gsb_rqst->tc;
576fc00bc8aSMaximilian Luz 	rqst.target_id = gsb_rqst->tid;
577fc00bc8aSMaximilian Luz 	rqst.command_id = gsb_rqst->cid;
578fc00bc8aSMaximilian Luz 	rqst.instance_id = gsb_rqst->iid;
579fc00bc8aSMaximilian Luz 	rqst.flags = gsb_rqst->snc ? SSAM_REQUEST_HAS_RESPONSE : 0;
580fc00bc8aSMaximilian Luz 	rqst.length = get_unaligned(&gsb_rqst->cdl);
581fc00bc8aSMaximilian Luz 	rqst.payload = &gsb_rqst->pld[0];
582fc00bc8aSMaximilian Luz 
583fc00bc8aSMaximilian Luz 	rsp.capacity = ARRAY_SIZE(rspbuf);
584fc00bc8aSMaximilian Luz 	rsp.length = 0;
585fc00bc8aSMaximilian Luz 	rsp.pointer = &rspbuf[0];
586fc00bc8aSMaximilian Luz 
587fc00bc8aSMaximilian Luz 	/* Handle suspended device. */
588fc00bc8aSMaximilian Luz 	if (d->dev->power.is_suspended) {
589fc00bc8aSMaximilian Luz 		dev_warn(d->dev, "rqst: device is suspended, not executing\n");
590fc00bc8aSMaximilian Luz 		return san_rqst_fixup_suspended(d, &rqst, buffer);
591fc00bc8aSMaximilian Luz 	}
592fc00bc8aSMaximilian Luz 
593*b09ee1cdSMaximilian Luz 	status = __ssam_retry(ssam_request_do_sync_onstack, SAN_REQUEST_NUM_TRIES,
594fc00bc8aSMaximilian Luz 			      d->ctrl, &rqst, &rsp, SAN_GSB_MAX_RQSX_PAYLOAD);
595fc00bc8aSMaximilian Luz 
596fc00bc8aSMaximilian Luz 	if (!status) {
597fc00bc8aSMaximilian Luz 		gsb_rqsx_response_success(buffer, rsp.pointer, rsp.length);
598fc00bc8aSMaximilian Luz 	} else {
599fc00bc8aSMaximilian Luz 		dev_err(d->dev, "rqst: failed with error %d\n", status);
600fc00bc8aSMaximilian Luz 		gsb_rqsx_response_error(buffer, status);
601fc00bc8aSMaximilian Luz 	}
602fc00bc8aSMaximilian Luz 
603fc00bc8aSMaximilian Luz 	return AE_OK;
604fc00bc8aSMaximilian Luz }
605fc00bc8aSMaximilian Luz 
san_rqsg(struct san_data * d,struct gsb_buffer * buffer)606fc00bc8aSMaximilian Luz static acpi_status san_rqsg(struct san_data *d, struct gsb_buffer *buffer)
607fc00bc8aSMaximilian Luz {
608fc00bc8aSMaximilian Luz 	struct gsb_data_rqsx *gsb_rqsg;
609fc00bc8aSMaximilian Luz 	struct san_dgpu_event evt;
610fc00bc8aSMaximilian Luz 	int status;
611fc00bc8aSMaximilian Luz 
612fc00bc8aSMaximilian Luz 	gsb_rqsg = san_validate_rqsx(d->dev, "RQSG", buffer);
613fc00bc8aSMaximilian Luz 	if (!gsb_rqsg)
614fc00bc8aSMaximilian Luz 		return AE_OK;
615fc00bc8aSMaximilian Luz 
616fc00bc8aSMaximilian Luz 	evt.category = gsb_rqsg->tc;
617fc00bc8aSMaximilian Luz 	evt.target = gsb_rqsg->tid;
618fc00bc8aSMaximilian Luz 	evt.command = gsb_rqsg->cid;
619fc00bc8aSMaximilian Luz 	evt.instance = gsb_rqsg->iid;
620fc00bc8aSMaximilian Luz 	evt.length = get_unaligned(&gsb_rqsg->cdl);
621fc00bc8aSMaximilian Luz 	evt.payload = &gsb_rqsg->pld[0];
622fc00bc8aSMaximilian Luz 
623fc00bc8aSMaximilian Luz 	status = san_dgpu_notifier_call(&evt);
624fc00bc8aSMaximilian Luz 	if (!status) {
625fc00bc8aSMaximilian Luz 		gsb_rqsx_response_success(buffer, NULL, 0);
626fc00bc8aSMaximilian Luz 	} else {
627fc00bc8aSMaximilian Luz 		dev_err(d->dev, "rqsg: failed with error %d\n", status);
628fc00bc8aSMaximilian Luz 		gsb_rqsx_response_error(buffer, status);
629fc00bc8aSMaximilian Luz 	}
630fc00bc8aSMaximilian Luz 
631fc00bc8aSMaximilian Luz 	return AE_OK;
632fc00bc8aSMaximilian Luz }
633fc00bc8aSMaximilian Luz 
san_opreg_handler(u32 function,acpi_physical_address command,u32 bits,u64 * value64,void * opreg_context,void * region_context)634fc00bc8aSMaximilian Luz static acpi_status san_opreg_handler(u32 function, acpi_physical_address command,
635fc00bc8aSMaximilian Luz 				     u32 bits, u64 *value64, void *opreg_context,
636fc00bc8aSMaximilian Luz 				     void *region_context)
637fc00bc8aSMaximilian Luz {
638fc00bc8aSMaximilian Luz 	struct san_data *d = to_san_data(opreg_context, info);
639fc00bc8aSMaximilian Luz 	struct gsb_buffer *buffer = (struct gsb_buffer *)value64;
640fc00bc8aSMaximilian Luz 	int accessor_type = (function & 0xFFFF0000) >> 16;
641fc00bc8aSMaximilian Luz 
642fc00bc8aSMaximilian Luz 	if (command != SAN_GSB_COMMAND) {
643fc00bc8aSMaximilian Luz 		dev_warn(d->dev, "unsupported command: %#04llx\n", command);
644fc00bc8aSMaximilian Luz 		return AE_OK;
645fc00bc8aSMaximilian Luz 	}
646fc00bc8aSMaximilian Luz 
647fc00bc8aSMaximilian Luz 	if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) {
648fc00bc8aSMaximilian Luz 		dev_err(d->dev, "invalid access type: %#04x\n", accessor_type);
649fc00bc8aSMaximilian Luz 		return AE_OK;
650fc00bc8aSMaximilian Luz 	}
651fc00bc8aSMaximilian Luz 
652fc00bc8aSMaximilian Luz 	/* Buffer must have at least contain the command-value. */
653fc00bc8aSMaximilian Luz 	if (buffer->len == 0) {
654fc00bc8aSMaximilian Luz 		dev_err(d->dev, "request-package too small\n");
655fc00bc8aSMaximilian Luz 		return AE_OK;
656fc00bc8aSMaximilian Luz 	}
657fc00bc8aSMaximilian Luz 
658fc00bc8aSMaximilian Luz 	switch (buffer->data.in.cv) {
659fc00bc8aSMaximilian Luz 	case SAN_GSB_REQUEST_CV_RQST:
660fc00bc8aSMaximilian Luz 		return san_rqst(d, buffer);
661fc00bc8aSMaximilian Luz 
662fc00bc8aSMaximilian Luz 	case SAN_GSB_REQUEST_CV_ETWL:
663fc00bc8aSMaximilian Luz 		return san_etwl(d, buffer);
664fc00bc8aSMaximilian Luz 
665fc00bc8aSMaximilian Luz 	case SAN_GSB_REQUEST_CV_RQSG:
666fc00bc8aSMaximilian Luz 		return san_rqsg(d, buffer);
667fc00bc8aSMaximilian Luz 
668fc00bc8aSMaximilian Luz 	default:
669fc00bc8aSMaximilian Luz 		dev_warn(d->dev, "unsupported SAN0 request (cv: %#04x)\n",
670fc00bc8aSMaximilian Luz 			 buffer->data.in.cv);
671fc00bc8aSMaximilian Luz 		return AE_OK;
672fc00bc8aSMaximilian Luz 	}
673fc00bc8aSMaximilian Luz }
674fc00bc8aSMaximilian Luz 
675fc00bc8aSMaximilian Luz 
676fc00bc8aSMaximilian Luz /* -- Driver setup. --------------------------------------------------------- */
677fc00bc8aSMaximilian Luz 
san_events_register(struct platform_device * pdev)678fc00bc8aSMaximilian Luz static int san_events_register(struct platform_device *pdev)
679fc00bc8aSMaximilian Luz {
680fc00bc8aSMaximilian Luz 	struct san_data *d = platform_get_drvdata(pdev);
681fc00bc8aSMaximilian Luz 	int status;
682fc00bc8aSMaximilian Luz 
683fc00bc8aSMaximilian Luz 	d->nf_bat.base.priority = 1;
684fc00bc8aSMaximilian Luz 	d->nf_bat.base.fn = san_evt_bat_nf;
685fc00bc8aSMaximilian Luz 	d->nf_bat.event.reg = SSAM_EVENT_REGISTRY_SAM;
686fc00bc8aSMaximilian Luz 	d->nf_bat.event.id.target_category = SSAM_SSH_TC_BAT;
687fc00bc8aSMaximilian Luz 	d->nf_bat.event.id.instance = 0;
688fc00bc8aSMaximilian Luz 	d->nf_bat.event.mask = SSAM_EVENT_MASK_TARGET;
689fc00bc8aSMaximilian Luz 	d->nf_bat.event.flags = SSAM_EVENT_SEQUENCED;
690fc00bc8aSMaximilian Luz 
691fc00bc8aSMaximilian Luz 	d->nf_tmp.base.priority = 1;
692fc00bc8aSMaximilian Luz 	d->nf_tmp.base.fn = san_evt_tmp_nf;
693fc00bc8aSMaximilian Luz 	d->nf_tmp.event.reg = SSAM_EVENT_REGISTRY_SAM;
694fc00bc8aSMaximilian Luz 	d->nf_tmp.event.id.target_category = SSAM_SSH_TC_TMP;
695fc00bc8aSMaximilian Luz 	d->nf_tmp.event.id.instance = 0;
696fc00bc8aSMaximilian Luz 	d->nf_tmp.event.mask = SSAM_EVENT_MASK_TARGET;
697fc00bc8aSMaximilian Luz 	d->nf_tmp.event.flags = SSAM_EVENT_SEQUENCED;
698fc00bc8aSMaximilian Luz 
699fc00bc8aSMaximilian Luz 	status = ssam_notifier_register(d->ctrl, &d->nf_bat);
700fc00bc8aSMaximilian Luz 	if (status)
701fc00bc8aSMaximilian Luz 		return status;
702fc00bc8aSMaximilian Luz 
703fc00bc8aSMaximilian Luz 	status = ssam_notifier_register(d->ctrl, &d->nf_tmp);
704fc00bc8aSMaximilian Luz 	if (status)
705fc00bc8aSMaximilian Luz 		ssam_notifier_unregister(d->ctrl, &d->nf_bat);
706fc00bc8aSMaximilian Luz 
707fc00bc8aSMaximilian Luz 	return status;
708fc00bc8aSMaximilian Luz }
709fc00bc8aSMaximilian Luz 
san_events_unregister(struct platform_device * pdev)710fc00bc8aSMaximilian Luz static void san_events_unregister(struct platform_device *pdev)
711fc00bc8aSMaximilian Luz {
712fc00bc8aSMaximilian Luz 	struct san_data *d = platform_get_drvdata(pdev);
713fc00bc8aSMaximilian Luz 
714fc00bc8aSMaximilian Luz 	ssam_notifier_unregister(d->ctrl, &d->nf_bat);
715fc00bc8aSMaximilian Luz 	ssam_notifier_unregister(d->ctrl, &d->nf_tmp);
716fc00bc8aSMaximilian Luz }
717fc00bc8aSMaximilian Luz 
718fc00bc8aSMaximilian Luz #define san_consumer_printk(level, dev, handle, fmt, ...)			\
719fc00bc8aSMaximilian Luz do {										\
720fc00bc8aSMaximilian Luz 	char *path = "<error getting consumer path>";				\
721fc00bc8aSMaximilian Luz 	struct acpi_buffer buffer = {						\
722fc00bc8aSMaximilian Luz 		.length = ACPI_ALLOCATE_BUFFER,					\
723fc00bc8aSMaximilian Luz 		.pointer = NULL,						\
724fc00bc8aSMaximilian Luz 	};									\
725fc00bc8aSMaximilian Luz 										\
726fc00bc8aSMaximilian Luz 	if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer)))	\
727fc00bc8aSMaximilian Luz 		path = buffer.pointer;						\
728fc00bc8aSMaximilian Luz 										\
729fc00bc8aSMaximilian Luz 	dev_##level(dev, "[%s]: " fmt, path, ##__VA_ARGS__);			\
730fc00bc8aSMaximilian Luz 	kfree(buffer.pointer);							\
731fc00bc8aSMaximilian Luz } while (0)
732fc00bc8aSMaximilian Luz 
733fc00bc8aSMaximilian Luz #define san_consumer_dbg(dev, handle, fmt, ...) \
734fc00bc8aSMaximilian Luz 	san_consumer_printk(dbg, dev, handle, fmt, ##__VA_ARGS__)
735fc00bc8aSMaximilian Luz 
736fc00bc8aSMaximilian Luz #define san_consumer_warn(dev, handle, fmt, ...) \
737fc00bc8aSMaximilian Luz 	san_consumer_printk(warn, dev, handle, fmt, ##__VA_ARGS__)
738fc00bc8aSMaximilian Luz 
is_san_consumer(struct platform_device * pdev,acpi_handle handle)739fc00bc8aSMaximilian Luz static bool is_san_consumer(struct platform_device *pdev, acpi_handle handle)
740fc00bc8aSMaximilian Luz {
741fc00bc8aSMaximilian Luz 	struct acpi_handle_list dep_devices;
742fc00bc8aSMaximilian Luz 	acpi_handle supplier = ACPI_HANDLE(&pdev->dev);
743fc00bc8aSMaximilian Luz 	acpi_status status;
744fc00bc8aSMaximilian Luz 	int i;
745fc00bc8aSMaximilian Luz 
746fc00bc8aSMaximilian Luz 	if (!acpi_has_method(handle, "_DEP"))
747fc00bc8aSMaximilian Luz 		return false;
748fc00bc8aSMaximilian Luz 
749fc00bc8aSMaximilian Luz 	status = acpi_evaluate_reference(handle, "_DEP", NULL, &dep_devices);
750fc00bc8aSMaximilian Luz 	if (ACPI_FAILURE(status)) {
751fc00bc8aSMaximilian Luz 		san_consumer_dbg(&pdev->dev, handle, "failed to evaluate _DEP\n");
752fc00bc8aSMaximilian Luz 		return false;
753fc00bc8aSMaximilian Luz 	}
754fc00bc8aSMaximilian Luz 
755fc00bc8aSMaximilian Luz 	for (i = 0; i < dep_devices.count; i++) {
756fc00bc8aSMaximilian Luz 		if (dep_devices.handles[i] == supplier)
757fc00bc8aSMaximilian Luz 			return true;
758fc00bc8aSMaximilian Luz 	}
759fc00bc8aSMaximilian Luz 
760fc00bc8aSMaximilian Luz 	return false;
761fc00bc8aSMaximilian Luz }
762fc00bc8aSMaximilian Luz 
san_consumer_setup(acpi_handle handle,u32 lvl,void * context,void ** rv)763fc00bc8aSMaximilian Luz static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl,
764fc00bc8aSMaximilian Luz 				      void *context, void **rv)
765fc00bc8aSMaximilian Luz {
766fc00bc8aSMaximilian Luz 	const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER;
767fc00bc8aSMaximilian Luz 	struct platform_device *pdev = context;
768fc00bc8aSMaximilian Luz 	struct acpi_device *adev;
769fc00bc8aSMaximilian Luz 	struct device_link *link;
770fc00bc8aSMaximilian Luz 
771fc00bc8aSMaximilian Luz 	if (!is_san_consumer(pdev, handle))
772fc00bc8aSMaximilian Luz 		return AE_OK;
773fc00bc8aSMaximilian Luz 
774fc00bc8aSMaximilian Luz 	/* Ignore ACPI devices that are not present. */
7756768bddbSRafael J. Wysocki 	adev = acpi_fetch_acpi_dev(handle);
7766768bddbSRafael J. Wysocki 	if (!adev)
777fc00bc8aSMaximilian Luz 		return AE_OK;
778fc00bc8aSMaximilian Luz 
779fc00bc8aSMaximilian Luz 	san_consumer_dbg(&pdev->dev, handle, "creating device link\n");
780fc00bc8aSMaximilian Luz 
781fc00bc8aSMaximilian Luz 	/* Try to set up device links, ignore but log errors. */
782fc00bc8aSMaximilian Luz 	link = device_link_add(&adev->dev, &pdev->dev, flags);
783fc00bc8aSMaximilian Luz 	if (!link) {
784fc00bc8aSMaximilian Luz 		san_consumer_warn(&pdev->dev, handle, "failed to create device link\n");
785fc00bc8aSMaximilian Luz 		return AE_OK;
786fc00bc8aSMaximilian Luz 	}
787fc00bc8aSMaximilian Luz 
788fc00bc8aSMaximilian Luz 	return AE_OK;
789fc00bc8aSMaximilian Luz }
790fc00bc8aSMaximilian Luz 
san_consumer_links_setup(struct platform_device * pdev)791fc00bc8aSMaximilian Luz static int san_consumer_links_setup(struct platform_device *pdev)
792fc00bc8aSMaximilian Luz {
793fc00bc8aSMaximilian Luz 	acpi_status status;
794fc00bc8aSMaximilian Luz 
795fc00bc8aSMaximilian Luz 	status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
796fc00bc8aSMaximilian Luz 				     ACPI_UINT32_MAX, san_consumer_setup, NULL,
797fc00bc8aSMaximilian Luz 				     pdev, NULL);
798fc00bc8aSMaximilian Luz 
799fc00bc8aSMaximilian Luz 	return status ? -EFAULT : 0;
800fc00bc8aSMaximilian Luz }
801fc00bc8aSMaximilian Luz 
san_probe(struct platform_device * pdev)802fc00bc8aSMaximilian Luz static int san_probe(struct platform_device *pdev)
803fc00bc8aSMaximilian Luz {
804a9e10e58SDaniel Scally 	struct acpi_device *san = ACPI_COMPANION(&pdev->dev);
805fc00bc8aSMaximilian Luz 	struct ssam_controller *ctrl;
806fc00bc8aSMaximilian Luz 	struct san_data *data;
807fc00bc8aSMaximilian Luz 	acpi_status astatus;
808fc00bc8aSMaximilian Luz 	int status;
809fc00bc8aSMaximilian Luz 
810fc00bc8aSMaximilian Luz 	ctrl = ssam_client_bind(&pdev->dev);
811fc00bc8aSMaximilian Luz 	if (IS_ERR(ctrl))
812fc00bc8aSMaximilian Luz 		return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
813fc00bc8aSMaximilian Luz 
814fc00bc8aSMaximilian Luz 	status = san_consumer_links_setup(pdev);
815fc00bc8aSMaximilian Luz 	if (status)
816fc00bc8aSMaximilian Luz 		return status;
817fc00bc8aSMaximilian Luz 
818fc00bc8aSMaximilian Luz 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
819fc00bc8aSMaximilian Luz 	if (!data)
820fc00bc8aSMaximilian Luz 		return -ENOMEM;
821fc00bc8aSMaximilian Luz 
822fc00bc8aSMaximilian Luz 	data->dev = &pdev->dev;
823fc00bc8aSMaximilian Luz 	data->ctrl = ctrl;
824fc00bc8aSMaximilian Luz 
825fc00bc8aSMaximilian Luz 	platform_set_drvdata(pdev, data);
826fc00bc8aSMaximilian Luz 
827a9e10e58SDaniel Scally 	astatus = acpi_install_address_space_handler(san->handle,
828a9e10e58SDaniel Scally 						     ACPI_ADR_SPACE_GSBUS,
829fc00bc8aSMaximilian Luz 						     &san_opreg_handler, NULL,
830fc00bc8aSMaximilian Luz 						     &data->info);
831fc00bc8aSMaximilian Luz 	if (ACPI_FAILURE(astatus))
832fc00bc8aSMaximilian Luz 		return -ENXIO;
833fc00bc8aSMaximilian Luz 
834fc00bc8aSMaximilian Luz 	status = san_events_register(pdev);
835fc00bc8aSMaximilian Luz 	if (status)
836fc00bc8aSMaximilian Luz 		goto err_enable_events;
837fc00bc8aSMaximilian Luz 
838fc00bc8aSMaximilian Luz 	status = san_set_rqsg_interface_device(&pdev->dev);
839fc00bc8aSMaximilian Luz 	if (status)
840fc00bc8aSMaximilian Luz 		goto err_install_dev;
841fc00bc8aSMaximilian Luz 
842a9e10e58SDaniel Scally 	acpi_dev_clear_dependencies(san);
843fc00bc8aSMaximilian Luz 	return 0;
844fc00bc8aSMaximilian Luz 
845fc00bc8aSMaximilian Luz err_install_dev:
846fc00bc8aSMaximilian Luz 	san_events_unregister(pdev);
847fc00bc8aSMaximilian Luz err_enable_events:
848fc00bc8aSMaximilian Luz 	acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS,
849fc00bc8aSMaximilian Luz 					  &san_opreg_handler);
850fc00bc8aSMaximilian Luz 	return status;
851fc00bc8aSMaximilian Luz }
852fc00bc8aSMaximilian Luz 
san_remove(struct platform_device * pdev)853fc00bc8aSMaximilian Luz static int san_remove(struct platform_device *pdev)
854fc00bc8aSMaximilian Luz {
855fc00bc8aSMaximilian Luz 	acpi_handle san = ACPI_HANDLE(&pdev->dev);
856fc00bc8aSMaximilian Luz 
857fc00bc8aSMaximilian Luz 	san_set_rqsg_interface_device(NULL);
858fc00bc8aSMaximilian Luz 	acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS,
859fc00bc8aSMaximilian Luz 					  &san_opreg_handler);
860fc00bc8aSMaximilian Luz 	san_events_unregister(pdev);
861fc00bc8aSMaximilian Luz 
862fc00bc8aSMaximilian Luz 	/*
863fc00bc8aSMaximilian Luz 	 * We have unregistered our event sources. Now we need to ensure that
864fc00bc8aSMaximilian Luz 	 * all delayed works they may have spawned are run to completion.
865fc00bc8aSMaximilian Luz 	 */
86631a1e4a5STetsuo Handa 	flush_workqueue(san_wq);
867fc00bc8aSMaximilian Luz 
868fc00bc8aSMaximilian Luz 	return 0;
869fc00bc8aSMaximilian Luz }
870fc00bc8aSMaximilian Luz 
871fc00bc8aSMaximilian Luz static const struct acpi_device_id san_match[] = {
872fc00bc8aSMaximilian Luz 	{ "MSHW0091" },
873fc00bc8aSMaximilian Luz 	{ },
874fc00bc8aSMaximilian Luz };
875fc00bc8aSMaximilian Luz MODULE_DEVICE_TABLE(acpi, san_match);
876fc00bc8aSMaximilian Luz 
877fc00bc8aSMaximilian Luz static struct platform_driver surface_acpi_notify = {
878fc00bc8aSMaximilian Luz 	.probe = san_probe,
879fc00bc8aSMaximilian Luz 	.remove = san_remove,
880fc00bc8aSMaximilian Luz 	.driver = {
881fc00bc8aSMaximilian Luz 		.name = "surface_acpi_notify",
882fc00bc8aSMaximilian Luz 		.acpi_match_table = san_match,
883fc00bc8aSMaximilian Luz 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
884fc00bc8aSMaximilian Luz 	},
885fc00bc8aSMaximilian Luz };
88631a1e4a5STetsuo Handa 
san_init(void)88731a1e4a5STetsuo Handa static int __init san_init(void)
88831a1e4a5STetsuo Handa {
88931a1e4a5STetsuo Handa 	int ret;
89031a1e4a5STetsuo Handa 
89131a1e4a5STetsuo Handa 	san_wq = alloc_workqueue("san_wq", 0, 0);
89231a1e4a5STetsuo Handa 	if (!san_wq)
89331a1e4a5STetsuo Handa 		return -ENOMEM;
89431a1e4a5STetsuo Handa 	ret = platform_driver_register(&surface_acpi_notify);
89531a1e4a5STetsuo Handa 	if (ret)
89631a1e4a5STetsuo Handa 		destroy_workqueue(san_wq);
89731a1e4a5STetsuo Handa 	return ret;
89831a1e4a5STetsuo Handa }
89931a1e4a5STetsuo Handa module_init(san_init);
90031a1e4a5STetsuo Handa 
san_exit(void)90131a1e4a5STetsuo Handa static void __exit san_exit(void)
90231a1e4a5STetsuo Handa {
90331a1e4a5STetsuo Handa 	platform_driver_unregister(&surface_acpi_notify);
90431a1e4a5STetsuo Handa 	destroy_workqueue(san_wq);
90531a1e4a5STetsuo Handa }
90631a1e4a5STetsuo Handa module_exit(san_exit);
907fc00bc8aSMaximilian Luz 
908fc00bc8aSMaximilian Luz MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
909fc00bc8aSMaximilian Luz MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module");
910fc00bc8aSMaximilian Luz MODULE_LICENSE("GPL");
911