From patchwork Fri Dec 4 12:41:09 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 11951685 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5602AC433FE for ; Fri, 4 Dec 2020 12:42:15 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 01C2422581 for ; Fri, 4 Dec 2020 12:42:14 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726090AbgLDMl7 (ORCPT ); Fri, 4 Dec 2020 07:41:59 -0500 Received: from mail-lj1-f196.google.com ([209.85.208.196]:41561 "EHLO mail-lj1-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725867AbgLDMl6 (ORCPT ); Fri, 4 Dec 2020 07:41:58 -0500 Received: by mail-lj1-f196.google.com with SMTP id y7so6431241lji.8; Fri, 04 Dec 2020 04:41:42 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=RXUzzoRMZ6PIXmVqYH2yp1o3qr6JX3BOhrhwT1JN5o8=; b=qllRvMQZl4XxvPgztHiP3PWoakMVKgMTbZ9QHlol6irEVTAzWbtEbxGzDRWnsewwnI kA+Q8H4avWtHgd7aju97QAalTEQmD5Ai5A0FlXdb3WrbN7xWy7W6HAD2sMhG3LMvBiON ypFtpZ6reSYCOU/X7iT3h6CEpbdlFL16eWYeHQhYTp4Ic5EJShbYkDb6ttESbggxXveV uAXfd2ewBvfJGyjQtSv9Kko7K/dx5BxilwGfLqQpsjB4FamMsC31cC9gVTQoZb2hwaTE W9ACzBjJdJuQ0Uw3A+FGzaMlcXv2N5ZjVSsmgjSN/khGXPNrjnXLz8SKGlOxrc7i+K0J 9jYg== X-Gm-Message-State: AOAM5320OFnxlT4IC0o/yz2QgVJbGoo44L96VswWcVSGy4NiUTFulHYn jGY9qK8Oggzo3s1qYkuQoVE= X-Google-Smtp-Source: ABdhPJzFwvB1CMOSO9J5xEMuaHzJLMbptmORJJvS7fEVnli7qvT1eW9rSg/rBQNH0DK8oPFRudpGwQ== X-Received: by 2002:a05:651c:1067:: with SMTP id y7mr3462768ljm.357.1607085676368; Fri, 04 Dec 2020 04:41:16 -0800 (PST) Received: from localhost.localdomain (62-78-225-252.bb.dnainternet.fi. [62.78.225.252]) by smtp.gmail.com with ESMTPSA id i6sm99310ljn.16.2020.12.04.04.41.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Dec 2020 04:41:15 -0800 (PST) Date: Fri, 4 Dec 2020 14:41:09 +0200 From: Matti Vaittinen To: matti.vaittinen@fi.rohmeurope.com, mazziesaccount@gmail.com Cc: Sebastian Reichel , Linus Walleij , Krzysztof Kozlowski , Dmitry Osipenko , Cong Pham , rostokus@gmail.com, fan.chen@mediatek.com, linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org Subject: [RFC PATCH v2 1/6] power: supply: add cap2ocv batinfo helper 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 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 --- No changes from RFC v1 - (this should be changed to support at least 0.1% SOC accuracy - will rework for next version if this is continued) drivers/power/supply/power_supply_core.c | 51 ++++++++++++++++++++++++ include/linux/power_supply.h | 5 +++ 2 files changed, 56 insertions(+) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 38e3aa642131..67258799ae2e 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -787,6 +787,43 @@ int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *t } EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple); +/** + * power_supply_cap2ocv_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 + * + * This helper function is 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. + */ +int power_supply_cap2ocv_simple(struct power_supply_battery_ocv_table *table, + int table_len, int cap) +{ + int i, ocv, tmp; + + for (i = 0; i < table_len; i++) + if (cap > table[i].capacity) + break; + + if (i > 0 && i < table_len) { + tmp = (table[i - 1].ocv - table[i].ocv) * + (cap - table[i].capacity); + + tmp /= table[i - 1].capacity - table[i].capacity; + 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_cap2ocv_simple); + /** * power_supply_ocv2cap_simple() - find the battery capacity * @table: Pointer to battery OCV lookup table @@ -847,6 +884,20 @@ power_supply_find_ocv2cap_table(struct power_supply_battery_info *info, } EXPORT_SYMBOL_GPL(power_supply_find_ocv2cap_table); +int power_supply_batinfo_cap2ocv(struct power_supply_battery_info *info, + int cap, 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_cap2ocv_simple(table, table_len, cap); +} +EXPORT_SYMBOL_GPL(power_supply_batinfo_cap2ocv); + 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 81a55e974feb..bae98b628f92 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -403,11 +403,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_cap2ocv_simple(struct power_supply_battery_ocv_table *table, + int table_len, int cap); + 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_cap2ocv(struct power_supply_battery_info *info, + int cap, int temp); extern int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table, int table_len, int temp); From patchwork Fri Dec 4 12:43:54 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 11951687 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0AC6AC4361A for ; Fri, 4 Dec 2020 12:45:04 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B166322AAD for ; Fri, 4 Dec 2020 12:45:03 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388152AbgLDMor (ORCPT ); Fri, 4 Dec 2020 07:44:47 -0500 Received: from mail-lj1-f194.google.com ([209.85.208.194]:45612 "EHLO mail-lj1-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388138AbgLDMoq (ORCPT ); Fri, 4 Dec 2020 07:44:46 -0500 Received: by mail-lj1-f194.google.com with SMTP id q8so6419705ljc.12; Fri, 04 Dec 2020 04:44:27 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=mkgmWk3F1v7jvcNTkPmTfW+4Fl09IVALpwH/3HkstQE=; b=hCd/Ezch2SbH8hr445m1IuAZZa/wnCvXHE7D2deQiVzUns+F+WBU8Ra3gapBKPvRWD NyoFo/ST18BKCr3SiAB8udZLZevG0oC9fi/DrrwoNtSQbAOeV7nixSpBRpoEsJqAgKS/ kjO9sD8WTKzfomwW2BEbQ3idASBuI6I860HcYLU+hzx/s+d73Gx4Dg94FpSdcB+zPOxy rLVg7v5M4uc3h6T5AmCynEtnBU0aTM+YGAi7PenkPqMckDFqTj6ITM10SwPuMLyInjfu rhCHJth0kDlDLEgw7BjulU85xSlo/1nnl7LNIDyemGEw/UR3vV2K6UOuthuRTjpK79vW Xe5A== X-Gm-Message-State: AOAM53198r49f2c54Un6chowNF/DWjphj+BNEAWp4DA/EnDqnQZaaVxD 8h0zeqAAIRTS3Qus+fJUuEk= X-Google-Smtp-Source: ABdhPJymLPPwX00BA4yhq1fiPoYwNcCo8b/J4QnxAyNB6be76yV1nMSA0rUpW9tMlU2WsWlCcDNOow== X-Received: by 2002:a2e:5705:: with SMTP id l5mr3367571ljb.93.1607085841055; Fri, 04 Dec 2020 04:44:01 -0800 (PST) Received: from localhost.localdomain (62-78-225-252.bb.dnainternet.fi. [62.78.225.252]) by smtp.gmail.com with ESMTPSA id a30sm1654837ljd.91.2020.12.04.04.43.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Dec 2020 04:44:00 -0800 (PST) Date: Fri, 4 Dec 2020 14:43:54 +0200 From: Matti Vaittinen To: matti.vaittinen@fi.rohmeurope.com, mazziesaccount@gmail.com Cc: Sebastian Reichel , Linus Walleij , Krzysztof Kozlowski , Dmitry Osipenko , Cong Pham , rostokus@gmail.com, fan.chen@mediatek.com, linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org Subject: [RFC PATCH v2 2/6] power: supply: add sw-gauge for SOC estimation and CC correction Message-ID: <639bd97975a7420f6357bdc6161e1fc427fca79d.1607085199.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 'sw 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 'sw-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). The SW 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. - provide 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 reset. Should POWER_SUPPLY_PROP_CYCLE_COUNT be used? - periodical wake-up for performing SOC estimation computation (RTC integration) Signed-off-by: Matti Vaittinen --- Here we have quite some fixes from v1 - but still this is just sent to collect opinions and suggestions (well, collaboration) regarding the basic concept. No acceptance or accurate review is requested. drivers/power/supply/Kconfig | 8 + drivers/power/supply/Makefile | 1 + drivers/power/supply/power_supply_swgauge.c | 1025 +++++++++++++++++++ include/linux/power/sw_gauge.h | 225 ++++ include/linux/power_supply.h | 6 + 5 files changed, 1265 insertions(+) create mode 100644 drivers/power/supply/power_supply_swgauge.c create mode 100644 include/linux/power/sw_gauge.h diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index eec646c568b7..d78231f3caf7 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -9,6 +9,14 @@ menuconfig POWER_SUPPLY if POWER_SUPPLY +config SW_GAUGE + bool "SW gauge" + help + Say Y here to enable kernel fuel-gauge which can be used to + compute the remaining state of charge in a battery. The SW gauge + can compensate battery aging and adjust coulomb counter on ICs + based on some battery/charger specific data. + config POWER_SUPPLY_DEBUG bool "Power supply debug" help diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index dd4b86318cd9..f3d9472ee8cb 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_SW_GAUGE) += power_supply_swgauge.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/power_supply_swgauge.c b/drivers/power/supply/power_supply_swgauge.c new file mode 100644 index 000000000000..0c30f5516774 --- /dev/null +++ b/drivers/power/supply/power_supply_swgauge.c @@ -0,0 +1,1025 @@ +// 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/sw_gauge.h struct sw_gauge_ops. Drivers can also specify + * time interval the IC is polled. + * + * After registration the sw_gauge 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). + * - 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 should provide 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 reset. (not yet implemnted - will work on it + * next if this RFC is not completely frowned upon - Oh, should userspace just + * use POWER_SUPPLY_PROP_CYCLE_COUNT for this? I only now noticed that :)) + * + * Some very low-power devices prefer periodical wake-up for performing SOC + * estimation computation even at the cost of power-consumption caused by + * such 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?) + * + * If this is not seen as a complete waste of time - then I would like to get + * suggestions and opinions :) Especially for following: + * 1. Is this kind of generic entity needed or should this just be left to be + * implemented in each individual driver? + * 2. 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 sw_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. + */ + +DEFINE_MUTEX(sw_gauge_lock); +DEFINE_MUTEX(sw_gauge_start_lock); +LIST_HEAD(sw_gauges); + +static int g_running; +static struct task_struct k; + +static int swgauge_set_cycle(struct sw_gauge *sw, int new_cycle) +{ + int ret, 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 sw_gauge_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + if (!psy->sw_gauge) + return -EBUSY; + + if (psp == POWER_SUPPLY_PROP_CYCLE_COUNT) + return swgauge_set_cycle(psy->sw_gauge, val->intval); + + return psy->sw_gauge->orig_set_property(psy, psp, val); +} + +static int sw_gauge_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sw_gauge *sw = psy->sw_gauge; + /* + * The sw-gauge initialization has race. We should set the sw_gauge + * to psy before psy-ops are registered. Consider melding SW gauge in + * psy-core. For now we just return -EBUSY if the sw_gauge was not yet + * set. + * + * Another thing 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? + */ + if (!sw) + return -EBUSY; + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + spin_lock(&sw->lock); + val->intval = sw->soc; + spin_unlock(&sw->lock); + return 0; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + spin_lock(&sw->lock); + val->intval = sw->cycle; + spin_unlock(&sw->lock); + return 0; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + /* uAh */ + val->intval = sw->designed_cap; + return 0; + case POWER_SUPPLY_PROP_CHARGE_FULL: + spin_lock(&sw->lock); + val->intval = sw->capacity_uah; + spin_unlock(&sw->lock); + return 0; + case POWER_SUPPLY_PROP_CHARGE_NOW: + spin_lock(&sw->lock); + val->intval = sw->cc_uah; + spin_unlock(&sw->lock); + return 0; + case POWER_SUPPLY_PROP_TEMP: + /* TODO: Cache last obtained temperature + * Units(?) Check if we have 0.1 degrees C + */ + spin_lock(&sw->lock); + val->intval = sw->temp; + spin_unlock(&sw->lock); + return 0; + default: + break; + } + + return psy->sw_gauge->orig_get_property(psy, psp, val); +} + +static void gauge_put(struct sw_gauge *sw) +{ + sw->refcount = 0; + /* Is barrier needed? Do we need to protect the acces with lock + * anyways? + * I don't see use-case where refcount would need to be increased beyond + * 1 - if that happened we should switch to atomic_inc/dec - which + * probably provides the barriers inside. + */ + smp_wmb(); + if (!sw->refcount) + wake_up(&sw->wq); +} + +static void gauge_get(struct sw_gauge *sw) +{ + sw->refcount = 1; + /* Is barrier needed? Ensure the refcount does not stay cached */ + smp_wmb(); +} + +static int gauge_reserved(struct sw_gauge *sw) +{ + /* Is barrier needed? Ensure the refcount is updated */ + smp_rmb(); + return sw->refcount; +} + +static int get_soc_from_ocv(struct sw_gauge *sw, int *soc, 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) { + *soc = ret; + 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, soc); + } + return ret; +} + +static int sw_gauge_get_temp(struct sw_gauge *sw, int *temp) +{ + if (sw->ops.get_temp) + return sw->ops.get_temp(sw, temp); + + return -EINVAL; +} + +static int age_correct_cap(struct sw_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 sw_gauge *sw, int rex_volt) +{ + int ret, temp, soc; + int full_uah = sw->designed_cap; + int uah_now; + + /* get temp */ + ret = sw_gauge_get_temp(sw, &temp); + if (ret) + return ret; + + /* get ocv */ + ret = get_soc_from_ocv(sw, &soc, 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 * soc / 100 + 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 sw_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 sw_gauge *sw) +{ + int ret, from_full_uah = 0; + int full_uah = sw->designed_cap; + + if (sw->ops.get_uah_from_full) + ret = sw->ops.get_uah_from_full(sw, &from_full_uah); + + 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 sw_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 sw_gauge_cap2ocv(struct sw_gauge *sw, int dsoc, int temp, int *ocv) +{ + int ret, soc; + + if (sw->ops.get_ocv_by_soc) + return sw->ops.get_ocv_by_soc(sw, dsoc, temp, ocv); + + /* + * Ouch.. I'm afraid this kind of kills the accuracy. + * The cap2OCV should accept soc at least as 0.1% + */ + soc = dsoc / 10; + ret = power_supply_batinfo_cap2ocv(&sw->info, soc, temp / 10); + if (ret > 0) { + *ocv = ret; + ret = 0; + } + + return ret; +} + +static int load_based_soc_zero_adjust(struct sw_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 = sw_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. + * + * TODO: Should we support non DT originated OCV table also here? Or + * should the driver just provide whole low-voltage call-back in that + * case? + */ + 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 sw_gauge_zero_cap_adjust(struct sw_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 compute_temp_correct_uah(struct sw_gauge *sw, int *cap_uah, int temp) +{ + int i, temp_diff, uah_corr; + + for (i = 0; i < sw->desc->amount_of_temp_dgr && + sw->desc->temp_dgr[i].temp_floor > temp; i++) + ; + + if (i == sw->desc->amount_of_temp_dgr) { + i -= 1; + dev_warn(sw->dev, + "Temperature below min %d, using range %d->\n", temp, + sw->desc->temp_dgr[i].temp_floor); + } + + temp_diff = sw->desc->temp_dgr[i].temp_floor - temp; + /* + * Temperaure range is in tenths of degrees and degrade value is for a + * degree => divide by 10 after multiplication to fix the scale + */ + uah_corr = temp_diff * sw->desc->temp_dgr[i].temp_degrade_1C / 10; + if (*cap_uah < -uah_corr) + *cap_uah = 0; + else + *cap_uah += uah_corr; + + return 0; +} + +static int compute_soc_by_cc(struct sw_gauge *sw, int state) +{ + int cc_uah, ret; + int current_cap_uah; + int temp; + int new_soc; + bool do_zero_correct; + + 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; + + 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; + spin_unlock(&sw->lock); + return 0; + } + + /* Do battery temperature compensation */ + ret = sw_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->desc->amount_of_temp_dgr) + ret = compute_temp_correct_uah(sw, ¤t_cap_uah, temp); + else + ret = -EINVAL; + + 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 = sw_gauge_zero_cap_adjust(sw, ¤t_cap_uah, + &cc_uah, vsys, temp); + if (ret) + dev_warn(sw->dev, "Low voltage adjustment failed\n"); + } + + if (cc_uah > sw->designed_cap) + cc_uah = sw->designed_cap; + + /* 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); + if (sw->soc != new_soc) + /* + * Should we ping user-space about the change? + * Should we ping user-space when SOC changes more than N%? + * Should the N be configurable by user-space? + */ + ; + + sw->soc = new_soc; + if (state & SW_GAUGE_CLAMP_SOC) { + if (sw->clamped_soc < sw->soc) + sw->soc = sw->clamped_soc; + } + sw->clamped_soc = sw->soc; + spin_unlock(&sw->lock); + return ret; +} + +static void calibrate(struct sw_gauge *sw) +{ + if (sw->ops.calibrate) + sw->ops.calibrate(sw); +} + +static void iterate(struct sw_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 sw_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 sw_gauge *sw, u64 time) +{ + if (sw->next_iter <= time + msecs_to_jiffies(SWGAUGE_TIMEOUT_JITTER)) { + sw->next_iter = time + + msecs_to_jiffies(sw->desc->poll_interval); + return true; + } + + return false; +} + +static void adjust_next_tmo(struct sw_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 int gauge_thread(void *data) +{ + for (;;) { + u64 timeout = 0; + struct sw_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; + } + + mutex_lock(&sw_gauge_lock); + list_for_each_entry(sw, &sw_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); + } + mutex_unlock(&sw_gauge_lock); + /* + * We should change this to wait_event_interruptible because + * when new swgauge is registered we must wake-up and perform + * the first iteration ASAP. First iteration needs to be done + * before we have SOC for user-space so registration needs to + * kick us here + */ + pr_debug("sleeping %u msec\n", jiffies_to_msecs(timeout)); + msleep(jiffies_to_msecs(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(&sw_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(&sw_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) +{ + kthread_stop(k); +} + +static bool is_needed_ops_given(struct sw_gauge_ops *ops) +{ + return (ops->get_uah && ops->get_temp && ops->update_cc_uah); +} + +static int sw_gauge_set_ops(struct sw_gauge *sw, struct sw_gauge_ops *ops) +{ + if (!is_needed_ops_given(ops)) + return -EINVAL; + + sw->ops = *ops; + + return 0; +} + +struct sw_gauge *__must_check psy_register_sw_gauge(struct device *parent, + struct sw_gauge_psy *psycfg, + struct sw_gauge_ops *ops, + struct sw_gauge_desc *desc) +{ + struct power_supply_desc *pd; + int ret; + struct sw_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); + } + + /* + * I don't like this. I mean, we should either get the psy desc from + * driver - or fill the desc here based on information from driver. + * + * This 'get info from driver but override it here' approach does not + * feel correct. Open to suggestions :) + */ + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) + return ERR_PTR(-ENOMEM); + + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!new) { + ret = -ENOMEM; + goto free_pd_out; + } + init_waitqueue_head(&new->wq); + + new->dev = parent; + + ret = sw_gauge_set_ops(new, ops); + if (ret) { + dev_err(new->dev, "bad ops\n"); + goto free_out; + } + new->desc = desc; + + /* + * Besides - we add our own property getting stuff so we should edit + * the available properties. Should we have constant set of properties + * always handled by swgauge (SOC, TEMP, ... ? ) - or should we allow + * different drivers to specify which properties swgauge handles - + * or should we silently handle properties which we can based on + * callbacks provided by driver? + */ + *pd = *psycfg->pdesc; + new->orig_get_property = pd->get_property; + new->orig_set_property = pd->set_property; + spin_lock_init(&new->lock); + pd->get_property = sw_gauge_get_property; + pd->set_property = sw_gauge_set_property; + + /* Do we need power_supply_register_ws? */ + /* + * Here we have a race. psy->swgauge is not set yet. Should we set it + * in power_supply_register? + * + * Also, 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, psycfg->pcfg); + if (IS_ERR(new->psy)) { + dev_err(new->dev, "power supply registration failed\n"); + ret = PTR_ERR(new->psy); + goto free_out; + } + new->psy->sw_gauge = new; + + 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 (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(&sw_gauge_lock); + list_add(&new->node, &sw_gauges); + mutex_unlock(&sw_gauge_lock); + ret = start_gauge_thread(&k); + if (ret) { + /* + * This error is not related to underlying device but to the + * swgauge 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"); +return new; + +info_out: + if (new->batinfo_got) + power_supply_put_battery_info(new->psy, &new->info); + + power_supply_unregister(new->psy); +free_out: + kfree(new); +free_pd_out: + kfree(pd); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(psy_register_sw_gauge); + +void psy_remove_sw_gauge(struct sw_gauge *sw) +{ + struct power_supply *psy = sw->psy; + + if (sw->batinfo_got) + power_supply_put_battery_info(sw->psy, &sw->info); + power_supply_unregister(sw->psy); + mutex_lock(&sw_gauge_lock); + if (sw) + list_del(&sw->node); + mutex_unlock(&sw_gauge_lock); + + psy->sw_gauge = NULL; + + wait_event(sw->wq, !gauge_reserved(sw)); + kfree(sw); +} +EXPORT_SYMBOL_GPL(psy_remove_sw_gauge); + +static void devm_sw_gauge_release(struct device *dev, void *res) +{ + struct sw_gauge **sw = res; + + psy_remove_sw_gauge(*sw); +} + +struct sw_gauge *__must_check +devm_psy_register_sw_gauge(struct device *parent, struct sw_gauge_psy *psycfg, + struct sw_gauge_ops *ops, struct sw_gauge_desc *desc) +{ + struct sw_gauge **ptr, *sw; + + ptr = devres_alloc(devm_sw_gauge_release, sizeof(*ptr), GFP_KERNEL); + + if (!ptr) + return ERR_PTR(-ENOMEM); + sw = psy_register_sw_gauge(parent, psycfg, ops, desc); + if (IS_ERR(sw)) { + devres_free(ptr); + } else { + *ptr = sw; + devres_add(parent, ptr); + } + return sw; +} +EXPORT_SYMBOL_GPL(devm_psy_register_sw_gauge); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("generic fuel-gauge on coulomb counter"); +MODULE_AUTHOR("Matti Vaittinen "); diff --git a/include/linux/power/sw_gauge.h b/include/linux/power/sw_gauge.h new file mode 100644 index 000000000000..a72af1efb584 --- /dev/null +++ b/include/linux/power/sw_gauge.h @@ -0,0 +1,225 @@ +/* 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) + +/** + * struct sw_gauge_temp_degr - linear 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) + * 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_floor: Lowest temperature where change is valid (unit 0.1 degree C) + * @temp_degrade_1C: Capacity change / temperature change (uAh / degree C) + */ +struct sw_gauge_temp_degr { + int temp_floor; + int temp_degrade_1C; +}; + +struct sw_gauge; + +/** + * struct sw_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. + * @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 sw_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 sw_gauge *gauge, int *rex_volt); + int (*get_temp)(struct sw_gauge *gauge, int *temp); + int (*get_uah_from_full)(struct sw_gauge *gauge, int *uah); + int (*get_uah)(struct sw_gauge *gauge, int *uah); + int (*update_cc_uah)(struct sw_gauge *gauge, int bcap); + int (*get_cycle)(struct sw_gauge *gauge, int *cycle); + int (*set_cycle)(struct sw_gauge *gauge, int old, int *new_cycle); + int (*get_vsys)(struct sw_gauge *gauge, int *uv); + int (*get_soc_by_ocv)(struct sw_gauge *sw, int ocv, int temp, int *soc); + int (*get_ocv_by_soc)(struct sw_gauge *sw, int soc, int temp, int *ocv); + int (*age_correct_cap)(struct sw_gauge *gauge, int cycle, int *cap); + int (*temp_correct_cap)(struct sw_gauge *gauge, int *cap, int temp); + int (*calibrate)(struct sw_gauge *sw); + int (*suspend_calibrate)(struct sw_gauge *sw, bool start); + int (*zero_cap_adjust)(struct sw_gauge *sw, int *effective_cap, + int cc_uah, int vbat, int temp); +}; + +/** + * struct sw_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 sw_gauge framework must be described by the + * sw_gauge_desc prior reistration to the sw_gauge framework. + * + * @name: Identifying name for gauge + * @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 sw_gauge_desc { + const char *name; + int degrade_cycle_uah; + int amount_of_temp_dgr; + struct sw_gauge_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; +}; + +/** + * struct sw_gauge_psy - power supply configuration + * + * configuration being further passed to power-supply registration. + */ +struct sw_gauge_psy { + const struct power_supply_desc *pdesc; + const struct power_supply_config *pcfg; +}; + +/** + * struct sw_gauge - sw_gauge runtime data + * + * Internal to sw-gauge. Should not be directly accessed/modified by drivers + */ +struct sw_gauge { + struct device *dev; + int designed_cap; /* This should be available for drivers */ + struct sw_gauge_desc *desc; + int cycle; + u64 next_iter; /* Time of next iteration in jiffies64 */ + u64 next_cal; /* Time of next calibration in jiffies64 */ + int refcount; + struct power_supply *psy; + int (*orig_get_property)(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); + int (*orig_set_property)(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val); + struct power_supply_battery_info info; + struct sw_gauge_ops ops; + struct list_head node; + 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 sw_gauge *__must_check psy_register_sw_gauge(struct device *parent, + struct sw_gauge_psy *psycfg, + struct sw_gauge_ops *ops, + struct sw_gauge_desc *desc); +void psy_remove_sw_gauge(struct sw_gauge *sw); + +struct sw_gauge *__must_check +devm_psy_register_sw_gauge(struct device *parent, struct sw_gauge_psy *psycfg, + struct sw_gauge_ops *ops, + struct sw_gauge_desc *desc); + +#endif /* POWER_SW_GAUGE_H */ diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index bae98b628f92..d80b5e1ba9a2 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -213,6 +213,9 @@ union power_supply_propval { struct device_node; struct power_supply; +#ifdef CONFIG_SW_GAUGE + struct sw_gauge; +#endif /* Run-time specific power supply configuration */ struct power_supply_config { @@ -309,6 +312,9 @@ struct power_supply { struct led_trigger *charging_blink_full_solid_trig; char *charging_blink_full_solid_trig_name; #endif +#ifdef CONFIG_SW_GAUGE + struct sw_gauge *sw_gauge; +#endif }; /* From patchwork Fri Dec 4 12:47:34 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 11951689 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1B048C433FE for ; Fri, 4 Dec 2020 12:48:46 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id C042622519 for ; Fri, 4 Dec 2020 12:48:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727385AbgLDMsa (ORCPT ); Fri, 4 Dec 2020 07:48:30 -0500 Received: from mail-lj1-f195.google.com ([209.85.208.195]:45825 "EHLO mail-lj1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726988AbgLDMs3 (ORCPT ); Fri, 4 Dec 2020 07:48:29 -0500 Received: by mail-lj1-f195.google.com with SMTP id q8so6432093ljc.12; Fri, 04 Dec 2020 04:48:06 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=voM8CIdlVjogp2cZAMUdQ7vlFQs6bZFYlhKRFXAOqlQ=; b=VXG/YO2GEkIBWSNzxq9vvC8g9zscmfEQ2hQwZUQsdG0afEwNVuoGRzxL4v/bqSzjX6 zRDKY42dCs+03X83TqyBleaTr4yVFulUyKuySQiiI932zgE2TR2AZz7fb7FrIP1l2LWT ZI6e868j+Q9vSQ1ehOJeRQRRz5Pf0ug4DSkyUzNMG93n9nV90Xm14JQM1sX5+nC11usv vJZpGinTxohPnupWIi3Jiml3fPKQWolQ7N+AqvFgrls5aYeq9CVXr9EKTFHacOKkoYTq lsdiG4b+EbHIHHYjQ32p9boX3ECk5SbRaxk4dCFmj7TCerTkroQ7OdnGRHurxAg78ATr 3Umg== X-Gm-Message-State: AOAM5323C+KX1a+NaVIfbN4g/zL/pk9A4Nmli+9Uu3AiMn9gujoQMADa 6SLaFLPYz/HHe3NCnRnt0ts= X-Google-Smtp-Source: ABdhPJy1XOH4L6tEIzyYtRblGbiMDgqDFMY3POHBaiCdobtelzoAtH2S0rx52L9BlMl2VI0QzTbtQg== X-Received: by 2002:a2e:a40e:: with SMTP id p14mr1007400ljn.63.1607086061254; Fri, 04 Dec 2020 04:47:41 -0800 (PST) Received: from localhost.localdomain (62-78-225-252.bb.dnainternet.fi. [62.78.225.252]) by smtp.gmail.com with ESMTPSA id f2sm1714164ljn.39.2020.12.04.04.47.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Dec 2020 04:47:40 -0800 (PST) Date: Fri, 4 Dec 2020 14:47:34 +0200 From: Matti Vaittinen To: matti.vaittinen@fi.rohmeurope.com, mazziesaccount@gmail.com Cc: Sebastian Reichel , Linus Walleij , Krzysztof Kozlowski , Dmitry Osipenko , Cong Pham , rostokus@gmail.com, fan.chen@mediatek.com, linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org Subject: [RFC PATCH v2 3/6] mfd: prepare to support BD718xx-charger 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 definitions for ROHM BD718(27/28/78) PMIC's charger blocks. Signed-off-by: Matti Vaittinen --- This patch was not in v1. This brings in some charger registers for the BD71828 charger driver which is in following patches. Patch split here reflects the subsystem change (for non RFC submitted for inclusion this would probably be Lee's territory). Now provided in this RFC series to give more context include/linux/mfd/rohm-bd71828.h | 65 ++++++++++++++++++++++++++++++++ include/linux/mfd/rohm-generic.h | 2 + 2 files changed, 67 insertions(+) diff --git a/include/linux/mfd/rohm-bd71828.h b/include/linux/mfd/rohm-bd71828.h index 017a4c01cb31..1a6a7804d28b 100644 --- a/include/linux/mfd/rohm-bd71828.h +++ b/include/linux/mfd/rohm-bd71828.h @@ -185,6 +185,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 4283b5b33e04..48af41d22d3f 100644 --- a/include/linux/mfd/rohm-generic.h +++ b/include/linux/mfd/rohm-generic.h @@ -12,6 +12,8 @@ enum rohm_chip_type { ROHM_CHIP_TYPE_BD71847, ROHM_CHIP_TYPE_BD70528, ROHM_CHIP_TYPE_BD71828, + ROHM_CHIP_TYPE_BD71827, + ROHM_CHIP_TYPE_BD71878, ROHM_CHIP_TYPE_AMOUNT }; From patchwork Fri Dec 4 12:49:06 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 11951691 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-10.9 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,UNWANTED_LANGUAGE_BODY, UPPERCASE_50_75,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9E880C433FE for ; Fri, 4 Dec 2020 12:49:59 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 45DDC22AAD for ; Fri, 4 Dec 2020 12:49:59 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728774AbgLDMt6 (ORCPT ); Fri, 4 Dec 2020 07:49:58 -0500 Received: from mail-lf1-f68.google.com ([209.85.167.68]:35590 "EHLO mail-lf1-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728668AbgLDMt6 (ORCPT ); Fri, 4 Dec 2020 07:49:58 -0500 Received: by mail-lf1-f68.google.com with SMTP id a9so7487987lfh.2; Fri, 04 Dec 2020 04:49:39 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=zVlqo6ADj5cxf32YkxOPbBveWmRWbJ3wYYzdPfgDlxo=; b=ZEJXPgNqES9Oeu5r6ueR1phDToEjTPPGExkRPRTUaErR2PA0ND02hi7FyUX0qdNX4w s1RyetGUiqWBjUPc364hyMyxiXKmrcZ83ZGaY2yKVQ8fPx8j3IuQCiTGCFfuyRK33PSy 8a2KBch+RvUlrOpzZiV7b9VhPstSQ73SpBH+w7ATvaP3JrCYuQ6m3eiCZpWJXVwSlhvQ /Z7mDu7qvNqPw3/Y6pBvUd0GnbuWygqchsNyAH1TJnJhzN26/oge6TlzIp3h83vaye6u nIHBiqvwzJjEO0rJT4kIoNoIyOKJ48pYMXaH01DQ6QbgzhImxhqVSskAceGx9nw6aE9f MAVg== X-Gm-Message-State: AOAM532HKFHZE6rL76zOKhfzSQxfk7oAbGEJW0DebUH+LREJPP+KYbI/ f6rbn288SxuSeMPleLShcZ4= X-Google-Smtp-Source: ABdhPJwzecCNz8S1StIh/uKkz5hKgliVp5Z7cHTXIKghi024EigbCEyK5spi96jVhOBjfA/oU5nOuw== X-Received: by 2002:ac2:43d2:: with SMTP id u18mr3060957lfl.120.1607086154164; Fri, 04 Dec 2020 04:49:14 -0800 (PST) Received: from localhost.localdomain (62-78-225-252.bb.dnainternet.fi. [62.78.225.252]) by smtp.gmail.com with ESMTPSA id w11sm1635952lfl.33.2020.12.04.04.49.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Dec 2020 04:49:13 -0800 (PST) Date: Fri, 4 Dec 2020 14:49:06 +0200 From: Matti Vaittinen To: matti.vaittinen@fi.rohmeurope.com, mazziesaccount@gmail.com Cc: Sebastian Reichel , Linus Walleij , Krzysztof Kozlowski , Dmitry Osipenko , Cong Pham , rostokus@gmail.com, fan.chen@mediatek.com, linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org Subject: [RFC PATCH v2 4/6] mfd: add BD71827 header 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 BD71827 driver header. For a record - Header is originally based on work authored by Cong Pham although not much of original work is left now. Signed-off-by: Matti Vaittinen --- This patch was not in v1. This brings in some charger registers for the BD71827 charger which is in following patches. include/linux/mfd/rohm-bd71827.h | 295 +++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 include/linux/mfd/rohm-bd71827.h 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 */ From patchwork Fri Dec 4 12:53:00 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 11951693 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,MENTIONS_GIT_HOSTING,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id D6764C433FE for ; Fri, 4 Dec 2020 12:53:57 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 6927222A85 for ; Fri, 4 Dec 2020 12:53:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726618AbgLDMx4 (ORCPT ); Fri, 4 Dec 2020 07:53:56 -0500 Received: from mail-lj1-f174.google.com ([209.85.208.174]:34724 "EHLO mail-lj1-f174.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726441AbgLDMx4 (ORCPT ); Fri, 4 Dec 2020 07:53:56 -0500 Received: by mail-lj1-f174.google.com with SMTP id y16so6513091ljk.1; Fri, 04 Dec 2020 04:53:34 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=SdNS9XUNC4ZzqVm79pxf+yDLSdfEG7Ie5RFai8XimqQ=; b=MxNcGvGgyot6qQtTL03Omoh+9XE5HFjJCubYDnafUPNIYdUBbk/6nJpN0wcCJGT3Q7 y9chG+JwOoLvK1ir62TKydesfML0dDmlzjyGkgZnU9iUdKLWR6Yxmquv+xVlKii+vjja OmtDMz6xgG99rbup4pa6rGErP6tPKCMpRK8+/CrynzH00ePEgQAl96DCWvzGLdak97Ia evD5lPdYaZ2KT6QPmrO//efP5pO7J6puV08R++F1fHltg7fQAot2NntyqW3QPbA0lWnr sUJ/LcYJcOSQX4eJGL4WPGvDbp1z978kLOqyTEKhsTuL6wh59a2XdTjHBfnrqlcVXoaa 7rKg== X-Gm-Message-State: AOAM533bKs8jc2KsQcDbPDNWjqAhJbqeijJsYYPsISuVrigWsrjxNsSV nrEdN3Dd6q1Q0FA9Bo1Jr2s= X-Google-Smtp-Source: ABdhPJwvvsww8p6nyQNJ6rnbIEf7mYD+UhISRV6YB4Dwf38WaQbqCgzSnNlxbK3K8HELm2Q6jPn5MQ== X-Received: by 2002:a2e:3a10:: with SMTP id h16mr3467671lja.400.1607086387890; Fri, 04 Dec 2020 04:53:07 -0800 (PST) Received: from localhost.localdomain (62-78-225-252.bb.dnainternet.fi. [62.78.225.252]) by smtp.gmail.com with ESMTPSA id x30sm1706333ljd.108.2020.12.04.04.53.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Dec 2020 04:53:06 -0800 (PST) Date: Fri, 4 Dec 2020 14:53:00 +0200 From: Matti Vaittinen To: matti.vaittinen@fi.rohmeurope.com, mazziesaccount@gmail.com Cc: Sebastian Reichel , Linus Walleij , Krzysztof Kozlowski , Dmitry Osipenko , Cong Pham , rostokus@gmail.com, fan.chen@mediatek.com, linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org Subject: [RFC PATCH v2 5/6] power: supply: Add bd718(27/28/78) charger driver Message-ID: <6a2b702f578b393425d6fad184972008a48b7eaf.1607085199.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 charger driver for ROHM BD718(27/28/78) PMIC charger block. Driver utilizes the swgauge for battery status / current polling, CC correction and SOC estimation. A version of driver which does not utilize swgauge can be seen in ROHM venor specific Linux tree - comparison can visualize how swgauge 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 --- This is an example of driver which could be using the swgauge. Driver is for ROHM BD71827, BD71828 and BD71878. This driver is not properly tested and no review is requested now - this is provided to give some context to sw-gauge (gah. I already hate that name - open to all suggestions. Naming is Hard). If the idea of swgauge is seen Ok - then I will do proper testing and send a proper version of this patch. drivers/power/supply/Kconfig | 10 + drivers/power/supply/Makefile | 1 + drivers/power/supply/bd71827-power.c | 2162 ++++++++++++++++++++++++++ 3 files changed, 2173 insertions(+) create mode 100644 drivers/power/supply/bd71827-power.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index d78231f3caf7..137daf2fb071 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -756,6 +756,16 @@ config CHARGER_BD70528 information and altering charger configurations from charger block of the ROHM BD70528 Power Management IC. +config CHARGER_BD71828 + tristate "Power-supply driver for ROHM BD71828 PMIC" + depends on MFD_ROHM_BD71828 + select SW_GAUGE + help + Say Y here to enable support for charger, battery and fuel gauge + in ROHM BD71827 and 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 f3d9472ee8cb..36c2f369085a 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -96,6 +96,7 @@ 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_BD70528) += bd70528-charger.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..4c1524fdde5f --- /dev/null +++ b/drivers/power/supply/bd71827-power.c @@ -0,0 +1,2162 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * bd71827-power.c + * @file ROHM BD71827 Charger driver + * + * Copyright 2016. + * + * 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 + +/* This is horrible - what kind of a helper would be good? */ +#define GAUGE_GET_DRVDATA(sw) (dev_get_drvdata((sw)->psy->dev.parent)) + +#define MAX(X, Y) ((X) >= (Y) ? (X) : (Y)) +#define uAMP_TO_mAMP(ma) ((ma) / 1000) + +/* BD71828 and BD71827 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 0x01 +#define BD7182x_MASK_CONF_PON 0x01 +#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 + +/* Measured min and max value clear bits */ +#define BD7182x_MASK_VSYS_MIN_AVG_CLR 0x10 +#define BD7182x_MASK_VBAT_MIN_AVG_CLR 0x01 + + +#define JITTER_DEFAULT 3000 /* hope 3s is enough */ +#define BATTERY_CAP_MAH_DEFAULT 1529 +#define MAX_VOLTAGE_DEFAULT ocv_table_default[0] +#define MIN_VOLTAGE_DEFAULT 3400000 +#define THR_VOLTAGE_DEFAULT 4250000 +#define MAX_CURRENT_DEFAULT 890000 /* uA */ +#define AC_NAME "bd71827_ac" +#define BAT_NAME "bd71827_bat" + +#define BY_BAT_VOLT 0 +#define BY_VBATLOAD_REG 1 +#define INIT_COULOMB BY_VBATLOAD_REG + +/* + * 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 unit */ + +#define DGRD_TEMP_H_DEFAULT 450 /* 0.1 degrees C unit */ +#define DGRD_TEMP_M_DEFAULT 250 /* 0.1 degrees C unit */ +#define DGRD_TEMP_L_DEFAULT 50 /* 0.1 degrees C unit */ +#define DGRD_TEMP_VL_DEFAULT 0 /* 0.1 degrees C unit */ + +#define SOC_EST_MAX_NUM_DEFAULT 5 +#define DGRD_TEMP_CAP_H_DEFAULT 0 +#define DGRD_TEMP_CAP_M_DEFAULT 0 +#define DGRD_TEMP_CAP_L_DEFAULT 0 +#define DGRD_TEMP_CAP_VL_DEFAULT 0 + +#define PWRCTRL_NORMAL 0x22 +#define PWRCTRL_RESET 0x23 + +#define NUM_BAT_PARAMS 23 + +struct pwr_regs { + u8 vbat_init; + u8 vbat_init2; + u8 vbat_init3; + u8 vbat_avg; + u8 ibat; + u8 ibat_avg; + u8 vsys_avg; + u8 vbat_min_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 rex_clear_reg; + u8 rex_clear_mask; + 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 bat_stat; + u8 vdcin; +#ifdef PWRCTRL_HACK + u8 pwrctrl; +#endif +}; + +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, + .vbat_avg = BD71827_REG_VM_SA_VBAT_U, + .ibat = BD71827_REG_CC_CURCD_U, + .ibat_avg = BD71827_REG_CC_SA_CURCD_U, + .vsys_avg = BD71827_REG_VM_SA_VSYS_U, + .vbat_min_avg = BD71827_REG_VM_SA_VBAT_MIN_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, + .vbat_rex_avg = BD71827_REG_REX_SA_VBAT_U, + .rex_clear_reg = BD71827_REG_REX_CTRL_1, + .rex_clear_mask = BD71827_REX_CLR_MASK, + .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, + .bat_stat = BD71827_REG_BAT_STAT, + .vdcin = BD71827_REG_VM_DCIN_U, +#ifdef PWRCTRL_HACK + .pwrctrl = BD71827_REG_PWRCTRL, + .hibernate_mask = 0x1, +#endif +}; + +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, + .vbat_avg = BD71828_REG_VBAT_U, + .ibat = BD71828_REG_IBAT_U, + .ibat_avg = BD71828_REG_IBAT_AVG_U, + .vsys_avg = BD71828_REG_VSYS_AVG_U, + .vbat_min_avg = BD71828_REG_VBAT_MIN_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, + .rex_clear_reg = BD71828_REG_COULOMB_CTRL2, + .rex_clear_mask = BD71828_MASK_REX_CC_CLR, + .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, + .bat_stat = BD71828_REG_BAT_STAT, + .vdcin = BD71828_REG_VDCIN_U, +#ifdef PWRCTRL_HACK + .pwrctrl = BD71828_REG_PS_CTRL_1, + .hibernate_mask = 0x2, +#endif +}; + + +static int ocv_table_default[NUM_BAT_PARAMS] = { + 4350000, + 4325945, + 4255935, + 4197476, + 4142843, + 4090615, + 4047113, + 3987352, + 3957835, + 3920815, + 3879834, + 3827010, + 3807239, + 3791379, + 3779925, + 3775038, + 3773530, + 3756695, + 3734099, + 3704867, + 3635377, + 3512942, + 3019825 +}; /* unit 1 micro V */ + +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 + /* unit 0.1% */ +}; + +static int vdr_table_h_default[NUM_BAT_PARAMS] = { + 100, + 100, + 102, + 104, + 105, + 108, + 111, + 115, + 122, + 138, + 158, + 96, + 108, + 112, + 117, + 123, + 137, + 109, + 131, + 150, + 172, + 136, + 218 +}; + +static int vdr_table_m_default[NUM_BAT_PARAMS] = { + 100, + 100, + 100, + 100, + 102, + 104, + 114, + 110, + 127, + 141, + 139, + 96, + 102, + 106, + 109, + 113, + 130, + 134, + 149, + 188, + 204, + 126, + 271 +}; + +static int vdr_table_l_default[NUM_BAT_PARAMS] = { + 100, + 100, + 98, + 96, + 96, + 96, + 105, + 94, + 108, + 105, + 95, + 89, + 90, + 92, + 99, + 112, + 129, + 143, + 155, + 162, + 156, + 119, + 326 +}; + +static int vdr_table_vl_default[NUM_BAT_PARAMS] = { + 100, + 100, + 98, + 96, + 95, + 97, + 101, + 92, + 100, + 97, + 91, + 89, + 90, + 93, + 103, + 115, + 128, + 139, + 148, + 148, + 156, + 246, + 336 +}; + +int use_load_bat_params; + +static int battery_cap_mah; + +static int dgrd_cyc_cap = DGRD_CYC_CAP_DEFAULT; + +static int soc_est_max_num; + +static int dgrd_temp_cap_h; +static int dgrd_temp_cap_m; +static int dgrd_temp_cap_l; +static int dgrd_temp_cap_vl; + +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 sw_gauge *sw; + struct sw_gauge_desc gdesc; + struct sw_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 */ + spinlock_t dlock; + struct delayed_work bd_work; /**< delayed work for timed work */ + + struct pwr_regs *regs; + /* Reg val to uA */ + int curr_factor; + int rsens; + int (*get_temp)(struct bd71827_power *pwr, int *temp); + int battery_cap; +}; + +#define CC_to_UAH(pwr, cc) \ +({ \ + u64 __tmp = ((u64)(cc)) * 1000000000000LLU; \ + \ + do_div(__tmp, (pwr)->rsens * 36); \ + (int)__tmp; \ +}) + +/* + * 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, +}; + +/* u32 bd71827_calc_soc_org(u32 cc, int designed_cap); */ + +static int bd7182x_write16(struct bd71827_power *pwr, int reg, uint16_t val) +{ + + val = cpu_to_be16(val); + + return regmap_bulk_write(pwr->regmap, reg, &val, sizeof(val)); +} + +static int bd7182x_read16_himask(struct bd71827_power *pwr, int reg, int himask, + uint16_t *val) +{ + struct regmap *regmap = pwr->regmap; + int ret; + u8 *tmp = (u8 *) val; + + ret = regmap_bulk_read(regmap, reg, val, sizeof(*val)); + if (!ret) { + *tmp &= himask; + *val = be16_to_cpu(*val); + } + return ret; +} + +#if INIT_COULOMB == BY_VBATLOAD_REG +#define INITIAL_OCV_REGS 3 +/** @brief get initial battery voltage and current + * @param pwr power device + * @return 0 + */ +static int bd71827_get_init_bat_stat(struct bd71827_power *pwr, + int *ocv) +{ + int ret; + int i; + u8 regs[INITIAL_OCV_REGS] = { + pwr->regs->vbat_init, + pwr->regs->vbat_init2, + pwr->regs->vbat_init3 + }; + uint16_t vals[INITIAL_OCV_REGS]; + + *ocv = 0; + + for (i = 0; i < INITIAL_OCV_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; +} +#endif + +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; +} + +#if INIT_COULOMB == BY_BAT_VOLT +static int bd71827_get_vbat_curr(struct bd71827_power *pwr, int *vcell, int *curr) +{ + int ret; + + ret = bd71827_get_vbat(pwr, vcell); + *curr = 0; + + return ret; +} +#endif + +static int bd71827_get_current_ds_adc(struct bd71827_power *pwr, int *curr, int *curr_avg) +{ + uint16_t 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 sw_gauge *sw + __attribute__((unused)), int ocv, + int temp __attribute__((unused)), + int *soc); + +static int bd71827_voltage_to_capacity(struct sw_gauge *sw, int ocv, int temp, + int *soc) +{ + int i = 0; + + if (ocv > ocv_table[0]) { + *soc = soc_table[0]; + } else { + for (i = 0; soc_table[i] != -50; i++) { + if ((ocv <= ocv_table[i]) && (ocv > ocv_table[i+1])) { + *soc = (soc_table[i] - soc_table[i+1]) * + (ocv - ocv_table[i+1]) / + (ocv_table[i] - ocv_table[i+1]); + *soc += soc_table[i+1]; + break; + } + } + if (soc_table[i] == -50) + *soc = soc_table[i]; + } + + return 0; +} + +/* Unit is tenths of degree C */ +static int bd71827_get_temp(struct sw_gauge *sw, int *temp) +{ + struct bd71827_power *pwr = 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 sw_gauge *sw, int *temp) +{ + struct bd71827_power *pwr = 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; +} + +#if INIT_COULOMB == BY_BAT_VOLT +static int bd71827_calib_voltage(struct bd71827_power *pwr, int *ocv) +{ + int r, curr, volt, ret; + + bd71827_get_vbat_curr(pwr, &volt, &curr); + + ret = regmap_read(pwr->regmap, pwr->regs->chg_state, &r); + if (ret) { + dev_err(pwr->dev, "Charger state reading failed (%d)\n", ret); + } else if (curr > 0) { + /* voltage increment caused by battery inner resistor */ + if (r == 3) + volt -= 100 * 1000; + else if (r == 2) + volt -= 50 * 1000; + } + *ocv = volt; + + return 0; +} +#endif +static int __write_cc(struct bd71827_power *pwr, uint16_t bcap, + unsigned int reg, uint32_t *new) +{ + int ret; + uint32_t tmp; + uint16_t *swap_hi = (uint16_t *)&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 = cpu_to_be32(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 sw_gauge *sw, int bcap) +{ + struct bd71827_power *pwr = 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; + u32 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; + +#if INIT_COULOMB == BY_VBATLOAD_REG + /* Get init OCV by HW */ + bd71827_get_init_bat_stat(pwr, &ocv); + + dev_dbg(pwr->dev, "ocv %d\n", ocv); +#elif INIT_COULOMB == BY_BAT_VOLT + bd71827_calib_voltage(pwr, &ocv); +#endif + + /* Get init soc from ocv/soc table */ + ret = bd71827_voltage_to_capacity(NULL, ocv, 0, &soc); + if (ret) + return ret; + + dev_dbg(pwr->dev, "soc %d[0.1%%]\n", soc); + if (soc < 0) + soc = 0; + bcap = UAH_to_CC(pwr, pwr->battery_cap) * soc / 100; + write_cc(pwr, bcap + UAH_to_CC(pwr, pwr->battery_cap) / 200); + + 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 sw_gauge *sw, int *uv) +{ + struct bd71827_power *pwr = 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, + BD7182x_MASK_VSYS_MIN_AVG_CLR, + BD7182x_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 sw_gauge *sw, int *vbat) +{ + int voltage, ret; + struct bd71827_power *pwr = GAUGE_GET_DRVDATA(sw); + + ret = bd71827_get_vbat(pwr, &voltage); + if (ret) + return ret; + + *vbat = voltage; + + return 0; +} + +static int bd71828_get_uah_from_full(struct sw_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 = 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 = CC_to_UAH(pwr, diff_coulomb_cnt); + + return 0; +} + +static int bd71828_get_uah(struct sw_gauge *sw, int *uah) +{ + struct bd71827_power *pwr = GAUGE_GET_DRVDATA(sw); + u32 cc; + int ret; + + ret = read_cc(pwr, &cc); + if (!ret) + *uah = CC_to_UAH(pwr, cc); + + return ret; +} + +static int init_cc(struct bd71827_power *pwr) +{ + return start_cc(pwr); +} + +/* + * 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. + */ +int bd71827_get_ocv(struct sw_gauge *sw, int dsoc, int temp, int *ocv) +{ + int i = 0; + + if (dsoc > soc_table[0]) { + *ocv = MAX_VOLTAGE_DEFAULT; + return 0; + } + if (dsoc == 0) { + *ocv = ocv_table[21]; + return 0; + } + + i = 0; + while (i < 22) { + 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[22]; + + 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; + + for (i = 0; i < items; i++) + res[i] = vdr[i] + (temp - dgrd_temp) * (vdr_hi[i] - vdr[i]) / + (dgrd_temp_hi - dgrd_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 >= DGRD_TEMP_H_DEFAULT) + for (i = 0; i < NUM_BAT_PARAMS; i++) + vdr_table[i] = vdr_table_h[i]; + else if (temp >= DGRD_TEMP_M_DEFAULT) + calc_vdr(vdr_table, vdr_table_m, temp, DGRD_TEMP_M_DEFAULT, + vdr_table_h, DGRD_TEMP_H_DEFAULT, + NUM_BAT_PARAMS); + else if (temp >= DGRD_TEMP_L_DEFAULT) + calc_vdr(vdr_table, vdr_table_l, temp, DGRD_TEMP_L_DEFAULT, + vdr_table_m, DGRD_TEMP_M_DEFAULT, + NUM_BAT_PARAMS); + else if (temp >= DGRD_TEMP_VL_DEFAULT) + calc_vdr(vdr_table, vdr_table_vl, temp, + DGRD_TEMP_VL_DEFAULT, vdr_table_l, DGRD_TEMP_L_DEFAULT, + NUM_BAT_PARAMS); + else + for (i = 0; i < NUM_BAT_PARAMS; i++) + vdr_table[i] = vdr_table_vl[i]; + + if (dsoc > soc_table[0]) { + vdr = 100; + } else if (dsoc == 0) { + vdr = vdr_table[NUM_BAT_PARAMS - 2]; + } else { + for (i = 0; i < NUM_BAT_PARAMS - 1; i++) + if ((dsoc <= soc_table[i]) && (dsoc > soc_table[i+1])) { + vdr = (vdr_table[i] - vdr_table[i+1]) * + (dsoc - soc_table[i+1]) / + (soc_table[i] - soc_table[i+1]) + + vdr_table[i+1]; + break; + } + if (i == NUM_BAT_PARAMS - 1) + vdr = vdr_table[i]; + } + dev_dbg(pwr->dev, "vdr = %d\n", vdr); + return vdr; +} + +static int bd71828_zero_correct(struct sw_gauge *sw, int *effective_cap, + int cc_uah, int vbat, int temp) +{ + int ocv_table_load[NUM_BAT_PARAMS]; + int i, ret; + int ocv; + int dsoc; + struct bd71827_power *pwr = GAUGE_GET_DRVDATA(sw); + + /* + * Calculate SOC from CC and effective battery cap. + * Use unit of 0.1% for dsoc to improve accuracy + */ + dsoc = cc_uah * 1000 / *effective_cap; + dev_dbg(pwr->dev, "dsoc = %d\n", dsoc); + + ret = bd71827_get_ocv(sw, dsoc, 0, &ocv); + if (ret) + return ret; + + for (i = 1; i < NUM_BAT_PARAMS; i++) { + ocv_table_load[i] = ocv_table[i] - (ocv - vbat); + if (ocv_table_load[i] <= MIN_VOLTAGE_DEFAULT) { + 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 < NUM_BAT_PARAMS) { + int j, k, m; + int dv; + int lost_cap, new_lost_cap; + int dsoc0; + int vdr, vdr0; + + dv = (ocv_table_load[i-1] - ocv_table_load[i]) / 5; + for (j = 1; j < 5; j++) { + if ((ocv_table_load[i] + dv * j) > + MIN_VOLTAGE_DEFAULT) { + break; + } + } + + lost_cap = ((NUM_BAT_PARAMS - 2 - i) * 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 = lost_cap * 1000 / *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 < NUM_BAT_PARAMS; k++) { + ocv_table_load[k] = ocv_table[k] - + (ocv - vbat) * vdr0 / vdr; + if (ocv_table_load[k] <= MIN_VOLTAGE_DEFAULT) { + dev_dbg(pwr->dev, + "ocv_table_load[%d] = %d\n", k, + ocv_table_load[k]); + break; + } + } + if (k < NUM_BAT_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) > + MIN_VOLTAGE_DEFAULT) + break; + + new_lost_cap = ((NUM_BAT_PARAMS - 2 - 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 bd71827_init_hardware(struct bd71827_power *pwr) +{ + int r, ret; + + 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 = regmap_read(pwr->regmap, pwr->regs->conf, &r); + if (ret) { + dev_err(pwr->dev, "Failed to read CONF register\n"); + return ret; + } + + if (r & BD7182x_MASK_CONF_PON) { + int cc_val; + + /* Init HW, when the battery is inserted. */ + ret = regmap_update_bits(pwr->regmap, pwr->regs->conf, + BD7182x_MASK_CONF_PON, 0); + if (ret) { + dev_err(pwr->dev, "Failed to clear CONF register\n"); + return ret; + } + + /* Stop Coulomb Counter */ + 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; + + /* Clear Relaxed Coulomb Counter */ + ret = regmap_update_bits(pwr->regmap, pwr->regs->rex_clear_reg, + pwr->regs->rex_clear_mask, + pwr->regs->rex_clear_mask); + + /* 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; + + /* convert to correct unit */ + cc_val = UAH_to_CC(pwr, pwr->battery_cap); + + ret = bd7182x_write16(pwr, pwr->regs->batcap_mon_limit_u, + cc_val * 9 / 10); + if (ret) + return ret; + + /* Set Battery Capacity Monitor threshold1 as 90% */ + dev_dbg(pwr->dev, "BD71827_REG_CC_BATCAP1_TH = %d\n", + (cc_val * 9 / 10)); + } + + ret = init_cc(pwr); + + return 0; +} + + /* Set default parameters if no module parameters were given */ +static void bd71827_set_battery_parameters(struct bd71827_power *pwr) +{ + int i; + + if (use_load_bat_params == 0) { + battery_cap_mah = BATTERY_CAP_MAH_DEFAULT; + dgrd_cyc_cap = DGRD_CYC_CAP_DEFAULT; + soc_est_max_num = SOC_EST_MAX_NUM_DEFAULT; + dgrd_temp_cap_h = DGRD_TEMP_CAP_H_DEFAULT; + dgrd_temp_cap_m = DGRD_TEMP_CAP_M_DEFAULT; + dgrd_temp_cap_l = DGRD_TEMP_CAP_L_DEFAULT; + for (i = 0; i < NUM_BAT_PARAMS; i++) { + ocv_table[i] = ocv_table_default[i]; + soc_table[i] = soc_table_default[i]; + vdr_table_h[i] = vdr_table_h_default[i]; + vdr_table_m[i] = vdr_table_m_default[i]; + vdr_table_l[i] = vdr_table_l_default[i]; + vdr_table_vl[i] = vdr_table_vl_default[i]; + } + } + for (i = 0; i < NUM_BAT_PARAMS; i++) + soc_table[i] = soc_table_default[i]; + + pwr->battery_cap = battery_cap_mah * 1000; +} + +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; + val->intval = 5000 * vot; // 5 milli volt steps + break; + default: + return -EINVAL; + } + + return 0; +} + +static int bd71827_battery_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); + 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 = MAX_VOLTAGE_DEFAULT; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + val->intval = MIN_VOLTAGE_DEFAULT; + 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, +}; + +/** @brief bat properies */ +/* TODO: Fix this in swgauge - swgauge must copy this and add own props + */ +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_CAPACITY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, + 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 power_supply *psy = dev_get_drvdata(dev); + struct bd71827_power *pwr = power_supply_get_drvdata(psy); + 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 power_supply *psy = dev_get_drvdata(dev); + struct bd71827_power *pwr = power_supply_get_drvdata(psy); + 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 power_supply_desc bd71827_battery_desc = { + .name = BAT_NAME, + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = bd71827_battery_props, + .num_properties = ARRAY_SIZE(bd71827_battery_props), + .get_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 + +static irqreturn_t bd7182x_long_push(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + kobject_uevent(&(pwr->dev->kobj), KOBJ_OFFLINE); + dev_info(pwr->dev, "POWERON_LONG\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd7182x_mid_push(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + kobject_uevent(&(pwr->dev->kobj), KOBJ_OFFLINE); + dev_info(pwr->dev, "POWERON_MID\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd7182x_push(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + kobject_uevent(&(pwr->dev->kobj), KOBJ_ONLINE); + dev_info(pwr->dev, "POWERON_PRESS\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd7182x_dcin_removed(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_info(pwr->dev, "\n~~~DCIN removed\n"); + return IRQ_HANDLED; +} + +static irqreturn_t bd7182x_dcin_detected(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_info(pwr->dev, "\n~~~DCIN inserted\n"); + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_vbat_low_res(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_info(pwr->dev, "\n~~~ 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_info(pwr->dev, "\n~~~ 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_info(pwr->dev, "\n~~~ Overtemp Detected ...\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_bat_hi_res(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_info(pwr->dev, "\n~~~ Overtemp Resumed ...\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_bat_low_det(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_info(pwr->dev, "\n~~~ Lowtemp Detected ...\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_bat_low_res(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_info(pwr->dev, "\n~~~ Lowtemp Resumed ...\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_vf_det(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_info(pwr->dev, "\n~~~ VF Detected ...\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_vf_res(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_info(pwr->dev, "\n~~~ VF Resumed ...\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_vf125_det(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_info(pwr->dev, "\n~~~ VF125 Detected ...\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71827_temp_vf125_res(int irq, void *data) +{ + struct bd71827_power *pwr = (struct bd71827_power *)data; + + dev_info(pwr->dev, "\n~~~ VF125 Resumed ...\n"); + + return IRQ_HANDLED; +} + +struct bd7182x_irq_res { + const char *name; + irq_handler_t handler; +}; + +#define BDIRQ(na, hn) { .name = (na), .handler = (hn) } + +int bd7182x_get_irqs(struct platform_device *pdev, struct bd71827_power *pwr) +{ + int i, irq, ret; + static const struct bd7182x_irq_res irqs[] = { + BDIRQ("bd71828-pwr-longpush", bd7182x_long_push), + BDIRQ("bd71828-pwr-midpush", bd7182x_mid_push), + BDIRQ("bd71828-pwr-push", bd7182x_push), + 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), + }; + + for (i = 0; i < ARRAY_SIZE(irqs); i++) { + irq = platform_get_irq_byname(pdev, irqs[i].name); + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + irqs[i].handler, 0 /* IRQF_ONESHOT */, + irqs[i].name, pwr); + if (ret) + break; + } + + return 0; +// return ret; +} + +#define RSENS_DEFAULT_30MOHM 30000000 + +int bd7182x_get_rsens(struct bd71827_power *pwr) +{ + u64 tmp = RSENS_CURR; + int rsens_ohm = RSENS_DEFAULT_30MOHM; + struct device_node *np = NULL; + + if (pwr->dev->parent) + np = pwr->dev->parent->of_node; + + if (np) { + int ret; + uint32_t rs; + + ret = of_property_read_u32(np, + "rohm,charger-sense-resistor-ohm", + &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 sw_gauge *sw, int *rex_volt) +{ + struct bd71827_power *pwr = 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 sw_gauge *sw, int *rex_volt) +{ + int ret; + u16 tmp; + struct bd71827_power *pwr = 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 sw_gauge *sw, int *cycle) +{ + int tmpret, ret, update = 0; + uint16_t charged_coulomb_cnt; + int cc_designed_cap; + struct bd71827_power *pwr = 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; +} + +/* TODO: These are battery specific and should come from DT */ +/* Add these in batinfo? */ +static struct sw_gauge_temp_degr battery_temp_dgr_table[] = { + { + .temp_floor = DGRD_TEMP_VL_DEFAULT, + }, + { + .temp_floor = DGRD_TEMP_L_DEFAULT, + }, + { + .temp_floor = DGRD_TEMP_M_DEFAULT, + }, + { + .temp_floor = DGRD_TEMP_H_DEFAULT, + }, +}; + +static void fgauge_initial_values(struct bd71827_power *pwr) +{ + struct sw_gauge_desc *d = &pwr->gdesc; + struct sw_gauge_ops *o = &pwr->ops; + int sz; + + /* Set temperature degradation by module parameters */ + battery_temp_dgr_table[0].temp_degrade_1C = dgrd_temp_cap_vl; + battery_temp_dgr_table[0].temp_degrade_1C = dgrd_temp_cap_l; + battery_temp_dgr_table[0].temp_degrade_1C = dgrd_temp_cap_m; + battery_temp_dgr_table[0].temp_degrade_1C = dgrd_temp_cap_h; + + /* TODO: See if these could be get from DT? */ + d->poll_interval = JITTER_DEFAULT; /* 3 seconds */ + d->allow_set_cycle = true; + d->amount_of_temp_dgr = ARRAY_SIZE(battery_temp_dgr_table); + d->temp_dgr = battery_temp_dgr_table; + d->degrade_cycle_uah = dgrd_cyc_cap; + d->cap_adjust_volt_threshold = THR_VOLTAGE_DEFAULT; + d->designed_cap = pwr->battery_cap; + + 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; + + /* + * TODO: Add VDR stuff to DT and add this call-back conditionally. + * We should not require the VDR parameters here - if user has no + * interest/need for more accurate SOC estimation then he should not + * be forced to get the VDR parameters from ROHM. + */ + o->zero_cap_adjust = bd71828_zero_correct; + + if (!of_find_property(pwr->dev->of_node, "ocv-capacity-celsius", + &sz)) { + o->get_soc_by_ocv = &bd71827_voltage_to_capacity; + o->get_ocv_by_soc = &bd71827_get_ocv; + } else { + dev_dbg(pwr->dev, "OCV values given from DT\n"); + } + + /* TODO: + * o->suspend_calibrate = bd71828_suspend_calibrate; + */ + switch (pwr->chip_type) { + case ROHM_CHIP_TYPE_BD71828: + case ROHM_CHIP_TYPE_BD71878: + o->get_temp = bd71828_get_temp; + o->is_relaxed = bd71828_is_relaxed; + break; + case ROHM_CHIP_TYPE_BD71827: + o->get_temp = bd71827_get_temp; + o->is_relaxed = bd71827_is_relaxed; + 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 power_supply_config bat_cfg = {}; + struct sw_gauge_psy psycfg; + int ret; + struct regmap *regmap; + + psycfg.pcfg = &bat_cfg; + psycfg.pdesc = &bd71827_battery_desc; + + 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 == NULL) + return -ENOMEM; + + pwr->regmap = regmap; + pwr->dev = &pdev->dev; + pwr->chip_type = platform_get_device_id(pdev)->driver_data; + spin_lock_init(&pwr->dlock); + + switch (pwr->chip_type) { + case ROHM_CHIP_TYPE_BD71828: + case ROHM_CHIP_TYPE_BD71878: + pwr->regs = &pwr_regs_bd71828; + dev_info(pwr->dev, "Found ROHM BD718x8\n"); + break; + case ROHM_CHIP_TYPE_BD71827: + pwr->regs = &pwr_regs_bd71827; + dev_warn(pwr->dev, "BD71817 not tested\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 */ + bd71827_set_battery_parameters(pwr); + fgauge_initial_values(pwr); + + ret = bd7182x_get_rsens(pwr); + if (ret) { + dev_err(&pdev->dev, "sense resistor missing\n"); + return ret; + } + + platform_set_drvdata(pdev, pwr); + bd71827_init_hardware(pwr); + + bat_cfg.drv_data = pwr; + bat_cfg.attr_grp = &bd71827_sysfs_attr_groups[0]; + bat_cfg.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; + } + + /* Is name needed? If yes, then it should be numbered.. :/ */ + pwr->gdesc.name = "bd71828-gauge"; + pwr->sw = devm_psy_register_sw_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[] = { + { "bd71827-power", ROHM_CHIP_TYPE_BD71827 }, + { "bd71828-power", ROHM_CHIP_TYPE_BD71828 }, + { "bd71878-power", ROHM_CHIP_TYPE_BD71878 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, bd71827_charger_id); + +static struct platform_driver bd71827_power_driver = { + .driver = { + .name = "bd71827-power", + }, + .probe = bd71827_power_probe, + .id_table = bd71827_charger_id, +}; + +module_platform_driver(bd71827_power_driver); + +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(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(dgrd_temp_cap_h, int, 0444); +MODULE_PARM_DESC(dgrd_temp_cap_h, "dgrd_temp_cap_h:Degraded capacity at high temperature (uAh)"); + +module_param(dgrd_temp_cap_m, int, 0444); +MODULE_PARM_DESC(dgrd_temp_cap_m, "dgrd_temp_cap_m:Degraded capacity at middle temperature (uAh)"); + +module_param(dgrd_temp_cap_l, int, 0444); +MODULE_PARM_DESC(dgrd_temp_cap_l, "dgrd_temp_cap_l:Degraded capacity at low temperature (uAh)"); + +module_param(dgrd_temp_cap_vl, int, 0444); +MODULE_PARM_DESC(dgrd_temp_cap_vl, "dgrd_temp_cap_vl:Degraded capacity at very low temperature (uAh)"); + +module_param_array(ocv_table, int, NULL, 0444); +MODULE_PARM_DESC(ocv_table, "ocv_table:Open Circuit Voltage table (uV)"); + +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(27/28/78) PMIC Battery Charger driver"); +MODULE_LICENSE("GPL"); From patchwork Fri Dec 4 12:54:04 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Vaittinen, Matti" X-Patchwork-Id: 11951695 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 507B5C4361A for ; Fri, 4 Dec 2020 12:55:09 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 202E022ADC for ; Fri, 4 Dec 2020 12:55:08 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730096AbgLDMyx (ORCPT ); Fri, 4 Dec 2020 07:54:53 -0500 Received: from mail-lf1-f67.google.com ([209.85.167.67]:41397 "EHLO mail-lf1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728668AbgLDMyx (ORCPT ); Fri, 4 Dec 2020 07:54:53 -0500 Received: by mail-lf1-f67.google.com with SMTP id r24so7469938lfm.8; Fri, 04 Dec 2020 04:54:36 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to; bh=s01ov5opcnyzf4x1O5fBO8Vti1W7Yid/d9Y3v+rRNR0=; b=f3ZHzbMKhiHPBs08M5izqoZSKcd5CY3dY6chJYz/YLs7OI8E0R0qHZjXjZNgDbdVnH Y+MNNjVqIT1kowW/PfCfSv4UBVkjPPxgVuDbdIoPJwEUmQrTg7N2XeiKZhhANZdXJKFb s29ipVsVEJpX7CYAosyQ46oR7N2GUMI8loBrTyBBPXji3nROz3UczEhmRhIlukjmLqoi HkH8XlTV41fqmMnIJgbAhHI5LKhksGvAfVbsjEw5U3vO7EG7rJf1+ogPrUVdHhbTSmFi /4J9qwwSUoFF7JYiI/Ycb8ct6LwC40b5jLrwF9IEqLjJcazL98pHIj51ddipez4sXhOw ohUQ== X-Gm-Message-State: AOAM532E4gJBCGXOrz+uU3KeKGLEH6VK158dDCM6U60/7iRyopOloxV5 Y0D6tRpF0x/W4XDXi+AjdNQ= X-Google-Smtp-Source: ABdhPJxa/3xsUbth7NczJ/xOZThJVyoTzzGXWMusLwNxVPO3DipC33rJWnSDbKhz1wjlRW7dVGsJyw== X-Received: by 2002:a19:cbd4:: with SMTP id b203mr454459lfg.506.1607086450890; Fri, 04 Dec 2020 04:54:10 -0800 (PST) Received: from localhost.localdomain (62-78-225-252.bb.dnainternet.fi. [62.78.225.252]) by smtp.gmail.com with ESMTPSA id o4sm1609553lfo.229.2020.12.04.04.54.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Dec 2020 04:54:10 -0800 (PST) Date: Fri, 4 Dec 2020 14:54:04 +0200 From: Matti Vaittinen To: matti.vaittinen@fi.rohmeurope.com, mazziesaccount@gmail.com Cc: Sebastian Reichel , Linus Walleij , Krzysztof Kozlowski , Dmitry Osipenko , Cong Pham , rostokus@gmail.com, fan.chen@mediatek.com, linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org Subject: [RFC PATCH v2 6/6] MFD: bd71828: differentiate bd71828 and bd71827 chargers 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 BD71828 and BD71827 charger blocks have some minor differencies. Use own name for BD71828 so that charger driver can differentiate these by device-id. Signed-off-by: Matti Vaittinen --- This patch is also provided in this RFC version only for the sake of the completeness. drivers/mfd/rohm-bd71828.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c index 210261d026f2..fbd6e30136d9 100644 --- a/drivers/mfd/rohm-bd71828.c +++ b/drivers/mfd/rohm-bd71828.c @@ -44,7 +44,7 @@ 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", }, { .name = "bd71828-rtc", .resources = rtc_irqs,