mirror of
https://github.com/torvalds/linux.git
synced 2024-11-29 23:51:37 +00:00
065d099f1b
Given channel intensity, LED brightness and max LED brightness, the
multicolor LED framework helper led_mc_calc_color_components() computes
the color channel brightness as
chan_brightness = brightness * chan_intensity / max_brightness
Consider the situation when (brightness, intensity, max_brightness) is
for example (16, 15, 255), then chan_brightness is computed to 0
although the fractional divison would give 0.94, which should be rounded
to 1.
Use DIV_ROUND_CLOSEST here for the division to give more realistic
component computation:
chan_brightness = DIV_ROUND_CLOSEST(brightness * chan_intensity,
max_brightness)
Fixes: 55d5d3b46b
("leds: multicolor: Introduce a multicolor class definition")
Signed-off-by: Marek Behún <kabel@kernel.org>
Link: https://lore.kernel.org/r/20230801124931.8661-1-kabel@kernel.org
Signed-off-by: Lee Jones <lee@kernel.org>
206 lines
5.0 KiB
C
206 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/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");
|