From patchwork Sun Oct 9 06:27:12 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Icenowy Zheng X-Patchwork-Id: 679998 Return-Path: X-Original-To: incoming-imx@patchwork.ozlabs.org Delivered-To: patchwork-incoming-imx@bilbo.ozlabs.org Received: from bombadil.infradead.org (bombadil.infradead.org [IPv6:2001:1868:205::9]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3ssD104qfPz9s9N for ; Sun, 9 Oct 2016 17:31:16 +1100 (AEDT) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=aosc.xyz header.i=@aosc.xyz header.b=m42SLv5k; dkim-atps=neutral Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.85_2 #1 (Red Hat Linux)) id 1bt7bJ-0007d9-N4; Sun, 09 Oct 2016 06:28:45 +0000 Received: from forward17h.cmail.yandex.net ([2a02:6b8:0:f35::a2]) by bombadil.infradead.org with esmtps (Exim 4.85_2 #1 (Red Hat Linux)) id 1bt7bC-0007a7-8B for linux-arm-kernel@lists.infradead.org; Sun, 09 Oct 2016 06:28:42 +0000 Received: from smtp3j.mail.yandex.net (smtp3j.mail.yandex.net [IPv6:2a02:6b8:0:801:1::12]) by forward17h.cmail.yandex.net (Yandex) with ESMTP id 80A9B2135C; Sun, 9 Oct 2016 09:28:12 +0300 (MSK) Received: from smtp3j.mail.yandex.net (localhost.localdomain [127.0.0.1]) by smtp3j.mail.yandex.net (Yandex) with ESMTP id 7E31E6240959; Sun, 9 Oct 2016 09:28:00 +0300 (MSK) Received: by smtp3j.mail.yandex.net (nwsmtp/Yandex) with ESMTPSA id MYSh2WzRiA-RrrGAH0w; Sun, 09 Oct 2016 09:27:58 +0300 (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (Client certificate not present) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=aosc.xyz; s=mail; t=1475994479; bh=5qgW88aecFdTcufziCm4EUNoX3pjnnOnieyZKlJP028=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References; b=m42SLv5kL8LRbYWcIzVpEzydX36uFypLxVS74gt5TFJCpkBUjRLwIZK9LZ5QiVtz8 WvMezv4inWoC7+7hREYrX7uXoYOwHyhcKnBFrHaHY9r7j4oL3s4bD/6aH4mcSxeol4 5Y4q+UP/vxamRnqMEvnzFXlnUES5j+TrbBEwMRlA= Authentication-Results: smtp3j.mail.yandex.net; dkim=pass header.i=@aosc.xyz X-Yandex-ForeignMX: FR X-Yandex-Suid-Status: 1 0, 1 0, 1 0, 1 0, 1 0, 1 0, 1 0, 1 0, 1 0, 1 0, 1 0, 1 0, 1 0, 1 1130000036118848 From: Icenowy Zheng To: Rob Herring , Chen-Yu Tsai , Maxime Ripard , Lee Jones , =?UTF-8?q?Bruno=20Pr=C3=A9mont?= , Michael Haas Subject: [PATCH v2 2/4] power: add axp20x-battery driver Date: Sun, 9 Oct 2016 14:27:12 +0800 Message-Id: <20161009062714.5085-2-icenowy@aosc.xyz> X-Mailer: git-send-email 2.10.0 In-Reply-To: <20161009062714.5085-1-icenowy@aosc.xyz> References: <20161009062714.5085-1-icenowy@aosc.xyz> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20161008_232838_748753_7026518D X-CRM114-Status: GOOD ( 22.98 ) X-Spam-Score: -2.7 (--) X-Spam-Report: SpamAssassin version 3.4.0 on bombadil.infradead.org summary: Content analysis details: (-2.7 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.7 RCVD_IN_DNSWL_LOW RBL: Sender listed at http://www.dnswl.org/, low trust [2a02:6b8:0:f35:0:0:0:a2 listed in] [list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Mark Rutland , devicetree@vger.kernel.org, linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, Russell King , Icenowy Zheng , Sebastian Reichel , linux-arm-kernel@lists.infradead.org Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+incoming-imx=patchwork.ozlabs.org@lists.infradead.org List-Id: linux-imx-kernel.lists.patchwork.ozlabs.org This driver is the battery power supply driver of axp20x PMIC. This driver currently contains only capacity and current properties (for axp20x only capacity is supported now) , however it's easy to implement more properties. Signed-off-by: Icenowy Zheng --- Changes since v1: - Add initial support for AXP20x. drivers/mfd/axp20x.c | 28 ++++ drivers/power/supply/Makefile | 1 + drivers/power/supply/axp20x_battery.c | 277 ++++++++++++++++++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 drivers/power/supply/axp20x_battery.c diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c index 17877b2..ebccb22 100644 --- a/drivers/mfd/axp20x.c +++ b/drivers/mfd/axp20x.c @@ -141,6 +141,15 @@ static struct resource axp20x_ac_power_supply_resources[] = { DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_ACIN_OVER_V, "ACIN_OVER_V"), }; +static struct resource axp20x_battery_resources[] = { + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_PLUGIN, "BATT_PLUGIN"), + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_REMOVAL, "BATT_REMOVAL"), + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_ENT_ACT_MODE, "BATT_ENT_ACT_MODE"), + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_EXIT_ACT_MODE, "BATT_EXIT_ACT_MODE"), + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_CHARG, "CHARG"), + DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_CHARG_DONE, "CHARG_DONE"), +}; + static struct resource axp20x_pek_resources[] = { { .name = "PEK_DBR", @@ -162,6 +171,15 @@ static struct resource axp20x_usb_power_supply_resources[] = { DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_VBUS_NOT_VALID, "VBUS_NOT_VALID"), }; +static struct resource axp22x_battery_resources[] = { + DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_BATT_PLUGIN, "BATT_PLUGIN"), + DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_BATT_REMOVAL, "BATT_REMOVAL"), + DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_BATT_ENT_ACT_MODE, "BATT_ENT_ACT_MODE"), + DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_BATT_EXIT_ACT_MODE, "BATT_EXIT_ACT_MODE"), + DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_CHARG, "CHARG"), + DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_CHARG_DONE, "CHARG_DONE"), +}; + static struct resource axp22x_usb_power_supply_resources[] = { DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_PLUGIN, "VBUS_PLUGIN"), DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_REMOVAL, "VBUS_REMOVAL"), @@ -520,6 +538,11 @@ static struct mfd_cell axp20x_cells[] = { .num_resources = ARRAY_SIZE(axp20x_ac_power_supply_resources), .resources = axp20x_ac_power_supply_resources, }, { + .name = "axp20x-battery", + .of_compatible = "x-powers,axp202-battery", + .num_resources = ARRAY_SIZE(axp20x_battery_resources), + .resources = axp20x_battery_resources, + }, { .name = "axp20x-usb-power-supply", .of_compatible = "x-powers,axp202-usb-power-supply", .num_resources = ARRAY_SIZE(axp20x_usb_power_supply_resources), @@ -535,6 +558,11 @@ static struct mfd_cell axp22x_cells[] = { }, { .name = "axp20x-regulator", }, { + .name = "axp20x-battery", + .of_compatible = "x-powers,axp221-battery", + .num_resources = ARRAY_SIZE(axp22x_battery_resources), + .resources = axp22x_battery_resources, + }, { .name = "axp20x-usb-power-supply", .of_compatible = "x-powers,axp221-usb-power-supply", .num_resources = ARRAY_SIZE(axp22x_usb_power_supply_resources), diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 36c599d..01271d3 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o obj-$(CONFIG_PDA_POWER) += pda_power.o obj-$(CONFIG_APM_POWER) += apm_power.o +obj-$(CONFIG_AXP20X_POWER) += axp20x_battery.o obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o obj-$(CONFIG_MAX8925_POWER) += max8925_power.o obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c new file mode 100644 index 0000000..67ee58f --- /dev/null +++ b/drivers/power/supply/axp20x_battery.c @@ -0,0 +1,277 @@ +/* + * AXP20x PMIC battery status driver + * + * Copyright (C) 2016 Icenowy Zheng + * Copyright (C) 2015 Hans de Goede + * Copyright (C) 2014 Bruno Prémont + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "axp20x-battery" + +#define AXP20X_PWR_STATUS_ACIN_USED BIT(6) +#define AXP20X_PWR_STATUS_VBUS_USED BIT(4) +#define AXP20X_PWR_STATUS_BAT_DIRECTION BIT(2) + +#define AXP20X_OP_MODE_CHARGING BIT(6) +#define AXP20X_OP_MODE_BATTERY_PRESENT BIT(5) +#define AXP20X_OP_MODE_BATTERY_ACTIVE BIT(3) + +#define AXP20X_CAPACITY_CORRECT BIT(7) + +struct axp20x_battery { + struct axp20x_dev *axp20x; + struct regmap *regmap; + struct power_supply *supply; +}; + +static irqreturn_t axp20x_battery_irq(int irq, void *devid) +{ + struct axp20x_battery *power = devid; + + power_supply_changed(power->supply); + + return IRQ_HANDLED; +} + +static int axp20x_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct axp20x_battery *power = power_supply_get_drvdata(psy); + unsigned int input, op_mode, capacity, dh, dl; + int ret, ext; + + /* All the properties below need the input-status reg value */ + ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &input); + if (ret) + return ret; + + ret = regmap_read(power->regmap, AXP20X_PWR_OP_MODE, &op_mode); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (!(op_mode & AXP20X_OP_MODE_BATTERY_PRESENT)) { + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + break; + } + + val->intval = POWER_SUPPLY_HEALTH_GOOD; + + if (op_mode & AXP20X_OP_MODE_BATTERY_ACTIVE) { + /* AXP20X is now trying to re-activate the battery */ + val->intval = POWER_SUPPLY_HEALTH_DEAD; + break; + } + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = !!(op_mode & AXP20X_OP_MODE_BATTERY_PRESENT); + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = regmap_read(power->regmap, AXP20X_FG_RES, &capacity); + if (ret) + return ret; + if (capacity & AXP20X_CAPACITY_CORRECT) + val->intval = capacity & (~AXP20X_CAPACITY_CORRECT); + else + return -EIO; + /* from axp_capchange function of Allwinner 3.4 driver */ + if (val->intval == 127) + val->intval = 100; + break; + case POWER_SUPPLY_PROP_STATUS: + if (!(op_mode & AXP20X_OP_MODE_BATTERY_PRESENT)) { + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + ret = regmap_read(power->regmap, AXP20X_FG_RES, &capacity); + if (ret) + return ret; + + ext = (input & AXP20X_PWR_STATUS_ACIN_USED) || + (input & AXP20X_PWR_STATUS_VBUS_USED); + + if (op_mode & AXP20X_OP_MODE_CHARGING) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (!(input & AXP20X_PWR_STATUS_BAT_DIRECTION) && !ext) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (capacity == (100 | AXP20X_CAPACITY_CORRECT) || + capacity == (127 | AXP20X_CAPACITY_CORRECT)) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = regmap_read(power->regmap, AXP20X_BATT_DISCHRG_I_H, &dh); + if (ret) + return ret; + ret = regmap_read(power->regmap, AXP20X_BATT_DISCHRG_I_L, &dl); + if (ret) + return ret; + /* it's a 12 bit integer, high 8-bit is stored in dh */ + val->intval = dh << 4 | dl >> 4; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = regmap_read(power->regmap, AXP20X_BATT_V_H, &dh); + if (ret) + return ret; + ret = regmap_read(power->regmap, AXP20X_BATT_V_L, &dl); + if (ret) + return ret; + /* it's a 12 bit integer, high 8-bit is stored in dh */ + val->intval = dh << 4 | dl >> 4; + /* The formula below is from axp22_vbat_to_mV function + * of Allwinner 3.4 kernel. + */ + val->intval = val->intval * 1100 / 1000; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property axp20x_battery_properties[] = { + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, +}; + +static const struct power_supply_desc axp20x_battery_desc = { + .name = "axp20x-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = axp20x_battery_properties, + .num_properties = ARRAY_SIZE(axp20x_battery_properties), + .get_property = axp20x_battery_get_property, +}; + +static enum power_supply_property axp22x_battery_properties[] = { + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static const struct power_supply_desc axp22x_battery_desc = { + .name = "axp20x-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = axp22x_battery_properties, + .num_properties = ARRAY_SIZE(axp22x_battery_properties), + .get_property = axp20x_battery_get_property, +}; + +static int axp20x_battery_probe(struct platform_device *pdev) +{ + struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config psy_cfg = {}; + struct axp20x_battery *power; + static const char * const axp20x_irq_names[] = { + "BATT_PLUGIN", "BATT_REMOVAL", "BATT_ENT_ACT_MODE", + "BATT_EXIT_ACT_MODE", "CHARG", "CHARG_DONE", NULL }; + static const char * const axp22x_irq_names[] = { + "BATT_PLUGIN", "BATT_REMOVAL", "BATT_ENT_ACT_MODE", + "BATT_EXIT_ACT_MODE", "CHARG", "CHARG_DONE", NULL }; + static const char * const *irq_names; + const struct power_supply_desc *battery_desc; + int i, irq, ret; + + if (!of_device_is_available(pdev->dev.of_node)) + return -ENODEV; + + if (!axp20x) { + dev_err(&pdev->dev, "Parent drvdata not set\n"); + return -EINVAL; + } + + power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); + if (!power) + return -ENOMEM; + + power->axp20x = axp20x; + power->regmap = axp20x->regmap; + + switch (power->axp20x->variant) { + case AXP202_ID: + case AXP209_ID: + battery_desc = &axp20x_battery_desc; + irq_names = axp20x_irq_names; + break; + case AXP221_ID: + case AXP223_ID: + battery_desc = &axp22x_battery_desc; + irq_names = axp22x_irq_names; + break; + default: + dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n", + axp20x->variant); + return -EINVAL; + } + + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = power; + + power->supply = devm_power_supply_register(&pdev->dev, battery_desc, + &psy_cfg); + if (IS_ERR(power->supply)) + return PTR_ERR(power->supply); + + /* Request irqs after registering, as irqs may trigger immediately */ + for (i = 0; irq_names[i]; i++) { + irq = platform_get_irq_byname(pdev, irq_names[i]); + if (irq < 0) { + dev_warn(&pdev->dev, "No IRQ for %s: %d\n", + irq_names[i], irq); + continue; + } + irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); + ret = devm_request_any_context_irq(&pdev->dev, irq, + axp20x_battery_irq, 0, DRVNAME, power); + if (ret < 0) + dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n", + irq_names[i], ret); + } + + return 0; +} + +static const struct of_device_id axp20x_battery_match[] = { + { .compatible = "x-powers,axp202-battery" }, + { } +}; +MODULE_DEVICE_TABLE(of, axp20x_battery_match); + +static struct platform_driver axp20x_battery_driver = { + .probe = axp20x_battery_probe, + .driver = { + .name = DRVNAME, + .of_match_table = axp20x_battery_match, + }, +}; + +module_platform_driver(axp20x_battery_driver); + +MODULE_AUTHOR("Icenowy Zheng "); +MODULE_DESCRIPTION("AXP20x PMIC battery status driver"); +MODULE_LICENSE("GPL");