This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Use PWM Subsystem to drive LED

Expert 2280 points

Hi, I would like to drive a LED using PWM Subsystem to modulate light intensity.

I've properly configured pin-mux in my custom board, and now I can drive the LED from the user-space using the sysfs interface. I would prefer to use the kernel LED framework, which provides several useful kernel-level led triggers, but as now this is not possible with latest PSP kernel.

Looking at kernel drivers source code, I've seen that AM335x PWMSS driver is based on PWM framework which came as a kernel patch by Bill Gatliff (which is not the same framework included in latest 3.6 official Linux kernel). Unfortunately it seems that not all the patches by Gatliff  have been included in AM335x Linux kernel: CONFIG_LEDS_PWM feature has not been patched, therefore LED framework and Gatliff PWM framework are not compatible in AM335x Linux kernel, and this makes impossible to drive a pwm-based leds using LED framework.

Can someone help or give some hints on which is the exact version of Gatliff patches already included?

I would try to complete that patching, and I will share the result if useful to someone else.

Thanks. Regards,

Max

  • for what it's worth: leds-omap-pwm driver

    diff -Narub linux.orig/arch/arm/plat-omap/include/plat/board.h linux/arch/arm/plat-omap/include/plat/board.h
    --- linux.orig/arch/arm/plat-omap/include/plat/board.h	2012-07-19 22:21:31.000000000 +0200
    +++ linux/arch/arm/plat-omap/include/plat/board.h	2012-09-26 13:00:17.288764186 +0200
    @@ -119,6 +119,14 @@
     	int intensity_timer;
     	int blink_timer;
     	void (*set_power)(struct omap_pwm_led_platform_data *self, int on_off);
    +#ifdef CONFIG_LEDS_OMAP_PWM
    +	void (*set_pad)(struct omap_pwm_led_platform_data *Self, int on_off);
    +	const char *default_trigger;
    +	unsigned int bkl_freq;
    +	unsigned char bkl_max;
    +	unsigned char bkl_min;
    +	unsigned invert;
    +#endif
     };
     
     struct omap_uart_config {
    diff -Narub linux.orig/drivers/leds/Kconfig linux/drivers/leds/Kconfig
    --- linux.orig/drivers/leds/Kconfig	2012-09-20 21:25:14.771827238 +0200
    +++ linux/drivers/leds/Kconfig	2012-09-26 12:57:43.247140169 +0200
    @@ -344,6 +344,13 @@
     	  This option enable support for on-chip LED drivers found
     	  on Freescale Semiconductor MC13783 PMIC.
     
    +config LEDS_OMAP_PWM
    +	tristate "LED Support for OMAP PWM-controlled LEDs"
    +	depends on LEDS_CLASS && ARCH_OMAP && OMAP_DM_TIMER
    +	help
    +	  This options enables support for LEDs connected to GPIO lines
    +	  controlled by a PWM timer on OMAP CPUs.
    +
     config LEDS_NS2
     	tristate "LED support for Network Space v2 GPIO LEDs"
     	depends on LEDS_CLASS
    diff -Narub linux.orig/drivers/leds/leds-omap-pwm.c linux/drivers/leds/leds-omap-pwm.c
    --- linux.orig/drivers/leds/leds-omap-pwm.c	1970-01-01 01:00:00.000000000 +0100
    +++ linux/drivers/leds/leds-omap-pwm.c	2012-09-26 13:02:05.075795659 +0200
    @@ -0,0 +1,454 @@
    +/* drivers/leds/leds-omap_pwm.c
    + *
    + * Driver to blink LEDs using OMAP PWM timers
    + *
    + * Copyright (C) 2006 Nokia Corporation
    + * Author: Timo Teras
    + *
    + * This program is free software; you can redistribute it and/or modify
    + * it under the terms of the GNU General Public License version 2 as
    + * published by the Free Software Foundation.
    +*/
    +#undef DEBUG
    +
    +#include <linux/kernel.h>
    +#include <linux/init.h>
    +#include <linux/err.h>
    +#include <linux/platform_device.h>
    +#include <linux/leds.h>
    +#include <linux/ctype.h>
    +#include <linux/sched.h>
    +#include <linux/clk.h>
    +#include <asm/delay.h>
    +#include <plat/board.h>
    +#include <plat/dmtimer.h>
    +#include <linux/slab.h>
    +#include <linux/delay.h>
    +#include <linux/module.h>
    +
    +#define MAX_GPTIMER_ID		12
    +
    +struct omap_pwm_led {
    +	struct led_classdev cdev;
    +	struct omap_pwm_led_platform_data *pdata;
    +	struct omap_dm_timer *intensity_timer;
    +	struct omap_dm_timer *blink_timer;
    +	int powered;
    +	unsigned int on_period, off_period;
    +	enum led_brightness brightness;
    +};
    +
    +static inline struct omap_pwm_led *pdev_to_omap_pwm_led(struct platform_device *pdev)
    +{
    +	return platform_get_drvdata(pdev);
    +}
    +
    +static inline struct omap_pwm_led *cdev_to_omap_pwm_led(struct led_classdev *led_cdev)
    +{
    +	return container_of(led_cdev, struct omap_pwm_led, cdev);
    +}
    +
    +static void omap_pwm_led_set_blink(struct omap_pwm_led *led)
    +{
    +	if (!led->powered)
    +		return;
    +
    +	if (led->on_period != 0 && led->off_period != 0) {
    +		unsigned long load_reg, cmp_reg;
    +
    +		load_reg = 32768 * (led->on_period + led->off_period) / 1000;
    +		cmp_reg = 32768 * led->on_period / 1000;
    +
    +		omap_dm_timer_stop(led->blink_timer);
    +		omap_dm_timer_set_load(led->blink_timer, 1, -load_reg);
    +		omap_dm_timer_set_match(led->blink_timer, 1, -cmp_reg);
    +		omap_dm_timer_set_pwm(led->blink_timer, 1, 1,
    +				      OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE);
    +		omap_dm_timer_write_counter(led->blink_timer, -2);
    +		omap_dm_timer_start(led->blink_timer);
    +	} else {
    +		omap_dm_timer_set_pwm(led->blink_timer, 1, 1,
    +				      OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE);
    +		omap_dm_timer_stop(led->blink_timer);
    +	}
    +}
    +
    +static void omap_pwm_led_pad_enable(struct omap_pwm_led *led)
    +{
    +	if (led->pdata->set_pad)
    +		led->pdata->set_pad(led->pdata, 1);
    +}
    +
    +static void omap_pwm_led_pad_disable(struct omap_pwm_led *led)
    +{
    +	if (led->pdata->set_pad)
    +		led->pdata->set_pad(led->pdata, 0);
    +}
    +
    +static void omap_pwm_led_power_on(struct omap_pwm_led *led)
    +{
    +	if (led->powered)
    +		return;
    +	led->powered = 1;
    +
    +	pr_debug("%s: brightness: %i\n", 
    +			__func__, led->brightness);
    +	
    +	/* Select clock */
    +	omap_dm_timer_set_source(led->intensity_timer, OMAP_TIMER_SRC_SYS_CLK);
    +
    +	/* Turn voltage on */
    +	if (led->pdata->set_power != NULL)
    +		led->pdata->set_power(led->pdata, 1);
    +
    +	/* explicitly enable the timer, saves some SAR later */
    +	omap_dm_timer_enable(led->intensity_timer);
    +	
    +	/* Enable PWM timers */
    +	if (led->blink_timer != NULL) {
    +		omap_dm_timer_set_source(led->blink_timer,
    +					 OMAP_TIMER_SRC_32_KHZ);
    +		omap_pwm_led_set_blink(led);
    +	}
    +}
    +
    +static void omap_pwm_led_power_off(struct omap_pwm_led *led)
    +{
    +	if (!led->powered)
    +		return;
    +	led->powered = 0;
    +
    +	pr_debug("%s: brightness: %i\n", 
    +			__func__, led->brightness);
    +	
    +	if (led->pdata->set_power != NULL)
    +		led->pdata->set_power(led->pdata, 0);
    +
    +	/* Everything off */
    +	omap_dm_timer_stop(led->intensity_timer);
    +
    +	if (led->blink_timer != NULL)
    +		omap_dm_timer_stop(led->blink_timer);
    +}
    +
    +static void pwm_set_speed(struct omap_dm_timer *gpt,
    +		int frequency, int duty_cycle)
    +{
    +	u32 val;
    +	u32 period;
    +	struct clk *timer_fclk;
    +
    +	/* and you will have an overflow in 1 sec         */
    +	/* so,                              */
    +	/* freq_timer     -> 1s             */
    +	/* carrier_period -> 1/carrier_freq */
    +	/* => carrier_period = freq_timer/carrier_freq */
    +
    +	timer_fclk = omap_dm_timer_get_fclk(gpt);
    +	period = clk_get_rate(timer_fclk) / frequency;
    +
    +	val = 0xFFFFFFFF+1-period;
    +	omap_dm_timer_set_load(gpt, 1, val);
    +
    +	val = 0xFFFFFFFF+1-(period*duty_cycle/256);
    +	omap_dm_timer_set_match(gpt, 1, val);
    +
    +	/* assume overflow first: no toogle if first trig is match */
    +	omap_dm_timer_write_counter(gpt, 0xFFFFFFFE);
    +}
    +
    +static void omap_pwm_led_set_pwm_cycle(struct omap_pwm_led *led, int cycle)
    +{
    +	int pwm_frequency = 10000;
    +	int def_on;
    +	
    +	pr_debug("%s: cycle: %i\n", 
    +			__func__, cycle);
    +	
    +	if (led->pdata->bkl_max)
    +		cycle = ( (cycle * led->pdata->bkl_max ) / 255);
    +	
    +	if (cycle < led->pdata->bkl_min)
    +		cycle = led->pdata->bkl_min;
    +
    +	if (led->pdata->bkl_freq)
    +		pwm_frequency = led->pdata->bkl_freq;
    +
    +	if (cycle != LED_FULL)
    +		def_on = led->pdata->invert ? 1:0;
    +	else
    +		def_on = led->pdata->invert ? 0:1;
    +	
    +	omap_dm_timer_set_pwm(led->intensity_timer, def_on, 1,
    +		      OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE);
    +
    +	if (cycle != LED_FULL) {
    +		pwm_set_speed(led->intensity_timer, pwm_frequency, 256-cycle);
    +		omap_dm_timer_start(led->intensity_timer);
    +	} else
    +		omap_dm_timer_stop(led->intensity_timer);
    +}
    +
    +static void omap_pwm_led_set(struct led_classdev *led_cdev,
    +			     enum led_brightness value)
    +{
    +	struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev);
    +
    +	pr_debug("%s: brightness: %i\n", __func__, value);
    +
    +	if (led->brightness != value) {
    +		if (led->brightness == LED_OFF ||
    +			led_cdev->flags & LED_SUSPENDED) {
    +			/* LED currently OFF */
    +			omap_pwm_led_power_on(led);
    +			if (value < led->pdata->bkl_min*2) {
    +				// some backlight stepup can't start without medium value during variable time
    +				omap_pwm_led_set_pwm_cycle(led, led->pdata->bkl_min*3);
    +				omap_pwm_led_pad_enable(led);
    +				msleep(50);
    +				omap_pwm_led_set_pwm_cycle(led, value);
    +			
    +			} else {
    +				omap_pwm_led_set_pwm_cycle(led, value);
    +				omap_pwm_led_pad_enable(led);
    +			}
    +		} else
    +			/* just set the new cycle */
    +			omap_pwm_led_set_pwm_cycle(led, value);
    +			
    +		if (value == LED_OFF) {
    +			/* LED now suspended */
    +			omap_pwm_led_pad_disable(led);
    +			omap_pwm_led_set_pwm_cycle(led, value);
    +			omap_pwm_led_power_off(led);
    +		}
    +		led->brightness = value;
    +	}
    +}
    +
    +static ssize_t omap_pwm_led_on_period_show(struct device *dev,
    +				struct device_attribute *attr, char *buf)
    +{
    +	struct led_classdev *led_cdev = dev_get_drvdata(dev);
    +	struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev);
    +
    +	return sprintf(buf, "%u\n", led->on_period) + 1;
    +}
    +
    +static ssize_t omap_pwm_led_on_period_store(struct device *dev,
    +				struct device_attribute *attr,
    +				const char *buf, size_t size)
    +{
    +	struct led_classdev *led_cdev = dev_get_drvdata(dev);
    +	struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev);
    +	int ret = -EINVAL;
    +	unsigned long val;
    +	char *after;
    +	size_t count;
    +
    +	val = simple_strtoul(buf, &after, 10);
    +	count = after - buf;
    +	if (*after && isspace(*after))
    +		count++;
    +
    +	if (count == size) {
    +		led->on_period = val;
    +		omap_pwm_led_set_blink(led);
    +		ret = count;
    +	}
    +
    +	return ret;
    +}
    +
    +static ssize_t omap_pwm_led_off_period_show(struct device *dev,
    +				struct device_attribute *attr, char *buf)
    +{
    +	struct led_classdev *led_cdev = dev_get_drvdata(dev);
    +	struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev);
    +
    +	return sprintf(buf, "%u\n", led->off_period) + 1;
    +}
    +
    +static ssize_t omap_pwm_led_off_period_store(struct device *dev,
    +					struct device_attribute *attr,
    +					const char *buf, size_t size)
    +{
    +	struct led_classdev *led_cdev = dev_get_drvdata(dev);
    +	struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev);
    +	int ret = -EINVAL;
    +	unsigned long val;
    +	char *after;
    +	size_t count;
    +
    +	val = simple_strtoul(buf, &after, 10);
    +	count = after - buf;
    +	if (*after && isspace(*after))
    +		count++;
    +
    +	if (count == size) {
    +		led->off_period = val;
    +		omap_pwm_led_set_blink(led);
    +		ret = count;
    +	}
    +
    +	return ret;
    +}
    +
    +static DEVICE_ATTR(on_period, 0644, omap_pwm_led_on_period_show,
    +				omap_pwm_led_on_period_store);
    +static DEVICE_ATTR(off_period, 0644, omap_pwm_led_off_period_show,
    +				omap_pwm_led_off_period_store);
    +
    +static int omap_pwm_led_probe(struct platform_device *pdev)
    +{
    +	struct omap_pwm_led_platform_data *pdata = pdev->dev.platform_data;
    +	struct omap_pwm_led *led;
    +	int ret;
    +
    +	if (pdata->intensity_timer < 1 || pdata->intensity_timer > MAX_GPTIMER_ID)
    +		return -EINVAL;
    +
    +	if (pdata->blink_timer != 0 || pdata->blink_timer > MAX_GPTIMER_ID)
    +		return -EINVAL;
    +
    +	led = kzalloc(sizeof(struct omap_pwm_led), GFP_KERNEL);
    +	if (led == NULL) {
    +		dev_err(&pdev->dev, "No memory for device\n");
    +		return -ENOMEM;
    +	}
    +
    +	platform_set_drvdata(pdev, led);
    +	led->cdev.brightness_set = omap_pwm_led_set;
    +	led->cdev.default_trigger = pdata->default_trigger;
    +	led->cdev.name = pdata->name;
    +	led->pdata = pdata;
    +	led->brightness = LED_OFF;
    +
    +	dev_info(&pdev->dev, "OMAP PWM LED (%s) at GP timer %d/%d\n",
    +		 pdata->name, pdata->intensity_timer, pdata->blink_timer);
    +
    +	/* register our new led device */
    +	ret = led_classdev_register(&pdev->dev, &led->cdev);
    +	if (ret < 0) {
    +		dev_err(&pdev->dev, "led_classdev_register failed\n");
    +		goto error_classdev;
    +	}
    +
    +	/* get related dm timers */
    +	led->intensity_timer = omap_dm_timer_request_specific(pdata->intensity_timer);
    +	if (led->intensity_timer == NULL) {
    +		dev_err(&pdev->dev, "failed to request intensity pwm timer\n");
    +		ret = -ENODEV;
    +		goto error_intensity;
    +	}
    +
    +	if (led->pdata->invert)
    +		omap_dm_timer_set_pwm(led->intensity_timer, 1, 1,
    +				      OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE);
    +
    +	if (pdata->blink_timer != 0) {
    +		led->blink_timer = omap_dm_timer_request_specific(pdata->blink_timer);
    +		if (led->blink_timer == NULL) {
    +			dev_err(&pdev->dev, "failed to request blinking pwm timer\n");
    +			ret = -ENODEV;
    +			goto error_blink1;
    +		}
    +		ret = device_create_file(led->cdev.dev,
    +					       &dev_attr_on_period);
    +		if(ret)
    +			goto error_blink2;
    +
    +		ret = device_create_file(led->cdev.dev,
    +					&dev_attr_off_period);
    +		if(ret)
    +			goto error_blink3;
    +
    +	}
    +
    +	return 0;
    +
    +error_blink3:
    +	device_remove_file(led->cdev.dev,
    +				 &dev_attr_on_period);
    +error_blink2:
    +	dev_err(&pdev->dev, "failed to create device file(s)\n");
    +error_blink1:
    +	omap_dm_timer_free(led->intensity_timer);
    +error_intensity:
    +	led_classdev_unregister(&led->cdev);
    +error_classdev:
    +	kfree(led);
    +	return ret;
    +}
    +
    +static int omap_pwm_led_remove(struct platform_device *pdev)
    +{
    +	struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev);
    +
    +	device_remove_file(led->cdev.dev,
    +				 &dev_attr_on_period);
    +	device_remove_file(led->cdev.dev,
    +				 &dev_attr_off_period);
    +	led_classdev_unregister(&led->cdev);
    +
    +	omap_pwm_led_set(&led->cdev, LED_OFF);
    +	if (led->blink_timer != NULL)
    +		omap_dm_timer_free(led->blink_timer);
    +	omap_dm_timer_free(led->intensity_timer);
    +	kfree(led);
    +
    +	return 0;
    +}
    +
    +#ifdef CONFIG_PM
    +static int omap_pwm_led_suspend(struct platform_device *pdev, pm_message_t state)
    +{
    +	struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev);
    +
    +	pr_debug("%s: brightness: %i\n", __func__,
    +			led->brightness);
    +	led_classdev_suspend(&led->cdev);
    +	return 0;
    +}
    +
    +static int omap_pwm_led_resume(struct platform_device *pdev)
    +{
    +	struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev);
    +
    +	pr_debug("%s: brightness: %i\n", __func__,
    +			led->brightness);
    +	led_classdev_resume(&led->cdev);
    +	return 0;
    +}
    +#else
    +#define omap_pwm_led_suspend NULL
    +#define omap_pwm_led_resume NULL
    +#endif
    +
    +static struct platform_driver omap_pwm_led_driver = {
    +	.probe		= omap_pwm_led_probe,
    +	.remove		= omap_pwm_led_remove,
    +	.suspend	= omap_pwm_led_suspend,
    +	.resume		= omap_pwm_led_resume,
    +	.driver		= {
    +		.name		= "omap_pwm_led",
    +		.owner		= THIS_MODULE,
    +	},
    +};
    +
    +static int __init omap_pwm_led_init(void)
    +{
    +	return platform_driver_register(&omap_pwm_led_driver);
    +}
    +
    +static void __exit omap_pwm_led_exit(void)
    +{
    +	platform_driver_unregister(&omap_pwm_led_driver);
    +}
    +
    +module_init(omap_pwm_led_init);
    +module_exit(omap_pwm_led_exit);
    +
    +MODULE_AUTHOR("Timo Teras");
    +MODULE_DESCRIPTION("OMAP PWM LED driver");
    +MODULE_LICENSE("GPL");
    diff -Narub linux.orig/drivers/leds/Makefile linux/drivers/leds/Makefile
    --- linux.orig/drivers/leds/Makefile	2012-09-20 21:25:14.780827655 +0200
    +++ linux/drivers/leds/Makefile	2012-09-26 12:58:08.060604765 +0200
    @@ -43,6 +43,7 @@
     obj-$(CONFIG_LEDS_NETXBIG)		+= leds-netxbig.o
     obj-$(CONFIG_LEDS_ASIC3)		+= leds-asic3.o
     obj-$(CONFIG_LEDS_RENESAS_TPU)		+= leds-renesas-tpu.o
    +obj-$(CONFIG_LEDS_OMAP_PWM)		+= leds-omap-pwm.o
     
     # LED SPI Drivers
     obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
    

    driver not yet tested

    Regards,

    Wim

  • Hi Wim, thanks for the suggestion.

    As now I had just a quick look, and I'm not sure but this implementation seems based on omap generic timers more than on PWM SubSystem. In this way I suspect the load for the cpu would be much more, having to handle timers timeout.

    My idea is to have something similar to current leds-pwm.c but based on Gatliff/TI PWM framework instead of on common kernel PWM APIs.

    Regards,

    Max

  • Hi Qmax,

    Gatliff PWM framework based on

    https://lkml.org/lkml/2011/3/12/155

    In this series support for leds_pwm is missing. Can you try after patching led pwm framework support

    https://lkml.org/lkml/2010/2/9/277

    or look at modification for pwm backlight

    http://arago-project.org/git/projects/?p=linux-am33x.git;a=commit;h=52818bf1d5a1f6d475303ebca572b9fbd2d3cbdc

    Thanks Avinash