From: Jonathan Brophy Add comprehensive driver documentation covering: Architecture: - Winner-takes-all arbitration model - Priority-based selection with sequence number tie-breaking - Deterministic channel ordering by LED_COLOR_ID - Locking hierarchy to prevent deadlocks Features: - Two operating modes (multicolor and standard) - Gamma correction support - Update batching for reduced bus traffic - Comprehensive debugfs interface (when CONFIG_DEBUG_FS enabled) Configuration: - Device tree binding examples (RGB, RGBW, fixed-color) - Module parameters for tuning - Sysfs interface usage examples - Performance optimization guidelines Troubleshooting: - Common issues and solutions - Debug logging instructions - Known limitations The documentation includes practical examples for channel ordering verification, priority arbitration scenarios, and debugfs monitoring. Signed-off-by: Jonathan Brophy --- .../leds/leds-group-virtualcolor.rst | 641 ++++++++++++++++++ 1 file changed, 641 insertions(+) create mode 100644 Documentation/leds/leds-group-virtualcolor.rst diff --git a/Documentation/leds/leds-group-virtualcolor.rst b/Documentation/leds/leds-group-virtualcolor.rst new file mode 100644 index 000000000000..a885fc614840 --- /dev/null +++ b/Documentation/leds/leds-group-virtualcolor.rst @@ -0,0 +1,641 @@ +.. SPDX-License-Identifier: GPL-2.0 + +===================================================== +Virtual Grouped LED Driver with Multicolor ABI +===================================================== + +:Author: Jonathan Brophy +:Version: 4 + +Overview +======== + +The ``leds-group-virtualcolor`` driver provides virtual LED devices that +arbitrate control over shared physical LEDs based on priority. Multiple +virtual LEDs can reference the same physical LEDs, with winner-takes-all +arbitration determining which virtual LED controls the hardware. + +This enables complex lighting scenarios where different subsystems (e.g., +notifications, indicators, effects) can request LED control without explicit +coordination. The driver handles arbitration automatically using a +priority-based system with sequence number tiebreaking. + +Key Features +============ + +* **Winner-takes-all arbitration**: Only ONE virtual LED controls hardware at any time +* **Priority-based selection**: Higher priority virtual LEDs win control +* **Sequence-based tiebreaking**: Most recent update wins among equal priorities +* **Multicolor ABI support**: Standard Linux multicolor LED interface +* **Deterministic channel ordering**: Channels sorted by LED_COLOR_ID value +* **Two operating modes**: + + - Multicolor mode (dynamic color mixing with intensity control) + - Standard mode (fixed color multipliers, brightness-only control) + +* **Gamma correction**: Optional perceptual brightness correction +* **Update batching**: Debounces rapid changes to reduce bus traffic +* **Comprehensive debugfs**: Runtime statistics and diagnostics (when CONFIG_DEBUG_FS enabled) +* **Power management**: Suspend/resume with state preservation + +Hardware Support +================ + +The driver works with any physical LED devices that expose the standard +``led_classdev`` interface. Physical LEDs are referenced via device tree +phandles and can be: + +* GPIO LEDs (gpio-leds compatible) +* PWM LEDs (pwm-leds compatible) +* I2C-connected LED controllers +* SPI-connected LED controllers +* Any device using the Linux LED subsystem + +Architecture +============ + +Winner-Takes-All Arbitration +----------------------------- + +The driver uses a winner-takes-all arbitration model: + +1. Only virtual LEDs with brightness > 0 participate in arbitration +2. The virtual LED with the highest priority wins +3. If priorities are equal, the most recently updated virtual LED wins (sequence number) +4. The winner controls **ALL** physical LEDs +5. Physical LEDs not used by the winner are turned off + +Each virtual LED has: + +* **Priority** (0 to INT_MAX): Higher values win arbitration +* **Sequence number**: Atomic counter incremented on brightness changes +* **Channel configuration**: Maps physical LEDs to color channels + +Channel Ordering +---------------- + +Physical LEDs are automatically grouped into channels by their color property. +**Channels are ordered by ascending LED_COLOR_ID value** (0, 1, 2, 3, ...). + +This ordering is deterministic and can be verified at runtime via the +``multi_index`` sysfs attribute. + +Example color ID values: + +* LED_COLOR_ID_WHITE = 0 +* LED_COLOR_ID_RED = 1 +* LED_COLOR_ID_GREEN = 2 +* LED_COLOR_ID_BLUE = 3 +* LED_COLOR_ID_AMBER = 4 +* LED_COLOR_ID_VIOLET = 5 + +For a virtual LED with ``leds = <&white>, <&red>, <&green>, <&blue>``: + +* Channel order: [0]=white (ID 0), [1]=red (ID 1), [2]=green (ID 2), [3]=blue (ID 3) +* multi_index reports: "0 1 2 3" +* multi_intensity order: white red green blue + +For a virtual LED with ``leds = <&red>, <&green>, <&blue>`` (no white): + +* Channel order: [0]=red (ID 1), [1]=green (ID 2), [2]=blue (ID 3) +* multi_index reports: "1 2 3" +* multi_intensity order: red green blue + +Brightness Calculation +---------------------- + +Final physical LED brightness is calculated as:: + + channel_value = intensity * multiplier / 255 (in multicolor mode) + multiplier (in standard mode) + + scaled_value = channel_value * vled_brightness / vled_max_brightness + + final_brightness = gamma_table[scaled_value] (if gamma enabled) + scaled_value (if gamma disabled) + +Locking Hierarchy +----------------- + +To prevent deadlocks, locks must be acquired in this order: + +1. ``vcolor_controller.lock`` (per-controller, protects arbitration state) +2. ``global_owner_rwsem`` (global, protects physical LED ownership) +3. ``virtual_led.lock`` (per-vLED, protects channel data) + +Virtual LED locks are never held during arbitration. The driver copies +channel state under lock, then releases before processing. + +Device Tree Bindings +==================== + +Controller Node +--------------- + +``compatible`` + Must be "leds-group-virtualcolor" + +``#address-cells`` + Must be 1 + +``#size-cells`` + Must be 0 + +Child Node Properties (Virtual LEDs) +------------------------------------- + +Each child node represents one virtual LED. + +``reg`` + Unique index for this virtual LED (required) + +``color`` + LED_COLOR_ID value (typically LED_COLOR_ID_MULTI for multicolor LEDs) + +``function`` + LED function identifier (e.g., LED_FUNCTION_STATUS) + +``leds`` + Phandle array referencing physical LED devices (required). + Physical LEDs are grouped by their color property into channels. + Channel order is determined by ascending LED_COLOR_ID value. + +``priority`` + Integer priority value (0 to 2147483647). Higher values win + arbitration. Default: 0 + +``led-mode`` + Operating mode, either "multicolor" or "standard". + Default: "multicolor" + + * **multicolor**: Intensity can be changed via multi_intensity sysfs + * **standard**: Color fixed by multipliers, only brightness control available + +``mc-channel-multipliers`` + Array of u32 values (0-255), one per color channel. Optional. + Must be ordered to match the channel order (sorted by color ID). + Default: 255 for all channels + + * In multicolor mode: Scales intensity values + * In standard mode: Defines fixed color mix (required) + +``linux,default-trigger`` + Default LED trigger (e.g., "heartbeat", "none") + +Example Device Tree +------------------- + +Basic RGB LED with Priority +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: dts + + #include + + virtualcolor { + compatible = "leds-group-virtualcolor"; + #address-cells = <1>; + #size-cells = <0>; + + notification_led: virtual-led@0 { + reg = <0>; + color = ; + function = LED_FUNCTION_STATUS; + priority = <100>; + led-mode = "multicolor"; + leds = <&red_led>, <&green_led>, <&blue_led>; + /* Channels: [0]=red (ID 1), [1]=green (ID 2), [2]=blue (ID 3) */ + }; + + ambient_led: virtual-led@1 { + reg = <1>; + color = ; + function = LED_FUNCTION_STATUS; + priority = <10>; + led-mode = "multicolor"; + leds = <&red_led>, <&green_led>, <&blue_led>; + }; + }; + +RGBW LED with White Channel +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: dts + + virtualcolor { + compatible = "leds-group-virtualcolor"; + #address-cells = <1>; + #size-cells = <0>; + + status_led: virtual-led@0 { + reg = <0>; + color = ; + function = LED_FUNCTION_STATUS; + priority = <100>; + led-mode = "multicolor"; + leds = <&red_led>, <&green_led>, <&blue_led>, <&white_led>; + /* Channels: [0]=white (ID 0), [1]=red, [2]=green, [3]=blue */ + /* Note: White comes FIRST because LED_COLOR_ID_WHITE = 0 */ + }; + }; + +Standard Mode with Fixed Color +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: dts + + virtualcolor { + compatible = "leds-group-virtualcolor"; + #address-cells = <1>; + #size-sizes = <0>; + + warm_white: virtual-led@0 { + reg = <0>; + color = ; + function = LED_FUNCTION_STATUS; + priority = <50>; + led-mode = "standard"; + leds = <&red_led>, <&green_led>, <&blue_led>; + mc-channel-multipliers = <255 180 100>; + /* Channels: [0]=red:255, [1]=green:180, [2]=blue:100 */ + /* Creates warm white: full red, 70% green, 40% blue */ + }; + }; + +Sysfs Interface +=============== + +Each virtual LED creates a standard LED class device at:: + + /sys/class/leds/: