Commit e4a7b2dc authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'leds-5.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds

Pull LED updates from Pavel Machek:
 "Okay, so... this one is interesting. RGB LEDs are very common, and we
  need to have some kind of support for them. Multicolor is for
  arbitrary set of LEDs in one package, RGB is for LEDs that can produce
  full range of colors. We do not have real multicolor LED that is not
  RGB in the pipeline, so that one is disabled for now.

  You can expect this saga to continue with next pull requests"

* tag 'leds-5.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds: (37 commits)
  MAINTAINERS: Remove myself as LED subsystem maintainer
  leds: disallow /sys/class/leds/*:multi:* for now
  leds: add RGB color option, as that is different from multicolor.
  Make LEDS_LP55XX_COMMON depend on I2C to fix build errors:
  Documentation: ABI: leds-turris-omnia: document sysfs attribute
  leds: initial support for Turris Omnia LEDs
  dt-bindings: leds: add cznic,turris-omnia-leds binding
  leds: pattern trigger -- check pattern for validity
  leds: Replace HTTP links with HTTPS ones
  leds: trigger: add support for LED-private device triggers
  leds: lp5521: Add multicolor framework multicolor brightness support
  leds: lp5523: Update the lp5523 code to add multicolor brightness function
  leds: lp55xx: Add multicolor framework support to lp55xx
  leds: lp55xx: Convert LED class registration to devm_*
  dt-bindings: leds: Convert leds-lp55xx to yaml
  leds: multicolor: Introduce a multicolor class definition
  leds: Add multicolor ID to the color ID list
  dt: bindings: Add multicolor class dt bindings documention
  leds: lp5523: Fix various formatting issues in the code
  leds: lp55xx: Fix file permissions to use DEVICE_ATTR macros
  ...
parents fffe3ae0 bba37471
What: /sys/class/leds/<led>/device/brightness
Date: July 2020
KernelVersion: 5.9
Contact: Marek Behún <marek.behun@nic.cz>
Description: (RW) On the front panel of the Turris Omnia router there is also
a button which can be used to control the intensity of all the
LEDs at once, so that if they are too bright, user can dim them.
The microcontroller cycles between 8 levels of this global
brightness (from 100% to 0%), but this setting can have any
integer value between 0 and 100. It is therefore convenient to be
able to change this setting from software.
Format: %i
What: /sys/class/leds/<led>/brightness
Date: March 2020
KernelVersion: 5.9
Contact: Dan Murphy <dmurphy@ti.com>
Description: read/write
Writing to this file will update all LEDs within the group to a
calculated percentage of what each color LED intensity is set
to. The percentage is calculated for each grouped LED via the
equation below:
led_brightness = brightness * multi_intensity/max_brightness
For additional details please refer to
Documentation/leds/leds-class-multicolor.rst.
The value of the LED is from 0 to
/sys/class/leds/<led>/max_brightness.
What: /sys/class/leds/<led>/multi_index
Date: March 2020
KernelVersion: 5.9
Contact: Dan Murphy <dmurphy@ti.com>
Description: read
The multi_index array, when read, will output the LED colors
as an array of strings as they are indexed in the
multi_intensity file.
What: /sys/class/leds/<led>/multi_intensity
Date: March 2020
KernelVersion: 5.9
Contact: Dan Murphy <dmurphy@ti.com>
Description: read/write
This file contains array of integers. Order of components is
described by the multi_index array. The maximum intensity should
not exceed /sys/class/leds/<led>/max_brightness.
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/cznic,turris-omnia-leds.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: CZ.NIC's Turris Omnia LEDs driver
maintainers:
- Marek Behún <marek.behun@nic.cz>
description:
This module adds support for the RGB LEDs found on the front panel of the
Turris Omnia router. There are 12 RGB LEDs that are controlled by a
microcontroller that communicates via the I2C bus. Each LED is described
as a subnode of this I2C device.
properties:
compatible:
const: cznic,turris-omnia-leds
reg:
description: I2C slave address of the microcontroller.
maxItems: 1
"#address-cells":
const: 1
"#size-cells":
const: 0
patternProperties:
"^multi-led[0-9a-f]$":
type: object
allOf:
- $ref: leds-class-multicolor.yaml#
description:
This node represents one of the RGB LED devices on Turris Omnia.
No subnodes need to be added for subchannels since this controller only
supports RGB LEDs.
properties:
reg:
minimum: 0
maximum: 11
description:
This property identifies one of the LEDs on the front panel of the
Turris Omnia router.
required:
- reg
additionalProperties: false
examples:
- |
#include <dt-bindings/leds/common.h>
i2c0 {
#address-cells = <1>;
#size-cells = <0>;
led-controller@2b {
compatible = "cznic,turris-omnia-leds";
reg = <0x2b>;
#address-cells = <1>;
#size-cells = <0>;
multi-led@0 {
/*
* No subnodes are needed, this controller only supports RGB
* LEDs.
*/
reg = <0>;
color = <LED_COLOR_ID_MULTI>;
function = LED_FUNCTION_POWER;
linux,default-trigger = "heartbeat";
};
multi-led@a {
reg = <0xa>;
color = <LED_COLOR_ID_MULTI>;
function = LED_FUNCTION_INDICATOR;
function-enumerator = <1>;
};
};
};
...
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/leds-class-multicolor.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Common properties for the multicolor LED class.
maintainers:
- Dan Murphy <dmurphy@ti.com>
description: |
Bindings for multi color LEDs show how to describe current outputs of
either integrated multi-color LED elements (like RGB, RGBW, RGBWA-UV
etc.) or standalone LEDs, to achieve logically grouped multi-color LED
modules. This is achieved by adding multi-led nodes layer to the
monochrome LED bindings.
The nodes and properties defined in this document are unique to the multicolor
LED class. Common LED nodes and properties are inherited from the common.txt
within this documentation directory.
patternProperties:
"^multi-led@([0-9a-f])$":
type: object
description: Represents the LEDs that are to be grouped.
properties:
color:
const: 8 # LED_COLOR_ID_MULTI
description: |
For multicolor LED support this property should be defined as
LED_COLOR_ID_MULTI which can be found in include/linux/leds/common.h.
$ref: "common.yaml#"
required:
- color
...
......@@ -102,4 +102,4 @@ led-controller@38 {
};
For more product information please see the links below:
http://www.ti.com/product/LM3532
https://www.ti.com/product/LM3532
......@@ -47,5 +47,5 @@ led-controller@64 {
}
For more product information please see the links below:
http://www.ti.com/product/LM36010
http://www.ti.com/product/LM36011
https://www.ti.com/product/LM36010
https://www.ti.com/product/LM36011
......@@ -82,4 +82,4 @@ lm36274@11 {
};
For more product information please see the link below:
http://www.ti.com/lit/ds/symlink/lm36274.pdf
https://www.ti.com/lit/ds/symlink/lm36274.pdf
......@@ -62,4 +62,4 @@ led-controller@36 {
}
For more product information please see the link below:
http://www.ti.com/lit/ds/snvsa29/snvsa29.pdf
https://www.ti.com/lit/ds/snvsa29/snvsa29.pdf
......@@ -70,4 +70,4 @@ led-controller@36 {
}
For more product information please see the link below:
http://www.ti.com/lit/ds/symlink/lm3697.pdf
https://www.ti.com/lit/ds/symlink/lm3697.pdf
Binding for TI/National Semiconductor LP55xx Led Drivers
Required properties:
- compatible: one of
national,lp5521
national,lp5523
ti,lp55231
ti,lp5562
ti,lp8501
- reg: I2C slave address
- clock-mode: Input clock mode, (0: automode, 1: internal, 2: external)
Each child has own specific current settings
- led-cur: Current setting at each led channel (mA x10, 0 if led is not connected)
- max-cur: Maximun current at each led channel.
Optional properties:
- enable-gpio: GPIO attached to the chip's enable pin
- label: Used for naming LEDs
- pwr-sel: LP8501 specific property. Power selection for output channels.
0: D1~9 are connected to VDD
1: D1~6 with VDD, D7~9 with VOUT
2: D1~6 with VOUT, D7~9 with VDD
3: D1~9 are connected to VOUT
Alternatively, each child can have a specific channel name and trigger:
- chan-name (optional): name of channel
- linux,default-trigger (optional): see
Documentation/devicetree/bindings/leds/common.txt
example 1) LP5521
3 LED channels, external clock used. Channel names are 'lp5521_pri:channel0',
'lp5521_pri:channel1' and 'lp5521_pri:channel2', with a heartbeat trigger
on channel 0.
lp5521@32 {
compatible = "national,lp5521";
reg = <0x32>;
label = "lp5521_pri";
clock-mode = /bits/ 8 <2>;
chan0 {
led-cur = /bits/ 8 <0x2f>;
max-cur = /bits/ 8 <0x5f>;
linux,default-trigger = "heartbeat";
};
chan1 {
led-cur = /bits/ 8 <0x2f>;
max-cur = /bits/ 8 <0x5f>;
};
chan2 {
led-cur = /bits/ 8 <0x2f>;
max-cur = /bits/ 8 <0x5f>;
};
};
example 2) LP5523
9 LED channels with specific name. Internal clock used.
The I2C slave address is configurable with ASEL1 and ASEL0 pins.
Available addresses are 32/33/34/35h.
ASEL1 ASEL0 Address
-------------------------
GND GND 32h
GND VEN 33h
VEN GND 34h
VEN VEN 35h
lp5523@32 {
compatible = "national,lp5523";
reg = <0x32>;
clock-mode = /bits/ 8 <1>;
chan0 {
chan-name = "d1";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan1 {
chan-name = "d2";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan2 {
chan-name = "d3";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan3 {
chan-name = "d4";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan4 {
chan-name = "d5";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan5 {
chan-name = "d6";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan6 {
chan-name = "d7";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan7 {
chan-name = "d8";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan8 {
chan-name = "d9";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
};
example 3) LP5562
4 channels are defined.
lp5562@30 {
compatible = "ti,lp5562";
reg = <0x30>;
clock-mode = /bits/8 <2>;
chan0 {
chan-name = "R";
led-cur = /bits/ 8 <0x20>;
max-cur = /bits/ 8 <0x60>;
};
chan1 {
chan-name = "G";
led-cur = /bits/ 8 <0x20>;
max-cur = /bits/ 8 <0x60>;
};
chan2 {
chan-name = "B";
led-cur = /bits/ 8 <0x20>;
max-cur = /bits/ 8 <0x60>;
};
chan3 {
chan-name = "W";
led-cur = /bits/ 8 <0x20>;
max-cur = /bits/ 8 <0x60>;
};
};
example 4) LP8501
9 channels are defined. The 'pwr-sel' is LP8501 specific property.
Others are same as LP5523.
lp8501@32 {
compatible = "ti,lp8501";
reg = <0x32>;
clock-mode = /bits/ 8 <2>;
pwr-sel = /bits/ 8 <3>; /* D1~9 connected to VOUT */
chan0 {
chan-name = "d1";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan1 {
chan-name = "d2";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan2 {
chan-name = "d3";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan3 {
chan-name = "d4";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan4 {
chan-name = "d5";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan5 {
chan-name = "d6";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan6 {
chan-name = "d7";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan7 {
chan-name = "d8";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
chan8 {
chan-name = "d9";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
};
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/leds-lp55xx.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: TI/National Semiconductor LP55xx and LP8501 LED Drivers
maintainers:
- Jacek Anaszewski <jacek.anaszewski@gmail.com>
- Pavel Machek <pavel@ucw.cz>
description: |
Bindings for the TI/National Semiconductor LP55xx and LP8501 multi channel
LED Drivers.
For more product information please see the link below:
https://www.ti.com/lit/gpn/lp5521
https://www.ti.com/lit/gpn/lp5523
https://www.ti.com/lit/gpn/lp55231
https://www.ti.com/lit/gpn/lp5562
https://www.ti.com/lit/gpn/lp8501
properties:
compatible:
enum:
- national,lp5521
- national,lp5523
- ti,lp55231
- ti,lp5562
- ti,lp8501
reg:
maxItems: 1
description: I2C slave address
clock-mode:
$ref: /schemas/types.yaml#definitions/uint8
description: |
Input clock mode
enum:
- 0 # automode
- 1 # internal
- 2 # external
enable-gpio:
maxItems: 1
description: |
GPIO attached to the chip's enable pin
pwr-sel:
$ref: /schemas/types.yaml#definitions/uint8
description: |
LP8501 specific property. Power selection for output channels.
enum:
- 0 # D1~9 are connected to VDD
- 1 # D1~6 with VDD, D7~9 with VOUT
- 2 # D1~6 with VOUT, D7~9 with VDD
- 3 # D1~9 are connected to VOUT
patternProperties:
"(^led@[0-9a-f]$|led)":
type: object
$ref: common.yaml#
properties:
led-cur:
$ref: /schemas/types.yaml#definitions/uint8
description: |
Current setting at each LED channel (mA x10, 0 if LED is not connected)
minimum: 0
maximum: 255
max-cur:
$ref: /schemas/types.yaml#definitions/uint8
description: Maximun current at each LED channel.
reg:
description: |
Output channel for the LED. This is zero based channel identifier and
the data sheet is a one based channel identifier.
reg value to output to LED output number
enum:
- 0 # LED output D1
- 1 # LED output D2
- 2 # LED output D3
- 3 # LED output D4
- 4 # LED output D5
- 5 # LED output D6
- 6 # LED output D7
- 7 # LED output D8
- 8 # LED output D9
chan-name:
$ref: /schemas/types.yaml#definitions/string
description: name of channel
required:
- compatible
- reg
examples:
- |
#include <dt-bindings/leds/common.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
led-controller@32 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "ti,lp8501";
reg = <0x32>;
clock-mode = /bits/ 8 <2>;
pwr-sel = /bits/ 8 <3>; /* D1~9 connected to VOUT */
led@0 {
reg = <0>;
chan-name = "d1";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
led@1 {
reg = <1>;
chan-name = "d2";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
led@2 {
reg = <2>;
chan-name = "d3";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
led@3 {
reg = <3>;
chan-name = "d4";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
led@4 {
reg = <4>;
chan-name = "d5";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
led@5 {
reg = <5>;
chan-name = "d6";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
led@6 {
reg = <6>;
chan-name = "d7";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
led@7 {
reg = <7>;
chan-name = "d8";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
led@8 {
reg = <8>;
chan-name = "d9";
led-cur = /bits/ 8 <0x14>;
max-cur = /bits/ 8 <0x20>;
};
};
led-controller@33 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "national,lp5523";
reg = <0x33>;
clock-mode = /bits/ 8 <0>;
multi-led@2 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0x2>;
color = <LED_COLOR_ID_MULTI>;
function = LED_FUNCTION_STANDBY;
linux,default-trigger = "heartbeat";
led@0 {
led-cur = /bits/ 8 <50>;
max-cur = /bits/ 8 <100>;
reg = <0x0>;
color = <LED_COLOR_ID_GREEN>;
};
led@1 {
led-cur = /bits/ 8 <50>;
max-cur = /bits/ 8 <100>;
reg = <0x1>;
color = <LED_COLOR_ID_BLUE>;
};
led@6 {
led-cur = /bits/ 8 <50>;
max-cur = /bits/ 8 <100>;
reg = <0x6>;
color = <LED_COLOR_ID_RED>;
};
};
};
};
...
......@@ -47,4 +47,4 @@ led-controller@2d {
}
For more product information please see the link below:
http://www.ti.com/product/lp8860-q1
https://www.ti.com/product/lp8860-q1
......@@ -26,9 +26,9 @@ LED sub-node properties:
from 0 to 15 for the pca9552
from 0 to 3 for the pca9553
- type: (optional) either
PCA9532_TYPE_NONE
PCA9532_TYPE_LED
PCA9532_TYPE_GPIO
PCA955X_TYPE_NONE
PCA955X_TYPE_LED
PCA955X_TYPE_GPIO
see dt-bindings/leds/leds-pca955x.h (default to LED)
- label : (optional)
see Documentation/devicetree/bindings/leds/common.txt
......
......@@ -9,6 +9,7 @@ LEDs
leds-class
leds-class-flash
leds-class-multicolor
ledtrig-oneshot
ledtrig-transient
ledtrig-usbport
......
.. SPDX-License-Identifier: GPL-2.0
====================================
Multicolor LED handling under Linux
====================================
Description
===========
The multicolor class groups monochrome LEDs and allows controlling two
aspects of the final combined color: hue and lightness. The former is
controlled via the multi_intensity array file and the latter is controlled
via brightness file.
Multicolor Class Control
========================
The multicolor class presents files that groups the colors as indexes in an
array. These files are children under the LED parent node created by the
led_class framework. The led_class framework is documented in led-class.rst
within this documentation directory.
Each colored LED will be indexed under the multi_* files. The order of the
colors will be arbitrary. The multi_index file can be read to determine the
color name to indexed value.
The multi_index file is an array that contains the string list of the colors as
they are defined in each multi_* array file.
The multi_intensity is an array that can be read or written to for the
individual color intensities. All elements within this array must be written in
order for the color LED intensities to be updated.
Directory Layout Example
========================
root:/sys/class/leds/multicolor:status# ls -lR
-rw-r--r-- 1 root root 4096 Oct 19 16:16 brightness
-r--r--r-- 1 root root 4096 Oct 19 16:16 max_brightness
-r--r--r-- 1 root root 4096 Oct 19 16:16 multi_index
-rw-r--r-- 1 root root 4096 Oct 19 16:16 multi_intensity
Multicolor Class Brightness Control
===================================
The brightness level for each LED is calculated based on the color LED
intensity setting divided by the global max_brightness setting multiplied by
the requested brightness.
led_brightness = brightness * multi_intensity/max_brightness
Example:
A user first writes the multi_intensity file with the brightness levels
for each LED that are necessary to achieve a certain color output from a
multicolor LED group.
cat /sys/class/leds/multicolor:status/multi_index
green blue red
echo 43 226 138 > /sys/class/leds/multicolor:status/multi_intensity
red -
intensity = 138
max_brightness = 255
green -
intensity = 43
max_brightness = 255
blue -
intensity = 226
max_brightness = 255
The user can control the brightness of that multicolor LED group by writing the
global 'brightness' control. Assuming a max_brightness of 255 the user
may want to dim the LED color group to half. The user would write a value of
128 to the global brightness file then the values written to each LED will be
adjusted base on this value.
cat /sys/class/leds/multicolor:status/max_brightness
255
echo 128 > /sys/class/leds/multicolor:status/brightness
adjusted_red_value = 128 * 138/255 = 69
adjusted_green_value = 128 * 43/255 = 21
adjusted_blue_value = 128 * 226/255 = 113
Reading the global brightness file will return the current brightness value of
the color LED group.
cat /sys/class/leds/multicolor:status/brightness
128
......@@ -9743,12 +9743,10 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/tobin/leaks.git
F: scripts/leaking_addresses.pl
LED SUBSYSTEM
M: Jacek Anaszewski <jacek.anaszewski@gmail.com>
M: Pavel Machek <pavel@ucw.cz>
R: Dan Murphy <dmurphy@ti.com>
L: linux-leds@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds.git
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git
F: Documentation/devicetree/bindings/leds/
F: drivers/leds/
......
......@@ -30,6 +30,16 @@ config LEDS_CLASS_FLASH
for the flash related features of a LED device. It can be built
as a module.
config LEDS_CLASS_MULTICOLOR
tristate "LED Multicolor Class Support"
depends on LEDS_CLASS
help
This option enables the multicolor LED sysfs class in /sys/class/leds.
It wraps LED class and adds multicolor LED specific sysfs attributes
and kernel internal API to it. You'll need this to provide support
for multicolor LEDs that are grouped together. This class is not
intended for single color LEDs. It can be built as a module.
config LEDS_BRIGHTNESS_HW_CHANGED
bool "LED Class brightness_hw_changed attribute support"
depends on LEDS_CLASS
......@@ -166,6 +176,17 @@ config LEDS_EL15203000
To compile this driver as a module, choose M here: the module
will be called leds-el15203000.
config LEDS_TURRIS_OMNIA
tristate "LED support for CZ.NIC's Turris Omnia"
depends on LEDS_CLASS_MULTICOLOR
depends on I2C
depends on MACH_ARMADA_38X || COMPILE_TEST
depends on OF
help
This option enables basic support for the LEDs found on the front
side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the
front panel.
config LEDS_LM3530
tristate "LCD Backlight driver for LM3530"
depends on LEDS_CLASS
......@@ -376,7 +397,9 @@ config LEDS_LP3952
config LEDS_LP55XX_COMMON
tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501"
depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501
depends on LEDS_CLASS_MULTICOLOR || !LEDS_CLASS_MULTICOLOR
depends on OF
depends on I2C
select FW_LOADER
select FW_LOADER_USER_HELPER
help
......@@ -386,7 +409,7 @@ config LEDS_LP55XX_COMMON
config LEDS_LP5521
tristate "LED Support for N.S. LP5521 LED driver chip"
depends on LEDS_CLASS && I2C
select LEDS_LP55XX_COMMON
depends on LEDS_LP55XX_COMMON
help
If you say yes here you get support for the National Semiconductor
LP5521 LED driver. It is 3 channel chip with programmable engines.
......@@ -396,7 +419,7 @@ config LEDS_LP5521
config LEDS_LP5523
tristate "LED Support for TI/National LP5523/55231 LED driver chip"
depends on LEDS_CLASS && I2C
select LEDS_LP55XX_COMMON
depends on LEDS_LP55XX_COMMON
help
If you say yes here you get support for TI/National Semiconductor
LP5523/55231 LED driver.
......@@ -407,7 +430,7 @@ config LEDS_LP5523
config LEDS_LP5562
tristate "LED Support for TI LP5562 LED driver chip"
depends on LEDS_CLASS && I2C
select LEDS_LP55XX_COMMON
depends on LEDS_LP55XX_COMMON
help
If you say yes here you get support for TI LP5562 LED driver.
It is 4 channels chip with programmable engines.
......@@ -417,7 +440,7 @@ config LEDS_LP5562
config LEDS_LP8501
tristate "LED Support for TI LP8501 LED driver chip"
depends on LEDS_CLASS && I2C
select LEDS_LP55XX_COMMON
depends on LEDS_LP55XX_COMMON
help
If you say yes here you get support for TI LP8501 LED driver.
It is 9 channel chip with programmable engines.
......
......@@ -4,6 +4,7 @@
obj-$(CONFIG_NEW_LEDS) += led-core.o
obj-$(CONFIG_LEDS_CLASS) += led-class.o
obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
obj-$(CONFIG_LEDS_CLASS_MULTICOLOR) += led-class-multicolor.o
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
# LED Platform Drivers (keep this sorted, M-| sort)
......@@ -86,6 +87,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o
obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o
obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o
obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o
obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o
obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o
obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o
obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o
......
// 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");
......@@ -173,6 +173,7 @@ void led_classdev_suspend(struct led_classdev *led_cdev)
{
led_cdev->flags |= LED_SUSPENDED;
led_set_brightness_nopm(led_cdev, 0);
flush_work(&led_cdev->set_brightness_work);
}
EXPORT_SYMBOL_GPL(led_classdev_suspend);
......
......@@ -34,6 +34,8 @@ const char * const led_colors[LED_COLOR_ID_MAX] = {
[LED_COLOR_ID_VIOLET] = "violet",
[LED_COLOR_ID_YELLOW] = "yellow",
[LED_COLOR_ID_IR] = "ir",
[LED_COLOR_ID_MULTI] = "multicolor",
[LED_COLOR_ID_RGB] = "rgb",
};
EXPORT_SYMBOL_GPL(led_colors);
......@@ -423,6 +425,10 @@ int led_compose_name(struct device *dev, struct led_init_data *init_data,
struct fwnode_handle *fwnode = init_data->fwnode;
const char *devicename = init_data->devicename;
/* We want to label LEDs that can produce full range of colors
* as RGB, not multicolor */
BUG_ON(props.color == LED_COLOR_ID_MULTI);
if (!led_classdev_name)
return -EINVAL;
......
......@@ -27,6 +27,12 @@ LIST_HEAD(trigger_list);
/* Used by LED Class */
static inline bool
trigger_relevant(struct led_classdev *led_cdev, struct led_trigger *trig)
{
return !trig->trigger_type || trig->trigger_type == led_cdev->trigger_type;
}
ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
loff_t pos, size_t count)
......@@ -50,7 +56,7 @@ ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
down_read(&triggers_list_lock);
list_for_each_entry(trig, &trigger_list, next_trig) {
if (sysfs_streq(buf, trig->name)) {
if (sysfs_streq(buf, trig->name) && trigger_relevant(led_cdev, trig)) {
down_write(&led_cdev->trigger_lock);
led_trigger_set(led_cdev, trig);
up_write(&led_cdev->trigger_lock);
......@@ -93,8 +99,12 @@ static int led_trigger_format(char *buf, size_t size,
led_cdev->trigger ? "none" : "[none]");
list_for_each_entry(trig, &trigger_list, next_trig) {
bool hit = led_cdev->trigger &&
!strcmp(led_cdev->trigger->name, trig->name);
bool hit;
if (!trigger_relevant(led_cdev, trig))
continue;
hit = led_cdev->trigger && !strcmp(led_cdev->trigger->name, trig->name);
len += led_trigger_snprintf(buf + len, size - len,
" %s%s%s", hit ? "[" : "",
......@@ -243,7 +253,8 @@ void led_trigger_set_default(struct led_classdev *led_cdev)
down_read(&triggers_list_lock);
down_write(&led_cdev->trigger_lock);
list_for_each_entry(trig, &trigger_list, next_trig) {
if (!strcmp(led_cdev->default_trigger, trig->name)) {
if (!strcmp(led_cdev->default_trigger, trig->name) &&
trigger_relevant(led_cdev, trig)) {
led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER;
led_trigger_set(led_cdev, trig);
break;
......@@ -280,7 +291,9 @@ int led_trigger_register(struct led_trigger *trig)
down_write(&triggers_list_lock);
/* Make sure the trigger's name isn't already in use */
list_for_each_entry(_trig, &trigger_list, next_trig) {
if (!strcmp(_trig->name, trig->name)) {
if (!strcmp(_trig->name, trig->name) &&
(trig->trigger_type == _trig->trigger_type ||
!trig->trigger_type || !_trig->trigger_type)) {
up_write(&triggers_list_lock);
return -EEXIST;
}
......@@ -294,7 +307,8 @@ int led_trigger_register(struct led_trigger *trig)
list_for_each_entry(led_cdev, &leds_list, node) {
down_write(&led_cdev->trigger_lock);
if (!led_cdev->trigger && led_cdev->default_trigger &&
!strcmp(led_cdev->default_trigger, trig->name)) {
!strcmp(led_cdev->default_trigger, trig->name) &&
trigger_relevant(led_cdev, trig)) {
led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER;
led_trigger_set(led_cdev, trig);
}
......@@ -358,7 +372,7 @@ int devm_led_trigger_register(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_led_trigger_register);
/* Simple LED Tigger Interface */
/* Simple LED Trigger Interface */
void led_trigger_event(struct led_trigger *trig,
enum led_brightness brightness)
......
......@@ -203,21 +203,33 @@ static int pm860x_led_probe(struct platform_device *pdev)
data->cdev.brightness_set_blocking = pm860x_led_set;
mutex_init(&data->lock);
ret = devm_led_classdev_register(chip->dev, &data->cdev);
ret = led_classdev_register(chip->dev, &data->cdev);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
return ret;
}
pm860x_led_set(&data->cdev, 0);
platform_set_drvdata(pdev, data);
return 0;
}
static int pm860x_led_remove(struct platform_device *pdev)
{
struct pm860x_led *data = platform_get_drvdata(pdev);
led_classdev_unregister(&data->cdev);
return 0;
}
static struct platform_driver pm860x_led_driver = {
.driver = {
.name = "88pm860x-led",
},
.probe = pm860x_led_probe,
.remove = pm860x_led_remove,
};
module_platform_driver(pm860x_led_driver);
......
......@@ -24,12 +24,17 @@
#define BCM6328_LED_MAX_COUNT 24
#define BCM6328_LED_DEF_DELAY 500
#define BCM6328_LED_INTERVAL_MS 20
#define BCM6328_LED_INTV_MASK 0x3f
#define BCM6328_LED_FAST_INTV_SHIFT 6
#define BCM6328_LED_FAST_INTV_MASK (BCM6328_LED_INTV_MASK << \
BCM6328_LED_FAST_INTV_SHIFT)
#define BCM6328_LED_BLINK_DELAYS 2
#define BCM6328_LED_BLINK_MS 20
#define BCM6328_LED_BLINK_MASK 0x3f
#define BCM6328_LED_BLINK1_SHIFT 0
#define BCM6328_LED_BLINK1_MASK (BCM6328_LED_BLINK_MASK << \
BCM6328_LED_BLINK1_SHIFT)
#define BCM6328_LED_BLINK2_SHIFT 6
#define BCM6328_LED_BLINK2_MASK (BCM6328_LED_BLINK_MASK << \
BCM6328_LED_BLINK2_SHIFT)
#define BCM6328_SERIAL_LED_EN BIT(12)
#define BCM6328_SERIAL_LED_MUX BIT(13)
#define BCM6328_SERIAL_LED_CLK_NPOL BIT(14)
......@@ -45,8 +50,8 @@
#define BCM6328_LED_MODE_MASK 3
#define BCM6328_LED_MODE_ON 0
#define BCM6328_LED_MODE_FAST 1
#define BCM6328_LED_MODE_BLINK 2
#define BCM6328_LED_MODE_BLINK1 1
#define BCM6328_LED_MODE_BLINK2 2
#define BCM6328_LED_MODE_OFF 3
#define BCM6328_LED_SHIFT(X) ((X) << 1)
......@@ -127,12 +132,18 @@ static void bcm6328_led_set(struct led_classdev *led_cdev,
unsigned long flags;
spin_lock_irqsave(led->lock, flags);
*(led->blink_leds) &= ~BIT(led->pin);
/* Remove LED from cached HW blinking intervals */
led->blink_leds[0] &= ~BIT(led->pin);
led->blink_leds[1] &= ~BIT(led->pin);
/* Set LED on/off */
if ((led->active_low && value == LED_OFF) ||
(!led->active_low && value != LED_OFF))
bcm6328_led_mode(led, BCM6328_LED_MODE_ON);
else
bcm6328_led_mode(led, BCM6328_LED_MODE_OFF);
spin_unlock_irqrestore(led->lock, flags);
}
......@@ -140,8 +151,8 @@ static unsigned long bcm6328_blink_delay(unsigned long delay)
{
unsigned long bcm6328_delay;
bcm6328_delay = delay + BCM6328_LED_INTERVAL_MS / 2;
bcm6328_delay = bcm6328_delay / BCM6328_LED_INTERVAL_MS;
bcm6328_delay = delay + BCM6328_LED_BLINK_MS / 2;
bcm6328_delay = bcm6328_delay / BCM6328_LED_BLINK_MS;
if (bcm6328_delay == 0)
bcm6328_delay = 1;
......@@ -168,28 +179,68 @@ static int bcm6328_blink_set(struct led_classdev *led_cdev,
return -EINVAL;
}
if (delay > BCM6328_LED_INTV_MASK) {
if (delay > BCM6328_LED_BLINK_MASK) {
dev_dbg(led_cdev->dev,
"fallback to soft blinking (delay > %ums)\n",
BCM6328_LED_INTV_MASK * BCM6328_LED_INTERVAL_MS);
BCM6328_LED_BLINK_MASK * BCM6328_LED_BLINK_MS);
return -EINVAL;
}
spin_lock_irqsave(led->lock, flags);
if (*(led->blink_leds) == 0 ||
*(led->blink_leds) == BIT(led->pin) ||
*(led->blink_delay) == delay) {
/*
* Check if any of the two configurable HW blinking intervals is
* available:
* 1. No LEDs assigned to the HW blinking interval.
* 2. Only this LED is assigned to the HW blinking interval.
* 3. LEDs with the same delay assigned.
*/
if (led->blink_leds[0] == 0 ||
led->blink_leds[0] == BIT(led->pin) ||
led->blink_delay[0] == delay) {
unsigned long val;
*(led->blink_leds) |= BIT(led->pin);
*(led->blink_delay) = delay;
/* Add LED to the first HW blinking interval cache */
led->blink_leds[0] |= BIT(led->pin);
/* Remove LED from the second HW blinking interval cache */
led->blink_leds[1] &= ~BIT(led->pin);
/* Cache first HW blinking interval delay */
led->blink_delay[0] = delay;
/* Update the delay for the first HW blinking interval */
val = bcm6328_led_read(led->mem + BCM6328_REG_INIT);
val &= ~BCM6328_LED_FAST_INTV_MASK;
val |= (delay << BCM6328_LED_FAST_INTV_SHIFT);
val &= ~BCM6328_LED_BLINK1_MASK;
val |= (delay << BCM6328_LED_BLINK1_SHIFT);
bcm6328_led_write(led->mem + BCM6328_REG_INIT, val);
bcm6328_led_mode(led, BCM6328_LED_MODE_BLINK);
/* Set the LED to first HW blinking interval */
bcm6328_led_mode(led, BCM6328_LED_MODE_BLINK1);
rc = 0;
} else if (led->blink_leds[1] == 0 ||
led->blink_leds[1] == BIT(led->pin) ||
led->blink_delay[1] == delay) {
unsigned long val;
/* Remove LED from the first HW blinking interval */
led->blink_leds[0] &= ~BIT(led->pin);
/* Add LED to the second HW blinking interval */
led->blink_leds[1] |= BIT(led->pin);
/* Cache second HW blinking interval delay */
led->blink_delay[1] = delay;
/* Update the delay for the second HW blinking interval */
val = bcm6328_led_read(led->mem + BCM6328_REG_INIT);
val &= ~BCM6328_LED_BLINK2_MASK;
val |= (delay << BCM6328_LED_BLINK2_SHIFT);
bcm6328_led_write(led->mem + BCM6328_REG_INIT, val);
/* Set the LED to second HW blinking interval */
bcm6328_led_mode(led, BCM6328_LED_MODE_BLINK2);
rc = 0;
} else {
dev_dbg(led_cdev->dev,
......@@ -358,11 +409,13 @@ static int bcm6328_leds_probe(struct platform_device *pdev)
if (!lock)
return -ENOMEM;
blink_leds = devm_kzalloc(dev, sizeof(*blink_leds), GFP_KERNEL);
blink_leds = devm_kcalloc(dev, BCM6328_LED_BLINK_DELAYS,
sizeof(*blink_leds), GFP_KERNEL);
if (!blink_leds)
return -ENOMEM;
blink_delay = devm_kzalloc(dev, sizeof(*blink_delay), GFP_KERNEL);
blink_delay = devm_kcalloc(dev, BCM6328_LED_BLINK_DELAYS,
sizeof(*blink_delay), GFP_KERNEL);
if (!blink_delay)
return -ENOMEM;
......
......@@ -110,12 +110,23 @@ static int da903x_led_probe(struct platform_device *pdev)
led->flags = pdata->flags;
led->master = pdev->dev.parent;
ret = devm_led_classdev_register(led->master, &led->cdev);
ret = led_classdev_register(led->master, &led->cdev);
if (ret) {
dev_err(&pdev->dev, "failed to register LED %d\n", id);
return ret;
}
platform_set_drvdata(pdev, led);
return 0;
}
static int da903x_led_remove(struct platform_device *pdev)
{
struct da903x_led *led = platform_get_drvdata(pdev);
led_classdev_unregister(&led->cdev);
return 0;
}
......@@ -124,6 +135,7 @@ static struct platform_driver da903x_led_driver = {
.name = "da903x-led",
},
.probe = da903x_led_probe,
.remove = da903x_led_remove,
};
module_platform_driver(da903x_led_driver);
......
......@@ -125,12 +125,6 @@ struct gpio_leds_priv {
struct gpio_led_data leds[];
};
static inline int sizeof_gpio_leds_priv(int num_leds)
{
return sizeof(struct gpio_leds_priv) +
(sizeof(struct gpio_led_data) * num_leds);
}
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
......@@ -142,7 +136,7 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
if (!count)
return ERR_PTR(-ENODEV);
priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL);
if (!priv)
return ERR_PTR(-ENOMEM);
......@@ -220,7 +214,7 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
* device, this will hit the board file, if any and get
* the GPIO from there.
*/
gpiod = devm_gpiod_get_index(dev, NULL, idx, flags);
gpiod = devm_gpiod_get_index(dev, NULL, idx, GPIOD_OUT_LOW);
if (!IS_ERR(gpiod)) {
gpiod_set_consumer_name(gpiod, template->name);
return gpiod;
......@@ -260,9 +254,8 @@ static int gpio_led_probe(struct platform_device *pdev)
int i, ret = 0;
if (pdata && pdata->num_leds) {
priv = devm_kzalloc(&pdev->dev,
sizeof_gpio_leds_priv(pdata->num_leds),
GFP_KERNEL);
priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, pdata->num_leds),
GFP_KERNEL);
if (!priv)
return -ENOMEM;
......
// SPDX-License-Identifier: GPL-2.0
// TI LM3532 LED driver
// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
// http://www.ti.com/lit/ds/symlink/lm3532.pdf
// Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com/
// https://www.ti.com/lit/ds/symlink/lm3532.pdf
#include <linux/i2c.h>
#include <linux/leds.h>
......
......@@ -694,7 +694,7 @@ static int lm3533_led_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, led);
ret = devm_led_classdev_register(pdev->dev.parent, &led->cdev);
ret = led_classdev_register(pdev->dev.parent, &led->cdev);
if (ret) {
dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id);
return ret;
......@@ -704,13 +704,18 @@ static int lm3533_led_probe(struct platform_device *pdev)
ret = lm3533_led_setup(led, pdata);
if (ret)
return ret;
goto err_deregister;
ret = lm3533_ctrlbank_enable(&led->cb);
if (ret)
return ret;
goto err_deregister;
return 0;
err_deregister:
led_classdev_unregister(&led->cdev);
return ret;
}
static int lm3533_led_remove(struct platform_device *pdev)
......@@ -720,6 +725,7 @@ static int lm3533_led_remove(struct platform_device *pdev)
dev_dbg(&pdev->dev, "%s\n", __func__);
lm3533_ctrlbank_disable(&led->cb);
led_classdev_unregister(&led->cdev);
return 0;
}
......
......@@ -164,18 +164,19 @@ static int lm355x_chip_init(struct lm355x_chip_data *chip)
/* input and output pins configuration */
switch (chip->type) {
case CHIP_LM3554:
reg_val = pdata->pin_tx2 | pdata->ntc_pin;
reg_val = (u32)pdata->pin_tx2 | (u32)pdata->ntc_pin;
ret = regmap_update_bits(chip->regmap, 0xE0, 0x28, reg_val);
if (ret < 0)
goto out;
reg_val = pdata->pass_mode;
reg_val = (u32)pdata->pass_mode;
ret = regmap_update_bits(chip->regmap, 0xA0, 0x04, reg_val);
if (ret < 0)
goto out;
break;
case CHIP_LM3556:
reg_val = pdata->pin_tx2 | pdata->ntc_pin | pdata->pass_mode;
reg_val = (u32)pdata->pin_tx2 | (u32)pdata->ntc_pin |
(u32)pdata->pass_mode;
ret = regmap_update_bits(chip->regmap, 0x0A, 0xC4, reg_val);
if (ret < 0)
goto out;
......@@ -452,8 +453,7 @@ static int lm355x_probe(struct i2c_client *client,
chip->cdev_flash.max_brightness = 16;
chip->cdev_flash.brightness_set_blocking = lm355x_strobe_brightness_set;
chip->cdev_flash.default_trigger = "flash";
err = led_classdev_register((struct device *)
&client->dev, &chip->cdev_flash);
err = led_classdev_register(&client->dev, &chip->cdev_flash);
if (err < 0)
goto err_out;
/* torch */
......@@ -461,8 +461,7 @@ static int lm355x_probe(struct i2c_client *client,
chip->cdev_torch.max_brightness = 8;
chip->cdev_torch.brightness_set_blocking = lm355x_torch_brightness_set;
chip->cdev_torch.default_trigger = "torch";
err = led_classdev_register((struct device *)
&client->dev, &chip->cdev_torch);
err = led_classdev_register(&client->dev, &chip->cdev_torch);
if (err < 0)
goto err_create_torch_file;
/* indicator */
......@@ -476,8 +475,7 @@ static int lm355x_probe(struct i2c_client *client,
/* indicator pattern control only for LM3556 */
if (id->driver_data == CHIP_LM3556)
chip->cdev_indicator.groups = lm355x_indicator_groups;
err = led_classdev_register((struct device *)
&client->dev, &chip->cdev_indicator);
err = led_classdev_register(&client->dev, &chip->cdev_indicator);
if (err < 0)
goto err_create_indicator_file;
......
// SPDX-License-Identifier: GPL-2.0
// Flash and torch driver for Texas Instruments LM3601X LED
// Flash driver chip family
// Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
// Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/
#include <linux/delay.h>
#include <linux/i2c.h>
......
// SPDX-License-Identifier: GPL-2.0
// TI LM36274 LED chip family driver
// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
// Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com/
#include <linux/bitops.h>
#include <linux/device.h>
......@@ -133,7 +133,7 @@ static int lm36274_probe(struct platform_device *pdev)
lm36274_data->pdev = pdev;
lm36274_data->dev = lmu->dev;
lm36274_data->regmap = lmu->regmap;
dev_set_drvdata(&pdev->dev, lm36274_data);
platform_set_drvdata(pdev, lm36274_data);
ret = lm36274_parse_dt(lm36274_data);
if (ret) {
......@@ -147,8 +147,16 @@ static int lm36274_probe(struct platform_device *pdev)
return ret;
}
return devm_led_classdev_register(lm36274_data->dev,
&lm36274_data->led_dev);
return led_classdev_register(lm36274_data->dev, &lm36274_data->led_dev);
}
static int lm36274_remove(struct platform_device *pdev)
{
struct lm36274 *lm36274_data = platform_get_drvdata(pdev);
led_classdev_unregister(&lm36274_data->led_dev);
return 0;
}
static const struct of_device_id of_lm36274_leds_match[] = {
......@@ -159,6 +167,7 @@ MODULE_DEVICE_TABLE(of, of_lm36274_leds_match);
static struct platform_driver lm36274_driver = {
.probe = lm36274_probe,
.remove = lm36274_remove,
.driver = {
.name = "lm36274-leds",
},
......
......@@ -340,8 +340,7 @@ static int lm3642_probe(struct i2c_client *client,
chip->cdev_flash.brightness_set_blocking = lm3642_strobe_brightness_set;
chip->cdev_flash.default_trigger = "flash";
chip->cdev_flash.groups = lm3642_flash_groups,
err = led_classdev_register((struct device *)
&client->dev, &chip->cdev_flash);
err = led_classdev_register(&client->dev, &chip->cdev_flash);
if (err < 0) {
dev_err(chip->dev, "failed to register flash\n");
goto err_out;
......@@ -353,8 +352,7 @@ static int lm3642_probe(struct i2c_client *client,
chip->cdev_torch.brightness_set_blocking = lm3642_torch_brightness_set;
chip->cdev_torch.default_trigger = "torch";
chip->cdev_torch.groups = lm3642_torch_groups,
err = led_classdev_register((struct device *)
&client->dev, &chip->cdev_torch);
err = led_classdev_register(&client->dev, &chip->cdev_torch);
if (err < 0) {
dev_err(chip->dev, "failed to register torch\n");
goto err_create_torch_file;
......@@ -365,8 +363,7 @@ static int lm3642_probe(struct i2c_client *client,
chip->cdev_indicator.max_brightness = 8;
chip->cdev_indicator.brightness_set_blocking =
lm3642_indicator_brightness_set;
err = led_classdev_register((struct device *)
&client->dev, &chip->cdev_indicator);
err = led_classdev_register(&client->dev, &chip->cdev_indicator);
if (err < 0) {
dev_err(chip->dev, "failed to register indicator\n");
goto err_create_indicator_file;
......
// SPDX-License-Identifier: GPL-2.0
// TI LM3692x LED chip family driver
// Copyright (C) 2017-18 Texas Instruments Incorporated - http://www.ti.com/
// Copyright (C) 2017-18 Texas Instruments Incorporated - https://www.ti.com/
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
......
// SPDX-License-Identifier: GPL-2.0
// TI LM3697 LED chip family driver
// Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
// Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
......
......@@ -349,6 +349,25 @@ static int lp5521_run_selftest(struct lp55xx_chip *chip, char *buf)
return 0;
}
static int lp5521_multicolor_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
int ret;
int i;
mutex_lock(&chip->lock);
for (i = 0; i < led->mc_cdev.num_colors; i++) {
ret = lp55xx_write(chip,
LP5521_REG_LED_PWM_BASE +
led->mc_cdev.subled_info[i].channel,
led->mc_cdev.subled_info[i].brightness);
if (ret)
break;
}
mutex_unlock(&chip->lock);
return ret;
}
static int lp5521_led_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
......@@ -490,6 +509,7 @@ static struct lp55xx_device_config lp5521_cfg = {
.max_channel = LP5521_MAX_LEDS,
.post_init_device = lp5521_post_init_device,
.brightness_fn = lp5521_led_brightness,
.multicolor_brightness_fn = lp5521_multicolor_brightness,
.set_led_current = lp5521_set_led_current,
.firmware_cb = lp5521_firmware_loaded,
.run_engine = lp5521_run_engine,
......@@ -505,9 +525,16 @@ static int lp5521_probe(struct i2c_client *client,
struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = client->dev.of_node;
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->cfg = &lp5521_cfg;
if (!pdata) {
if (np) {
pdata = lp55xx_of_populate_pdata(&client->dev, np);
pdata = lp55xx_of_populate_pdata(&client->dev, np,
chip);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} else {
......@@ -516,10 +543,6 @@ static int lp5521_probe(struct i2c_client *client,
}
}
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
led = devm_kcalloc(&client->dev,
pdata->num_channels, sizeof(*led), GFP_KERNEL);
if (!led)
......@@ -527,7 +550,6 @@ static int lp5521_probe(struct i2c_client *client,
chip->cl = client;
chip->pdata = pdata;
chip->cfg = &lp5521_cfg;
mutex_init(&chip->lock);
......@@ -541,19 +563,17 @@ static int lp5521_probe(struct i2c_client *client,
ret = lp55xx_register_leds(led, chip);
if (ret)
goto err_register_leds;
goto err_out;
ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
goto err_register_sysfs;
goto err_out;
}
return 0;
err_register_sysfs:
lp55xx_unregister_leds(led, chip);
err_register_leds:
err_out:
lp55xx_deinit_device(chip);
err_init:
return ret;
......@@ -566,7 +586,6 @@ static int lp5521_remove(struct i2c_client *client)
lp5521_stop_all_engines(chip);
lp55xx_unregister_sysfs(chip);
lp55xx_unregister_leds(led, chip);
lp55xx_deinit_device(chip);
return 0;
......
......@@ -23,13 +23,13 @@
#define LP5523_PROGRAM_LENGTH 32 /* bytes */
/* Memory is used like this:
0x00 engine 1 program
0x10 engine 2 program
0x20 engine 3 program
0x30 engine 1 muxing info
0x40 engine 2 muxing info
0x50 engine 3 muxing info
*/
* 0x00 engine 1 program
* 0x10 engine 2 program
* 0x20 engine 3 program
* 0x30 engine 1 muxing info
* 0x40 engine 2 muxing info
* 0x50 engine 3 muxing info
*/
#define LP5523_MAX_LEDS 9
/* Registers */
......@@ -326,7 +326,7 @@ static int lp5523_update_program_memory(struct lp55xx_chip *chip,
const u8 *data, size_t size)
{
u8 pattern[LP5523_PROGRAM_LENGTH] = {0};
unsigned cmd;
unsigned int cmd;
char c[3];
int nrchars;
int ret;
......@@ -468,6 +468,7 @@ static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len)
static void lp5523_mux_to_array(u16 led_mux, char *array)
{
int i, pos = 0;
for (i = 0; i < LP5523_MAX_LEDS; i++)
pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i));
......@@ -506,7 +507,7 @@ static int lp5523_load_mux(struct lp55xx_chip *chip, u16 mux, int nr)
if (ret)
return ret;
ret = lp55xx_write(chip, LP5523_REG_PROG_MEM , (u8)(mux >> 8));
ret = lp55xx_write(chip, LP5523_REG_PROG_MEM, (u8)(mux >> 8));
if (ret)
return ret;
......@@ -791,6 +792,25 @@ static ssize_t store_master_fader_leds(struct device *dev,
return ret;
}
static int lp5523_multicolor_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
int ret;
int i;
mutex_lock(&chip->lock);
for (i = 0; i < led->mc_cdev.num_colors; i++) {
ret = lp55xx_write(chip,
LP5523_REG_LED_PWM_BASE +
led->mc_cdev.subled_info[i].channel,
led->mc_cdev.subled_info[i].brightness);
if (ret)
break;
}
mutex_unlock(&chip->lock);
return ret;
}
static int lp5523_led_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
......@@ -857,6 +877,7 @@ static struct lp55xx_device_config lp5523_cfg = {
.max_channel = LP5523_MAX_LEDS,
.post_init_device = lp5523_post_init_device,
.brightness_fn = lp5523_led_brightness,
.multicolor_brightness_fn = lp5523_multicolor_brightness,
.set_led_current = lp5523_set_led_current,
.firmware_cb = lp5523_firmware_loaded,
.run_engine = lp5523_run_engine,
......@@ -872,9 +893,16 @@ static int lp5523_probe(struct i2c_client *client,
struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = client->dev.of_node;
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->cfg = &lp5523_cfg;
if (!pdata) {
if (np) {
pdata = lp55xx_of_populate_pdata(&client->dev, np);
pdata = lp55xx_of_populate_pdata(&client->dev, np,
chip);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} else {
......@@ -883,10 +911,6 @@ static int lp5523_probe(struct i2c_client *client,
}
}
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
led = devm_kcalloc(&client->dev,
pdata->num_channels, sizeof(*led), GFP_KERNEL);
if (!led)
......@@ -894,7 +918,6 @@ static int lp5523_probe(struct i2c_client *client,
chip->cl = client;
chip->pdata = pdata;
chip->cfg = &lp5523_cfg;
mutex_init(&chip->lock);
......@@ -908,19 +931,17 @@ static int lp5523_probe(struct i2c_client *client,
ret = lp55xx_register_leds(led, chip);
if (ret)
goto err_register_leds;
goto err_out;
ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
goto err_register_sysfs;
goto err_out;
}
return 0;
err_register_sysfs:
lp55xx_unregister_leds(led, chip);
err_register_leds:
err_out:
lp55xx_deinit_device(chip);
err_init:
return ret;
......@@ -933,7 +954,6 @@ static int lp5523_remove(struct i2c_client *client)
lp5523_stop_all_engines(chip);
lp55xx_unregister_sysfs(chip);
lp55xx_unregister_leds(led, chip);
lp55xx_deinit_device(chip);
return 0;
......
......@@ -520,9 +520,16 @@ static int lp5562_probe(struct i2c_client *client,
struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = client->dev.of_node;
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->cfg = &lp5562_cfg;
if (!pdata) {
if (np) {
pdata = lp55xx_of_populate_pdata(&client->dev, np);
pdata = lp55xx_of_populate_pdata(&client->dev, np,
chip);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} else {
......@@ -531,9 +538,6 @@ static int lp5562_probe(struct i2c_client *client,
}
}
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
led = devm_kcalloc(&client->dev,
pdata->num_channels, sizeof(*led), GFP_KERNEL);
......@@ -542,7 +546,6 @@ static int lp5562_probe(struct i2c_client *client,
chip->cl = client;
chip->pdata = pdata;
chip->cfg = &lp5562_cfg;
mutex_init(&chip->lock);
......@@ -554,19 +557,17 @@ static int lp5562_probe(struct i2c_client *client,
ret = lp55xx_register_leds(led, chip);
if (ret)
goto err_register_leds;
goto err_out;
ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
goto err_register_sysfs;
goto err_out;
}
return 0;
err_register_sysfs:
lp55xx_unregister_leds(led, chip);
err_register_leds:
err_out:
lp55xx_deinit_device(chip);
err_init:
return ret;
......@@ -580,7 +581,6 @@ static int lp5562_remove(struct i2c_client *client)
lp5562_stop_engine(chip);
lp55xx_unregister_sysfs(chip);
lp55xx_unregister_leds(led, chip);
lp55xx_deinit_device(chip);
return 0;
......
This diff is collapsed.
......@@ -12,6 +12,8 @@
#ifndef _LEDS_LP55XX_COMMON_H
#define _LEDS_LP55XX_COMMON_H
#include <linux/led-class-multicolor.h>
enum lp55xx_engine_index {
LP55XX_ENGINE_INVALID,
LP55XX_ENGINE_1,
......@@ -93,6 +95,7 @@ struct lp55xx_reg {
* @max_channel : Maximum number of channels
* @post_init_device : Chip specific initialization code
* @brightness_fn : Brightness function
* @multicolor_brightness_fn : Multicolor brightness function
* @set_led_current : LED current set function
* @firmware_cb : Call function when the firmware is loaded
* @run_engine : Run internal engine for pattern
......@@ -106,9 +109,12 @@ struct lp55xx_device_config {
/* define if the device has specific initialization process */
int (*post_init_device) (struct lp55xx_chip *chip);
/* access brightness register */
/* set LED brightness */
int (*brightness_fn)(struct lp55xx_led *led);
/* set multicolor LED brightness */
int (*multicolor_brightness_fn)(struct lp55xx_led *led);
/* current setting function */
void (*set_led_current) (struct lp55xx_led *led, u8 led_current);
......@@ -159,6 +165,8 @@ struct lp55xx_chip {
* struct lp55xx_led
* @chan_nr : Channel number
* @cdev : LED class device
* @mc_cdev : Multi color class device
* @color_components: Multi color LED map information
* @led_current : Current setting at each led channel
* @max_current : Maximun current at each led channel
* @brightness : Brightness value
......@@ -167,6 +175,7 @@ struct lp55xx_chip {
struct lp55xx_led {
int chan_nr;
struct led_classdev cdev;
struct led_classdev_mc mc_cdev;
u8 led_current;
u8 max_current;
u8 brightness;
......@@ -189,8 +198,6 @@ extern void lp55xx_deinit_device(struct lp55xx_chip *chip);
/* common LED class device functions */
extern int lp55xx_register_leds(struct lp55xx_led *led,
struct lp55xx_chip *chip);
extern void lp55xx_unregister_leds(struct lp55xx_led *led,
struct lp55xx_chip *chip);
/* common device attributes functions */
extern int lp55xx_register_sysfs(struct lp55xx_chip *chip);
......@@ -198,6 +205,7 @@ extern void lp55xx_unregister_sysfs(struct lp55xx_chip *chip);
/* common device tree population function */
extern struct lp55xx_platform_data
*lp55xx_of_populate_pdata(struct device *dev, struct device_node *np);
*lp55xx_of_populate_pdata(struct device *dev, struct device_node *np,
struct lp55xx_chip *chip);
#endif /* _LEDS_LP55XX_COMMON_H */
......@@ -308,9 +308,16 @@ static int lp8501_probe(struct i2c_client *client,
struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = client->dev.of_node;
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->cfg = &lp8501_cfg;
if (!pdata) {
if (np) {
pdata = lp55xx_of_populate_pdata(&client->dev, np);
pdata = lp55xx_of_populate_pdata(&client->dev, np,
chip);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} else {
......@@ -319,10 +326,6 @@ static int lp8501_probe(struct i2c_client *client,
}
}
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
led = devm_kcalloc(&client->dev,
pdata->num_channels, sizeof(*led), GFP_KERNEL);
if (!led)
......@@ -330,7 +333,6 @@ static int lp8501_probe(struct i2c_client *client,
chip->cl = client;
chip->pdata = pdata;
chip->cfg = &lp8501_cfg;
mutex_init(&chip->lock);
......@@ -344,19 +346,17 @@ static int lp8501_probe(struct i2c_client *client,
ret = lp55xx_register_leds(led, chip);
if (ret)
goto err_register_leds;
goto err_out;
ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
goto err_register_sysfs;
goto err_out;
}
return 0;
err_register_sysfs:
lp55xx_unregister_leds(led, chip);
err_register_leds:
err_out:
lp55xx_deinit_device(chip);
err_init:
return ret;
......@@ -369,7 +369,6 @@ static int lp8501_remove(struct i2c_client *client)
lp8501_stop_engine(chip);
lp55xx_unregister_sysfs(chip);
lp55xx_unregister_leds(led, chip);
lp55xx_deinit_device(chip);
return 0;
......
......@@ -348,12 +348,6 @@ struct ns2_led_priv {
struct ns2_led_data leds_data[];
};
static inline int sizeof_ns2_led_priv(int num_leds)
{
return sizeof(struct ns2_led_priv) +
(sizeof(struct ns2_led_data) * num_leds);
}
static int ns2_led_probe(struct platform_device *pdev)
{
struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
......@@ -378,8 +372,7 @@ static int ns2_led_probe(struct platform_device *pdev)
return -EINVAL;
#endif /* CONFIG_OF_GPIO */
priv = devm_kzalloc(&pdev->dev,
sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL);
priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds_data, pdata->num_leds), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->num_leds = pdata->num_leds;
......
......@@ -40,7 +40,7 @@
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/gpio/driver.h>
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/module.h>
......
// SPDX-License-Identifier: GPL-2.0
/*
* CZ.NIC's Turris Omnia LEDs driver
*
* 2020 by Marek Behun <marek.behun@nic.cz>
*/
#include <linux/i2c.h>
#include <linux/led-class-multicolor.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include "leds.h"
#define OMNIA_BOARD_LEDS 12
#define OMNIA_LED_NUM_CHANNELS 3
#define CMD_LED_MODE 3
#define CMD_LED_MODE_LED(l) ((l) & 0x0f)
#define CMD_LED_MODE_USER 0x10
#define CMD_LED_STATE 4
#define CMD_LED_STATE_LED(l) ((l) & 0x0f)
#define CMD_LED_STATE_ON 0x10
#define CMD_LED_COLOR 5
#define CMD_LED_SET_BRIGHTNESS 7
#define CMD_LED_GET_BRIGHTNESS 8
#define OMNIA_CMD 0
#define OMNIA_CMD_LED_COLOR_LED 1
#define OMNIA_CMD_LED_COLOR_R 2
#define OMNIA_CMD_LED_COLOR_G 3
#define OMNIA_CMD_LED_COLOR_B 4
#define OMNIA_CMD_LED_COLOR_LEN 5
struct omnia_led {
struct led_classdev_mc mc_cdev;
struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS];
int reg;
};
#define to_omnia_led(l) container_of(l, struct omnia_led, mc_cdev)
struct omnia_leds {
struct i2c_client *client;
struct mutex lock;
struct omnia_led leds[];
};
static int omnia_led_brightness_set_blocking(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
struct omnia_led *led = to_omnia_led(mc_cdev);
u8 buf[OMNIA_CMD_LED_COLOR_LEN], state;
int ret;
mutex_lock(&leds->lock);
led_mc_calc_color_components(&led->mc_cdev, brightness);
buf[OMNIA_CMD] = CMD_LED_COLOR;
buf[OMNIA_CMD_LED_COLOR_LED] = led->reg;
buf[OMNIA_CMD_LED_COLOR_R] = mc_cdev->subled_info[0].brightness;
buf[OMNIA_CMD_LED_COLOR_G] = mc_cdev->subled_info[1].brightness;
buf[OMNIA_CMD_LED_COLOR_B] = mc_cdev->subled_info[2].brightness;
state = CMD_LED_STATE_LED(led->reg);
if (buf[OMNIA_CMD_LED_COLOR_R] || buf[OMNIA_CMD_LED_COLOR_G] || buf[OMNIA_CMD_LED_COLOR_B])
state |= CMD_LED_STATE_ON;
ret = i2c_smbus_write_byte_data(leds->client, CMD_LED_STATE, state);
if (ret >= 0 && (state & CMD_LED_STATE_ON))
ret = i2c_master_send(leds->client, buf, 5);
mutex_unlock(&leds->lock);
return ret;
}
static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
struct device_node *np)
{
struct led_init_data init_data = {};
struct device *dev = &client->dev;
struct led_classdev *cdev;
int ret, color;
ret = of_property_read_u32(np, "reg", &led->reg);
if (ret || led->reg >= OMNIA_BOARD_LEDS) {
dev_warn(dev,
"Node %pOF: must contain 'reg' property with values between 0 and %i\n",
np, OMNIA_BOARD_LEDS - 1);
return 0;
}
ret = of_property_read_u32(np, "color", &color);
if (ret || color != LED_COLOR_ID_MULTI) {
dev_warn(dev,
"Node %pOF: must contain 'color' property with value LED_COLOR_ID_MULTI\n",
np);
return 0;
}
led->subled_info[0].color_index = LED_COLOR_ID_RED;
led->subled_info[0].channel = 0;
led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
led->subled_info[1].channel = 1;
led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
led->subled_info[2].channel = 2;
led->mc_cdev.subled_info = led->subled_info;
led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS;
init_data.fwnode = &np->fwnode;
cdev = &led->mc_cdev.led_cdev;
cdev->max_brightness = 255;
cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
of_property_read_string(np, "linux,default-trigger", &cdev->default_trigger);
/* put the LED into software mode */
ret = i2c_smbus_write_byte_data(client, CMD_LED_MODE,
CMD_LED_MODE_LED(led->reg) |
CMD_LED_MODE_USER);
if (ret < 0) {
dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np, ret);
return ret;
}
/* disable the LED */
ret = i2c_smbus_write_byte_data(client, CMD_LED_STATE, CMD_LED_STATE_LED(led->reg));
if (ret < 0) {
dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret);
return ret;
}
ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data);
if (ret < 0) {
dev_err(dev, "Cannot register LED %pOF: %i\n", np, ret);
return ret;
}
return 1;
}
/*
* On the front panel of the Turris Omnia router there is also a button which
* can be used to control the intensity of all the LEDs at once, so that if they
* are too bright, user can dim them.
* The microcontroller cycles between 8 levels of this global brightness (from
* 100% to 0%), but this setting can have any integer value between 0 and 100.
* It is therefore convenient to be able to change this setting from software.
* We expose this setting via a sysfs attribute file called "brightness". This
* file lives in the device directory of the LED controller, not an individual
* LED, so it should not confuse users.
*/
static ssize_t brightness_show(struct device *dev, struct device_attribute *a, char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct omnia_leds *leds = i2c_get_clientdata(client);
int ret;
mutex_lock(&leds->lock);
ret = i2c_smbus_read_byte_data(client, CMD_LED_GET_BRIGHTNESS);
mutex_unlock(&leds->lock);
if (ret < 0)
return ret;
return sprintf(buf, "%d\n", ret);
}
static ssize_t brightness_store(struct device *dev, struct device_attribute *a, const char *buf,
size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct omnia_leds *leds = i2c_get_clientdata(client);
unsigned int brightness;
int ret;
if (sscanf(buf, "%u", &brightness) != 1)
return -EINVAL;
if (brightness > 100)
return -EINVAL;
mutex_lock(&leds->lock);
ret = i2c_smbus_write_byte_data(client, CMD_LED_SET_BRIGHTNESS, (u8) brightness);
mutex_unlock(&leds->lock);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR_RW(brightness);
static struct attribute *omnia_led_controller_attrs[] = {
&dev_attr_brightness.attr,
NULL,
};
ATTRIBUTE_GROUPS(omnia_led_controller);
static int omnia_leds_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct device_node *np = dev->of_node, *child;
struct omnia_leds *leds;
struct omnia_led *led;
int ret, count;
count = of_get_available_child_count(np);
if (!count) {
dev_err(dev, "LEDs are not defined in device tree!\n");
return -ENODEV;
} else if (count > OMNIA_BOARD_LEDS) {
dev_err(dev, "Too many LEDs defined in device tree!\n");
return -EINVAL;
}
leds = devm_kzalloc(dev, struct_size(leds, leds, count), GFP_KERNEL);
if (!leds)
return -ENOMEM;
leds->client = client;
i2c_set_clientdata(client, leds);
mutex_init(&leds->lock);
led = &leds->leds[0];
for_each_available_child_of_node(np, child) {
ret = omnia_led_register(client, led, child);
if (ret < 0)
return ret;
led += ret;
}
if (devm_device_add_groups(dev, omnia_led_controller_groups))
dev_warn(dev, "Could not add attribute group!\n");
return 0;
}
static int omnia_leds_remove(struct i2c_client *client)
{
u8 buf[OMNIA_CMD_LED_COLOR_LEN];
/* put all LEDs into default (HW triggered) mode */
i2c_smbus_write_byte_data(client, CMD_LED_MODE,
CMD_LED_MODE_LED(OMNIA_BOARD_LEDS));
/* set all LEDs color to [255, 255, 255] */
buf[OMNIA_CMD] = CMD_LED_COLOR;
buf[OMNIA_CMD_LED_COLOR_LED] = OMNIA_BOARD_LEDS;
buf[OMNIA_CMD_LED_COLOR_R] = 255;
buf[OMNIA_CMD_LED_COLOR_G] = 255;
buf[OMNIA_CMD_LED_COLOR_B] = 255;
i2c_master_send(client, buf, 5);
return 0;
}
static const struct of_device_id of_omnia_leds_match[] = {
{ .compatible = "cznic,turris-omnia-leds", },
{},
};
static const struct i2c_device_id omnia_id[] = {
{ "omnia", 0 },
{ }
};
static struct i2c_driver omnia_leds_driver = {
.probe = omnia_leds_probe,
.remove = omnia_leds_remove,
.id_table = omnia_id,
.driver = {
.name = "leds-turris-omnia",
.of_match_table = of_omnia_leds_match,
},
};
module_i2c_driver(omnia_leds_driver);
MODULE_AUTHOR("Marek Behun <marek.behun@nic.cz>");
MODULE_DESCRIPTION("CZ.NIC's Turris Omnia LEDs");
MODULE_LICENSE("GPL v2");
......@@ -269,12 +269,23 @@ static int wm831x_status_probe(struct platform_device *pdev)
drvdata->cdev.blink_set = wm831x_status_blink_set;
drvdata->cdev.groups = wm831x_status_groups;
ret = devm_led_classdev_register(wm831x->dev, &drvdata->cdev);
ret = led_classdev_register(wm831x->dev, &drvdata->cdev);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, drvdata);
return 0;
}
static int wm831x_status_remove(struct platform_device *pdev)
{
struct wm831x_status *drvdata = platform_get_drvdata(pdev);
led_classdev_unregister(&drvdata->cdev);
return 0;
}
......@@ -283,6 +294,7 @@ static struct platform_driver wm831x_status_driver = {
.name = "wm831x-status",
},
.probe = wm831x_status_probe,
.remove = wm831x_status_remove,
};
module_platform_driver(wm831x_status_driver);
......
......@@ -99,7 +99,8 @@ static ssize_t gpio_trig_inverted_store(struct device *dev,
gpio_data->inverted = inverted;
/* After inverting, we need to update the LED. */
gpio_trig_irq(0, led);
if (gpio_is_valid(gpio_data->gpio))
gpio_trig_irq(0, led);
return n;
}
......
......@@ -227,10 +227,12 @@ static int pattern_trig_store_patterns_string(struct pattern_trig_data *data,
while (offset < count - 1 && data->npatterns < MAX_PATTERNS) {
cr = 0;
ccount = sscanf(buf + offset, "%d %u %n",
ccount = sscanf(buf + offset, "%u %u %n",
&data->patterns[data->npatterns].brightness,
&data->patterns[data->npatterns].delta_t, &cr);
if (ccount != 2) {
if (ccount != 2 ||
data->patterns[data->npatterns].brightness > data->led_cdev->max_brightness) {
data->npatterns = 0;
return -EINVAL;
}
......
......@@ -30,7 +30,10 @@
#define LED_COLOR_ID_VIOLET 5
#define LED_COLOR_ID_YELLOW 6
#define LED_COLOR_ID_IR 7
#define LED_COLOR_ID_MAX 8
#define LED_COLOR_ID_MULTI 8 /* For multicolor LEDs */
#define LED_COLOR_ID_RGB 9 /* For multicolor LEDs that can do arbitrary color,
so this would include RGBW and similar */
#define LED_COLOR_ID_MAX 10
/* Standard LED functions */
/* Keyboard LEDs, usually it would be input4::capslock etc. */
......
/* SPDX-License-Identifier: GPL-2.0 */
/* LED Multicolor class interface
* Copyright (C) 2019-20 Texas Instruments Incorporated - http://www.ti.com/
*/
#ifndef _LINUX_MULTICOLOR_LEDS_H_INCLUDED
#define _LINUX_MULTICOLOR_LEDS_H_INCLUDED
#include <linux/leds.h>
#include <dt-bindings/leds/common.h>
struct mc_subled {
unsigned int color_index;
unsigned int brightness;
unsigned int intensity;
unsigned int channel;
};
struct led_classdev_mc {
/* led class device */
struct led_classdev led_cdev;
unsigned int num_colors;
struct mc_subled *subled_info;
};
static inline struct led_classdev_mc *lcdev_to_mccdev(
struct led_classdev *led_cdev)
{
return container_of(led_cdev, struct led_classdev_mc, led_cdev);
}
#if IS_ENABLED(CONFIG_LEDS_CLASS_MULTICOLOR)
/**
* led_classdev_multicolor_register_ext - register a new object of led_classdev
* class with support for multicolor LEDs
* @parent: the multicolor LED to register
* @mcled_cdev: the led_classdev_mc structure for this device
* @init_data: the LED class multicolor device initialization data
*
* Returns: 0 on success or negative error value on failure
*/
int led_classdev_multicolor_register_ext(struct device *parent,
struct led_classdev_mc *mcled_cdev,
struct led_init_data *init_data);
static inline int led_classdev_multicolor_register(struct device *parent,
struct led_classdev_mc *mcled_cdev)
{
return led_classdev_multicolor_register_ext(parent, mcled_cdev, NULL);
}
/**
* led_classdev_multicolor_unregister - unregisters an object of led_classdev
* class with support for multicolor LEDs
* @mcled_cdev: the multicolor LED to unregister
*
* Unregister a previously registered via led_classdev_multicolor_register
* object
*/
void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev);
/* Calculate brightness for the monochrome LED cluster */
int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev,
enum led_brightness brightness);
int devm_led_classdev_multicolor_register_ext(struct device *parent,
struct led_classdev_mc *mcled_cdev,
struct led_init_data *init_data);
static inline int devm_led_classdev_multicolor_register(struct device *parent,
struct led_classdev_mc *mcled_cdev)
{
return devm_led_classdev_multicolor_register_ext(parent, mcled_cdev,
NULL);
}
void devm_led_classdev_multicolor_unregister(struct device *parent,
struct led_classdev_mc *mcled_cdev);
#else
static inline int led_classdev_multicolor_register_ext(struct device *parent,
struct led_classdev_mc *mcled_cdev,
struct led_init_data *init_data)
{
return -EINVAL;
}
static inline int led_classdev_multicolor_register(struct device *parent,
struct led_classdev_mc *mcled_cdev)
{
return led_classdev_multicolor_register_ext(parent, mcled_cdev, NULL);
}
static inline void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev) {};
static inline int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev,
enum led_brightness brightness)
{
return -EINVAL;
}
static inline int devm_led_classdev_multicolor_register_ext(struct device *parent,
struct led_classdev_mc *mcled_cdev,
struct led_init_data *init_data)
{
return -EINVAL;
}
static inline int devm_led_classdev_multicolor_register(struct device *parent,
struct led_classdev_mc *mcled_cdev)
{
return devm_led_classdev_multicolor_register_ext(parent, mcled_cdev,
NULL);
}
static inline void devm_led_classdev_multicolor_unregister(struct device *parent,
struct led_classdev_mc *mcled_cdev)
{};
#endif /* IS_ENABLED(CONFIG_LEDS_CLASS_MULTICOLOR) */
#endif /* _LINUX_MULTICOLOR_LEDS_H_INCLUDED */
......@@ -57,6 +57,10 @@ struct led_init_data {
bool devname_mandatory;
};
struct led_hw_trigger_type {
int dummy;
};
struct led_classdev {
const char *name;
enum led_brightness brightness;
......@@ -141,6 +145,9 @@ struct led_classdev {
void *trigger_data;
/* true if activated - deactivate routine uses it to do cleanup */
bool activated;
/* LEDs that have private triggers have this set */
struct led_hw_trigger_type *trigger_type;
#endif
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
......@@ -345,6 +352,9 @@ struct led_trigger {
int (*activate)(struct led_classdev *led_cdev);
void (*deactivate)(struct led_classdev *led_cdev);
/* LED-private triggers have this set */
struct led_hw_trigger_type *trigger_type;
/* LEDs under control by this trigger (for simple triggers) */
rwlock_t leddev_list_lock;
struct list_head led_cdevs;
......
......@@ -12,17 +12,26 @@
#ifndef _LEDS_LP55XX_H
#define _LEDS_LP55XX_H
#include <linux/gpio/consumer.h>
#include <linux/led-class-multicolor.h>
/* Clock configuration */
#define LP55XX_CLOCK_AUTO 0
#define LP55XX_CLOCK_INT 1
#define LP55XX_CLOCK_EXT 2
#define LP55XX_MAX_GROUPED_CHAN 4
struct lp55xx_led_config {
const char *name;
const char *default_trigger;
u8 chan_nr;
u8 led_current; /* mA x10, 0 if led is not connected */
u8 max_current;
int num_colors;
unsigned int max_channel;
int color_id[LED_COLOR_ID_MAX];
int output_num[LED_COLOR_ID_MAX];
};
struct lp55xx_predef_pattern {
......@@ -49,7 +58,7 @@ enum lp8501_pwr_sel {
* @clock_mode : Input clock mode. LP55XX_CLOCK_AUTO or _INT or _EXT
* @setup_resources : Platform specific function before enabling the chip
* @release_resources : Platform specific function after disabling the chip
* @enable : EN pin control by platform side
* @enable_gpiod : enable GPIO descriptor
* @patterns : Predefined pattern data for RGB channels
* @num_patterns : Number of patterns
* @update_config : Value of CONFIG register
......@@ -65,7 +74,7 @@ struct lp55xx_platform_data {
u8 clock_mode;
/* optional enable GPIO */
int enable_gpio;
struct gpio_desc *enable_gpiod;
/* Predefined pattern data */
struct lp55xx_predef_pattern *patterns;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment