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 ...tirimbino
commit
13d45f79a2
@ -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>; |
||||
}; |
||||
}; |
@ -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"; |
||||
}; |
||||
}; |
@ -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"; |
||||
}; |
||||
}; |
@ -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>; |
||||
}; |
||||
}; |
@ -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"; |
||||
}; |
||||
}; |
@ -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"); |
@ -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", ®)) |
||||
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"); |
@ -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", ®)) |
||||
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"); |
@ -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"); |
File diff suppressed because it is too large
Load Diff
@ -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", ®); |
||||
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"); |
@ -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"); |
@ -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 */ |
Loading…
Reference in new issue