Linux-Watchdog Archive on lore.kernel.org
 help / color / Atom feed
From: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
To: matti.vaittinen@fi.rohmeurope.com, mazziesaccount@gmail.com
Cc: lee.jones@linaro.org, robh+dt@kernel.org, mark.rutland@arm.com,
	lgirdwood@gmail.com, broonie@kernel.org,
	gregkh@linuxfoundation.org, rafael@kernel.org,
	mturquette@baylibre.com, sboyd@kernel.org,
	linus.walleij@linaro.org, bgolaszewski@baylibre.com,
	sre@kernel.org, a.zummo@towertech.it,
	alexandre.belloni@bootlin.com, wim@linux-watchdog.org,
	linux@roeck-us.net, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org,
	linux-gpio@vger.kernel.org, linux-pm@vger.kernel.org,
	linux-rtc@vger.kernel.org, linux-watchdog@vger.kernel.org,
	mikko.mutanen@fi.rohmeurope.com,
	heikki.haikola@fi.rohmeurope.com
Subject: [RFC PATCH v1 12/13] power: supply: Initial support for ROHM BD70528 PMIC charger block
Date: Tue, 22 Jan 2019 11:48:09 +0200
Message-ID: <2a3decd310a658adce012d32036975c51174be0c.1548149337.git.matti.vaittinen@fi.rohmeurope.com> (raw)
In-Reply-To: <cover.1548149337.git.matti.vaittinen@fi.rohmeurope.com>

ROHM BD70528 PMIC includes battery charger block. Support charger
staus queries and doing few basic settings like input current limit
and charging current.

Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
---
 drivers/power/supply/Kconfig           |   9 +
 drivers/power/supply/Makefile          |   1 +
 drivers/power/supply/bd70528-charger.c | 670 +++++++++++++++++++++++++++++++++
 3 files changed, 680 insertions(+)
 create mode 100644 drivers/power/supply/bd70528-charger.c

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index e901b9879e7e..903c97a67bf0 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -660,4 +660,13 @@ config FUEL_GAUGE_SC27XX
 	 Say Y here to enable support for fuel gauge with SC27XX
 	 PMIC chips.
 
+config CHARGER_BD70528
+	tristate "ROHM bd70528 charger driver"
+	depends on MFD_ROHM_BD70528
+	default n
+	help
+	 Say Y here to enable support for getting battery status
+	 information and altering charger configurations from charger
+	 block of the ROHM BD70528 Power Management IC.
+
 endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index b731c2a9b695..c60387b04bfa 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -87,3 +87,4 @@ obj-$(CONFIG_AXP288_CHARGER)	+= axp288_charger.o
 obj-$(CONFIG_CHARGER_CROS_USBPD)	+= cros_usbpd-charger.o
 obj-$(CONFIG_CHARGER_SC2731)	+= sc2731_charger.o
 obj-$(CONFIG_FUEL_GAUGE_SC27XX)	+= sc27xx_fuel_gauge.o
+obj-$(CONFIG_CHARGER_BD70528)	+= bd70528-charger.o
diff --git a/drivers/power/supply/bd70528-charger.c b/drivers/power/supply/bd70528-charger.c
new file mode 100644
index 000000000000..05d0d6015627
--- /dev/null
+++ b/drivers/power/supply/bd70528-charger.c
@@ -0,0 +1,670 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// Copyright (C) 2018 ROHM Semiconductors
+//
+// power-supply driver for ROHM BD70528 PMIC
+
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/rohm-bd70528.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#define CHG_STAT_SUSPEND	0x0
+#define CHG_STAT_TRICKLE	0x1
+#define CHG_STAT_FAST		0x3
+#define CHG_STAT_TOPOFF		0xe
+#define CHG_STAT_DONE		0xf
+#define CHG_STAT_OTP_TRICKLE	0x10
+#define CHG_STAT_OTP_FAST	0x11
+#define CHG_STAT_OTP_DONE	0x12
+#define CHG_STAT_TSD_TRICKLE	0x20
+#define CHG_STAT_TSD_FAST	0x21
+#define CHG_STAT_TSD_TOPOFF	0x22
+#define CHG_STAT_BAT_ERR	0x7f
+
+static const char *bd70528_charger_model = "BD70528";
+static const char *bd70528_charger_manufacturer = "ROHM Semiconductors";
+
+#define BD_ERR_IRQ_HND(_name_, _wrn_)					\
+static irqreturn_t bd0528_##_name_##_interrupt(int irq, void *arg)	\
+{									\
+	struct power_supply *psy = (struct power_supply *)arg;		\
+									\
+	power_supply_changed(psy);					\
+	dev_err(&psy->dev, (_wrn_));					\
+									\
+	return IRQ_HANDLED;						\
+}
+
+#define BD_INFO_IRQ_HND(_name_, _wrn_)					\
+static irqreturn_t bd0528_##_name_##_interrupt(int irq, void *arg)	\
+{									\
+	struct power_supply *psy = (struct power_supply *)arg;		\
+									\
+	power_supply_changed(psy);					\
+	dev_dbg(&psy->dev, (_wrn_));					\
+									\
+	return IRQ_HANDLED;						\
+}
+
+#define BD_IRQ_HND(_name_) bd0528_##_name_##_interrupt
+
+BD_ERR_IRQ_HND(BAT_OV_DET, "Battery overvoltage detected\n");
+BD_ERR_IRQ_HND(DBAT_DET, "Dead battery detected\n");
+BD_ERR_IRQ_HND(COLD_DET, "Battery cold\n");
+BD_ERR_IRQ_HND(HOT_DET, "Battery hot\n");
+BD_ERR_IRQ_HND(CHG_TSD, "Charger thermal shutdown\n");
+BD_ERR_IRQ_HND(DCIN2_OV_DET, "DCIN2 overvoltage detected\n");
+
+BD_INFO_IRQ_HND(BAT_OV_RES, "Battery voltage back to normal\n");
+BD_INFO_IRQ_HND(COLD_RES, "Battery temperature back to normal\n");
+BD_INFO_IRQ_HND(HOT_RES, "Battery temperature back to normal\n");
+BD_INFO_IRQ_HND(BAT_RMV, "Battery removed\n");
+BD_INFO_IRQ_HND(BAT_DET, "Battery detected\n");
+BD_INFO_IRQ_HND(DCIN2_OV_RES, "DCIN2 voltage back to normal\n");
+BD_INFO_IRQ_HND(DCIN2_RMV, "DCIN2 removed\n");
+BD_INFO_IRQ_HND(DCIN2_DET, "DCIN2 detected\n");
+BD_INFO_IRQ_HND(DCIN1_RMV, "DCIN1 removed\n");
+BD_INFO_IRQ_HND(DCIN1_DET, "DCIN1 detected\n");
+
+struct irq_name_pair {
+	const char *n;
+	irqreturn_t (*h)(int irq, void *arg);
+};
+
+static int bd70528_get_irqs(struct platform_device *pdev,
+			    struct bd70528 *bd70528)
+{
+	int irq, i, ret;
+	unsigned int mask;
+	struct irq_name_pair bd70528_chg_irqs[] = {
+		{ .n = "bd70528-bat-ov-res", .h = BD_IRQ_HND(BAT_OV_RES) },
+		{ .n = "bd70528-bat-ov-det", .h = BD_IRQ_HND(BAT_OV_DET) },
+		{ .n = "bd70528-bat-dead", .h = BD_IRQ_HND(DBAT_DET) },
+		{ .n = "bd70528-bat-warmed", .h = BD_IRQ_HND(COLD_RES) },
+		{ .n = "bd70528-bat-cold", .h = BD_IRQ_HND(COLD_DET) },
+		{ .n = "bd70528-bat-cooled", .h = BD_IRQ_HND(HOT_RES) },
+		{ .n = "bd70528-bat-hot", .h = BD_IRQ_HND(HOT_DET) },
+		{ .n = "bd70528-chg-tshd", .h = BD_IRQ_HND(CHG_TSD) },
+		{ .n = "bd70528-bat-removed", .h = BD_IRQ_HND(BAT_RMV) },
+		{ .n = "bd70528-bat-detected", .h = BD_IRQ_HND(BAT_DET) },
+		{ .n = "bd70528-dcin2-ov-res", .h = BD_IRQ_HND(DCIN2_OV_RES) },
+		{ .n = "bd70528-dcin2-ov-det", .h = BD_IRQ_HND(DCIN2_OV_DET) },
+		{ .n = "bd70528-dcin2-removed", .h = BD_IRQ_HND(DCIN2_RMV) },
+		{ .n = "bd70528-dcin2-detected", .h = BD_IRQ_HND(DCIN2_DET) },
+		{ .n = "bd70528-dcin1-removed", .h = BD_IRQ_HND(DCIN1_RMV) },
+		{ .n = "bd70528-dcin1-detected", .h = BD_IRQ_HND(DCIN1_DET) },
+	};
+
+	for (i = 0; i < ARRAY_SIZE(bd70528_chg_irqs); i++) {
+		irq = platform_get_irq_byname(pdev, bd70528_chg_irqs[i].n);
+		if (irq < 0) {
+			dev_err(&pdev->dev, "Bad IRQ information for %s (%d)\n",
+				bd70528_chg_irqs[i].n, irq);
+			return irq;
+		}
+		ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+						bd70528_chg_irqs[i].h,
+						IRQF_ONESHOT,
+						bd70528_chg_irqs[i].n, bd70528);
+
+		if (ret)
+			return ret;
+	}
+	/*
+	 * BD70528 irq controller is not touching the main mask register.
+	 * So enable the charger block interrupts at main level. We can just
+	 * leave them enabled as irq-controller should disable irqs
+	 * from sub-registers when IRQ is disabled or freed.
+	 */
+	mask = BD70528_REG_INT_BAT1_MASK | BD70528_REG_INT_BAT2_MASK;
+	ret = regmap_update_bits(bd70528->chip.regmap,
+				 BD70528_REG_INT_MAIN, mask, 0);
+	if (ret)
+		dev_err(&pdev->dev, "Failed to enable charger IRQs\n");
+
+	return ret;
+}
+
+static int bd70528_get_charger_status(struct bd70528 *bd70528, int *val)
+{
+	int ret;
+	unsigned int v;
+
+	ret = regmap_read(bd70528->chip.regmap, BD70528_REG_CHG_CURR_STAT, &v);
+	if (ret) {
+		dev_err(bd70528->chip.dev, "Charger state read failure %d\n",
+			ret);
+		return ret;
+	}
+
+	switch (v & BD70528_MASK_CHG_STAT) {
+	case CHG_STAT_SUSPEND:
+	/* Maybe we should check the CHG_TTRI_EN? */
+	case CHG_STAT_OTP_TRICKLE:
+	case CHG_STAT_OTP_FAST:
+	case CHG_STAT_OTP_DONE:
+	case CHG_STAT_TSD_TRICKLE:
+	case CHG_STAT_TSD_FAST:
+	case CHG_STAT_TSD_TOPOFF:
+	case CHG_STAT_BAT_ERR:
+		*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		break;
+	case CHG_STAT_DONE:
+		*val = POWER_SUPPLY_STATUS_FULL;
+		break;
+	case CHG_STAT_TRICKLE:
+	case CHG_STAT_FAST:
+	case CHG_STAT_TOPOFF:
+		*val = POWER_SUPPLY_STATUS_CHARGING;
+		break;
+	default:
+		*val = POWER_SUPPLY_STATUS_UNKNOWN;
+		break;
+	}
+
+	return 0;
+}
+
+static int bd70528_get_charge_type(struct bd70528 *bd70528, int *val)
+{
+	int ret;
+	unsigned int v;
+
+	ret = regmap_read(bd70528->chip.regmap, BD70528_REG_CHG_CURR_STAT, &v);
+	if (ret) {
+		dev_err(bd70528->chip.dev, "Charger state read failure %d\n",
+			ret);
+		return ret;
+	}
+
+	switch (v & BD70528_MASK_CHG_STAT) {
+	case CHG_STAT_TRICKLE:
+		*val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+		break;
+	case CHG_STAT_FAST:
+	case CHG_STAT_TOPOFF:
+		*val = POWER_SUPPLY_CHARGE_TYPE_FAST;
+		break;
+	case CHG_STAT_DONE:
+	case CHG_STAT_SUSPEND:
+	/* Maybe we should check the CHG_TTRI_EN? */
+	case CHG_STAT_OTP_TRICKLE:
+	case CHG_STAT_OTP_FAST:
+	case CHG_STAT_OTP_DONE:
+	case CHG_STAT_TSD_TRICKLE:
+	case CHG_STAT_TSD_FAST:
+	case CHG_STAT_TSD_TOPOFF:
+	case CHG_STAT_BAT_ERR:
+		*val = POWER_SUPPLY_CHARGE_TYPE_NONE;
+		break;
+	default:
+		*val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+		break;
+	}
+
+	return 0;
+}
+
+static int bd70528_get_battery_health(struct bd70528 *bd70528, int *val)
+{
+	int ret;
+	unsigned int v;
+
+	ret = regmap_read(bd70528->chip.regmap, BD70528_REG_CHG_BAT_STAT, &v);
+	if (ret) {
+		dev_err(bd70528->chip.dev, "Battery state read failure %d\n",
+			ret);
+		return ret;
+	}
+	/* No battery? */
+	if (!(v & BD70528_MASK_CHG_BAT_DETECT))
+		*val = POWER_SUPPLY_HEALTH_DEAD;
+	else if (v & BD70528_MASK_CHG_BAT_OVERVOLT)
+		*val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+	else if (v & BD70528_MASK_CHG_BAT_TIMER)
+		*val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+	else
+		*val = POWER_SUPPLY_HEALTH_GOOD;
+
+	return 0;
+}
+
+static int bd70528_get_online(struct bd70528 *bd70528, int *val)
+{
+	int ret;
+	unsigned int v;
+
+	ret = regmap_read(bd70528->chip.regmap, BD70528_REG_CHG_IN_STAT, &v);
+	if (ret) {
+		dev_err(bd70528->chip.dev, "DC1 IN state read failure %d\n",
+			ret);
+		return ret;
+	}
+
+	*val = (v & BD70528_MASK_CHG_DCIN1_UVLO) ? 1 : 0;
+
+	return 0;
+}
+
+static int bd70528_get_present(struct bd70528 *bd70528, int *val)
+{
+	int ret;
+	unsigned int v;
+
+	ret = regmap_read(bd70528->chip.regmap, BD70528_REG_CHG_BAT_STAT, &v);
+	if (ret) {
+		dev_err(bd70528->chip.dev, "Battery state read failure %d\n",
+			ret);
+		return ret;
+	}
+
+	*val = (v & BD70528_MASK_CHG_BAT_DETECT) ? 1 : 0;
+
+	return 0;
+}
+
+struct linear_range {
+	int min;
+	int step;
+	int vals;
+	int low_sel;
+};
+
+struct linear_range current_limit_ranges[] = {
+	{
+		.min = 5,
+		.step = 1,
+		.vals = 36,
+		.low_sel = 0,
+	},
+	{
+		.min = 40,
+		.step = 5,
+		.vals = 5,
+		.low_sel = 0x23,
+	},
+	{
+		.min = 60,
+		.step = 20,
+		.vals = 8,
+		.low_sel = 0x27,
+	},
+	{
+		.min = 200,
+		.step = 50,
+		.vals = 7,
+		.low_sel = 0x2e,
+	}
+};
+
+/*
+ * BD70528 would support setting and getting own charge current/
+ * voltage for low temperatures. The driver currently only reads
+ * the charge current at room temperature. We do set both though.
+ */
+struct linear_range warm_charge_curr[] = {
+	{
+		.min = 10,
+		.step = 10,
+		.vals = 20,
+		.low_sel = 0,
+	},
+	{
+		.min = 200,
+		.step = 25,
+		.vals = 13,
+		.low_sel = 0x13,
+	},
+};
+
+/*
+ * Cold charge current selectors are identical to warm charge current
+ * selectors. The difference is that only smaller currents are available
+ * at cold charge range.
+ */
+#define MAX_COLD_CHG_CURR_SEL 0x15
+#define MAX_WARM_CHG_CURR_SEL 0x1f
+#define MIN_CHG_CURR_SEL 0x0
+
+static int find_value_for_selector_low(struct linear_range *r, int selectors,
+				       unsigned int sel, unsigned int *val)
+{
+	int i;
+
+	for (i = 0; i < selectors; i++) {
+		if (r[i].low_sel <= sel && r[i].low_sel + r[i].vals >= sel) {
+			*val = r[i].min + (sel - r[i].low_sel) * r[i].step;
+			return 0;
+		}
+
+	}
+	return -EINVAL;
+}
+
+/*
+ * For BD70528 voltage/current limits we happily accept any value which
+ * belongs the range. We could check if value matching the selector is
+ * desired by computing the range min + (sel - sel_low) * range step - but
+ * I guess it is enough if we use voltage/current which is closest (below)
+ * the requested?
+ */
+static int find_selector_for_value_low(struct linear_range *r, int selectors,
+				       unsigned int val, unsigned int *sel,
+				       bool *found)
+{
+	int i;
+	int ret = -EINVAL;
+
+	*found = false;
+	for (i = 0; i < selectors; i++) {
+		if (r[i].min <= val) {
+			if (r[i].min + r[i].step * r[i].vals >= val) {
+				*found = true;
+				*sel = r[i].low_sel + (val - r[i].min) /
+				       r[i].step;
+				ret = 0;
+				break;
+			}
+			/*
+			 * If the range max is smaller than requested
+			 * we can set the max supported value from range
+			 */
+			*sel = r[i].low_sel + r[i].vals;
+			ret = 0;
+		}
+	}
+	return ret;
+}
+
+static int get_charge_current(struct bd70528 *bd70528, int *ma)
+{
+	unsigned int sel;
+	int ret;
+
+	ret = regmap_read(bd70528->chip.regmap, BD70528_REG_CHG_CHG_CURR_WARM,
+			  &sel);
+	if (ret) {
+		dev_err(bd70528->chip.dev,
+			"Charge current reading failed (%d)\n", ret);
+		return ret;
+	}
+
+	sel &= BD70528_MASK_CHG_CHG_CURR;
+
+	ret = find_value_for_selector_low(&warm_charge_curr[0],
+					  ARRAY_SIZE(warm_charge_curr), sel,
+					  ma);
+	if (ret) {
+		dev_err(bd70528->chip.dev,
+			"Unknown charge current value 0x%x\n",
+			sel);
+	}
+
+	return ret;
+}
+
+static int get_current_limit(struct bd70528 *bd70528, int *ma)
+{
+	unsigned int sel;
+	int ret;
+
+	ret = regmap_read(bd70528->chip.regmap, BD70528_REG_CHG_DCIN_ILIM,
+			  &sel);
+
+	if (ret) {
+		dev_err(bd70528->chip.dev,
+			"Input current limit reading failed (%d)\n", ret);
+		return ret;
+	}
+
+	sel &= BD70528_MASK_CHG_DCIN_ILIM;
+
+	ret = find_value_for_selector_low(&current_limit_ranges[0],
+					  ARRAY_SIZE(current_limit_ranges), sel,
+					  ma);
+
+	if (ret) {
+		/* Unspecified values mean 500 mA */
+		*ma = 500;
+	}
+	return 0;
+}
+
+static enum power_supply_property bd70528_charger_props[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_CHARGE_TYPE,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+	POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static int bd70528_charger_get_property(struct power_supply *psy,
+					enum power_supply_property psp,
+					union power_supply_propval *val)
+{
+	struct bd70528 *bd70528 = power_supply_get_drvdata(psy);
+	int ret = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		return bd70528_get_charger_status(bd70528, &val->intval);
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		return bd70528_get_charge_type(bd70528, &val->intval);
+	case POWER_SUPPLY_PROP_HEALTH:
+		return bd70528_get_battery_health(bd70528, &val->intval);
+	case POWER_SUPPLY_PROP_PRESENT:
+		return bd70528_get_present(bd70528, &val->intval);
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+		ret = get_current_limit(bd70528, &val->intval);
+		val->intval *= 1000;
+		return ret;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		ret = get_charge_current(bd70528, &val->intval);
+		val->intval *= 1000;
+		return ret;
+	case POWER_SUPPLY_PROP_ONLINE:
+		return bd70528_get_online(bd70528, &val->intval);
+	case POWER_SUPPLY_PROP_MODEL_NAME:
+		val->strval = bd70528_charger_model;
+		return 0;
+	case POWER_SUPPLY_PROP_MANUFACTURER:
+		val->strval = bd70528_charger_manufacturer;
+		return 0;
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+
+static int bd70528_prop_is_writable(struct power_supply *psy,
+				    enum power_supply_property psp)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		return 1;
+	default:
+		break;
+	}
+	return 0;
+}
+
+
+
+static int set_charge_current(struct bd70528 *bd70528, int ma)
+{
+	unsigned int reg;
+	int ret = 0, tmpret;
+	bool found;
+
+	if (ma > 500) {
+		dev_warn(bd70528->chip.dev,
+			 "Requested charge current %u exceed maximum (500mA)\n",
+			 ma);
+		reg = MAX_WARM_CHG_CURR_SEL;
+		goto set;
+	}
+	if (ma < 10) {
+		dev_err(bd70528->chip.dev,
+			"Requested charge current %u smaller than min (10mA)\n",
+			 ma);
+		reg = MIN_CHG_CURR_SEL;
+		ret = -EINVAL;
+		goto set;
+	}
+
+	ret = find_selector_for_value_low(&warm_charge_curr[0],
+					  ARRAY_SIZE(warm_charge_curr), ma,
+					  &reg, &found);
+
+	if (!found) {
+		/* There was a gap in supported values and we hit it */
+		dev_warn(bd70528->chip.dev,
+			 "Unsupported charge current %u mA\n", ma);
+	}
+set:
+
+	tmpret = regmap_update_bits(bd70528->chip.regmap,
+				    BD70528_REG_CHG_CHG_CURR_WARM,
+				    BD70528_MASK_CHG_CHG_CURR, reg);
+	if (tmpret)
+		dev_err(bd70528->chip.dev,
+			"Charge current write failure (%d)\n", tmpret);
+
+	if (reg > MAX_COLD_CHG_CURR_SEL)
+		reg = MAX_COLD_CHG_CURR_SEL;
+
+	if (!tmpret)
+		tmpret = regmap_update_bits(bd70528->chip.regmap,
+					    BD70528_REG_CHG_CHG_CURR_COLD,
+					    BD70528_MASK_CHG_CHG_CURR, reg);
+
+	if (!ret)
+		ret = tmpret;
+
+	return ret;
+}
+
+#define MAX_CURR_LIMIT_SEL 0x34
+#define MIN_CURR_LIMIT_SEL 0x0
+
+static int set_current_limit(struct bd70528 *bd70528, int ma)
+{
+	unsigned int reg;
+	int ret = 0, tmpret;
+	bool found;
+
+	if (ma > 500) {
+		dev_warn(bd70528->chip.dev,
+			 "Requested current limit %u exceed maximum (500mA)\n",
+			 ma);
+		reg = MAX_CURR_LIMIT_SEL;
+		goto set;
+	}
+	if (ma < 5) {
+		dev_err(bd70528->chip.dev,
+			"Requested current limit %u smaller than min (5mA)\n",
+			ma);
+		reg = MIN_CURR_LIMIT_SEL;
+		ret = -EINVAL;
+		goto set;
+	}
+
+	ret = find_selector_for_value_low(&current_limit_ranges[0],
+					  ARRAY_SIZE(current_limit_ranges), ma,
+					  &reg, &found);
+	if (!found) {
+		/* There was a gap in supported values and we hit it ?*/
+		dev_warn(bd70528->chip.dev, "Unsupported current limit %umA\n",
+			 ma);
+	}
+
+set:
+	tmpret = regmap_update_bits(bd70528->chip.regmap,
+				    BD70528_REG_CHG_DCIN_ILIM,
+				    BD70528_MASK_CHG_DCIN_ILIM, reg);
+
+	if (!ret)
+		ret = tmpret;
+
+	return ret;
+}
+
+static int bd70528_charger_set_property(struct power_supply *psy,
+					enum power_supply_property psp,
+					const union power_supply_propval *val)
+{
+	struct bd70528 *bd70528 = power_supply_get_drvdata(psy);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+		return set_current_limit(bd70528, val->intval / 1000);
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		return set_charge_current(bd70528, val->intval / 1000);
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct power_supply_desc bd70528_charger_desc = {
+	.name		= "bd70528-charger",
+	.type		= POWER_SUPPLY_TYPE_BATTERY,
+	.properties	= bd70528_charger_props,
+	.num_properties	= ARRAY_SIZE(bd70528_charger_props),
+	.get_property	= bd70528_charger_get_property,
+	.set_property	= bd70528_charger_set_property,
+	.property_is_writeable	= bd70528_prop_is_writable,
+};
+
+static int bd70528_power_probe(struct platform_device *pdev)
+{
+	struct bd70528 *bd70528, *tmp;
+	struct power_supply *psy;
+	struct power_supply_config cfg = {};
+
+	tmp = dev_get_drvdata(pdev->dev.parent);
+	if (!tmp) {
+		dev_err(&pdev->dev, "No MFD driver data\n");
+		return -EINVAL;
+	}
+	bd70528 = devm_kzalloc(&pdev->dev, sizeof(*bd70528), GFP_KERNEL);
+	if (!bd70528)
+		return -ENOMEM;
+	*bd70528 = *tmp;
+	bd70528->chip.dev = &pdev->dev;
+
+	platform_set_drvdata(pdev, bd70528);
+	cfg.drv_data = bd70528;
+	cfg.of_node = pdev->dev.parent->of_node;
+
+	psy = devm_power_supply_register(&pdev->dev, &bd70528_charger_desc,
+					 &cfg);
+	if (IS_ERR(psy)) {
+		dev_err(&pdev->dev, "failed: power supply register\n");
+		return PTR_ERR(psy);
+	}
+
+	return bd70528_get_irqs(pdev, bd70528);
+}
+
+static struct platform_driver bd70528_power = {
+	.driver = {
+		.name = "bd70528-power"
+	},
+	.probe = bd70528_power_probe,
+};
+
+module_platform_driver(bd70528_power);
+
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
+MODULE_DESCRIPTION("BD70528 power-supply driver");
+MODULE_LICENSE("GPL");
-- 
2.14.3


-- 
Matti Vaittinen
ROHM Semiconductors

~~~ "I don't think so," said Rene Descartes.  Just then, he vanished ~~~

  parent reply index

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-01-22  9:41 [RFC PATCH v1 00/13] support ROHM BD70528 PMIC Matti Vaittinen
2019-01-22  9:42 ` [RFC PATCH v1 01/13] regmap: regmap-irq: Add main status register support Matti Vaittinen
2019-01-23 15:55   ` Mark Brown
2019-01-22  9:42 ` [RFC PATCH v1 02/13] mfd: bd718x7.h split to ROHM common and bd718x7 specific parts Matti Vaittinen
2019-01-22  9:43 ` [RFC PATCH v1 03/13] regulator: bd718x7 use chip specific and generic data structs Matti Vaittinen
2019-01-23 15:51   ` Mark Brown
2019-01-24  7:16     ` Matti Vaittinen
2019-01-24 10:24       ` Mark Brown
2019-01-22  9:44 ` [RFC PATCH v1 04/13] clk: bd718x7: " Matti Vaittinen
2019-01-22  9:44 ` [RFC PATCH v1 05/13] mfd: bd70528: Support ROHM bd70528 PMIC - core Matti Vaittinen
2019-01-22 14:51   ` Guenter Roeck
2019-01-22 16:20     ` Matti Vaittinen
2019-01-22  9:45 ` [RFC PATCH v1 06/13] clk: bd718x7: Support ROHM BD70528 clk block Matti Vaittinen
2019-01-22  9:45 ` [RFC PATCH v1 07/13] devicetree: bindings: Document first ROHM BD70528 bindings Matti Vaittinen
2019-02-23  0:30   ` Rob Herring
2019-02-28  6:49     ` Matti Vaittinen
2019-02-28 15:37       ` Rob Herring
2019-01-22  9:46 ` [RFC PATCH v1 08/13] regulator: bd70528: Support ROHM BD70528 regulator block Matti Vaittinen
2019-01-22  9:46 ` [RFC PATCH v1 09/13] devicetree: bindings: ROHM bd70528 regulator bindings Matti Vaittinen
2019-01-22  9:47 ` [RFC PATCH v1 10/13] gpio: Initial support for ROHM bd70528 GPIO block Matti Vaittinen
2019-01-22  9:47 ` [RFC PATCH v1 11/13] rtc: bd70528: Initial support for ROHM bd70528 RTC Matti Vaittinen
2019-01-22 14:48   ` Guenter Roeck
2019-01-22 16:29     ` Matti Vaittinen
2019-01-22  9:48 ` Matti Vaittinen [this message]
2019-01-22  9:48 ` [RFC PATCH v1 13/13] watchdog: bd70528: Initial support for ROHM BD70528 watchdog block Matti Vaittinen
2019-01-22 15:47   ` Guenter Roeck
2019-01-22 17:10     ` Matti Vaittinen
2019-01-22 17:40       ` Guenter Roeck
2019-01-22 18:03         ` Matti Vaittinen
2019-01-23 17:47           ` Sebastian Reichel
2019-01-24 10:44             ` Matti Vaittinen
2019-01-24 14:37               ` Guenter Roeck

Reply instructions:

You may reply publically 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=2a3decd310a658adce012d32036975c51174be0c.1548149337.git.matti.vaittinen@fi.rohmeurope.com \
    --to=matti.vaittinen@fi.rohmeurope.com \
    --cc=a.zummo@towertech.it \
    --cc=alexandre.belloni@bootlin.com \
    --cc=bgolaszewski@baylibre.com \
    --cc=broonie@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=heikki.haikola@fi.rohmeurope.com \
    --cc=lee.jones@linaro.org \
    --cc=lgirdwood@gmail.com \
    --cc=linus.walleij@linaro.org \
    --cc=linux-clk@vger.kernel.org \
    --cc=linux-gpio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=linux-rtc@vger.kernel.org \
    --cc=linux-watchdog@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=mark.rutland@arm.com \
    --cc=mazziesaccount@gmail.com \
    --cc=mikko.mutanen@fi.rohmeurope.com \
    --cc=mturquette@baylibre.com \
    --cc=rafael@kernel.org \
    --cc=robh+dt@kernel.org \
    --cc=sboyd@kernel.org \
    --cc=sre@kernel.org \
    --cc=wim@linux-watchdog.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

Linux-Watchdog Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-watchdog/0 linux-watchdog/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-watchdog linux-watchdog/ https://lore.kernel.org/linux-watchdog \
		linux-watchdog@vger.kernel.org linux-watchdog@archiver.kernel.org
	public-inbox-index linux-watchdog


Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.linux-watchdog


AGPL code for this site: git clone https://public-inbox.org/ public-inbox