build/patch/kernel/archive/sunxi-5.10/0002-Add-leds-axp20-x-charger.patch

552 lines
15 KiB
Diff
Raw Normal View History

diff --git a/Documentation/devicetree/bindings/leds/leds-axp20x.txt b/Documentation/devicetree/bindings/leds/leds-axp20x.txt
new file mode 100644
index 000000000..fef2db500
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-axp20x.txt
@@ -0,0 +1,73 @@
+Device Tree Bindings for LED support on X-Powers PMIC
+
+Most of the X-Powers PMICs have integrated battery charger with LED indicator.
+The output is open-drain, so the state is either high-Z or output-low. The
+driver is a subnode of AXP20X MFD driver, since it uses shared bus with all
+other cells.
+The LED can be controlled either manually or automatically. Then in automatic
+(controlled by the charger) there are two indication modes:
+
+Mode-A
+======
+- output-low: Charging
+- high-Z: Not charging
+- 1Hz flashing: Abnormal alarm
+- 4Hz flashing: Overvoltage alarm
+
+Mode-B
+======
+- output-low: Battery full
+- high-Z: Not charging
+- 1Hz flashing: Charging
+- 4Hz flashing: Overvoltage or abnormal alarm
+
+The control and the mode can be changed from sysfs.
+
+For AXP20X MFD bindings see:
+Documentation/devicetree/bindings/mfd/axp20x.txt
+
+Required properties:
+- compatible : Must be "x-powers,axp20x-led"
+
+Supported common LED properties, see ./common.txt for more informationn
+- label : See Documentation/devicetree/bindings/leds/common.txt
+- linux,default-trigger : See Documentation/devicetree/bindings/leds/common.txt
+- default-state: See Documentation/devicetree/bindings/leds/common.txt
+
+Optional properties:
+- x-powers,charger-mode: 0 for Mode-A, 1 for Mode-B
+ If omitted, then the control is set to manual mode.
+ On invalid value, Mode-A is used.
+
+Example:
+
+ axp803: pmic@3a3 {
+ compatible = "x-powers,axp803";
+
+ ...
+
+ led@0 {
+ compatible = "x-powers,axp20x-led";
+ status = "okay";
+
+ label = "axp20x:yellow:chgled";
+ linux,default-trigger = "timer";
+ default-state = "on";
+ };
+ };
+
+or
+
+ axp803: pmic@3a3 {
+ compatible = "x-powers,axp803";
+
+ ...
+
+ led@0 {
+ compatible = "x-powers,axp20x-led";
+ status = "okay";
+
+ label = "axp20x:yellow:chgled";
+ x-powers,charger-mode = <1>;
+ };
+ };
diff --git a/arch/arm/boot/dts/axp209.dtsi b/arch/arm/boot/dts/axp209.dtsi
index 0d9ff12bd..f972b6f3e 100644
--- a/arch/arm/boot/dts/axp209.dtsi
+++ b/arch/arm/boot/dts/axp209.dtsi
@@ -69,6 +69,11 @@ axp_gpio: gpio {
#gpio-cells = <2>;
};
+ axp_led: led {
+ compatible = "x-powers,axp20x-led";
+ status = "disabled";
+ };
+
battery_power_supply: battery-power-supply {
compatible = "x-powers,axp209-battery-power-supply";
status = "disabled";
diff --git a/arch/arm/boot/dts/axp22x.dtsi b/arch/arm/boot/dts/axp22x.dtsi
index 65a07a67a..92a0b6425 100644
--- a/arch/arm/boot/dts/axp22x.dtsi
+++ b/arch/arm/boot/dts/axp22x.dtsi
@@ -62,6 +62,11 @@ axp_adc: adc {
#io-channel-cells = <1>;
};
+ axp_led: led {
+ compatible = "x-powers,axp20x-led";
+ status = "disabled";
+ };
+
battery_power_supply: battery-power-supply {
compatible = "x-powers,axp221-battery-power-supply";
status = "disabled";
diff --git a/arch/arm/boot/dts/axp81x.dtsi b/arch/arm/boot/dts/axp81x.dtsi
index 00b092f94..7d2bfa42d 100644
--- a/arch/arm/boot/dts/axp81x.dtsi
+++ b/arch/arm/boot/dts/axp81x.dtsi
@@ -74,6 +74,11 @@ gpio1_ldo: gpio1-ldo {
};
};
+ axp_led: led {
+ compatible = "x-powers,axp20x-led";
+ status = "disabled";
+ };
+
battery_power_supply: battery-power-supply {
compatible = "x-powers,axp813-battery-power-supply";
status = "disabled";
diff --git a/arch/arm64/boot/dts/allwinner/axp803.dtsi b/arch/arm64/boot/dts/allwinner/axp803.dtsi
index 43276d88b..ac484b5da 100644
--- a/arch/arm64/boot/dts/allwinner/axp803.dtsi
+++ b/arch/arm64/boot/dts/allwinner/axp803.dtsi
@@ -38,6 +38,11 @@ gpio1_ldo: gpio1-ldo {
};
};
+ axp_led: led {
+ compatible = "x-powers,axp20x-led";
+ status = "disabled";
+ };
+
battery_power_supply: battery-power-supply {
compatible = "x-powers,axp803-battery-power-supply",
"x-powers,axp813-battery-power-supply";
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index c59034432..937fbffae 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -887,6 +887,16 @@ config LEDS_SGM3140
This option enables support for the SGM3140 500mA Buck/Boost Charge
Pump LED Driver.
+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 d6b8a792c..ee4be5be3 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -89,6 +89,7 @@ obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o
obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o
obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o
obj-$(CONFIG_LEDS_WRAP) += leds-wrap.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 000000000..2d5ae1c08
--- /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");
diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
index 6c0d66ad6..c2272280a 100644
--- a/drivers/mfd/axp20x.c
+++ b/drivers/mfd/axp20x.c
@@ -616,6 +616,9 @@ static const struct mfd_cell axp20x_cells[] = {
.of_compatible = "x-powers,axp202-usb-power-supply",
.num_resources = ARRAY_SIZE(axp20x_usb_power_supply_resources),
.resources = axp20x_usb_power_supply_resources,
+ }, {
+ .name = "axp20x-led",
+ .of_compatible = "x-powers,axp20x-led",
},
};
@@ -642,6 +645,9 @@ static const struct mfd_cell axp221_cells[] = {
.of_compatible = "x-powers,axp221-usb-power-supply",
.num_resources = ARRAY_SIZE(axp22x_usb_power_supply_resources),
.resources = axp22x_usb_power_supply_resources,
+ }, {
+ .name = "axp20x-led",
+ .of_compatible = "x-powers,axp20x-led",
},
};
@@ -668,6 +674,9 @@ static const struct mfd_cell axp223_cells[] = {
.of_compatible = "x-powers,axp223-usb-power-supply",
.num_resources = ARRAY_SIZE(axp22x_usb_power_supply_resources),
.resources = axp22x_usb_power_supply_resources,
+ }, {
+ .name = "axp20x-led",
+ .of_compatible = "x-powers,axp20x-led",
},
};
@@ -725,6 +734,9 @@ static const struct mfd_cell axp288_cells[] = {
.resources = axp288_power_button_resources,
}, {
.name = "axp288_pmic_acpi",
+ }, {
+ .name = "axp20x-led",
+ .of_compatible = "x-powers,axp20x-led",
},
};
@@ -753,7 +765,12 @@ static const struct mfd_cell axp803_cells[] = {
.resources = axp803_usb_power_supply_resources,
.of_compatible = "x-powers,axp813-usb-power-supply",
},
- { .name = "axp20x-regulator" },
+ {
+ .name = "axp20x-regulator"
+ }, {
+ .name = "axp20x-led",
+ .of_compatible = "x-powers,axp20x-led",
+ },
};
static const struct mfd_cell axp806_self_working_cells[] = {
@@ -780,6 +797,9 @@ static const struct mfd_cell axp809_cells[] = {
}, {
.id = 1,
.name = "axp20x-regulator",
+ }, {
+ .name = "axp20x-led",
+ .of_compatible = "x-powers,axp20x-led",
},
};
@@ -809,6 +829,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-led",
+ .of_compatible = "x-powers,axp20x-led",
},
};