1 /* $NetBSD: lmu.c,v 1.4 2020/04/23 12:56:40 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.4 2020/04/23 12:56:40 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 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 { "lmu-micro", 0 }, 85 { "lmu-controller", 0 }, 86 { NULL, 0 } 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 int 127 lmu_match(device_t parent, cfdata_t match, void *aux) 128 { 129 struct i2c_attach_args *ia = aux; 130 int match_result; 131 132 if (iic_use_direct_match(ia, match, compat_data, &match_result)) 133 return match_result; 134 135 return 0; 136 } 137 138 static void 139 lmu_attach(device_t parent, device_t self, void *aux) 140 { 141 struct lmu_softc *sc = device_private(self); 142 struct i2c_attach_args *ia = aux; 143 envsys_data_t *s; 144 const struct sysctlnode *me; 145 146 sc->sc_dev = self; 147 sc->sc_i2c = ia->ia_tag; 148 sc->sc_addr = ia->ia_addr; 149 sc->sc_node = ia->ia_cookie; 150 sc->sc_last = 0; 151 152 aprint_naive("\n"); 153 aprint_normal(": ambient light sensor\n"); 154 155 sc->sc_lid_state = true; 156 pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_OPEN, 157 lmu_lid_open, true); 158 pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_CLOSE, 159 lmu_lid_close, true); 160 sc->sc_video_state = true; 161 pmf_event_register(sc->sc_dev, PMFE_DISPLAY_ON, 162 lmu_video_on, true); 163 pmf_event_register(sc->sc_dev, PMFE_DISPLAY_OFF, 164 lmu_video_off, true); 165 166 sc->sc_sme = sysmon_envsys_create(); 167 sc->sc_sme->sme_name = device_xname(self); 168 sc->sc_sme->sme_cookie = sc; 169 sc->sc_sme->sme_refresh = lmu_sensors_refresh; 170 171 s = &sc->sc_sensors[0]; 172 s->state = ENVSYS_SINVALID; 173 s->units = ENVSYS_LUX; 174 strcpy(s->desc, "right"); 175 s->private = 0; 176 sysmon_envsys_sensor_attach(sc->sc_sme, s); 177 178 s = &sc->sc_sensors[1]; 179 s->state = ENVSYS_SINVALID; 180 s->units = ENVSYS_LUX; 181 strcpy(s->desc, "left"); 182 s->private = 1; 183 sysmon_envsys_sensor_attach(sc->sc_sme, s); 184 185 sysmon_envsys_register(sc->sc_sme); 186 187 /* TODO: make this adjustable via sysctl */ 188 sc->sc_thresh = 300; 189 sc->sc_hyst = 30; 190 sc->sc_level = 16; 191 sc->sc_target = 0; 192 sc->sc_current = 0; 193 194 sysctl_createv(NULL, 0, NULL, &me, 195 CTLFLAG_READWRITE, 196 CTLTYPE_NODE, "lmu", 197 SYSCTL_DESCR("LMU driver"), 198 NULL, 0, NULL, 0, 199 CTL_HW, CTL_CREATE, CTL_EOL); 200 201 sysctl_createv(NULL, 0, NULL, NULL, 202 CTLFLAG_READWRITE, 203 CTLTYPE_INT, "level", 204 SYSCTL_DESCR("keyboard brightness"), 205 lmu_sysctl, 0, (void *)sc, 0, 206 CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL); 207 208 sysctl_createv(NULL, 0, NULL, NULL, 209 CTLFLAG_READWRITE, 210 CTLTYPE_INT, "threshold", 211 SYSCTL_DESCR("environmental light threshold"), 212 lmu_sysctl_thresh, 1, (void *)sc, 0, 213 CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL); 214 215 callout_init(&sc->sc_adjust, 0); 216 callout_setfunc(&sc->sc_adjust, lmu_adjust, sc); 217 callout_schedule(&sc->sc_adjust, 0); 218 } 219 220 static void 221 lmu_sensors_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 222 { 223 struct lmu_softc *sc = sme->sme_cookie; 224 int ret; 225 226 if ( edata->private < 3) { 227 ret = lmu_get_brightness(sc, edata->private); 228 if (ret == -1) return; 229 edata->value_cur = ret; 230 } 231 edata->state = ENVSYS_SVALID; 232 } 233 234 static int 235 lmu_get_brightness(struct lmu_softc *sc, int reg) 236 { 237 int error, i; 238 uint16_t buf[2]; 239 uint8_t cmd = 0; 240 241 if (reg > 1) return -1; 242 if (time_second == sc->sc_last) 243 return sc->sc_lux[reg]; 244 245 iic_acquire_bus(sc->sc_i2c, 0); 246 error = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 247 sc->sc_addr, &cmd, 1, buf, 4, 0); 248 iic_release_bus(sc->sc_i2c, 0); 249 if (error) return -1; 250 sc->sc_last = time_second; 251 252 for (i = 0; i < 2; i++) 253 sc->sc_lux[i] = be16toh(buf[i]); 254 255 DPRINTF("<%d %04x %04x>", reg, buf[0], buf[1]); 256 257 return (sc->sc_lux[reg]); 258 } 259 260 static void 261 lmu_set_brightness(struct lmu_softc *sc, int b) 262 { 263 uint8_t cmd[3]; 264 265 cmd[0] = 1; 266 267 cmd[1] = (b & 0xff); 268 cmd[2] = (b & 0xff) >> 8; 269 270 iic_acquire_bus(sc->sc_i2c, 0); 271 iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 272 sc->sc_addr, &cmd, 3, NULL, 0, 0); 273 iic_release_bus(sc->sc_i2c, 0); 274 } 275 276 static void 277 lmu_adjust(void *cookie) 278 { 279 struct lmu_softc *sc = cookie; 280 int left, right, b, offset; 281 282 left = lmu_get_brightness(sc, 1); 283 right = lmu_get_brightness(sc, 0); 284 b = left > right ? left : right; 285 286 if ((b > (sc->sc_thresh + sc->sc_hyst)) || 287 !(sc->sc_lid_state && sc->sc_video_state)) { 288 sc->sc_target = 0; 289 } else if (b < sc->sc_thresh) { 290 sc->sc_target = sc->sc_level; 291 } 292 293 if (sc->sc_target == sc->sc_current) { 294 /* no update needed, check again later */ 295 callout_schedule(&sc->sc_adjust, LMU_POLL); 296 return; 297 } 298 299 300 offset = ((sc->sc_target - sc->sc_current) > 0) ? 2 : -2; 301 sc->sc_current += offset; 302 if (sc->sc_current > sc->sc_level) sc->sc_current = sc->sc_level; 303 if (sc->sc_current < 0) sc->sc_current = 0; 304 305 DPRINTF("[%d]", sc->sc_current); 306 307 lmu_set_brightness(sc, sc->sc_current); 308 309 if (sc->sc_target == sc->sc_current) { 310 /* no update needed, check again later */ 311 callout_schedule(&sc->sc_adjust, LMU_POLL); 312 return; 313 } 314 315 /* more updates upcoming */ 316 callout_schedule(&sc->sc_adjust, LMU_FADE); 317 } 318 319 static int 320 lmu_sysctl(SYSCTLFN_ARGS) 321 { 322 struct sysctlnode node = *rnode; 323 struct lmu_softc *sc = node.sysctl_data; 324 int target; 325 326 target = sc->sc_level; 327 node.sysctl_data = ⌖ 328 if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) { 329 int new_reg; 330 331 new_reg = *(int *)node.sysctl_data; 332 if (new_reg != sc->sc_target) { 333 sc->sc_level = target; 334 sc->sc_target = target; 335 336 } 337 return 0; 338 } 339 return EINVAL; 340 } 341 342 static int 343 lmu_sysctl_thresh(SYSCTLFN_ARGS) 344 { 345 struct sysctlnode node = *rnode; 346 struct lmu_softc *sc = node.sysctl_data; 347 int thresh; 348 349 thresh = sc->sc_thresh; 350 node.sysctl_data = &thresh; 351 if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) { 352 int new_reg; 353 354 new_reg = *(int *)node.sysctl_data; 355 if (new_reg != sc->sc_thresh && new_reg > 0) { 356 sc->sc_thresh = new_reg; 357 } 358 return 0; 359 } 360 return EINVAL; 361 } 362