summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/leds/leds-blinkm.rst29
-rw-r--r--Documentation/leds/well-known-leds.txt8
-rw-r--r--drivers/leds/Kconfig8
-rw-r--r--drivers/leds/leds-blinkm.c220
4 files changed, 196 insertions, 69 deletions
diff --git a/Documentation/leds/leds-blinkm.rst b/Documentation/leds/leds-blinkm.rst
index 2d3c226a371a..647be1c6c552 100644
--- a/Documentation/leds/leds-blinkm.rst
+++ b/Documentation/leds/leds-blinkm.rst
@@ -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
diff --git a/Documentation/leds/well-known-leds.txt b/Documentation/leds/well-known-leds.txt
index 67b44704801f..17ef78faf1f3 100644
--- a/Documentation/leds/well-known-leds.txt
+++ b/Documentation/leds/well-known-leds.txt
@@ -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
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 8d9d8da376e4..2e7831867315 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -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
diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c
index e40b87aead2d..577497b9d426 100644
--- a/drivers/leds/leds-blinkm.c
+++ b/drivers/leds/leds-blinkm.c
@@ -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,117 +594,175 @@ static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info)
return 0;
}
-static int blinkm_probe(struct i2c_client *client)
+static int register_separate_colors(struct i2c_client *client, struct blinkm_data *data)
{
- struct blinkm_data *data;
- struct blinkm_led *led[3];
- int err, i;
+ /* 3 separate classes for red, green, and blue respectively */
+ struct blinkm_led *leds[NUM_LEDS];
+ int err;
char blinkm_led_name[28];
-
- data = devm_kzalloc(&client->dev,
- sizeof(struct blinkm_data), GFP_KERNEL);
- if (!data) {
- err = -ENOMEM;
- goto exit;
- }
-
- 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!) */
- data->script_id = 0x01;
- data->i2c_client = client;
-
- i2c_set_clientdata(client, data);
- mutex_init(&data->update_lock);
-
- /* Register sysfs hooks */
- err = sysfs_create_group(&client->dev.kobj, &blinkm_group);
- if (err < 0) {
- dev_err(&client->dev, "couldn't register sysfs group\n");
- goto exit;
- }
-
- for (i = 0; i < 3; i++) {
+ /* Register red, green, and blue sysfs classes */
+ for (int i = 0; i < NUM_LEDS; 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;
+ 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:
- snprintf(blinkm_led_name, sizeof(blinkm_led_name),
+ scnprintf(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 =
+ 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,
- &led[i]->led_cdev);
+ &leds[i]->cdev.led_cdev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
- led[i]->led_cdev.name);
+ leds[i]->cdev.led_cdev.name);
goto failred;
}
break;
case GREEN:
- snprintf(blinkm_led_name, sizeof(blinkm_led_name),
+ scnprintf(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 =
+ 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,
- &led[i]->led_cdev);
+ &leds[i]->cdev.led_cdev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
- led[i]->led_cdev.name);
+ leds[i]->cdev.led_cdev.name);
goto failgreen;
}
break;
case BLUE:
- snprintf(blinkm_led_name, sizeof(blinkm_led_name),
+ scnprintf(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 =
+ 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,
- &led[i]->led_cdev);
+ &leds[i]->cdev.led_cdev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
- led[i]->led_cdev.name);
+ leds[i]->cdev.led_cdev.name);
goto failblue;
}
break;
+ default:
+ break;
} /* end switch */
} /* end for */
-
- /* Initialize the blinkm */
- blinkm_init_hw(client);
-
return 0;
failblue:
- led_classdev_unregister(&led[GREEN]->led_cdev);
-
+ led_classdev_unregister(&leds[GREEN]->cdev.led_cdev);
failgreen:
- led_classdev_unregister(&led[RED]->led_cdev);
-
+ led_classdev_unregister(&leds[RED]->cdev.led_cdev);
failred:
sysfs_remove_group(&client->dev.kobj, &blinkm_group);
-exit:
+
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;
+ int err;
+
+ data = devm_kzalloc(&client->dev,
+ sizeof(struct blinkm_data), GFP_KERNEL);
+ 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!)
+ */
+ data->script_id = 0x01;
+ data->i2c_client = client;
+
+ i2c_set_clientdata(client, data);
+ mutex_init(&data->update_lock);
+
+ /* Register sysfs hooks */
+ err = sysfs_create_group(&client->dev.kobj, &blinkm_group);
+ if (err < 0) {
+ dev_err(&client->dev, "couldn't register sysfs group\n");
+ return err;
+ }
+
+ 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;
+ }
+
+ blinkm_init_hw(client);
+
+ return 0;
+}
+
static void blinkm_remove(struct i2c_client *client)
{
struct blinkm_data *data = i2c_get_clientdata(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");