From patchwork Sun Aug 23 14:08:41 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dmitry Osipenko X-Patchwork-Id: 255449 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.6 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id B673BC433E3 for ; Sun, 23 Aug 2020 14:10:05 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 8B6AE2078A for ; Sun, 23 Aug 2020 14:10:05 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="T22hOtrH" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726935AbgHWOKD (ORCPT ); Sun, 23 Aug 2020 10:10:03 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33538 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726912AbgHWOKD (ORCPT ); Sun, 23 Aug 2020 10:10:03 -0400 Received: from mail-lj1-x244.google.com (mail-lj1-x244.google.com [IPv6:2a00:1450:4864:20::244]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4190FC061573; Sun, 23 Aug 2020 07:10:02 -0700 (PDT) Received: by mail-lj1-x244.google.com with SMTP id m22so6737199ljj.5; Sun, 23 Aug 2020 07:10:02 -0700 (PDT) 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=TBgdtd71F0QM759PV8RPeCLfOWIqYKzDr4Ym8s23We0=; b=T22hOtrHKv11/bjnCHd6eoy87bLYacvldTS8JphYlqJaBFOVbxklPa6sqhKG21XJnd NQ0bpzno0T5DU8yiJbyXaLmsJJWoPs8M01lRgdW4u+Q/xbUbNShzetRZ880Jt1M2S2Ob 8njMHSxv9aTacy081qIOB4JQsPiPm9gUZ1CtkSd109q5FT+SLPYhpoSr9kwQWEFldSVj 9ae2MIrJ7DUF7j6hm3EiFocuMTWSAe6LOsw2aejrf9NSyx4bwey5rthxJc1ioIeA2eyC 1Dg59q92xUIl1dsAp3I/kWfue5UpiGLrFQgimN9UYaWCHG3Rw4SMgR/ZWxeavYO5u5og LlYg== 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=TBgdtd71F0QM759PV8RPeCLfOWIqYKzDr4Ym8s23We0=; b=Cpgf5B+JmyR9ZEdTjXp/qmCXqyTKCHc+aiqFBtWT72F5G8EP5V2rRYw2kCy41ghebK qkKOd98oR2s3lUgW/NC4avX5FiG8Ka0ueMGrZW1msVmUEG0TvaD8l9deff4f/COAIpb4 ifkC8Riq3Ce58QRjynuZethl33p3Nt2dlJTVuxA/Tch863s3iy6t0anO/uDYn9zg5/um QZtOW8qsOu6LGK3I9d5S0FERvGlU+lgRvB1esx3Kdr2GAjJRYiDF7nku+uly9ERC9bwv qoywHy1yvsr0p4A4zPlzV/qKOrxvctVFhN0V581sh57mxN3PO/KeYYHaXEVbrzGMX1Av 0HFA== X-Gm-Message-State: AOAM532lqwpgkkPBQvrj9EULtUChxo4tlJxEL9LCG2uackJU9cOzW2Z8 NBCrhMMaL6ovZQpJnbXAcCo= X-Google-Smtp-Source: ABdhPJxEktylPCMlkCurtgHaj9onDVJsXb5qLHJO6e65YlUBFzOP9BPatgq12peHSLSq8ZKa8UOOag== X-Received: by 2002:a05:651c:2007:: with SMTP id s7mr679006ljo.74.1598191800541; Sun, 23 Aug 2020 07:10:00 -0700 (PDT) Received: from localhost.localdomain (109-252-170-211.dynamic.spd-mgts.ru. [109.252.170.211]) by smtp.gmail.com with ESMTPSA id b17sm1641342ljp.9.2020.08.23.07.09.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 23 Aug 2020 07:09:59 -0700 (PDT) From: Dmitry Osipenko To: Lee Jones , Rob Herring , Thierry Reding , Jonathan Hunter , Pavel Machek , Dan Murphy , Sebastian Reichel , Lubomir Rintel Cc: devicetree@vger.kernel.org, linux-tegra@vger.kernel.org, linux-leds@vger.kernel.org, linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v1 1/6] mfd: Add driver for Embedded Controller found on Acer Iconia Tab A500 Date: Sun, 23 Aug 2020 17:08:41 +0300 Message-Id: <20200823140846.19299-2-digetx@gmail.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20200823140846.19299-1-digetx@gmail.com> References: <20200823140846.19299-1-digetx@gmail.com> MIME-Version: 1.0 Sender: linux-leds-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-leds@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 | 10 ++ drivers/mfd/Makefile | 1 + drivers/mfd/acer-ec-a500.c | 196 +++++++++++++++++++++++++++++++ include/linux/mfd/acer-ec-a500.h | 80 +++++++++++++ 4 files changed, 287 insertions(+) create mode 100644 drivers/mfd/acer-ec-a500.c create mode 100644 include/linux/mfd/acer-ec-a500.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 33df0837ab41..9e5cf88a52d8 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2062,6 +2062,16 @@ config MFD_KHADAS_MCU additional drivers must be enabled in order to use the functionality of the device. +config MFD_ACER_A500_EC + tristate "Embedded Controller driver for Acer Iconia Tab A500" + depends on (I2C_TEGRA && ARCH_TEGRA_2x_SOC) || COMPILE_TEST + select MFD_CORE + help + Support for Acer Iconia Tab A500 Embedded Controller. + + 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 a60e5f835283..6e3a6162ad94 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -262,5 +262,6 @@ 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 diff --git a/drivers/mfd/acer-ec-a500.c b/drivers/mfd/acer-ec-a500.c new file mode 100644 index 000000000000..f75bb60ab408 --- /dev/null +++ b/drivers/mfd/acer-ec-a500.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MFD driver for Acer Iconia Tab A500 Embedded Controller. + * + * Copyright 2020 GRATE-driver project. + * + * Based on downstream driver from Acer Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define A500_EC_I2C_ERR_TIMEOUT 500 + +/* cmd delay ms */ +A500_EC_COMMAND(SHUTDOWN, 0x52, 1000); +A500_EC_COMMAND(WARM_REBOOT, 0x54, 1000); +A500_EC_COMMAND(COLD_REBOOT, 0x55, 1000); + +static struct a500_ec *a500_ec_scratch; + +static void a500_ec_delay(unsigned int delay_ms) +{ + /* interrupts could be disabled on shutdown or reboot */ + if (irqs_disabled()) + mdelay(delay_ms); + else + msleep(delay_ms); +} + +int a500_ec_read_locked(struct a500_ec *ec_chip, + const struct a500_ec_cmd *cmd_desc) +{ + struct i2c_client *client = ec_chip->client; + unsigned int retries = 5; + s32 ret = 0; + + while (retries-- > 0) { + ret = i2c_smbus_read_word_data(client, cmd_desc->cmd); + if (ret >= 0) + break; + + a500_ec_delay(A500_EC_I2C_ERR_TIMEOUT); + } + + if (ret < 0) { + dev_err(&client->dev, "i2c read command 0x%x failed: %d\n", + cmd_desc->cmd, ret); + return ret; + } + + a500_ec_delay(cmd_desc->post_delay); + + return le16_to_cpu(ret); +} +EXPORT_SYMBOL_GPL(a500_ec_read_locked); + +int a500_ec_write_locked(struct a500_ec *ec_chip, + const struct a500_ec_cmd *cmd_desc, u16 value) +{ + struct i2c_client *client = ec_chip->client; + unsigned int retries = 5; + s32 ret = 0; + + while (retries-- > 0) { + ret = i2c_smbus_write_word_data(client, cmd_desc->cmd, + le16_to_cpu(value)); + if (ret >= 0) + break; + + a500_ec_delay(A500_EC_I2C_ERR_TIMEOUT); + } + + if (ret < 0) { + dev_err(&client->dev, "i2c write command 0x%x failed: %d\n", + cmd_desc->cmd, ret); + return ret; + } + + a500_ec_delay(cmd_desc->post_delay); + + return 0; +} +EXPORT_SYMBOL_GPL(a500_ec_write_locked); + +static void a500_ec_poweroff(void) +{ + struct a500_ec *ec_chip = a500_ec_scratch; + + a500_ec_write_locked(ec_chip, SHUTDOWN, 0); +} + +static int a500_ec_restart_notify(struct notifier_block *this, + unsigned long reboot_mode, void *data) +{ + struct a500_ec *ec_chip = a500_ec_scratch; + + if (reboot_mode == REBOOT_WARM) + a500_ec_write_locked(ec_chip, WARM_REBOOT, 0); + else + a500_ec_write_locked(ec_chip, COLD_REBOOT, 1); + + 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, + const struct i2c_device_id *id) +{ + struct a500_ec *ec_chip; + int err; + + ec_chip = devm_kzalloc(&client->dev, sizeof(*ec_chip), GFP_KERNEL); + if (!ec_chip) + return -ENOMEM; + + ec_chip->client = client; + mutex_init(&ec_chip->lock); + + i2c_set_clientdata(client, ec_chip); + + /* register battery and LED sub-devices */ + 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; + } + + /* set up power management functions */ + if (of_device_is_system_power_controller(client->dev.of_node)) { + a500_ec_scratch = ec_chip; + + err = register_restart_handler(&a500_ec_restart_handler); + if (err) { + dev_err(&client->dev, + "unable to register restart handler: %d\n", + 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 = 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 v2"); diff --git a/include/linux/mfd/acer-ec-a500.h b/include/linux/mfd/acer-ec-a500.h new file mode 100644 index 000000000000..08bc681e6525 --- /dev/null +++ b/include/linux/mfd/acer-ec-a500.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * API for Embedded Controller of Acer Iconia Tab A500. + * + * Copyright 2020 GRATE-driver project. + */ + +#ifndef __LINUX_MFD_ACER_A500_EC_H +#define __LINUX_MFD_ACER_A500_EC_H + +#include +#include +#include + +struct a500_ec_cmd { + unsigned int cmd; + unsigned int post_delay; +}; + +#define A500_EC_COMMAND(NAME, CMD, DELAY_MS) \ +static const struct a500_ec_cmd A500_EC_##NAME = { \ + .cmd = CMD, \ + .post_delay = DELAY_MS, \ +}; \ +static const __maybe_unused struct a500_ec_cmd *NAME = &A500_EC_##NAME + +struct a500_ec { + struct i2c_client *client; + + /* + * Some EC commands shall be executed sequentially and some commands + * shall not be executed instantly after the other commands. Hence the + * locking is needed in order to protect from conflicting accesses to + * the EC. + */ + struct mutex lock; +}; + +int a500_ec_read_locked(struct a500_ec *ec_chip, + const struct a500_ec_cmd *cmd_desc); + +int a500_ec_write_locked(struct a500_ec *ec_chip, + const struct a500_ec_cmd *cmd_desc, u16 value); + +static inline void a500_ec_lock(struct a500_ec *ec_chip) +{ + mutex_lock(&ec_chip->lock); +} + +static inline void a500_ec_unlock(struct a500_ec *ec_chip) +{ + mutex_unlock(&ec_chip->lock); +} + +static inline int a500_ec_read(struct a500_ec *ec_chip, + const struct a500_ec_cmd *cmd_desc) +{ + s32 ret; + + a500_ec_lock(ec_chip); + ret = a500_ec_read_locked(ec_chip, cmd_desc); + a500_ec_unlock(ec_chip); + + return ret; +} + +static inline int a500_ec_write(struct a500_ec *ec_chip, + const struct a500_ec_cmd *cmd_desc, + u16 value) +{ + s32 ret; + + a500_ec_lock(ec_chip); + ret = a500_ec_write_locked(ec_chip, cmd_desc, value); + a500_ec_unlock(ec_chip); + + return ret; +} + +#endif /* __LINUX_MFD_ACER_A500_EC_H */ From patchwork Sun Aug 23 14:08:42 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dmitry Osipenko X-Patchwork-Id: 255447 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.6 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id CC024C433EA for ; Sun, 23 Aug 2020 14:10:41 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id A564B2078D for ; Sun, 23 Aug 2020 14:10:41 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Gw1J5Rax" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727790AbgHWOK0 (ORCPT ); Sun, 23 Aug 2020 10:10:26 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33554 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726995AbgHWOKH (ORCPT ); Sun, 23 Aug 2020 10:10:07 -0400 Received: from mail-lf1-x142.google.com (mail-lf1-x142.google.com [IPv6:2a00:1450:4864:20::142]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7F590C061575; Sun, 23 Aug 2020 07:10:06 -0700 (PDT) Received: by mail-lf1-x142.google.com with SMTP id u23so716031lfl.10; Sun, 23 Aug 2020 07:10:06 -0700 (PDT) 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=zjpkV/460xf0TFgawt1/kH5qRAKcyuI0j4h8FTGZcLo=; b=Gw1J5Rax7mJMpIzKGjmaqqhesEGhtp6gkskCCEfxrvh/gppjY3f3sbDdpTKdv1ZtZn wgvJgbKVciQFsmI5VKvCWMqkQ41WsJ78vESJbJlsYu7xMuusgLsKL5pUftQenNWU7sud ikbVlFnwARwSE4Fbrap4YkVmfhz4hbdTCvfOxjlYpNAfdEPXUf3Tb+8DfgBQJryn2UnR LPtzavganVnSewa14+5l4npFNGaFZibpZjrM0K7anHHKXRQjMl6xuAHKpQkrPAP2Cok1 PuwN1lLsANkFINVOHfld2vt7n/KhjDoR6GTnuX4BljUQd7Zn/SeKfS7zB2m30SAIz2Da HdeQ== 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=zjpkV/460xf0TFgawt1/kH5qRAKcyuI0j4h8FTGZcLo=; b=MdCwgW8R7nncWRJuBHD4AVN81TwJS6zHpbHQ5Ot1pouS0dYHEtJLAIU5nXgSVzyJoa xoMRczwv0iU5HktaWYLLIFSsgrvbXslcpmjW3VMRnng5kXdkb8ZyrCtFMUj59yqbjkZp qtdRFNXXyeoNirf1Fs31A2G55XSrD9P5/yvFDQF072O9hfxdmQPps850dNtK7faBljKU xMG1WcgukRl10baldqF6xUcBlxezUVkSBptogRDwKla7Wf58kwjU8aSVHESuuOF3jfYe V0dmzmQH4yVtByyohKpKpT2HXKJphx+DBjp19LfGgL8qPClOaog1F5WJT31aK6kPizBR vBvw== X-Gm-Message-State: AOAM531Bo4wB33c++4mPYEnSSXSv7uTR1nRtnlFgOZwrteFgkjiEpgPx WcntHv6f8bApsv3srz3ad44= X-Google-Smtp-Source: ABdhPJzdnDTsw4lVxrnQv59TfqLArKITEvmSNtUVNIUG5CAHgmXN5ftO1IgabKG0s2TKzBzMLayCAA== X-Received: by 2002:a19:102:: with SMTP id 2mr661938lfb.54.1598191801958; Sun, 23 Aug 2020 07:10:01 -0700 (PDT) Received: from localhost.localdomain (109-252-170-211.dynamic.spd-mgts.ru. [109.252.170.211]) by smtp.gmail.com with ESMTPSA id b17sm1641342ljp.9.2020.08.23.07.10.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 23 Aug 2020 07:10:01 -0700 (PDT) From: Dmitry Osipenko To: Lee Jones , Rob Herring , Thierry Reding , Jonathan Hunter , Pavel Machek , Dan Murphy , Sebastian Reichel , Lubomir Rintel Cc: devicetree@vger.kernel.org, linux-tegra@vger.kernel.org, linux-leds@vger.kernel.org, linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v1 2/6] power: supply: Add battery gauge driver for Acer Iconia Tab A500 Date: Sun, 23 Aug 2020 17:08:42 +0300 Message-Id: <20200823140846.19299-3-digetx@gmail.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20200823140846.19299-1-digetx@gmail.com> References: <20200823140846.19299-1-digetx@gmail.com> MIME-Version: 1.0 Sender: linux-leds-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-leds@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. Signed-off-by: Dmitry Osipenko --- drivers/power/supply/Kconfig | 6 + drivers/power/supply/Makefile | 1 + drivers/power/supply/acer_a500_battery.c | 369 +++++++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 drivers/power/supply/acer_a500_battery.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index faf2830aa152..dff5e5a7383f 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -752,4 +752,10 @@ config CHARGER_WILCO information can be found in Documentation/ABI/testing/sysfs-class-power-wilco +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 b3c694a65114..08a5b49e2936 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -96,3 +96,4 @@ obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.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..933101e1261e --- /dev/null +++ b/drivers/power/supply/acer_a500_battery.c @@ -0,0 +1,369 @@ +// 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 + +#define BATTERY_SERIAL_LEN 22 + +enum { + REG_CAPACITY, + REG_VOLTAGE, + REG_CURRENT, + REG_DESIGN_CAPACITY, + REG_TEMPERATURE, + REG_SERIAL_NUMBER, +}; + +#define EC_DATA(_cmd, _delay, _psp) { \ + .psp = POWER_SUPPLY_PROP_ ## _psp, \ + .cmd = { \ + .cmd = _cmd, \ + .post_delay = _delay \ + }, \ +} + +static const struct chip_data { + enum power_supply_property psp; + struct a500_ec_cmd cmd; +} ec_data[] = { + [REG_CAPACITY] = EC_DATA(0x00, 0, CAPACITY), + [REG_VOLTAGE] = EC_DATA(0x01, 0, VOLTAGE_NOW), + [REG_CURRENT] = EC_DATA(0x03, 10, CURRENT_NOW), + [REG_DESIGN_CAPACITY] = EC_DATA(0x08, 0, CHARGE_FULL_DESIGN), + [REG_TEMPERATURE] = EC_DATA(0x0a, 0, TEMP), + [REG_SERIAL_NUMBER] = EC_DATA(0x6a, 0, SERIAL_NUMBER), +}; + +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_SERIAL_NUMBER, + 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 a500_ec *ec_chip; + unsigned int capacity; + char serial[BATTERY_SERIAL_LEN + 1]; +}; + +static int a500_battery_get_presence(struct a500_battery *bat, + union power_supply_propval *val) +{ + s32 ret; + + /* + * DESIGN_CAPACITY register always returns a non-zero value if + * battery is connected and zero if disconnected. + */ + ret = a500_ec_read(bat->ec_chip, &ec_data[REG_DESIGN_CAPACITY].cmd); + if (ret <= 0) + val->intval = 0; + else + val->intval = 1; + + return 0; +} + +static bool a500_battery_update_capacity(struct a500_battery *bat) +{ + unsigned int capacity; + s32 ret; + + ret = a500_ec_read(bat->ec_chip, &ec_data[REG_CAPACITY].cmd); + if (ret < 0) + return false; + + /* capacity can be >100% even if max value is 100% */ + capacity = min(ret, 100); + + if (bat->capacity != capacity) { + bat->capacity = capacity; + return true; + } + + return false; +} + +static void a500_battery_get_status(struct a500_battery *bat, + union power_supply_propval *val) +{ + if (bat->capacity < 100) { + if (power_supply_am_i_supplied(bat->psy)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + val->intval = 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; + + default: + dev_dbg(dev, + "%s: no need for unit conversion %d\n", __func__, psp); + } +} + +static int a500_battery_get_serial_number(struct a500_battery *bat, + union power_supply_propval *val) +{ + unsigned int i; + s32 ret = 0; + + if (bat->serial[0]) + goto done; + + a500_ec_lock(bat->ec_chip); + for (i = 0; i < BATTERY_SERIAL_LEN / 2; i++) { + ret = a500_ec_read_locked(bat->ec_chip, + &ec_data[REG_SERIAL_NUMBER].cmd); + if (ret < 0) { + bat->serial[0] = '\0'; + break; + } + + bat->serial[i * 2 + 0] = (ret >> 0) & 0xff; + bat->serial[i * 2 + 1] = (ret >> 8) & 0xff; + } + a500_ec_unlock(bat->ec_chip); +done: + val->strval = bat->serial; + + return ret; +} + +static int a500_battery_get_ec_data_index(struct device *dev, + enum power_supply_property psp) +{ + unsigned int i; + + 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_simple_property(struct a500_battery *bat, + union power_supply_propval *val, + unsigned int ec_idx) +{ + s32 ret; + + ret = a500_ec_read(bat->ec_chip, &ec_data[ec_idx].cmd); + if (ret < 0) + return ret; + + val->intval = (u16)ret; + + return 0; +} + +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_SERIAL_NUMBER: + ret = a500_battery_get_serial_number(bat, val); + break; + + case POWER_SUPPLY_PROP_PRESENT: + ret = a500_battery_get_presence(bat, val); + break; + + case POWER_SUPPLY_PROP_STATUS: + a500_battery_get_status(bat, val); + 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_TEMP: + ret = a500_battery_get_ec_data_index(dev, psp); + if (ret < 0) + break; + + ret = a500_battery_get_simple_property(bat, val, ret); + break; + + default: + dev_err(dev, "%s: invalid property %u\n", __func__, psp); + return -EINVAL; + } + + if (!ret) { + /* convert units to match requirements for 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_delayed_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; + int err; + + 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->ec_chip = dev_get_drvdata(pdev->dev.parent); + + bat->psy = devm_power_supply_register_no_ws(&pdev->dev, + &a500_battery_desc, + &psy_cfg); + err = PTR_ERR_OR_ZERO(bat->psy); + if (err) { + if (err == -EPROBE_DEFER) + dev_dbg(&pdev->dev, "failed to register battery, deferring probe\n"); + else + dev_err(&pdev->dev, "failed to register battery: %d\n", + err); + return err; + } + + INIT_DELAYED_WORK(&bat->poll_work, a500_battery_delayed_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 v2"); From patchwork Sun Aug 23 14:08:43 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dmitry Osipenko X-Patchwork-Id: 255448 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.6 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id C428CC433DF for ; Sun, 23 Aug 2020 14:10:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 997942078D for ; Sun, 23 Aug 2020 14:10:25 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="A3ankdiX" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727018AbgHWOKK (ORCPT ); Sun, 23 Aug 2020 10:10:10 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33546 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726961AbgHWOKF (ORCPT ); Sun, 23 Aug 2020 10:10:05 -0400 Received: from mail-lj1-x241.google.com (mail-lj1-x241.google.com [IPv6:2a00:1450:4864:20::241]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A6AF1C061573; Sun, 23 Aug 2020 07:10:04 -0700 (PDT) Received: by mail-lj1-x241.google.com with SMTP id i10so6748868ljn.2; Sun, 23 Aug 2020 07:10:04 -0700 (PDT) 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=IrKpeaWDCIbs0elrD0ervQn0EwoALOJQTmF6gmGudwk=; b=A3ankdiXKQcXxNVb464Y9CSK8uZmn/v7wkTOr8ZC32VSlRfBoFrDH9dldQUNZ7q0db be4up2EcJQJBIdQQz9wiwmaqw3OWktrzv2EWG6NF8TEhxszYwHIYQHWbNrGRy7yXxsxj lwljaYDN0oder2d2R+V/5pkX7BJBeiYRdExzhzeNnEuBvxypdCPmh6zhNzDv1iB5KMHe Y9x88UI5AEnSAelmciveimSf60PLvJtOj6Sp40TlaKfppYSiiTU2lNiyyyAVkK+0XMn4 VX2XrODp+74zcfNE8IdwYMb/Y5OfIBRzLTT3ea1lZxvDnKTMHsQhEBTBBdNZIiwGElyX 556A== 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=IrKpeaWDCIbs0elrD0ervQn0EwoALOJQTmF6gmGudwk=; b=eWkZsLwWWhXzjx93r90U9S37Oh1HdLfjisGd3I8WyipTAk1Mzc2XAP1pDzuUHCL5Kl 9muzBfsqyDMO81MKnlIvCTv0yhkeuq5oKFX+V8HDHs3Grz80JjKEiCtApyUDsZHxmdqb 3uXdz0AijQgwMfJpS+a4EZ7cu4VWzUy25j3rhdvIb6u6MtXGX/Seb5MDY94HRYg1eULf Fb8TLlFrtMYE8Lh4GaWisiqQZ5xxTrw5nOZnJmhqMy4te2Qi2L3+mrIDHX3/649ItmNy QUX4unH5bB1AdELWTdp6daBnu1IJNpYXjCSt8RJ//4oaIBuKzLoxBnLd/u7WxCvUDZ/5 TZlw== X-Gm-Message-State: AOAM532DiR321wedcAM0wvR+vnKMpQSMpiOgpwuvnGY59vBdp2h4uO1z qsbLY1oztMsadOPduU7X7+0= X-Google-Smtp-Source: ABdhPJwekZiY1NZHt+Cdvdzj6xO2L+gKT49464XJ7ZAmN4KyeCBtxLFZpi+kAiwchVtaC4TrvVludg== X-Received: by 2002:a2e:80c9:: with SMTP id r9mr626731ljg.95.1598191803007; Sun, 23 Aug 2020 07:10:03 -0700 (PDT) Received: from localhost.localdomain (109-252-170-211.dynamic.spd-mgts.ru. [109.252.170.211]) by smtp.gmail.com with ESMTPSA id b17sm1641342ljp.9.2020.08.23.07.10.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 23 Aug 2020 07:10:02 -0700 (PDT) From: Dmitry Osipenko To: Lee Jones , Rob Herring , Thierry Reding , Jonathan Hunter , Pavel Machek , Dan Murphy , Sebastian Reichel , Lubomir Rintel Cc: devicetree@vger.kernel.org, linux-tegra@vger.kernel.org, linux-leds@vger.kernel.org, linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v1 3/6] leds: Add driver for Acer Iconia Tab A500 Date: Sun, 23 Aug 2020 17:08:43 +0300 Message-Id: <20200823140846.19299-4-digetx@gmail.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20200823140846.19299-1-digetx@gmail.com> References: <20200823140846.19299-1-digetx@gmail.com> MIME-Version: 1.0 Sender: linux-leds-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org Acer Iconia Tab A500 is an Android tablet device which has two LEDs embedded into the Power Button. Orange LED indicates "battery charging" status and white LED indicates "wake-up/charge-done" status. The new LED driver provides control over both LEDs to userspace. Signed-off-by: Dmitry Osipenko --- drivers/leds/Kconfig | 7 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-acer-a500.c | 121 ++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 drivers/leds/leds-acer-a500.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 4f6464a169d5..4c39b53bcf1f 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -921,6 +921,13 @@ config LEDS_SGM3140 This option enables support for the SGM3140 500mA Buck/Boost Charge Pump LED Driver. +config LEDS_ACER_A500 + tristate "Power button LED support for Acer Iconia Tab A500" + depends on LEDS_CLASS && MFD_ACER_A500_EC + help + This option enables support for the Power Button LED of + Acer Iconia Tab A500. + comment "LED Triggers" source "drivers/leds/trigger/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 778cb4bb8c52..73e603e1727e 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o # LED Platform Drivers (keep this sorted, M-| sort) obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o +obj-$(CONFIG_LEDS_ACER_A500) += leds-acer-a500.o obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o obj-$(CONFIG_LEDS_APU) += leds-apu.o diff --git a/drivers/leds/leds-acer-a500.c b/drivers/leds/leds-acer-a500.c new file mode 100644 index 000000000000..65e69a40a91a --- /dev/null +++ b/drivers/leds/leds-acer-a500.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Power button LED driver for Acer Iconia Tab A500. + * + * Copyright 2020 GRATE-driver project. + */ + +#include +#include +#include + +#include + +struct a500_ec_led { + struct led_classdev cdev; + struct a500_ec_led *other_led; + const struct a500_ec_cmd *cmd; +}; + +/* cmd delay ms */ +A500_EC_COMMAND(RESET_LEDS, 0x40, 100); +A500_EC_COMMAND(POWER_LED_ON, 0x42, 100); +A500_EC_COMMAND(CHARGE_LED_ON, 0x43, 100); +A500_EC_COMMAND(ANDROID_LEDS_OFF, 0x5A, 100); + +static int a500_ec_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct device *a500_ec_leds_dev = led_cdev->dev->parent; + struct a500_ec *ec_chip = dev_get_drvdata(a500_ec_leds_dev->parent); + struct a500_ec_led *led = container_of(led_cdev, struct a500_ec_led, + cdev); + int ret; + + a500_ec_lock(ec_chip); + + if (value) { + ret = a500_ec_write_locked(ec_chip, led->cmd, 0); + } else { + /* + * There is no separate controls which can disable LEDs + * individually, there is only RESET_LEDS command that turns + * off both LEDs. + */ + ret = a500_ec_write_locked(ec_chip, RESET_LEDS, 0); + if (ret) + goto unlock; + + led = led->other_led; + + /* RESET_LEDS turns off both LEDs, thus restore other LED */ + if (led->cdev.brightness == LED_ON) + ret = a500_ec_write_locked(ec_chip, led->cmd, 0); + } + +unlock: + a500_ec_unlock(ec_chip); + + return ret; +} + +static int a500_ec_leds_probe(struct platform_device *pdev) +{ + struct a500_ec *ec_chip = dev_get_drvdata(pdev->dev.parent); + struct a500_ec_led *white_led, *orange_led; + int err; + + /* reset and turn off all LEDs */ + a500_ec_write(ec_chip, RESET_LEDS, 0); + a500_ec_write(ec_chip, ANDROID_LEDS_OFF, 0); + + white_led = devm_kzalloc(&pdev->dev, sizeof(*white_led), GFP_KERNEL); + if (!white_led) + return -ENOMEM; + + white_led->cdev.name = "power-button-white"; + white_led->cdev.brightness_set_blocking = a500_ec_led_brightness_set; + white_led->cdev.flags = LED_CORE_SUSPENDRESUME; + white_led->cdev.max_brightness = LED_ON; + white_led->cmd = &A500_EC_POWER_LED_ON; + + orange_led = devm_kzalloc(&pdev->dev, sizeof(*orange_led), GFP_KERNEL); + if (!orange_led) + return -ENOMEM; + + orange_led->cdev.name = "power-button-orange"; + orange_led->cdev.brightness_set_blocking = a500_ec_led_brightness_set; + orange_led->cdev.flags = LED_CORE_SUSPENDRESUME; + orange_led->cdev.max_brightness = LED_ON; + orange_led->cmd = &A500_EC_CHARGE_LED_ON; + + white_led->other_led = orange_led; + orange_led->other_led = white_led; + + err = devm_led_classdev_register(&pdev->dev, &white_led->cdev); + if (err) { + dev_err(&pdev->dev, "failed to register white LED\n"); + return err; + } + + err = devm_led_classdev_register(&pdev->dev, &orange_led->cdev); + if (err) { + dev_err(&pdev->dev, "failed to register orange LED\n"); + return err; + } + + return 0; +} + +static struct platform_driver a500_ec_leds_driver = { + .driver = { + .name = "acer-a500-iconia-leds", + }, + .probe = a500_ec_leds_probe, +}; +module_platform_driver(a500_ec_leds_driver); + +MODULE_DESCRIPTION("LED driver for Acer Iconia Tab A500 Power Button"); +MODULE_AUTHOR("Dmitry Osipenko "); +MODULE_ALIAS("platform:acer-a500-iconia-leds"); +MODULE_LICENSE("GPL v2");