/*
 * @Codingstyle LinuxKernel
 * @Copyright   Copyright (c) Imagination Technologies Ltd. All Rights Reserved
 * @License     Dual MIT/GPLv2
 *
 * The contents of this file are subject to the MIT license as set out below.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 ("GPL") in which case the provisions
 * of GPL are applicable instead of those above.
 *
 * If you wish to allow use of your version of this file only under the terms of
 * GPL, and not to allow others to use your version of this file under the terms
 * of the MIT license, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by GPL as set
 * out in the file called "GPL-COPYING" included in this distribution. If you do
 * not delete the provisions above, a recipient may use your version of this file
 * under the terms of either the MIT license or GPL.
 *
 * This License is also included in this distribution in the file called
 * "MIT-COPYING".
 *
 * EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS
 * PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <linux/delay.h>

#include "pdp_apollo.h"
#include "pdp_common.h"
#include "pdp_regs.h"
#include "tcf_rgbpdp_regs.h"
#include "tcf_pll.h"

/* Map a register to the "pll-regs" region */
#define PLL_REG(n) ((n) - TCF_PLL_PLL_PDP_CLK0)

bool pdp_apollo_clocks_set(struct device *dev,
			   void __iomem *pdp_reg, void __iomem *pll_reg,
			   u32 clock_in_mhz,
			   void __iomem *odn_core_reg,
			   u32 hdisplay, u32 vdisplay)
{
	/*
	 * Setup TCF_CR_PLL_PDP_CLK1TO5 based on the main clock speed
	 * (clock 0 or 3)
	 */
	const u32 clock = (clock_in_mhz >= 50) ? 0 : 0x3;

	/* Set phase 0, ratio 50:50 and frequency in MHz */
	pll_wreg32(pll_reg, PLL_REG(TCF_PLL_PLL_PDP_CLK0), clock_in_mhz);

	pll_wreg32(pll_reg, PLL_REG(TCF_PLL_PLL_PDP_CLK1TO5), clock);

	/* Now initiate reprogramming of the PLLs */
	pll_wreg32(pll_reg, PLL_REG(TCF_PLL_PLL_PDP_DRP_GO), 0x1);

	udelay(1000);

	pll_wreg32(pll_reg, PLL_REG(TCF_PLL_PLL_PDP_DRP_GO), 0x0);

	return true;
}

void pdp_apollo_set_updates_enabled(struct device *dev, void __iomem *pdp_reg,
				    bool enable)
{
#ifdef PDP_VERBOSE
	dev_info(dev, "Set updates: %s\n", enable ? "enable" : "disable");
#endif
	/* nothing to do here */
}

void pdp_apollo_set_syncgen_enabled(struct device *dev, void __iomem *pdp_reg,
				    bool enable)
{
	u32 value;

#ifdef PDP_VERBOSE
	dev_info(dev, "Set syncgen: %s\n", enable ? "enable" : "disable");
#endif

	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_SYNCCTRL);
	value = REG_VALUE_SET(value, enable ? 0x1 : 0x0,
			      SYNCACTIVE_SHIFT, SYNCACTIVE_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_SYNCCTRL, value);
}

void pdp_apollo_set_powerdwn_enabled(struct device *dev, void __iomem *pdp_reg,
				     bool enable)
{
	u32 value;

#ifdef PDP_VERBOSE
	dev_info(dev, "Set powerdwn: %s\n", enable ? "enable" : "disable");
#endif

	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_SYNCCTRL);
	value = REG_VALUE_SET(value, enable ? 0x1 : 0x0,
			      POWERDN_SHIFT, POWERDN_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_SYNCCTRL, value);
}

void pdp_apollo_set_vblank_enabled(struct device *dev, void __iomem *pdp_reg,
				   bool enable)
{
	u32 value;

#ifdef PDP_VERBOSE
	dev_info(dev, "Set vblank: %s\n", enable ? "enable" : "disable");
#endif

	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_INTENAB);
	value = REG_VALUE_SET(value, enable ? 0x1 : 0x0,
			      INTEN_VBLNK0_SHIFT, INTEN_VBLNK0_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_INTENAB, value);
}

bool pdp_apollo_check_and_clear_vblank(struct device *dev,
				       void __iomem *pdp_reg)
{
	u32 value;

	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_INTSTAT);

	if (REG_VALUE_GET(value, INTS_VBLNK0_SHIFT, INTS_VBLNK0_MASK)) {
		value = REG_VALUE_SET(0, 0x1,
				      INTCLR_VBLNK0_SHIFT, INTCLR_VBLNK0_MASK);
		pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_INTCLEAR, value);
		return true;
	}
	return false;
}

void pdp_apollo_set_plane_enabled(struct device *dev, void __iomem *pdp_reg,
				  u32 plane, bool enable)
{
	u32 value;

#ifdef PDP_VERBOSE
	dev_info(dev, "Set plane %u: %s\n",
		 plane, enable ? "enable" : "disable");
#endif

	if (plane > 0) {
		dev_err(dev, "Maximum of 1 plane is supported\n");
		return;
	}

	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_STR1ADDRCTRL);
	value = REG_VALUE_SET(value, enable ? 0x1 : 0x0,
			      STR1STREN_SHIFT, STR1STREN_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_STR1ADDRCTRL, value);
}

void pdp_apollo_reset_planes(struct device *dev, void __iomem *pdp_reg)
{
#ifdef PDP_VERBOSE
	dev_info(dev, "Reset planes\n");
#endif

	pdp_apollo_set_plane_enabled(dev, pdp_reg, 0, false);
}

void pdp_apollo_set_surface(struct device *dev, void __iomem *pdp_reg,
			    u32 plane, u32 address,
			    u32 posx, u32 posy,
			    u32 width, u32 height, u32 stride,
			    u32 format, u32 alpha, bool blend)
{
	u32 value;

#ifdef PDP_VERBOSE
	dev_info(dev,
		 "Set surface: size=%dx%d stride=%d format=%d address=0x%x\n",
		 width, height, stride, format, address);
#endif

	if (plane > 0) {
		dev_err(dev, "Maximum of 1 plane is supported\n");
		return;
	}

	/* Size & format */
	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_STR1SURF);
	value = REG_VALUE_SET(value, width - 1,
			      STR1WIDTH_SHIFT, STR1WIDTH_MASK);
	value = REG_VALUE_SET(value, height - 1,
			      STR1HEIGHT_SHIFT, STR1HEIGHT_MASK);
	value = REG_VALUE_SET(value, format,
			      STR1PIXFMT_SHIFT, STR1PIXFMT_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_STR1SURF, value);
	/* Stride */
	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_PDP_STR1POSN);
	value = REG_VALUE_SET(value,
			      (stride >> DCPDP_STR1POSN_STRIDE_SHIFT) - 1,
			      STR1STRIDE_SHIFT, STR1STRIDE_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_PDP_STR1POSN, value);
	/* Disable interlaced output */
	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_STR1ADDRCTRL);
	value = REG_VALUE_SET(value, 0x0,
			      STR1INTFIELD_SHIFT,
			      STR1INTFIELD_MASK);
	/* Frame buffer base address */
	value = REG_VALUE_SET(value,
			      address >> DCPDP_STR1ADDRCTRL_BASE_ADDR_SHIFT,
			      STR1BASE_SHIFT, STR1BASE_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_STR1ADDRCTRL, value);
}

void pdp_apollo_mode_set(struct device *dev, void __iomem *pdp_reg,
			 u32 h_display, u32 v_display,
			 u32 hbps, u32 ht, u32 has,
			 u32 hlbs, u32 hfps, u32 hrbs,
			 u32 vbps, u32 vt, u32 vas,
			 u32 vtbs, u32 vfps, u32 vbbs,
			 bool nhsync, bool nvsync)
{
	u32 value;

	dev_info(dev, "Set mode: %dx%d\n", h_display, v_display);
#ifdef PDP_VERBOSE
	dev_info(dev, " ht: %d hbps %d has %d hlbs %d hfps %d hrbs %d\n",
		 ht, hbps, has, hlbs, hfps, hrbs);
	dev_info(dev, " vt: %d vbps %d vas %d vtbs %d vfps %d vbbs %d\n",
		 vt, vbps, vas, vtbs, vfps, vbbs);
#endif

#if 0
	/* I don't really know what this is doing but it was in the Android
	 * implementation (not in the Linux one). Seems not to be necessary
	 * though!
	 */
	if (pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_STRCTRL)
		!= 0x0000C010) {
		/* Buffer request threshold */
		pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_STRCTRL,
				   0x00001C10);
	}
#endif

	/* Border colour */
	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_BORDCOL);
	value = REG_VALUE_SET(value, 0x0, BORDCOL_SHIFT, BORDCOL_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_BORDCOL, value);

	/* Update control */
	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_UPDCTRL);
	value = REG_VALUE_SET(value, 0x0, UPDFIELD_SHIFT, UPDFIELD_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_UPDCTRL, value);

	/* Set hsync timings */
	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_HSYNC1);
	value = REG_VALUE_SET(value, hbps, HBPS_SHIFT, HBPS_MASK);
	value = REG_VALUE_SET(value, ht, HT_SHIFT, HT_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_HSYNC1, value);

	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_HSYNC2);
	value = REG_VALUE_SET(value, has, HAS_SHIFT, HAS_MASK);
	value = REG_VALUE_SET(value, hlbs, HLBS_SHIFT, HLBS_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_HSYNC2, value);

	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_HSYNC3);
	value = REG_VALUE_SET(value, hfps, HFPS_SHIFT, HFPS_MASK);
	value = REG_VALUE_SET(value, hrbs, HRBS_SHIFT, HRBS_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_HSYNC3, value);

	/* Set vsync timings */
	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_VSYNC1);
	value = REG_VALUE_SET(value, vbps, VBPS_SHIFT, VBPS_MASK);
	value = REG_VALUE_SET(value, vt, VT_SHIFT, VT_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_VSYNC1, value);

	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_VSYNC2);
	value = REG_VALUE_SET(value, vas, VAS_SHIFT, VAS_MASK);
	value = REG_VALUE_SET(value, vtbs, VTBS_SHIFT, VTBS_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_VSYNC2, value);

	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_VSYNC3);
	value = REG_VALUE_SET(value, vfps, VFPS_SHIFT, VFPS_MASK);
	value = REG_VALUE_SET(value, vbbs, VBBS_SHIFT, VBBS_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_VSYNC3, value);

	/* Horizontal data enable */
	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_HDECTRL);
	value = REG_VALUE_SET(value, hlbs, HDES_SHIFT, HDES_MASK);
	value = REG_VALUE_SET(value, hfps, HDEF_SHIFT, HDEF_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_HDECTRL, value);

	/* Vertical data enable */
	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_VDECTRL);
	value = REG_VALUE_SET(value, vtbs, VDES_SHIFT, VDES_MASK);
	value = REG_VALUE_SET(value, vfps, VDEF_SHIFT, VDEF_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_VDECTRL, value);

	/* Vertical event start and vertical fetch start */
	value = pdp_rreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_VEVENT);
	value = REG_VALUE_SET(value, vbps, VFETCH_SHIFT, VFETCH_MASK);
	value = REG_VALUE_SET(value, vfps, VEVENT_SHIFT, VEVENT_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_VEVENT, value);

	/* Set up polarities of sync/blank */
	value = REG_VALUE_SET(0, 0x1, BLNKPOL_SHIFT, BLNKPOL_MASK);

	/*
	 * Enable this if you want vblnk1. You also need to change to vblnk1
	 * in the interrupt handler.
	 */
#if 0
	value = REG_VALUE_SET(value, 0x1, FIELDPOL_SHIFT, FIELDPOL_MASK);
#endif
	if (nhsync)
		value = REG_VALUE_SET(value, 0x1, HSPOL_SHIFT, HSPOL_MASK);
	if (nvsync)
		value = REG_VALUE_SET(value, 0x1, VSPOL_SHIFT, VSPOL_MASK);
	pdp_wreg32(pdp_reg, TCF_RGBPDP_PVR_TCF_RGBPDP_SYNCCTRL, value);
}
