2014-12-03 16:07:41

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 00/19] LED / flash API integration

This patch set is a follow-up of the LED / flash API integration
series [1].

========================
Changes since version 8:
========================

- added a new way of registering async sub-device
- switched to matching flash leds by DT phandles
- improved Device Tree bindings documentation
- split the drivers patches to LED Flash class
and V4L2 Flash part
- fixed indicator leds handling in v4l2-flash
- applied various fixes an cleanups

========================
Changes since version 7:
========================

- removed explicit support for indicator leds from
LED Flash class - indicator leds will be registered
as a separate LED Flash class devices
- added flash_sync_strobe sysfs attribute and related
V4L2_CID_FLASH_SYNC_STROBE control
- changed the way of matching V4L2 Flash sub-devices
in a media device, which entailed modification in
v4l2-async driver
- modified max77693 DT bindings documentation
- applied various fixes an cleanups

========================
Changes since version 6:
========================

- removed addition of public LED subsystem API for setting
torch brightness in favour of internal API for
synchronous and asynchronous led brightness level setting
- fixed possible race condition upon creating LED Flash class
related sysfs attributes

========================
Changes since version 5:
========================

- removed flash manager framework - its implementation needs
further thorough discussion.
- removed external strobe facilities from the LED Flash Class
and provided external_strobe_set op in v4l2-flash. LED subsystem
should be strobe provider agnostic.

Thanks,
Jacek Anaszewski

Jacek Anaszewski (19):
leds: Add LED Flash class extension to the LED subsystem
Documentation: leds: Add description of LED Flash class extension
mfd: max77693: Modify flash cell name identifiers
mfd: max77693: adjust max77693_led_platform_data
leds: Add support for max77693 mfd flash cell
DT: Add documentation for the mfd Maxim max77693
dt-binding: mfd: max77693: Add DT binding related macros
leds: Add driver for AAT1290 current regulator
of: Add Skyworks Solutions, Inc. vendor prefix
DT: Add documentation for the Skyworks AAT1290
v4l2-async: change custom.match callback argument type
v4l2-async: add V4L2_ASYNC_MATCH_CUSTOM_OF matching type
v4l2-ctrls: Add V4L2_CID_FLASH_SYNC_STROBE control
media: Add registration helpers for V4L2 flash sub-devices
Documentation: leds: Add description of v4l2-flash sub-device
exynos4-is: Add support for v4l2-flash subdevs
DT: Add documentation for exynos4-is 'flashes' property
leds: max77693: add support for V4L2 Flash sub-device
leds: aat1290: add support for V4L2 Flash sub-device

Documentation/DocBook/media/v4l/controls.xml | 11 +
.../devicetree/bindings/leds/leds-aat1290.txt | 17 +
.../devicetree/bindings/media/samsung-fimc.txt | 7 +
Documentation/devicetree/bindings/mfd/max77693.txt | 89 ++
.../devicetree/bindings/vendor-prefixes.txt | 1 +
Documentation/leds/leds-class-flash.txt | 63 ++
drivers/leds/Kconfig | 27 +
drivers/leds/Makefile | 3 +
drivers/leds/led-class-flash.c | 446 ++++++++
drivers/leds/led-class.c | 4 +
drivers/leds/leds-aat1290.c | 474 ++++++++
drivers/leds/leds-max77693.c | 1154 ++++++++++++++++++++
drivers/media/platform/exynos4-is/media-dev.c | 42 +-
drivers/media/platform/exynos4-is/media-dev.h | 13 +-
drivers/media/v4l2-core/Kconfig | 11 +
drivers/media/v4l2-core/Makefile | 2 +
drivers/media/v4l2-core/v4l2-async.c | 122 ++-
drivers/media/v4l2-core/v4l2-ctrls.c | 2 +
drivers/media/v4l2-core/v4l2-flash.c | 546 +++++++++
drivers/mfd/max77693.c | 4 +-
include/dt-bindings/mfd/max77693.h | 38 +
include/linux/led-class-flash.h | 186 ++++
include/linux/leds.h | 3 +
include/linux/mfd/max77693.h | 4 +-
include/media/v4l2-async.h | 6 +-
include/media/v4l2-flash.h | 139 +++
include/uapi/linux/v4l2-controls.h | 1 +
27 files changed, 3388 insertions(+), 27 deletions(-)
create mode 100644 Documentation/devicetree/bindings/leds/leds-aat1290.txt
create mode 100644 Documentation/leds/leds-class-flash.txt
create mode 100644 drivers/leds/led-class-flash.c
create mode 100644 drivers/leds/leds-aat1290.c
create mode 100644 drivers/leds/leds-max77693.c
create mode 100644 drivers/media/v4l2-core/v4l2-flash.c
create mode 100644 include/dt-bindings/mfd/max77693.h
create mode 100644 include/linux/led-class-flash.h
create mode 100644 include/media/v4l2-flash.h

--
1.7.9.5


2014-12-03 16:07:26

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 02/19] Documentation: leds: Add description of LED Flash class extension

The documentation being added contains overall description of the
LED Flash Class and the related sysfs attributes.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Bryan Wu <[email protected]>
Cc: Richard Purdie <[email protected]>
---
Documentation/leds/leds-class-flash.txt | 50 +++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
create mode 100644 Documentation/leds/leds-class-flash.txt

diff --git a/Documentation/leds/leds-class-flash.txt b/Documentation/leds/leds-class-flash.txt
new file mode 100644
index 0000000..82e58b1
--- /dev/null
+++ b/Documentation/leds/leds-class-flash.txt
@@ -0,0 +1,50 @@
+
+Flash LED handling under Linux
+==============================
+
+Some LED devices support two modes - torch and flash. The modes are
+supported by the LED class (see Documentation/leds/leds-class.txt)
+and LED Flash class respectively.
+
+In order to enable support for flash LEDs CONFIG_LEDS_CLASS_FLASH symbol
+must be defined in the kernel config. A flash LED driver must register
+in the LED subsystem with led_classdev_flash_register to gain flash
+capabilities.
+
+Following sysfs attributes are exposed for controlling flash led devices:
+
+ - flash_brightness - flash LED brightness in microamperes (RW)
+ - max_flash_brightness - maximum available flash LED brightness (RO)
+ - flash_timeout - flash strobe duration in microseconds (RW)
+ - max_flash_timeout - maximum available flash strobe duration (RO)
+ - flash_strobe - flash strobe state (RW)
+ - flash_sync_strobe - one flash device can control more than one
+ sub-led; when this atrribute is set to 1
+ the flash led will be strobed synchronously
+ with the other one controlled by the same
+ device; flash timeout setting is inherited
+ from the led being strobed explicitly and
+ flash brightness setting of a sub-led's
+ being synchronized is used (RW)
+ - flash_fault - bitmask of flash faults that may have occurred
+ possible flags are:
+ * 0x01 - flash controller voltage to the flash LED has exceeded
+ the limit specific to the flash controller
+ * 0x02 - the flash strobe was still on when the timeout set by
+ the user has expired; not all flash controllers may
+ set this in all such conditions
+ * 0x04 - the flash controller has overheated
+ * 0x08 - the short circuit protection of the flash controller
+ has been triggered
+ * 0x10 - current in the LED power supply has exceeded the limit
+ specific to the flash controller
+ * 0x40 - flash controller voltage to the flash LED has been
+ below the minimum limit specific to the flash
+ * 0x80 - the input voltage of the flash controller is below
+ the limit under which strobing the flash at full
+ current will not be possible. The condition persists
+ until this flag is no longer set
+ * 0x100 - the temperature of the LED has exceeded its allowed
+ upper limit
+
+ Flash faults are cleared by reading the attribute.
--
1.7.9.5

2014-12-03 16:07:45

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 03/19] mfd: max77693: Modify flash cell name identifiers

Change flash cell identifiers from max77693-flash to max77693-led
to avoid confusion with NOR/NAND Flash.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Chanwoo Choi <[email protected]>
Cc: Lee Jones <[email protected]>
---
drivers/mfd/max77693.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/mfd/max77693.c b/drivers/mfd/max77693.c
index a159593..cb14afa 100644
--- a/drivers/mfd/max77693.c
+++ b/drivers/mfd/max77693.c
@@ -53,8 +53,8 @@ static const struct mfd_cell max77693_devs[] = {
.of_compatible = "maxim,max77693-haptic",
},
{
- .name = "max77693-flash",
- .of_compatible = "maxim,max77693-flash",
+ .name = "max77693-led",
+ .of_compatible = "maxim,max77693-led",
},
};

--
1.7.9.5

2014-12-03 16:07:53

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 06/19] DT: Add documentation for the mfd Maxim max77693

This patch adds device tree binding documentation for
the flash cell of the Maxim max77693 multifunctional device.

Signed-off-by: Jacek Anaszewski <[email protected]>
Signed-off-by: Andrzej Hajda <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Lee Jones <[email protected]>
Cc: Chanwoo Choi <[email protected]>
Cc: Bryan Wu <[email protected]>
Cc: Richard Purdie <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Pawel Moll <[email protected]>
Cc: Mark Rutland <[email protected]>
Cc: Ian Campbell <[email protected]>
Cc: Kumar Gala <[email protected]>
Cc: <[email protected]>
---
Documentation/devicetree/bindings/mfd/max77693.txt | 89 ++++++++++++++++++++
1 file changed, 89 insertions(+)

diff --git a/Documentation/devicetree/bindings/mfd/max77693.txt b/Documentation/devicetree/bindings/mfd/max77693.txt
index 01e9f30..25a6e78 100644
--- a/Documentation/devicetree/bindings/mfd/max77693.txt
+++ b/Documentation/devicetree/bindings/mfd/max77693.txt
@@ -41,7 +41,66 @@ Optional properties:
To get more informations, please refer to documentaion.
[*] refer Documentation/devicetree/bindings/pwm/pwm.txt

+- led : the LED submodule device node
+
+There are two led outputs available - fled1 and fled2. Each of them can
+control a separate led or they can be connected together to double
+the maximum current for a single connected led. One led is represented
+by one child node.
+
+Required properties:
+- compatible : Must be "maxim,max77693-led".
+
+Optional properties:
+- maxim,fleds : Array of current outputs in order: fled1, fled2.
+ Note: both current outputs can be connected to a single led
+ Possible values:
+ MAX77693_LED_FLED_UNUSED - the output is left disconnected,
+ MAX77693_LED_FLED_USED - a diode is connected to the output.
+- maxim,trigger-type : Array of trigger types in order: flash, torch.
+ Possible trigger types:
+ MAX77693_LED_TRIG_TYPE_EDGE - Rising edge of the signal triggers
+ the flash/torch,
+ MAX77693_LED_TRIG_TYPE_LEVEL - Signal level controls duration of
+ the flash/torch.
+- maxim,trigger : Array of flags indicating which trigger can activate given led
+ in order: fled1, fled2.
+ Possible flag values (can be combined):
+ MAX77693_LED_TRIG_FLASHEN - FLASHEN pin of the chip,
+ MAX77693_LED_TRIG_TORCHEN - TORCHEN pin of the chip,
+ MAX77693_LED_TRIG_SOFTWARE - software via I2C command.
+- maxim,boost-mode :
+ In boost mode the device can produce up to 1.2A of total current
+ on both outputs. The maximum current on each output is reduced
+ to 625mA then. If there are two child led nodes defined then boost
+ is enabled by default.
+ Possible values:
+ MAX77693_LED_BOOST_OFF - no boost,
+ MAX77693_LED_BOOST_ADAPTIVE - adaptive mode,
+ MAX77693_LED_BOOST_FIXED - fixed mode.
+- maxim,boost-vout : Output voltage of the boost module in millivolts.
+- maxim,vsys-min : Low input voltage level in millivolts. Flash is not fired
+ if chip estimates that system voltage could drop below this level due
+ to flash power consumption.
+
+Required properties of the LED child node:
+- label : see Documentation/devicetree/bindings/leds/common.txt
+- maxim,fled_id : Identifier of the fled output the led is connected to;
+ MAX77693_LED_FLED1 - FLED1 output of the device - it has to be
+ used also if a single LED is connected to both outputs,
+ MAX77693_LED_FLED2 - FLED2 output of the device.
+
+Optional properties of the LED child node:
+- max-microamp : see Documentation/devicetree/bindings/leds/common.txt
+ Range: 15625 - 250000
+- flash-max-microamp : see Documentation/devicetree/bindings/leds/common.txt
+ Range: 15625 - 1000000
+- flash-timeout-microsec : see Documentation/devicetree/bindings/leds/common.txt
+ Range: 62500 - 1000000
+
Example:
+#include <dt-bindings/mfd/max77693.h>
+
max77693@66 {
compatible = "maxim,max77693";
reg = <0x66>;
@@ -73,4 +132,34 @@ Example:
pwms = <&pwm 0 40000 0>;
pwm-names = "haptic";
};
+
+ led {
+ compatible = "maxim,max77693-led";
+ maxim,fleds = <MAX77693_LED_FLED_USED
+ MAX77693_LED_FLED_USED>;
+ maxim,trigger = <MAX77693_LED_TRIG_ALL
+ (MAX77693_LED_TRIG_TORCHEN |
+ MAX77693_LED_TRIG_SOFTWARE)>;
+ maxim,trigger-type = <MAX77693_LED_TRIG_TYPE_EDGE
+ MAX77693_LED_TRIG_TYPE_LEVEL>;
+ maxim,boost-mode = <MAX77693_LED_BOOST_ADAPTIVE>;
+ maxim,boost-vout = <5000>;
+ maxim,vsys-min = <2400>;
+
+ camera1_flash: led1 {
+ maxim,fled_id = <MAX77693_LED_FLED1>;
+ label = "max77693-flash1";
+ max-microamp = <250000>;
+ flash-max-microamp = <625000>;
+ flash-timeout-microsec = <1000000>;
+ };
+
+ camera2_flash: led2 {
+ maxim,fled_id = <MAX77693_LED_FLED2>;
+ label = "max77693-flash2";
+ max-microamp = <250000>;
+ flash-max-microamp = <500000>;
+ flash-timeout-microsec = <1000000>;
+ };
+ };
};
--
1.7.9.5

2014-12-03 16:08:02

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 07/19] dt-binding: mfd: max77693: Add DT binding related macros

Add macros for max77693 led part related binding.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Lee Jones <[email protected]>
Cc: Chanwoo Choi <[email protected]>
---
include/dt-bindings/mfd/max77693.h | 38 ++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
create mode 100644 include/dt-bindings/mfd/max77693.h

diff --git a/include/dt-bindings/mfd/max77693.h b/include/dt-bindings/mfd/max77693.h
new file mode 100644
index 0000000..4011cb47
--- /dev/null
+++ b/include/dt-bindings/mfd/max77693.h
@@ -0,0 +1,38 @@
+/*
+ * This header provides macros for MAX77693 device binding
+ *
+ * Copyright (C) 2014, Samsung Electronics Co., Ltd.
+ *
+ * Author: Jacek Anaszewski <[email protected]>
+ */
+
+#ifndef __DT_BINDINGS_MAX77693_H__
+#define __DT_BINDINGS_MAX77693_H
+
+/* External control pins */
+#define MAX77693_LED_FLED_UNUSED 0
+#define MAX77693_LED_FLED_USED 1
+
+/* FLED pins */
+#define MAX77693_LED_FLED1 1
+#define MAX77693_LED_FLED2 2
+
+/* External trigger type */
+#define MAX77693_LED_TRIG_TYPE_EDGE 0
+#define MAX77693_LED_TRIG_TYPE_LEVEL 1
+
+/* Trigger flags */
+#define MAX77693_LED_TRIG_FLASHEN (1 << 0)
+#define MAX77693_LED_TRIG_TORCHEN (1 << 1)
+#define MAX77693_LED_TRIG_SOFTWARE (1 << 2)
+
+#define MAX77693_LED_TRIG_ALL (MAX77693_LED_TRIG_FLASHEN | \
+ MAX77693_LED_TRIG_TORCHEN | \
+ MAX77693_LED_TRIG_SOFTWARE)
+
+/* Boost modes */
+#define MAX77693_LED_BOOST_OFF 0
+#define MAX77693_LED_BOOST_ADAPTIVE 1
+#define MAX77693_LED_BOOST_FIXED 2
+
+#endif /* __DT_BINDINGS_MAX77693_H */
--
1.7.9.5

2014-12-03 16:08:11

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 08/19] leds: Add driver for AAT1290 current regulator

This patch adds a driver for the 1.5A Step-Up Current Regulator
for Flash LEDs. The device is programmed through a Skyworks proprietary
AS2Cwire serial digital interface.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Bryan Wu <[email protected]>
Cc: Richard Purdie <[email protected]>
---
drivers/leds/Kconfig | 7 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-aat1290.c | 413 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 421 insertions(+)
create mode 100644 drivers/leds/leds-aat1290.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 2e66d55..ec4b78c 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -39,6 +39,13 @@ config LEDS_88PM860X
This option enables support for on-chip LED drivers found on Marvell
Semiconductor 88PM8606 PMIC.

+config LEDS_AAT1290
+ tristate "LED support for the AAT1290"
+ depends on LEDS_CLASS_FLASH
+ depends on OF
+ help
+ This option enables support for the LEDs on the AAT1290.
+
config LEDS_LM3530
tristate "LCD Backlight driver for LM3530"
depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 57ca62b..b802251 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o

# LED Platform Drivers
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
+obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
diff --git a/drivers/leds/leds-aat1290.c b/drivers/leds/leds-aat1290.c
new file mode 100644
index 0000000..15d969b
--- /dev/null
+++ b/drivers/leds/leds-aat1290.c
@@ -0,0 +1,413 @@
+/*
+ * LED Flash class driver for the AAT1290
+ * 1.5A Step-Up Current Regulator for Flash LEDs
+ *
+ * Copyright (C) 2014, Samsung Electronics Co., Ltd.
+ * Author: Jacek Anaszewski <[email protected]>
+ *
+ * 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/slab.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/led-class-flash.h>
+#include <linux/leds.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/of.h>
+#include <linux/workqueue.h>
+
+#define AAT1290_MOVIE_MODE_CURRENT_ADDR 17
+#define AAT1290_FLASH_SAFETY_TIMER_ADDR 18
+#define AAT1290_MOVIE_MODE_CONFIG_ADDR 19
+#define AAT1290_MM_CURRENT_RATIO_ADDR 20
+#define AAT1290_LATCH_TIME_US 500
+#define AAT1290_EN_SET_TICK_TIME_US 1
+#define AAT1290_MOVIE_MODE_OFF 1
+#define AAT1290_MOVIE_MODE_ON 3
+#define AAT1290_MAX_MM_CURR_PERCENT_0 16
+#define AAT1290_MAX_MM_CURR_PERCENT_100 1
+#define AAT1290_FLASH_TM_NUM_LEVELS 16
+
+#define AAT1290_MM_TO_FL_1_92 1
+#define AAT1290_MM_TO_FL_3_7 2
+#define AAT1290_MM_TO_FL_5_5 3
+#define AAT1290_MM_TO_FL_7_3 4
+#define AAT1290_MM_TO_FL_9 5
+#define AAT1290_MM_TO_FL_10_7 6
+#define AAT1290_MM_TO_FL_12_4 7
+#define AAT1290_MM_TO_FL_14 8
+#define AAT1290_MM_TO_FL_15_9 9
+#define AAT1290_MM_TO_FL_17_5 10
+#define AAT1290_MM_TO_FL_19_1 11
+#define AAT1290_MM_TO_FL_20_8 12
+#define AAT1290_MM_TO_FL_22_4 13
+#define AAT1290_MM_TO_FL_24 14
+#define AAT1290_MM_TO_FL_25_6 15
+#define AAT1290_MM_TO_FL_OFF 16
+
+struct aat1290_led_settings {
+ struct led_flash_setting torch_brightness;
+ struct led_flash_setting flash_brightness;
+ struct led_flash_setting flash_timeout;
+};
+
+struct aat1290_led {
+ struct platform_device *pdev;
+ struct mutex lock;
+
+ struct led_classdev_flash ldev;
+
+ int flen_gpio;
+ int en_set_gpio;
+
+ u32 max_flash_tm;
+ bool movie_mode;
+
+ char *label;
+ unsigned int torch_brightness;
+ unsigned int flash_timeout;
+ struct work_struct work_brightness_set;
+};
+
+static struct aat1290_led *ldev_to_led(struct led_classdev_flash *ldev)
+{
+ return container_of(ldev, struct aat1290_led, ldev);
+}
+
+static void aat1290_as2cwire_write(struct aat1290_led *led, int addr, int value)
+{
+ int i;
+
+ gpio_set_value(led->flen_gpio, 0);
+ gpio_set_value(led->en_set_gpio, 0);
+
+ udelay(10);
+
+ /* write address */
+ for (i = 0; i < addr; ++i) {
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpio_set_value(led->en_set_gpio, 0);
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpio_set_value(led->en_set_gpio, 1);
+ }
+
+ udelay(AAT1290_LATCH_TIME_US);
+
+ /* write data */
+ for (i = 0; i < value; ++i) {
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpio_set_value(led->en_set_gpio, 0);
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpio_set_value(led->en_set_gpio, 1);
+ }
+
+ udelay(AAT1290_LATCH_TIME_US);
+}
+
+static void aat1290_set_flash_safety_timer(struct aat1290_led *led,
+ unsigned int micro_sec)
+{
+ struct led_classdev_flash *flash = &led->ldev;
+ struct led_flash_setting *flash_tm = &flash->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) {
+ gpio_set_value(led->flen_gpio, 0);
+ gpio_set_value(led->en_set_gpio, 0);
+ goto unlock;
+ }
+
+ 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);
+unlock:
+ 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 *flash = lcdev_to_flash(led_cdev);
+ struct aat1290_led *led = ldev_to_led(flash);
+
+ 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 *flash = lcdev_to_flash(led_cdev);
+ struct aat1290_led *led = ldev_to_led(flash);
+
+ aat1290_brightness_set(led, brightness);
+
+ return 0;
+}
+
+static int aat1290_led_flash_strobe_set(struct led_classdev_flash *flash,
+ bool state)
+
+{
+ struct aat1290_led *led = ldev_to_led(flash);
+ struct led_classdev *led_cdev = &flash->led_cdev;
+ struct led_flash_setting *timeout = &flash->timeout;
+
+ mutex_lock(&led->lock);
+
+ if (state == 0) {
+ gpio_set_value(led->flen_gpio, 0);
+ gpio_set_value(led->en_set_gpio, 0);
+ goto unlock;
+ }
+
+ aat1290_set_flash_safety_timer(led, timeout->val);
+
+ /*
+ * 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 value needs to be updated here to
+ * reflect the actual state.
+ */
+ led_cdev->brightness = 0;
+ led->movie_mode = false;
+
+ gpio_set_value(led->flen_gpio, 1);
+
+unlock:
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int aat1290_led_flash_timeout_set(struct led_classdev_flash *flash,
+ 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 device *dev)
+{
+ int ret;
+ char *pname = "label";
+
+ ret = of_property_read_string(dev->of_node, pname,
+ (const char **) &led->label);
+ if (ret < 0) {
+ dev_err(dev, "Error reading %s Device Tree property (%d)\n",
+ pname, ret);
+ return ret;
+ }
+
+ pname = "flash-timeout-microsec";
+
+ ret = of_property_read_u32(dev->of_node, pname, &led->max_flash_tm);
+ if (ret) {
+ dev_err(dev, "Error reading %s Device Tree property (%d)\n",
+ pname, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static void aat1290_init_flash_settings(struct aat1290_led *led,
+ struct aat1290_led_settings *s)
+{
+ struct led_flash_setting *setting;
+
+ /* Init flash intensity setting */
+ setting = &s->torch_brightness;
+ /*
+ * Torch current is adjustable in logarithmic fashion and thus
+ * it is not possible to define fixed step in microamperes.
+ * Instead led brightness levels are used to make possible
+ * setting all the supported levels from V4L2 Flash sub-device.
+ */
+ setting->min = 1;
+ setting->max = AAT1290_MAX_MM_CURR_PERCENT_0 -
+ AAT1290_MAX_MM_CURR_PERCENT_100;
+ setting->step = 1;
+ setting->val = setting->max;
+
+ /* Init flash timeout setting */
+ setting = &s->flash_timeout;
+ setting->min = led->max_flash_tm / AAT1290_FLASH_TM_NUM_LEVELS;
+ setting->max = setting->min * AAT1290_FLASH_TM_NUM_LEVELS;
+ setting->step = setting->min;
+ setting->val = setting->max;
+}
+
+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 *dev_node = pdev->dev.of_node;
+ struct aat1290_led *led;
+ struct led_classdev *led_cdev;
+ struct led_classdev_flash *flash;
+ struct aat1290_led_settings settings;
+ int flen_gpio, enset_gpio, ret;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pdev = pdev;
+ platform_set_drvdata(pdev, led);
+
+ if (!dev_node)
+ return -ENXIO;
+
+ flen_gpio = of_get_gpio(dev_node, 0);
+ if (gpio_is_valid(flen_gpio)) {
+ ret = gpio_request_one(flen_gpio, GPIOF_DIR_OUT,
+ "aat1290_flen");
+ if (ret < 0) {
+ dev_err(dev,
+ "failed to request GPIO %d, error %d\n",
+ flen_gpio, ret);
+ goto error_gpio_flen;
+ }
+ }
+ led->flen_gpio = flen_gpio;
+
+ enset_gpio = of_get_gpio(dev_node, 1);
+ if (gpio_is_valid(enset_gpio)) {
+ ret = gpio_request_one(enset_gpio, GPIOF_DIR_OUT,
+ "aat1290_en_set");
+ if (ret < 0) {
+ dev_err(dev,
+ "failed to request GPIO %d, error %d\n",
+ enset_gpio, ret);
+ goto error_gpio_en_set;
+ }
+ }
+ led->en_set_gpio = enset_gpio;
+
+ ret = aat1290_led_parse_dt(led, &pdev->dev);
+ if (ret < 0)
+ goto error_gpio_en_set;
+
+ mutex_init(&led->lock);
+
+ flash = &led->ldev;
+
+ /* Init flash settings */
+ aat1290_init_flash_settings(led, &settings);
+
+ flash->timeout = settings.flash_timeout;
+
+ /* Init led class */
+ led_cdev = &flash->led_cdev;
+ led_cdev->name = led->label;
+ led_cdev->brightness_set = aat1290_led_brightness_set;
+ led_cdev->brightness_set_sync = aat1290_led_brightness_set_sync;
+ led_cdev->max_brightness = settings.torch_brightness.max;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+
+ INIT_WORK(&led->work_brightness_set, aat1290_brightness_set_work);
+
+ flash->ops = &flash_ops;
+
+ /* Register in the LED subsystem. */
+ ret = led_classdev_flash_register(&pdev->dev, flash);
+ if (ret < 0)
+ goto error_gpio_en_set;
+
+ return 0;
+
+error_gpio_en_set:
+ if (gpio_is_valid(enset_gpio))
+ gpio_free(enset_gpio);
+error_gpio_flen:
+ if (gpio_is_valid(flen_gpio))
+ gpio_free(flen_gpio);
+ mutex_destroy(&led->lock);
+
+ return ret;
+}
+
+static int aat1290_led_remove(struct platform_device *pdev)
+{
+ struct aat1290_led *led = platform_get_drvdata(pdev);
+
+ led_classdev_flash_unregister(&led->ldev);
+ cancel_work_sync(&led->work_brightness_set);
+
+ if (gpio_is_valid(led->en_set_gpio))
+ gpio_free(led->en_set_gpio);
+ if (gpio_is_valid(led->flen_gpio))
+ gpio_free(led->flen_gpio);
+
+ mutex_destroy(&led->lock);
+
+ return 0;
+}
+
+static 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-led",
+ .owner = THIS_MODULE,
+ .of_match_table = aat1290_led_dt_match,
+ },
+};
+
+module_platform_driver(aat1290_led_driver);
+
+MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
+MODULE_DESCRIPTION("Skyworks Current Regulator for Flash LEDs");
+MODULE_LICENSE("GPL");
--
1.7.9.5

2014-12-03 16:08:23

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 10/19] DT: Add documentation for the Skyworks AAT1290

This patch adds device tree binding documentation for
1.5A Step-Up Current Regulator for Flash LEDs.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Bryan Wu <[email protected]>
Cc: Richard Purdie <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Pawel Moll <[email protected]>
Cc: Mark Rutland <[email protected]>
Cc: Ian Campbell <[email protected]>
Cc: Kumar Gala <[email protected]>
Cc: <[email protected]>
---
.../devicetree/bindings/leds/leds-aat1290.txt | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
create mode 100644 Documentation/devicetree/bindings/leds/leds-aat1290.txt

diff --git a/Documentation/devicetree/bindings/leds/leds-aat1290.txt b/Documentation/devicetree/bindings/leds/leds-aat1290.txt
new file mode 100644
index 0000000..17b8f05
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-aat1290.txt
@@ -0,0 +1,17 @@
+* Skyworks Solutions, Inc. AAT1290 Current Regulator for Flash LEDs
+
+Required properties:
+
+- compatible : Must be "skyworks,aat1290".
+- gpios : Two gpio pins in order FLEN, EN/SET.
+- flash-timeout-microsec : Maximum flash timeout in microseconds -
+ it can be calculated using following formula:
+ T = 8.82 * 10^9 * Ct.
+
+Example:
+
+flash_led: led {
+ compatible = "skyworks,aat1290";
+ gpios = <&gpj1 1 0>, <&gpj1 2 0>;
+ flash-timeout-microsec = <1940000>;
+}
--
1.7.9.5

2014-12-03 16:08:28

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 11/19] v4l2-async: change custom.match callback argument type

It is useful to have an access to the async sub-device
being matched, not only to the related struct device.
Change match callback argument from struct device
to struct v4l2_subdev.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Guennadi Liakhovetski <[email protected]>
Cc: Laurent Pinchart <[email protected]>
Cc: Hans Verkuil <[email protected]>
---
drivers/media/v4l2-core/v4l2-async.c | 16 ++++++++--------
include/media/v4l2-async.h | 2 +-
2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c
index 85a6a34..8140992 100644
--- a/drivers/media/v4l2-core/v4l2-async.c
+++ b/drivers/media/v4l2-core/v4l2-async.c
@@ -22,10 +22,10 @@
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>

-static bool match_i2c(struct device *dev, struct v4l2_async_subdev *asd)
+static bool match_i2c(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
{
#if IS_ENABLED(CONFIG_I2C)
- struct i2c_client *client = i2c_verify_client(dev);
+ struct i2c_client *client = i2c_verify_client(sd->dev);
return client &&
asd->match.i2c.adapter_id == client->adapter->nr &&
asd->match.i2c.address == client->addr;
@@ -34,14 +34,14 @@ static bool match_i2c(struct device *dev, struct v4l2_async_subdev *asd)
#endif
}

-static bool match_devname(struct device *dev, struct v4l2_async_subdev *asd)
+static bool match_devname(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
{
- return !strcmp(asd->match.device_name.name, dev_name(dev));
+ return !strcmp(asd->match.device_name.name, dev_name(sd->dev));
}

-static bool match_of(struct device *dev, struct v4l2_async_subdev *asd)
+static bool match_of(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
{
- return dev->of_node == asd->match.of.node;
+ return sd->dev->of_node == asd->match.of.node;
}

static LIST_HEAD(subdev_list);
@@ -52,7 +52,7 @@ static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *
struct v4l2_subdev *sd)
{
struct v4l2_async_subdev *asd;
- bool (*match)(struct device *, struct v4l2_async_subdev *);
+ bool (*match)(struct v4l2_subdev *, struct v4l2_async_subdev *);

list_for_each_entry(asd, &notifier->waiting, list) {
/* bus_type has been verified valid before */
@@ -79,7 +79,7 @@ static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *
}

/* match cannot be NULL here */
- if (match(sd->dev, asd))
+ if (match(sd, asd))
return asd;
}

diff --git a/include/media/v4l2-async.h b/include/media/v4l2-async.h
index 7683569..1c0b586 100644
--- a/include/media/v4l2-async.h
+++ b/include/media/v4l2-async.h
@@ -51,7 +51,7 @@ struct v4l2_async_subdev {
unsigned short address;
} i2c;
struct {
- bool (*match)(struct device *,
+ bool (*match)(struct v4l2_subdev *,
struct v4l2_async_subdev *);
void *priv;
} custom;
--
1.7.9.5

2014-12-03 16:08:35

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 12/19] v4l2-async: add V4L2_ASYNC_MATCH_CUSTOM_OF matching type

There are cases where a v4l2 sub-device is not related to the
main Device Tree node of a device, but to its child node.
Add v4l2_async_get_of_node_by_subdev function to facilitate
associating a sub-device with different Device Tree node
than the one from the related struct device. Added is also
V4L2_ASYNC_MATCH_CUSTOM_OF matching type to declare this
type of matching.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Guennadi Liakhovetski <[email protected]>
Cc: Laurent Pinchart <[email protected]>
Cc: Hans Verkuil <[email protected]>
---
drivers/media/v4l2-core/v4l2-async.c | 106 ++++++++++++++++++++++++++++++----
include/media/v4l2-async.h | 4 ++
2 files changed, 98 insertions(+), 12 deletions(-)

diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c
index 8140992..faa16484 100644
--- a/drivers/media/v4l2-core/v4l2-async.c
+++ b/drivers/media/v4l2-core/v4l2-async.c
@@ -22,6 +22,17 @@
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>

+static LIST_HEAD(subdev_list);
+static LIST_HEAD(notifier_list);
+static LIST_HEAD(custom_of_list);
+static DEFINE_MUTEX(list_lock);
+
+struct v4l2_subdev_to_of_node {
+ struct v4l2_subdev *sd;
+ struct device_node *node;
+ struct list_head list;
+};
+
static bool match_i2c(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
{
#if IS_ENABLED(CONFIG_I2C)
@@ -44,9 +55,17 @@ static bool match_of(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
return sd->dev->of_node == asd->match.of.node;
}

-static LIST_HEAD(subdev_list);
-static LIST_HEAD(notifier_list);
-static DEFINE_MUTEX(list_lock);
+static bool match_custom_of(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
+{
+ struct v4l2_subdev_to_of_node *sd_to_of;
+
+ list_for_each_entry(sd_to_of, &custom_of_list, list)
+ if ((sd_to_of->sd == sd) &&
+ (sd_to_of->node == asd->match.of.node))
+ return true;
+
+ return false;
+}

static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd)
@@ -72,6 +91,9 @@ static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *
case V4L2_ASYNC_MATCH_OF:
match = match_of;
break;
+ case V4L2_ASYNC_MATCH_CUSTOM_OF:
+ match = match_custom_of;
+ break;
default:
/* Cannot happen, unless someone breaks us */
WARN_ON(true);
@@ -120,9 +142,19 @@ static int v4l2_async_test_notify(struct v4l2_async_notifier *notifier,

static void v4l2_async_cleanup(struct v4l2_subdev *sd)
{
+ struct v4l2_subdev_to_of_node *sd_to_of, *tmp;
+
v4l2_device_unregister_subdev(sd);
/* Subdevice driver will reprobe and put the subdev back onto the list */
list_del_init(&sd->async_list);
+
+ list_for_each_entry_safe(sd_to_of, tmp, &custom_of_list, list) {
+ if (sd_to_of->sd == sd) {
+ list_del(&sd_to_of->list);
+ kfree (sd_to_of);
+ }
+ }
+
sd->asd = NULL;
sd->dev = NULL;
}
@@ -149,6 +181,7 @@ int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
case V4L2_ASYNC_MATCH_DEVNAME:
case V4L2_ASYNC_MATCH_I2C:
case V4L2_ASYNC_MATCH_OF:
+ case V4L2_ASYNC_MATCH_CUSTOM_OF:
break;
default:
dev_err(notifier->v4l2_dev ? notifier->v4l2_dev->dev : NULL,
@@ -262,32 +295,65 @@ void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)
}
EXPORT_SYMBOL(v4l2_async_notifier_unregister);

-int v4l2_async_register_subdev(struct v4l2_subdev *sd)
+static int __v4l2_async_register_subdev(struct v4l2_subdev *sd)
{
struct v4l2_async_notifier *notifier;

- mutex_lock(&list_lock);
-
INIT_LIST_HEAD(&sd->async_list);

list_for_each_entry(notifier, &notifier_list, list) {
struct v4l2_async_subdev *asd = v4l2_async_belongs(notifier, sd);
- if (asd) {
- int ret = v4l2_async_test_notify(notifier, sd, asd);
- mutex_unlock(&list_lock);
- return ret;
- }
+ if (asd)
+ return v4l2_async_test_notify(notifier, sd, asd);
}

/* None matched, wait for hot-plugging */
list_add(&sd->async_list, &subdev_list);

+ return 0;
+}
+
+int v4l2_async_register_subdev(struct v4l2_subdev *sd)
+{
+ int ret;
+
+ mutex_lock(&list_lock);
+
+ ret = __v4l2_async_register_subdev(sd);
+
mutex_unlock(&list_lock);

- return 0;
+ return ret;
}
EXPORT_SYMBOL(v4l2_async_register_subdev);

+int v4l2_async_register_subdev_with_of(struct v4l2_subdev *sd,
+ struct device_node *of_node)
+{
+ int ret;
+ struct v4l2_subdev_to_of_node *sd_to_of;
+
+ sd_to_of = kmalloc(sizeof(*sd_to_of), GFP_KERNEL);
+ if (!sd_to_of)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&sd_to_of->list);
+
+ sd_to_of->sd = sd;
+ sd_to_of->node = of_node;
+
+ mutex_lock(&list_lock);
+
+ list_add(&sd_to_of->list, &custom_of_list);
+
+ ret = __v4l2_async_register_subdev(sd);
+
+ mutex_unlock(&list_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(v4l2_async_register_subdev_with_of);
+
void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)
{
struct v4l2_async_notifier *notifier = sd->notifier;
@@ -310,3 +376,19 @@ void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)
mutex_unlock(&list_lock);
}
EXPORT_SYMBOL(v4l2_async_unregister_subdev);
+
+/* caller must ensure list_lock held */
+struct device_node *v4l2_async_get_of_node_by_subdev(struct v4l2_subdev *sd)
+{
+ struct v4l2_subdev_to_of_node *sd_to_of;
+
+ lockdep_assert_held(&list_lock);
+
+ list_for_each_entry(sd_to_of, &custom_of_list, list) {
+ if (sd_to_of->sd == sd)
+ return sd_to_of->node;
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL(v4l2_async_get_of_node_by_subdev);
diff --git a/include/media/v4l2-async.h b/include/media/v4l2-async.h
index 1c0b586..10fce8e 100644
--- a/include/media/v4l2-async.h
+++ b/include/media/v4l2-async.h
@@ -28,6 +28,7 @@ enum v4l2_async_match_type {
V4L2_ASYNC_MATCH_DEVNAME,
V4L2_ASYNC_MATCH_I2C,
V4L2_ASYNC_MATCH_OF,
+ V4L2_ASYNC_MATCH_CUSTOM_OF,
};

/**
@@ -93,5 +94,8 @@ int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
struct v4l2_async_notifier *notifier);
void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier);
int v4l2_async_register_subdev(struct v4l2_subdev *sd);
+int v4l2_async_register_subdev_with_of(struct v4l2_subdev *sd,
+ struct device_node *of_node);
void v4l2_async_unregister_subdev(struct v4l2_subdev *sd);
+struct device_node *v4l2_async_get_of_node_by_subdev(struct v4l2_subdev *sd);
#endif
--
1.7.9.5

2014-12-03 16:08:47

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 04/19] mfd: max77693: adjust max77693_led_platform_data

Add "label" array for Device Tree strings with the name of a LED device
and make flash_timeout a two element array, for caching the sub-led
related flash timeout. Added is also an array for caching pointers to the
sub-nodes representing sub-leds.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Chanwoo Choi <[email protected]>
Cc: Lee Jones <[email protected]>
---
include/linux/mfd/max77693.h | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
index f0b6585..c80ee99 100644
--- a/include/linux/mfd/max77693.h
+++ b/include/linux/mfd/max77693.h
@@ -88,16 +88,18 @@ enum max77693_led_boost_mode {
};

struct max77693_led_platform_data {
+ const char *label[2];
u32 fleds[2];
u32 iout_torch[2];
u32 iout_flash[2];
u32 trigger[2];
u32 trigger_type[2];
+ u32 flash_timeout[2];
u32 num_leds;
u32 boost_mode;
- u32 flash_timeout;
u32 boost_vout;
u32 low_vsys;
+ struct device_node *sub_nodes[2];
};

/* MAX77693 */
--
1.7.9.5

2014-12-03 16:08:54

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 15/19] Documentation: leds: Add description of v4l2-flash sub-device

This patch extends LED Flash class documention by
the description of interactions with v4l2-flash sub-device.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Acked-by: Sakari Ailus <[email protected]>
Cc: Bryan Wu <[email protected]>
Cc: Richard Purdie <[email protected]>
---
Documentation/leds/leds-class-flash.txt | 13 +++++++++++++
1 file changed, 13 insertions(+)

diff --git a/Documentation/leds/leds-class-flash.txt b/Documentation/leds/leds-class-flash.txt
index 82e58b1..bc3855b 100644
--- a/Documentation/leds/leds-class-flash.txt
+++ b/Documentation/leds/leds-class-flash.txt
@@ -48,3 +48,16 @@ Following sysfs attributes are exposed for controlling flash led devices:
upper limit

Flash faults are cleared by reading the attribute.
+
+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. On remove the
+v4l2_flash_release function has to be called (see <media/v4l2-flash.h>).
+
+After proper initialization a V4L2 Flash sub-device is created. The sub-device
+exposes a number of V4L2 controls, which allow for controlling a LED Flash class
+device with use of its internal kernel 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.
--
1.7.9.5

2014-12-03 16:09:02

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 16/19] exynos4-is: Add support for v4l2-flash subdevs

This patch adds suppport for external v4l2-flash devices.
The support includes parsing camera-flash DT property
and asynchronous subdevice registration.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Sylwester Nawrocki <[email protected]>
---
drivers/media/platform/exynos4-is/media-dev.c | 42 +++++++++++++++++++++++--
drivers/media/platform/exynos4-is/media-dev.h | 13 +++++++-
2 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/drivers/media/platform/exynos4-is/media-dev.c b/drivers/media/platform/exynos4-is/media-dev.c
index f315ef9..b422d2e 100644
--- a/drivers/media/platform/exynos4-is/media-dev.c
+++ b/drivers/media/platform/exynos4-is/media-dev.c
@@ -451,6 +451,25 @@ rpm_put:
return ret;
}

+static void fimc_md_register_flash_entities(struct fimc_md *fmd)
+{
+ struct device_node *parent = fmd->pdev->dev.of_node;
+ struct device_node *np;
+ int i = 0;
+
+ do {
+ np = of_parse_phandle(parent, "flashes", i);
+ if (np) {
+ fmd->flash[fmd->num_flashes].asd.match_type =
+ V4L2_ASYNC_MATCH_CUSTOM_OF;
+ fmd->flash[fmd->num_flashes].asd.match.of.node = np;
+ fmd->num_flashes++;
+ fmd->async_subdevs[fmd->num_sensors + i] =
+ &fmd->flash[i].asd;
+ }
+ } while (np && (++i < FIMC_MAX_FLASHES));
+}
+
static int __of_get_csis_id(struct device_node *np)
{
u32 reg = 0;
@@ -1272,9 +1291,24 @@ static int subdev_notifier_bound(struct v4l2_async_notifier *notifier,
struct v4l2_async_subdev *asd)
{
struct fimc_md *fmd = notifier_to_fimc_md(notifier);
+ struct device_node *node;
struct fimc_sensor_info *si = NULL;
int i;

+ node = v4l2_async_get_of_node_by_subdev(subdev);
+ if (node) {
+ /* Register flash subdev if detected any */
+ for (i = 0; i < ARRAY_SIZE(fmd->flash); i++) {
+ if (fmd->flash[i].asd.match.of.node == node) {
+ fmd->flash[i].subdev = subdev;
+ fmd->num_flashes++;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+ }
+
/* Find platform data for this sensor subdev */
for (i = 0; i < ARRAY_SIZE(fmd->sensor); i++)
if (fmd->sensor[i].asd.match.of.node == subdev->dev->of_node)
@@ -1385,6 +1419,8 @@ static int fimc_md_probe(struct platform_device *pdev)
goto err_m_ent;
}

+ fimc_md_register_flash_entities(fmd);
+
mutex_unlock(&fmd->media_dev.graph_mutex);

ret = device_create_file(&pdev->dev, &dev_attr_subdev_conf_mode);
@@ -1401,12 +1437,14 @@ static int fimc_md_probe(struct platform_device *pdev)
goto err_attr;
}

- if (fmd->num_sensors > 0) {
+ if (fmd->num_sensors > 0 || fmd->num_flashes > 0) {
fmd->subdev_notifier.subdevs = fmd->async_subdevs;
- fmd->subdev_notifier.num_subdevs = fmd->num_sensors;
+ fmd->subdev_notifier.num_subdevs = fmd->num_sensors +
+ fmd->num_flashes;
fmd->subdev_notifier.bound = subdev_notifier_bound;
fmd->subdev_notifier.complete = subdev_notifier_complete;
fmd->num_sensors = 0;
+ fmd->num_flashes = 0;

ret = v4l2_async_notifier_register(&fmd->v4l2_dev,
&fmd->subdev_notifier);
diff --git a/drivers/media/platform/exynos4-is/media-dev.h b/drivers/media/platform/exynos4-is/media-dev.h
index 0321454..feff9c8 100644
--- a/drivers/media/platform/exynos4-is/media-dev.h
+++ b/drivers/media/platform/exynos4-is/media-dev.h
@@ -34,6 +34,8 @@

#define FIMC_MAX_SENSORS 4
#define FIMC_MAX_CAMCLKS 2
+#define FIMC_MAX_FLASHES 2
+#define FIMC_MAX_ASYNC_SUBDEVS (FIMC_MAX_SENSORS + FIMC_MAX_FLASHES)
#define DEFAULT_SENSOR_CLK_FREQ 24000000U

/* LCD/ISP Writeback clocks (PIXELASYNCMx) */
@@ -93,6 +95,11 @@ struct fimc_sensor_info {
struct fimc_dev *host;
};

+struct fimc_flash_info {
+ struct v4l2_subdev *subdev;
+ struct v4l2_async_subdev asd;
+};
+
struct cam_clk {
struct clk_hw hw;
struct fimc_md *fmd;
@@ -104,6 +111,8 @@ struct cam_clk {
* @csis: MIPI CSIS subdevs data
* @sensor: array of registered sensor subdevs
* @num_sensors: actual number of registered sensors
+ * @flash: array of registered flash subdevs
+ * @num_flashes: actual number of registered flashes
* @camclk: external sensor clock information
* @fimc: array of registered fimc devices
* @fimc_is: fimc-is data structure
@@ -123,6 +132,8 @@ struct fimc_md {
struct fimc_csis_info csis[CSIS_MAX_ENTITIES];
struct fimc_sensor_info sensor[FIMC_MAX_SENSORS];
int num_sensors;
+ struct fimc_flash_info flash[FIMC_MAX_FLASHES];
+ int num_flashes;
struct fimc_camclk_info camclk[FIMC_MAX_CAMCLKS];
struct clk *wbclk[FIMC_MAX_WBCLKS];
struct fimc_lite *fimc_lite[FIMC_LITE_MAX_DEVS];
@@ -149,7 +160,7 @@ struct fimc_md {
} clk_provider;

struct v4l2_async_notifier subdev_notifier;
- struct v4l2_async_subdev *async_subdevs[FIMC_MAX_SENSORS];
+ struct v4l2_async_subdev *async_subdevs[FIMC_MAX_ASYNC_SUBDEVS];

bool user_subdev_api;
spinlock_t slock;
--
1.7.9.5

2014-12-03 16:09:16

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 18/19] leds: max77693: add support for V4L2 Flash sub-device

Add support for V4L2 Flash sub-device to the max77693 LED Flash class
driver. The support allows for V4L2 Flash sub-device to take the control
of the LED Flash class device.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Bryan Wu <[email protected]>
Cc: Richard Purdie <[email protected]>
Cc: Sakari Ailus <[email protected]>
---
drivers/leds/leds-max77693.c | 133 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 132 insertions(+), 1 deletion(-)

diff --git a/drivers/leds/leds-max77693.c b/drivers/leds/leds-max77693.c
index 67a2f8f..e93edbd 100644
--- a/drivers/leds/leds-max77693.c
+++ b/drivers/leds/leds-max77693.c
@@ -21,6 +21,7 @@
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
+#include <media/v4l2-flash.h>

#define MODE_OFF 0
#define MODE_FLASH1 (1 << 0)
@@ -49,6 +50,7 @@ enum {
struct max77693_sub_led {
struct led_classdev_flash ldev;
struct work_struct work_brightness_set;
+ struct v4l2_flash *v4l2_flash;

unsigned int torch_brightness;
unsigned int flash_timeout;
@@ -602,6 +604,32 @@ unlock: \
return ret; \
}

+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+#define MAX77693_LED_FLASH_EXTERNAL_STROBE_SET(ID) \
+static int max77693_led##ID##_external_strobe_set( \
+ struct v4l2_flash *v4l2_flash, \
+ bool enable) \
+{ \
+ struct max77693_led *led = \
+ ldev##ID##_to_led(v4l2_flash->flash); \
+ int ret; \
+ \
+ mutex_lock(&led->lock); \
+ \
+ if (enable) \
+ ret = max77693_add_mode(led, MODE_FLASH_EXTERNAL##ID); \
+ else \
+ ret = max77693_clear_mode(led, \
+ MODE_FLASH_EXTERNAL##ID); \
+ \
+ mutex_unlock(&led->lock); \
+ \
+ return ret; \
+}
+#else
+#define MAX77693_LED_FLASH_EXTERNAL_STROBE_SET(ID)
+#endif
+
#define MAX77693_LED_FLASH_FAULT_GET(ID) \
static int max77693_led##ID##_flash_fault_get( \
struct led_classdev_flash *flash, \
@@ -670,6 +698,7 @@ MAX77693_LED_TORCH_BRIGHTNESS_SET(1)
MAX77693_LED_FLASH_BRIGHTNESS_SET(1)
MAX77693_LED_FLASH_STROBE_SET(1)
MAX77693_LED_FLASH_STROBE_GET(1)
+MAX77693_LED_FLASH_EXTERNAL_STROBE_SET(1)
MAX77693_LED_FLASH_TIMEOUT_SET(1)
MAX77693_LED_FLASH_FAULT_GET(1)

@@ -679,6 +708,7 @@ MAX77693_LED_TORCH_BRIGHTNESS_SET(2)
MAX77693_LED_FLASH_BRIGHTNESS_SET(2)
MAX77693_LED_FLASH_STROBE_SET(2)
MAX77693_LED_FLASH_STROBE_GET(2)
+MAX77693_LED_FLASH_EXTERNAL_STROBE_SET(2)
MAX77693_LED_FLASH_TIMEOUT_SET(2)
MAX77693_LED_FLASH_FAULT_GET(2)

@@ -838,9 +868,35 @@ static const struct led_flash_ops flash_ops##ID = { \
.fault_get = max77693_led##ID##_flash_fault_get, \
}

+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+#define MAX77693_LED_V4L2_FLASH_OPS(ID) \
+static const struct v4l2_flash_ops v4l2_flash##ID##_ops = { \
+ .external_strobe_set = max77693_led##ID##_external_strobe_set, \
+}
+
+#define MAX77693_LED_GET_V4L2_FLASH_OPS(ID) \
+static inline const struct v4l2_flash_ops *get_v4l2_flash##ID##_ops(void) \
+{ \
+ return &v4l2_flash##ID##_ops; \
+}
+#else
+#define MAX77693_LED_V4L2_FLASH_OPS(ID)
+
+#define MAX77693_LED_GET_V4L2_FLASH_OPS(ID) \
+static inline const struct v4l2_flash_ops *get_v4l2_flash##ID##_ops(void) \
+{ \
+ return NULL; \
+}
+#endif
+
MAX77693_LED_INIT_FLASH_OPS(1);
MAX77693_LED_INIT_FLASH_OPS(2);

+MAX77693_LED_V4L2_FLASH_OPS(1);
+MAX77693_LED_V4L2_FLASH_OPS(2);
+MAX77693_LED_GET_V4L2_FLASH_OPS(1);
+MAX77693_LED_GET_V4L2_FLASH_OPS(2);
+
static void max77693_init_flash_settings(struct max77693_led *led,
struct max77693_led_settings *s,
int led_id)
@@ -876,18 +932,68 @@ static void max77693_init_flash_settings(struct max77693_led *led,
setting->val = setting->max;
}

+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static void max77693_init_v4l2_ctrl_config(struct max77693_led_settings *s,
+ struct max77693_led_platform_data *p,
+ struct v4l2_flash_ctrl_config *config,
+ int led_id)
+{
+ struct led_flash_setting *setting;
+ struct v4l2_ctrl_config *c;
+
+ c = &config->intensity;
+ setting = &s->torch_brightness;
+ c->min = setting->min;
+ c->max = setting->max;
+ c->step = setting->step;
+ c->def = setting->val;
+
+ c = &config->flash_intensity;
+ setting = &s->flash_brightness;
+ c->min = setting->min;
+ c->max = setting->max;
+ c->step = setting->step;
+ c->def = setting->val;
+
+ c = &config->flash_timeout;
+ setting = &s->flash_timeout;
+ c->min = setting->min;
+ c->max = setting->max;
+ c->step = setting->step;
+ c->def = setting->val;
+
+ /* Init flash faults config */
+ config->flash_faults = V4L2_FLASH_FAULT_OVER_VOLTAGE |
+ V4L2_FLASH_FAULT_SHORT_CIRCUIT |
+ V4L2_FLASH_FAULT_OVER_CURRENT;
+
+ config->has_external_strobe =
+ !!(p->trigger[led_id] & MAX77693_LED_TRIG_FLASH);
+}
+#else
+#define max77693_init_v4l2_ctrl_config(s, p, config, led_id)
+#endif
+
static int max77693_register_led(struct max77693_led *led, int id)
{
struct platform_device *pdev = led->pdev;
struct led_classdev_flash *flash;
struct led_classdev *led_cdev;
struct max77693_sub_led *sub_leds = led->sub_leds;
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+ struct v4l2_flash_ctrl_config v4l2_flash_config;
+#endif
+ const struct v4l2_flash_ops *v4l2_flash_ops = NULL;
struct max77693_led_settings settings;
+ int ret;

flash = &sub_leds[id].ldev;

/* Initialize flash settings */
max77693_init_flash_settings(led, &settings, id);
+ /* Initialize V4L2 Flash config basing on initialized settings */
+ max77693_init_v4l2_ctrl_config(&settings, led->pdata,
+ &v4l2_flash_config, id);

/* Initialize LED Flash class device */
led_cdev = &flash->led_cdev;
@@ -901,6 +1007,7 @@ static int max77693_register_led(struct max77693_led *led, int id)
INIT_WORK(&sub_leds[id].work_brightness_set,
max77693_led1_brightness_set_work);
flash->ops = &flash_ops1;
+ v4l2_flash_ops = get_v4l2_flash1_ops();
} else {
led_cdev->brightness_set = max77693_led2_brightness_set;
led_cdev->brightness_set_sync =
@@ -908,6 +1015,7 @@ static int max77693_register_led(struct max77693_led *led, int id)
INIT_WORK(&sub_leds[id].work_brightness_set,
max77693_led2_brightness_set_work);
flash->ops = &flash_ops2;
+ v4l2_flash_ops = get_v4l2_flash2_ops();
}

led_cdev->max_brightness = settings.torch_brightness.val /
@@ -921,7 +1029,27 @@ static int max77693_register_led(struct max77693_led *led, int id)
sub_leds[id].flash_timeout = flash->timeout.val;

/* Register in the LED subsystem. */
- return led_classdev_flash_register(&pdev->dev, flash);
+ ret = led_classdev_flash_register(&pdev->dev, flash);
+ if (ret < 0)
+ return ret;
+
+ sub_leds[id].v4l2_flash =
+ v4l2_flash_init(flash,
+ v4l2_flash_ops,
+ led->pdata->sub_nodes[id],
+ &v4l2_flash_config);
+
+ if (IS_ERR(sub_leds[id].v4l2_flash)) {
+ ret = PTR_ERR(sub_leds[id].v4l2_flash);
+ goto err_v4l2_flash_init;
+ }
+
+ return 0;
+
+err_v4l2_flash_init:
+ led_classdev_flash_unregister(flash);
+
+ return ret;
}

static int max77693_led_probe(struct platform_device *pdev)
@@ -972,6 +1100,7 @@ static int max77693_led_probe(struct platform_device *pdev)
err_register_led2:
if (!p->fleds[FLED1])
goto err_setup;
+ v4l2_flash_release(sub_leds[FLED1].v4l2_flash);
led_classdev_flash_unregister(&sub_leds[FLED1].ldev);
err_setup:
mutex_destroy(&led->lock);
@@ -986,11 +1115,13 @@ static int max77693_led_remove(struct platform_device *pdev)
struct max77693_sub_led *sub_leds = led->sub_leds;

if (led->iout_joint || p->fleds[FLED1]) {
+ v4l2_flash_release(sub_leds[FLED1].v4l2_flash);
led_classdev_flash_unregister(&sub_leds[FLED1].ldev);
cancel_work_sync(&sub_leds[FLED1].work_brightness_set);
}

if (!led->iout_joint && p->fleds[FLED2]) {
+ v4l2_flash_release(sub_leds[FLED2].v4l2_flash);
led_classdev_flash_unregister(&sub_leds[FLED2].ldev);
cancel_work_sync(&sub_leds[FLED2].work_brightness_set);
}
--
1.7.9.5

2014-12-03 16:09:47

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 19/19] leds: aat1290: add support for V4L2 Flash sub-device

Add support for V4L2 Flash sub-device to the aat1290 LED Flash class
driver. The support allows for V4L2 Flash sub-device to take the control
of the LED Flash class device.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Bryan Wu <[email protected]>
Cc: Richard Purdie <[email protected]>
Cc: Sakari Ailus <[email protected]>
---
drivers/leds/leds-aat1290.c | 61 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 61 insertions(+)

diff --git a/drivers/leds/leds-aat1290.c b/drivers/leds/leds-aat1290.c
index 15d969b..81a8f48 100644
--- a/drivers/leds/leds-aat1290.c
+++ b/drivers/leds/leds-aat1290.c
@@ -21,6 +21,7 @@
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
+#include <media/v4l2-flash.h>
#include <linux/workqueue.h>

#define AAT1290_MOVIE_MODE_CURRENT_ADDR 17
@@ -63,6 +64,7 @@ struct aat1290_led {
struct mutex lock;

struct led_classdev_flash ldev;
+ struct v4l2_flash *v4l2_flash;

int flen_gpio;
int en_set_gpio;
@@ -280,11 +282,51 @@ static void aat1290_init_flash_settings(struct aat1290_led *led,
setting->val = setting->max;
}

+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static void aat1290_init_v4l2_ctrl_config(struct aat1290_led_settings *s,
+ struct v4l2_flash_ctrl_config *config)
+{
+ struct led_flash_setting *setting;
+ struct v4l2_ctrl_config *c;
+
+ c = &config->intensity;
+ setting = &s->torch_brightness;
+ c->min = setting->min;
+ c->max = setting->max;
+ c->step = setting->step;
+ c->def = setting->val;
+
+ c = &config->flash_timeout;
+ setting = &s->flash_timeout;
+ c->min = setting->min;
+ c->max = setting->max;
+ c->step = setting->step;
+ c->def = setting->val;
+
+ config->has_external_strobe = false;
+}
+#else
+#define aat1290_init_v4l2_ctrl_config(s, config)
+#endif
+
static const struct led_flash_ops flash_ops = {
.strobe_set = aat1290_led_flash_strobe_set,
.timeout_set = aat1290_led_flash_timeout_set,
};

+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = NULL,
+};
+
+static const struct v4l2_flash_ops *get_v4l2_flash_ops(void)
+{
+ return &v4l2_flash_ops;
+}
+#else
+#define get_v4l2_flash_ops() (NULL)
+#endif
+
static int aat1290_led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -292,6 +334,9 @@ static int aat1290_led_probe(struct platform_device *pdev)
struct aat1290_led *led;
struct led_classdev *led_cdev;
struct led_classdev_flash *flash;
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+ struct v4l2_flash_ctrl_config v4l2_flash_config;
+#endif
struct aat1290_led_settings settings;
int flen_gpio, enset_gpio, ret;

@@ -344,6 +389,9 @@ static int aat1290_led_probe(struct platform_device *pdev)

flash->timeout = settings.flash_timeout;

+ /* Init V4L2 Flash controls basing on initialized settings */
+ aat1290_init_v4l2_ctrl_config(&settings, &v4l2_flash_config);
+
/* Init led class */
led_cdev = &flash->led_cdev;
led_cdev->name = led->label;
@@ -361,8 +409,20 @@ static int aat1290_led_probe(struct platform_device *pdev)
if (ret < 0)
goto error_gpio_en_set;

+ /* Create V4L2 Flash subdev. */
+ led->v4l2_flash = v4l2_flash_init(flash,
+ get_v4l2_flash_ops(),
+ dev_node,
+ &v4l2_flash_config);
+ 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(flash);
error_gpio_en_set:
if (gpio_is_valid(enset_gpio))
gpio_free(enset_gpio);
@@ -378,6 +438,7 @@ 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->ldev);
cancel_work_sync(&led->work_brightness_set);

--
1.7.9.5

2014-12-03 16:09:54

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 17/19] DT: Add documentation for exynos4-is 'flashes' property

This patch adds a description of 'flashes' property
to the samsung-fimc.txt.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Sylwester Nawrocki <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Pawel Moll <[email protected]>
Cc: Mark Rutland <[email protected]>
Cc: Ian Campbell <[email protected]>
Cc: Kumar Gala <[email protected]>
Cc: <[email protected]>
---
.../devicetree/bindings/media/samsung-fimc.txt | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/samsung-fimc.txt b/Documentation/devicetree/bindings/media/samsung-fimc.txt
index 922d6f8..22a6b2f 100644
--- a/Documentation/devicetree/bindings/media/samsung-fimc.txt
+++ b/Documentation/devicetree/bindings/media/samsung-fimc.txt
@@ -40,6 +40,12 @@ should be inactive. For the "active-a" state the camera port A must be activated
and the port B deactivated and for the state "active-b" it should be the other
way around.

+Optional properties:
+
+- flashes - Array of phandles to flash LED devices, or their sub-nodes
+ representing sub-leds.
+ (see Documentation/devicetree/bindings/leds/common.txt)
+
The 'camera' node must include at least one 'fimc' child node.


@@ -166,6 +172,7 @@ Example:
clock-output-names = "cam_a_clkout", "cam_b_clkout";
pinctrl-names = "default";
pinctrl-0 = <&cam_port_a_clk_active>;
+ flashes = <&camera_flash>, <&system_torch>;
status = "okay";
#address-cells = <1>;
#size-cells = <1>;
--
1.7.9.5

2014-12-03 16:08:52

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 13/19] v4l2-ctrls: Add V4L2_CID_FLASH_SYNC_STROBE control

Add V4L2_CID_FLASH_SYNC_STROBE control for determining
whether a flash device strobe has to be synchronized
with other flash leds controller by the same device.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Sakari Ailus <[email protected]>
Cc: Hans Verkuil <[email protected]>
---
Documentation/DocBook/media/v4l/controls.xml | 11 +++++++++++
drivers/media/v4l2-core/v4l2-ctrls.c | 2 ++
include/uapi/linux/v4l2-controls.h | 1 +
3 files changed, 14 insertions(+)

diff --git a/Documentation/DocBook/media/v4l/controls.xml b/Documentation/DocBook/media/v4l/controls.xml
index e013e4b..20179ab 100644
--- a/Documentation/DocBook/media/v4l/controls.xml
+++ b/Documentation/DocBook/media/v4l/controls.xml
@@ -4563,6 +4563,17 @@ interface and may change in the future.</para>
after strobe during which another strobe will not be
possible. This is a read-only control.</entry>
</row>
+ <row>
+ <entry spanname="id"><constant>V4L2_CID_FLASH_SYNC_STROBE</constant></entry>
+ <entry>boolean</entry>
+ </row>
+ <row>
+ <entry spanname="descr">Synchronized strobe: whether the flash
+ should be strobed synchronously with the other one controlled
+ by the same device. Flash timeout setting is inherited from the
+ LED being strobed explicitly and flash intensity setting of a LED
+ being synchronized is used.</entry>
+ </row>
<row><entry></entry></row>
</tbody>
</tgroup>
diff --git a/drivers/media/v4l2-core/v4l2-ctrls.c b/drivers/media/v4l2-core/v4l2-ctrls.c
index 45c5b47..a7cca8c 100644
--- a/drivers/media/v4l2-core/v4l2-ctrls.c
+++ b/drivers/media/v4l2-core/v4l2-ctrls.c
@@ -846,6 +846,7 @@ const char *v4l2_ctrl_get_name(u32 id)
case V4L2_CID_FLASH_FAULT: return "Faults";
case V4L2_CID_FLASH_CHARGE: return "Charge";
case V4L2_CID_FLASH_READY: return "Ready to Strobe";
+ case V4L2_CID_FLASH_SYNC_STROBE: return "Synchronize Strobe";

/* JPEG encoder controls */
/* Keep the order of the 'case's the same as in v4l2-controls.h! */
@@ -949,6 +950,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
case V4L2_CID_FLASH_STROBE_STATUS:
case V4L2_CID_FLASH_CHARGE:
case V4L2_CID_FLASH_READY:
+ case V4L2_CID_FLASH_SYNC_STROBE:
case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER:
case V4L2_CID_MPEG_VIDEO_DECODER_SLICE_INTERFACE:
case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE:
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index 661f119..5bce13d 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -833,6 +833,7 @@ enum v4l2_flash_strobe_source {

#define V4L2_CID_FLASH_CHARGE (V4L2_CID_FLASH_CLASS_BASE + 11)
#define V4L2_CID_FLASH_READY (V4L2_CID_FLASH_CLASS_BASE + 12)
+#define V4L2_CID_FLASH_SYNC_STROBE (V4L2_CID_FLASH_CLASS_BASE + 13)


/* JPEG-class control IDs */
--
1.7.9.5

2014-12-03 16:14:36

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 14/19] media: Add registration helpers for V4L2 flash sub-devices

This patch adds helper functions for registering/unregistering
LED Flash class devices as V4L2 sub-devices. The functions should
be called from the LED subsystem device driver. In case the
support for V4L2 Flash sub-devices is disabled in the kernel
config the functions' empty versions will be used.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Sakari Ailus <[email protected]>
Cc: Hans Verkuil <[email protected]>
---
drivers/media/v4l2-core/Kconfig | 11 +
drivers/media/v4l2-core/Makefile | 2 +
drivers/media/v4l2-core/v4l2-flash.c | 546 ++++++++++++++++++++++++++++++++++
include/media/v4l2-flash.h | 139 +++++++++
4 files changed, 698 insertions(+)
create mode 100644 drivers/media/v4l2-core/v4l2-flash.c
create mode 100644 include/media/v4l2-flash.h

diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig
index ba7e21a..f034f1a 100644
--- a/drivers/media/v4l2-core/Kconfig
+++ b/drivers/media/v4l2-core/Kconfig
@@ -44,6 +44,17 @@ config V4L2_MEM2MEM_DEV
tristate
depends on VIDEOBUF2_CORE

+# Used by LED subsystem flash drivers
+config V4L2_FLASH_LED_CLASS
+ tristate "Enable support for Flash sub-devices"
+ depends on VIDEO_V4L2_SUBDEV_API
+ depends on LEDS_CLASS_FLASH
+ ---help---
+ Say Y here to enable support for Flash sub-devices, which allow
+ to control LED class devices with use of V4L2 Flash controls.
+
+ When in doubt, say N.
+
# Used by drivers that need Videobuf modules
config VIDEOBUF_GEN
tristate
diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile
index 63d29f2..44e858c 100644
--- a/drivers/media/v4l2-core/Makefile
+++ b/drivers/media/v4l2-core/Makefile
@@ -22,6 +22,8 @@ obj-$(CONFIG_VIDEO_TUNER) += tuner.o

obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o

+obj-$(CONFIG_V4L2_FLASH_LED_CLASS) += v4l2-flash.o
+
obj-$(CONFIG_VIDEOBUF_GEN) += videobuf-core.o
obj-$(CONFIG_VIDEOBUF_DMA_SG) += videobuf-dma-sg.o
obj-$(CONFIG_VIDEOBUF_DMA_CONTIG) += videobuf-dma-contig.o
diff --git a/drivers/media/v4l2-core/v4l2-flash.c b/drivers/media/v4l2-core/v4l2-flash.c
new file mode 100644
index 0000000..beff400
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-flash.c
@@ -0,0 +1,546 @@
+/*
+ * V4L2 Flash LED sub-device registration helpers.
+ *
+ * Copyright (C) 2014 Samsung Electronics Co., Ltd
+ * Author: Jacek Anaszewski <[email protected]>
+ *
+ * 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/slab.h>
+#include <linux/types.h>
+#include <media/v4l2-flash.h>
+
+#define has_flash_op(v4l2_flash, op) \
+ (v4l2_flash && v4l2_flash->ops->op)
+
+#define call_flash_op(v4l2_flash, op, args...) \
+ (has_flash_op(v4l2_flash, op) ? \
+ v4l2_flash->ops->op(args) : \
+ -EINVAL)
+
+static inline enum led_brightness v4l2_flash_intensity_to_led_brightness(
+ struct v4l2_ctrl **ctrls,
+ enum ctrl_init_data_id cdata_id,
+ s32 intensity)
+{
+ struct v4l2_ctrl *ctrl = ctrls[cdata_id];
+ s64 __intensity = intensity - ctrl->minimum;
+
+ do_div(__intensity, 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 (cdata_id != INDICATOR_INTENSITY)
+ ++__intensity;
+
+ return __intensity;
+}
+
+static inline s32 v4l2_flash_led_brightness_to_intensity(
+ struct v4l2_ctrl **ctrls,
+ enum ctrl_init_data_id cdata_id,
+ enum led_brightness brightness)
+{
+ struct v4l2_ctrl *ctrl = ctrls[cdata_id];
+
+ /*
+ * 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 other
+ * cases the cached torch intensity value is used.
+ */
+ if (cdata_id != INDICATOR_INTENSITY)
+ --brightness;
+
+ return (brightness * ctrl->step) + ctrl->minimum;
+}
+
+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 *flash = v4l2_flash->flash;
+ struct led_classdev *led_cdev = &flash->led_cdev;
+ struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
+ bool is_strobing;
+ int ret;
+
+ switch (c->id) {
+ case V4L2_CID_FLASH_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 setting
+ * has changed.
+ */
+ if (ctrls[LED_MODE]->val == V4L2_FLASH_LED_MODE_TORCH) {
+ ret = led_update_brightness(led_cdev);
+ if (ret < 0)
+ return ret;
+ c->val = v4l2_flash_led_brightness_to_intensity(
+ ctrls, TORCH_INTENSITY,
+ led_cdev->brightness);
+ }
+ return 0;
+ case V4L2_CID_FLASH_INDICATOR_INTENSITY:
+ ret = led_update_brightness(led_cdev);
+ if (ret < 0)
+ return ret;
+ c->val = v4l2_flash_led_brightness_to_intensity(
+ ctrls, INDICATOR_INTENSITY,
+ led_cdev->brightness);
+ return 0;
+ case V4L2_CID_FLASH_INTENSITY:
+ ret = led_update_flash_brightness(flash);
+ if (ret < 0)
+ return ret;
+ /* no conversion is needed */
+ c->val = flash->brightness.val;
+ return 0;
+ case V4L2_CID_FLASH_STROBE_STATUS:
+ ret = led_get_flash_strobe(flash, &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(flash, &c->val);
+ case V4L2_CID_FLASH_SYNC_STROBE:
+ c->val = flash->sync_strobe;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+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 *flash = v4l2_flash->flash;
+ struct led_classdev *led_cdev = &flash->led_cdev;
+ struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
+ enum led_brightness brightness;
+ 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(flash, false);
+ case V4L2_FLASH_LED_MODE_FLASH:
+ /* Turn the torch LED off */
+ led_set_brightness(led_cdev, LED_OFF);
+ external_strobe = (ctrls[STROBE_SOURCE]->val ==
+ V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
+
+ if (has_flash_op(v4l2_flash, external_strobe_set))
+ ret = call_flash_op(v4l2_flash,
+ external_strobe_set, v4l2_flash,
+ external_strobe);
+ return ret;
+ case V4L2_FLASH_LED_MODE_TORCH:
+ /* Stop flash strobing */
+ ret = led_set_flash_strobe(flash, false);
+ if (ret < 0)
+ return ret;
+
+ brightness =
+ v4l2_flash_intensity_to_led_brightness(
+ ctrls, TORCH_INTENSITY,
+ ctrls[TORCH_INTENSITY]->val);
+ led_set_brightness(led_cdev, brightness);
+ return 0;
+ }
+ break;
+ case V4L2_CID_FLASH_STROBE_SOURCE:
+ external_strobe = (c->val == V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
+
+ return call_flash_op(v4l2_flash, external_strobe_set,
+ v4l2_flash, external_strobe);
+ case V4L2_CID_FLASH_STROBE:
+ if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_FLASH ||
+ ctrls[STROBE_SOURCE]->val !=
+ V4L2_FLASH_STROBE_SOURCE_SOFTWARE)
+ return -EINVAL;
+ return led_set_flash_strobe(flash, true);
+ case V4L2_CID_FLASH_STROBE_STOP:
+ if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_FLASH ||
+ ctrls[STROBE_SOURCE]->val !=
+ V4L2_FLASH_STROBE_SOURCE_SOFTWARE)
+ return -EINVAL;
+ return led_set_flash_strobe(flash, false);
+ case V4L2_CID_FLASH_TIMEOUT:
+ /* no conversion is needed */
+ return led_set_flash_timeout(flash, c->val);
+ case V4L2_CID_FLASH_INTENSITY:
+ /* no conversion is needed */
+ return led_set_flash_brightness(flash, c->val);
+ case V4L2_CID_FLASH_INDICATOR_INTENSITY:
+ brightness = v4l2_flash_intensity_to_led_brightness(
+ ctrls, INDICATOR_INTENSITY,
+ c->val);
+ led_set_brightness(led_cdev, brightness);
+ return 0;
+ case V4L2_CID_FLASH_TORCH_INTENSITY:
+ /*
+ * If not in MODE_TORCH don't call led-class brightness_set
+ * op, as it would result in turning the torch led on.
+ * Instead the value is cached only and will be written
+ * to the device upon transition to MODE_TORCH.
+ */
+ if (ctrls[LED_MODE]->val == V4L2_FLASH_LED_MODE_TORCH) {
+ brightness =
+ v4l2_flash_intensity_to_led_brightness(
+ ctrls, TORCH_INTENSITY,
+ c->val);
+ led_set_brightness(led_cdev, brightness);
+ }
+ return 0;
+ case V4L2_CID_FLASH_SYNC_STROBE:
+ flash->sync_strobe = c->val;
+ 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 fill_ctrl_init_data(struct v4l2_flash *v4l2_flash,
+ struct v4l2_flash_ctrl_config *flash_cfg,
+ struct v4l2_flash_ctrl_data *ctrl_init_data)
+{
+ struct led_classdev_flash *flash = v4l2_flash->flash;
+ const struct led_flash_ops *flash_ops = flash->ops;
+ struct led_classdev *led_cdev = &flash->led_cdev;
+ struct v4l2_ctrl_config *ctrl_cfg;
+ u32 mask;
+ s64 max;
+
+ /* Init FLASH_FAULT ctrl data */
+ if (flash_cfg->flash_faults) {
+ ctrl_init_data[FLASH_FAULT].supported = true;
+ 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 INDICATOR_INTENSITY ctrl data */
+ if (flash_cfg->indicator_led) {
+ ctrl_init_data[INDICATOR_INTENSITY].supported = true;
+ ctrl_init_data[INDICATOR_INTENSITY].config =
+ flash_cfg->intensity;
+ ctrl_cfg = &ctrl_init_data[INDICATOR_INTENSITY].config;
+ ctrl_cfg->id = V4L2_CID_FLASH_INDICATOR_INTENSITY;
+ ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE;
+
+ /* Indicator LED can have only faults and intensity controls. */
+ return;
+ }
+
+ /* 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].supported = true;
+ 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].supported = true;
+ ctrl_init_data[TORCH_INTENSITY].config = flash_cfg->intensity;
+ ctrl_cfg = &ctrl_init_data[TORCH_INTENSITY].config;
+ ctrl_cfg->id = V4L2_CID_FLASH_TORCH_INTENSITY;
+ ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE;
+
+ if (!(led_cdev->flags & LED_DEV_CAP_FLASH))
+ return;
+
+ /* Init FLASH_STROBE_SOURCE ctrl data */
+ mask = 1 << V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
+ if (flash_cfg->has_external_strobe) {
+ mask |= 1 << V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
+ max = V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
+ } else {
+ max = V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
+ }
+
+ ctrl_init_data[STROBE_SOURCE].supported = true;
+ ctrl_cfg = &ctrl_init_data[STROBE_SOURCE].config;
+ ctrl_cfg->id = V4L2_CID_FLASH_STROBE_SOURCE;
+ ctrl_cfg->max = max;
+ ctrl_cfg->menu_skip_mask = ~mask;
+ ctrl_cfg->def = V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
+
+ /* Init FLASH_STROBE ctrl data */
+ ctrl_init_data[FLASH_STROBE].supported = true;
+ 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].supported = true;
+ ctrl_cfg = &ctrl_init_data[STROBE_STOP].config;
+ ctrl_cfg->id = V4L2_CID_FLASH_STROBE_STOP;
+
+ /* Init STROBE_STATUS ctrl data */
+ if (flash_ops->strobe_get) {
+ ctrl_init_data[STROBE_STATUS].supported = true;
+ 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 (flash_ops->timeout_set) {
+ ctrl_init_data[FLASH_TIMEOUT].supported = true;
+ ctrl_init_data[FLASH_TIMEOUT].config = flash_cfg->flash_timeout;
+ ctrl_cfg = &ctrl_init_data[FLASH_TIMEOUT].config;
+ ctrl_cfg->id = V4L2_CID_FLASH_TIMEOUT;
+ ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE;
+ }
+
+ /* Init FLASH_INTENSITY ctrl data */
+ if (flash_ops->flash_brightness_set) {
+ ctrl_init_data[FLASH_INTENSITY].supported = true;
+ ctrl_init_data[FLASH_INTENSITY].config =
+ flash_cfg->flash_intensity;
+ ctrl_cfg = &ctrl_init_data[FLASH_INTENSITY].config;
+ ctrl_cfg->id = V4L2_CID_FLASH_INTENSITY;
+ ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE;
+ }
+
+ if (led_cdev->flags & LED_DEV_CAP_COMPOUND) {
+ ctrl_init_data[SYNC_STROBE].supported = true;
+ ctrl_cfg = &ctrl_init_data[SYNC_STROBE].config;
+ ctrl_cfg->id = V4L2_CID_FLASH_SYNC_STROBE;
+ ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE;
+ }
+}
+
+static int v4l2_flash_init_controls(struct v4l2_flash *v4l2_flash,
+ struct v4l2_flash_ctrl_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;
+
+ /* 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;
+
+ memset(ctrl_init_data, 0, sizeof(*ctrl_init_data));
+
+ fill_ctrl_init_data(v4l2_flash, flash_cfg, ctrl_init_data);
+
+ for (i = 0; i < NUM_FLASH_CTRLS; ++i)
+ if (ctrl_init_data[i].supported)
+ ++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].supported)
+ 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;
+}
+
+/*
+ * 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 *flash = v4l2_flash->flash;
+ struct led_classdev *led_cdev = &flash->led_cdev;
+ int ret = 0;
+
+ mutex_lock(&led_cdev->led_access);
+
+ if (!v4l2_fh_is_singular(&fh->vfh)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ led_sysfs_disable(led_cdev);
+
+unlock:
+ mutex_unlock(&led_cdev->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 *flash = v4l2_flash->flash;
+ struct led_classdev *led_cdev = &flash->led_cdev;
+ int ret = 0;
+
+ mutex_lock(&led_cdev->led_access);
+
+ if (has_flash_op(v4l2_flash, external_strobe_set))
+ ret = call_flash_op(v4l2_flash, external_strobe_set,
+ v4l2_flash, false);
+ led_sysfs_enable(led_cdev);
+
+ mutex_unlock(&led_cdev->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 led_classdev_flash *led_fdev,
+ const struct v4l2_flash_ops *ops,
+ struct device_node *of_node,
+ struct v4l2_flash_ctrl_config *config)
+{
+ struct v4l2_flash *v4l2_flash;
+ struct led_classdev *led_cdev = &led_fdev->led_cdev;
+ struct v4l2_subdev *sd;
+ int ret;
+
+ if (!ops || !of_node || !config)
+ return ERR_PTR(-EINVAL);
+
+ v4l2_flash = kzalloc(sizeof(*v4l2_flash), GFP_KERNEL);
+ if (!v4l2_flash)
+ return ERR_PTR(-ENOMEM);
+
+ sd = &v4l2_flash->sd;
+ v4l2_flash->flash = led_fdev;
+ v4l2_flash->ops = ops;
+ sd->dev = led_cdev->dev->parent;
+ v4l2_subdev_init(sd, &v4l2_flash_subdev_ops);
+ sd->internal_ops = &v4l2_flash_subdev_internal_ops;
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ snprintf(sd->name, sizeof(sd->name), led_cdev->name);
+
+ ret = v4l2_flash_init_controls(v4l2_flash, config);
+ if (ret < 0)
+ goto err_init_controls;
+
+ ret = media_entity_init(&sd->entity, 0, NULL, 0);
+ if (ret < 0)
+ goto err_init_entity;
+
+ sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_FLASH;
+
+ ret = v4l2_async_register_subdev_with_of(sd, of_node);
+ if (ret < 0)
+ goto err_init_entity;
+
+ return v4l2_flash;
+
+err_init_entity:
+ media_entity_cleanup(&sd->entity);
+err_init_controls:
+ v4l2_ctrl_handler_free(sd->ctrl_handler);
+ kfree(v4l2_flash);
+
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(v4l2_flash_init);
+
+void v4l2_flash_release(struct v4l2_flash *v4l2_flash)
+{
+ struct v4l2_subdev *sd = &v4l2_flash->sd;
+
+ if (!v4l2_flash)
+ return;
+
+ v4l2_async_unregister_subdev(sd);
+ media_entity_cleanup(&sd->entity);
+ v4l2_ctrl_handler_free(sd->ctrl_handler);
+ kfree(v4l2_flash);
+}
+EXPORT_SYMBOL_GPL(v4l2_flash_release);
+
+MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("V4L2 Flash sub-device helpers");
diff --git a/include/media/v4l2-flash.h b/include/media/v4l2-flash.h
new file mode 100644
index 0000000..8444261
--- /dev/null
+++ b/include/media/v4l2-flash.h
@@ -0,0 +1,139 @@
+/*
+ * V4L2 Flash LED sub-device registration helpers.
+ *
+ * Copyright (C) 2014 Samsung Electronics Co., Ltd
+ * Author: Jacek Anaszewski <[email protected]>
+ *
+ * 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-device.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+
+struct led_classdev_flash;
+struct led_classdev;
+struct v4l2_flash;
+enum led_brightness;
+
+enum ctrl_init_data_id {
+ LED_MODE,
+ TORCH_INTENSITY,
+ INDICATOR_INTENSITY,
+ STROBE_SOURCE,
+ FLASH_STROBE,
+ STROBE_STOP,
+ STROBE_STATUS,
+ FLASH_TIMEOUT,
+ FLASH_INTENSITY,
+ FLASH_FAULT,
+ SYNC_STROBE,
+ NUM_FLASH_CTRLS,
+};
+
+/*
+ * struct v4l2_flash_ctrl_data - flash control initialization data -
+ * filled basing on the features declared
+ * by the LED Flash class driver
+ * @config: initialization data for a control
+ * @supported: indicates whether a control is supported
+ * by the LED Flash class driver
+ */
+struct v4l2_flash_ctrl_data {
+ struct v4l2_ctrl_config config;
+ bool supported;
+};
+
+struct v4l2_flash_ops {
+ /* setup strobing the flash by hardware pin state assertion */
+ int (*external_strobe_set)(struct v4l2_flash *v4l2_flash,
+ bool enable);
+};
+
+/**
+ * struct v4l2_flash_ctrl_config - V4L2 Flash controls initialization data
+ * @intensity: constraints for the led in a non-flash mode
+ * @flash_intensity: V4L2_CID_FLASH_INTENSITY settings constraints
+ * @flash_timeout: V4L2_CID_FLASH_TIMEOUT constraints
+ * @flash_fault: possible flash faults
+ * @has_external_strobe: external strobe capability
+ * @indicator_led: signifies that a led is of indicator type
+ */
+struct v4l2_flash_ctrl_config {
+ struct v4l2_ctrl_config intensity;
+ struct v4l2_ctrl_config flash_intensity;
+ struct v4l2_ctrl_config flash_timeout;
+ u32 flash_faults;
+ bool has_external_strobe:1;
+ bool indicator_led:1;
+};
+
+/**
+ * struct v4l2_flash - Flash sub-device context
+ * @flash: LED Flash Class device controlled by this sub-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 *flash;
+ const struct v4l2_flash_ops *ops;
+
+ struct v4l2_subdev sd;
+ struct v4l2_ctrl_handler hdl;
+ struct v4l2_ctrl *ctrls[STROBE_SOURCE + 1];
+};
+
+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
+ * @led_fdev: the LED Flash Class device to wrap
+ * @flash_ops: V4L2 Flash device ops
+ * @of_node: Device Tree node
+ * @config: initialization data for V4L2 Flash controls
+ *
+ * Create V4L2 subdev 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 led_classdev_flash *led_fdev,
+ const struct v4l2_flash_ops *ops,
+ struct device_node *of_node,
+ struct v4l2_flash_ctrl_config *config);
+
+/**
+ * v4l2_flash_release - release V4L2 Flash sub-device
+ * @flash: the V4L2 Flash device to release
+ *
+ * Release V4L2 flash led subdev.
+ */
+void v4l2_flash_release(struct v4l2_flash *v4l2_flash);
+
+#else
+#define v4l2_flash_init(led_cdev, ops, of_node, config) (NULL)
+#define v4l2_flash_release(v4l2_flash)
+#endif /* CONFIG_V4L2_FLASH_LED_CLASS */
+
+#endif /* _V4L2_FLASH_H */
--
1.7.9.5

2014-12-03 16:14:34

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 09/19] of: Add Skyworks Solutions, Inc. vendor prefix

Use "skyworks" as the vendor prefix for the Skyworks Solutions, Inc.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Pawel Moll <[email protected]>
Cc: Mark Rutland <[email protected]>
Cc: Ian Campbell <[email protected]>
Cc: Kumar Gala <[email protected]>
Cc: <[email protected]>
---
.../devicetree/bindings/vendor-prefixes.txt | 1 +
1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index c177cd7..3006825 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -137,6 +137,7 @@ ricoh Ricoh Co. Ltd.
rockchip Fuzhou Rockchip Electronics Co., Ltd
samsung Samsung Semiconductor
sandisk Sandisk Corporation
+skyworks Skyworks Solutions, Inc.
sbs Smart Battery System
schindler Schindler
seagate Seagate Technology PLC
--
1.7.9.5

2014-12-03 16:17:46

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 05/19] leds: Add support for max77693 mfd flash cell

This patch adds led-flash support to Maxim max77693 chipset.
A device can be exposed to user space through LED subsystem
sysfs interface. Device supports up to two leds which can
work in flash and torch mode. The leds can be triggered
externally or by software.

Signed-off-by: Jacek Anaszewski <[email protected]>
Signed-off-by: Andrzej Hajda <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Bryan Wu <[email protected]>
Cc: Richard Purdie <[email protected]>
Cc: Lee Jones <[email protected]>
Cc: Chanwoo Choi <[email protected]>
---
drivers/leds/Kconfig | 10 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-max77693.c | 1023 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1034 insertions(+)
create mode 100644 drivers/leds/leds-max77693.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index fa8021e..2e66d55 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -463,6 +463,16 @@ config LEDS_TCA6507
LED driver chips accessed via the I2C bus.
Driver support brightness control and hardware-assisted blinking.

+config LEDS_MAX77693
+ tristate "LED support for MAX77693 Flash"
+ depends on LEDS_CLASS_FLASH
+ depends on MFD_MAX77693
+ depends on OF
+ help
+ This option enables support for the flash part of the MAX77693
+ multifunction device. It has build in control for two leds in flash
+ and torch mode.
+
config LEDS_MAX8997
tristate "LED support for MAX8997 PMIC"
depends on LEDS_CLASS && MFD_MAX8997
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index cbba921..57ca62b 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
+obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
diff --git a/drivers/leds/leds-max77693.c b/drivers/leds/leds-max77693.c
new file mode 100644
index 0000000..67a2f8f
--- /dev/null
+++ b/drivers/leds/leds-max77693.c
@@ -0,0 +1,1023 @@
+/*
+ * LED Flash class driver for the flash cell of max77693 mfd.
+ *
+ * Copyright (C) 2014, Samsung Electronics Co., Ltd.
+ *
+ * Authors: Jacek Anaszewski <[email protected]>
+ * Andrzej Hajda <[email protected]>
+ *
+ * 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 <asm/div64.h>
+#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>
+
+#define MODE_OFF 0
+#define MODE_FLASH1 (1 << 0)
+#define MODE_FLASH2 (1 << 1)
+#define MODE_TORCH1 (1 << 2)
+#define MODE_TORCH2 (1 << 3)
+#define MODE_FLASH_EXTERNAL1 (1 << 4)
+#define MODE_FLASH_EXTERNAL2 (1 << 5)
+
+#define MODE_FLASH (MODE_FLASH1 | MODE_FLASH2 | \
+ MODE_FLASH_EXTERNAL1 | MODE_FLASH_EXTERNAL2)
+
+#define FLED1_IOUT (1 << 0)
+#define FLED2_IOUT (1 << 1)
+
+enum {
+ FLED1,
+ FLED2
+};
+
+enum {
+ FLASH,
+ TORCH
+};
+
+struct max77693_sub_led {
+ struct led_classdev_flash ldev;
+ struct work_struct work_brightness_set;
+
+ unsigned int torch_brightness;
+ unsigned int flash_timeout;
+};
+
+struct max77693_led {
+ struct regmap *regmap;
+ struct platform_device *pdev;
+ struct max77693_led_platform_data *pdata;
+ struct mutex lock;
+
+ struct max77693_sub_led sub_leds[2];
+
+ unsigned int current_flash_timeout;
+ unsigned int mode_flags;
+ u8 torch_iout_reg;
+ bool iout_joint;
+ int strobing_sub_led_id;
+};
+
+struct max77693_led_settings {
+ struct led_flash_setting torch_brightness;
+ struct led_flash_setting flash_brightness;
+ struct led_flash_setting flash_timeout;
+};
+
+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_led *ldev1_to_led(
+ struct led_classdev_flash *ldev)
+{
+ struct max77693_sub_led *sub_led = container_of(ldev,
+ struct max77693_sub_led,
+ ldev);
+ return container_of(sub_led, struct max77693_led, sub_leds[0]);
+}
+
+static inline struct max77693_led *ldev2_to_led(
+ struct led_classdev_flash *ldev)
+{
+ struct max77693_sub_led *sub_led = container_of(ldev,
+ struct max77693_sub_led,
+ ldev);
+ return container_of(sub_led, struct max77693_led, sub_leds[1]);
+}
+
+static u8 max77693_led_vsys_to_reg(u32 mv)
+{
+ return ((mv - MAX_FLASH1_VSYS_MIN) / MAX_FLASH1_VSYS_STEP) << 2;
+}
+
+static u8 max77693_led_vout_to_reg(u32 mv)
+{
+ return (mv - FLASH_VOUT_MIN) / FLASH_VOUT_STEP + FLASH_VOUT_RMIN;
+}
+
+/* split composite current @i into two @iout according to @imax weights */
+static void max77693_calc_iout(u32 iout[2], u32 i, u32 imax[2])
+{
+ u64 t = i;
+
+ t *= imax[1];
+ do_div(t, imax[0] + imax[1]);
+
+ iout[1] = (u32)t / FLASH_IOUT_STEP * FLASH_IOUT_STEP;
+ iout[0] = i - iout[1];
+}
+
+static int max77693_set_mode(struct max77693_led *led, unsigned int mode)
+{
+ struct max77693_led_platform_data *p = led->pdata;
+ struct regmap *rmap = led->regmap;
+ int ret, v = 0;
+
+ if (mode & MODE_TORCH1) {
+ if (p->trigger[FLED1] & MAX77693_LED_TRIG_SOFT)
+ v |= FLASH_EN_ON << TORCH_EN_SHIFT(1);
+ }
+
+ if (mode & MODE_TORCH2) {
+ if (p->trigger[FLED2] & MAX77693_LED_TRIG_SOFT)
+ v |= FLASH_EN_ON << TORCH_EN_SHIFT(2);
+ }
+
+ if (mode & MODE_FLASH1) {
+ if (p->trigger[FLED1] & MAX77693_LED_TRIG_SOFT)
+ v |= FLASH_EN_ON << FLASH_EN_SHIFT(1);
+ } else if (mode & MODE_FLASH_EXTERNAL1) {
+ if (p->trigger[FLED1] & MAX77693_LED_TRIG_EXT)
+ v |= FLASH_EN_FLASH << FLASH_EN_SHIFT(2);
+ /*
+ * Enable hw triggering also for torch mode, as some camera
+ * sensors use torch led to fathom ambient light conditions
+ * before strobing the flash.
+ */
+ if (p->trigger[FLED1] & MAX77693_LED_TRIG_EXT)
+ v |= FLASH_EN_TORCH << TORCH_EN_SHIFT(1);
+ }
+
+ if (mode & MODE_FLASH2) {
+ if (p->trigger[FLED2] & MAX77693_LED_TRIG_SOFT)
+ v |= FLASH_EN_ON << FLASH_EN_SHIFT(2);
+ } else if (mode & MODE_FLASH_EXTERNAL2) {
+ if (p->trigger[FLED2] & MAX77693_LED_TRIG_EXT)
+ v |= FLASH_EN_FLASH << FLASH_EN_SHIFT(2);
+ /*
+ * Enable hw triggering also for torch mode, as some camera
+ * sensors use torch led to fathom ambient light conditions
+ * before strobing the flash.
+ */
+ if (p->trigger[FLED2] & MAX77693_LED_TRIG_EXT)
+ v |= FLASH_EN_TORCH << TORCH_EN_SHIFT(2);
+ }
+
+ /* Reset the register only prior setting flash modes */
+ if (mode & ~(MODE_TORCH1 | MODE_TORCH2)) {
+ 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 void max77693_set_sync_strobe(struct max77693_led *led,
+ unsigned int *mode)
+{
+ struct max77693_sub_led *sub_leds = led->sub_leds;
+ struct led_classdev_flash *flash;
+ unsigned int m = *mode;
+
+ /*
+ * If there are two leds then check if the other one
+ * wants to be strobed simultaneously.
+ */
+ if (!led->iout_joint) {
+ if (m & (MODE_FLASH1 | MODE_FLASH_EXTERNAL1)) {
+ flash = &sub_leds[FLED2].ldev;
+ if (flash->sync_strobe)
+ m |= m << 1;
+ } else if (m & (MODE_FLASH2 | MODE_FLASH_EXTERNAL2)) {
+ flash = &sub_leds[FLED1].ldev;
+ if (flash->sync_strobe)
+ m |= m >> 1;
+ }
+ }
+
+ *mode = m;
+}
+
+static int max77693_add_mode(struct max77693_led *led, unsigned int mode)
+{
+ int ret;
+
+ /* Span the mode on FLED2 for joint iouts case */
+ if (led->iout_joint)
+ mode |= (mode << 1);
+
+ /*
+ * Torch mode once enabled remains active until turned off,
+ * and thus no action is required in this case.
+ */
+ if ((mode & MODE_TORCH1) &&
+ (led->mode_flags & MODE_TORCH1))
+ return 0;
+ if ((mode & MODE_TORCH2) &&
+ (led->mode_flags & MODE_TORCH2))
+ return 0;
+
+ /* Span a flash mode on the other led if it is to be synchronized */
+ max77693_set_sync_strobe(led, &mode);
+
+ /*
+ * FLASH_EXTERNAL mode activates FLASHEN and TORCHEN pins
+ * in the device. The related register settings interfere
+ * with SW triggerred modes, thus clear them to ensure proper
+ * device configuration.
+ */
+ if (mode & MODE_FLASH_EXTERNAL1)
+ led->mode_flags &= (~MODE_TORCH1 & ~MODE_FLASH1);
+ if (mode & MODE_FLASH_EXTERNAL2)
+ led->mode_flags &= (~MODE_TORCH2 & ~MODE_FLASH2);
+
+ led->mode_flags |= mode;
+
+ ret = max77693_set_mode(led, led->mode_flags);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Clear flash mode flag after setting the mode to avoid
+ * spurious flash strobing on every subsequent torch mode
+ * setting.
+ */
+ if (mode & MODE_FLASH)
+ led->mode_flags &= ~mode;
+
+ return ret;
+}
+
+static int max77693_clear_mode(struct max77693_led *led, unsigned int mode)
+{
+ int ret;
+
+ if (led->iout_joint)
+ /* Clear mode also on FLED2 for joint iouts case */
+ mode |= (mode << 1);
+ else
+ /*
+ * Clear a flash mode on the other led
+ * if it is to be synchronized.
+ */
+ max77693_set_sync_strobe(led, &mode);
+
+ led->mode_flags &= ~mode;
+
+ ret = max77693_set_mode(led, led->mode_flags);
+
+ return ret;
+}
+
+static int max77693_set_torch_current(struct max77693_led *led,
+ int led_id, u32 micro_amp)
+{
+ struct max77693_led_platform_data *p = led->pdata;
+ struct regmap *rmap = led->regmap;
+ u32 iout[2], iout_max[2];
+ u8 iout1_reg = 0, iout2_reg = 0;
+
+ iout_max[FLED1] = p->iout_torch[FLED1];
+ iout_max[FLED2] = p->iout_torch[FLED2];
+
+ if (led_id == FLED1) {
+ /*
+ * Preclude splitting current to FLED2 if we
+ * are driving two separate leds.
+ */
+ if (!led->iout_joint)
+ iout_max[FLED2] = 0;
+ max77693_calc_iout(iout, micro_amp, iout_max);
+ } else if (led_id == FLED2) {
+ iout_max[FLED1] = 0;
+ max77693_calc_iout(iout, micro_amp, iout_max);
+ }
+
+ if (led_id == FLED1 || led->iout_joint) {
+ iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
+ led->torch_iout_reg &= 0xf0;
+ }
+ if (led_id == FLED2 || led->iout_joint) {
+ iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
+ led->torch_iout_reg &= 0x0f;
+ }
+
+ 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 *led,
+ int led_id,
+ u32 micro_amp)
+{
+ struct max77693_led_platform_data *p = led->pdata;
+ struct regmap *rmap = led->regmap;
+ u32 iout[2], iout_max[2];
+ u8 iout1_reg, iout2_reg;
+ int ret = -EINVAL;
+
+ iout_max[FLED1] = p->iout_flash[FLED1];
+ iout_max[FLED2] = p->iout_flash[FLED2];
+
+ if (led_id == FLED1) {
+ /*
+ * Preclude splitting current to FLED2 if we
+ * are driving two separate leds.
+ */
+ if (!led->iout_joint)
+ iout_max[FLED2] = 0;
+ max77693_calc_iout(iout, micro_amp, iout_max);
+ } else if (led_id == FLED2) {
+ iout_max[FLED1] = 0;
+ max77693_calc_iout(iout, micro_amp, iout_max);
+ }
+
+ if (led_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 (led_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 *led, u32 timeout)
+{
+ struct max77693_led_platform_data *p = led->pdata;
+ struct regmap *rmap = led->regmap;
+ u8 v;
+ int ret;
+
+ v = max77693_flash_timeout_to_reg(timeout);
+
+ if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
+ v |= FLASH_TMR_LEVEL;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
+ if (ret < 0)
+ return ret;
+
+ led->current_flash_timeout = timeout;
+
+ return 0;
+}
+
+static int max77693_strobe_status_get(struct max77693_led *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_int_flag_get(struct max77693_led *led, unsigned int *v)
+{
+ struct regmap *rmap = led->regmap;
+
+ return regmap_read(rmap, MAX77693_LED_REG_FLASH_INT, v);
+}
+
+static int max77693_setup(struct max77693_led *led)
+{
+ struct max77693_led_platform_data *p = led->pdata;
+ 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] = p->iout_flash[FLED1] +
+ p->iout_flash[FLED2];
+ } else {
+ first_led = p->fleds[FLED1] ? FLED1 : FLED2;
+ last_led = p->num_leds == 2 ? FLED2 : first_led;
+ max_flash_curr[FLED1] = p->iout_flash[FLED1];
+ max_flash_curr[FLED2] = p->iout_flash[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;
+
+ /* initially set FLED1 timeout */
+ ret = max77693_set_timeout(led, p->flash_timeout[FLED1]);
+ if (ret < 0)
+ return ret;
+
+ if (p->low_vsys > 0)
+ v = max77693_led_vsys_to_reg(p->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 (p->boost_mode == MAX77693_LED_BOOST_FIXED)
+ v = FLASH_BOOST_FIXED;
+ else
+ v = p->boost_mode | p->boost_mode << 1;
+ if (p->fleds[FLED1] && p->fleds[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(p->boost_vout);
+ ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
+ if (ret < 0)
+ return ret;
+
+ return max77693_set_mode(led, MODE_OFF);
+}
+
+static int max77693_led_brightness_set(struct max77693_led *led,
+ int led_id, enum led_brightness value)
+{
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (value == 0) {
+ ret = max77693_clear_mode(led, MODE_TORCH1 << led_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, led_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_TORCH1 << led_id);
+ if (ret < 0)
+ dev_dbg(&led->pdev->dev,
+ "Failed to set torch mode (%d)\n",
+ ret);
+unlock:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+#define MAX77693_LED_BRIGHTNESS_SET_WORK(ID) \
+static void max77693_led##ID##_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 *led = container_of(sub_led, \
+ struct max77693_led, \
+ sub_leds[FLED##ID]); \
+ struct max77693_sub_led *sub_leds = led->sub_leds; \
+ \
+ max77693_led_brightness_set(led, FLED##ID, \
+ sub_leds[FLED##ID].torch_brightness); \
+}
+
+/* LED subsystem callbacks */
+
+#define MAX77693_LED_TORCH_BRIGHTNESS_SET(ID) \
+static int max77693_led##ID##_brightness_set_sync( \
+ struct led_classdev *led_cdev, \
+ enum led_brightness value) \
+{ \
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev); \
+ struct max77693_led *led = ldev##ID##_to_led(flash); \
+ \
+ return max77693_led_brightness_set(led, FLED##ID, value); \
+}
+
+#define MAX77693_LED_BRIGHTNESS_SET(ID) \
+static void max77693_led##ID##_brightness_set( \
+ struct led_classdev *led_cdev, \
+ enum led_brightness value) \
+{ \
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev); \
+ struct max77693_led *led = ldev##ID##_to_led(flash); \
+ struct max77693_sub_led *sub_leds = led->sub_leds; \
+ \
+ sub_leds[FLED##ID].torch_brightness = value; \
+ schedule_work(&sub_leds[FLED##ID].work_brightness_set); \
+}
+
+#define MAX77693_LED_FLASH_BRIGHTNESS_SET(ID) \
+static int max77693_led##ID##_flash_brightness_set( \
+ struct led_classdev_flash *flash, \
+ u32 brightness) \
+{ \
+ struct max77693_led *led = ldev##ID##_to_led(flash); \
+ int ret; \
+ \
+ mutex_lock(&led->lock); \
+ ret = max77693_set_flash_current(led, FLED##ID, brightness); \
+ mutex_unlock(&led->lock); \
+ \
+ return ret; \
+}
+
+#define MAX77693_LED_FLASH_STROBE_SET(ID) \
+static int max77693_led##ID##_flash_strobe_set( \
+ struct led_classdev_flash *flash, \
+ bool state) \
+{ \
+ struct max77693_led *led = ldev##ID##_to_led(flash); \
+ struct max77693_sub_led *sub_leds = led->sub_leds; \
+ int ret; \
+ \
+ mutex_lock(&led->lock); \
+ \
+ if (!state) { \
+ ret = max77693_clear_mode(led, MODE_FLASH##ID); \
+ goto unlock; \
+ } \
+ \
+ if (sub_leds[FLED##ID].flash_timeout != \
+ led->current_flash_timeout) { \
+ ret = max77693_set_timeout(led, \
+ sub_leds[FLED##ID].flash_timeout); \
+ if (ret < 0) \
+ goto unlock; \
+ } \
+ \
+ led->strobing_sub_led_id = ID; \
+ \
+ ret = max77693_add_mode(led, MODE_FLASH##ID); \
+ \
+unlock: \
+ mutex_unlock(&led->lock); \
+ return ret; \
+}
+
+#define MAX77693_LED_FLASH_FAULT_GET(ID) \
+static int max77693_led##ID##_flash_fault_get( \
+ struct led_classdev_flash *flash, \
+ u32 *fault) \
+{ \
+ struct max77693_led *led = ldev##ID##_to_led(flash); \
+ unsigned int v; \
+ int ret; \
+ \
+ ret = max77693_int_flag_get(led, &v); \
+ if (ret < 0) \
+ return ret; \
+ \
+ *fault = 0; \
+ \
+ if (v & FLASH_INT_FLED##ID##_OPEN) \
+ *fault |= LED_FAULT_OVER_VOLTAGE; \
+ if (v & FLASH_INT_FLED##ID##_SHORT) \
+ *fault |= LED_FAULT_SHORT_CIRCUIT; \
+ if (v & FLASH_INT_OVER_CURRENT) \
+ *fault |= LED_FAULT_OVER_CURRENT; \
+ \
+ return 0; \
+}
+
+#define MAX77693_LED_FLASH_STROBE_GET(ID) \
+static int max77693_led##ID##_flash_strobe_get( \
+ struct led_classdev_flash *flash, \
+ bool *state) \
+{ \
+ struct max77693_led *led = ldev##ID##_to_led(flash); \
+ int ret; \
+ \
+ if (!state) \
+ return -EINVAL; \
+ \
+ mutex_lock(&led->lock); \
+ \
+ ret = max77693_strobe_status_get(led, state); \
+ \
+ *state = !!(*state && led->strobing_sub_led_id == ID); \
+ \
+ mutex_unlock(&led->lock); \
+ \
+ return ret; \
+}
+
+#define MAX77693_LED_FLASH_TIMEOUT_SET(ID) \
+static int max77693_led##ID##_flash_timeout_set( \
+ struct led_classdev_flash *flash, \
+ u32 timeout) \
+{ \
+ struct max77693_led *led = ldev##ID##_to_led(flash); \
+ struct max77693_sub_led *sub_leds = led->sub_leds; \
+ \
+ mutex_lock(&led->lock); \
+ sub_leds[FLED##ID].flash_timeout = timeout; \
+ mutex_unlock(&led->lock); \
+ \
+ return 0; \
+}
+
+MAX77693_LED_BRIGHTNESS_SET(1)
+MAX77693_LED_BRIGHTNESS_SET_WORK(1)
+MAX77693_LED_TORCH_BRIGHTNESS_SET(1)
+MAX77693_LED_FLASH_BRIGHTNESS_SET(1)
+MAX77693_LED_FLASH_STROBE_SET(1)
+MAX77693_LED_FLASH_STROBE_GET(1)
+MAX77693_LED_FLASH_TIMEOUT_SET(1)
+MAX77693_LED_FLASH_FAULT_GET(1)
+
+MAX77693_LED_BRIGHTNESS_SET(2)
+MAX77693_LED_BRIGHTNESS_SET_WORK(2)
+MAX77693_LED_TORCH_BRIGHTNESS_SET(2)
+MAX77693_LED_FLASH_BRIGHTNESS_SET(2)
+MAX77693_LED_FLASH_STROBE_SET(2)
+MAX77693_LED_FLASH_STROBE_GET(2)
+MAX77693_LED_FLASH_TIMEOUT_SET(2)
+MAX77693_LED_FLASH_FAULT_GET(2)
+
+static int max77693_led_parse_dt(struct max77693_led *led,
+ struct device_node *node)
+{
+ struct max77693_led_platform_data *p = led->pdata;
+ struct device *dev = &led->pdev->dev;
+ struct device_node *child_node;
+ u32 fled_id;
+ int ret;
+
+ of_property_read_u32_array(node, "maxim,fleds", p->fleds, 2);
+ of_property_read_u32_array(node, "maxim,trigger", p->trigger, 2);
+ of_property_read_u32_array(node, "maxim,trigger-type", p->trigger_type,
+ 2);
+ of_property_read_u32(node, "maxim,boost-mode", &p->boost_mode);
+ of_property_read_u32(node, "maxim,boost-vout", &p->boost_vout);
+ of_property_read_u32(node, "maxim,vsys-min", &p->low_vsys);
+
+ for_each_available_child_of_node(node, child_node) {
+ ret = of_property_read_u32(child_node, "maxim,fled_id",
+ &fled_id);
+ if (ret < 0) {
+ dev_err(dev, "Error reading \"fled_id\" DT property\n");
+ return ret;
+ }
+
+ fled_id = clamp_val(fled_id, 1, 2);
+ --fled_id;
+
+ p->sub_nodes[fled_id] = child_node;
+
+ ret = of_property_read_string(child_node, "label",
+ (const char **) &p->label[fled_id]);
+ if (ret < 0) {
+ dev_err(dev, "Error reading \"label\" DT property\n");
+ return ret;
+ }
+
+ of_property_read_u32(child_node, "max-microamp",
+ &p->iout_torch[fled_id]);
+ of_property_read_u32(child_node, "flash-max-microamp",
+ &p->iout_flash[fled_id]);
+ of_property_read_u32(child_node, "flash-timeout-microsec",
+ &p->flash_timeout[fled_id]);
+ if (++p->num_leds == 2)
+ break;
+ }
+
+ 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_led_validate_platform_data(
+ struct max77693_led_platform_data *p)
+{
+ u32 max;
+ int i;
+
+ p->boost_mode = clamp_val(p->boost_mode, MAX77693_LED_BOOST_NONE,
+ MAX77693_LED_BOOST_FIXED);
+
+ for (i = 0; i < ARRAY_SIZE(p->fleds); ++i)
+ p->fleds[i] = clamp_val(p->fleds[i], 0, 1);
+
+ /* Ensure fleds configuration is sane */
+ if (!p->fleds[FLED1] && !p->fleds[FLED2]) {
+ p->fleds[FLED1] = p->fleds[FLED2] = 1;
+ p->num_leds = 1;
+ }
+
+ /* Ensure num_leds is consistent with fleds configuration */
+ if ((!p->fleds[FLED1] || !p->fleds[FLED2]) && p->num_leds == 2)
+ p->num_leds = 1;
+
+ /*
+ * boost must be enabled if current outputs
+ * are connected to separate leds.
+ */
+ if ((p->num_leds == 2 || (p->fleds[FLED1] && p->fleds[FLED2])) &&
+ p->boost_mode == MAX77693_LED_BOOST_NONE)
+ p->boost_mode = MAX77693_LED_BOOST_FIXED;
+
+ max = p->boost_mode ? FLASH_IOUT_MAX_2LEDS : FLASH_IOUT_MAX_1LED;
+
+ if (p->fleds[FLED1]) {
+ clamp_align(&p->iout_torch[FLED1], TORCH_IOUT_MIN,
+ TORCH_IOUT_MAX, TORCH_IOUT_STEP);
+ clamp_align(&p->iout_flash[FLED1], FLASH_IOUT_MIN, max,
+ FLASH_IOUT_STEP);
+ } else {
+ p->iout_torch[FLED1] = p->iout_flash[FLED1] = 0;
+ }
+ if (p->fleds[FLED2]) {
+ clamp_align(&p->iout_torch[FLED2], TORCH_IOUT_MIN,
+ TORCH_IOUT_MAX, TORCH_IOUT_STEP);
+ clamp_align(&p->iout_flash[FLED2], FLASH_IOUT_MIN, max,
+ FLASH_IOUT_STEP);
+ } else {
+ p->iout_torch[FLED2] = p->iout_flash[FLED2] = 0;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(p->trigger); ++i)
+ p->trigger[i] = clamp_val(p->trigger[i], 0, 7);
+ for (i = 0; i < ARRAY_SIZE(p->trigger_type); ++i)
+ p->trigger_type[i] = clamp_val(p->trigger_type[i],
+ MAX77693_LED_TRIG_TYPE_EDGE,
+ MAX77693_LED_TRIG_TYPE_LEVEL);
+
+ for (i = 0; i < ARRAY_SIZE(p->flash_timeout); ++i)
+ clamp_align(&p->flash_timeout[i], FLASH_TIMEOUT_MIN,
+ FLASH_TIMEOUT_MAX, FLASH_TIMEOUT_STEP);
+
+ clamp_align(&p->boost_vout, FLASH_VOUT_MIN, FLASH_VOUT_MAX,
+ FLASH_VOUT_STEP);
+
+ if (p->low_vsys)
+ clamp_align(&p->low_vsys, MAX_FLASH1_VSYS_MIN,
+ MAX_FLASH1_VSYS_MAX, MAX_FLASH1_VSYS_STEP);
+}
+
+static int max77693_led_get_platform_data(struct max77693_led *led)
+{
+ struct device *dev = &led->pdev->dev;
+ int ret;
+
+ if (!dev->of_node)
+ return -EINVAL;
+
+ led->pdata = devm_kzalloc(dev, sizeof(*led->pdata), GFP_KERNEL);
+ if (!led->pdata)
+ return -ENOMEM;
+
+ ret = max77693_led_parse_dt(led, dev->of_node);
+ if (ret < 0)
+ return ret;
+
+ max77693_led_validate_platform_data(led->pdata);
+
+ return 0;
+}
+
+#define MAX77693_LED_INIT_FLASH_OPS(ID) \
+static const struct led_flash_ops flash_ops##ID = { \
+ \
+ .flash_brightness_set = max77693_led##ID##_flash_brightness_set, \
+ .strobe_set = max77693_led##ID##_flash_strobe_set, \
+ .strobe_get = max77693_led##ID##_flash_strobe_get, \
+ .timeout_set = max77693_led##ID##_flash_timeout_set, \
+ .fault_get = max77693_led##ID##_flash_fault_get, \
+}
+
+MAX77693_LED_INIT_FLASH_OPS(1);
+MAX77693_LED_INIT_FLASH_OPS(2);
+
+static void max77693_init_flash_settings(struct max77693_led *led,
+ struct max77693_led_settings *s,
+ int led_id)
+{
+ struct max77693_led_platform_data *p = led->pdata;
+ struct led_flash_setting *setting;
+
+ /* Init torch intensity setting */
+ setting = &s->torch_brightness;
+ setting->min = led->iout_joint ? TORCH_IOUT_MIN * 2 :
+ TORCH_IOUT_MIN;
+ setting->max = led->iout_joint ?
+ p->iout_torch[FLED1] + p->iout_torch[FLED2] :
+ p->iout_torch[led_id];
+ setting->step = TORCH_IOUT_STEP;
+ setting->val = setting->max;
+
+ /* Init flash intensity setting */
+ setting = &s->flash_brightness;
+ setting->min = led->iout_joint ? FLASH_IOUT_MIN * 2 :
+ FLASH_IOUT_MIN;
+ setting->max = led->iout_joint ?
+ p->iout_flash[FLED1] + p->iout_flash[FLED2] :
+ p->iout_flash[led_id];
+ setting->step = FLASH_IOUT_STEP;
+ setting->val = setting->max;
+
+ /* Init flash timeout setting */
+ setting = &s->flash_timeout;
+ setting->min = FLASH_TIMEOUT_MIN;
+ setting->max = p->flash_timeout[led_id];
+ setting->step = FLASH_TIMEOUT_STEP;
+ setting->val = setting->max;
+}
+
+static int max77693_register_led(struct max77693_led *led, int id)
+{
+ struct platform_device *pdev = led->pdev;
+ struct led_classdev_flash *flash;
+ struct led_classdev *led_cdev;
+ struct max77693_sub_led *sub_leds = led->sub_leds;
+ struct max77693_led_settings settings;
+
+ flash = &sub_leds[id].ldev;
+
+ /* Initialize flash settings */
+ max77693_init_flash_settings(led, &settings, id);
+
+ /* Initialize LED Flash class device */
+ led_cdev = &flash->led_cdev;
+
+ led_cdev->name = led->pdata->label[id];
+
+ if (id == FLED1) {
+ led_cdev->brightness_set = max77693_led1_brightness_set;
+ led_cdev->brightness_set_sync =
+ max77693_led1_brightness_set_sync;
+ INIT_WORK(&sub_leds[id].work_brightness_set,
+ max77693_led1_brightness_set_work);
+ flash->ops = &flash_ops1;
+ } else {
+ led_cdev->brightness_set = max77693_led2_brightness_set;
+ led_cdev->brightness_set_sync =
+ max77693_led2_brightness_set_sync;
+ INIT_WORK(&sub_leds[id].work_brightness_set,
+ max77693_led2_brightness_set_work);
+ flash->ops = &flash_ops2;
+ }
+
+ led_cdev->max_brightness = settings.torch_brightness.val /
+ TORCH_IOUT_STEP;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+ if (led->pdata->num_leds == 2)
+ led_cdev->flags |= LED_DEV_CAP_COMPOUND;
+
+ flash->brightness = settings.flash_brightness;
+ flash->timeout = settings.flash_timeout;
+ sub_leds[id].flash_timeout = flash->timeout.val;
+
+ /* Register in the LED subsystem. */
+ return led_classdev_flash_register(&pdev->dev, flash);
+}
+
+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 *led;
+ struct max77693_led_platform_data *p;
+ struct max77693_sub_led *sub_leds;
+ int ret;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pdev = pdev;
+ led->regmap = iodev->regmap;
+ sub_leds = led->sub_leds;
+ platform_set_drvdata(pdev, led);
+ ret = max77693_led_get_platform_data(led);
+ if (ret < 0)
+ return -EINVAL;
+
+ p = led->pdata;
+ mutex_init(&led->lock);
+
+ if (p->num_leds == 1 && p->fleds[FLED1] && p->fleds[FLED2])
+ led->iout_joint = true;
+
+ ret = max77693_setup(led);
+ if (ret < 0)
+ goto err_setup;
+
+ if (led->iout_joint || p->fleds[FLED1]) {
+ ret = max77693_register_led(led, FLED1);
+ if (ret < 0)
+ goto err_setup;
+ }
+
+ if (!led->iout_joint && p->fleds[FLED2]) {
+ ret = max77693_register_led(led, FLED2);
+ if (ret < 0)
+ goto err_register_led2;
+ }
+
+ return 0;
+
+err_register_led2:
+ if (!p->fleds[FLED1])
+ goto err_setup;
+ led_classdev_flash_unregister(&sub_leds[FLED1].ldev);
+err_setup:
+ mutex_destroy(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_remove(struct platform_device *pdev)
+{
+ struct max77693_led *led = platform_get_drvdata(pdev);
+ struct max77693_led_platform_data *p = led->pdata;
+ struct max77693_sub_led *sub_leds = led->sub_leds;
+
+ if (led->iout_joint || p->fleds[FLED1]) {
+ led_classdev_flash_unregister(&sub_leds[FLED1].ldev);
+ cancel_work_sync(&sub_leds[FLED1].work_brightness_set);
+ }
+
+ if (!led->iout_joint && p->fleds[FLED2]) {
+ led_classdev_flash_unregister(&sub_leds[FLED2].ldev);
+ cancel_work_sync(&sub_leds[FLED2].work_brightness_set);
+ }
+
+ mutex_destroy(&led->lock);
+
+ return 0;
+}
+
+static 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",
+ .owner = THIS_MODULE,
+ .of_match_table = max77693_led_dt_match,
+ },
+};
+
+module_platform_driver(max77693_led_driver);
+
+MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
+MODULE_AUTHOR("Andrzej Hajda <[email protected]>");
+MODULE_DESCRIPTION("Maxim MAX77693 led flash driver");
+MODULE_LICENSE("GPL");
--
1.7.9.5

2014-12-03 16:19:49

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v9 01/19] leds: Add LED Flash class extension to the LED subsystem

Some LED devices support two operation modes - torch and flash.
This patch provides support for flash LED devices in the LED subsystem
by introducing new sysfs attributes and kernel internal interface.
The attributes being introduced are: flash_brightness, flash_strobe,
flash_timeout, max_flash_timeout, max_flash_brightness, flash_fault
and flash_sync_strobe. All the flash related features are placed
in a separate module. Torch mode is supported by the LED class
interface.

The modifications aim to be compatible with V4L2 framework requirements
related to the flash devices management. The design assumes that V4L2
sub-device can take of the LED class device control and communicate
with it through the kernel internal interface. When V4L2 Flash sub-device
file is opened, the LED class device sysfs interface is made
unavailable.

Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Bryan Wu <[email protected]>
Cc: Richard Purdie <[email protected]>
---
drivers/leds/Kconfig | 10 +
drivers/leds/Makefile | 1 +
drivers/leds/led-class-flash.c | 446 +++++++++++++++++++++++++++++++++++++++
drivers/leds/led-class.c | 4 +
include/linux/led-class-flash.h | 186 ++++++++++++++++
include/linux/leds.h | 3 +
6 files changed, 650 insertions(+)
create mode 100644 drivers/leds/led-class-flash.c
create mode 100644 include/linux/led-class-flash.h

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index b3c0d8a..fa8021e 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -19,6 +19,16 @@ config LEDS_CLASS
This option enables the led sysfs class in /sys/class/leds. You'll
need this to do anything useful with LEDs. If unsure, say N.

+config LEDS_CLASS_FLASH
+ tristate "LED Flash Class Support"
+ depends on LEDS_CLASS
+ help
+ This option enables the flash led sysfs class in /sys/class/leds.
+ It wrapps LED Class and adds flash LEDs specific sysfs attributes
+ and kernel internal API to it. You'll need this to provide support
+ for the flash related features of a LED device. It can be built
+ as a module.
+
comment "LED drivers"

config LEDS_88PM860X
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 1c65a19..cbba921 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -2,6 +2,7 @@
# LED Core
obj-$(CONFIG_NEW_LEDS) += led-core.o
obj-$(CONFIG_LEDS_CLASS) += led-class.o
+obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o

# LED Platform Drivers
diff --git a/drivers/leds/led-class-flash.c b/drivers/leds/led-class-flash.c
new file mode 100644
index 0000000..219b414
--- /dev/null
+++ b/drivers/leds/led-class-flash.c
@@ -0,0 +1,446 @@
+/*
+ * LED Flash class interface
+ *
+ * Copyright (C) 2014 Samsung Electronics Co., Ltd.
+ * Author: Jacek Anaszewski <[email protected]>
+ *
+ * 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/device.h>
+#include <linux/init.h>
+#include <linux/led-class-flash.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include "leds.h"
+
+#define has_flash_op(flash, op) \
+ (flash && flash->ops->op)
+
+#define call_flash_op(flash, op, args...) \
+ ((has_flash_op(flash, op)) ? \
+ (flash->ops->op(flash, args)) : \
+ -EINVAL)
+
+static ssize_t flash_brightness_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
+ unsigned long state;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_access);
+
+ if (led_sysfs_is_disabled(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ goto unlock;
+
+ ret = led_set_flash_brightness(flash, state);
+ if (ret < 0)
+ goto unlock;
+
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
+}
+
+static ssize_t flash_brightness_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
+
+ /* no lock needed for this */
+ led_update_flash_brightness(flash);
+
+ return sprintf(buf, "%u\n", flash->brightness.val);
+}
+static DEVICE_ATTR_RW(flash_brightness);
+
+static ssize_t max_flash_brightness_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
+
+ return sprintf(buf, "%u\n", flash->brightness.max);
+}
+static DEVICE_ATTR_RO(max_flash_brightness);
+
+static ssize_t flash_strobe_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
+ unsigned long state;
+ ssize_t ret = -EINVAL;
+
+ mutex_lock(&led_cdev->led_access);
+
+ if (led_sysfs_is_disabled(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ goto unlock;
+
+ if (state < 0 || state > 1) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ ret = led_set_flash_strobe(flash, state);
+ if (ret < 0)
+ goto unlock;
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
+}
+
+static ssize_t flash_strobe_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
+ bool state;
+ int ret;
+
+ /* no lock needed for this */
+ ret = led_get_flash_strobe(flash, &state);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%u\n", state);
+}
+static DEVICE_ATTR_RW(flash_strobe);
+
+static ssize_t flash_timeout_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
+ unsigned long flash_timeout;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_access);
+
+ if (led_sysfs_is_disabled(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = kstrtoul(buf, 10, &flash_timeout);
+ if (ret)
+ goto unlock;
+
+ ret = led_set_flash_timeout(flash, flash_timeout);
+ if (ret < 0)
+ goto unlock;
+
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
+}
+
+static ssize_t flash_timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
+
+ return sprintf(buf, "%u\n", flash->timeout.val);
+}
+static DEVICE_ATTR_RW(flash_timeout);
+
+static ssize_t max_flash_timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
+
+ return sprintf(buf, "%u\n", flash->timeout.max);
+}
+static DEVICE_ATTR_RO(max_flash_timeout);
+
+static ssize_t flash_fault_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
+ u32 fault;
+ int ret;
+
+ ret = led_get_flash_fault(flash, &fault);
+ if (ret < 0)
+ return -EINVAL;
+
+ return sprintf(buf, "0x%8.8x\n", fault);
+}
+static DEVICE_ATTR_RO(flash_fault);
+
+static ssize_t flash_sync_strobe_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
+ unsigned long sync_strobe;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_access);
+
+ if (led_sysfs_is_disabled(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = kstrtoul(buf, 10, &sync_strobe);
+ if (ret)
+ goto unlock;
+
+ flash->sync_strobe = sync_strobe;
+
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
+}
+
+static ssize_t flash_sync_strobe_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
+
+ return sprintf(buf, "%u\n", flash->sync_strobe);
+}
+static DEVICE_ATTR_RW(flash_sync_strobe);
+
+static struct attribute *led_flash_strobe_attrs[] = {
+ &dev_attr_flash_strobe.attr,
+ NULL,
+};
+
+static struct attribute *led_flash_timeout_attrs[] = {
+ &dev_attr_flash_timeout.attr,
+ &dev_attr_max_flash_timeout.attr,
+ NULL,
+};
+
+static struct attribute *led_flash_brightness_attrs[] = {
+ &dev_attr_flash_brightness.attr,
+ &dev_attr_max_flash_brightness.attr,
+ NULL,
+};
+
+static struct attribute *led_flash_fault_attrs[] = {
+ &dev_attr_flash_fault.attr,
+ NULL,
+};
+
+static struct attribute *led_flash_sync_strobe_attrs[] = {
+ &dev_attr_flash_sync_strobe.attr,
+ NULL,
+};
+
+static const struct attribute_group led_flash_strobe_group = {
+ .attrs = led_flash_strobe_attrs,
+};
+
+static const struct attribute_group led_flash_timeout_group = {
+ .attrs = led_flash_timeout_attrs,
+};
+
+static const struct attribute_group led_flash_brightness_group = {
+ .attrs = led_flash_brightness_attrs,
+};
+
+static const struct attribute_group led_flash_fault_group = {
+ .attrs = led_flash_fault_attrs,
+};
+
+static const struct attribute_group led_flash_sync_strobe_group = {
+ .attrs = led_flash_sync_strobe_attrs,
+};
+
+static const struct attribute_group *flash_groups[] = {
+ &led_flash_strobe_group,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void led_flash_resume(struct led_classdev *led_cdev)
+{
+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
+
+ call_flash_op(flash, flash_brightness_set, flash->brightness.val);
+ call_flash_op(flash, timeout_set, flash->timeout.val);
+}
+
+static void led_flash_init_sysfs_groups(struct led_classdev_flash *flash)
+{
+ struct led_classdev *led_cdev = &flash->led_cdev;
+ const struct led_flash_ops *ops = flash->ops;
+ int num_sysfs_groups = 1;
+
+ if (ops->flash_brightness_set)
+ flash_groups[num_sysfs_groups++] = &led_flash_brightness_group;
+
+ if (ops->timeout_set)
+ flash_groups[num_sysfs_groups++] = &led_flash_timeout_group;
+
+ if (ops->fault_get)
+ flash_groups[num_sysfs_groups++] = &led_flash_fault_group;
+
+ if (led_cdev->flags & LED_DEV_CAP_COMPOUND)
+ flash_groups[num_sysfs_groups++] = &led_flash_sync_strobe_group;
+
+ led_cdev->groups = flash_groups;
+}
+
+int led_classdev_flash_register(struct device *parent,
+ struct led_classdev_flash *flash)
+{
+ struct led_classdev *led_cdev;
+ const struct led_flash_ops *ops;
+ int ret;
+
+ if (!flash)
+ return -EINVAL;
+
+ led_cdev = &flash->led_cdev;
+
+ if (led_cdev->flags & LED_DEV_CAP_FLASH) {
+ if (!led_cdev->brightness_set_sync)
+ return -EINVAL;
+
+ ops = flash->ops;
+ if (!ops || !ops->strobe_set)
+ return -EINVAL;
+
+ led_cdev->flash_resume = led_flash_resume;
+
+ /* Select the sysfs attributes to be created for the device */
+ led_flash_init_sysfs_groups(flash);
+ }
+
+ /* Register led class device */
+ ret = led_classdev_register(parent, led_cdev);
+ if (ret < 0)
+ return ret;
+
+ /* Setting a torch brightness needs to have immediate effect */
+ led_cdev->flags &= ~SET_BRIGHTNESS_ASYNC;
+ led_cdev->flags |= SET_BRIGHTNESS_SYNC;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_classdev_flash_register);
+
+void led_classdev_flash_unregister(struct led_classdev_flash *flash)
+{
+ if (!flash)
+ return;
+
+ led_classdev_unregister(&flash->led_cdev);
+}
+EXPORT_SYMBOL_GPL(led_classdev_flash_unregister);
+
+int led_set_flash_strobe(struct led_classdev_flash *flash, bool state)
+{
+ return call_flash_op(flash, strobe_set, state);
+}
+EXPORT_SYMBOL_GPL(led_set_flash_strobe);
+
+int led_get_flash_strobe(struct led_classdev_flash *flash, bool *state)
+{
+ return call_flash_op(flash, strobe_get, state);
+}
+EXPORT_SYMBOL_GPL(led_get_flash_strobe);
+
+static void led_clamp_align(struct led_flash_setting *s)
+{
+ u32 v, offset;
+
+ v = s->val + s->step / 2;
+ v = clamp(v, s->min, s->max);
+ offset = v - s->min;
+ offset = s->step * (offset / s->step);
+ s->val = s->min + offset;
+}
+
+int led_set_flash_timeout(struct led_classdev_flash *flash, u32 timeout)
+{
+ struct led_classdev *led_cdev = &flash->led_cdev;
+ struct led_flash_setting *s = &flash->timeout;
+
+ s->val = timeout;
+ led_clamp_align(s);
+
+ if (!(led_cdev->flags & LED_SUSPENDED))
+ return call_flash_op(flash, timeout_set, s->val);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_set_flash_timeout);
+
+int led_get_flash_fault(struct led_classdev_flash *flash, u32 *fault)
+{
+ return call_flash_op(flash, fault_get, fault);
+}
+EXPORT_SYMBOL_GPL(led_get_flash_fault);
+
+int led_set_flash_brightness(struct led_classdev_flash *flash,
+ u32 brightness)
+{
+ struct led_classdev *led_cdev = &flash->led_cdev;
+ struct led_flash_setting *s = &flash->brightness;
+
+ s->val = brightness;
+ led_clamp_align(s);
+
+ if (!(led_cdev->flags & LED_SUSPENDED))
+ return call_flash_op(flash, flash_brightness_set, s->val);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_set_flash_brightness);
+
+int led_update_flash_brightness(struct led_classdev_flash *flash)
+{
+ struct led_flash_setting *s = &flash->brightness;
+ u32 brightness;
+
+ if (has_flash_op(flash, flash_brightness_get)) {
+ int ret = call_flash_op(flash, flash_brightness_get,
+ &brightness);
+ if (ret < 0)
+ return ret;
+
+ s->val = brightness;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_update_flash_brightness);
+
+MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("LED Flash class Interface");
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index dbeebac..02564c5 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -179,6 +179,10 @@ EXPORT_SYMBOL_GPL(led_classdev_suspend);
void led_classdev_resume(struct led_classdev *led_cdev)
{
led_cdev->brightness_set(led_cdev, led_cdev->brightness);
+
+ if (led_cdev->flash_resume)
+ led_cdev->flash_resume(led_cdev);
+
led_cdev->flags &= ~LED_SUSPENDED;
}
EXPORT_SYMBOL_GPL(led_classdev_resume);
diff --git a/include/linux/led-class-flash.h b/include/linux/led-class-flash.h
new file mode 100644
index 0000000..2043082
--- /dev/null
+++ b/include/linux/led-class-flash.h
@@ -0,0 +1,186 @@
+/*
+ * LED Flash class interface
+ *
+ * Copyright (C) 2014 Samsung Electronics Co., Ltd.
+ * Author: Jacek Anaszewski <[email protected]>
+ *
+ * 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 __LINUX_FLASH_LEDS_H_INCLUDED
+#define __LINUX_FLASH_LEDS_H_INCLUDED
+
+#include <linux/leds.h>
+#include <uapi/linux/v4l2-controls.h>
+
+struct device_node;
+struct led_classdev_flash;
+
+/*
+ * Supported led fault bits - must be kept in synch
+ * with V4L2_FLASH_FAULT bits.
+ */
+#define LED_FAULT_OVER_VOLTAGE V4L2_FLASH_FAULT_OVER_VOLTAGE
+#define LED_FAULT_TIMEOUT V4L2_FLASH_FAULT_TIMEOUT
+#define LED_FAULT_OVER_TEMPERATURE V4L2_FLASH_FAULT_OVER_TEMPERATURE
+#define LED_FAULT_SHORT_CIRCUIT V4L2_FLASH_FAULT_SHORT_CIRCUIT
+#define LED_FAULT_OVER_CURRENT V4L2_FLASH_FAULT_OVER_CURRENT
+#define LED_FAULT_INDICATOR V4L2_FLASH_FAULT_INDICATOR
+#define LED_FAULT_UNDER_VOLTAGE V4L2_FLASH_FAULT_UNDER_VOLTAGE
+#define LED_FAULT_INPUT_VOLTAGE V4L2_FLASH_FAULT_INPUT_VOLTAGE
+#define LED_FAULT_LED_OVER_TEMPERATURE V4L2_FLASH_OVER_TEMPERATURE
+
+struct led_flash_ops {
+ /* set flash brightness */
+ int (*flash_brightness_set)(struct led_classdev_flash *flash,
+ u32 brightness);
+ /* get flash brightness */
+ int (*flash_brightness_get)(struct led_classdev_flash *flash,
+ u32 *brightness);
+ /* set flash strobe state */
+ int (*strobe_set)(struct led_classdev_flash *flash, bool state);
+ /* get flash strobe state */
+ int (*strobe_get)(struct led_classdev_flash *flash, bool *state);
+ /* set flash timeout */
+ int (*timeout_set)(struct led_classdev_flash *flash, u32 timeout);
+ /* get the flash LED fault */
+ int (*fault_get)(struct led_classdev_flash *flash, u32 *fault);
+};
+
+/*
+ * Current value of a flash setting along
+ * with its constraints.
+ */
+struct led_flash_setting {
+ /* maximum allowed value */
+ u32 min;
+ /* maximum allowed value */
+ u32 max;
+ /* step value */
+ u32 step;
+ /* current value */
+ u32 val;
+};
+
+struct led_classdev_flash {
+ /* led class device */
+ struct led_classdev led_cdev;
+
+ /* flash led specific ops */
+ const struct led_flash_ops *ops;
+
+ /* flash brightness value in microamperes along with its constraints */
+ struct led_flash_setting brightness;
+
+ /* flash timeout value in microseconds along with its constraints */
+ struct led_flash_setting timeout;
+
+ /*
+ * Indicates whether the flash sub-led should strobe
+ * upon strobe activation on any of the remaining sub-leds.
+ */
+ bool sync_strobe:1;
+};
+
+static inline struct led_classdev_flash *lcdev_to_flash(
+ struct led_classdev *lcdev)
+{
+ return container_of(lcdev, struct led_classdev_flash, led_cdev);
+}
+
+/**
+ * led_classdev_flash_register - register a new object of led_classdev class
+ * with support for flash LEDs
+ * @parent: the flash LED to register
+ * @flash: the led_classdev_flash structure for this device
+ *
+ * Returns: 0 on success or negative error value on failure
+ */
+int led_classdev_flash_register(struct device *parent,
+ struct led_classdev_flash *flash);
+
+/**
+ * led_classdev_flash_unregister - unregisters an object of led_classdev class
+ * with support for flash LEDs
+ * @flash: the flash LED to unregister
+ *
+ * Unregister a previously registered via led_classdev_flash_register object
+ */
+void led_classdev_flash_unregister(struct led_classdev_flash *flash);
+
+/**
+ * led_set_flash_strobe - setup flash strobe
+ * @flash: the flash LED to set strobe on
+ * @state: 1 - strobe flash, 0 - stop flash strobe
+ *
+ * Strobe the flash LED.
+ *
+ * Returns: 0 on success or negative error value on failure
+ */
+extern int led_set_flash_strobe(struct led_classdev_flash *flash,
+ bool state);
+
+/**
+ * led_get_flash_strobe - get flash strobe status
+ * @flash: the flash LED to query
+ * @state: 1 - flash is strobing, 0 - flash is off
+ *
+ * Check whether the flash is strobing at the moment.
+ *
+ * Returns: 0 on success or negative error value on failure
+ */
+extern int led_get_flash_strobe(struct led_classdev_flash *flash,
+ bool *state);
+
+/**
+ * led_set_flash_brightness - set flash LED brightness
+ * @flash: the flash LED to set
+ * @brightness: the brightness to set it to
+ *
+ * Set a flash LED's brightness.
+ *
+ * Returns: 0 on success or negative error value on failure
+ */
+extern int led_set_flash_brightness(struct led_classdev_flash *flash,
+ u32 brightness);
+
+/**
+ * led_update_flash_brightness - update flash LED brightness
+ * @flash: the flash LED to query
+ *
+ * Get a flash LED's current brightness and update led_flash->brightness
+ * member with the obtained value.
+ *
+ * Returns: 0 on success or negative error value on failure
+ */
+extern int led_update_flash_brightness(struct led_classdev_flash *flash);
+
+/**
+ * led_set_flash_timeout - set flash LED timeout
+ * @flash: the flash LED to set
+ * @timeout: the flash timeout to set it to
+ *
+ * Set the flash strobe duration. The duration set by the driver
+ * is returned in the timeout argument and may differ from the
+ * one that was originally passed.
+ *
+ * Returns: 0 on success or negative error value on failure
+ */
+extern int led_set_flash_timeout(struct led_classdev_flash *flash,
+ u32 timeout);
+
+/**
+ * led_get_flash_fault - get the flash LED fault
+ * @flash: the flash LED to query
+ * @fault: bitmask containing flash faults
+ *
+ * Get the flash LED fault.
+ *
+ * Returns: 0 on success or negative error value on failure
+ */
+extern int led_get_flash_fault(struct led_classdev_flash *flash,
+ u32 *fault);
+
+#endif /* __LINUX_FLASH_LEDS_H_INCLUDED */
diff --git a/include/linux/leds.h b/include/linux/leds.h
index cfceef3..c359f35 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -46,6 +46,8 @@ struct led_classdev {
#define LED_SYSFS_DISABLE (1 << 20)
#define SET_BRIGHTNESS_ASYNC (1 << 21)
#define SET_BRIGHTNESS_SYNC (1 << 22)
+#define LED_DEV_CAP_FLASH (1 << 23)
+#define LED_DEV_CAP_COMPOUND (1 << 24)

/* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */
@@ -81,6 +83,7 @@ struct led_classdev {
unsigned long blink_delay_on, blink_delay_off;
struct timer_list blink_timer;
int blink_brightness;
+ void (*flash_resume)(struct led_classdev *led_cdev);

struct work_struct set_brightness_work;
int delayed_set_value;
--
1.7.9.5

2014-12-03 16:59:34

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 01/19] leds: Add LED Flash class extension to the LED subsystem

Hi Jacek,

Thanks for the update. A few comments below.

On Wed, Dec 03, 2014 at 05:06:36PM +0100, Jacek Anaszewski wrote:
> Some LED devices support two operation modes - torch and flash.
> This patch provides support for flash LED devices in the LED subsystem
> by introducing new sysfs attributes and kernel internal interface.
> The attributes being introduced are: flash_brightness, flash_strobe,
> flash_timeout, max_flash_timeout, max_flash_brightness, flash_fault
> and flash_sync_strobe. All the flash related features are placed
> in a separate module. Torch mode is supported by the LED class
> interface.
>
> The modifications aim to be compatible with V4L2 framework requirements
> related to the flash devices management. The design assumes that V4L2
> sub-device can take of the LED class device control and communicate
> with it through the kernel internal interface. When V4L2 Flash sub-device
> file is opened, the LED class device sysfs interface is made
> unavailable.
>
> Signed-off-by: Jacek Anaszewski <[email protected]>
> Acked-by: Kyungmin Park <[email protected]>
> Cc: Bryan Wu <[email protected]>
> Cc: Richard Purdie <[email protected]>
> ---
> drivers/leds/Kconfig | 10 +
> drivers/leds/Makefile | 1 +
> drivers/leds/led-class-flash.c | 446 +++++++++++++++++++++++++++++++++++++++
> drivers/leds/led-class.c | 4 +
> include/linux/led-class-flash.h | 186 ++++++++++++++++
> include/linux/leds.h | 3 +
> 6 files changed, 650 insertions(+)
> create mode 100644 drivers/leds/led-class-flash.c
> create mode 100644 include/linux/led-class-flash.h
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index b3c0d8a..fa8021e 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -19,6 +19,16 @@ config LEDS_CLASS
> This option enables the led sysfs class in /sys/class/leds. You'll
> need this to do anything useful with LEDs. If unsure, say N.
>
> +config LEDS_CLASS_FLASH
> + tristate "LED Flash Class Support"
> + depends on LEDS_CLASS
> + help
> + This option enables the flash led sysfs class in /sys/class/leds.
> + It wrapps LED Class and adds flash LEDs specific sysfs attributes
> + and kernel internal API to it. You'll need this to provide support
> + for the flash related features of a LED device. It can be built
> + as a module.
> +
> comment "LED drivers"
>
> config LEDS_88PM860X
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 1c65a19..cbba921 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -2,6 +2,7 @@
> # LED Core
> obj-$(CONFIG_NEW_LEDS) += led-core.o
> obj-$(CONFIG_LEDS_CLASS) += led-class.o
> +obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
> obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
>
> # LED Platform Drivers
> diff --git a/drivers/leds/led-class-flash.c b/drivers/leds/led-class-flash.c
> new file mode 100644
> index 0000000..219b414
> --- /dev/null
> +++ b/drivers/leds/led-class-flash.c
> @@ -0,0 +1,446 @@
> +/*
> + * LED Flash class interface
> + *
> + * Copyright (C) 2014 Samsung Electronics Co., Ltd.
> + * Author: Jacek Anaszewski <[email protected]>
> + *
> + * 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/device.h>
> +#include <linux/init.h>
> +#include <linux/led-class-flash.h>
> +#include <linux/leds.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include "leds.h"
> +
> +#define has_flash_op(flash, op) \
> + (flash && flash->ops->op)
> +
> +#define call_flash_op(flash, op, args...) \
> + ((has_flash_op(flash, op)) ? \
> + (flash->ops->op(flash, args)) : \
> + -EINVAL)
> +
> +static ssize_t flash_brightness_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t size)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> + unsigned long state;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_access);
> +
> + if (led_sysfs_is_disabled(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &state);
> + if (ret)
> + goto unlock;
> +
> + ret = led_set_flash_brightness(flash, state);
> + if (ret < 0)
> + goto unlock;
> +
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_access);
> + return ret;
> +}
> +
> +static ssize_t flash_brightness_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> +
> + /* no lock needed for this */
> + led_update_flash_brightness(flash);
> +
> + return sprintf(buf, "%u\n", flash->brightness.val);
> +}
> +static DEVICE_ATTR_RW(flash_brightness);
> +
> +static ssize_t max_flash_brightness_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> +
> + return sprintf(buf, "%u\n", flash->brightness.max);
> +}
> +static DEVICE_ATTR_RO(max_flash_brightness);
> +
> +static ssize_t flash_strobe_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t size)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> + unsigned long state;
> + ssize_t ret = -EINVAL;
> +
> + mutex_lock(&led_cdev->led_access);
> +
> + if (led_sysfs_is_disabled(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &state);
> + if (ret)
> + goto unlock;
> +
> + if (state < 0 || state > 1) {
> + ret = -EINVAL;
> + goto unlock;
> + }
> +
> + ret = led_set_flash_strobe(flash, state);
> + if (ret < 0)
> + goto unlock;
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_access);
> + return ret;
> +}
> +
> +static ssize_t flash_strobe_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> + bool state;
> + int ret;
> +
> + /* no lock needed for this */
> + ret = led_get_flash_strobe(flash, &state);
> + if (ret < 0)
> + return ret;
> +
> + return sprintf(buf, "%u\n", state);
> +}
> +static DEVICE_ATTR_RW(flash_strobe);
> +
> +static ssize_t flash_timeout_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t size)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> + unsigned long flash_timeout;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_access);
> +
> + if (led_sysfs_is_disabled(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &flash_timeout);
> + if (ret)
> + goto unlock;
> +
> + ret = led_set_flash_timeout(flash, flash_timeout);
> + if (ret < 0)
> + goto unlock;
> +
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_access);
> + return ret;
> +}
> +
> +static ssize_t flash_timeout_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> +
> + return sprintf(buf, "%u\n", flash->timeout.val);
> +}
> +static DEVICE_ATTR_RW(flash_timeout);
> +
> +static ssize_t max_flash_timeout_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> +
> + return sprintf(buf, "%u\n", flash->timeout.max);
> +}
> +static DEVICE_ATTR_RO(max_flash_timeout);
> +
> +static ssize_t flash_fault_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> + u32 fault;
> + int ret;
> +
> + ret = led_get_flash_fault(flash, &fault);
> + if (ret < 0)
> + return -EINVAL;
> +
> + return sprintf(buf, "0x%8.8x\n", fault);
> +}
> +static DEVICE_ATTR_RO(flash_fault);
> +
> +static ssize_t flash_sync_strobe_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t size)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> + unsigned long sync_strobe;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_access);
> +
> + if (led_sysfs_is_disabled(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &sync_strobe);
> + if (ret)
> + goto unlock;
> +
> + flash->sync_strobe = sync_strobe;
> +
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_access);
> + return ret;
> +}
> +
> +static ssize_t flash_sync_strobe_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> +
> + return sprintf(buf, "%u\n", flash->sync_strobe);
> +}
> +static DEVICE_ATTR_RW(flash_sync_strobe);
> +
> +static struct attribute *led_flash_strobe_attrs[] = {
> + &dev_attr_flash_strobe.attr,
> + NULL,
> +};
> +
> +static struct attribute *led_flash_timeout_attrs[] = {
> + &dev_attr_flash_timeout.attr,
> + &dev_attr_max_flash_timeout.attr,
> + NULL,
> +};
> +
> +static struct attribute *led_flash_brightness_attrs[] = {
> + &dev_attr_flash_brightness.attr,
> + &dev_attr_max_flash_brightness.attr,
> + NULL,
> +};
> +
> +static struct attribute *led_flash_fault_attrs[] = {
> + &dev_attr_flash_fault.attr,
> + NULL,
> +};
> +
> +static struct attribute *led_flash_sync_strobe_attrs[] = {
> + &dev_attr_flash_sync_strobe.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group led_flash_strobe_group = {
> + .attrs = led_flash_strobe_attrs,
> +};
> +
> +static const struct attribute_group led_flash_timeout_group = {
> + .attrs = led_flash_timeout_attrs,
> +};
> +
> +static const struct attribute_group led_flash_brightness_group = {
> + .attrs = led_flash_brightness_attrs,
> +};
> +
> +static const struct attribute_group led_flash_fault_group = {
> + .attrs = led_flash_fault_attrs,
> +};
> +
> +static const struct attribute_group led_flash_sync_strobe_group = {
> + .attrs = led_flash_sync_strobe_attrs,
> +};
> +
> +static const struct attribute_group *flash_groups[] = {
> + &led_flash_strobe_group,
> + NULL,
> + NULL,
> + NULL,
> + NULL,
> + NULL,
> + NULL
> +};
> +
> +static void led_flash_resume(struct led_classdev *led_cdev)
> +{
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> +
> + call_flash_op(flash, flash_brightness_set, flash->brightness.val);
> + call_flash_op(flash, timeout_set, flash->timeout.val);
> +}
> +
> +static void led_flash_init_sysfs_groups(struct led_classdev_flash *flash)
> +{
> + struct led_classdev *led_cdev = &flash->led_cdev;
> + const struct led_flash_ops *ops = flash->ops;
> + int num_sysfs_groups = 1;
> +
> + if (ops->flash_brightness_set)
> + flash_groups[num_sysfs_groups++] = &led_flash_brightness_group;
> +
> + if (ops->timeout_set)
> + flash_groups[num_sysfs_groups++] = &led_flash_timeout_group;
> +
> + if (ops->fault_get)
> + flash_groups[num_sysfs_groups++] = &led_flash_fault_group;
> +
> + if (led_cdev->flags & LED_DEV_CAP_COMPOUND)
> + flash_groups[num_sysfs_groups++] = &led_flash_sync_strobe_group;
> +
> + led_cdev->groups = flash_groups;

Shouldn't you have groups local to the device instead? If you register
another flash device bad things will happen if the ops the device supports
are different.

> +}
> +
> +int led_classdev_flash_register(struct device *parent,
> + struct led_classdev_flash *flash)
> +{
> + struct led_classdev *led_cdev;
> + const struct led_flash_ops *ops;
> + int ret;
> +
> + if (!flash)

Do you have a use case for this?

> + return -EINVAL;
> +
> + led_cdev = &flash->led_cdev;
> +
> + if (led_cdev->flags & LED_DEV_CAP_FLASH) {
> + if (!led_cdev->brightness_set_sync)
> + return -EINVAL;
> +
> + ops = flash->ops;
> + if (!ops || !ops->strobe_set)
> + return -EINVAL;
> +
> + led_cdev->flash_resume = led_flash_resume;
> +
> + /* Select the sysfs attributes to be created for the device */
> + led_flash_init_sysfs_groups(flash);
> + }
> +
> + /* Register led class device */
> + ret = led_classdev_register(parent, led_cdev);
> + if (ret < 0)
> + return ret;
> +
> + /* Setting a torch brightness needs to have immediate effect */
> + led_cdev->flags &= ~SET_BRIGHTNESS_ASYNC;
> + led_cdev->flags |= SET_BRIGHTNESS_SYNC;

I'm ok with making this a property of the device (rather than interface as I
originally thought it).

> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(led_classdev_flash_register);
> +
> +void led_classdev_flash_unregister(struct led_classdev_flash *flash)
> +{
> + if (!flash)
> + return;
> +
> + led_classdev_unregister(&flash->led_cdev);
> +}
> +EXPORT_SYMBOL_GPL(led_classdev_flash_unregister);
> +
> +int led_set_flash_strobe(struct led_classdev_flash *flash, bool state)
> +{
> + return call_flash_op(flash, strobe_set, state);
> +}
> +EXPORT_SYMBOL_GPL(led_set_flash_strobe);
> +
> +int led_get_flash_strobe(struct led_classdev_flash *flash, bool *state)
> +{
> + return call_flash_op(flash, strobe_get, state);
> +}
> +EXPORT_SYMBOL_GPL(led_get_flash_strobe);

I'd instead have these in the header as static inline since they're just a
single function call, but up to you.

> +static void led_clamp_align(struct led_flash_setting *s)
> +{
> + u32 v, offset;
> +
> + v = s->val + s->step / 2;
> + v = clamp(v, s->min, s->max);
> + offset = v - s->min;
> + offset = s->step * (offset / s->step);
> + s->val = s->min + offset;
> +}
> +
> +int led_set_flash_timeout(struct led_classdev_flash *flash, u32 timeout)
> +{
> + struct led_classdev *led_cdev = &flash->led_cdev;
> + struct led_flash_setting *s = &flash->timeout;
> +
> + s->val = timeout;
> + led_clamp_align(s);
> +
> + if (!(led_cdev->flags & LED_SUSPENDED))
> + return call_flash_op(flash, timeout_set, s->val);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(led_set_flash_timeout);
> +
> +int led_get_flash_fault(struct led_classdev_flash *flash, u32 *fault)
> +{
> + return call_flash_op(flash, fault_get, fault);
> +}
> +EXPORT_SYMBOL_GPL(led_get_flash_fault);
> +
> +int led_set_flash_brightness(struct led_classdev_flash *flash,
> + u32 brightness)
> +{
> + struct led_classdev *led_cdev = &flash->led_cdev;
> + struct led_flash_setting *s = &flash->brightness;
> +
> + s->val = brightness;
> + led_clamp_align(s);
> +
> + if (!(led_cdev->flags & LED_SUSPENDED))
> + return call_flash_op(flash, flash_brightness_set, s->val);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(led_set_flash_brightness);
> +
> +int led_update_flash_brightness(struct led_classdev_flash *flash)
> +{
> + struct led_flash_setting *s = &flash->brightness;
> + u32 brightness;
> +
> + if (has_flash_op(flash, flash_brightness_get)) {
> + int ret = call_flash_op(flash, flash_brightness_get,
> + &brightness);
> + if (ret < 0)
> + return ret;
> +
> + s->val = brightness;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(led_update_flash_brightness);
> +
> +MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("LED Flash class Interface");
> diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
> index dbeebac..02564c5 100644
> --- a/drivers/leds/led-class.c
> +++ b/drivers/leds/led-class.c
> @@ -179,6 +179,10 @@ EXPORT_SYMBOL_GPL(led_classdev_suspend);
> void led_classdev_resume(struct led_classdev *led_cdev)
> {
> led_cdev->brightness_set(led_cdev, led_cdev->brightness);
> +
> + if (led_cdev->flash_resume)
> + led_cdev->flash_resume(led_cdev);
> +
> led_cdev->flags &= ~LED_SUSPENDED;
> }
> EXPORT_SYMBOL_GPL(led_classdev_resume);
> diff --git a/include/linux/led-class-flash.h b/include/linux/led-class-flash.h
> new file mode 100644
> index 0000000..2043082
> --- /dev/null
> +++ b/include/linux/led-class-flash.h
> @@ -0,0 +1,186 @@
> +/*
> + * LED Flash class interface
> + *
> + * Copyright (C) 2014 Samsung Electronics Co., Ltd.
> + * Author: Jacek Anaszewski <[email protected]>
> + *
> + * 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 __LINUX_FLASH_LEDS_H_INCLUDED
> +#define __LINUX_FLASH_LEDS_H_INCLUDED
> +
> +#include <linux/leds.h>
> +#include <uapi/linux/v4l2-controls.h>
> +
> +struct device_node;
> +struct led_classdev_flash;
> +
> +/*
> + * Supported led fault bits - must be kept in synch
> + * with V4L2_FLASH_FAULT bits.
> + */
> +#define LED_FAULT_OVER_VOLTAGE V4L2_FLASH_FAULT_OVER_VOLTAGE
> +#define LED_FAULT_TIMEOUT V4L2_FLASH_FAULT_TIMEOUT
> +#define LED_FAULT_OVER_TEMPERATURE V4L2_FLASH_FAULT_OVER_TEMPERATURE
> +#define LED_FAULT_SHORT_CIRCUIT V4L2_FLASH_FAULT_SHORT_CIRCUIT
> +#define LED_FAULT_OVER_CURRENT V4L2_FLASH_FAULT_OVER_CURRENT
> +#define LED_FAULT_INDICATOR V4L2_FLASH_FAULT_INDICATOR
> +#define LED_FAULT_UNDER_VOLTAGE V4L2_FLASH_FAULT_UNDER_VOLTAGE
> +#define LED_FAULT_INPUT_VOLTAGE V4L2_FLASH_FAULT_INPUT_VOLTAGE
> +#define LED_FAULT_LED_OVER_TEMPERATURE V4L2_FLASH_OVER_TEMPERATURE

V4L2_FLASH_FAULT_LED_OVER_TEMPERATURE

> +
> +struct led_flash_ops {
> + /* set flash brightness */
> + int (*flash_brightness_set)(struct led_classdev_flash *flash,
> + u32 brightness);
> + /* get flash brightness */
> + int (*flash_brightness_get)(struct led_classdev_flash *flash,
> + u32 *brightness);
> + /* set flash strobe state */
> + int (*strobe_set)(struct led_classdev_flash *flash, bool state);
> + /* get flash strobe state */
> + int (*strobe_get)(struct led_classdev_flash *flash, bool *state);
> + /* set flash timeout */
> + int (*timeout_set)(struct led_classdev_flash *flash, u32 timeout);
> + /* get the flash LED fault */
> + int (*fault_get)(struct led_classdev_flash *flash, u32 *fault);
> +};
> +
> +/*
> + * Current value of a flash setting along
> + * with its constraints.
> + */
> +struct led_flash_setting {
> + /* maximum allowed value */
> + u32 min;
> + /* maximum allowed value */
> + u32 max;
> + /* step value */
> + u32 step;
> + /* current value */
> + u32 val;
> +};
> +
> +struct led_classdev_flash {
> + /* led class device */
> + struct led_classdev led_cdev;
> +
> + /* flash led specific ops */
> + const struct led_flash_ops *ops;
> +
> + /* flash brightness value in microamperes along with its constraints */
> + struct led_flash_setting brightness;
> +
> + /* flash timeout value in microseconds along with its constraints */
> + struct led_flash_setting timeout;
> +
> + /*
> + * Indicates whether the flash sub-led should strobe
> + * upon strobe activation on any of the remaining sub-leds.
> + */
> + bool sync_strobe:1;
> +};
> +
> +static inline struct led_classdev_flash *lcdev_to_flash(
> + struct led_classdev *lcdev)
> +{
> + return container_of(lcdev, struct led_classdev_flash, led_cdev);
> +}
> +
> +/**
> + * led_classdev_flash_register - register a new object of led_classdev class
> + * with support for flash LEDs
> + * @parent: the flash LED to register
> + * @flash: the led_classdev_flash structure for this device
> + *
> + * Returns: 0 on success or negative error value on failure
> + */
> +int led_classdev_flash_register(struct device *parent,
> + struct led_classdev_flash *flash);
> +
> +/**
> + * led_classdev_flash_unregister - unregisters an object of led_classdev class
> + * with support for flash LEDs
> + * @flash: the flash LED to unregister
> + *
> + * Unregister a previously registered via led_classdev_flash_register object
> + */
> +void led_classdev_flash_unregister(struct led_classdev_flash *flash);
> +
> +/**
> + * led_set_flash_strobe - setup flash strobe
> + * @flash: the flash LED to set strobe on
> + * @state: 1 - strobe flash, 0 - stop flash strobe
> + *
> + * Strobe the flash LED.
> + *
> + * Returns: 0 on success or negative error value on failure
> + */
> +extern int led_set_flash_strobe(struct led_classdev_flash *flash,
> + bool state);
> +
> +/**
> + * led_get_flash_strobe - get flash strobe status
> + * @flash: the flash LED to query
> + * @state: 1 - flash is strobing, 0 - flash is off
> + *
> + * Check whether the flash is strobing at the moment.
> + *
> + * Returns: 0 on success or negative error value on failure
> + */
> +extern int led_get_flash_strobe(struct led_classdev_flash *flash,
> + bool *state);

Fits on a single line. The same above.

> +
> +/**
> + * led_set_flash_brightness - set flash LED brightness
> + * @flash: the flash LED to set
> + * @brightness: the brightness to set it to
> + *
> + * Set a flash LED's brightness.
> + *
> + * Returns: 0 on success or negative error value on failure
> + */
> +extern int led_set_flash_brightness(struct led_classdev_flash *flash,
> + u32 brightness);
> +
> +/**
> + * led_update_flash_brightness - update flash LED brightness
> + * @flash: the flash LED to query
> + *
> + * Get a flash LED's current brightness and update led_flash->brightness
> + * member with the obtained value.
> + *
> + * Returns: 0 on success or negative error value on failure
> + */
> +extern int led_update_flash_brightness(struct led_classdev_flash *flash);
> +
> +/**
> + * led_set_flash_timeout - set flash LED timeout
> + * @flash: the flash LED to set
> + * @timeout: the flash timeout to set it to
> + *
> + * Set the flash strobe duration. The duration set by the driver
> + * is returned in the timeout argument and may differ from the
> + * one that was originally passed.
> + *
> + * Returns: 0 on success or negative error value on failure
> + */
> +extern int led_set_flash_timeout(struct led_classdev_flash *flash,
> + u32 timeout);
> +
> +/**
> + * led_get_flash_fault - get the flash LED fault
> + * @flash: the flash LED to query
> + * @fault: bitmask containing flash faults
> + *
> + * Get the flash LED fault.
> + *
> + * Returns: 0 on success or negative error value on failure
> + */
> +extern int led_get_flash_fault(struct led_classdev_flash *flash,
> + u32 *fault);
> +
> +#endif /* __LINUX_FLASH_LEDS_H_INCLUDED */
> diff --git a/include/linux/leds.h b/include/linux/leds.h
> index cfceef3..c359f35 100644
> --- a/include/linux/leds.h
> +++ b/include/linux/leds.h
> @@ -46,6 +46,8 @@ struct led_classdev {
> #define LED_SYSFS_DISABLE (1 << 20)
> #define SET_BRIGHTNESS_ASYNC (1 << 21)
> #define SET_BRIGHTNESS_SYNC (1 << 22)
> +#define LED_DEV_CAP_FLASH (1 << 23)
> +#define LED_DEV_CAP_COMPOUND (1 << 24)
>
> /* Set LED brightness level */
> /* Must not sleep, use a workqueue if needed */
> @@ -81,6 +83,7 @@ struct led_classdev {
> unsigned long blink_delay_on, blink_delay_off;
> struct timer_list blink_timer;
> int blink_brightness;
> + void (*flash_resume)(struct led_classdev *led_cdev);
>
> struct work_struct set_brightness_work;
> int delayed_set_value;

--
Kind regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-03 17:08:23

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 02/19] Documentation: leds: Add description of LED Flash class extension

Hi Jacek,

On Wed, Dec 03, 2014 at 05:06:37PM +0100, Jacek Anaszewski wrote:
> The documentation being added contains overall description of the
> LED Flash Class and the related sysfs attributes.
>
> Signed-off-by: Jacek Anaszewski <[email protected]>
> Acked-by: Kyungmin Park <[email protected]>
> Cc: Bryan Wu <[email protected]>
> Cc: Richard Purdie <[email protected]>
> ---
> Documentation/leds/leds-class-flash.txt | 50 +++++++++++++++++++++++++++++++
> 1 file changed, 50 insertions(+)
> create mode 100644 Documentation/leds/leds-class-flash.txt
>
> diff --git a/Documentation/leds/leds-class-flash.txt b/Documentation/leds/leds-class-flash.txt
> new file mode 100644
> index 0000000..82e58b1
> --- /dev/null
> +++ b/Documentation/leds/leds-class-flash.txt
> @@ -0,0 +1,50 @@
> +
> +Flash LED handling under Linux
> +==============================
> +
> +Some LED devices support two modes - torch and flash. The modes are
> +supported by the LED class (see Documentation/leds/leds-class.txt)
> +and LED Flash class respectively.
> +
> +In order to enable support for flash LEDs CONFIG_LEDS_CLASS_FLASH symbol
> +must be defined in the kernel config. A flash LED driver must register
> +in the LED subsystem with led_classdev_flash_register to gain flash
> +capabilities.
> +
> +Following sysfs attributes are exposed for controlling flash led devices:
> +
> + - flash_brightness - flash LED brightness in microamperes (RW)
> + - max_flash_brightness - maximum available flash LED brightness (RO)
> + - flash_timeout - flash strobe duration in microseconds (RW)
> + - max_flash_timeout - maximum available flash strobe duration (RO)
> + - flash_strobe - flash strobe state (RW)
> + - flash_sync_strobe - one flash device can control more than one
> + sub-led; when this atrribute is set to 1

s/atrribute/attribute/

> + the flash led will be strobed synchronously
> + with the other one controlled by the same
> + device; flash timeout setting is inherited
> + from the led being strobed explicitly and
> + flash brightness setting of a sub-led's
> + being synchronized is used (RW)

The flash brightness shouldn't be determined by the strobed LED. If this is
a property of the hardware, then be it, but in general no, it it shouldn't
be an interface requirement. I think this should just say that the strobe is
synchronised.

How does the user btw. figure out which flash LEDs may be strobed
synchronously using the LED flash interface?

> + - flash_fault - bitmask of flash faults that may have occurred
> + possible flags are:
> + * 0x01 - flash controller voltage to the flash LED has exceeded
> + the limit specific to the flash controller
> + * 0x02 - the flash strobe was still on when the timeout set by
> + the user has expired; not all flash controllers may
> + set this in all such conditions
> + * 0x04 - the flash controller has overheated
> + * 0x08 - the short circuit protection of the flash controller
> + has been triggered
> + * 0x10 - current in the LED power supply has exceeded the limit
> + specific to the flash controller
> + * 0x40 - flash controller voltage to the flash LED has been
> + below the minimum limit specific to the flash
> + * 0x80 - the input voltage of the flash controller is below
> + the limit under which strobing the flash at full
> + current will not be possible. The condition persists
> + until this flag is no longer set
> + * 0x100 - the temperature of the LED has exceeded its allowed
> + upper limit
> +
> + Flash faults are cleared by reading the attribute.

--
Kind regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-04 09:29:34

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 01/19] leds: Add LED Flash class extension to the LED subsystem

Hi Sakari,

Thanks for the review.

On 12/03/2014 05:50 PM, Sakari Ailus wrote:
> Hi Jacek,
>
> Thanks for the update. A few comments below.
>
> On Wed, Dec 03, 2014 at 05:06:36PM +0100, Jacek Anaszewski wrote:
>> Some LED devices support two operation modes - torch and flash.
>> This patch provides support for flash LED devices in the LED subsystem
>> by introducing new sysfs attributes and kernel internal interface.
>> The attributes being introduced are: flash_brightness, flash_strobe,
>> flash_timeout, max_flash_timeout, max_flash_brightness, flash_fault
>> and flash_sync_strobe. All the flash related features are placed
>> in a separate module. Torch mode is supported by the LED class
>> interface.
>>
>> The modifications aim to be compatible with V4L2 framework requirements
>> related to the flash devices management. The design assumes that V4L2
>> sub-device can take of the LED class device control and communicate
>> with it through the kernel internal interface. When V4L2 Flash sub-device
>> file is opened, the LED class device sysfs interface is made
>> unavailable.
>>
>> Signed-off-by: Jacek Anaszewski <[email protected]>
>> Acked-by: Kyungmin Park <[email protected]>
>> Cc: Bryan Wu <[email protected]>
>> Cc: Richard Purdie <[email protected]>
>> ---
>> drivers/leds/Kconfig | 10 +
>> drivers/leds/Makefile | 1 +
>> drivers/leds/led-class-flash.c | 446 +++++++++++++++++++++++++++++++++++++++
>> drivers/leds/led-class.c | 4 +
>> include/linux/led-class-flash.h | 186 ++++++++++++++++
>> include/linux/leds.h | 3 +
>> 6 files changed, 650 insertions(+)
>> create mode 100644 drivers/leds/led-class-flash.c
>> create mode 100644 include/linux/led-class-flash.h
>>
>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>> index b3c0d8a..fa8021e 100644
>> --- a/drivers/leds/Kconfig
>> +++ b/drivers/leds/Kconfig
>> @@ -19,6 +19,16 @@ config LEDS_CLASS
>> This option enables the led sysfs class in /sys/class/leds. You'll
>> need this to do anything useful with LEDs. If unsure, say N.
>>
>> +config LEDS_CLASS_FLASH
>> + tristate "LED Flash Class Support"
>> + depends on LEDS_CLASS
>> + help
>> + This option enables the flash led sysfs class in /sys/class/leds.
>> + It wrapps LED Class and adds flash LEDs specific sysfs attributes
>> + and kernel internal API to it. You'll need this to provide support
>> + for the flash related features of a LED device. It can be built
>> + as a module.
>> +
>> comment "LED drivers"
>>
>> config LEDS_88PM860X
>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>> index 1c65a19..cbba921 100644
>> --- a/drivers/leds/Makefile
>> +++ b/drivers/leds/Makefile
>> @@ -2,6 +2,7 @@
>> # LED Core
>> obj-$(CONFIG_NEW_LEDS) += led-core.o
>> obj-$(CONFIG_LEDS_CLASS) += led-class.o
>> +obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
>> obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
>>
>> # LED Platform Drivers
>> diff --git a/drivers/leds/led-class-flash.c b/drivers/leds/led-class-flash.c
>> new file mode 100644
>> index 0000000..219b414
>> --- /dev/null
>> +++ b/drivers/leds/led-class-flash.c
>> @@ -0,0 +1,446 @@
>> +/*
>> + * LED Flash class interface
>> + *
>> + * Copyright (C) 2014 Samsung Electronics Co., Ltd.
>> + * Author: Jacek Anaszewski <[email protected]>
>> + *
>> + * 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/device.h>
>> +#include <linux/init.h>
>> +#include <linux/led-class-flash.h>
>> +#include <linux/leds.h>
>> +#include <linux/module.h>
>> +#include <linux/slab.h>
>> +#include "leds.h"
>> +
>> +#define has_flash_op(flash, op) \
>> + (flash && flash->ops->op)
>> +
>> +#define call_flash_op(flash, op, args...) \
>> + ((has_flash_op(flash, op)) ? \
>> + (flash->ops->op(flash, args)) : \
>> + -EINVAL)
>> +
>> +static ssize_t flash_brightness_store(struct device *dev,
>> + struct device_attribute *attr, const char *buf, size_t size)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> + unsigned long state;
>> + ssize_t ret;
>> +
>> + mutex_lock(&led_cdev->led_access);
>> +
>> + if (led_sysfs_is_disabled(led_cdev)) {
>> + ret = -EBUSY;
>> + goto unlock;
>> + }
>> +
>> + ret = kstrtoul(buf, 10, &state);
>> + if (ret)
>> + goto unlock;
>> +
>> + ret = led_set_flash_brightness(flash, state);
>> + if (ret < 0)
>> + goto unlock;
>> +
>> + ret = size;
>> +unlock:
>> + mutex_unlock(&led_cdev->led_access);
>> + return ret;
>> +}
>> +
>> +static ssize_t flash_brightness_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> +
>> + /* no lock needed for this */
>> + led_update_flash_brightness(flash);
>> +
>> + return sprintf(buf, "%u\n", flash->brightness.val);
>> +}
>> +static DEVICE_ATTR_RW(flash_brightness);
>> +
>> +static ssize_t max_flash_brightness_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> +
>> + return sprintf(buf, "%u\n", flash->brightness.max);
>> +}
>> +static DEVICE_ATTR_RO(max_flash_brightness);
>> +
>> +static ssize_t flash_strobe_store(struct device *dev,
>> + struct device_attribute *attr, const char *buf, size_t size)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> + unsigned long state;
>> + ssize_t ret = -EINVAL;
>> +
>> + mutex_lock(&led_cdev->led_access);
>> +
>> + if (led_sysfs_is_disabled(led_cdev)) {
>> + ret = -EBUSY;
>> + goto unlock;
>> + }
>> +
>> + ret = kstrtoul(buf, 10, &state);
>> + if (ret)
>> + goto unlock;
>> +
>> + if (state < 0 || state > 1) {
>> + ret = -EINVAL;
>> + goto unlock;
>> + }
>> +
>> + ret = led_set_flash_strobe(flash, state);
>> + if (ret < 0)
>> + goto unlock;
>> + ret = size;
>> +unlock:
>> + mutex_unlock(&led_cdev->led_access);
>> + return ret;
>> +}
>> +
>> +static ssize_t flash_strobe_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> + bool state;
>> + int ret;
>> +
>> + /* no lock needed for this */
>> + ret = led_get_flash_strobe(flash, &state);
>> + if (ret < 0)
>> + return ret;
>> +
>> + return sprintf(buf, "%u\n", state);
>> +}
>> +static DEVICE_ATTR_RW(flash_strobe);
>> +
>> +static ssize_t flash_timeout_store(struct device *dev,
>> + struct device_attribute *attr, const char *buf, size_t size)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> + unsigned long flash_timeout;
>> + ssize_t ret;
>> +
>> + mutex_lock(&led_cdev->led_access);
>> +
>> + if (led_sysfs_is_disabled(led_cdev)) {
>> + ret = -EBUSY;
>> + goto unlock;
>> + }
>> +
>> + ret = kstrtoul(buf, 10, &flash_timeout);
>> + if (ret)
>> + goto unlock;
>> +
>> + ret = led_set_flash_timeout(flash, flash_timeout);
>> + if (ret < 0)
>> + goto unlock;
>> +
>> + ret = size;
>> +unlock:
>> + mutex_unlock(&led_cdev->led_access);
>> + return ret;
>> +}
>> +
>> +static ssize_t flash_timeout_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> +
>> + return sprintf(buf, "%u\n", flash->timeout.val);
>> +}
>> +static DEVICE_ATTR_RW(flash_timeout);
>> +
>> +static ssize_t max_flash_timeout_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> +
>> + return sprintf(buf, "%u\n", flash->timeout.max);
>> +}
>> +static DEVICE_ATTR_RO(max_flash_timeout);
>> +
>> +static ssize_t flash_fault_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> + u32 fault;
>> + int ret;
>> +
>> + ret = led_get_flash_fault(flash, &fault);
>> + if (ret < 0)
>> + return -EINVAL;
>> +
>> + return sprintf(buf, "0x%8.8x\n", fault);
>> +}
>> +static DEVICE_ATTR_RO(flash_fault);
>> +
>> +static ssize_t flash_sync_strobe_store(struct device *dev,
>> + struct device_attribute *attr, const char *buf, size_t size)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> + unsigned long sync_strobe;
>> + ssize_t ret;
>> +
>> + mutex_lock(&led_cdev->led_access);
>> +
>> + if (led_sysfs_is_disabled(led_cdev)) {
>> + ret = -EBUSY;
>> + goto unlock;
>> + }
>> +
>> + ret = kstrtoul(buf, 10, &sync_strobe);
>> + if (ret)
>> + goto unlock;
>> +
>> + flash->sync_strobe = sync_strobe;
>> +
>> + ret = size;
>> +unlock:
>> + mutex_unlock(&led_cdev->led_access);
>> + return ret;
>> +}
>> +
>> +static ssize_t flash_sync_strobe_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> +
>> + return sprintf(buf, "%u\n", flash->sync_strobe);
>> +}
>> +static DEVICE_ATTR_RW(flash_sync_strobe);
>> +
>> +static struct attribute *led_flash_strobe_attrs[] = {
>> + &dev_attr_flash_strobe.attr,
>> + NULL,
>> +};
>> +
>> +static struct attribute *led_flash_timeout_attrs[] = {
>> + &dev_attr_flash_timeout.attr,
>> + &dev_attr_max_flash_timeout.attr,
>> + NULL,
>> +};
>> +
>> +static struct attribute *led_flash_brightness_attrs[] = {
>> + &dev_attr_flash_brightness.attr,
>> + &dev_attr_max_flash_brightness.attr,
>> + NULL,
>> +};
>> +
>> +static struct attribute *led_flash_fault_attrs[] = {
>> + &dev_attr_flash_fault.attr,
>> + NULL,
>> +};
>> +
>> +static struct attribute *led_flash_sync_strobe_attrs[] = {
>> + &dev_attr_flash_sync_strobe.attr,
>> + NULL,
>> +};
>> +
>> +static const struct attribute_group led_flash_strobe_group = {
>> + .attrs = led_flash_strobe_attrs,
>> +};
>> +
>> +static const struct attribute_group led_flash_timeout_group = {
>> + .attrs = led_flash_timeout_attrs,
>> +};
>> +
>> +static const struct attribute_group led_flash_brightness_group = {
>> + .attrs = led_flash_brightness_attrs,
>> +};
>> +
>> +static const struct attribute_group led_flash_fault_group = {
>> + .attrs = led_flash_fault_attrs,
>> +};
>> +
>> +static const struct attribute_group led_flash_sync_strobe_group = {
>> + .attrs = led_flash_sync_strobe_attrs,
>> +};
>> +
>> +static const struct attribute_group *flash_groups[] = {
>> + &led_flash_strobe_group,
>> + NULL,
>> + NULL,
>> + NULL,
>> + NULL,
>> + NULL,
>> + NULL
>> +};
>> +
>> +static void led_flash_resume(struct led_classdev *led_cdev)
>> +{
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> +
>> + call_flash_op(flash, flash_brightness_set, flash->brightness.val);
>> + call_flash_op(flash, timeout_set, flash->timeout.val);
>> +}
>> +
>> +static void led_flash_init_sysfs_groups(struct led_classdev_flash *flash)
>> +{
>> + struct led_classdev *led_cdev = &flash->led_cdev;
>> + const struct led_flash_ops *ops = flash->ops;
>> + int num_sysfs_groups = 1;
>> +
>> + if (ops->flash_brightness_set)
>> + flash_groups[num_sysfs_groups++] = &led_flash_brightness_group;
>> +
>> + if (ops->timeout_set)
>> + flash_groups[num_sysfs_groups++] = &led_flash_timeout_group;
>> +
>> + if (ops->fault_get)
>> + flash_groups[num_sysfs_groups++] = &led_flash_fault_group;
>> +
>> + if (led_cdev->flags & LED_DEV_CAP_COMPOUND)
>> + flash_groups[num_sysfs_groups++] = &led_flash_sync_strobe_group;
>> +
>> + led_cdev->groups = flash_groups;
>
> Shouldn't you have groups local to the device instead? If you register
> another flash device bad things will happen if the ops the device supports
> are different.

The groups are local to the device. A LED class device is registered
with device_create_with_groups called from led_classdev_register
function. It is passed led_cdev->groups in the fifth argument.

>> +}
>> +
>> +int led_classdev_flash_register(struct device *parent,
>> + struct led_classdev_flash *flash)
>> +{
>> + struct led_classdev *led_cdev;
>> + const struct led_flash_ops *ops;
>> + int ret;
>> +
>> + if (!flash)
>
> Do you have a use case for this?

This is just a guard against NULL pointer dereference. Maybe it is
indeed redundant, as the driver developer can easily check its
origin during implementation.

>> + return -EINVAL;
>> +
>> + led_cdev = &flash->led_cdev;
>> +
>> + if (led_cdev->flags & LED_DEV_CAP_FLASH) {
>> + if (!led_cdev->brightness_set_sync)
>> + return -EINVAL;
>> +
>> + ops = flash->ops;
>> + if (!ops || !ops->strobe_set)
>> + return -EINVAL;
>> +
>> + led_cdev->flash_resume = led_flash_resume;
>> +
>> + /* Select the sysfs attributes to be created for the device */
>> + led_flash_init_sysfs_groups(flash);
>> + }
>> +
>> + /* Register led class device */
>> + ret = led_classdev_register(parent, led_cdev);
>> + if (ret < 0)
>> + return ret;
>> +
>> + /* Setting a torch brightness needs to have immediate effect */
>> + led_cdev->flags &= ~SET_BRIGHTNESS_ASYNC;
>> + led_cdev->flags |= SET_BRIGHTNESS_SYNC;
>
> I'm ok with making this a property of the device (rather than interface as I
> originally thought it).

Ack.

>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(led_classdev_flash_register);
>> +
>> +void led_classdev_flash_unregister(struct led_classdev_flash *flash)
>> +{
>> + if (!flash)
>> + return;
>> +
>> + led_classdev_unregister(&flash->led_cdev);
>> +}
>> +EXPORT_SYMBOL_GPL(led_classdev_flash_unregister);
>> +
>> +int led_set_flash_strobe(struct led_classdev_flash *flash, bool state)
>> +{
>> + return call_flash_op(flash, strobe_set, state);
>> +}
>> +EXPORT_SYMBOL_GPL(led_set_flash_strobe);
>> +
>> +int led_get_flash_strobe(struct led_classdev_flash *flash, bool *state)
>> +{
>> + return call_flash_op(flash, strobe_get, state);
>> +}
>> +EXPORT_SYMBOL_GPL(led_get_flash_strobe);
>
> I'd instead have these in the header as static inline since they're just a
> single function call, but up to you.

Good point. I will apply it.

>> +static void led_clamp_align(struct led_flash_setting *s)
>> +{
>> + u32 v, offset;
>> +
>> + v = s->val + s->step / 2;
>> + v = clamp(v, s->min, s->max);
>> + offset = v - s->min;
>> + offset = s->step * (offset / s->step);
>> + s->val = s->min + offset;
>> +}
>> +
>> +int led_set_flash_timeout(struct led_classdev_flash *flash, u32 timeout)
>> +{
>> + struct led_classdev *led_cdev = &flash->led_cdev;
>> + struct led_flash_setting *s = &flash->timeout;
>> +
>> + s->val = timeout;
>> + led_clamp_align(s);
>> +
>> + if (!(led_cdev->flags & LED_SUSPENDED))
>> + return call_flash_op(flash, timeout_set, s->val);
>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(led_set_flash_timeout);
>> +
>> +int led_get_flash_fault(struct led_classdev_flash *flash, u32 *fault)
>> +{
>> + return call_flash_op(flash, fault_get, fault);
>> +}
>> +EXPORT_SYMBOL_GPL(led_get_flash_fault);
>> +
>> +int led_set_flash_brightness(struct led_classdev_flash *flash,
>> + u32 brightness)
>> +{
>> + struct led_classdev *led_cdev = &flash->led_cdev;
>> + struct led_flash_setting *s = &flash->brightness;
>> +
>> + s->val = brightness;
>> + led_clamp_align(s);
>> +
>> + if (!(led_cdev->flags & LED_SUSPENDED))
>> + return call_flash_op(flash, flash_brightness_set, s->val);
>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(led_set_flash_brightness);
>> +
>> +int led_update_flash_brightness(struct led_classdev_flash *flash)
>> +{
>> + struct led_flash_setting *s = &flash->brightness;
>> + u32 brightness;
>> +
>> + if (has_flash_op(flash, flash_brightness_get)) {
>> + int ret = call_flash_op(flash, flash_brightness_get,
>> + &brightness);
>> + if (ret < 0)
>> + return ret;
>> +
>> + s->val = brightness;
>> + }
>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(led_update_flash_brightness);
>> +
>> +MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("LED Flash class Interface");
>> diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
>> index dbeebac..02564c5 100644
>> --- a/drivers/leds/led-class.c
>> +++ b/drivers/leds/led-class.c
>> @@ -179,6 +179,10 @@ EXPORT_SYMBOL_GPL(led_classdev_suspend);
>> void led_classdev_resume(struct led_classdev *led_cdev)
>> {
>> led_cdev->brightness_set(led_cdev, led_cdev->brightness);
>> +
>> + if (led_cdev->flash_resume)
>> + led_cdev->flash_resume(led_cdev);
>> +
>> led_cdev->flags &= ~LED_SUSPENDED;
>> }
>> EXPORT_SYMBOL_GPL(led_classdev_resume);
>> diff --git a/include/linux/led-class-flash.h b/include/linux/led-class-flash.h
>> new file mode 100644
>> index 0000000..2043082
>> --- /dev/null
>> +++ b/include/linux/led-class-flash.h
>> @@ -0,0 +1,186 @@
>> +/*
>> + * LED Flash class interface
>> + *
>> + * Copyright (C) 2014 Samsung Electronics Co., Ltd.
>> + * Author: Jacek Anaszewski <[email protected]>
>> + *
>> + * 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 __LINUX_FLASH_LEDS_H_INCLUDED
>> +#define __LINUX_FLASH_LEDS_H_INCLUDED
>> +
>> +#include <linux/leds.h>
>> +#include <uapi/linux/v4l2-controls.h>
>> +
>> +struct device_node;
>> +struct led_classdev_flash;
>> +
>> +/*
>> + * Supported led fault bits - must be kept in synch
>> + * with V4L2_FLASH_FAULT bits.
>> + */
>> +#define LED_FAULT_OVER_VOLTAGE V4L2_FLASH_FAULT_OVER_VOLTAGE
>> +#define LED_FAULT_TIMEOUT V4L2_FLASH_FAULT_TIMEOUT
>> +#define LED_FAULT_OVER_TEMPERATURE V4L2_FLASH_FAULT_OVER_TEMPERATURE
>> +#define LED_FAULT_SHORT_CIRCUIT V4L2_FLASH_FAULT_SHORT_CIRCUIT
>> +#define LED_FAULT_OVER_CURRENT V4L2_FLASH_FAULT_OVER_CURRENT
>> +#define LED_FAULT_INDICATOR V4L2_FLASH_FAULT_INDICATOR
>> +#define LED_FAULT_UNDER_VOLTAGE V4L2_FLASH_FAULT_UNDER_VOLTAGE
>> +#define LED_FAULT_INPUT_VOLTAGE V4L2_FLASH_FAULT_INPUT_VOLTAGE
>> +#define LED_FAULT_LED_OVER_TEMPERATURE V4L2_FLASH_OVER_TEMPERATURE
>
> V4L2_FLASH_FAULT_LED_OVER_TEMPERATURE

Thanks for spotting this.

>> +
>> +struct led_flash_ops {
>> + /* set flash brightness */
>> + int (*flash_brightness_set)(struct led_classdev_flash *flash,
>> + u32 brightness);
>> + /* get flash brightness */
>> + int (*flash_brightness_get)(struct led_classdev_flash *flash,
>> + u32 *brightness);
>> + /* set flash strobe state */
>> + int (*strobe_set)(struct led_classdev_flash *flash, bool state);
>> + /* get flash strobe state */
>> + int (*strobe_get)(struct led_classdev_flash *flash, bool *state);
>> + /* set flash timeout */
>> + int (*timeout_set)(struct led_classdev_flash *flash, u32 timeout);
>> + /* get the flash LED fault */
>> + int (*fault_get)(struct led_classdev_flash *flash, u32 *fault);
>> +};
>> +
>> +/*
>> + * Current value of a flash setting along
>> + * with its constraints.
>> + */
>> +struct led_flash_setting {
>> + /* maximum allowed value */
>> + u32 min;
>> + /* maximum allowed value */
>> + u32 max;
>> + /* step value */
>> + u32 step;
>> + /* current value */
>> + u32 val;
>> +};
>> +
>> +struct led_classdev_flash {
>> + /* led class device */
>> + struct led_classdev led_cdev;
>> +
>> + /* flash led specific ops */
>> + const struct led_flash_ops *ops;
>> +
>> + /* flash brightness value in microamperes along with its constraints */
>> + struct led_flash_setting brightness;
>> +
>> + /* flash timeout value in microseconds along with its constraints */
>> + struct led_flash_setting timeout;
>> +
>> + /*
>> + * Indicates whether the flash sub-led should strobe
>> + * upon strobe activation on any of the remaining sub-leds.
>> + */
>> + bool sync_strobe:1;
>> +};
>> +
>> +static inline struct led_classdev_flash *lcdev_to_flash(
>> + struct led_classdev *lcdev)
>> +{
>> + return container_of(lcdev, struct led_classdev_flash, led_cdev);
>> +}
>> +
>> +/**
>> + * led_classdev_flash_register - register a new object of led_classdev class
>> + * with support for flash LEDs
>> + * @parent: the flash LED to register
>> + * @flash: the led_classdev_flash structure for this device
>> + *
>> + * Returns: 0 on success or negative error value on failure
>> + */
>> +int led_classdev_flash_register(struct device *parent,
>> + struct led_classdev_flash *flash);
>> +
>> +/**
>> + * led_classdev_flash_unregister - unregisters an object of led_classdev class
>> + * with support for flash LEDs
>> + * @flash: the flash LED to unregister
>> + *
>> + * Unregister a previously registered via led_classdev_flash_register object
>> + */
>> +void led_classdev_flash_unregister(struct led_classdev_flash *flash);
>> +
>> +/**
>> + * led_set_flash_strobe - setup flash strobe
>> + * @flash: the flash LED to set strobe on
>> + * @state: 1 - strobe flash, 0 - stop flash strobe
>> + *
>> + * Strobe the flash LED.
>> + *
>> + * Returns: 0 on success or negative error value on failure
>> + */
>> +extern int led_set_flash_strobe(struct led_classdev_flash *flash,
>> + bool state);
>> +
>> +/**
>> + * led_get_flash_strobe - get flash strobe status
>> + * @flash: the flash LED to query
>> + * @state: 1 - flash is strobing, 0 - flash is off
>> + *
>> + * Check whether the flash is strobing at the moment.
>> + *
>> + * Returns: 0 on success or negative error value on failure
>> + */
>> +extern int led_get_flash_strobe(struct led_classdev_flash *flash,
>> + bool *state);
>
> Fits on a single line. The same above.

Right.

>> +
>> +/**
>> + * led_set_flash_brightness - set flash LED brightness
>> + * @flash: the flash LED to set
>> + * @brightness: the brightness to set it to
>> + *
>> + * Set a flash LED's brightness.
>> + *
>> + * Returns: 0 on success or negative error value on failure
>> + */
>> +extern int led_set_flash_brightness(struct led_classdev_flash *flash,
>> + u32 brightness);
>> +
>> +/**
>> + * led_update_flash_brightness - update flash LED brightness
>> + * @flash: the flash LED to query
>> + *
>> + * Get a flash LED's current brightness and update led_flash->brightness
>> + * member with the obtained value.
>> + *
>> + * Returns: 0 on success or negative error value on failure
>> + */
>> +extern int led_update_flash_brightness(struct led_classdev_flash *flash);
>> +
>> +/**
>> + * led_set_flash_timeout - set flash LED timeout
>> + * @flash: the flash LED to set
>> + * @timeout: the flash timeout to set it to
>> + *
>> + * Set the flash strobe duration. The duration set by the driver
>> + * is returned in the timeout argument and may differ from the
>> + * one that was originally passed.
>> + *
>> + * Returns: 0 on success or negative error value on failure
>> + */
>> +extern int led_set_flash_timeout(struct led_classdev_flash *flash,
>> + u32 timeout);
>> +
>> +/**
>> + * led_get_flash_fault - get the flash LED fault
>> + * @flash: the flash LED to query
>> + * @fault: bitmask containing flash faults
>> + *
>> + * Get the flash LED fault.
>> + *
>> + * Returns: 0 on success or negative error value on failure
>> + */
>> +extern int led_get_flash_fault(struct led_classdev_flash *flash,
>> + u32 *fault);
>> +
>> +#endif /* __LINUX_FLASH_LEDS_H_INCLUDED */
>> diff --git a/include/linux/leds.h b/include/linux/leds.h
>> index cfceef3..c359f35 100644
>> --- a/include/linux/leds.h
>> +++ b/include/linux/leds.h
>> @@ -46,6 +46,8 @@ struct led_classdev {
>> #define LED_SYSFS_DISABLE (1 << 20)
>> #define SET_BRIGHTNESS_ASYNC (1 << 21)
>> #define SET_BRIGHTNESS_SYNC (1 << 22)
>> +#define LED_DEV_CAP_FLASH (1 << 23)
>> +#define LED_DEV_CAP_COMPOUND (1 << 24)
>>
>> /* Set LED brightness level */
>> /* Must not sleep, use a workqueue if needed */
>> @@ -81,6 +83,7 @@ struct led_classdev {
>> unsigned long blink_delay_on, blink_delay_off;
>> struct timer_list blink_timer;
>> int blink_brightness;
>> + void (*flash_resume)(struct led_classdev *led_cdev);
>>
>> struct work_struct set_brightness_work;
>> int delayed_set_value;
>

Best Regards,
Jacek Anaszewski

2014-12-04 09:39:24

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 05/19] leds: Add support for max77693 mfd flash cell

Hi Jacek,

On Wed, Dec 03, 2014 at 05:06:40PM +0100, Jacek Anaszewski wrote:
> This patch adds led-flash support to Maxim max77693 chipset.
> A device can be exposed to user space through LED subsystem
> sysfs interface. Device supports up to two leds which can
> work in flash and torch mode. The leds can be triggered
> externally or by software.
>
> Signed-off-by: Jacek Anaszewski <[email protected]>
> Signed-off-by: Andrzej Hajda <[email protected]>
> Acked-by: Kyungmin Park <[email protected]>
> Cc: Bryan Wu <[email protected]>
> Cc: Richard Purdie <[email protected]>
> Cc: Lee Jones <[email protected]>
> Cc: Chanwoo Choi <[email protected]>
> ---
> drivers/leds/Kconfig | 10 +
> drivers/leds/Makefile | 1 +
> drivers/leds/leds-max77693.c | 1023 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 1034 insertions(+)
> create mode 100644 drivers/leds/leds-max77693.c
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index fa8021e..2e66d55 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -463,6 +463,16 @@ config LEDS_TCA6507
> LED driver chips accessed via the I2C bus.
> Driver support brightness control and hardware-assisted blinking.
>
> +config LEDS_MAX77693
> + tristate "LED support for MAX77693 Flash"
> + depends on LEDS_CLASS_FLASH
> + depends on MFD_MAX77693
> + depends on OF
> + help
> + This option enables support for the flash part of the MAX77693
> + multifunction device. It has build in control for two leds in flash
> + and torch mode.
> +
> config LEDS_MAX8997
> tristate "LED support for MAX8997 PMIC"
> depends on LEDS_CLASS && MFD_MAX8997
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index cbba921..57ca62b 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -52,6 +52,7 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
> obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
> obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
> obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
> +obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
> obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
> obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
> obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
> diff --git a/drivers/leds/leds-max77693.c b/drivers/leds/leds-max77693.c
> new file mode 100644
> index 0000000..67a2f8f
> --- /dev/null
> +++ b/drivers/leds/leds-max77693.c
> @@ -0,0 +1,1023 @@
> +/*
> + * LED Flash class driver for the flash cell of max77693 mfd.
> + *
> + * Copyright (C) 2014, Samsung Electronics Co., Ltd.
> + *
> + * Authors: Jacek Anaszewski <[email protected]>
> + * Andrzej Hajda <[email protected]>
> + *
> + * 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 <asm/div64.h>
> +#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>
> +
> +#define MODE_OFF 0
> +#define MODE_FLASH1 (1 << 0)
> +#define MODE_FLASH2 (1 << 1)
> +#define MODE_TORCH1 (1 << 2)
> +#define MODE_TORCH2 (1 << 3)
> +#define MODE_FLASH_EXTERNAL1 (1 << 4)
> +#define MODE_FLASH_EXTERNAL2 (1 << 5)

You could do this based on an argument (led number). E.g.

#define MODE_FLASH_EXTERNAL(a) (1 << (4 + a))

> +#define MODE_FLASH (MODE_FLASH1 | MODE_FLASH2 | \
> + MODE_FLASH_EXTERNAL1 | MODE_FLASH_EXTERNAL2)
> +
> +#define FLED1_IOUT (1 << 0)
> +#define FLED2_IOUT (1 << 1)
> +
> +enum {
> + FLED1,
> + FLED2
> +};
> +
> +enum {
> + FLASH,
> + TORCH
> +};
> +
> +struct max77693_sub_led {
> + struct led_classdev_flash ldev;
> + struct work_struct work_brightness_set;
> +
> + unsigned int torch_brightness;
> + unsigned int flash_timeout;
> +};
> +
> +struct max77693_led {

As this does not refer to a device, how about struct max77693_device, for
instance?

> + struct regmap *regmap;
> + struct platform_device *pdev;
> + struct max77693_led_platform_data *pdata;
> + struct mutex lock;
> +
> + struct max77693_sub_led sub_leds[2];
> +
> + unsigned int current_flash_timeout;
> + unsigned int mode_flags;
> + u8 torch_iout_reg;
> + bool iout_joint;
> + int strobing_sub_led_id;
> +};
> +
> +struct max77693_led_settings {
> + struct led_flash_setting torch_brightness;
> + struct led_flash_setting flash_brightness;
> + struct led_flash_setting flash_timeout;
> +};
> +
> +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_led *ldev1_to_led(
> + struct led_classdev_flash *ldev)
> +{
> + struct max77693_sub_led *sub_led = container_of(ldev,
> + struct max77693_sub_led,
> + ldev);
> + return container_of(sub_led, struct max77693_led, sub_leds[0]);

You could have a common macro to find the flash controller struct if you add
the LED number to struct max77693_sub_led.

> +}
> +
> +static inline struct max77693_led *ldev2_to_led(
> + struct led_classdev_flash *ldev)
> +{
> + struct max77693_sub_led *sub_led = container_of(ldev,
> + struct max77693_sub_led,
> + ldev);
> + return container_of(sub_led, struct max77693_led, sub_leds[1]);
> +}
> +
> +static u8 max77693_led_vsys_to_reg(u32 mv)
> +{
> + return ((mv - MAX_FLASH1_VSYS_MIN) / MAX_FLASH1_VSYS_STEP) << 2;
> +}
> +
> +static u8 max77693_led_vout_to_reg(u32 mv)
> +{
> + return (mv - FLASH_VOUT_MIN) / FLASH_VOUT_STEP + FLASH_VOUT_RMIN;
> +}
> +
> +/* split composite current @i into two @iout according to @imax weights */

What do you intend to do in the oint iout mode? A single LED connected to
iout pins which are soldered together?

> +static void max77693_calc_iout(u32 iout[2], u32 i, u32 imax[2])
> +{
> + u64 t = i;
> +
> + t *= imax[1];
> + do_div(t, imax[0] + imax[1]);
> +
> + iout[1] = (u32)t / FLASH_IOUT_STEP * FLASH_IOUT_STEP;
> + iout[0] = i - iout[1];
> +}
> +
> +static int max77693_set_mode(struct max77693_led *led, unsigned int mode)
> +{
> + struct max77693_led_platform_data *p = led->pdata;
> + struct regmap *rmap = led->regmap;
> + int ret, v = 0;
> +
> + if (mode & MODE_TORCH1) {
> + if (p->trigger[FLED1] & MAX77693_LED_TRIG_SOFT)
> + v |= FLASH_EN_ON << TORCH_EN_SHIFT(1);
> + }
> +
> + if (mode & MODE_TORCH2) {
> + if (p->trigger[FLED2] & MAX77693_LED_TRIG_SOFT)
> + v |= FLASH_EN_ON << TORCH_EN_SHIFT(2);
> + }

The above could be turned to a loop from 0 to 1, as you have two LEDs.Just
the MODE_* macro misses the argument.

> +
> + if (mode & MODE_FLASH1) {
> + if (p->trigger[FLED1] & MAX77693_LED_TRIG_SOFT)
> + v |= FLASH_EN_ON << FLASH_EN_SHIFT(1);
> + } else if (mode & MODE_FLASH_EXTERNAL1) {
> + if (p->trigger[FLED1] & MAX77693_LED_TRIG_EXT)
> + v |= FLASH_EN_FLASH << FLASH_EN_SHIFT(2);
> + /*
> + * Enable hw triggering also for torch mode, as some camera
> + * sensors use torch led to fathom ambient light conditions
> + * before strobing the flash.
> + */
> + if (p->trigger[FLED1] & MAX77693_LED_TRIG_EXT)
> + v |= FLASH_EN_TORCH << TORCH_EN_SHIFT(1);
> + }
> +
> + if (mode & MODE_FLASH2) {
> + if (p->trigger[FLED2] & MAX77693_LED_TRIG_SOFT)
> + v |= FLASH_EN_ON << FLASH_EN_SHIFT(2);
> + } else if (mode & MODE_FLASH_EXTERNAL2) {
> + if (p->trigger[FLED2] & MAX77693_LED_TRIG_EXT)
> + v |= FLASH_EN_FLASH << FLASH_EN_SHIFT(2);
> + /*
> + * Enable hw triggering also for torch mode, as some camera
> + * sensors use torch led to fathom ambient light conditions
> + * before strobing the flash.
> + */
> + if (p->trigger[FLED2] & MAX77693_LED_TRIG_EXT)
> + v |= FLASH_EN_TORCH << TORCH_EN_SHIFT(2);
> + }

Same here.

> + /* Reset the register only prior setting flash modes */
> + if (mode & ~(MODE_TORCH1 | MODE_TORCH2)) {
> + 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 void max77693_set_sync_strobe(struct max77693_led *led,
> + unsigned int *mode)
> +{
> + struct max77693_sub_led *sub_leds = led->sub_leds;
> + struct led_classdev_flash *flash;
> + unsigned int m = *mode;
> +
> + /*
> + * If there are two leds then check if the other one
> + * wants to be strobed simultaneously.
> + */
> + if (!led->iout_joint) {

if (led->iout_joint)
return;

And you can more the rest left by one tab.

> + if (m & (MODE_FLASH1 | MODE_FLASH_EXTERNAL1)) {

I think this matters only software strobe. The hardware strobe works this
way automatically --- or I'd guess so at least.

> + flash = &sub_leds[FLED2].ldev;
> + if (flash->sync_strobe)
> + m |= m << 1;
> + } else if (m & (MODE_FLASH2 | MODE_FLASH_EXTERNAL2)) {
> + flash = &sub_leds[FLED1].ldev;
> + if (flash->sync_strobe)
> + m |= m >> 1;
> + }
> + }
> +
> + *mode = m;
> +}
> +
> +static int max77693_add_mode(struct max77693_led *led, unsigned int mode)
> +{
> + int ret;
> +
> + /* Span the mode on FLED2 for joint iouts case */
> + if (led->iout_joint)
> + mode |= (mode << 1);
> +
> + /*
> + * Torch mode once enabled remains active until turned off,
> + * and thus no action is required in this case.
> + */
> + if ((mode & MODE_TORCH1) &&
> + (led->mode_flags & MODE_TORCH1))
> + return 0;
> + if ((mode & MODE_TORCH2) &&
> + (led->mode_flags & MODE_TORCH2))
> + return 0;
> +
> + /* Span a flash mode on the other led if it is to be synchronized */
> + max77693_set_sync_strobe(led, &mode);
> +
> + /*
> + * FLASH_EXTERNAL mode activates FLASHEN and TORCHEN pins
> + * in the device. The related register settings interfere
> + * with SW triggerred modes, thus clear them to ensure proper
> + * device configuration.
> + */
> + if (mode & MODE_FLASH_EXTERNAL1)
> + led->mode_flags &= (~MODE_TORCH1 & ~MODE_FLASH1);
> + if (mode & MODE_FLASH_EXTERNAL2)
> + led->mode_flags &= (~MODE_TORCH2 & ~MODE_FLASH2);
> +
> + led->mode_flags |= mode;
> +
> + ret = max77693_set_mode(led, led->mode_flags);
> + if (ret < 0)
> + return ret;
> +
> + /*
> + * Clear flash mode flag after setting the mode to avoid
> + * spurious flash strobing on every subsequent torch mode
> + * setting.
> + */
> + if (mode & MODE_FLASH)
> + led->mode_flags &= ~mode;
> +
> + return ret;
> +}
> +
> +static int max77693_clear_mode(struct max77693_led *led, unsigned int mode)
> +{
> + int ret;
> +
> + if (led->iout_joint)
> + /* Clear mode also on FLED2 for joint iouts case */
> + mode |= (mode << 1);
> + else
> + /*
> + * Clear a flash mode on the other led
> + * if it is to be synchronized.
> + */
> + max77693_set_sync_strobe(led, &mode);
> +
> + led->mode_flags &= ~mode;
> +
> + ret = max77693_set_mode(led, led->mode_flags);
> +
> + return ret;
> +}
> +
> +static int max77693_set_torch_current(struct max77693_led *led,
> + int led_id, u32 micro_amp)
> +{
> + struct max77693_led_platform_data *p = led->pdata;
> + struct regmap *rmap = led->regmap;
> + u32 iout[2], iout_max[2];
> + u8 iout1_reg = 0, iout2_reg = 0;
> +
> + iout_max[FLED1] = p->iout_torch[FLED1];
> + iout_max[FLED2] = p->iout_torch[FLED2];
> +
> + if (led_id == FLED1) {
> + /*
> + * Preclude splitting current to FLED2 if we
> + * are driving two separate leds.
> + */
> + if (!led->iout_joint)
> + iout_max[FLED2] = 0;
> + max77693_calc_iout(iout, micro_amp, iout_max);
> + } else if (led_id == FLED2) {
> + iout_max[FLED1] = 0;
> + max77693_calc_iout(iout, micro_amp, iout_max);
> + }
> +
> + if (led_id == FLED1 || led->iout_joint) {
> + iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
> + led->torch_iout_reg &= 0xf0;
> + }
> + if (led_id == FLED2 || led->iout_joint) {
> + iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
> + led->torch_iout_reg &= 0x0f;
> + }
> +
> + 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 *led,
> + int led_id,
> + u32 micro_amp)
> +{
> + struct max77693_led_platform_data *p = led->pdata;
> + struct regmap *rmap = led->regmap;
> + u32 iout[2], iout_max[2];
> + u8 iout1_reg, iout2_reg;
> + int ret = -EINVAL;
> +
> + iout_max[FLED1] = p->iout_flash[FLED1];
> + iout_max[FLED2] = p->iout_flash[FLED2];
> +
> + if (led_id == FLED1) {
> + /*
> + * Preclude splitting current to FLED2 if we
> + * are driving two separate leds.
> + */
> + if (!led->iout_joint)
> + iout_max[FLED2] = 0;
> + max77693_calc_iout(iout, micro_amp, iout_max);
> + } else if (led_id == FLED2) {
> + iout_max[FLED1] = 0;
> + max77693_calc_iout(iout, micro_amp, iout_max);
> + }
> +
> + if (led_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 (led_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 *led, u32 timeout)
> +{
> + struct max77693_led_platform_data *p = led->pdata;
> + struct regmap *rmap = led->regmap;
> + u8 v;
> + int ret;
> +
> + v = max77693_flash_timeout_to_reg(timeout);
> +
> + if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
> + v |= FLASH_TMR_LEVEL;
> +
> + ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
> + if (ret < 0)
> + return ret;
> +
> + led->current_flash_timeout = timeout;
> +
> + return 0;
> +}
> +
> +static int max77693_strobe_status_get(struct max77693_led *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_int_flag_get(struct max77693_led *led, unsigned int *v)
> +{
> + struct regmap *rmap = led->regmap;
> +
> + return regmap_read(rmap, MAX77693_LED_REG_FLASH_INT, v);
> +}
> +
> +static int max77693_setup(struct max77693_led *led)
> +{
> + struct max77693_led_platform_data *p = led->pdata;
> + 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] = p->iout_flash[FLED1] +
> + p->iout_flash[FLED2];
> + } else {
> + first_led = p->fleds[FLED1] ? FLED1 : FLED2;
> + last_led = p->num_leds == 2 ? FLED2 : first_led;
> + max_flash_curr[FLED1] = p->iout_flash[FLED1];
> + max_flash_curr[FLED2] = p->iout_flash[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;
> +
> + /* initially set FLED1 timeout */
> + ret = max77693_set_timeout(led, p->flash_timeout[FLED1]);
> + if (ret < 0)
> + return ret;
> +
> + if (p->low_vsys > 0)
> + v = max77693_led_vsys_to_reg(p->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 (p->boost_mode == MAX77693_LED_BOOST_FIXED)
> + v = FLASH_BOOST_FIXED;
> + else
> + v = p->boost_mode | p->boost_mode << 1;
> + if (p->fleds[FLED1] && p->fleds[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(p->boost_vout);
> + ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
> + if (ret < 0)
> + return ret;
> +
> + return max77693_set_mode(led, MODE_OFF);
> +}
> +
> +static int max77693_led_brightness_set(struct max77693_led *led,
> + int led_id, enum led_brightness value)
> +{
> + int ret;
> +
> + mutex_lock(&led->lock);
> +
> + if (value == 0) {
> + ret = max77693_clear_mode(led, MODE_TORCH1 << led_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, led_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_TORCH1 << led_id);
> + if (ret < 0)
> + dev_dbg(&led->pdev->dev,
> + "Failed to set torch mode (%d)\n",
> + ret);
> +unlock:
> + mutex_unlock(&led->lock);
> + return ret;
> +}
> +
> +#define MAX77693_LED_BRIGHTNESS_SET_WORK(ID) \
> +static void max77693_led##ID##_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 *led = container_of(sub_led, \
> + struct max77693_led, \
> + sub_leds[FLED##ID]); \
> + struct max77693_sub_led *sub_leds = led->sub_leds; \
> + \
> + max77693_led_brightness_set(led, FLED##ID, \
> + sub_leds[FLED##ID].torch_brightness); \
> +}
> +
> +/* LED subsystem callbacks */
> +
> +#define MAX77693_LED_TORCH_BRIGHTNESS_SET(ID) \
> +static int max77693_led##ID##_brightness_set_sync( \
> + struct led_classdev *led_cdev, \
> + enum led_brightness value) \
> +{ \
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev); \
> + struct max77693_led *led = ldev##ID##_to_led(flash); \
> + \
> + return max77693_led_brightness_set(led, FLED##ID, value); \
> +}
> +
> +#define MAX77693_LED_BRIGHTNESS_SET(ID) \
> +static void max77693_led##ID##_brightness_set( \
> + struct led_classdev *led_cdev, \
> + enum led_brightness value) \
> +{ \
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev); \
> + struct max77693_led *led = ldev##ID##_to_led(flash); \
> + struct max77693_sub_led *sub_leds = led->sub_leds; \
> + \
> + sub_leds[FLED##ID].torch_brightness = value; \
> + schedule_work(&sub_leds[FLED##ID].work_brightness_set); \
> +}
> +
> +#define MAX77693_LED_FLASH_BRIGHTNESS_SET(ID) \
> +static int max77693_led##ID##_flash_brightness_set( \
> + struct led_classdev_flash *flash, \
> + u32 brightness) \
> +{ \
> + struct max77693_led *led = ldev##ID##_to_led(flash); \
> + int ret; \
> + \
> + mutex_lock(&led->lock); \
> + ret = max77693_set_flash_current(led, FLED##ID, brightness); \
> + mutex_unlock(&led->lock); \
> + \
> + return ret; \
> +}
> +
> +#define MAX77693_LED_FLASH_STROBE_SET(ID) \
> +static int max77693_led##ID##_flash_strobe_set( \
> + struct led_classdev_flash *flash, \
> + bool state) \
> +{ \
> + struct max77693_led *led = ldev##ID##_to_led(flash); \
> + struct max77693_sub_led *sub_leds = led->sub_leds; \
> + int ret; \
> + \
> + mutex_lock(&led->lock); \
> + \
> + if (!state) { \
> + ret = max77693_clear_mode(led, MODE_FLASH##ID); \
> + goto unlock; \
> + } \
> + \
> + if (sub_leds[FLED##ID].flash_timeout != \
> + led->current_flash_timeout) { \
> + ret = max77693_set_timeout(led, \
> + sub_leds[FLED##ID].flash_timeout); \
> + if (ret < 0) \
> + goto unlock; \
> + } \
> + \
> + led->strobing_sub_led_id = ID; \
> + \
> + ret = max77693_add_mode(led, MODE_FLASH##ID); \
> + \
> +unlock: \
> + mutex_unlock(&led->lock); \
> + return ret; \
> +}
> +
> +#define MAX77693_LED_FLASH_FAULT_GET(ID) \
> +static int max77693_led##ID##_flash_fault_get( \
> + struct led_classdev_flash *flash, \
> + u32 *fault) \
> +{ \
> + struct max77693_led *led = ldev##ID##_to_led(flash); \
> + unsigned int v; \
> + int ret; \
> + \
> + ret = max77693_int_flag_get(led, &v); \
> + if (ret < 0) \
> + return ret; \
> + \
> + *fault = 0; \
> + \
> + if (v & FLASH_INT_FLED##ID##_OPEN) \
> + *fault |= LED_FAULT_OVER_VOLTAGE; \
> + if (v & FLASH_INT_FLED##ID##_SHORT) \
> + *fault |= LED_FAULT_SHORT_CIRCUIT; \
> + if (v & FLASH_INT_OVER_CURRENT) \
> + *fault |= LED_FAULT_OVER_CURRENT; \
> + \
> + return 0; \
> +}
> +
> +#define MAX77693_LED_FLASH_STROBE_GET(ID) \
> +static int max77693_led##ID##_flash_strobe_get( \
> + struct led_classdev_flash *flash, \
> + bool *state) \
> +{ \
> + struct max77693_led *led = ldev##ID##_to_led(flash); \
> + int ret; \
> + \
> + if (!state) \
> + return -EINVAL; \
> + \
> + mutex_lock(&led->lock); \
> + \
> + ret = max77693_strobe_status_get(led, state); \
> + \
> + *state = !!(*state && led->strobing_sub_led_id == ID); \
> + \
> + mutex_unlock(&led->lock); \
> + \
> + return ret; \
> +}
> +
> +#define MAX77693_LED_FLASH_TIMEOUT_SET(ID) \
> +static int max77693_led##ID##_flash_timeout_set( \
> + struct led_classdev_flash *flash, \
> + u32 timeout) \
> +{ \
> + struct max77693_led *led = ldev##ID##_to_led(flash); \
> + struct max77693_sub_led *sub_leds = led->sub_leds; \
> + \
> + mutex_lock(&led->lock); \
> + sub_leds[FLED##ID].flash_timeout = timeout; \
> + mutex_unlock(&led->lock); \
> + \
> + return 0; \
> +}
> +
> +MAX77693_LED_BRIGHTNESS_SET(1)
> +MAX77693_LED_BRIGHTNESS_SET_WORK(1)
> +MAX77693_LED_TORCH_BRIGHTNESS_SET(1)
> +MAX77693_LED_FLASH_BRIGHTNESS_SET(1)
> +MAX77693_LED_FLASH_STROBE_SET(1)
> +MAX77693_LED_FLASH_STROBE_GET(1)
> +MAX77693_LED_FLASH_TIMEOUT_SET(1)
> +MAX77693_LED_FLASH_FAULT_GET(1)
> +
> +MAX77693_LED_BRIGHTNESS_SET(2)
> +MAX77693_LED_BRIGHTNESS_SET_WORK(2)
> +MAX77693_LED_TORCH_BRIGHTNESS_SET(2)
> +MAX77693_LED_FLASH_BRIGHTNESS_SET(2)
> +MAX77693_LED_FLASH_STROBE_SET(2)
> +MAX77693_LED_FLASH_STROBE_GET(2)
> +MAX77693_LED_FLASH_TIMEOUT_SET(2)
> +MAX77693_LED_FLASH_FAULT_GET(2)

You could implement these without the help of macros so that the function
gets the LED number from its arguments, directly or indirectly.

> +static int max77693_led_parse_dt(struct max77693_led *led,
> + struct device_node *node)
> +{
> + struct max77693_led_platform_data *p = led->pdata;
> + struct device *dev = &led->pdev->dev;
> + struct device_node *child_node;
> + u32 fled_id;
> + int ret;
> +
> + of_property_read_u32_array(node, "maxim,fleds", p->fleds, 2);
> + of_property_read_u32_array(node, "maxim,trigger", p->trigger, 2);
> + of_property_read_u32_array(node, "maxim,trigger-type", p->trigger_type,
> + 2);
> + of_property_read_u32(node, "maxim,boost-mode", &p->boost_mode);
> + of_property_read_u32(node, "maxim,boost-vout", &p->boost_vout);
> + of_property_read_u32(node, "maxim,vsys-min", &p->low_vsys);
> +
> + for_each_available_child_of_node(node, child_node) {
> + ret = of_property_read_u32(child_node, "maxim,fled_id",
> + &fled_id);
> + if (ret < 0) {
> + dev_err(dev, "Error reading \"fled_id\" DT property\n");
> + return ret;
> + }
> +
> + fled_id = clamp_val(fled_id, 1, 2);

I think you should check fled_id is really correct, and not clamp it.

> + --fled_id;
> +
> + p->sub_nodes[fled_id] = child_node;

p->sub_nodes is not accessed anywhere else. Do you plan to use it for
something?

> +
> + ret = of_property_read_string(child_node, "label",
> + (const char **) &p->label[fled_id]);
> + if (ret < 0) {
> + dev_err(dev, "Error reading \"label\" DT property\n");
> + return ret;
> + }
> +
> + of_property_read_u32(child_node, "max-microamp",
> + &p->iout_torch[fled_id]);
> + of_property_read_u32(child_node, "flash-max-microamp",
> + &p->iout_flash[fled_id]);
> + of_property_read_u32(child_node, "flash-timeout-microsec",
> + &p->flash_timeout[fled_id]);
> + if (++p->num_leds == 2)
> + break;
> + }
> +
> + 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_led_validate_platform_data(
> + struct max77693_led_platform_data *p)
> +{
> + u32 max;
> + int i;
> +
> + p->boost_mode = clamp_val(p->boost_mode, MAX77693_LED_BOOST_NONE,
> + MAX77693_LED_BOOST_FIXED);
> +
> + for (i = 0; i < ARRAY_SIZE(p->fleds); ++i)
> + p->fleds[i] = clamp_val(p->fleds[i], 0, 1);
> +
> + /* Ensure fleds configuration is sane */
> + if (!p->fleds[FLED1] && !p->fleds[FLED2]) {
> + p->fleds[FLED1] = p->fleds[FLED2] = 1;
> + p->num_leds = 1;
> + }
> +
> + /* Ensure num_leds is consistent with fleds configuration */
> + if ((!p->fleds[FLED1] || !p->fleds[FLED2]) && p->num_leds == 2)
> + p->num_leds = 1;
> +
> + /*
> + * boost must be enabled if current outputs
> + * are connected to separate leds.
> + */
> + if ((p->num_leds == 2 || (p->fleds[FLED1] && p->fleds[FLED2])) &&
> + p->boost_mode == MAX77693_LED_BOOST_NONE)
> + p->boost_mode = MAX77693_LED_BOOST_FIXED;
> +
> + max = p->boost_mode ? FLASH_IOUT_MAX_2LEDS : FLASH_IOUT_MAX_1LED;
> +
> + if (p->fleds[FLED1]) {
> + clamp_align(&p->iout_torch[FLED1], TORCH_IOUT_MIN,
> + TORCH_IOUT_MAX, TORCH_IOUT_STEP);
> + clamp_align(&p->iout_flash[FLED1], FLASH_IOUT_MIN, max,
> + FLASH_IOUT_STEP);
> + } else {
> + p->iout_torch[FLED1] = p->iout_flash[FLED1] = 0;
> + }
> + if (p->fleds[FLED2]) {
> + clamp_align(&p->iout_torch[FLED2], TORCH_IOUT_MIN,
> + TORCH_IOUT_MAX, TORCH_IOUT_STEP);
> + clamp_align(&p->iout_flash[FLED2], FLASH_IOUT_MIN, max,
> + FLASH_IOUT_STEP);
> + } else {
> + p->iout_torch[FLED2] = p->iout_flash[FLED2] = 0;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(p->trigger); ++i)
> + p->trigger[i] = clamp_val(p->trigger[i], 0, 7);
> + for (i = 0; i < ARRAY_SIZE(p->trigger_type); ++i)
> + p->trigger_type[i] = clamp_val(p->trigger_type[i],
> + MAX77693_LED_TRIG_TYPE_EDGE,
> + MAX77693_LED_TRIG_TYPE_LEVEL);
> +
> + for (i = 0; i < ARRAY_SIZE(p->flash_timeout); ++i)
> + clamp_align(&p->flash_timeout[i], FLASH_TIMEOUT_MIN,
> + FLASH_TIMEOUT_MAX, FLASH_TIMEOUT_STEP);
> +
> + clamp_align(&p->boost_vout, FLASH_VOUT_MIN, FLASH_VOUT_MAX,
> + FLASH_VOUT_STEP);
> +
> + if (p->low_vsys)
> + clamp_align(&p->low_vsys, MAX_FLASH1_VSYS_MIN,
> + MAX_FLASH1_VSYS_MAX, MAX_FLASH1_VSYS_STEP);
> +}
> +
> +static int max77693_led_get_platform_data(struct max77693_led *led)

This isn't really platform data anymore. Could this be just "configuration",
for instance?

> +{
> + struct device *dev = &led->pdev->dev;
> + int ret;
> +
> + if (!dev->of_node)
> + return -EINVAL;
> +
> + led->pdata = devm_kzalloc(dev, sizeof(*led->pdata), GFP_KERNEL);
> + if (!led->pdata)
> + return -ENOMEM;
> +
> + ret = max77693_led_parse_dt(led, dev->of_node);
> + if (ret < 0)
> + return ret;
> +
> + max77693_led_validate_platform_data(led->pdata);
> +
> + return 0;
> +}
> +
> +#define MAX77693_LED_INIT_FLASH_OPS(ID) \
> +static const struct led_flash_ops flash_ops##ID = { \
> + \
> + .flash_brightness_set = max77693_led##ID##_flash_brightness_set, \
> + .strobe_set = max77693_led##ID##_flash_strobe_set, \
> + .strobe_get = max77693_led##ID##_flash_strobe_get, \
> + .timeout_set = max77693_led##ID##_flash_timeout_set, \
> + .fault_get = max77693_led##ID##_flash_fault_get, \
> +}
> +
> +MAX77693_LED_INIT_FLASH_OPS(1);
> +MAX77693_LED_INIT_FLASH_OPS(2);

Same here, I think you can have just a single implementation of these.

> +static void max77693_init_flash_settings(struct max77693_led *led,
> + struct max77693_led_settings *s,
> + int led_id)
> +{
> + struct max77693_led_platform_data *p = led->pdata;
> + struct led_flash_setting *setting;
> +
> + /* Init torch intensity setting */
> + setting = &s->torch_brightness;
> + setting->min = led->iout_joint ? TORCH_IOUT_MIN * 2 :
> + TORCH_IOUT_MIN;
> + setting->max = led->iout_joint ?
> + p->iout_torch[FLED1] + p->iout_torch[FLED2] :
> + p->iout_torch[led_id];
> + setting->step = TORCH_IOUT_STEP;
> + setting->val = setting->max;
> +
> + /* Init flash intensity setting */
> + setting = &s->flash_brightness;
> + setting->min = led->iout_joint ? FLASH_IOUT_MIN * 2 :
> + FLASH_IOUT_MIN;
> + setting->max = led->iout_joint ?
> + p->iout_flash[FLED1] + p->iout_flash[FLED2] :
> + p->iout_flash[led_id];
> + setting->step = FLASH_IOUT_STEP;
> + setting->val = setting->max;
> +
> + /* Init flash timeout setting */
> + setting = &s->flash_timeout;
> + setting->min = FLASH_TIMEOUT_MIN;
> + setting->max = p->flash_timeout[led_id];
> + setting->step = FLASH_TIMEOUT_STEP;
> + setting->val = setting->max;
> +}
> +
> +static int max77693_register_led(struct max77693_led *led, int id)
> +{
> + struct platform_device *pdev = led->pdev;
> + struct led_classdev_flash *flash;
> + struct led_classdev *led_cdev;
> + struct max77693_sub_led *sub_leds = led->sub_leds;
> + struct max77693_led_settings settings;
> +
> + flash = &sub_leds[id].ldev;
> +
> + /* Initialize flash settings */
> + max77693_init_flash_settings(led, &settings, id);
> +
> + /* Initialize LED Flash class device */
> + led_cdev = &flash->led_cdev;
> +
> + led_cdev->name = led->pdata->label[id];
> +
> + if (id == FLED1) {
> + led_cdev->brightness_set = max77693_led1_brightness_set;
> + led_cdev->brightness_set_sync =
> + max77693_led1_brightness_set_sync;
> + INIT_WORK(&sub_leds[id].work_brightness_set,
> + max77693_led1_brightness_set_work);
> + flash->ops = &flash_ops1;
> + } else {
> + led_cdev->brightness_set = max77693_led2_brightness_set;
> + led_cdev->brightness_set_sync =
> + max77693_led2_brightness_set_sync;
> + INIT_WORK(&sub_leds[id].work_brightness_set,
> + max77693_led2_brightness_set_work);
> + flash->ops = &flash_ops2;
> + }
> +
> + led_cdev->max_brightness = settings.torch_brightness.val /
> + TORCH_IOUT_STEP;
> + led_cdev->flags |= LED_DEV_CAP_FLASH;
> + if (led->pdata->num_leds == 2)
> + led_cdev->flags |= LED_DEV_CAP_COMPOUND;
> +
> + flash->brightness = settings.flash_brightness;
> + flash->timeout = settings.flash_timeout;
> + sub_leds[id].flash_timeout = flash->timeout.val;
> +
> + /* Register in the LED subsystem. */
> + return led_classdev_flash_register(&pdev->dev, flash);
> +}
> +
> +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 *led;
> + struct max77693_led_platform_data *p;
> + struct max77693_sub_led *sub_leds;
> + int ret;
> +
> + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
> + if (!led)
> + return -ENOMEM;
> +
> + led->pdev = pdev;
> + led->regmap = iodev->regmap;
> + sub_leds = led->sub_leds;
> + platform_set_drvdata(pdev, led);
> + ret = max77693_led_get_platform_data(led);
> + if (ret < 0)
> + return -EINVAL;
> +
> + p = led->pdata;
> + mutex_init(&led->lock);
> +
> + if (p->num_leds == 1 && p->fleds[FLED1] && p->fleds[FLED2])
> + led->iout_joint = true;
> +
> + ret = max77693_setup(led);
> + if (ret < 0)
> + goto err_setup;
> +
> + if (led->iout_joint || p->fleds[FLED1]) {
> + ret = max77693_register_led(led, FLED1);
> + if (ret < 0)
> + goto err_setup;
> + }
> +
> + if (!led->iout_joint && p->fleds[FLED2]) {
> + ret = max77693_register_led(led, FLED2);
> + if (ret < 0)
> + goto err_register_led2;
> + }
> +
> + return 0;
> +
> +err_register_led2:
> + if (!p->fleds[FLED1])
> + goto err_setup;
> + led_classdev_flash_unregister(&sub_leds[FLED1].ldev);
> +err_setup:
> + mutex_destroy(&led->lock);
> +
> + return ret;
> +}
> +
> +static int max77693_led_remove(struct platform_device *pdev)
> +{
> + struct max77693_led *led = platform_get_drvdata(pdev);
> + struct max77693_led_platform_data *p = led->pdata;
> + struct max77693_sub_led *sub_leds = led->sub_leds;
> +
> + if (led->iout_joint || p->fleds[FLED1]) {
> + led_classdev_flash_unregister(&sub_leds[FLED1].ldev);
> + cancel_work_sync(&sub_leds[FLED1].work_brightness_set);
> + }
> +
> + if (!led->iout_joint && p->fleds[FLED2]) {
> + led_classdev_flash_unregister(&sub_leds[FLED2].ldev);
> + cancel_work_sync(&sub_leds[FLED2].work_brightness_set);
> + }
> +
> + mutex_destroy(&led->lock);
> +
> + return 0;
> +}
> +
> +static 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",
> + .owner = THIS_MODULE,
> + .of_match_table = max77693_led_dt_match,
> + },
> +};
> +
> +module_platform_driver(max77693_led_driver);
> +
> +MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
> +MODULE_AUTHOR("Andrzej Hajda <[email protected]>");
> +MODULE_DESCRIPTION("Maxim MAX77693 led flash driver");
> +MODULE_LICENSE("GPL");

--
Kind regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-04 09:43:04

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 02/19] Documentation: leds: Add description of LED Flash class extension

Hi Sakari,

Thanks for the review.

On 12/03/2014 06:08 PM, Sakari Ailus wrote:
> Hi Jacek,
>
> On Wed, Dec 03, 2014 at 05:06:37PM +0100, Jacek Anaszewski wrote:
>> The documentation being added contains overall description of the
>> LED Flash Class and the related sysfs attributes.
>>
>> Signed-off-by: Jacek Anaszewski <[email protected]>
>> Acked-by: Kyungmin Park <[email protected]>
>> Cc: Bryan Wu <[email protected]>
>> Cc: Richard Purdie <[email protected]>
>> ---
>> Documentation/leds/leds-class-flash.txt | 50 +++++++++++++++++++++++++++++++
>> 1 file changed, 50 insertions(+)
>> create mode 100644 Documentation/leds/leds-class-flash.txt
>>
>> diff --git a/Documentation/leds/leds-class-flash.txt b/Documentation/leds/leds-class-flash.txt
>> new file mode 100644
>> index 0000000..82e58b1
>> --- /dev/null
>> +++ b/Documentation/leds/leds-class-flash.txt
>> @@ -0,0 +1,50 @@
>> +
>> +Flash LED handling under Linux
>> +==============================
>> +
>> +Some LED devices support two modes - torch and flash. The modes are
>> +supported by the LED class (see Documentation/leds/leds-class.txt)
>> +and LED Flash class respectively.
>> +
>> +In order to enable support for flash LEDs CONFIG_LEDS_CLASS_FLASH symbol
>> +must be defined in the kernel config. A flash LED driver must register
>> +in the LED subsystem with led_classdev_flash_register to gain flash
>> +capabilities.
>> +
>> +Following sysfs attributes are exposed for controlling flash led devices:
>> +
>> + - flash_brightness - flash LED brightness in microamperes (RW)
>> + - max_flash_brightness - maximum available flash LED brightness (RO)
>> + - flash_timeout - flash strobe duration in microseconds (RW)
>> + - max_flash_timeout - maximum available flash strobe duration (RO)
>> + - flash_strobe - flash strobe state (RW)
>> + - flash_sync_strobe - one flash device can control more than one
>> + sub-led; when this atrribute is set to 1
>
> s/atrribute/attribute/
>
>> + the flash led will be strobed synchronously
>> + with the other one controlled by the same
>> + device; flash timeout setting is inherited
>> + from the led being strobed explicitly and
>> + flash brightness setting of a sub-led's
>> + being synchronized is used (RW)
>
> The flash brightness shouldn't be determined by the strobed LED. If this is
> a property of the hardware, then be it, but in general no, it it shouldn't
> be an interface requirement. I think this should just say that the strobe is
> synchronised.

I intended this to sound exactly as you laid it out above, but maybe it
is obscure English. "and flash brightness setting of a sub-led >>>being
synchronized<<< is used" - from my point of view the led being
synchronized is the one that isn't strobed explicitly. But I'm ok with
confining ourselves only to saying that strobe is synchronized.

> How does the user btw. figure out which flash LEDs may be strobed
> synchronously using the LED flash interface?

The flash_sync_strobe argument is absent if synchronized strobe
is not available for a LED. The driver defines this by setting
newly added LED_DEV_CAP_COMPOUND flag.

Best Regards,
Jacek Anaszewski

2014-12-04 10:07:16

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 06/19] DT: Add documentation for the mfd Maxim max77693

Hi Jacek,

On Wed, Dec 03, 2014 at 05:06:41PM +0100, Jacek Anaszewski wrote:
> This patch adds device tree binding documentation for
> the flash cell of the Maxim max77693 multifunctional device.
>
> Signed-off-by: Jacek Anaszewski <[email protected]>
> Signed-off-by: Andrzej Hajda <[email protected]>
> Acked-by: Kyungmin Park <[email protected]>
> Cc: Lee Jones <[email protected]>
> Cc: Chanwoo Choi <[email protected]>
> Cc: Bryan Wu <[email protected]>
> Cc: Richard Purdie <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Pawel Moll <[email protected]>
> Cc: Mark Rutland <[email protected]>
> Cc: Ian Campbell <[email protected]>
> Cc: Kumar Gala <[email protected]>
> Cc: <[email protected]>
> ---
> Documentation/devicetree/bindings/mfd/max77693.txt | 89 ++++++++++++++++++++
> 1 file changed, 89 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/mfd/max77693.txt b/Documentation/devicetree/bindings/mfd/max77693.txt
> index 01e9f30..25a6e78 100644
> --- a/Documentation/devicetree/bindings/mfd/max77693.txt
> +++ b/Documentation/devicetree/bindings/mfd/max77693.txt
> @@ -41,7 +41,66 @@ Optional properties:
> To get more informations, please refer to documentaion.
> [*] refer Documentation/devicetree/bindings/pwm/pwm.txt
>
> +- led : the LED submodule device node
> +
> +There are two led outputs available - fled1 and fled2. Each of them can
> +control a separate led or they can be connected together to double
> +the maximum current for a single connected led. One led is represented
> +by one child node.
> +
> +Required properties:
> +- compatible : Must be "maxim,max77693-led".
> +
> +Optional properties:
> +- maxim,fleds : Array of current outputs in order: fled1, fled2.
> + Note: both current outputs can be connected to a single led
> + Possible values:
> + MAX77693_LED_FLED_UNUSED - the output is left disconnected,
> + MAX77693_LED_FLED_USED - a diode is connected to the output.

As you have a LED sub-nodes for each LED already, isn't this redundant?

> +- maxim,trigger-type : Array of trigger types in order: flash, torch.
> + Possible trigger types:
> + MAX77693_LED_TRIG_TYPE_EDGE - Rising edge of the signal triggers
> + the flash/torch,
> + MAX77693_LED_TRIG_TYPE_LEVEL - Signal level controls duration of

How about: "Strobe pulse length ..."?

How long does the torch stay on if you use edge trigger for it? I've always
thought the torch enable pin was a practical joke. :-)

If you need it this for torch as well, I'd use separate properties for the
purpose, i.e. trigger-type-flash and trigger-type-torch.

> + the flash/torch.
> +- maxim,trigger : Array of flags indicating which trigger can activate given led
> + in order: fled1, fled2.
> + Possible flag values (can be combined):
> + MAX77693_LED_TRIG_FLASHEN - FLASHEN pin of the chip,
> + MAX77693_LED_TRIG_TORCHEN - TORCHEN pin of the chip,
> + MAX77693_LED_TRIG_SOFTWARE - software via I2C command.

Is there a need to prevent strobing using a certain method? Just wondering.

> +- maxim,boost-mode :
> + In boost mode the device can produce up to 1.2A of total current
> + on both outputs. The maximum current on each output is reduced
> + to 625mA then. If there are two child led nodes defined then boost
> + is enabled by default.
> + Possible values:
> + MAX77693_LED_BOOST_OFF - no boost,
> + MAX77693_LED_BOOST_ADAPTIVE - adaptive mode,
> + MAX77693_LED_BOOST_FIXED - fixed mode.
> +- maxim,boost-vout : Output voltage of the boost module in millivolts.
> +- maxim,vsys-min : Low input voltage level in millivolts. Flash is not fired
> + if chip estimates that system voltage could drop below this level due
> + to flash power consumption.
> +
> +Required properties of the LED child node:
> +- label : see Documentation/devicetree/bindings/leds/common.txt
> +- maxim,fled_id : Identifier of the fled output the led is connected to;

I'm pretty sure this will be needed for about every chip that can drive
multiple LEDs. Shouldn't it be documented in the generic documentation?

> + MAX77693_LED_FLED1 - FLED1 output of the device - it has to be
> + used also if a single LED is connected to both outputs,
> + MAX77693_LED_FLED2 - FLED2 output of the device.
> +
> +Optional properties of the LED child node:
> +- max-microamp : see Documentation/devicetree/bindings/leds/common.txt
> + Range: 15625 - 250000
> +- flash-max-microamp : see Documentation/devicetree/bindings/leds/common.txt
> + Range: 15625 - 1000000
> +- flash-timeout-microsec : see Documentation/devicetree/bindings/leds/common.txt
> + Range: 62500 - 1000000
> +
> Example:
> +#include <dt-bindings/mfd/max77693.h>
> +
> max77693@66 {
> compatible = "maxim,max77693";
> reg = <0x66>;
> @@ -73,4 +132,34 @@ Example:
> pwms = <&pwm 0 40000 0>;
> pwm-names = "haptic";
> };
> +
> + led {
> + compatible = "maxim,max77693-led";
> + maxim,fleds = <MAX77693_LED_FLED_USED
> + MAX77693_LED_FLED_USED>;
> + maxim,trigger = <MAX77693_LED_TRIG_ALL
> + (MAX77693_LED_TRIG_TORCHEN |
> + MAX77693_LED_TRIG_SOFTWARE)>;
> + maxim,trigger-type = <MAX77693_LED_TRIG_TYPE_EDGE
> + MAX77693_LED_TRIG_TYPE_LEVEL>;
> + maxim,boost-mode = <MAX77693_LED_BOOST_ADAPTIVE>;
> + maxim,boost-vout = <5000>;
> + maxim,vsys-min = <2400>;
> +
> + camera1_flash: led1 {
> + maxim,fled_id = <MAX77693_LED_FLED1>;
> + label = "max77693-flash1";
> + max-microamp = <250000>;
> + flash-max-microamp = <625000>;
> + flash-timeout-microsec = <1000000>;
> + };
> +
> + camera2_flash: led2 {
> + maxim,fled_id = <MAX77693_LED_FLED2>;
> + label = "max77693-flash2";
> + max-microamp = <250000>;
> + flash-max-microamp = <500000>;
> + flash-timeout-microsec = <1000000>;
> + };
> + };

I like how this looks like in general.

> };

--
Kind regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-04 11:07:09

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 05/19] leds: Add support for max77693 mfd flash cell

Hi Sakari,

Thanks for the review.

On 12/04/2014 10:39 AM, Sakari Ailus wrote:
> Hi Jacek,
>
> On Wed, Dec 03, 2014 at 05:06:40PM +0100, Jacek Anaszewski wrote:
>> This patch adds led-flash support to Maxim max77693 chipset.
>> A device can be exposed to user space through LED subsystem
>> sysfs interface. Device supports up to two leds which can
>> work in flash and torch mode. The leds can be triggered
>> externally or by software.
>>
>> Signed-off-by: Jacek Anaszewski <[email protected]>
>> Signed-off-by: Andrzej Hajda <[email protected]>
>> Acked-by: Kyungmin Park <[email protected]>
>> Cc: Bryan Wu <[email protected]>
>> Cc: Richard Purdie <[email protected]>
>> Cc: Lee Jones <[email protected]>
>> Cc: Chanwoo Choi <[email protected]>
>> ---
>> drivers/leds/Kconfig | 10 +
>> drivers/leds/Makefile | 1 +
>> drivers/leds/leds-max77693.c | 1023 ++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 1034 insertions(+)
>> create mode 100644 drivers/leds/leds-max77693.c
>>
>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>> index fa8021e..2e66d55 100644
>> --- a/drivers/leds/Kconfig
>> +++ b/drivers/leds/Kconfig
>> @@ -463,6 +463,16 @@ config LEDS_TCA6507
>> LED driver chips accessed via the I2C bus.
>> Driver support brightness control and hardware-assisted blinking.
>>
>> +config LEDS_MAX77693
>> + tristate "LED support for MAX77693 Flash"
>> + depends on LEDS_CLASS_FLASH
>> + depends on MFD_MAX77693
>> + depends on OF
>> + help
>> + This option enables support for the flash part of the MAX77693
>> + multifunction device. It has build in control for two leds in flash
>> + and torch mode.
>> +
>> config LEDS_MAX8997
>> tristate "LED support for MAX8997 PMIC"
>> depends on LEDS_CLASS && MFD_MAX8997
>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>> index cbba921..57ca62b 100644
>> --- a/drivers/leds/Makefile
>> +++ b/drivers/leds/Makefile
>> @@ -52,6 +52,7 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
>> obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
>> obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
>> obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
>> +obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
>> obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
>> obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
>> obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
>> diff --git a/drivers/leds/leds-max77693.c b/drivers/leds/leds-max77693.c
>> new file mode 100644
>> index 0000000..67a2f8f
>> --- /dev/null
>> +++ b/drivers/leds/leds-max77693.c
>> @@ -0,0 +1,1023 @@
>> +/*
>> + * LED Flash class driver for the flash cell of max77693 mfd.
>> + *
>> + * Copyright (C) 2014, Samsung Electronics Co., Ltd.
>> + *
>> + * Authors: Jacek Anaszewski <[email protected]>
>> + * Andrzej Hajda <[email protected]>
>> + *
>> + * 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 <asm/div64.h>
>> +#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>
>> +
>> +#define MODE_OFF 0
>> +#define MODE_FLASH1 (1 << 0)
>> +#define MODE_FLASH2 (1 << 1)
>> +#define MODE_TORCH1 (1 << 2)
>> +#define MODE_TORCH2 (1 << 3)
>> +#define MODE_FLASH_EXTERNAL1 (1 << 4)
>> +#define MODE_FLASH_EXTERNAL2 (1 << 5)
>
> You could do this based on an argument (led number). E.g.
>
> #define MODE_FLASH_EXTERNAL(a) (1 << (4 + a))

OK.

>> +#define MODE_FLASH (MODE_FLASH1 | MODE_FLASH2 | \
>> + MODE_FLASH_EXTERNAL1 | MODE_FLASH_EXTERNAL2)
>> +
>> +#define FLED1_IOUT (1 << 0)
>> +#define FLED2_IOUT (1 << 1)
>> +
>> +enum {
>> + FLED1,
>> + FLED2
>> +};
>> +
>> +enum {
>> + FLASH,
>> + TORCH
>> +};
>> +
>> +struct max77693_sub_led {
>> + struct led_classdev_flash ldev;
>> + struct work_struct work_brightness_set;
>> +
>> + unsigned int torch_brightness;
>> + unsigned int flash_timeout;
>> +};
>> +
>> +struct max77693_led {
>
> As this does not refer to a device, how about struct max77693_device, for
> instance?

OK.

>> + struct regmap *regmap;
>> + struct platform_device *pdev;
>> + struct max77693_led_platform_data *pdata;
>> + struct mutex lock;
>> +
>> + struct max77693_sub_led sub_leds[2];
>> +
>> + unsigned int current_flash_timeout;
>> + unsigned int mode_flags;
>> + u8 torch_iout_reg;
>> + bool iout_joint;
>> + int strobing_sub_led_id;
>> +};
>> +
>> +struct max77693_led_settings {
>> + struct led_flash_setting torch_brightness;
>> + struct led_flash_setting flash_brightness;
>> + struct led_flash_setting flash_timeout;
>> +};
>> +
>> +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_led *ldev1_to_led(
>> + struct led_classdev_flash *ldev)
>> +{
>> + struct max77693_sub_led *sub_led = container_of(ldev,
>> + struct max77693_sub_led,
>> + ldev);
>> + return container_of(sub_led, struct max77693_led, sub_leds[0]);
>
> You could have a common macro to find the flash controller struct if you add
> the LED number to struct max77693_sub_led.
>
>> +}
>> +
>> +static inline struct max77693_led *ldev2_to_led(
>> + struct led_classdev_flash *ldev)
>> +{
>> + struct max77693_sub_led *sub_led = container_of(ldev,
>> + struct max77693_sub_led,
>> + ldev);
>> + return container_of(sub_led, struct max77693_led, sub_leds[1]);
>> +}
>> +
>> +static u8 max77693_led_vsys_to_reg(u32 mv)
>> +{
>> + return ((mv - MAX_FLASH1_VSYS_MIN) / MAX_FLASH1_VSYS_STEP) << 2;
>> +}
>> +
>> +static u8 max77693_led_vout_to_reg(u32 mv)
>> +{
>> + return (mv - FLASH_VOUT_MIN) / FLASH_VOUT_STEP + FLASH_VOUT_RMIN;
>> +}
>> +
>> +/* split composite current @i into two @iout according to @imax weights */
>
> What do you intend to do in the oint iout mode? A single LED connected to
> iout pins which are soldered together?

Exactly that what is written in the comment - split the current into
both outputs.

>> +static void max77693_calc_iout(u32 iout[2], u32 i, u32 imax[2])
>> +{
>> + u64 t = i;
>> +
>> + t *= imax[1];
>> + do_div(t, imax[0] + imax[1]);
>> +
>> + iout[1] = (u32)t / FLASH_IOUT_STEP * FLASH_IOUT_STEP;
>> + iout[0] = i - iout[1];
>> +}
>> +
>> +static int max77693_set_mode(struct max77693_led *led, unsigned int mode)
>> +{
>> + struct max77693_led_platform_data *p = led->pdata;
>> + struct regmap *rmap = led->regmap;
>> + int ret, v = 0;
>> +
>> + if (mode & MODE_TORCH1) {
>> + if (p->trigger[FLED1] & MAX77693_LED_TRIG_SOFT)
>> + v |= FLASH_EN_ON << TORCH_EN_SHIFT(1);
>> + }
>> +
>> + if (mode & MODE_TORCH2) {
>> + if (p->trigger[FLED2] & MAX77693_LED_TRIG_SOFT)
>> + v |= FLASH_EN_ON << TORCH_EN_SHIFT(2);
>> + }
>
> The above could be turned to a loop from 0 to 1, as you have two LEDs.Just
> the MODE_* macro misses the argument.

Good point, thanks.

>> +
>> + if (mode & MODE_FLASH1) {
>> + if (p->trigger[FLED1] & MAX77693_LED_TRIG_SOFT)
>> + v |= FLASH_EN_ON << FLASH_EN_SHIFT(1);
>> + } else if (mode & MODE_FLASH_EXTERNAL1) {
>> + if (p->trigger[FLED1] & MAX77693_LED_TRIG_EXT)
>> + v |= FLASH_EN_FLASH << FLASH_EN_SHIFT(2);
>> + /*
>> + * Enable hw triggering also for torch mode, as some camera
>> + * sensors use torch led to fathom ambient light conditions
>> + * before strobing the flash.
>> + */
>> + if (p->trigger[FLED1] & MAX77693_LED_TRIG_EXT)
>> + v |= FLASH_EN_TORCH << TORCH_EN_SHIFT(1);
>> + }
>> +
>> + if (mode & MODE_FLASH2) {
>> + if (p->trigger[FLED2] & MAX77693_LED_TRIG_SOFT)
>> + v |= FLASH_EN_ON << FLASH_EN_SHIFT(2);
>> + } else if (mode & MODE_FLASH_EXTERNAL2) {
>> + if (p->trigger[FLED2] & MAX77693_LED_TRIG_EXT)
>> + v |= FLASH_EN_FLASH << FLASH_EN_SHIFT(2);
>> + /*
>> + * Enable hw triggering also for torch mode, as some camera
>> + * sensors use torch led to fathom ambient light conditions
>> + * before strobing the flash.
>> + */
>> + if (p->trigger[FLED2] & MAX77693_LED_TRIG_EXT)
>> + v |= FLASH_EN_TORCH << TORCH_EN_SHIFT(2);
>> + }
>
> Same here.

Agreed.

>> + /* Reset the register only prior setting flash modes */
>> + if (mode & ~(MODE_TORCH1 | MODE_TORCH2)) {
>> + 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 void max77693_set_sync_strobe(struct max77693_led *led,
>> + unsigned int *mode)
>> +{
>> + struct max77693_sub_led *sub_leds = led->sub_leds;
>> + struct led_classdev_flash *flash;
>> + unsigned int m = *mode;
>> +
>> + /*
>> + * If there are two leds then check if the other one
>> + * wants to be strobed simultaneously.
>> + */
>> + if (!led->iout_joint) {
>
> if (led->iout_joint)
> return;
>
> And you can more the rest left by one tab.

OK.

>> + if (m & (MODE_FLASH1 | MODE_FLASH_EXTERNAL1)) {
>
> I think this matters only software strobe. The hardware strobe works this
> way automatically --- or I'd guess so at least.

No, the device allows to enable external strobe separately for each
output.

>> + flash = &sub_leds[FLED2].ldev;
>> + if (flash->sync_strobe)
>> + m |= m << 1;
>> + } else if (m & (MODE_FLASH2 | MODE_FLASH_EXTERNAL2)) {
>> + flash = &sub_leds[FLED1].ldev;
>> + if (flash->sync_strobe)
>> + m |= m >> 1;
>> + }
>> + }
>> +
>> + *mode = m;
>> +}
>> +
>> +static int max77693_add_mode(struct max77693_led *led, unsigned int mode)
>> +{
>> + int ret;
>> +
>> + /* Span the mode on FLED2 for joint iouts case */
>> + if (led->iout_joint)
>> + mode |= (mode << 1);
>> +
>> + /*
>> + * Torch mode once enabled remains active until turned off,
>> + * and thus no action is required in this case.
>> + */
>> + if ((mode & MODE_TORCH1) &&
>> + (led->mode_flags & MODE_TORCH1))
>> + return 0;
>> + if ((mode & MODE_TORCH2) &&
>> + (led->mode_flags & MODE_TORCH2))
>> + return 0;
>> +
>> + /* Span a flash mode on the other led if it is to be synchronized */
>> + max77693_set_sync_strobe(led, &mode);
>> +
>> + /*
>> + * FLASH_EXTERNAL mode activates FLASHEN and TORCHEN pins
>> + * in the device. The related register settings interfere
>> + * with SW triggerred modes, thus clear them to ensure proper
>> + * device configuration.
>> + */
>> + if (mode & MODE_FLASH_EXTERNAL1)
>> + led->mode_flags &= (~MODE_TORCH1 & ~MODE_FLASH1);
>> + if (mode & MODE_FLASH_EXTERNAL2)
>> + led->mode_flags &= (~MODE_TORCH2 & ~MODE_FLASH2);
>> +
>> + led->mode_flags |= mode;
>> +
>> + ret = max77693_set_mode(led, led->mode_flags);
>> + if (ret < 0)
>> + return ret;
>> +
>> + /*
>> + * Clear flash mode flag after setting the mode to avoid
>> + * spurious flash strobing on every subsequent torch mode
>> + * setting.
>> + */
>> + if (mode & MODE_FLASH)
>> + led->mode_flags &= ~mode;
>> +
>> + return ret;
>> +}
>> +
>> +static int max77693_clear_mode(struct max77693_led *led, unsigned int mode)
>> +{
>> + int ret;
>> +
>> + if (led->iout_joint)
>> + /* Clear mode also on FLED2 for joint iouts case */
>> + mode |= (mode << 1);
>> + else
>> + /*
>> + * Clear a flash mode on the other led
>> + * if it is to be synchronized.
>> + */
>> + max77693_set_sync_strobe(led, &mode);
>> +
>> + led->mode_flags &= ~mode;
>> +
>> + ret = max77693_set_mode(led, led->mode_flags);
>> +
>> + return ret;
>> +}
>> +
>> +static int max77693_set_torch_current(struct max77693_led *led,
>> + int led_id, u32 micro_amp)
>> +{
>> + struct max77693_led_platform_data *p = led->pdata;
>> + struct regmap *rmap = led->regmap;
>> + u32 iout[2], iout_max[2];
>> + u8 iout1_reg = 0, iout2_reg = 0;
>> +
>> + iout_max[FLED1] = p->iout_torch[FLED1];
>> + iout_max[FLED2] = p->iout_torch[FLED2];
>> +
>> + if (led_id == FLED1) {
>> + /*
>> + * Preclude splitting current to FLED2 if we
>> + * are driving two separate leds.
>> + */
>> + if (!led->iout_joint)
>> + iout_max[FLED2] = 0;
>> + max77693_calc_iout(iout, micro_amp, iout_max);
>> + } else if (led_id == FLED2) {
>> + iout_max[FLED1] = 0;
>> + max77693_calc_iout(iout, micro_amp, iout_max);
>> + }
>> +
>> + if (led_id == FLED1 || led->iout_joint) {
>> + iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
>> + led->torch_iout_reg &= 0xf0;
>> + }
>> + if (led_id == FLED2 || led->iout_joint) {
>> + iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
>> + led->torch_iout_reg &= 0x0f;
>> + }
>> +
>> + 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 *led,
>> + int led_id,
>> + u32 micro_amp)
>> +{
>> + struct max77693_led_platform_data *p = led->pdata;
>> + struct regmap *rmap = led->regmap;
>> + u32 iout[2], iout_max[2];
>> + u8 iout1_reg, iout2_reg;
>> + int ret = -EINVAL;
>> +
>> + iout_max[FLED1] = p->iout_flash[FLED1];
>> + iout_max[FLED2] = p->iout_flash[FLED2];
>> +
>> + if (led_id == FLED1) {
>> + /*
>> + * Preclude splitting current to FLED2 if we
>> + * are driving two separate leds.
>> + */
>> + if (!led->iout_joint)
>> + iout_max[FLED2] = 0;
>> + max77693_calc_iout(iout, micro_amp, iout_max);
>> + } else if (led_id == FLED2) {
>> + iout_max[FLED1] = 0;
>> + max77693_calc_iout(iout, micro_amp, iout_max);
>> + }
>> +
>> + if (led_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 (led_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 *led, u32 timeout)
>> +{
>> + struct max77693_led_platform_data *p = led->pdata;
>> + struct regmap *rmap = led->regmap;
>> + u8 v;
>> + int ret;
>> +
>> + v = max77693_flash_timeout_to_reg(timeout);
>> +
>> + if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
>> + v |= FLASH_TMR_LEVEL;
>> +
>> + ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
>> + if (ret < 0)
>> + return ret;
>> +
>> + led->current_flash_timeout = timeout;
>> +
>> + return 0;
>> +}
>> +
>> +static int max77693_strobe_status_get(struct max77693_led *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_int_flag_get(struct max77693_led *led, unsigned int *v)
>> +{
>> + struct regmap *rmap = led->regmap;
>> +
>> + return regmap_read(rmap, MAX77693_LED_REG_FLASH_INT, v);
>> +}
>> +
>> +static int max77693_setup(struct max77693_led *led)
>> +{
>> + struct max77693_led_platform_data *p = led->pdata;
>> + 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] = p->iout_flash[FLED1] +
>> + p->iout_flash[FLED2];
>> + } else {
>> + first_led = p->fleds[FLED1] ? FLED1 : FLED2;
>> + last_led = p->num_leds == 2 ? FLED2 : first_led;
>> + max_flash_curr[FLED1] = p->iout_flash[FLED1];
>> + max_flash_curr[FLED2] = p->iout_flash[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;
>> +
>> + /* initially set FLED1 timeout */
>> + ret = max77693_set_timeout(led, p->flash_timeout[FLED1]);
>> + if (ret < 0)
>> + return ret;
>> +
>> + if (p->low_vsys > 0)
>> + v = max77693_led_vsys_to_reg(p->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 (p->boost_mode == MAX77693_LED_BOOST_FIXED)
>> + v = FLASH_BOOST_FIXED;
>> + else
>> + v = p->boost_mode | p->boost_mode << 1;
>> + if (p->fleds[FLED1] && p->fleds[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(p->boost_vout);
>> + ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
>> + if (ret < 0)
>> + return ret;
>> +
>> + return max77693_set_mode(led, MODE_OFF);
>> +}
>> +
>> +static int max77693_led_brightness_set(struct max77693_led *led,
>> + int led_id, enum led_brightness value)
>> +{
>> + int ret;
>> +
>> + mutex_lock(&led->lock);
>> +
>> + if (value == 0) {
>> + ret = max77693_clear_mode(led, MODE_TORCH1 << led_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, led_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_TORCH1 << led_id);
>> + if (ret < 0)
>> + dev_dbg(&led->pdev->dev,
>> + "Failed to set torch mode (%d)\n",
>> + ret);
>> +unlock:
>> + mutex_unlock(&led->lock);
>> + return ret;
>> +}
>> +
>> +#define MAX77693_LED_BRIGHTNESS_SET_WORK(ID) \
>> +static void max77693_led##ID##_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 *led = container_of(sub_led, \
>> + struct max77693_led, \
>> + sub_leds[FLED##ID]); \
>> + struct max77693_sub_led *sub_leds = led->sub_leds; \
>> + \
>> + max77693_led_brightness_set(led, FLED##ID, \
>> + sub_leds[FLED##ID].torch_brightness); \
>> +}
>> +
>> +/* LED subsystem callbacks */
>> +
>> +#define MAX77693_LED_TORCH_BRIGHTNESS_SET(ID) \
>> +static int max77693_led##ID##_brightness_set_sync( \
>> + struct led_classdev *led_cdev, \
>> + enum led_brightness value) \
>> +{ \
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev); \
>> + struct max77693_led *led = ldev##ID##_to_led(flash); \
>> + \
>> + return max77693_led_brightness_set(led, FLED##ID, value); \
>> +}
>> +
>> +#define MAX77693_LED_BRIGHTNESS_SET(ID) \
>> +static void max77693_led##ID##_brightness_set( \
>> + struct led_classdev *led_cdev, \
>> + enum led_brightness value) \
>> +{ \
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev); \
>> + struct max77693_led *led = ldev##ID##_to_led(flash); \
>> + struct max77693_sub_led *sub_leds = led->sub_leds; \
>> + \
>> + sub_leds[FLED##ID].torch_brightness = value; \
>> + schedule_work(&sub_leds[FLED##ID].work_brightness_set); \
>> +}
>> +
>> +#define MAX77693_LED_FLASH_BRIGHTNESS_SET(ID) \
>> +static int max77693_led##ID##_flash_brightness_set( \
>> + struct led_classdev_flash *flash, \
>> + u32 brightness) \
>> +{ \
>> + struct max77693_led *led = ldev##ID##_to_led(flash); \
>> + int ret; \
>> + \
>> + mutex_lock(&led->lock); \
>> + ret = max77693_set_flash_current(led, FLED##ID, brightness); \
>> + mutex_unlock(&led->lock); \
>> + \
>> + return ret; \
>> +}
>> +
>> +#define MAX77693_LED_FLASH_STROBE_SET(ID) \
>> +static int max77693_led##ID##_flash_strobe_set( \
>> + struct led_classdev_flash *flash, \
>> + bool state) \
>> +{ \
>> + struct max77693_led *led = ldev##ID##_to_led(flash); \
>> + struct max77693_sub_led *sub_leds = led->sub_leds; \
>> + int ret; \
>> + \
>> + mutex_lock(&led->lock); \
>> + \
>> + if (!state) { \
>> + ret = max77693_clear_mode(led, MODE_FLASH##ID); \
>> + goto unlock; \
>> + } \
>> + \
>> + if (sub_leds[FLED##ID].flash_timeout != \
>> + led->current_flash_timeout) { \
>> + ret = max77693_set_timeout(led, \
>> + sub_leds[FLED##ID].flash_timeout); \
>> + if (ret < 0) \
>> + goto unlock; \
>> + } \
>> + \
>> + led->strobing_sub_led_id = ID; \
>> + \
>> + ret = max77693_add_mode(led, MODE_FLASH##ID); \
>> + \
>> +unlock: \
>> + mutex_unlock(&led->lock); \
>> + return ret; \
>> +}
>> +
>> +#define MAX77693_LED_FLASH_FAULT_GET(ID) \
>> +static int max77693_led##ID##_flash_fault_get( \
>> + struct led_classdev_flash *flash, \
>> + u32 *fault) \
>> +{ \
>> + struct max77693_led *led = ldev##ID##_to_led(flash); \
>> + unsigned int v; \
>> + int ret; \
>> + \
>> + ret = max77693_int_flag_get(led, &v); \
>> + if (ret < 0) \
>> + return ret; \
>> + \
>> + *fault = 0; \
>> + \
>> + if (v & FLASH_INT_FLED##ID##_OPEN) \
>> + *fault |= LED_FAULT_OVER_VOLTAGE; \
>> + if (v & FLASH_INT_FLED##ID##_SHORT) \
>> + *fault |= LED_FAULT_SHORT_CIRCUIT; \
>> + if (v & FLASH_INT_OVER_CURRENT) \
>> + *fault |= LED_FAULT_OVER_CURRENT; \
>> + \
>> + return 0; \
>> +}
>> +
>> +#define MAX77693_LED_FLASH_STROBE_GET(ID) \
>> +static int max77693_led##ID##_flash_strobe_get( \
>> + struct led_classdev_flash *flash, \
>> + bool *state) \
>> +{ \
>> + struct max77693_led *led = ldev##ID##_to_led(flash); \
>> + int ret; \
>> + \
>> + if (!state) \
>> + return -EINVAL; \
>> + \
>> + mutex_lock(&led->lock); \
>> + \
>> + ret = max77693_strobe_status_get(led, state); \
>> + \
>> + *state = !!(*state && led->strobing_sub_led_id == ID); \
>> + \
>> + mutex_unlock(&led->lock); \
>> + \
>> + return ret; \
>> +}
>> +
>> +#define MAX77693_LED_FLASH_TIMEOUT_SET(ID) \
>> +static int max77693_led##ID##_flash_timeout_set( \
>> + struct led_classdev_flash *flash, \
>> + u32 timeout) \
>> +{ \
>> + struct max77693_led *led = ldev##ID##_to_led(flash); \
>> + struct max77693_sub_led *sub_leds = led->sub_leds; \
>> + \
>> + mutex_lock(&led->lock); \
>> + sub_leds[FLED##ID].flash_timeout = timeout; \
>> + mutex_unlock(&led->lock); \
>> + \
>> + return 0; \
>> +}
>> +
>> +MAX77693_LED_BRIGHTNESS_SET(1)
>> +MAX77693_LED_BRIGHTNESS_SET_WORK(1)
>> +MAX77693_LED_TORCH_BRIGHTNESS_SET(1)
>> +MAX77693_LED_FLASH_BRIGHTNESS_SET(1)
>> +MAX77693_LED_FLASH_STROBE_SET(1)
>> +MAX77693_LED_FLASH_STROBE_GET(1)
>> +MAX77693_LED_FLASH_TIMEOUT_SET(1)
>> +MAX77693_LED_FLASH_FAULT_GET(1)
>> +
>> +MAX77693_LED_BRIGHTNESS_SET(2)
>> +MAX77693_LED_BRIGHTNESS_SET_WORK(2)
>> +MAX77693_LED_TORCH_BRIGHTNESS_SET(2)
>> +MAX77693_LED_FLASH_BRIGHTNESS_SET(2)
>> +MAX77693_LED_FLASH_STROBE_SET(2)
>> +MAX77693_LED_FLASH_STROBE_GET(2)
>> +MAX77693_LED_FLASH_TIMEOUT_SET(2)
>> +MAX77693_LED_FLASH_FAULT_GET(2)
>
> You could implement these without the help of macros so that the function
> gets the LED number from its arguments, directly or indirectly.

I'll do this provided there are no other LED class related
obstacles I don't remember now.

>> +static int max77693_led_parse_dt(struct max77693_led *led,
>> + struct device_node *node)
>> +{
>> + struct max77693_led_platform_data *p = led->pdata;
>> + struct device *dev = &led->pdev->dev;
>> + struct device_node *child_node;
>> + u32 fled_id;
>> + int ret;
>> +
>> + of_property_read_u32_array(node, "maxim,fleds", p->fleds, 2);
>> + of_property_read_u32_array(node, "maxim,trigger", p->trigger, 2);
>> + of_property_read_u32_array(node, "maxim,trigger-type", p->trigger_type,
>> + 2);
>> + of_property_read_u32(node, "maxim,boost-mode", &p->boost_mode);
>> + of_property_read_u32(node, "maxim,boost-vout", &p->boost_vout);
>> + of_property_read_u32(node, "maxim,vsys-min", &p->low_vsys);
>> +
>> + for_each_available_child_of_node(node, child_node) {
>> + ret = of_property_read_u32(child_node, "maxim,fled_id",
>> + &fled_id);
>> + if (ret < 0) {
>> + dev_err(dev, "Error reading \"fled_id\" DT property\n");
>> + return ret;
>> + }
>> +
>> + fled_id = clamp_val(fled_id, 1, 2);
>
> I think you should check fled_id is really correct, and not clamp it.

Right.

>> + --fled_id;
>> +
>> + p->sub_nodes[fled_id] = child_node;
>
> p->sub_nodes is not accessed anywhere else. Do you plan to use it for
> something?

It is passed to v4l2_flash_init. In this patch set I add the V4L2
support in the separate patch. Probably this should be move there.

>> +
>> + ret = of_property_read_string(child_node, "label",
>> + (const char **) &p->label[fled_id]);
>> + if (ret < 0) {
>> + dev_err(dev, "Error reading \"label\" DT property\n");
>> + return ret;
>> + }
>> +
>> + of_property_read_u32(child_node, "max-microamp",
>> + &p->iout_torch[fled_id]);
>> + of_property_read_u32(child_node, "flash-max-microamp",
>> + &p->iout_flash[fled_id]);
>> + of_property_read_u32(child_node, "flash-timeout-microsec",
>> + &p->flash_timeout[fled_id]);
>> + if (++p->num_leds == 2)
>> + break;
>> + }
>> +
>> + 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_led_validate_platform_data(
>> + struct max77693_led_platform_data *p)
>> +{
>> + u32 max;
>> + int i;
>> +
>> + p->boost_mode = clamp_val(p->boost_mode, MAX77693_LED_BOOST_NONE,
>> + MAX77693_LED_BOOST_FIXED);
>> +
>> + for (i = 0; i < ARRAY_SIZE(p->fleds); ++i)
>> + p->fleds[i] = clamp_val(p->fleds[i], 0, 1);
>> +
>> + /* Ensure fleds configuration is sane */
>> + if (!p->fleds[FLED1] && !p->fleds[FLED2]) {
>> + p->fleds[FLED1] = p->fleds[FLED2] = 1;
>> + p->num_leds = 1;
>> + }
>> +
>> + /* Ensure num_leds is consistent with fleds configuration */
>> + if ((!p->fleds[FLED1] || !p->fleds[FLED2]) && p->num_leds == 2)
>> + p->num_leds = 1;
>> +
>> + /*
>> + * boost must be enabled if current outputs
>> + * are connected to separate leds.
>> + */
>> + if ((p->num_leds == 2 || (p->fleds[FLED1] && p->fleds[FLED2])) &&
>> + p->boost_mode == MAX77693_LED_BOOST_NONE)
>> + p->boost_mode = MAX77693_LED_BOOST_FIXED;
>> +
>> + max = p->boost_mode ? FLASH_IOUT_MAX_2LEDS : FLASH_IOUT_MAX_1LED;
>> +
>> + if (p->fleds[FLED1]) {
>> + clamp_align(&p->iout_torch[FLED1], TORCH_IOUT_MIN,
>> + TORCH_IOUT_MAX, TORCH_IOUT_STEP);
>> + clamp_align(&p->iout_flash[FLED1], FLASH_IOUT_MIN, max,
>> + FLASH_IOUT_STEP);
>> + } else {
>> + p->iout_torch[FLED1] = p->iout_flash[FLED1] = 0;
>> + }
>> + if (p->fleds[FLED2]) {
>> + clamp_align(&p->iout_torch[FLED2], TORCH_IOUT_MIN,
>> + TORCH_IOUT_MAX, TORCH_IOUT_STEP);
>> + clamp_align(&p->iout_flash[FLED2], FLASH_IOUT_MIN, max,
>> + FLASH_IOUT_STEP);
>> + } else {
>> + p->iout_torch[FLED2] = p->iout_flash[FLED2] = 0;
>> + }
>> +
>> + for (i = 0; i < ARRAY_SIZE(p->trigger); ++i)
>> + p->trigger[i] = clamp_val(p->trigger[i], 0, 7);
>> + for (i = 0; i < ARRAY_SIZE(p->trigger_type); ++i)
>> + p->trigger_type[i] = clamp_val(p->trigger_type[i],
>> + MAX77693_LED_TRIG_TYPE_EDGE,
>> + MAX77693_LED_TRIG_TYPE_LEVEL);
>> +
>> + for (i = 0; i < ARRAY_SIZE(p->flash_timeout); ++i)
>> + clamp_align(&p->flash_timeout[i], FLASH_TIMEOUT_MIN,
>> + FLASH_TIMEOUT_MAX, FLASH_TIMEOUT_STEP);
>> +
>> + clamp_align(&p->boost_vout, FLASH_VOUT_MIN, FLASH_VOUT_MAX,
>> + FLASH_VOUT_STEP);
>> +
>> + if (p->low_vsys)
>> + clamp_align(&p->low_vsys, MAX_FLASH1_VSYS_MIN,
>> + MAX_FLASH1_VSYS_MAX, MAX_FLASH1_VSYS_STEP);
>> +}
>> +
>> +static int max77693_led_get_platform_data(struct max77693_led *led)
>
> This isn't really platform data anymore. Could this be just "configuration",
> for instance?

OK, I was also pondering over it.

>> +{
>> + struct device *dev = &led->pdev->dev;
>> + int ret;
>> +
>> + if (!dev->of_node)
>> + return -EINVAL;
>> +
>> + led->pdata = devm_kzalloc(dev, sizeof(*led->pdata), GFP_KERNEL);
>> + if (!led->pdata)
>> + return -ENOMEM;
>> +
>> + ret = max77693_led_parse_dt(led, dev->of_node);
>> + if (ret < 0)
>> + return ret;
>> +
>> + max77693_led_validate_platform_data(led->pdata);
>> +
>> + return 0;
>> +}
>> +
>> +#define MAX77693_LED_INIT_FLASH_OPS(ID) \
>> +static const struct led_flash_ops flash_ops##ID = { \
>> + \
>> + .flash_brightness_set = max77693_led##ID##_flash_brightness_set, \
>> + .strobe_set = max77693_led##ID##_flash_strobe_set, \
>> + .strobe_get = max77693_led##ID##_flash_strobe_get, \
>> + .timeout_set = max77693_led##ID##_flash_timeout_set, \
>> + .fault_get = max77693_led##ID##_flash_fault_get, \
>> +}
>> +
>> +MAX77693_LED_INIT_FLASH_OPS(1);
>> +MAX77693_LED_INIT_FLASH_OPS(2);
>
> Same here, I think you can have just a single implementation of these.

I will check this out.

>> +static void max77693_init_flash_settings(struct max77693_led *led,
>> + struct max77693_led_settings *s,
>> + int led_id)
>> +{
>> + struct max77693_led_platform_data *p = led->pdata;
>> + struct led_flash_setting *setting;
>> +
>> + /* Init torch intensity setting */
>> + setting = &s->torch_brightness;
>> + setting->min = led->iout_joint ? TORCH_IOUT_MIN * 2 :
>> + TORCH_IOUT_MIN;
>> + setting->max = led->iout_joint ?
>> + p->iout_torch[FLED1] + p->iout_torch[FLED2] :
>> + p->iout_torch[led_id];
>> + setting->step = TORCH_IOUT_STEP;
>> + setting->val = setting->max;
>> +
>> + /* Init flash intensity setting */
>> + setting = &s->flash_brightness;
>> + setting->min = led->iout_joint ? FLASH_IOUT_MIN * 2 :
>> + FLASH_IOUT_MIN;
>> + setting->max = led->iout_joint ?
>> + p->iout_flash[FLED1] + p->iout_flash[FLED2] :
>> + p->iout_flash[led_id];
>> + setting->step = FLASH_IOUT_STEP;
>> + setting->val = setting->max;
>> +
>> + /* Init flash timeout setting */
>> + setting = &s->flash_timeout;
>> + setting->min = FLASH_TIMEOUT_MIN;
>> + setting->max = p->flash_timeout[led_id];
>> + setting->step = FLASH_TIMEOUT_STEP;
>> + setting->val = setting->max;
>> +}
>> +
>> +static int max77693_register_led(struct max77693_led *led, int id)
>> +{
>> + struct platform_device *pdev = led->pdev;
>> + struct led_classdev_flash *flash;
>> + struct led_classdev *led_cdev;
>> + struct max77693_sub_led *sub_leds = led->sub_leds;
>> + struct max77693_led_settings settings;
>> +
>> + flash = &sub_leds[id].ldev;
>> +
>> + /* Initialize flash settings */
>> + max77693_init_flash_settings(led, &settings, id);
>> +
>> + /* Initialize LED Flash class device */
>> + led_cdev = &flash->led_cdev;
>> +
>> + led_cdev->name = led->pdata->label[id];
>> +
>> + if (id == FLED1) {
>> + led_cdev->brightness_set = max77693_led1_brightness_set;
>> + led_cdev->brightness_set_sync =
>> + max77693_led1_brightness_set_sync;
>> + INIT_WORK(&sub_leds[id].work_brightness_set,
>> + max77693_led1_brightness_set_work);
>> + flash->ops = &flash_ops1;
>> + } else {
>> + led_cdev->brightness_set = max77693_led2_brightness_set;
>> + led_cdev->brightness_set_sync =
>> + max77693_led2_brightness_set_sync;
>> + INIT_WORK(&sub_leds[id].work_brightness_set,
>> + max77693_led2_brightness_set_work);
>> + flash->ops = &flash_ops2;
>> + }
>> +
>> + led_cdev->max_brightness = settings.torch_brightness.val /
>> + TORCH_IOUT_STEP;
>> + led_cdev->flags |= LED_DEV_CAP_FLASH;
>> + if (led->pdata->num_leds == 2)
>> + led_cdev->flags |= LED_DEV_CAP_COMPOUND;
>> +
>> + flash->brightness = settings.flash_brightness;
>> + flash->timeout = settings.flash_timeout;
>> + sub_leds[id].flash_timeout = flash->timeout.val;
>> +
>> + /* Register in the LED subsystem. */
>> + return led_classdev_flash_register(&pdev->dev, flash);
>> +}
>> +
>> +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 *led;
>> + struct max77693_led_platform_data *p;
>> + struct max77693_sub_led *sub_leds;
>> + int ret;
>> +
>> + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
>> + if (!led)
>> + return -ENOMEM;
>> +
>> + led->pdev = pdev;
>> + led->regmap = iodev->regmap;
>> + sub_leds = led->sub_leds;
>> + platform_set_drvdata(pdev, led);
>> + ret = max77693_led_get_platform_data(led);
>> + if (ret < 0)
>> + return -EINVAL;
>> +
>> + p = led->pdata;
>> + mutex_init(&led->lock);
>> +
>> + if (p->num_leds == 1 && p->fleds[FLED1] && p->fleds[FLED2])
>> + led->iout_joint = true;
>> +
>> + ret = max77693_setup(led);
>> + if (ret < 0)
>> + goto err_setup;
>> +
>> + if (led->iout_joint || p->fleds[FLED1]) {
>> + ret = max77693_register_led(led, FLED1);
>> + if (ret < 0)
>> + goto err_setup;
>> + }
>> +
>> + if (!led->iout_joint && p->fleds[FLED2]) {
>> + ret = max77693_register_led(led, FLED2);
>> + if (ret < 0)
>> + goto err_register_led2;
>> + }
>> +
>> + return 0;
>> +
>> +err_register_led2:
>> + if (!p->fleds[FLED1])
>> + goto err_setup;
>> + led_classdev_flash_unregister(&sub_leds[FLED1].ldev);
>> +err_setup:
>> + mutex_destroy(&led->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int max77693_led_remove(struct platform_device *pdev)
>> +{
>> + struct max77693_led *led = platform_get_drvdata(pdev);
>> + struct max77693_led_platform_data *p = led->pdata;
>> + struct max77693_sub_led *sub_leds = led->sub_leds;
>> +
>> + if (led->iout_joint || p->fleds[FLED1]) {
>> + led_classdev_flash_unregister(&sub_leds[FLED1].ldev);
>> + cancel_work_sync(&sub_leds[FLED1].work_brightness_set);
>> + }
>> +
>> + if (!led->iout_joint && p->fleds[FLED2]) {
>> + led_classdev_flash_unregister(&sub_leds[FLED2].ldev);
>> + cancel_work_sync(&sub_leds[FLED2].work_brightness_set);
>> + }
>> +
>> + mutex_destroy(&led->lock);
>> +
>> + return 0;
>> +}
>> +
>> +static 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",
>> + .owner = THIS_MODULE,
>> + .of_match_table = max77693_led_dt_match,
>> + },
>> +};
>> +
>> +module_platform_driver(max77693_led_driver);
>> +
>> +MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
>> +MODULE_AUTHOR("Andrzej Hajda <[email protected]>");
>> +MODULE_DESCRIPTION("Maxim MAX77693 led flash driver");
>> +MODULE_LICENSE("GPL");
>

Best Regards,
Jacek Anaszewski

2014-12-04 11:40:56

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 06/19] DT: Add documentation for the mfd Maxim max77693

Hi Sakari,

Thanks for the review.

On 12/04/2014 11:07 AM, Sakari Ailus wrote:
> Hi Jacek,
>
> On Wed, Dec 03, 2014 at 05:06:41PM +0100, Jacek Anaszewski wrote:
>> This patch adds device tree binding documentation for
>> the flash cell of the Maxim max77693 multifunctional device.
>>
>> Signed-off-by: Jacek Anaszewski <[email protected]>
>> Signed-off-by: Andrzej Hajda <[email protected]>
>> Acked-by: Kyungmin Park <[email protected]>
>> Cc: Lee Jones <[email protected]>
>> Cc: Chanwoo Choi <[email protected]>
>> Cc: Bryan Wu <[email protected]>
>> Cc: Richard Purdie <[email protected]>
>> Cc: Rob Herring <[email protected]>
>> Cc: Pawel Moll <[email protected]>
>> Cc: Mark Rutland <[email protected]>
>> Cc: Ian Campbell <[email protected]>
>> Cc: Kumar Gala <[email protected]>
>> Cc: <[email protected]>
>> ---
>> Documentation/devicetree/bindings/mfd/max77693.txt | 89 ++++++++++++++++++++
>> 1 file changed, 89 insertions(+)
>>
>> diff --git a/Documentation/devicetree/bindings/mfd/max77693.txt b/Documentation/devicetree/bindings/mfd/max77693.txt
>> index 01e9f30..25a6e78 100644
>> --- a/Documentation/devicetree/bindings/mfd/max77693.txt
>> +++ b/Documentation/devicetree/bindings/mfd/max77693.txt
>> @@ -41,7 +41,66 @@ Optional properties:
>> To get more informations, please refer to documentaion.
>> [*] refer Documentation/devicetree/bindings/pwm/pwm.txt
>>
>> +- led : the LED submodule device node
>> +
>> +There are two led outputs available - fled1 and fled2. Each of them can
>> +control a separate led or they can be connected together to double
>> +the maximum current for a single connected led. One led is represented
>> +by one child node.
>> +
>> +Required properties:
>> +- compatible : Must be "maxim,max77693-led".
>> +
>> +Optional properties:
>> +- maxim,fleds : Array of current outputs in order: fled1, fled2.
>> + Note: both current outputs can be connected to a single led
>> + Possible values:
>> + MAX77693_LED_FLED_UNUSED - the output is left disconnected,
>> + MAX77693_LED_FLED_USED - a diode is connected to the output.
>
> As you have a LED sub-nodes for each LED already, isn't this redundant?

Well, it seems so :)

>> +- maxim,trigger-type : Array of trigger types in order: flash, torch.
>> + Possible trigger types:
>> + MAX77693_LED_TRIG_TYPE_EDGE - Rising edge of the signal triggers
>> + the flash/torch,
>> + MAX77693_LED_TRIG_TYPE_LEVEL - Signal level controls duration of
>
> How about: "Strobe pulse length ..."?

OK, it will be more clear.

> How long does the torch stay on if you use edge trigger for it? I've always
> thought the torch enable pin was a practical joke. :-)

There is a torch timer available but I don't expose it to the user.

> If you need it this for torch as well, I'd use separate properties for the
> purpose, i.e. trigger-type-flash and trigger-type-torch.

OK.

>> + the flash/torch.
>> +- maxim,trigger : Array of flags indicating which trigger can activate given led
>> + in order: fled1, fled2.
>> + Possible flag values (can be combined):
>> + MAX77693_LED_TRIG_FLASHEN - FLASHEN pin of the chip,
>> + MAX77693_LED_TRIG_TORCHEN - TORCHEN pin of the chip,
>> + MAX77693_LED_TRIG_SOFTWARE - software via I2C command.
>
> Is there a need to prevent strobing using a certain method? Just wondering.

In some cases it could be convenient to prevent some options through
device tree.

>> +- maxim,boost-mode :
>> + In boost mode the device can produce up to 1.2A of total current
>> + on both outputs. The maximum current on each output is reduced
>> + to 625mA then. If there are two child led nodes defined then boost
>> + is enabled by default.
>> + Possible values:
>> + MAX77693_LED_BOOST_OFF - no boost,
>> + MAX77693_LED_BOOST_ADAPTIVE - adaptive mode,
>> + MAX77693_LED_BOOST_FIXED - fixed mode.
>> +- maxim,boost-vout : Output voltage of the boost module in millivolts.
>> +- maxim,vsys-min : Low input voltage level in millivolts. Flash is not fired
>> + if chip estimates that system voltage could drop below this level due
>> + to flash power consumption.
>> +
>> +Required properties of the LED child node:
>> +- label : see Documentation/devicetree/bindings/leds/common.txt
>> +- maxim,fled_id : Identifier of the fled output the led is connected to;
>
> I'm pretty sure this will be needed for about every chip that can drive
> multiple LEDs. Shouldn't it be documented in the generic documentation?

OK.

>> + MAX77693_LED_FLED1 - FLED1 output of the device - it has to be
>> + used also if a single LED is connected to both outputs,
>> + MAX77693_LED_FLED2 - FLED2 output of the device.
>> +
>> +Optional properties of the LED child node:
>> +- max-microamp : see Documentation/devicetree/bindings/leds/common.txt
>> + Range: 15625 - 250000
>> +- flash-max-microamp : see Documentation/devicetree/bindings/leds/common.txt
>> + Range: 15625 - 1000000
>> +- flash-timeout-microsec : see Documentation/devicetree/bindings/leds/common.txt
>> + Range: 62500 - 1000000
>> +
>> Example:
>> +#include <dt-bindings/mfd/max77693.h>
>> +
>> max77693@66 {
>> compatible = "maxim,max77693";
>> reg = <0x66>;
>> @@ -73,4 +132,34 @@ Example:
>> pwms = <&pwm 0 40000 0>;
>> pwm-names = "haptic";
>> };
>> +
>> + led {
>> + compatible = "maxim,max77693-led";
>> + maxim,fleds = <MAX77693_LED_FLED_USED
>> + MAX77693_LED_FLED_USED>;
>> + maxim,trigger = <MAX77693_LED_TRIG_ALL
>> + (MAX77693_LED_TRIG_TORCHEN |
>> + MAX77693_LED_TRIG_SOFTWARE)>;
>> + maxim,trigger-type = <MAX77693_LED_TRIG_TYPE_EDGE
>> + MAX77693_LED_TRIG_TYPE_LEVEL>;
>> + maxim,boost-mode = <MAX77693_LED_BOOST_ADAPTIVE>;
>> + maxim,boost-vout = <5000>;
>> + maxim,vsys-min = <2400>;
>> +
>> + camera1_flash: led1 {
>> + maxim,fled_id = <MAX77693_LED_FLED1>;
>> + label = "max77693-flash1";
>> + max-microamp = <250000>;
>> + flash-max-microamp = <625000>;
>> + flash-timeout-microsec = <1000000>;
>> + };
>> +
>> + camera2_flash: led2 {
>> + maxim,fled_id = <MAX77693_LED_FLED2>;
>> + label = "max77693-flash2";
>> + max-microamp = <250000>;
>> + flash-max-microamp = <500000>;
>> + flash-timeout-microsec = <1000000>;
>> + };
>> + };
>
> I like how this looks like in general.

Nice :)

>> };
>

Best Regards,
Jacek Anaszewski

2014-12-04 16:12:08

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 06/19] DT: Add documentation for the mfd Maxim max77693

Hi!

> >>+- maxim,boost-mode :
> >>+ In boost mode the device can produce up to 1.2A of total current
> >>+ on both outputs. The maximum current on each output is reduced
> >>+ to 625mA then. If there are two child led nodes defined then boost
> >>+ is enabled by default.
> >>+ Possible values:
> >>+ MAX77693_LED_BOOST_OFF - no boost,
> >>+ MAX77693_LED_BOOST_ADAPTIVE - adaptive mode,
> >>+ MAX77693_LED_BOOST_FIXED - fixed mode.
> >>+- maxim,boost-vout : Output voltage of the boost module in millivolts.
> >>+- maxim,vsys-min : Low input voltage level in millivolts. Flash is not fired
> >>+ if chip estimates that system voltage could drop below this level due
> >>+ to flash power consumption.
> >>+
> >>+Required properties of the LED child node:
> >>+- label : see Documentation/devicetree/bindings/leds/common.txt
> >>+- maxim,fled_id : Identifier of the fled output the led is connected to;
> >
> >I'm pretty sure this will be needed for about every chip that can drive
> >multiple LEDs. Shouldn't it be documented in the generic documentation?
>
> OK.

Well... "fled_id" is not exactly suitable name. On other busses, it
would be "reg = <1>"?

Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

2014-12-08 10:29:27

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 06/19] DT: Add documentation for the mfd Maxim max77693

Hi Pavel,

On 12/04/2014 05:12 PM, Pavel Machek wrote:
> Hi!
>
>>>> +- maxim,boost-mode :
>>>> + In boost mode the device can produce up to 1.2A of total current
>>>> + on both outputs. The maximum current on each output is reduced
>>>> + to 625mA then. If there are two child led nodes defined then boost
>>>> + is enabled by default.
>>>> + Possible values:
>>>> + MAX77693_LED_BOOST_OFF - no boost,
>>>> + MAX77693_LED_BOOST_ADAPTIVE - adaptive mode,
>>>> + MAX77693_LED_BOOST_FIXED - fixed mode.
>>>> +- maxim,boost-vout : Output voltage of the boost module in millivolts.
>>>> +- maxim,vsys-min : Low input voltage level in millivolts. Flash is not fired
>>>> + if chip estimates that system voltage could drop below this level due
>>>> + to flash power consumption.
>>>> +
>>>> +Required properties of the LED child node:
>>>> +- label : see Documentation/devicetree/bindings/leds/common.txt
>>>> +- maxim,fled_id : Identifier of the fled output the led is connected to;
>>>
>>> I'm pretty sure this will be needed for about every chip that can drive
>>> multiple LEDs. Shouldn't it be documented in the generic documentation?
>>
>> OK.
>
> Well... "fled_id" is not exactly suitable name. On other busses, it
> would be "reg = <1>"?

I'm ok with "reg". This scheme is used for pca963x.txt and is described
as "number of LED line". We could define it similarly in the common.txt.
A device would have to specify the range of allowed values though.
I would add such a note to the generic binding.

Regards,
Jacek

2014-12-09 08:50:56

by Lee Jones

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 04/19] mfd: max77693: adjust max77693_led_platform_data

On Wed, 03 Dec 2014, Jacek Anaszewski wrote:

> Add "label" array for Device Tree strings with the name of a LED device
> and make flash_timeout a two element array, for caching the sub-led
> related flash timeout. Added is also an array for caching pointers to the
> sub-nodes representing sub-leds.
>
> Signed-off-by: Jacek Anaszewski <[email protected]>
> Acked-by: Kyungmin Park <[email protected]>
> Cc: Chanwoo Choi <[email protected]>
> Cc: Lee Jones <[email protected]>
> ---
> include/linux/mfd/max77693.h | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
> index f0b6585..c80ee99 100644
> --- a/include/linux/mfd/max77693.h
> +++ b/include/linux/mfd/max77693.h
> @@ -88,16 +88,18 @@ enum max77693_led_boost_mode {
> };
>
> struct max77693_led_platform_data {
> + const char *label[2];
> u32 fleds[2];
> u32 iout_torch[2];
> u32 iout_flash[2];
> u32 trigger[2];
> u32 trigger_type[2];
> + u32 flash_timeout[2];
> u32 num_leds;
> u32 boost_mode;
> - u32 flash_timeout;
> u32 boost_vout;
> u32 low_vsys;
> + struct device_node *sub_nodes[2];

I haven't seen anyone do this before. Why can't you use the provided
OF functions to traverse through your tree?

> };
>
> /* MAX77693 */

--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

2014-12-09 08:52:45

by Lee Jones

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 03/19] mfd: max77693: Modify flash cell name identifiers

On Wed, 03 Dec 2014, Jacek Anaszewski wrote:

> Change flash cell identifiers from max77693-flash to max77693-led
> to avoid confusion with NOR/NAND Flash.
>
> Signed-off-by: Jacek Anaszewski <[email protected]>
> Acked-by: Kyungmin Park <[email protected]>
> Cc: Chanwoo Choi <[email protected]>
> Cc: Lee Jones <[email protected]>
> ---
> drivers/mfd/max77693.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/mfd/max77693.c b/drivers/mfd/max77693.c
> index a159593..cb14afa 100644
> --- a/drivers/mfd/max77693.c
> +++ b/drivers/mfd/max77693.c
> @@ -53,8 +53,8 @@ static const struct mfd_cell max77693_devs[] = {
> .of_compatible = "maxim,max77693-haptic",
> },
> {
> - .name = "max77693-flash",
> - .of_compatible = "maxim,max77693-flash",
> + .name = "max77693-led",
> + .of_compatible = "maxim,max77693-led",

This is fine by me, so long as you've been through the usual
deprecation procedures or this platform is still WiP.

> },
> };
>

--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

2014-12-09 09:09:33

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 04/19] mfd: max77693: adjust max77693_led_platform_data

On 12/09/2014 09:50 AM, Lee Jones wrote:
> On Wed, 03 Dec 2014, Jacek Anaszewski wrote:
>
>> Add "label" array for Device Tree strings with the name of a LED device
>> and make flash_timeout a two element array, for caching the sub-led
>> related flash timeout. Added is also an array for caching pointers to the
>> sub-nodes representing sub-leds.
>>
>> Signed-off-by: Jacek Anaszewski <[email protected]>
>> Acked-by: Kyungmin Park <[email protected]>
>> Cc: Chanwoo Choi <[email protected]>
>> Cc: Lee Jones <[email protected]>
>> ---
>> include/linux/mfd/max77693.h | 4 +++-
>> 1 file changed, 3 insertions(+), 1 deletion(-)
>>
>> diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
>> index f0b6585..c80ee99 100644
>> --- a/include/linux/mfd/max77693.h
>> +++ b/include/linux/mfd/max77693.h
>> @@ -88,16 +88,18 @@ enum max77693_led_boost_mode {
>> };
>>
>> struct max77693_led_platform_data {
>> + const char *label[2];
>> u32 fleds[2];
>> u32 iout_torch[2];for_each_available_child_of_node
>> u32 iout_flash[2];
>> u32 trigger[2];
>> u32 trigger_type[2];
>> + u32 flash_timeout[2];
>> u32 num_leds;
>> u32 boost_mode;
>> - u32 flash_timeout;
>> u32 boost_vout;
>> u32 low_vsys;
>> + struct device_node *sub_nodes[2];
>
> I haven't seen anyone do this before. Why can't you use the provided
> OF functions to traverse through your tree?

I use for_each_available_child_of_node when parsing DT node, but I
need to cache the pointer to sub-node to be able to use it later
when it needs to be passed to V4L2 sub-device which is then
asynchronously matched by the phandle to sub-node.

If it is not well seen to cache it in the platform data then
I will find different way to accomplish this.

Best Regards,
Jacek Anaszewski

2014-12-09 09:18:44

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 03/19] mfd: max77693: Modify flash cell name identifiers

On 12/09/2014 09:52 AM, Lee Jones wrote:
> On Wed, 03 Dec 2014, Jacek Anaszewski wrote:
>
>> Change flash cell identifiers from max77693-flash to max77693-led
>> to avoid confusion with NOR/NAND Flash.
>>
>> Signed-off-by: Jacek Anaszewski <[email protected]>
>> Acked-by: Kyungmin Park <[email protected]>
>> Cc: Chanwoo Choi <[email protected]>
>> Cc: Lee Jones <[email protected]>
>> ---
>> drivers/mfd/max77693.c | 4 ++--
>> 1 file changed, 2 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/mfd/max77693.c b/drivers/mfd/max77693.c
>> index a159593..cb14afa 100644
>> --- a/drivers/mfd/max77693.c
>> +++ b/drivers/mfd/max77693.c
>> @@ -53,8 +53,8 @@ static const struct mfd_cell max77693_devs[] = {
>> .of_compatible = "maxim,max77693-haptic",
>> },
>> {
>> - .name = "max77693-flash",
>> - .of_compatible = "maxim,max77693-flash",
>> + .name = "max77693-led",
>> + .of_compatible = "maxim,max77693-led",
>
> This is fine by me, so long as you've been through the usual
> deprecation procedures or this platform is still WiP.

It was me who added of_compatible for max77693-flash, but the
related led driver has not been yet merged and there are no
other drivers depending on it.

Best Regards,
Jacek Anaszewski

2014-12-09 10:02:35

by Lee Jones

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 03/19] mfd: max77693: Modify flash cell name identifiers

On Tue, 09 Dec 2014, Jacek Anaszewski wrote:

> On 12/09/2014 09:52 AM, Lee Jones wrote:
> >On Wed, 03 Dec 2014, Jacek Anaszewski wrote:
> >
> >>Change flash cell identifiers from max77693-flash to max77693-led
> >>to avoid confusion with NOR/NAND Flash.
> >>
> >>Signed-off-by: Jacek Anaszewski <[email protected]>
> >>Acked-by: Kyungmin Park <[email protected]>
> >>Cc: Chanwoo Choi <[email protected]>
> >>Cc: Lee Jones <[email protected]>
> >>---
> >> drivers/mfd/max77693.c | 4 ++--
> >> 1 file changed, 2 insertions(+), 2 deletions(-)
> >>
> >>diff --git a/drivers/mfd/max77693.c b/drivers/mfd/max77693.c
> >>index a159593..cb14afa 100644
> >>--- a/drivers/mfd/max77693.c
> >>+++ b/drivers/mfd/max77693.c
> >>@@ -53,8 +53,8 @@ static const struct mfd_cell max77693_devs[] = {
> >> .of_compatible = "maxim,max77693-haptic",
> >> },
> >> {
> >>- .name = "max77693-flash",
> >>- .of_compatible = "maxim,max77693-flash",
> >>+ .name = "max77693-led",
> >>+ .of_compatible = "maxim,max77693-led",
> >
> >This is fine by me, so long as you've been through the usual
> >deprecation procedures or this platform is still WiP.
>
> It was me who added of_compatible for max77693-flash, but the
> related led driver has not been yet merged and there are no
> other drivers depending on it.

Very well.

For my own reference:

Acked-by: Lee Jones <[email protected]>

--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

2014-12-09 10:04:23

by Lee Jones

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 04/19] mfd: max77693: adjust max77693_led_platform_data

On Tue, 09 Dec 2014, Jacek Anaszewski wrote:

> On 12/09/2014 09:50 AM, Lee Jones wrote:
> >On Wed, 03 Dec 2014, Jacek Anaszewski wrote:
> >
> >>Add "label" array for Device Tree strings with the name of a LED device
> >>and make flash_timeout a two element array, for caching the sub-led
> >>related flash timeout. Added is also an array for caching pointers to the
> >>sub-nodes representing sub-leds.
> >>
> >>Signed-off-by: Jacek Anaszewski <[email protected]>
> >>Acked-by: Kyungmin Park <[email protected]>
> >>Cc: Chanwoo Choi <[email protected]>
> >>Cc: Lee Jones <[email protected]>
> >>---
> >> include/linux/mfd/max77693.h | 4 +++-
> >> 1 file changed, 3 insertions(+), 1 deletion(-)
> >>
> >>diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
> >>index f0b6585..c80ee99 100644
> >>--- a/include/linux/mfd/max77693.h
> >>+++ b/include/linux/mfd/max77693.h
> >>@@ -88,16 +88,18 @@ enum max77693_led_boost_mode {
> >> };
> >>
> >> struct max77693_led_platform_data {
> >>+ const char *label[2];
> >> u32 fleds[2];
> >> u32 iout_torch[2];for_each_available_child_of_node
> >> u32 iout_flash[2];
> >> u32 trigger[2];
> >> u32 trigger_type[2];
> >>+ u32 flash_timeout[2];
> >> u32 num_leds;
> >> u32 boost_mode;
> >>- u32 flash_timeout;
> >> u32 boost_vout;
> >> u32 low_vsys;
> >>+ struct device_node *sub_nodes[2];
> >
> >I haven't seen anyone do this before. Why can't you use the provided
> >OF functions to traverse through your tree?
>
> I use for_each_available_child_of_node when parsing DT node, but I
> need to cache the pointer to sub-node to be able to use it later
> when it needs to be passed to V4L2 sub-device which is then
> asynchronously matched by the phandle to sub-node.
>
> If it is not well seen to cache it in the platform data then
> I will find different way to accomplish this.

I haven't seen the end-driver for this, but why can't you use that
device's of_node pointer?

--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

2014-12-09 10:25:42

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 04/19] mfd: max77693: adjust max77693_led_platform_data

On 12/09/2014 11:04 AM, Lee Jones wrote:
> On Tue, 09 Dec 2014, Jacek Anaszewski wrote:
>
>> On 12/09/2014 09:50 AM, Lee Jones wrote:
>>> On Wed, 03 Dec 2014, Jacek Anaszewski wrote:
>>>
>>>> Add "label" array for Device Tree strings with the name of a LED device
>>>> and make flash_timeout a two element array, for caching the sub-led
>>>> related flash timeout. Added is also an array for caching pointers to the
>>>> sub-nodes representing sub-leds.
>>>>
>>>> Signed-off-by: Jacek Anaszewski <[email protected]>
>>>> Acked-by: Kyungmin Park <[email protected]>
>>>> Cc: Chanwoo Choi <[email protected]>
>>>> Cc: Lee Jones <[email protected]>
>>>> ---
>>>> include/linux/mfd/max77693.h | 4 +++-
>>>> 1 file changed, 3 insertions(+), 1 deletion(-)
>>>>
>>>> diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
>>>> index f0b6585..c80ee99 100644
>>>> --- a/include/linux/mfd/max77693.h
>>>> +++ b/include/linux/mfd/max77693.h
>>>> @@ -88,16 +88,18 @@ enum max77693_led_boost_mode {
>>>> };
>>>>
>>>> struct max77693_led_platform_data {
>>>> + const char *label[2];
>>>> u32 fleds[2];
>>>> u32 iout_torch[2];for_each_available_child_of_node
>>>> u32 iout_flash[2];
>>>> u32 trigger[2];
>>>> u32 trigger_type[2];
>>>> + u32 flash_timeout[2];
>>>> u32 num_leds;
>>>> u32 boost_mode;
>>>> - u32 flash_timeout;
>>>> u32 boost_vout;
>>>> u32 low_vsys;
>>>> + struct device_node *sub_nodes[2];
>>>
>>> I haven't seen anyone do this before. Why can't you use the provided
>>> OF functions to traverse through your tree?
>>
>> I use for_each_available_child_of_node when parsing DT node, but I
>> need to cache the pointer to sub-node to be able to use it later
>> when it needs to be passed to V4L2 sub-device which is then
>> asynchronously matched by the phandle to sub-node.
>>
>> If it is not well seen to cache it in the platform data then
>> I will find different way to accomplish this.
>
> I haven't seen the end-driver for this, but why can't you use that
> device's of_node pointer?

Maybe it is indeed a good idea. I could pass the of_node pointer
and the sub-led identifier to the V4L2 sub-device and there look
for the sub-node containing relevant identifier. The downside
would be only that for_each_available_child_of_node would
have to be called twice - in the led driver and in the V4L2 sub-device.
I think that we can live with it.

Thanks for the hint.

Best Regards,
Jacek Anaszewski

2014-12-09 12:37:00

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 01/19] leds: Add LED Flash class extension to the LED subsystem

Hi Jacek,

On Thu, Dec 04, 2014 at 10:29:10AM +0100, Jacek Anaszewski wrote:
...
> >>+static struct attribute *led_flash_strobe_attrs[] = {
> >>+ &dev_attr_flash_strobe.attr,
> >>+ NULL,
> >>+};
> >>+
> >>+static struct attribute *led_flash_timeout_attrs[] = {
> >>+ &dev_attr_flash_timeout.attr,
> >>+ &dev_attr_max_flash_timeout.attr,
> >>+ NULL,
> >>+};
> >>+
> >>+static struct attribute *led_flash_brightness_attrs[] = {
> >>+ &dev_attr_flash_brightness.attr,
> >>+ &dev_attr_max_flash_brightness.attr,
> >>+ NULL,
> >>+};
> >>+
> >>+static struct attribute *led_flash_fault_attrs[] = {
> >>+ &dev_attr_flash_fault.attr,
> >>+ NULL,
> >>+};
> >>+
> >>+static struct attribute *led_flash_sync_strobe_attrs[] = {
> >>+ &dev_attr_flash_sync_strobe.attr,
> >>+ NULL,
> >>+};
> >>+
> >>+static const struct attribute_group led_flash_strobe_group = {
> >>+ .attrs = led_flash_strobe_attrs,
> >>+};
> >>+
> >>+static const struct attribute_group led_flash_timeout_group = {
> >>+ .attrs = led_flash_timeout_attrs,
> >>+};
> >>+
> >>+static const struct attribute_group led_flash_brightness_group = {
> >>+ .attrs = led_flash_brightness_attrs,
> >>+};
> >>+
> >>+static const struct attribute_group led_flash_fault_group = {
> >>+ .attrs = led_flash_fault_attrs,
> >>+};
> >>+
> >>+static const struct attribute_group led_flash_sync_strobe_group = {
> >>+ .attrs = led_flash_sync_strobe_attrs,
> >>+};
> >>+
> >>+static const struct attribute_group *flash_groups[] = {
> >>+ &led_flash_strobe_group,
> >>+ NULL,
> >>+ NULL,
> >>+ NULL,
> >>+ NULL,
> >>+ NULL,
> >>+ NULL
> >>+};
> >>+
> >>+static void led_flash_resume(struct led_classdev *led_cdev)
> >>+{
> >>+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> >>+
> >>+ call_flash_op(flash, flash_brightness_set, flash->brightness.val);
> >>+ call_flash_op(flash, timeout_set, flash->timeout.val);
> >>+}
> >>+
> >>+static void led_flash_init_sysfs_groups(struct led_classdev_flash *flash)
> >>+{
> >>+ struct led_classdev *led_cdev = &flash->led_cdev;
> >>+ const struct led_flash_ops *ops = flash->ops;
> >>+ int num_sysfs_groups = 1;
> >>+
> >>+ if (ops->flash_brightness_set)
> >>+ flash_groups[num_sysfs_groups++] = &led_flash_brightness_group;
> >>+
> >>+ if (ops->timeout_set)
> >>+ flash_groups[num_sysfs_groups++] = &led_flash_timeout_group;
> >>+
> >>+ if (ops->fault_get)
> >>+ flash_groups[num_sysfs_groups++] = &led_flash_fault_group;
> >>+
> >>+ if (led_cdev->flags & LED_DEV_CAP_COMPOUND)
> >>+ flash_groups[num_sysfs_groups++] = &led_flash_sync_strobe_group;
> >>+
> >>+ led_cdev->groups = flash_groups;
> >
> >Shouldn't you have groups local to the device instead? If you register
> >another flash device bad things will happen if the ops the device supports
> >are different.
>
> The groups are local to the device. A LED class device is registered
> with device_create_with_groups called from led_classdev_register
> function. It is passed led_cdev->groups in the fifth argument.

The groups pointer will be stored in struct device. If you have another
driver using different groups, it will affect the groups for all flash
devices that use the same groups pointer. I'm not sure what exactly would
follow from that but I'd rather not change them once the device is created.

> >>+}
> >>+
> >>+int led_classdev_flash_register(struct device *parent,
> >>+ struct led_classdev_flash *flash)
> >>+{
> >>+ struct led_classdev *led_cdev;
> >>+ const struct led_flash_ops *ops;
> >>+ int ret;
> >>+
> >>+ if (!flash)
> >
> >Do you have a use case for this?
>
> This is just a guard against NULL pointer dereference. Maybe it is
> indeed redundant, as the driver developer can easily check its
> origin during implementation.

Fine for me.

--
Regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-09 12:39:18

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 02/19] Documentation: leds: Add description of LED Flash class extension

Hi Jacek,

On Thu, Dec 04, 2014 at 10:42:55AM +0100, Jacek Anaszewski wrote:
> Hi Sakari,
>
> Thanks for the review.
>
> On 12/03/2014 06:08 PM, Sakari Ailus wrote:
> >Hi Jacek,
> >
> >On Wed, Dec 03, 2014 at 05:06:37PM +0100, Jacek Anaszewski wrote:
> >>The documentation being added contains overall description of the
> >>LED Flash Class and the related sysfs attributes.
> >>
> >>Signed-off-by: Jacek Anaszewski <[email protected]>
> >>Acked-by: Kyungmin Park <[email protected]>
> >>Cc: Bryan Wu <[email protected]>
> >>Cc: Richard Purdie <[email protected]>
> >>---
> >> Documentation/leds/leds-class-flash.txt | 50 +++++++++++++++++++++++++++++++
> >> 1 file changed, 50 insertions(+)
> >> create mode 100644 Documentation/leds/leds-class-flash.txt
> >>
> >>diff --git a/Documentation/leds/leds-class-flash.txt b/Documentation/leds/leds-class-flash.txt
> >>new file mode 100644
> >>index 0000000..82e58b1
> >>--- /dev/null
> >>+++ b/Documentation/leds/leds-class-flash.txt
> >>@@ -0,0 +1,50 @@
> >>+
> >>+Flash LED handling under Linux
> >>+==============================
> >>+
> >>+Some LED devices support two modes - torch and flash. The modes are
> >>+supported by the LED class (see Documentation/leds/leds-class.txt)
> >>+and LED Flash class respectively.
> >>+
> >>+In order to enable support for flash LEDs CONFIG_LEDS_CLASS_FLASH symbol
> >>+must be defined in the kernel config. A flash LED driver must register
> >>+in the LED subsystem with led_classdev_flash_register to gain flash
> >>+capabilities.
> >>+
> >>+Following sysfs attributes are exposed for controlling flash led devices:
> >>+
> >>+ - flash_brightness - flash LED brightness in microamperes (RW)
> >>+ - max_flash_brightness - maximum available flash LED brightness (RO)
> >>+ - flash_timeout - flash strobe duration in microseconds (RW)
> >>+ - max_flash_timeout - maximum available flash strobe duration (RO)
> >>+ - flash_strobe - flash strobe state (RW)
> >>+ - flash_sync_strobe - one flash device can control more than one
> >>+ sub-led; when this atrribute is set to 1
> >
> >s/atrribute/attribute/
> >
> >>+ the flash led will be strobed synchronously
> >>+ with the other one controlled by the same
> >>+ device; flash timeout setting is inherited
> >>+ from the led being strobed explicitly and
> >>+ flash brightness setting of a sub-led's
> >>+ being synchronized is used (RW)
> >
> >The flash brightness shouldn't be determined by the strobed LED. If this is
> >a property of the hardware, then be it, but in general no, it it shouldn't
> >be an interface requirement. I think this should just say that the strobe is
> >synchronised.
>
> I intended this to sound exactly as you laid it out above, but maybe it
> is obscure English. "and flash brightness setting of a sub-led >>>being
> synchronized<<< is used" - from my point of view the led being
> synchronized is the one that isn't strobed explicitly. But I'm ok with
> confining ourselves only to saying that strobe is synchronized.

Agreed.

> >How does the user btw. figure out which flash LEDs may be strobed
> >synchronously using the LED flash interface?
>
> The flash_sync_strobe argument is absent if synchronized strobe
> is not available for a LED. The driver defines this by setting
> newly added LED_DEV_CAP_COMPOUND flag.

I meant that how does the user figure out which LEDs may be strobed
synchronously, together. Say, if you have two of these chips and four LEDs,
then how does it work? :-)

--
Kind regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-09 12:56:53

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 01/19] leds: Add LED Flash class extension to the LED subsystem

Hi Sakari,

On 12/09/2014 01:36 PM, Sakari Ailus wrote:
> Hi Jacek,
>
> On Thu, Dec 04, 2014 at 10:29:10AM +0100, Jacek Anaszewski wrote:
> ...
>>>> +static struct attribute *led_flash_strobe_attrs[] = {
>>>> + &dev_attr_flash_strobe.attr,
>>>> + NULL,
>>>> +};
>>>> +
>>>> +static struct attribute *led_flash_timeout_attrs[] = {
>>>> + &dev_attr_flash_timeout.attr,
>>>> + &dev_attr_max_flash_timeout.attr,
>>>> + NULL,
>>>> +};
>>>> +
>>>> +static struct attribute *led_flash_brightness_attrs[] = {
>>>> + &dev_attr_flash_brightness.attr,
>>>> + &dev_attr_max_flash_brightness.attr,
>>>> + NULL,
>>>> +};
>>>> +
>>>> +static struct attribute *led_flash_fault_attrs[] = {
>>>> + &dev_attr_flash_fault.attr,
>>>> + NULL,
>>>> +};
>>>> +
>>>> +static struct attribute *led_flash_sync_strobe_attrs[] = {
>>>> + &dev_attr_flash_sync_strobe.attr,
>>>> + NULL,
>>>> +};
>>>> +
>>>> +static const struct attribute_group led_flash_strobe_group = {
>>>> + .attrs = led_flash_strobe_attrs,
>>>> +};
>>>> +
>>>> +static const struct attribute_group led_flash_timeout_group = {
>>>> + .attrs = led_flash_timeout_attrs,
>>>> +};
>>>> +
>>>> +static const struct attribute_group led_flash_brightness_group = {
>>>> + .attrs = led_flash_brightness_attrs,
>>>> +};
>>>> +
>>>> +static const struct attribute_group led_flash_fault_group = {
>>>> + .attrs = led_flash_fault_attrs,
>>>> +};
>>>> +
>>>> +static const struct attribute_group led_flash_sync_strobe_group = {
>>>> + .attrs = led_flash_sync_strobe_attrs,
>>>> +};
>>>> +
>>>> +static const struct attribute_group *flash_groups[] = {
>>>> + &led_flash_strobe_group,
>>>> + NULL,
>>>> + NULL,
>>>> + NULL,
>>>> + NULL,
>>>> + NULL,
>>>> + NULL
>>>> +};
>>>> +
>>>> +static void led_flash_resume(struct led_classdev *led_cdev)
>>>> +{
>>>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>>>> +
>>>> + call_flash_op(flash, flash_brightness_set, flash->brightness.val);
>>>> + call_flash_op(flash, timeout_set, flash->timeout.val);
>>>> +}
>>>> +
>>>> +static void led_flash_init_sysfs_groups(struct led_classdev_flash *flash)
>>>> +{
>>>> + struct led_classdev *led_cdev = &flash->led_cdev;
>>>> + const struct led_flash_ops *ops = flash->ops;
>>>> + int num_sysfs_groups = 1;
>>>> +
>>>> + if (ops->flash_brightness_set)
>>>> + flash_groups[num_sysfs_groups++] = &led_flash_brightness_group;
>>>> +
>>>> + if (ops->timeout_set)
>>>> + flash_groups[num_sysfs_groups++] = &led_flash_timeout_group;
>>>> +
>>>> + if (ops->fault_get)
>>>> + flash_groups[num_sysfs_groups++] = &led_flash_fault_group;
>>>> +
>>>> + if (led_cdev->flags & LED_DEV_CAP_COMPOUND)
>>>> + flash_groups[num_sysfs_groups++] = &led_flash_sync_strobe_group;
>>>> +
>>>> + led_cdev->groups = flash_groups;
>>>
>>> Shouldn't you have groups local to the device instead? If you register
>>> another flash device bad things will happen if the ops the device supports
>>> are different.
>>
>> The groups are local to the device. A LED class device is registered
>> with device_create_with_groups called from led_classdev_register
>> function. It is passed led_cdev->groups in the fifth argument.
>
> The groups pointer will be stored in struct device. If you have another
> driver using different groups, it will affect the groups for all flash
> devices that use the same groups pointer. I'm not sure what exactly would
> follow from that but I'd rather not change them once the device is created.

I had to take another look at this to understand the problem.
I think that the best option will be making flash_groups array
a member of struct led_classdev_flash.

>>>> +}
>>>> +
>>>> +int led_classdev_flash_register(struct device *parent,
>>>> + struct led_classdev_flash *flash)
>>>> +{
>>>> + struct led_classdev *led_cdev;
>>>> + const struct led_flash_ops *ops;
>>>> + int ret;
>>>> +
>>>> + if (!flash)
>>>
>>> Do you have a use case for this?
>>
>> This is just a guard against NULL pointer dereference. Maybe it is
>> indeed redundant, as the driver developer can easily check its
>> origin during implementation.
>
> Fine for me.

Fine regarding my explanation or you agree that it is redundant?

Best Regards,
Jacek Anaszewski

2014-12-09 13:14:20

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 02/19] Documentation: leds: Add description of LED Flash class extension

Hi Sakari,

On 12/09/2014 01:38 PM, Sakari Ailus wrote:

[...]

>>> How does the user btw. figure out which flash LEDs may be strobed
>>> synchronously using the LED flash interface?
>>
>> The flash_sync_strobe argument is absent if synchronized strobe
>> is not available for a LED. The driver defines this by setting
>> newly added LED_DEV_CAP_COMPOUND flag.
>
> I meant that how does the user figure out which LEDs may be strobed
> synchronously, together. Say, if you have two of these chips and four LEDs,
> then how does it work? :-)
>

User can figure it out by checking the existence of the
flash_sync_strobe attribute. Sub-leds can by synchronized only
when are driven by common chip. It is assumed that sub-leds of
one chip will have common segment in their name, defined in
DT 'label' property. Maybe we should enforce it by adding another
property to the leds/common.txt DT binding, e.g. 'device-prefix'?

Best Regards,
Jacek Anaszewski

2014-12-09 13:20:49

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 05/19] leds: Add support for max77693 mfd flash cell

Hi Jacek,

On Thu, Dec 04, 2014 at 12:06:59PM +0100, Jacek Anaszewski wrote:
> Hi Sakari,
>
> Thanks for the review.

You're welcome! :-)

> On 12/04/2014 10:39 AM, Sakari Ailus wrote:
> >Hi Jacek,
> >
> >On Wed, Dec 03, 2014 at 05:06:40PM +0100, Jacek Anaszewski wrote:
> >>This patch adds led-flash support to Maxim max77693 chipset.
> >>A device can be exposed to user space through LED subsystem
> >>sysfs interface. Device supports up to two leds which can
> >>work in flash and torch mode. The leds can be triggered
> >>externally or by software.
> >>
> >>Signed-off-by: Jacek Anaszewski <[email protected]>
> >>Signed-off-by: Andrzej Hajda <[email protected]>
> >>Acked-by: Kyungmin Park <[email protected]>
> >>Cc: Bryan Wu <[email protected]>
> >>Cc: Richard Purdie <[email protected]>
> >>Cc: Lee Jones <[email protected]>
> >>Cc: Chanwoo Choi <[email protected]>
> >>---
> >> drivers/leds/Kconfig | 10 +
> >> drivers/leds/Makefile | 1 +
> >> drivers/leds/leds-max77693.c | 1023 ++++++++++++++++++++++++++++++++++++++++++
> >> 3 files changed, 1034 insertions(+)
> >> create mode 100644 drivers/leds/leds-max77693.c
> >>
> >>diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> >>index fa8021e..2e66d55 100644
> >>--- a/drivers/leds/Kconfig
> >>+++ b/drivers/leds/Kconfig
> >>@@ -463,6 +463,16 @@ config LEDS_TCA6507
> >> LED driver chips accessed via the I2C bus.
> >> Driver support brightness control and hardware-assisted blinking.
> >>
> >>+config LEDS_MAX77693
> >>+ tristate "LED support for MAX77693 Flash"
> >>+ depends on LEDS_CLASS_FLASH
> >>+ depends on MFD_MAX77693
> >>+ depends on OF
> >>+ help
> >>+ This option enables support for the flash part of the MAX77693
> >>+ multifunction device. It has build in control for two leds in flash
> >>+ and torch mode.
> >>+
> >> config LEDS_MAX8997
> >> tristate "LED support for MAX8997 PMIC"
> >> depends on LEDS_CLASS && MFD_MAX8997
> >>diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> >>index cbba921..57ca62b 100644
> >>--- a/drivers/leds/Makefile
> >>+++ b/drivers/leds/Makefile
> >>@@ -52,6 +52,7 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
> >> obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
> >> obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
> >> obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
> >>+obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
> >> obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
> >> obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
> >> obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
> >>diff --git a/drivers/leds/leds-max77693.c b/drivers/leds/leds-max77693.c
> >>new file mode 100644
> >>index 0000000..67a2f8f
> >>--- /dev/null
> >>+++ b/drivers/leds/leds-max77693.c
> >>@@ -0,0 +1,1023 @@
> >>+/*
> >>+ * LED Flash class driver for the flash cell of max77693 mfd.
> >>+ *
> >>+ * Copyright (C) 2014, Samsung Electronics Co., Ltd.
> >>+ *
> >>+ * Authors: Jacek Anaszewski <[email protected]>
> >>+ * Andrzej Hajda <[email protected]>
> >>+ *
> >>+ * 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 <asm/div64.h>
> >>+#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>
> >>+
> >>+#define MODE_OFF 0
> >>+#define MODE_FLASH1 (1 << 0)
> >>+#define MODE_FLASH2 (1 << 1)
> >>+#define MODE_TORCH1 (1 << 2)
> >>+#define MODE_TORCH2 (1 << 3)
> >>+#define MODE_FLASH_EXTERNAL1 (1 << 4)
> >>+#define MODE_FLASH_EXTERNAL2 (1 << 5)
> >
> >You could do this based on an argument (led number). E.g.
> >
> >#define MODE_FLASH_EXTERNAL(a) (1 << (4 + a))
>
> OK.
>
> >>+#define MODE_FLASH (MODE_FLASH1 | MODE_FLASH2 | \
> >>+ MODE_FLASH_EXTERNAL1 | MODE_FLASH_EXTERNAL2)
> >>+
> >>+#define FLED1_IOUT (1 << 0)
> >>+#define FLED2_IOUT (1 << 1)
> >>+
> >>+enum {
> >>+ FLED1,
> >>+ FLED2
> >>+};
> >>+
> >>+enum {
> >>+ FLASH,
> >>+ TORCH
> >>+};
> >>+
> >>+struct max77693_sub_led {

This could then be renamed as "max77693_led"; up to you.

> >>+ struct led_classdev_flash ldev;
> >>+ struct work_struct work_brightness_set;
> >>+
> >>+ unsigned int torch_brightness;
> >>+ unsigned int flash_timeout;
> >>+};
> >>+
> >>+struct max77693_led {
> >
> >As this does not refer to a device, how about struct max77693_device, for
> >instance?
>
> OK.
>
> >>+ struct regmap *regmap;
> >>+ struct platform_device *pdev;
> >>+ struct max77693_led_platform_data *pdata;
> >>+ struct mutex lock;
> >>+
> >>+ struct max77693_sub_led sub_leds[2];
> >>+
> >>+ unsigned int current_flash_timeout;
> >>+ unsigned int mode_flags;
> >>+ u8 torch_iout_reg;
> >>+ bool iout_joint;
> >>+ int strobing_sub_led_id;
> >>+};
> >>+
> >>+struct max77693_led_settings {
> >>+ struct led_flash_setting torch_brightness;
> >>+ struct led_flash_setting flash_brightness;
> >>+ struct led_flash_setting flash_timeout;
> >>+};
> >>+
> >>+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_led *ldev1_to_led(
> >>+ struct led_classdev_flash *ldev)
> >>+{
> >>+ struct max77693_sub_led *sub_led = container_of(ldev,
> >>+ struct max77693_sub_led,
> >>+ ldev);
> >>+ return container_of(sub_led, struct max77693_led, sub_leds[0]);
> >
> >You could have a common macro to find the flash controller struct if you add
> >the LED number to struct max77693_sub_led.
> >
> >>+}
> >>+
> >>+static inline struct max77693_led *ldev2_to_led(
> >>+ struct led_classdev_flash *ldev)
> >>+{
> >>+ struct max77693_sub_led *sub_led = container_of(ldev,
> >>+ struct max77693_sub_led,
> >>+ ldev);
> >>+ return container_of(sub_led, struct max77693_led, sub_leds[1]);
> >>+}
> >>+
> >>+static u8 max77693_led_vsys_to_reg(u32 mv)
> >>+{
> >>+ return ((mv - MAX_FLASH1_VSYS_MIN) / MAX_FLASH1_VSYS_STEP) << 2;
> >>+}
> >>+
> >>+static u8 max77693_led_vout_to_reg(u32 mv)
> >>+{
> >>+ return (mv - FLASH_VOUT_MIN) / FLASH_VOUT_STEP + FLASH_VOUT_RMIN;
> >>+}
> >>+
> >>+/* split composite current @i into two @iout according to @imax weights */
> >
> >What do you intend to do in the oint iout mode? A single LED connected to
> >iout pins which are soldered together?
>
> Exactly that what is written in the comment - split the current into
> both outputs.

I think we discussed this on #v4l --- was it so that both outputs, if
they're connected, should have the same current if there's a single LED
connected to them?

...

> >>+static int max77693_led_parse_dt(struct max77693_led *led,
> >>+ struct device_node *node)
> >>+{
> >>+ struct max77693_led_platform_data *p = led->pdata;
> >>+ struct device *dev = &led->pdev->dev;
> >>+ struct device_node *child_node;
> >>+ u32 fled_id;
> >>+ int ret;
> >>+
> >>+ of_property_read_u32_array(node, "maxim,fleds", p->fleds, 2);
> >>+ of_property_read_u32_array(node, "maxim,trigger", p->trigger, 2);
> >>+ of_property_read_u32_array(node, "maxim,trigger-type", p->trigger_type,
> >>+ 2);
> >>+ of_property_read_u32(node, "maxim,boost-mode", &p->boost_mode);
> >>+ of_property_read_u32(node, "maxim,boost-vout", &p->boost_vout);
> >>+ of_property_read_u32(node, "maxim,vsys-min", &p->low_vsys);
> >>+
> >>+ for_each_available_child_of_node(node, child_node) {
> >>+ ret = of_property_read_u32(child_node, "maxim,fled_id",
> >>+ &fled_id);
> >>+ if (ret < 0) {
> >>+ dev_err(dev, "Error reading \"fled_id\" DT property\n");
> >>+ return ret;
> >>+ }
> >>+
> >>+ fled_id = clamp_val(fled_id, 1, 2);
> >
> >I think you should check fled_id is really correct, and not clamp it.
>
> Right.
>
> >>+ --fled_id;
> >>+
> >>+ p->sub_nodes[fled_id] = child_node;
> >
> >p->sub_nodes is not accessed anywhere else. Do you plan to use it for
> >something?
>
> It is passed to v4l2_flash_init. In this patch set I add the V4L2
> support in the separate patch. Probably this should be move there.

Ok. I'm ok to keep it here as well, up to you.

--
Regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-09 13:50:28

by Lee Jones

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 04/19] mfd: max77693: adjust max77693_led_platform_data

On Tue, 09 Dec 2014, Jacek Anaszewski wrote:

> On 12/09/2014 11:04 AM, Lee Jones wrote:
> >On Tue, 09 Dec 2014, Jacek Anaszewski wrote:
> >
> >>On 12/09/2014 09:50 AM, Lee Jones wrote:
> >>>On Wed, 03 Dec 2014, Jacek Anaszewski wrote:
> >>>
> >>>>Add "label" array for Device Tree strings with the name of a LED device
> >>>>and make flash_timeout a two element array, for caching the sub-led
> >>>>related flash timeout. Added is also an array for caching pointers to the
> >>>>sub-nodes representing sub-leds.
> >>>>
> >>>>Signed-off-by: Jacek Anaszewski <[email protected]>
> >>>>Acked-by: Kyungmin Park <[email protected]>
> >>>>Cc: Chanwoo Choi <[email protected]>
> >>>>Cc: Lee Jones <[email protected]>
> >>>>---
> >>>> include/linux/mfd/max77693.h | 4 +++-
> >>>> 1 file changed, 3 insertions(+), 1 deletion(-)
> >>>>
> >>>>diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
> >>>>index f0b6585..c80ee99 100644
> >>>>--- a/include/linux/mfd/max77693.h
> >>>>+++ b/include/linux/mfd/max77693.h
> >>>>@@ -88,16 +88,18 @@ enum max77693_led_boost_mode {
> >>>> };
> >>>>
> >>>> struct max77693_led_platform_data {
> >>>>+ const char *label[2];
> >>>> u32 fleds[2];
> >>>> u32 iout_torch[2];for_each_available_child_of_node
> >>>> u32 iout_flash[2];
> >>>> u32 trigger[2];
> >>>> u32 trigger_type[2];
> >>>>+ u32 flash_timeout[2];
> >>>> u32 num_leds;
> >>>> u32 boost_mode;
> >>>>- u32 flash_timeout;
> >>>> u32 boost_vout;
> >>>> u32 low_vsys;
> >>>>+ struct device_node *sub_nodes[2];
> >>>
> >>>I haven't seen anyone do this before. Why can't you use the provided
> >>>OF functions to traverse through your tree?
> >>
> >>I use for_each_available_child_of_node when parsing DT node, but I
> >>need to cache the pointer to sub-node to be able to use it later
> >>when it needs to be passed to V4L2 sub-device which is then
> >>asynchronously matched by the phandle to sub-node.
> >>
> >>If it is not well seen to cache it in the platform data then
> >>I will find different way to accomplish this.
> >
> >I haven't seen the end-driver for this, but why can't you use that
> >device's of_node pointer?
>
> Maybe it is indeed a good idea. I could pass the of_node pointer
> and the sub-led identifier to the V4L2 sub-device and there look
> for the sub-node containing relevant identifier. The downside
> would be only that for_each_available_child_of_node would
> have to be called twice - in the led driver and in the V4L2 sub-device.
> I think that we can live with it.

Are the LED and V4L2 drivers children of this MFD? If so, you can use
the of_compatible attribute in struct mfd_cell to populate the each
child's of_node dynamically i.e. the MFD core will do that for you.

--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

2014-12-09 14:02:13

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 04/19] mfd: max77693: adjust max77693_led_platform_data

On 12/09/2014 02:50 PM, Lee Jones wrote:
> On Tue, 09 Dec 2014, Jacek Anaszewski wrote:
>
>> On 12/09/2014 11:04 AM, Lee Jones wrote:
>>> On Tue, 09 Dec 2014, Jacek Anaszewski wrote:
>>>
>>>> On 12/09/2014 09:50 AM, Lee Jones wrote:
>>>>> On Wed, 03 Dec 2014, Jacek Anaszewski wrote:
>>>>>
>>>>>> Add "label" array for Device Tree strings with the name of a LED device
>>>>>> and make flash_timeout a two element array, for caching the sub-led
>>>>>> related flash timeout. Added is also an array for caching pointers to the
>>>>>> sub-nodes representing sub-leds.
>>>>>>
>>>>>> Signed-off-by: Jacek Anaszewski <[email protected]>
>>>>>> Acked-by: Kyungmin Park <[email protected]>
>>>>>> Cc: Chanwoo Choi <[email protected]>
>>>>>> Cc: Lee Jones <[email protected]>
>>>>>> ---
>>>>>> include/linux/mfd/max77693.h | 4 +++-
>>>>>> 1 file changed, 3 insertions(+), 1 deletion(-)
>>>>>>
>>>>>> diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
>>>>>> index f0b6585..c80ee99 100644
>>>>>> --- a/include/linux/mfd/max77693.h
>>>>>> +++ b/include/linux/mfd/max77693.h
>>>>>> @@ -88,16 +88,18 @@ enum max77693_led_boost_mode {
>>>>>> };
>>>>>>
>>>>>> struct max77693_led_platform_data {
>>>>>> + const char *label[2];
>>>>>> u32 fleds[2];
>>>>>> u32 iout_torch[2];for_each_available_child_of_node
>>>>>> u32 iout_flash[2];
>>>>>> u32 trigger[2];
>>>>>> u32 trigger_type[2];
>>>>>> + u32 flash_timeout[2];
>>>>>> u32 num_leds;
>>>>>> u32 boost_mode;
>>>>>> - u32 flash_timeout;
>>>>>> u32 boost_vout;
>>>>>> u32 low_vsys;
>>>>>> + struct device_node *sub_nodes[2];
>>>>>
>>>>> I haven't seen anyone do this before. Why can't you use the provided
>>>>> OF functions to traverse through your tree?
>>>>
>>>> I use for_each_available_child_of_node when parsing DT node, but I
>>>> need to cache the pointer to sub-node to be able to use it later
>>>> when it needs to be passed to V4L2 sub-device which is then
>>>> asynchronously matched by the phandle to sub-node.
>>>>
>>>> If it is not well seen to cache it in the platform data then
>>>> I will find different way to accomplish this.
>>>
>>> I haven't seen the end-driver for this, but why can't you use that
>>> device's of_node pointer?
>>
>> Maybe it is indeed a good idea. I could pass the of_node pointer
>> and the sub-led identifier to the V4L2 sub-device and there look
>> for the sub-node containing relevant identifier. The downside
>> would be only that for_each_available_child_of_node would
>> have to be called twice - in the led driver and in the V4L2 sub-device.
>> I think that we can live with it.
>
> Are the LED and V4L2 drivers children of this MFD? If so, you can use
> the of_compatible attribute in struct mfd_cell to populate the each
> child's of_node dynamically i.e. the MFD core will do that for you.
>

V4L2 driver wraps LED driver. This way the LED device can be
controlled with use of two interfaces - LED subsystem sysfs
and V4L2 Flash. This is the aim of the whole patch set.

I've thought it over again and it seems that I will need to cache
somewhere these sub_nodes pointers. They have to be easily accessible
for the V4L2 sub-device as it can be asynchronously registered
or unregistered within V4L2 media device. Sub-devices are matched
basing on the sub-node phandle.

Best Regards,
Jacek Anaszewski

2014-12-09 14:09:37

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 06/19] DT: Add documentation for the mfd Maxim max77693

Hi Jacek,

On Thu, Dec 04, 2014 at 12:40:48PM +0100, Jacek Anaszewski wrote:
> >>+ the flash/torch.
> >>+- maxim,trigger : Array of flags indicating which trigger can activate given led
> >>+ in order: fled1, fled2.
> >>+ Possible flag values (can be combined):
> >>+ MAX77693_LED_TRIG_FLASHEN - FLASHEN pin of the chip,
> >>+ MAX77693_LED_TRIG_TORCHEN - TORCHEN pin of the chip,
> >>+ MAX77693_LED_TRIG_SOFTWARE - software via I2C command.
> >
> >Is there a need to prevent strobing using a certain method? Just wondering.
>
> In some cases it could be convenient to prevent some options through
> device tree.

Do you have that need now?

If not, I'd propose to postpone this and add it only if there ever is one.

--
Kind regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-09 14:13:01

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 01/19] leds: Add LED Flash class extension to the LED subsystem

Hi Jacek,

On Tue, Dec 09, 2014 at 01:56:47PM +0100, Jacek Anaszewski wrote:
> Hi Sakari,
>
> On 12/09/2014 01:36 PM, Sakari Ailus wrote:
> >Hi Jacek,
> >
> >On Thu, Dec 04, 2014 at 10:29:10AM +0100, Jacek Anaszewski wrote:
> >...
> >>>>+static struct attribute *led_flash_strobe_attrs[] = {
> >>>>+ &dev_attr_flash_strobe.attr,
> >>>>+ NULL,
> >>>>+};
> >>>>+
> >>>>+static struct attribute *led_flash_timeout_attrs[] = {
> >>>>+ &dev_attr_flash_timeout.attr,
> >>>>+ &dev_attr_max_flash_timeout.attr,
> >>>>+ NULL,
> >>>>+};
> >>>>+
> >>>>+static struct attribute *led_flash_brightness_attrs[] = {
> >>>>+ &dev_attr_flash_brightness.attr,
> >>>>+ &dev_attr_max_flash_brightness.attr,
> >>>>+ NULL,
> >>>>+};
> >>>>+
> >>>>+static struct attribute *led_flash_fault_attrs[] = {
> >>>>+ &dev_attr_flash_fault.attr,
> >>>>+ NULL,
> >>>>+};
> >>>>+
> >>>>+static struct attribute *led_flash_sync_strobe_attrs[] = {
> >>>>+ &dev_attr_flash_sync_strobe.attr,
> >>>>+ NULL,
> >>>>+};
> >>>>+
> >>>>+static const struct attribute_group led_flash_strobe_group = {
> >>>>+ .attrs = led_flash_strobe_attrs,
> >>>>+};
> >>>>+
> >>>>+static const struct attribute_group led_flash_timeout_group = {
> >>>>+ .attrs = led_flash_timeout_attrs,
> >>>>+};
> >>>>+
> >>>>+static const struct attribute_group led_flash_brightness_group = {
> >>>>+ .attrs = led_flash_brightness_attrs,
> >>>>+};
> >>>>+
> >>>>+static const struct attribute_group led_flash_fault_group = {
> >>>>+ .attrs = led_flash_fault_attrs,
> >>>>+};
> >>>>+
> >>>>+static const struct attribute_group led_flash_sync_strobe_group = {
> >>>>+ .attrs = led_flash_sync_strobe_attrs,
> >>>>+};
> >>>>+
> >>>>+static const struct attribute_group *flash_groups[] = {
> >>>>+ &led_flash_strobe_group,
> >>>>+ NULL,
> >>>>+ NULL,
> >>>>+ NULL,
> >>>>+ NULL,
> >>>>+ NULL,
> >>>>+ NULL
> >>>>+};
> >>>>+
> >>>>+static void led_flash_resume(struct led_classdev *led_cdev)
> >>>>+{
> >>>>+ struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> >>>>+
> >>>>+ call_flash_op(flash, flash_brightness_set, flash->brightness.val);
> >>>>+ call_flash_op(flash, timeout_set, flash->timeout.val);
> >>>>+}
> >>>>+
> >>>>+static void led_flash_init_sysfs_groups(struct led_classdev_flash *flash)
> >>>>+{
> >>>>+ struct led_classdev *led_cdev = &flash->led_cdev;
> >>>>+ const struct led_flash_ops *ops = flash->ops;
> >>>>+ int num_sysfs_groups = 1;
> >>>>+
> >>>>+ if (ops->flash_brightness_set)
> >>>>+ flash_groups[num_sysfs_groups++] = &led_flash_brightness_group;
> >>>>+
> >>>>+ if (ops->timeout_set)
> >>>>+ flash_groups[num_sysfs_groups++] = &led_flash_timeout_group;
> >>>>+
> >>>>+ if (ops->fault_get)
> >>>>+ flash_groups[num_sysfs_groups++] = &led_flash_fault_group;
> >>>>+
> >>>>+ if (led_cdev->flags & LED_DEV_CAP_COMPOUND)
> >>>>+ flash_groups[num_sysfs_groups++] = &led_flash_sync_strobe_group;
> >>>>+
> >>>>+ led_cdev->groups = flash_groups;
> >>>
> >>>Shouldn't you have groups local to the device instead? If you register
> >>>another flash device bad things will happen if the ops the device supports
> >>>are different.
> >>
> >>The groups are local to the device. A LED class device is registered
> >>with device_create_with_groups called from led_classdev_register
> >>function. It is passed led_cdev->groups in the fifth argument.
> >
> >The groups pointer will be stored in struct device. If you have another
> >driver using different groups, it will affect the groups for all flash
> >devices that use the same groups pointer. I'm not sure what exactly would
> >follow from that but I'd rather not change them once the device is created.
>
> I had to take another look at this to understand the problem.
> I think that the best option will be making flash_groups array
> a member of struct led_classdev_flash.

Sounds good to me.

> >>>>+}
> >>>>+
> >>>>+int led_classdev_flash_register(struct device *parent,
> >>>>+ struct led_classdev_flash *flash)
> >>>>+{
> >>>>+ struct led_classdev *led_cdev;
> >>>>+ const struct led_flash_ops *ops;
> >>>>+ int ret;
> >>>>+
> >>>>+ if (!flash)
> >>>
> >>>Do you have a use case for this?
> >>
> >>This is just a guard against NULL pointer dereference. Maybe it is
> >>indeed redundant, as the driver developer can easily check its
> >>origin during implementation.
> >
> >Fine for me.
>
> Fine regarding my explanation or you agree that it is redundant?

:-) With the explanation. I might have removed it but I'm having it there as
well.

--
Regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-09 14:14:25

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 06/19] DT: Add documentation for the mfd Maxim max77693

Hi Sakari,

On 12/09/2014 03:09 PM, Sakari Ailus wrote:
> Hi Jacek,
>
> On Thu, Dec 04, 2014 at 12:40:48PM +0100, Jacek Anaszewski wrote:
>>>> + the flash/torch.
>>>> +- maxim,trigger : Array of flags indicating which trigger can activate given led
>>>> + in order: fled1, fled2.
>>>> + Possible flag values (can be combined):
>>>> + MAX77693_LED_TRIG_FLASHEN - FLASHEN pin of the chip,
>>>> + MAX77693_LED_TRIG_TORCHEN - TORCHEN pin of the chip,
>>>> + MAX77693_LED_TRIG_SOFTWARE - software via I2C command.
>>>
>>> Is there a need to prevent strobing using a certain method? Just wondering.
>>
>> In some cases it could be convenient to prevent some options through
>> device tree.
>
> Do you have that need now?
>
> If not, I'd propose to postpone this and add it only if there ever is one.
>

No, I don't. So let's postpone it.

Best Regards,
Jacek Anaszewski

2014-12-09 14:41:15

by Lee Jones

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 04/19] mfd: max77693: adjust max77693_led_platform_data

On Tue, 09 Dec 2014, Jacek Anaszewski wrote:

> On 12/09/2014 02:50 PM, Lee Jones wrote:
> >On Tue, 09 Dec 2014, Jacek Anaszewski wrote:
> >
> >>On 12/09/2014 11:04 AM, Lee Jones wrote:
> >>>On Tue, 09 Dec 2014, Jacek Anaszewski wrote:
> >>>
> >>>>On 12/09/2014 09:50 AM, Lee Jones wrote:
> >>>>>On Wed, 03 Dec 2014, Jacek Anaszewski wrote:
> >>>>>
> >>>>>>Add "label" array for Device Tree strings with the name of a LED device
> >>>>>>and make flash_timeout a two element array, for caching the sub-led
> >>>>>>related flash timeout. Added is also an array for caching pointers to the
> >>>>>>sub-nodes representing sub-leds.
> >>>>>>
> >>>>>>Signed-off-by: Jacek Anaszewski <[email protected]>
> >>>>>>Acked-by: Kyungmin Park <[email protected]>
> >>>>>>Cc: Chanwoo Choi <[email protected]>
> >>>>>>Cc: Lee Jones <[email protected]>
> >>>>>>---
> >>>>>> include/linux/mfd/max77693.h | 4 +++-
> >>>>>> 1 file changed, 3 insertions(+), 1 deletion(-)
> >>>>>>
> >>>>>>diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
> >>>>>>index f0b6585..c80ee99 100644
> >>>>>>--- a/include/linux/mfd/max77693.h
> >>>>>>+++ b/include/linux/mfd/max77693.h
> >>>>>>@@ -88,16 +88,18 @@ enum max77693_led_boost_mode {
> >>>>>> };
> >>>>>>
> >>>>>> struct max77693_led_platform_data {
> >>>>>>+ const char *label[2];
> >>>>>> u32 fleds[2];
> >>>>>> u32 iout_torch[2];for_each_available_child_of_node
> >>>>>> u32 iout_flash[2];
> >>>>>> u32 trigger[2];
> >>>>>> u32 trigger_type[2];
> >>>>>>+ u32 flash_timeout[2];
> >>>>>> u32 num_leds;
> >>>>>> u32 boost_mode;
> >>>>>>- u32 flash_timeout;
> >>>>>> u32 boost_vout;
> >>>>>> u32 low_vsys;
> >>>>>>+ struct device_node *sub_nodes[2];
> >>>>>
> >>>>>I haven't seen anyone do this before. Why can't you use the provided
> >>>>>OF functions to traverse through your tree?
> >>>>
> >>>>I use for_each_available_child_of_node when parsing DT node, but I
> >>>>need to cache the pointer to sub-node to be able to use it later
> >>>>when it needs to be passed to V4L2 sub-device which is then
> >>>>asynchronously matched by the phandle to sub-node.
> >>>>
> >>>>If it is not well seen to cache it in the platform data then
> >>>>I will find different way to accomplish this.
> >>>
> >>>I haven't seen the end-driver for this, but why can't you use that
> >>>device's of_node pointer?
> >>
> >>Maybe it is indeed a good idea. I could pass the of_node pointer
> >>and the sub-led identifier to the V4L2 sub-device and there look
> >>for the sub-node containing relevant identifier. The downside
> >>would be only that for_each_available_child_of_node would
> >>have to be called twice - in the led driver and in the V4L2 sub-device.
> >>I think that we can live with it.
> >
> >Are the LED and V4L2 drivers children of this MFD? If so, you can use
> >the of_compatible attribute in struct mfd_cell to populate the each
> >child's of_node dynamically i.e. the MFD core will do that for you.
> >
>
> V4L2 driver wraps LED driver. This way the LED device can be
> controlled with use of two interfaces - LED subsystem sysfs
> and V4L2 Flash. This is the aim of the whole patch set.
>
> I've thought it over again and it seems that I will need to cache
> somewhere these sub_nodes pointers. They have to be easily accessible
> for the V4L2 sub-device as it can be asynchronously registered
> or unregistered within V4L2 media device. Sub-devices are matched
> basing on the sub-node phandle.

Not quite getting this. Can you explain this in another way please?

--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

2014-12-09 15:08:37

by Sylwester Nawrocki

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 04/19] mfd: max77693: adjust max77693_led_platform_data

On 09/12/14 15:41, Lee Jones wrote:
>>>>>>>> struct max77693_led_platform_data {
>>>>>>>> > >>>>>>+ const char *label[2];
>>>>>>>> > >>>>>> u32 fleds[2];
>>>>>>>> > >>>>>> u32 iout_torch[2];for_each_available_child_of_node
>>>>>>>> > >>>>>> u32 iout_flash[2];
>>>>>>>> > >>>>>> u32 trigger[2];
>>>>>>>> > >>>>>> u32 trigger_type[2];
>>>>>>>> > >>>>>>+ u32 flash_timeout[2];
>>>>>>>> > >>>>>> u32 num_leds;
>>>>>>>> > >>>>>> u32 boost_mode;
>>>>>>>> > >>>>>>- u32 flash_timeout;
>>>>>>>> > >>>>>> u32 boost_vout;
>>>>>>>> > >>>>>> u32 low_vsys;
>>>>>>>> > >>>>>>+ struct device_node *sub_nodes[2];
>>>>>>> > >>>>>
>>>>>>> > >>>>>I haven't seen anyone do this before. Why can't you use the provided
>>>>>>> > >>>>>OF functions to traverse through your tree?
>>>>>> > >>>>
>>>>>> > >>>>I use for_each_available_child_of_node when parsing DT node, but I
>>>>>> > >>>>need to cache the pointer to sub-node to be able to use it later
>>>>>> > >>>>when it needs to be passed to V4L2 sub-device which is then
>>>>>> > >>>>asynchronously matched by the phandle to sub-node.
>>>>>> > >>>>
>>>>>> > >>>>If it is not well seen to cache it in the platform data then
>>>>>> > >>>>I will find different way to accomplish this.
>>>>> > >>>
>>>>> > >>>I haven't seen the end-driver for this, but why can't you use that
>>>>> > >>>device's of_node pointer?
>>>> > >>
>>>> > >>Maybe it is indeed a good idea. I could pass the of_node pointer
>>>> > >>and the sub-led identifier to the V4L2 sub-device and there look
>>>> > >>for the sub-node containing relevant identifier. The downside
>>>> > >>would be only that for_each_available_child_of_node would
>>>> > >>have to be called twice - in the led driver and in the V4L2 sub-device.
>>>> > >>I think that we can live with it.
>>> > >
>>> > >Are the LED and V4L2 drivers children of this MFD? If so, you can use
>>> > >the of_compatible attribute in struct mfd_cell to populate the each
>>> > >child's of_node dynamically i.e. the MFD core will do that for you.
>>> > >
>> >
>> > V4L2 driver wraps LED driver. This way the LED device can be
>> > controlled with use of two interfaces - LED subsystem sysfs
>> > and V4L2 Flash. This is the aim of the whole patch set.
>> >
>> > I've thought it over again and it seems that I will need to cache
>> > somewhere these sub_nodes pointers. They have to be easily accessible
>> > for the V4L2 sub-device as it can be asynchronously registered
>> > or unregistered within V4L2 media device. Sub-devices are matched
>> > basing on the sub-node phandle.
>
> Not quite getting this. Can you explain this in another way please?

Only the LED controller driver is a child the MFD. The LED controller
can contain multiple outputs with a physical LED attached to it. AFAICS
this binding is modelling each such an output as a the LED's controller
node child node.

I'm not sure though why storing the device node pointers is required,
rather than traversing OF tree when needed.
I guess we only need the list of the node pointer to populate struct
v4l2_async_subdev array for v4l2_async_notifier_register() call ?


--
Regards,
Sylwester

2014-12-10 10:02:15

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 06/19] DT: Add documentation for the mfd Maxim max77693

Hi Sakari,

On 12/04/2014 12:40 PM, Jacek Anaszewski wrote:

> On 12/04/2014 11:07 AM, Sakari Ailus wrote:
>> Hi Jacek,
>>
>> On Wed, Dec 03, 2014 at 05:06:41PM +0100, Jacek Anaszewski wrote:
>>> This patch adds device tree binding documentation for
>>> the flash cell of the Maxim max77693 multifunctional device.
>>>
>>> Signed-off-by: Jacek Anaszewski <[email protected]>
>>> Signed-off-by: Andrzej Hajda <[email protected]>
>>> Acked-by: Kyungmin Park <[email protected]>
>>> Cc: Lee Jones <[email protected]>
>>> Cc: Chanwoo Choi <[email protected]>
>>> Cc: Bryan Wu <[email protected]>
>>> Cc: Richard Purdie <[email protected]>
>>> Cc: Rob Herring <[email protected]>
>>> Cc: Pawel Moll <[email protected]>
>>> Cc: Mark Rutland <[email protected]>
>>> Cc: Ian Campbell <[email protected]>
>>> Cc: Kumar Gala <[email protected]>
>>> Cc: <[email protected]>
>>> ---
>>> Documentation/devicetree/bindings/mfd/max77693.txt | 89
>>> ++++++++++++++++++++
>>> 1 file changed, 89 insertions(+)
>>>
>>> diff --git a/Documentation/devicetree/bindings/mfd/max77693.txt
>>> b/Documentation/devicetree/bindings/mfd/max77693.txt
>>> index 01e9f30..25a6e78 100644
>>> --- a/Documentation/devicetree/bindings/mfd/max77693.txt
>>> +++ b/Documentation/devicetree/bindings/mfd/max77693.txt
>>> @@ -41,7 +41,66 @@ Optional properties:
>>> To get more informations, please refer to documentaion.
>>> [*] refer Documentation/devicetree/bindings/pwm/pwm.txt
>>>
>>> +- led : the LED submodule device node
>>> +
>>> +There are two led outputs available - fled1 and fled2. Each of them can
>>> +control a separate led or they can be connected together to double
>>> +the maximum current for a single connected led. One led is represented
>>> +by one child node.
>>> +
>>> +Required properties:
>>> +- compatible : Must be "maxim,max77693-led".
>>> +
>>> +Optional properties:
>>> +- maxim,fleds : Array of current outputs in order: fled1, fled2.
>>> + Note: both current outputs can be connected to a single led
>>> + Possible values:
>>> + MAX77693_LED_FLED_UNUSED - the output is left disconnected,
>>> + MAX77693_LED_FLED_USED - a diode is connected to the output.
>>
>> As you have a LED sub-nodes for each LED already, isn't this redundant?
>
> Well, it seems so :)

I agreed here recklessly. This property allows to describe the
situation when one LED is connected to both outputs. Single sub-node
can describe two type of designs: one LED connected to a single
output or one LED connected to both outputs. Therefore additional
property is needed to assess what is the actual case.

Best Regards,
Jacek Anaszewski

2014-12-10 10:51:00

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 06/19] DT: Add documentation for the mfd Maxim max77693

Hi Jacek,

On Wed, Dec 10, 2014 at 11:02:07AM +0100, Jacek Anaszewski wrote:
> Hi Sakari,
>
> On 12/04/2014 12:40 PM, Jacek Anaszewski wrote:
>
> >On 12/04/2014 11:07 AM, Sakari Ailus wrote:
> >>Hi Jacek,
> >>
> >>On Wed, Dec 03, 2014 at 05:06:41PM +0100, Jacek Anaszewski wrote:
> >>>This patch adds device tree binding documentation for
> >>>the flash cell of the Maxim max77693 multifunctional device.
> >>>
> >>>Signed-off-by: Jacek Anaszewski <[email protected]>
> >>>Signed-off-by: Andrzej Hajda <[email protected]>
> >>>Acked-by: Kyungmin Park <[email protected]>
> >>>Cc: Lee Jones <[email protected]>
> >>>Cc: Chanwoo Choi <[email protected]>
> >>>Cc: Bryan Wu <[email protected]>
> >>>Cc: Richard Purdie <[email protected]>
> >>>Cc: Rob Herring <[email protected]>
> >>>Cc: Pawel Moll <[email protected]>
> >>>Cc: Mark Rutland <[email protected]>
> >>>Cc: Ian Campbell <[email protected]>
> >>>Cc: Kumar Gala <[email protected]>
> >>>Cc: <[email protected]>
> >>>---
> >>> Documentation/devicetree/bindings/mfd/max77693.txt | 89
> >>>++++++++++++++++++++
> >>> 1 file changed, 89 insertions(+)
> >>>
> >>>diff --git a/Documentation/devicetree/bindings/mfd/max77693.txt
> >>>b/Documentation/devicetree/bindings/mfd/max77693.txt
> >>>index 01e9f30..25a6e78 100644
> >>>--- a/Documentation/devicetree/bindings/mfd/max77693.txt
> >>>+++ b/Documentation/devicetree/bindings/mfd/max77693.txt
> >>>@@ -41,7 +41,66 @@ Optional properties:
> >>> To get more informations, please refer to documentaion.
> >>> [*] refer Documentation/devicetree/bindings/pwm/pwm.txt
> >>>
> >>>+- led : the LED submodule device node
> >>>+
> >>>+There are two led outputs available - fled1 and fled2. Each of them can
> >>>+control a separate led or they can be connected together to double
> >>>+the maximum current for a single connected led. One led is represented
> >>>+by one child node.
> >>>+
> >>>+Required properties:
> >>>+- compatible : Must be "maxim,max77693-led".
> >>>+
> >>>+Optional properties:
> >>>+- maxim,fleds : Array of current outputs in order: fled1, fled2.
> >>>+ Note: both current outputs can be connected to a single led
> >>>+ Possible values:
> >>>+ MAX77693_LED_FLED_UNUSED - the output is left disconnected,
> >>>+ MAX77693_LED_FLED_USED - a diode is connected to the output.
> >>
> >>As you have a LED sub-nodes for each LED already, isn't this redundant?
> >
> >Well, it seems so :)
>
> I agreed here recklessly. This property allows to describe the

If this is reckless then we're doing very, very well. :-D

> situation when one LED is connected to both outputs. Single sub-node
> can describe two type of designs: one LED connected to a single
> output or one LED connected to both outputs. Therefore additional
> property is needed to assess what is the actual case.

Which output do you say such LED is connected then?

I wonder if the reg property could be made an array, so you could say the
LED is connected to this and that output.

The advantage would be that this still works even if you have three outputs.

--
Regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-10 10:59:47

by Sylwester Nawrocki

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 06/19] DT: Add documentation for the mfd Maxim max77693

On 10/12/14 11:02, Jacek Anaszewski wrote:
>>>> +Optional properties:
>>>> >>> +- maxim,fleds : Array of current outputs in order: fled1, fled2.

s/current outputs/LED current regulator outputs used/ ?

>>>> >>> + Note: both current outputs can be connected to a single led

s/led/LED ? And there seem to be other similar occurrences that would
need to be put in upper case.

>>>> >>> + Possible values:
>>>> >>> + MAX77693_LED_FLED_UNUSED - the output is left disconnected,
>>>> >>> + MAX77693_LED_FLED_USED - a diode is connected to the output.

As noted below, I would simply use 0/1 for these.

>>> >>
>>> >> As you have a LED sub-nodes for each LED already, isn't this redundant?
>> >
>> > Well, it seems so :)
>
> I agreed here recklessly. This property allows to describe the
> situation when one LED is connected to both outputs. Single sub-node
> can describe two type of designs: one LED connected to a single
> output or one LED connected to both outputs. Therefore additional
> property is needed to assess what is the actual case.

How about renaming "maxim,fleds" to "maxim,active-outputs" ?
And simply using 0 and 1 to indicate if one is used or not, rather
than defining macros for these true/false values ?

--
Regards,
Sylwester

2014-12-10 12:20:25

by Sylwester Nawrocki

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 06/19] DT: Add documentation for the mfd Maxim max77693

Hi,

On 04/12/14 17:12, Pavel Machek wrote:
>>>> +- maxim,boost-mode :
>>>> > >>+ In boost mode the device can produce up to 1.2A of total current
>>>> > >>+ on both outputs. The maximum current on each output is reduced
>>>> > >>+ to 625mA then. If there are two child led nodes defined then boost
>>>> > >>+ is enabled by default.
>>>> > >>+ Possible values:
>>>> > >>+ MAX77693_LED_BOOST_OFF - no boost,
>>>> > >>+ MAX77693_LED_BOOST_ADAPTIVE - adaptive mode,
>>>> > >>+ MAX77693_LED_BOOST_FIXED - fixed mode.
>>>> > >>+- maxim,boost-vout : Output voltage of the boost module in millivolts.
>>>> > >>+- maxim,vsys-min : Low input voltage level in millivolts. Flash is not fired
>>>> > >>+ if chip estimates that system voltage could drop below this level due
>>>> > >>+ to flash power consumption.
>>>> > >>+
>>>> > >>+Required properties of the LED child node:
>>>> > >>+- label : see Documentation/devicetree/bindings/leds/common.txt
>>>> > >>+- maxim,fled_id : Identifier of the fled output the led is connected to;
>>> > >
>>> > >I'm pretty sure this will be needed for about every chip that can drive
>>> > >multiple LEDs. Shouldn't it be documented in the generic documentation?
>> >
>> > OK.
>
> Well... "fled_id" is not exactly suitable name. On other busses, it
> would be "reg = <1>"?

I think we need to clarify what the LED device node subnodes really mean.
I thought initially they describe a physical current output of the LED
controller, but it turns out the subnode corresponds to a LED attached
to the LED controller. Since a LED can be connected to multiple outputs
of the LED controller I think 'reg' property doesn't make sense here.

Then presumably we should use a property in each subnode, telling which
LED controller outputs a LED is connected to?

For instance, if we assign numbers 0, 1 to FLED1, FLED2 outputs of
MAX77693 and there is just one LED connected to those outputs we would
have something like:

max77693: led {
compatible = "maxim,max77693-led";
...
led1 {
maxim,fled-sources = <0 1>;
...
};
};

Feel free to propose better name for the property, I guess we need to
avoid "maxim,current-sources" due to ambiguity of the word "current".

--
Regards,
Sylwester

2014-12-10 12:39:01

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 04/19] mfd: max77693: adjust max77693_led_platform_data

On 12/09/2014 03:41 PM, Lee Jones wrote:
> On Tue, 09 Dec 2014, Jacek Anaszewski wrote:
>
>> On 12/09/2014 02:50 PM, Lee Jones wrote:
>>> On Tue, 09 Dec 2014, Jacek Anaszewski wrote:
>>>
>>>> On 12/09/2014 11:04 AM, Lee Jones wrote:
>>>>> On Tue, 09 Dec 2014, Jacek Anaszewski wrote:
>>>>>
>>>>>> On 12/09/2014 09:50 AM, Lee Jones wrote:
>>>>>>> On Wed, 03 Dec 2014, Jacek Anaszewski wrote:
>>>>>>>
>>>>>>>> Add "label" array for Device Tree strings with the name of a LED device
>>>>>>>> and make flash_timeout a two element array, for caching the sub-led
>>>>>>>> related flash timeout. Added is also an array for caching pointers to the
>>>>>>>> sub-nodes representing sub-leds.
>>>>>>>>
>>>>>>>> Signed-off-by: Jacek Anaszewski <[email protected]>
>>>>>>>> Acked-by: Kyungmin Park <[email protected]>
>>>>>>>> Cc: Chanwoo Choi <[email protected]>
>>>>>>>> Cc: Lee Jones <[email protected]>
>>>>>>>> ---
>>>>>>>> include/linux/mfd/max77693.h | 4 +++-
>>>>>>>> 1 file changed, 3 insertions(+), 1 deletion(-)
>>>>>>>>
>>>>>>>> diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
>>>>>>>> index f0b6585..c80ee99 100644
>>>>>>>> --- a/include/linux/mfd/max77693.h
>>>>>>>> +++ b/include/linux/mfd/max77693.h
>>>>>>>> @@ -88,16 +88,18 @@ enum max77693_led_boost_mode {
>>>>>>>> };
>>>>>>>>
>>>>>>>> struct max77693_led_platform_data {
>>>>>>>> + const char *label[2];
>>>>>>>> u32 fleds[2];
>>>>>>>> u32 iout_torch[2];for_each_available_child_of_node
>>>>>>>> u32 iout_flash[2];
>>>>>>>> u32 trigger[2];
>>>>>>>> u32 trigger_type[2];
>>>>>>>> + u32 flash_timeout[2];
>>>>>>>> u32 num_leds;
>>>>>>>> u32 boost_mode;
>>>>>>>> - u32 flash_timeout;
>>>>>>>> u32 boost_vout;
>>>>>>>> u32 low_vsys;
>>>>>>>> + struct device_node *sub_nodes[2];
>>>>>>>
>>>>>>> I haven't seen anyone do this before. Why can't you use the provided
>>>>>>> OF functions to traverse through your tree?
>>>>>>
>>>>>> I use for_each_available_child_of_node when parsing DT node, but I
>>>>>> need to cache the pointer to sub-node to be able to use it later
>>>>>> when it needs to be passed to V4L2 sub-device which is then
>>>>>> asynchronously matched by the phandle to sub-node.
>>>>>>
>>>>>> If it is not well seen to cache it in the platform data then
>>>>>> I will find different way to accomplish this.
>>>>>
>>>>> I haven't seen the end-driver for this, but why can't you use that
>>>>> device's of_node pointer?
>>>>
>>>> Maybe it is indeed a good idea. I could pass the of_node pointer
>>>> and the sub-led identifier to the V4L2 sub-device and there look
>>>> for the sub-node containing relevant identifier. The downside
>>>> would be only that for_each_available_child_of_node would
>>>> have to be called twice - in the led driver and in the V4L2 sub-device.
>>>> I think that we can live with it.
>>>
>>> Are the LED and V4L2 drivers children of this MFD? If so, you can use
>>> the of_compatible attribute in struct mfd_cell to populate the each
>>> child's of_node dynamically i.e. the MFD core will do that for you.
>>>
>>
>> V4L2 driver wraps LED driver. This way the LED device can be
>> controlled with use of two interfaces - LED subsystem sysfs
>> and V4L2 Flash. This is the aim of the whole patch set.
>>
>> I've thought it over again and it seems that I will need to cache
>> somewhere these sub_nodes pointers. They have to be easily accessible
>> for the V4L2 sub-device as it can be asynchronously registered
>> or unregistered within V4L2 media device. Sub-devices are matched
>> basing on the sub-node phandle.
>
> Not quite getting this. Can you explain this in another way please?
>

It turned out that it is possible to store the sub-node pointer in the
struct device returned by device_create_with_groups, while creating LED
class device. Its of_node property is uninitialized.
Regardless of it - there will be next version of this patch.

Best Regards,
Jacek Anaszewski

2014-12-10 12:41:28

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 06/19] DT: Add documentation for the mfd Maxim max77693

On 12/10/2014 01:20 PM, Sylwester Nawrocki wrote:
> Hi,
>
> On 04/12/14 17:12, Pavel Machek wrote:
>>>>> +- maxim,boost-mode :
>>>>>>>> + In boost mode the device can produce up to 1.2A of total current
>>>>>>>> + on both outputs. The maximum current on each output is reduced
>>>>>>>> + to 625mA then. If there are two child led nodes defined then boost
>>>>>>>> + is enabled by default.
>>>>>>>> + Possible values:
>>>>>>>> + MAX77693_LED_BOOST_OFF - no boost,
>>>>>>>> + MAX77693_LED_BOOST_ADAPTIVE - adaptive mode,
>>>>>>>> + MAX77693_LED_BOOST_FIXED - fixed mode.
>>>>>>>> +- maxim,boost-vout : Output voltage of the boost module in millivolts.
>>>>>>>> +- maxim,vsys-min : Low input voltage level in millivolts. Flash is not fired
>>>>>>>> + if chip estimates that system voltage could drop below this level due
>>>>>>>> + to flash power consumption.
>>>>>>>> +
>>>>>>>> +Required properties of the LED child node:
>>>>>>>> +- label : see Documentation/devicetree/bindings/leds/common.txt
>>>>>>>> +- maxim,fled_id : Identifier of the fled output the led is connected to;
>>>>>>
>>>>>> I'm pretty sure this will be needed for about every chip that can drive
>>>>>> multiple LEDs. Shouldn't it be documented in the generic documentation?
>>>>
>>>> OK.
>>
>> Well... "fled_id" is not exactly suitable name. On other busses, it
>> would be "reg = <1>"?
>
> I think we need to clarify what the LED device node subnodes really mean.
> I thought initially they describe a physical current output of the LED
> controller, but it turns out the subnode corresponds to a LED attached
> to the LED controller. Since a LED can be connected to multiple outputs
> of the LED controller I think 'reg' property doesn't make sense here.
>
> Then presumably we should use a property in each subnode, telling which
> LED controller outputs a LED is connected to?
>
> For instance, if we assign numbers 0, 1 to FLED1, FLED2 outputs of
> MAX77693 and there is just one LED connected to those outputs we would
> have something like:
>
> max77693: led {
> compatible = "maxim,max77693-led";
> ...
> led1 {
> maxim,fled-sources = <0 1>;
> ...
> };
> };
>
> Feel free to propose better name for the property, I guess we need to
> avoid "maxim,current-sources" due to ambiguity of the word "current".

For me this sounds reasonable. Moreover we will avoid the need for
address-cells and size-cells properties in the parent node.

Best Regards,
Jacek Anaszewski

2014-12-10 13:48:24

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 19/19] leds: aat1290: add support for V4L2 Flash sub-device

Hi Jacek,

On Wed, Dec 03, 2014 at 05:06:54PM +0100, Jacek Anaszewski wrote:
> Add support for V4L2 Flash sub-device to the aat1290 LED Flash class
> driver. The support allows for V4L2 Flash sub-device to take the control
> of the LED Flash class device.
>
> Signed-off-by: Jacek Anaszewski <[email protected]>
> Acked-by: Kyungmin Park <[email protected]>
> Cc: Bryan Wu <[email protected]>
> Cc: Richard Purdie <[email protected]>
> Cc: Sakari Ailus <[email protected]>
> ---
> drivers/leds/leds-aat1290.c | 61 +++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 61 insertions(+)
>
> diff --git a/drivers/leds/leds-aat1290.c b/drivers/leds/leds-aat1290.c
> index 15d969b..81a8f48 100644
> --- a/drivers/leds/leds-aat1290.c
> +++ b/drivers/leds/leds-aat1290.c
> @@ -21,6 +21,7 @@
> #include <linux/gpio.h>
> #include <linux/of_gpio.h>
> #include <linux/of.h>
> +#include <media/v4l2-flash.h>
> #include <linux/workqueue.h>
>
> #define AAT1290_MOVIE_MODE_CURRENT_ADDR 17
> @@ -63,6 +64,7 @@ struct aat1290_led {
> struct mutex lock;
>
> struct led_classdev_flash ldev;
> + struct v4l2_flash *v4l2_flash;
>
> int flen_gpio;
> int en_set_gpio;
> @@ -280,11 +282,51 @@ static void aat1290_init_flash_settings(struct aat1290_led *led,
> setting->val = setting->max;
> }
>
> +#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
> +static void aat1290_init_v4l2_ctrl_config(struct aat1290_led_settings *s,
> + struct v4l2_flash_ctrl_config *config)
> +{
> + struct led_flash_setting *setting;
> + struct v4l2_ctrl_config *c;
> +
> + c = &config->intensity;
> + setting = &s->torch_brightness;
> + c->min = setting->min;
> + c->max = setting->max;
> + c->step = setting->step;

Hmm. How does the intensity get configured over the V4L2 controls? Based on
an earlier patch, the intensity control looks very much non-linear.

> + c->def = setting->val;
> +
> + c = &config->flash_timeout;
> + setting = &s->flash_timeout;
> + c->min = setting->min;
> + c->max = setting->max;
> + c->step = setting->step;
> + c->def = setting->val;
> +
> + config->has_external_strobe = false;
> +}
> +#else
> +#define aat1290_init_v4l2_ctrl_config(s, config)
> +#endif
> +
> static const struct led_flash_ops flash_ops = {
> .strobe_set = aat1290_led_flash_strobe_set,
> .timeout_set = aat1290_led_flash_timeout_set,
> };
>
> +#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
> +static const struct v4l2_flash_ops v4l2_flash_ops = {
> + .external_strobe_set = NULL,
> +};
> +
> +static const struct v4l2_flash_ops *get_v4l2_flash_ops(void)
> +{
> + return &v4l2_flash_ops;

You can use v4l2_flash_ops directly, no need for a function to return them.

> +}
> +#else
> +#define get_v4l2_flash_ops() (NULL)
> +#endif
> +
> static int aat1290_led_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> @@ -292,6 +334,9 @@ static int aat1290_led_probe(struct platform_device *pdev)
> struct aat1290_led *led;
> struct led_classdev *led_cdev;
> struct led_classdev_flash *flash;
> +#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
> + struct v4l2_flash_ctrl_config v4l2_flash_config;
> +#endif
> struct aat1290_led_settings settings;
> int flen_gpio, enset_gpio, ret;
>
> @@ -344,6 +389,9 @@ static int aat1290_led_probe(struct platform_device *pdev)
>
> flash->timeout = settings.flash_timeout;
>
> + /* Init V4L2 Flash controls basing on initialized settings */
> + aat1290_init_v4l2_ctrl_config(&settings, &v4l2_flash_config);
> +
> /* Init led class */
> led_cdev = &flash->led_cdev;
> led_cdev->name = led->label;
> @@ -361,8 +409,20 @@ static int aat1290_led_probe(struct platform_device *pdev)
> if (ret < 0)
> goto error_gpio_en_set;
>
> + /* Create V4L2 Flash subdev. */
> + led->v4l2_flash = v4l2_flash_init(flash,
> + get_v4l2_flash_ops(),
> + dev_node,
> + &v4l2_flash_config);

Less newlines needed; up to you.

> + 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(flash);
> error_gpio_en_set:
> if (gpio_is_valid(enset_gpio))
> gpio_free(enset_gpio);
> @@ -378,6 +438,7 @@ 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->ldev);
> cancel_work_sync(&led->work_brightness_set);
>

--
Kind regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-11 13:53:45

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 05/19] leds: Add support for max77693 mfd flash cell

Hi Sakari,

On 12/09/2014 02:11 PM, Sakari Ailus wrote:
> Hi Jacek,
>
> On Thu, Dec 04, 2014 at 12:06:59PM +0100, Jacek Anaszewski wrote:
>> Hi Sakari,
>>
>> Thanks for the review.
>
> You're welcome! :-)
>
>> On 12/04/2014 10:39 AM, Sakari Ailus wrote:
>>> Hi Jacek,
>>>
>>> On Wed, Dec 03, 2014 at 05:06:40PM +0100, Jacek Anaszewski wrote:
>>>> This patch adds led-flash support to Maxim max77693 chipset.
>>>> A device can be exposed to user space through LED subsystem
>>>> sysfs interface. Device supports up to two leds which can
>>>> work in flash and torch mode. The leds can be triggered
>>>> externally or by software.
>>>>
>>>> Signed-off-by: Jacek Anaszewski <[email protected]>
>>>> Signed-off-by: Andrzej Hajda <[email protected]>
>>>> Acked-by: Kyungmin Park <[email protected]>
>>>> Cc: Bryan Wu <[email protected]>
>>>> Cc: Richard Purdie <[email protected]>
>>>> Cc: Lee Jones <[email protected]>
>>>> Cc: Chanwoo Choi <[email protected]>
>>>> ---
>>>> drivers/leds/Kconfig | 10 +
>>>> drivers/leds/Makefile | 1 +
>>>> drivers/leds/leds-max77693.c | 1023 ++++++++++++++++++++++++++++++++++++++++++
>>>> 3 files changed, 1034 insertions(+)
>>>> create mode 100644 drivers/leds/leds-max77693.c
>>>>
>>>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>>>> index fa8021e..2e66d55 100644
>>>> --- a/drivers/leds/Kconfig
>>>> +++ b/drivers/leds/Kconfig
>>>> @@ -463,6 +463,16 @@ config LEDS_TCA6507
>>>> LED driver chips accessed via the I2C bus.
>>>> Driver support brightness control and hardware-assisted blinking.
>>>>
>>>> +config LEDS_MAX77693
>>>> + tristate "LED support for MAX77693 Flash"
>>>> + depends on LEDS_CLASS_FLASH
>>>> + depends on MFD_MAX77693
>>>> + depends on OF
>>>> + help
>>>> + This option enables support for the flash part of the MAX77693
>>>> + multifunction device. It has build in control for two leds in flash
>>>> + and torch mode.
>>>> +
>>>> config LEDS_MAX8997
>>>> tristate "LED support for MAX8997 PMIC"
>>>> depends on LEDS_CLASS && MFD_MAX8997
>>>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>>>> index cbba921..57ca62b 100644
>>>> --- a/drivers/leds/Makefile
>>>> +++ b/drivers/leds/Makefile
>>>> @@ -52,6 +52,7 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
>>>> obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
>>>> obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
>>>> obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
>>>> +obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
>>>> obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
>>>> obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
>>>> obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
>>>> diff --git a/drivers/leds/leds-max77693.c b/drivers/leds/leds-max77693.c
>>>> new file mode 100644
>>>> index 0000000..67a2f8f
>>>> --- /dev/null
>>>> +++ b/drivers/leds/leds-max77693.c
>>>> @@ -0,0 +1,1023 @@
>>>> +/*
>>>> + * LED Flash class driver for the flash cell of max77693 mfd.
>>>> + *
>>>> + * Copyright (C) 2014, Samsung Electronics Co., Ltd.
>>>> + *
>>>> + * Authors: Jacek Anaszewski <[email protected]>
>>>> + * Andrzej Hajda <[email protected]>
>>>> + *
>>>> + * 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 <asm/div64.h>
>>>> +#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>
>>>> +
>>>> +#define MODE_OFF 0
>>>> +#define MODE_FLASH1 (1 << 0)
>>>> +#define MODE_FLASH2 (1 << 1)
>>>> +#define MODE_TORCH1 (1 << 2)
>>>> +#define MODE_TORCH2 (1 << 3)
>>>> +#define MODE_FLASH_EXTERNAL1 (1 << 4)
>>>> +#define MODE_FLASH_EXTERNAL2 (1 << 5)
>>>
>>> You could do this based on an argument (led number). E.g.
>>>
>>> #define MODE_FLASH_EXTERNAL(a) (1 << (4 + a))
>>
>> OK.
>>
>>>> +#define MODE_FLASH (MODE_FLASH1 | MODE_FLASH2 | \
>>>> + MODE_FLASH_EXTERNAL1 | MODE_FLASH_EXTERNAL2)
>>>> +
>>>> +#define FLED1_IOUT (1 << 0)
>>>> +#define FLED2_IOUT (1 << 1)
>>>> +
>>>> +enum {
>>>> + FLED1,
>>>> + FLED2
>>>> +};
>>>> +
>>>> +enum {
>>>> + FLASH,
>>>> + TORCH
>>>> +};
>>>> +
>>>> +struct max77693_sub_led {
>
> This could then be renamed as "max77693_led"; up to you.

It will be better to rename the max77693_led structure to
max77693_led_device instead of max77693_device like you proposed. This
is because there is already max77693_dev structure in the mfd driver
for max77693 and this is only a driver for the led part of a device.

Taking above into account I'd rather leave struct max77693_sub_led
name unchanged.

>>>> + struct led_classdev_flash ldev;
>>>> + struct work_struct work_brightness_set;
>>>> +
>>>> + unsigned int torch_brightness;
>>>> + unsigned int flash_timeout;
>>>> +};
>>>> +
>>>> +struct max77693_led {
>>>
>>> As this does not refer to a device, how about struct max77693_device, for
>>> instance?
>>
>> OK.
>>
>>>> + struct regmap *regmap;
>>>> + struct platform_device *pdev;
>>>> + struct max77693_led_platform_data *pdata;
>>>> + struct mutex lock;
>>>> +
>>>> + struct max77693_sub_led sub_leds[2];
>>>> +
>>>> + unsigned int current_flash_timeout;
>>>> + unsigned int mode_flags;
>>>> + u8 torch_iout_reg;
>>>> + bool iout_joint;
>>>> + int strobing_sub_led_id;
>>>> +};
>>>> +
>>>> +struct max77693_led_settings {
>>>> + struct led_flash_setting torch_brightness;
>>>> + struct led_flash_setting flash_brightness;
>>>> + struct led_flash_setting flash_timeout;
>>>> +};
>>>> +
>>>> +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_led *ldev1_to_led(
>>>> + struct led_classdev_flash *ldev)
>>>> +{
>>>> + struct max77693_sub_led *sub_led = container_of(ldev,
>>>> + struct max77693_sub_led,
>>>> + ldev);
>>>> + return container_of(sub_led, struct max77693_led, sub_leds[0]);
>>>
>>> You could have a common macro to find the flash controller struct if you add
>>> the LED number to struct max77693_sub_led.
>>>
>>>> +}
>>>> +
>>>> +static inline struct max77693_led *ldev2_to_led(
>>>> + struct led_classdev_flash *ldev)
>>>> +{
>>>> + struct max77693_sub_led *sub_led = container_of(ldev,
>>>> + struct max77693_sub_led,
>>>> + ldev);
>>>> + return container_of(sub_led, struct max77693_led, sub_leds[1]);
>>>> +}
>>>> +
>>>> +static u8 max77693_led_vsys_to_reg(u32 mv)
>>>> +{
>>>> + return ((mv - MAX_FLASH1_VSYS_MIN) / MAX_FLASH1_VSYS_STEP) << 2;
>>>> +}
>>>> +
>>>> +static u8 max77693_led_vout_to_reg(u32 mv)
>>>> +{
>>>> + return (mv - FLASH_VOUT_MIN) / FLASH_VOUT_STEP + FLASH_VOUT_RMIN;
>>>> +}
>>>> +
>>>> +/* split composite current @i into two @iout according to @imax weights */
>>>
>>> What do you intend to do in the oint iout mode? A single LED connected to
>>> iout pins which are soldered together?
>>
>> Exactly that what is written in the comment - split the current into
>> both outputs.
>
> I think we discussed this on #v4l --- was it so that both outputs, if
> they're connected, should have the same current if there's a single LED
> connected to them?

Precisely: the current levels will differ between the outputs by
no more than one level, as for odd levels it will be impossible
to split the current to two even components.

>>>> +static int max77693_led_parse_dt(struct max77693_led *led,
>>>> + struct device_node *node)
>>>> +{
>>>> + struct max77693_led_platform_data *p = led->pdata;
>>>> + struct device *dev = &led->pdev->dev;
>>>> + struct device_node *child_node;
>>>> + u32 fled_id;
>>>> + int ret;
>>>> +
>>>> + of_property_read_u32_array(node, "maxim,fleds", p->fleds, 2);
>>>> + of_property_read_u32_array(node, "maxim,trigger", p->trigger, 2);
>>>> + of_property_read_u32_array(node, "maxim,trigger-type", p->trigger_type,
>>>> + 2);
>>>> + of_property_read_u32(node, "maxim,boost-mode", &p->boost_mode);
>>>> + of_property_read_u32(node, "maxim,boost-vout", &p->boost_vout);
>>>> + of_property_read_u32(node, "maxim,vsys-min", &p->low_vsys);
>>>> +
>>>> + for_each_available_child_of_node(node, child_node) {
>>>> + ret = of_property_read_u32(child_node, "maxim,fled_id",
>>>> + &fled_id);
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "Error reading \"fled_id\" DT property\n");
>>>> + return ret;
>>>> + }
>>>> +
>>>> + fled_id = clamp_val(fled_id, 1, 2);
>>>
>>> I think you should check fled_id is really correct, and not clamp it.
>>
>> Right.
>>
>>>> + --fled_id;
>>>> +
>>>> + p->sub_nodes[fled_id] = child_node;
>>>
>>> p->sub_nodes is not accessed anywhere else. Do you plan to use it for
>>> something?
>>
>> It is passed to v4l2_flash_init. In this patch set I add the V4L2
>> support in the separate patch. Probably this should be move there.
>
> Ok. I'm ok to keep it here as well, up to you.
>

As I explained on #v4l it won't be needed because it turned out that
the sub-node pointer can be stored in the dev member of
the led_classdev structure.

--
Best Regards,
Jacek Anaszewski

2014-12-11 14:25:46

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 08/19] leds: Add driver for AAT1290 current regulator

Hi Jacek,

On Wed, Dec 03, 2014 at 05:06:43PM +0100, Jacek Anaszewski wrote:
> This patch adds a driver for the 1.5A Step-Up Current Regulator
> for Flash LEDs. The device is programmed through a Skyworks proprietary
> AS2Cwire serial digital interface.
>
> Signed-off-by: Jacek Anaszewski <[email protected]>
> Acked-by: Kyungmin Park <[email protected]>
> Cc: Bryan Wu <[email protected]>
> Cc: Richard Purdie <[email protected]>
> ---
> drivers/leds/Kconfig | 7 +
> drivers/leds/Makefile | 1 +
> drivers/leds/leds-aat1290.c | 413 +++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 421 insertions(+)
> create mode 100644 drivers/leds/leds-aat1290.c
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 2e66d55..ec4b78c 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -39,6 +39,13 @@ config LEDS_88PM860X
> This option enables support for on-chip LED drivers found on Marvell
> Semiconductor 88PM8606 PMIC.
>
> +config LEDS_AAT1290
> + tristate "LED support for the AAT1290"
> + depends on LEDS_CLASS_FLASH
> + depends on OF
> + help
> + This option enables support for the LEDs on the AAT1290.
> +
> config LEDS_LM3530
> tristate "LCD Backlight driver for LM3530"
> depends on LEDS_CLASS
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 57ca62b..b802251 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -7,6 +7,7 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
>
> # LED Platform Drivers
> obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
> +obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
> obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
> obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
> obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
> diff --git a/drivers/leds/leds-aat1290.c b/drivers/leds/leds-aat1290.c
> new file mode 100644
> index 0000000..15d969b
> --- /dev/null
> +++ b/drivers/leds/leds-aat1290.c
> @@ -0,0 +1,413 @@
> +/*
> + * LED Flash class driver for the AAT1290
> + * 1.5A Step-Up Current Regulator for Flash LEDs
> + *
> + * Copyright (C) 2014, Samsung Electronics Co., Ltd.
> + * Author: Jacek Anaszewski <[email protected]>
> + *
> + * 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/slab.h>
> +#include <linux/platform_device.h>
> +#include <linux/module.h>
> +#include <linux/i2c.h>
> +#include <linux/led-class-flash.h>
> +#include <linux/leds.h>
> +#include <linux/mutex.h>
> +#include <linux/gpio.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of.h>
> +#include <linux/workqueue.h>

Alphabetic order, please.

> +
> +#define AAT1290_MOVIE_MODE_CURRENT_ADDR 17
> +#define AAT1290_FLASH_SAFETY_TIMER_ADDR 18
> +#define AAT1290_MOVIE_MODE_CONFIG_ADDR 19
> +#define AAT1290_MM_CURRENT_RATIO_ADDR 20
> +#define AAT1290_LATCH_TIME_US 500
> +#define AAT1290_EN_SET_TICK_TIME_US 1
> +#define AAT1290_MOVIE_MODE_OFF 1
> +#define AAT1290_MOVIE_MODE_ON 3
> +#define AAT1290_MAX_MM_CURR_PERCENT_0 16
> +#define AAT1290_MAX_MM_CURR_PERCENT_100 1
> +#define AAT1290_FLASH_TM_NUM_LEVELS 16

It'd be nice to arrange register bit definitions next to the register
address they're related to. That makes this a lot more readable. Timeouts
separately as well.

Please also align the values on the right to the same column. I think they
mostly are, but at least AAT1290_MAX_MM_CURR_PERCENT_100 uses space instead
of a tab.

> +#define AAT1290_MM_TO_FL_1_92 1
> +#define AAT1290_MM_TO_FL_3_7 2
> +#define AAT1290_MM_TO_FL_5_5 3
> +#define AAT1290_MM_TO_FL_7_3 4
> +#define AAT1290_MM_TO_FL_9 5
> +#define AAT1290_MM_TO_FL_10_7 6
> +#define AAT1290_MM_TO_FL_12_4 7
> +#define AAT1290_MM_TO_FL_14 8
> +#define AAT1290_MM_TO_FL_15_9 9
> +#define AAT1290_MM_TO_FL_17_5 10
> +#define AAT1290_MM_TO_FL_19_1 11
> +#define AAT1290_MM_TO_FL_20_8 12
> +#define AAT1290_MM_TO_FL_22_4 13
> +#define AAT1290_MM_TO_FL_24 14
> +#define AAT1290_MM_TO_FL_25_6 15
> +#define AAT1290_MM_TO_FL_OFF 16
> +
> +struct aat1290_led_settings {
> + struct led_flash_setting torch_brightness;
> + struct led_flash_setting flash_brightness;
> + struct led_flash_setting flash_timeout;
> +};
> +
> +struct aat1290_led {
> + struct platform_device *pdev;
> + struct mutex lock;
> +
> + struct led_classdev_flash ldev;
> +
> + int flen_gpio;
> + int en_set_gpio;
> +
> + u32 max_flash_tm;
> + bool movie_mode;
> +
> + char *label;
> + unsigned int torch_brightness;
> + unsigned int flash_timeout;
> + struct work_struct work_brightness_set;
> +};
> +
> +static struct aat1290_led *ldev_to_led(struct led_classdev_flash *ldev)
> +{
> + return container_of(ldev, struct aat1290_led, ldev);
> +}
> +
> +static void aat1290_as2cwire_write(struct aat1290_led *led, int addr, int value)
> +{
> + int i;
> +
> + gpio_set_value(led->flen_gpio, 0);
> + gpio_set_value(led->en_set_gpio, 0);
> +
> + udelay(10);

Could you use a #define for this as you are using for the rest?

> +
> + /* write address */
> + for (i = 0; i < addr; ++i) {
> + udelay(AAT1290_EN_SET_TICK_TIME_US);
> + gpio_set_value(led->en_set_gpio, 0);
> + udelay(AAT1290_EN_SET_TICK_TIME_US);
> + gpio_set_value(led->en_set_gpio, 1);
> + }

This is a very interesting approach to bus implementation. It's a bit like
pulse dial on POTS. :-)

> +
> + udelay(AAT1290_LATCH_TIME_US);

How precise does this need to be? Could you use usleep_range() instead?

> +
> + /* write data */
> + for (i = 0; i < value; ++i) {
> + udelay(AAT1290_EN_SET_TICK_TIME_US);
> + gpio_set_value(led->en_set_gpio, 0);
> + udelay(AAT1290_EN_SET_TICK_TIME_US);
> + gpio_set_value(led->en_set_gpio, 1);
> + }
> +
> + udelay(AAT1290_LATCH_TIME_US);
> +}
> +
> +static void aat1290_set_flash_safety_timer(struct aat1290_led *led,
> + unsigned int micro_sec)
> +{
> + struct led_classdev_flash *flash = &led->ldev;
> + struct led_flash_setting *flash_tm = &flash->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) {
> + gpio_set_value(led->flen_gpio, 0);
> + gpio_set_value(led->en_set_gpio, 0);
> + goto unlock;
> + }
> +
> + 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);
> +unlock:
> + 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 *flash = lcdev_to_flash(led_cdev);
> + struct aat1290_led *led = ldev_to_led(flash);
> +
> + led->torch_brightness = brightness;
> + schedule_work(&led->work_brightness_set);

Where is the asynchronous brightness setting used? Don't flash class devices
use always the synchronous variant?

> +}
> +
> +static int aat1290_led_brightness_set_sync(struct led_classdev *led_cdev,
> + enum led_brightness brightness)
> +{
> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
> + struct aat1290_led *led = ldev_to_led(flash);
> +
> + aat1290_brightness_set(led, brightness);
> +
> + return 0;
> +}
> +
> +static int aat1290_led_flash_strobe_set(struct led_classdev_flash *flash,
> + bool state)
> +
> +{
> + struct aat1290_led *led = ldev_to_led(flash);
> + struct led_classdev *led_cdev = &flash->led_cdev;
> + struct led_flash_setting *timeout = &flash->timeout;
> +
> + mutex_lock(&led->lock);
> +
> + if (state == 0) {
> + gpio_set_value(led->flen_gpio, 0);
> + gpio_set_value(led->en_set_gpio, 0);
> + goto unlock;
> + }
> +
> + aat1290_set_flash_safety_timer(led, timeout->val);
> +
> + /*
> + * 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 value needs to be updated here to
> + * reflect the actual state.
> + */
> + led_cdev->brightness = 0;
> + led->movie_mode = false;
> +
> + gpio_set_value(led->flen_gpio, 1);
> +
> +unlock:
> + mutex_unlock(&led->lock);
> +
> + return 0;
> +}
> +
> +static int aat1290_led_flash_timeout_set(struct led_classdev_flash *flash,
> + 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 device *dev)
> +{
> + int ret;
> + char *pname = "label";

You could use two const static variables for this, or a define. No need to
assign the variable in the middle.

I have to admit I've usually just written the string in verbatim where I
need it. :-)

> +
> + ret = of_property_read_string(dev->of_node, pname,
> + (const char **) &led->label);
> + if (ret < 0) {
> + dev_err(dev, "Error reading %s Device Tree property (%d)\n",
> + pname, ret);
> + return ret;
> + }
> +
> + pname = "flash-timeout-microsec";
> +
> + ret = of_property_read_u32(dev->of_node, pname, &led->max_flash_tm);
> + if (ret) {
> + dev_err(dev, "Error reading %s Device Tree property (%d)\n",
> + pname, ret);
> + return ret;
> + }
> +
> + return ret;

You always return ret whether here or above.

> +}
> +
> +static void aat1290_init_flash_settings(struct aat1290_led *led,
> + struct aat1290_led_settings *s)
> +{
> + struct led_flash_setting *setting;
> +
> + /* Init flash intensity setting */
> + setting = &s->torch_brightness;
> + /*
> + * Torch current is adjustable in logarithmic fashion and thus
> + * it is not possible to define fixed step in microamperes.
> + * Instead led brightness levels are used to make possible
> + * setting all the supported levels from V4L2 Flash sub-device.
> + */
> + setting->min = 1;
> + setting->max = AAT1290_MAX_MM_CURR_PERCENT_0 -
> + AAT1290_MAX_MM_CURR_PERCENT_100;
> + setting->step = 1;
> + setting->val = setting->max;
> +
> + /* Init flash timeout setting */
> + setting = &s->flash_timeout;
> + setting->min = led->max_flash_tm / AAT1290_FLASH_TM_NUM_LEVELS;
> + setting->max = setting->min * AAT1290_FLASH_TM_NUM_LEVELS;
> + setting->step = setting->min;
> + setting->val = setting->max;
> +}
> +
> +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 *dev_node = pdev->dev.of_node;
> + struct aat1290_led *led;
> + struct led_classdev *led_cdev;
> + struct led_classdev_flash *flash;
> + struct aat1290_led_settings settings;
> + int flen_gpio, enset_gpio, ret;
> +
> + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
> + if (!led)
> + return -ENOMEM;
> +
> + led->pdev = pdev;
> + platform_set_drvdata(pdev, led);
> +
> + if (!dev_node)
> + return -ENXIO;
> +
> + flen_gpio = of_get_gpio(dev_node, 0);
> + if (gpio_is_valid(flen_gpio)) {
> + ret = gpio_request_one(flen_gpio, GPIOF_DIR_OUT,
> + "aat1290_flen");
> + if (ret < 0) {
> + dev_err(dev,
> + "failed to request GPIO %d, error %d\n",
> + flen_gpio, ret);
> + goto error_gpio_flen;
> + }
> + }
> + led->flen_gpio = flen_gpio;
> +
> + enset_gpio = of_get_gpio(dev_node, 1);
> + if (gpio_is_valid(enset_gpio)) {
> + ret = gpio_request_one(enset_gpio, GPIOF_DIR_OUT,

Could you use devm_gpio_request_one()?

> + "aat1290_en_set");
> + if (ret < 0) {
> + dev_err(dev,
> + "failed to request GPIO %d, error %d\n",
> + enset_gpio, ret);
> + goto error_gpio_en_set;
> + }
> + }
> + led->en_set_gpio = enset_gpio;
> +
> + ret = aat1290_led_parse_dt(led, &pdev->dev);
> + if (ret < 0)
> + goto error_gpio_en_set;
> +
> + mutex_init(&led->lock);

By moving mutex_init() up, you can avoid calling mutex_destroy() on an
uninitialised mutex in error paths.

> +
> + flash = &led->ldev;
> +
> + /* Init flash settings */
> + aat1290_init_flash_settings(led, &settings);
> +
> + flash->timeout = settings.flash_timeout;
> +
> + /* Init led class */
> + led_cdev = &flash->led_cdev;
> + led_cdev->name = led->label;
> + led_cdev->brightness_set = aat1290_led_brightness_set;
> + led_cdev->brightness_set_sync = aat1290_led_brightness_set_sync;
> + led_cdev->max_brightness = settings.torch_brightness.max;
> + led_cdev->flags |= LED_DEV_CAP_FLASH;
> +
> + INIT_WORK(&led->work_brightness_set, aat1290_brightness_set_work);
> +
> + flash->ops = &flash_ops;
> +
> + /* Register in the LED subsystem. */
> + ret = led_classdev_flash_register(&pdev->dev, flash);
> + if (ret < 0)
> + goto error_gpio_en_set;
> +
> + return 0;
> +
> +error_gpio_en_set:
> + if (gpio_is_valid(enset_gpio))
> + gpio_free(enset_gpio);
> +error_gpio_flen:
> + if (gpio_is_valid(flen_gpio))
> + gpio_free(flen_gpio);
> + mutex_destroy(&led->lock);
> +
> + return ret;
> +}
> +
> +static int aat1290_led_remove(struct platform_device *pdev)
> +{
> + struct aat1290_led *led = platform_get_drvdata(pdev);
> +
> + led_classdev_flash_unregister(&led->ldev);
> + cancel_work_sync(&led->work_brightness_set);
> +
> + if (gpio_is_valid(led->en_set_gpio))
> + gpio_free(led->en_set_gpio);
> + if (gpio_is_valid(led->flen_gpio))
> + gpio_free(led->flen_gpio);
> +
> + mutex_destroy(&led->lock);
> +
> + return 0;
> +}
> +
> +static 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-led",
> + .owner = THIS_MODULE,
> + .of_match_table = aat1290_led_dt_match,
> + },
> +};
> +
> +module_platform_driver(aat1290_led_driver);
> +
> +MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
> +MODULE_DESCRIPTION("Skyworks Current Regulator for Flash LEDs");
> +MODULE_LICENSE("GPL");

--
Kind regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-11 15:34:51

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 08/19] leds: Add driver for AAT1290 current regulator

Hi Sakari,

Thanks for the review.

On 12/11/2014 03:16 PM, Sakari Ailus wrote:
> Hi Jacek,
>
> On Wed, Dec 03, 2014 at 05:06:43PM +0100, Jacek Anaszewski wrote:
>> This patch adds a driver for the 1.5A Step-Up Current Regulator
>> for Flash LEDs. The device is programmed through a Skyworks proprietary
>> AS2Cwire serial digital interface.
>>
>> Signed-off-by: Jacek Anaszewski <[email protected]>
>> Acked-by: Kyungmin Park <[email protected]>
>> Cc: Bryan Wu <[email protected]>
>> Cc: Richard Purdie <[email protected]>
>> ---
>> drivers/leds/Kconfig | 7 +
>> drivers/leds/Makefile | 1 +
>> drivers/leds/leds-aat1290.c | 413 +++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 421 insertions(+)
>> create mode 100644 drivers/leds/leds-aat1290.c
>>
>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>> index 2e66d55..ec4b78c 100644
>> --- a/drivers/leds/Kconfig
>> +++ b/drivers/leds/Kconfig
>> @@ -39,6 +39,13 @@ config LEDS_88PM860X
>> This option enables support for on-chip LED drivers found on Marvell
>> Semiconductor 88PM8606 PMIC.
>>
>> +config LEDS_AAT1290
>> + tristate "LED support for the AAT1290"
>> + depends on LEDS_CLASS_FLASH
>> + depends on OF
>> + help
>> + This option enables support for the LEDs on the AAT1290.
>> +
>> config LEDS_LM3530
>> tristate "LCD Backlight driver for LM3530"
>> depends on LEDS_CLASS
>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>> index 57ca62b..b802251 100644
>> --- a/drivers/leds/Makefile
>> +++ b/drivers/leds/Makefile
>> @@ -7,6 +7,7 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
>>
>> # LED Platform Drivers
>> obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
>> +obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
>> obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
>> obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
>> obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
>> diff --git a/drivers/leds/leds-aat1290.c b/drivers/leds/leds-aat1290.c
>> new file mode 100644
>> index 0000000..15d969b
>> --- /dev/null
>> +++ b/drivers/leds/leds-aat1290.c
>> @@ -0,0 +1,413 @@
>> +/*
>> + * LED Flash class driver for the AAT1290
>> + * 1.5A Step-Up Current Regulator for Flash LEDs
>> + *
>> + * Copyright (C) 2014, Samsung Electronics Co., Ltd.
>> + * Author: Jacek Anaszewski <[email protected]>
>> + *
>> + * 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/slab.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/module.h>
>> +#include <linux/i2c.h>
>> +#include <linux/led-class-flash.h>
>> +#include <linux/leds.h>
>> +#include <linux/mutex.h>
>> +#include <linux/gpio.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/of.h>
>> +#include <linux/workqueue.h>
>
> Alphabetic order, please.

Oops, I missed that.

>> +
>> +#define AAT1290_MOVIE_MODE_CURRENT_ADDR 17
>> +#define AAT1290_FLASH_SAFETY_TIMER_ADDR 18
>> +#define AAT1290_MOVIE_MODE_CONFIG_ADDR 19
>> +#define AAT1290_MM_CURRENT_RATIO_ADDR 20
>> +#define AAT1290_LATCH_TIME_US 500
>> +#define AAT1290_EN_SET_TICK_TIME_US 1
>> +#define AAT1290_MOVIE_MODE_OFF 1
>> +#define AAT1290_MOVIE_MODE_ON 3
>> +#define AAT1290_MAX_MM_CURR_PERCENT_0 16
>> +#define AAT1290_MAX_MM_CURR_PERCENT_100 1
>> +#define AAT1290_FLASH_TM_NUM_LEVELS 16
>
> It'd be nice to arrange register bit definitions next to the register
> address they're related to. That makes this a lot more readable. Timeouts
> separately as well.
>
> Please also align the values on the right to the same column. I think they
> mostly are, but at least AAT1290_MAX_MM_CURR_PERCENT_100 uses space instead
> of a tab.

OK.

>> +#define AAT1290_MM_TO_FL_1_92 1
>> +#define AAT1290_MM_TO_FL_3_7 2
>> +#define AAT1290_MM_TO_FL_5_5 3
>> +#define AAT1290_MM_TO_FL_7_3 4
>> +#define AAT1290_MM_TO_FL_9 5
>> +#define AAT1290_MM_TO_FL_10_7 6
>> +#define AAT1290_MM_TO_FL_12_4 7
>> +#define AAT1290_MM_TO_FL_14 8
>> +#define AAT1290_MM_TO_FL_15_9 9
>> +#define AAT1290_MM_TO_FL_17_5 10
>> +#define AAT1290_MM_TO_FL_19_1 11
>> +#define AAT1290_MM_TO_FL_20_8 12
>> +#define AAT1290_MM_TO_FL_22_4 13
>> +#define AAT1290_MM_TO_FL_24 14
>> +#define AAT1290_MM_TO_FL_25_6 15
>> +#define AAT1290_MM_TO_FL_OFF 16
>> +
>> +struct aat1290_led_settings {
>> + struct led_flash_setting torch_brightness;
>> + struct led_flash_setting flash_brightness;
>> + struct led_flash_setting flash_timeout;
>> +};
>> +
>> +struct aat1290_led {
>> + struct platform_device *pdev;
>> + struct mutex lock;
>> +
>> + struct led_classdev_flash ldev;
>> +
>> + int flen_gpio;
>> + int en_set_gpio;
>> +
>> + u32 max_flash_tm;
>> + bool movie_mode;
>> +
>> + char *label;
>> + unsigned int torch_brightness;
>> + unsigned int flash_timeout;
>> + struct work_struct work_brightness_set;
>> +};
>> +
>> +static struct aat1290_led *ldev_to_led(struct led_classdev_flash *ldev)
>> +{
>> + return container_of(ldev, struct aat1290_led, ldev);
>> +}
>> +
>> +static void aat1290_as2cwire_write(struct aat1290_led *led, int addr, int value)
>> +{
>> + int i;
>> +
>> + gpio_set_value(led->flen_gpio, 0);
>> + gpio_set_value(led->en_set_gpio, 0);
>> +
>> + udelay(10);
>
> Could you use a #define for this as you are using for the rest?

OK.

>> +
>> + /* write address */
>> + for (i = 0; i < addr; ++i) {
>> + udelay(AAT1290_EN_SET_TICK_TIME_US);
>> + gpio_set_value(led->en_set_gpio, 0);
>> + udelay(AAT1290_EN_SET_TICK_TIME_US);
>> + gpio_set_value(led->en_set_gpio, 1);
>> + }
>
> This is a very interesting approach to bus implementation. It's a bit like
> pulse dial on POTS. :-)
>
>> +
>> + udelay(AAT1290_LATCH_TIME_US);
>
> How precise does this need to be? Could you use usleep_range() instead?

This is minimal required time, so usleep_range could be used here,
which would however make the delay of setting the torch brightness
even more unstable.

>> +
>> + /* write data */
>> + for (i = 0; i < value; ++i) {
>> + udelay(AAT1290_EN_SET_TICK_TIME_US);
>> + gpio_set_value(led->en_set_gpio, 0);
>> + udelay(AAT1290_EN_SET_TICK_TIME_US);
>> + gpio_set_value(led->en_set_gpio, 1);
>> + }
>> +
>> + udelay(AAT1290_LATCH_TIME_US);
>> +}
>> +
>> +static void aat1290_set_flash_safety_timer(struct aat1290_led *led,
>> + unsigned int micro_sec)
>> +{
>> + struct led_classdev_flash *flash = &led->ldev;
>> + struct led_flash_setting *flash_tm = &flash->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) {
>> + gpio_set_value(led->flen_gpio, 0);
>> + gpio_set_value(led->en_set_gpio, 0);
>> + goto unlock;
>> + }
>> +
>> + 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);
>> +unlock:
>> + 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 *flash = lcdev_to_flash(led_cdev);
>> + struct aat1290_led *led = ldev_to_led(flash);
>> +
>> + led->torch_brightness = brightness;
>> + schedule_work(&led->work_brightness_set);
>
> Where is the asynchronous brightness setting used? Don't flash class devices
> use always the synchronous variant?

It is used by led-triggers. They are still available as the torch
functionality is handled by the LED class.

>> +}
>> +
>> +static int aat1290_led_brightness_set_sync(struct led_classdev *led_cdev,
>> + enum led_brightness brightness)
>> +{
>> + struct led_classdev_flash *flash = lcdev_to_flash(led_cdev);
>> + struct aat1290_led *led = ldev_to_led(flash);
>> +
>> + aat1290_brightness_set(led, brightness);
>> +
>> + return 0;
>> +}
>> +
>> +static int aat1290_led_flash_strobe_set(struct led_classdev_flash *flash,
>> + bool state)
>> +
>> +{
>> + struct aat1290_led *led = ldev_to_led(flash);
>> + struct led_classdev *led_cdev = &flash->led_cdev;
>> + struct led_flash_setting *timeout = &flash->timeout;
>> +
>> + mutex_lock(&led->lock);
>> +
>> + if (state == 0) {
>> + gpio_set_value(led->flen_gpio, 0);
>> + gpio_set_value(led->en_set_gpio, 0);
>> + goto unlock;
>> + }
>> +
>> + aat1290_set_flash_safety_timer(led, timeout->val);
>> +
>> + /*
>> + * 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 value needs to be updated here to
>> + * reflect the actual state.
>> + */
>> + led_cdev->brightness = 0;
>> + led->movie_mode = false;
>> +
>> + gpio_set_value(led->flen_gpio, 1);
>> +
>> +unlock:
>> + mutex_unlock(&led->lock);
>> +
>> + return 0;
>> +}
>> +
>> +static int aat1290_led_flash_timeout_set(struct led_classdev_flash *flash,
>> + 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 device *dev)
>> +{
>> + int ret;
>> + char *pname = "label";
>
> You could use two const static variables for this, or a define. No need to
> assign the variable in the middle.
>
> I have to admit I've usually just written the string in verbatim where I
> need it. :-)

I will also write them in verbatim, as I am doing it in the max77693-led
driver. Here I got influenced by some code few months ago probably :)

>> +
>> + ret = of_property_read_string(dev->of_node, pname,
>> + (const char **) &led->label);
>> + if (ret < 0) {
>> + dev_err(dev, "Error reading %s Device Tree property (%d)\n",
>> + pname, ret);
>> + return ret;
>> + }
>> +
>> + pname = "flash-timeout-microsec";
>> +
>> + ret = of_property_read_u32(dev->of_node, pname, &led->max_flash_tm);
>> + if (ret) {
>> + dev_err(dev, "Error reading %s Device Tree property (%d)\n",
>> + pname, ret);
>> + return ret;
>> + }
>> +
>> + return ret;
>
> You always return ret whether here or above.

Indeed, only the last occurrence should be left.

>> +}
>> +
>> +static void aat1290_init_flash_settings(struct aat1290_led *led,
>> + struct aat1290_led_settings *s)
>> +{
>> + struct led_flash_setting *setting;
>> +
>> + /* Init flash intensity setting */
>> + setting = &s->torch_brightness;
>> + /*
>> + * Torch current is adjustable in logarithmic fashion and thus
>> + * it is not possible to define fixed step in microamperes.
>> + * Instead led brightness levels are used to make possible
>> + * setting all the supported levels from V4L2 Flash sub-device.
>> + */
>> + setting->min = 1;
>> + setting->max = AAT1290_MAX_MM_CURR_PERCENT_0 -
>> + AAT1290_MAX_MM_CURR_PERCENT_100;
>> + setting->step = 1;
>> + setting->val = setting->max;
>> +
>> + /* Init flash timeout setting */
>> + setting = &s->flash_timeout;
>> + setting->min = led->max_flash_tm / AAT1290_FLASH_TM_NUM_LEVELS;
>> + setting->max = setting->min * AAT1290_FLASH_TM_NUM_LEVELS;
>> + setting->step = setting->min;
>> + setting->val = setting->max;
>> +}
>> +
>> +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 *dev_node = pdev->dev.of_node;
>> + struct aat1290_led *led;
>> + struct led_classdev *led_cdev;
>> + struct led_classdev_flash *flash;
>> + struct aat1290_led_settings settings;
>> + int flen_gpio, enset_gpio, ret;
>> +
>> + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
>> + if (!led)
>> + return -ENOMEM;
>> +
>> + led->pdev = pdev;
>> + platform_set_drvdata(pdev, led);
>> +
>> + if (!dev_node)
>> + return -ENXIO;
>> +
>> + flen_gpio = of_get_gpio(dev_node, 0);
>> + if (gpio_is_valid(flen_gpio)) {
>> + ret = gpio_request_one(flen_gpio, GPIOF_DIR_OUT,
>> + "aat1290_flen");
>> + if (ret < 0) {
>> + dev_err(dev,
>> + "failed to request GPIO %d, error %d\n",
>> + flen_gpio, ret);
>> + goto error_gpio_flen;
>> + }
>> + }
>> + led->flen_gpio = flen_gpio;
>> +
>> + enset_gpio = of_get_gpio(dev_node, 1);
>> + if (gpio_is_valid(enset_gpio)) {
>> + ret = gpio_request_one(enset_gpio, GPIOF_DIR_OUT,
>
> Could you use devm_gpio_request_one()?

OK.

>> + "aat1290_en_set");
>> + if (ret < 0) {
>> + dev_err(dev,
>> + "failed to request GPIO %d, error %d\n",
>> + enset_gpio, ret);
>> + goto error_gpio_en_set;
>> + }
>> + }
>> + led->en_set_gpio = enset_gpio;
>> +
>> + ret = aat1290_led_parse_dt(led, &pdev->dev);
>> + if (ret < 0)
>> + goto error_gpio_en_set;
>> +
>> + mutex_init(&led->lock);
>
> By moving mutex_init() up, you can avoid calling mutex_destroy() on an
> uninitialised mutex in error paths.

Right.

>> +
>> + flash = &led->ldev;
>> +
>> + /* Init flash settings */
>> + aat1290_init_flash_settings(led, &settings);
>> +
>> + flash->timeout = settings.flash_timeout;
>> +
>> + /* Init led class */
>> + led_cdev = &flash->led_cdev;
>> + led_cdev->name = led->label;
>> + led_cdev->brightness_set = aat1290_led_brightness_set;
>> + led_cdev->brightness_set_sync = aat1290_led_brightness_set_sync;
>> + led_cdev->max_brightness = settings.torch_brightness.max;
>> + led_cdev->flags |= LED_DEV_CAP_FLASH;
>> +
>> + INIT_WORK(&led->work_brightness_set, aat1290_brightness_set_work);
>> +
>> + flash->ops = &flash_ops;
>> +
>> + /* Register in the LED subsystem. */
>> + ret = led_classdev_flash_register(&pdev->dev, flash);
>> + if (ret < 0)
>> + goto error_gpio_en_set;
>> +
>> + return 0;
>> +
>> +error_gpio_en_set:
>> + if (gpio_is_valid(enset_gpio))
>> + gpio_free(enset_gpio);
>> +error_gpio_flen:
>> + if (gpio_is_valid(flen_gpio))
>> + gpio_free(flen_gpio);
>> + mutex_destroy(&led->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int aat1290_led_remove(struct platform_device *pdev)
>> +{
>> + struct aat1290_led *led = platform_get_drvdata(pdev);
>> +
>> + led_classdev_flash_unregister(&led->ldev);
>> + cancel_work_sync(&led->work_brightness_set);
>> +
>> + if (gpio_is_valid(led->en_set_gpio))
>> + gpio_free(led->en_set_gpio);
>> + if (gpio_is_valid(led->flen_gpio))
>> + gpio_free(led->flen_gpio);
>> +
>> + mutex_destroy(&led->lock);
>> +
>> + return 0;
>> +}
>> +
>> +static 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-led",
>> + .owner = THIS_MODULE,
>> + .of_match_table = aat1290_led_dt_match,
>> + },
>> +};
>> +
>> +module_platform_driver(aat1290_led_driver);
>> +
>> +MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
>> +MODULE_DESCRIPTION("Skyworks Current Regulator for Flash LEDs");
>> +MODULE_LICENSE("GPL");
>


--
Best Regards,
Jacek Anaszewski

2014-12-11 15:48:40

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 05/19] leds: Add support for max77693 mfd flash cell

Hi Jacek,

On Thu, Dec 11, 2014 at 02:53:37PM +0100, Jacek Anaszewski wrote:
> Hi Sakari,
>
> On 12/09/2014 02:11 PM, Sakari Ailus wrote:
> >Hi Jacek,
> >
> >On Thu, Dec 04, 2014 at 12:06:59PM +0100, Jacek Anaszewski wrote:
> >>Hi Sakari,
> >>
> >>Thanks for the review.
> >
> >You're welcome! :-)
> >
> >>On 12/04/2014 10:39 AM, Sakari Ailus wrote:
> >>>Hi Jacek,
> >>>
> >>>On Wed, Dec 03, 2014 at 05:06:40PM +0100, Jacek Anaszewski wrote:
> >>>>This patch adds led-flash support to Maxim max77693 chipset.
> >>>>A device can be exposed to user space through LED subsystem
> >>>>sysfs interface. Device supports up to two leds which can
> >>>>work in flash and torch mode. The leds can be triggered
> >>>>externally or by software.
> >>>>
> >>>>Signed-off-by: Jacek Anaszewski <[email protected]>
> >>>>Signed-off-by: Andrzej Hajda <[email protected]>
> >>>>Acked-by: Kyungmin Park <[email protected]>
> >>>>Cc: Bryan Wu <[email protected]>
> >>>>Cc: Richard Purdie <[email protected]>
> >>>>Cc: Lee Jones <[email protected]>
> >>>>Cc: Chanwoo Choi <[email protected]>
> >>>>---
> >>>> drivers/leds/Kconfig | 10 +
> >>>> drivers/leds/Makefile | 1 +
> >>>> drivers/leds/leds-max77693.c | 1023 ++++++++++++++++++++++++++++++++++++++++++
> >>>> 3 files changed, 1034 insertions(+)
> >>>> create mode 100644 drivers/leds/leds-max77693.c
> >>>>
> >>>>diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> >>>>index fa8021e..2e66d55 100644
> >>>>--- a/drivers/leds/Kconfig
> >>>>+++ b/drivers/leds/Kconfig
> >>>>@@ -463,6 +463,16 @@ config LEDS_TCA6507
> >>>> LED driver chips accessed via the I2C bus.
> >>>> Driver support brightness control and hardware-assisted blinking.
> >>>>
> >>>>+config LEDS_MAX77693
> >>>>+ tristate "LED support for MAX77693 Flash"
> >>>>+ depends on LEDS_CLASS_FLASH
> >>>>+ depends on MFD_MAX77693
> >>>>+ depends on OF
> >>>>+ help
> >>>>+ This option enables support for the flash part of the MAX77693
> >>>>+ multifunction device. It has build in control for two leds in flash
> >>>>+ and torch mode.
> >>>>+
> >>>> config LEDS_MAX8997
> >>>> tristate "LED support for MAX8997 PMIC"
> >>>> depends on LEDS_CLASS && MFD_MAX8997
> >>>>diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> >>>>index cbba921..57ca62b 100644
> >>>>--- a/drivers/leds/Makefile
> >>>>+++ b/drivers/leds/Makefile
> >>>>@@ -52,6 +52,7 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
> >>>> obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
> >>>> obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
> >>>> obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
> >>>>+obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
> >>>> obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
> >>>> obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
> >>>> obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
> >>>>diff --git a/drivers/leds/leds-max77693.c b/drivers/leds/leds-max77693.c
> >>>>new file mode 100644
> >>>>index 0000000..67a2f8f
> >>>>--- /dev/null
> >>>>+++ b/drivers/leds/leds-max77693.c
> >>>>@@ -0,0 +1,1023 @@
> >>>>+/*
> >>>>+ * LED Flash class driver for the flash cell of max77693 mfd.
> >>>>+ *
> >>>>+ * Copyright (C) 2014, Samsung Electronics Co., Ltd.
> >>>>+ *
> >>>>+ * Authors: Jacek Anaszewski <[email protected]>
> >>>>+ * Andrzej Hajda <[email protected]>
> >>>>+ *
> >>>>+ * 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 <asm/div64.h>
> >>>>+#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>
> >>>>+
> >>>>+#define MODE_OFF 0
> >>>>+#define MODE_FLASH1 (1 << 0)
> >>>>+#define MODE_FLASH2 (1 << 1)
> >>>>+#define MODE_TORCH1 (1 << 2)
> >>>>+#define MODE_TORCH2 (1 << 3)
> >>>>+#define MODE_FLASH_EXTERNAL1 (1 << 4)
> >>>>+#define MODE_FLASH_EXTERNAL2 (1 << 5)
> >>>
> >>>You could do this based on an argument (led number). E.g.
> >>>
> >>>#define MODE_FLASH_EXTERNAL(a) (1 << (4 + a))
> >>
> >>OK.
> >>
> >>>>+#define MODE_FLASH (MODE_FLASH1 | MODE_FLASH2 | \
> >>>>+ MODE_FLASH_EXTERNAL1 | MODE_FLASH_EXTERNAL2)
> >>>>+
> >>>>+#define FLED1_IOUT (1 << 0)
> >>>>+#define FLED2_IOUT (1 << 1)
> >>>>+
> >>>>+enum {
> >>>>+ FLED1,
> >>>>+ FLED2
> >>>>+};
> >>>>+
> >>>>+enum {
> >>>>+ FLASH,
> >>>>+ TORCH
> >>>>+};
> >>>>+
> >>>>+struct max77693_sub_led {
> >
> >This could then be renamed as "max77693_led"; up to you.
>
> It will be better to rename the max77693_led structure to
> max77693_led_device instead of max77693_device like you proposed. This
> is because there is already max77693_dev structure in the mfd driver
> for max77693 and this is only a driver for the led part of a device.
>
> Taking above into account I'd rather leave struct max77693_sub_led
> name unchanged.

Fine for me.

> >>>>+ struct led_classdev_flash ldev;
> >>>>+ struct work_struct work_brightness_set;
> >>>>+
> >>>>+ unsigned int torch_brightness;
> >>>>+ unsigned int flash_timeout;
> >>>>+};
> >>>>+
> >>>>+struct max77693_led {
> >>>
> >>>As this does not refer to a device, how about struct max77693_device, for
> >>>instance?
> >>
> >>OK.
> >>
> >>>>+ struct regmap *regmap;
> >>>>+ struct platform_device *pdev;
> >>>>+ struct max77693_led_platform_data *pdata;
> >>>>+ struct mutex lock;
> >>>>+
> >>>>+ struct max77693_sub_led sub_leds[2];
> >>>>+
> >>>>+ unsigned int current_flash_timeout;
> >>>>+ unsigned int mode_flags;
> >>>>+ u8 torch_iout_reg;
> >>>>+ bool iout_joint;
> >>>>+ int strobing_sub_led_id;
> >>>>+};
> >>>>+
> >>>>+struct max77693_led_settings {
> >>>>+ struct led_flash_setting torch_brightness;
> >>>>+ struct led_flash_setting flash_brightness;
> >>>>+ struct led_flash_setting flash_timeout;
> >>>>+};
> >>>>+
> >>>>+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_led *ldev1_to_led(
> >>>>+ struct led_classdev_flash *ldev)
> >>>>+{
> >>>>+ struct max77693_sub_led *sub_led = container_of(ldev,
> >>>>+ struct max77693_sub_led,
> >>>>+ ldev);
> >>>>+ return container_of(sub_led, struct max77693_led, sub_leds[0]);
> >>>
> >>>You could have a common macro to find the flash controller struct if you add
> >>>the LED number to struct max77693_sub_led.
> >>>
> >>>>+}
> >>>>+
> >>>>+static inline struct max77693_led *ldev2_to_led(
> >>>>+ struct led_classdev_flash *ldev)
> >>>>+{
> >>>>+ struct max77693_sub_led *sub_led = container_of(ldev,
> >>>>+ struct max77693_sub_led,
> >>>>+ ldev);
> >>>>+ return container_of(sub_led, struct max77693_led, sub_leds[1]);
> >>>>+}
> >>>>+
> >>>>+static u8 max77693_led_vsys_to_reg(u32 mv)
> >>>>+{
> >>>>+ return ((mv - MAX_FLASH1_VSYS_MIN) / MAX_FLASH1_VSYS_STEP) << 2;
> >>>>+}
> >>>>+
> >>>>+static u8 max77693_led_vout_to_reg(u32 mv)
> >>>>+{
> >>>>+ return (mv - FLASH_VOUT_MIN) / FLASH_VOUT_STEP + FLASH_VOUT_RMIN;
> >>>>+}
> >>>>+
> >>>>+/* split composite current @i into two @iout according to @imax weights */
> >>>
> >>>What do you intend to do in the oint iout mode? A single LED connected to
> >>>iout pins which are soldered together?
> >>
> >>Exactly that what is written in the comment - split the current into
> >>both outputs.
> >
> >I think we discussed this on #v4l --- was it so that both outputs, if
> >they're connected, should have the same current if there's a single LED
> >connected to them?
>
> Precisely: the current levels will differ between the outputs by
> no more than one level, as for odd levels it will be impossible
> to split the current to two even components.

True. If that's allowed from HW point of view, sure.

> >>>>+static int max77693_led_parse_dt(struct max77693_led *led,
> >>>>+ struct device_node *node)
> >>>>+{
> >>>>+ struct max77693_led_platform_data *p = led->pdata;
> >>>>+ struct device *dev = &led->pdev->dev;
> >>>>+ struct device_node *child_node;
> >>>>+ u32 fled_id;
> >>>>+ int ret;
> >>>>+
> >>>>+ of_property_read_u32_array(node, "maxim,fleds", p->fleds, 2);
> >>>>+ of_property_read_u32_array(node, "maxim,trigger", p->trigger, 2);
> >>>>+ of_property_read_u32_array(node, "maxim,trigger-type", p->trigger_type,
> >>>>+ 2);
> >>>>+ of_property_read_u32(node, "maxim,boost-mode", &p->boost_mode);
> >>>>+ of_property_read_u32(node, "maxim,boost-vout", &p->boost_vout);
> >>>>+ of_property_read_u32(node, "maxim,vsys-min", &p->low_vsys);
> >>>>+
> >>>>+ for_each_available_child_of_node(node, child_node) {
> >>>>+ ret = of_property_read_u32(child_node, "maxim,fled_id",
> >>>>+ &fled_id);
> >>>>+ if (ret < 0) {
> >>>>+ dev_err(dev, "Error reading \"fled_id\" DT property\n");
> >>>>+ return ret;
> >>>>+ }
> >>>>+
> >>>>+ fled_id = clamp_val(fled_id, 1, 2);
> >>>
> >>>I think you should check fled_id is really correct, and not clamp it.
> >>
> >>Right.
> >>
> >>>>+ --fled_id;
> >>>>+
> >>>>+ p->sub_nodes[fled_id] = child_node;
> >>>
> >>>p->sub_nodes is not accessed anywhere else. Do you plan to use it for
> >>>something?
> >>
> >>It is passed to v4l2_flash_init. In this patch set I add the V4L2
> >>support in the separate patch. Probably this should be move there.
> >
> >Ok. I'm ok to keep it here as well, up to you.
> >
>
> As I explained on #v4l it won't be needed because it turned out that
> the sub-node pointer can be stored in the dev member of
> the led_classdev structure.

Ack.

--
Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-11 15:52:58

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 08/19] leds: Add driver for AAT1290 current regulator

Hi Jacek,

On Thu, Dec 11, 2014 at 04:34:45PM +0100, Jacek Anaszewski wrote:
> >>+
> >>+ /* write address */
> >>+ for (i = 0; i < addr; ++i) {
> >>+ udelay(AAT1290_EN_SET_TICK_TIME_US);
> >>+ gpio_set_value(led->en_set_gpio, 0);
> >>+ udelay(AAT1290_EN_SET_TICK_TIME_US);
> >>+ gpio_set_value(led->en_set_gpio, 1);
> >>+ }
> >
> >This is a very interesting approach to bus implementation. It's a bit like
> >pulse dial on POTS. :-)
> >
> >>+
> >>+ udelay(AAT1290_LATCH_TIME_US);
> >
> >How precise does this need to be? Could you use usleep_range() instead?
>
> This is minimal required time, so usleep_range could be used here,
> which would however make the delay of setting the torch brightness
> even more unstable.

True as well. Half a ms isn't that long but then again torch typically isn't
time critical either. I'd use usleep_range(), up to you.

--
Regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]

2014-12-30 22:16:01

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v9 07/19] dt-binding: mfd: max77693: Add DT binding related macros

Hi Jacek,

The driver depends on these so I'd rearrange this patch in the set before
the driver patch.

On Wed, Dec 03, 2014 at 05:06:42PM +0100, Jacek Anaszewski wrote:
> Add macros for max77693 led part related binding.
>
> Signed-off-by: Jacek Anaszewski <[email protected]>
> Acked-by: Kyungmin Park <[email protected]>
> Cc: Lee Jones <[email protected]>
> Cc: Chanwoo Choi <[email protected]>
> ---
> include/dt-bindings/mfd/max77693.h | 38 ++++++++++++++++++++++++++++++++++++
> 1 file changed, 38 insertions(+)
> create mode 100644 include/dt-bindings/mfd/max77693.h
>
> diff --git a/include/dt-bindings/mfd/max77693.h b/include/dt-bindings/mfd/max77693.h
> new file mode 100644
> index 0000000..4011cb47
> --- /dev/null
> +++ b/include/dt-bindings/mfd/max77693.h
> @@ -0,0 +1,38 @@
> +/*
> + * This header provides macros for MAX77693 device binding
> + *
> + * Copyright (C) 2014, Samsung Electronics Co., Ltd.
> + *
> + * Author: Jacek Anaszewski <[email protected]>
> + */
> +
> +#ifndef __DT_BINDINGS_MAX77693_H__
> +#define __DT_BINDINGS_MAX77693_H
> +
> +/* External control pins */
> +#define MAX77693_LED_FLED_UNUSED 0
> +#define MAX77693_LED_FLED_USED 1
> +
> +/* FLED pins */
> +#define MAX77693_LED_FLED1 1
> +#define MAX77693_LED_FLED2 2

I'd personally simply use numbers for the above but I can't really say to be
an expert on the topic.

> +/* External trigger type */
> +#define MAX77693_LED_TRIG_TYPE_EDGE 0
> +#define MAX77693_LED_TRIG_TYPE_LEVEL 1
> +
> +/* Trigger flags */
> +#define MAX77693_LED_TRIG_FLASHEN (1 << 0)
> +#define MAX77693_LED_TRIG_TORCHEN (1 << 1)
> +#define MAX77693_LED_TRIG_SOFTWARE (1 << 2)
> +
> +#define MAX77693_LED_TRIG_ALL (MAX77693_LED_TRIG_FLASHEN | \
> + MAX77693_LED_TRIG_TORCHEN | \
> + MAX77693_LED_TRIG_SOFTWARE)
> +
> +/* Boost modes */
> +#define MAX77693_LED_BOOST_OFF 0
> +#define MAX77693_LED_BOOST_ADAPTIVE 1
> +#define MAX77693_LED_BOOST_FIXED 2
> +
> +#endif /* __DT_BINDINGS_MAX77693_H */

--
Regards,

Sakari Ailus
e-mail: [email protected] XMPP: [email protected]