1/*	$OpenBSD: acpivout.c,v 1.26 2024/04/13 23:44:11 jsg Exp $	*/
2/*
3 * Copyright (c) 2009 Paul Irofti <paul@irofti.net>
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/param.h>
19#include <sys/systm.h>
20#include <sys/device.h>
21#include <sys/malloc.h>
22
23#include <machine/bus.h>
24
25#include <dev/acpi/acpivar.h>
26#include <dev/acpi/acpidev.h>
27#include <dev/acpi/amltypes.h>
28#include <dev/acpi/dsdt.h>
29
30#include <dev/wscons/wsconsio.h>
31#include <dev/wscons/wsdisplayvar.h>
32
33int	acpivout_match(struct device *, void *, void *);
34void	acpivout_attach(struct device *, struct device *, void *);
35int	acpivout_notify(struct aml_node *, int, void *);
36
37#ifdef ACPIVIDEO_DEBUG
38#define DPRINTF(x)	printf x
39#else
40#define DPRINTF(x)
41#endif
42
43/* Notifications for Output Devices */
44#define NOTIFY_BRIGHTNESS_CYCLE		0x85
45#define NOTIFY_BRIGHTNESS_UP		0x86
46#define NOTIFY_BRIGHTNESS_DOWN		0x87
47#define NOTIFY_BRIGHTNESS_ZERO		0x88
48#define NOTIFY_DISPLAY_OFF		0x89
49
50#define BRIGHTNESS_STEP			5
51
52struct acpivout_softc {
53	struct device		sc_dev;
54
55	bus_space_tag_t		sc_iot;
56	bus_space_handle_t	sc_ioh;
57
58	struct acpi_softc	*sc_acpi;
59	struct aml_node		*sc_devnode;
60
61	int	*sc_bcl;
62	size_t	sc_bcl_len;
63
64	int	sc_brightness;
65};
66
67int	acpivout_get_brightness(struct acpivout_softc *);
68int	acpivout_select_brightness(struct acpivout_softc *, int);
69int	acpivout_find_brightness(struct acpivout_softc *, int);
70void	acpivout_set_brightness(void *, int);
71void	acpivout_get_bcl(struct acpivout_softc *);
72
73/* wconsole hook functions */
74int	acpivout_get_param(struct wsdisplay_param *);
75int	acpivout_set_param(struct wsdisplay_param *);
76
77const struct cfattach acpivout_ca = {
78	sizeof(struct acpivout_softc), acpivout_match, acpivout_attach
79};
80
81struct cfdriver acpivout_cd = {
82	NULL, "acpivout", DV_DULL
83};
84
85int
86acpivout_match(struct device *parent, void *match, void *aux)
87{
88	struct acpi_attach_args	*aaa = aux;
89	struct cfdata		*cf = match;
90
91	if (aaa->aaa_name == NULL ||
92	    strcmp(aaa->aaa_name, cf->cf_driver->cd_name) != 0 ||
93	    aaa->aaa_table != NULL)
94		return (0);
95
96	return (1);
97}
98
99void
100acpivout_attach(struct device *parent, struct device *self, void *aux)
101{
102	struct acpivout_softc	*sc = (struct acpivout_softc *)self;
103	struct acpi_attach_args	*aaa = aux;
104
105	sc->sc_acpi = ((struct acpivideo_softc *)parent)->sc_acpi;
106	sc->sc_devnode = aaa->aaa_node;
107
108	printf(": %s\n", sc->sc_devnode->name);
109
110	aml_register_notify(sc->sc_devnode, aaa->aaa_dev,
111	    acpivout_notify, sc, ACPIDEV_NOPOLL);
112
113	if (!aml_searchname(sc->sc_devnode, "_BQC") ||
114	    ws_get_param || ws_set_param ||
115	    acpi_max_osi >= OSI_WIN_8)
116		return;
117
118	acpivout_get_bcl(sc);
119	if (sc->sc_bcl_len == 0)
120		return;
121
122	sc->sc_brightness = acpivout_get_brightness(sc);
123
124	ws_get_param = acpivout_get_param;
125	ws_set_param = acpivout_set_param;
126}
127
128int
129acpivout_notify(struct aml_node *node, int notify, void *arg)
130{
131	struct acpivout_softc *sc = arg;
132
133	switch (notify) {
134	case NOTIFY_BRIGHTNESS_CYCLE:
135		wsdisplay_brightness_cycle(NULL);
136		break;
137	case NOTIFY_BRIGHTNESS_UP:
138		wsdisplay_brightness_step(NULL, 1);
139		break;
140	case NOTIFY_BRIGHTNESS_DOWN:
141		wsdisplay_brightness_step(NULL, -1);
142		break;
143	case NOTIFY_BRIGHTNESS_ZERO:
144		wsdisplay_brightness_zero(NULL);
145		break;
146	case NOTIFY_DISPLAY_OFF:
147		/* TODO: D3 state change */
148		break;
149	default:
150		printf("%s: unknown event 0x%02x\n", DEVNAME(sc), notify);
151		break;
152	}
153
154	return (0);
155}
156
157
158int
159acpivout_get_brightness(struct acpivout_softc *sc)
160{
161	struct aml_value res;
162	int level;
163
164	aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BQC", 0, NULL, &res);
165	level = aml_val2int(&res);
166	aml_freevalue(&res);
167	DPRINTF(("%s: BQC = %d\n", DEVNAME(sc), level));
168
169	if (level < sc->sc_bcl[0])
170		level = sc->sc_bcl[0];
171	else if (level > sc->sc_bcl[sc->sc_bcl_len - 1])
172		level = sc->sc_bcl[sc->sc_bcl_len - 1];
173
174	return (level);
175}
176
177int
178acpivout_select_brightness(struct acpivout_softc *sc, int nlevel)
179{
180	int nindex, level;
181
182	level = sc->sc_brightness;
183	nindex = acpivout_find_brightness(sc, nlevel);
184	if (sc->sc_bcl[nindex] == level) {
185		if (nlevel > level && (nindex + 1 < sc->sc_bcl_len))
186			nindex++;
187		else if (nlevel < level && (nindex - 1 >= 0))
188			nindex--;
189	}
190
191	return nindex;
192}
193
194int
195acpivout_find_brightness(struct acpivout_softc *sc, int level)
196{
197	int i, mid;
198
199	for (i = 0; i < sc->sc_bcl_len - 1; i++) {
200		mid = sc->sc_bcl[i] + (sc->sc_bcl[i + 1] - sc->sc_bcl[i]) / 2;
201		if (sc->sc_bcl[i] <= level && level <=  mid)
202			return i;
203		if  (mid < level && level <= sc->sc_bcl[i + 1])
204			return i + 1;
205	}
206	if (level < sc->sc_bcl[0])
207		return 0;
208	else
209		return i;
210}
211
212void
213acpivout_set_brightness(void *arg0, int arg1)
214{
215	struct acpivout_softc *sc = arg0;
216	struct aml_value args, res;
217
218	memset(&args, 0, sizeof(args));
219	args.v_integer = sc->sc_brightness;
220	args.type = AML_OBJTYPE_INTEGER;
221
222	DPRINTF(("%s: BCM = %d\n", DEVNAME(sc), sc->sc_brightness));
223	aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BCM", 1, &args, &res);
224
225	aml_freevalue(&res);
226}
227
228void
229acpivout_get_bcl(struct acpivout_softc *sc)
230{
231	int	i, j, value;
232	struct aml_value res;
233
234	DPRINTF(("Getting _BCL!"));
235	aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BCL", 0, NULL, &res);
236	if (res.type != AML_OBJTYPE_PACKAGE) {
237		sc->sc_bcl_len = 0;
238		goto err;
239	}
240	/*
241	 * Per the ACPI spec section B.6.2 the _BCL method returns a package.
242	 * The first integer in the package is the brightness level
243	 * when the computer has full power, and the second is the
244	 * brightness level when the computer is on batteries.
245	 * All other levels may be used by OSPM.
246	 * So we skip the first two integers in the package.
247	 */
248	if (res.length <= 2) {
249		sc->sc_bcl_len = 0;
250		goto err;
251	}
252	sc->sc_bcl_len = res.length - 2;
253
254	sc->sc_bcl = mallocarray(sc->sc_bcl_len, sizeof(int), M_DEVBUF,
255	    M_WAITOK | M_ZERO);
256
257	for (i = 0; i < sc->sc_bcl_len; i++) {
258		/* Sort darkest to brightest */
259		value = aml_val2int(res.v_package[i + 2]);
260		for (j = i; j > 0 && sc->sc_bcl[j - 1] > value; j--)
261			sc->sc_bcl[j] = sc->sc_bcl[j - 1];
262		sc->sc_bcl[j] = value;
263	}
264
265err:
266	aml_freevalue(&res);
267}
268
269
270int
271acpivout_get_param(struct wsdisplay_param *dp)
272{
273	struct acpivout_softc	*sc = NULL;
274	int i;
275
276	switch (dp->param) {
277	case WSDISPLAYIO_PARAM_BRIGHTNESS:
278		for (i = 0; i < acpivout_cd.cd_ndevs; i++) {
279			if (acpivout_cd.cd_devs[i] == NULL)
280				continue;
281			sc = (struct acpivout_softc *)acpivout_cd.cd_devs[i];
282			/* Ignore device if not connected. */
283			if (sc->sc_bcl_len != 0)
284				break;
285		}
286		if (sc != NULL && sc->sc_bcl_len != 0) {
287			dp->min = 0;
288			dp->max = sc->sc_bcl[sc->sc_bcl_len - 1];
289			dp->curval = sc->sc_brightness;
290			return 0;
291		}
292		return -1;
293	default:
294		return -1;
295	}
296}
297
298int
299acpivout_set_param(struct wsdisplay_param *dp)
300{
301	struct acpivout_softc	*sc = NULL;
302	int i, nindex;
303
304	switch (dp->param) {
305	case WSDISPLAYIO_PARAM_BRIGHTNESS:
306		for (i = 0; i < acpivout_cd.cd_ndevs; i++) {
307			if (acpivout_cd.cd_devs[i] == NULL)
308				continue;
309			sc = (struct acpivout_softc *)acpivout_cd.cd_devs[i];
310			/* Ignore device if not connected. */
311			if (sc->sc_bcl_len != 0)
312				break;
313		}
314		if (sc != NULL && sc->sc_bcl_len != 0) {
315			nindex = acpivout_select_brightness(sc, dp->curval);
316			sc->sc_brightness = sc->sc_bcl[nindex];
317			acpi_addtask(sc->sc_acpi,
318			    acpivout_set_brightness, sc, 0);
319			acpi_wakeup(sc->sc_acpi);
320			return 0;
321		}
322		return -1;
323	default:
324		return -1;
325	}
326}
327