leds: Add multicolor support to BlinkM LED driver

Add multicolor support to the BlinkM driver, making it easier to control
from userspace. The BlinkM LED is a programmable RGB LED. The driver
currently supports only the regular LED sysfs class, resulting in the
creation of three distinct classes, one for red, green, and blue. The
user then has to input three values into the three seperate brightness
files within those classes. The multicolor LED framework makes the
device easier to control with the multi_intensity file: the user can
input three values at once to form a color, while still controlling the
lightness with the brightness file.

The main struct blinkm_led has changed slightly. The struct led_classdev
for the regular sysfs classes remain. The blinkm_probe function checks
CONFIG_LEDS_BLINKM_MULTICOLOR to decide whether to load the seperate
sysfs classes or the single multicolor one, but never both. The
blinkm_set_mc_brightness() function had to be added to calculate the
three color components and then set the fields of the blinkm_data
structure accordingly.

Signed-off-by: Joseph Strauss <jstrauss@mailbox.org>
Link: https://lore.kernel.org/r/20240710184844.108006-1-jstrauss@mailbox.org
Signed-off-by: Lee Jones <lee@kernel.org>
This commit is contained in:
Joseph Strauss 2024-07-10 13:48:44 -05:00 committed by Lee Jones
parent 6b08d07cac
commit 56e8c56c9a
4 changed files with 218 additions and 91 deletions

View File

@ -13,9 +13,31 @@ The device accepts RGB and HSB color values through separate commands.
Also you can store blinking sequences as "scripts" in
the controller and run them. Also fading is an option.
The interface this driver provides is 2-fold:
The interface this driver provides is 3-fold:
a) LED class interface for use with triggers
a) LED multicolor class interface for use with triggers
#######################################################
The registration follows the scheme::
blinkm-<i2c-bus-nr>-<i2c-device-nr>:rgb:indicator
$ ls -h /sys/class/leds/blinkm-1-9:rgb:indicator
brightness device max_brightness multi_index multi_intensity power subsystem trigger uevent
Hue is controlled by the multi_intensity file and lightness is controlled by
the brightness file.
The order in which to write the intensity values can be found in multi_index.
Exactly three values between 0 and 255 must be written to multi_intensity to
change the color::
$ echo 255 100 50 > multi_intensity
The overall lightness be changed by writing a value between 0 and 255 to the
brightness file.
b) LED class interface for use with triggers
############################################
The registration follows the scheme::
@ -79,6 +101,7 @@ E.g.::
as of 6/2012
as of 07/2024
dl9pf <at> gmx <dot> de
jstrauss <at> mailbox <dot> org

View File

@ -72,6 +72,14 @@ Good: "platform:*:charging" (allwinner sun50i, leds-cht-wcove)
Good: ":backlight" (Motorola Droid 4)
* Indicators
Good: ":indicator" (Blinkm)
* RGB
Good: ":rgb" (Blinkm)
* Ethernet LEDs
Currently two types of Network LEDs are support, those controlled by

View File

@ -825,6 +825,14 @@ config LEDS_BLINKM
This option enables support for the BlinkM RGB LED connected
through I2C. Say Y to enable support for the BlinkM LED.
config LEDS_BLINKM_MULTICOLOR
bool "Enable multicolor support for BlinkM I2C RGB LED"
depends on LEDS_BLINKM
depends on LEDS_CLASS_MULTICOLOR
help
This option enables multicolor sysfs class support for BlinkM LED and
disables the older, separated sysfs interface
config LEDS_POWERNV
tristate "LED support for PowerNV Platform"
depends on LEDS_CLASS

View File

@ -2,6 +2,7 @@
/*
* leds-blinkm.c
* (c) Jan-Simon Möller (dl9pf@gmx.de)
* (c) Joseph Strauss (jstrauss@mailbox.org)
*/
#include <linux/module.h>
@ -15,6 +16,10 @@
#include <linux/pm_runtime.h>
#include <linux/leds.h>
#include <linux/delay.h>
#include <linux/led-class-multicolor.h>
#include <linux/kconfig.h>
#define NUM_LEDS 3
/* Addresses to scan - BlinkM is on 0x09 by default*/
static const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END };
@ -22,19 +27,25 @@ static const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END };
static int blinkm_transfer_hw(struct i2c_client *client, int cmd);
static int blinkm_test_run(struct i2c_client *client);
/* Contains structs for both the color-separated sysfs classes, and the new multicolor class */
struct blinkm_led {
struct i2c_client *i2c_client;
struct led_classdev led_cdev;
union {
/* used when multicolor support is disabled */
struct led_classdev led_cdev;
struct led_classdev_mc mcled_cdev;
} cdev;
int id;
};
#define cdev_to_blmled(c) container_of(c, struct blinkm_led, led_cdev)
#define led_cdev_to_blmled(c) container_of(c, struct blinkm_led, cdev.led_cdev)
#define mcled_cdev_to_led(c) container_of(c, struct blinkm_led, cdev.mcled_cdev)
struct blinkm_data {
struct i2c_client *i2c_client;
struct mutex update_lock;
/* used for led class interface */
struct blinkm_led blinkm_leds[3];
struct blinkm_led blinkm_leds[NUM_LEDS];
/* used for "blinkm" sysfs interface */
u8 red; /* color red */
u8 green; /* color green */
@ -419,11 +430,29 @@ static int blinkm_transfer_hw(struct i2c_client *client, int cmd)
return 0;
}
static int blinkm_set_mc_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
struct blinkm_led *led = mcled_cdev_to_led(mcled_cdev);
struct blinkm_data *data = i2c_get_clientdata(led->i2c_client);
led_mc_calc_color_components(mcled_cdev, value);
data->next_red = (u8) mcled_cdev->subled_info[RED].brightness;
data->next_green = (u8) mcled_cdev->subled_info[GREEN].brightness;
data->next_blue = (u8) mcled_cdev->subled_info[BLUE].brightness;
blinkm_transfer_hw(led->i2c_client, BLM_GO_RGB);
return 0;
}
static int blinkm_led_common_set(struct led_classdev *led_cdev,
enum led_brightness value, int color)
{
/* led_brightness is 0, 127 or 255 - we just use it here as-is */
struct blinkm_led *led = cdev_to_blmled(led_cdev);
struct blinkm_led *led = led_cdev_to_blmled(led_cdev);
struct blinkm_data *data = i2c_get_clientdata(led->i2c_client);
switch (color) {
@ -565,25 +594,147 @@ static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info)
return 0;
}
static int register_separate_colors(struct i2c_client *client, struct blinkm_data *data)
{
/* 3 separate classes for red, green, and blue respectively */
struct blinkm_led *leds[NUM_LEDS];
int err;
char blinkm_led_name[28];
/* Register red, green, and blue sysfs classes */
for (int i = 0; i < NUM_LEDS; i++) {
/* RED = 0, GREEN = 1, BLUE = 2 */
leds[i] = &data->blinkm_leds[i];
leds[i]->i2c_client = client;
leds[i]->id = i;
leds[i]->cdev.led_cdev.max_brightness = 255;
leds[i]->cdev.led_cdev.flags = LED_CORE_SUSPENDRESUME;
switch (i) {
case RED:
scnprintf(blinkm_led_name, sizeof(blinkm_led_name),
"blinkm-%d-%d-red",
client->adapter->nr,
client->addr);
leds[i]->cdev.led_cdev.name = blinkm_led_name;
leds[i]->cdev.led_cdev.brightness_set_blocking =
blinkm_led_red_set;
err = led_classdev_register(&client->dev,
&leds[i]->cdev.led_cdev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
leds[i]->cdev.led_cdev.name);
goto failred;
}
break;
case GREEN:
scnprintf(blinkm_led_name, sizeof(blinkm_led_name),
"blinkm-%d-%d-green",
client->adapter->nr,
client->addr);
leds[i]->cdev.led_cdev.name = blinkm_led_name;
leds[i]->cdev.led_cdev.brightness_set_blocking =
blinkm_led_green_set;
err = led_classdev_register(&client->dev,
&leds[i]->cdev.led_cdev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
leds[i]->cdev.led_cdev.name);
goto failgreen;
}
break;
case BLUE:
scnprintf(blinkm_led_name, sizeof(blinkm_led_name),
"blinkm-%d-%d-blue",
client->adapter->nr,
client->addr);
leds[i]->cdev.led_cdev.name = blinkm_led_name;
leds[i]->cdev.led_cdev.brightness_set_blocking =
blinkm_led_blue_set;
err = led_classdev_register(&client->dev,
&leds[i]->cdev.led_cdev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
leds[i]->cdev.led_cdev.name);
goto failblue;
}
break;
default:
break;
} /* end switch */
} /* end for */
return 0;
failblue:
led_classdev_unregister(&leds[GREEN]->cdev.led_cdev);
failgreen:
led_classdev_unregister(&leds[RED]->cdev.led_cdev);
failred:
sysfs_remove_group(&client->dev.kobj, &blinkm_group);
return err;
}
static int register_multicolor(struct i2c_client *client, struct blinkm_data *data)
{
struct blinkm_led *mc_led;
struct mc_subled *mc_led_info;
char blinkm_led_name[28];
int err;
/* Register multicolor sysfs class */
/* The first element of leds is used for multicolor facilities */
mc_led = &data->blinkm_leds[RED];
mc_led->i2c_client = client;
mc_led_info = devm_kcalloc(&client->dev, NUM_LEDS, sizeof(*mc_led_info),
GFP_KERNEL);
if (!mc_led_info)
return -ENOMEM;
mc_led_info[RED].color_index = LED_COLOR_ID_RED;
mc_led_info[GREEN].color_index = LED_COLOR_ID_GREEN;
mc_led_info[BLUE].color_index = LED_COLOR_ID_BLUE;
mc_led->cdev.mcled_cdev.subled_info = mc_led_info;
mc_led->cdev.mcled_cdev.num_colors = NUM_LEDS;
mc_led->cdev.mcled_cdev.led_cdev.brightness = 255;
mc_led->cdev.mcled_cdev.led_cdev.max_brightness = 255;
mc_led->cdev.mcled_cdev.led_cdev.flags = LED_CORE_SUSPENDRESUME;
scnprintf(blinkm_led_name, sizeof(blinkm_led_name),
"blinkm-%d-%d:rgb:indicator",
client->adapter->nr,
client->addr);
mc_led->cdev.mcled_cdev.led_cdev.name = blinkm_led_name;
mc_led->cdev.mcled_cdev.led_cdev.brightness_set_blocking = blinkm_set_mc_brightness;
err = led_classdev_multicolor_register(&client->dev, &mc_led->cdev.mcled_cdev);
if (err < 0) {
dev_err(&client->dev, "couldn't register LED %s\n",
mc_led->cdev.led_cdev.name);
sysfs_remove_group(&client->dev.kobj, &blinkm_group);
}
return 0;
}
static int blinkm_probe(struct i2c_client *client)
{
struct blinkm_data *data;
struct blinkm_led *led[3];
int err, i;
char blinkm_led_name[28];
int err;
data = devm_kzalloc(&client->dev,
sizeof(struct blinkm_data), GFP_KERNEL);
if (!data) {
err = -ENOMEM;
goto exit;
}
if (!data)
return -ENOMEM;
data->i2c_addr = 0x08;
/* i2c addr - use fake addr of 0x08 initially (real is 0x09) */
data->fw_ver = 0xfe;
/* firmware version - use fake until we read real value
* (currently broken - BlinkM confused!) */
* (currently broken - BlinkM confused!)
*/
data->script_id = 0x01;
data->i2c_client = client;
@ -594,86 +745,22 @@ static int blinkm_probe(struct i2c_client *client)
err = sysfs_create_group(&client->dev.kobj, &blinkm_group);
if (err < 0) {
dev_err(&client->dev, "couldn't register sysfs group\n");
goto exit;
return err;
}
for (i = 0; i < 3; i++) {
/* RED = 0, GREEN = 1, BLUE = 2 */
led[i] = &data->blinkm_leds[i];
led[i]->i2c_client = client;
led[i]->id = i;
led[i]->led_cdev.max_brightness = 255;
led[i]->led_cdev.flags = LED_CORE_SUSPENDRESUME;
switch (i) {
case RED:
snprintf(blinkm_led_name, sizeof(blinkm_led_name),
"blinkm-%d-%d-red",
client->adapter->nr,
client->addr);
led[i]->led_cdev.name = blinkm_led_name;
led[i]->led_cdev.brightness_set_blocking =
blinkm_led_red_set;
err = led_classdev_register(&client->dev,
&led[i]->led_cdev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
led[i]->led_cdev.name);
goto failred;
}
break;
case GREEN:
snprintf(blinkm_led_name, sizeof(blinkm_led_name),
"blinkm-%d-%d-green",
client->adapter->nr,
client->addr);
led[i]->led_cdev.name = blinkm_led_name;
led[i]->led_cdev.brightness_set_blocking =
blinkm_led_green_set;
err = led_classdev_register(&client->dev,
&led[i]->led_cdev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
led[i]->led_cdev.name);
goto failgreen;
}
break;
case BLUE:
snprintf(blinkm_led_name, sizeof(blinkm_led_name),
"blinkm-%d-%d-blue",
client->adapter->nr,
client->addr);
led[i]->led_cdev.name = blinkm_led_name;
led[i]->led_cdev.brightness_set_blocking =
blinkm_led_blue_set;
err = led_classdev_register(&client->dev,
&led[i]->led_cdev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
led[i]->led_cdev.name);
goto failblue;
}
break;
} /* end switch */
} /* end for */
if (!IS_ENABLED(CONFIG_LEDS_BLINKM_MULTICOLOR)) {
err = register_separate_colors(client, data);
if (err < 0)
return err;
} else {
err = register_multicolor(client, data);
if (err < 0)
return err;
}
/* Initialize the blinkm */
blinkm_init_hw(client);
return 0;
failblue:
led_classdev_unregister(&led[GREEN]->led_cdev);
failgreen:
led_classdev_unregister(&led[RED]->led_cdev);
failred:
sysfs_remove_group(&client->dev.kobj, &blinkm_group);
exit:
return err;
}
static void blinkm_remove(struct i2c_client *client)
@ -683,8 +770,8 @@ static void blinkm_remove(struct i2c_client *client)
int i;
/* make sure no workqueue entries are pending */
for (i = 0; i < 3; i++)
led_classdev_unregister(&data->blinkm_leds[i].led_cdev);
for (i = 0; i < NUM_LEDS; i++)
led_classdev_unregister(&data->blinkm_leds[i].cdev.led_cdev);
/* reset rgb */
data->next_red = 0x00;
@ -740,6 +827,7 @@ static struct i2c_driver blinkm_driver = {
module_i2c_driver(blinkm_driver);
MODULE_AUTHOR("Jan-Simon Moeller <dl9pf@gmx.de>");
MODULE_AUTHOR("Joseph Strauss <jstrauss@mailbox.org>");
MODULE_DESCRIPTION("BlinkM RGB LED driver");
MODULE_LICENSE("GPL");