lkml.org 
[lkml]   [2020]   [Feb]   [23]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCH 2/4] leds: axp20x: Support charger LED on AXP20x like PMICs
Date
There is single LED that can be turned on and off by the user, or set to
be controlled by the charger in 2 different modes.

The driver initializes the LED to be controlled by the charger, but
allows to switch it to user control, and change the mode of charging
indication via a sysfs.

The driver was developed on AXP813, but should work on other PMICs like
that without changes.

Signed-off-by: Ondrej Jirman <megous@megous.com>
---
drivers/leds/Kconfig | 7 ++
drivers/leds/Makefile | 1 +
drivers/leds/leds-axp20x.c | 240 +++++++++++++++++++++++++++++++++++++
drivers/mfd/axp20x.c | 3 +
4 files changed, 251 insertions(+)
create mode 100644 drivers/leds/leds-axp20x.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index d82f1dea37111..80a3f31f6f4c3 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -846,6 +846,13 @@ config LEDS_TPS6105X
It is a single boost converter primarily for white LEDs and
audio amplifiers.

+config LEDS_AXP20X
+ tristate "Charger LED support for AXP20X-like PMICs (AXP813, ...)"
+ depends on LEDS_CLASS && MFD_AXP20X
+ help
+ This option enables support for on-chip LED driver on
+ AXP20X-like PMICs.
+
comment "LED Triggers"
source "drivers/leds/trigger/Kconfig"

diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index d7e1107753fb1..80ea1bc4744b0 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -86,6 +86,7 @@ obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o
obj-$(CONFIG_LEDS_LM3697) += leds-lm3697.o
obj-$(CONFIG_LEDS_LM36274) += leds-lm36274.o
obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.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 0000000000000..e6c9853b84d52
--- /dev/null
+++ b/drivers/leds/leds-axp20x.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LED Driver for X-Powers AXP813 PMIC and similar.
+ *
+ * Copyright(c) 2020 Ondrej Jirman <megous@megous.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/mfd/axp20x.h>
+
+#define AXP20X_CHGLED_CTRL_MASK BIT(3)
+#define AXP20X_CHGLED_CTRL_CHARGER BIT(3)
+#define AXP20X_CHGLED_CTRL_USER 0
+
+#define AXP20X_CHRG_CTRL2_MODE BIT(4)
+
+#define AXP20X_CHGLED_USER_STATE_MASK GENMASK(5, 4)
+#define AXP20X_CHGLED_USER_STATE_OFF (0 << 4)
+#define AXP20X_CHGLED_USER_STATE_BLINK_SLOW (1 << 4)
+#define AXP20X_CHGLED_USER_STATE_BLINK_FAST (2 << 4)
+#define AXP20X_CHGLED_USER_STATE_ON (3 << 4)
+
+struct axp20x_led {
+ struct led_classdev cdev;
+ struct regmap *regmap;
+};
+
+static int axp20x_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct axp20x_led *led =
+ container_of(led_cdev, struct axp20x_led, cdev);
+ unsigned int val;
+
+ val = value == LED_OFF ? AXP20X_CHGLED_USER_STATE_OFF :
+ AXP20X_CHGLED_USER_STATE_ON;
+
+ return regmap_update_bits(led->regmap, AXP20X_OFF_CTRL,
+ AXP20X_CHGLED_USER_STATE_MASK, val);
+
+}
+
+static ssize_t charger_control_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct axp20x_led *led =
+ container_of(led_cdev, struct axp20x_led, cdev);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(led->regmap, AXP20X_OFF_CTRL, &val);
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n",
+ val & AXP20X_CHGLED_CTRL_MASK ? 1 : 0);
+}
+
+static ssize_t charger_control_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct axp20x_led *led =
+ container_of(led_cdev, struct axp20x_led, cdev);
+ bool status;
+ int ret;
+
+ ret = kstrtobool(buf, &status);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(led->regmap, AXP20X_OFF_CTRL,
+ AXP20X_CHGLED_CTRL_MASK,
+ status ? AXP20X_CHGLED_CTRL_CHARGER :
+ AXP20X_CHGLED_CTRL_USER);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t charger_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct axp20x_led *led =
+ container_of(led_cdev, struct axp20x_led, cdev);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(led->regmap, AXP20X_CHRG_CTRL2, &val);
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n",
+ val & AXP20X_CHRG_CTRL2_MODE ? 1 : 0);
+}
+
+static ssize_t charger_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct axp20x_led *led =
+ container_of(led_cdev, struct axp20x_led, cdev);
+ unsigned int mode;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &mode);
+ if (ret)
+ return ret;
+
+ if (mode > 1)
+ return -ERANGE;
+
+ ret = regmap_update_bits(led->regmap, AXP20X_CHRG_CTRL2,
+ AXP20X_CHRG_CTRL2_MODE,
+ mode ? AXP20X_CHRG_CTRL2_MODE : 0);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static DEVICE_ATTR_RW(charger_control);
+static DEVICE_ATTR_RW(charger_mode);
+
+static struct attribute *axp20x_led_attrs[] = {
+ &dev_attr_charger_control.attr,
+ &dev_attr_charger_mode.attr,
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(axp20x_led);
+
+static int axp20x_led_probe(struct platform_device *pdev)
+{
+ struct axp20x_dev *axp20x;
+ struct axp20x_led *led;
+ int ret;
+
+ if (!of_device_is_available(pdev->dev.of_node))
+ return -ENODEV;
+
+ axp20x = dev_get_drvdata(pdev->dev.parent);
+ if (!axp20x)
+ return -EINVAL;
+
+ led = devm_kzalloc(&pdev->dev,
+ sizeof(struct axp20x_led),
+ GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, led);
+
+ led->regmap = axp20x->regmap;
+
+ led->cdev.name = "axp20x-chgarger-led";
+ led->cdev.brightness_set_blocking = axp20x_led_set;
+ led->cdev.brightness = LED_OFF;
+ led->cdev.max_brightness = 1;
+ led->cdev.groups = axp20x_led_groups,
+
+ ret = devm_led_classdev_register(pdev->dev.parent, &led->cdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register device %s\n",
+ led->cdev.name);
+ return ret;
+ }
+
+ ret = regmap_update_bits(led->regmap, AXP20X_OFF_CTRL,
+ AXP20X_CHGLED_CTRL_MASK,
+ AXP20X_CHGLED_CTRL_CHARGER);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to enable charger control\n");
+ return ret;
+ }
+
+ ret = axp20x_led_set(&led->cdev, led->cdev.brightness);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to init led brightness\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void axp20x_led_shutdown(struct platform_device *pdev)
+{
+ struct axp20x_led *led = platform_get_drvdata(pdev);
+
+ /* On shutdown, we want to give LED control back to the PMIC,
+ * so that it doesn't stay on, while the system is off.
+ */
+
+ axp20x_led_set(&led->cdev, LED_OFF);
+
+ regmap_update_bits(led->regmap, AXP20X_OFF_CTRL,
+ AXP20X_CHGLED_CTRL_MASK,
+ AXP20X_CHGLED_CTRL_CHARGER);
+}
+
+static int axp20x_led_remove(struct platform_device *pdev)
+{
+ axp20x_led_shutdown(pdev);
+
+ return 0;
+}
+
+static const struct of_device_id axp20x_leds_of_match[] = {
+ { .compatible = "x-powers,axp813-charger-led", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, axp20x_leds_of_match);
+
+static struct platform_driver axp20x_led_driver = {
+ .driver = {
+ .name = "leds-axp20x",
+ .of_match_table = axp20x_leds_of_match,
+ },
+ .probe = axp20x_led_probe,
+ .remove = axp20x_led_remove,
+ .shutdown = axp20x_led_shutdown,
+};
+
+module_platform_driver(axp20x_led_driver);
+
+MODULE_AUTHOR("Ondrej Jirman <megous@megous.com>");
+MODULE_DESCRIPTION("LED driver for AXP813 PMIC");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:leds-axp20x");
diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
index aa59496e43768..dcd68397b9d23 100644
--- a/drivers/mfd/axp20x.c
+++ b/drivers/mfd/axp20x.c
@@ -806,6 +806,9 @@ static const struct mfd_cell axp813_cells[] = {
.num_resources = ARRAY_SIZE(axp803_usb_power_supply_resources),
.resources = axp803_usb_power_supply_resources,
.of_compatible = "x-powers,axp813-usb-power-supply",
+ }, {
+ .name = "axp20x-charger-led",
+ .of_compatible = "x-powers,axp813-charger-led",
},
};

--
2.25.1
\
 
 \ /
  Last update: 2020-02-23 14:16    [W:0.152 / U:0.620 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site