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