Received: by 10.192.165.148 with SMTP id m20csp4068682imm; Mon, 30 Apr 2018 11:07:11 -0700 (PDT) X-Google-Smtp-Source: AB8JxZor82SFWffSM6Llmy4eoRkTjwJa1gc2TijnR8uk54lEEtP7eoYQ9uPLpiK6JKAlXw6BC9EJ X-Received: by 2002:a65:4d03:: with SMTP id i3-v6mr10662076pgt.452.1525111631782; Mon, 30 Apr 2018 11:07:11 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1525111631; cv=none; d=google.com; s=arc-20160816; b=I6ugio1+nT/8F4Kc04gcg2fJBnbCD6ynrk5rgRCnY/LL8MW6wDgnBQsN/NuCQoVR9d x+6dVm1DBqB7yRZQNivD/DCIjp4fJh+7Utlq1k45KARE6H/wmkIL2C6iQ0PvAgPPSP0V YW+iM1U+HIl8eG4Lgxoxn//ysXG6VBuBsSNvZ6Ok8XkhpURcjaLvDfk507oAtGDCU6sS HX9eOqpHcbKSMr418NRaJpfM4uaUfayr0xZytSihnpHhJDGLHy+ddmW/ULwAsFFDorzK IjxbYdTX3j1P0b4xEIfwMKK3b6aI4N4YVWoDdsCNZ1n45KwHNmwSQRi2S/XSZN6z7YY3 Ma9g== 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=BkU+4XMk3JF1FsAAJuLXzgNfAJlQoT3MtUk8B+Eth+A=; b=BhP/R5YSQiJo8D13+LDb0R5uSAyUd/uGHkVtoRkInWJqE7B3/i/dcwGjAaUVXzFLiI PCHNA0O+ZFl8iB/ApgWKXr+ABesY1t4JOXB2KqGcUFMqGkGJemu9br2AoO97HUXdPQEl WKgLmZ0dZTKs0q71MKzQkTkHXVvB0obTW6RqKWg5kxuLRuXMkld1Si23ybEEZEH+8+J6 0fN7/Eiz3DxS0oRRI2zHMUV8i+QIJrL5UuyEDjOFzbdJ6z/cg4lZT6WvtmeKnhvorOc+ hxj49YTiFB97jXIfyoHyvM9amYNahoSCGYeT0S8MrYYVH+K43Rlrpgtdfvu57ag6ayvJ Q40w== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=K5+720CL; 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=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id c4-v6si6449791pgt.264.2018.04.30.11.06.56; Mon, 30 Apr 2018 11:07:11 -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=@gmail.com header.s=20161025 header.b=K5+720CL; 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=QUARANTINE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754407AbeD3SGl (ORCPT + 99 others); Mon, 30 Apr 2018 14:06:41 -0400 Received: from mail-wm0-f67.google.com ([74.125.82.67]:51931 "EHLO mail-wm0-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753425AbeD3SGi (ORCPT ); Mon, 30 Apr 2018 14:06:38 -0400 Received: by mail-wm0-f67.google.com with SMTP id j4so14638267wme.1; Mon, 30 Apr 2018 11:06:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:in-reply-to:user-agent; bh=BkU+4XMk3JF1FsAAJuLXzgNfAJlQoT3MtUk8B+Eth+A=; b=K5+720CLMETXK4WBT6Ly3fJVgffUGQ3uOW/Mx9lfiIK8Z8gfjeesRXFt1UaCtavesx OuVfduBwf2VHu36k0yxKYKKa6DHY+hGhpc3NArQWzW5qNvF+HdSJqcZ8b3yq6O76UeOx 21CzwEUo8Ee3rvZTzN8t8f/duTflk/VW7O9IP2o+3F4I8JNMn0e6KAF9EKGOMu432ZWL /da2lrKAERHndaNd3kF2PeOw/YrjWrXbQ/bMSg0mfuREelcSlUFUoxH20xTNDd92U84C GyfL5e+ru1jCxgY3yQ7jvWfmTXGG/Xec+l6sJ1Q1EWoh9jYH/P3picbAD+ZaTkG/BeSM t4BQ== 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=BkU+4XMk3JF1FsAAJuLXzgNfAJlQoT3MtUk8B+Eth+A=; b=Gv0THcwU3nYbUCFQhFg9l/p58Vx6kmqaCg002vovARJVQW0jGGDwF6E7jrXdKuRiYs H3wIogCHSQ+NVsfSuFvgZ24ERCDHDp0BWyFwHHZDXi9Msiw5PPKwwQeN0RGLVDE/Q2tc 9q4Dz8JfBj8A92VcLmseymhznNFw9F8lDTTn2ZkBZ1Ek3c1YN2LPMszlwWbN7JUMyq3e 1n6SMJS5gq5VzHcXOyWdNOCGg7+szit58eXamJ3wSFLxEcxd1hwRnMcL0Kufxn1grVDk vt6Ipw7Fap8CRe8+sGSIP1YKZrbC2J6kXQio/l1tTE54TUdRDczwUF1SZvxSBNvYKXT6 r9SQ== X-Gm-Message-State: ALQs6tAc6oDb8O2oEdYoeLiFHYlODcIs5cROfdeMBT9v7f4LizPsnjmR 1KM2Frz5+jZ1aZ+6v8mfDA== X-Received: by 10.28.111.136 with SMTP id c8mr7728299wmi.9.1525111596872; Mon, 30 Apr 2018 11:06:36 -0700 (PDT) Received: from arch (host86-168-6-48.range86-168.btcentralplus.com. [86.168.6.48]) by smtp.gmail.com with ESMTPSA id 19-v6sm19188665wrz.7.2018.04.30.11.06.35 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Mon, 30 Apr 2018 11:06:36 -0700 (PDT) Date: Mon, 30 Apr 2018 19:06:20 +0100 From: Craig Tatlor To: Linus Walleij Cc: Antonio Ospite , Hans Verkuil , Jonathan Cameron , linux-arm-msm@vger.kernel.org, Sebastian Reichel , Rob Herring , Mark Rutland , Mauro Carvalho Chehab , "David S. Miller" , Greg Kroah-Hartman , Andrew Morton , Randy Dunlap , Linux PM list , "open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS" , "linux-kernel@vger.kernel.org" Subject: Re: [PATCH v4 1/3] power: supply: Add support for the Qualcomm Battery Monitoring System Message-ID: <20180430180620.GA4411@arch> References: <20180407135934.26122-1-ctatlor97@gmail.com> <20180407175802.29444-1-ctatlor97@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: User-Agent: Mutt/1.9.5 (2018-04-13) Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Thanks for review, replies inline. On Thu, Apr 26, 2018 at 01:34:00PM +0200, Linus Walleij wrote: > On Sat, Apr 7, 2018 at 7:57 PM, Craig Tatlor wrote: > > Hi Craig! Thanks for your patch! > > > This patch adds a driver for the BMS (Battery Monitoring System) > > block of the PM8941 PMIC, it uses a lookup table defined in the > > device tree to generate a capacity from the BMS supplied OCV, it > > then ammends the coulomb counter to that to increase the accuracy > > of the estimated capacity. > > > > Signed-off-by: Craig Tatlor > > Just some minor remarks. > > NB: I see that you are writing from a private email address > so if you're working as a hobbyist on your precious sparetime > I have lower expectation on how much work you will put into > this, so see it more as suggestions than demands. Yeah, I am a just hobbyist :) > > My overall feedback is that for algorithmic charger what > we need is infrastructure. What is currently piling up in > drivers/power/supply scares me a bit in it's lack of > framework and code reuse. > > It also scares me because this is vital technology dealing > with physical devices and as such really need to have > modularized reusable reviewed code with several users > and deployments. > > Code reuse would include: > > - Mathematical helpers such as interpolation of > values from absolute values or tables > Suggestions below! > - State machines and transitions > - CC/CV alorithms (using the above) > - Many other things > > Not that *I* can make the situation much better, I'm just > sharing my fears, > > > +static s64 sign_extend_s36(uint64_t raw) > > +{ > > + raw = raw & CC_36_BIT_MASK; > > + > > + return (raw >> 35) == 0LL ? > > + raw : (SIGN_EXTEND_36_TO_64_MASK | raw); > > +} > > #include > > Use sign_extend32() Right. > > > +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) > > +{ > > + if (left <= val && val <= right) > > + return 1; > > + > > + return 0; > > +} > > How are these things not library functions? > > Every cell of my brain says this code should be reusable. > > Can you put this in ? > > I bet a million to one that the video people will sooner or later > need linear interpolation and there are more users in the kernel > than drivers/power/, certainly drivers/iio as well. > > If noone else says anything I vote to put at least the linear > interpolation into with a bonus if you > move some of the current users in drivers/power over > while you're at it. I was pretty surprised there wasn't a library function for it aswell, i will add it there. > > > +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); > > + } > > This code really needs some comments to tell what is going on > here. Also I sense that you can break out a smaller function > for interpolation based on table values, such as a function > that would take a standard format of tables, look up where we > are in that table and interpolate from the neighboring values. Yeah, ill add some comments. Tbh i'm not really sure how that would work as I interpolate from values at several different indecies, can you expand ont this? > > People can then later go in and refine the algorithms if they > e.g. want to introduce spline or RMS interpolation instead > and we can get better interpolation for everybody. > > > +static unsigned long interpolate_fcc(int temp, struct bms_fcc_lut fcc_lut) > > +{ > > + 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; > > +} > > So then only this would really remain: pass a table and interpolate. > > > +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; > > + } > > Reuse of regmap is very nice, thanks for doing it this way. > > > +static int bms_read_cc(struct bms_device_info *di, s64 *cc_uah) > > +static void bms_reset_cc(struct bms_device_info *di) > > These indeed need to be mostly hardware-specific so they > are fine. > Thanks :) > > +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; > > + } > > Very nice that you use IIO ADC as a back-end. > > > + temp_degc = (temp + 500) / 1000; > > That deserves a comment I think. Like what the manual > says about this and why the raw temperature is given > like this. > > Maybe you want to use DIV_ROUND_CLOSEST() > or DIV_ROUND_UP() from instead > of just "/"? > > Maybe you want to use that in other places too like > the below divisions. That was just converting it to celcius, adding 500 to round it. I'll skip that and make bindings use millicelcius. > > > + 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); > > + fcc = interpolate_fcc(temp_degc, di->fcc_lut); > > + > > + capacity_nodiv = ((fcc * ocv_capacity) / 100 - cc) * 100; > > + *capacity = div64_ul(capacity_nodiv, fcc); > > So I guess you fit the capacity between 0..100, please > add some comment on what's going on here. Basically, 1. Read coulomb counter delta since last ocv update 2. Interpolate open circuit voltage (ocv) and full charge capacity (fcc) 3. Math just ammends the coulomb counter delta to the last taken ocv reading, Used the nodiv variable to make it a bit more but i could probably just take that out. Will add something like this in code. > > > +static irqreturn_t bms_ocv_thr_irq_handler(int irq, void *dev_id) > > +{ > > + struct bms_device_info *di = dev_id; > > + > > + if (bms_read_ocv(di, &di->ocv) < 0) > > + return IRQ_HANDLED; > > + > > + bms_reset_cc(di); > > + return IRQ_HANDLED; > > +} > > So that is a coloumb counter interrupt? Please add some > comment on when this gets called. Is it called whenever > a coloumb is added/removed from the battery? Its called whenever a new open circuit voltage reading happens, which only occurs when device us using very little power (probably below 10mA). The coulomb counter is reset because its kept as a delta. > > > + ret = of_property_read_u8_array(di->dev->of_node, > > + "qcom,ocv-temp-legend", > > + (u8 *)di->ocv_lut.temp_legend, > > + TEMPERATURE_COLS); > > + if (ret < 0) { > > + dev_err(di->dev, "no ocv temperature legend found"); > > + return ret; > > + } > > + > > + di->ocv_lut.rows = of_property_read_variable_u8_array(di->dev->of_node, > > + "qcom,ocv-capacity-legend", > > + di->ocv_lut.capacity_legend, 0, > > + MAX_CAPACITY_ROWS); > > + if (di->ocv_lut.rows < 0) { > > + dev_err(di->dev, "no ocv capacity legend found"); > > + return ret; > > + } > > + > > + ret = of_property_read_variable_u16_array(di->dev->of_node, > > + "qcom,ocv-lut", > > + (u16 *)di->ocv_lut.lut, > > + TEMPERATURE_COLS, > > + TEMPERATURE_COLS * > > + MAX_CAPACITY_ROWS); > > + if (ret < 0) { > > + dev_err(di->dev, "no ocv lut array found"); > > + return ret; > > + } > > + > > + ret = of_property_read_u8_array(di->dev->of_node, > > + "qcom,fcc-temp-legend", > > + (u8 *)di->fcc_lut.temp_legend, > > + TEMPERATURE_COLS); > > + if (ret < 0) { > > + dev_err(di->dev, "no fcc temperature legend found"); > > + return ret; > > + } > > + > > + ret = of_property_read_u16_array(di->dev->of_node, > > + "qcom,fcc-lut", > > + di->fcc_lut.lut, > > + TEMPERATURE_COLS); > > + if (ret < 0) { > > + dev_err(di->dev, "no fcc lut array found"); > > + return ret; > > + } > > These tables (that I also suggest to use libraries to parse) > should probably have standardized DT names so other > battery drivers can use the same properties and we can > use the same DT parsing code for all. Okay, any recommendations on names? > > > + ret = bms_read_ocv(di, &di->ocv); > > + if (ret < 0) { > > + dev_err(di->dev, "failed to read initial ocv: %d", ret); > > + return ret; > > + } > > OCV = original coloumb counter value? Nope, open circuit voltage. > > Please expand the acronym somewhere because I get > a bit lost. Will do. > > Overall it is a very nicely coded driver. My worries are > all about code reuse, not code quality per se. Thanks! > > Yours, > Linus Walleij