2014-04-11 14:57:22

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v3 0/5] LED / flash API integration

This is is the third version of the patch series being a follow up
of the discussion on Media summit 2013-10-23, related to the
LED / flash API integration (the notes from the discussion were
enclosed in the message [1], paragraph 5).
The series is based on linux-next next-20140328

Description of the proposed modifications according to
the kernel components they are relevant to:
- LED subsystem modifications
* added led_flash module which, when enabled in the config,
registers flash specific sysfs attributes:
- flash_brightness
- max_flash_brightness
- indicator_brightness
- max_indicator_brightness
- flash_timeout
- max_flash_timeout
- flash_strobe
- flash_fault
- external_strobe
and exposes kernel internal API
- led_set_flash_strobe
- led_get_flash_strobe
- led_set_indicator_brightness
- led_update_indicator_brightness
- led_set_flash_timeout
- led_get_flash_fault
- led_set_external_strobe
- led_sysfs_lock
- led_sysfs_unlock
- Addition of a V4L2 Flash sub-device registration helpers
* added v4l2-flash.c and v4l2-flash.h files with helper
functions that facilitate registration/unregistration
of a subdevice, which wrapps a LED subsystem device and
exposes V4L2 Flash control interface
- Addition of a driver for the flash cell of the MAX77693 mfd
* the driver exploits the newly introduced mechanism
- Update of the max77693.txt DT bindings documentation

================
Changes since v2
================

- refactored the code so that it is possible to build
led-core without led-flash module
- added v4l2-flash ops which slackens dependency from
the led-flash module
- implemented led_clamp_align_val function and led_ctrl
structure which allows to align led control values
in the manner compatible with V4L2 Flash controls;
the flash brightness and timeout units have been defined
as microamperes and microseconds respectively to properly
support devices which define current and time levels
as fractions of 1/1000.
- added support for the flash privacy leds
- modified LED sysfs locking mechanism - now it locks/unlocks
the interface on V4L2 Flash sub-device file open/close
- changed hw_triggered attribute name to external_strobe,
which maps on the V4L2_FLASH_STROBE_SOURCE_EXTERNAL name
more intuitively
- made external_strobe and indicator related sysfs attributes
created optionally only if related features are declared
by the led device driver
- removed from the series patches modifying exynos4-is media
controller - a proposal for "flash manager" which will take
care of flash devices registration is due to be submitted
- removed modifications to the LED class devices documentation,
it will be covered after the whole functionality is accepted

Thanks,
Jacek Anaszewski

[1] http://www.spinics.net/lists/linux-media/msg69253.html

Jacek Anaszewski (5):
leds: Add sysfs and kernel internal API for flash LEDs
leds: Improve and export led_update_brightness function
leds: Add support for max77693 mfd flash cell
DT: Add documentation for the mfd Maxim max77693 flash cell
media: Add registration helpers for V4L2 flash sub-devices

Documentation/devicetree/bindings/mfd/max77693.txt | 57 ++
drivers/leds/Kconfig | 18 +
drivers/leds/Makefile | 2 +
drivers/leds/led-class.c | 42 +-
drivers/leds/led-core.c | 16 +
drivers/leds/led-flash.c | 627 ++++++++++++++++
drivers/leds/led-triggers.c | 16 +-
drivers/leds/leds-max77693.c | 794 ++++++++++++++++++++
drivers/leds/leds.h | 6 +
drivers/media/v4l2-core/Kconfig | 10 +
drivers/media/v4l2-core/Makefile | 2 +
drivers/media/v4l2-core/v4l2-flash.c | 393 ++++++++++
drivers/mfd/max77693.c | 2 +-
include/linux/leds.h | 60 +-
include/linux/leds_flash.h | 252 +++++++
include/linux/mfd/max77693.h | 38 +
include/media/v4l2-flash.h | 119 +++
17 files changed, 2433 insertions(+), 21 deletions(-)
create mode 100644 drivers/leds/led-flash.c
create mode 100644 drivers/leds/leds-max77693.c
create mode 100644 drivers/media/v4l2-core/v4l2-flash.c
create mode 100644 include/linux/leds_flash.h
create mode 100644 include/media/v4l2-flash.h

--
1.7.9.5


2014-04-11 14:57:34

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v3 1/5] leds: Add sysfs and kernel internal API for flash LEDs

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
optional external_strobe, indicator_brightness and
max_indicator_btightness. All the flash related features
are placed in a separate module.
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 | 8 +
drivers/leds/Makefile | 1 +
drivers/leds/led-class.c | 36 ++-
drivers/leds/led-flash.c | 627 +++++++++++++++++++++++++++++++++++++++++++
drivers/leds/led-triggers.c | 16 +-
drivers/leds/leds.h | 6 +
include/linux/leds.h | 50 +++-
include/linux/leds_flash.h | 252 +++++++++++++++++
8 files changed, 982 insertions(+), 14 deletions(-)
create mode 100644 drivers/leds/led-flash.c
create mode 100644 include/linux/leds_flash.h

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 2062682..1e1c81f 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -19,6 +19,14 @@ 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 "Flash LEDs Support"
+ depends on LEDS_CLASS
+ help
+ This option enables support for flash LED devices. Say Y if you
+ want to use flash specific features of a LED device, if they
+ are supported.
+
comment "LED drivers"

config LEDS_88PM860X
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 3cd76db..8861b86 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-flash.o
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o

# LED Platform Drivers
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index f37d63c..58f16c3 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -9,15 +9,16 @@
* published by the Free Software Foundation.
*/

-#include <linux/module.h>
-#include <linux/kernel.h>
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/err.h>
#include <linux/init.h>
+#include <linux/kernel.h>
#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
#include <linux/spinlock.h>
-#include <linux/device.h>
#include <linux/timer.h>
-#include <linux/err.h>
-#include <linux/ctype.h>
#include <linux/leds.h>
#include "leds.h"

@@ -45,28 +46,38 @@ static ssize_t brightness_store(struct device *dev,
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
unsigned long state;
- ssize_t ret = -EINVAL;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_lock);
+
+ if (led_sysfs_is_locked(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }

ret = kstrtoul(buf, 10, &state);
if (ret)
- return ret;
+ goto unlock;

if (state == LED_OFF)
led_trigger_remove(led_cdev);
__led_set_brightness(led_cdev, state);
+ ret = size;

- return size;
+unlock:
+ mutex_unlock(&led_cdev->led_lock);
+ return ret;
}
static DEVICE_ATTR_RW(brightness);

-static ssize_t led_max_brightness_show(struct device *dev,
+static ssize_t max_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);

return sprintf(buf, "%u\n", led_cdev->max_brightness);
}
-static DEVICE_ATTR(max_brightness, 0444, led_max_brightness_show, NULL);
+static DEVICE_ATTR_RO(max_brightness);

#ifdef CONFIG_LEDS_TRIGGERS
static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
@@ -174,6 +185,8 @@ 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);
@@ -218,6 +231,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
#ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
#endif
+ mutex_init(&led_cdev->led_lock);
/* add to the list of leds */
down_write(&leds_list_lock);
list_add_tail(&led_cdev->node, &leds_list);
@@ -271,6 +285,8 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
down_write(&leds_list_lock);
list_del(&led_cdev->node);
up_write(&leds_list_lock);
+
+ mutex_destroy(&led_cdev->led_lock);
}
EXPORT_SYMBOL_GPL(led_classdev_unregister);

diff --git a/drivers/leds/led-flash.c b/drivers/leds/led-flash.c
new file mode 100644
index 0000000..9d482a4
--- /dev/null
+++ b/drivers/leds/led-flash.c
@@ -0,0 +1,627 @@
+/*
+ * LED Class Flash 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/module.h>
+#include <linux/leds.h>
+#include <linux/leds_flash.h>
+#include "leds.h"
+
+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);
+ unsigned long state;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_lock);
+
+ if (led_sysfs_is_locked(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ goto unlock;
+
+ ret = led_set_flash_brightness(led_cdev, state);
+ if (ret < 0)
+ goto unlock;
+
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_lock);
+ 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_flash *flash = led_cdev->flash;
+
+ /* no lock needed for this */
+ led_update_flash_brightness(led_cdev);
+
+ 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_flash *flash = led_cdev->flash;
+
+ return sprintf(buf, "%u\n", flash->brightness.max);
+}
+static DEVICE_ATTR_RO(max_flash_brightness);
+
+static ssize_t indicator_brightness_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ unsigned long state;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_lock);
+
+ if (led_sysfs_is_locked(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ goto unlock;
+
+ ret = led_set_indicator_brightness(led_cdev, state);
+ if (ret < 0)
+ goto unlock;
+
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_lock);
+ return ret;
+}
+
+static ssize_t indicator_brightness_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_flash *flash = led_cdev->flash;
+
+ /* no lock needed for this */
+ led_update_indicator_brightness(led_cdev);
+
+ return sprintf(buf, "%u\n", flash->indicator_brightness->val);
+}
+static DEVICE_ATTR_RW(indicator_brightness);
+
+static ssize_t max_indicator_brightness_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_flash *flash = led_cdev->flash;
+
+ return sprintf(buf, "%u\n", flash->indicator_brightness->max);
+}
+static DEVICE_ATTR_RO(max_indicator_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);
+ unsigned long state;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_lock);
+
+ if (led_sysfs_is_locked(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ goto unlock;
+
+ if (state < 0 || state > 1)
+ return -EINVAL;
+
+ ret = led_set_flash_strobe(led_cdev, state);
+ if (ret < 0)
+ goto unlock;
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_lock);
+ 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);
+ int ret;
+
+ /* no lock needed for this */
+ ret = led_get_flash_strobe(led_cdev);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%u\n", ret);
+}
+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);
+ unsigned long flash_timeout;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_lock);
+
+ if (led_sysfs_is_locked(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = kstrtoul(buf, 10, &flash_timeout);
+ if (ret)
+ goto unlock;
+
+ ret = led_set_flash_timeout(led_cdev, flash_timeout);
+ if (ret < 0)
+ goto unlock;
+
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_lock);
+ 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_flash *flash = led_cdev->flash;
+
+ return sprintf(buf, "%d\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_flash *flash = led_cdev->flash;
+
+ return sprintf(buf, "%d\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);
+ unsigned int fault;
+ int ret;
+
+ ret = led_get_flash_fault(led_cdev, &fault);
+ if (ret < 0)
+ return -EINVAL;
+
+ return sprintf(buf, "0x%8.8x\n", fault);
+}
+static DEVICE_ATTR_RO(flash_fault);
+
+static ssize_t external_strobe_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ unsigned long external_strobe;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_lock);
+
+ if (led_sysfs_is_locked(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = kstrtoul(buf, 10, &external_strobe);
+ if (ret)
+ goto unlock;
+
+ if (external_strobe > 1) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ ret = led_set_external_strobe(led_cdev, external_strobe);
+ if (ret < 0)
+ goto unlock;
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_lock);
+ return ret;
+}
+
+static ssize_t external_strobe_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", led_cdev->flash->external_strobe);
+}
+static DEVICE_ATTR_RW(external_strobe);
+
+static struct attribute *led_flash_attrs[] = {
+ &dev_attr_flash_brightness.attr,
+ &dev_attr_flash_strobe.attr,
+ &dev_attr_flash_timeout.attr,
+ &dev_attr_max_flash_timeout.attr,
+ &dev_attr_max_flash_brightness.attr,
+ &dev_attr_flash_fault.attr,
+ NULL,
+};
+
+static struct attribute *led_flash_indicator_attrs[] = {
+ &dev_attr_indicator_brightness.attr,
+ &dev_attr_max_indicator_brightness.attr,
+ NULL,
+};
+
+static struct attribute *led_flash_external_strobe_attrs[] = {
+ &dev_attr_external_strobe.attr,
+ NULL,
+};
+
+static struct attribute_group led_flash_group = {
+ .attrs = led_flash_attrs,
+};
+
+static struct attribute_group led_flash_indicator_group = {
+ .attrs = led_flash_indicator_attrs,
+};
+
+static struct attribute_group led_flash_external_strobe_group = {
+ .attrs = led_flash_external_strobe_attrs,
+};
+
+void led_flash_resume(struct led_classdev *led_cdev)
+{
+ struct led_flash *flash = led_cdev->flash;
+
+ call_flash_op(brightness_set, led_cdev,
+ flash->brightness.val);
+ call_flash_op(timeout_set, led_cdev,
+ flash->timeout.val);
+ if (has_flash_op(indicator_brightness_set))
+ call_flash_op(indicator_brightness_set, led_cdev,
+ flash->indicator_brightness->val);
+}
+
+#ifdef CONFIG_V4L2_FLASH
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .brightness_set = led_set_brightness,
+ .brightness_update = led_update_brightness,
+ .flash_brightness_set = led_set_flash_brightness,
+ .flash_brightness_update = led_update_flash_brightness,
+ .indicator_brightness_set = led_set_indicator_brightness,
+ .indicator_brightness_update = led_update_indicator_brightness,
+ .strobe_set = led_set_flash_strobe,
+ .strobe_get = led_get_flash_strobe,
+ .timeout_set = led_set_flash_timeout,
+ .external_strobe_set = led_set_external_strobe,
+ .fault_get = led_get_flash_fault,
+ .sysfs_lock = led_sysfs_lock,
+ .sysfs_unlock = led_sysfs_unlock,
+};
+#define V4L2_FLASH_OPS (&v4l2_flash_ops)
+#else
+#define V4L2_FLASH_OPS NULL
+#endif
+
+
+void led_flash_remove_sysfs_groups(struct led_classdev *led_cdev)
+{
+ struct led_flash *flash = led_cdev->flash;
+ int i;
+
+ for (i = 0; i < LED_FLASH_MAX_SYSFS_GROUPS; ++i)
+ if (flash->sysfs_groups[i])
+ sysfs_remove_group(&led_cdev->dev->kobj,
+ flash->sysfs_groups[i]);
+}
+
+int led_flash_create_sysfs_groups(struct led_classdev *led_cdev)
+{
+ struct led_flash *flash = led_cdev->flash;
+ int ret, num_sysfs_groups = 0;
+
+ memset(flash->sysfs_groups, 0, sizeof(*flash->sysfs_groups) *
+ LED_FLASH_MAX_SYSFS_GROUPS);
+
+ ret = sysfs_create_group(&led_cdev->dev->kobj, &led_flash_group);
+ if (ret < 0)
+ goto err_create_group;
+ flash->sysfs_groups[num_sysfs_groups++] = &led_flash_group;
+
+ if (flash->indicator_brightness) {
+ ret = sysfs_create_group(&led_cdev->dev->kobj,
+ &led_flash_indicator_group);
+ if (ret < 0)
+ goto err_create_group;
+ flash->sysfs_groups[num_sysfs_groups++] =
+ &led_flash_indicator_group;
+ }
+ if (flash->has_external_strobe) {
+ ret = sysfs_create_group(&led_cdev->dev->kobj,
+ &led_flash_external_strobe_group);
+ if (ret < 0)
+ goto err_create_group;
+ flash->sysfs_groups[num_sysfs_groups++] =
+ &led_flash_external_strobe_group;
+ }
+
+ return 0;
+
+err_create_group:
+ led_flash_remove_sysfs_groups(led_cdev);
+ return ret;
+}
+
+int led_classdev_flash_register(struct device *parent,
+ struct led_classdev *led_cdev)
+{
+ struct led_flash *flash = led_cdev->flash;
+ const struct led_flash_ops *ops;
+ int ret;
+
+ if (!flash)
+ return -EINVAL;
+
+ /* Register led class device */
+ ret = led_classdev_register(parent, led_cdev);
+ if (ret < 0)
+ return ret;
+
+ if (!flash->has_flash_led)
+ goto exit;
+
+ /* Validate flash related ops */
+ ops = &flash->ops;
+ if (!ops || !ops->brightness_set || !ops->strobe_set || !ops->strobe_get
+ || !ops->timeout_set || !ops->fault_get)
+ return -EINVAL;
+
+ if (flash->has_external_strobe && !ops->external_strobe_set)
+ return -EINVAL;
+
+ if (flash->indicator_brightness && !ops->indicator_brightness_set)
+ return -EINVAL;
+
+ /* Install resume callback for flash controls */
+ led_cdev->flash_resume = led_flash_resume;
+
+ /* Create flash led specific sysfs attributes */
+ ret = led_flash_create_sysfs_groups(led_cdev);
+ if (ret < 0)
+ goto err_create_groups;
+
+exit:
+ /* This will create V4L2 Flash sub-device if it is enabled */
+ ret = v4l2_flash_init(led_cdev, V4L2_FLASH_OPS);
+ if (ret < 0)
+ goto err_create_groups;
+
+ return 0;
+
+err_create_groups:
+ led_classdev_unregister(led_cdev);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(led_classdev_flash_register);
+
+void led_classdev_flash_unregister(struct led_classdev *led_cdev)
+{
+ v4l2_flash_release(led_cdev);
+ led_flash_remove_sysfs_groups(led_cdev);
+ led_classdev_unregister(led_cdev);
+}
+EXPORT_SYMBOL_GPL(led_classdev_flash_unregister);
+
+/* Caller must ensure led_cdev->led_lock held */
+void led_sysfs_lock(struct led_classdev *led_cdev)
+{
+ led_cdev->flags |= LED_SYSFS_LOCK;
+}
+EXPORT_SYMBOL(led_sysfs_lock);
+
+/* Caller must ensure led_cdev->led_lock held */
+void led_sysfs_unlock(struct led_classdev *led_cdev)
+{
+ led_cdev->flags &= ~LED_SYSFS_LOCK;
+}
+EXPORT_SYMBOL(led_sysfs_unlock);
+
+int led_set_flash_strobe(struct led_classdev *led_cdev, bool state)
+{
+ if (!has_flash_op(strobe_set))
+ return -EINVAL;
+
+ return call_flash_op(strobe_set, led_cdev, state);
+}
+EXPORT_SYMBOL(led_set_flash_strobe);
+
+int led_get_flash_strobe(struct led_classdev *led_cdev)
+{
+ if (!has_flash_op(strobe_get))
+ return -EINVAL;
+
+ return call_flash_op(strobe_get, led_cdev);
+}
+EXPORT_SYMBOL(led_get_flash_strobe);
+
+void led_clamp_align_val(struct led_ctrl *c)
+{
+ u32 v, offset;
+
+ v = c->val + c->step / 2;
+ v = clamp(v, c->min, c->max);
+ offset = v - c->min;
+ offset = c->step * (offset / c->step);
+ c->val = c->min + offset;
+}
+
+int led_set_flash_timeout(struct led_classdev *led_cdev, u32 timeout)
+{
+ struct led_flash *flash = led_cdev->flash;
+ struct led_ctrl *c = &flash->timeout;
+ int ret = 0;
+
+ if (!has_flash_op(timeout_set))
+ return -EINVAL;
+
+ c->val = timeout;
+ led_clamp_align_val(c);
+
+ if (!(led_cdev->flags & LED_SUSPENDED))
+ ret = call_flash_op(timeout_set, led_cdev, c->val);
+
+ return ret;
+}
+EXPORT_SYMBOL(led_set_flash_timeout);
+
+int led_get_flash_fault(struct led_classdev *led_cdev, u32 *fault)
+{
+ if (!has_flash_op(fault_get))
+ return -EINVAL;
+
+ return call_flash_op(fault_get, led_cdev, fault);
+}
+EXPORT_SYMBOL(led_get_flash_fault);
+
+int led_set_external_strobe(struct led_classdev *led_cdev, bool enable)
+{
+ struct led_flash *flash = led_cdev->flash;
+ int ret;
+
+ if (!has_flash_op(external_strobe_set))
+ return -EINVAL;
+
+ if (flash->has_external_strobe) {
+ ret = call_flash_op(external_strobe_set, led_cdev, enable);
+ if (ret < 0)
+ return -EINVAL;
+ flash->external_strobe = enable;
+ } else if (enable)
+ return -EINVAL;
+
+ return 0;
+}
+EXPORT_SYMBOL(led_set_external_strobe);
+
+int led_set_flash_brightness(struct led_classdev *led_cdev,
+ u32 brightness)
+{
+ struct led_flash *flash = led_cdev->flash;
+ struct led_ctrl *c = &flash->brightness;
+ int ret = 0;
+
+ if (!has_flash_op(brightness_set))
+ return -EINVAL;
+
+ c->val = brightness;
+ led_clamp_align_val(c);
+
+ if (!(led_cdev->flags & LED_SUSPENDED))
+ ret = call_flash_op(brightness_set, led_cdev, c->val);
+ return ret;
+}
+EXPORT_SYMBOL(led_set_flash_brightness);
+
+int led_update_flash_brightness(struct led_classdev *led_cdev)
+{
+ struct led_flash *flash = led_cdev->flash;
+ struct led_ctrl *c = &flash->brightness;
+ u32 brightness;
+ int ret = 0;
+
+ if (has_flash_op(brightness_get)) {
+ ret = call_flash_op(brightness_get, led_cdev, &brightness);
+ if (ret < 0)
+ return ret;
+ c->val = brightness;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(led_update_flash_brightness);
+
+int led_set_indicator_brightness(struct led_classdev *led_cdev,
+ u32 brightness)
+{
+ struct led_flash *flash = led_cdev->flash;
+ struct led_ctrl *c = flash->indicator_brightness;
+ int ret = 0;
+
+ if (!has_flash_op(indicator_brightness_set))
+ return -EINVAL;
+
+ c->val = brightness;
+ led_clamp_align_val(c);
+
+ if (!(led_cdev->flags & LED_SUSPENDED))
+ ret = call_flash_op(indicator_brightness_set, led_cdev, c->val);
+
+ return ret;
+}
+EXPORT_SYMBOL(led_set_indicator_brightness);
+
+int led_update_indicator_brightness(struct led_classdev *led_cdev)
+{
+ struct led_flash *flash = led_cdev->flash;
+ struct led_ctrl *c = flash->indicator_brightness;
+ u32 brightness;
+ int ret = 0;
+
+ if (has_flash_op(indicator_brightness_get)) {
+ ret = call_flash_op(indicator_brightness_get, led_cdev,
+ &brightness);
+ if (ret < 0)
+ return ret;
+ c->val = brightness;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(led_update_indicator_brightness);
+
+static int __init leds_flash_init(void)
+{
+ return 0;
+}
+
+static void __exit leds_flash_exit(void)
+{
+}
+
+subsys_initcall(leds_flash_init);
+module_exit(leds_flash_exit);
+
+MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("LED Class Flash Interface");
diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index df1a7c1..40e21c0 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -37,6 +37,14 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
char trigger_name[TRIG_NAME_MAX];
struct led_trigger *trig;
size_t len;
+ int ret = count;
+
+ mutex_lock(&led_cdev->led_lock);
+
+ if (led_sysfs_is_locked(led_cdev)) {
+ ret = -EBUSY;
+ goto exit_unlock;
+ }

trigger_name[sizeof(trigger_name) - 1] = '\0';
strncpy(trigger_name, buf, sizeof(trigger_name) - 1);
@@ -47,7 +55,7 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,

if (!strcmp(trigger_name, "none")) {
led_trigger_remove(led_cdev);
- return count;
+ goto exit_unlock;
}

down_read(&triggers_list_lock);
@@ -58,12 +66,14 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
up_write(&led_cdev->trigger_lock);

up_read(&triggers_list_lock);
- return count;
+ goto exit_unlock;
}
}
up_read(&triggers_list_lock);

- return -EINVAL;
+exit_unlock:
+ mutex_unlock(&led_cdev->led_lock);
+ return ret;
}
EXPORT_SYMBOL_GPL(led_trigger_store);

diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
index 4c50365..f66a0c3 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -17,6 +17,12 @@
#include <linux/rwsem.h>
#include <linux/leds.h>

+#define call_flash_op(op, args...) \
+ ((led_cdev)->flash->ops.op(args))
+
+#define has_flash_op(op) \
+ ((led_cdev)->flash && (led_cdev)->flash->ops.op)
+
static inline void __led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
diff --git a/include/linux/leds.h b/include/linux/leds.h
index 0287ab2..a794817 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -13,12 +13,14 @@
#define __LINUX_LEDS_H_INCLUDED

#include <linux/list.h>
-#include <linux/spinlock.h>
+#include <linux/mutex.h>
#include <linux/rwsem.h>
+#include <linux/spinlock.h>
#include <linux/timer.h>
#include <linux/workqueue.h>

struct device;
+struct led_flash;
/*
* LED Core
*/
@@ -29,6 +31,28 @@ enum led_brightness {
LED_FULL = 255,
};

+/*
+ * This structure is required in two cases:
+ * - it defines allowed levels of flash leds brightness and timeout
+ * - it provides initialization data for V4L2 Flash controls
+ * when CONFIG_V4L2_FLASH is enabled and allows for proper
+ * enum led_brightness <-> microamperes conversion for the
+ * V4L2_CID_FLASH_TORCH_INTENSITY
+ */
+struct led_ctrl {
+ /* maximum allowed value */
+ u32 min;
+ /* maximum allowed value */
+ u32 max;
+ /* step value */
+ u32 step;
+ /*
+ * Default value for V4L2 controls and for flash leds
+ * it also serves for caching the value currently set.
+ */
+ u32 val;
+};
+
struct led_classdev {
const char *name;
int brightness;
@@ -42,6 +66,7 @@ struct led_classdev {
#define LED_BLINK_ONESHOT (1 << 17)
#define LED_BLINK_ONESHOT_STOP (1 << 18)
#define LED_BLINK_INVERT (1 << 19)
+#define LED_SYSFS_LOCK (1 << 21)

/* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */
@@ -69,6 +94,17 @@ struct led_classdev {
unsigned long blink_delay_on, blink_delay_off;
struct timer_list blink_timer;
int blink_brightness;
+ struct led_flash *flash;
+ void (*flash_resume)(struct led_classdev *led_cdev);
+#ifdef CONFIG_V4L2_FLASH
+ /* Initialization data for the V4L2_CID_FLASH_TORCH_INTENSITY control */
+ struct led_ctrl brightness_ctrl;
+#endif
+ /*
+ * Ensures consistent LED sysfs access and protects
+ * LED sysfs locking mechanism
+ */
+ struct mutex led_lock;

struct work_struct set_brightness_work;
int delayed_set_value;
@@ -139,6 +175,18 @@ extern void led_blink_set_oneshot(struct led_classdev *led_cdev,
extern void led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness brightness);

+/**
+ * led_sysfs_is_locked
+ * @led_cdev: the LED to query
+ *
+ * Returns: true if the sysfs interface of the led is disabled,
+ * false otherwise
+ */
+static inline bool led_sysfs_is_locked(struct led_classdev *led_cdev)
+{
+ return led_cdev->flags & LED_SYSFS_LOCK;
+}
+
/*
* LED Triggers
*/
diff --git a/include/linux/leds_flash.h b/include/linux/leds_flash.h
new file mode 100644
index 0000000..0f885a0
--- /dev/null
+++ b/include/linux/leds_flash.h
@@ -0,0 +1,252 @@
+/*
+ * Flash leds API
+ *
+ * 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 <media/v4l2-flash.h>
+#include <linux/leds.h>
+
+/*
+ * Supported led fault bits - must be kept in synch
+ * with V4L2_FLASH_FAULT bits.
+ */
+#define LED_FAULT_OVER_VOLTAGE (1 << 0)
+#define LED_FAULT_TIMEOUT (1 << 1)
+#define LED_FAULT_OVER_TEMPERATURE (1 << 2)
+#define LED_FAULT_SHORT_CIRCUIT (1 << 3)
+#define LED_FAULT_OVER_CURRENT (1 << 4)
+#define LED_FAULT_INDICATOR (1 << 5)
+#define LED_FAULT_UNDER_VOLTAGE (1 << 6)
+#define LED_FAULT_INPUT_VOLTAGE (1 << 7)
+#define LED_FAULT_LED_OVER_TEMPERATURE (1 << 8)
+
+#define LED_FLASH_MAX_SYSFS_GROUPS 3
+
+struct led_flash_ops {
+ /* set flash brightness */
+ int (*brightness_set)(struct led_classdev *led_cdev,
+ u32 brightness);
+ /* get flash brightness */
+ int (*brightness_get)(struct led_classdev *led_cdev, u32 *brightness);
+ /* set flash indicator brightness */
+ int (*indicator_brightness_set)(struct led_classdev *led_cdev,
+ u32 brightness);
+ /* get flash indicator brightness */
+ int (*indicator_brightness_get)(struct led_classdev *led_cdev,
+ u32 *brightness);
+ /* setup flash strobe */
+ int (*strobe_set)(struct led_classdev *led_cdev,
+ bool state);
+ /* get flash strobe state */
+ int (*strobe_get)(struct led_classdev *led_cdev);
+ /* setup flash timeout */
+ int (*timeout_set)(struct led_classdev *led_cdev,
+ u32 timeout);
+ /* setup strobing the flash from external source */
+ int (*external_strobe_set)(struct led_classdev *led_cdev,
+ bool enable);
+ /* get the flash LED fault */
+ int (*fault_get)(struct led_classdev *led_cdev,
+ u32 *fault);
+};
+
+struct led_flash {
+ /*
+ * 1 - add support for both flash and torch leds,
+ * 0 - handle only torch led
+ */
+ bool has_flash_led;
+ /* flash led specific ops */
+ const struct led_flash_ops ops;
+
+ /* flash sysfs groups */
+ struct attribute_group *sysfs_groups[LED_FLASH_MAX_SYSFS_GROUPS];
+
+ /* flash brightness value in microamperes along with its constraints */
+ struct led_ctrl brightness;
+
+ /* timeout value in microseconds along with its constraints */
+ struct led_ctrl timeout;
+
+ /*
+ * Indicator brightness value in microamperes along with
+ * its constraints - this is an optional control and must
+ * be allocated by the driver if the device supports privacy
+ * indicator led.
+ */
+ struct led_ctrl *indicator_brightness;
+
+ /*
+ * determines whether device supports external
+ * flash strobe sources
+ */
+ bool has_external_strobe;
+
+ /*
+ * If true the device doesn't strobe the flash immediately
+ * after writing 1 to the flash_strobe file, but waits
+ * for an external signal.
+ */
+ bool external_strobe;
+
+#ifdef CONFIG_V4L2_FLASH
+ /* V4L2 Flash sub-device data */
+ struct v4l2_flash v4l2_flash;
+
+ /* flash fault bits that may be set by the device */
+ u32 fault_flags;
+#endif
+
+};
+
+/**
+ * led_classdev_flash_register - register a new object of led_classdev class
+ with support for flash LEDs
+ * @parent: the device to register
+ * @led_cdev: the led_classdev structure for this device
+ *
+ * Returns: 0 on success, error code on failure.
+ */
+int led_classdev_flash_register(struct device *parent,
+ struct led_classdev *led_cdev);
+
+/**
+ * led_classdev_flash_unregister - unregisters an object of led_properties class
+ with support for flash LEDs
+ * @led_cdev: the flash led device to unregister
+ *
+ * Unregisters a previously registered via led_classdev_flash_register object
+ */
+void led_classdev_flash_unregister(struct led_classdev *led_cdev);
+
+/**
+ * led_set_flash_strobe - setup flash strobe
+ * @led_cdev: the flash LED to set strobe on
+ * @state: 1 - strobe flash, 0 - stop flash strobe
+ *
+ * Setup flash strobe - trigger flash strobe
+ *
+ * Returns: 0 on success or negative error value on failure
+ */
+extern int led_set_flash_strobe(struct led_classdev *led_cdev, bool state);
+
+/**
+ * led_get_flash_strobe - get flash strobe status
+ * @led_cdev: the LED to query
+ *
+ * Check whether the flash is strobing at the moment or not.
+ *
+ * Returns: flash strobe status (0 or 1) on success or negative
+ * error value on failure.
+ */
+extern int led_get_flash_strobe(struct led_classdev *led_cdev);
+
+/**
+ * led_set_flash_brightness - set flash LED brightness
+ * @led_cdev: the LED to set
+ * @brightness: the brightness to set it to
+ *
+ * Returns: 0 on success, -EINVAL on failure
+ *
+ * Set a flash LED's brightness.
+ */
+extern int led_set_flash_brightness(struct led_classdev *led_cdev,
+ u32 brightness);
+
+/**
+ * led_update_flash_brightness - update flash LED brightness
+ * @led_cdev: the 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 *led_cdev);
+
+/**
+ * led_set_flash_timeout - set flash LED timeout
+ * @led_cdev: the LED to set
+ * @timeout: the flash timeout to set it to
+ *
+ * Returns: 0 on success, -EINVAL on failure
+ *
+ * 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.
+ */
+extern int led_set_flash_timeout(struct led_classdev *led_cdev,
+ u32 timeout);
+
+/**
+ * led_get_flash_fault - get the flash LED fault
+ * @led_cdev: the LED to query
+ * @fault: bitmask containing flash faults
+ *
+ * Returns: 0 on success, -EINVAL on failure
+ *
+ * Get the flash LED fault.
+ */
+extern int led_get_flash_fault(struct led_classdev *led_cdev,
+ u32 *fault);
+
+/**
+ * led_set_external_strobe - set the flash LED external_strobe mode
+ * @led_cdev: the LED to set
+ * @enable: the state to set it to
+ *
+ * Returns: 0 on success, -EINVAL on failure
+ *
+ * Enable/disable strobing the flash LED with use of external source
+ */
+extern int led_set_external_strobe(struct led_classdev *led_cdev, bool enable);
+
+/**
+ * led_set_indicator_brightness - set indicator LED brightness
+ * @led_cdev: the LED to set
+ * @brightness: the brightness to set it to
+ *
+ * Returns: 0 on success, -EINVAL on failure
+ *
+ * Set a flash LED's brightness.
+ */
+extern int led_set_indicator_brightness(struct led_classdev *led_cdev,
+ u32 led_brightness);
+
+/**
+ * led_update_indicator_brightness - update flash indicator LED brightness
+ * @led_cdev: the LED to query
+ *
+ * Get a flash indicator LED's current brightness and update
+ * led_flash->indicator_brightness member with the obtained value.
+ *
+ * Returns: 0 on success or negative error value on failure
+ */
+extern int led_update_indicator_brightness(struct led_classdev *led_cdev);
+
+/**
+ * led_sysfs_lock - lock LED sysfs interface
+ * @led_cdev: the LED to set
+ *
+ * Lock the LED's sysfs interface
+ */
+extern void led_sysfs_lock(struct led_classdev *led_cdev);
+
+/**
+ * led_sysfs_unlock - unlock LED sysfs interface
+ * @led_cdev: the LED to set
+ *
+ * Unlock the LED's sysfs interface
+ */
+extern void led_sysfs_unlock(struct led_classdev *led_cdev);
+
+#endif /* __LINUX_FLASH_LEDS_H_INCLUDED */
--
1.7.9.5

2014-04-11 14:57:44

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v3 3/5] 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 or through V4L2 subdevice when the support
for V4L2 Flash sub-devices is enabled. Device supports up to
two leds which can work in flash and torch mode. Leds can
be triggered externally or by software.

Signed-off-by: Andrzej Hajda <[email protected]>
Signed-off-by: Jacek Anaszewski <[email protected]>
Acked-by: Kyungmin Park <[email protected]>
Cc: Bryan Wu <[email protected]>
Cc: Richard Purdie <[email protected]>
Cc: SangYoung Son <[email protected]>
Cc: Samuel Ortiz <[email protected]>
Cc: Lee Jones <[email protected]>
---
drivers/leds/Kconfig | 10 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-max77693.c | 794 ++++++++++++++++++++++++++++++++++++++++++
drivers/mfd/max77693.c | 2 +-
include/linux/mfd/max77693.h | 38 ++
5 files changed, 844 insertions(+), 1 deletion(-)
create mode 100644 drivers/leds/leds-max77693.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 1e1c81f..b2152a6 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -462,6 +462,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 8861b86..64f6234 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..979736c
--- /dev/null
+++ b/drivers/leds/leds-max77693.c
@@ -0,0 +1,794 @@
+/*
+ * Copyright (C) 2014, Samsung Electronics Co., Ltd.
+ *
+ * Authors: Andrzej Hajda <[email protected]>
+ * 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 <asm/div64.h>
+#include <linux/leds_flash.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <media/v4l2-flash.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/max77693.h>
+#include <linux/mfd/max77693-private.h>
+
+#define MAX77693_LED_NAME "max77693-flash"
+
+#define MAX77693_TORCH_IOUT_BITS 4
+
+#define MAX77693_TORCH_NO_TIMER 0x40
+#define MAX77693_FLASH_TIMER_LEVEL 0x80
+
+#define MAX77693_FLASH_EN_OFF 0
+#define MAX77693_FLASH_EN_FLASH 1
+#define MAX77693_FLASH_EN_TORCH 2
+#define MAX77693_FLASH_EN_ON 3
+
+#define MAX77693_FLASH_EN1_SHIFT 6
+#define MAX77693_FLASH_EN2_SHIFT 4
+#define MAX77693_TORCH_EN1_SHIFT 2
+#define MAX77693_TORCH_EN2_SHIFT 0
+
+#define MAX77693_FLASH_LOW_BATTERY_EN 0x80
+
+#define MAX77693_FLASH_BOOST_FIXED 0x04
+#define MAX77693_FLASH_BOOST_LEDNUM_2 0x80
+
+#define MAX77693_FLASH_TIMEOUT_MIN 62500
+#define MAX77693_FLASH_TIMEOUT_MAX 1000000
+#define MAX77693_FLASH_TIMEOUT_STEP 62500
+
+#define MAX77693_TORCH_TIMEOUT_MIN 262000
+#define MAX77693_TORCH_TIMEOUT_MAX 15728000
+
+#define MAX77693_FLASH_IOUT_MIN 15625
+#define MAX77693_FLASH_IOUT_MAX_1LED 1000000
+#define MAX77693_FLASH_IOUT_MAX_2LEDS 625000
+#define MAX77693_FLASH_IOUT_STEP 15625
+
+#define MAX77693_TORCH_IOUT_MIN 15625
+#define MAX77693_TORCH_IOUT_MAX 250000
+#define MAX77693_TORCH_IOUT_STEP 15625
+
+#define MAX77693_FLASH_VSYS_MIN 2400
+#define MAX77693_FLASH_VSYS_MAX 3400
+#define MAX77693_FLASH_VSYS_STEP 33
+
+#define MAX77693_FLASH_VOUT_MIN 3300
+#define MAX77693_FLASH_VOUT_MAX 5500
+#define MAX77693_FLASH_VOUT_STEP 25
+#define MAX77693_FLASH_VOUT_RMIN 0x0c
+
+#define MAX77693_LED_STATUS_FLASH_ON (1 << 3)
+#define MAX77693_LED_STATUS_TORCH_ON (1 << 2)
+
+#define MAX77693_LED_FLASH_INT_FLED2_OPEN (1 << 0)
+#define MAX77693_LED_FLASH_INT_FLED2_SHORT (1 << 1)
+#define MAX77693_LED_FLASH_INT_FLED1_OPEN (1 << 2)
+#define MAX77693_LED_FLASH_INT_FLED1_SHORT (1 << 3)
+#define MAX77693_LED_FLASH_INT_OVER_CURRENT (1 << 4)
+
+#define MAX77693_MODE_OFF 0
+#define MAX77693_MODE_FLASH (1 << 0)
+#define MAX77693_MODE_TORCH (1 << 1)
+#define MAX77693_MODE_FLASH_EXTERNAL (1 << 2)
+
+enum {
+ FLASH1,
+ FLASH2,
+ TORCH1,
+ TORCH2
+};
+
+enum {
+ FLASH,
+ TORCH
+};
+
+struct max77693_led {
+ struct regmap *regmap;
+ struct platform_device *pdev;
+ struct max77693_led_platform_data *pdata;
+ struct mutex lock;
+
+ struct led_classdev ldev;
+
+ unsigned int torch_brightness;
+ struct work_struct work_brightness_set;
+ unsigned int mode_flags;
+};
+
+static u8 max77693_led_iout_to_reg(u32 ua)
+{
+ if (ua < MAX77693_FLASH_IOUT_MIN)
+ ua = MAX77693_FLASH_IOUT_MIN;
+ return (ua - MAX77693_FLASH_IOUT_MIN) / MAX77693_FLASH_IOUT_STEP;
+}
+
+static u8 max77693_flash_timeout_to_reg(u32 us)
+{
+ return (us - MAX77693_FLASH_TIMEOUT_MIN) / MAX77693_FLASH_TIMEOUT_STEP;
+}
+
+static const u32 max77693_torch_timeouts[] = {
+ 262000, 524000, 786000, 1048000,
+ 1572000, 2096000, 2620000, 3144000,
+ 4193000, 5242000, 6291000, 7340000,
+ 9437000, 11534000, 13631000, 1572800
+};
+
+static u8 max77693_torch_timeout_to_reg(u32 us)
+{
+ int i, b = 0, e = ARRAY_SIZE(max77693_torch_timeouts);
+
+ while (e - b > 1) {
+ i = b + (e - b) / 2;
+ if (us < max77693_torch_timeouts[i])
+ e = i;
+ else
+ b = i;
+ }
+ return b;
+}
+
+static struct max77693_led *ldev_to_led(struct led_classdev *ldev)
+{
+ return container_of(ldev, struct max77693_led, ldev);
+}
+
+static u32 max77693_torch_timeout_from_reg(u8 reg)
+{
+ return max77693_torch_timeouts[reg];
+}
+
+static u8 max77693_led_vsys_to_reg(u32 mv)
+{
+ return ((mv - MAX77693_FLASH_VSYS_MIN) / MAX77693_FLASH_VSYS_STEP) << 2;
+}
+
+static u8 max77693_led_vout_to_reg(u32 mv)
+{
+ return (mv - MAX77693_FLASH_VOUT_MIN) / MAX77693_FLASH_VOUT_STEP +
+ MAX77693_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 / MAX77693_FLASH_IOUT_STEP * MAX77693_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 & MAX77693_MODE_TORCH) {
+ if (p->trigger[TORCH1] & MAX77693_LED_TRIG_SOFT)
+ v |= MAX77693_FLASH_EN_ON << MAX77693_TORCH_EN1_SHIFT;
+ if (p->trigger[TORCH2] & MAX77693_LED_TRIG_SOFT)
+ v |= MAX77693_FLASH_EN_ON << MAX77693_TORCH_EN2_SHIFT;
+ }
+
+ if (mode & MAX77693_MODE_FLASH) {
+ if (p->trigger[FLASH1] & MAX77693_LED_TRIG_SOFT)
+ v |= MAX77693_FLASH_EN_ON << MAX77693_FLASH_EN1_SHIFT;
+ if (p->trigger[FLASH2] & MAX77693_LED_TRIG_SOFT)
+ v |= MAX77693_FLASH_EN_ON << MAX77693_FLASH_EN2_SHIFT;
+ } else if (mode & MAX77693_MODE_FLASH_EXTERNAL) {
+ if (p->trigger[FLASH1] & MAX77693_LED_TRIG_EXT)
+ v |= MAX77693_FLASH_EN_FLASH << MAX77693_FLASH_EN1_SHIFT;
+ if (p->trigger[FLASH2] & MAX77693_LED_TRIG_EXT)
+ v |= MAX77693_FLASH_EN_FLASH << MAX77693_FLASH_EN2_SHIFT;
+ /*
+ * 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[TORCH1] & MAX77693_LED_TRIG_EXT)
+ v |= MAX77693_FLASH_EN_TORCH << MAX77693_TORCH_EN1_SHIFT;
+ if (p->trigger[TORCH2] & MAX77693_LED_TRIG_EXT)
+ v |= MAX77693_FLASH_EN_TORCH << MAX77693_TORCH_EN2_SHIFT;
+ }
+
+ /* Reset the register only prior setting flash modes */
+ if (mode != MAX77693_MODE_TORCH) {
+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_EN, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ return max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_EN, v);
+}
+
+static int max77693_add_mode(struct max77693_led *led, unsigned int mode)
+{
+ int ret;
+
+ /* Once enabled torch mode is active until turned off */
+ if ((mode == MAX77693_MODE_TORCH) &&
+ (led->mode_flags & MAX77693_MODE_TORCH))
+ return 0;
+
+ /*
+ * FLASH_EXTERNAL mode activates HW triggered flash and torch
+ * modes in the device. The related register settings interfere
+ * with SW triggerred modes, thus clear them to ensure proper
+ * device configuration.
+ */
+ if (mode == MAX77693_MODE_FLASH_EXTERNAL)
+ led->mode_flags &= (~MAX77693_MODE_TORCH &
+ ~MAX77693_MODE_FLASH);
+
+ 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
+ * spurous flash strobing on each successive torch mode
+ * setting.
+ */
+ if ((mode == MAX77693_MODE_FLASH) ||
+ (mode == MAX77693_MODE_FLASH_EXTERNAL))
+ led->mode_flags &= ~mode;
+
+ return 0;
+}
+
+static int max77693_clear_mode(struct max77693_led *led, unsigned int mode)
+{
+ led->mode_flags &= ~mode;
+
+ return max77693_set_mode(led, led->mode_flags);
+}
+
+static int max77693_set_torch_current(struct max77693_led *led,
+ u32 micro_amp)
+{
+ struct max77693_led_platform_data *p = led->pdata;
+ struct regmap *rmap = led->regmap;
+ u32 iout[2];
+ u8 v, iout1_reg, iout2_reg;
+ int ret;
+
+ max77693_calc_iout(iout, micro_amp, &p->iout[TORCH1]);
+ iout1_reg = max77693_led_iout_to_reg(iout[0]);
+ iout2_reg = max77693_led_iout_to_reg(iout[1]);
+
+ v = iout1_reg | (iout2_reg << MAX77693_TORCH_IOUT_BITS);
+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCH, v);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int max77693_set_flash_current(struct max77693_led *led,
+ u32 micro_amp)
+{
+ struct max77693_led_platform_data *p = led->pdata;
+ struct regmap *rmap = led->regmap;
+ u32 iout[2];
+ u8 iout1_reg, iout2_reg;
+ int ret;
+
+ max77693_calc_iout(iout, micro_amp, &p->iout[FLASH1]);
+ iout1_reg = max77693_led_iout_to_reg(iout[0]);
+ iout2_reg = max77693_led_iout_to_reg(iout[1]);
+
+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH1, iout1_reg);
+ if (ret < 0)
+ goto error_ret;
+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH2, iout2_reg);
+ if (ret < 0)
+ goto error_ret;
+
+error_ret:
+ 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;
+
+ v = max77693_flash_timeout_to_reg(timeout);
+
+ if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
+ v |= MAX77693_FLASH_TIMER_LEVEL;
+
+ return max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
+}
+
+static int max77693_strobe_status_get(struct max77693_led *led)
+{
+ struct regmap *rmap = led->regmap;
+ u8 v;
+ int ret;
+
+ ret = max77693_read_reg(rmap, MAX77693_LED_REG_FLASH_INT_STATUS, &v);
+ if (ret < 0)
+ return ret;
+
+ return !!(v & MAX77693_LED_STATUS_FLASH_ON);
+}
+
+static int max77693_int_flag_get(struct max77693_led *led, u8 *v)
+{
+ struct regmap *rmap = led->regmap;
+
+ return max77693_read_reg(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 ret;
+ u8 v;
+
+ ret = max77693_set_torch_current(led, p->iout[TORCH1] +
+ p->iout[TORCH2]);
+ if (ret < 0)
+ return ret;
+
+ ret = max77693_set_flash_current(led, p->iout[FLASH1] +
+ p->iout[FLASH2]);
+ if (ret < 0)
+ return ret;
+
+ if (p->timeout[TORCH] > 0)
+ v = max77693_torch_timeout_to_reg(p->timeout[TORCH]);
+ else
+ v = MAX77693_TORCH_NO_TIMER;
+ if (p->trigger_type[TORCH] == MAX77693_LED_TRIG_TYPE_LEVEL)
+ v |= MAX77693_FLASH_TIMER_LEVEL;
+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCHTIMER, v);
+ if (ret < 0)
+ return ret;
+
+ v = max77693_flash_timeout_to_reg(p->timeout[FLASH]);
+ if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
+ v |= MAX77693_FLASH_TIMER_LEVEL;
+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
+ if (ret < 0)
+ return ret;
+
+ if (p->low_vsys > 0)
+ v = max77693_led_vsys_to_reg(p->low_vsys) |
+ MAX77693_FLASH_LOW_BATTERY_EN;
+ else
+ v = 0;
+
+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_MAX_FLASH1, v);
+ if (ret < 0)
+ return ret;
+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_MAX_FLASH2, 0);
+ if (ret < 0)
+ return ret;
+
+ if (p->boost_mode[FLASH1] == MAX77693_LED_BOOST_FIXED ||
+ p->boost_mode[FLASH2] == MAX77693_LED_BOOST_FIXED)
+ v = MAX77693_FLASH_BOOST_FIXED;
+ else
+ v = p->boost_mode[FLASH1] | (p->boost_mode[FLASH2] << 1);
+ if (p->boost_mode[FLASH1] && p->boost_mode[FLASH2])
+ v |= MAX77693_FLASH_BOOST_LEDNUM_2;
+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_VOUT_CNTL, v);
+ if (ret < 0)
+ return ret;
+
+ v = max77693_led_vout_to_reg(p->boost_vout);
+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
+ if (ret < 0)
+ return ret;
+
+ return max77693_set_mode(led, MAX77693_MODE_OFF);
+}
+
+/* LED subsystem callbacks */
+
+static void max77693_brightness_set_work(struct work_struct *work)
+{
+ struct max77693_led *led =
+ container_of(work, struct max77693_led, work_brightness_set);
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (led->torch_brightness == 0) {
+ ret = max77693_clear_mode(led, MAX77693_MODE_TORCH);
+ 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->torch_brightness *
+ MAX77693_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, MAX77693_MODE_TORCH);
+ if (ret < 0)
+ dev_dbg(&led->pdev->dev, "Failed to set torch mode (%d)\n",
+ ret);
+unlock:
+ mutex_unlock(&led->lock);
+}
+
+static void max77693_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct max77693_led *led = ldev_to_led(led_cdev);
+
+ led->torch_brightness = value;
+ schedule_work(&led->work_brightness_set);
+}
+
+static int max77693_led_flash_strobe_get(struct led_classdev *led_cdev)
+{
+ struct max77693_led *led = ldev_to_led(led_cdev);
+ int ret;
+
+ mutex_lock(&led->lock);
+ ret = max77693_strobe_status_get(led);
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_flash_fault_get(struct led_classdev *led_cdev,
+ u32 *fault)
+{
+ struct max77693_led *led = ldev_to_led(led_cdev);
+ u8 v;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ ret = max77693_int_flag_get(led, &v);
+ if (ret < 0)
+ goto unlock;
+
+ *fault = 0;
+
+ if (v & MAX77693_LED_FLASH_INT_FLED2_OPEN ||
+ v & MAX77693_LED_FLASH_INT_FLED1_OPEN)
+ *fault |= LED_FAULT_OVER_VOLTAGE;
+ if (v & MAX77693_LED_FLASH_INT_FLED2_SHORT ||
+ v & MAX77693_LED_FLASH_INT_FLED1_SHORT)
+ *fault |= LED_FAULT_SHORT_CIRCUIT;
+ if (v & MAX77693_LED_FLASH_INT_OVER_CURRENT)
+ *fault |= LED_FAULT_OVER_CURRENT;
+unlock:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int max77693_led_flash_strobe_set(struct led_classdev *led_cdev,
+ bool state)
+{
+ struct max77693_led *led = ldev_to_led(led_cdev);
+ struct led_flash *flash = led_cdev->flash;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (flash->external_strobe) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (!state) {
+ ret = max77693_clear_mode(led, MAX77693_MODE_FLASH);
+ goto unlock;
+ }
+
+ ret = max77693_add_mode(led, MAX77693_MODE_FLASH);
+ if (ret < 0)
+ goto unlock;
+unlock:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int max77693_led_external_strobe_set(struct led_classdev *led_cdev,
+ bool enable)
+{
+ struct max77693_led *led = ldev_to_led(led_cdev);
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (enable)
+ ret = max77693_add_mode(led, MAX77693_MODE_FLASH_EXTERNAL);
+ else
+ ret = max77693_clear_mode(led, MAX77693_MODE_FLASH_EXTERNAL);
+
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_flash_brightness_set(struct led_classdev *led_cdev,
+ u32 brightness)
+{
+ struct max77693_led *led = ldev_to_led(led_cdev);
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ ret = max77693_set_flash_current(led, brightness);
+ if (ret < 0)
+ goto unlock;
+unlock:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int max77693_led_flash_timeout_set(struct led_classdev *led_cdev,
+ u32 timeout)
+{
+ struct max77693_led *led = ldev_to_led(led_cdev);
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ ret = max77693_set_timeout(led, timeout);
+ if (ret < 0)
+ goto unlock;
+
+unlock:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static void max77693_led_parse_dt(struct max77693_led_platform_data *p,
+ struct device_node *node)
+{
+ of_property_read_u32_array(node, "maxim,iout", p->iout, 4);
+ of_property_read_u32_array(node, "maxim,trigger", p->trigger, 4);
+ of_property_read_u32_array(node, "maxim,trigger-type", p->trigger_type,
+ 2);
+ of_property_read_u32_array(node, "maxim,timeout", p->timeout, 2);
+ of_property_read_u32_array(node, "maxim,boost-mode", p->boost_mode, 2);
+ of_property_read_u32(node, "maxim,boost-vout", &p->boost_vout);
+ of_property_read_u32(node, "maxim,vsys-min", &p->low_vsys);
+}
+
+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;
+
+ for (i = 0; i < 2; ++i)
+ clamp_align(&p->boost_mode[i], MAX77693_LED_BOOST_NONE,
+ MAX77693_LED_BOOST_FIXED, 1);
+ /* boost, if enabled, should be the same on both leds */
+ if (p->boost_mode[0] != MAX77693_LED_BOOST_NONE &&
+ p->boost_mode[1] != MAX77693_LED_BOOST_NONE)
+ p->boost_mode[1] = p->boost_mode[0];
+
+ max = (p->boost_mode[FLASH1] && p->boost_mode[FLASH2]) ?
+ MAX77693_FLASH_IOUT_MAX_2LEDS : MAX77693_FLASH_IOUT_MAX_1LED;
+
+ clamp_align(&p->iout[FLASH1], MAX77693_FLASH_IOUT_MIN,
+ max, MAX77693_FLASH_IOUT_STEP);
+ clamp_align(&p->iout[FLASH2], MAX77693_FLASH_IOUT_MIN,
+ max, MAX77693_FLASH_IOUT_STEP);
+ clamp_align(&p->iout[TORCH1], MAX77693_TORCH_IOUT_MIN,
+ MAX77693_TORCH_IOUT_MAX, MAX77693_TORCH_IOUT_STEP);
+ clamp_align(&p->iout[TORCH2], MAX77693_TORCH_IOUT_MIN,
+ MAX77693_TORCH_IOUT_MAX, MAX77693_TORCH_IOUT_STEP);
+
+ for (i = 0; i < 4; ++i)
+ clamp_align(&p->trigger[i], 0, 7, 1);
+ for (i = 0; i < 2; ++i)
+ clamp_align(&p->trigger_type[i], MAX77693_LED_TRIG_TYPE_EDGE,
+ MAX77693_LED_TRIG_TYPE_LEVEL, 1);
+
+ clamp_align(&p->timeout[FLASH], MAX77693_FLASH_TIMEOUT_MIN,
+ MAX77693_FLASH_TIMEOUT_MAX, MAX77693_FLASH_TIMEOUT_STEP);
+
+ if (p->timeout[TORCH]) {
+ clamp_align(&p->timeout[TORCH], MAX77693_TORCH_TIMEOUT_MIN,
+ MAX77693_TORCH_TIMEOUT_MAX, 1);
+ p->timeout[TORCH] = max77693_torch_timeout_from_reg(
+ max77693_torch_timeout_to_reg(p->timeout[TORCH]));
+ }
+
+ clamp_align(&p->boost_vout, MAX77693_FLASH_VOUT_MIN,
+ MAX77693_FLASH_VOUT_MAX, MAX77693_FLASH_VOUT_STEP);
+
+ if (p->low_vsys) {
+ clamp_align(&p->low_vsys, MAX77693_FLASH_VSYS_MIN,
+ MAX77693_FLASH_VSYS_MAX, MAX77693_FLASH_VSYS_STEP);
+ }
+}
+
+static int max77693_led_get_platform_data(struct max77693_led *led)
+{
+ struct max77693_led_platform_data *p;
+ struct device *dev = &led->pdev->dev;
+
+ if (dev->of_node) {
+ p = devm_kzalloc(dev, sizeof(*led->pdata), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ max77693_led_parse_dt(p, dev->of_node);
+ } else {
+ p = dev_get_platdata(dev);
+ if (!p)
+ return -ENODEV;
+ }
+ led->pdata = p;
+
+ max77693_led_validate_platform_data(p);
+
+ return 0;
+}
+
+static struct led_flash led_flash = {
+ .ops = {
+ .brightness_set = max77693_led_flash_brightness_set,
+ .strobe_set = max77693_led_flash_strobe_set,
+ .strobe_get = max77693_led_flash_strobe_get,
+ .timeout_set = max77693_led_flash_timeout_set,
+ .external_strobe_set = max77693_led_external_strobe_set,
+ .fault_get = max77693_led_flash_fault_get,
+ },
+ .has_flash_led = true,
+};
+
+static void max77693_init_led_controls(struct led_classdev *led_cdev,
+ struct max77693_led_platform_data *p)
+{
+ struct led_flash *flash = led_cdev->flash;
+ struct led_ctrl *c;
+
+ /*
+ * brightness_ctrl and fault_flags are used only
+ * for initializing related V4L2 controls.
+ */
+#ifdef CONFIG_V4L2_FLASH
+ flash->fault_flags = V4L2_FLASH_FAULT_OVER_VOLTAGE |
+ V4L2_FLASH_FAULT_SHORT_CIRCUIT |
+ V4L2_FLASH_FAULT_OVER_CURRENT;
+
+ c = &led_cdev->brightness_ctrl;
+ c->min = (p->iout[TORCH1] != 0 && p->iout[TORCH2] != 0) ?
+ MAX77693_TORCH_IOUT_MIN * 2 :
+ MAX77693_TORCH_IOUT_MIN;
+ c->max = p->iout[TORCH1] + p->iout[TORCH2];
+ c->step = MAX77693_TORCH_IOUT_STEP;
+ c->val = p->iout[TORCH1] + p->iout[TORCH2];
+#endif
+
+ c = &flash->brightness;
+ c->min = (p->iout[FLASH1] != 0 && p->iout[FLASH2] != 0) ?
+ MAX77693_FLASH_IOUT_MIN * 2 :
+ MAX77693_FLASH_IOUT_MIN;
+ c->max = p->iout[FLASH1] + p->iout[FLASH2];
+ c->step = MAX77693_FLASH_IOUT_STEP;
+ c->val = p->iout[FLASH1] + p->iout[FLASH2];
+
+ c = &flash->timeout;
+ c->min = MAX77693_FLASH_TIMEOUT_MIN;
+ c->max = MAX77693_FLASH_TIMEOUT_MAX;
+ c->step = MAX77693_FLASH_TIMEOUT_STEP;
+ c->val = p->timeout[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 led_classdev *led_cdev;
+ int ret;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pdev = pdev;
+ led_cdev = &led->ldev;
+ led->regmap = iodev->regmap;
+ platform_set_drvdata(pdev, led);
+ ret = max77693_led_get_platform_data(led);
+ if (ret < 0)
+ return -EINVAL;
+ p = led->pdata;
+
+ mutex_init(&led->lock);
+
+ INIT_WORK(&led->work_brightness_set, max77693_brightness_set_work);
+
+ /* LED class device initialization */
+ led_cdev->name = MAX77693_LED_NAME;
+ led_cdev->brightness_set = max77693_led_brightness_set;
+ led_cdev->max_brightness = (p->iout[TORCH1] + p->iout[TORCH2]) /
+ MAX77693_TORCH_IOUT_STEP;
+
+ if ((p->trigger[FLASH1] & MAX77693_LED_TRIG_FLASH) ||
+ (p->trigger[FLASH2] & MAX77693_LED_TRIG_FLASH))
+ led_flash.has_external_strobe = true;
+ led_cdev->flash = &led_flash;
+
+ max77693_init_led_controls(led_cdev, p);
+
+ /* Register in the LED subsystem. */
+ ret = led_classdev_flash_register(&pdev->dev, led_cdev);
+ if (ret < 0)
+ return -EINVAL;
+
+ ret = max77693_setup(led);
+
+ return 0;
+}
+
+static int max77693_led_remove(struct platform_device *pdev)
+{
+ struct max77693_led *led = platform_get_drvdata(pdev);
+
+ led_classdev_flash_unregister(&led->ldev);
+
+ return 0;
+}
+
+static struct of_device_id max77693_led_dt_match[] = {
+ {.compatible = "maxim,max77693-flash"},
+ {},
+};
+
+static struct platform_driver max77693_led_driver = {
+ .probe = max77693_led_probe,
+ .remove = max77693_led_remove,
+ .driver = {
+ .name = "max77693-flash",
+ .owner = THIS_MODULE,
+ .of_match_table = max77693_led_dt_match,
+ },
+};
+
+module_platform_driver(max77693_led_driver);
+
+MODULE_AUTHOR("Andrzej Hajda <[email protected]>");
+MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
+MODULE_DESCRIPTION("Maxim MAX77693 led flash driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/max77693.c b/drivers/mfd/max77693.c
index c5535f0..f061aa8 100644
--- a/drivers/mfd/max77693.c
+++ b/drivers/mfd/max77693.c
@@ -44,7 +44,7 @@
static const struct mfd_cell max77693_devs[] = {
{ .name = "max77693-pmic", },
{ .name = "max77693-charger", },
- { .name = "max77693-flash", },
+ { .name = "max77693-flash", .of_compatible = "maxim,max77693-flash", },
{ .name = "max77693-muic", },
{ .name = "max77693-haptic", },
};
diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
index 3f3dc45..f2285b7 100644
--- a/include/linux/mfd/max77693.h
+++ b/include/linux/mfd/max77693.h
@@ -63,6 +63,43 @@ struct max77693_muic_platform_data {
int path_uart;
};

+/* MAX77693 led flash */
+
+/* triggers */
+enum max77693_led_trigger {
+ MAX77693_LED_TRIG_OFF,
+ MAX77693_LED_TRIG_FLASH,
+ MAX77693_LED_TRIG_TORCH,
+ MAX77693_LED_TRIG_EXT,
+ MAX77693_LED_TRIG_SOFT,
+};
+
+
+/* trigger types */
+enum max77693_led_trigger_type {
+ MAX77693_LED_TRIG_TYPE_EDGE,
+ MAX77693_LED_TRIG_TYPE_LEVEL,
+};
+
+/* boost modes */
+enum max77693_led_boost_mode {
+ MAX77693_LED_BOOST_NONE,
+ MAX77693_LED_BOOST_ADAPTIVE,
+ MAX77693_LED_BOOST_FIXED,
+};
+
+struct max77693_led_platform_data {
+ u32 iout[4];
+ u32 trigger[4];
+ u32 trigger_type[2];
+ u32 timeout[2];
+ u32 boost_mode[2];
+ u32 boost_vout;
+ u32 low_vsys;
+};
+
+/* MAX77693 */
+
struct max77693_platform_data {
/* regulator data */
struct max77693_regulator_data *regulators;
@@ -70,5 +107,6 @@ struct max77693_platform_data {

/* muic data */
struct max77693_muic_platform_data *muic_data;
+ struct max77693_led_platform_data *led_data;
};
#endif /* __LINUX_MFD_MAX77693_H */
--
1.7.9.5

2014-04-11 14:57:50

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v3 5/5] media: Add registration helpers for V4L2 flash sub-devices

This patch adds helper functions for registering/unregistering
LED class flash devices as V4L2 subdevs. 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]>
---
drivers/media/v4l2-core/Kconfig | 10 +
drivers/media/v4l2-core/Makefile | 2 +
drivers/media/v4l2-core/v4l2-flash.c | 393 ++++++++++++++++++++++++++++++++++
include/media/v4l2-flash.h | 119 ++++++++++
4 files changed, 524 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 2189bfb..1f8514d 100644
--- a/drivers/media/v4l2-core/Kconfig
+++ b/drivers/media/v4l2-core/Kconfig
@@ -35,6 +35,16 @@ config V4L2_MEM2MEM_DEV
tristate
depends on VIDEOBUF2_CORE

+# Used by LED subsystem flash drivers
+config V4L2_FLASH
+ bool "Enable support for Flash sub-devices"
+ 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 c6ae7ba..8e37ab4 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) += 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..f1be332
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-flash.c
@@ -0,0 +1,393 @@
+/*
+ * 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/leds_flash.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-flash.h>
+
+static inline enum led_brightness v4l2_flash_intensity_to_led_brightness(
+ struct led_ctrl *config,
+ u32 intensity)
+{
+ return intensity / config->step;
+}
+
+static inline u32 v4l2_flash_led_brightness_to_intensity(
+ struct led_ctrl *config,
+ enum led_brightness brightness)
+{
+ return brightness * config->step;
+}
+
+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 *led_cdev = v4l2_flash->led_cdev;
+ struct led_flash *flash = led_cdev->flash;
+ struct v4l2_flash_ctrl *ctrl = &v4l2_flash->ctrl;
+ u32 fault;
+ int ret;
+
+ switch (c->id) {
+ case V4L2_CID_FLASH_TORCH_INTENSITY:
+ if (ctrl->led_mode->val == V4L2_FLASH_LED_MODE_TORCH) {
+ ret = v4l2_call_flash_op(brightness_update, led_cdev);
+ if (ret < 0)
+ return ret;
+ ctrl->torch_intensity->val =
+ v4l2_flash_led_brightness_to_intensity(
+ &led_cdev->brightness_ctrl,
+ led_cdev->brightness);
+ }
+ return 0;
+ case V4L2_CID_FLASH_INTENSITY:
+ ret = v4l2_call_flash_op(flash_brightness_update, led_cdev);
+ if (ret < 0)
+ return ret;
+ /* no conversion is needed */
+ c->val = flash->brightness.val;
+ return 0;
+ case V4L2_CID_FLASH_INDICATOR_INTENSITY:
+ ret = v4l2_call_flash_op(indicator_brightness_update, led_cdev);
+ if (ret < 0)
+ return ret;
+ /* no conversion is needed */
+ c->val = flash->indicator_brightness->val;
+ return 0;
+ case V4L2_CID_FLASH_STROBE_STATUS:
+ ret = v4l2_call_flash_op(strobe_get, led_cdev);
+ if (ret < 0)
+ return ret;
+ c->val = !!ret;
+ return 0;
+ case V4L2_CID_FLASH_FAULT:
+ /* led faults map directly to V4L2 flash faults */
+ ret = v4l2_call_flash_op(fault_get, led_cdev, &fault);
+ if (!ret)
+ c->val = fault;
+ return ret;
+ 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 *led_cdev = v4l2_flash->led_cdev;
+ struct v4l2_flash_ctrl *ctrl = &v4l2_flash->ctrl;
+ enum led_brightness torch_brightness;
+ bool external_strobe;
+ int ret;
+
+ switch (c->id) {
+ case V4L2_CID_FLASH_LED_MODE:
+ switch (c->val) {
+ case V4L2_FLASH_LED_MODE_NONE:
+ v4l2_call_flash_op(brightness_set, led_cdev, 0);
+ return v4l2_call_flash_op(strobe_set, led_cdev, false);
+ case V4L2_FLASH_LED_MODE_FLASH:
+ /* Turn off torch LED */
+ v4l2_call_flash_op(brightness_set, led_cdev, 0);
+ external_strobe = (ctrl->source->val ==
+ V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
+ return v4l2_call_flash_op(external_strobe_set, led_cdev,
+ external_strobe);
+ case V4L2_FLASH_LED_MODE_TORCH:
+ /* Stop flash strobing */
+ ret = v4l2_call_flash_op(strobe_set, led_cdev, false);
+ if (ret)
+ return ret;
+ /* torch is always triggered by software */
+ ret = v4l2_call_flash_op(external_strobe_set, led_cdev,
+ false);
+ if (ret)
+ return ret;
+
+ torch_brightness =
+ v4l2_flash_intensity_to_led_brightness(
+ &led_cdev->brightness_ctrl,
+ ctrl->torch_intensity->val);
+ v4l2_call_flash_op(brightness_set, led_cdev,
+ torch_brightness);
+ return ret;
+ }
+ break;
+ case V4L2_CID_FLASH_STROBE_SOURCE:
+ return v4l2_call_flash_op(external_strobe_set, led_cdev,
+ c->val == V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
+ case V4L2_CID_FLASH_STROBE:
+ if (ctrl->led_mode->val != V4L2_FLASH_LED_MODE_FLASH ||
+ ctrl->source->val != V4L2_FLASH_STROBE_SOURCE_SOFTWARE)
+ return -EINVAL;
+ return v4l2_call_flash_op(strobe_set, led_cdev, true);
+ case V4L2_CID_FLASH_STROBE_STOP:
+ return v4l2_call_flash_op(strobe_set, led_cdev, false);
+ case V4L2_CID_FLASH_TIMEOUT:
+ ret = v4l2_call_flash_op(timeout_set, led_cdev, c->val);
+ case V4L2_CID_FLASH_INTENSITY:
+ /* no conversion is needed */
+ return v4l2_call_flash_op(flash_brightness_set, led_cdev,
+ c->val);
+ case V4L2_CID_FLASH_INDICATOR_INTENSITY:
+ /* no conversion is needed */
+ return v4l2_call_flash_op(indicator_brightness_set, led_cdev,
+ c->val);
+ case V4L2_CID_FLASH_TORCH_INTENSITY:
+ if (ctrl->led_mode->val == V4L2_FLASH_LED_MODE_TORCH) {
+ torch_brightness =
+ v4l2_flash_intensity_to_led_brightness(
+ &led_cdev->brightness_ctrl,
+ ctrl->torch_intensity->val);
+ v4l2_call_flash_op(brightness_set, led_cdev,
+ torch_brightness);
+ }
+ 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 int v4l2_flash_init_controls(struct v4l2_flash *v4l2_flash)
+
+{
+ struct led_classdev *led_cdev = v4l2_flash->led_cdev;
+ struct led_flash *flash = led_cdev->flash;
+ bool has_indicator = flash->indicator_brightness;
+ struct v4l2_ctrl *ctrl;
+ struct led_ctrl *ctrl_cfg;
+ unsigned int mask;
+ int ret, max, num_ctrls;
+
+ num_ctrls = flash->has_flash_led ? 8 : 2;
+ if (flash->fault_flags)
+ ++num_ctrls;
+ if (has_indicator)
+ ++num_ctrls;
+
+ v4l2_ctrl_handler_init(&v4l2_flash->hdl, num_ctrls);
+
+ mask = 1 << V4L2_FLASH_LED_MODE_NONE |
+ 1 << V4L2_FLASH_LED_MODE_TORCH;
+ if (flash->has_flash_led)
+ mask |= 1 << V4L2_FLASH_LED_MODE_FLASH;
+
+ /* Configure FLASH_LED_MODE ctrl */
+ v4l2_flash->ctrl.led_mode = v4l2_ctrl_new_std_menu(
+ &v4l2_flash->hdl,
+ &v4l2_flash_ctrl_ops, V4L2_CID_FLASH_LED_MODE,
+ V4L2_FLASH_LED_MODE_TORCH, ~mask,
+ V4L2_FLASH_LED_MODE_NONE);
+
+ /* Configure TORCH_INTENSITY ctrl */
+ ctrl_cfg = &led_cdev->brightness_ctrl;
+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
+ V4L2_CID_FLASH_TORCH_INTENSITY,
+ ctrl_cfg->min, ctrl_cfg->max,
+ ctrl_cfg->step, ctrl_cfg->val);
+ if (ctrl)
+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
+ v4l2_flash->ctrl.torch_intensity = ctrl;
+
+ if (flash->has_flash_led) {
+ /* Configure FLASH_STROBE_SOURCE ctrl */
+ mask = 1 << V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
+
+ if (flash->has_external_strobe) {
+ mask |= 1 << V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
+ max = V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
+ } else {
+ max = V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
+ }
+
+ v4l2_flash->ctrl.source = v4l2_ctrl_new_std_menu(
+ &v4l2_flash->hdl,
+ &v4l2_flash_ctrl_ops,
+ V4L2_CID_FLASH_STROBE_SOURCE,
+ max,
+ ~mask,
+ V4L2_FLASH_STROBE_SOURCE_SOFTWARE);
+
+ /* Configure FLASH_STROBE ctrl */
+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
+ V4L2_CID_FLASH_STROBE, 0, 1, 1, 0);
+
+ /* Configure FLASH_STROBE_STOP ctrl */
+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
+ V4L2_CID_FLASH_STROBE_STOP,
+ 0, 1, 1, 0);
+
+ /* Configure FLASH_STROBE_STATUS ctrl */
+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
+ V4L2_CID_FLASH_STROBE_STATUS,
+ 0, 1, 1, 1);
+ if (ctrl)
+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE |
+ V4L2_CTRL_FLAG_READ_ONLY;
+
+ /* Configure FLASH_TIMEOUT ctrl */
+ ctrl_cfg = &flash->timeout;
+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
+ V4L2_CID_FLASH_TIMEOUT, ctrl_cfg->min,
+ ctrl_cfg->max, ctrl_cfg->step,
+ ctrl_cfg->val);
+ if (ctrl)
+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
+
+ /* Configure FLASH_INTENSITY ctrl */
+ ctrl_cfg = &flash->brightness;
+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
+ V4L2_CID_FLASH_INTENSITY,
+ ctrl_cfg->min, ctrl_cfg->max,
+ ctrl_cfg->step, ctrl_cfg->val);
+ if (ctrl)
+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
+
+ if (flash->fault_flags) {
+ /* Configure FLASH_FAULT ctrl */
+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl,
+ &v4l2_flash_ctrl_ops,
+ V4L2_CID_FLASH_FAULT, 0,
+ flash->fault_flags,
+ 0, 0);
+ if (ctrl)
+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE |
+ V4L2_CTRL_FLAG_READ_ONLY;
+ }
+ if (has_indicator) {
+ /* Configure FLASH_INDICATOR_INTENSITY ctrl */
+ ctrl_cfg = flash->indicator_brightness;
+ ctrl = v4l2_ctrl_new_std(
+ &v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
+ V4L2_CID_FLASH_INDICATOR_INTENSITY,
+ ctrl_cfg->min, ctrl_cfg->max,
+ ctrl_cfg->step, ctrl_cfg->val);
+ if (ctrl)
+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
+ }
+ }
+
+ if (v4l2_flash->hdl.error) {
+ ret = v4l2_flash->hdl.error;
+ goto error_free;
+ }
+
+ ret = v4l2_ctrl_handler_setup(&v4l2_flash->hdl);
+ if (ret < 0)
+ goto error_free;
+
+ v4l2_flash->subdev.ctrl_handler = &v4l2_flash->hdl;
+
+ return 0;
+
+error_free:
+ 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 *led_cdev = v4l2_flash->led_cdev;
+
+ mutex_lock(&led_cdev->led_lock);
+ v4l2_call_flash_op(sysfs_lock, led_cdev);
+ mutex_unlock(&led_cdev->led_lock);
+
+ return 0;
+}
+
+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 *led_cdev = v4l2_flash->led_cdev;
+
+ mutex_lock(&led_cdev->led_lock);
+ v4l2_call_flash_op(sysfs_unlock, led_cdev);
+ mutex_unlock(&led_cdev->led_lock);
+
+ return 0;
+}
+
+static const struct v4l2_subdev_internal_ops v4l2_flash_subdev_internal_ops = {
+ .open = v4l2_flash_open,
+ .close = v4l2_flash_close,
+};
+
+static struct v4l2_subdev_ops v4l2_flash_subdev_ops = {
+};
+
+int v4l2_flash_init(struct led_classdev *led_cdev,
+ const struct v4l2_flash_ops *ops)
+{
+ struct v4l2_flash *flash = &led_cdev->flash->v4l2_flash;
+ struct v4l2_subdev *sd = &flash->subdev;
+ int ret;
+
+ if (!led_cdev || !ops)
+ return -EINVAL;
+
+ flash->led_cdev = led_cdev;
+ 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);
+
+ flash->ops = ops;
+
+ ret = v4l2_flash_init_controls(flash);
+ 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(sd);
+ if (ret < 0)
+ goto err_init_entity;
+
+ return 0;
+
+err_init_entity:
+ media_entity_cleanup(&sd->entity);
+err_init_controls:
+ v4l2_ctrl_handler_free(sd->ctrl_handler);
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(v4l2_flash_init);
+
+void v4l2_flash_release(struct led_classdev *led_cdev)
+{
+ struct v4l2_flash *flash = &led_cdev->flash->v4l2_flash;
+
+ v4l2_ctrl_handler_free(flash->subdev.ctrl_handler);
+ v4l2_async_unregister_subdev(&flash->subdev);
+ media_entity_cleanup(&flash->subdev.entity);
+}
+EXPORT_SYMBOL_GPL(v4l2_flash_release);
diff --git a/include/media/v4l2-flash.h b/include/media/v4l2-flash.h
new file mode 100644
index 0000000..fe16ddd
--- /dev/null
+++ b/include/media/v4l2-flash.h
@@ -0,0 +1,119 @@
+/*
+ * 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>
+
+#define v4l2_call_flash_op(op, args...) \
+ ((v4l2_flash)->ops->op(args)) \
+
+struct led_classdev;
+enum led_brightness;
+
+struct v4l2_flash_ops {
+ void (*brightness_set)(struct led_classdev *led_cdev,
+ enum led_brightness brightness);
+ int (*brightness_update)(struct led_classdev *led_cdev);
+ int (*flash_brightness_set)(struct led_classdev *led_cdev,
+ u32 brightness);
+ int (*flash_brightness_update)(struct led_classdev *led_cdev);
+ int (*strobe_set)(struct led_classdev *led_cdev,
+ bool state);
+ int (*strobe_get)(struct led_classdev *led_cdev);
+ int (*timeout_set)(struct led_classdev *led_cdev,
+ u32 timeout);
+ int (*indicator_brightness_set)(struct led_classdev *led_cdev,
+ u32 brightness);
+ int (*indicator_brightness_update)(struct led_classdev *led_cdev);
+ int (*external_strobe_set)(struct led_classdev *led_cdev,
+ bool enable);
+ int (*fault_get)(struct led_classdev *led_cdev,
+ u32 *fault);
+ void (*sysfs_lock)(struct led_classdev *led_cdev);
+ void (*sysfs_unlock)(struct led_classdev *led_cdev);
+};
+
+/**
+ * struct v4l2_flash_ctrl - controls that define the sub-dev's state
+ * @source: V4L2_CID_FLASH_STROBE_SOURCE control
+ * @led_mode: V4L2_CID_FLASH_LED_MODE control
+ * @torch_intensity: V4L2_CID_FLASH_TORCH_INTENSITY control
+ */
+struct v4l2_flash_ctrl {
+ struct v4l2_ctrl *source;
+ struct v4l2_ctrl *led_mode;
+ struct v4l2_ctrl *torch_intensity;
+};
+
+/**
+ * struct v4l2_flash - Flash sub-device context
+ * @led_cdev: LED class device controlled by this sub-device
+ * @ops: LED class device ops
+ * @subdev: V4L2 sub-device
+ * @hdl: flash controls handler
+ * @ctrl: state defining controls
+ */
+struct v4l2_flash {
+ struct led_classdev *led_cdev;
+ const struct v4l2_flash_ops *ops;
+
+ struct v4l2_subdev subdev;
+ struct v4l2_ctrl_handler hdl;
+ struct v4l2_flash_ctrl ctrl;
+};
+
+static inline struct v4l2_flash *v4l2_subdev_to_v4l2_flash(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct v4l2_flash, subdev);
+}
+
+static inline struct v4l2_flash *v4l2_ctrl_to_v4l2_flash(struct v4l2_ctrl *c)
+{
+ return container_of(c->handler, struct v4l2_flash, hdl);
+}
+
+#ifdef CONFIG_V4L2_FLASH
+/**
+ * v4l2_flash_init - initialize V4L2 flash led sub-device
+ * @led_cdev: the LED to create subdev upon
+ * @ops: LED subsystem callbacks
+ *
+ * Create V4L2 subdev wrapping given LED subsystem device.
+ */
+int v4l2_flash_init(struct led_classdev *led_cdev,
+ const struct v4l2_flash_ops *ops);
+
+/**
+ * v4l2_flash_release - release V4L2 flash led sub-device
+ * @flash: a structure representing V4L2 flash led device
+ *
+ * Release V4L2 flash led subdev.
+ */
+void v4l2_flash_release(struct led_classdev *led_cdev);
+#else
+static inline int v4l2_flash_init(struct led_classdev *led_cdev,
+ const struct v4l2_flash_ops *ops)
+{
+ return 0;
+}
+
+static inline void v4l2_flash_release(struct led_classdev *led_cdev)
+{
+}
+#endif /* CONFIG_V4L2_FLASH */
+
+#endif /* _V4L2_FLASH_H */
--
1.7.9.5

2014-04-11 14:58:22

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v3 4/5] DT: Add documentation for the mfd Maxim max77693 flash cell

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

Signed-off-by: Andrzej Hajda <[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]>
---
Documentation/devicetree/bindings/mfd/max77693.txt | 57 ++++++++++++++++++++
1 file changed, 57 insertions(+)

diff --git a/Documentation/devicetree/bindings/mfd/max77693.txt b/Documentation/devicetree/bindings/mfd/max77693.txt
index 11921cc..f58d192 100644
--- a/Documentation/devicetree/bindings/mfd/max77693.txt
+++ b/Documentation/devicetree/bindings/mfd/max77693.txt
@@ -27,6 +27,53 @@ Optional properties:

[*] refer Documentation/devicetree/bindings/regulator/regulator.txt

+Optional node:
+- led-flash : the LED submodule device node
+
+Required properties of "led-flash" node:
+- compatible : must be "maxim,max77693-flash"
+
+Optional properties of "led-flash" node:
+- maxim,iout : Array of four maximum intensities in microamperes of the current
+ in order: flash1, flash2, torch1, torch2.
+ Range:
+ flash - 15625 - 1000000 (max 625000 if boost mode
+ is enabled for both outputs),
+ torch - 15625 - 250000.
+- maxim,trigger : Array of flags indicating which trigger can activate given led
+ in order: flash1, flash2, torch1, torch2.
+ Possible flag values (can be combined):
+ 1 - FLASH pin of the chip,
+ 2 - TORCH pin of the chip,
+ 4 - software via I2C command.
+- maxim,trigger-type : Array of trigger types in order: flash, torch.
+ Possible trigger types:
+ 0 - Rising edge of the signal triggers the flash/torch,
+ 1 - Signal level controls duration of the flash/torch.
+- maxim,timeout : Array of timeouts in microseconds after which leds are
+ turned off in order: flash, torch.
+ Range:
+ flash: 62500 - 1000000,
+ torch: 0 (no timeout) - 15728000.
+- maxim,boost-mode : Array of the flash boost modes in order: flash1, flash2.
+ If both current outputs are connected then the same non-zero value
+ has to be set for them. This setting influences also maximum
+ current value for torch and flash modes:
+ flash1 and flash2 set to 1 or 2:
+ - max flash current: 1250 mA (625 mA on each output)
+ - max torch current: 500 mA (250 mA on each output)
+ flash1 or flash2 set to 0:
+ - max flash current: 1000 mA
+ - max torch current: 250 mA
+ Possible values:
+ 0 - no boost,
+ 1 - adaptive mode,
+ 2 - 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.
+
Example:
max77693@66 {
compatible = "maxim,max77693";
@@ -52,4 +99,14 @@ Example:
regulator-boot-on;
};
};
+ led_flash: led-flash {
+ compatible = "maxim,max77693-flash";
+ maxim,iout = <625000 625000 250000 250000>;
+ maxim,trigger = <5 5 6 6>;
+ maxim,trigger-type = <0 1>;
+ maxim,timeout = <500000 0>;
+ maxim,boost-mode = <1 1>;
+ maxim,boost-vout = <5000>;
+ maxim,vsys-min = <2400>;
+ };
};
--
1.7.9.5

2014-04-11 14:59:47

by Jacek Anaszewski

[permalink] [raw]
Subject: [PATCH/RFC v3 2/5] leds: Improve and export led_update_brightness function

led_update_brightness helper function used to be exploited
only locally in the led-class.c module, where its result was
being passed to the brightness_show sysfs callback. With the
introduction of v4l2-flash subdevice the same functionality
became required for reading current brightness from a LED
device. This patch adds checking brightness_get callback
error code and adds the function to the LED subsystem
public API.

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/led-class.c | 6 ------
drivers/leds/led-core.c | 16 ++++++++++++++++
include/linux/leds.h | 10 ++++++++++
3 files changed, 26 insertions(+), 6 deletions(-)

diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index 58f16c3..7f285d7 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -24,12 +24,6 @@

static struct class *leds_class;

-static void led_update_brightness(struct led_classdev *led_cdev)
-{
- if (led_cdev->brightness_get)
- led_cdev->brightness = led_cdev->brightness_get(led_cdev);
-}
-
static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index 71b40d3..376166c 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -126,3 +126,19 @@ void led_set_brightness(struct led_classdev *led_cdev,
__led_set_brightness(led_cdev, brightness);
}
EXPORT_SYMBOL(led_set_brightness);
+
+int led_update_brightness(struct led_classdev *led_cdev)
+{
+ int ret = 0;
+
+ if (led_cdev->brightness_get) {
+ ret = led_cdev->brightness_get(led_cdev);
+ if (ret >= 0) {
+ led_cdev->brightness = ret;
+ return 0;
+ }
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(led_update_brightness);
diff --git a/include/linux/leds.h b/include/linux/leds.h
index a794817..d085c21 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -174,6 +174,16 @@ extern void led_blink_set_oneshot(struct led_classdev *led_cdev,
*/
extern void led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness brightness);
+/**
+ * led_update_brightness - update LED brightness
+ * @led_cdev: the LED to query
+ *
+ * Get an LED's current brightness and update led_cdev->brightness
+ * member with the obtained value.
+ *
+ * Returns: 0 on success or negative error value on failure
+ */
+extern int led_update_brightness(struct led_classdev *led_cdev);

/**
* led_sysfs_is_locked
--
1.7.9.5

2014-04-14 13:49:20

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 1/5] leds: Add sysfs and kernel internal API for flash LEDs

Hi Jacek,

Thanks for the update! Some comments below. I'll try to reply to the rest
during the coming days.

On Fri, Apr 11, 2014 at 04:56:52PM +0200, 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
> optional external_strobe, indicator_brightness and
> max_indicator_btightness. All the flash related features
> are placed in a separate module.
> 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 | 8 +
> drivers/leds/Makefile | 1 +
> drivers/leds/led-class.c | 36 ++-
> drivers/leds/led-flash.c | 627 +++++++++++++++++++++++++++++++++++++++++++
> drivers/leds/led-triggers.c | 16 +-
> drivers/leds/leds.h | 6 +
> include/linux/leds.h | 50 +++-
> include/linux/leds_flash.h | 252 +++++++++++++++++
> 8 files changed, 982 insertions(+), 14 deletions(-)
> create mode 100644 drivers/leds/led-flash.c
> create mode 100644 include/linux/leds_flash.h
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 2062682..1e1c81f 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -19,6 +19,14 @@ 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 "Flash LEDs Support"
> + depends on LEDS_CLASS
> + help
> + This option enables support for flash LED devices. Say Y if you
> + want to use flash specific features of a LED device, if they
> + are supported.
> +
> comment "LED drivers"
>
> config LEDS_88PM860X
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 3cd76db..8861b86 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-flash.o
> obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
>
> # LED Platform Drivers
> diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
> index f37d63c..58f16c3 100644
> --- a/drivers/leds/led-class.c
> +++ b/drivers/leds/led-class.c
> @@ -9,15 +9,16 @@
> * published by the Free Software Foundation.
> */
>
> -#include <linux/module.h>
> -#include <linux/kernel.h>
> +#include <linux/ctype.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> #include <linux/init.h>
> +#include <linux/kernel.h>
> #include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> #include <linux/spinlock.h>
> -#include <linux/device.h>
> #include <linux/timer.h>
> -#include <linux/err.h>
> -#include <linux/ctype.h>
> #include <linux/leds.h>
> #include "leds.h"
>
> @@ -45,28 +46,38 @@ static ssize_t brightness_store(struct device *dev,
> {
> struct led_classdev *led_cdev = dev_get_drvdata(dev);
> unsigned long state;
> - ssize_t ret = -EINVAL;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
>
> ret = kstrtoul(buf, 10, &state);
> if (ret)
> - return ret;
> + goto unlock;
>
> if (state == LED_OFF)
> led_trigger_remove(led_cdev);
> __led_set_brightness(led_cdev, state);
> + ret = size;
>
> - return size;
> +unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + return ret;
> }
> static DEVICE_ATTR_RW(brightness);
>
> -static ssize_t led_max_brightness_show(struct device *dev,
> +static ssize_t max_brightness_show(struct device *dev,
> struct device_attribute *attr, char *buf)
> {
> struct led_classdev *led_cdev = dev_get_drvdata(dev);
>
> return sprintf(buf, "%u\n", led_cdev->max_brightness);
> }
> -static DEVICE_ATTR(max_brightness, 0444, led_max_brightness_show, NULL);
> +static DEVICE_ATTR_RO(max_brightness);
>
> #ifdef CONFIG_LEDS_TRIGGERS
> static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
> @@ -174,6 +185,8 @@ 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);
> @@ -218,6 +231,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
> #ifdef CONFIG_LEDS_TRIGGERS
> init_rwsem(&led_cdev->trigger_lock);
> #endif
> + mutex_init(&led_cdev->led_lock);
> /* add to the list of leds */
> down_write(&leds_list_lock);
> list_add_tail(&led_cdev->node, &leds_list);
> @@ -271,6 +285,8 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
> down_write(&leds_list_lock);
> list_del(&led_cdev->node);
> up_write(&leds_list_lock);
> +
> + mutex_destroy(&led_cdev->led_lock);
> }
> EXPORT_SYMBOL_GPL(led_classdev_unregister);
>
> diff --git a/drivers/leds/led-flash.c b/drivers/leds/led-flash.c
> new file mode 100644
> index 0000000..9d482a4
> --- /dev/null
> +++ b/drivers/leds/led-flash.c
> @@ -0,0 +1,627 @@
> +/*
> + * LED Class Flash 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/module.h>
> +#include <linux/leds.h>
> +#include <linux/leds_flash.h>
> +#include "leds.h"
> +
> +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);
> + unsigned long state;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &state);
> + if (ret)
> + goto unlock;
> +
> + ret = led_set_flash_brightness(led_cdev, state);
> + if (ret < 0)
> + goto unlock;
> +
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + 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_flash *flash = led_cdev->flash;
> +
> + /* no lock needed for this */
> + led_update_flash_brightness(led_cdev);
> +
> + 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_flash *flash = led_cdev->flash;
> +
> + return sprintf(buf, "%u\n", flash->brightness.max);
> +}
> +static DEVICE_ATTR_RO(max_flash_brightness);
> +
> +static ssize_t indicator_brightness_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t size)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + unsigned long state;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &state);
> + if (ret)
> + goto unlock;
> +
> + ret = led_set_indicator_brightness(led_cdev, state);
> + if (ret < 0)
> + goto unlock;
> +
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + return ret;
> +}
> +
> +static ssize_t indicator_brightness_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_flash *flash = led_cdev->flash;
> +
> + /* no lock needed for this */
> + led_update_indicator_brightness(led_cdev);
> +
> + return sprintf(buf, "%u\n", flash->indicator_brightness->val);
> +}
> +static DEVICE_ATTR_RW(indicator_brightness);
> +
> +static ssize_t max_indicator_brightness_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_flash *flash = led_cdev->flash;
> +
> + return sprintf(buf, "%u\n", flash->indicator_brightness->max);
> +}
> +static DEVICE_ATTR_RO(max_indicator_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);
> + unsigned long state;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &state);
> + if (ret)
> + goto unlock;
> +
> + if (state < 0 || state > 1)
> + return -EINVAL;

How about assigning ret and a goto instead? :-)

> + ret = led_set_flash_strobe(led_cdev, state);
> + if (ret < 0)
> + goto unlock;
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + 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);
> + int ret;
> +
> + /* no lock needed for this */
> + ret = led_get_flash_strobe(led_cdev);
> + if (ret < 0)
> + return ret;
> +
> + return sprintf(buf, "%u\n", ret);
> +}
> +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);
> + unsigned long flash_timeout;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &flash_timeout);
> + if (ret)
> + goto unlock;
> +
> + ret = led_set_flash_timeout(led_cdev, flash_timeout);
> + if (ret < 0)
> + goto unlock;
> +
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + 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_flash *flash = led_cdev->flash;
> +
> + return sprintf(buf, "%d\n", flash->timeout.val);

val is unsigned.

> +}
> +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_flash *flash = led_cdev->flash;
> +
> + return sprintf(buf, "%d\n", flash->timeout.max);

Same here (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);
> + unsigned int fault;

As led_get_flash_fault() expects a u32, it'd be cleaner to use that for
passing a reference.

> + int ret;
> +
> + ret = led_get_flash_fault(led_cdev, &fault);
> + if (ret < 0)
> + return -EINVAL;
> +
> + return sprintf(buf, "0x%8.8x\n", fault);
> +}
> +static DEVICE_ATTR_RO(flash_fault);
> +
> +static ssize_t external_strobe_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t size)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + unsigned long external_strobe;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &external_strobe);
> + if (ret)
> + goto unlock;
> +
> + if (external_strobe > 1) {
> + ret = -EINVAL;
> + goto unlock;
> + }
> +
> + ret = led_set_external_strobe(led_cdev, external_strobe);
> + if (ret < 0)
> + goto unlock;
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + return ret;
> +}
> +
> +static ssize_t external_strobe_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> +
> + return sprintf(buf, "%u\n", led_cdev->flash->external_strobe);
> +}
> +static DEVICE_ATTR_RW(external_strobe);
> +
> +static struct attribute *led_flash_attrs[] = {
> + &dev_attr_flash_brightness.attr,
> + &dev_attr_flash_strobe.attr,
> + &dev_attr_flash_timeout.attr,
> + &dev_attr_max_flash_timeout.attr,
> + &dev_attr_max_flash_brightness.attr,
> + &dev_attr_flash_fault.attr,
> + NULL,
> +};
> +
> +static struct attribute *led_flash_indicator_attrs[] = {
> + &dev_attr_indicator_brightness.attr,
> + &dev_attr_max_indicator_brightness.attr,
> + NULL,
> +};
> +
> +static struct attribute *led_flash_external_strobe_attrs[] = {
> + &dev_attr_external_strobe.attr,
> + NULL,
> +};
> +
> +static struct attribute_group led_flash_group = {
> + .attrs = led_flash_attrs,
> +};
> +
> +static struct attribute_group led_flash_indicator_group = {
> + .attrs = led_flash_indicator_attrs,
> +};
> +
> +static struct attribute_group led_flash_external_strobe_group = {
> + .attrs = led_flash_external_strobe_attrs,
> +};
> +
> +void led_flash_resume(struct led_classdev *led_cdev)
> +{
> + struct led_flash *flash = led_cdev->flash;
> +
> + call_flash_op(brightness_set, led_cdev,
> + flash->brightness.val);
> + call_flash_op(timeout_set, led_cdev,
> + flash->timeout.val);

Both fit on a single line.

> + if (has_flash_op(indicator_brightness_set))
> + call_flash_op(indicator_brightness_set, led_cdev,
> + flash->indicator_brightness->val);
> +}
> +
> +#ifdef CONFIG_V4L2_FLASH
> +static const struct v4l2_flash_ops v4l2_flash_ops = {
> + .brightness_set = led_set_brightness,
> + .brightness_update = led_update_brightness,
> + .flash_brightness_set = led_set_flash_brightness,
> + .flash_brightness_update = led_update_flash_brightness,
> + .indicator_brightness_set = led_set_indicator_brightness,
> + .indicator_brightness_update = led_update_indicator_brightness,
> + .strobe_set = led_set_flash_strobe,
> + .strobe_get = led_get_flash_strobe,
> + .timeout_set = led_set_flash_timeout,
> + .external_strobe_set = led_set_external_strobe,
> + .fault_get = led_get_flash_fault,
> + .sysfs_lock = led_sysfs_lock,
> + .sysfs_unlock = led_sysfs_unlock,
> +};
> +#define V4L2_FLASH_OPS (&v4l2_flash_ops)
> +#else
> +#define V4L2_FLASH_OPS NULL
> +#endif
> +
> +
> +void led_flash_remove_sysfs_groups(struct led_classdev *led_cdev)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + int i;
> +
> + for (i = 0; i < LED_FLASH_MAX_SYSFS_GROUPS; ++i)
> + if (flash->sysfs_groups[i])
> + sysfs_remove_group(&led_cdev->dev->kobj,
> + flash->sysfs_groups[i]);
> +}
> +
> +int led_flash_create_sysfs_groups(struct led_classdev *led_cdev)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + int ret, num_sysfs_groups = 0;
> +
> + memset(flash->sysfs_groups, 0, sizeof(*flash->sysfs_groups) *
> + LED_FLASH_MAX_SYSFS_GROUPS);
> +
> + ret = sysfs_create_group(&led_cdev->dev->kobj, &led_flash_group);
> + if (ret < 0)
> + goto err_create_group;
> + flash->sysfs_groups[num_sysfs_groups++] = &led_flash_group;
> +
> + if (flash->indicator_brightness) {
> + ret = sysfs_create_group(&led_cdev->dev->kobj,
> + &led_flash_indicator_group);
> + if (ret < 0)
> + goto err_create_group;
> + flash->sysfs_groups[num_sysfs_groups++] =
> + &led_flash_indicator_group;
> + }
> + if (flash->has_external_strobe) {
> + ret = sysfs_create_group(&led_cdev->dev->kobj,
> + &led_flash_external_strobe_group);
> + if (ret < 0)
> + goto err_create_group;
> + flash->sysfs_groups[num_sysfs_groups++] =
> + &led_flash_external_strobe_group;
> + }
> +
> + return 0;
> +
> +err_create_group:
> + led_flash_remove_sysfs_groups(led_cdev);
> + return ret;
> +}
> +
> +int led_classdev_flash_register(struct device *parent,
> + struct led_classdev *led_cdev)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + const struct led_flash_ops *ops;
> + int ret;
> +
> + if (!flash)
> + return -EINVAL;
> +
> + /* Register led class device */
> + ret = led_classdev_register(parent, led_cdev);
> + if (ret < 0)
> + return ret;
> +
> + if (!flash->has_flash_led)
> + goto exit;
> +
> + /* Validate flash related ops */
> + ops = &flash->ops;
> + if (!ops || !ops->brightness_set || !ops->strobe_set || !ops->strobe_get
> + || !ops->timeout_set || !ops->fault_get)

Could you align the condition to right of the opening parenthese?

The adp1653 cannot tell the strobe status. While the driver is there I think
it's unlikely it'd ever be supported using the LED framework, I think making
strobe_get() optional now isn't necessary. Just FYI; things like this are
out there.

> + return -EINVAL;
> +
> + if (flash->has_external_strobe && !ops->external_strobe_set)
> + return -EINVAL;
> +
> + if (flash->indicator_brightness && !ops->indicator_brightness_set)
> + return -EINVAL;
> +
> + /* Install resume callback for flash controls */
> + led_cdev->flash_resume = led_flash_resume;
> +
> + /* Create flash led specific sysfs attributes */
> + ret = led_flash_create_sysfs_groups(led_cdev);
> + if (ret < 0)
> + goto err_create_groups;
> +
> +exit:
> + /* This will create V4L2 Flash sub-device if it is enabled */
> + ret = v4l2_flash_init(led_cdev, V4L2_FLASH_OPS);

v4l2_flash_init() is only defined in the last patch. I think you need to
split it at least into two so you can have definitions for the LED class
patches that need them.

I think you could also refer to v4l2_flash_ops() directly, and make
v4l2_flash_init() a macro returning zero if CONFIG_V4L2_FLASH isn't defined.
As as result, you wouldn't need to define V4L2_FLASH_OPS.

On the naming of the config option --- as this is directly related to LED
class, what would you think about calling it CONFIG_V4L2_LED_CLASS or
CONFIG_V4L2_FLASH_LED_CLASS, for instance? The same API (but probably not
the implementation) supports also xenon flash devices.

> + if (ret < 0)
> + goto err_create_groups;

What about the sysfs groups?

> +
> + return 0;
> +
> +err_create_groups:
> + led_classdev_unregister(led_cdev);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(led_classdev_flash_register);
> +
> +void led_classdev_flash_unregister(struct led_classdev *led_cdev)
> +{
> + v4l2_flash_release(led_cdev);
> + led_flash_remove_sysfs_groups(led_cdev);
> + led_classdev_unregister(led_cdev);
> +}
> +EXPORT_SYMBOL_GPL(led_classdev_flash_unregister);
> +
> +/* Caller must ensure led_cdev->led_lock held */
> +void led_sysfs_lock(struct led_classdev *led_cdev)
> +{

How about e.g. WARN_ON(!mutex_is_locked(&led_cdev->led_lock));?

> + led_cdev->flags |= LED_SYSFS_LOCK;
> +}
> +EXPORT_SYMBOL(led_sysfs_lock);
> +
> +/* Caller must ensure led_cdev->led_lock held */
> +void led_sysfs_unlock(struct led_classdev *led_cdev)
> +{
> + led_cdev->flags &= ~LED_SYSFS_LOCK;
> +}
> +EXPORT_SYMBOL(led_sysfs_unlock);
> +
> +int led_set_flash_strobe(struct led_classdev *led_cdev, bool state)
> +{
> + if (!has_flash_op(strobe_set))
> + return -EINVAL;
> +
> + return call_flash_op(strobe_set, led_cdev, state);
> +}
> +EXPORT_SYMBOL(led_set_flash_strobe);
> +
> +int led_get_flash_strobe(struct led_classdev *led_cdev)
> +{
> + if (!has_flash_op(strobe_get))
> + return -EINVAL;
> +
> + return call_flash_op(strobe_get, led_cdev);
> +}
> +EXPORT_SYMBOL(led_get_flash_strobe);
> +
> +void led_clamp_align_val(struct led_ctrl *c)
> +{
> + u32 v, offset;
> +
> + v = c->val + c->step / 2;
> + v = clamp(v, c->min, c->max);
> + offset = v - c->min;
> + offset = c->step * (offset / c->step);
> + c->val = c->min + offset;
> +}
> +
> +int led_set_flash_timeout(struct led_classdev *led_cdev, u32 timeout)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + struct led_ctrl *c = &flash->timeout;
> + int ret = 0;
> +
> + if (!has_flash_op(timeout_set))
> + return -EINVAL;
> +
> + c->val = timeout;
> + led_clamp_align_val(c);
> +
> + if (!(led_cdev->flags & LED_SUSPENDED))
> + ret = call_flash_op(timeout_set, led_cdev, c->val);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(led_set_flash_timeout);
> +
> +int led_get_flash_fault(struct led_classdev *led_cdev, u32 *fault)
> +{
> + if (!has_flash_op(fault_get))
> + return -EINVAL;
> +
> + return call_flash_op(fault_get, led_cdev, fault);
> +}
> +EXPORT_SYMBOL(led_get_flash_fault);
> +
> +int led_set_external_strobe(struct led_classdev *led_cdev, bool enable)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + int ret;
> +
> + if (!has_flash_op(external_strobe_set))
> + return -EINVAL;
> +
> + if (flash->has_external_strobe) {
> + ret = call_flash_op(external_strobe_set, led_cdev, enable);
> + if (ret < 0)
> + return -EINVAL;
> + flash->external_strobe = enable;
> + } else if (enable)
> + return -EINVAL;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(led_set_external_strobe);
> +
> +int led_set_flash_brightness(struct led_classdev *led_cdev,
> + u32 brightness)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + struct led_ctrl *c = &flash->brightness;
> + int ret = 0;
> +
> + if (!has_flash_op(brightness_set))
> + return -EINVAL;
> +
> + c->val = brightness;
> + led_clamp_align_val(c);
> +
> + if (!(led_cdev->flags & LED_SUSPENDED))
> + ret = call_flash_op(brightness_set, led_cdev, c->val);
> + return ret;
> +}
> +EXPORT_SYMBOL(led_set_flash_brightness);
> +
> +int led_update_flash_brightness(struct led_classdev *led_cdev)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + struct led_ctrl *c = &flash->brightness;
> + u32 brightness;
> + int ret = 0;
> +
> + if (has_flash_op(brightness_get)) {
> + ret = call_flash_op(brightness_get, led_cdev, &brightness);
> + if (ret < 0)
> + return ret;
> + c->val = brightness;
> + }
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(led_update_flash_brightness);
> +
> +int led_set_indicator_brightness(struct led_classdev *led_cdev,
> + u32 brightness)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + struct led_ctrl *c = flash->indicator_brightness;
> + int ret = 0;
> +
> + if (!has_flash_op(indicator_brightness_set))
> + return -EINVAL;
> +
> + c->val = brightness;
> + led_clamp_align_val(c);
> +
> + if (!(led_cdev->flags & LED_SUSPENDED))
> + ret = call_flash_op(indicator_brightness_set, led_cdev, c->val);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(led_set_indicator_brightness);
> +
> +int led_update_indicator_brightness(struct led_classdev *led_cdev)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + struct led_ctrl *c = flash->indicator_brightness;
> + u32 brightness;
> + int ret = 0;
> +
> + if (has_flash_op(indicator_brightness_get)) {
> + ret = call_flash_op(indicator_brightness_get, led_cdev,
> + &brightness);
> + if (ret < 0)
> + return ret;
> + c->val = brightness;
> + }
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(led_update_indicator_brightness);
> +
> +static int __init leds_flash_init(void)
> +{
> + return 0;
> +}
> +
> +static void __exit leds_flash_exit(void)
> +{
> +}
> +
> +subsys_initcall(leds_flash_init);
> +module_exit(leds_flash_exit);
> +
> +MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("LED Class Flash Interface");
> diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
> index df1a7c1..40e21c0 100644
> --- a/drivers/leds/led-triggers.c
> +++ b/drivers/leds/led-triggers.c
> @@ -37,6 +37,14 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
> char trigger_name[TRIG_NAME_MAX];
> struct led_trigger *trig;
> size_t len;
> + int ret = count;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto exit_unlock;
> + }
>
> trigger_name[sizeof(trigger_name) - 1] = '\0';
> strncpy(trigger_name, buf, sizeof(trigger_name) - 1);
> @@ -47,7 +55,7 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
>
> if (!strcmp(trigger_name, "none")) {
> led_trigger_remove(led_cdev);
> - return count;
> + goto exit_unlock;
> }
>
> down_read(&triggers_list_lock);
> @@ -58,12 +66,14 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
> up_write(&led_cdev->trigger_lock);
>
> up_read(&triggers_list_lock);
> - return count;
> + goto exit_unlock;
> }
> }
> up_read(&triggers_list_lock);
>
> - return -EINVAL;
> +exit_unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + return ret;
> }
> EXPORT_SYMBOL_GPL(led_trigger_store);
>
> diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
> index 4c50365..f66a0c3 100644
> --- a/drivers/leds/leds.h
> +++ b/drivers/leds/leds.h
> @@ -17,6 +17,12 @@
> #include <linux/rwsem.h>
> #include <linux/leds.h>
>
> +#define call_flash_op(op, args...) \
> + ((led_cdev)->flash->ops.op(args))
> +
> +#define has_flash_op(op) \
> + ((led_cdev)->flash && (led_cdev)->flash->ops.op)

I think you're using the two in a pattern where you first check if the op is
there, and then call it if it is. Could you combine both into
call_flash_op(), and return an error if the op isn't there?

> static inline void __led_set_brightness(struct led_classdev *led_cdev,
> enum led_brightness value)
> {
> diff --git a/include/linux/leds.h b/include/linux/leds.h
> index 0287ab2..a794817 100644
> --- a/include/linux/leds.h
> +++ b/include/linux/leds.h
> @@ -13,12 +13,14 @@
> #define __LINUX_LEDS_H_INCLUDED
>
> #include <linux/list.h>
> -#include <linux/spinlock.h>
> +#include <linux/mutex.h>
> #include <linux/rwsem.h>
> +#include <linux/spinlock.h>
> #include <linux/timer.h>
> #include <linux/workqueue.h>
>
> struct device;
> +struct led_flash;
> /*
> * LED Core
> */
> @@ -29,6 +31,28 @@ enum led_brightness {
> LED_FULL = 255,
> };
>
> +/*
> + * This structure is required in two cases:
> + * - it defines allowed levels of flash leds brightness and timeout
> + * - it provides initialization data for V4L2 Flash controls
> + * when CONFIG_V4L2_FLASH is enabled and allows for proper
> + * enum led_brightness <-> microamperes conversion for the
> + * V4L2_CID_FLASH_TORCH_INTENSITY
> + */
> +struct led_ctrl {
> + /* maximum allowed value */
> + u32 min;
> + /* maximum allowed value */
> + u32 max;
> + /* step value */
> + u32 step;
> + /*
> + * Default value for V4L2 controls and for flash leds
> + * it also serves for caching the value currently set.
> + */
> + u32 val;
> +};
> +
> struct led_classdev {
> const char *name;
> int brightness;
> @@ -42,6 +66,7 @@ struct led_classdev {
> #define LED_BLINK_ONESHOT (1 << 17)
> #define LED_BLINK_ONESHOT_STOP (1 << 18)
> #define LED_BLINK_INVERT (1 << 19)
> +#define LED_SYSFS_LOCK (1 << 21)
>
> /* Set LED brightness level */
> /* Must not sleep, use a workqueue if needed */
> @@ -69,6 +94,17 @@ struct led_classdev {
> unsigned long blink_delay_on, blink_delay_off;
> struct timer_list blink_timer;
> int blink_brightness;
> + struct led_flash *flash;
> + void (*flash_resume)(struct led_classdev *led_cdev);
> +#ifdef CONFIG_V4L2_FLASH
> + /* Initialization data for the V4L2_CID_FLASH_TORCH_INTENSITY control */
> + struct led_ctrl brightness_ctrl;
> +#endif
> + /*
> + * Ensures consistent LED sysfs access and protects
> + * LED sysfs locking mechanism
> + */
> + struct mutex led_lock;
>
> struct work_struct set_brightness_work;
> int delayed_set_value;
> @@ -139,6 +175,18 @@ extern void led_blink_set_oneshot(struct led_classdev *led_cdev,
> extern void led_set_brightness(struct led_classdev *led_cdev,
> enum led_brightness brightness);
>
> +/**
> + * led_sysfs_is_locked
> + * @led_cdev: the LED to query
> + *
> + * Returns: true if the sysfs interface of the led is disabled,
> + * false otherwise
> + */
> +static inline bool led_sysfs_is_locked(struct led_classdev *led_cdev)
> +{
> + return led_cdev->flags & LED_SYSFS_LOCK;
> +}
> +
> /*
> * LED Triggers
> */
> diff --git a/include/linux/leds_flash.h b/include/linux/leds_flash.h
> new file mode 100644
> index 0000000..0f885a0
> --- /dev/null
> +++ b/include/linux/leds_flash.h
> @@ -0,0 +1,252 @@
> +/*
> + * Flash leds API
> + *
> + * 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 <media/v4l2-flash.h>
> +#include <linux/leds.h>

"l" comes before "m".

> +/*
> + * Supported led fault bits - must be kept in synch
> + * with V4L2_FLASH_FAULT bits.
> + */
> +#define LED_FAULT_OVER_VOLTAGE (1 << 0)
> +#define LED_FAULT_TIMEOUT (1 << 1)
> +#define LED_FAULT_OVER_TEMPERATURE (1 << 2)
> +#define LED_FAULT_SHORT_CIRCUIT (1 << 3)
> +#define LED_FAULT_OVER_CURRENT (1 << 4)
> +#define LED_FAULT_INDICATOR (1 << 5)
> +#define LED_FAULT_UNDER_VOLTAGE (1 << 6)
> +#define LED_FAULT_INPUT_VOLTAGE (1 << 7)
> +#define LED_FAULT_LED_OVER_TEMPERATURE (1 << 8)
> +
> +#define LED_FLASH_MAX_SYSFS_GROUPS 3
> +
> +struct led_flash_ops {
> + /* set flash brightness */
> + int (*brightness_set)(struct led_classdev *led_cdev,
> + u32 brightness);
> + /* get flash brightness */
> + int (*brightness_get)(struct led_classdev *led_cdev, u32 *brightness);
> + /* set flash indicator brightness */
> + int (*indicator_brightness_set)(struct led_classdev *led_cdev,
> + u32 brightness);
> + /* get flash indicator brightness */
> + int (*indicator_brightness_get)(struct led_classdev *led_cdev,
> + u32 *brightness);
> + /* setup flash strobe */
> + int (*strobe_set)(struct led_classdev *led_cdev,
> + bool state);
> + /* get flash strobe state */
> + int (*strobe_get)(struct led_classdev *led_cdev);
> + /* setup flash timeout */
> + int (*timeout_set)(struct led_classdev *led_cdev,
> + u32 timeout);

Fits on a single line. Applies to some others as well.

> + /* setup strobing the flash from external source */
> + int (*external_strobe_set)(struct led_classdev *led_cdev,
> + bool enable);
> + /* get the flash LED fault */
> + int (*fault_get)(struct led_classdev *led_cdev,
> + u32 *fault);
> +};
> +
> +struct led_flash {
> + /*
> + * 1 - add support for both flash and torch leds,
> + * 0 - handle only torch led
> + */
> + bool has_flash_led;
> + /* flash led specific ops */
> + const struct led_flash_ops ops;
> +
> + /* flash sysfs groups */
> + struct attribute_group *sysfs_groups[LED_FLASH_MAX_SYSFS_GROUPS];
> +
> + /* flash brightness value in microamperes along with its constraints */
> + struct led_ctrl brightness;
> +
> + /* timeout value in microseconds along with its constraints */
> + struct led_ctrl timeout;
> +
> + /*
> + * Indicator brightness value in microamperes along with
> + * its constraints - this is an optional control and must
> + * be allocated by the driver if the device supports privacy
> + * indicator led.
> + */
> + struct led_ctrl *indicator_brightness;
> +
> + /*
> + * determines whether device supports external
> + * flash strobe sources
> + */
> + bool has_external_strobe;
> +
> + /*
> + * If true the device doesn't strobe the flash immediately
> + * after writing 1 to the flash_strobe file, but waits
> + * for an external signal.
> + */
> + bool external_strobe;
> +
> +#ifdef CONFIG_V4L2_FLASH
> + /* V4L2 Flash sub-device data */
> + struct v4l2_flash v4l2_flash;
> +
> + /* flash fault bits that may be set by the device */
> + u32 fault_flags;
> +#endif
> +
> +};
> +
> +/**
> + * led_classdev_flash_register - register a new object of led_classdev class
> + with support for flash LEDs
> + * @parent: the device to register
> + * @led_cdev: the led_classdev structure for this device
> + *
> + * Returns: 0 on success, error code on failure.
> + */
> +int led_classdev_flash_register(struct device *parent,
> + struct led_classdev *led_cdev);
> +
> +/**
> + * led_classdev_flash_unregister - unregisters an object of led_properties class
> + with support for flash LEDs
> + * @led_cdev: the flash led device to unregister
> + *
> + * Unregisters a previously registered via led_classdev_flash_register object
> + */
> +void led_classdev_flash_unregister(struct led_classdev *led_cdev);
> +
> +/**
> + * led_set_flash_strobe - setup flash strobe
> + * @led_cdev: the flash LED to set strobe on
> + * @state: 1 - strobe flash, 0 - stop flash strobe
> + *
> + * Setup flash strobe - trigger flash strobe
> + *
> + * Returns: 0 on success or negative error value on failure
> + */
> +extern int led_set_flash_strobe(struct led_classdev *led_cdev, bool state);
> +
> +/**
> + * led_get_flash_strobe - get flash strobe status
> + * @led_cdev: the LED to query
> + *
> + * Check whether the flash is strobing at the moment or not.
> + *
> + * Returns: flash strobe status (0 or 1) on success or negative
> + * error value on failure.
> + */
> +extern int led_get_flash_strobe(struct led_classdev *led_cdev);
> +
> +/**
> + * led_set_flash_brightness - set flash LED brightness
> + * @led_cdev: the LED to set
> + * @brightness: the brightness to set it to
> + *
> + * Returns: 0 on success, -EINVAL on failure

Could this function return other error codes? Same for other functions
below where a specific error code is listed.

> + * Set a flash LED's brightness.
> + */
> +extern int led_set_flash_brightness(struct led_classdev *led_cdev,
> + u32 brightness);
> +
> +/**
> + * led_update_flash_brightness - update flash LED brightness
> + * @led_cdev: the 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 *led_cdev);
> +
> +/**
> + * led_set_flash_timeout - set flash LED timeout
> + * @led_cdev: the LED to set
> + * @timeout: the flash timeout to set it to
> + *
> + * Returns: 0 on success, -EINVAL on failure
> + *
> + * 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.
> + */
> +extern int led_set_flash_timeout(struct led_classdev *led_cdev,
> + u32 timeout);
> +
> +/**
> + * led_get_flash_fault - get the flash LED fault
> + * @led_cdev: the LED to query
> + * @fault: bitmask containing flash faults
> + *
> + * Returns: 0 on success, -EINVAL on failure
> + *
> + * Get the flash LED fault.
> + */
> +extern int led_get_flash_fault(struct led_classdev *led_cdev,
> + u32 *fault);
> +
> +/**
> + * led_set_external_strobe - set the flash LED external_strobe mode
> + * @led_cdev: the LED to set
> + * @enable: the state to set it to
> + *
> + * Returns: 0 on success, -EINVAL on failure
> + *
> + * Enable/disable strobing the flash LED with use of external source
> + */
> +extern int led_set_external_strobe(struct led_classdev *led_cdev, bool enable);
> +
> +/**
> + * led_set_indicator_brightness - set indicator LED brightness
> + * @led_cdev: the LED to set
> + * @brightness: the brightness to set it to
> + *
> + * Returns: 0 on success, -EINVAL on failure
> + *
> + * Set a flash LED's brightness.
> + */
> +extern int led_set_indicator_brightness(struct led_classdev *led_cdev,
> + u32 led_brightness);
> +
> +/**
> + * led_update_indicator_brightness - update flash indicator LED brightness
> + * @led_cdev: the LED to query
> + *
> + * Get a flash indicator LED's current brightness and update
> + * led_flash->indicator_brightness member with the obtained value.
> + *
> + * Returns: 0 on success or negative error value on failure
> + */
> +extern int led_update_indicator_brightness(struct led_classdev *led_cdev);
> +
> +/**
> + * led_sysfs_lock - lock LED sysfs interface
> + * @led_cdev: the LED to set
> + *
> + * Lock the LED's sysfs interface
> + */
> +extern void led_sysfs_lock(struct led_classdev *led_cdev);
> +
> +/**
> + * led_sysfs_unlock - unlock LED sysfs interface
> + * @led_cdev: the LED to set
> + *
> + * Unlock the LED's sysfs interface
> + */
> +extern void led_sysfs_unlock(struct led_classdev *led_cdev);
> +
> +#endif /* __LINUX_FLASH_LEDS_H_INCLUDED */

--
Kind regards,

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

2014-04-16 10:35:51

by Lee Jones

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 3/5] 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 or through V4L2 subdevice when the support
> for V4L2 Flash sub-devices is enabled. Device supports up to
> two leds which can work in flash and torch mode. Leds can
> be triggered externally or by software.
>
> Signed-off-by: Andrzej Hajda <[email protected]>
> Signed-off-by: Jacek Anaszewski <[email protected]>
> Acked-by: Kyungmin Park <[email protected]>
> Cc: Bryan Wu <[email protected]>
> Cc: Richard Purdie <[email protected]>
> Cc: SangYoung Son <[email protected]>
> Cc: Samuel Ortiz <[email protected]>
> Cc: Lee Jones <[email protected]>
> ---
> drivers/leds/Kconfig | 10 +
> drivers/leds/Makefile | 1 +
> drivers/leds/leds-max77693.c | 794 ++++++++++++++++++++++++++++++++++++++++++
> drivers/mfd/max77693.c | 2 +-
> include/linux/mfd/max77693.h | 38 ++
> 5 files changed, 844 insertions(+), 1 deletion(-)
> create mode 100644 drivers/leds/leds-max77693.c

[...]

> diff --git a/drivers/mfd/max77693.c b/drivers/mfd/max77693.c
> index c5535f0..f061aa8 100644
> --- a/drivers/mfd/max77693.c
> +++ b/drivers/mfd/max77693.c
> @@ -44,7 +44,7 @@
> static const struct mfd_cell max77693_devs[] = {
> { .name = "max77693-pmic", },
> { .name = "max77693-charger", },
> - { .name = "max77693-flash", },
> + { .name = "max77693-flash", .of_compatible = "maxim,max77693-flash", },

I would prefer for this to be opened up i.e. not on one line.

> { .name = "max77693-muic", },
> { .name = "max77693-haptic", },
> };
> diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
> index 3f3dc45..f2285b7 100644
> --- a/include/linux/mfd/max77693.h
> +++ b/include/linux/mfd/max77693.h
> @@ -63,6 +63,43 @@ struct max77693_muic_platform_data {
> int path_uart;
> };
>
> +/* MAX77693 led flash */
> +
> +/* triggers */
> +enum max77693_led_trigger {
> + MAX77693_LED_TRIG_OFF,
> + MAX77693_LED_TRIG_FLASH,
> + MAX77693_LED_TRIG_TORCH,
> + MAX77693_LED_TRIG_EXT,
> + MAX77693_LED_TRIG_SOFT,
> +};
> +
> +

Extra '\n' here.

> +/* trigger types */
> +enum max77693_led_trigger_type {
> + MAX77693_LED_TRIG_TYPE_EDGE,
> + MAX77693_LED_TRIG_TYPE_LEVEL,
> +};
> +
> +/* boost modes */
> +enum max77693_led_boost_mode {
> + MAX77693_LED_BOOST_NONE,
> + MAX77693_LED_BOOST_ADAPTIVE,
> + MAX77693_LED_BOOST_FIXED,
> +};
> +
> +struct max77693_led_platform_data {
> + u32 iout[4];
> + u32 trigger[4];
> + u32 trigger_type[2];
> + u32 timeout[2];
> + u32 boost_mode[2];
> + u32 boost_vout;
> + u32 low_vsys;
> +};

Bryan will have to review this.

> +/* MAX77693 */
> +
> struct max77693_platform_data {
> /* regulator data */
> struct max77693_regulator_data *regulators;
> @@ -70,5 +107,6 @@ struct max77693_platform_data {
>
> /* muic data */
> struct max77693_muic_platform_data *muic_data;
> + struct max77693_led_platform_data *led_data;
> };
> #endif /* __LINUX_MFD_MAX77693_H */

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

2014-04-16 17:26:44

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 3/5] leds: Add support for max77693 mfd flash cell

Hi Jacek,

Thanks for the patch! Comments below.

On Fri, Apr 11, 2014 at 04:56:54PM +0200, 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 or through V4L2 subdevice when the support
> for V4L2 Flash sub-devices is enabled. Device supports up to
> two leds which can work in flash and torch mode. Leds can
> be triggered externally or by software.
>
> Signed-off-by: Andrzej Hajda <[email protected]>
> Signed-off-by: Jacek Anaszewski <[email protected]>
> Acked-by: Kyungmin Park <[email protected]>
> Cc: Bryan Wu <[email protected]>
> Cc: Richard Purdie <[email protected]>
> Cc: SangYoung Son <[email protected]>
> Cc: Samuel Ortiz <[email protected]>
> Cc: Lee Jones <[email protected]>
> ---
> drivers/leds/Kconfig | 10 +
> drivers/leds/Makefile | 1 +
> drivers/leds/leds-max77693.c | 794 ++++++++++++++++++++++++++++++++++++++++++
> drivers/mfd/max77693.c | 2 +-
> include/linux/mfd/max77693.h | 38 ++
> 5 files changed, 844 insertions(+), 1 deletion(-)
> create mode 100644 drivers/leds/leds-max77693.c
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 1e1c81f..b2152a6 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -462,6 +462,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 8861b86..64f6234 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..979736c
> --- /dev/null
> +++ b/drivers/leds/leds-max77693.c
> @@ -0,0 +1,794 @@
> +/*
> + * Copyright (C) 2014, Samsung Electronics Co., Ltd.
> + *
> + * Authors: Andrzej Hajda <[email protected]>
> + * 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 <asm/div64.h>
> +#include <linux/leds_flash.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <media/v4l2-flash.h>

I guess this should be last in the list.

> +#include <linux/workqueue.h>
> +#include <linux/mfd/max77693.h>
> +#include <linux/mfd/max77693-private.h>
> +
> +#define MAX77693_LED_NAME "max77693-flash"
> +
> +#define MAX77693_TORCH_IOUT_BITS 4
> +
> +#define MAX77693_TORCH_NO_TIMER 0x40
> +#define MAX77693_FLASH_TIMER_LEVEL 0x80
> +
> +#define MAX77693_FLASH_EN_OFF 0
> +#define MAX77693_FLASH_EN_FLASH 1
> +#define MAX77693_FLASH_EN_TORCH 2
> +#define MAX77693_FLASH_EN_ON 3
> +
> +#define MAX77693_FLASH_EN1_SHIFT 6
> +#define MAX77693_FLASH_EN2_SHIFT 4
> +#define MAX77693_TORCH_EN1_SHIFT 2
> +#define MAX77693_TORCH_EN2_SHIFT 0
> +
> +#define MAX77693_FLASH_LOW_BATTERY_EN 0x80
> +
> +#define MAX77693_FLASH_BOOST_FIXED 0x04
> +#define MAX77693_FLASH_BOOST_LEDNUM_2 0x80
> +
> +#define MAX77693_FLASH_TIMEOUT_MIN 62500
> +#define MAX77693_FLASH_TIMEOUT_MAX 1000000
> +#define MAX77693_FLASH_TIMEOUT_STEP 62500
> +
> +#define MAX77693_TORCH_TIMEOUT_MIN 262000
> +#define MAX77693_TORCH_TIMEOUT_MAX 15728000
> +
> +#define MAX77693_FLASH_IOUT_MIN 15625
> +#define MAX77693_FLASH_IOUT_MAX_1LED 1000000
> +#define MAX77693_FLASH_IOUT_MAX_2LEDS 625000
> +#define MAX77693_FLASH_IOUT_STEP 15625
> +
> +#define MAX77693_TORCH_IOUT_MIN 15625
> +#define MAX77693_TORCH_IOUT_MAX 250000
> +#define MAX77693_TORCH_IOUT_STEP 15625
> +
> +#define MAX77693_FLASH_VSYS_MIN 2400
> +#define MAX77693_FLASH_VSYS_MAX 3400
> +#define MAX77693_FLASH_VSYS_STEP 33
> +
> +#define MAX77693_FLASH_VOUT_MIN 3300
> +#define MAX77693_FLASH_VOUT_MAX 5500
> +#define MAX77693_FLASH_VOUT_STEP 25
> +#define MAX77693_FLASH_VOUT_RMIN 0x0c
> +
> +#define MAX77693_LED_STATUS_FLASH_ON (1 << 3)
> +#define MAX77693_LED_STATUS_TORCH_ON (1 << 2)
> +
> +#define MAX77693_LED_FLASH_INT_FLED2_OPEN (1 << 0)
> +#define MAX77693_LED_FLASH_INT_FLED2_SHORT (1 << 1)
> +#define MAX77693_LED_FLASH_INT_FLED1_OPEN (1 << 2)
> +#define MAX77693_LED_FLASH_INT_FLED1_SHORT (1 << 3)
> +#define MAX77693_LED_FLASH_INT_OVER_CURRENT (1 << 4)
> +
> +#define MAX77693_MODE_OFF 0
> +#define MAX77693_MODE_FLASH (1 << 0)
> +#define MAX77693_MODE_TORCH (1 << 1)
> +#define MAX77693_MODE_FLASH_EXTERNAL (1 << 2)
> +
> +enum {
> + FLASH1,
> + FLASH2,
> + TORCH1,
> + TORCH2
> +};
> +
> +enum {
> + FLASH,
> + TORCH
> +};
> +
> +struct max77693_led {
> + struct regmap *regmap;
> + struct platform_device *pdev;
> + struct max77693_led_platform_data *pdata;
> + struct mutex lock;
> +
> + struct led_classdev ldev;
> +
> + unsigned int torch_brightness;
> + struct work_struct work_brightness_set;
> + unsigned int mode_flags;
> +};
> +
> +static u8 max77693_led_iout_to_reg(u32 ua)
> +{
> + if (ua < MAX77693_FLASH_IOUT_MIN)
> + ua = MAX77693_FLASH_IOUT_MIN;
> + return (ua - MAX77693_FLASH_IOUT_MIN) / MAX77693_FLASH_IOUT_STEP;
> +}
> +
> +static u8 max77693_flash_timeout_to_reg(u32 us)
> +{
> + return (us - MAX77693_FLASH_TIMEOUT_MIN) / MAX77693_FLASH_TIMEOUT_STEP;
> +}
> +
> +static const u32 max77693_torch_timeouts[] = {
> + 262000, 524000, 786000, 1048000,
> + 1572000, 2096000, 2620000, 3144000,
> + 4193000, 5242000, 6291000, 7340000,
> + 9437000, 11534000, 13631000, 1572800
> +};
> +
> +static u8 max77693_torch_timeout_to_reg(u32 us)
> +{
> + int i, b = 0, e = ARRAY_SIZE(max77693_torch_timeouts);

I haven't run this, but it looks like it'll access max77693_torch_timeouts[]
array after the last element if you pass it a value greater than 1572800.
Shouldn't e be initialised to ARRAY_SIZE() - 1 instead?

> +
> + while (e - b > 1) {
> + i = b + (e - b) / 2;
> + if (us < max77693_torch_timeouts[i])
> + e = i;
> + else
> + b = i;
> + }
> + return b;
> +}
> +
> +static struct max77693_led *ldev_to_led(struct led_classdev *ldev)
> +{
> + return container_of(ldev, struct max77693_led, ldev);
> +}
> +
> +static u32 max77693_torch_timeout_from_reg(u8 reg)
> +{

I might limit reg to ARRAY_SIZE(...) as well. Up to you.

> + return max77693_torch_timeouts[reg];
> +}
> +
> +static u8 max77693_led_vsys_to_reg(u32 mv)
> +{
> + return ((mv - MAX77693_FLASH_VSYS_MIN) / MAX77693_FLASH_VSYS_STEP) << 2;
> +}
> +
> +static u8 max77693_led_vout_to_reg(u32 mv)
> +{
> + return (mv - MAX77693_FLASH_VOUT_MIN) / MAX77693_FLASH_VOUT_STEP +
> + MAX77693_FLASH_VOUT_RMIN;
> +}
> +
> +/* split composite current @i into two @iout according to @imax weights */

Are there dependencies between the two LEDs or are they entirely
independent? If they're independent (with the possible exception of strobe),
then I'd expose them individually.

> +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 / MAX77693_FLASH_IOUT_STEP * MAX77693_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 & MAX77693_MODE_TORCH) {
> + if (p->trigger[TORCH1] & MAX77693_LED_TRIG_SOFT)
> + v |= MAX77693_FLASH_EN_ON << MAX77693_TORCH_EN1_SHIFT;
> + if (p->trigger[TORCH2] & MAX77693_LED_TRIG_SOFT)
> + v |= MAX77693_FLASH_EN_ON << MAX77693_TORCH_EN2_SHIFT;
> + }
> +
> + if (mode & MAX77693_MODE_FLASH) {
> + if (p->trigger[FLASH1] & MAX77693_LED_TRIG_SOFT)
> + v |= MAX77693_FLASH_EN_ON << MAX77693_FLASH_EN1_SHIFT;
> + if (p->trigger[FLASH2] & MAX77693_LED_TRIG_SOFT)
> + v |= MAX77693_FLASH_EN_ON << MAX77693_FLASH_EN2_SHIFT;
> + } else if (mode & MAX77693_MODE_FLASH_EXTERNAL) {
> + if (p->trigger[FLASH1] & MAX77693_LED_TRIG_EXT)
> + v |= MAX77693_FLASH_EN_FLASH << MAX77693_FLASH_EN1_SHIFT;
> + if (p->trigger[FLASH2] & MAX77693_LED_TRIG_EXT)
> + v |= MAX77693_FLASH_EN_FLASH << MAX77693_FLASH_EN2_SHIFT;
> + /*
> + * 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[TORCH1] & MAX77693_LED_TRIG_EXT)
> + v |= MAX77693_FLASH_EN_TORCH << MAX77693_TORCH_EN1_SHIFT;
> + if (p->trigger[TORCH2] & MAX77693_LED_TRIG_EXT)
> + v |= MAX77693_FLASH_EN_TORCH << MAX77693_TORCH_EN2_SHIFT;
> + }
> +
> + /* Reset the register only prior setting flash modes */
> + if (mode != MAX77693_MODE_TORCH) {
> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_EN, 0);

A single wrie for strobe. Looks good!

> + if (ret < 0)
> + return ret;
> + }
> +
> + return max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_EN, v);
> +}
> +
> +static int max77693_add_mode(struct max77693_led *led, unsigned int mode)
> +{
> + int ret;
> +
> + /* Once enabled torch mode is active until turned off */
> + if ((mode == MAX77693_MODE_TORCH) &&
> + (led->mode_flags & MAX77693_MODE_TORCH))
> + return 0;
> +
> + /*
> + * FLASH_EXTERNAL mode activates HW triggered flash and torch
> + * modes in the device. The related register settings interfere
> + * with SW triggerred modes, thus clear them to ensure proper
> + * device configuration.
> + */
> + if (mode == MAX77693_MODE_FLASH_EXTERNAL)
> + led->mode_flags &= (~MAX77693_MODE_TORCH &
> + ~MAX77693_MODE_FLASH);
> +
> + 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
> + * spurous flash strobing on each successive torch mode
> + * setting.
> + */
> + if ((mode == MAX77693_MODE_FLASH) ||
> + (mode == MAX77693_MODE_FLASH_EXTERNAL))
> + led->mode_flags &= ~mode;
> +
> + return 0;
> +}
> +
> +static int max77693_clear_mode(struct max77693_led *led, unsigned int mode)
> +{
> + led->mode_flags &= ~mode;
> +
> + return max77693_set_mode(led, led->mode_flags);
> +}
> +
> +static int max77693_set_torch_current(struct max77693_led *led,
> + u32 micro_amp)
> +{
> + struct max77693_led_platform_data *p = led->pdata;
> + struct regmap *rmap = led->regmap;
> + u32 iout[2];
> + u8 v, iout1_reg, iout2_reg;
> + int ret;
> +
> + max77693_calc_iout(iout, micro_amp, &p->iout[TORCH1]);
> + iout1_reg = max77693_led_iout_to_reg(iout[0]);
> + iout2_reg = max77693_led_iout_to_reg(iout[1]);
> +
> + v = iout1_reg | (iout2_reg << MAX77693_TORCH_IOUT_BITS);
> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCH, v);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int max77693_set_flash_current(struct max77693_led *led,
> + u32 micro_amp)
> +{
> + struct max77693_led_platform_data *p = led->pdata;
> + struct regmap *rmap = led->regmap;
> + u32 iout[2];
> + u8 iout1_reg, iout2_reg;
> + int ret;
> +
> + max77693_calc_iout(iout, micro_amp, &p->iout[FLASH1]);
> + iout1_reg = max77693_led_iout_to_reg(iout[0]);
> + iout2_reg = max77693_led_iout_to_reg(iout[1]);
> +
> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH1, iout1_reg);
> + if (ret < 0)
> + goto error_ret;
> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH2, iout2_reg);
> + if (ret < 0)
> + goto error_ret;
> +
> +error_ret:
> + 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;
> +
> + v = max77693_flash_timeout_to_reg(timeout);
> +
> + if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
> + v |= MAX77693_FLASH_TIMER_LEVEL;
> +
> + return max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
> +}
> +
> +static int max77693_strobe_status_get(struct max77693_led *led)
> +{
> + struct regmap *rmap = led->regmap;
> + u8 v;
> + int ret;
> +
> + ret = max77693_read_reg(rmap, MAX77693_LED_REG_FLASH_INT_STATUS, &v);
> + if (ret < 0)
> + return ret;
> +
> + return !!(v & MAX77693_LED_STATUS_FLASH_ON);
> +}
> +
> +static int max77693_int_flag_get(struct max77693_led *led, u8 *v)
> +{
> + struct regmap *rmap = led->regmap;
> +
> + return max77693_read_reg(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 ret;
> + u8 v;
> +
> + ret = max77693_set_torch_current(led, p->iout[TORCH1] +
> + p->iout[TORCH2]);
> + if (ret < 0)
> + return ret;
> +
> + ret = max77693_set_flash_current(led, p->iout[FLASH1] +
> + p->iout[FLASH2]);
> + if (ret < 0)
> + return ret;
> +
> + if (p->timeout[TORCH] > 0)
> + v = max77693_torch_timeout_to_reg(p->timeout[TORCH]);
> + else
> + v = MAX77693_TORCH_NO_TIMER;
> + if (p->trigger_type[TORCH] == MAX77693_LED_TRIG_TYPE_LEVEL)
> + v |= MAX77693_FLASH_TIMER_LEVEL;
> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCHTIMER, v);
> + if (ret < 0)
> + return ret;
> +
> + v = max77693_flash_timeout_to_reg(p->timeout[FLASH]);
> + if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
> + v |= MAX77693_FLASH_TIMER_LEVEL;
> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
> + if (ret < 0)
> + return ret;
> +
> + if (p->low_vsys > 0)
> + v = max77693_led_vsys_to_reg(p->low_vsys) |
> + MAX77693_FLASH_LOW_BATTERY_EN;
> + else
> + v = 0;
> +
> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_MAX_FLASH1, v);
> + if (ret < 0)
> + return ret;
> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_MAX_FLASH2, 0);
> + if (ret < 0)
> + return ret;
> +
> + if (p->boost_mode[FLASH1] == MAX77693_LED_BOOST_FIXED ||
> + p->boost_mode[FLASH2] == MAX77693_LED_BOOST_FIXED)
> + v = MAX77693_FLASH_BOOST_FIXED;
> + else
> + v = p->boost_mode[FLASH1] | (p->boost_mode[FLASH2] << 1);
> + if (p->boost_mode[FLASH1] && p->boost_mode[FLASH2])
> + v |= MAX77693_FLASH_BOOST_LEDNUM_2;
> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_VOUT_CNTL, v);
> + if (ret < 0)
> + return ret;
> +
> + v = max77693_led_vout_to_reg(p->boost_vout);
> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
> + if (ret < 0)
> + return ret;
> +
> + return max77693_set_mode(led, MAX77693_MODE_OFF);
> +}
> +
> +/* LED subsystem callbacks */
> +
> +static void max77693_brightness_set_work(struct work_struct *work)
> +{
> + struct max77693_led *led =
> + container_of(work, struct max77693_led, work_brightness_set);
> + int ret;
> +
> + mutex_lock(&led->lock);
> +
> + if (led->torch_brightness == 0) {
> + ret = max77693_clear_mode(led, MAX77693_MODE_TORCH);
> + 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->torch_brightness *
> + MAX77693_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, MAX77693_MODE_TORCH);
> + if (ret < 0)
> + dev_dbg(&led->pdev->dev, "Failed to set torch mode (%d)\n",
> + ret);
> +unlock:
> + mutex_unlock(&led->lock);
> +}
> +
> +static void max77693_led_brightness_set(struct led_classdev *led_cdev,
> + enum led_brightness value)
> +{
> + struct max77693_led *led = ldev_to_led(led_cdev);
> +
> + led->torch_brightness = value;
> + schedule_work(&led->work_brightness_set);

Is there a reason not to do this right now (but in a work queue instead)?

> +}
> +
> +static int max77693_led_flash_strobe_get(struct led_classdev *led_cdev)
> +{
> + struct max77693_led *led = ldev_to_led(led_cdev);
> + int ret;
> +
> + mutex_lock(&led->lock);
> + ret = max77693_strobe_status_get(led);
> + mutex_unlock(&led->lock);
> +
> + return ret;
> +}
> +
> +static int max77693_led_flash_fault_get(struct led_classdev *led_cdev,
> + u32 *fault)
> +{
> + struct max77693_led *led = ldev_to_led(led_cdev);
> + u8 v;
> + int ret;
> +
> + mutex_lock(&led->lock);
> +
> + ret = max77693_int_flag_get(led, &v);
> + if (ret < 0)
> + goto unlock;
> +
> + *fault = 0;
> +
> + if (v & MAX77693_LED_FLASH_INT_FLED2_OPEN ||
> + v & MAX77693_LED_FLASH_INT_FLED1_OPEN)
> + *fault |= LED_FAULT_OVER_VOLTAGE;
> + if (v & MAX77693_LED_FLASH_INT_FLED2_SHORT ||
> + v & MAX77693_LED_FLASH_INT_FLED1_SHORT)
> + *fault |= LED_FAULT_SHORT_CIRCUIT;
> + if (v & MAX77693_LED_FLASH_INT_OVER_CURRENT)
> + *fault |= LED_FAULT_OVER_CURRENT;
> +unlock:
> + mutex_unlock(&led->lock);
> + return ret;
> +}
> +
> +static int max77693_led_flash_strobe_set(struct led_classdev *led_cdev,
> + bool state)
> +{
> + struct max77693_led *led = ldev_to_led(led_cdev);
> + struct led_flash *flash = led_cdev->flash;
> + int ret;
> +
> + mutex_lock(&led->lock);
> +
> + if (flash->external_strobe) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + if (!state) {
> + ret = max77693_clear_mode(led, MAX77693_MODE_FLASH);
> + goto unlock;
> + }
> +
> + ret = max77693_add_mode(led, MAX77693_MODE_FLASH);
> + if (ret < 0)
> + goto unlock;
> +unlock:
> + mutex_unlock(&led->lock);
> + return ret;
> +}
> +
> +static int max77693_led_external_strobe_set(struct led_classdev *led_cdev,
> + bool enable)
> +{
> + struct max77693_led *led = ldev_to_led(led_cdev);
> + int ret;
> +
> + mutex_lock(&led->lock);
> +
> + if (enable)
> + ret = max77693_add_mode(led, MAX77693_MODE_FLASH_EXTERNAL);
> + else
> + ret = max77693_clear_mode(led, MAX77693_MODE_FLASH_EXTERNAL);
> +
> + mutex_unlock(&led->lock);
> +
> + return ret;
> +}
> +
> +static int max77693_led_flash_brightness_set(struct led_classdev *led_cdev,
> + u32 brightness)
> +{
> + struct max77693_led *led = ldev_to_led(led_cdev);
> + int ret;
> +
> + mutex_lock(&led->lock);
> +
> + ret = max77693_set_flash_current(led, brightness);
> + if (ret < 0)
> + goto unlock;
> +unlock:
> + mutex_unlock(&led->lock);
> + return ret;
> +}
> +
> +static int max77693_led_flash_timeout_set(struct led_classdev *led_cdev,
> + u32 timeout)
> +{
> + struct max77693_led *led = ldev_to_led(led_cdev);
> + int ret;
> +
> + mutex_lock(&led->lock);
> +
> + ret = max77693_set_timeout(led, timeout);
> + if (ret < 0)
> + goto unlock;
> +
> +unlock:
> + mutex_unlock(&led->lock);
> + return ret;
> +}
> +
> +static void max77693_led_parse_dt(struct max77693_led_platform_data *p,
> + struct device_node *node)
> +{
> + of_property_read_u32_array(node, "maxim,iout", p->iout, 4);

How about separate current for flash and torch modes? They are the same
LEDs; just the mode is different.

> + of_property_read_u32_array(node, "maxim,trigger", p->trigger, 4);
> + of_property_read_u32_array(node, "maxim,trigger-type", p->trigger_type,
> + 2);
> + of_property_read_u32_array(node, "maxim,timeout", p->timeout, 2);
> + of_property_read_u32_array(node, "maxim,boost-mode", p->boost_mode, 2);
> + of_property_read_u32(node, "maxim,boost-vout", &p->boost_vout);
> + of_property_read_u32(node, "maxim,vsys-min", &p->low_vsys);

Are these values specific to the maxim chip? I'd suppose e.g. timeout and
iout are something that can be found pretty much in any flash controller.

> +}
> +
> +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;
> +
> + for (i = 0; i < 2; ++i)

How about using ARRAY_SIZE() here, too?

> + clamp_align(&p->boost_mode[i], MAX77693_LED_BOOST_NONE,
> + MAX77693_LED_BOOST_FIXED, 1);
> + /* boost, if enabled, should be the same on both leds */
> + if (p->boost_mode[0] != MAX77693_LED_BOOST_NONE &&
> + p->boost_mode[1] != MAX77693_LED_BOOST_NONE)
> + p->boost_mode[1] = p->boost_mode[0];
> +
> + max = (p->boost_mode[FLASH1] && p->boost_mode[FLASH2]) ?
> + MAX77693_FLASH_IOUT_MAX_2LEDS : MAX77693_FLASH_IOUT_MAX_1LED;
> +
> + clamp_align(&p->iout[FLASH1], MAX77693_FLASH_IOUT_MIN,
> + max, MAX77693_FLASH_IOUT_STEP);
> + clamp_align(&p->iout[FLASH2], MAX77693_FLASH_IOUT_MIN,
> + max, MAX77693_FLASH_IOUT_STEP);
> + clamp_align(&p->iout[TORCH1], MAX77693_TORCH_IOUT_MIN,
> + MAX77693_TORCH_IOUT_MAX, MAX77693_TORCH_IOUT_STEP);
> + clamp_align(&p->iout[TORCH2], MAX77693_TORCH_IOUT_MIN,
> + MAX77693_TORCH_IOUT_MAX, MAX77693_TORCH_IOUT_STEP);
> +
> + for (i = 0; i < 4; ++i)
> + clamp_align(&p->trigger[i], 0, 7, 1);
> + for (i = 0; i < 2; ++i)
> + clamp_align(&p->trigger_type[i], MAX77693_LED_TRIG_TYPE_EDGE,
> + MAX77693_LED_TRIG_TYPE_LEVEL, 1);

ARRAY_SIZE() would be nicer than using numeric values for the loop
condition.

> + clamp_align(&p->timeout[FLASH], MAX77693_FLASH_TIMEOUT_MIN,
> + MAX77693_FLASH_TIMEOUT_MAX, MAX77693_FLASH_TIMEOUT_STEP);
> +
> + if (p->timeout[TORCH]) {
> + clamp_align(&p->timeout[TORCH], MAX77693_TORCH_TIMEOUT_MIN,
> + MAX77693_TORCH_TIMEOUT_MAX, 1);
> + p->timeout[TORCH] = max77693_torch_timeout_from_reg(
> + max77693_torch_timeout_to_reg(p->timeout[TORCH]));
> + }
> +
> + clamp_align(&p->boost_vout, MAX77693_FLASH_VOUT_MIN,
> + MAX77693_FLASH_VOUT_MAX, MAX77693_FLASH_VOUT_STEP);
> +
> + if (p->low_vsys) {
> + clamp_align(&p->low_vsys, MAX77693_FLASH_VSYS_MIN,
> + MAX77693_FLASH_VSYS_MAX, MAX77693_FLASH_VSYS_STEP);
> + }
> +}
> +
> +static int max77693_led_get_platform_data(struct max77693_led *led)
> +{
> + struct max77693_led_platform_data *p;
> + struct device *dev = &led->pdev->dev;
> +
> + if (dev->of_node) {
> + p = devm_kzalloc(dev, sizeof(*led->pdata), GFP_KERNEL);
> + if (!p)
> + return -ENOMEM;

Check for p can be moved out of the if as it's the same for both.

You could also use led->pdata directly. Up to you.

> + max77693_led_parse_dt(p, dev->of_node);
> + } else {
> + p = dev_get_platdata(dev);
> + if (!p)
> + return -ENODEV;
> + }
> + led->pdata = p;
> +
> + max77693_led_validate_platform_data(p);
> +
> + return 0;
> +}
> +
> +static struct led_flash led_flash = {
> + .ops = {
> + .brightness_set = max77693_led_flash_brightness_set,
> + .strobe_set = max77693_led_flash_strobe_set,
> + .strobe_get = max77693_led_flash_strobe_get,
> + .timeout_set = max77693_led_flash_timeout_set,
> + .external_strobe_set = max77693_led_external_strobe_set,
> + .fault_get = max77693_led_flash_fault_get,
> + },
> + .has_flash_led = true,
> +};
> +
> +static void max77693_init_led_controls(struct led_classdev *led_cdev,
> + struct max77693_led_platform_data *p)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + struct led_ctrl *c;
> +
> + /*
> + * brightness_ctrl and fault_flags are used only
> + * for initializing related V4L2 controls.
> + */
> +#ifdef CONFIG_V4L2_FLASH
> + flash->fault_flags = V4L2_FLASH_FAULT_OVER_VOLTAGE |
> + V4L2_FLASH_FAULT_SHORT_CIRCUIT |
> + V4L2_FLASH_FAULT_OVER_CURRENT;
> +
> + c = &led_cdev->brightness_ctrl;
> + c->min = (p->iout[TORCH1] != 0 && p->iout[TORCH2] != 0) ?
> + MAX77693_TORCH_IOUT_MIN * 2 :
> + MAX77693_TORCH_IOUT_MIN;
> + c->max = p->iout[TORCH1] + p->iout[TORCH2];
> + c->step = MAX77693_TORCH_IOUT_STEP;
> + c->val = p->iout[TORCH1] + p->iout[TORCH2];

Can you control the current for the two flash LEDs separately? If yes, this
should be also available on the V4L2 flash API. The lm3560 driver does this,
for example. (It creates two sub-devices since we can only control a single
LED using a single sub-device, at least for the time being.)

> +#endif
> +
> + c = &flash->brightness;
> + c->min = (p->iout[FLASH1] != 0 && p->iout[FLASH2] != 0) ?
> + MAX77693_FLASH_IOUT_MIN * 2 :
> + MAX77693_FLASH_IOUT_MIN;
> + c->max = p->iout[FLASH1] + p->iout[FLASH2];
> + c->step = MAX77693_FLASH_IOUT_STEP;
> + c->val = p->iout[FLASH1] + p->iout[FLASH2];
> +
> + c = &flash->timeout;
> + c->min = MAX77693_FLASH_TIMEOUT_MIN;
> + c->max = MAX77693_FLASH_TIMEOUT_MAX;
> + c->step = MAX77693_FLASH_TIMEOUT_STEP;
> + c->val = p->timeout[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 led_classdev *led_cdev;
> + int ret;
> +
> + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
> + if (!led)
> + return -ENOMEM;
> +
> + led->pdev = pdev;
> + led_cdev = &led->ldev;
> + led->regmap = iodev->regmap;
> + platform_set_drvdata(pdev, led);
> + ret = max77693_led_get_platform_data(led);
> + if (ret < 0)
> + return -EINVAL;
> + p = led->pdata;
> +
> + mutex_init(&led->lock);
> +
> + INIT_WORK(&led->work_brightness_set, max77693_brightness_set_work);
> +
> + /* LED class device initialization */
> + led_cdev->name = MAX77693_LED_NAME;
> + led_cdev->brightness_set = max77693_led_brightness_set;
> + led_cdev->max_brightness = (p->iout[TORCH1] + p->iout[TORCH2]) /
> + MAX77693_TORCH_IOUT_STEP;
> +
> + if ((p->trigger[FLASH1] & MAX77693_LED_TRIG_FLASH) ||
> + (p->trigger[FLASH2] & MAX77693_LED_TRIG_FLASH))
> + led_flash.has_external_strobe = true;
> + led_cdev->flash = &led_flash;
> +
> + max77693_init_led_controls(led_cdev, p);
> +
> + /* Register in the LED subsystem. */
> + ret = led_classdev_flash_register(&pdev->dev, led_cdev);
> + if (ret < 0)

mutex_destroy()?

> + return -EINVAL;
> +
> + ret = max77693_setup(led);

Do you intentionally ignore the return value?

> + return 0;
> +}
> +
> +static int max77693_led_remove(struct platform_device *pdev)
> +{
> + struct max77693_led *led = platform_get_drvdata(pdev);
> +
> + led_classdev_flash_unregister(&led->ldev);

mutex_destroy()?

> + return 0;
> +}
> +
> +static struct of_device_id max77693_led_dt_match[] = {
> + {.compatible = "maxim,max77693-flash"},
> + {},
> +};
> +
> +static struct platform_driver max77693_led_driver = {
> + .probe = max77693_led_probe,
> + .remove = max77693_led_remove,
> + .driver = {
> + .name = "max77693-flash",
> + .owner = THIS_MODULE,
> + .of_match_table = max77693_led_dt_match,
> + },
> +};
> +
> +module_platform_driver(max77693_led_driver);
> +
> +MODULE_AUTHOR("Andrzej Hajda <[email protected]>");
> +MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
> +MODULE_DESCRIPTION("Maxim MAX77693 led flash driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/mfd/max77693.c b/drivers/mfd/max77693.c
> index c5535f0..f061aa8 100644
> --- a/drivers/mfd/max77693.c
> +++ b/drivers/mfd/max77693.c
> @@ -44,7 +44,7 @@
> static const struct mfd_cell max77693_devs[] = {
> { .name = "max77693-pmic", },
> { .name = "max77693-charger", },
> - { .name = "max77693-flash", },
> + { .name = "max77693-flash", .of_compatible = "maxim,max77693-flash", },
> { .name = "max77693-muic", },
> { .name = "max77693-haptic", },
> };
> diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
> index 3f3dc45..f2285b7 100644
> --- a/include/linux/mfd/max77693.h
> +++ b/include/linux/mfd/max77693.h
> @@ -63,6 +63,43 @@ struct max77693_muic_platform_data {
> int path_uart;
> };
>
> +/* MAX77693 led flash */
> +
> +/* triggers */
> +enum max77693_led_trigger {
> + MAX77693_LED_TRIG_OFF,
> + MAX77693_LED_TRIG_FLASH,
> + MAX77693_LED_TRIG_TORCH,
> + MAX77693_LED_TRIG_EXT,
> + MAX77693_LED_TRIG_SOFT,
> +};
> +
> +
> +/* trigger types */
> +enum max77693_led_trigger_type {
> + MAX77693_LED_TRIG_TYPE_EDGE,
> + MAX77693_LED_TRIG_TYPE_LEVEL,
> +};
> +
> +/* boost modes */
> +enum max77693_led_boost_mode {
> + MAX77693_LED_BOOST_NONE,
> + MAX77693_LED_BOOST_ADAPTIVE,
> + MAX77693_LED_BOOST_FIXED,
> +};
> +
> +struct max77693_led_platform_data {
> + u32 iout[4];
> + u32 trigger[4];
> + u32 trigger_type[2];
> + u32 timeout[2];
> + u32 boost_mode[2];
> + u32 boost_vout;
> + u32 low_vsys;
> +};
> +
> +/* MAX77693 */
> +
> struct max77693_platform_data {
> /* regulator data */
> struct max77693_regulator_data *regulators;
> @@ -70,5 +107,6 @@ struct max77693_platform_data {
>
> /* muic data */
> struct max77693_muic_platform_data *muic_data;
> + struct max77693_led_platform_data *led_data;
> };
> #endif /* __LINUX_MFD_MAX77693_H */

--
Kind regards,

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

2014-04-16 18:22:19

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 5/5] media: Add registration helpers for V4L2 flash sub-devices

Hi Jacek,

Thanks for the update!

On Fri, Apr 11, 2014 at 04:56:56PM +0200, Jacek Anaszewski wrote:
> This patch adds helper functions for registering/unregistering
> LED class flash devices as V4L2 subdevs. 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]>
> ---
> drivers/media/v4l2-core/Kconfig | 10 +
> drivers/media/v4l2-core/Makefile | 2 +
> drivers/media/v4l2-core/v4l2-flash.c | 393 ++++++++++++++++++++++++++++++++++
> include/media/v4l2-flash.h | 119 ++++++++++
> 4 files changed, 524 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 2189bfb..1f8514d 100644
> --- a/drivers/media/v4l2-core/Kconfig
> +++ b/drivers/media/v4l2-core/Kconfig
> @@ -35,6 +35,16 @@ config V4L2_MEM2MEM_DEV
> tristate
> depends on VIDEOBUF2_CORE
>
> +# Used by LED subsystem flash drivers
> +config V4L2_FLASH
> + bool "Enable support for Flash sub-devices"
> + depends on LEDS_CLASS_FLASH

Also:

depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && MEDIA_CAMERA_SUPPORT

The last one might be a matter of opinion.

> + ---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 c6ae7ba..8e37ab4 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) += 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..f1be332
> --- /dev/null
> +++ b/drivers/media/v4l2-core/v4l2-flash.c
> @@ -0,0 +1,393 @@
> +/*
> + * 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/leds_flash.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-flash.h>
> +
> +static inline enum led_brightness v4l2_flash_intensity_to_led_brightness(
> + struct led_ctrl *config,
> + u32 intensity)

Fits on a single line.

> +{
> + return intensity / config->step;

Shouldn't you first decrement the minimum before the division?

> +}
> +
> +static inline u32 v4l2_flash_led_brightness_to_intensity(
> + struct led_ctrl *config,
> + enum led_brightness brightness)
> +{
> + return brightness * config->step;

And do the opposite here?

> +}
> +
> +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 *led_cdev = v4l2_flash->led_cdev;
> + struct led_flash *flash = led_cdev->flash;
> + struct v4l2_flash_ctrl *ctrl = &v4l2_flash->ctrl;
> + u32 fault;
> + int ret;
> +
> + switch (c->id) {
> + case V4L2_CID_FLASH_TORCH_INTENSITY:
> + if (ctrl->led_mode->val == V4L2_FLASH_LED_MODE_TORCH) {
> + ret = v4l2_call_flash_op(brightness_update, led_cdev);
> + if (ret < 0)
> + return ret;
> + ctrl->torch_intensity->val =
> + v4l2_flash_led_brightness_to_intensity(
> + &led_cdev->brightness_ctrl,
> + led_cdev->brightness);
> + }
> + return 0;
> + case V4L2_CID_FLASH_INTENSITY:
> + ret = v4l2_call_flash_op(flash_brightness_update, led_cdev);
> + if (ret < 0)
> + return ret;
> + /* no conversion is needed */
> + c->val = flash->brightness.val;
> + return 0;
> + case V4L2_CID_FLASH_INDICATOR_INTENSITY:
> + ret = v4l2_call_flash_op(indicator_brightness_update, led_cdev);
> + if (ret < 0)
> + return ret;
> + /* no conversion is needed */
> + c->val = flash->indicator_brightness->val;
> + return 0;
> + case V4L2_CID_FLASH_STROBE_STATUS:
> + ret = v4l2_call_flash_op(strobe_get, led_cdev);
> + if (ret < 0)
> + return ret;
> + c->val = !!ret;
> + return 0;
> + case V4L2_CID_FLASH_FAULT:
> + /* led faults map directly to V4L2 flash faults */
> + ret = v4l2_call_flash_op(fault_get, led_cdev, &fault);
> + if (!ret)

The return value seems to come all the way from regmap_read() and the bus
related implementatio. I would just consider negative values errors, as
above.

> + c->val = fault;
> + return ret;
> + 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 *led_cdev = v4l2_flash->led_cdev;
> + struct v4l2_flash_ctrl *ctrl = &v4l2_flash->ctrl;
> + enum led_brightness torch_brightness;
> + bool external_strobe;
> + int ret;
> +
> + switch (c->id) {
> + case V4L2_CID_FLASH_LED_MODE:
> + switch (c->val) {
> + case V4L2_FLASH_LED_MODE_NONE:
> + v4l2_call_flash_op(brightness_set, led_cdev, 0);
> + return v4l2_call_flash_op(strobe_set, led_cdev, false);
> + case V4L2_FLASH_LED_MODE_FLASH:
> + /* Turn off torch LED */
> + v4l2_call_flash_op(brightness_set, led_cdev, 0);
> + external_strobe = (ctrl->source->val ==
> + V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
> + return v4l2_call_flash_op(external_strobe_set, led_cdev,
> + external_strobe);
> + case V4L2_FLASH_LED_MODE_TORCH:
> + /* Stop flash strobing */
> + ret = v4l2_call_flash_op(strobe_set, led_cdev, false);
> + if (ret)
> + return ret;
> + /* torch is always triggered by software */
> + ret = v4l2_call_flash_op(external_strobe_set, led_cdev,
> + false);

Does the LED API not assume this at the moment? I'm not saying it should,
though. :-)

> + if (ret)
> + return ret;
> +
> + torch_brightness =
> + v4l2_flash_intensity_to_led_brightness(
> + &led_cdev->brightness_ctrl,
> + ctrl->torch_intensity->val);
> + v4l2_call_flash_op(brightness_set, led_cdev,
> + torch_brightness);
> + return ret;
> + }
> + break;
> + case V4L2_CID_FLASH_STROBE_SOURCE:
> + return v4l2_call_flash_op(external_strobe_set, led_cdev,
> + c->val == V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
> + case V4L2_CID_FLASH_STROBE:
> + if (ctrl->led_mode->val != V4L2_FLASH_LED_MODE_FLASH ||
> + ctrl->source->val != V4L2_FLASH_STROBE_SOURCE_SOFTWARE)

I vote for -EBUSY instead.

> + return -EINVAL;
> + return v4l2_call_flash_op(strobe_set, led_cdev, true);
> + case V4L2_CID_FLASH_STROBE_STOP:
> + return v4l2_call_flash_op(strobe_set, led_cdev, false);
> + case V4L2_CID_FLASH_TIMEOUT:
> + ret = v4l2_call_flash_op(timeout_set, led_cdev, c->val);
> + case V4L2_CID_FLASH_INTENSITY:
> + /* no conversion is needed */
> + return v4l2_call_flash_op(flash_brightness_set, led_cdev,
> + c->val);
> + case V4L2_CID_FLASH_INDICATOR_INTENSITY:
> + /* no conversion is needed */
> + return v4l2_call_flash_op(indicator_brightness_set, led_cdev,
> + c->val);
> + case V4L2_CID_FLASH_TORCH_INTENSITY:
> + if (ctrl->led_mode->val == V4L2_FLASH_LED_MODE_TORCH) {
> + torch_brightness =
> + v4l2_flash_intensity_to_led_brightness(
> + &led_cdev->brightness_ctrl,
> + ctrl->torch_intensity->val);
> + v4l2_call_flash_op(brightness_set, led_cdev,
> + torch_brightness);

I could be missing something but don't torch and indicator require similar
handling?

> + }
> + 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 int v4l2_flash_init_controls(struct v4l2_flash *v4l2_flash)
> +
> +{
> + struct led_classdev *led_cdev = v4l2_flash->led_cdev;
> + struct led_flash *flash = led_cdev->flash;
> + bool has_indicator = flash->indicator_brightness;
> + struct v4l2_ctrl *ctrl;
> + struct led_ctrl *ctrl_cfg;
> + unsigned int mask;
> + int ret, max, num_ctrls;
> +
> + num_ctrls = flash->has_flash_led ? 8 : 2;
> + if (flash->fault_flags)
> + ++num_ctrls;
> + if (has_indicator)
> + ++num_ctrls;
> +
> + v4l2_ctrl_handler_init(&v4l2_flash->hdl, num_ctrls);
> +
> + mask = 1 << V4L2_FLASH_LED_MODE_NONE |
> + 1 << V4L2_FLASH_LED_MODE_TORCH;
> + if (flash->has_flash_led)
> + mask |= 1 << V4L2_FLASH_LED_MODE_FLASH;
> +
> + /* Configure FLASH_LED_MODE ctrl */
> + v4l2_flash->ctrl.led_mode = v4l2_ctrl_new_std_menu(
> + &v4l2_flash->hdl,
> + &v4l2_flash_ctrl_ops, V4L2_CID_FLASH_LED_MODE,
> + V4L2_FLASH_LED_MODE_TORCH, ~mask,
> + V4L2_FLASH_LED_MODE_NONE);
> +
> + /* Configure TORCH_INTENSITY ctrl */
> + ctrl_cfg = &led_cdev->brightness_ctrl;
> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> + V4L2_CID_FLASH_TORCH_INTENSITY,
> + ctrl_cfg->min, ctrl_cfg->max,
> + ctrl_cfg->step, ctrl_cfg->val);
> + if (ctrl)
> + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
> + v4l2_flash->ctrl.torch_intensity = ctrl;
> +
> + if (flash->has_flash_led) {
> + /* Configure FLASH_STROBE_SOURCE ctrl */
> + mask = 1 << V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
> +
> + if (flash->has_external_strobe) {
> + mask |= 1 << V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
> + max = V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
> + } else {
> + max = V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
> + }
> +
> + v4l2_flash->ctrl.source = v4l2_ctrl_new_std_menu(
> + &v4l2_flash->hdl,
> + &v4l2_flash_ctrl_ops,
> + V4L2_CID_FLASH_STROBE_SOURCE,
> + max,
> + ~mask,
> + V4L2_FLASH_STROBE_SOURCE_SOFTWARE);
> +
> + /* Configure FLASH_STROBE ctrl */
> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> + V4L2_CID_FLASH_STROBE, 0, 1, 1, 0);
> +
> + /* Configure FLASH_STROBE_STOP ctrl */
> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> + V4L2_CID_FLASH_STROBE_STOP,
> + 0, 1, 1, 0);
> +
> + /* Configure FLASH_STROBE_STATUS ctrl */
> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> + V4L2_CID_FLASH_STROBE_STATUS,
> + 0, 1, 1, 1);
> + if (ctrl)
> + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE |
> + V4L2_CTRL_FLAG_READ_ONLY;
> +
> + /* Configure FLASH_TIMEOUT ctrl */
> + ctrl_cfg = &flash->timeout;
> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> + V4L2_CID_FLASH_TIMEOUT, ctrl_cfg->min,
> + ctrl_cfg->max, ctrl_cfg->step,
> + ctrl_cfg->val);
> + if (ctrl)
> + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
> +
> + /* Configure FLASH_INTENSITY ctrl */
> + ctrl_cfg = &flash->brightness;
> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> + V4L2_CID_FLASH_INTENSITY,
> + ctrl_cfg->min, ctrl_cfg->max,
> + ctrl_cfg->step, ctrl_cfg->val);
> + if (ctrl)
> + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
> +
> + if (flash->fault_flags) {
> + /* Configure FLASH_FAULT ctrl */
> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl,
> + &v4l2_flash_ctrl_ops,
> + V4L2_CID_FLASH_FAULT, 0,
> + flash->fault_flags,
> + 0, 0);
> + if (ctrl)
> + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE |
> + V4L2_CTRL_FLAG_READ_ONLY;
> + }
> + if (has_indicator) {

In theory it's possible to have an indicator without the flash. So I'd keep
the two independent.

> + /* Configure FLASH_INDICATOR_INTENSITY ctrl */
> + ctrl_cfg = flash->indicator_brightness;
> + ctrl = v4l2_ctrl_new_std(
> + &v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> + V4L2_CID_FLASH_INDICATOR_INTENSITY,
> + ctrl_cfg->min, ctrl_cfg->max,
> + ctrl_cfg->step, ctrl_cfg->val);
> + if (ctrl)
> + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
> + }
> + }
> +
> + if (v4l2_flash->hdl.error) {
> + ret = v4l2_flash->hdl.error;
> + goto error_free;
> + }
> +
> + ret = v4l2_ctrl_handler_setup(&v4l2_flash->hdl);
> + if (ret < 0)
> + goto error_free;
> +
> + v4l2_flash->subdev.ctrl_handler = &v4l2_flash->hdl;
> +
> + return 0;
> +
> +error_free:
> + 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 *led_cdev = v4l2_flash->led_cdev;
> +
> + mutex_lock(&led_cdev->led_lock);
> + v4l2_call_flash_op(sysfs_lock, led_cdev);

Have you thought about device power management yet?

Traditionally, the existing flash controller drivers are powered when a file
handle is opened. Many newer implementations seem to consume so little power
that it's not worthwhile to try to power them off.

Binding the power state to open file handles isn't very generic either but
it's very simple and works.

V4L2 sub-devices do not use runtime PM as of yet, so perhaps this could be
left for later.

> + mutex_unlock(&led_cdev->led_lock);
> +
> + return 0;
> +}
> +
> +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 *led_cdev = v4l2_flash->led_cdev;
> +
> + mutex_lock(&led_cdev->led_lock);
> + v4l2_call_flash_op(sysfs_unlock, led_cdev);
> + mutex_unlock(&led_cdev->led_lock);
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_internal_ops v4l2_flash_subdev_internal_ops = {
> + .open = v4l2_flash_open,
> + .close = v4l2_flash_close,
> +};
> +
> +static struct v4l2_subdev_ops v4l2_flash_subdev_ops = {
> +};
> +
> +int v4l2_flash_init(struct led_classdev *led_cdev,
> + const struct v4l2_flash_ops *ops)
> +{
> + struct v4l2_flash *flash = &led_cdev->flash->v4l2_flash;
> + struct v4l2_subdev *sd = &flash->subdev;
> + int ret;
> +
> + if (!led_cdev || !ops)
> + return -EINVAL;
> +
> + flash->led_cdev = led_cdev;
> + 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);

It'd be nice to have the bus address in the name as well. But that would
belong to the driver which calls v4l2_flash_init(). The existing flash
controller drivers probably don't do this but many sensor drivers already
do. The name is expected to be unique in the media device.

> + flash->ops = ops;
> +
> + ret = v4l2_flash_init_controls(flash);
> + 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(sd);
> + if (ret < 0)
> + goto err_init_entity;
> +
> + return 0;
> +
> +err_init_entity:
> + media_entity_cleanup(&sd->entity);
> +err_init_controls:
> + v4l2_ctrl_handler_free(sd->ctrl_handler);
> + return -EINVAL;
> +}
> +EXPORT_SYMBOL_GPL(v4l2_flash_init);
> +
> +void v4l2_flash_release(struct led_classdev *led_cdev)
> +{
> + struct v4l2_flash *flash = &led_cdev->flash->v4l2_flash;
> +
> + v4l2_ctrl_handler_free(flash->subdev.ctrl_handler);
> + v4l2_async_unregister_subdev(&flash->subdev);
> + media_entity_cleanup(&flash->subdev.entity);
> +}
> +EXPORT_SYMBOL_GPL(v4l2_flash_release);
> diff --git a/include/media/v4l2-flash.h b/include/media/v4l2-flash.h
> new file mode 100644
> index 0000000..fe16ddd
> --- /dev/null
> +++ b/include/media/v4l2-flash.h
> @@ -0,0 +1,119 @@
> +/*
> + * 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>
> +
> +#define v4l2_call_flash_op(op, args...) \
> + ((v4l2_flash)->ops->op(args)) \

Extra backslash above.

I don't think you should assume the caller will have a local variable called
v4l2_flash. :-)

> +struct led_classdev;
> +enum led_brightness;
> +
> +struct v4l2_flash_ops {
> + void (*brightness_set)(struct led_classdev *led_cdev,
> + enum led_brightness brightness);
> + int (*brightness_update)(struct led_classdev *led_cdev);
> + int (*flash_brightness_set)(struct led_classdev *led_cdev,
> + u32 brightness);
> + int (*flash_brightness_update)(struct led_classdev *led_cdev);
> + int (*strobe_set)(struct led_classdev *led_cdev,
> + bool state);
> + int (*strobe_get)(struct led_classdev *led_cdev);
> + int (*timeout_set)(struct led_classdev *led_cdev,
> + u32 timeout);
> + int (*indicator_brightness_set)(struct led_classdev *led_cdev,
> + u32 brightness);
> + int (*indicator_brightness_update)(struct led_classdev *led_cdev);
> + int (*external_strobe_set)(struct led_classdev *led_cdev,
> + bool enable);
> + int (*fault_get)(struct led_classdev *led_cdev,
> + u32 *fault);
> + void (*sysfs_lock)(struct led_classdev *led_cdev);
> + void (*sysfs_unlock)(struct led_classdev *led_cdev);
> +};
> +
> +/**
> + * struct v4l2_flash_ctrl - controls that define the sub-dev's state
> + * @source: V4L2_CID_FLASH_STROBE_SOURCE control
> + * @led_mode: V4L2_CID_FLASH_LED_MODE control
> + * @torch_intensity: V4L2_CID_FLASH_TORCH_INTENSITY control
> + */
> +struct v4l2_flash_ctrl {
> + struct v4l2_ctrl *source;
> + struct v4l2_ctrl *led_mode;
> + struct v4l2_ctrl *torch_intensity;
> +};
> +
> +/**
> + * struct v4l2_flash - Flash sub-device context
> + * @led_cdev: LED class device controlled by this sub-device
> + * @ops: LED class device ops
> + * @subdev: V4L2 sub-device
> + * @hdl: flash controls handler
> + * @ctrl: state defining controls
> + */
> +struct v4l2_flash {
> + struct led_classdev *led_cdev;
> + const struct v4l2_flash_ops *ops;
> +
> + struct v4l2_subdev subdev;
> + struct v4l2_ctrl_handler hdl;
> + struct v4l2_flash_ctrl ctrl;
> +};
> +
> +static inline struct v4l2_flash *v4l2_subdev_to_v4l2_flash(struct v4l2_subdev *sd)

Over 80 characters per line.

> +{
> + return container_of(sd, struct v4l2_flash, subdev);
> +}
> +
> +static inline struct v4l2_flash *v4l2_ctrl_to_v4l2_flash(struct v4l2_ctrl *c)
> +{
> + return container_of(c->handler, struct v4l2_flash, hdl);
> +}
> +
> +#ifdef CONFIG_V4L2_FLASH
> +/**
> + * v4l2_flash_init - initialize V4L2 flash led sub-device
> + * @led_cdev: the LED to create subdev upon
> + * @ops: LED subsystem callbacks
> + *
> + * Create V4L2 subdev wrapping given LED subsystem device.
> + */
> +int v4l2_flash_init(struct led_classdev *led_cdev,
> + const struct v4l2_flash_ops *ops);
> +
> +/**
> + * v4l2_flash_release - release V4L2 flash led sub-device
> + * @flash: a structure representing V4L2 flash led device
> + *
> + * Release V4L2 flash led subdev.
> + */
> +void v4l2_flash_release(struct led_classdev *led_cdev);
> +#else
> +static inline int v4l2_flash_init(struct led_classdev *led_cdev,
> + const struct v4l2_flash_ops *ops)
> +{
> + return 0;
> +}
> +
> +static inline void v4l2_flash_release(struct led_classdev *led_cdev)
> +{
> +}

Could you put the two to the first patch? It won't compile otherwise.

> +#endif /* CONFIG_V4L2_FLASH */
> +
> +#endif /* _V4L2_FLASH_H */

--
Kind regards,

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

2014-04-17 08:27:50

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 5/5] media: Add registration helpers for V4L2 flash sub-devices

Hi Sakari,

Thanks for the review.

On 04/16/2014 08:21 PM, Sakari Ailus wrote:
> Hi Jacek,
>
> Thanks for the update!
>
[...]
>> +static inline enum led_brightness v4l2_flash_intensity_to_led_brightness(
>> + struct led_ctrl *config,
>> + u32 intensity)
>
> Fits on a single line.
>
>> +{
>> + return intensity / config->step;
>
> Shouldn't you first decrement the minimum before the division?

Brightness level 0 means that led is off. Let's consider following case:

intensity - 15625
config->step - 15625
intensity / config->step = 1 (the lowest possible current level)

>> +}
>> +
>> +static inline u32 v4l2_flash_led_brightness_to_intensity(
>> + struct led_ctrl *config,
>> + enum led_brightness brightness)
>> +{
>> + return brightness * config->step;
>
> And do the opposite here?
>
>> +}
>> +
>> +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 *led_cdev = v4l2_flash->led_cdev;
>> + struct led_flash *flash = led_cdev->flash;
>> + struct v4l2_flash_ctrl *ctrl = &v4l2_flash->ctrl;
>> + u32 fault;
>> + int ret;
>> +
>> + switch (c->id) {
>> + case V4L2_CID_FLASH_TORCH_INTENSITY:
>> + if (ctrl->led_mode->val == V4L2_FLASH_LED_MODE_TORCH) {
>> + ret = v4l2_call_flash_op(brightness_update, led_cdev);
>> + if (ret < 0)
>> + return ret;
>> + ctrl->torch_intensity->val =
>> + v4l2_flash_led_brightness_to_intensity(
>> + &led_cdev->brightness_ctrl,
>> + led_cdev->brightness);
>> + }
>> + return 0;
>> + case V4L2_CID_FLASH_INTENSITY:
>> + ret = v4l2_call_flash_op(flash_brightness_update, led_cdev);
>> + if (ret < 0)
>> + return ret;
>> + /* no conversion is needed */
>> + c->val = flash->brightness.val;
>> + return 0;
>> + case V4L2_CID_FLASH_INDICATOR_INTENSITY:
>> + ret = v4l2_call_flash_op(indicator_brightness_update, led_cdev);
>> + if (ret < 0)
>> + return ret;
>> + /* no conversion is needed */
>> + c->val = flash->indicator_brightness->val;
>> + return 0;
>> + case V4L2_CID_FLASH_STROBE_STATUS:
>> + ret = v4l2_call_flash_op(strobe_get, led_cdev);
>> + if (ret < 0)
>> + return ret;
>> + c->val = !!ret;
>> + return 0;
>> + case V4L2_CID_FLASH_FAULT:
>> + /* led faults map directly to V4L2 flash faults */
>> + ret = v4l2_call_flash_op(fault_get, led_cdev, &fault);
>> + if (!ret)
>
> The return value seems to come all the way from regmap_read() and the bus
> related implementatio. I would just consider negative values errors, as
> above.

I think that ret would be returned if it wasn't equal to 0. But indeed
it should be done the same way as for the other cases.

>> + c->val = fault;
>> + return ret;
>> + 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 *led_cdev = v4l2_flash->led_cdev;
>> + struct v4l2_flash_ctrl *ctrl = &v4l2_flash->ctrl;
>> + enum led_brightness torch_brightness;
>> + bool external_strobe;
>> + int ret;
>> +
>> + switch (c->id) {
>> + case V4L2_CID_FLASH_LED_MODE:
>> + switch (c->val) {
>> + case V4L2_FLASH_LED_MODE_NONE:
>> + v4l2_call_flash_op(brightness_set, led_cdev, 0);
>> + return v4l2_call_flash_op(strobe_set, led_cdev, false);
>> + case V4L2_FLASH_LED_MODE_FLASH:
>> + /* Turn off torch LED */
>> + v4l2_call_flash_op(brightness_set, led_cdev, 0);
>> + external_strobe = (ctrl->source->val ==
>> + V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
>> + return v4l2_call_flash_op(external_strobe_set, led_cdev,
>> + external_strobe);
>> + case V4L2_FLASH_LED_MODE_TORCH:
>> + /* Stop flash strobing */
>> + ret = v4l2_call_flash_op(strobe_set, led_cdev, false);
>> + if (ret)
>> + return ret;
>> + /* torch is always triggered by software */
>> + ret = v4l2_call_flash_op(external_strobe_set, led_cdev,
>> + false);
>
> Does the LED API not assume this at the moment? I'm not saying it should,
> though. :-)

Actually strobe source should apply only to flash leds. I will
remove this call.

>> + if (ret)
>> + return ret;
>> +
>> + torch_brightness =
>> + v4l2_flash_intensity_to_led_brightness(
>> + &led_cdev->brightness_ctrl,
>> + ctrl->torch_intensity->val);
>> + v4l2_call_flash_op(brightness_set, led_cdev,
>> + torch_brightness);
>> + return ret;
>> + }
>> + break;
>> + case V4L2_CID_FLASH_STROBE_SOURCE:
>> + return v4l2_call_flash_op(external_strobe_set, led_cdev,
>> + c->val == V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
>> + case V4L2_CID_FLASH_STROBE:
>> + if (ctrl->led_mode->val != V4L2_FLASH_LED_MODE_FLASH ||
>> + ctrl->source->val != V4L2_FLASH_STROBE_SOURCE_SOFTWARE)
>
> I vote for -EBUSY instead.

I agree.

>> + return -EINVAL;
>> + return v4l2_call_flash_op(strobe_set, led_cdev, true);
>> + case V4L2_CID_FLASH_STROBE_STOP:
>> + return v4l2_call_flash_op(strobe_set, led_cdev, false);
>> + case V4L2_CID_FLASH_TIMEOUT:
>> + ret = v4l2_call_flash_op(timeout_set, led_cdev, c->val);
>> + case V4L2_CID_FLASH_INTENSITY:
>> + /* no conversion is needed */
>> + return v4l2_call_flash_op(flash_brightness_set, led_cdev,
>> + c->val);
>> + case V4L2_CID_FLASH_INDICATOR_INTENSITY:
>> + /* no conversion is needed */
>> + return v4l2_call_flash_op(indicator_brightness_set, led_cdev,
>> + c->val);
>> + case V4L2_CID_FLASH_TORCH_INTENSITY:
>> + if (ctrl->led_mode->val == V4L2_FLASH_LED_MODE_TORCH) {
>> + torch_brightness =
>> + v4l2_flash_intensity_to_led_brightness(
>> + &led_cdev->brightness_ctrl,
>> + ctrl->torch_intensity->val);
>> + v4l2_call_flash_op(brightness_set, led_cdev,
>> + torch_brightness);
>
> I could be missing something but don't torch and indicator require similar
> handling?

Why? Torch units need conversion whereas indicator units don't.
Moreover they have different LED API.

>> + }
>> + 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 int v4l2_flash_init_controls(struct v4l2_flash *v4l2_flash)
>> +
>> +{
>> + struct led_classdev *led_cdev = v4l2_flash->led_cdev;
>> + struct led_flash *flash = led_cdev->flash;
>> + bool has_indicator = flash->indicator_brightness;
>> + struct v4l2_ctrl *ctrl;
>> + struct led_ctrl *ctrl_cfg;
>> + unsigned int mask;
>> + int ret, max, num_ctrls;
>> +
>> + num_ctrls = flash->has_flash_led ? 8 : 2;
>> + if (flash->fault_flags)
>> + ++num_ctrls;
>> + if (has_indicator)
>> + ++num_ctrls;
>> +
>> + v4l2_ctrl_handler_init(&v4l2_flash->hdl, num_ctrls);
>> +
>> + mask = 1 << V4L2_FLASH_LED_MODE_NONE |
>> + 1 << V4L2_FLASH_LED_MODE_TORCH;
>> + if (flash->has_flash_led)
>> + mask |= 1 << V4L2_FLASH_LED_MODE_FLASH;
>> +
>> + /* Configure FLASH_LED_MODE ctrl */
>> + v4l2_flash->ctrl.led_mode = v4l2_ctrl_new_std_menu(
>> + &v4l2_flash->hdl,
>> + &v4l2_flash_ctrl_ops, V4L2_CID_FLASH_LED_MODE,
>> + V4L2_FLASH_LED_MODE_TORCH, ~mask,
>> + V4L2_FLASH_LED_MODE_NONE);
>> +
>> + /* Configure TORCH_INTENSITY ctrl */
>> + ctrl_cfg = &led_cdev->brightness_ctrl;
>> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
>> + V4L2_CID_FLASH_TORCH_INTENSITY,
>> + ctrl_cfg->min, ctrl_cfg->max,
>> + ctrl_cfg->step, ctrl_cfg->val);
>> + if (ctrl)
>> + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
>> + v4l2_flash->ctrl.torch_intensity = ctrl;
>> +
>> + if (flash->has_flash_led) {
>> + /* Configure FLASH_STROBE_SOURCE ctrl */
>> + mask = 1 << V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
>> +
>> + if (flash->has_external_strobe) {
>> + mask |= 1 << V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
>> + max = V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
>> + } else {
>> + max = V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
>> + }
>> +
>> + v4l2_flash->ctrl.source = v4l2_ctrl_new_std_menu(
>> + &v4l2_flash->hdl,
>> + &v4l2_flash_ctrl_ops,
>> + V4L2_CID_FLASH_STROBE_SOURCE,
>> + max,
>> + ~mask,
>> + V4L2_FLASH_STROBE_SOURCE_SOFTWARE);
>> +
>> + /* Configure FLASH_STROBE ctrl */
>> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
>> + V4L2_CID_FLASH_STROBE, 0, 1, 1, 0);
>> +
>> + /* Configure FLASH_STROBE_STOP ctrl */
>> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
>> + V4L2_CID_FLASH_STROBE_STOP,
>> + 0, 1, 1, 0);
>> +
>> + /* Configure FLASH_STROBE_STATUS ctrl */
>> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
>> + V4L2_CID_FLASH_STROBE_STATUS,
>> + 0, 1, 1, 1);
>> + if (ctrl)
>> + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE |
>> + V4L2_CTRL_FLAG_READ_ONLY;
>> +
>> + /* Configure FLASH_TIMEOUT ctrl */
>> + ctrl_cfg = &flash->timeout;
>> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
>> + V4L2_CID_FLASH_TIMEOUT, ctrl_cfg->min,
>> + ctrl_cfg->max, ctrl_cfg->step,
>> + ctrl_cfg->val);
>> + if (ctrl)
>> + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
>> +
>> + /* Configure FLASH_INTENSITY ctrl */
>> + ctrl_cfg = &flash->brightness;
>> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
>> + V4L2_CID_FLASH_INTENSITY,
>> + ctrl_cfg->min, ctrl_cfg->max,
>> + ctrl_cfg->step, ctrl_cfg->val);
>> + if (ctrl)
>> + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
>> +
>> + if (flash->fault_flags) {
>> + /* Configure FLASH_FAULT ctrl */
>> + ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl,
>> + &v4l2_flash_ctrl_ops,
>> + V4L2_CID_FLASH_FAULT, 0,
>> + flash->fault_flags,
>> + 0, 0);
>> + if (ctrl)
>> + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE |
>> + V4L2_CTRL_FLAG_READ_ONLY;
>> + }
>> + if (has_indicator) {
>
> In theory it's possible to have an indicator without the flash. So I'd keep
> the two independent.

OK.

>> + /* Configure FLASH_INDICATOR_INTENSITY ctrl */
>> + ctrl_cfg = flash->indicator_brightness;
>> + ctrl = v4l2_ctrl_new_std(
>> + &v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
>> + V4L2_CID_FLASH_INDICATOR_INTENSITY,
>> + ctrl_cfg->min, ctrl_cfg->max,
>> + ctrl_cfg->step, ctrl_cfg->val);
>> + if (ctrl)
>> + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
>> + }
>> + }
>> +
>> + if (v4l2_flash->hdl.error) {
>> + ret = v4l2_flash->hdl.error;
>> + goto error_free;
>> + }
>> +
>> + ret = v4l2_ctrl_handler_setup(&v4l2_flash->hdl);
>> + if (ret < 0)
>> + goto error_free;
>> +
>> + v4l2_flash->subdev.ctrl_handler = &v4l2_flash->hdl;
>> +
>> + return 0;
>> +
>> +error_free:
>> + 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 *led_cdev = v4l2_flash->led_cdev;
>> +
>> + mutex_lock(&led_cdev->led_lock);
>> + v4l2_call_flash_op(sysfs_lock, led_cdev);
>
> Have you thought about device power management yet?

Having in mind that the V4L2 Flash sub-device is only a wrapper
for LED driver, shouldn't power management be left to the
drivers?

> Traditionally, the existing flash controller drivers are powered when a file
> handle is opened. Many newer implementations seem to consume so little power
> that it's not worthwhile to try to power them off.
>
> Binding the power state to open file handles isn't very generic either but
> it's very simple and works.
>
> V4L2 sub-devices do not use runtime PM as of yet, so perhaps this could be
> left for later.
>
>> + mutex_unlock(&led_cdev->led_lock);
>> +
>> + return 0;
>> +}
>> +
>> +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 *led_cdev = v4l2_flash->led_cdev;
>> +
>> + mutex_lock(&led_cdev->led_lock);
>> + v4l2_call_flash_op(sysfs_unlock, led_cdev);
>> + mutex_unlock(&led_cdev->led_lock);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_internal_ops v4l2_flash_subdev_internal_ops = {
>> + .open = v4l2_flash_open,
>> + .close = v4l2_flash_close,
>> +};
>> +
>> +static struct v4l2_subdev_ops v4l2_flash_subdev_ops = {
>> +};
>> +
>> +int v4l2_flash_init(struct led_classdev *led_cdev,
>> + const struct v4l2_flash_ops *ops)
>> +{
>> + struct v4l2_flash *flash = &led_cdev->flash->v4l2_flash;
>> + struct v4l2_subdev *sd = &flash->subdev;
>> + int ret;
>> +
>> + if (!led_cdev || !ops)
>> + return -EINVAL;
>> +
>> + flash->led_cdev = led_cdev;
>> + 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);
>
> It'd be nice to have the bus address in the name as well. But that would
> belong to the driver which calls v4l2_flash_init(). The existing flash
> controller drivers probably don't do this but many sensor drivers already
> do. The name is expected to be unique in the media device.

Will cover this.

>> + flash->ops = ops;
>> +
>> + ret = v4l2_flash_init_controls(flash);
>> + 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(sd);
>> + if (ret < 0)
>> + goto err_init_entity;
>> +
>> + return 0;
>> +
>> +err_init_entity:
>> + media_entity_cleanup(&sd->entity);
>> +err_init_controls:
>> + v4l2_ctrl_handler_free(sd->ctrl_handler);
>> + return -EINVAL;
>> +}
>> +EXPORT_SYMBOL_GPL(v4l2_flash_init);
>> +
>> +void v4l2_flash_release(struct led_classdev *led_cdev)
>> +{
>> + struct v4l2_flash *flash = &led_cdev->flash->v4l2_flash;
>> +
>> + v4l2_ctrl_handler_free(flash->subdev.ctrl_handler);
>> + v4l2_async_unregister_subdev(&flash->subdev);
>> + media_entity_cleanup(&flash->subdev.entity);
>> +}
>> +EXPORT_SYMBOL_GPL(v4l2_flash_release);
>> diff --git a/include/media/v4l2-flash.h b/include/media/v4l2-flash.h
>> new file mode 100644
>> index 0000000..fe16ddd
>> --- /dev/null
>> +++ b/include/media/v4l2-flash.h
>> @@ -0,0 +1,119 @@
>> +/*
>> + * 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>
>> +
>> +#define v4l2_call_flash_op(op, args...) \
>> + ((v4l2_flash)->ops->op(args)) \
>
> Extra backslash above.
>
> I don't think you should assume the caller will have a local variable called
> v4l2_flash. :-)

I assumed that as this macro is only for the module internal use
and aim at simplifying the calling code it can be implemented that way.
But when I look at it now it indeed doesn't seem reasonable.

>> +struct led_classdev;
>> +enum led_brightness;
>> +
>> +struct v4l2_flash_ops {
>> + void (*brightness_set)(struct led_classdev *led_cdev,
>> + enum led_brightness brightness);
>> + int (*brightness_update)(struct led_classdev *led_cdev);
>> + int (*flash_brightness_set)(struct led_classdev *led_cdev,
>> + u32 brightness);
>> + int (*flash_brightness_update)(struct led_classdev *led_cdev);
>> + int (*strobe_set)(struct led_classdev *led_cdev,
>> + bool state);
>> + int (*strobe_get)(struct led_classdev *led_cdev);
>> + int (*timeout_set)(struct led_classdev *led_cdev,
>> + u32 timeout);
>> + int (*indicator_brightness_set)(struct led_classdev *led_cdev,
>> + u32 brightness);
>> + int (*indicator_brightness_update)(struct led_classdev *led_cdev);
>> + int (*external_strobe_set)(struct led_classdev *led_cdev,
>> + bool enable);
>> + int (*fault_get)(struct led_classdev *led_cdev,
>> + u32 *fault);
>> + void (*sysfs_lock)(struct led_classdev *led_cdev);
>> + void (*sysfs_unlock)(struct led_classdev *led_cdev);
>> +};
>> +
>> +/**
>> + * struct v4l2_flash_ctrl - controls that define the sub-dev's state
>> + * @source: V4L2_CID_FLASH_STROBE_SOURCE control
>> + * @led_mode: V4L2_CID_FLASH_LED_MODE control
>> + * @torch_intensity: V4L2_CID_FLASH_TORCH_INTENSITY control
>> + */
>> +struct v4l2_flash_ctrl {
>> + struct v4l2_ctrl *source;
>> + struct v4l2_ctrl *led_mode;
>> + struct v4l2_ctrl *torch_intensity;
>> +};
>> +
>> +/**
>> + * struct v4l2_flash - Flash sub-device context
>> + * @led_cdev: LED class device controlled by this sub-device
>> + * @ops: LED class device ops
>> + * @subdev: V4L2 sub-device
>> + * @hdl: flash controls handler
>> + * @ctrl: state defining controls
>> + */
>> +struct v4l2_flash {
>> + struct led_classdev *led_cdev;
>> + const struct v4l2_flash_ops *ops;
>> +
>> + struct v4l2_subdev subdev;
>> + struct v4l2_ctrl_handler hdl;
>> + struct v4l2_flash_ctrl ctrl;
>> +};
>> +
>> +static inline struct v4l2_flash *v4l2_subdev_to_v4l2_flash(struct v4l2_subdev *sd)
>
> Over 80 characters per line.
>
>> +{
>> + return container_of(sd, struct v4l2_flash, subdev);
>> +}
>> +
>> +static inline struct v4l2_flash *v4l2_ctrl_to_v4l2_flash(struct v4l2_ctrl *c)
>> +{
>> + return container_of(c->handler, struct v4l2_flash, hdl);
>> +}
>> +
>> +#ifdef CONFIG_V4L2_FLASH
>> +/**
>> + * v4l2_flash_init - initialize V4L2 flash led sub-device
>> + * @led_cdev: the LED to create subdev upon
>> + * @ops: LED subsystem callbacks
>> + *
>> + * Create V4L2 subdev wrapping given LED subsystem device.
>> + */
>> +int v4l2_flash_init(struct led_classdev *led_cdev,
>> + const struct v4l2_flash_ops *ops);
>> +
>> +/**
>> + * v4l2_flash_release - release V4L2 flash led sub-device
>> + * @flash: a structure representing V4L2 flash led device
>> + *
>> + * Release V4L2 flash led subdev.
>> + */
>> +void v4l2_flash_release(struct led_classdev *led_cdev);
>> +#else
>> +static inline int v4l2_flash_init(struct led_classdev *led_cdev,
>> + const struct v4l2_flash_ops *ops)
>> +{
>> + return 0;
>> +}
>> +
>> +static inline void v4l2_flash_release(struct led_classdev *led_cdev)
>> +{
>> +}
>
> Could you put the two to the first patch? It won't compile otherwise.

Yeah. I didn't make test build after every patch :-/.

>> +#endif /* CONFIG_V4L2_FLASH */
>> +
>> +#endif /* _V4L2_FLASH_H */
>

2014-04-17 09:23:16

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 3/5] leds: Add support for max77693 mfd flash cell

Hi Sakari,

Thanks for the review.

On 04/16/2014 07:26 PM, Sakari Ailus wrote:
> Hi Jacek,
>
> Thanks for the patch! Comments below.
>
> On Fri, Apr 11, 2014 at 04:56:54PM +0200, 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 or through V4L2 subdevice when the support
>> for V4L2 Flash sub-devices is enabled. Device supports up to
>> two leds which can work in flash and torch mode. Leds can
>> be triggered externally or by software.
>>
>> Signed-off-by: Andrzej Hajda <[email protected]>
>> Signed-off-by: Jacek Anaszewski <[email protected]>
>> Acked-by: Kyungmin Park <[email protected]>
>> Cc: Bryan Wu <[email protected]>
>> Cc: Richard Purdie <[email protected]>
>> Cc: SangYoung Son <[email protected]>
>> Cc: Samuel Ortiz <[email protected]>
>> Cc: Lee Jones <[email protected]>
>> ---
>> drivers/leds/Kconfig | 10 +
>> drivers/leds/Makefile | 1 +
>> drivers/leds/leds-max77693.c | 794 ++++++++++++++++++++++++++++++++++++++++++
>> drivers/mfd/max77693.c | 2 +-
>> include/linux/mfd/max77693.h | 38 ++
>> 5 files changed, 844 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/leds/leds-max77693.c
>>
>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>> index 1e1c81f..b2152a6 100644
>> --- a/drivers/leds/Kconfig
>> +++ b/drivers/leds/Kconfig
>> @@ -462,6 +462,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 8861b86..64f6234 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..979736c
>> --- /dev/null
>> +++ b/drivers/leds/leds-max77693.c
>> @@ -0,0 +1,794 @@
>> +/*
>> + * Copyright (C) 2014, Samsung Electronics Co., Ltd.
>> + *
>> + * Authors: Andrzej Hajda <[email protected]>
>> + * 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 <asm/div64.h>
>> +#include <linux/leds_flash.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +#include <media/v4l2-flash.h>
>
> I guess this should be last in the list.
>
>> +#include <linux/workqueue.h>
>> +#include <linux/mfd/max77693.h>
>> +#include <linux/mfd/max77693-private.h>
>> +
>> +#define MAX77693_LED_NAME "max77693-flash"
>> +
>> +#define MAX77693_TORCH_IOUT_BITS 4
>> +
>> +#define MAX77693_TORCH_NO_TIMER 0x40
>> +#define MAX77693_FLASH_TIMER_LEVEL 0x80
>> +
>> +#define MAX77693_FLASH_EN_OFF 0
>> +#define MAX77693_FLASH_EN_FLASH 1
>> +#define MAX77693_FLASH_EN_TORCH 2
>> +#define MAX77693_FLASH_EN_ON 3
>> +
>> +#define MAX77693_FLASH_EN1_SHIFT 6
>> +#define MAX77693_FLASH_EN2_SHIFT 4
>> +#define MAX77693_TORCH_EN1_SHIFT 2
>> +#define MAX77693_TORCH_EN2_SHIFT 0
>> +
>> +#define MAX77693_FLASH_LOW_BATTERY_EN 0x80
>> +
>> +#define MAX77693_FLASH_BOOST_FIXED 0x04
>> +#define MAX77693_FLASH_BOOST_LEDNUM_2 0x80
>> +
>> +#define MAX77693_FLASH_TIMEOUT_MIN 62500
>> +#define MAX77693_FLASH_TIMEOUT_MAX 1000000
>> +#define MAX77693_FLASH_TIMEOUT_STEP 62500
>> +
>> +#define MAX77693_TORCH_TIMEOUT_MIN 262000
>> +#define MAX77693_TORCH_TIMEOUT_MAX 15728000
>> +
>> +#define MAX77693_FLASH_IOUT_MIN 15625
>> +#define MAX77693_FLASH_IOUT_MAX_1LED 1000000
>> +#define MAX77693_FLASH_IOUT_MAX_2LEDS 625000
>> +#define MAX77693_FLASH_IOUT_STEP 15625
>> +
>> +#define MAX77693_TORCH_IOUT_MIN 15625
>> +#define MAX77693_TORCH_IOUT_MAX 250000
>> +#define MAX77693_TORCH_IOUT_STEP 15625
>> +
>> +#define MAX77693_FLASH_VSYS_MIN 2400
>> +#define MAX77693_FLASH_VSYS_MAX 3400
>> +#define MAX77693_FLASH_VSYS_STEP 33
>> +
>> +#define MAX77693_FLASH_VOUT_MIN 3300
>> +#define MAX77693_FLASH_VOUT_MAX 5500
>> +#define MAX77693_FLASH_VOUT_STEP 25
>> +#define MAX77693_FLASH_VOUT_RMIN 0x0c
>> +
>> +#define MAX77693_LED_STATUS_FLASH_ON (1 << 3)
>> +#define MAX77693_LED_STATUS_TORCH_ON (1 << 2)
>> +
>> +#define MAX77693_LED_FLASH_INT_FLED2_OPEN (1 << 0)
>> +#define MAX77693_LED_FLASH_INT_FLED2_SHORT (1 << 1)
>> +#define MAX77693_LED_FLASH_INT_FLED1_OPEN (1 << 2)
>> +#define MAX77693_LED_FLASH_INT_FLED1_SHORT (1 << 3)
>> +#define MAX77693_LED_FLASH_INT_OVER_CURRENT (1 << 4)
>> +
>> +#define MAX77693_MODE_OFF 0
>> +#define MAX77693_MODE_FLASH (1 << 0)
>> +#define MAX77693_MODE_TORCH (1 << 1)
>> +#define MAX77693_MODE_FLASH_EXTERNAL (1 << 2)
>> +
>> +enum {
>> + FLASH1,
>> + FLASH2,
>> + TORCH1,
>> + TORCH2
>> +};
>> +
>> +enum {
>> + FLASH,
>> + TORCH
>> +};
>> +
>> +struct max77693_led {
>> + struct regmap *regmap;
>> + struct platform_device *pdev;
>> + struct max77693_led_platform_data *pdata;
>> + struct mutex lock;
>> +
>> + struct led_classdev ldev;
>> +
>> + unsigned int torch_brightness;
>> + struct work_struct work_brightness_set;
>> + unsigned int mode_flags;
>> +};
>> +
>> +static u8 max77693_led_iout_to_reg(u32 ua)
>> +{
>> + if (ua < MAX77693_FLASH_IOUT_MIN)
>> + ua = MAX77693_FLASH_IOUT_MIN;
>> + return (ua - MAX77693_FLASH_IOUT_MIN) / MAX77693_FLASH_IOUT_STEP;
>> +}
>> +
>> +static u8 max77693_flash_timeout_to_reg(u32 us)
>> +{
>> + return (us - MAX77693_FLASH_TIMEOUT_MIN) / MAX77693_FLASH_TIMEOUT_STEP;
>> +}
>> +
>> +static const u32 max77693_torch_timeouts[] = {
>> + 262000, 524000, 786000, 1048000,
>> + 1572000, 2096000, 2620000, 3144000,
>> + 4193000, 5242000, 6291000, 7340000,
>> + 9437000, 11534000, 13631000, 1572800
>> +};
>> +
>> +static u8 max77693_torch_timeout_to_reg(u32 us)
>> +{
>> + int i, b = 0, e = ARRAY_SIZE(max77693_torch_timeouts);
>
> I haven't run this, but it looks like it'll access max77693_torch_timeouts[]
> array after the last element if you pass it a value greater than 1572800.
> Shouldn't e be initialised to ARRAY_SIZE() - 1 instead?
>
>> +
>> + while (e - b > 1) {
>> + i = b + (e - b) / 2;
>> + if (us < max77693_torch_timeouts[i])
>> + e = i;
>> + else
>> + b = i;
>> + }
>> + return b;
>> +}

Let's track this case:

us = 1600000
e = 16 (ARRAY_SIZE(max77693_torch_timeouts))

1st iteration:
while (16 - 0 > 1) {
i = 0 + (16 - 0) / 2 //= 8
b = 8
}

2nd iteration:
while (16 - 8 > 1) {
i = 8 + (16 - 8) / 2 //= 12
b = 12
}

3rd iteration:
while (16 - 12 > 1) {
i = 12 + (16 - 12) / 2 //= 14
b = 14
}

4th iteration:
while (16 - 14 > 1) {
i = 14 + (16 - 14) / 2 //= 15
b = 15
}

5th iteration:
while (16 - 15 > 1) { //false
}

return b (15) - last element in the array


>> +static struct max77693_led *ldev_to_led(struct led_classdev *ldev)
>> +{
>> + return container_of(ldev, struct max77693_led, ldev);
>> +}
>> +
>> +static u32 max77693_torch_timeout_from_reg(u8 reg)
>> +{
>
> I might limit reg to ARRAY_SIZE(...) as well. Up to you.

It is already aligned during validation of platform data.

>> + return max77693_torch_timeouts[reg];
>> +}
>> +
>> +static u8 max77693_led_vsys_to_reg(u32 mv)
>> +{
>> + return ((mv - MAX77693_FLASH_VSYS_MIN) / MAX77693_FLASH_VSYS_STEP) << 2;
>> +}
>> +
>> +static u8 max77693_led_vout_to_reg(u32 mv)
>> +{
>> + return (mv - MAX77693_FLASH_VOUT_MIN) / MAX77693_FLASH_VOUT_STEP +
>> + MAX77693_FLASH_VOUT_RMIN;
>> +}
>> +
>> +/* split composite current @i into two @iout according to @imax weights */
>
> Are there dependencies between the two LEDs or are they entirely
> independent? If they're independent (with the possible exception of strobe),
> then I'd expose them individually.

They are independent.

>> +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 / MAX77693_FLASH_IOUT_STEP * MAX77693_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 & MAX77693_MODE_TORCH) {
>> + if (p->trigger[TORCH1] & MAX77693_LED_TRIG_SOFT)
>> + v |= MAX77693_FLASH_EN_ON << MAX77693_TORCH_EN1_SHIFT;
>> + if (p->trigger[TORCH2] & MAX77693_LED_TRIG_SOFT)
>> + v |= MAX77693_FLASH_EN_ON << MAX77693_TORCH_EN2_SHIFT;
>> + }
>> +
>> + if (mode & MAX77693_MODE_FLASH) {
>> + if (p->trigger[FLASH1] & MAX77693_LED_TRIG_SOFT)
>> + v |= MAX77693_FLASH_EN_ON << MAX77693_FLASH_EN1_SHIFT;
>> + if (p->trigger[FLASH2] & MAX77693_LED_TRIG_SOFT)
>> + v |= MAX77693_FLASH_EN_ON << MAX77693_FLASH_EN2_SHIFT;
>> + } else if (mode & MAX77693_MODE_FLASH_EXTERNAL) {
>> + if (p->trigger[FLASH1] & MAX77693_LED_TRIG_EXT)
>> + v |= MAX77693_FLASH_EN_FLASH << MAX77693_FLASH_EN1_SHIFT;
>> + if (p->trigger[FLASH2] & MAX77693_LED_TRIG_EXT)
>> + v |= MAX77693_FLASH_EN_FLASH << MAX77693_FLASH_EN2_SHIFT;
>> + /*
>> + * 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[TORCH1] & MAX77693_LED_TRIG_EXT)
>> + v |= MAX77693_FLASH_EN_TORCH << MAX77693_TORCH_EN1_SHIFT;
>> + if (p->trigger[TORCH2] & MAX77693_LED_TRIG_EXT)
>> + v |= MAX77693_FLASH_EN_TORCH << MAX77693_TORCH_EN2_SHIFT;
>> + }
>> +
>> + /* Reset the register only prior setting flash modes */
>> + if (mode != MAX77693_MODE_TORCH) {
>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_EN, 0);
>
> A single wrie for strobe. Looks good!
>
>> + if (ret < 0)
>> + return ret;
>> + }
>> +
>> + return max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_EN, v);
>> +}
>> +
>> +static int max77693_add_mode(struct max77693_led *led, unsigned int mode)
>> +{
>> + int ret;
>> +
>> + /* Once enabled torch mode is active until turned off */
>> + if ((mode == MAX77693_MODE_TORCH) &&
>> + (led->mode_flags & MAX77693_MODE_TORCH))
>> + return 0;
>> +
>> + /*
>> + * FLASH_EXTERNAL mode activates HW triggered flash and torch
>> + * modes in the device. The related register settings interfere
>> + * with SW triggerred modes, thus clear them to ensure proper
>> + * device configuration.
>> + */
>> + if (mode == MAX77693_MODE_FLASH_EXTERNAL)
>> + led->mode_flags &= (~MAX77693_MODE_TORCH &
>> + ~MAX77693_MODE_FLASH);
>> +
>> + 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
>> + * spurous flash strobing on each successive torch mode
>> + * setting.
>> + */
>> + if ((mode == MAX77693_MODE_FLASH) ||
>> + (mode == MAX77693_MODE_FLASH_EXTERNAL))
>> + led->mode_flags &= ~mode;
>> +
>> + return 0;
>> +}
>> +
>> +static int max77693_clear_mode(struct max77693_led *led, unsigned int mode)
>> +{
>> + led->mode_flags &= ~mode;
>> +
>> + return max77693_set_mode(led, led->mode_flags);
>> +}
>> +
>> +static int max77693_set_torch_current(struct max77693_led *led,
>> + u32 micro_amp)
>> +{
>> + struct max77693_led_platform_data *p = led->pdata;
>> + struct regmap *rmap = led->regmap;
>> + u32 iout[2];
>> + u8 v, iout1_reg, iout2_reg;
>> + int ret;
>> +
>> + max77693_calc_iout(iout, micro_amp, &p->iout[TORCH1]);
>> + iout1_reg = max77693_led_iout_to_reg(iout[0]);
>> + iout2_reg = max77693_led_iout_to_reg(iout[1]);
>> +
>> + v = iout1_reg | (iout2_reg << MAX77693_TORCH_IOUT_BITS);
>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCH, v);
>> + if (ret < 0)
>> + return ret;
>> +
>> + return 0;
>> +}
>> +
>> +static int max77693_set_flash_current(struct max77693_led *led,
>> + u32 micro_amp)
>> +{
>> + struct max77693_led_platform_data *p = led->pdata;
>> + struct regmap *rmap = led->regmap;
>> + u32 iout[2];
>> + u8 iout1_reg, iout2_reg;
>> + int ret;
>> +
>> + max77693_calc_iout(iout, micro_amp, &p->iout[FLASH1]);
>> + iout1_reg = max77693_led_iout_to_reg(iout[0]);
>> + iout2_reg = max77693_led_iout_to_reg(iout[1]);
>> +
>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH1, iout1_reg);
>> + if (ret < 0)
>> + goto error_ret;
>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH2, iout2_reg);
>> + if (ret < 0)
>> + goto error_ret;
>> +
>> +error_ret:
>> + 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;
>> +
>> + v = max77693_flash_timeout_to_reg(timeout);
>> +
>> + if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
>> + v |= MAX77693_FLASH_TIMER_LEVEL;
>> +
>> + return max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
>> +}
>> +
>> +static int max77693_strobe_status_get(struct max77693_led *led)
>> +{
>> + struct regmap *rmap = led->regmap;
>> + u8 v;
>> + int ret;
>> +
>> + ret = max77693_read_reg(rmap, MAX77693_LED_REG_FLASH_INT_STATUS, &v);
>> + if (ret < 0)
>> + return ret;
>> +
>> + return !!(v & MAX77693_LED_STATUS_FLASH_ON);
>> +}
>> +
>> +static int max77693_int_flag_get(struct max77693_led *led, u8 *v)
>> +{
>> + struct regmap *rmap = led->regmap;
>> +
>> + return max77693_read_reg(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 ret;
>> + u8 v;
>> +
>> + ret = max77693_set_torch_current(led, p->iout[TORCH1] +
>> + p->iout[TORCH2]);
>> + if (ret < 0)
>> + return ret;
>> +
>> + ret = max77693_set_flash_current(led, p->iout[FLASH1] +
>> + p->iout[FLASH2]);
>> + if (ret < 0)
>> + return ret;
>> +
>> + if (p->timeout[TORCH] > 0)
>> + v = max77693_torch_timeout_to_reg(p->timeout[TORCH]);
>> + else
>> + v = MAX77693_TORCH_NO_TIMER;
>> + if (p->trigger_type[TORCH] == MAX77693_LED_TRIG_TYPE_LEVEL)
>> + v |= MAX77693_FLASH_TIMER_LEVEL;
>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCHTIMER, v);
>> + if (ret < 0)
>> + return ret;
>> +
>> + v = max77693_flash_timeout_to_reg(p->timeout[FLASH]);
>> + if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
>> + v |= MAX77693_FLASH_TIMER_LEVEL;
>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
>> + if (ret < 0)
>> + return ret;
>> +
>> + if (p->low_vsys > 0)
>> + v = max77693_led_vsys_to_reg(p->low_vsys) |
>> + MAX77693_FLASH_LOW_BATTERY_EN;
>> + else
>> + v = 0;
>> +
>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_MAX_FLASH1, v);
>> + if (ret < 0)
>> + return ret;
>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_MAX_FLASH2, 0);
>> + if (ret < 0)
>> + return ret;
>> +
>> + if (p->boost_mode[FLASH1] == MAX77693_LED_BOOST_FIXED ||
>> + p->boost_mode[FLASH2] == MAX77693_LED_BOOST_FIXED)
>> + v = MAX77693_FLASH_BOOST_FIXED;
>> + else
>> + v = p->boost_mode[FLASH1] | (p->boost_mode[FLASH2] << 1);
>> + if (p->boost_mode[FLASH1] && p->boost_mode[FLASH2])
>> + v |= MAX77693_FLASH_BOOST_LEDNUM_2;
>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_VOUT_CNTL, v);
>> + if (ret < 0)
>> + return ret;
>> +
>> + v = max77693_led_vout_to_reg(p->boost_vout);
>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
>> + if (ret < 0)
>> + return ret;
>> +
>> + return max77693_set_mode(led, MAX77693_MODE_OFF);
>> +}
>> +
>> +/* LED subsystem callbacks */
>> +
>> +static void max77693_brightness_set_work(struct work_struct *work)
>> +{
>> + struct max77693_led *led =
>> + container_of(work, struct max77693_led, work_brightness_set);
>> + int ret;
>> +
>> + mutex_lock(&led->lock);
>> +
>> + if (led->torch_brightness == 0) {
>> + ret = max77693_clear_mode(led, MAX77693_MODE_TORCH);
>> + 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->torch_brightness *
>> + MAX77693_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, MAX77693_MODE_TORCH);
>> + if (ret < 0)
>> + dev_dbg(&led->pdev->dev, "Failed to set torch mode (%d)\n",
>> + ret);
>> +unlock:
>> + mutex_unlock(&led->lock);
>> +}
>> +
>> +static void max77693_led_brightness_set(struct led_classdev *led_cdev,
>> + enum led_brightness value)
>> +{
>> + struct max77693_led *led = ldev_to_led(led_cdev);
>> +
>> + led->torch_brightness = value;
>> + schedule_work(&led->work_brightness_set);
>
> Is there a reason not to do this right now (but in a work queue instead)?

Almost all the drivers in the LED subsystem do it that way.
I think that it is caused by the fact that setting led brightness
should be as fast as possible and non-blocking. The led may be
used e.g. for HD LED (see ledtrig-ide) and activated many times
per second, and thus it could have impact on the system performance
if it wasn't run in a work queue.

>> +}
>> +
>> +static int max77693_led_flash_strobe_get(struct led_classdev *led_cdev)
>> +{
>> + struct max77693_led *led = ldev_to_led(led_cdev);
>> + int ret;
>> +
>> + mutex_lock(&led->lock);
>> + ret = max77693_strobe_status_get(led);
>> + mutex_unlock(&led->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int max77693_led_flash_fault_get(struct led_classdev *led_cdev,
>> + u32 *fault)
>> +{
>> + struct max77693_led *led = ldev_to_led(led_cdev);
>> + u8 v;
>> + int ret;
>> +
>> + mutex_lock(&led->lock);
>> +
>> + ret = max77693_int_flag_get(led, &v);
>> + if (ret < 0)
>> + goto unlock;
>> +
>> + *fault = 0;
>> +
>> + if (v & MAX77693_LED_FLASH_INT_FLED2_OPEN ||
>> + v & MAX77693_LED_FLASH_INT_FLED1_OPEN)
>> + *fault |= LED_FAULT_OVER_VOLTAGE;
>> + if (v & MAX77693_LED_FLASH_INT_FLED2_SHORT ||
>> + v & MAX77693_LED_FLASH_INT_FLED1_SHORT)
>> + *fault |= LED_FAULT_SHORT_CIRCUIT;
>> + if (v & MAX77693_LED_FLASH_INT_OVER_CURRENT)
>> + *fault |= LED_FAULT_OVER_CURRENT;
>> +unlock:
>> + mutex_unlock(&led->lock);
>> + return ret;
>> +}
>> +
>> +static int max77693_led_flash_strobe_set(struct led_classdev *led_cdev,
>> + bool state)
>> +{
>> + struct max77693_led *led = ldev_to_led(led_cdev);
>> + struct led_flash *flash = led_cdev->flash;
>> + int ret;
>> +
>> + mutex_lock(&led->lock);
>> +
>> + if (flash->external_strobe) {
>> + ret = -EBUSY;
>> + goto unlock;
>> + }
>> +
>> + if (!state) {
>> + ret = max77693_clear_mode(led, MAX77693_MODE_FLASH);
>> + goto unlock;
>> + }
>> +
>> + ret = max77693_add_mode(led, MAX77693_MODE_FLASH);
>> + if (ret < 0)
>> + goto unlock;
>> +unlock:
>> + mutex_unlock(&led->lock);
>> + return ret;
>> +}
>> +
>> +static int max77693_led_external_strobe_set(struct led_classdev *led_cdev,
>> + bool enable)
>> +{
>> + struct max77693_led *led = ldev_to_led(led_cdev);
>> + int ret;
>> +
>> + mutex_lock(&led->lock);
>> +
>> + if (enable)
>> + ret = max77693_add_mode(led, MAX77693_MODE_FLASH_EXTERNAL);
>> + else
>> + ret = max77693_clear_mode(led, MAX77693_MODE_FLASH_EXTERNAL);
>> +
>> + mutex_unlock(&led->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int max77693_led_flash_brightness_set(struct led_classdev *led_cdev,
>> + u32 brightness)
>> +{
>> + struct max77693_led *led = ldev_to_led(led_cdev);
>> + int ret;
>> +
>> + mutex_lock(&led->lock);
>> +
>> + ret = max77693_set_flash_current(led, brightness);
>> + if (ret < 0)
>> + goto unlock;
>> +unlock:
>> + mutex_unlock(&led->lock);
>> + return ret;
>> +}
>> +
>> +static int max77693_led_flash_timeout_set(struct led_classdev *led_cdev,
>> + u32 timeout)
>> +{
>> + struct max77693_led *led = ldev_to_led(led_cdev);
>> + int ret;
>> +
>> + mutex_lock(&led->lock);
>> +
>> + ret = max77693_set_timeout(led, timeout);
>> + if (ret < 0)
>> + goto unlock;
>> +
>> +unlock:
>> + mutex_unlock(&led->lock);
>> + return ret;
>> +}
>> +
>> +static void max77693_led_parse_dt(struct max77693_led_platform_data *p,
>> + struct device_node *node)
>> +{
>> + of_property_read_u32_array(node, "maxim,iout", p->iout, 4);
>
> How about separate current for flash and torch modes? They are the same
> LEDs; just the mode is different.

There are separate currents - 2 for torch and 2 for flash mode.

>> + of_property_read_u32_array(node, "maxim,trigger", p->trigger, 4);
>> + of_property_read_u32_array(node, "maxim,trigger-type", p->trigger_type,
>> + 2);
>> + of_property_read_u32_array(node, "maxim,timeout", p->timeout, 2);
>> + of_property_read_u32_array(node, "maxim,boost-mode", p->boost_mode, 2);
>> + of_property_read_u32(node, "maxim,boost-vout", &p->boost_vout);
>> + of_property_read_u32(node, "maxim,vsys-min", &p->low_vsys);
>
> Are these values specific to the maxim chip? I'd suppose e.g. timeout and
> iout are something that can be found pretty much in any flash controller.

Besides the two they are specific. And what with timeout and iout
if they are common for all flash controllers?

>> +}
>> +
>> +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;
>> +
>> + for (i = 0; i < 2; ++i)
>
> How about using ARRAY_SIZE() here, too?

OK.

>
>> + clamp_align(&p->boost_mode[i], MAX77693_LED_BOOST_NONE,
>> + MAX77693_LED_BOOST_FIXED, 1);
>> + /* boost, if enabled, should be the same on both leds */
>> + if (p->boost_mode[0] != MAX77693_LED_BOOST_NONE &&
>> + p->boost_mode[1] != MAX77693_LED_BOOST_NONE)
>> + p->boost_mode[1] = p->boost_mode[0];
>> +
>> + max = (p->boost_mode[FLASH1] && p->boost_mode[FLASH2]) ?
>> + MAX77693_FLASH_IOUT_MAX_2LEDS : MAX77693_FLASH_IOUT_MAX_1LED;
>> +
>> + clamp_align(&p->iout[FLASH1], MAX77693_FLASH_IOUT_MIN,
>> + max, MAX77693_FLASH_IOUT_STEP);
>> + clamp_align(&p->iout[FLASH2], MAX77693_FLASH_IOUT_MIN,
>> + max, MAX77693_FLASH_IOUT_STEP);
>> + clamp_align(&p->iout[TORCH1], MAX77693_TORCH_IOUT_MIN,
>> + MAX77693_TORCH_IOUT_MAX, MAX77693_TORCH_IOUT_STEP);
>> + clamp_align(&p->iout[TORCH2], MAX77693_TORCH_IOUT_MIN,
>> + MAX77693_TORCH_IOUT_MAX, MAX77693_TORCH_IOUT_STEP);
>> +
>> + for (i = 0; i < 4; ++i)
>> + clamp_align(&p->trigger[i], 0, 7, 1);
>> + for (i = 0; i < 2; ++i)
>> + clamp_align(&p->trigger_type[i], MAX77693_LED_TRIG_TYPE_EDGE,
>> + MAX77693_LED_TRIG_TYPE_LEVEL, 1);
>
> ARRAY_SIZE() would be nicer than using numeric values for the loop
> condition.
>
>> + clamp_align(&p->timeout[FLASH], MAX77693_FLASH_TIMEOUT_MIN,
>> + MAX77693_FLASH_TIMEOUT_MAX, MAX77693_FLASH_TIMEOUT_STEP);
>> +
>> + if (p->timeout[TORCH]) {
>> + clamp_align(&p->timeout[TORCH], MAX77693_TORCH_TIMEOUT_MIN,
>> + MAX77693_TORCH_TIMEOUT_MAX, 1);
>> + p->timeout[TORCH] = max77693_torch_timeout_from_reg(
>> + max77693_torch_timeout_to_reg(p->timeout[TORCH]));
>> + }
>> +
>> + clamp_align(&p->boost_vout, MAX77693_FLASH_VOUT_MIN,
>> + MAX77693_FLASH_VOUT_MAX, MAX77693_FLASH_VOUT_STEP);
>> +
>> + if (p->low_vsys) {
>> + clamp_align(&p->low_vsys, MAX77693_FLASH_VSYS_MIN,
>> + MAX77693_FLASH_VSYS_MAX, MAX77693_FLASH_VSYS_STEP);
>> + }
>> +}
>> +
>> +static int max77693_led_get_platform_data(struct max77693_led *led)
>> +{
>> + struct max77693_led_platform_data *p;
>> + struct device *dev = &led->pdev->dev;
>> +
>> + if (dev->of_node) {
>> + p = devm_kzalloc(dev, sizeof(*led->pdata), GFP_KERNEL);
>> + if (!p)
>> + return -ENOMEM;
>
> Check for p can be moved out of the if as it's the same for both.
>
> You could also use led->pdata directly. Up to you.
>
>> + max77693_led_parse_dt(p, dev->of_node);
>> + } else {
>> + p = dev_get_platdata(dev);
>> + if (!p)
>> + return -ENODEV;
>> + }
>> + led->pdata = p;
>> +
>> + max77693_led_validate_platform_data(p);
>> +
>> + return 0;
>> +}
>> +
>> +static struct led_flash led_flash = {
>> + .ops = {
>> + .brightness_set = max77693_led_flash_brightness_set,
>> + .strobe_set = max77693_led_flash_strobe_set,
>> + .strobe_get = max77693_led_flash_strobe_get,
>> + .timeout_set = max77693_led_flash_timeout_set,
>> + .external_strobe_set = max77693_led_external_strobe_set,
>> + .fault_get = max77693_led_flash_fault_get,
>> + },
>> + .has_flash_led = true,
>> +};
>> +
>> +static void max77693_init_led_controls(struct led_classdev *led_cdev,
>> + struct max77693_led_platform_data *p)
>> +{
>> + struct led_flash *flash = led_cdev->flash;
>> + struct led_ctrl *c;
>> +
>> + /*
>> + * brightness_ctrl and fault_flags are used only
>> + * for initializing related V4L2 controls.
>> + */
>> +#ifdef CONFIG_V4L2_FLASH
>> + flash->fault_flags = V4L2_FLASH_FAULT_OVER_VOLTAGE |
>> + V4L2_FLASH_FAULT_SHORT_CIRCUIT |
>> + V4L2_FLASH_FAULT_OVER_CURRENT;
>> +
>> + c = &led_cdev->brightness_ctrl;
>> + c->min = (p->iout[TORCH1] != 0 && p->iout[TORCH2] != 0) ?
>> + MAX77693_TORCH_IOUT_MIN * 2 :
>> + MAX77693_TORCH_IOUT_MIN;
>> + c->max = p->iout[TORCH1] + p->iout[TORCH2];
>> + c->step = MAX77693_TORCH_IOUT_STEP;
>> + c->val = p->iout[TORCH1] + p->iout[TORCH2];
>
> Can you control the current for the two flash LEDs separately?

Yes.

> If yes, this
> should be also available on the V4L2 flash API. The lm3560 driver does this,
> for example. (It creates two sub-devices since we can only control a single
> LED using a single sub-device, at least for the time being.)

So, should I propose new V4L2 flash API for controlling more than
one led? Probably similar improvement should be applied to the
LED subsystem.

>> +#endif
>> +
>> + c = &flash->brightness;
>> + c->min = (p->iout[FLASH1] != 0 && p->iout[FLASH2] != 0) ?
>> + MAX77693_FLASH_IOUT_MIN * 2 :
>> + MAX77693_FLASH_IOUT_MIN;
>> + c->max = p->iout[FLASH1] + p->iout[FLASH2];
>> + c->step = MAX77693_FLASH_IOUT_STEP;
>> + c->val = p->iout[FLASH1] + p->iout[FLASH2];
>> +
>> + c = &flash->timeout;
>> + c->min = MAX77693_FLASH_TIMEOUT_MIN;
>> + c->max = MAX77693_FLASH_TIMEOUT_MAX;
>> + c->step = MAX77693_FLASH_TIMEOUT_STEP;
>> + c->val = p->timeout[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 led_classdev *led_cdev;
>> + int ret;
>> +
>> + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
>> + if (!led)
>> + return -ENOMEM;
>> +
>> + led->pdev = pdev;
>> + led_cdev = &led->ldev;
>> + led->regmap = iodev->regmap;
>> + platform_set_drvdata(pdev, led);
>> + ret = max77693_led_get_platform_data(led);
>> + if (ret < 0)
>> + return -EINVAL;
>> + p = led->pdata;
>> +
>> + mutex_init(&led->lock);
>> +
>> + INIT_WORK(&led->work_brightness_set, max77693_brightness_set_work);
>> +
>> + /* LED class device initialization */
>> + led_cdev->name = MAX77693_LED_NAME;
>> + led_cdev->brightness_set = max77693_led_brightness_set;
>> + led_cdev->max_brightness = (p->iout[TORCH1] + p->iout[TORCH2]) /
>> + MAX77693_TORCH_IOUT_STEP;
>> +
>> + if ((p->trigger[FLASH1] & MAX77693_LED_TRIG_FLASH) ||
>> + (p->trigger[FLASH2] & MAX77693_LED_TRIG_FLASH))
>> + led_flash.has_external_strobe = true;
>> + led_cdev->flash = &led_flash;
>> +
>> + max77693_init_led_controls(led_cdev, p);
>> +
>> + /* Register in the LED subsystem. */
>> + ret = led_classdev_flash_register(&pdev->dev, led_cdev);
>> + if (ret < 0)
>
> mutex_destroy()?
>
>> + return -EINVAL;
>> +
>> + ret = max77693_setup(led);
>
> Do you intentionally ignore the return value?

No, I just forgot about it :-)

>> + return 0;
>> +}
>> +
>> +static int max77693_led_remove(struct platform_device *pdev)
>> +{
>> + struct max77693_led *led = platform_get_drvdata(pdev);
>> +
>> + led_classdev_flash_unregister(&led->ldev);
>
> mutex_destroy()?

Sure.

>> + return 0;
>> +}
>> +
>> +static struct of_device_id max77693_led_dt_match[] = {
>> + {.compatible = "maxim,max77693-flash"},
>> + {},
>> +};
>> +
>> +static struct platform_driver max77693_led_driver = {
>> + .probe = max77693_led_probe,
>> + .remove = max77693_led_remove,
>> + .driver = {
>> + .name = "max77693-flash",
>> + .owner = THIS_MODULE,
>> + .of_match_table = max77693_led_dt_match,
>> + },
>> +};
>> +
>> +module_platform_driver(max77693_led_driver);
>> +
>> +MODULE_AUTHOR("Andrzej Hajda <[email protected]>");
>> +MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
>> +MODULE_DESCRIPTION("Maxim MAX77693 led flash driver");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/mfd/max77693.c b/drivers/mfd/max77693.c
>> index c5535f0..f061aa8 100644
>> --- a/drivers/mfd/max77693.c
>> +++ b/drivers/mfd/max77693.c
>> @@ -44,7 +44,7 @@
>> static const struct mfd_cell max77693_devs[] = {
>> { .name = "max77693-pmic", },
>> { .name = "max77693-charger", },
>> - { .name = "max77693-flash", },
>> + { .name = "max77693-flash", .of_compatible = "maxim,max77693-flash", },
>> { .name = "max77693-muic", },
>> { .name = "max77693-haptic", },
>> };
>> diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h
>> index 3f3dc45..f2285b7 100644
>> --- a/include/linux/mfd/max77693.h
>> +++ b/include/linux/mfd/max77693.h
>> @@ -63,6 +63,43 @@ struct max77693_muic_platform_data {
>> int path_uart;
>> };
>>
>> +/* MAX77693 led flash */
>> +
>> +/* triggers */
>> +enum max77693_led_trigger {
>> + MAX77693_LED_TRIG_OFF,
>> + MAX77693_LED_TRIG_FLASH,
>> + MAX77693_LED_TRIG_TORCH,
>> + MAX77693_LED_TRIG_EXT,
>> + MAX77693_LED_TRIG_SOFT,
>> +};
>> +
>> +
>> +/* trigger types */
>> +enum max77693_led_trigger_type {
>> + MAX77693_LED_TRIG_TYPE_EDGE,
>> + MAX77693_LED_TRIG_TYPE_LEVEL,
>> +};
>> +
>> +/* boost modes */
>> +enum max77693_led_boost_mode {
>> + MAX77693_LED_BOOST_NONE,
>> + MAX77693_LED_BOOST_ADAPTIVE,
>> + MAX77693_LED_BOOST_FIXED,
>> +};
>> +
>> +struct max77693_led_platform_data {
>> + u32 iout[4];
>> + u32 trigger[4];
>> + u32 trigger_type[2];
>> + u32 timeout[2];
>> + u32 boost_mode[2];
>> + u32 boost_vout;
>> + u32 low_vsys;
>> +};
>> +
>> +/* MAX77693 */
>> +
>> struct max77693_platform_data {
>> /* regulator data */
>> struct max77693_regulator_data *regulators;
>> @@ -70,5 +107,6 @@ struct max77693_platform_data {
>>
>> /* muic data */
>> struct max77693_muic_platform_data *muic_data;
>> + struct max77693_led_platform_data *led_data;
>> };
>> #endif /* __LINUX_MFD_MAX77693_H */
>

2014-04-18 17:55:01

by Bryan Wu

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 0/5] LED / flash API integration

On Fri, Apr 11, 2014 at 7:56 AM, Jacek Anaszewski
<[email protected]> wrote:
> This is is the third version of the patch series being a follow up
> of the discussion on Media summit 2013-10-23, related to the
> LED / flash API integration (the notes from the discussion were
> enclosed in the message [1], paragraph 5).
> The series is based on linux-next next-20140328
>
> Description of the proposed modifications according to
> the kernel components they are relevant to:
> - LED subsystem modifications
> * added led_flash module which, when enabled in the config,
> registers flash specific sysfs attributes:
> - flash_brightness
> - max_flash_brightness
> - indicator_brightness
> - max_indicator_brightness
> - flash_timeout
> - max_flash_timeout
> - flash_strobe
> - flash_fault
> - external_strobe
> and exposes kernel internal API
> - led_set_flash_strobe
> - led_get_flash_strobe
> - led_set_indicator_brightness
> - led_update_indicator_brightness
> - led_set_flash_timeout
> - led_get_flash_fault
> - led_set_external_strobe
> - led_sysfs_lock
> - led_sysfs_unlock
> - Addition of a V4L2 Flash sub-device registration helpers
> * added v4l2-flash.c and v4l2-flash.h files with helper
> functions that facilitate registration/unregistration
> of a subdevice, which wrapps a LED subsystem device and
> exposes V4L2 Flash control interface
> - Addition of a driver for the flash cell of the MAX77693 mfd
> * the driver exploits the newly introduced mechanism
> - Update of the max77693.txt DT bindings documentation
>
> ================
> Changes since v2
> ================
>
> - refactored the code so that it is possible to build
> led-core without led-flash module
> - added v4l2-flash ops which slackens dependency from
> the led-flash module
> - implemented led_clamp_align_val function and led_ctrl
> structure which allows to align led control values
> in the manner compatible with V4L2 Flash controls;
> the flash brightness and timeout units have been defined
> as microamperes and microseconds respectively to properly
> support devices which define current and time levels
> as fractions of 1/1000.
> - added support for the flash privacy leds
> - modified LED sysfs locking mechanism - now it locks/unlocks
> the interface on V4L2 Flash sub-device file open/close
> - changed hw_triggered attribute name to external_strobe,
> which maps on the V4L2_FLASH_STROBE_SOURCE_EXTERNAL name
> more intuitively
> - made external_strobe and indicator related sysfs attributes
> created optionally only if related features are declared
> by the led device driver
> - removed from the series patches modifying exynos4-is media
> controller - a proposal for "flash manager" which will take
> care of flash devices registration is due to be submitted
> - removed modifications to the LED class devices documentation,
> it will be covered after the whole functionality is accepted
>

Thanks a lot for pushing this, I'm trapping in some other urgent tasks
and will review it soon when I'm free.

-Bryan


> Thanks,
> Jacek Anaszewski
>
> [1] http://www.spinics.net/lists/linux-media/msg69253.html
>
> Jacek Anaszewski (5):
> leds: Add sysfs and kernel internal API for flash LEDs
> leds: Improve and export led_update_brightness function
> leds: Add support for max77693 mfd flash cell
> DT: Add documentation for the mfd Maxim max77693 flash cell
> media: Add registration helpers for V4L2 flash sub-devices
>
> Documentation/devicetree/bindings/mfd/max77693.txt | 57 ++
> drivers/leds/Kconfig | 18 +
> drivers/leds/Makefile | 2 +
> drivers/leds/led-class.c | 42 +-
> drivers/leds/led-core.c | 16 +
> drivers/leds/led-flash.c | 627 ++++++++++++++++
> drivers/leds/led-triggers.c | 16 +-
> drivers/leds/leds-max77693.c | 794 ++++++++++++++++++++
> drivers/leds/leds.h | 6 +
> drivers/media/v4l2-core/Kconfig | 10 +
> drivers/media/v4l2-core/Makefile | 2 +
> drivers/media/v4l2-core/v4l2-flash.c | 393 ++++++++++
> drivers/mfd/max77693.c | 2 +-
> include/linux/leds.h | 60 +-
> include/linux/leds_flash.h | 252 +++++++
> include/linux/mfd/max77693.h | 38 +
> include/media/v4l2-flash.h | 119 +++
> 17 files changed, 2433 insertions(+), 21 deletions(-)
> create mode 100644 drivers/leds/led-flash.c
> create mode 100644 drivers/leds/leds-max77693.c
> create mode 100644 drivers/media/v4l2-core/v4l2-flash.c
> create mode 100644 include/linux/leds_flash.h
> create mode 100644 include/media/v4l2-flash.h
>
> --
> 1.7.9.5
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-leds" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html

2014-04-23 15:25:18

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 5/5] media: Add registration helpers for V4L2 flash sub-devices

Hi Jacek,

On Thu, Apr 17, 2014 at 10:26:44AM +0200, Jacek Anaszewski wrote:
> Hi Sakari,
>
> Thanks for the review.
>
> On 04/16/2014 08:21 PM, Sakari Ailus wrote:
> >Hi Jacek,
> >
> >Thanks for the update!
> >
> [...]
> >>+static inline enum led_brightness v4l2_flash_intensity_to_led_brightness(
> >>+ struct led_ctrl *config,
> >>+ u32 intensity)
> >
> >Fits on a single line.
> >
> >>+{
> >>+ return intensity / config->step;
> >
> >Shouldn't you first decrement the minimum before the division?
>
> Brightness level 0 means that led is off. Let's consider following case:
>
> intensity - 15625
> config->step - 15625
> intensity / config->step = 1 (the lowest possible current level)

In V4L2 controls the minimum is not off, and zero might not be a possible
value since minimum isn't divisible by step.

I wonder how to best take that into account.

> >>+}
> >>+
> >>+static inline u32 v4l2_flash_led_brightness_to_intensity(
> >>+ struct led_ctrl *config,
> >>+ enum led_brightness brightness)
> >>+{
> >>+ return brightness * config->step;
> >
> >And do the opposite here?

..

> >>+ return -EINVAL;
> >>+ return v4l2_call_flash_op(strobe_set, led_cdev, true);
> >>+ case V4L2_CID_FLASH_STROBE_STOP:
> >>+ return v4l2_call_flash_op(strobe_set, led_cdev, false);
> >>+ case V4L2_CID_FLASH_TIMEOUT:
> >>+ ret = v4l2_call_flash_op(timeout_set, led_cdev, c->val);
> >>+ case V4L2_CID_FLASH_INTENSITY:
> >>+ /* no conversion is needed */
> >>+ return v4l2_call_flash_op(flash_brightness_set, led_cdev,
> >>+ c->val);
> >>+ case V4L2_CID_FLASH_INDICATOR_INTENSITY:
> >>+ /* no conversion is needed */
> >>+ return v4l2_call_flash_op(indicator_brightness_set, led_cdev,
> >>+ c->val);
> >>+ case V4L2_CID_FLASH_TORCH_INTENSITY:
> >>+ if (ctrl->led_mode->val == V4L2_FLASH_LED_MODE_TORCH) {
> >>+ torch_brightness =
> >>+ v4l2_flash_intensity_to_led_brightness(
> >>+ &led_cdev->brightness_ctrl,
> >>+ ctrl->torch_intensity->val);
> >>+ v4l2_call_flash_op(brightness_set, led_cdev,
> >>+ torch_brightness);
> >
> >I could be missing something but don't torch and indicator require similar
> >handling?
>
> Why? Torch units need conversion whereas indicator units don't.
> Moreover they have different LED API.

I missed it was already in micro-Amps.

> >>+ }
> >>+ 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 int v4l2_flash_init_controls(struct v4l2_flash *v4l2_flash)
> >>+
> >>+{
> >>+ struct led_classdev *led_cdev = v4l2_flash->led_cdev;
> >>+ struct led_flash *flash = led_cdev->flash;
> >>+ bool has_indicator = flash->indicator_brightness;
> >>+ struct v4l2_ctrl *ctrl;
> >>+ struct led_ctrl *ctrl_cfg;
> >>+ unsigned int mask;
> >>+ int ret, max, num_ctrls;
> >>+
> >>+ num_ctrls = flash->has_flash_led ? 8 : 2;
> >>+ if (flash->fault_flags)
> >>+ ++num_ctrls;
> >>+ if (has_indicator)
> >>+ ++num_ctrls;
> >>+
> >>+ v4l2_ctrl_handler_init(&v4l2_flash->hdl, num_ctrls);
> >>+
> >>+ mask = 1 << V4L2_FLASH_LED_MODE_NONE |
> >>+ 1 << V4L2_FLASH_LED_MODE_TORCH;
> >>+ if (flash->has_flash_led)
> >>+ mask |= 1 << V4L2_FLASH_LED_MODE_FLASH;
> >>+
> >>+ /* Configure FLASH_LED_MODE ctrl */
> >>+ v4l2_flash->ctrl.led_mode = v4l2_ctrl_new_std_menu(
> >>+ &v4l2_flash->hdl,
> >>+ &v4l2_flash_ctrl_ops, V4L2_CID_FLASH_LED_MODE,
> >>+ V4L2_FLASH_LED_MODE_TORCH, ~mask,
> >>+ V4L2_FLASH_LED_MODE_NONE);
> >>+
> >>+ /* Configure TORCH_INTENSITY ctrl */
> >>+ ctrl_cfg = &led_cdev->brightness_ctrl;
> >>+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> >>+ V4L2_CID_FLASH_TORCH_INTENSITY,
> >>+ ctrl_cfg->min, ctrl_cfg->max,
> >>+ ctrl_cfg->step, ctrl_cfg->val);
> >>+ if (ctrl)
> >>+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
> >>+ v4l2_flash->ctrl.torch_intensity = ctrl;
> >>+
> >>+ if (flash->has_flash_led) {
> >>+ /* Configure FLASH_STROBE_SOURCE ctrl */
> >>+ mask = 1 << V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
> >>+
> >>+ if (flash->has_external_strobe) {
> >>+ mask |= 1 << V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
> >>+ max = V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
> >>+ } else {
> >>+ max = V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
> >>+ }
> >>+
> >>+ v4l2_flash->ctrl.source = v4l2_ctrl_new_std_menu(
> >>+ &v4l2_flash->hdl,
> >>+ &v4l2_flash_ctrl_ops,
> >>+ V4L2_CID_FLASH_STROBE_SOURCE,
> >>+ max,
> >>+ ~mask,
> >>+ V4L2_FLASH_STROBE_SOURCE_SOFTWARE);
> >>+
> >>+ /* Configure FLASH_STROBE ctrl */
> >>+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> >>+ V4L2_CID_FLASH_STROBE, 0, 1, 1, 0);
> >>+
> >>+ /* Configure FLASH_STROBE_STOP ctrl */
> >>+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> >>+ V4L2_CID_FLASH_STROBE_STOP,
> >>+ 0, 1, 1, 0);
> >>+
> >>+ /* Configure FLASH_STROBE_STATUS ctrl */
> >>+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> >>+ V4L2_CID_FLASH_STROBE_STATUS,
> >>+ 0, 1, 1, 1);
> >>+ if (ctrl)
> >>+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE |
> >>+ V4L2_CTRL_FLAG_READ_ONLY;
> >>+
> >>+ /* Configure FLASH_TIMEOUT ctrl */
> >>+ ctrl_cfg = &flash->timeout;
> >>+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> >>+ V4L2_CID_FLASH_TIMEOUT, ctrl_cfg->min,
> >>+ ctrl_cfg->max, ctrl_cfg->step,
> >>+ ctrl_cfg->val);
> >>+ if (ctrl)
> >>+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
> >>+
> >>+ /* Configure FLASH_INTENSITY ctrl */
> >>+ ctrl_cfg = &flash->brightness;
> >>+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> >>+ V4L2_CID_FLASH_INTENSITY,
> >>+ ctrl_cfg->min, ctrl_cfg->max,
> >>+ ctrl_cfg->step, ctrl_cfg->val);
> >>+ if (ctrl)
> >>+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
> >>+
> >>+ if (flash->fault_flags) {
> >>+ /* Configure FLASH_FAULT ctrl */
> >>+ ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl,
> >>+ &v4l2_flash_ctrl_ops,
> >>+ V4L2_CID_FLASH_FAULT, 0,
> >>+ flash->fault_flags,
> >>+ 0, 0);
> >>+ if (ctrl)
> >>+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE |
> >>+ V4L2_CTRL_FLAG_READ_ONLY;
> >>+ }
> >>+ if (has_indicator) {
> >
> >In theory it's possible to have an indicator without the flash. So I'd keep
> >the two independent.
>
> OK.
>
> >>+ /* Configure FLASH_INDICATOR_INTENSITY ctrl */
> >>+ ctrl_cfg = flash->indicator_brightness;
> >>+ ctrl = v4l2_ctrl_new_std(
> >>+ &v4l2_flash->hdl, &v4l2_flash_ctrl_ops,
> >>+ V4L2_CID_FLASH_INDICATOR_INTENSITY,
> >>+ ctrl_cfg->min, ctrl_cfg->max,
> >>+ ctrl_cfg->step, ctrl_cfg->val);
> >>+ if (ctrl)
> >>+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
> >>+ }
> >>+ }
> >>+
> >>+ if (v4l2_flash->hdl.error) {
> >>+ ret = v4l2_flash->hdl.error;
> >>+ goto error_free;
> >>+ }
> >>+
> >>+ ret = v4l2_ctrl_handler_setup(&v4l2_flash->hdl);
> >>+ if (ret < 0)
> >>+ goto error_free;
> >>+
> >>+ v4l2_flash->subdev.ctrl_handler = &v4l2_flash->hdl;
> >>+
> >>+ return 0;
> >>+
> >>+error_free:
> >>+ 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 *led_cdev = v4l2_flash->led_cdev;
> >>+
> >>+ mutex_lock(&led_cdev->led_lock);
> >>+ v4l2_call_flash_op(sysfs_lock, led_cdev);
> >
> >Have you thought about device power management yet?
>
> Having in mind that the V4L2 Flash sub-device is only a wrapper
> for LED driver, shouldn't power management be left to the
> drivers?

How does the LED controller driver know it needs to power the device up in
that case?

I think an s_power() op which uses PM runtime to set the power state until
V4L2 sub-device switches to it should be enough. But I'm fine leaving it out
from this patchset.

--
Kind regards,

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

2014-04-23 15:52:46

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 3/5] leds: Add support for max77693 mfd flash cell

Hi Jacek,

Thanks for the answers to my comments! :-)

On Thu, Apr 17, 2014 at 11:23:06AM +0200, Jacek Anaszewski wrote:
> On 04/16/2014 07:26 PM, Sakari Ailus wrote:
> >Hi Jacek,
> >
> >Thanks for the patch! Comments below.
> >
> >On Fri, Apr 11, 2014 at 04:56:54PM +0200, 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 or through V4L2 subdevice when the support
> >>for V4L2 Flash sub-devices is enabled. Device supports up to
> >>two leds which can work in flash and torch mode. Leds can
> >>be triggered externally or by software.
> >>
> >>Signed-off-by: Andrzej Hajda <[email protected]>
> >>Signed-off-by: Jacek Anaszewski <[email protected]>
> >>Acked-by: Kyungmin Park <[email protected]>
> >>Cc: Bryan Wu <[email protected]>
> >>Cc: Richard Purdie <[email protected]>
> >>Cc: SangYoung Son <[email protected]>
> >>Cc: Samuel Ortiz <[email protected]>
> >>Cc: Lee Jones <[email protected]>
> >>---
> >> drivers/leds/Kconfig | 10 +
> >> drivers/leds/Makefile | 1 +
> >> drivers/leds/leds-max77693.c | 794 ++++++++++++++++++++++++++++++++++++++++++
> >> drivers/mfd/max77693.c | 2 +-
> >> include/linux/mfd/max77693.h | 38 ++
> >> 5 files changed, 844 insertions(+), 1 deletion(-)
> >> create mode 100644 drivers/leds/leds-max77693.c
> >>
> >>diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> >>index 1e1c81f..b2152a6 100644
> >>--- a/drivers/leds/Kconfig
> >>+++ b/drivers/leds/Kconfig
> >>@@ -462,6 +462,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 8861b86..64f6234 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..979736c
> >>--- /dev/null
> >>+++ b/drivers/leds/leds-max77693.c
> >>@@ -0,0 +1,794 @@
> >>+/*
> >>+ * Copyright (C) 2014, Samsung Electronics Co., Ltd.
> >>+ *
> >>+ * Authors: Andrzej Hajda <[email protected]>
> >>+ * 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 <asm/div64.h>
> >>+#include <linux/leds_flash.h>
> >>+#include <linux/module.h>
> >>+#include <linux/mutex.h>
> >>+#include <linux/platform_device.h>
> >>+#include <linux/slab.h>
> >>+#include <media/v4l2-flash.h>
> >
> >I guess this should be last in the list.
> >
> >>+#include <linux/workqueue.h>
> >>+#include <linux/mfd/max77693.h>
> >>+#include <linux/mfd/max77693-private.h>
> >>+
> >>+#define MAX77693_LED_NAME "max77693-flash"
> >>+
> >>+#define MAX77693_TORCH_IOUT_BITS 4
> >>+
> >>+#define MAX77693_TORCH_NO_TIMER 0x40
> >>+#define MAX77693_FLASH_TIMER_LEVEL 0x80
> >>+
> >>+#define MAX77693_FLASH_EN_OFF 0
> >>+#define MAX77693_FLASH_EN_FLASH 1
> >>+#define MAX77693_FLASH_EN_TORCH 2
> >>+#define MAX77693_FLASH_EN_ON 3
> >>+
> >>+#define MAX77693_FLASH_EN1_SHIFT 6
> >>+#define MAX77693_FLASH_EN2_SHIFT 4
> >>+#define MAX77693_TORCH_EN1_SHIFT 2
> >>+#define MAX77693_TORCH_EN2_SHIFT 0
> >>+
> >>+#define MAX77693_FLASH_LOW_BATTERY_EN 0x80
> >>+
> >>+#define MAX77693_FLASH_BOOST_FIXED 0x04
> >>+#define MAX77693_FLASH_BOOST_LEDNUM_2 0x80
> >>+
> >>+#define MAX77693_FLASH_TIMEOUT_MIN 62500
> >>+#define MAX77693_FLASH_TIMEOUT_MAX 1000000
> >>+#define MAX77693_FLASH_TIMEOUT_STEP 62500
> >>+
> >>+#define MAX77693_TORCH_TIMEOUT_MIN 262000
> >>+#define MAX77693_TORCH_TIMEOUT_MAX 15728000
> >>+
> >>+#define MAX77693_FLASH_IOUT_MIN 15625
> >>+#define MAX77693_FLASH_IOUT_MAX_1LED 1000000
> >>+#define MAX77693_FLASH_IOUT_MAX_2LEDS 625000
> >>+#define MAX77693_FLASH_IOUT_STEP 15625
> >>+
> >>+#define MAX77693_TORCH_IOUT_MIN 15625
> >>+#define MAX77693_TORCH_IOUT_MAX 250000
> >>+#define MAX77693_TORCH_IOUT_STEP 15625
> >>+
> >>+#define MAX77693_FLASH_VSYS_MIN 2400
> >>+#define MAX77693_FLASH_VSYS_MAX 3400
> >>+#define MAX77693_FLASH_VSYS_STEP 33
> >>+
> >>+#define MAX77693_FLASH_VOUT_MIN 3300
> >>+#define MAX77693_FLASH_VOUT_MAX 5500
> >>+#define MAX77693_FLASH_VOUT_STEP 25
> >>+#define MAX77693_FLASH_VOUT_RMIN 0x0c
> >>+
> >>+#define MAX77693_LED_STATUS_FLASH_ON (1 << 3)
> >>+#define MAX77693_LED_STATUS_TORCH_ON (1 << 2)
> >>+
> >>+#define MAX77693_LED_FLASH_INT_FLED2_OPEN (1 << 0)
> >>+#define MAX77693_LED_FLASH_INT_FLED2_SHORT (1 << 1)
> >>+#define MAX77693_LED_FLASH_INT_FLED1_OPEN (1 << 2)
> >>+#define MAX77693_LED_FLASH_INT_FLED1_SHORT (1 << 3)
> >>+#define MAX77693_LED_FLASH_INT_OVER_CURRENT (1 << 4)
> >>+
> >>+#define MAX77693_MODE_OFF 0
> >>+#define MAX77693_MODE_FLASH (1 << 0)
> >>+#define MAX77693_MODE_TORCH (1 << 1)
> >>+#define MAX77693_MODE_FLASH_EXTERNAL (1 << 2)
> >>+
> >>+enum {
> >>+ FLASH1,
> >>+ FLASH2,
> >>+ TORCH1,
> >>+ TORCH2
> >>+};
> >>+
> >>+enum {
> >>+ FLASH,
> >>+ TORCH
> >>+};
> >>+
> >>+struct max77693_led {
> >>+ struct regmap *regmap;
> >>+ struct platform_device *pdev;
> >>+ struct max77693_led_platform_data *pdata;
> >>+ struct mutex lock;
> >>+
> >>+ struct led_classdev ldev;
> >>+
> >>+ unsigned int torch_brightness;
> >>+ struct work_struct work_brightness_set;
> >>+ unsigned int mode_flags;
> >>+};
> >>+
> >>+static u8 max77693_led_iout_to_reg(u32 ua)
> >>+{
> >>+ if (ua < MAX77693_FLASH_IOUT_MIN)
> >>+ ua = MAX77693_FLASH_IOUT_MIN;
> >>+ return (ua - MAX77693_FLASH_IOUT_MIN) / MAX77693_FLASH_IOUT_STEP;
> >>+}
> >>+
> >>+static u8 max77693_flash_timeout_to_reg(u32 us)
> >>+{
> >>+ return (us - MAX77693_FLASH_TIMEOUT_MIN) / MAX77693_FLASH_TIMEOUT_STEP;
> >>+}
> >>+
> >>+static const u32 max77693_torch_timeouts[] = {
> >>+ 262000, 524000, 786000, 1048000,
> >>+ 1572000, 2096000, 2620000, 3144000,
> >>+ 4193000, 5242000, 6291000, 7340000,
> >>+ 9437000, 11534000, 13631000, 1572800
> >>+};
> >>+
> >>+static u8 max77693_torch_timeout_to_reg(u32 us)
> >>+{
> >>+ int i, b = 0, e = ARRAY_SIZE(max77693_torch_timeouts);
> >
> >I haven't run this, but it looks like it'll access max77693_torch_timeouts[]
> >array after the last element if you pass it a value greater than 1572800.
> >Shouldn't e be initialised to ARRAY_SIZE() - 1 instead?
> >
> >>+
> >>+ while (e - b > 1) {
> >>+ i = b + (e - b) / 2;
> >>+ if (us < max77693_torch_timeouts[i])
> >>+ e = i;
> >>+ else
> >>+ b = i;
> >>+ }
> >>+ return b;
> >>+}
>
> Let's track this case:
>
> us = 1600000
> e = 16 (ARRAY_SIZE(max77693_torch_timeouts))
>
> 1st iteration:
> while (16 - 0 > 1) {
> i = 0 + (16 - 0) / 2 //= 8
> b = 8
> }
>
> 2nd iteration:
> while (16 - 8 > 1) {
> i = 8 + (16 - 8) / 2 //= 12
> b = 12
> }
>
> 3rd iteration:
> while (16 - 12 > 1) {
> i = 12 + (16 - 12) / 2 //= 14
> b = 14
> }
>
> 4th iteration:
> while (16 - 14 > 1) {
> i = 14 + (16 - 14) / 2 //= 15
> b = 15
> }
>
> 5th iteration:
> while (16 - 15 > 1) { //false

Indeed, you're right -- "> 1" is the crucial part that I missed.

> }
>
> return b (15) - last element in the array
>
>
> >>+static struct max77693_led *ldev_to_led(struct led_classdev *ldev)
> >>+{
> >>+ return container_of(ldev, struct max77693_led, ldev);
> >>+}
> >>+
> >>+static u32 max77693_torch_timeout_from_reg(u8 reg)
> >>+{
> >
> >I might limit reg to ARRAY_SIZE(...) as well. Up to you.
>
> It is already aligned during validation of platform data.

Ack.

> >>+ return max77693_torch_timeouts[reg];
> >>+}
> >>+
> >>+static u8 max77693_led_vsys_to_reg(u32 mv)
> >>+{
> >>+ return ((mv - MAX77693_FLASH_VSYS_MIN) / MAX77693_FLASH_VSYS_STEP) << 2;
> >>+}
> >>+
> >>+static u8 max77693_led_vout_to_reg(u32 mv)
> >>+{
> >>+ return (mv - MAX77693_FLASH_VOUT_MIN) / MAX77693_FLASH_VOUT_STEP +
> >>+ MAX77693_FLASH_VOUT_RMIN;
> >>+}
> >>+
> >>+/* split composite current @i into two @iout according to @imax weights */
> >
> >Are there dependencies between the two LEDs or are they entirely
> >independent? If they're independent (with the possible exception of strobe),
> >then I'd expose them individually.
>
> They are independent.
>
> >>+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 / MAX77693_FLASH_IOUT_STEP * MAX77693_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 & MAX77693_MODE_TORCH) {
> >>+ if (p->trigger[TORCH1] & MAX77693_LED_TRIG_SOFT)
> >>+ v |= MAX77693_FLASH_EN_ON << MAX77693_TORCH_EN1_SHIFT;
> >>+ if (p->trigger[TORCH2] & MAX77693_LED_TRIG_SOFT)
> >>+ v |= MAX77693_FLASH_EN_ON << MAX77693_TORCH_EN2_SHIFT;
> >>+ }
> >>+
> >>+ if (mode & MAX77693_MODE_FLASH) {
> >>+ if (p->trigger[FLASH1] & MAX77693_LED_TRIG_SOFT)
> >>+ v |= MAX77693_FLASH_EN_ON << MAX77693_FLASH_EN1_SHIFT;
> >>+ if (p->trigger[FLASH2] & MAX77693_LED_TRIG_SOFT)
> >>+ v |= MAX77693_FLASH_EN_ON << MAX77693_FLASH_EN2_SHIFT;
> >>+ } else if (mode & MAX77693_MODE_FLASH_EXTERNAL) {
> >>+ if (p->trigger[FLASH1] & MAX77693_LED_TRIG_EXT)
> >>+ v |= MAX77693_FLASH_EN_FLASH << MAX77693_FLASH_EN1_SHIFT;
> >>+ if (p->trigger[FLASH2] & MAX77693_LED_TRIG_EXT)
> >>+ v |= MAX77693_FLASH_EN_FLASH << MAX77693_FLASH_EN2_SHIFT;
> >>+ /*
> >>+ * 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[TORCH1] & MAX77693_LED_TRIG_EXT)
> >>+ v |= MAX77693_FLASH_EN_TORCH << MAX77693_TORCH_EN1_SHIFT;
> >>+ if (p->trigger[TORCH2] & MAX77693_LED_TRIG_EXT)
> >>+ v |= MAX77693_FLASH_EN_TORCH << MAX77693_TORCH_EN2_SHIFT;
> >>+ }
> >>+
> >>+ /* Reset the register only prior setting flash modes */
> >>+ if (mode != MAX77693_MODE_TORCH) {
> >>+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_EN, 0);
> >
> >A single wrie for strobe. Looks good!
> >
> >>+ if (ret < 0)
> >>+ return ret;
> >>+ }
> >>+
> >>+ return max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_EN, v);
> >>+}
> >>+
> >>+static int max77693_add_mode(struct max77693_led *led, unsigned int mode)
> >>+{
> >>+ int ret;
> >>+
> >>+ /* Once enabled torch mode is active until turned off */
> >>+ if ((mode == MAX77693_MODE_TORCH) &&
> >>+ (led->mode_flags & MAX77693_MODE_TORCH))
> >>+ return 0;
> >>+
> >>+ /*
> >>+ * FLASH_EXTERNAL mode activates HW triggered flash and torch
> >>+ * modes in the device. The related register settings interfere
> >>+ * with SW triggerred modes, thus clear them to ensure proper
> >>+ * device configuration.
> >>+ */
> >>+ if (mode == MAX77693_MODE_FLASH_EXTERNAL)
> >>+ led->mode_flags &= (~MAX77693_MODE_TORCH &
> >>+ ~MAX77693_MODE_FLASH);
> >>+
> >>+ 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
> >>+ * spurous flash strobing on each successive torch mode
> >>+ * setting.
> >>+ */
> >>+ if ((mode == MAX77693_MODE_FLASH) ||
> >>+ (mode == MAX77693_MODE_FLASH_EXTERNAL))
> >>+ led->mode_flags &= ~mode;
> >>+
> >>+ return 0;
> >>+}
> >>+
> >>+static int max77693_clear_mode(struct max77693_led *led, unsigned int mode)
> >>+{
> >>+ led->mode_flags &= ~mode;
> >>+
> >>+ return max77693_set_mode(led, led->mode_flags);
> >>+}
> >>+
> >>+static int max77693_set_torch_current(struct max77693_led *led,
> >>+ u32 micro_amp)
> >>+{
> >>+ struct max77693_led_platform_data *p = led->pdata;
> >>+ struct regmap *rmap = led->regmap;
> >>+ u32 iout[2];
> >>+ u8 v, iout1_reg, iout2_reg;
> >>+ int ret;
> >>+
> >>+ max77693_calc_iout(iout, micro_amp, &p->iout[TORCH1]);
> >>+ iout1_reg = max77693_led_iout_to_reg(iout[0]);
> >>+ iout2_reg = max77693_led_iout_to_reg(iout[1]);
> >>+
> >>+ v = iout1_reg | (iout2_reg << MAX77693_TORCH_IOUT_BITS);
> >>+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCH, v);
> >>+ if (ret < 0)
> >>+ return ret;
> >>+
> >>+ return 0;
> >>+}
> >>+
> >>+static int max77693_set_flash_current(struct max77693_led *led,
> >>+ u32 micro_amp)
> >>+{
> >>+ struct max77693_led_platform_data *p = led->pdata;
> >>+ struct regmap *rmap = led->regmap;
> >>+ u32 iout[2];
> >>+ u8 iout1_reg, iout2_reg;
> >>+ int ret;
> >>+
> >>+ max77693_calc_iout(iout, micro_amp, &p->iout[FLASH1]);
> >>+ iout1_reg = max77693_led_iout_to_reg(iout[0]);
> >>+ iout2_reg = max77693_led_iout_to_reg(iout[1]);
> >>+
> >>+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH1, iout1_reg);
> >>+ if (ret < 0)
> >>+ goto error_ret;
> >>+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH2, iout2_reg);
> >>+ if (ret < 0)
> >>+ goto error_ret;
> >>+
> >>+error_ret:
> >>+ 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;
> >>+
> >>+ v = max77693_flash_timeout_to_reg(timeout);
> >>+
> >>+ if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
> >>+ v |= MAX77693_FLASH_TIMER_LEVEL;
> >>+
> >>+ return max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
> >>+}
> >>+
> >>+static int max77693_strobe_status_get(struct max77693_led *led)
> >>+{
> >>+ struct regmap *rmap = led->regmap;
> >>+ u8 v;
> >>+ int ret;
> >>+
> >>+ ret = max77693_read_reg(rmap, MAX77693_LED_REG_FLASH_INT_STATUS, &v);
> >>+ if (ret < 0)
> >>+ return ret;
> >>+
> >>+ return !!(v & MAX77693_LED_STATUS_FLASH_ON);
> >>+}
> >>+
> >>+static int max77693_int_flag_get(struct max77693_led *led, u8 *v)
> >>+{
> >>+ struct regmap *rmap = led->regmap;
> >>+
> >>+ return max77693_read_reg(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 ret;
> >>+ u8 v;
> >>+
> >>+ ret = max77693_set_torch_current(led, p->iout[TORCH1] +
> >>+ p->iout[TORCH2]);
> >>+ if (ret < 0)
> >>+ return ret;
> >>+
> >>+ ret = max77693_set_flash_current(led, p->iout[FLASH1] +
> >>+ p->iout[FLASH2]);
> >>+ if (ret < 0)
> >>+ return ret;
> >>+
> >>+ if (p->timeout[TORCH] > 0)
> >>+ v = max77693_torch_timeout_to_reg(p->timeout[TORCH]);
> >>+ else
> >>+ v = MAX77693_TORCH_NO_TIMER;
> >>+ if (p->trigger_type[TORCH] == MAX77693_LED_TRIG_TYPE_LEVEL)
> >>+ v |= MAX77693_FLASH_TIMER_LEVEL;
> >>+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCHTIMER, v);
> >>+ if (ret < 0)
> >>+ return ret;
> >>+
> >>+ v = max77693_flash_timeout_to_reg(p->timeout[FLASH]);
> >>+ if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
> >>+ v |= MAX77693_FLASH_TIMER_LEVEL;
> >>+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
> >>+ if (ret < 0)
> >>+ return ret;
> >>+
> >>+ if (p->low_vsys > 0)
> >>+ v = max77693_led_vsys_to_reg(p->low_vsys) |
> >>+ MAX77693_FLASH_LOW_BATTERY_EN;
> >>+ else
> >>+ v = 0;
> >>+
> >>+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_MAX_FLASH1, v);
> >>+ if (ret < 0)
> >>+ return ret;
> >>+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_MAX_FLASH2, 0);
> >>+ if (ret < 0)
> >>+ return ret;
> >>+
> >>+ if (p->boost_mode[FLASH1] == MAX77693_LED_BOOST_FIXED ||
> >>+ p->boost_mode[FLASH2] == MAX77693_LED_BOOST_FIXED)
> >>+ v = MAX77693_FLASH_BOOST_FIXED;
> >>+ else
> >>+ v = p->boost_mode[FLASH1] | (p->boost_mode[FLASH2] << 1);
> >>+ if (p->boost_mode[FLASH1] && p->boost_mode[FLASH2])
> >>+ v |= MAX77693_FLASH_BOOST_LEDNUM_2;
> >>+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_VOUT_CNTL, v);
> >>+ if (ret < 0)
> >>+ return ret;
> >>+
> >>+ v = max77693_led_vout_to_reg(p->boost_vout);
> >>+ ret = max77693_write_reg(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
> >>+ if (ret < 0)
> >>+ return ret;
> >>+
> >>+ return max77693_set_mode(led, MAX77693_MODE_OFF);
> >>+}
> >>+
> >>+/* LED subsystem callbacks */
> >>+
> >>+static void max77693_brightness_set_work(struct work_struct *work)
> >>+{
> >>+ struct max77693_led *led =
> >>+ container_of(work, struct max77693_led, work_brightness_set);
> >>+ int ret;
> >>+
> >>+ mutex_lock(&led->lock);
> >>+
> >>+ if (led->torch_brightness == 0) {
> >>+ ret = max77693_clear_mode(led, MAX77693_MODE_TORCH);
> >>+ 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->torch_brightness *
> >>+ MAX77693_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, MAX77693_MODE_TORCH);
> >>+ if (ret < 0)
> >>+ dev_dbg(&led->pdev->dev, "Failed to set torch mode (%d)\n",
> >>+ ret);
> >>+unlock:
> >>+ mutex_unlock(&led->lock);
> >>+}
> >>+
> >>+static void max77693_led_brightness_set(struct led_classdev *led_cdev,
> >>+ enum led_brightness value)
> >>+{
> >>+ struct max77693_led *led = ldev_to_led(led_cdev);
> >>+
> >>+ led->torch_brightness = value;
> >>+ schedule_work(&led->work_brightness_set);
> >
> >Is there a reason not to do this right now (but in a work queue instead)?
>
> Almost all the drivers in the LED subsystem do it that way.
> I think that it is caused by the fact that setting led brightness
> should be as fast as possible and non-blocking. The led may be
> used e.g. for HD LED (see ledtrig-ide) and activated many times
> per second, and thus it could have impact on the system performance
> if it wasn't run in a work queue.

Fair enough. But the expectation is that the V4L2 control's value has taken
effect when the set control handler returns. That is also what virtually all
existing implementations do.

Could this be handled in the LED framework instead so that the V4L2 controls
would function synchronously?

I'm ok for postponing this as long as we agree on how it'd be fixed. Perhaps
someone from the LED framework side to comment.

> >>+}
> >>+
> >>+static int max77693_led_flash_strobe_get(struct led_classdev *led_cdev)
> >>+{
> >>+ struct max77693_led *led = ldev_to_led(led_cdev);
> >>+ int ret;
> >>+
> >>+ mutex_lock(&led->lock);
> >>+ ret = max77693_strobe_status_get(led);
> >>+ mutex_unlock(&led->lock);
> >>+
> >>+ return ret;
> >>+}
> >>+
> >>+static int max77693_led_flash_fault_get(struct led_classdev *led_cdev,
> >>+ u32 *fault)
> >>+{
> >>+ struct max77693_led *led = ldev_to_led(led_cdev);
> >>+ u8 v;
> >>+ int ret;
> >>+
> >>+ mutex_lock(&led->lock);
> >>+
> >>+ ret = max77693_int_flag_get(led, &v);
> >>+ if (ret < 0)
> >>+ goto unlock;
> >>+
> >>+ *fault = 0;
> >>+
> >>+ if (v & MAX77693_LED_FLASH_INT_FLED2_OPEN ||
> >>+ v & MAX77693_LED_FLASH_INT_FLED1_OPEN)
> >>+ *fault |= LED_FAULT_OVER_VOLTAGE;
> >>+ if (v & MAX77693_LED_FLASH_INT_FLED2_SHORT ||
> >>+ v & MAX77693_LED_FLASH_INT_FLED1_SHORT)
> >>+ *fault |= LED_FAULT_SHORT_CIRCUIT;
> >>+ if (v & MAX77693_LED_FLASH_INT_OVER_CURRENT)
> >>+ *fault |= LED_FAULT_OVER_CURRENT;
> >>+unlock:
> >>+ mutex_unlock(&led->lock);
> >>+ return ret;
> >>+}
> >>+
> >>+static int max77693_led_flash_strobe_set(struct led_classdev *led_cdev,
> >>+ bool state)
> >>+{
> >>+ struct max77693_led *led = ldev_to_led(led_cdev);
> >>+ struct led_flash *flash = led_cdev->flash;
> >>+ int ret;
> >>+
> >>+ mutex_lock(&led->lock);
> >>+
> >>+ if (flash->external_strobe) {
> >>+ ret = -EBUSY;
> >>+ goto unlock;
> >>+ }
> >>+
> >>+ if (!state) {
> >>+ ret = max77693_clear_mode(led, MAX77693_MODE_FLASH);
> >>+ goto unlock;
> >>+ }
> >>+
> >>+ ret = max77693_add_mode(led, MAX77693_MODE_FLASH);
> >>+ if (ret < 0)
> >>+ goto unlock;
> >>+unlock:
> >>+ mutex_unlock(&led->lock);
> >>+ return ret;
> >>+}
> >>+
> >>+static int max77693_led_external_strobe_set(struct led_classdev *led_cdev,
> >>+ bool enable)
> >>+{
> >>+ struct max77693_led *led = ldev_to_led(led_cdev);
> >>+ int ret;
> >>+
> >>+ mutex_lock(&led->lock);
> >>+
> >>+ if (enable)
> >>+ ret = max77693_add_mode(led, MAX77693_MODE_FLASH_EXTERNAL);
> >>+ else
> >>+ ret = max77693_clear_mode(led, MAX77693_MODE_FLASH_EXTERNAL);
> >>+
> >>+ mutex_unlock(&led->lock);
> >>+
> >>+ return ret;
> >>+}
> >>+
> >>+static int max77693_led_flash_brightness_set(struct led_classdev *led_cdev,
> >>+ u32 brightness)
> >>+{
> >>+ struct max77693_led *led = ldev_to_led(led_cdev);
> >>+ int ret;
> >>+
> >>+ mutex_lock(&led->lock);
> >>+
> >>+ ret = max77693_set_flash_current(led, brightness);
> >>+ if (ret < 0)
> >>+ goto unlock;
> >>+unlock:
> >>+ mutex_unlock(&led->lock);
> >>+ return ret;
> >>+}
> >>+
> >>+static int max77693_led_flash_timeout_set(struct led_classdev *led_cdev,
> >>+ u32 timeout)
> >>+{
> >>+ struct max77693_led *led = ldev_to_led(led_cdev);
> >>+ int ret;
> >>+
> >>+ mutex_lock(&led->lock);
> >>+
> >>+ ret = max77693_set_timeout(led, timeout);
> >>+ if (ret < 0)
> >>+ goto unlock;
> >>+
> >>+unlock:
> >>+ mutex_unlock(&led->lock);
> >>+ return ret;
> >>+}
> >>+
> >>+static void max77693_led_parse_dt(struct max77693_led_platform_data *p,
> >>+ struct device_node *node)
> >>+{
> >>+ of_property_read_u32_array(node, "maxim,iout", p->iout, 4);
> >
> >How about separate current for flash and torch modes? They are the same
> >LEDs; just the mode is different.
>
> There are separate currents - 2 for torch and 2 for flash mode.

True. But shouldn't they be two different properties as, well, these are
different properties of individual hardware devices? :-)

> >>+ of_property_read_u32_array(node, "maxim,trigger", p->trigger, 4);
> >>+ of_property_read_u32_array(node, "maxim,trigger-type", p->trigger_type,
> >>+ 2);
> >>+ of_property_read_u32_array(node, "maxim,timeout", p->timeout, 2);
> >>+ of_property_read_u32_array(node, "maxim,boost-mode", p->boost_mode, 2);
> >>+ of_property_read_u32(node, "maxim,boost-vout", &p->boost_vout);
> >>+ of_property_read_u32(node, "maxim,vsys-min", &p->low_vsys);
> >
> >Are these values specific to the maxim chip? I'd suppose e.g. timeout and
> >iout are something that can be found pretty much in any flash controller.
>
> Besides the two they are specific. And what with timeout and iout
> if they are common for all flash controllers?

They should be defined in a way which is not specific to the chip itself.
That would also change the property names. I'm not sure how much of this is
already done on the LED side.

> >>+}
> >>+
> >>+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;
> >>+
> >>+ for (i = 0; i < 2; ++i)
> >
> >How about using ARRAY_SIZE() here, too?
>
> OK.
>
> >
> >>+ clamp_align(&p->boost_mode[i], MAX77693_LED_BOOST_NONE,
> >>+ MAX77693_LED_BOOST_FIXED, 1);
> >>+ /* boost, if enabled, should be the same on both leds */
> >>+ if (p->boost_mode[0] != MAX77693_LED_BOOST_NONE &&
> >>+ p->boost_mode[1] != MAX77693_LED_BOOST_NONE)
> >>+ p->boost_mode[1] = p->boost_mode[0];
> >>+
> >>+ max = (p->boost_mode[FLASH1] && p->boost_mode[FLASH2]) ?
> >>+ MAX77693_FLASH_IOUT_MAX_2LEDS : MAX77693_FLASH_IOUT_MAX_1LED;
> >>+
> >>+ clamp_align(&p->iout[FLASH1], MAX77693_FLASH_IOUT_MIN,
> >>+ max, MAX77693_FLASH_IOUT_STEP);
> >>+ clamp_align(&p->iout[FLASH2], MAX77693_FLASH_IOUT_MIN,
> >>+ max, MAX77693_FLASH_IOUT_STEP);
> >>+ clamp_align(&p->iout[TORCH1], MAX77693_TORCH_IOUT_MIN,
> >>+ MAX77693_TORCH_IOUT_MAX, MAX77693_TORCH_IOUT_STEP);
> >>+ clamp_align(&p->iout[TORCH2], MAX77693_TORCH_IOUT_MIN,
> >>+ MAX77693_TORCH_IOUT_MAX, MAX77693_TORCH_IOUT_STEP);
> >>+
> >>+ for (i = 0; i < 4; ++i)
> >>+ clamp_align(&p->trigger[i], 0, 7, 1);

You can just use clamp() here. Same elsewhere where step == 1.

> >>+ for (i = 0; i < 2; ++i)
> >>+ clamp_align(&p->trigger_type[i], MAX77693_LED_TRIG_TYPE_EDGE,
> >>+ MAX77693_LED_TRIG_TYPE_LEVEL, 1);
> >
> >ARRAY_SIZE() would be nicer than using numeric values for the loop
> >condition.
> >
> >>+ clamp_align(&p->timeout[FLASH], MAX77693_FLASH_TIMEOUT_MIN,
> >>+ MAX77693_FLASH_TIMEOUT_MAX, MAX77693_FLASH_TIMEOUT_STEP);
> >>+
> >>+ if (p->timeout[TORCH]) {
> >>+ clamp_align(&p->timeout[TORCH], MAX77693_TORCH_TIMEOUT_MIN,
> >>+ MAX77693_TORCH_TIMEOUT_MAX, 1);
> >>+ p->timeout[TORCH] = max77693_torch_timeout_from_reg(
> >>+ max77693_torch_timeout_to_reg(p->timeout[TORCH]));
> >>+ }
> >>+
> >>+ clamp_align(&p->boost_vout, MAX77693_FLASH_VOUT_MIN,
> >>+ MAX77693_FLASH_VOUT_MAX, MAX77693_FLASH_VOUT_STEP);
> >>+
> >>+ if (p->low_vsys) {

Extra braces.

> >>+ clamp_align(&p->low_vsys, MAX77693_FLASH_VSYS_MIN,
> >>+ MAX77693_FLASH_VSYS_MAX, MAX77693_FLASH_VSYS_STEP);
> >>+ }
> >>+}
> >>+
> >>+static int max77693_led_get_platform_data(struct max77693_led *led)
> >>+{
> >>+ struct max77693_led_platform_data *p;
> >>+ struct device *dev = &led->pdev->dev;
> >>+
> >>+ if (dev->of_node) {
> >>+ p = devm_kzalloc(dev, sizeof(*led->pdata), GFP_KERNEL);
> >>+ if (!p)
> >>+ return -ENOMEM;
> >
> >Check for p can be moved out of the if as it's the same for both.
> >
> >You could also use led->pdata directly. Up to you.
> >
> >>+ max77693_led_parse_dt(p, dev->of_node);
> >>+ } else {
> >>+ p = dev_get_platdata(dev);
> >>+ if (!p)
> >>+ return -ENODEV;
> >>+ }
> >>+ led->pdata = p;
> >>+
> >>+ max77693_led_validate_platform_data(p);
> >>+
> >>+ return 0;
> >>+}
> >>+
> >>+static struct led_flash led_flash = {
> >>+ .ops = {
> >>+ .brightness_set = max77693_led_flash_brightness_set,
> >>+ .strobe_set = max77693_led_flash_strobe_set,
> >>+ .strobe_get = max77693_led_flash_strobe_get,
> >>+ .timeout_set = max77693_led_flash_timeout_set,
> >>+ .external_strobe_set = max77693_led_external_strobe_set,
> >>+ .fault_get = max77693_led_flash_fault_get,
> >>+ },
> >>+ .has_flash_led = true,
> >>+};
> >>+
> >>+static void max77693_init_led_controls(struct led_classdev *led_cdev,
> >>+ struct max77693_led_platform_data *p)
> >>+{
> >>+ struct led_flash *flash = led_cdev->flash;
> >>+ struct led_ctrl *c;
> >>+
> >>+ /*
> >>+ * brightness_ctrl and fault_flags are used only
> >>+ * for initializing related V4L2 controls.
> >>+ */
> >>+#ifdef CONFIG_V4L2_FLASH
> >>+ flash->fault_flags = V4L2_FLASH_FAULT_OVER_VOLTAGE |
> >>+ V4L2_FLASH_FAULT_SHORT_CIRCUIT |
> >>+ V4L2_FLASH_FAULT_OVER_CURRENT;
> >>+
> >>+ c = &led_cdev->brightness_ctrl;
> >>+ c->min = (p->iout[TORCH1] != 0 && p->iout[TORCH2] != 0) ?
> >>+ MAX77693_TORCH_IOUT_MIN * 2 :
> >>+ MAX77693_TORCH_IOUT_MIN;
> >>+ c->max = p->iout[TORCH1] + p->iout[TORCH2];
> >>+ c->step = MAX77693_TORCH_IOUT_STEP;
> >>+ c->val = p->iout[TORCH1] + p->iout[TORCH2];
> >
> >Can you control the current for the two flash LEDs separately?
>
> Yes.
>
> >If yes, this
> >should be also available on the V4L2 flash API. The lm3560 driver does this,
> >for example. (It creates two sub-devices since we can only control a single
> >LED using a single sub-device, at least for the time being.)
>
> So, should I propose new V4L2 flash API for controlling more than
> one led? Probably similar improvement should be applied to the
> LED subsystem.

As said, the V4L2 API exposes two sub-devices currently. That's just a hack,
though; I think we need extensions in the V4L2 core API to support this
properly: controls are per-(sub)device and we certainly do not want controls
such as V4L2_CID_FLASH2_INTENSITY. But I don't think it's an excuse for e.g.
LED API not to do it. :-)

One option would be to use a matrix control but I'm not sure how much I like
that option either: there's nothing in the API that suggests that the index
is the LED number, for instance. That is still the only realistic
possibility right now. Actually --- this is what I'd suggest right now. Cc
Hans.

What I'm worried about is that, as this will affect the user space API in a
way or another very probably, changing it later on could be a problem. That
has been proved multiple times and people are often afraid of even trying to
do so. So if we can think of a way how to meaningfully extend the LED API
now into this direction and get an acceptance from the LED API developers,
that would be highly appreciated.

--
Kind regards,

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

2014-04-25 23:18:07

by Bryan Wu

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 1/5] leds: Add sysfs and kernel internal API for flash LEDs

On Fri, Apr 11, 2014 at 7:56 AM, Jacek Anaszewski
<[email protected]> wrote:
> Some LED devices support two operation modes - torch and
> flash.

Do we have a method to look up the capabilities from LED devices driver?
For example, the LED device supports Torch/Flash then LED device
driver should set a flag like LED_DEV_CAP_TORCH or LED_DEV_CAP_FLASH.
If it doesn't support those functions, it won't set those flags.

LED Flash class core can check those flags for further usage.

> 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
> optional external_strobe, indicator_brightness and
> max_indicator_btightness. All the flash related features

typo here, it should max_indicator_btightness -> max_indicator_brightness

> are placed in a separate module.

Please add one empty line here.

> 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.
>

I don't quite understand the last sentence here. Looks like the LED
flash class interface binds to V4L2 Flash sub-device, then why we need
to export sysfs for user space if the only user is V4L2 which can talk
through kernel internal API here.

> 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 | 8 +
> drivers/leds/Makefile | 1 +
> drivers/leds/led-class.c | 36 ++-
> drivers/leds/led-flash.c | 627
+++++++++++++++++++++++++++++++++++++++++++

If we go for the LED Flash class, I prefer to use led-class-flash.c
rather than led-flash.c

> drivers/leds/led-triggers.c | 16 +-
> drivers/leds/leds.h | 6 +
> include/linux/leds.h | 50 +++-
> include/linux/leds_flash.h | 252 +++++++++++++++++

leds_flash.h -> led-class-flash.h

> 8 files changed, 982 insertions(+), 14 deletions(-)
> create mode 100644 drivers/leds/led-flash.c
> create mode 100644 include/linux/leds_flash.h
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 2062682..1e1c81f 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -19,6 +19,14 @@ 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 "Flash LEDs Support"
"LED Flash Class Support"

> + depends on LEDS_CLASS
> + help
> + This option enables support for flash LED devices. Say Y if you
> + want to use flash specific features of a LED device, if they
> + are supported.
> +

This help message is not very accurate, please take a look at
LEDS_CLASS. And I prefer this driver can be a module, so it should be
mentioned here.

> comment "LED drivers"
>
> config LEDS_88PM860X
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 3cd76db..8861b86 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-flash.o
> obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
>
> # LED Platform Drivers
> diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
> index f37d63c..58f16c3 100644
> --- a/drivers/leds/led-class.c
> +++ b/drivers/leds/led-class.c
> @@ -9,15 +9,16 @@
> * published by the Free Software Foundation.
> */
>
> -#include <linux/module.h>
> -#include <linux/kernel.h>
> +#include <linux/ctype.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> #include <linux/init.h>
> +#include <linux/kernel.h>
> #include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> #include <linux/spinlock.h>
> -#include <linux/device.h>
> #include <linux/timer.h>
> -#include <linux/err.h>
> -#include <linux/ctype.h>
> #include <linux/leds.h>
> #include "leds.h"
>

I believe this change is kind of cleanup, could you please split them
out? For this patch we just need add those LED Flash class related
code.


> @@ -45,28 +46,38 @@ static ssize_t brightness_store(struct device *dev,
> {
> struct led_classdev *led_cdev = dev_get_drvdata(dev);
> unsigned long state;
> - ssize_t ret = -EINVAL;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
>
> ret = kstrtoul(buf, 10, &state);
> if (ret)
> - return ret;
> + goto unlock;
>
> if (state == LED_OFF)
> led_trigger_remove(led_cdev);
> __led_set_brightness(led_cdev, state);
> + ret = size;
>
> - return size;
> +unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + return ret;

Is this change related to some bug or race condition? I failed to find
any comments about that. Or we need to split it out for further
discussion.

> }
> static DEVICE_ATTR_RW(brightness);
>
> -static ssize_t led_max_brightness_show(struct device *dev,
> +static ssize_t max_brightness_show(struct device *dev,
> struct device_attribute *attr, char *buf)
> {
> struct led_classdev *led_cdev = dev_get_drvdata(dev);
>
> return sprintf(buf, "%u\n", led_cdev->max_brightness);
> }
> -static DEVICE_ATTR(max_brightness, 0444, led_max_brightness_show, NULL);
> +static DEVICE_ATTR_RO(max_brightness);
>

This is cosmetics patch, please split it out.

> #ifdef CONFIG_LEDS_TRIGGERS
> static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
> @@ -174,6 +185,8 @@ 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);
> @@ -218,6 +231,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
> #ifdef CONFIG_LEDS_TRIGGERS
> init_rwsem(&led_cdev->trigger_lock);
> #endif
> + mutex_init(&led_cdev->led_lock);
> /* add to the list of leds */
> down_write(&leds_list_lock);
> list_add_tail(&led_cdev->node, &leds_list);
> @@ -271,6 +285,8 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
> down_write(&leds_list_lock);
> list_del(&led_cdev->node);
> up_write(&leds_list_lock);
> +
> + mutex_destroy(&led_cdev->led_lock);
> }
> EXPORT_SYMBOL_GPL(led_classdev_unregister);
>
> diff --git a/drivers/leds/led-flash.c b/drivers/leds/led-flash.c
> new file mode 100644
> index 0000000..9d482a4
> --- /dev/null
> +++ b/drivers/leds/led-flash.c
> @@ -0,0 +1,627 @@
> +/*
> + * LED Class Flash interface

Is LED Class Flash or LED Flash Class? Please make them consistent.

> + *
> + * 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/module.h>
> +#include <linux/leds.h>
> +#include <linux/leds_flash.h>
> +#include "leds.h"
> +
> +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);
> + unsigned long state;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &state);
> + if (ret)
> + goto unlock;
> +
> + ret = led_set_flash_brightness(led_cdev, state);
> + if (ret < 0)
> + goto unlock;
> +
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + 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_flash *flash = led_cdev->flash;
> +
> + /* no lock needed for this */
> + led_update_flash_brightness(led_cdev);
> +
> + 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_flash *flash = led_cdev->flash;
> +
> + return sprintf(buf, "%u\n", flash->brightness.max);
> +}
> +static DEVICE_ATTR_RO(max_flash_brightness);
> +
> +static ssize_t indicator_brightness_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t size)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + unsigned long state;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &state);
> + if (ret)
> + goto unlock;
> +
> + ret = led_set_indicator_brightness(led_cdev, state);
> + if (ret < 0)
> + goto unlock;
> +
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + return ret;
> +}
> +
> +static ssize_t indicator_brightness_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_flash *flash = led_cdev->flash;
> +
> + /* no lock needed for this */
> + led_update_indicator_brightness(led_cdev);
> +
> + return sprintf(buf, "%u\n", flash->indicator_brightness->val);
> +}
> +static DEVICE_ATTR_RW(indicator_brightness);
> +
> +static ssize_t max_indicator_brightness_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct led_flash *flash = led_cdev->flash;
> +
> + return sprintf(buf, "%u\n", flash->indicator_brightness->max);
> +}
> +static DEVICE_ATTR_RO(max_indicator_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);
> + unsigned long state;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &state);
> + if (ret)
> + goto unlock;
> +
> + if (state < 0 || state > 1)
> + return -EINVAL;
> +
> + ret = led_set_flash_strobe(led_cdev, state);
> + if (ret < 0)
> + goto unlock;
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + 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);
> + int ret;
> +
> + /* no lock needed for this */
> + ret = led_get_flash_strobe(led_cdev);
> + if (ret < 0)
> + return ret;
> +
> + return sprintf(buf, "%u\n", ret);
> +}
> +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);
> + unsigned long flash_timeout;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &flash_timeout);
> + if (ret)
> + goto unlock;
> +
> + ret = led_set_flash_timeout(led_cdev, flash_timeout);
> + if (ret < 0)
> + goto unlock;
> +
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + 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_flash *flash = led_cdev->flash;
> +
> + return sprintf(buf, "%d\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_flash *flash = led_cdev->flash;
> +
> + return sprintf(buf, "%d\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);
> + unsigned int fault;
> + int ret;
> +
> + ret = led_get_flash_fault(led_cdev, &fault);
> + if (ret < 0)
> + return -EINVAL;
> +
> + return sprintf(buf, "0x%8.8x\n", fault);
> +}
> +static DEVICE_ATTR_RO(flash_fault);
> +
> +static ssize_t external_strobe_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t size)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + unsigned long external_strobe;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> +
> + ret = kstrtoul(buf, 10, &external_strobe);
> + if (ret)
> + goto unlock;
> +
> + if (external_strobe > 1) {
> + ret = -EINVAL;
> + goto unlock;
> + }
> +
> + ret = led_set_external_strobe(led_cdev, external_strobe);
> + if (ret < 0)
> + goto unlock;
> + ret = size;
> +unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + return ret;
> +}
> +
> +static ssize_t external_strobe_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> +
> + return sprintf(buf, "%u\n", led_cdev->flash->external_strobe);
> +}
> +static DEVICE_ATTR_RW(external_strobe);
> +
> +static struct attribute *led_flash_attrs[] = {
> + &dev_attr_flash_brightness.attr,
> + &dev_attr_flash_strobe.attr,
> + &dev_attr_flash_timeout.attr,
> + &dev_attr_max_flash_timeout.attr,
> + &dev_attr_max_flash_brightness.attr,
> + &dev_attr_flash_fault.attr,
> + NULL,
> +};
> +
> +static struct attribute *led_flash_indicator_attrs[] = {
> + &dev_attr_indicator_brightness.attr,
> + &dev_attr_max_indicator_brightness.attr,
> + NULL,
> +};
> +
> +static struct attribute *led_flash_external_strobe_attrs[] = {
> + &dev_attr_external_strobe.attr,
> + NULL,
> +};
> +
> +static struct attribute_group led_flash_group = {
> + .attrs = led_flash_attrs,
> +};
> +
> +static struct attribute_group led_flash_indicator_group = {
> + .attrs = led_flash_indicator_attrs,
> +};
> +
> +static struct attribute_group led_flash_external_strobe_group = {
> + .attrs = led_flash_external_strobe_attrs,
> +};
> +
> +void led_flash_resume(struct led_classdev *led_cdev)
> +{
> + struct led_flash *flash = led_cdev->flash;
> +
> + call_flash_op(brightness_set, led_cdev,
> + flash->brightness.val);
> + call_flash_op(timeout_set, led_cdev,
> + flash->timeout.val);
> + if (has_flash_op(indicator_brightness_set))
> + call_flash_op(indicator_brightness_set, led_cdev,
> + flash->indicator_brightness->val);
> +}
> +
> +#ifdef CONFIG_V4L2_FLASH
> +static const struct v4l2_flash_ops v4l2_flash_ops = {
> + .brightness_set = led_set_brightness,
> + .brightness_update = led_update_brightness,
> + .flash_brightness_set = led_set_flash_brightness,
> + .flash_brightness_update = led_update_flash_brightness,
> + .indicator_brightness_set = led_set_indicator_brightness,
> + .indicator_brightness_update = led_update_indicator_brightness,
> + .strobe_set = led_set_flash_strobe,
> + .strobe_get = led_get_flash_strobe,
> + .timeout_set = led_set_flash_timeout,
> + .external_strobe_set = led_set_external_strobe,
> + .fault_get = led_get_flash_fault,
> + .sysfs_lock = led_sysfs_lock,
> + .sysfs_unlock = led_sysfs_unlock,
> +};
> +#define V4L2_FLASH_OPS (&v4l2_flash_ops)
> +#else
> +#define V4L2_FLASH_OPS NULL
> +#endif
> +

This struct can be moved to V4L2 flash driver.

> +
> +void led_flash_remove_sysfs_groups(struct led_classdev *led_cdev)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + int i;
> +
> + for (i = 0; i < LED_FLASH_MAX_SYSFS_GROUPS; ++i)
> + if (flash->sysfs_groups[i])
> + sysfs_remove_group(&led_cdev->dev->kobj,
> + flash->sysfs_groups[i]);
> +}
> +
> +int led_flash_create_sysfs_groups(struct led_classdev *led_cdev)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + int ret, num_sysfs_groups = 0;
> +
> + memset(flash->sysfs_groups, 0, sizeof(*flash->sysfs_groups) *
> + LED_FLASH_MAX_SYSFS_GROUPS);
> +
> + ret = sysfs_create_group(&led_cdev->dev->kobj, &led_flash_group);
> + if (ret < 0)
> + goto err_create_group;
> + flash->sysfs_groups[num_sysfs_groups++] = &led_flash_group;
> +
> + if (flash->indicator_brightness) {
> + ret = sysfs_create_group(&led_cdev->dev->kobj,
> + &led_flash_indicator_group);
> + if (ret < 0)
> + goto err_create_group;
> + flash->sysfs_groups[num_sysfs_groups++] =
> + &led_flash_indicator_group;
> + }
> + if (flash->has_external_strobe) {
> + ret = sysfs_create_group(&led_cdev->dev->kobj,
> + &led_flash_external_strobe_group);
> + if (ret < 0)
> + goto err_create_group;
> + flash->sysfs_groups[num_sysfs_groups++] =
> + &led_flash_external_strobe_group;
> + }
> +
> + return 0;
> +
> +err_create_group:
> + led_flash_remove_sysfs_groups(led_cdev);
> + return ret;
> +}
> +
> +int led_classdev_flash_register(struct device *parent,
> + struct led_classdev *led_cdev)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + const struct led_flash_ops *ops;
> + int ret;
> +
> + if (!flash)
> + return -EINVAL;
> +
> + /* Register led class device */
> + ret = led_classdev_register(parent, led_cdev);
> + if (ret < 0)
> + return ret;
> +

I think this should be after following ops checks, then you don't need
to do unregister when checks fail.

> + if (!flash->has_flash_led)
> + goto exit;

This looks a little bit strange to me.
We check a flag of normal led_cdev firstly like
led_cdev->flags & LED_DEV_CAP_FLASH or LED_DEV_CAP_TORTH,
Then check led_cdev->flash struct.

If you already have struct flash, has_flash_led is redundant to me.

> +
> + /* Validate flash related ops */
> + ops = &flash->ops;
> + if (!ops || !ops->brightness_set || !ops->strobe_set || !ops->strobe_get
> + || !ops->timeout_set || !ops->fault_get)
> + return -EINVAL;
> +
> + if (flash->has_external_strobe && !ops->external_strobe_set)
> + return -EINVAL;
> +
> + if (flash->indicator_brightness && !ops->indicator_brightness_set)
> + return -EINVAL;
> +
> + /* Install resume callback for flash controls */
> + led_cdev->flash_resume = led_flash_resume;
> +
> + /* Create flash led specific sysfs attributes */
> + ret = led_flash_create_sysfs_groups(led_cdev);
> + if (ret < 0)
> + goto err_create_groups;

I'd like to see these sysfs interfaces are documented, please add them
to Documentation/leds/

> +
> +exit:
> + /* This will create V4L2 Flash sub-device if it is enabled */
> + ret = v4l2_flash_init(led_cdev, V4L2_FLASH_OPS);
> + if (ret < 0)
> + goto err_create_groups;
> +
> + return 0;
> +

I don't think this is the right place to call v4l2_flash_init() API.
It should be called during LED Flash device driver probing.
max77693_led_probe() calls led_classdev_flash_register() then calls
v4l2_flash_init()

We should keep the core code separated, calling this in LED Flash
Class API is not a good idea. Let's say if there's another subsystem
also want to use LED Flash Class API but it doesn't want to init V4L2
flash interface, it doesn't need to call v4l2_flash_init().

> +err_create_groups:
> + led_classdev_unregister(led_cdev);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(led_classdev_flash_register);
> +
> +void led_classdev_flash_unregister(struct led_classdev *led_cdev)
> +{
> + v4l2_flash_release(led_cdev);
Move out this also.

> + led_flash_remove_sysfs_groups(led_cdev);
> + led_classdev_unregister(led_cdev);
> +}
> +EXPORT_SYMBOL_GPL(led_classdev_flash_unregister);
> +
> +/* Caller must ensure led_cdev->led_lock held */
> +void led_sysfs_lock(struct led_classdev *led_cdev)
> +{
> + led_cdev->flags |= LED_SYSFS_LOCK;
> +}
> +EXPORT_SYMBOL(led_sysfs_lock);
> +
> +/* Caller must ensure led_cdev->led_lock held */
> +void led_sysfs_unlock(struct led_classdev *led_cdev)
> +{
> + led_cdev->flags &= ~LED_SYSFS_LOCK;
> +}
> +EXPORT_SYMBOL(led_sysfs_unlock);
> +
> +int led_set_flash_strobe(struct led_classdev *led_cdev, bool state)
> +{
> + if (!has_flash_op(strobe_set))
> + return -EINVAL;
> +
> + return call_flash_op(strobe_set, led_cdev, state);
> +}
> +EXPORT_SYMBOL(led_set_flash_strobe);
> +
> +int led_get_flash_strobe(struct led_classdev *led_cdev)
> +{
> + if (!has_flash_op(strobe_get))
> + return -EINVAL;
> +
> + return call_flash_op(strobe_get, led_cdev);
> +}
> +EXPORT_SYMBOL(led_get_flash_strobe);
> +
> +void led_clamp_align_val(struct led_ctrl *c)
> +{
> + u32 v, offset;
> +
> + v = c->val + c->step / 2;
> + v = clamp(v, c->min, c->max);
> + offset = v - c->min;
> + offset = c->step * (offset / c->step);
> + c->val = c->min + offset;
> +}
> +
> +int led_set_flash_timeout(struct led_classdev *led_cdev, u32 timeout)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + struct led_ctrl *c = &flash->timeout;
> + int ret = 0;
> +
> + if (!has_flash_op(timeout_set))
> + return -EINVAL;
> +
> + c->val = timeout;
> + led_clamp_align_val(c);
> +
> + if (!(led_cdev->flags & LED_SUSPENDED))
> + ret = call_flash_op(timeout_set, led_cdev, c->val);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(led_set_flash_timeout);
> +
> +int led_get_flash_fault(struct led_classdev *led_cdev, u32 *fault)
> +{
> + if (!has_flash_op(fault_get))
> + return -EINVAL;
> +
> + return call_flash_op(fault_get, led_cdev, fault);
> +}
> +EXPORT_SYMBOL(led_get_flash_fault);
> +
> +int led_set_external_strobe(struct led_classdev *led_cdev, bool enable)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + int ret;
> +
> + if (!has_flash_op(external_strobe_set))
> + return -EINVAL;
> +
> + if (flash->has_external_strobe) {
> + ret = call_flash_op(external_strobe_set, led_cdev, enable);
> + if (ret < 0)
> + return -EINVAL;
> + flash->external_strobe = enable;
> + } else if (enable)
> + return -EINVAL;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(led_set_external_strobe);
> +
> +int led_set_flash_brightness(struct led_classdev *led_cdev,
> + u32 brightness)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + struct led_ctrl *c = &flash->brightness;
> + int ret = 0;
> +
> + if (!has_flash_op(brightness_set))
> + return -EINVAL;
> +
> + c->val = brightness;
> + led_clamp_align_val(c);
> +
> + if (!(led_cdev->flags & LED_SUSPENDED))
> + ret = call_flash_op(brightness_set, led_cdev, c->val);
> + return ret;
> +}
> +EXPORT_SYMBOL(led_set_flash_brightness);
> +
> +int led_update_flash_brightness(struct led_classdev *led_cdev)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + struct led_ctrl *c = &flash->brightness;
> + u32 brightness;
> + int ret = 0;
> +
> + if (has_flash_op(brightness_get)) {
> + ret = call_flash_op(brightness_get, led_cdev, &brightness);
> + if (ret < 0)
> + return ret;
> + c->val = brightness;
> + }
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(led_update_flash_brightness);
> +
> +int led_set_indicator_brightness(struct led_classdev *led_cdev,
> + u32 brightness)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + struct led_ctrl *c = flash->indicator_brightness;
> + int ret = 0;
> +
> + if (!has_flash_op(indicator_brightness_set))
> + return -EINVAL;
> +
> + c->val = brightness;
> + led_clamp_align_val(c);
> +
> + if (!(led_cdev->flags & LED_SUSPENDED))
> + ret = call_flash_op(indicator_brightness_set, led_cdev, c->val);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(led_set_indicator_brightness);
> +
> +int led_update_indicator_brightness(struct led_classdev *led_cdev)
> +{
> + struct led_flash *flash = led_cdev->flash;
> + struct led_ctrl *c = flash->indicator_brightness;
> + u32 brightness;
> + int ret = 0;
> +
> + if (has_flash_op(indicator_brightness_get)) {
> + ret = call_flash_op(indicator_brightness_get, led_cdev,
> + &brightness);
> + if (ret < 0)
> + return ret;
> + c->val = brightness;
> + }
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(led_update_indicator_brightness);
> +
> +static int __init leds_flash_init(void)
> +{
> + return 0;
> +}
> +
> +static void __exit leds_flash_exit(void)
> +{
> +}
> +
> +subsys_initcall(leds_flash_init);
> +module_exit(leds_flash_exit);

I don't think we need these empty functions here. Looks it useless to me.

> +
> +MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("LED Class Flash Interface");
> diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
> index df1a7c1..40e21c0 100644
> --- a/drivers/leds/led-triggers.c
> +++ b/drivers/leds/led-triggers.c
> @@ -37,6 +37,14 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
> char trigger_name[TRIG_NAME_MAX];
> struct led_trigger *trig;
> size_t len;
> + int ret = count;
> +
> + mutex_lock(&led_cdev->led_lock);
> +
> + if (led_sysfs_is_locked(led_cdev)) {
> + ret = -EBUSY;
> + goto exit_unlock;
> + }
>
> trigger_name[sizeof(trigger_name) - 1] = '\0';
> strncpy(trigger_name, buf, sizeof(trigger_name) - 1);
> @@ -47,7 +55,7 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
>
> if (!strcmp(trigger_name, "none")) {
> led_trigger_remove(led_cdev);
> - return count;
> + goto exit_unlock;
> }
>
> down_read(&triggers_list_lock);
> @@ -58,12 +66,14 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
> up_write(&led_cdev->trigger_lock);
>
> up_read(&triggers_list_lock);
> - return count;
> + goto exit_unlock;
> }
> }
> up_read(&triggers_list_lock);
>
> - return -EINVAL;
> +exit_unlock:
> + mutex_unlock(&led_cdev->led_lock);
> + return ret;
> }
> EXPORT_SYMBOL_GPL(led_trigger_store);
>
> diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
> index 4c50365..f66a0c3 100644
> --- a/drivers/leds/leds.h
> +++ b/drivers/leds/leds.h
> @@ -17,6 +17,12 @@
> #include <linux/rwsem.h>
> #include <linux/leds.h>
>
> +#define call_flash_op(op, args...) \
> + ((led_cdev)->flash->ops.op(args))
> +
> +#define has_flash_op(op) \
> + ((led_cdev)->flash && (led_cdev)->flash->ops.op)
> +
> static inline void __led_set_brightness(struct led_classdev *led_cdev,
> enum led_brightness value)
> {
> diff --git a/include/linux/leds.h b/include/linux/leds.h
> index 0287ab2..a794817 100644
> --- a/include/linux/leds.h
> +++ b/include/linux/leds.h
> @@ -13,12 +13,14 @@
> #define __LINUX_LEDS_H_INCLUDED
>
> #include <linux/list.h>
> -#include <linux/spinlock.h>
> +#include <linux/mutex.h>
> #include <linux/rwsem.h>
> +#include <linux/spinlock.h>
> #include <linux/timer.h>
> #include <linux/workqueue.h>
>
> struct device;
> +struct led_flash;
> /*
> * LED Core
> */
> @@ -29,6 +31,28 @@ enum led_brightness {
> LED_FULL = 255,
> };
>
> +/*
> + * This structure is required in two cases:
> + * - it defines allowed levels of flash leds brightness and timeout
> + * - it provides initialization data for V4L2 Flash controls
> + * when CONFIG_V4L2_FLASH is enabled and allows for proper
> + * enum led_brightness <-> microamperes conversion for the
> + * V4L2_CID_FLASH_TORCH_INTENSITY
> + */
> +struct led_ctrl {

What about moving this to V4L2 flash driver and the name "led_ctrl" is
not related to val cramp.

In LED subsystem we are using brightness, so it's better do the
conversion or ctrl in V4L2 and pass the right brightness value to LED
subsystem.

> + /* maximum allowed value */
> + u32 min;
> + /* maximum allowed value */
> + u32 max;
> + /* step value */
> + u32 step;
> + /*
> + * Default value for V4L2 controls and for flash leds
> + * it also serves for caching the value currently set.
> + */
> + u32 val;
> +};
> +
> struct led_classdev {
> const char *name;
> int brightness;
> @@ -42,6 +66,7 @@ struct led_classdev {
> #define LED_BLINK_ONESHOT (1 << 17)
> #define LED_BLINK_ONESHOT_STOP (1 << 18)
> #define LED_BLINK_INVERT (1 << 19)
> +#define LED_SYSFS_LOCK (1 << 21)
>
> /* Set LED brightness level */
> /* Must not sleep, use a workqueue if needed */
> @@ -69,6 +94,17 @@ struct led_classdev {
> unsigned long blink_delay_on, blink_delay_off;
> struct timer_list blink_timer;
> int blink_brightness;
> + struct led_flash *flash;
> + void (*flash_resume)(struct led_classdev *led_cdev);
> +#ifdef CONFIG_V4L2_FLASH
> + /* Initialization data for the V4L2_CID_FLASH_TORCH_INTENSITY control */
> + struct led_ctrl brightness_ctrl;
> +#endif

Move it to V4L2 Flash

> + /*
> + * Ensures consistent LED sysfs access and protects
> + * LED sysfs locking mechanism
> + */
> + struct mutex led_lock;
>
I guess we can split it out as a new patch.

> struct work_struct set_brightness_work;
> int delayed_set_value;
> @@ -139,6 +175,18 @@ extern void led_blink_set_oneshot(struct led_classdev *led_cdev,
> extern void led_set_brightness(struct led_classdev *led_cdev,
> enum led_brightness brightness);
>
> +/**
> + * led_sysfs_is_locked
> + * @led_cdev: the LED to query
> + *
> + * Returns: true if the sysfs interface of the led is disabled,
> + * false otherwise
> + */
> +static inline bool led_sysfs_is_locked(struct led_classdev *led_cdev)
> +{
> + return led_cdev->flags & LED_SYSFS_LOCK;
> +}
> +

Why we need this lock check? We just do mutex_lock() which do lock
check definitely.

> /*
> * LED Triggers
> */
> diff --git a/include/linux/leds_flash.h b/include/linux/leds_flash.h
> new file mode 100644
> index 0000000..0f885a0
> --- /dev/null
> +++ b/include/linux/leds_flash.h
> @@ -0,0 +1,252 @@
> +/*
> + * Flash leds API
> + *
> + * 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 <media/v4l2-flash.h>
> +#include <linux/leds.h>
> +
> +/*
> + * Supported led fault bits - must be kept in synch
> + * with V4L2_FLASH_FAULT bits.
> + */
> +#define LED_FAULT_OVER_VOLTAGE (1 << 0)
> +#define LED_FAULT_TIMEOUT (1 << 1)
> +#define LED_FAULT_OVER_TEMPERATURE (1 << 2)
> +#define LED_FAULT_SHORT_CIRCUIT (1 << 3)
> +#define LED_FAULT_OVER_CURRENT (1 << 4)
> +#define LED_FAULT_INDICATOR (1 << 5)
> +#define LED_FAULT_UNDER_VOLTAGE (1 << 6)
> +#define LED_FAULT_INPUT_VOLTAGE (1 << 7)
> +#define LED_FAULT_LED_OVER_TEMPERATURE (1 << 8)
> +

Probably we can just reuse the predefined V4L2_FLASH_FAULT bits.
Or V4L2_FLASH_FAULT use LED_FLASH_FAULT bit definitions, but don't
need to copy and paste.

> +#define LED_FLASH_MAX_SYSFS_GROUPS 3
> +
> +struct led_flash_ops {
> + /* set flash brightness */
> + int (*brightness_set)(struct led_classdev *led_cdev,
> + u32 brightness);
> + /* get flash brightness */
> + int (*brightness_get)(struct led_classdev *led_cdev, u32 *brightness);
> + /* set flash indicator brightness */
> + int (*indicator_brightness_set)(struct led_classdev *led_cdev,
> + u32 brightness);
> + /* get flash indicator brightness */
> + int (*indicator_brightness_get)(struct led_classdev *led_cdev,
> + u32 *brightness);
> + /* setup flash strobe */
> + int (*strobe_set)(struct led_classdev *led_cdev,
> + bool state);
> + /* get flash strobe state */
> + int (*strobe_get)(struct led_classdev *led_cdev);
> + /* setup flash timeout */
> + int (*timeout_set)(struct led_classdev *led_cdev,
> + u32 timeout);
> + /* setup strobing the flash from external source */
> + int (*external_strobe_set)(struct led_classdev *led_cdev,
> + bool enable);
> + /* get the flash LED fault */
> + int (*fault_get)(struct led_classdev *led_cdev,
> + u32 *fault);
> +};
> +
> +struct led_flash {
> + /*
> + * 1 - add support for both flash and torch leds,
> + * 0 - handle only torch led
> + */
> + bool has_flash_led;
> + /* flash led specific ops */
> + const struct led_flash_ops ops;
> +
> + /* flash sysfs groups */
> + struct attribute_group *sysfs_groups[LED_FLASH_MAX_SYSFS_GROUPS];
> +
> + /* flash brightness value in microamperes along with its constraints */
> + struct led_ctrl brightness;
> +
> + /* timeout value in microseconds along with its constraints */
> + struct led_ctrl timeout;
> +
> + /*
> + * Indicator brightness value in microamperes along with
> + * its constraints - this is an optional control and must
> + * be allocated by the driver if the device supports privacy
> + * indicator led.
> + */
> + struct led_ctrl *indicator_brightness;
> +
> + /*
> + * determines whether device supports external
> + * flash strobe sources
> + */
> + bool has_external_strobe;
> +
> + /*
> + * If true the device doesn't strobe the flash immediately
> + * after writing 1 to the flash_strobe file, but waits
> + * for an external signal.
> + */
> + bool external_strobe;
> +
> +#ifdef CONFIG_V4L2_FLASH
> + /* V4L2 Flash sub-device data */
> + struct v4l2_flash v4l2_flash;
> +
> + /* flash fault bits that may be set by the device */
> + u32 fault_flags;
> +#endif
> +

Let's consider move all this V4L2 Flash driver related stuff to V4L2
and treat LED Flash Class as a base struct and API. V4L2 Flash driver
can create a struct contains LED Flash struct and wrap the API.
Anyway, LED subsystem should be not just for V4L2 usage and but
provide good API for other subsystems using.

> +};
> +
> +/**
> + * led_classdev_flash_register - register a new object of led_classdev class
> + with support for flash LEDs
> + * @parent: the device to register
> + * @led_cdev: the led_classdev structure for this device
> + *
> + * Returns: 0 on success, error code on failure.
> + */
> +int led_classdev_flash_register(struct device *parent,
> + struct led_classdev *led_cdev);
> +
> +/**
> + * led_classdev_flash_unregister - unregisters an object of led_properties class
> + with support for flash LEDs
> + * @led_cdev: the flash led device to unregister
> + *
> + * Unregisters a previously registered via led_classdev_flash_register object
> + */
> +void led_classdev_flash_unregister(struct led_classdev *led_cdev);
> +
> +/**
> + * led_set_flash_strobe - setup flash strobe
> + * @led_cdev: the flash LED to set strobe on
> + * @state: 1 - strobe flash, 0 - stop flash strobe
> + *
> + * Setup flash strobe - trigger flash strobe
> + *
> + * Returns: 0 on success or negative error value on failure
> + */
> +extern int led_set_flash_strobe(struct led_classdev *led_cdev, bool state);
> +
> +/**
> + * led_get_flash_strobe - get flash strobe status
> + * @led_cdev: the LED to query
> + *
> + * Check whether the flash is strobing at the moment or not.
> + *
> + * Returns: flash strobe status (0 or 1) on success or negative
> + * error value on failure.
> + */
> +extern int led_get_flash_strobe(struct led_classdev *led_cdev);
> +
> +/**
> + * led_set_flash_brightness - set flash LED brightness
> + * @led_cdev: the LED to set
> + * @brightness: the brightness to set it to
> + *
> + * Returns: 0 on success, -EINVAL on failure
> + *
> + * Set a flash LED's brightness.
> + */
> +extern int led_set_flash_brightness(struct led_classdev *led_cdev,
> + u32 brightness);
> +
> +/**
> + * led_update_flash_brightness - update flash LED brightness
> + * @led_cdev: the 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 *led_cdev);
> +
> +/**
> + * led_set_flash_timeout - set flash LED timeout
> + * @led_cdev: the LED to set
> + * @timeout: the flash timeout to set it to
> + *
> + * Returns: 0 on success, -EINVAL on failure
> + *
> + * 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.
> + */
> +extern int led_set_flash_timeout(struct led_classdev *led_cdev,
> + u32 timeout);
> +
> +/**
> + * led_get_flash_fault - get the flash LED fault
> + * @led_cdev: the LED to query
> + * @fault: bitmask containing flash faults
> + *
> + * Returns: 0 on success, -EINVAL on failure
> + *
> + * Get the flash LED fault.
> + */
> +extern int led_get_flash_fault(struct led_classdev *led_cdev,
> + u32 *fault);
> +
> +/**
> + * led_set_external_strobe - set the flash LED external_strobe mode
> + * @led_cdev: the LED to set
> + * @enable: the state to set it to
> + *
> + * Returns: 0 on success, -EINVAL on failure
> + *
> + * Enable/disable strobing the flash LED with use of external source
> + */
> +extern int led_set_external_strobe(struct led_classdev *led_cdev, bool enable);
> +
> +/**
> + * led_set_indicator_brightness - set indicator LED brightness
> + * @led_cdev: the LED to set
> + * @brightness: the brightness to set it to
> + *
> + * Returns: 0 on success, -EINVAL on failure
> + *
> + * Set a flash LED's brightness.
> + */
> +extern int led_set_indicator_brightness(struct led_classdev *led_cdev,
> + u32 led_brightness);
> +
> +/**
> + * led_update_indicator_brightness - update flash indicator LED brightness
> + * @led_cdev: the LED to query
> + *
> + * Get a flash indicator LED's current brightness and update
> + * led_flash->indicator_brightness member with the obtained value.
> + *
> + * Returns: 0 on success or negative error value on failure
> + */
> +extern int led_update_indicator_brightness(struct led_classdev *led_cdev);
> +
> +/**
> + * led_sysfs_lock - lock LED sysfs interface
> + * @led_cdev: the LED to set
> + *
> + * Lock the LED's sysfs interface
> + */
> +extern void led_sysfs_lock(struct led_classdev *led_cdev);
> +
> +/**
> + * led_sysfs_unlock - unlock LED sysfs interface
> + * @led_cdev: the LED to set
> + *
> + * Unlock the LED's sysfs interface
> + */
> +extern void led_sysfs_unlock(struct led_classdev *led_cdev);
> +
> +#endif /* __LINUX_FLASH_LEDS_H_INCLUDED */
> --
> 1.7.9.5
>

I don't have big objections to these APIs. But I'd like to invite Milo
to review this Flash related APIs.

Milo, do you think these Flash APIs are good enough for your LED Flash devices?

Thanks,
-Bryan

2014-04-28 11:25:17

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 5/5] media: Add registration helpers for V4L2 flash sub-devices

Hi Sakari,

On 04/23/2014 05:24 PM, Sakari Ailus wrote:
> Hi Jacek,
>
> On Thu, Apr 17, 2014 at 10:26:44AM +0200, Jacek Anaszewski wrote:
>> Hi Sakari,
>>
>> Thanks for the review.
>>
>> On 04/16/2014 08:21 PM, Sakari Ailus wrote:
>>> Hi Jacek,
>>>
>>> Thanks for the update!
>>>
>> [...]
>>>> +static inline enum led_brightness v4l2_flash_intensity_to_led_brightness(
>>>> + struct led_ctrl *config,
>>>> + u32 intensity)
>>>
>>> Fits on a single line.
>>>
>>>> +{
>>>> + return intensity / config->step;
>>>
>>> Shouldn't you first decrement the minimum before the division?
>>
>> Brightness level 0 means that led is off. Let's consider following case:
>>
>> intensity - 15625
>> config->step - 15625
>> intensity / config->step = 1 (the lowest possible current level)
>
> In V4L2 controls the minimum is not off, and zero might not be a possible
> value since minimum isn't divisible by step.
>
> I wonder how to best take that into account.

I've assumed that in MODE_TORCH a led is always on. Switching
the mode to MODE_FLASH or MODE_OFF turns the led off.
This way we avoid the problem with converting 0 uA value to
led_brightness, as available torch brightness levels start from
the minimum current level value and turning the led off is
accomplished on transition to MODE_OFF or MODE_FLASH, by
calling brightness_set op with led_brightness = 0.

[...]

>>>> +/*
>>>> + * 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 *led_cdev = v4l2_flash->led_cdev;
>>>> +
>>>> + mutex_lock(&led_cdev->led_lock);
>>>> + v4l2_call_flash_op(sysfs_lock, led_cdev);
>>>
>>> Have you thought about device power management yet?
>>
>> Having in mind that the V4L2 Flash sub-device is only a wrapper
>> for LED driver, shouldn't power management be left to the
>> drivers?
>
> How does the LED controller driver know it needs to power the device up in
> that case?
>
> I think an s_power() op which uses PM runtime to set the power state until
> V4L2 sub-device switches to it should be enough. But I'm fine leaving it out
> from this patchset.
>

This solution looks reasonable.

Regards,
Jacek Anaszewski

2014-04-28 11:27:33

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 3/5] leds: Add support for max77693 mfd flash cell

Hi Sakari,

On 04/23/2014 05:52 PM, Sakari Ailus wrote:
> Hi Jacek,
>
> Thanks for the answers to my comments! :-)
>
> On Thu, Apr 17, 2014 at 11:23:06AM +0200, Jacek Anaszewski wrote:
>> On 04/16/2014 07:26 PM, Sakari Ailus wrote:
>>> Hi Jacek,
>>>
>>> Thanks for the patch! Comments below.
>>>
>>> On Fri, Apr 11, 2014 at 04:56:54PM +0200, 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 or through V4L2 subdevice when the support
>>>> for V4L2 Flash sub-devices is enabled. Device supports up to
>>>> two leds which can work in flash and torch mode. Leds can
>>>> be triggered externally or by software.
>>>>
>>>> Signed-off-by: Andrzej Hajda <[email protected]>
>>>> Signed-off-by: Jacek Anaszewski <[email protected]>
>>>> Acked-by: Kyungmin Park <[email protected]>
>>>> Cc: Bryan Wu <[email protected]>
>>>> Cc: Richard Purdie <[email protected]>
>>>> Cc: SangYoung Son <[email protected]>
>>>> Cc: Samuel Ortiz <[email protected]>
>>>> Cc: Lee Jones <[email protected]>
>>>> ---
>>>> drivers/leds/Kconfig | 10 +
>>>> drivers/leds/Makefile | 1 +
>>>> drivers/leds/leds-max77693.c | 794 ++++++++++++++++++++++++++++++++++++++++++
>>>> drivers/mfd/max77693.c | 2 +-
>>>> include/linux/mfd/max77693.h | 38 ++
>>>> 5 files changed, 844 insertions(+), 1 deletion(-)
>>>> create mode 100644 drivers/leds/leds-max77693.c
>>>>
>>>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>>>> index 1e1c81f..b2152a6 100644
>>>> --- a/drivers/leds/Kconfig
>>>> +++ b/drivers/leds/Kconfig
>>>> @@ -462,6 +462,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 8861b86..64f6234 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..979736c
>>>> --- /dev/null
>>>> +++ b/drivers/leds/leds-max77693.c
>>>> @@ -0,0 +1,794 @@
>>>> +/*
>>>> + * Copyright (C) 2014, Samsung Electronics Co., Ltd.
>>>> + *
>>>> + * Authors: Andrzej Hajda <[email protected]>
>>>> + * 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 <asm/div64.h>
>>>> +#include <linux/leds_flash.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/mutex.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/slab.h>
>>>> +#include <media/v4l2-flash.h>
>>>
>>> I guess this should be last in the list.
>>>
>>>> +#include <linux/workqueue.h>
>>>> +#include <linux/mfd/max77693.h>
>>>> +#include <linux/mfd/max77693-private.h>
>>>> +
>>>> +#define MAX77693_LED_NAME "max77693-flash"
>>>> +
>>>> +#define MAX77693_TORCH_IOUT_BITS 4
>>>> +
>>>> +#define MAX77693_TORCH_NO_TIMER 0x40
>>>> +#define MAX77693_FLASH_TIMER_LEVEL 0x80
>>>> +
>>>> +#define MAX77693_FLASH_EN_OFF 0
>>>> +#define MAX77693_FLASH_EN_FLASH 1
>>>> +#define MAX77693_FLASH_EN_TORCH 2
>>>> +#define MAX77693_FLASH_EN_ON 3
>>>> +
>>>> +#define MAX77693_FLASH_EN1_SHIFT 6
>>>> +#define MAX77693_FLASH_EN2_SHIFT 4
>>>> +#define MAX77693_TORCH_EN1_SHIFT 2
>>>> +#define MAX77693_TORCH_EN2_SHIFT 0
>>>> +
>>>> +#define MAX77693_FLASH_LOW_BATTERY_EN 0x80
>>>> +
>>>> +#define MAX77693_FLASH_BOOST_FIXED 0x04
>>>> +#define MAX77693_FLASH_BOOST_LEDNUM_2 0x80
>>>> +
>>>> +#define MAX77693_FLASH_TIMEOUT_MIN 62500
>>>> +#define MAX77693_FLASH_TIMEOUT_MAX 1000000
>>>> +#define MAX77693_FLASH_TIMEOUT_STEP 62500
>>>> +
>>>> +#define MAX77693_TORCH_TIMEOUT_MIN 262000
>>>> +#define MAX77693_TORCH_TIMEOUT_MAX 15728000
>>>> +
>>>> +#define MAX77693_FLASH_IOUT_MIN 15625
>>>> +#define MAX77693_FLASH_IOUT_MAX_1LED 1000000
>>>> +#define MAX77693_FLASH_IOUT_MAX_2LEDS 625000
>>>> +#define MAX77693_FLASH_IOUT_STEP 15625
>>>> +
>>>> +#define MAX77693_TORCH_IOUT_MIN 15625
>>>> +#define MAX77693_TORCH_IOUT_MAX 250000
>>>> +#define MAX77693_TORCH_IOUT_STEP 15625
>>>> +
>>>> +#define MAX77693_FLASH_VSYS_MIN 2400
>>>> +#define MAX77693_FLASH_VSYS_MAX 3400
>>>> +#define MAX77693_FLASH_VSYS_STEP 33
>>>> +
>>>> +#define MAX77693_FLASH_VOUT_MIN 3300
>>>> +#define MAX77693_FLASH_VOUT_MAX 5500
>>>> +#define MAX77693_FLASH_VOUT_STEP 25
>>>> +#define MAX77693_FLASH_VOUT_RMIN 0x0c
>>>> +
>>>> +#define MAX77693_LED_STATUS_FLASH_ON (1 << 3)
>>>> +#define MAX77693_LED_STATUS_TORCH_ON (1 << 2)
>>>> +
>>>> +#define MAX77693_LED_FLASH_INT_FLED2_OPEN (1 << 0)
>>>> +#define MAX77693_LED_FLASH_INT_FLED2_SHORT (1 << 1)
>>>> +#define MAX77693_LED_FLASH_INT_FLED1_OPEN (1 << 2)
>>>> +#define MAX77693_LED_FLASH_INT_FLED1_SHORT (1 << 3)
>>>> +#define MAX77693_LED_FLASH_INT_OVER_CURRENT (1 << 4)
>>>> +
>>>> +#define MAX77693_MODE_OFF 0
>>>> +#define MAX77693_MODE_FLASH (1 << 0)
>>>> +#define MAX77693_MODE_TORCH (1 << 1)
>>>> +#define MAX77693_MODE_FLASH_EXTERNAL (1 << 2)
>>>> +
>>>> +enum {
>>>> + FLASH1,
>>>> + FLASH2,
>>>> + TORCH1,
>>>> + TORCH2
>>>> +};
>>>> +
>>>> +enum {
>>>> + FLASH,
>>>> + TORCH
>>>> +};
>>>> +
>>>> +struct max77693_led {
>>>> + struct regmap *regmap;
>>>> + struct platform_device *pdev;
>>>> + struct max77693_led_platform_data *pdata;
>>>> + struct mutex lock;
>>>> +
>>>> + struct led_classdev ldev;
>>>> +
>>>> + unsigned int torch_brightness;
>>>> + struct work_struct work_brightness_set;
>>>> + unsigned int mode_flags;
>>>> +};
>>>> +
>>>> +static u8 max77693_led_iout_to_reg(u32 ua)
>>>> +{
>>>> + if (ua < MAX77693_FLASH_IOUT_MIN)
>>>> + ua = MAX77693_FLASH_IOUT_MIN;
>>>> + return (ua - MAX77693_FLASH_IOUT_MIN) / MAX77693_FLASH_IOUT_STEP;
>>>> +}
>>>> +
>>>> +static u8 max77693_flash_timeout_to_reg(u32 us)
>>>> +{
>>>> + return (us - MAX77693_FLASH_TIMEOUT_MIN) / MAX77693_FLASH_TIMEOUT_STEP;
>>>> +}
>>>> +
>>>> +static const u32 max77693_torch_timeouts[] = {
>>>> + 262000, 524000, 786000, 1048000,
>>>> + 1572000, 2096000, 2620000, 3144000,
>>>> + 4193000, 5242000, 6291000, 7340000,
>>>> + 9437000, 11534000, 13631000, 1572800
>>>> +};
>>>> +
>>>> +static u8 max77693_torch_timeout_to_reg(u32 us)
>>>> +{
>>>> + int i, b = 0, e = ARRAY_SIZE(max77693_torch_timeouts);
>>>
>>> I haven't run this, but it looks like it'll access max77693_torch_timeouts[]
>>> array after the last element if you pass it a value greater than 1572800.
>>> Shouldn't e be initialised to ARRAY_SIZE() - 1 instead?
>>>
>>>> +
>>>> + while (e - b > 1) {
>>>> + i = b + (e - b) / 2;
>>>> + if (us < max77693_torch_timeouts[i])
>>>> + e = i;
>>>> + else
>>>> + b = i;
>>>> + }
>>>> + return b;
>>>> +}
>>
>> Let's track this case:
>>
>> us = 1600000
>> e = 16 (ARRAY_SIZE(max77693_torch_timeouts))
>>
>> 1st iteration:
>> while (16 - 0 > 1) {
>> i = 0 + (16 - 0) / 2 //= 8
>> b = 8
>> }
>>
>> 2nd iteration:
>> while (16 - 8 > 1) {
>> i = 8 + (16 - 8) / 2 //= 12
>> b = 12
>> }
>>
>> 3rd iteration:
>> while (16 - 12 > 1) {
>> i = 12 + (16 - 12) / 2 //= 14
>> b = 14
>> }
>>
>> 4th iteration:
>> while (16 - 14 > 1) {
>> i = 14 + (16 - 14) / 2 //= 15
>> b = 15
>> }
>>
>> 5th iteration:
>> while (16 - 15 > 1) { //false
>
> Indeed, you're right -- "> 1" is the crucial part that I missed.
>
>> }
>>
>> return b (15) - last element in the array
>>
>>
>>>> +static struct max77693_led *ldev_to_led(struct led_classdev *ldev)
>>>> +{
>>>> + return container_of(ldev, struct max77693_led, ldev);
>>>> +}
>>>> +
>>>> +static u32 max77693_torch_timeout_from_reg(u8 reg)
>>>> +{
>>>
>>> I might limit reg to ARRAY_SIZE(...) as well. Up to you.
>>
>> It is already aligned during validation of platform data.
>
> Ack.
>
>>>> + return max77693_torch_timeouts[reg];
>>>> +}
>>>> +
>>>> +static u8 max77693_led_vsys_to_reg(u32 mv)
>>>> +{
>>>> + return ((mv - MAX77693_FLASH_VSYS_MIN) / MAX77693_FLASH_VSYS_STEP) << 2;
>>>> +}
>>>> +
>>>> +static u8 max77693_led_vout_to_reg(u32 mv)
>>>> +{
>>>> + return (mv - MAX77693_FLASH_VOUT_MIN) / MAX77693_FLASH_VOUT_STEP +
>>>> + MAX77693_FLASH_VOUT_RMIN;
>>>> +}
>>>> +
>>>> +/* split composite current @i into two @iout according to @imax weights */
>>>
>>> Are there dependencies between the two LEDs or are they entirely
>>> independent? If they're independent (with the possible exception of strobe),
>>> then I'd expose them individually.
>>
>> They are independent.
>>
>>>> +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 / MAX77693_FLASH_IOUT_STEP * MAX77693_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 & MAX77693_MODE_TORCH) {
>>>> + if (p->trigger[TORCH1] & MAX77693_LED_TRIG_SOFT)
>>>> + v |= MAX77693_FLASH_EN_ON << MAX77693_TORCH_EN1_SHIFT;
>>>> + if (p->trigger[TORCH2] & MAX77693_LED_TRIG_SOFT)
>>>> + v |= MAX77693_FLASH_EN_ON << MAX77693_TORCH_EN2_SHIFT;
>>>> + }
>>>> +
>>>> + if (mode & MAX77693_MODE_FLASH) {
>>>> + if (p->trigger[FLASH1] & MAX77693_LED_TRIG_SOFT)
>>>> + v |= MAX77693_FLASH_EN_ON << MAX77693_FLASH_EN1_SHIFT;
>>>> + if (p->trigger[FLASH2] & MAX77693_LED_TRIG_SOFT)
>>>> + v |= MAX77693_FLASH_EN_ON << MAX77693_FLASH_EN2_SHIFT;
>>>> + } else if (mode & MAX77693_MODE_FLASH_EXTERNAL) {
>>>> + if (p->trigger[FLASH1] & MAX77693_LED_TRIG_EXT)
>>>> + v |= MAX77693_FLASH_EN_FLASH << MAX77693_FLASH_EN1_SHIFT;
>>>> + if (p->trigger[FLASH2] & MAX77693_LED_TRIG_EXT)
>>>> + v |= MAX77693_FLASH_EN_FLASH << MAX77693_FLASH_EN2_SHIFT;
>>>> + /*
>>>> + * 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[TORCH1] & MAX77693_LED_TRIG_EXT)
>>>> + v |= MAX77693_FLASH_EN_TORCH << MAX77693_TORCH_EN1_SHIFT;
>>>> + if (p->trigger[TORCH2] & MAX77693_LED_TRIG_EXT)
>>>> + v |= MAX77693_FLASH_EN_TORCH << MAX77693_TORCH_EN2_SHIFT;
>>>> + }
>>>> +
>>>> + /* Reset the register only prior setting flash modes */
>>>> + if (mode != MAX77693_MODE_TORCH) {
>>>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_EN, 0);
>>>
>>> A single wrie for strobe. Looks good!
>>>
>>>> + if (ret < 0)
>>>> + return ret;
>>>> + }
>>>> +
>>>> + return max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_EN, v);
>>>> +}
>>>> +
>>>> +static int max77693_add_mode(struct max77693_led *led, unsigned int mode)
>>>> +{
>>>> + int ret;
>>>> +
>>>> + /* Once enabled torch mode is active until turned off */
>>>> + if ((mode == MAX77693_MODE_TORCH) &&
>>>> + (led->mode_flags & MAX77693_MODE_TORCH))
>>>> + return 0;
>>>> +
>>>> + /*
>>>> + * FLASH_EXTERNAL mode activates HW triggered flash and torch
>>>> + * modes in the device. The related register settings interfere
>>>> + * with SW triggerred modes, thus clear them to ensure proper
>>>> + * device configuration.
>>>> + */
>>>> + if (mode == MAX77693_MODE_FLASH_EXTERNAL)
>>>> + led->mode_flags &= (~MAX77693_MODE_TORCH &
>>>> + ~MAX77693_MODE_FLASH);
>>>> +
>>>> + 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
>>>> + * spurous flash strobing on each successive torch mode
>>>> + * setting.
>>>> + */
>>>> + if ((mode == MAX77693_MODE_FLASH) ||
>>>> + (mode == MAX77693_MODE_FLASH_EXTERNAL))
>>>> + led->mode_flags &= ~mode;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int max77693_clear_mode(struct max77693_led *led, unsigned int mode)
>>>> +{
>>>> + led->mode_flags &= ~mode;
>>>> +
>>>> + return max77693_set_mode(led, led->mode_flags);
>>>> +}
>>>> +
>>>> +static int max77693_set_torch_current(struct max77693_led *led,
>>>> + u32 micro_amp)
>>>> +{
>>>> + struct max77693_led_platform_data *p = led->pdata;
>>>> + struct regmap *rmap = led->regmap;
>>>> + u32 iout[2];
>>>> + u8 v, iout1_reg, iout2_reg;
>>>> + int ret;
>>>> +
>>>> + max77693_calc_iout(iout, micro_amp, &p->iout[TORCH1]);
>>>> + iout1_reg = max77693_led_iout_to_reg(iout[0]);
>>>> + iout2_reg = max77693_led_iout_to_reg(iout[1]);
>>>> +
>>>> + v = iout1_reg | (iout2_reg << MAX77693_TORCH_IOUT_BITS);
>>>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCH, v);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int max77693_set_flash_current(struct max77693_led *led,
>>>> + u32 micro_amp)
>>>> +{
>>>> + struct max77693_led_platform_data *p = led->pdata;
>>>> + struct regmap *rmap = led->regmap;
>>>> + u32 iout[2];
>>>> + u8 iout1_reg, iout2_reg;
>>>> + int ret;
>>>> +
>>>> + max77693_calc_iout(iout, micro_amp, &p->iout[FLASH1]);
>>>> + iout1_reg = max77693_led_iout_to_reg(iout[0]);
>>>> + iout2_reg = max77693_led_iout_to_reg(iout[1]);
>>>> +
>>>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH1, iout1_reg);
>>>> + if (ret < 0)
>>>> + goto error_ret;
>>>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_IFLASH2, iout2_reg);
>>>> + if (ret < 0)
>>>> + goto error_ret;
>>>> +
>>>> +error_ret:
>>>> + 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;
>>>> +
>>>> + v = max77693_flash_timeout_to_reg(timeout);
>>>> +
>>>> + if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
>>>> + v |= MAX77693_FLASH_TIMER_LEVEL;
>>>> +
>>>> + return max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
>>>> +}
>>>> +
>>>> +static int max77693_strobe_status_get(struct max77693_led *led)
>>>> +{
>>>> + struct regmap *rmap = led->regmap;
>>>> + u8 v;
>>>> + int ret;
>>>> +
>>>> + ret = max77693_read_reg(rmap, MAX77693_LED_REG_FLASH_INT_STATUS, &v);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + return !!(v & MAX77693_LED_STATUS_FLASH_ON);
>>>> +}
>>>> +
>>>> +static int max77693_int_flag_get(struct max77693_led *led, u8 *v)
>>>> +{
>>>> + struct regmap *rmap = led->regmap;
>>>> +
>>>> + return max77693_read_reg(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 ret;
>>>> + u8 v;
>>>> +
>>>> + ret = max77693_set_torch_current(led, p->iout[TORCH1] +
>>>> + p->iout[TORCH2]);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + ret = max77693_set_flash_current(led, p->iout[FLASH1] +
>>>> + p->iout[FLASH2]);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + if (p->timeout[TORCH] > 0)
>>>> + v = max77693_torch_timeout_to_reg(p->timeout[TORCH]);
>>>> + else
>>>> + v = MAX77693_TORCH_NO_TIMER;
>>>> + if (p->trigger_type[TORCH] == MAX77693_LED_TRIG_TYPE_LEVEL)
>>>> + v |= MAX77693_FLASH_TIMER_LEVEL;
>>>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_ITORCHTIMER, v);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + v = max77693_flash_timeout_to_reg(p->timeout[FLASH]);
>>>> + if (p->trigger_type[FLASH] == MAX77693_LED_TRIG_TYPE_LEVEL)
>>>> + v |= MAX77693_FLASH_TIMER_LEVEL;
>>>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + if (p->low_vsys > 0)
>>>> + v = max77693_led_vsys_to_reg(p->low_vsys) |
>>>> + MAX77693_FLASH_LOW_BATTERY_EN;
>>>> + else
>>>> + v = 0;
>>>> +
>>>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_MAX_FLASH1, v);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_MAX_FLASH2, 0);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + if (p->boost_mode[FLASH1] == MAX77693_LED_BOOST_FIXED ||
>>>> + p->boost_mode[FLASH2] == MAX77693_LED_BOOST_FIXED)
>>>> + v = MAX77693_FLASH_BOOST_FIXED;
>>>> + else
>>>> + v = p->boost_mode[FLASH1] | (p->boost_mode[FLASH2] << 1);
>>>> + if (p->boost_mode[FLASH1] && p->boost_mode[FLASH2])
>>>> + v |= MAX77693_FLASH_BOOST_LEDNUM_2;
>>>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_VOUT_CNTL, v);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + v = max77693_led_vout_to_reg(p->boost_vout);
>>>> + ret = max77693_write_reg(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + return max77693_set_mode(led, MAX77693_MODE_OFF);
>>>> +}
>>>> +
>>>> +/* LED subsystem callbacks */
>>>> +
>>>> +static void max77693_brightness_set_work(struct work_struct *work)
>>>> +{
>>>> + struct max77693_led *led =
>>>> + container_of(work, struct max77693_led, work_brightness_set);
>>>> + int ret;
>>>> +
>>>> + mutex_lock(&led->lock);
>>>> +
>>>> + if (led->torch_brightness == 0) {
>>>> + ret = max77693_clear_mode(led, MAX77693_MODE_TORCH);
>>>> + 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->torch_brightness *
>>>> + MAX77693_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, MAX77693_MODE_TORCH);
>>>> + if (ret < 0)
>>>> + dev_dbg(&led->pdev->dev, "Failed to set torch mode (%d)\n",
>>>> + ret);
>>>> +unlock:
>>>> + mutex_unlock(&led->lock);
>>>> +}
>>>> +
>>>> +static void max77693_led_brightness_set(struct led_classdev *led_cdev,
>>>> + enum led_brightness value)
>>>> +{
>>>> + struct max77693_led *led = ldev_to_led(led_cdev);
>>>> +
>>>> + led->torch_brightness = value;
>>>> + schedule_work(&led->work_brightness_set);
>>>
>>> Is there a reason not to do this right now (but in a work queue instead)?
>>
>> Almost all the drivers in the LED subsystem do it that way.
>> I think that it is caused by the fact that setting led brightness
>> should be as fast as possible and non-blocking. The led may be
>> used e.g. for HD LED (see ledtrig-ide) and activated many times
>> per second, and thus it could have impact on the system performance
>> if it wasn't run in a work queue.
>
> Fair enough. But the expectation is that the V4L2 control's value has taken
> effect when the set control handler returns. That is also what virtually all
> existing implementations do.
>
> Could this be handled in the LED framework instead so that the V4L2 controls
> would function synchronously?

There could be added an op to the led_flash_ops structure, for setting
led brightness with guaranteed immediate effect, intended for use only
by V4L2 flash sub-devs. The Flash LED driver would have to implement two
ops for setting torch brightness - one for use by led class API,
using work queue, and the other for use by V4L2 flash sub-dev, without
work queue.

>
> I'm ok for postponing this as long as we agree on how it'd be fixed. Perhaps
> someone from the LED framework side to comment.
>
>>>> +}
>>>> +
>>>> +static int max77693_led_flash_strobe_get(struct led_classdev *led_cdev)
>>>> +{
>>>> + struct max77693_led *led = ldev_to_led(led_cdev);
>>>> + int ret;
>>>> +
>>>> + mutex_lock(&led->lock);
>>>> + ret = max77693_strobe_status_get(led);
>>>> + mutex_unlock(&led->lock);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int max77693_led_flash_fault_get(struct led_classdev *led_cdev,
>>>> + u32 *fault)
>>>> +{
>>>> + struct max77693_led *led = ldev_to_led(led_cdev);
>>>> + u8 v;
>>>> + int ret;
>>>> +
>>>> + mutex_lock(&led->lock);
>>>> +
>>>> + ret = max77693_int_flag_get(led, &v);
>>>> + if (ret < 0)
>>>> + goto unlock;
>>>> +
>>>> + *fault = 0;
>>>> +
>>>> + if (v & MAX77693_LED_FLASH_INT_FLED2_OPEN ||
>>>> + v & MAX77693_LED_FLASH_INT_FLED1_OPEN)
>>>> + *fault |= LED_FAULT_OVER_VOLTAGE;
>>>> + if (v & MAX77693_LED_FLASH_INT_FLED2_SHORT ||
>>>> + v & MAX77693_LED_FLASH_INT_FLED1_SHORT)
>>>> + *fault |= LED_FAULT_SHORT_CIRCUIT;
>>>> + if (v & MAX77693_LED_FLASH_INT_OVER_CURRENT)
>>>> + *fault |= LED_FAULT_OVER_CURRENT;
>>>> +unlock:
>>>> + mutex_unlock(&led->lock);
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int max77693_led_flash_strobe_set(struct led_classdev *led_cdev,
>>>> + bool state)
>>>> +{
>>>> + struct max77693_led *led = ldev_to_led(led_cdev);
>>>> + struct led_flash *flash = led_cdev->flash;
>>>> + int ret;
>>>> +
>>>> + mutex_lock(&led->lock);
>>>> +
>>>> + if (flash->external_strobe) {
>>>> + ret = -EBUSY;
>>>> + goto unlock;
>>>> + }
>>>> +
>>>> + if (!state) {
>>>> + ret = max77693_clear_mode(led, MAX77693_MODE_FLASH);
>>>> + goto unlock;
>>>> + }
>>>> +
>>>> + ret = max77693_add_mode(led, MAX77693_MODE_FLASH);
>>>> + if (ret < 0)
>>>> + goto unlock;
>>>> +unlock:
>>>> + mutex_unlock(&led->lock);
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int max77693_led_external_strobe_set(struct led_classdev *led_cdev,
>>>> + bool enable)
>>>> +{
>>>> + struct max77693_led *led = ldev_to_led(led_cdev);
>>>> + int ret;
>>>> +
>>>> + mutex_lock(&led->lock);
>>>> +
>>>> + if (enable)
>>>> + ret = max77693_add_mode(led, MAX77693_MODE_FLASH_EXTERNAL);
>>>> + else
>>>> + ret = max77693_clear_mode(led, MAX77693_MODE_FLASH_EXTERNAL);
>>>> +
>>>> + mutex_unlock(&led->lock);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int max77693_led_flash_brightness_set(struct led_classdev *led_cdev,
>>>> + u32 brightness)
>>>> +{
>>>> + struct max77693_led *led = ldev_to_led(led_cdev);
>>>> + int ret;
>>>> +
>>>> + mutex_lock(&led->lock);
>>>> +
>>>> + ret = max77693_set_flash_current(led, brightness);
>>>> + if (ret < 0)
>>>> + goto unlock;
>>>> +unlock:
>>>> + mutex_unlock(&led->lock);
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int max77693_led_flash_timeout_set(struct led_classdev *led_cdev,
>>>> + u32 timeout)
>>>> +{
>>>> + struct max77693_led *led = ldev_to_led(led_cdev);
>>>> + int ret;
>>>> +
>>>> + mutex_lock(&led->lock);
>>>> +
>>>> + ret = max77693_set_timeout(led, timeout);
>>>> + if (ret < 0)
>>>> + goto unlock;
>>>> +
>>>> +unlock:
>>>> + mutex_unlock(&led->lock);
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static void max77693_led_parse_dt(struct max77693_led_platform_data *p,
>>>> + struct device_node *node)
>>>> +{
>>>> + of_property_read_u32_array(node, "maxim,iout", p->iout, 4);
>>>
>>> How about separate current for flash and torch modes? They are the same
>>> LEDs; just the mode is different.
>>
>> There are separate currents - 2 for torch and 2 for flash mode.
>
> True. But shouldn't they be two different properties as, well, these are
> different properties of individual hardware devices? :-)

Sure. I will split them.

>>>> + of_property_read_u32_array(node, "maxim,trigger", p->trigger, 4);
>>>> + of_property_read_u32_array(node, "maxim,trigger-type", p->trigger_type,
>>>> + 2);
>>>> + of_property_read_u32_array(node, "maxim,timeout", p->timeout, 2);
>>>> + of_property_read_u32_array(node, "maxim,boost-mode", p->boost_mode, 2);
>>>> + of_property_read_u32(node, "maxim,boost-vout", &p->boost_vout);
>>>> + of_property_read_u32(node, "maxim,vsys-min", &p->low_vsys);
>>>
>>> Are these values specific to the maxim chip? I'd suppose e.g. timeout and
>>> iout are something that can be found pretty much in any flash controller.
>>
>> Besides the two they are specific. And what with timeout and iout
>> if they are common for all flash controllers?
>
> They should be defined in a way which is not specific to the chip itself.
> That would also change the property names. I'm not sure how much of this is
> already done on the LED side.

I've missed Documentation/devicetree/bindings/leds/common.txt.
The iout and timeout properties could be added there.

>>>> +}
>>>> +
>>>> +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;
>>>> +
>>>> + for (i = 0; i < 2; ++i)
>>>
>>> How about using ARRAY_SIZE() here, too?
>>
>> OK.
>>
>>>
>>>> + clamp_align(&p->boost_mode[i], MAX77693_LED_BOOST_NONE,
>>>> + MAX77693_LED_BOOST_FIXED, 1);
>>>> + /* boost, if enabled, should be the same on both leds */
>>>> + if (p->boost_mode[0] != MAX77693_LED_BOOST_NONE &&
>>>> + p->boost_mode[1] != MAX77693_LED_BOOST_NONE)
>>>> + p->boost_mode[1] = p->boost_mode[0];
>>>> +
>>>> + max = (p->boost_mode[FLASH1] && p->boost_mode[FLASH2]) ?
>>>> + MAX77693_FLASH_IOUT_MAX_2LEDS : MAX77693_FLASH_IOUT_MAX_1LED;
>>>> +
>>>> + clamp_align(&p->iout[FLASH1], MAX77693_FLASH_IOUT_MIN,
>>>> + max, MAX77693_FLASH_IOUT_STEP);
>>>> + clamp_align(&p->iout[FLASH2], MAX77693_FLASH_IOUT_MIN,
>>>> + max, MAX77693_FLASH_IOUT_STEP);
>>>> + clamp_align(&p->iout[TORCH1], MAX77693_TORCH_IOUT_MIN,
>>>> + MAX77693_TORCH_IOUT_MAX, MAX77693_TORCH_IOUT_STEP);
>>>> + clamp_align(&p->iout[TORCH2], MAX77693_TORCH_IOUT_MIN,
>>>> + MAX77693_TORCH_IOUT_MAX, MAX77693_TORCH_IOUT_STEP);
>>>> +
>>>> + for (i = 0; i < 4; ++i)
>>>> + clamp_align(&p->trigger[i], 0, 7, 1);
>
> You can just use clamp() here. Same elsewhere where step == 1.
>
>>>> + for (i = 0; i < 2; ++i)
>>>> + clamp_align(&p->trigger_type[i], MAX77693_LED_TRIG_TYPE_EDGE,
>>>> + MAX77693_LED_TRIG_TYPE_LEVEL, 1);
>>>
>>> ARRAY_SIZE() would be nicer than using numeric values for the loop
>>> condition.
>>>
>>>> + clamp_align(&p->timeout[FLASH], MAX77693_FLASH_TIMEOUT_MIN,
>>>> + MAX77693_FLASH_TIMEOUT_MAX, MAX77693_FLASH_TIMEOUT_STEP);
>>>> +
>>>> + if (p->timeout[TORCH]) {
>>>> + clamp_align(&p->timeout[TORCH], MAX77693_TORCH_TIMEOUT_MIN,
>>>> + MAX77693_TORCH_TIMEOUT_MAX, 1);
>>>> + p->timeout[TORCH] = max77693_torch_timeout_from_reg(
>>>> + max77693_torch_timeout_to_reg(p->timeout[TORCH]));
>>>> + }
>>>> +
>>>> + clamp_align(&p->boost_vout, MAX77693_FLASH_VOUT_MIN,
>>>> + MAX77693_FLASH_VOUT_MAX, MAX77693_FLASH_VOUT_STEP);
>>>> +
>>>> + if (p->low_vsys) {
>
> Extra braces.
>
>>>> + clamp_align(&p->low_vsys, MAX77693_FLASH_VSYS_MIN,
>>>> + MAX77693_FLASH_VSYS_MAX, MAX77693_FLASH_VSYS_STEP);
>>>> + }
>>>> +}
>>>> +
>>>> +static int max77693_led_get_platform_data(struct max77693_led *led)
>>>> +{
>>>> + struct max77693_led_platform_data *p;
>>>> + struct device *dev = &led->pdev->dev;
>>>> +
>>>> + if (dev->of_node) {
>>>> + p = devm_kzalloc(dev, sizeof(*led->pdata), GFP_KERNEL);
>>>> + if (!p)
>>>> + return -ENOMEM;
>>>
>>> Check for p can be moved out of the if as it's the same for both.
>>>
>>> You could also use led->pdata directly. Up to you.
>>>
>>>> + max77693_led_parse_dt(p, dev->of_node);
>>>> + } else {
>>>> + p = dev_get_platdata(dev);
>>>> + if (!p)
>>>> + return -ENODEV;
>>>> + }
>>>> + led->pdata = p;
>>>> +
>>>> + max77693_led_validate_platform_data(p);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static struct led_flash led_flash = {
>>>> + .ops = {
>>>> + .brightness_set = max77693_led_flash_brightness_set,
>>>> + .strobe_set = max77693_led_flash_strobe_set,
>>>> + .strobe_get = max77693_led_flash_strobe_get,
>>>> + .timeout_set = max77693_led_flash_timeout_set,
>>>> + .external_strobe_set = max77693_led_external_strobe_set,
>>>> + .fault_get = max77693_led_flash_fault_get,
>>>> + },
>>>> + .has_flash_led = true,
>>>> +};
>>>> +
>>>> +static void max77693_init_led_controls(struct led_classdev *led_cdev,
>>>> + struct max77693_led_platform_data *p)
>>>> +{
>>>> + struct led_flash *flash = led_cdev->flash;
>>>> + struct led_ctrl *c;
>>>> +
>>>> + /*
>>>> + * brightness_ctrl and fault_flags are used only
>>>> + * for initializing related V4L2 controls.
>>>> + */
>>>> +#ifdef CONFIG_V4L2_FLASH
>>>> + flash->fault_flags = V4L2_FLASH_FAULT_OVER_VOLTAGE |
>>>> + V4L2_FLASH_FAULT_SHORT_CIRCUIT |
>>>> + V4L2_FLASH_FAULT_OVER_CURRENT;
>>>> +
>>>> + c = &led_cdev->brightness_ctrl;
>>>> + c->min = (p->iout[TORCH1] != 0 && p->iout[TORCH2] != 0) ?
>>>> + MAX77693_TORCH_IOUT_MIN * 2 :
>>>> + MAX77693_TORCH_IOUT_MIN;
>>>> + c->max = p->iout[TORCH1] + p->iout[TORCH2];
>>>> + c->step = MAX77693_TORCH_IOUT_STEP;
>>>> + c->val = p->iout[TORCH1] + p->iout[TORCH2];
>>>
>>> Can you control the current for the two flash LEDs separately?
>>
>> Yes.
>>
>>> If yes, this
>>> should be also available on the V4L2 flash API. The lm3560 driver does this,
>>> for example. (It creates two sub-devices since we can only control a single
>>> LED using a single sub-device, at least for the time being.)
>>
>> So, should I propose new V4L2 flash API for controlling more than
>> one led? Probably similar improvement should be applied to the
>> LED subsystem.
>
> As said, the V4L2 API exposes two sub-devices currently. That's just a hack,
> though; I think we need extensions in the V4L2 core API to support this
> properly: controls are per-(sub)device and we certainly do not want controls
> such as V4L2_CID_FLASH2_INTENSITY. But I don't think it's an excuse for e.g.
> LED API not to do it. :-)
>
> One option would be to use a matrix control but I'm not sure how much I like
> that option either: there's nothing in the API that suggests that the index
> is the LED number, for instance. That is still the only realistic
> possibility right now. Actually --- this is what I'd suggest right now. Cc
> Hans.
>
> What I'm worried about is that, as this will affect the user space API in a
> way or another very probably, changing it later on could be a problem. That
> has been proved multiple times and people are often afraid of even trying to
> do so. So if we can think of a way how to meaningfully extend the LED API
> now into this direction and get an acceptance from the LED API developers,
> that would be highly appreciated.
>

As the LED class devices also call led_classdev_register separately
for every led exposed by the device I propose we would stick to
this which would also allow to continue exploiting V4L2 hack and
create separate V4L2 sub-dev for each sub-led.

Regards,
Jacek Anaszewski

2014-04-28 11:53:06

by Jacek Anaszewski

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 1/5] leds: Add sysfs and kernel internal API for flash LEDs

Hi Bryan,

Thanks for the review.

On 04/26/2014 01:17 AM, Bryan Wu wrote:
> On Fri, Apr 11, 2014 at 7:56 AM, Jacek Anaszewski
> <[email protected]> wrote:
>> Some LED devices support two operation modes - torch and
>> flash.
>
> Do we have a method to look up the capabilities from LED devices driver?
> For example, the LED device supports Torch/Flash then LED device
> driver should set a flag like LED_DEV_CAP_TORCH or LED_DEV_CAP_FLASH.
> If it doesn't support those functions, it won't set those flags.

It is assumed that torch led is always available. For declaring
the existence of the flash led there is 'has_flash_led' flag in the
struct led_flash.

> LED Flash class core can check those flags for further usage.
>
>> 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
>> optional external_strobe, indicator_brightness and
>> max_indicator_btightness. All the flash related features
>
> typo here, it should max_indicator_btightness -> max_indicator_brightness
>
>> are placed in a separate module.
>
> Please add one empty line here.
>
>> 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.
>>
>
> I don't quite understand the last sentence here. Looks like the LED
> flash class interface binds to V4L2 Flash sub-device, then why we need
> to export sysfs for user space if the only user is V4L2 which can talk
> through kernel internal API here.

It has been agreed that the two types of interfaces should be available
for the users for operating on LED flash devices. Currently on open
the V4L2 flash sub-device sets the lock flag to disable LED sysfs
interface which was exported when the LED device was created.
Do you suggest that attributes should be removed each time V4L2
takes control of the LED flash device and re-created after
the device is released?

>> 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 | 8 +
>> drivers/leds/Makefile | 1 +
>> drivers/leds/led-class.c | 36 ++-
>> drivers/leds/led-flash.c | 627
> +++++++++++++++++++++++++++++++++++++++++++
>
> If we go for the LED Flash class, I prefer to use led-class-flash.c
> rather than led-flash.c

OK.

>> drivers/leds/led-triggers.c | 16 +-
>> drivers/leds/leds.h | 6 +
>> include/linux/leds.h | 50 +++-
>> include/linux/leds_flash.h | 252 +++++++++++++++++
>
> leds_flash.h -> led-class-flash.h
>
>> 8 files changed, 982 insertions(+), 14 deletions(-)
>> create mode 100644 drivers/leds/led-flash.c
>> create mode 100644 include/linux/leds_flash.h
>>
>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>> index 2062682..1e1c81f 100644
>> --- a/drivers/leds/Kconfig
>> +++ b/drivers/leds/Kconfig
>> @@ -19,6 +19,14 @@ 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 "Flash LEDs Support"
> "LED Flash Class Support"
>
>> + depends on LEDS_CLASS
>> + help
>> + This option enables support for flash LED devices. Say Y if you
>> + want to use flash specific features of a LED device, if they
>> + are supported.
>> +
>
> This help message is not very accurate, please take a look at
> LEDS_CLASS. And I prefer this driver can be a module, so it should be
> mentioned here.

Doesn't 'tristate' property suffice for indicating that the driver
can be a module?

>> comment "LED drivers"
>>
>> config LEDS_88PM860X
>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>> index 3cd76db..8861b86 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-flash.o
>> obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
>>
>> # LED Platform Drivers
>> diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
>> index f37d63c..58f16c3 100644
>> --- a/drivers/leds/led-class.c
>> +++ b/drivers/leds/led-class.c
>> @@ -9,15 +9,16 @@
>> * published by the Free Software Foundation.
>> */
>>
>> -#include <linux/module.h>
>> -#include <linux/kernel.h>
>> +#include <linux/ctype.h>
>> +#include <linux/device.h>
>> +#include <linux/err.h>
>> #include <linux/init.h>
>> +#include <linux/kernel.h>
>> #include <linux/list.h>
>> +#include <linux/module.h>
>> +#include <linux/slab.h>
>> #include <linux/spinlock.h>
>> -#include <linux/device.h>
>> #include <linux/timer.h>
>> -#include <linux/err.h>
>> -#include <linux/ctype.h>
>> #include <linux/leds.h>
>> #include "leds.h"
>>
>
> I believe this change is kind of cleanup, could you please split them
> out? For this patch we just need add those LED Flash class related
> code.

Sure.

>
>> @@ -45,28 +46,38 @@ static ssize_t brightness_store(struct device *dev,
>> {
>> struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> unsigned long state;
>> - ssize_t ret = -EINVAL;
>> + ssize_t ret;
>> +
>> + mutex_lock(&led_cdev->led_lock);
>> +
>> + if (led_sysfs_is_locked(led_cdev)) {
>> + ret = -EBUSY;
>> + goto unlock;
>> + }
>>
>> ret = kstrtoul(buf, 10, &state);
>> if (ret)
>> - return ret;
>> + goto unlock;
>>
>> if (state == LED_OFF)
>> led_trigger_remove(led_cdev);
>> __led_set_brightness(led_cdev, state);
>> + ret = size;
>>
>> - return size;
>> +unlock:
>> + mutex_unlock(&led_cdev->led_lock);
>> + return ret;
>
> Is this change related to some bug or race condition? I failed to find
> any comments about that. Or we need to split it out for further
> discussion.

This modification is required for locking the sysfs interface
when V4L2 flash sub-device is opened.


>> }
>> static DEVICE_ATTR_RW(brightness);
>>
>> -static ssize_t led_max_brightness_show(struct device *dev,
>> +static ssize_t max_brightness_show(struct device *dev,
>> struct device_attribute *attr, char *buf)
>> {
>> struct led_classdev *led_cdev = dev_get_drvdata(dev);
>>
>> return sprintf(buf, "%u\n", led_cdev->max_brightness);
>> }
>> -static DEVICE_ATTR(max_brightness, 0444, led_max_brightness_show, NULL);
>> +static DEVICE_ATTR_RO(max_brightness);
>>
>
> This is cosmetics patch, please split it out.

OK.

>> #ifdef CONFIG_LEDS_TRIGGERS
>> static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
>> @@ -174,6 +185,8 @@ 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);
>> @@ -218,6 +231,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
>> #ifdef CONFIG_LEDS_TRIGGERS
>> init_rwsem(&led_cdev->trigger_lock);
>> #endif
>> + mutex_init(&led_cdev->led_lock);
>> /* add to the list of leds */
>> down_write(&leds_list_lock);
>> list_add_tail(&led_cdev->node, &leds_list);
>> @@ -271,6 +285,8 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
>> down_write(&leds_list_lock);
>> list_del(&led_cdev->node);
>> up_write(&leds_list_lock);
>> +
>> + mutex_destroy(&led_cdev->led_lock);
>> }
>> EXPORT_SYMBOL_GPL(led_classdev_unregister);
>>
>> diff --git a/drivers/leds/led-flash.c b/drivers/leds/led-flash.c
>> new file mode 100644
>> index 0000000..9d482a4
>> --- /dev/null
>> +++ b/drivers/leds/led-flash.c
>> @@ -0,0 +1,627 @@
>> +/*
>> + * LED Class Flash interface
>
> Is LED Class Flash or LED Flash Class? Please make them consistent.
>
>> + *
>> + * 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/module.h>
>> +#include <linux/leds.h>
>> +#include <linux/leds_flash.h>
>> +#include "leds.h"
>> +
>> +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);
>> + unsigned long state;
>> + ssize_t ret;
>> +
>> + mutex_lock(&led_cdev->led_lock);
>> +
>> + if (led_sysfs_is_locked(led_cdev)) {
>> + ret = -EBUSY;
>> + goto unlock;
>> + }
>> +
>> + ret = kstrtoul(buf, 10, &state);
>> + if (ret)
>> + goto unlock;
>> +
>> + ret = led_set_flash_brightness(led_cdev, state);
>> + if (ret < 0)
>> + goto unlock;
>> +
>> + ret = size;
>> +unlock:
>> + mutex_unlock(&led_cdev->led_lock);
>> + 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_flash *flash = led_cdev->flash;
>> +
>> + /* no lock needed for this */
>> + led_update_flash_brightness(led_cdev);
>> +
>> + 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_flash *flash = led_cdev->flash;
>> +
>> + return sprintf(buf, "%u\n", flash->brightness.max);
>> +}
>> +static DEVICE_ATTR_RO(max_flash_brightness);
>> +
>> +static ssize_t indicator_brightness_store(struct device *dev,
>> + struct device_attribute *attr, const char *buf, size_t size)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + unsigned long state;
>> + ssize_t ret;
>> +
>> + mutex_lock(&led_cdev->led_lock);
>> +
>> + if (led_sysfs_is_locked(led_cdev)) {
>> + ret = -EBUSY;
>> + goto unlock;
>> + }
>> +
>> + ret = kstrtoul(buf, 10, &state);
>> + if (ret)
>> + goto unlock;
>> +
>> + ret = led_set_indicator_brightness(led_cdev, state);
>> + if (ret < 0)
>> + goto unlock;
>> +
>> + ret = size;
>> +unlock:
>> + mutex_unlock(&led_cdev->led_lock);
>> + return ret;
>> +}
>> +
>> +static ssize_t indicator_brightness_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_flash *flash = led_cdev->flash;
>> +
>> + /* no lock needed for this */
>> + led_update_indicator_brightness(led_cdev);
>> +
>> + return sprintf(buf, "%u\n", flash->indicator_brightness->val);
>> +}
>> +static DEVICE_ATTR_RW(indicator_brightness);
>> +
>> +static ssize_t max_indicator_brightness_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + struct led_flash *flash = led_cdev->flash;
>> +
>> + return sprintf(buf, "%u\n", flash->indicator_brightness->max);
>> +}
>> +static DEVICE_ATTR_RO(max_indicator_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);
>> + unsigned long state;
>> + ssize_t ret;
>> +
>> + mutex_lock(&led_cdev->led_lock);
>> +
>> + if (led_sysfs_is_locked(led_cdev)) {
>> + ret = -EBUSY;
>> + goto unlock;
>> + }
>> +
>> + ret = kstrtoul(buf, 10, &state);
>> + if (ret)
>> + goto unlock;
>> +
>> + if (state < 0 || state > 1)
>> + return -EINVAL;
>> +
>> + ret = led_set_flash_strobe(led_cdev, state);
>> + if (ret < 0)
>> + goto unlock;
>> + ret = size;
>> +unlock:
>> + mutex_unlock(&led_cdev->led_lock);
>> + 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);
>> + int ret;
>> +
>> + /* no lock needed for this */
>> + ret = led_get_flash_strobe(led_cdev);
>> + if (ret < 0)
>> + return ret;
>> +
>> + return sprintf(buf, "%u\n", ret);
>> +}
>> +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);
>> + unsigned long flash_timeout;
>> + ssize_t ret;
>> +
>> + mutex_lock(&led_cdev->led_lock);
>> +
>> + if (led_sysfs_is_locked(led_cdev)) {
>> + ret = -EBUSY;
>> + goto unlock;
>> + }
>> +
>> + ret = kstrtoul(buf, 10, &flash_timeout);
>> + if (ret)
>> + goto unlock;
>> +
>> + ret = led_set_flash_timeout(led_cdev, flash_timeout);
>> + if (ret < 0)
>> + goto unlock;
>> +
>> + ret = size;
>> +unlock:
>> + mutex_unlock(&led_cdev->led_lock);
>> + 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_flash *flash = led_cdev->flash;
>> +
>> + return sprintf(buf, "%d\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_flash *flash = led_cdev->flash;
>> +
>> + return sprintf(buf, "%d\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);
>> + unsigned int fault;
>> + int ret;
>> +
>> + ret = led_get_flash_fault(led_cdev, &fault);
>> + if (ret < 0)
>> + return -EINVAL;
>> +
>> + return sprintf(buf, "0x%8.8x\n", fault);
>> +}
>> +static DEVICE_ATTR_RO(flash_fault);
>> +
>> +static ssize_t external_strobe_store(struct device *dev,
>> + struct device_attribute *attr, const char *buf, size_t size)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> + unsigned long external_strobe;
>> + ssize_t ret;
>> +
>> + mutex_lock(&led_cdev->led_lock);
>> +
>> + if (led_sysfs_is_locked(led_cdev)) {
>> + ret = -EBUSY;
>> + goto unlock;
>> + }
>> +
>> + ret = kstrtoul(buf, 10, &external_strobe);
>> + if (ret)
>> + goto unlock;
>> +
>> + if (external_strobe > 1) {
>> + ret = -EINVAL;
>> + goto unlock;
>> + }
>> +
>> + ret = led_set_external_strobe(led_cdev, external_strobe);
>> + if (ret < 0)
>> + goto unlock;
>> + ret = size;
>> +unlock:
>> + mutex_unlock(&led_cdev->led_lock);
>> + return ret;
>> +}
>> +
>> +static ssize_t external_strobe_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
>> +
>> + return sprintf(buf, "%u\n", led_cdev->flash->external_strobe);
>> +}
>> +static DEVICE_ATTR_RW(external_strobe);
>> +
>> +static struct attribute *led_flash_attrs[] = {
>> + &dev_attr_flash_brightness.attr,
>> + &dev_attr_flash_strobe.attr,
>> + &dev_attr_flash_timeout.attr,
>> + &dev_attr_max_flash_timeout.attr,
>> + &dev_attr_max_flash_brightness.attr,
>> + &dev_attr_flash_fault.attr,
>> + NULL,
>> +};
>> +
>> +static struct attribute *led_flash_indicator_attrs[] = {
>> + &dev_attr_indicator_brightness.attr,
>> + &dev_attr_max_indicator_brightness.attr,
>> + NULL,
>> +};
>> +
>> +static struct attribute *led_flash_external_strobe_attrs[] = {
>> + &dev_attr_external_strobe.attr,
>> + NULL,
>> +};
>> +
>> +static struct attribute_group led_flash_group = {
>> + .attrs = led_flash_attrs,
>> +};
>> +
>> +static struct attribute_group led_flash_indicator_group = {
>> + .attrs = led_flash_indicator_attrs,
>> +};
>> +
>> +static struct attribute_group led_flash_external_strobe_group = {
>> + .attrs = led_flash_external_strobe_attrs,
>> +};
>> +
>> +void led_flash_resume(struct led_classdev *led_cdev)
>> +{
>> + struct led_flash *flash = led_cdev->flash;
>> +
>> + call_flash_op(brightness_set, led_cdev,
>> + flash->brightness.val);
>> + call_flash_op(timeout_set, led_cdev,
>> + flash->timeout.val);
>> + if (has_flash_op(indicator_brightness_set))
>> + call_flash_op(indicator_brightness_set, led_cdev,
>> + flash->indicator_brightness->val);
>> +}
>> +
>> +#ifdef CONFIG_V4L2_FLASH
>> +static const struct v4l2_flash_ops v4l2_flash_ops = {
>> + .brightness_set = led_set_brightness,
>> + .brightness_update = led_update_brightness,
>> + .flash_brightness_set = led_set_flash_brightness,
>> + .flash_brightness_update = led_update_flash_brightness,
>> + .indicator_brightness_set = led_set_indicator_brightness,
>> + .indicator_brightness_update = led_update_indicator_brightness,
>> + .strobe_set = led_set_flash_strobe,
>> + .strobe_get = led_get_flash_strobe,
>> + .timeout_set = led_set_flash_timeout,
>> + .external_strobe_set = led_set_external_strobe,
>> + .fault_get = led_get_flash_fault,
>> + .sysfs_lock = led_sysfs_lock,
>> + .sysfs_unlock = led_sysfs_unlock,
>> +};
>> +#define V4L2_FLASH_OPS (&v4l2_flash_ops)
>> +#else
>> +#define V4L2_FLASH_OPS NULL
>> +#endif
>> +
>
> This struct can be moved to V4L2 flash driver.

I'm affraid not if we want to let this driver to be built as a module.
In such case v4l2-flash driver doesn't see led_flash's symbols
and build break occurs.

>
>> +
>> +void led_flash_remove_sysfs_groups(struct led_classdev *led_cdev)
>> +{
>> + struct led_flash *flash = led_cdev->flash;
>> + int i;
>> +
>> + for (i = 0; i < LED_FLASH_MAX_SYSFS_GROUPS; ++i)
>> + if (flash->sysfs_groups[i])
>> + sysfs_remove_group(&led_cdev->dev->kobj,
>> + flash->sysfs_groups[i]);
>> +}
>> +
>> +int led_flash_create_sysfs_groups(struct led_classdev *led_cdev)
>> +{
>> + struct led_flash *flash = led_cdev->flash;
>> + int ret, num_sysfs_groups = 0;
>> +
>> + memset(flash->sysfs_groups, 0, sizeof(*flash->sysfs_groups) *
>> + LED_FLASH_MAX_SYSFS_GROUPS);
>> +
>> + ret = sysfs_create_group(&led_cdev->dev->kobj, &led_flash_group);
>> + if (ret < 0)
>> + goto err_create_group;
>> + flash->sysfs_groups[num_sysfs_groups++] = &led_flash_group;
>> +
>> + if (flash->indicator_brightness) {
>> + ret = sysfs_create_group(&led_cdev->dev->kobj,
>> + &led_flash_indicator_group);
>> + if (ret < 0)
>> + goto err_create_group;
>> + flash->sysfs_groups[num_sysfs_groups++] =
>> + &led_flash_indicator_group;
>> + }
>> + if (flash->has_external_strobe) {
>> + ret = sysfs_create_group(&led_cdev->dev->kobj,
>> + &led_flash_external_strobe_group);
>> + if (ret < 0)
>> + goto err_create_group;
>> + flash->sysfs_groups[num_sysfs_groups++] =
>> + &led_flash_external_strobe_group;
>> + }
>> +
>> + return 0;
>> +
>> +err_create_group:
>> + led_flash_remove_sysfs_groups(led_cdev);
>> + return ret;
>> +}
>> +
>> +int led_classdev_flash_register(struct device *parent,
>> + struct led_classdev *led_cdev)
>> +{
>> + struct led_flash *flash = led_cdev->flash;
>> + const struct led_flash_ops *ops;
>> + int ret;
>> +
>> + if (!flash)
>> + return -EINVAL;
>> +
>> + /* Register led class device */
>> + ret = led_classdev_register(parent, led_cdev);
>> + if (ret < 0)
>> + return ret;
>> +
>
> I think this should be after following ops checks, then you don't need
> to do unregister when checks fail.

It is here to allow for registering only torch led if this is
only one declared by the LED flash class driver.

>> + if (!flash->has_flash_led)
>> + goto exit;
>
> This looks a little bit strange to me.
> We check a flag of normal led_cdev firstly like
> led_cdev->flags & LED_DEV_CAP_FLASH or LED_DEV_CAP_TORTH,
> Then check led_cdev->flash struct.
>
> If you already have struct flash, has_flash_led is redundant to me.

In this implementation having struct flash doesn't imply presence
of a flash led. But this function will be modified in the new version
of the patchset.

>> +
>> + /* Validate flash related ops */
>> + ops = &flash->ops;
>> + if (!ops || !ops->brightness_set || !ops->strobe_set || !ops->strobe_get
>> + || !ops->timeout_set || !ops->fault_get)
>> + return -EINVAL;
>> +
>> + if (flash->has_external_strobe && !ops->external_strobe_set)
>> + return -EINVAL;
>> +
>> + if (flash->indicator_brightness && !ops->indicator_brightness_set)
>> + return -EINVAL;
>> +
>> + /* Install resume callback for flash controls */
>> + led_cdev->flash_resume = led_flash_resume;
>> +
>> + /* Create flash led specific sysfs attributes */
>> + ret = led_flash_create_sysfs_groups(led_cdev);
>> + if (ret < 0)
>> + goto err_create_groups;
>
> I'd like to see these sysfs interfaces are documented, please add them
> to Documentation/leds/

Sure, I've already sent modifications to the Documentation/leds
in my two previous patch series, but postponed updating
it in this version until I submit proposal for flash manager
which will provide a way for dynamic association of flash
led devices and camera sensors.

>> +
>> +exit:
>> + /* This will create V4L2 Flash sub-device if it is enabled */
>> + ret = v4l2_flash_init(led_cdev, V4L2_FLASH_OPS);
>> + if (ret < 0)
>> + goto err_create_groups;
>> +
>> + return 0;
>> +
>
> I don't think this is the right place to call v4l2_flash_init() API.
> It should be called during LED Flash device driver probing.
> max77693_led_probe() calls led_classdev_flash_register() then calls
> v4l2_flash_init()
>
> We should keep the core code separated, calling this in LED Flash
> Class API is not a good idea. Let's say if there's another subsystem
> also want to use LED Flash Class API but it doesn't want to init V4L2
> flash interface, it doesn't need to call v4l2_flash_init().

This was the way I did it in my RFC v1, I'll bring it back.

>> +err_create_groups:
>> + led_classdev_unregister(led_cdev);
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(led_classdev_flash_register);
>> +
>> +void led_classdev_flash_unregister(struct led_classdev *led_cdev)
>> +{
>> + v4l2_flash_release(led_cdev);
> Move out this also.
>
>> + led_flash_remove_sysfs_groups(led_cdev);
>> + led_classdev_unregister(led_cdev);
>> +}
>> +EXPORT_SYMBOL_GPL(led_classdev_flash_unregister);
>> +
>> +/* Caller must ensure led_cdev->led_lock held */
>> +void led_sysfs_lock(struct led_classdev *led_cdev)
>> +{
>> + led_cdev->flags |= LED_SYSFS_LOCK;
>> +}
>> +EXPORT_SYMBOL(led_sysfs_lock);
>> +
>> +/* Caller must ensure led_cdev->led_lock held */
>> +void led_sysfs_unlock(struct led_classdev *led_cdev)
>> +{
>> + led_cdev->flags &= ~LED_SYSFS_LOCK;
>> +}
>> +EXPORT_SYMBOL(led_sysfs_unlock);
>> +
>> +int led_set_flash_strobe(struct led_classdev *led_cdev, bool state)
>> +{
>> + if (!has_flash_op(strobe_set))
>> + return -EINVAL;
>> +
>> + return call_flash_op(strobe_set, led_cdev, state);
>> +}
>> +EXPORT_SYMBOL(led_set_flash_strobe);
>> +
>> +int led_get_flash_strobe(struct led_classdev *led_cdev)
>> +{
>> + if (!has_flash_op(strobe_get))
>> + return -EINVAL;
>> +
>> + return call_flash_op(strobe_get, led_cdev);
>> +}
>> +EXPORT_SYMBOL(led_get_flash_strobe);
>> +
>> +void led_clamp_align_val(struct led_ctrl *c)
>> +{
>> + u32 v, offset;
>> +
>> + v = c->val + c->step / 2;
>> + v = clamp(v, c->min, c->max);
>> + offset = v - c->min;
>> + offset = c->step * (offset / c->step);
>> + c->val = c->min + offset;
>> +}
>> +
>> +int led_set_flash_timeout(struct led_classdev *led_cdev, u32 timeout)
>> +{
>> + struct led_flash *flash = led_cdev->flash;
>> + struct led_ctrl *c = &flash->timeout;
>> + int ret = 0;
>> +
>> + if (!has_flash_op(timeout_set))
>> + return -EINVAL;
>> +
>> + c->val = timeout;
>> + led_clamp_align_val(c);
>> +
>> + if (!(led_cdev->flags & LED_SUSPENDED))
>> + ret = call_flash_op(timeout_set, led_cdev, c->val);
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL(led_set_flash_timeout);
>> +
>> +int led_get_flash_fault(struct led_classdev *led_cdev, u32 *fault)
>> +{
>> + if (!has_flash_op(fault_get))
>> + return -EINVAL;
>> +
>> + return call_flash_op(fault_get, led_cdev, fault);
>> +}
>> +EXPORT_SYMBOL(led_get_flash_fault);
>> +
>> +int led_set_external_strobe(struct led_classdev *led_cdev, bool enable)
>> +{
>> + struct led_flash *flash = led_cdev->flash;
>> + int ret;
>> +
>> + if (!has_flash_op(external_strobe_set))
>> + return -EINVAL;
>> +
>> + if (flash->has_external_strobe) {
>> + ret = call_flash_op(external_strobe_set, led_cdev, enable);
>> + if (ret < 0)
>> + return -EINVAL;
>> + flash->external_strobe = enable;
>> + } else if (enable)
>> + return -EINVAL;
>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL(led_set_external_strobe);
>> +
>> +int led_set_flash_brightness(struct led_classdev *led_cdev,
>> + u32 brightness)
>> +{
>> + struct led_flash *flash = led_cdev->flash;
>> + struct led_ctrl *c = &flash->brightness;
>> + int ret = 0;
>> +
>> + if (!has_flash_op(brightness_set))
>> + return -EINVAL;
>> +
>> + c->val = brightness;
>> + led_clamp_align_val(c);
>> +
>> + if (!(led_cdev->flags & LED_SUSPENDED))
>> + ret = call_flash_op(brightness_set, led_cdev, c->val);
>> + return ret;
>> +}
>> +EXPORT_SYMBOL(led_set_flash_brightness);
>> +
>> +int led_update_flash_brightness(struct led_classdev *led_cdev)
>> +{
>> + struct led_flash *flash = led_cdev->flash;
>> + struct led_ctrl *c = &flash->brightness;
>> + u32 brightness;
>> + int ret = 0;
>> +
>> + if (has_flash_op(brightness_get)) {
>> + ret = call_flash_op(brightness_get, led_cdev, &brightness);
>> + if (ret < 0)
>> + return ret;
>> + c->val = brightness;
>> + }
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL(led_update_flash_brightness);
>> +
>> +int led_set_indicator_brightness(struct led_classdev *led_cdev,
>> + u32 brightness)
>> +{
>> + struct led_flash *flash = led_cdev->flash;
>> + struct led_ctrl *c = flash->indicator_brightness;
>> + int ret = 0;
>> +
>> + if (!has_flash_op(indicator_brightness_set))
>> + return -EINVAL;
>> +
>> + c->val = brightness;
>> + led_clamp_align_val(c);
>> +
>> + if (!(led_cdev->flags & LED_SUSPENDED))
>> + ret = call_flash_op(indicator_brightness_set, led_cdev, c->val);
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL(led_set_indicator_brightness);
>> +
>> +int led_update_indicator_brightness(struct led_classdev *led_cdev)
>> +{
>> + struct led_flash *flash = led_cdev->flash;
>> + struct led_ctrl *c = flash->indicator_brightness;
>> + u32 brightness;
>> + int ret = 0;
>> +
>> + if (has_flash_op(indicator_brightness_get)) {
>> + ret = call_flash_op(indicator_brightness_get, led_cdev,
>> + &brightness);
>> + if (ret < 0)
>> + return ret;
>> + c->val = brightness;
>> + }
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL(led_update_indicator_brightness);
>> +
>> +static int __init leds_flash_init(void)
>> +{
>> + return 0;
>> +}
>> +
>> +static void __exit leds_flash_exit(void)
>> +{
>> +}
>> +
>> +subsys_initcall(leds_flash_init);
>> +module_exit(leds_flash_exit);
>
> I don't think we need these empty functions here. Looks it useless to me.

Yes, it's my ommission.

>> +
>> +MODULE_AUTHOR("Jacek Anaszewski <[email protected]>");
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("LED Class Flash Interface");
>> diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
>> index df1a7c1..40e21c0 100644
>> --- a/drivers/leds/led-triggers.c
>> +++ b/drivers/leds/led-triggers.c
>> @@ -37,6 +37,14 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
>> char trigger_name[TRIG_NAME_MAX];
>> struct led_trigger *trig;
>> size_t len;
>> + int ret = count;
>> +
>> + mutex_lock(&led_cdev->led_lock);
>> +
>> + if (led_sysfs_is_locked(led_cdev)) {
>> + ret = -EBUSY;
>> + goto exit_unlock;
>> + }
>>
>> trigger_name[sizeof(trigger_name) - 1] = '\0';
>> strncpy(trigger_name, buf, sizeof(trigger_name) - 1);
>> @@ -47,7 +55,7 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
>>
>> if (!strcmp(trigger_name, "none")) {
>> led_trigger_remove(led_cdev);
>> - return count;
>> + goto exit_unlock;
>> }
>>
>> down_read(&triggers_list_lock);
>> @@ -58,12 +66,14 @@ ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
>> up_write(&led_cdev->trigger_lock);
>>
>> up_read(&triggers_list_lock);
>> - return count;
>> + goto exit_unlock;
>> }
>> }
>> up_read(&triggers_list_lock);
>>
>> - return -EINVAL;
>> +exit_unlock:
>> + mutex_unlock(&led_cdev->led_lock);
>> + return ret;
>> }
>> EXPORT_SYMBOL_GPL(led_trigger_store);
>>
>> diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
>> index 4c50365..f66a0c3 100644
>> --- a/drivers/leds/leds.h
>> +++ b/drivers/leds/leds.h
>> @@ -17,6 +17,12 @@
>> #include <linux/rwsem.h>
>> #include <linux/leds.h>
>>
>> +#define call_flash_op(op, args...) \
>> + ((led_cdev)->flash->ops.op(args))
>> +
>> +#define has_flash_op(op) \
>> + ((led_cdev)->flash && (led_cdev)->flash->ops.op)
>> +
>> static inline void __led_set_brightness(struct led_classdev *led_cdev,
>> enum led_brightness value)
>> {
>> diff --git a/include/linux/leds.h b/include/linux/leds.h
>> index 0287ab2..a794817 100644
>> --- a/include/linux/leds.h
>> +++ b/include/linux/leds.h
>> @@ -13,12 +13,14 @@
>> #define __LINUX_LEDS_H_INCLUDED
>>
>> #include <linux/list.h>
>> -#include <linux/spinlock.h>
>> +#include <linux/mutex.h>
>> #include <linux/rwsem.h>
>> +#include <linux/spinlock.h>
>> #include <linux/timer.h>
>> #include <linux/workqueue.h>
>>
>> struct device;
>> +struct led_flash;
>> /*
>> * LED Core
>> */
>> @@ -29,6 +31,28 @@ enum led_brightness {
>> LED_FULL = 255,
>> };
>>
>> +/*
>> + * This structure is required in two cases:
>> + * - it defines allowed levels of flash leds brightness and timeout
>> + * - it provides initialization data for V4L2 Flash controls
>> + * when CONFIG_V4L2_FLASH is enabled and allows for proper
>> + * enum led_brightness <-> microamperes conversion for the
>> + * V4L2_CID_FLASH_TORCH_INTENSITY
>> + */
>> +struct led_ctrl {
>
> What about moving this to V4L2 flash driver and the name "led_ctrl" is
> not related to val cramp.
>
> In LED subsystem we are using brightness, so it's better do the
> conversion or ctrl in V4L2 and pass the right brightness value to LED
> subsystem.

I defined LED flash class brightness interface in microamperes
under influence of the Sakari Ailus' statement in the message [1],
where he argues that for leds that "generally are used as flash the
current is known, and thus it should also be visible in the interface".

I find this argument reasonable.

>> + /* maximum allowed value */
>> + u32 min;
>> + /* maximum allowed value */
>> + u32 max;
>> + /* step value */
>> + u32 step;
>> + /*
>> + * Default value for V4L2 controls and for flash leds
>> + * it also serves for caching the value currently set.
>> + */
>> + u32 val;
>> +};
>> +
>> struct led_classdev {
>> const char *name;
>> int brightness;
>> @@ -42,6 +66,7 @@ struct led_classdev {
>> #define LED_BLINK_ONESHOT (1 << 17)
>> #define LED_BLINK_ONESHOT_STOP (1 << 18)
>> #define LED_BLINK_INVERT (1 << 19)
>> +#define LED_SYSFS_LOCK (1 << 21)
>>
>> /* Set LED brightness level */
>> /* Must not sleep, use a workqueue if needed */
>> @@ -69,6 +94,17 @@ struct led_classdev {
>> unsigned long blink_delay_on, blink_delay_off;
>> struct timer_list blink_timer;
>> int blink_brightness;
>> + struct led_flash *flash;
>> + void (*flash_resume)(struct led_classdev *led_cdev);
>> +#ifdef CONFIG_V4L2_FLASH
>> + /* Initialization data for the V4L2_CID_FLASH_TORCH_INTENSITY control */
>> + struct led_ctrl brightness_ctrl;
>> +#endif
>
> Move it to V4L2 Flash

It is required here if the flash brightness is to be expressed in
microamperes.

>> + /*
>> + * Ensures consistent LED sysfs access and protects
>> + * LED sysfs locking mechanism
>> + */
>> + struct mutex led_lock;
>>
> I guess we can split it out as a new patch.

>> struct work_struct set_brightness_work;
>> int delayed_set_value;
>> @@ -139,6 +175,18 @@ extern void led_blink_set_oneshot(struct led_classdev *led_cdev,
>> extern void led_set_brightness(struct led_classdev *led_cdev,
>> enum led_brightness brightness);
>>
>> +/**
>> + * led_sysfs_is_locked
>> + * @led_cdev: the LED to query
>> + *
>> + * Returns: true if the sysfs interface of the led is disabled,
>> + * false otherwise
>> + */
>> +static inline bool led_sysfs_is_locked(struct led_classdev *led_cdev)
>> +{
>> + return led_cdev->flags & LED_SYSFS_LOCK;
>> +}
>> +
>
> Why we need this lock check? We just do mutex_lock() which do lock
> check definitely.

The V4L2 flash sub-device may be opened for relatively long time
and hold the lock. I think the better option is to give the user
immediate feedback that the the LED flash device control is currently
taken of by the V4L2.

>> /*
>> * LED Triggers
>> */
>> diff --git a/include/linux/leds_flash.h b/include/linux/leds_flash.h
>> new file mode 100644
>> index 0000000..0f885a0
>> --- /dev/null
>> +++ b/include/linux/leds_flash.h
>> @@ -0,0 +1,252 @@
>> +/*
>> + * Flash leds API
>> + *
>> + * 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 <media/v4l2-flash.h>
>> +#include <linux/leds.h>
>> +
>> +/*
>> + * Supported led fault bits - must be kept in synch
>> + * with V4L2_FLASH_FAULT bits.
>> + */
>> +#define LED_FAULT_OVER_VOLTAGE (1 << 0)
>> +#define LED_FAULT_TIMEOUT (1 << 1)
>> +#define LED_FAULT_OVER_TEMPERATURE (1 << 2)
>> +#define LED_FAULT_SHORT_CIRCUIT (1 << 3)
>> +#define LED_FAULT_OVER_CURRENT (1 << 4)
>> +#define LED_FAULT_INDICATOR (1 << 5)
>> +#define LED_FAULT_UNDER_VOLTAGE (1 << 6)
>> +#define LED_FAULT_INPUT_VOLTAGE (1 << 7)
>> +#define LED_FAULT_LED_OVER_TEMPERATURE (1 << 8)
>> +
>
> Probably we can just reuse the predefined V4L2_FLASH_FAULT bits.
> Or V4L2_FLASH_FAULT use LED_FLASH_FAULT bit definitions, but don't
> need to copy and paste.

OK.

>> +#define LED_FLASH_MAX_SYSFS_GROUPS 3
>> +
>> +struct led_flash_ops {
>> + /* set flash brightness */
>> + int (*brightness_set)(struct led_classdev *led_cdev,
>> + u32 brightness);
>> + /* get flash brightness */
>> + int (*brightness_get)(struct led_classdev *led_cdev, u32 *brightness);
>> + /* set flash indicator brightness */
>> + int (*indicator_brightness_set)(struct led_classdev *led_cdev,
>> + u32 brightness);
>> + /* get flash indicator brightness */
>> + int (*indicator_brightness_get)(struct led_classdev *led_cdev,
>> + u32 *brightness);
>> + /* setup flash strobe */
>> + int (*strobe_set)(struct led_classdev *led_cdev,
>> + bool state);
>> + /* get flash strobe state */
>> + int (*strobe_get)(struct led_classdev *led_cdev);
>> + /* setup flash timeout */
>> + int (*timeout_set)(struct led_classdev *led_cdev,
>> + u32 timeout);
>> + /* setup strobing the flash from external source */
>> + int (*external_strobe_set)(struct led_classdev *led_cdev,
>> + bool enable);
>> + /* get the flash LED fault */
>> + int (*fault_get)(struct led_classdev *led_cdev,
>> + u32 *fault);
>> +};
>> +
>> +struct led_flash {
>> + /*
>> + * 1 - add support for both flash and torch leds,
>> + * 0 - handle only torch led
>> + */
>> + bool has_flash_led;
>> + /* flash led specific ops */
>> + const struct led_flash_ops ops;
>> +
>> + /* flash sysfs groups */
>> + struct attribute_group *sysfs_groups[LED_FLASH_MAX_SYSFS_GROUPS];
>> +
>> + /* flash brightness value in microamperes along with its constraints */
>> + struct led_ctrl brightness;
>> +
>> + /* timeout value in microseconds along with its constraints */
>> + struct led_ctrl timeout;
>> +
>> + /*
>> + * Indicator brightness value in microamperes along with
>> + * its constraints - this is an optional control and must
>> + * be allocated by the driver if the device supports privacy
>> + * indicator led.
>> + */
>> + struct led_ctrl *indicator_brightness;
>> +
>> + /*
>> + * determines whether device supports external
>> + * flash strobe sources
>> + */
>> + bool has_external_strobe;
>> +
>> + /*
>> + * If true the device doesn't strobe the flash immediately
>> + * after writing 1 to the flash_strobe file, but waits
>> + * for an external signal.
>> + */
>> + bool external_strobe;
>> +
>> +#ifdef CONFIG_V4L2_FLASH
>> + /* V4L2 Flash sub-device data */
>> + struct v4l2_flash v4l2_flash;
>> +
>> + /* flash fault bits that may be set by the device */
>> + u32 fault_flags;
>> +#endif
>> +
>
> Let's consider move all this V4L2 Flash driver related stuff to V4L2
> and treat LED Flash Class as a base struct and API. V4L2 Flash driver
> can create a struct contains LED Flash struct and wrap the API.
> Anyway, LED subsystem should be not just for V4L2 usage and but
> provide good API for other subsystems using.

Indeed, it will be cleaner.

>> +};
>> +
>> +/**
>> + * led_classdev_flash_register - register a new object of led_classdev class
>> + with support for flash LEDs
>> + * @parent: the device to register
>> + * @led_cdev: the led_classdev structure for this device
>> + *
>> + * Returns: 0 on success, error code on failure.
>> + */
>> +int led_classdev_flash_register(struct device *parent,
>> + struct led_classdev *led_cdev);
>> +
>> +/**
>> + * led_classdev_flash_unregister - unregisters an object of led_properties class
>> + with support for flash LEDs
>> + * @led_cdev: the flash led device to unregister
>> + *
>> + * Unregisters a previously registered via led_classdev_flash_register object
>> + */
>> +void led_classdev_flash_unregister(struct led_classdev *led_cdev);
>> +
>> +/**
>> + * led_set_flash_strobe - setup flash strobe
>> + * @led_cdev: the flash LED to set strobe on
>> + * @state: 1 - strobe flash, 0 - stop flash strobe
>> + *
>> + * Setup flash strobe - trigger flash strobe
>> + *
>> + * Returns: 0 on success or negative error value on failure
>> + */
>> +extern int led_set_flash_strobe(struct led_classdev *led_cdev, bool state);
>> +
>> +/**
>> + * led_get_flash_strobe - get flash strobe status
>> + * @led_cdev: the LED to query
>> + *
>> + * Check whether the flash is strobing at the moment or not.
>> + *
>> + * Returns: flash strobe status (0 or 1) on success or negative
>> + * error value on failure.
>> + */
>> +extern int led_get_flash_strobe(struct led_classdev *led_cdev);
>> +
>> +/**
>> + * led_set_flash_brightness - set flash LED brightness
>> + * @led_cdev: the LED to set
>> + * @brightness: the brightness to set it to
>> + *
>> + * Returns: 0 on success, -EINVAL on failure
>> + *
>> + * Set a flash LED's brightness.
>> + */
>> +extern int led_set_flash_brightness(struct led_classdev *led_cdev,
>> + u32 brightness);
>> +
>> +/**
>> + * led_update_flash_brightness - update flash LED brightness
>> + * @led_cdev: the 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 *led_cdev);
>> +
>> +/**
>> + * led_set_flash_timeout - set flash LED timeout
>> + * @led_cdev: the LED to set
>> + * @timeout: the flash timeout to set it to
>> + *
>> + * Returns: 0 on success, -EINVAL on failure
>> + *
>> + * 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.
>> + */
>> +extern int led_set_flash_timeout(struct led_classdev *led_cdev,
>> + u32 timeout);
>> +
>> +/**
>> + * led_get_flash_fault - get the flash LED fault
>> + * @led_cdev: the LED to query
>> + * @fault: bitmask containing flash faults
>> + *
>> + * Returns: 0 on success, -EINVAL on failure
>> + *
>> + * Get the flash LED fault.
>> + */
>> +extern int led_get_flash_fault(struct led_classdev *led_cdev,
>> + u32 *fault);
>> +
>> +/**
>> + * led_set_external_strobe - set the flash LED external_strobe mode
>> + * @led_cdev: the LED to set
>> + * @enable: the state to set it to
>> + *
>> + * Returns: 0 on success, -EINVAL on failure
>> + *
>> + * Enable/disable strobing the flash LED with use of external source
>> + */
>> +extern int led_set_external_strobe(struct led_classdev *led_cdev, bool enable);
>> +
>> +/**
>> + * led_set_indicator_brightness - set indicator LED brightness
>> + * @led_cdev: the LED to set
>> + * @brightness: the brightness to set it to
>> + *
>> + * Returns: 0 on success, -EINVAL on failure
>> + *
>> + * Set a flash LED's brightness.
>> + */
>> +extern int led_set_indicator_brightness(struct led_classdev *led_cdev,
>> + u32 led_brightness);
>> +
>> +/**
>> + * led_update_indicator_brightness - update flash indicator LED brightness
>> + * @led_cdev: the LED to query
>> + *
>> + * Get a flash indicator LED's current brightness and update
>> + * led_flash->indicator_brightness member with the obtained value.
>> + *
>> + * Returns: 0 on success or negative error value on failure
>> + */
>> +extern int led_update_indicator_brightness(struct led_classdev *led_cdev);
>> +
>> +/**
>> + * led_sysfs_lock - lock LED sysfs interface
>> + * @led_cdev: the LED to set
>> + *
>> + * Lock the LED's sysfs interface
>> + */
>> +extern void led_sysfs_lock(struct led_classdev *led_cdev);
>> +
>> +/**
>> + * led_sysfs_unlock - unlock LED sysfs interface
>> + * @led_cdev: the LED to set
>> + *
>> + * Unlock the LED's sysfs interface
>> + */
>> +extern void led_sysfs_unlock(struct led_classdev *led_cdev);
>> +
>> +#endif /* __LINUX_FLASH_LEDS_H_INCLUDED */
>> --
>> 1.7.9.5
>>
>
> I don't have big objections to these APIs. But I'd like to invite Milo
> to review this Flash related APIs.
>
> Milo, do you think these Flash APIs are good enough for your LED Flash devices?
>
> Thanks,
> -Bryan
>

Thanks,
Jacek

[1] - http://www.spinics.net/lists/linux-leds/msg01650.html

2014-07-10 18:40:31

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH/RFC v3 5/5] media: Add registration helpers for V4L2 flash sub-devices

Hi Jacek,

On Fri, May 09, 2014 at 09:18:55AM +0200, Jacek Anaszewski wrote:
> Hi Sakari,
>
> On 05/07/2014 09:58 AM, Sakari Ailus wrote:
> >Hi Jacek,
> >
> >On Wed, May 07, 2014 at 09:20:17AM +0200, Jacek Anaszewski wrote:
> >>On 05/06/2014 11:10 AM, Sakari Ailus wrote:
> >>>Hi Jacek,
> >>>
> >>>On Tue, May 06, 2014 at 08:44:41AM +0200, Jacek Anaszewski wrote:
> >>>>Hi Sakari,
> >>>>
> >>>>On 05/02/2014 01:06 PM, Sakari Ailus wrote:
> >>>>
> >>>>>>>>[...]
> >>>>>>>>>>+static inline enum led_brightness v4l2_flash_intensity_to_led_brightness(
> >>>>>>>>>>+ struct led_ctrl *config,
> >>>>>>>>>>+ u32 intensity)
> >>>>>>>>>
> >>>>>>>>>Fits on a single line.
> >>>>>>>>>
> >>>>>>>>>>+{
> >>>>>>>>>>+ return intensity / config->step;
> >>>>>>>>>
> >>>>>>>>>Shouldn't you first decrement the minimum before the division?
> >>>>>>>>
> >>>>>>>>Brightness level 0 means that led is off. Let's consider following case:
> >>>>>>>>
> >>>>>>>>intensity - 15625
> >>>>>>>>config->step - 15625
> >>>>>>>>intensity / config->step = 1 (the lowest possible current level)
> >>>>>>>
> >>>>>>>In V4L2 controls the minimum is not off, and zero might not be a possible
> >>>>>>>value since minimum isn't divisible by step.
> >>>>>>>
> >>>>>>>I wonder how to best take that into account.
> >>>>>>
> >>>>>>I've assumed that in MODE_TORCH a led is always on. Switching
> >>>>>>the mode to MODE_FLASH or MODE_OFF turns the led off.
> >>>>>>This way we avoid the problem with converting 0 uA value to
> >>>>>>led_brightness, as available torch brightness levels start from
> >>>>>>the minimum current level value and turning the led off is
> >>>>>>accomplished on transition to MODE_OFF or MODE_FLASH, by
> >>>>>>calling brightness_set op with led_brightness = 0.
> >>>>>
> >>>>>I'm not sure if we understood the issue the same way. My concern was that if
> >>>>>the intensity isn't a multiple of step (but intensity - min is), the above
> >>>>>formula won't return a valid result (unless I miss something).
> >>>>>
> >>>>
> >>>>Please note that v4l2_flash_intensity_to_led_brightness is called only
> >>>>from s_ctrl callback, and thus it expects to get the intensity aligned
> >>>>to the step value, so it will always be a multiple of step.
> >>>>Is it possible that s_ctrl callback would be passed a non-aligned
> >>>>control value?
> >>>
> >>>In a nutshell: value - min is aligned but value is not. Please see
> >>>validate_new() in drivers/media/v4l2-core/v4l2-ctrls.c .
> >>>
> >>
> >>Still, to my mind, value is aligned.
> >>
> >>Below I execute the calculation steps one by one
> >>according to the V4L2_CTRL_TYPE_INTEGER case in the
> >>validate_new function:
> >>
> >>c->value = 35000
> >>
> >>val = c->value + step / 2; // 35000 + 15625 / 2 = 42812
> >>val = clamp(val, min, max); // val = 42812
> >>offset = val - min; // 42812 - 15625 = 27187
> >>offset = step * (offset / step); // 15625 * (27187 / 15625) = 15625
> >>c->value = min + offset; // 15625 + 15625 = 31250
> >>
> >>Value is aligned to the nearest step.
> >>
> >>Please spot any discrepancies in my way of thinking if there
> >>are any :)
> >
> >min is aligned to step above. This is not necessarily the case. And if min
> >is not aligned, neither is value.
> >
>
> Thanks for spotting this. Below are improved versions of the conversion
> functions. Please let me know if you have any comments.
>
> static inline
> enum led_brightnessv4l2_flash_intensity_to_led_brightness(
> struct led_ctrl *config,
> u32 intensity)
> {
> return ((intensity - config->min) / config->step) + 1;
> }
>
> static inline
> u32 v4l2_flash_led_brightness_to_intensity(
> struct led_ctrl *config,
> enum led_brightness brightness)
> {
> return ((brightness - 1) * config->step) + config->min;

V4L2 control integer values are signed, thus s32 instead of u32. Otherwise
looks good to me.

--
Regards,

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