xref: /freebsd/sys/dev/acpica/acpi_smbat.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
1 /*-
2  * Copyright (c) 2005 Hans Petter Selasky
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include "opt_acpi.h"
31 #include <sys/param.h>
32 #include <sys/kernel.h>
33 #include <sys/module.h>
34 #include <sys/bus.h>
35 
36 #include <contrib/dev/acpica/acpi.h>
37 #include <dev/acpica/acpivar.h>
38 #include <dev/acpica/acpiio.h>
39 #include <dev/acpica/acpi_smbus.h>
40 
41 /* Transactions have failed after 500 ms. */
42 #define SMBUS_TIMEOUT	50
43 
44 struct acpi_smbat_softc {
45 	uint8_t		sb_base_addr;
46 	device_t	ec_dev;
47 
48 	struct acpi_bif	bif;
49 	struct acpi_bst	bst;
50 	struct timespec	bif_lastupdated;
51 	struct timespec	bst_lastupdated;
52 };
53 
54 static int	acpi_smbat_probe(device_t dev);
55 static int	acpi_smbat_attach(device_t dev);
56 static int	acpi_smbat_shutdown(device_t dev);
57 static int	acpi_smbat_info_expired(struct timespec *lastupdated);
58 static void	acpi_smbat_info_updated(struct timespec *lastupdated);
59 static int	acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif);
60 static int	acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst);
61 
62 ACPI_SERIAL_DECL(smbat, "ACPI Smart Battery");
63 
64 static device_method_t acpi_smbat_methods[] = {
65 	/* device interface */
66 	DEVMETHOD(device_probe, acpi_smbat_probe),
67 	DEVMETHOD(device_attach, acpi_smbat_attach),
68 	DEVMETHOD(device_shutdown, acpi_smbat_shutdown),
69 
70 	/* ACPI battery interface */
71 	DEVMETHOD(acpi_batt_get_status, acpi_smbat_get_bst),
72 	DEVMETHOD(acpi_batt_get_info, acpi_smbat_get_bif),
73 
74 	{0, 0}
75 };
76 
77 static driver_t	acpi_smbat_driver = {
78 	"battery",
79 	acpi_smbat_methods,
80 	sizeof(struct acpi_smbat_softc),
81 };
82 
83 static devclass_t acpi_smbat_devclass;
84 DRIVER_MODULE(acpi_smbat, acpi, acpi_smbat_driver, acpi_smbat_devclass, 0, 0);
85 MODULE_DEPEND(acpi_smbat, acpi, 1, 1, 1);
86 
87 static int
88 acpi_smbat_probe(device_t dev)
89 {
90 	static char *smbat_ids[] = {"ACPI0001", "ACPI0005", NULL};
91 	ACPI_STATUS status;
92 
93 	if (acpi_disabled("smbat") ||
94 	    ACPI_ID_PROBE(device_get_parent(dev), dev, smbat_ids) == NULL)
95 		return (ENXIO);
96 	status = AcpiEvaluateObject(acpi_get_handle(dev), "_EC", NULL, NULL);
97 	if (ACPI_FAILURE(status))
98 		return (ENXIO);
99 
100 	device_set_desc(dev, "ACPI Smart Battery");
101 	return (0);
102 }
103 
104 static int
105 acpi_smbat_attach(device_t dev)
106 {
107 	struct acpi_smbat_softc *sc;
108 	uint32_t base;
109 
110 	sc = device_get_softc(dev);
111 	if (ACPI_FAILURE(acpi_GetInteger(acpi_get_handle(dev), "_EC", &base))) {
112 		device_printf(dev, "cannot get EC base address\n");
113 		return (ENXIO);
114 	}
115 	sc->sb_base_addr = (base >> 8) & 0xff;
116 
117 	/* XXX Only works with one EC, but nearly all systems only have one. */
118 	sc->ec_dev = devclass_get_device(devclass_find("acpi_ec"), 0);
119 	if (sc->ec_dev == NULL) {
120 		device_printf(dev, "cannot find EC device\n");
121 		return (ENXIO);
122 	}
123 
124 	timespecclear(&sc->bif_lastupdated);
125 	timespecclear(&sc->bst_lastupdated);
126 
127 	if (acpi_battery_register(dev) != 0) {
128 		device_printf(dev, "cannot register battery\n");
129 		return (ENXIO);
130 	}
131 	return (0);
132 }
133 
134 static int
135 acpi_smbat_shutdown(device_t dev)
136 {
137 
138 	acpi_battery_remove(dev);
139 	return (0);
140 }
141 
142 static int
143 acpi_smbat_info_expired(struct timespec *lastupdated)
144 {
145 	struct timespec	curtime;
146 
147 	ACPI_SERIAL_ASSERT(smbat);
148 
149 	if (lastupdated == NULL)
150 		return (TRUE);
151 	if (!timespecisset(lastupdated))
152 		return (TRUE);
153 
154 	getnanotime(&curtime);
155 	timespecsub(&curtime, lastupdated);
156 	return (curtime.tv_sec < 0 ||
157 	    curtime.tv_sec > acpi_battery_get_info_expire());
158 }
159 
160 static void
161 acpi_smbat_info_updated(struct timespec *lastupdated)
162 {
163 
164 	ACPI_SERIAL_ASSERT(smbat);
165 
166 	if (lastupdated != NULL)
167 		getnanotime(lastupdated);
168 }
169 
170 static int
171 acpi_smbus_read_2(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd,
172     uint16_t *ptr)
173 {
174 	int error, to;
175 	ACPI_INTEGER val;
176 
177 	ACPI_SERIAL_ASSERT(smbat);
178 
179 	val = addr;
180 	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR,
181 	    val, 1);
182 	if (error)
183 		goto out;
184 
185 	val = cmd;
186 	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD,
187 	    val, 1);
188 	if (error)
189 		goto out;
190 
191 	val = 0x09; /* | 0x80 if PEC */
192 	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
193 	    val, 1);
194 	if (error)
195 		goto out;
196 
197 	for (to = SMBUS_TIMEOUT; to != 0; to--) {
198 		error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
199 		    &val, 1);
200 		if (error)
201 			goto out;
202 		if (val == 0)
203 			break;
204 		AcpiOsSleep(10);
205 	}
206 	if (to == 0) {
207 		error = ETIMEDOUT;
208 		goto out;
209 	}
210 
211 	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1);
212 	if (error)
213 		goto out;
214 	if (val & SMBUS_STS_MASK) {
215 		printf("%s: AE_ERROR 0x%x\n",
216 		       __FUNCTION__, (int)(val & SMBUS_STS_MASK));
217 		error = EIO;
218 		goto out;
219 	}
220 
221 	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA,
222 	    &val, 2);
223 	if (error)
224 		goto out;
225 
226 	*ptr = val;
227 
228 out:
229 	return (error);
230 }
231 
232 static int
233 acpi_smbus_read_multi_1(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd,
234     uint8_t *ptr, uint16_t len)
235 {
236 	ACPI_INTEGER val;
237 	uint8_t	to;
238 	int error;
239 
240 	ACPI_SERIAL_ASSERT(smbat);
241 
242 	val = addr;
243 	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR,
244 	    val, 1);
245 	if (error)
246 		goto out;
247 
248 	val = cmd;
249 	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD,
250 	    val, 1);
251 	if (error)
252 		goto out;
253 
254 	val = 0x0B /* | 0x80 if PEC */ ;
255 	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
256 	    val, 1);
257 	if (error)
258 		goto out;
259 
260 	for (to = SMBUS_TIMEOUT; to != 0; to--) {
261 		error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
262 		    &val, 1);
263 		if (error)
264 			goto out;
265 		if (val == 0)
266 			break;
267 		AcpiOsSleep(10);
268 	}
269 	if (to == 0) {
270 		error = ETIMEDOUT;
271 		goto out;
272 	}
273 
274 	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1);
275 	if (error)
276 		goto out;
277 	if (val & SMBUS_STS_MASK) {
278 		printf("%s: AE_ERROR 0x%x\n",
279 		       __FUNCTION__, (int)(val & SMBUS_STS_MASK));
280 		error = EIO;
281 		goto out;
282 	}
283 
284 	/* get length */
285 	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_BCNT,
286 	    &val, 1);
287 	if (error)
288 		goto out;
289 	val = (val & 0x1f) + 1;
290 
291 	bzero(ptr, len);
292 	if (len > val)
293 		len = val;
294 
295 	while (len--) {
296 		error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA
297 		    + len, &val, 1);
298 		if (error)
299 			goto out;
300 
301 		ptr[len] = val;
302 	}
303 
304 out:
305 	return (error);
306 }
307 
308 static int
309 acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst)
310 {
311 	struct acpi_smbat_softc *sc;
312 	int error;
313 	uint32_t cap_units, factor;
314 	int16_t val;
315 	uint8_t	addr;
316 
317 	ACPI_SERIAL_BEGIN(smbat);
318 
319 	addr = SMBATT_ADDRESS;
320 	error = ENXIO;
321 	sc = device_get_softc(dev);
322 
323 	if (!acpi_smbat_info_expired(&sc->bst_lastupdated)) {
324 		error = 0;
325 		goto out;
326 	}
327 
328 	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val))
329 		goto out;
330 	if (val & SMBATT_BM_CAPACITY_MODE) {
331 		factor = 10;
332 		cap_units = ACPI_BIF_UNITS_MW;
333 	} else {
334 		factor = 1;
335 		cap_units = ACPI_BIF_UNITS_MA;
336 	}
337 
338 	/* get battery status */
339 	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_STATUS, &val))
340 		goto out;
341 
342 	sc->bst.state = 0;
343 	if (val & SMBATT_BS_DISCHARGING)
344 		sc->bst.state |= ACPI_BATT_STAT_DISCHARG;
345 
346 	if (val & SMBATT_BS_REMAINING_CAPACITY_ALARM)
347 		sc->bst.state |= ACPI_BATT_STAT_CRITICAL;
348 
349 	/*
350 	 * If the rate is negative, it is discharging.  Otherwise,
351 	 * it is charging.
352 	 */
353 	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_CURRENT, &val))
354 		goto out;
355 
356 	if (val > 0) {
357 		sc->bst.rate = val * factor;
358 		sc->bst.state |= ACPI_BATT_STAT_CHARGING;
359 	} else if (val < 0)
360 		sc->bst.rate = (-val) * factor;
361 	else
362 		sc->bst.rate = 0;
363 
364 	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_REMAINING_CAPACITY, &val))
365 		goto out;
366 	sc->bst.cap = val * factor;
367 
368 	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_VOLTAGE, &val))
369 		goto out;
370 	sc->bst.volt = val;
371 
372 	acpi_smbat_info_updated(&sc->bst_lastupdated);
373 	error = 0;
374 
375 out:
376 	if (error == 0)
377 		memcpy(bst, &sc->bst, sizeof(sc->bst));
378 	ACPI_SERIAL_END(smbat);
379 	return (error);
380 }
381 
382 static int
383 acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif)
384 {
385 	struct acpi_smbat_softc *sc;
386 	int error;
387 	uint32_t factor;
388 	uint16_t val;
389 	uint8_t addr;
390 
391 	ACPI_SERIAL_BEGIN(smbat);
392 
393 	addr = SMBATT_ADDRESS;
394 	error = ENXIO;
395 	sc = device_get_softc(dev);
396 
397 	if (!acpi_smbat_info_expired(&sc->bif_lastupdated)) {
398 		error = 0;
399 		goto out;
400 	}
401 
402 	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val))
403 		goto out;
404 	if (val & SMBATT_BM_CAPACITY_MODE) {
405 		factor = 10;
406 		sc->bif.units = ACPI_BIF_UNITS_MW;
407 	} else {
408 		factor = 1;
409 		sc->bif.units = ACPI_BIF_UNITS_MA;
410 	}
411 
412 	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_DESIGN_CAPACITY, &val))
413 		goto out;
414 	sc->bif.dcap = val * factor;
415 
416 	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_FULL_CHARGE_CAPACITY, &val))
417 		goto out;
418 	sc->bif.lfcap = val * factor;
419 	sc->bif.btech = 1;		/* secondary (rechargeable) */
420 
421 	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_DESIGN_VOLTAGE, &val))
422 		goto out;
423 	sc->bif.dvol = val;
424 
425 	sc->bif.wcap = sc->bif.dcap / 10;
426 	sc->bif.lcap = sc->bif.dcap / 10;
427 
428 	sc->bif.gra1 = factor;	/* not supported */
429 	sc->bif.gra2 = factor;	/* not supported */
430 
431 	if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_DEVICE_NAME,
432 	    sc->bif.model, sizeof(sc->bif.model)))
433 		goto out;
434 
435 	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_SERIAL_NUMBER, &val))
436 		goto out;
437 	snprintf(sc->bif.serial, sizeof(sc->bif.serial), "0x%04x", val);
438 
439 	if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_DEVICE_CHEMISTRY,
440 	    sc->bif.type, sizeof(sc->bif.type)))
441 		goto out;
442 
443 	if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_MANUFACTURER_DATA,
444 	    sc->bif.oeminfo, sizeof(sc->bif.oeminfo)))
445 		goto out;
446 
447 	/* XXX check if device was replugged during read? */
448 
449 	acpi_smbat_info_updated(&sc->bif_lastupdated);
450 	error = 0;
451 
452 out:
453 	if (error == 0)
454 		memcpy(bif, &sc->bif, sizeof(sc->bif));
455 	ACPI_SERIAL_END(smbat);
456 	return (error);
457 }
458