mirror of
https://github.com/torvalds/linux.git
synced 2024-12-23 03:11:46 +00:00
503dd28af1
The V4L2 flash interface allows controlling multiple LEDs through a single sub-devices if, and only if, these LEDs are of different types. This approach scales badly for flash controllers that drive multiple flash LEDs or for LED specific associations. Essentially, the original assumption of a LED driver chip that drives a single flash LED and an indicator LED is no longer valid. Address the matter by registering one sub-device per LED. Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> Reviewed-by: Jacek Anaszewski <jacek.anaszewski@gmail.com> Acked-by: Pavel Machek <pavel@ucw.cz> Reviewed-by: Rui Miguel Silva <rmfrfs@gmail.com> (for greybus/light) Acked-by: Hans Verkuil <hans.verkuil@cisco.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
711 lines
20 KiB
C
711 lines
20 KiB
C
/*
|
|
* V4L2 flash LED sub-device registration helpers.
|
|
*
|
|
* Copyright (C) 2015 Samsung Electronics Co., Ltd
|
|
* Author: Jacek Anaszewski <j.anaszewski@samsung.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/led-class-flash.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/property.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/types.h>
|
|
#include <media/v4l2-flash-led-class.h>
|
|
|
|
#define has_flash_op(v4l2_flash, op) \
|
|
(v4l2_flash && v4l2_flash->ops && v4l2_flash->ops->op)
|
|
|
|
#define call_flash_op(v4l2_flash, op, arg) \
|
|
(has_flash_op(v4l2_flash, op) ? \
|
|
v4l2_flash->ops->op(v4l2_flash, arg) : \
|
|
-EINVAL)
|
|
|
|
enum ctrl_init_data_id {
|
|
LED_MODE,
|
|
TORCH_INTENSITY,
|
|
FLASH_INTENSITY,
|
|
INDICATOR_INTENSITY,
|
|
FLASH_TIMEOUT,
|
|
STROBE_SOURCE,
|
|
/*
|
|
* Only above values are applicable to
|
|
* the 'ctrls' array in the struct v4l2_flash.
|
|
*/
|
|
FLASH_STROBE,
|
|
STROBE_STOP,
|
|
STROBE_STATUS,
|
|
FLASH_FAULT,
|
|
NUM_FLASH_CTRLS,
|
|
};
|
|
|
|
static enum led_brightness __intensity_to_led_brightness(
|
|
struct v4l2_ctrl *ctrl, s32 intensity)
|
|
{
|
|
intensity -= ctrl->minimum;
|
|
intensity /= (u32) ctrl->step;
|
|
|
|
/*
|
|
* Indicator LEDs, unlike torch LEDs, are turned on/off basing on
|
|
* the state of V4L2_CID_FLASH_INDICATOR_INTENSITY control only.
|
|
* Therefore it must be possible to set it to 0 level which in
|
|
* the LED subsystem reflects LED_OFF state.
|
|
*/
|
|
if (ctrl->minimum)
|
|
++intensity;
|
|
|
|
return intensity;
|
|
}
|
|
|
|
static s32 __led_brightness_to_intensity(struct v4l2_ctrl *ctrl,
|
|
enum led_brightness brightness)
|
|
{
|
|
/*
|
|
* Indicator LEDs, unlike torch LEDs, are turned on/off basing on
|
|
* the state of V4L2_CID_FLASH_INDICATOR_INTENSITY control only.
|
|
* Do not decrement brightness read from the LED subsystem for
|
|
* indicator LED as it may equal 0. For torch LEDs this function
|
|
* is called only when V4L2_FLASH_LED_MODE_TORCH is set and the
|
|
* brightness read is guaranteed to be greater than 0. In the mode
|
|
* V4L2_FLASH_LED_MODE_NONE the cached torch intensity value is used.
|
|
*/
|
|
if (ctrl->id != V4L2_CID_FLASH_INDICATOR_INTENSITY)
|
|
--brightness;
|
|
|
|
return (brightness * ctrl->step) + ctrl->minimum;
|
|
}
|
|
|
|
static void v4l2_flash_set_led_brightness(struct v4l2_flash *v4l2_flash,
|
|
struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
|
|
enum led_brightness brightness;
|
|
|
|
if (has_flash_op(v4l2_flash, intensity_to_led_brightness))
|
|
brightness = call_flash_op(v4l2_flash,
|
|
intensity_to_led_brightness,
|
|
ctrl->val);
|
|
else
|
|
brightness = __intensity_to_led_brightness(ctrl, ctrl->val);
|
|
/*
|
|
* In case a LED Flash class driver provides ops for custom
|
|
* brightness <-> intensity conversion, it also must have defined
|
|
* related v4l2 control step == 1. In such a case a backward conversion
|
|
* from led brightness to v4l2 intensity is required to find out the
|
|
* the aligned intensity value.
|
|
*/
|
|
if (has_flash_op(v4l2_flash, led_brightness_to_intensity))
|
|
ctrl->val = call_flash_op(v4l2_flash,
|
|
led_brightness_to_intensity,
|
|
brightness);
|
|
|
|
if (ctrl == ctrls[TORCH_INTENSITY]) {
|
|
if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH)
|
|
return;
|
|
|
|
led_set_brightness_sync(&v4l2_flash->fled_cdev->led_cdev,
|
|
brightness);
|
|
} else {
|
|
led_set_brightness_sync(v4l2_flash->iled_cdev,
|
|
brightness);
|
|
}
|
|
}
|
|
|
|
static int v4l2_flash_update_led_brightness(struct v4l2_flash *v4l2_flash,
|
|
struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
|
|
struct led_classdev *led_cdev;
|
|
int ret;
|
|
|
|
if (ctrl == ctrls[TORCH_INTENSITY]) {
|
|
/*
|
|
* Update torch brightness only if in TORCH_MODE. In other modes
|
|
* torch led is turned off, which would spuriously inform the
|
|
* user space that V4L2_CID_FLASH_TORCH_INTENSITY control value
|
|
* has changed to 0.
|
|
*/
|
|
if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH)
|
|
return 0;
|
|
led_cdev = &v4l2_flash->fled_cdev->led_cdev;
|
|
} else {
|
|
led_cdev = v4l2_flash->iled_cdev;
|
|
}
|
|
|
|
ret = led_update_brightness(led_cdev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (has_flash_op(v4l2_flash, led_brightness_to_intensity))
|
|
ctrl->val = call_flash_op(v4l2_flash,
|
|
led_brightness_to_intensity,
|
|
led_cdev->brightness);
|
|
else
|
|
ctrl->val = __led_brightness_to_intensity(ctrl,
|
|
led_cdev->brightness);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int v4l2_flash_g_volatile_ctrl(struct v4l2_ctrl *c)
|
|
{
|
|
struct v4l2_flash *v4l2_flash = v4l2_ctrl_to_v4l2_flash(c);
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
bool is_strobing;
|
|
int ret;
|
|
|
|
switch (c->id) {
|
|
case V4L2_CID_FLASH_TORCH_INTENSITY:
|
|
case V4L2_CID_FLASH_INDICATOR_INTENSITY:
|
|
return v4l2_flash_update_led_brightness(v4l2_flash, c);
|
|
case V4L2_CID_FLASH_INTENSITY:
|
|
ret = led_update_flash_brightness(fled_cdev);
|
|
if (ret < 0)
|
|
return ret;
|
|
/*
|
|
* No conversion is needed as LED Flash class also uses
|
|
* microamperes for flash intensity units.
|
|
*/
|
|
c->val = fled_cdev->brightness.val;
|
|
return 0;
|
|
case V4L2_CID_FLASH_STROBE_STATUS:
|
|
ret = led_get_flash_strobe(fled_cdev, &is_strobing);
|
|
if (ret < 0)
|
|
return ret;
|
|
c->val = is_strobing;
|
|
return 0;
|
|
case V4L2_CID_FLASH_FAULT:
|
|
/* LED faults map directly to V4L2 flash faults */
|
|
return led_get_flash_fault(fled_cdev, &c->val);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static bool __software_strobe_mode_inactive(struct v4l2_ctrl **ctrls)
|
|
{
|
|
return ((ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_FLASH) ||
|
|
(ctrls[STROBE_SOURCE] && (ctrls[STROBE_SOURCE]->val !=
|
|
V4L2_FLASH_STROBE_SOURCE_SOFTWARE)));
|
|
}
|
|
|
|
static int v4l2_flash_s_ctrl(struct v4l2_ctrl *c)
|
|
{
|
|
struct v4l2_flash *v4l2_flash = v4l2_ctrl_to_v4l2_flash(c);
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
struct led_classdev *led_cdev = fled_cdev ? &fled_cdev->led_cdev : NULL;
|
|
struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
|
|
bool external_strobe;
|
|
int ret = 0;
|
|
|
|
switch (c->id) {
|
|
case V4L2_CID_FLASH_LED_MODE:
|
|
switch (c->val) {
|
|
case V4L2_FLASH_LED_MODE_NONE:
|
|
led_set_brightness_sync(led_cdev, LED_OFF);
|
|
return led_set_flash_strobe(fled_cdev, false);
|
|
case V4L2_FLASH_LED_MODE_FLASH:
|
|
/* Turn the torch LED off */
|
|
led_set_brightness_sync(led_cdev, LED_OFF);
|
|
if (ctrls[STROBE_SOURCE]) {
|
|
external_strobe = (ctrls[STROBE_SOURCE]->val ==
|
|
V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
|
|
|
|
ret = call_flash_op(v4l2_flash,
|
|
external_strobe_set,
|
|
external_strobe);
|
|
}
|
|
return ret;
|
|
case V4L2_FLASH_LED_MODE_TORCH:
|
|
if (ctrls[STROBE_SOURCE]) {
|
|
ret = call_flash_op(v4l2_flash,
|
|
external_strobe_set,
|
|
false);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
/* Stop flash strobing */
|
|
ret = led_set_flash_strobe(fled_cdev, false);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
v4l2_flash_set_led_brightness(v4l2_flash,
|
|
ctrls[TORCH_INTENSITY]);
|
|
return 0;
|
|
}
|
|
break;
|
|
case V4L2_CID_FLASH_STROBE_SOURCE:
|
|
external_strobe = (c->val == V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
|
|
/*
|
|
* For some hardware arrangements setting strobe source may
|
|
* affect torch mode. Therefore, if not in the flash mode,
|
|
* cache only this setting. It will be applied upon switching
|
|
* to flash mode.
|
|
*/
|
|
if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_FLASH)
|
|
return 0;
|
|
|
|
return call_flash_op(v4l2_flash, external_strobe_set,
|
|
external_strobe);
|
|
case V4L2_CID_FLASH_STROBE:
|
|
if (__software_strobe_mode_inactive(ctrls))
|
|
return -EBUSY;
|
|
return led_set_flash_strobe(fled_cdev, true);
|
|
case V4L2_CID_FLASH_STROBE_STOP:
|
|
if (__software_strobe_mode_inactive(ctrls))
|
|
return -EBUSY;
|
|
return led_set_flash_strobe(fled_cdev, false);
|
|
case V4L2_CID_FLASH_TIMEOUT:
|
|
/*
|
|
* No conversion is needed as LED Flash class also uses
|
|
* microseconds for flash timeout units.
|
|
*/
|
|
return led_set_flash_timeout(fled_cdev, c->val);
|
|
case V4L2_CID_FLASH_INTENSITY:
|
|
/*
|
|
* No conversion is needed as LED Flash class also uses
|
|
* microamperes for flash intensity units.
|
|
*/
|
|
return led_set_flash_brightness(fled_cdev, c->val);
|
|
case V4L2_CID_FLASH_TORCH_INTENSITY:
|
|
case V4L2_CID_FLASH_INDICATOR_INTENSITY:
|
|
v4l2_flash_set_led_brightness(v4l2_flash, c);
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static const struct v4l2_ctrl_ops v4l2_flash_ctrl_ops = {
|
|
.g_volatile_ctrl = v4l2_flash_g_volatile_ctrl,
|
|
.s_ctrl = v4l2_flash_s_ctrl,
|
|
};
|
|
|
|
static void __lfs_to_v4l2_ctrl_config(struct led_flash_setting *s,
|
|
struct v4l2_ctrl_config *c)
|
|
{
|
|
c->min = s->min;
|
|
c->max = s->max;
|
|
c->step = s->step;
|
|
c->def = s->val;
|
|
}
|
|
|
|
static void __fill_ctrl_init_data(struct v4l2_flash *v4l2_flash,
|
|
struct v4l2_flash_config *flash_cfg,
|
|
struct v4l2_flash_ctrl_data *ctrl_init_data)
|
|
{
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
struct led_classdev *led_cdev = fled_cdev ? &fled_cdev->led_cdev : NULL;
|
|
struct v4l2_ctrl_config *ctrl_cfg;
|
|
u32 mask;
|
|
|
|
/* Init INDICATOR_INTENSITY ctrl data */
|
|
if (v4l2_flash->iled_cdev) {
|
|
ctrl_init_data[INDICATOR_INTENSITY].cid =
|
|
V4L2_CID_FLASH_INDICATOR_INTENSITY;
|
|
ctrl_cfg = &ctrl_init_data[INDICATOR_INTENSITY].config;
|
|
__lfs_to_v4l2_ctrl_config(&flash_cfg->intensity,
|
|
ctrl_cfg);
|
|
ctrl_cfg->id = V4L2_CID_FLASH_INDICATOR_INTENSITY;
|
|
ctrl_cfg->min = 0;
|
|
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
|
|
V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
|
|
}
|
|
|
|
if (!led_cdev || WARN_ON(!(led_cdev->flags & LED_DEV_CAP_FLASH)))
|
|
return;
|
|
|
|
/* Init FLASH_FAULT ctrl data */
|
|
if (flash_cfg->flash_faults) {
|
|
ctrl_init_data[FLASH_FAULT].cid = V4L2_CID_FLASH_FAULT;
|
|
ctrl_cfg = &ctrl_init_data[FLASH_FAULT].config;
|
|
ctrl_cfg->id = V4L2_CID_FLASH_FAULT;
|
|
ctrl_cfg->max = flash_cfg->flash_faults;
|
|
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
|
|
V4L2_CTRL_FLAG_READ_ONLY;
|
|
}
|
|
|
|
/* Init FLASH_LED_MODE ctrl data */
|
|
mask = 1 << V4L2_FLASH_LED_MODE_NONE |
|
|
1 << V4L2_FLASH_LED_MODE_TORCH;
|
|
if (led_cdev->flags & LED_DEV_CAP_FLASH)
|
|
mask |= 1 << V4L2_FLASH_LED_MODE_FLASH;
|
|
|
|
ctrl_init_data[LED_MODE].cid = V4L2_CID_FLASH_LED_MODE;
|
|
ctrl_cfg = &ctrl_init_data[LED_MODE].config;
|
|
ctrl_cfg->id = V4L2_CID_FLASH_LED_MODE;
|
|
ctrl_cfg->max = V4L2_FLASH_LED_MODE_TORCH;
|
|
ctrl_cfg->menu_skip_mask = ~mask;
|
|
ctrl_cfg->def = V4L2_FLASH_LED_MODE_NONE;
|
|
ctrl_cfg->flags = 0;
|
|
|
|
/* Init TORCH_INTENSITY ctrl data */
|
|
ctrl_init_data[TORCH_INTENSITY].cid = V4L2_CID_FLASH_TORCH_INTENSITY;
|
|
ctrl_cfg = &ctrl_init_data[TORCH_INTENSITY].config;
|
|
__lfs_to_v4l2_ctrl_config(&flash_cfg->intensity, ctrl_cfg);
|
|
ctrl_cfg->id = V4L2_CID_FLASH_TORCH_INTENSITY;
|
|
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
|
|
V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
|
|
|
|
/* Init FLASH_STROBE ctrl data */
|
|
ctrl_init_data[FLASH_STROBE].cid = V4L2_CID_FLASH_STROBE;
|
|
ctrl_cfg = &ctrl_init_data[FLASH_STROBE].config;
|
|
ctrl_cfg->id = V4L2_CID_FLASH_STROBE;
|
|
|
|
/* Init STROBE_STOP ctrl data */
|
|
ctrl_init_data[STROBE_STOP].cid = V4L2_CID_FLASH_STROBE_STOP;
|
|
ctrl_cfg = &ctrl_init_data[STROBE_STOP].config;
|
|
ctrl_cfg->id = V4L2_CID_FLASH_STROBE_STOP;
|
|
|
|
/* Init FLASH_STROBE_SOURCE ctrl data */
|
|
if (flash_cfg->has_external_strobe) {
|
|
mask = (1 << V4L2_FLASH_STROBE_SOURCE_SOFTWARE) |
|
|
(1 << V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
|
|
ctrl_init_data[STROBE_SOURCE].cid =
|
|
V4L2_CID_FLASH_STROBE_SOURCE;
|
|
ctrl_cfg = &ctrl_init_data[STROBE_SOURCE].config;
|
|
ctrl_cfg->id = V4L2_CID_FLASH_STROBE_SOURCE;
|
|
ctrl_cfg->max = V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
|
|
ctrl_cfg->menu_skip_mask = ~mask;
|
|
ctrl_cfg->def = V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
|
|
}
|
|
|
|
/* Init STROBE_STATUS ctrl data */
|
|
if (has_flash_op(fled_cdev, strobe_get)) {
|
|
ctrl_init_data[STROBE_STATUS].cid =
|
|
V4L2_CID_FLASH_STROBE_STATUS;
|
|
ctrl_cfg = &ctrl_init_data[STROBE_STATUS].config;
|
|
ctrl_cfg->id = V4L2_CID_FLASH_STROBE_STATUS;
|
|
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
|
|
V4L2_CTRL_FLAG_READ_ONLY;
|
|
}
|
|
|
|
/* Init FLASH_TIMEOUT ctrl data */
|
|
if (has_flash_op(fled_cdev, timeout_set)) {
|
|
ctrl_init_data[FLASH_TIMEOUT].cid = V4L2_CID_FLASH_TIMEOUT;
|
|
ctrl_cfg = &ctrl_init_data[FLASH_TIMEOUT].config;
|
|
__lfs_to_v4l2_ctrl_config(&fled_cdev->timeout, ctrl_cfg);
|
|
ctrl_cfg->id = V4L2_CID_FLASH_TIMEOUT;
|
|
}
|
|
|
|
/* Init FLASH_INTENSITY ctrl data */
|
|
if (has_flash_op(fled_cdev, flash_brightness_set)) {
|
|
ctrl_init_data[FLASH_INTENSITY].cid = V4L2_CID_FLASH_INTENSITY;
|
|
ctrl_cfg = &ctrl_init_data[FLASH_INTENSITY].config;
|
|
__lfs_to_v4l2_ctrl_config(&fled_cdev->brightness, ctrl_cfg);
|
|
ctrl_cfg->id = V4L2_CID_FLASH_INTENSITY;
|
|
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
|
|
V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
|
|
}
|
|
}
|
|
|
|
static int v4l2_flash_init_controls(struct v4l2_flash *v4l2_flash,
|
|
struct v4l2_flash_config *flash_cfg)
|
|
|
|
{
|
|
struct v4l2_flash_ctrl_data *ctrl_init_data;
|
|
struct v4l2_ctrl *ctrl;
|
|
struct v4l2_ctrl_config *ctrl_cfg;
|
|
int i, ret, num_ctrls = 0;
|
|
|
|
v4l2_flash->ctrls = devm_kzalloc(v4l2_flash->sd.dev,
|
|
sizeof(*v4l2_flash->ctrls) *
|
|
(STROBE_SOURCE + 1), GFP_KERNEL);
|
|
if (!v4l2_flash->ctrls)
|
|
return -ENOMEM;
|
|
|
|
/* allocate memory dynamically so as not to exceed stack frame size */
|
|
ctrl_init_data = kcalloc(NUM_FLASH_CTRLS, sizeof(*ctrl_init_data),
|
|
GFP_KERNEL);
|
|
if (!ctrl_init_data)
|
|
return -ENOMEM;
|
|
|
|
__fill_ctrl_init_data(v4l2_flash, flash_cfg, ctrl_init_data);
|
|
|
|
for (i = 0; i < NUM_FLASH_CTRLS; ++i)
|
|
if (ctrl_init_data[i].cid)
|
|
++num_ctrls;
|
|
|
|
v4l2_ctrl_handler_init(&v4l2_flash->hdl, num_ctrls);
|
|
|
|
for (i = 0; i < NUM_FLASH_CTRLS; ++i) {
|
|
ctrl_cfg = &ctrl_init_data[i].config;
|
|
if (!ctrl_init_data[i].cid)
|
|
continue;
|
|
|
|
if (ctrl_cfg->id == V4L2_CID_FLASH_LED_MODE ||
|
|
ctrl_cfg->id == V4L2_CID_FLASH_STROBE_SOURCE)
|
|
ctrl = v4l2_ctrl_new_std_menu(&v4l2_flash->hdl,
|
|
&v4l2_flash_ctrl_ops,
|
|
ctrl_cfg->id,
|
|
ctrl_cfg->max,
|
|
ctrl_cfg->menu_skip_mask,
|
|
ctrl_cfg->def);
|
|
else
|
|
ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl,
|
|
&v4l2_flash_ctrl_ops,
|
|
ctrl_cfg->id,
|
|
ctrl_cfg->min,
|
|
ctrl_cfg->max,
|
|
ctrl_cfg->step,
|
|
ctrl_cfg->def);
|
|
|
|
if (ctrl)
|
|
ctrl->flags |= ctrl_cfg->flags;
|
|
|
|
if (i <= STROBE_SOURCE)
|
|
v4l2_flash->ctrls[i] = ctrl;
|
|
}
|
|
|
|
kfree(ctrl_init_data);
|
|
|
|
if (v4l2_flash->hdl.error) {
|
|
ret = v4l2_flash->hdl.error;
|
|
goto error_free_handler;
|
|
}
|
|
|
|
v4l2_ctrl_handler_setup(&v4l2_flash->hdl);
|
|
|
|
v4l2_flash->sd.ctrl_handler = &v4l2_flash->hdl;
|
|
|
|
return 0;
|
|
|
|
error_free_handler:
|
|
v4l2_ctrl_handler_free(&v4l2_flash->hdl);
|
|
return ret;
|
|
}
|
|
|
|
static int __sync_device_with_v4l2_controls(struct v4l2_flash *v4l2_flash)
|
|
{
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
|
|
int ret = 0;
|
|
|
|
if (ctrls[TORCH_INTENSITY])
|
|
v4l2_flash_set_led_brightness(v4l2_flash,
|
|
ctrls[TORCH_INTENSITY]);
|
|
|
|
if (ctrls[INDICATOR_INTENSITY])
|
|
v4l2_flash_set_led_brightness(v4l2_flash,
|
|
ctrls[INDICATOR_INTENSITY]);
|
|
|
|
if (ctrls[FLASH_TIMEOUT]) {
|
|
ret = led_set_flash_timeout(fled_cdev,
|
|
ctrls[FLASH_TIMEOUT]->val);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
if (ctrls[FLASH_INTENSITY]) {
|
|
ret = led_set_flash_brightness(fled_cdev,
|
|
ctrls[FLASH_INTENSITY]->val);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* For some hardware arrangements setting strobe source may affect
|
|
* torch mode. Synchronize strobe source setting only if not in torch
|
|
* mode. For torch mode case it will get synchronized upon switching
|
|
* to flash mode.
|
|
*/
|
|
if (ctrls[STROBE_SOURCE] &&
|
|
ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH)
|
|
ret = call_flash_op(v4l2_flash, external_strobe_set,
|
|
ctrls[STROBE_SOURCE]->val);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* V4L2 subdev internal operations
|
|
*/
|
|
|
|
static int v4l2_flash_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
|
|
{
|
|
struct v4l2_flash *v4l2_flash = v4l2_subdev_to_v4l2_flash(sd);
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
struct led_classdev *led_cdev = fled_cdev ? &fled_cdev->led_cdev : NULL;
|
|
struct led_classdev *led_cdev_ind = v4l2_flash->iled_cdev;
|
|
int ret = 0;
|
|
|
|
if (!v4l2_fh_is_singular(&fh->vfh))
|
|
return 0;
|
|
|
|
if (led_cdev) {
|
|
mutex_lock(&led_cdev->led_access);
|
|
|
|
led_sysfs_disable(led_cdev);
|
|
led_trigger_remove(led_cdev);
|
|
|
|
mutex_unlock(&led_cdev->led_access);
|
|
}
|
|
|
|
if (led_cdev_ind) {
|
|
mutex_lock(&led_cdev_ind->led_access);
|
|
|
|
led_sysfs_disable(led_cdev_ind);
|
|
led_trigger_remove(led_cdev_ind);
|
|
|
|
mutex_unlock(&led_cdev_ind->led_access);
|
|
}
|
|
|
|
ret = __sync_device_with_v4l2_controls(v4l2_flash);
|
|
if (ret < 0)
|
|
goto out_sync_device;
|
|
|
|
return 0;
|
|
out_sync_device:
|
|
if (led_cdev) {
|
|
mutex_lock(&led_cdev->led_access);
|
|
led_sysfs_enable(led_cdev);
|
|
mutex_unlock(&led_cdev->led_access);
|
|
}
|
|
|
|
if (led_cdev_ind) {
|
|
mutex_lock(&led_cdev_ind->led_access);
|
|
led_sysfs_enable(led_cdev_ind);
|
|
mutex_unlock(&led_cdev_ind->led_access);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int v4l2_flash_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
|
|
{
|
|
struct v4l2_flash *v4l2_flash = v4l2_subdev_to_v4l2_flash(sd);
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
struct led_classdev *led_cdev = fled_cdev ? &fled_cdev->led_cdev : NULL;
|
|
struct led_classdev *led_cdev_ind = v4l2_flash->iled_cdev;
|
|
int ret = 0;
|
|
|
|
if (!v4l2_fh_is_singular(&fh->vfh))
|
|
return 0;
|
|
|
|
if (led_cdev) {
|
|
mutex_lock(&led_cdev->led_access);
|
|
|
|
if (v4l2_flash->ctrls[STROBE_SOURCE])
|
|
ret = v4l2_ctrl_s_ctrl(
|
|
v4l2_flash->ctrls[STROBE_SOURCE],
|
|
V4L2_FLASH_STROBE_SOURCE_SOFTWARE);
|
|
led_sysfs_enable(led_cdev);
|
|
|
|
mutex_unlock(&led_cdev->led_access);
|
|
}
|
|
|
|
if (led_cdev_ind) {
|
|
mutex_lock(&led_cdev_ind->led_access);
|
|
led_sysfs_enable(led_cdev_ind);
|
|
mutex_unlock(&led_cdev_ind->led_access);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct v4l2_subdev_internal_ops v4l2_flash_subdev_internal_ops = {
|
|
.open = v4l2_flash_open,
|
|
.close = v4l2_flash_close,
|
|
};
|
|
|
|
static const struct v4l2_subdev_ops v4l2_flash_subdev_ops;
|
|
|
|
static struct v4l2_flash *__v4l2_flash_init(
|
|
struct device *dev, struct fwnode_handle *fwn,
|
|
struct led_classdev_flash *fled_cdev, struct led_classdev *iled_cdev,
|
|
const struct v4l2_flash_ops *ops, struct v4l2_flash_config *config)
|
|
{
|
|
struct v4l2_flash *v4l2_flash;
|
|
struct v4l2_subdev *sd;
|
|
int ret;
|
|
|
|
if (!config)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
v4l2_flash = devm_kzalloc(dev, sizeof(*v4l2_flash), GFP_KERNEL);
|
|
if (!v4l2_flash)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
sd = &v4l2_flash->sd;
|
|
v4l2_flash->fled_cdev = fled_cdev;
|
|
v4l2_flash->iled_cdev = iled_cdev;
|
|
v4l2_flash->ops = ops;
|
|
sd->dev = dev;
|
|
sd->fwnode = fwn ? fwn : dev_fwnode(dev);
|
|
v4l2_subdev_init(sd, &v4l2_flash_subdev_ops);
|
|
sd->internal_ops = &v4l2_flash_subdev_internal_ops;
|
|
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
strlcpy(sd->name, config->dev_name, sizeof(sd->name));
|
|
|
|
ret = media_entity_pads_init(&sd->entity, 0, NULL);
|
|
if (ret < 0)
|
|
return ERR_PTR(ret);
|
|
|
|
sd->entity.function = MEDIA_ENT_F_FLASH;
|
|
|
|
ret = v4l2_flash_init_controls(v4l2_flash, config);
|
|
if (ret < 0)
|
|
goto err_init_controls;
|
|
|
|
fwnode_handle_get(sd->fwnode);
|
|
|
|
ret = v4l2_async_register_subdev(sd);
|
|
if (ret < 0)
|
|
goto err_async_register_sd;
|
|
|
|
return v4l2_flash;
|
|
|
|
err_async_register_sd:
|
|
fwnode_handle_put(sd->fwnode);
|
|
v4l2_ctrl_handler_free(sd->ctrl_handler);
|
|
err_init_controls:
|
|
media_entity_cleanup(&sd->entity);
|
|
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
struct v4l2_flash *v4l2_flash_init(
|
|
struct device *dev, struct fwnode_handle *fwn,
|
|
struct led_classdev_flash *fled_cdev,
|
|
const struct v4l2_flash_ops *ops,
|
|
struct v4l2_flash_config *config)
|
|
{
|
|
return __v4l2_flash_init(dev, fwn, fled_cdev, NULL, ops, config);
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_flash_init);
|
|
|
|
struct v4l2_flash *v4l2_flash_indicator_init(
|
|
struct device *dev, struct fwnode_handle *fwn,
|
|
struct led_classdev *iled_cdev,
|
|
struct v4l2_flash_config *config)
|
|
{
|
|
return __v4l2_flash_init(dev, fwn, NULL, iled_cdev, NULL, config);
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_flash_indicator_init);
|
|
|
|
void v4l2_flash_release(struct v4l2_flash *v4l2_flash)
|
|
{
|
|
struct v4l2_subdev *sd;
|
|
|
|
if (IS_ERR_OR_NULL(v4l2_flash))
|
|
return;
|
|
|
|
sd = &v4l2_flash->sd;
|
|
|
|
v4l2_async_unregister_subdev(sd);
|
|
|
|
fwnode_handle_put(sd->fwnode);
|
|
|
|
v4l2_ctrl_handler_free(sd->ctrl_handler);
|
|
media_entity_cleanup(&sd->entity);
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_flash_release);
|
|
|
|
MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
|
|
MODULE_DESCRIPTION("V4L2 Flash sub-device helpers");
|
|
MODULE_LICENSE("GPL v2");
|