- 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:
Linus Torvalds 2023-05-02 10:36:02 -07:00
commit c5eb8bf767
27 changed files with 3087 additions and 108 deletions

View File

@ -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: |

View File

@ -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

View File

@ -27,6 +27,7 @@ properties:
- qcom,pmc8180c-lpg
- qcom,pmi8994-lpg
- qcom,pmi8998-lpg
- qcom,pmk8550-pwm
"#pwm-cells":
const: 2

View 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";
};
};
};
...

View 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>;
};
};
};

View File

@ -0,0 +1,81 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/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;
};
};
};
...

View File

@ -25,5 +25,6 @@ LEDs
leds-lp5562
leds-lp55xx
leds-mlxcpld
leds-mt6370-rgb
leds-sc27xx
leds-qcom-lpg

View 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.

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View 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");

View 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", &current_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", &current_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", &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");

View 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", &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");

View File

@ -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);

View File

@ -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"))

View File

@ -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",

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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);