Current-Users archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

ThinkPad battery charge control



Hi,

I've added some things to the thinkpad acpi driver to allow modifying
battery charging behavior: keep charge within a range, force discharge,
disable charging on AC.  I think something like this would be good to
have in NetBSD, but not necessarily how I implemented it.  So this is
mainly a request for discussion :)

I've attached patches for reference and in case anyone wants to use them
-- tested on my old X230 and W530 but based on my reading and studying
acpidumps the same should work on most newer models.

On Linux and FreeBSD people just make acpi calls from userspace it
seems, usually hidden away in tools like TLP.

OpenBSD recently added sysctls hw.battery.{chargestart,chargestop,
chargemode} to provide this functionality (supported currently only by
the thinkpad and apple silicon drivers.)  The approach is simple, but
not as flexible as I would like:
- It doesn't let you control charging behavior of individual batteries.
- There are reasons to want to control charge_inhibit and
  force_discharge modes separately.

Finally, a question: The ACPI standard has for a long time had
(optional) _BMD & _BMC methods to control charging of control method
batteries.  Has anyone seen these implemented in the wild?

Cheers,
-- 
Malte Dehling
From 43e122db83d0fdd55817477446b46892b080c4da Mon Sep 17 00:00:00 2001
From: Malte Dehling <mdehling%gmail.com@localhost>
Date: Tue, 23 Apr 2024 15:48:25 -0700
Subject: [PATCH 1/2] thinkpad(4): cosmetic changes

---
 sys/dev/acpi/thinkpad_acpi.c | 53 ++++++++++++++++++------------------
 1 file changed, 27 insertions(+), 26 deletions(-)

diff --git a/sys/dev/acpi/thinkpad_acpi.c b/sys/dev/acpi/thinkpad_acpi.c
index 395bbb65c03..0ef5e9d4653 100644
--- a/sys/dev/acpi/thinkpad_acpi.c
+++ b/sys/dev/acpi/thinkpad_acpi.c
@@ -138,8 +138,8 @@ typedef struct thinkpad_softc {
 #define THINKPAD_WWAN_RADIOSSW		0x02
 #define THINKPAD_WWAN_RESUMECTRL	0x04
 
-#define THINKPAD_UWB_HWPRESENT	0x01
-#define THINKPAD_UWB_RADIOSSW	0x02
+#define THINKPAD_UWB_HWPRESENT		0x01
+#define THINKPAD_UWB_RADIOSSW		0x02
 
 #define THINKPAD_RFK_BLUETOOTH		0
 #define THINKPAD_RFK_WWAN		1
@@ -165,7 +165,7 @@ static void	thinkpad_bluetooth_toggle(thinkpad_softc_t *);
 static bool	thinkpad_resume(device_t, const pmf_qual_t *);
 static void	thinkpad_brightness_up(device_t);
 static void	thinkpad_brightness_down(device_t);
-static uint8_t	thinkpad_brightness_read(thinkpad_softc_t *sc);
+static uint8_t	thinkpad_brightness_read(thinkpad_softc_t *);
 static void	thinkpad_cmos(thinkpad_softc_t *, uint8_t);
 
 CFATTACH_DECL3_NEW(thinkpad, sizeof(thinkpad_softc_t),
@@ -230,7 +230,7 @@ thinkpad_attach(device_t parent, device_t self, void *opaque)
 
 	sc->sc_ecdev = NULL;
 	for (curdev = deviter_first(&di, DEVITER_F_ROOT_FIRST);
-	     curdev != NULL; curdev = deviter_next(&di))
+	    curdev != NULL; curdev = deviter_next(&di))
 		if (device_is_a(curdev, "acpiecdt") ||
 		    device_is_a(curdev, "acpiec")) {
 			sc->sc_ecdev = curdev;
@@ -330,29 +330,30 @@ thinkpad_attach(device_t parent, device_t self, void *opaque)
 #endif
 	for (i = TP_PSW_DISPLAY_CYCLE; i < TP_PSW_LAST; i++)
 		sc->sc_smpsw[i].smpsw_type = PSWITCH_TYPE_HOTKEY;
-	psw[TP_PSW_DISPLAY_CYCLE].smpsw_name = PSWITCH_HK_DISPLAY_CYCLE;
-	psw[TP_PSW_LOCK_SCREEN].smpsw_name = PSWITCH_HK_LOCK_SCREEN;
-	psw[TP_PSW_BATTERY_INFO].smpsw_name = PSWITCH_HK_BATTERY_INFO;
-	psw[TP_PSW_EJECT_BUTTON].smpsw_name = PSWITCH_HK_EJECT_BUTTON;
-	psw[TP_PSW_ZOOM_BUTTON].smpsw_name = PSWITCH_HK_ZOOM_BUTTON;
-	psw[TP_PSW_VENDOR_BUTTON].smpsw_name = PSWITCH_HK_VENDOR_BUTTON;
+
+	psw[TP_PSW_DISPLAY_CYCLE].smpsw_name	= PSWITCH_HK_DISPLAY_CYCLE;
+	psw[TP_PSW_LOCK_SCREEN].smpsw_name	= PSWITCH_HK_LOCK_SCREEN;
+	psw[TP_PSW_BATTERY_INFO].smpsw_name	= PSWITCH_HK_BATTERY_INFO;
+	psw[TP_PSW_EJECT_BUTTON].smpsw_name	= PSWITCH_HK_EJECT_BUTTON;
+	psw[TP_PSW_ZOOM_BUTTON].smpsw_name	= PSWITCH_HK_ZOOM_BUTTON;
+	psw[TP_PSW_VENDOR_BUTTON].smpsw_name	= PSWITCH_HK_VENDOR_BUTTON;
 #ifndef THINKPAD_NORMAL_HOTKEYS
-	psw[TP_PSW_FNF1_BUTTON].smpsw_name     = PSWITCH_HK_FNF1_BUTTON;
-	psw[TP_PSW_WIRELESS_BUTTON].smpsw_name = PSWITCH_HK_WIRELESS_BUTTON;
-	psw[TP_PSW_WWAN_BUTTON].smpsw_name     = PSWITCH_HK_WWAN_BUTTON;
-	psw[TP_PSW_POINTER_BUTTON].smpsw_name  = PSWITCH_HK_POINTER_BUTTON;
-	psw[TP_PSW_FNF10_BUTTON].smpsw_name    = PSWITCH_HK_FNF10_BUTTON;
-	psw[TP_PSW_FNF11_BUTTON].smpsw_name    = PSWITCH_HK_FNF11_BUTTON;
-	psw[TP_PSW_BRIGHTNESS_UP].smpsw_name   = PSWITCH_HK_BRIGHTNESS_UP;
-	psw[TP_PSW_BRIGHTNESS_DOWN].smpsw_name = PSWITCH_HK_BRIGHTNESS_DOWN;
-	psw[TP_PSW_THINKLIGHT].smpsw_name      = PSWITCH_HK_THINKLIGHT;
-	psw[TP_PSW_VOLUME_UP].smpsw_name       = PSWITCH_HK_VOLUME_UP;
-	psw[TP_PSW_VOLUME_DOWN].smpsw_name     = PSWITCH_HK_VOLUME_DOWN;
-	psw[TP_PSW_VOLUME_MUTE].smpsw_name     = PSWITCH_HK_VOLUME_MUTE;
-	psw[TP_PSW_STAR_BUTTON].smpsw_name     = PSWITCH_HK_STAR_BUTTON;
-	psw[TP_PSW_SCISSORS_BUTTON].smpsw_name = PSWITCH_HK_SCISSORS_BUTTON;
-	psw[TP_PSW_BLUETOOTH_BUTTON].smpsw_name = PSWITCH_HK_BLUETOOTH_BUTTON;
-	psw[TP_PSW_KEYBOARD_BUTTON].smpsw_name = PSWITCH_HK_KEYBOARD_BUTTON;
+	psw[TP_PSW_FNF1_BUTTON].smpsw_name	= PSWITCH_HK_FNF1_BUTTON;
+	psw[TP_PSW_WIRELESS_BUTTON].smpsw_name	= PSWITCH_HK_WIRELESS_BUTTON;
+	psw[TP_PSW_WWAN_BUTTON].smpsw_name	= PSWITCH_HK_WWAN_BUTTON;
+	psw[TP_PSW_POINTER_BUTTON].smpsw_name	= PSWITCH_HK_POINTER_BUTTON;
+	psw[TP_PSW_FNF10_BUTTON].smpsw_name	= PSWITCH_HK_FNF10_BUTTON;
+	psw[TP_PSW_FNF11_BUTTON].smpsw_name	= PSWITCH_HK_FNF11_BUTTON;
+	psw[TP_PSW_BRIGHTNESS_UP].smpsw_name	= PSWITCH_HK_BRIGHTNESS_UP;
+	psw[TP_PSW_BRIGHTNESS_DOWN].smpsw_name	= PSWITCH_HK_BRIGHTNESS_DOWN;
+	psw[TP_PSW_THINKLIGHT].smpsw_name	= PSWITCH_HK_THINKLIGHT;
+	psw[TP_PSW_VOLUME_UP].smpsw_name	= PSWITCH_HK_VOLUME_UP;
+	psw[TP_PSW_VOLUME_DOWN].smpsw_name	= PSWITCH_HK_VOLUME_DOWN;
+	psw[TP_PSW_VOLUME_MUTE].smpsw_name	= PSWITCH_HK_VOLUME_MUTE;
+	psw[TP_PSW_STAR_BUTTON].smpsw_name	= PSWITCH_HK_STAR_BUTTON;
+	psw[TP_PSW_SCISSORS_BUTTON].smpsw_name	= PSWITCH_HK_SCISSORS_BUTTON;
+	psw[TP_PSW_BLUETOOTH_BUTTON].smpsw_name	= PSWITCH_HK_BLUETOOTH_BUTTON;
+	psw[TP_PSW_KEYBOARD_BUTTON].smpsw_name	= PSWITCH_HK_KEYBOARD_BUTTON;
 #endif /* THINKPAD_NORMAL_HOTKEYS */
 
 	for (i = 0; i < TP_PSW_LAST; i++) {
-- 
2.44.0

From 3b633ff78fb5f3779544bc4943884cb58d19e621 Mon Sep 17 00:00:00 2001
From: Malte Dehling <mdehling%gmail.com@localhost>
Date: Tue, 23 Apr 2024 15:51:02 -0700
Subject: [PATCH 2/2] thinkpad(4): Add battery charge control

Exposes a sysctl interface hw.acpi.thinkpad<M>.bat[<N>] 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
---
 sys/dev/acpi/thinkpad_acpi.c | 339 +++++++++++++++++++++++++++++++++++
 1 file changed, 339 insertions(+)

diff --git a/sys/dev/acpi/thinkpad_acpi.c b/sys/dev/acpi/thinkpad_acpi.c
index 0ef5e9d4653..4643c902caf 100644
--- a/sys/dev/acpi/thinkpad_acpi.c
+++ b/sys/dev/acpi/thinkpad_acpi.c
@@ -34,6 +34,7 @@ __KERNEL_RCSID(0, "$NetBSD: thinkpad_acpi.c,v 1.55 2022/08/12 16:21:41 riastradh
 #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(device_t);
 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_t self, void *opaque)
 	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_t self, void *opaque)
 	/* 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, uint8_t cmd)
 		    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)
 {
-- 
2.44.0

Attachment: signature.asc
Description: PGP signature



Home | Main Index | Thread Index | Old Index