Commit 14c8a145 authored by Szilard Fabian's avatar Szilard Fabian Committed by Ilpo Järvinen

platform/x86/fujitsu-laptop: Add battery charge control support

This patch adds battery charge control support on Fujitsu notebooks
via the S006 method of the FUJ02E3 ACPI device. With this method it's
possible to set charge_control_end_threshold between 50 and 100%.

Tested on Lifebook E5411 and Lifebook U728. Sadly I can't test this
patch on a dual battery one, but I didn't find any clue about
independent battery charge control on dual battery Fujitsu notebooks
either. And by that I mean checking the DSDT table of various Lifebook
notebooks and reverse engineering FUJ02E3.dll.
Signed-off-by: default avatarSzilard Fabian <szfabian@bluemarch.art>
Acked-by: default avatarJonathan Woithe <jwoithe@just42.net>
Link: https://lore.kernel.org/r/20240215203012.228758-2-szfabian@bluemarch.art
[ij: coding style cleanups.]
Reviewed-by: default avatarIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: default avatarIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
parent 301c1904
...@@ -370,6 +370,7 @@ config FUJITSU_LAPTOP ...@@ -370,6 +370,7 @@ config FUJITSU_LAPTOP
depends on ACPI depends on ACPI
depends on INPUT depends on INPUT
depends on BACKLIGHT_CLASS_DEVICE depends on BACKLIGHT_CLASS_DEVICE
depends on ACPI_BATTERY
depends on ACPI_VIDEO || ACPI_VIDEO = n depends on ACPI_VIDEO || ACPI_VIDEO = n
select INPUT_SPARSEKMAP select INPUT_SPARSEKMAP
select NEW_LEDS select NEW_LEDS
......
...@@ -49,6 +49,8 @@ ...@@ -49,6 +49,8 @@
#include <linux/kfifo.h> #include <linux/kfifo.h>
#include <linux/leds.h> #include <linux/leds.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <acpi/battery.h>
#include <acpi/video.h> #include <acpi/video.h>
#define FUJITSU_DRIVER_VERSION "0.6.0" #define FUJITSU_DRIVER_VERSION "0.6.0"
...@@ -97,6 +99,10 @@ ...@@ -97,6 +99,10 @@
#define BACKLIGHT_OFF (BIT(0) | BIT(1)) #define BACKLIGHT_OFF (BIT(0) | BIT(1))
#define BACKLIGHT_ON 0 #define BACKLIGHT_ON 0
/* FUNC interface - battery control interface */
#define FUNC_S006_METHOD 0x1006
#define CHARGE_CONTROL_RW 0x21
/* Scancodes read from the GIRB register */ /* Scancodes read from the GIRB register */
#define KEY1_CODE 0x410 #define KEY1_CODE 0x410
#define KEY2_CODE 0x411 #define KEY2_CODE 0x411
...@@ -132,6 +138,7 @@ struct fujitsu_laptop { ...@@ -132,6 +138,7 @@ struct fujitsu_laptop {
spinlock_t fifo_lock; spinlock_t fifo_lock;
int flags_supported; int flags_supported;
int flags_state; int flags_state;
bool charge_control_supported;
}; };
static struct acpi_device *fext; static struct acpi_device *fext;
...@@ -164,6 +171,110 @@ static int call_fext_func(struct acpi_device *device, ...@@ -164,6 +171,110 @@ static int call_fext_func(struct acpi_device *device,
return value; return value;
} }
/* Battery charge control code */
static ssize_t charge_control_end_threshold_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int cc_end_value, s006_cc_return;
int value, ret;
ret = kstrtouint(buf, 10, &value);
if (ret)
return ret;
if (value < 50 || value > 100)
return -EINVAL;
cc_end_value = value * 0x100 + 0x20;
s006_cc_return = call_fext_func(fext, FUNC_S006_METHOD,
CHARGE_CONTROL_RW, cc_end_value, 0x0);
if (s006_cc_return < 0)
return s006_cc_return;
/*
* The S006 0x21 method returns 0x00 in case the provided value
* is invalid.
*/
if (s006_cc_return == 0x00)
return -EINVAL;
return count;
}
static ssize_t charge_control_end_threshold_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int status;
status = call_fext_func(fext, FUNC_S006_METHOD,
CHARGE_CONTROL_RW, 0x21, 0x0);
if (status < 0)
return status;
return sysfs_emit(buf, "%d\n", status);
}
static DEVICE_ATTR_RW(charge_control_end_threshold);
/* ACPI battery hook */
static int fujitsu_battery_add_hook(struct power_supply *battery,
struct acpi_battery_hook *hook)
{
return device_create_file(&battery->dev,
&dev_attr_charge_control_end_threshold);
}
static int fujitsu_battery_remove_hook(struct power_supply *battery,
struct acpi_battery_hook *hook)
{
device_remove_file(&battery->dev,
&dev_attr_charge_control_end_threshold);
return 0;
}
static struct acpi_battery_hook battery_hook = {
.add_battery = fujitsu_battery_add_hook,
.remove_battery = fujitsu_battery_remove_hook,
.name = "Fujitsu Battery Extension",
};
/*
* These functions are intended to be called from acpi_fujitsu_laptop_add and
* acpi_fujitsu_laptop_remove.
*/
static int fujitsu_battery_charge_control_add(struct acpi_device *device)
{
struct fujitsu_laptop *priv = acpi_driver_data(device);
int s006_cc_return;
priv->charge_control_supported = false;
/*
* Check if the S006 0x21 method exists by trying to get the current
* battery charge limit.
*/
s006_cc_return = call_fext_func(fext, FUNC_S006_METHOD,
CHARGE_CONTROL_RW, 0x21, 0x0);
if (s006_cc_return < 0)
return s006_cc_return;
if (s006_cc_return == UNSUPPORTED_CMD)
return -ENODEV;
priv->charge_control_supported = true;
battery_hook_register(&battery_hook);
return 0;
}
static void fujitsu_battery_charge_control_remove(struct acpi_device *device)
{
struct fujitsu_laptop *priv = acpi_driver_data(device);
if (priv->charge_control_supported)
battery_hook_unregister(&battery_hook);
}
/* Hardware access for LCD brightness control */ /* Hardware access for LCD brightness control */
static int set_lcd_level(struct acpi_device *device, int level) static int set_lcd_level(struct acpi_device *device, int level)
...@@ -839,6 +950,10 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device) ...@@ -839,6 +950,10 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device)
if (ret) if (ret)
goto err_free_fifo; goto err_free_fifo;
ret = fujitsu_battery_charge_control_add(device);
if (ret < 0)
pr_warn("Unable to register battery charge control: %d\n", ret);
return 0; return 0;
err_free_fifo: err_free_fifo:
...@@ -851,6 +966,8 @@ static void acpi_fujitsu_laptop_remove(struct acpi_device *device) ...@@ -851,6 +966,8 @@ static void acpi_fujitsu_laptop_remove(struct acpi_device *device)
{ {
struct fujitsu_laptop *priv = acpi_driver_data(device); struct fujitsu_laptop *priv = acpi_driver_data(device);
fujitsu_battery_charge_control_remove(device);
fujitsu_laptop_platform_remove(device); fujitsu_laptop_platform_remove(device);
kfifo_free(&priv->fifo); kfifo_free(&priv->fifo);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment