| // SPDX-License-Identifier: GPL-2.0 |
| // LED Multicolor class interface |
| // Copyright (C) 2019-20 Texas Instruments Incorporated - http://www.ti.com/ |
| // Author: Dan Murphy <dmurphy@ti.com> |
| |
| #include <linux/device.h> |
| #include <linux/init.h> |
| #include <linux/led-class-multicolor.h> |
| #include <linux/math.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| |
| #include "leds.h" |
| |
| int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev, |
| enum led_brightness brightness) |
| { |
| struct led_classdev *led_cdev = &mcled_cdev->led_cdev; |
| int i; |
| |
| for (i = 0; i < mcled_cdev->num_colors; i++) |
| mcled_cdev->subled_info[i].brightness = |
| DIV_ROUND_CLOSEST(brightness * |
| mcled_cdev->subled_info[i].intensity, |
| led_cdev->max_brightness); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(led_mc_calc_color_components); |
| |
| static ssize_t multi_intensity_store(struct device *dev, |
| struct device_attribute *intensity_attr, |
| const char *buf, size_t size) |
| { |
| struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); |
| int nrchars, offset = 0; |
| int intensity_value[LED_COLOR_ID_MAX]; |
| int i; |
| ssize_t ret; |
| |
| mutex_lock(&led_cdev->led_access); |
| |
| for (i = 0; i < mcled_cdev->num_colors; i++) { |
| ret = sscanf(buf + offset, "%i%n", |
| &intensity_value[i], &nrchars); |
| if (ret != 1) { |
| ret = -EINVAL; |
| goto err_out; |
| } |
| offset += nrchars; |
| } |
| |
| offset++; |
| if (offset < size) { |
| ret = -EINVAL; |
| goto err_out; |
| } |
| |
| for (i = 0; i < mcled_cdev->num_colors; i++) |
| mcled_cdev->subled_info[i].intensity = intensity_value[i]; |
| |
| led_set_brightness(led_cdev, led_cdev->brightness); |
| ret = size; |
| err_out: |
| mutex_unlock(&led_cdev->led_access); |
| return ret; |
| } |
| |
| static ssize_t multi_intensity_show(struct device *dev, |
| struct device_attribute *intensity_attr, |
| char *buf) |
| { |
| struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); |
| int len = 0; |
| int i; |
| |
| for (i = 0; i < mcled_cdev->num_colors; i++) { |
| len += sprintf(buf + len, "%d", |
| mcled_cdev->subled_info[i].intensity); |
| if (i < mcled_cdev->num_colors - 1) |
| len += sprintf(buf + len, " "); |
| } |
| |
| buf[len++] = '\n'; |
| return len; |
| } |
| static DEVICE_ATTR_RW(multi_intensity); |
| |
| static ssize_t multi_index_show(struct device *dev, |
| struct device_attribute *multi_index_attr, |
| char *buf) |
| { |
| struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); |
| int len = 0; |
| int index; |
| int i; |
| |
| for (i = 0; i < mcled_cdev->num_colors; i++) { |
| index = mcled_cdev->subled_info[i].color_index; |
| len += sprintf(buf + len, "%s", led_get_color_name(index)); |
| if (i < mcled_cdev->num_colors - 1) |
| len += sprintf(buf + len, " "); |
| } |
| |
| buf[len++] = '\n'; |
| return len; |
| } |
| static DEVICE_ATTR_RO(multi_index); |
| |
| static struct attribute *led_multicolor_attrs[] = { |
| &dev_attr_multi_intensity.attr, |
| &dev_attr_multi_index.attr, |
| NULL, |
| }; |
| ATTRIBUTE_GROUPS(led_multicolor); |
| |
| int led_classdev_multicolor_register_ext(struct device *parent, |
| struct led_classdev_mc *mcled_cdev, |
| struct led_init_data *init_data) |
| { |
| struct led_classdev *led_cdev; |
| |
| if (!mcled_cdev) |
| return -EINVAL; |
| |
| if (mcled_cdev->num_colors <= 0) |
| return -EINVAL; |
| |
| if (mcled_cdev->num_colors > LED_COLOR_ID_MAX) |
| return -EINVAL; |
| |
| led_cdev = &mcled_cdev->led_cdev; |
| led_cdev->flags |= LED_MULTI_COLOR; |
| mcled_cdev->led_cdev.groups = led_multicolor_groups; |
| |
| return led_classdev_register_ext(parent, led_cdev, init_data); |
| } |
| EXPORT_SYMBOL_GPL(led_classdev_multicolor_register_ext); |
| |
| void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev) |
| { |
| if (!mcled_cdev) |
| return; |
| |
| led_classdev_unregister(&mcled_cdev->led_cdev); |
| } |
| EXPORT_SYMBOL_GPL(led_classdev_multicolor_unregister); |
| |
| static void devm_led_classdev_multicolor_release(struct device *dev, void *res) |
| { |
| led_classdev_multicolor_unregister(*(struct led_classdev_mc **)res); |
| } |
| |
| int devm_led_classdev_multicolor_register_ext(struct device *parent, |
| struct led_classdev_mc *mcled_cdev, |
| struct led_init_data *init_data) |
| { |
| struct led_classdev_mc **dr; |
| int ret; |
| |
| dr = devres_alloc(devm_led_classdev_multicolor_release, |
| sizeof(*dr), GFP_KERNEL); |
| if (!dr) |
| return -ENOMEM; |
| |
| ret = led_classdev_multicolor_register_ext(parent, mcled_cdev, |
| init_data); |
| if (ret) { |
| devres_free(dr); |
| return ret; |
| } |
| |
| *dr = mcled_cdev; |
| devres_add(parent, dr); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_register_ext); |
| |
| static int devm_led_classdev_multicolor_match(struct device *dev, |
| void *res, void *data) |
| { |
| struct led_classdev_mc **p = res; |
| |
| if (WARN_ON(!p || !*p)) |
| return 0; |
| |
| return *p == data; |
| } |
| |
| void devm_led_classdev_multicolor_unregister(struct device *dev, |
| struct led_classdev_mc *mcled_cdev) |
| { |
| WARN_ON(devres_release(dev, |
| devm_led_classdev_multicolor_release, |
| devm_led_classdev_multicolor_match, mcled_cdev)); |
| } |
| EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_unregister); |
| |
| MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>"); |
| MODULE_DESCRIPTION("Multicolor LED class interface"); |
| MODULE_LICENSE("GPL v2"); |