Received: by 2002:ac0:946b:0:0:0:0:0 with SMTP id j40csp874317imj; Fri, 15 Feb 2019 08:12:42 -0800 (PST) X-Google-Smtp-Source: AHgI3IaifOXRrYN49kkPyCmh4UGA2ZTueOWJUliqykLClOM0+Ehl7qhCknIUTRrrPRbDQysfHVMJ X-Received: by 2002:a17:902:6b49:: with SMTP id g9mr11087845plt.291.1550247162829; Fri, 15 Feb 2019 08:12:42 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1550247162; cv=none; d=google.com; s=arc-20160816; b=GetPXK7kccn8ngrG8wmXwKISJ2PRb0ctq7UR3NAComZXNMoAuI3pZ3Eo1tE4k9w3Ks ZUkm5Md1WSJA1ayFBPr6C3RrGeuAb80CGoSuuH6zKCFhcZVkYfi5I4rO/7oIFBgNUwpC Knpk6agAJTvFjrqxrF7WYeX4UTDFhrD2Eg1+YE3jZgmL8O0C7Mu0Bt+ENf+RLpeO/e3r efehT+a7egVzcYwnQ1nOEmzc4EDllgdEF15uevLdsresN7D5BrN1y/Nt8KgYS3WJ5oTr egxUJYbNwLCWtewXgbAeIBVw1/lVZDOinc03Znx80W1NxMOmUh14cENlG3bx1zWwX5O8 7/kg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from; bh=AhOWnnddz6esRSKEO+5Kvi/iPROIaduRUjXjyCig5hc=; b=LP+tcsf2QFLrvwcljhLQpf3K6v84+wTyai56x9lylNKZedOkJBfa/Z8rjVZazWNypj z/0vHmYJuWr1HvIgZ7M4/hQcHARHyW4Vhv8ZtU9wMyzKq68+tecnl7RbZY7s4PQtKQAR yE9Snci9Mcfq/fs37s31vX8gxp7zRiSusN8rX11ftYsI0rnwwWkmDSeVMTJh47HWoJqS 7BtXwQ2lywvnPGmMaizsba63c6zxoZFY61Mi0r5mNHv7/thKHIxN/nesIEuUXEEUnUBB p99+Zy2rOduj+pS4wtJ9c6cIql6ueqFluG2sDc0pRP1w0gCugXUYY1k4fi8rznGFikg2 uWeg== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id p9si6045684pgc.448.2019.02.15.08.12.26; Fri, 15 Feb 2019 08:12:42 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2436705AbfBOLun (ORCPT + 99 others); Fri, 15 Feb 2019 06:50:43 -0500 Received: from olimex.com ([184.105.72.32]:53246 "EHLO olimex.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1731970AbfBOLum (ORCPT ); Fri, 15 Feb 2019 06:50:42 -0500 Received: from localhost.localdomain ([195.238.85.143]) by olimex.com with ESMTPSA (ECDHE-RSA-AES128-GCM-SHA256:TLSv1.2:Kx=ECDH:Au=RSA:Enc=AESGCM(128):Mac=AEAD) (SMTP-AUTH username stefan@olimex.com, mechanism PLAIN) for ; Fri, 15 Feb 2019 03:50:39 -0800 From: Stefan Mavrodiev To: Jacek Anaszewski , Pavel Machek , Rob Herring , Mark Rutland , Chen-Yu Tsai , Maxime Ripard , Lee Jones , 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 Subject: [PATCH v2 1/8] leds: Add support for AXP20X CHGLED Date: Fri, 15 Feb 2019 13:50:06 +0200 Message-Id: <20190215115013.11098-2-stefan@olimex.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190215115013.11098-1-stefan@olimex.com> References: <20190215115013.11098-1-stefan@olimex.com> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 --- 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#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