Module Name: src Committed By: christos Date: Sat Apr 27 14:50:18 UTC 2024
Modified Files: src/sys/dev/acpi: thinkpad_acpi.c Log Message: Expose a sysctl interface hw.acpi.thinkpad<M>.bat[<N>].<behavior> to control some aspects of battery charging behavior on supported systems: charge_start threshold below which to start charging (in %, 0-99) charge_stop threshold above which to stop charging (in %, 1-100) force_discharge discharge while on AC power, e.g., for calibration charge_inhibit inhibit charging while on AC power >From Malte Dehling To generate a diff of this commit: cvs rdiff -u -r1.56 -r1.57 src/sys/dev/acpi/thinkpad_acpi.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/dev/acpi/thinkpad_acpi.c diff -u src/sys/dev/acpi/thinkpad_acpi.c:1.56 src/sys/dev/acpi/thinkpad_acpi.c:1.57 --- src/sys/dev/acpi/thinkpad_acpi.c:1.56 Sat Apr 27 10:45:11 2024 +++ src/sys/dev/acpi/thinkpad_acpi.c Sat Apr 27 10:50:18 2024 @@ -1,4 +1,4 @@ -/* $NetBSD: thinkpad_acpi.c,v 1.56 2024/04/27 14:45:11 christos Exp $ */ +/* $NetBSD: thinkpad_acpi.c,v 1.57 2024/04/27 14:50:18 christos Exp $ */ /*- * Copyright (c) 2007 Jared D. McNeill <jmcne...@invisible.ca> @@ -27,13 +27,14 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: thinkpad_acpi.c,v 1.56 2024/04/27 14:45:11 christos Exp $"); +__KERNEL_RCSID(0, "$NetBSD: thinkpad_acpi.c,v 1.57 2024/04/27 14:50:18 christos Exp $"); #include <sys/param.h> #include <sys/device.h> #include <sys/module.h> #include <sys/sdt.h> #include <sys/systm.h> +#include <sys/sysctl.h> #include <dev/acpi/acpireg.h> #include <dev/acpi/acpivar.h> @@ -49,10 +50,27 @@ ACPI_MODULE_NAME ("thinkpad_acpi") #define THINKPAD_NFANSENSORS 1 #define THINKPAD_NSENSORS (THINKPAD_NTEMPSENSORS + THINKPAD_NFANSENSORS) +typedef struct tp_sysctl_param { + device_t sp_dev; + int sp_bat; +} tp_sysctl_param_t; + +typedef union tp_batctl { + int have_any; + struct { + int charge_start:1; + int charge_stop:1; + int charge_inhibit:1; + int force_discharge:1; + int individual_control:1; + } have; +} tp_batctl_t; + typedef struct thinkpad_softc { device_t sc_dev; device_t sc_ecdev; struct acpi_devnode *sc_node; + struct sysctllog *sc_log; ACPI_HANDLE sc_powhdl; ACPI_HANDLE sc_cmoshdl; ACPI_INTEGER sc_ver; @@ -90,6 +108,14 @@ typedef struct thinkpad_softc { envsys_data_t sc_sensor[THINKPAD_NSENSORS]; int sc_display_state; + +#define THINKPAD_BAT_ANY 0 +#define THINKPAD_BAT_PRIMARY 1 +#define THINKPAD_BAT_SECONDARY 2 +#define THINKPAD_BAT_LAST 3 + + tp_batctl_t sc_batctl; + tp_sysctl_param_t sc_scparam[THINKPAD_BAT_LAST]; } thinkpad_softc_t; /* Hotkey events */ @@ -130,6 +156,17 @@ typedef struct thinkpad_softc { #define THINKPAD_DISPLAY_ALL \ (THINKPAD_DISPLAY_LCD | THINKPAD_DISPLAY_CRT | THINKPAD_DISPLAY_DVI) +#define THINKPAD_GET_CHARGE_START "BCTG" +#define THINKPAD_SET_CHARGE_START "BCCS" +#define THINKPAD_GET_CHARGE_STOP "BCSG" +#define THINKPAD_SET_CHARGE_STOP "BCSS" +#define THINKPAD_GET_FORCE_DISCHARGE "BDSG" +#define THINKPAD_SET_FORCE_DISCHARGE "BDSS" +#define THINKPAD_GET_CHARGE_INHIBIT "BICG" +#define THINKPAD_SET_CHARGE_INHIBIT "BICS" + +#define THINKPAD_CALL_ERROR 0x80000000 + #define THINKPAD_BLUETOOTH_HWPRESENT 0x01 #define THINKPAD_BLUETOOTH_RADIOSSW 0x02 #define THINKPAD_BLUETOOTH_RESUMECTRL 0x04 @@ -168,6 +205,9 @@ static void thinkpad_brightness_down(dev static uint8_t thinkpad_brightness_read(thinkpad_softc_t *); static void thinkpad_cmos(thinkpad_softc_t *, uint8_t); +static void thinkpad_battery_probe_support(device_t); +static void thinkpad_battery_sysctl_setup(device_t); + CFATTACH_DECL3_NEW(thinkpad, sizeof(thinkpad_softc_t), thinkpad_match, thinkpad_attach, thinkpad_detach, NULL, NULL, NULL, 0); @@ -220,6 +260,7 @@ thinkpad_attach(device_t parent, device_ int i; sc->sc_dev = self; + sc->sc_log = NULL; sc->sc_powhdl = NULL; sc->sc_cmoshdl = NULL; sc->sc_node = aa->aa_node; @@ -371,6 +412,17 @@ thinkpad_attach(device_t parent, device_ /* Register temperature and fan sensors with envsys */ thinkpad_sensors_init(sc); + /* Probe supported battery charge/control operations */ + thinkpad_battery_probe_support(self); + + if (sc->sc_batctl.have_any) { + for (i = 0; i < THINKPAD_BAT_LAST; i++) { + sc->sc_scparam[i].sp_dev = self; + sc->sc_scparam[i].sp_bat = i; + } + thinkpad_battery_sysctl_setup(self); + } + fail: if (!pmf_device_register(self, NULL, thinkpad_resume)) aprint_error_dev(self, "couldn't establish power handler\n"); @@ -396,6 +448,9 @@ thinkpad_detach(device_t self, int flags if (sc->sc_sme != NULL) sysmon_envsys_unregister(sc->sc_sme); + if (sc->sc_log != NULL) + sysctl_teardown(&sc->sc_log); + pmf_device_deregister(self); pmf_event_deregister(self, PMFE_DISPLAY_BRIGHTNESS_UP, @@ -948,6 +1003,290 @@ thinkpad_cmos(thinkpad_softc_t *sc, uint AcpiFormatException(rv)); } +static uint32_t +thinkpad_call_method(device_t self, const char *path, uint32_t arg) +{ + thinkpad_softc_t *sc = device_private(self); + ACPI_HANDLE handle = sc->sc_node->ad_handle; + ACPI_OBJECT args[1]; + ACPI_OBJECT_LIST arg_list; + ACPI_OBJECT rval; + ACPI_BUFFER buf; + ACPI_STATUS rv; + + args[0].Type = ACPI_TYPE_INTEGER; + args[0].Integer.Value = arg; + arg_list.Pointer = &args[0]; + arg_list.Count = __arraycount(args); + + memset(&rval, 0, sizeof rval); + buf.Pointer = &rval; + buf.Length = sizeof rval; + + rv = AcpiEvaluateObjectTyped(handle, path, &arg_list, &buf, + ACPI_TYPE_INTEGER); + + if (ACPI_FAILURE(rv)) { + aprint_error_dev(self, "call %s.%s(%x) failed: %s\n", + acpi_name(handle), path, (unsigned)arg, + AcpiFormatException(rv)); + return THINKPAD_CALL_ERROR; + } + + return rval.Integer.Value; +} + +static void +thinkpad_battery_probe_support(device_t self) +{ + thinkpad_softc_t *sc = device_private(self); + ACPI_HANDLE hdl = sc->sc_node->ad_handle, tmp; + ACPI_STATUS rv; + uint32_t val; + + sc->sc_batctl.have_any = 0; + + rv = AcpiGetHandle(hdl, THINKPAD_GET_CHARGE_START, &tmp); + if (ACPI_SUCCESS(rv)) { + val = thinkpad_call_method(self, THINKPAD_GET_CHARGE_START, + THINKPAD_BAT_PRIMARY); + if (!(val & THINKPAD_CALL_ERROR) && (val & 0x100)) { + sc->sc_batctl.have.charge_start = 1; + if (val & 0x200) + sc->sc_batctl.have.individual_control = 1; + } + } + + rv = AcpiGetHandle(hdl, THINKPAD_GET_CHARGE_STOP, &tmp); + if (ACPI_SUCCESS(rv)) { + val = thinkpad_call_method(self, THINKPAD_GET_CHARGE_STOP, + THINKPAD_BAT_PRIMARY); + if (!(val & THINKPAD_CALL_ERROR) && (val & 0x100)) + sc->sc_batctl.have.charge_stop = 1; + } + + rv = AcpiGetHandle(hdl, THINKPAD_GET_FORCE_DISCHARGE, &tmp); + if (ACPI_SUCCESS(rv)) { + val = thinkpad_call_method(self, THINKPAD_GET_FORCE_DISCHARGE, + THINKPAD_BAT_PRIMARY); + if (!(val & THINKPAD_CALL_ERROR) && (val & 0x100)) + sc->sc_batctl.have.force_discharge = 1; + } + + rv = AcpiGetHandle(hdl, THINKPAD_GET_CHARGE_INHIBIT, &tmp); + if (ACPI_SUCCESS(rv)) { + val = thinkpad_call_method(self, THINKPAD_GET_CHARGE_INHIBIT, + THINKPAD_BAT_PRIMARY); + if (!(val & THINKPAD_CALL_ERROR) && (val & 0x20)) + sc->sc_batctl.have.charge_inhibit = 1; + } + + if (sc->sc_batctl.have_any) + aprint_verbose_dev(self, "battery control capabilities: %x\n", + sc->sc_batctl.have_any); +} + +static int +thinkpad_battery_sysctl_charge_start(SYSCTLFN_ARGS) +{ + struct sysctlnode node = *rnode; + tp_sysctl_param_t *sp = node.sysctl_data; + int charge_start; + int err; + + charge_start = thinkpad_call_method(sp->sp_dev, + THINKPAD_GET_CHARGE_START, sp->sp_bat) & 0xff; + + node.sysctl_data = &charge_start; + err = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (err || newp == NULL) + return err; + + if (charge_start < 0 || charge_start > 99) + return EINVAL; + + if (thinkpad_call_method(sp->sp_dev, THINKPAD_SET_CHARGE_START, + charge_start | sp->sp_bat<<8) & THINKPAD_CALL_ERROR) + return EIO; + + return 0; +} + +static int +thinkpad_battery_sysctl_charge_stop(SYSCTLFN_ARGS) +{ + struct sysctlnode node = *rnode; + tp_sysctl_param_t *sp = node.sysctl_data; + int charge_stop; + int err; + + charge_stop = thinkpad_call_method(sp->sp_dev, + THINKPAD_GET_CHARGE_STOP, sp->sp_bat) & 0xff; + + if (charge_stop == 0) + charge_stop = 100; + + node.sysctl_data = &charge_stop; + err = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (err || newp == NULL) + return err; + + if (charge_stop < 1 || charge_stop > 100) + return EINVAL; + + if (charge_stop == 100) + charge_stop = 0; + + if (thinkpad_call_method(sp->sp_dev, THINKPAD_SET_CHARGE_STOP, + charge_stop | sp->sp_bat<<8) & THINKPAD_CALL_ERROR) + return EIO; + + return 0; +} + +static int +thinkpad_battery_sysctl_charge_inhibit(SYSCTLFN_ARGS) +{ + struct sysctlnode node = *rnode; + tp_sysctl_param_t *sp = node.sysctl_data; + bool charge_inhibit; + int err; + + charge_inhibit = thinkpad_call_method(sp->sp_dev, + THINKPAD_GET_CHARGE_INHIBIT, sp->sp_bat) & 0x01; + + node.sysctl_data = &charge_inhibit; + err = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (err || newp == NULL) + return err; + + if (thinkpad_call_method(sp->sp_dev, THINKPAD_SET_CHARGE_INHIBIT, + charge_inhibit | sp->sp_bat<<4 | 0xffff<<8) & THINKPAD_CALL_ERROR) + return EIO; + + return 0; +} + +static int +thinkpad_battery_sysctl_force_discharge(SYSCTLFN_ARGS) +{ + struct sysctlnode node = *rnode; + tp_sysctl_param_t *sp = node.sysctl_data; + bool force_discharge; + int err; + + force_discharge = thinkpad_call_method(sp->sp_dev, + THINKPAD_GET_FORCE_DISCHARGE, sp->sp_bat) & 0x01; + + node.sysctl_data = &force_discharge; + err = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (err || newp == NULL) + return err; + + if (thinkpad_call_method(sp->sp_dev, THINKPAD_SET_FORCE_DISCHARGE, + force_discharge | sp->sp_bat<<8) & THINKPAD_CALL_ERROR) + return EIO; + + return 0; +} + +static void +thinkpad_battery_sysctl_setup_controls(device_t self, + const struct sysctlnode *rnode, int battery) +{ + thinkpad_softc_t *sc = device_private(self); + + if (sc->sc_batctl.have.charge_start) + (void)sysctl_createv(&sc->sc_log, 0, &rnode, NULL, + CTLFLAG_READWRITE, CTLTYPE_INT, "charge_start", + SYSCTL_DESCR("charge start threshold (0-99)"), + thinkpad_battery_sysctl_charge_start, 0, + (void *)&(sc->sc_scparam[battery]), 0, + CTL_CREATE, CTL_EOL); + + if (sc->sc_batctl.have.charge_stop) + (void)sysctl_createv(&sc->sc_log, 0, &rnode, NULL, + CTLFLAG_READWRITE, CTLTYPE_INT, "charge_stop", + SYSCTL_DESCR("charge stop threshold (1-100)"), + thinkpad_battery_sysctl_charge_stop, 0, + (void *)&(sc->sc_scparam[battery]), 0, + CTL_CREATE, CTL_EOL); + + if (sc->sc_batctl.have.charge_inhibit) + (void)sysctl_createv(&sc->sc_log, 0, &rnode, NULL, + CTLFLAG_READWRITE, CTLTYPE_BOOL, "charge_inhibit", + SYSCTL_DESCR("charge inhibit"), + thinkpad_battery_sysctl_charge_inhibit, 0, + (void *)&(sc->sc_scparam[battery]), 0, + CTL_CREATE, CTL_EOL); + + if (sc->sc_batctl.have.force_discharge) + (void)sysctl_createv(&sc->sc_log, 0, &rnode, NULL, + CTLFLAG_READWRITE, CTLTYPE_BOOL, "force_discharge", + SYSCTL_DESCR("force discharge"), + thinkpad_battery_sysctl_force_discharge, 0, + (void *)&(sc->sc_scparam[battery]), 0, + CTL_CREATE, CTL_EOL); +} + +static void +thinkpad_battery_sysctl_setup(device_t self) +{ + thinkpad_softc_t *sc = device_private(self); + const struct sysctlnode *rnode, *cnode; + int err; + + err = sysctl_createv(&sc->sc_log, 0, NULL, &rnode, + 0, CTLTYPE_NODE, "acpi", NULL, + NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL); + if (err) + goto fail; + + err = sysctl_createv(&sc->sc_log, 0, &rnode, &rnode, + 0, CTLTYPE_NODE, device_xname(self), + SYSCTL_DESCR("ThinkPad ACPI controls"), + NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); + if (err) + goto fail; + + if (sc->sc_batctl.have.individual_control) { + err = sysctl_createv(&sc->sc_log, 0, &rnode, &cnode, + 0, CTLTYPE_NODE, "bat0", + SYSCTL_DESCR("battery charge controls (primary battery)"), + NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); + if (err) + goto fail; + + thinkpad_battery_sysctl_setup_controls(self, cnode, + THINKPAD_BAT_PRIMARY); + + err = sysctl_createv(&sc->sc_log, 0, &rnode, &cnode, + 0, CTLTYPE_NODE, "bat1", + SYSCTL_DESCR("battery charge controls (secondary battery)"), + NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); + if (err) + goto fail; + + thinkpad_battery_sysctl_setup_controls(self, cnode, + THINKPAD_BAT_SECONDARY); + } else { + err = sysctl_createv(&sc->sc_log, 0, &rnode, &cnode, + 0, CTLTYPE_NODE, "bat", + SYSCTL_DESCR("battery charge controls"), + NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); + if (err) + goto fail; + + thinkpad_battery_sysctl_setup_controls(self, cnode, + THINKPAD_BAT_ANY); + } + + return; + +fail: + aprint_error_dev(self, "unable to add sysctl nodes (%d)\n", err); +} + static bool thinkpad_resume(device_t dv, const pmf_qual_t *qual) {