Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/cooloney...
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 2 Jul 2015 02:09:11 +0000 (19:09 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 2 Jul 2015 02:09:11 +0000 (19:09 -0700)
Pull LED subsystem updates from Bryan Wu:
 "In this cycle, we finished to merge patches for LED Flash class
  driver.

  Other than that we have some bug fixes and new drivers for LED
  controllers"

* 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/cooloney/linux-leds: (33 commits)
  leds:lp55xx: fix firmware loading error
  leds: fix max77693-led build errors
  leds: fix aat1290 build errors
  leds: aat1290: pass flags parameter to devm_gpiod_get
  leds: ktd2692: pass flags parameter to devm_gpiod_get
  drivers/leds: don't use module_init in non-modular leds-cobalt-raq.c
  leds: aat1290: add support for V4L2 Flash sub-device
  DT: aat1290: Document handling external strobe sources
  leds: max77693: add support for V4L2 Flash sub-device
  media: Add registration helpers for V4L2 flash sub-devices
  v4l: async: Add a pointer to of_node to struct v4l2_subdev, match it
  Documentation: leds: Add description of v4l2-flash sub-device
  leds: add BCM6358 LED driver
  leds: add DT binding for BCM6358 LED controller
  leds: fix brightness changing when software blinking is active
  Documentation: leds-lp5523: describe master fader attributes
  leds: lp5523: add master_fader support
  leds: leds-gpio: Allow compile test if !GPIOLIB
  leds: leds-gpio: Add missing #include <linux/of.h>
  gpiolib: Add missing dummies for the unified device properties interface
  ...

31 files changed:
Documentation/devicetree/bindings/leds/leds-aat1290.txt [new file with mode: 0644]
Documentation/devicetree/bindings/leds/leds-bcm6328.txt [new file with mode: 0644]
Documentation/devicetree/bindings/leds/leds-bcm6358.txt [new file with mode: 0644]
Documentation/devicetree/bindings/leds/leds-ktd2692.txt [new file with mode: 0644]
Documentation/devicetree/bindings/leds/leds-tlc591xx.txt [new file with mode: 0644]
Documentation/devicetree/bindings/vendor-prefixes.txt
Documentation/leds/leds-class-flash.txt
Documentation/leds/leds-lp5523.txt
drivers/leds/Kconfig
drivers/leds/Makefile
drivers/leds/led-class.c
drivers/leds/led-core.c
drivers/leds/leds-aat1290.c [new file with mode: 0644]
drivers/leds/leds-bcm6328.c [new file with mode: 0644]
drivers/leds/leds-bcm6358.c [new file with mode: 0644]
drivers/leds/leds-cobalt-raq.c
drivers/leds/leds-gpio.c
drivers/leds/leds-ktd2692.c [new file with mode: 0644]
drivers/leds/leds-lp5523.c
drivers/leds/leds-lp55xx-common.c
drivers/leds/leds-max77693.c [new file with mode: 0644]
drivers/leds/leds-tlc591xx.c [new file with mode: 0644]
drivers/leds/leds.h
drivers/media/v4l2-core/Kconfig
drivers/media/v4l2-core/Makefile
drivers/media/v4l2-core/v4l2-async.c
drivers/media/v4l2-core/v4l2-flash-led-class.c [new file with mode: 0644]
include/linux/gpio/consumer.h
include/linux/leds.h
include/media/v4l2-flash-led-class.h [new file with mode: 0644]
include/media/v4l2-subdev.h

diff --git a/Documentation/devicetree/bindings/leds/leds-aat1290.txt b/Documentation/devicetree/bindings/leds/leds-aat1290.txt
new file mode 100644 (file)
index 0000000..c05ed91
--- /dev/null
@@ -0,0 +1,73 @@
+* Skyworks Solutions, Inc. AAT1290 Current Regulator for Flash LEDs
+
+The device is controlled through two pins: FL_EN and EN_SET. The pins when,
+asserted high, enable flash strobe and movie mode (max 1/2 of flash current)
+respectively. In order to add a capability of selecting the strobe signal source
+(e.g. CPU or camera sensor) there is an additional switch required, independent
+of the flash chip. The switch is controlled with pin control.
+
+Required properties:
+
+- compatible : Must be "skyworks,aat1290".
+- flen-gpios : Must be device tree identifier of the flash device FL_EN pin.
+- enset-gpios : Must be device tree identifier of the flash device EN_SET pin.
+
+Optional properties:
+- pinctrl-names : Must contain entries: "default", "host", "isp". Entries
+               "default" and "host" must refer to the same pin configuration
+               node, which sets the host as a strobe signal provider. Entry
+               "isp" must refer to the pin configuration node, which sets the
+               ISP as a strobe signal provider.
+
+A discrete LED element connected to the device must be represented by a child
+node - see Documentation/devicetree/bindings/leds/common.txt.
+
+Required properties of the LED child node:
+- led-max-microamp : see Documentation/devicetree/bindings/leds/common.txt
+- flash-max-microamp : see Documentation/devicetree/bindings/leds/common.txt
+                       Maximum flash LED supply current can be calculated using
+                       following formula: I = 1A * 162kohm / Rset.
+- flash-timeout-us : see Documentation/devicetree/bindings/leds/common.txt
+                     Maximum flash timeout can be calculated using following
+                     formula: T = 8.82 * 10^9 * Ct.
+
+Optional properties of the LED child node:
+- label : see Documentation/devicetree/bindings/leds/common.txt
+
+Example (by Ct = 220nF, Rset = 160kohm and exynos4412-trats2 board with
+a switch that allows for routing strobe signal either from the host or from
+the camera sensor):
+
+#include "exynos4412.dtsi"
+
+aat1290 {
+       compatible = "skyworks,aat1290";
+       flen-gpios = <&gpj1 1 GPIO_ACTIVE_HIGH>;
+       enset-gpios = <&gpj1 2 GPIO_ACTIVE_HIGH>;
+
+       pinctrl-names = "default", "host", "isp";
+       pinctrl-0 = <&camera_flash_host>;
+       pinctrl-1 = <&camera_flash_host>;
+       pinctrl-2 = <&camera_flash_isp>;
+
+       camera_flash: flash-led {
+               label = "aat1290-flash";
+               led-max-microamp = <520833>;
+               flash-max-microamp = <1012500>;
+               flash-timeout-us = <1940000>;
+       };
+};
+
+&pinctrl_0 {
+       camera_flash_host: camera-flash-host {
+               samsung,pins = "gpj1-0";
+               samsung,pin-function = <1>;
+               samsung,pin-val = <0>;
+       };
+
+       camera_flash_isp: camera-flash-isp {
+               samsung,pins = "gpj1-0";
+               samsung,pin-function = <1>;
+               samsung,pin-val = <1>;
+       };
+};
diff --git a/Documentation/devicetree/bindings/leds/leds-bcm6328.txt b/Documentation/devicetree/bindings/leds/leds-bcm6328.txt
new file mode 100644 (file)
index 0000000..f9e36ad
--- /dev/null
@@ -0,0 +1,309 @@
+LEDs connected to Broadcom BCM6328 controller
+
+This controller is present on BCM6318, BCM6328, BCM6362 and BCM63268.
+In these SoCs it's possible to control LEDs both as GPIOs or by hardware.
+However, on some devices there are Serial LEDs (LEDs connected to a 74x164
+controller), which can either be controlled by software (exporting the 74x164
+as spi-gpio. See Documentation/devicetree/bindings/gpio/gpio-74x164.txt), or
+by hardware using this driver.
+Some of these Serial LEDs are hardware controlled (e.g. ethernet LEDs) and
+exporting the 74x164 as spi-gpio prevents those LEDs to be hardware
+controlled, so the only chance to keep them working is by using this driver.
+
+BCM6328 LED controller has a HWDIS register, which controls whether a LED
+should be controlled by a hardware signal instead of the MODE register value,
+with 0 meaning hardware control enabled and 1 hardware control disabled. This
+is usually 1:1 for hardware to LED signals, but through the activity/link
+registers you have some limited control over rerouting the LEDs (as
+explained later in brcm,link-signal-sources). Even if a LED is hardware
+controlled you are still able to make it blink or light it up if it isn't,
+but you can't turn it off if the hardware decides to light it up. For this
+reason, hardware controlled LEDs aren't registered as LED class devices.
+
+Required properties:
+  - compatible : should be "brcm,bcm6328-leds".
+  - #address-cells : must be 1.
+  - #size-cells : must be 0.
+  - reg : BCM6328 LED controller address and size.
+
+Optional properties:
+  - brcm,serial-leds : Boolean, enables Serial LEDs.
+    Default : false
+
+Each LED is represented as a sub-node of the brcm,bcm6328-leds device.
+
+LED sub-node required properties:
+  - reg : LED pin number (only LEDs 0 to 23 are valid).
+
+LED sub-node optional properties:
+  a) Optional properties for sub-nodes related to software controlled LEDs:
+    - label : see Documentation/devicetree/bindings/leds/common.txt
+    - active-low : Boolean, makes LED active low.
+      Default : false
+    - default-state : see
+      Documentation/devicetree/bindings/leds/leds-gpio.txt
+    - linux,default-trigger : see
+      Documentation/devicetree/bindings/leds/common.txt
+
+  b) Optional properties for sub-nodes related to hardware controlled LEDs:
+    - brcm,hardware-controlled : Boolean, makes this LED hardware controlled.
+      Default : false
+    - brcm,link-signal-sources : An array of hardware link
+      signal sources. Up to four link hardware signals can get muxed into
+      these LEDs. Only valid for LEDs 0 to 7, where LED signals 0 to 3 may
+      be muxed to LEDs 0 to 3, and signals 4 to 7 may be muxed to LEDs
+      4 to 7. A signal can be muxed to more than one LED, and one LED can
+      have more than one source signal.
+    - brcm,activity-signal-sources : An array of hardware activity
+      signal sources. Up to four activity hardware signals can get muxed into
+      these LEDs. Only valid for LEDs 0 to 7, where LED signals 0 to 3 may
+      be muxed to LEDs 0 to 3, and signals 4 to 7 may be muxed to LEDs
+      4 to 7. A signal can be muxed to more than one LED, and one LED can
+      have more than one source signal.
+
+Examples:
+Scenario 1 : BCM6328 with 4 EPHY LEDs
+       leds0: led-controller@10000800 {
+               compatible = "brcm,bcm6328-leds";
+               #address-cells = <1>;
+               #size-cells = <0>;
+               reg = <0x10000800 0x24>;
+
+               alarm_red@2 {
+                       reg = <2>;
+                       active-low;
+                       label = "red:alarm";
+               };
+               inet_green@3 {
+                       reg = <3>;
+                       active-low;
+                       label = "green:inet";
+               };
+               power_green@4 {
+                       reg = <4>;
+                       active-low;
+                       label = "green:power";
+                       default-state = "on";
+               };
+               ephy0_spd@17 {
+                       reg = <17>;
+                       brcm,hardware-controlled;
+               };
+               ephy1_spd@18 {
+                       reg = <18>;
+                       brcm,hardware-controlled;
+               };
+               ephy2_spd@19 {
+                       reg = <19>;
+                       brcm,hardware-controlled;
+               };
+               ephy3_spd@20 {
+                       reg = <20>;
+                       brcm,hardware-controlled;
+               };
+       };
+
+Scenario 2 : BCM63268 with Serial/GPHY0 LEDs
+       leds0: led-controller@10001900 {
+               compatible = "brcm,bcm6328-leds";
+               #address-cells = <1>;
+               #size-cells = <0>;
+               reg = <0x10001900 0x24>;
+               brcm,serial-leds;
+
+               gphy0_spd0@0 {
+                       reg = <0>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <0>;
+               };
+               gphy0_spd1@1 {
+                       reg = <1>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <1>;
+               };
+               inet_red@2 {
+                       reg = <2>;
+                       active-low;
+                       label = "red:inet";
+               };
+               dsl_green@3 {
+                       reg = <3>;
+                       active-low;
+                       label = "green:dsl";
+               };
+               usb_green@4 {
+                       reg = <4>;
+                       active-low;
+                       label = "green:usb";
+               };
+               wps_green@7 {
+                       reg = <7>;
+                       active-low;
+                       label = "green:wps";
+               };
+               inet_green@8 {
+                       reg = <8>;
+                       active-low;
+                       label = "green:inet";
+               };
+               ephy0_act@9 {
+                       reg = <9>;
+                       brcm,hardware-controlled;
+               };
+               ephy1_act@10 {
+                       reg = <10>;
+                       brcm,hardware-controlled;
+               };
+               ephy2_act@11 {
+                       reg = <11>;
+                       brcm,hardware-controlled;
+               };
+               gphy0_act@12 {
+                       reg = <12>;
+                       brcm,hardware-controlled;
+               };
+               ephy0_spd@13 {
+                       reg = <13>;
+                       brcm,hardware-controlled;
+               };
+               ephy1_spd@14 {
+                       reg = <14>;
+                       brcm,hardware-controlled;
+               };
+               ephy2_spd@15 {
+                       reg = <15>;
+                       brcm,hardware-controlled;
+               };
+               power_green@20 {
+                       reg = <20>;
+                       active-low;
+                       label = "green:power";
+                       default-state = "on";
+               };
+       };
+
+Scenario 3 : BCM6362 with 1 LED for each EPHY
+       leds0: led-controller@10001900 {
+               compatible = "brcm,bcm6328-leds";
+               #address-cells = <1>;
+               #size-cells = <0>;
+               reg = <0x10001900 0x24>;
+
+               usb@0 {
+                       reg = <0>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <0>;
+                       brcm,activity-signal-sources = <0>;
+                       /* USB link/activity routed to USB LED */
+               };
+               inet@1 {
+                       reg = <1>;
+                       brcm,hardware-controlled;
+                       brcm,activity-signal-sources = <1>;
+                       /* INET activity routed to INET LED */
+               };
+               ephy0@4 {
+                       reg = <4>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <4>;
+                       /* EPHY0 link routed to EPHY0 LED */
+               };
+               ephy1@5 {
+                       reg = <5>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <5>;
+                       /* EPHY1 link routed to EPHY1 LED */
+               };
+               ephy2@6 {
+                       reg = <6>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <6>;
+                       /* EPHY2 link routed to EPHY2 LED */
+               };
+               ephy3@7 {
+                       reg = <7>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <7>;
+                       /* EPHY3 link routed to EPHY3 LED */
+               };
+               power_green@20 {
+                       reg = <20>;
+                       active-low;
+                       label = "green:power";
+                       default-state = "on";
+               };
+       };
+
+Scenario 4 : BCM6362 with 1 LED for all EPHYs
+       leds0: led-controller@10001900 {
+               compatible = "brcm,bcm6328-leds";
+               #address-cells = <1>;
+               #size-cells = <0>;
+               reg = <0x10001900 0x24>;
+
+               usb@0 {
+                       reg = <0>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <0 1>;
+                       brcm,activity-signal-sources = <0 1>;
+                       /* USB/INET link/activity routed to USB LED */
+               };
+               ephy@4 {
+                       reg = <4>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <4 5 6 7>;
+                       /* EPHY0/1/2/3 link routed to EPHY0 LED */
+               };
+               power_green@20 {
+                       reg = <20>;
+                       active-low;
+                       label = "green:power";
+                       default-state = "on";
+               };
+       };
+
+Scenario 5 : BCM6362 with EPHY LEDs swapped
+       leds0: led-controller@10001900 {
+               compatible = "brcm,bcm6328-leds";
+               #address-cells = <1>;
+               #size-cells = <0>;
+               reg = <0x10001900 0x24>;
+
+               usb@0 {
+                       reg = <0>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <0>;
+                       brcm,activity-signal-sources = <0 1>;
+                       /* USB link/act and INET act routed to USB LED */
+               };
+               ephy0@4 {
+                       reg = <4>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <7>;
+                       /* EPHY3 link routed to EPHY0 LED */
+               };
+               ephy1@5 {
+                       reg = <5>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <6>;
+                       /* EPHY2 link routed to EPHY1 LED */
+               };
+               ephy2@6 {
+                       reg = <6>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <5>;
+                       /* EPHY1 link routed to EPHY2 LED */
+               };
+               ephy3@7 {
+                       reg = <7>;
+                       brcm,hardware-controlled;
+                       brcm,link-signal-sources = <4>;
+                       /* EPHY0 link routed to EPHY3 LED */
+               };
+               power_green@20 {
+                       reg = <20>;
+                       active-low;
+                       label = "green:power";
+                       default-state = "on";
+               };
+       };
diff --git a/Documentation/devicetree/bindings/leds/leds-bcm6358.txt b/Documentation/devicetree/bindings/leds/leds-bcm6358.txt
new file mode 100644 (file)
index 0000000..b22a55b
--- /dev/null
@@ -0,0 +1,145 @@
+LEDs connected to Broadcom BCM6358 controller
+
+This controller is present on BCM6358 and BCM6368.
+In these SoCs there are Serial LEDs (LEDs connected to a 74x164 controller),
+which can either be controlled by software (exporting the 74x164 as spi-gpio.
+See Documentation/devicetree/bindings/gpio/gpio-74x164.txt), or
+by hardware using this driver.
+
+Required properties:
+  - compatible : should be "brcm,bcm6358-leds".
+  - #address-cells : must be 1.
+  - #size-cells : must be 0.
+  - reg : BCM6358 LED controller address and size.
+
+Optional properties:
+  - brcm,clk-div : SCK signal divider. Possible values are 1, 2, 4 and 8.
+    Default : 1
+  - brcm,clk-dat-low : Boolean, makes clock and data signals active low.
+    Default : false
+
+Each LED is represented as a sub-node of the brcm,bcm6358-leds device.
+
+LED sub-node required properties:
+  - reg : LED pin number (only LEDs 0 to 31 are valid).
+
+LED sub-node optional properties:
+  - label : see Documentation/devicetree/bindings/leds/common.txt
+  - active-low : Boolean, makes LED active low.
+    Default : false
+  - default-state : see
+    Documentation/devicetree/bindings/leds/leds-gpio.txt
+  - linux,default-trigger : see
+    Documentation/devicetree/bindings/leds/common.txt
+
+Examples:
+Scenario 1 : BCM6358
+       leds0: led-controller@fffe00d0 {
+               compatible = "brcm,bcm6358-leds";
+               #address-cells = <1>;
+               #size-cells = <0>;
+               reg = <0xfffe00d0 0x8>;
+
+               alarm_white {
+                       reg = <0>;
+                       active-low;
+                       label = "white:alarm";
+               };
+               tv_white {
+                       reg = <2>;
+                       active-low;
+                       label = "white:tv";
+               };
+               tel_white {
+                       reg = <3>;
+                       active-low;
+                       label = "white:tel";
+               };
+               adsl_white {
+                       reg = <4>;
+                       active-low;
+                       label = "white:adsl";
+               };
+       };
+
+Scenario 2 : BCM6368
+       leds0: led-controller@100000d0 {
+               compatible = "brcm,bcm6358-leds";
+               #address-cells = <1>;
+               #size-cells = <0>;
+               reg = <0x100000d0 0x8>;
+               brcm,pol-low;
+               brcm,clk-div = <4>;
+
+               power_red {
+                       reg = <0>;
+                       active-low;
+                       label = "red:power";
+               };
+               power_green {
+                       reg = <1>;
+                       active-low;
+                       label = "green:power";
+                       default-state = "on";
+               };
+               power_blue {
+                       reg = <2>;
+                       label = "blue:power";
+               };
+               broadband_red {
+                       reg = <3>;
+                       active-low;
+                       label = "red:broadband";
+               };
+               broadband_green {
+                       reg = <4>;
+                       label = "green:broadband";
+               };
+               broadband_blue {
+                       reg = <5>;
+                       active-low;
+                       label = "blue:broadband";
+               };
+               wireless_red {
+                       reg = <6>;
+                       active-low;
+                       label = "red:wireless";
+               };
+               wireless_green {
+                       reg = <7>;
+                       active-low;
+                       label = "green:wireless";
+               };
+               wireless_blue {
+                       reg = <8>;
+                       label = "blue:wireless";
+               };
+               phone_red {
+                       reg = <9>;
+                       active-low;
+                       label = "red:phone";
+               };
+               phone_green {
+                       reg = <10>;
+                       active-low;
+                       label = "green:phone";
+               };
+               phone_blue {
+                       reg = <11>;
+                       label = "blue:phone";
+               };
+               upgrading_red {
+                       reg = <12>;
+                       active-low;
+                       label = "red:upgrading";
+               };
+               upgrading_green {
+                       reg = <13>;
+                       active-low;
+                       label = "green:upgrading";
+               };
+               upgrading_blue {
+                       reg = <14>;
+                       label = "blue:upgrading";
+               };
+       };
diff --git a/Documentation/devicetree/bindings/leds/leds-ktd2692.txt b/Documentation/devicetree/bindings/leds/leds-ktd2692.txt
new file mode 100644 (file)
index 0000000..8537374
--- /dev/null
@@ -0,0 +1,50 @@
+* Kinetic Technologies - KTD2692 Flash LED Driver
+
+KTD2692 is the ideal power solution for high-power flash LEDs.
+It uses ExpressWire single-wire programming for maximum flexibility.
+
+The ExpressWire interface through CTRL pin can control LED on/off and
+enable/disable the IC, Movie(max 1/3 of Flash current) / Flash mode current,
+Flash timeout, LVP(low voltage protection).
+
+Also, When the AUX pin is pulled high while CTRL pin is high,
+LED current will be ramped up to the flash-mode current level.
+
+Required properties:
+- compatible : Should be "kinetic,ktd2692".
+- ctrl-gpios : Specifier of the GPIO connected to CTRL pin.
+- aux-gpios : Specifier of the GPIO connected to AUX pin.
+
+Optional properties:
+- vin-supply : "vin" LED supply (2.7V to 5.5V).
+  See Documentation/devicetree/bindings/regulator/regulator.txt
+
+A discrete LED element connected to the device must be represented by a child
+node - See Documentation/devicetree/bindings/leds/common.txt
+
+Required properties for flash LED child nodes:
+  See Documentation/devicetree/bindings/leds/common.txt
+- led-max-microamp : Minimum Threshold for Timer protection
+  is defined internally (Maximum 300mA).
+- flash-max-microamp : Flash LED maximum current
+  Formula : I(mA) = 15000 / Rset.
+- flash-max-timeout-us : Flash LED maximum timeout.
+
+Optional properties for flash LED child nodes:
+- label : See Documentation/devicetree/bindings/leds/common.txt
+
+Example:
+
+ktd2692 {
+       compatible = "kinetic,ktd2692";
+       ctrl-gpios = <&gpc0 1 0>;
+       aux-gpios = <&gpc0 2 0>;
+       vin-supply = <&vbat>;
+
+       flash-led {
+               label = "ktd2692-flash";
+               led-max-microamp = <300000>;
+               flash-max-microamp = <1500000>;
+               flash-max-timeout-us = <1835000>;
+       };
+};
diff --git a/Documentation/devicetree/bindings/leds/leds-tlc591xx.txt b/Documentation/devicetree/bindings/leds/leds-tlc591xx.txt
new file mode 100644 (file)
index 0000000..3bbbf70
--- /dev/null
@@ -0,0 +1,40 @@
+LEDs connected to tlc59116 or tlc59108
+
+Required properties
+- compatible: should be "ti,tlc59116" or "ti,tlc59108"
+- #address-cells: must be 1
+- #size-cells: must be 0
+- reg: typically 0x68
+
+Each led is represented as a sub-node of the ti,tlc59116.
+See Documentation/devicetree/bindings/leds/common.txt
+
+LED sub-node properties:
+- reg: number of LED line, 0 to 15 or 0 to 7
+- label: (optional) name of LED
+- linux,default-trigger : (optional)
+
+Examples:
+
+tlc59116@68 {
+       #address-cells = <1>;
+       #size-cells = <0>;
+       compatible = "ti,tlc59116";
+       reg = <0x68>;
+
+       wan@0 {
+               label = "wrt1900ac:amber:wan";
+               reg = <0x0>;
+       };
+
+       2g@2 {
+               label = "wrt1900ac:white:2g";
+               reg = <0x2>;
+       };
+
+       alive@9 {
+               label = "wrt1900ac:green:alive";
+               reg = <0x9>;
+               linux,default_trigger = "heartbeat";
+       };
+};
index 7b607761b7748c19899f407b062cdcaa514ea2d2..347c8fdcf250908c6dda500487f1efa2f8ef42c7 100644 (file)
@@ -114,6 +114,7 @@ isee        ISEE 2007 S.L.
 isil   Intersil
 karo   Ka-Ro electronics GmbH
 keymile        Keymile GmbH
+kinetic Kinetic Technologies
 lacie  LaCie
 lantiq Lantiq Semiconductor
 lenovo Lenovo Group Ltd.
index 19bb67355424a053127f19b12f347a9cdafd513a..8da3c6f4b60b8d095056f98a5da495d6d971931e 100644 (file)
@@ -20,3 +20,54 @@ Following sysfs attributes are exposed for controlling flash LED devices:
        - max_flash_timeout
        - flash_strobe
        - flash_fault
+
+
+V4L2 flash wrapper for flash LEDs
+=================================
+
+A LED subsystem driver can be controlled also from the level of VideoForLinux2
+subsystem. In order to enable this CONFIG_V4L2_FLASH_LED_CLASS symbol has to
+be defined in the kernel config.
+
+The driver must call the v4l2_flash_init function to get registered in the
+V4L2 subsystem. The function takes six arguments:
+- dev       : flash device, e.g. an I2C device
+- of_node   : of_node of the LED, may be NULL if the same as device's
+- fled_cdev : LED flash class device to wrap
+- iled_cdev : LED flash class device representing indicator LED associated with
+             fled_cdev, may be NULL
+- ops : V4L2 specific ops
+       * external_strobe_set - defines the source of the flash LED strobe -
+               V4L2_CID_FLASH_STROBE control or external source, typically
+               a sensor, which makes it possible to synchronise the flash
+               strobe start with exposure start,
+       * intensity_to_led_brightness and led_brightness_to_intensity - perform
+               enum led_brightness <-> V4L2 intensity conversion in a device
+               specific manner - they can be used for devices with non-linear
+               LED current scale.
+- config : configuration for V4L2 Flash sub-device
+       * dev_name - the name of the media entity, unique in the system,
+       * flash_faults - bitmask of flash faults that the LED flash class
+               device can report; corresponding LED_FAULT* bit definitions are
+               available in <linux/led-class-flash.h>,
+       * torch_intensity - constraints for the LED in TORCH mode
+               in microamperes,
+       * indicator_intensity - constraints for the indicator LED
+               in microamperes,
+       * has_external_strobe - determines whether the flash strobe source
+               can be switched to external,
+
+On remove the v4l2_flash_release function has to be called, which takes one
+argument - struct v4l2_flash pointer returned previously by v4l2_flash_init.
+This function can be safely called with NULL or error pointer argument.
+
+Please refer to drivers/leds/leds-max77693.c for an exemplary usage of the
+v4l2 flash wrapper.
+
+Once the V4L2 sub-device is registered by the driver which created the Media
+controller device, the sub-device node acts just as a node of a native V4L2
+flash API device would. The calls are simply routed to the LED flash API.
+
+Opening the V4L2 flash sub-device makes the LED subsystem sysfs interface
+unavailable. The interface is re-enabled after the V4L2 flash sub-device
+is closed.
index 5b3e91d4ac5912011f0f4dfc59e1022f9ac24476..0dbbd279c9b92ea4fa579a2a94e5cf3a8e9227d6 100644 (file)
@@ -49,6 +49,36 @@ There are two ways to run LED patterns.
 2) Firmware interface - LP55xx common interface
   For the details, please refer to 'firmware' section in leds-lp55xx.txt
 
+LP5523 has three master faders. If a channel is mapped to one of
+the master faders, its output is dimmed based on the value of the master
+fader.
+
+For example,
+
+  echo "123000123" > master_fader_leds
+
+creates the following channel-fader mappings:
+
+  channel 0,6 to master_fader1
+  channel 1,7 to master_fader2
+  channel 2,8 to master_fader3
+
+Then, to have 25% of the original output on channel 0,6:
+
+  echo 64 > master_fader1
+
+To have 0% of the original output (i.e. no output) channel 1,7:
+
+  echo 0 > master_fader2
+
+To have 100% of the original output (i.e. no dimming) on channel 2,8:
+
+  echo 255 > master_fader3
+
+To clear all master fader controls:
+
+  echo "000000000" > master_fader_leds
+
 Selftest uses always the current from the platform data.
 
 Each channel contains led current settings.
index 4191614c4651eaef0e3d58dffa2008e737ad877b..9ad35f72ab4c077791cf7942cceb36301325872d 100644 (file)
@@ -39,6 +39,32 @@ config LEDS_88PM860X
          This option enables support for on-chip LED drivers found on Marvell
          Semiconductor 88PM8606 PMIC.
 
+config LEDS_AAT1290
+       tristate "LED support for the AAT1290"
+       depends on LEDS_CLASS_FLASH
+       depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+       depends on GPIOLIB
+       depends on OF
+       depends on PINCTRL
+       help
+        This option enables support for the LEDs on the AAT1290.
+
+config LEDS_BCM6328
+       tristate "LED Support for Broadcom BCM6328"
+       depends on LEDS_CLASS
+       depends on OF
+       help
+         This option enables support for LEDs connected to the BCM6328
+         LED HW controller accessed via MMIO registers.
+
+config LEDS_BCM6358
+       tristate "LED Support for Broadcom BCM6358"
+       depends on LEDS_CLASS
+       depends on OF
+       help
+         This option enables support for LEDs connected to the BCM6358
+         LED HW controller accessed via MMIO registers.
+
 config LEDS_LM3530
        tristate "LCD Backlight driver for LM3530"
        depends on LEDS_CLASS
@@ -179,7 +205,7 @@ config LEDS_PCA9532_GPIO
 config LEDS_GPIO
        tristate "LED Support for GPIO connected LEDs"
        depends on LEDS_CLASS
-       depends on GPIOLIB
+       depends on GPIOLIB || COMPILE_TEST
        help
          This option enables support for the LEDs connected to GPIO
          outputs. To be useful the particular board must have LEDs
@@ -203,6 +229,7 @@ config LEDS_LP55XX_COMMON
        tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501"
        depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501
        select FW_LOADER
+       select FW_LOADER_USER_HELPER_FALLBACK
        help
          This option supports common operations for LP5521/5523/55231/5562/8501
          devices.
@@ -464,6 +491,25 @@ config LEDS_TCA6507
          LED driver chips accessed via the I2C bus.
          Driver support brightness control and hardware-assisted blinking.
 
+config LEDS_TLC591XX
+       tristate "LED driver for TLC59108 and TLC59116 controllers"
+       depends on LEDS_CLASS && I2C
+       select REGMAP_I2C
+       help
+         This option enables support for Texas Instruments TLC59108
+         and TLC59116 LED controllers.
+
+config LEDS_MAX77693
+       tristate "LED support for MAX77693 Flash"
+       depends on LEDS_CLASS_FLASH
+       depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+       depends on MFD_MAX77693
+       depends on OF
+       help
+         This option enables support for the flash part of the MAX77693
+         multifunction device. It has build in control for two leds in flash
+         and torch mode.
+
 config LEDS_MAX8997
        tristate "LED support for MAX8997 PMIC"
        depends on LEDS_CLASS && MFD_MAX8997
@@ -495,6 +541,15 @@ config LEDS_MENF21BMC
          This driver can also be built as a module. If so the module
          will be called leds-menf21bmc.
 
+config LEDS_KTD2692
+       tristate "LED support for KTD2692 flash LED controller"
+       depends on LEDS_CLASS_FLASH && GPIOLIB && OF
+       help
+         This option enables support for KTD2692 LED flash connected
+         through ExpressWire interface.
+
+         Say Y to enable this driver.
+
 comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
 
 config LEDS_BLINKM
index bf4609338e1047fee8119d690c376a31a0eb9074..8d6a24a2f51376fb9a6aaa704cc1d1908dc51d5a 100644 (file)
@@ -7,6 +7,9 @@ obj-$(CONFIG_LEDS_TRIGGERS)             += led-triggers.o
 
 # LED Platform Drivers
 obj-$(CONFIG_LEDS_88PM860X)            += leds-88pm860x.o
+obj-$(CONFIG_LEDS_AAT1290)             += leds-aat1290.o
+obj-$(CONFIG_LEDS_BCM6328)             += leds-bcm6328.o
+obj-$(CONFIG_LEDS_BCM6358)             += leds-bcm6358.o
 obj-$(CONFIG_LEDS_BD2802)              += leds-bd2802.o
 obj-$(CONFIG_LEDS_LOCOMO)              += leds-locomo.o
 obj-$(CONFIG_LEDS_LM3530)              += leds-lm3530.o
@@ -31,6 +34,7 @@ obj-$(CONFIG_LEDS_LP8501)             += leds-lp8501.o
 obj-$(CONFIG_LEDS_LP8788)              += leds-lp8788.o
 obj-$(CONFIG_LEDS_LP8860)              += leds-lp8860.o
 obj-$(CONFIG_LEDS_TCA6507)             += leds-tca6507.o
+obj-$(CONFIG_LEDS_TLC591XX)            += leds-tlc591xx.o
 obj-$(CONFIG_LEDS_CLEVO_MAIL)          += leds-clevo-mail.o
 obj-$(CONFIG_LEDS_IPAQ_MICRO)          += leds-ipaq-micro.o
 obj-$(CONFIG_LEDS_HP6XX)               += leds-hp6xx.o
@@ -52,6 +56,7 @@ obj-$(CONFIG_LEDS_MC13783)            += leds-mc13783.o
 obj-$(CONFIG_LEDS_NS2)                 += leds-ns2.o
 obj-$(CONFIG_LEDS_NETXBIG)             += leds-netxbig.o
 obj-$(CONFIG_LEDS_ASIC3)               += leds-asic3.o
+obj-$(CONFIG_LEDS_MAX77693)            += leds-max77693.o
 obj-$(CONFIG_LEDS_MAX8997)             += leds-max8997.o
 obj-$(CONFIG_LEDS_LM355x)              += leds-lm355x.o
 obj-$(CONFIG_LEDS_BLINKM)              += leds-blinkm.o
@@ -59,6 +64,7 @@ obj-$(CONFIG_LEDS_SYSCON)             += leds-syscon.o
 obj-$(CONFIG_LEDS_VERSATILE)           += leds-versatile.o
 obj-$(CONFIG_LEDS_MENF21BMC)           += leds-menf21bmc.o
 obj-$(CONFIG_LEDS_PM8941_WLED)         += leds-pm8941-wled.o
+obj-$(CONFIG_LEDS_KTD2692)             += leds-ktd2692.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_DAC124S085)          += leds-dac124s085.o
index 7fb2a19ac649c55906f96f17eb2a9e988658260e..beabfbc6f7cdd406f6ba96692bd5b376a893bdc4 100644 (file)
@@ -121,6 +121,11 @@ static void led_timer_function(unsigned long data)
        brightness = led_get_brightness(led_cdev);
        if (!brightness) {
                /* Time to switch the LED on. */
+               if (led_cdev->delayed_set_value) {
+                       led_cdev->blink_brightness =
+                                       led_cdev->delayed_set_value;
+                       led_cdev->delayed_set_value = 0;
+               }
                brightness = led_cdev->blink_brightness;
                delay = led_cdev->blink_delay_on;
        } else {
index 9886dace5ad226950e0b70cc9ee2b5ccd42ff228..549de7e24cfdf445f27180ab7c776da21af50a94 100644 (file)
@@ -119,10 +119,11 @@ void led_set_brightness(struct led_classdev *led_cdev,
 {
        int ret = 0;
 
-       /* delay brightness setting if need to stop soft-blink timer */
+       /* delay brightness if soft-blink is active */
        if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) {
                led_cdev->delayed_set_value = brightness;
-               schedule_work(&led_cdev->set_brightness_work);
+               if (brightness == LED_OFF)
+                       schedule_work(&led_cdev->set_brightness_work);
                return;
        }
 
diff --git a/drivers/leds/leds-aat1290.c b/drivers/leds/leds-aat1290.c
new file mode 100644 (file)
index 0000000..fd7c25f
--- /dev/null
@@ -0,0 +1,576 @@
+/*
+ *     LED Flash class driver for the AAT1290
+ *     1.5A Step-Up Current Regulator for Flash LEDs
+ *
+ *     Copyright (C) 2015, Samsung Electronics Co., Ltd.
+ *     Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/led-class-flash.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define AAT1290_MOVIE_MODE_CURRENT_ADDR        17
+#define AAT1290_MAX_MM_CURR_PERCENT_0  16
+#define AAT1290_MAX_MM_CURR_PERCENT_100        1
+
+#define AAT1290_FLASH_SAFETY_TIMER_ADDR        18
+
+#define AAT1290_MOVIE_MODE_CONFIG_ADDR 19
+#define AAT1290_MOVIE_MODE_OFF         1
+#define AAT1290_MOVIE_MODE_ON          3
+
+#define AAT1290_MM_CURRENT_RATIO_ADDR  20
+#define AAT1290_MM_TO_FL_1_92          1
+
+#define AAT1290_MM_TO_FL_RATIO         1000 / 1920
+#define AAT1290_MAX_MM_CURRENT(fl_max) (fl_max * AAT1290_MM_TO_FL_RATIO)
+
+#define AAT1290_LATCH_TIME_MIN_US      500
+#define AAT1290_LATCH_TIME_MAX_US      1000
+#define AAT1290_EN_SET_TICK_TIME_US    1
+#define AAT1290_FLEN_OFF_DELAY_TIME_US 10
+#define AAT1290_FLASH_TM_NUM_LEVELS    16
+#define AAT1290_MM_CURRENT_SCALE_SIZE  15
+
+
+struct aat1290_led_config_data {
+       /* maximum LED current in movie mode */
+       u32 max_mm_current;
+       /* maximum LED current in flash mode */
+       u32 max_flash_current;
+       /* maximum flash timeout */
+       u32 max_flash_tm;
+       /* external strobe capability */
+       bool has_external_strobe;
+       /* max LED brightness level */
+       enum led_brightness max_brightness;
+};
+
+struct aat1290_led {
+       /* platform device data */
+       struct platform_device *pdev;
+       /* secures access to the device */
+       struct mutex lock;
+
+       /* corresponding LED Flash class device */
+       struct led_classdev_flash fled_cdev;
+       /* V4L2 Flash device */
+       struct v4l2_flash *v4l2_flash;
+
+       /* FLEN pin */
+       struct gpio_desc *gpio_fl_en;
+       /* EN|SET pin  */
+       struct gpio_desc *gpio_en_set;
+       /* movie mode current scale */
+       int *mm_current_scale;
+       /* device mode */
+       bool movie_mode;
+
+       /* brightness cache */
+       unsigned int torch_brightness;
+       /* assures led-triggers compatibility */
+       struct work_struct work_brightness_set;
+};
+
+static struct aat1290_led *fled_cdev_to_led(
+                               struct led_classdev_flash *fled_cdev)
+{
+       return container_of(fled_cdev, struct aat1290_led, fled_cdev);
+}
+
+static void aat1290_as2cwire_write(struct aat1290_led *led, int addr, int value)
+{
+       int i;
+
+       gpiod_direction_output(led->gpio_fl_en, 0);
+       gpiod_direction_output(led->gpio_en_set, 0);
+
+       udelay(AAT1290_FLEN_OFF_DELAY_TIME_US);
+
+       /* write address */
+       for (i = 0; i < addr; ++i) {
+               udelay(AAT1290_EN_SET_TICK_TIME_US);
+               gpiod_direction_output(led->gpio_en_set, 0);
+               udelay(AAT1290_EN_SET_TICK_TIME_US);
+               gpiod_direction_output(led->gpio_en_set, 1);
+       }
+
+       usleep_range(AAT1290_LATCH_TIME_MIN_US, AAT1290_LATCH_TIME_MAX_US);
+
+       /* write data */
+       for (i = 0; i < value; ++i) {
+               udelay(AAT1290_EN_SET_TICK_TIME_US);
+               gpiod_direction_output(led->gpio_en_set, 0);
+               udelay(AAT1290_EN_SET_TICK_TIME_US);
+               gpiod_direction_output(led->gpio_en_set, 1);
+       }
+
+       usleep_range(AAT1290_LATCH_TIME_MIN_US, AAT1290_LATCH_TIME_MAX_US);
+}
+
+static void aat1290_set_flash_safety_timer(struct aat1290_led *led,
+                                       unsigned int micro_sec)
+{
+       struct led_classdev_flash *fled_cdev = &led->fled_cdev;
+       struct led_flash_setting *flash_tm = &fled_cdev->timeout;
+       int flash_tm_reg = AAT1290_FLASH_TM_NUM_LEVELS -
+                               (micro_sec / flash_tm->step) + 1;
+
+       aat1290_as2cwire_write(led, AAT1290_FLASH_SAFETY_TIMER_ADDR,
+                                                       flash_tm_reg);
+}
+
+static void aat1290_brightness_set(struct aat1290_led *led,
+                                       enum led_brightness brightness)
+{
+       mutex_lock(&led->lock);
+
+       if (brightness == 0) {
+               gpiod_direction_output(led->gpio_fl_en, 0);
+               gpiod_direction_output(led->gpio_en_set, 0);
+               led->movie_mode = false;
+       } else {
+               if (!led->movie_mode) {
+                       aat1290_as2cwire_write(led,
+                               AAT1290_MM_CURRENT_RATIO_ADDR,
+                               AAT1290_MM_TO_FL_1_92);
+                       led->movie_mode = true;
+               }
+
+               aat1290_as2cwire_write(led, AAT1290_MOVIE_MODE_CURRENT_ADDR,
+                               AAT1290_MAX_MM_CURR_PERCENT_0 - brightness);
+               aat1290_as2cwire_write(led, AAT1290_MOVIE_MODE_CONFIG_ADDR,
+                               AAT1290_MOVIE_MODE_ON);
+       }
+
+       mutex_unlock(&led->lock);
+}
+
+/* LED subsystem callbacks */
+
+static void aat1290_brightness_set_work(struct work_struct *work)
+{
+       struct aat1290_led *led =
+               container_of(work, struct aat1290_led, work_brightness_set);
+
+       aat1290_brightness_set(led, led->torch_brightness);
+}
+
+static void aat1290_led_brightness_set(struct led_classdev *led_cdev,
+                                       enum led_brightness brightness)
+{
+       struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+       struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+
+       led->torch_brightness = brightness;
+       schedule_work(&led->work_brightness_set);
+}
+
+static int aat1290_led_brightness_set_sync(struct led_classdev *led_cdev,
+                                       enum led_brightness brightness)
+{
+       struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+       struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+
+       aat1290_brightness_set(led, brightness);
+
+       return 0;
+}
+
+static int aat1290_led_flash_strobe_set(struct led_classdev_flash *fled_cdev,
+                                        bool state)
+
+{
+       struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+       struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+       struct led_flash_setting *timeout = &fled_cdev->timeout;
+
+       mutex_lock(&led->lock);
+
+       if (state) {
+               aat1290_set_flash_safety_timer(led, timeout->val);
+               gpiod_direction_output(led->gpio_fl_en, 1);
+       } else {
+               gpiod_direction_output(led->gpio_fl_en, 0);
+               gpiod_direction_output(led->gpio_en_set, 0);
+       }
+
+       /*
+        * To reenter movie mode after a flash event the part must be cycled
+        * off and back on to reset the movie mode and reprogrammed via the
+        * AS2Cwire. Therefore the brightness and movie_mode properties needs
+        * to be updated here to reflect the actual state.
+        */
+       led_cdev->brightness = 0;
+       led->movie_mode = false;
+
+       mutex_unlock(&led->lock);
+
+       return 0;
+}
+
+static int aat1290_led_flash_timeout_set(struct led_classdev_flash *fled_cdev,
+                                               u32 timeout)
+{
+       /*
+        * Don't do anything - flash timeout is cached in the led-class-flash
+        * core and will be applied in the strobe_set op, as writing the
+        * safety timer register spuriously turns the torch mode on.
+        */
+
+       return 0;
+}
+
+static int aat1290_led_parse_dt(struct aat1290_led *led,
+                       struct aat1290_led_config_data *cfg,
+                       struct device_node **sub_node)
+{
+       struct led_classdev *led_cdev = &led->fled_cdev.led_cdev;
+       struct device *dev = &led->pdev->dev;
+       struct device_node *child_node;
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+       struct pinctrl *pinctrl;
+#endif
+       int ret = 0;
+
+       led->gpio_fl_en = devm_gpiod_get(dev, "flen", GPIOD_ASIS);
+       if (IS_ERR(led->gpio_fl_en)) {
+               ret = PTR_ERR(led->gpio_fl_en);
+               dev_err(dev, "Unable to claim gpio \"flen\".\n");
+               return ret;
+       }
+
+       led->gpio_en_set = devm_gpiod_get(dev, "enset", GPIOD_ASIS);
+       if (IS_ERR(led->gpio_en_set)) {
+               ret = PTR_ERR(led->gpio_en_set);
+               dev_err(dev, "Unable to claim gpio \"enset\".\n");
+               return ret;
+       }
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+       pinctrl = devm_pinctrl_get_select_default(&led->pdev->dev);
+       if (IS_ERR(pinctrl)) {
+               cfg->has_external_strobe = false;
+               dev_info(dev,
+                        "No support for external strobe detected.\n");
+       } else {
+               cfg->has_external_strobe = true;
+       }
+#endif
+
+       child_node = of_get_next_available_child(dev->of_node, NULL);
+       if (!child_node) {
+               dev_err(dev, "No DT child node found for connected LED.\n");
+               return -EINVAL;
+       }
+
+       led_cdev->name = of_get_property(child_node, "label", NULL) ? :
+                                               child_node->name;
+
+       ret = of_property_read_u32(child_node, "led-max-microamp",
+                               &cfg->max_mm_current);
+       /*
+        * led-max-microamp will default to 1/20 of flash-max-microamp
+        * in case it is missing.
+        */
+       if (ret < 0)
+               dev_warn(dev,
+                       "led-max-microamp DT property missing\n");
+
+       ret = of_property_read_u32(child_node, "flash-max-microamp",
+                               &cfg->max_flash_current);
+       if (ret < 0) {
+               dev_err(dev,
+                       "flash-max-microamp DT property missing\n");
+               return ret;
+       }
+
+       ret = of_property_read_u32(child_node, "flash-max-timeout-us",
+                               &cfg->max_flash_tm);
+       if (ret < 0) {
+               dev_err(dev,
+                       "flash-max-timeout-us DT property missing\n");
+               return ret;
+       }
+
+       of_node_put(child_node);
+
+       *sub_node = child_node;
+
+       return ret;
+}
+
+static void aat1290_led_validate_mm_current(struct aat1290_led *led,
+                                       struct aat1290_led_config_data *cfg)
+{
+       int i, b = 0, e = AAT1290_MM_CURRENT_SCALE_SIZE;
+
+       while (e - b > 1) {
+               i = b + (e - b) / 2;
+               if (cfg->max_mm_current < led->mm_current_scale[i])
+                       e = i;
+               else
+                       b = i;
+       }
+
+       cfg->max_mm_current = led->mm_current_scale[b];
+       cfg->max_brightness = b + 1;
+}
+
+int init_mm_current_scale(struct aat1290_led *led,
+                       struct aat1290_led_config_data *cfg)
+{
+       int max_mm_current_percent[] = { 20, 22, 25, 28, 32, 36, 40, 45, 50, 56,
+                                               63, 71, 79, 89, 100 };
+       int i, max_mm_current =
+                       AAT1290_MAX_MM_CURRENT(cfg->max_flash_current);
+
+       led->mm_current_scale = devm_kzalloc(&led->pdev->dev,
+                                       sizeof(max_mm_current_percent),
+                                       GFP_KERNEL);
+       if (!led->mm_current_scale)
+               return -ENOMEM;
+
+       for (i = 0; i < AAT1290_MM_CURRENT_SCALE_SIZE; ++i)
+               led->mm_current_scale[i] = max_mm_current *
+                                         max_mm_current_percent[i] / 100;
+
+       return 0;
+}
+
+static int aat1290_led_get_configuration(struct aat1290_led *led,
+                                       struct aat1290_led_config_data *cfg,
+                                       struct device_node **sub_node)
+{
+       int ret;
+
+       ret = aat1290_led_parse_dt(led, cfg, sub_node);
+       if (ret < 0)
+               return ret;
+       /*
+        * Init non-linear movie mode current scale basing
+        * on the max flash current from led configuration.
+        */
+       ret = init_mm_current_scale(led, cfg);
+       if (ret < 0)
+               return ret;
+
+       aat1290_led_validate_mm_current(led, cfg);
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+#else
+       devm_kfree(&led->pdev->dev, led->mm_current_scale);
+#endif
+
+       return 0;
+}
+
+static void aat1290_init_flash_timeout(struct aat1290_led *led,
+                               struct aat1290_led_config_data *cfg)
+{
+       struct led_classdev_flash *fled_cdev = &led->fled_cdev;
+       struct led_flash_setting *setting;
+
+       /* Init flash timeout setting */
+       setting = &fled_cdev->timeout;
+       setting->min = cfg->max_flash_tm / AAT1290_FLASH_TM_NUM_LEVELS;
+       setting->max = cfg->max_flash_tm;
+       setting->step = setting->min;
+       setting->val = setting->max;
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static enum led_brightness aat1290_intensity_to_brightness(
+                                       struct v4l2_flash *v4l2_flash,
+                                       s32 intensity)
+{
+       struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+       struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+       int i;
+
+       for (i = AAT1290_MM_CURRENT_SCALE_SIZE - 1; i >= 0; --i)
+               if (intensity >= led->mm_current_scale[i])
+                       return i + 1;
+
+       return 1;
+}
+
+static s32 aat1290_brightness_to_intensity(struct v4l2_flash *v4l2_flash,
+                                       enum led_brightness brightness)
+{
+       struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+       struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+
+       return led->mm_current_scale[brightness - 1];
+}
+
+static int aat1290_led_external_strobe_set(struct v4l2_flash *v4l2_flash,
+                                               bool enable)
+{
+       struct aat1290_led *led = fled_cdev_to_led(v4l2_flash->fled_cdev);
+       struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+       struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+       struct pinctrl *pinctrl;
+
+       gpiod_direction_output(led->gpio_fl_en, 0);
+       gpiod_direction_output(led->gpio_en_set, 0);
+
+       led->movie_mode = false;
+       led_cdev->brightness = 0;
+
+       pinctrl = devm_pinctrl_get_select(&led->pdev->dev,
+                                               enable ? "isp" : "host");
+       if (IS_ERR(pinctrl)) {
+               dev_warn(&led->pdev->dev, "Unable to switch strobe source.\n");
+               return PTR_ERR(pinctrl);
+       }
+
+       return 0;
+}
+
+static void aat1290_init_v4l2_flash_config(struct aat1290_led *led,
+                                       struct aat1290_led_config_data *led_cfg,
+                                       struct v4l2_flash_config *v4l2_sd_cfg)
+{
+       struct led_classdev *led_cdev = &led->fled_cdev.led_cdev;
+       struct led_flash_setting *s;
+
+       strlcpy(v4l2_sd_cfg->dev_name, led_cdev->name,
+               sizeof(v4l2_sd_cfg->dev_name));
+
+       s = &v4l2_sd_cfg->torch_intensity;
+       s->min = led->mm_current_scale[0];
+       s->max = led_cfg->max_mm_current;
+       s->step = 1;
+       s->val = s->max;
+
+       v4l2_sd_cfg->has_external_strobe = led_cfg->has_external_strobe;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+       .external_strobe_set = aat1290_led_external_strobe_set,
+       .intensity_to_led_brightness = aat1290_intensity_to_brightness,
+       .led_brightness_to_intensity = aat1290_brightness_to_intensity,
+};
+#else
+static inline void aat1290_init_v4l2_flash_config(struct aat1290_led *led,
+                               struct aat1290_led_config_data *led_cfg,
+                               struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+static const struct v4l2_flash_ops v4l2_flash_ops;
+#endif
+
+static const struct led_flash_ops flash_ops = {
+       .strobe_set = aat1290_led_flash_strobe_set,
+       .timeout_set = aat1290_led_flash_timeout_set,
+};
+
+static int aat1290_led_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *sub_node = NULL;
+       struct aat1290_led *led;
+       struct led_classdev *led_cdev;
+       struct led_classdev_flash *fled_cdev;
+       struct aat1290_led_config_data led_cfg = {};
+       struct v4l2_flash_config v4l2_sd_cfg = {};
+       int ret;
+
+       led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+       if (!led)
+               return -ENOMEM;
+
+       led->pdev = pdev;
+       platform_set_drvdata(pdev, led);
+
+       fled_cdev = &led->fled_cdev;
+       fled_cdev->ops = &flash_ops;
+       led_cdev = &fled_cdev->led_cdev;
+
+       ret = aat1290_led_get_configuration(led, &led_cfg, &sub_node);
+       if (ret < 0)
+               return ret;
+
+       mutex_init(&led->lock);
+
+       /* Initialize LED Flash class device */
+       led_cdev->brightness_set = aat1290_led_brightness_set;
+       led_cdev->brightness_set_sync = aat1290_led_brightness_set_sync;
+       led_cdev->max_brightness = led_cfg.max_brightness;
+       led_cdev->flags |= LED_DEV_CAP_FLASH;
+       INIT_WORK(&led->work_brightness_set, aat1290_brightness_set_work);
+
+       aat1290_init_flash_timeout(led, &led_cfg);
+
+       /* Register LED Flash class device */
+       ret = led_classdev_flash_register(&pdev->dev, fled_cdev);
+       if (ret < 0)
+               goto err_flash_register;
+
+       aat1290_init_v4l2_flash_config(led, &led_cfg, &v4l2_sd_cfg);
+
+       /* Create V4L2 Flash subdev. */
+       led->v4l2_flash = v4l2_flash_init(dev, sub_node, fled_cdev, NULL,
+                                         &v4l2_flash_ops, &v4l2_sd_cfg);
+       if (IS_ERR(led->v4l2_flash)) {
+               ret = PTR_ERR(led->v4l2_flash);
+               goto error_v4l2_flash_init;
+       }
+
+       return 0;
+
+error_v4l2_flash_init:
+       led_classdev_flash_unregister(fled_cdev);
+err_flash_register:
+       mutex_destroy(&led->lock);
+
+       return ret;
+}
+
+static int aat1290_led_remove(struct platform_device *pdev)
+{
+       struct aat1290_led *led = platform_get_drvdata(pdev);
+
+       v4l2_flash_release(led->v4l2_flash);
+       led_classdev_flash_unregister(&led->fled_cdev);
+       cancel_work_sync(&led->work_brightness_set);
+
+       mutex_destroy(&led->lock);
+
+       return 0;
+}
+
+static const struct of_device_id aat1290_led_dt_match[] = {
+       { .compatible = "skyworks,aat1290" },
+       {},
+};
+
+static struct platform_driver aat1290_led_driver = {
+       .probe          = aat1290_led_probe,
+       .remove         = aat1290_led_remove,
+       .driver         = {
+               .name   = "aat1290",
+               .of_match_table = aat1290_led_dt_match,
+       },
+};
+
+module_platform_driver(aat1290_led_driver);
+
+MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
+MODULE_DESCRIPTION("Skyworks Current Regulator for Flash LEDs");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-bcm6328.c b/drivers/leds/leds-bcm6328.c
new file mode 100644 (file)
index 0000000..986fe1e
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+ * Driver for BCM6328 memory-mapped LEDs, based on leds-syscon.c
+ *
+ * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
+ * Copyright 2015 Jonas Gorski <jogo@openwrt.org>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+#include <linux/io.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BCM6328_REG_INIT               0x00
+#define BCM6328_REG_MODE_HI            0x04
+#define BCM6328_REG_MODE_LO            0x08
+#define BCM6328_REG_HWDIS              0x0c
+#define BCM6328_REG_STROBE             0x10
+#define BCM6328_REG_LNKACTSEL_HI       0x14
+#define BCM6328_REG_LNKACTSEL_LO       0x18
+#define BCM6328_REG_RBACK              0x1c
+#define BCM6328_REG_SERMUX             0x20
+
+#define BCM6328_LED_MAX_COUNT          24
+#define BCM6328_LED_DEF_DELAY          500
+#define BCM6328_LED_INTERVAL_MS                20
+
+#define BCM6328_LED_INTV_MASK          0x3f
+#define BCM6328_LED_FAST_INTV_SHIFT    6
+#define BCM6328_LED_FAST_INTV_MASK     (BCM6328_LED_INTV_MASK << \
+                                        BCM6328_LED_FAST_INTV_SHIFT)
+#define BCM6328_SERIAL_LED_EN          BIT(12)
+#define BCM6328_SERIAL_LED_MUX         BIT(13)
+#define BCM6328_SERIAL_LED_CLK_NPOL    BIT(14)
+#define BCM6328_SERIAL_LED_DATA_PPOL   BIT(15)
+#define BCM6328_SERIAL_LED_SHIFT_DIR   BIT(16)
+#define BCM6328_LED_SHIFT_TEST         BIT(30)
+#define BCM6328_LED_TEST               BIT(31)
+
+#define BCM6328_LED_MODE_MASK          3
+#define BCM6328_LED_MODE_OFF           0
+#define BCM6328_LED_MODE_FAST          1
+#define BCM6328_LED_MODE_BLINK         2
+#define BCM6328_LED_MODE_ON            3
+#define BCM6328_LED_SHIFT(X)           ((X) << 1)
+
+/**
+ * struct bcm6328_led - state container for bcm6328 based LEDs
+ * @cdev: LED class device for this LED
+ * @mem: memory resource
+ * @lock: memory lock
+ * @pin: LED pin number
+ * @blink_leds: blinking LEDs
+ * @blink_delay: blinking delay
+ * @active_low: LED is active low
+ */
+struct bcm6328_led {
+       struct led_classdev cdev;
+       void __iomem *mem;
+       spinlock_t *lock;
+       unsigned long pin;
+       unsigned long *blink_leds;
+       unsigned long *blink_delay;
+       bool active_low;
+};
+
+static void bcm6328_led_write(void __iomem *reg, unsigned long data)
+{
+       iowrite32be(data, reg);
+}
+
+static unsigned long bcm6328_led_read(void __iomem *reg)
+{
+       return ioread32be(reg);
+}
+
+/**
+ * LEDMode 64 bits / 24 LEDs
+ * bits [31:0] -> LEDs 8-23
+ * bits [47:32] -> LEDs 0-7
+ * bits [63:48] -> unused
+ */
+static unsigned long bcm6328_pin2shift(unsigned long pin)
+{
+       if (pin < 8)
+               return pin + 16; /* LEDs 0-7 (bits 47:32) */
+       else
+               return pin - 8; /* LEDs 8-23 (bits 31:0) */
+}
+
+static void bcm6328_led_mode(struct bcm6328_led *led, unsigned long value)
+{
+       void __iomem *mode;
+       unsigned long val, shift;
+
+       shift = bcm6328_pin2shift(led->pin);
+       if (shift / 16)
+               mode = led->mem + BCM6328_REG_MODE_HI;
+       else
+               mode = led->mem + BCM6328_REG_MODE_LO;
+
+       val = bcm6328_led_read(mode);
+       val &= ~(BCM6328_LED_MODE_MASK << BCM6328_LED_SHIFT(shift % 16));
+       val |= (value << BCM6328_LED_SHIFT(shift % 16));
+       bcm6328_led_write(mode, val);
+}
+
+static void bcm6328_led_set(struct led_classdev *led_cdev,
+                           enum led_brightness value)
+{
+       struct bcm6328_led *led =
+               container_of(led_cdev, struct bcm6328_led, cdev);
+       unsigned long flags;
+
+       spin_lock_irqsave(led->lock, flags);
+       *(led->blink_leds) &= ~BIT(led->pin);
+       if ((led->active_low && value == LED_OFF) ||
+           (!led->active_low && value != LED_OFF))
+               bcm6328_led_mode(led, BCM6328_LED_MODE_OFF);
+       else
+               bcm6328_led_mode(led, BCM6328_LED_MODE_ON);
+       spin_unlock_irqrestore(led->lock, flags);
+}
+
+static int bcm6328_blink_set(struct led_classdev *led_cdev,
+                            unsigned long *delay_on, unsigned long *delay_off)
+{
+       struct bcm6328_led *led =
+               container_of(led_cdev, struct bcm6328_led, cdev);
+       unsigned long delay, flags;
+
+       if (!*delay_on)
+               *delay_on = BCM6328_LED_DEF_DELAY;
+       if (!*delay_off)
+               *delay_off = BCM6328_LED_DEF_DELAY;
+
+       if (*delay_on != *delay_off) {
+               dev_dbg(led_cdev->dev,
+                       "fallback to soft blinking (delay_on != delay_off)\n");
+               return -EINVAL;
+       }
+
+       delay = *delay_on / BCM6328_LED_INTERVAL_MS;
+       if (delay == 0)
+               delay = 1;
+       else if (delay > BCM6328_LED_INTV_MASK) {
+               dev_dbg(led_cdev->dev,
+                       "fallback to soft blinking (delay > %ums)\n",
+                       BCM6328_LED_INTV_MASK * BCM6328_LED_INTERVAL_MS);
+               return -EINVAL;
+       }
+
+       spin_lock_irqsave(led->lock, flags);
+       if (*(led->blink_leds) == 0 ||
+           *(led->blink_leds) == BIT(led->pin) ||
+           *(led->blink_delay) == delay) {
+               unsigned long val;
+
+               *(led->blink_leds) |= BIT(led->pin);
+               *(led->blink_delay) = delay;
+
+               val = bcm6328_led_read(led->mem + BCM6328_REG_INIT);
+               val &= ~BCM6328_LED_FAST_INTV_MASK;
+               val |= (delay << BCM6328_LED_FAST_INTV_SHIFT);
+               bcm6328_led_write(led->mem + BCM6328_REG_INIT, val);
+
+               bcm6328_led_mode(led, BCM6328_LED_MODE_BLINK);
+
+               spin_unlock_irqrestore(led->lock, flags);
+       } else {
+               spin_unlock_irqrestore(led->lock, flags);
+               dev_dbg(led_cdev->dev,
+                       "fallback to soft blinking (delay already set)\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int bcm6328_hwled(struct device *dev, struct device_node *nc, u32 reg,
+                        void __iomem *mem, spinlock_t *lock)
+{
+       int i, cnt;
+       unsigned long flags, val;
+
+       spin_lock_irqsave(lock, flags);
+       val = bcm6328_led_read(mem + BCM6328_REG_HWDIS);
+       val &= ~BIT(reg);
+       bcm6328_led_write(mem + BCM6328_REG_HWDIS, val);
+       spin_unlock_irqrestore(lock, flags);
+
+       /* Only LEDs 0-7 can be activity/link controlled */
+       if (reg >= 8)
+               return 0;
+
+       cnt = of_property_count_elems_of_size(nc, "brcm,link-signal-sources",
+                                             sizeof(u32));
+       for (i = 0; i < cnt; i++) {
+               u32 sel;
+               void __iomem *addr;
+
+               if (reg < 4)
+                       addr = mem + BCM6328_REG_LNKACTSEL_LO;
+               else
+                       addr = mem + BCM6328_REG_LNKACTSEL_HI;
+
+               of_property_read_u32_index(nc, "brcm,link-signal-sources", i,
+                                          &sel);
+
+               if (reg / 4 != sel / 4) {
+                       dev_warn(dev, "invalid link signal source\n");
+                       continue;
+               }
+
+               spin_lock_irqsave(lock, flags);
+               val = bcm6328_led_read(addr);
+               val |= (BIT(reg) << (((sel % 4) * 4) + 16));
+               bcm6328_led_write(addr, val);
+               spin_unlock_irqrestore(lock, flags);
+       }
+
+       cnt = of_property_count_elems_of_size(nc,
+                                             "brcm,activity-signal-sources",
+                                             sizeof(u32));
+       for (i = 0; i < cnt; i++) {
+               u32 sel;
+               void __iomem *addr;
+
+               if (reg < 4)
+                       addr = mem + BCM6328_REG_LNKACTSEL_LO;
+               else
+                       addr = mem + BCM6328_REG_LNKACTSEL_HI;
+
+               of_property_read_u32_index(nc, "brcm,activity-signal-sources",
+                                          i, &sel);
+
+               if (reg / 4 != sel / 4) {
+                       dev_warn(dev, "invalid activity signal source\n");
+                       continue;
+               }
+
+               spin_lock_irqsave(lock, flags);
+               val = bcm6328_led_read(addr);
+               val |= (BIT(reg) << ((sel % 4) * 4));
+               bcm6328_led_write(addr, val);
+               spin_unlock_irqrestore(lock, flags);
+       }
+
+       return 0;
+}
+
+static int bcm6328_led(struct device *dev, struct device_node *nc, u32 reg,
+                      void __iomem *mem, spinlock_t *lock,
+                      unsigned long *blink_leds, unsigned long *blink_delay)
+{
+       struct bcm6328_led *led;
+       unsigned long flags;
+       const char *state;
+       int rc;
+
+       led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+       if (!led)
+               return -ENOMEM;
+
+       led->pin = reg;
+       led->mem = mem;
+       led->lock = lock;
+       led->blink_leds = blink_leds;
+       led->blink_delay = blink_delay;
+
+       if (of_property_read_bool(nc, "active-low"))
+               led->active_low = true;
+
+       led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name;
+       led->cdev.default_trigger = of_get_property(nc,
+                                                   "linux,default-trigger",
+                                                   NULL);
+
+       if (!of_property_read_string(nc, "default-state", &state)) {
+               spin_lock_irqsave(lock, flags);
+               if (!strcmp(state, "on")) {
+                       led->cdev.brightness = LED_FULL;
+                       bcm6328_led_mode(led, BCM6328_LED_MODE_ON);
+               } else if (!strcmp(state, "keep")) {
+                       void __iomem *mode;
+                       unsigned long val, shift;
+
+                       shift = bcm6328_pin2shift(led->pin);
+                       if (shift / 16)
+                               mode = mem + BCM6328_REG_MODE_HI;
+                       else
+                               mode = mem + BCM6328_REG_MODE_LO;
+
+                       val = bcm6328_led_read(mode) >> (shift % 16);
+                       val &= BCM6328_LED_MODE_MASK;
+                       if (val == BCM6328_LED_MODE_ON)
+                               led->cdev.brightness = LED_FULL;
+                       else {
+                               led->cdev.brightness = LED_OFF;
+                               bcm6328_led_mode(led, BCM6328_LED_MODE_OFF);
+                       }
+               } else {
+                       led->cdev.brightness = LED_OFF;
+                       bcm6328_led_mode(led, BCM6328_LED_MODE_OFF);
+               }
+               spin_unlock_irqrestore(lock, flags);
+       }
+
+       led->cdev.brightness_set = bcm6328_led_set;
+       led->cdev.blink_set = bcm6328_blink_set;
+
+       rc = led_classdev_register(dev, &led->cdev);
+       if (rc < 0)
+               return rc;
+
+       dev_dbg(dev, "registered LED %s\n", led->cdev.name);
+
+       return 0;
+}
+
+static int bcm6328_leds_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np = pdev->dev.of_node;
+       struct device_node *child;
+       struct resource *mem_r;
+       void __iomem *mem;
+       spinlock_t *lock;
+       unsigned long val, *blink_leds, *blink_delay;
+
+       mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!mem_r)
+               return -EINVAL;
+
+       mem = devm_ioremap_resource(dev, mem_r);
+       if (IS_ERR(mem))
+               return PTR_ERR(mem);
+
+       lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
+       if (!lock)
+               return -ENOMEM;
+
+       blink_leds = devm_kzalloc(dev, sizeof(*blink_leds), GFP_KERNEL);
+       if (!blink_leds)
+               return -ENOMEM;
+
+       blink_delay = devm_kzalloc(dev, sizeof(*blink_delay), GFP_KERNEL);
+       if (!blink_delay)
+               return -ENOMEM;
+
+       spin_lock_init(lock);
+
+       bcm6328_led_write(mem + BCM6328_REG_HWDIS, ~0);
+       bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_HI, 0);
+       bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_LO, 0);
+
+       val = bcm6328_led_read(mem + BCM6328_REG_INIT);
+       val &= ~BCM6328_SERIAL_LED_EN;
+       if (of_property_read_bool(np, "brcm,serial-leds"))
+               val |= BCM6328_SERIAL_LED_EN;
+       bcm6328_led_write(mem + BCM6328_REG_INIT, val);
+
+       for_each_available_child_of_node(np, child) {
+               int rc;
+               u32 reg;
+
+               if (of_property_read_u32(child, "reg", &reg))
+                       continue;
+
+               if (reg >= BCM6328_LED_MAX_COUNT) {
+                       dev_err(dev, "invalid LED (>= %d)\n",
+                               BCM6328_LED_MAX_COUNT);
+                       continue;
+               }
+
+               if (of_property_read_bool(child, "brcm,hardware-controlled"))
+                       rc = bcm6328_hwled(dev, child, reg, mem, lock);
+               else
+                       rc = bcm6328_led(dev, child, reg, mem, lock,
+                                        blink_leds, blink_delay);
+
+               if (rc < 0)
+                       return rc;
+       }
+
+       return 0;
+}
+
+static const struct of_device_id bcm6328_leds_of_match[] = {
+       { .compatible = "brcm,bcm6328-leds", },
+       { },
+};
+
+static struct platform_driver bcm6328_leds_driver = {
+       .probe = bcm6328_leds_probe,
+       .driver = {
+               .name = "leds-bcm6328",
+               .of_match_table = bcm6328_leds_of_match,
+       },
+};
+
+module_platform_driver(bcm6328_leds_driver);
+
+MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
+MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
+MODULE_DESCRIPTION("LED driver for BCM6328 controllers");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:leds-bcm6328");
diff --git a/drivers/leds/leds-bcm6358.c b/drivers/leds/leds-bcm6358.c
new file mode 100644 (file)
index 0000000..21f9693
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c
+ *
+ * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BCM6358_REG_MODE               0x0
+#define BCM6358_REG_CTRL               0x4
+
+#define BCM6358_SLED_CLKDIV_MASK       3
+#define BCM6358_SLED_CLKDIV_1          0
+#define BCM6358_SLED_CLKDIV_2          1
+#define BCM6358_SLED_CLKDIV_4          2
+#define BCM6358_SLED_CLKDIV_8          3
+
+#define BCM6358_SLED_POLARITY          BIT(2)
+#define BCM6358_SLED_BUSY              BIT(3)
+
+#define BCM6358_SLED_MAX_COUNT         32
+#define BCM6358_SLED_WAIT              100
+
+/**
+ * struct bcm6358_led - state container for bcm6358 based LEDs
+ * @cdev: LED class device for this LED
+ * @mem: memory resource
+ * @lock: memory lock
+ * @pin: LED pin number
+ * @active_low: LED is active low
+ */
+struct bcm6358_led {
+       struct led_classdev cdev;
+       void __iomem *mem;
+       spinlock_t *lock;
+       unsigned long pin;
+       bool active_low;
+};
+
+static void bcm6358_led_write(void __iomem *reg, unsigned long data)
+{
+       iowrite32be(data, reg);
+}
+
+static unsigned long bcm6358_led_read(void __iomem *reg)
+{
+       return ioread32be(reg);
+}
+
+static unsigned long bcm6358_led_busy(void __iomem *mem)
+{
+       unsigned long val;
+
+       while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) &
+               BCM6358_SLED_BUSY)
+               udelay(BCM6358_SLED_WAIT);
+
+       return val;
+}
+
+static void bcm6358_led_mode(struct bcm6358_led *led, unsigned long value)
+{
+       unsigned long val;
+
+       bcm6358_led_busy(led->mem);
+
+       val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
+       if ((led->active_low && value == LED_OFF) ||
+           (!led->active_low && value != LED_OFF))
+               val |= BIT(led->pin);
+       else
+               val &= ~(BIT(led->pin));
+       bcm6358_led_write(led->mem + BCM6358_REG_MODE, val);
+}
+
+static void bcm6358_led_set(struct led_classdev *led_cdev,
+                           enum led_brightness value)
+{
+       struct bcm6358_led *led =
+               container_of(led_cdev, struct bcm6358_led, cdev);
+       unsigned long flags;
+
+       spin_lock_irqsave(led->lock, flags);
+       bcm6358_led_mode(led, value);
+       spin_unlock_irqrestore(led->lock, flags);
+}
+
+static int bcm6358_led(struct device *dev, struct device_node *nc, u32 reg,
+                      void __iomem *mem, spinlock_t *lock)
+{
+       struct bcm6358_led *led;
+       unsigned long flags;
+       const char *state;
+       int rc;
+
+       led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+       if (!led)
+               return -ENOMEM;
+
+       led->pin = reg;
+       led->mem = mem;
+       led->lock = lock;
+
+       if (of_property_read_bool(nc, "active-low"))
+               led->active_low = true;
+
+       led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name;
+       led->cdev.default_trigger = of_get_property(nc,
+                                                   "linux,default-trigger",
+                                                   NULL);
+
+       spin_lock_irqsave(lock, flags);
+       if (!of_property_read_string(nc, "default-state", &state)) {
+               if (!strcmp(state, "on")) {
+                       led->cdev.brightness = LED_FULL;
+               } else if (!strcmp(state, "keep")) {
+                       unsigned long val;
+
+                       bcm6358_led_busy(led->mem);
+
+                       val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
+                       val &= BIT(led->pin);
+                       if ((led->active_low && !val) ||
+                           (!led->active_low && val))
+                               led->cdev.brightness = LED_FULL;
+                       else
+                               led->cdev.brightness = LED_OFF;
+               } else {
+                       led->cdev.brightness = LED_OFF;
+               }
+       } else {
+               led->cdev.brightness = LED_OFF;
+       }
+       bcm6358_led_mode(led, led->cdev.brightness);
+       spin_unlock_irqrestore(lock, flags);
+
+       led->cdev.brightness_set = bcm6358_led_set;
+
+       rc = led_classdev_register(dev, &led->cdev);
+       if (rc < 0)
+               return rc;
+
+       dev_dbg(dev, "registered LED %s\n", led->cdev.name);
+
+       return 0;
+}
+
+static int bcm6358_leds_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np = pdev->dev.of_node;
+       struct device_node *child;
+       struct resource *mem_r;
+       void __iomem *mem;
+       spinlock_t *lock; /* memory lock */
+       unsigned long val;
+       u32 clk_div;
+
+       mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!mem_r)
+               return -EINVAL;
+
+       mem = devm_ioremap_resource(dev, mem_r);
+       if (IS_ERR(mem))
+               return PTR_ERR(mem);
+
+       lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
+       if (!lock)
+               return -ENOMEM;
+
+       spin_lock_init(lock);
+
+       val = bcm6358_led_busy(mem);
+       val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK);
+       if (of_property_read_bool(np, "brcm,clk-dat-low"))
+               val |= BCM6358_SLED_POLARITY;
+       of_property_read_u32(np, "brcm,clk-div", &clk_div);
+       switch (clk_div) {
+       case 8:
+               val |= BCM6358_SLED_CLKDIV_8;
+               break;
+       case 4:
+               val |= BCM6358_SLED_CLKDIV_4;
+               break;
+       case 2:
+               val |= BCM6358_SLED_CLKDIV_2;
+               break;
+       default:
+               val |= BCM6358_SLED_CLKDIV_1;
+               break;
+       }
+       bcm6358_led_write(mem + BCM6358_REG_CTRL, val);
+
+       for_each_available_child_of_node(np, child) {
+               int rc;
+               u32 reg;
+
+               if (of_property_read_u32(child, "reg", &reg))
+                       continue;
+
+               if (reg >= BCM6358_SLED_MAX_COUNT) {
+                       dev_err(dev, "invalid LED (%u >= %d)\n", reg,
+                               BCM6358_SLED_MAX_COUNT);
+                       continue;
+               }
+
+               rc = bcm6358_led(dev, child, reg, mem, lock);
+               if (rc < 0)
+                       return rc;
+       }
+
+       return 0;
+}
+
+static const struct of_device_id bcm6358_leds_of_match[] = {
+       { .compatible = "brcm,bcm6358-leds", },
+       { },
+};
+
+static struct platform_driver bcm6358_leds_driver = {
+       .probe = bcm6358_leds_probe,
+       .driver = {
+               .name = "leds-bcm6358",
+               .of_match_table = bcm6358_leds_of_match,
+       },
+};
+
+module_platform_driver(bcm6358_leds_driver);
+
+MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
+MODULE_DESCRIPTION("LED driver for BCM6358 controllers");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:leds-bcm6358");
index 06dbe18a2065b8d0d799ed1cbcd92083d2a09fd9..b316df4a8c1e375c5c2ed3e20454fcc661159d3e 100644 (file)
@@ -108,20 +108,8 @@ err_null:
        return retval;
 }
 
-static int cobalt_raq_led_remove(struct platform_device *pdev)
-{
-       led_classdev_unregister(&raq_power_off_led);
-       led_classdev_unregister(&raq_web_led);
-
-       if (led_port)
-               led_port = NULL;
-
-       return 0;
-}
-
 static struct platform_driver cobalt_raq_led_driver = {
        .probe  = cobalt_raq_led_probe,
-       .remove = cobalt_raq_led_remove,
        .driver = {
                .name   = "cobalt-raq-leds",
        },
@@ -131,5 +119,4 @@ static int __init cobalt_raq_led_init(void)
 {
        return platform_driver_register(&cobalt_raq_led_driver);
 }
-
-module_init(cobalt_raq_led_init);
+device_initcall(cobalt_raq_led_init);
index d2d54d62afee6b701835c1e39777fb5921cfc698..af1876a3a77c883a630f1c7765864ea28d3f0a03 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/kernel.h>
 #include <linux/leds.h>
 #include <linux/module.h>
+#include <linux/of.h>
 #include <linux/platform_device.h>
 #include <linux/property.h>
 #include <linux/slab.h>
@@ -198,8 +199,10 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
                } else {
                        if (IS_ENABLED(CONFIG_OF) && !led.name && np)
                                led.name = np->name;
-                       if (!led.name)
-                               return ERR_PTR(-EINVAL);
+                       if (!led.name) {
+                               ret = -EINVAL;
+                               goto err;
+                       }
                }
                fwnode_property_read_string(child, "linux,default-trigger",
                                            &led.default_trigger);
@@ -217,18 +220,19 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
                if (fwnode_property_present(child, "retain-state-suspended"))
                        led.retain_state_suspended = 1;
 
-               ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
+               ret = create_gpio_led(&led, &priv->leds[priv->num_leds],
                                      dev, NULL);
                if (ret < 0) {
                        fwnode_handle_put(child);
                        goto err;
                }
+               priv->num_leds++;
        }
 
        return priv;
 
 err:
-       for (count = priv->num_leds - 2; count >= 0; count--)
+       for (count = priv->num_leds - 1; count >= 0; count--)
                delete_gpio_led(&priv->leds[count]);
        return ERR_PTR(ret);
 }
diff --git a/drivers/leds/leds-ktd2692.c b/drivers/leds/leds-ktd2692.c
new file mode 100644 (file)
index 0000000..2ae8c4d
--- /dev/null
@@ -0,0 +1,443 @@
+/*
+ * LED driver : leds-ktd2692.c
+ *
+ * Copyright (C) 2015 Samsung Electronics
+ * Ingi Kim <ingi2.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+
+/* Value related the movie mode */
+#define KTD2692_MOVIE_MODE_CURRENT_LEVELS      16
+#define KTD2692_MM_TO_FL_RATIO(x)              ((x) / 3)
+#define KTD2962_MM_MIN_CURR_THRESHOLD_SCALE    8
+
+/* Value related the flash mode */
+#define KTD2692_FLASH_MODE_TIMEOUT_LEVELS      8
+#define KTD2692_FLASH_MODE_TIMEOUT_DISABLE     0
+#define KTD2692_FLASH_MODE_CURR_PERCENT(x)     (((x) * 16) / 100)
+
+/* Macro for getting offset of flash timeout */
+#define GET_TIMEOUT_OFFSET(timeout, step)      ((timeout) / (step))
+
+/* Base register address */
+#define KTD2692_REG_LVP_BASE                   0x00
+#define KTD2692_REG_FLASH_TIMEOUT_BASE         0x20
+#define KTD2692_REG_MM_MIN_CURR_THRESHOLD_BASE 0x40
+#define KTD2692_REG_MOVIE_CURRENT_BASE         0x60
+#define KTD2692_REG_FLASH_CURRENT_BASE         0x80
+#define KTD2692_REG_MODE_BASE                  0xA0
+
+/* Set bit coding time for expresswire interface */
+#define KTD2692_TIME_RESET_US                  700
+#define KTD2692_TIME_DATA_START_TIME_US                10
+#define KTD2692_TIME_HIGH_END_OF_DATA_US       350
+#define KTD2692_TIME_LOW_END_OF_DATA_US                10
+#define KTD2692_TIME_SHORT_BITSET_US           4
+#define KTD2692_TIME_LONG_BITSET_US            12
+
+/* KTD2692 default length of name */
+#define KTD2692_NAME_LENGTH                    20
+
+enum ktd2692_bitset {
+       KTD2692_LOW = 0,
+       KTD2692_HIGH,
+};
+
+/* Movie / Flash Mode Control */
+enum ktd2692_led_mode {
+       KTD2692_MODE_DISABLE = 0,       /* default */
+       KTD2692_MODE_MOVIE,
+       KTD2692_MODE_FLASH,
+};
+
+struct ktd2692_led_config_data {
+       /* maximum LED current in movie mode */
+       u32 movie_max_microamp;
+       /* maximum LED current in flash mode */
+       u32 flash_max_microamp;
+       /* maximum flash timeout */
+       u32 flash_max_timeout;
+       /* max LED brightness level */
+       enum led_brightness max_brightness;
+};
+
+struct ktd2692_context {
+       /* Related LED Flash class device */
+       struct led_classdev_flash fled_cdev;
+
+       /* secures access to the device */
+       struct mutex lock;
+       struct regulator *regulator;
+       struct work_struct work_brightness_set;
+
+       struct gpio_desc *aux_gpio;
+       struct gpio_desc *ctrl_gpio;
+
+       enum ktd2692_led_mode mode;
+       enum led_brightness torch_brightness;
+};
+
+static struct ktd2692_context *fled_cdev_to_led(
+                               struct led_classdev_flash *fled_cdev)
+{
+       return container_of(fled_cdev, struct ktd2692_context, fled_cdev);
+}
+
+static void ktd2692_expresswire_start(struct ktd2692_context *led)
+{
+       gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH);
+       udelay(KTD2692_TIME_DATA_START_TIME_US);
+}
+
+static void ktd2692_expresswire_reset(struct ktd2692_context *led)
+{
+       gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW);
+       udelay(KTD2692_TIME_RESET_US);
+}
+
+static void ktd2692_expresswire_end(struct ktd2692_context *led)
+{
+       gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW);
+       udelay(KTD2692_TIME_LOW_END_OF_DATA_US);
+       gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH);
+       udelay(KTD2692_TIME_HIGH_END_OF_DATA_US);
+}
+
+static void ktd2692_expresswire_set_bit(struct ktd2692_context *led, bool bit)
+{
+       /*
+        * The Low Bit(0) and High Bit(1) is based on a time detection
+        * algorithm between time low and time high
+        * Time_(L_LB) : Low time of the Low Bit(0)
+        * Time_(H_LB) : High time of the LOW Bit(0)
+        * Time_(L_HB) : Low time of the High Bit(1)
+        * Time_(H_HB) : High time of the High Bit(1)
+        *
+        * It can be simplified to:
+        * Low Bit(0) : 2 * Time_(H_LB) < Time_(L_LB)
+        * High Bit(1) : 2 * Time_(L_HB) < Time_(H_HB)
+        * HIGH  ___           ____    _..     _________    ___
+        *          |_________|    |_..  |____|         |__|
+        * LOW        <L_LB>  <H_LB>     <L_HB>  <H_HB>
+        *          [  Low Bit (0) ]     [  High Bit(1) ]
+        */
+       if (bit) {
+               gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW);
+               udelay(KTD2692_TIME_SHORT_BITSET_US);
+               gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH);
+               udelay(KTD2692_TIME_LONG_BITSET_US);
+       } else {
+               gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW);
+               udelay(KTD2692_TIME_LONG_BITSET_US);
+               gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH);
+               udelay(KTD2692_TIME_SHORT_BITSET_US);
+       }
+}
+
+static void ktd2692_expresswire_write(struct ktd2692_context *led, u8 value)
+{
+       int i;
+
+       ktd2692_expresswire_start(led);
+       for (i = 7; i >= 0; i--)
+               ktd2692_expresswire_set_bit(led, value & BIT(i));
+       ktd2692_expresswire_end(led);
+}
+
+static void ktd2692_brightness_set(struct ktd2692_context *led,
+                                  enum led_brightness brightness)
+{
+       mutex_lock(&led->lock);
+
+       if (brightness == LED_OFF) {
+               led->mode = KTD2692_MODE_DISABLE;
+               gpiod_direction_output(led->aux_gpio, KTD2692_LOW);
+       } else {
+               ktd2692_expresswire_write(led, brightness |
+                                       KTD2692_REG_MOVIE_CURRENT_BASE);
+               led->mode = KTD2692_MODE_MOVIE;
+       }
+
+       ktd2692_expresswire_write(led, led->mode | KTD2692_REG_MODE_BASE);
+       mutex_unlock(&led->lock);
+}
+
+static void ktd2692_brightness_set_work(struct work_struct *work)
+{
+       struct ktd2692_context *led =
+               container_of(work, struct ktd2692_context, work_brightness_set);
+
+       ktd2692_brightness_set(led, led->torch_brightness);
+}
+
+static void ktd2692_led_brightness_set(struct led_classdev *led_cdev,
+                                      enum led_brightness brightness)
+{
+       struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+       struct ktd2692_context *led = fled_cdev_to_led(fled_cdev);
+
+       led->torch_brightness = brightness;
+       schedule_work(&led->work_brightness_set);
+}
+
+static int ktd2692_led_brightness_set_sync(struct led_classdev *led_cdev,
+                                          enum led_brightness brightness)
+{
+       struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+       struct ktd2692_context *led = fled_cdev_to_led(fled_cdev);
+
+       ktd2692_brightness_set(led, brightness);
+
+       return 0;
+}
+
+static int ktd2692_led_flash_strobe_set(struct led_classdev_flash *fled_cdev,
+                                       bool state)
+{
+       struct ktd2692_context *led = fled_cdev_to_led(fled_cdev);
+       struct led_flash_setting *timeout = &fled_cdev->timeout;
+       u32 flash_tm_reg;
+
+       mutex_lock(&led->lock);
+
+       if (state) {
+               flash_tm_reg = GET_TIMEOUT_OFFSET(timeout->val, timeout->step);
+               ktd2692_expresswire_write(led, flash_tm_reg
+                               | KTD2692_REG_FLASH_TIMEOUT_BASE);
+
+               led->mode = KTD2692_MODE_FLASH;
+               gpiod_direction_output(led->aux_gpio, KTD2692_HIGH);
+       } else {
+               led->mode = KTD2692_MODE_DISABLE;
+               gpiod_direction_output(led->aux_gpio, KTD2692_LOW);
+       }
+
+       ktd2692_expresswire_write(led, led->mode | KTD2692_REG_MODE_BASE);
+
+       fled_cdev->led_cdev.brightness = LED_OFF;
+       led->mode = KTD2692_MODE_DISABLE;
+
+       mutex_unlock(&led->lock);
+
+       return 0;
+}
+
+static int ktd2692_led_flash_timeout_set(struct led_classdev_flash *fled_cdev,
+                                        u32 timeout)
+{
+       return 0;
+}
+
+static void ktd2692_init_movie_current_max(struct ktd2692_led_config_data *cfg)
+{
+       u32 offset, step;
+       u32 movie_current_microamp;
+
+       offset = KTD2692_MOVIE_MODE_CURRENT_LEVELS;
+       step = KTD2692_MM_TO_FL_RATIO(cfg->flash_max_microamp)
+               / KTD2692_MOVIE_MODE_CURRENT_LEVELS;
+
+       do {
+               movie_current_microamp = step * offset;
+               offset--;
+       } while ((movie_current_microamp > cfg->movie_max_microamp) &&
+               (offset > 0));
+
+       cfg->max_brightness = offset;
+}
+
+static void ktd2692_init_flash_timeout(struct led_classdev_flash *fled_cdev,
+                                      struct ktd2692_led_config_data *cfg)
+{
+       struct led_flash_setting *setting;
+
+       setting = &fled_cdev->timeout;
+       setting->min = KTD2692_FLASH_MODE_TIMEOUT_DISABLE;
+       setting->max = cfg->flash_max_timeout;
+       setting->step = cfg->flash_max_timeout
+                       / (KTD2692_FLASH_MODE_TIMEOUT_LEVELS - 1);
+       setting->val = cfg->flash_max_timeout;
+}
+
+static void ktd2692_setup(struct ktd2692_context *led)
+{
+       led->mode = KTD2692_MODE_DISABLE;
+       ktd2692_expresswire_reset(led);
+       gpiod_direction_output(led->aux_gpio, KTD2692_LOW);
+
+       ktd2692_expresswire_write(led, (KTD2962_MM_MIN_CURR_THRESHOLD_SCALE - 1)
+                                | KTD2692_REG_MM_MIN_CURR_THRESHOLD_BASE);
+       ktd2692_expresswire_write(led, KTD2692_FLASH_MODE_CURR_PERCENT(45)
+                                | KTD2692_REG_FLASH_CURRENT_BASE);
+}
+
+static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev,
+                           struct ktd2692_led_config_data *cfg)
+{
+       struct device_node *np = dev->of_node;
+       struct device_node *child_node;
+       int ret;
+
+       if (!dev->of_node)
+               return -ENXIO;
+
+       led->ctrl_gpio = devm_gpiod_get(dev, "ctrl", GPIOD_ASIS);
+       if (IS_ERR(led->ctrl_gpio)) {
+               ret = PTR_ERR(led->ctrl_gpio);
+               dev_err(dev, "cannot get ctrl-gpios %d\n", ret);
+               return ret;
+       }
+
+       led->aux_gpio = devm_gpiod_get(dev, "aux", GPIOD_ASIS);
+       if (IS_ERR(led->aux_gpio)) {
+               ret = PTR_ERR(led->aux_gpio);
+               dev_err(dev, "cannot get aux-gpios %d\n", ret);
+               return ret;
+       }
+
+       led->regulator = devm_regulator_get(dev, "vin");
+       if (IS_ERR(led->regulator))
+               led->regulator = NULL;
+
+       if (led->regulator) {
+               ret = regulator_enable(led->regulator);
+               if (ret)
+                       dev_err(dev, "Failed to enable supply: %d\n", ret);
+       }
+
+       child_node = of_get_next_available_child(np, NULL);
+       if (!child_node) {
+               dev_err(dev, "No DT child node found for connected LED.\n");
+               return -EINVAL;
+       }
+
+       led->fled_cdev.led_cdev.name =
+               of_get_property(child_node, "label", NULL) ? : child_node->name;
+
+       ret = of_property_read_u32(child_node, "led-max-microamp",
+                                  &cfg->movie_max_microamp);
+       if (ret) {
+               dev_err(dev, "failed to parse led-max-microamp\n");
+               return ret;
+       }
+
+       ret = of_property_read_u32(child_node, "flash-max-microamp",
+                                  &cfg->flash_max_microamp);
+       if (ret) {
+               dev_err(dev, "failed to parse flash-max-microamp\n");
+               return ret;
+       }
+
+       ret = of_property_read_u32(child_node, "flash-max-timeout-us",
+                                  &cfg->flash_max_timeout);
+       if (ret)
+               dev_err(dev, "failed to parse flash-max-timeout-us\n");
+
+       of_node_put(child_node);
+       return ret;
+}
+
+static const struct led_flash_ops flash_ops = {
+       .strobe_set = ktd2692_led_flash_strobe_set,
+       .timeout_set = ktd2692_led_flash_timeout_set,
+};
+
+static int ktd2692_probe(struct platform_device *pdev)
+{
+       struct ktd2692_context *led;
+       struct led_classdev *led_cdev;
+       struct led_classdev_flash *fled_cdev;
+       struct ktd2692_led_config_data led_cfg;
+       int ret;
+
+       led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
+       if (!led)
+               return -ENOMEM;
+
+       fled_cdev = &led->fled_cdev;
+       led_cdev = &fled_cdev->led_cdev;
+
+       ret = ktd2692_parse_dt(led, &pdev->dev, &led_cfg);
+       if (ret)
+               return ret;
+
+       ktd2692_init_flash_timeout(fled_cdev, &led_cfg);
+       ktd2692_init_movie_current_max(&led_cfg);
+
+       fled_cdev->ops = &flash_ops;
+
+       led_cdev->max_brightness = led_cfg.max_brightness;
+       led_cdev->brightness_set = ktd2692_led_brightness_set;
+       led_cdev->brightness_set_sync = ktd2692_led_brightness_set_sync;
+       led_cdev->flags |= LED_CORE_SUSPENDRESUME | LED_DEV_CAP_FLASH;
+
+       mutex_init(&led->lock);
+       INIT_WORK(&led->work_brightness_set, ktd2692_brightness_set_work);
+
+       platform_set_drvdata(pdev, led);
+
+       ret = led_classdev_flash_register(&pdev->dev, fled_cdev);
+       if (ret) {
+               dev_err(&pdev->dev, "can't register LED %s\n", led_cdev->name);
+               mutex_destroy(&led->lock);
+               return ret;
+       }
+
+       ktd2692_setup(led);
+
+       return 0;
+}
+
+static int ktd2692_remove(struct platform_device *pdev)
+{
+       struct ktd2692_context *led = platform_get_drvdata(pdev);
+       int ret;
+
+       led_classdev_flash_unregister(&led->fled_cdev);
+       cancel_work_sync(&led->work_brightness_set);
+
+       if (led->regulator) {
+               ret = regulator_disable(led->regulator);
+               if (ret)
+                       dev_err(&pdev->dev,
+                               "Failed to disable supply: %d\n", ret);
+       }
+
+       mutex_destroy(&led->lock);
+
+       return 0;
+}
+
+static const struct of_device_id ktd2692_match[] = {
+       { .compatible = "kinetic,ktd2692", },
+       { /* sentinel */ },
+};
+
+static struct platform_driver ktd2692_driver = {
+       .driver = {
+               .name  = "ktd2692",
+               .of_match_table = ktd2692_match,
+       },
+       .probe  = ktd2692_probe,
+       .remove = ktd2692_remove,
+};
+
+module_platform_driver(ktd2692_driver);
+
+MODULE_AUTHOR("Ingi Kim <ingi2.kim@samsung.com>");
+MODULE_DESCRIPTION("Kinetic KTD2692 LED driver");
+MODULE_LICENSE("GPL v2");
index 9e1716f8098ca2b95aa9e558b912968af8d1e2ed..584dbbcec65955d49f8e9603e129b1c0f83c2122 100644 (file)
@@ -50,6 +50,7 @@
 #define LP5523_REG_OP_MODE             0x01
 #define LP5523_REG_ENABLE_LEDS_MSB     0x04
 #define LP5523_REG_ENABLE_LEDS_LSB     0x05
+#define LP5523_REG_LED_CTRL_BASE       0x06
 #define LP5523_REG_LED_PWM_BASE                0x16
 #define LP5523_REG_LED_CURRENT_BASE    0x26
 #define LP5523_REG_CONFIG              0x36
@@ -57,6 +58,7 @@
 #define LP5523_REG_RESET               0x3D
 #define LP5523_REG_LED_TEST_CTRL       0x41
 #define LP5523_REG_LED_TEST_ADC                0x42
+#define LP5523_REG_MASTER_FADER_BASE   0x48
 #define LP5523_REG_CH1_PROG_START      0x4C
 #define LP5523_REG_CH2_PROG_START      0x4D
 #define LP5523_REG_CH3_PROG_START      0x4E
@@ -78,6 +80,9 @@
 #define LP5523_EXT_CLK_USED            0x08
 #define LP5523_ENG_STATUS_MASK         0x07
 
+#define LP5523_FADER_MAPPING_MASK      0xC0
+#define LP5523_FADER_MAPPING_SHIFT     6
+
 /* Memory Page Selection */
 #define LP5523_PAGE_ENG1               0
 #define LP5523_PAGE_ENG2               1
@@ -666,6 +671,137 @@ release_lock:
        return pos;
 }
 
+#define show_fader(nr)                                         \
+static ssize_t show_master_fader##nr(struct device *dev,       \
+                           struct device_attribute *attr,      \
+                           char *buf)                          \
+{                                                              \
+       return show_master_fader(dev, attr, buf, nr);           \
+}
+
+#define store_fader(nr)                                                \
+static ssize_t store_master_fader##nr(struct device *dev,      \
+                            struct device_attribute *attr,     \
+                            const char *buf, size_t len)       \
+{                                                              \
+       return store_master_fader(dev, attr, buf, len, nr);     \
+}
+
+static ssize_t show_master_fader(struct device *dev,
+                                struct device_attribute *attr,
+                                char *buf, int nr)
+{
+       struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+       struct lp55xx_chip *chip = led->chip;
+       int ret;
+       u8 val;
+
+       mutex_lock(&chip->lock);
+       ret = lp55xx_read(chip, LP5523_REG_MASTER_FADER_BASE + nr - 1, &val);
+       mutex_unlock(&chip->lock);
+
+       if (ret == 0)
+               ret = sprintf(buf, "%u\n", val);
+
+       return ret;
+}
+show_fader(1)
+show_fader(2)
+show_fader(3)
+
+static ssize_t store_master_fader(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t len, int nr)
+{
+       struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+       struct lp55xx_chip *chip = led->chip;
+       int ret;
+       unsigned long val;
+
+       if (kstrtoul(buf, 0, &val))
+               return -EINVAL;
+
+       if (val > 0xff)
+               return -EINVAL;
+
+       mutex_lock(&chip->lock);
+       ret = lp55xx_write(chip, LP5523_REG_MASTER_FADER_BASE + nr - 1,
+                          (u8)val);
+       mutex_unlock(&chip->lock);
+
+       if (ret == 0)
+               ret = len;
+
+       return ret;
+}
+store_fader(1)
+store_fader(2)
+store_fader(3)
+
+static ssize_t show_master_fader_leds(struct device *dev,
+                                     struct device_attribute *attr,
+                                     char *buf)
+{
+       struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+       struct lp55xx_chip *chip = led->chip;
+       int i, ret, pos = 0;
+       u8 val;
+
+       mutex_lock(&chip->lock);
+
+       for (i = 0; i < LP5523_MAX_LEDS; i++) {
+               ret = lp55xx_read(chip, LP5523_REG_LED_CTRL_BASE + i, &val);
+               if (ret)
+                       goto leave;
+
+               val = (val & LP5523_FADER_MAPPING_MASK)
+                       >> LP5523_FADER_MAPPING_SHIFT;
+               if (val > 3) {
+                       ret = -EINVAL;
+                       goto leave;
+               }
+               buf[pos++] = val + '0';
+       }
+       buf[pos++] = '\n';
+       ret = pos;
+leave:
+       mutex_unlock(&chip->lock);
+       return ret;
+}
+
+static ssize_t store_master_fader_leds(struct device *dev,
+                                      struct device_attribute *attr,
+                                      const char *buf, size_t len)
+{
+       struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+       struct lp55xx_chip *chip = led->chip;
+       int i, n, ret;
+       u8 val;
+
+       n = min_t(int, len, LP5523_MAX_LEDS);
+
+       mutex_lock(&chip->lock);
+
+       for (i = 0; i < n; i++) {
+               if (buf[i] >= '0' && buf[i] <= '3') {
+                       val = (buf[i] - '0') << LP5523_FADER_MAPPING_SHIFT;
+                       ret = lp55xx_update_bits(chip,
+                                                LP5523_REG_LED_CTRL_BASE + i,
+                                                LP5523_FADER_MAPPING_MASK,
+                                                val);
+                       if (ret)
+                               goto leave;
+               } else {
+                       ret = -EINVAL;
+                       goto leave;
+               }
+       }
+       ret = len;
+leave:
+       mutex_unlock(&chip->lock);
+       return ret;
+}
+
 static void lp5523_led_brightness_work(struct work_struct *work)
 {
        struct lp55xx_led *led = container_of(work, struct lp55xx_led,
@@ -688,6 +824,14 @@ static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load);
 static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load);
 static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load);
 static LP55XX_DEV_ATTR_RO(selftest, lp5523_selftest);
+static LP55XX_DEV_ATTR_RW(master_fader1, show_master_fader1,
+                         store_master_fader1);
+static LP55XX_DEV_ATTR_RW(master_fader2, show_master_fader2,
+                         store_master_fader2);
+static LP55XX_DEV_ATTR_RW(master_fader3, show_master_fader3,
+                         store_master_fader3);
+static LP55XX_DEV_ATTR_RW(master_fader_leds, show_master_fader_leds,
+                         store_master_fader_leds);
 
 static struct attribute *lp5523_attributes[] = {
        &dev_attr_engine1_mode.attr,
@@ -700,6 +844,10 @@ static struct attribute *lp5523_attributes[] = {
        &dev_attr_engine2_leds.attr,
        &dev_attr_engine3_leds.attr,
        &dev_attr_selftest.attr,
+       &dev_attr_master_fader1.attr,
+       &dev_attr_master_fader2.attr,
+       &dev_attr_master_fader3.attr,
+       &dev_attr_master_fader_leds.attr,
        NULL,
 };
 
index 77c26bc32eed561a26c4ccb45b3238e60184d017..96d51e9879c905d2c4eb6bac2325af174665a9f1 100644 (file)
@@ -223,7 +223,7 @@ static int lp55xx_request_firmware(struct lp55xx_chip *chip)
        const char *name = chip->cl->name;
        struct device *dev = &chip->cl->dev;
 
-       return request_firmware_nowait(THIS_MODULE, true, name, dev,
+       return request_firmware_nowait(THIS_MODULE, false, name, dev,
                                GFP_KERNEL, chip, lp55xx_firmware_loaded);
 }
 
diff --git a/drivers/leds/leds-max77693.c b/drivers/leds/leds-max77693.c
new file mode 100644 (file)
index 0000000..b8b0eec
--- /dev/null
@@ -0,0 +1,1097 @@
+/*
+ * LED Flash class driver for the flash cell of max77693 mfd.
+ *
+ *     Copyright (C) 2015, Samsung Electronics Co., Ltd.
+ *
+ *     Authors: Jacek Anaszewski <j.anaszewski@samsung.com>
+ *              Andrzej Hajda <a.hajda@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ */
+
+#include <linux/led-class-flash.h>
+#include <linux/mfd/max77693.h>
+#include <linux/mfd/max77693-private.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define MODE_OFF               0
+#define MODE_FLASH(a)          (1 << (a))
+#define MODE_TORCH(a)          (1 << (2 + (a)))
+#define MODE_FLASH_EXTERNAL(a) (1 << (4 + (a)))
+
+#define MODE_FLASH_MASK                (MODE_FLASH(FLED1) | MODE_FLASH(FLED2) | \
+                                MODE_FLASH_EXTERNAL(FLED1) | \
+                                MODE_FLASH_EXTERNAL(FLED2))
+#define MODE_TORCH_MASK                (MODE_TORCH(FLED1) | MODE_TORCH(FLED2))
+
+#define FLED1_IOUT             (1 << 0)
+#define FLED2_IOUT             (1 << 1)
+
+enum max77693_fled {
+       FLED1,
+       FLED2,
+};
+
+enum max77693_led_mode {
+       FLASH,
+       TORCH,
+};
+
+struct max77693_led_config_data {
+       const char *label[2];
+       u32 iout_torch_max[2];
+       u32 iout_flash_max[2];
+       u32 flash_timeout_max[2];
+       u32 num_leds;
+       u32 boost_mode;
+       u32 boost_vout;
+       u32 low_vsys;
+};
+
+struct max77693_sub_led {
+       /* corresponding FLED output identifier */
+       int fled_id;
+       /* corresponding LED Flash class device */
+       struct led_classdev_flash fled_cdev;
+       /* assures led-triggers compatibility */
+       struct work_struct work_brightness_set;
+       /* V4L2 Flash device */
+       struct v4l2_flash *v4l2_flash;
+
+       /* brightness cache */
+       unsigned int torch_brightness;
+       /* flash timeout cache */
+       unsigned int flash_timeout;
+       /* flash faults that may have occurred */
+       u32 flash_faults;
+};
+
+struct max77693_led_device {
+       /* parent mfd regmap */
+       struct regmap *regmap;
+       /* platform device data */
+       struct platform_device *pdev;
+       /* secures access to the device */
+       struct mutex lock;
+
+       /* sub led data */
+       struct max77693_sub_led sub_leds[2];
+
+       /* maximum torch current values for FLED outputs */
+       u32 iout_torch_max[2];
+       /* maximum flash current values for FLED outputs */
+       u32 iout_flash_max[2];
+
+       /* current flash timeout cache */
+       unsigned int current_flash_timeout;
+       /* ITORCH register cache */
+       u8 torch_iout_reg;
+       /* mode of fled outputs */
+       unsigned int mode_flags;
+       /* recently strobed fled */
+       int strobing_sub_led_id;
+       /* bitmask of FLED outputs use state (bit 0. - FLED1, bit 1. - FLED2) */
+       u8 fled_mask;
+       /* FLED modes that can be set */
+       u8 allowed_modes;
+
+       /* arrangement of current outputs */
+       bool iout_joint;
+};
+
+static u8 max77693_led_iout_to_reg(u32 ua)
+{
+       if (ua < FLASH_IOUT_MIN)
+               ua = FLASH_IOUT_MIN;
+       return (ua - FLASH_IOUT_MIN) / FLASH_IOUT_STEP;
+}
+
+static u8 max77693_flash_timeout_to_reg(u32 us)
+{
+       return (us - FLASH_TIMEOUT_MIN) / FLASH_TIMEOUT_STEP;
+}
+
+static inline struct max77693_sub_led *flcdev_to_sub_led(
+                                       struct led_classdev_flash *fled_cdev)
+{
+       return container_of(fled_cdev, struct max77693_sub_led, fled_cdev);
+}
+
+static inline struct max77693_led_device *sub_led_to_led(
+                                       struct max77693_sub_led *sub_led)
+{
+       return container_of(sub_led, struct max77693_led_device,
+                               sub_leds[sub_led->fled_id]);
+}
+
+static inline u8 max77693_led_vsys_to_reg(u32 mv)
+{
+       return ((mv - MAX_FLASH1_VSYS_MIN) / MAX_FLASH1_VSYS_STEP) << 2;
+}
+
+static inline u8 max77693_led_vout_to_reg(u32 mv)
+{
+       return (mv - FLASH_VOUT_MIN) / FLASH_VOUT_STEP + FLASH_VOUT_RMIN;
+}
+
+static inline bool max77693_fled_used(struct max77693_led_device *led,
+                                        int fled_id)
+{
+       u8 fled_bit = (fled_id == FLED1) ? FLED1_IOUT : FLED2_IOUT;
+
+       return led->fled_mask & fled_bit;
+}
+
+static int max77693_set_mode_reg(struct max77693_led_device *led, u8 mode)
+{
+       struct regmap *rmap = led->regmap;
+       int ret, v = 0, i;
+
+       for (i = FLED1; i <= FLED2; ++i) {
+               if (mode & MODE_TORCH(i))
+                       v |= FLASH_EN_ON << TORCH_EN_SHIFT(i);
+
+               if (mode & MODE_FLASH(i)) {
+                       v |= FLASH_EN_ON << FLASH_EN_SHIFT(i);
+               } else if (mode & MODE_FLASH_EXTERNAL(i)) {
+                       v |= FLASH_EN_FLASH << FLASH_EN_SHIFT(i);
+                       /*
+                        * Enable hw triggering also for torch mode, as some
+                        * camera sensors use torch led to fathom ambient light
+                        * conditions before strobing the flash.
+                        */
+                       v |= FLASH_EN_TORCH << TORCH_EN_SHIFT(i);
+               }
+       }
+
+       /* Reset the register only prior setting flash modes */
+       if (mode & ~(MODE_TORCH(FLED1) | MODE_TORCH(FLED2))) {
+               ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_EN, 0);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return regmap_write(rmap, MAX77693_LED_REG_FLASH_EN, v);
+}
+
+static int max77693_add_mode(struct max77693_led_device *led, u8 mode)
+{
+       u8 new_mode_flags;
+       int i, ret;
+
+       if (led->iout_joint)
+               /* Span the mode on FLED2 for joint iouts case */
+               mode |= (mode << 1);
+
+       /*
+        * FLASH_EXTERNAL mode activates FLASHEN and TORCHEN pins in the device.
+        * Corresponding register bit fields interfere with SW triggered modes,
+        * thus clear them to ensure proper device configuration.
+        */
+       for (i = FLED1; i <= FLED2; ++i)
+               if (mode & MODE_FLASH_EXTERNAL(i))
+                       led->mode_flags &= (~MODE_TORCH(i) & ~MODE_FLASH(i));
+
+       new_mode_flags = mode | led->mode_flags;
+       new_mode_flags &= led->allowed_modes;
+
+       if (new_mode_flags ^ led->mode_flags)
+               led->mode_flags = new_mode_flags;
+       else
+               return 0;
+
+       ret = max77693_set_mode_reg(led, led->mode_flags);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * Clear flash mode flag after setting the mode to avoid spurious flash
+        * strobing on each subsequent torch mode setting.
+        */
+       if (mode & MODE_FLASH_MASK)
+               led->mode_flags &= ~mode;
+
+       return ret;
+}
+
+static int max77693_clear_mode(struct max77693_led_device *led,
+                               u8 mode)
+{
+       if (led->iout_joint)
+               /* Clear mode also on FLED2 for joint iouts case */
+               mode |= (mode << 1);
+
+       led->mode_flags &= ~mode;
+
+       return max77693_set_mode_reg(led, led->mode_flags);
+}
+
+static void max77693_add_allowed_modes(struct max77693_led_device *led,
+                               int fled_id, enum max77693_led_mode mode)
+{
+       if (mode == FLASH)
+               led->allowed_modes |= (MODE_FLASH(fled_id) |
+                                      MODE_FLASH_EXTERNAL(fled_id));
+       else
+               led->allowed_modes |= MODE_TORCH(fled_id);
+}
+
+static void max77693_distribute_currents(struct max77693_led_device *led,
+                               int fled_id, enum max77693_led_mode mode,
+                               u32 micro_amp, u32 iout_max[2], u32 iout[2])
+{
+       if (!led->iout_joint) {
+               iout[fled_id] = micro_amp;
+               max77693_add_allowed_modes(led, fled_id, mode);
+               return;
+       }
+
+       iout[FLED1] = min(micro_amp, iout_max[FLED1]);
+       iout[FLED2] = micro_amp - iout[FLED1];
+
+       if (mode == FLASH)
+               led->allowed_modes &= ~MODE_FLASH_MASK;
+       else
+               led->allowed_modes &= ~MODE_TORCH_MASK;
+
+       max77693_add_allowed_modes(led, FLED1, mode);
+
+       if (iout[FLED2])
+               max77693_add_allowed_modes(led, FLED2, mode);
+}
+
+static int max77693_set_torch_current(struct max77693_led_device *led,
+                               int fled_id, u32 micro_amp)
+{
+       struct regmap *rmap = led->regmap;
+       u8 iout1_reg = 0, iout2_reg = 0;
+       u32 iout[2];
+
+       max77693_distribute_currents(led, fled_id, TORCH, micro_amp,
+                                       led->iout_torch_max, iout);
+
+       if (fled_id == FLED1 || led->iout_joint) {
+               iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
+               led->torch_iout_reg &= TORCH_IOUT_MASK(TORCH_IOUT2_SHIFT);
+       }
+       if (fled_id == FLED2 || led->iout_joint) {
+               iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
+               led->torch_iout_reg &= TORCH_IOUT_MASK(TORCH_IOUT1_SHIFT);
+       }
+
+       led->torch_iout_reg |= ((iout1_reg << TORCH_IOUT1_SHIFT) |
+                               (iout2_reg << TORCH_IOUT2_SHIFT));
+
+       return regmap_write(rmap, MAX77693_LED_REG_ITORCH,
+                                               led->torch_iout_reg);
+}
+
+static int max77693_set_flash_current(struct max77693_led_device *led,
+                                       int fled_id,
+                                       u32 micro_amp)
+{
+       struct regmap *rmap = led->regmap;
+       u8 iout1_reg, iout2_reg;
+       u32 iout[2];
+       int ret = -EINVAL;
+
+       max77693_distribute_currents(led, fled_id, FLASH, micro_amp,
+                                       led->iout_flash_max, iout);
+
+       if (fled_id == FLED1 || led->iout_joint) {
+               iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
+               ret = regmap_write(rmap, MAX77693_LED_REG_IFLASH1,
+                                                       iout1_reg);
+               if (ret < 0)
+                       return ret;
+       }
+       if (fled_id == FLED2 || led->iout_joint) {
+               iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
+               ret = regmap_write(rmap, MAX77693_LED_REG_IFLASH2,
+                                                       iout2_reg);
+       }
+
+       return ret;
+}
+
+static int max77693_set_timeout(struct max77693_led_device *led, u32 microsec)
+{
+       struct regmap *rmap = led->regmap;
+       u8 v;
+       int ret;
+
+       v = max77693_flash_timeout_to_reg(microsec) | FLASH_TMR_LEVEL;
+
+       ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
+       if (ret < 0)
+               return ret;
+
+       led->current_flash_timeout = microsec;
+
+       return 0;
+}
+
+static int max77693_get_strobe_status(struct max77693_led_device *led,
+                                       bool *state)
+{
+       struct regmap *rmap = led->regmap;
+       unsigned int v;
+       int ret;
+
+       ret = regmap_read(rmap, MAX77693_LED_REG_FLASH_STATUS, &v);
+       if (ret < 0)
+               return ret;
+
+       *state = v & FLASH_STATUS_FLASH_ON;
+
+       return ret;
+}
+
+static int max77693_get_flash_faults(struct max77693_sub_led *sub_led)
+{
+       struct max77693_led_device *led = sub_led_to_led(sub_led);
+       struct regmap *rmap = led->regmap;
+       unsigned int v;
+       u8 fault_open_mask, fault_short_mask;
+       int ret;
+
+       sub_led->flash_faults = 0;
+
+       if (led->iout_joint) {
+               fault_open_mask = FLASH_INT_FLED1_OPEN | FLASH_INT_FLED2_OPEN;
+               fault_short_mask = FLASH_INT_FLED1_SHORT |
+                                                       FLASH_INT_FLED2_SHORT;
+       } else {
+               fault_open_mask = (sub_led->fled_id == FLED1) ?
+                                               FLASH_INT_FLED1_OPEN :
+                                               FLASH_INT_FLED2_OPEN;
+               fault_short_mask = (sub_led->fled_id == FLED1) ?
+                                               FLASH_INT_FLED1_SHORT :
+                                               FLASH_INT_FLED2_SHORT;
+       }
+
+       ret = regmap_read(rmap, MAX77693_LED_REG_FLASH_INT, &v);
+       if (ret < 0)
+               return ret;
+
+       if (v & fault_open_mask)
+               sub_led->flash_faults |= LED_FAULT_OVER_VOLTAGE;
+       if (v & fault_short_mask)
+               sub_led->flash_faults |= LED_FAULT_SHORT_CIRCUIT;
+       if (v & FLASH_INT_OVER_CURRENT)
+               sub_led->flash_faults |= LED_FAULT_OVER_CURRENT;
+
+       return 0;
+}
+
+static int max77693_setup(struct max77693_led_device *led,
+                        struct max77693_led_config_data *led_cfg)
+{
+       struct regmap *rmap = led->regmap;
+       int i, first_led, last_led, ret;
+       u32 max_flash_curr[2];
+       u8 v;
+
+       /*
+        * Initialize only flash current. Torch current doesn't
+        * require initialization as ITORCH register is written with
+        * new value each time brightness_set op is called.
+        */
+       if (led->iout_joint) {
+               first_led = FLED1;
+               last_led = FLED1;
+               max_flash_curr[FLED1] = led_cfg->iout_flash_max[FLED1] +
+                                       led_cfg->iout_flash_max[FLED2];
+       } else {
+               first_led = max77693_fled_used(led, FLED1) ? FLED1 : FLED2;
+               last_led = max77693_fled_used(led, FLED2) ? FLED2 : FLED1;
+               max_flash_curr[FLED1] = led_cfg->iout_flash_max[FLED1];
+               max_flash_curr[FLED2] = led_cfg->iout_flash_max[FLED2];
+       }
+
+       for (i = first_led; i <= last_led; ++i) {
+               ret = max77693_set_flash_current(led, i,
+                                       max_flash_curr[i]);
+               if (ret < 0)
+                       return ret;
+       }
+
+       v = TORCH_TMR_NO_TIMER | MAX77693_LED_TRIG_TYPE_LEVEL;
+       ret = regmap_write(rmap, MAX77693_LED_REG_ITORCHTIMER, v);
+       if (ret < 0)
+               return ret;
+
+       if (led_cfg->low_vsys > 0)
+               v = max77693_led_vsys_to_reg(led_cfg->low_vsys) |
+                                               MAX_FLASH1_MAX_FL_EN;
+       else
+               v = 0;
+
+       ret = regmap_write(rmap, MAX77693_LED_REG_MAX_FLASH1, v);
+       if (ret < 0)
+               return ret;
+       ret = regmap_write(rmap, MAX77693_LED_REG_MAX_FLASH2, 0);
+       if (ret < 0)
+               return ret;
+
+       if (led_cfg->boost_mode == MAX77693_LED_BOOST_FIXED)
+               v = FLASH_BOOST_FIXED;
+       else
+               v = led_cfg->boost_mode | led_cfg->boost_mode << 1;
+
+       if (max77693_fled_used(led, FLED1) && max77693_fled_used(led, FLED2))
+               v |= FLASH_BOOST_LEDNUM_2;
+
+       ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_CNTL, v);
+       if (ret < 0)
+               return ret;
+
+       v = max77693_led_vout_to_reg(led_cfg->boost_vout);
+       ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
+       if (ret < 0)
+               return ret;
+
+       return max77693_set_mode_reg(led, MODE_OFF);
+}
+
+static int __max77693_led_brightness_set(struct max77693_led_device *led,
+                                       int fled_id, enum led_brightness value)
+{
+       int ret;
+
+       mutex_lock(&led->lock);
+
+       if (value == 0) {
+               ret = max77693_clear_mode(led, MODE_TORCH(fled_id));
+               if (ret < 0)
+                       dev_dbg(&led->pdev->dev,
+                               "Failed to clear torch mode (%d)\n",
+                               ret);
+               goto unlock;
+       }
+
+       ret = max77693_set_torch_current(led, fled_id, value * TORCH_IOUT_STEP);
+       if (ret < 0) {
+               dev_dbg(&led->pdev->dev,
+                       "Failed to set torch current (%d)\n",
+                       ret);
+               goto unlock;
+       }
+
+       ret = max77693_add_mode(led, MODE_TORCH(fled_id));
+       if (ret < 0)
+               dev_dbg(&led->pdev->dev,
+                       "Failed to set torch mode (%d)\n",
+                       ret);
+unlock:
+       mutex_unlock(&led->lock);
+       return ret;
+}
+
+static void max77693_led_brightness_set_work(
+                                       struct work_struct *work)
+{
+       struct max77693_sub_led *sub_led =
+                       container_of(work, struct max77693_sub_led,
+                                       work_brightness_set);
+       struct max77693_led_device *led = sub_led_to_led(sub_led);
+
+       __max77693_led_brightness_set(led, sub_led->fled_id,
+                               sub_led->torch_brightness);
+}
+
+/* LED subsystem callbacks */
+
+static int max77693_led_brightness_set_sync(
+                               struct led_classdev *led_cdev,
+                               enum led_brightness value)
+{
+       struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+       struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+       struct max77693_led_device *led = sub_led_to_led(sub_led);
+
+       return __max77693_led_brightness_set(led, sub_led->fled_id, value);
+}
+
+static void max77693_led_brightness_set(
+                               struct led_classdev *led_cdev,
+                               enum led_brightness value)
+{
+       struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+       struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+
+       sub_led->torch_brightness = value;
+       schedule_work(&sub_led->work_brightness_set);
+}
+
+static int max77693_led_flash_brightness_set(
+                               struct led_classdev_flash *fled_cdev,
+                               u32 brightness)
+{
+       struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+       struct max77693_led_device *led = sub_led_to_led(sub_led);
+       int ret;
+
+       mutex_lock(&led->lock);
+       ret = max77693_set_flash_current(led, sub_led->fled_id, brightness);
+       mutex_unlock(&led->lock);
+
+       return ret;
+}
+
+static int max77693_led_flash_strobe_set(
+                               struct led_classdev_flash *fled_cdev,
+                               bool state)
+{
+       struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+       struct max77693_led_device *led = sub_led_to_led(sub_led);
+       int fled_id = sub_led->fled_id;
+       int ret;
+
+       mutex_lock(&led->lock);
+
+       if (!state) {
+               ret = max77693_clear_mode(led, MODE_FLASH(fled_id));
+               goto unlock;
+       }
+
+       if (sub_led->flash_timeout != led->current_flash_timeout) {
+               ret = max77693_set_timeout(led, sub_led->flash_timeout);
+               if (ret < 0)
+                       goto unlock;
+       }
+
+       led->strobing_sub_led_id = fled_id;
+
+       ret = max77693_add_mode(led, MODE_FLASH(fled_id));
+       if (ret < 0)
+               goto unlock;
+
+       ret = max77693_get_flash_faults(sub_led);
+
+unlock:
+       mutex_unlock(&led->lock);
+       return ret;
+}
+
+static int max77693_led_flash_fault_get(
+                               struct led_classdev_flash *fled_cdev,
+                               u32 *fault)
+{
+       struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+
+       *fault = sub_led->flash_faults;
+
+       return 0;
+}
+
+static int max77693_led_flash_strobe_get(
+                               struct led_classdev_flash *fled_cdev,
+                               bool *state)
+{
+       struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+       struct max77693_led_device *led = sub_led_to_led(sub_led);
+       int ret;
+
+       if (!state)
+               return -EINVAL;
+
+       mutex_lock(&led->lock);
+
+       ret = max77693_get_strobe_status(led, state);
+
+       *state = !!(*state && (led->strobing_sub_led_id == sub_led->fled_id));
+
+       mutex_unlock(&led->lock);
+
+       return ret;
+}
+
+static int max77693_led_flash_timeout_set(
+                               struct led_classdev_flash *fled_cdev,
+                               u32 timeout)
+{
+       struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+       struct max77693_led_device *led = sub_led_to_led(sub_led);
+
+       mutex_lock(&led->lock);
+       sub_led->flash_timeout = timeout;
+       mutex_unlock(&led->lock);
+
+       return 0;
+}
+
+static int max77693_led_parse_dt(struct max77693_led_device *led,
+                               struct max77693_led_config_data *cfg,
+                               struct device_node **sub_nodes)
+{
+       struct device *dev = &led->pdev->dev;
+       struct max77693_sub_led *sub_leds = led->sub_leds;
+       struct device_node *node = dev->of_node, *child_node;
+       struct property *prop;
+       u32 led_sources[2];
+       int i, ret, fled_id;
+
+       of_property_read_u32(node, "maxim,boost-mode", &cfg->boost_mode);
+       of_property_read_u32(node, "maxim,boost-mvout", &cfg->boost_vout);
+       of_property_read_u32(node, "maxim,mvsys-min", &cfg->low_vsys);
+
+       for_each_available_child_of_node(node, child_node) {
+               prop = of_find_property(child_node, "led-sources", NULL);
+               if (prop) {
+                       const __be32 *srcs = NULL;
+
+                       for (i = 0; i < ARRAY_SIZE(led_sources); ++i) {
+                               srcs = of_prop_next_u32(prop, srcs,
+                                                       &led_sources[i]);
+                               if (!srcs)
+                                       break;
+                       }
+               } else {
+                       dev_err(dev,
+                               "led-sources DT property missing\n");
+                       of_node_put(child_node);
+                       return -EINVAL;
+               }
+
+               if (i == 2) {
+                       fled_id = FLED1;
+                       led->fled_mask = FLED1_IOUT | FLED2_IOUT;
+               } else if (led_sources[0] == FLED1) {
+                       fled_id = FLED1;
+                       led->fled_mask |= FLED1_IOUT;
+               } else if (led_sources[0] == FLED2) {
+                       fled_id = FLED2;
+                       led->fled_mask |= FLED2_IOUT;
+               } else {
+                       dev_err(dev,
+                               "Wrong led-sources DT property value.\n");
+                       of_node_put(child_node);
+                       return -EINVAL;
+               }
+
+               if (sub_nodes[fled_id]) {
+                       dev_err(dev,
+                               "Conflicting \"led-sources\" DT properties\n");
+                       return -EINVAL;
+               }
+
+               sub_nodes[fled_id] = child_node;
+               sub_leds[fled_id].fled_id = fled_id;
+
+               cfg->label[fled_id] =
+                       of_get_property(child_node, "label", NULL) ? :
+                                               child_node->name;
+
+               ret = of_property_read_u32(child_node, "led-max-microamp",
+                                       &cfg->iout_torch_max[fled_id]);
+               if (ret < 0) {
+                       cfg->iout_torch_max[fled_id] = TORCH_IOUT_MIN;
+                       dev_warn(dev, "led-max-microamp DT property missing\n");
+               }
+
+               ret = of_property_read_u32(child_node, "flash-max-microamp",
+                                       &cfg->iout_flash_max[fled_id]);
+               if (ret < 0) {
+                       cfg->iout_flash_max[fled_id] = FLASH_IOUT_MIN;
+                       dev_warn(dev,
+                                "flash-max-microamp DT property missing\n");
+               }
+
+               ret = of_property_read_u32(child_node, "flash-max-timeout-us",
+                                       &cfg->flash_timeout_max[fled_id]);
+               if (ret < 0) {
+                       cfg->flash_timeout_max[fled_id] = FLASH_TIMEOUT_MIN;
+                       dev_warn(dev,
+                                "flash-max-timeout-us DT property missing\n");
+               }
+
+               if (++cfg->num_leds == 2 ||
+                   (max77693_fled_used(led, FLED1) &&
+                    max77693_fled_used(led, FLED2))) {
+                       of_node_put(child_node);
+                       break;
+               }
+       }
+
+       if (cfg->num_leds == 0) {
+               dev_err(dev, "No DT child node found for connected LED(s).\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void clamp_align(u32 *v, u32 min, u32 max, u32 step)
+{
+       *v = clamp_val(*v, min, max);
+       if (step > 1)
+               *v = (*v - min) / step * step + min;
+}
+
+static void max77693_align_iout_current(struct max77693_led_device *led,
+                                       u32 *iout, u32 min, u32 max, u32 step)
+{
+       int i;
+
+       if (led->iout_joint) {
+               if (iout[FLED1] > min) {
+                       iout[FLED1] /= 2;
+                       iout[FLED2] = iout[FLED1];
+               } else {
+                       iout[FLED1] = min;
+                       iout[FLED2] = 0;
+                       return;
+               }
+       }
+
+       for (i = FLED1; i <= FLED2; ++i)
+               if (max77693_fled_used(led, i))
+                       clamp_align(&iout[i], min, max, step);
+               else
+                       iout[i] = 0;
+}
+
+static void max77693_led_validate_configuration(struct max77693_led_device *led,
+                                       struct max77693_led_config_data *cfg)
+{
+       u32 flash_iout_max = cfg->boost_mode ? FLASH_IOUT_MAX_2LEDS :
+                                              FLASH_IOUT_MAX_1LED;
+       int i;
+
+       if (cfg->num_leds == 1 &&
+           max77693_fled_used(led, FLED1) && max77693_fled_used(led, FLED2))
+               led->iout_joint = true;
+
+       cfg->boost_mode = clamp_val(cfg->boost_mode, MAX77693_LED_BOOST_NONE,
+                           MAX77693_LED_BOOST_FIXED);
+
+       /* Boost must be enabled if both current outputs are used */
+       if ((cfg->boost_mode == MAX77693_LED_BOOST_NONE) && led->iout_joint)
+               cfg->boost_mode = MAX77693_LED_BOOST_FIXED;
+
+       max77693_align_iout_current(led, cfg->iout_torch_max,
+                       TORCH_IOUT_MIN, TORCH_IOUT_MAX, TORCH_IOUT_STEP);
+
+       max77693_align_iout_current(led, cfg->iout_flash_max,
+                       FLASH_IOUT_MIN, flash_iout_max, FLASH_IOUT_STEP);
+
+       for (i = 0; i < ARRAY_SIZE(cfg->flash_timeout_max); ++i)
+               clamp_align(&cfg->flash_timeout_max[i], FLASH_TIMEOUT_MIN,
+                               FLASH_TIMEOUT_MAX, FLASH_TIMEOUT_STEP);
+
+       clamp_align(&cfg->boost_vout, FLASH_VOUT_MIN, FLASH_VOUT_MAX,
+                                                       FLASH_VOUT_STEP);
+
+       if (cfg->low_vsys)
+               clamp_align(&cfg->low_vsys, MAX_FLASH1_VSYS_MIN,
+                               MAX_FLASH1_VSYS_MAX, MAX_FLASH1_VSYS_STEP);
+}
+
+static int max77693_led_get_configuration(struct max77693_led_device *led,
+                               struct max77693_led_config_data *cfg,
+                               struct device_node **sub_nodes)
+{
+       int ret;
+
+       ret = max77693_led_parse_dt(led, cfg, sub_nodes);
+       if (ret < 0)
+               return ret;
+
+       max77693_led_validate_configuration(led, cfg);
+
+       memcpy(led->iout_torch_max, cfg->iout_torch_max,
+                               sizeof(led->iout_torch_max));
+       memcpy(led->iout_flash_max, cfg->iout_flash_max,
+                               sizeof(led->iout_flash_max));
+
+       return 0;
+}
+
+static const struct led_flash_ops flash_ops = {
+       .flash_brightness_set   = max77693_led_flash_brightness_set,
+       .strobe_set             = max77693_led_flash_strobe_set,
+       .strobe_get             = max77693_led_flash_strobe_get,
+       .timeout_set            = max77693_led_flash_timeout_set,
+       .fault_get              = max77693_led_flash_fault_get,
+};
+
+static void max77693_init_flash_settings(struct max77693_sub_led *sub_led,
+                                struct max77693_led_config_data *led_cfg)
+{
+       struct led_classdev_flash *fled_cdev = &sub_led->fled_cdev;
+       struct max77693_led_device *led = sub_led_to_led(sub_led);
+       int fled_id = sub_led->fled_id;
+       struct led_flash_setting *setting;
+
+       /* Init flash intensity setting */
+       setting = &fled_cdev->brightness;
+       setting->min = FLASH_IOUT_MIN;
+       setting->max = led->iout_joint ?
+               led_cfg->iout_flash_max[FLED1] +
+               led_cfg->iout_flash_max[FLED2] :
+               led_cfg->iout_flash_max[fled_id];
+       setting->step = FLASH_IOUT_STEP;
+       setting->val = setting->max;
+
+       /* Init flash timeout setting */
+       setting = &fled_cdev->timeout;
+       setting->min = FLASH_TIMEOUT_MIN;
+       setting->max = led_cfg->flash_timeout_max[fled_id];
+       setting->step = FLASH_TIMEOUT_STEP;
+       setting->val = setting->max;
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+
+static int max77693_led_external_strobe_set(
+                               struct v4l2_flash *v4l2_flash,
+                               bool enable)
+{
+       struct max77693_sub_led *sub_led =
+                               flcdev_to_sub_led(v4l2_flash->fled_cdev);
+       struct max77693_led_device *led = sub_led_to_led(sub_led);
+       int fled_id = sub_led->fled_id;
+       int ret;
+
+       mutex_lock(&led->lock);
+
+       if (enable)
+               ret = max77693_add_mode(led, MODE_FLASH_EXTERNAL(fled_id));
+       else
+               ret = max77693_clear_mode(led, MODE_FLASH_EXTERNAL(fled_id));
+
+       mutex_unlock(&led->lock);
+
+       return ret;
+}
+
+static void max77693_init_v4l2_flash_config(struct max77693_sub_led *sub_led,
+                               struct max77693_led_config_data *led_cfg,
+                               struct v4l2_flash_config *v4l2_sd_cfg)
+{
+       struct max77693_led_device *led = sub_led_to_led(sub_led);
+       struct device *dev = &led->pdev->dev;
+       struct max77693_dev *iodev = dev_get_drvdata(dev->parent);
+       struct i2c_client *i2c = iodev->i2c;
+       struct led_flash_setting *s;
+
+       snprintf(v4l2_sd_cfg->dev_name, sizeof(v4l2_sd_cfg->dev_name),
+                "%s %d-%04x", sub_led->fled_cdev.led_cdev.name,
+                i2c_adapter_id(i2c->adapter), i2c->addr);
+
+       s = &v4l2_sd_cfg->torch_intensity;
+       s->min = TORCH_IOUT_MIN;
+       s->max = sub_led->fled_cdev.led_cdev.max_brightness * TORCH_IOUT_STEP;
+       s->step = TORCH_IOUT_STEP;
+       s->val = s->max;
+
+       /* Init flash faults config */
+       v4l2_sd_cfg->flash_faults = LED_FAULT_OVER_VOLTAGE |
+                               LED_FAULT_SHORT_CIRCUIT |
+                               LED_FAULT_OVER_CURRENT;
+
+       v4l2_sd_cfg->has_external_strobe = true;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+       .external_strobe_set = max77693_led_external_strobe_set,
+};
+#else
+static inline void max77693_init_v4l2_flash_config(
+                               struct max77693_sub_led *sub_led,
+                               struct max77693_led_config_data *led_cfg,
+                               struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+static const struct v4l2_flash_ops v4l2_flash_ops;
+#endif
+
+static void max77693_init_fled_cdev(struct max77693_sub_led *sub_led,
+                               struct max77693_led_config_data *led_cfg)
+{
+       struct max77693_led_device *led = sub_led_to_led(sub_led);
+       int fled_id = sub_led->fled_id;
+       struct led_classdev_flash *fled_cdev;
+       struct led_classdev *led_cdev;
+
+       /* Initialize LED Flash class device */
+       fled_cdev = &sub_led->fled_cdev;
+       fled_cdev->ops = &flash_ops;
+       led_cdev = &fled_cdev->led_cdev;
+
+       led_cdev->name = led_cfg->label[fled_id];
+
+       led_cdev->brightness_set = max77693_led_brightness_set;
+       led_cdev->brightness_set_sync = max77693_led_brightness_set_sync;
+       led_cdev->max_brightness = (led->iout_joint ?
+                                       led_cfg->iout_torch_max[FLED1] +
+                                       led_cfg->iout_torch_max[FLED2] :
+                                       led_cfg->iout_torch_max[fled_id]) /
+                                  TORCH_IOUT_STEP;
+       led_cdev->flags |= LED_DEV_CAP_FLASH;
+       INIT_WORK(&sub_led->work_brightness_set,
+                       max77693_led_brightness_set_work);
+
+       max77693_init_flash_settings(sub_led, led_cfg);
+
+       /* Init flash timeout cache */
+       sub_led->flash_timeout = fled_cdev->timeout.val;
+}
+
+static int max77693_register_led(struct max77693_sub_led *sub_led,
+                                struct max77693_led_config_data *led_cfg,
+                                struct device_node *sub_node)
+{
+       struct max77693_led_device *led = sub_led_to_led(sub_led);
+       struct led_classdev_flash *fled_cdev = &sub_led->fled_cdev;
+       struct device *dev = &led->pdev->dev;
+       struct v4l2_flash_config v4l2_sd_cfg = {};
+       int ret;
+
+       /* Register in the LED subsystem */
+       ret = led_classdev_flash_register(dev, fled_cdev);
+       if (ret < 0)
+               return ret;
+
+       max77693_init_v4l2_flash_config(sub_led, led_cfg, &v4l2_sd_cfg);
+
+       /* Register in the V4L2 subsystem. */
+       sub_led->v4l2_flash = v4l2_flash_init(dev, sub_node, fled_cdev, NULL,
+                                             &v4l2_flash_ops, &v4l2_sd_cfg);
+       if (IS_ERR(sub_led->v4l2_flash)) {
+               ret = PTR_ERR(sub_led->v4l2_flash);
+               goto err_v4l2_flash_init;
+       }
+
+       return 0;
+
+err_v4l2_flash_init:
+       led_classdev_flash_unregister(fled_cdev);
+       return ret;
+}
+
+static int max77693_led_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct max77693_dev *iodev = dev_get_drvdata(dev->parent);
+       struct max77693_led_device *led;
+       struct max77693_sub_led *sub_leds;
+       struct device_node *sub_nodes[2] = {};
+       struct max77693_led_config_data led_cfg = {};
+       int init_fled_cdev[2], i, ret;
+
+       led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+       if (!led)
+               return -ENOMEM;
+
+       led->pdev = pdev;
+       led->regmap = iodev->regmap;
+       led->allowed_modes = MODE_FLASH_MASK;
+       sub_leds = led->sub_leds;
+
+       platform_set_drvdata(pdev, led);
+       ret = max77693_led_get_configuration(led, &led_cfg, sub_nodes);
+       if (ret < 0)
+               return ret;
+
+       ret = max77693_setup(led, &led_cfg);
+       if (ret < 0)
+               return ret;
+
+       mutex_init(&led->lock);
+
+       init_fled_cdev[FLED1] =
+                       led->iout_joint || max77693_fled_used(led, FLED1);
+       init_fled_cdev[FLED2] =
+                       !led->iout_joint && max77693_fled_used(led, FLED2);
+
+       for (i = FLED1; i <= FLED2; ++i) {
+               if (!init_fled_cdev[i])
+                       continue;
+
+               /* Initialize LED Flash class device */
+               max77693_init_fled_cdev(&sub_leds[i], &led_cfg);
+
+               /*
+                * Register LED Flash class device and corresponding
+                * V4L2 Flash device.
+                */
+               ret = max77693_register_led(&sub_leds[i], &led_cfg,
+                                               sub_nodes[i]);
+               if (ret < 0) {
+                       /*
+                        * At this moment FLED1 might have been already
+                        * registered and it needs to be released.
+                        */
+                       if (i == FLED2)
+                               goto err_register_led2;
+                       else
+                               goto err_register_led1;
+               }
+       }
+
+       return 0;
+
+err_register_led2:
+       /* It is possible than only FLED2 was to be registered */
+       if (!init_fled_cdev[FLED1])
+               goto err_register_led1;
+       v4l2_flash_release(sub_leds[FLED1].v4l2_flash);
+       led_classdev_flash_unregister(&sub_leds[FLED1].fled_cdev);
+err_register_led1:
+       mutex_destroy(&led->lock);
+
+       return ret;
+}
+
+static int max77693_led_remove(struct platform_device *pdev)
+{
+       struct max77693_led_device *led = platform_get_drvdata(pdev);
+       struct max77693_sub_led *sub_leds = led->sub_leds;
+
+       if (led->iout_joint || max77693_fled_used(led, FLED1)) {
+               v4l2_flash_release(sub_leds[FLED1].v4l2_flash);
+               led_classdev_flash_unregister(&sub_leds[FLED1].fled_cdev);
+               cancel_work_sync(&sub_leds[FLED1].work_brightness_set);
+       }
+
+       if (!led->iout_joint && max77693_fled_used(led, FLED2)) {
+               v4l2_flash_release(sub_leds[FLED2].v4l2_flash);
+               led_classdev_flash_unregister(&sub_leds[FLED2].fled_cdev);
+               cancel_work_sync(&sub_leds[FLED2].work_brightness_set);
+       }
+
+       mutex_destroy(&led->lock);
+
+       return 0;
+}
+
+static const struct of_device_id max77693_led_dt_match[] = {
+       { .compatible = "maxim,max77693-led" },
+       {},
+};
+
+static struct platform_driver max77693_led_driver = {
+       .probe          = max77693_led_probe,
+       .remove         = max77693_led_remove,
+       .driver         = {
+               .name   = "max77693-led",
+               .of_match_table = max77693_led_dt_match,
+       },
+};
+
+module_platform_driver(max77693_led_driver);
+
+MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
+MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
+MODULE_DESCRIPTION("Maxim MAX77693 led flash driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-tlc591xx.c b/drivers/leds/leds-tlc591xx.c
new file mode 100644 (file)
index 0000000..de16c29
--- /dev/null
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2014 Belkin Inc.
+ * Copyright 2015 Andrew Lunn <andrew@lunn.ch>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ */
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define TLC591XX_MAX_LEDS      16
+
+#define TLC591XX_REG_MODE1     0x00
+#define MODE1_RESPON_ADDR_MASK 0xF0
+#define MODE1_NORMAL_MODE      (0 << 4)
+#define MODE1_SPEED_MODE       (1 << 4)
+
+#define TLC591XX_REG_MODE2     0x01
+#define MODE2_DIM              (0 << 5)
+#define MODE2_BLINK            (1 << 5)
+#define MODE2_OCH_STOP         (0 << 3)
+#define MODE2_OCH_ACK          (1 << 3)
+
+#define TLC591XX_REG_PWM(x)    (0x02 + (x))
+
+#define TLC591XX_REG_GRPPWM    0x12
+#define TLC591XX_REG_GRPFREQ   0x13
+
+/* LED Driver Output State, determine the source that drives LED outputs */
+#define LEDOUT_OFF             0x0     /* Output LOW */
+#define LEDOUT_ON              0x1     /* Output HI-Z */
+#define LEDOUT_DIM             0x2     /* Dimming */
+#define LEDOUT_BLINK           0x3     /* Blinking */
+#define LEDOUT_MASK            0x3
+
+#define ldev_to_led(c)         container_of(c, struct tlc591xx_led, ldev)
+#define work_to_led(work)      container_of(work, struct tlc591xx_led, work)
+
+struct tlc591xx_led {
+       bool active;
+       unsigned int led_no;
+       struct led_classdev ldev;
+       struct work_struct work;
+       struct tlc591xx_priv *priv;
+};
+
+struct tlc591xx_priv {
+       struct tlc591xx_led leds[TLC591XX_MAX_LEDS];
+       struct regmap *regmap;
+       unsigned int reg_ledout_offset;
+};
+
+struct tlc591xx {
+       unsigned int max_leds;
+       unsigned int reg_ledout_offset;
+};
+
+static const struct tlc591xx tlc59116 = {
+       .max_leds = 16,
+       .reg_ledout_offset = 0x14,
+};
+
+static const struct tlc591xx tlc59108 = {
+       .max_leds = 8,
+       .reg_ledout_offset = 0x0c,
+};
+
+static int
+tlc591xx_set_mode(struct regmap *regmap, u8 mode)
+{
+       int err;
+       u8 val;
+
+       err = regmap_write(regmap, TLC591XX_REG_MODE1, MODE1_NORMAL_MODE);
+       if (err)
+               return err;
+
+       val = MODE2_OCH_STOP | mode;
+
+       return regmap_write(regmap, TLC591XX_REG_MODE2, val);
+}
+
+static int
+tlc591xx_set_ledout(struct tlc591xx_priv *priv, struct tlc591xx_led *led,
+                   u8 val)
+{
+       unsigned int i = (led->led_no % 4) * 2;
+       unsigned int mask = LEDOUT_MASK << i;
+       unsigned int addr = priv->reg_ledout_offset + (led->led_no >> 2);
+
+       val = val << i;
+
+       return regmap_update_bits(priv->regmap, addr, mask, val);
+}
+
+static int
+tlc591xx_set_pwm(struct tlc591xx_priv *priv, struct tlc591xx_led *led,
+                u8 brightness)
+{
+       u8 pwm = TLC591XX_REG_PWM(led->led_no);
+
+       return regmap_write(priv->regmap, pwm, brightness);
+}
+
+static void
+tlc591xx_led_work(struct work_struct *work)
+{
+       struct tlc591xx_led *led = work_to_led(work);
+       struct tlc591xx_priv *priv = led->priv;
+       enum led_brightness brightness = led->ldev.brightness;
+       int err;
+
+       switch (brightness) {
+       case 0:
+               err = tlc591xx_set_ledout(priv, led, LEDOUT_OFF);
+               break;
+       case LED_FULL:
+               err = tlc591xx_set_ledout(priv, led, LEDOUT_ON);
+               break;
+       default:
+               err = tlc591xx_set_ledout(priv, led, LEDOUT_DIM);
+               if (!err)
+                       err = tlc591xx_set_pwm(priv, led, brightness);
+       }
+
+       if (err)
+               dev_err(led->ldev.dev, "Failed setting brightness\n");
+}
+
+static void
+tlc591xx_brightness_set(struct led_classdev *led_cdev,
+                       enum led_brightness brightness)
+{
+       struct tlc591xx_led *led = ldev_to_led(led_cdev);
+
+       led->ldev.brightness = brightness;
+       schedule_work(&led->work);
+}
+
+static void
+tlc591xx_destroy_devices(struct tlc591xx_priv *priv, unsigned int j)
+{
+       int i = j;
+
+       while (--i >= 0) {
+               if (priv->leds[i].active) {
+                       led_classdev_unregister(&priv->leds[i].ldev);
+                       cancel_work_sync(&priv->leds[i].work);
+               }
+       }
+}
+
+static int
+tlc591xx_configure(struct device *dev,
+                  struct tlc591xx_priv *priv,
+                  const struct tlc591xx *tlc591xx)
+{
+       unsigned int i;
+       int err = 0;
+
+       tlc591xx_set_mode(priv->regmap, MODE2_DIM);
+       for (i = 0; i < TLC591XX_MAX_LEDS; i++) {
+               struct tlc591xx_led *led = &priv->leds[i];
+
+               if (!led->active)
+                       continue;
+
+               led->priv = priv;
+               led->led_no = i;
+               led->ldev.brightness_set = tlc591xx_brightness_set;
+               led->ldev.max_brightness = LED_FULL;
+               INIT_WORK(&led->work, tlc591xx_led_work);
+               err = led_classdev_register(dev, &led->ldev);
+               if (err < 0) {
+                       dev_err(dev, "couldn't register LED %s\n",
+                               led->ldev.name);
+                       goto exit;
+               }
+       }
+
+       return 0;
+
+exit:
+       tlc591xx_destroy_devices(priv, i);
+       return err;
+}
+
+static const struct regmap_config tlc591xx_regmap = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = 0x1e,
+};
+
+static const struct of_device_id of_tlc591xx_leds_match[] = {
+       { .compatible = "ti,tlc59116",
+         .data = &tlc59116 },
+       { .compatible = "ti,tlc59108",
+         .data = &tlc59108 },
+       {},
+};
+MODULE_DEVICE_TABLE(of, of_tlc591xx_leds_match);
+
+static int
+tlc591xx_probe(struct i2c_client *client,
+              const struct i2c_device_id *id)
+{
+       struct device_node *np = client->dev.of_node, *child;
+       struct device *dev = &client->dev;
+       const struct of_device_id *match;
+       const struct tlc591xx *tlc591xx;
+       struct tlc591xx_priv *priv;
+       int err, count, reg;
+
+       match = of_match_device(of_tlc591xx_leds_match, dev);
+       if (!match)
+               return -ENODEV;
+
+       tlc591xx = match->data;
+       if (!np)
+               return -ENODEV;
+
+       count = of_get_child_count(np);
+       if (!count || count > tlc591xx->max_leds)
+               return -EINVAL;
+
+       if (!i2c_check_functionality(client->adapter,
+                                    I2C_FUNC_SMBUS_BYTE_DATA))
+               return -EIO;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->regmap = devm_regmap_init_i2c(client, &tlc591xx_regmap);
+       if (IS_ERR(priv->regmap)) {
+               err = PTR_ERR(priv->regmap);
+               dev_err(dev, "Failed to allocate register map: %d\n", err);
+               return err;
+       }
+       priv->reg_ledout_offset = tlc591xx->reg_ledout_offset;
+
+       i2c_set_clientdata(client, priv);
+
+       for_each_child_of_node(np, child) {
+               err = of_property_read_u32(child, "reg", &reg);
+               if (err)
+                       return err;
+               if (reg < 0 || reg >= tlc591xx->max_leds)
+                       return -EINVAL;
+               if (priv->leds[reg].active)
+                       return -EINVAL;
+               priv->leds[reg].active = true;
+               priv->leds[reg].ldev.name =
+                       of_get_property(child, "label", NULL) ? : child->name;
+               priv->leds[reg].ldev.default_trigger =
+                       of_get_property(child, "linux,default-trigger", NULL);
+       }
+       return tlc591xx_configure(dev, priv, tlc591xx);
+}
+
+static int
+tlc591xx_remove(struct i2c_client *client)
+{
+       struct tlc591xx_priv *priv = i2c_get_clientdata(client);
+
+       tlc591xx_destroy_devices(priv, TLC591XX_MAX_LEDS);
+
+       return 0;
+}
+
+static const struct i2c_device_id tlc591xx_id[] = {
+       { "tlc59116" },
+       { "tlc59108" },
+       {},
+};
+MODULE_DEVICE_TABLE(i2c, tlc591xx_id);
+
+static struct i2c_driver tlc591xx_driver = {
+       .driver = {
+               .name = "tlc591xx",
+               .of_match_table = of_match_ptr(of_tlc591xx_leds_match),
+       },
+       .probe = tlc591xx_probe,
+       .remove = tlc591xx_remove,
+       .id_table = tlc591xx_id,
+};
+
+module_i2c_driver(tlc591xx_driver);
+
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("TLC591XX LED driver");
index 79efe57c74058ffd8fecf6583257f3b6c193b3dd..bc89d7ace2c44e48b1e4c318dc53103767eba942 100644 (file)
@@ -13,7 +13,6 @@
 #ifndef __LEDS_H_INCLUDED
 #define __LEDS_H_INCLUDED
 
-#include <linux/device.h>
 #include <linux/rwsem.h>
 #include <linux/leds.h>
 
@@ -50,27 +49,4 @@ void led_stop_software_blink(struct led_classdev *led_cdev);
 extern struct rw_semaphore leds_list_lock;
 extern struct list_head leds_list;
 
-#ifdef CONFIG_LEDS_TRIGGERS
-void led_trigger_set_default(struct led_classdev *led_cdev);
-void led_trigger_set(struct led_classdev *led_cdev,
-                       struct led_trigger *trigger);
-void led_trigger_remove(struct led_classdev *led_cdev);
-
-static inline void *led_get_trigger_data(struct led_classdev *led_cdev)
-{
-       return led_cdev->trigger_data;
-}
-
-#else
-#define led_trigger_set_default(x) do {} while (0)
-#define led_trigger_set(x, y) do {} while (0)
-#define led_trigger_remove(x) do {} while (0)
-#define led_get_trigger_data(x) (NULL)
-#endif
-
-ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
-                       const char *buf, size_t count);
-ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
-                       char *buf);
-
 #endif /* __LEDS_H_INCLUDED */
index f7a01a72eb9e09e39f3128f16ed86f09ff82345f..b4b022933e29e463c8075b53a2ec8b0a0d7271b5 100644 (file)
@@ -44,6 +44,17 @@ config V4L2_MEM2MEM_DEV
         tristate
         depends on VIDEOBUF2_CORE
 
+# Used by LED subsystem flash drivers
+config V4L2_FLASH_LED_CLASS
+       tristate "V4L2 flash API for LED flash class devices"
+       depends on VIDEO_V4L2_SUBDEV_API
+       depends on LEDS_CLASS_FLASH
+       ---help---
+         Say Y here to enable V4L2 flash API support for LED flash
+         class drivers.
+
+         When in doubt, say N.
+
 # Used by drivers that need Videobuf modules
 config VIDEOBUF_GEN
        tristate
index 63d29f27538c079a20397d7f00c45c77d9104f81..dc3de00d68b5a4d88c140cfa0dfc0f2aa459fb5c 100644 (file)
@@ -22,6 +22,8 @@ obj-$(CONFIG_VIDEO_TUNER) += tuner.o
 
 obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o
 
+obj-$(CONFIG_V4L2_FLASH_LED_CLASS) += v4l2-flash-led-class.o
+
 obj-$(CONFIG_VIDEOBUF_GEN) += videobuf-core.o
 obj-$(CONFIG_VIDEOBUF_DMA_SG) += videobuf-dma-sg.o
 obj-$(CONFIG_VIDEOBUF_DMA_CONTIG) += videobuf-dma-contig.o
index 85a6a34128a8ecfcba63c372886bcc00cadf74d7..5bada202b2d38c264cbcd87022278d699e411a40 100644 (file)
 #include <media/v4l2-device.h>
 #include <media/v4l2-subdev.h>
 
-static bool match_i2c(struct device *dev, struct v4l2_async_subdev *asd)
+static bool match_i2c(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
 {
 #if IS_ENABLED(CONFIG_I2C)
-       struct i2c_client *client = i2c_verify_client(dev);
+       struct i2c_client *client = i2c_verify_client(sd->dev);
        return client &&
                asd->match.i2c.adapter_id == client->adapter->nr &&
                asd->match.i2c.address == client->addr;
@@ -34,14 +34,24 @@ static bool match_i2c(struct device *dev, struct v4l2_async_subdev *asd)
 #endif
 }
 
-static bool match_devname(struct device *dev, struct v4l2_async_subdev *asd)
+static bool match_devname(struct v4l2_subdev *sd,
+                         struct v4l2_async_subdev *asd)
 {
-       return !strcmp(asd->match.device_name.name, dev_name(dev));
+       return !strcmp(asd->match.device_name.name, dev_name(sd->dev));
 }
 
-static bool match_of(struct device *dev, struct v4l2_async_subdev *asd)
+static bool match_of(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
 {
-       return dev->of_node == asd->match.of.node;
+       return sd->of_node == asd->match.of.node;
+}
+
+static bool match_custom(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
+{
+       if (!asd->match.custom.match)
+               /* Match always */
+               return true;
+
+       return asd->match.custom.match(sd->dev, asd);
 }
 
 static LIST_HEAD(subdev_list);
@@ -51,17 +61,14 @@ static DEFINE_MUTEX(list_lock);
 static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *notifier,
                                                    struct v4l2_subdev *sd)
 {
+       bool (*match)(struct v4l2_subdev *, struct v4l2_async_subdev *);
        struct v4l2_async_subdev *asd;
-       bool (*match)(struct device *, struct v4l2_async_subdev *);
 
        list_for_each_entry(asd, &notifier->waiting, list) {
                /* bus_type has been verified valid before */
                switch (asd->match_type) {
                case V4L2_ASYNC_MATCH_CUSTOM:
-                       match = asd->match.custom.match;
-                       if (!match)
-                               /* Match always */
-                               return asd;
+                       match = match_custom;
                        break;
                case V4L2_ASYNC_MATCH_DEVNAME:
                        match = match_devname;
@@ -79,7 +86,7 @@ static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *
                }
 
                /* match cannot be NULL here */
-               if (match(sd->dev, asd))
+               if (match(sd, asd))
                        return asd;
        }
 
@@ -266,6 +273,14 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd)
 {
        struct v4l2_async_notifier *notifier;
 
+       /*
+        * No reference taken. The reference is held by the device
+        * (struct v4l2_subdev.dev), and async sub-device does not
+        * exist independently of the device at any point of time.
+        */
+       if (!sd->of_node && sd->dev)
+               sd->of_node = sd->dev->of_node;
+
        mutex_lock(&list_lock);
 
        INIT_LIST_HEAD(&sd->async_list);
diff --git a/drivers/media/v4l2-core/v4l2-flash-led-class.c b/drivers/media/v4l2-core/v4l2-flash-led-class.c
new file mode 100644 (file)
index 0000000..5bdfb8d
--- /dev/null
@@ -0,0 +1,710 @@
+/*
+ * V4L2 flash LED sub-device registration helpers.
+ *
+ *     Copyright (C) 2015 Samsung Electronics Co., Ltd
+ *     Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define has_flash_op(v4l2_flash, op)                           \
+       (v4l2_flash && v4l2_flash->ops->op)
+
+#define call_flash_op(v4l2_flash, op, arg)                     \
+               (has_flash_op(v4l2_flash, op) ?                 \
+                       v4l2_flash->ops->op(v4l2_flash, arg) :  \
+                       -EINVAL)
+
+enum ctrl_init_data_id {
+       LED_MODE,
+       TORCH_INTENSITY,
+       FLASH_INTENSITY,
+       INDICATOR_INTENSITY,
+       FLASH_TIMEOUT,
+       STROBE_SOURCE,
+       /*
+        * Only above values are applicable to
+        * the 'ctrls' array in the struct v4l2_flash.
+        */
+       FLASH_STROBE,
+       STROBE_STOP,
+       STROBE_STATUS,
+       FLASH_FAULT,
+       NUM_FLASH_CTRLS,
+};
+
+static enum led_brightness __intensity_to_led_brightness(
+                                       struct v4l2_ctrl *ctrl, s32 intensity)
+{
+       intensity -= ctrl->minimum;
+       intensity /= (u32) ctrl->step;
+
+       /*
+        * Indicator LEDs, unlike torch LEDs, are turned on/off basing on
+        * the state of V4L2_CID_FLASH_INDICATOR_INTENSITY control only.
+        * Therefore it must be possible to set it to 0 level which in
+        * the LED subsystem reflects LED_OFF state.
+        */
+       if (ctrl->minimum)
+               ++intensity;
+
+       return intensity;
+}
+
+static s32 __led_brightness_to_intensity(struct v4l2_ctrl *ctrl,
+                                        enum led_brightness brightness)
+{
+       /*
+        * Indicator LEDs, unlike torch LEDs, are turned on/off basing on
+        * the state of V4L2_CID_FLASH_INDICATOR_INTENSITY control only.
+        * Do not decrement brightness read from the LED subsystem for
+        * indicator LED as it may equal 0. For torch LEDs this function
+        * is called only when V4L2_FLASH_LED_MODE_TORCH is set and the
+        * brightness read is guaranteed to be greater than 0. In the mode
+        * V4L2_FLASH_LED_MODE_NONE the cached torch intensity value is used.
+        */
+       if (ctrl->id != V4L2_CID_FLASH_INDICATOR_INTENSITY)
+               --brightness;
+
+       return (brightness * ctrl->step) + ctrl->minimum;
+}
+
+static void v4l2_flash_set_led_brightness(struct v4l2_flash *v4l2_flash,
+                                       struct v4l2_ctrl *ctrl)
+{
+       struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
+       enum led_brightness brightness;
+
+       if (has_flash_op(v4l2_flash, intensity_to_led_brightness))
+               brightness = call_flash_op(v4l2_flash,
+                                       intensity_to_led_brightness,
+                                       ctrl->val);
+       else
+               brightness = __intensity_to_led_brightness(ctrl, ctrl->val);
+       /*
+        * In case a LED Flash class driver provides ops for custom
+        * brightness <-> intensity conversion, it also must have defined
+        * related v4l2 control step == 1. In such a case a backward conversion
+        * from led brightness to v4l2 intensity is required to find out the
+        * the aligned intensity value.
+        */
+       if (has_flash_op(v4l2_flash, led_brightness_to_intensity))
+               ctrl->val = call_flash_op(v4l2_flash,
+                                       led_brightness_to_intensity,
+                                       brightness);
+
+       if (ctrl == ctrls[TORCH_INTENSITY]) {
+               if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH)
+                       return;
+
+               led_set_brightness(&v4l2_flash->fled_cdev->led_cdev,
+                                       brightness);
+       } else {
+               led_set_brightness(&v4l2_flash->iled_cdev->led_cdev,
+                                       brightness);
+       }
+}
+
+static int v4l2_flash_update_led_brightness(struct v4l2_flash *v4l2_flash,
+                                       struct v4l2_ctrl *ctrl)
+{
+       struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
+       struct led_classdev *led_cdev;
+       int ret;
+
+       if (ctrl == ctrls[TORCH_INTENSITY]) {
+               /*
+                * Update torch brightness only if in TORCH_MODE. In other modes
+                * torch led is turned off, which would spuriously inform the
+                * user space that V4L2_CID_FLASH_TORCH_INTENSITY control value
+                * has changed to 0.
+                */
+               if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH)
+                       return 0;
+               led_cdev = &v4l2_flash->fled_cdev->led_cdev;
+       } else {
+               led_cdev = &v4l2_flash->iled_cdev->led_cdev;
+       }
+
+       ret = led_update_brightness(led_cdev);
+       if (ret < 0)
+               return ret;
+
+       if (has_flash_op(v4l2_flash, led_brightness_to_intensity))
+               ctrl->val = call_flash_op(v4l2_flash,
+                                               led_brightness_to_intensity,
+                                               led_cdev->brightness);
+       else
+               ctrl->val = __led_brightness_to_intensity(ctrl,
+                                               led_cdev->brightness);
+
+       return 0;
+}
+
+static int v4l2_flash_g_volatile_ctrl(struct v4l2_ctrl *c)
+{
+       struct v4l2_flash *v4l2_flash = v4l2_ctrl_to_v4l2_flash(c);
+       struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+       bool is_strobing;
+       int ret;
+
+       switch (c->id) {
+       case V4L2_CID_FLASH_TORCH_INTENSITY:
+       case V4L2_CID_FLASH_INDICATOR_INTENSITY:
+               return v4l2_flash_update_led_brightness(v4l2_flash, c);
+       case V4L2_CID_FLASH_INTENSITY:
+               ret = led_update_flash_brightness(fled_cdev);
+               if (ret < 0)
+                       return ret;
+               /*
+                * No conversion is needed as LED Flash class also uses
+                * microamperes for flash intensity units.
+                */
+               c->val = fled_cdev->brightness.val;
+               return 0;
+       case V4L2_CID_FLASH_STROBE_STATUS:
+               ret = led_get_flash_strobe(fled_cdev, &is_strobing);
+               if (ret < 0)
+                       return ret;
+               c->val = is_strobing;
+               return 0;
+       case V4L2_CID_FLASH_FAULT:
+               /* LED faults map directly to V4L2 flash faults */
+               return led_get_flash_fault(fled_cdev, &c->val);
+       default:
+               return -EINVAL;
+       }
+}
+
+static bool __software_strobe_mode_inactive(struct v4l2_ctrl **ctrls)
+{
+       return ((ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_FLASH) ||
+               (ctrls[STROBE_SOURCE] && (ctrls[STROBE_SOURCE]->val !=
+                               V4L2_FLASH_STROBE_SOURCE_SOFTWARE)));
+}
+
+static int v4l2_flash_s_ctrl(struct v4l2_ctrl *c)
+{
+       struct v4l2_flash *v4l2_flash = v4l2_ctrl_to_v4l2_flash(c);
+       struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+       struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+       struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
+       bool external_strobe;
+       int ret = 0;
+
+       switch (c->id) {
+       case V4L2_CID_FLASH_LED_MODE:
+               switch (c->val) {
+               case V4L2_FLASH_LED_MODE_NONE:
+                       led_set_brightness(led_cdev, LED_OFF);
+                       return led_set_flash_strobe(fled_cdev, false);
+               case V4L2_FLASH_LED_MODE_FLASH:
+                       /* Turn the torch LED off */
+                       led_set_brightness(led_cdev, LED_OFF);
+                       if (ctrls[STROBE_SOURCE]) {
+                               external_strobe = (ctrls[STROBE_SOURCE]->val ==
+                                       V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
+
+                               ret = call_flash_op(v4l2_flash,
+                                               external_strobe_set,
+                                               external_strobe);
+                       }
+                       return ret;
+               case V4L2_FLASH_LED_MODE_TORCH:
+                       if (ctrls[STROBE_SOURCE]) {
+                               ret = call_flash_op(v4l2_flash,
+                                               external_strobe_set,
+                                               false);
+                               if (ret < 0)
+                                       return ret;
+                       }
+                       /* Stop flash strobing */
+                       ret = led_set_flash_strobe(fled_cdev, false);
+                       if (ret < 0)
+                               return ret;
+
+                       v4l2_flash_set_led_brightness(v4l2_flash,
+                                                       ctrls[TORCH_INTENSITY]);
+                       return 0;
+               }
+               break;
+       case V4L2_CID_FLASH_STROBE_SOURCE:
+               external_strobe = (c->val == V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
+               /*
+                * For some hardware arrangements setting strobe source may
+                * affect torch mode. Therefore, if not in the flash mode,
+                * cache only this setting. It will be applied upon switching
+                * to flash mode.
+                */
+               if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_FLASH)
+                       return 0;
+
+               return call_flash_op(v4l2_flash, external_strobe_set,
+                                       external_strobe);
+       case V4L2_CID_FLASH_STROBE:
+               if (__software_strobe_mode_inactive(ctrls))
+                       return -EBUSY;
+               return led_set_flash_strobe(fled_cdev, true);
+       case V4L2_CID_FLASH_STROBE_STOP:
+               if (__software_strobe_mode_inactive(ctrls))
+                       return -EBUSY;
+               return led_set_flash_strobe(fled_cdev, false);
+       case V4L2_CID_FLASH_TIMEOUT:
+               /*
+                * No conversion is needed as LED Flash class also uses
+                * microseconds for flash timeout units.
+                */
+               return led_set_flash_timeout(fled_cdev, c->val);
+       case V4L2_CID_FLASH_INTENSITY:
+               /*
+                * No conversion is needed as LED Flash class also uses
+                * microamperes for flash intensity units.
+                */
+               return led_set_flash_brightness(fled_cdev, c->val);
+       case V4L2_CID_FLASH_TORCH_INTENSITY:
+       case V4L2_CID_FLASH_INDICATOR_INTENSITY:
+               v4l2_flash_set_led_brightness(v4l2_flash, c);
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static const struct v4l2_ctrl_ops v4l2_flash_ctrl_ops = {
+       .g_volatile_ctrl = v4l2_flash_g_volatile_ctrl,
+       .s_ctrl = v4l2_flash_s_ctrl,
+};
+
+static void __lfs_to_v4l2_ctrl_config(struct led_flash_setting *s,
+                               struct v4l2_ctrl_config *c)
+{
+       c->min = s->min;
+       c->max = s->max;
+       c->step = s->step;
+       c->def = s->val;
+}
+
+static void __fill_ctrl_init_data(struct v4l2_flash *v4l2_flash,
+                         struct v4l2_flash_config *flash_cfg,
+                         struct v4l2_flash_ctrl_data *ctrl_init_data)
+{
+       struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+       const struct led_flash_ops *fled_cdev_ops = fled_cdev->ops;
+       struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+       struct v4l2_ctrl_config *ctrl_cfg;
+       u32 mask;
+
+       /* Init FLASH_FAULT ctrl data */
+       if (flash_cfg->flash_faults) {
+               ctrl_init_data[FLASH_FAULT].cid = V4L2_CID_FLASH_FAULT;
+               ctrl_cfg = &ctrl_init_data[FLASH_FAULT].config;
+               ctrl_cfg->id = V4L2_CID_FLASH_FAULT;
+               ctrl_cfg->max = flash_cfg->flash_faults;
+               ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
+                                 V4L2_CTRL_FLAG_READ_ONLY;
+       }
+
+       /* Init FLASH_LED_MODE ctrl data */
+       mask = 1 << V4L2_FLASH_LED_MODE_NONE |
+              1 << V4L2_FLASH_LED_MODE_TORCH;
+       if (led_cdev->flags & LED_DEV_CAP_FLASH)
+               mask |= 1 << V4L2_FLASH_LED_MODE_FLASH;
+
+       ctrl_init_data[LED_MODE].cid = V4L2_CID_FLASH_LED_MODE;
+       ctrl_cfg = &ctrl_init_data[LED_MODE].config;
+       ctrl_cfg->id = V4L2_CID_FLASH_LED_MODE;
+       ctrl_cfg->max = V4L2_FLASH_LED_MODE_TORCH;
+       ctrl_cfg->menu_skip_mask = ~mask;
+       ctrl_cfg->def = V4L2_FLASH_LED_MODE_NONE;
+       ctrl_cfg->flags = 0;
+
+       /* Init TORCH_INTENSITY ctrl data */
+       ctrl_init_data[TORCH_INTENSITY].cid = V4L2_CID_FLASH_TORCH_INTENSITY;
+       ctrl_cfg = &ctrl_init_data[TORCH_INTENSITY].config;
+       __lfs_to_v4l2_ctrl_config(&flash_cfg->torch_intensity, ctrl_cfg);
+       ctrl_cfg->id = V4L2_CID_FLASH_TORCH_INTENSITY;
+       ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
+                         V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
+
+       /* Init INDICATOR_INTENSITY ctrl data */
+       if (v4l2_flash->iled_cdev) {
+               ctrl_init_data[INDICATOR_INTENSITY].cid =
+                                       V4L2_CID_FLASH_INDICATOR_INTENSITY;
+               ctrl_cfg = &ctrl_init_data[INDICATOR_INTENSITY].config;
+               __lfs_to_v4l2_ctrl_config(&flash_cfg->indicator_intensity,
+                                         ctrl_cfg);
+               ctrl_cfg->id = V4L2_CID_FLASH_INDICATOR_INTENSITY;
+               ctrl_cfg->min = 0;
+               ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
+                                 V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
+       }
+
+       if (!(led_cdev->flags & LED_DEV_CAP_FLASH))
+               return;
+
+       /* Init FLASH_STROBE ctrl data */
+       ctrl_init_data[FLASH_STROBE].cid = V4L2_CID_FLASH_STROBE;
+       ctrl_cfg = &ctrl_init_data[FLASH_STROBE].config;
+       ctrl_cfg->id = V4L2_CID_FLASH_STROBE;
+
+       /* Init STROBE_STOP ctrl data */
+       ctrl_init_data[STROBE_STOP].cid = V4L2_CID_FLASH_STROBE_STOP;
+       ctrl_cfg = &ctrl_init_data[STROBE_STOP].config;
+       ctrl_cfg->id = V4L2_CID_FLASH_STROBE_STOP;
+
+       /* Init FLASH_STROBE_SOURCE ctrl data */
+       if (flash_cfg->has_external_strobe) {
+               mask = (1 << V4L2_FLASH_STROBE_SOURCE_SOFTWARE) |
+                      (1 << V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
+               ctrl_init_data[STROBE_SOURCE].cid =
+                                       V4L2_CID_FLASH_STROBE_SOURCE;
+               ctrl_cfg = &ctrl_init_data[STROBE_SOURCE].config;
+               ctrl_cfg->id = V4L2_CID_FLASH_STROBE_SOURCE;
+               ctrl_cfg->max = V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
+               ctrl_cfg->menu_skip_mask = ~mask;
+               ctrl_cfg->def = V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
+       }
+
+       /* Init STROBE_STATUS ctrl data */
+       if (fled_cdev_ops->strobe_get) {
+               ctrl_init_data[STROBE_STATUS].cid =
+                                       V4L2_CID_FLASH_STROBE_STATUS;
+               ctrl_cfg = &ctrl_init_data[STROBE_STATUS].config;
+               ctrl_cfg->id = V4L2_CID_FLASH_STROBE_STATUS;
+               ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
+                                 V4L2_CTRL_FLAG_READ_ONLY;
+       }
+
+       /* Init FLASH_TIMEOUT ctrl data */
+       if (fled_cdev_ops->timeout_set) {
+               ctrl_init_data[FLASH_TIMEOUT].cid = V4L2_CID_FLASH_TIMEOUT;
+               ctrl_cfg = &ctrl_init_data[FLASH_TIMEOUT].config;
+               __lfs_to_v4l2_ctrl_config(&fled_cdev->timeout, ctrl_cfg);
+               ctrl_cfg->id = V4L2_CID_FLASH_TIMEOUT;
+       }
+
+       /* Init FLASH_INTENSITY ctrl data */
+       if (fled_cdev_ops->flash_brightness_set) {
+               ctrl_init_data[FLASH_INTENSITY].cid = V4L2_CID_FLASH_INTENSITY;
+               ctrl_cfg = &ctrl_init_data[FLASH_INTENSITY].config;
+               __lfs_to_v4l2_ctrl_config(&fled_cdev->brightness, ctrl_cfg);
+               ctrl_cfg->id = V4L2_CID_FLASH_INTENSITY;
+               ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
+                                 V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
+       }
+}
+
+static int v4l2_flash_init_controls(struct v4l2_flash *v4l2_flash,
+                               struct v4l2_flash_config *flash_cfg)
+
+{
+       struct v4l2_flash_ctrl_data *ctrl_init_data;
+       struct v4l2_ctrl *ctrl;
+       struct v4l2_ctrl_config *ctrl_cfg;
+       int i, ret, num_ctrls = 0;
+
+       v4l2_flash->ctrls = devm_kzalloc(v4l2_flash->sd.dev,
+                                       sizeof(*v4l2_flash->ctrls) *
+                                       (STROBE_SOURCE + 1), GFP_KERNEL);
+       if (!v4l2_flash->ctrls)
+               return -ENOMEM;
+
+       /* allocate memory dynamically so as not to exceed stack frame size */
+       ctrl_init_data = kcalloc(NUM_FLASH_CTRLS, sizeof(*ctrl_init_data),
+                                       GFP_KERNEL);
+       if (!ctrl_init_data)
+               return -ENOMEM;
+
+       __fill_ctrl_init_data(v4l2_flash, flash_cfg, ctrl_init_data);
+
+       for (i = 0; i < NUM_FLASH_CTRLS; ++i)
+               if (ctrl_init_data[i].cid)
+                       ++num_ctrls;
+
+       v4l2_ctrl_handler_init(&v4l2_flash->hdl, num_ctrls);
+
+       for (i = 0; i < NUM_FLASH_CTRLS; ++i) {
+               ctrl_cfg = &ctrl_init_data[i].config;
+               if (!ctrl_init_data[i].cid)
+                       continue;
+
+               if (ctrl_cfg->id == V4L2_CID_FLASH_LED_MODE ||
+                   ctrl_cfg->id == V4L2_CID_FLASH_STROBE_SOURCE)
+                       ctrl = v4l2_ctrl_new_std_menu(&v4l2_flash->hdl,
+                                               &v4l2_flash_ctrl_ops,
+                                               ctrl_cfg->id,
+                                               ctrl_cfg->max,
+                                               ctrl_cfg->menu_skip_mask,
+                                               ctrl_cfg->def);
+               else
+                       ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl,
+                                               &v4l2_flash_ctrl_ops,
+                                               ctrl_cfg->id,
+                                               ctrl_cfg->min,
+                                               ctrl_cfg->max,
+                                               ctrl_cfg->step,
+                                               ctrl_cfg->def);
+
+               if (ctrl)
+                       ctrl->flags |= ctrl_cfg->flags;
+
+               if (i <= STROBE_SOURCE)
+                       v4l2_flash->ctrls[i] = ctrl;
+       }
+
+       kfree(ctrl_init_data);
+
+       if (v4l2_flash->hdl.error) {
+               ret = v4l2_flash->hdl.error;
+               goto error_free_handler;
+       }
+
+       v4l2_ctrl_handler_setup(&v4l2_flash->hdl);
+
+       v4l2_flash->sd.ctrl_handler = &v4l2_flash->hdl;
+
+       return 0;
+
+error_free_handler:
+       v4l2_ctrl_handler_free(&v4l2_flash->hdl);
+       return ret;
+}
+
+static int __sync_device_with_v4l2_controls(struct v4l2_flash *v4l2_flash)
+{
+       struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+       struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
+       int ret = 0;
+
+       v4l2_flash_set_led_brightness(v4l2_flash, ctrls[TORCH_INTENSITY]);
+
+       if (ctrls[INDICATOR_INTENSITY])
+               v4l2_flash_set_led_brightness(v4l2_flash,
+                                               ctrls[INDICATOR_INTENSITY]);
+
+       if (ctrls[FLASH_TIMEOUT]) {
+               ret = led_set_flash_timeout(fled_cdev,
+                                       ctrls[FLASH_TIMEOUT]->val);
+               if (ret < 0)
+                       return ret;
+       }
+
+       if (ctrls[FLASH_INTENSITY]) {
+               ret = led_set_flash_brightness(fled_cdev,
+                                       ctrls[FLASH_INTENSITY]->val);
+               if (ret < 0)
+                       return ret;
+       }
+
+       /*
+        * For some hardware arrangements setting strobe source may affect
+        * torch mode. Synchronize strobe source setting only if not in torch
+        * mode. For torch mode case it will get synchronized upon switching
+        * to flash mode.
+        */
+       if (ctrls[STROBE_SOURCE] &&
+           ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH)
+               ret = call_flash_op(v4l2_flash, external_strobe_set,
+                                       ctrls[STROBE_SOURCE]->val);
+
+       return ret;
+}
+
+/*
+ * V4L2 subdev internal operations
+ */
+
+static int v4l2_flash_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+       struct v4l2_flash *v4l2_flash = v4l2_subdev_to_v4l2_flash(sd);
+       struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+       struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+       struct led_classdev_flash *iled_cdev = v4l2_flash->iled_cdev;
+       struct led_classdev *led_cdev_ind = NULL;
+       int ret = 0;
+
+       if (!v4l2_fh_is_singular(&fh->vfh))
+               return 0;
+
+       mutex_lock(&led_cdev->led_access);
+
+       led_sysfs_disable(led_cdev);
+       led_trigger_remove(led_cdev);
+
+       mutex_unlock(&led_cdev->led_access);
+
+       if (iled_cdev) {
+               led_cdev_ind = &iled_cdev->led_cdev;
+
+               mutex_lock(&led_cdev_ind->led_access);
+
+               led_sysfs_disable(led_cdev_ind);
+               led_trigger_remove(led_cdev_ind);
+
+               mutex_unlock(&led_cdev_ind->led_access);
+       }
+
+       ret = __sync_device_with_v4l2_controls(v4l2_flash);
+       if (ret < 0)
+               goto out_sync_device;
+
+       return 0;
+out_sync_device:
+       mutex_lock(&led_cdev->led_access);
+       led_sysfs_enable(led_cdev);
+       mutex_unlock(&led_cdev->led_access);
+
+       if (led_cdev_ind) {
+               mutex_lock(&led_cdev_ind->led_access);
+               led_sysfs_enable(led_cdev_ind);
+               mutex_unlock(&led_cdev_ind->led_access);
+       }
+
+       return ret;
+}
+
+static int v4l2_flash_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+       struct v4l2_flash *v4l2_flash = v4l2_subdev_to_v4l2_flash(sd);
+       struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+       struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+       struct led_classdev_flash *iled_cdev = v4l2_flash->iled_cdev;
+       int ret = 0;
+
+       if (!v4l2_fh_is_singular(&fh->vfh))
+               return 0;
+
+       mutex_lock(&led_cdev->led_access);
+
+       if (v4l2_flash->ctrls[STROBE_SOURCE])
+               ret = v4l2_ctrl_s_ctrl(v4l2_flash->ctrls[STROBE_SOURCE],
+                               V4L2_FLASH_STROBE_SOURCE_SOFTWARE);
+       led_sysfs_enable(led_cdev);
+
+       mutex_unlock(&led_cdev->led_access);
+
+       if (iled_cdev) {
+               struct led_classdev *led_cdev_ind = &iled_cdev->led_cdev;
+
+               mutex_lock(&led_cdev_ind->led_access);
+               led_sysfs_enable(led_cdev_ind);
+               mutex_unlock(&led_cdev_ind->led_access);
+       }
+
+       return ret;
+}
+
+static const struct v4l2_subdev_internal_ops v4l2_flash_subdev_internal_ops = {
+       .open = v4l2_flash_open,
+       .close = v4l2_flash_close,
+};
+
+static const struct v4l2_subdev_core_ops v4l2_flash_core_ops = {
+       .queryctrl = v4l2_subdev_queryctrl,
+       .querymenu = v4l2_subdev_querymenu,
+};
+
+static const struct v4l2_subdev_ops v4l2_flash_subdev_ops = {
+       .core = &v4l2_flash_core_ops,
+};
+
+struct v4l2_flash *v4l2_flash_init(
+       struct device *dev, struct device_node *of_node,
+       struct led_classdev_flash *fled_cdev,
+       struct led_classdev_flash *iled_cdev,
+       const struct v4l2_flash_ops *ops,
+       struct v4l2_flash_config *config)
+{
+       struct v4l2_flash *v4l2_flash;
+       struct led_classdev *led_cdev;
+       struct v4l2_subdev *sd;
+       int ret;
+
+       if (!fled_cdev || !ops || !config)
+               return ERR_PTR(-EINVAL);
+
+       led_cdev = &fled_cdev->led_cdev;
+
+       v4l2_flash = devm_kzalloc(led_cdev->dev, sizeof(*v4l2_flash),
+                                       GFP_KERNEL);
+       if (!v4l2_flash)
+               return ERR_PTR(-ENOMEM);
+
+       sd = &v4l2_flash->sd;
+       v4l2_flash->fled_cdev = fled_cdev;
+       v4l2_flash->iled_cdev = iled_cdev;
+       v4l2_flash->ops = ops;
+       sd->dev = dev;
+       sd->of_node = of_node;
+       v4l2_subdev_init(sd, &v4l2_flash_subdev_ops);
+       sd->internal_ops = &v4l2_flash_subdev_internal_ops;
+       sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+       strlcpy(sd->name, config->dev_name, sizeof(sd->name));
+
+       ret = media_entity_init(&sd->entity, 0, NULL, 0);
+       if (ret < 0)
+               return ERR_PTR(ret);
+
+       sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_FLASH;
+
+       ret = v4l2_flash_init_controls(v4l2_flash, config);
+       if (ret < 0)
+               goto err_init_controls;
+
+       if (sd->of_node)
+               of_node_get(sd->of_node);
+       else
+               of_node_get(led_cdev->dev->of_node);
+
+       ret = v4l2_async_register_subdev(sd);
+       if (ret < 0)
+               goto err_async_register_sd;
+
+       return v4l2_flash;
+
+err_async_register_sd:
+       of_node_put(led_cdev->dev->of_node);
+       v4l2_ctrl_handler_free(sd->ctrl_handler);
+err_init_controls:
+       media_entity_cleanup(&sd->entity);
+
+       return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(v4l2_flash_init);
+
+void v4l2_flash_release(struct v4l2_flash *v4l2_flash)
+{
+       struct v4l2_subdev *sd;
+       struct led_classdev *led_cdev;
+
+       if (IS_ERR_OR_NULL(v4l2_flash))
+               return;
+
+       sd = &v4l2_flash->sd;
+       led_cdev = &v4l2_flash->fled_cdev->led_cdev;
+
+       v4l2_async_unregister_subdev(sd);
+
+       if (sd->of_node)
+               of_node_put(sd->of_node);
+       else
+               of_node_put(led_cdev->dev->of_node);
+
+       v4l2_ctrl_handler_free(sd->ctrl_handler);
+       media_entity_cleanup(&sd->entity);
+}
+EXPORT_SYMBOL_GPL(v4l2_flash_release);
+
+MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
+MODULE_DESCRIPTION("V4L2 Flash sub-device helpers");
+MODULE_LICENSE("GPL v2");
index fd098169fe87ed0b92aecd23b46e7a9a790bafad..adac255aee86e359fb83085ba2c41edfc6f1991d 100644 (file)
@@ -407,6 +407,21 @@ static inline int desc_to_gpio(const struct gpio_desc *desc)
        return -EINVAL;
 }
 
+/* Child properties interface */
+struct fwnode_handle;
+
+static inline struct gpio_desc *fwnode_get_named_gpiod(
+       struct fwnode_handle *fwnode, const char *propname)
+{
+       return ERR_PTR(-ENOSYS);
+}
+
+static inline struct gpio_desc *devm_get_gpiod_from_child(
+       struct device *dev, const char *con_id, struct fwnode_handle *child)
+{
+       return ERR_PTR(-ENOSYS);
+}
+
 #endif /* CONFIG_GPIOLIB */
 
 /*
index 9a2b000094cf98e5758e2a3b463bae69b8febef8..b122eeafb5dc17b8a8b1a1852dc1c420ecf0f8d2 100644 (file)
@@ -12,6 +12,7 @@
 #ifndef __LINUX_LEDS_H_INCLUDED
 #define __LINUX_LEDS_H_INCLUDED
 
+#include <linux/device.h>
 #include <linux/list.h>
 #include <linux/mutex.h>
 #include <linux/rwsem.h>
@@ -222,6 +223,11 @@ struct led_trigger {
        struct list_head  next_trig;
 };
 
+ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
+                       const char *buf, size_t count);
+ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
+                       char *buf);
+
 /* Registration functions for complex triggers */
 extern int led_trigger_register(struct led_trigger *trigger);
 extern void led_trigger_unregister(struct led_trigger *trigger);
@@ -238,6 +244,16 @@ extern void led_trigger_blink_oneshot(struct led_trigger *trigger,
                                      unsigned long *delay_on,
                                      unsigned long *delay_off,
                                      int invert);
+extern void led_trigger_set_default(struct led_classdev *led_cdev);
+extern void led_trigger_set(struct led_classdev *led_cdev,
+                       struct led_trigger *trigger);
+extern void led_trigger_remove(struct led_classdev *led_cdev);
+
+static inline void *led_get_trigger_data(struct led_classdev *led_cdev)
+{
+       return led_cdev->trigger_data;
+}
+
 /**
  * led_trigger_rename_static - rename a trigger
  * @name: the new trigger name
@@ -267,6 +283,15 @@ static inline void led_trigger_register_simple(const char *name,
 static inline void led_trigger_unregister_simple(struct led_trigger *trigger) {}
 static inline void led_trigger_event(struct led_trigger *trigger,
                                enum led_brightness event) {}
+static inline void led_trigger_set_default(struct led_classdev *led_cdev) {}
+static inline void led_trigger_set(struct led_classdev *led_cdev,
+                               struct led_trigger *trigger) {}
+static inline void led_trigger_remove(struct led_classdev *led_cdev) {}
+static inline void *led_get_trigger_data(struct led_classdev *led_cdev)
+{
+       return NULL;
+}
+
 #endif /* CONFIG_LEDS_TRIGGERS */
 
 /* Trigger specific functions */
diff --git a/include/media/v4l2-flash-led-class.h b/include/media/v4l2-flash-led-class.h
new file mode 100644 (file)
index 0000000..098236c
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * V4L2 flash LED sub-device registration helpers.
+ *
+ *     Copyright (C) 2015 Samsung Electronics Co., Ltd
+ *     Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _V4L2_FLASH_H
+#define _V4L2_FLASH_H
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-subdev.h>
+
+struct led_classdev_flash;
+struct led_classdev;
+struct v4l2_flash;
+enum led_brightness;
+
+/*
+ * struct v4l2_flash_ctrl_data - flash control initialization data, filled
+ *                             basing on the features declared by the LED flash
+ *                             class driver in the v4l2_flash_config
+ * @config:    initialization data for a control
+ * @cid:       contains v4l2 flash control id if the config
+ *             field was initialized, 0 otherwise
+ */
+struct v4l2_flash_ctrl_data {
+       struct v4l2_ctrl_config config;
+       u32 cid;
+};
+
+struct v4l2_flash_ops {
+       /* setup strobing the flash by hardware pin state assertion */
+       int (*external_strobe_set)(struct v4l2_flash *v4l2_flash,
+                                       bool enable);
+       /* convert intensity to brightness in a device specific manner */
+       enum led_brightness (*intensity_to_led_brightness)
+               (struct v4l2_flash *v4l2_flash, s32 intensity);
+       /* convert brightness to intensity in a device specific manner */
+       s32 (*led_brightness_to_intensity)
+               (struct v4l2_flash *v4l2_flash, enum led_brightness);
+};
+
+/**
+ * struct v4l2_flash_config - V4L2 Flash sub-device initialization data
+ * @dev_name:                  the name of the media entity,
+                               unique in the system
+ * @torch_intensity:           constraints for the LED in torch mode
+ * @indicator_intensity:       constraints for the indicator LED
+ * @flash_faults:              bitmask of flash faults that the LED flash class
+                               device can report; corresponding LED_FAULT* bit
+                               definitions are available in the header file
+                               <linux/led-class-flash.h>
+ * @has_external_strobe:       external strobe capability
+ */
+struct v4l2_flash_config {
+       char dev_name[32];
+       struct led_flash_setting torch_intensity;
+       struct led_flash_setting indicator_intensity;
+       u32 flash_faults;
+       unsigned int has_external_strobe:1;
+};
+
+/**
+ * struct v4l2_flash - Flash sub-device context
+ * @fled_cdev:         LED flash class device controlled by this sub-device
+ * @iled_cdev:         LED class device representing indicator LED associated
+ *                     with the LED flash class device
+ * @ops:               V4L2 specific flash ops
+ * @sd:                        V4L2 sub-device
+ * @hdl:               flash controls handler
+ * @ctrls:             array of pointers to controls, whose values define
+ *                     the sub-device state
+ */
+struct v4l2_flash {
+       struct led_classdev_flash *fled_cdev;
+       struct led_classdev_flash *iled_cdev;
+       const struct v4l2_flash_ops *ops;
+
+       struct v4l2_subdev sd;
+       struct v4l2_ctrl_handler hdl;
+       struct v4l2_ctrl **ctrls;
+};
+
+static inline struct v4l2_flash *v4l2_subdev_to_v4l2_flash(
+                                                       struct v4l2_subdev *sd)
+{
+       return container_of(sd, struct v4l2_flash, sd);
+}
+
+static inline struct v4l2_flash *v4l2_ctrl_to_v4l2_flash(struct v4l2_ctrl *c)
+{
+       return container_of(c->handler, struct v4l2_flash, hdl);
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+/**
+ * v4l2_flash_init - initialize V4L2 flash led sub-device
+ * @dev:       flash device, e.g. an I2C device
+ * @of_node:   of_node of the LED, may be NULL if the same as device's
+ * @fled_cdev: LED flash class device to wrap
+ * @iled_cdev: LED flash class device representing indicator LED associated
+ *             with fled_cdev, may be NULL
+ * @flash_ops: V4L2 Flash device ops
+ * @config:    initialization data for V4L2 Flash sub-device
+ *
+ * Create V4L2 Flash sub-device wrapping given LED subsystem device.
+ *
+ * Returns: A valid pointer, or, when an error occurs, the return
+ * value is encoded using ERR_PTR(). Use IS_ERR() to check and
+ * PTR_ERR() to obtain the numeric return value.
+ */
+struct v4l2_flash *v4l2_flash_init(
+       struct device *dev, struct device_node *of_node,
+       struct led_classdev_flash *fled_cdev,
+       struct led_classdev_flash *iled_cdev,
+       const struct v4l2_flash_ops *ops,
+       struct v4l2_flash_config *config);
+
+/**
+ * v4l2_flash_release - release V4L2 Flash sub-device
+ * @flash: the V4L2 Flash sub-device to release
+ *
+ * Release V4L2 Flash sub-device.
+ */
+void v4l2_flash_release(struct v4l2_flash *v4l2_flash);
+
+#else
+static inline struct v4l2_flash *v4l2_flash_init(
+       struct device *dev, struct device_node *of_node,
+       struct led_classdev_flash *fled_cdev,
+       struct led_classdev_flash *iled_cdev,
+       const struct v4l2_flash_ops *ops,
+       struct v4l2_flash_config *config)
+{
+       return NULL;
+}
+
+static inline void v4l2_flash_release(struct v4l2_flash *v4l2_flash)
+{
+}
+#endif /* CONFIG_V4L2_FLASH_LED_CLASS */
+
+#endif /* _V4L2_FLASH_H */
index dc20102ff600bc9834ed6d83963cfc09816a8c0e..4e18318eb425f27e696d08ce926867ee13459a49 100644 (file)
@@ -605,6 +605,8 @@ struct v4l2_subdev {
        struct video_device *devnode;
        /* pointer to the physical device, if any */
        struct device *dev;
+       /* The device_node of the subdev, usually the same as dev->of_node. */
+       struct device_node *of_node;
        /* Links this subdev to a global subdev_list or @notifier->done list. */
        struct list_head async_list;
        /* Pointer to respective struct v4l2_async_subdev. */