1 /* $NetBSD: lmu.c,v 1.8 2021/01/27 02:17:28 thorpej 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.8 2021/01/27 02:17:28 thorpej 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 49 #ifdef LMU_DEBUG 50 #define DPRINTF printf 51 #else 52 #define DPRINTF if (0) printf 53 #endif 54 55 struct lmu_softc { 56 device_t sc_dev; 57 i2c_tag_t sc_i2c; 58 i2c_addr_t sc_addr; 59 int sc_node; 60 61 struct sysmon_envsys *sc_sme; 62 envsys_data_t sc_sensors[2]; 63 callout_t sc_adjust; 64 int sc_thresh, sc_hyst, sc_level, sc_target, sc_current; 65 int sc_lux[2]; 66 time_t sc_last; 67 int sc_lid_state, sc_video_state; 68 }; 69 70 static int lmu_match(device_t, cfdata_t, void *); 71 static void lmu_attach(device_t, device_t, void *); 72 73 static void lmu_sensors_refresh(struct sysmon_envsys *, envsys_data_t *); 74 static void lmu_set_brightness(struct lmu_softc *, int); 75 static int lmu_get_brightness(struct lmu_softc *, int); 76 static void lmu_adjust(void *); 77 static int lmu_sysctl(SYSCTLFN_ARGS); 78 static int lmu_sysctl_thresh(SYSCTLFN_ARGS); 79 80 CFATTACH_DECL_NEW(lmu, sizeof(struct lmu_softc), 81 lmu_match, lmu_attach, NULL, NULL); 82 83 static const struct device_compatible_entry compat_data[] = { 84 { .compat = "lmu-micro" }, 85 { .compat = "lmu-controller" }, 86 DEVICE_COMPAT_EOL 87 }; 88 89 /* time between polling the light sensors */ 90 #define LMU_POLL (hz * 2) 91 /* time between updates to keyboard brightness */ 92 #define LMU_FADE (hz / 16) 93 94 static void 95 lmu_lid_open(device_t dev) 96 { 97 struct lmu_softc * const sc = device_private(dev); 98 99 sc->sc_lid_state = true; 100 } 101 102 static void 103 lmu_lid_close(device_t dev) 104 { 105 struct lmu_softc * const sc = device_private(dev); 106 107 sc->sc_lid_state = false; 108 } 109 110 static void 111 lmu_video_on(device_t dev) 112 { 113 struct lmu_softc * const sc = device_private(dev); 114 115 sc->sc_video_state = true; 116 } 117 118 static void 119 lmu_video_off(device_t dev) 120 { 121 struct lmu_softc * const sc = device_private(dev); 122 123 sc->sc_video_state = false; 124 } 125 126 static void 127 lmu_kbd_brightness_up(device_t dev) 128 { 129 struct lmu_softc * const sc = device_private(dev); 130 131 sc->sc_level = __MIN(16, sc->sc_level + 2); 132 sc->sc_target = sc->sc_level; 133 callout_schedule(&sc->sc_adjust, LMU_FADE); 134 } 135 136 static void 137 lmu_kbd_brightness_down(device_t dev) 138 { 139 struct lmu_softc * const sc = device_private(dev); 140 141 sc->sc_level = __MAX(0, sc->sc_level - 2); 142 sc->sc_target = sc->sc_level; 143 callout_schedule(&sc->sc_adjust, LMU_FADE); 144 } 145 146 static int 147 lmu_match(device_t parent, cfdata_t match, void *aux) 148 { 149 struct i2c_attach_args *ia = aux; 150 int match_result; 151 152 if (iic_use_direct_match(ia, match, compat_data, &match_result)) 153 return match_result; 154 155 return 0; 156 } 157 158 static void 159 lmu_attach(device_t parent, device_t self, void *aux) 160 { 161 struct lmu_softc *sc = device_private(self); 162 struct i2c_attach_args *ia = aux; 163 envsys_data_t *s; 164 const struct sysctlnode *me; 165 166 sc->sc_dev = self; 167 sc->sc_i2c = ia->ia_tag; 168 sc->sc_addr = ia->ia_addr; 169 sc->sc_node = ia->ia_cookie; 170 sc->sc_last = 0; 171 172 aprint_naive("\n"); 173 aprint_normal(": ambient light sensor\n"); 174 175 sc->sc_lid_state = true; 176 pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_OPEN, 177 lmu_lid_open, true); 178 pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_CLOSE, 179 lmu_lid_close, true); 180 sc->sc_video_state = true; 181 pmf_event_register(sc->sc_dev, PMFE_DISPLAY_ON, 182 lmu_video_on, true); 183 pmf_event_register(sc->sc_dev, PMFE_DISPLAY_OFF, 184 lmu_video_off, true); 185 pmf_event_register(sc->sc_dev, PMFE_KEYBOARD_BRIGHTNESS_UP, 186 lmu_kbd_brightness_up, true); 187 pmf_event_register(sc->sc_dev, PMFE_KEYBOARD_BRIGHTNESS_DOWN, 188 lmu_kbd_brightness_down, true); 189 190 sc->sc_sme = sysmon_envsys_create(); 191 sc->sc_sme->sme_name = device_xname(self); 192 sc->sc_sme->sme_cookie = sc; 193 sc->sc_sme->sme_refresh = lmu_sensors_refresh; 194 195 s = &sc->sc_sensors[0]; 196 s->state = ENVSYS_SINVALID; 197 s->units = ENVSYS_LUX; 198 strcpy(s->desc, "right"); 199 s->private = 0; 200 sysmon_envsys_sensor_attach(sc->sc_sme, s); 201 202 s = &sc->sc_sensors[1]; 203 s->state = ENVSYS_SINVALID; 204 s->units = ENVSYS_LUX; 205 strcpy(s->desc, "left"); 206 s->private = 1; 207 sysmon_envsys_sensor_attach(sc->sc_sme, s); 208 209 sysmon_envsys_register(sc->sc_sme); 210 211 /* TODO: make this adjustable via sysctl */ 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