1c72a1d60SRichard Purdie /* 2c72a1d60SRichard Purdie * LED Class Core 3c72a1d60SRichard Purdie * 4c72a1d60SRichard Purdie * Copyright 2005-2006 Openedhand Ltd. 5c72a1d60SRichard Purdie * 6c72a1d60SRichard Purdie * Author: Richard Purdie <rpurdie@openedhand.com> 7c72a1d60SRichard Purdie * 8c72a1d60SRichard Purdie * This program is free software; you can redistribute it and/or modify 9c72a1d60SRichard Purdie * it under the terms of the GNU General Public License version 2 as 10c72a1d60SRichard Purdie * published by the Free Software Foundation. 11c72a1d60SRichard Purdie * 12c72a1d60SRichard Purdie */ 13c72a1d60SRichard Purdie 14c72a1d60SRichard Purdie #include <linux/kernel.h> 1504713306SJacek Anaszewski #include <linux/leds.h> 16c72a1d60SRichard Purdie #include <linux/list.h> 17c72a1d60SRichard Purdie #include <linux/module.h> 1804713306SJacek Anaszewski #include <linux/mutex.h> 1972f8da32SRichard Purdie #include <linux/rwsem.h> 20c72a1d60SRichard Purdie #include "leds.h" 21c72a1d60SRichard Purdie 2272f8da32SRichard Purdie DECLARE_RWSEM(leds_list_lock); 23c72a1d60SRichard Purdie EXPORT_SYMBOL_GPL(leds_list_lock); 244d404fd5SNémeth Márton 254d404fd5SNémeth Márton LIST_HEAD(leds_list); 264d404fd5SNémeth Márton EXPORT_SYMBOL_GPL(leds_list); 27a403d930SBryan Wu 28d4887af9SHeiner Kallweit static int __led_set_brightness(struct led_classdev *led_cdev, 29d4887af9SHeiner Kallweit enum led_brightness value) 30d4887af9SHeiner Kallweit { 31d4887af9SHeiner Kallweit if (!led_cdev->brightness_set) 32d4887af9SHeiner Kallweit return -ENOTSUPP; 33d4887af9SHeiner Kallweit 34d4887af9SHeiner Kallweit led_cdev->brightness_set(led_cdev, value); 35d4887af9SHeiner Kallweit 36d4887af9SHeiner Kallweit return 0; 37d4887af9SHeiner Kallweit } 38d4887af9SHeiner Kallweit 39d4887af9SHeiner Kallweit static int __led_set_brightness_blocking(struct led_classdev *led_cdev, 40d4887af9SHeiner Kallweit enum led_brightness value) 41d4887af9SHeiner Kallweit { 42d4887af9SHeiner Kallweit if (!led_cdev->brightness_set_blocking) 43d4887af9SHeiner Kallweit return -ENOTSUPP; 44d4887af9SHeiner Kallweit 45d4887af9SHeiner Kallweit return led_cdev->brightness_set_blocking(led_cdev, value); 46d4887af9SHeiner Kallweit } 47d4887af9SHeiner Kallweit 48757b06aeSJacek Anaszewski static void led_timer_function(unsigned long data) 49757b06aeSJacek Anaszewski { 50757b06aeSJacek Anaszewski struct led_classdev *led_cdev = (void *)data; 51757b06aeSJacek Anaszewski unsigned long brightness; 52757b06aeSJacek Anaszewski unsigned long delay; 53757b06aeSJacek Anaszewski 54757b06aeSJacek Anaszewski if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) { 5581fe8e5bSJacek Anaszewski led_set_brightness_nosleep(led_cdev, LED_OFF); 56a9c6ce57SHans de Goede clear_bit(LED_BLINK_SW, &led_cdev->work_flags); 57757b06aeSJacek Anaszewski return; 58757b06aeSJacek Anaszewski } 59757b06aeSJacek Anaszewski 60a9c6ce57SHans de Goede if (test_and_clear_bit(LED_BLINK_ONESHOT_STOP, 61a9c6ce57SHans de Goede &led_cdev->work_flags)) { 62a9c6ce57SHans de Goede clear_bit(LED_BLINK_SW, &led_cdev->work_flags); 63757b06aeSJacek Anaszewski return; 64757b06aeSJacek Anaszewski } 65757b06aeSJacek Anaszewski 66757b06aeSJacek Anaszewski brightness = led_get_brightness(led_cdev); 67757b06aeSJacek Anaszewski if (!brightness) { 68757b06aeSJacek Anaszewski /* Time to switch the LED on. */ 69eb1610b4SHans de Goede if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, 70eb1610b4SHans de Goede &led_cdev->work_flags)) 71eb1610b4SHans de Goede brightness = led_cdev->new_blink_brightness; 72eb1610b4SHans de Goede else 73757b06aeSJacek Anaszewski brightness = led_cdev->blink_brightness; 74757b06aeSJacek Anaszewski delay = led_cdev->blink_delay_on; 75757b06aeSJacek Anaszewski } else { 76757b06aeSJacek Anaszewski /* Store the current brightness value to be able 77757b06aeSJacek Anaszewski * to restore it when the delay_off period is over. 78757b06aeSJacek Anaszewski */ 79757b06aeSJacek Anaszewski led_cdev->blink_brightness = brightness; 80757b06aeSJacek Anaszewski brightness = LED_OFF; 81757b06aeSJacek Anaszewski delay = led_cdev->blink_delay_off; 82757b06aeSJacek Anaszewski } 83757b06aeSJacek Anaszewski 8481fe8e5bSJacek Anaszewski led_set_brightness_nosleep(led_cdev, brightness); 85757b06aeSJacek Anaszewski 86757b06aeSJacek Anaszewski /* Return in next iteration if led is in one-shot mode and we are in 87757b06aeSJacek Anaszewski * the final blink state so that the led is toggled each delay_on + 88757b06aeSJacek Anaszewski * delay_off milliseconds in worst case. 89757b06aeSJacek Anaszewski */ 90a9c6ce57SHans de Goede if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags)) { 91a9c6ce57SHans de Goede if (test_bit(LED_BLINK_INVERT, &led_cdev->work_flags)) { 92757b06aeSJacek Anaszewski if (brightness) 93a9c6ce57SHans de Goede set_bit(LED_BLINK_ONESHOT_STOP, 94a9c6ce57SHans de Goede &led_cdev->work_flags); 95757b06aeSJacek Anaszewski } else { 96757b06aeSJacek Anaszewski if (!brightness) 97a9c6ce57SHans de Goede set_bit(LED_BLINK_ONESHOT_STOP, 98a9c6ce57SHans de Goede &led_cdev->work_flags); 99757b06aeSJacek Anaszewski } 100757b06aeSJacek Anaszewski } 101757b06aeSJacek Anaszewski 102757b06aeSJacek Anaszewski mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay)); 103757b06aeSJacek Anaszewski } 104757b06aeSJacek Anaszewski 105757b06aeSJacek Anaszewski static void set_brightness_delayed(struct work_struct *ws) 106757b06aeSJacek Anaszewski { 107757b06aeSJacek Anaszewski struct led_classdev *led_cdev = 108757b06aeSJacek Anaszewski container_of(ws, struct led_classdev, set_brightness_work); 1091afcadfcSJacek Anaszewski int ret = 0; 110757b06aeSJacek Anaszewski 111a9c6ce57SHans de Goede if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) { 112f1e80c07SJacek Anaszewski led_cdev->delayed_set_value = LED_OFF; 113757b06aeSJacek Anaszewski led_stop_software_blink(led_cdev); 114f1e80c07SJacek Anaszewski } 115757b06aeSJacek Anaszewski 116d4887af9SHeiner Kallweit ret = __led_set_brightness(led_cdev, led_cdev->delayed_set_value); 117d4887af9SHeiner Kallweit if (ret == -ENOTSUPP) 118d4887af9SHeiner Kallweit ret = __led_set_brightness_blocking(led_cdev, 1191afcadfcSJacek Anaszewski led_cdev->delayed_set_value); 120d84d80f3SHeiner Kallweit if (ret < 0 && 121d84d80f3SHeiner Kallweit /* LED HW might have been unplugged, therefore don't warn */ 122d84d80f3SHeiner Kallweit !(ret == -ENODEV && (led_cdev->flags & LED_UNREGISTERING) && 123d84d80f3SHeiner Kallweit (led_cdev->flags & LED_HW_PLUGGABLE))) 1241afcadfcSJacek Anaszewski dev_err(led_cdev->dev, 1251afcadfcSJacek Anaszewski "Setting an LED's brightness failed (%d)\n", ret); 126757b06aeSJacek Anaszewski } 127757b06aeSJacek Anaszewski 128a403d930SBryan Wu static void led_set_software_blink(struct led_classdev *led_cdev, 129a403d930SBryan Wu unsigned long delay_on, 130a403d930SBryan Wu unsigned long delay_off) 131a403d930SBryan Wu { 132a403d930SBryan Wu int current_brightness; 133a403d930SBryan Wu 134a403d930SBryan Wu current_brightness = led_get_brightness(led_cdev); 135a403d930SBryan Wu if (current_brightness) 136a403d930SBryan Wu led_cdev->blink_brightness = current_brightness; 137a403d930SBryan Wu if (!led_cdev->blink_brightness) 138a403d930SBryan Wu led_cdev->blink_brightness = led_cdev->max_brightness; 139a403d930SBryan Wu 140a403d930SBryan Wu led_cdev->blink_delay_on = delay_on; 141a403d930SBryan Wu led_cdev->blink_delay_off = delay_off; 142a403d930SBryan Wu 1438d82fef8SStefan Sørensen /* never on - just set to off */ 1448d82fef8SStefan Sørensen if (!delay_on) { 14581fe8e5bSJacek Anaszewski led_set_brightness_nosleep(led_cdev, LED_OFF); 146a403d930SBryan Wu return; 1478d82fef8SStefan Sørensen } 148a403d930SBryan Wu 149a403d930SBryan Wu /* never off - just set to brightness */ 150a403d930SBryan Wu if (!delay_off) { 15181fe8e5bSJacek Anaszewski led_set_brightness_nosleep(led_cdev, 15281fe8e5bSJacek Anaszewski led_cdev->blink_brightness); 153a403d930SBryan Wu return; 154a403d930SBryan Wu } 155a403d930SBryan Wu 156a9c6ce57SHans de Goede set_bit(LED_BLINK_SW, &led_cdev->work_flags); 1579067359fSJiri Kosina mod_timer(&led_cdev->blink_timer, jiffies + 1); 158a403d930SBryan Wu } 159a403d930SBryan Wu 160a403d930SBryan Wu 16120c0e6b8SBryan Wu static void led_blink_setup(struct led_classdev *led_cdev, 162a403d930SBryan Wu unsigned long *delay_on, 163a403d930SBryan Wu unsigned long *delay_off) 164a403d930SBryan Wu { 165a9c6ce57SHans de Goede if (!test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) && 1665bb629c5SFabio Baltieri led_cdev->blink_set && 167a403d930SBryan Wu !led_cdev->blink_set(led_cdev, delay_on, delay_off)) 168a403d930SBryan Wu return; 169a403d930SBryan Wu 170a403d930SBryan Wu /* blink with 1 Hz as default if nothing specified */ 171a403d930SBryan Wu if (!*delay_on && !*delay_off) 172a403d930SBryan Wu *delay_on = *delay_off = 500; 173a403d930SBryan Wu 174a403d930SBryan Wu led_set_software_blink(led_cdev, *delay_on, *delay_off); 175a403d930SBryan Wu } 1765bb629c5SFabio Baltieri 177757b06aeSJacek Anaszewski void led_init_core(struct led_classdev *led_cdev) 178757b06aeSJacek Anaszewski { 179757b06aeSJacek Anaszewski INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed); 180757b06aeSJacek Anaszewski 181757b06aeSJacek Anaszewski setup_timer(&led_cdev->blink_timer, led_timer_function, 182757b06aeSJacek Anaszewski (unsigned long)led_cdev); 183757b06aeSJacek Anaszewski } 184757b06aeSJacek Anaszewski EXPORT_SYMBOL_GPL(led_init_core); 185757b06aeSJacek Anaszewski 1865bb629c5SFabio Baltieri void led_blink_set(struct led_classdev *led_cdev, 1875bb629c5SFabio Baltieri unsigned long *delay_on, 1885bb629c5SFabio Baltieri unsigned long *delay_off) 1895bb629c5SFabio Baltieri { 1909067359fSJiri Kosina del_timer_sync(&led_cdev->blink_timer); 1915bb629c5SFabio Baltieri 192a9c6ce57SHans de Goede clear_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags); 193a9c6ce57SHans de Goede clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags); 1945bb629c5SFabio Baltieri 1955bb629c5SFabio Baltieri led_blink_setup(led_cdev, delay_on, delay_off); 1965bb629c5SFabio Baltieri } 1972806e2ffSJacek Anaszewski EXPORT_SYMBOL_GPL(led_blink_set); 198a403d930SBryan Wu 1995bb629c5SFabio Baltieri void led_blink_set_oneshot(struct led_classdev *led_cdev, 2005bb629c5SFabio Baltieri unsigned long *delay_on, 2015bb629c5SFabio Baltieri unsigned long *delay_off, 2025bb629c5SFabio Baltieri int invert) 2035bb629c5SFabio Baltieri { 204a9c6ce57SHans de Goede if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) && 2059067359fSJiri Kosina timer_pending(&led_cdev->blink_timer)) 2065bb629c5SFabio Baltieri return; 2075bb629c5SFabio Baltieri 208a9c6ce57SHans de Goede set_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags); 209a9c6ce57SHans de Goede clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags); 2105bb629c5SFabio Baltieri 2115bb629c5SFabio Baltieri if (invert) 212a9c6ce57SHans de Goede set_bit(LED_BLINK_INVERT, &led_cdev->work_flags); 2135bb629c5SFabio Baltieri else 214a9c6ce57SHans de Goede clear_bit(LED_BLINK_INVERT, &led_cdev->work_flags); 2155bb629c5SFabio Baltieri 2165bb629c5SFabio Baltieri led_blink_setup(led_cdev, delay_on, delay_off); 2175bb629c5SFabio Baltieri } 2182806e2ffSJacek Anaszewski EXPORT_SYMBOL_GPL(led_blink_set_oneshot); 2195bb629c5SFabio Baltieri 220d23a22a7SFabio Baltieri void led_stop_software_blink(struct led_classdev *led_cdev) 221a403d930SBryan Wu { 2229067359fSJiri Kosina del_timer_sync(&led_cdev->blink_timer); 22343786482SFabio Baltieri led_cdev->blink_delay_on = 0; 22443786482SFabio Baltieri led_cdev->blink_delay_off = 0; 225a9c6ce57SHans de Goede clear_bit(LED_BLINK_SW, &led_cdev->work_flags); 226d23a22a7SFabio Baltieri } 227d23a22a7SFabio Baltieri EXPORT_SYMBOL_GPL(led_stop_software_blink); 228d23a22a7SFabio Baltieri 229d23a22a7SFabio Baltieri void led_set_brightness(struct led_classdev *led_cdev, 230d23a22a7SFabio Baltieri enum led_brightness brightness) 231d23a22a7SFabio Baltieri { 232f1e80c07SJacek Anaszewski /* 2337cfe749fSTony Makkiel * If software blink is active, delay brightness setting 234f1e80c07SJacek Anaszewski * until the next timer tick. 235f1e80c07SJacek Anaszewski */ 236a9c6ce57SHans de Goede if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) { 237f1e80c07SJacek Anaszewski /* 238f1e80c07SJacek Anaszewski * If we need to disable soft blinking delegate this to the 239f1e80c07SJacek Anaszewski * work queue task to avoid problems in case we are called 240f1e80c07SJacek Anaszewski * from hard irq context. 241f1e80c07SJacek Anaszewski */ 242f1e80c07SJacek Anaszewski if (brightness == LED_OFF) { 243a9c6ce57SHans de Goede set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags); 244d23a22a7SFabio Baltieri schedule_work(&led_cdev->set_brightness_work); 245f1e80c07SJacek Anaszewski } else { 246a9c6ce57SHans de Goede set_bit(LED_BLINK_BRIGHTNESS_CHANGE, 247a9c6ce57SHans de Goede &led_cdev->work_flags); 248eb1610b4SHans de Goede led_cdev->new_blink_brightness = brightness; 249f1e80c07SJacek Anaszewski } 250d23a22a7SFabio Baltieri return; 251d23a22a7SFabio Baltieri } 25243786482SFabio Baltieri 25381fe8e5bSJacek Anaszewski led_set_brightness_nosleep(led_cdev, brightness); 254a403d930SBryan Wu } 2552806e2ffSJacek Anaszewski EXPORT_SYMBOL_GPL(led_set_brightness); 2563ef7de53SJacek Anaszewski 25781fe8e5bSJacek Anaszewski void led_set_brightness_nopm(struct led_classdev *led_cdev, 25881fe8e5bSJacek Anaszewski enum led_brightness value) 25981fe8e5bSJacek Anaszewski { 26081fe8e5bSJacek Anaszewski /* Use brightness_set op if available, it is guaranteed not to sleep */ 261d4887af9SHeiner Kallweit if (!__led_set_brightness(led_cdev, value)) 26281fe8e5bSJacek Anaszewski return; 26381fe8e5bSJacek Anaszewski 26481fe8e5bSJacek Anaszewski /* If brightness setting can sleep, delegate it to a work queue task */ 26581fe8e5bSJacek Anaszewski led_cdev->delayed_set_value = value; 26681fe8e5bSJacek Anaszewski schedule_work(&led_cdev->set_brightness_work); 26781fe8e5bSJacek Anaszewski } 26881fe8e5bSJacek Anaszewski EXPORT_SYMBOL_GPL(led_set_brightness_nopm); 26981fe8e5bSJacek Anaszewski 27081fe8e5bSJacek Anaszewski void led_set_brightness_nosleep(struct led_classdev *led_cdev, 27181fe8e5bSJacek Anaszewski enum led_brightness value) 27281fe8e5bSJacek Anaszewski { 27381fe8e5bSJacek Anaszewski led_cdev->brightness = min(value, led_cdev->max_brightness); 27481fe8e5bSJacek Anaszewski 27581fe8e5bSJacek Anaszewski if (led_cdev->flags & LED_SUSPENDED) 27681fe8e5bSJacek Anaszewski return; 27781fe8e5bSJacek Anaszewski 27881fe8e5bSJacek Anaszewski led_set_brightness_nopm(led_cdev, led_cdev->brightness); 27981fe8e5bSJacek Anaszewski } 28081fe8e5bSJacek Anaszewski EXPORT_SYMBOL_GPL(led_set_brightness_nosleep); 28181fe8e5bSJacek Anaszewski 28213ae79bbSJacek Anaszewski int led_set_brightness_sync(struct led_classdev *led_cdev, 28313ae79bbSJacek Anaszewski enum led_brightness value) 28413ae79bbSJacek Anaszewski { 28513ae79bbSJacek Anaszewski if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) 28613ae79bbSJacek Anaszewski return -EBUSY; 28713ae79bbSJacek Anaszewski 28813ae79bbSJacek Anaszewski led_cdev->brightness = min(value, led_cdev->max_brightness); 28913ae79bbSJacek Anaszewski 29013ae79bbSJacek Anaszewski if (led_cdev->flags & LED_SUSPENDED) 29113ae79bbSJacek Anaszewski return 0; 29213ae79bbSJacek Anaszewski 293d4887af9SHeiner Kallweit return __led_set_brightness_blocking(led_cdev, led_cdev->brightness); 29413ae79bbSJacek Anaszewski } 29513ae79bbSJacek Anaszewski EXPORT_SYMBOL_GPL(led_set_brightness_sync); 29613ae79bbSJacek Anaszewski 2973ef7de53SJacek Anaszewski int led_update_brightness(struct led_classdev *led_cdev) 2983ef7de53SJacek Anaszewski { 2993ef7de53SJacek Anaszewski int ret = 0; 3003ef7de53SJacek Anaszewski 3013ef7de53SJacek Anaszewski if (led_cdev->brightness_get) { 3023ef7de53SJacek Anaszewski ret = led_cdev->brightness_get(led_cdev); 3033ef7de53SJacek Anaszewski if (ret >= 0) { 3043ef7de53SJacek Anaszewski led_cdev->brightness = ret; 3053ef7de53SJacek Anaszewski return 0; 3063ef7de53SJacek Anaszewski } 3073ef7de53SJacek Anaszewski } 3083ef7de53SJacek Anaszewski 3093ef7de53SJacek Anaszewski return ret; 3103ef7de53SJacek Anaszewski } 3112806e2ffSJacek Anaszewski EXPORT_SYMBOL_GPL(led_update_brightness); 312acd899e4SJacek Anaszewski 313acd899e4SJacek Anaszewski /* Caller must ensure led_cdev->led_access held */ 314acd899e4SJacek Anaszewski void led_sysfs_disable(struct led_classdev *led_cdev) 315acd899e4SJacek Anaszewski { 316acd899e4SJacek Anaszewski lockdep_assert_held(&led_cdev->led_access); 317acd899e4SJacek Anaszewski 318acd899e4SJacek Anaszewski led_cdev->flags |= LED_SYSFS_DISABLE; 319acd899e4SJacek Anaszewski } 320acd899e4SJacek Anaszewski EXPORT_SYMBOL_GPL(led_sysfs_disable); 321acd899e4SJacek Anaszewski 322acd899e4SJacek Anaszewski /* Caller must ensure led_cdev->led_access held */ 323acd899e4SJacek Anaszewski void led_sysfs_enable(struct led_classdev *led_cdev) 324acd899e4SJacek Anaszewski { 325acd899e4SJacek Anaszewski lockdep_assert_held(&led_cdev->led_access); 326acd899e4SJacek Anaszewski 327acd899e4SJacek Anaszewski led_cdev->flags &= ~LED_SYSFS_DISABLE; 328acd899e4SJacek Anaszewski } 329acd899e4SJacek Anaszewski EXPORT_SYMBOL_GPL(led_sysfs_enable); 330