- New Drivers

- Add support for Allwinner A100 RGB LED controller
    - Add support for Maxim 5970 Dual Hot-swap controller
 
  - New Device Support
    - Add support for AW20108 to Awinic LED driver
 
  - New Functionality
    - Extend support for Net speeds to include; 2.5G, 5G and 10G
    - Allow tx/rx and cts/dsr/dcd/rng TTY LEDS to be turned on and off via sysfs if required
    - Add support for hardware control in AW200xx
 
  - Fix-ups
    - Use safer methods for string handling
    - Improve error handling; return proper error values, simplify, avoid duplicates, etc
    - Replace Mutex use with the Completion mechanism
    - Fix include lists; alphabetise, remove unused, explicitly add used
    - Use generic platform device properties
    - Use/convert to new/better APIs/helpers/MACROs instead of hand-rolling implementations
    - Device Tree binding adaptions/conversions/creation
    - Continue work to remove superfluous platform .remove() call-backs
    - Remove superfluous/defunct code
    - Trivial; whitespace, unused variables, spelling, clean-ups, etc
    - Avoid unnecessary duplicate locks
 
  - Bug Fixes
    - Repair Kconfig based dependency lists
    - Ensure unused dynamically allocated data is freed after use
    - Fix support for brightness control
    - Add missing sufficient delays during reset to ensure correct operation
    - Avoid division-by-zero issues
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmWmsRkACgkQUa+KL4f8
 d2HjTQ/8DKBBDEJQLX1R9GN3W+F1RwenAyeuLcaBzIR1eAcw2CV6bb686CO+WxIn
 pgZE33PiB1VR2Y571dUmj1oAJ8QMRsGed0bDzjNHO592ANHbGX/kxRlLsvcYqxE5
 zAe0W93qn5ZEHRek6bJ55fsCuwRt1S/sPK/UDRb1MtJNQ51mh1ErhKk9rO0GkaDz
 OtOeOwIqwNIDBqmYs8IAgfFolzBgnCMBnAW7EGA6hJjc2lWHHr+T8flT7rEPPcxD
 s3ZT/m2jg0bAwWzFYWYxweyJ50NnP1xe7ABSqLi2jTcFkOKyYa/wvuL8GINXOSvM
 9OVXPQ4MwiPTCPOhWex0WJ2/s0g2L5rL8gz+GBNVRppn53rYY0GwyXuEjmznYSrp
 X4T8C1wRUMXQeBTNyoDxDid3oGoObGfyzIfI/aPOpqRHmeGWsbBITztCXgBEQcbs
 k5WuiLzqYpLdTcjE0TJ4WTsR98zoY0yVwF5PFtTBcFTWz1QGmXujAa5gAIGJPhx6
 fVovV0aih8hoZOq2xmCYRuR47rwH/QjfHcYZbhGC4YOPPA6Hh6j+eS9+1IpaWdLs
 gUtXpU/pIWKUn0FVmtvK83MJ6VbJy5QHpQi7nf06ADNlDt0IoMTJAoMYsiKzqgeG
 3L+sAd/DYcuS7Eyf5DP9SY/rqSsamsdSJpaSynP1Rm8Cyqka/Qg=
 =/f98
 -----END PGP SIGNATURE-----

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

Pull LED updates from Lee Jones:
 "New Drivers:
   - Add support for Allwinner A100 RGB LED controller
   - Add support for Maxim 5970 Dual Hot-swap controller

  New Device Support:
   - Add support for AW20108 to Awinic LED driver

  New Functionality:
   - Extend support for Net speeds to include; 2.5G, 5G and 10G
   - Allow tx/rx and cts/dsr/dcd/rng TTY LEDS to be turned on and off
     via sysfs if required
   - Add support for hardware control in AW200xx

  Fix-ups:
   - Use safer methods for string handling
   - Improve error handling; return proper error values, simplify,
     avoid duplicates, etc
   - Replace Mutex use with the Completion mechanism
   - Fix include lists; alphabetise, remove unused, explicitly add used
   - Use generic platform device properties
   - Use/convert to new/better APIs/helpers/MACROs instead of
     hand-rolling implementations
   - Device Tree binding adaptions/conversions/creation
   - Continue work to remove superfluous platform .remove() call-backs
   - Remove superfluous/defunct code
   - Trivial; whitespace, unused variables, spelling, clean-ups, etc
   - Avoid unnecessary duplicate locks

  Bug Fixes:
   - Repair Kconfig based dependency lists
   - Ensure unused dynamically allocated data is freed after use
   - Fix support for brightness control
   - Add missing sufficient delays during reset to ensure correct
     operation
   - Avoid division-by-zero issues"

* tag 'leds-next-6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (45 commits)
  leds: trigger: netdev: Add core support for hw not supporting fallback to LED sw control
  leds: trigger: panic: Don't register panic notifier if creating the trigger failed
  leds: sun50i-a100: Convert to be agnostic to property provider
  leds: max5970: Add missing headers
  leds: max5970: Make use of dev_err_probe()
  leds: max5970: Make use of device properties
  leds: max5970: Remove unused variable
  leds: rgb: Drop obsolete dependency on COMPILE_TEST
  leds: sun50i-a100: Avoid division-by-zero warning
  leds: trigger: Remove unused function led_trigger_rename_static()
  leds: qcom-lpg: Introduce a wrapper for getting driver data from a pwm chip
  leds: gpio: Add kernel log if devm_fwnode_gpiod_get() fails
  dt-bindings: leds: qcom,spmi-flash-led: Fix example node name
  dt-bindings: leds: aw200xx: Fix led pattern and add reg constraints
  dt-bindings: leds: awinic,aw200xx: Add AW20108 device
  leds: aw200xx: Add support for aw20108 device
  leds: aw200xx: Improve autodim calculation method
  leds: aw200xx: Enable disable_locking flag in regmap config
  leds: aw200xx: Add delay after software reset
  dt-bindings: leds: aw200xx: Remove property "awinic,display-rows"
  ...
This commit is contained in:
Linus Torvalds 2024-01-17 15:25:27 -08:00
commit 08df80a3c5
24 changed files with 1449 additions and 201 deletions

View File

@ -114,6 +114,45 @@ Description:
speed of 1000Mbps of the named network device.
Setting this value also immediately changes the LED state.
What: /sys/class/leds/<led>/link_2500
Date: Nov 2023
KernelVersion: 6.8
Contact: linux-leds@vger.kernel.org
Description:
Signal the link speed state of 2500Mbps of the named network device.
If set to 0 (default), the LED's normal state is off.
If set to 1, the LED's normal state reflects the link state
speed of 2500Mbps of the named network device.
Setting this value also immediately changes the LED state.
What: /sys/class/leds/<led>/link_5000
Date: Nov 2023
KernelVersion: 6.8
Contact: linux-leds@vger.kernel.org
Description:
Signal the link speed state of 5000Mbps of the named network device.
If set to 0 (default), the LED's normal state is off.
If set to 1, the LED's normal state reflects the link state
speed of 5000Mbps of the named network device.
Setting this value also immediately changes the LED state.
What: /sys/class/leds/<led>/link_10000
Date: Nov 2023
KernelVersion: 6.8
Contact: linux-leds@vger.kernel.org
Description:
Signal the link speed state of 10000Mbps of the named network device.
If set to 0 (default), the LED's normal state is off.
If set to 1, the LED's normal state reflects the link state
speed of 10000Mbps of the named network device.
Setting this value also immediately changes the LED state.
What: /sys/class/leds/<led>/half_duplex
Date: Jun 2023
KernelVersion: 6.5

View File

@ -4,3 +4,59 @@ KernelVersion: 5.10
Contact: linux-leds@vger.kernel.org
Description:
Specifies the tty device name of the triggering tty
What: /sys/class/leds/<led>/rx
Date: February 2024
KernelVersion: 6.8
Description:
Signal reception (rx) of data on the named tty device.
If set to 0, the LED will not blink on reception.
If set to 1 (default), the LED will blink on reception.
What: /sys/class/leds/<led>/tx
Date: February 2024
KernelVersion: 6.8
Description:
Signal transmission (tx) of data on the named tty device.
If set to 0, the LED will not blink on transmission.
If set to 1 (default), the LED will blink on transmission.
What: /sys/class/leds/<led>/cts
Date: February 2024
KernelVersion: 6.8
Description:
CTS = Clear To Send
DCE is ready to accept data from the DTE.
If the line state is detected, the LED is switched on.
If set to 0 (default), the LED will not evaluate CTS.
If set to 1, the LED will evaluate CTS.
What: /sys/class/leds/<led>/dsr
Date: February 2024
KernelVersion: 6.8
Description:
DSR = Data Set Ready
DCE is ready to receive and send data.
If the line state is detected, the LED is switched on.
If set to 0 (default), the LED will not evaluate DSR.
If set to 1, the LED will evaluate DSR.
What: /sys/class/leds/<led>/dcd
Date: February 2024
KernelVersion: 6.8
Description:
DCD = Data Carrier Detect
DTE is receiving a carrier from the DCE.
If the line state is detected, the LED is switched on.
If set to 0 (default), the LED will not evaluate CAR (DCD).
If set to 1, the LED will evaluate CAR (DCD).
What: /sys/class/leds/<led>/rng
Date: February 2024
KernelVersion: 6.8
Description:
RNG = Ring Indicator
DCE has detected an incoming ring signal on the telephone
line. If the line state is detected, the LED is switched on.
If set to 0 (default), the LED will not evaluate RNG.
If set to 1, the LED will evaluate RNG.

View File

@ -0,0 +1,137 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/allwinner,sun50i-a100-ledc.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Allwinner A100 LED Controller
maintainers:
- Samuel Holland <samuel@sholland.org>
description:
The LED controller found in Allwinner sunxi SoCs uses a one-wire serial
interface to drive up to 1024 RGB LEDs.
properties:
compatible:
oneOf:
- const: allwinner,sun50i-a100-ledc
- items:
- enum:
- allwinner,sun20i-d1-ledc
- allwinner,sun50i-r329-ledc
- const: allwinner,sun50i-a100-ledc
reg:
maxItems: 1
"#address-cells":
const: 1
"#size-cells":
const: 0
interrupts:
maxItems: 1
clocks:
items:
- description: Bus clock
- description: Module clock
clock-names:
items:
- const: bus
- const: mod
resets:
maxItems: 1
dmas:
maxItems: 1
description: TX DMA channel
dma-names:
const: tx
allwinner,pixel-format:
description: Pixel format (subpixel transmission order), default is "grb"
enum:
- bgr
- brg
- gbr
- grb
- rbg
- rgb
allwinner,t0h-ns:
default: 336
description: Length of high pulse when transmitting a "0" bit
allwinner,t0l-ns:
default: 840
description: Length of low pulse when transmitting a "0" bit
allwinner,t1h-ns:
default: 882
description: Length of high pulse when transmitting a "1" bit
allwinner,t1l-ns:
default: 294
description: Length of low pulse when transmitting a "1" bit
allwinner,treset-ns:
default: 300000
description: Minimum delay between transmission frames
patternProperties:
"^multi-led@[0-9a-f]+$":
type: object
$ref: leds-class-multicolor.yaml#
unevaluatedProperties: false
properties:
reg:
minimum: 0
maximum: 1023
description: Index of the LED in the series (must be contiguous)
required:
- reg
required:
- compatible
- reg
- interrupts
- clocks
- clock-names
- resets
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
ledc: led-controller@2008000 {
compatible = "allwinner,sun20i-d1-ledc",
"allwinner,sun50i-a100-ledc";
reg = <0x2008000 0x400>;
interrupts = <36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu 12>, <&ccu 34>;
clock-names = "bus", "mod";
resets = <&ccu 12>;
dmas = <&dma 42>;
dma-names = "tx";
#address-cells = <1>;
#size-cells = <0>;
multi-led@0 {
reg = <0x0>;
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_INDICATOR;
};
};
...

View File

@ -10,15 +10,19 @@ maintainers:
- Martin Kurbanov <mmkurbanov@sberdevices.ru>
description: |
This controller is present on AW20036/AW20054/AW20072.
It is a 3x12/6x9/6x12 matrix LED programmed via
an I2C interface, up to 36/54/72 LEDs or 12/18/24 RGBs,
3 pattern controllers for auto breathing or group dimming control.
It is a matrix LED driver programmed via an I2C interface. Devices have
a set of individually controlled leds and support 3 pattern controllers
for auto breathing or group dimming control. Supported devices:
- AW20036 (3x12) 36 LEDs
- AW20054 (6x9) 54 LEDs
- AW20072 (6x12) 72 LEDs
- AW20108 (9x12) 108 LEDs
For more product information please see the link below:
aw20036 - https://www.awinic.com/en/productDetail/AW20036QNR#tech-docs
aw20054 - https://www.awinic.com/en/productDetail/AW20054QNR#tech-docs
aw20072 - https://www.awinic.com/en/productDetail/AW20072QNR#tech-docs
aw20108 - https://www.awinic.com/en/productDetail/AW20108QNR#tech-docs
properties:
compatible:
@ -26,6 +30,7 @@ properties:
- awinic,aw20036
- awinic,aw20054
- awinic,aw20072
- awinic,aw20108
reg:
maxItems: 1
@ -36,13 +41,11 @@ properties:
"#size-cells":
const: 0
awinic,display-rows:
$ref: /schemas/types.yaml#/definitions/uint32
description:
Leds matrix size
enable-gpios:
maxItems: 1
patternProperties:
"^led@[0-9a-f]$":
"^led@[0-9a-f]+$":
type: object
$ref: common.yaml#
unevaluatedProperties: false
@ -60,16 +63,11 @@ patternProperties:
since the chip has a single global setting.
The maximum output current of each LED is calculated by the
following formula:
IMAXled = 160000 * (592 / 600.5) * (1 / display-rows)
IMAXled = 160000 * (592 / 600.5) * (1 / max-current-switch-number)
And the minimum output current formula:
IMINled = 3300 * (592 / 600.5) * (1 / display-rows)
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
- awinic,display-rows
IMINled = 3300 * (592 / 600.5) * (1 / max-current-switch-number)
where max-current-switch-number is determinated by led configuration
and depends on how leds are physically connected to the led driver.
allOf:
- if:
@ -78,18 +76,67 @@ allOf:
contains:
const: awinic,aw20036
then:
patternProperties:
"^led@[0-9a-f]+$":
properties:
reg:
items:
minimum: 0
maximum: 36
- if:
properties:
awinic,display-rows:
enum: [1, 2, 3]
else:
compatible:
contains:
const: awinic,aw20054
then:
patternProperties:
"^led@[0-9a-f]+$":
properties:
reg:
items:
minimum: 0
maximum: 54
- if:
properties:
awinic,display-rows:
enum: [1, 2, 3, 4, 5, 6, 7]
compatible:
contains:
const: awinic,aw20072
then:
patternProperties:
"^led@[0-9a-f]+$":
properties:
reg:
items:
minimum: 0
maximum: 72
- if:
properties:
compatible:
contains:
const: awinic,aw20108
then:
patternProperties:
"^led@[0-9a-f]+$":
properties:
reg:
items:
minimum: 0
maximum: 108
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
i2c {
@ -101,7 +148,7 @@ examples:
reg = <0x3a>;
#address-cells = <1>;
#size-cells = <0>;
awinic,display-rows = <3>;
enable-gpios = <&gpio 3 GPIO_ACTIVE_HIGH>;
led@0 {
reg = <0x0>;

View File

@ -167,7 +167,7 @@ properties:
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
$ref: /schemas/types.yaml#/definitions/uint32
panic-indicator:
description:

View File

@ -89,9 +89,11 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/leds/common.h>
spmi {
pmic {
#address-cells = <1>;
#size-cells = <0>;
led-controller@ee00 {
compatible = "qcom,pm8350c-flash-led", "qcom,spmi-flash-led";
reg = <0xee00>;

View File

@ -95,14 +95,18 @@ config LEDS_ARIEL
Say Y to if your machine is a Dell Wyse 3020 thin client.
config LEDS_AW200XX
tristate "LED support for Awinic AW20036/AW20054/AW20072"
tristate "LED support for Awinic AW20036/AW20054/AW20072/AW20108"
depends on LEDS_CLASS
depends on I2C
help
This option enables support for the AW20036/AW20054/AW20072 LED driver.
It is a 3x12/6x9/6x12 matrix LED driver programmed via
an I2C interface, up to 36/54/72 LEDs or 12/18/24 RGBs,
3 pattern controllers for auto breathing or group dimming control.
This option enables support for the Awinic AW200XX LED controllers.
It is a matrix LED driver programmed via an I2C interface. Devices have
a set of individually controlled LEDs and support 3 pattern controllers
for auto breathing or group dimming control. Supported devices:
- AW20036 (3x12) 36 LEDs
- AW20054 (6x9) 54 LEDs
- AW20072 (6x12) 72 LEDs
- AW20108 (9x12) 108 LEDs
To compile this driver as a module, choose M here: the module
will be called leds-aw200xx.
@ -110,6 +114,7 @@ config LEDS_AW200XX
config LEDS_AW2013
tristate "LED support for Awinic AW2013"
depends on LEDS_CLASS && I2C && OF
select REGMAP_I2C
help
This option enables support for the AW2013 3-channel
LED driver.
@ -298,6 +303,15 @@ config LEDS_COBALT_RAQ
help
This option enables support for the Cobalt Raq series LEDs.
config LEDS_SUN50I_A100
tristate "LED support for Allwinner A100 RGB LED controller"
depends on LEDS_CLASS_MULTICOLOR
depends on ARCH_SUNXI || COMPILE_TEST
help
This option enables support for the RGB LED controller found
in some Allwinner sunxi SoCs, including A100, R329, and D1.
It uses a one-wire interface to control up to 1024 LEDs.
config LEDS_SUNFIRE
tristate "LED support for SunFire servers."
depends on LEDS_CLASS
@ -638,6 +652,17 @@ config LEDS_ADP5520
To compile this driver as a module, choose M here: the module will
be called leds-adp5520.
config LEDS_MAX5970
tristate "LED Support for Maxim 5970"
depends on LEDS_CLASS
depends on MFD_MAX5970
help
This option enables support for the Maxim MAX5970 & MAX5978 smart
switch indication LEDs via the I2C bus.
To compile this driver as a module, choose M here: the module will
be called leds-max5970.
config LEDS_MC13783
tristate "LED Support for MC13XXX PMIC"
depends on LEDS_CLASS

View File

@ -56,6 +56,7 @@ obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o
obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o
obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o
obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o
obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o
obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o
obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
@ -78,6 +79,7 @@ obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o
obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o
obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o
obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o

View File

@ -269,19 +269,6 @@ void led_trigger_set_default(struct led_classdev *led_cdev)
}
EXPORT_SYMBOL_GPL(led_trigger_set_default);
void led_trigger_rename_static(const char *name, struct led_trigger *trig)
{
/* new name must be on a temporary string to prevent races */
BUG_ON(name == trig->name);
down_write(&triggers_list_lock);
/* this assumes that trig->name was originaly allocated to
* non constant storage */
strcpy((char *)trig->name, name);
up_write(&triggers_list_lock);
}
EXPORT_SYMBOL_GPL(led_trigger_rename_static);
/* LED Trigger Interface */
int led_trigger_register(struct led_trigger *trig)

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Awinic AW20036/AW20054/AW20072 LED driver
* Awinic AW20036/AW20054/AW20072/AW20108 LED driver
*
* Copyright (c) 2023, SberDevices. All Rights Reserved.
*
@ -10,6 +10,7 @@
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/container_of.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/mod_devicetable.h>
@ -74,6 +75,10 @@
#define AW200XX_LED2REG(x, columns) \
((x) + (((x) / (columns)) * (AW200XX_DSIZE_COLUMNS_MAX - (columns))))
/* DIM current configuration register on page 1 */
#define AW200XX_REG_DIM_PAGE1(x, columns) \
AW200XX_REG(AW200XX_PAGE1, AW200XX_LED2REG(x, columns))
/*
* DIM current configuration register (page 4).
* The even address for current DIM configuration.
@ -82,6 +87,8 @@
#define AW200XX_REG_DIM(x, columns) \
AW200XX_REG(AW200XX_PAGE4, AW200XX_LED2REG(x, columns) * 2)
#define AW200XX_REG_DIM2FADE(x) ((x) + 1)
#define AW200XX_REG_FADE2DIM(fade) \
DIV_ROUND_UP((fade) * AW200XX_DIM_MAX, AW200XX_FADE_MAX)
/*
* Duty ratio of display scan (see p.15 of datasheet for formula):
@ -112,6 +119,7 @@ struct aw200xx {
struct mutex mutex;
u32 num_leds;
u32 display_rows;
struct gpio_desc *hwen;
struct aw200xx_led leds[] __counted_by(num_leds);
};
@ -153,7 +161,8 @@ static ssize_t dim_store(struct device *dev, struct device_attribute *devattr,
if (dim >= 0) {
ret = regmap_write(chip->regmap,
AW200XX_REG_DIM(led->num, columns), dim);
AW200XX_REG_DIM_PAGE1(led->num, columns),
dim);
if (ret)
goto out_unlock;
}
@ -188,9 +197,7 @@ static int aw200xx_brightness_set(struct led_classdev *cdev,
dim = led->dim;
if (dim < 0)
dim = max_t(int,
brightness / (AW200XX_FADE_MAX / AW200XX_DIM_MAX),
1);
dim = AW200XX_REG_FADE2DIM(brightness);
ret = regmap_write(chip->regmap, reg, dim);
if (ret)
@ -314,6 +321,9 @@ static int aw200xx_chip_reset(const struct aw200xx *const chip)
if (ret)
return ret;
/* According to the datasheet software reset takes at least 1ms */
fsleep(1000);
regcache_mark_dirty(chip->regmap);
return regmap_write(chip->regmap, AW200XX_REG_FCD, AW200XX_FCD_CLEAR);
}
@ -353,6 +363,50 @@ static int aw200xx_chip_check(const struct aw200xx *const chip)
return 0;
}
static void aw200xx_enable(const struct aw200xx *const chip)
{
gpiod_set_value_cansleep(chip->hwen, 1);
/*
* After HWEN pin set high the chip begins to load the OTP information,
* which takes 200us to complete. About 200us wait time is needed for
* internal oscillator startup and display SRAM initialization. After
* display SRAM initialization, the registers in page1 to page5 can be
* configured via i2c interface.
*/
fsleep(400);
}
static void aw200xx_disable(const struct aw200xx *const chip)
{
return gpiod_set_value_cansleep(chip->hwen, 0);
}
static int aw200xx_probe_get_display_rows(struct device *dev,
struct aw200xx *chip)
{
struct fwnode_handle *child;
u32 max_source = 0;
device_for_each_child_node(dev, child) {
u32 source;
int ret;
ret = fwnode_property_read_u32(child, "reg", &source);
if (ret || source >= chip->cdef->channels)
continue;
max_source = max(max_source, source);
}
if (max_source == 0)
return -EINVAL;
chip->display_rows = max_source / chip->cdef->display_size_columns + 1;
return 0;
}
static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
{
struct fwnode_handle *child;
@ -360,18 +414,10 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
int ret;
int i;
ret = device_property_read_u32(dev, "awinic,display-rows",
&chip->display_rows);
ret = aw200xx_probe_get_display_rows(dev, chip);
if (ret)
return dev_err_probe(dev, ret,
"Failed to read 'display-rows' property\n");
if (!chip->display_rows ||
chip->display_rows > chip->cdef->display_size_rows_max) {
return dev_err_probe(dev, -EINVAL,
"Invalid leds display size %u\n",
chip->display_rows);
}
"No valid led definitions found\n");
current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA);
current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA);
@ -416,6 +462,7 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
led->num = source;
led->chip = chip;
led->cdev.brightness_set_blocking = aw200xx_brightness_set;
led->cdev.max_brightness = AW200XX_FADE_MAX;
led->cdev.groups = dim_groups;
init_data.fwnode = child;
@ -480,6 +527,7 @@ static const struct regmap_config aw200xx_regmap_config = {
.rd_table = &aw200xx_readable_table,
.wr_table = &aw200xx_writeable_table,
.cache_type = REGCACHE_MAPLE,
.disable_locking = true,
};
static int aw200xx_probe(struct i2c_client *client)
@ -512,6 +560,14 @@ static int aw200xx_probe(struct i2c_client *client)
if (IS_ERR(chip->regmap))
return PTR_ERR(chip->regmap);
chip->hwen = devm_gpiod_get_optional(&client->dev, "enable",
GPIOD_OUT_HIGH);
if (IS_ERR(chip->hwen))
return dev_err_probe(&client->dev, PTR_ERR(chip->hwen),
"Cannot get enable GPIO");
aw200xx_enable(chip);
ret = aw200xx_chip_check(chip);
if (ret)
return ret;
@ -532,6 +588,9 @@ static int aw200xx_probe(struct i2c_client *client)
ret = aw200xx_chip_init(chip);
out_unlock:
if (ret)
aw200xx_disable(chip);
mutex_unlock(&chip->mutex);
return ret;
}
@ -541,6 +600,7 @@ static void aw200xx_remove(struct i2c_client *client)
struct aw200xx *chip = i2c_get_clientdata(client);
aw200xx_chip_reset(chip);
aw200xx_disable(chip);
mutex_destroy(&chip->mutex);
}
@ -562,10 +622,17 @@ static const struct aw200xx_chipdef aw20072_cdef = {
.display_size_columns = 12,
};
static const struct aw200xx_chipdef aw20108_cdef = {
.channels = 108,
.display_size_rows_max = 9,
.display_size_columns = 12,
};
static const struct i2c_device_id aw200xx_id[] = {
{ "aw20036" },
{ "aw20054" },
{ "aw20072" },
{ "aw20108" },
{}
};
MODULE_DEVICE_TABLE(i2c, aw200xx_id);
@ -574,6 +641,7 @@ static const struct of_device_id aw200xx_match_table[] = {
{ .compatible = "awinic,aw20036", .data = &aw20036_cdef, },
{ .compatible = "awinic,aw20054", .data = &aw20054_cdef, },
{ .compatible = "awinic,aw20072", .data = &aw20072_cdef, },
{ .compatible = "awinic,aw20108", .data = &aw20108_cdef, },
{}
};
MODULE_DEVICE_TABLE(of, aw200xx_match_table);

View File

@ -172,6 +172,8 @@ static struct gpio_leds_priv *gpio_leds_create(struct device *dev)
led.gpiod = devm_fwnode_gpiod_get(dev, child, NULL, GPIOD_ASIS,
NULL);
if (IS_ERR(led.gpiod)) {
dev_err_probe(dev, PTR_ERR(led.gpiod), "Failed to get GPIO '%pfw'\n",
child);
fwnode_handle_put(child);
return ERR_CAST(led.gpiod);
}

111
drivers/leds/leds-max5970.c Normal file
View File

@ -0,0 +1,111 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Device driver for leds in MAX5970 and MAX5978 IC
*
* Copyright (c) 2022 9elements GmbH
*
* Author: Patrick Rudolph <patrick.rudolph@9elements.com>
*/
#include <linux/bits.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/leds.h>
#include <linux/mfd/max5970.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>
#define ldev_to_maxled(c) container_of(c, struct max5970_led, cdev)
struct max5970_led {
struct device *dev;
struct regmap *regmap;
struct led_classdev cdev;
unsigned int index;
};
static int max5970_led_set_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct max5970_led *ddata = ldev_to_maxled(cdev);
int ret, val;
/* Set/clear corresponding bit for given led index */
val = !brightness ? BIT(ddata->index) : 0;
ret = regmap_update_bits(ddata->regmap, MAX5970_REG_LED_FLASH, BIT(ddata->index), val);
if (ret < 0)
dev_err(cdev->dev, "failed to set brightness %d", ret);
return ret;
}
static int max5970_led_probe(struct platform_device *pdev)
{
struct fwnode_handle *led_node, *child;
struct device *dev = &pdev->dev;
struct regmap *regmap;
struct max5970_led *ddata;
int ret = -ENODEV;
regmap = dev_get_regmap(dev->parent, NULL);
if (!regmap)
return -ENODEV;
led_node = device_get_named_child_node(dev->parent, "leds");
if (!led_node)
return -ENODEV;
fwnode_for_each_available_child_node(led_node, child) {
u32 reg;
if (fwnode_property_read_u32(child, "reg", &reg))
continue;
if (reg >= MAX5970_NUM_LEDS) {
dev_err_probe(dev, -EINVAL, "invalid LED (%u >= %d)\n", reg, MAX5970_NUM_LEDS);
continue;
}
ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata) {
fwnode_handle_put(child);
return -ENOMEM;
}
ddata->index = reg;
ddata->regmap = regmap;
ddata->dev = dev;
if (fwnode_property_read_string(child, "label", &ddata->cdev.name))
ddata->cdev.name = fwnode_get_name(child);
ddata->cdev.max_brightness = 1;
ddata->cdev.brightness_set_blocking = max5970_led_set_brightness;
ddata->cdev.default_trigger = "none";
ret = devm_led_classdev_register(dev, &ddata->cdev);
if (ret < 0) {
fwnode_handle_put(child);
return dev_err_probe(dev, ret, "Failed to initialize LED %u\n", reg);
}
}
return ret;
}
static struct platform_driver max5970_led_driver = {
.driver = {
.name = "max5970-led",
},
.probe = max5970_led_probe,
};
module_platform_driver(max5970_led_driver);
MODULE_AUTHOR("Patrick Rudolph <patrick.rudolph@9elements.com>");
MODULE_AUTHOR("Naresh Solanki <Naresh.Solanki@9elements.com>");
MODULE_DESCRIPTION("MAX5970_hot-swap controller LED driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,584 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021-2023 Samuel Holland <samuel@sholland.org>
*
* Partly based on drivers/leds/leds-turris-omnia.c, which is:
* Copyright (c) 2020 by Marek Behún <kabel@kernel.org>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/led-class-multicolor.h>
#include <linux/leds.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/property.h>
#include <linux/reset.h>
#include <linux/spinlock.h>
#define LEDC_CTRL_REG 0x0000
#define LEDC_CTRL_REG_DATA_LENGTH GENMASK(28, 16)
#define LEDC_CTRL_REG_RGB_MODE GENMASK(8, 6)
#define LEDC_CTRL_REG_LEDC_EN BIT(0)
#define LEDC_T01_TIMING_CTRL_REG 0x0004
#define LEDC_T01_TIMING_CTRL_REG_T1H GENMASK(26, 21)
#define LEDC_T01_TIMING_CTRL_REG_T1L GENMASK(20, 16)
#define LEDC_T01_TIMING_CTRL_REG_T0H GENMASK(10, 6)
#define LEDC_T01_TIMING_CTRL_REG_T0L GENMASK(5, 0)
#define LEDC_RESET_TIMING_CTRL_REG 0x000c
#define LEDC_RESET_TIMING_CTRL_REG_TR GENMASK(28, 16)
#define LEDC_RESET_TIMING_CTRL_REG_LED_NUM GENMASK(9, 0)
#define LEDC_DATA_REG 0x0014
#define LEDC_DMA_CTRL_REG 0x0018
#define LEDC_DMA_CTRL_REG_DMA_EN BIT(5)
#define LEDC_DMA_CTRL_REG_FIFO_TRIG_LEVEL GENMASK(4, 0)
#define LEDC_INT_CTRL_REG 0x001c
#define LEDC_INT_CTRL_REG_GLOBAL_INT_EN BIT(5)
#define LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN BIT(1)
#define LEDC_INT_CTRL_REG_TRANS_FINISH_INT_EN BIT(0)
#define LEDC_INT_STS_REG 0x0020
#define LEDC_INT_STS_REG_FIFO_WLW GENMASK(15, 10)
#define LEDC_INT_STS_REG_FIFO_CPUREQ_INT BIT(1)
#define LEDC_INT_STS_REG_TRANS_FINISH_INT BIT(0)
#define LEDC_FIFO_DEPTH 32U
#define LEDC_MAX_LEDS 1024
#define LEDC_CHANNELS_PER_LED 3 /* RGB */
#define LEDS_TO_BYTES(n) ((n) * sizeof(u32))
struct sun50i_a100_ledc_led {
struct led_classdev_mc mc_cdev;
struct mc_subled subled_info[LEDC_CHANNELS_PER_LED];
u32 addr;
};
#define to_ledc_led(mc) container_of(mc, struct sun50i_a100_ledc_led, mc_cdev)
struct sun50i_a100_ledc_timing {
u32 t0h_ns;
u32 t0l_ns;
u32 t1h_ns;
u32 t1l_ns;
u32 treset_ns;
};
struct sun50i_a100_ledc {
struct device *dev;
void __iomem *base;
struct clk *bus_clk;
struct clk *mod_clk;
struct reset_control *reset;
u32 *buffer;
struct dma_chan *dma_chan;
dma_addr_t dma_handle;
unsigned int pio_length;
unsigned int pio_offset;
spinlock_t lock;
unsigned int next_length;
bool xfer_active;
u32 format;
struct sun50i_a100_ledc_timing timing;
u32 max_addr;
u32 num_leds;
struct sun50i_a100_ledc_led leds[] __counted_by(num_leds);
};
static int sun50i_a100_ledc_dma_xfer(struct sun50i_a100_ledc *priv, unsigned int length)
{
struct dma_async_tx_descriptor *desc;
dma_cookie_t cookie;
desc = dmaengine_prep_slave_single(priv->dma_chan, priv->dma_handle,
LEDS_TO_BYTES(length), DMA_MEM_TO_DEV, 0);
if (!desc)
return -ENOMEM;
cookie = dmaengine_submit(desc);
if (dma_submit_error(cookie))
return -EIO;
dma_async_issue_pending(priv->dma_chan);
return 0;
}
static void sun50i_a100_ledc_pio_xfer(struct sun50i_a100_ledc *priv, unsigned int fifo_used)
{
unsigned int burst, length, offset;
u32 control;
length = priv->pio_length;
offset = priv->pio_offset;
burst = min(length, LEDC_FIFO_DEPTH - fifo_used);
iowrite32_rep(priv->base + LEDC_DATA_REG, priv->buffer + offset, burst);
if (burst < length) {
priv->pio_length = length - burst;
priv->pio_offset = offset + burst;
if (!offset) {
control = readl(priv->base + LEDC_INT_CTRL_REG);
control |= LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN;
writel(control, priv->base + LEDC_INT_CTRL_REG);
}
} else {
/* Disable the request IRQ once all data is written. */
control = readl(priv->base + LEDC_INT_CTRL_REG);
control &= ~LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN;
writel(control, priv->base + LEDC_INT_CTRL_REG);
}
}
static void sun50i_a100_ledc_start_xfer(struct sun50i_a100_ledc *priv, unsigned int length)
{
bool use_dma = false;
u32 control;
if (priv->dma_chan && length > LEDC_FIFO_DEPTH) {
int ret;
ret = sun50i_a100_ledc_dma_xfer(priv, length);
if (ret)
dev_warn(priv->dev, "Failed to set up DMA (%d), using PIO\n", ret);
else
use_dma = true;
}
/* The DMA trigger level must be at least the burst length. */
control = FIELD_PREP(LEDC_DMA_CTRL_REG_DMA_EN, use_dma) |
FIELD_PREP_CONST(LEDC_DMA_CTRL_REG_FIFO_TRIG_LEVEL, LEDC_FIFO_DEPTH / 2);
writel(control, priv->base + LEDC_DMA_CTRL_REG);
control = readl(priv->base + LEDC_CTRL_REG);
control &= ~LEDC_CTRL_REG_DATA_LENGTH;
control |= FIELD_PREP(LEDC_CTRL_REG_DATA_LENGTH, length) | LEDC_CTRL_REG_LEDC_EN;
writel(control, priv->base + LEDC_CTRL_REG);
if (!use_dma) {
/* The FIFO is empty when starting a new transfer. */
unsigned int fifo_used = 0;
priv->pio_length = length;
priv->pio_offset = 0;
sun50i_a100_ledc_pio_xfer(priv, fifo_used);
}
}
static irqreturn_t sun50i_a100_ledc_irq(int irq, void *data)
{
struct sun50i_a100_ledc *priv = data;
u32 status;
status = readl(priv->base + LEDC_INT_STS_REG);
if (status & LEDC_INT_STS_REG_TRANS_FINISH_INT) {
unsigned int next_length;
spin_lock(&priv->lock);
/* If another transfer is queued, dequeue and start it. */
next_length = priv->next_length;
if (next_length)
priv->next_length = 0;
else
priv->xfer_active = false;
spin_unlock(&priv->lock);
if (next_length)
sun50i_a100_ledc_start_xfer(priv, next_length);
} else if (status & LEDC_INT_STS_REG_FIFO_CPUREQ_INT) {
/* Continue the current transfer. */
sun50i_a100_ledc_pio_xfer(priv, FIELD_GET(LEDC_INT_STS_REG_FIFO_WLW, status));
}
/* Clear the W1C status bits. */
writel(status, priv->base + LEDC_INT_STS_REG);
return IRQ_HANDLED;
}
static void sun50i_a100_ledc_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct sun50i_a100_ledc *priv = dev_get_drvdata(cdev->dev->parent);
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
struct sun50i_a100_ledc_led *led = to_ledc_led(mc_cdev);
unsigned int next_length;
unsigned long flags;
bool xfer_active;
led_mc_calc_color_components(mc_cdev, brightness);
priv->buffer[led->addr] = led->subled_info[0].brightness << 16 |
led->subled_info[1].brightness << 8 |
led->subled_info[2].brightness;
spin_lock_irqsave(&priv->lock, flags);
/* Start, enqueue, or extend an enqueued transfer, as appropriate. */
next_length = max(priv->next_length, led->addr + 1);
xfer_active = priv->xfer_active;
if (xfer_active)
priv->next_length = next_length;
else
priv->xfer_active = true;
spin_unlock_irqrestore(&priv->lock, flags);
if (!xfer_active)
sun50i_a100_ledc_start_xfer(priv, next_length);
}
static const char *const sun50i_a100_ledc_formats[] = {
"rgb", "rbg", "grb", "gbr", "brg", "bgr",
};
static int sun50i_a100_ledc_parse_format(struct device *dev,
struct sun50i_a100_ledc *priv)
{
const char *format = "grb";
u32 i;
device_property_read_string(dev, "allwinner,pixel-format", &format);
for (i = 0; i < ARRAY_SIZE(sun50i_a100_ledc_formats); i++) {
if (!strcmp(format, sun50i_a100_ledc_formats[i])) {
priv->format = i;
return 0;
}
}
return dev_err_probe(dev, -EINVAL, "Bad pixel format '%s'\n", format);
}
static void sun50i_a100_ledc_set_format(struct sun50i_a100_ledc *priv)
{
u32 control;
control = readl(priv->base + LEDC_CTRL_REG);
control &= ~LEDC_CTRL_REG_RGB_MODE;
control |= FIELD_PREP(LEDC_CTRL_REG_RGB_MODE, priv->format);
writel(control, priv->base + LEDC_CTRL_REG);
}
static const struct sun50i_a100_ledc_timing sun50i_a100_ledc_default_timing = {
.t0h_ns = 336,
.t0l_ns = 840,
.t1h_ns = 882,
.t1l_ns = 294,
.treset_ns = 300000,
};
static int sun50i_a100_ledc_parse_timing(struct device *dev,
struct sun50i_a100_ledc *priv)
{
struct sun50i_a100_ledc_timing *timing = &priv->timing;
*timing = sun50i_a100_ledc_default_timing;
device_property_read_u32(dev, "allwinner,t0h-ns", &timing->t0h_ns);
device_property_read_u32(dev, "allwinner,t0l-ns", &timing->t0l_ns);
device_property_read_u32(dev, "allwinner,t1h-ns", &timing->t1h_ns);
device_property_read_u32(dev, "allwinner,t1l-ns", &timing->t1l_ns);
device_property_read_u32(dev, "allwinner,treset-ns", &timing->treset_ns);
return 0;
}
static void sun50i_a100_ledc_set_timing(struct sun50i_a100_ledc *priv)
{
const struct sun50i_a100_ledc_timing *timing = &priv->timing;
unsigned long mod_freq = clk_get_rate(priv->mod_clk);
u32 cycle_ns;
u32 control;
if (!mod_freq)
return;
cycle_ns = NSEC_PER_SEC / mod_freq;
control = FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1H, timing->t1h_ns / cycle_ns) |
FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1L, timing->t1l_ns / cycle_ns) |
FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T0H, timing->t0h_ns / cycle_ns) |
FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T0L, timing->t0l_ns / cycle_ns);
writel(control, priv->base + LEDC_T01_TIMING_CTRL_REG);
control = FIELD_PREP(LEDC_RESET_TIMING_CTRL_REG_TR, timing->treset_ns / cycle_ns) |
FIELD_PREP(LEDC_RESET_TIMING_CTRL_REG_LED_NUM, priv->max_addr);
writel(control, priv->base + LEDC_RESET_TIMING_CTRL_REG);
}
static int sun50i_a100_ledc_resume(struct device *dev)
{
struct sun50i_a100_ledc *priv = dev_get_drvdata(dev);
int ret;
ret = reset_control_deassert(priv->reset);
if (ret)
return ret;
ret = clk_prepare_enable(priv->bus_clk);
if (ret)
goto err_assert_reset;
ret = clk_prepare_enable(priv->mod_clk);
if (ret)
goto err_disable_bus_clk;
sun50i_a100_ledc_set_format(priv);
sun50i_a100_ledc_set_timing(priv);
writel(LEDC_INT_CTRL_REG_GLOBAL_INT_EN | LEDC_INT_CTRL_REG_TRANS_FINISH_INT_EN,
priv->base + LEDC_INT_CTRL_REG);
return 0;
err_disable_bus_clk:
clk_disable_unprepare(priv->bus_clk);
err_assert_reset:
reset_control_assert(priv->reset);
return ret;
}
static int sun50i_a100_ledc_suspend(struct device *dev)
{
struct sun50i_a100_ledc *priv = dev_get_drvdata(dev);
/* Wait for all transfers to complete. */
for (;;) {
unsigned long flags;
bool xfer_active;
spin_lock_irqsave(&priv->lock, flags);
xfer_active = priv->xfer_active;
spin_unlock_irqrestore(&priv->lock, flags);
if (!xfer_active)
break;
msleep(1);
}
clk_disable_unprepare(priv->mod_clk);
clk_disable_unprepare(priv->bus_clk);
reset_control_assert(priv->reset);
return 0;
}
static void sun50i_a100_ledc_dma_cleanup(void *data)
{
struct sun50i_a100_ledc *priv = data;
dma_release_channel(priv->dma_chan);
}
static int sun50i_a100_ledc_probe(struct platform_device *pdev)
{
struct dma_slave_config dma_cfg = {};
struct led_init_data init_data = {};
struct sun50i_a100_ledc_led *led;
struct device *dev = &pdev->dev;
struct sun50i_a100_ledc *priv;
struct fwnode_handle *child;
struct resource *mem;
u32 max_addr = 0;
u32 num_leds = 0;
int irq, ret;
/*
* The maximum LED address must be known in sun50i_a100_ledc_resume() before
* class device registration, so parse and validate the subnodes up front.
*/
device_for_each_child_node(dev, child) {
u32 addr, color;
ret = fwnode_property_read_u32(child, "reg", &addr);
if (ret || addr >= LEDC_MAX_LEDS) {
fwnode_handle_put(child);
return dev_err_probe(dev, -EINVAL, "'reg' must be between 0 and %d\n",
LEDC_MAX_LEDS - 1);
}
ret = fwnode_property_read_u32(child, "color", &color);
if (ret || color != LED_COLOR_ID_RGB) {
fwnode_handle_put(child);
return dev_err_probe(dev, -EINVAL, "'color' must be LED_COLOR_ID_RGB\n");
}
max_addr = max(max_addr, addr);
num_leds++;
}
if (!num_leds)
return -ENODEV;
priv = devm_kzalloc(dev, struct_size(priv, leds, num_leds), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->max_addr = max_addr;
priv->num_leds = num_leds;
spin_lock_init(&priv->lock);
dev_set_drvdata(dev, priv);
ret = sun50i_a100_ledc_parse_format(dev, priv);
if (ret)
return ret;
ret = sun50i_a100_ledc_parse_timing(dev, priv);
if (ret)
return ret;
priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
priv->bus_clk = devm_clk_get(dev, "bus");
if (IS_ERR(priv->bus_clk))
return PTR_ERR(priv->bus_clk);
priv->mod_clk = devm_clk_get(dev, "mod");
if (IS_ERR(priv->mod_clk))
return PTR_ERR(priv->mod_clk);
priv->reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(priv->reset))
return PTR_ERR(priv->reset);
priv->dma_chan = dma_request_chan(dev, "tx");
if (IS_ERR(priv->dma_chan)) {
if (PTR_ERR(priv->dma_chan) != -ENODEV)
return PTR_ERR(priv->dma_chan);
priv->dma_chan = NULL;
priv->buffer = devm_kzalloc(dev, LEDS_TO_BYTES(LEDC_MAX_LEDS), GFP_KERNEL);
if (!priv->buffer)
return -ENOMEM;
} else {
ret = devm_add_action_or_reset(dev, sun50i_a100_ledc_dma_cleanup, priv);
if (ret)
return ret;
dma_cfg.dst_addr = mem->start + LEDC_DATA_REG;
dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dma_cfg.dst_maxburst = LEDC_FIFO_DEPTH / 2;
ret = dmaengine_slave_config(priv->dma_chan, &dma_cfg);
if (ret)
return ret;
priv->buffer = dmam_alloc_attrs(dmaengine_get_dma_device(priv->dma_chan),
LEDS_TO_BYTES(LEDC_MAX_LEDS), &priv->dma_handle,
GFP_KERNEL, DMA_ATTR_WRITE_COMBINE);
if (!priv->buffer)
return -ENOMEM;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(dev, irq, sun50i_a100_ledc_irq, 0, dev_name(dev), priv);
if (ret)
return ret;
ret = sun50i_a100_ledc_resume(dev);
if (ret)
return ret;
led = priv->leds;
device_for_each_child_node(dev, child) {
struct led_classdev *cdev;
/* The node was already validated above. */
fwnode_property_read_u32(child, "reg", &led->addr);
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.num_colors = ARRAY_SIZE(led->subled_info);
led->mc_cdev.subled_info = led->subled_info;
cdev = &led->mc_cdev.led_cdev;
cdev->max_brightness = U8_MAX;
cdev->brightness_set = sun50i_a100_ledc_brightness_set;
init_data.fwnode = child;
ret = led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data);
if (ret) {
dev_err_probe(dev, ret, "Failed to register multicolor LED %u", led->addr);
goto err_put_child;
}
led++;
}
dev_info(dev, "Registered %u LEDs\n", num_leds);
return 0;
err_put_child:
fwnode_handle_put(child);
while (led-- > priv->leds)
led_classdev_multicolor_unregister(&led->mc_cdev);
sun50i_a100_ledc_suspend(&pdev->dev);
return ret;
}
static void sun50i_a100_ledc_remove(struct platform_device *pdev)
{
struct sun50i_a100_ledc *priv = platform_get_drvdata(pdev);
for (u32 i = 0; i < priv->num_leds; i++)
led_classdev_multicolor_unregister(&priv->leds[i].mc_cdev);
sun50i_a100_ledc_suspend(&pdev->dev);
}
static const struct of_device_id sun50i_a100_ledc_of_match[] = {
{ .compatible = "allwinner,sun50i-a100-ledc" },
{}
};
MODULE_DEVICE_TABLE(of, sun50i_a100_ledc_of_match);
static DEFINE_SIMPLE_DEV_PM_OPS(sun50i_a100_ledc_pm,
sun50i_a100_ledc_suspend,
sun50i_a100_ledc_resume);
static struct platform_driver sun50i_a100_ledc_driver = {
.probe = sun50i_a100_ledc_probe,
.remove_new = sun50i_a100_ledc_remove,
.shutdown = sun50i_a100_ledc_remove,
.driver = {
.name = "sun50i-a100-ledc",
.of_match_table = sun50i_a100_ledc_of_match,
.pm = pm_ptr(&sun50i_a100_ledc_pm),
},
};
module_platform_driver(sun50i_a100_ledc_driver);
MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
MODULE_DESCRIPTION("Allwinner A100 LED controller driver");
MODULE_LICENSE("GPL");

View File

@ -81,7 +81,8 @@ static int syscon_led_probe(struct platform_device *pdev)
sled->map = map;
if (of_property_read_u32(np, "offset", &sled->offset))
if (of_property_read_u32(np, "reg", &sled->offset) &&
of_property_read_u32(np, "offset", &sled->offset))
return -EINVAL;
if (of_property_read_u32(np, "mask", &sled->mask))
return -EINVAL;

View File

@ -638,19 +638,13 @@ static int tca6507_probe_gpios(struct device *dev,
tca->gpio.direction_output = tca6507_gpio_direction_output;
tca->gpio.set = tca6507_gpio_set_value;
tca->gpio.parent = dev;
err = gpiochip_add_data(&tca->gpio, tca);
err = devm_gpiochip_add_data(dev, &tca->gpio, tca);
if (err) {
tca->gpio.ngpio = 0;
return err;
}
return 0;
}
static void tca6507_remove_gpio(struct tca6507_chip *tca)
{
if (tca->gpio.ngpio)
gpiochip_remove(&tca->gpio);
}
#else /* CONFIG_GPIOLIB */
static int tca6507_probe_gpios(struct device *dev,
struct tca6507_chip *tca,
@ -658,9 +652,6 @@ static int tca6507_probe_gpios(struct device *dev,
{
return 0;
}
static void tca6507_remove_gpio(struct tca6507_chip *tca)
{
}
#endif /* CONFIG_GPIOLIB */
static struct tca6507_platform_data *
@ -762,38 +753,25 @@ static int tca6507_probe(struct i2c_client *client)
l->led_cdev.brightness_set = tca6507_brightness_set;
l->led_cdev.blink_set = tca6507_blink_set;
l->bank = -1;
err = led_classdev_register(dev, &l->led_cdev);
err = devm_led_classdev_register(dev, &l->led_cdev);
if (err < 0)
goto exit;
return err;
}
}
err = tca6507_probe_gpios(dev, tca, pdata);
if (err)
goto exit;
return err;
/* set all registers to known state - zero */
tca->reg_set = 0x7f;
schedule_work(&tca->work);
return 0;
exit:
while (i--) {
if (tca->leds[i].led_cdev.name)
led_classdev_unregister(&tca->leds[i].led_cdev);
}
return err;
}
static void tca6507_remove(struct i2c_client *client)
{
int i;
struct tca6507_chip *tca = i2c_get_clientdata(client);
struct tca6507_led *tca_leds = tca->leds;
for (i = 0; i < NUM_LEDS; i++) {
if (tca_leds[i].led_cdev.name)
led_classdev_unregister(&tca_leds[i].led_cdev);
}
tca6507_remove_gpio(tca);
cancel_work_sync(&tca->work);
}

View File

@ -4,7 +4,7 @@ if LEDS_CLASS_MULTICOLOR
config LEDS_GROUP_MULTICOLOR
tristate "LEDs group multi-color support"
depends on OF || COMPILE_TEST
depends on OF
help
This option enables support for monochrome LEDs that are grouped
into multicolor LEDs which is useful in the case where LEDs of

View File

@ -552,9 +552,9 @@ static int lpg_parse_dtest(struct lpg *lpg)
ret = count;
goto err_malformed;
} else if (count != lpg->data->num_channels * 2) {
dev_err(lpg->dev, "qcom,dtest needs to be %d items\n",
lpg->data->num_channels * 2);
return -EINVAL;
return dev_err_probe(lpg->dev, -EINVAL,
"qcom,dtest needs to be %d items\n",
lpg->data->num_channels * 2);
}
for (i = 0; i < lpg->data->num_channels; i++) {
@ -574,8 +574,7 @@ static int lpg_parse_dtest(struct lpg *lpg)
return 0;
err_malformed:
dev_err(lpg->dev, "malformed qcom,dtest\n");
return ret;
return dev_err_probe(lpg->dev, ret, "malformed qcom,dtest\n");
}
static void lpg_apply_dtest(struct lpg_channel *chan)
@ -977,9 +976,14 @@ static int lpg_pattern_mc_clear(struct led_classdev *cdev)
return lpg_pattern_clear(led);
}
static inline struct lpg *lpg_pwm_from_chip(struct pwm_chip *chip)
{
return container_of(chip, struct lpg, pwm);
}
static int lpg_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct lpg *lpg = container_of(chip, struct lpg, pwm);
struct lpg *lpg = lpg_pwm_from_chip(chip);
struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
return chan->in_use ? -EBUSY : 0;
@ -995,7 +999,7 @@ static int lpg_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
static int lpg_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct lpg *lpg = container_of(chip, struct lpg, pwm);
struct lpg *lpg = lpg_pwm_from_chip(chip);
struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
int ret = 0;
@ -1026,7 +1030,7 @@ out_unlock:
static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct lpg *lpg = container_of(chip, struct lpg, pwm);
struct lpg *lpg = lpg_pwm_from_chip(chip);
struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
unsigned int resolution;
unsigned int pre_div;
@ -1095,9 +1099,9 @@ static int lpg_add_pwm(struct lpg *lpg)
lpg->pwm.npwm = lpg->num_channels;
lpg->pwm.ops = &lpg_pwm_ops;
ret = pwmchip_add(&lpg->pwm);
ret = devm_pwmchip_add(lpg->dev, &lpg->pwm);
if (ret)
dev_err(lpg->dev, "failed to add PWM chip: ret %d\n", ret);
dev_err_probe(lpg->dev, ret, "failed to add PWM chip\n");
return ret;
}
@ -1111,19 +1115,16 @@ static int lpg_parse_channel(struct lpg *lpg, struct device_node *np,
int ret;
ret = of_property_read_u32(np, "reg", &reg);
if (ret || !reg || reg > lpg->num_channels) {
dev_err(lpg->dev, "invalid \"reg\" of %pOFn\n", np);
return -EINVAL;
}
if (ret || !reg || reg > lpg->num_channels)
return dev_err_probe(lpg->dev, -EINVAL, "invalid \"reg\" of %pOFn\n", np);
chan = &lpg->channels[reg - 1];
chan->in_use = true;
ret = of_property_read_u32(np, "color", &color);
if (ret < 0 && ret != -EINVAL) {
dev_err(lpg->dev, "failed to parse \"color\" of %pOF\n", np);
return ret;
}
if (ret < 0 && ret != -EINVAL)
return dev_err_probe(lpg->dev, ret,
"failed to parse \"color\" of %pOF\n", np);
chan->color = color;
@ -1146,10 +1147,9 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
int i;
ret = of_property_read_u32(np, "color", &color);
if (ret < 0 && ret != -EINVAL) {
dev_err(lpg->dev, "failed to parse \"color\" of %pOF\n", np);
return ret;
}
if (ret < 0 && ret != -EINVAL)
return dev_err_probe(lpg->dev, ret,
"failed to parse \"color\" of %pOF\n", np);
if (color == LED_COLOR_ID_RGB)
num_channels = of_get_available_child_count(np);
@ -1226,7 +1226,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
else
ret = devm_led_classdev_register_ext(lpg->dev, &led->cdev, &init_data);
if (ret)
dev_err(lpg->dev, "unable to register %s\n", cdev->name);
dev_err_probe(lpg->dev, ret, "unable to register %s\n", cdev->name);
return ret;
}
@ -1272,10 +1272,9 @@ static int lpg_init_triled(struct lpg *lpg)
if (lpg->triled_has_src_sel) {
ret = of_property_read_u32(np, "qcom,power-source", &lpg->triled_src);
if (ret || lpg->triled_src == 2 || lpg->triled_src > 3) {
dev_err(lpg->dev, "invalid power source\n");
return -EINVAL;
}
if (ret || lpg->triled_src == 2 || lpg->triled_src > 3)
return dev_err_probe(lpg->dev, -EINVAL,
"invalid power source\n");
}
/* Disable automatic trickle charge LED */
@ -1324,8 +1323,6 @@ static int lpg_probe(struct platform_device *pdev)
if (!lpg->data)
return -EINVAL;
platform_set_drvdata(pdev, lpg);
lpg->dev = &pdev->dev;
mutex_init(&lpg->lock);
@ -1363,13 +1360,6 @@ static int lpg_probe(struct platform_device *pdev)
return lpg_add_pwm(lpg);
}
static void lpg_remove(struct platform_device *pdev)
{
struct lpg *lpg = platform_get_drvdata(pdev);
pwmchip_remove(&lpg->pwm);
}
static const struct lpg_data pm8916_pwm_data = {
.num_channels = 1,
.channels = (const struct lpg_channel_data[]) {
@ -1529,7 +1519,6 @@ MODULE_DEVICE_TABLE(of, lpg_of_table);
static struct platform_driver lpg_driver = {
.probe = lpg_probe,
.remove_new = lpg_remove,
.driver = {
.name = "qcom-spmi-lpg",
.of_match_table = lpg_of_table,

View File

@ -41,33 +41,30 @@ static irqreturn_t gpio_trig_irq(int irq, void *_led)
return IRQ_HANDLED;
}
static ssize_t gpio_trig_brightness_show(struct device *dev,
static ssize_t desired_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev);
return sprintf(buf, "%u\n", gpio_data->desired_brightness);
return sysfs_emit(buf, "%u\n", gpio_data->desired_brightness);
}
static ssize_t gpio_trig_brightness_store(struct device *dev,
static ssize_t desired_brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t n)
{
struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev);
unsigned desired_brightness;
u8 desired_brightness;
int ret;
ret = sscanf(buf, "%u", &desired_brightness);
if (ret < 1 || desired_brightness > 255) {
dev_err(dev, "invalid value\n");
return -EINVAL;
}
ret = kstrtou8(buf, 10, &desired_brightness);
if (ret)
return ret;
gpio_data->desired_brightness = desired_brightness;
return n;
}
static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show,
gpio_trig_brightness_store);
static DEVICE_ATTR_RW(desired_brightness);
static struct attribute *gpio_trig_attrs[] = {
&dev_attr_desired_brightness.attr,
@ -89,10 +86,7 @@ static int gpio_trig_activate(struct led_classdev *led)
* The generic property "trigger-sources" is followed,
* and we hope that this is a GPIO.
*/
gpio_data->gpiod = fwnode_gpiod_get_index(dev->fwnode,
"trigger-sources",
0, GPIOD_IN,
"led-trigger");
gpio_data->gpiod = gpiod_get_optional(dev, "trigger-sources", GPIOD_IN);
if (IS_ERR(gpio_data->gpiod)) {
ret = PTR_ERR(gpio_data->gpiod);
kfree(gpio_data);
@ -104,6 +98,8 @@ static int gpio_trig_activate(struct led_classdev *led)
return -EINVAL;
}
gpiod_set_consumer_name(gpio_data->gpiod, "led-trigger");
gpio_data->led = led;
led_set_trigger_data(led, gpio_data);

View File

@ -38,6 +38,16 @@
* tx - LED blinks on transmitted data
* rx - LED blinks on receive data
*
* Note: If the user selects a mode that is not supported by hw, default
* behavior is to fall back to software control of the LED. However not every
* hw supports software control. LED callbacks brightness_set() and
* brightness_set_blocking() are NULL in this case. hw_control_is_supported()
* should use available means supported by hw to inform the user that selected
* mode isn't supported by hw. This could be switching off the LED or any
* hw blink mode. If software control fallback isn't possible, we return
* -EOPNOTSUPP to the user, but still store the selected mode. This is needed
* in case an intermediate unsupported mode is necessary to switch from one
* supported mode to another.
*/
struct led_netdev_data {
@ -99,6 +109,18 @@ static void set_baseline_state(struct led_netdev_data *trigger_data)
trigger_data->link_speed == SPEED_1000)
blink_on = true;
if (test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) &&
trigger_data->link_speed == SPEED_2500)
blink_on = true;
if (test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) &&
trigger_data->link_speed == SPEED_5000)
blink_on = true;
if (test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) &&
trigger_data->link_speed == SPEED_10000)
blink_on = true;
if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) &&
trigger_data->duplex == DUPLEX_HALF)
blink_on = true;
@ -289,6 +311,9 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf,
case TRIGGER_NETDEV_LINK_10:
case TRIGGER_NETDEV_LINK_100:
case TRIGGER_NETDEV_LINK_1000:
case TRIGGER_NETDEV_LINK_2500:
case TRIGGER_NETDEV_LINK_5000:
case TRIGGER_NETDEV_LINK_10000:
case TRIGGER_NETDEV_HALF_DUPLEX:
case TRIGGER_NETDEV_FULL_DUPLEX:
case TRIGGER_NETDEV_TX:
@ -306,6 +331,7 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
size_t size, enum led_trigger_netdev_modes attr)
{
struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
struct led_classdev *led_cdev = trigger_data->led_cdev;
unsigned long state, mode = trigger_data->mode;
int ret;
int bit;
@ -319,6 +345,9 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
case TRIGGER_NETDEV_LINK_10:
case TRIGGER_NETDEV_LINK_100:
case TRIGGER_NETDEV_LINK_1000:
case TRIGGER_NETDEV_LINK_2500:
case TRIGGER_NETDEV_LINK_5000:
case TRIGGER_NETDEV_LINK_10000:
case TRIGGER_NETDEV_HALF_DUPLEX:
case TRIGGER_NETDEV_FULL_DUPLEX:
case TRIGGER_NETDEV_TX:
@ -337,7 +366,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
if (test_bit(TRIGGER_NETDEV_LINK, &mode) &&
(test_bit(TRIGGER_NETDEV_LINK_10, &mode) ||
test_bit(TRIGGER_NETDEV_LINK_100, &mode) ||
test_bit(TRIGGER_NETDEV_LINK_1000, &mode)))
test_bit(TRIGGER_NETDEV_LINK_1000, &mode) ||
test_bit(TRIGGER_NETDEV_LINK_2500, &mode) ||
test_bit(TRIGGER_NETDEV_LINK_5000, &mode) ||
test_bit(TRIGGER_NETDEV_LINK_10000, &mode)))
return -EINVAL;
cancel_delayed_work_sync(&trigger_data->work);
@ -345,6 +377,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
trigger_data->mode = mode;
trigger_data->hw_control = can_hw_control(trigger_data);
if (!led_cdev->brightness_set && !led_cdev->brightness_set_blocking &&
!trigger_data->hw_control)
return -EOPNOTSUPP;
set_baseline_state(trigger_data);
return size;
@ -367,6 +403,9 @@ DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK);
DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10);
DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100);
DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000);
DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500);
DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000);
DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000);
DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX);
DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX);
DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
@ -425,6 +464,9 @@ static struct attribute *netdev_trig_attrs[] = {
&dev_attr_link_10.attr,
&dev_attr_link_100.attr,
&dev_attr_link_1000.attr,
&dev_attr_link_2500.attr,
&dev_attr_link_5000.attr,
&dev_attr_link_10000.attr,
&dev_attr_full_duplex.attr,
&dev_attr_half_duplex.attr,
&dev_attr_rx.attr,
@ -522,6 +564,9 @@ static void netdev_trig_work(struct work_struct *work)
test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode);
interval = jiffies_to_msecs(

View File

@ -64,10 +64,13 @@ static long led_panic_blink(int state)
static int __init ledtrig_panic_init(void)
{
led_trigger_register_simple("panic", &trigger);
if (!trigger)
return -ENOMEM;
atomic_notifier_chain_register(&panic_notifier_list,
&led_trigger_panic_nb);
led_trigger_register_simple("panic", &trigger);
panic_blink = led_panic_blink;
return 0;
}

View File

@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/leds.h>
#include <linux/module.h>
@ -12,15 +13,45 @@
struct ledtrig_tty_data {
struct led_classdev *led_cdev;
struct delayed_work dwork;
struct mutex mutex;
struct completion sysfs;
const char *ttyname;
struct tty_struct *tty;
int rx, tx;
bool mode_rx;
bool mode_tx;
bool mode_cts;
bool mode_dsr;
bool mode_dcd;
bool mode_rng;
};
static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data)
/* Indicates which state the LED should now display */
enum led_trigger_tty_state {
TTY_LED_BLINK,
TTY_LED_ENABLE,
TTY_LED_DISABLE,
};
enum led_trigger_tty_modes {
TRIGGER_TTY_RX = 0,
TRIGGER_TTY_TX,
TRIGGER_TTY_CTS,
TRIGGER_TTY_DSR,
TRIGGER_TTY_DCD,
TRIGGER_TTY_RNG,
};
static int ledtrig_tty_wait_for_completion(struct device *dev)
{
schedule_delayed_work(&trigger_data->dwork, 0);
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
int ret;
ret = wait_for_completion_timeout(&trigger_data->sysfs,
msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 20));
if (ret == 0)
return -ETIMEDOUT;
return ret;
}
static ssize_t ttyname_show(struct device *dev,
@ -28,14 +59,16 @@ static ssize_t ttyname_show(struct device *dev,
{
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
ssize_t len = 0;
int completion;
mutex_lock(&trigger_data->mutex);
reinit_completion(&trigger_data->sysfs);
completion = ledtrig_tty_wait_for_completion(dev);
if (completion < 0)
return completion;
if (trigger_data->ttyname)
len = sprintf(buf, "%s\n", trigger_data->ttyname);
mutex_unlock(&trigger_data->mutex);
return len;
}
@ -46,7 +79,7 @@ static ssize_t ttyname_store(struct device *dev,
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
char *ttyname;
ssize_t ret = size;
bool running;
int completion;
if (size > 0 && buf[size - 1] == '\n')
size -= 1;
@ -59,9 +92,10 @@ static ssize_t ttyname_store(struct device *dev,
ttyname = NULL;
}
mutex_lock(&trigger_data->mutex);
running = trigger_data->ttyname != NULL;
reinit_completion(&trigger_data->sysfs);
completion = ledtrig_tty_wait_for_completion(dev);
if (completion < 0)
return completion;
kfree(trigger_data->ttyname);
tty_kref_put(trigger_data->tty);
@ -69,29 +103,107 @@ static ssize_t ttyname_store(struct device *dev,
trigger_data->ttyname = ttyname;
mutex_unlock(&trigger_data->mutex);
if (ttyname && !running)
ledtrig_tty_restart(trigger_data);
return ret;
}
static DEVICE_ATTR_RW(ttyname);
static ssize_t ledtrig_tty_attr_show(struct device *dev, char *buf,
enum led_trigger_tty_modes attr)
{
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
bool state;
switch (attr) {
case TRIGGER_TTY_RX:
state = trigger_data->mode_rx;
break;
case TRIGGER_TTY_TX:
state = trigger_data->mode_tx;
break;
case TRIGGER_TTY_CTS:
state = trigger_data->mode_cts;
break;
case TRIGGER_TTY_DSR:
state = trigger_data->mode_dsr;
break;
case TRIGGER_TTY_DCD:
state = trigger_data->mode_dcd;
break;
case TRIGGER_TTY_RNG:
state = trigger_data->mode_rng;
break;
}
return sysfs_emit(buf, "%u\n", state);
}
static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf,
size_t size, enum led_trigger_tty_modes attr)
{
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
bool state;
int ret;
ret = kstrtobool(buf, &state);
if (ret)
return ret;
switch (attr) {
case TRIGGER_TTY_RX:
trigger_data->mode_rx = state;
break;
case TRIGGER_TTY_TX:
trigger_data->mode_tx = state;
break;
case TRIGGER_TTY_CTS:
trigger_data->mode_cts = state;
break;
case TRIGGER_TTY_DSR:
trigger_data->mode_dsr = state;
break;
case TRIGGER_TTY_DCD:
trigger_data->mode_dcd = state;
break;
case TRIGGER_TTY_RNG:
trigger_data->mode_rng = state;
break;
}
return size;
}
#define DEFINE_TTY_TRIGGER(trigger_name, trigger) \
static ssize_t trigger_name##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
return ledtrig_tty_attr_show(dev, buf, trigger); \
} \
static ssize_t trigger_name##_store(struct device *dev, \
struct device_attribute *attr, const char *buf, size_t size) \
{ \
return ledtrig_tty_attr_store(dev, buf, size, trigger); \
} \
static DEVICE_ATTR_RW(trigger_name)
DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX);
DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX);
DEFINE_TTY_TRIGGER(cts, TRIGGER_TTY_CTS);
DEFINE_TTY_TRIGGER(dsr, TRIGGER_TTY_DSR);
DEFINE_TTY_TRIGGER(dcd, TRIGGER_TTY_DCD);
DEFINE_TTY_TRIGGER(rng, TRIGGER_TTY_RNG);
static void ledtrig_tty_work(struct work_struct *work)
{
struct ledtrig_tty_data *trigger_data =
container_of(work, struct ledtrig_tty_data, dwork.work);
struct serial_icounter_struct icount;
enum led_trigger_tty_state state = TTY_LED_DISABLE;
unsigned long interval = LEDTRIG_TTY_INTERVAL;
bool invert = false;
int status;
int ret;
mutex_lock(&trigger_data->mutex);
if (!trigger_data->ttyname) {
/* exit without rescheduling */
mutex_unlock(&trigger_data->mutex);
return;
}
if (!trigger_data->ttyname)
goto out;
/* try to get the tty corresponding to $ttyname */
if (!trigger_data->tty) {
@ -115,32 +227,83 @@ static void ledtrig_tty_work(struct work_struct *work)
trigger_data->tty = tty;
}
ret = tty_get_icount(trigger_data->tty, &icount);
if (ret) {
dev_info(trigger_data->tty->dev, "Failed to get icount, stopped polling\n");
mutex_unlock(&trigger_data->mutex);
return;
status = tty_get_tiocm(trigger_data->tty);
if (status > 0) {
if (trigger_data->mode_cts) {
if (status & TIOCM_CTS)
state = TTY_LED_ENABLE;
}
if (trigger_data->mode_dsr) {
if (status & TIOCM_DSR)
state = TTY_LED_ENABLE;
}
if (trigger_data->mode_dcd) {
if (status & TIOCM_CAR)
state = TTY_LED_ENABLE;
}
if (trigger_data->mode_rng) {
if (status & TIOCM_RNG)
state = TTY_LED_ENABLE;
}
}
if (icount.rx != trigger_data->rx ||
icount.tx != trigger_data->tx) {
unsigned long interval = LEDTRIG_TTY_INTERVAL;
/*
* The evaluation of rx/tx must be done after the evaluation
* of TIOCM_*, because rx/tx has priority.
*/
if (trigger_data->mode_rx || trigger_data->mode_tx) {
struct serial_icounter_struct icount;
led_blink_set_oneshot(trigger_data->led_cdev, &interval,
&interval, 0);
ret = tty_get_icount(trigger_data->tty, &icount);
if (ret)
goto out;
trigger_data->rx = icount.rx;
trigger_data->tx = icount.tx;
if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) {
trigger_data->tx = icount.tx;
invert = state == TTY_LED_ENABLE;
state = TTY_LED_BLINK;
}
if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) {
trigger_data->rx = icount.rx;
invert = state == TTY_LED_ENABLE;
state = TTY_LED_BLINK;
}
}
out:
mutex_unlock(&trigger_data->mutex);
switch (state) {
case TTY_LED_BLINK:
led_blink_set_oneshot(trigger_data->led_cdev, &interval,
&interval, invert);
break;
case TTY_LED_ENABLE:
led_set_brightness(trigger_data->led_cdev,
trigger_data->led_cdev->blink_brightness);
break;
case TTY_LED_DISABLE:
fallthrough;
default:
led_set_brightness(trigger_data->led_cdev, LED_OFF);
break;
}
complete_all(&trigger_data->sysfs);
schedule_delayed_work(&trigger_data->dwork,
msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2));
}
static struct attribute *ledtrig_tty_attrs[] = {
&dev_attr_ttyname.attr,
&dev_attr_rx.attr,
&dev_attr_tx.attr,
&dev_attr_cts.attr,
&dev_attr_dsr.attr,
&dev_attr_dcd.attr,
&dev_attr_rng.attr,
NULL
};
ATTRIBUTE_GROUPS(ledtrig_tty);
@ -153,11 +316,17 @@ static int ledtrig_tty_activate(struct led_classdev *led_cdev)
if (!trigger_data)
return -ENOMEM;
/* Enable default rx/tx mode */
trigger_data->mode_rx = true;
trigger_data->mode_tx = true;
led_set_trigger_data(led_cdev, trigger_data);
INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work);
trigger_data->led_cdev = led_cdev;
mutex_init(&trigger_data->mutex);
init_completion(&trigger_data->sysfs);
schedule_delayed_work(&trigger_data->dwork, 0);
return 0;
}
@ -168,6 +337,10 @@ static void ledtrig_tty_deactivate(struct led_classdev *led_cdev)
cancel_delayed_work_sync(&trigger_data->dwork);
kfree(trigger_data->ttyname);
tty_kref_put(trigger_data->tty);
trigger_data->tty = NULL;
kfree(trigger_data);
}

View File

@ -2498,6 +2498,24 @@ static int send_break(struct tty_struct *tty, unsigned int duration)
return retval;
}
/**
* tty_get_tiocm - get tiocm status register
* @tty: tty device
*
* Obtain the modem status bits from the tty driver if the feature
* is supported.
*/
int tty_get_tiocm(struct tty_struct *tty)
{
int retval = -ENOTTY;
if (tty->ops->tiocmget)
retval = tty->ops->tiocmget(tty);
return retval;
}
EXPORT_SYMBOL_GPL(tty_get_tiocm);
/**
* tty_tiocmget - get modem status
* @tty: tty device
@ -2510,14 +2528,12 @@ static int send_break(struct tty_struct *tty, unsigned int duration)
*/
static int tty_tiocmget(struct tty_struct *tty, int __user *p)
{
int retval = -ENOTTY;
int retval;
if (tty->ops->tiocmget) {
retval = tty->ops->tiocmget(tty);
retval = tty_get_tiocm(tty);
if (retval >= 0)
retval = put_user(retval, p);
if (retval >= 0)
retval = put_user(retval, p);
}
return retval;
}

View File

@ -527,23 +527,6 @@ static inline void *led_get_trigger_data(struct led_classdev *led_cdev)
return led_cdev->trigger_data;
}
/**
* led_trigger_rename_static - rename a trigger
* @name: the new trigger name
* @trig: the LED trigger to rename
*
* Change a LED trigger name by copying the string passed in
* name into current trigger name, which MUST be large
* enough for the new string.
*
* Note that name must NOT point to the same string used
* during LED registration, as that could lead to races.
*
* This is meant to be used on triggers with statically
* allocated name.
*/
void led_trigger_rename_static(const char *name, struct led_trigger *trig);
#define module_led_trigger(__led_trigger) \
module_driver(__led_trigger, led_trigger_register, \
led_trigger_unregister)
@ -588,6 +571,9 @@ enum led_trigger_netdev_modes {
TRIGGER_NETDEV_LINK_10,
TRIGGER_NETDEV_LINK_100,
TRIGGER_NETDEV_LINK_1000,
TRIGGER_NETDEV_LINK_2500,
TRIGGER_NETDEV_LINK_5000,
TRIGGER_NETDEV_LINK_10000,
TRIGGER_NETDEV_HALF_DUPLEX,
TRIGGER_NETDEV_FULL_DUPLEX,
TRIGGER_NETDEV_TX,

View File

@ -419,6 +419,7 @@ bool tty_unthrottle_safe(struct tty_struct *tty);
int tty_do_resize(struct tty_struct *tty, struct winsize *ws);
int tty_get_icount(struct tty_struct *tty,
struct serial_icounter_struct *icount);
int tty_get_tiocm(struct tty_struct *tty);
int is_current_pgrp_orphaned(void);
void tty_hangup(struct tty_struct *tty);
void tty_vhangup(struct tty_struct *tty);