diff --git a/Documentation/devicetree/bindings/leds/leds-qcom-clk.txt b/Documentation/devicetree/bindings/leds/leds-qcom-clk.txt new file mode 100644 index 000000000000..d3e4d95ef646 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-qcom-clk.txt @@ -0,0 +1,67 @@ +Qualcomm Technologies, Inc. clock controller based PWM driver +------------------------------------------------------------- + +Qualcomm Technologies, Inc. provides clock controller to be connected to LED +to generate PWM. This driver can configure duty-cycle of clock to get the +required brightness. The complete clock duty-cycle is 100 and brightness is +denoted by the period in which signal is active or high. + +Required properties: +- compatible: Should be "qcom,clk-led-pwm". +- clock_names: Should be "core". +- clocks: Should contain the phandle of the source clock. +- assgined-clocks: Phandle of the source clock. +- assinged-clock-rates: Desired frequency of the source clock. +- qcom,max_duty: Maximum duty cycle that can be set on the clock. + Should be less than 100. +- pinctrl-names: Property should contain "active" and "sleep" for the + pin configurations during the usecase and during idle. +- pinctrl-x: phandle to the default/sleep pin configurations. + +Optional properties: +- qcom,label: Label for the sysfs node. + + +Example: + +qcom_clk_led { + compatible = "qcom,clk-led-pwm"; + qcom,label = "led_clk_pwm"; + qcom,max_duty = <80>; + clocks = <&clock_gcc GCC_GP2_CLK>; + clock-names = "core"; + assigned-clocks = <&clock_gcc GCC_GP2_CLK>; + assigned-clock-rates = <80000>; + pinctrl-names = "active", "sleep"; + pinctrl-0 = <&qcom_clk_led_gp2_active>; + pinctrl-1 = <&qcom_clk_led_gp2_sleep>; +}; + +== Pinctrl configurations == +qcom_clk_led_gp2_pins: qcom_clk_led_gp2_pins { + qcom_clk_led_gp2_active: qcom_clk_led_gp2_active { + mux { + pins = "gpio21"; + function = "gcc_gp2"; + }; + + config { + pins = "gpio21"; + drive-strength = <2>; + bias-disable; + }; + }; + + qcom_clk_led_gp2_sleep: qqcom_clk_led_gp2_sleep { + mux { + pins = "gpio21"; + function = "gpio"; + }; + + config { + pins = "gpio21"; + drive-strength = <2>; + bias-pull-down; + }; + }; +}; diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 1ab5f21c0a0c..b07a31619609 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -660,6 +660,15 @@ config LEDS_IS31FL32XX LED controllers. They are I2C devices with multiple constant-current channels, each with independent 256-level PWM control. +config LEDS_QCOM_CLK + tristate "LED Support for cock controller based PWM drvier" + depends on OF && PINCTRL && LEDS_CLASS + help + This option enables the driver for clock driven LED. + This driver controls the duty cycle of clocks generated + by Qualcomm Technologies, Inc Chipsets. + It also configures the pinctrl to output the clock. + comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)" config LEDS_BLINKM diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 9ea6d8a1cd6d..8080fbff0a01 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.o obj-$(CONFIG_LEDS_PCA9956B) += leds-pca9956b.o obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o +obj-$(CONFIG_LEDS_QCOM_CLK) += leds-qcom-clk.o obj-$(CONFIG_LEDS_QPNP_FLASH_V2) += leds-qpnp-flash-v2.o obj-$(CONFIG_LEDS_QPNP_HAPTICS) += leds-qpnp-haptics.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o diff --git a/drivers/leds/leds-qcom-clk.c b/drivers/leds/leds-qcom-clk.c new file mode 100644 index 000000000000..cbb5851e440d --- /dev/null +++ b/drivers/leds/leds-qcom-clk.c @@ -0,0 +1,189 @@ +/* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#define PINCTRL_ACTIVE "active" +#define PINCTRL_SLEEP "sleep" +#define CLK_DUTY_DEN 100 + +struct led_qcom_clk_priv { + struct led_classdev cdev; + struct clk *core; + struct pinctrl *pinctrl; + struct pinctrl_state *gpio_active; + struct pinctrl_state *gpio_sleep; + const char *name; + int duty_cycle, max_duty; + bool clk_enabled; +}; + +static int qcom_clk_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_qcom_clk_priv *led_priv = + container_of(led_cdev, struct led_qcom_clk_priv, cdev); + int rc; + + if (brightness == LED_OFF) { + if (led_priv->clk_enabled) { + clk_disable_unprepare(led_priv->core); + pinctrl_select_state(led_priv->pinctrl, + led_priv->gpio_sleep); + led_priv->clk_enabled = false; + } + return 0; + } + + if (!led_priv->clk_enabled) { + rc = pinctrl_select_state(led_priv->pinctrl, + led_priv->gpio_active); + if (rc < 0) { + dev_err(led_cdev->dev, "Failed to select pinctrl state rc=%d\n", + rc); + return rc; + } + + rc = clk_prepare_enable(led_priv->core); + if (rc < 0) { + dev_err(led_cdev->dev, "Failed to enable clock rc=%d\n", + rc); + goto err_enable; + } + + led_priv->clk_enabled = true; + } + + /* + * Duty cycle will be configured based on the brightness. + * Complete clock period/duty cycle is 100 and + * brightness is the period in which signal is active. + */ + led_priv->duty_cycle = brightness; + rc = clk_set_duty_cycle(led_priv->core, + led_priv->duty_cycle, CLK_DUTY_DEN); + if (rc < 0) { + dev_err(led_cdev->dev, "Failed to set duty cycle rc=%d\n", + rc); + goto err_set_duty; + } + + return rc; + +err_set_duty: + clk_disable_unprepare(led_priv->core); + led_priv->clk_enabled = false; +err_enable: + pinctrl_select_state(led_priv->pinctrl, led_priv->gpio_sleep); + + return rc; +} + +static int qcom_clk_led_parse_dt(struct device *dev, + struct led_qcom_clk_priv *led) +{ + int ret; + + led->name = of_get_property(dev->of_node, "qcom,label", NULL) ? : + dev->of_node->name; + + ret = of_property_read_u32(dev->of_node, "qcom,max_duty", + &led->max_duty); + if (ret) { + dev_err(dev, "Failed to read max_duty\n"); + return ret; + } + + if (led->max_duty <= 0) { + dev_err(dev, "Max duty cycle should not be <= 0\n"); + return -EINVAL; + } + + led->core = devm_clk_get(dev, "core"); + if (IS_ERR(led->core)) { + ret = PTR_ERR(led->core); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get core source\n"); + + return ret; + } + + led->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR_OR_NULL(led->pinctrl)) { + dev_err(dev, "No pinctrl config specified!\n"); + return PTR_ERR(led->pinctrl); + } + + led->gpio_active = pinctrl_lookup_state(led->pinctrl, + PINCTRL_ACTIVE); + if (IS_ERR_OR_NULL(led->gpio_active)) { + dev_err(dev, "No default config specified!\n"); + return PTR_ERR(led->gpio_active); + } + + led->gpio_sleep = pinctrl_lookup_state(led->pinctrl, + PINCTRL_SLEEP); + if (IS_ERR_OR_NULL(led->gpio_sleep)) { + dev_err(dev, "No sleep config specified!\n"); + return PTR_ERR(led->gpio_sleep); + } + + return 0; +} + +static int led_qcom_clk_probe(struct platform_device *pdev) +{ + struct led_qcom_clk_priv *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct led_qcom_clk_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ret = qcom_clk_led_parse_dt(&pdev->dev, priv); + if (ret) + return ret; + + priv->cdev.name = priv->name; + priv->cdev.brightness = LED_OFF; + priv->cdev.max_brightness = priv->max_duty; + priv->cdev.flags = LED_CORE_SUSPENDRESUME; + priv->cdev.brightness_set_blocking = qcom_clk_led_set; + + return devm_led_classdev_register(&pdev->dev, &priv->cdev); +} + +static const struct of_device_id of_qcom_clk_leds_match[] = { + { .compatible = "qcom,clk-led-pwm", }, + {} +}; +MODULE_DEVICE_TABLE(of, of_qcom_clk_leds_match); + +static struct platform_driver led_qcom_clk_driver = { + .probe = led_qcom_clk_probe, + .driver = { + .name = "led_qcom_clk_pwm", + .of_match_table = of_qcom_clk_leds_match, + }, +}; + +module_platform_driver(led_qcom_clk_driver); + +MODULE_DESCRIPTION("Generic clock driven LED driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:leds-qcom-clk");