xref: /openbsd-src/sys/dev/acpi/acpipwrres.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /* $OpenBSD: acpipwrres.c,v 1.6 2013/12/17 15:18:28 deraadt Exp $ */
2 
3 /*
4  * Copyright (c) 2013 Martin Pieuchot <mpi@openbsd.org>
5  * Copyright (c) 2009 Paul Irofti <pirofti@openbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/param.h>
21 #include <sys/proc.h>
22 #include <sys/signalvar.h>
23 #include <sys/systm.h>
24 #include <sys/device.h>
25 #include <sys/malloc.h>
26 
27 #include <machine/bus.h>
28 
29 #include <dev/acpi/acpireg.h>
30 #include <dev/acpi/acpivar.h>
31 #include <dev/acpi/acpidev.h>
32 #include <dev/acpi/amltypes.h>
33 #include <dev/acpi/dsdt.h>
34 
35 int	acpipwrres_match(struct device *, void *, void *);
36 void	acpipwrres_attach(struct device *, struct device *, void *);
37 
38 #ifdef ACPIPWRRES_DEBUG
39 #define DPRINTF(x)	printf x
40 #else
41 #define DPRINTF(x)
42 #endif
43 
44 struct acpipwrres_softc {
45 	struct device		sc_dev;
46 
47 	bus_space_tag_t		sc_iot;
48 	bus_space_handle_t	sc_ioh;
49 
50 	struct acpi_softc	*sc_acpi;
51 	struct aml_node		*sc_devnode;
52 
53 	SIMPLEQ_HEAD(, acpipwrres_consumer)	sc_cons;
54 	int					sc_cons_ref;
55 
56 	int	sc_level;
57 	int	sc_order;
58 	int	sc_state;
59 #define ACPIPWRRES_OFF		0
60 #define ACPIPWRRES_ON		1
61 #define ACPIPWRRES_UNK		-1
62 };
63 
64 struct acpipwrres_consumer {
65 	struct aml_node				*cs_node;
66 	SIMPLEQ_ENTRY(acpipwrres_consumer)	cs_next;
67 };
68 
69 struct cfattach acpipwrres_ca = {
70 	sizeof(struct acpipwrres_softc), acpipwrres_match, acpipwrres_attach
71 };
72 
73 struct cfdriver acpipwrres_cd = {
74 	NULL, "acpipwrres", DV_DULL
75 };
76 
77 int	acpipwrres_hascons(struct acpipwrres_softc *, struct aml_node *);
78 int	acpipwrres_addcons(struct acpipwrres_softc *, struct aml_node *);
79 int	acpipwrres_foundcons(struct aml_node *, void *);
80 
81 int
82 acpipwrres_match(struct device *parent, void *match, void *aux)
83 {
84 	struct acpi_attach_args *aaa = aux;
85 	struct cfdata		*cf = match;
86 
87 	if (aaa->aaa_name == NULL || strcmp(aaa->aaa_name,
88 	    cf->cf_driver->cd_name) != 0 || aaa->aaa_table != NULL)
89 		return (0);
90 
91 	return (1);
92 }
93 
94 void
95 acpipwrres_attach(struct device *parent, struct device *self, void *aux)
96 {
97 	struct acpipwrres_softc		*sc = (struct acpipwrres_softc *)self;
98 	struct acpi_attach_args		*aaa = aux;
99 	struct aml_value		res;
100 	struct acpipwrres_consumer	*cons;
101 
102 	extern struct aml_node	aml_root;
103 
104 	sc->sc_acpi = (struct acpi_softc *)parent;
105 	sc->sc_devnode = aaa->aaa_node;
106 	memset(&res, 0, sizeof res);
107 
108 	printf(": %s", sc->sc_devnode->name);
109 
110 	if (!aml_evalname(sc->sc_acpi, sc->sc_devnode, "_STA", 0, NULL, &res)) {
111 		sc->sc_state = (int)aml_val2int(&res);
112 		if (sc->sc_state != ACPIPWRRES_ON &&
113 		    sc->sc_state != ACPIPWRRES_OFF)
114 			sc->sc_state = ACPIPWRRES_UNK;
115 	} else
116 		sc->sc_state = ACPIPWRRES_UNK;
117 	DPRINTF(("\n%s: state = %d\n", DEVNAME(sc), sc->sc_state));
118 	if (aml_evalnode(sc->sc_acpi, aaa->aaa_node, 0, NULL, &res) == 0) {
119 		sc->sc_level = res.v_powerrsrc.pwr_level;
120 		sc->sc_order = res.v_powerrsrc.pwr_order;
121 		DPRINTF(("%s: level = %d, order %d\n", DEVNAME(sc),
122 		    sc->sc_level, sc->sc_order));
123 		aml_freevalue(&res);
124 	}
125 
126 	/* Get the list of consumers */
127 	SIMPLEQ_INIT(&sc->sc_cons);
128 #if notyet
129 	aml_find_node(&aml_root, "_PRW", acpipwrres_foundcons, sc);
130 #endif
131 	aml_find_node(&aml_root, "_PR0", acpipwrres_foundcons, sc);
132 	aml_find_node(&aml_root, "_PR1", acpipwrres_foundcons, sc);
133 	aml_find_node(&aml_root, "_PR2", acpipwrres_foundcons, sc);
134 	aml_find_node(&aml_root, "_PR3", acpipwrres_foundcons, sc);
135 
136 	DPRINTF(("%s", DEVNAME(sc)));
137 	if (!SIMPLEQ_EMPTY(&sc->sc_cons)) {
138 		printf(", resource for");
139 		SIMPLEQ_FOREACH(cons, &sc->sc_cons, cs_next)
140 			printf(" %s%s", cons->cs_node->name,
141 			   (SIMPLEQ_NEXT(cons, cs_next) == NULL) ? "" : ",");
142 	}
143 	printf("\n");
144 }
145 
146 int
147 acpipwrres_ref_incr(struct acpipwrres_softc *sc, struct aml_node *node)
148 {
149 	struct aml_value		res;
150 
151 	if (!acpipwrres_hascons(sc, node))
152 		return (1);
153 
154 	DPRINTF(("%s: dev %s ON %d\n", DEVNAME(sc), node->name,
155 	    sc->sc_cons_ref));
156 
157 	if (sc->sc_cons_ref++ == 0) {
158 		memset(&res, 0, sizeof(res));
159 		aml_evalname(sc->sc_acpi, sc->sc_devnode, "_ON", 0,
160 		    NULL, &res);
161 		aml_freevalue(&res);
162 	}
163 
164 	return (0);
165 }
166 
167 int
168 acpipwrres_ref_decr(struct acpipwrres_softc *sc, struct aml_node *node)
169 {
170 	struct aml_value		res;
171 
172 	if (!acpipwrres_hascons(sc, node))
173 		return (1);
174 
175 	DPRINTF(("%s: dev %s OFF %d\n", DEVNAME(sc), node->name,
176 	    sc->sc_cons_ref));
177 
178 	if (--sc->sc_cons_ref == 0) {
179 		memset(&res, 0, sizeof(res));
180 		aml_evalname(sc->sc_acpi, sc->sc_devnode, "_OFF", 0,
181 		    NULL, &res);
182 		aml_freevalue(&res);
183 	}
184 
185 	return (0);
186 }
187 
188 int
189 acpipwrres_hascons(struct acpipwrres_softc *sc, struct aml_node *node)
190 {
191 	struct acpipwrres_consumer	*cons;
192 
193 	SIMPLEQ_FOREACH(cons, &sc->sc_cons, cs_next) {
194 		if (cons->cs_node == node)
195 			return (1);
196 	}
197 
198 	return (0);
199 }
200 
201 int
202 acpipwrres_addcons(struct acpipwrres_softc *sc, struct aml_node *node)
203 {
204 	struct acpipwrres_consumer	*cons;
205 	struct acpi_pwrres		*pr;
206 	int				state;
207 
208 	/*
209 	 * Add handlers to put the device into Dx states.
210 	 *
211 	 * XXX What about PRW?
212 	 */
213 	if (strcmp(node->name, "_PR0") == 0) {
214 		state = ACPI_STATE_D0;
215 	} else if (strcmp(node->name, "_PR1") == 0) {
216 		state = ACPI_STATE_D1;
217 	} else if (strcmp(node->name, "_PR2") == 0) {
218 		state = ACPI_STATE_D2;
219 	} else if (strcmp(node->name, "_PR3") == 0) {
220 		state = ACPI_STATE_D3;
221 	} else {
222 		return (0);
223 	}
224 
225 	if (!acpipwrres_hascons(sc, node->parent)) {
226 		cons = malloc(sizeof(*cons), M_DEVBUF, M_NOWAIT | M_ZERO);
227 		if (cons == NULL)
228 			return (ENOMEM);
229 
230 		cons->cs_node = node->parent;
231 		SIMPLEQ_INSERT_TAIL(&sc->sc_cons, cons, cs_next);
232 	}
233 
234 	DPRINTF(("%s: resource for %s (D%d) \n", DEVNAME(sc),
235 	    node->parent->name, state));
236 
237 	/*
238 	 * Make sure we attach only once the same Power Resource for a
239 	 * given state.
240 	 */
241 	SIMPLEQ_FOREACH(pr, &sc->sc_acpi->sc_pwrresdevs, p_next) {
242 		if (pr->p_node == node->parent &&
243 		    pr->p_res_state == state &&
244 		    pr->p_res_sc == sc) {
245 			DPRINTF(("error: pr for %s already set\n",
246 			   aml_nodename(pr->p_node)));
247 			return (EINVAL);
248 		}
249 	}
250 
251 	pr = malloc(sizeof(struct acpi_pwrres), M_DEVBUF, M_NOWAIT | M_ZERO);
252 	if (pr == NULL)
253 		return (ENOMEM);
254 
255 	pr->p_node = node->parent;
256 	pr->p_state = -1;
257 	pr->p_res_state = state;
258 	pr->p_res_sc = sc;
259 
260 	SIMPLEQ_INSERT_TAIL(&sc->sc_acpi->sc_pwrresdevs, pr, p_next);
261 
262 	return (0);
263 }
264 
265 int
266 acpipwrres_foundcons(struct aml_node *node, void *arg)
267 {
268 	struct acpipwrres_softc		*sc = (struct acpipwrres_softc *)arg;
269 	struct aml_value		res, *ref;
270 	int				i = 0;
271 
272 	extern struct aml_node	aml_root;
273 
274 	memset(&res, 0, sizeof(res));
275 
276 	if (aml_evalnode(sc->sc_acpi, node, 0, NULL, &res) == -1) {
277 		DPRINTF(("pwr: consumer not found\n"));
278 		return (1);
279 	}
280 
281 	if (res.type != AML_OBJTYPE_PACKAGE) {
282 		DPRINTF(("%s: %s is not a package\n", DEVNAME(sc),
283 		    aml_nodename(node)));
284 		aml_freevalue(&res);
285 		return (1);
286 	}
287 
288 	DPRINTF(("%s: node name %s\n", DEVNAME(sc), aml_nodename(node)));
289 	if (!strcmp(node->name, "_PRW"))
290 		i = 2;          /* _PRW first two values are ints */
291 
292 	for (; i < res.length; i++) {
293 		ref = res.v_package[i];
294 
295 		if (ref->type == AML_OBJTYPE_STRING) {
296 			struct aml_node	*pnode;
297 
298 			pnode = aml_searchrel(&aml_root, ref->v_string);
299 			if (pnode == NULL) {
300 				DPRINTF(("%s: device %s not found\n",
301 				    DEVNAME(sc), ref->v_string));
302 				continue;
303 			}
304 			ref = pnode->value;
305 		}
306 
307 		if (ref->type == AML_OBJTYPE_OBJREF)
308 			ref = ref->v_objref.ref;
309 
310 		if (ref->type != AML_OBJTYPE_POWERRSRC) {
311 			DPRINTF(("%s: object reference has a wrong type (%d)\n",
312 			    DEVNAME(sc), ref->type));
313 			continue;
314 		}
315 
316 		if (ref->node == sc->sc_devnode)
317 			(void)acpipwrres_addcons(sc, node);
318 	}
319 	aml_freevalue(&res);
320 
321 	return (0);
322 }
323