Messages in this thread | | | Date | Wed, 2 Nov 2022 16:22:41 +0800 | Subject | Re: [PATCH v4 1/2] leds: flash: add driver to support flash LED module in QCOM PMICs | From | Fenglin Wu <> |
| |
Hi Dylan,
On 2022/11/2 16:14, Dylan Van Assche wrote: > > Hi Fenglin, > >> Hi Dylan, >> >> >> On 2022/11/2 1:39, Dylan Van Assche wrote: >>> >>> Hi Fenglin, >>> >>>> On 2022/10/31 18:34, Dylan Van Assche wrote: >>>>> >>>>> Hi Fenglin, >>>>> >>>>> Thanks for submitting these patches! They are really nice! >>>>> I tested these patches on an SDM845 based device as the >>>>> downstream >>>>> driver is the same for the PMI8998 compared to the mentioned >>>>> PMICs >>>>> in >>>>> this patchseries. >>>>> >>>>> Tested-by: Dylan Van Assche <me@dylanvanassche.be> # sdm845- >>>>> shift- >>>>> axolotl + pmi8998 >>>>> >>>> Thanks for testing the patch at your side, Dylan! >>>> >>>>>>> Add initial driver to support flash LED module found in >>>>>>> Qualcomm >>>>>>> Technologies, Inc. PMICs. The flash module can have 3 or 4 >>>>>>> channels >>>>>>> and each channel can be controlled indepedently and support >>>>>>> full > >>>>>>> scale >>>>>>> current up to 1.5 A. It also supports connecting two >>>>>>> channels > >>>>>>> together >>>>>>> to supply one LED component with full scale current up to 2 >>>>>>> A. >>>>>>> In > >>>>>>> that >>>>>>> case, the current will be split on each channel >>>>>>> symmetrically >>>>>>> and >>>>>>> the >>>>>>> channels will be enabled and disabled at the same time. >>>>>>> >>>>>>> Signed-off-by: Fenglin Wu <quic_fenglinw@quicinc.com> >>>>>>> Tested-by: Luca Weiss <luca.weiss@fairphone.com> # > >>>>>>> sm7225- >>>>>>> fairphone-fp4 + pm6150l >>>>>>> --- >>>>>>> drivers/leds/flash/Kconfig | 15 + >>>>>>> drivers/leds/flash/Makefile | 1 + >>>>>>> drivers/leds/flash/leds-qcom-flash.c | 700 > >>>>>>> +++++++++++++++++++++++++++ >>>>>>> 3 files changed, 716 insertions(+) >>>>>>> create mode 100644 drivers/leds/flash/leds-qcom-flash.c >>>>>>> >>>>>>> diff --git a/drivers/leds/flash/Kconfig >>>>>>> b/drivers/leds/flash/Kconfig >>>>>>> index d3eb689b193c..f36a60409290 100644 >>>>>>> --- a/drivers/leds/flash/Kconfig >>>>>>> +++ b/drivers/leds/flash/Kconfig >>>>>>> @@ -61,6 +61,21 @@ config LEDS_MT6360 >>>>>>> Independent current sources supply for each >>>>>>> flash >>>>>>> LED > >>>>>>> support torch >>>>>>> and strobe mode. >>>>>>> >>>>>>> +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 currentwill 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 >>>>>>> diff --git a/drivers/leds/flash/Makefile > >>>>>>> b/drivers/leds/flash/Makefile >>>>>>> index 0acbddc0b91b..8a60993f1a25 100644 >>>>>>> --- a/drivers/leds/flash/Makefile >>>>>>> +++ b/drivers/leds/flash/Makefile >>>>>>> @@ -6,6 +6,7 @@ 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 >>>>>>> diff --git a/drivers/leds/flash/leds-qcom-flash.c > >>>>>>> b/drivers/leds/flash/leds-qcom-flash.c >>>>>>> new file mode 100644 >>>>>>> index 000000000000..a9fcf61600c3 >>>>>>> --- /dev/null >>>>>>> +++ b/drivers/leds/flash/leds-qcom-flash.c >>>>>>> @@ -0,0 +1,700 @@ >>>>>>> +// 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 >>>>> >>>>> PMI8998 has also a 3 channel flash subtype, but reports 0x03 as >>>>> subtype. The driver won't probe because of this since the >>>>> subtype >>>>> did >>>>> not match. Changing this value to 0x03 made this driver work on >>>>> SDM845 >>>>> based device as well with PMI8998. Would you consider >>>>> supporting >>>>> SDM845+PMI8998 as well? >>>>> >>>> I just double checked, flash module in PMI8998 (with subtype 0x3) >>>> has >>>> very similar register definition as the flash module in PM8150C >>>> (with >>>> subtype 0x4) that the driver is intended to support, it's good >>>> know >>>> that >>>> it's 99% work. >>>> I can add the 0x3 subtype support but the only problem is I >>>> couldn't >>>> find a device with PMI8998 to test it out. Would it be good if I >>>> add >>>> the >>>> support and rely on your test result? >>> >>> Sure! I already have a branch locally to test your patches, happy >>> to >>> help! I have an SDM845 device here with PMI8998 with 2 LEDs: cold >>> and >>> warm white. >>> >> Thank you for the help! >>>> >>>>>>> +#define FLASH_SUBTYPE_4CH_VAL 0x07 >>>>>>> + >>>>>>> +#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 >>>>>>> + >>>>>>> +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, >>>>>>> +}; >>>>>>> + >>>>>>> +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 */ >>>>>>> +}; >>>>> >>>>> These registers map perfectly for SDM845 with PMI8998 as well, >>>>> adding >>>>> support for this subtype (0x03) would be great. >>>>> >>>>>>> + >>>>>>> +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_led { >>>>>>> + struct qcom_flash_chip *chip; >>>>>>> + struct led_classdev_flash flash; >>>>>>> + struct v4l2_flash *v4l2_flash; >>>>>>> + u32 max_flash_current_m >>>>>>> a; >>>>>>> + u32 max_torch_current_m >>>>>>> a; >>>>>>> + u32 max_timeout_ms; >>>>>>> + u32 flash_current_ma; >>>>>>> + u32 flash_timeout_ms; >>>>>>> + u8 *chan_id; >>>>>>> + u8 chan_count; >>>>>>> + bool enabled; >>>>>>> +}; >>>>>>> + >>>>>>> +struct qcom_flash_chip { >>>>>>> + struct qcom_flash_led *leds; >>>>>>> + struct regmap_field *r_fields[REG_MAX_COUNT]; >>>>>>> + struct device *dev; >>>>>>> + struct mutex lock; >>>>>>> + enum hw_type hw_type; >>>>>>> + u8 leds_count; >>>>>>> + u8 max_channels; >>>>>>> + u8 chan_en_bits; >>>>>>> +}; >>>>>>> + >>>>>>> +static int set_flash_module_en(struct qcom_flash_led *led, >>>>>>> bool >>>>>>> en) >>>>>>> +{ >>>>>>> + struct qcom_flash_chip *chip= led->chip; >>>>>>> + u8 led_mask = 0, val; >>>>>>> + int i, rc; >>>>>>> + >>>>>>> + for (i = 0; i < led->chan_count; i++) >>>>>>> + led_mask |= BIT(led->chan_id[i] - 1); >>>>>>> + >>>>>>> + mutex_lock(&chip->lock); >>>>>>> + if (en) >>>>>>> + chip->chan_en_bits |= led_mask; >>>>>>> + else >>>>>>> + chip->chan_en_bits &= ~led_mask; >>>>>>> + >>>>>>> + val = !!chip->chan_en_bits; >>>>>>> + rc = regmap_field_write(chip- >>>>>>>> r_fields[REG_MODULE_EN], >>>>>>> val); >>>>>>> + if (rc < 0) >>>>>>> + dev_err(chip->dev, "write module_en failed, >>>>>>> rc=%d\n", > rc); >>>>>>> + mutex_unlock(&chip->lock); >>>>>>> + >>>>>>> + return rc; >>>>>>> +} >>>>>>> + >>>>>>> +static int set_flash_current(struct qcom_flash_led *led, >>>>>>> u32 > >>>>>>> current_ma, enum led_mode mode) >>>>>>> +{ >>>>>>> + struct qcom_flash_chip *chip= led->chip; >>>>>>> + u32 itarg_ua = current_ma * 1000 / led->chan_count >>>>>>> + 1; >>>>>>> + u32 ires_ua = (mode ==FLASH_MODE) ? FLASH_IRES_UA : >>>>>>>> >>>>>>> TORCH_IRES_UA; >>>>>>> + u8 val, 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. >>>>>>> + */ >>>>>>> + for (i = 0; i < led->chan_count; i++) { >>>>>>> + chan_id = led->chan_id[i]; >>>>>>> + if (itarg_ua < ires_ua) >>>>>>> + val = 0; >>>>>>> + else >>>>>>> + val = itarg_ua / ires_ua - 1; >>>>>>> + >>>>>>> + rc = regmap_fields_write(chip- >>>>>>>> r_fields[REG_ITARGET], > chan_id - 1, val); >>>>>>> + if (rc < 0) >>>>>>> + returnrc; >>>>>>> + >>>>>>> + if (chip->hw_type == QCOM_MVFLASH_3CH) { >>>>>>> + shift = (chan_id - 1) * 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 (chip->hw_type == >>>>>>> QCOM_MVFLASH_4CH) { >>>>>>> + shift = chan_id - 1; >>>>>>> + ires_mask |= FLASH_IRES_MASK_4CH << >>>>>>> shift; >>>>>>> + ires_val |= ((mode == FLASH_MODE) ? >>>>>>> + (FLASH_IRES_12P5MA_VAL << >>>>>>> shift) : >>>>>>> + (FLASH_IRES_5MA_VAL_4CH << >>>>>>> shift)); >>>>>>> + } >>>>>>> + } >>>>>>> + >>>>>>> + return > regmap_field_update_bits(chip- >>>>>>>> r_fields[REG_IRESOLUTION], ires_mask, > ires_val); >>>>>>> +} >>>>>>> + >>>>>>> +static int set_flash_timeout(struct qcom_flash_led *led, >>>>>>> u32 > >>>>>>> timeout_ms) >>>>>>> +{ >>>>>>> + struct qcom_flash_chip *chip= led->chip; >>>>>>> + u8 val, 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]; >>>>>>> + val = timeout_ms / FLASH_TIMER_STEP_MS; >>>>>>> + val = clamp_t(u8, val, 0, >>>>>>> FLASH_TIMER_VAL_MASK); >>>>>>> + if (timeout_ms) >>>>>>> + val |= FLASH_TIMER_EN_BIT; >>>>>>> + >>>>>>> + rc = > regmap_fields_write(chip- >>>>>>>> r_fields[REG_CHAN_TIMER], chan_id - 1, > val); >>>>>>> + if (rc < 0) >>>>>>> + returnrc; >>>>>>> + } >>>>>>> + >>>>>>> + return 0; >>>>>>> +} >>>>>>> + >>>>>>> +static int set_flash_strobe(struct qcom_flash_led *led, >>>>>>> enum > >>>>>>> led_strobe strobe, bool state) >>>>>>> +{ >>>>>>> + struct qcom_flash_chip *chip= led->chip; >>>>>>> + u8 mask, val, chan_id = 0,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) >>>>>>> + val = >>>>>>> FIELD_PREP(FLASH_STROBE_HW_SW_SEL_BIT, > SW_STROBE_VAL); >>>>>>> + else >>>>>>> + val = >>>>>>> FIELD_PREP(FLASH_STROBE_HW_SW_SEL_BIT, > HW_STROBE_VAL); >>>>>>> + >>>>>>> + val |= >>>>>>> FIELD_PREP(FLASH_HW_STROBE_TRIGGER_SEL_BIT, >>>>>>>> STROBE_LEVEL_TRIGGER_VAL) | >>>>>>> + FIELD_PREP(FLASH_STROBE_POLARITY_BI >>>>>>> T, > >>>>>>> STROBE_ACTIVE_HIGH_VAL); >>>>>>> + rc = > regmap_fields_write(chip- >>>>>>>> r_fields[REG_CHAN_STROBE], chan_id - 1, > val); >>>>>>> + if (rc < 0) >>>>>>> + returnrc; >>>>>>> + >>>>>>> + chan_mask |= BIT(chan_id - 1); >>>>>>> + } >>>>>>> + >>>>>>> + /* enable/disable flash channels */ >>>>>>> + mask = chan_mask; >>>>>>> + val = state ? mask : 0; >>>>>>> + rc = regmap_field_update_bits(chip- >>>>>>>> r_fields[REG_CHAN_EN], >>>>>>>> mask, val); >>>>>>> + if (rc < 0) >>>>>>> + return rc; >>>>>>> + >>>>>>> + led->enabled = state; >>>>>>> + return 0; >>>>>>> +} >>>>>>> + >>>>>>> +static int qcom_flash_brightness_set(struct >>>>>>> led_classdev_flash >>>>>>>> >>>>>>> *fled_cdev, u32 brightness) >>>>>>> +{ >>>>>>> + struct qcom_flash_led *led = >>>>>>> container_of(fled_cdev, >>>>>>> struct >>>>>>>> qcom_flash_led, flash); >>>>>>> + >>>>>>> + led->flash_current_ma = min_t(u32, led- >>>>>>>> max_flash_current_ma, > brightness / 1000); >>>>>>> + return 0; >>>>>>> +} >>>>> >>>>> This doesn't seem to work in torch mode for me on PMI8998. If >>>>> the >>>>> brightness is 0, the torch is OFF as expected, but when the >>>>> torch >>>>> is >>>>> ON, you cannot control the brightness. The brightness is the >>>>> same >>>>> for >>>>> [1, 255]. If the hardware cannot change the brightness in torch >>>>> mode, >>>>> it would be nice to report a max_brightness value of 1 instead >>>>> of >>>>> 255 >>>>> for userspace. This could be a limitation of the PMI8998, not >>>>> sure. >>>>> Do you have any insights here? This driver is already 99% >>>>> working >>>>> for >>>>> SDM845+PMI8998. >>>>> >>>>> Flash mode works great, timeout & brightness can be configured >>>>> just >>>>> fine. >>>>> >>>> >>>> Here, function qcom_flash_brightness_set() is used for setting >>>> brightness in flash mode and it was supposed to be working with >>>> following commands combination: >>>> echo xxx > flash_brightness >>>> echo 1 > flash_strobe (you will need to toggle >>>> flash_strobe >>>> when you >>>> enabling it again) >>>> >>>> Can you check if you can see the brightness change in flash mode >>>> when >>>> you updating the flash_brightness value with these commands? >>>> >>>> You can echo any value between [0, max_flash_brightness] into the >>>> flash_brightness, there is a "max_flash_brightness" sysfs node >>>> and >>>> the >>>> value comes from DT property "flash-max-microamp", ex. my dtsi >>>> node >>>> has >>>> "flash-max-microamp = <2000000>;" defined so I am having >>>> following >>>> value >>>> for max_flash_brightness: >>>> >>>> kalama:/sys/class/leds/white:flash-0 # cat max_flash_brightness >>>> 2000000 >>>> >>>> And by default flash mode uses 12500uA resolution so you won't >>>> see >>>> brightness change when you update it with values between [1, >>>> 12500]. >>> >>> Yes, this works perfectly fine on PMI8998. I can adjust the flash >>> brightness and flash timeout perfectly. >>> >>>> >>>> If you want to update the brightness for torch node, you can >>>> directly >>>> update the "brightness" node with values between [0, 255], and >>>> it's >>>> mapping to the torch current between [0, led-max-microamp]. >>>> "led-max-microamp" is also has value coming from the DT property. >>>> >>>> This worked at my side on both pm8150C and pm8550.I think it >>>> should >>>> work >>>> on PMI8998 as well because the flash module in it is very similar >>>> to >>>> the >>>> one in PM8150c. Let me know if you still see such issues at your >>>> side. >>>> Thanks >>> >>> Only here I have an issue with PMI8998: the "brightness" nodes >>> should >>> change the brightness of the torch, but it doesn't make a >>> difference >>> here. When I do: >>> >>> - echo 0 > brightness --> LED turns OFF >>> - echo 255 > brightness --> LED turns ON >>> - echo 100 > brightness --> LED is still ON, but brightness is the >>> same >>> as with 255. >>> >>> Could it be that PMI8998 is slightly different here than with >>> PM8150c >>> for example? Maybe it doesn't support brightness for torch? >>> >>> Here's my DTS: >>> >>> *PMI8998* >>> >>> pmi8998_flash: led-controller@d300 { >>> compatible = "qcom,pm6150l-flash-led", "qcom,spmi-flash- >>> led"; >>> reg = <0xd300>; >>> status = "disabled"; >>> }; >>> >>> *SDM845 SHIFT axolotl* >>> >>> &pmi8998_flash { >>> status = "okay"; >>> >>> led-0 { >>> function = LED_FUNCTION_FLASH; >>> color = <LED_COLOR_ID_WHITE>; >>> led-sources = <1>; >>> led-max-microamp = <180000>; >>> flash-max-microamp = <1000000>; >>> flash-max-timeout-us = <1280000>; >>> }; >>> >>> led-1 { >>> function = LED_FUNCTION_FLASH; >>> color = <LED_COLOR_ID_YELLOW>; >>> led-sources = <2>; >>> led-max-microamp = <180000>; >>> flash-max-microamp = <1000000>; >>> flash-max-timeout-us = <1280000>; >>> }; >>> }; >>> >> Thank you for getting back all the details. The devicetree node looks >> good to me. >> >> I checked again and confirmed that the flash modules in PMI8998 and >> PM8150C have the same register definition for ITARGETx (0xD343 + x) >> and >> IRESOLUTION (0xD347), these are the only 2 settings would impact LED >> brightness, and in torch mode, IRESOLUTION is fixed to 5mA and only >> ITARGET is updated accordingly. >> >> I updated my workspace to use the same current for torch mode as your >> settings in devicetree and tried again on my PM8150C device,I could >> notice the brightness change when echoing different values to the >> brightness sysfs node, however, when I updating values between 255 >> and >> 200, it wasn't a very noticeable brightness change with naked eys, >> but I >> could see the ITRAGETx register changed accordingly when reading its >> value back from the regmap debugfs node. >> >> Can you try the same and see if the register got updated accordingly >> when you updating brightness values? If yes, I would wonder if the >> LED >> component on your device has upper current limit in torch mode close >> to >> (100 / 255 ) * 180mA so you that you can't observe the brightness >> change >> when updating between 100 to 255. Another easier thing to try, can >> you >> echo a lower brightness value, such as 10, to see if you can notice >> the >> brightness change? >> > > Aha! It does seem to work partially! > > I tried it with brightness 0 --> 255 --> 1. This order keeps the > highest brightness at all times, matching 255. The other way around: 0 > --> 1 --> 255 keeps the lowest brightness, matching 1. > Changing the brightness only works if the LED was turned OFF first: > > 0 --> 255 --> 0 --> 1 > 0 --> 1 --> 0 --> 255 > > both work fine, verified it with the following shell commands (assumed > that the LED was turned OFF to start): > > echo 1 > brightness && sleep 3 && echo 0 > brightness && echo 255 > > brightness > echo 255 > brightness && sleep 3 && echo 0 > brightness && echo 1 > > brightness > > Maybe a register needs to be reset when the brightness changes? > Thanks for the information, this is helpful! Let me check in the team internally and get back if any special sequence is required for the flash module inside PMI8998.
>>> >>>> >>>>>>> + >>>>>>> +static int qcom_flash_timeout_set(struct >>>>>>> led_classdev_flash > >>>>>>> *fled_cdev, u32 timeout) >>>>>>> +{ >>>>>>> + struct qcom_flash_led *led = >>>>>>> container_of(fled_cdev, >>>>>>> struct >>>>>>>> qcom_flash_led, flash); >>>>>>> + >>>>>>> + led->flash_timeout_ms = timeout / 1000; >>>>>>> + return 0; >>>>>>> +} >>>>>>> + >>>>>>> +static int qcom_flash_strobe_set(struct led_classdev_flash >>>>>>>> >>>>>>> *fled_cdev, bool state) >>>>>>> +{ >>>>>>> + struct qcom_flash_led *led = >>>>>>> container_of(fled_cdev, >>>>>>> struct >>>>>>>> qcom_flash_led, flash); >>>>>>> + int rc; >>>>>>> + >>>>>>> + rc = set_flash_current(led, led->flash_current_ma, >>>>>>>> >>>>>>> FLASH_MODE); >>>>>>> + if (rc < 0) >>>>>>> + return rc; >>>>>>> + >>>>>>> + rc = set_flash_timeout(led, led->flash_timeout_ms); >>>>>>> + if (rc < 0) >>>>>>> + return rc; >>>>>>> + >>>>>>> + rc = set_flash_module_en(led, state); >>>>>>> + if (rc < 0) >>>>>>> + 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 = >>>>>>> container_of(fled_cdev, >>>>>>> struct >>>>>>>> qcom_flash_led, flash); >>>>>>> + >>>>>>> + *state = led->enabled; >>>>>>> + return 0; >>>>>>> +} >>>>>>> + >>>>>>> +static int qcom_flash_fault_get(struct led_classdev_flash >>>>>>>> >>>>>>> *fled_cdev, u32 *fault) >>>>>>> +{ >>>>>>> + struct qcom_flash_led *led = >>>>>>> container_of(fled_cdev, >>>>>>> struct >>>>>>>> qcom_flash_led, flash); >>>>>>> + struct qcom_flash_chip *chip= led->chip; >>>>>>> + u8 shift, chan_id = 0, 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(chip->r_fields[REG_STATUS1], >>>>>>> &val); >>>>>>> + if (rc < 0) >>>>>>> + return rc; >>>>>>> + >>>>>>> + for (i = 0; i < led->chan_count; i++) { >>>>>>> + chan_id = led->chan_id[i]; >>>>>>> + shift = (chan_id - 1) * 2; >>>>>>> + if (val & BIT(shift)) >>>>>>> + fault_sts |= >>>>>>> LED_FAULT_SHORT_CIRCUIT; >>>>>>> + chan_mask |= BIT(chan_id - 1); >>>>>>> + } >>>>>>> + >>>>>>> + rc = regmap_field_read(chip->r_fields[REG_STATUS2], >>>>>>> &val); >>>>>>> + if (rc < 0) >>>>>>> + return rc; >>>>>>> + >>>>>>> + if (chip->hw_type == QCOM_MVFLASH_3CH) { >>>>>>> + ot_mask = 0x0f; >>>>>>> + oc_mask = 0xe0; >>>>>>> + uv_mask = 0x10; >>>>>>> + } else if (chip->hw_type == QCOM_MVFLASH_4CH) { >>>>>>> + ot_mask = 0x70; >>>>>>> + oc_mask = 0x0e; >>>>>>> + uv_mask = 0x01; >>>>>>> + } >>>>>>> + >>>>>>> + 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(chip->r_fields[REG_STATUS3], >>>>>>> &val); >>>>>>> + if (rc < 0) >>>>>>> + return rc; >>>>>>> + >>>>>>> + if (chip->hw_type == QCOM_MVFLASH_3CH) { >>>>>>> + if (val & chan_mask) >>>>>>> + fault_sts |= LED_FAULT_TIMEOUT; >>>>>>> + } else if (chip->hw_type == QCOM_MVFLASH_4CH) { >>>>>>> + for (i = 0; i < led->chan_count; i++) { >>>>>>> + chan_id = led->chan_id[i]; >>>>>>> + shift = (chan_id - 1) * 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 = >>>>>>> + container_of(led_cdev, struct >>>>>>> led_classdev_flash, > >>>>>>> led_cdev); >>>>>>> + struct qcom_flash_led *led = >>>>>>> + container_of(fled_cdev, struct >>>>>>> qcom_flash_led, >>>>>>>> >>>>>>> flash); >>>>>>> + 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 < 0) >>>>>>> + return rc; >>>>>>> + >>>>>>> + /* disable flash timeout fortorch LED */ >>>>>>> + rc = set_flash_timeout(led, 0); >>>>>>> + if (rc < 0) >>>>>>> + return rc; >>>>>>> + >>>>>>> + rc = set_flash_module_en(led, enable); >>>>>>> + if (rc < 0) >>>>>>> + 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 *flash = v4l2_flash- >>>>>>>> fled_cdev; >>>>>>> + struct qcom_flash_led *led = container_of(flash, >>>>>>> struct >>>>>>>> >>>>>>> qcom_flash_led, flash); >>>>>>> + int rc; >>>>>>> + >>>>>>> + rc = set_flash_module_en(led, enable); >>>>>>> + if (rc < 0) >>>>>>> + 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 *flash = v4l2_flash- >>>>>>>> fled_cdev; >>>>>>> + struct qcom_flash_led *led = container_of(flash, >>>>>>> struct >>>>>>>> >>>>>>> qcom_flash_led, flash); >>>>>>> + u32 current_ma = intensity/ 1000; >>>>>>> + >>>>>>> + 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 *flash = v4l2_flash- >>>>>>>> fled_cdev; >>>>>>> + struct qcom_flash_led *led = container_of(flash, >>>>>>> struct >>>>>>>> >>>>>>> qcom_flash_led, flash); >>>>>>> + >>>>>>> + return (brightness * led->max_torch_current_ma * >>>>>>> 1000) >>>>>>> / > >>>>>>> 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 qcom_flash_led >>>>>>> *led, >>>>>>> struct >>>>>>>> fwnode_handle *fwnode) >>>>>>> +{ >>>>>>> + struct v4l2_flash_config v4l2_cfg = {0}; >>>>>>> + struct led_flash_setting *s = &v4l2_cfg.intensity; >>>>>>> + >>>>>>> + if (!(led->flash.led_cdev.flags & >>>>>>> LED_DEV_CAP_FLASH)) >>>>>>> + return 0; >>>>>>> + >>>>>>> + s->min = s->step = TORCH_IRES_UA * led->chan_count; >>>>>>> + s->max = led->max_torch_current_ma * 1000; >>>>>>> + s->val = min_t(u32, s->max, >>>>>>> TORCH_CURRENT_DEFAULT_UA); >>>>>>> + >>>>>>> + strscpy(v4l2_cfg.dev_name, >led- >>>>>>>> flash.led_cdev.dev- >>>>>>>> kobj.name, >>>>>>> + sizeof(v4l2_cfg.dev >>>>>>> _nam >>>>>>> e)); >>>>>>> + v4l2_cfg.has_external_strobe= 1; >>>>>>> + v4l2_cfg.flash_faults = LED_FAULT_INPUT_VOLTAGE | > >>>>>>> LED_FAULT_OVER_CURRENT | >>>>>>> + LED_FAULT_SHORT_CIRCUIT | >>>>>>> LED_FAULT_OVER_TEMPERATURE > | LED_FAULT_TIMEOUT; >>>>>>> + >>>>>>> + led->v4l2_flash = v4l2_flash_init(led->chip->dev, >>>>>>> fwnode, > >>>>>>> &led->flash, >>>>>>> + &qcom_v4l2_flash_op >>>>>>> s, > >>>>>>> &v4l2_cfg); >>>>>>> + return PTR_ERR_OR_ZERO(led->v4l2_flash); >>>>>>> +} >>>>>>> +# else >>>>>>> +static int qcom_flash_v4l2_init(struct qcom_flash_led >>>>>>> *led, >>>>>>> struct >>>>>>>> fwnode_handle *fwnode) >>>>>>> +{ >>>>>>> + return 0; >>>>>>> +} >>>>>>> +#endif >>>>>>> + >>>>>>> +static int qcom_flash_register_led_device(struct device >>>>>>> *parent, >>>>>>> + struct fwnode_handle *node, struct >>>>>>> qcom_flash_led > >>>>>>> *led) >>>>>>> +{ >>>>>>> + struct qcom_flash_chip *chip= led->chip; >>>>>>> + struct led_init_data init_data; >>>>>>> + struct led_classdev_flash *flash; >>>>>>> + struct led_flash_setting *s; >>>>>>> + u32 count, val; >>>>>>> + u32 channels[4]; >>>>>>> + int i, rc; >>>>>>> + >>>>>>> + flash = &led->flash; >>>>>>> + count = fwnode_property_count_u32(node, "led- >>>>>>> sources"); >>>>>>> + if (count <= 0) { >>>>>>> + dev_err(chip->dev, "No led-sources >>>>>>> specified\n"); >>>>>>> + return -ENODEV; >>>>>>> + } >>>>>>> + >>>>>>> + if (count > chip->max_channels) { >>>>>>> + dev_err(chip->dev, "led-sources count %u >>>>>>> exceeds > >>>>>>> maximum channel count %u\n", >>>>>>> + count, chip->max_channels); >>>>>>> + return -EINVAL; >>>>>>> + } >>>>>>> + >>>>>>> + rc = fwnode_property_read_u32_array(node, "led- >>>>>>> sources", > >>>>>>> channels, count); >>>>>>> + if (rc < 0) { >>>>>>> + dev_err(chip->dev, "get led-sources failed, >>>>>>> rc=%d\n", > rc); >>>>>>> + return rc; >>>>>>> + } >>>>>>> + >>>>>>> + led->chan_count = count; >>>>>>> + led->chan_id = devm_kcalloc(chip->dev, count, >>>>>>> sizeof(u8), > >>>>>>> GFP_KERNEL); >>>>>>> + if (!led->chan_id) >>>>>>> + return -ENOMEM; >>>>>>> + >>>>>>> + for (i = 0; i < count; i++) { >>>>>>> + if (channels[i] > chip->max_channels) { >>>>>>> + dev_err(chip->dev, "led-source out >>>>>>> of >>>>>>> HW > >>>>>>> support range [1-%u]\n", >>>>>>> + chip- >>>>>>>> max_channels); >>>>>>> + return-EINVAL; >>>>>>> + } >>>>>>> + >>>>>>> + led->chan_id[i] = channels[i]; >>>>>>> + } >>>>>>> + >>>>>>> + rc = fwnode_property_read_u32(node, "led-max- >>>>>>> microamp", >>>>>>>> >>>>>>> &val); >>>>>>> + if (rc < 0) { >>>>>>> + dev_err(chip->dev, "Get led-max-microamp >>>>>>> failed, > >>>>>>> rc=%d\n", rc); >>>>>>> + return rc; >>>>>>> + } >>>>>>> + >>>>>>> + if (!val) { >>>>>>> + dev_err(chip->dev, "led-max-microamp >>>>>>> shouldn't >>>>>>> be > >>>>>>> 0\n"); >>>>>>> + return -EINVAL; >>>>>>> + } >>>>>>> + >>>>>>> + val = min_t(u32, val, TORCH_CURRENT_MAX_UA * > led- >>>>>>>> chan_count); >>>>>>> + led->max_torch_current_ma = val / 1000; >>>>>>> + >>>>>>> + 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", &val); >>>>>>> + if (rc < 0) { >>>>>>> + dev_err(chip->dev, "Get flash-max- >>>>>>> microamp >>>>>>>> failed, rc=%d\n", rc); >>>>>>> + returnrc; >>>>>>> + } >>>>>>> + >>>>>>> + val = min_t(u32, val, FLASH_CURRENT_MAX_UA >>>>>>> * > >>>>>>> led- >>>>>>>> chan_count); >>>>>>> + val = min_t(u32, val, >>>>>>> FLASH_TOTAL_CURRENT_MAX_UA); >>>>>>> + s = &flash->brightness; >>>>>>> + s->min = s->step = FLASH_IRES_UA * led- >>>>>>>> chan_count; >>>>>>> + s->max = val; >>>>>>> + s->val = min_t(u32, val, >>>>>>> FLASH_CURRENT_DEFAULT_UA); >>>>>>> + led->max_flash_current_ma = val / 1000; >>>>>>> + led->flash_current_ma = s->val / 1000; >>>>>>> + >>>>>>> + rc = fwnode_property_read_u32(node, > >>>>>>> "flash- >>>>>>> max- >>>>>>> timeout-us", &val); >>>>>>> + if (rc < 0) { >>>>>>> + dev_err(chip->dev, "Get flash-max- >>>>>>> timeout- >>>>>>> us > failed, rc=%d\n", rc); >>>>>>> + returnrc; >>>>>>> + } >>>>>>> + >>>>>>> + val = min_t(u32, val, >>>>>>> FLASH_TIMEOUT_MAX_US); >>>>>>> + s = &flash->timeout; >>>>>>> + s->min = s->step = FLASH_TIMEOUT_STEP_US; >>>>>>> + s->val = s->max = val; >>>>>>> + led->max_timeout_ms = led->flash_timeout_ms >>>>>>> = >>>>>>> val / >>>>>>>> 1000; >>>>>>> + >>>>>>> + 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(parent, >>>>>>> flash, > >>>>>>> &init_data); >>>>>>> + if (rc < 0) { >>>>>>> + dev_err(chip->dev, "Register flash LED >>>>>>> classdev >>>>>>>> >>>>>>> failed, rc=%d\n", rc); >>>>>>> + return rc; >>>>>>> + } >>>>>>> + >>>>>>> + return qcom_flash_v4l2_init(led, node); >>>>>>> +} >>>>>>> + >>>>>>> +static int qcom_flash_led_probe(struct platform_device >>>>>>> *pdev) >>>>>>> +{ >>>>>>> + struct qcom_flash_chip *chip; >>>>>>> + struct qcom_flash_led *led; >>>>>>> + struct fwnode_handle *child; >>>>>>> + struct device *dev = &pdev->dev; >>>>>>> + struct regmap *map; >>>>>>> + struct reg_field *regs; >>>>>>> + int count, i, rc; >>>>>>> + u32 val, reg_base; >>>>>>> + >>>>>>> + chip = devm_kzalloc(dev, sizeof(*chip), >>>>>>> GFP_KERNEL); >>>>>>> + if (!chip) >>>>>>> + return -ENOMEM; >>>>>>> + >>>>>>> + map = dev_get_regmap(dev->parent, NULL); >>>>>>> + if (!map) { >>>>>>> + 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(map, reg_base + FLASH_TYPE_REG, >>>>>>> &val); >>>>>>> + if (rc < 0) { >>>>>>> + dev_err(dev, "Read flash module type >>>>>>> failed, > >>>>>>> rc=%d\n", rc); >>>>>>> + return rc; >>>>>>> + } >>>>>>> + >>>>>>> + if (val != FLASH_TYPE_VAL){ >>>>>>> + dev_err(dev, "type %#x is not a flash >>>>>>> module\n", > >>>>>>> val); >>>>>>> + return -ENODEV; >>>>>>> + } >>>>>>> + >>>>>>> + rc = regmap_read(map, reg_base + FLASH_SUBTYPE_REG, >>>>>>> &val); >>>>>>> + if (rc < 0) { >>>>>>> + dev_err(dev, "Read flash module subtype >>>>>>> failed, >>>>>>>> >>>>>>> rc=%d\n", rc); >>>>>>> + return rc; >>>>>>> + } >>>>>>> + >>>>>>> + if (val == FLASH_SUBTYPE_3CH_VAL) { >>>>>>> + chip->hw_type = QCOM_MVFLASH_3CH; >>>>>>> + chip->max_channels = 3; >>>>>>> + regs = mvflash_3ch_regs; >>>>>>> + } else if (val == FLASH_SUBTYPE_4CH_VAL) { >>>>>>> + chip->hw_type = QCOM_MVFLASH_4CH; >>>>>>> + chip->max_channels = 4; >>>>>>> + regs = mvflash_4ch_regs; >>>>>>> + } else { >>>>>>> + dev_err(dev, "flash 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, map, chip- >>>>>>>> r_fields, >>>>>>>> regs, REG_MAX_COUNT); >>>>>>> + if (rc < 0) { >>>>>>> + dev_err(dev, "failed to alloc regmap filed, >>>>>>> rc=%d\n", > rc); >>>>>>> + return rc; >>>>>>> + } >>>>>>> + >>>>>>> + chip->dev = dev; >>>>>>> + platform_set_drvdata(pdev, chip); >>>>>>> + mutex_init(&chip->lock); >>>>>>> + count = device_get_child_node_count(dev); >>>>>>> + if (count == 0 || count > chip->max_channels) { >>>>>>> + dev_err(dev, "No child or child count >>>>>>> exceeds >>>>>>> %d\n", > chip->max_channels); >>>>>>> + return -EINVAL; >>>>>>> + } >>>>>>> + >>>>>>> + chip->leds = devm_kcalloc(dev, count, sizeof(*chip- >>>>>>>> leds), >>>>>>>> GFP_KERNEL); >>>>>>> + if (!chip->leds) >>>>>>> + return -ENOMEM; >>>>>>> + >>>>>>> + device_for_each_child_node(dev, child) { >>>>>>> + led = &chip->leds[chip->leds_count]; >>>>>>> + led->chip = chip; >>>>>>> + rc = qcom_flash_register_led_device(dev, >>>>>>> child, >>>>>>> led); >>>>>>> + if (rc < 0) >>>>>>> + goto release; >>>>>>> + >>>>>>> + chip->leds_count++; >>>>>>> + } >>>>>>> + >>>>>>> + return 0; >>>>>>> +release: >>>>>>> + while (chip->leds && chip->leds_count--) >>>>>>> + v4l2_flash_release(chip->leds[chip- >>>>>>>> leds_count].v4l2_> flash); >>>>>>> + return rc; >>>>>>> +} >>>>>>> + >>>>>>> +static int qcom_flash_led_remove(struct platform_device >>>>>>> *pdev) >>>>>>> +{ >>>>>>> + struct qcom_flash_chip *chip= >>>>>>> platform_get_drvdata(pdev); >>>>>>> + >>>>>>> + while (chip->leds_count--) >>>>>>> + v4l2_flash_release(chip->leds[chip- >>>>>>>> leds_count].v4l2_> flash); >>>>>>> + >>>>>>> + mutex_destroy(&chip->lock); >>>>>>> + return 0; >>>>>>> +} >>>>>>> + >>>>>>> +static const struct of_device_id >>>>>>> qcom_flash_led_match_table[] >>>>>>> = { >>>>>>> + { .compatible = "qcom,spmi-flash-led" }, >>>>>>> + { } >>>>>>> +}; >>>>>>> + >>>>>>> +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"); >>>>> >>>>> Kind regards, >>>>> Dylan Van Assche >>> >>> Kind regards, >>> Dylan Van Assche >>> > > Kind regards, > Dylan Van Assche >
| |