From patchwork Sun Dec 15 17:21:24 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 851349 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 328624C80 for ; Sun, 15 Dec 2024 17:21:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283307; cv=none; b=uTukWCr4JJmMI+IfpERP4I9KBvgyyCfscsuxElKaiYA6ST0MMEJPHz/hn084JgkSDN/15YaqzxXcIA8lGtrFm9BlCGVq8dAD9vdHNI1ujlK65/Rd3pkDLhUtr6Bb21notQSMiIQXXhxsN4PJPlIW2hYcM88DZCknHSCmzuLBq4Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283307; c=relaxed/simple; bh=BLwC3WwoDUlLpGQKyzAtZQKR+rqkAQoUqDZK7nZ6UTA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=uDJihlBp3ToYzORwNV1FbYfIfjPlLa7OOZhk7rEE5R1ZljcYFBTnfvjSR4UKDaI1BncdIXNiRCEPa1A7w/dH0fTubBCtpctlvbbupeApNSxDMLXfhoMiHI8cP+8o4Gk5XYHHwe0Qz1Ga1CyJE2Ur0JKW8MTtMRGTuJDhnqHuVW0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=eAtApgtZ; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="eAtApgtZ" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1734283305; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=iYwhi850U32uEuTPC8+/nR1O8eC3/lakGHGU3/SHSFQ=; b=eAtApgtZ030HlOss23bkOg9MupoZbnEqUWLK85bYrgqmHgbU5qtelxPBc3H9kWZRRTPFJ/ jhzPtsoYhQtT3eD5h2efCbe6J+/o8SP8d3Pqf9xcvUQZQpfNknqqcC55jYqSkDt+iy1vmA RdbsLGXVX2d+jH6my/WEwFyzx5gjOUc= Received: from mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-390-EoR1iIyJNNq5WEoku9wXRw-1; Sun, 15 Dec 2024 12:21:43 -0500 X-MC-Unique: EoR1iIyJNNq5WEoku9wXRw-1 X-Mimecast-MFC-AGG-ID: EoR1iIyJNNq5WEoku9wXRw Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 7B3551956086; Sun, 15 Dec 2024 17:21:42 +0000 (UTC) Received: from localhost.localdomain (unknown [10.39.192.51]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 5E5DC195605A; Sun, 15 Dec 2024 17:21:41 +0000 (UTC) From: Hans de Goede To: Sebastian Reichel Cc: Hans de Goede , linux-pm@vger.kernel.org Subject: [PATCH 1/8] power: supply: Use power_supply_external_power_changed() in __power_supply_changed_work() Date: Sun, 15 Dec 2024 18:21:24 +0100 Message-ID: <20241215172133.178460-2-hdegoede@redhat.com> In-Reply-To: <20241215172133.178460-1-hdegoede@redhat.com> References: <20241215172133.178460-1-hdegoede@redhat.com> Precedence: bulk X-Mailing-List: linux-pm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 The power-supply core is designed so that power-supply driver callbacks such as get_property() and external_power_changed() will not be called until the power-supply's parent driver's probe() function has completed. There is a race where power_supply_changed() can be called for a supplier of a power-supply which is being probed after the device_add() in __power_supply_register() but before the parent driver's probe() function has completed. Hitting this race breaks the power-supply core's design to not call power-supply driver callbacks before probe() completion. This problem is caused by __power_supply_changed_work() calling the external_power_changed() directly rather then going through the power_supply_external_power_changed() helper which correcly checks psy->use_cnt . Switch to using power_supply_external_power_changed() to fix this race. Signed-off-by: Hans de Goede --- drivers/power/supply/power_supply_core.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 76ebaef403ed..54ee4c83b32f 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -70,10 +70,8 @@ static int __power_supply_changed_work(struct power_supply *pst, void *data) { struct power_supply *psy = data; - if (__power_supply_is_supplied_by(psy, pst)) { - if (pst->desc->external_power_changed) - pst->desc->external_power_changed(pst); - } + if (__power_supply_is_supplied_by(psy, pst)) + power_supply_external_power_changed(pst); return 0; } From patchwork Sun Dec 15 17:21:25 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 851181 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B75784C80 for ; Sun, 15 Dec 2024 17:21:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283311; cv=none; b=S7gqqIrMdVY2ufDrQJG4xYNW6AP+PctR6N5WaJY3rsANUtV0Waogww819wHXELpdAnlPAHUXf5ef778ukN7/cREyTEjg6/UNiBYzNhMkNzB+EOobSX9sOr4ur0FosYOZfQmbw/S89tmzSBcrwC7agG0dm3IxBA2IeB4uDLbjlU4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283311; c=relaxed/simple; bh=dqm78o3iJUPDTnbMWVy9z3p6RC8Sytq/g8OavqYhsTc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Jj7fP/eGpo5wpWVcYHpFPhWniPAGYofCAsHE1JCdjI/pMjP4LY34oqsfjKRlQHH6R6K5trcLPud6yToqXljJsIf+yVLshdyds5j2rYJbd40sGX6mi9z6x5gkT/31mswNFnqsGY7UnyK7ogvSd8Nr14OmjJipxh403l8F0mAuXJg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=C83Fryxe; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="C83Fryxe" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1734283308; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=507VWvX1YTc+kUxssdmKsxyrMUMTo7VOoAiMpryT+IU=; b=C83FryxeUyvAKf+KlJv1e83cg+1TmwSCw4IuMhd2u3PkvLlpTjlXZ4o3Lye3yGD2kwhRqu RTG+8eyujdqRxTVrKWfq8O0cEZjfwJnnezud/yb/ef7QZVy8RqmsfF2Duv+8Y/ZELJFbwq ML5GjkkEu4ZJoP6Hd/dusXhu0cTUFYg= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-423-BiJ59dCgMtKSjX6uMSJfpg-1; Sun, 15 Dec 2024 12:21:45 -0500 X-MC-Unique: BiJ59dCgMtKSjX6uMSJfpg-1 X-Mimecast-MFC-AGG-ID: BiJ59dCgMtKSjX6uMSJfpg Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 09ABC19560BD; Sun, 15 Dec 2024 17:21:44 +0000 (UTC) Received: from localhost.localdomain (unknown [10.39.192.51]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id E0FE3195605A; Sun, 15 Dec 2024 17:21:42 +0000 (UTC) From: Hans de Goede To: Sebastian Reichel Cc: Hans de Goede , linux-pm@vger.kernel.org Subject: [PATCH 2/8] power: supply: Add adc-battery-helper Date: Sun, 15 Dec 2024 18:21:25 +0100 Message-ID: <20241215172133.178460-3-hdegoede@redhat.com> In-Reply-To: <20241215172133.178460-1-hdegoede@redhat.com> References: <20241215172133.178460-1-hdegoede@redhat.com> Precedence: bulk X-Mailing-List: linux-pm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 The TI PMIC used on some Intel Bay/Cherry Trail systems has some builtin fuel-gauge functionality which just like the UG3105 fuel-gauge is not a full featured autonomous fuel-gauge. These fuel-gauges offer accurate current and voltage measurements but their coulomb-counters are intended to work together with an always on micro-controller monitoring the fuel-gauge. Add an adc-battery-helper offering capacity estimation for devices where such limited functionality fuel-gauges are exposed directly to Linux. This is a copy of the existing UG3105 capacity estimating code, generalized so that it can be re-used in other drivers. The next commit will replace the UG3105 driver's version of this code with using the adc-battery-helper. The API has been designed for easy integration into existing power-supply drivers. For example this functionality might also be a useful addition to the generic-adc-battery driver. The requirement of needing the adc_battery_helper struct to be the first member of a battery driver's data struct is not ideal. This is a compromise which is necessary to allow directly using the helper's get_property(), external_power_changed() and suspend()/resume() functions as power-supply / suspend-resume callbacks. Signed-off-by: Hans de Goede --- drivers/power/supply/Kconfig | 3 + drivers/power/supply/Makefile | 1 + drivers/power/supply/adc-battery-helper.c | 359 ++++++++++++++++++++++ drivers/power/supply/adc-battery-helper.h | 58 ++++ 4 files changed, 421 insertions(+) create mode 100644 drivers/power/supply/adc-battery-helper.c create mode 100644 drivers/power/supply/adc-battery-helper.h diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 9f2eef6787f7..3f780aafbbab 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -35,6 +35,9 @@ config APM_POWER Say Y here to enable support APM status emulation using battery class devices. +config ADC_BATTERY_HELPER + tristate + config GENERIC_ADC_BATTERY tristate "Generic battery support using IIO" depends on IIO diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 59c4a9f40d28..025eace9b80d 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -7,6 +7,7 @@ power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o obj-$(CONFIG_POWER_SUPPLY) += power_supply.o obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o +obj-$(CONFIG_ADC_BATTERY_HELPER) += adc-battery-helper.o obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o obj-$(CONFIG_APM_POWER) += apm_power.o diff --git a/drivers/power/supply/adc-battery-helper.c b/drivers/power/supply/adc-battery-helper.c new file mode 100644 index 000000000000..1917e92ab1eb --- /dev/null +++ b/drivers/power/supply/adc-battery-helper.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Helper to add capacity estimation to batteries with current and voltage measurement + * + * Some fuel-gauges are not full-featured autonomous fuel-gauges. + * These fuel-gauges offer accurate current and voltage measurements but + * their coulomb-counters are intended to work together with an always on + * micro-controller monitoring the fuel-gauge. + * + * This adc-battery-helper code offers capacity estimation for devices where + * such limited functionality fuel-gauges are exposed directly to Linux. + * This helper requires the hw to provide accurate battery current_now and + * voltage_now measurement and this helper the provides the following properties + * based on top of those readings: + * + * POWER_SUPPLY_PROP_STATUS + * POWER_SUPPLY_PROP_VOLTAGE_OCV + * POWER_SUPPLY_PROP_VOLTAGE_NOW + * POWER_SUPPLY_PROP_CURRENT_NOW + * POWER_SUPPLY_PROP_CAPACITY + * + * As well as optional the following properties assuming an always present + * system-scope battery, allowing direct use of adc_battery_helper_get_prop() + * in this common case: + * POWER_SUPPLY_PROP_PRESENT + * POWER_SUPPLY_PROP_SCOPE + * + * Using this helper is as simple as: + * + * 1. Embed a struct adc_battery_helper this MUST be the first member of + * the battery driver's data struct. + * 2. Use adc_battery_helper_props[] or add the above properties to + * the list of properties in power_supply_desc + * 3. Call adc_battery_helper_init() after registering the power_supply and + * before returning from the probe() function + * 4. Use adc_battery_helper_get_prop() as the power-supply's get_property() + * method, or call it for the above properties. + * 5. Use adc_battery_helper_external_power_changed() as the power-supply's + * external_power_changed() method or call it from that method. + * 6. Use adc_battery_helper_[suspend|resume]() as suspend-resume methods or + * call them from the driver's suspend-resume methods. + * + * The provided get_voltage_and_current_now() method will be called by this + * helper at adc_battery_helper_init() time and later. + * + * Copyright (c) 2021-2024 Hans de Goede + */ + +#include +#include +#include +#include +#include + +#include "adc-battery-helper.h" + +#define MOV_AVG_WINDOW 8 +#define INIT_POLL_TIME (5 * HZ) +#define POLL_TIME (30 * HZ) +#define SETTLE_TIME (1 * HZ) + +#define INIT_POLL_COUNT 30 + +#define CURR_HYST_UA 65000 + +#define LOW_BAT_UV 3700000 +#define FULL_BAT_HYST_UV 38000 + +static int adc_battery_helper_get_status(struct adc_battery_helper *help) +{ + int full_uv = + help->psy->battery_info->constant_charge_voltage_max_uv - FULL_BAT_HYST_UV; + + if (help->curr_ua > CURR_HYST_UA) + return POWER_SUPPLY_STATUS_CHARGING; + + if (help->curr_ua < -CURR_HYST_UA) + return POWER_SUPPLY_STATUS_DISCHARGING; + + if (help->supplied && help->ocv_avg_uv > full_uv) + return POWER_SUPPLY_STATUS_FULL; + + return POWER_SUPPLY_STATUS_NOT_CHARGING; +} + +static int adc_battery_helper_get_capacity(struct adc_battery_helper *help) +{ + /* + * OCV voltages in uV for 0-110% in 5% increments, the 100-110% is + * for LiPo HV (High-Voltage) bateries which can go up to 4.35V + * instead of the usual 4.2V. + */ + static const int ocv_capacity_tbl[23] = { + 3350000, + 3610000, + 3690000, + 3710000, + 3730000, + 3750000, + 3770000, + 3786667, + 3803333, + 3820000, + 3836667, + 3853333, + 3870000, + 3907500, + 3945000, + 3982500, + 4020000, + 4075000, + 4110000, + 4150000, + 4200000, + 4250000, + 4300000, + }; + int i, ocv_diff, ocv_step; + + if (help->ocv_avg_uv < ocv_capacity_tbl[0]) + return 0; + + if (help->status == POWER_SUPPLY_STATUS_FULL) + return 100; + + for (i = 1; i < ARRAY_SIZE(ocv_capacity_tbl); i++) { + if (help->ocv_avg_uv > ocv_capacity_tbl[i]) + continue; + + ocv_diff = ocv_capacity_tbl[i] - help->ocv_avg_uv; + ocv_step = ocv_capacity_tbl[i] - ocv_capacity_tbl[i - 1]; + /* scale 0-110% down to 0-100% for LiPo HV */ + if (help->psy->battery_info->constant_charge_voltage_max_uv >= 4300000) + return (i * 500 - ocv_diff * 500 / ocv_step) / 110; + else + return i * 5 - ocv_diff * 5 / ocv_step; + } + + return 100; +} + +static void adc_battery_helper_work(struct work_struct *work) +{ + struct adc_battery_helper *help = container_of(work, struct adc_battery_helper, + work.work); + int i, curr_diff_ua, volt_diff_uv, res, ret, win_size; + struct device *dev = help->psy->dev.parent; + int volt_uv, prev_volt_uv = help->volt_uv; + int curr_ua, prev_curr_ua = help->curr_ua; + bool prev_supplied = help->supplied; + int prev_status = help->status; + + guard(mutex)(&help->lock); + + ret = help->get_voltage_and_current_now(help->psy, &volt_uv, &curr_ua); + if (ret) + goto out; + + help->volt_uv = volt_uv; + help->curr_ua = curr_ua; + + help->ocv_uv[help->ocv_avg_index] = + help->volt_uv - help->curr_ua * help->intern_res_avg_mohm / 1000; + dev_dbg(dev, "volt-now: %d, curr-now: %d, volt-ocv: %d\n", + help->volt_uv, help->curr_ua, help->ocv_uv[help->ocv_avg_index]); + help->ocv_avg_index = (help->ocv_avg_index + 1) % MOV_AVG_WINDOW; + help->poll_count++; + + help->ocv_avg_uv = 0; + win_size = min(help->poll_count, MOV_AVG_WINDOW); + for (i = 0; i < win_size; i++) + help->ocv_avg_uv += help->ocv_uv[i]; + help->ocv_avg_uv /= win_size; + + help->supplied = power_supply_am_i_supplied(help->psy); + help->status = adc_battery_helper_get_status(help); + help->capacity = adc_battery_helper_get_capacity(help); + + /* + * Skip internal resistance calc on charger [un]plug and + * when the battery is almost empty (voltage low). + */ + if (help->supplied != prev_supplied || + help->volt_uv < LOW_BAT_UV || + help->poll_count < 2) + goto out; + + /* + * Assuming that the OCV voltage does not change significantly + * between 2 polls, then we can calculate the internal resistance + * on a significant current change by attributing all voltage + * change between the 2 readings to the internal resistance. + */ + curr_diff_ua = abs(help->curr_ua - prev_curr_ua); + if (curr_diff_ua < CURR_HYST_UA) + goto out; + + volt_diff_uv = abs(help->volt_uv - prev_volt_uv); + res = volt_diff_uv * 1000 / curr_diff_ua; + + if ((res < (help->intern_res_avg_mohm * 2 / 3)) || + (res > (help->intern_res_avg_mohm * 4 / 3))) { + dev_dbg(dev, "Ignoring outlier internal resistance %d mOhm\n", res); + goto out; + } + + dev_dbg(dev, "Internal resistance %d mOhm\n", res); + + help->intern_res_mohm[help->intern_res_avg_index] = res; + help->intern_res_avg_index = (help->intern_res_avg_index + 1) % MOV_AVG_WINDOW; + help->intern_res_poll_count++; + + help->intern_res_avg_mohm = 0; + win_size = min(help->intern_res_poll_count, MOV_AVG_WINDOW); + for (i = 0; i < win_size; i++) + help->intern_res_avg_mohm += help->intern_res_mohm[i]; + help->intern_res_avg_mohm /= win_size; + +out: + queue_delayed_work(system_wq, &help->work, + (help->poll_count <= INIT_POLL_COUNT) ? + INIT_POLL_TIME : POLL_TIME); + + if (help->status != prev_status) + power_supply_changed(help->psy); +} + +const enum power_supply_property adc_battery_helper_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_SCOPE, +}; +EXPORT_SYMBOL_GPL(adc_battery_helper_properties); + +static_assert(ARRAY_SIZE(adc_battery_helper_properties) == + ADC_HELPER_NUM_PROPERTIES); + +int adc_battery_helper_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct adc_battery_helper *help = power_supply_get_drvdata(psy); + int dummy, ret = 0; + + /* + * Avoid racing with adc_battery_helper_work() while it is updating + * variables and avoid calling get_voltage_and_current_now() reentrantly. + */ + guard(mutex)(&help->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = help->status; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = help->get_voltage_and_current_now(psy, &val->intval, &dummy); + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = help->ocv_avg_uv; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = help->get_voltage_and_current_now(psy, &dummy, &val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = help->capacity; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + break; + default: + return -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(adc_battery_helper_get_property); + +void adc_battery_helper_external_power_changed(struct power_supply *psy) +{ + struct adc_battery_helper *help = power_supply_get_drvdata(psy); + + dev_dbg(help->psy->dev.parent, "external power changed\n"); + mod_delayed_work(system_wq, &help->work, SETTLE_TIME); +} +EXPORT_SYMBOL_GPL(adc_battery_helper_external_power_changed); + +static void adc_battery_helper_start_work(struct adc_battery_helper *help) +{ + help->poll_count = 0; + help->ocv_avg_index = 0; + + queue_delayed_work(system_wq, &help->work, 0); + flush_delayed_work(&help->work); +} + +int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy, + adc_battery_helper_get_func get_voltage_and_current_now) +{ + struct device *dev = psy->dev.parent; + int ret; + + help->psy = psy; + help->get_voltage_and_current_now = get_voltage_and_current_now; + + ret = devm_mutex_init(dev, &help->lock); + if (ret) + return ret; + + ret = devm_delayed_work_autocancel(dev, &help->work, adc_battery_helper_work); + if (ret) + return ret; + + if (!help->psy->battery_info || + help->psy->battery_info->factory_internal_resistance_uohm == -EINVAL || + help->psy->battery_info->constant_charge_voltage_max_uv == -EINVAL) { + dev_err(dev, "error required properties are missing\n"); + return -ENODEV; + } + + /* Use provided internal resistance as start point (in milli-ohm) */ + help->intern_res_avg_mohm = + help->psy->battery_info->factory_internal_resistance_uohm / 1000; + /* Also add it to the internal resistance moving average window */ + help->intern_res_mohm[0] = help->intern_res_avg_mohm; + help->intern_res_avg_index = 1; + help->intern_res_poll_count = 1; + + adc_battery_helper_start_work(help); + return 0; +} +EXPORT_SYMBOL_GPL(adc_battery_helper_init); + +int adc_battery_helper_suspend(struct device *dev) +{ + struct adc_battery_helper *help = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&help->work); + return 0; +} +EXPORT_SYMBOL_GPL(adc_battery_helper_suspend); + +int adc_battery_helper_resume(struct device *dev) +{ + struct adc_battery_helper *help = dev_get_drvdata(dev); + + adc_battery_helper_start_work(help); + return 0; +} +EXPORT_SYMBOL_GPL(adc_battery_helper_resume); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/adc-battery-helper.h b/drivers/power/supply/adc-battery-helper.h new file mode 100644 index 000000000000..2a13c64eadf1 --- /dev/null +++ b/drivers/power/supply/adc-battery-helper.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Helper to add capacity estimation to batteries with current and voltage measurement + * Copyright (c) 2021-2024 Hans de Goede + */ + +#include +#include + +#define ADC_BAT_HELPER_MOV_AVG_WINDOW 8 + +struct power_supply; + +/* + * The adc battery helper code needs voltage- and current-now to be sampled as + * close to each other (in sample-time) as possible. A single getter function is + * used to allow the battery driver to handle this in the best way possible. + */ +typedef int (*adc_battery_helper_get_func)(struct power_supply *psy, int *volt, int *curr); + +struct adc_battery_helper { + struct power_supply *psy; + struct delayed_work work; + struct mutex lock; + adc_battery_helper_get_func get_voltage_and_current_now; + int ocv_uv[ADC_BAT_HELPER_MOV_AVG_WINDOW]; /* micro-volt */ + int intern_res_mohm[ADC_BAT_HELPER_MOV_AVG_WINDOW]; /* milli-ohm */ + int poll_count; + int ocv_avg_index; + int ocv_avg_uv; /* micro-volt */ + int intern_res_poll_count; + int intern_res_avg_index; + int intern_res_avg_mohm; /* milli-ohm */ + int volt_uv; /* micro-volt */ + int curr_ua; /* micro-ampere */ + int capacity; /* procent */ + int status; + bool supplied; +}; + +extern const enum power_supply_property adc_battery_helper_properties[]; +/* Must be const cannot be an external. Asserted in adc-battery-helper.c */ +#define ADC_HELPER_NUM_PROPERTIES 7 + +int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy, + adc_battery_helper_get_func get_voltage_and_current_now); +/* + * The below functions can be directly used as power-supply / suspend-resume + * callbacks. They cast the power_supply_get_drvdata() / dev_get_drvdata() data + * directly to struct adc_battery_helper. Therefor struct adc_battery_helper + * MUST be the first member of the battery driver's data struct. + */ +int adc_battery_helper_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); +void adc_battery_helper_external_power_changed(struct power_supply *psy); +int adc_battery_helper_suspend(struct device *dev); +int adc_battery_helper_resume(struct device *dev); From patchwork Sun Dec 15 17:21:26 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 851348 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DEEAE199252 for ; Sun, 15 Dec 2024 17:21:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283310; cv=none; b=j2Jtggu0XZejTmOQPbDHZdmPgEePo9zQ9iosgn43NsnXtbduVVoeqRkMqoqQfNVwdLWjqBo8W0xPcCl/4doPCS5CBThnTW0Gzwq2N9caOnmnH+5uWma5c3TOuCAo+6+4s6PUdCVixJthNSczymc2GyjzNHr4iNs81N4339snM/0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283310; c=relaxed/simple; bh=FpagBzpadjdugK8vP+FdqRPXynBQIevUfmOWE6+DXM0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lS6YFoj/QoTTQTWtT0t6MN9KVQm4D9kj/xV4kdFAjLvUmqCwbHF0RCl8bEyQrfA0yyKn+WrFX1MAtuzqpojrMu1rukR3vw58AqkK4EqwA7yA/35J/AFwqVt1FTyru434aDyKtmn1j1YGh9MXAaXeaZi8STh/eW6Nkxs8gX4vl1A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=HQQEQIeC; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="HQQEQIeC" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1734283308; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=gCExS2GQQVUfl2HkmDSFZ4RIkpnjywd3Qq4IE+TUcDc=; b=HQQEQIeCy5tka3bltdukkvo9C7dsKOm3c60cFv7SQ726uxjvMAIeeZHroq6dJccVpjK5h9 06XbJHlQBhQnd8I2ViAOeF0W7rrLOHMkAMLx0Yz4KctGOsJsEQ2y0WadQzViOK3S0XA1b4 z2JbmLwT0aO4lmG06OGNlSV7+9x7OrQ= Received: from mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-655-HaSNKq72Mj65MS2xJIRzlQ-1; Sun, 15 Dec 2024 12:21:46 -0500 X-MC-Unique: HaSNKq72Mj65MS2xJIRzlQ-1 X-Mimecast-MFC-AGG-ID: HaSNKq72Mj65MS2xJIRzlQ Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B007D19560A3; Sun, 15 Dec 2024 17:21:45 +0000 (UTC) Received: from localhost.localdomain (unknown [10.39.192.51]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 6EF2D195605A; Sun, 15 Dec 2024 17:21:44 +0000 (UTC) From: Hans de Goede To: Sebastian Reichel Cc: Hans de Goede , linux-pm@vger.kernel.org Subject: [PATCH 3/8] power: supply: ug3105_battery: Let the core handle POWER_SUPPLY_PROP_TECHNOLOGY Date: Sun, 15 Dec 2024 18:21:26 +0100 Message-ID: <20241215172133.178460-4-hdegoede@redhat.com> In-Reply-To: <20241215172133.178460-1-hdegoede@redhat.com> References: <20241215172133.178460-1-hdegoede@redhat.com> Precedence: bulk X-Mailing-List: linux-pm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 The power-supply core already takes care of handling POWER_SUPPLY_PROP_TECHNOLOGY based on the battery_info. Drop the unnecessary handling from the driver. Signed-off-by: Hans de Goede --- drivers/power/supply/ug3105_battery.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c index ccc5c4d2e230..38e23bdd4603 100644 --- a/drivers/power/supply/ug3105_battery.c +++ b/drivers/power/supply/ug3105_battery.c @@ -287,7 +287,6 @@ static void ug3105_work(struct work_struct *work) static enum power_supply_property ug3105_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_OCV, @@ -316,9 +315,6 @@ static int ug3105_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_PRESENT: val->intval = 1; break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = chip->info->technology; - break; case POWER_SUPPLY_PROP_SCOPE: val->intval = POWER_SUPPLY_SCOPE_SYSTEM; break; From patchwork Sun Dec 15 17:21:27 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 851180 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8614D4C80 for ; Sun, 15 Dec 2024 17:21:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283314; cv=none; b=X5wByRJ5KUhzEApFOojbDYzjzT4iUjY3/ENh/Xce7MSFD++I4XcyQPbKJm31e9wk+bTkBHdeJHLK4Gk8fopcHMHCjFYYAREMl1VA1OEpQvcFnn9RgVGcmBp1hJfJOV/ieWxjNzMk0FyXUESdRnpRHfy0farmr4bEmERKK2qkyZQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283314; c=relaxed/simple; bh=BuH1simZDs3fuGORY7/Oi+jp2g4HWIJGMiVy+fw1PQM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Vmgq3YuFhvuov2SQmENl8oDDknHsdkhdOX8p3wbEpXlOa2Fyzc6P8pPHgDH9Wn5qfDC964uRqW58eiNh2f20SGeGENhqpS19rCAbM/PSuYOmU6pbyM6dqeBryhtPtXzMDsh8ASw6+6RZ/606MsLtJpn27C3+LkjcHI6JUWn6uPo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=hRL7VjzO; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="hRL7VjzO" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1734283311; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=qJJ7f2Q8sadvowiHFao/zRbWGKF04WbV41w3+6C9oBQ=; b=hRL7VjzODFO6CzwCgzA4MIkXGWjGU6b5n+lidaSk5+EleYJ7bKZR0J10UqKIllBCX+GcwT W6X3VZVCz3GLD78zStEaNi+EwolUBBcYSO9c7d6CHrGWJCyh+tRgzMVH+7c3PqbP+5mz7H hgKydkRosJOnhRUUCeZvDgLVKDgWhtU= Received: from mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-110-d5r1E4h7MBmihN-IKWV0ig-1; Sun, 15 Dec 2024 12:21:48 -0500 X-MC-Unique: d5r1E4h7MBmihN-IKWV0ig-1 X-Mimecast-MFC-AGG-ID: d5r1E4h7MBmihN-IKWV0ig Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 2C9DC19560A2; Sun, 15 Dec 2024 17:21:47 +0000 (UTC) Received: from localhost.localdomain (unknown [10.39.192.51]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 0F1CE195605A; Sun, 15 Dec 2024 17:21:45 +0000 (UTC) From: Hans de Goede To: Sebastian Reichel Cc: Hans de Goede , linux-pm@vger.kernel.org Subject: [PATCH 4/8] power: supply: ug3105_battery: Switch to adc-battery-helper Date: Sun, 15 Dec 2024 18:21:27 +0100 Message-ID: <20241215172133.178460-5-hdegoede@redhat.com> In-Reply-To: <20241215172133.178460-1-hdegoede@redhat.com> References: <20241215172133.178460-1-hdegoede@redhat.com> Precedence: bulk X-Mailing-List: linux-pm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 Switch ug3105_battery to using the new adc-battery-helper, since the helper's algorithms are a copy of the replaced ug3105_battery code this should not cause any functional differences. Signed-off-by: Hans de Goede --- drivers/power/supply/Kconfig | 1 + drivers/power/supply/ug3105_battery.c | 391 ++++---------------------- 2 files changed, 63 insertions(+), 329 deletions(-) diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 3f780aafbbab..6705808b0ec0 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -1000,6 +1000,7 @@ config CHARGER_SURFACE config BATTERY_UG3105 tristate "uPI uG3105 battery monitor driver" depends on I2C + select ADC_BATTERY_HELPER help Battery monitor driver for the uPI uG3105 battery monitor. diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c index 38e23bdd4603..3cd0c6944750 100644 --- a/drivers/power/supply/ug3105_battery.c +++ b/drivers/power/supply/ug3105_battery.c @@ -10,7 +10,22 @@ * is off or suspended, the coulomb counter is not used atm. * * Possible improvements: - * 1. Activate commented out total_coulomb_count code + * 1. Add coulumb counter reading, e.g. something like this: + * Read + reset coulomb counter every 10 polls (every 300 seconds) + * + * if ((chip->poll_count % 10) == 0) { + * val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT); + * if (val < 0) + * goto out; + * + * i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, + * UG3105_CTRL1_RESET_COULOMB_CNT); + * + * chip->total_coulomb_count += (s16)val; + * dev_dbg(&chip->client->dev, "coulomb count %d total %d\n", + * (s16)val, chip->total_coulomb_count); + * } + * * 2. Reset total_coulomb_count val to 0 when the battery is as good as empty * and remember that we did this (and clear the flag for this on susp/resume) * 3. When the battery is full check if the flag that we set total_coulomb_count @@ -34,21 +49,13 @@ * Copyright (C) 2021 Hans de Goede */ -#include #include -#include #include #include #include #include -#include -#define UG3105_MOV_AVG_WINDOW 8 -#define UG3105_INIT_POLL_TIME (5 * HZ) -#define UG3105_POLL_TIME (30 * HZ) -#define UG3105_SETTLE_TIME (1 * HZ) - -#define UG3105_INIT_POLL_COUNT 30 +#include "adc-battery-helper.h" #define UG3105_REG_MODE 0x00 #define UG3105_REG_CTRL1 0x01 @@ -61,33 +68,13 @@ #define UG3105_CTRL1_RESET_COULOMB_CNT 0x03 -#define UG3105_CURR_HYST_UA 65000 - -#define UG3105_LOW_BAT_UV 3700000 -#define UG3105_FULL_BAT_HYST_UV 38000 - struct ug3105_chip { + /* Must be the first member see adc-battery-helper documentation */ + struct adc_battery_helper helper; struct i2c_client *client; struct power_supply *psy; - struct power_supply_battery_info *info; - struct delayed_work work; - struct mutex lock; - int ocv[UG3105_MOV_AVG_WINDOW]; /* micro-volt */ - int intern_res[UG3105_MOV_AVG_WINDOW]; /* milli-ohm */ - int poll_count; - int ocv_avg_index; - int ocv_avg; /* micro-volt */ - int intern_res_poll_count; - int intern_res_avg_index; - int intern_res_avg; /* milli-ohm */ - int volt; /* micro-volt */ - int curr; /* micro-ampere */ - int total_coulomb_count; int uv_per_unit; int ua_per_unit; - int status; - int capacity; - bool supplied; }; static int ug3105_read_word(struct i2c_client *client, u8 reg) @@ -101,280 +88,43 @@ static int ug3105_read_word(struct i2c_client *client, u8 reg) return val; } -static int ug3105_get_status(struct ug3105_chip *chip) -{ - int full = chip->info->constant_charge_voltage_max_uv - UG3105_FULL_BAT_HYST_UV; - - if (chip->curr > UG3105_CURR_HYST_UA) - return POWER_SUPPLY_STATUS_CHARGING; - - if (chip->curr < -UG3105_CURR_HYST_UA) - return POWER_SUPPLY_STATUS_DISCHARGING; - - if (chip->supplied && chip->ocv_avg > full) - return POWER_SUPPLY_STATUS_FULL; - - return POWER_SUPPLY_STATUS_NOT_CHARGING; -} - -static int ug3105_get_capacity(struct ug3105_chip *chip) -{ - /* - * OCV voltages in uV for 0-110% in 5% increments, the 100-110% is - * for LiPo HV (High-Voltage) bateries which can go up to 4.35V - * instead of the usual 4.2V. - */ - static const int ocv_capacity_tbl[23] = { - 3350000, - 3610000, - 3690000, - 3710000, - 3730000, - 3750000, - 3770000, - 3786667, - 3803333, - 3820000, - 3836667, - 3853333, - 3870000, - 3907500, - 3945000, - 3982500, - 4020000, - 4075000, - 4110000, - 4150000, - 4200000, - 4250000, - 4300000, - }; - int i, ocv_diff, ocv_step; - - if (chip->ocv_avg < ocv_capacity_tbl[0]) - return 0; - - if (chip->status == POWER_SUPPLY_STATUS_FULL) - return 100; - - for (i = 1; i < ARRAY_SIZE(ocv_capacity_tbl); i++) { - if (chip->ocv_avg > ocv_capacity_tbl[i]) - continue; - - ocv_diff = ocv_capacity_tbl[i] - chip->ocv_avg; - ocv_step = ocv_capacity_tbl[i] - ocv_capacity_tbl[i - 1]; - /* scale 0-110% down to 0-100% for LiPo HV */ - if (chip->info->constant_charge_voltage_max_uv >= 4300000) - return (i * 500 - ocv_diff * 500 / ocv_step) / 110; - else - return i * 5 - ocv_diff * 5 / ocv_step; - } - - return 100; -} - -static void ug3105_work(struct work_struct *work) -{ - struct ug3105_chip *chip = container_of(work, struct ug3105_chip, - work.work); - int i, val, curr_diff, volt_diff, res, win_size; - bool prev_supplied = chip->supplied; - int prev_status = chip->status; - int prev_volt = chip->volt; - int prev_curr = chip->curr; - struct power_supply *psy; - - mutex_lock(&chip->lock); - - psy = chip->psy; - if (!psy) - goto out; - - val = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); - if (val < 0) - goto out; - chip->volt = val * chip->uv_per_unit; - - val = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); - if (val < 0) - goto out; - chip->curr = (s16)val * chip->ua_per_unit; - - chip->ocv[chip->ocv_avg_index] = - chip->volt - chip->curr * chip->intern_res_avg / 1000; - chip->ocv_avg_index = (chip->ocv_avg_index + 1) % UG3105_MOV_AVG_WINDOW; - chip->poll_count++; - - /* - * See possible improvements comment above. - * - * Read + reset coulomb counter every 10 polls (every 300 seconds) - * if ((chip->poll_count % 10) == 0) { - * val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT); - * if (val < 0) - * goto out; - * - * i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, - * UG3105_CTRL1_RESET_COULOMB_CNT); - * - * chip->total_coulomb_count += (s16)val; - * dev_dbg(&chip->client->dev, "coulomb count %d total %d\n", - * (s16)val, chip->total_coulomb_count); - * } - */ - - chip->ocv_avg = 0; - win_size = min(chip->poll_count, UG3105_MOV_AVG_WINDOW); - for (i = 0; i < win_size; i++) - chip->ocv_avg += chip->ocv[i]; - chip->ocv_avg /= win_size; - - chip->supplied = power_supply_am_i_supplied(psy); - chip->status = ug3105_get_status(chip); - chip->capacity = ug3105_get_capacity(chip); - - /* - * Skip internal resistance calc on charger [un]plug and - * when the battery is almost empty (voltage low). - */ - if (chip->supplied != prev_supplied || - chip->volt < UG3105_LOW_BAT_UV || - chip->poll_count < 2) - goto out; - - /* - * Assuming that the OCV voltage does not change significantly - * between 2 polls, then we can calculate the internal resistance - * on a significant current change by attributing all voltage - * change between the 2 readings to the internal resistance. - */ - curr_diff = abs(chip->curr - prev_curr); - if (curr_diff < UG3105_CURR_HYST_UA) - goto out; - - volt_diff = abs(chip->volt - prev_volt); - res = volt_diff * 1000 / curr_diff; - - if ((res < (chip->intern_res_avg * 2 / 3)) || - (res > (chip->intern_res_avg * 4 / 3))) { - dev_dbg(&chip->client->dev, "Ignoring outlier internal resistance %d mOhm\n", res); - goto out; - } - - dev_dbg(&chip->client->dev, "Internal resistance %d mOhm\n", res); - - chip->intern_res[chip->intern_res_avg_index] = res; - chip->intern_res_avg_index = (chip->intern_res_avg_index + 1) % UG3105_MOV_AVG_WINDOW; - chip->intern_res_poll_count++; - - chip->intern_res_avg = 0; - win_size = min(chip->intern_res_poll_count, UG3105_MOV_AVG_WINDOW); - for (i = 0; i < win_size; i++) - chip->intern_res_avg += chip->intern_res[i]; - chip->intern_res_avg /= win_size; - -out: - mutex_unlock(&chip->lock); - - queue_delayed_work(system_wq, &chip->work, - (chip->poll_count <= UG3105_INIT_POLL_COUNT) ? - UG3105_INIT_POLL_TIME : UG3105_POLL_TIME); - - if (chip->status != prev_status && psy) - power_supply_changed(psy); -} - -static enum power_supply_property ug3105_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_SCOPE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_OCV, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_CAPACITY, -}; - -static int ug3105_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) +static int ug3105_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr) { struct ug3105_chip *chip = power_supply_get_drvdata(psy); - int ret = 0; + int ret; - mutex_lock(&chip->lock); + ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); + if (ret < 0) + return ret; - if (!chip->psy) { - ret = -EAGAIN; - goto out; - } + *volt = ret * chip->uv_per_unit; - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = chip->status; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = 1; - break; - case POWER_SUPPLY_PROP_SCOPE: - val->intval = POWER_SUPPLY_SCOPE_SYSTEM; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); - if (ret < 0) - break; - val->intval = ret * chip->uv_per_unit; - ret = 0; - break; - case POWER_SUPPLY_PROP_VOLTAGE_OCV: - val->intval = chip->ocv_avg; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); - if (ret < 0) - break; - val->intval = (s16)ret * chip->ua_per_unit; - ret = 0; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = chip->capacity; - break; - default: - ret = -EINVAL; - } + ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); + if (ret < 0) + return ret; -out: - mutex_unlock(&chip->lock); - return ret; -} - -static void ug3105_external_power_changed(struct power_supply *psy) -{ - struct ug3105_chip *chip = power_supply_get_drvdata(psy); - - dev_dbg(&chip->client->dev, "external power changed\n"); - mod_delayed_work(system_wq, &chip->work, UG3105_SETTLE_TIME); + *curr = (s16)ret * chip->ua_per_unit; + return 0; } static const struct power_supply_desc ug3105_psy_desc = { .name = "ug3105_battery", .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = ug3105_get_property, - .external_power_changed = ug3105_external_power_changed, - .properties = ug3105_battery_props, - .num_properties = ARRAY_SIZE(ug3105_battery_props), + .get_property = adc_battery_helper_get_property, + .external_power_changed = adc_battery_helper_external_power_changed, + .properties = adc_battery_helper_properties, + .num_properties = ADC_HELPER_NUM_PROPERTIES, }; -static void ug3105_init(struct ug3105_chip *chip) +static void ug3105_start(struct i2c_client *client) { - chip->poll_count = 0; - chip->ocv_avg_index = 0; - chip->total_coulomb_count = 0; - i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE, - UG3105_MODE_RUN); - i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, - UG3105_CTRL1_RESET_COULOMB_CNT); - queue_delayed_work(system_wq, &chip->work, 0); - flush_delayed_work(&chip->work); + i2c_smbus_write_byte_data(client, UG3105_REG_MODE, UG3105_MODE_RUN); + i2c_smbus_write_byte_data(client, UG3105_REG_CTRL1, UG3105_CTRL1_RESET_COULOMB_CNT); +} + +static void ug3105_stop(struct i2c_client *client) +{ + i2c_smbus_write_byte_data(client, UG3105_REG_MODE, UG3105_MODE_STANDBY); } static int ug3105_probe(struct i2c_client *client) @@ -382,7 +132,6 @@ static int ug3105_probe(struct i2c_client *client) struct power_supply_config psy_cfg = {}; struct device *dev = &client->dev; u32 curr_sense_res_uohm = 10000; - struct power_supply *psy; struct ug3105_chip *chip; int ret; @@ -391,25 +140,8 @@ static int ug3105_probe(struct i2c_client *client) return -ENOMEM; chip->client = client; - mutex_init(&chip->lock); - ret = devm_delayed_work_autocancel(dev, &chip->work, ug3105_work); - if (ret) - return ret; - psy_cfg.drv_data = chip; - psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg); - if (IS_ERR(psy)) - return PTR_ERR(psy); - - ret = power_supply_get_battery_info(psy, &chip->info); - if (ret) - return ret; - - if (chip->info->factory_internal_resistance_uohm == -EINVAL || - chip->info->constant_charge_voltage_max_uv == -EINVAL) { - dev_err(dev, "error required properties are missing\n"); - return -ENODEV; - } + ug3105_start(client); device_property_read_u32(dev, "upisemi,rsns-microohm", &curr_sense_res_uohm); @@ -417,35 +149,36 @@ static int ug3105_probe(struct i2c_client *client) * DAC maximum is 4.5V divided by 65536 steps + an unknown factor of 10 * coming from somewhere for some reason (verified with a volt-meter). */ - chip->uv_per_unit = 45000000/65536; + chip->uv_per_unit = 45000000 / 65536; /* Datasheet says 8.1 uV per unit for the current ADC */ chip->ua_per_unit = 8100000 / curr_sense_res_uohm; - /* Use provided internal resistance as start point (in milli-ohm) */ - chip->intern_res_avg = chip->info->factory_internal_resistance_uohm / 1000; - /* Also add it to the internal resistance moving average window */ - chip->intern_res[0] = chip->intern_res_avg; - chip->intern_res_avg_index = 1; - chip->intern_res_poll_count = 1; + psy_cfg.drv_data = chip; + chip->psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg); + if (IS_ERR(chip->psy)) { + ret = PTR_ERR(chip->psy); + goto stop; + } - mutex_lock(&chip->lock); - chip->psy = psy; - mutex_unlock(&chip->lock); - - ug3105_init(chip); + ret = adc_battery_helper_init(&chip->helper, chip->psy, + ug3105_get_voltage_and_current_now); + if (ret) + goto stop; i2c_set_clientdata(client, chip); return 0; + +stop: + ug3105_stop(client); + return ret; } static int __maybe_unused ug3105_suspend(struct device *dev) { struct ug3105_chip *chip = dev_get_drvdata(dev); - cancel_delayed_work_sync(&chip->work); - i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE, - UG3105_MODE_STANDBY); - + adc_battery_helper_suspend(dev); + ug3105_stop(chip->client); return 0; } @@ -453,8 +186,8 @@ static int __maybe_unused ug3105_resume(struct device *dev) { struct ug3105_chip *chip = dev_get_drvdata(dev); - ug3105_init(chip); - + ug3105_start(chip->client); + adc_battery_helper_resume(dev); return 0; } From patchwork Sun Dec 15 17:21:28 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 851347 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 55FDC1B4138 for ; Sun, 15 Dec 2024 17:21:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283313; cv=none; b=d0U9bk9cQq/PLVlgDCrpBtcLXmAscsTPW4lYU8DCntsNfaKw1I3ITk5z0LXQVqlt/HLVBF7u/ZMfwl1BxHb8w88Ab2VMCwh3EO8JTEo38KJE+Jn7gaD322Ou77SDXjl/SR0vh+YL9A9ThBSvo1aNkoCG5bHjFbhdkVY/GCz8wKU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283313; c=relaxed/simple; bh=FiV/ZuFImTrqTHapITSZqL4+GK1rpTFj9SEW4OW8Tb4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lqZ2oSLPqv5NPkTrMq461pu2cP5Q67eV7ZVDziGG9PgmwL9XgNU26EWTljqHyMopbkTEh8IlcmF7E3ZQIJNzGdGXyZVADfO7qX7kHCShlRthIKna5Sw3k2kUZfdNaL/Mhp7Yg5RPfQF85nnl6kSQDTWjZP+Qcb3o7wlul2Go+k0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=XtP0f/ur; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="XtP0f/ur" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1734283311; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=V7DZmgTPNoj6PSGzKxL+tnUIAhI5Qohrd49vV833GF8=; b=XtP0f/urHZcCsIgVS2Q4itWRnYh0+aTo071nYLvB0EBp8tWV16TLHsJmAjh7bMWpt22nZ8 gt+K84zlFfk64E5ynvRPmR+XSsEt3o2fNaejgW8aRnelCc//qCtX2Z89O/9gU8GokeY5sD SILQhINX1eaIenK5qyyZ39D7kZ2P8As= Received: from mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-474-8LP9ARGyM-OLCIfvk5EaWw-1; Sun, 15 Dec 2024 12:21:49 -0500 X-MC-Unique: 8LP9ARGyM-OLCIfvk5EaWw-1 X-Mimecast-MFC-AGG-ID: 8LP9ARGyM-OLCIfvk5EaWw Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id C05001956089; Sun, 15 Dec 2024 17:21:48 +0000 (UTC) Received: from localhost.localdomain (unknown [10.39.192.51]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 94544195605A; Sun, 15 Dec 2024 17:21:47 +0000 (UTC) From: Hans de Goede To: Sebastian Reichel Cc: Hans de Goede , linux-pm@vger.kernel.org Subject: [PATCH 5/8] power: supply: ug3105_battery: Put FG in standby on remove and shutdown Date: Sun, 15 Dec 2024 18:21:28 +0100 Message-ID: <20241215172133.178460-6-hdegoede@redhat.com> In-Reply-To: <20241215172133.178460-1-hdegoede@redhat.com> References: <20241215172133.178460-1-hdegoede@redhat.com> Precedence: bulk X-Mailing-List: linux-pm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 Put the fuel-gauge in standby mode when the driver is unbound and on system shutdown. This avoids unnecessary battery drain when the system is off. Signed-off-by: Hans de Goede --- drivers/power/supply/ug3105_battery.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c index 3cd0c6944750..8d4ee8c12db9 100644 --- a/drivers/power/supply/ug3105_battery.c +++ b/drivers/power/supply/ug3105_battery.c @@ -206,6 +206,8 @@ static struct i2c_driver ug3105_i2c_driver = { .pm = &ug3105_pm_ops, }, .probe = ug3105_probe, + .remove = ug3105_stop, + .shutdown = ug3105_stop, .id_table = ug3105_id, }; module_i2c_driver(ug3105_i2c_driver); From patchwork Sun Dec 15 17:21:29 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 851346 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D536E19993D for ; Sun, 15 Dec 2024 17:21:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283315; cv=none; b=MV1P1dwmzAp46JStqZWEF9OY+EMefT1ZDNxndBDSkJ0oH8h+KV9ou0laVA6gZkw6CcUQ4/ComjPqKuRuuv2z89ULYqTZ3gV2cRhoaLieUAXnLlbqF3yq8EKGka2aV9Tk3jLGvDRsYdIarXFz+FViN0h9YLaK55MLULnjsFY7UIQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283315; c=relaxed/simple; bh=baC+5Y0AsXod56OL6cElErtZM6yuubPxFZSumQXpLWc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nOuqGjf892E+Q4JSifLYXOF8q/UokBCLlw7sRRS+d7fflraZ2hJZKwW+OGh60mUdF2JkyKNxMEeOY+7rX1aSJdG9d0I7/N4BzC03M4cd1vNDR/ON1ILJDsnrls2pRF+dPmrBJnpKVKg1b97ZWOfeiqlgrK+sAGXuAOlPEqTB26w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=O+nPwgY1; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="O+nPwgY1" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1734283312; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=o051YtfIONNUoOhyEifg3AicqGsMQyhVD2bNTrnKX9I=; b=O+nPwgY1zxfoyQFr5iQipV7c57gHVw+Uf1zKjKNgmXOa4L/2BScDyXhWvIryztfhrVxiI5 HJhg3y+cKRguctHYYEnzpCnax5i3xEFBd/U/Cas/8G2Td6yF1/2zqD+FCTG+2BJsTeOMOX fqyxlW6B8dWov3cG0GwCrb+AJJ2w8VY= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-503-ZS95ceNdNam1MuokLHgo3g-1; Sun, 15 Dec 2024 12:21:51 -0500 X-MC-Unique: ZS95ceNdNam1MuokLHgo3g-1 X-Mimecast-MFC-AGG-ID: ZS95ceNdNam1MuokLHgo3g Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 4EF3B19560A3; Sun, 15 Dec 2024 17:21:50 +0000 (UTC) Received: from localhost.localdomain (unknown [10.39.192.51]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 3276F195605A; Sun, 15 Dec 2024 17:21:48 +0000 (UTC) From: Hans de Goede To: Sebastian Reichel Cc: Hans de Goede , linux-pm@vger.kernel.org Subject: [PATCH 6/8] power: supply: adc-battery-helper: Fix reporting capacity > 100% Date: Sun, 15 Dec 2024 18:21:29 +0100 Message-ID: <20241215172133.178460-7-hdegoede@redhat.com> In-Reply-To: <20241215172133.178460-1-hdegoede@redhat.com> References: <20241215172133.178460-1-hdegoede@redhat.com> Precedence: bulk X-Mailing-List: linux-pm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 The ocv_capacity_tbl[] to map the ocv voltage to capacity goes up to 4.3V for LiPo High Voltage (LiHV) cells. For non HV cells the code assumes that the estimated ocv value never comes above 4.2V, but there might be cases where it does go above 4.2V leading to adc_battery_helper_get_capacity() reporting a capacity above 100%. Do not use the table entries with a voltage above 4.2V for non HV cells to avoid this use. Signed-off-by: Hans de Goede --- drivers/power/supply/adc-battery-helper.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/adc-battery-helper.c b/drivers/power/supply/adc-battery-helper.c index 1917e92ab1eb..104d9a888486 100644 --- a/drivers/power/supply/adc-battery-helper.c +++ b/drivers/power/supply/adc-battery-helper.c @@ -115,7 +115,7 @@ static int adc_battery_helper_get_capacity(struct adc_battery_helper *help) 4250000, 4300000, }; - int i, ocv_diff, ocv_step; + int i, array_len, ocv_diff, ocv_step; if (help->ocv_avg_uv < ocv_capacity_tbl[0]) return 0; @@ -123,7 +123,16 @@ static int adc_battery_helper_get_capacity(struct adc_battery_helper *help) if (help->status == POWER_SUPPLY_STATUS_FULL) return 100; - for (i = 1; i < ARRAY_SIZE(ocv_capacity_tbl); i++) { + /* + * Ignore the array-entries with voltages > 4200000 for non High-Voltage + * batteries to avoid reporting values > 100% in the non HV case. + */ + if (help->psy->battery_info->constant_charge_voltage_max_uv >= 4300000) + array_len = ARRAY_SIZE(ocv_capacity_tbl); + else + array_len = ARRAY_SIZE(ocv_capacity_tbl) - 2; + + for (i = 1; i < array_len; i++) { if (help->ocv_avg_uv > ocv_capacity_tbl[i]) continue; From patchwork Sun Dec 15 17:21:30 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 851179 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ED4CD19993D for ; Sun, 15 Dec 2024 17:21:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283319; cv=none; b=d0rt66RJylpVU4hTJOeGILUKAh54scGsa5CdUUtUXAheK3sNL6rGYHjxof8uzeW6oGUo1WtMuJdNFq7wYCoshsz3qQaYdgBdjLYN3uZKM8LjjT2Yf887+7L46vz7PpVwV1QfjNlFHahdaBrMe6NL1Lx6YZvxhXFXWI8+CD5ay24= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283319; c=relaxed/simple; bh=Rpa9os+HMb6BZmvFQKYMNOceMWBJlqmibDiUH7sqII0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XM2lT4AN64nIc42lFnYhvqL78ZawCKEl69PDAwjNgl6J/CVQ03WxM7Wb6iZz/slqHiqg7PUvTWoF6nJXRmN+38OjcXgTabw4HuZeUO8p7iWIQqdufiDV+IPzytLVgWdxSboP3pQ0TVDYKPtTOCztGH4qFO5YVz1QMLsWAhnH+gQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=D0zMq8LG; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="D0zMq8LG" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1734283316; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=aKUvUq/LS0E73eqmlsiioHSTEqxYzpVFnKaC48IsVGw=; b=D0zMq8LGzhCz/6LWjK0RvScYHM04HQPOLnGKO3PRW9w0pXmQCRA4OKiOZYhTBl0JlDaVWl kIgOVpD35xkaSkpzJ1NRuoPhtapGp1Ir53w/8KyZJ2ZqHz5U+deK1e25LtxCt2LETE2fkr FlnYjilJgkaC9Dw12s4LW9MldHHRUjc= Received: from mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-60-NyHVTjE2NHuqnKJ7OpypiA-1; Sun, 15 Dec 2024 12:21:52 -0500 X-MC-Unique: NyHVTjE2NHuqnKJ7OpypiA-1 X-Mimecast-MFC-AGG-ID: NyHVTjE2NHuqnKJ7OpypiA Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id F07D319560A5; Sun, 15 Dec 2024 17:21:51 +0000 (UTC) Received: from localhost.localdomain (unknown [10.39.192.51]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id B4E7E195605A; Sun, 15 Dec 2024 17:21:50 +0000 (UTC) From: Hans de Goede To: Sebastian Reichel Cc: Hans de Goede , linux-pm@vger.kernel.org Subject: [PATCH 7/8] power: supply: adc-battery-helper: Add support for optional charge_finished GPIO Date: Sun, 15 Dec 2024 18:21:30 +0100 Message-ID: <20241215172133.178460-8-hdegoede@redhat.com> In-Reply-To: <20241215172133.178460-1-hdegoede@redhat.com> References: <20241215172133.178460-1-hdegoede@redhat.com> Precedence: bulk X-Mailing-List: linux-pm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 Charger ICs often have a status pin which indicates when the charger has finished charging the battery. Sometimes the status of this pin can be read over a GPIO. Add support for optionally reading a charge-finished GPIO and when available use this to determine when to return POWER_SUPPLY_STATUS_FULL. Signed-off-by: Hans de Goede --- drivers/power/supply/adc-battery-helper.c | 18 +++++++++++++++--- drivers/power/supply/adc-battery-helper.h | 5 ++++- drivers/power/supply/ug3105_battery.c | 2 +- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/drivers/power/supply/adc-battery-helper.c b/drivers/power/supply/adc-battery-helper.c index 104d9a888486..f01976b95466 100644 --- a/drivers/power/supply/adc-battery-helper.c +++ b/drivers/power/supply/adc-battery-helper.c @@ -48,6 +48,7 @@ #include #include +#include #include #include #include @@ -77,8 +78,17 @@ static int adc_battery_helper_get_status(struct adc_battery_helper *help) if (help->curr_ua < -CURR_HYST_UA) return POWER_SUPPLY_STATUS_DISCHARGING; - if (help->supplied && help->ocv_avg_uv > full_uv) - return POWER_SUPPLY_STATUS_FULL; + if (help->supplied) { + bool full; + + if (help->charge_finished) + full = gpiod_get_value_cansleep(help->charge_finished); + else + full = help->ocv_avg_uv > full_uv; + + if (full) + return POWER_SUPPLY_STATUS_FULL; + } return POWER_SUPPLY_STATUS_NOT_CHARGING; } @@ -310,13 +320,15 @@ static void adc_battery_helper_start_work(struct adc_battery_helper *help) } int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy, - adc_battery_helper_get_func get_voltage_and_current_now) + adc_battery_helper_get_func get_voltage_and_current_now, + struct gpio_desc *charge_finished_gpio) { struct device *dev = psy->dev.parent; int ret; help->psy = psy; help->get_voltage_and_current_now = get_voltage_and_current_now; + help->charge_finished = charge_finished_gpio; ret = devm_mutex_init(dev, &help->lock); if (ret) diff --git a/drivers/power/supply/adc-battery-helper.h b/drivers/power/supply/adc-battery-helper.h index 2a13c64eadf1..3cb73d2818f0 100644 --- a/drivers/power/supply/adc-battery-helper.h +++ b/drivers/power/supply/adc-battery-helper.h @@ -10,6 +10,7 @@ #define ADC_BAT_HELPER_MOV_AVG_WINDOW 8 struct power_supply; +struct gpio_desc; /* * The adc battery helper code needs voltage- and current-now to be sampled as @@ -20,6 +21,7 @@ typedef int (*adc_battery_helper_get_func)(struct power_supply *psy, int *volt, struct adc_battery_helper { struct power_supply *psy; + struct gpio_desc *charge_finished; struct delayed_work work; struct mutex lock; adc_battery_helper_get_func get_voltage_and_current_now; @@ -43,7 +45,8 @@ extern const enum power_supply_property adc_battery_helper_properties[]; #define ADC_HELPER_NUM_PROPERTIES 7 int adc_battery_helper_init(struct adc_battery_helper *help, struct power_supply *psy, - adc_battery_helper_get_func get_voltage_and_current_now); + adc_battery_helper_get_func get_voltage_and_current_now, + struct gpio_desc *charge_finished_gpio); /* * The below functions can be directly used as power-supply / suspend-resume * callbacks. They cast the power_supply_get_drvdata() / dev_get_drvdata() data diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c index 8d4ee8c12db9..0aaff3c4476d 100644 --- a/drivers/power/supply/ug3105_battery.c +++ b/drivers/power/supply/ug3105_battery.c @@ -161,7 +161,7 @@ static int ug3105_probe(struct i2c_client *client) } ret = adc_battery_helper_init(&chip->helper, chip->psy, - ug3105_get_voltage_and_current_now); + ug3105_get_voltage_and_current_now, NULL); if (ret) goto stop; From patchwork Sun Dec 15 17:21:31 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Hans de Goede X-Patchwork-Id: 851345 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C61E21B2180 for ; Sun, 15 Dec 2024 17:21:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283321; cv=none; b=qOfZVWe4XjTqEAB1PimDdU6uBtrDmPChI2MmduX2e9zLxplGZt2pwf8oqO8UhNWdUq+T7Hc0qJo5KyQcmH/REV0u5DsBOa64pOpLDGVVKS+J5HJ6CE7ppJWkd6ZlnWR1P3K+tJtkzDudGJjeSa2vpoMjh+BEOBfk9XAysaQjyBA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734283321; c=relaxed/simple; bh=n4uWf8Oqs+w+CkIxre7V4x9nxf7ocdo7/V9/n74usK4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=NuiF1l4d1lpYMV/H/qCBdtEmJfwWpCq9Bzj3/7Z/AX944DZKZmEViH72vKCo9Z0TDtrxhsmkxA+9OCKUDHFJdEuhwj2U65wQBVigWDxHhzbqy47xp5JHDs/vKgEGSWmgIHZt79ZbsIctf9HFV8kyR2MEgSekDoGPZFpTWhGHbwE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=EccvQabQ; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="EccvQabQ" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1734283317; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=9tAxtJ3N5NU09hYjCxL7GHmC1embXVeswIxss9mPyv4=; b=EccvQabQ8hBWQ17PUjIJ+nUZm1lbtv216ufFpztKjzWw5wzQsIhbkmsnUUHrclFiIvpGWs yD4Bt8lttM6oFZoOPZlNjYlCeb0Hz3WakOA8lMubZ3PwXzzNhEQOfY8nv0yC4Pj1UaC1eo bnqDxyTUZTtr2Gu2V98tJ/5rWNOrC7o= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-70-jeVCCRayP7G1Qld0q69FiA-1; Sun, 15 Dec 2024 12:21:54 -0500 X-MC-Unique: jeVCCRayP7G1Qld0q69FiA-1 X-Mimecast-MFC-AGG-ID: jeVCCRayP7G1Qld0q69FiA Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 9E2DD19560BA; Sun, 15 Dec 2024 17:21:53 +0000 (UTC) Received: from localhost.localdomain (unknown [10.39.192.51]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 4300D195605A; Sun, 15 Dec 2024 17:21:52 +0000 (UTC) From: Hans de Goede To: Sebastian Reichel Cc: Hans de Goede , linux-pm@vger.kernel.org Subject: [PATCH 8/8] power: supply: Add new Intel Dollar Cove TI battery driver Date: Sun, 15 Dec 2024 18:21:31 +0100 Message-ID: <20241215172133.178460-9-hdegoede@redhat.com> In-Reply-To: <20241215172133.178460-1-hdegoede@redhat.com> References: <20241215172133.178460-1-hdegoede@redhat.com> Precedence: bulk X-Mailing-List: linux-pm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 Intel has 2 completely different "Dollar Cove" PMICs for its Bay Trail / Cherry Trail SoCs. One is made by X-Powers and is called the AXP288. The AXP288's builtin charger and fuel-gauge functions are already supported by the axp288_charger / axp288_fuel_gauge drivers. The other "Dollar Cove" PMIC is made by TI and does not have any clear TI denomination, its MFD driver calls it the "Intel Dollar Cove TI PMIC". The Intel Dollar Cove TI PMIC comes with a coulomb-counters with limited functionality which is intended to work together with an always on micro-controller monitoring it for fuel-gauge functionality. Most devices with the Dollar Cove TI PMIC have full-featured fuel-gauge functionality exposed through ACPI with the information coming from either the embedded-controller or a separate full-featured full-gauge IC. But some designs lack this, add a battery-monitoring driver using the PMIC's coulomb-counter combined with the adc-battery-helper for capacity estimation for these designs. Register definitions were taken from kernel/drivers/platform/x86/dc_ti_cc.c from the Acer A1-840 Android kernel source-code archive named: "App. Guide_Acer_20151221_A_A.zip" which is distributed by Acer from the Acer A1-840 support page: https://www.acer.com/us-en/support/product-support/A1-840/downloads Signed-off-by: Hans de Goede --- drivers/power/supply/Kconfig | 12 + drivers/power/supply/Makefile | 1 + drivers/power/supply/intel_dc_ti_battery.c | 429 +++++++++++++++++++++ 3 files changed, 442 insertions(+) create mode 100644 drivers/power/supply/intel_dc_ti_battery.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 6705808b0ec0..496aaf41e6c1 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -225,6 +225,18 @@ config BATTERY_INGENIC This driver can also be built as a module. If so, the module will be called ingenic-battery. +config BATTERY_INTEL_DC_TI + tristate "Intel Bay / Cherry Trail Dollar Cove TI battery driver" + depends on INTEL_SOC_PMIC_CHTDC_TI && INTEL_DC_TI_ADC && IIO && ACPI + select ADC_BATTERY_HELPER + help + Choose this option if you want to monitor battery status on Intel + Bay Trail / Cherry Trail tablets using the Dollar Cove TI PMIC's + coulomb-counter as fuel-gauge. + + To compile this driver as a module, choose M here: the module will be + called intel_dc_ti_battery. + config BATTERY_IPAQ_MICRO tristate "iPAQ Atmel Micro ASIC battery driver" depends on MFD_IPAQ_MICRO diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 025eace9b80d..9ccbddc0ea32 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o obj-$(CONFIG_BATTERY_SAMSUNG_SDI) += samsung-sdi-battery.o obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o obj-$(CONFIG_BATTERY_INGENIC) += ingenic-battery.o +obj-$(CONFIG_BATTERY_INTEL_DC_TI) += intel_dc_ti_battery.o obj-$(CONFIG_BATTERY_IPAQ_MICRO) += ipaq_micro_battery.o obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o diff --git a/drivers/power/supply/intel_dc_ti_battery.c b/drivers/power/supply/intel_dc_ti_battery.c new file mode 100644 index 000000000000..c84245611edc --- /dev/null +++ b/drivers/power/supply/intel_dc_ti_battery.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Battery driver for the coulomb-counter of the Intel Dollar Cove TI PMIC + * + * Note the Intel Dollar Cove TI PMIC coulomb-counter is not a full-featured + * autonomous fuel-gauge. It is intended to work together with an always on + * micro-controller monitoring it. + * + * Since Linux does not monitor coulomb-counter changes while the device + * is off or suspended, voltage based capacity estimation from + * the adc-battery-helper code is used. + * + * Copyright (C) 2024 Hans de Goede + * + * Register definitions and calibration code was taken from + * kernel/drivers/platform/x86/dc_ti_cc.c from the Acer A1-840 Android kernel + * which has the following copyright header: + * + * Copyright (C) 2014 Intel Corporation + * Author: Ramakrishna Pallala + * + * dc_ti_cc.c is part of the Acer A1-840 Android kernel source-code archive + * named: "App. Guide_Acer_20151221_A_A.zip" + * which is distributed by Acer from the Acer A1-840 support page: + * https://www.acer.com/us-en/support/product-support/A1-840/downloads + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adc-battery-helper.h" + +#define DC_TI_PMIC_VERSION_REG 0x00 +#define PMIC_VERSION_A0 0xC0 +#define PMIC_VERSION_A1 0xC1 + +#define DC_TI_VBAT_ZSE_GE_REG 0x53 +#define VBAT_GE GENMASK(3, 0) +#define VBAT_ZSE GENMASK(7, 4) + +#define DC_TI_CC_CNTL_REG 0x60 +#define CC_CNTL_CC_CTR_EN BIT(0) +#define CC_CNTL_CC_CLR_EN BIT(1) +#define CC_CNTL_CC_CAL_EN BIT(2) +#define CC_CNTL_CC_OFFSET_EN BIT(3) +#define CC_CNTL_SMPL_INTVL GENMASK(5, 4) +#define CC_CNTL_SMPL_INTVL_15MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 0) +#define CC_CNTL_SMPL_INTVL_62MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 1) +#define CC_CNTL_SMPL_INTVL_125MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 2) +#define CC_CNTL_SMPL_INTVL_250MS FIELD_PREP(CC_CNTL_SMPL_INTVL, 3) + +#define DC_TI_SMPL_CTR0_REG 0x69 +#define DC_TI_SMPL_CTR1_REG 0x68 +#define DC_TI_SMPL_CTR2_REG 0x67 + +#define DC_TI_CC_OFFSET_HI_REG 0x61 +#define CC_OFFSET_HI_MASK 0x3F +#define DC_TI_CC_OFFSET_LO_REG 0x62 + +#define DC_TI_SW_OFFSET_REG 0x6C + +#define DC_TI_CC_ACC3_REG 0x63 +#define DC_TI_CC_ACC2_REG 0x64 +#define DC_TI_CC_ACC1_REG 0x65 +#define DC_TI_CC_ACC0_REG 0x66 + +#define DC_TI_CC_INTG1_REG 0x6A +#define DC_TI_CC_INTG1_MASK 0x3F +#define DC_TI_CC_INTG0_REG 0x6B + +#define DC_TI_EEPROM_ACCESS_CONTROL 0x88 +#define EEPROM_UNLOCK 0xDA +#define EEPROM_LOCK 0x00 + +#define DC_TI_EEPROM_CC_GAIN_REG 0xF4 +#define CC_TRIM_REVISION GENMASK(3, 0) +#define CC_GAIN_CORRECTION GENMASK(7, 4) + +#define PMIC_VERSION_A0_TRIM_REV 3 +#define PMIC_VERSION_A1_MIN_TRIM_REV 1 + +#define DC_TI_EEPROM_CC_OFFSET_REG 0xFD + +#define DC_TI_EEPROM_CTRL 0xFE +#define EEPROM_BANK0_SEL 0x01 +#define EEPROM_BANK1_SEL 0x02 + +#define SMPL_INTVL_US 15000 +#define SMPL_INTVL_MS (SMPL_INTVL_US / USEC_PER_MSEC) +#define CALIBRATION_TIME_US (10 * SMPL_INTVL_US) +#define SLEEP_SLACK_US 2500 + +/* VBAT GE gain correction is in 0.0015 increments, ZSE is in 1.0 increments */ +#define VBAT_GE_STEP 15 +#define VBAT_GE_DIV 10000 + +/* Vbat ADC scale is 4687.5 ųV / unit */ +#define VBAT_RAW_TO_UVOLT(raw) ((raw) * 46875 / 10) + +/* CC gain correction is in 0.0025 increments */ +#define CC_GAIN_STEP 25 +#define CC_GAIN_DIV 10000 + +/* CC offset is in 0.5 units per 250ms (default sample interval) */ +#define CC_OFFSET_DIV 2 +#define CC_OFFSET_SMPL_INTVL_MS 250 + +/* CC accumulator scale is 366.2 ųCoulumb / unit */ +#define CC_ACC_TO_UA(acc, smpl_ctr) \ + ((acc) * (3662 * MSEC_PER_SEC / 10) / ((smpl_ctr) * SMPL_INTVL_MS)) + +#define DEV_NAME "chtdc_ti_battery" + +struct dc_ti_battery_chip { + /* Must be the first member see adc-battery-helper documentation */ + struct adc_battery_helper helper; + struct device *dev; + struct regmap *regmap; + struct iio_channel *vbat_channel; + struct power_supply *psy; + int vbat_zse; + int vbat_ge; + int cc_gain; + int cc_offset; +}; + +static int dc_ti_battery_get_voltage_and_current_now(struct power_supply *psy, int *volt, int *curr) +{ + struct dc_ti_battery_chip *chip = power_supply_get_drvdata(psy); + s64 cnt_start_usec, now_usec, sleep_usec; + s32 acc, smpl_ctr; + unsigned int reg_val; + int volt_raw, ret; + + /* + * Enable coulomb-counter before reading Vbat from ADC, so that the CC + * samples are from the same time period as the Vbat reading. + */ + ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG, + CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN | CC_CNTL_CC_CTR_EN); + if (ret) + goto out_err; + + cnt_start_usec = ktime_get_ns() / NSEC_PER_USEC; + + /* Read Vbat */ + ret = iio_read_channel_raw(chip->vbat_channel, &volt_raw); + if (ret < 0) + goto out_err; + + /* Apply calibration */ + volt_raw -= chip->vbat_zse; + volt_raw = volt_raw * (VBAT_GE_DIV - chip->vbat_ge * VBAT_GE_STEP) / VBAT_GE_DIV; + *volt = VBAT_RAW_TO_UVOLT(volt_raw); + + /* Sleep at least 3 sample-times + slack to get 3+ CC samples */ + now_usec = ktime_get_ns() / NSEC_PER_USEC; + sleep_usec = 3 * SMPL_INTVL_US + SLEEP_SLACK_US - (now_usec - cnt_start_usec); + if (sleep_usec > 0 && sleep_usec < 1000000) + usleep_range(sleep_usec, sleep_usec + SLEEP_SLACK_US); + + /* + * The PMIC latches the coulomb- and sample-counters upon reading the + * CC_ACC0 register. Reading multiple registers at once is not supported. + * + * Step 1: Read CC_ACC0 - CC_ACC3 + */ + ret = regmap_read(chip->regmap, DC_TI_CC_ACC0_REG, ®_val); + if (ret) + goto out_err; + + acc = reg_val; + + ret = regmap_read(chip->regmap, DC_TI_CC_ACC1_REG, ®_val); + if (ret) + goto out_err; + + acc |= reg_val << 8; + + ret = regmap_read(chip->regmap, DC_TI_CC_ACC2_REG, ®_val); + if (ret) + goto out_err; + + acc |= reg_val << 16; + + ret = regmap_read(chip->regmap, DC_TI_CC_ACC3_REG, ®_val); + if (ret) + goto out_err; + + acc |= reg_val << 24; + + /* Step 2: Read SMPL_CTR0 - SMPL_CTR2 */ + ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR0_REG, ®_val); + if (ret) + goto out_err; + + smpl_ctr = reg_val; + + ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR1_REG, ®_val); + if (ret) + goto out_err; + + smpl_ctr |= reg_val << 8; + + ret = regmap_read(chip->regmap, DC_TI_SMPL_CTR2_REG, ®_val); + if (ret) + goto out_err; + + smpl_ctr |= reg_val << 16; + + /* Disable the coulumb-counter again */ + ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG, + CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN); + if (ret) + goto out_err; + + /* Apply calibration */ + acc -= chip->cc_offset * smpl_ctr * SMPL_INTVL_MS / + (CC_OFFSET_SMPL_INTVL_MS * CC_OFFSET_DIV); + acc = acc * (CC_GAIN_DIV - chip->cc_gain * CC_GAIN_STEP) / CC_GAIN_DIV; + *curr = CC_ACC_TO_UA(acc, smpl_ctr); + + return 0; + +out_err: + dev_err(chip->dev, "IO-error %d communicating with PMIC\n", ret); + return ret; +} + +static const struct power_supply_desc dc_ti_battery_psy_desc = { + .name = "intel_dc_ti_battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = adc_battery_helper_get_property, + .external_power_changed = adc_battery_helper_external_power_changed, + .properties = adc_battery_helper_properties, + .num_properties = ADC_HELPER_NUM_PROPERTIES, +}; + +static int dc_ti_battery_hw_init(struct dc_ti_battery_chip *chip) +{ + u8 pmic_version, cc_trim_rev; + unsigned int reg_val; + int ret, val; + + /* Set sample rate to 15 ms and calibrate the coulomb-counter */ + ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG, + CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN | + CC_CNTL_CC_CAL_EN | CC_CNTL_CC_CTR_EN); + if (ret) + goto out; + + fsleep(CALIBRATION_TIME_US); + + /* Disable coulomb-counter it is only used while getting the current */ + ret = regmap_write(chip->regmap, DC_TI_CC_CNTL_REG, + CC_CNTL_SMPL_INTVL_15MS | CC_CNTL_CC_OFFSET_EN); + if (ret) + goto out; + + ret = regmap_read(chip->regmap, DC_TI_VBAT_ZSE_GE_REG, ®_val); + if (ret) + goto out; + + val = FIELD_GET(VBAT_ZSE, reg_val); + if (val >= 8) + chip->vbat_zse = val - 16; + else + chip->vbat_zse = val; + + val = FIELD_GET(VBAT_GE, reg_val); + if (val >= 8) + chip->vbat_ge = val - 16; + else + chip->vbat_ge = val; + + ret = regmap_read(chip->regmap, DC_TI_PMIC_VERSION_REG, ®_val); + if (ret) + goto out; + + pmic_version = reg_val; + + /* + * As per the PMIC vendor (TI), the calibration offset and gain err + * values are stored in EEPROM Bank 0 and Bank 1 of the PMIC. + * We need to read the stored offset and gain margins and need + * to apply the corrections to the raw coulomb counter value. + */ + + /* Unlock the EEPROM Access */ + ret = regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_UNLOCK); + if (ret) + goto out; + + /* Select Bank 1 to read CC GAIN Err correction */ + ret = regmap_write(chip->regmap, DC_TI_EEPROM_CTRL, EEPROM_BANK1_SEL); + if (ret) + goto out; + + ret = regmap_read(chip->regmap, DC_TI_EEPROM_CC_GAIN_REG, ®_val); + if (ret) + goto out; + + cc_trim_rev = FIELD_GET(CC_TRIM_REVISION, reg_val); + + dev_dbg(chip->dev, "pmic-ver 0x%02x trim-rev %d vbat-zse %d vbat-ge %d\n", + pmic_version, cc_trim_rev, chip->vbat_zse, chip->vbat_ge); + + if (!(pmic_version == PMIC_VERSION_A0 && cc_trim_rev == PMIC_VERSION_A0_TRIM_REV) && + !(pmic_version == PMIC_VERSION_A1 && cc_trim_rev >= PMIC_VERSION_A1_MIN_TRIM_REV)) { + dev_dbg(chip->dev, "unsupported trim-revision, using uncalibrated CC values\n"); + goto out_relock; + } + + chip->cc_gain = 1 - (int)FIELD_GET(CC_GAIN_CORRECTION, reg_val); + + /* Select Bank 0 to read CC OFFSET Correction */ + ret = regmap_write(chip->regmap, DC_TI_EEPROM_CTRL, EEPROM_BANK0_SEL); + if (ret) + goto out_relock; + + ret = regmap_read(chip->regmap, DC_TI_EEPROM_CC_OFFSET_REG, ®_val); + if (ret) + goto out_relock; + + chip->cc_offset = (s8)reg_val; + + dev_dbg(chip->dev, "cc-offset %d cc-gain %d\n", chip->cc_offset, chip->cc_gain); + +out_relock: + /* Re-lock the EEPROM Access */ + regmap_write(chip->regmap, DC_TI_EEPROM_ACCESS_CONTROL, EEPROM_LOCK); +out: + if (ret) + dev_err(chip->dev, "IO-error %d initializing PMIC\n", ret); + + return ret; +} + +static int dc_ti_battery_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent); + struct power_supply_config psy_cfg = {}; + struct fwnode_reference_args args; + struct gpio_desc *charge_finished; + struct dc_ti_battery_chip *chip; + int ret; + + /* On most devices with a Dollar Cove TI the battery is handled by ACPI */ + if (!acpi_quirk_skip_acpi_ac_and_battery()) + return -ENODEV; + + /* ACPI glue code adds a "monitored-battery" fwnode, wait for this */ + ret = fwnode_property_get_reference_args(dev_fwnode(dev), "monitored-battery", + NULL, 0, 0, &args); + if (ret) { + dev_dbg(dev, "fwnode_property_get_ref() ret %d\n", ret); + return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for monitored-battery fwnode\n"); + } + + fwnode_handle_put(args.fwnode); + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = dev; + chip->regmap = pmic->regmap; + + /* + * Note cannot use devm_iio_channel_get because ACPI systems lack + * the device<->channel maps which iio_channel_get will uses when passed + * a non NULL device pointer. + */ + chip->vbat_channel = devm_iio_channel_get(dev, "VBAT"); + if (IS_ERR(chip->vbat_channel)) { + dev_dbg(dev, "devm_iio_channel_get() ret %ld\n", PTR_ERR(chip->vbat_channel)); + return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for VBAT IIO channel\n"); + } + + charge_finished = devm_gpiod_get_optional(dev, "charged", GPIOD_IN); + if (IS_ERR(charge_finished)) + return dev_err_probe(dev, PTR_ERR(charge_finished), "Getting charged GPIO\n"); + + ret = dc_ti_battery_hw_init(chip); + if (ret) + return ret; + + platform_set_drvdata(pdev, chip); + + psy_cfg.drv_data = chip; + chip->psy = devm_power_supply_register(dev, &dc_ti_battery_psy_desc, &psy_cfg); + if (IS_ERR(chip->psy)) + return PTR_ERR(chip->psy); + + return adc_battery_helper_init(&chip->helper, chip->psy, + dc_ti_battery_get_voltage_and_current_now, + charge_finished); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(dc_ti_battery_pm_ops, adc_battery_helper_suspend, + adc_battery_helper_resume, NULL); + +static struct platform_driver dc_ti_battery_driver = { + .driver = { + .name = DEV_NAME, + .pm = pm_sleep_ptr(&dc_ti_battery_pm_ops), + }, + .probe = dc_ti_battery_probe, +}; +module_platform_driver(dc_ti_battery_driver); + +MODULE_ALIAS("platform:" DEV_NAME); +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("Intel Dollar Cove (TI) battery driver"); +MODULE_LICENSE("GPL");