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 28*757b06aeSJacek Anaszewski static void led_timer_function(unsigned long data) 29*757b06aeSJacek Anaszewski { 30*757b06aeSJacek Anaszewski struct led_classdev *led_cdev = (void *)data; 31*757b06aeSJacek Anaszewski unsigned long brightness; 32*757b06aeSJacek Anaszewski unsigned long delay; 33*757b06aeSJacek Anaszewski 34*757b06aeSJacek Anaszewski if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) { 35*757b06aeSJacek Anaszewski led_set_brightness_async(led_cdev, LED_OFF); 36*757b06aeSJacek Anaszewski return; 37*757b06aeSJacek Anaszewski } 38*757b06aeSJacek Anaszewski 39*757b06aeSJacek Anaszewski if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) { 40*757b06aeSJacek Anaszewski led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; 41*757b06aeSJacek Anaszewski return; 42*757b06aeSJacek Anaszewski } 43*757b06aeSJacek Anaszewski 44*757b06aeSJacek Anaszewski brightness = led_get_brightness(led_cdev); 45*757b06aeSJacek Anaszewski if (!brightness) { 46*757b06aeSJacek Anaszewski /* Time to switch the LED on. */ 47*757b06aeSJacek Anaszewski if (led_cdev->delayed_set_value) { 48*757b06aeSJacek Anaszewski led_cdev->blink_brightness = 49*757b06aeSJacek Anaszewski led_cdev->delayed_set_value; 50*757b06aeSJacek Anaszewski led_cdev->delayed_set_value = 0; 51*757b06aeSJacek Anaszewski } 52*757b06aeSJacek Anaszewski brightness = led_cdev->blink_brightness; 53*757b06aeSJacek Anaszewski delay = led_cdev->blink_delay_on; 54*757b06aeSJacek Anaszewski } else { 55*757b06aeSJacek Anaszewski /* Store the current brightness value to be able 56*757b06aeSJacek Anaszewski * to restore it when the delay_off period is over. 57*757b06aeSJacek Anaszewski */ 58*757b06aeSJacek Anaszewski led_cdev->blink_brightness = brightness; 59*757b06aeSJacek Anaszewski brightness = LED_OFF; 60*757b06aeSJacek Anaszewski delay = led_cdev->blink_delay_off; 61*757b06aeSJacek Anaszewski } 62*757b06aeSJacek Anaszewski 63*757b06aeSJacek Anaszewski led_set_brightness_async(led_cdev, brightness); 64*757b06aeSJacek Anaszewski 65*757b06aeSJacek Anaszewski /* Return in next iteration if led is in one-shot mode and we are in 66*757b06aeSJacek Anaszewski * the final blink state so that the led is toggled each delay_on + 67*757b06aeSJacek Anaszewski * delay_off milliseconds in worst case. 68*757b06aeSJacek Anaszewski */ 69*757b06aeSJacek Anaszewski if (led_cdev->flags & LED_BLINK_ONESHOT) { 70*757b06aeSJacek Anaszewski if (led_cdev->flags & LED_BLINK_INVERT) { 71*757b06aeSJacek Anaszewski if (brightness) 72*757b06aeSJacek Anaszewski led_cdev->flags |= LED_BLINK_ONESHOT_STOP; 73*757b06aeSJacek Anaszewski } else { 74*757b06aeSJacek Anaszewski if (!brightness) 75*757b06aeSJacek Anaszewski led_cdev->flags |= LED_BLINK_ONESHOT_STOP; 76*757b06aeSJacek Anaszewski } 77*757b06aeSJacek Anaszewski } 78*757b06aeSJacek Anaszewski 79*757b06aeSJacek Anaszewski mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay)); 80*757b06aeSJacek Anaszewski } 81*757b06aeSJacek Anaszewski 82*757b06aeSJacek Anaszewski static void set_brightness_delayed(struct work_struct *ws) 83*757b06aeSJacek Anaszewski { 84*757b06aeSJacek Anaszewski struct led_classdev *led_cdev = 85*757b06aeSJacek Anaszewski container_of(ws, struct led_classdev, set_brightness_work); 86*757b06aeSJacek Anaszewski 87*757b06aeSJacek Anaszewski led_stop_software_blink(led_cdev); 88*757b06aeSJacek Anaszewski 89*757b06aeSJacek Anaszewski led_set_brightness_async(led_cdev, led_cdev->delayed_set_value); 90*757b06aeSJacek Anaszewski } 91*757b06aeSJacek Anaszewski 92a403d930SBryan Wu static void led_set_software_blink(struct led_classdev *led_cdev, 93a403d930SBryan Wu unsigned long delay_on, 94a403d930SBryan Wu unsigned long delay_off) 95a403d930SBryan Wu { 96a403d930SBryan Wu int current_brightness; 97a403d930SBryan Wu 98a403d930SBryan Wu current_brightness = led_get_brightness(led_cdev); 99a403d930SBryan Wu if (current_brightness) 100a403d930SBryan Wu led_cdev->blink_brightness = current_brightness; 101a403d930SBryan Wu if (!led_cdev->blink_brightness) 102a403d930SBryan Wu led_cdev->blink_brightness = led_cdev->max_brightness; 103a403d930SBryan Wu 104a403d930SBryan Wu led_cdev->blink_delay_on = delay_on; 105a403d930SBryan Wu led_cdev->blink_delay_off = delay_off; 106a403d930SBryan Wu 1078d82fef8SStefan Sørensen /* never on - just set to off */ 1088d82fef8SStefan Sørensen if (!delay_on) { 1094d71a4a1SJacek Anaszewski led_set_brightness_async(led_cdev, LED_OFF); 110a403d930SBryan Wu return; 1118d82fef8SStefan Sørensen } 112a403d930SBryan Wu 113a403d930SBryan Wu /* never off - just set to brightness */ 114a403d930SBryan Wu if (!delay_off) { 1154d71a4a1SJacek Anaszewski led_set_brightness_async(led_cdev, led_cdev->blink_brightness); 116a403d930SBryan Wu return; 117a403d930SBryan Wu } 118a403d930SBryan Wu 1199067359fSJiri Kosina mod_timer(&led_cdev->blink_timer, jiffies + 1); 120a403d930SBryan Wu } 121a403d930SBryan Wu 122a403d930SBryan Wu 12320c0e6b8SBryan Wu static void led_blink_setup(struct led_classdev *led_cdev, 124a403d930SBryan Wu unsigned long *delay_on, 125a403d930SBryan Wu unsigned long *delay_off) 126a403d930SBryan Wu { 1275bb629c5SFabio Baltieri if (!(led_cdev->flags & LED_BLINK_ONESHOT) && 1285bb629c5SFabio Baltieri led_cdev->blink_set && 129a403d930SBryan Wu !led_cdev->blink_set(led_cdev, delay_on, delay_off)) 130a403d930SBryan Wu return; 131a403d930SBryan Wu 132a403d930SBryan Wu /* blink with 1 Hz as default if nothing specified */ 133a403d930SBryan Wu if (!*delay_on && !*delay_off) 134a403d930SBryan Wu *delay_on = *delay_off = 500; 135a403d930SBryan Wu 136a403d930SBryan Wu led_set_software_blink(led_cdev, *delay_on, *delay_off); 137a403d930SBryan Wu } 1385bb629c5SFabio Baltieri 139*757b06aeSJacek Anaszewski void led_init_core(struct led_classdev *led_cdev) 140*757b06aeSJacek Anaszewski { 141*757b06aeSJacek Anaszewski INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed); 142*757b06aeSJacek Anaszewski 143*757b06aeSJacek Anaszewski setup_timer(&led_cdev->blink_timer, led_timer_function, 144*757b06aeSJacek Anaszewski (unsigned long)led_cdev); 145*757b06aeSJacek Anaszewski } 146*757b06aeSJacek Anaszewski EXPORT_SYMBOL_GPL(led_init_core); 147*757b06aeSJacek Anaszewski 1485bb629c5SFabio Baltieri void led_blink_set(struct led_classdev *led_cdev, 1495bb629c5SFabio Baltieri unsigned long *delay_on, 1505bb629c5SFabio Baltieri unsigned long *delay_off) 1515bb629c5SFabio Baltieri { 1529067359fSJiri Kosina del_timer_sync(&led_cdev->blink_timer); 1535bb629c5SFabio Baltieri 1545bb629c5SFabio Baltieri led_cdev->flags &= ~LED_BLINK_ONESHOT; 1555bb629c5SFabio Baltieri led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; 1565bb629c5SFabio Baltieri 1575bb629c5SFabio Baltieri led_blink_setup(led_cdev, delay_on, delay_off); 1585bb629c5SFabio Baltieri } 159a403d930SBryan Wu EXPORT_SYMBOL(led_blink_set); 160a403d930SBryan Wu 1615bb629c5SFabio Baltieri void led_blink_set_oneshot(struct led_classdev *led_cdev, 1625bb629c5SFabio Baltieri unsigned long *delay_on, 1635bb629c5SFabio Baltieri unsigned long *delay_off, 1645bb629c5SFabio Baltieri int invert) 1655bb629c5SFabio Baltieri { 1665bb629c5SFabio Baltieri if ((led_cdev->flags & LED_BLINK_ONESHOT) && 1679067359fSJiri Kosina timer_pending(&led_cdev->blink_timer)) 1685bb629c5SFabio Baltieri return; 1695bb629c5SFabio Baltieri 1705bb629c5SFabio Baltieri led_cdev->flags |= LED_BLINK_ONESHOT; 1715bb629c5SFabio Baltieri led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; 1725bb629c5SFabio Baltieri 1735bb629c5SFabio Baltieri if (invert) 1745bb629c5SFabio Baltieri led_cdev->flags |= LED_BLINK_INVERT; 1755bb629c5SFabio Baltieri else 1765bb629c5SFabio Baltieri led_cdev->flags &= ~LED_BLINK_INVERT; 1775bb629c5SFabio Baltieri 1785bb629c5SFabio Baltieri led_blink_setup(led_cdev, delay_on, delay_off); 1795bb629c5SFabio Baltieri } 1805bb629c5SFabio Baltieri EXPORT_SYMBOL(led_blink_set_oneshot); 1815bb629c5SFabio Baltieri 182d23a22a7SFabio Baltieri void led_stop_software_blink(struct led_classdev *led_cdev) 183a403d930SBryan Wu { 1849067359fSJiri Kosina del_timer_sync(&led_cdev->blink_timer); 18543786482SFabio Baltieri led_cdev->blink_delay_on = 0; 18643786482SFabio Baltieri led_cdev->blink_delay_off = 0; 187d23a22a7SFabio Baltieri } 188d23a22a7SFabio Baltieri EXPORT_SYMBOL_GPL(led_stop_software_blink); 189d23a22a7SFabio Baltieri 190d23a22a7SFabio Baltieri void led_set_brightness(struct led_classdev *led_cdev, 191d23a22a7SFabio Baltieri enum led_brightness brightness) 192d23a22a7SFabio Baltieri { 1934d71a4a1SJacek Anaszewski int ret = 0; 1944d71a4a1SJacek Anaszewski 19576931eddSStas Sergeev /* delay brightness if soft-blink is active */ 196d23a22a7SFabio Baltieri if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) { 197d23a22a7SFabio Baltieri led_cdev->delayed_set_value = brightness; 19876931eddSStas Sergeev if (brightness == LED_OFF) 199d23a22a7SFabio Baltieri schedule_work(&led_cdev->set_brightness_work); 200d23a22a7SFabio Baltieri return; 201d23a22a7SFabio Baltieri } 20243786482SFabio Baltieri 2034d71a4a1SJacek Anaszewski if (led_cdev->flags & SET_BRIGHTNESS_ASYNC) { 2044d71a4a1SJacek Anaszewski led_set_brightness_async(led_cdev, brightness); 2054d71a4a1SJacek Anaszewski return; 2064d71a4a1SJacek Anaszewski } else if (led_cdev->flags & SET_BRIGHTNESS_SYNC) 2074d71a4a1SJacek Anaszewski ret = led_set_brightness_sync(led_cdev, brightness); 2084d71a4a1SJacek Anaszewski else 2094d71a4a1SJacek Anaszewski ret = -EINVAL; 2104d71a4a1SJacek Anaszewski 2114d71a4a1SJacek Anaszewski if (ret < 0) 2124d71a4a1SJacek Anaszewski dev_dbg(led_cdev->dev, "Setting LED brightness failed (%d)\n", 2134d71a4a1SJacek Anaszewski ret); 214a403d930SBryan Wu } 21519cd67e2SShuah Khan EXPORT_SYMBOL(led_set_brightness); 2163ef7de53SJacek Anaszewski 2173ef7de53SJacek Anaszewski int led_update_brightness(struct led_classdev *led_cdev) 2183ef7de53SJacek Anaszewski { 2193ef7de53SJacek Anaszewski int ret = 0; 2203ef7de53SJacek Anaszewski 2213ef7de53SJacek Anaszewski if (led_cdev->brightness_get) { 2223ef7de53SJacek Anaszewski ret = led_cdev->brightness_get(led_cdev); 2233ef7de53SJacek Anaszewski if (ret >= 0) { 2243ef7de53SJacek Anaszewski led_cdev->brightness = ret; 2253ef7de53SJacek Anaszewski return 0; 2263ef7de53SJacek Anaszewski } 2273ef7de53SJacek Anaszewski } 2283ef7de53SJacek Anaszewski 2293ef7de53SJacek Anaszewski return ret; 2303ef7de53SJacek Anaszewski } 2313ef7de53SJacek Anaszewski EXPORT_SYMBOL(led_update_brightness); 232acd899e4SJacek Anaszewski 233acd899e4SJacek Anaszewski /* Caller must ensure led_cdev->led_access held */ 234acd899e4SJacek Anaszewski void led_sysfs_disable(struct led_classdev *led_cdev) 235acd899e4SJacek Anaszewski { 236acd899e4SJacek Anaszewski lockdep_assert_held(&led_cdev->led_access); 237acd899e4SJacek Anaszewski 238acd899e4SJacek Anaszewski led_cdev->flags |= LED_SYSFS_DISABLE; 239acd899e4SJacek Anaszewski } 240acd899e4SJacek Anaszewski EXPORT_SYMBOL_GPL(led_sysfs_disable); 241acd899e4SJacek Anaszewski 242acd899e4SJacek Anaszewski /* Caller must ensure led_cdev->led_access held */ 243acd899e4SJacek Anaszewski void led_sysfs_enable(struct led_classdev *led_cdev) 244acd899e4SJacek Anaszewski { 245acd899e4SJacek Anaszewski lockdep_assert_held(&led_cdev->led_access); 246acd899e4SJacek Anaszewski 247acd899e4SJacek Anaszewski led_cdev->flags &= ~LED_SYSFS_DISABLE; 248acd899e4SJacek Anaszewski } 249acd899e4SJacek Anaszewski EXPORT_SYMBOL_GPL(led_sysfs_enable); 250