1 /* $OpenBSD: pwmleds.c,v 1.2 2023/04/25 11:12:38 tobhe Exp $ */
2 /*
3 * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
4 *
5 * Permission to use, copy, modify, and 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 #include <machine/fdt.h>
25
26 #include <dev/ofw/openfirm.h>
27 #include <dev/ofw/ofw_misc.h>
28 #include <dev/ofw/fdt.h>
29
30 #include <dev/wscons/wsconsio.h>
31
32 extern int (*wskbd_get_backlight)(struct wskbd_backlight *);
33 extern int (*wskbd_set_backlight)(struct wskbd_backlight *);
34
35 struct pwmleds_softc {
36 struct device sc_dev;
37
38 /* Keyboard backlight. */
39 uint32_t *sc_pwm;
40 int sc_pwm_len;
41 uint32_t sc_max_brightness;
42 struct pwm_state sc_ps_saved;
43 };
44
45 int pwmleds_match(struct device *, void *, void *);
46 void pwmleds_attach(struct device *, struct device *, void *);
47 int pwmleds_activate(struct device *, int);
48
49 const struct cfattach pwmleds_ca = {
50 sizeof (struct pwmleds_softc), pwmleds_match, pwmleds_attach, NULL,
51 pwmleds_activate
52 };
53
54 struct cfdriver pwmleds_cd = {
55 NULL, "pwmleds", DV_DULL
56 };
57
58 int pwmleds_get_kbd_backlight(struct wskbd_backlight *);
59 int pwmleds_set_kbd_backlight(struct wskbd_backlight *);
60
61 int
pwmleds_match(struct device * parent,void * match,void * aux)62 pwmleds_match(struct device *parent, void *match, void *aux)
63 {
64 const struct fdt_attach_args *faa = aux;
65
66 return OF_is_compatible(faa->fa_node, "pwm-leds");
67 }
68
69 void
pwmleds_attach(struct device * parent,struct device * self,void * aux)70 pwmleds_attach(struct device *parent, struct device *self, void *aux)
71 {
72 struct pwmleds_softc *sc = (struct pwmleds_softc *)self;
73 struct fdt_attach_args *faa = aux;
74 char *function;
75 int len, node;
76
77 printf("\n");
78
79 for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) {
80 len = OF_getproplen(node, "function");
81 if (len <= 0)
82 continue;
83
84 function = malloc(len, M_TEMP, M_WAITOK);
85 OF_getprop(node, "function", function, len);
86 if (strcmp(function, "kbd_backlight") != 0) {
87 free(function, M_TEMP, len);
88 continue;
89 }
90 free(function, M_TEMP, len);
91
92 len = OF_getproplen(node, "pwms");
93 if (len <= 0)
94 continue;
95
96 sc->sc_pwm = malloc(len, M_DEVBUF, M_WAITOK);
97 OF_getpropintarray(node, "pwms", sc->sc_pwm, len);
98 sc->sc_pwm_len = len;
99
100 sc->sc_max_brightness =
101 OF_getpropint(node, "max-brightness", 0);
102 wskbd_get_backlight = pwmleds_get_kbd_backlight;
103 wskbd_set_backlight = pwmleds_set_kbd_backlight;
104 }
105 }
106
107 int
pwmleds_activate(struct device * self,int act)108 pwmleds_activate(struct device *self, int act)
109 {
110 struct pwmleds_softc *sc = (struct pwmleds_softc *)self;
111 struct pwm_state ps;
112 int error;
113
114 switch (act) {
115 case DVACT_QUIESCE:
116 error = pwm_get_state(sc->sc_pwm, &sc->sc_ps_saved);
117 if (error)
118 return error;
119
120 pwm_init_state(sc->sc_pwm, &ps);
121 ps.ps_pulse_width = 0;
122 ps.ps_enabled = 0;
123 return pwm_set_state(sc->sc_pwm, &ps);
124 case DVACT_WAKEUP:
125 return pwm_set_state(sc->sc_pwm, &sc->sc_ps_saved);
126 }
127 return 0;
128 }
129
130 struct pwmleds_softc *
pwmleds_kbd_backlight(void)131 pwmleds_kbd_backlight(void)
132 {
133 struct pwmleds_softc *sc;
134 int i;
135
136 for (i = 0; i < pwmleds_cd.cd_ndevs; i++) {
137 sc = pwmleds_cd.cd_devs[i];
138 if (sc == NULL)
139 continue;
140 if (sc->sc_max_brightness > 0)
141 return sc;
142 }
143
144 return NULL;
145 }
146
147 int
pwmleds_get_kbd_backlight(struct wskbd_backlight * kbl)148 pwmleds_get_kbd_backlight(struct wskbd_backlight *kbl)
149 {
150 struct pwmleds_softc *sc;
151 struct pwm_state ps;
152 int error;
153
154 sc = pwmleds_kbd_backlight();
155 if (sc == NULL)
156 return ENOTTY;
157
158 error = pwm_get_state(sc->sc_pwm, &ps);
159 if (error)
160 return error;
161
162 kbl->min = 0;
163 kbl->max = sc->sc_max_brightness;
164 kbl->curval = (ps.ps_enabled) ?
165 ((uint64_t)ps.ps_pulse_width * kbl->max) / ps.ps_period : 0;
166 return 0;
167 }
168
169 int
pwmleds_set_kbd_backlight(struct wskbd_backlight * kbl)170 pwmleds_set_kbd_backlight(struct wskbd_backlight *kbl)
171 {
172 struct pwmleds_softc *sc;
173 struct pwm_state ps;
174
175 sc = pwmleds_kbd_backlight();
176 if (sc == NULL)
177 return ENOTTY;
178
179 if (kbl->curval < 0 || kbl->curval > sc->sc_max_brightness)
180 return EINVAL;
181
182 pwm_init_state(sc->sc_pwm, &ps);
183 ps.ps_pulse_width =
184 ((uint64_t)kbl->curval * ps.ps_period) / sc->sc_max_brightness;
185 ps.ps_enabled = (ps.ps_pulse_width > 0);
186 return pwm_set_state(sc->sc_pwm, &ps);
187 }
188