1 /* $NetBSD: lmu.c,v 1.9 2021/06/18 22:52:04 macallan Exp $ */ 2 3 /*- 4 * Copyright (c) 2020 Michael Lorenz 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 /* 30 * ambient light controller found in PowerBook5,6 31 */ 32 33 #include <sys/cdefs.h> 34 __KERNEL_RCSID(0, "$NetBSD: lmu.c,v 1.9 2021/06/18 22:52:04 macallan Exp $"); 35 36 #include <sys/param.h> 37 #include <sys/systm.h> 38 #include <sys/device.h> 39 #include <sys/conf.h> 40 #include <sys/bus.h> 41 #include <sys/time.h> 42 #include <sys/callout.h> 43 #include <sys/sysctl.h> 44 45 #include <dev/i2c/i2cvar.h> 46 47 #include <dev/sysmon/sysmonvar.h> 48 #include "opt_lmu.h" 49 50 #ifdef LMU_DEBUG 51 #define DPRINTF printf 52 #else 53 #define DPRINTF if (0) printf 54 #endif 55 56 struct lmu_softc { 57 device_t sc_dev; 58 i2c_tag_t sc_i2c; 59 i2c_addr_t sc_addr; 60 int sc_node; 61 62 struct sysmon_envsys *sc_sme; 63 envsys_data_t sc_sensors[2]; 64 callout_t sc_adjust; 65 int sc_thresh, sc_hyst, sc_level, sc_target, sc_current; 66 int sc_lux[2]; 67 time_t sc_last; 68 int sc_lid_state, sc_video_state; 69 }; 70 71 static int lmu_match(device_t, cfdata_t, void *); 72 static void lmu_attach(device_t, device_t, void *); 73 74 static void lmu_sensors_refresh(struct sysmon_envsys *, envsys_data_t *); 75 static void lmu_set_brightness(struct lmu_softc *, int); 76 static int lmu_get_brightness(struct lmu_softc *, int); 77 static void lmu_adjust(void *); 78 static int lmu_sysctl(SYSCTLFN_ARGS); 79 static int lmu_sysctl_thresh(SYSCTLFN_ARGS); 80 81 CFATTACH_DECL_NEW(lmu, sizeof(struct lmu_softc), 82 lmu_match, lmu_attach, NULL, NULL); 83 84 static const struct device_compatible_entry compat_data[] = { 85 { .compat = "lmu-micro" }, 86 { .compat = "lmu-controller" }, 87 DEVICE_COMPAT_EOL 88 }; 89 90 /* time between polling the light sensors */ 91 #define LMU_POLL (hz * 2) 92 /* time between updates to keyboard brightness */ 93 #define LMU_FADE (hz / 16) 94 95 static void 96 lmu_lid_open(device_t dev) 97 { 98 struct lmu_softc * const sc = device_private(dev); 99 100 sc->sc_lid_state = true; 101 } 102 103 static void 104 lmu_lid_close(device_t dev) 105 { 106 struct lmu_softc * const sc = device_private(dev); 107 108 sc->sc_lid_state = false; 109 } 110 111 static void 112 lmu_video_on(device_t dev) 113 { 114 struct lmu_softc * const sc = device_private(dev); 115 116 sc->sc_video_state = true; 117 } 118 119 static void 120 lmu_video_off(device_t dev) 121 { 122 struct lmu_softc * const sc = device_private(dev); 123 124 sc->sc_video_state = false; 125 } 126 127 static void 128 lmu_kbd_brightness_up(device_t dev) 129 { 130 struct lmu_softc * const sc = device_private(dev); 131 132 sc->sc_level = __MIN(16, sc->sc_level + 2); 133 sc->sc_target = sc->sc_level; 134 callout_schedule(&sc->sc_adjust, LMU_FADE); 135 } 136 137 static void 138 lmu_kbd_brightness_down(device_t dev) 139 { 140 struct lmu_softc * const sc = device_private(dev); 141 142 sc->sc_level = __MAX(0, sc->sc_level - 2); 143 sc->sc_target = sc->sc_level; 144 callout_schedule(&sc->sc_adjust, LMU_FADE); 145 } 146 147 static int 148 lmu_match(device_t parent, cfdata_t match, void *aux) 149 { 150 struct i2c_attach_args *ia = aux; 151 int match_result; 152 153 if (iic_use_direct_match(ia, match, compat_data, &match_result)) 154 return match_result; 155 156 return 0; 157 } 158 159 static void 160 lmu_attach(device_t parent, device_t self, void *aux) 161 { 162 struct lmu_softc *sc = device_private(self); 163 struct i2c_attach_args *ia = aux; 164 envsys_data_t *s; 165 const struct sysctlnode *me; 166 167 sc->sc_dev = self; 168 sc->sc_i2c = ia->ia_tag; 169 sc->sc_addr = ia->ia_addr; 170 sc->sc_node = ia->ia_cookie; 171 sc->sc_last = 0; 172 173 aprint_naive("\n"); 174 aprint_normal(": ambient light sensor\n"); 175 176 sc->sc_lid_state = true; 177 pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_OPEN, 178 lmu_lid_open, true); 179 pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_CLOSE, 180 lmu_lid_close, true); 181 sc->sc_video_state = true; 182 pmf_event_register(sc->sc_dev, PMFE_DISPLAY_ON, 183 lmu_video_on, true); 184 pmf_event_register(sc->sc_dev, PMFE_DISPLAY_OFF, 185 lmu_video_off, true); 186 pmf_event_register(sc->sc_dev, PMFE_KEYBOARD_BRIGHTNESS_UP, 187 lmu_kbd_brightness_up, true); 188 pmf_event_register(sc->sc_dev, PMFE_KEYBOARD_BRIGHTNESS_DOWN, 189 lmu_kbd_brightness_down, true); 190 191 sc->sc_sme = sysmon_envsys_create(); 192 sc->sc_sme->sme_name = device_xname(self); 193 sc->sc_sme->sme_cookie = sc; 194 sc->sc_sme->sme_refresh = lmu_sensors_refresh; 195 196 s = &sc->sc_sensors[0]; 197 s->state = ENVSYS_SINVALID; 198 s->units = ENVSYS_LUX; 199 strcpy(s->desc, "right"); 200 s->private = 0; 201 sysmon_envsys_sensor_attach(sc->sc_sme, s); 202 203 s = &sc->sc_sensors[1]; 204 s->state = ENVSYS_SINVALID; 205 s->units = ENVSYS_LUX; 206 strcpy(s->desc, "left"); 207 s->private = 1; 208 sysmon_envsys_sensor_attach(sc->sc_sme, s); 209 210 sysmon_envsys_register(sc->sc_sme); 211 212 sc->sc_thresh = 300; 213 sc->sc_hyst = 30; 214 sc->sc_level = 16; 215 sc->sc_target = 0; 216 sc->sc_current = 0; 217 218 sysctl_createv(NULL, 0, NULL, &me, 219 CTLFLAG_READWRITE, 220 CTLTYPE_NODE, "lmu", 221 SYSCTL_DESCR("LMU driver"), 222 NULL, 0, NULL, 0, 223 CTL_HW, CTL_CREATE, CTL_EOL); 224 225 sysctl_createv(NULL, 0, NULL, NULL, 226 CTLFLAG_READWRITE, 227 CTLTYPE_INT, "level", 228 SYSCTL_DESCR("keyboard brightness"), 229 lmu_sysctl, 0, (void *)sc, 0, 230 CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL); 231 232 sysctl_createv(NULL, 0, NULL, NULL, 233 CTLFLAG_READWRITE, 234 CTLTYPE_INT, "threshold", 235 SYSCTL_DESCR("environmental light threshold"), 236 lmu_sysctl_thresh, 1, (void *)sc, 0, 237 CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL); 238 239 callout_init(&sc->sc_adjust, 0); 240 callout_setfunc(&sc->sc_adjust, lmu_adjust, sc); 241 callout_schedule(&sc->sc_adjust, 0); 242 } 243 244 static void 245 lmu_sensors_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 246 { 247 struct lmu_softc *sc = sme->sme_cookie; 248 int ret; 249 250 if ( edata->private < 3) { 251 ret = lmu_get_brightness(sc, edata->private); 252 if (ret == -1) return; 253 edata->value_cur = ret; 254 } 255 edata->state = ENVSYS_SVALID; 256 } 257 258 static int 259 lmu_get_brightness(struct lmu_softc *sc, int reg) 260 { 261 int error, i; 262 uint16_t buf[2]; 263 uint8_t cmd = 0; 264 265 if (reg > 1) return -1; 266 if (time_second == sc->sc_last) 267 return sc->sc_lux[reg]; 268 269 iic_acquire_bus(sc->sc_i2c, 0); 270 error = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 271 sc->sc_addr, &cmd, 1, buf, 4, 0); 272 iic_release_bus(sc->sc_i2c, 0); 273 if (error) return -1; 274 sc->sc_last = time_second; 275 276 for (i = 0; i < 2; i++) 277 sc->sc_lux[i] = be16toh(buf[i]); 278 279 DPRINTF("<%d %04x %04x>", reg, buf[0], buf[1]); 280 281 return (sc->sc_lux[reg]); 282 } 283 284 static void 285 lmu_set_brightness(struct lmu_softc *sc, int b) 286 { 287 uint8_t cmd[3]; 288 289 cmd[0] = 1; 290 291 cmd[1] = (b & 0xff); 292 cmd[2] = (b & 0xff) >> 8; 293 294 iic_acquire_bus(sc->sc_i2c, 0); 295 iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 296 sc->sc_addr, &cmd, 3, NULL, 0, 0); 297 iic_release_bus(sc->sc_i2c, 0); 298 } 299 300 static void 301 lmu_adjust(void *cookie) 302 { 303 struct lmu_softc *sc = cookie; 304 int left, right, b, offset; 305 306 left = lmu_get_brightness(sc, 1); 307 right = lmu_get_brightness(sc, 0); 308 b = left > right ? left : right; 309 310 if ((b > (sc->sc_thresh + sc->sc_hyst)) || 311 !(sc->sc_lid_state && sc->sc_video_state)) { 312 sc->sc_target = 0; 313 } else if (b < sc->sc_thresh) { 314 sc->sc_target = sc->sc_level; 315 } 316 317 if (sc->sc_target == sc->sc_current) { 318 /* no update needed, check again later */ 319 callout_schedule(&sc->sc_adjust, LMU_POLL); 320 return; 321 } 322 323 324 offset = ((sc->sc_target - sc->sc_current) > 0) ? 2 : -2; 325 sc->sc_current += offset; 326 if (sc->sc_current > sc->sc_level) sc->sc_current = sc->sc_level; 327 if (sc->sc_current < 0) sc->sc_current = 0; 328 329 DPRINTF("[%d]", sc->sc_current); 330 331 lmu_set_brightness(sc, sc->sc_current); 332 333 if (sc->sc_target == sc->sc_current) { 334 /* no update needed, check again later */ 335 callout_schedule(&sc->sc_adjust, LMU_POLL); 336 return; 337 } 338 339 /* more updates upcoming */ 340 callout_schedule(&sc->sc_adjust, LMU_FADE); 341 } 342 343 static int 344 lmu_sysctl(SYSCTLFN_ARGS) 345 { 346 struct sysctlnode node = *rnode; 347 struct lmu_softc *sc = node.sysctl_data; 348 int target; 349 350 target = sc->sc_level; 351 node.sysctl_data = ⌖ 352 if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) { 353 int new_reg; 354 355 new_reg = *(int *)node.sysctl_data; 356 if (new_reg != sc->sc_target) { 357 sc->sc_level = target; 358 sc->sc_target = target; 359 360 } 361 return 0; 362 } 363 return EINVAL; 364 } 365 366 static int 367 lmu_sysctl_thresh(SYSCTLFN_ARGS) 368 { 369 struct sysctlnode node = *rnode; 370 struct lmu_softc *sc = node.sysctl_data; 371 int thresh; 372 373 thresh = sc->sc_thresh; 374 node.sysctl_data = &thresh; 375 if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) { 376 int new_reg; 377 378 new_reg = *(int *)node.sysctl_data; 379 if (new_reg != sc->sc_thresh && new_reg > 0) { 380 sc->sc_thresh = new_reg; 381 } 382 return 0; 383 } 384 return EINVAL; 385 } 386