1c167b9c7SMaximilian Luz // SPDX-License-Identifier: GPL-2.0+
2c167b9c7SMaximilian Luz /*
3c167b9c7SMaximilian Luz  * Main SSAM/SSH controller structure and functionality.
4c167b9c7SMaximilian Luz  *
5c167b9c7SMaximilian Luz  * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
6c167b9c7SMaximilian Luz  */
7c167b9c7SMaximilian Luz 
8c167b9c7SMaximilian Luz #include <linux/acpi.h>
9c167b9c7SMaximilian Luz #include <linux/atomic.h>
10c167b9c7SMaximilian Luz #include <linux/completion.h>
11c167b9c7SMaximilian Luz #include <linux/gpio/consumer.h>
12c167b9c7SMaximilian Luz #include <linux/interrupt.h>
13c167b9c7SMaximilian Luz #include <linux/kref.h>
14c167b9c7SMaximilian Luz #include <linux/limits.h>
15c167b9c7SMaximilian Luz #include <linux/list.h>
16c167b9c7SMaximilian Luz #include <linux/lockdep.h>
17c167b9c7SMaximilian Luz #include <linux/mutex.h>
18c167b9c7SMaximilian Luz #include <linux/rculist.h>
19c167b9c7SMaximilian Luz #include <linux/rbtree.h>
20c167b9c7SMaximilian Luz #include <linux/rwsem.h>
21c167b9c7SMaximilian Luz #include <linux/serdev.h>
22c167b9c7SMaximilian Luz #include <linux/slab.h>
23c167b9c7SMaximilian Luz #include <linux/spinlock.h>
24c167b9c7SMaximilian Luz #include <linux/srcu.h>
25c167b9c7SMaximilian Luz #include <linux/types.h>
26c167b9c7SMaximilian Luz #include <linux/workqueue.h>
27c167b9c7SMaximilian Luz 
28c167b9c7SMaximilian Luz #include <linux/surface_aggregator/controller.h>
29c167b9c7SMaximilian Luz #include <linux/surface_aggregator/serial_hub.h>
30c167b9c7SMaximilian Luz 
31c167b9c7SMaximilian Luz #include "controller.h"
32c167b9c7SMaximilian Luz #include "ssh_msgb.h"
33c167b9c7SMaximilian Luz #include "ssh_request_layer.h"
34c167b9c7SMaximilian Luz 
350d21bb85SMaximilian Luz #include "trace.h"
360d21bb85SMaximilian Luz 
37c167b9c7SMaximilian Luz 
38c167b9c7SMaximilian Luz /* -- Safe counters. -------------------------------------------------------- */
39c167b9c7SMaximilian Luz 
40c167b9c7SMaximilian Luz /**
41c167b9c7SMaximilian Luz  * ssh_seq_reset() - Reset/initialize sequence ID counter.
42c167b9c7SMaximilian Luz  * @c: The counter to reset.
43c167b9c7SMaximilian Luz  */
44c167b9c7SMaximilian Luz static void ssh_seq_reset(struct ssh_seq_counter *c)
45c167b9c7SMaximilian Luz {
46c167b9c7SMaximilian Luz 	WRITE_ONCE(c->value, 0);
47c167b9c7SMaximilian Luz }
48c167b9c7SMaximilian Luz 
49c167b9c7SMaximilian Luz /**
50c167b9c7SMaximilian Luz  * ssh_seq_next() - Get next sequence ID.
51c167b9c7SMaximilian Luz  * @c: The counter providing the sequence IDs.
52c167b9c7SMaximilian Luz  *
53c167b9c7SMaximilian Luz  * Return: Returns the next sequence ID of the counter.
54c167b9c7SMaximilian Luz  */
55c167b9c7SMaximilian Luz static u8 ssh_seq_next(struct ssh_seq_counter *c)
56c167b9c7SMaximilian Luz {
57c167b9c7SMaximilian Luz 	u8 old = READ_ONCE(c->value);
58c167b9c7SMaximilian Luz 	u8 new = old + 1;
59c167b9c7SMaximilian Luz 	u8 ret;
60c167b9c7SMaximilian Luz 
61c167b9c7SMaximilian Luz 	while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) {
62c167b9c7SMaximilian Luz 		old = ret;
63c167b9c7SMaximilian Luz 		new = old + 1;
64c167b9c7SMaximilian Luz 	}
65c167b9c7SMaximilian Luz 
66c167b9c7SMaximilian Luz 	return old;
67c167b9c7SMaximilian Luz }
68c167b9c7SMaximilian Luz 
69c167b9c7SMaximilian Luz /**
70c167b9c7SMaximilian Luz  * ssh_rqid_reset() - Reset/initialize request ID counter.
71c167b9c7SMaximilian Luz  * @c: The counter to reset.
72c167b9c7SMaximilian Luz  */
73c167b9c7SMaximilian Luz static void ssh_rqid_reset(struct ssh_rqid_counter *c)
74c167b9c7SMaximilian Luz {
75c167b9c7SMaximilian Luz 	WRITE_ONCE(c->value, 0);
76c167b9c7SMaximilian Luz }
77c167b9c7SMaximilian Luz 
78c167b9c7SMaximilian Luz /**
79c167b9c7SMaximilian Luz  * ssh_rqid_next() - Get next request ID.
80c167b9c7SMaximilian Luz  * @c: The counter providing the request IDs.
81c167b9c7SMaximilian Luz  *
82c167b9c7SMaximilian Luz  * Return: Returns the next request ID of the counter, skipping any reserved
83c167b9c7SMaximilian Luz  * request IDs.
84c167b9c7SMaximilian Luz  */
85c167b9c7SMaximilian Luz static u16 ssh_rqid_next(struct ssh_rqid_counter *c)
86c167b9c7SMaximilian Luz {
87c167b9c7SMaximilian Luz 	u16 old = READ_ONCE(c->value);
88c167b9c7SMaximilian Luz 	u16 new = ssh_rqid_next_valid(old);
89c167b9c7SMaximilian Luz 	u16 ret;
90c167b9c7SMaximilian Luz 
91c167b9c7SMaximilian Luz 	while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) {
92c167b9c7SMaximilian Luz 		old = ret;
93c167b9c7SMaximilian Luz 		new = ssh_rqid_next_valid(old);
94c167b9c7SMaximilian Luz 	}
95c167b9c7SMaximilian Luz 
96c167b9c7SMaximilian Luz 	return old;
97c167b9c7SMaximilian Luz }
98c167b9c7SMaximilian Luz 
99c167b9c7SMaximilian Luz 
100c167b9c7SMaximilian Luz /* -- Event notifier/callbacks. --------------------------------------------- */
101c167b9c7SMaximilian Luz /*
102c167b9c7SMaximilian Luz  * The notifier system is based on linux/notifier.h, specifically the SRCU
103c167b9c7SMaximilian Luz  * implementation. The difference to that is, that some bits of the notifier
104c167b9c7SMaximilian Luz  * call return value can be tracked across multiple calls. This is done so
105c167b9c7SMaximilian Luz  * that handling of events can be tracked and a warning can be issued in case
106c167b9c7SMaximilian Luz  * an event goes unhandled. The idea of that warning is that it should help
107c167b9c7SMaximilian Luz  * discover and identify new/currently unimplemented features.
108c167b9c7SMaximilian Luz  */
109c167b9c7SMaximilian Luz 
110c167b9c7SMaximilian Luz /**
111c167b9c7SMaximilian Luz  * ssam_event_matches_notifier() - Test if an event matches a notifier.
112c167b9c7SMaximilian Luz  * @n: The event notifier to test against.
113c167b9c7SMaximilian Luz  * @event: The event to test.
114c167b9c7SMaximilian Luz  *
115c167b9c7SMaximilian Luz  * Return: Returns %true if the given event matches the given notifier
116c167b9c7SMaximilian Luz  * according to the rules set in the notifier's event mask, %false otherwise.
117c167b9c7SMaximilian Luz  */
118c167b9c7SMaximilian Luz static bool ssam_event_matches_notifier(const struct ssam_event_notifier *n,
119c167b9c7SMaximilian Luz 					const struct ssam_event *event)
120c167b9c7SMaximilian Luz {
121c167b9c7SMaximilian Luz 	bool match = n->event.id.target_category == event->target_category;
122c167b9c7SMaximilian Luz 
123c167b9c7SMaximilian Luz 	if (n->event.mask & SSAM_EVENT_MASK_TARGET)
124c167b9c7SMaximilian Luz 		match &= n->event.reg.target_id == event->target_id;
125c167b9c7SMaximilian Luz 
126c167b9c7SMaximilian Luz 	if (n->event.mask & SSAM_EVENT_MASK_INSTANCE)
127c167b9c7SMaximilian Luz 		match &= n->event.id.instance == event->instance_id;
128c167b9c7SMaximilian Luz 
129c167b9c7SMaximilian Luz 	return match;
130c167b9c7SMaximilian Luz }
131c167b9c7SMaximilian Luz 
132c167b9c7SMaximilian Luz /**
133c167b9c7SMaximilian Luz  * ssam_nfblk_call_chain() - Call event notifier callbacks of the given chain.
134c167b9c7SMaximilian Luz  * @nh:    The notifier head for which the notifier callbacks should be called.
135c167b9c7SMaximilian Luz  * @event: The event data provided to the callbacks.
136c167b9c7SMaximilian Luz  *
137c167b9c7SMaximilian Luz  * Call all registered notifier callbacks in order of their priority until
138c167b9c7SMaximilian Luz  * either no notifier is left or a notifier returns a value with the
139c167b9c7SMaximilian Luz  * %SSAM_NOTIF_STOP bit set. Note that this bit is automatically set via
140c167b9c7SMaximilian Luz  * ssam_notifier_from_errno() on any non-zero error value.
141c167b9c7SMaximilian Luz  *
142c167b9c7SMaximilian Luz  * Return: Returns the notifier status value, which contains the notifier
143c167b9c7SMaximilian Luz  * status bits (%SSAM_NOTIF_HANDLED and %SSAM_NOTIF_STOP) as well as a
144c167b9c7SMaximilian Luz  * potential error value returned from the last executed notifier callback.
145c167b9c7SMaximilian Luz  * Use ssam_notifier_to_errno() to convert this value to the original error
146c167b9c7SMaximilian Luz  * value.
147c167b9c7SMaximilian Luz  */
148c167b9c7SMaximilian Luz static int ssam_nfblk_call_chain(struct ssam_nf_head *nh, struct ssam_event *event)
149c167b9c7SMaximilian Luz {
150c167b9c7SMaximilian Luz 	struct ssam_event_notifier *nf;
151c167b9c7SMaximilian Luz 	int ret = 0, idx;
152c167b9c7SMaximilian Luz 
153c167b9c7SMaximilian Luz 	idx = srcu_read_lock(&nh->srcu);
154c167b9c7SMaximilian Luz 
155c167b9c7SMaximilian Luz 	list_for_each_entry_rcu(nf, &nh->head, base.node,
156c167b9c7SMaximilian Luz 				srcu_read_lock_held(&nh->srcu)) {
157c167b9c7SMaximilian Luz 		if (ssam_event_matches_notifier(nf, event)) {
158c167b9c7SMaximilian Luz 			ret = (ret & SSAM_NOTIF_STATE_MASK) | nf->base.fn(nf, event);
159c167b9c7SMaximilian Luz 			if (ret & SSAM_NOTIF_STOP)
160c167b9c7SMaximilian Luz 				break;
161c167b9c7SMaximilian Luz 		}
162c167b9c7SMaximilian Luz 	}
163c167b9c7SMaximilian Luz 
164c167b9c7SMaximilian Luz 	srcu_read_unlock(&nh->srcu, idx);
165c167b9c7SMaximilian Luz 	return ret;
166c167b9c7SMaximilian Luz }
167c167b9c7SMaximilian Luz 
168c167b9c7SMaximilian Luz /**
169c167b9c7SMaximilian Luz  * ssam_nfblk_insert() - Insert a new notifier block into the given notifier
170c167b9c7SMaximilian Luz  * list.
171c167b9c7SMaximilian Luz  * @nh: The notifier head into which the block should be inserted.
172c167b9c7SMaximilian Luz  * @nb: The notifier block to add.
173c167b9c7SMaximilian Luz  *
174c167b9c7SMaximilian Luz  * Note: This function must be synchronized by the caller with respect to other
175c167b9c7SMaximilian Luz  * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``.
176c167b9c7SMaximilian Luz  *
177c167b9c7SMaximilian Luz  * Return: Returns zero on success, %-EEXIST if the notifier block has already
178c167b9c7SMaximilian Luz  * been registered.
179c167b9c7SMaximilian Luz  */
180c167b9c7SMaximilian Luz static int ssam_nfblk_insert(struct ssam_nf_head *nh, struct ssam_notifier_block *nb)
181c167b9c7SMaximilian Luz {
182c167b9c7SMaximilian Luz 	struct ssam_notifier_block *p;
183c167b9c7SMaximilian Luz 	struct list_head *h;
184c167b9c7SMaximilian Luz 
185c167b9c7SMaximilian Luz 	/* Runs under lock, no need for RCU variant. */
186c167b9c7SMaximilian Luz 	list_for_each(h, &nh->head) {
187c167b9c7SMaximilian Luz 		p = list_entry(h, struct ssam_notifier_block, node);
188c167b9c7SMaximilian Luz 
189c167b9c7SMaximilian Luz 		if (unlikely(p == nb)) {
190c167b9c7SMaximilian Luz 			WARN(1, "double register detected");
191c167b9c7SMaximilian Luz 			return -EEXIST;
192c167b9c7SMaximilian Luz 		}
193c167b9c7SMaximilian Luz 
194c167b9c7SMaximilian Luz 		if (nb->priority > p->priority)
195c167b9c7SMaximilian Luz 			break;
196c167b9c7SMaximilian Luz 	}
197c167b9c7SMaximilian Luz 
198c167b9c7SMaximilian Luz 	list_add_tail_rcu(&nb->node, h);
199c167b9c7SMaximilian Luz 	return 0;
200c167b9c7SMaximilian Luz }
201c167b9c7SMaximilian Luz 
202c167b9c7SMaximilian Luz /**
203c167b9c7SMaximilian Luz  * ssam_nfblk_find() - Check if a notifier block is registered on the given
204c167b9c7SMaximilian Luz  * notifier head.
205c167b9c7SMaximilian Luz  * list.
206c167b9c7SMaximilian Luz  * @nh: The notifier head on which to search.
207c167b9c7SMaximilian Luz  * @nb: The notifier block to search for.
208c167b9c7SMaximilian Luz  *
209c167b9c7SMaximilian Luz  * Note: This function must be synchronized by the caller with respect to other
210c167b9c7SMaximilian Luz  * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``.
211c167b9c7SMaximilian Luz  *
212c167b9c7SMaximilian Luz  * Return: Returns true if the given notifier block is registered on the given
213c167b9c7SMaximilian Luz  * notifier head, false otherwise.
214c167b9c7SMaximilian Luz  */
215c167b9c7SMaximilian Luz static bool ssam_nfblk_find(struct ssam_nf_head *nh, struct ssam_notifier_block *nb)
216c167b9c7SMaximilian Luz {
217c167b9c7SMaximilian Luz 	struct ssam_notifier_block *p;
218c167b9c7SMaximilian Luz 
219c167b9c7SMaximilian Luz 	/* Runs under lock, no need for RCU variant. */
220c167b9c7SMaximilian Luz 	list_for_each_entry(p, &nh->head, node) {
221c167b9c7SMaximilian Luz 		if (p == nb)
222c167b9c7SMaximilian Luz 			return true;
223c167b9c7SMaximilian Luz 	}
224c167b9c7SMaximilian Luz 
225c167b9c7SMaximilian Luz 	return false;
226c167b9c7SMaximilian Luz }
227c167b9c7SMaximilian Luz 
228c167b9c7SMaximilian Luz /**
229c167b9c7SMaximilian Luz  * ssam_nfblk_remove() - Remove a notifier block from its notifier list.
230c167b9c7SMaximilian Luz  * @nb: The notifier block to be removed.
231c167b9c7SMaximilian Luz  *
232c167b9c7SMaximilian Luz  * Note: This function must be synchronized by the caller with respect to
233c167b9c7SMaximilian Luz  * other insert, find, and/or remove calls by holding ``struct ssam_nf.lock``.
234c167b9c7SMaximilian Luz  * Furthermore, the caller _must_ ensure SRCU synchronization by calling
235c167b9c7SMaximilian Luz  * synchronize_srcu() with ``nh->srcu`` after leaving the critical section, to
236c167b9c7SMaximilian Luz  * ensure that the removed notifier block is not in use any more.
237c167b9c7SMaximilian Luz  */
238c167b9c7SMaximilian Luz static void ssam_nfblk_remove(struct ssam_notifier_block *nb)
239c167b9c7SMaximilian Luz {
240c167b9c7SMaximilian Luz 	list_del_rcu(&nb->node);
241c167b9c7SMaximilian Luz }
242c167b9c7SMaximilian Luz 
243c167b9c7SMaximilian Luz /**
244c167b9c7SMaximilian Luz  * ssam_nf_head_init() - Initialize the given notifier head.
245c167b9c7SMaximilian Luz  * @nh: The notifier head to initialize.
246c167b9c7SMaximilian Luz  */
247c167b9c7SMaximilian Luz static int ssam_nf_head_init(struct ssam_nf_head *nh)
248c167b9c7SMaximilian Luz {
249c167b9c7SMaximilian Luz 	int status;
250c167b9c7SMaximilian Luz 
251c167b9c7SMaximilian Luz 	status = init_srcu_struct(&nh->srcu);
252c167b9c7SMaximilian Luz 	if (status)
253c167b9c7SMaximilian Luz 		return status;
254c167b9c7SMaximilian Luz 
255c167b9c7SMaximilian Luz 	INIT_LIST_HEAD(&nh->head);
256c167b9c7SMaximilian Luz 	return 0;
257c167b9c7SMaximilian Luz }
258c167b9c7SMaximilian Luz 
259c167b9c7SMaximilian Luz /**
260c167b9c7SMaximilian Luz  * ssam_nf_head_destroy() - Deinitialize the given notifier head.
261c167b9c7SMaximilian Luz  * @nh: The notifier head to deinitialize.
262c167b9c7SMaximilian Luz  */
263c167b9c7SMaximilian Luz static void ssam_nf_head_destroy(struct ssam_nf_head *nh)
264c167b9c7SMaximilian Luz {
265c167b9c7SMaximilian Luz 	cleanup_srcu_struct(&nh->srcu);
266c167b9c7SMaximilian Luz }
267c167b9c7SMaximilian Luz 
268c167b9c7SMaximilian Luz 
269c167b9c7SMaximilian Luz /* -- Event/notification registry. ------------------------------------------ */
270c167b9c7SMaximilian Luz 
271c167b9c7SMaximilian Luz /**
272c167b9c7SMaximilian Luz  * struct ssam_nf_refcount_key - Key used for event activation reference
273c167b9c7SMaximilian Luz  * counting.
274c167b9c7SMaximilian Luz  * @reg: The registry via which the event is enabled/disabled.
275c167b9c7SMaximilian Luz  * @id:  The ID uniquely describing the event.
276c167b9c7SMaximilian Luz  */
277c167b9c7SMaximilian Luz struct ssam_nf_refcount_key {
278c167b9c7SMaximilian Luz 	struct ssam_event_registry reg;
279c167b9c7SMaximilian Luz 	struct ssam_event_id id;
280c167b9c7SMaximilian Luz };
281c167b9c7SMaximilian Luz 
282c167b9c7SMaximilian Luz /**
283c167b9c7SMaximilian Luz  * struct ssam_nf_refcount_entry - RB-tree entry for reference counting event
284c167b9c7SMaximilian Luz  * activations.
285c167b9c7SMaximilian Luz  * @node:     The node of this entry in the rb-tree.
286c167b9c7SMaximilian Luz  * @key:      The key of the event.
287c167b9c7SMaximilian Luz  * @refcount: The reference-count of the event.
288c167b9c7SMaximilian Luz  * @flags:    The flags used when enabling the event.
289c167b9c7SMaximilian Luz  */
290c167b9c7SMaximilian Luz struct ssam_nf_refcount_entry {
291c167b9c7SMaximilian Luz 	struct rb_node node;
292c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_key key;
293c167b9c7SMaximilian Luz 	int refcount;
294c167b9c7SMaximilian Luz 	u8 flags;
295c167b9c7SMaximilian Luz };
296c167b9c7SMaximilian Luz 
297c167b9c7SMaximilian Luz /**
298c167b9c7SMaximilian Luz  * ssam_nf_refcount_inc() - Increment reference-/activation-count of the given
299c167b9c7SMaximilian Luz  * event.
300c167b9c7SMaximilian Luz  * @nf:  The notifier system reference.
301c167b9c7SMaximilian Luz  * @reg: The registry used to enable/disable the event.
302c167b9c7SMaximilian Luz  * @id:  The event ID.
303c167b9c7SMaximilian Luz  *
304c167b9c7SMaximilian Luz  * Increments the reference-/activation-count associated with the specified
305c167b9c7SMaximilian Luz  * event type/ID, allocating a new entry for this event ID if necessary. A
306c167b9c7SMaximilian Luz  * newly allocated entry will have a refcount of one.
307c167b9c7SMaximilian Luz  *
308c167b9c7SMaximilian Luz  * Note: ``nf->lock`` must be held when calling this function.
309c167b9c7SMaximilian Luz  *
310c167b9c7SMaximilian Luz  * Return: Returns the refcount entry on success. Returns an error pointer
311c167b9c7SMaximilian Luz  * with %-ENOSPC if there have already been %INT_MAX events of the specified
312c167b9c7SMaximilian Luz  * ID and type registered, or %-ENOMEM if the entry could not be allocated.
313c167b9c7SMaximilian Luz  */
314c167b9c7SMaximilian Luz static struct ssam_nf_refcount_entry *
315c167b9c7SMaximilian Luz ssam_nf_refcount_inc(struct ssam_nf *nf, struct ssam_event_registry reg,
316c167b9c7SMaximilian Luz 		     struct ssam_event_id id)
317c167b9c7SMaximilian Luz {
318c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_entry *entry;
319c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_key key;
320c167b9c7SMaximilian Luz 	struct rb_node **link = &nf->refcount.rb_node;
321c167b9c7SMaximilian Luz 	struct rb_node *parent = NULL;
322c167b9c7SMaximilian Luz 	int cmp;
323c167b9c7SMaximilian Luz 
324c167b9c7SMaximilian Luz 	lockdep_assert_held(&nf->lock);
325c167b9c7SMaximilian Luz 
326c167b9c7SMaximilian Luz 	key.reg = reg;
327c167b9c7SMaximilian Luz 	key.id = id;
328c167b9c7SMaximilian Luz 
329c167b9c7SMaximilian Luz 	while (*link) {
330c167b9c7SMaximilian Luz 		entry = rb_entry(*link, struct ssam_nf_refcount_entry, node);
331c167b9c7SMaximilian Luz 		parent = *link;
332c167b9c7SMaximilian Luz 
333c167b9c7SMaximilian Luz 		cmp = memcmp(&key, &entry->key, sizeof(key));
334c167b9c7SMaximilian Luz 		if (cmp < 0) {
335c167b9c7SMaximilian Luz 			link = &(*link)->rb_left;
336c167b9c7SMaximilian Luz 		} else if (cmp > 0) {
337c167b9c7SMaximilian Luz 			link = &(*link)->rb_right;
338c167b9c7SMaximilian Luz 		} else if (entry->refcount < INT_MAX) {
339c167b9c7SMaximilian Luz 			entry->refcount++;
340c167b9c7SMaximilian Luz 			return entry;
341c167b9c7SMaximilian Luz 		} else {
342c167b9c7SMaximilian Luz 			WARN_ON(1);
343c167b9c7SMaximilian Luz 			return ERR_PTR(-ENOSPC);
344c167b9c7SMaximilian Luz 		}
345c167b9c7SMaximilian Luz 	}
346c167b9c7SMaximilian Luz 
347c167b9c7SMaximilian Luz 	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
348c167b9c7SMaximilian Luz 	if (!entry)
349c167b9c7SMaximilian Luz 		return ERR_PTR(-ENOMEM);
350c167b9c7SMaximilian Luz 
351c167b9c7SMaximilian Luz 	entry->key = key;
352c167b9c7SMaximilian Luz 	entry->refcount = 1;
353c167b9c7SMaximilian Luz 
354c167b9c7SMaximilian Luz 	rb_link_node(&entry->node, parent, link);
355c167b9c7SMaximilian Luz 	rb_insert_color(&entry->node, &nf->refcount);
356c167b9c7SMaximilian Luz 
357c167b9c7SMaximilian Luz 	return entry;
358c167b9c7SMaximilian Luz }
359c167b9c7SMaximilian Luz 
360c167b9c7SMaximilian Luz /**
361c167b9c7SMaximilian Luz  * ssam_nf_refcount_dec() - Decrement reference-/activation-count of the given
362c167b9c7SMaximilian Luz  * event.
363c167b9c7SMaximilian Luz  * @nf:  The notifier system reference.
364c167b9c7SMaximilian Luz  * @reg: The registry used to enable/disable the event.
365c167b9c7SMaximilian Luz  * @id:  The event ID.
366c167b9c7SMaximilian Luz  *
367c167b9c7SMaximilian Luz  * Decrements the reference-/activation-count of the specified event,
368c167b9c7SMaximilian Luz  * returning its entry. If the returned entry has a refcount of zero, the
369c167b9c7SMaximilian Luz  * caller is responsible for freeing it using kfree().
370c167b9c7SMaximilian Luz  *
371c167b9c7SMaximilian Luz  * Note: ``nf->lock`` must be held when calling this function.
372c167b9c7SMaximilian Luz  *
373c167b9c7SMaximilian Luz  * Return: Returns the refcount entry on success or %NULL if the entry has not
374c167b9c7SMaximilian Luz  * been found.
375c167b9c7SMaximilian Luz  */
376c167b9c7SMaximilian Luz static struct ssam_nf_refcount_entry *
377c167b9c7SMaximilian Luz ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg,
378c167b9c7SMaximilian Luz 		     struct ssam_event_id id)
379c167b9c7SMaximilian Luz {
380c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_entry *entry;
381c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_key key;
382c167b9c7SMaximilian Luz 	struct rb_node *node = nf->refcount.rb_node;
383c167b9c7SMaximilian Luz 	int cmp;
384c167b9c7SMaximilian Luz 
385c167b9c7SMaximilian Luz 	lockdep_assert_held(&nf->lock);
386c167b9c7SMaximilian Luz 
387c167b9c7SMaximilian Luz 	key.reg = reg;
388c167b9c7SMaximilian Luz 	key.id = id;
389c167b9c7SMaximilian Luz 
390c167b9c7SMaximilian Luz 	while (node) {
391c167b9c7SMaximilian Luz 		entry = rb_entry(node, struct ssam_nf_refcount_entry, node);
392c167b9c7SMaximilian Luz 
393c167b9c7SMaximilian Luz 		cmp = memcmp(&key, &entry->key, sizeof(key));
394c167b9c7SMaximilian Luz 		if (cmp < 0) {
395c167b9c7SMaximilian Luz 			node = node->rb_left;
396c167b9c7SMaximilian Luz 		} else if (cmp > 0) {
397c167b9c7SMaximilian Luz 			node = node->rb_right;
398c167b9c7SMaximilian Luz 		} else {
399c167b9c7SMaximilian Luz 			entry->refcount--;
400c167b9c7SMaximilian Luz 			if (entry->refcount == 0)
401c167b9c7SMaximilian Luz 				rb_erase(&entry->node, &nf->refcount);
402c167b9c7SMaximilian Luz 
403c167b9c7SMaximilian Luz 			return entry;
404c167b9c7SMaximilian Luz 		}
405c167b9c7SMaximilian Luz 	}
406c167b9c7SMaximilian Luz 
407c167b9c7SMaximilian Luz 	return NULL;
408c167b9c7SMaximilian Luz }
409c167b9c7SMaximilian Luz 
410c167b9c7SMaximilian Luz /**
411c167b9c7SMaximilian Luz  * ssam_nf_refcount_empty() - Test if the notification system has any
412c167b9c7SMaximilian Luz  * enabled/active events.
413c167b9c7SMaximilian Luz  * @nf: The notification system.
414c167b9c7SMaximilian Luz  */
415c167b9c7SMaximilian Luz static bool ssam_nf_refcount_empty(struct ssam_nf *nf)
416c167b9c7SMaximilian Luz {
417c167b9c7SMaximilian Luz 	return RB_EMPTY_ROOT(&nf->refcount);
418c167b9c7SMaximilian Luz }
419c167b9c7SMaximilian Luz 
420c167b9c7SMaximilian Luz /**
421c167b9c7SMaximilian Luz  * ssam_nf_call() - Call notification callbacks for the provided event.
422c167b9c7SMaximilian Luz  * @nf:    The notifier system
423c167b9c7SMaximilian Luz  * @dev:   The associated device, only used for logging.
424c167b9c7SMaximilian Luz  * @rqid:  The request ID of the event.
425c167b9c7SMaximilian Luz  * @event: The event provided to the callbacks.
426c167b9c7SMaximilian Luz  *
427c167b9c7SMaximilian Luz  * Execute registered callbacks in order of their priority until either no
428c167b9c7SMaximilian Luz  * callback is left or a callback returns a value with the %SSAM_NOTIF_STOP
429c167b9c7SMaximilian Luz  * bit set. Note that this bit is set automatically when converting non-zero
430c167b9c7SMaximilian Luz  * error values via ssam_notifier_from_errno() to notifier values.
431c167b9c7SMaximilian Luz  *
432c167b9c7SMaximilian Luz  * Also note that any callback that could handle an event should return a value
433c167b9c7SMaximilian Luz  * with bit %SSAM_NOTIF_HANDLED set, indicating that the event does not go
434c167b9c7SMaximilian Luz  * unhandled/ignored. In case no registered callback could handle an event,
435c167b9c7SMaximilian Luz  * this function will emit a warning.
436c167b9c7SMaximilian Luz  *
437c167b9c7SMaximilian Luz  * In case a callback failed, this function will emit an error message.
438c167b9c7SMaximilian Luz  */
439c167b9c7SMaximilian Luz static void ssam_nf_call(struct ssam_nf *nf, struct device *dev, u16 rqid,
440c167b9c7SMaximilian Luz 			 struct ssam_event *event)
441c167b9c7SMaximilian Luz {
442c167b9c7SMaximilian Luz 	struct ssam_nf_head *nf_head;
443c167b9c7SMaximilian Luz 	int status, nf_ret;
444c167b9c7SMaximilian Luz 
445c167b9c7SMaximilian Luz 	if (!ssh_rqid_is_event(rqid)) {
446c167b9c7SMaximilian Luz 		dev_warn(dev, "event: unsupported rqid: %#06x\n", rqid);
447c167b9c7SMaximilian Luz 		return;
448c167b9c7SMaximilian Luz 	}
449c167b9c7SMaximilian Luz 
450c167b9c7SMaximilian Luz 	nf_head = &nf->head[ssh_rqid_to_event(rqid)];
451c167b9c7SMaximilian Luz 	nf_ret = ssam_nfblk_call_chain(nf_head, event);
452c167b9c7SMaximilian Luz 	status = ssam_notifier_to_errno(nf_ret);
453c167b9c7SMaximilian Luz 
454c167b9c7SMaximilian Luz 	if (status < 0) {
455c167b9c7SMaximilian Luz 		dev_err(dev,
456c167b9c7SMaximilian Luz 			"event: error handling event: %d (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n",
457c167b9c7SMaximilian Luz 			status, event->target_category, event->target_id,
458c167b9c7SMaximilian Luz 			event->command_id, event->instance_id);
459c167b9c7SMaximilian Luz 	} else if (!(nf_ret & SSAM_NOTIF_HANDLED)) {
460c167b9c7SMaximilian Luz 		dev_warn(dev,
461c167b9c7SMaximilian Luz 			 "event: unhandled event (rqid: %#04x, tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n",
462c167b9c7SMaximilian Luz 			 rqid, event->target_category, event->target_id,
463c167b9c7SMaximilian Luz 			 event->command_id, event->instance_id);
464c167b9c7SMaximilian Luz 	}
465c167b9c7SMaximilian Luz }
466c167b9c7SMaximilian Luz 
467c167b9c7SMaximilian Luz /**
468c167b9c7SMaximilian Luz  * ssam_nf_init() - Initialize the notifier system.
469c167b9c7SMaximilian Luz  * @nf: The notifier system to initialize.
470c167b9c7SMaximilian Luz  */
471c167b9c7SMaximilian Luz static int ssam_nf_init(struct ssam_nf *nf)
472c167b9c7SMaximilian Luz {
473c167b9c7SMaximilian Luz 	int i, status;
474c167b9c7SMaximilian Luz 
475c167b9c7SMaximilian Luz 	for (i = 0; i < SSH_NUM_EVENTS; i++) {
476c167b9c7SMaximilian Luz 		status = ssam_nf_head_init(&nf->head[i]);
477c167b9c7SMaximilian Luz 		if (status)
478c167b9c7SMaximilian Luz 			break;
479c167b9c7SMaximilian Luz 	}
480c167b9c7SMaximilian Luz 
481c167b9c7SMaximilian Luz 	if (status) {
482c167b9c7SMaximilian Luz 		while (i--)
483c167b9c7SMaximilian Luz 			ssam_nf_head_destroy(&nf->head[i]);
484c167b9c7SMaximilian Luz 
485c167b9c7SMaximilian Luz 		return status;
486c167b9c7SMaximilian Luz 	}
487c167b9c7SMaximilian Luz 
488c167b9c7SMaximilian Luz 	mutex_init(&nf->lock);
489c167b9c7SMaximilian Luz 	return 0;
490c167b9c7SMaximilian Luz }
491c167b9c7SMaximilian Luz 
492c167b9c7SMaximilian Luz /**
493c167b9c7SMaximilian Luz  * ssam_nf_destroy() - Deinitialize the notifier system.
494c167b9c7SMaximilian Luz  * @nf: The notifier system to deinitialize.
495c167b9c7SMaximilian Luz  */
496c167b9c7SMaximilian Luz static void ssam_nf_destroy(struct ssam_nf *nf)
497c167b9c7SMaximilian Luz {
498c167b9c7SMaximilian Luz 	int i;
499c167b9c7SMaximilian Luz 
500c167b9c7SMaximilian Luz 	for (i = 0; i < SSH_NUM_EVENTS; i++)
501c167b9c7SMaximilian Luz 		ssam_nf_head_destroy(&nf->head[i]);
502c167b9c7SMaximilian Luz 
503c167b9c7SMaximilian Luz 	mutex_destroy(&nf->lock);
504c167b9c7SMaximilian Luz }
505c167b9c7SMaximilian Luz 
506c167b9c7SMaximilian Luz 
507c167b9c7SMaximilian Luz /* -- Event/async request completion system. -------------------------------- */
508c167b9c7SMaximilian Luz 
509c167b9c7SMaximilian Luz #define SSAM_CPLT_WQ_NAME	"ssam_cpltq"
510c167b9c7SMaximilian Luz 
511c167b9c7SMaximilian Luz /*
512c167b9c7SMaximilian Luz  * SSAM_CPLT_WQ_BATCH - Maximum number of event item completions executed per
513c167b9c7SMaximilian Luz  * work execution. Used to prevent livelocking of the workqueue. Value chosen
514c167b9c7SMaximilian Luz  * via educated guess, may be adjusted.
515c167b9c7SMaximilian Luz  */
516c167b9c7SMaximilian Luz #define SSAM_CPLT_WQ_BATCH	10
517c167b9c7SMaximilian Luz 
5183a7081f6SMaximilian Luz /*
5193a7081f6SMaximilian Luz  * SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN - Maximum payload length for a cached
5203a7081f6SMaximilian Luz  * &struct ssam_event_item.
5213a7081f6SMaximilian Luz  *
5223a7081f6SMaximilian Luz  * This length has been chosen to be accommodate standard touchpad and
5233a7081f6SMaximilian Luz  * keyboard input events. Events with larger payloads will be allocated
5243a7081f6SMaximilian Luz  * separately.
5253a7081f6SMaximilian Luz  */
5263a7081f6SMaximilian Luz #define SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN	32
5273a7081f6SMaximilian Luz 
5283a7081f6SMaximilian Luz static struct kmem_cache *ssam_event_item_cache;
5293a7081f6SMaximilian Luz 
5303a7081f6SMaximilian Luz /**
5313a7081f6SMaximilian Luz  * ssam_event_item_cache_init() - Initialize the event item cache.
5323a7081f6SMaximilian Luz  */
5333a7081f6SMaximilian Luz int ssam_event_item_cache_init(void)
5343a7081f6SMaximilian Luz {
5353a7081f6SMaximilian Luz 	const unsigned int size = sizeof(struct ssam_event_item)
5363a7081f6SMaximilian Luz 				  + SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN;
5373a7081f6SMaximilian Luz 	const unsigned int align = __alignof__(struct ssam_event_item);
5383a7081f6SMaximilian Luz 	struct kmem_cache *cache;
5393a7081f6SMaximilian Luz 
5403a7081f6SMaximilian Luz 	cache = kmem_cache_create("ssam_event_item", size, align, 0, NULL);
5413a7081f6SMaximilian Luz 	if (!cache)
5423a7081f6SMaximilian Luz 		return -ENOMEM;
5433a7081f6SMaximilian Luz 
5443a7081f6SMaximilian Luz 	ssam_event_item_cache = cache;
5453a7081f6SMaximilian Luz 	return 0;
5463a7081f6SMaximilian Luz }
5473a7081f6SMaximilian Luz 
5483a7081f6SMaximilian Luz /**
5493a7081f6SMaximilian Luz  * ssam_event_item_cache_destroy() - Deinitialize the event item cache.
5503a7081f6SMaximilian Luz  */
5513a7081f6SMaximilian Luz void ssam_event_item_cache_destroy(void)
5523a7081f6SMaximilian Luz {
5533a7081f6SMaximilian Luz 	kmem_cache_destroy(ssam_event_item_cache);
5543a7081f6SMaximilian Luz 	ssam_event_item_cache = NULL;
5553a7081f6SMaximilian Luz }
5563a7081f6SMaximilian Luz 
5573a7081f6SMaximilian Luz static void __ssam_event_item_free_cached(struct ssam_event_item *item)
5583a7081f6SMaximilian Luz {
5593a7081f6SMaximilian Luz 	kmem_cache_free(ssam_event_item_cache, item);
5603a7081f6SMaximilian Luz }
5613a7081f6SMaximilian Luz 
5623a7081f6SMaximilian Luz static void __ssam_event_item_free_generic(struct ssam_event_item *item)
5633a7081f6SMaximilian Luz {
5643a7081f6SMaximilian Luz 	kfree(item);
5653a7081f6SMaximilian Luz }
5663a7081f6SMaximilian Luz 
5673a7081f6SMaximilian Luz /**
5683a7081f6SMaximilian Luz  * ssam_event_item_free() - Free the provided event item.
5693a7081f6SMaximilian Luz  * @item: The event item to free.
5703a7081f6SMaximilian Luz  */
5713a7081f6SMaximilian Luz static void ssam_event_item_free(struct ssam_event_item *item)
5723a7081f6SMaximilian Luz {
5730d21bb85SMaximilian Luz 	trace_ssam_event_item_free(item);
5743a7081f6SMaximilian Luz 	item->ops.free(item);
5753a7081f6SMaximilian Luz }
5763a7081f6SMaximilian Luz 
577c167b9c7SMaximilian Luz /**
578c167b9c7SMaximilian Luz  * ssam_event_item_alloc() - Allocate an event item with the given payload size.
579c167b9c7SMaximilian Luz  * @len:   The event payload length.
580c167b9c7SMaximilian Luz  * @flags: The flags used for allocation.
581c167b9c7SMaximilian Luz  *
5823a7081f6SMaximilian Luz  * Allocate an event item with the given payload size, preferring allocation
5833a7081f6SMaximilian Luz  * from the event item cache if the payload is small enough (i.e. smaller than
5843a7081f6SMaximilian Luz  * %SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN). Sets the item operations and payload
5853a7081f6SMaximilian Luz  * length values. The item free callback (``ops.free``) should not be
5863a7081f6SMaximilian Luz  * overwritten after this call.
587c167b9c7SMaximilian Luz  *
588c167b9c7SMaximilian Luz  * Return: Returns the newly allocated event item.
589c167b9c7SMaximilian Luz  */
590c167b9c7SMaximilian Luz static struct ssam_event_item *ssam_event_item_alloc(size_t len, gfp_t flags)
591c167b9c7SMaximilian Luz {
592c167b9c7SMaximilian Luz 	struct ssam_event_item *item;
593c167b9c7SMaximilian Luz 
5943a7081f6SMaximilian Luz 	if (len <= SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN) {
5953a7081f6SMaximilian Luz 		item = kmem_cache_alloc(ssam_event_item_cache, flags);
5963a7081f6SMaximilian Luz 		if (!item)
5973a7081f6SMaximilian Luz 			return NULL;
5983a7081f6SMaximilian Luz 
5993a7081f6SMaximilian Luz 		item->ops.free = __ssam_event_item_free_cached;
6003a7081f6SMaximilian Luz 	} else {
601c167b9c7SMaximilian Luz 		item = kzalloc(struct_size(item, event.data, len), flags);
602c167b9c7SMaximilian Luz 		if (!item)
603c167b9c7SMaximilian Luz 			return NULL;
604c167b9c7SMaximilian Luz 
6053a7081f6SMaximilian Luz 		item->ops.free = __ssam_event_item_free_generic;
6063a7081f6SMaximilian Luz 	}
6073a7081f6SMaximilian Luz 
608c167b9c7SMaximilian Luz 	item->event.length = len;
6090d21bb85SMaximilian Luz 
6100d21bb85SMaximilian Luz 	trace_ssam_event_item_alloc(item, len);
611c167b9c7SMaximilian Luz 	return item;
612c167b9c7SMaximilian Luz }
613c167b9c7SMaximilian Luz 
614c167b9c7SMaximilian Luz /**
615c167b9c7SMaximilian Luz  * ssam_event_queue_push() - Push an event item to the event queue.
616c167b9c7SMaximilian Luz  * @q:    The event queue.
617c167b9c7SMaximilian Luz  * @item: The item to add.
618c167b9c7SMaximilian Luz  */
619c167b9c7SMaximilian Luz static void ssam_event_queue_push(struct ssam_event_queue *q,
620c167b9c7SMaximilian Luz 				  struct ssam_event_item *item)
621c167b9c7SMaximilian Luz {
622c167b9c7SMaximilian Luz 	spin_lock(&q->lock);
623c167b9c7SMaximilian Luz 	list_add_tail(&item->node, &q->head);
624c167b9c7SMaximilian Luz 	spin_unlock(&q->lock);
625c167b9c7SMaximilian Luz }
626c167b9c7SMaximilian Luz 
627c167b9c7SMaximilian Luz /**
628c167b9c7SMaximilian Luz  * ssam_event_queue_pop() - Pop the next event item from the event queue.
629c167b9c7SMaximilian Luz  * @q: The event queue.
630c167b9c7SMaximilian Luz  *
631c167b9c7SMaximilian Luz  * Returns and removes the next event item from the queue. Returns %NULL If
632c167b9c7SMaximilian Luz  * there is no event item left.
633c167b9c7SMaximilian Luz  */
634c167b9c7SMaximilian Luz static struct ssam_event_item *ssam_event_queue_pop(struct ssam_event_queue *q)
635c167b9c7SMaximilian Luz {
636c167b9c7SMaximilian Luz 	struct ssam_event_item *item;
637c167b9c7SMaximilian Luz 
638c167b9c7SMaximilian Luz 	spin_lock(&q->lock);
639c167b9c7SMaximilian Luz 	item = list_first_entry_or_null(&q->head, struct ssam_event_item, node);
640c167b9c7SMaximilian Luz 	if (item)
641c167b9c7SMaximilian Luz 		list_del(&item->node);
642c167b9c7SMaximilian Luz 	spin_unlock(&q->lock);
643c167b9c7SMaximilian Luz 
644c167b9c7SMaximilian Luz 	return item;
645c167b9c7SMaximilian Luz }
646c167b9c7SMaximilian Luz 
647c167b9c7SMaximilian Luz /**
648c167b9c7SMaximilian Luz  * ssam_event_queue_is_empty() - Check if the event queue is empty.
649c167b9c7SMaximilian Luz  * @q: The event queue.
650c167b9c7SMaximilian Luz  */
651c167b9c7SMaximilian Luz static bool ssam_event_queue_is_empty(struct ssam_event_queue *q)
652c167b9c7SMaximilian Luz {
653c167b9c7SMaximilian Luz 	bool empty;
654c167b9c7SMaximilian Luz 
655c167b9c7SMaximilian Luz 	spin_lock(&q->lock);
656c167b9c7SMaximilian Luz 	empty = list_empty(&q->head);
657c167b9c7SMaximilian Luz 	spin_unlock(&q->lock);
658c167b9c7SMaximilian Luz 
659c167b9c7SMaximilian Luz 	return empty;
660c167b9c7SMaximilian Luz }
661c167b9c7SMaximilian Luz 
662c167b9c7SMaximilian Luz /**
663c167b9c7SMaximilian Luz  * ssam_cplt_get_event_queue() - Get the event queue for the given parameters.
664c167b9c7SMaximilian Luz  * @cplt: The completion system on which to look for the queue.
665c167b9c7SMaximilian Luz  * @tid:  The target ID of the queue.
666c167b9c7SMaximilian Luz  * @rqid: The request ID representing the event ID for which to get the queue.
667c167b9c7SMaximilian Luz  *
668c167b9c7SMaximilian Luz  * Return: Returns the event queue corresponding to the event type described
669c167b9c7SMaximilian Luz  * by the given parameters. If the request ID does not represent an event,
670c167b9c7SMaximilian Luz  * this function returns %NULL. If the target ID is not supported, this
671c167b9c7SMaximilian Luz  * function will fall back to the default target ID (``tid = 1``).
672c167b9c7SMaximilian Luz  */
673c167b9c7SMaximilian Luz static
674c167b9c7SMaximilian Luz struct ssam_event_queue *ssam_cplt_get_event_queue(struct ssam_cplt *cplt,
675c167b9c7SMaximilian Luz 						   u8 tid, u16 rqid)
676c167b9c7SMaximilian Luz {
677c167b9c7SMaximilian Luz 	u16 event = ssh_rqid_to_event(rqid);
678c167b9c7SMaximilian Luz 	u16 tidx = ssh_tid_to_index(tid);
679c167b9c7SMaximilian Luz 
680c167b9c7SMaximilian Luz 	if (!ssh_rqid_is_event(rqid)) {
681c167b9c7SMaximilian Luz 		dev_err(cplt->dev, "event: unsupported request ID: %#06x\n", rqid);
682c167b9c7SMaximilian Luz 		return NULL;
683c167b9c7SMaximilian Luz 	}
684c167b9c7SMaximilian Luz 
685c167b9c7SMaximilian Luz 	if (!ssh_tid_is_valid(tid)) {
686c167b9c7SMaximilian Luz 		dev_warn(cplt->dev, "event: unsupported target ID: %u\n", tid);
687c167b9c7SMaximilian Luz 		tidx = 0;
688c167b9c7SMaximilian Luz 	}
689c167b9c7SMaximilian Luz 
690c167b9c7SMaximilian Luz 	return &cplt->event.target[tidx].queue[event];
691c167b9c7SMaximilian Luz }
692c167b9c7SMaximilian Luz 
693c167b9c7SMaximilian Luz /**
694c167b9c7SMaximilian Luz  * ssam_cplt_submit() - Submit a work item to the completion system workqueue.
695c167b9c7SMaximilian Luz  * @cplt: The completion system.
696c167b9c7SMaximilian Luz  * @work: The work item to submit.
697c167b9c7SMaximilian Luz  */
698c167b9c7SMaximilian Luz static bool ssam_cplt_submit(struct ssam_cplt *cplt, struct work_struct *work)
699c167b9c7SMaximilian Luz {
700c167b9c7SMaximilian Luz 	return queue_work(cplt->wq, work);
701c167b9c7SMaximilian Luz }
702c167b9c7SMaximilian Luz 
703c167b9c7SMaximilian Luz /**
704c167b9c7SMaximilian Luz  * ssam_cplt_submit_event() - Submit an event to the completion system.
705c167b9c7SMaximilian Luz  * @cplt: The completion system.
706c167b9c7SMaximilian Luz  * @item: The event item to submit.
707c167b9c7SMaximilian Luz  *
708c167b9c7SMaximilian Luz  * Submits the event to the completion system by queuing it on the event item
709c167b9c7SMaximilian Luz  * queue and queuing the respective event queue work item on the completion
710c167b9c7SMaximilian Luz  * workqueue, which will eventually complete the event.
711c167b9c7SMaximilian Luz  *
712c167b9c7SMaximilian Luz  * Return: Returns zero on success, %-EINVAL if there is no event queue that
713c167b9c7SMaximilian Luz  * can handle the given event item.
714c167b9c7SMaximilian Luz  */
715c167b9c7SMaximilian Luz static int ssam_cplt_submit_event(struct ssam_cplt *cplt,
716c167b9c7SMaximilian Luz 				  struct ssam_event_item *item)
717c167b9c7SMaximilian Luz {
718c167b9c7SMaximilian Luz 	struct ssam_event_queue *evq;
719c167b9c7SMaximilian Luz 
720c167b9c7SMaximilian Luz 	evq = ssam_cplt_get_event_queue(cplt, item->event.target_id, item->rqid);
721c167b9c7SMaximilian Luz 	if (!evq)
722c167b9c7SMaximilian Luz 		return -EINVAL;
723c167b9c7SMaximilian Luz 
724c167b9c7SMaximilian Luz 	ssam_event_queue_push(evq, item);
725c167b9c7SMaximilian Luz 	ssam_cplt_submit(cplt, &evq->work);
726c167b9c7SMaximilian Luz 	return 0;
727c167b9c7SMaximilian Luz }
728c167b9c7SMaximilian Luz 
729c167b9c7SMaximilian Luz /**
730c167b9c7SMaximilian Luz  * ssam_cplt_flush() - Flush the completion system.
731c167b9c7SMaximilian Luz  * @cplt: The completion system.
732c167b9c7SMaximilian Luz  *
733c167b9c7SMaximilian Luz  * Flush the completion system by waiting until all currently submitted work
734c167b9c7SMaximilian Luz  * items have been completed.
735c167b9c7SMaximilian Luz  *
736c167b9c7SMaximilian Luz  * Note: This function does not guarantee that all events will have been
737c167b9c7SMaximilian Luz  * handled once this call terminates. In case of a larger number of
738c167b9c7SMaximilian Luz  * to-be-completed events, the event queue work function may re-schedule its
739c167b9c7SMaximilian Luz  * work item, which this flush operation will ignore.
740c167b9c7SMaximilian Luz  *
741c167b9c7SMaximilian Luz  * This operation is only intended to, during normal operation prior to
742c167b9c7SMaximilian Luz  * shutdown, try to complete most events and requests to get them out of the
743c167b9c7SMaximilian Luz  * system while the system is still fully operational. It does not aim to
744c167b9c7SMaximilian Luz  * provide any guarantee that all of them have been handled.
745c167b9c7SMaximilian Luz  */
746c167b9c7SMaximilian Luz static void ssam_cplt_flush(struct ssam_cplt *cplt)
747c167b9c7SMaximilian Luz {
748c167b9c7SMaximilian Luz 	flush_workqueue(cplt->wq);
749c167b9c7SMaximilian Luz }
750c167b9c7SMaximilian Luz 
751c167b9c7SMaximilian Luz static void ssam_event_queue_work_fn(struct work_struct *work)
752c167b9c7SMaximilian Luz {
753c167b9c7SMaximilian Luz 	struct ssam_event_queue *queue;
754c167b9c7SMaximilian Luz 	struct ssam_event_item *item;
755c167b9c7SMaximilian Luz 	struct ssam_nf *nf;
756c167b9c7SMaximilian Luz 	struct device *dev;
757c167b9c7SMaximilian Luz 	unsigned int iterations = SSAM_CPLT_WQ_BATCH;
758c167b9c7SMaximilian Luz 
759c167b9c7SMaximilian Luz 	queue = container_of(work, struct ssam_event_queue, work);
760c167b9c7SMaximilian Luz 	nf = &queue->cplt->event.notif;
761c167b9c7SMaximilian Luz 	dev = queue->cplt->dev;
762c167b9c7SMaximilian Luz 
763c167b9c7SMaximilian Luz 	/* Limit number of processed events to avoid livelocking. */
764c167b9c7SMaximilian Luz 	do {
765c167b9c7SMaximilian Luz 		item = ssam_event_queue_pop(queue);
766c167b9c7SMaximilian Luz 		if (!item)
767c167b9c7SMaximilian Luz 			return;
768c167b9c7SMaximilian Luz 
769c167b9c7SMaximilian Luz 		ssam_nf_call(nf, dev, item->rqid, &item->event);
7703a7081f6SMaximilian Luz 		ssam_event_item_free(item);
771c167b9c7SMaximilian Luz 	} while (--iterations);
772c167b9c7SMaximilian Luz 
773c167b9c7SMaximilian Luz 	if (!ssam_event_queue_is_empty(queue))
774c167b9c7SMaximilian Luz 		ssam_cplt_submit(queue->cplt, &queue->work);
775c167b9c7SMaximilian Luz }
776c167b9c7SMaximilian Luz 
777c167b9c7SMaximilian Luz /**
778c167b9c7SMaximilian Luz  * ssam_event_queue_init() - Initialize an event queue.
779c167b9c7SMaximilian Luz  * @cplt: The completion system on which the queue resides.
780c167b9c7SMaximilian Luz  * @evq:  The event queue to initialize.
781c167b9c7SMaximilian Luz  */
782c167b9c7SMaximilian Luz static void ssam_event_queue_init(struct ssam_cplt *cplt,
783c167b9c7SMaximilian Luz 				  struct ssam_event_queue *evq)
784c167b9c7SMaximilian Luz {
785c167b9c7SMaximilian Luz 	evq->cplt = cplt;
786c167b9c7SMaximilian Luz 	spin_lock_init(&evq->lock);
787c167b9c7SMaximilian Luz 	INIT_LIST_HEAD(&evq->head);
788c167b9c7SMaximilian Luz 	INIT_WORK(&evq->work, ssam_event_queue_work_fn);
789c167b9c7SMaximilian Luz }
790c167b9c7SMaximilian Luz 
791c167b9c7SMaximilian Luz /**
792c167b9c7SMaximilian Luz  * ssam_cplt_init() - Initialize completion system.
793c167b9c7SMaximilian Luz  * @cplt: The completion system to initialize.
794c167b9c7SMaximilian Luz  * @dev:  The device used for logging.
795c167b9c7SMaximilian Luz  */
796c167b9c7SMaximilian Luz static int ssam_cplt_init(struct ssam_cplt *cplt, struct device *dev)
797c167b9c7SMaximilian Luz {
798c167b9c7SMaximilian Luz 	struct ssam_event_target *target;
799c167b9c7SMaximilian Luz 	int status, c, i;
800c167b9c7SMaximilian Luz 
801c167b9c7SMaximilian Luz 	cplt->dev = dev;
802c167b9c7SMaximilian Luz 
803c167b9c7SMaximilian Luz 	cplt->wq = create_workqueue(SSAM_CPLT_WQ_NAME);
804c167b9c7SMaximilian Luz 	if (!cplt->wq)
805c167b9c7SMaximilian Luz 		return -ENOMEM;
806c167b9c7SMaximilian Luz 
807c167b9c7SMaximilian Luz 	for (c = 0; c < ARRAY_SIZE(cplt->event.target); c++) {
808c167b9c7SMaximilian Luz 		target = &cplt->event.target[c];
809c167b9c7SMaximilian Luz 
810c167b9c7SMaximilian Luz 		for (i = 0; i < ARRAY_SIZE(target->queue); i++)
811c167b9c7SMaximilian Luz 			ssam_event_queue_init(cplt, &target->queue[i]);
812c167b9c7SMaximilian Luz 	}
813c167b9c7SMaximilian Luz 
814c167b9c7SMaximilian Luz 	status = ssam_nf_init(&cplt->event.notif);
815c167b9c7SMaximilian Luz 	if (status)
816c167b9c7SMaximilian Luz 		destroy_workqueue(cplt->wq);
817c167b9c7SMaximilian Luz 
818c167b9c7SMaximilian Luz 	return status;
819c167b9c7SMaximilian Luz }
820c167b9c7SMaximilian Luz 
821c167b9c7SMaximilian Luz /**
822c167b9c7SMaximilian Luz  * ssam_cplt_destroy() - Deinitialize the completion system.
823c167b9c7SMaximilian Luz  * @cplt: The completion system to deinitialize.
824c167b9c7SMaximilian Luz  *
825c167b9c7SMaximilian Luz  * Deinitialize the given completion system and ensure that all pending, i.e.
826c167b9c7SMaximilian Luz  * yet-to-be-completed, event items and requests have been handled.
827c167b9c7SMaximilian Luz  */
828c167b9c7SMaximilian Luz static void ssam_cplt_destroy(struct ssam_cplt *cplt)
829c167b9c7SMaximilian Luz {
830c167b9c7SMaximilian Luz 	/*
831c167b9c7SMaximilian Luz 	 * Note: destroy_workqueue ensures that all currently queued work will
832c167b9c7SMaximilian Luz 	 * be fully completed and the workqueue drained. This means that this
833c167b9c7SMaximilian Luz 	 * call will inherently also free any queued ssam_event_items, thus we
834c167b9c7SMaximilian Luz 	 * don't have to take care of that here explicitly.
835c167b9c7SMaximilian Luz 	 */
836c167b9c7SMaximilian Luz 	destroy_workqueue(cplt->wq);
837c167b9c7SMaximilian Luz 	ssam_nf_destroy(&cplt->event.notif);
838c167b9c7SMaximilian Luz }
839c167b9c7SMaximilian Luz 
840c167b9c7SMaximilian Luz 
841c167b9c7SMaximilian Luz /* -- Main SSAM device structures. ------------------------------------------ */
842c167b9c7SMaximilian Luz 
843c167b9c7SMaximilian Luz /**
844c167b9c7SMaximilian Luz  * ssam_controller_device() - Get the &struct device associated with this
845c167b9c7SMaximilian Luz  * controller.
846c167b9c7SMaximilian Luz  * @c: The controller for which to get the device.
847c167b9c7SMaximilian Luz  *
848c167b9c7SMaximilian Luz  * Return: Returns the &struct device associated with this controller,
849c167b9c7SMaximilian Luz  * providing its lower-level transport.
850c167b9c7SMaximilian Luz  */
851c167b9c7SMaximilian Luz struct device *ssam_controller_device(struct ssam_controller *c)
852c167b9c7SMaximilian Luz {
853c167b9c7SMaximilian Luz 	return ssh_rtl_get_device(&c->rtl);
854c167b9c7SMaximilian Luz }
855c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_controller_device);
856c167b9c7SMaximilian Luz 
857c167b9c7SMaximilian Luz static void __ssam_controller_release(struct kref *kref)
858c167b9c7SMaximilian Luz {
859c167b9c7SMaximilian Luz 	struct ssam_controller *ctrl = to_ssam_controller(kref, kref);
860c167b9c7SMaximilian Luz 
861c167b9c7SMaximilian Luz 	/*
862c167b9c7SMaximilian Luz 	 * The lock-call here is to satisfy lockdep. At this point we really
863c167b9c7SMaximilian Luz 	 * expect this to be the last remaining reference to the controller.
864c167b9c7SMaximilian Luz 	 * Anything else is a bug.
865c167b9c7SMaximilian Luz 	 */
866c167b9c7SMaximilian Luz 	ssam_controller_lock(ctrl);
867c167b9c7SMaximilian Luz 	ssam_controller_destroy(ctrl);
868c167b9c7SMaximilian Luz 	ssam_controller_unlock(ctrl);
869c167b9c7SMaximilian Luz 
870c167b9c7SMaximilian Luz 	kfree(ctrl);
871c167b9c7SMaximilian Luz }
872c167b9c7SMaximilian Luz 
873c167b9c7SMaximilian Luz /**
874c167b9c7SMaximilian Luz  * ssam_controller_get() - Increment reference count of controller.
875c167b9c7SMaximilian Luz  * @c: The controller.
876c167b9c7SMaximilian Luz  *
877c167b9c7SMaximilian Luz  * Return: Returns the controller provided as input.
878c167b9c7SMaximilian Luz  */
879c167b9c7SMaximilian Luz struct ssam_controller *ssam_controller_get(struct ssam_controller *c)
880c167b9c7SMaximilian Luz {
881c167b9c7SMaximilian Luz 	if (c)
882c167b9c7SMaximilian Luz 		kref_get(&c->kref);
883c167b9c7SMaximilian Luz 	return c;
884c167b9c7SMaximilian Luz }
885c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_controller_get);
886c167b9c7SMaximilian Luz 
887c167b9c7SMaximilian Luz /**
888c167b9c7SMaximilian Luz  * ssam_controller_put() - Decrement reference count of controller.
889c167b9c7SMaximilian Luz  * @c: The controller.
890c167b9c7SMaximilian Luz  */
891c167b9c7SMaximilian Luz void ssam_controller_put(struct ssam_controller *c)
892c167b9c7SMaximilian Luz {
893c167b9c7SMaximilian Luz 	if (c)
894c167b9c7SMaximilian Luz 		kref_put(&c->kref, __ssam_controller_release);
895c167b9c7SMaximilian Luz }
896c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_controller_put);
897c167b9c7SMaximilian Luz 
898c167b9c7SMaximilian Luz /**
899c167b9c7SMaximilian Luz  * ssam_controller_statelock() - Lock the controller against state transitions.
900c167b9c7SMaximilian Luz  * @c: The controller to lock.
901c167b9c7SMaximilian Luz  *
902c167b9c7SMaximilian Luz  * Lock the controller against state transitions. Holding this lock guarantees
903c167b9c7SMaximilian Luz  * that the controller will not transition between states, i.e. if the
904c167b9c7SMaximilian Luz  * controller is in state "started", when this lock has been acquired, it will
905c167b9c7SMaximilian Luz  * remain in this state at least until the lock has been released.
906c167b9c7SMaximilian Luz  *
907c167b9c7SMaximilian Luz  * Multiple clients may concurrently hold this lock. In other words: The
908c167b9c7SMaximilian Luz  * ``statelock`` functions represent the read-lock part of a r/w-semaphore.
909c167b9c7SMaximilian Luz  * Actions causing state transitions of the controller must be executed while
910c167b9c7SMaximilian Luz  * holding the write-part of this r/w-semaphore (see ssam_controller_lock()
911c167b9c7SMaximilian Luz  * and ssam_controller_unlock() for that).
912c167b9c7SMaximilian Luz  *
913c167b9c7SMaximilian Luz  * See ssam_controller_stateunlock() for the corresponding unlock function.
914c167b9c7SMaximilian Luz  */
915c167b9c7SMaximilian Luz void ssam_controller_statelock(struct ssam_controller *c)
916c167b9c7SMaximilian Luz {
917c167b9c7SMaximilian Luz 	down_read(&c->lock);
918c167b9c7SMaximilian Luz }
919c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_controller_statelock);
920c167b9c7SMaximilian Luz 
921c167b9c7SMaximilian Luz /**
922c167b9c7SMaximilian Luz  * ssam_controller_stateunlock() - Unlock controller state transitions.
923c167b9c7SMaximilian Luz  * @c: The controller to unlock.
924c167b9c7SMaximilian Luz  *
925c167b9c7SMaximilian Luz  * See ssam_controller_statelock() for the corresponding lock function.
926c167b9c7SMaximilian Luz  */
927c167b9c7SMaximilian Luz void ssam_controller_stateunlock(struct ssam_controller *c)
928c167b9c7SMaximilian Luz {
929c167b9c7SMaximilian Luz 	up_read(&c->lock);
930c167b9c7SMaximilian Luz }
931c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_controller_stateunlock);
932c167b9c7SMaximilian Luz 
933c167b9c7SMaximilian Luz /**
934c167b9c7SMaximilian Luz  * ssam_controller_lock() - Acquire the main controller lock.
935c167b9c7SMaximilian Luz  * @c: The controller to lock.
936c167b9c7SMaximilian Luz  *
937c167b9c7SMaximilian Luz  * This lock must be held for any state transitions, including transition to
938c167b9c7SMaximilian Luz  * suspend/resumed states and during shutdown. See ssam_controller_statelock()
939c167b9c7SMaximilian Luz  * for more details on controller locking.
940c167b9c7SMaximilian Luz  *
941c167b9c7SMaximilian Luz  * See ssam_controller_unlock() for the corresponding unlock function.
942c167b9c7SMaximilian Luz  */
943c167b9c7SMaximilian Luz void ssam_controller_lock(struct ssam_controller *c)
944c167b9c7SMaximilian Luz {
945c167b9c7SMaximilian Luz 	down_write(&c->lock);
946c167b9c7SMaximilian Luz }
947c167b9c7SMaximilian Luz 
948c167b9c7SMaximilian Luz /*
949c167b9c7SMaximilian Luz  * ssam_controller_unlock() - Release the main controller lock.
950c167b9c7SMaximilian Luz  * @c: The controller to unlock.
951c167b9c7SMaximilian Luz  *
952c167b9c7SMaximilian Luz  * See ssam_controller_lock() for the corresponding lock function.
953c167b9c7SMaximilian Luz  */
954c167b9c7SMaximilian Luz void ssam_controller_unlock(struct ssam_controller *c)
955c167b9c7SMaximilian Luz {
956c167b9c7SMaximilian Luz 	up_write(&c->lock);
957c167b9c7SMaximilian Luz }
958c167b9c7SMaximilian Luz 
959c167b9c7SMaximilian Luz static void ssam_handle_event(struct ssh_rtl *rtl,
960c167b9c7SMaximilian Luz 			      const struct ssh_command *cmd,
961c167b9c7SMaximilian Luz 			      const struct ssam_span *data)
962c167b9c7SMaximilian Luz {
963c167b9c7SMaximilian Luz 	struct ssam_controller *ctrl = to_ssam_controller(rtl, rtl);
964c167b9c7SMaximilian Luz 	struct ssam_event_item *item;
965c167b9c7SMaximilian Luz 
966c167b9c7SMaximilian Luz 	item = ssam_event_item_alloc(data->len, GFP_KERNEL);
967c167b9c7SMaximilian Luz 	if (!item)
968c167b9c7SMaximilian Luz 		return;
969c167b9c7SMaximilian Luz 
970c167b9c7SMaximilian Luz 	item->rqid = get_unaligned_le16(&cmd->rqid);
971c167b9c7SMaximilian Luz 	item->event.target_category = cmd->tc;
972c167b9c7SMaximilian Luz 	item->event.target_id = cmd->tid_in;
973c167b9c7SMaximilian Luz 	item->event.command_id = cmd->cid;
974c167b9c7SMaximilian Luz 	item->event.instance_id = cmd->iid;
975c167b9c7SMaximilian Luz 	memcpy(&item->event.data[0], data->ptr, data->len);
976c167b9c7SMaximilian Luz 
977c167b9c7SMaximilian Luz 	if (WARN_ON(ssam_cplt_submit_event(&ctrl->cplt, item)))
9783a7081f6SMaximilian Luz 		ssam_event_item_free(item);
979c167b9c7SMaximilian Luz }
980c167b9c7SMaximilian Luz 
981c167b9c7SMaximilian Luz static const struct ssh_rtl_ops ssam_rtl_ops = {
982c167b9c7SMaximilian Luz 	.handle_event = ssam_handle_event,
983c167b9c7SMaximilian Luz };
984c167b9c7SMaximilian Luz 
985c167b9c7SMaximilian Luz static bool ssam_notifier_is_empty(struct ssam_controller *ctrl);
986c167b9c7SMaximilian Luz static void ssam_notifier_unregister_all(struct ssam_controller *ctrl);
987c167b9c7SMaximilian Luz 
988c167b9c7SMaximilian Luz #define SSAM_SSH_DSM_REVISION	0
989c167b9c7SMaximilian Luz 
990c167b9c7SMaximilian Luz /* d5e383e1-d892-4a76-89fc-f6aaae7ed5b5 */
991c167b9c7SMaximilian Luz static const guid_t SSAM_SSH_DSM_GUID =
992c167b9c7SMaximilian Luz 	GUID_INIT(0xd5e383e1, 0xd892, 0x4a76,
993c167b9c7SMaximilian Luz 		  0x89, 0xfc, 0xf6, 0xaa, 0xae, 0x7e, 0xd5, 0xb5);
994c167b9c7SMaximilian Luz 
995c167b9c7SMaximilian Luz enum ssh_dsm_fn {
996c167b9c7SMaximilian Luz 	SSH_DSM_FN_SSH_POWER_PROFILE             = 0x05,
997c167b9c7SMaximilian Luz 	SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT  = 0x06,
998c167b9c7SMaximilian Luz 	SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT = 0x07,
999c167b9c7SMaximilian Luz 	SSH_DSM_FN_D3_CLOSES_HANDLE              = 0x08,
1000c167b9c7SMaximilian Luz 	SSH_DSM_FN_SSH_BUFFER_SIZE               = 0x09,
1001c167b9c7SMaximilian Luz };
1002c167b9c7SMaximilian Luz 
1003c167b9c7SMaximilian Luz static int ssam_dsm_get_functions(acpi_handle handle, u64 *funcs)
1004c167b9c7SMaximilian Luz {
1005c167b9c7SMaximilian Luz 	union acpi_object *obj;
1006c167b9c7SMaximilian Luz 	u64 mask = 0;
1007c167b9c7SMaximilian Luz 	int i;
1008c167b9c7SMaximilian Luz 
1009c167b9c7SMaximilian Luz 	*funcs = 0;
1010c167b9c7SMaximilian Luz 
1011c167b9c7SMaximilian Luz 	/*
1012c167b9c7SMaximilian Luz 	 * The _DSM function is only present on newer models. It is not
1013c167b9c7SMaximilian Luz 	 * present on 5th and 6th generation devices (i.e. up to and including
1014c167b9c7SMaximilian Luz 	 * Surface Pro 6, Surface Laptop 2, Surface Book 2).
1015c167b9c7SMaximilian Luz 	 *
1016c167b9c7SMaximilian Luz 	 * If the _DSM is not present, indicate that no function is supported.
1017c167b9c7SMaximilian Luz 	 * This will result in default values being set.
1018c167b9c7SMaximilian Luz 	 */
1019c167b9c7SMaximilian Luz 	if (!acpi_has_method(handle, "_DSM"))
1020c167b9c7SMaximilian Luz 		return 0;
1021c167b9c7SMaximilian Luz 
1022c167b9c7SMaximilian Luz 	obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID,
1023c167b9c7SMaximilian Luz 				      SSAM_SSH_DSM_REVISION, 0, NULL,
1024c167b9c7SMaximilian Luz 				      ACPI_TYPE_BUFFER);
1025c167b9c7SMaximilian Luz 	if (!obj)
1026c167b9c7SMaximilian Luz 		return -EIO;
1027c167b9c7SMaximilian Luz 
1028c167b9c7SMaximilian Luz 	for (i = 0; i < obj->buffer.length && i < 8; i++)
1029c167b9c7SMaximilian Luz 		mask |= (((u64)obj->buffer.pointer[i]) << (i * 8));
1030c167b9c7SMaximilian Luz 
1031c167b9c7SMaximilian Luz 	if (mask & BIT(0))
1032c167b9c7SMaximilian Luz 		*funcs = mask;
1033c167b9c7SMaximilian Luz 
1034c167b9c7SMaximilian Luz 	ACPI_FREE(obj);
1035c167b9c7SMaximilian Luz 	return 0;
1036c167b9c7SMaximilian Luz }
1037c167b9c7SMaximilian Luz 
1038c167b9c7SMaximilian Luz static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret)
1039c167b9c7SMaximilian Luz {
1040c167b9c7SMaximilian Luz 	union acpi_object *obj;
1041c167b9c7SMaximilian Luz 	u64 val;
1042c167b9c7SMaximilian Luz 
1043366f0a30SDan Carpenter 	if (!(funcs & BIT_ULL(func)))
1044c167b9c7SMaximilian Luz 		return 0; /* Not supported, leave *ret at its default value */
1045c167b9c7SMaximilian Luz 
1046c167b9c7SMaximilian Luz 	obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID,
1047c167b9c7SMaximilian Luz 				      SSAM_SSH_DSM_REVISION, func, NULL,
1048c167b9c7SMaximilian Luz 				      ACPI_TYPE_INTEGER);
1049c167b9c7SMaximilian Luz 	if (!obj)
1050c167b9c7SMaximilian Luz 		return -EIO;
1051c167b9c7SMaximilian Luz 
1052c167b9c7SMaximilian Luz 	val = obj->integer.value;
1053c167b9c7SMaximilian Luz 	ACPI_FREE(obj);
1054c167b9c7SMaximilian Luz 
1055c167b9c7SMaximilian Luz 	if (val > U32_MAX)
1056c167b9c7SMaximilian Luz 		return -ERANGE;
1057c167b9c7SMaximilian Luz 
1058c167b9c7SMaximilian Luz 	*ret = val;
1059c167b9c7SMaximilian Luz 	return 0;
1060c167b9c7SMaximilian Luz }
1061c167b9c7SMaximilian Luz 
1062c167b9c7SMaximilian Luz /**
1063c167b9c7SMaximilian Luz  * ssam_controller_caps_load_from_acpi() - Load controller capabilities from
1064c167b9c7SMaximilian Luz  * ACPI _DSM.
1065c167b9c7SMaximilian Luz  * @handle: The handle of the ACPI controller/SSH device.
1066c167b9c7SMaximilian Luz  * @caps:   Where to store the capabilities in.
1067c167b9c7SMaximilian Luz  *
1068c167b9c7SMaximilian Luz  * Initializes the given controller capabilities with default values, then
1069c167b9c7SMaximilian Luz  * checks and, if the respective _DSM functions are available, loads the
1070c167b9c7SMaximilian Luz  * actual capabilities from the _DSM.
1071c167b9c7SMaximilian Luz  *
1072c167b9c7SMaximilian Luz  * Return: Returns zero on success, a negative error code on failure.
1073c167b9c7SMaximilian Luz  */
1074c167b9c7SMaximilian Luz static
1075c167b9c7SMaximilian Luz int ssam_controller_caps_load_from_acpi(acpi_handle handle,
1076c167b9c7SMaximilian Luz 					struct ssam_controller_caps *caps)
1077c167b9c7SMaximilian Luz {
1078c167b9c7SMaximilian Luz 	u32 d3_closes_handle = false;
1079c167b9c7SMaximilian Luz 	u64 funcs;
1080c167b9c7SMaximilian Luz 	int status;
1081c167b9c7SMaximilian Luz 
1082c167b9c7SMaximilian Luz 	/* Set defaults. */
1083c167b9c7SMaximilian Luz 	caps->ssh_power_profile = U32_MAX;
1084c167b9c7SMaximilian Luz 	caps->screen_on_sleep_idle_timeout = U32_MAX;
1085c167b9c7SMaximilian Luz 	caps->screen_off_sleep_idle_timeout = U32_MAX;
1086c167b9c7SMaximilian Luz 	caps->d3_closes_handle = false;
1087c167b9c7SMaximilian Luz 	caps->ssh_buffer_size = U32_MAX;
1088c167b9c7SMaximilian Luz 
1089c167b9c7SMaximilian Luz 	/* Pre-load supported DSM functions. */
1090c167b9c7SMaximilian Luz 	status = ssam_dsm_get_functions(handle, &funcs);
1091c167b9c7SMaximilian Luz 	if (status)
1092c167b9c7SMaximilian Luz 		return status;
1093c167b9c7SMaximilian Luz 
1094c167b9c7SMaximilian Luz 	/* Load actual values from ACPI, if present. */
1095c167b9c7SMaximilian Luz 	status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_POWER_PROFILE,
1096c167b9c7SMaximilian Luz 				   &caps->ssh_power_profile);
1097c167b9c7SMaximilian Luz 	if (status)
1098c167b9c7SMaximilian Luz 		return status;
1099c167b9c7SMaximilian Luz 
1100c167b9c7SMaximilian Luz 	status = ssam_dsm_load_u32(handle, funcs,
1101c167b9c7SMaximilian Luz 				   SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT,
1102c167b9c7SMaximilian Luz 				   &caps->screen_on_sleep_idle_timeout);
1103c167b9c7SMaximilian Luz 	if (status)
1104c167b9c7SMaximilian Luz 		return status;
1105c167b9c7SMaximilian Luz 
1106c167b9c7SMaximilian Luz 	status = ssam_dsm_load_u32(handle, funcs,
1107c167b9c7SMaximilian Luz 				   SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT,
1108c167b9c7SMaximilian Luz 				   &caps->screen_off_sleep_idle_timeout);
1109c167b9c7SMaximilian Luz 	if (status)
1110c167b9c7SMaximilian Luz 		return status;
1111c167b9c7SMaximilian Luz 
1112c167b9c7SMaximilian Luz 	status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_D3_CLOSES_HANDLE,
1113c167b9c7SMaximilian Luz 				   &d3_closes_handle);
1114c167b9c7SMaximilian Luz 	if (status)
1115c167b9c7SMaximilian Luz 		return status;
1116c167b9c7SMaximilian Luz 
1117c167b9c7SMaximilian Luz 	caps->d3_closes_handle = !!d3_closes_handle;
1118c167b9c7SMaximilian Luz 
1119c167b9c7SMaximilian Luz 	status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_BUFFER_SIZE,
1120c167b9c7SMaximilian Luz 				   &caps->ssh_buffer_size);
1121c167b9c7SMaximilian Luz 	if (status)
1122c167b9c7SMaximilian Luz 		return status;
1123c167b9c7SMaximilian Luz 
1124c167b9c7SMaximilian Luz 	return 0;
1125c167b9c7SMaximilian Luz }
1126c167b9c7SMaximilian Luz 
1127c167b9c7SMaximilian Luz /**
1128c167b9c7SMaximilian Luz  * ssam_controller_init() - Initialize SSAM controller.
1129c167b9c7SMaximilian Luz  * @ctrl:   The controller to initialize.
1130c167b9c7SMaximilian Luz  * @serdev: The serial device representing the underlying data transport.
1131c167b9c7SMaximilian Luz  *
1132c167b9c7SMaximilian Luz  * Initializes the given controller. Does neither start receiver nor
1133c167b9c7SMaximilian Luz  * transmitter threads. After this call, the controller has to be hooked up to
1134c167b9c7SMaximilian Luz  * the serdev core separately via &struct serdev_device_ops, relaying calls to
1135c167b9c7SMaximilian Luz  * ssam_controller_receive_buf() and ssam_controller_write_wakeup(). Once the
1136c167b9c7SMaximilian Luz  * controller has been hooked up, transmitter and receiver threads may be
1137c167b9c7SMaximilian Luz  * started via ssam_controller_start(). These setup steps need to be completed
1138c167b9c7SMaximilian Luz  * before controller can be used for requests.
1139c167b9c7SMaximilian Luz  */
1140c167b9c7SMaximilian Luz int ssam_controller_init(struct ssam_controller *ctrl,
1141c167b9c7SMaximilian Luz 			 struct serdev_device *serdev)
1142c167b9c7SMaximilian Luz {
1143c167b9c7SMaximilian Luz 	acpi_handle handle = ACPI_HANDLE(&serdev->dev);
1144c167b9c7SMaximilian Luz 	int status;
1145c167b9c7SMaximilian Luz 
1146c167b9c7SMaximilian Luz 	init_rwsem(&ctrl->lock);
1147c167b9c7SMaximilian Luz 	kref_init(&ctrl->kref);
1148c167b9c7SMaximilian Luz 
1149c167b9c7SMaximilian Luz 	status = ssam_controller_caps_load_from_acpi(handle, &ctrl->caps);
1150c167b9c7SMaximilian Luz 	if (status)
1151c167b9c7SMaximilian Luz 		return status;
1152c167b9c7SMaximilian Luz 
1153c167b9c7SMaximilian Luz 	dev_dbg(&serdev->dev,
1154c167b9c7SMaximilian Luz 		"device capabilities:\n"
1155c167b9c7SMaximilian Luz 		"  ssh_power_profile:             %u\n"
1156c167b9c7SMaximilian Luz 		"  ssh_buffer_size:               %u\n"
1157c167b9c7SMaximilian Luz 		"  screen_on_sleep_idle_timeout:  %u\n"
1158c167b9c7SMaximilian Luz 		"  screen_off_sleep_idle_timeout: %u\n"
1159c167b9c7SMaximilian Luz 		"  d3_closes_handle:              %u\n",
1160c167b9c7SMaximilian Luz 		ctrl->caps.ssh_power_profile,
1161c167b9c7SMaximilian Luz 		ctrl->caps.ssh_buffer_size,
1162c167b9c7SMaximilian Luz 		ctrl->caps.screen_on_sleep_idle_timeout,
1163c167b9c7SMaximilian Luz 		ctrl->caps.screen_off_sleep_idle_timeout,
1164c167b9c7SMaximilian Luz 		ctrl->caps.d3_closes_handle);
1165c167b9c7SMaximilian Luz 
1166c167b9c7SMaximilian Luz 	ssh_seq_reset(&ctrl->counter.seq);
1167c167b9c7SMaximilian Luz 	ssh_rqid_reset(&ctrl->counter.rqid);
1168c167b9c7SMaximilian Luz 
1169c167b9c7SMaximilian Luz 	/* Initialize event/request completion system. */
1170c167b9c7SMaximilian Luz 	status = ssam_cplt_init(&ctrl->cplt, &serdev->dev);
1171c167b9c7SMaximilian Luz 	if (status)
1172c167b9c7SMaximilian Luz 		return status;
1173c167b9c7SMaximilian Luz 
1174c167b9c7SMaximilian Luz 	/* Initialize request and packet transport layers. */
1175c167b9c7SMaximilian Luz 	status = ssh_rtl_init(&ctrl->rtl, serdev, &ssam_rtl_ops);
1176c167b9c7SMaximilian Luz 	if (status) {
1177c167b9c7SMaximilian Luz 		ssam_cplt_destroy(&ctrl->cplt);
1178c167b9c7SMaximilian Luz 		return status;
1179c167b9c7SMaximilian Luz 	}
1180c167b9c7SMaximilian Luz 
1181c167b9c7SMaximilian Luz 	/*
1182c167b9c7SMaximilian Luz 	 * Set state via write_once even though we expect to be in an
1183c167b9c7SMaximilian Luz 	 * exclusive context, due to smoke-testing in
1184c167b9c7SMaximilian Luz 	 * ssam_request_sync_submit().
1185c167b9c7SMaximilian Luz 	 */
1186c167b9c7SMaximilian Luz 	WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_INITIALIZED);
1187c167b9c7SMaximilian Luz 	return 0;
1188c167b9c7SMaximilian Luz }
1189c167b9c7SMaximilian Luz 
1190c167b9c7SMaximilian Luz /**
1191c167b9c7SMaximilian Luz  * ssam_controller_start() - Start the receiver and transmitter threads of the
1192c167b9c7SMaximilian Luz  * controller.
1193c167b9c7SMaximilian Luz  * @ctrl: The controller.
1194c167b9c7SMaximilian Luz  *
1195c167b9c7SMaximilian Luz  * Note: When this function is called, the controller should be properly
1196c167b9c7SMaximilian Luz  * hooked up to the serdev core via &struct serdev_device_ops. Please refer
1197c167b9c7SMaximilian Luz  * to ssam_controller_init() for more details on controller initialization.
1198c167b9c7SMaximilian Luz  *
1199c167b9c7SMaximilian Luz  * This function must be called with the main controller lock held (i.e. by
1200c167b9c7SMaximilian Luz  * calling ssam_controller_lock()).
1201c167b9c7SMaximilian Luz  */
1202c167b9c7SMaximilian Luz int ssam_controller_start(struct ssam_controller *ctrl)
1203c167b9c7SMaximilian Luz {
1204c167b9c7SMaximilian Luz 	int status;
1205c167b9c7SMaximilian Luz 
1206c167b9c7SMaximilian Luz 	lockdep_assert_held_write(&ctrl->lock);
1207c167b9c7SMaximilian Luz 
1208c167b9c7SMaximilian Luz 	if (ctrl->state != SSAM_CONTROLLER_INITIALIZED)
1209c167b9c7SMaximilian Luz 		return -EINVAL;
1210c167b9c7SMaximilian Luz 
1211c167b9c7SMaximilian Luz 	status = ssh_rtl_start(&ctrl->rtl);
1212c167b9c7SMaximilian Luz 	if (status)
1213c167b9c7SMaximilian Luz 		return status;
1214c167b9c7SMaximilian Luz 
1215c167b9c7SMaximilian Luz 	/*
1216c167b9c7SMaximilian Luz 	 * Set state via write_once even though we expect to be locked/in an
1217c167b9c7SMaximilian Luz 	 * exclusive context, due to smoke-testing in
1218c167b9c7SMaximilian Luz 	 * ssam_request_sync_submit().
1219c167b9c7SMaximilian Luz 	 */
1220c167b9c7SMaximilian Luz 	WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED);
1221c167b9c7SMaximilian Luz 	return 0;
1222c167b9c7SMaximilian Luz }
1223c167b9c7SMaximilian Luz 
1224c167b9c7SMaximilian Luz /*
1225c167b9c7SMaximilian Luz  * SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT - Timeout for flushing requests during
1226c167b9c7SMaximilian Luz  * shutdown.
1227c167b9c7SMaximilian Luz  *
1228c167b9c7SMaximilian Luz  * Chosen to be larger than one full request timeout, including packets timing
1229c167b9c7SMaximilian Luz  * out. This value should give ample time to complete any outstanding requests
1230c167b9c7SMaximilian Luz  * during normal operation and account for the odd package timeout.
1231c167b9c7SMaximilian Luz  */
1232c167b9c7SMaximilian Luz #define SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT	msecs_to_jiffies(5000)
1233c167b9c7SMaximilian Luz 
1234c167b9c7SMaximilian Luz /**
1235c167b9c7SMaximilian Luz  * ssam_controller_shutdown() - Shut down the controller.
1236c167b9c7SMaximilian Luz  * @ctrl: The controller.
1237c167b9c7SMaximilian Luz  *
1238c167b9c7SMaximilian Luz  * Shuts down the controller by flushing all pending requests and stopping the
1239c167b9c7SMaximilian Luz  * transmitter and receiver threads. All requests submitted after this call
1240c167b9c7SMaximilian Luz  * will fail with %-ESHUTDOWN. While it is discouraged to do so, this function
1241c167b9c7SMaximilian Luz  * is safe to use in parallel with ongoing request submission.
1242c167b9c7SMaximilian Luz  *
1243c167b9c7SMaximilian Luz  * In the course of this shutdown procedure, all currently registered
1244c167b9c7SMaximilian Luz  * notifiers will be unregistered. It is, however, strongly recommended to not
1245c167b9c7SMaximilian Luz  * rely on this behavior, and instead the party registering the notifier
1246c167b9c7SMaximilian Luz  * should unregister it before the controller gets shut down, e.g. via the
1247c167b9c7SMaximilian Luz  * SSAM bus which guarantees client devices to be removed before a shutdown.
1248c167b9c7SMaximilian Luz  *
1249c167b9c7SMaximilian Luz  * Note that events may still be pending after this call, but, due to the
1250c167b9c7SMaximilian Luz  * notifiers being unregistered, these events will be dropped when the
1251c167b9c7SMaximilian Luz  * controller is subsequently destroyed via ssam_controller_destroy().
1252c167b9c7SMaximilian Luz  *
1253c167b9c7SMaximilian Luz  * This function must be called with the main controller lock held (i.e. by
1254c167b9c7SMaximilian Luz  * calling ssam_controller_lock()).
1255c167b9c7SMaximilian Luz  */
1256c167b9c7SMaximilian Luz void ssam_controller_shutdown(struct ssam_controller *ctrl)
1257c167b9c7SMaximilian Luz {
1258c167b9c7SMaximilian Luz 	enum ssam_controller_state s = ctrl->state;
1259c167b9c7SMaximilian Luz 	int status;
1260c167b9c7SMaximilian Luz 
1261c167b9c7SMaximilian Luz 	lockdep_assert_held_write(&ctrl->lock);
1262c167b9c7SMaximilian Luz 
1263c167b9c7SMaximilian Luz 	if (s == SSAM_CONTROLLER_UNINITIALIZED || s == SSAM_CONTROLLER_STOPPED)
1264c167b9c7SMaximilian Luz 		return;
1265c167b9c7SMaximilian Luz 
1266c167b9c7SMaximilian Luz 	/*
1267c167b9c7SMaximilian Luz 	 * Try to flush pending events and requests while everything still
1268c167b9c7SMaximilian Luz 	 * works. Note: There may still be packets and/or requests in the
1269c167b9c7SMaximilian Luz 	 * system after this call (e.g. via control packets submitted by the
1270c167b9c7SMaximilian Luz 	 * packet transport layer or flush timeout / failure, ...). Those will
1271c167b9c7SMaximilian Luz 	 * be handled with the ssh_rtl_shutdown() call below.
1272c167b9c7SMaximilian Luz 	 */
1273c167b9c7SMaximilian Luz 	status = ssh_rtl_flush(&ctrl->rtl, SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT);
1274c167b9c7SMaximilian Luz 	if (status) {
1275c167b9c7SMaximilian Luz 		ssam_err(ctrl, "failed to flush request transport layer: %d\n",
1276c167b9c7SMaximilian Luz 			 status);
1277c167b9c7SMaximilian Luz 	}
1278c167b9c7SMaximilian Luz 
1279c167b9c7SMaximilian Luz 	/* Try to flush all currently completing requests and events. */
1280c167b9c7SMaximilian Luz 	ssam_cplt_flush(&ctrl->cplt);
1281c167b9c7SMaximilian Luz 
1282c167b9c7SMaximilian Luz 	/*
1283c167b9c7SMaximilian Luz 	 * We expect all notifiers to have been removed by the respective client
1284c167b9c7SMaximilian Luz 	 * driver that set them up at this point. If this warning occurs, some
1285c167b9c7SMaximilian Luz 	 * client driver has not done that...
1286c167b9c7SMaximilian Luz 	 */
1287c167b9c7SMaximilian Luz 	WARN_ON(!ssam_notifier_is_empty(ctrl));
1288c167b9c7SMaximilian Luz 
1289c167b9c7SMaximilian Luz 	/*
1290c167b9c7SMaximilian Luz 	 * Nevertheless, we should still take care of drivers that don't behave
1291c167b9c7SMaximilian Luz 	 * well. Thus disable all enabled events, unregister all notifiers.
1292c167b9c7SMaximilian Luz 	 */
1293c167b9c7SMaximilian Luz 	ssam_notifier_unregister_all(ctrl);
1294c167b9c7SMaximilian Luz 
1295c167b9c7SMaximilian Luz 	/*
1296c167b9c7SMaximilian Luz 	 * Cancel remaining requests. Ensure no new ones can be queued and stop
1297c167b9c7SMaximilian Luz 	 * threads.
1298c167b9c7SMaximilian Luz 	 */
1299c167b9c7SMaximilian Luz 	ssh_rtl_shutdown(&ctrl->rtl);
1300c167b9c7SMaximilian Luz 
1301c167b9c7SMaximilian Luz 	/*
1302c167b9c7SMaximilian Luz 	 * Set state via write_once even though we expect to be locked/in an
1303c167b9c7SMaximilian Luz 	 * exclusive context, due to smoke-testing in
1304c167b9c7SMaximilian Luz 	 * ssam_request_sync_submit().
1305c167b9c7SMaximilian Luz 	 */
1306c167b9c7SMaximilian Luz 	WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STOPPED);
1307c167b9c7SMaximilian Luz 	ctrl->rtl.ptl.serdev = NULL;
1308c167b9c7SMaximilian Luz }
1309c167b9c7SMaximilian Luz 
1310c167b9c7SMaximilian Luz /**
1311c167b9c7SMaximilian Luz  * ssam_controller_destroy() - Destroy the controller and free its resources.
1312c167b9c7SMaximilian Luz  * @ctrl: The controller.
1313c167b9c7SMaximilian Luz  *
1314c167b9c7SMaximilian Luz  * Ensures that all resources associated with the controller get freed. This
1315c167b9c7SMaximilian Luz  * function should only be called after the controller has been stopped via
1316c167b9c7SMaximilian Luz  * ssam_controller_shutdown(). In general, this function should not be called
1317c167b9c7SMaximilian Luz  * directly. The only valid place to call this function directly is during
1318c167b9c7SMaximilian Luz  * initialization, before the controller has been fully initialized and passed
1319c167b9c7SMaximilian Luz  * to other processes. This function is called automatically when the
1320c167b9c7SMaximilian Luz  * reference count of the controller reaches zero.
1321c167b9c7SMaximilian Luz  *
1322c167b9c7SMaximilian Luz  * This function must be called with the main controller lock held (i.e. by
1323c167b9c7SMaximilian Luz  * calling ssam_controller_lock()).
1324c167b9c7SMaximilian Luz  */
1325c167b9c7SMaximilian Luz void ssam_controller_destroy(struct ssam_controller *ctrl)
1326c167b9c7SMaximilian Luz {
1327c167b9c7SMaximilian Luz 	lockdep_assert_held_write(&ctrl->lock);
1328c167b9c7SMaximilian Luz 
1329c167b9c7SMaximilian Luz 	if (ctrl->state == SSAM_CONTROLLER_UNINITIALIZED)
1330c167b9c7SMaximilian Luz 		return;
1331c167b9c7SMaximilian Luz 
1332c167b9c7SMaximilian Luz 	WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED);
1333c167b9c7SMaximilian Luz 
1334c167b9c7SMaximilian Luz 	/*
1335c167b9c7SMaximilian Luz 	 * Note: New events could still have been received after the previous
1336c167b9c7SMaximilian Luz 	 * flush in ssam_controller_shutdown, before the request transport layer
1337c167b9c7SMaximilian Luz 	 * has been shut down. At this point, after the shutdown, we can be sure
1338c167b9c7SMaximilian Luz 	 * that no new events will be queued. The call to ssam_cplt_destroy will
1339c167b9c7SMaximilian Luz 	 * ensure that those remaining are being completed and freed.
1340c167b9c7SMaximilian Luz 	 */
1341c167b9c7SMaximilian Luz 
1342c167b9c7SMaximilian Luz 	/* Actually free resources. */
1343c167b9c7SMaximilian Luz 	ssam_cplt_destroy(&ctrl->cplt);
1344c167b9c7SMaximilian Luz 	ssh_rtl_destroy(&ctrl->rtl);
1345c167b9c7SMaximilian Luz 
1346c167b9c7SMaximilian Luz 	/*
1347c167b9c7SMaximilian Luz 	 * Set state via write_once even though we expect to be locked/in an
1348c167b9c7SMaximilian Luz 	 * exclusive context, due to smoke-testing in
1349c167b9c7SMaximilian Luz 	 * ssam_request_sync_submit().
1350c167b9c7SMaximilian Luz 	 */
1351c167b9c7SMaximilian Luz 	WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_UNINITIALIZED);
1352c167b9c7SMaximilian Luz }
1353c167b9c7SMaximilian Luz 
1354c167b9c7SMaximilian Luz /**
1355c167b9c7SMaximilian Luz  * ssam_controller_suspend() - Suspend the controller.
1356c167b9c7SMaximilian Luz  * @ctrl: The controller to suspend.
1357c167b9c7SMaximilian Luz  *
1358c167b9c7SMaximilian Luz  * Marks the controller as suspended. Note that display-off and D0-exit
1359c167b9c7SMaximilian Luz  * notifications have to be sent manually before transitioning the controller
1360c167b9c7SMaximilian Luz  * into the suspended state via this function.
1361c167b9c7SMaximilian Luz  *
1362c167b9c7SMaximilian Luz  * See ssam_controller_resume() for the corresponding resume function.
1363c167b9c7SMaximilian Luz  *
1364c167b9c7SMaximilian Luz  * Return: Returns %-EINVAL if the controller is currently not in the
1365c167b9c7SMaximilian Luz  * "started" state.
1366c167b9c7SMaximilian Luz  */
1367c167b9c7SMaximilian Luz int ssam_controller_suspend(struct ssam_controller *ctrl)
1368c167b9c7SMaximilian Luz {
1369c167b9c7SMaximilian Luz 	ssam_controller_lock(ctrl);
1370c167b9c7SMaximilian Luz 
1371c167b9c7SMaximilian Luz 	if (ctrl->state != SSAM_CONTROLLER_STARTED) {
1372c167b9c7SMaximilian Luz 		ssam_controller_unlock(ctrl);
1373c167b9c7SMaximilian Luz 		return -EINVAL;
1374c167b9c7SMaximilian Luz 	}
1375c167b9c7SMaximilian Luz 
1376c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: suspending controller\n");
1377c167b9c7SMaximilian Luz 
1378c167b9c7SMaximilian Luz 	/*
1379c167b9c7SMaximilian Luz 	 * Set state via write_once even though we're locked, due to
1380c167b9c7SMaximilian Luz 	 * smoke-testing in ssam_request_sync_submit().
1381c167b9c7SMaximilian Luz 	 */
1382c167b9c7SMaximilian Luz 	WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_SUSPENDED);
1383c167b9c7SMaximilian Luz 
1384c167b9c7SMaximilian Luz 	ssam_controller_unlock(ctrl);
1385c167b9c7SMaximilian Luz 	return 0;
1386c167b9c7SMaximilian Luz }
1387c167b9c7SMaximilian Luz 
1388c167b9c7SMaximilian Luz /**
1389c167b9c7SMaximilian Luz  * ssam_controller_resume() - Resume the controller from suspend.
1390c167b9c7SMaximilian Luz  * @ctrl: The controller to resume.
1391c167b9c7SMaximilian Luz  *
1392c167b9c7SMaximilian Luz  * Resume the controller from the suspended state it was put into via
1393c167b9c7SMaximilian Luz  * ssam_controller_suspend(). This function does not issue display-on and
1394c167b9c7SMaximilian Luz  * D0-entry notifications. If required, those have to be sent manually after
1395c167b9c7SMaximilian Luz  * this call.
1396c167b9c7SMaximilian Luz  *
1397c167b9c7SMaximilian Luz  * Return: Returns %-EINVAL if the controller is currently not suspended.
1398c167b9c7SMaximilian Luz  */
1399c167b9c7SMaximilian Luz int ssam_controller_resume(struct ssam_controller *ctrl)
1400c167b9c7SMaximilian Luz {
1401c167b9c7SMaximilian Luz 	ssam_controller_lock(ctrl);
1402c167b9c7SMaximilian Luz 
1403c167b9c7SMaximilian Luz 	if (ctrl->state != SSAM_CONTROLLER_SUSPENDED) {
1404c167b9c7SMaximilian Luz 		ssam_controller_unlock(ctrl);
1405c167b9c7SMaximilian Luz 		return -EINVAL;
1406c167b9c7SMaximilian Luz 	}
1407c167b9c7SMaximilian Luz 
1408c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: resuming controller\n");
1409c167b9c7SMaximilian Luz 
1410c167b9c7SMaximilian Luz 	/*
1411c167b9c7SMaximilian Luz 	 * Set state via write_once even though we're locked, due to
1412c167b9c7SMaximilian Luz 	 * smoke-testing in ssam_request_sync_submit().
1413c167b9c7SMaximilian Luz 	 */
1414c167b9c7SMaximilian Luz 	WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED);
1415c167b9c7SMaximilian Luz 
1416c167b9c7SMaximilian Luz 	ssam_controller_unlock(ctrl);
1417c167b9c7SMaximilian Luz 	return 0;
1418c167b9c7SMaximilian Luz }
1419c167b9c7SMaximilian Luz 
1420c167b9c7SMaximilian Luz 
1421c167b9c7SMaximilian Luz /* -- Top-level request interface ------------------------------------------- */
1422c167b9c7SMaximilian Luz 
1423c167b9c7SMaximilian Luz /**
1424c167b9c7SMaximilian Luz  * ssam_request_write_data() - Construct and write SAM request message to
1425c167b9c7SMaximilian Luz  * buffer.
1426c167b9c7SMaximilian Luz  * @buf:  The buffer to write the data to.
1427c167b9c7SMaximilian Luz  * @ctrl: The controller via which the request will be sent.
1428c167b9c7SMaximilian Luz  * @spec: The request data and specification.
1429c167b9c7SMaximilian Luz  *
1430c167b9c7SMaximilian Luz  * Constructs a SAM/SSH request message and writes it to the provided buffer.
1431c167b9c7SMaximilian Luz  * The request and transport counters, specifically RQID and SEQ, will be set
1432c167b9c7SMaximilian Luz  * in this call. These counters are obtained from the controller. It is thus
1433c167b9c7SMaximilian Luz  * only valid to send the resulting message via the controller specified here.
1434c167b9c7SMaximilian Luz  *
1435c167b9c7SMaximilian Luz  * For calculation of the required buffer size, refer to the
1436c167b9c7SMaximilian Luz  * SSH_COMMAND_MESSAGE_LENGTH() macro.
1437c167b9c7SMaximilian Luz  *
1438c167b9c7SMaximilian Luz  * Return: Returns the number of bytes used in the buffer on success. Returns
1439c167b9c7SMaximilian Luz  * %-EINVAL if the payload length provided in the request specification is too
1440c167b9c7SMaximilian Luz  * large (larger than %SSH_COMMAND_MAX_PAYLOAD_SIZE) or if the provided buffer
1441c167b9c7SMaximilian Luz  * is too small.
1442c167b9c7SMaximilian Luz  */
1443c167b9c7SMaximilian Luz ssize_t ssam_request_write_data(struct ssam_span *buf,
1444c167b9c7SMaximilian Luz 				struct ssam_controller *ctrl,
1445c167b9c7SMaximilian Luz 				const struct ssam_request *spec)
1446c167b9c7SMaximilian Luz {
1447c167b9c7SMaximilian Luz 	struct msgbuf msgb;
1448c167b9c7SMaximilian Luz 	u16 rqid;
1449c167b9c7SMaximilian Luz 	u8 seq;
1450c167b9c7SMaximilian Luz 
1451c167b9c7SMaximilian Luz 	if (spec->length > SSH_COMMAND_MAX_PAYLOAD_SIZE)
1452c167b9c7SMaximilian Luz 		return -EINVAL;
1453c167b9c7SMaximilian Luz 
1454c167b9c7SMaximilian Luz 	if (SSH_COMMAND_MESSAGE_LENGTH(spec->length) > buf->len)
1455c167b9c7SMaximilian Luz 		return -EINVAL;
1456c167b9c7SMaximilian Luz 
1457c167b9c7SMaximilian Luz 	msgb_init(&msgb, buf->ptr, buf->len);
1458c167b9c7SMaximilian Luz 	seq = ssh_seq_next(&ctrl->counter.seq);
1459c167b9c7SMaximilian Luz 	rqid = ssh_rqid_next(&ctrl->counter.rqid);
1460c167b9c7SMaximilian Luz 	msgb_push_cmd(&msgb, seq, rqid, spec);
1461c167b9c7SMaximilian Luz 
1462c167b9c7SMaximilian Luz 	return msgb_bytes_used(&msgb);
1463c167b9c7SMaximilian Luz }
1464c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_write_data);
1465c167b9c7SMaximilian Luz 
1466c167b9c7SMaximilian Luz static void ssam_request_sync_complete(struct ssh_request *rqst,
1467c167b9c7SMaximilian Luz 				       const struct ssh_command *cmd,
1468c167b9c7SMaximilian Luz 				       const struct ssam_span *data, int status)
1469c167b9c7SMaximilian Luz {
1470c167b9c7SMaximilian Luz 	struct ssh_rtl *rtl = ssh_request_rtl(rqst);
1471c167b9c7SMaximilian Luz 	struct ssam_request_sync *r;
1472c167b9c7SMaximilian Luz 
1473c167b9c7SMaximilian Luz 	r = container_of(rqst, struct ssam_request_sync, base);
1474c167b9c7SMaximilian Luz 	r->status = status;
1475c167b9c7SMaximilian Luz 
1476c167b9c7SMaximilian Luz 	if (r->resp)
1477c167b9c7SMaximilian Luz 		r->resp->length = 0;
1478c167b9c7SMaximilian Luz 
1479c167b9c7SMaximilian Luz 	if (status) {
1480c167b9c7SMaximilian Luz 		rtl_dbg_cond(rtl, "rsp: request failed: %d\n", status);
1481c167b9c7SMaximilian Luz 		return;
1482c167b9c7SMaximilian Luz 	}
1483c167b9c7SMaximilian Luz 
1484c167b9c7SMaximilian Luz 	if (!data)	/* Handle requests without a response. */
1485c167b9c7SMaximilian Luz 		return;
1486c167b9c7SMaximilian Luz 
1487c167b9c7SMaximilian Luz 	if (!r->resp || !r->resp->pointer) {
1488c167b9c7SMaximilian Luz 		if (data->len)
1489c167b9c7SMaximilian Luz 			rtl_warn(rtl, "rsp: no response buffer provided, dropping data\n");
1490c167b9c7SMaximilian Luz 		return;
1491c167b9c7SMaximilian Luz 	}
1492c167b9c7SMaximilian Luz 
1493c167b9c7SMaximilian Luz 	if (data->len > r->resp->capacity) {
1494c167b9c7SMaximilian Luz 		rtl_err(rtl,
1495c167b9c7SMaximilian Luz 			"rsp: response buffer too small, capacity: %zu bytes, got: %zu bytes\n",
1496c167b9c7SMaximilian Luz 			r->resp->capacity, data->len);
1497c167b9c7SMaximilian Luz 		r->status = -ENOSPC;
1498c167b9c7SMaximilian Luz 		return;
1499c167b9c7SMaximilian Luz 	}
1500c167b9c7SMaximilian Luz 
1501c167b9c7SMaximilian Luz 	r->resp->length = data->len;
1502c167b9c7SMaximilian Luz 	memcpy(r->resp->pointer, data->ptr, data->len);
1503c167b9c7SMaximilian Luz }
1504c167b9c7SMaximilian Luz 
1505c167b9c7SMaximilian Luz static void ssam_request_sync_release(struct ssh_request *rqst)
1506c167b9c7SMaximilian Luz {
1507c167b9c7SMaximilian Luz 	complete_all(&container_of(rqst, struct ssam_request_sync, base)->comp);
1508c167b9c7SMaximilian Luz }
1509c167b9c7SMaximilian Luz 
1510c167b9c7SMaximilian Luz static const struct ssh_request_ops ssam_request_sync_ops = {
1511c167b9c7SMaximilian Luz 	.release = ssam_request_sync_release,
1512c167b9c7SMaximilian Luz 	.complete = ssam_request_sync_complete,
1513c167b9c7SMaximilian Luz };
1514c167b9c7SMaximilian Luz 
1515c167b9c7SMaximilian Luz /**
1516c167b9c7SMaximilian Luz  * ssam_request_sync_alloc() - Allocate a synchronous request.
1517c167b9c7SMaximilian Luz  * @payload_len: The length of the request payload.
1518c167b9c7SMaximilian Luz  * @flags:       Flags used for allocation.
1519c167b9c7SMaximilian Luz  * @rqst:        Where to store the pointer to the allocated request.
1520c167b9c7SMaximilian Luz  * @buffer:      Where to store the buffer descriptor for the message buffer of
1521c167b9c7SMaximilian Luz  *               the request.
1522c167b9c7SMaximilian Luz  *
1523c167b9c7SMaximilian Luz  * Allocates a synchronous request with corresponding message buffer. The
1524c167b9c7SMaximilian Luz  * request still needs to be initialized ssam_request_sync_init() before
1525c167b9c7SMaximilian Luz  * it can be submitted, and the message buffer data must still be set to the
1526c167b9c7SMaximilian Luz  * returned buffer via ssam_request_sync_set_data() after it has been filled,
1527c167b9c7SMaximilian Luz  * if need be with adjusted message length.
1528c167b9c7SMaximilian Luz  *
1529c167b9c7SMaximilian Luz  * After use, the request and its corresponding message buffer should be freed
1530c167b9c7SMaximilian Luz  * via ssam_request_sync_free(). The buffer must not be freed separately.
1531c167b9c7SMaximilian Luz  *
1532c167b9c7SMaximilian Luz  * Return: Returns zero on success, %-ENOMEM if the request could not be
1533c167b9c7SMaximilian Luz  * allocated.
1534c167b9c7SMaximilian Luz  */
1535c167b9c7SMaximilian Luz int ssam_request_sync_alloc(size_t payload_len, gfp_t flags,
1536c167b9c7SMaximilian Luz 			    struct ssam_request_sync **rqst,
1537c167b9c7SMaximilian Luz 			    struct ssam_span *buffer)
1538c167b9c7SMaximilian Luz {
1539c167b9c7SMaximilian Luz 	size_t msglen = SSH_COMMAND_MESSAGE_LENGTH(payload_len);
1540c167b9c7SMaximilian Luz 
1541c167b9c7SMaximilian Luz 	*rqst = kzalloc(sizeof(**rqst) + msglen, flags);
1542c167b9c7SMaximilian Luz 	if (!*rqst)
1543c167b9c7SMaximilian Luz 		return -ENOMEM;
1544c167b9c7SMaximilian Luz 
1545c167b9c7SMaximilian Luz 	buffer->ptr = (u8 *)(*rqst + 1);
1546c167b9c7SMaximilian Luz 	buffer->len = msglen;
1547c167b9c7SMaximilian Luz 
1548c167b9c7SMaximilian Luz 	return 0;
1549c167b9c7SMaximilian Luz }
1550c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_sync_alloc);
1551c167b9c7SMaximilian Luz 
1552c167b9c7SMaximilian Luz /**
1553c167b9c7SMaximilian Luz  * ssam_request_sync_free() - Free a synchronous request.
1554c167b9c7SMaximilian Luz  * @rqst: The request to be freed.
1555c167b9c7SMaximilian Luz  *
1556c167b9c7SMaximilian Luz  * Free a synchronous request and its corresponding buffer allocated with
1557c167b9c7SMaximilian Luz  * ssam_request_sync_alloc(). Do not use for requests allocated on the stack
1558c167b9c7SMaximilian Luz  * or via any other function.
1559c167b9c7SMaximilian Luz  *
1560c167b9c7SMaximilian Luz  * Warning: The caller must ensure that the request is not in use any more.
1561c167b9c7SMaximilian Luz  * I.e. the caller must ensure that it has the only reference to the request
1562c167b9c7SMaximilian Luz  * and the request is not currently pending. This means that the caller has
1563c167b9c7SMaximilian Luz  * either never submitted the request, request submission has failed, or the
1564c167b9c7SMaximilian Luz  * caller has waited until the submitted request has been completed via
1565c167b9c7SMaximilian Luz  * ssam_request_sync_wait().
1566c167b9c7SMaximilian Luz  */
1567c167b9c7SMaximilian Luz void ssam_request_sync_free(struct ssam_request_sync *rqst)
1568c167b9c7SMaximilian Luz {
1569c167b9c7SMaximilian Luz 	kfree(rqst);
1570c167b9c7SMaximilian Luz }
1571c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_sync_free);
1572c167b9c7SMaximilian Luz 
1573c167b9c7SMaximilian Luz /**
1574c167b9c7SMaximilian Luz  * ssam_request_sync_init() - Initialize a synchronous request struct.
1575c167b9c7SMaximilian Luz  * @rqst:  The request to initialize.
1576c167b9c7SMaximilian Luz  * @flags: The request flags.
1577c167b9c7SMaximilian Luz  *
1578c167b9c7SMaximilian Luz  * Initializes the given request struct. Does not initialize the request
1579c167b9c7SMaximilian Luz  * message data. This has to be done explicitly after this call via
1580c167b9c7SMaximilian Luz  * ssam_request_sync_set_data() and the actual message data has to be written
1581c167b9c7SMaximilian Luz  * via ssam_request_write_data().
1582c167b9c7SMaximilian Luz  *
1583c167b9c7SMaximilian Luz  * Return: Returns zero on success or %-EINVAL if the given flags are invalid.
1584c167b9c7SMaximilian Luz  */
1585c167b9c7SMaximilian Luz int ssam_request_sync_init(struct ssam_request_sync *rqst,
1586c167b9c7SMaximilian Luz 			   enum ssam_request_flags flags)
1587c167b9c7SMaximilian Luz {
1588c167b9c7SMaximilian Luz 	int status;
1589c167b9c7SMaximilian Luz 
1590c167b9c7SMaximilian Luz 	status = ssh_request_init(&rqst->base, flags, &ssam_request_sync_ops);
1591c167b9c7SMaximilian Luz 	if (status)
1592c167b9c7SMaximilian Luz 		return status;
1593c167b9c7SMaximilian Luz 
1594c167b9c7SMaximilian Luz 	init_completion(&rqst->comp);
1595c167b9c7SMaximilian Luz 	rqst->resp = NULL;
1596c167b9c7SMaximilian Luz 	rqst->status = 0;
1597c167b9c7SMaximilian Luz 
1598c167b9c7SMaximilian Luz 	return 0;
1599c167b9c7SMaximilian Luz }
1600c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_sync_init);
1601c167b9c7SMaximilian Luz 
1602c167b9c7SMaximilian Luz /**
1603c167b9c7SMaximilian Luz  * ssam_request_sync_submit() - Submit a synchronous request.
1604c167b9c7SMaximilian Luz  * @ctrl: The controller with which to submit the request.
1605c167b9c7SMaximilian Luz  * @rqst: The request to submit.
1606c167b9c7SMaximilian Luz  *
1607c167b9c7SMaximilian Luz  * Submit a synchronous request. The request has to be initialized and
1608c167b9c7SMaximilian Luz  * properly set up, including response buffer (may be %NULL if no response is
1609c167b9c7SMaximilian Luz  * expected) and command message data. This function does not wait for the
1610c167b9c7SMaximilian Luz  * request to be completed.
1611c167b9c7SMaximilian Luz  *
1612c167b9c7SMaximilian Luz  * If this function succeeds, ssam_request_sync_wait() must be used to ensure
1613c167b9c7SMaximilian Luz  * that the request has been completed before the response data can be
1614c167b9c7SMaximilian Luz  * accessed and/or the request can be freed. On failure, the request may
1615c167b9c7SMaximilian Luz  * immediately be freed.
1616c167b9c7SMaximilian Luz  *
1617c167b9c7SMaximilian Luz  * This function may only be used if the controller is active, i.e. has been
1618c167b9c7SMaximilian Luz  * initialized and not suspended.
1619c167b9c7SMaximilian Luz  */
1620c167b9c7SMaximilian Luz int ssam_request_sync_submit(struct ssam_controller *ctrl,
1621c167b9c7SMaximilian Luz 			     struct ssam_request_sync *rqst)
1622c167b9c7SMaximilian Luz {
1623c167b9c7SMaximilian Luz 	int status;
1624c167b9c7SMaximilian Luz 
1625c167b9c7SMaximilian Luz 	/*
1626c167b9c7SMaximilian Luz 	 * This is only a superficial check. In general, the caller needs to
1627c167b9c7SMaximilian Luz 	 * ensure that the controller is initialized and is not (and does not
1628c167b9c7SMaximilian Luz 	 * get) suspended during use, i.e. until the request has been completed
1629c167b9c7SMaximilian Luz 	 * (if _absolutely_ necessary, by use of ssam_controller_statelock/
1630c167b9c7SMaximilian Luz 	 * ssam_controller_stateunlock, but something like ssam_client_link
1631c167b9c7SMaximilian Luz 	 * should be preferred as this needs to last until the request has been
1632c167b9c7SMaximilian Luz 	 * completed).
1633c167b9c7SMaximilian Luz 	 *
1634c167b9c7SMaximilian Luz 	 * Note that it is actually safe to use this function while the
1635c167b9c7SMaximilian Luz 	 * controller is in the process of being shut down (as ssh_rtl_submit
1636c167b9c7SMaximilian Luz 	 * is safe with regards to this), but it is generally discouraged to do
1637c167b9c7SMaximilian Luz 	 * so.
1638c167b9c7SMaximilian Luz 	 */
1639c167b9c7SMaximilian Luz 	if (WARN_ON(READ_ONCE(ctrl->state) != SSAM_CONTROLLER_STARTED)) {
1640c167b9c7SMaximilian Luz 		ssh_request_put(&rqst->base);
1641c167b9c7SMaximilian Luz 		return -ENODEV;
1642c167b9c7SMaximilian Luz 	}
1643c167b9c7SMaximilian Luz 
1644c167b9c7SMaximilian Luz 	status = ssh_rtl_submit(&ctrl->rtl, &rqst->base);
1645c167b9c7SMaximilian Luz 	ssh_request_put(&rqst->base);
1646c167b9c7SMaximilian Luz 
1647c167b9c7SMaximilian Luz 	return status;
1648c167b9c7SMaximilian Luz }
1649c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_sync_submit);
1650c167b9c7SMaximilian Luz 
1651c167b9c7SMaximilian Luz /**
1652c167b9c7SMaximilian Luz  * ssam_request_sync() - Execute a synchronous request.
1653c167b9c7SMaximilian Luz  * @ctrl: The controller via which the request will be submitted.
1654c167b9c7SMaximilian Luz  * @spec: The request specification and payload.
1655c167b9c7SMaximilian Luz  * @rsp:  The response buffer.
1656c167b9c7SMaximilian Luz  *
1657c167b9c7SMaximilian Luz  * Allocates a synchronous request with its message data buffer on the heap
1658c167b9c7SMaximilian Luz  * via ssam_request_sync_alloc(), fully initializes it via the provided
1659c167b9c7SMaximilian Luz  * request specification, submits it, and finally waits for its completion
1660c167b9c7SMaximilian Luz  * before freeing it and returning its status.
1661c167b9c7SMaximilian Luz  *
1662c167b9c7SMaximilian Luz  * Return: Returns the status of the request or any failure during setup.
1663c167b9c7SMaximilian Luz  */
1664c167b9c7SMaximilian Luz int ssam_request_sync(struct ssam_controller *ctrl,
1665c167b9c7SMaximilian Luz 		      const struct ssam_request *spec,
1666c167b9c7SMaximilian Luz 		      struct ssam_response *rsp)
1667c167b9c7SMaximilian Luz {
1668c167b9c7SMaximilian Luz 	struct ssam_request_sync *rqst;
1669c167b9c7SMaximilian Luz 	struct ssam_span buf;
1670c167b9c7SMaximilian Luz 	ssize_t len;
1671c167b9c7SMaximilian Luz 	int status;
1672c167b9c7SMaximilian Luz 
1673c167b9c7SMaximilian Luz 	status = ssam_request_sync_alloc(spec->length, GFP_KERNEL, &rqst, &buf);
1674c167b9c7SMaximilian Luz 	if (status)
1675c167b9c7SMaximilian Luz 		return status;
1676c167b9c7SMaximilian Luz 
1677c167b9c7SMaximilian Luz 	status = ssam_request_sync_init(rqst, spec->flags);
1678c167b9c7SMaximilian Luz 	if (status)
1679c167b9c7SMaximilian Luz 		return status;
1680c167b9c7SMaximilian Luz 
1681c167b9c7SMaximilian Luz 	ssam_request_sync_set_resp(rqst, rsp);
1682c167b9c7SMaximilian Luz 
1683c167b9c7SMaximilian Luz 	len = ssam_request_write_data(&buf, ctrl, spec);
1684c167b9c7SMaximilian Luz 	if (len < 0) {
1685c167b9c7SMaximilian Luz 		ssam_request_sync_free(rqst);
1686c167b9c7SMaximilian Luz 		return len;
1687c167b9c7SMaximilian Luz 	}
1688c167b9c7SMaximilian Luz 
1689c167b9c7SMaximilian Luz 	ssam_request_sync_set_data(rqst, buf.ptr, len);
1690c167b9c7SMaximilian Luz 
1691c167b9c7SMaximilian Luz 	status = ssam_request_sync_submit(ctrl, rqst);
1692c167b9c7SMaximilian Luz 	if (!status)
1693c167b9c7SMaximilian Luz 		status = ssam_request_sync_wait(rqst);
1694c167b9c7SMaximilian Luz 
1695c167b9c7SMaximilian Luz 	ssam_request_sync_free(rqst);
1696c167b9c7SMaximilian Luz 	return status;
1697c167b9c7SMaximilian Luz }
1698c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_sync);
1699c167b9c7SMaximilian Luz 
1700c167b9c7SMaximilian Luz /**
1701c167b9c7SMaximilian Luz  * ssam_request_sync_with_buffer() - Execute a synchronous request with the
1702c167b9c7SMaximilian Luz  * provided buffer as back-end for the message buffer.
1703c167b9c7SMaximilian Luz  * @ctrl: The controller via which the request will be submitted.
1704c167b9c7SMaximilian Luz  * @spec: The request specification and payload.
1705c167b9c7SMaximilian Luz  * @rsp:  The response buffer.
1706c167b9c7SMaximilian Luz  * @buf:  The buffer for the request message data.
1707c167b9c7SMaximilian Luz  *
1708c167b9c7SMaximilian Luz  * Allocates a synchronous request struct on the stack, fully initializes it
1709c167b9c7SMaximilian Luz  * using the provided buffer as message data buffer, submits it, and then
1710c167b9c7SMaximilian Luz  * waits for its completion before returning its status. The
1711c167b9c7SMaximilian Luz  * SSH_COMMAND_MESSAGE_LENGTH() macro can be used to compute the required
1712c167b9c7SMaximilian Luz  * message buffer size.
1713c167b9c7SMaximilian Luz  *
1714c167b9c7SMaximilian Luz  * This function does essentially the same as ssam_request_sync(), but instead
1715c167b9c7SMaximilian Luz  * of dynamically allocating the request and message data buffer, it uses the
1716c167b9c7SMaximilian Luz  * provided message data buffer and stores the (small) request struct on the
1717c167b9c7SMaximilian Luz  * heap.
1718c167b9c7SMaximilian Luz  *
1719c167b9c7SMaximilian Luz  * Return: Returns the status of the request or any failure during setup.
1720c167b9c7SMaximilian Luz  */
1721c167b9c7SMaximilian Luz int ssam_request_sync_with_buffer(struct ssam_controller *ctrl,
1722c167b9c7SMaximilian Luz 				  const struct ssam_request *spec,
1723c167b9c7SMaximilian Luz 				  struct ssam_response *rsp,
1724c167b9c7SMaximilian Luz 				  struct ssam_span *buf)
1725c167b9c7SMaximilian Luz {
1726c167b9c7SMaximilian Luz 	struct ssam_request_sync rqst;
1727c167b9c7SMaximilian Luz 	ssize_t len;
1728c167b9c7SMaximilian Luz 	int status;
1729c167b9c7SMaximilian Luz 
1730c167b9c7SMaximilian Luz 	status = ssam_request_sync_init(&rqst, spec->flags);
1731c167b9c7SMaximilian Luz 	if (status)
1732c167b9c7SMaximilian Luz 		return status;
1733c167b9c7SMaximilian Luz 
1734c167b9c7SMaximilian Luz 	ssam_request_sync_set_resp(&rqst, rsp);
1735c167b9c7SMaximilian Luz 
1736c167b9c7SMaximilian Luz 	len = ssam_request_write_data(buf, ctrl, spec);
1737c167b9c7SMaximilian Luz 	if (len < 0)
1738c167b9c7SMaximilian Luz 		return len;
1739c167b9c7SMaximilian Luz 
1740c167b9c7SMaximilian Luz 	ssam_request_sync_set_data(&rqst, buf->ptr, len);
1741c167b9c7SMaximilian Luz 
1742c167b9c7SMaximilian Luz 	status = ssam_request_sync_submit(ctrl, &rqst);
1743c167b9c7SMaximilian Luz 	if (!status)
1744c167b9c7SMaximilian Luz 		status = ssam_request_sync_wait(&rqst);
1745c167b9c7SMaximilian Luz 
1746c167b9c7SMaximilian Luz 	return status;
1747c167b9c7SMaximilian Luz }
1748c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer);
1749c167b9c7SMaximilian Luz 
1750c167b9c7SMaximilian Luz 
1751c167b9c7SMaximilian Luz /* -- Internal SAM requests. ------------------------------------------------ */
1752c167b9c7SMaximilian Luz 
175303ee3183SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, {
1754c167b9c7SMaximilian Luz 	.target_category = SSAM_SSH_TC_SAM,
1755c167b9c7SMaximilian Luz 	.target_id       = 0x01,
1756c167b9c7SMaximilian Luz 	.command_id      = 0x13,
1757c167b9c7SMaximilian Luz 	.instance_id     = 0x00,
1758c167b9c7SMaximilian Luz });
1759c167b9c7SMaximilian Luz 
176003ee3183SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, {
1761c167b9c7SMaximilian Luz 	.target_category = SSAM_SSH_TC_SAM,
1762c167b9c7SMaximilian Luz 	.target_id       = 0x01,
1763c167b9c7SMaximilian Luz 	.command_id      = 0x15,
1764c167b9c7SMaximilian Luz 	.instance_id     = 0x00,
1765c167b9c7SMaximilian Luz });
1766c167b9c7SMaximilian Luz 
176703ee3183SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, {
1768c167b9c7SMaximilian Luz 	.target_category = SSAM_SSH_TC_SAM,
1769c167b9c7SMaximilian Luz 	.target_id       = 0x01,
1770c167b9c7SMaximilian Luz 	.command_id      = 0x16,
1771c167b9c7SMaximilian Luz 	.instance_id     = 0x00,
1772c167b9c7SMaximilian Luz });
1773c167b9c7SMaximilian Luz 
177403ee3183SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, {
1775c167b9c7SMaximilian Luz 	.target_category = SSAM_SSH_TC_SAM,
1776c167b9c7SMaximilian Luz 	.target_id       = 0x01,
1777c167b9c7SMaximilian Luz 	.command_id      = 0x33,
1778c167b9c7SMaximilian Luz 	.instance_id     = 0x00,
1779c167b9c7SMaximilian Luz });
1780c167b9c7SMaximilian Luz 
178103ee3183SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, {
1782c167b9c7SMaximilian Luz 	.target_category = SSAM_SSH_TC_SAM,
1783c167b9c7SMaximilian Luz 	.target_id       = 0x01,
1784c167b9c7SMaximilian Luz 	.command_id      = 0x34,
1785c167b9c7SMaximilian Luz 	.instance_id     = 0x00,
1786c167b9c7SMaximilian Luz });
1787c167b9c7SMaximilian Luz 
1788c167b9c7SMaximilian Luz /**
1789c167b9c7SMaximilian Luz  * struct ssh_notification_params - Command payload to enable/disable SSH
1790c167b9c7SMaximilian Luz  * notifications.
1791c167b9c7SMaximilian Luz  * @target_category: The target category for which notifications should be
1792c167b9c7SMaximilian Luz  *                   enabled/disabled.
1793c167b9c7SMaximilian Luz  * @flags:           Flags determining how notifications are being sent.
1794c167b9c7SMaximilian Luz  * @request_id:      The request ID that is used to send these notifications.
1795c167b9c7SMaximilian Luz  * @instance_id:     The specific instance in the given target category for
1796c167b9c7SMaximilian Luz  *                   which notifications should be enabled.
1797c167b9c7SMaximilian Luz  */
1798c167b9c7SMaximilian Luz struct ssh_notification_params {
1799c167b9c7SMaximilian Luz 	u8 target_category;
1800c167b9c7SMaximilian Luz 	u8 flags;
1801c167b9c7SMaximilian Luz 	__le16 request_id;
1802c167b9c7SMaximilian Luz 	u8 instance_id;
1803c167b9c7SMaximilian Luz } __packed;
1804c167b9c7SMaximilian Luz 
1805c167b9c7SMaximilian Luz static_assert(sizeof(struct ssh_notification_params) == 5);
1806c167b9c7SMaximilian Luz 
1807c167b9c7SMaximilian Luz static int __ssam_ssh_event_request(struct ssam_controller *ctrl,
1808c167b9c7SMaximilian Luz 				    struct ssam_event_registry reg, u8 cid,
1809c167b9c7SMaximilian Luz 				    struct ssam_event_id id, u8 flags)
1810c167b9c7SMaximilian Luz {
1811c167b9c7SMaximilian Luz 	struct ssh_notification_params params;
1812c167b9c7SMaximilian Luz 	struct ssam_request rqst;
1813c167b9c7SMaximilian Luz 	struct ssam_response result;
1814c167b9c7SMaximilian Luz 	int status;
1815c167b9c7SMaximilian Luz 
1816c167b9c7SMaximilian Luz 	u16 rqid = ssh_tc_to_rqid(id.target_category);
1817c167b9c7SMaximilian Luz 	u8 buf = 0;
1818c167b9c7SMaximilian Luz 
1819c167b9c7SMaximilian Luz 	/* Only allow RQIDs that lie within the event spectrum. */
1820c167b9c7SMaximilian Luz 	if (!ssh_rqid_is_event(rqid))
1821c167b9c7SMaximilian Luz 		return -EINVAL;
1822c167b9c7SMaximilian Luz 
1823c167b9c7SMaximilian Luz 	params.target_category = id.target_category;
1824c167b9c7SMaximilian Luz 	params.instance_id = id.instance;
1825c167b9c7SMaximilian Luz 	params.flags = flags;
1826c167b9c7SMaximilian Luz 	put_unaligned_le16(rqid, &params.request_id);
1827c167b9c7SMaximilian Luz 
1828c167b9c7SMaximilian Luz 	rqst.target_category = reg.target_category;
1829c167b9c7SMaximilian Luz 	rqst.target_id = reg.target_id;
1830c167b9c7SMaximilian Luz 	rqst.command_id = cid;
1831c167b9c7SMaximilian Luz 	rqst.instance_id = 0x00;
1832c167b9c7SMaximilian Luz 	rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
1833c167b9c7SMaximilian Luz 	rqst.length = sizeof(params);
1834c167b9c7SMaximilian Luz 	rqst.payload = (u8 *)&params;
1835c167b9c7SMaximilian Luz 
1836c167b9c7SMaximilian Luz 	result.capacity = sizeof(buf);
1837c167b9c7SMaximilian Luz 	result.length = 0;
1838c167b9c7SMaximilian Luz 	result.pointer = &buf;
1839c167b9c7SMaximilian Luz 
1840c167b9c7SMaximilian Luz 	status = ssam_retry(ssam_request_sync_onstack, ctrl, &rqst, &result,
1841c167b9c7SMaximilian Luz 			    sizeof(params));
1842c167b9c7SMaximilian Luz 
1843c167b9c7SMaximilian Luz 	return status < 0 ? status : buf;
1844c167b9c7SMaximilian Luz }
1845c167b9c7SMaximilian Luz 
1846c167b9c7SMaximilian Luz /**
1847c167b9c7SMaximilian Luz  * ssam_ssh_event_enable() - Enable SSH event.
1848c167b9c7SMaximilian Luz  * @ctrl:  The controller for which to enable the event.
1849c167b9c7SMaximilian Luz  * @reg:   The event registry describing what request to use for enabling and
1850c167b9c7SMaximilian Luz  *         disabling the event.
1851c167b9c7SMaximilian Luz  * @id:    The event identifier.
1852c167b9c7SMaximilian Luz  * @flags: The event flags.
1853c167b9c7SMaximilian Luz  *
1854c167b9c7SMaximilian Luz  * Enables the specified event on the EC. This function does not manage
1855c167b9c7SMaximilian Luz  * reference counting of enabled events and is basically only a wrapper for
1856c167b9c7SMaximilian Luz  * the raw EC request. If the specified event is already enabled, the EC will
1857c167b9c7SMaximilian Luz  * ignore this request.
1858c167b9c7SMaximilian Luz  *
1859c167b9c7SMaximilian Luz  * Return: Returns the status of the executed SAM request (zero on success and
1860c167b9c7SMaximilian Luz  * negative on direct failure) or %-EPROTO if the request response indicates a
1861c167b9c7SMaximilian Luz  * failure.
1862c167b9c7SMaximilian Luz  */
1863c167b9c7SMaximilian Luz static int ssam_ssh_event_enable(struct ssam_controller *ctrl,
1864c167b9c7SMaximilian Luz 				 struct ssam_event_registry reg,
1865c167b9c7SMaximilian Luz 				 struct ssam_event_id id, u8 flags)
1866c167b9c7SMaximilian Luz {
1867c167b9c7SMaximilian Luz 	int status;
1868c167b9c7SMaximilian Luz 
1869c167b9c7SMaximilian Luz 	status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags);
1870c167b9c7SMaximilian Luz 
1871c167b9c7SMaximilian Luz 	if (status < 0 && status != -EINVAL) {
1872c167b9c7SMaximilian Luz 		ssam_err(ctrl,
1873c167b9c7SMaximilian Luz 			 "failed to enable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n",
1874c167b9c7SMaximilian Luz 			 id.target_category, id.instance, reg.target_category);
1875c167b9c7SMaximilian Luz 	}
1876c167b9c7SMaximilian Luz 
1877c167b9c7SMaximilian Luz 	if (status > 0) {
1878c167b9c7SMaximilian Luz 		ssam_err(ctrl,
1879c167b9c7SMaximilian Luz 			 "unexpected result while enabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n",
1880c167b9c7SMaximilian Luz 			 status, id.target_category, id.instance, reg.target_category);
1881c167b9c7SMaximilian Luz 		return -EPROTO;
1882c167b9c7SMaximilian Luz 	}
1883c167b9c7SMaximilian Luz 
1884c167b9c7SMaximilian Luz 	return status;
1885c167b9c7SMaximilian Luz }
1886c167b9c7SMaximilian Luz 
1887c167b9c7SMaximilian Luz /**
1888c167b9c7SMaximilian Luz  * ssam_ssh_event_disable() - Disable SSH event.
1889c167b9c7SMaximilian Luz  * @ctrl:  The controller for which to disable the event.
1890c167b9c7SMaximilian Luz  * @reg:   The event registry describing what request to use for enabling and
1891c167b9c7SMaximilian Luz  *         disabling the event (must be same as used when enabling the event).
1892c167b9c7SMaximilian Luz  * @id:    The event identifier.
1893c167b9c7SMaximilian Luz  * @flags: The event flags (likely ignored for disabling of events).
1894c167b9c7SMaximilian Luz  *
1895c167b9c7SMaximilian Luz  * Disables the specified event on the EC. This function does not manage
1896c167b9c7SMaximilian Luz  * reference counting of enabled events and is basically only a wrapper for
1897c167b9c7SMaximilian Luz  * the raw EC request. If the specified event is already disabled, the EC will
1898c167b9c7SMaximilian Luz  * ignore this request.
1899c167b9c7SMaximilian Luz  *
1900c167b9c7SMaximilian Luz  * Return: Returns the status of the executed SAM request (zero on success and
1901c167b9c7SMaximilian Luz  * negative on direct failure) or %-EPROTO if the request response indicates a
1902c167b9c7SMaximilian Luz  * failure.
1903c167b9c7SMaximilian Luz  */
1904c167b9c7SMaximilian Luz static int ssam_ssh_event_disable(struct ssam_controller *ctrl,
1905c167b9c7SMaximilian Luz 				  struct ssam_event_registry reg,
1906c167b9c7SMaximilian Luz 				  struct ssam_event_id id, u8 flags)
1907c167b9c7SMaximilian Luz {
1908c167b9c7SMaximilian Luz 	int status;
1909c167b9c7SMaximilian Luz 
1910c167b9c7SMaximilian Luz 	status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags);
1911c167b9c7SMaximilian Luz 
1912c167b9c7SMaximilian Luz 	if (status < 0 && status != -EINVAL) {
1913c167b9c7SMaximilian Luz 		ssam_err(ctrl,
1914c167b9c7SMaximilian Luz 			 "failed to disable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n",
1915c167b9c7SMaximilian Luz 			 id.target_category, id.instance, reg.target_category);
1916c167b9c7SMaximilian Luz 	}
1917c167b9c7SMaximilian Luz 
1918c167b9c7SMaximilian Luz 	if (status > 0) {
1919c167b9c7SMaximilian Luz 		ssam_err(ctrl,
1920c167b9c7SMaximilian Luz 			 "unexpected result while disabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n",
1921c167b9c7SMaximilian Luz 			 status, id.target_category, id.instance, reg.target_category);
1922c167b9c7SMaximilian Luz 		return -EPROTO;
1923c167b9c7SMaximilian Luz 	}
1924c167b9c7SMaximilian Luz 
1925c167b9c7SMaximilian Luz 	return status;
1926c167b9c7SMaximilian Luz }
1927c167b9c7SMaximilian Luz 
1928c167b9c7SMaximilian Luz 
1929c167b9c7SMaximilian Luz /* -- Wrappers for internal SAM requests. ----------------------------------- */
1930c167b9c7SMaximilian Luz 
1931c167b9c7SMaximilian Luz /**
1932c167b9c7SMaximilian Luz  * ssam_get_firmware_version() - Get the SAM/EC firmware version.
1933c167b9c7SMaximilian Luz  * @ctrl:    The controller.
1934c167b9c7SMaximilian Luz  * @version: Where to store the version number.
1935c167b9c7SMaximilian Luz  *
1936c167b9c7SMaximilian Luz  * Return: Returns zero on success or the status of the executed SAM request
1937c167b9c7SMaximilian Luz  * if that request failed.
1938c167b9c7SMaximilian Luz  */
1939c167b9c7SMaximilian Luz int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version)
1940c167b9c7SMaximilian Luz {
1941c167b9c7SMaximilian Luz 	__le32 __version;
1942c167b9c7SMaximilian Luz 	int status;
1943c167b9c7SMaximilian Luz 
1944c167b9c7SMaximilian Luz 	status = ssam_retry(ssam_ssh_get_firmware_version, ctrl, &__version);
1945c167b9c7SMaximilian Luz 	if (status)
1946c167b9c7SMaximilian Luz 		return status;
1947c167b9c7SMaximilian Luz 
1948c167b9c7SMaximilian Luz 	*version = le32_to_cpu(__version);
1949c167b9c7SMaximilian Luz 	return 0;
1950c167b9c7SMaximilian Luz }
1951c167b9c7SMaximilian Luz 
1952c167b9c7SMaximilian Luz /**
1953c167b9c7SMaximilian Luz  * ssam_ctrl_notif_display_off() - Notify EC that the display has been turned
1954c167b9c7SMaximilian Luz  * off.
1955c167b9c7SMaximilian Luz  * @ctrl: The controller.
1956c167b9c7SMaximilian Luz  *
1957c167b9c7SMaximilian Luz  * Notify the EC that the display has been turned off and the driver may enter
1958c167b9c7SMaximilian Luz  * a lower-power state. This will prevent events from being sent directly.
1959c167b9c7SMaximilian Luz  * Rather, the EC signals an event by pulling the wakeup GPIO high for as long
1960c167b9c7SMaximilian Luz  * as there are pending events. The events then need to be manually released,
1961c167b9c7SMaximilian Luz  * one by one, via the GPIO callback request. All pending events accumulated
1962c167b9c7SMaximilian Luz  * during this state can also be released by issuing the display-on
1963c167b9c7SMaximilian Luz  * notification, e.g. via ssam_ctrl_notif_display_on(), which will also reset
1964c167b9c7SMaximilian Luz  * the GPIO.
1965c167b9c7SMaximilian Luz  *
1966c167b9c7SMaximilian Luz  * On some devices, specifically ones with an integrated keyboard, the keyboard
1967c167b9c7SMaximilian Luz  * backlight will be turned off by this call.
1968c167b9c7SMaximilian Luz  *
1969c167b9c7SMaximilian Luz  * This function will only send the display-off notification command if
1970c167b9c7SMaximilian Luz  * display notifications are supported by the EC. Currently all known devices
1971c167b9c7SMaximilian Luz  * support these notifications.
1972c167b9c7SMaximilian Luz  *
1973c167b9c7SMaximilian Luz  * Use ssam_ctrl_notif_display_on() to reverse the effects of this function.
1974c167b9c7SMaximilian Luz  *
1975c167b9c7SMaximilian Luz  * Return: Returns zero on success or if no request has been executed, the
1976c167b9c7SMaximilian Luz  * status of the executed SAM request if that request failed, or %-EPROTO if
1977c167b9c7SMaximilian Luz  * an unexpected response has been received.
1978c167b9c7SMaximilian Luz  */
1979c167b9c7SMaximilian Luz int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl)
1980c167b9c7SMaximilian Luz {
1981c167b9c7SMaximilian Luz 	int status;
1982c167b9c7SMaximilian Luz 	u8 response;
1983c167b9c7SMaximilian Luz 
1984c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: notifying display off\n");
1985c167b9c7SMaximilian Luz 
1986c167b9c7SMaximilian Luz 	status = ssam_retry(ssam_ssh_notif_display_off, ctrl, &response);
1987c167b9c7SMaximilian Luz 	if (status)
1988c167b9c7SMaximilian Luz 		return status;
1989c167b9c7SMaximilian Luz 
1990c167b9c7SMaximilian Luz 	if (response != 0) {
1991c167b9c7SMaximilian Luz 		ssam_err(ctrl, "unexpected response from display-off notification: %#04x\n",
1992c167b9c7SMaximilian Luz 			 response);
1993c167b9c7SMaximilian Luz 		return -EPROTO;
1994c167b9c7SMaximilian Luz 	}
1995c167b9c7SMaximilian Luz 
1996c167b9c7SMaximilian Luz 	return 0;
1997c167b9c7SMaximilian Luz }
1998c167b9c7SMaximilian Luz 
1999c167b9c7SMaximilian Luz /**
2000c167b9c7SMaximilian Luz  * ssam_ctrl_notif_display_on() - Notify EC that the display has been turned on.
2001c167b9c7SMaximilian Luz  * @ctrl: The controller.
2002c167b9c7SMaximilian Luz  *
2003c167b9c7SMaximilian Luz  * Notify the EC that the display has been turned back on and the driver has
2004c167b9c7SMaximilian Luz  * exited its lower-power state. This notification is the counterpart to the
2005c167b9c7SMaximilian Luz  * display-off notification sent via ssam_ctrl_notif_display_off() and will
2006c167b9c7SMaximilian Luz  * reverse its effects, including resetting events to their default behavior.
2007c167b9c7SMaximilian Luz  *
2008c167b9c7SMaximilian Luz  * This function will only send the display-on notification command if display
2009c167b9c7SMaximilian Luz  * notifications are supported by the EC. Currently all known devices support
2010c167b9c7SMaximilian Luz  * these notifications.
2011c167b9c7SMaximilian Luz  *
2012c167b9c7SMaximilian Luz  * See ssam_ctrl_notif_display_off() for more details.
2013c167b9c7SMaximilian Luz  *
2014c167b9c7SMaximilian Luz  * Return: Returns zero on success or if no request has been executed, the
2015c167b9c7SMaximilian Luz  * status of the executed SAM request if that request failed, or %-EPROTO if
2016c167b9c7SMaximilian Luz  * an unexpected response has been received.
2017c167b9c7SMaximilian Luz  */
2018c167b9c7SMaximilian Luz int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl)
2019c167b9c7SMaximilian Luz {
2020c167b9c7SMaximilian Luz 	int status;
2021c167b9c7SMaximilian Luz 	u8 response;
2022c167b9c7SMaximilian Luz 
2023c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: notifying display on\n");
2024c167b9c7SMaximilian Luz 
2025c167b9c7SMaximilian Luz 	status = ssam_retry(ssam_ssh_notif_display_on, ctrl, &response);
2026c167b9c7SMaximilian Luz 	if (status)
2027c167b9c7SMaximilian Luz 		return status;
2028c167b9c7SMaximilian Luz 
2029c167b9c7SMaximilian Luz 	if (response != 0) {
2030c167b9c7SMaximilian Luz 		ssam_err(ctrl, "unexpected response from display-on notification: %#04x\n",
2031c167b9c7SMaximilian Luz 			 response);
2032c167b9c7SMaximilian Luz 		return -EPROTO;
2033c167b9c7SMaximilian Luz 	}
2034c167b9c7SMaximilian Luz 
2035c167b9c7SMaximilian Luz 	return 0;
2036c167b9c7SMaximilian Luz }
2037c167b9c7SMaximilian Luz 
2038c167b9c7SMaximilian Luz /**
2039c167b9c7SMaximilian Luz  * ssam_ctrl_notif_d0_exit() - Notify EC that the driver/device exits the D0
2040c167b9c7SMaximilian Luz  * power state.
2041c167b9c7SMaximilian Luz  * @ctrl: The controller
2042c167b9c7SMaximilian Luz  *
2043c167b9c7SMaximilian Luz  * Notifies the EC that the driver prepares to exit the D0 power state in
2044c167b9c7SMaximilian Luz  * favor of a lower-power state. Exact effects of this function related to the
2045c167b9c7SMaximilian Luz  * EC are currently unknown.
2046c167b9c7SMaximilian Luz  *
2047c167b9c7SMaximilian Luz  * This function will only send the D0-exit notification command if D0-state
2048c167b9c7SMaximilian Luz  * notifications are supported by the EC. Only newer Surface generations
2049c167b9c7SMaximilian Luz  * support these notifications.
2050c167b9c7SMaximilian Luz  *
2051c167b9c7SMaximilian Luz  * Use ssam_ctrl_notif_d0_entry() to reverse the effects of this function.
2052c167b9c7SMaximilian Luz  *
2053c167b9c7SMaximilian Luz  * Return: Returns zero on success or if no request has been executed, the
2054c167b9c7SMaximilian Luz  * status of the executed SAM request if that request failed, or %-EPROTO if
2055c167b9c7SMaximilian Luz  * an unexpected response has been received.
2056c167b9c7SMaximilian Luz  */
2057c167b9c7SMaximilian Luz int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl)
2058c167b9c7SMaximilian Luz {
2059c167b9c7SMaximilian Luz 	int status;
2060c167b9c7SMaximilian Luz 	u8 response;
2061c167b9c7SMaximilian Luz 
2062c167b9c7SMaximilian Luz 	if (!ctrl->caps.d3_closes_handle)
2063c167b9c7SMaximilian Luz 		return 0;
2064c167b9c7SMaximilian Luz 
2065c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: notifying D0 exit\n");
2066c167b9c7SMaximilian Luz 
2067c167b9c7SMaximilian Luz 	status = ssam_retry(ssam_ssh_notif_d0_exit, ctrl, &response);
2068c167b9c7SMaximilian Luz 	if (status)
2069c167b9c7SMaximilian Luz 		return status;
2070c167b9c7SMaximilian Luz 
2071c167b9c7SMaximilian Luz 	if (response != 0) {
2072c167b9c7SMaximilian Luz 		ssam_err(ctrl, "unexpected response from D0-exit notification: %#04x\n",
2073c167b9c7SMaximilian Luz 			 response);
2074c167b9c7SMaximilian Luz 		return -EPROTO;
2075c167b9c7SMaximilian Luz 	}
2076c167b9c7SMaximilian Luz 
2077c167b9c7SMaximilian Luz 	return 0;
2078c167b9c7SMaximilian Luz }
2079c167b9c7SMaximilian Luz 
2080c167b9c7SMaximilian Luz /**
2081c167b9c7SMaximilian Luz  * ssam_ctrl_notif_d0_entry() - Notify EC that the driver/device enters the D0
2082c167b9c7SMaximilian Luz  * power state.
2083c167b9c7SMaximilian Luz  * @ctrl: The controller
2084c167b9c7SMaximilian Luz  *
2085c167b9c7SMaximilian Luz  * Notifies the EC that the driver has exited a lower-power state and entered
2086c167b9c7SMaximilian Luz  * the D0 power state. Exact effects of this function related to the EC are
2087c167b9c7SMaximilian Luz  * currently unknown.
2088c167b9c7SMaximilian Luz  *
2089c167b9c7SMaximilian Luz  * This function will only send the D0-entry notification command if D0-state
2090c167b9c7SMaximilian Luz  * notifications are supported by the EC. Only newer Surface generations
2091c167b9c7SMaximilian Luz  * support these notifications.
2092c167b9c7SMaximilian Luz  *
2093c167b9c7SMaximilian Luz  * See ssam_ctrl_notif_d0_exit() for more details.
2094c167b9c7SMaximilian Luz  *
2095c167b9c7SMaximilian Luz  * Return: Returns zero on success or if no request has been executed, the
2096c167b9c7SMaximilian Luz  * status of the executed SAM request if that request failed, or %-EPROTO if
2097c167b9c7SMaximilian Luz  * an unexpected response has been received.
2098c167b9c7SMaximilian Luz  */
2099c167b9c7SMaximilian Luz int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
2100c167b9c7SMaximilian Luz {
2101c167b9c7SMaximilian Luz 	int status;
2102c167b9c7SMaximilian Luz 	u8 response;
2103c167b9c7SMaximilian Luz 
2104c167b9c7SMaximilian Luz 	if (!ctrl->caps.d3_closes_handle)
2105c167b9c7SMaximilian Luz 		return 0;
2106c167b9c7SMaximilian Luz 
2107c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: notifying D0 entry\n");
2108c167b9c7SMaximilian Luz 
2109c167b9c7SMaximilian Luz 	status = ssam_retry(ssam_ssh_notif_d0_entry, ctrl, &response);
2110c167b9c7SMaximilian Luz 	if (status)
2111c167b9c7SMaximilian Luz 		return status;
2112c167b9c7SMaximilian Luz 
2113c167b9c7SMaximilian Luz 	if (response != 0) {
2114c167b9c7SMaximilian Luz 		ssam_err(ctrl, "unexpected response from D0-entry notification: %#04x\n",
2115c167b9c7SMaximilian Luz 			 response);
2116c167b9c7SMaximilian Luz 		return -EPROTO;
2117c167b9c7SMaximilian Luz 	}
2118c167b9c7SMaximilian Luz 
2119c167b9c7SMaximilian Luz 	return 0;
2120c167b9c7SMaximilian Luz }
2121c167b9c7SMaximilian Luz 
2122c167b9c7SMaximilian Luz 
2123c167b9c7SMaximilian Luz /* -- Top-level event registry interface. ----------------------------------- */
2124c167b9c7SMaximilian Luz 
2125c167b9c7SMaximilian Luz /**
2126c167b9c7SMaximilian Luz  * ssam_notifier_register() - Register an event notifier.
2127c167b9c7SMaximilian Luz  * @ctrl: The controller to register the notifier on.
2128c167b9c7SMaximilian Luz  * @n:    The event notifier to register.
2129c167b9c7SMaximilian Luz  *
2130c167b9c7SMaximilian Luz  * Register an event notifier and increment the usage counter of the
2131c167b9c7SMaximilian Luz  * associated SAM event. If the event was previously not enabled, it will be
2132c167b9c7SMaximilian Luz  * enabled during this call.
2133c167b9c7SMaximilian Luz  *
2134c167b9c7SMaximilian Luz  * Return: Returns zero on success, %-ENOSPC if there have already been
2135c167b9c7SMaximilian Luz  * %INT_MAX notifiers for the event ID/type associated with the notifier block
2136c167b9c7SMaximilian Luz  * registered, %-ENOMEM if the corresponding event entry could not be
2137c167b9c7SMaximilian Luz  * allocated. If this is the first time that a notifier block is registered
2138c167b9c7SMaximilian Luz  * for the specific associated event, returns the status of the event-enable
2139c167b9c7SMaximilian Luz  * EC-command.
2140c167b9c7SMaximilian Luz  */
2141c167b9c7SMaximilian Luz int ssam_notifier_register(struct ssam_controller *ctrl,
2142c167b9c7SMaximilian Luz 			   struct ssam_event_notifier *n)
2143c167b9c7SMaximilian Luz {
2144c167b9c7SMaximilian Luz 	u16 rqid = ssh_tc_to_rqid(n->event.id.target_category);
2145c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_entry *entry;
2146c167b9c7SMaximilian Luz 	struct ssam_nf_head *nf_head;
2147c167b9c7SMaximilian Luz 	struct ssam_nf *nf;
2148c167b9c7SMaximilian Luz 	int status;
2149c167b9c7SMaximilian Luz 
2150c167b9c7SMaximilian Luz 	if (!ssh_rqid_is_event(rqid))
2151c167b9c7SMaximilian Luz 		return -EINVAL;
2152c167b9c7SMaximilian Luz 
2153c167b9c7SMaximilian Luz 	nf = &ctrl->cplt.event.notif;
2154c167b9c7SMaximilian Luz 	nf_head = &nf->head[ssh_rqid_to_event(rqid)];
2155c167b9c7SMaximilian Luz 
2156c167b9c7SMaximilian Luz 	mutex_lock(&nf->lock);
2157c167b9c7SMaximilian Luz 
2158c167b9c7SMaximilian Luz 	entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id);
2159c167b9c7SMaximilian Luz 	if (IS_ERR(entry)) {
2160c167b9c7SMaximilian Luz 		mutex_unlock(&nf->lock);
2161c167b9c7SMaximilian Luz 		return PTR_ERR(entry);
2162c167b9c7SMaximilian Luz 	}
2163c167b9c7SMaximilian Luz 
2164c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
2165c167b9c7SMaximilian Luz 		 n->event.reg.target_category, n->event.id.target_category,
2166c167b9c7SMaximilian Luz 		 n->event.id.instance, entry->refcount);
2167c167b9c7SMaximilian Luz 
2168c167b9c7SMaximilian Luz 	status = ssam_nfblk_insert(nf_head, &n->base);
2169c167b9c7SMaximilian Luz 	if (status) {
2170c167b9c7SMaximilian Luz 		entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
2171c167b9c7SMaximilian Luz 		if (entry->refcount == 0)
2172c167b9c7SMaximilian Luz 			kfree(entry);
2173c167b9c7SMaximilian Luz 
2174c167b9c7SMaximilian Luz 		mutex_unlock(&nf->lock);
2175c167b9c7SMaximilian Luz 		return status;
2176c167b9c7SMaximilian Luz 	}
2177c167b9c7SMaximilian Luz 
2178c167b9c7SMaximilian Luz 	if (entry->refcount == 1) {
2179c167b9c7SMaximilian Luz 		status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id,
2180c167b9c7SMaximilian Luz 					       n->event.flags);
2181c167b9c7SMaximilian Luz 		if (status) {
2182c167b9c7SMaximilian Luz 			ssam_nfblk_remove(&n->base);
2183c167b9c7SMaximilian Luz 			kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id));
2184c167b9c7SMaximilian Luz 			mutex_unlock(&nf->lock);
2185c167b9c7SMaximilian Luz 			synchronize_srcu(&nf_head->srcu);
2186c167b9c7SMaximilian Luz 			return status;
2187c167b9c7SMaximilian Luz 		}
2188c167b9c7SMaximilian Luz 
2189c167b9c7SMaximilian Luz 		entry->flags = n->event.flags;
2190c167b9c7SMaximilian Luz 
2191c167b9c7SMaximilian Luz 	} else if (entry->flags != n->event.flags) {
2192c167b9c7SMaximilian Luz 		ssam_warn(ctrl,
2193c167b9c7SMaximilian Luz 			  "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
2194c167b9c7SMaximilian Luz 			  n->event.flags, entry->flags, n->event.reg.target_category,
2195c167b9c7SMaximilian Luz 			  n->event.id.target_category, n->event.id.instance);
2196c167b9c7SMaximilian Luz 	}
2197c167b9c7SMaximilian Luz 
2198c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2199c167b9c7SMaximilian Luz 	return 0;
2200c167b9c7SMaximilian Luz }
2201c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_notifier_register);
2202c167b9c7SMaximilian Luz 
2203c167b9c7SMaximilian Luz /**
2204c167b9c7SMaximilian Luz  * ssam_notifier_unregister() - Unregister an event notifier.
2205c167b9c7SMaximilian Luz  * @ctrl: The controller the notifier has been registered on.
2206c167b9c7SMaximilian Luz  * @n:    The event notifier to unregister.
2207c167b9c7SMaximilian Luz  *
2208c167b9c7SMaximilian Luz  * Unregister an event notifier and decrement the usage counter of the
2209c167b9c7SMaximilian Luz  * associated SAM event. If the usage counter reaches zero, the event will be
2210c167b9c7SMaximilian Luz  * disabled.
2211c167b9c7SMaximilian Luz  *
2212c167b9c7SMaximilian Luz  * Return: Returns zero on success, %-ENOENT if the given notifier block has
2213c167b9c7SMaximilian Luz  * not been registered on the controller. If the given notifier block was the
2214c167b9c7SMaximilian Luz  * last one associated with its specific event, returns the status of the
2215c167b9c7SMaximilian Luz  * event-disable EC-command.
2216c167b9c7SMaximilian Luz  */
2217c167b9c7SMaximilian Luz int ssam_notifier_unregister(struct ssam_controller *ctrl,
2218c167b9c7SMaximilian Luz 			     struct ssam_event_notifier *n)
2219c167b9c7SMaximilian Luz {
2220c167b9c7SMaximilian Luz 	u16 rqid = ssh_tc_to_rqid(n->event.id.target_category);
2221c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_entry *entry;
2222c167b9c7SMaximilian Luz 	struct ssam_nf_head *nf_head;
2223c167b9c7SMaximilian Luz 	struct ssam_nf *nf;
2224c167b9c7SMaximilian Luz 	int status = 0;
2225c167b9c7SMaximilian Luz 
2226c167b9c7SMaximilian Luz 	if (!ssh_rqid_is_event(rqid))
2227c167b9c7SMaximilian Luz 		return -EINVAL;
2228c167b9c7SMaximilian Luz 
2229c167b9c7SMaximilian Luz 	nf = &ctrl->cplt.event.notif;
2230c167b9c7SMaximilian Luz 	nf_head = &nf->head[ssh_rqid_to_event(rqid)];
2231c167b9c7SMaximilian Luz 
2232c167b9c7SMaximilian Luz 	mutex_lock(&nf->lock);
2233c167b9c7SMaximilian Luz 
2234c167b9c7SMaximilian Luz 	if (!ssam_nfblk_find(nf_head, &n->base)) {
2235c167b9c7SMaximilian Luz 		mutex_unlock(&nf->lock);
2236c167b9c7SMaximilian Luz 		return -ENOENT;
2237c167b9c7SMaximilian Luz 	}
2238c167b9c7SMaximilian Luz 
2239c167b9c7SMaximilian Luz 	entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
2240c167b9c7SMaximilian Luz 	if (WARN_ON(!entry)) {
2241c167b9c7SMaximilian Luz 		/*
2242c167b9c7SMaximilian Luz 		 * If this does not return an entry, there's a logic error
2243c167b9c7SMaximilian Luz 		 * somewhere: The notifier block is registered, but the event
2244c167b9c7SMaximilian Luz 		 * refcount entry is not there. Remove the notifier block
2245c167b9c7SMaximilian Luz 		 * anyways.
2246c167b9c7SMaximilian Luz 		 */
2247c167b9c7SMaximilian Luz 		status = -ENOENT;
2248c167b9c7SMaximilian Luz 		goto remove;
2249c167b9c7SMaximilian Luz 	}
2250c167b9c7SMaximilian Luz 
2251c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
2252c167b9c7SMaximilian Luz 		 n->event.reg.target_category, n->event.id.target_category,
2253c167b9c7SMaximilian Luz 		 n->event.id.instance, entry->refcount);
2254c167b9c7SMaximilian Luz 
2255c167b9c7SMaximilian Luz 	if (entry->flags != n->event.flags) {
2256c167b9c7SMaximilian Luz 		ssam_warn(ctrl,
2257c167b9c7SMaximilian Luz 			  "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
2258c167b9c7SMaximilian Luz 			  n->event.flags, entry->flags, n->event.reg.target_category,
2259c167b9c7SMaximilian Luz 			  n->event.id.target_category, n->event.id.instance);
2260c167b9c7SMaximilian Luz 	}
2261c167b9c7SMaximilian Luz 
2262c167b9c7SMaximilian Luz 	if (entry->refcount == 0) {
2263c167b9c7SMaximilian Luz 		status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id,
2264c167b9c7SMaximilian Luz 						n->event.flags);
2265c167b9c7SMaximilian Luz 		kfree(entry);
2266c167b9c7SMaximilian Luz 	}
2267c167b9c7SMaximilian Luz 
2268c167b9c7SMaximilian Luz remove:
2269c167b9c7SMaximilian Luz 	ssam_nfblk_remove(&n->base);
2270c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2271c167b9c7SMaximilian Luz 	synchronize_srcu(&nf_head->srcu);
2272c167b9c7SMaximilian Luz 
2273c167b9c7SMaximilian Luz 	return status;
2274c167b9c7SMaximilian Luz }
2275c167b9c7SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_notifier_unregister);
2276c167b9c7SMaximilian Luz 
2277c167b9c7SMaximilian Luz /**
2278c167b9c7SMaximilian Luz  * ssam_notifier_disable_registered() - Disable events for all registered
2279c167b9c7SMaximilian Luz  * notifiers.
2280c167b9c7SMaximilian Luz  * @ctrl: The controller for which to disable the notifiers/events.
2281c167b9c7SMaximilian Luz  *
2282c167b9c7SMaximilian Luz  * Disables events for all currently registered notifiers. In case of an error
2283c167b9c7SMaximilian Luz  * (EC command failing), all previously disabled events will be restored and
2284c167b9c7SMaximilian Luz  * the error code returned.
2285c167b9c7SMaximilian Luz  *
2286c167b9c7SMaximilian Luz  * This function is intended to disable all events prior to hibernation entry.
2287c167b9c7SMaximilian Luz  * See ssam_notifier_restore_registered() to restore/re-enable all events
2288c167b9c7SMaximilian Luz  * disabled with this function.
2289c167b9c7SMaximilian Luz  *
2290c167b9c7SMaximilian Luz  * Note that this function will not disable events for notifiers registered
2291c167b9c7SMaximilian Luz  * after calling this function. It should thus be made sure that no new
2292c167b9c7SMaximilian Luz  * notifiers are going to be added after this call and before the corresponding
2293c167b9c7SMaximilian Luz  * call to ssam_notifier_restore_registered().
2294c167b9c7SMaximilian Luz  *
2295c167b9c7SMaximilian Luz  * Return: Returns zero on success. In case of failure returns the error code
2296c167b9c7SMaximilian Luz  * returned by the failed EC command to disable an event.
2297c167b9c7SMaximilian Luz  */
2298c167b9c7SMaximilian Luz int ssam_notifier_disable_registered(struct ssam_controller *ctrl)
2299c167b9c7SMaximilian Luz {
2300c167b9c7SMaximilian Luz 	struct ssam_nf *nf = &ctrl->cplt.event.notif;
2301c167b9c7SMaximilian Luz 	struct rb_node *n;
2302c167b9c7SMaximilian Luz 	int status;
2303c167b9c7SMaximilian Luz 
2304c167b9c7SMaximilian Luz 	mutex_lock(&nf->lock);
2305c167b9c7SMaximilian Luz 	for (n = rb_first(&nf->refcount); n; n = rb_next(n)) {
2306c167b9c7SMaximilian Luz 		struct ssam_nf_refcount_entry *e;
2307c167b9c7SMaximilian Luz 
2308c167b9c7SMaximilian Luz 		e = rb_entry(n, struct ssam_nf_refcount_entry, node);
2309c167b9c7SMaximilian Luz 		status = ssam_ssh_event_disable(ctrl, e->key.reg,
2310c167b9c7SMaximilian Luz 						e->key.id, e->flags);
2311c167b9c7SMaximilian Luz 		if (status)
2312c167b9c7SMaximilian Luz 			goto err;
2313c167b9c7SMaximilian Luz 	}
2314c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2315c167b9c7SMaximilian Luz 
2316c167b9c7SMaximilian Luz 	return 0;
2317c167b9c7SMaximilian Luz 
2318c167b9c7SMaximilian Luz err:
2319c167b9c7SMaximilian Luz 	for (n = rb_prev(n); n; n = rb_prev(n)) {
2320c167b9c7SMaximilian Luz 		struct ssam_nf_refcount_entry *e;
2321c167b9c7SMaximilian Luz 
2322c167b9c7SMaximilian Luz 		e = rb_entry(n, struct ssam_nf_refcount_entry, node);
2323c167b9c7SMaximilian Luz 		ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags);
2324c167b9c7SMaximilian Luz 	}
2325c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2326c167b9c7SMaximilian Luz 
2327c167b9c7SMaximilian Luz 	return status;
2328c167b9c7SMaximilian Luz }
2329c167b9c7SMaximilian Luz 
2330c167b9c7SMaximilian Luz /**
2331c167b9c7SMaximilian Luz  * ssam_notifier_restore_registered() - Restore/re-enable events for all
2332c167b9c7SMaximilian Luz  * registered notifiers.
2333c167b9c7SMaximilian Luz  * @ctrl: The controller for which to restore the notifiers/events.
2334c167b9c7SMaximilian Luz  *
2335c167b9c7SMaximilian Luz  * Restores/re-enables all events for which notifiers have been registered on
2336c167b9c7SMaximilian Luz  * the given controller. In case of a failure, the error is logged and the
2337c167b9c7SMaximilian Luz  * function continues to try and enable the remaining events.
2338c167b9c7SMaximilian Luz  *
2339c167b9c7SMaximilian Luz  * This function is intended to restore/re-enable all registered events after
2340c167b9c7SMaximilian Luz  * hibernation. See ssam_notifier_disable_registered() for the counter part
2341c167b9c7SMaximilian Luz  * disabling the events and more details.
2342c167b9c7SMaximilian Luz  */
2343c167b9c7SMaximilian Luz void ssam_notifier_restore_registered(struct ssam_controller *ctrl)
2344c167b9c7SMaximilian Luz {
2345c167b9c7SMaximilian Luz 	struct ssam_nf *nf = &ctrl->cplt.event.notif;
2346c167b9c7SMaximilian Luz 	struct rb_node *n;
2347c167b9c7SMaximilian Luz 
2348c167b9c7SMaximilian Luz 	mutex_lock(&nf->lock);
2349c167b9c7SMaximilian Luz 	for (n = rb_first(&nf->refcount); n; n = rb_next(n)) {
2350c167b9c7SMaximilian Luz 		struct ssam_nf_refcount_entry *e;
2351c167b9c7SMaximilian Luz 
2352c167b9c7SMaximilian Luz 		e = rb_entry(n, struct ssam_nf_refcount_entry, node);
2353c167b9c7SMaximilian Luz 
2354c167b9c7SMaximilian Luz 		/* Ignore errors, will get logged in call. */
2355c167b9c7SMaximilian Luz 		ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags);
2356c167b9c7SMaximilian Luz 	}
2357c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2358c167b9c7SMaximilian Luz }
2359c167b9c7SMaximilian Luz 
2360c167b9c7SMaximilian Luz /**
2361c167b9c7SMaximilian Luz  * ssam_notifier_is_empty() - Check if there are any registered notifiers.
2362c167b9c7SMaximilian Luz  * @ctrl: The controller to check on.
2363c167b9c7SMaximilian Luz  *
2364c167b9c7SMaximilian Luz  * Return: Returns %true if there are currently no notifiers registered on the
2365c167b9c7SMaximilian Luz  * controller, %false otherwise.
2366c167b9c7SMaximilian Luz  */
2367c167b9c7SMaximilian Luz static bool ssam_notifier_is_empty(struct ssam_controller *ctrl)
2368c167b9c7SMaximilian Luz {
2369c167b9c7SMaximilian Luz 	struct ssam_nf *nf = &ctrl->cplt.event.notif;
2370c167b9c7SMaximilian Luz 	bool result;
2371c167b9c7SMaximilian Luz 
2372c167b9c7SMaximilian Luz 	mutex_lock(&nf->lock);
2373c167b9c7SMaximilian Luz 	result = ssam_nf_refcount_empty(nf);
2374c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2375c167b9c7SMaximilian Luz 
2376c167b9c7SMaximilian Luz 	return result;
2377c167b9c7SMaximilian Luz }
2378c167b9c7SMaximilian Luz 
2379c167b9c7SMaximilian Luz /**
2380c167b9c7SMaximilian Luz  * ssam_notifier_unregister_all() - Unregister all currently registered
2381c167b9c7SMaximilian Luz  * notifiers.
2382c167b9c7SMaximilian Luz  * @ctrl: The controller to unregister the notifiers on.
2383c167b9c7SMaximilian Luz  *
2384c167b9c7SMaximilian Luz  * Unregisters all currently registered notifiers. This function is used to
2385c167b9c7SMaximilian Luz  * ensure that all notifiers will be unregistered and associated
2386c167b9c7SMaximilian Luz  * entries/resources freed when the controller is being shut down.
2387c167b9c7SMaximilian Luz  */
2388c167b9c7SMaximilian Luz static void ssam_notifier_unregister_all(struct ssam_controller *ctrl)
2389c167b9c7SMaximilian Luz {
2390c167b9c7SMaximilian Luz 	struct ssam_nf *nf = &ctrl->cplt.event.notif;
2391c167b9c7SMaximilian Luz 	struct ssam_nf_refcount_entry *e, *n;
2392c167b9c7SMaximilian Luz 
2393c167b9c7SMaximilian Luz 	mutex_lock(&nf->lock);
2394c167b9c7SMaximilian Luz 	rbtree_postorder_for_each_entry_safe(e, n, &nf->refcount, node) {
2395c167b9c7SMaximilian Luz 		/* Ignore errors, will get logged in call. */
2396c167b9c7SMaximilian Luz 		ssam_ssh_event_disable(ctrl, e->key.reg, e->key.id, e->flags);
2397c167b9c7SMaximilian Luz 		kfree(e);
2398c167b9c7SMaximilian Luz 	}
2399c167b9c7SMaximilian Luz 	nf->refcount = RB_ROOT;
2400c167b9c7SMaximilian Luz 	mutex_unlock(&nf->lock);
2401c167b9c7SMaximilian Luz }
2402c167b9c7SMaximilian Luz 
2403c167b9c7SMaximilian Luz 
2404c167b9c7SMaximilian Luz /* -- Wakeup IRQ. ----------------------------------------------------------- */
2405c167b9c7SMaximilian Luz 
2406c167b9c7SMaximilian Luz static irqreturn_t ssam_irq_handle(int irq, void *dev_id)
2407c167b9c7SMaximilian Luz {
2408c167b9c7SMaximilian Luz 	struct ssam_controller *ctrl = dev_id;
2409c167b9c7SMaximilian Luz 
2410c167b9c7SMaximilian Luz 	ssam_dbg(ctrl, "pm: wake irq triggered\n");
2411c167b9c7SMaximilian Luz 
2412c167b9c7SMaximilian Luz 	/*
2413c167b9c7SMaximilian Luz 	 * Note: Proper wakeup detection is currently unimplemented.
2414c167b9c7SMaximilian Luz 	 *       When the EC is in display-off or any other non-D0 state, it
2415c167b9c7SMaximilian Luz 	 *       does not send events/notifications to the host. Instead it
2416c167b9c7SMaximilian Luz 	 *       signals that there are events available via the wakeup IRQ.
2417c167b9c7SMaximilian Luz 	 *       This driver is responsible for calling back to the EC to
2418c167b9c7SMaximilian Luz 	 *       release these events one-by-one.
2419c167b9c7SMaximilian Luz 	 *
2420c167b9c7SMaximilian Luz 	 *       This IRQ should not cause a full system resume by its own.
2421c167b9c7SMaximilian Luz 	 *       Instead, events should be handled by their respective subsystem
2422c167b9c7SMaximilian Luz 	 *       drivers, which in turn should signal whether a full system
2423c167b9c7SMaximilian Luz 	 *       resume should be performed.
2424c167b9c7SMaximilian Luz 	 *
2425c167b9c7SMaximilian Luz 	 * TODO: Send GPIO callback command repeatedly to EC until callback
2426c167b9c7SMaximilian Luz 	 *       returns 0x00. Return flag of callback is "has more events".
2427c167b9c7SMaximilian Luz 	 *       Each time the command is sent, one event is "released". Once
2428c167b9c7SMaximilian Luz 	 *       all events have been released (return = 0x00), the GPIO is
2429c167b9c7SMaximilian Luz 	 *       re-armed. Detect wakeup events during this process, go back to
2430c167b9c7SMaximilian Luz 	 *       sleep if no wakeup event has been received.
2431c167b9c7SMaximilian Luz 	 */
2432c167b9c7SMaximilian Luz 
2433c167b9c7SMaximilian Luz 	return IRQ_HANDLED;
2434c167b9c7SMaximilian Luz }
2435c167b9c7SMaximilian Luz 
2436c167b9c7SMaximilian Luz /**
2437c167b9c7SMaximilian Luz  * ssam_irq_setup() - Set up SAM EC wakeup-GPIO interrupt.
2438c167b9c7SMaximilian Luz  * @ctrl: The controller for which the IRQ should be set up.
2439c167b9c7SMaximilian Luz  *
2440c167b9c7SMaximilian Luz  * Set up an IRQ for the wakeup-GPIO pin of the SAM EC. This IRQ can be used
2441c167b9c7SMaximilian Luz  * to wake the device from a low power state.
2442c167b9c7SMaximilian Luz  *
2443c167b9c7SMaximilian Luz  * Note that this IRQ can only be triggered while the EC is in the display-off
2444c167b9c7SMaximilian Luz  * state. In this state, events are not sent to the host in the usual way.
2445c167b9c7SMaximilian Luz  * Instead the wakeup-GPIO gets pulled to "high" as long as there are pending
2446c167b9c7SMaximilian Luz  * events and these events need to be released one-by-one via the GPIO
2447c167b9c7SMaximilian Luz  * callback request, either until there are no events left and the GPIO is
2448c167b9c7SMaximilian Luz  * reset, or all at once by transitioning the EC out of the display-off state,
2449c167b9c7SMaximilian Luz  * which will also clear the GPIO.
2450c167b9c7SMaximilian Luz  *
2451c167b9c7SMaximilian Luz  * Not all events, however, should trigger a full system wakeup. Instead the
2452c167b9c7SMaximilian Luz  * driver should, if necessary, inspect and forward each event to the
2453c167b9c7SMaximilian Luz  * corresponding subsystem, which in turn should decide if the system needs to
2454c167b9c7SMaximilian Luz  * be woken up. This logic has not been implemented yet, thus wakeup by this
2455c167b9c7SMaximilian Luz  * IRQ should be disabled by default to avoid spurious wake-ups, caused, for
2456c167b9c7SMaximilian Luz  * example, by the remaining battery percentage changing. Refer to comments in
2457c167b9c7SMaximilian Luz  * this function and comments in the corresponding IRQ handler for more
2458c167b9c7SMaximilian Luz  * details on how this should be implemented.
2459c167b9c7SMaximilian Luz  *
2460c167b9c7SMaximilian Luz  * See also ssam_ctrl_notif_display_off() and ssam_ctrl_notif_display_off()
2461c167b9c7SMaximilian Luz  * for functions to transition the EC into and out of the display-off state as
2462c167b9c7SMaximilian Luz  * well as more details on it.
2463c167b9c7SMaximilian Luz  *
2464c167b9c7SMaximilian Luz  * The IRQ is disabled by default and has to be enabled before it can wake up
2465c167b9c7SMaximilian Luz  * the device from suspend via ssam_irq_arm_for_wakeup(). On teardown, the IRQ
2466c167b9c7SMaximilian Luz  * should be freed via ssam_irq_free().
2467c167b9c7SMaximilian Luz  */
2468c167b9c7SMaximilian Luz int ssam_irq_setup(struct ssam_controller *ctrl)
2469c167b9c7SMaximilian Luz {
2470c167b9c7SMaximilian Luz 	struct device *dev = ssam_controller_device(ctrl);
2471c167b9c7SMaximilian Luz 	struct gpio_desc *gpiod;
2472c167b9c7SMaximilian Luz 	int irq;
2473c167b9c7SMaximilian Luz 	int status;
2474c167b9c7SMaximilian Luz 
2475c167b9c7SMaximilian Luz 	/*
2476c167b9c7SMaximilian Luz 	 * The actual GPIO interrupt is declared in ACPI as TRIGGER_HIGH.
2477c167b9c7SMaximilian Luz 	 * However, the GPIO line only gets reset by sending the GPIO callback
2478c167b9c7SMaximilian Luz 	 * command to SAM (or alternatively the display-on notification). As
2479c167b9c7SMaximilian Luz 	 * proper handling for this interrupt is not implemented yet, leaving
2480c167b9c7SMaximilian Luz 	 * the IRQ at TRIGGER_HIGH would cause an IRQ storm (as the callback
2481c167b9c7SMaximilian Luz 	 * never gets sent and thus the line never gets reset). To avoid this,
2482c167b9c7SMaximilian Luz 	 * mark the IRQ as TRIGGER_RISING for now, only creating a single
2483c167b9c7SMaximilian Luz 	 * interrupt, and let the SAM resume callback during the controller
2484c167b9c7SMaximilian Luz 	 * resume process clear it.
2485c167b9c7SMaximilian Luz 	 */
2486*647e6cc9SMaximilian Luz 	const int irqf = IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
2487c167b9c7SMaximilian Luz 
2488c167b9c7SMaximilian Luz 	gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);
2489c167b9c7SMaximilian Luz 	if (IS_ERR(gpiod))
2490c167b9c7SMaximilian Luz 		return PTR_ERR(gpiod);
2491c167b9c7SMaximilian Luz 
2492c167b9c7SMaximilian Luz 	irq = gpiod_to_irq(gpiod);
2493c167b9c7SMaximilian Luz 	gpiod_put(gpiod);
2494c167b9c7SMaximilian Luz 
2495c167b9c7SMaximilian Luz 	if (irq < 0)
2496c167b9c7SMaximilian Luz 		return irq;
2497c167b9c7SMaximilian Luz 
2498c167b9c7SMaximilian Luz 	status = request_threaded_irq(irq, NULL, ssam_irq_handle, irqf,
2499c167b9c7SMaximilian Luz 				      "ssam_wakeup", ctrl);
2500c167b9c7SMaximilian Luz 	if (status)
2501c167b9c7SMaximilian Luz 		return status;
2502c167b9c7SMaximilian Luz 
2503c167b9c7SMaximilian Luz 	ctrl->irq.num = irq;
2504c167b9c7SMaximilian Luz 	return 0;
2505c167b9c7SMaximilian Luz }
2506c167b9c7SMaximilian Luz 
2507c167b9c7SMaximilian Luz /**
2508c167b9c7SMaximilian Luz  * ssam_irq_free() - Free SAM EC wakeup-GPIO interrupt.
2509c167b9c7SMaximilian Luz  * @ctrl: The controller for which the IRQ should be freed.
2510c167b9c7SMaximilian Luz  *
2511c167b9c7SMaximilian Luz  * Free the wakeup-GPIO IRQ previously set-up via ssam_irq_setup().
2512c167b9c7SMaximilian Luz  */
2513c167b9c7SMaximilian Luz void ssam_irq_free(struct ssam_controller *ctrl)
2514c167b9c7SMaximilian Luz {
2515c167b9c7SMaximilian Luz 	free_irq(ctrl->irq.num, ctrl);
2516c167b9c7SMaximilian Luz 	ctrl->irq.num = -1;
2517c167b9c7SMaximilian Luz }
2518c167b9c7SMaximilian Luz 
2519c167b9c7SMaximilian Luz /**
2520c167b9c7SMaximilian Luz  * ssam_irq_arm_for_wakeup() - Arm the EC IRQ for wakeup, if enabled.
2521c167b9c7SMaximilian Luz  * @ctrl: The controller for which the IRQ should be armed.
2522c167b9c7SMaximilian Luz  *
2523c167b9c7SMaximilian Luz  * Sets up the IRQ so that it can be used to wake the device. Specifically,
2524c167b9c7SMaximilian Luz  * this function enables the irq and then, if the device is allowed to wake up
2525c167b9c7SMaximilian Luz  * the system, calls enable_irq_wake(). See ssam_irq_disarm_wakeup() for the
2526c167b9c7SMaximilian Luz  * corresponding function to disable the IRQ.
2527c167b9c7SMaximilian Luz  *
2528c167b9c7SMaximilian Luz  * This function is intended to arm the IRQ before entering S2idle suspend.
2529c167b9c7SMaximilian Luz  *
2530c167b9c7SMaximilian Luz  * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must
2531c167b9c7SMaximilian Luz  * be balanced.
2532c167b9c7SMaximilian Luz  */
2533c167b9c7SMaximilian Luz int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl)
2534c167b9c7SMaximilian Luz {
2535c167b9c7SMaximilian Luz 	struct device *dev = ssam_controller_device(ctrl);
2536c167b9c7SMaximilian Luz 	int status;
2537c167b9c7SMaximilian Luz 
2538c167b9c7SMaximilian Luz 	enable_irq(ctrl->irq.num);
2539c167b9c7SMaximilian Luz 	if (device_may_wakeup(dev)) {
2540c167b9c7SMaximilian Luz 		status = enable_irq_wake(ctrl->irq.num);
2541c167b9c7SMaximilian Luz 		if (status) {
2542c167b9c7SMaximilian Luz 			ssam_err(ctrl, "failed to enable wake IRQ: %d\n", status);
2543c167b9c7SMaximilian Luz 			disable_irq(ctrl->irq.num);
2544c167b9c7SMaximilian Luz 			return status;
2545c167b9c7SMaximilian Luz 		}
2546c167b9c7SMaximilian Luz 
2547c167b9c7SMaximilian Luz 		ctrl->irq.wakeup_enabled = true;
2548c167b9c7SMaximilian Luz 	} else {
2549c167b9c7SMaximilian Luz 		ctrl->irq.wakeup_enabled = false;
2550c167b9c7SMaximilian Luz 	}
2551c167b9c7SMaximilian Luz 
2552c167b9c7SMaximilian Luz 	return 0;
2553c167b9c7SMaximilian Luz }
2554c167b9c7SMaximilian Luz 
2555c167b9c7SMaximilian Luz /**
2556c167b9c7SMaximilian Luz  * ssam_irq_disarm_wakeup() - Disarm the wakeup IRQ.
2557c167b9c7SMaximilian Luz  * @ctrl: The controller for which the IRQ should be disarmed.
2558c167b9c7SMaximilian Luz  *
2559c167b9c7SMaximilian Luz  * Disarm the IRQ previously set up for wake via ssam_irq_arm_for_wakeup().
2560c167b9c7SMaximilian Luz  *
2561c167b9c7SMaximilian Luz  * This function is intended to disarm the IRQ after exiting S2idle suspend.
2562c167b9c7SMaximilian Luz  *
2563c167b9c7SMaximilian Luz  * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must
2564c167b9c7SMaximilian Luz  * be balanced.
2565c167b9c7SMaximilian Luz  */
2566c167b9c7SMaximilian Luz void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl)
2567c167b9c7SMaximilian Luz {
2568c167b9c7SMaximilian Luz 	int status;
2569c167b9c7SMaximilian Luz 
2570c167b9c7SMaximilian Luz 	if (ctrl->irq.wakeup_enabled) {
2571c167b9c7SMaximilian Luz 		status = disable_irq_wake(ctrl->irq.num);
2572c167b9c7SMaximilian Luz 		if (status)
2573c167b9c7SMaximilian Luz 			ssam_err(ctrl, "failed to disable wake IRQ: %d\n", status);
2574c167b9c7SMaximilian Luz 
2575c167b9c7SMaximilian Luz 		ctrl->irq.wakeup_enabled = false;
2576c167b9c7SMaximilian Luz 	}
2577c167b9c7SMaximilian Luz 	disable_irq(ctrl->irq.num);
2578c167b9c7SMaximilian Luz }
2579