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