xref: /netbsd-src/sys/dev/fdt/pwm_backlight.c (revision e6c7e151de239c49d2e38720a061ed9d1fa99309)
1 /* $NetBSD: pwm_backlight.c,v 1.7 2020/01/22 07:29:23 mrg Exp $ */
2 
3 /*-
4  * Copyright (c) 2018 Jared McNeill <jmcneill@invisible.ca>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: pwm_backlight.c,v 1.7 2020/01/22 07:29:23 mrg Exp $");
31 
32 #include <sys/param.h>
33 #include <sys/bus.h>
34 #include <sys/device.h>
35 #include <sys/systm.h>
36 #include <sys/sysctl.h>
37 #include <sys/kmem.h>
38 #include <sys/gpio.h>
39 
40 #include <dev/pwm/pwmvar.h>
41 
42 #include <dev/fdt/fdtvar.h>
43 
44 struct pwm_backlight_softc {
45 	device_t		sc_dev;
46 	pwm_tag_t		sc_pwm;
47 	struct fdtbus_gpio_pin *sc_pin;
48 
49 	u_int			*sc_levels;
50 	u_int			sc_nlevels;
51 	u_int			sc_curlevel;
52 
53 	char			*sc_levelstr;
54 
55 	bool			sc_lid_state;
56 };
57 
58 static int	pwm_backlight_match(device_t, cfdata_t, void *);
59 static void	pwm_backlight_attach(device_t, device_t, void *);
60 
61 static void	pwm_backlight_sysctl_init(struct pwm_backlight_softc *);
62 static void	pwm_backlight_pmf_init(struct pwm_backlight_softc *);
63 static void	pwm_backlight_set(struct pwm_backlight_softc *, u_int, bool);
64 static u_int	pwm_backlight_get(struct pwm_backlight_softc *);
65 
66 static const char *compatible[] = {
67 	"pwm-backlight",
68 	NULL
69 };
70 
71 CFATTACH_DECL_NEW(pwmbacklight, sizeof(struct pwm_backlight_softc),
72 	pwm_backlight_match, pwm_backlight_attach, NULL, NULL);
73 
74 static int
75 pwm_backlight_match(device_t parent, cfdata_t cf, void *aux)
76 {
77 	struct fdt_attach_args * const faa = aux;
78 
79 	return of_match_compatible(faa->faa_phandle, compatible);
80 }
81 
82 static void
83 pwm_backlight_attach(device_t parent, device_t self, void *aux)
84 {
85 	struct pwm_backlight_softc * const sc = device_private(self);
86 	struct fdt_attach_args * const faa = aux;
87 	const int phandle = faa->faa_phandle;
88 	const u_int *levels;
89 	u_int default_level;
90 	u_int n;
91 	int len;
92 
93 	sc->sc_dev = self;
94 	sc->sc_pwm = fdtbus_pwm_acquire(phandle, "pwms");
95 	if (sc->sc_pwm == NULL) {
96 		aprint_error(": couldn't acquire pwm\n");
97 		return;
98 	}
99 	if (of_hasprop(phandle, "enable-gpios")) {
100 		sc->sc_pin = fdtbus_gpio_acquire(phandle, "enable-gpios",
101 		    GPIO_PIN_OUTPUT);
102 		if (!sc->sc_pin) {
103 			aprint_error(": couldn't acquire enable gpio\n");
104 			return;
105 		}
106 		fdtbus_gpio_write(sc->sc_pin, 1);
107 	}
108 
109 	levels = fdtbus_get_prop(phandle, "brightness-levels", &len);
110 	if (len < 4) {
111 		aprint_error(": couldn't get 'brightness-levels' property\n");
112 		return;
113 	}
114 	sc->sc_levels = kmem_alloc(len, KM_SLEEP);
115 	sc->sc_nlevels = len / 4;
116 	for (n = 0; n < sc->sc_nlevels; n++)
117 		sc->sc_levels[n] = be32toh(levels[n]);
118 
119 	aprint_naive("\n");
120 	aprint_normal(": PWM Backlight");
121 	if (sc->sc_nlevels > 1) {
122 		aprint_normal(", %u-%u (%u steps)",
123 		    sc->sc_levels[0], sc->sc_levels[sc->sc_nlevels - 1],
124 		    sc->sc_nlevels);
125 	}
126 	aprint_normal("\n");
127 
128 	sc->sc_lid_state = true;
129 
130 	if (of_getprop_uint32(phandle, "default-brightness-level", &default_level) == 0) {
131 		/* set the default level now */
132 		pwm_backlight_set(sc, default_level, true);
133 	}
134 
135 	sc->sc_curlevel = pwm_backlight_get(sc);
136 
137 	pwm_backlight_sysctl_init(sc);
138 	pwm_backlight_pmf_init(sc);
139 }
140 
141 static void
142 pwm_backlight_set(struct pwm_backlight_softc *sc, u_int index, bool set_cur)
143 {
144 	struct pwm_config conf;
145 
146 	if (index >= sc->sc_nlevels)
147 		return;
148 
149 	aprint_debug_dev(sc->sc_dev, "set duty cycle to %u%%\n", sc->sc_levels[index]);
150 
151 	pwm_disable(sc->sc_pwm);
152 	pwm_get_config(sc->sc_pwm, &conf);
153 	conf.duty_cycle = (conf.period * sc->sc_levels[index]) / sc->sc_levels[sc->sc_nlevels - 1];
154 	pwm_set_config(sc->sc_pwm, &conf);
155 	pwm_enable(sc->sc_pwm);
156 
157 	if (set_cur)
158 		sc->sc_curlevel = index;
159 }
160 
161 static u_int
162 pwm_backlight_get(struct pwm_backlight_softc *sc)
163 {
164 	struct pwm_config conf;
165 	u_int raw_val, n;
166 
167 	pwm_get_config(sc->sc_pwm, &conf);
168 
169 	raw_val = (conf.duty_cycle * sc->sc_levels[sc->sc_nlevels - 1]) / conf.period;
170 
171 	/* Return the closest setting to the raw value */
172 	for (n = 0; n < sc->sc_nlevels; n++) {
173 		if (raw_val <= sc->sc_levels[n])
174 			break;
175 	}
176 	return n;
177 }
178 
179 static int
180 pwm_backlight_sysctl_helper(SYSCTLFN_ARGS)
181 {
182 	struct pwm_backlight_softc * const sc = rnode->sysctl_data;
183 	struct sysctlnode node;
184 	int error;
185 	u_int level, n;
186 
187 	node = *rnode;
188 	node.sysctl_data = &level;
189 
190 	n = pwm_backlight_get(sc);
191 	level = sc->sc_levels[n];
192 
193 	error = sysctl_lookup(SYSCTLFN_CALL(&node));
194 	if (error || newp == NULL)
195 		return error;
196 
197 	for (n = 0; n < sc->sc_nlevels; n++) {
198 		if (sc->sc_levels[n] == level) {
199 			pwm_backlight_set(sc, n, true);
200 			return 0;
201 		}
202 	}
203 
204 	return EINVAL;
205 }
206 
207 static void
208 pwm_backlight_sysctl_init(struct pwm_backlight_softc *sc)
209 {
210 	const struct sysctlnode *node, *pwmnode;
211 	struct sysctllog *log = NULL;
212 	int error;
213 	u_int n;
214 
215 	sc->sc_levelstr = kmem_zalloc(strlen("XXXXX ") * sc->sc_nlevels, KM_SLEEP);
216 	for (n = 0; n < sc->sc_nlevels; n++) {
217 		char buf[7];
218 		snprintf(buf, sizeof(buf), n ? " %u" : "%u", sc->sc_levels[n]);
219 		strcat(sc->sc_levelstr, buf);
220 	}
221 
222 	error = sysctl_createv(&log, 0, NULL, &node,
223 	    CTLFLAG_PERMANENT, CTLTYPE_NODE, "hw", NULL,
224 	    NULL, 0, NULL, 0, CTL_HW, CTL_EOL);
225 	if (error)
226 		goto failed;
227 
228 	error = sysctl_createv(&log, 0, &node, &pwmnode,
229 	    0, CTLTYPE_NODE, device_xname(sc->sc_dev), NULL,
230 	    NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
231 	if (error)
232 		goto failed;
233 
234 	error = sysctl_createv(&log, 0, &pwmnode, NULL,
235 	    0, CTLTYPE_STRING, "levels", NULL,
236 	    NULL, 0, sc->sc_levelstr, 0,
237 	    CTL_CREATE, CTL_EOL);
238 	if (error)
239 		goto failed;
240 
241 	error = sysctl_createv(&log, 0, &pwmnode, NULL,
242 	    CTLFLAG_READWRITE, CTLTYPE_INT, "level", NULL,
243 	    pwm_backlight_sysctl_helper, 0, (void *)sc, 0,
244 	    CTL_CREATE, CTL_EOL);
245 	if (error)
246 		goto failed;
247 
248 	return;
249 
250 failed:
251 	aprint_error_dev(sc->sc_dev, "couldn't create sysctl nodes: %d\n", error);
252 	sysctl_teardown(&log);
253 }
254 
255 static void
256 pwm_backlight_enable(struct pwm_backlight_softc *sc, int enable)
257 {
258 	if (sc->sc_pin)
259 		fdtbus_gpio_write(sc->sc_pin, enable);
260 	else
261 		pwm_backlight_set(sc, enable ? sc->sc_curlevel : 0, false);
262 }
263 
264 static void
265 pwm_backlight_display_on(device_t dev)
266 {
267 	struct pwm_backlight_softc * const sc = device_private(dev);
268 
269 	if (sc->sc_lid_state)
270 		pwm_backlight_enable(sc, 1);
271 }
272 
273 static void
274 pwm_backlight_display_off(device_t dev)
275 {
276 	struct pwm_backlight_softc * const sc = device_private(dev);
277 
278 	pwm_backlight_enable(sc, 0);
279 }
280 
281 static void
282 pwm_backlight_chassis_lid_open(device_t dev)
283 {
284 	struct pwm_backlight_softc * const sc = device_private(dev);
285 
286 	sc->sc_lid_state = true;
287 
288 	pwm_backlight_enable(sc, 1);
289 }
290 
291 static void
292 pwm_backlight_chassis_lid_close(device_t dev)
293 {
294 	struct pwm_backlight_softc * const sc = device_private(dev);
295 
296 	sc->sc_lid_state = false;
297 
298 	pwm_backlight_enable(sc, 0);
299 }
300 
301 static void
302 pwm_backlight_display_brightness_up(device_t dev)
303 {
304 	struct pwm_backlight_softc * const sc = device_private(dev);
305 	u_int n;
306 
307 	n = pwm_backlight_get(sc);
308 	if (n < sc->sc_nlevels - 1)
309 		pwm_backlight_set(sc, n + 1, true);
310 }
311 
312 static void
313 pwm_backlight_display_brightness_down(device_t dev)
314 {
315 	struct pwm_backlight_softc * const sc = device_private(dev);
316 	u_int n;
317 
318 	n = pwm_backlight_get(sc);
319 	if (n > 0)
320 		pwm_backlight_set(sc, n - 1, true);
321 }
322 
323 static void
324 pwm_backlight_pmf_init(struct pwm_backlight_softc *sc)
325 {
326 	pmf_event_register(sc->sc_dev, PMFE_DISPLAY_ON,
327 	    pwm_backlight_display_on, true);
328 	pmf_event_register(sc->sc_dev, PMFE_DISPLAY_OFF,
329 	    pwm_backlight_display_off, true);
330 	pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_OPEN,
331 	    pwm_backlight_chassis_lid_open, true);
332 	pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_CLOSE,
333 	    pwm_backlight_chassis_lid_close, true);
334 	pmf_event_register(sc->sc_dev, PMFE_DISPLAY_BRIGHTNESS_UP,
335 	    pwm_backlight_display_brightness_up, true);
336 	pmf_event_register(sc->sc_dev, PMFE_DISPLAY_BRIGHTNESS_DOWN,
337 	    pwm_backlight_display_brightness_down, true);
338 }
339