acpi_smbat.c revision 151564
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 151564 2005-10-23 00:20:13Z njl $");
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
44struct acpi_smbat_softc {
45	uint8_t		sb_base_addr;
46	device_t	ec_dev;
47};
48
49static int	acpi_smbat_probe(device_t dev);
50static int	acpi_smbat_attach(device_t dev);
51static int	acpi_smbat_shutdown(device_t dev);
52static int	acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif);
53static int	acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst);
54
55ACPI_SERIAL_DECL(smbat, "ACPI Smart Battery");
56
57static device_method_t acpi_smbat_methods[] = {
58	/* device interface */
59	DEVMETHOD(device_probe, acpi_smbat_probe),
60	DEVMETHOD(device_attach, acpi_smbat_attach),
61	DEVMETHOD(device_shutdown, acpi_smbat_shutdown),
62
63	/* ACPI battery interface */
64	DEVMETHOD(acpi_batt_get_status, acpi_smbat_get_bst),
65	DEVMETHOD(acpi_batt_get_info, acpi_smbat_get_bif),
66
67	{0, 0}
68};
69
70static driver_t	acpi_smbat_driver = {
71	"battery",
72	acpi_smbat_methods,
73	sizeof(struct acpi_smbat_softc),
74};
75
76static devclass_t acpi_smbat_devclass;
77DRIVER_MODULE(acpi_smbat, acpi, acpi_smbat_driver, acpi_smbat_devclass, 0, 0);
78MODULE_DEPEND(acpi_smbat, acpi, 1, 1, 1);
79
80static int
81acpi_smbat_probe(device_t dev)
82{
83	static char *smbat_ids[] = {"ACPI0001", "ACPI0005", NULL};
84	ACPI_STATUS status;
85
86	if (acpi_disabled("smbat") ||
87	    ACPI_ID_PROBE(device_get_parent(dev), dev, smbat_ids) == NULL)
88		return (ENXIO);
89	status = AcpiEvaluateObject(acpi_get_handle(dev), "_EC", NULL, NULL);
90	if (ACPI_FAILURE(status))
91		return (ENXIO);
92
93	device_set_desc(dev, "ACPI Smart Battery");
94	return (0);
95}
96
97static int
98acpi_smbat_attach(device_t dev)
99{
100	struct acpi_smbat_softc *sc;
101	uint32_t base;
102
103	sc = device_get_softc(dev);
104	if (ACPI_FAILURE(acpi_GetInteger(acpi_get_handle(dev), "_EC", &base))) {
105		device_printf(dev, "cannot get EC base address\n");
106		return (ENXIO);
107	}
108	sc->sb_base_addr = (base >> 8) & 0xff;
109
110	/* XXX Only works with one EC, but nearly all systems only have one. */
111	sc->ec_dev = devclass_get_device(devclass_find("acpi_ec"), 0);
112	if (sc->ec_dev == NULL) {
113		device_printf(dev, "cannot find EC device\n");
114		return (ENXIO);
115	}
116
117	if (acpi_battery_register(dev) != 0) {
118		device_printf(dev, "cannot register battery\n");
119		return (ENXIO);
120	}
121	return (0);
122}
123
124static int
125acpi_smbat_shutdown(device_t dev)
126{
127	struct acpi_smbat_softc *sc;
128
129	sc = device_get_softc(dev);
130	acpi_battery_remove(dev);
131	return (0);
132}
133
134static int
135acpi_smbus_read_2(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd,
136    uint16_t *ptr)
137{
138	int error, to;
139	ACPI_INTEGER val;
140
141	ACPI_SERIAL_ASSERT(smbat);
142
143	val = addr;
144	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR,
145	    val, 1);
146	if (error)
147		goto out;
148
149	val = cmd;
150	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD,
151	    val, 1);
152	if (error)
153		goto out;
154
155	val = 0x09; /* | 0x80 if PEC */
156	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
157	    val, 1);
158	if (error)
159		goto out;
160
161	for (to = SMBUS_TIMEOUT; to != 0; to--) {
162		error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
163		    &val, 1);
164		if (error)
165			goto out;
166		if (val == 0)
167			break;
168		AcpiOsSleep(10);
169	}
170	if (to == 0) {
171		error = ETIMEDOUT;
172		goto out;
173	}
174
175	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1);
176	if (error)
177		goto out;
178	if (val & SMBUS_STS_MASK) {
179		printf("%s: AE_ERROR 0x%x\n",
180		       __FUNCTION__, (int)(val & SMBUS_STS_MASK));
181		error = EIO;
182		goto out;
183	}
184
185	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA,
186	    &val, 2);
187	if (error)
188		goto out;
189
190	*ptr = val;
191
192out:
193	return (error);
194}
195
196static int
197acpi_smbus_read_multi_1(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd,
198    uint8_t *ptr, uint16_t len)
199{
200	ACPI_INTEGER val;
201	uint8_t	to;
202	int error;
203
204	ACPI_SERIAL_ASSERT(smbat);
205
206	val = addr;
207	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR,
208	    val, 1);
209	if (error)
210		goto out;
211
212	val = cmd;
213	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD,
214	    val, 1);
215	if (error)
216		goto out;
217
218	val = 0x0B /* | 0x80 if PEC */ ;
219	error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
220	    val, 1);
221	if (error)
222		goto out;
223
224	for (to = SMBUS_TIMEOUT; to != 0; to--) {
225		error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL,
226		    &val, 1);
227		if (error)
228			goto out;
229		if (val == 0)
230			break;
231		AcpiOsSleep(10);
232	}
233	if (to == 0) {
234		error = ETIMEDOUT;
235		goto out;
236	}
237
238	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1);
239	if (error)
240		goto out;
241	if (val & SMBUS_STS_MASK) {
242		printf("%s: AE_ERROR 0x%x\n",
243		       __FUNCTION__, (int)(val & SMBUS_STS_MASK));
244		error = EIO;
245		goto out;
246	}
247
248	/* get length */
249	error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_BCNT,
250	    &val, 1);
251	if (error)
252		goto out;
253	val = (val & 0x1f) + 1;
254
255	bzero(ptr, len);
256	if (len > val)
257		len = val;
258
259	while (len--) {
260		error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA
261		    + len, &val, 1);
262		if (error)
263			goto out;
264
265		ptr[len] = val;
266	}
267
268out:
269	return (error);
270}
271
272static int
273acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst)
274{
275	struct acpi_smbat_softc *sc;
276	int error;
277	uint32_t cap_units, factor;
278	int16_t val;
279	uint8_t	addr;
280
281	ACPI_SERIAL_BEGIN(smbat);
282
283	addr = SMBATT_ADDRESS;
284	error = ENXIO;
285	sc = device_get_softc(dev);
286
287	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val))
288		goto out;
289	if (val & SMBATT_BM_CAPACITY_MODE) {
290		factor = 10;
291		cap_units = ACPI_BIF_UNITS_MW;
292	} else {
293		factor = 1;
294		cap_units = ACPI_BIF_UNITS_MA;
295	}
296
297	/* get battery status */
298	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_STATUS, &val))
299		goto out;
300
301	if (val & SMBATT_BS_DISCHARGING) {
302		bst->state |= ACPI_BATT_STAT_DISCHARG;
303
304		/*
305		 * If the rate is negative, it is discharging.  Otherwise,
306		 * it is charging.
307		 */
308		if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_AT_RATE, &val))
309			goto out;
310		if (val < 0)
311			bst->rate = (-val) * factor;
312		else
313			bst->rate = -1;
314	} else {
315		bst->state |= ACPI_BATT_STAT_CHARGING;
316		bst->rate = -1;
317	}
318
319	if (val & SMBATT_BS_REMAINING_CAPACITY_ALARM)
320		bst->state |= ACPI_BATT_STAT_CRITICAL;
321
322	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_REMAINING_CAPACITY, &val))
323		goto out;
324	bst->cap = val * factor;
325
326	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_VOLTAGE, &val))
327		goto out;
328	bst->volt = val;
329	error = 0;
330
331out:
332	ACPI_SERIAL_END(smbat);
333	return (error);
334}
335
336static int
337acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif)
338{
339	struct acpi_smbat_softc *sc;
340	int error;
341	uint32_t factor;
342	uint16_t val;
343	uint8_t addr;
344
345	ACPI_SERIAL_BEGIN(smbat);
346
347	addr = SMBATT_ADDRESS;
348	error = ENXIO;
349	sc = device_get_softc(dev);
350
351	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val))
352		goto out;
353	if (val & SMBATT_BM_CAPACITY_MODE) {
354		factor = 10;
355		bif->units = ACPI_BIF_UNITS_MW;
356	} else {
357		factor = 1;
358		bif->units = ACPI_BIF_UNITS_MA;
359	}
360
361	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_DESIGN_CAPACITY, &val))
362		goto out;
363	bif->dcap = val * factor;
364
365	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_FULL_CHARGE_CAPACITY, &val))
366		goto out;
367	bif->lfcap = val * factor;
368	bif->btech = 1;		/* secondary (rechargeable) */
369
370	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_DESIGN_VOLTAGE, &val))
371		goto out;
372	bif->dvol = val;
373
374	bif->wcap = bif->dcap / 10;
375	bif->lcap = bif->dcap / 10;
376
377	bif->gra1 = factor;	/* not supported */
378	bif->gra2 = factor;	/* not supported */
379
380	if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_DEVICE_NAME,
381	    bif->model, sizeof(bif->model)))
382		goto out;
383
384	if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_SERIAL_NUMBER, &val))
385		goto out;
386	snprintf(bif->serial, sizeof(bif->serial), "0x%04x", val);
387
388	if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_DEVICE_CHEMISTRY,
389	    bif->type, sizeof(bif->type)))
390		goto out;
391
392	if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_MANUFACTURER_DATA,
393	    bif->oeminfo, sizeof(bif->oeminfo)))
394		goto out;
395
396	/* XXX check if device was replugged during read? */
397	error = 0;
398
399out:
400	ACPI_SERIAL_END(smbat);
401	return (error);
402}
403