From patchwork Mon Dec 28 16:05:45 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dmitry Osipenko X-Patchwork-Id: 1420924 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-tegra-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20161025 header.b=hZlmdwIE; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4D4Ms95tdvz9sWK for ; Tue, 29 Dec 2020 03:07:57 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2393448AbgL1QG6 (ORCPT ); Mon, 28 Dec 2020 11:06:58 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49460 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2393359AbgL1QG4 (ORCPT ); Mon, 28 Dec 2020 11:06:56 -0500 Received: from mail-lf1-x135.google.com (mail-lf1-x135.google.com [IPv6:2a00:1450:4864:20::135]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 232AEC06179B; Mon, 28 Dec 2020 08:06:06 -0800 (PST) Received: by mail-lf1-x135.google.com with SMTP id y19so24749110lfa.13; Mon, 28 Dec 2020 08:06:06 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=tPhbBtY24leD6EYjNk4lZTpgtDU5k3awjWfX3kBti/E=; b=hZlmdwIEtYVMxAah4R51EStHa7MVaz+0z4ZF5K2G6/5MgA6VvXfJKylmJcduM8SSTy 6+oUt41T4RXwxJJrok0QcbTYhZC+knZkve31rOYCzEKW0Qc/mOv0sANohnWOMivjQvCu ia2PwSyu3jENcjZnEkmlArjPS1BdVd1mqWSscLTQrwFLEena10X4Rx3cooheayc+MHxT nvt/ViHIWzjtLAm56lpCcXXAElZUcmq1mbJJ2W4NIiYikV8uaFyZPsdzDEcMYEC0fANU HvAeChTuPzI+mx4NePulkVpFEX//SpoJYnmBYkZsZM+3fPQCBeuOd8SAze2AvT4qMEyX xLHw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=tPhbBtY24leD6EYjNk4lZTpgtDU5k3awjWfX3kBti/E=; b=B4jcbPA9PtSl6MyKzGxd/jpebQRjokgaHA/KCky3zVpoOonXbvO2Y7n8JkrgQ3OOVb TKTeP+C7HcpFWuUuq5xtOjmmpMzSnTEx1kGaTjEIHFNxpOZcez+Y2uy13UChPg35wiUt PrDqRN7nRmyvwluh0JfSpQL9io+YZ26RIpQOBXs879oTLlW/EQY1xAeJk6vhwwrPxGcM /Blbz37+BABgO6I4aEh9Sl9VkbbzJf39KDSGBHLZQvOjBqj9cZRSyxqQoqZxVE6PHNAI Guve6NNE/dnhf+IgT59QOFDUlGKDJTHVQ27j+jRgJZWzhL73XLmwiS0N413lmQTnyyx1 NCnQ== X-Gm-Message-State: AOAM5320YYgamwSTaPOthx4T/6Jb8I7v48vUgvIfOfr/s+OqHJahP7X3 ZMdybKD2C1peXW2FmCUfacE= X-Google-Smtp-Source: ABdhPJzc5hbC46eyt5/uSQFTJBsDdbXDw/FqdkedIy3IoUxH0F+OZmWY0ntAqDqvcQMbDXb4MC/u/Q== X-Received: by 2002:a2e:920b:: with SMTP id k11mr21283230ljg.313.1609171564697; Mon, 28 Dec 2020 08:06:04 -0800 (PST) Received: from localhost.localdomain (109-252-192-57.dynamic.spd-mgts.ru. [109.252.192.57]) by smtp.gmail.com with ESMTPSA id v28sm5338837lfd.57.2020.12.28.08.06.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 28 Dec 2020 08:06:04 -0800 (PST) From: Dmitry Osipenko To: Lee Jones , Rob Herring , Thierry Reding , Jonathan Hunter , Dan Murphy , Sebastian Reichel Cc: devicetree@vger.kernel.org, linux-tegra@vger.kernel.org, linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v8 1/3] dt-bindings: mfd: Add ENE KB930 Embedded Controller binding Date: Mon, 28 Dec 2020 19:05:45 +0300 Message-Id: <20201228160547.30562-2-digetx@gmail.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201228160547.30562-1-digetx@gmail.com> References: <20201228160547.30562-1-digetx@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-tegra@vger.kernel.org Add binding document for the ENE KB930 Embedded Controller. Reviewed-by: Rob Herring Signed-off-by: Dmitry Osipenko --- .../devicetree/bindings/mfd/ene-kb930.yaml | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/ene-kb930.yaml diff --git a/Documentation/devicetree/bindings/mfd/ene-kb930.yaml b/Documentation/devicetree/bindings/mfd/ene-kb930.yaml new file mode 100644 index 000000000000..06ed9ec8f4bb --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/ene-kb930.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/ene-kb930.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ENE KB930 Embedded Controller bindings + +description: | + This binding describes the ENE KB930 Embedded Controller attached to an + I2C bus. + +maintainers: + - Dmitry Osipenko + +properties: + compatible: + items: + - enum: + - acer,a500-iconia-ec # Acer A500 Iconia tablet device + - const: ene,kb930 + reg: + maxItems: 1 + + monitored-battery: true + power-supplies: true + system-power-controller: true + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + battery: battery-cell { + compatible = "simple-battery"; + charge-full-design-microamp-hours = <3260000>; + energy-full-design-microwatt-hours = <24000000>; + operating-range-celsius = <0 40>; + }; + + mains: ac-adapter { + compatible = "gpio-charger"; + charger-type = "mains"; + gpios = <&gpio 125 0>; + }; + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + embedded-controller@58 { + compatible = "acer,a500-iconia-ec", "ene,kb930"; + reg = <0x58>; + + system-power-controller; + + monitored-battery = <&battery>; + power-supplies = <&mains>; + }; + }; + +... From patchwork Mon Dec 28 16:05:46 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dmitry Osipenko X-Patchwork-Id: 1420925 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-tegra-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20161025 header.b=GLZwzVel; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4D4MsB4fGkz9sWR for ; Tue, 29 Dec 2020 03:07:58 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2392762AbgL1QHr (ORCPT ); Mon, 28 Dec 2020 11:07:47 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49462 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2393373AbgL1QG4 (ORCPT ); Mon, 28 Dec 2020 11:06:56 -0500 Received: from mail-lf1-x12b.google.com (mail-lf1-x12b.google.com [IPv6:2a00:1450:4864:20::12b]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 0BCF0C06179C; Mon, 28 Dec 2020 08:06:07 -0800 (PST) Received: by mail-lf1-x12b.google.com with SMTP id m25so24832072lfc.11; Mon, 28 Dec 2020 08:06:06 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=MzXM5kh78uP6JLUZoqHPn7MjFL/ZE3XLB3soRLgh4bc=; b=GLZwzVelfhq+xp0TqV0TXTHUo5jdFglOGjlwnCzlbl7PuhJzhZbdSafcEV7zvdElgQ AKdfNcMtxnyYFxi56X2DvnupLg6VxukmZ3ho3Wu8PsbHeJXCejCd43luOl4Nw7hZFr3/ U2BJ2VF4A3KRfD54OZlM/6V98YEBF42jWYLdRUQ6AMqIY917cphWa438zfghOjL9q0oo tFql8Fj5oposECv+zPsZxa0ZwMS/hGhKkIkopGs5jZ0EGQAIb6P0YNH0Zi9jjvKUF6/d jbIYWaFNaZQdg4r1O8IgPdfo0A5bvmJpMyDMw9/9k3WkvmtmRRzplMpEkSrebbWc+g0m W6CQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=MzXM5kh78uP6JLUZoqHPn7MjFL/ZE3XLB3soRLgh4bc=; b=ZxB5yKpgaJEuqH3NzfY6PXrhK6yje0UyTZY6uUjfiTo2HbfJHCYDv7jnjaHAPUhZXM 75BdGTmZLqBckqzTok2BNq8zETEUzD/1vINXukKJMdyV7zzHH2oDhySetX86SthuAROR ANlzP6qDCtvTR5qNa43DpdeIA8CaIXMtu+EDNx4VfpYGXewDNx4LnW4qp5vjZOnXGxGg igFH6s5iQPQ7MnpgfvOU8hSL5l9C1PGojHxMFN9XpZOo9ne6EkvmW9Dz/h+3ATcSBXIR KQC4oRbW/RvKe/dhT/9KGnxormMGlQrKxd619gm8venC9zHj+zeYlZv178jQPlRwDT/3 iCqg== X-Gm-Message-State: AOAM530m+1cJrU4CEJdYbe7hiO3FviE2BOSkdLJvKnvHnZHE6abogtMe 3T9SXgVG86fCX/5OOvDXJ6Y= X-Google-Smtp-Source: ABdhPJxufQZeXEMU9Zgtw7D/nNJx/2lpv9IRh1qF5mFkc4vVvICfegprJMaN8z19aQknnZieyoMUuQ== X-Received: by 2002:a2e:5452:: with SMTP id y18mr21724022ljd.224.1609171565541; Mon, 28 Dec 2020 08:06:05 -0800 (PST) Received: from localhost.localdomain (109-252-192-57.dynamic.spd-mgts.ru. [109.252.192.57]) by smtp.gmail.com with ESMTPSA id v28sm5338837lfd.57.2020.12.28.08.06.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 28 Dec 2020 08:06:05 -0800 (PST) From: Dmitry Osipenko To: Lee Jones , Rob Herring , Thierry Reding , Jonathan Hunter , Dan Murphy , Sebastian Reichel Cc: devicetree@vger.kernel.org, linux-tegra@vger.kernel.org, linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v8 2/3] mfd: Add driver for Embedded Controller found on Acer Iconia Tab A500 Date: Mon, 28 Dec 2020 19:05:46 +0300 Message-Id: <20201228160547.30562-3-digetx@gmail.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201228160547.30562-1-digetx@gmail.com> References: <20201228160547.30562-1-digetx@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-tegra@vger.kernel.org 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 --- 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 +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL"); From patchwork Mon Dec 28 16:05:47 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dmitry Osipenko X-Patchwork-Id: 1420922 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-tegra-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20161025 header.b=E9ABOjyx; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4D4Ms755DHz9sWK for ; Tue, 29 Dec 2020 03:07:55 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2632856AbgL1QG6 (ORCPT ); Mon, 28 Dec 2020 11:06:58 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49464 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2393395AbgL1QG4 (ORCPT ); Mon, 28 Dec 2020 11:06:56 -0500 Received: from mail-lf1-x12f.google.com (mail-lf1-x12f.google.com [IPv6:2a00:1450:4864:20::12f]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E892CC06179E; Mon, 28 Dec 2020 08:06:07 -0800 (PST) Received: by mail-lf1-x12f.google.com with SMTP id x20so24823261lfe.12; Mon, 28 Dec 2020 08:06:07 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=CFJiOxbFynL24gVLzjszwGgwYXRjyPqIbyEb12EfMGo=; b=E9ABOjyxLxhSP4LNrbmlCVRj20IuW2Zk8G1gFhKjqYSMmbhFh7dqNOCS2ZaPe2MHtm fOYx+9CwXb505vNKEYO6yvp8Y+9+ZsBQ2SsPdr0AJBG7227AZSrh79QQo2iXc7dBBkqp AwqddnUghtHK+hJFDHHXeiXJNTIJFkBrlsvHMrmOT9/pDT/otIfRs09A3I9sFPI9xbDi E0mrV3Y3RhXDdwUXrRGeuAVet3l9tcHFKIkVwNV+QT6T5M97+XajxXiBCA6ib1w00A83 d2vC3PcJKiWuJYRuR53HxcH/qVXpnTkfPxWnAAujlTfgQxpRjwN8ryGNxHRDqQ+dpU1C oXLg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=CFJiOxbFynL24gVLzjszwGgwYXRjyPqIbyEb12EfMGo=; b=ajClbVAe5nVFidipJdN4Zi7R/bcD5SeApAONEFw2OOfS1krUQ1ifa4++HrZ8TL5++g xys1d2VnYtUYMO0GvWxByOfBLnxxmyafoXTLLtg4MSjBd4lvA9Kp4Cl4m3YdHTakdEbn CQ/6FBf4b2iNct3wNKSChTfVzaa5y77C6kWbsGRsUVRiDfTkQzhs9cd08E76tpzGh8p8 0z/FeHOmYzFPCskpcKW67iwCyRpTCyTabKFmC4PLvXE52CpECTgZaG0TtKDO4GVwpGwQ YaWHEGURnf8YqRLEMUOGR9ErxtCNMF/GloWjfodpgbzzFkwXKPWGVNUQihRX2pcgzJ2p Y8mQ== X-Gm-Message-State: AOAM533U3c6g6bPMC0JZe1wqIxnHsEy2i6Il4Hv2GPaYc42U1pkQGgI3 iKY0pRGldtkUc9qPCiN+7po= X-Google-Smtp-Source: ABdhPJyhPbnEzLK/h2sLcWYP9Lsf1lrpZqWSS0imLwFvYadXFQXqR8OrXOg1axihxCRzzRgpZDByYQ== X-Received: by 2002:a2e:8995:: with SMTP id c21mr21313013lji.251.1609171566450; Mon, 28 Dec 2020 08:06:06 -0800 (PST) Received: from localhost.localdomain (109-252-192-57.dynamic.spd-mgts.ru. [109.252.192.57]) by smtp.gmail.com with ESMTPSA id v28sm5338837lfd.57.2020.12.28.08.06.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 28 Dec 2020 08:06:06 -0800 (PST) From: Dmitry Osipenko To: Lee Jones , Rob Herring , Thierry Reding , Jonathan Hunter , Dan Murphy , Sebastian Reichel Cc: devicetree@vger.kernel.org, linux-tegra@vger.kernel.org, linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v8 3/3] power: supply: Add battery gauge driver for Acer Iconia Tab A500 Date: Mon, 28 Dec 2020 19:05:47 +0300 Message-Id: <20201228160547.30562-4-digetx@gmail.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201228160547.30562-1-digetx@gmail.com> References: <20201228160547.30562-1-digetx@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-tegra@vger.kernel.org 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 Signed-off-by: Dmitry Osipenko --- 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 +#include +#include +#include +#include +#include +#include + +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 "); +MODULE_ALIAS("platform:acer-a500-iconia-battery"); +MODULE_LICENSE("GPL");