mirror of
https://github.com/torvalds/linux.git
synced 2024-11-05 19:41:54 +00:00
77a76b04d2
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJWlNJNAAoJEAhfPr2O5OEVrVEP/3wGrkzC3ykROabLiuFx6+uN fUxMlSnnIwSPsiK7OIP45EHI7PJr2zRLfg4p3X6ABOTBtycziUQGP3ARCcqllrSG Tl7KoJokNBTiNRPY2nefC3gB7H36D+TBv3zgpR+vPggr6HGSfc8c6y6sETl/IAvl do7EltQ5BUJzgQ9/ZAM/FLBLNSLGexkqeJe7EC5IX0hqJ/tlc1vqIEu2xcdsG1cd w1k03C+/ukOr1wfcVmqlh9K2WCPZ59V0c3XcNS+4SeCKH4wnmmf4/BOrAuRFtCjh 691RuXHXpZKdX1l9TVAmp9CuVvXNDjWdFfNovxVG8POn9TF769zI6t+9rCBSg5Gb lzSMOB75/EArOml8sU7gxhEsVsVw9RlukMN7luVwiqPi9vDSPzQw0fzvYkX85Ay3 TMBW2z1cVjWBvjotTBkSg/rgqetmTgaU04kJ2vIqlfLLd8r/8o5ILZU5WzQtugLJ HHipyqG5FZIhXCRmqmr1cEmk+PKP8IA59zKQQG7Z2W+H3rqM6jakc3AxXtJFcz08 8c4gwXrNBNCWYh9x6e3h0x3p5pdTvrhH0cG9BxWYwaqdCiFZVL+3ZgZB0LmlRc0m shJkNAGNX503SqeMQOJnfwEi7Kdeb8mIQBSvM1ZmCrheNwyxtrdJnFkJK3w+q/Tf PsycImJtKpskfiXbhqWR =6J4t -----END PGP SIGNATURE----- Merge tag 'media/v4.5-2' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media Pull second batch of media updates from Mauro Carvalho Chehab: "This is the second part of the media patches. It contains the media controller next generation patches, with is the result of one year of discussions and development. It also contains patches to enable media controller support at the DVB subsystem. The goal is to improve the media controller to allow proper support for other types of Video4Linux devices (radio and TV ones) and to extend the media controller functionality to allow it to be used by other subsystems like DVB, ALSA and IIO. In order to use the new functionality, a new ioctl is needed (MEDIA_IOC_G_TOPOLOGY). As we're still discussing how to pack the struct fields of this ioctl in order to avoid compat32 issues, I decided to add a patch at the end of this series commenting out the new ioctl, in order to postpone the addition of the new ioctl to the next Kernel version (4.6). With that, no userspace visible changes should happen at the media controller API, as the existing ioctls are untouched. Yet, it helps DVB, ALSA and IIO developers to develop and test the patches adding media controller support there, as the core will contain all required internal changes to allow adding support for devices that belong to those subsystems" * tag 'media/v4.5-2' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (177 commits) [media] Postpone the addition of MEDIA_IOC_G_TOPOLOGY [media] mxl111sf: Add a tuner entity [media] dvbdev: create links on devices with multiple frontends [media] media-entitiy: add a function to create multiple links [media] dvb-usb-v2: postpone removal of media_device [media] dvbdev: Add RF connector if needed [media] dvbdev: remove two dead functions if !CONFIG_MEDIA_CONTROLLER_DVB [media] call media_device_init() before registering the V4L2 device [media] uapi/media.h: Use u32 for the number of graph objects [media] media-entity: don't sleep at media_device_register_entity() [media] media-entity: increase max number of PADs [media] media-entity.h: document the remaining functions [media] media-device.h: use just one u32 counter for object ID [media] media-entity.h fix documentation for several parameters [media] DocBook: document media_entity_graph_walk_cleanup() [media] move documentation to the header files [media] media: Move MEDIA_ENTITY_MAX_PADS from media-entity.h to media-entity.c [media] media: Remove pre-allocated entity enumeration bitmap [media] staging: v4l: davinci_vpbe: Use the new media graph walk interface [media] staging: v4l: omap4iss: Use the new media graph walk interface ...
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/of.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/types.h>
|
|
#include <media/v4l2-flash-led-class.h>
|
|
|
|
#define has_flash_op(v4l2_flash, op) \
|
|
(v4l2_flash && v4l2_flash->ops->op)
|
|
|
|
#define call_flash_op(v4l2_flash, op, arg) \
|
|
(has_flash_op(v4l2_flash, op) ? \
|
|
v4l2_flash->ops->op(v4l2_flash, arg) : \
|
|
-EINVAL)
|
|
|
|
enum ctrl_init_data_id {
|
|
LED_MODE,
|
|
TORCH_INTENSITY,
|
|
FLASH_INTENSITY,
|
|
INDICATOR_INTENSITY,
|
|
FLASH_TIMEOUT,
|
|
STROBE_SOURCE,
|
|
/*
|
|
* Only above values are applicable to
|
|
* the 'ctrls' array in the struct v4l2_flash.
|
|
*/
|
|
FLASH_STROBE,
|
|
STROBE_STOP,
|
|
STROBE_STATUS,
|
|
FLASH_FAULT,
|
|
NUM_FLASH_CTRLS,
|
|
};
|
|
|
|
static enum led_brightness __intensity_to_led_brightness(
|
|
struct v4l2_ctrl *ctrl, s32 intensity)
|
|
{
|
|
intensity -= ctrl->minimum;
|
|
intensity /= (u32) ctrl->step;
|
|
|
|
/*
|
|
* Indicator LEDs, unlike torch LEDs, are turned on/off basing on
|
|
* the state of V4L2_CID_FLASH_INDICATOR_INTENSITY control only.
|
|
* Therefore it must be possible to set it to 0 level which in
|
|
* the LED subsystem reflects LED_OFF state.
|
|
*/
|
|
if (ctrl->minimum)
|
|
++intensity;
|
|
|
|
return intensity;
|
|
}
|
|
|
|
static s32 __led_brightness_to_intensity(struct v4l2_ctrl *ctrl,
|
|
enum led_brightness brightness)
|
|
{
|
|
/*
|
|
* Indicator LEDs, unlike torch LEDs, are turned on/off basing on
|
|
* the state of V4L2_CID_FLASH_INDICATOR_INTENSITY control only.
|
|
* Do not decrement brightness read from the LED subsystem for
|
|
* indicator LED as it may equal 0. For torch LEDs this function
|
|
* is called only when V4L2_FLASH_LED_MODE_TORCH is set and the
|
|
* brightness read is guaranteed to be greater than 0. In the mode
|
|
* V4L2_FLASH_LED_MODE_NONE the cached torch intensity value is used.
|
|
*/
|
|
if (ctrl->id != V4L2_CID_FLASH_INDICATOR_INTENSITY)
|
|
--brightness;
|
|
|
|
return (brightness * ctrl->step) + ctrl->minimum;
|
|
}
|
|
|
|
static void v4l2_flash_set_led_brightness(struct v4l2_flash *v4l2_flash,
|
|
struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
|
|
enum led_brightness brightness;
|
|
|
|
if (has_flash_op(v4l2_flash, intensity_to_led_brightness))
|
|
brightness = call_flash_op(v4l2_flash,
|
|
intensity_to_led_brightness,
|
|
ctrl->val);
|
|
else
|
|
brightness = __intensity_to_led_brightness(ctrl, ctrl->val);
|
|
/*
|
|
* In case a LED Flash class driver provides ops for custom
|
|
* brightness <-> intensity conversion, it also must have defined
|
|
* related v4l2 control step == 1. In such a case a backward conversion
|
|
* from led brightness to v4l2 intensity is required to find out the
|
|
* the aligned intensity value.
|
|
*/
|
|
if (has_flash_op(v4l2_flash, led_brightness_to_intensity))
|
|
ctrl->val = call_flash_op(v4l2_flash,
|
|
led_brightness_to_intensity,
|
|
brightness);
|
|
|
|
if (ctrl == ctrls[TORCH_INTENSITY]) {
|
|
if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH)
|
|
return;
|
|
|
|
led_set_brightness_sync(&v4l2_flash->fled_cdev->led_cdev,
|
|
brightness);
|
|
} else {
|
|
led_set_brightness_sync(&v4l2_flash->iled_cdev->led_cdev,
|
|
brightness);
|
|
}
|
|
}
|
|
|
|
static int v4l2_flash_update_led_brightness(struct v4l2_flash *v4l2_flash,
|
|
struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
|
|
struct led_classdev *led_cdev;
|
|
int ret;
|
|
|
|
if (ctrl == ctrls[TORCH_INTENSITY]) {
|
|
/*
|
|
* Update torch brightness only if in TORCH_MODE. In other modes
|
|
* torch led is turned off, which would spuriously inform the
|
|
* user space that V4L2_CID_FLASH_TORCH_INTENSITY control value
|
|
* has changed to 0.
|
|
*/
|
|
if (ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH)
|
|
return 0;
|
|
led_cdev = &v4l2_flash->fled_cdev->led_cdev;
|
|
} else {
|
|
led_cdev = &v4l2_flash->iled_cdev->led_cdev;
|
|
}
|
|
|
|
ret = led_update_brightness(led_cdev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (has_flash_op(v4l2_flash, led_brightness_to_intensity))
|
|
ctrl->val = call_flash_op(v4l2_flash,
|
|
led_brightness_to_intensity,
|
|
led_cdev->brightness);
|
|
else
|
|
ctrl->val = __led_brightness_to_intensity(ctrl,
|
|
led_cdev->brightness);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int v4l2_flash_g_volatile_ctrl(struct v4l2_ctrl *c)
|
|
{
|
|
struct v4l2_flash *v4l2_flash = v4l2_ctrl_to_v4l2_flash(c);
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
bool is_strobing;
|
|
int ret;
|
|
|
|
switch (c->id) {
|
|
case V4L2_CID_FLASH_TORCH_INTENSITY:
|
|
case V4L2_CID_FLASH_INDICATOR_INTENSITY:
|
|
return v4l2_flash_update_led_brightness(v4l2_flash, c);
|
|
case V4L2_CID_FLASH_INTENSITY:
|
|
ret = led_update_flash_brightness(fled_cdev);
|
|
if (ret < 0)
|
|
return ret;
|
|
/*
|
|
* No conversion is needed as LED Flash class also uses
|
|
* microamperes for flash intensity units.
|
|
*/
|
|
c->val = fled_cdev->brightness.val;
|
|
return 0;
|
|
case V4L2_CID_FLASH_STROBE_STATUS:
|
|
ret = led_get_flash_strobe(fled_cdev, &is_strobing);
|
|
if (ret < 0)
|
|
return ret;
|
|
c->val = is_strobing;
|
|
return 0;
|
|
case V4L2_CID_FLASH_FAULT:
|
|
/* LED faults map directly to V4L2 flash faults */
|
|
return led_get_flash_fault(fled_cdev, &c->val);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static bool __software_strobe_mode_inactive(struct v4l2_ctrl **ctrls)
|
|
{
|
|
return ((ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_FLASH) ||
|
|
(ctrls[STROBE_SOURCE] && (ctrls[STROBE_SOURCE]->val !=
|
|
V4L2_FLASH_STROBE_SOURCE_SOFTWARE)));
|
|
}
|
|
|
|
static int v4l2_flash_s_ctrl(struct v4l2_ctrl *c)
|
|
{
|
|
struct v4l2_flash *v4l2_flash = v4l2_ctrl_to_v4l2_flash(c);
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
struct led_classdev *led_cdev = &fled_cdev->led_cdev;
|
|
struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
|
|
bool external_strobe;
|
|
int ret = 0;
|
|
|
|
switch (c->id) {
|
|
case V4L2_CID_FLASH_LED_MODE:
|
|
switch (c->val) {
|
|
case V4L2_FLASH_LED_MODE_NONE:
|
|
led_set_brightness_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;
|
|
const struct led_flash_ops *fled_cdev_ops = fled_cdev->ops;
|
|
struct led_classdev *led_cdev = &fled_cdev->led_cdev;
|
|
struct v4l2_ctrl_config *ctrl_cfg;
|
|
u32 mask;
|
|
|
|
/* Init FLASH_FAULT ctrl data */
|
|
if (flash_cfg->flash_faults) {
|
|
ctrl_init_data[FLASH_FAULT].cid = V4L2_CID_FLASH_FAULT;
|
|
ctrl_cfg = &ctrl_init_data[FLASH_FAULT].config;
|
|
ctrl_cfg->id = V4L2_CID_FLASH_FAULT;
|
|
ctrl_cfg->max = flash_cfg->flash_faults;
|
|
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
|
|
V4L2_CTRL_FLAG_READ_ONLY;
|
|
}
|
|
|
|
/* Init FLASH_LED_MODE ctrl data */
|
|
mask = 1 << V4L2_FLASH_LED_MODE_NONE |
|
|
1 << V4L2_FLASH_LED_MODE_TORCH;
|
|
if (led_cdev->flags & LED_DEV_CAP_FLASH)
|
|
mask |= 1 << V4L2_FLASH_LED_MODE_FLASH;
|
|
|
|
ctrl_init_data[LED_MODE].cid = V4L2_CID_FLASH_LED_MODE;
|
|
ctrl_cfg = &ctrl_init_data[LED_MODE].config;
|
|
ctrl_cfg->id = V4L2_CID_FLASH_LED_MODE;
|
|
ctrl_cfg->max = V4L2_FLASH_LED_MODE_TORCH;
|
|
ctrl_cfg->menu_skip_mask = ~mask;
|
|
ctrl_cfg->def = V4L2_FLASH_LED_MODE_NONE;
|
|
ctrl_cfg->flags = 0;
|
|
|
|
/* Init TORCH_INTENSITY ctrl data */
|
|
ctrl_init_data[TORCH_INTENSITY].cid = V4L2_CID_FLASH_TORCH_INTENSITY;
|
|
ctrl_cfg = &ctrl_init_data[TORCH_INTENSITY].config;
|
|
__lfs_to_v4l2_ctrl_config(&flash_cfg->torch_intensity, ctrl_cfg);
|
|
ctrl_cfg->id = V4L2_CID_FLASH_TORCH_INTENSITY;
|
|
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
|
|
V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
|
|
|
|
/* Init INDICATOR_INTENSITY ctrl data */
|
|
if (v4l2_flash->iled_cdev) {
|
|
ctrl_init_data[INDICATOR_INTENSITY].cid =
|
|
V4L2_CID_FLASH_INDICATOR_INTENSITY;
|
|
ctrl_cfg = &ctrl_init_data[INDICATOR_INTENSITY].config;
|
|
__lfs_to_v4l2_ctrl_config(&flash_cfg->indicator_intensity,
|
|
ctrl_cfg);
|
|
ctrl_cfg->id = V4L2_CID_FLASH_INDICATOR_INTENSITY;
|
|
ctrl_cfg->min = 0;
|
|
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
|
|
V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
|
|
}
|
|
|
|
if (!(led_cdev->flags & LED_DEV_CAP_FLASH))
|
|
return;
|
|
|
|
/* Init FLASH_STROBE ctrl data */
|
|
ctrl_init_data[FLASH_STROBE].cid = V4L2_CID_FLASH_STROBE;
|
|
ctrl_cfg = &ctrl_init_data[FLASH_STROBE].config;
|
|
ctrl_cfg->id = V4L2_CID_FLASH_STROBE;
|
|
|
|
/* Init STROBE_STOP ctrl data */
|
|
ctrl_init_data[STROBE_STOP].cid = V4L2_CID_FLASH_STROBE_STOP;
|
|
ctrl_cfg = &ctrl_init_data[STROBE_STOP].config;
|
|
ctrl_cfg->id = V4L2_CID_FLASH_STROBE_STOP;
|
|
|
|
/* Init FLASH_STROBE_SOURCE ctrl data */
|
|
if (flash_cfg->has_external_strobe) {
|
|
mask = (1 << V4L2_FLASH_STROBE_SOURCE_SOFTWARE) |
|
|
(1 << V4L2_FLASH_STROBE_SOURCE_EXTERNAL);
|
|
ctrl_init_data[STROBE_SOURCE].cid =
|
|
V4L2_CID_FLASH_STROBE_SOURCE;
|
|
ctrl_cfg = &ctrl_init_data[STROBE_SOURCE].config;
|
|
ctrl_cfg->id = V4L2_CID_FLASH_STROBE_SOURCE;
|
|
ctrl_cfg->max = V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
|
|
ctrl_cfg->menu_skip_mask = ~mask;
|
|
ctrl_cfg->def = V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
|
|
}
|
|
|
|
/* Init STROBE_STATUS ctrl data */
|
|
if (fled_cdev_ops->strobe_get) {
|
|
ctrl_init_data[STROBE_STATUS].cid =
|
|
V4L2_CID_FLASH_STROBE_STATUS;
|
|
ctrl_cfg = &ctrl_init_data[STROBE_STATUS].config;
|
|
ctrl_cfg->id = V4L2_CID_FLASH_STROBE_STATUS;
|
|
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
|
|
V4L2_CTRL_FLAG_READ_ONLY;
|
|
}
|
|
|
|
/* Init FLASH_TIMEOUT ctrl data */
|
|
if (fled_cdev_ops->timeout_set) {
|
|
ctrl_init_data[FLASH_TIMEOUT].cid = V4L2_CID_FLASH_TIMEOUT;
|
|
ctrl_cfg = &ctrl_init_data[FLASH_TIMEOUT].config;
|
|
__lfs_to_v4l2_ctrl_config(&fled_cdev->timeout, ctrl_cfg);
|
|
ctrl_cfg->id = V4L2_CID_FLASH_TIMEOUT;
|
|
}
|
|
|
|
/* Init FLASH_INTENSITY ctrl data */
|
|
if (fled_cdev_ops->flash_brightness_set) {
|
|
ctrl_init_data[FLASH_INTENSITY].cid = V4L2_CID_FLASH_INTENSITY;
|
|
ctrl_cfg = &ctrl_init_data[FLASH_INTENSITY].config;
|
|
__lfs_to_v4l2_ctrl_config(&fled_cdev->brightness, ctrl_cfg);
|
|
ctrl_cfg->id = V4L2_CID_FLASH_INTENSITY;
|
|
ctrl_cfg->flags = V4L2_CTRL_FLAG_VOLATILE |
|
|
V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
|
|
}
|
|
}
|
|
|
|
static int v4l2_flash_init_controls(struct v4l2_flash *v4l2_flash,
|
|
struct v4l2_flash_config *flash_cfg)
|
|
|
|
{
|
|
struct v4l2_flash_ctrl_data *ctrl_init_data;
|
|
struct v4l2_ctrl *ctrl;
|
|
struct v4l2_ctrl_config *ctrl_cfg;
|
|
int i, ret, num_ctrls = 0;
|
|
|
|
v4l2_flash->ctrls = devm_kzalloc(v4l2_flash->sd.dev,
|
|
sizeof(*v4l2_flash->ctrls) *
|
|
(STROBE_SOURCE + 1), GFP_KERNEL);
|
|
if (!v4l2_flash->ctrls)
|
|
return -ENOMEM;
|
|
|
|
/* allocate memory dynamically so as not to exceed stack frame size */
|
|
ctrl_init_data = kcalloc(NUM_FLASH_CTRLS, sizeof(*ctrl_init_data),
|
|
GFP_KERNEL);
|
|
if (!ctrl_init_data)
|
|
return -ENOMEM;
|
|
|
|
__fill_ctrl_init_data(v4l2_flash, flash_cfg, ctrl_init_data);
|
|
|
|
for (i = 0; i < NUM_FLASH_CTRLS; ++i)
|
|
if (ctrl_init_data[i].cid)
|
|
++num_ctrls;
|
|
|
|
v4l2_ctrl_handler_init(&v4l2_flash->hdl, num_ctrls);
|
|
|
|
for (i = 0; i < NUM_FLASH_CTRLS; ++i) {
|
|
ctrl_cfg = &ctrl_init_data[i].config;
|
|
if (!ctrl_init_data[i].cid)
|
|
continue;
|
|
|
|
if (ctrl_cfg->id == V4L2_CID_FLASH_LED_MODE ||
|
|
ctrl_cfg->id == V4L2_CID_FLASH_STROBE_SOURCE)
|
|
ctrl = v4l2_ctrl_new_std_menu(&v4l2_flash->hdl,
|
|
&v4l2_flash_ctrl_ops,
|
|
ctrl_cfg->id,
|
|
ctrl_cfg->max,
|
|
ctrl_cfg->menu_skip_mask,
|
|
ctrl_cfg->def);
|
|
else
|
|
ctrl = v4l2_ctrl_new_std(&v4l2_flash->hdl,
|
|
&v4l2_flash_ctrl_ops,
|
|
ctrl_cfg->id,
|
|
ctrl_cfg->min,
|
|
ctrl_cfg->max,
|
|
ctrl_cfg->step,
|
|
ctrl_cfg->def);
|
|
|
|
if (ctrl)
|
|
ctrl->flags |= ctrl_cfg->flags;
|
|
|
|
if (i <= STROBE_SOURCE)
|
|
v4l2_flash->ctrls[i] = ctrl;
|
|
}
|
|
|
|
kfree(ctrl_init_data);
|
|
|
|
if (v4l2_flash->hdl.error) {
|
|
ret = v4l2_flash->hdl.error;
|
|
goto error_free_handler;
|
|
}
|
|
|
|
v4l2_ctrl_handler_setup(&v4l2_flash->hdl);
|
|
|
|
v4l2_flash->sd.ctrl_handler = &v4l2_flash->hdl;
|
|
|
|
return 0;
|
|
|
|
error_free_handler:
|
|
v4l2_ctrl_handler_free(&v4l2_flash->hdl);
|
|
return ret;
|
|
}
|
|
|
|
static int __sync_device_with_v4l2_controls(struct v4l2_flash *v4l2_flash)
|
|
{
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
struct v4l2_ctrl **ctrls = v4l2_flash->ctrls;
|
|
int ret = 0;
|
|
|
|
v4l2_flash_set_led_brightness(v4l2_flash, ctrls[TORCH_INTENSITY]);
|
|
|
|
if (ctrls[INDICATOR_INTENSITY])
|
|
v4l2_flash_set_led_brightness(v4l2_flash,
|
|
ctrls[INDICATOR_INTENSITY]);
|
|
|
|
if (ctrls[FLASH_TIMEOUT]) {
|
|
ret = led_set_flash_timeout(fled_cdev,
|
|
ctrls[FLASH_TIMEOUT]->val);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
if (ctrls[FLASH_INTENSITY]) {
|
|
ret = led_set_flash_brightness(fled_cdev,
|
|
ctrls[FLASH_INTENSITY]->val);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* For some hardware arrangements setting strobe source may affect
|
|
* torch mode. Synchronize strobe source setting only if not in torch
|
|
* mode. For torch mode case it will get synchronized upon switching
|
|
* to flash mode.
|
|
*/
|
|
if (ctrls[STROBE_SOURCE] &&
|
|
ctrls[LED_MODE]->val != V4L2_FLASH_LED_MODE_TORCH)
|
|
ret = call_flash_op(v4l2_flash, external_strobe_set,
|
|
ctrls[STROBE_SOURCE]->val);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* V4L2 subdev internal operations
|
|
*/
|
|
|
|
static int v4l2_flash_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
|
|
{
|
|
struct v4l2_flash *v4l2_flash = v4l2_subdev_to_v4l2_flash(sd);
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
struct led_classdev *led_cdev = &fled_cdev->led_cdev;
|
|
struct led_classdev_flash *iled_cdev = v4l2_flash->iled_cdev;
|
|
struct led_classdev *led_cdev_ind = NULL;
|
|
int ret = 0;
|
|
|
|
if (!v4l2_fh_is_singular(&fh->vfh))
|
|
return 0;
|
|
|
|
mutex_lock(&led_cdev->led_access);
|
|
|
|
led_sysfs_disable(led_cdev);
|
|
led_trigger_remove(led_cdev);
|
|
|
|
mutex_unlock(&led_cdev->led_access);
|
|
|
|
if (iled_cdev) {
|
|
led_cdev_ind = &iled_cdev->led_cdev;
|
|
|
|
mutex_lock(&led_cdev_ind->led_access);
|
|
|
|
led_sysfs_disable(led_cdev_ind);
|
|
led_trigger_remove(led_cdev_ind);
|
|
|
|
mutex_unlock(&led_cdev_ind->led_access);
|
|
}
|
|
|
|
ret = __sync_device_with_v4l2_controls(v4l2_flash);
|
|
if (ret < 0)
|
|
goto out_sync_device;
|
|
|
|
return 0;
|
|
out_sync_device:
|
|
mutex_lock(&led_cdev->led_access);
|
|
led_sysfs_enable(led_cdev);
|
|
mutex_unlock(&led_cdev->led_access);
|
|
|
|
if (led_cdev_ind) {
|
|
mutex_lock(&led_cdev_ind->led_access);
|
|
led_sysfs_enable(led_cdev_ind);
|
|
mutex_unlock(&led_cdev_ind->led_access);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int v4l2_flash_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
|
|
{
|
|
struct v4l2_flash *v4l2_flash = v4l2_subdev_to_v4l2_flash(sd);
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
struct led_classdev *led_cdev = &fled_cdev->led_cdev;
|
|
struct led_classdev_flash *iled_cdev = v4l2_flash->iled_cdev;
|
|
int ret = 0;
|
|
|
|
if (!v4l2_fh_is_singular(&fh->vfh))
|
|
return 0;
|
|
|
|
mutex_lock(&led_cdev->led_access);
|
|
|
|
if (v4l2_flash->ctrls[STROBE_SOURCE])
|
|
ret = v4l2_ctrl_s_ctrl(v4l2_flash->ctrls[STROBE_SOURCE],
|
|
V4L2_FLASH_STROBE_SOURCE_SOFTWARE);
|
|
led_sysfs_enable(led_cdev);
|
|
|
|
mutex_unlock(&led_cdev->led_access);
|
|
|
|
if (iled_cdev) {
|
|
struct led_classdev *led_cdev_ind = &iled_cdev->led_cdev;
|
|
|
|
mutex_lock(&led_cdev_ind->led_access);
|
|
led_sysfs_enable(led_cdev_ind);
|
|
mutex_unlock(&led_cdev_ind->led_access);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct v4l2_subdev_internal_ops v4l2_flash_subdev_internal_ops = {
|
|
.open = v4l2_flash_open,
|
|
.close = v4l2_flash_close,
|
|
};
|
|
|
|
static const struct v4l2_subdev_core_ops v4l2_flash_core_ops = {
|
|
.queryctrl = v4l2_subdev_queryctrl,
|
|
.querymenu = v4l2_subdev_querymenu,
|
|
};
|
|
|
|
static const struct v4l2_subdev_ops v4l2_flash_subdev_ops = {
|
|
.core = &v4l2_flash_core_ops,
|
|
};
|
|
|
|
struct v4l2_flash *v4l2_flash_init(
|
|
struct device *dev, struct device_node *of_node,
|
|
struct led_classdev_flash *fled_cdev,
|
|
struct led_classdev_flash *iled_cdev,
|
|
const struct v4l2_flash_ops *ops,
|
|
struct v4l2_flash_config *config)
|
|
{
|
|
struct v4l2_flash *v4l2_flash;
|
|
struct led_classdev *led_cdev;
|
|
struct v4l2_subdev *sd;
|
|
int ret;
|
|
|
|
if (!fled_cdev || !ops || !config)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
led_cdev = &fled_cdev->led_cdev;
|
|
|
|
v4l2_flash = devm_kzalloc(led_cdev->dev, sizeof(*v4l2_flash),
|
|
GFP_KERNEL);
|
|
if (!v4l2_flash)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
sd = &v4l2_flash->sd;
|
|
v4l2_flash->fled_cdev = fled_cdev;
|
|
v4l2_flash->iled_cdev = iled_cdev;
|
|
v4l2_flash->ops = ops;
|
|
sd->dev = dev;
|
|
sd->of_node = of_node;
|
|
v4l2_subdev_init(sd, &v4l2_flash_subdev_ops);
|
|
sd->internal_ops = &v4l2_flash_subdev_internal_ops;
|
|
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
strlcpy(sd->name, config->dev_name, sizeof(sd->name));
|
|
|
|
ret = media_entity_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;
|
|
|
|
if (sd->of_node)
|
|
of_node_get(sd->of_node);
|
|
else
|
|
of_node_get(led_cdev->dev->of_node);
|
|
|
|
ret = v4l2_async_register_subdev(sd);
|
|
if (ret < 0)
|
|
goto err_async_register_sd;
|
|
|
|
return v4l2_flash;
|
|
|
|
err_async_register_sd:
|
|
of_node_put(led_cdev->dev->of_node);
|
|
v4l2_ctrl_handler_free(sd->ctrl_handler);
|
|
err_init_controls:
|
|
media_entity_cleanup(&sd->entity);
|
|
|
|
return ERR_PTR(ret);
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_flash_init);
|
|
|
|
void v4l2_flash_release(struct v4l2_flash *v4l2_flash)
|
|
{
|
|
struct v4l2_subdev *sd;
|
|
struct led_classdev *led_cdev;
|
|
|
|
if (IS_ERR_OR_NULL(v4l2_flash))
|
|
return;
|
|
|
|
sd = &v4l2_flash->sd;
|
|
led_cdev = &v4l2_flash->fled_cdev->led_cdev;
|
|
|
|
v4l2_async_unregister_subdev(sd);
|
|
|
|
if (sd->of_node)
|
|
of_node_put(sd->of_node);
|
|
else
|
|
of_node_put(led_cdev->dev->of_node);
|
|
|
|
v4l2_ctrl_handler_free(sd->ctrl_handler);
|
|
media_entity_cleanup(&sd->entity);
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_flash_release);
|
|
|
|
MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
|
|
MODULE_DESCRIPTION("V4L2 Flash sub-device helpers");
|
|
MODULE_LICENSE("GPL v2");
|