linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] leds: blink resolution improvements
@ 2015-04-22 17:02 Stas Sergeev
  2015-04-22 17:04 ` [PATCH 1/2] leds: use hrtimer for blinking Stas Sergeev
  2015-04-22 17:06 ` [PATCH 2/2] ledtrig-timer: add blink resolution control Stas Sergeev
  0 siblings, 2 replies; 3+ messages in thread
From: Stas Sergeev @ 2015-04-22 17:02 UTC (permalink / raw)
  To: linux-leds; +Cc: Linux kernel, Stas Sergeev

Hello.

The following patches improve the precision of led
timer trigger and add the resolution control.
That allows to make PWM brightness control with timer
trigger.

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 1/2] leds: use hrtimer for blinking
  2015-04-22 17:02 [PATCH 0/2] leds: blink resolution improvements Stas Sergeev
@ 2015-04-22 17:04 ` Stas Sergeev
  2015-04-22 17:06 ` [PATCH 2/2] ledtrig-timer: add blink resolution control Stas Sergeev
  1 sibling, 0 replies; 3+ messages in thread
From: Stas Sergeev @ 2015-04-22 17:04 UTC (permalink / raw)
  To: linux-leds; +Cc: Linux kernel, Stas Sergeev, Bryan Wu, Richard Purdie


Normal timer has a jiffy resolution, usually 10ms.
But leds trigger timer control allows to set the delays with 1ms granularity.
In order to make this to really work we need to use hrtimer.

CC: Bryan Wu <cooloney@gmail.com>
CC: Richard Purdie <rpurdie@rpsys.net>
CC: linux-leds@vger.kernel.org
CC: linux-kernel@vger.kernel.org

Signed-off-by: Stas Sergeev <stsp@users.sourceforge.net>
---
 drivers/leds/led-class.c |   19 ++++++++++++-------
 drivers/leds/led-core.c  |    9 +++++----
 include/linux/leds.h     |    4 ++--
 3 files changed, 19 insertions(+), 13 deletions(-)

diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index 795ec99..f95ce912 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -102,20 +102,21 @@ static const struct attribute_group *led_groups[] = {
 	NULL,
 };

-static void led_timer_function(unsigned long data)
+static enum hrtimer_restart led_timer_function(struct hrtimer *timer)
 {
-	struct led_classdev *led_cdev = (void *)data;
+	struct led_classdev *led_cdev = container_of(timer,
+			     struct led_classdev, blink_timer);
 	unsigned long brightness;
 	unsigned long delay;

 	if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
 		led_set_brightness_async(led_cdev, LED_OFF);
-		return;
+		return HRTIMER_NORESTART;
 	}

 	if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) {
 		led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
-		return;
+		return HRTIMER_NORESTART;
 	}

 	brightness = led_get_brightness(led_cdev);
@@ -148,7 +149,10 @@ static void led_timer_function(unsigned long data)
 		}
 	}

-	mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
+	hrtimer_forward(&led_cdev->blink_timer,
+			hrtimer_get_expires(&led_cdev->blink_timer),
+			ms_to_ktime(delay));
+	return HRTIMER_RESTART;
 }

 static void set_brightness_delayed(struct work_struct *ws)
@@ -243,8 +247,9 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)

 	INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);

-	setup_timer(&led_cdev->blink_timer, led_timer_function,
-		    (unsigned long)led_cdev);
+	hrtimer_init(&led_cdev->blink_timer, CLOCK_MONOTONIC,
+		     HRTIMER_MODE_REL);
+	led_cdev->blink_timer.function = led_timer_function;

 #ifdef CONFIG_LEDS_TRIGGERS
 	led_trigger_set_default(led_cdev);
diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index 9886dac..2937259 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -52,7 +52,8 @@ static void led_set_software_blink(struct led_classdev *led_cdev,
 		return;
 	}

-	mod_timer(&led_cdev->blink_timer, jiffies + 1);
+	hrtimer_start(&led_cdev->blink_timer, ktime_set(0, 0),
+		      HRTIMER_MODE_REL);
 }


@@ -76,7 +77,7 @@ void led_blink_set(struct led_classdev *led_cdev,
 		   unsigned long *delay_on,
 		   unsigned long *delay_off)
 {
-	del_timer_sync(&led_cdev->blink_timer);
+	hrtimer_cancel(&led_cdev->blink_timer);

 	led_cdev->flags &= ~LED_BLINK_ONESHOT;
 	led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
@@ -91,7 +92,7 @@ void led_blink_set_oneshot(struct led_classdev *led_cdev,
 			   int invert)
 {
 	if ((led_cdev->flags & LED_BLINK_ONESHOT) &&
-	     timer_pending(&led_cdev->blink_timer))
+	     hrtimer_active(&led_cdev->blink_timer))
 		return;

 	led_cdev->flags |= LED_BLINK_ONESHOT;
@@ -108,7 +109,7 @@ EXPORT_SYMBOL(led_blink_set_oneshot);

 void led_stop_software_blink(struct led_classdev *led_cdev)
 {
-	del_timer_sync(&led_cdev->blink_timer);
+	hrtimer_cancel(&led_cdev->blink_timer);
 	led_cdev->blink_delay_on = 0;
 	led_cdev->blink_delay_off = 0;
 }
diff --git a/include/linux/leds.h b/include/linux/leds.h
index f70f84f..68f5a23 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -16,7 +16,7 @@
 #include <linux/mutex.h>
 #include <linux/rwsem.h>
 #include <linux/spinlock.h>
-#include <linux/timer.h>
+#include <linux/hrtimer.h>
 #include <linux/workqueue.h>

 struct device;
@@ -81,7 +81,7 @@ struct led_classdev {
 	const char		*default_trigger;	/* Trigger to use */

 	unsigned long		 blink_delay_on, blink_delay_off;
-	struct timer_list	 blink_timer;
+	struct hrtimer		 blink_timer;
 	int			 blink_brightness;
 	void			(*flash_resume)(struct led_classdev *led_cdev);

-- 
1.7.9.5

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 2/2] ledtrig-timer: add blink resolution control
  2015-04-22 17:02 [PATCH 0/2] leds: blink resolution improvements Stas Sergeev
  2015-04-22 17:04 ` [PATCH 1/2] leds: use hrtimer for blinking Stas Sergeev
@ 2015-04-22 17:06 ` Stas Sergeev
  1 sibling, 0 replies; 3+ messages in thread
From: Stas Sergeev @ 2015-04-22 17:06 UTC (permalink / raw)
  To: linux-leds; +Cc: Linux kernel, Stas Sergeev, Bryan Wu, Richard Purdie


Add the following resolutions to led trigger timer:
'n' for nanosecond resolution
'u' for microsecond resolution
'm' for millisecond resolution
The default is 'm' for backward compatibility.

This functionality is needed for things like PWM for software
brightness control, because the default mS resolution is not enough
for that tasks.

CC: Bryan Wu <cooloney@gmail.com>
CC: Richard Purdie <rpurdie@rpsys.net>
CC: linux-leds@vger.kernel.org
CC: linux-kernel@vger.kernel.org

Signed-off-by: Stas Sergeev <stsp@users.sourceforge.net>
---
 drivers/leds/led-class.c             |   18 +++++++++++--
 drivers/leds/trigger/ledtrig-timer.c |   49 ++++++++++++++++++++++++++++++++++
 include/linux/leds.h                 |    7 +++++
 3 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index f95ce912..2cfbb9d 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -108,6 +108,7 @@ static enum hrtimer_restart led_timer_function(struct hrtimer *timer)
 			     struct led_classdev, blink_timer);
 	unsigned long brightness;
 	unsigned long delay;
+	ktime_t k_delay;

 	if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
 		led_set_brightness_async(led_cdev, LED_OFF);
@@ -149,9 +150,22 @@ static enum hrtimer_restart led_timer_function(struct hrtimer *timer)
 		}
 	}

+	switch (led_cdev->resolution) {
+	case LED_BLINK_MS:
+		k_delay = ms_to_ktime(delay);
+		break;
+	case LED_BLINK_US:
+		k_delay = ns_to_ktime(delay * 1000);
+		break;
+	case LED_BLINK_NS:
+		k_delay = ns_to_ktime(delay);
+		break;
+	default:
+		/* should not happen */
+		return HRTIMER_NORESTART;
+	}
 	hrtimer_forward(&led_cdev->blink_timer,
-			hrtimer_get_expires(&led_cdev->blink_timer),
-			ms_to_ktime(delay));
+			hrtimer_get_expires(&led_cdev->blink_timer), k_delay);
 	return HRTIMER_RESTART;
 }

diff --git a/drivers/leds/trigger/ledtrig-timer.c b/drivers/leds/trigger/ledtrig-timer.c
index 8d09327..222c755 100644
--- a/drivers/leds/trigger/ledtrig-timer.c
+++ b/drivers/leds/trigger/ledtrig-timer.c
@@ -68,8 +68,51 @@ static ssize_t led_delay_off_store(struct device *dev,
 	return size;
 }

+static ssize_t led_res_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	const char res_suffix[] = {
+		[LED_BLINK_MS] = 'm',
+		[LED_BLINK_US] = 'u',
+		[LED_BLINK_NS] = 'n',
+	};
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%c\n", res_suffix[led_cdev->resolution]);
+}
+
+static ssize_t led_res_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t size)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	int ret = strlen(buf);
+	/* char and \n */
+	if (ret != 2)
+		return -EINVAL;
+
+	switch (buf[0]) {
+	case 'm':
+		led_cdev->resolution = LED_BLINK_MS;
+		break;
+	case 'u':
+		led_cdev->resolution = LED_BLINK_US;
+		break;
+	case 'n':
+		led_cdev->resolution = LED_BLINK_NS;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	led_blink_set(led_cdev, &led_cdev->blink_delay_on,
+		      &led_cdev->blink_delay_off);
+
+	return ret;
+}
+
 static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store);
 static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store);
+static DEVICE_ATTR(resolution, 0644, led_res_show, led_res_store);

 static void timer_trig_activate(struct led_classdev *led_cdev)
 {
@@ -83,6 +126,9 @@ static void timer_trig_activate(struct led_classdev *led_cdev)
 	rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);
 	if (rc)
 		goto err_out_delayon;
+	rc = device_create_file(led_cdev->dev, &dev_attr_resolution);
+	if (rc)
+		goto err_out_delayoff;

 	led_blink_set(led_cdev, &led_cdev->blink_delay_on,
 		      &led_cdev->blink_delay_off);
@@ -90,6 +136,8 @@ static void timer_trig_activate(struct led_classdev *led_cdev)

 	return;

+err_out_delayoff:
+	device_remove_file(led_cdev->dev, &dev_attr_delay_off);
 err_out_delayon:
 	device_remove_file(led_cdev->dev, &dev_attr_delay_on);
 }
@@ -99,6 +147,7 @@ static void timer_trig_deactivate(struct led_classdev *led_cdev)
 	if (led_cdev->activated) {
 		device_remove_file(led_cdev->dev, &dev_attr_delay_on);
 		device_remove_file(led_cdev->dev, &dev_attr_delay_off);
+		device_remove_file(led_cdev->dev, &dev_attr_resolution);
 		led_cdev->activated = false;
 	}

diff --git a/include/linux/leds.h b/include/linux/leds.h
index 68f5a23..5e6fe26 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -30,10 +30,17 @@ enum led_brightness {
 	LED_FULL	= 255,
 };

+enum led_blink_resolution {
+	LED_BLINK_MS,
+	LED_BLINK_US,
+	LED_BLINK_NS,
+};
+
 struct led_classdev {
 	const char		*name;
 	enum led_brightness	 brightness;
 	enum led_brightness	 max_brightness;
+	enum led_blink_resolution resolution;
 	int			 flags;

 	/* Lower 16 bits reflect status */
-- 
1.7.9.5

^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2015-04-22 17:06 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-04-22 17:02 [PATCH 0/2] leds: blink resolution improvements Stas Sergeev
2015-04-22 17:04 ` [PATCH 1/2] leds: use hrtimer for blinking Stas Sergeev
2015-04-22 17:06 ` [PATCH 2/2] ledtrig-timer: add blink resolution control Stas Sergeev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).