forked from Minki/linux
Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/cooloney/linux-leds
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 ...
This commit is contained in:
commit
13d45f79a2
73
Documentation/devicetree/bindings/leds/leds-aat1290.txt
Normal file
73
Documentation/devicetree/bindings/leds/leds-aat1290.txt
Normal file
@ -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>;
|
||||
};
|
||||
};
|
309
Documentation/devicetree/bindings/leds/leds-bcm6328.txt
Normal file
309
Documentation/devicetree/bindings/leds/leds-bcm6328.txt
Normal file
@ -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";
|
||||
};
|
||||
};
|
145
Documentation/devicetree/bindings/leds/leds-bcm6358.txt
Normal file
145
Documentation/devicetree/bindings/leds/leds-bcm6358.txt
Normal file
@ -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";
|
||||
};
|
||||
};
|
50
Documentation/devicetree/bindings/leds/leds-ktd2692.txt
Normal file
50
Documentation/devicetree/bindings/leds/leds-ktd2692.txt
Normal file
@ -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>;
|
||||
};
|
||||
};
|
40
Documentation/devicetree/bindings/leds/leds-tlc591xx.txt
Normal file
40
Documentation/devicetree/bindings/leds/leds-tlc591xx.txt
Normal file
@ -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";
|
||||
};
|
||||
};
|
@ -114,6 +114,7 @@ isee ISEE 2007 S.L.
|
||||
isil Intersil
|
||||
karo Ka-Ro electronics GmbH
|
||||
keymile Keymile GmbH
|
||||
kinetic Kinetic Technologies
|
||||
lacie LaCie
|
||||
lantiq Lantiq Semiconductor
|
||||
lenovo Lenovo Group Ltd.
|
||||
|
@ -20,3 +20,54 @@ Following sysfs attributes are exposed for controlling flash LED devices:
|
||||
- max_flash_timeout
|
||||
- flash_strobe
|
||||
- flash_fault
|
||||
|
||||
|
||||
V4L2 flash wrapper for flash LEDs
|
||||
=================================
|
||||
|
||||
A LED subsystem driver can be controlled also from the level of VideoForLinux2
|
||||
subsystem. In order to enable this CONFIG_V4L2_FLASH_LED_CLASS symbol has to
|
||||
be defined in the kernel config.
|
||||
|
||||
The driver must call the v4l2_flash_init function to get registered in the
|
||||
V4L2 subsystem. The function takes six arguments:
|
||||
- dev : flash device, e.g. an I2C device
|
||||
- of_node : of_node of the LED, may be NULL if the same as device's
|
||||
- fled_cdev : LED flash class device to wrap
|
||||
- iled_cdev : LED flash class device representing indicator LED associated with
|
||||
fled_cdev, may be NULL
|
||||
- ops : V4L2 specific ops
|
||||
* external_strobe_set - defines the source of the flash LED strobe -
|
||||
V4L2_CID_FLASH_STROBE control or external source, typically
|
||||
a sensor, which makes it possible to synchronise the flash
|
||||
strobe start with exposure start,
|
||||
* intensity_to_led_brightness and led_brightness_to_intensity - perform
|
||||
enum led_brightness <-> V4L2 intensity conversion in a device
|
||||
specific manner - they can be used for devices with non-linear
|
||||
LED current scale.
|
||||
- config : configuration for V4L2 Flash sub-device
|
||||
* dev_name - the name of the media entity, unique in the system,
|
||||
* flash_faults - bitmask of flash faults that the LED flash class
|
||||
device can report; corresponding LED_FAULT* bit definitions are
|
||||
available in <linux/led-class-flash.h>,
|
||||
* torch_intensity - constraints for the LED in TORCH mode
|
||||
in microamperes,
|
||||
* indicator_intensity - constraints for the indicator LED
|
||||
in microamperes,
|
||||
* has_external_strobe - determines whether the flash strobe source
|
||||
can be switched to external,
|
||||
|
||||
On remove the v4l2_flash_release function has to be called, which takes one
|
||||
argument - struct v4l2_flash pointer returned previously by v4l2_flash_init.
|
||||
This function can be safely called with NULL or error pointer argument.
|
||||
|
||||
Please refer to drivers/leds/leds-max77693.c for an exemplary usage of the
|
||||
v4l2 flash wrapper.
|
||||
|
||||
Once the V4L2 sub-device is registered by the driver which created the Media
|
||||
controller device, the sub-device node acts just as a node of a native V4L2
|
||||
flash API device would. The calls are simply routed to the LED flash API.
|
||||
|
||||
Opening the V4L2 flash sub-device makes the LED subsystem sysfs interface
|
||||
unavailable. The interface is re-enabled after the V4L2 flash sub-device
|
||||
is closed.
|
||||
|
@ -49,6 +49,36 @@ There are two ways to run LED patterns.
|
||||
2) Firmware interface - LP55xx common interface
|
||||
For the details, please refer to 'firmware' section in leds-lp55xx.txt
|
||||
|
||||
LP5523 has three master faders. If a channel is mapped to one of
|
||||
the master faders, its output is dimmed based on the value of the master
|
||||
fader.
|
||||
|
||||
For example,
|
||||
|
||||
echo "123000123" > master_fader_leds
|
||||
|
||||
creates the following channel-fader mappings:
|
||||
|
||||
channel 0,6 to master_fader1
|
||||
channel 1,7 to master_fader2
|
||||
channel 2,8 to master_fader3
|
||||
|
||||
Then, to have 25% of the original output on channel 0,6:
|
||||
|
||||
echo 64 > master_fader1
|
||||
|
||||
To have 0% of the original output (i.e. no output) channel 1,7:
|
||||
|
||||
echo 0 > master_fader2
|
||||
|
||||
To have 100% of the original output (i.e. no dimming) on channel 2,8:
|
||||
|
||||
echo 255 > master_fader3
|
||||
|
||||
To clear all master fader controls:
|
||||
|
||||
echo "000000000" > master_fader_leds
|
||||
|
||||
Selftest uses always the current from the platform data.
|
||||
|
||||
Each channel contains led current settings.
|
||||
|
@ -39,6 +39,32 @@ config LEDS_88PM860X
|
||||
This option enables support for on-chip LED drivers found on Marvell
|
||||
Semiconductor 88PM8606 PMIC.
|
||||
|
||||
config LEDS_AAT1290
|
||||
tristate "LED support for the AAT1290"
|
||||
depends on LEDS_CLASS_FLASH
|
||||
depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
|
||||
depends on GPIOLIB
|
||||
depends on OF
|
||||
depends on PINCTRL
|
||||
help
|
||||
This option enables support for the LEDs on the AAT1290.
|
||||
|
||||
config LEDS_BCM6328
|
||||
tristate "LED Support for Broadcom BCM6328"
|
||||
depends on LEDS_CLASS
|
||||
depends on OF
|
||||
help
|
||||
This option enables support for LEDs connected to the BCM6328
|
||||
LED HW controller accessed via MMIO registers.
|
||||
|
||||
config LEDS_BCM6358
|
||||
tristate "LED Support for Broadcom BCM6358"
|
||||
depends on LEDS_CLASS
|
||||
depends on OF
|
||||
help
|
||||
This option enables support for LEDs connected to the BCM6358
|
||||
LED HW controller accessed via MMIO registers.
|
||||
|
||||
config LEDS_LM3530
|
||||
tristate "LCD Backlight driver for LM3530"
|
||||
depends on LEDS_CLASS
|
||||
@ -179,7 +205,7 @@ config LEDS_PCA9532_GPIO
|
||||
config LEDS_GPIO
|
||||
tristate "LED Support for GPIO connected LEDs"
|
||||
depends on LEDS_CLASS
|
||||
depends on GPIOLIB
|
||||
depends on GPIOLIB || COMPILE_TEST
|
||||
help
|
||||
This option enables support for the LEDs connected to GPIO
|
||||
outputs. To be useful the particular board must have LEDs
|
||||
@ -203,6 +229,7 @@ config LEDS_LP55XX_COMMON
|
||||
tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501"
|
||||
depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501
|
||||
select FW_LOADER
|
||||
select FW_LOADER_USER_HELPER_FALLBACK
|
||||
help
|
||||
This option supports common operations for LP5521/5523/55231/5562/8501
|
||||
devices.
|
||||
@ -464,6 +491,25 @@ config LEDS_TCA6507
|
||||
LED driver chips accessed via the I2C bus.
|
||||
Driver support brightness control and hardware-assisted blinking.
|
||||
|
||||
config LEDS_TLC591XX
|
||||
tristate "LED driver for TLC59108 and TLC59116 controllers"
|
||||
depends on LEDS_CLASS && I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
This option enables support for Texas Instruments TLC59108
|
||||
and TLC59116 LED controllers.
|
||||
|
||||
config LEDS_MAX77693
|
||||
tristate "LED support for MAX77693 Flash"
|
||||
depends on LEDS_CLASS_FLASH
|
||||
depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
|
||||
depends on MFD_MAX77693
|
||||
depends on OF
|
||||
help
|
||||
This option enables support for the flash part of the MAX77693
|
||||
multifunction device. It has build in control for two leds in flash
|
||||
and torch mode.
|
||||
|
||||
config LEDS_MAX8997
|
||||
tristate "LED support for MAX8997 PMIC"
|
||||
depends on LEDS_CLASS && MFD_MAX8997
|
||||
@ -495,6 +541,15 @@ config LEDS_MENF21BMC
|
||||
This driver can also be built as a module. If so the module
|
||||
will be called leds-menf21bmc.
|
||||
|
||||
config LEDS_KTD2692
|
||||
tristate "LED support for KTD2692 flash LED controller"
|
||||
depends on LEDS_CLASS_FLASH && GPIOLIB && OF
|
||||
help
|
||||
This option enables support for KTD2692 LED flash connected
|
||||
through ExpressWire interface.
|
||||
|
||||
Say Y to enable this driver.
|
||||
|
||||
comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
|
||||
|
||||
config LEDS_BLINKM
|
||||
|
@ -7,6 +7,9 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
|
||||
|
||||
# LED Platform Drivers
|
||||
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
|
||||
obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
|
||||
obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
|
||||
obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
|
||||
obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
|
||||
obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
|
||||
obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
|
||||
@ -31,6 +34,7 @@ obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o
|
||||
obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o
|
||||
obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o
|
||||
obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o
|
||||
obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o
|
||||
obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o
|
||||
obj-$(CONFIG_LEDS_IPAQ_MICRO) += leds-ipaq-micro.o
|
||||
obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
|
||||
@ -52,6 +56,7 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
|
||||
obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
|
||||
obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
|
||||
obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
|
||||
obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
|
||||
obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
|
||||
obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
|
||||
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
|
||||
@ -59,6 +64,7 @@ obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
|
||||
obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o
|
||||
obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o
|
||||
obj-$(CONFIG_LEDS_PM8941_WLED) += leds-pm8941-wled.o
|
||||
obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o
|
||||
|
||||
# LED SPI Drivers
|
||||
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
|
||||
|
@ -121,6 +121,11 @@ static void led_timer_function(unsigned long data)
|
||||
brightness = led_get_brightness(led_cdev);
|
||||
if (!brightness) {
|
||||
/* Time to switch the LED on. */
|
||||
if (led_cdev->delayed_set_value) {
|
||||
led_cdev->blink_brightness =
|
||||
led_cdev->delayed_set_value;
|
||||
led_cdev->delayed_set_value = 0;
|
||||
}
|
||||
brightness = led_cdev->blink_brightness;
|
||||
delay = led_cdev->blink_delay_on;
|
||||
} else {
|
||||
|
@ -119,10 +119,11 @@ void led_set_brightness(struct led_classdev *led_cdev,
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/* delay brightness setting if need to stop soft-blink timer */
|
||||
/* delay brightness if soft-blink is active */
|
||||
if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) {
|
||||
led_cdev->delayed_set_value = brightness;
|
||||
schedule_work(&led_cdev->set_brightness_work);
|
||||
if (brightness == LED_OFF)
|
||||
schedule_work(&led_cdev->set_brightness_work);
|
||||
return;
|
||||
}
|
||||
|
||||
|
576
drivers/leds/leds-aat1290.c
Normal file
576
drivers/leds/leds-aat1290.c
Normal file
@ -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");
|
413
drivers/leds/leds-bcm6328.c
Normal file
413
drivers/leds/leds-bcm6328.c
Normal file
@ -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");
|
243
drivers/leds/leds-bcm6358.c
Normal file
243
drivers/leds/leds-bcm6358.c
Normal file
@ -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");
|
@ -108,20 +108,8 @@ err_null:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int cobalt_raq_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
led_classdev_unregister(&raq_power_off_led);
|
||||
led_classdev_unregister(&raq_web_led);
|
||||
|
||||
if (led_port)
|
||||
led_port = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver cobalt_raq_led_driver = {
|
||||
.probe = cobalt_raq_led_probe,
|
||||
.remove = cobalt_raq_led_remove,
|
||||
.driver = {
|
||||
.name = "cobalt-raq-leds",
|
||||
},
|
||||
@ -131,5 +119,4 @@ static int __init cobalt_raq_led_init(void)
|
||||
{
|
||||
return platform_driver_register(&cobalt_raq_led_driver);
|
||||
}
|
||||
|
||||
module_init(cobalt_raq_led_init);
|
||||
device_initcall(cobalt_raq_led_init);
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/slab.h>
|
||||
@ -198,8 +199,10 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
|
||||
} else {
|
||||
if (IS_ENABLED(CONFIG_OF) && !led.name && np)
|
||||
led.name = np->name;
|
||||
if (!led.name)
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (!led.name) {
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
fwnode_property_read_string(child, "linux,default-trigger",
|
||||
&led.default_trigger);
|
||||
@ -217,18 +220,19 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
|
||||
if (fwnode_property_present(child, "retain-state-suspended"))
|
||||
led.retain_state_suspended = 1;
|
||||
|
||||
ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
|
||||
ret = create_gpio_led(&led, &priv->leds[priv->num_leds],
|
||||
dev, NULL);
|
||||
if (ret < 0) {
|
||||
fwnode_handle_put(child);
|
||||
goto err;
|
||||
}
|
||||
priv->num_leds++;
|
||||
}
|
||||
|
||||
return priv;
|
||||
|
||||
err:
|
||||
for (count = priv->num_leds - 2; count >= 0; count--)
|
||||
for (count = priv->num_leds - 1; count >= 0; count--)
|
||||
delete_gpio_led(&priv->leds[count]);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
443
drivers/leds/leds-ktd2692.c
Normal file
443
drivers/leds/leds-ktd2692.c
Normal file
@ -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");
|
@ -50,6 +50,7 @@
|
||||
#define LP5523_REG_OP_MODE 0x01
|
||||
#define LP5523_REG_ENABLE_LEDS_MSB 0x04
|
||||
#define LP5523_REG_ENABLE_LEDS_LSB 0x05
|
||||
#define LP5523_REG_LED_CTRL_BASE 0x06
|
||||
#define LP5523_REG_LED_PWM_BASE 0x16
|
||||
#define LP5523_REG_LED_CURRENT_BASE 0x26
|
||||
#define LP5523_REG_CONFIG 0x36
|
||||
@ -57,6 +58,7 @@
|
||||
#define LP5523_REG_RESET 0x3D
|
||||
#define LP5523_REG_LED_TEST_CTRL 0x41
|
||||
#define LP5523_REG_LED_TEST_ADC 0x42
|
||||
#define LP5523_REG_MASTER_FADER_BASE 0x48
|
||||
#define LP5523_REG_CH1_PROG_START 0x4C
|
||||
#define LP5523_REG_CH2_PROG_START 0x4D
|
||||
#define LP5523_REG_CH3_PROG_START 0x4E
|
||||
@ -78,6 +80,9 @@
|
||||
#define LP5523_EXT_CLK_USED 0x08
|
||||
#define LP5523_ENG_STATUS_MASK 0x07
|
||||
|
||||
#define LP5523_FADER_MAPPING_MASK 0xC0
|
||||
#define LP5523_FADER_MAPPING_SHIFT 6
|
||||
|
||||
/* Memory Page Selection */
|
||||
#define LP5523_PAGE_ENG1 0
|
||||
#define LP5523_PAGE_ENG2 1
|
||||
@ -666,6 +671,137 @@ release_lock:
|
||||
return pos;
|
||||
}
|
||||
|
||||
#define show_fader(nr) \
|
||||
static ssize_t show_master_fader##nr(struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
return show_master_fader(dev, attr, buf, nr); \
|
||||
}
|
||||
|
||||
#define store_fader(nr) \
|
||||
static ssize_t store_master_fader##nr(struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
const char *buf, size_t len) \
|
||||
{ \
|
||||
return store_master_fader(dev, attr, buf, len, nr); \
|
||||
}
|
||||
|
||||
static ssize_t show_master_fader(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf, int nr)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
ret = lp55xx_read(chip, LP5523_REG_MASTER_FADER_BASE + nr - 1, &val);
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
if (ret == 0)
|
||||
ret = sprintf(buf, "%u\n", val);
|
||||
|
||||
return ret;
|
||||
}
|
||||
show_fader(1)
|
||||
show_fader(2)
|
||||
show_fader(3)
|
||||
|
||||
static ssize_t store_master_fader(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len, int nr)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
int ret;
|
||||
unsigned long val;
|
||||
|
||||
if (kstrtoul(buf, 0, &val))
|
||||
return -EINVAL;
|
||||
|
||||
if (val > 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
ret = lp55xx_write(chip, LP5523_REG_MASTER_FADER_BASE + nr - 1,
|
||||
(u8)val);
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
if (ret == 0)
|
||||
ret = len;
|
||||
|
||||
return ret;
|
||||
}
|
||||
store_fader(1)
|
||||
store_fader(2)
|
||||
store_fader(3)
|
||||
|
||||
static ssize_t show_master_fader_leds(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
int i, ret, pos = 0;
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
for (i = 0; i < LP5523_MAX_LEDS; i++) {
|
||||
ret = lp55xx_read(chip, LP5523_REG_LED_CTRL_BASE + i, &val);
|
||||
if (ret)
|
||||
goto leave;
|
||||
|
||||
val = (val & LP5523_FADER_MAPPING_MASK)
|
||||
>> LP5523_FADER_MAPPING_SHIFT;
|
||||
if (val > 3) {
|
||||
ret = -EINVAL;
|
||||
goto leave;
|
||||
}
|
||||
buf[pos++] = val + '0';
|
||||
}
|
||||
buf[pos++] = '\n';
|
||||
ret = pos;
|
||||
leave:
|
||||
mutex_unlock(&chip->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t store_master_fader_leds(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
int i, n, ret;
|
||||
u8 val;
|
||||
|
||||
n = min_t(int, len, LP5523_MAX_LEDS);
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
if (buf[i] >= '0' && buf[i] <= '3') {
|
||||
val = (buf[i] - '0') << LP5523_FADER_MAPPING_SHIFT;
|
||||
ret = lp55xx_update_bits(chip,
|
||||
LP5523_REG_LED_CTRL_BASE + i,
|
||||
LP5523_FADER_MAPPING_MASK,
|
||||
val);
|
||||
if (ret)
|
||||
goto leave;
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
goto leave;
|
||||
}
|
||||
}
|
||||
ret = len;
|
||||
leave:
|
||||
mutex_unlock(&chip->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void lp5523_led_brightness_work(struct work_struct *work)
|
||||
{
|
||||
struct lp55xx_led *led = container_of(work, struct lp55xx_led,
|
||||
@ -688,6 +824,14 @@ static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load);
|
||||
static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load);
|
||||
static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load);
|
||||
static LP55XX_DEV_ATTR_RO(selftest, lp5523_selftest);
|
||||
static LP55XX_DEV_ATTR_RW(master_fader1, show_master_fader1,
|
||||
store_master_fader1);
|
||||
static LP55XX_DEV_ATTR_RW(master_fader2, show_master_fader2,
|
||||
store_master_fader2);
|
||||
static LP55XX_DEV_ATTR_RW(master_fader3, show_master_fader3,
|
||||
store_master_fader3);
|
||||
static LP55XX_DEV_ATTR_RW(master_fader_leds, show_master_fader_leds,
|
||||
store_master_fader_leds);
|
||||
|
||||
static struct attribute *lp5523_attributes[] = {
|
||||
&dev_attr_engine1_mode.attr,
|
||||
@ -700,6 +844,10 @@ static struct attribute *lp5523_attributes[] = {
|
||||
&dev_attr_engine2_leds.attr,
|
||||
&dev_attr_engine3_leds.attr,
|
||||
&dev_attr_selftest.attr,
|
||||
&dev_attr_master_fader1.attr,
|
||||
&dev_attr_master_fader2.attr,
|
||||
&dev_attr_master_fader3.attr,
|
||||
&dev_attr_master_fader_leds.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
@ -223,7 +223,7 @@ static int lp55xx_request_firmware(struct lp55xx_chip *chip)
|
||||
const char *name = chip->cl->name;
|
||||
struct device *dev = &chip->cl->dev;
|
||||
|
||||
return request_firmware_nowait(THIS_MODULE, true, name, dev,
|
||||
return request_firmware_nowait(THIS_MODULE, false, name, dev,
|
||||
GFP_KERNEL, chip, lp55xx_firmware_loaded);
|
||||
}
|
||||
|
||||
|
1097
drivers/leds/leds-max77693.c
Normal file
1097
drivers/leds/leds-max77693.c
Normal file
File diff suppressed because it is too large
Load Diff
300
drivers/leds/leds-tlc591xx.c
Normal file
300
drivers/leds/leds-tlc591xx.c
Normal file
@ -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");
|
@ -13,7 +13,6 @@
|
||||
#ifndef __LEDS_H_INCLUDED
|
||||
#define __LEDS_H_INCLUDED
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
@ -50,27 +49,4 @@ void led_stop_software_blink(struct led_classdev *led_cdev);
|
||||
extern struct rw_semaphore leds_list_lock;
|
||||
extern struct list_head leds_list;
|
||||
|
||||
#ifdef CONFIG_LEDS_TRIGGERS
|
||||
void led_trigger_set_default(struct led_classdev *led_cdev);
|
||||
void led_trigger_set(struct led_classdev *led_cdev,
|
||||
struct led_trigger *trigger);
|
||||
void led_trigger_remove(struct led_classdev *led_cdev);
|
||||
|
||||
static inline void *led_get_trigger_data(struct led_classdev *led_cdev)
|
||||
{
|
||||
return led_cdev->trigger_data;
|
||||
}
|
||||
|
||||
#else
|
||||
#define led_trigger_set_default(x) do {} while (0)
|
||||
#define led_trigger_set(x, y) do {} while (0)
|
||||
#define led_trigger_remove(x) do {} while (0)
|
||||
#define led_get_trigger_data(x) (NULL)
|
||||
#endif
|
||||
|
||||
ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf);
|
||||
|
||||
#endif /* __LEDS_H_INCLUDED */
|
||||
|
@ -44,6 +44,17 @@ config V4L2_MEM2MEM_DEV
|
||||
tristate
|
||||
depends on VIDEOBUF2_CORE
|
||||
|
||||
# Used by LED subsystem flash drivers
|
||||
config V4L2_FLASH_LED_CLASS
|
||||
tristate "V4L2 flash API for LED flash class devices"
|
||||
depends on VIDEO_V4L2_SUBDEV_API
|
||||
depends on LEDS_CLASS_FLASH
|
||||
---help---
|
||||
Say Y here to enable V4L2 flash API support for LED flash
|
||||
class drivers.
|
||||
|
||||
When in doubt, say N.
|
||||
|
||||
# Used by drivers that need Videobuf modules
|
||||
config VIDEOBUF_GEN
|
||||
tristate
|
||||
|
@ -22,6 +22,8 @@ obj-$(CONFIG_VIDEO_TUNER) += tuner.o
|
||||
|
||||
obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o
|
||||
|
||||
obj-$(CONFIG_V4L2_FLASH_LED_CLASS) += v4l2-flash-led-class.o
|
||||
|
||||
obj-$(CONFIG_VIDEOBUF_GEN) += videobuf-core.o
|
||||
obj-$(CONFIG_VIDEOBUF_DMA_SG) += videobuf-dma-sg.o
|
||||
obj-$(CONFIG_VIDEOBUF_DMA_CONTIG) += videobuf-dma-contig.o
|
||||
|
@ -22,10 +22,10 @@
|
||||
#include <media/v4l2-device.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
|
||||
static bool match_i2c(struct device *dev, struct v4l2_async_subdev *asd)
|
||||
static bool match_i2c(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
|
||||
{
|
||||
#if IS_ENABLED(CONFIG_I2C)
|
||||
struct i2c_client *client = i2c_verify_client(dev);
|
||||
struct i2c_client *client = i2c_verify_client(sd->dev);
|
||||
return client &&
|
||||
asd->match.i2c.adapter_id == client->adapter->nr &&
|
||||
asd->match.i2c.address == client->addr;
|
||||
@ -34,14 +34,24 @@ static bool match_i2c(struct device *dev, struct v4l2_async_subdev *asd)
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool match_devname(struct device *dev, struct v4l2_async_subdev *asd)
|
||||
static bool match_devname(struct v4l2_subdev *sd,
|
||||
struct v4l2_async_subdev *asd)
|
||||
{
|
||||
return !strcmp(asd->match.device_name.name, dev_name(dev));
|
||||
return !strcmp(asd->match.device_name.name, dev_name(sd->dev));
|
||||
}
|
||||
|
||||
static bool match_of(struct device *dev, struct v4l2_async_subdev *asd)
|
||||
static bool match_of(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
|
||||
{
|
||||
return dev->of_node == asd->match.of.node;
|
||||
return sd->of_node == asd->match.of.node;
|
||||
}
|
||||
|
||||
static bool match_custom(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
|
||||
{
|
||||
if (!asd->match.custom.match)
|
||||
/* Match always */
|
||||
return true;
|
||||
|
||||
return asd->match.custom.match(sd->dev, asd);
|
||||
}
|
||||
|
||||
static LIST_HEAD(subdev_list);
|
||||
@ -51,17 +61,14 @@ static DEFINE_MUTEX(list_lock);
|
||||
static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *notifier,
|
||||
struct v4l2_subdev *sd)
|
||||
{
|
||||
bool (*match)(struct v4l2_subdev *, struct v4l2_async_subdev *);
|
||||
struct v4l2_async_subdev *asd;
|
||||
bool (*match)(struct device *, struct v4l2_async_subdev *);
|
||||
|
||||
list_for_each_entry(asd, ¬ifier->waiting, list) {
|
||||
/* bus_type has been verified valid before */
|
||||
switch (asd->match_type) {
|
||||
case V4L2_ASYNC_MATCH_CUSTOM:
|
||||
match = asd->match.custom.match;
|
||||
if (!match)
|
||||
/* Match always */
|
||||
return asd;
|
||||
match = match_custom;
|
||||
break;
|
||||
case V4L2_ASYNC_MATCH_DEVNAME:
|
||||
match = match_devname;
|
||||
@ -79,7 +86,7 @@ static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *
|
||||
}
|
||||
|
||||
/* match cannot be NULL here */
|
||||
if (match(sd->dev, asd))
|
||||
if (match(sd, asd))
|
||||
return asd;
|
||||
}
|
||||
|
||||
@ -266,6 +273,14 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd)
|
||||
{
|
||||
struct v4l2_async_notifier *notifier;
|
||||
|
||||
/*
|
||||
* No reference taken. The reference is held by the device
|
||||
* (struct v4l2_subdev.dev), and async sub-device does not
|
||||
* exist independently of the device at any point of time.
|
||||
*/
|
||||
if (!sd->of_node && sd->dev)
|
||||
sd->of_node = sd->dev->of_node;
|
||||
|
||||
mutex_lock(&list_lock);
|
||||
|
||||
INIT_LIST_HEAD(&sd->async_list);
|
||||
|
710
drivers/media/v4l2-core/v4l2-flash-led-class.c
Normal file
710
drivers/media/v4l2-core/v4l2-flash-led-class.c
Normal file
@ -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");
|
@ -407,6 +407,21 @@ static inline int desc_to_gpio(const struct gpio_desc *desc)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Child properties interface */
|
||||
struct fwnode_handle;
|
||||
|
||||
static inline struct gpio_desc *fwnode_get_named_gpiod(
|
||||
struct fwnode_handle *fwnode, const char *propname)
|
||||
{
|
||||
return ERR_PTR(-ENOSYS);
|
||||
}
|
||||
|
||||
static inline struct gpio_desc *devm_get_gpiod_from_child(
|
||||
struct device *dev, const char *con_id, struct fwnode_handle *child)
|
||||
{
|
||||
return ERR_PTR(-ENOSYS);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_GPIOLIB */
|
||||
|
||||
/*
|
||||
|
@ -12,6 +12,7 @@
|
||||
#ifndef __LINUX_LEDS_H_INCLUDED
|
||||
#define __LINUX_LEDS_H_INCLUDED
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/rwsem.h>
|
||||
@ -222,6 +223,11 @@ struct led_trigger {
|
||||
struct list_head next_trig;
|
||||
};
|
||||
|
||||
ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf);
|
||||
|
||||
/* Registration functions for complex triggers */
|
||||
extern int led_trigger_register(struct led_trigger *trigger);
|
||||
extern void led_trigger_unregister(struct led_trigger *trigger);
|
||||
@ -238,6 +244,16 @@ extern void led_trigger_blink_oneshot(struct led_trigger *trigger,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off,
|
||||
int invert);
|
||||
extern void led_trigger_set_default(struct led_classdev *led_cdev);
|
||||
extern void led_trigger_set(struct led_classdev *led_cdev,
|
||||
struct led_trigger *trigger);
|
||||
extern void led_trigger_remove(struct led_classdev *led_cdev);
|
||||
|
||||
static inline void *led_get_trigger_data(struct led_classdev *led_cdev)
|
||||
{
|
||||
return led_cdev->trigger_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* led_trigger_rename_static - rename a trigger
|
||||
* @name: the new trigger name
|
||||
@ -267,6 +283,15 @@ static inline void led_trigger_register_simple(const char *name,
|
||||
static inline void led_trigger_unregister_simple(struct led_trigger *trigger) {}
|
||||
static inline void led_trigger_event(struct led_trigger *trigger,
|
||||
enum led_brightness event) {}
|
||||
static inline void led_trigger_set_default(struct led_classdev *led_cdev) {}
|
||||
static inline void led_trigger_set(struct led_classdev *led_cdev,
|
||||
struct led_trigger *trigger) {}
|
||||
static inline void led_trigger_remove(struct led_classdev *led_cdev) {}
|
||||
static inline void *led_get_trigger_data(struct led_classdev *led_cdev)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_LEDS_TRIGGERS */
|
||||
|
||||
/* Trigger specific functions */
|
||||
|
148
include/media/v4l2-flash-led-class.h
Normal file
148
include/media/v4l2-flash-led-class.h
Normal file
@ -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 */
|
@ -605,6 +605,8 @@ struct v4l2_subdev {
|
||||
struct video_device *devnode;
|
||||
/* pointer to the physical device, if any */
|
||||
struct device *dev;
|
||||
/* The device_node of the subdev, usually the same as dev->of_node. */
|
||||
struct device_node *of_node;
|
||||
/* Links this subdev to a global subdev_list or @notifier->done list. */
|
||||
struct list_head async_list;
|
||||
/* Pointer to respective struct v4l2_async_subdev. */
|
||||
|
Loading…
Reference in New Issue
Block a user