Received: by 10.192.165.148 with SMTP id m20csp2467007imm; Thu, 26 Apr 2018 11:28:34 -0700 (PDT) X-Google-Smtp-Source: AB8JxZpz/SKbPS2tWC4HuWqgb3feQ4aErRZykuKOvrlx93klSRkQMyurnwSyuu6D+zc3ImLLD/L0 X-Received: by 2002:a17:902:7c92:: with SMTP id y18-v6mr10247281pll.378.1524767314902; Thu, 26 Apr 2018 11:28:34 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1524767314; cv=none; d=google.com; s=arc-20160816; b=PVYGdppklP7TeJwd6DxaXgjsRxrxEst5a6/hWxYDlBIugoybZ4M6+AWLgGttM9SFa7 6KcENMt0jArzJAOzvCxE/vcjV/pRbuD9V3C9USQr+makLF8YmZfp9Vy6khol6x05UWof 2kqcQdYrjkv/whGZ+/8rKtO4FCDZEi35mWdonJMCT609d2rOzJdvLuKQaV92oS4C7Uyl SLG90JrixXfeApsdN83vYT8UGnvpoVXctMVE8Z8KGhSbUa7ZMKtvJYdkDgVbsGwr7qn4 Y8p88EriPKYPBWMwZphiCw50hzB31Sv2ajifrQnW2cZ6SU61iJraksjeAOKDXc6woNY+ kD/w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:user-agent:in-reply-to :content-disposition:mime-version:references:message-id:subject:cc :to:from:date:dkim-signature:arc-authentication-results; bh=vFKE7CeBmJWdjWH3Mk+bKdXPazlInnyr8udThSzYDzk=; b=ZX/xheH5i0pZZzmal04IYHN0/f+PAVwn93x64C4hqQm3yGHXoAkU8Ke22o+Vb5DTfN fSYMH+q2dx3ecdbXMUcGZ/iDC8JXtM+NNHBt0cZaP3jsMD1djmbH6vw29GbSP1U55bRK eeDQAsppvUnvjvHl7X3wXORsOTESXUGBL1B5U0UUOTt0GnrGSqWTelBEAw7tA7xE6T2U SYl9mbFTzU4mnTPKsbakTwZcA7d80SQV1tMMqWeuhTaNWADrLo2/rW3jjOik88pyCbP7 TJUqJYbCDtMR49O4gjyD4HysOrcsiadVq1zwFtMLYnpvqVLXtvSJ1lt/NHyt4PAXaHp3 3x8A== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=dLWCCLw2; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id y1si16306032pge.198.2018.04.26.11.28.20; Thu, 26 Apr 2018 11:28:34 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=dLWCCLw2; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756884AbeDZSVq (ORCPT + 99 others); Thu, 26 Apr 2018 14:21:46 -0400 Received: from mail-pf0-f196.google.com ([209.85.192.196]:42542 "EHLO mail-pf0-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756986AbeDZSVi (ORCPT ); Thu, 26 Apr 2018 14:21:38 -0400 Received: by mail-pf0-f196.google.com with SMTP id a11so1030055pfn.9 for ; Thu, 26 Apr 2018 11:21:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:in-reply-to:user-agent; bh=vFKE7CeBmJWdjWH3Mk+bKdXPazlInnyr8udThSzYDzk=; b=dLWCCLw2U5C6JoXdIwoacLwz1WOGu7mjDhqNrMBhM96n7YIpnx45csD40p9+YX3NaH Pdb/MKmRSJEu8GWTlwiKZ7vAhLk4zWda6SYknk3K/Px0m0M9ZBFOQ4E7jkScG1ybYpmd j8tYoarbdQvUOZLebQ34O7g5+KtR7PePwvgII= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to:user-agent; bh=vFKE7CeBmJWdjWH3Mk+bKdXPazlInnyr8udThSzYDzk=; b=bAOLGaI6VtBcDn71eDvcEEsBP9eix6nk72tN9znAmucntV4d97f2whTG8k1zXS/b65 XXRuMP9XIJ7Kxl6/EqPhwlVhpuAGjFZGViAIODong+uZf0Kz7ANBvUr5ux9Z4+M/tS4l bzCb3r1rW7JzizXiM9Nrqps/gE/aK5aYvG9KpqU5pHePKluI+ktHvNniN9LuNZWQxldp gdKOFYefiA/n+7N00DIUtPO8UWajVBIl+bvuXuDlkLL4scmWGAm15FeDk7d4GPyhdqTL MmW5L9BHuwim6k7c89LcouwUJvs2GQclh8EATpeMkvcx7o+13xKTYVIkfEbchsH6Mr2W 0GFA== X-Gm-Message-State: ALQs6tBZXRi/GJz9NTsSE1iy82oj4mJCOf1rUMokPt34lqdggh1yGaxA z1tNaqMuDO6MMzBV2MiPeac4Nw== X-Received: by 2002:a17:902:8ec5:: with SMTP id x5-v6mr2024297plo.57.1524766897721; Thu, 26 Apr 2018 11:21:37 -0700 (PDT) Received: from minitux (104-188-17-28.lightspeed.sndgca.sbcglobal.net. [104.188.17.28]) by smtp.gmail.com with ESMTPSA id d8sm33115134pgu.60.2018.04.26.11.21.36 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Thu, 26 Apr 2018 11:21:37 -0700 (PDT) Date: Thu, 26 Apr 2018 11:21:34 -0700 From: Bjorn Andersson To: Craig Tatlor Cc: linux-arm-msm@vger.kernel.org, Sebastian Reichel , Rob Herring , Mark Rutland , Mauro Carvalho Chehab , "David S. Miller" , Greg Kroah-Hartman , Linus Walleij , Andrew Morton , Randy Dunlap , 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 Message-ID: <20180426182134.GM18510@minitux> References: <20180407134712.23131-1-ctatlor97@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20180407134712.23131-1-ctatlor97@gmail.com> User-Agent: Mutt/1.9.2 (2017-12-15) Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 > + */ > + > +#include > +#include param.h unused? > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#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 "); > +MODULE_DESCRIPTION("Qualcomm BMS driver"); > +MODULE_LICENSE("GPL"); Regards, Bjorn