/* Copyright (c) 2016-2017, 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.
 */

#define pr_fmt(fmt) "MSM-CPP-SOC %s:%d " fmt, __func__, __LINE__

#include <linux/clk/msm-clk.h>
#include <linux/clk/msm-clk-provider.h>
#include <linux/delay.h>
#include <media/msmb_pproc.h>
#include "msm_cpp.h"


#define CPP_DT_READ_U32_ERR(_dev, _key, _str, _ret, _out) { \
		_key = _str; \
		_ret = of_property_read_u32(_dev, _key, &_out); \
		if (_ret) \
			break; \
	}

#define CPP_DT_READ_U32(_dev, _str, _out) { \
		of_property_read_u32(_dev, _str, &_out); \
	}

void msm_cpp_fetch_dt_params(struct cpp_device *cpp_dev)
{
	int rc = 0;
	struct device_node *of_node = cpp_dev->pdev->dev.of_node;

	if (!of_node) {
		pr_err("%s: invalid params\n", __func__);
		return;
	}

	of_property_read_u32(of_node, "cell-index", &cpp_dev->pdev->id);

	rc = of_property_read_u32(of_node, "qcom,min-clock-rate",
			&cpp_dev->min_clk_rate);
	if (rc < 0) {
		pr_debug("min-clk-rate not defined, setting it to 0\n");
		cpp_dev->min_clk_rate = 0;
	}

	rc = of_property_read_u32(of_node, "qcom,bus-master",
			&cpp_dev->bus_master_flag);
	if (rc)
		cpp_dev->bus_master_flag = 0;
}

int msm_cpp_get_clock_index(struct cpp_device *cpp_dev, const char *clk_name)
{
	uint32_t i = 0;

	for (i = 0; i < cpp_dev->num_clks; i++) {
		if (!strcmp(clk_name, cpp_dev->clk_info[i].clk_name))
			return i;
	}
	return -EINVAL;
}

static int cpp_get_clk_freq_tbl(struct clk *clk, struct cpp_hw_info *hw_info,
	uint32_t min_clk_rate)
{
	uint32_t i;
	uint32_t idx = 0;
	signed long freq_tbl_entry = 0;

	if ((clk == NULL) || (hw_info == NULL) || (clk->ops == NULL) ||
		(clk->ops->list_rate == NULL)) {
		pr_err("Bad parameter\n");
		return -EINVAL;
	}

	for (i = 0; i < MAX_FREQ_TBL; i++) {
		freq_tbl_entry = clk->ops->list_rate(clk, i);
		pr_debug("entry=%ld\n", freq_tbl_entry);
		if (freq_tbl_entry >= 0) {
			if (freq_tbl_entry >= min_clk_rate) {
				hw_info->freq_tbl[idx++] = freq_tbl_entry;
				pr_debug("tbl[%d]=%ld\n", idx-1,
					freq_tbl_entry);
			}
		} else {
			pr_debug("freq table returned invalid entry/end %ld\n",
				freq_tbl_entry);
			break;
		}
	}

	pr_debug("%s: idx %d", __func__, idx);
	hw_info->freq_tbl_count = idx;

	return 0;
}

int msm_cpp_set_micro_clk(struct cpp_device *cpp_dev)
{
#ifdef ENABLE_CPP_MICRO
	uint32_t msm_micro_iface_idx;
	int rc;

	msm_micro_iface_idx = msm_cpp_get_clock_index(cpp_dev,
		"micro_iface_clk");
	if (msm_micro_iface_idx < 0)  {
		pr_err("Fail to get clock index\n");
		return -EINVAL;
	}


	rc = msm_clk_reset(cpp_dev->cpp_clk[msm_micro_iface_idx],
		CLK_RESET_ASSERT);
	if (rc) {
		pr_err("%s:micro_iface_clk assert failed\n",
		__func__);
		return -EINVAL;
	}

	/*
	 * Below usleep values are chosen based on experiments
	 * and this was the smallest number which works. This
	 * sleep is needed to leave enough time for Microcontroller
	 * to resets all its registers.
	 */
	usleep_range(1000, 1200);

	rc = msm_clk_reset(cpp_dev->cpp_clk[msm_micro_iface_idx],
		CLK_RESET_DEASSERT);
	if (rc) {
		pr_err("%s:micro_iface_clk de-assert failed\n", __func__);
		return -EINVAL;
	}
	/*
	 * Below usleep values are chosen based on experiments
	 * and this was the smallest number which works. This
	 * sleep is needed to leave enough time for Microcontroller
	 * to resets all its registers.
	 */
	usleep_range(1000, 1200);
#endif
	return 0;
}

int msm_update_freq_tbl(struct cpp_device *cpp_dev)
{
	uint32_t msm_cpp_core_clk_idx;
	int rc = 0;

	msm_cpp_core_clk_idx = msm_cpp_get_clock_index(cpp_dev, "cpp_core_clk");
	if (msm_cpp_core_clk_idx < 0)  {
		pr_err("%s: fail to get clock index\n", __func__);
		rc = msm_cpp_core_clk_idx;
		return rc;
	}
	rc = cpp_get_clk_freq_tbl(cpp_dev->cpp_clk[msm_cpp_core_clk_idx],
		&cpp_dev->hw_info, cpp_dev->min_clk_rate);
	if (rc < 0)  {
		pr_err("%s: fail to get frequency table\n", __func__);
		return rc;
	}

	return rc;
}

long msm_cpp_set_core_clk(struct cpp_device *cpp_dev, long rate, int idx)
{
	long rc = 0;

	rc = msm_camera_clk_set_rate(&cpp_dev->pdev->dev,
		cpp_dev->cpp_clk[idx], rate);
	if (rc < 0) {
		pr_err("%s: fail to get frequency table\n", __func__);
		return rc;
	}

	return rc;
}

int msm_cpp_read_payload_params_from_dt(struct cpp_device *cpp_dev)
{
	struct platform_device *pdev = cpp_dev->pdev;
	struct device_node *fw_info_node = NULL, *dev_node = NULL;
	char *key = "qcom,cpp-fw-payload-info";
	struct msm_cpp_payload_params *payload_params;
	int ret = 0;

	if (!pdev || !pdev->dev.of_node) {
		pr_err("%s: Invalid platform device/node\n", __func__);
		ret = -ENODEV;
		goto no_cpp_node;
	}

	dev_node = pdev->dev.of_node;
	fw_info_node = of_find_node_by_name(dev_node, key);
	if (!fw_info_node) {
		ret = -ENODEV;
		goto no_binding;
	}
	payload_params = &cpp_dev->payload_params;
	memset(payload_params, 0x0, sizeof(struct msm_cpp_payload_params));

	do {
		CPP_DT_READ_U32_ERR(fw_info_node, key, "qcom,stripe-base", ret,
			payload_params->stripe_base);
		CPP_DT_READ_U32_ERR(fw_info_node, key, "qcom,plane-base", ret,
			payload_params->plane_base);
		CPP_DT_READ_U32_ERR(fw_info_node, key, "qcom,stripe-size", ret,
			payload_params->stripe_size);
		CPP_DT_READ_U32_ERR(fw_info_node, key, "qcom,plane-size", ret,
			payload_params->plane_size);
		CPP_DT_READ_U32_ERR(fw_info_node, key, "qcom,fe-ptr-off", ret,
			payload_params->rd_pntr_off);
		CPP_DT_READ_U32_ERR(fw_info_node, key, "qcom,we-ptr-off", ret,
			payload_params->wr_0_pntr_off);

		CPP_DT_READ_U32(fw_info_node, "qcom,ref-fe-ptr-off",
			payload_params->rd_ref_pntr_off);
		CPP_DT_READ_U32(fw_info_node, "qcom,ref-we-ptr-off",
			payload_params->wr_ref_pntr_off);
		CPP_DT_READ_U32(fw_info_node, "qcom,we-meta-ptr-off",
			payload_params->wr_0_meta_data_wr_pntr_off);
		CPP_DT_READ_U32(fw_info_node, "qcom,fe-mmu-pf-ptr-off",
			payload_params->fe_mmu_pf_ptr_off);
		CPP_DT_READ_U32(fw_info_node, "qcom,ref-fe-mmu-pf-ptr-off",
			payload_params->ref_fe_mmu_pf_ptr_off);
		CPP_DT_READ_U32(fw_info_node, "qcom,we-mmu-pf-ptr-off",
			payload_params->we_mmu_pf_ptr_off);
		CPP_DT_READ_U32(fw_info_node, "qcom,dup-we-mmu-pf-ptr-off",
			payload_params->dup_we_mmu_pf_ptr_off);
		CPP_DT_READ_U32(fw_info_node, "qcom,ref-we-mmu-pf-ptr-off",
			payload_params->ref_we_mmu_pf_ptr_off);
		CPP_DT_READ_U32(fw_info_node, "qcom,set-group-buffer-len",
			payload_params->set_group_buffer_len);
		CPP_DT_READ_U32(fw_info_node, "qcom,dup-frame-indicator-off",
			payload_params->dup_frame_indicator_off);
	} while (0);

no_binding:
	if (ret)
		pr_err("%s: Error reading binding %s, ret %d\n",
			__func__, key, ret);
no_cpp_node:
	return ret;
}
