xref: /netbsd-src/sys/dev/fdt/pwm_backlight.c (revision deb6f0161a9109e7de9b519dc8dfb9478668dcdd)
1 /* $NetBSD: pwm_backlight.c,v 1.4 2018/05/10 13:11:21 jmcneill 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.4 2018/05/10 13:11:21 jmcneill 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 
52 	char			*sc_levelstr;
53 
54 	bool			sc_lid_state;
55 };
56 
57 static int	pwm_backlight_match(device_t, cfdata_t, void *);
58 static void	pwm_backlight_attach(device_t, device_t, void *);
59 
60 static void	pwm_backlight_sysctl_init(struct pwm_backlight_softc *);
61 static void	pwm_backlight_pmf_init(struct pwm_backlight_softc *);
62 static void	pwm_backlight_set(struct pwm_backlight_softc *, u_int);
63 static u_int	pwm_backlight_get(struct pwm_backlight_softc *);
64 
65 static const char *compatible[] = {
66 	"pwm-backlight",
67 	NULL
68 };
69 
70 CFATTACH_DECL_NEW(pwmbacklight, sizeof(struct pwm_backlight_softc),
71 	pwm_backlight_match, pwm_backlight_attach, NULL, NULL);
72 
73 static int
74 pwm_backlight_match(device_t parent, cfdata_t cf, void *aux)
75 {
76 	struct fdt_attach_args * const faa = aux;
77 
78 	return of_match_compatible(faa->faa_phandle, compatible);
79 }
80 
81 static void
82 pwm_backlight_attach(device_t parent, device_t self, void *aux)
83 {
84 	struct pwm_backlight_softc * const sc = device_private(self);
85 	struct fdt_attach_args * const faa = aux;
86 	const int phandle = faa->faa_phandle;
87 	const u_int *levels;
88 	u_int default_level;
89 	u_int n;
90 	int len;
91 
92 	sc->sc_dev = self;
93 	sc->sc_pwm = fdtbus_pwm_acquire(phandle, "pwms");
94 	if (sc->sc_pwm == NULL) {
95 		aprint_error(": couldn't acquire pwm\n");
96 		return;
97 	}
98 	if (of_hasprop(phandle, "enable-gpios")) {
99 		sc->sc_pin = fdtbus_gpio_acquire(phandle, "enable-gpios",
100 		    GPIO_PIN_OUTPUT);
101 		if (!sc->sc_pin) {
102 			aprint_error(": couldn't acquire enable gpio\n");
103 			return;
104 		}
105 		fdtbus_gpio_write(sc->sc_pin, 1);
106 	}
107 
108 	levels = fdtbus_get_prop(phandle, "brightness-levels", &len);
109 	if (len < 4) {
110 		aprint_error(": couldn't get 'brightness-levels' property\n");
111 		return;
112 	}
113 	sc->sc_levels = kmem_alloc(len, KM_SLEEP);
114 	sc->sc_nlevels = len / 4;
115 	for (n = 0; n < sc->sc_nlevels; n++)
116 		sc->sc_levels[n] = be32toh(levels[n]);
117 
118 	aprint_naive("\n");
119 	aprint_normal(": PWM Backlight");
120 	aprint_verbose(" <");
121 	for (n = 0; n < sc->sc_nlevels; n++) {
122 		aprint_verbose("%s%u", n ? " " : "", sc->sc_levels[n]);
123 	}
124 	aprint_verbose(">");
125 	aprint_normal("\n");
126 
127 	sc->sc_lid_state = true;
128 
129 	if (of_getprop_uint32(phandle, "default-brightness-level", &default_level) == 0) {
130 		/* set the default level now */
131 		pwm_backlight_set(sc, default_level);
132 	}
133 
134 	pwm_backlight_sysctl_init(sc);
135 	pwm_backlight_pmf_init(sc);
136 }
137 
138 static void
139 pwm_backlight_set(struct pwm_backlight_softc *sc, u_int index)
140 {
141 	struct pwm_config conf;
142 
143 	if (index >= sc->sc_nlevels)
144 		return;
145 
146 	aprint_debug_dev(sc->sc_dev, "set duty cycle to %u%%\n", sc->sc_levels[index]);
147 
148 	pwm_disable(sc->sc_pwm);
149 	pwm_get_config(sc->sc_pwm, &conf);
150 	conf.duty_cycle = (conf.period * sc->sc_levels[index]) / sc->sc_levels[sc->sc_nlevels - 1];
151 	pwm_set_config(sc->sc_pwm, &conf);
152 	pwm_enable(sc->sc_pwm);
153 }
154 
155 static u_int
156 pwm_backlight_get(struct pwm_backlight_softc *sc)
157 {
158 	struct pwm_config conf;
159 	u_int raw_val, n;
160 
161 	pwm_get_config(sc->sc_pwm, &conf);
162 
163 	raw_val = (conf.duty_cycle * sc->sc_levels[sc->sc_nlevels - 1]) / conf.period;
164 
165 	/* Return the closest setting to the raw value */
166 	for (n = 0; n < sc->sc_nlevels; n++) {
167 		if (raw_val <= sc->sc_levels[n])
168 			break;
169 	}
170 	return n;
171 }
172 
173 static int
174 pwm_backlight_sysctl_helper(SYSCTLFN_ARGS)
175 {
176 	struct pwm_backlight_softc * const sc = rnode->sysctl_data;
177 	struct sysctlnode node;
178 	int error;
179 	u_int level, n;
180 
181 	node = *rnode;
182 	node.sysctl_data = &level;
183 
184 	n = pwm_backlight_get(sc);
185 	level = sc->sc_levels[n];
186 
187 	error = sysctl_lookup(SYSCTLFN_CALL(&node));
188 	if (error || newp == NULL)
189 		return error;
190 
191 	for (n = 0; n < sc->sc_nlevels; n++) {
192 		if (sc->sc_levels[n] == level) {
193 			pwm_backlight_set(sc, n);
194 			return 0;
195 		}
196 	}
197 
198 	return EINVAL;
199 }
200 
201 static void
202 pwm_backlight_sysctl_init(struct pwm_backlight_softc *sc)
203 {
204 	const struct sysctlnode *node, *pwmnode;
205 	struct sysctllog *log = NULL;
206 	int error;
207 	u_int n;
208 
209 	sc->sc_levelstr = kmem_zalloc(strlen("XXXXX ") * sc->sc_nlevels, KM_SLEEP);
210 	for (n = 0; n < sc->sc_nlevels; n++) {
211 		char buf[7];
212 		snprintf(buf, sizeof(buf), n ? " %u" : "%u", sc->sc_levels[n]);
213 		strcat(sc->sc_levelstr, buf);
214 	}
215 
216 	error = sysctl_createv(&log, 0, NULL, &node,
217 	    CTLFLAG_PERMANENT, CTLTYPE_NODE, "hw", NULL,
218 	    NULL, 0, NULL, 0, CTL_HW, CTL_EOL);
219 	if (error)
220 		goto failed;
221 
222 	error = sysctl_createv(&log, 0, &node, &pwmnode,
223 	    0, CTLTYPE_NODE, device_xname(sc->sc_dev), NULL,
224 	    NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
225 	if (error)
226 		goto failed;
227 
228 	error = sysctl_createv(&log, 0, &pwmnode, NULL,
229 	    0, CTLTYPE_STRING, "levels", NULL,
230 	    NULL, 0, sc->sc_levelstr, 0,
231 	    CTL_CREATE, CTL_EOL);
232 	if (error)
233 		goto failed;
234 
235 	error = sysctl_createv(&log, 0, &pwmnode, NULL,
236 	    CTLFLAG_READWRITE, CTLTYPE_INT, "level", NULL,
237 	    pwm_backlight_sysctl_helper, 0, (void *)sc, 0,
238 	    CTL_CREATE, CTL_EOL);
239 	if (error)
240 		goto failed;
241 
242 	return;
243 
244 failed:
245 	aprint_error_dev(sc->sc_dev, "couldn't create sysctl nodes: %d\n", error);
246 	sysctl_teardown(&log);
247 }
248 
249 static void
250 pwm_backlight_display_on(device_t dev)
251 {
252 	struct pwm_backlight_softc * const sc = device_private(dev);
253 
254 	if (sc->sc_pin && sc->sc_lid_state)
255 		fdtbus_gpio_write(sc->sc_pin, 1);
256 }
257 
258 static void
259 pwm_backlight_display_off(device_t dev)
260 {
261 	struct pwm_backlight_softc * const sc = device_private(dev);
262 
263 	if (sc->sc_pin)
264 		fdtbus_gpio_write(sc->sc_pin, 0);
265 }
266 
267 static void
268 pwm_backlight_chassis_lid_open(device_t dev)
269 {
270 	struct pwm_backlight_softc * const sc = device_private(dev);
271 
272 	sc->sc_lid_state = true;
273 
274 	if (sc->sc_pin)
275 		fdtbus_gpio_write(sc->sc_pin, 1);
276 }
277 
278 static void
279 pwm_backlight_chassis_lid_close(device_t dev)
280 {
281 	struct pwm_backlight_softc * const sc = device_private(dev);
282 
283 	sc->sc_lid_state = false;
284 
285 	if (sc->sc_pin)
286 		fdtbus_gpio_write(sc->sc_pin, 0);
287 }
288 
289 static void
290 pwm_backlight_display_brightness_up(device_t dev)
291 {
292 	struct pwm_backlight_softc * const sc = device_private(dev);
293 	u_int n;
294 
295 	n = pwm_backlight_get(sc);
296 	if (n < sc->sc_nlevels - 1)
297 		pwm_backlight_set(sc, n + 1);
298 }
299 
300 static void
301 pwm_backlight_display_brightness_down(device_t dev)
302 {
303 	struct pwm_backlight_softc * const sc = device_private(dev);
304 	u_int n;
305 
306 	n = pwm_backlight_get(sc);
307 	if (n > 0)
308 		pwm_backlight_set(sc, n - 1);
309 }
310 
311 static void
312 pwm_backlight_pmf_init(struct pwm_backlight_softc *sc)
313 {
314 	pmf_event_register(sc->sc_dev, PMFE_DISPLAY_ON,
315 	    pwm_backlight_display_on, true);
316 	pmf_event_register(sc->sc_dev, PMFE_DISPLAY_OFF,
317 	    pwm_backlight_display_off, true);
318 	pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_OPEN,
319 	    pwm_backlight_chassis_lid_open, true);
320 	pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_CLOSE,
321 	    pwm_backlight_chassis_lid_close, true);
322 	pmf_event_register(sc->sc_dev, PMFE_DISPLAY_BRIGHTNESS_UP,
323 	    pwm_backlight_display_brightness_up, true);
324 	pmf_event_register(sc->sc_dev, PMFE_DISPLAY_BRIGHTNESS_DOWN,
325 	    pwm_backlight_display_brightness_down, true);
326 }
327