acpi_smbat.c revision 155869
1169695Skan/*-
2169695Skan * Copyright (c) 2005 Hans Petter Selasky
3169695Skan * All rights reserved.
4169695Skan *
5169695Skan * Redistribution and use in source and binary forms, with or without
6169695Skan * modification, are permitted provided that the following conditions
7169695Skan * are met:
8169695Skan * 1. Redistributions of source code must retain the above copyright
9169695Skan *    notice, this list of conditions and the following disclaimer.
10169695Skan * 2. Redistributions in binary form must reproduce the above copyright
11169695Skan *    notice, this list of conditions and the following disclaimer in the
12169695Skan *    documentation and/or other materials provided with the distribution.
13169695Skan *
14169695Skan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15169695Skan * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16169695Skan * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17169695Skan * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18169695Skan * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19169695Skan * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20169695Skan * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21169695Skan * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22169695Skan * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23169695Skan * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24169695Skan * SUCH DAMAGE.
25169695Skan */
26169695Skan
27169695Skan#include <sys/cdefs.h>
28169695Skan__FBSDID("$FreeBSD: head/sys/dev/acpica/acpi_smbat.c 155869 2006-02-21 03:16:58Z njl $");
29169695Skan
30169695Skan#include "opt_acpi.h"
31169695Skan#include <sys/param.h>
32169695Skan#include <sys/kernel.h>
33169695Skan#include <sys/module.h>
34169695Skan#include <sys/bus.h>
35169695Skan
36169695Skan#include <contrib/dev/acpica/acpi.h>
37169695Skan#include <dev/acpica/acpivar.h>
38169695Skan#include <dev/acpica/acpiio.h>
39169695Skan#include <dev/acpica/acpi_smbus.h>
40169695Skan
41169695Skan/* Transactions have failed after 500 ms. */
42169695Skan#define SMBUS_TIMEOUT	50
43169695Skan
44169695Skanstruct acpi_smbat_softc {
45169695Skan	uint8_t		sb_base_addr;
46169695Skan	device_t	ec_dev;
47169695Skan
48169695Skan	struct acpi_bif	bif;
49169695Skan	struct acpi_bst	bst;
50169695Skan	struct timespec	bif_lastupdated;
51169695Skan	struct timespec	bst_lastupdated;
52169695Skan};
53169695Skan
54169695Skanstatic int	acpi_smbat_probe(device_t dev);
55169695Skanstatic int	acpi_smbat_attach(device_t dev);
56169695Skanstatic int	acpi_smbat_shutdown(device_t dev);
57169695Skanstatic int	acpi_smbat_info_expired(struct timespec *lastupdated);
58169695Skanstatic void	acpi_smbat_info_updated(struct timespec *lastupdated);
59169695Skanstatic int	acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif);
60169695Skanstatic int	acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst);
61169695Skan
62169695SkanACPI_SERIAL_DECL(smbat, "ACPI Smart Battery");
63169695Skan
64169695Skanstatic device_method_t acpi_smbat_methods[] = {
65169695Skan	/* device interface */
66169695Skan	DEVMETHOD(device_probe, acpi_smbat_probe),
67169695Skan	DEVMETHOD(device_attach, acpi_smbat_attach),
68169695Skan	DEVMETHOD(device_shutdown, acpi_smbat_shutdown),
69169695Skan
70169695Skan	/* ACPI battery interface */
71169695Skan	DEVMETHOD(acpi_batt_get_status, acpi_smbat_get_bst),
72169695Skan	DEVMETHOD(acpi_batt_get_info, acpi_smbat_get_bif),
73169695Skan
74169695Skan	{0, 0}
75169695Skan};
76169695Skan
77169695Skanstatic driver_t	acpi_smbat_driver = {
78169695Skan	"battery",
79169695Skan	acpi_smbat_methods,
80169695Skan	sizeof(struct acpi_smbat_softc),
81169695Skan};
82169695Skan
83169695Skanstatic devclass_t acpi_smbat_devclass;
84169695SkanDRIVER_MODULE(acpi_smbat, acpi, acpi_smbat_driver, acpi_smbat_devclass, 0, 0);
85169695SkanMODULE_DEPEND(acpi_smbat, acpi, 1, 1, 1);
86169695Skan
87169695Skanstatic int
88169695Skanacpi_smbat_probe(device_t dev)
89169695Skan{
90169695Skan	static char *smbat_ids[] = {"ACPI0001", "ACPI0005", NULL};
91169695Skan	ACPI_STATUS status;
92169695Skan
93169695Skan	if (acpi_disabled("smbat") ||
94169695Skan	    ACPI_ID_PROBE(device_get_parent(dev), dev, smbat_ids) == NULL)
95169695Skan		return (ENXIO);
96169695Skan	status = AcpiEvaluateObject(acpi_get_handle(dev), "_EC", NULL, NULL);
97169695Skan	if (ACPI_FAILURE(status))
98169695Skan		return (ENXIO);
99169695Skan
100169695Skan	device_set_desc(dev, "ACPI Smart Battery");
101169695Skan	return (0);
102169695Skan}
103169695Skan
104169695Skanstatic int
105169695Skanacpi_smbat_attach(device_t dev)
106169695Skan{
107169695Skan	struct acpi_smbat_softc *sc;
108169695Skan	uint32_t base;
109169695Skan
110169695Skan	sc = device_get_softc(dev);
111169695Skan	if (ACPI_FAILURE(acpi_GetInteger(acpi_get_handle(dev), "_EC", &base))) {
112169695Skan		device_printf(dev, "cannot get EC base address\n");
113169695Skan		return (ENXIO);
114169695Skan	}
115169695Skan	sc->sb_base_addr = (base >> 8) & 0xff;
116169695Skan
117169695Skan	/* XXX Only works with one EC, but nearly all systems only have one. */
118169695Skan	sc->ec_dev = devclass_get_device(devclass_find("acpi_ec"), 0);
119169695Skan	if (sc->ec_dev == NULL) {
120169695Skan		device_printf(dev, "cannot find EC device\n");
121169695Skan		return (ENXIO);
122169695Skan	}
123169695Skan
124169695Skan	timespecclear(&sc->bif_lastupdated);
125169695Skan	timespecclear(&sc->bst_lastupdated);
126169695Skan
127169695Skan	if (acpi_battery_register(dev) != 0) {
128169695Skan		device_printf(dev, "cannot register battery\n");
129169695Skan		return (ENXIO);
130169695Skan	}
131169695Skan	return (0);
132169695Skan}
133169695Skan
134169695Skanstatic int
135169695Skanacpi_smbat_shutdown(device_t dev)
136169695Skan{
137169695Skan
138169695Skan	acpi_battery_remove(dev);
139169695Skan	return (0);
140169695Skan}
141169695Skan
142169695Skanstatic int
143169695Skanacpi_smbat_info_expired(struct timespec *lastupdated)
144169695Skan{
145169695Skan	struct timespec	curtime;
146169695Skan
147169695Skan	ACPI_SERIAL_ASSERT(smbat);
148169695Skan
149169695Skan	if (lastupdated == NULL)
150169695Skan		return (TRUE);
151169695Skan	if (!timespecisset(lastupdated))
152169695Skan		return (TRUE);
153169695Skan
154169695Skan	getnanotime(&curtime);
155169695Skan	timespecsub(&curtime, lastupdated);
156169695Skan	return (curtime.tv_sec < 0 ||
157169695Skan	    curtime.tv_sec > acpi_battery_get_info_expire());
158169695Skan}
159169695Skan
160169695Skanstatic void
161169695Skanacpi_smbat_info_updated(struct timespec *lastupdated)
162169695Skan{
163169695Skan
164169695Skan	ACPI_SERIAL_ASSERT(smbat);
165169695Skan
166169695Skan	if (lastupdated != NULL)
167169695Skan		getnanotime(lastupdated);
168169695Skan}
169169695Skan
170169695Skanstatic int
171169695Skanacpi_smbus_read_2(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd,
172169695Skan    uint16_t *ptr)
173169695Skan{
174169695Skan	int error, to;
175169695Skan	ACPI_INTEGER val;
176169695Skan
177169695Skan	ACPI_SERIAL_ASSERT(smbat);
178169695Skan
179169695Skan	val = addr;
180169695Skan	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR,
181169695Skan	    val, 1);
182169695Skan	if (error)
183169695Skan		goto out;
184169695Skan
185169695Skan	val = cmd;
186169695Skan	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD,
187169695Skan	    val, 1);
188169695Skan	if (error)
189169695Skan		goto out;
190169695Skan
191169695Skan	val = 0x09; /* | 0x80 if PEC */
192169695Skan	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
193169695Skan	    val, 1);
194169695Skan	if (error)
195169695Skan		goto out;
196169695Skan
197169695Skan	for (to = SMBUS_TIMEOUT; to != 0; to--) {
198169695Skan		error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
199169695Skan		    &val, 1);
200169695Skan		if (error)
201169695Skan			goto out;
202169695Skan		if (val == 0)
203169695Skan			break;
204169695Skan		AcpiOsSleep(10);
205169695Skan	}
206169695Skan	if (to == 0) {
207169695Skan		error = ETIMEDOUT;
208169695Skan		goto out;
209169695Skan	}
210169695Skan
211169695Skan	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1);
212169695Skan	if (error)
213169695Skan		goto out;
214169695Skan	if (val & SMBUS_STS_MASK) {
215169695Skan		printf("%s: AE_ERROR 0x%x\n",
216169695Skan		       __FUNCTION__, (int)(val & SMBUS_STS_MASK));
217169695Skan		error = EIO;
218169695Skan		goto out;
219169695Skan	}
220169695Skan
221169695Skan	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA,
222169695Skan	    &val, 2);
223169695Skan	if (error)
224169695Skan		goto out;
225169695Skan
226169695Skan	*ptr = val;
227169695Skan
228169695Skanout:
229169695Skan	return (error);
230169695Skan}
231169695Skan
232169695Skanstatic int
233169695Skanacpi_smbus_read_multi_1(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd,
234169695Skan    uint8_t *ptr, uint16_t len)
235169695Skan{
236169695Skan	ACPI_INTEGER val;
237169695Skan	uint8_t	to;
238169695Skan	int error;
239169695Skan
240169695Skan	ACPI_SERIAL_ASSERT(smbat);
241169695Skan
242169695Skan	val = addr;
243169695Skan	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR,
244169695Skan	    val, 1);
245169695Skan	if (error)
246169695Skan		goto out;
247169695Skan
248169695Skan	val = cmd;
249169695Skan	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD,
250169695Skan	    val, 1);
251169695Skan	if (error)
252169695Skan		goto out;
253169695Skan
254169695Skan	val = 0x0B /* | 0x80 if PEC */ ;
255169695Skan	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
256169695Skan	    val, 1);
257169695Skan	if (error)
258169695Skan		goto out;
259169695Skan
260169695Skan	for (to = SMBUS_TIMEOUT; to != 0; to--) {
261169695Skan		error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
262169695Skan		    &val, 1);
263169695Skan		if (error)
264169695Skan			goto out;
265169695Skan		if (val == 0)
266169695Skan			break;
267169695Skan		AcpiOsSleep(10);
268169695Skan	}
269169695Skan	if (to == 0) {
270169695Skan		error = ETIMEDOUT;
271169695Skan		goto out;
272169695Skan	}
273169695Skan
274169695Skan	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1);
275169695Skan	if (error)
276169695Skan		goto out;
277169695Skan	if (val & SMBUS_STS_MASK) {
278169695Skan		printf("%s: AE_ERROR 0x%x\n",
279169695Skan		       __FUNCTION__, (int)(val & SMBUS_STS_MASK));
280169695Skan		error = EIO;
281169695Skan		goto out;
282169695Skan	}
283169695Skan
284169695Skan	/* get length */
285169695Skan	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_BCNT,
286169695Skan	    &val, 1);
287169695Skan	if (error)
288169695Skan		goto out;
289169695Skan	val = (val & 0x1f) + 1;
290169695Skan
291169695Skan	bzero(ptr, len);
292169695Skan	if (len > val)
293169695Skan		len = val;
294169695Skan
295169695Skan	while (len--) {
296169695Skan		error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA
297169695Skan		    + len, &val, 1);
298169695Skan		if (error)
299169695Skan			goto out;
300169695Skan
301169695Skan		ptr[len] = val;
302169695Skan	}
303169695Skan
304169695Skanout:
305169695Skan	return (error);
306169695Skan}
307169695Skan
308169695Skanstatic int
309169695Skanacpi_smbat_get_bst(device_t dev, struct acpi_bst *bst)
310169695Skan{
311169695Skan	struct acpi_smbat_softc *sc;
312169695Skan	int error;
313169695Skan	uint32_t cap_units, factor;
314169695Skan	int16_t val;
315169695Skan	uint8_t	addr;
316169695Skan
317169695Skan	ACPI_SERIAL_BEGIN(smbat);
318169695Skan
319169695Skan	addr = SMBATT_ADDRESS;
320169695Skan	error = ENXIO;
321169695Skan	sc = device_get_softc(dev);
322169695Skan
323169695Skan	if (!acpi_smbat_info_expired(&sc->bst_lastupdated)) {
324169695Skan		error = 0;
325169695Skan		goto out;
326169695Skan	}
327169695Skan
328169695Skan	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val))
329169695Skan		goto out;
330169695Skan	if (val & SMBATT_BM_CAPACITY_MODE) {
331169695Skan		factor = 10;
332169695Skan		cap_units = ACPI_BIF_UNITS_MW;
333169695Skan	} else {
334169695Skan		factor = 1;
335169695Skan		cap_units = ACPI_BIF_UNITS_MA;
336169695Skan	}
337169695Skan
338169695Skan	/* get battery status */
339169695Skan	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_STATUS, &val))
340169695Skan		goto out;
341169695Skan
342169695Skan	sc->bst.state = 0;
343169695Skan	if (val & SMBATT_BS_DISCHARGING)
344169695Skan		sc->bst.state |= ACPI_BATT_STAT_DISCHARG;
345169695Skan
346169695Skan	if (val & SMBATT_BS_REMAINING_CAPACITY_ALARM)
347169695Skan		sc->bst.state |= ACPI_BATT_STAT_CRITICAL;
348169695Skan
349169695Skan	/*
350169695Skan	 * If the rate is negative, it is discharging.  Otherwise,
351169695Skan	 * it is charging.
352169695Skan	 */
353169695Skan	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_CURRENT, &val))
354169695Skan		goto out;
355169695Skan
356169695Skan	if (val > 0) {
357169695Skan		sc->bst.rate = val * factor;
358169695Skan		sc->bst.state |= ACPI_BATT_STAT_CHARGING;
359169695Skan	} else if (val < 0)
360169695Skan		sc->bst.rate = (-val) * factor;
361169695Skan	else
362169695Skan		sc->bst.rate = 0;
363169695Skan
364169695Skan	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_REMAINING_CAPACITY, &val))
365169695Skan		goto out;
366169695Skan	sc->bst.cap = val * factor;
367169695Skan
368169695Skan	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_VOLTAGE, &val))
369169695Skan		goto out;
370169695Skan	sc->bst.volt = val;
371169695Skan
372169695Skan	acpi_smbat_info_updated(&sc->bst_lastupdated);
373169695Skan	error = 0;
374169695Skan
375169695Skanout:
376169695Skan	if (error == 0)
377169695Skan		memcpy(bst, &sc->bst, sizeof(sc->bst));
378169695Skan	ACPI_SERIAL_END(smbat);
379169695Skan	return (error);
380169695Skan}
381169695Skan
382169695Skanstatic int
383169695Skanacpi_smbat_get_bif(device_t dev, struct acpi_bif *bif)
384169695Skan{
385169695Skan	struct acpi_smbat_softc *sc;
386169695Skan	int error;
387169695Skan	uint32_t factor;
388169695Skan	uint16_t val;
389169695Skan	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
452out:
453	if (error == 0)
454		memcpy(bif, &sc->bif, sizeof(sc->bif));
455	ACPI_SERIAL_END(smbat);
456	return (error);
457}
458