Add support for battery charge thresholds in new Sandy Bridge and Ivy Bridge
ThinkPads. Based on the unofficial documentation in tpacpi-bat.

The threshold files support the values '0' for the controller's default,
and 1-99 as percentages. Values outside of that range are rejected. The
behaviour of '0' might be confusing, especially for the stop case where
it basically seems to mean '100'.

Signed-off-by: Julian Andres Klode <j...@jak-linux.org>
---
 Documentation/laptops/thinkpad-acpi.txt |  17 ++++
 drivers/platform/x86/thinkpad_acpi.c    | 173 ++++++++++++++++++++++++++++++++
 2 files changed, 190 insertions(+)

diff --git a/Documentation/laptops/thinkpad-acpi.txt 
b/Documentation/laptops/thinkpad-acpi.txt
index 86c5236..97b150a 100644
--- a/Documentation/laptops/thinkpad-acpi.txt
+++ b/Documentation/laptops/thinkpad-acpi.txt
@@ -46,6 +46,7 @@ detailed description):
        - Fan control and monitoring: fan speed, fan enable/disable
        - WAN enable and disable
        - UWB enable and disable
+       - Charging control
 
 A compatibility table by model and feature is maintained on the web
 site, http://ibm-acpi.sf.net/. I appreciate any success or failure
@@ -1352,6 +1353,22 @@ Sysfs notes:
        rfkill controller switch "tpacpi_uwb_sw": refer to
        Documentation/rfkill.txt for details.
 
+Charging control
+----------------
+sysfs attribute groups: BAT0, BAT1, and so on, depending on how many batteries
+are supported by the embedded controller.
+
+This feature controls the battery charging process.
+
+battery sysfs attribute: start_charge_thresh
+
+       A percentage value from 1-99%, controlling when charging should start, 
or
+       0 for the default value.
+
+battery sysfs attribute: stop_charge_thresh
+
+       A percentage value from 1-99%, controlling when charging should stop, or
+       0 for the default value.
 
 Multiple Commands, Module Parameters
 ------------------------------------
diff --git a/drivers/platform/x86/thinkpad_acpi.c 
b/drivers/platform/x86/thinkpad_acpi.c
index 03ca6c1..66e629e 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -323,6 +323,7 @@ static struct {
        u32 sensors_pdrv_attrs_registered:1;
        u32 sensors_pdev_attrs_registered:1;
        u32 hotkey_poll_active:1;
+       u32 battery:1;
 } tp_features;
 
 static struct {
@@ -8350,6 +8351,174 @@ static struct ibm_struct fan_driver_data = {
        .resume = fan_resume,
 };
 
+
+/*************************************************************************
+ * Battery subdriver
+ */
+
+/* Modify battery_init() if you modify them */
+#define BATTERY_MAX_COUNT 3
+#define BATTERY_MAX_ATTRS 2
+
+static struct battery {
+       char name[3 + 1 + 1];
+       struct attribute_set *set;
+       struct dev_ext_attribute attributes[BATTERY_MAX_ATTRS];
+} batteries[BATTERY_MAX_COUNT];
+
+static int battery_attribute_get_battery(struct device_attribute *attr)
+{
+       return (int) (unsigned long) container_of(attr,
+                                                 struct dev_ext_attribute,
+                                                 attr)->var;
+}
+
+static ssize_t battery_start_charge_thresh_store(struct device *dev,
+                                                struct device_attribute *attr,
+                                                const char *buf, size_t count)
+{
+       int bat = battery_attribute_get_battery(attr);
+       int res = -EINVAL;
+       unsigned long value;
+
+       res = kstrtoul(buf, 0, &value);
+       if (res || value > 99)
+               return res ? res : -EINVAL;
+
+       if (!hkey_handle || !acpi_evalf(hkey_handle, &res, "BCCS", "dd",
+                                       (int) value | (bat << 8)) || res < 0)
+               return -EIO;
+       return count;
+}
+
+static ssize_t battery_stop_charge_thresh_store(struct device *dev,
+                                               struct device_attribute *attr,
+                                               const char *buf, size_t count)
+{
+       int bat = battery_attribute_get_battery(attr);
+       int res = -EINVAL;
+       unsigned long value;
+
+       res = kstrtoul(buf, 0, &value);
+       if (res || value > 99)
+               return res ? res : -EINVAL;
+
+       if (!hkey_handle || !acpi_evalf(hkey_handle, &res, "BCSS", "dd",
+                                       (int) value | (bat << 8)) || res < 0)
+               return -EIO;
+       return count;
+}
+
+static ssize_t battery_start_charge_thresh_show(struct device *dev,
+                                               struct device_attribute *attr,
+                                               char *buf)
+{
+       int bat = battery_attribute_get_battery(attr);
+       int value;
+
+       if (!hkey_handle || !acpi_evalf(hkey_handle, &value, "BCTG",
+                                       "dd", bat))
+               return -EIO;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", value & 0xFF);
+}
+
+static ssize_t battery_stop_charge_thresh_show(struct device *dev,
+                                              struct device_attribute *attr,
+                                              char *buf)
+{
+       int bat = battery_attribute_get_battery(attr);
+       int value;
+
+       if (!hkey_handle || !acpi_evalf(hkey_handle, &value, "BCSG",
+                                       "dd", bat))
+               return -EIO;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", value & 0xFF);
+}
+
+static int __init battery_init(struct ibm_init_struct *iibm)
+{
+       int res;
+       int i;
+       int state;
+
+       vdbg_printk(TPACPI_DBG_INIT,
+                   "initializing battery commands subdriver\n");
+
+       TPACPI_ACPIHANDLE_INIT(hkey);
+
+       /* Check whether getter for start threshold exists */
+       tp_features.battery = hkey_handle &&
+           acpi_evalf(hkey_handle, &state, "BCTG", "qdd", 1);
+
+       vdbg_printk(TPACPI_DBG_INIT, "battery commands are %s\n",
+                   str_supported(tp_features.battery));
+
+       if (!tp_features.battery)
+               return 1;
+
+       for (i = 0; i < BATTERY_MAX_COUNT; i++) {
+               int j = 0;
+               if (!acpi_evalf(hkey_handle, &state, "BCTG", "qdd", i + 1))
+                       continue;
+               /* If the sign bit was set, we could not get the start charge
+                * threshold of that battery. Let's assume that this battery
+                * (and all following ones) do not exist */
+               if (state < 0)
+                       break;
+               /* Modify BATTERY_MAX_ATTRS if you add an attribute */
+               batteries[i].attributes[j++] = (struct dev_ext_attribute) {
+                       .attr = __ATTR(start_charge_tresh,
+                                      S_IWUSR | S_IRUGO,
+                                      battery_start_charge_thresh_show,
+                                      battery_start_charge_thresh_store),
+                       .var = (void *) (unsigned long) (i + 1)
+               };
+               batteries[i].attributes[j++] = (struct dev_ext_attribute) {
+                       .attr = __ATTR(stop_charge_tresh,
+                                      S_IWUSR | S_IRUGO,
+                                      battery_stop_charge_thresh_show,
+                                      battery_stop_charge_thresh_store),
+                       .var = (void *) (unsigned long) (i + 1)
+               };
+
+               strncpy(batteries[i].name, "BAT", 3);
+               batteries[i].name[3] = '0' + i;
+               batteries[i].name[4] = '\0';
+               batteries[i].set = create_attr_set(j - 1, batteries[i].name);
+
+               for (j = j - 1; j >= 0; j--)
+                       add_to_attr_set(batteries[i].set,
+                                       &batteries[i].attributes[j].attr.attr);
+
+               res = register_attr_set_with_sysfs(batteries[i].set,
+                                                  &tpacpi_pdev->dev.kobj);
+
+               if (res)
+                       return res;
+       }
+
+       return 0;
+}
+
+static void battery_exit(void)
+{
+       int i;
+
+       for (i = 0; i < BATTERY_MAX_COUNT; i++) {
+               if (batteries[i].set != NULL) {
+                       delete_attr_set(batteries[i].set,
+                                       &tpacpi_pdev->dev.kobj);
+               }
+       }
+}
+
+static struct ibm_struct battery_driver_data = {
+       .name = "battery",
+       .exit = battery_exit,
+};
+
 /****************************************************************************
  ****************************************************************************
  *
@@ -8741,6 +8910,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
                .data = &light_driver_data,
        },
        {
+               .init = battery_init,
+               .data = &battery_driver_data,
+       },
+       {
                .init = cmos_init,
                .data = &cmos_driver_data,
        },
-- 
1.8.4.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to