acpi_smbat.c revision 227309
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: head/sys/dev/acpica/acpi_smbat.c 227309 2011-11-07 15:43:11Z ed $");
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/include/acpi.h>
37
38#include <dev/acpica/acpivar.h>
39#include <dev/acpica/acpiio.h>
40#include <dev/acpica/acpi_smbus.h>
41
42/* Transactions have failed after 500 ms. */
43#define SMBUS_TIMEOUT	50
44
45struct acpi_smbat_softc {
46	uint8_t		sb_base_addr;
47	device_t	ec_dev;
48
49	struct acpi_bif	bif;
50	struct acpi_bst	bst;
51	struct timespec	bif_lastupdated;
52	struct timespec	bst_lastupdated;
53};
54
55static int	acpi_smbat_probe(device_t dev);
56static int	acpi_smbat_attach(device_t dev);
57static int	acpi_smbat_shutdown(device_t dev);
58static int	acpi_smbat_info_expired(struct timespec *lastupdated);
59static void	acpi_smbat_info_updated(struct timespec *lastupdated);
60static int	acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif);
61static int	acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst);
62
63ACPI_SERIAL_DECL(smbat, "ACPI Smart Battery");
64
65SYSCTL_DECL(_debug_acpi);
66static SYSCTL_NODE(_debug_acpi, OID_AUTO, batt, CTLFLAG_RD, NULL,
67    "Battery debugging");
68
69/* On some laptops with smart batteries, enabling battery monitoring
70 * software causes keystrokes from atkbd to be lost.  This has also been
71 * reported on Linux, and is apparently due to the keyboard and I2C line
72 * for the battery being routed through the same chip.  Whether that's
73 * accurate or not, adding extra sleeps to the status checking code
74 * causes the problem to go away.
75 *
76 * If you experience that problem, try a value of 10ms and move up
77 * from there.
78 */
79static int      batt_sleep_ms;
80SYSCTL_INT(_debug_acpi_batt, OID_AUTO, batt_sleep_ms, CTLFLAG_RW, &batt_sleep_ms, 0,
81    "Sleep during battery status updates to prevent keystroke loss.");
82
83static device_method_t acpi_smbat_methods[] = {
84	/* device interface */
85	DEVMETHOD(device_probe, acpi_smbat_probe),
86	DEVMETHOD(device_attach, acpi_smbat_attach),
87	DEVMETHOD(device_shutdown, acpi_smbat_shutdown),
88
89	/* ACPI battery interface */
90	DEVMETHOD(acpi_batt_get_status, acpi_smbat_get_bst),
91	DEVMETHOD(acpi_batt_get_info, acpi_smbat_get_bif),
92
93	{0, 0}
94};
95
96static driver_t	acpi_smbat_driver = {
97	"battery",
98	acpi_smbat_methods,
99	sizeof(struct acpi_smbat_softc),
100};
101
102static devclass_t acpi_smbat_devclass;
103DRIVER_MODULE(acpi_smbat, acpi, acpi_smbat_driver, acpi_smbat_devclass, 0, 0);
104MODULE_DEPEND(acpi_smbat, acpi, 1, 1, 1);
105
106static int
107acpi_smbat_probe(device_t dev)
108{
109	static char *smbat_ids[] = {"ACPI0001", "ACPI0005", NULL};
110	ACPI_STATUS status;
111
112	if (acpi_disabled("smbat") ||
113	    ACPI_ID_PROBE(device_get_parent(dev), dev, smbat_ids) == NULL)
114		return (ENXIO);
115	status = AcpiEvaluateObject(acpi_get_handle(dev), "_EC", NULL, NULL);
116	if (ACPI_FAILURE(status))
117		return (ENXIO);
118
119	device_set_desc(dev, "ACPI Smart Battery");
120	return (0);
121}
122
123static int
124acpi_smbat_attach(device_t dev)
125{
126	struct acpi_smbat_softc *sc;
127	uint32_t base;
128
129	sc = device_get_softc(dev);
130	if (ACPI_FAILURE(acpi_GetInteger(acpi_get_handle(dev), "_EC", &base))) {
131		device_printf(dev, "cannot get EC base address\n");
132		return (ENXIO);
133	}
134	sc->sb_base_addr = (base >> 8) & 0xff;
135
136	/* XXX Only works with one EC, but nearly all systems only have one. */
137	sc->ec_dev = devclass_get_device(devclass_find("acpi_ec"), 0);
138	if (sc->ec_dev == NULL) {
139		device_printf(dev, "cannot find EC device\n");
140		return (ENXIO);
141	}
142
143	timespecclear(&sc->bif_lastupdated);
144	timespecclear(&sc->bst_lastupdated);
145
146	if (acpi_battery_register(dev) != 0) {
147		device_printf(dev, "cannot register battery\n");
148		return (ENXIO);
149	}
150	return (0);
151}
152
153static int
154acpi_smbat_shutdown(device_t dev)
155{
156
157	acpi_battery_remove(dev);
158	return (0);
159}
160
161static int
162acpi_smbat_info_expired(struct timespec *lastupdated)
163{
164	struct timespec	curtime;
165
166	ACPI_SERIAL_ASSERT(smbat);
167
168	if (lastupdated == NULL)
169		return (TRUE);
170	if (!timespecisset(lastupdated))
171		return (TRUE);
172
173	getnanotime(&curtime);
174	timespecsub(&curtime, lastupdated);
175	return (curtime.tv_sec < 0 ||
176	    curtime.tv_sec > acpi_battery_get_info_expire());
177}
178
179static void
180acpi_smbat_info_updated(struct timespec *lastupdated)
181{
182
183	ACPI_SERIAL_ASSERT(smbat);
184
185	if (lastupdated != NULL)
186		getnanotime(lastupdated);
187}
188
189static int
190acpi_smbus_read_2(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd,
191    uint16_t *ptr)
192{
193	int error, to;
194	UINT64 val;
195
196	ACPI_SERIAL_ASSERT(smbat);
197
198	if (batt_sleep_ms)
199	    AcpiOsSleep(batt_sleep_ms);
200
201	val = addr;
202	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR,
203	    val, 1);
204	if (error)
205		goto out;
206
207	val = cmd;
208	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD,
209	    val, 1);
210	if (error)
211		goto out;
212
213	val = 0x09; /* | 0x80 if PEC */
214	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
215	    val, 1);
216	if (error)
217		goto out;
218
219	if (batt_sleep_ms)
220	    AcpiOsSleep(batt_sleep_ms);
221
222	for (to = SMBUS_TIMEOUT; to != 0; to--) {
223		error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
224		    &val, 1);
225		if (error)
226			goto out;
227		if (val == 0)
228			break;
229		AcpiOsSleep(10);
230	}
231	if (to == 0) {
232		error = ETIMEDOUT;
233		goto out;
234	}
235
236	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1);
237	if (error)
238		goto out;
239	if (val & SMBUS_STS_MASK) {
240		printf("%s: AE_ERROR 0x%x\n",
241		       __FUNCTION__, (int)(val & SMBUS_STS_MASK));
242		error = EIO;
243		goto out;
244	}
245
246	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA,
247	    &val, 2);
248	if (error)
249		goto out;
250
251	*ptr = val;
252
253out:
254	return (error);
255}
256
257static int
258acpi_smbus_read_multi_1(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd,
259    uint8_t *ptr, uint16_t len)
260{
261	UINT64 val;
262	uint8_t	to;
263	int error;
264
265	ACPI_SERIAL_ASSERT(smbat);
266
267	if (batt_sleep_ms)
268	    AcpiOsSleep(batt_sleep_ms);
269
270	val = addr;
271	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR,
272	    val, 1);
273	if (error)
274		goto out;
275
276	val = cmd;
277	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD,
278	    val, 1);
279	if (error)
280		goto out;
281
282	val = 0x0B /* | 0x80 if PEC */ ;
283	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
284	    val, 1);
285	if (error)
286		goto out;
287
288	if (batt_sleep_ms)
289	    AcpiOsSleep(batt_sleep_ms);
290
291	for (to = SMBUS_TIMEOUT; to != 0; to--) {
292		error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
293		    &val, 1);
294		if (error)
295			goto out;
296		if (val == 0)
297			break;
298		AcpiOsSleep(10);
299	}
300	if (to == 0) {
301		error = ETIMEDOUT;
302		goto out;
303	}
304
305	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1);
306	if (error)
307		goto out;
308	if (val & SMBUS_STS_MASK) {
309		printf("%s: AE_ERROR 0x%x\n",
310		       __FUNCTION__, (int)(val & SMBUS_STS_MASK));
311		error = EIO;
312		goto out;
313	}
314
315	/* get length */
316	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_BCNT,
317	    &val, 1);
318	if (error)
319		goto out;
320	val = (val & 0x1f) + 1;
321
322	bzero(ptr, len);
323	if (len > val)
324		len = val;
325
326	if (batt_sleep_ms)
327	    AcpiOsSleep(batt_sleep_ms);
328
329	while (len--) {
330		error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA
331		    + len, &val, 1);
332		if (error)
333			goto out;
334
335		ptr[len] = val;
336		if (batt_sleep_ms)
337		    AcpiOsSleep(batt_sleep_ms);
338	}
339
340out:
341	return (error);
342}
343
344static int
345acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst)
346{
347	struct acpi_smbat_softc *sc;
348	int error;
349	uint32_t cap_units, factor;
350	int16_t val;
351	uint8_t	addr;
352
353	ACPI_SERIAL_BEGIN(smbat);
354
355	addr = SMBATT_ADDRESS;
356	error = ENXIO;
357	sc = device_get_softc(dev);
358
359	if (!acpi_smbat_info_expired(&sc->bst_lastupdated)) {
360		error = 0;
361		goto out;
362	}
363
364	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val))
365		goto out;
366	if (val & SMBATT_BM_CAPACITY_MODE) {
367		factor = 10;
368		cap_units = ACPI_BIF_UNITS_MW;
369	} else {
370		factor = 1;
371		cap_units = ACPI_BIF_UNITS_MA;
372	}
373
374	/* get battery status */
375	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_STATUS, &val))
376		goto out;
377
378	sc->bst.state = 0;
379	if (val & SMBATT_BS_DISCHARGING)
380		sc->bst.state |= ACPI_BATT_STAT_DISCHARG;
381
382	if (val & SMBATT_BS_REMAINING_CAPACITY_ALARM)
383		sc->bst.state |= ACPI_BATT_STAT_CRITICAL;
384
385	/*
386	 * If the rate is negative, it is discharging.  Otherwise,
387	 * it is charging.
388	 */
389	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_CURRENT, &val))
390		goto out;
391
392	if (val > 0) {
393		sc->bst.rate = val * factor;
394		sc->bst.state &= ~SMBATT_BS_DISCHARGING;
395		sc->bst.state |= ACPI_BATT_STAT_CHARGING;
396	} else if (val < 0)
397		sc->bst.rate = (-val) * factor;
398	else
399		sc->bst.rate = 0;
400
401	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_REMAINING_CAPACITY, &val))
402		goto out;
403	sc->bst.cap = val * factor;
404
405	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_VOLTAGE, &val))
406		goto out;
407	sc->bst.volt = val;
408
409	acpi_smbat_info_updated(&sc->bst_lastupdated);
410	error = 0;
411
412out:
413	if (error == 0)
414		memcpy(bst, &sc->bst, sizeof(sc->bst));
415	ACPI_SERIAL_END(smbat);
416	return (error);
417}
418
419static int
420acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif)
421{
422	struct acpi_smbat_softc *sc;
423	int error;
424	uint32_t factor;
425	uint16_t val;
426	uint8_t addr;
427
428	ACPI_SERIAL_BEGIN(smbat);
429
430	addr = SMBATT_ADDRESS;
431	error = ENXIO;
432	sc = device_get_softc(dev);
433
434	if (!acpi_smbat_info_expired(&sc->bif_lastupdated)) {
435		error = 0;
436		goto out;
437	}
438
439	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val))
440		goto out;
441	if (val & SMBATT_BM_CAPACITY_MODE) {
442		factor = 10;
443		sc->bif.units = ACPI_BIF_UNITS_MW;
444	} else {
445		factor = 1;
446		sc->bif.units = ACPI_BIF_UNITS_MA;
447	}
448
449	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_DESIGN_CAPACITY, &val))
450		goto out;
451	sc->bif.dcap = val * factor;
452
453	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_FULL_CHARGE_CAPACITY, &val))
454		goto out;
455	sc->bif.lfcap = val * factor;
456	sc->bif.btech = 1;		/* secondary (rechargeable) */
457
458	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_DESIGN_VOLTAGE, &val))
459		goto out;
460	sc->bif.dvol = val;
461
462	sc->bif.wcap = sc->bif.dcap / 10;
463	sc->bif.lcap = sc->bif.dcap / 10;
464
465	sc->bif.gra1 = factor;	/* not supported */
466	sc->bif.gra2 = factor;	/* not supported */
467
468	if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_DEVICE_NAME,
469	    sc->bif.model, sizeof(sc->bif.model)))
470		goto out;
471
472	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_SERIAL_NUMBER, &val))
473		goto out;
474	snprintf(sc->bif.serial, sizeof(sc->bif.serial), "0x%04x", val);
475
476	if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_DEVICE_CHEMISTRY,
477	    sc->bif.type, sizeof(sc->bif.type)))
478		goto out;
479
480	if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_MANUFACTURER_DATA,
481	    sc->bif.oeminfo, sizeof(sc->bif.oeminfo)))
482		goto out;
483
484	/* XXX check if device was replugged during read? */
485
486	acpi_smbat_info_updated(&sc->bif_lastupdated);
487	error = 0;
488
489out:
490	if (error == 0)
491		memcpy(bif, &sc->bif, sizeof(sc->bif));
492	ACPI_SERIAL_END(smbat);
493	return (error);
494}
495