2020-12-28 16:09:25

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v8 0/3] Introduce Embedded Controller driver for Acer A500

This series adds support for the Embedded Controller which is found on
Acer Iconia Tab A500 (Android tablet device).

The Embedded Controller is ENE KB930 and it's running firmware customized
for the A500. The firmware interface may be reused by some other sibling
Acer tablets, although none of those tablets are supported in upstream yet.

Changelog:

v8: - This series partially missed v5.11 kernel release, hence resending
for v5.12.

Please note that Thierry Reding already applied the v7 DT patch which
added the EC node to the Acer A500 device-tree, this change presents
in v5.11.

v7: - Improved MFD Kconfig entry by adding explicit dependency on OF and
by rewording the help message, matching it to the other MFD drivers.

- Improved comments in the code by removing unnecessary comments, adding
necessary and rewording some others.

- Added expressive defines for the command opcodes.

- Fixed alphabet order of the MFD driver includes.

- Removed unnecessary size checks which are already done by regmap core.

- Renamed 'rmap' variable to 'regmap'.

v6: - Fixed dtschema-checker warning about a wrong indentation, reported
by kernel bot for v5.

v5: - No changes. Re-sending again in order to check whether dtschema-bot
warning is resolved now, which didn't happen in v4 because bot used
older 5.9 kernel code base instead of 5.10.

v4: - No code changes. Added r-b from Rob Herring and Sebastian Reichel.
Re-sending for 5.11.

- The v3 of LED driver was applied by Pavel Machek and already presents
in v5.10 kernel.

v3: - Rebased on a recent linux-next. Fixed new merge conflict and dropped
"regmap: Use flexible sleep" patch because it's already applied.

v2: - Factored out KB930 device-tree binding into a separate file, like it
was suggested by Lubomir Rintel.

- Switched to use regmap API like it was suggested by Lubomir Rintel.

- Added patch "regmap: Use flexible sleep" which allows not to hog
CPU while LED is switching state.

- Corrected MODULE_LICENSE to use "GPL" in all patches.

- Corrected MFD driver Kconfig entry like it was suggested by
Lubomir Rintel, it now depends on I2C.

- Switched to use I2C probe_new() in the MFD driver.

- Renamed the global pm_off variable, like it was suggested by
Lubomir Rintel and Lee Jones.

- Dropped serial number from the battery driver because I realized
that it's not a battery serial, but a device serial.

- Battery driver now uses dev_err_probe(), like it was suggested by
Sebastian Reichel.

- Dropped legacy LED_ON usage from the LED driver and renamed the
LEDs, like it was suggested by Pavel Machek. I also checked whether
LED-name customization via device-tree could be needed by other
potentially compatible devices and it shouldn't be needed, anyways it
won't be difficult to extend the code even if I'm wrong.


Dmitry Osipenko (3):
dt-bindings: mfd: Add ENE KB930 Embedded Controller binding
mfd: Add driver for Embedded Controller found on Acer Iconia Tab A500
power: supply: Add battery gauge driver for Acer Iconia Tab A500

.../devicetree/bindings/mfd/ene-kb930.yaml | 65 ++++
drivers/mfd/Kconfig | 11 +
drivers/mfd/Makefile | 1 +
drivers/mfd/acer-ec-a500.c | 202 ++++++++++++
drivers/power/supply/Kconfig | 6 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/acer_a500_battery.c | 297 ++++++++++++++++++
7 files changed, 583 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/ene-kb930.yaml
create mode 100644 drivers/mfd/acer-ec-a500.c
create mode 100644 drivers/power/supply/acer_a500_battery.c

--
2.29.2


2020-12-28 16:09:26

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v8 3/3] power: supply: Add battery gauge driver for Acer Iconia Tab A500

This patch adds battery gauge driver for Acer Iconia Tab A500 device.
The battery gauge function is provided via the Embedded Controller,
which is found on the Acer A500.

Reviewed-by: Sebastian Reichel <[email protected]>
Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/power/supply/Kconfig | 6 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/acer_a500_battery.c | 297 +++++++++++++++++++++++
3 files changed, 304 insertions(+)
create mode 100644 drivers/power/supply/acer_a500_battery.c

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index eec646c568b7..bc493173ddbc 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -774,4 +774,10 @@ config RN5T618_POWER
This driver can also be built as a module. If so, the module will be
called rn5t618_power.

+config BATTERY_ACER_A500
+ tristate "Acer Iconia Tab A500 battery driver"
+ depends on MFD_ACER_A500_EC
+ help
+ Say Y to include support for Acer Iconia Tab A500 battery fuel gauge.
+
endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index dd4b86318cd9..0607a3d64c0f 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -98,3 +98,4 @@ obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o
obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
+obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
diff --git a/drivers/power/supply/acer_a500_battery.c b/drivers/power/supply/acer_a500_battery.c
new file mode 100644
index 000000000000..32a0bfcac08f
--- /dev/null
+++ b/drivers/power/supply/acer_a500_battery.c
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Battery driver for Acer Iconia Tab A500.
+ *
+ * Copyright 2020 GRATE-driver project.
+ *
+ * Based on downstream driver from Acer Inc.
+ * Based on NVIDIA Gas Gauge driver for SBS Compliant Batteries.
+ *
+ * Copyright (c) 2010, NVIDIA Corporation.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+enum {
+ REG_CAPACITY,
+ REG_VOLTAGE,
+ REG_CURRENT,
+ REG_DESIGN_CAPACITY,
+ REG_TEMPERATURE,
+};
+
+#define EC_DATA(_reg, _psp) { \
+ .psp = POWER_SUPPLY_PROP_ ## _psp, \
+ .reg = _reg, \
+}
+
+static const struct battery_register {
+ enum power_supply_property psp;
+ unsigned int reg;
+} ec_data[] = {
+ [REG_CAPACITY] = EC_DATA(0x00, CAPACITY),
+ [REG_VOLTAGE] = EC_DATA(0x01, VOLTAGE_NOW),
+ [REG_CURRENT] = EC_DATA(0x03, CURRENT_NOW),
+ [REG_DESIGN_CAPACITY] = EC_DATA(0x08, CHARGE_FULL_DESIGN),
+ [REG_TEMPERATURE] = EC_DATA(0x0a, TEMP),
+};
+
+static const enum power_supply_property a500_battery_properties[] = {
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+struct a500_battery {
+ struct delayed_work poll_work;
+ struct power_supply *psy;
+ struct regmap *regmap;
+ unsigned int capacity;
+};
+
+static bool a500_battery_update_capacity(struct a500_battery *bat)
+{
+ unsigned int capacity;
+ int err;
+
+ err = regmap_read(bat->regmap, ec_data[REG_CAPACITY].reg, &capacity);
+ if (err)
+ return false;
+
+ /* capacity can be >100% even if max value is 100% */
+ capacity = min(capacity, 100u);
+
+ if (bat->capacity != capacity) {
+ bat->capacity = capacity;
+ return true;
+ }
+
+ return false;
+}
+
+static int a500_battery_get_status(struct a500_battery *bat)
+{
+ if (bat->capacity < 100) {
+ if (power_supply_am_i_supplied(bat->psy))
+ return POWER_SUPPLY_STATUS_CHARGING;
+ else
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ return POWER_SUPPLY_STATUS_FULL;
+}
+
+static void a500_battery_unit_adjustment(struct device *dev,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ const unsigned int base_unit_conversion = 1000;
+ const unsigned int temp_kelvin_to_celsius = 2731;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval *= base_unit_conversion;
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval -= temp_kelvin_to_celsius;
+ break;
+
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !!val->intval;
+ break;
+
+ default:
+ dev_dbg(dev,
+ "%s: no need for unit conversion %d\n", __func__, psp);
+ }
+}
+
+static int a500_battery_get_ec_data_index(struct device *dev,
+ enum power_supply_property psp)
+{
+ unsigned int i;
+
+ /*
+ * DESIGN_CAPACITY register always returns a non-zero value if
+ * battery is connected and zero if disconnected, hence we'll use
+ * it for judging the battery presence.
+ */
+ if (psp == POWER_SUPPLY_PROP_PRESENT)
+ psp = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
+
+ for (i = 0; i < ARRAY_SIZE(ec_data); i++)
+ if (psp == ec_data[i].psp)
+ return i;
+
+ dev_dbg(dev, "%s: invalid property %u\n", __func__, psp);
+
+ return -EINVAL;
+}
+
+static int a500_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct a500_battery *bat = power_supply_get_drvdata(psy);
+ struct device *dev = psy->dev.parent;
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = a500_battery_get_status(bat);
+ break;
+
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ a500_battery_update_capacity(bat);
+ val->intval = bat->capacity;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_PRESENT:
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = a500_battery_get_ec_data_index(dev, psp);
+ if (ret < 0)
+ break;
+
+ ret = regmap_read(bat->regmap, ec_data[ret].reg, &val->intval);
+ break;
+
+ default:
+ dev_err(dev, "%s: invalid property %u\n", __func__, psp);
+ return -EINVAL;
+ }
+
+ if (!ret) {
+ /* convert units to match requirements of power supply class */
+ a500_battery_unit_adjustment(dev, psp, val);
+ }
+
+ dev_dbg(dev, "%s: property = %d, value = %x\n",
+ __func__, psp, val->intval);
+
+ /* return NODATA for properties if battery not presents */
+ if (ret)
+ return -ENODATA;
+
+ return 0;
+}
+
+static void a500_battery_poll_work(struct work_struct *work)
+{
+ struct a500_battery *bat;
+ bool capacity_changed;
+
+ bat = container_of(work, struct a500_battery, poll_work.work);
+ capacity_changed = a500_battery_update_capacity(bat);
+
+ if (capacity_changed)
+ power_supply_changed(bat->psy);
+
+ /* continuously send uevent notification */
+ schedule_delayed_work(&bat->poll_work, 30 * HZ);
+}
+
+static const struct power_supply_desc a500_battery_desc = {
+ .name = "ec-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = a500_battery_properties,
+ .get_property = a500_battery_get_property,
+ .num_properties = ARRAY_SIZE(a500_battery_properties),
+ .external_power_changed = power_supply_changed,
+};
+
+static int a500_battery_probe(struct platform_device *pdev)
+{
+ struct power_supply_config psy_cfg = {};
+ struct a500_battery *bat;
+
+ bat = devm_kzalloc(&pdev->dev, sizeof(*bat), GFP_KERNEL);
+ if (!bat)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, bat);
+
+ psy_cfg.of_node = pdev->dev.parent->of_node;
+ psy_cfg.drv_data = bat;
+
+ bat->regmap = dev_get_regmap(pdev->dev.parent, "KB930");
+ if (!bat->regmap)
+ return -EINVAL;
+
+ bat->psy = devm_power_supply_register_no_ws(&pdev->dev,
+ &a500_battery_desc,
+ &psy_cfg);
+ if (IS_ERR(bat->psy))
+ return dev_err_probe(&pdev->dev, PTR_ERR(bat->psy),
+ "failed to register battery\n");
+
+ INIT_DELAYED_WORK(&bat->poll_work, a500_battery_poll_work);
+ schedule_delayed_work(&bat->poll_work, HZ);
+
+ return 0;
+}
+
+static int a500_battery_remove(struct platform_device *pdev)
+{
+ struct a500_battery *bat = dev_get_drvdata(&pdev->dev);
+
+ cancel_delayed_work_sync(&bat->poll_work);
+
+ return 0;
+}
+
+static int __maybe_unused a500_battery_suspend(struct device *dev)
+{
+ struct a500_battery *bat = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&bat->poll_work);
+
+ return 0;
+}
+
+static int __maybe_unused a500_battery_resume(struct device *dev)
+{
+ struct a500_battery *bat = dev_get_drvdata(dev);
+
+ schedule_delayed_work(&bat->poll_work, HZ);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(a500_battery_pm_ops,
+ a500_battery_suspend, a500_battery_resume);
+
+static struct platform_driver a500_battery_driver = {
+ .driver = {
+ .name = "acer-a500-iconia-battery",
+ .pm = &a500_battery_pm_ops,
+ },
+ .probe = a500_battery_probe,
+ .remove = a500_battery_remove,
+};
+module_platform_driver(a500_battery_driver);
+
+MODULE_DESCRIPTION("Battery gauge driver for Acer Iconia Tab A500");
+MODULE_AUTHOR("Dmitry Osipenko <[email protected]>");
+MODULE_ALIAS("platform:acer-a500-iconia-battery");
+MODULE_LICENSE("GPL");
--
2.29.2

2020-12-28 16:09:57

by Dmitry Osipenko

[permalink] [raw]
Subject: [PATCH v8 2/3] mfd: Add driver for Embedded Controller found on Acer Iconia Tab A500

Acer Iconia Tab A500 is an Android tablet device, it has ENE KB930
Embedded Controller which provides battery-gauge, LED, GPIO and some
other functions. The EC uses firmware that is specifically customized
for Acer A500. This patch adds MFD driver for the Embedded Controller
which allows to power-off / reboot the A500 device, it also provides
a common register read/write API that will be used by the sub-devices.

Signed-off-by: Dmitry Osipenko <[email protected]>
---
drivers/mfd/Kconfig | 11 ++
drivers/mfd/Makefile | 1 +
drivers/mfd/acer-ec-a500.c | 202 +++++++++++++++++++++++++++++++++++++
3 files changed, 214 insertions(+)
create mode 100644 drivers/mfd/acer-ec-a500.c

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index bdfce7b15621..750709219e00 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2085,6 +2085,17 @@ config MFD_KHADAS_MCU
additional drivers must be enabled in order to use the functionality
of the device.

+config MFD_ACER_A500_EC
+ tristate "Support for Acer Iconia Tab A500 Embedded Controller"
+ depends on I2C
+ depends on (ARCH_TEGRA_2x_SOC && OF) || COMPILE_TEST
+ select MFD_CORE
+ select REGMAP
+ help
+ Support for Embedded Controller found on Acer Iconia Tab A500.
+ The controller itself is ENE KB930, it is running firmware
+ customized for the specific needs of the Acer A500 hardware.
+
menu "Multimedia Capabilities Port drivers"
depends on ARCH_SA1100

diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 14fdb188af02..025543418835 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -264,6 +264,7 @@ obj-$(CONFIG_MFD_ROHM_BD71828) += rohm-bd71828.o
obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o
obj-$(CONFIG_MFD_STMFX) += stmfx.o
obj-$(CONFIG_MFD_KHADAS_MCU) += khadas-mcu.o
+obj-$(CONFIG_MFD_ACER_A500_EC) += acer-ec-a500.o

obj-$(CONFIG_SGI_MFD_IOC3) += ioc3.o
obj-$(CONFIG_MFD_SIMPLE_MFD_I2C) += simple-mfd-i2c.o
diff --git a/drivers/mfd/acer-ec-a500.c b/drivers/mfd/acer-ec-a500.c
new file mode 100644
index 000000000000..80c2fdd14fc4
--- /dev/null
+++ b/drivers/mfd/acer-ec-a500.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Acer Iconia Tab A500 Embedded Controller Driver
+ *
+ * Copyright 2020 GRATE-driver project
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+
+#define A500_EC_I2C_ERR_TIMEOUT 500
+#define A500_EC_POWER_CMD_TIMEOUT 1000
+
+/*
+ * Controller's firmware expects specific command opcodes to be used for the
+ * corresponding registers. Unsupported commands are skipped by the firmware.
+ */
+#define CMD_SHUTDOWN 0x0
+#define CMD_WARM_REBOOT 0x0
+#define CMD_COLD_REBOOT 0x1
+
+enum {
+ REG_CURRENT_NOW = 0x03,
+ REG_SHUTDOWN = 0x52,
+ REG_WARM_REBOOT = 0x54,
+ REG_COLD_REBOOT = 0x55,
+};
+
+static struct i2c_client *a500_ec_client_pm_off;
+
+static int a500_ec_read(void *context, const void *reg_buf, size_t reg_size,
+ void *val_buf, size_t val_sizel)
+{
+ struct i2c_client *client = context;
+ unsigned int reg, retries = 5;
+ u16 *ret_val = val_buf;
+ s32 ret = 0;
+
+ reg = *(u8 *)reg_buf;
+
+ while (retries-- > 0) {
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret >= 0)
+ break;
+
+ msleep(A500_EC_I2C_ERR_TIMEOUT);
+ }
+
+ if (ret < 0) {
+ dev_err(&client->dev, "read 0x%x failed: %d\n", reg, ret);
+ return ret;
+ }
+
+ *ret_val = ret;
+
+ if (reg == REG_CURRENT_NOW)
+ fsleep(10000);
+
+ return 0;
+}
+
+static int a500_ec_write(void *context, const void *data, size_t count)
+{
+ struct i2c_client *client = context;
+ unsigned int reg, val, retries = 5;
+ s32 ret = 0;
+
+ reg = *(u8 *)(data + 0);
+ val = *(u16 *)(data + 1);
+
+ while (retries-- > 0) {
+ ret = i2c_smbus_write_word_data(client, reg, val);
+ if (ret >= 0)
+ break;
+
+ msleep(A500_EC_I2C_ERR_TIMEOUT);
+ }
+
+ if (ret < 0) {
+ dev_err(&client->dev, "write 0x%x failed: %d\n", reg, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct regmap_config a500_ec_regmap_config = {
+ .name = "KB930",
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = 0xff,
+};
+
+static const struct regmap_bus a500_ec_regmap_bus = {
+ .reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
+ .val_format_endian_default = REGMAP_ENDIAN_LITTLE,
+ .write = a500_ec_write,
+ .read = a500_ec_read,
+ .max_raw_read = 2,
+};
+
+static void a500_ec_poweroff(void)
+{
+ i2c_smbus_write_word_data(a500_ec_client_pm_off,
+ REG_SHUTDOWN, CMD_SHUTDOWN);
+
+ mdelay(A500_EC_POWER_CMD_TIMEOUT);
+}
+
+static int a500_ec_restart_notify(struct notifier_block *this,
+ unsigned long reboot_mode, void *data)
+{
+ if (reboot_mode == REBOOT_WARM)
+ i2c_smbus_write_word_data(a500_ec_client_pm_off,
+ REG_WARM_REBOOT, CMD_WARM_REBOOT);
+ else
+ i2c_smbus_write_word_data(a500_ec_client_pm_off,
+ REG_COLD_REBOOT, CMD_COLD_REBOOT);
+
+ mdelay(A500_EC_POWER_CMD_TIMEOUT);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block a500_ec_restart_handler = {
+ .notifier_call = a500_ec_restart_notify,
+ .priority = 200,
+};
+
+static const struct mfd_cell a500_ec_cells[] = {
+ { .name = "acer-a500-iconia-battery", },
+ { .name = "acer-a500-iconia-leds", },
+};
+
+static int a500_ec_probe(struct i2c_client *client)
+{
+ struct regmap *regmap;
+ int err;
+
+ regmap = devm_regmap_init(&client->dev, &a500_ec_regmap_bus,
+ client, &a500_ec_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ err = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO,
+ a500_ec_cells, ARRAY_SIZE(a500_ec_cells),
+ NULL, 0, NULL);
+ if (err) {
+ dev_err(&client->dev, "failed to add sub-devices: %d\n", err);
+ return err;
+ }
+
+ if (of_device_is_system_power_controller(client->dev.of_node)) {
+ a500_ec_client_pm_off = client;
+
+ err = register_restart_handler(&a500_ec_restart_handler);
+ if (err)
+ return err;
+
+ if (!pm_power_off)
+ pm_power_off = a500_ec_poweroff;
+ }
+
+ return 0;
+}
+
+static int a500_ec_remove(struct i2c_client *client)
+{
+ if (of_device_is_system_power_controller(client->dev.of_node)) {
+ if (pm_power_off == a500_ec_poweroff)
+ pm_power_off = NULL;
+
+ unregister_restart_handler(&a500_ec_restart_handler);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id a500_ec_match[] = {
+ { .compatible = "acer,a500-iconia-ec" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, a500_ec_match);
+
+static struct i2c_driver a500_ec_driver = {
+ .driver = {
+ .name = "acer-a500-embedded-controller",
+ .of_match_table = a500_ec_match,
+ },
+ .probe_new = a500_ec_probe,
+ .remove = a500_ec_remove,
+};
+module_i2c_driver(a500_ec_driver);
+
+MODULE_DESCRIPTION("Acer Iconia Tab A500 Embedded Controller driver");
+MODULE_AUTHOR("Dmitry Osipenko <[email protected]>");
+MODULE_LICENSE("GPL");
--
2.29.2

2021-01-12 11:32:09

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v8 0/3] Introduce Embedded Controller driver for Acer A500

28.12.2020 19:05, Dmitry Osipenko пишет:
> This series adds support for the Embedded Controller which is found on
> Acer Iconia Tab A500 (Android tablet device).
>
> The Embedded Controller is ENE KB930 and it's running firmware customized
> for the A500. The firmware interface may be reused by some other sibling
> Acer tablets, although none of those tablets are supported in upstream yet.
>
> Changelog:
>
> v8: - This series partially missed v5.11 kernel release, hence resending
> for v5.12.

Hello Lee,

Could you please take a look at the MFD patch? If it's good to you, then
please apply this whole series via MFD tree.

Thanks in advance.

2021-01-14 13:50:22

by Lee Jones

[permalink] [raw]
Subject: Re: [PATCH v8 2/3] mfd: Add driver for Embedded Controller found on Acer Iconia Tab A500

On Mon, 28 Dec 2020, Dmitry Osipenko wrote:

> Acer Iconia Tab A500 is an Android tablet device, it has ENE KB930
> Embedded Controller which provides battery-gauge, LED, GPIO and some
> other functions. The EC uses firmware that is specifically customized
> for Acer A500. This patch adds MFD driver for the Embedded Controller
> which allows to power-off / reboot the A500 device, it also provides
> a common register read/write API that will be used by the sub-devices.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/mfd/Kconfig | 11 ++
> drivers/mfd/Makefile | 1 +
> drivers/mfd/acer-ec-a500.c | 202 +++++++++++++++++++++++++++++++++++++
> 3 files changed, 214 insertions(+)
> create mode 100644 drivers/mfd/acer-ec-a500.c

Looks good to me:

For my own reference (apply this as-is to your sign-off block):

Acked-for-MFD-by: Lee Jones <[email protected]>

Do you have a merge plan?

--
Lee Jones [李琼斯]
Senior Technical Lead - Developer Services
Linaro.org │ Open source software for Arm SoCs
Follow Linaro: Facebook | Twitter | Blog

2021-01-14 14:15:03

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v8 2/3] mfd: Add driver for Embedded Controller found on Acer Iconia Tab A500

14.01.2021 16:47, Lee Jones пишет:
> On Mon, 28 Dec 2020, Dmitry Osipenko wrote:
>
>> Acer Iconia Tab A500 is an Android tablet device, it has ENE KB930
>> Embedded Controller which provides battery-gauge, LED, GPIO and some
>> other functions. The EC uses firmware that is specifically customized
>> for Acer A500. This patch adds MFD driver for the Embedded Controller
>> which allows to power-off / reboot the A500 device, it also provides
>> a common register read/write API that will be used by the sub-devices.
>>
>> Signed-off-by: Dmitry Osipenko <[email protected]>
>> ---
>> drivers/mfd/Kconfig | 11 ++
>> drivers/mfd/Makefile | 1 +
>> drivers/mfd/acer-ec-a500.c | 202 +++++++++++++++++++++++++++++++++++++
>> 3 files changed, 214 insertions(+)
>> create mode 100644 drivers/mfd/acer-ec-a500.c
>
> Looks good to me:
>
> For my own reference (apply this as-is to your sign-off block):
>
> Acked-for-MFD-by: Lee Jones <[email protected]>
>
> Do you have a merge plan?
>

Thanks, you could take the DT binding and the MFD patches into the MFD
tree right now.

Perhaps will be better to merge the battery patch via the Power tree
since it doesn't have build dependency on the MFD patch. The battery
patch was reviewed by Sebastian, but not acked, as I see now.

Sebastian, could you please take the battery patch?

2021-01-14 14:32:43

by Lee Jones

[permalink] [raw]
Subject: Re: [PATCH v8 2/3] mfd: Add driver for Embedded Controller found on Acer Iconia Tab A500

On Mon, 28 Dec 2020, Dmitry Osipenko wrote:

> Acer Iconia Tab A500 is an Android tablet device, it has ENE KB930
> Embedded Controller which provides battery-gauge, LED, GPIO and some
> other functions. The EC uses firmware that is specifically customized
> for Acer A500. This patch adds MFD driver for the Embedded Controller
> which allows to power-off / reboot the A500 device, it also provides
> a common register read/write API that will be used by the sub-devices.
>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---
> drivers/mfd/Kconfig | 11 ++
> drivers/mfd/Makefile | 1 +
> drivers/mfd/acer-ec-a500.c | 202 +++++++++++++++++++++++++++++++++++++
> 3 files changed, 214 insertions(+)
> create mode 100644 drivers/mfd/acer-ec-a500.c

Applied, thanks.

--
Lee Jones [李琼斯]
Senior Technical Lead - Developer Services
Linaro.org │ Open source software for Arm SoCs
Follow Linaro: Facebook | Twitter | Blog

2021-01-14 18:52:35

by Sebastian Reichel

[permalink] [raw]
Subject: Re: [PATCH v8 3/3] power: supply: Add battery gauge driver for Acer Iconia Tab A500

Hi,

On Mon, Dec 28, 2020 at 07:05:47PM +0300, Dmitry Osipenko wrote:
> This patch adds battery gauge driver for Acer Iconia Tab A500 device.
> The battery gauge function is provided via the Embedded Controller,
> which is found on the Acer A500.
>
> Reviewed-by: Sebastian Reichel <[email protected]>
> Signed-off-by: Dmitry Osipenko <[email protected]>
> ---

Thanks, queued.

-- Sebastian

> drivers/power/supply/Kconfig | 6 +
> drivers/power/supply/Makefile | 1 +
> drivers/power/supply/acer_a500_battery.c | 297 +++++++++++++++++++++++
> 3 files changed, 304 insertions(+)
> create mode 100644 drivers/power/supply/acer_a500_battery.c
>
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index eec646c568b7..bc493173ddbc 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -774,4 +774,10 @@ config RN5T618_POWER
> This driver can also be built as a module. If so, the module will be
> called rn5t618_power.
>
> +config BATTERY_ACER_A500
> + tristate "Acer Iconia Tab A500 battery driver"
> + depends on MFD_ACER_A500_EC
> + help
> + Say Y to include support for Acer Iconia Tab A500 battery fuel gauge.
> +
> endif # POWER_SUPPLY
> diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
> index dd4b86318cd9..0607a3d64c0f 100644
> --- a/drivers/power/supply/Makefile
> +++ b/drivers/power/supply/Makefile
> @@ -98,3 +98,4 @@ obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o
> obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
> obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
> obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
> +obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
> diff --git a/drivers/power/supply/acer_a500_battery.c b/drivers/power/supply/acer_a500_battery.c
> new file mode 100644
> index 000000000000..32a0bfcac08f
> --- /dev/null
> +++ b/drivers/power/supply/acer_a500_battery.c
> @@ -0,0 +1,297 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Battery driver for Acer Iconia Tab A500.
> + *
> + * Copyright 2020 GRATE-driver project.
> + *
> + * Based on downstream driver from Acer Inc.
> + * Based on NVIDIA Gas Gauge driver for SBS Compliant Batteries.
> + *
> + * Copyright (c) 2010, NVIDIA Corporation.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/power_supply.h>
> +#include <linux/regmap.h>
> +#include <linux/sched.h>
> +#include <linux/slab.h>
> +#include <linux/workqueue.h>
> +
> +enum {
> + REG_CAPACITY,
> + REG_VOLTAGE,
> + REG_CURRENT,
> + REG_DESIGN_CAPACITY,
> + REG_TEMPERATURE,
> +};
> +
> +#define EC_DATA(_reg, _psp) { \
> + .psp = POWER_SUPPLY_PROP_ ## _psp, \
> + .reg = _reg, \
> +}
> +
> +static const struct battery_register {
> + enum power_supply_property psp;
> + unsigned int reg;
> +} ec_data[] = {
> + [REG_CAPACITY] = EC_DATA(0x00, CAPACITY),
> + [REG_VOLTAGE] = EC_DATA(0x01, VOLTAGE_NOW),
> + [REG_CURRENT] = EC_DATA(0x03, CURRENT_NOW),
> + [REG_DESIGN_CAPACITY] = EC_DATA(0x08, CHARGE_FULL_DESIGN),
> + [REG_TEMPERATURE] = EC_DATA(0x0a, TEMP),
> +};
> +
> +static const enum power_supply_property a500_battery_properties[] = {
> + POWER_SUPPLY_PROP_CAPACITY,
> + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
> + POWER_SUPPLY_PROP_CURRENT_NOW,
> + POWER_SUPPLY_PROP_PRESENT,
> + POWER_SUPPLY_PROP_STATUS,
> + POWER_SUPPLY_PROP_TECHNOLOGY,
> + POWER_SUPPLY_PROP_TEMP,
> + POWER_SUPPLY_PROP_VOLTAGE_NOW,
> +};
> +
> +struct a500_battery {
> + struct delayed_work poll_work;
> + struct power_supply *psy;
> + struct regmap *regmap;
> + unsigned int capacity;
> +};
> +
> +static bool a500_battery_update_capacity(struct a500_battery *bat)
> +{
> + unsigned int capacity;
> + int err;
> +
> + err = regmap_read(bat->regmap, ec_data[REG_CAPACITY].reg, &capacity);
> + if (err)
> + return false;
> +
> + /* capacity can be >100% even if max value is 100% */
> + capacity = min(capacity, 100u);
> +
> + if (bat->capacity != capacity) {
> + bat->capacity = capacity;
> + return true;
> + }
> +
> + return false;
> +}
> +
> +static int a500_battery_get_status(struct a500_battery *bat)
> +{
> + if (bat->capacity < 100) {
> + if (power_supply_am_i_supplied(bat->psy))
> + return POWER_SUPPLY_STATUS_CHARGING;
> + else
> + return POWER_SUPPLY_STATUS_DISCHARGING;
> + }
> +
> + return POWER_SUPPLY_STATUS_FULL;
> +}
> +
> +static void a500_battery_unit_adjustment(struct device *dev,
> + enum power_supply_property psp,
> + union power_supply_propval *val)
> +{
> + const unsigned int base_unit_conversion = 1000;
> + const unsigned int temp_kelvin_to_celsius = 2731;
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
> + case POWER_SUPPLY_PROP_CURRENT_NOW:
> + case POWER_SUPPLY_PROP_VOLTAGE_NOW:
> + val->intval *= base_unit_conversion;
> + break;
> +
> + case POWER_SUPPLY_PROP_TEMP:
> + val->intval -= temp_kelvin_to_celsius;
> + break;
> +
> + case POWER_SUPPLY_PROP_PRESENT:
> + val->intval = !!val->intval;
> + break;
> +
> + default:
> + dev_dbg(dev,
> + "%s: no need for unit conversion %d\n", __func__, psp);
> + }
> +}
> +
> +static int a500_battery_get_ec_data_index(struct device *dev,
> + enum power_supply_property psp)
> +{
> + unsigned int i;
> +
> + /*
> + * DESIGN_CAPACITY register always returns a non-zero value if
> + * battery is connected and zero if disconnected, hence we'll use
> + * it for judging the battery presence.
> + */
> + if (psp == POWER_SUPPLY_PROP_PRESENT)
> + psp = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
> +
> + for (i = 0; i < ARRAY_SIZE(ec_data); i++)
> + if (psp == ec_data[i].psp)
> + return i;
> +
> + dev_dbg(dev, "%s: invalid property %u\n", __func__, psp);
> +
> + return -EINVAL;
> +}
> +
> +static int a500_battery_get_property(struct power_supply *psy,
> + enum power_supply_property psp,
> + union power_supply_propval *val)
> +{
> + struct a500_battery *bat = power_supply_get_drvdata(psy);
> + struct device *dev = psy->dev.parent;
> + int ret = 0;
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_STATUS:
> + val->intval = a500_battery_get_status(bat);
> + break;
> +
> + case POWER_SUPPLY_PROP_TECHNOLOGY:
> + val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
> + break;
> +
> + case POWER_SUPPLY_PROP_CAPACITY:
> + a500_battery_update_capacity(bat);
> + val->intval = bat->capacity;
> + break;
> +
> + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
> + case POWER_SUPPLY_PROP_CURRENT_NOW:
> + case POWER_SUPPLY_PROP_VOLTAGE_NOW:
> + case POWER_SUPPLY_PROP_PRESENT:
> + case POWER_SUPPLY_PROP_TEMP:
> + ret = a500_battery_get_ec_data_index(dev, psp);
> + if (ret < 0)
> + break;
> +
> + ret = regmap_read(bat->regmap, ec_data[ret].reg, &val->intval);
> + break;
> +
> + default:
> + dev_err(dev, "%s: invalid property %u\n", __func__, psp);
> + return -EINVAL;
> + }
> +
> + if (!ret) {
> + /* convert units to match requirements of power supply class */
> + a500_battery_unit_adjustment(dev, psp, val);
> + }
> +
> + dev_dbg(dev, "%s: property = %d, value = %x\n",
> + __func__, psp, val->intval);
> +
> + /* return NODATA for properties if battery not presents */
> + if (ret)
> + return -ENODATA;
> +
> + return 0;
> +}
> +
> +static void a500_battery_poll_work(struct work_struct *work)
> +{
> + struct a500_battery *bat;
> + bool capacity_changed;
> +
> + bat = container_of(work, struct a500_battery, poll_work.work);
> + capacity_changed = a500_battery_update_capacity(bat);
> +
> + if (capacity_changed)
> + power_supply_changed(bat->psy);
> +
> + /* continuously send uevent notification */
> + schedule_delayed_work(&bat->poll_work, 30 * HZ);
> +}
> +
> +static const struct power_supply_desc a500_battery_desc = {
> + .name = "ec-battery",
> + .type = POWER_SUPPLY_TYPE_BATTERY,
> + .properties = a500_battery_properties,
> + .get_property = a500_battery_get_property,
> + .num_properties = ARRAY_SIZE(a500_battery_properties),
> + .external_power_changed = power_supply_changed,
> +};
> +
> +static int a500_battery_probe(struct platform_device *pdev)
> +{
> + struct power_supply_config psy_cfg = {};
> + struct a500_battery *bat;
> +
> + bat = devm_kzalloc(&pdev->dev, sizeof(*bat), GFP_KERNEL);
> + if (!bat)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, bat);
> +
> + psy_cfg.of_node = pdev->dev.parent->of_node;
> + psy_cfg.drv_data = bat;
> +
> + bat->regmap = dev_get_regmap(pdev->dev.parent, "KB930");
> + if (!bat->regmap)
> + return -EINVAL;
> +
> + bat->psy = devm_power_supply_register_no_ws(&pdev->dev,
> + &a500_battery_desc,
> + &psy_cfg);
> + if (IS_ERR(bat->psy))
> + return dev_err_probe(&pdev->dev, PTR_ERR(bat->psy),
> + "failed to register battery\n");
> +
> + INIT_DELAYED_WORK(&bat->poll_work, a500_battery_poll_work);
> + schedule_delayed_work(&bat->poll_work, HZ);
> +
> + return 0;
> +}
> +
> +static int a500_battery_remove(struct platform_device *pdev)
> +{
> + struct a500_battery *bat = dev_get_drvdata(&pdev->dev);
> +
> + cancel_delayed_work_sync(&bat->poll_work);
> +
> + return 0;
> +}
> +
> +static int __maybe_unused a500_battery_suspend(struct device *dev)
> +{
> + struct a500_battery *bat = dev_get_drvdata(dev);
> +
> + cancel_delayed_work_sync(&bat->poll_work);
> +
> + return 0;
> +}
> +
> +static int __maybe_unused a500_battery_resume(struct device *dev)
> +{
> + struct a500_battery *bat = dev_get_drvdata(dev);
> +
> + schedule_delayed_work(&bat->poll_work, HZ);
> +
> + return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(a500_battery_pm_ops,
> + a500_battery_suspend, a500_battery_resume);
> +
> +static struct platform_driver a500_battery_driver = {
> + .driver = {
> + .name = "acer-a500-iconia-battery",
> + .pm = &a500_battery_pm_ops,
> + },
> + .probe = a500_battery_probe,
> + .remove = a500_battery_remove,
> +};
> +module_platform_driver(a500_battery_driver);
> +
> +MODULE_DESCRIPTION("Battery gauge driver for Acer Iconia Tab A500");
> +MODULE_AUTHOR("Dmitry Osipenko <[email protected]>");
> +MODULE_ALIAS("platform:acer-a500-iconia-battery");
> +MODULE_LICENSE("GPL");
> --
> 2.29.2
>


Attachments:
(No filename) (10.21 kB)
signature.asc (849.00 B)
Download all attachments