leds: pm8xxx: Add PWM feature in leds-pm8xxx
Pass PWM period and other configuration from board file.
Update the LPG LUT based on the duty cycles,
pause count, and period.
Change-Id: Ie1812ae8010d986a943af6f3883b13c25bc7244c
Signed-off-by: Jay Chokshi <jchokshi@codeaurora.org>
diff --git a/drivers/leds/leds-pm8xxx.c b/drivers/leds/leds-pm8xxx.c
index 94af3ce..025b756 100644
--- a/drivers/leds/leds-pm8xxx.c
+++ b/drivers/leds/leds-pm8xxx.c
@@ -18,8 +18,10 @@
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/workqueue.h>
+#include <linux/err.h>
#include <linux/mfd/pm8xxx/core.h>
+#include <linux/mfd/pm8xxx/pwm.h>
#include <linux/leds-pm8xxx.h>
#define SSBI_REG_ADDR_DRV_KEYPAD 0x48
@@ -54,6 +56,8 @@
#define PM8XXX_LED_OFFSET(id) ((id) - PM8XXX_ID_LED_0)
+#define PM8XXX_LED_PWM_FLAGS (PM_PWM_LUT_LOOP | PM_PWM_LUT_RAMP_UP)
+
/**
* struct pm8xxx_led_data - internal led data structure
* @led_classdev - led class device
@@ -61,6 +65,10 @@
* @work - workqueue for led
* @lock - to protect the transactions
* @reg - cached value of led register
+ * @pwm_dev - pointer to PWM device if LED is driven using PWM
+ * @pwm_channel - PWM channel ID
+ * @pwm_period_us - PWM period in micro seconds
+ * @pwm_duty_cycles - struct that describes PWM duty cycles info
*/
struct pm8xxx_led_data {
struct led_classdev cdev;
@@ -69,6 +77,10 @@
struct device *dev;
struct work_struct work;
struct mutex lock;
+ struct pwm_device *pwm_dev;
+ int pwm_channel;
+ u32 pwm_period_us;
+ struct pm8xxx_pwm_duty_cycles *pwm_duty_cycles;
};
static void led_kp_set(struct pm8xxx_led_data *led, enum led_brightness value)
@@ -133,6 +145,26 @@
led->id, rc);
}
+static int pm8xxx_led_pwm_work(struct pm8xxx_led_data *led)
+{
+ int duty_us;
+ int rc = 0;
+
+ if (led->pwm_duty_cycles == NULL) {
+ duty_us = (led->pwm_period_us * led->cdev.brightness) /
+ LED_FULL;
+ rc = pwm_config(led->pwm_dev, duty_us, led->pwm_period_us);
+ if (led->cdev.brightness)
+ rc = pwm_enable(led->pwm_dev);
+ else
+ pwm_disable(led->pwm_dev);
+ } else {
+ rc = pm8xxx_pwm_lut_enable(led->pwm_dev, led->cdev.brightness);
+ }
+
+ return rc;
+}
+
static void __pm8xxx_led_work(struct pm8xxx_led_data *led,
enum led_brightness level)
{
@@ -158,11 +190,19 @@
static void pm8xxx_led_work(struct work_struct *work)
{
+ int rc;
+
struct pm8xxx_led_data *led = container_of(work,
struct pm8xxx_led_data, work);
- int level = led->cdev.brightness;
- __pm8xxx_led_work(led, level);
+ if (led->pwm_dev == NULL) {
+ __pm8xxx_led_work(led, led->cdev.brightness);
+ } else {
+ rc = pm8xxx_led_pwm_work(led);
+ if (rc)
+ pr_err("could not configure PWM mode for LED:%d\n",
+ led->id);
+ }
}
static void pm8xxx_led_set(struct led_classdev *led_cdev,
@@ -270,6 +310,47 @@
return rc;
}
+static int pm8xxx_led_pwm_configure(struct pm8xxx_led_data *led)
+{
+ int start_idx, idx_len, duty_us, rc;
+
+ led->pwm_dev = pwm_request(led->pwm_channel,
+ led->cdev.name);
+
+ if (IS_ERR_OR_NULL(led->pwm_dev)) {
+ pr_err("could not acquire PWM Channel %d, "
+ "error %ld\n", led->pwm_channel,
+ PTR_ERR(led->pwm_dev));
+ led->pwm_dev = NULL;
+ return -ENODEV;
+ }
+
+ if (led->pwm_duty_cycles != NULL) {
+ start_idx = led->pwm_duty_cycles->start_idx;
+ idx_len = led->pwm_duty_cycles->num_duty_pcts;
+
+ if (idx_len >= PM_PWM_LUT_SIZE && start_idx) {
+ pr_err("Wrong LUT size or index\n");
+ return -EINVAL;
+ }
+ if ((start_idx + idx_len) > PM_PWM_LUT_SIZE) {
+ pr_err("Exceed LUT limit\n");
+ return -EINVAL;
+ }
+
+ rc = pm8xxx_pwm_lut_config(led->pwm_dev, led->pwm_period_us,
+ led->pwm_duty_cycles->duty_pcts,
+ led->pwm_duty_cycles->duty_ms,
+ start_idx, idx_len, 0, 0,
+ PM8XXX_LED_PWM_FLAGS);
+ } else {
+ duty_us = led->pwm_period_us;
+ rc = pwm_config(led->pwm_dev, duty_us, led->pwm_period_us);
+ }
+
+ return rc;
+}
+
static int __devinit pm8xxx_led_probe(struct platform_device *pdev)
{
const struct pm8xxx_led_platform_data *pdata = pdev->dev.platform_data;
@@ -304,6 +385,9 @@
led_cfg = &pdata->configs[i];
led_dat->id = led_cfg->id;
+ led_dat->pwm_channel = led_cfg->pwm_channel;
+ led_dat->pwm_period_us = led_cfg->pwm_period_us;
+ led_dat->pwm_duty_cycles = led_cfg->pwm_duty_cycles;
if (!((led_dat->id >= PM8XXX_ID_LED_KB_LIGHT) &&
(led_dat->id <= PM8XXX_ID_FLASH_LED_1))) {
@@ -340,11 +424,22 @@
goto fail_id_check;
}
- if (led_cfg->mode != PM8XXX_LED_MODE_MANUAL)
+ if (led_cfg->mode != PM8XXX_LED_MODE_MANUAL) {
__pm8xxx_led_work(led_dat,
led_dat->cdev.max_brightness);
- else
+
+ if (led_dat->pwm_channel != -1) {
+ led_dat->cdev.max_brightness = LED_FULL;
+ rc = pm8xxx_led_pwm_configure(led_dat);
+ if (rc) {
+ dev_err(&pdev->dev, "failed to "
+ "configure LED, error: %d\n", rc);
+ goto fail_id_check;
+ }
+ }
+ } else {
__pm8xxx_led_work(led_dat, LED_OFF);
+ }
}
platform_set_drvdata(pdev, led);
@@ -356,6 +451,8 @@
for (i = i - 1; i >= 0; i--) {
mutex_destroy(&led[i].lock);
led_classdev_unregister(&led[i].cdev);
+ if (led[i].pwm_dev != NULL)
+ pwm_free(led[i].pwm_dev);
}
}
kfree(led);
@@ -373,6 +470,8 @@
cancel_work_sync(&led[i].work);
mutex_destroy(&led[i].lock);
led_classdev_unregister(&led[i].cdev);
+ if (led[i].pwm_dev != NULL)
+ pwm_free(led[i].pwm_dev);
}
kfree(led);