From patchwork Tue Nov 16 12:24:48 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 12622287 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 369D0C433F5 for ; Tue, 16 Nov 2021 12:25:04 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1C15361B72 for ; Tue, 16 Nov 2021 12:25:04 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236068AbhKPM15 (ORCPT ); Tue, 16 Nov 2021 07:27:57 -0500 Received: from mail-lf1-f49.google.com ([209.85.167.49]:37652 "EHLO mail-lf1-f49.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235642AbhKPM1z (ORCPT ); Tue, 16 Nov 2021 07:27:55 -0500 Received: by mail-lf1-f49.google.com with SMTP id c32so52748280lfv.4; Tue, 16 Nov 2021 04:24:57 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=WMO0t1uurBTIxOJVQPcqP087oNF80LPwcuDfiYz5sTc=; b=HGeLvGa63Ga9JaPrU2ZSdYPGKAm0gvY0YhnMlXeA1KGDFf+dn7k1Y4rnbYChZv/XNt 2gPNxrxgRHSDFk8maNd0yhYonXBB6zNGLv2YbDVFMXb81OAxA9TX+zdmC1oTtAmDqTh3 2ysO5bCXQUYepISwaNok876bsfmWFdLLb3RNCRi2BnTSA7wpXIHqCttfMAWyRpTorxtD EAGgaI26VKGkgeev+5hqhlN0Neyt0ErGSOlMIoMvCNEi8JTUY0XF9OSDneTXpCGB6Mfn tL48V9Wvxq6sZ1nWPM4WpqUu6HzVZ73euU85U0zbkgaPi3/QnMCHDvyAiPNPIhF/uTF8 2R9Q== X-Gm-Message-State: AOAM5320dKnR6uHvt3k6AhVsgMT/T37VUmdP3S/XDE4BOKBQRkTrSU5M oXh6Ol7oAq01eabGq5cjvt6NoeYISH8= X-Google-Smtp-Source: ABdhPJzyWC25zTahuHJUiyGqcWfYWpCE1g//4OwXQ3ZQhPay2qJv95aklcqE7RGr4Wqyt9UJNVNJTA== X-Received: by 2002:a19:c50f:: with SMTP id w15mr6320960lfe.479.1637065496615; Tue, 16 Nov 2021 04:24:56 -0800 (PST) Received: from fedora (dc73szyyyyyyyyyyyyycy-3.rev.dnainternet.fi. [2001:14ba:16ee:fa00::4]) by smtp.gmail.com with ESMTPSA id i18sm1745409lfe.186.2021.11.16.04.24.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Nov 2021 04:24:56 -0800 (PST) Date: Tue, 16 Nov 2021 14:24:48 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Sebastian Reichel , Rob Herring , Matti Vaittinen , Lee Jones , Linus Walleij , rostokus@gmail.com, fan.chen@mediatek.com, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-power@fi.rohmeurope.com Subject: [RFC PATCH v3 1/9] dt-bindings: battery: Add temperature-capacity degradation table Message-ID: <740503b6b6439e01959016223f1ae464e82824c3.1637061794.git.matti.vaittinen@fi.rohmeurope.com> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org Some charger/battery vendors describe the temperature impact to battery capacity by providing tables with capacity change at given temperature. Support providing this temperature - capacity dependency using the simple-battery DT nodes. Signed-off-by: Matti Vaittinen --- .../bindings/power/supply/battery.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Documentation/devicetree/bindings/power/supply/battery.yaml b/Documentation/devicetree/bindings/power/supply/battery.yaml index d56ac484fec5..98cc85b92a71 100644 --- a/Documentation/devicetree/bindings/power/supply/battery.yaml +++ b/Documentation/devicetree/bindings/power/supply/battery.yaml @@ -114,6 +114,25 @@ properties: - description: alert when battery temperature is lower than this value - description: alert when battery temperature is higher than this value + temp-degrade-table: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + description: | + An array of value triplets. First value is "capacity change per degree C" + when temperature differs from 'set point'. Second value is "capacity + degradation at given 'set point' temperature" and third value is "the + 'set-point' temperature" where given degradation is correct. + Up to 100 value triplets can be provided to specify different degradation + for different temperature ranges. When capacity change caused by + temperatures is computed the range which 'set point' is closest to the + current temperature is used. Capacity change should be in units of + micro Ah. Temperature is in units of 0.1 C degree. + maxItems: 100 + items: + items: + - description: capacity drop per degree C in micro Ah + - description: capacity drop at 'set point' temperature in micro Ah + - description: 'set point' temperature for this range in 0.1 degree C + required: - compatible From patchwork Tue Nov 16 12:25:25 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 12622289 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 43A7AC433F5 for ; Tue, 16 Nov 2021 12:25:37 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1BF16619E1 for ; Tue, 16 Nov 2021 12:25:37 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232166AbhKPM2c (ORCPT ); Tue, 16 Nov 2021 07:28:32 -0500 Received: from mail-lf1-f42.google.com ([209.85.167.42]:45647 "EHLO mail-lf1-f42.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236128AbhKPM2c (ORCPT ); Tue, 16 Nov 2021 07:28:32 -0500 Received: by mail-lf1-f42.google.com with SMTP id m27so28340076lfj.12; Tue, 16 Nov 2021 04:25:34 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=+WRFZ9VTjiWEB6JeEorrAKAYQgvA2HAsGTHlfXpHVoY=; b=P8XIKHkEG7wV7FH5SFlBLb2N/Hlm+AdDOhMdFF3Z2u9s+gg22UC7ygZGclKDhvTY2C oMatuZp/vjpgydbKducZIbd+Cjd3OeTOa1nim5ZSEwaiKpYGf7oleQ/7jQi6so0jlBCe TlbVrU7YGNrzq465vkpOlbOGwKxg66+zKeKVRdoJNSsS6tpAffjAW4dq3OpK1IURFlvL tsO+NCNSFIjabCfyKle3k2dc8Sxq3mA7b8vdWn4tDtUWck1CyI58ODiR60KiTwIdh/rs ng8O5Dy6hx/RLz8lshFxF7bjmHtDHs8qnnXLUd8mb3fW/3xLO5O/qy/FqVHlhO5hBk1b Iejg== X-Gm-Message-State: AOAM533gsu40xhLfuXQsjCT89KdHcIeBopgfyL/INQIL7KOc8RJB5t8G ioWelCHIlRRcWLTe+2tFqx/T86Dx2aM= X-Google-Smtp-Source: ABdhPJyr+pHVzUU8nxYhtR5y3GtUCUKJqDylMndPFm6NlnXqESbFAJ8Ri30tuv2QeT2WLX1z+5ABbQ== X-Received: by 2002:ac2:4c85:: with SMTP id d5mr6394851lfl.452.1637065533851; Tue, 16 Nov 2021 04:25:33 -0800 (PST) Received: from fedora (dc73szyyyyyyyyyyyyycy-3.rev.dnainternet.fi. [2001:14ba:16ee:fa00::4]) by smtp.gmail.com with ESMTPSA id x40sm1938781lfa.130.2021.11.16.04.25.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Nov 2021 04:25:33 -0800 (PST) Date: Tue, 16 Nov 2021 14:25:25 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Sebastian Reichel , Rob Herring , Matti Vaittinen , Lee Jones , Linus Walleij , rostokus@gmail.com, fan.chen@mediatek.com, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-power@fi.rohmeurope.com Subject: [RFC PATCH v3 2/9] power: supply: add cap2ocv batinfo helper Message-ID: <20cfdc60b148646a0473640a8efdb056b207c56e.1637061794.git.matti.vaittinen@fi.rohmeurope.com> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org The power-supply core supports concept of OCV (Open Circuit Voltage) => SOC (State Of Charge) conversion tables. Usually these tables are used to estimate SOC based on OCV. Some systems use so called "Zero Adjust" where at the near end-of-battery condition the SOC from coulomb counter is used to retrieve the OCV - and OCV and VSYS difference is used to re-estimate the battery capacity. Add helper to do look-up the other-way around and also get the OCV based on SOC Signed-off-by: Matti Vaittinen --- RFC v3: - Kerneldoc fixes --- drivers/power/supply/power_supply_core.c | 69 ++++++++++++++++++++++++ include/linux/power_supply.h | 5 ++ 2 files changed, 74 insertions(+) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index fc12a4f407f4..295672165836 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -806,6 +806,48 @@ int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *t } EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple); +/** + * power_supply_dcap2ocv_simple() - find the battery OCV by capacity + * @table: Pointer to battery OCV/CAP lookup table + * @table_len: OCV/CAP table length + * @cap: Current cap value in units of 0.1% + * + * OCV (Open Circuit Voltage) is often used to estimate the battery SOC (State + * Of Charge). Usually conversion tables are used to store the corresponding + * OCV and SOC. Systems which use so called "Zero Adjust" where at the near + * end-of-battery condition the SOC from coulomb counter is used to retrieve + * the OCV - and OCV and VSYS difference is used to re-estimate the battery + * capacity. This helper function can be used to look up battery OCV according + * to current capacity value from one OCV table, and the OCV table must be + * ordered descending. + * + * Return: the battery OCV in uV. + */ +int power_supply_dcap2ocv_simple(struct power_supply_battery_ocv_table *table, + int table_len, int dcap) +{ + int i, ocv, tmp; + + for (i = 0; i < table_len; i++) + if (dcap > table[i].capacity * 10) + break; + + if (i > 0 && i < table_len) { + tmp = (table[i - 1].ocv - table[i].ocv) * + (dcap - table[i].capacity * 10); + + tmp /= (table[i - 1].capacity - table[i].capacity) * 10; + ocv = tmp + table[i].ocv; + } else if (i == 0) { + ocv = table[0].ocv; + } else { + ocv = table[table_len - 1].ocv; + } + + return ocv; +} +EXPORT_SYMBOL_GPL(power_supply_dcap2ocv_simple); + /** * power_supply_ocv2cap_simple() - find the battery capacity * @table: Pointer to battery OCV lookup table @@ -866,6 +908,33 @@ power_supply_find_ocv2cap_table(struct power_supply_battery_info *info, } EXPORT_SYMBOL_GPL(power_supply_find_ocv2cap_table); +/** + * power_supply_batinfo_dcap2ocv() - Compute OCV based on SOC + * @info: Pointer to battery information. + * @dcao: Battery capacity in units of 0.1% + * @temp: Temperatur in Celsius + * + * Compute the open circuit voltage at given temperature matching given + * capacity for a battery described by given battery info. Computation is + * done based on tables of known capacity - open circuit voltage value pairs. + * Requires the OCV tables being populated in the battery info. + * + * Return: The battery OCV in uV or -EINVAL if OCV table is not available. + */ +int power_supply_batinfo_dcap2ocv(struct power_supply_battery_info *info, + int dcap, int temp) +{ + struct power_supply_battery_ocv_table *table; + int table_len; + + table = power_supply_find_ocv2cap_table(info, temp, &table_len); + if (!table) + return -EINVAL; + + return power_supply_dcap2ocv_simple(table, table_len, dcap); +} +EXPORT_SYMBOL_GPL(power_supply_batinfo_dcap2ocv); + int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info, int ocv, int temp) { diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 9ca1f120a211..fa8cf434f7e3 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -410,11 +410,16 @@ extern void power_supply_put_battery_info(struct power_supply *psy, struct power_supply_battery_info *info); extern int power_supply_ocv2cap_simple(struct power_supply_battery_ocv_table *table, int table_len, int ocv); +int power_supply_dcap2ocv_simple(struct power_supply_battery_ocv_table *table, + int table_len, int dcap); + extern struct power_supply_battery_ocv_table * power_supply_find_ocv2cap_table(struct power_supply_battery_info *info, int temp, int *table_len); extern int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info, int ocv, int temp); +int power_supply_batinfo_dcap2ocv(struct power_supply_battery_info *info, + int dcap, int temp); extern int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table, int table_len, int temp); From patchwork Tue Nov 16 12:25:53 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 12622291 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5AF33C433F5 for ; Tue, 16 Nov 2021 12:26:10 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3DA5961882 for ; Tue, 16 Nov 2021 12:26:10 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236144AbhKPM3E (ORCPT ); Tue, 16 Nov 2021 07:29:04 -0500 Received: from mail-lf1-f41.google.com ([209.85.167.41]:46990 "EHLO mail-lf1-f41.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236140AbhKPM3B (ORCPT ); Tue, 16 Nov 2021 07:29:01 -0500 Received: by mail-lf1-f41.google.com with SMTP id b1so46840558lfs.13; Tue, 16 Nov 2021 04:26:03 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=cAXGZZ6nd6Kr3NLJSibWSA76ESUyMzTBbvb3SIYaImo=; b=TNOSPIT/o0WF2kMhCfxh9WlCzxF3PUchXP+4g+2HzgNLze5l+31z42b10I1iI1q5Hm Dz8bDBOKB/mupLXFrD84WZA9Zjac+WD1ipiuQok5e2DK8kjCV/rfJA8H2FG9fcA0HaT1 UN+AQP4lLdcDx4MLEKhHJSZF/TRhfUBzP6i/MLXHHl1hO2FASRznNqiuCcjEgldcDl0Y qc7Ws7DSNS3NhT0kxJZc40EiL7662Q0tfyUwQTw1nF6My3mZOql0Jc0GNEgdlBAduUdq jQis/TveZGn1acuIQbEwCmiiyecZ1Y5gyZSVIYm613DNZ4VHP7RJG2ec62TuKBOTBPJc n28Q== X-Gm-Message-State: AOAM533qVvTz7k+6eBH9ZGGf7NVxYhmfumuzIJn/4UcSVbDI2eDoDJq7 vwlV/I71ONZcXzhIFoiSTaI= X-Google-Smtp-Source: ABdhPJyMSv1GDEU5VDePN/B7WoOYnoIAsywv3Qg7QWaujWOdhjXMNHWtaDTFfMfRbTYMjrDi1MYYwA== X-Received: by 2002:a05:6512:20d4:: with SMTP id u20mr6321919lfr.339.1637065562990; Tue, 16 Nov 2021 04:26:02 -0800 (PST) Received: from fedora (dc73szyyyyyyyyyyyyycy-3.rev.dnainternet.fi. [2001:14ba:16ee:fa00::4]) by smtp.gmail.com with ESMTPSA id n4sm1938734lfu.70.2021.11.16.04.26.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Nov 2021 04:26:02 -0800 (PST) Date: Tue, 16 Nov 2021 14:25:53 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Sebastian Reichel , Rob Herring , Matti Vaittinen , Lee Jones , Linus Walleij , rostokus@gmail.com, fan.chen@mediatek.com, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-power@fi.rohmeurope.com Subject: [RFC PATCH v3 3/9] power: supply: Support DT originated temperature-capacity tables Message-ID: <6123f62ac44e6513a498d15034a4b6b22abe5f5b.1637061794.git.matti.vaittinen@fi.rohmeurope.com> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org Support obtaining the "capacity degradation by temperature" - tables from device-tree to batinfo. Signed-off-by: Matti Vaittinen --- RFCv3: - rename simple_gauge_temp_degr to power_supply_temp_degr --- drivers/power/supply/power_supply_core.c | 53 ++++++++++++++++++++++++ include/linux/power_supply.h | 26 ++++++++++++ 2 files changed, 79 insertions(+) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 295672165836..1a21f692ab81 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -562,10 +562,12 @@ struct power_supply *devm_power_supply_get_by_phandle(struct device *dev, EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle); #endif /* CONFIG_OF */ +#define POWER_SUPPLY_TEMP_DGRD_MAX_VALUES 100 int power_supply_get_battery_info(struct power_supply *psy, struct power_supply_battery_info *info) { struct power_supply_resistance_temp_table *resist_table; + u32 *dgrd_table; struct device_node *battery_np; const char *value; int err, len, index; @@ -588,6 +590,8 @@ int power_supply_get_battery_info(struct power_supply *psy, info->temp_max = INT_MAX; info->factory_internal_resistance_uohm = -EINVAL; info->resist_table = NULL; + info->temp_dgrd_values = 0; + info->temp_dgrd = NULL; for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) { info->ocv_table[index] = NULL; @@ -677,6 +681,55 @@ int power_supply_get_battery_info(struct power_supply *psy, of_property_read_u32_index(battery_np, "operating-range-celsius", 1, &info->temp_max); + len = of_property_count_u32_elems(battery_np, "temp-degrade-table"); + if (len == -EINVAL) + len = 0; + if (len < 0) { + err = len; + goto out_put_node; + } + /* table should consist of value pairs - maximum of 100 pairs */ + if (len % 3 || len / 3 > POWER_SUPPLY_TEMP_DGRD_MAX_VALUES) { + dev_warn(&psy->dev, + "bad amount of temperature-capacity degrade values\n"); + err = -EINVAL; + goto out_put_node; + } + info->temp_dgrd_values = len / 3; + if (info->temp_dgrd_values) { + info->temp_dgrd = devm_kcalloc(&psy->dev, + info->temp_dgrd_values, + sizeof(*info->temp_dgrd), + GFP_KERNEL); + if (!info->temp_dgrd) { + err = -ENOMEM; + goto out_put_node; + } + dgrd_table = kcalloc(len, sizeof(*dgrd_table), GFP_KERNEL); + if (!dgrd_table) { + err = -ENOMEM; + goto out_put_node; + } + err = of_property_read_u32_array(battery_np, + "temp-degrade-table", + dgrd_table, len); + if (err) { + dev_warn(&psy->dev, + "bad temperature - capacity degrade values %d\n", err); + kfree(dgrd_table); + info->temp_dgrd_values = 0; + goto out_put_node; + } + for (index = 0; index < info->temp_dgrd_values; index++) { + struct power_supply_temp_degr *d = &info->temp_dgrd[index]; + + d->temp_degrade_1C = dgrd_table[index * 3]; + d->degrade_at_set = dgrd_table[index * 3 + 1]; + d->temp_set_point = dgrd_table[index * 3 + 2]; + } + kfree(dgrd_table); + } + len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius"); if (len < 0 && len != -EINVAL) { err = len; diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index fa8cf434f7e3..fbc07d403f41 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -214,6 +214,30 @@ union power_supply_propval { struct device_node; struct power_supply; +/** + * struct power_supply_temp_degr - impact of temperature to battery capacity + * + * Usually temperature impacts on battery capacity. For systems where it is + * sufficient to describe capacity change as a series of temperature ranges + * where the change is linear (Eg delta cap = temperature_change * constant + + * offset) can be described by this structure. + * + * Please note - in order to avoid unnecessary rounding errors the change + * of capacity (uAh) is per change of temperature degree C while the temperature + * range floor is in tenths of degree C + * + * @temp_set_point: Temperature where cap change is as given in + * degrade_at_set. Units are 0.1 degree C + * @degrade_at_set: Capacity difference (from ideal) at temp_set_point + * temperature + * @temp_degrade_1C: Capacity change / temperature change (uAh / degree C) + */ +struct power_supply_temp_degr { + int temp_set_point; + int degrade_at_set; + int temp_degrade_1C; +}; + /* Run-time specific power supply configuration */ struct power_supply_config { struct device_node *of_node; @@ -377,6 +401,8 @@ struct power_supply_battery_info { int ocv_table_size[POWER_SUPPLY_OCV_TEMP_MAX]; struct power_supply_resistance_temp_table *resist_table; int resist_table_size; + int temp_dgrd_values; + struct power_supply_temp_degr *temp_dgrd; }; extern struct atomic_notifier_head power_supply_notifier; From patchwork Tue Nov 16 12:26:36 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 12622293 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 23E12C433F5 for ; Tue, 16 Nov 2021 12:27:02 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 05F8661A07 for ; Tue, 16 Nov 2021 12:27:02 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236178AbhKPM3z (ORCPT ); Tue, 16 Nov 2021 07:29:55 -0500 Received: from mail-lj1-f169.google.com ([209.85.208.169]:38857 "EHLO mail-lj1-f169.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236140AbhKPM3m (ORCPT ); Tue, 16 Nov 2021 07:29:42 -0500 Received: by mail-lj1-f169.google.com with SMTP id e9so42906655ljl.5; Tue, 16 Nov 2021 04:26:44 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=ieVf7eKj9Dxwh6oAOuZ5j/xJ0HObNRApjkdhF8clVTo=; b=ttUmrVfTcY4zx2R/F+dxSS/kjchqenN1TTrnTYml+oMd4eOGH6EWgORbfXMGvswkvk iMw/sh6Mkiyisk1lMo1jjiJo2T/hufHGu7NPoaoBRMjsEH6G4A9XKxj+Sbevdn6h76eY KVRNMslANov+ke0JUhYksgn0l3hb82JtVFFQUTdDgOcVAW6V36PZMtI53cRarK6rqlWF xCyJlHEQ5iVxUZ+WTDREDymOIWQEjMSKsn9G9XQkbEwFS+LbJPR1ds5n9yLi/zxX9DR5 CrQFDK/UTAGQp3xZx4vmjY8wSKXmAPg7qycvoF0RNuFefE5yELFw/v4zoUhMOo/Q1ydc n0ig== X-Gm-Message-State: AOAM530YUnDeRNAu+cbNluyUqjCdQGf5jXN/vVIC+51AKxOvv8xtSEYN 10S/ufhefEqSXq9Z0CCsEAY= X-Google-Smtp-Source: ABdhPJzUFR+1LwtwK4LKi7fXgE1y+eHknbo5BV6mjdpFByIawUeDYxCfV+O+BAxHRJS3a+zmM4+oQA== X-Received: by 2002:a05:651c:49b:: with SMTP id s27mr6367308ljc.404.1637065603266; Tue, 16 Nov 2021 04:26:43 -0800 (PST) Received: from fedora (dc73szyyyyyyyyyyyyycy-3.rev.dnainternet.fi. [2001:14ba:16ee:fa00::4]) by smtp.gmail.com with ESMTPSA id i18sm1742852lfv.147.2021.11.16.04.26.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Nov 2021 04:26:42 -0800 (PST) Date: Tue, 16 Nov 2021 14:26:36 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Sebastian Reichel , Rob Herring , Matti Vaittinen , Lee Jones , Linus Walleij , rostokus@gmail.com, fan.chen@mediatek.com, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-power@fi.rohmeurope.com Subject: [RFC PATCH v3 4/9] power: supply: Add batinfo getters usable prior supply registration Message-ID: <6645a55c05cf12954f97347ade1cf47ddf62bb86.1637061794.git.matti.vaittinen@fi.rohmeurope.com> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org In some cases it is beneficial to be able to query the static battery node properties prior power_supply registration. The device-tree parsing does not really depend on psy so add APIs which can be used without the power-supply. Also, convert APIs to operate on fwnode while at it. Signed-off-by: Matti Vaittinen Reviewed-by: Linus Walleij --- RFCv3: - New patch --- drivers/power/supply/power_supply_core.c | 279 ++++++++++++++--------- include/linux/power_supply.h | 5 + 2 files changed, 176 insertions(+), 108 deletions(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 1a21f692ab81..47176ed2570b 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -562,16 +562,63 @@ struct power_supply *devm_power_supply_get_by_phandle(struct device *dev, EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle); #endif /* CONFIG_OF */ +struct psy_int_tuple { + int a; + int b; +}; + +static int get_fwnode_tuple_array(struct device *dev, struct fwnode_handle *fw, + const char *name, + struct psy_int_tuple **tuple, int *num_tuple) +{ + int num_values, i, ret; + u32 *tmp_table; + + num_values = fwnode_property_count_u32(fw, name); + if (num_values <= 0) { + dev_err(dev, "failed to get %s\n", name); + return -EINVAL; + } + + if (num_values & 0x1) + dev_warn(dev, "odd number of '%s' values\n", name); + + tmp_table = kcalloc(num_values, sizeof(*tmp_table), GFP_KERNEL); + if (!tmp_table) + return -ENOMEM; + + *tuple = devm_kcalloc(dev, num_values / 2, sizeof(**tuple), + GFP_KERNEL); + if (!*tuple) { + ret = -ENOMEM; + goto out; + } + ret = fwnode_property_read_u32_array(fw, name, tmp_table, num_values); + if (ret) + goto out; + + *num_tuple = num_values / 2; + for (i = 0; i < *num_tuple; i++) { + (*tuple)[i].a = tmp_table[i * 2]; + (*tuple)[i].b = tmp_table[i * 2 + 1]; + } + +out: + kfree(tmp_table); + + return ret; +} + #define POWER_SUPPLY_TEMP_DGRD_MAX_VALUES 100 -int power_supply_get_battery_info(struct power_supply *psy, - struct power_supply_battery_info *info) +int power_supply_dev_get_battery_info(struct device *dev, + struct fwnode_handle *node, + struct power_supply_battery_info *info) { - struct power_supply_resistance_temp_table *resist_table; u32 *dgrd_table; - struct device_node *battery_np; - const char *value; + struct fwnode_handle *battery_node; int err, len, index; - const __be32 *list; + const char *value; + u32 tuple[2]; info->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; info->energy_full_design_uwh = -EINVAL; @@ -599,21 +646,23 @@ int power_supply_get_battery_info(struct power_supply *psy, info->ocv_table_size[index] = -EINVAL; } - if (!psy->of_node) { - dev_warn(&psy->dev, "%s currently only supports devicetree\n", - __func__); - return -ENXIO; - } + if (!node) + node = dev_fwnode(dev); - battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0); - if (!battery_np) + if (!node) { + dev_err(dev, "no charger node\n"); return -ENODEV; + } - err = of_property_read_string(battery_np, "compatible", &value); - if (err) - goto out_put_node; + battery_node = fwnode_find_reference(node, "monitored-battery", 0); + if (IS_ERR(battery_node)) { + dev_err(dev, "No battery node found\n"); + return PTR_ERR(battery_node); + } + + if (fwnode_property_match_string(battery_node, "compatible", + "simple-battery")) { - if (strcmp("simple-battery", value)) { err = -ENODEV; goto out_put_node; } @@ -622,8 +671,7 @@ int power_supply_get_battery_info(struct power_supply *psy, * in enum power_supply_property. For reasoning, see * Documentation/power/power_supply_class.rst. */ - - if (!of_property_read_string(battery_np, "device-chemistry", &value)) { + if (!fwnode_property_read_string(battery_node, "device-chemistry", &value)) { if (!strcmp("nickel-cadmium", value)) info->technology = POWER_SUPPLY_TECHNOLOGY_NiCd; else if (!strcmp("nickel-metal-hydride", value)) @@ -638,67 +686,73 @@ int power_supply_get_battery_info(struct power_supply *psy, else if (!strcmp("lithium-ion-manganese-oxide", value)) info->technology = POWER_SUPPLY_TECHNOLOGY_LiMn; else - dev_warn(&psy->dev, "%s unknown battery type\n", value); + dev_warn(dev, "%s unknown battery type\n", value); } - of_property_read_u32(battery_np, "energy-full-design-microwatt-hours", + fwnode_property_read_u32(battery_node, "energy-full-design-microwatt-hours", &info->energy_full_design_uwh); - of_property_read_u32(battery_np, "charge-full-design-microamp-hours", + fwnode_property_read_u32(battery_node, "charge-full-design-microamp-hours", &info->charge_full_design_uah); - of_property_read_u32(battery_np, "voltage-min-design-microvolt", + fwnode_property_read_u32(battery_node, "voltage-min-design-microvolt", &info->voltage_min_design_uv); - of_property_read_u32(battery_np, "voltage-max-design-microvolt", + fwnode_property_read_u32(battery_node, "voltage-max-design-microvolt", &info->voltage_max_design_uv); - of_property_read_u32(battery_np, "trickle-charge-current-microamp", + fwnode_property_read_u32(battery_node, "trickle-charge-current-microamp", &info->tricklecharge_current_ua); - of_property_read_u32(battery_np, "precharge-current-microamp", + fwnode_property_read_u32(battery_node, "precharge-current-microamp", &info->precharge_current_ua); - of_property_read_u32(battery_np, "precharge-upper-limit-microvolt", + fwnode_property_read_u32(battery_node, "precharge-upper-limit-microvolt", &info->precharge_voltage_max_uv); - of_property_read_u32(battery_np, "charge-term-current-microamp", + fwnode_property_read_u32(battery_node, "charge-term-current-microamp", &info->charge_term_current_ua); - of_property_read_u32(battery_np, "re-charge-voltage-microvolt", + fwnode_property_read_u32(battery_node, "re-charge-voltage-microvolt", &info->charge_restart_voltage_uv); - of_property_read_u32(battery_np, "over-voltage-threshold-microvolt", + fwnode_property_read_u32(battery_node, "over-voltage-threshold-microvolt", &info->overvoltage_limit_uv); - of_property_read_u32(battery_np, "constant-charge-current-max-microamp", + fwnode_property_read_u32(battery_node, "constant-charge-current-max-microamp", &info->constant_charge_current_max_ua); - of_property_read_u32(battery_np, "constant-charge-voltage-max-microvolt", + fwnode_property_read_u32(battery_node, "constant-charge-voltage-max-microvolt", &info->constant_charge_voltage_max_uv); - of_property_read_u32(battery_np, "factory-internal-resistance-micro-ohms", + fwnode_property_read_u32(battery_node, "factory-internal-resistance-micro-ohms", &info->factory_internal_resistance_uohm); - of_property_read_u32_index(battery_np, "ambient-celsius", - 0, &info->temp_ambient_alert_min); - of_property_read_u32_index(battery_np, "ambient-celsius", - 1, &info->temp_ambient_alert_max); - of_property_read_u32_index(battery_np, "alert-celsius", - 0, &info->temp_alert_min); - of_property_read_u32_index(battery_np, "alert-celsius", - 1, &info->temp_alert_max); - of_property_read_u32_index(battery_np, "operating-range-celsius", - 0, &info->temp_min); - of_property_read_u32_index(battery_np, "operating-range-celsius", - 1, &info->temp_max); - - len = of_property_count_u32_elems(battery_np, "temp-degrade-table"); + if (!fwnode_property_read_u32_array(battery_node, "ambient-celsius", + &tuple[0], 2)) { + info->temp_ambient_alert_min = tuple[0]; + info->temp_ambient_alert_max = tuple[1]; + } + + if (!fwnode_property_read_u32_array(battery_node, "alert-celsius", + &tuple[0], 2)) { + info->temp_alert_min = tuple[0]; + info->temp_alert_max = tuple[1]; + } + + if (!fwnode_property_read_u32_array(battery_node, + "operating-range-celsius", + &tuple[0], 2)) { + info->temp_min = tuple[0]; + info->temp_max = tuple[1]; + } + + len = fwnode_property_count_u32(battery_node, "temp-degrade-table"); if (len == -EINVAL) len = 0; if (len < 0) { + dev_err(dev, "malformed temp-degrade-table %d\n", len); err = len; goto out_put_node; } - /* table should consist of value pairs - maximum of 100 pairs */ + /* table should consist of value triplets - maximum of 100 triplets */ if (len % 3 || len / 3 > POWER_SUPPLY_TEMP_DGRD_MAX_VALUES) { - dev_warn(&psy->dev, + dev_warn(dev, "bad amount of temperature-capacity degrade values\n"); err = -EINVAL; goto out_put_node; } info->temp_dgrd_values = len / 3; if (info->temp_dgrd_values) { - info->temp_dgrd = devm_kcalloc(&psy->dev, - info->temp_dgrd_values, + info->temp_dgrd = devm_kcalloc(dev, info->temp_dgrd_values, sizeof(*info->temp_dgrd), GFP_KERNEL); if (!info->temp_dgrd) { @@ -710,12 +764,13 @@ int power_supply_get_battery_info(struct power_supply *psy, err = -ENOMEM; goto out_put_node; } - err = of_property_read_u32_array(battery_np, - "temp-degrade-table", - dgrd_table, len); + err = fwnode_property_read_u32_array(battery_node, + "temp-degrade-table", + dgrd_table, len); if (err) { - dev_warn(&psy->dev, - "bad temperature - capacity degrade values %d\n", err); + dev_warn(dev, + "bad temperature - capacity degrade values %d\n", + err); kfree(dgrd_table); info->temp_dgrd_values = 0; goto out_put_node; @@ -730,92 +785,100 @@ int power_supply_get_battery_info(struct power_supply *psy, kfree(dgrd_table); } - len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius"); - if (len < 0 && len != -EINVAL) { + len = fwnode_property_count_u32(battery_node, "ocv-capacity-celsius"); + if (len == -EINVAL) + len = 0; + + if (len < 0) { + dev_err(dev, "malformed ocv-capacity-celsius table\n"); err = len; goto out_put_node; } else if (len > POWER_SUPPLY_OCV_TEMP_MAX) { - dev_err(&psy->dev, "Too many temperature values\n"); + dev_err(dev, "Too many temperature values\n"); err = -EINVAL; goto out_put_node; } else if (len > 0) { - of_property_read_u32_array(battery_np, "ocv-capacity-celsius", - info->ocv_temp, len); + u32 *tmp; + + tmp = kcalloc(len, sizeof(*tmp), GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + fwnode_property_read_u32_array(battery_node, + "ocv-capacity-celsius", + tmp, len); + for (index = 0; index < len; index++) + info->ocv_temp[index] = tmp[index]; + + kfree(tmp); } for (index = 0; index < len; index++) { - struct power_supply_battery_ocv_table *table; char *propname; - int i, tab_len, size; propname = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d", index); - list = of_get_property(battery_np, propname, &size); - if (!list || !size) { - dev_err(&psy->dev, "failed to get %s\n", propname); + + err = get_fwnode_tuple_array(dev, battery_node, propname, + (struct psy_int_tuple **) + &info->ocv_table[index], + &info->ocv_table_size[index]); + if (err) { kfree(propname); - power_supply_put_battery_info(psy, info); - err = -EINVAL; + power_supply_dev_put_battery_info(dev, info); goto out_put_node; } - kfree(propname); - tab_len = size / (2 * sizeof(__be32)); - info->ocv_table_size[index] = tab_len; - - table = info->ocv_table[index] = - devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL); - if (!info->ocv_table[index]) { - power_supply_put_battery_info(psy, info); - err = -ENOMEM; - goto out_put_node; - } - - for (i = 0; i < tab_len; i++) { - table[i].ocv = be32_to_cpu(*list); - list++; - table[i].capacity = be32_to_cpu(*list); - list++; - } } - list = of_get_property(battery_np, "resistance-temp-table", &len); - if (!list || !len) - goto out_put_node; - - info->resist_table_size = len / (2 * sizeof(__be32)); - resist_table = info->resist_table = devm_kcalloc(&psy->dev, - info->resist_table_size, - sizeof(*resist_table), - GFP_KERNEL); - if (!info->resist_table) { - power_supply_put_battery_info(psy, info); - err = -ENOMEM; + err = get_fwnode_tuple_array(dev, battery_node, + "resistance-temp-table", + (struct psy_int_tuple **)&info->resist_table, + &info->resist_table_size); + if (err == -ENOMEM) { + power_supply_dev_put_battery_info(dev, info); goto out_put_node; } - - for (index = 0; index < info->resist_table_size; index++) { - resist_table[index].temp = be32_to_cpu(*list++); - resist_table[index].resistance = be32_to_cpu(*list++); - } + err = 0; out_put_node: - of_node_put(battery_np); + fwnode_handle_put(battery_node); + return err; + +} +EXPORT_SYMBOL_GPL(power_supply_dev_get_battery_info); + +int power_supply_get_battery_info(struct power_supply *psy, + struct power_supply_battery_info *info) +{ + struct fwnode_handle *fw = NULL; + + if (psy->of_node) + fw = of_fwnode_handle(psy->of_node); + + return power_supply_dev_get_battery_info(&psy->dev, fw, info); } EXPORT_SYMBOL_GPL(power_supply_get_battery_info); -void power_supply_put_battery_info(struct power_supply *psy, - struct power_supply_battery_info *info) +void power_supply_dev_put_battery_info(struct device *dev, + struct power_supply_battery_info *info) { int i; for (i = 0; i < POWER_SUPPLY_OCV_TEMP_MAX; i++) { if (info->ocv_table[i]) - devm_kfree(&psy->dev, info->ocv_table[i]); + devm_kfree(dev, info->ocv_table[i]); } if (info->resist_table) - devm_kfree(&psy->dev, info->resist_table); + devm_kfree(dev, info->resist_table); +} +EXPORT_SYMBOL_GPL(power_supply_dev_put_battery_info); + +void power_supply_put_battery_info(struct power_supply *psy, + struct power_supply_battery_info *info) +{ + power_supply_dev_put_battery_info(&psy->dev, info); } EXPORT_SYMBOL_GPL(power_supply_put_battery_info); diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index fbc07d403f41..ef7db73a5bd1 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -430,6 +430,11 @@ devm_power_supply_get_by_phandle(struct device *dev, const char *property) { return NULL; } #endif /* CONFIG_OF */ +void power_supply_dev_put_battery_info(struct device *dev, + struct power_supply_battery_info *info); +int power_supply_dev_get_battery_info(struct device *dev, + struct fwnode_handle *node, + struct power_supply_battery_info *info); extern int power_supply_get_battery_info(struct power_supply *psy, struct power_supply_battery_info *info); extern void power_supply_put_battery_info(struct power_supply *psy, From patchwork Tue Nov 16 12:27:07 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 12622295 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id F2215C433EF for ; Tue, 16 Nov 2021 12:27:27 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D083F61882 for ; Tue, 16 Nov 2021 12:27:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236082AbhKPMaX (ORCPT ); Tue, 16 Nov 2021 07:30:23 -0500 Received: from mail-lf1-f50.google.com ([209.85.167.50]:37670 "EHLO mail-lf1-f50.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236114AbhKPMaN (ORCPT ); Tue, 16 Nov 2021 07:30:13 -0500 Received: by mail-lf1-f50.google.com with SMTP id c32so52765827lfv.4; Tue, 16 Nov 2021 04:27:15 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=aalC5ofyXPHijBiHsgoqrQ/kcLW/eBv0HeQgHcvpoB4=; b=sUsbHyFqbILu2PLv6BuodPqDxhACAzW4ZqAdWF2IoTZ0iMLu9NWj2LWGkjSi/lmDYw RPXcbpTd+OhJhPOXv6vEZa8tX+QtzX9xwJcMcIWdB5qziqQQGVxusVyH4kBQTKtbk2kG utphzmrhi/r0fBEgxRrn0hlv5eKxXIcNfhz0RYVrPqtZfIik67gimWqIKDgooYuREkCc wpTDuaNG2n3NiCR6hYwRdqrWaBDri2VEu7Y0c4TVgw/cOxLwMm/SXJKSoB2h6PnBnz8G ZRjli3lxTUMwaPHEjUo1a47xO5P5oR91dlpZYO2VzBeAHRECg4/JgpfxSocaBXoLT5tz Jadg== X-Gm-Message-State: AOAM531ahVOnqPdnrgX+JYDmB9MxdU5fMHob5E8poZDZDWyWZ41mfaqT qOAIvljqsI1NEpFbv/1d9ng= X-Google-Smtp-Source: ABdhPJyulYjgXMfFelFdLJhLdwJWeX4lZBvTVo8ytXzSPMqyLcKBq8uKSLyz09uWLSILfxHjBI/hkQ== X-Received: by 2002:a05:6512:3f87:: with SMTP id x7mr6109171lfa.5.1637065634734; Tue, 16 Nov 2021 04:27:14 -0800 (PST) Received: from fedora (dc73szyyyyyyyyyyyyycy-3.rev.dnainternet.fi. [2001:14ba:16ee:fa00::4]) by smtp.gmail.com with ESMTPSA id t15sm1745407lfp.181.2021.11.16.04.27.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Nov 2021 04:27:14 -0800 (PST) Date: Tue, 16 Nov 2021 14:27:07 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Sebastian Reichel , Rob Herring , Matti Vaittinen , Lee Jones , Linus Walleij , rostokus@gmail.com, fan.chen@mediatek.com, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-power@fi.rohmeurope.com Subject: [RFC PATCH v3 5/9] power: supply: Add constant battery aging degradation to batinfo Message-ID: References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org Few batteries can be modelled to degrade at constant rate for each increased cycle. Add degradation constant for such simple model to batinfo. Signed-off-by: Matti Vaittinen --- RFCv3: New patch --- drivers/power/supply/power_supply_core.c | 3 +++ include/linux/power_supply.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 47176ed2570b..ebc961b5aa45 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -639,6 +639,7 @@ int power_supply_dev_get_battery_info(struct device *dev, info->resist_table = NULL; info->temp_dgrd_values = 0; info->temp_dgrd = NULL; + info->degrade_cycle_uah = 0; for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) { info->ocv_table[index] = NULL; @@ -689,6 +690,8 @@ int power_supply_dev_get_battery_info(struct device *dev, dev_warn(dev, "%s unknown battery type\n", value); } + fwnode_property_read_u32(battery_node, "degrade-cycle-microamp-hours", + &info->degrade_cycle_uah); fwnode_property_read_u32(battery_node, "energy-full-design-microwatt-hours", &info->energy_full_design_uwh); fwnode_property_read_u32(battery_node, "charge-full-design-microamp-hours", diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index ef7db73a5bd1..c5118265b3ab 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -403,6 +403,7 @@ struct power_supply_battery_info { int resist_table_size; int temp_dgrd_values; struct power_supply_temp_degr *temp_dgrd; + int degrade_cycle_uah; }; extern struct atomic_notifier_head power_supply_notifier; From patchwork Tue Nov 16 12:27:40 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 12622297 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id DED01C433EF for ; Tue, 16 Nov 2021 12:27:51 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id C834A61B3D for ; Tue, 16 Nov 2021 12:27:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236200AbhKPMar (ORCPT ); Tue, 16 Nov 2021 07:30:47 -0500 Received: from mail-lj1-f180.google.com ([209.85.208.180]:34527 "EHLO mail-lj1-f180.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236215AbhKPMaq (ORCPT ); Tue, 16 Nov 2021 07:30:46 -0500 Received: by mail-lj1-f180.google.com with SMTP id k23so14776263lje.1; Tue, 16 Nov 2021 04:27:49 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=FmTMfd20hULSCl+7yMqqQJbPyDaNAZmbbo2TAAoDw2E=; b=zwDfv9DjngjH3j/ctAG9XXDQxX5b3WQaTyedkk6RPrYag0foN0g5jiJFOfjK51zGfw UGvP5dKtm1QsBWi9/KQcPMQ0vmmcXWDZCtNkszuWyfOwu8GsXrC3ONBTwiC4RVMJEQ/K e1DdXrAHd/VyyGUXT3Y2S6n7qReZctOBg/IjA6nvbnnYqjRX6oOSN8bx8bgbc1jPXVEV GXS5lgqWGHgb/+XDbNhNOpsuJhNbzr0CmKks3q3zbwrOOsmMUNycOWK4DE+DBZrKZTXJ t6CojzlOHbFHX5G9vLIQ8b7bdLkgRxgJiMfk61HM/oTkkJdwhyDVTT3bnES0MealhXDZ ASMA== X-Gm-Message-State: AOAM530eMxGt/d+36oB7OpQAlDgaWFEWrt6KgimDifdgVvCLYexXNd/w dGfk3EN6mtYvKe0NqAnTyGI= X-Google-Smtp-Source: ABdhPJwwHvXfBpPHnGHb13uz+BAtjbE6+rk2VK8KQ9qO8unppGwXzMBU9/0JniLgBGOKl44xj+/byg== X-Received: by 2002:a05:651c:905:: with SMTP id e5mr6684944ljq.238.1637065668414; Tue, 16 Nov 2021 04:27:48 -0800 (PST) Received: from fedora (dc73szyyyyyyyyyyyyycy-3.rev.dnainternet.fi. [2001:14ba:16ee:fa00::4]) by smtp.gmail.com with ESMTPSA id w36sm1751324lfu.81.2021.11.16.04.27.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Nov 2021 04:27:47 -0800 (PST) Date: Tue, 16 Nov 2021 14:27:40 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Sebastian Reichel , Rob Herring , Matti Vaittinen , Lee Jones , Linus Walleij , rostokus@gmail.com, fan.chen@mediatek.com, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-power@fi.rohmeurope.com Subject: [RFC PATCH v3 6/9] power: supply: Add batinfo functions for OCV to SOC with 0.1% accuracy Message-ID: <392c7aa891d466bc4df06a076090150319e0e7dc.1637061794.git.matti.vaittinen@fi.rohmeurope.com> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org The battery info functions computing the state-of-charge (SOC) based on open-circuit-voltage (OCV) are returning SOC using units of 1%. Some capacity estimation computations require higher accuracy. Add functions that return SOC using units of 0.1% to reduce rounding error. Signed-off-by: Matti Vaittinen --- RFCv3 changes: - Kerneldoc fixes --- drivers/power/supply/power_supply_core.c | 65 ++++++++++++++++++++++++ include/linux/power_supply.h | 4 ++ 2 files changed, 69 insertions(+) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index ebc961b5aa45..62ea113db3b4 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1003,6 +1003,45 @@ int power_supply_ocv2cap_simple(struct power_supply_battery_ocv_table *table, } EXPORT_SYMBOL_GPL(power_supply_ocv2cap_simple); +/** + * power_supply_ocv2dcap_simple() - find the battery capacity at 0.1% accuracy + * @table: Pointer to battery OCV lookup table + * @table_len: OCV table length + * @ocv: Current OCV value + * + * This helper function is used to look up battery capacity according to + * current OCV value from one OCV table, and the OCV table must be ordered + * descending. Return the SOC in the units of 0.1% for improved accuracy. + * + * Return: the battery capacity using the unit 0.1%. + */ +int power_supply_ocv2dcap_simple(struct power_supply_battery_ocv_table *table, + int table_len, int ocv) +{ + int i, cap, tmp; + + for (i = 0; i < table_len; i++) + if (ocv > table[i].ocv) + break; + + if (i > 0 && i < table_len) { + tmp = (table[i - 1].capacity - table[i].capacity) * + (ocv - table[i].ocv) * 10; + tmp /= table[i - 1].ocv - table[i].ocv; + cap = tmp + table[i].capacity * 10; + } else if (i == 0) { + cap = table[0].capacity * 10; + } else { + cap = table[table_len - 1].capacity * 10; + } + + if (cap < 0) + cap = 0; + + return cap; +} +EXPORT_SYMBOL_GPL(power_supply_ocv2dcap_simple); + struct power_supply_battery_ocv_table * power_supply_find_ocv2cap_table(struct power_supply_battery_info *info, int temp, int *table_len) @@ -1054,6 +1093,32 @@ int power_supply_batinfo_dcap2ocv(struct power_supply_battery_info *info, } EXPORT_SYMBOL_GPL(power_supply_batinfo_dcap2ocv); +/** + * power_supply_batinfo_ocv2dcap - compute SOC based on OCV and temperature + * @info: pointer to battery information + * @ocv: Open circuit voltage in uV + * @temp: Temperature in Celsius + * + * Use OCV tables in battery info to compute the battery capacity based on + * provided open circuit voltage at given and temperature. + * + * Return: battery capacity correspondinggiven OCV and temperature at 0.1%. + * -EINVAL if OCV table is not present. + */ +int power_supply_batinfo_ocv2dcap(struct power_supply_battery_info *info, + int ocv, int temp) +{ + struct power_supply_battery_ocv_table *table; + int table_len; + + table = power_supply_find_ocv2cap_table(info, temp, &table_len); + if (!table) + return -EINVAL; + + return power_supply_ocv2dcap_simple(table, table_len, ocv); +} +EXPORT_SYMBOL_GPL(power_supply_batinfo_ocv2dcap); + int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info, int ocv, int temp) { diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index c5118265b3ab..5e6575e97492 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -444,6 +444,10 @@ extern int power_supply_ocv2cap_simple(struct power_supply_battery_ocv_table *ta int table_len, int ocv); int power_supply_dcap2ocv_simple(struct power_supply_battery_ocv_table *table, int table_len, int dcap); +int power_supply_ocv2dcap_simple(struct power_supply_battery_ocv_table *table, + int table_len, int ocv); +int power_supply_batinfo_ocv2dcap(struct power_supply_battery_info *info, + int ocv, int temp); extern struct power_supply_battery_ocv_table * power_supply_find_ocv2cap_table(struct power_supply_battery_info *info, From patchwork Tue Nov 16 12:28:13 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 12622299 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9E613C433F5 for ; Tue, 16 Nov 2021 12:28:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 759D861B50 for ; Tue, 16 Nov 2021 12:28:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236208AbhKPMbV (ORCPT ); Tue, 16 Nov 2021 07:31:21 -0500 Received: from mail-lf1-f52.google.com ([209.85.167.52]:37420 "EHLO mail-lf1-f52.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236046AbhKPMbU (ORCPT ); Tue, 16 Nov 2021 07:31:20 -0500 Received: by mail-lf1-f52.google.com with SMTP id c32so52774373lfv.4; Tue, 16 Nov 2021 04:28:22 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=x34Wmq5UEibmmAViM4h392AJ2nzdGPzrPviO8aZH958=; b=RwOnkK1kPcVYq9izUkfCVCAjSZcocSUFy1mdm97IYptjQNVBeZgBMFFOQdwPEdoWNb Rj7gTLVwhX6MbAdChSatP0cCAW+AHugAEs5SxShzzoEpQgrE8qBK2rbYDv0kIGvexQ65 KgfOAA4KjJKQsEyI/1wT1dLRi64d02Ad7LeKqJuby/MnOKgipHlijU0tWCwo1GUMSFaO N/Iye0+IRA41Y0c7BRl+4TW8VNXDPNYFtpQBwZde1z3jBHONB5pWHCf1iIMCIrcT+MhN 0+NtARSPV2xtYfnaF7OgalDUclcS+O4RTVXUC3oUuX9HYIL2S0FD4QJ7180uKQiMwFqZ aSsQ== X-Gm-Message-State: AOAM5334aRr3Mi10LT2cKeCbt2YQGVj3ll8os6hwUH+KSgcEAz+6Tqal jr7s8tPNQOKqmBAKcgLx7Mk= X-Google-Smtp-Source: ABdhPJwVMNPnMiTXtG4+v4jp8KH2KUUJQh74RZsmEtNcFkP9nF5YIgcmZRbfSW+pQvjDFhiAyMJbSQ== X-Received: by 2002:ac2:54b7:: with SMTP id w23mr6479169lfk.163.1637065701267; Tue, 16 Nov 2021 04:28:21 -0800 (PST) Received: from fedora (dc73szyyyyyyyyyyyyycy-3.rev.dnainternet.fi. [2001:14ba:16ee:fa00::4]) by smtp.gmail.com with ESMTPSA id k5sm1816451lja.34.2021.11.16.04.28.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Nov 2021 04:28:20 -0800 (PST) Date: Tue, 16 Nov 2021 14:28:13 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Sebastian Reichel , Rob Herring , Matti Vaittinen , Lee Jones , Linus Walleij , rostokus@gmail.com, fan.chen@mediatek.com, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-power@fi.rohmeurope.com Subject: [RFC PATCH v3 7/9] power: supply: add simple-gauge for SOC estimation and CC correction Message-ID: <26a80d9081382976cec58f9c3bc0ecb181c5836e.1637061794.git.matti.vaittinen@fi.rohmeurope.com> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org Add generic 'simple gauge' helper for performing iterative SOC estimation and coulomb counter correction for devices with a (drifting) coulomb counter. This should allow few charger/fuel-gauge drivers to use generic loop instead of implementing their own. Charger/fuel-gauge drivers can register 'simple-gauge' which does periodically poll the driver and: - get battery state - adjust coulomb counter value (to fix drifting caused for example by ADC offset) if: - Battery is relaxed and OCV<=>SOC table is given - Battery is full charged - get battery age (cycles) from driver - get battery temperature - do battery capacity correction - by battery temperature - by battery age - by computed Vbat/OCV difference at low-battery condition if low-limit is set and OCV table given - by IC specific low-battery correction if provided - compute current State Of Charge (SOC) - do periodical calibration if IC supports that. (Many ICs do calibration of CC by shorting the ADC pins and getting the offset). - provide the user-space a consistent interface for getting/setting the battery-cycle information for ICs which can't store the battery aging information. Uses POWER_SUPPLY_PROP_CYCLE_COUNT for this. The simple gauge provides the last computed SOC as POWER_SUPPLY_PROP_CAPACITY to power_supply_class when requested. Things that should/could be added but are missing from this commit: - Support starting calibration in HW when entering to suspend. This is useful for ICs supporting delayed calibration to mitigate CC error during suspend - and to make periodical wake-up less critical. - periodical wake-up for performing SOC estimation computation (RTC integration) Signed-off-by: Matti Vaittinen Acked-by: Linus Walleij --- RFCv3: - use devm_add_action_or_reset - Rename to simple_gauge - Limit access to power_supply parameters - Introduce simple_gauge_drvdata - no need to show simple_gauge at config menu. Users should just SELECT it. - allow tristate - Add blocking 'iteration run' - loop. - updated the comment section - Fixed clamped SOC which was not updated - Small comment improvements - Don't allow negative CC after computing capacity corrections - Fix gauge looping when last client exits --- drivers/power/supply/Kconfig | 3 + drivers/power/supply/Makefile | 1 + drivers/power/supply/simple-gauge.c | 1303 +++++++++++++++++++++++++++ include/linux/power/simple_gauge.h | 244 +++++ 4 files changed, 1551 insertions(+) create mode 100644 drivers/power/supply/simple-gauge.c create mode 100644 include/linux/power/simple_gauge.h diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 5cf5bb56d2e3..24a8d030a391 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -9,6 +9,9 @@ menuconfig POWER_SUPPLY if POWER_SUPPLY +config POWER_SIMPLE_GAUGE + tristate + config POWER_SUPPLY_DEBUG bool "Power supply debug" help diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 4e55a11aab79..8c8c5f6a6492 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -5,6 +5,7 @@ power_supply-y := power_supply_core.o power_supply-$(CONFIG_SYSFS) += power_supply_sysfs.o power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o +obj-$(CONFIG_POWER_SIMPLE_GAUGE) += simple-gauge.o obj-$(CONFIG_POWER_SUPPLY) += power_supply.o obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o diff --git a/drivers/power/supply/simple-gauge.c b/drivers/power/supply/simple-gauge.c new file mode 100644 index 000000000000..7bf96b4d35dd --- /dev/null +++ b/drivers/power/supply/simple-gauge.c @@ -0,0 +1,1303 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Implementation of some generic state-of-charge computations for devices + * with coulomb counter + * + * Copyright 2020 ROHM Semiconductors + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SWGAUGE_TIMEOUT_JITTER 100 +/* We add 0.5% of uAh to avoid rounding error */ +#define SOC_BY_CAP(uah, round, cap) ((uah + round) * 100 / (cap)) + +/* + * The idea here is to implement (typical?) periodical coulomb-counter polling + * and adjusting. I know few ROHM ICs which do this in out-of-tree drivers - + * and I think there is few drivers also in-tree doing something similar. I + * believe adding some SOC computation logic to core could benefit a few of the + * drivers. + * + * I selected the ROHM algorithm here because I know it better than others. + * + * So let's go explaining the logic pulled in here: + * + * Device drivers for (charger) ICs which contain a coulomb counter can + * register here and provide IC specific functions as listed in + * include/linux/power/simple_gauge.h struct simple_gauge_ops. Drivers can also + * specify time interval the IC is polled. + * + * After registration the simple_gauge does periodically poll the driver and: + * 1. get battery state + * 2. adjust coulomb counter value (to fix drifting caused for example by ADC + * offset) if: + * - Battery is relaxed and OCV<=>SOC table is given + * - Battery is fully charged + * 3. get battery age (cycles) from driver + * 4. get battery temperature + * 5. do battery capacity correction + * - by battery temperature + * - by battery age + * - by computed Vbat/OCV difference at low-battery condition if + * low-limit is set and OCV table given + * - by IC specific low-battery correction if provided + * 6. compute current State Of Charge (SOC) based on corrected capacity + * 7. do periodical calibration if IC supports that. (Many ICs do calibration + * of CC by shorting the ADC pins and getting the offset). + * TODO: Support starting calibration in HW when entering to suspend. This + * is useful for ICs supporting delayed calibration to mitigate CC error + * during suspend - and to make periodical wake-up less critical. + * + * The SW gauge provides the last computed SOC as POWER_SUPPLY_PROP_CAPACITY to + * power_supply_class when requested. + * + * Additionally the SW-gauge provides the user-space a consistent interface for + * getting/setting the battery-cycle information for ICs which can't store the + * battery aging information (like how many times battery has been charged to + * full) over a reset. POWER_SUPPLY_PROP_CYCLE_COUNT is used for this. + * + * TODO: Some low-power devices which may spend long times suspended may prefer + * periodical wake-up for performing SOC estimation/computation even at the + * cost of power-consumption caused by such a wake-up. So as a future + * improvement it would be nice to see a RTC integration which might allow the + * periodic wake-up. (Or is there better ideas?). This could be useful for the + * devices which do not support calibration when SOC is turned off and current + * consumption is minimal. + * + * If this is not seen as a complete waste of time - then I would like to get + * suggestions and opinions :) Especially for following: + * 1. Should this be meld-in power_supply_class? I didn't go to that route as I + * didn't want to obfuscate the power_supply registration with the items in + * the simple_gauge desc and ops. OTOH, the psy now has a pointer to sw-gauge + * and sw-gauge to psy - which is a clear hint that the relation of them + * is quite tight. + */ + +static DEFINE_MUTEX(simple_gauge_lock); +static DEFINE_MUTEX(simple_gauge_start_lock); +static LIST_HEAD(simple_gauges); + +static int g_running; +static struct task_struct k; + +static int simple_gauge_set_cycle(struct simple_gauge *sw, int new_cycle) +{ + int ret = 0, old_cycle = sw->cycle; + + if (!sw->desc.allow_set_cycle && !sw->ops.set_cycle) + return -EINVAL; + + if (sw->ops.set_cycle) + ret = sw->ops.set_cycle(sw, old_cycle, &new_cycle); + + if (!ret) + sw->cycle = new_cycle; + + return ret; +} + +static int simple_gauge_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct simple_gauge *sg = power_supply_get_drvdata(psy); + + if (!sg) { + WARN_ON(!sg); + return -EINVAL; + } + + if (psp == POWER_SUPPLY_PROP_CYCLE_COUNT && sg->desc.allow_set_cycle) + return simple_gauge_set_cycle(sg, val->intval); + + if (sg->set_custom_property) + return sg->set_custom_property(sg, psp, val); + + return -EOPNOTSUPP; +} + +static int simple_gauge_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct simple_gauge *sg = power_supply_get_drvdata(psy); + + if (!sg) { + WARN_ON(!sg); + return -EINVAL; + } + /* + * We should ensure here is that the first iteration + * loop for SOC calculation must've been performed. TODO: + * How to ensure it? + * + * TODO: Add flags to advertice which properties can be computed by + * sw-gauge (with given call-backs) and only return those. Else + * default with driver get_property to allow the driver to compute + * and provide those w/o the SW-gauge. + * + * Or - we could first try calling the driver call-back and do these + * as a fall-back if driver returns an error? + */ + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + spin_lock(&sg->lock); + val->intval = sg->soc; + spin_unlock(&sg->lock); + return 0; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + spin_lock(&sg->lock); + val->intval = sg->cycle; + spin_unlock(&sg->lock); + return 0; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + /* uAh */ + val->intval = sg->designed_cap; + return 0; + case POWER_SUPPLY_PROP_CHARGE_FULL: + spin_lock(&sg->lock); + val->intval = sg->capacity_uah; + spin_unlock(&sg->lock); + return 0; + case POWER_SUPPLY_PROP_CHARGE_NOW: + spin_lock(&sg->lock); + val->intval = sg->cc_uah; + spin_unlock(&sg->lock); + return 0; + case POWER_SUPPLY_PROP_TEMP: + spin_lock(&sg->lock); + val->intval = sg->temp; + spin_unlock(&sg->lock); + return 0; + default: + break; + } + + if (sg->get_custom_property) + return sg->get_custom_property(sg, psp, val); + + return -EOPNOTSUPP; +} + +static void gauge_put(struct simple_gauge *sw) +{ + sw->refcount = 0; + if (!sw->refcount) + wake_up(&sw->wq); +} + +static void gauge_get(struct simple_gauge *sw) +{ + sw->refcount = 1; +} + +static int gauge_reserved(struct simple_gauge *sw) +{ + return sw->refcount; +} + +static int get_dsoc_from_ocv(struct simple_gauge *sw, int *dsoc, int temp, int ocv) +{ + int ret; + + /* + * The OCV tables use degree C as units (if I did not misread the + * code - why?) - power_supply class user-space seems to mandate + * the tenths of a degree - so we require this from drivers and + * lose accuracy here :/ + */ + ret = power_supply_batinfo_ocv2cap(&sw->info, ocv, temp / 10); + if (ret > 0) { + *dsoc = ret * 10; + ret = 0; + } + + if (ret) { + if (!sw->ops.get_soc_by_ocv) + return ret; + /* For driver callbacks we use tenths of degree */ + ret = sw->ops.get_soc_by_ocv(sw, ocv, temp, dsoc); + } + return ret; +} + +static int simple_gauge_get_temp(struct simple_gauge *sw, int *temp) +{ + if (sw->ops.get_temp) + return sw->ops.get_temp(sw, temp); + + return -EINVAL; +} + +static int age_correct_cap(struct simple_gauge *sw, int *uah) +{ + int ret = 0; + + /* If IC provides more complex degradation computation - use it */ + if (sw->ops.age_correct_cap) { + int tmp = *uah; + + ret = sw->ops.age_correct_cap(sw, sw->cycle, &tmp); + if (!ret) { + *uah = tmp; + return 0; + } + } + /* Calculate constant uAh/cycle degradation */ + if (sw->desc.degrade_cycle_uah) { + int lost_cap; + + lost_cap = sw->desc.degrade_cycle_uah * sw->cycle; + if (lost_cap > sw->designed_cap) + *uah = 0; + else + *uah -= sw->desc.degrade_cycle_uah * sw->cycle; + + return 0; + } + + return ret; +} + +static int adjust_cc_relax(struct simple_gauge *sw, int rex_volt) +{ + int ret, temp, dsoc; + int full_uah = sw->designed_cap; + int uah_now; + + /* get temp */ + ret = simple_gauge_get_temp(sw, &temp); + if (ret) + return ret; + + /* get ocv */ + ret = get_dsoc_from_ocv(sw, &dsoc, temp, rex_volt); + if (ret) + return ret; + + /* + * Typically ROHM implemented drivers have kept the value CC in PMIC + * corresponding to IDEAL battery capacity and then substracted the + * lost capacity when converting CC value to uAh. I guess this prevents + * CC from hitting the floor. + */ + uah_now = full_uah * dsoc / 1000 + sw->soc_rounding; + if (uah_now > sw->designed_cap) + uah_now = sw->designed_cap; + + return sw->ops.update_cc_uah(sw, full_uah); +} + +static int get_state(struct simple_gauge *sw, int *state, int *rex_volt) +{ + int ret; + enum power_supply_property psp = POWER_SUPPLY_PROP_STATUS; + union power_supply_propval pstate; + + *state = 0; + + ret = power_supply_get_property(sw->psy, psp, &pstate); + if (ret) + return ret; + + if (pstate.intval == POWER_SUPPLY_STATUS_FULL) + *state |= SW_GAUGE_FULL; + if (pstate.intval == POWER_SUPPLY_STATUS_DISCHARGING || + pstate.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) { + *state |= SW_GAUGE_MAY_BE_LOW; + if (sw->desc.clamp_soc) + *state |= SW_GAUGE_CLAMP_SOC; + } + + if (sw->ops.is_relaxed) + if (sw->ops.is_relaxed(sw, rex_volt)) + *state |= SW_GAUGE_RELAX; + return ret; +} + +static int adjust_cc_full(struct simple_gauge *sw) +{ + int ret = 0, from_full_uah = 0; + int full_uah = sw->designed_cap; + + /* + * Some ICs are able to provide the uAh lost since the battery was + * fully charged. Decrease this from the designed capacity and set + * the CC value accordingly. + */ + if (sw->ops.get_uah_from_full) + ret = sw->ops.get_uah_from_full(sw, &from_full_uah); + + if (ret) + dev_warn(sw->dev, + "Failed to get capacity lost after fully charged\n"); + + full_uah -= from_full_uah; + + /* + * ROHM algorithm adjusts CC here based on designed capacity and not + * based on age/temperature corrected capacity. This helps avoiding + * CC dropping below zero when we estimate aging/temperature impact + * badly. It also allows to keep the estimated SOC in the sw-gauge + * so that all IC drivers do not need to care about it - at least + * in theory. But most importantly - this approach is tested on the + * field :) + */ + return sw->ops.update_cc_uah(sw, full_uah); +} + +/* + * Some charger ICs keep count of battery charge systems but can only store + * one or few cycles. They may need to clear the cycle counter and update + * counter in SW. This function fetches the counter from HW and allows HW to + * clear IC counter if needed. + */ +static int update_cycle(struct simple_gauge *sw) +{ + int cycle, ret = -EINVAL; + + if (sw->ops.get_cycle) { + /* + * We provide old cycle value to driver so driver does not + * need to cache it + */ + cycle = sw->cycle; + ret = sw->ops.get_cycle(sw, &cycle); + if (ret) + return ret; + sw->cycle = cycle; + } else + sw->cycle++; + + return 0; +} + +static int simple_gauge_cap2ocv(struct simple_gauge *sw, int dsoc, int temp, int *ocv) +{ + int ret; + + if (sw->ops.get_ocv_by_soc) + return sw->ops.get_ocv_by_soc(sw, dsoc, temp, ocv); + + ret = power_supply_batinfo_dcap2ocv(&sw->info, dsoc, temp / 10); + if (ret > 0) { + *ocv = ret; + ret = 0; + } + + return ret; +} + +static int load_based_soc_zero_adjust(struct simple_gauge *sw, int *effective_cap, + int cc_uah, int vsys, int temp) +{ + int dsoc, ocv_by_cap; + int ret, i; + int vdrop; + struct power_supply_battery_ocv_table *table; + int table_len; + int soc_adjust = 0; + + /* + * Get OCV for current estimated SOC - we use unit of 0.1% for SOC + * (dsoc) to improve accuracy. Note - batinfo expects 1% - should we + * introduce new DT entry of more accurate OCV table for improved SOC + * => OCV where SOC was given using unit of 0.1% for improved internal + * calculation? User space should still only see 1% - but for "zero + * adjust" where we do SOC => OCV => drop-voltage => SOC correction + * => CC/capacity adjustment we would like to have more accurate SOC + * in these intermediate steps. + */ + dsoc = SOC_BY_CAP(cc_uah * 10, 0, *effective_cap); + ret = simple_gauge_cap2ocv(sw, dsoc, temp, &ocv_by_cap); + if (ret) { + dev_err(sw->dev, "Failed to convert cap to OCV\n"); + return ret; + } + /* Get the difference of OCV (no load) and VBAT (current load) */ + vdrop = ocv_by_cap - vsys; + dev_dbg(sw->dev, "Obtained OCV: %d, vsys %d, Computed Vdrop %d\n", + ocv_by_cap, vsys, vdrop); + if (vdrop <= 0) + return 0; + + /* + * We know that the SOC should be 0 at the moment when voltage with + * this load drops below system limit. So let's scan the OCV table + * and just assume the vdrop stays constant for the rest of the game. + * This way we can see what is the new 'zero adjusted capacity' for + * our battery. + * + * We don't support non DT originated OCV table here. + * + * I guess we could allow user to provide standard OCV tables in desc. + * Or the full batinfo for that matter. But for now we just force the + * drivers W/O OCV tables in DT to just provide whole low-voltage + * call-back. + */ + table = power_supply_find_ocv2cap_table(&sw->info, temp / 10, + &table_len); + if (!table) { + dev_warn(sw->dev, "No OCV table found\n"); + return -EINVAL; + } + + for (i = 0; i < table_len; i++) + if (table[i].ocv - vdrop <= sw->desc.system_min_voltage) + break; + + if (!i) + soc_adjust = table[0].capacity; + else if (i && i < table_len) { + int j; + int soc_range = table[i-1].capacity - table[i].capacity; + int volt_range = table[i-1].ocv - table[i].ocv; + int v_div = volt_range/soc_range; + + for (j = 0; j < soc_range; j++) + if (table[i].ocv + v_div * j - vdrop >= + sw->desc.system_min_voltage) + break; + soc_adjust = table[i].capacity + j; + } + if (soc_adjust) { + int new_full_cap; + + /* + * So we know that actually we will have SOC = 0 when capacity + * is soc_adjust. Lets compute new battery max capacity based + * on this. + */ + new_full_cap = *effective_cap * (100 - soc_adjust) / 100; + + *effective_cap = new_full_cap; + } + + return 0; +} + +static int simple_gauge_zero_cap_adjust(struct simple_gauge *sw, int *effective_cap, + int *cc_uah, int vsys, int temp) +{ + int ret, old_eff_cap = *effective_cap; + + if (sw->ops.zero_cap_adjust) + ret = sw->ops.zero_cap_adjust(sw, effective_cap, *cc_uah, vsys, + temp); + else + ret = load_based_soc_zero_adjust(sw, effective_cap, *cc_uah, + vsys, temp); + /* + * As we keep HW CC aligned to designed-cap - we need to + * also cancel this new offset from CC measured uAh + */ + if (!ret) + *cc_uah -= old_eff_cap - *effective_cap; + + return ret; +} + +static int find_dcap_change(struct simple_gauge *sw, int temp, int *delta_cap) +{ + struct power_supply_temp_degr *dclosest = NULL, *d; + int i; + + for (i = 0; i < sw->amount_of_temp_dgr; i++) { + d = &sw->temp_dgr[i]; + if (!dclosest) { + dclosest = d; + continue; + } + if (abs(d->temp_set_point - temp) < + abs(dclosest->temp_set_point - temp)) + dclosest = d; + } + + if (!dclosest) + return -EINVAL; + + /* + * Temperaure range is in tenths of degrees and degrade value is for a + * degree => divide by 10 after multiplication to fix the scale + */ + *delta_cap = (dclosest->temp_set_point - temp) * + dclosest->temp_degrade_1C / 10 + + dclosest->degrade_at_set; + + return 0; +} + +static int compute_temp_correct_uah(struct simple_gauge *sw, int *cap_uah, int temp) +{ + int ret, uah_corr; + + ret = find_dcap_change(sw, temp, &uah_corr); + if (ret) + return ret; + + if (*cap_uah < -uah_corr) + *cap_uah = 0; + else + *cap_uah += uah_corr; + + return 0; +} + +static int compute_soc_by_cc(struct simple_gauge *sw, int state) +{ + int cc_uah, ret; + int current_cap_uah; + int temp; + int new_soc; + bool do_zero_correct, changed = false; + + ret = sw->ops.get_uah(sw, &cc_uah); + /* The CC value should never exceed designed_cap as CC value. */ + if (cc_uah > sw->designed_cap) { + cc_uah = sw->designed_cap; + sw->ops.update_cc_uah(sw, sw->designed_cap); + } + + current_cap_uah = sw->designed_cap; + + dev_dbg(sw->dev, "iteration started - CC %u, cap %u (SOC %u)\n", + cc_uah, current_cap_uah, + SOC_BY_CAP(cc_uah, sw->soc_rounding, current_cap_uah)); + + ret = age_correct_cap(sw, ¤t_cap_uah); + if (ret) { + dev_err(sw->dev, "Age correction of battery failed\n"); + return ret; + } + + if (current_cap_uah == 0) { + dev_warn(sw->dev, "Battery EOL\n"); + spin_lock(&sw->lock); + sw->capacity_uah = 0; + sw->soc = 0; + changed = true; + goto battery_eol_out; + } + + /* Do battery temperature compensation */ + ret = simple_gauge_get_temp(sw, &temp); + if (ret) { + dev_err(sw->dev, "Failed to get temperature\n"); + return ret; + } + + if (sw->ops.temp_correct_cap) + ret = sw->ops.temp_correct_cap(sw, ¤t_cap_uah, temp); + else if (sw->amount_of_temp_dgr) + ret = compute_temp_correct_uah(sw, ¤t_cap_uah, temp); + + if (ret) + dev_warn(sw->dev, + "Couldn't do temperature correction to battery cap\n"); + + /* + * We keep HW CC counter aligned to ideal battery CAP - EG, when + * battery is full, CC is set according to ideal battery capacity. + * Same when we set it based on OCV. Thus - when we compute SOC we will + * cancel this offset by decreasing the CC uah with the lost capacity + */ + cc_uah -= (sw->designed_cap - current_cap_uah); + + /* Only need zero correction when discharging */ + do_zero_correct = !!(state & SW_GAUGE_MAY_BE_LOW); + + /* + * Allow all ICs to have own adjustment functions for low Vsys to allow + * them tackle potential issues in capacity estimation at near + * depleted battery + */ + if (sw->desc.cap_adjust_volt_threshold && sw->ops.get_vsys && + do_zero_correct) { + int vsys; + + ret = sw->ops.get_vsys(sw, &vsys); + if (ret) { + dev_err(sw->dev, "Failed to get vsys\n"); + return ret; + } + + if (sw->desc.cap_adjust_volt_threshold >= vsys) + ret = simple_gauge_zero_cap_adjust(sw, ¤t_cap_uah, + &cc_uah, vsys, temp); + if (ret) + dev_warn(sw->dev, "Low voltage adjustment failed\n"); + } + + dev_dbg(sw->dev, "Corrected cap %u, designed-cap %u (SOC %u)\n", + current_cap_uah, sw->designed_cap, + SOC_BY_CAP(cc_uah, sw->soc_rounding, current_cap_uah)); + + if (cc_uah > sw->designed_cap) + cc_uah = sw->designed_cap; + + /* + * With badly behaving CC or wrong VDR values we may make the CC to go + * negative. Floor it to zero to avoid exhausting the battery W/O warning. + */ + if (cc_uah < 0) { + dev_warn(sw->dev, + "Bad battery capacity estimate\n"); + cc_uah = 0; + } + /* Store computed values */ + spin_lock(&sw->lock); + sw->cc_uah = cc_uah; + sw->temp = temp; + sw->capacity_uah = current_cap_uah; + new_soc = SOC_BY_CAP(cc_uah, sw->soc_rounding, current_cap_uah); + + /* + * Should we only ping user-space when SOC changes more than N%? + * Should the N be configurable (by user-space?) + */ + if (sw->soc != new_soc) + changed = true; + + sw->soc = new_soc; + if (sw->clamped_soc >= 0 && state & SW_GAUGE_CLAMP_SOC) { + if (sw->clamped_soc < sw->soc) + sw->soc = sw->clamped_soc; + } + sw->clamped_soc = sw->soc; + +battery_eol_out: + spin_unlock(&sw->lock); + if (changed) + power_supply_changed(sw->psy); + + return ret; +} + +static void calibrate(struct simple_gauge *sw) +{ + if (sw->ops.calibrate) + sw->ops.calibrate(sw); +} + +static void iterate(struct simple_gauge *sw) +{ + int state, ret, rex_volt; + + /* Adjust battery aging information */ + ret = update_cycle(sw); + if (ret) { + dev_err(sw->dev, "Failed to update battery cycle\n"); + return; + } + + ret = get_state(sw, &state, &rex_volt); + if (ret) { + dev_err(sw->dev, "Failed to get state\n"); + return; + } + + /* Setting CC not possible? Omit CC adjustment */ + if (sw->ops.update_cc_uah) { + if (state & SW_GAUGE_FULL) { + ret = adjust_cc_full(sw); + if (ret) + dev_err(sw->dev, "Failed to do FULL adjust\n"); + } + if (state & SW_GAUGE_RELAX) { + ret = adjust_cc_relax(sw, rex_volt); + if (ret) + dev_err(sw->dev, "Failed to do RELAX adjust\n"); + } + } + + ret = compute_soc_by_cc(sw, state); + if (ret) + dev_err(sw->dev, "Failed to compute SOC for gauge\n"); +} + +static bool should_calibrate(struct simple_gauge *sw, u64 time) +{ + if (sw->desc.calibrate_interval && + sw->next_cal <= time + msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER)) { + sw->next_cal = time + + msecs_to_jiffies(sw->desc.calibrate_interval); + return true; + } + + return false; + +} + +static bool should_compute(struct simple_gauge *sw, u64 time) +{ + if (sw->next_iter <= time + msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER) || + sw->force_run) { + sw->force_run = 0; + sw->next_iter = time + + msecs_to_jiffies(sw->desc.poll_interval); + return true; + } + + return false; +} + +static void adjust_next_tmo(struct simple_gauge *sw, u64 *timeout, u64 now) +{ + u64 t = (sw->desc.calibrate_interval && sw->next_cal < sw->next_iter) ? + sw->next_cal : sw->next_iter; + + if (!*timeout || t - now < *timeout) + *timeout = t - now; + + if (*timeout < msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER)) + *timeout = msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER); +} + +static DECLARE_WAIT_QUEUE_HEAD(simple_gauge_thread_wait); +static DECLARE_WAIT_QUEUE_HEAD(simple_gauge_forced_wait); + +static int simple_gauge_forced_run; + +/** + * simple_gauge_run - force running the computation loop for gauge + * + * Drivers utilizing simple_gauge can trigger running the SOC computation loop even + * prior the time-out occurs. This is usable for drivers with longish period + * but which may get interrupts form device when some condition changes. Note, + * this function schedules the iteration but does not block. + * + * @sw: gauge fow which the computation should be ran. + */ +void simple_gauge_run(struct simple_gauge *sw) +{ + sw->force_run = 1; + simple_gauge_forced_run = 1; + barrier(); + wake_up(&simple_gauge_thread_wait); +} +EXPORT_SYMBOL_GPL(simple_gauge_run); + +static unsigned int g_iteration; + +static unsigned int simple_gauge_run_locked(struct simple_gauge *sg) +{ + unsigned int ctr; + + /* Wait for any ongoing iteration */ + mutex_lock(&simple_gauge_lock); + ctr = g_iteration; + simple_gauge_run(sg); + mutex_unlock(&simple_gauge_lock); + + return ctr; +} + +/** + * simple_gauge_run_blocking_timeout - Run gauge loop and block until ran + * + * Trigger simple-gauge to run. Wait until simple-gauge has been ran or timeout + * occurs. + * + * @sg: Pointer to gauge + * @timeout_ms: Timeout in milliseconds. + * + * Returns: 0 if gauge loop was ran. -EAGAIN if timeout occurred and + * -ERESTARTSYS if call was interrupted. + */ +int simple_gauge_run_blocking_timeout(struct simple_gauge *sg, + unsigned int timeout_ms) +{ + unsigned int ctr; + int ret; + + ctr = simple_gauge_run_locked(sg); + ret = wait_event_interruptible_timeout(simple_gauge_forced_wait, + g_iteration > ctr, + timeout_ms); + if (ret == 1) + ret = 0; + else if (!ret) + ret = -EAGAIN; + + return ret; +} +EXPORT_SYMBOL_GPL(simple_gauge_run_blocking_timeout); + +/** + * simple_gauge_run_blocking - Run gauge loop and block until ran + * + * Trigger simple-gauge to run. Wait until simple-gauge has been ran. + * + * @sg: Pointer to gauge + * + * Returns: 0 on success. -ERESTARTSYS if call was interrupted. + */ +int simple_gauge_run_blocking(struct simple_gauge *sg) +{ + unsigned int ctr; + int ret; + + ctr = simple_gauge_run_locked(sg); + ret = wait_event_interruptible(simple_gauge_forced_wait, + g_iteration > ctr); + + return ret; +} +EXPORT_SYMBOL_GPL(simple_gauge_run_blocking); + +static int gauge_thread(void *data) +{ + + for (;;) { + u64 timeout = 0; + struct simple_gauge *sw; + u64 now = get_jiffies_64(); + + if (kthread_should_stop()) { + g_running = 0; + pr_info("gauge thread stopping...\n"); + /* + * Ensure the g_running is visible to all. OTOH - if + * thread stopping is not supported we can drop this + * clearing. + */ + smp_wmb(); + break; + } + + simple_gauge_forced_run = 0; + + mutex_lock(&simple_gauge_lock); + list_for_each_entry(sw, &simple_gauges, node) { + gauge_get(sw); + if (should_compute(sw, now)) + iterate(sw); + if (should_calibrate(sw, now)) + calibrate(sw); + adjust_next_tmo(sw, &timeout, now); + gauge_put(sw); + } + /* + * Increase iteration counter and wake up the waiters who have + * requested the blocking forced run. NOTE: We must increase + * iteration here inside the locking to avoid race. + */ + g_iteration++; + mutex_unlock(&simple_gauge_lock); + wake_up(&simple_gauge_forced_wait); + + /* + * If the last gauge exited we fall to sleep in order to not + * go lopoping with zero timeout. New client registration will + * wake us up. + */ + if (!timeout && list_empty(&simple_gauges)) { + pr_debug("No clients: going to sleep\n"); + wait_event_interruptible(simple_gauge_thread_wait, + simple_gauge_forced_run); + } else { + pr_debug("sleeping %u msec\n", + jiffies_to_msecs(timeout)); + wait_event_interruptible_timeout(simple_gauge_thread_wait, + simple_gauge_forced_run, timeout); + } + } + + return 0; +} + +static int start_gauge_thread(struct task_struct *k) +{ + int ret = 0; + + /* Ensure the running state is updated. Is this needed? How likely it is + * this would not be updated? Only reason why we have this first check + * is to avoid unnecessarily getting the lock when the thread is started + * - and it should always be except for the first caller. But does this + * memory barrier actually make this almost as heavy as getting the lock + * every time? Maybe we should just drop the barrier and let us hit the + * lock if g_running is in cache or some such (lots of hand waving to + * make it look like I knew what I was talking about :]). + */ + smp_rmb(); + if (g_running) + return 0; + + /* + * Would this be cleaner if we used schedule_delayed_work? rather than + * kthread? What I like is only one thread for sw-gauge, with + * not-so-high priority. What is the simplest way to achieve it? + */ + mutex_lock(&simple_gauge_start_lock); + if (!g_running) { + k = kthread_run(gauge_thread, NULL, "sw-gauge"); + if (IS_ERR(k)) + ret = PTR_ERR(k); + else + g_running = 1; + } + mutex_unlock(&simple_gauge_start_lock); + + return ret; +} + +/* + * I think this is unnecessary. If someone registers SW gauge to the system + * then we can probably leave this running even if the gauge was temporarily + * removed. So let's consider removing this and thus simplifying the design. + * + * Perhaps even always launch the thread if SW-gauge is configured in? + */ +void stop_gauge_thread(struct task_struct *k); +void stop_gauge_thread(struct task_struct *k) +{ + kthread_stop(k); +} + +static bool is_needed_ops_given(struct simple_gauge_ops *ops) +{ + return (ops->get_uah && ops->get_temp && ops->update_cc_uah); +} + +static int simple_gauge_set_ops(struct simple_gauge *sw, struct simple_gauge_ops *ops) +{ + if (!is_needed_ops_given(ops)) + return -EINVAL; + + sw->ops = *ops; + + return 0; +} + +static int simple_gauge_is_writable(struct power_supply *psy, + enum power_supply_property psp) +{ + struct simple_gauge *sg = power_supply_get_drvdata(psy); + + if (!sg) { + WARN_ON(!sg); + return -EINVAL; + } + + if (psp == POWER_SUPPLY_PROP_CYCLE_COUNT) + return sg->desc.allow_set_cycle; + + if (sg->custom_is_writable) + return sg->custom_is_writable(sg, psp); + + return 0; +} + +static int sgauge_config_check(struct device *dev, struct simple_gauge_psy *pcfg) +{ + const char *errstr = NULL; + + if (!pcfg) { + errstr = "Missing config"; + } else { + if (!pcfg->psy_name) + errstr = "No power supply name"; + if (pcfg->num_additional_props) { + if (!pcfg->get_custom_property) + errstr = "property reader required"; + } + if (!pcfg->is_writable) { + if (pcfg->set_custom_property) + errstr = + "set_custom_property() but no is_writable()"; + } + } + if (!errstr) + return 0; + + dev_err(dev, "%s\n", errstr); + return -EINVAL; +} +static int simple_gauge_set_props(struct simple_gauge *new, + struct power_supply_desc *pd, + struct simple_gauge_psy *pcfg) +{ + int i, j; + + new->properties = kzalloc(sizeof(*pcfg->additional_props) * + pcfg->num_additional_props + SIMPLE_GAUGE_PROP_SIZE, + GFP_KERNEL); + if (!new->properties) + return -ENOMEM; + + for (i = 0; i < NUM_SIMPLE_GAUGE_PROPS; i++) + new->properties[i] = SIMPLE_GAUGE_PROPS[i]; + + for (j = 0; j < pcfg->num_additional_props; j++, i++) + new->properties[i] = pcfg->additional_props[j]; + + pd->properties = new->properties; + pd->num_properties = i; + new->get_custom_property = pcfg->get_custom_property; + new->set_custom_property = pcfg->set_custom_property; + + return 0; +} + +void *simple_gauge_get_drvdata(struct simple_gauge *sg) +{ + return sg->desc.drv_data; +} +EXPORT_SYMBOL_GPL(simple_gauge_get_drvdata); + +/** + * psy_register_simple_gauge - register driver to simple_gauge + * + * @parent: Parent device for power-supply class device. + * @psycfg: Confiurations for power-supply class. + * @ops: simple_gauge specific operations. + * @desc: simple_gauge configuration data. + * + * Return: pointer to simple_gauge on success, an ERR_PTR on failure. + * + * A power-supply driver for a device with drifting coulomb counter (CC) can + * register for periodical polling/CC correction. CC correction is done when + * battery is reported to be FULL or relaxed. For FULL battery the CC is set + * based on designed capacity and for relaxed battery CC is set based on open + * circuit voltage. The simple_gauge takes care of registering a power-supply class + * and reporting a few power-supply properties to user-space. See + * SWGAUGE_PSY_PROPS. Swauge can also do battery capacity corretions based on + * provided temperature/cycle degradation values and/or system voltage limit. + */ +struct simple_gauge *__must_check psy_register_simple_gauge(struct device *parent, + struct simple_gauge_psy *pcfg, + struct simple_gauge_ops *ops, + struct simple_gauge_desc *desc) +{ + struct power_supply_desc *pd; + struct power_supply_config pg = { 0 }; + int ret; + struct simple_gauge *new; + + if (!parent) { + pr_err("no parent\n"); + return ERR_PTR(-EINVAL); + } + + if (!desc->poll_interval) { + dev_err(parent, "interval missing\n"); + return ERR_PTR(-EINVAL); + } + + if (sgauge_config_check(parent, pcfg)) + return ERR_PTR(-EINVAL); + + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) + return ERR_PTR(-ENOMEM); + + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!new) { + ret = -ENOMEM; + goto free_out; + } + pd->name = pcfg->psy_name; + pd->type = POWER_SUPPLY_TYPE_BATTERY; + + pg.drv_data = new; + pg.of_node = pcfg->of_node; + pg.attr_grp = pcfg->attr_grp; + + ret = simple_gauge_set_props(new, pd, pcfg); + if (ret) { + ret = -ENOMEM; + goto free_out; + } + new->dev = parent; + new->custom_is_writable = pcfg->is_writable; + /* We don't want to clamp SOC before it is initialized */ + new->clamped_soc = -1; + + init_waitqueue_head(&new->wq); + + ret = simple_gauge_set_ops(new, ops); + if (ret) { + dev_err(new->dev, "bad ops\n"); + goto free_out; + } + new->desc = *desc; + + spin_lock_init(&new->lock); + pd->get_property = simple_gauge_get_property; + pd->set_property = simple_gauge_set_property; + if (pcfg->is_writable || desc->allow_set_cycle) + pd->property_is_writeable = simple_gauge_is_writable; + + /* Do we need power_supply_register_ws? */ + /* + * We should not return SOC to user-space before the first + * estimation iteration is ran. How should we sync this? Should we + * actually start the gauge thread and try getting SOC prior + * registering to psy class? That would require us to do the + * battery-info reading here. Or should we have some way of delaying + * sysfs creation from psy-class until first iteration is done? Kind + * of two-step power-supply class registration? Again, I am open to + * all suggestions! + * + * I don't know how this should be integrated to psy class. Should + * this sit between psy-class and driver? Should this be part of + * psy-class or ?? If this was meld in psy-class registration and + * the psy class registration was just given a new parameter for fuel + * gauge desc - then the synchronization between 1st iteration and + * sysfs creation might get more natural. + */ + new->psy = power_supply_register(parent, pd, &pg); + if (IS_ERR(new->psy)) { + dev_err(new->dev, "power supply registration failed\n"); + ret = PTR_ERR(new->psy); + goto free_out; + } + + ret = power_supply_get_battery_info(new->psy, &new->info); + if (ret && !new->ops.get_soc_by_ocv) { + dev_err(new->dev, "No OCV => SoC conversion\n"); + goto info_out; + } + if (!ret) + new->batinfo_got = true; + + if (new->info.temp_dgrd_values) { + new->amount_of_temp_dgr = new->info.temp_dgrd_values; + new->temp_dgr = new->info.temp_dgrd; + } else { + new->amount_of_temp_dgr = new->desc.amount_of_temp_dgr; + new->temp_dgr = new->desc.temp_dgr; + } + + if (desc->designed_cap) { + new->designed_cap = desc->designed_cap; + } else if (ret || !new->info.charge_full_design_uah) { + dev_err(new->dev, "Unknown battery capacity\n"); + goto info_out; + } else { + new->designed_cap = new->info.charge_full_design_uah; + } + /* We add 0.5 % to soc uah in order to avoid flooring */ + new->soc_rounding = new->designed_cap / 200; + mutex_lock(&simple_gauge_lock); + list_add(&new->node, &simple_gauges); + mutex_unlock(&simple_gauge_lock); + ret = start_gauge_thread(&k); + if (ret) { + /* + * This error is not related to underlying device but to the + * simple_gauge itself. Thus don't print error using the parent + * device + */ + pr_err("Failed to start fuel-gauge thread\n"); + goto info_out; + } + dev_dbg(new->dev, "YaY! SW-gauge registered\n"); + + simple_gauge_run_blocking(new); + + return new; + +info_out: + if (new->batinfo_got) + power_supply_put_battery_info(new->psy, &new->info); + + power_supply_unregister(new->psy); +free_out: + if (new) { + kfree(new->properties); + kfree(new); + } + kfree(pd); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(psy_register_simple_gauge); + +/** + * psy_remove_simple_gauge - deregister driver from simple_gauge + * + * @sw: gauge driver to be deregistered. + */ +void psy_remove_simple_gauge(struct simple_gauge *sw) +{ + const struct power_supply_desc *desc; + + mutex_lock(&simple_gauge_lock); + list_del(&sw->node); + mutex_unlock(&simple_gauge_lock); + + wait_event(sw->wq, !gauge_reserved(sw)); + + if (sw->batinfo_got) + power_supply_put_battery_info(sw->psy, &sw->info); + + desc = sw->psy->desc; + power_supply_unregister(sw->psy); + + kfree(desc); + kfree(sw->properties); + kfree(sw); +} +EXPORT_SYMBOL_GPL(psy_remove_simple_gauge); + +static void devm_simple_gauge_release(void *res) +{ + psy_remove_simple_gauge(res); +} + +/** + * devm_psy_register_simple_gauge - managed register driver to simple_gauge + * + * @parent: Parent device for power-supply class device. Swgauge's lifetime + * is also bound to this device. + * @psycfg: Confiurations for power-supply class. + * @ops: simple_gauge specific operations. + * @desc: simple_gauge configuration data. + * + * Return: pointer to simple_gauge on success, an ERR_PTR on failure. + * + * A power-supply driver for a device with drifting coulomb counter (CC) can + * register for periodical polling/CC correction. CC correction is done when + * battery is reported to be FULL or relaxed. For FULL battery the CC is set + * based on designed capacity and for relaxed battery CC is set based on open + * circuit voltage. The simple_gauge takes care of registering a power-supply class + * and reporting a few power-supply properties to user-space. See + * SWGAUGE_PSY_PROPS. Swauge can also do battery capacity corretions based on + * provided temperature/cycle degradation values and/or system voltage limit. + */ +struct simple_gauge *__must_check +devm_psy_register_simple_gauge(struct device *parent, struct simple_gauge_psy *psycfg, + struct simple_gauge_ops *ops, struct simple_gauge_desc *desc) +{ + struct simple_gauge *sw; + int ret; + + sw = psy_register_simple_gauge(parent, psycfg, ops, desc); + if (IS_ERR(sw)) + return sw; + + ret = devm_add_action_or_reset(parent, devm_simple_gauge_release, sw); + if (ret) + return ERR_PTR(ret); + + return sw; +} +EXPORT_SYMBOL_GPL(devm_psy_register_simple_gauge); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("generic fuel-gauge on coulomb counter"); +MODULE_AUTHOR("Matti Vaittinen "); diff --git a/include/linux/power/simple_gauge.h b/include/linux/power/simple_gauge.h new file mode 100644 index 000000000000..cd085375ddec --- /dev/null +++ b/include/linux/power/simple_gauge.h @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2020 ROHM Semiconductors */ + +#ifndef POWER_SW_GAUGE_H +#define POWER_SW_GAUGE_H + +#include +#include +#include +#include +#include + +#define SW_GAUGE_FULL BIT(0) +#define SW_GAUGE_RELAX BIT(1) +#define SW_GAUGE_MAY_BE_LOW BIT(2) +#define SW_GAUGE_CLAMP_SOC BIT(3) + +/* Power supply properties handled by simple_gauge */ +static const enum power_supply_property SIMPLE_GAUGE_PROPS[] = { + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_TEMP +}; +#define SIMPLE_GAUGE_PROP_SIZE (sizeof(SIMPLE_GAUGE_PROPS)) +#define NUM_SIMPLE_GAUGE_PROPS (ARRAY_SIZE(SIMPLE_GAUGE_PROPS)) + +struct simple_gauge; + +/** + * struct simple_gauge_ops - fuel-gauge operations + * + * @is_relaxed: return true if battery is at relaxed state. Update + * rex_volt to contain measured relaxed battery voltage. + * @get_temp: return the battery temperature in tenths of a degree C. + * @get_uah_from_full: some chargers can provide CC value change since battery + * was last charged full. This value can be used by + * sw-gauge when correcting CC based on battery full + * status. This function should return charge lost since + * battery was last load full. Units in uAh. + * @get_uah: return current charge as measured by coulomb counter in + * uAh + * @update_cc_uah: Update CC by given charge in uAh + * @get_cycle: get battery cycle for age compensation + * @set_cycle: some batteries/chargers rely on user-space to store the + * cycle infomration over reset. Those drivers can + * implement the set_cycle callback which user-space can + * use to set the stored battery cycle after reset. + * @get_sys: get the current system voltage in uV. Used for + * IC specific low-voltage SOC correction. + * @get_soc_by_ocv: setups which do not store the OCV/SOC information in + * standard battery_info can implement this function to + * compute SOC based on OCV. SOC should be returned as + * units of 0.1% + * @get_ocv_by_soc: setups which do not store the OCV/SOC information in + * standard battery_info can implement this function to + * compute OCV based on SOC. NOTE: Soc is provided to + * the function in units of 0.1% to improve accuracy. + * @age_correct_cap: batteries/devices with more complicated aging + * correction than constant uAh times battery cycles + * can implement this to adjust capacity based on battery + * cycles. For constant aging use degrade_cycle_uah + * in desc. + * @temp_correct_cap: batteries/devices with more complicated temperature + * correction than ranges of temperatures with constant + * change uah/degree C can implement this to adjust + * capacity based on battery temperature. + * For temperature ranges with constant change uAh/degree + * use temp_dgr and amount_of_temp_dgr at desc. + * @calibrate: many devices implement coulomb counter calibration + * (for example by measuring ADC offset pins shorted). + * Such devices can implement this function for periodical + * calibration. + * @suspend_calibrate: Many small capacity battery devices or devices which + * spend long time MCU suspended can benefit from + * starting the calibration when entering to suspend. Such + * devices can implement this callback to initiaite + * calibration when entering to suspend + * @zero_cap_adjust: IC specific SOC estimation adjustment to be performed + * when battery is approaching empty. + */ +struct simple_gauge_ops { + /* + * Get battery relax - could probably also use PSY class state if + * it was extended with some properties like BATTERY_RELAXED to know + * if OCV can be used. + * + * Currently meaningfull states are charging/discharging/full/relaxed + * Full so we can correct battery capacity and/or CC + * Relax so we know we can use OCV + */ + bool (*is_relaxed)(struct simple_gauge *gauge, int *rex_volt); + int (*get_temp)(struct simple_gauge *gauge, int *temp); + int (*get_uah_from_full)(struct simple_gauge *gauge, int *uah); + int (*get_uah)(struct simple_gauge *gauge, int *uah); + int (*update_cc_uah)(struct simple_gauge *gauge, int bcap); + int (*get_cycle)(struct simple_gauge *gauge, int *cycle); + int (*set_cycle)(struct simple_gauge *gauge, int old, int *new_cycle); + int (*get_vsys)(struct simple_gauge *gauge, int *uv); + int (*get_soc_by_ocv)(struct simple_gauge *sw, int ocv, int temp, int *soc); + int (*get_ocv_by_soc)(struct simple_gauge *sw, int soc, int temp, int *ocv); + int (*age_correct_cap)(struct simple_gauge *gauge, int cycle, int *cap); + int (*temp_correct_cap)(struct simple_gauge *gauge, int *cap, int temp); + int (*calibrate)(struct simple_gauge *sw); + int (*suspend_calibrate)(struct simple_gauge *sw, bool start); + int (*zero_cap_adjust)(struct simple_gauge *sw, int *effective_cap, + int cc_uah, int vbat, int temp); +}; + +/** + * struct simple_gauge_desc - fuel gauge description + * + * The fuel gauges which benefit from generic computations (typically devices + * with coulomb counter. OCV - SOC table and iterative polling / error + * correction) provided by the simple_gauge framework must be described by the + * simple_gauge_desc prior registration to the simple_gauge framework. + * + * @name: Identifying name for gauge (Is this needed?) + * @degrade_cycle_uah: constant lost capacity / battery cycle in uAh. + * @amount_of_temp_dgr: amount of temperature ranges provided in temp_dgr + * @temp_dgr: ranges of constant lost capacity / temperature degree + * in uAh. Ranges should be sorted in asecnding order by + * temperature_floor. + * @poll_interval: time interval in mS at which this fuel gauge iteration + * loop for volage polling and coulomb counter corrections + * should be ran. + * @calibrate_interval: time interval in mS at which this IC should be + * calibrated. + * @designed_cap: designed battery capacity in uAh. Can be given here if + * not available via batinfo. + * @allow_set_cycle: Allow userspace to set cached battery cycle. If no HW + * access is required when new battery cycle value is set + * the driver can omit set_cycle callback and just set + * this to true. + * @clamp_soc: Set true tonot allow computed SOC to increase if state + * is discharging. + * @cap_adjust_volt_threshold: some systems want to apply extra computation + * to estimate battery capacity when voltage gets close + * to system limit in order to avoid shut-down for as long + * as possible. Such ICs can set this limit and optionally + * implement zero_cap_adjust callback. + * @system_min_voltage: ICs using the cap_adjust_volt_threshold and no + * zero_cap_adjust call-back should set this voltage + * to Vsys which correspond empty battery situation. + */ +struct simple_gauge_desc { + int degrade_cycle_uah; + int amount_of_temp_dgr; + struct power_supply_temp_degr *temp_dgr; + int poll_interval; + int calibrate_interval; + int designed_cap; /* This is also looked from batinfo (DT node) */ + int cap_adjust_volt_threshold; + int system_min_voltage; + bool allow_set_cycle; + bool clamp_soc; + void *drv_data; +}; + +/** + * struct simple_gauge_psy - power supply configuration + * + * configuration being further passed to power-supply registration. + */ +struct simple_gauge_psy { + const char *psy_name; + struct device_node *of_node; + /* Device specific sysfs attributes, delivered to power_supply */ + const struct attribute_group **attr_grp; + + enum power_supply_property *additional_props; + int num_additional_props; + + int (*is_writable)(struct simple_gauge *gauge, + enum power_supply_property psp); + int (*get_custom_property)(struct simple_gauge *gauge, + enum power_supply_property psp, + union power_supply_propval *val); + int (*set_custom_property)(struct simple_gauge *gauge, + enum power_supply_property psp, + const union power_supply_propval *val); +}; + +/** + * struct simple_gauge - simple_gauge runtime data + * + * Internal to sw-gauge. Should not be directly accessed/modified by drivers + */ +struct simple_gauge { + struct device *dev; + int designed_cap; /* This should be available for drivers */ + struct simple_gauge_desc desc; + int cycle; + u64 next_iter; /* Time of next iteration in jiffies64 */ + u64 next_cal; /* Time of next calibration in jiffies64 */ + int force_run; + int refcount; + struct power_supply *psy; + enum power_supply_property *properties; + + int (*get_custom_property)(struct simple_gauge *gauge, + enum power_supply_property psp, + union power_supply_propval *val); + int (*set_custom_property)(struct simple_gauge *gauge, + enum power_supply_property psp, + const union power_supply_propval *val); + int (*custom_is_writable)(struct simple_gauge *gauge, + enum power_supply_property psp); + struct power_supply_battery_info info; + struct simple_gauge_ops ops; + struct list_head node; + int amount_of_temp_dgr; + struct power_supply_temp_degr *temp_dgr; + spinlock_t lock; + bool batinfo_got; + wait_queue_head_t wq; + int soc_rounding; + int clamped_soc; + /* Cached values from prev iteration */ + int soc; /* SOC computed at previous iteration */ + int capacity_uah; /* CAP computed at previous iteration (uAh) */ + int cc_uah; /* uAh reported by CC at previous iteration */ + int temp; /* Temperature at previous iteration */ +}; + +struct simple_gauge *__must_check psy_register_simple_gauge(struct device *parent, + struct simple_gauge_psy *psycfg, + struct simple_gauge_ops *ops, + struct simple_gauge_desc *desc); +void psy_remove_simple_gauge(struct simple_gauge *sw); + +struct simple_gauge *__must_check +devm_psy_register_simple_gauge(struct device *parent, struct simple_gauge_psy *psycfg, + struct simple_gauge_ops *ops, + struct simple_gauge_desc *desc); +void simple_gauge_run(struct simple_gauge *sw); +int simple_gauge_run_blocking_timeout(struct simple_gauge *sg, + unsigned int timeout_ms); +int simple_gauge_run_blocking(struct simple_gauge *sg); +void *simple_gauge_get_drvdata(struct simple_gauge *sg); + +#endif /* POWER_SW_GAUGE_H */ From patchwork Tue Nov 16 12:29:24 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 12622303 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 593D2C433FE for ; Tue, 16 Nov 2021 12:29:47 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 40AA661284 for ; Tue, 16 Nov 2021 12:29:47 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236107AbhKPMcm (ORCPT ); Tue, 16 Nov 2021 07:32:42 -0500 Received: from mail-lf1-f50.google.com ([209.85.167.50]:35667 "EHLO mail-lf1-f50.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236341AbhKPMca (ORCPT ); Tue, 16 Nov 2021 07:32:30 -0500 Received: by mail-lf1-f50.google.com with SMTP id p16so52853131lfa.2; Tue, 16 Nov 2021 04:29:33 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=56xLv6JmcjfsI8FlVRwUTjzLIMUQa2SiRCS0GWh88Nc=; b=eN7qZPOF9JMKFyIHAZfSVrrkNaZ+oiUtZuSLB1+x6exlRob7LDLBZylytC26MkqbbS VwKlECWCgb4u5XsPy9l1KDP75vXTuZ9dblywl3dbsRXdfA0cWUK34gXsmj5SLIFxWnBH fEv/7/HnESgQjPpJ+8CBSqGmgRQR84/0As1Ya/zoDs4V4CAC2uNIxzDBKTjx170hKORK H72ynRz7hdXL0Dz+2nfus4jKV4m9nWAOzc5vm9RGcBO6GunVtHN10zgPLzQWAUmz3YPX Z90sYy8HVpA5bHJUR7pz60S5ZMgiGJGupLA0S4E1/j4tBP9fpy+9lGyzKnkM2KSeFupC sE9Q== X-Gm-Message-State: AOAM5308t+JTRRDXH6sdZrqfFkn4d4nspkvA9795vKoWlQ+ffH1mES6d 0CDjPHtzH6qBgTL4X0bzT94/QoPDSzE= X-Google-Smtp-Source: ABdhPJxBZip9rSM/LC4Ed2xHSYrQ60UwRdqpr/NGMtQcLDhbmG4sIZdwtr2H3T96N6E+kdXL2GHk2A== X-Received: by 2002:a05:6512:3090:: with SMTP id z16mr6364257lfd.335.1637065772419; Tue, 16 Nov 2021 04:29:32 -0800 (PST) Received: from fedora (dc73szyyyyyyyyyyyyycy-3.rev.dnainternet.fi. [2001:14ba:16ee:fa00::4]) by smtp.gmail.com with ESMTPSA id e20sm1832964ljo.119.2021.11.16.04.29.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Nov 2021 04:29:31 -0800 (PST) Date: Tue, 16 Nov 2021 14:29:24 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Sebastian Reichel , Rob Herring , Matti Vaittinen , Lee Jones , Linus Walleij , rostokus@gmail.com, fan.chen@mediatek.com, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-power@fi.rohmeurope.com Subject: [RFC PATCH v3 8/9] mfd: bd71828, bd71815 prepare for power-supply support Message-ID: References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org Add core support for ROHM BD718(15/28/78) PMIC's charger blocks. Signed-off-by: Matti Vaittinen --- RFCv3: - Updated BD71815 IRQ information drop include/linux/mfd/rohm-bd71827.h --- drivers/mfd/rohm-bd71828.c | 42 +++++++++++++++++---- include/linux/mfd/rohm-bd71828.h | 65 ++++++++++++++++++++++++++++++++ include/linux/mfd/rohm-generic.h | 2 + 3 files changed, 101 insertions(+), 8 deletions(-) diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c index 714d9fcbf07b..0dd56c6f827a 100644 --- a/drivers/mfd/rohm-bd71828.c +++ b/drivers/mfd/rohm-bd71828.c @@ -45,8 +45,8 @@ static const struct resource bd71828_rtc_irqs[] = { static struct resource bd71815_power_irqs[] = { DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_RMV, "bd71815-dcin-rmv"), - DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_OUT, "bd71815-clps-out"), - DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_IN, "bd71815-clps-in"), + DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_OUT, "bd71815-dcin-clps-out"), + DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_IN, "bd71815-dcin-clps-in"), DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_OVP_RES, "bd71815-dcin-ovp-res"), DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_OVP_DET, "bd71815-dcin-ovp-det"), DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_MON_RES, "bd71815-dcin-mon-res"), @@ -56,7 +56,7 @@ static struct resource bd71815_power_irqs[] = { DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_LOW_RES, "bd71815-vsys-low-res"), DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_LOW_DET, "bd71815-vsys-low-det"), DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_MON_RES, "bd71815-vsys-mon-res"), - DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_MON_RES, "bd71815-vsys-mon-det"), + DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_MON_DET, "bd71815-vsys-mon-det"), DEFINE_RES_IRQ_NAMED(BD71815_INT_CHG_WDG_TEMP, "bd71815-chg-wdg-temp"), DEFINE_RES_IRQ_NAMED(BD71815_INT_CHG_WDG_TIME, "bd71815-chg-wdg"), DEFINE_RES_IRQ_NAMED(BD71815_INT_CHG_RECHARGE_RES, "bd71815-rechg-res"), @@ -87,10 +87,10 @@ static struct resource bd71815_power_irqs[] = { DEFINE_RES_IRQ_NAMED(BD71815_INT_BAT_OVER_CURR_2_DET, "bd71815-bat-oc2-det"), DEFINE_RES_IRQ_NAMED(BD71815_INT_BAT_OVER_CURR_3_RES, "bd71815-bat-oc3-res"), DEFINE_RES_IRQ_NAMED(BD71815_INT_BAT_OVER_CURR_3_DET, "bd71815-bat-oc3-det"), - DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_LOW_RES, "bd71815-bat-low-res"), - DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_LOW_DET, "bd71815-bat-low-det"), - DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_HI_RES, "bd71815-bat-hi-res"), - DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_HI_DET, "bd71815-bat-hi-det"), + DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_LOW_RES, "bd71815-temp-bat-low-res"), + DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_LOW_DET, "bd71815-temp-bat-low-det"), + DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_HI_RES, "bd71815-temp-bat-hi-res"), + DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_HI_DET, "bd71815-temp-bat-hi-det"), }; static struct mfd_cell bd71815_mfd_cells[] = { @@ -109,6 +109,29 @@ static struct mfd_cell bd71815_mfd_cells[] = { }, }; +static const struct resource bd71828_power_irqs[] = { + DEFINE_RES_IRQ_NAMED(BD71828_INT_CHG_TOPOFF_TO_DONE, + "bd71828-chg-done"), + DEFINE_RES_IRQ_NAMED(BD71828_INT_DCIN_DET, "bd71828-pwr-dcin-in"), + DEFINE_RES_IRQ_NAMED(BD71828_INT_DCIN_RMV, "bd71828-pwr-dcin-out"), + DEFINE_RES_IRQ_NAMED(BD71828_INT_BAT_LOW_VOLT_RES, + "bd71828-vbat-normal"), + DEFINE_RES_IRQ_NAMED(BD71828_INT_BAT_LOW_VOLT_DET, "bd71828-vbat-low"), + DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_BAT_HI_DET, "bd71828-btemp-hi"), + DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_BAT_HI_RES, "bd71828-btemp-cool"), + DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_BAT_LOW_DET, "bd71828-btemp-lo"), + DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_BAT_LOW_RES, + "bd71828-btemp-warm"), + DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_CHIP_OVER_VF_DET, + "bd71828-temp-hi"), + DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_CHIP_OVER_VF_RES, + "bd71828-temp-norm"), + DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_CHIP_OVER_125_DET, + "bd71828-temp-125-over"), + DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_CHIP_OVER_125_RES, + "bd71828-temp-125-under"), +}; + static struct mfd_cell bd71828_mfd_cells[] = { { .name = "bd71828-pmic", }, { .name = "bd71828-gpio", }, @@ -118,8 +141,11 @@ static struct mfd_cell bd71828_mfd_cells[] = { * BD70528 clock gate are the register address and mask. */ { .name = "bd71828-clk", }, - { .name = "bd71827-power", }, { + .name = "bd71828-power", + .resources = bd71828_power_irqs, + .num_resources = ARRAY_SIZE(bd71828_power_irqs), + }, { .name = "bd71828-rtc", .resources = bd71828_rtc_irqs, .num_resources = ARRAY_SIZE(bd71828_rtc_irqs), diff --git a/include/linux/mfd/rohm-bd71828.h b/include/linux/mfd/rohm-bd71828.h index 3b5f3a7db4bd..7e0674f184ee 100644 --- a/include/linux/mfd/rohm-bd71828.h +++ b/include/linux/mfd/rohm-bd71828.h @@ -188,6 +188,71 @@ enum { /* Charger/Battey */ #define BD71828_REG_CHG_STATE 0x65 #define BD71828_REG_CHG_FULL 0xd2 +#define BD71828_REG_CHG_EN 0x6F +#define BD71828_REG_DCIN_STAT 0x68 +#define BD71828_MASK_DCIN_DET 0x01 +#define BD71828_REG_VDCIN_U 0x9c +#define BD71828_MASK_CHG_EN 0x01 +#define BD71828_CHG_MASK_DCIN_U 0x0f +#define BD71828_REG_BAT_STAT 0x67 +#define BD71828_REG_BAT_TEMP 0x6c +#define BD71828_MASK_BAT_TEMP 0x07 +#define BD71828_BAT_TEMP_OPEN 0x07 +#define BD71828_MASK_BAT_DET 0x20 +#define BD71828_MASK_BAT_DET_DONE 0x10 +#define BD71828_REG_CHG_STATE 0x65 +#define BD71828_REG_VBAT_U 0x8c +#define BD71828_MASK_VBAT_U 0x0f +#define BD71828_REG_VBAT_REX_AVG_U 0x92 + +#define BD71828_REG_OCV_PWRON_U 0x8A + +#define BD71828_REG_VBAT_MIN_AVG_U 0x8e +#define BD71828_REG_VBAT_MIN_AVG_L 0x8f + +#define BD71828_REG_CC_CNT3 0xb5 +#define BD71828_REG_CC_CNT2 0xb6 +#define BD71828_REG_CC_CNT1 0xb7 +#define BD71828_REG_CC_CNT0 0xb8 +#define BD71828_REG_CC_CURCD_AVG_U 0xb2 +#define BD71828_MASK_CC_CURCD_AVG_U 0x3f +#define BD71828_MASK_CC_CUR_DIR 0x80 +#define BD71828_REG_VM_BTMP_U 0xa1 +#define BD71828_REG_VM_BTMP_L 0xa2 +#define BD71828_MASK_VM_BTMP_U 0x0f +#define BD71828_REG_COULOMB_CTRL 0xc4 +#define BD71828_REG_COULOMB_CTRL2 0xd2 +#define BD71828_MASK_REX_CC_CLR 0x01 +#define BD71828_MASK_FULL_CC_CLR 0x10 +#define BD71828_REG_CC_CNT_FULL3 0xbd +#define BD71828_REG_CC_CNT_CHG3 0xc1 + +#define BD71828_REG_VBAT_INITIAL1_U 0x86 +#define BD71828_REG_VBAT_INITIAL1_L 0x87 + +#define BD71828_REG_VBAT_INITIAL2_U 0x88 +#define BD71828_REG_VBAT_INITIAL2_L 0x89 + +#define BD71828_REG_IBAT_U 0xb0 +#define BD71828_REG_IBAT_L 0xb1 + +#define BD71828_REG_IBAT_AVG_U 0xb2 +#define BD71828_REG_IBAT_AVG_L 0xb3 + +#define BD71828_REG_VSYS_AVG_U 0x96 +#define BD71828_REG_VSYS_AVG_L 0x97 +#define BD71828_REG_VSYS_MIN_AVG_U 0x98 +#define BD71828_REG_VSYS_MIN_AVG_L 0x99 +#define BD71828_REG_CHG_SET1 0x75 +#define BD71828_REG_ALM_VBAT_LIMIT_U 0xaa +#define BD71828_REG_BATCAP_MON_LIMIT_U 0xcc +#define BD71828_REG_CONF 0x64 + +#define BD71828_REG_DCIN_CLPS 0x71 + +#define BD71828_REG_MEAS_CLEAR 0xaf + + /* LEDs */ #define BD71828_REG_LED_CTRL 0x4A diff --git a/include/linux/mfd/rohm-generic.h b/include/linux/mfd/rohm-generic.h index 35b392a0d73a..b60c2ea3e37c 100644 --- a/include/linux/mfd/rohm-generic.h +++ b/include/linux/mfd/rohm-generic.h @@ -14,9 +14,11 @@ enum rohm_chip_type { ROHM_CHIP_TYPE_BD9576, ROHM_CHIP_TYPE_BD70528, ROHM_CHIP_TYPE_BD71815, + ROHM_CHIP_TYPE_BD71827, ROHM_CHIP_TYPE_BD71828, ROHM_CHIP_TYPE_BD71837, ROHM_CHIP_TYPE_BD71847, + ROHM_CHIP_TYPE_BD71878, ROHM_CHIP_TYPE_AMOUNT }; From patchwork Tue Nov 16 12:29:53 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 12622305 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 056B4C433F5 for ; Tue, 16 Nov 2021 12:30:11 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id C4E4A61B3D for ; Tue, 16 Nov 2021 12:30:10 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236215AbhKPMdF (ORCPT ); Tue, 16 Nov 2021 07:33:05 -0500 Received: from mail-lj1-f178.google.com ([209.85.208.178]:35336 "EHLO mail-lj1-f178.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236286AbhKPMdD (ORCPT ); Tue, 16 Nov 2021 07:33:03 -0500 Received: by mail-lj1-f178.google.com with SMTP id 1so42927115ljv.2; Tue, 16 Nov 2021 04:30:02 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=Jps1J167NdbhscKTXWY3kk0piH+M307iQW4GKlVdWVY=; b=Nv6S6BvJLn4UIqQA/8HIv2f9sPbpQj4wavf2L71uwl04Bq81zvwq4C6+X0cLNI7MSL aTXqcB5Hc0GpM4VEG337S+UK+O9zN+Zx3FMEFcG4SXgxqHbwhDGW8vBgJsyl4RZLyDcz blgCFXe29T9GQGPwKOg6nXq/AnQrfIZfG4+g+3w6HSMHMrtqvTHK28LkVUpTCXRu6FR2 QtiOI3V667uIicVeicwYw8enJk+fCa9hBzLQcHFPKghrp+64rq+5K5gwwlf7QWlHeOJP 2mYP+kLkbnP+hC8aZrK624W8Ql0r3kQpuvX6+zOrKFoZ/JfPf673h1Ykj69T1nR4Io08 /QrA== X-Gm-Message-State: AOAM530AT0p0Gguu07vzJOvd8TfO+nXPMlNQpQPeAE3oclDmhhN4dMIa 8Po6C5cQJcrgKfJeQUthPsI= X-Google-Smtp-Source: ABdhPJxe9kV20MpOMDIRROor3So7G8XkiHZ/wHIN3Ix4h/vqVvQaROpBx5F/JIYj01wC7s6PoYD1Vw== X-Received: by 2002:a05:651c:4c9:: with SMTP id e9mr6777166lji.10.1637065801372; Tue, 16 Nov 2021 04:30:01 -0800 (PST) Received: from fedora (dc73szyyyyyyyyyyyyycy-3.rev.dnainternet.fi. [2001:14ba:16ee:fa00::4]) by smtp.gmail.com with ESMTPSA id k30sm547012lfv.196.2021.11.16.04.30.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Nov 2021 04:30:00 -0800 (PST) Date: Tue, 16 Nov 2021 14:29:53 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Sebastian Reichel , Rob Herring , Matti Vaittinen , Lee Jones , Linus Walleij , rostokus@gmail.com, fan.chen@mediatek.com, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-power@fi.rohmeurope.com Subject: [RFC PATCH v3 9/9] power: supply: Add bd718(15/27/28/78) charger driver Message-ID: References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org Add charger driver for ROHM BD718(15/27/28/78) PMIC charger block. Driver utilizes the simple-gauge for battery status / current polling, CC correction and SOC estimation. A version of driver which does not utilize simple-gauge can be seen in ROHM venor specific Linux tree - comparison can visualize how simple-gauge can simplify IC specific drivers. https://github.com/RohmSemiconductor/Linux-Kernel-PMIC-Drivers/blob/stable-v5.4.6/drivers/power/supply/bd71827-power.c Signed-off-by: Matti Vaittinen --- Changes for RFCv3: - adapt to changed swgauge name (simple_gauge) - adapt to simple_gauge parameter changes - Initial BD71815 support - Use drvdata properly. - Sort includes - Prepare to provide dcin_collapse voltage from DT - clean unused defines - use OCV tables from batinfo if module params not given - do not directly call bd71827_voltage_to_capacity from calibration but use provided operation. - Mask the power-state from relax-condition on BD71815 as is done by the ROHM driver. REX state is used to do OCV => SOC conversion when battery is relaxed even if REX_CC was not used. - Clarify that we require either the module params or DT values for battery. Fail probe if parameters are not given. - Utilize degrade_cycle_uah aging degradation. - Get battery max and min values either as module parameters or from static battery node at DT. - Allow giving the zero correction threshold as a module param or compute it as 10% of "remaining battery voltage" based on max and min voltages given via DT. - Add proper MODULE_ALIAS - Implement VDR table reading from DT - Do not require fixed amount of battery parameters - Fix Coulomb Counter to uAh conversion - Fix endianess related warnings - clean-up comment - Avoid dividing by zero at VDR computation - Use the fwnode API instead of of_* API . don't assume 32bit int - Fix IC type prints - Fix the current sense resistor DT property *-ohm => *-ohms --- drivers/power/supply/Kconfig | 11 + drivers/power/supply/Makefile | 1 + drivers/power/supply/bd71827-power.c | 2473 ++++++++++++++++++++++++++ include/linux/mfd/rohm-bd71827.h | 295 +++ 4 files changed, 2780 insertions(+) create mode 100644 drivers/power/supply/bd71827-power.c create mode 100644 include/linux/mfd/rohm-bd71827.h diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 24a8d030a391..384afd53fe46 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -791,6 +791,17 @@ config CHARGER_UCS1002 Say Y to enable support for Microchip UCS1002 Programmable USB Port Power Controller with Charger Emulation. +config CHARGER_BD71828 + tristate "Power-supply driver for ROHM BD71828 and BD71815 PMIC" + depends on MFD_ROHM_BD71828 + select POWER_SIMPLE_GAUGE + help + Say Y here to enable support for charger, battery and fuel gauge + in ROHM BD71815, BD71817, BD71827, ROHM BD71828 power management + ICs. This driver gets various bits of information about battery + and charger states and also implements fuel gauge based on + coulomb counters on PMIC. + config CHARGER_BD99954 tristate "ROHM bd99954 charger driver" depends on I2C diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 8c8c5f6a6492..e26f6ae598ad 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -99,6 +99,7 @@ obj-$(CONFIG_CHARGER_CROS_PCHG) += cros_peripheral_charger.o obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o +obj-$(CONFIG_CHARGER_BD71828) += bd71827-power.o obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o diff --git a/drivers/power/supply/bd71827-power.c b/drivers/power/supply/bd71827-power.c new file mode 100644 index 000000000000..6d2a9bc6f3dd --- /dev/null +++ b/drivers/power/supply/bd71827-power.c @@ -0,0 +1,2473 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * bd71827-power.c + * @file ROHM BD71815, BD71827, BD71828 and BD71878 Charger driver + * + * Copyright 2021. + * + * 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 +#include +#include +#include +#include + +#define MAX(X, Y) ((X) >= (Y) ? (X) : (Y)) +#define uAMP_TO_mAMP(ma) ((ma) / 1000) + +#define LINEAR_INTERPOLATE(y_hi, y_lo, x_hi, x_lo, x) \ + ((y_lo) + ((x) - (x_lo)) * ((y_hi) - (y_lo)) / ((x_hi) - (x_lo))) + +#define CAP2DSOC(cap, full_cap) ((cap) * 1000 / (full_cap)) + +/* common defines */ +#define BD7182x_MASK_VBAT_U 0x1f +#define BD7182x_MASK_VDCIN_U 0x0f +#define BD7182x_MASK_IBAT_U 0x3f +#define BD7182x_MASK_CURDIR_DISCHG 0x80 +#define BD7182x_MASK_CC_CCNTD_HI 0x0FFF +#define BD7182x_MASK_CC_CCNTD 0x0FFFFFFF +#define BD7182x_MASK_CHG_STATE 0x7f +#define BD7182x_MASK_CC_FULL_CLR 0x10 +#define BD7182x_MASK_BAT_TEMP 0x07 +#define BD7182x_MASK_DCIN_DET BIT(0) +#define BD7182x_MASK_CONF_PON BIT(0) +#define BD71815_MASK_CONF_XSTB BIT(1) +#define BD7182x_MASK_BAT_STAT 0x3f +#define BD7182x_MASK_DCIN_STAT 0x07 + +#define BD7182x_MASK_CCNTRST 0x80 +#define BD7182x_MASK_CCNTENB 0x40 +#define BD7182x_MASK_CCCALIB 0x20 +#define BD7182x_MASK_WDT_AUTO 0x40 +#define BD7182x_MASK_VBAT_ALM_LIMIT_U 0x01 +#define BD7182x_MASK_CHG_EN 0x01 + +#define BD7182x_DCIN_COLLAPSE_DEFAULT 0x36 + +static const struct linear_range dcin_collapse = { + .min = 0, + .min_sel = 0, + .max_sel = 0xff, + /* Anti-collapse voltage threshold 0.0V to 20.4V range, 80 mV steps */ + .step = 80000, +}; + +/* Measured min and max value clear bits */ +#define BD718XX_MASK_VSYS_MIN_AVG_CLR 0x10 + +#define JITTER_DEFAULT 3000 +#define MAX_CURRENT_DEFAULT 890000 /* uA */ +#define AC_NAME "bd71827_ac" +#define BAT_NAME "bd71827_bat" + +/* + * VBAT Low voltage detection Threshold + * 0x00D4*16mV = 212*0.016 = 3.392v + */ +#define VBAT_LOW_TH 0x00D4 + +#define THR_RELAX_CURRENT_DEFAULT 5 /* mA */ +#define THR_RELAX_TIME_DEFAULT (60 * 60) /* sec. */ + +#define DGRD_CYC_CAP_DEFAULT 88 /* 1 micro Ah */ + +#define DGRD_TEMP_H_DEFAULT 450 /* 0.1 degrees C */ +#define DGRD_TEMP_M_DEFAULT 250 /* 0.1 degrees C */ +#define DGRD_TEMP_L_DEFAULT 50 /* 0.1 degrees C */ +#define DGRD_TEMP_VL_DEFAULT 0 /* 0.1 degrees C */ + +#define SOC_EST_MAX_NUM_DEFAULT 5 +#define PWRCTRL_NORMAL 0x22 +#define PWRCTRL_RESET 0x23 + +/* + * Originally we relied upon a fixed size table of OCV and VDR params. + * However the exising linux power-supply batinfo interface for getting the OCV + * values from DT does not have fixed amount of OCV values. Thus we use fixed + * parameter amount only for values provided as module params - and use this + * only as maximum number of parameters when values come from DT. + */ +#define NUM_BAT_PARAMS 23 +#define MAX_NUM_VDR_VALUES NUM_BAT_PARAMS + +struct pwr_regs { + int used_init_regs; + u8 vbat_init; + u8 vbat_init2; + u8 vbat_init3; + u8 vbat_avg; + u8 ibat; + u8 ibat_avg; + u8 meas_clear; + u8 vsys_min_avg; + u8 btemp_vth; + u8 chg_state; + u8 coulomb3; + u8 coulomb2; + u8 coulomb1; + u8 coulomb0; + u8 coulomb_ctrl; + u8 vbat_rex_avg; + u8 coulomb_full3; + u8 cc_full_clr; + u8 coulomb_chg3; + u8 bat_temp; + u8 dcin_stat; + u8 dcin_collapse_limit; + u8 chg_set1; + u8 chg_en; + u8 vbat_alm_limit_u; + u8 batcap_mon_limit_u; + u8 conf; + u8 vdcin; +#ifdef PWRCTRL_HACK + u8 pwrctrl; +#endif +}; + +/* Regions for High, Medium, Low, Very Low temperature */ +enum { + VDR_TEMP_HIGH, + VDR_TEMP_NORMAL, + VDR_TEMP_LOW, + VDR_TEMP_VERY_LOW, + NUM_VDR_TEMPS +}; + +/* + * This works as long as we have only one instance of this driver (which is + * likely to be the case even with DT originated battery info). Anyways, + * consider moving these in allocated data just to pretend to know what I am + * doing XD + */ +static int vdr_temps[NUM_VDR_TEMPS] = { -EINVAL, -EINVAL, -EINVAL, -EINVAL}; +static int g_num_vdr_params; + +static struct pwr_regs pwr_regs_bd71827 = { + .vbat_init = BD71827_REG_VM_OCV_PRE_U, + .vbat_init2 = BD71827_REG_VM_OCV_PST_U, + .vbat_init3 = BD71827_REG_VM_OCV_PWRON_U, + .used_init_regs = 3, + .vbat_avg = BD71827_REG_VM_SA_VBAT_U, + .ibat = BD71827_REG_CC_CURCD_U, + .ibat_avg = BD71827_REG_CC_SA_CURCD_U, + .meas_clear = BD71827_REG_VM_SA_MINMAX_CLR, + .vsys_min_avg = BD71827_REG_VM_SA_VSYS_MIN_U, + .btemp_vth = BD71827_REG_VM_BTMP, + .chg_state = BD71827_REG_CHG_STATE, + .coulomb3 = BD71827_REG_CC_CCNTD_3, + .coulomb2 = BD71827_REG_CC_CCNTD_2, + .coulomb1 = BD71827_REG_CC_CCNTD_1, + .coulomb0 = BD71827_REG_CC_CCNTD_0, + .coulomb_ctrl = BD71827_REG_CC_CTRL, + .coulomb_full3 = BD71827_REG_FULL_CCNTD_3, + .cc_full_clr = BD71827_REG_FULL_CTRL, + .coulomb_chg3 = BD71827_REG_CCNTD_CHG_3, + .bat_temp = BD71827_REG_BAT_TEMP, + .dcin_stat = BD71827_REG_DCIN_STAT, + .dcin_collapse_limit = BD71827_REG_DCIN_CLPS, + .chg_set1 = BD71827_REG_CHG_SET1, + .chg_en = BD71827_REG_CHG_SET1, + .vbat_alm_limit_u = BD71827_REG_ALM_VBAT_TH_U, + .batcap_mon_limit_u = BD71827_REG_CC_BATCAP1_TH_U, + .conf = BD71827_REG_CONF, + .vdcin = BD71827_REG_VM_DCIN_U, +#ifdef PWRCTRL_HACK + .pwrctrl = BD71827_REG_PWRCTRL, + .hibernate_mask = 0x1, +#endif +}; + +static struct pwr_regs pwr_regs_bd71828 = { + .vbat_init = BD71828_REG_VBAT_INITIAL1_U, + .vbat_init2 = BD71828_REG_VBAT_INITIAL2_U, + .vbat_init3 = BD71828_REG_OCV_PWRON_U, + .used_init_regs = 3, + .vbat_avg = BD71828_REG_VBAT_U, + .ibat = BD71828_REG_IBAT_U, + .ibat_avg = BD71828_REG_IBAT_AVG_U, + .meas_clear = BD71828_REG_MEAS_CLEAR, + .vsys_min_avg = BD71828_REG_VSYS_MIN_AVG_U, + .btemp_vth = BD71828_REG_VM_BTMP_U, + .chg_state = BD71828_REG_CHG_STATE, + .coulomb3 = BD71828_REG_CC_CNT3, + .coulomb2 = BD71828_REG_CC_CNT2, + .coulomb1 = BD71828_REG_CC_CNT1, + .coulomb0 = BD71828_REG_CC_CNT0, + .coulomb_ctrl = BD71828_REG_COULOMB_CTRL, + .vbat_rex_avg = BD71828_REG_VBAT_REX_AVG_U, + .coulomb_full3 = BD71828_REG_CC_CNT_FULL3, + .cc_full_clr = BD71828_REG_COULOMB_CTRL2, + .coulomb_chg3 = BD71828_REG_CC_CNT_CHG3, + .bat_temp = BD71828_REG_BAT_TEMP, + .dcin_stat = BD71828_REG_DCIN_STAT, + .dcin_collapse_limit = BD71828_REG_DCIN_CLPS, + .chg_set1 = BD71828_REG_CHG_SET1, + .chg_en = BD71828_REG_CHG_EN, + .vbat_alm_limit_u = BD71828_REG_ALM_VBAT_LIMIT_U, + .batcap_mon_limit_u = BD71828_REG_BATCAP_MON_LIMIT_U, + .conf = BD71828_REG_CONF, + .vdcin = BD71828_REG_VDCIN_U, +#ifdef PWRCTRL_HACK + .pwrctrl = BD71828_REG_PS_CTRL_1, + .hibernate_mask = 0x2, +#endif +}; + +static struct pwr_regs pwr_regs_bd71815 = { + .vbat_init = BD71815_REG_VM_OCV_PRE_U, + .vbat_init2 = BD71815_REG_VM_OCV_PST_U, + .used_init_regs = 2, + .vbat_avg = BD71815_REG_VM_SA_VBAT_U, + /* BD71815 does not have separate current and current avg */ + .ibat = BD71815_REG_CC_CURCD_U, + .ibat_avg = BD71815_REG_CC_CURCD_U, + + .meas_clear = BD71815_REG_VM_SA_MINMAX_CLR, + .vsys_min_avg = BD71815_REG_VM_SA_VSYS_MIN_U, + .btemp_vth = BD71815_REG_VM_BTMP, + .chg_state = BD71815_REG_CHG_STATE, + .coulomb3 = BD71815_REG_CC_CCNTD_3, + .coulomb2 = BD71815_REG_CC_CCNTD_2, + .coulomb1 = BD71815_REG_CC_CCNTD_1, + .coulomb0 = BD71815_REG_CC_CCNTD_0, + .coulomb_ctrl = BD71815_REG_CC_CTRL, + .vbat_rex_avg = BD71815_REG_REX_SA_VBAT_U, + .coulomb_full3 = BD71815_REG_FULL_CCNTD_3, + .cc_full_clr = BD71815_REG_FULL_CTRL, + .coulomb_chg3 = BD71815_REG_CCNTD_CHG_3, + .bat_temp = BD71815_REG_BAT_TEMP, + .dcin_stat = BD71815_REG_DCIN_STAT, + .dcin_collapse_limit = BD71815_REG_DCIN_CLPS, + .chg_set1 = BD71815_REG_CHG_SET1, + .chg_en = BD71815_REG_CHG_SET1, + .vbat_alm_limit_u = BD71815_REG_ALM_VBAT_TH_U, + .batcap_mon_limit_u = BD71815_REG_CC_BATCAP1_TH_U, + .conf = BD71815_REG_CONF, + + .vdcin = BD71815_REG_VM_DCIN_U, +#ifdef PWRCTRL_HACK + #error "Not implemented for BD71815" +#endif +}; + +/* + * unit 0.1% + * + * These are the SOCs we use at zero-correction. If OCV is given via DT + * we interpolate the OCV tables to get the OCV corresponding these SOCs. + * + * If VDR tables are given we will ovrride these SOCs by SOCs corresponding + * the VDR values. + */ +static int soc_table_default[NUM_BAT_PARAMS] = { + 1000, + 1000, + 950, + 900, + 850, + 800, + 750, + 700, + 650, + 600, + 550, + 500, + 450, + 400, + 350, + 300, + 250, + 200, + 150, + 100, + 50, + 0, + -50 +}; + +/* Module parameters */ +static int use_load_bat_params; +static int param_thr_voltage; +static int param_max_voltage; +static int param_min_voltage; + +static int battery_cap_mah; + +static int dgrd_cyc_cap = DGRD_CYC_CAP_DEFAULT; + +static int soc_est_max_num; +static int ocv_table[NUM_BAT_PARAMS]; +static int soc_table[NUM_BAT_PARAMS]; +static int vdr_table_h[NUM_BAT_PARAMS]; +static int vdr_table_m[NUM_BAT_PARAMS]; +static int vdr_table_l[NUM_BAT_PARAMS]; +static int vdr_table_vl[NUM_BAT_PARAMS]; + +struct bd71827_power { + struct simple_gauge *sw; + struct simple_gauge_desc gdesc; + struct simple_gauge_ops ops; + struct regmap *regmap; + enum rohm_chip_type chip_type; + struct device *dev; + struct power_supply *ac; /**< alternating current power */ + int gauge_delay; /**< Schedule to call gauge algorithm */ + int relax_time; /**< Relax Time */ + + struct pwr_regs *regs; + /* Reg val to uA */ + int curr_factor; + int rsens; + int min_voltage; + int max_voltage; + int low_thr_voltage; + int (*get_temp)(struct bd71827_power *pwr, int *temp); + int (*bat_inserted)(struct bd71827_power *pwr); + int battery_cap; + struct power_supply_battery_info batinfo; +}; + +#define __CC_to_UAH(pwr, cc) \ +({ \ + u64 __tmp = ((u64)(cc)) * 1000000000LLU; \ + \ + do_div(__tmp, (pwr)->rsens * 36 / 1000); \ + __tmp; \ +}) + +#define CC16_to_UAH(pwe, cc) ((int)__CC_to_UAH((pwr), (cc))) +#define CC32_to_UAH(pwe, cc) ((int)(__CC_to_UAH((pwr), (cc)) >> 16)) + +/* + * rsens is typically tens of Mohms so dividing by 1000 should be ok. (usual + * values are 10 and 30 Mohms so division is likely to go even). We do this + * to avoid doing two do_divs which would be unnecessary performance hit + * even if this should not be time critical. + */ +#define UAH_to_CC(pwr, uah) ({ \ + u64 __tmp = (uah); \ + u32 __rs = (pwr)->rsens / 1000; \ + __tmp *= ((u64)__rs) * 36LLU; \ + \ + do_div(__tmp, 1000000000); \ + (int)__tmp; \ +}) + +#define CALIB_NORM 0 +#define CALIB_START 1 +#define CALIB_GO 2 + +enum { + STAT_POWER_ON, + STAT_INITIALIZED, +}; + +static int bd7182x_write16(struct bd71827_power *pwr, int reg, uint16_t val) +{ + __be16 tmp; + + tmp = cpu_to_be16(val); + + return regmap_bulk_write(pwr->regmap, reg, &tmp, sizeof(tmp)); +} + +static int bd7182x_read16_himask(struct bd71827_power *pwr, int reg, int himask, + uint16_t *val) +{ + struct regmap *regmap = pwr->regmap; + int ret; + __be16 rvals; + u8 *tmp = (u8 *) &rvals; + + ret = regmap_bulk_read(regmap, reg, &rvals, sizeof(*val)); + if (!ret) { + *tmp &= himask; + *val = be16_to_cpu(rvals); + } + return ret; +} + +#define MAX_INITIAL_OCV_REGS 3 +/* get initial battery voltage and current */ +static int bd71827_get_init_voltage(struct bd71827_power *pwr, + int *ocv) +{ + int ret; + int i; + u8 regs[MAX_INITIAL_OCV_REGS] = { + pwr->regs->vbat_init, + pwr->regs->vbat_init2, + pwr->regs->vbat_init3 + }; + uint16_t vals[MAX_INITIAL_OCV_REGS]; + + *ocv = 0; + + for (i = 0; i < pwr->regs->used_init_regs; i++) { + + ret = bd7182x_read16_himask(pwr, regs[i], BD7182x_MASK_VBAT_U, + &vals[i]); + if (ret) { + dev_err(pwr->dev, + "Failed to read initial battery voltage\n"); + return ret; + } + *ocv = MAX(vals[i], *ocv); + + dev_dbg(pwr->dev, "VM_OCV_%d = %d\n", i, ((int)vals[i]) * 1000); + } + + *ocv *= 1000; + + return ret; +} + +static int bd71827_get_vbat(struct bd71827_power *pwr, int *vcell) +{ + uint16_t tmp_vcell; + int ret; + + ret = bd7182x_read16_himask(pwr, pwr->regs->vbat_avg, + BD7182x_MASK_VBAT_U, &tmp_vcell); + if (ret) + dev_err(pwr->dev, "Failed to read battery average voltage\n"); + else + *vcell = ((int)tmp_vcell) * 1000; + + return ret; +} + +static int bd71827_get_current_ds_adc(struct bd71827_power *pwr, int *curr, int *curr_avg) +{ + __be16 tmp_curr; + char *tmp = (char *)&tmp_curr; + int dir = 1; + int regs[] = { pwr->regs->ibat, pwr->regs->ibat_avg }; + int *vals[] = { curr, curr_avg }; + int ret, i; + + for (dir = 1, i = 0; i < ARRAY_SIZE(regs); i++) { + ret = regmap_bulk_read(pwr->regmap, regs[i], &tmp_curr, + sizeof(tmp_curr)); + if (ret) + break; + + if (*tmp & BD7182x_MASK_CURDIR_DISCHG) + dir = -1; + + *tmp &= BD7182x_MASK_IBAT_U; + tmp_curr = be16_to_cpu(tmp_curr); + + *vals[i] = dir * ((int)tmp_curr) * pwr->curr_factor; + } + + return ret; +} + +static int bd71827_voltage_to_capacity(struct simple_gauge *sw, int ocv, + int temp __always_unused, + int *dsoc); + +static int bd71827_voltage_to_capacity(struct simple_gauge *sw, int ocv, int temp, + int *dsoc) +{ + int i = 0; + struct bd71827_power *pwr; + + /* If ocv_table is not given try luck with batinfo */ + if (!use_load_bat_params || !ocv_table[0]) { + if (!sw) + return -EINVAL; + + pwr = simple_gauge_get_drvdata(sw); + *dsoc = power_supply_batinfo_ocv2dcap(&pwr->batinfo, ocv, 0); + if (*dsoc < 0) + return *dsoc; + + return 0; + } + + /* Default or module param OCV table. We have NUM_BAT_PARAMS */ + if (ocv > ocv_table[0]) { + *dsoc = soc_table[0]; + } else { + for (i = 0; i < NUM_BAT_PARAMS; i++) { + if ((ocv <= ocv_table[i]) && (ocv > ocv_table[i+1])) { + *dsoc = (soc_table[i] - soc_table[i+1]) * + (ocv - ocv_table[i+1]) / + (ocv_table[i] - ocv_table[i+1]); + *dsoc += soc_table[i+1]; + break; + } + } + if (i == NUM_BAT_PARAMS) + *dsoc = soc_table[i - 1]; + } + + return 0; +} + +/* Unit is tenths of degree C */ +static int bd71827_get_temp(struct simple_gauge *sw, int *temp) +{ + struct bd71827_power *pwr = simple_gauge_get_drvdata(sw); + struct regmap *regmap = pwr->regmap; + int ret; + int t; + + ret = regmap_read(regmap, pwr->regs->btemp_vth, &t); + t = 200 - t; + + if (ret || t > 200) { + dev_err(pwr->dev, "Failed to read battery temperature\n"); + *temp = 2000; + } else { + *temp = t * 10; + } + + return ret; +} + +/* Unit is tenths of degree C */ +static int bd71828_get_temp(struct simple_gauge *sw, int *temp) +{ + struct bd71827_power *pwr = simple_gauge_get_drvdata(sw); + uint16_t t; + int ret; + int tmp = 200 * 10000; + + ret = bd7182x_read16_himask(pwr, pwr->regs->btemp_vth, + BD71828_MASK_VM_BTMP_U, &t); + if (ret || t > 3200) + dev_err(pwr->dev, + "Failed to read system min average voltage\n"); + + tmp -= 625ULL * (unsigned int)t; + *temp = tmp / 1000; + + return ret; +} + +static int bd71827_charge_status(struct bd71827_power *pwr, + int *s, int *h) +{ + unsigned int state; + int status, health; + int ret = 1; + + ret = regmap_read(pwr->regmap, pwr->regs->chg_state, &state); + if (ret) + dev_err(pwr->dev, "charger status reading failed (%d)\n", ret); + + state &= BD7182x_MASK_CHG_STATE; + + dev_dbg(pwr->dev, "CHG_STATE %d\n", state); + + switch (state) { + case 0x00: + ret = 0; + status = POWER_SUPPLY_STATUS_DISCHARGING; + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x01: + case 0x02: + case 0x03: + case 0x0E: + status = POWER_SUPPLY_STATUS_CHARGING; + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x0F: + ret = 0; + status = POWER_SUPPLY_STATUS_FULL; + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + ret = 0; + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + case 0x30: + case 0x31: + case 0x32: + case 0x40: + ret = 0; + status = POWER_SUPPLY_STATUS_DISCHARGING; + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x7f: + default: + ret = 0; + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + health = POWER_SUPPLY_HEALTH_DEAD; + break; + } + + if (s) + *s = status; + if (h) + *h = health; + + return ret; +} + +static int __write_cc(struct bd71827_power *pwr, uint16_t bcap, + unsigned int reg, uint32_t *new) +{ + int ret; + __be32 tmp; + __be16 *swap_hi = (__be16 *)&tmp; + uint16_t *swap_lo = swap_hi + 1; + + *swap_hi = cpu_to_be16(bcap & BD7182x_MASK_CC_CCNTD_HI); + *swap_lo = 0; + + ret = regmap_bulk_write(pwr->regmap, reg, &tmp, sizeof(tmp)); + if (ret) { + dev_err(pwr->dev, "Failed to write coulomb counter\n"); + return ret; + } + if (new) + *new = be32_to_cpu(tmp); + + return ret; +} + +static int write_cc(struct bd71827_power *pwr, uint16_t bcap) +{ + int ret; + uint32_t new; + + ret = __write_cc(pwr, bcap, pwr->regs->coulomb3, &new); + if (!ret) + dev_dbg(pwr->dev, "CC set to 0x%x\n", (int)new); + + return ret; +} + +static int stop_cc(struct bd71827_power *pwr) +{ + struct regmap *r = pwr->regmap; + + return regmap_update_bits(r, pwr->regs->coulomb_ctrl, + BD7182x_MASK_CCNTENB, 0); +} + +static int start_cc(struct bd71827_power *pwr) +{ + struct regmap *r = pwr->regmap; + + return regmap_update_bits(r, pwr->regs->coulomb_ctrl, + BD7182x_MASK_CCNTENB, BD7182x_MASK_CCNTENB); +} + +static int update_cc(struct bd71827_power *pwr, uint16_t bcap) +{ + int ret; + + ret = stop_cc(pwr); + if (ret) + goto err_out; + + ret = write_cc(pwr, bcap); + if (ret) + goto enable_out; + + ret = start_cc(pwr); + if (ret) + goto enable_out; + + return 0; + +enable_out: + start_cc(pwr); +err_out: + dev_err(pwr->dev, "Coulomb counter write failed (%d)\n", ret); + return ret; +} + +static int bd71828_set_uah(struct simple_gauge *sw, int bcap) +{ + struct bd71827_power *pwr = simple_gauge_get_drvdata(sw); + u16 cc_val = UAH_to_CC(pwr, bcap); + + return update_cc(pwr, cc_val); +} + +static int __read_cc(struct bd71827_power *pwr, u32 *cc, unsigned int reg) +{ + int ret; + __be32 tmp_cc; + + ret = regmap_bulk_read(pwr->regmap, reg, &tmp_cc, sizeof(tmp_cc)); + if (ret) { + dev_err(pwr->dev, "Failed to read coulomb counter\n"); + return ret; + } + *cc = be32_to_cpu(tmp_cc) & BD7182x_MASK_CC_CCNTD; + + return 0; +} + +static int read_cc_full(struct bd71827_power *pwr, u32 *cc) +{ + return __read_cc(pwr, cc, pwr->regs->coulomb_full3); +} + +static int read_cc(struct bd71827_power *pwr, u32 *cc) +{ + return __read_cc(pwr, cc, pwr->regs->coulomb3); +} + +/** @brief set initial coulomb counter value from battery voltage + * @param pwr power device + * @return 0 + */ +static int calibration_coulomb_counter(struct bd71827_power *pwr) +{ + struct regmap *regmap = pwr->regmap; + u32 bcap; + int soc, ocv, ret = 0, tmpret = 0; + + /* Get initial OCV */ + bd71827_get_init_voltage(pwr, &ocv); + dev_dbg(pwr->dev, "ocv %d\n", ocv); + + if (pwr->ops.get_soc_by_ocv) { + ret = pwr->ops.get_soc_by_ocv(NULL, ocv, 0, &soc); + } else { + soc = power_supply_batinfo_ocv2dcap(&pwr->batinfo, ocv, 0); + if (soc < 0) + return soc; + } + /* Get init soc from ocv/soc table */ + + dev_dbg(pwr->dev, "soc %d[0.1%%]\n", soc); + if (soc < 0) + soc = 0; + + bcap = UAH_to_CC(pwr, pwr->battery_cap) * soc / 1000; + write_cc(pwr, bcap + UAH_to_CC(pwr, pwr->battery_cap) / 200); + + msleep(5000); + + tmpret = write_cc(pwr, bcap); + if (tmpret) + goto enable_cc_out; + +enable_cc_out: + /* Start canceling offset of the DS ADC. This needs 1 second at least */ + ret = regmap_update_bits(regmap, pwr->regs->coulomb_ctrl, + BD7182x_MASK_CCCALIB, BD7182x_MASK_CCCALIB); + + return (tmpret) ? tmpret : ret; +} + +/* This should be used to get VSYS for low limit calculations */ +static int bd71827_get_vsys_min(struct simple_gauge *sw, int *uv) +{ + struct bd71827_power *pwr = simple_gauge_get_drvdata(sw); + uint16_t tmp_vcell; + int ret; + + ret = bd7182x_read16_himask(pwr, pwr->regs->vsys_min_avg, + BD7182x_MASK_VBAT_U, &tmp_vcell); + if (ret) { + dev_err(pwr->dev, + "Failed to read system min average voltage\n"); + return ret; + } + ret = regmap_update_bits(pwr->regmap, pwr->regs->meas_clear, + BD718XX_MASK_VSYS_MIN_AVG_CLR, + BD718XX_MASK_VSYS_MIN_AVG_CLR); + if (ret) + dev_warn(pwr->dev, "failed to clear cached Vsys\n"); + + *uv = ((int)tmp_vcell) * 1000; + + return 0; +} + +/* This should be used for relax Vbat with BD71827 */ +static int bd71827_get_voltage(struct simple_gauge *sg, int *vbat) +{ + int voltage, ret; + struct bd71827_power *pwr = simple_gauge_get_drvdata(sg); + + ret = bd71827_get_vbat(pwr, &voltage); + if (ret) + return ret; + + *vbat = voltage; + + return 0; +} + +static int bd71828_get_uah_from_full(struct simple_gauge *sw, int *from_full_uah) +{ + int ret; + struct bd71827_power *pwr; + struct regmap *regmap; + u32 full_charged_coulomb_cnt; + u32 cc; + int diff_coulomb_cnt; + + pwr = simple_gauge_get_drvdata(sw); + regmap = pwr->regmap; + + /* + * Read and clear the stored CC value from moment when battery was + * last charged to full. + */ + ret = read_cc_full(pwr, &full_charged_coulomb_cnt); + if (ret) { + dev_err(pwr->dev, "failed to read full coulomb counter\n"); + return ret; + } + + ret = regmap_update_bits(regmap, pwr->regs->cc_full_clr, + BD7182x_MASK_CC_FULL_CLR, + BD7182x_MASK_CC_FULL_CLR); + /* Get current CC value to estimate change of charge since full */ + ret = read_cc(pwr, &cc); + if (ret) + return ret; + + diff_coulomb_cnt = full_charged_coulomb_cnt - cc; + + diff_coulomb_cnt >>= 16; + + /* + * Ignore possible increase in CC which can be caused by ADC offset or + * temperature change + */ + if (diff_coulomb_cnt > 0) + diff_coulomb_cnt = 0; + + *from_full_uah = CC16_to_UAH(pwr, diff_coulomb_cnt); + + return 0; +} + +static int bd71828_get_uah(struct simple_gauge *sw, int *uah) +{ + struct bd71827_power *pwr = simple_gauge_get_drvdata(sw); + u32 cc; + int ret; + + ret = read_cc(pwr, &cc); + if (!ret) + *uah = CC32_to_UAH(pwr, cc); + + return ret; +} + +/* + * Standard batinfo supports only accuracy of 1% for SOC - which + * may not be sufficient for us. SWGAUGE provides soc in unts of 0.1% here + * to allow more accurate computation. + */ +static int bd71827_get_ocv(struct simple_gauge *sw, int dsoc, int temp, int *ocv) +{ + int i = 0; + struct bd71827_power *pwr; + + /* If soc_table is not given try luck with batinfo */ + if (!use_load_bat_params || !ocv_table[0]) { + if (!sw) + return -EINVAL; + + pwr = simple_gauge_get_drvdata(sw); + *ocv = power_supply_batinfo_dcap2ocv(&pwr->batinfo, dsoc, temp); + if (*ocv < 0) + return *ocv; + + return 0; + } + + /* Default or module param OCV table. We have NUM_BAT_PARAMS */ + + if (dsoc > soc_table[0]) { + *ocv = pwr->max_voltage; + return 0; + } + if (dsoc == 0) { + *ocv = ocv_table[NUM_BAT_PARAMS - 2]; + return 0; + } + + i = 0; + while (i < NUM_BAT_PARAMS - 1) { + if ((dsoc <= soc_table[i]) && (dsoc > soc_table[i+1])) { + *ocv = (ocv_table[i] - ocv_table[i+1]) * + (dsoc - soc_table[i+1]) / (soc_table[i] - + soc_table[i+1]) + ocv_table[i+1]; + return 0; + } + i++; + } + + *ocv = ocv_table[NUM_BAT_PARAMS - 1]; + + return 0; +} + +static void calc_vdr(int *res, int *vdr, int temp, int dgrd_temp, + int *vdr_hi, int dgrd_temp_hi, int items) +{ + int i; + + /* Get VDR weighed by temperature */ + for (i = 0; i < items; i++) + res[i] = LINEAR_INTERPOLATE(vdr_hi[i], vdr[i], dgrd_temp_hi, + dgrd_temp, temp); +} + +/* get VDR(Voltage Drop Rate) value by SOC */ +static int bd71827_get_vdr(struct bd71827_power *pwr, int dsoc, int temp) +{ + int i = 0; + int vdr = 100; + int vdr_table[NUM_BAT_PARAMS]; + + /* Calculate VDR by temperature */ + if (temp >= vdr_temps[VDR_TEMP_HIGH]) + for (i = 0; i < g_num_vdr_params; i++) + vdr_table[i] = vdr_table_h[i]; + else if (temp >= vdr_temps[VDR_TEMP_NORMAL]) + calc_vdr(vdr_table, vdr_table_m, temp, vdr_temps[VDR_TEMP_NORMAL], + vdr_table_h, vdr_temps[VDR_TEMP_HIGH], + g_num_vdr_params); + else if (temp >= vdr_temps[VDR_TEMP_LOW]) + calc_vdr(vdr_table, vdr_table_l, temp, vdr_temps[VDR_TEMP_LOW], + vdr_table_m, vdr_temps[VDR_TEMP_NORMAL], + g_num_vdr_params); + else if (temp >= vdr_temps[VDR_TEMP_VERY_LOW]) + calc_vdr(vdr_table, vdr_table_vl, temp, + vdr_temps[VDR_TEMP_VERY_LOW], vdr_table_l, + vdr_temps[VDR_TEMP_LOW], g_num_vdr_params); + else + for (i = 0; i < g_num_vdr_params; i++) + vdr_table[i] = vdr_table_vl[i]; + + if (dsoc > soc_table[0]) { + vdr = 100; + } else if (dsoc == 0) { + vdr = vdr_table[g_num_vdr_params - 1]; + } else { + for (i = 0; i < g_num_vdr_params - 1; i++) + if ((dsoc <= soc_table[i]) && (dsoc > soc_table[i+1])) { + vdr = LINEAR_INTERPOLATE(vdr_table[i], + vdr_table[i+1], + soc_table[i], + soc_table[i+1], dsoc); + + break; + } + if (i == g_num_vdr_params - 1) + vdr = vdr_table[i]; + } + dev_dbg(pwr->dev, "vdr = %d\n", vdr); + return vdr; +} + +static int bd71828_zero_correct(struct simple_gauge *sw, int *effective_cap, + int cc_uah, int vbat, int temp) +{ + int ocv_table_load[NUM_BAT_PARAMS]; + int i, ret; + /* Assume fixed-size module param table */ + static int params = NUM_BAT_PARAMS; + int ocv; + int dsoc; + struct bd71827_power *pwr = simple_gauge_get_drvdata(sw); + + /* + * Calculate SOC from CC and effective battery cap. + * Use unit of 0.1% for dsoc to improve accuracy + */ + dsoc = CAP2DSOC(cc_uah, *effective_cap); + dev_dbg(pwr->dev, "dsoc = %d\n", dsoc); + + ret = bd71827_get_ocv(sw, dsoc, 0, &ocv); + if (ret) + return ret; + + if (!ocv_table[0]) { + for (i = 0; i < g_num_vdr_params; i++) + ocv_table[i] = power_supply_batinfo_dcap2ocv(&pwr->batinfo, + soc_table[i], temp); + /* + * Update amount of OCV values id we didn't have the fixed size + * module param table + */ + params = g_num_vdr_params; + } + for (i = 1; i < params; i++) { + ocv_table_load[i] = ocv_table[i] - (ocv - vbat); + if (ocv_table_load[i] <= pwr->min_voltage) { + dev_dbg(pwr->dev, "ocv_table_load[%d] = %d\n", i, + ocv_table_load[i]); + break; + } + } + + /* + * For improved accuracy ROHM helps customers to measure some + * battery voltage drop curves to do further SOC estimation improvement. + * If VDR tables are available we perform these corrections. + */ + if (i < params) { + int zero_soc_pos; + int j, k, m; + int dv; + int lost_cap, new_lost_cap; + int dsoc0; + int vdr, vdr0; + int soc_range; + + /* + * The original ROHM algorithm had fixed amount of OCV and VDR + * values. The quiet expectation of the algorithm was that the + * second last value in these tables correspond zero SOC. In + * order to relax this assumption when values come from DT we + * try to scan the SOC table for zero SOC. + */ + for (zero_soc_pos = params - 1; zero_soc_pos >= 0; + zero_soc_pos--) + if (soc_table[zero_soc_pos] >= 0) + break; + + if (soc_table[zero_soc_pos]) + dev_warn_once(pwr->dev, + "VDR/OCV: zero SOC not found\n"); + + /* + * We want to know the zero soc position from the last entry + * in SOC table so that we know where the fully depleted cap + * is met. + */ + zero_soc_pos = params - zero_soc_pos; + + soc_range = (soc_table[i - 1] - soc_table[i]) / 10; + if (soc_range < 1) { + dev_err_once(pwr->dev, "Bad SOC table values %u, %u\n", + soc_table[i - 1], soc_table[i]); + return -EINVAL; + } + dv = (ocv_table_load[i - 1] - ocv_table_load[i]) / soc_range; /* was hard coded 5 */ + for (j = 1; j < soc_range/* was 5 */; j++) { + if ((ocv_table_load[i] + dv * j) > + pwr->min_voltage) { + break; + } + } + + lost_cap = ((params - zero_soc_pos - i) * soc_range /* was 5 */ + + (j - 1)) * *effective_cap / 100; + dev_dbg(pwr->dev, "lost_cap-1 = %d\n", lost_cap); + + for (m = 0; m < soc_est_max_num; m++) { + new_lost_cap = lost_cap; + dsoc0 = CAP2DSOC(lost_cap, *effective_cap); + if ((dsoc >= 0 && dsoc0 > dsoc) || + (dsoc < 0 && dsoc0 < dsoc)) + dsoc0 = dsoc; + + dev_dbg(pwr->dev, "dsoc0(%d) = %d\n", m, dsoc0); + + vdr = bd71827_get_vdr(pwr, dsoc, temp); + vdr0 = bd71827_get_vdr(pwr, dsoc0, temp); + + for (k = 1; k < params; k++) { + if (!vdr) { + dev_err(pwr->dev, + "VDR zero-correction failed\n"); + break; + } + ocv_table_load[k] = ocv_table[k] - + (ocv - vbat) * vdr0 / vdr; + if (ocv_table_load[k] <= pwr->min_voltage) { + dev_dbg(pwr->dev, + "ocv_table_load[%d] = %d\n", k, + ocv_table_load[k]); + break; + } + } + if (k < params) { + dv = (ocv_table_load[k-1] - + ocv_table_load[k]) / 5; + for (j = 1; j < 5; j++) + if ((ocv_table_load[k] + dv * j) > + pwr->min_voltage) + break; + + new_lost_cap = ((params - zero_soc_pos - k) * + 5 + (j - 1)) * + *effective_cap / 100; + if (soc_est_max_num == 1) + lost_cap = new_lost_cap; + else + lost_cap += (new_lost_cap - lost_cap) / + (2 * (soc_est_max_num - m)); + dev_dbg(pwr->dev, "lost_cap-2(%d) = %d\n", m, + lost_cap); + } + if (new_lost_cap == lost_cap) + break; + } + + *effective_cap -= lost_cap; + } + + return 0; +} + +static int get_chg_online(struct bd71827_power *pwr, int *chg_online) +{ + int r, ret; + + ret = regmap_read(pwr->regmap, pwr->regs->dcin_stat, &r); + if (ret) { + dev_err(pwr->dev, "Failed to read DCIN status\n"); + return ret; + } + *chg_online = ((r & BD7182x_MASK_DCIN_DET) != 0); + + return 0; +} + +static int get_bat_online(struct bd71827_power *pwr, int *bat_online) +{ + int r, ret; + +#define BAT_OPEN 0x7 + ret = regmap_read(pwr->regmap, pwr->regs->bat_temp, &r); + if (ret) { + dev_err(pwr->dev, "Failed to read battery temperature\n"); + return ret; + } + *bat_online = ((r & BD7182x_MASK_BAT_TEMP) != BAT_OPEN); + + return 0; +} + +static int bd71828_bat_inserted(struct bd71827_power *pwr) +{ + int ret, val; + + ret = regmap_read(pwr->regmap, pwr->regs->conf, &val); + if (ret) { + dev_err(pwr->dev, "Failed to read CONF register\n"); + return 0; + } + ret = val & BD7182x_MASK_CONF_PON; + + if (ret) + regmap_update_bits(pwr->regmap, pwr->regs->conf, + BD7182x_MASK_CONF_PON, 0); + + return ret; +} + +static int bd71815_bat_inserted(struct bd71827_power *pwr) +{ + int ret, val; + + ret = regmap_read(pwr->regmap, pwr->regs->conf, &val); + if (ret) { + dev_err(pwr->dev, "Failed to read CONF register\n"); + return ret; + } + + ret = !(val & BD71815_MASK_CONF_XSTB); + if (ret) + regmap_write(pwr->regmap, pwr->regs->conf, val | + BD71815_MASK_CONF_XSTB); + + return ret; +} + +static int bd71827_init_hardware(struct bd71827_power *pwr) +{ + int ret; + + /* TODO: Collapse limit should come from device-tree ? */ + ret = regmap_write(pwr->regmap, pwr->regs->dcin_collapse_limit, + BD7182x_DCIN_COLLAPSE_DEFAULT); + if (ret) { + dev_err(pwr->dev, "Failed to write DCIN collapse limit\n"); + return ret; + } + + ret = pwr->bat_inserted(pwr); + if (ret < 0) + return ret; + + if (ret) { + int cc_val; + + /* Ensure Coulomb Counter is stopped */ + ret = stop_cc(pwr); + if (ret) + return ret; + + /* Set Coulomb Counter Reset bit*/ + ret = regmap_update_bits(pwr->regmap, pwr->regs->coulomb_ctrl, + BD7182x_MASK_CCNTRST, + BD7182x_MASK_CCNTRST); + if (ret) + return ret; + + /* Clear Coulomb Counter Reset bit*/ + ret = regmap_update_bits(pwr->regmap, pwr->regs->coulomb_ctrl, + BD7182x_MASK_CCNTRST, 0); + if (ret) + return ret; + + /* Set initial Coulomb Counter by HW OCV */ + calibration_coulomb_counter(pwr); + + /* WDT_FST auto set */ + ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_set1, + BD7182x_MASK_WDT_AUTO, + BD7182x_MASK_WDT_AUTO); + if (ret) + return ret; + + ret = bd7182x_write16(pwr, pwr->regs->vbat_alm_limit_u, + VBAT_LOW_TH); + if (ret) + return ret; + + /* Set monitor threshold to 9/10 of battery uAh capacity */ + cc_val = UAH_to_CC(pwr, pwr->battery_cap); + + /* + * On BD71815 "we mask the power-state" from relax detection. + * I am unsure what the impact of the power-state would be if + * we didn't - but this is what the vendor driver did - and + * that driver has been used in few projects so I just assume + * this is needed. + */ + if (pwr->chip_type == ROHM_CHIP_TYPE_BD71815) { + ret = regmap_set_bits(pwr->regmap, + BD71815_REG_REX_CTRL_1, + REX_PMU_STATE_MASK); + if (ret) + return ret; + } + + ret = bd7182x_write16(pwr, pwr->regs->batcap_mon_limit_u, + cc_val * 9 / 10); + if (ret) + return ret; + + dev_dbg(pwr->dev, "REG_CC_BATCAP1_TH = %d\n", + (cc_val * 9 / 10)); + } + + return start_cc(pwr); +} + +#define MK_2_100MCELSIUS(m_kevl_in) (((int)(m_kevl_in) - 273150) / 100) +static int get_vdr_from_dt(struct bd71827_power *pwr, + struct fwnode_handle *node, int temp_values) +{ + int i, ret, num_values, *tmp_table; + u32 vdr_kelvin[NUM_VDR_TEMPS]; + + if (temp_values != NUM_VDR_TEMPS) { + dev_err(pwr->dev, "Bad VDR temperature table size (%d). Expected %d", + temp_values, NUM_VDR_TEMPS); + return -EINVAL; + } + ret = fwnode_property_read_u32_array(node, + "rohm,volt-drop-temp-millikelvin", + &vdr_kelvin[0], NUM_VDR_TEMPS); + if (ret) { + dev_err(pwr->dev, "Invalid VDR temperatures in device-tree"); + return ret; + } + /* Convert millikelvin to .1 celsius as is expected by VDR algo */ + for (i = 0; i < NUM_VDR_TEMPS; i++) + vdr_temps[i] = MK_2_100MCELSIUS(vdr_kelvin[i]); + + num_values = fwnode_property_count_u32(node, "rohm,volt-drop-soc"); + if (num_values <= 0 || num_values > MAX_NUM_VDR_VALUES) { + dev_err(pwr->dev, "malformed voltage drop parameters\n"); + return -EINVAL; + } + g_num_vdr_params = num_values; + + tmp_table = kcalloc(num_values, sizeof(int), GFP_KERNEL); + if (!tmp_table) + return -ENOMEM; + /* + * We collect NUM_VDR_TEMPS + 1 tables, all temperature tables + + * the SOC table + */ + for (i = 0; i < NUM_VDR_TEMPS + 1; i++) { + int index; + static const char * const prop[] = { + /* + * SOC in units of 0.1 percent. TODO: Check if we have + * standard DT unit for percentage with higher accuracy + */ + "rohm,volt-drop-soc", + "rohm,volt-drop-high-temp-microvolt", + "rohm,volt-drop-normal-temp-microvolt", + "rohm,volt-drop-low-temp-microvolt", + "rohm,volt-drop-very-low-temp-microvolt", + }; + int *tables[5] = { + &soc_table[0], &vdr_table_h[0], &vdr_table_m[0], + &vdr_table_l[0], &vdr_table_vl[0] + }; + + if (num_values != fwnode_property_count_u32(node, prop[i])) { + dev_err(pwr->dev, + "%s: Bad size. Expected %d parameters", + prop[i], num_values); + ret = -EINVAL; + goto out; + } + ret = fwnode_property_read_u32_array(node, prop[i], tmp_table, + num_values); + if (ret) { + dev_err(pwr->dev, + "Invalid VDR temperatures in device-tree"); + goto out; + } + for (index = 0; index < num_values; index++) + tables[i][index] = tmp_table[index]; + } +out: + kfree(tmp_table); + + return ret; +} + + /* Set default parameters if no module parameters were given */ +static int bd71827_set_battery_parameters(struct bd71827_power *pwr) +{ + int i; + + /* + * We support getting battery parameters from static battery node + * or as module parameters. + */ + if (!use_load_bat_params) { + int ret, num_vdr; + struct fwnode_handle *node; + + /* + * power_supply_dev_get_battery_info uses devm internally + * so we do not need explicit remove() + */ + ret = power_supply_dev_get_battery_info(pwr->dev->parent, NULL, + &pwr->batinfo); + if (ret) { + dev_err(pwr->dev, "No battery information (%d)\n", ret); + return ret; + } + + if (!pwr->batinfo.ocv_table[0]) { + dev_err(pwr->dev, "No Open Circuit Voltages for battery\n"); + return -EINVAL; + } + + if (pwr->batinfo.charge_full_design_uah == -EINVAL) { + dev_err(pwr->dev, "Unknown battery capacity\n"); + return -EINVAL; + } + + if (pwr->batinfo.voltage_max_design_uv == -EINVAL) { + /* + * We could try digging this from OCV table but + * lets just bail-out for now + */ + dev_err(pwr->dev, "Unknown max voltage\n"); + return -EINVAL; + } + pwr->max_voltage = pwr->batinfo.voltage_max_design_uv; + + if (pwr->batinfo.voltage_min_design_uv == -EINVAL) { + /* We could try digging this from OCV table....? */ + dev_err(pwr->dev, "Unknown max voltage\n"); + return -EINVAL; + } + pwr->min_voltage = pwr->batinfo.voltage_min_design_uv; + /* + * Let's default the zero-correction limit to 10% + * voltage limit. TODO: See what would be the correct value + */ + pwr->battery_cap = pwr->batinfo.charge_full_design_uah; + pwr->gdesc.degrade_cycle_uah = pwr->batinfo.degrade_cycle_uah; + + soc_est_max_num = SOC_EST_MAX_NUM_DEFAULT; + + node = dev_fwnode(pwr->dev->parent); + if (!node) { + dev_err(pwr->dev, "no charger node\n"); + return -ENODEV; + } + node = fwnode_find_reference(node, "monitored-battery", 0); + if (IS_ERR(node)) { + dev_err(pwr->dev, "No battery node found\n"); + return PTR_ERR(node); + } + + num_vdr = fwnode_property_count_u32(node, "rohm,volt-drop-temp-millikelvin"); + if (num_vdr > 0) { + ret = get_vdr_from_dt(pwr, node, num_vdr); + if (ret) + return ret; + } else { + vdr_temps[VDR_TEMP_HIGH] = DGRD_TEMP_H_DEFAULT; + vdr_temps[VDR_TEMP_NORMAL] = DGRD_TEMP_M_DEFAULT; + vdr_temps[VDR_TEMP_LOW] = DGRD_TEMP_L_DEFAULT; + vdr_temps[VDR_TEMP_VERY_LOW] = DGRD_TEMP_VL_DEFAULT; + } + } else { + if (vdr_temps[VDR_TEMP_HIGH] == -EINVAL || + vdr_temps[VDR_TEMP_NORMAL] == -EINVAL || + vdr_temps[VDR_TEMP_LOW] == -EINVAL || + vdr_temps[VDR_TEMP_VERY_LOW] == -EINVAL) { + vdr_temps[VDR_TEMP_HIGH] = DGRD_TEMP_H_DEFAULT; + vdr_temps[VDR_TEMP_NORMAL] = DGRD_TEMP_M_DEFAULT; + vdr_temps[VDR_TEMP_LOW] = DGRD_TEMP_L_DEFAULT; + vdr_temps[VDR_TEMP_VERY_LOW] = DGRD_TEMP_VL_DEFAULT; + } + + pwr->min_voltage = param_max_voltage; + pwr->max_voltage = param_min_voltage; + pwr->low_thr_voltage = param_thr_voltage; + pwr->battery_cap = battery_cap_mah * 1000; + pwr->gdesc.degrade_cycle_uah = dgrd_cyc_cap; + } + /* SOC table is expected to be in descending order. */ + if (!soc_table[0]) + for (i = 0; i < NUM_BAT_PARAMS; i++) + soc_table[i] = soc_table_default[i]; + + if (!pwr->min_voltage || !pwr->max_voltage || !pwr->battery_cap) { + dev_err(pwr->dev, "Battery parameters missing\n"); + + return -EINVAL; + } + if (!pwr->low_thr_voltage) + pwr->low_thr_voltage = pwr->min_voltage + + (pwr->max_voltage - pwr->min_voltage) / 10; + return 0; +} + +static int bd71827_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bd71827_power *pwr = dev_get_drvdata(psy->dev.parent); + u32 vot; + uint16_t tmp; + int online; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = get_chg_online(pwr, &online); + if (!ret) + val->intval = online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bd7182x_read16_himask(pwr, pwr->regs->vdcin, + BD7182x_MASK_VDCIN_U, &tmp); + if (ret) + return ret; + + vot = tmp; + /* 5 milli volt steps */ + val->intval = 5000 * vot; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int bd71827_battery_get_property(struct simple_gauge *gauge, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bd71827_power *pwr = simple_gauge_get_drvdata(gauge); + int ret = 0; + int status, health, tmp, curr, curr_avg; + + if (psp == POWER_SUPPLY_PROP_STATUS || psp == POWER_SUPPLY_PROP_HEALTH + || psp == POWER_SUPPLY_PROP_CHARGE_TYPE) + ret = bd71827_charge_status(pwr, &status, &health); + else if (psp == POWER_SUPPLY_PROP_CURRENT_AVG || + psp == POWER_SUPPLY_PROP_CURRENT_NOW) + ret = bd71827_get_current_ds_adc(pwr, &curr, &curr_avg); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = health; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (status == POWER_SUPPLY_STATUS_CHARGING) + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + else + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_PRESENT: + ret = get_bat_online(pwr, &tmp); + if (!ret) + val->intval = tmp; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bd71827_get_vbat(pwr, &tmp); + val->intval = tmp; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = curr_avg; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = curr; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = pwr->max_voltage; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + val->intval = pwr->min_voltage; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = MAX_CURRENT_DEFAULT; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/** @brief ac properties */ +static enum power_supply_property bd71827_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static enum power_supply_property bd71827_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static ssize_t charging_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct bd71827_power *pwr = dev_get_drvdata(dev->parent); + ssize_t ret = 0; + unsigned long val; + + ret = kstrtoul(buf, 0, &val); + if (ret) + return ret; + + if (val != 0 && val != 1) { + dev_warn(dev, "use 0/1 to disable/enable charging\n"); + return -EINVAL; + } + + if (val) + ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en, + BD7182x_MASK_CHG_EN, + BD7182x_MASK_CHG_EN); + else + ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en, + BD7182x_MASK_CHG_EN, + 0); + if (ret) + return ret; + + return count; +} + +static ssize_t charging_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct bd71827_power *pwr = dev_get_drvdata(dev->parent); + int chg_en, chg_online, ret; + + ret = regmap_read(pwr->regmap, pwr->regs->chg_en, &chg_en); + if (ret) + return ret; + + ret = get_chg_online(pwr, &chg_online); + if (ret) + return ret; + + chg_en &= BD7182x_MASK_CHG_EN; + return sprintf(buf, "%x\n", chg_online && chg_en); +} + +static DEVICE_ATTR_RW(charging); + +static struct attribute *bd71827_sysfs_attributes[] = { + &dev_attr_charging.attr, NULL, +}; + +static const struct attribute_group bd71827_sysfs_attr_group = { + .attrs = bd71827_sysfs_attributes, +}; + +static const struct attribute_group *bd71827_sysfs_attr_groups[] = { + &bd71827_sysfs_attr_group, NULL, +}; + +/** @brief powers supplied by bd71827_ac */ +static char *bd71827_ac_supplied_to[] = { + BAT_NAME, +}; + +static const struct power_supply_desc bd71827_ac_desc = { + .name = AC_NAME, + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = bd71827_charger_props, + .num_properties = ARRAY_SIZE(bd71827_charger_props), + .get_property = bd71827_charger_get_property, +}; + +static const struct simple_gauge_psy gauge_psy_config = { + .psy_name = BAT_NAME, + .additional_props = bd71827_battery_props, + .num_additional_props = ARRAY_SIZE(bd71827_battery_props), + .get_custom_property = bd71827_battery_get_property, +}; +#ifdef PWRCTRL_HACK +/* + * This is not-so-pretty hack for allowing external code to call + * bd71827_chip_hibernate() without this power device-data + */ +static struct bd71827_power *hack; +static DEFINE_SPINLOCK(pwrlock); + +static struct bd71827_power *get_power(void) +{ + mutex_lock(&pwrlock); + if (!hack) { + mutex_unlock(&pwrlock); + return -ENOENT; + } + return hack; +} + +static void put_power(void) +{ + mutex_unlock(&pwrlock); +} + +static int set_power(struct bd71827_power *pwr) +{ + mutex_lock(&pwrlock); + hack = pwr; + mutex_unlock(&pwrlock); +} + +static void free_power(void) +{ + mutex_lock(&pwrlock); + hack = NULL; + mutex_unlock(&pwrlock); +} + +/* + * TODO: Find the corret way to do this + */ +void bd71827_chip_hibernate(void) +{ + struct bd71827_power *pwr = get_power(); + + if (IS_ERR(pwr)) { + pr_err("%s called before probe finished\n", __func__); + return PTR_ERR(pwr); + } + + /* programming sequence in EANAB-151 */ + regmap_update_bits(pwr->regmap, pwr->regs->pwrctrl, + pwr->regs->hibernate_mask, 0); + regmap_update_bits(pwr->regmap, pwr->regs->pwrctrl, + pwr->regs->hibernate_mask, + pwr->regs->hibernate_mask); + put_power(); +} +#endif + +#define RSENS_CURR 10000000000LLU + +#define BD_ISR_NAME(name) \ +bd7181x_##name##_isr + +#define BD_ISR_BAT(name, print, run_gauge) \ +static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \ +{ \ + struct bd71827_power *pwr = (struct bd71827_power *)data; \ + \ + if (run_gauge) \ + simple_gauge_run(pwr->sw); \ + dev_dbg(pwr->dev, "%s\n", print); \ + power_supply_changed(pwr->sw->psy); \ + \ + return IRQ_HANDLED; \ +} + +#define BD_ISR_AC(name, print, run_gauge) \ +static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \ +{ \ + struct bd71827_power *pwr = (struct bd71827_power *)data; \ + \ + if (run_gauge) \ + simple_gauge_run(pwr->sw); \ + power_supply_changed(pwr->ac); \ + dev_dbg(pwr->dev, "%s\n", print); \ + power_supply_changed(pwr->sw->psy); \ + \ + return IRQ_HANDLED; \ +} + +#define BD_ISR_DUMMY(name, print) \ +static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \ +{ \ + struct bd71827_power *pwr = (struct bd71827_power *)data; \ + \ + dev_dbg(pwr->dev, "%s\n", print); \ + \ + return IRQ_HANDLED; \ +} + +BD_ISR_BAT(chg_state_changed, "CHG state changed", true) +/* DCIN voltage changes */ +BD_ISR_AC(dcin_removed, "DCIN removed", true) +BD_ISR_AC(clps_out, "DCIN voltage back to normal", true) +BD_ISR_AC(clps_in, "DCIN voltage collapsed", false) +BD_ISR_AC(dcin_ovp_res, "DCIN voltage normal", true) +BD_ISR_AC(dcin_ovp_det, "DCIN OVER VOLTAGE", true) + +BD_ISR_DUMMY(dcin_mon_det, "DCIN voltage below threshold") +BD_ISR_DUMMY(dcin_mon_res, "DCIN voltage above threshold") + +BD_ISR_DUMMY(vsys_uv_res, "VSYS under-voltage cleared") +BD_ISR_DUMMY(vsys_uv_det, "VSYS under-voltage") +BD_ISR_DUMMY(vsys_low_res, "'VSYS low' cleared") +BD_ISR_DUMMY(vsys_low_det, "VSYS low") +BD_ISR_DUMMY(vsys_mon_res, "VSYS mon - resumed") +BD_ISR_DUMMY(vsys_mon_det, "VSYS mon - detected") +BD_ISR_BAT(chg_wdg_temp, "charger temperature watchdog triggered", true) +BD_ISR_BAT(chg_wdg, "charging watchdog triggered", true) +BD_ISR_BAT(bat_removed, "Battery removed", true) +BD_ISR_BAT(bat_det, "Battery detected", true) +/* TODO: Verify the meaning of these interrupts */ +BD_ISR_BAT(rechg_det, "Recharging", true) +BD_ISR_BAT(rechg_res, "Recharge ending", true) +BD_ISR_DUMMY(temp_transit, "Temperature transition") +BD_ISR_BAT(therm_rmv, "bd71815-therm-rmv", false) +BD_ISR_BAT(therm_det, "bd71815-therm-det", true) +BD_ISR_BAT(bat_dead, "bd71815-bat-dead", false) +BD_ISR_BAT(bat_short_res, "bd71815-bat-short-res", true) +BD_ISR_BAT(bat_short, "bd71815-bat-short-det", false) +BD_ISR_BAT(bat_low_res, "bd71815-bat-low-res", true) +BD_ISR_BAT(bat_low, "bd71815-bat-low-det", true) +BD_ISR_BAT(bat_ov_res, "bd71815-bat-over-res", true) +/* What should we do here? */ +BD_ISR_BAT(bat_ov, "bd71815-bat-over-det", false) +BD_ISR_BAT(bat_mon_res, "bd71815-bat-mon-res", true) +BD_ISR_BAT(bat_mon, "bd71815-bat-mon-det", true) +BD_ISR_BAT(bat_cc_mon, "bd71815-bat-cc-mon2", false) +BD_ISR_BAT(bat_oc1_res, "bd71815-bat-oc1-res", true) +BD_ISR_BAT(bat_oc1, "bd71815-bat-oc1-det", false) +BD_ISR_BAT(bat_oc2_res, "bd71815-bat-oc2-res", true) +BD_ISR_BAT(bat_oc2, "bd71815-bat-oc2-det", false) +BD_ISR_BAT(bat_oc3_res, "bd71815-bat-oc3-res", true) +BD_ISR_BAT(bat_oc3, "bd71815-bat-oc3-det", false) +BD_ISR_BAT(temp_bat_low_res, "bd71815-temp-bat-low-res", true) +BD_ISR_BAT(temp_bat_low, "bd71815-temp-bat-low-det", true) +BD_ISR_BAT(temp_bat_hi_res, "bd71815-temp-bat-hi-res", true) +BD_ISR_BAT(temp_bat_hi, "bd71815-temp-bat-hi-det", true) + +static irqreturn_t bd7182x_dcin_removed(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + simple_gauge_run(pwr->sw); + power_supply_changed(pwr->ac); + dev_dbg(pwr->dev, "DCIN removed\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd718x7_chg_done(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + /* + * Battery is likely to be FULL => run simple_gauge to initiate + * CC setting + */ + simple_gauge_run(pwr->sw); + + return IRQ_HANDLED; +} + +static irqreturn_t bd7182x_dcin_detected(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_dbg(pwr->dev, "DCIN inserted\n"); + power_supply_changed(pwr->ac); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_vbat_low_res(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_dbg(pwr->dev, "VBAT LOW Resumed\n"); + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_vbat_low_det(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_dbg(pwr->dev, "VBAT LOW Detected\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_bat_hi_det(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_warn(pwr->dev, "Overtemp Detected\n"); + power_supply_changed(pwr->sw->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_bat_hi_res(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_dbg(pwr->dev, "Overtemp Resumed\n"); + power_supply_changed(pwr->sw->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_bat_low_det(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_dbg(pwr->dev, "Lowtemp Detected\n"); + power_supply_changed(pwr->sw->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_bat_low_res(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_dbg(pwr->dev, "Lowtemp Resumed\n"); + power_supply_changed(pwr->sw->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_vf_det(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_dbg(pwr->dev, "VF Detected\n"); + power_supply_changed(pwr->sw->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_vf_res(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_dbg(pwr->dev, "VF Resumed\n"); + power_supply_changed(pwr->sw->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_vf125_det(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_dbg(pwr->dev, "VF125 Detected\n"); + power_supply_changed(pwr->sw->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_vf125_res(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_dbg(pwr->dev, "VF125 Resumed\n"); + power_supply_changed(pwr->sw->psy); + + return IRQ_HANDLED; +} + +struct bd7182x_irq_res { + const char *name; + irq_handler_t handler; +}; + +#define BDIRQ(na, hn) { .name = (na), .handler = (hn) } + +static int bd7182x_get_irqs(struct platform_device *pdev, + struct bd71827_power *pwr) +{ + int i, irq, ret; + static const struct bd7182x_irq_res bd71815_irqs[] = { + BDIRQ("bd71815-dcin-rmv", BD_ISR_NAME(dcin_removed)), + BDIRQ("bd71815-dcin-clps-out", BD_ISR_NAME(clps_out)), + BDIRQ("bd71815-dcin-clps-in", BD_ISR_NAME(clps_in)), + BDIRQ("bd71815-dcin-ovp-res", BD_ISR_NAME(dcin_ovp_res)), + BDIRQ("bd71815-dcin-ovp-det", BD_ISR_NAME(dcin_ovp_det)), + BDIRQ("bd71815-dcin-mon-res", BD_ISR_NAME(dcin_mon_res)), + BDIRQ("bd71815-dcin-mon-det", BD_ISR_NAME(dcin_mon_det)), + + BDIRQ("bd71815-vsys-uv-res", BD_ISR_NAME(vsys_uv_res)), + BDIRQ("bd71815-vsys-uv-det", BD_ISR_NAME(vsys_uv_det)), + BDIRQ("bd71815-vsys-low-res", BD_ISR_NAME(vsys_low_res)), + BDIRQ("bd71815-vsys-low-det", BD_ISR_NAME(vsys_low_det)), + BDIRQ("bd71815-vsys-mon-res", BD_ISR_NAME(vsys_mon_res)), + BDIRQ("bd71815-vsys-mon-det", BD_ISR_NAME(vsys_mon_det)), + BDIRQ("bd71815-chg-wdg-temp", BD_ISR_NAME(chg_wdg_temp)), + BDIRQ("bd71815-chg-wdg", BD_ISR_NAME(chg_wdg)), + BDIRQ("bd71815-rechg-det", BD_ISR_NAME(rechg_det)), + BDIRQ("bd71815-rechg-res", BD_ISR_NAME(rechg_res)), + BDIRQ("bd71815-ranged-temp-transit", BD_ISR_NAME(temp_transit)), + BDIRQ("bd71815-chg-state-change", BD_ISR_NAME(chg_state_changed)), + BDIRQ("bd71815-bat-temp-normal", bd71827_temp_bat_hi_res), + BDIRQ("bd71815-bat-temp-erange", bd71827_temp_bat_hi_det), + BDIRQ("bd71815-bat-rmv", BD_ISR_NAME(bat_removed)), + BDIRQ("bd71815-bat-det", BD_ISR_NAME(bat_det)), + + /* Add ISRs for these */ + BDIRQ("bd71815-therm-rmv", BD_ISR_NAME(therm_rmv)), + BDIRQ("bd71815-therm-det", BD_ISR_NAME(therm_det)), + BDIRQ("bd71815-bat-dead", BD_ISR_NAME(bat_dead)), + BDIRQ("bd71815-bat-short-res", BD_ISR_NAME(bat_short_res)), + BDIRQ("bd71815-bat-short-det", BD_ISR_NAME(bat_short)), + BDIRQ("bd71815-bat-low-res", BD_ISR_NAME(bat_low_res)), + BDIRQ("bd71815-bat-low-det", BD_ISR_NAME(bat_low)), + BDIRQ("bd71815-bat-over-res", BD_ISR_NAME(bat_ov_res)), + BDIRQ("bd71815-bat-over-det", BD_ISR_NAME(bat_ov)), + BDIRQ("bd71815-bat-mon-res", BD_ISR_NAME(bat_mon_res)), + BDIRQ("bd71815-bat-mon-det", BD_ISR_NAME(bat_mon)), + /* cc-mon 1 & 3 ? */ + BDIRQ("bd71815-bat-cc-mon2", BD_ISR_NAME(bat_cc_mon)), + BDIRQ("bd71815-bat-oc1-res", BD_ISR_NAME(bat_oc1_res)), + BDIRQ("bd71815-bat-oc1-det", BD_ISR_NAME(bat_oc1)), + BDIRQ("bd71815-bat-oc2-res", BD_ISR_NAME(bat_oc2_res)), + BDIRQ("bd71815-bat-oc2-det", BD_ISR_NAME(bat_oc2)), + BDIRQ("bd71815-bat-oc3-res", BD_ISR_NAME(bat_oc3_res)), + BDIRQ("bd71815-bat-oc3-det", BD_ISR_NAME(bat_oc3)), + BDIRQ("bd71815-temp-bat-low-res", BD_ISR_NAME(temp_bat_low_res)), + BDIRQ("bd71815-temp-bat-low-det", BD_ISR_NAME(temp_bat_low)), + BDIRQ("bd71815-temp-bat-hi-res", BD_ISR_NAME(temp_bat_hi_res)), + BDIRQ("bd71815-temp-bat-hi-det", BD_ISR_NAME(temp_bat_hi)), + /* + * TODO: add rest of the IRQs and re-check the handling. + * Check the bd71815-bat-cc-mon1, bd71815-bat-cc-mon3, + * bd71815-bat-low-res, bd71815-bat-low-det, + * bd71815-bat-hi-res, bd71815-bat-hi-det. + */ + }; + static const struct bd7182x_irq_res bd71828_irqs[] = { + BDIRQ("bd71828-chg-done", bd718x7_chg_done), + BDIRQ("bd71828-pwr-dcin-in", bd7182x_dcin_detected), + BDIRQ("bd71828-pwr-dcin-out", bd7182x_dcin_removed), + BDIRQ("bd71828-vbat-normal", bd71827_vbat_low_res), + BDIRQ("bd71828-vbat-low", bd71827_vbat_low_det), + BDIRQ("bd71828-btemp-hi", bd71827_temp_bat_hi_det), + BDIRQ("bd71828-btemp-cool", bd71827_temp_bat_hi_res), + BDIRQ("bd71828-btemp-lo", bd71827_temp_bat_low_det), + BDIRQ("bd71828-btemp-warm", bd71827_temp_bat_low_res), + BDIRQ("bd71828-temp-hi", bd71827_temp_vf_det), + BDIRQ("bd71828-temp-norm", bd71827_temp_vf_res), + BDIRQ("bd71828-temp-125-over", bd71827_temp_vf125_det), + BDIRQ("bd71828-temp-125-under", bd71827_temp_vf125_res), + }; + int num_irqs; + const struct bd7182x_irq_res *irqs; + + + switch (pwr->chip_type) { + case ROHM_CHIP_TYPE_BD71827: + case ROHM_CHIP_TYPE_BD71828: + irqs = &bd71828_irqs[0]; + num_irqs = ARRAY_SIZE(bd71828_irqs); + break; + case ROHM_CHIP_TYPE_BD71815: + irqs = &bd71815_irqs[0]; + num_irqs = ARRAY_SIZE(bd71815_irqs); + break; + default: + return -EINVAL; + } + + for (i = 0; i < num_irqs; i++) { + irq = platform_get_irq_byname(pdev, irqs[i].name); + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + irqs[i].handler, 0, + irqs[i].name, pwr); + if (ret) + break; + } + + return ret; +} + +#define RSENS_DEFAULT_30MOHM 30000000 + +static int bd7182x_get_rsens(struct bd71827_power *pwr) +{ + u64 tmp = RSENS_CURR; + int rsens_ohm = RSENS_DEFAULT_30MOHM; + struct fwnode_handle *node = NULL; + + if (pwr->dev->parent) + node = dev_fwnode(pwr->dev->parent); + + if (node) { + int ret; + uint32_t rs; + + ret = fwnode_property_read_u32(node, + "rohm,charger-sense-resistor-ohms", + &rs); + if (ret) { + if (ret == -EINVAL) { + rs = RSENS_DEFAULT_30MOHM; + } else { + dev_err(pwr->dev, "Bad RSENS dt property\n"); + return ret; + } + } + if (!rs) { + dev_err(pwr->dev, "Bad RSENS value\n"); + return -EINVAL; + } + + rsens_ohm = (int)rs; + } + + /* Reg val to uA */ + do_div(tmp, rsens_ohm); + + pwr->curr_factor = tmp; + pwr->rsens = rsens_ohm; + dev_dbg(pwr->dev, "Setting rsens to %u ohm\n", pwr->rsens); + dev_dbg(pwr->dev, "Setting curr-factor to %u\n", pwr->curr_factor); + return 0; +} + +/* + * BD71827 has no proper support for detecting relaxed battery. + * Driver has implemented current polling and logic has been that: + * if for the specified time current consumption has always been below + * threshold value when polled - then battery is assumed to be relaxed. This + * for sure leads to a problem when current cunsumption has had short + * 'spikes' - but this is what the logic has been - and it has probably been + * working as the driver is left as is? So let's just keep this logic here. + */ +static bool bd71827_is_relaxed(struct simple_gauge *sw, int *rex_volt) +{ + struct bd71827_power *pwr = simple_gauge_get_drvdata(sw); + int tmp_curr_mA, ret, curr, curr_avg; + + ret = bd71827_get_current_ds_adc(pwr, &curr, &curr_avg); + if (ret) { + dev_err(pwr->dev, "Failed to get current\n"); + return false; + } + + tmp_curr_mA = uAMP_TO_mAMP(curr); + if ((tmp_curr_mA * tmp_curr_mA) <= + (THR_RELAX_CURRENT_DEFAULT * THR_RELAX_CURRENT_DEFAULT)) + /* No load */ + pwr->relax_time = pwr->relax_time + (JITTER_DEFAULT / 1000); + else + pwr->relax_time = 0; + if (!(pwr->relax_time >= THR_RELAX_TIME_DEFAULT)) + return false; + + ret = bd71827_get_voltage(sw, rex_volt); + if (ret) { + dev_err(pwr->dev, "Failed to get Vbat\n"); + return false; + } + + return true; +} + +static bool bd71828_is_relaxed(struct simple_gauge *sw, int *rex_volt) +{ + int ret; + u16 tmp; + struct bd71827_power *pwr = simple_gauge_get_drvdata(sw); + + ret = bd7182x_read16_himask(pwr, pwr->regs->vbat_rex_avg, + BD7182x_MASK_VBAT_U, &tmp); + if (ret) { + dev_err(pwr->dev, + "Failed to read battery relax voltage\n"); + return 0; + } + *rex_volt = tmp * 1000; + + return !!tmp; +} + +static int bd71828_get_cycle(struct simple_gauge *sw, int *cycle) +{ + int tmpret, ret, update = 0; + uint16_t charged_coulomb_cnt; + int cc_designed_cap; + struct bd71827_power *pwr = simple_gauge_get_drvdata(sw); + + ret = bd7182x_read16_himask(pwr, pwr->regs->coulomb_chg3, 0xff, + &charged_coulomb_cnt); + if (ret) { + dev_err(pwr->dev, "Failed to read charging CC (%d)\n", ret); + return ret; + } + dev_dbg(pwr->dev, "charged_coulomb_cnt = 0x%x\n", + (int)charged_coulomb_cnt); + + cc_designed_cap = UAH_to_CC(pwr, sw->designed_cap); + + while (charged_coulomb_cnt >= cc_designed_cap) { + update = 1; + /* + * sw-gauge caches old cycle value so we do not need to care + * about it. We just add new cycles + */ + *cycle = *cycle + 1; + dev_dbg(pwr->dev, "Update cycle = %d\n", *cycle); + charged_coulomb_cnt -= cc_designed_cap; + } + if (update) { + ret = stop_cc(pwr); + if (ret) + return ret; + + ret = bd7182x_write16(pwr, pwr->regs->coulomb_chg3, + charged_coulomb_cnt); + if (ret) { + dev_err(pwr->dev, "Failed to update charging CC (%d)\n", + ret); + } + + tmpret = start_cc(pwr); + if (tmpret) + return tmpret; + } + return ret; +} + +static void fgauge_initial_values(struct bd71827_power *pwr) +{ + struct simple_gauge_desc *d = &pwr->gdesc; + struct simple_gauge_ops *o = &pwr->ops; + bool use_vdr = false; + + /* TODO: See if these could be get from DT? */ + d->poll_interval = JITTER_DEFAULT; /* 3 seconds */ + d->allow_set_cycle = true; + d->cap_adjust_volt_threshold = pwr->low_thr_voltage; + d->designed_cap = pwr->battery_cap; + d->clamp_soc = true; + + o->get_uah_from_full = bd71828_get_uah_from_full; /* Ok */ + o->get_uah = bd71828_get_uah; /* Ok */ + o->update_cc_uah = bd71828_set_uah; /* Ok */ + o->get_cycle = bd71828_get_cycle; /* Ok */ + o->get_vsys = bd71827_get_vsys_min; + + /* + * We have custom OCV table => provide our own volt_to_cap and + * ocv_by_soc which utilize the custom tables. + */ + if (ocv_table[0]) { + dev_dbg(pwr->dev, "OCV values given as parameters\n"); + o->get_soc_by_ocv = &bd71827_voltage_to_capacity; + o->get_ocv_by_soc = &bd71827_get_ocv; + } + + if (vdr_table_h[0] && vdr_table_m[0] && vdr_table_l[0] && + vdr_table_vl[0]) + use_vdr = true; + + /* TODO: + * o->suspend_calibrate = bd71828_suspend_calibrate; + */ + switch (pwr->chip_type) { + case ROHM_CHIP_TYPE_BD71828: + o->get_temp = bd71828_get_temp; + o->is_relaxed = bd71828_is_relaxed; + if (use_vdr) + o->zero_cap_adjust = bd71828_zero_correct; + break; + case ROHM_CHIP_TYPE_BD71827: + o->get_temp = bd71827_get_temp; + o->is_relaxed = bd71827_is_relaxed; + if (use_vdr) + o->zero_cap_adjust = bd71828_zero_correct; + break; + case ROHM_CHIP_TYPE_BD71815: + o->get_temp = bd71827_get_temp; + o->is_relaxed = bd71828_is_relaxed; + /* + * TODO: BD71815 has not been used with VDR. This is untested + * but I don't see why it wouldn't work by setting thresholds + * and by populating correct SOC-OCV tables. + */ + if (use_vdr) + o->zero_cap_adjust = bd71828_zero_correct; + break; + /* + * No need to handle default here as this is done already in probe. + * But this keeps gcc shut-up. + */ + default: + break; + } +} + +static int bd71827_power_probe(struct platform_device *pdev) +{ + struct bd71827_power *pwr; + struct power_supply_config ac_cfg = {}; + struct simple_gauge_psy psycfg; + int ret; + struct regmap *regmap; + + psycfg = gauge_psy_config; + + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!regmap) { + dev_err(&pdev->dev, "No parent regmap\n"); + return -EINVAL; + } + + pwr = devm_kzalloc(&pdev->dev, sizeof(*pwr), GFP_KERNEL); + if (!pwr) + return -ENOMEM; + + pwr->regmap = regmap; + pwr->dev = &pdev->dev; + pwr->chip_type = platform_get_device_id(pdev)->driver_data; + + switch (pwr->chip_type) { + case ROHM_CHIP_TYPE_BD71828: + pwr->bat_inserted = bd71828_bat_inserted; + pwr->regs = &pwr_regs_bd71828; + dev_dbg(pwr->dev, "Found ROHM BD71828\n"); + psycfg.psy_name = "bd71828-charger"; + break; + case ROHM_CHIP_TYPE_BD71827: + pwr->bat_inserted = bd71828_bat_inserted; + pwr->regs = &pwr_regs_bd71827; + dev_dbg(pwr->dev, "Found ROHM BD71817\n"); + psycfg.psy_name = "bd71827-charger"; + break; + case ROHM_CHIP_TYPE_BD71815: + pwr->bat_inserted = bd71815_bat_inserted; + pwr->regs = &pwr_regs_bd71815; + psycfg.psy_name = "bd71815-charger"; + dev_dbg(pwr->dev, "Found ROHM BD71815\n"); + break; + default: + dev_err(pwr->dev, "Unknown PMIC\n"); + return -EINVAL; + } + + /* We need to set batcap etc before we do set fgauge initial values */ + ret = bd71827_set_battery_parameters(pwr); + if (ret) { + dev_err(pwr->dev, "Missing battery parameters\n"); + + return ret; + } + fgauge_initial_values(pwr); + + pwr->gdesc.drv_data = pwr; + + ret = bd7182x_get_rsens(pwr); + if (ret) { + dev_err(&pdev->dev, "sense resistor missing\n"); + return ret; + } + + dev_set_drvdata(&pdev->dev, pwr); + bd71827_init_hardware(pwr); + + psycfg.attr_grp = &bd71827_sysfs_attr_groups[0]; + psycfg.of_node = pdev->dev.parent->of_node; + + ac_cfg.supplied_to = bd71827_ac_supplied_to; + ac_cfg.num_supplicants = ARRAY_SIZE(bd71827_ac_supplied_to); + ac_cfg.drv_data = pwr; + + pwr->ac = devm_power_supply_register(&pdev->dev, &bd71827_ac_desc, + &ac_cfg); + if (IS_ERR(pwr->ac)) { + ret = PTR_ERR(pwr->ac); + dev_err(&pdev->dev, "failed to register ac: %d\n", ret); + return ret; + } + + pwr->sw = devm_psy_register_simple_gauge(pwr->dev, &psycfg, &pwr->ops, + &pwr->gdesc); + if (IS_ERR(pwr->sw)) { + dev_err(&pdev->dev, "SW-gauge registration failed\n"); + return PTR_ERR(pwr->sw); + } + + ret = bd7182x_get_irqs(pdev, pwr); + if (ret) { + dev_err(&pdev->dev, "failed to request IRQs: %d\n", ret); + return ret; + }; + + /* Configure wakeup capable */ + device_set_wakeup_capable(pwr->dev, 1); + device_set_wakeup_enable(pwr->dev, 1); + + return 0; +} + +static const struct platform_device_id bd71827_charger_id[] = { + { "bd71815-power", ROHM_CHIP_TYPE_BD71815 }, + { "bd71827-power", ROHM_CHIP_TYPE_BD71827 }, + { "bd71828-power", ROHM_CHIP_TYPE_BD71828 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, bd71827_charger_id); + +static struct platform_driver bd71827_power_driver = { + .driver = { + .name = "bd718xx-power", + }, + .probe = bd71827_power_probe, + .id_table = bd71827_charger_id, +}; + +module_platform_driver(bd71827_power_driver); +MODULE_ALIAS("platform:bd718xx-power"); + +module_param(use_load_bat_params, int, 0444); +MODULE_PARM_DESC(use_load_bat_params, "use_load_bat_params:Use loading battery parameters"); + +module_param(param_max_voltage, int, 0444); +MODULE_PARM_DESC(param_max_voltage, + "Maximum voltage of fully charged battery, uV"); + +module_param(param_min_voltage, int, 0444); +MODULE_PARM_DESC(param_min_voltage, + "Minimum voltage of fully drained battery, uV"); + +module_param(param_thr_voltage, int, 0444); +MODULE_PARM_DESC(param_thr_voltage, + "Threshold voltage for applying zero correction, uV"); + +module_param(battery_cap_mah, int, 0444); +MODULE_PARM_DESC(battery_cap_mah, "battery_cap_mah:Battery capacity (mAh)"); + +module_param(dgrd_cyc_cap, int, 0444); +MODULE_PARM_DESC(dgrd_cyc_cap, "dgrd_cyc_cap:Degraded capacity per cycle (uAh)"); + +module_param(soc_est_max_num, int, 0444); +MODULE_PARM_DESC(soc_est_max_num, "soc_est_max_num:SOC estimation max repeat number"); + +module_param_array(ocv_table, int, NULL, 0444); +MODULE_PARM_DESC(ocv_table, "ocv_table:Open Circuit Voltage table (uV)"); + +module_param_array(vdr_temps, int, NULL, 0444); +MODULE_PARM_DESC(vdr_temps, "vdr_temps:temperatures for VDR tables. (0.1C)"); + +module_param_array(vdr_table_h, int, NULL, 0444); +MODULE_PARM_DESC(vdr_table_h, "vdr_table_h:Voltage Drop Ratio temperature high area table"); + +module_param_array(vdr_table_m, int, NULL, 0444); +MODULE_PARM_DESC(vdr_table_m, "vdr_table_m:Voltage Drop Ratio temperature middle area table"); + +module_param_array(vdr_table_l, int, NULL, 0444); +MODULE_PARM_DESC(vdr_table_l, "vdr_table_l:Voltage Drop Ratio temperature low area table"); + +module_param_array(vdr_table_vl, int, NULL, 0444); +MODULE_PARM_DESC(vdr_table_vl, "vdr_table_vl:Voltage Drop Ratio temperature very low area table"); + +MODULE_AUTHOR("Cong Pham "); +MODULE_DESCRIPTION("ROHM BD718(15/17/27/28/78) PMIC Battery Charger driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/rohm-bd71827.h b/include/linux/mfd/rohm-bd71827.h new file mode 100644 index 000000000000..0f5a343b10ae --- /dev/null +++ b/include/linux/mfd/rohm-bd71827.h @@ -0,0 +1,295 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2016 + * + * @author cpham2403@gmail.com + */ + +#ifndef __LINUX_MFD_BD71827_H +#define __LINUX_MFD_BD71827_H + +#include + +#define LDO5VSEL_EQ_H 0 + +#ifndef LDO5VSEL_EQ_H + #error define LDO5VSEL_EQ_H to 1 when connect to High, to 0 when connect to Low +#else + #if LDO5VSEL_EQ_H == 1 + #define BD71827_REG_LDO5_VOLT BD71827_REG_LDO5_VOLT_H + #define LDO5_MASK LDO5_H_MASK + #elif LDO5VSEL_EQ_H == 0 + #define BD71827_REG_LDO5_VOLT BD71827_REG_LDO5_VOLT_L + #define LDO5_MASK LDO5_L_MASK + #else + #error Define LDO5VSEL_EQ_H only to 0 or 1 + #endif +#endif + +enum { + BD71827_BUCK1 = 0, + BD71827_BUCK2, + BD71827_BUCK3, + BD71827_BUCK4, + BD71827_BUCK5, + // General Purpose + BD71827_LDO1, + BD71827_LDO2, + BD71827_LDO3, + BD71827_LDO4, + BD71827_LDO5, + BD71827_LDO6, + // LDO for Secure Non-Volatile Storage + BD71827_LDOSNVS, + BD71827_REGULATOR_CNT, +}; + +#define BD71827_SUPPLY_STATE_ENABLED 0x1 + +#define BD71827_BUCK1_VOLTAGE_NUM 0x3F +#define BD71827_BUCK2_VOLTAGE_NUM 0x3F +#define BD71827_BUCK3_VOLTAGE_NUM 0x3F +#define BD71827_BUCK4_VOLTAGE_NUM 0x1F +#define BD71827_BUCK5_VOLTAGE_NUM 0x1F +#define BD71827_LDO1_VOLTAGE_NUM 0x3F +#define BD71827_LDO2_VOLTAGE_NUM 0x3F +#define BD71827_LDO3_VOLTAGE_NUM 0x3F +#define BD71827_LDO4_VOLTAGE_NUM 0x3F +#define BD71827_LDO5_VOLTAGE_NUM 0x3F +#define BD71827_LDO6_VOLTAGE_NUM 0x1 +#define BD71827_LDOSNVS_VOLTAGE_NUM 0x1 + +#define BD71827_GPIO_NUM 2 /* BD71827 have 2 GPO */ + +enum { + BD71827_REG_DEVICE = 0x00, + BD71827_REG_PWRCTRL = 0x01, + BD71827_REG_BUCK1_MODE = 0x02, + BD71827_REG_BUCK2_MODE = 0x03, + BD71827_REG_BUCK3_MODE = 0x04, + BD71827_REG_BUCK4_MODE = 0x05, + BD71827_REG_BUCK5_MODE = 0x06, + BD71827_REG_BUCK1_VOLT_RUN = 0x07, + BD71827_REG_BUCK1_VOLT_SUSP = 0x08, + BD71827_REG_BUCK2_VOLT_RUN = 0x09, + BD71827_REG_BUCK2_VOLT_SUSP = 0x0A, + BD71827_REG_BUCK3_VOLT = 0x0B, + BD71827_REG_BUCK4_VOLT = 0x0C, + BD71827_REG_BUCK5_VOLT = 0x0D, + BD71827_REG_LED_CTRL = 0x0E, + BD71827_REG_reserved_0F = 0x0F, + BD71827_REG_LDO_MODE1 = 0x10, + BD71827_REG_LDO_MODE2 = 0x11, + BD71827_REG_LDO_MODE3 = 0x12, + BD71827_REG_LDO_MODE4 = 0x13, + BD71827_REG_LDO1_VOLT = 0x14, + BD71827_REG_LDO2_VOLT = 0x15, + BD71827_REG_LDO3_VOLT = 0x16, + BD71827_REG_LDO4_VOLT = 0x17, + BD71827_REG_LDO5_VOLT_H = 0x18, + BD71827_REG_LDO5_VOLT_L = 0x19, + BD71827_REG_BUCK_PD_DIS = 0x1A, + BD71827_REG_LDO_PD_DIS = 0x1B, + BD71827_REG_GPIO = 0x1C, + BD71827_REG_OUT32K = 0x1D, + BD71827_REG_SEC = 0x1E, + BD71827_REG_MIN = 0x1F, + BD71827_REG_HOUR = 0x20, + BD71827_REG_WEEK = 0x21, + BD71827_REG_DAY = 0x22, + BD71827_REG_MONTH = 0x23, + BD71827_REG_YEAR = 0x24, + BD71827_REG_ALM0_SEC = 0x25, + BD71827_REG_ALM0_MIN = 0x26, + BD71827_REG_ALM0_HOUR = 0x27, + BD71827_REG_ALM0_WEEK = 0x28, + BD71827_REG_ALM0_DAY = 0x29, + BD71827_REG_ALM0_MONTH = 0x2A, + BD71827_REG_ALM0_YEAR = 0x2B, + BD71827_REG_ALM1_SEC = 0x2C, + BD71827_REG_ALM1_MIN = 0x2D, + BD71827_REG_ALM1_HOUR = 0x2E, + BD71827_REG_ALM1_WEEK = 0x2F, + BD71827_REG_ALM1_DAY = 0x30, + BD71827_REG_ALM1_MONTH = 0x31, + BD71827_REG_ALM1_YEAR = 0x32, + BD71827_REG_ALM0_MASK = 0x33, + BD71827_REG_ALM1_MASK = 0x34, + BD71827_REG_ALM2 = 0x35, + BD71827_REG_TRIM = 0x36, + BD71827_REG_CONF = 0x37, + BD71827_REG_SYS_INIT = 0x38, + BD71827_REG_CHG_STATE = 0x39, + BD71827_REG_CHG_LAST_STATE = 0x3A, + BD71827_REG_BAT_STAT = 0x3B, + BD71827_REG_DCIN_STAT = 0x3C, + BD71827_REG_VSYS_STAT = 0x3D, + BD71827_REG_CHG_STAT = 0x3E, + BD71827_REG_CHG_WDT_STAT = 0x3F, + BD71827_REG_BAT_TEMP = 0x40, + BD71827_REG_ILIM_STAT = 0x41, + BD71827_REG_DCIN_SET = 0x42, + BD71827_REG_DCIN_CLPS = 0x43, + BD71827_REG_VSYS_REG = 0x44, + BD71827_REG_VSYS_MAX = 0x45, + BD71827_REG_VSYS_MIN = 0x46, + BD71827_REG_CHG_SET1 = 0x47, + BD71827_REG_CHG_SET2 = 0x48, + BD71827_REG_CHG_WDT_PRE = 0x49, + BD71827_REG_CHG_WDT_FST = 0x4A, + BD71827_REG_CHG_IPRE = 0x4B, + BD71827_REG_CHG_IFST = 0x4C, + BD71827_REG_CHG_IFST_TERM = 0x4D, + BD71827_REG_CHG_VPRE = 0x4E, + BD71827_REG_CHG_VBAT_1 = 0x4F, + BD71827_REG_CHG_VBAT_2 = 0x50, + BD71827_REG_CHG_VBAT_3 = 0x51, + BD71827_REG_CHG_LED_1 = 0x52, + BD71827_REG_VF_TH = 0x53, + BD71827_REG_BAT_SET_1 = 0x54, + BD71827_REG_BAT_SET_2 = 0x55, + BD71827_REG_BAT_SET_3 = 0x56, + BD71827_REG_ALM_VBAT_TH_U = 0x57, + BD71827_REG_ALM_VBAT_TH_L = 0x58, + BD71827_REG_ALM_DCIN_TH = 0x59, + BD71827_REG_ALM_VSYS_TH = 0x5A, + BD71827_REG_reserved_5B = 0x5B, + BD71827_REG_reserved_5C = 0x5C, + BD71827_REG_VM_VBAT_U = 0x5D, + BD71827_REG_VM_VBAT_L = 0x5E, + BD71827_REG_VM_BTMP = 0x5F, + BD71827_REG_VM_VTH = 0x60, + BD71827_REG_VM_DCIN_U = 0x61, + BD71827_REG_VM_DCIN_L = 0x62, + BD71827_REG_reserved_63 = 0x63, + BD71827_REG_VM_VF = 0x64, + BD71827_REG_reserved_65 = 0x65, + BD71827_REG_reserved_66 = 0x66, + BD71827_REG_VM_OCV_PRE_U = 0x67, + BD71827_REG_VM_OCV_PRE_L = 0x68, + BD71827_REG_reserved_69 = 0x69, + BD71827_REG_reserved_6A = 0x6A, + BD71827_REG_VM_OCV_PST_U = 0x6B, + BD71827_REG_VM_OCV_PST_L = 0x6C, + BD71827_REG_VM_SA_VBAT_U = 0x6D, + BD71827_REG_VM_SA_VBAT_L = 0x6E, + BD71827_REG_reserved_6F = 0x6F, + BD71827_REG_reserved_70 = 0x70, + BD71827_REG_CC_CTRL = 0x71, + BD71827_REG_CC_BATCAP1_TH_U = 0x72, + BD71827_REG_CC_BATCAP1_TH_L = 0x73, + BD71827_REG_CC_BATCAP2_TH_U = 0x74, + BD71827_REG_CC_BATCAP2_TH_L = 0x75, + BD71827_REG_CC_BATCAP3_TH_U = 0x76, + BD71827_REG_CC_BATCAP3_TH_L = 0x77, + BD71827_REG_CC_STAT = 0x78, + BD71827_REG_CC_CCNTD_3 = 0x79, + BD71827_REG_CC_CCNTD_2 = 0x7A, + BD71827_REG_CC_CCNTD_1 = 0x7B, + BD71827_REG_CC_CCNTD_0 = 0x7C, + BD71827_REG_CC_CURCD_U = 0x7D, + BD71827_REG_CC_CURCD_L = 0x7E, + BD71827_REG_CC_OCUR_THR_1 = 0x7F, + BD71827_REG_CC_OCUR_DUR_1 = 0x80, + BD71827_REG_CC_OCUR_THR_2 = 0x81, + BD71827_REG_CC_OCUR_DUR_2 = 0x82, + BD71827_REG_CC_OCUR_THR_3 = 0x83, + BD71827_REG_CC_OCUR_DUR_3 = 0x84, + BD71827_REG_CC_OCUR_MON = 0x85, + BD71827_REG_VM_BTMP_OV_THR = 0x86, + BD71827_REG_VM_BTMP_OV_DUR = 0x87, + BD71827_REG_VM_BTMP_LO_THR = 0x88, + BD71827_REG_VM_BTMP_LO_DUR = 0x89, + BD71827_REG_VM_BTMP_MON = 0x8A, + BD71827_REG_INT_EN_01 = 0x8B, + BD71827_REG_INT_EN_02 = 0x8C, + BD71827_REG_INT_EN_03 = 0x8D, + BD71827_REG_INT_EN_04 = 0x8E, + BD71827_REG_INT_EN_05 = 0x8F, + BD71827_REG_INT_EN_06 = 0x90, + BD71827_REG_INT_EN_07 = 0x91, + BD71827_REG_INT_EN_08 = 0x92, + BD71827_REG_INT_EN_09 = 0x93, + BD71827_REG_INT_EN_10 = 0x94, + BD71827_REG_INT_EN_11 = 0x95, + BD71827_REG_INT_EN_12 = 0x96, + BD71827_REG_INT_STAT = 0x97, + BD71827_REG_INT_STAT_01 = 0x98, + BD71827_REG_INT_STAT_02 = 0x99, + BD71827_REG_INT_STAT_03 = 0x9A, + BD71827_REG_INT_STAT_04 = 0x9B, + BD71827_REG_INT_STAT_05 = 0x9C, + BD71827_REG_INT_STAT_06 = 0x9D, + BD71827_REG_INT_STAT_07 = 0x9E, + BD71827_REG_INT_STAT_08 = 0x9F, + BD71827_REG_INT_STAT_09 = 0xA0, + BD71827_REG_INT_STAT_10 = 0xA1, + BD71827_REG_INT_STAT_11 = 0xA2, + BD71827_REG_INT_STAT_12 = 0xA3, + BD71827_REG_INT_UPDATE = 0xA4, + BD71827_REG_PWRCTRL2 = 0xA8, + BD71827_REG_PWRCTRL3 = 0xA9, + BD71827_REG_SWRESET = 0xAA, + BD71827_REG_BUCK1_VOLT_IDLE = 0xAB, + BD71827_REG_BUCK2_VOLT_IDLE = 0xAC, + BD71827_REG_ONEVNT_MODE_1 = 0xAD, + BD71827_REG_ONEVNT_MODE_2 = 0xAE, + BD71827_REG_RESERVE_0 = 0xB0, + BD71827_REG_RESERVE_1 = 0xB1, + BD71827_REG_RESERVE_2 = 0xB2, + BD71827_REG_RESERVE_3 = 0xB3, + BD71827_REG_RESERVE_4 = 0xB4, + BD71827_REG_RESERVE_5 = 0xB5, + BD71827_REG_RESERVE_6 = 0xB6, + BD71827_REG_RESERVE_7 = 0xB7, + BD71827_REG_RESERVE_8 = 0xB8, + BD71827_REG_RESERVE_9 = 0xB9, + BD71827_REG_RESERVE_A = 0xBA, + BD71827_REG_RESERVE_B = 0xBB, + BD71827_REG_RESERVE_C = 0xBC, + BD71827_REG_RESERVE_D = 0xBD, + BD71827_REG_RESERVE_E = 0xBE, + BD71827_REG_RESERVE_F = 0xBF, + BD71827_REG_VM_VSYS_U = 0xC0, + BD71827_REG_VM_VSYS_L = 0xC1, + BD71827_REG_VM_SA_VSYS_U = 0xC2, + BD71827_REG_VM_SA_VSYS_L = 0xC3, + BD71827_REG_CC_SA_CURCD_U = 0xC4, + BD71827_REG_CC_SA_CURCD_L = 0xC5, + BD71827_REG_BATID = 0xC6, + BD71827_REG_VM_SA_VBAT_MIN_U = 0xD4, + BD71827_REG_VM_SA_VBAT_MIN_L = 0xD5, + BD71827_REG_VM_SA_VBAT_MAX_U = 0xD6, + BD71827_REG_VM_SA_VBAT_MAX_L = 0xD7, + BD71827_REG_VM_SA_VSYS_MIN_U = 0xD8, + BD71827_REG_VM_SA_VSYS_MIN_L = 0xD9, + BD71827_REG_VM_SA_VSYS_MAX_U = 0xDA, + BD71827_REG_VM_SA_VSYS_MAX_L = 0xDB, + BD71827_REG_VM_SA_MINMAX_CLR = 0xDC, + BD71827_REG_VM_OCV_PWRON_U = 0xDD, + BD71827_REG_VM_OCV_PWRON_L = 0xDE, + BD71827_REG_REX_CCNTD_3 = 0xE0, + BD71827_REG_REX_CCNTD_2 = 0xE1, + BD71827_REG_REX_CCNTD_1 = 0xE2, + BD71827_REG_REX_CCNTD_0 = 0xE3, + BD71827_REG_REX_SA_VBAT_U = 0xE4, + BD71827_REG_REX_SA_VBAT_L = 0xE5, + BD71827_REG_REX_CTRL_1 = 0xE6, + BD71827_REG_REX_CTRL_2 = 0xE7, + BD71827_REG_FULL_CCNTD_3 = 0xE8, + BD71827_REG_FULL_CCNTD_2 = 0xE9, + BD71827_REG_FULL_CCNTD_1 = 0xEA, + BD71827_REG_FULL_CCNTD_0 = 0xEB, + BD71827_REG_FULL_CTRL = 0xEC, + BD71827_REG_CCNTD_CHG_3 = 0xF0, + BD71827_REG_CCNTD_CHG_2 = 0xF1, + BD71827_REG_INT_EN_13 = 0xF8, + BD71827_REG_INT_STAT_13 = 0xF9, + BD71827_REG_I2C_MAGIC = 0xFE, + BD71827_REG_PRODUCT = 0xFF, + BD71827_MAX_REGISTER = 0x100, +}; + +#define BD71827_REX_CLR_MASK 0x10 + +#endif /* __LINUX_MFD_BD71827_H */