All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 1/2] dt-bindings: power: Add Spreadtrum SC2731 charger documentation
@ 2018-09-03  8:55 Baolin Wang
  2018-09-03  8:55 ` [PATCH v2 2/2] power: supply: Add Spreadtrum SC2731 charger support Baolin Wang
  2018-09-03 10:14 ` [PATCH v2 1/2] dt-bindings: power: Add Spreadtrum SC2731 charger documentation Krzysztof Kozlowski
  0 siblings, 2 replies; 6+ messages in thread
From: Baolin Wang @ 2018-09-03  8:55 UTC (permalink / raw)
  To: sre, robh+dt, mark.rutland
  Cc: linux-pm, devicetree, linux-kernel, yuanjiang.yu, baolin.wang,
	broonie, krzk

This patch adds the binding documentation for Spreadtrum SC2731 charger
device.

Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
---
Changes from v1:
 - Add parent node.
 - Add some battery standard properties.
---
 .../bindings/power/supply/sc2731_charger.txt       |   40 ++++++++++++++++++++
 1 file changed, 40 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/power/supply/sc2731_charger.txt

diff --git a/Documentation/devicetree/bindings/power/supply/sc2731_charger.txt b/Documentation/devicetree/bindings/power/supply/sc2731_charger.txt
new file mode 100644
index 0000000..5266fab
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/sc2731_charger.txt
@@ -0,0 +1,40 @@
+Spreadtrum SC2731 PMIC battery charger binding
+
+Required properties:
+ - compatible: Should be "sprd,sc2731-charger".
+ - reg: Address offset of charger register.
+ - phys: Contains a phandle to the USB phy.
+
+Optional Properties:
+- monitored-battery: phandle of battery characteristics devicetree node.
+  The charger uses the following battery properties:
+- charge-term-current-microamp: current for charge termination phase.
+- constant-charge-voltage-max-microvolt: maximum constant input voltage.
+  See Documentation/devicetree/bindings/power/supply/battery.txt
+
+Example:
+
+	bat: battery {
+		compatible = "simple-battery";
+		charge-term-current-microamp = <120000>;
+		constant-charge-voltage-max-microvolt = <4350000>;
+		......
+	};
+
+	sc2731_pmic: pmic@0 {
+		compatible = "sprd,sc2731";
+		reg = <0>;
+		spi-max-frequency = <26000000>;
+		interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-controller;
+		#interrupt-cells = <2>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		charger@0 {
+			compatible = "sprd,sc2731-charger";
+			reg = <0x0>;
+			phys = <&ssphy>;
+			monitored-battery = <&bat>;
+		};
+	};
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH v2 2/2] power: supply: Add Spreadtrum SC2731 charger support
  2018-09-03  8:55 [PATCH v2 1/2] dt-bindings: power: Add Spreadtrum SC2731 charger documentation Baolin Wang
@ 2018-09-03  8:55 ` Baolin Wang
  2018-09-03 10:20   ` Krzysztof Kozlowski
  2018-09-16 10:17   ` Sebastian Reichel
  2018-09-03 10:14 ` [PATCH v2 1/2] dt-bindings: power: Add Spreadtrum SC2731 charger documentation Krzysztof Kozlowski
  1 sibling, 2 replies; 6+ messages in thread
From: Baolin Wang @ 2018-09-03  8:55 UTC (permalink / raw)
  To: sre, robh+dt, mark.rutland
  Cc: linux-pm, devicetree, linux-kernel, yuanjiang.yu, baolin.wang,
	broonie, krzk

This patch adds the SC2731 PMIC switch charger support.

Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
---
Changes from v1:
 - Remove some redundant head files.
 - Add one mutex to make sure getting the charger status is correct.
 - Fix some coding style issues.
 - Use standard DT properties.
---
 drivers/power/supply/Kconfig          |    7 +
 drivers/power/supply/Makefile         |    1 +
 drivers/power/supply/sc2731_charger.c |  503 +++++++++++++++++++++++++++++++++
 3 files changed, 511 insertions(+)
 create mode 100644 drivers/power/supply/sc2731_charger.c

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index ff6dab0..f27cf07 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -645,4 +645,11 @@ config CHARGER_CROS_USBPD
 	  what is connected to USB PD ports from the EC and converts
 	  that into power_supply properties.
 
+config CHARGER_SC2731
+	tristate "Spreadtrum SC2731 charger driver"
+	depends on MFD_SC27XX_PMIC || COMPILE_TEST
+	help
+	 Say Y here to enable support for battery charging with SC2731
+	 PMIC chips.
+
 endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index a26b402..767105b 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -85,3 +85,4 @@ obj-$(CONFIG_CHARGER_TPS65217)	+= tps65217_charger.o
 obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
 obj-$(CONFIG_AXP288_CHARGER)	+= axp288_charger.o
 obj-$(CONFIG_CHARGER_CROS_USBPD)	+= cros_usbpd-charger.o
+obj-$(CONFIG_CHARGER_SC2731)	+= sc2731_charger.o
diff --git a/drivers/power/supply/sc2731_charger.c b/drivers/power/supply/sc2731_charger.c
new file mode 100644
index 0000000..c92e5fc
--- /dev/null
+++ b/drivers/power/supply/sc2731_charger.c
@@ -0,0 +1,503 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2018 Spreadtrum Communications Inc.
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/usb/phy.h>
+#include <linux/regmap.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+
+/* PMIC global registers definition */
+#define SC2731_CHARGE_STATUS		0xedc
+#define SC2731_CHARGE_FULL		BIT(4)
+#define SC2731_MODULE_EN1		0xc0c
+#define SC2731_CHARGE_EN		BIT(5)
+
+/* SC2731 switch charger registers definition */
+#define SC2731_CHG_CFG0			0x0
+#define SC2731_CHG_CFG1			0x4
+#define SC2731_CHG_CFG2			0x8
+#define SC2731_CHG_CFG3			0xc
+#define SC2731_CHG_CFG4			0x10
+#define SC2731_CHG_CFG5			0x28
+
+/* SC2731_CHG_CFG0 register definition */
+#define SC2731_PRECHG_RNG_SHIFT		11
+#define SC2731_PRECHG_RNG_MASK		GENMASK(12, 11)
+
+#define SC2731_TERMINATION_VOL_MASK	GENMASK(2, 1)
+#define SC2731_TERMINATION_VOL_SHIFT	1
+#define SC2731_TERMINATION_VOL_CAL_MASK	GENMASK(8, 3)
+#define SC2731_TERMINATION_VOL_CAL_SHIFT	3
+#define SC2731_TERMINATION_CUR_MASK	GENMASK(2, 0)
+
+#define SC2731_CC_EN			BIT(13)
+#define SC2731_CHARGER_PD		BIT(0)
+
+/* SC2731_CHG_CFG1 register definition */
+#define SC2731_CUR_MASK			GENMASK(5, 0)
+
+/* SC2731_CHG_CFG5 register definition */
+#define SC2731_CUR_LIMIT_SHIFT		8
+#define SC2731_CUR_LIMIT_MASK		GENMASK(9, 8)
+
+/* Default current definition (unit is mA) */
+#define SC2731_CURRENT_LIMIT_100	100
+#define SC2731_CURRENT_LIMIT_500	500
+#define SC2731_CURRENT_LIMIT_900	900
+#define SC2731_CURRENT_LIMIT_2000	2000
+#define SC2731_CURRENT_PRECHG		450
+#define SC2731_CURRENT_STEP		50
+
+struct sc2731_charger_info {
+	struct device *dev;
+	struct regmap *regmap;
+	struct usb_phy *usb_phy;
+	struct notifier_block usb_notify;
+	struct power_supply *psy_usb;
+	struct mutex lock;
+	bool charging;
+	u32 base;
+};
+
+static void sc2731_charger_stop_charge(struct sc2731_charger_info *info)
+{
+	regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
+			   SC2731_CC_EN, 0);
+
+	regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
+			   SC2731_CHARGER_PD, SC2731_CHARGER_PD);
+}
+
+static int sc2731_charger_start_charge(struct sc2731_charger_info *info)
+{
+	int ret;
+
+	/* Enable charger constant current mode */
+	ret = regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
+				 SC2731_CC_EN, SC2731_CC_EN);
+	if (ret)
+		return ret;
+
+	/* Start charging */
+	return regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
+				  SC2731_CHARGER_PD, 0);
+}
+
+static int sc2731_charger_set_current_limit(struct sc2731_charger_info *info,
+					    u32 limit)
+{
+	u32 val;
+
+	if (limit <= SC2731_CURRENT_LIMIT_100)
+		val = 0;
+	else if (limit <= SC2731_CURRENT_LIMIT_500)
+		val = 3;
+	else if (limit <= SC2731_CURRENT_LIMIT_900)
+		val = 2;
+	else
+		val = 1;
+
+	return regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG5,
+				  SC2731_CUR_LIMIT_MASK,
+				  val << SC2731_CUR_LIMIT_SHIFT);
+}
+
+static int sc2731_charger_set_current(struct sc2731_charger_info *info, u32 cur)
+{
+	u32 val;
+	int ret;
+
+	if (cur > SC2731_CURRENT_LIMIT_2000)
+		cur = SC2731_CURRENT_LIMIT_2000;
+	else if (cur < SC2731_CURRENT_PRECHG)
+		cur = SC2731_CURRENT_PRECHG;
+
+	/* Calculate the step value, each step is 50 mA */
+	val = (cur - SC2731_CURRENT_PRECHG) / SC2731_CURRENT_STEP;
+
+	/* Set pre-charge current as 450 mA */
+	ret = regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
+				 SC2731_PRECHG_RNG_MASK,
+				 0x3 << SC2731_PRECHG_RNG_SHIFT);
+	if (ret)
+		return ret;
+
+	return regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG1,
+				  SC2731_CUR_MASK, val);
+}
+
+static int sc2731_charger_get_status(struct sc2731_charger_info *info)
+{
+	u32 val;
+	int ret;
+
+	ret = regmap_read(info->regmap, SC2731_CHARGE_STATUS, &val);
+	if (ret)
+		return ret;
+
+	if (val & SC2731_CHARGE_FULL)
+		return POWER_SUPPLY_STATUS_FULL;
+
+	return POWER_SUPPLY_STATUS_CHARGING;
+}
+
+static int sc2731_charger_get_current(struct sc2731_charger_info *info,
+				      u32 *cur)
+{
+	int ret;
+	u32 val;
+
+	ret = regmap_read(info->regmap, info->base + SC2731_CHG_CFG1, &val);
+	if (ret)
+		return ret;
+
+	val &= SC2731_CUR_MASK;
+	*cur = val * SC2731_CURRENT_STEP + SC2731_CURRENT_PRECHG;
+
+	return 0;
+}
+
+static int sc2731_charger_get_current_limit(struct sc2731_charger_info *info,
+					    u32 *cur)
+{
+	int ret;
+	u32 val;
+
+	ret = regmap_read(info->regmap, info->base + SC2731_CHG_CFG5, &val);
+	if (ret)
+		return ret;
+
+	val = (val & SC2731_CUR_LIMIT_MASK) >> SC2731_CUR_LIMIT_SHIFT;
+
+	switch (val) {
+	case 0:
+		*cur = SC2731_CURRENT_LIMIT_100;
+		break;
+
+	case 1:
+		*cur = SC2731_CURRENT_LIMIT_2000;
+		break;
+
+	case 2:
+		*cur = SC2731_CURRENT_LIMIT_900;
+		break;
+
+	case 3:
+		*cur = SC2731_CURRENT_LIMIT_500;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int
+sc2731_charger_usb_set_property(struct power_supply *psy,
+				enum power_supply_property psp,
+				const union power_supply_propval *val)
+{
+	struct sc2731_charger_info *info = power_supply_get_drvdata(psy);
+	int ret;
+
+	mutex_lock(&info->lock);
+
+	if (!info->charging) {
+		mutex_unlock(&info->lock);
+		return -ENODEV;
+	}
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		ret = sc2731_charger_set_current(info, val->intval);
+		if (ret < 0)
+			dev_err(info->dev, "set charge current failed\n");
+		break;
+
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+		ret = sc2731_charger_set_current_limit(info, val->intval);
+		if (ret < 0)
+			dev_err(info->dev, "set input current limit failed\n");
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	mutex_unlock(&info->lock);
+	return ret;
+}
+
+static int sc2731_charger_usb_get_property(struct power_supply *psy,
+					   enum power_supply_property psp,
+					   union power_supply_propval *val)
+{
+	struct sc2731_charger_info *info = power_supply_get_drvdata(psy);
+	int ret = 0;
+	u32 cur;
+
+	mutex_lock(&info->lock);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		if (info->charging)
+			val->intval = sc2731_charger_get_status(info);
+		else
+			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		break;
+
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		if (!info->charging) {
+			val->intval = 0;
+		} else {
+			ret = sc2731_charger_get_current(info, &cur);
+			if (ret)
+				goto out;
+
+			val->intval = cur;
+		}
+		break;
+
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+		if (!info->charging) {
+			val->intval = 0;
+		} else {
+			ret = sc2731_charger_get_current_limit(info, &cur);
+			if (ret)
+				goto out;
+
+			val->intval = cur;
+		}
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+out:
+	mutex_unlock(&info->lock);
+	return ret;
+}
+
+static int sc2731_charger_property_is_writeable(struct power_supply *psy,
+						enum power_supply_property psp)
+{
+	int ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+		ret = 1;
+		break;
+
+	default:
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static enum power_supply_property sc2731_usb_props[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+};
+
+static const struct power_supply_desc sc2731_charger_desc = {
+	.name			= "sc2731_charger",
+	.type			= POWER_SUPPLY_TYPE_USB,
+	.properties		= sc2731_usb_props,
+	.num_properties		= ARRAY_SIZE(sc2731_usb_props),
+	.get_property		= sc2731_charger_usb_get_property,
+	.set_property		= sc2731_charger_usb_set_property,
+	.property_is_writeable	= sc2731_charger_property_is_writeable,
+};
+
+static int sc2731_charger_usb_change(struct notifier_block *nb,
+				     unsigned long limit, void *data)
+{
+	struct sc2731_charger_info *info =
+		container_of(nb, struct sc2731_charger_info, usb_notify);
+	int ret = 0;
+
+	mutex_lock(&info->lock);
+
+	if (limit > 0) {
+		/* set current limitation and start to charge */
+		ret = sc2731_charger_set_current_limit(info, limit);
+		if (ret)
+			goto out;
+
+		ret = sc2731_charger_set_current(info, limit);
+		if (ret)
+			goto out;
+
+		ret = sc2731_charger_start_charge(info);
+		if (ret)
+			goto out;
+
+		info->charging = true;
+	} else {
+		/* Stop charging */
+		info->charging = false;
+		sc2731_charger_stop_charge(info);
+	}
+
+out:
+	mutex_unlock(&info->lock);
+	return ret;
+}
+
+static int sc2731_charger_hw_init(struct sc2731_charger_info *info)
+{
+	struct power_supply_battery_info bat_info = { };
+	u32 term_currrent, term_voltage, cur_val, vol_val;
+	int ret;
+
+	/* Enable charger module */
+	ret = regmap_update_bits(info->regmap, SC2731_MODULE_EN1,
+				 SC2731_CHARGE_EN, SC2731_CHARGE_EN);
+	if (ret)
+		return ret;
+
+	ret = power_supply_get_battery_info(info->psy_usb, &bat_info);
+	if (ret) {
+		dev_warn(info->dev, "no battery information is supplied\n");
+
+		/*
+		 * If no battery information is supplied, we should set
+		 * default charge termination current to 120 mA, and default
+		 * charge termination voltage to 4.35V.
+		 */
+		cur_val = 0x2;
+		vol_val = 0x1;
+	} else {
+		term_currrent = bat_info.charge_term_current_ua / 1000;
+
+		if (term_currrent <= 90)
+			cur_val = 0;
+		else if (term_currrent >= 265)
+			cur_val = 0x7;
+		else
+			cur_val = ((term_currrent - 90) / 25) + 1;
+
+		term_voltage = bat_info.constant_charge_voltage_max_uv / 1000;
+
+		if (term_voltage > 4500)
+			term_voltage = 4500;
+
+		if (term_voltage > 4200)
+			vol_val = (term_voltage - 4200) / 100;
+		else
+			vol_val = 0;
+	}
+
+	/* Set charge termination current */
+	ret = regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG2,
+				 SC2731_TERMINATION_CUR_MASK, cur_val);
+	if (ret)
+		goto error;
+
+	/* Set charge termination voltage */
+	ret = regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
+				 SC2731_TERMINATION_VOL_MASK |
+				 SC2731_TERMINATION_VOL_CAL_MASK,
+				 (vol_val << SC2731_TERMINATION_VOL_SHIFT) |
+				 (0x6 << SC2731_TERMINATION_VOL_CAL_SHIFT));
+	if (ret)
+		goto error;
+
+	return 0;
+
+error:
+	regmap_update_bits(info->regmap, SC2731_MODULE_EN1, SC2731_CHARGE_EN, 0);
+	return ret;
+}
+
+static int sc2731_charger_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct sc2731_charger_info *info;
+	struct power_supply_config charger_cfg = { };
+	int ret;
+
+	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	mutex_init(&info->lock);
+	info->dev = &pdev->dev;
+
+	info->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!info->regmap) {
+		dev_err(&pdev->dev, "failed to get charger regmap\n");
+		return -ENODEV;
+	}
+
+	ret = of_property_read_u32(np, "reg", &info->base);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to get register address\n");
+		return -ENODEV;
+	}
+
+	charger_cfg.drv_data = info;
+	charger_cfg.of_node = np;
+	info->psy_usb = devm_power_supply_register(&pdev->dev,
+						   &sc2731_charger_desc,
+						   &charger_cfg);
+	if (IS_ERR(info->psy_usb)) {
+		dev_err(&pdev->dev, "failed to register power supply\n");
+		return PTR_ERR(info->psy_usb);
+	}
+
+	ret = sc2731_charger_hw_init(info);
+	if (ret)
+		return ret;
+
+	info->usb_phy = devm_usb_get_phy_by_phandle(&pdev->dev, "phys", 0);
+	if (IS_ERR(info->usb_phy)) {
+		dev_err(&pdev->dev, "failed to find USB phy\n");
+		return PTR_ERR(info->usb_phy);
+	}
+
+	info->usb_notify.notifier_call = sc2731_charger_usb_change;
+	ret = usb_register_notifier(info->usb_phy, &info->usb_notify);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register notifier: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int sc2731_charger_remove(struct platform_device *pdev)
+{
+	struct sc2731_charger_info *info = platform_get_drvdata(pdev);
+
+	usb_unregister_notifier(info->usb_phy, &info->usb_notify);
+
+	return 0;
+}
+
+static const struct of_device_id sc2731_charger_of_match[] = {
+	{ .compatible = "sprd,sc2731-charger", },
+	{ }
+};
+
+static struct platform_driver sc2731_charger_driver = {
+	.driver = {
+		.name = "sc2731-charger",
+		.of_match_table = sc2731_charger_of_match,
+	},
+	.probe = sc2731_charger_probe,
+	.remove = sc2731_charger_remove,
+};
+
+module_platform_driver(sc2731_charger_driver);
+
+MODULE_DESCRIPTION("Spreadtrum SC2731 Charger Driver");
+MODULE_LICENSE("GPL v2");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH v2 1/2] dt-bindings: power: Add Spreadtrum SC2731 charger documentation
  2018-09-03  8:55 [PATCH v2 1/2] dt-bindings: power: Add Spreadtrum SC2731 charger documentation Baolin Wang
  2018-09-03  8:55 ` [PATCH v2 2/2] power: supply: Add Spreadtrum SC2731 charger support Baolin Wang
@ 2018-09-03 10:14 ` Krzysztof Kozlowski
  1 sibling, 0 replies; 6+ messages in thread
From: Krzysztof Kozlowski @ 2018-09-03 10:14 UTC (permalink / raw)
  To: baolin.wang
  Cc: sre, robh+dt, mark.rutland, linux-pm, devicetree, linux-kernel,
	yuanjiang.yu, broonie

On Mon, 3 Sep 2018 at 10:56, Baolin Wang <baolin.wang@linaro.org> wrote:
>
> This patch adds the binding documentation for Spreadtrum SC2731 charger
> device.
>
> Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
> ---
> Changes from v1:
>  - Add parent node.
>  - Add some battery standard properties.
> ---
>  .../bindings/power/supply/sc2731_charger.txt       |   40 ++++++++++++++++++++
>  1 file changed, 40 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/power/supply/sc2731_charger.txt

Reviewed-by: Krzysztof Kozlowski <krzk@kernel.org>

Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v2 2/2] power: supply: Add Spreadtrum SC2731 charger support
  2018-09-03  8:55 ` [PATCH v2 2/2] power: supply: Add Spreadtrum SC2731 charger support Baolin Wang
@ 2018-09-03 10:20   ` Krzysztof Kozlowski
  2018-09-16 10:17   ` Sebastian Reichel
  1 sibling, 0 replies; 6+ messages in thread
From: Krzysztof Kozlowski @ 2018-09-03 10:20 UTC (permalink / raw)
  To: baolin.wang
  Cc: sre, robh+dt, mark.rutland, linux-pm, devicetree, linux-kernel,
	yuanjiang.yu, broonie

On Mon, 3 Sep 2018 at 10:56, Baolin Wang <baolin.wang@linaro.org> wrote:
>
> This patch adds the SC2731 PMIC switch charger support.
>
> Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
> ---
> Changes from v1:
>  - Remove some redundant head files.
>  - Add one mutex to make sure getting the charger status is correct.
>  - Fix some coding style issues.
>  - Use standard DT properties.
> ---
>  drivers/power/supply/Kconfig          |    7 +
>  drivers/power/supply/Makefile         |    1 +
>  drivers/power/supply/sc2731_charger.c |  503 +++++++++++++++++++++++++++++++++
>  3 files changed, 511 insertions(+)
>  create mode 100644 drivers/power/supply/sc2731_charger.c

Reviewed-by: Krzysztof Kozlowski <krzk@kernel.org>

Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v2 2/2] power: supply: Add Spreadtrum SC2731 charger support
  2018-09-03  8:55 ` [PATCH v2 2/2] power: supply: Add Spreadtrum SC2731 charger support Baolin Wang
  2018-09-03 10:20   ` Krzysztof Kozlowski
@ 2018-09-16 10:17   ` Sebastian Reichel
  2018-09-17  3:32     ` Baolin Wang
  1 sibling, 1 reply; 6+ messages in thread
From: Sebastian Reichel @ 2018-09-16 10:17 UTC (permalink / raw)
  To: Baolin Wang
  Cc: robh+dt, mark.rutland, linux-pm, devicetree, linux-kernel,
	yuanjiang.yu, broonie, krzk

[-- Attachment #1: Type: text/plain, Size: 16207 bytes --]

Hi Baolin,

On Mon, Sep 03, 2018 at 04:55:46PM +0800, Baolin Wang wrote:
> This patch adds the SC2731 PMIC switch charger support.
> 
> Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
> ---

The power-supply sysfs files uses uA instead of mA, otherwise the
driver and the binding looks fine to me.

-- Sebastian

> Changes from v1:
>  - Remove some redundant head files.
>  - Add one mutex to make sure getting the charger status is correct.
>  - Fix some coding style issues.
>  - Use standard DT properties.
> ---
>  drivers/power/supply/Kconfig          |    7 +
>  drivers/power/supply/Makefile         |    1 +
>  drivers/power/supply/sc2731_charger.c |  503 +++++++++++++++++++++++++++++++++
>  3 files changed, 511 insertions(+)
>  create mode 100644 drivers/power/supply/sc2731_charger.c
> 
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index ff6dab0..f27cf07 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -645,4 +645,11 @@ config CHARGER_CROS_USBPD
>  	  what is connected to USB PD ports from the EC and converts
>  	  that into power_supply properties.
>  
> +config CHARGER_SC2731
> +	tristate "Spreadtrum SC2731 charger driver"
> +	depends on MFD_SC27XX_PMIC || COMPILE_TEST
> +	help
> +	 Say Y here to enable support for battery charging with SC2731
> +	 PMIC chips.
> +
>  endif # POWER_SUPPLY
> diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
> index a26b402..767105b 100644
> --- a/drivers/power/supply/Makefile
> +++ b/drivers/power/supply/Makefile
> @@ -85,3 +85,4 @@ obj-$(CONFIG_CHARGER_TPS65217)	+= tps65217_charger.o
>  obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
>  obj-$(CONFIG_AXP288_CHARGER)	+= axp288_charger.o
>  obj-$(CONFIG_CHARGER_CROS_USBPD)	+= cros_usbpd-charger.o
> +obj-$(CONFIG_CHARGER_SC2731)	+= sc2731_charger.o
> diff --git a/drivers/power/supply/sc2731_charger.c b/drivers/power/supply/sc2731_charger.c
> new file mode 100644
> index 0000000..c92e5fc
> --- /dev/null
> +++ b/drivers/power/supply/sc2731_charger.c
> @@ -0,0 +1,503 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// Copyright (C) 2018 Spreadtrum Communications Inc.
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/power_supply.h>
> +#include <linux/usb/phy.h>
> +#include <linux/regmap.h>
> +#include <linux/notifier.h>
> +#include <linux/of.h>
> +
> +/* PMIC global registers definition */
> +#define SC2731_CHARGE_STATUS		0xedc
> +#define SC2731_CHARGE_FULL		BIT(4)
> +#define SC2731_MODULE_EN1		0xc0c
> +#define SC2731_CHARGE_EN		BIT(5)
> +
> +/* SC2731 switch charger registers definition */
> +#define SC2731_CHG_CFG0			0x0
> +#define SC2731_CHG_CFG1			0x4
> +#define SC2731_CHG_CFG2			0x8
> +#define SC2731_CHG_CFG3			0xc
> +#define SC2731_CHG_CFG4			0x10
> +#define SC2731_CHG_CFG5			0x28
> +
> +/* SC2731_CHG_CFG0 register definition */
> +#define SC2731_PRECHG_RNG_SHIFT		11
> +#define SC2731_PRECHG_RNG_MASK		GENMASK(12, 11)
> +
> +#define SC2731_TERMINATION_VOL_MASK	GENMASK(2, 1)
> +#define SC2731_TERMINATION_VOL_SHIFT	1
> +#define SC2731_TERMINATION_VOL_CAL_MASK	GENMASK(8, 3)
> +#define SC2731_TERMINATION_VOL_CAL_SHIFT	3
> +#define SC2731_TERMINATION_CUR_MASK	GENMASK(2, 0)
> +
> +#define SC2731_CC_EN			BIT(13)
> +#define SC2731_CHARGER_PD		BIT(0)
> +
> +/* SC2731_CHG_CFG1 register definition */
> +#define SC2731_CUR_MASK			GENMASK(5, 0)
> +
> +/* SC2731_CHG_CFG5 register definition */
> +#define SC2731_CUR_LIMIT_SHIFT		8
> +#define SC2731_CUR_LIMIT_MASK		GENMASK(9, 8)
> +
> +/* Default current definition (unit is mA) */
> +#define SC2731_CURRENT_LIMIT_100	100
> +#define SC2731_CURRENT_LIMIT_500	500
> +#define SC2731_CURRENT_LIMIT_900	900
> +#define SC2731_CURRENT_LIMIT_2000	2000
> +#define SC2731_CURRENT_PRECHG		450
> +#define SC2731_CURRENT_STEP		50
> +
> +struct sc2731_charger_info {
> +	struct device *dev;
> +	struct regmap *regmap;
> +	struct usb_phy *usb_phy;
> +	struct notifier_block usb_notify;
> +	struct power_supply *psy_usb;
> +	struct mutex lock;
> +	bool charging;
> +	u32 base;
> +};
> +
> +static void sc2731_charger_stop_charge(struct sc2731_charger_info *info)
> +{
> +	regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
> +			   SC2731_CC_EN, 0);
> +
> +	regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
> +			   SC2731_CHARGER_PD, SC2731_CHARGER_PD);
> +}
> +
> +static int sc2731_charger_start_charge(struct sc2731_charger_info *info)
> +{
> +	int ret;
> +
> +	/* Enable charger constant current mode */
> +	ret = regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
> +				 SC2731_CC_EN, SC2731_CC_EN);
> +	if (ret)
> +		return ret;
> +
> +	/* Start charging */
> +	return regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
> +				  SC2731_CHARGER_PD, 0);
> +}
> +
> +static int sc2731_charger_set_current_limit(struct sc2731_charger_info *info,
> +					    u32 limit)
> +{
> +	u32 val;
> +
> +	if (limit <= SC2731_CURRENT_LIMIT_100)
> +		val = 0;
> +	else if (limit <= SC2731_CURRENT_LIMIT_500)
> +		val = 3;
> +	else if (limit <= SC2731_CURRENT_LIMIT_900)
> +		val = 2;
> +	else
> +		val = 1;
> +
> +	return regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG5,
> +				  SC2731_CUR_LIMIT_MASK,
> +				  val << SC2731_CUR_LIMIT_SHIFT);
> +}
> +
> +static int sc2731_charger_set_current(struct sc2731_charger_info *info, u32 cur)
> +{
> +	u32 val;
> +	int ret;
> +
> +	if (cur > SC2731_CURRENT_LIMIT_2000)
> +		cur = SC2731_CURRENT_LIMIT_2000;
> +	else if (cur < SC2731_CURRENT_PRECHG)
> +		cur = SC2731_CURRENT_PRECHG;
> +
> +	/* Calculate the step value, each step is 50 mA */
> +	val = (cur - SC2731_CURRENT_PRECHG) / SC2731_CURRENT_STEP;
> +
> +	/* Set pre-charge current as 450 mA */
> +	ret = regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
> +				 SC2731_PRECHG_RNG_MASK,
> +				 0x3 << SC2731_PRECHG_RNG_SHIFT);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG1,
> +				  SC2731_CUR_MASK, val);
> +}
> +
> +static int sc2731_charger_get_status(struct sc2731_charger_info *info)
> +{
> +	u32 val;
> +	int ret;
> +
> +	ret = regmap_read(info->regmap, SC2731_CHARGE_STATUS, &val);
> +	if (ret)
> +		return ret;
> +
> +	if (val & SC2731_CHARGE_FULL)
> +		return POWER_SUPPLY_STATUS_FULL;
> +
> +	return POWER_SUPPLY_STATUS_CHARGING;
> +}
> +
> +static int sc2731_charger_get_current(struct sc2731_charger_info *info,
> +				      u32 *cur)
> +{
> +	int ret;
> +	u32 val;
> +
> +	ret = regmap_read(info->regmap, info->base + SC2731_CHG_CFG1, &val);
> +	if (ret)
> +		return ret;
> +
> +	val &= SC2731_CUR_MASK;
> +	*cur = val * SC2731_CURRENT_STEP + SC2731_CURRENT_PRECHG;
> +
> +	return 0;
> +}
> +
> +static int sc2731_charger_get_current_limit(struct sc2731_charger_info *info,
> +					    u32 *cur)
> +{
> +	int ret;
> +	u32 val;
> +
> +	ret = regmap_read(info->regmap, info->base + SC2731_CHG_CFG5, &val);
> +	if (ret)
> +		return ret;
> +
> +	val = (val & SC2731_CUR_LIMIT_MASK) >> SC2731_CUR_LIMIT_SHIFT;
> +
> +	switch (val) {
> +	case 0:
> +		*cur = SC2731_CURRENT_LIMIT_100;
> +		break;
> +
> +	case 1:
> +		*cur = SC2731_CURRENT_LIMIT_2000;
> +		break;
> +
> +	case 2:
> +		*cur = SC2731_CURRENT_LIMIT_900;
> +		break;
> +
> +	case 3:
> +		*cur = SC2731_CURRENT_LIMIT_500;
> +		break;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +sc2731_charger_usb_set_property(struct power_supply *psy,
> +				enum power_supply_property psp,
> +				const union power_supply_propval *val)
> +{
> +	struct sc2731_charger_info *info = power_supply_get_drvdata(psy);
> +	int ret;
> +
> +	mutex_lock(&info->lock);
> +
> +	if (!info->charging) {
> +		mutex_unlock(&info->lock);
> +		return -ENODEV;
> +	}
> +
> +	switch (psp) {
> +	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
> +		ret = sc2731_charger_set_current(info, val->intval);
> +		if (ret < 0)
> +			dev_err(info->dev, "set charge current failed\n");
> +		break;

This gets uA as input (instead of mA).

> +	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
> +		ret = sc2731_charger_set_current_limit(info, val->intval);
> +		if (ret < 0)
> +			dev_err(info->dev, "set input current limit failed\n");
> +		break;

This gets uA as input (instead of mA).

> +
> +	default:
> +		ret = -EINVAL;
> +	}
> +
> +	mutex_unlock(&info->lock);
> +	return ret;
> +}
> +
> +static int sc2731_charger_usb_get_property(struct power_supply *psy,
> +					   enum power_supply_property psp,
> +					   union power_supply_propval *val)
> +{
> +	struct sc2731_charger_info *info = power_supply_get_drvdata(psy);
> +	int ret = 0;
> +	u32 cur;
> +
> +	mutex_lock(&info->lock);
> +
> +	switch (psp) {
> +	case POWER_SUPPLY_PROP_STATUS:
> +		if (info->charging)
> +			val->intval = sc2731_charger_get_status(info);
> +		else
> +			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
> +		break;
> +
> +	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
> +		if (!info->charging) {
> +			val->intval = 0;
> +		} else {
> +			ret = sc2731_charger_get_current(info, &cur);
> +			if (ret)
> +				goto out;
> +
> +			val->intval = cur;
> +		}
> +		break;

This should always report uA (instead of mA).

> +	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
> +		if (!info->charging) {
> +			val->intval = 0;
> +		} else {
> +			ret = sc2731_charger_get_current_limit(info, &cur);
> +			if (ret)
> +				goto out;
> +
> +			val->intval = cur;
> +		}
> +		break;

This should always report uA (instead of mA).

> +	default:
> +		ret = -EINVAL;
> +	}
> +
> +out:
> +	mutex_unlock(&info->lock);
> +	return ret;
> +}
> +
> +static int sc2731_charger_property_is_writeable(struct power_supply *psy,
> +						enum power_supply_property psp)
> +{
> +	int ret;
> +
> +	switch (psp) {
> +	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
> +	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
> +		ret = 1;
> +		break;
> +
> +	default:
> +		ret = 0;
> +	}
> +
> +	return ret;
> +}
> +
> +static enum power_supply_property sc2731_usb_props[] = {
> +	POWER_SUPPLY_PROP_STATUS,
> +	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
> +	POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
> +};
> +
> +static const struct power_supply_desc sc2731_charger_desc = {
> +	.name			= "sc2731_charger",
> +	.type			= POWER_SUPPLY_TYPE_USB,
> +	.properties		= sc2731_usb_props,
> +	.num_properties		= ARRAY_SIZE(sc2731_usb_props),
> +	.get_property		= sc2731_charger_usb_get_property,
> +	.set_property		= sc2731_charger_usb_set_property,
> +	.property_is_writeable	= sc2731_charger_property_is_writeable,
> +};
> +
> +static int sc2731_charger_usb_change(struct notifier_block *nb,
> +				     unsigned long limit, void *data)
> +{
> +	struct sc2731_charger_info *info =
> +		container_of(nb, struct sc2731_charger_info, usb_notify);
> +	int ret = 0;
> +
> +	mutex_lock(&info->lock);
> +
> +	if (limit > 0) {
> +		/* set current limitation and start to charge */
> +		ret = sc2731_charger_set_current_limit(info, limit);
> +		if (ret)
> +			goto out;
> +
> +		ret = sc2731_charger_set_current(info, limit);
> +		if (ret)
> +			goto out;
> +
> +		ret = sc2731_charger_start_charge(info);
> +		if (ret)
> +			goto out;
> +
> +		info->charging = true;
> +	} else {
> +		/* Stop charging */
> +		info->charging = false;
> +		sc2731_charger_stop_charge(info);
> +	}
> +
> +out:
> +	mutex_unlock(&info->lock);
> +	return ret;
> +}
> +
> +static int sc2731_charger_hw_init(struct sc2731_charger_info *info)
> +{
> +	struct power_supply_battery_info bat_info = { };
> +	u32 term_currrent, term_voltage, cur_val, vol_val;
> +	int ret;
> +
> +	/* Enable charger module */
> +	ret = regmap_update_bits(info->regmap, SC2731_MODULE_EN1,
> +				 SC2731_CHARGE_EN, SC2731_CHARGE_EN);
> +	if (ret)
> +		return ret;
> +
> +	ret = power_supply_get_battery_info(info->psy_usb, &bat_info);
> +	if (ret) {
> +		dev_warn(info->dev, "no battery information is supplied\n");
> +
> +		/*
> +		 * If no battery information is supplied, we should set
> +		 * default charge termination current to 120 mA, and default
> +		 * charge termination voltage to 4.35V.
> +		 */
> +		cur_val = 0x2;
> +		vol_val = 0x1;
> +	} else {
> +		term_currrent = bat_info.charge_term_current_ua / 1000;
> +
> +		if (term_currrent <= 90)
> +			cur_val = 0;
> +		else if (term_currrent >= 265)
> +			cur_val = 0x7;
> +		else
> +			cur_val = ((term_currrent - 90) / 25) + 1;
> +
> +		term_voltage = bat_info.constant_charge_voltage_max_uv / 1000;
> +
> +		if (term_voltage > 4500)
> +			term_voltage = 4500;
> +
> +		if (term_voltage > 4200)
> +			vol_val = (term_voltage - 4200) / 100;
> +		else
> +			vol_val = 0;
> +	}
> +
> +	/* Set charge termination current */
> +	ret = regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG2,
> +				 SC2731_TERMINATION_CUR_MASK, cur_val);
> +	if (ret)
> +		goto error;
> +
> +	/* Set charge termination voltage */
> +	ret = regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
> +				 SC2731_TERMINATION_VOL_MASK |
> +				 SC2731_TERMINATION_VOL_CAL_MASK,
> +				 (vol_val << SC2731_TERMINATION_VOL_SHIFT) |
> +				 (0x6 << SC2731_TERMINATION_VOL_CAL_SHIFT));
> +	if (ret)
> +		goto error;
> +
> +	return 0;
> +
> +error:
> +	regmap_update_bits(info->regmap, SC2731_MODULE_EN1, SC2731_CHARGE_EN, 0);
> +	return ret;
> +}
> +
> +static int sc2731_charger_probe(struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct sc2731_charger_info *info;
> +	struct power_supply_config charger_cfg = { };
> +	int ret;
> +
> +	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
> +	if (!info)
> +		return -ENOMEM;
> +
> +	mutex_init(&info->lock);
> +	info->dev = &pdev->dev;
> +
> +	info->regmap = dev_get_regmap(pdev->dev.parent, NULL);
> +	if (!info->regmap) {
> +		dev_err(&pdev->dev, "failed to get charger regmap\n");
> +		return -ENODEV;
> +	}
> +
> +	ret = of_property_read_u32(np, "reg", &info->base);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to get register address\n");
> +		return -ENODEV;
> +	}
> +
> +	charger_cfg.drv_data = info;
> +	charger_cfg.of_node = np;
> +	info->psy_usb = devm_power_supply_register(&pdev->dev,
> +						   &sc2731_charger_desc,
> +						   &charger_cfg);
> +	if (IS_ERR(info->psy_usb)) {
> +		dev_err(&pdev->dev, "failed to register power supply\n");
> +		return PTR_ERR(info->psy_usb);
> +	}
> +
> +	ret = sc2731_charger_hw_init(info);
> +	if (ret)
> +		return ret;
> +
> +	info->usb_phy = devm_usb_get_phy_by_phandle(&pdev->dev, "phys", 0);
> +	if (IS_ERR(info->usb_phy)) {
> +		dev_err(&pdev->dev, "failed to find USB phy\n");
> +		return PTR_ERR(info->usb_phy);
> +	}
> +
> +	info->usb_notify.notifier_call = sc2731_charger_usb_change;
> +	ret = usb_register_notifier(info->usb_phy, &info->usb_notify);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to register notifier: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int sc2731_charger_remove(struct platform_device *pdev)
> +{
> +	struct sc2731_charger_info *info = platform_get_drvdata(pdev);
> +
> +	usb_unregister_notifier(info->usb_phy, &info->usb_notify);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id sc2731_charger_of_match[] = {
> +	{ .compatible = "sprd,sc2731-charger", },
> +	{ }
> +};
> +
> +static struct platform_driver sc2731_charger_driver = {
> +	.driver = {
> +		.name = "sc2731-charger",
> +		.of_match_table = sc2731_charger_of_match,
> +	},
> +	.probe = sc2731_charger_probe,
> +	.remove = sc2731_charger_remove,
> +};
> +
> +module_platform_driver(sc2731_charger_driver);
> +
> +MODULE_DESCRIPTION("Spreadtrum SC2731 Charger Driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 1.7.9.5
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v2 2/2] power: supply: Add Spreadtrum SC2731 charger support
  2018-09-16 10:17   ` Sebastian Reichel
@ 2018-09-17  3:32     ` Baolin Wang
  0 siblings, 0 replies; 6+ messages in thread
From: Baolin Wang @ 2018-09-17  3:32 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Rob Herring, Mark Rutland, Linux PM list, DTML, LKML,
	yuanjiang.yu, Mark Brown, Krzysztof Kozlowski

Hi Sebastian,

On 16 September 2018 at 18:17, Sebastian Reichel
<sebastian.reichel@collabora.com> wrote:
> Hi Baolin,
>
> On Mon, Sep 03, 2018 at 04:55:46PM +0800, Baolin Wang wrote:
>> This patch adds the SC2731 PMIC switch charger support.
>>
>> Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
>> ---
>
> The power-supply sysfs files uses uA instead of mA, otherwise the
> driver and the binding looks fine to me.

OK. Thanks for your reviewing.

>
> -- Sebastian
>
>> Changes from v1:
>>  - Remove some redundant head files.
>>  - Add one mutex to make sure getting the charger status is correct.
>>  - Fix some coding style issues.
>>  - Use standard DT properties.
>> ---
>>  drivers/power/supply/Kconfig          |    7 +
>>  drivers/power/supply/Makefile         |    1 +
>>  drivers/power/supply/sc2731_charger.c |  503 +++++++++++++++++++++++++++++++++
>>  3 files changed, 511 insertions(+)
>>  create mode 100644 drivers/power/supply/sc2731_charger.c
>>
>> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
>> index ff6dab0..f27cf07 100644
>> --- a/drivers/power/supply/Kconfig
>> +++ b/drivers/power/supply/Kconfig
>> @@ -645,4 +645,11 @@ config CHARGER_CROS_USBPD
>>         what is connected to USB PD ports from the EC and converts
>>         that into power_supply properties.
>>
>> +config CHARGER_SC2731
>> +     tristate "Spreadtrum SC2731 charger driver"
>> +     depends on MFD_SC27XX_PMIC || COMPILE_TEST
>> +     help
>> +      Say Y here to enable support for battery charging with SC2731
>> +      PMIC chips.
>> +
>>  endif # POWER_SUPPLY
>> diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
>> index a26b402..767105b 100644
>> --- a/drivers/power/supply/Makefile
>> +++ b/drivers/power/supply/Makefile
>> @@ -85,3 +85,4 @@ obj-$(CONFIG_CHARGER_TPS65217)      += tps65217_charger.o
>>  obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
>>  obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o
>>  obj-$(CONFIG_CHARGER_CROS_USBPD)     += cros_usbpd-charger.o
>> +obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
>> diff --git a/drivers/power/supply/sc2731_charger.c b/drivers/power/supply/sc2731_charger.c
>> new file mode 100644
>> index 0000000..c92e5fc
>> --- /dev/null
>> +++ b/drivers/power/supply/sc2731_charger.c
>> @@ -0,0 +1,503 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +// Copyright (C) 2018 Spreadtrum Communications Inc.
>> +
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/power_supply.h>
>> +#include <linux/usb/phy.h>
>> +#include <linux/regmap.h>
>> +#include <linux/notifier.h>
>> +#include <linux/of.h>
>> +
>> +/* PMIC global registers definition */
>> +#define SC2731_CHARGE_STATUS         0xedc
>> +#define SC2731_CHARGE_FULL           BIT(4)
>> +#define SC2731_MODULE_EN1            0xc0c
>> +#define SC2731_CHARGE_EN             BIT(5)
>> +
>> +/* SC2731 switch charger registers definition */
>> +#define SC2731_CHG_CFG0                      0x0
>> +#define SC2731_CHG_CFG1                      0x4
>> +#define SC2731_CHG_CFG2                      0x8
>> +#define SC2731_CHG_CFG3                      0xc
>> +#define SC2731_CHG_CFG4                      0x10
>> +#define SC2731_CHG_CFG5                      0x28
>> +
>> +/* SC2731_CHG_CFG0 register definition */
>> +#define SC2731_PRECHG_RNG_SHIFT              11
>> +#define SC2731_PRECHG_RNG_MASK               GENMASK(12, 11)
>> +
>> +#define SC2731_TERMINATION_VOL_MASK  GENMASK(2, 1)
>> +#define SC2731_TERMINATION_VOL_SHIFT 1
>> +#define SC2731_TERMINATION_VOL_CAL_MASK      GENMASK(8, 3)
>> +#define SC2731_TERMINATION_VOL_CAL_SHIFT     3
>> +#define SC2731_TERMINATION_CUR_MASK  GENMASK(2, 0)
>> +
>> +#define SC2731_CC_EN                 BIT(13)
>> +#define SC2731_CHARGER_PD            BIT(0)
>> +
>> +/* SC2731_CHG_CFG1 register definition */
>> +#define SC2731_CUR_MASK                      GENMASK(5, 0)
>> +
>> +/* SC2731_CHG_CFG5 register definition */
>> +#define SC2731_CUR_LIMIT_SHIFT               8
>> +#define SC2731_CUR_LIMIT_MASK                GENMASK(9, 8)
>> +
>> +/* Default current definition (unit is mA) */
>> +#define SC2731_CURRENT_LIMIT_100     100
>> +#define SC2731_CURRENT_LIMIT_500     500
>> +#define SC2731_CURRENT_LIMIT_900     900
>> +#define SC2731_CURRENT_LIMIT_2000    2000
>> +#define SC2731_CURRENT_PRECHG                450
>> +#define SC2731_CURRENT_STEP          50
>> +
>> +struct sc2731_charger_info {
>> +     struct device *dev;
>> +     struct regmap *regmap;
>> +     struct usb_phy *usb_phy;
>> +     struct notifier_block usb_notify;
>> +     struct power_supply *psy_usb;
>> +     struct mutex lock;
>> +     bool charging;
>> +     u32 base;
>> +};
>> +
>> +static void sc2731_charger_stop_charge(struct sc2731_charger_info *info)
>> +{
>> +     regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
>> +                        SC2731_CC_EN, 0);
>> +
>> +     regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
>> +                        SC2731_CHARGER_PD, SC2731_CHARGER_PD);
>> +}
>> +
>> +static int sc2731_charger_start_charge(struct sc2731_charger_info *info)
>> +{
>> +     int ret;
>> +
>> +     /* Enable charger constant current mode */
>> +     ret = regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
>> +                              SC2731_CC_EN, SC2731_CC_EN);
>> +     if (ret)
>> +             return ret;
>> +
>> +     /* Start charging */
>> +     return regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
>> +                               SC2731_CHARGER_PD, 0);
>> +}
>> +
>> +static int sc2731_charger_set_current_limit(struct sc2731_charger_info *info,
>> +                                         u32 limit)
>> +{
>> +     u32 val;
>> +
>> +     if (limit <= SC2731_CURRENT_LIMIT_100)
>> +             val = 0;
>> +     else if (limit <= SC2731_CURRENT_LIMIT_500)
>> +             val = 3;
>> +     else if (limit <= SC2731_CURRENT_LIMIT_900)
>> +             val = 2;
>> +     else
>> +             val = 1;
>> +
>> +     return regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG5,
>> +                               SC2731_CUR_LIMIT_MASK,
>> +                               val << SC2731_CUR_LIMIT_SHIFT);
>> +}
>> +
>> +static int sc2731_charger_set_current(struct sc2731_charger_info *info, u32 cur)
>> +{
>> +     u32 val;
>> +     int ret;
>> +
>> +     if (cur > SC2731_CURRENT_LIMIT_2000)
>> +             cur = SC2731_CURRENT_LIMIT_2000;
>> +     else if (cur < SC2731_CURRENT_PRECHG)
>> +             cur = SC2731_CURRENT_PRECHG;
>> +
>> +     /* Calculate the step value, each step is 50 mA */
>> +     val = (cur - SC2731_CURRENT_PRECHG) / SC2731_CURRENT_STEP;
>> +
>> +     /* Set pre-charge current as 450 mA */
>> +     ret = regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
>> +                              SC2731_PRECHG_RNG_MASK,
>> +                              0x3 << SC2731_PRECHG_RNG_SHIFT);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG1,
>> +                               SC2731_CUR_MASK, val);
>> +}
>> +
>> +static int sc2731_charger_get_status(struct sc2731_charger_info *info)
>> +{
>> +     u32 val;
>> +     int ret;
>> +
>> +     ret = regmap_read(info->regmap, SC2731_CHARGE_STATUS, &val);
>> +     if (ret)
>> +             return ret;
>> +
>> +     if (val & SC2731_CHARGE_FULL)
>> +             return POWER_SUPPLY_STATUS_FULL;
>> +
>> +     return POWER_SUPPLY_STATUS_CHARGING;
>> +}
>> +
>> +static int sc2731_charger_get_current(struct sc2731_charger_info *info,
>> +                                   u32 *cur)
>> +{
>> +     int ret;
>> +     u32 val;
>> +
>> +     ret = regmap_read(info->regmap, info->base + SC2731_CHG_CFG1, &val);
>> +     if (ret)
>> +             return ret;
>> +
>> +     val &= SC2731_CUR_MASK;
>> +     *cur = val * SC2731_CURRENT_STEP + SC2731_CURRENT_PRECHG;
>> +
>> +     return 0;
>> +}
>> +
>> +static int sc2731_charger_get_current_limit(struct sc2731_charger_info *info,
>> +                                         u32 *cur)
>> +{
>> +     int ret;
>> +     u32 val;
>> +
>> +     ret = regmap_read(info->regmap, info->base + SC2731_CHG_CFG5, &val);
>> +     if (ret)
>> +             return ret;
>> +
>> +     val = (val & SC2731_CUR_LIMIT_MASK) >> SC2731_CUR_LIMIT_SHIFT;
>> +
>> +     switch (val) {
>> +     case 0:
>> +             *cur = SC2731_CURRENT_LIMIT_100;
>> +             break;
>> +
>> +     case 1:
>> +             *cur = SC2731_CURRENT_LIMIT_2000;
>> +             break;
>> +
>> +     case 2:
>> +             *cur = SC2731_CURRENT_LIMIT_900;
>> +             break;
>> +
>> +     case 3:
>> +             *cur = SC2731_CURRENT_LIMIT_500;
>> +             break;
>> +
>> +     default:
>> +             return -EINVAL;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int
>> +sc2731_charger_usb_set_property(struct power_supply *psy,
>> +                             enum power_supply_property psp,
>> +                             const union power_supply_propval *val)
>> +{
>> +     struct sc2731_charger_info *info = power_supply_get_drvdata(psy);
>> +     int ret;
>> +
>> +     mutex_lock(&info->lock);
>> +
>> +     if (!info->charging) {
>> +             mutex_unlock(&info->lock);
>> +             return -ENODEV;
>> +     }
>> +
>> +     switch (psp) {
>> +     case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
>> +             ret = sc2731_charger_set_current(info, val->intval);
>> +             if (ret < 0)
>> +                     dev_err(info->dev, "set charge current failed\n");
>> +             break;
>
> This gets uA as input (instead of mA).

Yes. Sorry I missed this and will fix in next version.

>
>> +     case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
>> +             ret = sc2731_charger_set_current_limit(info, val->intval);
>> +             if (ret < 0)
>> +                     dev_err(info->dev, "set input current limit failed\n");
>> +             break;
>
> This gets uA as input (instead of mA).

OK.

>
>> +
>> +     default:
>> +             ret = -EINVAL;
>> +     }
>> +
>> +     mutex_unlock(&info->lock);
>> +     return ret;
>> +}
>> +
>> +static int sc2731_charger_usb_get_property(struct power_supply *psy,
>> +                                        enum power_supply_property psp,
>> +                                        union power_supply_propval *val)
>> +{
>> +     struct sc2731_charger_info *info = power_supply_get_drvdata(psy);
>> +     int ret = 0;
>> +     u32 cur;
>> +
>> +     mutex_lock(&info->lock);
>> +
>> +     switch (psp) {
>> +     case POWER_SUPPLY_PROP_STATUS:
>> +             if (info->charging)
>> +                     val->intval = sc2731_charger_get_status(info);
>> +             else
>> +                     val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
>> +             break;
>> +
>> +     case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
>> +             if (!info->charging) {
>> +                     val->intval = 0;
>> +             } else {
>> +                     ret = sc2731_charger_get_current(info, &cur);
>> +                     if (ret)
>> +                             goto out;
>> +
>> +                     val->intval = cur;
>> +             }
>> +             break;
>
> This should always report uA (instead of mA).

OK.

>
>> +     case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
>> +             if (!info->charging) {
>> +                     val->intval = 0;
>> +             } else {
>> +                     ret = sc2731_charger_get_current_limit(info, &cur);
>> +                     if (ret)
>> +                             goto out;
>> +
>> +                     val->intval = cur;
>> +             }
>> +             break;
>
> This should always report uA (instead of mA).

OK. Thanks.

>
>> +     default:
>> +             ret = -EINVAL;
>> +     }
>> +
>> +out:
>> +     mutex_unlock(&info->lock);
>> +     return ret;
>> +}
>> +
>> +static int sc2731_charger_property_is_writeable(struct power_supply *psy,
>> +                                             enum power_supply_property psp)
>> +{
>> +     int ret;
>> +
>> +     switch (psp) {
>> +     case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
>> +     case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
>> +             ret = 1;
>> +             break;
>> +
>> +     default:
>> +             ret = 0;
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +static enum power_supply_property sc2731_usb_props[] = {
>> +     POWER_SUPPLY_PROP_STATUS,
>> +     POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
>> +     POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
>> +};
>> +
>> +static const struct power_supply_desc sc2731_charger_desc = {
>> +     .name                   = "sc2731_charger",
>> +     .type                   = POWER_SUPPLY_TYPE_USB,
>> +     .properties             = sc2731_usb_props,
>> +     .num_properties         = ARRAY_SIZE(sc2731_usb_props),
>> +     .get_property           = sc2731_charger_usb_get_property,
>> +     .set_property           = sc2731_charger_usb_set_property,
>> +     .property_is_writeable  = sc2731_charger_property_is_writeable,
>> +};
>> +
>> +static int sc2731_charger_usb_change(struct notifier_block *nb,
>> +                                  unsigned long limit, void *data)
>> +{
>> +     struct sc2731_charger_info *info =
>> +             container_of(nb, struct sc2731_charger_info, usb_notify);
>> +     int ret = 0;
>> +
>> +     mutex_lock(&info->lock);
>> +
>> +     if (limit > 0) {
>> +             /* set current limitation and start to charge */
>> +             ret = sc2731_charger_set_current_limit(info, limit);
>> +             if (ret)
>> +                     goto out;
>> +
>> +             ret = sc2731_charger_set_current(info, limit);
>> +             if (ret)
>> +                     goto out;
>> +
>> +             ret = sc2731_charger_start_charge(info);
>> +             if (ret)
>> +                     goto out;
>> +
>> +             info->charging = true;
>> +     } else {
>> +             /* Stop charging */
>> +             info->charging = false;
>> +             sc2731_charger_stop_charge(info);
>> +     }
>> +
>> +out:
>> +     mutex_unlock(&info->lock);
>> +     return ret;
>> +}
>> +
>> +static int sc2731_charger_hw_init(struct sc2731_charger_info *info)
>> +{
>> +     struct power_supply_battery_info bat_info = { };
>> +     u32 term_currrent, term_voltage, cur_val, vol_val;
>> +     int ret;
>> +
>> +     /* Enable charger module */
>> +     ret = regmap_update_bits(info->regmap, SC2731_MODULE_EN1,
>> +                              SC2731_CHARGE_EN, SC2731_CHARGE_EN);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = power_supply_get_battery_info(info->psy_usb, &bat_info);
>> +     if (ret) {
>> +             dev_warn(info->dev, "no battery information is supplied\n");
>> +
>> +             /*
>> +              * If no battery information is supplied, we should set
>> +              * default charge termination current to 120 mA, and default
>> +              * charge termination voltage to 4.35V.
>> +              */
>> +             cur_val = 0x2;
>> +             vol_val = 0x1;
>> +     } else {
>> +             term_currrent = bat_info.charge_term_current_ua / 1000;
>> +
>> +             if (term_currrent <= 90)
>> +                     cur_val = 0;
>> +             else if (term_currrent >= 265)
>> +                     cur_val = 0x7;
>> +             else
>> +                     cur_val = ((term_currrent - 90) / 25) + 1;
>> +
>> +             term_voltage = bat_info.constant_charge_voltage_max_uv / 1000;
>> +
>> +             if (term_voltage > 4500)
>> +                     term_voltage = 4500;
>> +
>> +             if (term_voltage > 4200)
>> +                     vol_val = (term_voltage - 4200) / 100;
>> +             else
>> +                     vol_val = 0;
>> +     }
>> +
>> +     /* Set charge termination current */
>> +     ret = regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG2,
>> +                              SC2731_TERMINATION_CUR_MASK, cur_val);
>> +     if (ret)
>> +             goto error;
>> +
>> +     /* Set charge termination voltage */
>> +     ret = regmap_update_bits(info->regmap, info->base + SC2731_CHG_CFG0,
>> +                              SC2731_TERMINATION_VOL_MASK |
>> +                              SC2731_TERMINATION_VOL_CAL_MASK,
>> +                              (vol_val << SC2731_TERMINATION_VOL_SHIFT) |
>> +                              (0x6 << SC2731_TERMINATION_VOL_CAL_SHIFT));
>> +     if (ret)
>> +             goto error;
>> +
>> +     return 0;
>> +
>> +error:
>> +     regmap_update_bits(info->regmap, SC2731_MODULE_EN1, SC2731_CHARGE_EN, 0);
>> +     return ret;
>> +}
>> +
>> +static int sc2731_charger_probe(struct platform_device *pdev)
>> +{
>> +     struct device_node *np = pdev->dev.of_node;
>> +     struct sc2731_charger_info *info;
>> +     struct power_supply_config charger_cfg = { };
>> +     int ret;
>> +
>> +     info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
>> +     if (!info)
>> +             return -ENOMEM;
>> +
>> +     mutex_init(&info->lock);
>> +     info->dev = &pdev->dev;
>> +
>> +     info->regmap = dev_get_regmap(pdev->dev.parent, NULL);
>> +     if (!info->regmap) {
>> +             dev_err(&pdev->dev, "failed to get charger regmap\n");
>> +             return -ENODEV;
>> +     }
>> +
>> +     ret = of_property_read_u32(np, "reg", &info->base);
>> +     if (ret) {
>> +             dev_err(&pdev->dev, "failed to get register address\n");
>> +             return -ENODEV;
>> +     }
>> +
>> +     charger_cfg.drv_data = info;
>> +     charger_cfg.of_node = np;
>> +     info->psy_usb = devm_power_supply_register(&pdev->dev,
>> +                                                &sc2731_charger_desc,
>> +                                                &charger_cfg);
>> +     if (IS_ERR(info->psy_usb)) {
>> +             dev_err(&pdev->dev, "failed to register power supply\n");
>> +             return PTR_ERR(info->psy_usb);
>> +     }
>> +
>> +     ret = sc2731_charger_hw_init(info);
>> +     if (ret)
>> +             return ret;
>> +
>> +     info->usb_phy = devm_usb_get_phy_by_phandle(&pdev->dev, "phys", 0);
>> +     if (IS_ERR(info->usb_phy)) {
>> +             dev_err(&pdev->dev, "failed to find USB phy\n");
>> +             return PTR_ERR(info->usb_phy);
>> +     }
>> +
>> +     info->usb_notify.notifier_call = sc2731_charger_usb_change;
>> +     ret = usb_register_notifier(info->usb_phy, &info->usb_notify);
>> +     if (ret) {
>> +             dev_err(&pdev->dev, "failed to register notifier: %d\n", ret);
>> +             return ret;
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int sc2731_charger_remove(struct platform_device *pdev)
>> +{
>> +     struct sc2731_charger_info *info = platform_get_drvdata(pdev);
>> +
>> +     usb_unregister_notifier(info->usb_phy, &info->usb_notify);
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct of_device_id sc2731_charger_of_match[] = {
>> +     { .compatible = "sprd,sc2731-charger", },
>> +     { }
>> +};
>> +
>> +static struct platform_driver sc2731_charger_driver = {
>> +     .driver = {
>> +             .name = "sc2731-charger",
>> +             .of_match_table = sc2731_charger_of_match,
>> +     },
>> +     .probe = sc2731_charger_probe,
>> +     .remove = sc2731_charger_remove,
>> +};
>> +
>> +module_platform_driver(sc2731_charger_driver);
>> +
>> +MODULE_DESCRIPTION("Spreadtrum SC2731 Charger Driver");
>> +MODULE_LICENSE("GPL v2");
>> --
>> 1.7.9.5
>>



-- 
Baolin Wang
Best Regards

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2018-09-17  3:32 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-09-03  8:55 [PATCH v2 1/2] dt-bindings: power: Add Spreadtrum SC2731 charger documentation Baolin Wang
2018-09-03  8:55 ` [PATCH v2 2/2] power: supply: Add Spreadtrum SC2731 charger support Baolin Wang
2018-09-03 10:20   ` Krzysztof Kozlowski
2018-09-16 10:17   ` Sebastian Reichel
2018-09-17  3:32     ` Baolin Wang
2018-09-03 10:14 ` [PATCH v2 1/2] dt-bindings: power: Add Spreadtrum SC2731 charger documentation Krzysztof Kozlowski

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.