leds: qcom-clk: Add clock controller based PWM driver
Duty cycle of the clock can be controlled by D register of the RCG. Add support for configuring the duty cyles through LED framework's brightness. Pinctrl settings are needed for configuring the pins to output the core clock on the pin. Change-Id: Id2dd3ef34943c07731e27ca8b7e1d04d1b8e231e Signed-off-by: Veera Vegivada <vvegivad@codeaurora.org>
This commit is contained in:
67
Documentation/devicetree/bindings/leds/leds-qcom-clk.txt
Normal file
67
Documentation/devicetree/bindings/leds/leds-qcom-clk.txt
Normal file
@@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
189
drivers/leds/leds-qcom-clk.c
Normal file
189
drivers/leds/leds-qcom-clk.c
Normal file
@@ -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 <linux/clk.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#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");
|
||||
Reference in New Issue
Block a user