|  | // 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_colors[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; | 
|  | 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"); |