mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-03 19:55:31 +00:00
- New Drivers
- Add support for MediaTek MT6370 LED Indicator - Add support for MediaTek MT6370 Flashlight - Add support for QCOM PMIC Flash - Add support for Rohm BD2606MVV Charge Pump LED - New Device Support - Add support for PMK8550 PWM to QCOM LPG - New Functionality - Add support for high resolution PWM to QCOM LPG - Fix-ups - Kconfig 'depends' and 'select' dependency changes - Remove unused / irrelevant includes - Remove unnecessary checks (already performed further into the call stack) - Trivial: Fix commentary, simplify error messages - Rid 'defined but not used' warnings - Provide documentation - Explicitly provide include files - Bug Fixes - Mark GPIO LED as BROKEN - Fix Kconfig entries - Fix various Smatch staticify reports - Fix error handling (or a lack there of) -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmRRONwACgkQUa+KL4f8 d2Er8w/+Iu6LK+NvxSvELKzyuUw8gsJxCmAYSTFc2ywjFKPJK91giAXYBhvFFhlv mduJkO+Mbz3yFdXTmG3OjTs/K9J/XIjOl0mB4FTWLsthyytJwUbZIdr9La60/LF/ 0ExWCI3lRPEFLC1kbODmgpDrBmlegqH5vzvGStmy3r4HbDvBVXl1No5ArWYXMof1 e4W7p0tCQNOKaSDLpbQNcFzZ5682h/CY/+R6UQzTiI+6/3g+3IqHa4qqtcXGI0Tk 7UtPuGd5f7Gm+1iQO+gxXAlOPictOI5SfHIVWJqtfAEY4RetBulyxkOaG+dw/LN3 pp77yF6b4DU7P89jb/cKL1lD5y3SCYJpNwZrkkytGDqeS4C2J218RTPGzvrJLAtj tNa65SC7fM6Qh/Mv8b5A7wMrysAF2JAusP8UEBamVNlyt2jTY/MwO3EgTNVFuNGo um88Cia1BIQMSej0GpRFbOqflbrGEVqJjh/ag7ftGp9jnO4gmCSB5LuNN6QGhtQ8 0T2agMZ9udd2nNKlgbfxvAM+rqGq0/KSgX6nt7XRDDfw5rOI5YZORUFNmo4IKVyx bfzPW8QOpL6fbtfbFktJDgndV4bKJ1zRLUgk9PhfL23tiSo3HuC+0NdFqG0UCaEy RnNm8k4Q5SD+OfnzstMF9Zq0cXWCjFm7QVVElRBSFs/fzP5iHfE= =aJQw -----END PGP SIGNATURE----- Merge tag 'leds-next-6.4' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds Pull LED updates from Lee Jones: "New Drivers: - Add support for MediaTek MT6370 LED Indicator - Add support for MediaTek MT6370 Flashlight - Add support for QCOM PMIC Flash - Add support for Rohm BD2606MVV Charge Pump LED New Device Support: - Add support for PMK8550 PWM to QCOM LPG New Functionality: - Add support for high resolution PWM to QCOM LPG Fix-ups: - Kconfig 'depends' and 'select' dependency changes - Remove unused / irrelevant includes - Remove unnecessary checks (already performed further into the call stack) - Trivial: Fix commentary, simplify error messages - Rid 'defined but not used' warnings - Provide documentation - Explicitly provide include files Bug Fixes: - Mark GPIO LED as BROKEN - Fix Kconfig entries - Fix various Smatch staticify reports - Fix error handling (or a lack there of)" * tag 'leds-next-6.4' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (30 commits) leds: bd2606mvv: Driver for the Rohm 6 Channel i2c LED driver dt-bindings: leds: Add ROHM BD2606MVV LED docs: leds: ledtrig-oneshot: Fix spelling mistake leds: pwm-multicolor: Simplify an error message dt-bindings: leds: Convert PCA9532 to dtschema leds: rgb: leds-qcom-lpg: Add support for PMK8550 PWM leds: rgb: leds-qcom-lpg: Add support for high resolution PWM dt-bindings: leds-qcom-lpg: Add qcom,pmk8550-pwm compatible string leds: tca6507: Fix error handling of using fwnode_property_read_string leds: flash: Set variables mvflash_{3,4}ch_regs storage-class-specifier to static leds: rgb: mt6370: Correct config name to select in LEDS_MT6370_RGB MAINTAINERS: Add entry for LED devices documentation Documentation: leds: MT6370: Use bullet lists for timing variables Documentation: leds: mt6370: Properly wrap hw_pattern chart Documentation: leds: Add MT6370 doc to the toctree leds: rgb: mt6370: Fix implicit declaration for FIELD_GET docs: leds: Add MT6370 RGB LED pattern document leds: flash: mt6370: Add MediaTek MT6370 flashlight support leds: rgb: mt6370: Add MediaTek MT6370 current sink type LED Indicator support dt-bindings: leds: spmi-flash-led: Add pm6150l compatible ...
This commit is contained in:
commit
c5eb8bf767
@ -90,22 +90,51 @@ properties:
|
||||
- heartbeat
|
||||
# LED indicates disk activity
|
||||
- disk-activity
|
||||
# LED indicates disk read activity
|
||||
- disk-read
|
||||
# LED indicates disk write activity
|
||||
- disk-write
|
||||
# LED flashes at a fixed, configurable rate
|
||||
- timer
|
||||
# LED alters the brightness for the specified duration with one software
|
||||
# timer (requires "led-pattern" property)
|
||||
- pattern
|
||||
# LED indicates mic mute state
|
||||
- audio-micmute
|
||||
# LED indicates audio mute state
|
||||
- audio-mute
|
||||
# LED indicates bluetooth power state
|
||||
- bluetooth-power
|
||||
# LED indicates activity of all CPUs
|
||||
- cpu
|
||||
# LED indicates camera flash state
|
||||
- flash
|
||||
# LED indicated keyboard capslock
|
||||
- kbd-capslock
|
||||
# LED indicates MTD memory activity
|
||||
- mtd
|
||||
# LED indicates NAND memory activity (deprecated),
|
||||
# in new implementations use "mtd"
|
||||
- nand-disk
|
||||
# No trigger assigned to the LED. This is the default mode
|
||||
# if trigger is absent
|
||||
- none
|
||||
# LED indicates camera torch state
|
||||
- torch
|
||||
# LED indicates USB gadget activity
|
||||
- usb-gadget
|
||||
# LED indicates USB host activity
|
||||
- usb-host
|
||||
# LED indicates USB port state
|
||||
- usbport
|
||||
# LED is triggered by CPU activity
|
||||
- pattern: "^cpu[0-9]*$"
|
||||
- pattern: "^hci[0-9]+-power$"
|
||||
# LED is triggered by Bluetooth activity
|
||||
- pattern: "^mmc[0-9]+$"
|
||||
- pattern: "^hci[0-9]+-power$"
|
||||
# LED is triggered by SD/MMC activity
|
||||
- pattern: "^phy[0-9]+tx$"
|
||||
- pattern: "^mmc[0-9]+$"
|
||||
# LED is triggered by WLAN activity
|
||||
- pattern: "^phy[0-9]+tx$"
|
||||
|
||||
led-pattern:
|
||||
description: |
|
||||
|
@ -1,49 +0,0 @@
|
||||
*NXP - pca9532 PWM LED Driver
|
||||
|
||||
The PCA9532 family is SMBus I/O expander optimized for dimming LEDs.
|
||||
The PWM support 256 steps.
|
||||
|
||||
Required properties:
|
||||
- compatible:
|
||||
"nxp,pca9530"
|
||||
"nxp,pca9531"
|
||||
"nxp,pca9532"
|
||||
"nxp,pca9533"
|
||||
- reg - I2C slave address
|
||||
|
||||
Each led is represented as a sub-node of the nxp,pca9530.
|
||||
|
||||
Optional sub-node properties:
|
||||
- label: see Documentation/devicetree/bindings/leds/common.txt
|
||||
- type: Output configuration, see dt-bindings/leds/leds-pca9532.h (default NONE)
|
||||
- linux,default-trigger: see Documentation/devicetree/bindings/leds/common.txt
|
||||
- default-state: see Documentation/devicetree/bindings/leds/common.txt
|
||||
This property is only valid for sub-nodes of type <PCA9532_TYPE_LED>.
|
||||
|
||||
Example:
|
||||
#include <dt-bindings/leds/leds-pca9532.h>
|
||||
|
||||
leds: pca9530@60 {
|
||||
compatible = "nxp,pca9530";
|
||||
reg = <0x60>;
|
||||
|
||||
red-power {
|
||||
label = "pca:red:power";
|
||||
type = <PCA9532_TYPE_LED>;
|
||||
};
|
||||
green-power {
|
||||
label = "pca:green:power";
|
||||
type = <PCA9532_TYPE_LED>;
|
||||
};
|
||||
kernel-booting {
|
||||
type = <PCA9532_TYPE_LED>;
|
||||
default-state = "on";
|
||||
};
|
||||
sys-stat {
|
||||
type = <PCA9532_TYPE_LED>;
|
||||
default-state = "keep"; // don't touch, was set by U-Boot
|
||||
};
|
||||
};
|
||||
|
||||
For more product information please see the link below:
|
||||
http://nxp.com/documents/data_sheet/PCA9532.pdf
|
@ -27,6 +27,7 @@ properties:
|
||||
- qcom,pmc8180c-lpg
|
||||
- qcom,pmi8994-lpg
|
||||
- qcom,pmi8998-lpg
|
||||
- qcom,pmk8550-pwm
|
||||
|
||||
"#pwm-cells":
|
||||
const: 2
|
||||
|
90
Documentation/devicetree/bindings/leds/nxp,pca953x.yaml
Normal file
90
Documentation/devicetree/bindings/leds/nxp,pca953x.yaml
Normal file
@ -0,0 +1,90 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/leds/nxp,pca953x.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: NXP PCA9532 LED Dimmer
|
||||
|
||||
maintainers:
|
||||
- Riku Voipio <riku.voipio@iki.fi>
|
||||
|
||||
description: |
|
||||
The PCA9532 family is SMBus I/O expander optimized for dimming LEDs.
|
||||
The PWM support 256 steps.
|
||||
|
||||
For more product information please see the link below:
|
||||
https://www.nxp.com/docs/en/data-sheet/PCA9532.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- nxp,pca9530
|
||||
- nxp,pca9531
|
||||
- nxp,pca9532
|
||||
- nxp,pca9533
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
gpio-controller: true
|
||||
|
||||
'#gpio-cells':
|
||||
const: 2
|
||||
|
||||
patternProperties:
|
||||
"^led-[0-9a-z]+$":
|
||||
type: object
|
||||
$ref: common.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
type:
|
||||
description: |
|
||||
Output configuration, see include/dt-bindings/leds/leds-pca9532.h
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
default: 0
|
||||
minimum: 0
|
||||
maximum: 4
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/leds/leds-pca9532.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led-controller@62 {
|
||||
compatible = "nxp,pca9533";
|
||||
reg = <0x62>;
|
||||
|
||||
led-1 {
|
||||
label = "pca:red:power";
|
||||
type = <PCA9532_TYPE_LED>;
|
||||
};
|
||||
|
||||
led-2 {
|
||||
label = "pca:green:power";
|
||||
type = <PCA9532_TYPE_LED>;
|
||||
};
|
||||
|
||||
led-3 {
|
||||
type = <PCA9532_TYPE_LED>;
|
||||
default-state = "on";
|
||||
};
|
||||
|
||||
led-4 {
|
||||
type = <PCA9532_TYPE_LED>;
|
||||
default-state = "keep";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
117
Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml
Normal file
117
Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml
Normal file
@ -0,0 +1,117 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/leds/qcom,spmi-flash-led.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Flash LED device inside Qualcomm Technologies, Inc. PMICs
|
||||
|
||||
maintainers:
|
||||
- Fenglin Wu <quic_fenglinw@quicinc.com>
|
||||
|
||||
description: |
|
||||
Flash LED controller is present inside some Qualcomm Technologies, Inc. PMICs.
|
||||
The flash LED module can have different number of LED channels supported
|
||||
e.g. 3 or 4. There are some different registers between them but they can
|
||||
both support maximum current up to 1.5 A per channel and they can also support
|
||||
ganging 2 channels together to supply maximum current up to 2 A. The current
|
||||
will be split symmetrically on each channel and they will be enabled and
|
||||
disabled at the same time.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- enum:
|
||||
- qcom,pm6150l-flash-led
|
||||
- qcom,pm8150c-flash-led
|
||||
- qcom,pm8150l-flash-led
|
||||
- qcom,pm8350c-flash-led
|
||||
- const: qcom,spmi-flash-led
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
patternProperties:
|
||||
"^led-[0-3]$":
|
||||
type: object
|
||||
$ref: common.yaml#
|
||||
unevaluatedProperties: false
|
||||
description:
|
||||
Represents the physical LED components which are connected to the
|
||||
flash LED channels' output.
|
||||
|
||||
properties:
|
||||
led-sources:
|
||||
description:
|
||||
The HW indices of the flash LED channels that connect to the
|
||||
physical LED
|
||||
allOf:
|
||||
- minItems: 1
|
||||
maxItems: 2
|
||||
items:
|
||||
enum: [1, 2, 3, 4]
|
||||
|
||||
led-max-microamp:
|
||||
anyOf:
|
||||
- minimum: 5000
|
||||
maximum: 500000
|
||||
multipleOf: 5000
|
||||
- minimum: 10000
|
||||
maximum: 1000000
|
||||
multipleOf: 10000
|
||||
|
||||
flash-max-microamp:
|
||||
anyOf:
|
||||
- minimum: 12500
|
||||
maximum: 1500000
|
||||
multipleOf: 12500
|
||||
- minimum: 25000
|
||||
maximum: 2000000
|
||||
multipleOf: 25000
|
||||
|
||||
flash-max-timeout-us:
|
||||
minimum: 10000
|
||||
maximum: 1280000
|
||||
multipleOf: 10000
|
||||
|
||||
required:
|
||||
- led-sources
|
||||
- led-max-microamp
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/leds/common.h>
|
||||
spmi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
led-controller@ee00 {
|
||||
compatible = "qcom,pm8350c-flash-led", "qcom,spmi-flash-led";
|
||||
reg = <0xee00>;
|
||||
|
||||
led-0 {
|
||||
function = LED_FUNCTION_FLASH;
|
||||
color = <LED_COLOR_ID_WHITE>;
|
||||
led-sources = <1>, <4>;
|
||||
led-max-microamp = <300000>;
|
||||
flash-max-microamp = <2000000>;
|
||||
flash-max-timeout-us = <1280000>;
|
||||
function-enumerator = <0>;
|
||||
};
|
||||
|
||||
led-1 {
|
||||
function = LED_FUNCTION_FLASH;
|
||||
color = <LED_COLOR_ID_YELLOW>;
|
||||
led-sources = <2>, <3>;
|
||||
led-max-microamp = <300000>;
|
||||
flash-max-microamp = <2000000>;
|
||||
flash-max-timeout-us = <1280000>;
|
||||
function-enumerator = <1>;
|
||||
};
|
||||
};
|
||||
};
|
81
Documentation/devicetree/bindings/leds/rohm,bd2606mvv.yaml
Normal file
81
Documentation/devicetree/bindings/leds/rohm,bd2606mvv.yaml
Normal file
@ -0,0 +1,81 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/leds/rohm,bd2606mvv.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: ROHM BD2606MVV LED controller
|
||||
|
||||
maintainers:
|
||||
- Andreas Kemnade <andreas@kemnade.info>
|
||||
|
||||
description:
|
||||
The BD2606 MVV is a programmable LED controller connected via I2C that can
|
||||
drive 6 separate lines. Each of them can be individually switched on and off,
|
||||
but the brightness setting is shared between pairs of them.
|
||||
|
||||
Datasheet is available at
|
||||
https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/led_driver/bd2606mvv_1-e.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: rohm,bd2606mvv
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
enable-gpios:
|
||||
maxItems: 1
|
||||
description: GPIO pin to enable/disable the device.
|
||||
|
||||
patternProperties:
|
||||
"^led@[0-6]$":
|
||||
type: object
|
||||
$ref: common.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
reg:
|
||||
minimum: 0
|
||||
maximum: 6
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/leds/common.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led-controller@66 {
|
||||
compatible = "rohm,bd2606mvv";
|
||||
reg = <0x66>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led@0 {
|
||||
reg = <0x0>;
|
||||
color = <LED_COLOR_ID_RED>;
|
||||
function = LED_FUNCTION_POWER;
|
||||
};
|
||||
|
||||
led@2 {
|
||||
reg = <0x2>;
|
||||
color = <LED_COLOR_ID_WHITE>;
|
||||
function = LED_FUNCTION_STATUS;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
@ -25,5 +25,6 @@ LEDs
|
||||
leds-lp5562
|
||||
leds-lp55xx
|
||||
leds-mlxcpld
|
||||
leds-mt6370-rgb
|
||||
leds-sc27xx
|
||||
leds-qcom-lpg
|
||||
|
64
Documentation/leds/leds-mt6370-rgb.rst
Normal file
64
Documentation/leds/leds-mt6370-rgb.rst
Normal file
@ -0,0 +1,64 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
=========================================
|
||||
The device for Mediatek MT6370 RGB LED
|
||||
=========================================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The MT6370 integrates a four-channel RGB LED driver, designed to provide a
|
||||
variety of lighting effect for mobile device applications. The RGB LED devices
|
||||
includes a smart LED string controller and it can drive 3 channels of LEDs with
|
||||
a sink current up to 24mA and a CHG_VIN power good indicator LED with sink
|
||||
current up to 6mA. It provides three operation modes for RGB LEDs:
|
||||
PWM Dimming mode, breath pattern mode, and constant current mode. The device
|
||||
can increase or decrease the brightness of the RGB LED via an I2C interface.
|
||||
|
||||
The breath pattern for a channel can be programmed using the "pattern" trigger,
|
||||
using the hw_pattern attribute.
|
||||
|
||||
/sys/class/leds/<led>/hw_pattern
|
||||
--------------------------------
|
||||
|
||||
Specify a hardware breath pattern for a MT6370 RGB LED.
|
||||
|
||||
The breath pattern is a series of timing pairs, with the hold-time expressed in
|
||||
milliseconds. And the brightness is controlled by
|
||||
'/sys/class/leds/<led>/brightness'. The pattern doesn't include the brightness
|
||||
setting. Hardware pattern only controls the timing for each pattern stage
|
||||
depending on the current brightness setting.
|
||||
|
||||
Pattern diagram::
|
||||
|
||||
"0 Tr1 0 Tr2 0 Tf1 0 Tf2 0 Ton 0 Toff" --> '0' for dummy brightness code
|
||||
|
||||
^
|
||||
| ============
|
||||
| / \ /
|
||||
Icurr | / \ /
|
||||
| / \ /
|
||||
| / \ / .....repeat
|
||||
| / \ /
|
||||
| --- --- ---
|
||||
|--- --- ---
|
||||
+----------------------------------============------------> Time
|
||||
< Tr1><Tr2>< Ton ><Tf1><Tf2 >< Toff >< Tr1><Tr2>
|
||||
|
||||
Timing description:
|
||||
|
||||
* Tr1: First rising time for 0% - 30% load.
|
||||
* Tr2: Second rising time for 31% - 100% load.
|
||||
* Ton: On time for 100% load.
|
||||
* Tf1: First falling time for 100% - 31% load.
|
||||
* Tf2: Second falling time for 30% to 0% load.
|
||||
* Toff: Off time for 0% load.
|
||||
|
||||
* Tr1/Tr2/Tf1/Tf2/Ton: 125ms to 3125ms, 200ms per step.
|
||||
* Toff: 250ms to 6250ms, 400ms per step.
|
||||
|
||||
Pattern example::
|
||||
|
||||
"0 125 0 125 0 125 0 125 0 625 0 1050"
|
||||
|
||||
This Will configure Tr1/Tr2/Tf1/Tf2 to 125m, Ton to 625ms, and Toff to 1050ms.
|
@ -5,7 +5,7 @@ One-shot LED Trigger
|
||||
This is a LED trigger useful for signaling the user of an event where there are
|
||||
no clear trap points to put standard led-on and led-off settings. Using this
|
||||
trigger, the application needs only to signal the trigger when an event has
|
||||
happened, than the trigger turns the LED on and than keeps it off for a
|
||||
happened, then the trigger turns the LED on and then keeps it off for a
|
||||
specified amount of time.
|
||||
|
||||
This trigger is meant to be usable both for sporadic and dense events. In the
|
||||
|
@ -11724,6 +11724,7 @@ L: linux-leds@vger.kernel.org
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git
|
||||
F: Documentation/devicetree/bindings/leds/
|
||||
F: Documentation/leds/
|
||||
F: drivers/leds/
|
||||
F: include/dt-bindings/leds/
|
||||
F: include/linux/leds.h
|
||||
|
@ -551,6 +551,20 @@ config LEDS_REGULATOR
|
||||
help
|
||||
This option enables support for regulator driven LEDs.
|
||||
|
||||
config LEDS_BD2606MVV
|
||||
tristate "LED driver for BD2606MVV"
|
||||
depends on LEDS_CLASS
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
This option enables support for BD2606MVV LED driver chips
|
||||
accessed via the I2C bus. It supports setting brightness, with
|
||||
the limitiation that there are groups of two channels sharing
|
||||
a brightness setting, but not the on/off setting.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called leds-bd2606mvv.
|
||||
|
||||
config LEDS_BD2802
|
||||
tristate "LED driver for BD2802 RGB LED"
|
||||
depends on LEDS_CLASS
|
||||
@ -795,7 +809,7 @@ config LEDS_SPI_BYTE
|
||||
config LEDS_TI_LMU_COMMON
|
||||
tristate "LED driver for TI LMU"
|
||||
depends on LEDS_CLASS
|
||||
depends on REGMAP
|
||||
select REGMAP
|
||||
help
|
||||
Say Y to enable the LED driver for TI LMU devices.
|
||||
This supports common features between the TI LM3532, LM3631, LM3632,
|
||||
|
@ -17,6 +17,7 @@ obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o
|
||||
obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o
|
||||
obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
|
||||
obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
|
||||
obj-$(CONFIG_LEDS_BD2606MVV) += leds-bd2606mvv.o
|
||||
obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
|
||||
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
|
||||
obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o
|
||||
|
@ -61,6 +61,34 @@ config LEDS_MT6360
|
||||
Independent current sources supply for each flash LED support torch
|
||||
and strobe mode.
|
||||
|
||||
config LEDS_MT6370_FLASH
|
||||
tristate "Flash LED Support for MediaTek MT6370 PMIC"
|
||||
depends on LEDS_CLASS
|
||||
depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
|
||||
depends on MFD_MT6370
|
||||
help
|
||||
Support 2 channels and torch/strobe mode.
|
||||
Say Y here to enable support for
|
||||
MT6370_FLASH_LED device.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called "leds-mt6370-flash".
|
||||
|
||||
config LEDS_QCOM_FLASH
|
||||
tristate "LED support for flash module inside Qualcomm Technologies, Inc. PMIC"
|
||||
depends on MFD_SPMI_PMIC || COMPILE_TEST
|
||||
depends on LEDS_CLASS && OF
|
||||
depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
|
||||
select REGMAP
|
||||
help
|
||||
This option enables support for the flash module found in Qualcomm
|
||||
Technologies, Inc. PMICs. The flash module can have 3 or 4 flash LED
|
||||
channels and each channel is programmable to support up to 1.5 A full
|
||||
scale current. It also supports connecting two channels' output together
|
||||
to supply one LED component to achieve current up to 2 A. In such case,
|
||||
the total LED current will be split symmetrically on each channel and
|
||||
they will be enabled/disabled at the same time.
|
||||
|
||||
config LEDS_RT4505
|
||||
tristate "LED support for RT4505 flashlight controller"
|
||||
depends on I2C && OF
|
||||
|
@ -1,11 +1,13 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_LEDS_MT6360) += leds-mt6360.o
|
||||
obj-$(CONFIG_LEDS_MT6370_FLASH) += leds-mt6370-flash.o
|
||||
obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
|
||||
obj-$(CONFIG_LEDS_AS3645A) += leds-as3645a.o
|
||||
obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o
|
||||
obj-$(CONFIG_LEDS_LM3601X) += leds-lm3601x.o
|
||||
obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
|
||||
obj-$(CONFIG_LEDS_QCOM_FLASH) += leds-qcom-flash.o
|
||||
obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o
|
||||
obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o
|
||||
obj-$(CONFIG_LEDS_SGM3140) += leds-sgm3140.o
|
||||
|
573
drivers/leds/flash/leds-mt6370-flash.c
Normal file
573
drivers/leds/flash/leds-mt6370-flash.c
Normal file
@ -0,0 +1,573 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2023 Richtek Technology Corp.
|
||||
*
|
||||
* Authors:
|
||||
* Alice Chen <alice_chen@richtek.com>
|
||||
* ChiYuan Huang <cy_huang@richtek.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/led-class-flash.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <media/v4l2-flash-led-class.h>
|
||||
|
||||
enum {
|
||||
MT6370_LED_FLASH1 = 0,
|
||||
MT6370_LED_FLASH2,
|
||||
MT6370_MAX_LEDS
|
||||
};
|
||||
|
||||
/* Virtual definition for multicolor */
|
||||
|
||||
#define MT6370_REG_FLEDEN 0x17E
|
||||
#define MT6370_REG_STRBTO 0x173
|
||||
#define MT6370_REG_CHGSTAT2 0x1D1
|
||||
#define MT6370_REG_FLEDSTAT1 0x1D9
|
||||
#define MT6370_REG_FLEDISTRB(_id) (0x174 + 4 * (_id))
|
||||
#define MT6370_REG_FLEDITOR(_id) (0x175 + 4 * (_id))
|
||||
#define MT6370_ITORCH_MASK GENMASK(4, 0)
|
||||
#define MT6370_ISTROBE_MASK GENMASK(6, 0)
|
||||
#define MT6370_STRBTO_MASK GENMASK(6, 0)
|
||||
#define MT6370_TORCHEN_MASK BIT(3)
|
||||
#define MT6370_STROBEN_MASK BIT(2)
|
||||
#define MT6370_FLCSEN_MASK(_id) BIT(MT6370_LED_FLASH2 - (_id))
|
||||
#define MT6370_FLCSEN_MASK_ALL GENMASK(1, 0)
|
||||
#define MT6370_FLEDCHGVINOVP_MASK BIT(3)
|
||||
#define MT6370_FLED1STRBTO_MASK BIT(11)
|
||||
#define MT6370_FLED2STRBTO_MASK BIT(10)
|
||||
#define MT6370_FLED1STRB_MASK BIT(9)
|
||||
#define MT6370_FLED2STRB_MASK BIT(8)
|
||||
#define MT6370_FLED1SHORT_MASK BIT(7)
|
||||
#define MT6370_FLED2SHORT_MASK BIT(6)
|
||||
#define MT6370_FLEDLVF_MASK BIT(3)
|
||||
|
||||
#define MT6370_LED_JOINT 2
|
||||
#define MT6370_RANGE_FLED_REG 4
|
||||
#define MT6370_ITORCH_MIN_uA 25000
|
||||
#define MT6370_ITORCH_STEP_uA 12500
|
||||
#define MT6370_ITORCH_MAX_uA 400000
|
||||
#define MT6370_ITORCH_DOUBLE_MAX_uA 800000
|
||||
#define MT6370_ISTRB_MIN_uA 50000
|
||||
#define MT6370_ISTRB_STEP_uA 12500
|
||||
#define MT6370_ISTRB_MAX_uA 1500000
|
||||
#define MT6370_ISTRB_DOUBLE_MAX_uA 3000000
|
||||
#define MT6370_STRBTO_MIN_US 64000
|
||||
#define MT6370_STRBTO_STEP_US 32000
|
||||
#define MT6370_STRBTO_MAX_US 2432000
|
||||
|
||||
#define to_mt6370_led(ptr, member) container_of(ptr, struct mt6370_led, member)
|
||||
|
||||
struct mt6370_led {
|
||||
struct led_classdev_flash flash;
|
||||
struct v4l2_flash *v4l2_flash;
|
||||
struct mt6370_priv *priv;
|
||||
u8 led_no;
|
||||
};
|
||||
|
||||
struct mt6370_priv {
|
||||
struct regmap *regmap;
|
||||
struct mutex lock;
|
||||
unsigned int fled_strobe_used;
|
||||
unsigned int fled_torch_used;
|
||||
unsigned int leds_active;
|
||||
unsigned int leds_count;
|
||||
struct mt6370_led leds[];
|
||||
};
|
||||
|
||||
static int mt6370_torch_brightness_set(struct led_classdev *lcdev, enum led_brightness level)
|
||||
{
|
||||
struct mt6370_led *led = to_mt6370_led(lcdev, flash.led_cdev);
|
||||
struct mt6370_priv *priv = led->priv;
|
||||
u32 led_enable_mask = led->led_no == MT6370_LED_JOINT ? MT6370_FLCSEN_MASK_ALL :
|
||||
MT6370_FLCSEN_MASK(led->led_no);
|
||||
u32 enable_mask = MT6370_TORCHEN_MASK | led_enable_mask;
|
||||
u32 val = level ? led_enable_mask : 0;
|
||||
u32 curr;
|
||||
int ret, i;
|
||||
|
||||
mutex_lock(&priv->lock);
|
||||
|
||||
/*
|
||||
* There is only one set of flash control logic, and this flag is used to check if 'strobe'
|
||||
* is currently being used.
|
||||
*/
|
||||
if (priv->fled_strobe_used) {
|
||||
dev_warn(lcdev->dev, "Please disable strobe first [%d]\n", priv->fled_strobe_used);
|
||||
ret = -EBUSY;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (level)
|
||||
curr = priv->fled_torch_used | BIT(led->led_no);
|
||||
else
|
||||
curr = priv->fled_torch_used & ~BIT(led->led_no);
|
||||
|
||||
if (curr)
|
||||
val |= MT6370_TORCHEN_MASK;
|
||||
|
||||
if (level) {
|
||||
level -= 1;
|
||||
if (led->led_no == MT6370_LED_JOINT) {
|
||||
u32 flevel[MT6370_MAX_LEDS];
|
||||
|
||||
/*
|
||||
* There're two flash channels in MT6370. If joint flash output is used,
|
||||
* torch current will be averaged output from both channels.
|
||||
*/
|
||||
flevel[0] = level / 2;
|
||||
flevel[1] = level - flevel[0];
|
||||
for (i = 0; i < MT6370_MAX_LEDS; i++) {
|
||||
ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDITOR(i),
|
||||
MT6370_ITORCH_MASK, flevel[i]);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
}
|
||||
} else {
|
||||
ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDITOR(led->led_no),
|
||||
MT6370_ITORCH_MASK, level);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDEN, enable_mask, val);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
priv->fled_torch_used = curr;
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&priv->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt6370_flash_brightness_set(struct led_classdev_flash *fl_cdev, u32 brightness)
|
||||
{
|
||||
/*
|
||||
* Because of the current spikes when turning on the flash, the brightness should be kept
|
||||
* by the LED framework. This empty function is used to prevent checking failure when
|
||||
* led_classdev_flash registers ops.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _mt6370_flash_brightness_set(struct led_classdev_flash *fl_cdev, u32 brightness)
|
||||
{
|
||||
struct mt6370_led *led = to_mt6370_led(fl_cdev, flash);
|
||||
struct mt6370_priv *priv = led->priv;
|
||||
struct led_flash_setting *setting = &fl_cdev->brightness;
|
||||
u32 val = (brightness - setting->min) / setting->step;
|
||||
int ret, i;
|
||||
|
||||
if (led->led_no == MT6370_LED_JOINT) {
|
||||
u32 flevel[MT6370_MAX_LEDS];
|
||||
|
||||
/*
|
||||
* There're two flash channels in MT6370. If joint flash output is used, storbe
|
||||
* current will be averaged output from both channels.
|
||||
*/
|
||||
flevel[0] = val / 2;
|
||||
flevel[1] = val - flevel[0];
|
||||
for (i = 0; i < MT6370_MAX_LEDS; i++) {
|
||||
ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDISTRB(i),
|
||||
MT6370_ISTROBE_MASK, flevel[i]);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDISTRB(led->led_no),
|
||||
MT6370_ISTROBE_MASK, val);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt6370_strobe_set(struct led_classdev_flash *fl_cdev, bool state)
|
||||
{
|
||||
struct mt6370_led *led = to_mt6370_led(fl_cdev, flash);
|
||||
struct mt6370_priv *priv = led->priv;
|
||||
struct led_classdev *lcdev = &fl_cdev->led_cdev;
|
||||
struct led_flash_setting *s = &fl_cdev->brightness;
|
||||
u32 led_enable_mask = led->led_no == MT6370_LED_JOINT ? MT6370_FLCSEN_MASK_ALL :
|
||||
MT6370_FLCSEN_MASK(led->led_no);
|
||||
u32 enable_mask = MT6370_STROBEN_MASK | led_enable_mask;
|
||||
u32 val = state ? led_enable_mask : 0;
|
||||
u32 curr;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&priv->lock);
|
||||
|
||||
/*
|
||||
* There is only one set of flash control logic, and this flag is used to check if 'torch'
|
||||
* is currently being used.
|
||||
*/
|
||||
if (priv->fled_torch_used) {
|
||||
dev_warn(lcdev->dev, "Please disable torch first [0x%x]\n", priv->fled_torch_used);
|
||||
ret = -EBUSY;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (state)
|
||||
curr = priv->fled_strobe_used | BIT(led->led_no);
|
||||
else
|
||||
curr = priv->fled_strobe_used & ~BIT(led->led_no);
|
||||
|
||||
if (curr)
|
||||
val |= MT6370_STROBEN_MASK;
|
||||
|
||||
ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDEN, enable_mask, val);
|
||||
if (ret) {
|
||||
dev_err(lcdev->dev, "[%d] control current source %d fail\n", led->led_no, state);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the flash needs to turn on, configure the flash current to ramp up to the setting
|
||||
* value. Otherwise, always revert to the minimum one.
|
||||
*/
|
||||
ret = _mt6370_flash_brightness_set(fl_cdev, state ? s->val : s->min);
|
||||
if (ret) {
|
||||
dev_err(lcdev->dev, "[%d] Failed to set brightness\n", led->led_no);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/*
|
||||
* For the flash to turn on/off, we must wait for HW ramping up/down time 5ms/500us to
|
||||
* prevent the unexpected problem.
|
||||
*/
|
||||
if (!priv->fled_strobe_used && curr)
|
||||
usleep_range(5000, 6000);
|
||||
else if (priv->fled_strobe_used && !curr)
|
||||
usleep_range(500, 600);
|
||||
|
||||
priv->fled_strobe_used = curr;
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&priv->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mt6370_strobe_get(struct led_classdev_flash *fl_cdev, bool *state)
|
||||
{
|
||||
struct mt6370_led *led = to_mt6370_led(fl_cdev, flash);
|
||||
struct mt6370_priv *priv = led->priv;
|
||||
|
||||
mutex_lock(&priv->lock);
|
||||
*state = !!(priv->fled_strobe_used & BIT(led->led_no));
|
||||
mutex_unlock(&priv->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt6370_timeout_set(struct led_classdev_flash *fl_cdev, u32 timeout)
|
||||
{
|
||||
struct mt6370_led *led = to_mt6370_led(fl_cdev, flash);
|
||||
struct mt6370_priv *priv = led->priv;
|
||||
struct led_flash_setting *s = &fl_cdev->timeout;
|
||||
u32 val = (timeout - s->min) / s->step;
|
||||
|
||||
return regmap_update_bits(priv->regmap, MT6370_REG_STRBTO, MT6370_STRBTO_MASK, val);
|
||||
}
|
||||
|
||||
static int mt6370_fault_get(struct led_classdev_flash *fl_cdev, u32 *fault)
|
||||
{
|
||||
struct mt6370_led *led = to_mt6370_led(fl_cdev, flash);
|
||||
struct mt6370_priv *priv = led->priv;
|
||||
u16 fled_stat;
|
||||
unsigned int chg_stat, strobe_timeout_mask, fled_short_mask;
|
||||
u32 rfault = 0;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(priv->regmap, MT6370_REG_CHGSTAT2, &chg_stat);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_raw_read(priv->regmap, MT6370_REG_FLEDSTAT1, &fled_stat, sizeof(fled_stat));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (led->led_no) {
|
||||
case MT6370_LED_FLASH1:
|
||||
strobe_timeout_mask = MT6370_FLED1STRBTO_MASK;
|
||||
fled_short_mask = MT6370_FLED1SHORT_MASK;
|
||||
break;
|
||||
|
||||
case MT6370_LED_FLASH2:
|
||||
strobe_timeout_mask = MT6370_FLED2STRBTO_MASK;
|
||||
fled_short_mask = MT6370_FLED2SHORT_MASK;
|
||||
break;
|
||||
|
||||
case MT6370_LED_JOINT:
|
||||
strobe_timeout_mask = MT6370_FLED1STRBTO_MASK | MT6370_FLED2STRBTO_MASK;
|
||||
fled_short_mask = MT6370_FLED1SHORT_MASK | MT6370_FLED2SHORT_MASK;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (chg_stat & MT6370_FLEDCHGVINOVP_MASK)
|
||||
rfault |= LED_FAULT_INPUT_VOLTAGE;
|
||||
|
||||
if (fled_stat & strobe_timeout_mask)
|
||||
rfault |= LED_FAULT_TIMEOUT;
|
||||
|
||||
if (fled_stat & fled_short_mask)
|
||||
rfault |= LED_FAULT_SHORT_CIRCUIT;
|
||||
|
||||
if (fled_stat & MT6370_FLEDLVF_MASK)
|
||||
rfault |= LED_FAULT_UNDER_VOLTAGE;
|
||||
|
||||
*fault = rfault;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct led_flash_ops mt6370_flash_ops = {
|
||||
.flash_brightness_set = mt6370_flash_brightness_set,
|
||||
.strobe_set = mt6370_strobe_set,
|
||||
.strobe_get = mt6370_strobe_get,
|
||||
.timeout_set = mt6370_timeout_set,
|
||||
.fault_get = mt6370_fault_get,
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
|
||||
static int mt6370_flash_external_strobe_set(struct v4l2_flash *v4l2_flash,
|
||||
bool enable)
|
||||
{
|
||||
struct led_classdev_flash *flash = v4l2_flash->fled_cdev;
|
||||
struct mt6370_led *led = to_mt6370_led(flash, flash);
|
||||
struct mt6370_priv *priv = led->priv;
|
||||
u32 mask = led->led_no == MT6370_LED_JOINT ? MT6370_FLCSEN_MASK_ALL :
|
||||
MT6370_FLCSEN_MASK(led->led_no);
|
||||
u32 val = enable ? mask : 0;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&priv->lock);
|
||||
|
||||
ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDEN, mask, val);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
if (enable)
|
||||
priv->fled_strobe_used |= BIT(led->led_no);
|
||||
else
|
||||
priv->fled_strobe_used &= ~BIT(led->led_no);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&priv->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct v4l2_flash_ops v4l2_flash_ops = {
|
||||
.external_strobe_set = mt6370_flash_external_strobe_set,
|
||||
};
|
||||
|
||||
static void mt6370_init_v4l2_flash_config(struct mt6370_led *led, struct v4l2_flash_config *cfg)
|
||||
{
|
||||
struct led_classdev *lcdev;
|
||||
struct led_flash_setting *s = &cfg->intensity;
|
||||
|
||||
lcdev = &led->flash.led_cdev;
|
||||
|
||||
s->min = MT6370_ITORCH_MIN_uA;
|
||||
s->step = MT6370_ITORCH_STEP_uA;
|
||||
s->val = s->max = s->min + (lcdev->max_brightness - 1) * s->step;
|
||||
|
||||
cfg->has_external_strobe = 1;
|
||||
strscpy(cfg->dev_name, dev_name(lcdev->dev), sizeof(cfg->dev_name));
|
||||
|
||||
cfg->flash_faults = LED_FAULT_SHORT_CIRCUIT | LED_FAULT_TIMEOUT |
|
||||
LED_FAULT_INPUT_VOLTAGE | LED_FAULT_UNDER_VOLTAGE;
|
||||
}
|
||||
#else
|
||||
static const struct v4l2_flash_ops v4l2_flash_ops;
|
||||
static void mt6370_init_v4l2_flash_config(struct mt6370_led *led, struct v4l2_flash_config *cfg)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static void mt6370_v4l2_flash_release(void *v4l2_flash)
|
||||
{
|
||||
v4l2_flash_release(v4l2_flash);
|
||||
}
|
||||
|
||||
static int mt6370_led_register(struct device *parent, struct mt6370_led *led,
|
||||
struct fwnode_handle *fwnode)
|
||||
{
|
||||
struct led_init_data init_data = { .fwnode = fwnode };
|
||||
struct v4l2_flash_config v4l2_config = {};
|
||||
int ret;
|
||||
|
||||
ret = devm_led_classdev_flash_register_ext(parent, &led->flash, &init_data);
|
||||
if (ret)
|
||||
return dev_err_probe(parent, ret, "Couldn't register flash %d\n", led->led_no);
|
||||
|
||||
mt6370_init_v4l2_flash_config(led, &v4l2_config);
|
||||
led->v4l2_flash = v4l2_flash_init(parent, fwnode, &led->flash, &v4l2_flash_ops,
|
||||
&v4l2_config);
|
||||
if (IS_ERR(led->v4l2_flash))
|
||||
return dev_err_probe(parent, PTR_ERR(led->v4l2_flash),
|
||||
"Failed to register %d v4l2 sd\n", led->led_no);
|
||||
|
||||
return devm_add_action_or_reset(parent, mt6370_v4l2_flash_release, led->v4l2_flash);
|
||||
}
|
||||
|
||||
static u32 mt6370_clamp(u32 val, u32 min, u32 max, u32 step)
|
||||
{
|
||||
u32 retval;
|
||||
|
||||
retval = clamp_val(val, min, max);
|
||||
if (step > 1)
|
||||
retval = rounddown(retval - min, step) + min;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int mt6370_init_flash_properties(struct device *dev, struct mt6370_led *led,
|
||||
struct fwnode_handle *fwnode)
|
||||
{
|
||||
struct led_classdev_flash *flash = &led->flash;
|
||||
struct led_classdev *lcdev = &flash->led_cdev;
|
||||
struct mt6370_priv *priv = led->priv;
|
||||
struct led_flash_setting *s;
|
||||
u32 sources[MT6370_MAX_LEDS];
|
||||
u32 max_ua, val;
|
||||
int i, ret, num;
|
||||
|
||||
num = fwnode_property_count_u32(fwnode, "led-sources");
|
||||
if (num < 1)
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"Not specified or wrong number of led-sources\n");
|
||||
|
||||
ret = fwnode_property_read_u32_array(fwnode, "led-sources", sources, num);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
if (sources[i] >= MT6370_MAX_LEDS)
|
||||
return -EINVAL;
|
||||
if (priv->leds_active & BIT(sources[i]))
|
||||
return -EINVAL;
|
||||
priv->leds_active |= BIT(sources[i]);
|
||||
}
|
||||
|
||||
/* If both channels are specified in 'led-sources', joint flash output mode is used */
|
||||
led->led_no = num == 2 ? MT6370_LED_JOINT : sources[0];
|
||||
|
||||
max_ua = num == 2 ? MT6370_ITORCH_DOUBLE_MAX_uA : MT6370_ITORCH_MAX_uA;
|
||||
val = MT6370_ITORCH_MIN_uA;
|
||||
ret = fwnode_property_read_u32(fwnode, "led-max-microamp", &val);
|
||||
if (!ret)
|
||||
val = mt6370_clamp(val, MT6370_ITORCH_MIN_uA, max_ua, MT6370_ITORCH_STEP_uA);
|
||||
|
||||
lcdev->max_brightness = (val - MT6370_ITORCH_MIN_uA) / MT6370_ITORCH_STEP_uA + 1;
|
||||
lcdev->brightness_set_blocking = mt6370_torch_brightness_set;
|
||||
lcdev->flags |= LED_DEV_CAP_FLASH;
|
||||
|
||||
max_ua = num == 2 ? MT6370_ISTRB_DOUBLE_MAX_uA : MT6370_ISTRB_MAX_uA;
|
||||
val = MT6370_ISTRB_MIN_uA;
|
||||
ret = fwnode_property_read_u32(fwnode, "flash-max-microamp", &val);
|
||||
if (!ret)
|
||||
val = mt6370_clamp(val, MT6370_ISTRB_MIN_uA, max_ua, MT6370_ISTRB_STEP_uA);
|
||||
|
||||
s = &flash->brightness;
|
||||
s->min = MT6370_ISTRB_MIN_uA;
|
||||
s->step = MT6370_ISTRB_STEP_uA;
|
||||
s->val = s->max = val;
|
||||
|
||||
/* Always configure to the minimum level when off to prevent flash current spikes. */
|
||||
ret = _mt6370_flash_brightness_set(flash, s->min);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = MT6370_STRBTO_MIN_US;
|
||||
ret = fwnode_property_read_u32(fwnode, "flash-max-timeout-us", &val);
|
||||
if (!ret)
|
||||
val = mt6370_clamp(val, MT6370_STRBTO_MIN_US, MT6370_STRBTO_MAX_US,
|
||||
MT6370_STRBTO_STEP_US);
|
||||
|
||||
s = &flash->timeout;
|
||||
s->min = MT6370_STRBTO_MIN_US;
|
||||
s->step = MT6370_STRBTO_STEP_US;
|
||||
s->val = s->max = val;
|
||||
|
||||
flash->ops = &mt6370_flash_ops;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt6370_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mt6370_priv *priv;
|
||||
struct fwnode_handle *child;
|
||||
size_t count;
|
||||
int i = 0, ret;
|
||||
|
||||
count = device_get_child_node_count(dev);
|
||||
if (!count || count > MT6370_MAX_LEDS)
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"No child node or node count over max led number %zu\n", count);
|
||||
|
||||
priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->leds_count = count;
|
||||
mutex_init(&priv->lock);
|
||||
|
||||
priv->regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!priv->regmap)
|
||||
return dev_err_probe(dev, -ENODEV, "Failed to get parent regmap\n");
|
||||
|
||||
device_for_each_child_node(dev, child) {
|
||||
struct mt6370_led *led = priv->leds + i;
|
||||
|
||||
led->priv = priv;
|
||||
|
||||
ret = mt6370_init_flash_properties(dev, led, child);
|
||||
if (ret) {
|
||||
fwnode_handle_put(child);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = mt6370_led_register(dev, led, child);
|
||||
if (ret) {
|
||||
fwnode_handle_put(child);
|
||||
return ret;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id mt6370_led_of_id[] = {
|
||||
{ .compatible = "mediatek,mt6370-flashlight" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mt6370_led_of_id);
|
||||
|
||||
static struct platform_driver mt6370_led_driver = {
|
||||
.driver = {
|
||||
.name = "mt6370-flashlight",
|
||||
.of_match_table = mt6370_led_of_id,
|
||||
},
|
||||
.probe = mt6370_led_probe,
|
||||
};
|
||||
module_platform_driver(mt6370_led_driver);
|
||||
|
||||
MODULE_AUTHOR("Alice Chen <alice_chen@richtek.com>");
|
||||
MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
|
||||
MODULE_DESCRIPTION("MT6370 FLASH LED Driver");
|
||||
MODULE_LICENSE("GPL");
|
773
drivers/leds/flash/leds-qcom-flash.c
Normal file
773
drivers/leds/flash/leds-qcom-flash.c
Normal file
@ -0,0 +1,773 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/led-class-flash.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <media/v4l2-flash-led-class.h>
|
||||
|
||||
/* registers definitions */
|
||||
#define FLASH_TYPE_REG 0x04
|
||||
#define FLASH_TYPE_VAL 0x18
|
||||
|
||||
#define FLASH_SUBTYPE_REG 0x05
|
||||
#define FLASH_SUBTYPE_3CH_VAL 0x04
|
||||
#define FLASH_SUBTYPE_4CH_VAL 0x07
|
||||
|
||||
#define FLASH_STS_3CH_OTST1 BIT(0)
|
||||
#define FLASH_STS_3CH_OTST2 BIT(1)
|
||||
#define FLASH_STS_3CH_OTST3 BIT(2)
|
||||
#define FLASH_STS_3CH_BOB_THM_OVERLOAD BIT(3)
|
||||
#define FLASH_STS_3CH_VPH_DROOP BIT(4)
|
||||
#define FLASH_STS_3CH_BOB_ILIM_S1 BIT(5)
|
||||
#define FLASH_STS_3CH_BOB_ILIM_S2 BIT(6)
|
||||
#define FLASH_STS_3CH_BCL_IBAT BIT(7)
|
||||
|
||||
#define FLASH_STS_4CH_VPH_LOW BIT(0)
|
||||
#define FLASH_STS_4CH_BCL_IBAT BIT(1)
|
||||
#define FLASH_STS_4CH_BOB_ILIM_S1 BIT(2)
|
||||
#define FLASH_STS_4CH_BOB_ILIM_S2 BIT(3)
|
||||
#define FLASH_STS_4CH_OTST2 BIT(4)
|
||||
#define FLASH_STS_4CH_OTST1 BIT(5)
|
||||
#define FLASH_STS_4CHG_BOB_THM_OVERLOAD BIT(6)
|
||||
|
||||
#define FLASH_TIMER_EN_BIT BIT(7)
|
||||
#define FLASH_TIMER_VAL_MASK GENMASK(6, 0)
|
||||
#define FLASH_TIMER_STEP_MS 10
|
||||
|
||||
#define FLASH_STROBE_HW_SW_SEL_BIT BIT(2)
|
||||
#define SW_STROBE_VAL 0
|
||||
#define HW_STROBE_VAL 1
|
||||
#define FLASH_HW_STROBE_TRIGGER_SEL_BIT BIT(1)
|
||||
#define STROBE_LEVEL_TRIGGER_VAL 0
|
||||
#define STROBE_EDGE_TRIGGER_VAL 1
|
||||
#define FLASH_STROBE_POLARITY_BIT BIT(0)
|
||||
#define STROBE_ACTIVE_HIGH_VAL 1
|
||||
|
||||
#define FLASH_IRES_MASK_4CH BIT(0)
|
||||
#define FLASH_IRES_MASK_3CH GENMASK(1, 0)
|
||||
#define FLASH_IRES_12P5MA_VAL 0
|
||||
#define FLASH_IRES_5MA_VAL_4CH 1
|
||||
#define FLASH_IRES_5MA_VAL_3CH 3
|
||||
|
||||
/* constants */
|
||||
#define FLASH_CURRENT_MAX_UA 1500000
|
||||
#define TORCH_CURRENT_MAX_UA 500000
|
||||
#define FLASH_TOTAL_CURRENT_MAX_UA 2000000
|
||||
#define FLASH_CURRENT_DEFAULT_UA 1000000
|
||||
#define TORCH_CURRENT_DEFAULT_UA 200000
|
||||
|
||||
#define TORCH_IRES_UA 5000
|
||||
#define FLASH_IRES_UA 12500
|
||||
|
||||
#define FLASH_TIMEOUT_MAX_US 1280000
|
||||
#define FLASH_TIMEOUT_STEP_US 10000
|
||||
|
||||
#define UA_PER_MA 1000
|
||||
|
||||
enum hw_type {
|
||||
QCOM_MVFLASH_3CH,
|
||||
QCOM_MVFLASH_4CH,
|
||||
};
|
||||
|
||||
enum led_mode {
|
||||
FLASH_MODE,
|
||||
TORCH_MODE,
|
||||
};
|
||||
|
||||
enum led_strobe {
|
||||
SW_STROBE,
|
||||
HW_STROBE,
|
||||
};
|
||||
|
||||
enum {
|
||||
REG_STATUS1,
|
||||
REG_STATUS2,
|
||||
REG_STATUS3,
|
||||
REG_CHAN_TIMER,
|
||||
REG_ITARGET,
|
||||
REG_MODULE_EN,
|
||||
REG_IRESOLUTION,
|
||||
REG_CHAN_STROBE,
|
||||
REG_CHAN_EN,
|
||||
REG_MAX_COUNT,
|
||||
};
|
||||
|
||||
static struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = {
|
||||
REG_FIELD(0x08, 0, 7), /* status1 */
|
||||
REG_FIELD(0x09, 0, 7), /* status2 */
|
||||
REG_FIELD(0x0a, 0, 7), /* status3 */
|
||||
REG_FIELD_ID(0x40, 0, 7, 3, 1), /* chan_timer */
|
||||
REG_FIELD_ID(0x43, 0, 6, 3, 1), /* itarget */
|
||||
REG_FIELD(0x46, 7, 7), /* module_en */
|
||||
REG_FIELD(0x47, 0, 5), /* iresolution */
|
||||
REG_FIELD_ID(0x49, 0, 2, 3, 1), /* chan_strobe */
|
||||
REG_FIELD(0x4c, 0, 2), /* chan_en */
|
||||
};
|
||||
|
||||
static struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = {
|
||||
REG_FIELD(0x06, 0, 7), /* status1 */
|
||||
REG_FIELD(0x07, 0, 6), /* status2 */
|
||||
REG_FIELD(0x09, 0, 7), /* status3 */
|
||||
REG_FIELD_ID(0x3e, 0, 7, 4, 1), /* chan_timer */
|
||||
REG_FIELD_ID(0x42, 0, 6, 4, 1), /* itarget */
|
||||
REG_FIELD(0x46, 7, 7), /* module_en */
|
||||
REG_FIELD(0x49, 0, 3), /* iresolution */
|
||||
REG_FIELD_ID(0x4a, 0, 6, 4, 1), /* chan_strobe */
|
||||
REG_FIELD(0x4e, 0, 3), /* chan_en */
|
||||
};
|
||||
|
||||
struct qcom_flash_data {
|
||||
struct v4l2_flash **v4l2_flash;
|
||||
struct regmap_field *r_fields[REG_MAX_COUNT];
|
||||
struct mutex lock;
|
||||
enum hw_type hw_type;
|
||||
u8 leds_count;
|
||||
u8 max_channels;
|
||||
u8 chan_en_bits;
|
||||
};
|
||||
|
||||
struct qcom_flash_led {
|
||||
struct qcom_flash_data *flash_data;
|
||||
struct led_classdev_flash flash;
|
||||
u32 max_flash_current_ma;
|
||||
u32 max_torch_current_ma;
|
||||
u32 max_timeout_ms;
|
||||
u32 flash_current_ma;
|
||||
u32 flash_timeout_ms;
|
||||
u8 *chan_id;
|
||||
u8 chan_count;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
static int set_flash_module_en(struct qcom_flash_led *led, bool en)
|
||||
{
|
||||
struct qcom_flash_data *flash_data = led->flash_data;
|
||||
u8 led_mask = 0, enable;
|
||||
int i, rc;
|
||||
|
||||
for (i = 0; i < led->chan_count; i++)
|
||||
led_mask |= BIT(led->chan_id[i]);
|
||||
|
||||
mutex_lock(&flash_data->lock);
|
||||
if (en)
|
||||
flash_data->chan_en_bits |= led_mask;
|
||||
else
|
||||
flash_data->chan_en_bits &= ~led_mask;
|
||||
|
||||
enable = !!flash_data->chan_en_bits;
|
||||
rc = regmap_field_write(flash_data->r_fields[REG_MODULE_EN], enable);
|
||||
if (rc)
|
||||
dev_err(led->flash.led_cdev.dev, "write module_en failed, rc=%d\n", rc);
|
||||
mutex_unlock(&flash_data->lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int set_flash_current(struct qcom_flash_led *led, u32 current_ma, enum led_mode mode)
|
||||
{
|
||||
struct qcom_flash_data *flash_data = led->flash_data;
|
||||
u32 itarg_ua, ires_ua;
|
||||
u8 shift, ires_mask = 0, ires_val = 0, chan_id;
|
||||
int i, rc;
|
||||
|
||||
/*
|
||||
* Split the current across the channels and set the
|
||||
* IRESOLUTION and ITARGET registers accordingly.
|
||||
*/
|
||||
itarg_ua = (current_ma * UA_PER_MA) / led->chan_count + 1;
|
||||
ires_ua = (mode == FLASH_MODE) ? FLASH_IRES_UA : TORCH_IRES_UA;
|
||||
|
||||
for (i = 0; i < led->chan_count; i++) {
|
||||
u8 itarget = 0;
|
||||
|
||||
if (itarg_ua > ires_ua)
|
||||
itarget = itarg_ua / ires_ua - 1;
|
||||
|
||||
chan_id = led->chan_id[i];
|
||||
|
||||
rc = regmap_fields_write(flash_data->r_fields[REG_ITARGET], chan_id, itarget);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
|
||||
shift = chan_id * 2;
|
||||
ires_mask |= FLASH_IRES_MASK_3CH << shift;
|
||||
ires_val |= ((mode == FLASH_MODE) ?
|
||||
(FLASH_IRES_12P5MA_VAL << shift) :
|
||||
(FLASH_IRES_5MA_VAL_3CH << shift));
|
||||
} else if (flash_data->hw_type == QCOM_MVFLASH_4CH) {
|
||||
shift = chan_id;
|
||||
ires_mask |= FLASH_IRES_MASK_4CH << shift;
|
||||
ires_val |= ((mode == FLASH_MODE) ?
|
||||
(FLASH_IRES_12P5MA_VAL << shift) :
|
||||
(FLASH_IRES_5MA_VAL_4CH << shift));
|
||||
} else {
|
||||
dev_err(led->flash.led_cdev.dev,
|
||||
"HW type %d is not supported\n", flash_data->hw_type);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
return regmap_field_update_bits(flash_data->r_fields[REG_IRESOLUTION], ires_mask, ires_val);
|
||||
}
|
||||
|
||||
static int set_flash_timeout(struct qcom_flash_led *led, u32 timeout_ms)
|
||||
{
|
||||
struct qcom_flash_data *flash_data = led->flash_data;
|
||||
u8 timer, chan_id;
|
||||
int rc, i;
|
||||
|
||||
/* set SAFETY_TIMER for all the channels connected to the same LED */
|
||||
timeout_ms = min_t(u32, timeout_ms, led->max_timeout_ms);
|
||||
|
||||
for (i = 0; i < led->chan_count; i++) {
|
||||
chan_id = led->chan_id[i];
|
||||
|
||||
timer = timeout_ms / FLASH_TIMER_STEP_MS;
|
||||
timer = clamp_t(u8, timer, 0, FLASH_TIMER_VAL_MASK);
|
||||
|
||||
if (timeout_ms)
|
||||
timer |= FLASH_TIMER_EN_BIT;
|
||||
|
||||
rc = regmap_fields_write(flash_data->r_fields[REG_CHAN_TIMER], chan_id, timer);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_flash_strobe(struct qcom_flash_led *led, enum led_strobe strobe, bool state)
|
||||
{
|
||||
struct qcom_flash_data *flash_data = led->flash_data;
|
||||
u8 strobe_sel, chan_en, chan_id, chan_mask = 0;
|
||||
int rc, i;
|
||||
|
||||
/* Set SW strobe config for all channels connected to the LED */
|
||||
for (i = 0; i < led->chan_count; i++) {
|
||||
chan_id = led->chan_id[i];
|
||||
|
||||
if (strobe == SW_STROBE)
|
||||
strobe_sel = FIELD_PREP(FLASH_STROBE_HW_SW_SEL_BIT, SW_STROBE_VAL);
|
||||
else
|
||||
strobe_sel = FIELD_PREP(FLASH_STROBE_HW_SW_SEL_BIT, HW_STROBE_VAL);
|
||||
|
||||
strobe_sel |=
|
||||
FIELD_PREP(FLASH_HW_STROBE_TRIGGER_SEL_BIT, STROBE_LEVEL_TRIGGER_VAL) |
|
||||
FIELD_PREP(FLASH_STROBE_POLARITY_BIT, STROBE_ACTIVE_HIGH_VAL);
|
||||
|
||||
rc = regmap_fields_write(
|
||||
flash_data->r_fields[REG_CHAN_STROBE], chan_id, strobe_sel);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
chan_mask |= BIT(chan_id);
|
||||
}
|
||||
|
||||
/* Enable/disable flash channels */
|
||||
chan_en = state ? chan_mask : 0;
|
||||
rc = regmap_field_update_bits(flash_data->r_fields[REG_CHAN_EN], chan_mask, chan_en);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
led->enabled = state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline struct qcom_flash_led *flcdev_to_qcom_fled(struct led_classdev_flash *flcdev)
|
||||
{
|
||||
return container_of(flcdev, struct qcom_flash_led, flash);
|
||||
}
|
||||
|
||||
static int qcom_flash_brightness_set(struct led_classdev_flash *fled_cdev, u32 brightness)
|
||||
{
|
||||
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
||||
|
||||
led->flash_current_ma = min_t(u32, led->max_flash_current_ma, brightness / UA_PER_MA);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_flash_timeout_set(struct led_classdev_flash *fled_cdev, u32 timeout)
|
||||
{
|
||||
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
||||
|
||||
led->flash_timeout_ms = timeout / USEC_PER_MSEC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_flash_strobe_set(struct led_classdev_flash *fled_cdev, bool state)
|
||||
{
|
||||
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
||||
int rc;
|
||||
|
||||
rc = set_flash_current(led, led->flash_current_ma, FLASH_MODE);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = set_flash_timeout(led, led->flash_timeout_ms);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = set_flash_module_en(led, state);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return set_flash_strobe(led, SW_STROBE, state);
|
||||
}
|
||||
|
||||
static int qcom_flash_strobe_get(struct led_classdev_flash *fled_cdev, bool *state)
|
||||
{
|
||||
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
||||
|
||||
*state = led->enabled;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_flash_fault_get(struct led_classdev_flash *fled_cdev, u32 *fault)
|
||||
{
|
||||
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
||||
struct qcom_flash_data *flash_data = led->flash_data;
|
||||
u8 shift, chan_id, chan_mask = 0;
|
||||
u8 ot_mask = 0, oc_mask = 0, uv_mask = 0;
|
||||
u32 val, fault_sts = 0;
|
||||
int i, rc;
|
||||
|
||||
rc = regmap_field_read(flash_data->r_fields[REG_STATUS1], &val);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
for (i = 0; i < led->chan_count; i++) {
|
||||
chan_id = led->chan_id[i];
|
||||
shift = chan_id * 2;
|
||||
|
||||
if (val & BIT(shift))
|
||||
fault_sts |= LED_FAULT_SHORT_CIRCUIT;
|
||||
|
||||
chan_mask |= BIT(chan_id);
|
||||
}
|
||||
|
||||
rc = regmap_field_read(flash_data->r_fields[REG_STATUS2], &val);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
|
||||
ot_mask = FLASH_STS_3CH_OTST1 |
|
||||
FLASH_STS_3CH_OTST2 |
|
||||
FLASH_STS_3CH_OTST3 |
|
||||
FLASH_STS_3CH_BOB_THM_OVERLOAD;
|
||||
oc_mask = FLASH_STS_3CH_BOB_ILIM_S1 |
|
||||
FLASH_STS_3CH_BOB_ILIM_S2 |
|
||||
FLASH_STS_3CH_BCL_IBAT;
|
||||
uv_mask = FLASH_STS_3CH_VPH_DROOP;
|
||||
} else if (flash_data->hw_type == QCOM_MVFLASH_4CH) {
|
||||
ot_mask = FLASH_STS_4CH_OTST2 |
|
||||
FLASH_STS_4CH_OTST1 |
|
||||
FLASH_STS_4CHG_BOB_THM_OVERLOAD;
|
||||
oc_mask = FLASH_STS_4CH_BCL_IBAT |
|
||||
FLASH_STS_4CH_BOB_ILIM_S1 |
|
||||
FLASH_STS_4CH_BOB_ILIM_S2;
|
||||
uv_mask = FLASH_STS_4CH_VPH_LOW;
|
||||
}
|
||||
|
||||
if (val & ot_mask)
|
||||
fault_sts |= LED_FAULT_OVER_TEMPERATURE;
|
||||
|
||||
if (val & oc_mask)
|
||||
fault_sts |= LED_FAULT_OVER_CURRENT;
|
||||
|
||||
if (val & uv_mask)
|
||||
fault_sts |= LED_FAULT_INPUT_VOLTAGE;
|
||||
|
||||
rc = regmap_field_read(flash_data->r_fields[REG_STATUS3], &val);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
|
||||
if (val & chan_mask)
|
||||
fault_sts |= LED_FAULT_TIMEOUT;
|
||||
} else if (flash_data->hw_type == QCOM_MVFLASH_4CH) {
|
||||
for (i = 0; i < led->chan_count; i++) {
|
||||
chan_id = led->chan_id[i];
|
||||
shift = chan_id * 2;
|
||||
|
||||
if (val & BIT(shift))
|
||||
fault_sts |= LED_FAULT_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
*fault = fault_sts;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_flash_led_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
|
||||
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
||||
u32 current_ma = brightness * led->max_torch_current_ma / LED_FULL;
|
||||
bool enable = !!brightness;
|
||||
int rc;
|
||||
|
||||
rc = set_flash_current(led, current_ma, TORCH_MODE);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Disable flash timeout for torch LED */
|
||||
rc = set_flash_timeout(led, 0);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = set_flash_module_en(led, enable);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return set_flash_strobe(led, SW_STROBE, enable);
|
||||
}
|
||||
|
||||
static const struct led_flash_ops qcom_flash_ops = {
|
||||
.flash_brightness_set = qcom_flash_brightness_set,
|
||||
.strobe_set = qcom_flash_strobe_set,
|
||||
.strobe_get = qcom_flash_strobe_get,
|
||||
.timeout_set = qcom_flash_timeout_set,
|
||||
.fault_get = qcom_flash_fault_get,
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
|
||||
static int qcom_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable)
|
||||
{
|
||||
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
||||
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
||||
int rc;
|
||||
|
||||
rc = set_flash_module_en(led, enable);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (enable)
|
||||
return set_flash_strobe(led, HW_STROBE, true);
|
||||
else
|
||||
return set_flash_strobe(led, SW_STROBE, false);
|
||||
}
|
||||
|
||||
static enum led_brightness
|
||||
qcom_flash_intensity_to_led_brightness(struct v4l2_flash *v4l2_flash, s32 intensity)
|
||||
{
|
||||
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
||||
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
||||
u32 current_ma = intensity / UA_PER_MA;
|
||||
|
||||
current_ma = min_t(u32, current_ma, led->max_torch_current_ma);
|
||||
if (!current_ma)
|
||||
return LED_OFF;
|
||||
|
||||
return (current_ma * LED_FULL) / led->max_torch_current_ma;
|
||||
}
|
||||
|
||||
static s32 qcom_flash_brightness_to_led_intensity(struct v4l2_flash *v4l2_flash,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
||||
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
||||
|
||||
return (brightness * led->max_torch_current_ma * UA_PER_MA) / LED_FULL;
|
||||
}
|
||||
|
||||
static const struct v4l2_flash_ops qcom_v4l2_flash_ops = {
|
||||
.external_strobe_set = qcom_flash_external_strobe_set,
|
||||
.intensity_to_led_brightness = qcom_flash_intensity_to_led_brightness,
|
||||
.led_brightness_to_intensity = qcom_flash_brightness_to_led_intensity,
|
||||
};
|
||||
|
||||
static int
|
||||
qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwnode_handle *fwnode)
|
||||
{
|
||||
struct qcom_flash_data *flash_data = led->flash_data;
|
||||
struct v4l2_flash_config v4l2_cfg = { 0 };
|
||||
struct led_flash_setting *intensity = &v4l2_cfg.intensity;
|
||||
|
||||
if (!(led->flash.led_cdev.flags & LED_DEV_CAP_FLASH))
|
||||
return 0;
|
||||
|
||||
intensity->min = intensity->step = TORCH_IRES_UA * led->chan_count;
|
||||
intensity->max = led->max_torch_current_ma * UA_PER_MA;
|
||||
intensity->val = min_t(u32, intensity->max, TORCH_CURRENT_DEFAULT_UA);
|
||||
|
||||
strscpy(v4l2_cfg.dev_name, led->flash.led_cdev.dev->kobj.name,
|
||||
sizeof(v4l2_cfg.dev_name));
|
||||
|
||||
v4l2_cfg.has_external_strobe = true;
|
||||
v4l2_cfg.flash_faults = LED_FAULT_INPUT_VOLTAGE |
|
||||
LED_FAULT_OVER_CURRENT |
|
||||
LED_FAULT_SHORT_CIRCUIT |
|
||||
LED_FAULT_OVER_TEMPERATURE |
|
||||
LED_FAULT_TIMEOUT;
|
||||
|
||||
flash_data->v4l2_flash[flash_data->leds_count] =
|
||||
v4l2_flash_init(dev, fwnode, &led->flash, &qcom_v4l2_flash_ops, &v4l2_cfg);
|
||||
return PTR_ERR_OR_ZERO(flash_data->v4l2_flash);
|
||||
}
|
||||
# else
|
||||
static int
|
||||
qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwnode_handle *fwnode)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int qcom_flash_register_led_device(struct device *dev,
|
||||
struct fwnode_handle *node, struct qcom_flash_led *led)
|
||||
{
|
||||
struct qcom_flash_data *flash_data = led->flash_data;
|
||||
struct led_init_data init_data;
|
||||
struct led_classdev_flash *flash = &led->flash;
|
||||
struct led_flash_setting *brightness, *timeout;
|
||||
u32 count, current_ua, timeout_us;
|
||||
u32 channels[4];
|
||||
int i, rc;
|
||||
|
||||
count = fwnode_property_count_u32(node, "led-sources");
|
||||
if (count <= 0) {
|
||||
dev_err(dev, "No led-sources specified\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (count > flash_data->max_channels) {
|
||||
dev_err(dev, "led-sources count %u exceeds maximum channel count %u\n",
|
||||
count, flash_data->max_channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = fwnode_property_read_u32_array(node, "led-sources", channels, count);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "Failed to read led-sources property, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
led->chan_count = count;
|
||||
led->chan_id = devm_kcalloc(dev, count, sizeof(u8), GFP_KERNEL);
|
||||
if (!led->chan_id)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if ((channels[i] == 0) || (channels[i] > flash_data->max_channels)) {
|
||||
dev_err(dev, "led-source out of HW support range [1-%u]\n",
|
||||
flash_data->max_channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Make chan_id indexing from 0 */
|
||||
led->chan_id[i] = channels[i] - 1;
|
||||
}
|
||||
|
||||
rc = fwnode_property_read_u32(node, "led-max-microamp", ¤t_ua);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "Failed to read led-max-microamp property, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (current_ua == 0) {
|
||||
dev_err(dev, "led-max-microamp shouldn't be 0\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
current_ua = min_t(u32, current_ua, TORCH_CURRENT_MAX_UA * led->chan_count);
|
||||
led->max_torch_current_ma = current_ua / UA_PER_MA;
|
||||
|
||||
if (fwnode_property_present(node, "flash-max-microamp")) {
|
||||
flash->led_cdev.flags |= LED_DEV_CAP_FLASH;
|
||||
|
||||
rc = fwnode_property_read_u32(node, "flash-max-microamp", ¤t_ua);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "Failed to read flash-max-microamp property, rc=%d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
current_ua = min_t(u32, current_ua, FLASH_CURRENT_MAX_UA * led->chan_count);
|
||||
current_ua = min_t(u32, current_ua, FLASH_TOTAL_CURRENT_MAX_UA);
|
||||
|
||||
/* Initialize flash class LED device brightness settings */
|
||||
brightness = &flash->brightness;
|
||||
brightness->min = brightness->step = FLASH_IRES_UA * led->chan_count;
|
||||
brightness->max = current_ua;
|
||||
brightness->val = min_t(u32, current_ua, FLASH_CURRENT_DEFAULT_UA);
|
||||
|
||||
led->max_flash_current_ma = current_ua / UA_PER_MA;
|
||||
led->flash_current_ma = brightness->val / UA_PER_MA;
|
||||
|
||||
rc = fwnode_property_read_u32(node, "flash-max-timeout-us", &timeout_us);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "Failed to read flash-max-timeout-us property, rc=%d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
timeout_us = min_t(u32, timeout_us, FLASH_TIMEOUT_MAX_US);
|
||||
|
||||
/* Initialize flash class LED device timeout settings */
|
||||
timeout = &flash->timeout;
|
||||
timeout->min = timeout->step = FLASH_TIMEOUT_STEP_US;
|
||||
timeout->val = timeout->max = timeout_us;
|
||||
|
||||
led->max_timeout_ms = led->flash_timeout_ms = timeout_us / USEC_PER_MSEC;
|
||||
|
||||
flash->ops = &qcom_flash_ops;
|
||||
}
|
||||
|
||||
flash->led_cdev.brightness_set_blocking = qcom_flash_led_brightness_set;
|
||||
|
||||
init_data.fwnode = node;
|
||||
init_data.devicename = NULL;
|
||||
init_data.default_label = NULL;
|
||||
init_data.devname_mandatory = false;
|
||||
|
||||
rc = devm_led_classdev_flash_register_ext(dev, flash, &init_data);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "Register flash LED classdev failed, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return qcom_flash_v4l2_init(dev, led, node);
|
||||
}
|
||||
|
||||
static int qcom_flash_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct qcom_flash_data *flash_data;
|
||||
struct qcom_flash_led *led;
|
||||
struct fwnode_handle *child;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct regmap *regmap;
|
||||
struct reg_field *regs;
|
||||
int count, i, rc;
|
||||
u32 val, reg_base;
|
||||
|
||||
flash_data = devm_kzalloc(dev, sizeof(*flash_data), GFP_KERNEL);
|
||||
if (!flash_data)
|
||||
return -ENOMEM;
|
||||
|
||||
regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!regmap) {
|
||||
dev_err(dev, "Failed to get parent regmap\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = fwnode_property_read_u32(dev->fwnode, "reg", ®_base);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "Failed to get register base address, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = regmap_read(regmap, reg_base + FLASH_TYPE_REG, &val);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "Read flash LED module type failed, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (val != FLASH_TYPE_VAL) {
|
||||
dev_err(dev, "type %#x is not a flash LED module\n", val);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
rc = regmap_read(regmap, reg_base + FLASH_SUBTYPE_REG, &val);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "Read flash LED module subtype failed, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (val == FLASH_SUBTYPE_3CH_VAL) {
|
||||
flash_data->hw_type = QCOM_MVFLASH_3CH;
|
||||
flash_data->max_channels = 3;
|
||||
regs = mvflash_3ch_regs;
|
||||
} else if (val == FLASH_SUBTYPE_4CH_VAL) {
|
||||
flash_data->hw_type = QCOM_MVFLASH_4CH;
|
||||
flash_data->max_channels = 4;
|
||||
regs = mvflash_4ch_regs;
|
||||
} else {
|
||||
dev_err(dev, "flash LED subtype %#x is not yet supported\n", val);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
for (i = 0; i < REG_MAX_COUNT; i++)
|
||||
regs[i].reg += reg_base;
|
||||
|
||||
rc = devm_regmap_field_bulk_alloc(dev, regmap, flash_data->r_fields, regs, REG_MAX_COUNT);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "Failed to allocate regmap field, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, flash_data);
|
||||
mutex_init(&flash_data->lock);
|
||||
|
||||
count = device_get_child_node_count(dev);
|
||||
if (count == 0 || count > flash_data->max_channels) {
|
||||
dev_err(dev, "No child or child count exceeds %d\n", flash_data->max_channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
flash_data->v4l2_flash = devm_kcalloc(dev, count,
|
||||
sizeof(*flash_data->v4l2_flash), GFP_KERNEL);
|
||||
if (!flash_data->v4l2_flash)
|
||||
return -ENOMEM;
|
||||
|
||||
device_for_each_child_node(dev, child) {
|
||||
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
|
||||
if (!led) {
|
||||
rc = -ENOMEM;
|
||||
goto release;
|
||||
}
|
||||
|
||||
led->flash_data = flash_data;
|
||||
rc = qcom_flash_register_led_device(dev, child, led);
|
||||
if (rc < 0)
|
||||
goto release;
|
||||
|
||||
flash_data->leds_count++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
release:
|
||||
while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count)
|
||||
v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int qcom_flash_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct qcom_flash_data *flash_data = platform_get_drvdata(pdev);
|
||||
|
||||
while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count)
|
||||
v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]);
|
||||
|
||||
mutex_destroy(&flash_data->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id qcom_flash_led_match_table[] = {
|
||||
{ .compatible = "qcom,spmi-flash-led" },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, qcom_flash_led_match_table);
|
||||
static struct platform_driver qcom_flash_led_driver = {
|
||||
.driver = {
|
||||
.name = "leds-qcom-flash",
|
||||
.of_match_table = qcom_flash_led_match_table,
|
||||
},
|
||||
.probe = qcom_flash_led_probe,
|
||||
.remove = qcom_flash_led_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(qcom_flash_led_driver);
|
||||
|
||||
MODULE_DESCRIPTION("QCOM Flash LED driver");
|
||||
MODULE_LICENSE("GPL");
|
160
drivers/leds/leds-bd2606mvv.c
Normal file
160
drivers/leds/leds-bd2606mvv.c
Normal file
@ -0,0 +1,160 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2023 Andreas Kemnade
|
||||
*
|
||||
* Datasheet:
|
||||
* https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/led_driver/bd2606mvv_1-e.pdf
|
||||
*
|
||||
* If LED brightness cannot be controlled independently due to shared
|
||||
* brightness registers, max_brightness is set to 1 and only on/off
|
||||
* is possible for the affected LED pair.
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define BD2606_MAX_LEDS 6
|
||||
#define BD2606_MAX_BRIGHTNESS 63
|
||||
#define BD2606_REG_PWRCNT 3
|
||||
#define ldev_to_led(c) container_of(c, struct bd2606mvv_led, ldev)
|
||||
|
||||
struct bd2606mvv_led {
|
||||
unsigned int led_no;
|
||||
struct led_classdev ldev;
|
||||
struct bd2606mvv_priv *priv;
|
||||
};
|
||||
|
||||
struct bd2606mvv_priv {
|
||||
struct bd2606mvv_led leds[BD2606_MAX_LEDS];
|
||||
struct regmap *regmap;
|
||||
};
|
||||
|
||||
static int
|
||||
bd2606mvv_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct bd2606mvv_led *led = ldev_to_led(led_cdev);
|
||||
struct bd2606mvv_priv *priv = led->priv;
|
||||
int err;
|
||||
|
||||
if (brightness == 0)
|
||||
return regmap_update_bits(priv->regmap,
|
||||
BD2606_REG_PWRCNT,
|
||||
1 << led->led_no,
|
||||
0);
|
||||
|
||||
/* shared brightness register */
|
||||
err = regmap_write(priv->regmap, led->led_no / 2,
|
||||
led_cdev->max_brightness == 1 ?
|
||||
BD2606_MAX_BRIGHTNESS : brightness);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return regmap_update_bits(priv->regmap,
|
||||
BD2606_REG_PWRCNT,
|
||||
1 << led->led_no,
|
||||
1 << led->led_no);
|
||||
}
|
||||
|
||||
static const struct regmap_config bd2606mvv_regmap = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = 0x3,
|
||||
};
|
||||
|
||||
static int bd2606mvv_probe(struct i2c_client *client)
|
||||
{
|
||||
struct fwnode_handle *np, *child;
|
||||
struct device *dev = &client->dev;
|
||||
struct bd2606mvv_priv *priv;
|
||||
struct fwnode_handle *led_fwnodes[BD2606_MAX_LEDS] = { 0 };
|
||||
int active_pairs[BD2606_MAX_LEDS / 2] = { 0 };
|
||||
int err, reg;
|
||||
int i;
|
||||
|
||||
np = dev_fwnode(dev);
|
||||
if (!np)
|
||||
return -ENODEV;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->regmap = devm_regmap_init_i2c(client, &bd2606mvv_regmap);
|
||||
if (IS_ERR(priv->regmap)) {
|
||||
err = PTR_ERR(priv->regmap);
|
||||
dev_err(dev, "Failed to allocate register map: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, priv);
|
||||
|
||||
fwnode_for_each_available_child_node(np, child) {
|
||||
struct bd2606mvv_led *led;
|
||||
|
||||
err = fwnode_property_read_u32(child, "reg", ®);
|
||||
if (err) {
|
||||
fwnode_handle_put(child);
|
||||
return err;
|
||||
}
|
||||
if (reg < 0 || reg >= BD2606_MAX_LEDS || led_fwnodes[reg]) {
|
||||
fwnode_handle_put(child);
|
||||
return -EINVAL;
|
||||
}
|
||||
led = &priv->leds[reg];
|
||||
led_fwnodes[reg] = child;
|
||||
active_pairs[reg / 2]++;
|
||||
led->priv = priv;
|
||||
led->led_no = reg;
|
||||
led->ldev.brightness_set_blocking = bd2606mvv_brightness_set;
|
||||
led->ldev.max_brightness = BD2606_MAX_BRIGHTNESS;
|
||||
}
|
||||
|
||||
for (i = 0; i < BD2606_MAX_LEDS; i++) {
|
||||
struct led_init_data init_data = {};
|
||||
|
||||
if (!led_fwnodes[i])
|
||||
continue;
|
||||
|
||||
init_data.fwnode = led_fwnodes[i];
|
||||
/* Check whether brightness can be independently adjusted. */
|
||||
if (active_pairs[i / 2] == 2)
|
||||
priv->leds[i].ldev.max_brightness = 1;
|
||||
|
||||
err = devm_led_classdev_register_ext(dev,
|
||||
&priv->leds[i].ldev,
|
||||
&init_data);
|
||||
if (err < 0) {
|
||||
fwnode_handle_put(child);
|
||||
return dev_err_probe(dev, err,
|
||||
"couldn't register LED %s\n",
|
||||
priv->leds[i].ldev.name);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id __maybe_unused of_bd2606mvv_leds_match[] = {
|
||||
{ .compatible = "rohm,bd2606mvv", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_bd2606mvv_leds_match);
|
||||
|
||||
static struct i2c_driver bd2606mvv_driver = {
|
||||
.driver = {
|
||||
.name = "leds-bd2606mvv",
|
||||
.of_match_table = of_match_ptr(of_bd2606mvv_leds_match),
|
||||
},
|
||||
.probe_new = bd2606mvv_probe,
|
||||
};
|
||||
|
||||
module_i2c_driver(bd2606mvv_driver);
|
||||
|
||||
MODULE_AUTHOR("Andreas Kemnade <andreas@kemnade.info>");
|
||||
MODULE_DESCRIPTION("BD2606 LED driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -15,7 +15,6 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
@ -250,8 +249,7 @@ static int lp8860_init(struct lp8860_led *led)
|
||||
}
|
||||
}
|
||||
|
||||
if (led->enable_gpio)
|
||||
gpiod_direction_output(led->enable_gpio, 1);
|
||||
gpiod_direction_output(led->enable_gpio, 1);
|
||||
|
||||
ret = lp8860_fault_check(led);
|
||||
if (ret)
|
||||
@ -294,8 +292,7 @@ static int lp8860_init(struct lp8860_led *led)
|
||||
|
||||
out:
|
||||
if (ret)
|
||||
if (led->enable_gpio)
|
||||
gpiod_direction_output(led->enable_gpio, 0);
|
||||
gpiod_direction_output(led->enable_gpio, 0);
|
||||
|
||||
if (led->regulator) {
|
||||
ret = regulator_disable(led->regulator);
|
||||
@ -449,8 +446,7 @@ static void lp8860_remove(struct i2c_client *client)
|
||||
struct lp8860_led *led = i2c_get_clientdata(client);
|
||||
int ret;
|
||||
|
||||
if (led->enable_gpio)
|
||||
gpiod_direction_output(led->enable_gpio, 0);
|
||||
gpiod_direction_output(led->enable_gpio, 0);
|
||||
|
||||
if (led->regulator) {
|
||||
ret = regulator_disable(led->regulator);
|
||||
|
@ -691,8 +691,9 @@ tca6507_led_dt_init(struct device *dev)
|
||||
if (fwnode_property_read_string(child, "label", &led.name))
|
||||
led.name = fwnode_get_name(child);
|
||||
|
||||
fwnode_property_read_string(child, "linux,default-trigger",
|
||||
&led.default_trigger);
|
||||
if (fwnode_property_read_string(child, "linux,default-trigger",
|
||||
&led.default_trigger))
|
||||
led.default_trigger = NULL;
|
||||
|
||||
led.flags = 0;
|
||||
if (fwnode_device_is_compatible(child, "gpio"))
|
||||
|
@ -135,7 +135,7 @@ static const struct regmap_config tlc591xx_regmap = {
|
||||
.max_register = 0x1e,
|
||||
};
|
||||
|
||||
static const struct of_device_id of_tlc591xx_leds_match[] = {
|
||||
static const struct of_device_id of_tlc591xx_leds_match[] __maybe_unused = {
|
||||
{ .compatible = "ti,tlc59116",
|
||||
.data = &tlc59116 },
|
||||
{ .compatible = "ti,tlc59108",
|
||||
|
@ -26,4 +26,17 @@ config LEDS_QCOM_LPG
|
||||
|
||||
If compiled as a module, the module will be named leds-qcom-lpg.
|
||||
|
||||
config LEDS_MT6370_RGB
|
||||
tristate "LED Support for MediaTek MT6370 PMIC"
|
||||
depends on MFD_MT6370
|
||||
select LINEAR_RANGES
|
||||
help
|
||||
Say Y here to enable support for MT6370_RGB LED device.
|
||||
In MT6370, there are four channel current-sink LED drivers that
|
||||
support hardware pattern for constant current, PWM, and breath mode.
|
||||
Isink4 channel can also be used as a CHG_VIN power good indicator.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called "leds-mt6370-rgb".
|
||||
|
||||
endif # LEDS_CLASS_MULTICOLOR
|
||||
|
@ -2,3 +2,4 @@
|
||||
|
||||
obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o
|
||||
obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o
|
||||
obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o
|
||||
|
1011
drivers/leds/rgb/leds-mt6370-rgb.c
Normal file
1011
drivers/leds/rgb/leds-mt6370-rgb.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -158,8 +158,8 @@ static int led_pwm_mc_probe(struct platform_device *pdev)
|
||||
ret = led_pwm_mc_set(cdev, cdev->brightness);
|
||||
if (ret)
|
||||
return dev_err_probe(&pdev->dev, ret,
|
||||
"failed to set led PWM value for %s: %d",
|
||||
cdev->name, ret);
|
||||
"failed to set led PWM value for %s\n",
|
||||
cdev->name);
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
return 0;
|
||||
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Copyright (c) 2017-2022 Linaro Ltd
|
||||
* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
#include <linux/bits.h>
|
||||
#include <linux/bitfield.h>
|
||||
@ -17,10 +18,13 @@
|
||||
#define LPG_SUBTYPE_REG 0x05
|
||||
#define LPG_SUBTYPE_LPG 0x2
|
||||
#define LPG_SUBTYPE_PWM 0xb
|
||||
#define LPG_SUBTYPE_HI_RES_PWM 0xc
|
||||
#define LPG_SUBTYPE_LPG_LITE 0x11
|
||||
#define LPG_PATTERN_CONFIG_REG 0x40
|
||||
#define LPG_SIZE_CLK_REG 0x41
|
||||
#define PWM_CLK_SELECT_MASK GENMASK(1, 0)
|
||||
#define PWM_CLK_SELECT_HI_RES_MASK GENMASK(2, 0)
|
||||
#define PWM_SIZE_HI_RES_MASK GENMASK(6, 4)
|
||||
#define LPG_PREDIV_CLK_REG 0x42
|
||||
#define PWM_FREQ_PRE_DIV_MASK GENMASK(6, 5)
|
||||
#define PWM_FREQ_EXP_MASK GENMASK(2, 0)
|
||||
@ -43,8 +47,10 @@
|
||||
#define LPG_LUT_REG(x) (0x40 + (x) * 2)
|
||||
#define RAMP_CONTROL_REG 0xc8
|
||||
|
||||
#define LPG_RESOLUTION 512
|
||||
#define LPG_RESOLUTION_9BIT BIT(9)
|
||||
#define LPG_RESOLUTION_15BIT BIT(15)
|
||||
#define LPG_MAX_M 7
|
||||
#define LPG_MAX_PREDIV 6
|
||||
|
||||
struct lpg_channel;
|
||||
struct lpg_data;
|
||||
@ -106,6 +112,7 @@ struct lpg {
|
||||
* @clk_sel: reference clock frequency selector
|
||||
* @pre_div_sel: divider selector of the reference clock
|
||||
* @pre_div_exp: exponential divider of the reference clock
|
||||
* @pwm_resolution_sel: pwm resolution selector
|
||||
* @ramp_enabled: duty cycle is driven by iterating over lookup table
|
||||
* @ramp_ping_pong: reverse through pattern, rather than wrapping to start
|
||||
* @ramp_oneshot: perform only a single pass over the pattern
|
||||
@ -138,6 +145,7 @@ struct lpg_channel {
|
||||
unsigned int clk_sel;
|
||||
unsigned int pre_div_sel;
|
||||
unsigned int pre_div_exp;
|
||||
unsigned int pwm_resolution_sel;
|
||||
|
||||
bool ramp_enabled;
|
||||
bool ramp_ping_pong;
|
||||
@ -253,17 +261,24 @@ static int lpg_lut_sync(struct lpg *lpg, unsigned int mask)
|
||||
}
|
||||
|
||||
static const unsigned int lpg_clk_rates[] = {0, 1024, 32768, 19200000};
|
||||
static const unsigned int lpg_clk_rates_hi_res[] = {0, 1024, 32768, 19200000, 76800000};
|
||||
static const unsigned int lpg_pre_divs[] = {1, 3, 5, 6};
|
||||
static const unsigned int lpg_pwm_resolution[] = {9};
|
||||
static const unsigned int lpg_pwm_resolution_hi_res[] = {8, 9, 10, 11, 12, 13, 14, 15};
|
||||
|
||||
static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
|
||||
{
|
||||
unsigned int clk_sel, best_clk = 0;
|
||||
unsigned int i, pwm_resolution_count, best_pwm_resolution_sel = 0;
|
||||
const unsigned int *clk_rate_arr, *pwm_resolution_arr;
|
||||
unsigned int clk_sel, clk_len, best_clk = 0;
|
||||
unsigned int div, best_div = 0;
|
||||
unsigned int m, best_m = 0;
|
||||
unsigned int resolution;
|
||||
unsigned int error;
|
||||
unsigned int best_err = UINT_MAX;
|
||||
u64 max_period, min_period;
|
||||
u64 best_period = 0;
|
||||
u64 max_period;
|
||||
u64 max_res;
|
||||
|
||||
/*
|
||||
* The PWM period is determined by:
|
||||
@ -272,73 +287,107 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
|
||||
* period = --------------------------
|
||||
* refclk
|
||||
*
|
||||
* With resolution fixed at 2^9 bits, pre_div = {1, 3, 5, 6} and
|
||||
* Resolution = 2^9 bits for PWM or
|
||||
* 2^{8, 9, 10, 11, 12, 13, 14, 15} bits for high resolution PWM
|
||||
* pre_div = {1, 3, 5, 6} and
|
||||
* M = [0..7].
|
||||
*
|
||||
* This allows for periods between 27uS and 384s, as the PWM framework
|
||||
* wants a period of equal or lower length than requested, reject
|
||||
* anything below 27uS.
|
||||
* This allows for periods between 27uS and 384s for PWM channels and periods between
|
||||
* 3uS and 24576s for high resolution PWMs.
|
||||
* The PWM framework wants a period of equal or lower length than requested,
|
||||
* reject anything below minimum period.
|
||||
*/
|
||||
if (period <= (u64)NSEC_PER_SEC * LPG_RESOLUTION / 19200000)
|
||||
|
||||
if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
|
||||
clk_rate_arr = lpg_clk_rates_hi_res;
|
||||
clk_len = ARRAY_SIZE(lpg_clk_rates_hi_res);
|
||||
pwm_resolution_arr = lpg_pwm_resolution_hi_res;
|
||||
pwm_resolution_count = ARRAY_SIZE(lpg_pwm_resolution_hi_res);
|
||||
max_res = LPG_RESOLUTION_15BIT;
|
||||
} else {
|
||||
clk_rate_arr = lpg_clk_rates;
|
||||
clk_len = ARRAY_SIZE(lpg_clk_rates);
|
||||
pwm_resolution_arr = lpg_pwm_resolution;
|
||||
pwm_resolution_count = ARRAY_SIZE(lpg_pwm_resolution);
|
||||
max_res = LPG_RESOLUTION_9BIT;
|
||||
}
|
||||
|
||||
min_period = (u64)NSEC_PER_SEC *
|
||||
div64_u64((1 << pwm_resolution_arr[0]), clk_rate_arr[clk_len - 1]);
|
||||
if (period <= min_period)
|
||||
return -EINVAL;
|
||||
|
||||
/* Limit period to largest possible value, to avoid overflows */
|
||||
max_period = (u64)NSEC_PER_SEC * LPG_RESOLUTION * 6 * (1 << LPG_MAX_M) / 1024;
|
||||
max_period = (u64)NSEC_PER_SEC * max_res * LPG_MAX_PREDIV *
|
||||
div64_u64((1 << LPG_MAX_M), 1024);
|
||||
if (period > max_period)
|
||||
period = max_period;
|
||||
|
||||
/*
|
||||
* Search for the pre_div, refclk and M by solving the rewritten formula
|
||||
* for each refclk and pre_div value:
|
||||
* Search for the pre_div, refclk, resolution and M by solving the rewritten formula
|
||||
* for each refclk, resolution and pre_div value:
|
||||
*
|
||||
* period * refclk
|
||||
* M = log2 -------------------------------------
|
||||
* NSEC_PER_SEC * pre_div * resolution
|
||||
*/
|
||||
for (clk_sel = 1; clk_sel < ARRAY_SIZE(lpg_clk_rates); clk_sel++) {
|
||||
u64 numerator = period * lpg_clk_rates[clk_sel];
|
||||
|
||||
for (div = 0; div < ARRAY_SIZE(lpg_pre_divs); div++) {
|
||||
u64 denominator = (u64)NSEC_PER_SEC * lpg_pre_divs[div] * LPG_RESOLUTION;
|
||||
u64 actual;
|
||||
u64 ratio;
|
||||
for (i = 0; i < pwm_resolution_count; i++) {
|
||||
resolution = 1 << pwm_resolution_arr[i];
|
||||
for (clk_sel = 1; clk_sel < clk_len; clk_sel++) {
|
||||
u64 numerator = period * clk_rate_arr[clk_sel];
|
||||
|
||||
if (numerator < denominator)
|
||||
continue;
|
||||
for (div = 0; div < ARRAY_SIZE(lpg_pre_divs); div++) {
|
||||
u64 denominator = (u64)NSEC_PER_SEC * lpg_pre_divs[div] *
|
||||
resolution;
|
||||
u64 actual;
|
||||
u64 ratio;
|
||||
|
||||
ratio = div64_u64(numerator, denominator);
|
||||
m = ilog2(ratio);
|
||||
if (m > LPG_MAX_M)
|
||||
m = LPG_MAX_M;
|
||||
if (numerator < denominator)
|
||||
continue;
|
||||
|
||||
actual = DIV_ROUND_UP_ULL(denominator * (1 << m), lpg_clk_rates[clk_sel]);
|
||||
ratio = div64_u64(numerator, denominator);
|
||||
m = ilog2(ratio);
|
||||
if (m > LPG_MAX_M)
|
||||
m = LPG_MAX_M;
|
||||
|
||||
error = period - actual;
|
||||
if (error < best_err) {
|
||||
best_err = error;
|
||||
|
||||
best_div = div;
|
||||
best_m = m;
|
||||
best_clk = clk_sel;
|
||||
best_period = actual;
|
||||
actual = DIV_ROUND_UP_ULL(denominator * (1 << m),
|
||||
clk_rate_arr[clk_sel]);
|
||||
error = period - actual;
|
||||
if (error < best_err) {
|
||||
best_err = error;
|
||||
best_div = div;
|
||||
best_m = m;
|
||||
best_clk = clk_sel;
|
||||
best_period = actual;
|
||||
best_pwm_resolution_sel = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chan->clk_sel = best_clk;
|
||||
chan->pre_div_sel = best_div;
|
||||
chan->pre_div_exp = best_m;
|
||||
chan->period = best_period;
|
||||
|
||||
chan->pwm_resolution_sel = best_pwm_resolution_sel;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lpg_calc_duty(struct lpg_channel *chan, uint64_t duty)
|
||||
{
|
||||
unsigned int max = LPG_RESOLUTION - 1;
|
||||
unsigned int max;
|
||||
unsigned int val;
|
||||
unsigned int clk_rate;
|
||||
|
||||
val = div64_u64(duty * lpg_clk_rates[chan->clk_sel],
|
||||
if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
|
||||
max = LPG_RESOLUTION_15BIT - 1;
|
||||
clk_rate = lpg_clk_rates_hi_res[chan->clk_sel];
|
||||
} else {
|
||||
max = LPG_RESOLUTION_9BIT - 1;
|
||||
clk_rate = lpg_clk_rates[chan->clk_sel];
|
||||
}
|
||||
|
||||
val = div64_u64(duty * clk_rate,
|
||||
(u64)NSEC_PER_SEC * lpg_pre_divs[chan->pre_div_sel] * (1 << chan->pre_div_exp));
|
||||
|
||||
chan->pwm_value = min(val, max);
|
||||
@ -354,7 +403,7 @@ static void lpg_apply_freq(struct lpg_channel *chan)
|
||||
|
||||
val = chan->clk_sel;
|
||||
|
||||
/* Specify 9bit resolution, based on the subtype of the channel */
|
||||
/* Specify resolution, based on the subtype of the channel */
|
||||
switch (chan->subtype) {
|
||||
case LPG_SUBTYPE_LPG:
|
||||
val |= GENMASK(5, 4);
|
||||
@ -362,6 +411,9 @@ static void lpg_apply_freq(struct lpg_channel *chan)
|
||||
case LPG_SUBTYPE_PWM:
|
||||
val |= BIT(2);
|
||||
break;
|
||||
case LPG_SUBTYPE_HI_RES_PWM:
|
||||
val |= FIELD_PREP(PWM_SIZE_HI_RES_MASK, chan->pwm_resolution_sel);
|
||||
break;
|
||||
case LPG_SUBTYPE_LPG_LITE:
|
||||
default:
|
||||
val |= BIT(4);
|
||||
@ -670,7 +722,7 @@ static int lpg_blink_set(struct lpg_led *led,
|
||||
triled_set(lpg, triled_mask, triled_mask);
|
||||
|
||||
chan = led->channels[0];
|
||||
duty = div_u64(chan->pwm_value * chan->period, LPG_RESOLUTION);
|
||||
duty = div_u64(chan->pwm_value * chan->period, LPG_RESOLUTION_9BIT);
|
||||
*delay_on = div_u64(duty, NSEC_PER_MSEC);
|
||||
*delay_off = div_u64(chan->period - duty, NSEC_PER_MSEC);
|
||||
|
||||
@ -977,6 +1029,7 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
{
|
||||
struct lpg *lpg = container_of(chip, struct lpg, pwm);
|
||||
struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
|
||||
unsigned int resolution;
|
||||
unsigned int pre_div;
|
||||
unsigned int refclk;
|
||||
unsigned int val;
|
||||
@ -988,7 +1041,14 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
refclk = lpg_clk_rates[val & PWM_CLK_SELECT_MASK];
|
||||
if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
|
||||
refclk = lpg_clk_rates_hi_res[FIELD_GET(PWM_CLK_SELECT_HI_RES_MASK, val)];
|
||||
resolution = lpg_pwm_resolution_hi_res[FIELD_GET(PWM_SIZE_HI_RES_MASK, val)];
|
||||
} else {
|
||||
refclk = lpg_clk_rates[FIELD_GET(PWM_CLK_SELECT_MASK, val)];
|
||||
resolution = 9;
|
||||
}
|
||||
|
||||
if (refclk) {
|
||||
ret = regmap_read(lpg->map, chan->base + LPG_PREDIV_CLK_REG, &val);
|
||||
if (ret)
|
||||
@ -1001,7 +1061,8 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * LPG_RESOLUTION * pre_div * (1 << m), refclk);
|
||||
state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * (1 << resolution) *
|
||||
pre_div * (1 << m), refclk);
|
||||
state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pwm_value * pre_div * (1 << m), refclk);
|
||||
} else {
|
||||
state->period = 0;
|
||||
@ -1149,7 +1210,7 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
|
||||
}
|
||||
|
||||
cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL);
|
||||
cdev->max_brightness = LPG_RESOLUTION - 1;
|
||||
cdev->max_brightness = LPG_RESOLUTION_9BIT - 1;
|
||||
|
||||
if (!of_property_read_string(np, "default-state", &state) &&
|
||||
!strcmp(state, "on"))
|
||||
@ -1429,6 +1490,14 @@ static const struct lpg_data pm8350c_pwm_data = {
|
||||
},
|
||||
};
|
||||
|
||||
static const struct lpg_data pmk8550_pwm_data = {
|
||||
.num_channels = 2,
|
||||
.channels = (const struct lpg_channel_data[]) {
|
||||
{ .base = 0xe800 },
|
||||
{ .base = 0xe900 },
|
||||
},
|
||||
};
|
||||
|
||||
static const struct of_device_id lpg_of_table[] = {
|
||||
{ .compatible = "qcom,pm8150b-lpg", .data = &pm8150b_lpg_data },
|
||||
{ .compatible = "qcom,pm8150l-lpg", .data = &pm8150l_lpg_data },
|
||||
@ -1439,6 +1508,7 @@ static const struct of_device_id lpg_of_table[] = {
|
||||
{ .compatible = "qcom,pmi8994-lpg", .data = &pmi8994_lpg_data },
|
||||
{ .compatible = "qcom,pmi8998-lpg", .data = &pmi8998_lpg_data },
|
||||
{ .compatible = "qcom,pmc8180c-lpg", .data = &pm8150l_lpg_data },
|
||||
{ .compatible = "qcom,pmk8550-pwm", .data = &pmk8550_pwm_data },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, lpg_of_table);
|
||||
|
@ -83,6 +83,7 @@ config LEDS_TRIGGER_ACTIVITY
|
||||
config LEDS_TRIGGER_GPIO
|
||||
tristate "LED GPIO Trigger"
|
||||
depends on GPIOLIB || COMPILE_TEST
|
||||
depends on BROKEN
|
||||
help
|
||||
This allows LEDs to be controlled by gpio events. It's good
|
||||
when using gpios as switches and triggering the needed LEDs
|
||||
|
@ -274,7 +274,7 @@ struct led_classdev *__must_check devm_of_led_get(struct device *dev,
|
||||
*
|
||||
* Note that if software blinking is active, simply calling
|
||||
* led_cdev->brightness_set() will not stop the blinking,
|
||||
* use led_classdev_brightness_set() instead.
|
||||
* use led_set_brightness() instead.
|
||||
*/
|
||||
void led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
|
||||
unsigned long *delay_off);
|
||||
|
Loading…
Reference in New Issue
Block a user