- 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:
commit
08df80a3c5
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
...
|
@ -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>;
|
||||
|
@ -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:
|
||||
|
@ -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>;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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
111
drivers/leds/leds-max5970.c
Normal 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", ®))
|
||||
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");
|
584
drivers/leds/leds-sun50i-a100.c
Normal file
584
drivers/leds/leds-sun50i-a100.c
Normal 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");
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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", ®);
|
||||
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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user