lmu.c revision 1.2 1 /* $NetBSD: lmu.c,v 1.2 2020/02/06 02:17:24 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.2 2020/02/06 02:17:24 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
44 #include <dev/ofw/openfirm.h>
45
46 #include <dev/i2c/i2cvar.h>
47
48 #include <dev/sysmon/sysmonvar.h>
49
50 struct lmu_softc {
51 device_t sc_dev;
52 i2c_tag_t sc_i2c;
53 i2c_addr_t sc_addr;
54 int sc_node;
55
56 struct sysmon_envsys *sc_sme;
57 envsys_data_t sc_sensors[2];
58 callout_t sc_adjust;
59 int sc_thresh, sc_hyst, sc_level;
60 int sc_lid_state, sc_video_state;
61 };
62
63 static int lmu_match(device_t, cfdata_t, void *);
64 static void lmu_attach(device_t, device_t, void *);
65
66 static void lmu_sensors_refresh(struct sysmon_envsys *, envsys_data_t *);
67 static void lmu_set_brightness(struct lmu_softc *, int);
68 static int lmu_get_brightness(struct lmu_softc *, int);
69 static void lmu_adjust(void *);
70
71 CFATTACH_DECL_NEW(lmu, sizeof(struct lmu_softc),
72 lmu_match, lmu_attach, NULL, NULL);
73
74 static const struct device_compatible_entry compat_data[] = {
75 { "lmu-micro", 0 },
76 { "lmu-controller", 0 },
77 { NULL, 0 }
78 };
79
80 static void
81 lmu_lid_open(device_t dev)
82 {
83 struct lmu_softc * const sc = device_private(dev);
84
85 sc->sc_lid_state = true;
86 }
87
88 static void
89 lmu_lid_close(device_t dev)
90 {
91 struct lmu_softc * const sc = device_private(dev);
92
93 sc->sc_lid_state = false;
94 }
95
96 static void
97 lmu_video_on(device_t dev)
98 {
99 struct lmu_softc * const sc = device_private(dev);
100
101 sc->sc_video_state = true;
102 }
103
104 static void
105 lmu_video_off(device_t dev)
106 {
107 struct lmu_softc * const sc = device_private(dev);
108
109 sc->sc_video_state = false;
110 }
111
112 static int
113 lmu_match(device_t parent, cfdata_t match, void *aux)
114 {
115 struct i2c_attach_args *ia = aux;
116 int match_result;
117
118 if (iic_use_direct_match(ia, match, compat_data, &match_result))
119 return match_result;
120
121 return 0;
122 }
123
124 static void
125 lmu_attach(device_t parent, device_t self, void *aux)
126 {
127 struct lmu_softc *sc = device_private(self);
128 struct i2c_attach_args *ia = aux;
129 envsys_data_t *s;
130
131 sc->sc_dev = self;
132 sc->sc_i2c = ia->ia_tag;
133 sc->sc_addr = ia->ia_addr;
134 sc->sc_node = ia->ia_cookie;
135
136 aprint_naive("\n");
137 aprint_normal(": ambient light sensor\n");
138
139 sc->sc_lid_state = true;
140 pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_OPEN,
141 lmu_lid_open, true);
142 pmf_event_register(sc->sc_dev, PMFE_CHASSIS_LID_CLOSE,
143 lmu_lid_close, true);
144 sc->sc_video_state = true;
145 pmf_event_register(sc->sc_dev, PMFE_DISPLAY_ON,
146 lmu_video_on, true);
147 pmf_event_register(sc->sc_dev, PMFE_DISPLAY_OFF,
148 lmu_video_off, true);
149
150 sc->sc_sme = sysmon_envsys_create();
151 sc->sc_sme->sme_name = device_xname(self);
152 sc->sc_sme->sme_cookie = sc;
153 sc->sc_sme->sme_refresh = lmu_sensors_refresh;
154
155 s = &sc->sc_sensors[0];
156 s->state = ENVSYS_SINVALID;
157 s->units = ENVSYS_LUX;
158 strcpy(s->desc, "right");
159 s->private = 0;
160 sysmon_envsys_sensor_attach(sc->sc_sme, s);
161
162 s = &sc->sc_sensors[1];
163 s->state = ENVSYS_SINVALID;
164 s->units = ENVSYS_LUX;
165 strcpy(s->desc, "left");
166 s->private = 2;
167 sysmon_envsys_sensor_attach(sc->sc_sme, s);
168
169 sysmon_envsys_register(sc->sc_sme);
170
171 /* TODO: make this adjustable via sysctl */
172 sc->sc_thresh = 300;
173 sc->sc_hyst = 30;
174 sc->sc_level = 100;
175
176 callout_init(&sc->sc_adjust, 0);
177 callout_setfunc(&sc->sc_adjust, lmu_adjust, sc);
178 callout_schedule(&sc->sc_adjust, 0);
179 }
180
181 static void
182 lmu_sensors_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
183 {
184 struct lmu_softc *sc = sme->sme_cookie;
185 int ret;
186
187 if ( edata->private < 3) {
188 ret = lmu_get_brightness(sc, edata->private);
189 if (ret == -1) return;
190 edata->value_cur = ret;
191 }
192 edata->state = ENVSYS_SVALID;
193 }
194
195 static int
196 lmu_get_brightness(struct lmu_softc *sc, int reg)
197 {
198 int error;
199 uint16_t buf;
200 uint8_t cmd = reg;
201
202 iic_acquire_bus(sc->sc_i2c, 0);
203 error = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
204 sc->sc_addr, &cmd, 1, &buf, 2, 0);
205 iic_release_bus(sc->sc_i2c, 0);
206 if (error) return -1;
207 return be16toh(buf);
208 }
209
210 static void
211 lmu_set_brightness(struct lmu_softc *sc, int b)
212 {
213 int bb;
214 uint8_t cmd[3];
215
216 cmd[0] = 1;
217 bb = b * 255;
218 cmd[1] = (bb & 0xff00) >> 8;
219 cmd[2] = bb & 0xff;
220
221 iic_acquire_bus(sc->sc_i2c, 0);
222 iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
223 sc->sc_addr, &cmd, 3, NULL, 0, 0);
224 iic_release_bus(sc->sc_i2c, 0);
225 }
226
227 static void
228 lmu_adjust(void *cookie)
229 {
230 struct lmu_softc *sc = cookie;
231 int left, right, b;
232
233 left = lmu_get_brightness(sc, 2);
234 right = lmu_get_brightness(sc, 0);
235 b = left > right ? left : right;
236
237 if ((b > (sc->sc_thresh + sc->sc_hyst)) ||
238 !(sc->sc_lid_state && sc->sc_video_state)) {
239 lmu_set_brightness(sc, 0);
240 } else if (b < sc->sc_thresh) {
241 lmu_set_brightness(sc, sc->sc_level);
242 }
243
244 callout_schedule(&sc->sc_adjust, hz * 2);
245 }
246