Received: by 2002:ac0:946b:0:0:0:0:0 with SMTP id j40csp882328imj; Fri, 15 Feb 2019 08:19:52 -0800 (PST) X-Google-Smtp-Source: AHgI3IadguYg+icqlpIuoCuzCMsZHZ4WszN3lTdVLys1Mx+kkV7UgwaPHcRc/1kRE1vCXScd3nL6 X-Received: by 2002:a17:902:7889:: with SMTP id q9mr11150891pll.134.1550247592419; Fri, 15 Feb 2019 08:19:52 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1550247592; cv=none; d=google.com; s=arc-20160816; b=qu8RV2+C4+t/CU/W/6oI++l9qaMK6CLwA1vpRJ3dLeYA2Jzw5Kp4BCwPiM3bf3GTRM GJyfPAWb9LKpn2aPamRF0B9YheSKLEdFIizAyO5Eu+0rl/GN18xrOkBPHZgnbaUmGqge 9QVINBGyMNmjT2MPaD/f8Q1p3pYa2Fl0L3VGK/LHSa71aLGyzuuVGS3zHnfie+2JHHww zyx/uLqRFoR2kYdcvTQE1Er1RP6kRuHKP9dca160Imju+Sa5tRHOGJeRcV1UqlhjD2RT IzLRCRRXQq1h9dfCnjznNEQZkklL0Zo+14d45I1dCEYngOqlA0AWOng6+y3OTRXAp0/X ssuQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-language :content-transfer-encoding:in-reply-to:mime-version:user-agent:date :message-id:from:references:to:subject; bh=dONfNNUR5aD3hztAJZudjHAHToqRqKdfPUWhIRk2aDs=; b=r7oBanNg2T+gy9Sb/8SdUGWun5Yc2utOmLtRPl/3MSSdNTLsKrOilWZ16CpSFoV8cp vUvkkaCOy5eA5lHdLHFlB8he7tsX1BDdF+PX7AM1UoI/SVFb2Var+FziWffwsa0DaKK1 W+qPQccLPh7fw+zUM4rih/rk4SBObwtlWUo0Nrv2XJK3PSyhJIyDkPod4E3maHQYohlR BDLe8heiYjLkQCBuYm2Fo7r4sxcvIR4RVXYazT1qhqoJXr26DI44mOLP5AZsJuLViu24 MX02B0PbkwuSbP2T+vVNBDJIrmNDfXbEdbd9Ptx8RnfCjKkGN+jnsEH+GEKNH7e+hFBT 6tAA== 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 6si6086680plc.241.2019.02.15.08.19.35; Fri, 15 Feb 2019 08:19:52 -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 S2405421AbfBOOHn (ORCPT + 99 others); Fri, 15 Feb 2019 09:07:43 -0500 Received: from olimex.com ([184.105.72.32]:33620 "EHLO olimex.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2389389AbfBOOHl (ORCPT ); Fri, 15 Feb 2019 09:07:41 -0500 Received: from 195.238.85.143 ([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 06:07:37 -0800 Subject: Re: [PATCH v2 1/8] leds: Add support for AXP20X CHGLED To: Stefan Mavrodiev , Jacek Anaszewski , Pavel Machek , Rob Herring , Mark Rutland , Chen-Yu Tsai , Maxime Ripard , Lee Jones , "open list:LED SUBSYSTEM" , "open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS" , "open list:X-POWERS MULTIFUNCTION PMIC DEVICE DRIVERS" , "moderated list:ARM/Allwinner sunXi SoC support" References: <20190215115013.11098-1-stefan@olimex.com> <20190215115013.11098-2-stefan@olimex.com> From: Stefan Mavrodiev Message-ID: Date: Fri, 15 Feb 2019 16:07:25 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.4.0 MIME-Version: 1.0 In-Reply-To: <20190215115013.11098-2-stefan@olimex.com> Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 7bit Content-Language: en-US Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On 2/15/19 1:50 PM, Stefan Mavrodiev wrote: > 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 > + The copyright header is wrong. It should be "Copyright 2019 Olimex Ltd.". I'd like to fix it before the patchis merged (eventually). Regards, 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 +MODULE_DESCRIPTION("X-Powers PMIC CHGLED driver"); > +MODULE_LICENSE("GPL");