661 lines
16 KiB
C
661 lines
16 KiB
C
|
/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <linux/jiffies.h>
|
||
|
#include <linux/sched.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/kernel.h>
|
||
|
|
||
|
#include "msm_ba_debug.h"
|
||
|
#include "msm_ba_common.h"
|
||
|
|
||
|
struct msm_ba_input_config msm_ba_inp_cfg[] = {
|
||
|
/* type, index, name, adv inp, dev id, sd name, signal status */
|
||
|
{BA_INPUT_CVBS, 0, "CVBS-0", BA_IP_CVBS_0, 0, "adv7180", 1},
|
||
|
#ifdef CONFIG_MSM_S_PLATFORM
|
||
|
{BA_INPUT_CVBS, 1, "CVBS-1", BA_IP_CVBS_0, 0, "adv7180", 1},
|
||
|
#else
|
||
|
{BA_INPUT_CVBS, 1, "CVBS-1", BA_IP_CVBS_0, 1, "adv7180", 1},
|
||
|
{BA_INPUT_CVBS, 2, "CVBS-2", BA_IP_CVBS_1, 1, "adv7180", 1},
|
||
|
#endif
|
||
|
{BA_INPUT_HDMI, 1, "HDMI-1", BA_IP_HDMI_1, 2, "adv7481", 1},
|
||
|
};
|
||
|
|
||
|
static struct msm_ba_ctrl msm_ba_ctrls[] = {
|
||
|
{
|
||
|
.id = MSM_BA_PRIV_SD_NODE_ADDR,
|
||
|
.name = "Sub-device Node Address",
|
||
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
||
|
.minimum = 0,
|
||
|
.maximum = 16,
|
||
|
.default_value = 0,
|
||
|
.step = 2,
|
||
|
.menu_skip_mask = 0,
|
||
|
.flags = V4L2_CTRL_FLAG_VOLATILE,
|
||
|
.qmenu = NULL,
|
||
|
},
|
||
|
{
|
||
|
.id = MSM_BA_PRIV_FPS,
|
||
|
.name = "FPS in Q16 format",
|
||
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
||
|
.minimum = 0,
|
||
|
.maximum = 0x7fffffff,
|
||
|
.default_value = 60 << 16,
|
||
|
.step = 1,
|
||
|
.menu_skip_mask = 0,
|
||
|
.flags = V4L2_CTRL_FLAG_VOLATILE,
|
||
|
.qmenu = NULL,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
#define BA_NUM_CTRLS ARRAY_SIZE(msm_ba_ctrls)
|
||
|
|
||
|
/* Assuming den is not zero, max 32 bits */
|
||
|
#define BA_FRAC_TO_Q16(q, num, den) { \
|
||
|
uint32_t pwr; \
|
||
|
pwr = ilog2(den); \
|
||
|
(q) = (num) << (16 - pwr); \
|
||
|
}
|
||
|
|
||
|
struct msm_ba_dev *get_ba_dev(void)
|
||
|
{
|
||
|
struct ba_ctxt *ba_ctxt;
|
||
|
struct msm_ba_dev *dev_ctxt = NULL;
|
||
|
|
||
|
ba_ctxt = msm_ba_get_ba_context();
|
||
|
|
||
|
mutex_lock(&ba_ctxt->ba_cs);
|
||
|
dev_ctxt = ba_ctxt->dev_ctxt;
|
||
|
mutex_unlock(&ba_ctxt->ba_cs);
|
||
|
|
||
|
return dev_ctxt;
|
||
|
}
|
||
|
|
||
|
void msm_ba_queue_v4l2_event(struct msm_ba_inst *inst,
|
||
|
const struct v4l2_event *sd_event)
|
||
|
{
|
||
|
v4l2_event_queue_fh(&inst->event_handler, sd_event);
|
||
|
wake_up(&inst->kernel_event_queue);
|
||
|
}
|
||
|
|
||
|
static void msm_ba_print_event(struct v4l2_event *sd_event)
|
||
|
{
|
||
|
switch (sd_event->type) {
|
||
|
case V4L2_EVENT_MSM_BA_SIGNAL_IN_LOCK:
|
||
|
dprintk(BA_DBG, "Signal in lock for ip_idx %d",
|
||
|
((int *)sd_event->u.data)[0]);
|
||
|
break;
|
||
|
case V4L2_EVENT_MSM_BA_SIGNAL_LOST_LOCK:
|
||
|
dprintk(BA_DBG, "Signal lost lock for ip_idx %d",
|
||
|
((int *)sd_event->u.data)[0]);
|
||
|
break;
|
||
|
case V4L2_EVENT_MSM_BA_SOURCE_CHANGE:
|
||
|
dprintk(BA_DBG, "Video source change 0x%x",
|
||
|
((int *)sd_event->u.data)[1]);
|
||
|
break;
|
||
|
case V4L2_EVENT_MSM_BA_HDMI_HPD:
|
||
|
dprintk(BA_DBG, "HDMI hotplug detected!");
|
||
|
break;
|
||
|
case V4L2_EVENT_MSM_BA_HDMI_CEC_MESSAGE:
|
||
|
dprintk(BA_DBG, "HDMI CEC message!");
|
||
|
break;
|
||
|
case V4L2_EVENT_MSM_BA_CP:
|
||
|
dprintk(BA_DBG, "Content protection detected!");
|
||
|
break;
|
||
|
case V4L2_EVENT_MSM_BA_ERROR:
|
||
|
dprintk(BA_DBG, "Subdev error %d!",
|
||
|
((int *)sd_event->u.data)[1]);
|
||
|
break;
|
||
|
default:
|
||
|
dprintk(BA_ERR, "Unknown event: 0x%x", sd_event->type);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void msm_ba_signal_sessions_event(struct v4l2_event *sd_event)
|
||
|
{
|
||
|
struct msm_ba_inst *inst = NULL;
|
||
|
struct msm_ba_dev *dev_ctxt = NULL;
|
||
|
unsigned int *ptr;
|
||
|
|
||
|
uintptr_t arg;
|
||
|
|
||
|
const struct v4l2_event event = {
|
||
|
.id = 0,
|
||
|
.type = sd_event->type,
|
||
|
.u = sd_event->u};
|
||
|
|
||
|
msm_ba_print_event(sd_event);
|
||
|
dev_ctxt = get_ba_dev();
|
||
|
ptr = (unsigned int *)sd_event->u.data;
|
||
|
|
||
|
list_for_each_entry(inst, &(dev_ctxt->instances), list) {
|
||
|
if (inst->ext_ops && inst->ext_ops->msm_ba_cb) {
|
||
|
arg = ptr[1];
|
||
|
inst->ext_ops->msm_ba_cb(
|
||
|
inst, sd_event->id, (void *)arg);
|
||
|
} else {
|
||
|
msm_ba_queue_v4l2_event(inst, &event);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void msm_ba_subdev_event_hndlr_delayed(struct work_struct *work)
|
||
|
{
|
||
|
struct msm_ba_dev *dev_ctxt = NULL;
|
||
|
struct msm_ba_sd_event *ba_sd_event = NULL;
|
||
|
struct msm_ba_sd_event *ba_sd_event_tmp = NULL;
|
||
|
|
||
|
dev_ctxt = get_ba_dev();
|
||
|
|
||
|
mutex_lock(&dev_ctxt->dev_cs);
|
||
|
if (!list_empty(&dev_ctxt->sd_events)) {
|
||
|
list_for_each_entry_safe(ba_sd_event, ba_sd_event_tmp,
|
||
|
&(dev_ctxt->sd_events), list) {
|
||
|
msm_ba_signal_sessions_event(&ba_sd_event->sd_event);
|
||
|
list_del(&ba_sd_event->list);
|
||
|
kfree(ba_sd_event);
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
dprintk(BA_ERR, "%s - queue empty!!!", __func__);
|
||
|
}
|
||
|
mutex_unlock(&dev_ctxt->dev_cs);
|
||
|
}
|
||
|
|
||
|
struct v4l2_subdev *msm_ba_sd_find(const char *name)
|
||
|
{
|
||
|
struct v4l2_subdev *sd = NULL;
|
||
|
struct v4l2_subdev *sd_out = NULL;
|
||
|
struct msm_ba_dev *dev_ctxt = NULL;
|
||
|
|
||
|
dev_ctxt = get_ba_dev();
|
||
|
if (!list_empty(&(dev_ctxt->v4l2_dev.subdevs))) {
|
||
|
list_for_each_entry(sd, &(dev_ctxt->v4l2_dev.subdevs), list)
|
||
|
if (!strcmp(name, sd->name)) {
|
||
|
sd_out = sd;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return sd_out;
|
||
|
}
|
||
|
|
||
|
void msm_ba_add_inputs(struct v4l2_subdev *sd)
|
||
|
{
|
||
|
struct msm_ba_input *input = NULL;
|
||
|
struct msm_ba_dev *dev_ctxt = NULL;
|
||
|
int i;
|
||
|
int str_length = 0;
|
||
|
int rc;
|
||
|
int start_index = 0;
|
||
|
int end_index = 0;
|
||
|
int dev_id = 0;
|
||
|
int status = 0;
|
||
|
|
||
|
dev_ctxt = get_ba_dev();
|
||
|
if (!list_empty(&dev_ctxt->inputs))
|
||
|
start_index = dev_ctxt->num_inputs;
|
||
|
|
||
|
dev_id = msm_ba_inp_cfg[start_index].ba_out;
|
||
|
end_index = sizeof(msm_ba_inp_cfg)/sizeof(msm_ba_inp_cfg[0]);
|
||
|
for (i = start_index; i < end_index; i++) {
|
||
|
str_length = strlen(msm_ba_inp_cfg[i].sd_name);
|
||
|
rc = memcmp(sd->name, msm_ba_inp_cfg[i].sd_name, str_length);
|
||
|
if (!rc && dev_id == msm_ba_inp_cfg[i].ba_out) {
|
||
|
input = kzalloc(sizeof(*input), GFP_KERNEL);
|
||
|
|
||
|
if (!input) {
|
||
|
dprintk(BA_ERR, "Failed to allocate memory");
|
||
|
break;
|
||
|
}
|
||
|
input->inputType = msm_ba_inp_cfg[i].inputType;
|
||
|
input->name_index = msm_ba_inp_cfg[i].index;
|
||
|
strlcpy(input->name, msm_ba_inp_cfg[i].name,
|
||
|
sizeof(input->name));
|
||
|
input->bridge_chip_ip = msm_ba_inp_cfg[i].ba_ip;
|
||
|
input->ba_out = msm_ba_inp_cfg[i].ba_out;
|
||
|
input->ba_ip_idx = i;
|
||
|
input->prio = V4L2_PRIORITY_DEFAULT;
|
||
|
input->sd = sd;
|
||
|
rc = v4l2_subdev_call(
|
||
|
sd, video, g_input_status, &status);
|
||
|
if (rc)
|
||
|
dprintk(BA_ERR,
|
||
|
"g_input_status failed for sd: %s",
|
||
|
sd->name);
|
||
|
else
|
||
|
input->signal_status = status;
|
||
|
list_add_tail(&input->list, &dev_ctxt->inputs);
|
||
|
dev_ctxt->num_inputs++;
|
||
|
dprintk(BA_DBG, "Add input: name %s on %d",
|
||
|
input->name, input->ba_out);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void msm_ba_del_inputs(struct v4l2_subdev *sd)
|
||
|
{
|
||
|
struct msm_ba_input *input = NULL;
|
||
|
struct list_head *ptr;
|
||
|
struct list_head *next;
|
||
|
struct msm_ba_dev *dev_ctxt = NULL;
|
||
|
|
||
|
dev_ctxt = get_ba_dev();
|
||
|
|
||
|
list_for_each_safe(ptr, next, &(dev_ctxt->inputs)) {
|
||
|
input = list_entry(ptr, struct msm_ba_input, list);
|
||
|
if (input->sd == sd) {
|
||
|
list_del(&input->list);
|
||
|
kfree(input);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void msm_ba_set_out_in_use(struct v4l2_subdev *sd, int on)
|
||
|
{
|
||
|
struct msm_ba_input *input = NULL;
|
||
|
struct msm_ba_dev *dev_ctxt = NULL;
|
||
|
|
||
|
dev_ctxt = get_ba_dev();
|
||
|
|
||
|
if (!list_empty(&(dev_ctxt->inputs))) {
|
||
|
list_for_each_entry(input, &(dev_ctxt->inputs), list)
|
||
|
if (input->sd == sd)
|
||
|
input->ba_out_in_use = on;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int msm_ba_find_ip_in_use_from_sd(struct v4l2_subdev *sd)
|
||
|
{
|
||
|
struct msm_ba_input *input = NULL;
|
||
|
struct msm_ba_dev *dev_ctxt = NULL;
|
||
|
int ba_ip = BA_IP_MAX;
|
||
|
|
||
|
dev_ctxt = get_ba_dev();
|
||
|
|
||
|
if (!list_empty(&(dev_ctxt->inputs))) {
|
||
|
list_for_each_entry(input, &(dev_ctxt->inputs), list)
|
||
|
if (input->sd == sd &&
|
||
|
input->in_use) {
|
||
|
ba_ip = input->bridge_chip_ip;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return ba_ip;
|
||
|
}
|
||
|
|
||
|
void msm_ba_reset_ip_in_use_from_sd(struct v4l2_subdev *sd)
|
||
|
{
|
||
|
struct msm_ba_input *input = NULL;
|
||
|
struct msm_ba_dev *dev_ctxt = NULL;
|
||
|
|
||
|
dev_ctxt = get_ba_dev();
|
||
|
|
||
|
if (!list_empty(&(dev_ctxt->inputs))) {
|
||
|
list_for_each_entry(input, &(dev_ctxt->inputs), list)
|
||
|
if (input->sd == sd &&
|
||
|
input->in_use) {
|
||
|
input->in_use = 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct msm_ba_input *msm_ba_find_input_from_sd(struct v4l2_subdev *sd,
|
||
|
int bridge_chip_ip)
|
||
|
{
|
||
|
struct msm_ba_input *input = NULL;
|
||
|
struct msm_ba_input *input_out = NULL;
|
||
|
struct msm_ba_dev *dev_ctxt = NULL;
|
||
|
|
||
|
dev_ctxt = get_ba_dev();
|
||
|
|
||
|
if (!list_empty(&(dev_ctxt->inputs))) {
|
||
|
list_for_each_entry(input, &(dev_ctxt->inputs), list)
|
||
|
if (input->sd == sd &&
|
||
|
input->bridge_chip_ip == bridge_chip_ip) {
|
||
|
input_out = input;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return input_out;
|
||
|
}
|
||
|
|
||
|
struct msm_ba_input *msm_ba_find_input(int ba_input_idx)
|
||
|
{
|
||
|
struct msm_ba_input *input = NULL;
|
||
|
struct msm_ba_input *input_out = NULL;
|
||
|
struct msm_ba_dev *dev_ctxt = NULL;
|
||
|
|
||
|
dev_ctxt = get_ba_dev();
|
||
|
|
||
|
if (!list_empty(&(dev_ctxt->inputs))) {
|
||
|
list_for_each_entry(input, &(dev_ctxt->inputs), list)
|
||
|
if (input->ba_ip_idx == ba_input_idx) {
|
||
|
input_out = input;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return input_out;
|
||
|
}
|
||
|
|
||
|
struct msm_ba_input *msm_ba_find_output(int ba_output)
|
||
|
{
|
||
|
struct msm_ba_input *input = NULL;
|
||
|
struct msm_ba_input *input_out = NULL;
|
||
|
struct msm_ba_dev *dev_ctxt = NULL;
|
||
|
|
||
|
dev_ctxt = get_ba_dev();
|
||
|
|
||
|
if (!list_empty(&(dev_ctxt->inputs))) {
|
||
|
list_for_each_entry(input, &(dev_ctxt->inputs), list) {
|
||
|
if (input->ba_out == ba_output) {
|
||
|
input_out = input;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return input_out;
|
||
|
}
|
||
|
|
||
|
int msm_ba_g_fps(void *instance, int *fps_q16)
|
||
|
{
|
||
|
struct msm_ba_inst *inst = instance;
|
||
|
struct v4l2_subdev *sd = NULL;
|
||
|
struct v4l2_subdev_frame_interval sd_frame_int;
|
||
|
int rc = 0;
|
||
|
|
||
|
if (!inst || !fps_q16)
|
||
|
return -EINVAL;
|
||
|
|
||
|
sd = inst->sd;
|
||
|
if (!sd) {
|
||
|
dprintk(BA_ERR, "No sd registered");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
rc = v4l2_subdev_call(sd, video, g_frame_interval, &sd_frame_int);
|
||
|
if (rc) {
|
||
|
dprintk(BA_ERR, "get frame interval failed %d for sd: %s",
|
||
|
rc, sd->name);
|
||
|
} else {
|
||
|
/* subdevice returns frame interval not fps! */
|
||
|
if (sd_frame_int.interval.numerator) {
|
||
|
BA_FRAC_TO_Q16(*fps_q16,
|
||
|
sd_frame_int.interval.denominator,
|
||
|
sd_frame_int.interval.numerator);
|
||
|
} else {
|
||
|
*fps_q16 =
|
||
|
sd_frame_int.interval.denominator << 16;
|
||
|
}
|
||
|
}
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int msm_ba_try_get_ctrl(struct msm_ba_inst *inst,
|
||
|
struct v4l2_ctrl *ctrl)
|
||
|
{
|
||
|
struct msm_ba_input *ba_input = NULL;
|
||
|
int rc = 0;
|
||
|
|
||
|
if (!inst) {
|
||
|
dprintk(BA_ERR, "%s invalid parameters", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
dprintk(BA_DBG, "%s ctrl->id: 0x%x", __func__, ctrl->id);
|
||
|
|
||
|
switch (ctrl->id) {
|
||
|
case MSM_BA_PRIV_SD_NODE_ADDR:
|
||
|
ba_input = msm_ba_find_input(inst->sd_input.index);
|
||
|
if (ba_input) {
|
||
|
ctrl->val = ba_input->ba_node_addr;
|
||
|
dprintk(BA_DBG,
|
||
|
"%s: SD NODE ADDR ctrl->id:0x%x ctrl->val:%d",
|
||
|
__func__, ctrl->id, ctrl->val);
|
||
|
} else {
|
||
|
dprintk(BA_ERR, "%s Could not find input",
|
||
|
__func__);
|
||
|
rc = -EINVAL;
|
||
|
}
|
||
|
break;
|
||
|
case MSM_BA_PRIV_FPS:
|
||
|
rc = msm_ba_g_fps(inst, &ctrl->val);
|
||
|
break;
|
||
|
default:
|
||
|
dprintk(BA_ERR, "%s id: 0x%x not supported",
|
||
|
__func__, ctrl->id);
|
||
|
rc = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int msm_ba_try_set_ctrl(struct msm_ba_inst *inst,
|
||
|
struct v4l2_ctrl *ctrl)
|
||
|
{
|
||
|
struct msm_ba_input *ba_input = NULL;
|
||
|
int rc = 0;
|
||
|
|
||
|
if (!inst) {
|
||
|
dprintk(BA_ERR, "%s invalid parameters", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
dprintk(BA_DBG, "%s ctrl->id: 0x%x", __func__, ctrl->id);
|
||
|
|
||
|
switch (ctrl->id) {
|
||
|
case MSM_BA_PRIV_SD_NODE_ADDR:
|
||
|
ba_input = msm_ba_find_input(inst->sd_input.index);
|
||
|
if (ba_input) {
|
||
|
ba_input->ba_node_addr = ctrl->val;
|
||
|
dprintk(BA_DBG,
|
||
|
"%s: SD NODE ADDR ctrl->id:0x%x node_addr:%d",
|
||
|
__func__, ctrl->id, ba_input->ba_node_addr);
|
||
|
} else {
|
||
|
dprintk(BA_ERR, "%s Could not find input",
|
||
|
__func__);
|
||
|
rc = -EINVAL;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
dprintk(BA_ERR, "%s id: 0x%x not supported",
|
||
|
__func__, ctrl->id);
|
||
|
rc = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int msm_ba_op_s_ctrl(struct v4l2_ctrl *ctrl)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
int c = 0;
|
||
|
struct msm_ba_inst *inst = container_of(ctrl->handler,
|
||
|
struct msm_ba_inst, ctrl_handler);
|
||
|
if (!inst) {
|
||
|
dprintk(BA_ERR, "%s invalid parameters", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
for (c = 0; c < ctrl->ncontrols; ++c) {
|
||
|
if (ctrl->cluster[c]->is_new) {
|
||
|
rc = msm_ba_try_set_ctrl(inst, ctrl->cluster[c]);
|
||
|
if (rc) {
|
||
|
dprintk(BA_ERR, "Failed setting 0x%x",
|
||
|
ctrl->cluster[c]->id);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int msm_ba_op_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
int c = 0;
|
||
|
struct msm_ba_inst *inst = container_of(ctrl->handler,
|
||
|
struct msm_ba_inst, ctrl_handler);
|
||
|
struct v4l2_ctrl *master = ctrl->cluster[0];
|
||
|
|
||
|
for (c = 0; c < master->ncontrols; c++) {
|
||
|
if (master->cluster[c]->id == ctrl->id) {
|
||
|
rc = msm_ba_try_get_ctrl(inst, ctrl);
|
||
|
if (rc) {
|
||
|
dprintk(BA_ERR, "Failed getting 0x%x",
|
||
|
ctrl->id);
|
||
|
return rc;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static const struct v4l2_ctrl_ops msm_ba_ctrl_ops = {
|
||
|
|
||
|
.g_volatile_ctrl = msm_ba_op_g_volatile_ctrl,
|
||
|
.s_ctrl = msm_ba_op_s_ctrl,
|
||
|
};
|
||
|
|
||
|
const struct v4l2_ctrl_ops *msm_ba_get_ctrl_ops(void)
|
||
|
{
|
||
|
return &msm_ba_ctrl_ops;
|
||
|
}
|
||
|
|
||
|
static struct v4l2_ctrl **msm_ba_get_super_cluster(struct msm_ba_inst *inst,
|
||
|
int *size)
|
||
|
{
|
||
|
int c = 0;
|
||
|
int sz = 0;
|
||
|
struct v4l2_ctrl **cluster = kmalloc(sizeof(struct v4l2_ctrl *) *
|
||
|
BA_NUM_CTRLS, GFP_KERNEL);
|
||
|
|
||
|
if (!size || !cluster || !inst)
|
||
|
return NULL;
|
||
|
|
||
|
for (c = 0; c < BA_NUM_CTRLS; c++)
|
||
|
cluster[sz++] = inst->ctrls[c];
|
||
|
|
||
|
*size = sz;
|
||
|
return cluster;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Controls init function.
|
||
|
* Caller is expected to call deinit in case of failure.
|
||
|
*/
|
||
|
int msm_ba_ctrl_init(struct msm_ba_inst *inst)
|
||
|
{
|
||
|
int idx = 0;
|
||
|
struct v4l2_ctrl_config ctrl_cfg = {0};
|
||
|
int rc = 0;
|
||
|
int cluster_size = 0;
|
||
|
|
||
|
if (!inst) {
|
||
|
dprintk(BA_ERR, "%s - invalid instance", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
inst->ctrls = kzalloc(sizeof(struct v4l2_ctrl *) * BA_NUM_CTRLS,
|
||
|
GFP_KERNEL);
|
||
|
if (!inst->ctrls) {
|
||
|
dprintk(BA_ERR, "%s - failed to allocate ctrl", __func__);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
rc = v4l2_ctrl_handler_init(&inst->ctrl_handler, BA_NUM_CTRLS);
|
||
|
|
||
|
if (rc) {
|
||
|
dprintk(BA_ERR, "CTRL ERR: Control handler init failed, %d",
|
||
|
inst->ctrl_handler.error);
|
||
|
return rc;
|
||
|
}
|
||
|
for (; idx < BA_NUM_CTRLS; idx++) {
|
||
|
struct v4l2_ctrl *ctrl = NULL;
|
||
|
|
||
|
if (BA_IS_PRIV_CTRL(msm_ba_ctrls[idx].id)) {
|
||
|
/* add private control */
|
||
|
ctrl_cfg.def = msm_ba_ctrls[idx].default_value;
|
||
|
ctrl_cfg.flags = 0;
|
||
|
ctrl_cfg.id = msm_ba_ctrls[idx].id;
|
||
|
ctrl_cfg.max = msm_ba_ctrls[idx].maximum;
|
||
|
ctrl_cfg.min = msm_ba_ctrls[idx].minimum;
|
||
|
ctrl_cfg.menu_skip_mask =
|
||
|
msm_ba_ctrls[idx].menu_skip_mask;
|
||
|
ctrl_cfg.name = msm_ba_ctrls[idx].name;
|
||
|
ctrl_cfg.ops = &msm_ba_ctrl_ops;
|
||
|
ctrl_cfg.step = msm_ba_ctrls[idx].step;
|
||
|
ctrl_cfg.type = msm_ba_ctrls[idx].type;
|
||
|
ctrl_cfg.qmenu = msm_ba_ctrls[idx].qmenu;
|
||
|
|
||
|
ctrl = v4l2_ctrl_new_custom(&inst->ctrl_handler,
|
||
|
&ctrl_cfg, NULL);
|
||
|
} else {
|
||
|
if (msm_ba_ctrls[idx].type == V4L2_CTRL_TYPE_MENU) {
|
||
|
ctrl = v4l2_ctrl_new_std_menu(
|
||
|
&inst->ctrl_handler,
|
||
|
&msm_ba_ctrl_ops,
|
||
|
msm_ba_ctrls[idx].id,
|
||
|
msm_ba_ctrls[idx].maximum,
|
||
|
msm_ba_ctrls[idx].menu_skip_mask,
|
||
|
msm_ba_ctrls[idx].default_value);
|
||
|
} else {
|
||
|
ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler,
|
||
|
&msm_ba_ctrl_ops,
|
||
|
msm_ba_ctrls[idx].id,
|
||
|
msm_ba_ctrls[idx].minimum,
|
||
|
msm_ba_ctrls[idx].maximum,
|
||
|
msm_ba_ctrls[idx].step,
|
||
|
msm_ba_ctrls[idx].default_value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch (msm_ba_ctrls[idx].id) {
|
||
|
case MSM_BA_PRIV_SD_NODE_ADDR:
|
||
|
case MSM_BA_PRIV_FPS:
|
||
|
if (ctrl)
|
||
|
ctrl->flags |= msm_ba_ctrls[idx].flags;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
rc = inst->ctrl_handler.error;
|
||
|
if (rc) {
|
||
|
dprintk(BA_ERR,
|
||
|
"Error adding ctrl (%s) to ctrl handle, %d",
|
||
|
msm_ba_ctrls[idx].name,
|
||
|
inst->ctrl_handler.error);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
inst->ctrls[idx] = ctrl;
|
||
|
}
|
||
|
|
||
|
/* Construct a super cluster of all controls */
|
||
|
inst->cluster = msm_ba_get_super_cluster(inst, &cluster_size);
|
||
|
if (!inst->cluster || !cluster_size) {
|
||
|
dprintk(BA_WARN,
|
||
|
"Failed to setup super cluster");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
v4l2_ctrl_cluster(cluster_size, inst->cluster);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
void msm_ba_ctrl_deinit(struct msm_ba_inst *inst)
|
||
|
{
|
||
|
kfree(inst->ctrls);
|
||
|
kfree(inst->cluster);
|
||
|
v4l2_ctrl_handler_free(&inst->ctrl_handler);
|
||
|
}
|