From: Antheas Kapenekakis <lkml@antheas.dev>
To: platform-driver-x86@vger.kernel.org
Cc: linux-hwmon@vger.kernel.org, linux-doc@vger.kernel.org,
linux-pm@vger.kernel.org, Guenter Roeck <linux@roeck-us.net>,
Jean Delvare <jdelvare@suse.com>,
Jonathan Corbet <corbet@lwn.net>,
Joaquin Ignacio Aramendia <samsagax@gmail.com>,
Derek J Clark <derekjohn.clark@gmail.com>,
Kevin Greenberg <kdgreenberg234@protonmail.com>,
Joshua Tam <csinaction@pm.me>,
Parth Menon <parthasarathymenon@gmail.com>,
Eileen <eileen@one-netbook.com>,
Antheas Kapenekakis <lkml@antheas.dev>
Subject: [PATCH v3 06/12] platform/x86: oxpec: Add charge threshold and behaviour to OneXPlayer
Date: Sun, 9 Mar 2025 12:21:07 +0100 [thread overview]
Message-ID: <20250309112114.1177361-7-lkml@antheas.dev> (raw)
In-Reply-To: <20250309112114.1177361-1-lkml@antheas.dev>
With the X1 (AMD), OneXPlayer added a charge limit and charge bypass to
their devices. Charge limit allows for choosing an arbitrary battery
charge setpoint in percentages. Charge bypass allows to instruct the
device to stop charging either when it is in s0 or always.
This feature was then extended for the F1Pro as well. OneXPlayer also
released BIOS updates for the X1 Mini, X1 (Intel), and F1 devices that
add support for this feature. Therefore, enable it for all F1 and
X1 devices.
Add both of these under the standard sysfs battery endpoints for them,
by looking for the battery. OneXPlayer devices have a single battery.
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/oxpec.c | 217 +++++++++++++++++++++++++++++++++++
1 file changed, 217 insertions(+)
diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c
index dc3a0871809c..dd6d333ebcfa 100644
--- a/drivers/platform/x86/oxpec.c
+++ b/drivers/platform/x86/oxpec.c
@@ -24,6 +24,7 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/processor.h>
+#include <acpi/battery.h>
/* Handle ACPI lock mechanism */
static u32 oxp_mutex;
@@ -87,6 +88,24 @@ static enum oxp_board board;
#define OXP_TURBO_RETURN_VAL 0x00 /* Common return val */
+/* Battery bypass settings */
+#define EC_CHARGE_CONTROL_BEHAVIOURS_X1 (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) | \
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_S0))
+
+#define OXP_X1_CHARGE_LIMIT_REG 0xA3 /* X1 charge limit (%) */
+#define OXP_X1_CHARGE_BYPASS_REG 0xA4 /* X1 bypass charging */
+
+#define OXP_X1_CHARGE_BYPASS_MASK_S0 0x01
+/*
+ * Cannot control S3, S5 individually.
+ * X1 Mask is 0x0A, OneXFly F1Pro is just 0x02
+ * but the extra bit on the X1 does nothing.
+ */
+#define OXP_X1_CHARGE_BYPASS_MASK_S3S5 0x02
+#define OXP_X1_CHARGE_BYPASS_MASK_ALWAYS (OXP_X1_CHARGE_BYPASS_MASK_S0 | \
+ OXP_X1_CHARGE_BYPASS_MASK_S3S5)
+
static const struct dmi_system_id dmi_table[] = {
{
.matches = {
@@ -434,6 +453,194 @@ static ssize_t tt_toggle_show(struct device *dev,
static DEVICE_ATTR_RW(tt_toggle);
+/* Callbacks for turbo toggle attribute */
+static bool charge_behaviour_supported(void)
+{
+ switch (board) {
+ case oxp_x1:
+ case oxp_fly:
+ return 1;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static ssize_t charge_behaviour_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 reg;
+ long val, s0, always;
+ unsigned int available;
+
+ switch (board) {
+ case oxp_x1:
+ case oxp_fly:
+ s0 = OXP_X1_CHARGE_BYPASS_MASK_S0;
+ always = OXP_X1_CHARGE_BYPASS_MASK_ALWAYS;
+ reg = OXP_X1_CHARGE_BYPASS_REG;
+ available = EC_CHARGE_CONTROL_BEHAVIOURS_X1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = power_supply_charge_behaviour_parse(available, buf);
+ if (ret < 0)
+ return ret;
+
+ switch (ret) {
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+ val = 0;
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_S0:
+ val = s0;
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+ val = always;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = write_to_ec(reg, val);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t charge_behaviour_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ u8 reg;
+ long val, s0, always, sel;
+ unsigned int available;
+
+ switch (board) {
+ case oxp_x1:
+ case oxp_fly:
+ s0 = OXP_X1_CHARGE_BYPASS_MASK_S0;
+ always = OXP_X1_CHARGE_BYPASS_MASK_ALWAYS;
+ reg = OXP_X1_CHARGE_BYPASS_REG;
+ available = EC_CHARGE_CONTROL_BEHAVIOURS_X1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = read_from_ec(reg, 1, &val);
+ if (ret < 0)
+ return ret;
+
+ if ((val & always) == always)
+ sel = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ else if ((val & s0) == s0)
+ sel = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_S0;
+ else
+ sel = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+
+ return power_supply_charge_behaviour_show(dev, available, sel, buf);
+}
+
+static DEVICE_ATTR_RW(charge_behaviour);
+
+static ssize_t charge_control_end_threshold_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ u64 val, reg;
+ int ret;
+
+ ret = kstrtou64(buf, 10, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val > 100)
+ return -EINVAL;
+
+ switch (board) {
+ case oxp_x1:
+ case oxp_fly:
+ reg = OXP_X1_CHARGE_LIMIT_REG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = write_to_ec(reg, val);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t charge_control_end_threshold_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ u8 reg;
+ long val;
+
+ switch (board) {
+ case oxp_x1:
+ case oxp_fly:
+ reg = OXP_X1_CHARGE_LIMIT_REG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = read_from_ec(reg, 1, &val);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%ld\n", val);
+}
+
+static DEVICE_ATTR_RW(charge_control_end_threshold);
+
+static int oxp_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ /* OneXPlayer devices only have one battery. */
+ if (strcmp(battery->desc->name, "BAT0") != 0 &&
+ strcmp(battery->desc->name, "BAT1") != 0 &&
+ strcmp(battery->desc->name, "BATC") != 0 &&
+ strcmp(battery->desc->name, "BATT") != 0)
+ return -ENODEV;
+
+ if (device_create_file(&battery->dev,
+ &dev_attr_charge_control_end_threshold))
+ return -ENODEV;
+
+ if (device_create_file(&battery->dev,
+ &dev_attr_charge_behaviour)) {
+ device_remove_file(&battery->dev,
+ &dev_attr_charge_control_end_threshold);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int oxp_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ device_remove_file(&battery->dev,
+ &dev_attr_charge_control_end_threshold);
+ device_remove_file(&battery->dev,
+ &dev_attr_charge_behaviour);
+ return 0;
+}
+
+static struct acpi_battery_hook battery_hook = {
+ .add_battery = oxp_battery_add,
+ .remove_battery = oxp_battery_remove,
+ .name = "OneXPlayer Battery",
+};
+
/* PWM enable/disable functions */
static int oxp_pwm_enable(void)
{
@@ -716,15 +923,25 @@ static int oxp_platform_probe(struct platform_device *pdev)
hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", NULL,
&oxp_ec_chip_info, NULL);
+ if (charge_behaviour_supported())
+ battery_hook_register(&battery_hook);
+
return PTR_ERR_OR_ZERO(hwdev);
}
+static void oxp_platform_remove(struct platform_device *device)
+{
+ if (charge_behaviour_supported())
+ battery_hook_unregister(&battery_hook);
+}
+
static struct platform_driver oxp_platform_driver = {
.driver = {
.name = "oxp-platform",
.dev_groups = oxp_ec_groups,
},
.probe = oxp_platform_probe,
+ .remove = oxp_platform_remove,
};
static struct platform_device *oxp_platform_device;
--
2.48.1
next prev parent reply other threads:[~2025-03-09 11:21 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-09 11:21 [PATCH v3 00/12] hwmon: (oxpsensors) Add devices, features, fix ABI and move to platform/x86 Antheas Kapenekakis
2025-03-09 11:21 ` [PATCH v3 01/12] hwmon: (oxp-sensors) Distinguish the X1 variants Antheas Kapenekakis
2025-03-09 15:24 ` Guenter Roeck
2025-03-10 23:04 ` Derek John Clark
2025-03-09 11:21 ` [PATCH v3 02/12] hwmon: (oxp-sensors) Add all OneXFly variants Antheas Kapenekakis
2025-03-09 15:24 ` Guenter Roeck
2025-03-10 23:03 ` Derek John Clark
2025-03-10 23:19 ` Antheas Kapenekakis
2025-03-09 11:21 ` [PATCH v3 03/12] platform/x86: oxpec: Move hwmon/oxp-sensors to platform/x86 Antheas Kapenekakis
2025-03-10 23:17 ` Derek John Clark
2025-03-10 23:30 ` Antheas Kapenekakis
2025-03-10 23:39 ` Guenter Roeck
2025-03-09 11:21 ` [PATCH v3 04/12] ABI: testing: add tt_toggle and tt_led entries Antheas Kapenekakis
2025-03-10 23:25 ` Derek John Clark
2025-03-10 23:37 ` Antheas Kapenekakis
2025-03-09 11:21 ` [PATCH v3 05/12] power: supply: add inhibit-charge-s0 to charge_behaviour Antheas Kapenekakis
2025-03-10 23:30 ` Derek John Clark
2025-03-09 11:21 ` Antheas Kapenekakis [this message]
2025-03-10 23:50 ` [PATCH v3 06/12] platform/x86: oxpec: Add charge threshold and behaviour to OneXPlayer Derek John Clark
2025-03-11 0:02 ` Antheas Kapenekakis
2025-03-11 14:09 ` kernel test robot
2025-03-09 11:21 ` [PATCH v3 07/12] platform/x86: oxpec: Rename ec group to tt_toggle Antheas Kapenekakis
2025-03-10 23:51 ` Derek John Clark
2025-03-09 11:21 ` [PATCH v3 08/12] platform/x86: oxpec: Add turbo led support to X1 devices Antheas Kapenekakis
2025-03-10 23:57 ` Derek John Clark
2025-03-09 11:21 ` [PATCH v3 09/12] platform/x86: oxpec: Move pwm_enable read to its own function Antheas Kapenekakis
2025-03-10 23:26 ` Derek John Clark
2025-03-09 11:21 ` [PATCH v3 10/12] platform/x86: oxpec: Move pwm value read/write to separate functions Antheas Kapenekakis
2025-03-10 23:58 ` Derek John Clark
2025-03-09 11:21 ` [PATCH v3 11/12] platform/x86: oxpec: Move fan speed read to separate function Antheas Kapenekakis
2025-03-10 23:58 ` Derek John Clark
2025-03-09 11:21 ` [PATCH v3 12/12] platform/x86: oxpec: Adhere to sysfs-class-hwmon and enable pwm on 2 Antheas Kapenekakis
2025-03-11 0:02 ` Derek John Clark
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250309112114.1177361-7-lkml@antheas.dev \
--to=lkml@antheas.dev \
--cc=corbet@lwn.net \
--cc=csinaction@pm.me \
--cc=derekjohn.clark@gmail.com \
--cc=eileen@one-netbook.com \
--cc=jdelvare@suse.com \
--cc=kdgreenberg234@protonmail.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-hwmon@vger.kernel.org \
--cc=linux-pm@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=parthasarathymenon@gmail.com \
--cc=platform-driver-x86@vger.kernel.org \
--cc=samsagax@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).