Commit 13d45f79 authored by Linus Torvalds's avatar Linus Torvalds

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
  ...
parents 05fde26a b6789320
* 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>;
};
};
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";
};
};
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";
};
};
* 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>;
};
};
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. ...@@ -114,6 +114,7 @@ isee ISEE 2007 S.L.
isil Intersil isil Intersil
karo Ka-Ro electronics GmbH karo Ka-Ro electronics GmbH
keymile Keymile GmbH keymile Keymile GmbH
kinetic Kinetic Technologies
lacie LaCie lacie LaCie
lantiq Lantiq Semiconductor lantiq Lantiq Semiconductor
lenovo Lenovo Group Ltd. lenovo Lenovo Group Ltd.
......
...@@ -20,3 +20,54 @@ Following sysfs attributes are exposed for controlling flash LED devices: ...@@ -20,3 +20,54 @@ Following sysfs attributes are exposed for controlling flash LED devices:
- max_flash_timeout - max_flash_timeout
- flash_strobe - flash_strobe
- flash_fault - 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. ...@@ -49,6 +49,36 @@ There are two ways to run LED patterns.
2) Firmware interface - LP55xx common interface 2) Firmware interface - LP55xx common interface
For the details, please refer to 'firmware' section in leds-lp55xx.txt 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. Selftest uses always the current from the platform data.
Each channel contains led current settings. Each channel contains led current settings.
......
...@@ -39,6 +39,32 @@ config LEDS_88PM860X ...@@ -39,6 +39,32 @@ config LEDS_88PM860X
This option enables support for on-chip LED drivers found on Marvell This option enables support for on-chip LED drivers found on Marvell
Semiconductor 88PM8606 PMIC. 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 config LEDS_LM3530
tristate "LCD Backlight driver for LM3530" tristate "LCD Backlight driver for LM3530"
depends on LEDS_CLASS depends on LEDS_CLASS
...@@ -179,7 +205,7 @@ config LEDS_PCA9532_GPIO ...@@ -179,7 +205,7 @@ config LEDS_PCA9532_GPIO
config LEDS_GPIO config LEDS_GPIO
tristate "LED Support for GPIO connected LEDs" tristate "LED Support for GPIO connected LEDs"
depends on LEDS_CLASS depends on LEDS_CLASS
depends on GPIOLIB depends on GPIOLIB || COMPILE_TEST
help help
This option enables support for the LEDs connected to GPIO This option enables support for the LEDs connected to GPIO
outputs. To be useful the particular board must have LEDs outputs. To be useful the particular board must have LEDs
...@@ -203,6 +229,7 @@ config LEDS_LP55XX_COMMON ...@@ -203,6 +229,7 @@ config LEDS_LP55XX_COMMON
tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501" tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501"
depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501 depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501
select FW_LOADER select FW_LOADER
select FW_LOADER_USER_HELPER_FALLBACK
help help
This option supports common operations for LP5521/5523/55231/5562/8501 This option supports common operations for LP5521/5523/55231/5562/8501
devices. devices.
...@@ -464,6 +491,25 @@ config LEDS_TCA6507 ...@@ -464,6 +491,25 @@ config LEDS_TCA6507
LED driver chips accessed via the I2C bus. LED driver chips accessed via the I2C bus.
Driver support brightness control and hardware-assisted blinking. 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 config LEDS_MAX8997
tristate "LED support for MAX8997 PMIC" tristate "LED support for MAX8997 PMIC"
depends on LEDS_CLASS && MFD_MAX8997 depends on LEDS_CLASS && MFD_MAX8997
...@@ -495,6 +541,15 @@ config LEDS_MENF21BMC ...@@ -495,6 +541,15 @@ config LEDS_MENF21BMC
This driver can also be built as a module. If so the module This driver can also be built as a module. If so the module
will be called leds-menf21bmc. 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)" comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
config LEDS_BLINKM config LEDS_BLINKM
......
...@@ -7,6 +7,9 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o ...@@ -7,6 +7,9 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
# LED Platform Drivers # LED Platform Drivers
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o 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_BD2802) += leds-bd2802.o
obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
...@@ -31,6 +34,7 @@ obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o ...@@ -31,6 +34,7 @@ obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o
obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o
obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o
obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.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_CLEVO_MAIL) += leds-clevo-mail.o
obj-$(CONFIG_LEDS_IPAQ_MICRO) += leds-ipaq-micro.o obj-$(CONFIG_LEDS_IPAQ_MICRO) += leds-ipaq-micro.o
obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
...@@ -52,6 +56,7 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o ...@@ -52,6 +56,7 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
obj-$(CONFIG_LEDS_NS2) += leds-ns2.o obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.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_MAX8997) += leds-max8997.o
obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
...@@ -59,6 +64,7 @@ obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o ...@@ -59,6 +64,7 @@ obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o
obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o
obj-$(CONFIG_LEDS_PM8941_WLED) += leds-pm8941-wled.o obj-$(CONFIG_LEDS_PM8941_WLED) += leds-pm8941-wled.o
obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o
# LED SPI Drivers # LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
......
...@@ -121,6 +121,11 @@ static void led_timer_function(unsigned long data) ...@@ -121,6 +121,11 @@ static void led_timer_function(unsigned long data)
brightness = led_get_brightness(led_cdev); brightness = led_get_brightness(led_cdev);
if (!brightness) { if (!brightness) {
/* Time to switch the LED on. */ /* 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; brightness = led_cdev->blink_brightness;
delay = led_cdev->blink_delay_on; delay = led_cdev->blink_delay_on;
} else { } else {
......
...@@ -119,10 +119,11 @@ void led_set_brightness(struct led_classdev *led_cdev, ...@@ -119,10 +119,11 @@ void led_set_brightness(struct led_classdev *led_cdev,
{ {
int ret = 0; 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) { if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) {
led_cdev->delayed_set_value = brightness; 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; return;
} }
......
/*
* 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");
/*
* Driver for BCM6328 memory-mapped LEDs, based on leds-syscon.c
*
* Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
* Copyright 2015 Jonas Gorski <jogo@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/io.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#define BCM6328_REG_INIT 0x00
#define BCM6328_REG_MODE_HI 0x04
#define BCM6328_REG_MODE_LO 0x08
#define BCM6328_REG_HWDIS 0x0c
#define BCM6328_REG_STROBE 0x10
#define BCM6328_REG_LNKACTSEL_HI 0x14
#define BCM6328_REG_LNKACTSEL_LO 0x18
#define BCM6328_REG_RBACK 0x1c
#define BCM6328_REG_SERMUX 0x20
#define BCM6328_LED_MAX_COUNT 24
#define BCM6328_LED_DEF_DELAY 500
#define BCM6328_LED_INTERVAL_MS 20
#define BCM6328_LED_INTV_MASK 0x3f
#define BCM6328_LED_FAST_INTV_SHIFT 6
#define BCM6328_LED_FAST_INTV_MASK (BCM6328_LED_INTV_MASK << \
BCM6328_LED_FAST_INTV_SHIFT)
#define BCM6328_SERIAL_LED_EN BIT(12)
#define BCM6328_SERIAL_LED_MUX BIT(13)
#define BCM6328_SERIAL_LED_CLK_NPOL BIT(14)
#define BCM6328_SERIAL_LED_DATA_PPOL BIT(15)
#define BCM6328_SERIAL_LED_SHIFT_DIR BIT(16)
#define BCM6328_LED_SHIFT_TEST BIT(30)
#define BCM6328_LED_TEST BIT(31)
#define BCM6328_LED_MODE_MASK 3
#define BCM6328_LED_MODE_OFF 0
#define BCM6328_LED_MODE_FAST 1
#define BCM6328_LED_MODE_BLINK 2
#define BCM6328_LED_MODE_ON 3
#define BCM6328_LED_SHIFT(X) ((X) << 1)
/**
* struct bcm6328_led - state container for bcm6328 based LEDs
* @cdev: LED class device for this LED
* @mem: memory resource
* @lock: memory lock
* @pin: LED pin number
* @blink_leds: blinking LEDs
* @blink_delay: blinking delay
* @active_low: LED is active low
*/
struct bcm6328_led {
struct led_classdev cdev;
void __iomem *mem;
spinlock_t *lock;
unsigned long pin;
unsigned long *blink_leds;
unsigned long *blink_delay;
bool active_low;
};
static void bcm6328_led_write(void __iomem *reg, unsigned long data)
{
iowrite32be(data, reg);
}
static unsigned long bcm6328_led_read(void __iomem *reg)
{
return ioread32be(reg);
}
/**
* LEDMode 64 bits / 24 LEDs
* bits [31:0] -> LEDs 8-23
* bits [47:32] -> LEDs 0-7
* bits [63:48] -> unused
*/
static unsigned long bcm6328_pin2shift(unsigned long pin)
{
if (pin < 8)
return pin + 16; /* LEDs 0-7 (bits 47:32) */
else
return pin - 8; /* LEDs 8-23 (bits 31:0) */
}
static void bcm6328_led_mode(struct bcm6328_led *led, unsigned long value)
{
void __iomem *mode;
unsigned long val, shift;
shift = bcm6328_pin2shift(led->pin);
if (shift / 16)
mode = led->mem + BCM6328_REG_MODE_HI;
else
mode = led->mem + BCM6328_REG_MODE_LO;
val = bcm6328_led_read(mode);
val &= ~(BCM6328_LED_MODE_MASK << BCM6328_LED_SHIFT(shift % 16));
val |= (value << BCM6328_LED_SHIFT(shift % 16));
bcm6328_led_write(mode, val);
}
static void bcm6328_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct bcm6328_led *led =
container_of(led_cdev, struct bcm6328_led, cdev);
unsigned long flags;
spin_lock_irqsave(led->lock, flags);
*(led->blink_leds) &= ~BIT(led->pin);
if ((led->active_low && value == LED_OFF) ||
(!led->active_low && value != LED_OFF))
bcm6328_led_mode(led, BCM6328_LED_MODE_OFF);
else
bcm6328_led_mode(led, BCM6328_LED_MODE_ON);
spin_unlock_irqrestore(led->lock, flags);
}
static int bcm6328_blink_set(struct led_classdev *led_cdev,
unsigned long *delay_on, unsigned long *delay_off)
{
struct bcm6328_led *led =
container_of(led_cdev, struct bcm6328_led, cdev);
unsigned long delay, flags;
if (!*delay_on)
*delay_on = BCM6328_LED_DEF_DELAY;
if (!*delay_off)
*delay_off = BCM6328_LED_DEF_DELAY;
if (*delay_on != *delay_off) {
dev_dbg(led_cdev->dev,
"fallback to soft blinking (delay_on != delay_off)\n");
return -EINVAL;
}
delay = *delay_on / BCM6328_LED_INTERVAL_MS;
if (delay == 0)
delay = 1;
else if (delay > BCM6328_LED_INTV_MASK) {
dev_dbg(led_cdev->dev,
"fallback to soft blinking (delay > %ums)\n",
BCM6328_LED_INTV_MASK * BCM6328_LED_INTERVAL_MS);
return -EINVAL;
}
spin_lock_irqsave(led->lock, flags);
if (*(led->blink_leds) == 0 ||
*(led->blink_leds) == BIT(led->pin) ||
*(led->blink_delay) == delay) {
unsigned long val;
*(led->blink_leds) |= BIT(led->pin);
*(led->blink_delay) = delay;
val = bcm6328_led_read(led->mem + BCM6328_REG_INIT);
val &= ~BCM6328_LED_FAST_INTV_MASK;
val |= (delay << BCM6328_LED_FAST_INTV_SHIFT);
bcm6328_led_write(led->mem + BCM6328_REG_INIT, val);
bcm6328_led_mode(led, BCM6328_LED_MODE_BLINK);
spin_unlock_irqrestore(led->lock, flags);
} else {
spin_unlock_irqrestore(led->lock, flags);
dev_dbg(led_cdev->dev,
"fallback to soft blinking (delay already set)\n");
return -EINVAL;
}
return 0;
}
static int bcm6328_hwled(struct device *dev, struct device_node *nc, u32 reg,
void __iomem *mem, spinlock_t *lock)
{
int i, cnt;
unsigned long flags, val;
spin_lock_irqsave(lock, flags);
val = bcm6328_led_read(mem + BCM6328_REG_HWDIS);
val &= ~BIT(reg);
bcm6328_led_write(mem + BCM6328_REG_HWDIS, val);
spin_unlock_irqrestore(lock, flags);
/* Only LEDs 0-7 can be activity/link controlled */
if (reg >= 8)
return 0;
cnt = of_property_count_elems_of_size(nc, "brcm,link-signal-sources",
sizeof(u32));
for (i = 0; i < cnt; i++) {
u32 sel;
void __iomem *addr;
if (reg < 4)
addr = mem + BCM6328_REG_LNKACTSEL_LO;
else
addr = mem + BCM6328_REG_LNKACTSEL_HI;
of_property_read_u32_index(nc, "brcm,link-signal-sources", i,
&sel);
if (reg / 4 != sel / 4) {
dev_warn(dev, "invalid link signal source\n");
continue;
}
spin_lock_irqsave(lock, flags);
val = bcm6328_led_read(addr);
val |= (BIT(reg) << (((sel % 4) * 4) + 16));
bcm6328_led_write(addr, val);
spin_unlock_irqrestore(lock, flags);
}
cnt = of_property_count_elems_of_size(nc,
"brcm,activity-signal-sources",
sizeof(u32));
for (i = 0; i < cnt; i++) {
u32 sel;
void __iomem *addr;
if (reg < 4)
addr = mem + BCM6328_REG_LNKACTSEL_LO;
else
addr = mem + BCM6328_REG_LNKACTSEL_HI;
of_property_read_u32_index(nc, "brcm,activity-signal-sources",
i, &sel);
if (reg / 4 != sel / 4) {
dev_warn(dev, "invalid activity signal source\n");
continue;
}
spin_lock_irqsave(lock, flags);
val = bcm6328_led_read(addr);
val |= (BIT(reg) << ((sel % 4) * 4));
bcm6328_led_write(addr, val);
spin_unlock_irqrestore(lock, flags);
}
return 0;
}
static int bcm6328_led(struct device *dev, struct device_node *nc, u32 reg,
void __iomem *mem, spinlock_t *lock,
unsigned long *blink_leds, unsigned long *blink_delay)
{
struct bcm6328_led *led;
unsigned long flags;
const char *state;
int rc;
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
led->pin = reg;
led->mem = mem;
led->lock = lock;
led->blink_leds = blink_leds;
led->blink_delay = blink_delay;
if (of_property_read_bool(nc, "active-low"))
led->active_low = true;
led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name;
led->cdev.default_trigger = of_get_property(nc,
"linux,default-trigger",
NULL);
if (!of_property_read_string(nc, "default-state", &state)) {
spin_lock_irqsave(lock, flags);
if (!strcmp(state, "on")) {
led->cdev.brightness = LED_FULL;
bcm6328_led_mode(led, BCM6328_LED_MODE_ON);
} else if (!strcmp(state, "keep")) {
void __iomem *mode;
unsigned long val, shift;
shift = bcm6328_pin2shift(led->pin);
if (shift / 16)
mode = mem + BCM6328_REG_MODE_HI;
else
mode = mem + BCM6328_REG_MODE_LO;
val = bcm6328_led_read(mode) >> (shift % 16);
val &= BCM6328_LED_MODE_MASK;
if (val == BCM6328_LED_MODE_ON)
led->cdev.brightness = LED_FULL;
else {
led->cdev.brightness = LED_OFF;
bcm6328_led_mode(led, BCM6328_LED_MODE_OFF);
}
} else {
led->cdev.brightness = LED_OFF;
bcm6328_led_mode(led, BCM6328_LED_MODE_OFF);
}
spin_unlock_irqrestore(lock, flags);
}
led->cdev.brightness_set = bcm6328_led_set;
led->cdev.blink_set = bcm6328_blink_set;
rc = led_classdev_register(dev, &led->cdev);
if (rc < 0)
return rc;
dev_dbg(dev, "registered LED %s\n", led->cdev.name);
return 0;
}
static int bcm6328_leds_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct device_node *child;
struct resource *mem_r;
void __iomem *mem;
spinlock_t *lock;
unsigned long val, *blink_leds, *blink_delay;
mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_r)
return -EINVAL;
mem = devm_ioremap_resource(dev, mem_r);
if (IS_ERR(mem))
return PTR_ERR(mem);
lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
if (!lock)
return -ENOMEM;
blink_leds = devm_kzalloc(dev, sizeof(*blink_leds), GFP_KERNEL);
if (!blink_leds)
return -ENOMEM;
blink_delay = devm_kzalloc(dev, sizeof(*blink_delay), GFP_KERNEL);
if (!blink_delay)
return -ENOMEM;
spin_lock_init(lock);
bcm6328_led_write(mem + BCM6328_REG_HWDIS, ~0);
bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_HI, 0);
bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_LO, 0);
val = bcm6328_led_read(mem + BCM6328_REG_INIT);
val &= ~BCM6328_SERIAL_LED_EN;
if (of_property_read_bool(np, "brcm,serial-leds"))
val |= BCM6328_SERIAL_LED_EN;
bcm6328_led_write(mem + BCM6328_REG_INIT, val);
for_each_available_child_of_node(np, child) {
int rc;
u32 reg;
if (of_property_read_u32(child, "reg", &reg))
continue;
if (reg >= BCM6328_LED_MAX_COUNT) {
dev_err(dev, "invalid LED (>= %d)\n",
BCM6328_LED_MAX_COUNT);
continue;
}
if (of_property_read_bool(child, "brcm,hardware-controlled"))
rc = bcm6328_hwled(dev, child, reg, mem, lock);
else
rc = bcm6328_led(dev, child, reg, mem, lock,
blink_leds, blink_delay);
if (rc < 0)
return rc;
}
return 0;
}
static const struct of_device_id bcm6328_leds_of_match[] = {
{ .compatible = "brcm,bcm6328-leds", },
{ },
};
static struct platform_driver bcm6328_leds_driver = {
.probe = bcm6328_leds_probe,
.driver = {
.name = "leds-bcm6328",
.of_match_table = bcm6328_leds_of_match,
},
};
module_platform_driver(bcm6328_leds_driver);
MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
MODULE_DESCRIPTION("LED driver for BCM6328 controllers");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:leds-bcm6328");
/*
* Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c
*
* Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#define BCM6358_REG_MODE 0x0
#define BCM6358_REG_CTRL 0x4
#define BCM6358_SLED_CLKDIV_MASK 3
#define BCM6358_SLED_CLKDIV_1 0
#define BCM6358_SLED_CLKDIV_2 1
#define BCM6358_SLED_CLKDIV_4 2
#define BCM6358_SLED_CLKDIV_8 3
#define BCM6358_SLED_POLARITY BIT(2)
#define BCM6358_SLED_BUSY BIT(3)
#define BCM6358_SLED_MAX_COUNT 32
#define BCM6358_SLED_WAIT 100
/**
* struct bcm6358_led - state container for bcm6358 based LEDs
* @cdev: LED class device for this LED
* @mem: memory resource
* @lock: memory lock
* @pin: LED pin number
* @active_low: LED is active low
*/
struct bcm6358_led {
struct led_classdev cdev;
void __iomem *mem;
spinlock_t *lock;
unsigned long pin;
bool active_low;
};
static void bcm6358_led_write(void __iomem *reg, unsigned long data)
{
iowrite32be(data, reg);
}
static unsigned long bcm6358_led_read(void __iomem *reg)
{
return ioread32be(reg);
}
static unsigned long bcm6358_led_busy(void __iomem *mem)
{
unsigned long val;
while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) &
BCM6358_SLED_BUSY)
udelay(BCM6358_SLED_WAIT);
return val;
}
static void bcm6358_led_mode(struct bcm6358_led *led, unsigned long value)
{
unsigned long val;
bcm6358_led_busy(led->mem);
val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
if ((led->active_low && value == LED_OFF) ||
(!led->active_low && value != LED_OFF))
val |= BIT(led->pin);
else
val &= ~(BIT(led->pin));
bcm6358_led_write(led->mem + BCM6358_REG_MODE, val);
}
static void bcm6358_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct bcm6358_led *led =
container_of(led_cdev, struct bcm6358_led, cdev);
unsigned long flags;
spin_lock_irqsave(led->lock, flags);
bcm6358_led_mode(led, value);
spin_unlock_irqrestore(led->lock, flags);
}
static int bcm6358_led(struct device *dev, struct device_node *nc, u32 reg,
void __iomem *mem, spinlock_t *lock)
{
struct bcm6358_led *led;
unsigned long flags;
const char *state;
int rc;
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
led->pin = reg;
led->mem = mem;
led->lock = lock;
if (of_property_read_bool(nc, "active-low"))
led->active_low = true;
led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name;
led->cdev.default_trigger = of_get_property(nc,
"linux,default-trigger",
NULL);
spin_lock_irqsave(lock, flags);
if (!of_property_read_string(nc, "default-state", &state)) {
if (!strcmp(state, "on")) {
led->cdev.brightness = LED_FULL;
} else if (!strcmp(state, "keep")) {
unsigned long val;
bcm6358_led_busy(led->mem);
val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
val &= BIT(led->pin);
if ((led->active_low && !val) ||
(!led->active_low && val))
led->cdev.brightness = LED_FULL;
else
led->cdev.brightness = LED_OFF;
} else {
led->cdev.brightness = LED_OFF;
}
} else {
led->cdev.brightness = LED_OFF;
}
bcm6358_led_mode(led, led->cdev.brightness);
spin_unlock_irqrestore(lock, flags);
led->cdev.brightness_set = bcm6358_led_set;
rc = led_classdev_register(dev, &led->cdev);
if (rc < 0)
return rc;
dev_dbg(dev, "registered LED %s\n", led->cdev.name);
return 0;
}
static int bcm6358_leds_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct device_node *child;
struct resource *mem_r;
void __iomem *mem;
spinlock_t *lock; /* memory lock */
unsigned long val;
u32 clk_div;
mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_r)
return -EINVAL;
mem = devm_ioremap_resource(dev, mem_r);
if (IS_ERR(mem))
return PTR_ERR(mem);
lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
if (!lock)
return -ENOMEM;
spin_lock_init(lock);
val = bcm6358_led_busy(mem);
val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK);
if (of_property_read_bool(np, "brcm,clk-dat-low"))
val |= BCM6358_SLED_POLARITY;
of_property_read_u32(np, "brcm,clk-div", &clk_div);
switch (clk_div) {
case 8:
val |= BCM6358_SLED_CLKDIV_8;
break;
case 4:
val |= BCM6358_SLED_CLKDIV_4;
break;
case 2:
val |= BCM6358_SLED_CLKDIV_2;
break;
default:
val |= BCM6358_SLED_CLKDIV_1;
break;
}
bcm6358_led_write(mem + BCM6358_REG_CTRL, val);
for_each_available_child_of_node(np, child) {
int rc;
u32 reg;
if (of_property_read_u32(child, "reg", &reg))
continue;
if (reg >= BCM6358_SLED_MAX_COUNT) {
dev_err(dev, "invalid LED (%u >= %d)\n", reg,
BCM6358_SLED_MAX_COUNT);
continue;
}
rc = bcm6358_led(dev, child, reg, mem, lock);
if (rc < 0)
return rc;
}
return 0;
}
static const struct of_device_id bcm6358_leds_of_match[] = {
{ .compatible = "brcm,bcm6358-leds", },
{ },
};
static struct platform_driver bcm6358_leds_driver = {
.probe = bcm6358_leds_probe,
.driver = {
.name = "leds-bcm6358",
.of_match_table = bcm6358_leds_of_match,
},
};
module_platform_driver(bcm6358_leds_driver);
MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
MODULE_DESCRIPTION("LED driver for BCM6358 controllers");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:leds-bcm6358");
...@@ -108,20 +108,8 @@ static int cobalt_raq_led_probe(struct platform_device *pdev) ...@@ -108,20 +108,8 @@ static int cobalt_raq_led_probe(struct platform_device *pdev)
return retval; 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 = { static struct platform_driver cobalt_raq_led_driver = {
.probe = cobalt_raq_led_probe, .probe = cobalt_raq_led_probe,
.remove = cobalt_raq_led_remove,
.driver = { .driver = {
.name = "cobalt-raq-leds", .name = "cobalt-raq-leds",
}, },
...@@ -131,5 +119,4 @@ static int __init cobalt_raq_led_init(void) ...@@ -131,5 +119,4 @@ static int __init cobalt_raq_led_init(void)
{ {
return platform_driver_register(&cobalt_raq_led_driver); return platform_driver_register(&cobalt_raq_led_driver);
} }
device_initcall(cobalt_raq_led_init);
module_init(cobalt_raq_led_init);
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/leds.h> #include <linux/leds.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/property.h> #include <linux/property.h>
#include <linux/slab.h> #include <linux/slab.h>
...@@ -198,8 +199,10 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) ...@@ -198,8 +199,10 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
} else { } else {
if (IS_ENABLED(CONFIG_OF) && !led.name && np) if (IS_ENABLED(CONFIG_OF) && !led.name && np)
led.name = np->name; led.name = np->name;
if (!led.name) if (!led.name) {
return ERR_PTR(-EINVAL); ret = -EINVAL;
goto err;
}
} }
fwnode_property_read_string(child, "linux,default-trigger", fwnode_property_read_string(child, "linux,default-trigger",
&led.default_trigger); &led.default_trigger);
...@@ -217,18 +220,19 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) ...@@ -217,18 +220,19 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
if (fwnode_property_present(child, "retain-state-suspended")) if (fwnode_property_present(child, "retain-state-suspended"))
led.retain_state_suspended = 1; 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); dev, NULL);
if (ret < 0) { if (ret < 0) {
fwnode_handle_put(child); fwnode_handle_put(child);
goto err; goto err;
} }
priv->num_leds++;
} }
return priv; return priv;
err: 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]); delete_gpio_led(&priv->leds[count]);
return ERR_PTR(ret); return ERR_PTR(ret);
} }
......
/*
* 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 @@ ...@@ -50,6 +50,7 @@
#define LP5523_REG_OP_MODE 0x01 #define LP5523_REG_OP_MODE 0x01
#define LP5523_REG_ENABLE_LEDS_MSB 0x04 #define LP5523_REG_ENABLE_LEDS_MSB 0x04
#define LP5523_REG_ENABLE_LEDS_LSB 0x05 #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_PWM_BASE 0x16
#define LP5523_REG_LED_CURRENT_BASE 0x26 #define LP5523_REG_LED_CURRENT_BASE 0x26
#define LP5523_REG_CONFIG 0x36 #define LP5523_REG_CONFIG 0x36
...@@ -57,6 +58,7 @@ ...@@ -57,6 +58,7 @@
#define LP5523_REG_RESET 0x3D #define LP5523_REG_RESET 0x3D
#define LP5523_REG_LED_TEST_CTRL 0x41 #define LP5523_REG_LED_TEST_CTRL 0x41
#define LP5523_REG_LED_TEST_ADC 0x42 #define LP5523_REG_LED_TEST_ADC 0x42
#define LP5523_REG_MASTER_FADER_BASE 0x48
#define LP5523_REG_CH1_PROG_START 0x4C #define LP5523_REG_CH1_PROG_START 0x4C
#define LP5523_REG_CH2_PROG_START 0x4D #define LP5523_REG_CH2_PROG_START 0x4D
#define LP5523_REG_CH3_PROG_START 0x4E #define LP5523_REG_CH3_PROG_START 0x4E
...@@ -78,6 +80,9 @@ ...@@ -78,6 +80,9 @@
#define LP5523_EXT_CLK_USED 0x08 #define LP5523_EXT_CLK_USED 0x08
#define LP5523_ENG_STATUS_MASK 0x07 #define LP5523_ENG_STATUS_MASK 0x07
#define LP5523_FADER_MAPPING_MASK 0xC0
#define LP5523_FADER_MAPPING_SHIFT 6
/* Memory Page Selection */ /* Memory Page Selection */
#define LP5523_PAGE_ENG1 0 #define LP5523_PAGE_ENG1 0
#define LP5523_PAGE_ENG2 1 #define LP5523_PAGE_ENG2 1
...@@ -666,6 +671,137 @@ static ssize_t lp5523_selftest(struct device *dev, ...@@ -666,6 +671,137 @@ static ssize_t lp5523_selftest(struct device *dev,
return pos; 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) static void lp5523_led_brightness_work(struct work_struct *work)
{ {
struct lp55xx_led *led = container_of(work, struct lp55xx_led, struct lp55xx_led *led = container_of(work, struct lp55xx_led,
...@@ -688,6 +824,14 @@ static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load); ...@@ -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(engine2_load, store_engine2_load);
static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load); static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load);
static LP55XX_DEV_ATTR_RO(selftest, lp5523_selftest); 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[] = { static struct attribute *lp5523_attributes[] = {
&dev_attr_engine1_mode.attr, &dev_attr_engine1_mode.attr,
...@@ -700,6 +844,10 @@ static struct attribute *lp5523_attributes[] = { ...@@ -700,6 +844,10 @@ static struct attribute *lp5523_attributes[] = {
&dev_attr_engine2_leds.attr, &dev_attr_engine2_leds.attr,
&dev_attr_engine3_leds.attr, &dev_attr_engine3_leds.attr,
&dev_attr_selftest.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, NULL,
}; };
......
...@@ -223,7 +223,7 @@ static int lp55xx_request_firmware(struct lp55xx_chip *chip) ...@@ -223,7 +223,7 @@ static int lp55xx_request_firmware(struct lp55xx_chip *chip)
const char *name = chip->cl->name; const char *name = chip->cl->name;
struct device *dev = &chip->cl->dev; 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); GFP_KERNEL, chip, lp55xx_firmware_loaded);
} }
......
/*
* LED Flash class driver for the flash cell of max77693 mfd.
*
* Copyright (C) 2015, Samsung Electronics Co., Ltd.
*
* Authors: Jacek Anaszewski <j.anaszewski@samsung.com>
* Andrzej Hajda <a.hajda@samsung.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include <linux/led-class-flash.h>
#include <linux/mfd/max77693.h>
#include <linux/mfd/max77693-private.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <media/v4l2-flash-led-class.h>
#define MODE_OFF 0
#define MODE_FLASH(a) (1 << (a))
#define MODE_TORCH(a) (1 << (2 + (a)))
#define MODE_FLASH_EXTERNAL(a) (1 << (4 + (a)))
#define MODE_FLASH_MASK (MODE_FLASH(FLED1) | MODE_FLASH(FLED2) | \
MODE_FLASH_EXTERNAL(FLED1) | \
MODE_FLASH_EXTERNAL(FLED2))
#define MODE_TORCH_MASK (MODE_TORCH(FLED1) | MODE_TORCH(FLED2))
#define FLED1_IOUT (1 << 0)
#define FLED2_IOUT (1 << 1)
enum max77693_fled {
FLED1,
FLED2,
};
enum max77693_led_mode {
FLASH,
TORCH,
};
struct max77693_led_config_data {
const char *label[2];
u32 iout_torch_max[2];
u32 iout_flash_max[2];
u32 flash_timeout_max[2];
u32 num_leds;
u32 boost_mode;
u32 boost_vout;
u32 low_vsys;
};
struct max77693_sub_led {
/* corresponding FLED output identifier */
int fled_id;
/* corresponding LED Flash class device */
struct led_classdev_flash fled_cdev;
/* assures led-triggers compatibility */
struct work_struct work_brightness_set;
/* V4L2 Flash device */
struct v4l2_flash *v4l2_flash;
/* brightness cache */
unsigned int torch_brightness;
/* flash timeout cache */
unsigned int flash_timeout;
/* flash faults that may have occurred */
u32 flash_faults;
};
struct max77693_led_device {
/* parent mfd regmap */
struct regmap *regmap;
/* platform device data */
struct platform_device *pdev;
/* secures access to the device */
struct mutex lock;
/* sub led data */
struct max77693_sub_led sub_leds[2];
/* maximum torch current values for FLED outputs */
u32 iout_torch_max[2];
/* maximum flash current values for FLED outputs */
u32 iout_flash_max[2];
/* current flash timeout cache */
unsigned int current_flash_timeout;
/* ITORCH register cache */
u8 torch_iout_reg;
/* mode of fled outputs */
unsigned int mode_flags;
/* recently strobed fled */
int strobing_sub_led_id;
/* bitmask of FLED outputs use state (bit 0. - FLED1, bit 1. - FLED2) */
u8 fled_mask;
/* FLED modes that can be set */
u8 allowed_modes;
/* arrangement of current outputs */
bool iout_joint;
};
static u8 max77693_led_iout_to_reg(u32 ua)
{
if (ua < FLASH_IOUT_MIN)
ua = FLASH_IOUT_MIN;
return (ua - FLASH_IOUT_MIN) / FLASH_IOUT_STEP;
}
static u8 max77693_flash_timeout_to_reg(u32 us)
{
return (us - FLASH_TIMEOUT_MIN) / FLASH_TIMEOUT_STEP;
}
static inline struct max77693_sub_led *flcdev_to_sub_led(
struct led_classdev_flash *fled_cdev)
{
return container_of(fled_cdev, struct max77693_sub_led, fled_cdev);
}
static inline struct max77693_led_device *sub_led_to_led(
struct max77693_sub_led *sub_led)
{
return container_of(sub_led, struct max77693_led_device,
sub_leds[sub_led->fled_id]);
}
static inline u8 max77693_led_vsys_to_reg(u32 mv)
{
return ((mv - MAX_FLASH1_VSYS_MIN) / MAX_FLASH1_VSYS_STEP) << 2;
}
static inline u8 max77693_led_vout_to_reg(u32 mv)
{
return (mv - FLASH_VOUT_MIN) / FLASH_VOUT_STEP + FLASH_VOUT_RMIN;
}
static inline bool max77693_fled_used(struct max77693_led_device *led,
int fled_id)
{
u8 fled_bit = (fled_id == FLED1) ? FLED1_IOUT : FLED2_IOUT;
return led->fled_mask & fled_bit;
}
static int max77693_set_mode_reg(struct max77693_led_device *led, u8 mode)
{
struct regmap *rmap = led->regmap;
int ret, v = 0, i;
for (i = FLED1; i <= FLED2; ++i) {
if (mode & MODE_TORCH(i))
v |= FLASH_EN_ON << TORCH_EN_SHIFT(i);
if (mode & MODE_FLASH(i)) {
v |= FLASH_EN_ON << FLASH_EN_SHIFT(i);
} else if (mode & MODE_FLASH_EXTERNAL(i)) {
v |= FLASH_EN_FLASH << FLASH_EN_SHIFT(i);
/*
* Enable hw triggering also for torch mode, as some
* camera sensors use torch led to fathom ambient light
* conditions before strobing the flash.
*/
v |= FLASH_EN_TORCH << TORCH_EN_SHIFT(i);
}
}
/* Reset the register only prior setting flash modes */
if (mode & ~(MODE_TORCH(FLED1) | MODE_TORCH(FLED2))) {
ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_EN, 0);
if (ret < 0)
return ret;
}
return regmap_write(rmap, MAX77693_LED_REG_FLASH_EN, v);
}
static int max77693_add_mode(struct max77693_led_device *led, u8 mode)
{
u8 new_mode_flags;
int i, ret;
if (led->iout_joint)
/* Span the mode on FLED2 for joint iouts case */
mode |= (mode << 1);
/*
* FLASH_EXTERNAL mode activates FLASHEN and TORCHEN pins in the device.
* Corresponding register bit fields interfere with SW triggered modes,
* thus clear them to ensure proper device configuration.
*/
for (i = FLED1; i <= FLED2; ++i)
if (mode & MODE_FLASH_EXTERNAL(i))
led->mode_flags &= (~MODE_TORCH(i) & ~MODE_FLASH(i));
new_mode_flags = mode | led->mode_flags;
new_mode_flags &= led->allowed_modes;
if (new_mode_flags ^ led->mode_flags)
led->mode_flags = new_mode_flags;
else
return 0;
ret = max77693_set_mode_reg(led, led->mode_flags);
if (ret < 0)
return ret;
/*
* Clear flash mode flag after setting the mode to avoid spurious flash
* strobing on each subsequent torch mode setting.
*/
if (mode & MODE_FLASH_MASK)
led->mode_flags &= ~mode;
return ret;
}
static int max77693_clear_mode(struct max77693_led_device *led,
u8 mode)
{
if (led->iout_joint)
/* Clear mode also on FLED2 for joint iouts case */
mode |= (mode << 1);
led->mode_flags &= ~mode;
return max77693_set_mode_reg(led, led->mode_flags);
}
static void max77693_add_allowed_modes(struct max77693_led_device *led,
int fled_id, enum max77693_led_mode mode)
{
if (mode == FLASH)
led->allowed_modes |= (MODE_FLASH(fled_id) |
MODE_FLASH_EXTERNAL(fled_id));
else
led->allowed_modes |= MODE_TORCH(fled_id);
}
static void max77693_distribute_currents(struct max77693_led_device *led,
int fled_id, enum max77693_led_mode mode,
u32 micro_amp, u32 iout_max[2], u32 iout[2])
{
if (!led->iout_joint) {
iout[fled_id] = micro_amp;
max77693_add_allowed_modes(led, fled_id, mode);
return;
}
iout[FLED1] = min(micro_amp, iout_max[FLED1]);
iout[FLED2] = micro_amp - iout[FLED1];
if (mode == FLASH)
led->allowed_modes &= ~MODE_FLASH_MASK;
else
led->allowed_modes &= ~MODE_TORCH_MASK;
max77693_add_allowed_modes(led, FLED1, mode);
if (iout[FLED2])
max77693_add_allowed_modes(led, FLED2, mode);
}
static int max77693_set_torch_current(struct max77693_led_device *led,
int fled_id, u32 micro_amp)
{
struct regmap *rmap = led->regmap;
u8 iout1_reg = 0, iout2_reg = 0;
u32 iout[2];
max77693_distribute_currents(led, fled_id, TORCH, micro_amp,
led->iout_torch_max, iout);
if (fled_id == FLED1 || led->iout_joint) {
iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
led->torch_iout_reg &= TORCH_IOUT_MASK(TORCH_IOUT2_SHIFT);
}
if (fled_id == FLED2 || led->iout_joint) {
iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
led->torch_iout_reg &= TORCH_IOUT_MASK(TORCH_IOUT1_SHIFT);
}
led->torch_iout_reg |= ((iout1_reg << TORCH_IOUT1_SHIFT) |
(iout2_reg << TORCH_IOUT2_SHIFT));
return regmap_write(rmap, MAX77693_LED_REG_ITORCH,
led->torch_iout_reg);
}
static int max77693_set_flash_current(struct max77693_led_device *led,
int fled_id,
u32 micro_amp)
{
struct regmap *rmap = led->regmap;
u8 iout1_reg, iout2_reg;
u32 iout[2];
int ret = -EINVAL;
max77693_distribute_currents(led, fled_id, FLASH, micro_amp,
led->iout_flash_max, iout);
if (fled_id == FLED1 || led->iout_joint) {
iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
ret = regmap_write(rmap, MAX77693_LED_REG_IFLASH1,
iout1_reg);
if (ret < 0)
return ret;
}
if (fled_id == FLED2 || led->iout_joint) {
iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
ret = regmap_write(rmap, MAX77693_LED_REG_IFLASH2,
iout2_reg);
}
return ret;
}
static int max77693_set_timeout(struct max77693_led_device *led, u32 microsec)
{
struct regmap *rmap = led->regmap;
u8 v;
int ret;
v = max77693_flash_timeout_to_reg(microsec) | FLASH_TMR_LEVEL;
ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
if (ret < 0)
return ret;
led->current_flash_timeout = microsec;
return 0;
}
static int max77693_get_strobe_status(struct max77693_led_device *led,
bool *state)
{
struct regmap *rmap = led->regmap;
unsigned int v;
int ret;
ret = regmap_read(rmap, MAX77693_LED_REG_FLASH_STATUS, &v);
if (ret < 0)
return ret;
*state = v & FLASH_STATUS_FLASH_ON;
return ret;
}
static int max77693_get_flash_faults(struct max77693_sub_led *sub_led)
{
struct max77693_led_device *led = sub_led_to_led(sub_led);
struct regmap *rmap = led->regmap;
unsigned int v;
u8 fault_open_mask, fault_short_mask;
int ret;
sub_led->flash_faults = 0;
if (led->iout_joint) {
fault_open_mask = FLASH_INT_FLED1_OPEN | FLASH_INT_FLED2_OPEN;
fault_short_mask = FLASH_INT_FLED1_SHORT |
FLASH_INT_FLED2_SHORT;
} else {
fault_open_mask = (sub_led->fled_id == FLED1) ?
FLASH_INT_FLED1_OPEN :
FLASH_INT_FLED2_OPEN;
fault_short_mask = (sub_led->fled_id == FLED1) ?
FLASH_INT_FLED1_SHORT :
FLASH_INT_FLED2_SHORT;
}
ret = regmap_read(rmap, MAX77693_LED_REG_FLASH_INT, &v);
if (ret < 0)
return ret;
if (v & fault_open_mask)
sub_led->flash_faults |= LED_FAULT_OVER_VOLTAGE;
if (v & fault_short_mask)
sub_led->flash_faults |= LED_FAULT_SHORT_CIRCUIT;
if (v & FLASH_INT_OVER_CURRENT)
sub_led->flash_faults |= LED_FAULT_OVER_CURRENT;
return 0;
}
static int max77693_setup(struct max77693_led_device *led,
struct max77693_led_config_data *led_cfg)
{
struct regmap *rmap = led->regmap;
int i, first_led, last_led, ret;
u32 max_flash_curr[2];
u8 v;
/*
* Initialize only flash current. Torch current doesn't
* require initialization as ITORCH register is written with
* new value each time brightness_set op is called.
*/
if (led->iout_joint) {
first_led = FLED1;
last_led = FLED1;
max_flash_curr[FLED1] = led_cfg->iout_flash_max[FLED1] +
led_cfg->iout_flash_max[FLED2];
} else {
first_led = max77693_fled_used(led, FLED1) ? FLED1 : FLED2;
last_led = max77693_fled_used(led, FLED2) ? FLED2 : FLED1;
max_flash_curr[FLED1] = led_cfg->iout_flash_max[FLED1];
max_flash_curr[FLED2] = led_cfg->iout_flash_max[FLED2];
}
for (i = first_led; i <= last_led; ++i) {
ret = max77693_set_flash_current(led, i,
max_flash_curr[i]);
if (ret < 0)
return ret;
}
v = TORCH_TMR_NO_TIMER | MAX77693_LED_TRIG_TYPE_LEVEL;
ret = regmap_write(rmap, MAX77693_LED_REG_ITORCHTIMER, v);
if (ret < 0)
return ret;
if (led_cfg->low_vsys > 0)
v = max77693_led_vsys_to_reg(led_cfg->low_vsys) |
MAX_FLASH1_MAX_FL_EN;
else
v = 0;
ret = regmap_write(rmap, MAX77693_LED_REG_MAX_FLASH1, v);
if (ret < 0)
return ret;
ret = regmap_write(rmap, MAX77693_LED_REG_MAX_FLASH2, 0);
if (ret < 0)
return ret;
if (led_cfg->boost_mode == MAX77693_LED_BOOST_FIXED)
v = FLASH_BOOST_FIXED;
else
v = led_cfg->boost_mode | led_cfg->boost_mode << 1;
if (max77693_fled_used(led, FLED1) && max77693_fled_used(led, FLED2))
v |= FLASH_BOOST_LEDNUM_2;
ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_CNTL, v);
if (ret < 0)
return ret;
v = max77693_led_vout_to_reg(led_cfg->boost_vout);
ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
if (ret < 0)
return ret;
return max77693_set_mode_reg(led, MODE_OFF);
}
static int __max77693_led_brightness_set(struct max77693_led_device *led,
int fled_id, enum led_brightness value)
{
int ret;
mutex_lock(&led->lock);
if (value == 0) {
ret = max77693_clear_mode(led, MODE_TORCH(fled_id));
if (ret < 0)
dev_dbg(&led->pdev->dev,
"Failed to clear torch mode (%d)\n",
ret);
goto unlock;
}
ret = max77693_set_torch_current(led, fled_id, value * TORCH_IOUT_STEP);
if (ret < 0) {
dev_dbg(&led->pdev->dev,
"Failed to set torch current (%d)\n",
ret);
goto unlock;
}
ret = max77693_add_mode(led, MODE_TORCH(fled_id));
if (ret < 0)
dev_dbg(&led->pdev->dev,
"Failed to set torch mode (%d)\n",
ret);
unlock:
mutex_unlock(&led->lock);
return ret;
}
static void max77693_led_brightness_set_work(
struct work_struct *work)
{
struct max77693_sub_led *sub_led =
container_of(work, struct max77693_sub_led,
work_brightness_set);
struct max77693_led_device *led = sub_led_to_led(sub_led);
__max77693_led_brightness_set(led, sub_led->fled_id,
sub_led->torch_brightness);
}
/* LED subsystem callbacks */
static int max77693_led_brightness_set_sync(
struct led_classdev *led_cdev,
enum led_brightness value)
{
struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
struct max77693_led_device *led = sub_led_to_led(sub_led);
return __max77693_led_brightness_set(led, sub_led->fled_id, value);
}
static void max77693_led_brightness_set(
struct led_classdev *led_cdev,
enum led_brightness value)
{
struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
sub_led->torch_brightness = value;
schedule_work(&sub_led->work_brightness_set);
}
static int max77693_led_flash_brightness_set(
struct led_classdev_flash *fled_cdev,
u32 brightness)
{
struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
struct max77693_led_device *led = sub_led_to_led(sub_led);
int ret;
mutex_lock(&led->lock);
ret = max77693_set_flash_current(led, sub_led->fled_id, brightness);
mutex_unlock(&led->lock);
return ret;
}
static int max77693_led_flash_strobe_set(
struct led_classdev_flash *fled_cdev,
bool state)
{
struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
struct max77693_led_device *led = sub_led_to_led(sub_led);
int fled_id = sub_led->fled_id;
int ret;
mutex_lock(&led->lock);
if (!state) {
ret = max77693_clear_mode(led, MODE_FLASH(fled_id));
goto unlock;
}
if (sub_led->flash_timeout != led->current_flash_timeout) {
ret = max77693_set_timeout(led, sub_led->flash_timeout);
if (ret < 0)
goto unlock;
}
led->strobing_sub_led_id = fled_id;
ret = max77693_add_mode(led, MODE_FLASH(fled_id));
if (ret < 0)
goto unlock;
ret = max77693_get_flash_faults(sub_led);
unlock:
mutex_unlock(&led->lock);
return ret;
}
static int max77693_led_flash_fault_get(
struct led_classdev_flash *fled_cdev,
u32 *fault)
{
struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
*fault = sub_led->flash_faults;
return 0;
}
static int max77693_led_flash_strobe_get(
struct led_classdev_flash *fled_cdev,
bool *state)
{
struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
struct max77693_led_device *led = sub_led_to_led(sub_led);
int ret;
if (!state)
return -EINVAL;
mutex_lock(&led->lock);
ret = max77693_get_strobe_status(led, state);
*state = !!(*state && (led->strobing_sub_led_id == sub_led->fled_id));
mutex_unlock(&led->lock);
return ret;
}
static int max77693_led_flash_timeout_set(
struct led_classdev_flash *fled_cdev,
u32 timeout)
{
struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
struct max77693_led_device *led = sub_led_to_led(sub_led);
mutex_lock(&led->lock);
sub_led->flash_timeout = timeout;
mutex_unlock(&led->lock);
return 0;
}
static int max77693_led_parse_dt(struct max77693_led_device *led,
struct max77693_led_config_data *cfg,
struct device_node **sub_nodes)
{
struct device *dev = &led->pdev->dev;
struct max77693_sub_led *sub_leds = led->sub_leds;
struct device_node *node = dev->of_node, *child_node;
struct property *prop;
u32 led_sources[2];
int i, ret, fled_id;
of_property_read_u32(node, "maxim,boost-mode", &cfg->boost_mode);
of_property_read_u32(node, "maxim,boost-mvout", &cfg->boost_vout);
of_property_read_u32(node, "maxim,mvsys-min", &cfg->low_vsys);
for_each_available_child_of_node(node, child_node) {
prop = of_find_property(child_node, "led-sources", NULL);
if (prop) {
const __be32 *srcs = NULL;
for (i = 0; i < ARRAY_SIZE(led_sources); ++i) {
srcs = of_prop_next_u32(prop, srcs,
&led_sources[i]);
if (!srcs)
break;
}
} else {
dev_err(dev,
"led-sources DT property missing\n");
of_node_put(child_node);
return -EINVAL;
}
if (i == 2) {
fled_id = FLED1;
led->fled_mask = FLED1_IOUT | FLED2_IOUT;
} else if (led_sources[0] == FLED1) {
fled_id = FLED1;
led->fled_mask |= FLED1_IOUT;
} else if (led_sources[0] == FLED2) {
fled_id = FLED2;
led->fled_mask |= FLED2_IOUT;
} else {
dev_err(dev,
"Wrong led-sources DT property value.\n");
of_node_put(child_node);
return -EINVAL;
}
if (sub_nodes[fled_id]) {
dev_err(dev,
"Conflicting \"led-sources\" DT properties\n");
return -EINVAL;
}
sub_nodes[fled_id] = child_node;
sub_leds[fled_id].fled_id = fled_id;
cfg->label[fled_id] =
of_get_property(child_node, "label", NULL) ? :
child_node->name;
ret = of_property_read_u32(child_node, "led-max-microamp",
&cfg->iout_torch_max[fled_id]);
if (ret < 0) {
cfg->iout_torch_max[fled_id] = TORCH_IOUT_MIN;
dev_warn(dev, "led-max-microamp DT property missing\n");
}
ret = of_property_read_u32(child_node, "flash-max-microamp",
&cfg->iout_flash_max[fled_id]);
if (ret < 0) {
cfg->iout_flash_max[fled_id] = FLASH_IOUT_MIN;
dev_warn(dev,
"flash-max-microamp DT property missing\n");
}
ret = of_property_read_u32(child_node, "flash-max-timeout-us",
&cfg->flash_timeout_max[fled_id]);
if (ret < 0) {
cfg->flash_timeout_max[fled_id] = FLASH_TIMEOUT_MIN;
dev_warn(dev,
"flash-max-timeout-us DT property missing\n");
}
if (++cfg->num_leds == 2 ||
(max77693_fled_used(led, FLED1) &&
max77693_fled_used(led, FLED2))) {
of_node_put(child_node);
break;
}
}
if (cfg->num_leds == 0) {
dev_err(dev, "No DT child node found for connected LED(s).\n");
return -EINVAL;
}
return 0;
}
static void clamp_align(u32 *v, u32 min, u32 max, u32 step)
{
*v = clamp_val(*v, min, max);
if (step > 1)
*v = (*v - min) / step * step + min;
}
static void max77693_align_iout_current(struct max77693_led_device *led,
u32 *iout, u32 min, u32 max, u32 step)
{
int i;
if (led->iout_joint) {
if (iout[FLED1] > min) {
iout[FLED1] /= 2;
iout[FLED2] = iout[FLED1];
} else {
iout[FLED1] = min;
iout[FLED2] = 0;
return;
}
}
for (i = FLED1; i <= FLED2; ++i)
if (max77693_fled_used(led, i))
clamp_align(&iout[i], min, max, step);
else
iout[i] = 0;
}
static void max77693_led_validate_configuration(struct max77693_led_device *led,
struct max77693_led_config_data *cfg)
{
u32 flash_iout_max = cfg->boost_mode ? FLASH_IOUT_MAX_2LEDS :
FLASH_IOUT_MAX_1LED;
int i;
if (cfg->num_leds == 1 &&
max77693_fled_used(led, FLED1) && max77693_fled_used(led, FLED2))
led->iout_joint = true;
cfg->boost_mode = clamp_val(cfg->boost_mode, MAX77693_LED_BOOST_NONE,
MAX77693_LED_BOOST_FIXED);
/* Boost must be enabled if both current outputs are used */
if ((cfg->boost_mode == MAX77693_LED_BOOST_NONE) && led->iout_joint)
cfg->boost_mode = MAX77693_LED_BOOST_FIXED;
max77693_align_iout_current(led, cfg->iout_torch_max,
TORCH_IOUT_MIN, TORCH_IOUT_MAX, TORCH_IOUT_STEP);
max77693_align_iout_current(led, cfg->iout_flash_max,
FLASH_IOUT_MIN, flash_iout_max, FLASH_IOUT_STEP);
for (i = 0; i < ARRAY_SIZE(cfg->flash_timeout_max); ++i)
clamp_align(&cfg->flash_timeout_max[i], FLASH_TIMEOUT_MIN,
FLASH_TIMEOUT_MAX, FLASH_TIMEOUT_STEP);
clamp_align(&cfg->boost_vout, FLASH_VOUT_MIN, FLASH_VOUT_MAX,
FLASH_VOUT_STEP);
if (cfg->low_vsys)
clamp_align(&cfg->low_vsys, MAX_FLASH1_VSYS_MIN,
MAX_FLASH1_VSYS_MAX, MAX_FLASH1_VSYS_STEP);
}
static int max77693_led_get_configuration(struct max77693_led_device *led,
struct max77693_led_config_data *cfg,
struct device_node **sub_nodes)
{
int ret;
ret = max77693_led_parse_dt(led, cfg, sub_nodes);
if (ret < 0)
return ret;
max77693_led_validate_configuration(led, cfg);
memcpy(led->iout_torch_max, cfg->iout_torch_max,
sizeof(led->iout_torch_max));
memcpy(led->iout_flash_max, cfg->iout_flash_max,
sizeof(led->iout_flash_max));
return 0;
}
static const struct led_flash_ops flash_ops = {
.flash_brightness_set = max77693_led_flash_brightness_set,
.strobe_set = max77693_led_flash_strobe_set,
.strobe_get = max77693_led_flash_strobe_get,
.timeout_set = max77693_led_flash_timeout_set,
.fault_get = max77693_led_flash_fault_get,
};
static void max77693_init_flash_settings(struct max77693_sub_led *sub_led,
struct max77693_led_config_data *led_cfg)
{
struct led_classdev_flash *fled_cdev = &sub_led->fled_cdev;
struct max77693_led_device *led = sub_led_to_led(sub_led);
int fled_id = sub_led->fled_id;
struct led_flash_setting *setting;
/* Init flash intensity setting */
setting = &fled_cdev->brightness;
setting->min = FLASH_IOUT_MIN;
setting->max = led->iout_joint ?
led_cfg->iout_flash_max[FLED1] +
led_cfg->iout_flash_max[FLED2] :
led_cfg->iout_flash_max[fled_id];
setting->step = FLASH_IOUT_STEP;
setting->val = setting->max;
/* Init flash timeout setting */
setting = &fled_cdev->timeout;
setting->min = FLASH_TIMEOUT_MIN;
setting->max = led_cfg->flash_timeout_max[fled_id];
setting->step = FLASH_TIMEOUT_STEP;
setting->val = setting->max;
}
#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
static int max77693_led_external_strobe_set(
struct v4l2_flash *v4l2_flash,
bool enable)
{
struct max77693_sub_led *sub_led =
flcdev_to_sub_led(v4l2_flash->fled_cdev);
struct max77693_led_device *led = sub_led_to_led(sub_led);
int fled_id = sub_led->fled_id;
int ret;
mutex_lock(&led->lock);
if (enable)
ret = max77693_add_mode(led, MODE_FLASH_EXTERNAL(fled_id));
else
ret = max77693_clear_mode(led, MODE_FLASH_EXTERNAL(fled_id));
mutex_unlock(&led->lock);
return ret;
}
static void max77693_init_v4l2_flash_config(struct max77693_sub_led *sub_led,
struct max77693_led_config_data *led_cfg,
struct v4l2_flash_config *v4l2_sd_cfg)
{
struct max77693_led_device *led = sub_led_to_led(sub_led);
struct device *dev = &led->pdev->dev;
struct max77693_dev *iodev = dev_get_drvdata(dev->parent);
struct i2c_client *i2c = iodev->i2c;
struct led_flash_setting *s;
snprintf(v4l2_sd_cfg->dev_name, sizeof(v4l2_sd_cfg->dev_name),
"%s %d-%04x", sub_led->fled_cdev.led_cdev.name,
i2c_adapter_id(i2c->adapter), i2c->addr);
s = &v4l2_sd_cfg->torch_intensity;
s->min = TORCH_IOUT_MIN;
s->max = sub_led->fled_cdev.led_cdev.max_brightness * TORCH_IOUT_STEP;
s->step = TORCH_IOUT_STEP;
s->val = s->max;
/* Init flash faults config */
v4l2_sd_cfg->flash_faults = LED_FAULT_OVER_VOLTAGE |
LED_FAULT_SHORT_CIRCUIT |
LED_FAULT_OVER_CURRENT;
v4l2_sd_cfg->has_external_strobe = true;
}
static const struct v4l2_flash_ops v4l2_flash_ops = {
.external_strobe_set = max77693_led_external_strobe_set,
};
#else
static inline void max77693_init_v4l2_flash_config(
struct max77693_sub_led *sub_led,
struct max77693_led_config_data *led_cfg,
struct v4l2_flash_config *v4l2_sd_cfg)
{
}
static const struct v4l2_flash_ops v4l2_flash_ops;
#endif
static void max77693_init_fled_cdev(struct max77693_sub_led *sub_led,
struct max77693_led_config_data *led_cfg)
{
struct max77693_led_device *led = sub_led_to_led(sub_led);
int fled_id = sub_led->fled_id;
struct led_classdev_flash *fled_cdev;
struct led_classdev *led_cdev;
/* Initialize LED Flash class device */
fled_cdev = &sub_led->fled_cdev;
fled_cdev->ops = &flash_ops;
led_cdev = &fled_cdev->led_cdev;
led_cdev->name = led_cfg->label[fled_id];
led_cdev->brightness_set = max77693_led_brightness_set;
led_cdev->brightness_set_sync = max77693_led_brightness_set_sync;
led_cdev->max_brightness = (led->iout_joint ?
led_cfg->iout_torch_max[FLED1] +
led_cfg->iout_torch_max[FLED2] :
led_cfg->iout_torch_max[fled_id]) /
TORCH_IOUT_STEP;
led_cdev->flags |= LED_DEV_CAP_FLASH;
INIT_WORK(&sub_led->work_brightness_set,
max77693_led_brightness_set_work);
max77693_init_flash_settings(sub_led, led_cfg);
/* Init flash timeout cache */
sub_led->flash_timeout = fled_cdev->timeout.val;
}
static int max77693_register_led(struct max77693_sub_led *sub_led,
struct max77693_led_config_data *led_cfg,
struct device_node *sub_node)
{
struct max77693_led_device *led = sub_led_to_led(sub_led);
struct led_classdev_flash *fled_cdev = &sub_led->fled_cdev;
struct device *dev = &led->pdev->dev;
struct v4l2_flash_config v4l2_sd_cfg = {};
int ret;
/* Register in the LED subsystem */
ret = led_classdev_flash_register(dev, fled_cdev);
if (ret < 0)
return ret;
max77693_init_v4l2_flash_config(sub_led, led_cfg, &v4l2_sd_cfg);
/* Register in the V4L2 subsystem. */
sub_led->v4l2_flash = v4l2_flash_init(dev, sub_node, fled_cdev, NULL,
&v4l2_flash_ops, &v4l2_sd_cfg);
if (IS_ERR(sub_led->v4l2_flash)) {
ret = PTR_ERR(sub_led->v4l2_flash);
goto err_v4l2_flash_init;
}
return 0;
err_v4l2_flash_init:
led_classdev_flash_unregister(fled_cdev);
return ret;
}
static int max77693_led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct max77693_dev *iodev = dev_get_drvdata(dev->parent);
struct max77693_led_device *led;
struct max77693_sub_led *sub_leds;
struct device_node *sub_nodes[2] = {};
struct max77693_led_config_data led_cfg = {};
int init_fled_cdev[2], i, ret;
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
led->pdev = pdev;
led->regmap = iodev->regmap;
led->allowed_modes = MODE_FLASH_MASK;
sub_leds = led->sub_leds;
platform_set_drvdata(pdev, led);
ret = max77693_led_get_configuration(led, &led_cfg, sub_nodes);
if (ret < 0)
return ret;
ret = max77693_setup(led, &led_cfg);
if (ret < 0)
return ret;
mutex_init(&led->lock);
init_fled_cdev[FLED1] =
led->iout_joint || max77693_fled_used(led, FLED1);
init_fled_cdev[FLED2] =
!led->iout_joint && max77693_fled_used(led, FLED2);
for (i = FLED1; i <= FLED2; ++i) {
if (!init_fled_cdev[i])
continue;
/* Initialize LED Flash class device */
max77693_init_fled_cdev(&sub_leds[i], &led_cfg);
/*
* Register LED Flash class device and corresponding
* V4L2 Flash device.
*/
ret = max77693_register_led(&sub_leds[i], &led_cfg,
sub_nodes[i]);
if (ret < 0) {
/*
* At this moment FLED1 might have been already
* registered and it needs to be released.
*/
if (i == FLED2)
goto err_register_led2;
else
goto err_register_led1;
}
}
return 0;
err_register_led2:
/* It is possible than only FLED2 was to be registered */
if (!init_fled_cdev[FLED1])
goto err_register_led1;
v4l2_flash_release(sub_leds[FLED1].v4l2_flash);
led_classdev_flash_unregister(&sub_leds[FLED1].fled_cdev);
err_register_led1:
mutex_destroy(&led->lock);
return ret;
}
static int max77693_led_remove(struct platform_device *pdev)
{
struct max77693_led_device *led = platform_get_drvdata(pdev);
struct max77693_sub_led *sub_leds = led->sub_leds;
if (led->iout_joint || max77693_fled_used(led, FLED1)) {
v4l2_flash_release(sub_leds[FLED1].v4l2_flash);
led_classdev_flash_unregister(&sub_leds[FLED1].fled_cdev);
cancel_work_sync(&sub_leds[FLED1].work_brightness_set);
}
if (!led->iout_joint && max77693_fled_used(led, FLED2)) {
v4l2_flash_release(sub_leds[FLED2].v4l2_flash);
led_classdev_flash_unregister(&sub_leds[FLED2].fled_cdev);
cancel_work_sync(&sub_leds[FLED2].work_brightness_set);
}
mutex_destroy(&led->lock);
return 0;
}
static const struct of_device_id max77693_led_dt_match[] = {
{ .compatible = "maxim,max77693-led" },
{},
};
static struct platform_driver max77693_led_driver = {
.probe = max77693_led_probe,
.remove = max77693_led_remove,
.driver = {
.name = "max77693-led",
.of_match_table = max77693_led_dt_match,
},
};
module_platform_driver(max77693_led_driver);
MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
MODULE_DESCRIPTION("Maxim MAX77693 led flash driver");
MODULE_LICENSE("GPL v2");
/*
* Copyright 2014 Belkin Inc.
* Copyright 2015 Andrew Lunn <andrew@lunn.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*/
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#define TLC591XX_MAX_LEDS 16
#define TLC591XX_REG_MODE1 0x00
#define MODE1_RESPON_ADDR_MASK 0xF0
#define MODE1_NORMAL_MODE (0 << 4)
#define MODE1_SPEED_MODE (1 << 4)
#define TLC591XX_REG_MODE2 0x01
#define MODE2_DIM (0 << 5)
#define MODE2_BLINK (1 << 5)
#define MODE2_OCH_STOP (0 << 3)
#define MODE2_OCH_ACK (1 << 3)
#define TLC591XX_REG_PWM(x) (0x02 + (x))
#define TLC591XX_REG_GRPPWM 0x12
#define TLC591XX_REG_GRPFREQ 0x13
/* LED Driver Output State, determine the source that drives LED outputs */
#define LEDOUT_OFF 0x0 /* Output LOW */
#define LEDOUT_ON 0x1 /* Output HI-Z */
#define LEDOUT_DIM 0x2 /* Dimming */
#define LEDOUT_BLINK 0x3 /* Blinking */
#define LEDOUT_MASK 0x3
#define ldev_to_led(c) container_of(c, struct tlc591xx_led, ldev)
#define work_to_led(work) container_of(work, struct tlc591xx_led, work)
struct tlc591xx_led {
bool active;
unsigned int led_no;
struct led_classdev ldev;
struct work_struct work;
struct tlc591xx_priv *priv;
};
struct tlc591xx_priv {
struct tlc591xx_led leds[TLC591XX_MAX_LEDS];
struct regmap *regmap;
unsigned int reg_ledout_offset;
};
struct tlc591xx {
unsigned int max_leds;
unsigned int reg_ledout_offset;
};
static const struct tlc591xx tlc59116 = {
.max_leds = 16,
.reg_ledout_offset = 0x14,
};
static const struct tlc591xx tlc59108 = {
.max_leds = 8,
.reg_ledout_offset = 0x0c,
};
static int
tlc591xx_set_mode(struct regmap *regmap, u8 mode)
{
int err;
u8 val;
err = regmap_write(regmap, TLC591XX_REG_MODE1, MODE1_NORMAL_MODE);
if (err)
return err;
val = MODE2_OCH_STOP | mode;
return regmap_write(regmap, TLC591XX_REG_MODE2, val);
}
static int
tlc591xx_set_ledout(struct tlc591xx_priv *priv, struct tlc591xx_led *led,
u8 val)
{
unsigned int i = (led->led_no % 4) * 2;
unsigned int mask = LEDOUT_MASK << i;
unsigned int addr = priv->reg_ledout_offset + (led->led_no >> 2);
val = val << i;
return regmap_update_bits(priv->regmap, addr, mask, val);
}
static int
tlc591xx_set_pwm(struct tlc591xx_priv *priv, struct tlc591xx_led *led,
u8 brightness)
{
u8 pwm = TLC591XX_REG_PWM(led->led_no);
return regmap_write(priv->regmap, pwm, brightness);
}
static void
tlc591xx_led_work(struct work_struct *work)
{
struct tlc591xx_led *led = work_to_led(work);
struct tlc591xx_priv *priv = led->priv;
enum led_brightness brightness = led->ldev.brightness;
int err;
switch (brightness) {
case 0:
err = tlc591xx_set_ledout(priv, led, LEDOUT_OFF);
break;
case LED_FULL:
err = tlc591xx_set_ledout(priv, led, LEDOUT_ON);
break;
default:
err = tlc591xx_set_ledout(priv, led, LEDOUT_DIM);
if (!err)
err = tlc591xx_set_pwm(priv, led, brightness);
}
if (err)
dev_err(led->ldev.dev, "Failed setting brightness\n");
}
static void
tlc591xx_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct tlc591xx_led *led = ldev_to_led(led_cdev);
led->ldev.brightness = brightness;
schedule_work(&led->work);
}
static void
tlc591xx_destroy_devices(struct tlc591xx_priv *priv, unsigned int j)
{
int i = j;
while (--i >= 0) {
if (priv->leds[i].active) {
led_classdev_unregister(&priv->leds[i].ldev);
cancel_work_sync(&priv->leds[i].work);
}
}
}
static int
tlc591xx_configure(struct device *dev,
struct tlc591xx_priv *priv,
const struct tlc591xx *tlc591xx)
{
unsigned int i;
int err = 0;
tlc591xx_set_mode(priv->regmap, MODE2_DIM);
for (i = 0; i < TLC591XX_MAX_LEDS; i++) {
struct tlc591xx_led *led = &priv->leds[i];
if (!led->active)
continue;
led->priv = priv;
led->led_no = i;
led->ldev.brightness_set = tlc591xx_brightness_set;
led->ldev.max_brightness = LED_FULL;
INIT_WORK(&led->work, tlc591xx_led_work);
err = led_classdev_register(dev, &led->ldev);
if (err < 0) {
dev_err(dev, "couldn't register LED %s\n",
led->ldev.name);
goto exit;
}
}
return 0;
exit:
tlc591xx_destroy_devices(priv, i);
return err;
}
static const struct regmap_config tlc591xx_regmap = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0x1e,
};
static const struct of_device_id of_tlc591xx_leds_match[] = {
{ .compatible = "ti,tlc59116",
.data = &tlc59116 },
{ .compatible = "ti,tlc59108",
.data = &tlc59108 },
{},
};
MODULE_DEVICE_TABLE(of, of_tlc591xx_leds_match);
static int
tlc591xx_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device_node *np = client->dev.of_node, *child;
struct device *dev = &client->dev;
const struct of_device_id *match;
const struct tlc591xx *tlc591xx;
struct tlc591xx_priv *priv;
int err, count, reg;
match = of_match_device(of_tlc591xx_leds_match, dev);
if (!match)
return -ENODEV;
tlc591xx = match->data;
if (!np)
return -ENODEV;
count = of_get_child_count(np);
if (!count || count > tlc591xx->max_leds)
return -EINVAL;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->regmap = devm_regmap_init_i2c(client, &tlc591xx_regmap);
if (IS_ERR(priv->regmap)) {
err = PTR_ERR(priv->regmap);
dev_err(dev, "Failed to allocate register map: %d\n", err);
return err;
}
priv->reg_ledout_offset = tlc591xx->reg_ledout_offset;
i2c_set_clientdata(client, priv);
for_each_child_of_node(np, child) {
err = of_property_read_u32(child, "reg", &reg);
if (err)
return err;
if (reg < 0 || reg >= tlc591xx->max_leds)
return -EINVAL;
if (priv->leds[reg].active)
return -EINVAL;
priv->leds[reg].active = true;
priv->leds[reg].ldev.name =
of_get_property(child, "label", NULL) ? : child->name;
priv->leds[reg].ldev.default_trigger =
of_get_property(child, "linux,default-trigger", NULL);
}
return tlc591xx_configure(dev, priv, tlc591xx);
}
static int
tlc591xx_remove(struct i2c_client *client)
{
struct tlc591xx_priv *priv = i2c_get_clientdata(client);
tlc591xx_destroy_devices(priv, TLC591XX_MAX_LEDS);
return 0;
}
static const struct i2c_device_id tlc591xx_id[] = {
{ "tlc59116" },
{ "tlc59108" },
{},
};
MODULE_DEVICE_TABLE(i2c, tlc591xx_id);
static struct i2c_driver tlc591xx_driver = {
.driver = {
.name = "tlc591xx",
.of_match_table = of_match_ptr(of_tlc591xx_leds_match),
},
.probe = tlc591xx_probe,
.remove = tlc591xx_remove,
.id_table = tlc591xx_id,
};
module_i2c_driver(tlc591xx_driver);
MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("TLC591XX LED driver");
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
#ifndef __LEDS_H_INCLUDED #ifndef __LEDS_H_INCLUDED
#define __LEDS_H_INCLUDED #define __LEDS_H_INCLUDED
#include <linux/device.h>
#include <linux/rwsem.h> #include <linux/rwsem.h>
#include <linux/leds.h> #include <linux/leds.h>
...@@ -50,27 +49,4 @@ void led_stop_software_blink(struct led_classdev *led_cdev); ...@@ -50,27 +49,4 @@ void led_stop_software_blink(struct led_classdev *led_cdev);
extern struct rw_semaphore leds_list_lock; extern struct rw_semaphore leds_list_lock;
extern struct list_head leds_list; 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 */ #endif /* __LEDS_H_INCLUDED */
...@@ -44,6 +44,17 @@ config V4L2_MEM2MEM_DEV ...@@ -44,6 +44,17 @@ config V4L2_MEM2MEM_DEV
tristate tristate
depends on VIDEOBUF2_CORE 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 # Used by drivers that need Videobuf modules
config VIDEOBUF_GEN config VIDEOBUF_GEN
tristate tristate
......
...@@ -22,6 +22,8 @@ obj-$(CONFIG_VIDEO_TUNER) += tuner.o ...@@ -22,6 +22,8 @@ obj-$(CONFIG_VIDEO_TUNER) += tuner.o
obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.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_GEN) += videobuf-core.o
obj-$(CONFIG_VIDEOBUF_DMA_SG) += videobuf-dma-sg.o obj-$(CONFIG_VIDEOBUF_DMA_SG) += videobuf-dma-sg.o
obj-$(CONFIG_VIDEOBUF_DMA_CONTIG) += videobuf-dma-contig.o obj-$(CONFIG_VIDEOBUF_DMA_CONTIG) += videobuf-dma-contig.o
......
...@@ -22,10 +22,10 @@ ...@@ -22,10 +22,10 @@
#include <media/v4l2-device.h> #include <media/v4l2-device.h>
#include <media/v4l2-subdev.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) #if IS_ENABLED(CONFIG_I2C)
struct i2c_client *client = i2c_verify_client(dev); struct i2c_client *client = i2c_verify_client(sd->dev);
return client && return client &&
asd->match.i2c.adapter_id == client->adapter->nr && asd->match.i2c.adapter_id == client->adapter->nr &&
asd->match.i2c.address == client->addr; asd->match.i2c.address == client->addr;
...@@ -34,14 +34,24 @@ static bool match_i2c(struct device *dev, struct v4l2_async_subdev *asd) ...@@ -34,14 +34,24 @@ static bool match_i2c(struct device *dev, struct v4l2_async_subdev *asd)
#endif #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); static LIST_HEAD(subdev_list);
...@@ -51,17 +61,14 @@ static DEFINE_MUTEX(list_lock); ...@@ -51,17 +61,14 @@ static DEFINE_MUTEX(list_lock);
static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *notifier, static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd) struct v4l2_subdev *sd)
{ {
bool (*match)(struct v4l2_subdev *, struct v4l2_async_subdev *);
struct v4l2_async_subdev *asd; struct v4l2_async_subdev *asd;
bool (*match)(struct device *, struct v4l2_async_subdev *);
list_for_each_entry(asd, &notifier->waiting, list) { list_for_each_entry(asd, &notifier->waiting, list) {
/* bus_type has been verified valid before */ /* bus_type has been verified valid before */
switch (asd->match_type) { switch (asd->match_type) {
case V4L2_ASYNC_MATCH_CUSTOM: case V4L2_ASYNC_MATCH_CUSTOM:
match = asd->match.custom.match; match = match_custom;
if (!match)
/* Match always */
return asd;
break; break;
case V4L2_ASYNC_MATCH_DEVNAME: case V4L2_ASYNC_MATCH_DEVNAME:
match = match_devname; match = match_devname;
...@@ -79,7 +86,7 @@ static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier * ...@@ -79,7 +86,7 @@ static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *
} }
/* match cannot be NULL here */ /* match cannot be NULL here */
if (match(sd->dev, asd)) if (match(sd, asd))
return asd; return asd;
} }
...@@ -266,6 +273,14 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd) ...@@ -266,6 +273,14 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd)
{ {
struct v4l2_async_notifier *notifier; 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); mutex_lock(&list_lock);
INIT_LIST_HEAD(&sd->async_list); INIT_LIST_HEAD(&sd->async_list);
......
/*
* 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) ...@@ -407,6 +407,21 @@ static inline int desc_to_gpio(const struct gpio_desc *desc)
return -EINVAL; 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 */ #endif /* CONFIG_GPIOLIB */
/* /*
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#ifndef __LINUX_LEDS_H_INCLUDED #ifndef __LINUX_LEDS_H_INCLUDED
#define __LINUX_LEDS_H_INCLUDED #define __LINUX_LEDS_H_INCLUDED
#include <linux/device.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/rwsem.h> #include <linux/rwsem.h>
...@@ -222,6 +223,11 @@ struct led_trigger { ...@@ -222,6 +223,11 @@ struct led_trigger {
struct list_head next_trig; 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 */ /* Registration functions for complex triggers */
extern int led_trigger_register(struct led_trigger *trigger); extern int led_trigger_register(struct led_trigger *trigger);
extern void led_trigger_unregister(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, ...@@ -238,6 +244,16 @@ extern void led_trigger_blink_oneshot(struct led_trigger *trigger,
unsigned long *delay_on, unsigned long *delay_on,
unsigned long *delay_off, unsigned long *delay_off,
int invert); 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 * led_trigger_rename_static - rename a trigger
* @name: the new trigger name * @name: the new trigger name
...@@ -267,6 +283,15 @@ static inline void led_trigger_register_simple(const char *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_unregister_simple(struct led_trigger *trigger) {}
static inline void led_trigger_event(struct led_trigger *trigger, static inline void led_trigger_event(struct led_trigger *trigger,
enum led_brightness event) {} 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 */ #endif /* CONFIG_LEDS_TRIGGERS */
/* Trigger specific functions */ /* Trigger specific functions */
......
/*
* 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 { ...@@ -605,6 +605,8 @@ struct v4l2_subdev {
struct video_device *devnode; struct video_device *devnode;
/* pointer to the physical device, if any */ /* pointer to the physical device, if any */
struct device *dev; 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. */ /* Links this subdev to a global subdev_list or @notifier->done list. */
struct list_head async_list; struct list_head async_list;
/* Pointer to respective struct v4l2_async_subdev. */ /* Pointer to respective struct v4l2_async_subdev. */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment