linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Stefan Mavrodiev <stefan@olimex.com>
To: Jacek Anaszewski <jacek.anaszewski@gmail.com>,
	Pavel Machek <pavel@ucw.cz>, Rob Herring <robh+dt@kernel.org>,
	Mark Rutland <mark.rutland@arm.com>, Chen-Yu Tsai <wens@csie.org>,
	Maxime Ripard <maxime.ripard@bootlin.com>,
	Lee Jones <lee.jones@linaro.org>,
	linux-leds@vger.kernel.org (open list:LED SUBSYSTEM),
	devicetree@vger.kernel.org (open list:OPEN FIRMWARE AND
	FLATTENED DEVICE TREE BINDINGS),
	linux-kernel@vger.kernel.org (open list:X-POWERS MULTIFUNCTION
	PMIC DEVICE DRIVERS),
	linux-arm-kernel@lists.infradead.org (moderated
	list:ARM/Allwinner sunXi SoC support)
Cc: Stefan Mavrodiev <stefan@olimex.com>
Subject: [PATCH v2 1/8] leds: Add support for AXP20X CHGLED
Date: Fri, 15 Feb 2019 13:50:06 +0200	[thread overview]
Message-ID: <20190215115013.11098-2-stefan@olimex.com> (raw)
In-Reply-To: <20190215115013.11098-1-stefan@olimex.com>

Most of AXP20x PMIC chips have built-in battery charger with LED indicator.
The LED can be controlled ether by the charger or manually by a register.

The default is (except for AXP209) manual control, which makes this LED
useless, since there is no device driver.

The driver rely on AXP20X MFD driver.

Signed-off-by: Stefan Mavrodiev <stefan@olimex.com>
---
 drivers/leds/Kconfig       |  10 ++
 drivers/leds/Makefile      |   1 +
 drivers/leds/leds-axp20x.c | 291 +++++++++++++++++++++++++++++++++++++
 3 files changed, 302 insertions(+)
 create mode 100644 drivers/leds/leds-axp20x.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index a72f97fca57b..82dce9063d41 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -766,6 +766,16 @@ config LEDS_NIC78BX
 	  To compile this driver as a module, choose M here: the module
 	  will be called leds-nic78bx.
 
+config LEDS_AXP20X
+	tristate "LED support for X-Powers PMICs"
+	depends on MFD_AXP20X
+	help
+	  This option enables support for CHGLED found on most of X-Powers
+	  PMICs.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called leds-axp20x.
+
 comment "LED Triggers"
 source "drivers/leds/trigger/Kconfig"
 
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 4c1b0054f379..d3fb76e119d8 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -79,6 +79,7 @@ obj-$(CONFIG_LEDS_MT6323)		+= leds-mt6323.o
 obj-$(CONFIG_LEDS_LM3692X)		+= leds-lm3692x.o
 obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
 obj-$(CONFIG_LEDS_LM3601X)		+= leds-lm3601x.o
+obj-$(CONFIG_LEDS_AXP20X)		+= leds-axp20x.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_CR0014114)		+= leds-cr0014114.o
diff --git a/drivers/leds/leds-axp20x.c b/drivers/leds/leds-axp20x.c
new file mode 100644
index 000000000000..2d5ae1c085f0
--- /dev/null
+++ b/drivers/leds/leds-axp20x.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Copyright 2019 Stefan Mavrodiev <stefan@olimex.com>
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/mfd/axp20x.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <uapi/linux/uleds.h>
+
+
+#define AXP20X_CHGLED_CTRL_REG		AXP20X_OFF_CTRL
+#define AXP20X_CHGLED_FUNC_MASK			GENMASK(5, 4)
+#define AXP20X_CHGLED_FUNC_OFF			(0 << 4)
+#define AXP20X_CHGLED_FUNC_1HZ			(1 << 4)
+#define AXP20X_CHGLED_FUNC_4HZ			(2 << 4)
+#define AXP20X_CHGLED_FUNC_FULL			(3 << 4)
+#define AXP20X_CHGLED_CTRL_MASK			BIT(3)
+#define AXP20X_CHGLED_CTRL_MANUAL		0
+#define AXP20X_CHGLED_CTRL_CHARGER		1
+#define AXP20X_CHGLED_CTRL(_ctrl)		(_ctrl << 3)
+
+#define AXP20X_CHGLED_MODE_REG		AXP20X_CHRG_CTRL2
+#define AXP20X_CHGLED_MODE_MASK			BIT(4)
+#define AXP20X_CHGLED_MODE_A			0
+#define AXP20X_CHGLED_MODE_B			1
+#define AXP20X_CHGLED_MODE(_mode)		(_mode << 4)
+
+struct axp20x_led {
+	char			name[LED_MAX_NAME_SIZE];
+	struct led_classdev	cdev;
+	struct mutex		lock;
+	u8			mode : 1;
+	u8			ctrl : 1;
+	u8			ctrl_inverted : 1;
+	struct axp20x_dev	*axp20x;
+};
+
+static inline struct axp20x_led *to_axp20x_led(struct led_classdev *cdev)
+{
+	return container_of(cdev, struct axp20x_led, cdev);
+}
+
+static int axp20x_led_setup(struct axp20x_led *priv)
+{
+	int ret;
+	u8 val;
+
+	/* Invert the logic, if necessary */
+	val = priv->ctrl ^ priv->ctrl_inverted;
+
+	mutex_lock(&priv->lock);
+	ret = regmap_update_bits(priv->axp20x->regmap, AXP20X_CHGLED_CTRL_REG,
+				 AXP20X_CHGLED_CTRL_MASK,
+				 AXP20X_CHGLED_CTRL(val));
+	if (ret < 0)
+		goto out;
+
+	ret = regmap_update_bits(priv->axp20x->regmap, AXP20X_CHGLED_MODE_REG,
+				 AXP20X_CHGLED_MODE_MASK,
+				 AXP20X_CHGLED_MODE(priv->mode));
+out:
+	mutex_unlock(&priv->lock);
+	return ret;
+}
+
+static ssize_t control_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct axp20x_led *priv = to_axp20x_led(cdev);
+
+	return sprintf(buf, "%u\n", priv->ctrl);
+}
+
+static ssize_t control_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t size)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct axp20x_led *priv = to_axp20x_led(cdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	/**
+	 * Supported values are:
+	 *   - 0 : Manual control
+	 *   - 1 : Charger control
+	 */
+	if (val > 1)
+		return -EINVAL;
+
+	priv->ctrl = val;
+
+	return axp20x_led_setup(priv) ? : size;
+}
+static DEVICE_ATTR_RW(control);
+
+static ssize_t mode_show(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct axp20x_led *priv = to_axp20x_led(cdev);
+
+	return sprintf(buf, "%u\n", priv->mode);
+}
+
+static ssize_t mode_store(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t size)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct axp20x_led *priv = to_axp20x_led(cdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &val);
+	if (ret)
+		return ret;
+	/**
+	 * Supported values are:
+	 *   - 0 : Mode A
+	 *   - 1 : Mode B
+	 */
+	if (val > 1)
+		return -EINVAL;
+
+	priv->mode = val;
+
+	return axp20x_led_setup(priv) ? : size;
+}
+static DEVICE_ATTR_RW(mode);
+
+static struct attribute *axp20x_led_attrs[] = {
+	&dev_attr_control.attr,
+	&dev_attr_mode.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(axp20x_led);
+
+enum led_brightness axp20x_led_brightness_get(struct led_classdev *cdev)
+{
+	struct axp20x_led *priv = to_axp20x_led(cdev);
+	u32 val;
+	int ret;
+
+	mutex_lock(&priv->lock);
+	ret = regmap_read(priv->axp20x->regmap, AXP20X_CHGLED_CTRL_REG, &val);
+	mutex_unlock(&priv->lock);
+	if (ret < 0)
+		return LED_OFF;
+
+	return (val & AXP20X_CHGLED_FUNC_FULL) ? LED_FULL : LED_OFF;
+}
+
+static int axp20x_led_brightness_set_blocking(struct led_classdev *cdev,
+					      enum led_brightness brightness)
+{
+	struct axp20x_led *priv = to_axp20x_led(cdev);
+	int ret = 0;
+
+	mutex_lock(&priv->lock);
+	ret = regmap_update_bits(priv->axp20x->regmap,
+				 AXP20X_CHGLED_CTRL_REG,
+				 AXP20X_CHGLED_FUNC_MASK,
+				 (brightness) ?
+				 AXP20X_CHGLED_FUNC_FULL :
+				 AXP20X_CHGLED_FUNC_OFF);
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+static int axp20x_led_parse_dt(struct axp20x_led *priv, struct device_node *np)
+{
+	const char *str;
+	u8 value;
+	int ret = 0;
+
+	str = of_get_property(np, "label", NULL);
+	if (!str)
+		snprintf(priv->name, sizeof(priv->name), "axp20x::");
+	else
+		snprintf(priv->name, sizeof(priv->name), "axp20x:%s", str);
+	priv->cdev.name = priv->name;
+
+	priv->cdev.default_trigger = of_get_property(np,
+						     "linux,default-trigger",
+						     NULL);
+
+	if (!of_property_read_u8(np, "x-powers,charger-mode", &value)) {
+		priv->ctrl = AXP20X_CHGLED_CTRL_CHARGER;
+		priv->mode = (value < 2) ? value : 0;
+	} else {
+		priv->ctrl = AXP20X_CHGLED_CTRL_MANUAL;
+	}
+
+	str = of_get_property(np, "default-state", NULL);
+	if (str) {
+		if (!strcmp(str, "keep")) {
+			ret = axp20x_led_brightness_get(&priv->cdev);
+			if (ret < 0)
+				return ret;
+			priv->cdev.brightness = ret;
+		} else if (!strcmp(str, "on")) {
+			ret = axp20x_led_brightness_set_blocking(&priv->cdev,
+								 LED_FULL);
+		} else  {
+			ret = axp20x_led_brightness_set_blocking(&priv->cdev,
+								 LED_OFF);
+		}
+	}
+
+	return ret;
+}
+
+static const struct of_device_id axp20x_led_of_match[] = {
+	{ .compatible = "x-powers,axp20x-led" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, axp20x_led_of_match);
+
+static int axp20x_led_probe(struct platform_device *pdev)
+{
+	struct axp20x_led *priv;
+	int ret;
+
+	if (!of_device_is_available(pdev->dev.of_node))
+		return -ENODEV;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(struct axp20x_led),
+			    GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->axp20x = dev_get_drvdata(pdev->dev.parent);
+	if (!priv->axp20x) {
+		dev_err(&pdev->dev, "Failed to get parent data\n");
+		return -ENXIO;
+	}
+
+	mutex_init(&priv->lock);
+
+	priv->cdev.brightness_set_blocking = axp20x_led_brightness_set_blocking;
+	priv->cdev.brightness_get = axp20x_led_brightness_get;
+	priv->cdev.groups = axp20x_led_groups;
+
+	ret = axp20x_led_parse_dt(priv, pdev->dev.of_node);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to set parameters\n");
+		return ret;
+	}
+
+	/**
+	 * For some reason in AXP209 the bit that controls CHGLED is with
+	 * inverted logic compared to all other PMICs.
+	 * If the PMIC is actually AXP209, set inverted flag and later use it
+	 * when configuring the LED.
+	 */
+	if (priv->axp20x->variant == AXP209_ID)
+		priv->ctrl_inverted = 1;
+
+	ret =  axp20x_led_setup(priv);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to configure led");
+		return ret;
+	}
+
+	return devm_led_classdev_register(&pdev->dev, &priv->cdev);
+}
+
+static struct platform_driver axp20x_led_driver = {
+	.driver = {
+		.name	= "axp20x-led",
+		.of_match_table = of_match_ptr(axp20x_led_of_match),
+	},
+	.probe = axp20x_led_probe,
+};
+
+module_platform_driver(axp20x_led_driver);
+
+MODULE_AUTHOR("Stefan Mavrodiev <stefan@olimex.com");
+MODULE_DESCRIPTION("X-Powers PMIC CHGLED driver");
+MODULE_LICENSE("GPL");
-- 
2.17.1


  reply	other threads:[~2019-02-15 11:50 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-02-15 11:50 [PATCH v2 0/8] leds: Add AXP20X CHGLED Stefan Mavrodiev
2019-02-15 11:50 ` Stefan Mavrodiev [this message]
2019-02-15 14:07   ` [PATCH v2 1/8] leds: Add support for " Stefan Mavrodiev
2019-02-15 15:57   ` Maxime Ripard
2019-02-15 18:32     ` Pavel Machek
2019-02-19  7:42       ` Stefan Mavrodiev
2019-02-15 11:50 ` [PATCH v2 2/8] mfd: axp20x: Add axp20x-led cell Stefan Mavrodiev
2019-03-25 10:01   ` Lee Jones
2019-02-15 11:50 ` [PATCH v2 3/8] dt-bindings: leds: Add binding for axp20x-led device driver Stefan Mavrodiev
2019-02-22 19:51   ` Rob Herring
2019-02-23 13:02     ` Jacek Anaszewski
2019-02-15 11:50 ` [PATCH v2 4/8] arm64: dts: allwinner: axp803: add charge led node Stefan Mavrodiev
2019-02-15 11:50 ` [PATCH v2 5/8] arm64: dts: allwinner: Enable AXP803 CHGLED for Olimex boards Stefan Mavrodiev
2019-02-15 18:49   ` Pavel Machek
2019-02-19  7:43     ` Stefan Mavrodiev
2019-02-15 11:50 ` [PATCH v2 6/8] arm: dts: axpxx: add charge led node Stefan Mavrodiev
2019-02-15 11:50 ` [PATCH v2 7/8] ARM: dts: sun7i: Enable AXP209 CHGLED for Olimex boards Stefan Mavrodiev
2019-02-15 11:50 ` [PATCH v2 8/8] ARM: dts: sun8i: a33: Enable AXP223 CHGLED for A33-OLinuXino Stefan Mavrodiev

Reply instructions:

You may reply publicly 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=20190215115013.11098-2-stefan@olimex.com \
    --to=stefan@olimex.com \
    --cc=devicetree@vger.kernel.org \
    --cc=jacek.anaszewski@gmail.com \
    --cc=lee.jones@linaro.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=maxime.ripard@bootlin.com \
    --cc=pavel@ucw.cz \
    --cc=robh+dt@kernel.org \
    --cc=wens@csie.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
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).