- Core Frameworks

- Add new framework to support Group Multi-Color (GMC) LEDs
    - Offer an 'optional' API for non-essential LEDs
    - Support obtaining 'max brightness' values from Device Tree
    - Provide new led_classdev member 'color' (settable via DT and SYFS)
    - Stop TTY Trigger from using the old LED_ON constraints
    - Statically allocate leds_class
 
  - New Drivers
    - Add support for NXP PCA995x I2C Constant Current LED Driver
 
  - New Device Support
    - Add support for Siemens Simatic IPC BX-21 to Simatic IPC
 
  - Fix-ups
    - Some dependency / Kconfig tweaking
    - Move final probe() functions back over from .probe_new()
    - Simplify obtaining resources (memory, device data) using unified API helpers
    - Bunch of Device Tree additions, conversions and adaptions
    - Fix trivial styling issues; comments
    - Ensure correct includes are present and remove some that are not required
    - Omit the use of redundant casts and if relevant replace with better ones
    - Use purpose-built APIs for various actions; sysfs_emit(), module_led_trigger()
    - Remove a bunch of superfluous locking
 
  - Bug Fixes
    - Ensure error codes are correctly propagated back up the call chain
    - Fix incorrect error values from being returned (missing '-')
    - Ensure get'ed resources are put'ed to prevent leaks
    - Use correct class when exporting module resources
    - Fixing rounding (or lack there of) issues
    - Fix 'always false' LED_COLOR_ID_MULTI BUG() check
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmT15DUACgkQUa+KL4f8
 d2ER0BAAoejCCnb24JOrYQy7Pq3/ZaaT6PWOB+/U+6s5jyj9ikwHqqH+g/E+FqkE
 AHvhkBalgw1l+QRCvrZMEcsiQmjK5ZR010Ju3lCbvUDa1tsm2Fn/K2IZZxI1j9O8
 mGnPjYrX3FRfejscw0a+MA75o3DuN97V9mHNk6lyRnm/d0ronu+vv/fRYFON04iY
 v2e166/9wfJWvDwOLo+phDS2aCkjLkxzj2lcTtcvuzYqQjOPDqiFTJsft9gOpC9r
 hKOqUkexKebShbi08A+x0fmiB9LNs5kZ3xWWLaxVDT58Plkqv905oLVrWARTGhIG
 BDAhW2/TLV30bhSiTb/bX0eNEMCU6q6WIPwc3otUYpkDeLgbxLD7Rlhp49oud7j9
 5vWX8bxSczIYRir42TKZ+s/d6pKKSvMrsNRDplFb1ahZTv3ssDxuxAA/FY2UPqTt
 C83j0hJOwVfj6ric01R+yedPSW8Po8xqEIzjoxzYtsu3IYGiz6ddjd2ZnhNF5YfP
 AN7GDuja9r7+Fa8JcTFssHe6HtuzkfpJvrpjV3BxL79q6s1z+lT9szTw5Z65zGmO
 k/z4xz95RHUTbpR4rVT+A1pdbQ8OsbVthdy1XnJoYK3NJYJrPldj3Ur3W4wRecP6
 32RWF8ZF6WARsSyyACLo/MP6qxwAE2xk5TjmpLqCeK0ObVmlwDM=
 =wNmv
 -----END PGP SIGNATURE-----

Merge tag 'leds-next-6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds

Pull LED updates from Lee Jones:
 "Core Frameworks:
   - Add new framework to support Group Multi-Color (GMC) LEDs
   - Offer an 'optional' API for non-essential LEDs
   - Support obtaining 'max brightness' values from Device Tree
   - Provide new led_classdev member 'color' (settable via DT and SYFS)
   - Stop TTY Trigger from using the old LED_ON constraints
   - Statically allocate leds_class

  New Drivers:
   - Add support for NXP PCA995x I2C Constant Current LED Driver

  New Device Support:
   - Add support for Siemens Simatic IPC BX-21 to Simatic IPC

  Fix-ups:
   - Some dependency / Kconfig tweaking
   - Move final probe() functions back over from .probe_new()
   - Simplify obtaining resources (memory, device data) using unified
     API helpers
   - Bunch of Device Tree additions, conversions and adaptions
   - Fix trivial styling issues; comments
   - Ensure correct includes are present and remove some that are not
     required
   - Omit the use of redundant casts and if relevant replace with better
     ones
   - Use purpose-built APIs for various actions; sysfs_emit(),
     module_led_trigger()
   - Remove a bunch of superfluous locking

  Bug Fixes:
   - Ensure error codes are correctly propagated back up the call chain
   - Fix incorrect error values from being returned (missing '-')
   - Ensure get'ed resources are put'ed to prevent leaks
   - Use correct class when exporting module resources
   - Fixing rounding (or lack there of) issues
   - Fix 'always false' LED_COLOR_ID_MULTI BUG() check"

* tag 'leds-next-6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (40 commits)
  leds: aw2013: Enable pull-up supply for interrupt and I2C
  dt-bindings: leds: Document pull-up supply for interrupt and I2C
  dt-bindings: leds: aw2013: Document interrupt
  leds: uleds: Use module_misc_device macro to simplify the code
  leds: trigger: netdev: Use module_led_trigger macro to simplify the code
  dt-bindings: leds: Fix reference to definition of default-state
  leds: turris-omnia: Drop unnecessary mutex locking
  leds: turris-omnia: Use sysfs_emit() instead of sprintf()
  leds: Make leds_class a static const structure
  leds: Remove redundant of_match_ptr()
  dt-bindings: leds: Add gpio-line-names to PCA9532 GPIO
  leds: trigger: tty: Do not use LED_ON/OFF constants, use led_blink_set_oneshot instead
  dt-bindings: leds: rohm,bd71828: Drop select:false
  leds: Fix BUG_ON check for LED_COLOR_ID_MULTI that is always false
  leds: multicolor: Use rounded division when calculating color components
  leds: rgb: Add a multicolor LED driver to group monochromatic LEDs
  dt-bindings: leds: Add binding for a multicolor group of LEDs
  leds: class: Store the color index in struct led_classdev
  leds: Provide devm_of_led_get_optional()
  leds: pca995x: Fix MODULE_DEVICE_TABLE for OF
  ...
This commit is contained in:
Linus Torvalds 2023-09-04 13:52:58 -07:00
commit 2be6bc48df
54 changed files with 880 additions and 176 deletions

View File

@ -59,6 +59,15 @@ Description:
brightness. Reading this file when no hw brightness change
event has happened will return an ENODATA error.
What: /sys/class/leds/<led>/color
Date: June 2023
KernelVersion: 6.5
Description:
Color of the LED.
This is a read-only file. Reading this file returns the color
of the LED as a string (e.g: "red", "green", "multicolor").
What: /sys/class/leds/<led>/trigger
Date: March 2006
KernelVersion: 2.6.17

View File

@ -83,8 +83,7 @@ properties:
- enum:
# LED will act as a back-light, controlled by the framebuffer system
- backlight
# LED will turn on (but for leds-gpio see "default-state" property in
# Documentation/devicetree/bindings/leds/leds-gpio.yaml)
# LED will turn on (see also "default-state" property)
- default-on
# LED "double" flashes at a load average based rate
- heartbeat
@ -158,6 +157,18 @@ properties:
For flash LED controllers with configurable current this property is
mandatory for the LEDs in the non-flash modes (e.g. torch or indicator).
max-brightness:
description:
Normally, the maximum brightness is determined by the hardware, and this
property is not required. This property is used to set a software limit.
It could happen that an LED is made so bright that it gets damaged or
causes damage due to restrictions in a specific system, such as mounting
conditions.
Note that this flag is mainly used for PWM-LEDs, where it is not possible
to map brightness to current. Drivers for other controllers should use
led-max-microamp.
$ref: /schemas/types.yaml#definitions/uint32
panic-indicator:
description:
This property specifies that the LED should be used, if at all possible,

View File

@ -1,55 +0,0 @@
* Panasonic AN30259A 3-channel LED driver
The AN30259A is a LED controller capable of driving three LEDs independently. It supports
constant current output and sloping current output modes. The chip is connected over I2C.
Required properties:
- compatible: Must be "panasonic,an30259a".
- reg: I2C slave address.
- #address-cells: Must be 1.
- #size-cells: Must be 0.
Each LED is represented as a sub-node of the panasonic,an30259a node.
Required sub-node properties:
- reg: Pin that the LED is connected to. Must be 1, 2, or 3.
Optional sub-node properties:
- function :
see Documentation/devicetree/bindings/leds/common.txt
- color :
see Documentation/devicetree/bindings/leds/common.txt
- label :
see Documentation/devicetree/bindings/leds/common.txt (deprecated)
- linux,default-trigger :
see Documentation/devicetree/bindings/leds/common.txt
Example:
#include <dt-bindings/leds/common.h>
led-controller@30 {
compatible = "panasonic,an30259a";
reg = <0x30>;
#address-cells = <1>;
#size-cells = <0>;
led@1 {
reg = <1>;
linux,default-trigger = "heartbeat";
function = LED_FUNCTION_INDICATOR;
color = <LED_COLOR_ID_RED>;
};
led@2 {
reg = <2>;
function = LED_FUNCTION_INDICATOR;
color = <LED_COLOR_ID_GREEN>;
};
led@3 {
reg = <3>;
function = LED_FUNCTION_INDICATOR;
color = <LED_COLOR_ID_BLUE>;
};
};

View File

@ -20,9 +20,20 @@ properties:
reg:
maxItems: 1
interrupts:
maxItems: 1
description: Open-drain, low active interrupt pin "INTN".
Used to report completion of operations (power up, LED breath effects).
vcc-supply:
description: Regulator providing power to the "VCC" pin.
vio-supply:
description: Regulator providing power for pull-up of the I/O lines.
"VIO1" in the typical application circuit example of the datasheet.
Note that this regulator does not directly connect to AW2013, but is
needed for the correct operation of the interrupt and I2C lines.
"#address-cells":
const: 1
@ -52,6 +63,7 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
i2c {
@ -61,6 +73,7 @@ examples:
led-controller@45 {
compatible = "awinic,aw2013";
reg = <0x45>;
interrupts = <42 IRQ_TYPE_LEVEL_LOW>;
#address-cells = <1>;
#size-cells = <0>;

View File

@ -0,0 +1,64 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/leds-group-multicolor.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Multi-color LED built with monochromatic LEDs
maintainers:
- Jean-Jacques Hiblot <jjhiblot@traphandler.com>
description: |
This driver combines several monochromatic LEDs into one multi-color
LED using the multicolor LED class.
properties:
compatible:
const: leds-group-multicolor
leds:
description:
An aray of monochromatic leds
$ref: /schemas/types.yaml#/definitions/phandle-array
required:
- leds
allOf:
- $ref: leds-class-multicolor.yaml#
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
monochromatic-leds {
compatible = "gpio-leds";
led0: led-0 {
gpios = <&mcu_pio 0 GPIO_ACTIVE_LOW>;
color = <LED_COLOR_ID_RED>;
};
led1: led-1 {
gpios = <&mcu_pio 1 GPIO_ACTIVE_HIGH>;
color = <LED_COLOR_ID_GREEN>;
};
led2: led-2 {
gpios = <&mcu_pio 2 GPIO_ACTIVE_HIGH>;
color = <LED_COLOR_ID_BLUE>;
};
};
multi-led {
compatible = "leds-group-multicolor";
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
leds = <&led0>, <&led1>, <&led2>;
};
...

View File

@ -29,6 +29,10 @@ properties:
gpio-controller: true
gpio-line-names:
minItems: 1
maxItems: 16
'#gpio-cells':
const: 2

View File

@ -0,0 +1,81 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/nxp,pca995x.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: NXP PCA995x LED controllers
maintainers:
- Isai Gaspar <isaiezequiel.gaspar@nxp.com>
- Marek Vasut <marex@denx.de>
description:
The NXP PCA9952/PCA9955B are programmable LED controllers connected via I2C
that can drive 16 separate lines. Each of them can be individually switched
on and off, and brightness can be controlled via individual PWM.
Datasheets are available at
https://www.nxp.com/docs/en/data-sheet/PCA9952_PCA9955.pdf
https://www.nxp.com/docs/en/data-sheet/PCA9955B.pdf
properties:
compatible:
enum:
- nxp,pca9952
- nxp,pca9955b
reg:
maxItems: 1
"#address-cells":
const: 1
"#size-cells":
const: 0
patternProperties:
"^led@[0-9a-f]+$":
type: object
$ref: common.yaml#
unevaluatedProperties: false
properties:
reg:
minimum: 0
maximum: 15
required:
- reg
additionalProperties: false
examples:
- |
#include <dt-bindings/leds/common.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
led-controller@1 {
compatible = "nxp,pca9955b";
reg = <0x01>;
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0x0>;
color = <LED_COLOR_ID_RED>;
function = LED_FUNCTION_POWER;
};
led@2 {
reg = <0x2>;
color = <LED_COLOR_ID_WHITE>;
function = LED_FUNCTION_STATUS;
};
};
};
...

View File

@ -0,0 +1,84 @@
# SPDX-License-Identifier: GPL-2.0
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/panasonic,an30259a.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Panasonic AN30259A 3-channel LED controller
maintainers:
- Iskren Chernev <me@iskren.info>
description:
The AN30259A is a LED controller capable of driving three LEDs independently.
It supports constant current output and sloping current output modes. The chip
is connected over I2C.
properties:
compatible:
const: panasonic,an30259a
reg:
maxItems: 1
interrupts:
maxItems: 1
"#address-cells":
const: 1
"#size-cells":
const: 0
patternProperties:
"^led@[1-3]$":
$ref: common.yaml#
unevaluatedProperties: false
properties:
reg:
enum: [ 1, 2, 3 ]
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
additionalProperties: false
examples:
- |
#include <dt-bindings/leds/common.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
led-controller@30 {
compatible = "panasonic,an30259a";
reg = <0x30>;
#address-cells = <1>;
#size-cells = <0>;
led@1 {
reg = <1>;
linux,default-trigger = "heartbeat";
function = LED_FUNCTION_INDICATOR;
color = <LED_COLOR_ID_RED>;
};
led@2 {
reg = <2>;
function = LED_FUNCTION_INDICATOR;
color = <LED_COLOR_ID_GREEN>;
};
led@3 {
reg = <3>;
function = LED_FUNCTION_INDICATOR;
color = <LED_COLOR_ID_BLUE>;
};
};
};
...

View File

@ -35,7 +35,7 @@ properties:
description: GPIO pin to enable/disable the device.
patternProperties:
"^led@[0-6]$":
"^led@[0-5]$":
type: object
$ref: common.yaml#
unevaluatedProperties: false
@ -43,7 +43,7 @@ patternProperties:
properties:
reg:
minimum: 0
maximum: 6
maximum: 5
required:
- reg

View File

@ -18,8 +18,6 @@ description: |
The device has two LED outputs referred as GRNLED and AMBLED in data-sheet.
select: false
properties:
compatible:
const: rohm,bd71828-leds

View File

@ -521,6 +521,15 @@ config LEDS_PCA963X
LED driver chip accessed via the I2C bus. Supported
devices include PCA9633 and PCA9634
config LEDS_PCA995X
tristate "LED Support for PCA995x I2C chips"
depends on LEDS_CLASS
depends on I2C
help
This option enables support for LEDs connected to PCA995x
LED driver chips accessed via the I2C bus. Supported
devices include PCA9955BTW, PCA9952TW and PCA9955TW.
config LEDS_WM831X_STATUS
tristate "LED support for status LEDs on WM831x PMICs"
depends on LEDS_CLASS

View File

@ -72,6 +72,7 @@ obj-$(CONFIG_LEDS_OT200) += leds-ot200.o
obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o
obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.o
obj-$(CONFIG_LEDS_PCA995X) += leds-pca995x.o
obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o
obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
obj-$(CONFIG_LEDS_PWM) += leds-pwm.o

View File

@ -1,10 +1,10 @@
config LEDS_BCM63138
tristate "LED Support for Broadcom BCM63138 SoC"
depends on LEDS_CLASS
depends on ARCH_BCM4908 || ARCH_BCM_5301X || BCM63XX || COMPILE_TEST
depends on ARCH_BCMBCA || ARCH_BCM_5301X || BCM63XX || COMPILE_TEST
depends on HAS_IOMEM
depends on OF
default ARCH_BCM4908
default ARCH_BCMBCA
help
This option enables support for LED controller that is part of
BCM63138 SoC. The same hardware block is known to be also used

View File

@ -89,6 +89,8 @@ config LEDS_QCOM_FLASH
the total LED current will be split symmetrically on each channel and
they will be enabled/disabled at the same time.
This driver can be built as a module, it will be called "leds-qcom-flash".
config LEDS_RT4505
tristate "LED support for RT4505 flashlight controller"
depends on I2C && OF

View File

@ -309,6 +309,10 @@ static int qcom_flash_strobe_set(struct led_classdev_flash *fled_cdev, bool stat
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
int rc;
rc = set_flash_strobe(led, SW_STROBE, false);
if (rc)
return rc;
rc = set_flash_current(led, led->flash_current_ma, FLASH_MODE);
if (rc)
return rc;
@ -745,6 +749,7 @@ static int qcom_flash_led_probe(struct platform_device *pdev)
return 0;
release:
fwnode_handle_put(child);
while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count)
v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]);
return rc;

View File

@ -6,6 +6,7 @@
#include <linux/device.h>
#include <linux/init.h>
#include <linux/led-class-multicolor.h>
#include <linux/math.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
@ -19,9 +20,10 @@ int led_mc_calc_color_components(struct led_classdev_mc *mcled_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;
mcled_cdev->subled_info[i].brightness =
DIV_ROUND_CLOSEST(brightness *
mcled_cdev->subled_info[i].intensity,
led_cdev->max_brightness);
return 0;
}

View File

@ -22,7 +22,6 @@
#include <linux/of.h>
#include "leds.h"
static struct class *leds_class;
static DEFINE_MUTEX(leds_lookup_lock);
static LIST_HEAD(leds_lookup_list);
@ -76,6 +75,19 @@ static ssize_t max_brightness_show(struct device *dev,
}
static DEVICE_ATTR_RO(max_brightness);
static ssize_t color_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
const char *color_text = "invalid";
struct led_classdev *led_cdev = dev_get_drvdata(dev);
if (led_cdev->color < LED_COLOR_ID_MAX)
color_text = led_colors[led_cdev->color];
return sysfs_emit(buf, "%s\n", color_text);
}
static DEVICE_ATTR_RO(color);
#ifdef CONFIG_LEDS_TRIGGERS
static BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0);
static struct bin_attribute *led_trigger_bin_attrs[] = {
@ -90,6 +102,7 @@ static const struct attribute_group led_trigger_group = {
static struct attribute *led_class_attrs[] = {
&dev_attr_brightness.attr,
&dev_attr_max_brightness.attr,
&dev_attr_color.attr,
NULL,
};
@ -234,6 +247,12 @@ static struct led_classdev *led_module_get(struct device *led_dev)
return led_cdev;
}
static const struct class leds_class = {
.name = "leds",
.dev_groups = led_groups,
.pm = &leds_class_dev_pm_ops,
};
/**
* of_led_get() - request a LED device via the LED framework
* @np: device node to get the LED device from
@ -251,7 +270,7 @@ struct led_classdev *of_led_get(struct device_node *np, int index)
if (!led_node)
return ERR_PTR(-ENOENT);
led_dev = class_find_device_by_of_node(leds_class, led_node);
led_dev = class_find_device_by_of_node(&leds_class, led_node);
of_node_put(led_node);
put_device(led_dev);
@ -346,7 +365,7 @@ struct led_classdev *led_get(struct device *dev, char *con_id)
if (!provider)
return ERR_PTR(-ENOENT);
led_dev = class_find_device_by_name(leds_class, provider);
led_dev = class_find_device_by_name(&leds_class, provider);
kfree_const(provider);
return led_module_get(led_dev);
@ -402,6 +421,31 @@ void led_remove_lookup(struct led_lookup_data *led_lookup)
}
EXPORT_SYMBOL_GPL(led_remove_lookup);
/**
* devm_of_led_get_optional - Resource-managed request of an optional LED device
* @dev: LED consumer
* @index: index of the LED to obtain in the consumer
*
* The device node of the device is parsed to find the requested LED device.
* The LED device returned from this function is automatically released
* on driver detach.
*
* @return a pointer to a LED device, ERR_PTR(errno) on failure and NULL if the
* led was not found.
*/
struct led_classdev *__must_check devm_of_led_get_optional(struct device *dev,
int index)
{
struct led_classdev *led;
led = devm_of_led_get(dev, index);
if (IS_ERR(led) && PTR_ERR(led) == -ENOENT)
return NULL;
return led;
}
EXPORT_SYMBOL_GPL(devm_of_led_get_optional);
static int led_classdev_next_name(const char *init_name, char *name,
size_t len)
{
@ -412,7 +456,7 @@ static int led_classdev_next_name(const char *init_name, char *name,
strscpy(name, init_name, len);
while ((ret < len) &&
(dev = class_find_device_by_name(leds_class, name))) {
(dev = class_find_device_by_name(&leds_class, name))) {
put_device(dev);
ret = snprintf(name, len, "%s_%u", init_name, ++i);
}
@ -457,6 +501,14 @@ int led_classdev_register_ext(struct device *parent,
if (fwnode_property_present(init_data->fwnode,
"retain-state-shutdown"))
led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN;
fwnode_property_read_u32(init_data->fwnode,
"max-brightness",
&led_cdev->max_brightness);
if (fwnode_property_present(init_data->fwnode, "color"))
fwnode_property_read_u32(init_data->fwnode, "color",
&led_cdev->color);
}
} else {
proposed_name = led_cdev->name;
@ -466,10 +518,13 @@ int led_classdev_register_ext(struct device *parent,
if (ret < 0)
return ret;
if (led_cdev->color >= LED_COLOR_ID_MAX)
dev_warn(parent, "LED %s color identifier out of range\n", final_name);
mutex_init(&led_cdev->led_access);
mutex_lock(&led_cdev->led_access);
led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
led_cdev, led_cdev->groups, "%s", final_name);
led_cdev->dev = device_create_with_groups(&leds_class, parent, 0,
led_cdev, led_cdev->groups, "%s", final_name);
if (IS_ERR(led_cdev->dev)) {
mutex_unlock(&led_cdev->led_access);
return PTR_ERR(led_cdev->dev);
@ -626,17 +681,12 @@ EXPORT_SYMBOL_GPL(devm_led_classdev_unregister);
static int __init leds_init(void)
{
leds_class = class_create("leds");
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
leds_class->pm = &leds_class_dev_pm_ops;
leds_class->dev_groups = led_groups;
return 0;
return class_register(&leds_class);
}
static void __exit leds_exit(void)
{
class_destroy(leds_class);
class_unregister(&leds_class);
}
subsys_initcall(leds_init);

View File

@ -474,15 +474,15 @@ 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;
led_parse_fwnode_props(dev, fwnode, &props);
/* 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 (props.label) {
/*
* If init_data.devicename is NULL, then it indicates that

View File

@ -344,7 +344,7 @@ MODULE_DEVICE_TABLE(i2c, an30259a_id);
static struct i2c_driver an30259a_driver = {
.driver = {
.name = "leds-an30259a",
.of_match_table = of_match_ptr(an30259a_match_table),
.of_match_table = an30259a_match_table,
},
.probe = an30259a_probe,
.remove = an30259a_remove,

View File

@ -7,8 +7,8 @@
#include <linux/module.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/of_platform.h>
enum ec_index {
EC_BLUE_LED = 0x01,

View File

@ -368,7 +368,7 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
if (!chip->display_rows ||
chip->display_rows > chip->cdef->display_size_rows_max) {
return dev_err_probe(dev, ret,
return dev_err_probe(dev, -EINVAL,
"Invalid leds display size %u\n",
chip->display_rows);
}
@ -583,7 +583,7 @@ static struct i2c_driver aw200xx_driver = {
.name = "aw200xx",
.of_match_table = aw200xx_match_table,
},
.probe_new = aw200xx_probe,
.probe = aw200xx_probe,
.remove = aw200xx_remove,
.id_table = aw200xx_id,
};

View File

@ -62,7 +62,7 @@ struct aw2013_led {
struct aw2013 {
struct mutex mutex; /* held when writing to registers */
struct regulator *vcc_regulator;
struct regulator_bulk_data regulators[2];
struct i2c_client *client;
struct aw2013_led leds[AW2013_MAX_LEDS];
struct regmap *regmap;
@ -106,10 +106,11 @@ static void aw2013_chip_disable(struct aw2013 *chip)
regmap_write(chip->regmap, AW2013_GCR, 0);
ret = regulator_disable(chip->vcc_regulator);
ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators),
chip->regulators);
if (ret) {
dev_err(&chip->client->dev,
"Failed to disable regulator: %d\n", ret);
"Failed to disable regulators: %d\n", ret);
return;
}
@ -123,10 +124,11 @@ static int aw2013_chip_enable(struct aw2013 *chip)
if (chip->enabled)
return 0;
ret = regulator_enable(chip->vcc_regulator);
ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators),
chip->regulators);
if (ret) {
dev_err(&chip->client->dev,
"Failed to enable regulator: %d\n", ret);
"Failed to enable regulators: %d\n", ret);
return ret;
}
chip->enabled = true;
@ -348,19 +350,23 @@ static int aw2013_probe(struct i2c_client *client)
goto error;
}
chip->vcc_regulator = devm_regulator_get(&client->dev, "vcc");
ret = PTR_ERR_OR_ZERO(chip->vcc_regulator);
if (ret) {
chip->regulators[0].supply = "vcc";
chip->regulators[1].supply = "vio";
ret = devm_regulator_bulk_get(&client->dev,
ARRAY_SIZE(chip->regulators),
chip->regulators);
if (ret < 0) {
if (ret != -EPROBE_DEFER)
dev_err(&client->dev,
"Failed to request regulator: %d\n", ret);
"Failed to request regulators: %d\n", ret);
goto error;
}
ret = regulator_enable(chip->vcc_regulator);
ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators),
chip->regulators);
if (ret) {
dev_err(&client->dev,
"Failed to enable regulator: %d\n", ret);
"Failed to enable regulators: %d\n", ret);
goto error;
}
@ -382,10 +388,11 @@ static int aw2013_probe(struct i2c_client *client)
if (ret < 0)
goto error_reg;
ret = regulator_disable(chip->vcc_regulator);
ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators),
chip->regulators);
if (ret) {
dev_err(&client->dev,
"Failed to disable regulator: %d\n", ret);
"Failed to disable regulators: %d\n", ret);
goto error;
}
@ -394,7 +401,8 @@ static int aw2013_probe(struct i2c_client *client)
return 0;
error_reg:
regulator_disable(chip->vcc_regulator);
regulator_bulk_disable(ARRAY_SIZE(chip->regulators),
chip->regulators);
error:
mutex_destroy(&chip->mutex);
@ -420,7 +428,7 @@ MODULE_DEVICE_TABLE(of, aw2013_match_table);
static struct i2c_driver aw2013_driver = {
.driver = {
.name = "leds-aw2013",
.of_match_table = of_match_ptr(aw2013_match_table),
.of_match_table = aw2013_match_table,
},
.probe = aw2013_probe,
.remove = aw2013_remove,

View File

@ -7,7 +7,7 @@
#include <linux/mfd/motorola-cpcap.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>

View File

@ -4,8 +4,8 @@
#include <linux/delay.h>
#include <linux/leds.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/spi/spi.h>
#include <linux/workqueue.h>

View File

@ -27,22 +27,16 @@ static void ip30led_set(struct led_classdev *led_cdev,
static int ip30led_create(struct platform_device *pdev, int num)
{
struct resource *res;
struct ip30_led *data;
res = platform_get_resource(pdev, IORESOURCE_MEM, num);
if (!res)
return -EBUSY;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->reg = devm_ioremap_resource(&pdev->dev, res);
data->reg = devm_platform_ioremap_resource(pdev, num);
if (IS_ERR(data->reg))
return PTR_ERR(data->reg);
switch (num) {
case IP30_LED_SYSTEM:
data->cdev.name = "white:power";

View File

@ -15,7 +15,6 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
/* Used to indicate a device has no such register */
#define IS31FL32XX_REG_NONE 0xFF

View File

@ -594,18 +594,17 @@ static const struct i2c_device_id lp5521_id[] = {
};
MODULE_DEVICE_TABLE(i2c, lp5521_id);
#ifdef CONFIG_OF
static const struct of_device_id of_lp5521_leds_match[] = {
{ .compatible = "national,lp5521", },
{},
};
MODULE_DEVICE_TABLE(of, of_lp5521_leds_match);
#endif
static struct i2c_driver lp5521_driver = {
.driver = {
.name = "lp5521",
.of_match_table = of_match_ptr(of_lp5521_leds_match),
.of_match_table = of_lp5521_leds_match,
},
.probe = lp5521_probe,
.remove = lp5521_remove,

View File

@ -972,7 +972,6 @@ static const struct i2c_device_id lp5523_id[] = {
MODULE_DEVICE_TABLE(i2c, lp5523_id);
#ifdef CONFIG_OF
static const struct of_device_id of_lp5523_leds_match[] = {
{ .compatible = "national,lp5523", },
{ .compatible = "ti,lp55231", },
@ -980,12 +979,11 @@ static const struct of_device_id of_lp5523_leds_match[] = {
};
MODULE_DEVICE_TABLE(of, of_lp5523_leds_match);
#endif
static struct i2c_driver lp5523_driver = {
.driver = {
.name = "lp5523x",
.of_match_table = of_match_ptr(of_lp5523_leds_match),
.of_match_table = of_lp5523_leds_match,
},
.probe = lp5523_probe,
.remove = lp5523_remove,

View File

@ -589,19 +589,17 @@ static const struct i2c_device_id lp5562_id[] = {
};
MODULE_DEVICE_TABLE(i2c, lp5562_id);
#ifdef CONFIG_OF
static const struct of_device_id of_lp5562_leds_match[] = {
{ .compatible = "ti,lp5562", },
{},
};
MODULE_DEVICE_TABLE(of, of_lp5562_leds_match);
#endif
static struct i2c_driver lp5562_driver = {
.driver = {
.name = "lp5562",
.of_match_table = of_match_ptr(of_lp5562_leds_match),
.of_match_table = of_lp5562_leds_match,
},
.probe = lp5562_probe,
.remove = lp5562_remove,

View File

@ -380,19 +380,17 @@ static const struct i2c_device_id lp8501_id[] = {
};
MODULE_DEVICE_TABLE(i2c, lp8501_id);
#ifdef CONFIG_OF
static const struct of_device_id of_lp8501_leds_match[] = {
{ .compatible = "ti,lp8501", },
{},
};
MODULE_DEVICE_TABLE(of, of_lp8501_leds_match);
#endif
static struct i2c_driver lp8501_driver = {
.driver = {
.name = "lp8501",
.of_match_table = of_match_ptr(of_lp8501_leds_match),
.of_match_table = of_lp8501_leds_match,
},
.probe = lp8501_probe,
.remove = lp8501_remove,

View File

@ -8,7 +8,6 @@
#include <linux/io.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_data/mlxreg.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>

View File

@ -247,7 +247,7 @@ static int ns2_led_probe(struct platform_device *pdev)
if (!count)
return -ENODEV;
leds = devm_kzalloc(dev, array_size(sizeof(*leds), count), GFP_KERNEL);
leds = devm_kcalloc(dev, count, sizeof(*leds), GFP_KERNEL);
if (!leds)
return -ENOMEM;

View File

@ -18,7 +18,6 @@
#include <linux/leds-pca9532.h>
#include <linux/gpio/driver.h>
#include <linux/of.h>
#include <linux/of_device.h>
/* m = num_leds*/
#define PCA9532_REG_INPUT(i) ((i) >> 3)

204
drivers/leds/leds-pca995x.c Normal file
View File

@ -0,0 +1,204 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* LED driver for PCA995x I2C LED drivers
*
* Copyright 2011 bct electronic GmbH
* Copyright 2013 Qtechnology/AS
* Copyright 2022 NXP
* Copyright 2023 Marek Vasut
*/
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/property.h>
#include <linux/regmap.h>
/* Register definition */
#define PCA995X_MODE1 0x00
#define PCA995X_MODE2 0x01
#define PCA995X_LEDOUT0 0x02
#define PCA9955B_PWM0 0x08
#define PCA9952_PWM0 0x0A
#define PCA9952_IREFALL 0x43
#define PCA9955B_IREFALL 0x45
/* Auto-increment disabled. Normal mode */
#define PCA995X_MODE1_CFG 0x00
/* LED select registers determine the source that drives LED outputs */
#define PCA995X_LED_OFF 0x0
#define PCA995X_LED_ON 0x1
#define PCA995X_LED_PWM_MODE 0x2
#define PCA995X_LDRX_MASK 0x3
#define PCA995X_LDRX_BITS 2
#define PCA995X_MAX_OUTPUTS 16
#define PCA995X_OUTPUTS_PER_REG 4
#define PCA995X_IREFALL_FULL_CFG 0xFF
#define PCA995X_IREFALL_HALF_CFG (PCA995X_IREFALL_FULL_CFG / 2)
#define PCA995X_TYPE_NON_B 0
#define PCA995X_TYPE_B 1
#define ldev_to_led(c) container_of(c, struct pca995x_led, ldev)
struct pca995x_led {
unsigned int led_no;
struct led_classdev ldev;
struct pca995x_chip *chip;
};
struct pca995x_chip {
struct regmap *regmap;
struct pca995x_led leds[PCA995X_MAX_OUTPUTS];
int btype;
};
static int pca995x_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct pca995x_led *led = ldev_to_led(led_cdev);
struct pca995x_chip *chip = led->chip;
u8 ledout_addr, pwmout_addr;
int shift, ret;
pwmout_addr = (chip->btype ? PCA9955B_PWM0 : PCA9952_PWM0) + led->led_no;
ledout_addr = PCA995X_LEDOUT0 + (led->led_no / PCA995X_OUTPUTS_PER_REG);
shift = PCA995X_LDRX_BITS * (led->led_no % PCA995X_OUTPUTS_PER_REG);
switch (brightness) {
case LED_FULL:
return regmap_update_bits(chip->regmap, ledout_addr,
PCA995X_LDRX_MASK << shift,
PCA995X_LED_ON << shift);
case LED_OFF:
return regmap_update_bits(chip->regmap, ledout_addr,
PCA995X_LDRX_MASK << shift, 0);
default:
/* Adjust brightness as per user input by changing individual PWM */
ret = regmap_write(chip->regmap, pwmout_addr, brightness);
if (ret)
return ret;
/*
* Change LDRx configuration to individual brightness via PWM.
* LED will stop blinking if it's doing so.
*/
return regmap_update_bits(chip->regmap, ledout_addr,
PCA995X_LDRX_MASK << shift,
PCA995X_LED_PWM_MODE << shift);
}
}
static const struct regmap_config pca995x_regmap = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0x49,
};
static int pca995x_probe(struct i2c_client *client)
{
struct fwnode_handle *led_fwnodes[PCA995X_MAX_OUTPUTS] = { 0 };
struct fwnode_handle *np, *child;
struct device *dev = &client->dev;
struct pca995x_chip *chip;
struct pca995x_led *led;
int i, btype, reg, ret;
btype = (unsigned long)device_get_match_data(&client->dev);
np = dev_fwnode(dev);
if (!np)
return -ENODEV;
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->btype = btype;
chip->regmap = devm_regmap_init_i2c(client, &pca995x_regmap);
if (IS_ERR(chip->regmap))
return PTR_ERR(chip->regmap);
i2c_set_clientdata(client, chip);
fwnode_for_each_available_child_node(np, child) {
ret = fwnode_property_read_u32(child, "reg", &reg);
if (ret) {
fwnode_handle_put(child);
return ret;
}
if (reg < 0 || reg >= PCA995X_MAX_OUTPUTS || led_fwnodes[reg]) {
fwnode_handle_put(child);
return -EINVAL;
}
led = &chip->leds[reg];
led_fwnodes[reg] = child;
led->chip = chip;
led->led_no = reg;
led->ldev.brightness_set_blocking = pca995x_brightness_set;
led->ldev.max_brightness = 255;
}
for (i = 0; i < PCA995X_MAX_OUTPUTS; i++) {
struct led_init_data init_data = {};
if (!led_fwnodes[i])
continue;
init_data.fwnode = led_fwnodes[i];
ret = devm_led_classdev_register_ext(dev,
&chip->leds[i].ldev,
&init_data);
if (ret < 0) {
fwnode_handle_put(child);
return dev_err_probe(dev, ret,
"Could not register LED %s\n",
chip->leds[i].ldev.name);
}
}
/* Disable LED all-call address and set normal mode */
ret = regmap_write(chip->regmap, PCA995X_MODE1, PCA995X_MODE1_CFG);
if (ret)
return ret;
/* IREF Output current value for all LEDn outputs */
return regmap_write(chip->regmap,
btype ? PCA9955B_IREFALL : PCA9952_IREFALL,
PCA995X_IREFALL_HALF_CFG);
}
static const struct i2c_device_id pca995x_id[] = {
{ "pca9952", .driver_data = (kernel_ulong_t)PCA995X_TYPE_NON_B },
{ "pca9955b", .driver_data = (kernel_ulong_t)PCA995X_TYPE_B },
{}
};
MODULE_DEVICE_TABLE(i2c, pca995x_id);
static const struct of_device_id pca995x_of_match[] = {
{ .compatible = "nxp,pca9952", .data = (void *)PCA995X_TYPE_NON_B },
{ .compatible = "nxp,pca9955b", .data = (void *)PCA995X_TYPE_B },
{},
};
MODULE_DEVICE_TABLE(of, pca995x_of_match);
static struct i2c_driver pca995x_driver = {
.driver = {
.name = "leds-pca995x",
.of_match_table = pca995x_of_match,
},
.probe = pca995x_probe,
.id_table = pca995x_id,
};
module_i2c_driver(pca995x_driver);
MODULE_AUTHOR("Isai Gaspar <isaiezequiel.gaspar@nxp.com>");
MODULE_DESCRIPTION("PCA995x LED driver");
MODULE_LICENSE("GPL");

View File

@ -4,7 +4,6 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/regmap.h>

View File

@ -12,7 +12,7 @@
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/of.h>
#include <linux/leds.h>
#include <linux/err.h>
#include <linux/pwm.h>
@ -146,7 +146,7 @@ static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv)
led.name = to_of_node(fwnode)->name;
if (!led.name) {
ret = EINVAL;
ret = -EINVAL;
goto err_child_out;
}

View File

@ -30,7 +30,7 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of.h>
#include <linux/spi/spi.h>
#include <linux/mutex.h>
#include <uapi/linux/uleds.h>

View File

@ -7,8 +7,7 @@
*/
#include <linux/io.h>
#include <linux/init.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/stat.h>
#include <linux/slab.h>

View File

@ -7,7 +7,7 @@
#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/of_device.h>
#include <linux/property.h>
#include <linux/leds-ti-lmu-common.h>

View File

@ -8,7 +8,6 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>

View File

@ -156,24 +156,20 @@ 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);
return sysfs_emit(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 long brightness;
int ret;
@ -183,15 +179,10 @@ static ssize_t brightness_store(struct device *dev, struct device_attribute *a,
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;
return ret < 0 ? ret : count;
}
static DEVICE_ATTR_RW(brightness);

View File

@ -2,6 +2,18 @@
if LEDS_CLASS_MULTICOLOR
config LEDS_GROUP_MULTICOLOR
tristate "LEDs group multi-color support"
depends on OF || COMPILE_TEST
help
This option enables support for monochrome LEDs that are grouped
into multicolor LEDs which is useful in the case where LEDs of
different colors are physically grouped in a single multi-color LED
and driven by a controller that doesn't have multi-color support.
To compile this driver as a module, choose M here: the module
will be called leds-group-multicolor.
config LEDS_PWM_MULTICOLOR
tristate "PWM driven multi-color LED Support"
depends on PWM

View File

@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o
obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o
obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o
obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o

View File

@ -0,0 +1,169 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Multi-color LED built with monochromatic LED devices
*
* This driver groups several monochromatic LED devices in a single multicolor LED device.
*
* Compared to handling this grouping in user-space, the benefits are:
* - The state of the monochromatic LED relative to each other is always consistent.
* - The sysfs interface of the LEDs can be used for the group as a whole.
*
* Copyright 2023 Jean-Jacques Hiblot <jjhiblot@traphandler.com>
*/
#include <linux/err.h>
#include <linux/leds.h>
#include <linux/led-class-multicolor.h>
#include <linux/math.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/property.h>
struct leds_multicolor {
struct led_classdev_mc mc_cdev;
struct led_classdev **monochromatics;
};
static int leds_gmc_set(struct led_classdev *cdev, enum led_brightness brightness)
{
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
struct leds_multicolor *priv = container_of(mc_cdev, struct leds_multicolor, mc_cdev);
const unsigned int group_max_brightness = mc_cdev->led_cdev.max_brightness;
int i;
for (i = 0; i < mc_cdev->num_colors; i++) {
struct led_classdev *mono = priv->monochromatics[i];
const unsigned int mono_max_brightness = mono->max_brightness;
unsigned int intensity = mc_cdev->subled_info[i].intensity;
int mono_brightness;
/*
* Scale the brightness according to relative intensity of the
* color AND the max brightness of the monochromatic LED.
*/
mono_brightness = DIV_ROUND_CLOSEST(brightness * intensity * mono_max_brightness,
group_max_brightness * group_max_brightness);
led_set_brightness(mono, mono_brightness);
}
return 0;
}
static void restore_sysfs_write_access(void *data)
{
struct led_classdev *led_cdev = data;
/* Restore the write acccess to the LED */
mutex_lock(&led_cdev->led_access);
led_sysfs_enable(led_cdev);
mutex_unlock(&led_cdev->led_access);
}
static int leds_gmc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct led_init_data init_data = {};
struct led_classdev *cdev;
struct mc_subled *subled;
struct leds_multicolor *priv;
unsigned int max_brightness = 0;
int i, ret, count = 0;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
for (;;) {
struct led_classdev *led_cdev;
led_cdev = devm_of_led_get_optional(dev, count);
if (IS_ERR(led_cdev))
return dev_err_probe(dev, PTR_ERR(led_cdev), "Unable to get LED #%d",
count);
if (!led_cdev)
break;
priv->monochromatics = devm_krealloc_array(dev, priv->monochromatics,
count + 1, sizeof(*priv->monochromatics),
GFP_KERNEL);
if (!priv->monochromatics)
return -ENOMEM;
priv->monochromatics[count] = led_cdev;
max_brightness = max(max_brightness, led_cdev->max_brightness);
count++;
}
subled = devm_kcalloc(dev, count, sizeof(*subled), GFP_KERNEL);
if (!subled)
return -ENOMEM;
priv->mc_cdev.subled_info = subled;
for (i = 0; i < count; i++) {
struct led_classdev *led_cdev = priv->monochromatics[i];
subled[i].color_index = led_cdev->color;
/* Configure the LED intensity to its maximum */
subled[i].intensity = max_brightness;
}
/* Initialise the multicolor's LED class device */
cdev = &priv->mc_cdev.led_cdev;
cdev->flags = LED_CORE_SUSPENDRESUME;
cdev->brightness_set_blocking = leds_gmc_set;
cdev->max_brightness = max_brightness;
cdev->color = LED_COLOR_ID_MULTI;
priv->mc_cdev.num_colors = count;
init_data.fwnode = dev_fwnode(dev);
ret = devm_led_classdev_multicolor_register_ext(dev, &priv->mc_cdev, &init_data);
if (ret)
return dev_err_probe(dev, ret, "failed to register multicolor LED for %s.\n",
cdev->name);
ret = leds_gmc_set(cdev, cdev->brightness);
if (ret)
return dev_err_probe(dev, ret, "failed to set LED value for %s.", cdev->name);
for (i = 0; i < count; i++) {
struct led_classdev *led_cdev = priv->monochromatics[i];
/*
* Make the individual LED sysfs interface read-only to prevent the user
* to change the brightness of the individual LEDs of the group.
*/
mutex_lock(&led_cdev->led_access);
led_sysfs_disable(led_cdev);
mutex_unlock(&led_cdev->led_access);
/* Restore the write access to the LED sysfs when the group is destroyed */
devm_add_action_or_reset(dev, restore_sysfs_write_access, led_cdev);
}
return 0;
}
static const struct of_device_id of_leds_group_multicolor_match[] = {
{ .compatible = "leds-group-multicolor" },
{}
};
MODULE_DEVICE_TABLE(of, of_leds_group_multicolor_match);
static struct platform_driver leds_group_multicolor_driver = {
.probe = leds_gmc_probe,
.driver = {
.name = "leds_group_multicolor",
.of_match_table = of_leds_group_multicolor_match,
}
};
module_platform_driver(leds_group_multicolor_driver);
MODULE_AUTHOR("Jean-Jacques Hiblot <jjhiblot@traphandler.com>");
MODULE_DESCRIPTION("LEDs group multicolor driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:leds-group-multicolor");

View File

@ -9,7 +9,6 @@
#include <linux/led-class-multicolor.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
@ -1093,7 +1092,6 @@ static int lpg_add_pwm(struct lpg *lpg)
{
int ret;
lpg->pwm.base = -1;
lpg->pwm.dev = lpg->dev;
lpg->pwm.npwm = lpg->num_channels;
lpg->pwm.ops = &lpg_pwm_ops;

View File

@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
config LEDS_SIEMENS_SIMATIC_IPC
tristate "LED driver for Siemens Simatic IPCs"
depends on LEDS_CLASS
depends on SIEMENS_SIMATIC_IPC
default y
help
@ -35,3 +36,16 @@ config LEDS_SIEMENS_SIMATIC_IPC_F7188X
To compile this driver as a module, choose M here: the module
will be called simatic-ipc-leds-gpio-f7188x.
config LEDS_SIEMENS_SIMATIC_IPC_ELKHARTLAKE
tristate "LED driver for Siemens Simatic IPCs based on Intel Elkhart Lake GPIO"
depends on LEDS_GPIO
depends on PINCTRL_ELKHARTLAKE
depends on SIEMENS_SIMATIC_IPC
default LEDS_SIEMENS_SIMATIC_IPC
help
This option enables support for the LEDs of several Industrial PCs
from Siemens based on Elkhart Lake GPIO i.e. BX-21A.
To compile this driver as a module, choose M here: the module
will be called simatic-ipc-leds-gpio-elkhartlake.

View File

@ -2,3 +2,4 @@
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds.o
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_APOLLOLAKE) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-apollolake.o
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_F7188X) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-f7188x.o
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_ELKHARTLAKE) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-elkhartlake.o

View File

@ -57,6 +57,7 @@ int simatic_ipc_leds_gpio_probe(struct platform_device *pdev,
switch (plat->devmode) {
case SIMATIC_IPC_DEVICE_127E:
case SIMATIC_IPC_DEVICE_227G:
case SIMATIC_IPC_DEVICE_BX_21A:
break;
default:
return -ENODEV;
@ -72,6 +73,9 @@ int simatic_ipc_leds_gpio_probe(struct platform_device *pdev,
goto out;
}
if (!table_extra)
return 0;
table_extra->dev_id = dev_name(dev);
gpiod_add_lookup_table(table_extra);

View File

@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Siemens SIMATIC IPC driver for GPIO based LEDs
*
* Copyright (c) Siemens AG, 2023
*
* Author:
* Henning Schild <henning.schild@siemens.com>
*/
#include <linux/gpio/machine.h>
#include <linux/gpio/consumer.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_data/x86/simatic-ipc-base.h>
#include "simatic-ipc-leds-gpio.h"
static struct gpiod_lookup_table simatic_ipc_led_gpio_table = {
.dev_id = "leds-gpio",
.table = {
GPIO_LOOKUP_IDX("INTC1020:04", 72, NULL, 0, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX("INTC1020:04", 77, NULL, 1, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX("INTC1020:04", 78, NULL, 2, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX("INTC1020:04", 58, NULL, 3, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX("INTC1020:04", 60, NULL, 4, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX("INTC1020:04", 62, NULL, 5, GPIO_ACTIVE_HIGH),
{} /* Terminating entry */
},
};
static int simatic_ipc_leds_gpio_elkhartlake_probe(struct platform_device *pdev)
{
return simatic_ipc_leds_gpio_probe(pdev, &simatic_ipc_led_gpio_table,
NULL);
}
static int simatic_ipc_leds_gpio_elkhartlake_remove(struct platform_device *pdev)
{
return simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table,
NULL);
}
static struct platform_driver simatic_ipc_led_gpio_elkhartlake_driver = {
.probe = simatic_ipc_leds_gpio_elkhartlake_probe,
.remove = simatic_ipc_leds_gpio_elkhartlake_remove,
.driver = {
.name = KBUILD_MODNAME,
},
};
module_platform_driver(simatic_ipc_led_gpio_elkhartlake_driver);
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core platform:elkhartlake-pinctrl");
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Siemens SIMATIC IPC driver for GPIO based LEDs
*

View File

@ -609,18 +609,7 @@ static struct led_trigger netdev_led_trigger = {
.groups = netdev_trig_groups,
};
static int __init netdev_trig_init(void)
{
return led_trigger_register(&netdev_led_trigger);
}
static void __exit netdev_trig_exit(void)
{
led_trigger_unregister(&netdev_led_trigger);
}
module_init(netdev_trig_init);
module_exit(netdev_trig_exit);
module_led_trigger(netdev_led_trigger);
MODULE_AUTHOR("Ben Whitten <ben.whitten@gmail.com>");
MODULE_AUTHOR("Oliver Jowett <oliver@opencloud.com>");

View File

@ -7,6 +7,8 @@
#include <linux/tty.h>
#include <uapi/linux/serial.h>
#define LEDTRIG_TTY_INTERVAL 50
struct ledtrig_tty_data {
struct led_classdev *led_cdev;
struct delayed_work dwork;
@ -122,17 +124,19 @@ static void ledtrig_tty_work(struct work_struct *work)
if (icount.rx != trigger_data->rx ||
icount.tx != trigger_data->tx) {
led_set_brightness_sync(trigger_data->led_cdev, LED_ON);
unsigned long interval = LEDTRIG_TTY_INTERVAL;
led_blink_set_oneshot(trigger_data->led_cdev, &interval,
&interval, 0);
trigger_data->rx = icount.rx;
trigger_data->tx = icount.tx;
} else {
led_set_brightness_sync(trigger_data->led_cdev, LED_OFF);
}
out:
mutex_unlock(&trigger_data->mutex);
schedule_delayed_work(&trigger_data->dwork, msecs_to_jiffies(100));
schedule_delayed_work(&trigger_data->dwork,
msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2));
}
static struct attribute *ledtrig_tty_attrs[] = {

View File

@ -209,17 +209,7 @@ static struct miscdevice uleds_misc = {
.name = ULEDS_NAME,
};
static int __init uleds_init(void)
{
return misc_register(&uleds_misc);
}
module_init(uleds_init);
static void __exit uleds_exit(void)
{
misc_deregister(&uleds_misc);
}
module_exit(uleds_exit);
module_misc_device(uleds_misc);
MODULE_AUTHOR("David Lechner <david@lechnology.com>");
MODULE_DESCRIPTION("Userspace driver for the LED subsystem");

View File

@ -100,6 +100,7 @@ struct led_classdev {
const char *name;
unsigned int brightness;
unsigned int max_brightness;
unsigned int color;
int flags;
/* Lower 16 bits reflect status */
@ -313,6 +314,8 @@ extern struct led_classdev *of_led_get(struct device_node *np, int index);
extern void led_put(struct led_classdev *led_cdev);
struct led_classdev *__must_check devm_of_led_get(struct device *dev,
int index);
struct led_classdev *__must_check devm_of_led_get_optional(struct device *dev,
int index);
/**
* led_blink_set - set blinking with software fallback