162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * LED Class Core 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2005-2006 Openedhand Ltd. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Richard Purdie <rpurdie@openedhand.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/leds.h> 1262306a36Sopenharmony_ci#include <linux/list.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/mutex.h> 1562306a36Sopenharmony_ci#include <linux/of.h> 1662306a36Sopenharmony_ci#include <linux/property.h> 1762306a36Sopenharmony_ci#include <linux/rwsem.h> 1862306a36Sopenharmony_ci#include <linux/slab.h> 1962306a36Sopenharmony_ci#include <uapi/linux/uleds.h> 2062306a36Sopenharmony_ci#include "leds.h" 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ciDECLARE_RWSEM(leds_list_lock); 2362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(leds_list_lock); 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ciLIST_HEAD(leds_list); 2662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(leds_list); 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ciconst char * const led_colors[LED_COLOR_ID_MAX] = { 2962306a36Sopenharmony_ci [LED_COLOR_ID_WHITE] = "white", 3062306a36Sopenharmony_ci [LED_COLOR_ID_RED] = "red", 3162306a36Sopenharmony_ci [LED_COLOR_ID_GREEN] = "green", 3262306a36Sopenharmony_ci [LED_COLOR_ID_BLUE] = "blue", 3362306a36Sopenharmony_ci [LED_COLOR_ID_AMBER] = "amber", 3462306a36Sopenharmony_ci [LED_COLOR_ID_VIOLET] = "violet", 3562306a36Sopenharmony_ci [LED_COLOR_ID_YELLOW] = "yellow", 3662306a36Sopenharmony_ci [LED_COLOR_ID_IR] = "ir", 3762306a36Sopenharmony_ci [LED_COLOR_ID_MULTI] = "multicolor", 3862306a36Sopenharmony_ci [LED_COLOR_ID_RGB] = "rgb", 3962306a36Sopenharmony_ci}; 4062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_colors); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic int __led_set_brightness(struct led_classdev *led_cdev, unsigned int value) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci if (!led_cdev->brightness_set) 4562306a36Sopenharmony_ci return -ENOTSUPP; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci led_cdev->brightness_set(led_cdev, value); 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci return 0; 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic int __led_set_brightness_blocking(struct led_classdev *led_cdev, unsigned int value) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci if (!led_cdev->brightness_set_blocking) 5562306a36Sopenharmony_ci return -ENOTSUPP; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci return led_cdev->brightness_set_blocking(led_cdev, value); 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic void led_timer_function(struct timer_list *t) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci struct led_classdev *led_cdev = from_timer(led_cdev, t, blink_timer); 6362306a36Sopenharmony_ci unsigned long brightness; 6462306a36Sopenharmony_ci unsigned long delay; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) { 6762306a36Sopenharmony_ci led_set_brightness_nosleep(led_cdev, LED_OFF); 6862306a36Sopenharmony_ci clear_bit(LED_BLINK_SW, &led_cdev->work_flags); 6962306a36Sopenharmony_ci return; 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci if (test_and_clear_bit(LED_BLINK_ONESHOT_STOP, 7362306a36Sopenharmony_ci &led_cdev->work_flags)) { 7462306a36Sopenharmony_ci clear_bit(LED_BLINK_SW, &led_cdev->work_flags); 7562306a36Sopenharmony_ci return; 7662306a36Sopenharmony_ci } 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci brightness = led_get_brightness(led_cdev); 7962306a36Sopenharmony_ci if (!brightness) { 8062306a36Sopenharmony_ci /* Time to switch the LED on. */ 8162306a36Sopenharmony_ci if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, 8262306a36Sopenharmony_ci &led_cdev->work_flags)) 8362306a36Sopenharmony_ci brightness = led_cdev->new_blink_brightness; 8462306a36Sopenharmony_ci else 8562306a36Sopenharmony_ci brightness = led_cdev->blink_brightness; 8662306a36Sopenharmony_ci delay = led_cdev->blink_delay_on; 8762306a36Sopenharmony_ci } else { 8862306a36Sopenharmony_ci /* Store the current brightness value to be able 8962306a36Sopenharmony_ci * to restore it when the delay_off period is over. 9062306a36Sopenharmony_ci */ 9162306a36Sopenharmony_ci led_cdev->blink_brightness = brightness; 9262306a36Sopenharmony_ci brightness = LED_OFF; 9362306a36Sopenharmony_ci delay = led_cdev->blink_delay_off; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci led_set_brightness_nosleep(led_cdev, brightness); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci /* Return in next iteration if led is in one-shot mode and we are in 9962306a36Sopenharmony_ci * the final blink state so that the led is toggled each delay_on + 10062306a36Sopenharmony_ci * delay_off milliseconds in worst case. 10162306a36Sopenharmony_ci */ 10262306a36Sopenharmony_ci if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags)) { 10362306a36Sopenharmony_ci if (test_bit(LED_BLINK_INVERT, &led_cdev->work_flags)) { 10462306a36Sopenharmony_ci if (brightness) 10562306a36Sopenharmony_ci set_bit(LED_BLINK_ONESHOT_STOP, 10662306a36Sopenharmony_ci &led_cdev->work_flags); 10762306a36Sopenharmony_ci } else { 10862306a36Sopenharmony_ci if (!brightness) 10962306a36Sopenharmony_ci set_bit(LED_BLINK_ONESHOT_STOP, 11062306a36Sopenharmony_ci &led_cdev->work_flags); 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay)); 11562306a36Sopenharmony_ci} 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_cistatic void set_brightness_delayed_set_brightness(struct led_classdev *led_cdev, 11862306a36Sopenharmony_ci unsigned int value) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci int ret = 0; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci ret = __led_set_brightness(led_cdev, value); 12362306a36Sopenharmony_ci if (ret == -ENOTSUPP) 12462306a36Sopenharmony_ci ret = __led_set_brightness_blocking(led_cdev, value); 12562306a36Sopenharmony_ci if (ret < 0 && 12662306a36Sopenharmony_ci /* LED HW might have been unplugged, therefore don't warn */ 12762306a36Sopenharmony_ci !(ret == -ENODEV && (led_cdev->flags & LED_UNREGISTERING) && 12862306a36Sopenharmony_ci (led_cdev->flags & LED_HW_PLUGGABLE))) 12962306a36Sopenharmony_ci dev_err(led_cdev->dev, 13062306a36Sopenharmony_ci "Setting an LED's brightness failed (%d)\n", ret); 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic void set_brightness_delayed(struct work_struct *ws) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci struct led_classdev *led_cdev = 13662306a36Sopenharmony_ci container_of(ws, struct led_classdev, set_brightness_work); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) { 13962306a36Sopenharmony_ci led_stop_software_blink(led_cdev); 14062306a36Sopenharmony_ci set_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags); 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci /* 14462306a36Sopenharmony_ci * Triggers may call led_set_brightness(LED_OFF), 14562306a36Sopenharmony_ci * led_set_brightness(LED_FULL) in quick succession to disable blinking 14662306a36Sopenharmony_ci * and turn the LED on. Both actions may have been scheduled to run 14762306a36Sopenharmony_ci * before this work item runs once. To make sure this works properly 14862306a36Sopenharmony_ci * handle LED_SET_BRIGHTNESS_OFF first. 14962306a36Sopenharmony_ci */ 15062306a36Sopenharmony_ci if (test_and_clear_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags)) 15162306a36Sopenharmony_ci set_brightness_delayed_set_brightness(led_cdev, LED_OFF); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci if (test_and_clear_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags)) 15462306a36Sopenharmony_ci set_brightness_delayed_set_brightness(led_cdev, led_cdev->delayed_set_value); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci if (test_and_clear_bit(LED_SET_BLINK, &led_cdev->work_flags)) { 15762306a36Sopenharmony_ci unsigned long delay_on = led_cdev->delayed_delay_on; 15862306a36Sopenharmony_ci unsigned long delay_off = led_cdev->delayed_delay_off; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci led_blink_set(led_cdev, &delay_on, &delay_off); 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic void led_set_software_blink(struct led_classdev *led_cdev, 16562306a36Sopenharmony_ci unsigned long delay_on, 16662306a36Sopenharmony_ci unsigned long delay_off) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci int current_brightness; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci current_brightness = led_get_brightness(led_cdev); 17162306a36Sopenharmony_ci if (current_brightness) 17262306a36Sopenharmony_ci led_cdev->blink_brightness = current_brightness; 17362306a36Sopenharmony_ci if (!led_cdev->blink_brightness) 17462306a36Sopenharmony_ci led_cdev->blink_brightness = led_cdev->max_brightness; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci led_cdev->blink_delay_on = delay_on; 17762306a36Sopenharmony_ci led_cdev->blink_delay_off = delay_off; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* never on - just set to off */ 18062306a36Sopenharmony_ci if (!delay_on) { 18162306a36Sopenharmony_ci led_set_brightness_nosleep(led_cdev, LED_OFF); 18262306a36Sopenharmony_ci return; 18362306a36Sopenharmony_ci } 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci /* never off - just set to brightness */ 18662306a36Sopenharmony_ci if (!delay_off) { 18762306a36Sopenharmony_ci led_set_brightness_nosleep(led_cdev, 18862306a36Sopenharmony_ci led_cdev->blink_brightness); 18962306a36Sopenharmony_ci return; 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci set_bit(LED_BLINK_SW, &led_cdev->work_flags); 19362306a36Sopenharmony_ci mod_timer(&led_cdev->blink_timer, jiffies + 1); 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic void led_blink_setup(struct led_classdev *led_cdev, 19862306a36Sopenharmony_ci unsigned long *delay_on, 19962306a36Sopenharmony_ci unsigned long *delay_off) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci if (!test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) && 20262306a36Sopenharmony_ci led_cdev->blink_set && 20362306a36Sopenharmony_ci !led_cdev->blink_set(led_cdev, delay_on, delay_off)) 20462306a36Sopenharmony_ci return; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci /* blink with 1 Hz as default if nothing specified */ 20762306a36Sopenharmony_ci if (!*delay_on && !*delay_off) 20862306a36Sopenharmony_ci *delay_on = *delay_off = 500; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci led_set_software_blink(led_cdev, *delay_on, *delay_off); 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_civoid led_init_core(struct led_classdev *led_cdev) 21462306a36Sopenharmony_ci{ 21562306a36Sopenharmony_ci INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci timer_setup(&led_cdev->blink_timer, led_timer_function, 0); 21862306a36Sopenharmony_ci} 21962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_init_core); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_civoid led_blink_set(struct led_classdev *led_cdev, 22262306a36Sopenharmony_ci unsigned long *delay_on, 22362306a36Sopenharmony_ci unsigned long *delay_off) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci del_timer_sync(&led_cdev->blink_timer); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci clear_bit(LED_BLINK_SW, &led_cdev->work_flags); 22862306a36Sopenharmony_ci clear_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags); 22962306a36Sopenharmony_ci clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci led_blink_setup(led_cdev, delay_on, delay_off); 23262306a36Sopenharmony_ci} 23362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_blink_set); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_civoid led_blink_set_oneshot(struct led_classdev *led_cdev, 23662306a36Sopenharmony_ci unsigned long *delay_on, 23762306a36Sopenharmony_ci unsigned long *delay_off, 23862306a36Sopenharmony_ci int invert) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) && 24162306a36Sopenharmony_ci timer_pending(&led_cdev->blink_timer)) 24262306a36Sopenharmony_ci return; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci set_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags); 24562306a36Sopenharmony_ci clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci if (invert) 24862306a36Sopenharmony_ci set_bit(LED_BLINK_INVERT, &led_cdev->work_flags); 24962306a36Sopenharmony_ci else 25062306a36Sopenharmony_ci clear_bit(LED_BLINK_INVERT, &led_cdev->work_flags); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci led_blink_setup(led_cdev, delay_on, delay_off); 25362306a36Sopenharmony_ci} 25462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_blink_set_oneshot); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_civoid led_blink_set_nosleep(struct led_classdev *led_cdev, unsigned long delay_on, 25762306a36Sopenharmony_ci unsigned long delay_off) 25862306a36Sopenharmony_ci{ 25962306a36Sopenharmony_ci /* If necessary delegate to a work queue task. */ 26062306a36Sopenharmony_ci if (led_cdev->blink_set && led_cdev->brightness_set_blocking) { 26162306a36Sopenharmony_ci led_cdev->delayed_delay_on = delay_on; 26262306a36Sopenharmony_ci led_cdev->delayed_delay_off = delay_off; 26362306a36Sopenharmony_ci set_bit(LED_SET_BLINK, &led_cdev->work_flags); 26462306a36Sopenharmony_ci schedule_work(&led_cdev->set_brightness_work); 26562306a36Sopenharmony_ci return; 26662306a36Sopenharmony_ci } 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci led_blink_set(led_cdev, &delay_on, &delay_off); 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_blink_set_nosleep); 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_civoid led_stop_software_blink(struct led_classdev *led_cdev) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci del_timer_sync(&led_cdev->blink_timer); 27562306a36Sopenharmony_ci led_cdev->blink_delay_on = 0; 27662306a36Sopenharmony_ci led_cdev->blink_delay_off = 0; 27762306a36Sopenharmony_ci clear_bit(LED_BLINK_SW, &led_cdev->work_flags); 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_stop_software_blink); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_civoid led_set_brightness(struct led_classdev *led_cdev, unsigned int brightness) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci /* 28462306a36Sopenharmony_ci * If software blink is active, delay brightness setting 28562306a36Sopenharmony_ci * until the next timer tick. 28662306a36Sopenharmony_ci */ 28762306a36Sopenharmony_ci if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) { 28862306a36Sopenharmony_ci /* 28962306a36Sopenharmony_ci * If we need to disable soft blinking delegate this to the 29062306a36Sopenharmony_ci * work queue task to avoid problems in case we are called 29162306a36Sopenharmony_ci * from hard irq context. 29262306a36Sopenharmony_ci */ 29362306a36Sopenharmony_ci if (!brightness) { 29462306a36Sopenharmony_ci set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags); 29562306a36Sopenharmony_ci schedule_work(&led_cdev->set_brightness_work); 29662306a36Sopenharmony_ci } else { 29762306a36Sopenharmony_ci set_bit(LED_BLINK_BRIGHTNESS_CHANGE, 29862306a36Sopenharmony_ci &led_cdev->work_flags); 29962306a36Sopenharmony_ci led_cdev->new_blink_brightness = brightness; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci return; 30262306a36Sopenharmony_ci } 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci led_set_brightness_nosleep(led_cdev, brightness); 30562306a36Sopenharmony_ci} 30662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_set_brightness); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_civoid led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value) 30962306a36Sopenharmony_ci{ 31062306a36Sopenharmony_ci /* Use brightness_set op if available, it is guaranteed not to sleep */ 31162306a36Sopenharmony_ci if (!__led_set_brightness(led_cdev, value)) 31262306a36Sopenharmony_ci return; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci /* 31562306a36Sopenharmony_ci * Brightness setting can sleep, delegate it to a work queue task. 31662306a36Sopenharmony_ci * value 0 / LED_OFF is special, since it also disables hw-blinking 31762306a36Sopenharmony_ci * (sw-blink disable is handled in led_set_brightness()). 31862306a36Sopenharmony_ci * To avoid a hw-blink-disable getting lost when a second brightness 31962306a36Sopenharmony_ci * change is done immediately afterwards (before the work runs), 32062306a36Sopenharmony_ci * it uses a separate work_flag. 32162306a36Sopenharmony_ci */ 32262306a36Sopenharmony_ci if (value) { 32362306a36Sopenharmony_ci led_cdev->delayed_set_value = value; 32462306a36Sopenharmony_ci set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags); 32562306a36Sopenharmony_ci } else { 32662306a36Sopenharmony_ci clear_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags); 32762306a36Sopenharmony_ci clear_bit(LED_SET_BLINK, &led_cdev->work_flags); 32862306a36Sopenharmony_ci set_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags); 32962306a36Sopenharmony_ci } 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci schedule_work(&led_cdev->set_brightness_work); 33262306a36Sopenharmony_ci} 33362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_set_brightness_nopm); 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_civoid led_set_brightness_nosleep(struct led_classdev *led_cdev, unsigned int value) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci led_cdev->brightness = min(value, led_cdev->max_brightness); 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci if (led_cdev->flags & LED_SUSPENDED) 34062306a36Sopenharmony_ci return; 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci led_set_brightness_nopm(led_cdev, led_cdev->brightness); 34362306a36Sopenharmony_ci} 34462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_set_brightness_nosleep); 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ciint led_set_brightness_sync(struct led_classdev *led_cdev, unsigned int value) 34762306a36Sopenharmony_ci{ 34862306a36Sopenharmony_ci if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) 34962306a36Sopenharmony_ci return -EBUSY; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci led_cdev->brightness = min(value, led_cdev->max_brightness); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci if (led_cdev->flags & LED_SUSPENDED) 35462306a36Sopenharmony_ci return 0; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci return __led_set_brightness_blocking(led_cdev, led_cdev->brightness); 35762306a36Sopenharmony_ci} 35862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_set_brightness_sync); 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ciint led_update_brightness(struct led_classdev *led_cdev) 36162306a36Sopenharmony_ci{ 36262306a36Sopenharmony_ci int ret = 0; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci if (led_cdev->brightness_get) { 36562306a36Sopenharmony_ci ret = led_cdev->brightness_get(led_cdev); 36662306a36Sopenharmony_ci if (ret >= 0) { 36762306a36Sopenharmony_ci led_cdev->brightness = ret; 36862306a36Sopenharmony_ci return 0; 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci } 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci return ret; 37362306a36Sopenharmony_ci} 37462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_update_brightness); 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ciu32 *led_get_default_pattern(struct led_classdev *led_cdev, unsigned int *size) 37762306a36Sopenharmony_ci{ 37862306a36Sopenharmony_ci struct fwnode_handle *fwnode = led_cdev->dev->fwnode; 37962306a36Sopenharmony_ci u32 *pattern; 38062306a36Sopenharmony_ci int count; 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci count = fwnode_property_count_u32(fwnode, "led-pattern"); 38362306a36Sopenharmony_ci if (count < 0) 38462306a36Sopenharmony_ci return NULL; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci pattern = kcalloc(count, sizeof(*pattern), GFP_KERNEL); 38762306a36Sopenharmony_ci if (!pattern) 38862306a36Sopenharmony_ci return NULL; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci if (fwnode_property_read_u32_array(fwnode, "led-pattern", pattern, count)) { 39162306a36Sopenharmony_ci kfree(pattern); 39262306a36Sopenharmony_ci return NULL; 39362306a36Sopenharmony_ci } 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci *size = count; 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci return pattern; 39862306a36Sopenharmony_ci} 39962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_get_default_pattern); 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci/* Caller must ensure led_cdev->led_access held */ 40262306a36Sopenharmony_civoid led_sysfs_disable(struct led_classdev *led_cdev) 40362306a36Sopenharmony_ci{ 40462306a36Sopenharmony_ci lockdep_assert_held(&led_cdev->led_access); 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci led_cdev->flags |= LED_SYSFS_DISABLE; 40762306a36Sopenharmony_ci} 40862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_sysfs_disable); 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci/* Caller must ensure led_cdev->led_access held */ 41162306a36Sopenharmony_civoid led_sysfs_enable(struct led_classdev *led_cdev) 41262306a36Sopenharmony_ci{ 41362306a36Sopenharmony_ci lockdep_assert_held(&led_cdev->led_access); 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci led_cdev->flags &= ~LED_SYSFS_DISABLE; 41662306a36Sopenharmony_ci} 41762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_sysfs_enable); 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_cistatic void led_parse_fwnode_props(struct device *dev, 42062306a36Sopenharmony_ci struct fwnode_handle *fwnode, 42162306a36Sopenharmony_ci struct led_properties *props) 42262306a36Sopenharmony_ci{ 42362306a36Sopenharmony_ci int ret; 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci if (!fwnode) 42662306a36Sopenharmony_ci return; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci if (fwnode_property_present(fwnode, "label")) { 42962306a36Sopenharmony_ci ret = fwnode_property_read_string(fwnode, "label", &props->label); 43062306a36Sopenharmony_ci if (ret) 43162306a36Sopenharmony_ci dev_err(dev, "Error parsing 'label' property (%d)\n", ret); 43262306a36Sopenharmony_ci return; 43362306a36Sopenharmony_ci } 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci if (fwnode_property_present(fwnode, "color")) { 43662306a36Sopenharmony_ci ret = fwnode_property_read_u32(fwnode, "color", &props->color); 43762306a36Sopenharmony_ci if (ret) 43862306a36Sopenharmony_ci dev_err(dev, "Error parsing 'color' property (%d)\n", ret); 43962306a36Sopenharmony_ci else if (props->color >= LED_COLOR_ID_MAX) 44062306a36Sopenharmony_ci dev_err(dev, "LED color identifier out of range\n"); 44162306a36Sopenharmony_ci else 44262306a36Sopenharmony_ci props->color_present = true; 44362306a36Sopenharmony_ci } 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci if (!fwnode_property_present(fwnode, "function")) 44762306a36Sopenharmony_ci return; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci ret = fwnode_property_read_string(fwnode, "function", &props->function); 45062306a36Sopenharmony_ci if (ret) { 45162306a36Sopenharmony_ci dev_err(dev, 45262306a36Sopenharmony_ci "Error parsing 'function' property (%d)\n", 45362306a36Sopenharmony_ci ret); 45462306a36Sopenharmony_ci } 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci if (!fwnode_property_present(fwnode, "function-enumerator")) 45762306a36Sopenharmony_ci return; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci ret = fwnode_property_read_u32(fwnode, "function-enumerator", 46062306a36Sopenharmony_ci &props->func_enum); 46162306a36Sopenharmony_ci if (ret) { 46262306a36Sopenharmony_ci dev_err(dev, 46362306a36Sopenharmony_ci "Error parsing 'function-enumerator' property (%d)\n", 46462306a36Sopenharmony_ci ret); 46562306a36Sopenharmony_ci } else { 46662306a36Sopenharmony_ci props->func_enum_present = true; 46762306a36Sopenharmony_ci } 46862306a36Sopenharmony_ci} 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ciint led_compose_name(struct device *dev, struct led_init_data *init_data, 47162306a36Sopenharmony_ci char *led_classdev_name) 47262306a36Sopenharmony_ci{ 47362306a36Sopenharmony_ci struct led_properties props = {}; 47462306a36Sopenharmony_ci struct fwnode_handle *fwnode = init_data->fwnode; 47562306a36Sopenharmony_ci const char *devicename = init_data->devicename; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci if (!led_classdev_name) 47862306a36Sopenharmony_ci return -EINVAL; 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci led_parse_fwnode_props(dev, fwnode, &props); 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci if (props.label) { 48362306a36Sopenharmony_ci /* 48462306a36Sopenharmony_ci * If init_data.devicename is NULL, then it indicates that 48562306a36Sopenharmony_ci * DT label should be used as-is for LED class device name. 48662306a36Sopenharmony_ci * Otherwise the label is prepended with devicename to compose 48762306a36Sopenharmony_ci * the final LED class device name. 48862306a36Sopenharmony_ci */ 48962306a36Sopenharmony_ci if (!devicename) { 49062306a36Sopenharmony_ci strscpy(led_classdev_name, props.label, 49162306a36Sopenharmony_ci LED_MAX_NAME_SIZE); 49262306a36Sopenharmony_ci } else { 49362306a36Sopenharmony_ci snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s", 49462306a36Sopenharmony_ci devicename, props.label); 49562306a36Sopenharmony_ci } 49662306a36Sopenharmony_ci } else if (props.function || props.color_present) { 49762306a36Sopenharmony_ci char tmp_buf[LED_MAX_NAME_SIZE]; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci if (props.func_enum_present) { 50062306a36Sopenharmony_ci snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s-%d", 50162306a36Sopenharmony_ci props.color_present ? led_colors[props.color] : "", 50262306a36Sopenharmony_ci props.function ?: "", props.func_enum); 50362306a36Sopenharmony_ci } else { 50462306a36Sopenharmony_ci snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s", 50562306a36Sopenharmony_ci props.color_present ? led_colors[props.color] : "", 50662306a36Sopenharmony_ci props.function ?: ""); 50762306a36Sopenharmony_ci } 50862306a36Sopenharmony_ci if (init_data->devname_mandatory) { 50962306a36Sopenharmony_ci snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s", 51062306a36Sopenharmony_ci devicename, tmp_buf); 51162306a36Sopenharmony_ci } else { 51262306a36Sopenharmony_ci strscpy(led_classdev_name, tmp_buf, LED_MAX_NAME_SIZE); 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci } 51562306a36Sopenharmony_ci } else if (init_data->default_label) { 51662306a36Sopenharmony_ci if (!devicename) { 51762306a36Sopenharmony_ci dev_err(dev, "Legacy LED naming requires devicename segment"); 51862306a36Sopenharmony_ci return -EINVAL; 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s", 52162306a36Sopenharmony_ci devicename, init_data->default_label); 52262306a36Sopenharmony_ci } else if (is_of_node(fwnode)) { 52362306a36Sopenharmony_ci strscpy(led_classdev_name, to_of_node(fwnode)->name, 52462306a36Sopenharmony_ci LED_MAX_NAME_SIZE); 52562306a36Sopenharmony_ci } else 52662306a36Sopenharmony_ci return -EINVAL; 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci return 0; 52962306a36Sopenharmony_ci} 53062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_compose_name); 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_cienum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode) 53362306a36Sopenharmony_ci{ 53462306a36Sopenharmony_ci const char *state = NULL; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci if (!fwnode_property_read_string(fwnode, "default-state", &state)) { 53762306a36Sopenharmony_ci if (!strcmp(state, "keep")) 53862306a36Sopenharmony_ci return LEDS_DEFSTATE_KEEP; 53962306a36Sopenharmony_ci if (!strcmp(state, "on")) 54062306a36Sopenharmony_ci return LEDS_DEFSTATE_ON; 54162306a36Sopenharmony_ci } 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci return LEDS_DEFSTATE_OFF; 54462306a36Sopenharmony_ci} 54562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(led_init_default_state_get); 546