* [PATCH 1/3] power: supply: Add support for the Qualcomm Battery Monitoring System
@ 2018-04-07 13:46 ` Craig Tatlor
0 siblings, 0 replies; 8+ messages in thread
From: Craig Tatlor @ 2018-04-07 13:46 UTC (permalink / raw)
Cc: ctatlor97, linux-arm-msm, Sebastian Reichel, Rob Herring,
Mark Rutland, Mauro Carvalho Chehab, David S. Miller,
Greg Kroah-Hartman, Linus Walleij, Andrew Morton, Randy Dunlap,
linux-pm, devicetree, linux-kernel
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 <ctatlor97@gmail.com>
---
drivers/power/supply/Kconfig | 9 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/qcom_bms.c | 500 ++++++++++++++++++++++++++++++++
3 files changed, 510 insertions(+)
create mode 100644 drivers/power/supply/qcom_bms.c
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 428b426842f4..6c354c37bc55 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -82,6 +82,15 @@ config BATTERY_ACT8945A
Say Y here to enable support for power supply provided by
Active-semi ActivePath ACT8945A charger.
+config BATTERY_BMS
+ tristate "Qualcomm Battery Monitoring System driver"
+ depends on MFD_SPMI_PMIC || COMPILE_TEST
+ depends on OF
+ depends on REGMAP_SPMI
+ help
+ Say Y to include support for the Battery Monitoring hardware
+ found in some Qualcomm PM series PMICs.
+
config BATTERY_CPCAP
tristate "Motorola CPCAP PMIC battery driver"
depends on MFD_CPCAP && IIO
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index e83aa843bcc6..04204174b047 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o
obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
+obj-$(CONFIG_BATTERY_BMS) += qcom_bms.o
obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o
diff --git a/drivers/power/supply/qcom_bms.c b/drivers/power/supply/qcom_bms.c
new file mode 100644
index 000000000000..5aa6e906d1b9
--- /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>
+#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
+#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;
+ 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;
+
+ int ocv_thr_irq;
+ int ocv;
+};
+
+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);
+}
+
+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;
+}
+
+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);
+ }
+
+ if (pcj)
+ return pcj;
+
+ if (pcj_minus_one)
+ return pcj_minus_one;
+
+ return 100;
+}
+
+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;
+}
+
+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);
+
+ 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;
+ }
+
+ 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);
+ 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);
+ return ret;
+ }
+
+ ret = bms_unlock_output_data(di);
+ if (ret < 0)
+ 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);
+
+ 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);
+ fcc = interpolate_fcc(temp_degc, di->fcc_lut);
+
+ capacity_nodiv = ((fcc * ocv_capacity) / 100 - cc) * 100;
+ *capacity = div64_ul(capacity_nodiv, fcc);
+
+ return 0;
+}
+
+
+
+/*
+ * 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;
+
+ return ret;
+}
+
+static enum power_supply_property bms_props[] = {
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+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;
+}
+
+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);
+
+ di->dev = &pdev->dev;
+
+ di->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!di->regmap) {
+ dev_err(di->dev, "Unable to get regmap");
+ return -EINVAL;
+ }
+
+ di->adc = devm_iio_channel_get(&pdev->dev, "temp");
+ if (IS_ERR(di->adc))
+ return PTR_ERR(di->adc);
+
+ ret = of_property_read_u32(di->dev->of_node, "reg", &di->base_addr);
+ if (ret < 0)
+ return ret;
+
+ 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;
+ }
+
+ ret = bms_read_ocv(di, &di->ocv);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to read initial ocv: %d", ret);
+ return ret;
+ }
+
+ di->ocv_thr_irq = platform_get_irq_byname(pdev, "ocv_thr");
+
+ ret = devm_request_irq(di->dev, di->ocv_thr_irq,
+ bms_ocv_thr_irq_handler,
+ IRQF_TRIGGER_RISING,
+ pdev->name, di);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to request handler for ocv threshold IRQ");
+ return ret;
+ }
+
+ spin_lock_init(&di->bms_output_lock);
+
+ 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);
+ if (IS_ERR(di->bat))
+ return PTR_ERR(di->bat);
+
+ return 0;
+}
+
+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");
--
2.17.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 1/3] power: supply: Add support for the Qualcomm Battery Monitoring System
@ 2018-04-07 13:46 ` Craig Tatlor
0 siblings, 0 replies; 8+ messages in thread
From: Craig Tatlor @ 2018-04-07 13:46 UTC (permalink / raw)
Cc: ctatlor97, linux-arm-msm, Sebastian Reichel, Rob Herring,
Mark Rutland, Mauro Carvalho Chehab, David S. Miller,
Greg Kroah-Hartman, Linus Walleij, Andrew Morton, Randy Dunlap,
linux-pm, devicetree, linux-kernel
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 <ctatlor97@gmail.com>
---
drivers/power/supply/Kconfig | 9 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/qcom_bms.c | 500 ++++++++++++++++++++++++++++++++
3 files changed, 510 insertions(+)
create mode 100644 drivers/power/supply/qcom_bms.c
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 428b426842f4..6c354c37bc55 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -82,6 +82,15 @@ config BATTERY_ACT8945A
Say Y here to enable support for power supply provided by
Active-semi ActivePath ACT8945A charger.
+config BATTERY_BMS
+ tristate "Qualcomm Battery Monitoring System driver"
+ depends on MFD_SPMI_PMIC || COMPILE_TEST
+ depends on OF
+ depends on REGMAP_SPMI
+ help
+ Say Y to include support for the Battery Monitoring hardware
+ found in some Qualcomm PM series PMICs.
+
config BATTERY_CPCAP
tristate "Motorola CPCAP PMIC battery driver"
depends on MFD_CPCAP && IIO
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index e83aa843bcc6..04204174b047 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o
obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
+obj-$(CONFIG_BATTERY_BMS) += qcom_bms.o
obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o
diff --git a/drivers/power/supply/qcom_bms.c b/drivers/power/supply/qcom_bms.c
new file mode 100644
index 000000000000..5aa6e906d1b9
--- /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>
+#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
+#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;
+ 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;
+
+ int ocv_thr_irq;
+ int ocv;
+};
+
+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);
+}
+
+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;
+}
+
+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);
+ }
+
+ if (pcj)
+ return pcj;
+
+ if (pcj_minus_one)
+ return pcj_minus_one;
+
+ return 100;
+}
+
+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;
+}
+
+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);
+
+ 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;
+ }
+
+ 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);
+ 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);
+ return ret;
+ }
+
+ ret = bms_unlock_output_data(di);
+ if (ret < 0)
+ 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);
+
+ 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);
+ fcc = interpolate_fcc(temp_degc, di->fcc_lut);
+
+ capacity_nodiv = ((fcc * ocv_capacity) / 100 - cc) * 100;
+ *capacity = div64_ul(capacity_nodiv, fcc);
+
+ return 0;
+}
+
+
+
+/*
+ * 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;
+
+ return ret;
+}
+
+static enum power_supply_property bms_props[] = {
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+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;
+}
+
+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);
+
+ di->dev = &pdev->dev;
+
+ di->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!di->regmap) {
+ dev_err(di->dev, "Unable to get regmap");
+ return -EINVAL;
+ }
+
+ di->adc = devm_iio_channel_get(&pdev->dev, "temp");
+ if (IS_ERR(di->adc))
+ return PTR_ERR(di->adc);
+
+ ret = of_property_read_u32(di->dev->of_node, "reg", &di->base_addr);
+ if (ret < 0)
+ return ret;
+
+ 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;
+ }
+
+ ret = bms_read_ocv(di, &di->ocv);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to read initial ocv: %d", ret);
+ return ret;
+ }
+
+ di->ocv_thr_irq = platform_get_irq_byname(pdev, "ocv_thr");
+
+ ret = devm_request_irq(di->dev, di->ocv_thr_irq,
+ bms_ocv_thr_irq_handler,
+ IRQF_TRIGGER_RISING,
+ pdev->name, di);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to request handler for ocv threshold IRQ");
+ return ret;
+ }
+
+ spin_lock_init(&di->bms_output_lock);
+
+ 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);
+ if (IS_ERR(di->bat))
+ return PTR_ERR(di->bat);
+
+ return 0;
+}
+
+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");
--
2.17.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 2/3] dt-bindings: power: supply: qcom_bms: Add bindings
2018-04-07 13:46 ` Craig Tatlor
@ 2018-04-07 13:46 ` Craig Tatlor
-1 siblings, 0 replies; 8+ messages in thread
From: Craig Tatlor @ 2018-04-07 13:46 UTC (permalink / raw)
Cc: ctatlor97, linux-arm-msm, Sebastian Reichel, Rob Herring,
Mark Rutland, Mauro Carvalho Chehab, David S. Miller,
Greg Kroah-Hartman, Linus Walleij, Andrew Morton, Randy Dunlap,
linux-pm, devicetree, linux-kernel
Add bindings for the Qualcomm battery measurement system.
Signed-off-by: Craig Tatlor <ctatlor97@gmail.com>
---
.../bindings/power/supply/qcom_bms.txt | 93 +++++++++++++++++++
1 file changed, 93 insertions(+)
create mode 100644 Documentation/devicetree/bindings/power/supply/qcom_bms.txt
diff --git a/Documentation/devicetree/bindings/power/supply/qcom_bms.txt b/Documentation/devicetree/bindings/power/supply/qcom_bms.txt
new file mode 100644
index 000000000000..6296399edc09
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/qcom_bms.txt
@@ -0,0 +1,93 @@
+Qualcomm Battery Measurement System
+
+The Qualcomm Battery Measurement System is found inside of Qualcomm PM8941
+PMICs. It provides OCV and coulomb counter registers that allow the kernel
+to infer a capacity level.
+
+Required properties:
+- compatible: Should contain "qcom,pm8941-bms".
+- reg: Specifies the SPMI address and length of the
+ controller's registers.
+- interrupts: OCV threshold interrupt.
+- io-channels: Should contain IIO channel specifier for the
+ ADC channel that reports battery temperature.
+- io-channel-names: Should contain "temp".
+- qcom,fcc-temp-legend: An array containing the temperature, in degC,
+ for each column of the FCC lookup table.
+- qcom,fcc-lut: An array of FCC values in mah, one entry for each
+ temperature defined in in qcom,fcc-temp-legend.
+- qcom,ocv-temp-legend: An array containing the temperature, in degC,
+ for each column of the OCV lookup table.
+- qcom,ocv-capacity-legend: An array containing the capacity for each
+ row of the OCV lookup table.
+- qcom,ocv-lut: An array of OCV values in mV, one entry for each
+ capacity defined in qcom,ocv-capacity-legend.
+
+Example:
+
+ pm8941_vadc: vadc@3100 {
+ compatible = "qcom,spmi-vadc";
+ reg = <0x3100>;
+ interrupts = <0x0 0x31 0x0 IRQ_TYPE_EDGE_RISING>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #io-channel-cells = <1>;
+
+ bat_temp {
+ reg = <VADC_LR_MUX1_BAT_THERM>;
+ };
+ };
+
+ bms@4000 {
+ compatible = "qcom,pm8941-bms";
+ reg = <0x4000>;
+ interrupts = <0x0 0x40 0x4 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "ocv_thr";
+
+ io-channels = <&pm8941_vadc VADC_LR_MUX1_BAT_THERM>;
+ io-channel-names = "temp";
+
+ qcom,fcc-temp-legend = /bits/ 8 <(-10) 0 25 50 65>;
+ qcom,fcc-lut = /bits/ 16 <6010 6070 6680 6780 6670>;
+
+ qcom,ocv-capacity-legend = /bits/ 8 <100 95 90 85
+ 80 75 70 65
+ 60 55 50 45
+ 40 35 30 25
+ 20 15 10 9
+ 8 7 6 5 4
+ 3 2 1 0>;
+
+ qcom,ocv-temp-legend = /bits/ 8 <(-10) 0 25 50 65>;
+ qcom,ocv-lut = /bits/ 16 <4288 4288 4306 4315 4315
+ 4261 4241 4259 4266 4246
+ 4201 4181 4201 4207 4187
+ 4153 4133 4150 4155 4135
+ 4105 4085 4100 4104 4084
+ 4058 4038 4052 4058 4038
+ 4012 3992 4004 4014 3994
+ 3970 3950 3959 3971 3951
+ 3931 3911 3915 3927 3907
+ 3899 3879 3880 3884 3864
+ 3873 3853 3851 3853 3833
+ 3848 3828 3827 3829 3809
+ 3829 3809 3808 3809 3789
+ 3815 3795 3791 3791 3771
+ 3801 3781 3775 3772 3752
+ 3785 3765 3751 3746 3726
+ 3767 3747 3727 3719 3699
+ 3750 3730 3702 3692 3672
+ 3728 3708 3680 3672 3652
+ 3720 3700 3676 3665 3645
+ 3712 3692 3670 3660 3645
+ 3695 3675 3658 3648 3633
+ 3662 3647 3629 3620 3610
+ 3620 3605 3589 3580 3570
+ 3562 3552 3538 3529 3519
+ 3490 3480 3474 3470 3465
+ 3403 3398 3388 3380 3375
+ 3320 3300 3255 3221 3206
+ 3000 3000 3000 3000 3000>;
+ };
+ };
+};
--
2.17.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 2/3] dt-bindings: power: supply: qcom_bms: Add bindings
@ 2018-04-07 13:46 ` Craig Tatlor
0 siblings, 0 replies; 8+ messages in thread
From: Craig Tatlor @ 2018-04-07 13:46 UTC (permalink / raw)
Cc: ctatlor97, linux-arm-msm, Sebastian Reichel, Rob Herring,
Mark Rutland, Mauro Carvalho Chehab, David S. Miller,
Greg Kroah-Hartman, Linus Walleij, Andrew Morton, Randy Dunlap,
linux-pm, devicetree, linux-kernel
Add bindings for the Qualcomm battery measurement system.
Signed-off-by: Craig Tatlor <ctatlor97@gmail.com>
---
.../bindings/power/supply/qcom_bms.txt | 93 +++++++++++++++++++
1 file changed, 93 insertions(+)
create mode 100644 Documentation/devicetree/bindings/power/supply/qcom_bms.txt
diff --git a/Documentation/devicetree/bindings/power/supply/qcom_bms.txt b/Documentation/devicetree/bindings/power/supply/qcom_bms.txt
new file mode 100644
index 000000000000..6296399edc09
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/qcom_bms.txt
@@ -0,0 +1,93 @@
+Qualcomm Battery Measurement System
+
+The Qualcomm Battery Measurement System is found inside of Qualcomm PM8941
+PMICs. It provides OCV and coulomb counter registers that allow the kernel
+to infer a capacity level.
+
+Required properties:
+- compatible: Should contain "qcom,pm8941-bms".
+- reg: Specifies the SPMI address and length of the
+ controller's registers.
+- interrupts: OCV threshold interrupt.
+- io-channels: Should contain IIO channel specifier for the
+ ADC channel that reports battery temperature.
+- io-channel-names: Should contain "temp".
+- qcom,fcc-temp-legend: An array containing the temperature, in degC,
+ for each column of the FCC lookup table.
+- qcom,fcc-lut: An array of FCC values in mah, one entry for each
+ temperature defined in in qcom,fcc-temp-legend.
+- qcom,ocv-temp-legend: An array containing the temperature, in degC,
+ for each column of the OCV lookup table.
+- qcom,ocv-capacity-legend: An array containing the capacity for each
+ row of the OCV lookup table.
+- qcom,ocv-lut: An array of OCV values in mV, one entry for each
+ capacity defined in qcom,ocv-capacity-legend.
+
+Example:
+
+ pm8941_vadc: vadc@3100 {
+ compatible = "qcom,spmi-vadc";
+ reg = <0x3100>;
+ interrupts = <0x0 0x31 0x0 IRQ_TYPE_EDGE_RISING>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #io-channel-cells = <1>;
+
+ bat_temp {
+ reg = <VADC_LR_MUX1_BAT_THERM>;
+ };
+ };
+
+ bms@4000 {
+ compatible = "qcom,pm8941-bms";
+ reg = <0x4000>;
+ interrupts = <0x0 0x40 0x4 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "ocv_thr";
+
+ io-channels = <&pm8941_vadc VADC_LR_MUX1_BAT_THERM>;
+ io-channel-names = "temp";
+
+ qcom,fcc-temp-legend = /bits/ 8 <(-10) 0 25 50 65>;
+ qcom,fcc-lut = /bits/ 16 <6010 6070 6680 6780 6670>;
+
+ qcom,ocv-capacity-legend = /bits/ 8 <100 95 90 85
+ 80 75 70 65
+ 60 55 50 45
+ 40 35 30 25
+ 20 15 10 9
+ 8 7 6 5 4
+ 3 2 1 0>;
+
+ qcom,ocv-temp-legend = /bits/ 8 <(-10) 0 25 50 65>;
+ qcom,ocv-lut = /bits/ 16 <4288 4288 4306 4315 4315
+ 4261 4241 4259 4266 4246
+ 4201 4181 4201 4207 4187
+ 4153 4133 4150 4155 4135
+ 4105 4085 4100 4104 4084
+ 4058 4038 4052 4058 4038
+ 4012 3992 4004 4014 3994
+ 3970 3950 3959 3971 3951
+ 3931 3911 3915 3927 3907
+ 3899 3879 3880 3884 3864
+ 3873 3853 3851 3853 3833
+ 3848 3828 3827 3829 3809
+ 3829 3809 3808 3809 3789
+ 3815 3795 3791 3791 3771
+ 3801 3781 3775 3772 3752
+ 3785 3765 3751 3746 3726
+ 3767 3747 3727 3719 3699
+ 3750 3730 3702 3692 3672
+ 3728 3708 3680 3672 3652
+ 3720 3700 3676 3665 3645
+ 3712 3692 3670 3660 3645
+ 3695 3675 3658 3648 3633
+ 3662 3647 3629 3620 3610
+ 3620 3605 3589 3580 3570
+ 3562 3552 3538 3529 3519
+ 3490 3480 3474 3470 3465
+ 3403 3398 3388 3380 3375
+ 3320 3300 3255 3221 3206
+ 3000 3000 3000 3000 3000>;
+ };
+ };
+};
--
2.17.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH 2/3] dt-bindings: power: supply: qcom_bms: Add bindings
2018-04-07 13:46 ` Craig Tatlor
(?)
@ 2018-04-26 11:39 ` Linus Walleij
-1 siblings, 0 replies; 8+ messages in thread
From: Linus Walleij @ 2018-04-26 11:39 UTC (permalink / raw)
To: Craig Tatlor
Cc: linux-arm-msm, 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
On Sat, Apr 7, 2018 at 3:46 PM, Craig Tatlor <ctatlor97@gmail.com> wrote:
This is very interesting bindings!
> Add bindings for the Qualcomm battery measurement system.
>
> Signed-off-by: Craig Tatlor <ctatlor97@gmail.com>
Please expand on acronyms as requested by Rob.
> +Required properties:
> +- compatible: Should contain "qcom,pm8941-bms".
> +- reg: Specifies the SPMI address and length of the
> + controller's registers.
> +- interrupts: OCV threshold interrupt.
> +- io-channels: Should contain IIO channel specifier for the
> + ADC channel that reports battery temperature.
> +- io-channel-names: Should contain "temp".
These are fine (and good) as bindings go.
> +- qcom,fcc-temp-legend: An array containing the temperature, in degC,
> + for each column of the FCC lookup table.
> +- qcom,fcc-lut: An array of FCC values in mah, one entry for each
> + temperature defined in in qcom,fcc-temp-legend.
> +- qcom,ocv-temp-legend: An array containing the temperature, in degC,
> + for each column of the OCV lookup table.
> +- qcom,ocv-capacity-legend: An array containing the capacity for each
> + row of the OCV lookup table.
> +- qcom,ocv-lut: An array of OCV values in mV, one entry for each
> + capacity defined in qcom,ocv-capacity-legend.
I wonder if these are really Qualcomm-specific.
Can't we just cut the "qcom,*" prefix from all and simply define
these lookup tables in a way (with units) that makes sense to all
kinds of battery capacities?
Maybe spell them out though and avoid the abbreviations.
Just put those in the already existing
Documentation/devicetree/bindings/power/supply/battery.txt
so others can reuse them from there.
You will see that those bindings does not abbreviate but
spells out the natural sciences definitions to it's a bliss to
read for everyone.
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 3/3] MAINTAINERS: Add entry for the Qualcomm BMS
2018-04-07 13:46 ` Craig Tatlor
@ 2018-04-07 13:46 ` Craig Tatlor
-1 siblings, 0 replies; 8+ messages in thread
From: Craig Tatlor @ 2018-04-07 13:46 UTC (permalink / raw)
Cc: ctatlor97, linux-arm-msm, Sebastian Reichel, Rob Herring,
Mark Rutland, Mauro Carvalho Chehab, David S. Miller,
Greg Kroah-Hartman, Linus Walleij, Andrew Morton, Randy Dunlap,
linux-pm, devicetree, linux-kernel
Signed-off-by: Craig Tatlor <ctatlor97@gmail.com>
---
MAINTAINERS | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 0c3ad62c638c..b7ffa9268384 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11605,6 +11605,12 @@ W: http://wireless.kernel.org/en/users/Drivers/ath9k
S: Supported
F: drivers/net/wireless/ath/ath9k/
+QUALCOMM BATTERY MEASUREMENT SYSTEM
+M: Craig Tatlor <ctatlor97@gmail.com>
+L: linux-pm@vger.kernel.org
+S: Maintained
+F: drivers/power/supply/qcom_bms.c
+
QUALCOMM CAMERA SUBSYSTEM DRIVER
M: Todor Tomov <todor.tomov@linaro.org>
L: linux-media@vger.kernel.org
--
2.17.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 3/3] MAINTAINERS: Add entry for the Qualcomm BMS
@ 2018-04-07 13:46 ` Craig Tatlor
0 siblings, 0 replies; 8+ messages in thread
From: Craig Tatlor @ 2018-04-07 13:46 UTC (permalink / raw)
Cc: ctatlor97, linux-arm-msm, Sebastian Reichel, Rob Herring,
Mark Rutland, Mauro Carvalho Chehab, David S. Miller,
Greg Kroah-Hartman, Linus Walleij, Andrew Morton, Randy Dunlap,
linux-pm, devicetree, linux-kernel
Signed-off-by: Craig Tatlor <ctatlor97@gmail.com>
---
MAINTAINERS | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 0c3ad62c638c..b7ffa9268384 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11605,6 +11605,12 @@ W: http://wireless.kernel.org/en/users/Drivers/ath9k
S: Supported
F: drivers/net/wireless/ath/ath9k/
+QUALCOMM BATTERY MEASUREMENT SYSTEM
+M: Craig Tatlor <ctatlor97@gmail.com>
+L: linux-pm@vger.kernel.org
+S: Maintained
+F: drivers/power/supply/qcom_bms.c
+
QUALCOMM CAMERA SUBSYSTEM DRIVER
M: Todor Tomov <todor.tomov@linaro.org>
L: linux-media@vger.kernel.org
--
2.17.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH 1/3] power: supply: Add support for the Qualcomm Battery Monitoring System
2018-04-07 13:46 ` Craig Tatlor
` (2 preceding siblings ...)
(?)
@ 2018-04-26 18:21 ` Bjorn Andersson
-1 siblings, 0 replies; 8+ messages in thread
From: Bjorn Andersson @ 2018-04-26 18:21 UTC (permalink / raw)
To: Craig Tatlor
Cc: linux-arm-msm, Sebastian Reichel, Rob Herring, Mark Rutland,
Mauro Carvalho Chehab, David S. Miller, Greg Kroah-Hartman,
Linus Walleij, Andrew Morton, Randy Dunlap, linux-pm, devicetree,
linux-kernel
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
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2018-04-26 18:21 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH 1/3] power: supply: Add support for the Qualcomm Battery Monitoring System Bjorn Andersson
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.