204 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			204 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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/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 = 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");
 |