Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-only 2 : : /* 3 : : * LED Class Core 4 : : * 5 : : * Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu> 6 : : * Copyright (C) 2005-2007 Richard Purdie <rpurdie@openedhand.com> 7 : : */ 8 : : 9 : : #include <linux/ctype.h> 10 : : #include <linux/device.h> 11 : : #include <linux/err.h> 12 : : #include <linux/init.h> 13 : : #include <linux/kernel.h> 14 : : #include <linux/leds.h> 15 : : #include <linux/list.h> 16 : : #include <linux/module.h> 17 : : #include <linux/property.h> 18 : : #include <linux/slab.h> 19 : : #include <linux/spinlock.h> 20 : : #include <linux/timer.h> 21 : : #include <uapi/linux/uleds.h> 22 : : #include "leds.h" 23 : : 24 : : static struct class *leds_class; 25 : : 26 : 0 : static ssize_t brightness_show(struct device *dev, 27 : : struct device_attribute *attr, char *buf) 28 : : { 29 : : struct led_classdev *led_cdev = dev_get_drvdata(dev); 30 : : 31 : : /* no lock needed for this */ 32 : 0 : led_update_brightness(led_cdev); 33 : : 34 : 0 : return sprintf(buf, "%u\n", led_cdev->brightness); 35 : : } 36 : : 37 : 0 : static ssize_t brightness_store(struct device *dev, 38 : : struct device_attribute *attr, const char *buf, size_t size) 39 : : { 40 : : struct led_classdev *led_cdev = dev_get_drvdata(dev); 41 : : unsigned long state; 42 : : ssize_t ret; 43 : : 44 : 0 : mutex_lock(&led_cdev->led_access); 45 : : 46 : 0 : if (led_sysfs_is_disabled(led_cdev)) { 47 : : ret = -EBUSY; 48 : : goto unlock; 49 : : } 50 : : 51 : : ret = kstrtoul(buf, 10, &state); 52 : 0 : if (ret) 53 : : goto unlock; 54 : : 55 : 0 : if (state == LED_OFF) 56 : 0 : led_trigger_remove(led_cdev); 57 : 0 : led_set_brightness(led_cdev, state); 58 : 0 : flush_work(&led_cdev->set_brightness_work); 59 : : 60 : 0 : ret = size; 61 : : unlock: 62 : 0 : mutex_unlock(&led_cdev->led_access); 63 : 0 : return ret; 64 : : } 65 : : static DEVICE_ATTR_RW(brightness); 66 : : 67 : 0 : static ssize_t max_brightness_show(struct device *dev, 68 : : struct device_attribute *attr, char *buf) 69 : : { 70 : : struct led_classdev *led_cdev = dev_get_drvdata(dev); 71 : : 72 : 0 : return sprintf(buf, "%u\n", led_cdev->max_brightness); 73 : : } 74 : : static DEVICE_ATTR_RO(max_brightness); 75 : : 76 : : #ifdef CONFIG_LEDS_TRIGGERS 77 : : static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store); 78 : : static struct attribute *led_trigger_attrs[] = { 79 : : &dev_attr_trigger.attr, 80 : : NULL, 81 : : }; 82 : : static const struct attribute_group led_trigger_group = { 83 : : .attrs = led_trigger_attrs, 84 : : }; 85 : : #endif 86 : : 87 : : static struct attribute *led_class_attrs[] = { 88 : : &dev_attr_brightness.attr, 89 : : &dev_attr_max_brightness.attr, 90 : : NULL, 91 : : }; 92 : : 93 : : static const struct attribute_group led_group = { 94 : : .attrs = led_class_attrs, 95 : : }; 96 : : 97 : : static const struct attribute_group *led_groups[] = { 98 : : &led_group, 99 : : #ifdef CONFIG_LEDS_TRIGGERS 100 : : &led_trigger_group, 101 : : #endif 102 : : NULL, 103 : : }; 104 : : 105 : : #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED 106 : : static ssize_t brightness_hw_changed_show(struct device *dev, 107 : : struct device_attribute *attr, char *buf) 108 : : { 109 : : struct led_classdev *led_cdev = dev_get_drvdata(dev); 110 : : 111 : : if (led_cdev->brightness_hw_changed == -1) 112 : : return -ENODATA; 113 : : 114 : : return sprintf(buf, "%u\n", led_cdev->brightness_hw_changed); 115 : : } 116 : : 117 : : static DEVICE_ATTR_RO(brightness_hw_changed); 118 : : 119 : : static int led_add_brightness_hw_changed(struct led_classdev *led_cdev) 120 : : { 121 : : struct device *dev = led_cdev->dev; 122 : : int ret; 123 : : 124 : : ret = device_create_file(dev, &dev_attr_brightness_hw_changed); 125 : : if (ret) { 126 : : dev_err(dev, "Error creating brightness_hw_changed\n"); 127 : : return ret; 128 : : } 129 : : 130 : : led_cdev->brightness_hw_changed_kn = 131 : : sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed"); 132 : : if (!led_cdev->brightness_hw_changed_kn) { 133 : : dev_err(dev, "Error getting brightness_hw_changed kn\n"); 134 : : device_remove_file(dev, &dev_attr_brightness_hw_changed); 135 : : return -ENXIO; 136 : : } 137 : : 138 : : return 0; 139 : : } 140 : : 141 : : static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev) 142 : : { 143 : : sysfs_put(led_cdev->brightness_hw_changed_kn); 144 : : device_remove_file(led_cdev->dev, &dev_attr_brightness_hw_changed); 145 : : } 146 : : 147 : : void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev, 148 : : enum led_brightness brightness) 149 : : { 150 : : if (WARN_ON(!led_cdev->brightness_hw_changed_kn)) 151 : : return; 152 : : 153 : : led_cdev->brightness_hw_changed = brightness; 154 : : sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn); 155 : : } 156 : : EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed); 157 : : #else 158 : : static int led_add_brightness_hw_changed(struct led_classdev *led_cdev) 159 : : { 160 : : return 0; 161 : : } 162 : : static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev) 163 : : { 164 : : } 165 : : #endif 166 : : 167 : : /** 168 : : * led_classdev_suspend - suspend an led_classdev. 169 : : * @led_cdev: the led_classdev to suspend. 170 : : */ 171 : 0 : void led_classdev_suspend(struct led_classdev *led_cdev) 172 : : { 173 : 0 : led_cdev->flags |= LED_SUSPENDED; 174 : 0 : led_set_brightness_nopm(led_cdev, 0); 175 : 0 : flush_work(&led_cdev->set_brightness_work); 176 : 0 : } 177 : : EXPORT_SYMBOL_GPL(led_classdev_suspend); 178 : : 179 : : /** 180 : : * led_classdev_resume - resume an led_classdev. 181 : : * @led_cdev: the led_classdev to resume. 182 : : */ 183 : 0 : void led_classdev_resume(struct led_classdev *led_cdev) 184 : : { 185 : 0 : led_set_brightness_nopm(led_cdev, led_cdev->brightness); 186 : : 187 : 0 : if (led_cdev->flash_resume) 188 : 0 : led_cdev->flash_resume(led_cdev); 189 : : 190 : 0 : led_cdev->flags &= ~LED_SUSPENDED; 191 : 0 : } 192 : : EXPORT_SYMBOL_GPL(led_classdev_resume); 193 : : 194 : : #ifdef CONFIG_PM_SLEEP 195 : : static int led_suspend(struct device *dev) 196 : : { 197 : : struct led_classdev *led_cdev = dev_get_drvdata(dev); 198 : : 199 : : if (led_cdev->flags & LED_CORE_SUSPENDRESUME) 200 : : led_classdev_suspend(led_cdev); 201 : : 202 : : return 0; 203 : : } 204 : : 205 : : static int led_resume(struct device *dev) 206 : : { 207 : : struct led_classdev *led_cdev = dev_get_drvdata(dev); 208 : : 209 : : if (led_cdev->flags & LED_CORE_SUSPENDRESUME) 210 : : led_classdev_resume(led_cdev); 211 : : 212 : : return 0; 213 : : } 214 : : #endif 215 : : 216 : : static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume); 217 : : 218 : 3 : static int led_classdev_next_name(const char *init_name, char *name, 219 : : size_t len) 220 : : { 221 : : unsigned int i = 0; 222 : : int ret = 0; 223 : : struct device *dev; 224 : : 225 : 3 : strlcpy(name, init_name, len); 226 : : 227 : 3 : while ((ret < len) && 228 : 3 : (dev = class_find_device_by_name(leds_class, name))) { 229 : 0 : put_device(dev); 230 : 0 : ret = snprintf(name, len, "%s_%u", init_name, ++i); 231 : : } 232 : : 233 : 3 : if (ret >= len) 234 : : return -ENOMEM; 235 : : 236 : 3 : return i; 237 : : } 238 : : 239 : : /** 240 : : * led_classdev_register_ext - register a new object of led_classdev class 241 : : * with init data. 242 : : * 243 : : * @parent: parent of LED device 244 : : * @led_cdev: the led_classdev structure for this device. 245 : : * @init_data: LED class device initialization data 246 : : */ 247 : 3 : int led_classdev_register_ext(struct device *parent, 248 : : struct led_classdev *led_cdev, 249 : : struct led_init_data *init_data) 250 : : { 251 : : char composed_name[LED_MAX_NAME_SIZE]; 252 : : char final_name[LED_MAX_NAME_SIZE]; 253 : : const char *proposed_name = composed_name; 254 : : int ret; 255 : : 256 : 3 : if (init_data) { 257 : 3 : if (init_data->devname_mandatory && !init_data->devicename) { 258 : 0 : dev_err(parent, "Mandatory device name is missing"); 259 : 0 : return -EINVAL; 260 : : } 261 : 3 : ret = led_compose_name(parent, init_data, composed_name); 262 : 3 : if (ret < 0) 263 : : return ret; 264 : : } else { 265 : 3 : proposed_name = led_cdev->name; 266 : : } 267 : : 268 : 3 : ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name)); 269 : 3 : if (ret < 0) 270 : : return ret; 271 : : 272 : 3 : mutex_init(&led_cdev->led_access); 273 : 3 : mutex_lock(&led_cdev->led_access); 274 : 3 : led_cdev->dev = device_create_with_groups(leds_class, parent, 0, 275 : : led_cdev, led_cdev->groups, "%s", final_name); 276 : 3 : if (IS_ERR(led_cdev->dev)) { 277 : 0 : mutex_unlock(&led_cdev->led_access); 278 : 0 : return PTR_ERR(led_cdev->dev); 279 : : } 280 : 3 : if (init_data && init_data->fwnode) 281 : 3 : led_cdev->dev->fwnode = init_data->fwnode; 282 : : 283 : 3 : if (ret) 284 : 0 : dev_warn(parent, "Led %s renamed to %s due to name collision", 285 : : proposed_name, dev_name(led_cdev->dev)); 286 : : 287 : : if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) { 288 : : ret = led_add_brightness_hw_changed(led_cdev); 289 : : if (ret) { 290 : : device_unregister(led_cdev->dev); 291 : : led_cdev->dev = NULL; 292 : : mutex_unlock(&led_cdev->led_access); 293 : : return ret; 294 : : } 295 : : } 296 : : 297 : 3 : led_cdev->work_flags = 0; 298 : : #ifdef CONFIG_LEDS_TRIGGERS 299 : 3 : init_rwsem(&led_cdev->trigger_lock); 300 : : #endif 301 : : #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED 302 : : led_cdev->brightness_hw_changed = -1; 303 : : #endif 304 : : /* add to the list of leds */ 305 : 3 : down_write(&leds_list_lock); 306 : 3 : list_add_tail(&led_cdev->node, &leds_list); 307 : 3 : up_write(&leds_list_lock); 308 : : 309 : 3 : if (!led_cdev->max_brightness) 310 : 3 : led_cdev->max_brightness = LED_FULL; 311 : : 312 : 3 : led_update_brightness(led_cdev); 313 : : 314 : 3 : led_init_core(led_cdev); 315 : : 316 : : #ifdef CONFIG_LEDS_TRIGGERS 317 : 3 : led_trigger_set_default(led_cdev); 318 : : #endif 319 : : 320 : 3 : mutex_unlock(&led_cdev->led_access); 321 : : 322 : : dev_dbg(parent, "Registered led device: %s\n", 323 : : led_cdev->name); 324 : : 325 : 3 : return 0; 326 : : } 327 : : EXPORT_SYMBOL_GPL(led_classdev_register_ext); 328 : : 329 : : /** 330 : : * led_classdev_unregister - unregisters a object of led_properties class. 331 : : * @led_cdev: the led device to unregister 332 : : * 333 : : * Unregisters a previously registered via led_classdev_register object. 334 : : */ 335 : 0 : void led_classdev_unregister(struct led_classdev *led_cdev) 336 : : { 337 : 0 : if (IS_ERR_OR_NULL(led_cdev->dev)) 338 : 0 : return; 339 : : 340 : : #ifdef CONFIG_LEDS_TRIGGERS 341 : 0 : down_write(&led_cdev->trigger_lock); 342 : 0 : if (led_cdev->trigger) 343 : 0 : led_trigger_set(led_cdev, NULL); 344 : 0 : up_write(&led_cdev->trigger_lock); 345 : : #endif 346 : : 347 : 0 : led_cdev->flags |= LED_UNREGISTERING; 348 : : 349 : : /* Stop blinking */ 350 : 0 : led_stop_software_blink(led_cdev); 351 : : 352 : 0 : led_set_brightness(led_cdev, LED_OFF); 353 : : 354 : 0 : flush_work(&led_cdev->set_brightness_work); 355 : : 356 : : if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) 357 : : led_remove_brightness_hw_changed(led_cdev); 358 : : 359 : 0 : device_unregister(led_cdev->dev); 360 : : 361 : 0 : down_write(&leds_list_lock); 362 : : list_del(&led_cdev->node); 363 : 0 : up_write(&leds_list_lock); 364 : : 365 : : mutex_destroy(&led_cdev->led_access); 366 : : } 367 : : EXPORT_SYMBOL_GPL(led_classdev_unregister); 368 : : 369 : 0 : static void devm_led_classdev_release(struct device *dev, void *res) 370 : : { 371 : 0 : led_classdev_unregister(*(struct led_classdev **)res); 372 : 0 : } 373 : : 374 : : /** 375 : : * devm_led_classdev_register_ext - resource managed led_classdev_register_ext() 376 : : * 377 : : * @parent: parent of LED device 378 : : * @led_cdev: the led_classdev structure for this device. 379 : : * @init_data: LED class device initialization data 380 : : */ 381 : 3 : int devm_led_classdev_register_ext(struct device *parent, 382 : : struct led_classdev *led_cdev, 383 : : struct led_init_data *init_data) 384 : : { 385 : : struct led_classdev **dr; 386 : : int rc; 387 : : 388 : : dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL); 389 : 3 : if (!dr) 390 : : return -ENOMEM; 391 : : 392 : 3 : rc = led_classdev_register_ext(parent, led_cdev, init_data); 393 : 3 : if (rc) { 394 : 0 : devres_free(dr); 395 : 0 : return rc; 396 : : } 397 : : 398 : 3 : *dr = led_cdev; 399 : 3 : devres_add(parent, dr); 400 : : 401 : 3 : return 0; 402 : : } 403 : : EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext); 404 : : 405 : 0 : static int devm_led_classdev_match(struct device *dev, void *res, void *data) 406 : : { 407 : : struct led_cdev **p = res; 408 : : 409 : 0 : if (WARN_ON(!p || !*p)) 410 : : return 0; 411 : : 412 : 0 : return *p == data; 413 : : } 414 : : 415 : : /** 416 : : * devm_led_classdev_unregister() - resource managed led_classdev_unregister() 417 : : * @parent: The device to unregister. 418 : : * @led_cdev: the led_classdev structure for this device. 419 : : */ 420 : 0 : void devm_led_classdev_unregister(struct device *dev, 421 : : struct led_classdev *led_cdev) 422 : : { 423 : 0 : WARN_ON(devres_release(dev, 424 : : devm_led_classdev_release, 425 : : devm_led_classdev_match, led_cdev)); 426 : 0 : } 427 : : EXPORT_SYMBOL_GPL(devm_led_classdev_unregister); 428 : : 429 : 3 : static int __init leds_init(void) 430 : : { 431 : 3 : leds_class = class_create(THIS_MODULE, "leds"); 432 : 3 : if (IS_ERR(leds_class)) 433 : 0 : return PTR_ERR(leds_class); 434 : 3 : leds_class->pm = &leds_class_dev_pm_ops; 435 : 3 : leds_class->dev_groups = led_groups; 436 : 3 : return 0; 437 : : } 438 : : 439 : 0 : static void __exit leds_exit(void) 440 : : { 441 : 0 : class_destroy(leds_class); 442 : 0 : } 443 : : 444 : : subsys_initcall(leds_init); 445 : : module_exit(leds_exit); 446 : : 447 : : MODULE_AUTHOR("John Lenz, Richard Purdie"); 448 : : MODULE_LICENSE("GPL"); 449 : : MODULE_DESCRIPTION("LED Class Interface");