/*
 * Copyright (c) 2012-2015, 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 "dsi_pll.h"

static int dsi_pll_enable(struct msm_dsi_pll *pll)
{
	int i, ret = 0;

	/*
	 * Certain PLLs do not allow VCO rate update when it is on.
	 * Keep track of their status to turn on/off after set rate success.
	 */
	if (unlikely(pll->pll_on))
		return 0;

	/* Try all enable sequences until one succeeds */
	for (i = 0; i < pll->en_seq_cnt; i++) {
		ret = pll->enable_seqs[i](pll);
		DBG("DSI PLL %s after sequence #%d",
			ret ? "unlocked" : "locked", i + 1);
		if (!ret)
			break;
	}

	if (ret) {
		DRM_ERROR("DSI PLL failed to lock\n");
		return ret;
	}

	pll->pll_on = true;

	return 0;
}

static void dsi_pll_disable(struct msm_dsi_pll *pll)
{
	if (unlikely(!pll->pll_on))
		return;

	pll->disable_seq(pll);

	pll->pll_on = false;
}

/*
 * DSI PLL Helper functions
 */
long msm_dsi_pll_helper_clk_round_rate(struct clk_hw *hw,
		unsigned long rate, unsigned long *parent_rate)
{
	struct msm_dsi_pll *pll = hw_clk_to_pll(hw);

	if      (rate < pll->min_rate)
		return  pll->min_rate;
	else if (rate > pll->max_rate)
		return  pll->max_rate;
	else
		return rate;
}

int msm_dsi_pll_helper_clk_prepare(struct clk_hw *hw)
{
	struct msm_dsi_pll *pll = hw_clk_to_pll(hw);

	return dsi_pll_enable(pll);
}

void msm_dsi_pll_helper_clk_unprepare(struct clk_hw *hw)
{
	struct msm_dsi_pll *pll = hw_clk_to_pll(hw);

	dsi_pll_disable(pll);
}

void msm_dsi_pll_helper_unregister_clks(struct platform_device *pdev,
					struct clk **clks, u32 num_clks)
{
	of_clk_del_provider(pdev->dev.of_node);

	if (!num_clks || !clks)
		return;

	do {
		clk_unregister(clks[--num_clks]);
		clks[num_clks] = NULL;
	} while (num_clks);
}

/*
 * DSI PLL API
 */
int msm_dsi_pll_get_clk_provider(struct msm_dsi_pll *pll,
	struct clk **byte_clk_provider, struct clk **pixel_clk_provider)
{
	if (pll->get_provider)
		return pll->get_provider(pll,
					byte_clk_provider,
					pixel_clk_provider);

	return -EINVAL;
}

void msm_dsi_pll_destroy(struct msm_dsi_pll *pll)
{
	if (pll->destroy)
		pll->destroy(pll);
}

void msm_dsi_pll_save_state(struct msm_dsi_pll *pll)
{
	if (pll->save_state) {
		pll->save_state(pll);
		pll->state_saved = true;
	}
}

int msm_dsi_pll_restore_state(struct msm_dsi_pll *pll)
{
	int ret;

	if (pll->restore_state && pll->state_saved) {
		ret = pll->restore_state(pll);
		if (ret)
			return ret;

		pll->state_saved = false;
	}

	return 0;
}

struct msm_dsi_pll *msm_dsi_pll_init(struct platform_device *pdev,
			enum msm_dsi_phy_type type, int id)
{
	struct device *dev = &pdev->dev;
	struct msm_dsi_pll *pll;

	switch (type) {
	case MSM_DSI_PHY_28NM_HPM:
	case MSM_DSI_PHY_28NM_LP:
		pll = msm_dsi_pll_28nm_init(pdev, type, id);
		break;
	default:
		pll = ERR_PTR(-ENXIO);
		break;
	}

	if (IS_ERR(pll)) {
		dev_err(dev, "%s: failed to init DSI PLL\n", __func__);
		return NULL;
	}

	pll->type = type;

	DBG("DSI:%d PLL registered", id);

	return pll;
}

