/* 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"

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,
		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_PORT_SETTINGS_CHANGED:
		dprintk(BA_DBG, "Port settings changed for ip_idx %d",
			((int *)sd_event->u.data)[0]);
		break;
	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_CABLE_DETECT:
		dprintk(BA_DBG, "Cable detected: %d on ip_idx %d",
			((int *)sd_event->u.data)[1],
			((int *)sd_event->u.data)[0]);
		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;
	int *ptr;

	msm_ba_print_event(sd_event);
	dev_ctxt = get_ba_dev();
	ptr = (int *)sd_event->u.data;

	list_for_each_entry(inst, &(dev_ctxt->instances), list) {
		if (inst->ext_ops && inst->ext_ops->msm_ba_cb)
			inst->ext_ops->msm_ba_cb(
					inst, sd_event->id, (void *)&ptr[1]);
		else
			msm_ba_queue_v4l2_event(inst, sd_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;
	struct msm_ba_input_config *msm_ba_inp_cfg = NULL;
	int i;
	int str_length = 0;
	int rc;
	int start_index = 0;
	int end_index = 0;
	int dev_id = 0;

	dev_ctxt = get_ba_dev();
	if (!list_empty(&dev_ctxt->inputs))
		start_index = dev_ctxt->num_inputs;

	msm_ba_inp_cfg = dev_ctxt->msm_ba_inp_cfg;
	dev_id = msm_ba_inp_cfg[start_index].ba_out;
	end_index = dev_ctxt->num_config_inputs;
	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->input_type = msm_ba_inp_cfg[i].input_type;
			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_node_addr = msm_ba_inp_cfg[i].ba_node;
			input->ba_ip_idx = i;
			input->prio = V4L2_PRIORITY_DEFAULT;
			input->input_user_type =
				msm_ba_inp_cfg[i].input_user_type;
			input->sd = sd;
			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);
			}
		}

		if (!ctrl) {
			dprintk(BA_ERR, "%s - invalid ctrl", __func__);
			return -EINVAL;
		}

		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;
		}

		switch (msm_ba_ctrls[idx].id) {
		case MSM_BA_PRIV_SD_NODE_ADDR:
		case MSM_BA_PRIV_FPS:
			ctrl->flags |= msm_ba_ctrls[idx].flags;
			break;
		}

		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);
}
