All of lore.kernel.org
 help / color / mirror / Atom feed
From: Bjorn Andersson <bjorn.andersson@linaro.org>
To: Craig Tatlor <ctatlor97@gmail.com>
Cc: linux-arm-msm@vger.kernel.org, Sebastian Reichel <sre@kernel.org>,
	Rob Herring <robh+dt@kernel.org>,
	Mark Rutland <mark.rutland@arm.com>,
	Mauro Carvalho Chehab <mchehab@kernel.org>,
	"David S. Miller" <davem@davemloft.net>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	Linus Walleij <linus.walleij@linaro.org>,
	Andrew Morton <akpm@linux-foundation.org>,
	Randy Dunlap <rdunlap@infradead.org>,
	linux-pm@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH 1/3] power: supply: Add support for the Qualcomm Battery Monitoring System
Date: Thu, 26 Apr 2018 11:21:34 -0700	[thread overview]
Message-ID: <20180426182134.GM18510@minitux> (raw)
In-Reply-To: <20180407134712.23131-1-ctatlor97@gmail.com>

On Sat 07 Apr 10:57 PDT 2018, Craig Tatlor wrote:

Looks pretty good, just some minor things inline.

[..]
> diff --git a/drivers/power/supply/qcom_bms.c b/drivers/power/supply/qcom_bms.c
> new file mode 100644
> index 000000000000..f31c99c03518
> --- /dev/null
> +++ b/drivers/power/supply/qcom_bms.c
> @@ -0,0 +1,500 @@
> +// SPDX-License-Identifier: GPL
> +
> +/*
> + * Qualcomm Battery Monitoring System driver
> + *
> + * Copyright (C) 2018 Craig Tatlor <ctatlor97@gmail.com>
> + */
> +
> +#include <linux/module.h>
> +#include <linux/param.h>

param.h unused?

> +#include <linux/platform_device.h>
> +#include <linux/power_supply.h>
> +#include <linux/slab.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/regmap.h>
> +#include <linux/irq.h>
> +#include <linux/interrupt.h>
> +#include <linux/iio/consumer.h>
> +
> +#define REG_BMS_OCV_FOR_SOC_DATA0	0x90
> +#define REG_BMS_SHDW_CC_DATA0		0xA8
> +#define REG_BMS_CC_DATA_CTL		0x42
> +#define REG_BMS_CC_CLEAR_CTL		0x4
> +
> +#define BMS_HOLD_OREG_DATA		BIT(0)
> +#define BMS_CLEAR_SHDW_CC		BIT(6)
> +
> +#define CC_36_BIT_MASK			0xFFFFFFFFFLL

How about GENMASK_ULL() ? 

> +#define SIGN_EXTEND_36_TO_64_MASK	(-1LL ^ CC_36_BIT_MASK)
> +
> +#define BMS_CC_READING_RESOLUTION_N	542535
> +#define BMS_CC_READING_RESOLUTION_D	10000
> +#define BMS_CC_READING_TICKS		56
> +#define BMS_SLEEP_CLK_HZ		32764
> +
> +#define SECONDS_PER_HOUR		3600
> +#define TEMPERATURE_COLS		5
> +#define MAX_CAPACITY_ROWS		50
> +
> +/* lookup table for ocv -> capacity conversion */
> +struct bms_ocv_lut {
> +	int rows;
> +	s8 temp_legend[TEMPERATURE_COLS];
> +	u8 capacity_legend[MAX_CAPACITY_ROWS];
> +	u16 lut[MAX_CAPACITY_ROWS][TEMPERATURE_COLS];
> +};
> +
> +/* lookup table for battery temperature -> fcc conversion */
> +struct bms_fcc_lut {
> +	s8 temp_legend[TEMPERATURE_COLS];
> +	u16 lut[TEMPERATURE_COLS];
> +};
> +
> +struct bms_device_info {
> +	struct device *dev;
> +	struct regmap *regmap;
> +	struct power_supply *bat;

bat is local to bms_probe.

> +	struct power_supply_desc bat_desc;
> +	struct bms_ocv_lut ocv_lut;
> +	struct bms_fcc_lut fcc_lut;
> +	struct iio_channel *adc;
> +	spinlock_t bms_output_lock;
> +	int base_addr;

u32, as you're passing a pointer to this into of_property_read_u32().

> +
> +	int ocv_thr_irq;
> +	int ocv;
> +};
> +
> +static s64 sign_extend_s36(uint64_t raw)
> +{
> +	raw = raw & CC_36_BIT_MASK;

raw &= 

> +
> +	return (raw >> 35) == 0LL ?
> +		raw : (SIGN_EXTEND_36_TO_64_MASK | raw);
> +}
> +
> +static unsigned int interpolate(int y0, int x0, int y1, int x1, int x)
> +{
> +	if (y0 == y1 || x == x0)
> +		return y0;
> +	if (x1 == x0 || x == x1)
> +		return y1;
> +
> +	return y0 + ((y1 - y0) * (x - x0) / (x1 - x0));
> +}
> +
> +static unsigned int between(int left, int right, int val)

Return bool and use true/false instead.

> +{
> +	if (left <= val && val <= right)
> +		return 1;
> +
> +	return 0;
> +}
> +
> +static unsigned int interpolate_capacity(int temp, u16 ocv,
> +				struct bms_ocv_lut ocv_lut)
> +{
> +	unsigned int pcj_minus_one = 0, pcj = 0;
> +	int i, j;
> +
> +	for (j = 0; j < TEMPERATURE_COLS; j++)
> +		if (temp <= ocv_lut.temp_legend[j])
> +			break;
> +
> +	if (ocv >= ocv_lut.lut[0][j])
> +		return ocv_lut.capacity_legend[0];
> +
> +	if (ocv <= ocv_lut.lut[ocv_lut.rows - 1][j - 1])
> +		return ocv_lut.capacity_legend[ocv_lut.rows - 1];
> +
> +	for (i = 0; i < ocv_lut.rows - 1; i++) {
> +		if (pcj == 0 && between(ocv_lut.lut[i][j],
> +					ocv_lut.lut[i+1][j], ocv))
> +			pcj = interpolate(ocv_lut.capacity_legend[i],
> +					  ocv_lut.lut[i][j],
> +					  ocv_lut.capacity_legend[i + 1],
> +					  ocv_lut.lut[i+1][j],
> +					  ocv);
> +
> +		if (pcj_minus_one == 0 && between(ocv_lut.lut[i][j-1],
> +						  ocv_lut.lut[i+1][j-1], ocv))
> +			pcj_minus_one = interpolate(ocv_lut.capacity_legend[i],
> +						    ocv_lut.lut[i][j-1],
> +						    ocv_lut.capacity_legend[i + 1],
> +						    ocv_lut.lut[i+1][j-1],
> +						    ocv);
> +
> +		if (pcj && pcj_minus_one)
> +			return interpolate(pcj_minus_one,
> +					   ocv_lut.temp_legend[j-1],
> +					   pcj,
> +					   ocv_lut.temp_legend[j],
> +					   temp);
> +	}

How about finding the four indices first and then do the three
interpolation steps after that?

> +
> +	if (pcj)
> +		return pcj;
> +
> +	if (pcj_minus_one)
> +		return pcj_minus_one;

Do you need these special cases? Is it even possible that we get out
above loop without pcj and pcj_minus_one assigned?

> +
> +	return 100;
> +}
> +
> +static unsigned long interpolate_fcc(int temp, struct bms_fcc_lut fcc_lut)

Pass fcc_lut by reference, as it's "large".

> +{
> +	int i, fcc_mv;
> +
> +	for (i = 0; i < TEMPERATURE_COLS; i++)
> +		if (temp <= fcc_lut.temp_legend[i])
> +			break;
> +
> +	fcc_mv = interpolate(fcc_lut.lut[i - 1],
> +			     fcc_lut.temp_legend[i - 1],
> +			     fcc_lut.lut[i],
> +			     fcc_lut.temp_legend[i],
> +			     temp);
> +
> +	return fcc_mv * 10000;

What does this function return? Why do you multiply with 10k here? A
comment would be nice.

> +}
> +
> +static int bms_lock_output_data(struct bms_device_info *di)
> +{
> +	int ret;
> +
> +	ret = regmap_update_bits(di->regmap, di->base_addr +
> +				 REG_BMS_CC_DATA_CTL,
> +				 BMS_HOLD_OREG_DATA, BMS_HOLD_OREG_DATA);
> +	if (ret < 0) {
> +		dev_err(di->dev, "failed to lock bms output: %d", ret);
> +		return ret;
> +	}
> +
> +	/*
> +	 * Sleep for 100 microseconds here to make sure there has
> +	 * been at least three cycles of the sleep clock so that
> +	 * the registers are correctly locked.
> +	 */
> +	udelay(100);

usleep_range(100, 1000); as this is longer than 10us

See Documentation/timers/timers-howto.txt

> +
> +	return 0;
> +}
> +
> +static int bms_unlock_output_data(struct bms_device_info *di)
> +{
> +	int ret;
> +
> +	ret = regmap_update_bits(di->regmap, di->base_addr +
> +				 REG_BMS_CC_DATA_CTL,
> +				 BMS_HOLD_OREG_DATA, 0);
> +	if (ret < 0) {
> +		dev_err(di->dev, "failed to unlock bms output: %d", ret);
> +		return ret;
> +	}

regmap_update_bits() returns 0 on success, so you can:

ret = regmap_update_bits();
if (ret)
	dev_err();

return ret;

> +
> +	return 0;
> +}
> +
> +static int bms_read_ocv(struct bms_device_info *di, int *ocv)
> +{
> +	unsigned long flags;
> +	int ret;
> +	u16 read_ocv;
> +
> +	spin_lock_irqsave(&di->bms_output_lock, flags);
> +
> +	ret = bms_lock_output_data(di);
> +	if (ret < 0)
> +		goto err_lock;
> +
> +	ret = regmap_bulk_read(di->regmap, di->base_addr +
> +			       REG_BMS_OCV_FOR_SOC_DATA0, &read_ocv, 2);
> +	if (ret < 0) {
> +		dev_err(di->dev, "OCV read failed: %d", ret);

Returning with spinlock and output lock held.

> +		return ret;
> +	}
> +
> +	dev_dbg(di->dev, "read OCV value of: %d", read_ocv);
> +	*ocv = read_ocv;
> +
> +	ret = bms_unlock_output_data(di);
> +
> +err_lock:
> +	spin_unlock_irqrestore(&di->bms_output_lock, flags);
> +
> +	return ret;
> +}
> +
> +static int bms_read_cc(struct bms_device_info *di, s64 *cc_uah)
> +{
> +	unsigned long flags;
> +	int ret;
> +	s64 cc_raw_s36, cc_raw, cc_uv, cc_pvh;
> +
> +	spin_lock_irqsave(&di->bms_output_lock, flags);
> +
> +	ret = bms_lock_output_data(di);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_bulk_read(di->regmap, di->base_addr +
> +			       REG_BMS_SHDW_CC_DATA0,
> +			       &cc_raw_s36, 5);
> +	if (ret < 0) {
> +		dev_err(di->dev, "coulomb counter read failed: %d", ret);

Returning with spinlock and output locked.

> +		return ret;
> +	}
> +
> +	ret = bms_unlock_output_data(di);
> +	if (ret < 0)

Returning with spinlock held.

> +		return ret;
> +
> +	spin_unlock_irqrestore(&di->bms_output_lock, flags);
> +
> +	cc_raw = sign_extend_s36(cc_raw_s36);
> +
> +	/* convert raw to uv */
> +	cc_uv = div_s64(cc_raw * BMS_CC_READING_RESOLUTION_N,
> +			BMS_CC_READING_RESOLUTION_D);
> +
> +	/* convert uv to pvh */
> +	cc_pvh = div_s64(cc_uv * BMS_CC_READING_TICKS * 100000,
> +			 BMS_SLEEP_CLK_HZ * SECONDS_PER_HOUR) * 10;
> +
> +	/* divide by impedance */
> +	*cc_uah = div_s64(cc_pvh, 10000);
> +
> +	dev_dbg(di->dev, "read coulomb counter value of: %lld", *cc_uah);
> +
> +	return 0;
> +}
> +
> +static void bms_reset_cc(struct bms_device_info *di)
> +{
> +	int ret;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&di->bms_output_lock, flags);
> +
> +	ret = regmap_update_bits(di->regmap, di->base_addr +
> +				 REG_BMS_CC_CLEAR_CTL,
> +				 BMS_CLEAR_SHDW_CC,
> +				 BMS_CLEAR_SHDW_CC);
> +	if (ret < 0) {
> +		dev_err(di->dev, "coulomb counter reset failed: %d", ret);
> +		goto err_lock;
> +	}
> +
> +	/* wait two sleep cycles for cc to reset */
> +	udelay(100);

usleep_range(100, 1000);


Perhaps you can make the irq handler threaded, so that you don't have to
spend so long time with irqs disabled.

> +
> +	ret = regmap_update_bits(di->regmap, di->base_addr +
> +				 REG_BMS_CC_CLEAR_CTL,
> +				 BMS_CLEAR_SHDW_CC, 0);
> +	if (ret < 0)
> +		dev_err(di->dev, "coulomb counter re-enable failed: %d", ret);
> +
> +err_lock:
> +	spin_unlock_irqrestore(&di->bms_output_lock, flags);
> +}
> +
> +static int bms_calculate_capacity(struct bms_device_info *di, int *capacity)
> +{
> +	unsigned long ocv_capacity, fcc;
> +	int ret, temp, temp_degc;
> +	s64 cc, capacity_nodiv;
> +
> +	ret = iio_read_channel_raw(di->adc, &temp);
> +	if (ret < 0) {
> +		dev_err(di->dev, "failed to read temperature: %d", ret);
> +		return ret;
> +	}
> +
> +	temp_degc = (temp + 500) / 1000;
> +
> +	ret = bms_read_cc(di, &cc);
> +	if (ret < 0) {
> +		dev_err(di->dev, "failed to read coulomb counter: %d", ret);
> +		return ret;
> +	}
> +
> +	ocv_capacity = interpolate_capacity(temp_degc, (di->ocv + 5) / 10,
> +					    di->ocv_lut);

interpolate_capacity returns unsigned int, but ocv_capacity is unsigned
long. Please pick one.

> +	fcc = interpolate_fcc(temp_degc, di->fcc_lut);
> +
> +	capacity_nodiv = ((fcc * ocv_capacity) / 100 - cc) * 100;
> +	*capacity = div64_ul(capacity_nodiv, fcc);
> +
> +	return 0;
> +}
> +
> +
> +

Remove a few empty lines.

> +/*
> + * Return power_supply property
> + */
> +static int bms_get_property(struct power_supply *psy,
> +				   enum power_supply_property psp,
> +				   union power_supply_propval *val)
> +{
> +	struct bms_device_info *di = power_supply_get_drvdata(psy);
> +	int ret;
> +
> +	switch (psp) {
> +	case POWER_SUPPLY_PROP_CAPACITY:
> +		ret = bms_calculate_capacity(di, &val->intval);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	if (val->intval == INT_MAX || val->intval == INT_MIN)
> +		ret = -EINVAL;

Can this happen?

> +
> +	return ret;
> +}
[..]
> +static int bms_probe(struct platform_device *pdev)
> +{
> +	struct power_supply_config psy_cfg = {};
> +	struct bms_device_info *di;
> +	int ret;
> +
> +	di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
> +	if (!di)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, di);

You don't use this, so no need to set it.

> +
[..]
> +
> +	spin_lock_init(&di->bms_output_lock);

Initialize the spinlock before registering the irq handler, just in case
it would fire immediately.

> +
> +	di->bat_desc.name = "bms";
> +	di->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY;
> +	di->bat_desc.properties = bms_props;
> +	di->bat_desc.num_properties = ARRAY_SIZE(bms_props);
> +	di->bat_desc.get_property = bms_get_property;
> +
> +	psy_cfg.drv_data = di;
> +	di->bat = devm_power_supply_register(di->dev, &di->bat_desc, &psy_cfg);

Replace:

> +	if (IS_ERR(di->bat))
> +		return PTR_ERR(di->bat);
> +
> +	return 0;

with:

	return PTR_ERR_OR_ZERO(di->bat);

> +}
> +
> +static const struct of_device_id bms_of_match[] = {
> +	{.compatible = "qcom,pm8941-bms", },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, bms_of_match);
> +
> +static struct platform_driver bms_driver = {
> +	.probe = bms_probe,
> +	.driver = {
> +		.name = "qcom-bms",
> +		.of_match_table = of_match_ptr(bms_of_match),
> +	},
> +};
> +module_platform_driver(bms_driver);
> +
> +MODULE_AUTHOR("Craig Tatlor <ctatlor97@gmail.com>");
> +MODULE_DESCRIPTION("Qualcomm BMS driver");
> +MODULE_LICENSE("GPL");

Regards,
Bjorn

      parent reply	other threads:[~2018-04-26 18:21 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-04-07 13:46 [PATCH 1/3] power: supply: Add support for the Qualcomm Battery Monitoring System Craig Tatlor
2018-04-07 13:46 ` Craig Tatlor
2018-04-07 13:46 ` [PATCH 2/3] dt-bindings: power: supply: qcom_bms: Add bindings Craig Tatlor
2018-04-07 13:46   ` Craig Tatlor
2018-04-26 11:39   ` Linus Walleij
2018-04-07 13:46 ` [PATCH 3/3] MAINTAINERS: Add entry for the Qualcomm BMS Craig Tatlor
2018-04-07 13:46   ` Craig Tatlor
2018-04-26 18:21 ` Bjorn Andersson [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180426182134.GM18510@minitux \
    --to=bjorn.andersson@linaro.org \
    --cc=akpm@linux-foundation.org \
    --cc=ctatlor97@gmail.com \
    --cc=davem@davemloft.net \
    --cc=devicetree@vger.kernel.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=linus.walleij@linaro.org \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=mchehab@kernel.org \
    --cc=rdunlap@infradead.org \
    --cc=robh+dt@kernel.org \
    --cc=sre@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.