1*5178a7baSmacallan /* $NetBSD: lmu.c,v 1.9 2021/06/18 22:52:04 macallan Exp $ */
22abd9a46Smacallan
32abd9a46Smacallan /*-
42abd9a46Smacallan * Copyright (c) 2020 Michael Lorenz
52abd9a46Smacallan * All rights reserved.
62abd9a46Smacallan *
72abd9a46Smacallan * Redistribution and use in source and binary forms, with or without
82abd9a46Smacallan * modification, are permitted provided that the following conditions
92abd9a46Smacallan * are met:
102abd9a46Smacallan * 1. Redistributions of source code must retain the above copyright
112abd9a46Smacallan * notice, this list of conditions and the following disclaimer.
122abd9a46Smacallan * 2. Redistributions in binary form must reproduce the above copyright
132abd9a46Smacallan * notice, this list of conditions and the following disclaimer in the
142abd9a46Smacallan * documentation and/or other materials provided with the distribution.
152abd9a46Smacallan *
162abd9a46Smacallan * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
172abd9a46Smacallan * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
182abd9a46Smacallan * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
192abd9a46Smacallan * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
202abd9a46Smacallan * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
212abd9a46Smacallan * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
222abd9a46Smacallan * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
232abd9a46Smacallan * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
242abd9a46Smacallan * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
252abd9a46Smacallan * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
262abd9a46Smacallan * POSSIBILITY OF SUCH DAMAGE.
272abd9a46Smacallan */
282abd9a46Smacallan
292abd9a46Smacallan /*
302abd9a46Smacallan * ambient light controller found in PowerBook5,6
312abd9a46Smacallan */
322abd9a46Smacallan
332abd9a46Smacallan #include <sys/cdefs.h>
34*5178a7baSmacallan __KERNEL_RCSID(0, "$NetBSD: lmu.c,v 1.9 2021/06/18 22:52:04 macallan Exp $");
352abd9a46Smacallan
362abd9a46Smacallan #include <sys/param.h>
372abd9a46Smacallan #include <sys/systm.h>
382abd9a46Smacallan #include <sys/device.h>
392abd9a46Smacallan #include <sys/conf.h>
402abd9a46Smacallan #include <sys/bus.h>
412abd9a46Smacallan #include <sys/time.h>
422abd9a46Smacallan #include <sys/callout.h>
43b4b09679Smacallan #include <sys/sysctl.h>
442abd9a46Smacallan
452abd9a46Smacallan #include <dev/i2c/i2cvar.h>
462abd9a46Smacallan
472abd9a46Smacallan #include <dev/sysmon/sysmonvar.h>
48*5178a7baSmacallan #include "opt_lmu.h"
492abd9a46Smacallan
50b4b09679Smacallan #ifdef LMU_DEBUG
51b4b09679Smacallan #define DPRINTF printf
52b4b09679Smacallan #else
53b4b09679Smacallan #define DPRINTF if (0) printf
54b4b09679Smacallan #endif
55b4b09679Smacallan
562abd9a46Smacallan struct lmu_softc {
572abd9a46Smacallan device_t sc_dev;
582abd9a46Smacallan i2c_tag_t sc_i2c;
592abd9a46Smacallan i2c_addr_t sc_addr;
602abd9a46Smacallan int sc_node;
612abd9a46Smacallan
622abd9a46Smacallan struct sysmon_envsys *sc_sme;
632abd9a46Smacallan envsys_data_t sc_sensors[2];
642abd9a46Smacallan callout_t sc_adjust;
65b4b09679Smacallan int sc_thresh, sc_hyst, sc_level, sc_target, sc_current;
66b4b09679Smacallan int sc_lux[2];
67b4b09679Smacallan time_t sc_last;
680ab295ffSmacallan int sc_lid_state, sc_video_state;
692abd9a46Smacallan };
702abd9a46Smacallan
712abd9a46Smacallan static int lmu_match(device_t, cfdata_t, void *);
722abd9a46Smacallan static void lmu_attach(device_t, device_t, void *);
732abd9a46Smacallan
742abd9a46Smacallan static void lmu_sensors_refresh(struct sysmon_envsys *, envsys_data_t *);
752abd9a46Smacallan static void lmu_set_brightness(struct lmu_softc *, int);
762abd9a46Smacallan static int lmu_get_brightness(struct lmu_softc *, int);
772abd9a46Smacallan static void lmu_adjust(void *);
78b4b09679Smacallan static int lmu_sysctl(SYSCTLFN_ARGS);
79b4b09679Smacallan static int lmu_sysctl_thresh(SYSCTLFN_ARGS);
802abd9a46Smacallan
812abd9a46Smacallan CFATTACH_DECL_NEW(lmu, sizeof(struct lmu_softc),
822abd9a46Smacallan lmu_match, lmu_attach, NULL, NULL);
832abd9a46Smacallan
842abd9a46Smacallan static const struct device_compatible_entry compat_data[] = {
8556caee62Sthorpej { .compat = "lmu-micro" },
8656caee62Sthorpej { .compat = "lmu-controller" },
87ec189949Sthorpej DEVICE_COMPAT_EOL
882abd9a46Smacallan };
892abd9a46Smacallan
90b4b09679Smacallan /* time between polling the light sensors */
91b4b09679Smacallan #define LMU_POLL (hz * 2)
92b4b09679Smacallan /* time between updates to keyboard brightness */
93b4b09679Smacallan #define LMU_FADE (hz / 16)
94b4b09679Smacallan
950ab295ffSmacallan static void
lmu_lid_open(device_t dev)960ab295ffSmacallan lmu_lid_open(device_t dev)
970ab295ffSmacallan {
980ab295ffSmacallan struct lmu_softc * const sc = device_private(dev);
990ab295ffSmacallan
1000ab295ffSmacallan sc->sc_lid_state = true;
1010ab295ffSmacallan }
1020ab295ffSmacallan
1030ab295ffSmacallan static void
lmu_lid_close(device_t dev)1040ab295ffSmacallan lmu_lid_close(device_t dev)
1050ab295ffSmacallan {
1060ab295ffSmacallan struct lmu_softc * const sc = device_private(dev);
1070ab295ffSmacallan
1080ab295ffSmacallan sc->sc_lid_state = false;
1090ab295ffSmacallan }
1100ab295ffSmacallan
1110ab295ffSmacallan static void
lmu_video_on(device_t dev)1120ab295ffSmacallan lmu_video_on(device_t dev)
1130ab295ffSmacallan {
1140ab295ffSmacallan struct lmu_softc * const sc = device_private(dev);
1150ab295ffSmacallan
1160ab295ffSmacallan sc->sc_video_state = true;
1170ab295ffSmacallan }
1180ab295ffSmacallan
1190ab295ffSmacallan static void
lmu_video_off(device_t dev)1200ab295ffSmacallan lmu_video_off(device_t dev)
1210ab295ffSmacallan {
1220ab295ffSmacallan struct lmu_softc * const sc = device_private(dev);
1230ab295ffSmacallan
1240ab295ffSmacallan sc->sc_video_state = false;
1250ab295ffSmacallan }
1260ab295ffSmacallan
12714d389afSmacallan static void
lmu_kbd_brightness_up(device_t dev)12814d389afSmacallan lmu_kbd_brightness_up(device_t dev)
12914d389afSmacallan {
13014d389afSmacallan struct lmu_softc * const sc = device_private(dev);
13114d389afSmacallan
13214d389afSmacallan sc->sc_level = __MIN(16, sc->sc_level + 2);
13314d389afSmacallan sc->sc_target = sc->sc_level;
13414d389afSmacallan callout_schedule(&sc->sc_adjust, LMU_FADE);
13514d389afSmacallan }
13614d389afSmacallan
13714d389afSmacallan static void
lmu_kbd_brightness_down(device_t dev)13814d389afSmacallan lmu_kbd_brightness_down(device_t dev)
13914d389afSmacallan {
14014d389afSmacallan struct lmu_softc * const sc = device_private(dev);
14114d389afSmacallan
14214d389afSmacallan sc->sc_level = __MAX(0, sc->sc_level - 2);
14314d389afSmacallan sc->sc_target = sc->sc_level;
14414d389afSmacallan callout_schedule(&sc->sc_adjust, LMU_FADE);
14514d389afSmacallan }
14614d389afSmacallan
1472abd9a46Smacallan static int
lmu_match(device_t parent,cfdata_t match,void * aux)1482abd9a46Smacallan lmu_match(device_t parent, cfdata_t match, void *aux)
1492abd9a46Smacallan {
1502abd9a46Smacallan struct i2c_attach_args *ia = aux;
1512abd9a46Smacallan int match_result;
1522abd9a46Smacallan
1532abd9a46Smacallan if (iic_use_direct_match(ia, match, compat_data, &match_result))
1542abd9a46Smacallan return match_result;
1552abd9a46Smacallan
1562abd9a46Smacallan return 0;
1572abd9a46Smacallan }
1582abd9a46Smacallan
1592abd9a46Smacallan static void
lmu_attach(device_t parent,device_t self,void * aux)1602abd9a46Smacallan lmu_attach(device_t parent, device_t self, void *aux)
1612abd9a46Smacallan {
1622abd9a46Smacallan struct lmu_softc *sc = device_private(self);
1632abd9a46Smacallan struct i2c_attach_args *ia = aux;
1642abd9a46Smacallan envsys_data_t *s;
165b4b09679Smacallan const struct sysctlnode *me;
1662abd9a46Smacallan
1672abd9a46Smacallan sc->sc_dev = self;
1682abd9a46Smacallan sc->sc_i2c = ia->ia_tag;
1692abd9a46Smacallan sc->sc_addr = ia->ia_addr;
1702abd9a46Smacallan sc->sc_node = ia->ia_cookie;
171b4b09679Smacallan sc->sc_last = 0;
1722abd9a46Smacallan
1732abd9a46Smacallan aprint_naive("\n");
1742abd9a46Smacallan aprint_normal(": ambient light sensor\n");
1752abd9a46Smacallan
1760ab295ffSmacallan sc->sc_lid_state = true;
1770ab295ffSmacallan pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_OPEN,
1780ab295ffSmacallan lmu_lid_open, true);
1790ab295ffSmacallan pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_CLOSE,
1800ab295ffSmacallan lmu_lid_close, true);
1810ab295ffSmacallan sc->sc_video_state = true;
1820ab295ffSmacallan pmf_event_register(sc->sc_dev, PMFE_DISPLAY_ON,
1830ab295ffSmacallan lmu_video_on, true);
1840ab295ffSmacallan pmf_event_register(sc->sc_dev, PMFE_DISPLAY_OFF,
1850ab295ffSmacallan lmu_video_off, true);
18614d389afSmacallan pmf_event_register(sc->sc_dev, PMFE_KEYBOARD_BRIGHTNESS_UP,
18714d389afSmacallan lmu_kbd_brightness_up, true);
18814d389afSmacallan pmf_event_register(sc->sc_dev, PMFE_KEYBOARD_BRIGHTNESS_DOWN,
18914d389afSmacallan lmu_kbd_brightness_down, true);
1900ab295ffSmacallan
1912abd9a46Smacallan sc->sc_sme = sysmon_envsys_create();
1922abd9a46Smacallan sc->sc_sme->sme_name = device_xname(self);
1932abd9a46Smacallan sc->sc_sme->sme_cookie = sc;
1942abd9a46Smacallan sc->sc_sme->sme_refresh = lmu_sensors_refresh;
1952abd9a46Smacallan
1962abd9a46Smacallan s = &sc->sc_sensors[0];
1972abd9a46Smacallan s->state = ENVSYS_SINVALID;
1982abd9a46Smacallan s->units = ENVSYS_LUX;
1992abd9a46Smacallan strcpy(s->desc, "right");
2002abd9a46Smacallan s->private = 0;
2012abd9a46Smacallan sysmon_envsys_sensor_attach(sc->sc_sme, s);
2022abd9a46Smacallan
2032abd9a46Smacallan s = &sc->sc_sensors[1];
2042abd9a46Smacallan s->state = ENVSYS_SINVALID;
2052abd9a46Smacallan s->units = ENVSYS_LUX;
2062abd9a46Smacallan strcpy(s->desc, "left");
207b4b09679Smacallan s->private = 1;
2082abd9a46Smacallan sysmon_envsys_sensor_attach(sc->sc_sme, s);
2092abd9a46Smacallan
2102abd9a46Smacallan sysmon_envsys_register(sc->sc_sme);
2112abd9a46Smacallan
2122abd9a46Smacallan sc->sc_thresh = 300;
2132abd9a46Smacallan sc->sc_hyst = 30;
214b4b09679Smacallan sc->sc_level = 16;
215b4b09679Smacallan sc->sc_target = 0;
216b4b09679Smacallan sc->sc_current = 0;
217b4b09679Smacallan
218b4b09679Smacallan sysctl_createv(NULL, 0, NULL, &me,
219b4b09679Smacallan CTLFLAG_READWRITE,
220b4b09679Smacallan CTLTYPE_NODE, "lmu",
221b4b09679Smacallan SYSCTL_DESCR("LMU driver"),
222b4b09679Smacallan NULL, 0, NULL, 0,
223b4b09679Smacallan CTL_HW, CTL_CREATE, CTL_EOL);
224b4b09679Smacallan
225b4b09679Smacallan sysctl_createv(NULL, 0, NULL, NULL,
226b4b09679Smacallan CTLFLAG_READWRITE,
227b4b09679Smacallan CTLTYPE_INT, "level",
228b4b09679Smacallan SYSCTL_DESCR("keyboard brightness"),
229b4b09679Smacallan lmu_sysctl, 0, (void *)sc, 0,
230b4b09679Smacallan CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL);
231b4b09679Smacallan
232b4b09679Smacallan sysctl_createv(NULL, 0, NULL, NULL,
233b4b09679Smacallan CTLFLAG_READWRITE,
234b4b09679Smacallan CTLTYPE_INT, "threshold",
235b4b09679Smacallan SYSCTL_DESCR("environmental light threshold"),
236b4b09679Smacallan lmu_sysctl_thresh, 1, (void *)sc, 0,
237b4b09679Smacallan CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL);
2382abd9a46Smacallan
2392abd9a46Smacallan callout_init(&sc->sc_adjust, 0);
2402abd9a46Smacallan callout_setfunc(&sc->sc_adjust, lmu_adjust, sc);
2412abd9a46Smacallan callout_schedule(&sc->sc_adjust, 0);
2422abd9a46Smacallan }
2432abd9a46Smacallan
2442abd9a46Smacallan static void
lmu_sensors_refresh(struct sysmon_envsys * sme,envsys_data_t * edata)2452abd9a46Smacallan lmu_sensors_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
2462abd9a46Smacallan {
2472abd9a46Smacallan struct lmu_softc *sc = sme->sme_cookie;
2482abd9a46Smacallan int ret;
2492abd9a46Smacallan
2502abd9a46Smacallan if ( edata->private < 3) {
2512abd9a46Smacallan ret = lmu_get_brightness(sc, edata->private);
2522abd9a46Smacallan if (ret == -1) return;
2532abd9a46Smacallan edata->value_cur = ret;
2542abd9a46Smacallan }
2552abd9a46Smacallan edata->state = ENVSYS_SVALID;
2562abd9a46Smacallan }
2572abd9a46Smacallan
2582abd9a46Smacallan static int
lmu_get_brightness(struct lmu_softc * sc,int reg)2592abd9a46Smacallan lmu_get_brightness(struct lmu_softc *sc, int reg)
2602abd9a46Smacallan {
261b4b09679Smacallan int error, i;
262b4b09679Smacallan uint16_t buf[2];
263b4b09679Smacallan uint8_t cmd = 0;
264b4b09679Smacallan
265b4b09679Smacallan if (reg > 1) return -1;
266b4b09679Smacallan if (time_second == sc->sc_last)
267b4b09679Smacallan return sc->sc_lux[reg];
2682abd9a46Smacallan
2692abd9a46Smacallan iic_acquire_bus(sc->sc_i2c, 0);
2702abd9a46Smacallan error = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
271b4b09679Smacallan sc->sc_addr, &cmd, 1, buf, 4, 0);
2722abd9a46Smacallan iic_release_bus(sc->sc_i2c, 0);
2732abd9a46Smacallan if (error) return -1;
274b4b09679Smacallan sc->sc_last = time_second;
275b4b09679Smacallan
276b4b09679Smacallan for (i = 0; i < 2; i++)
277b4b09679Smacallan sc->sc_lux[i] = be16toh(buf[i]);
278b4b09679Smacallan
279b4b09679Smacallan DPRINTF("<%d %04x %04x>", reg, buf[0], buf[1]);
280b4b09679Smacallan
281b4b09679Smacallan return (sc->sc_lux[reg]);
2822abd9a46Smacallan }
2832abd9a46Smacallan
2842abd9a46Smacallan static void
lmu_set_brightness(struct lmu_softc * sc,int b)2852abd9a46Smacallan lmu_set_brightness(struct lmu_softc *sc, int b)
2862abd9a46Smacallan {
2872abd9a46Smacallan uint8_t cmd[3];
2882abd9a46Smacallan
2892abd9a46Smacallan cmd[0] = 1;
290b4b09679Smacallan
291b4b09679Smacallan cmd[1] = (b & 0xff);
292b4b09679Smacallan cmd[2] = (b & 0xff) >> 8;
2932abd9a46Smacallan
2942abd9a46Smacallan iic_acquire_bus(sc->sc_i2c, 0);
2952abd9a46Smacallan iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
2962abd9a46Smacallan sc->sc_addr, &cmd, 3, NULL, 0, 0);
2972abd9a46Smacallan iic_release_bus(sc->sc_i2c, 0);
2982abd9a46Smacallan }
2992abd9a46Smacallan
3002abd9a46Smacallan static void
lmu_adjust(void * cookie)3012abd9a46Smacallan lmu_adjust(void *cookie)
3022abd9a46Smacallan {
3032abd9a46Smacallan struct lmu_softc *sc = cookie;
304b4b09679Smacallan int left, right, b, offset;
3052abd9a46Smacallan
306b4b09679Smacallan left = lmu_get_brightness(sc, 1);
3072abd9a46Smacallan right = lmu_get_brightness(sc, 0);
3082abd9a46Smacallan b = left > right ? left : right;
3092abd9a46Smacallan
3100ab295ffSmacallan if ((b > (sc->sc_thresh + sc->sc_hyst)) ||
3110ab295ffSmacallan !(sc->sc_lid_state && sc->sc_video_state)) {
312b4b09679Smacallan sc->sc_target = 0;
3132abd9a46Smacallan } else if (b < sc->sc_thresh) {
314b4b09679Smacallan sc->sc_target = sc->sc_level;
3152abd9a46Smacallan }
3162abd9a46Smacallan
317b4b09679Smacallan if (sc->sc_target == sc->sc_current) {
318b4b09679Smacallan /* no update needed, check again later */
319b4b09679Smacallan callout_schedule(&sc->sc_adjust, LMU_POLL);
320b4b09679Smacallan return;
321b4b09679Smacallan }
322b4b09679Smacallan
323b4b09679Smacallan
324b4b09679Smacallan offset = ((sc->sc_target - sc->sc_current) > 0) ? 2 : -2;
325b4b09679Smacallan sc->sc_current += offset;
326b4b09679Smacallan if (sc->sc_current > sc->sc_level) sc->sc_current = sc->sc_level;
327b4b09679Smacallan if (sc->sc_current < 0) sc->sc_current = 0;
328b4b09679Smacallan
329b4b09679Smacallan DPRINTF("[%d]", sc->sc_current);
330b4b09679Smacallan
331b4b09679Smacallan lmu_set_brightness(sc, sc->sc_current);
332b4b09679Smacallan
333b4b09679Smacallan if (sc->sc_target == sc->sc_current) {
334b4b09679Smacallan /* no update needed, check again later */
335b4b09679Smacallan callout_schedule(&sc->sc_adjust, LMU_POLL);
336b4b09679Smacallan return;
337b4b09679Smacallan }
338b4b09679Smacallan
339b4b09679Smacallan /* more updates upcoming */
340b4b09679Smacallan callout_schedule(&sc->sc_adjust, LMU_FADE);
341b4b09679Smacallan }
342b4b09679Smacallan
343b4b09679Smacallan static int
lmu_sysctl(SYSCTLFN_ARGS)344b4b09679Smacallan lmu_sysctl(SYSCTLFN_ARGS)
345b4b09679Smacallan {
346b4b09679Smacallan struct sysctlnode node = *rnode;
347b4b09679Smacallan struct lmu_softc *sc = node.sysctl_data;
348b4b09679Smacallan int target;
349b4b09679Smacallan
350b4b09679Smacallan target = sc->sc_level;
351b4b09679Smacallan node.sysctl_data = ⌖
352b4b09679Smacallan if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) {
353b4b09679Smacallan int new_reg;
354b4b09679Smacallan
355b4b09679Smacallan new_reg = *(int *)node.sysctl_data;
356b4b09679Smacallan if (new_reg != sc->sc_target) {
357b4b09679Smacallan sc->sc_level = target;
358b4b09679Smacallan sc->sc_target = target;
359b4b09679Smacallan
360b4b09679Smacallan }
361b4b09679Smacallan return 0;
362b4b09679Smacallan }
363b4b09679Smacallan return EINVAL;
364b4b09679Smacallan }
365b4b09679Smacallan
366b4b09679Smacallan static int
lmu_sysctl_thresh(SYSCTLFN_ARGS)367b4b09679Smacallan lmu_sysctl_thresh(SYSCTLFN_ARGS)
368b4b09679Smacallan {
369b4b09679Smacallan struct sysctlnode node = *rnode;
370b4b09679Smacallan struct lmu_softc *sc = node.sysctl_data;
371b4b09679Smacallan int thresh;
372b4b09679Smacallan
373b4b09679Smacallan thresh = sc->sc_thresh;
374b4b09679Smacallan node.sysctl_data = &thresh;
375b4b09679Smacallan if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) {
376b4b09679Smacallan int new_reg;
377b4b09679Smacallan
378b4b09679Smacallan new_reg = *(int *)node.sysctl_data;
379b4b09679Smacallan if (new_reg != sc->sc_thresh && new_reg > 0) {
380b4b09679Smacallan sc->sc_thresh = new_reg;
381b4b09679Smacallan }
382b4b09679Smacallan return 0;
383b4b09679Smacallan }
384b4b09679Smacallan return EINVAL;
3852abd9a46Smacallan }
386