/* Copyright (c) 2010, Code Aurora Forum. 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/* Architecture-specific VCM functions */

#include <linux/kernel.h>
#include <linux/vcm_mm.h>

#include <asm/pgtable-hwdef.h>
#include <asm/tlbflush.h>

#define MRC(reg, processor, op1, crn, crm, op2)				\
__asm__ __volatile__ (							\
"   mrc   "   #processor "," #op1 ", %0,"  #crn "," #crm "," #op2 " \n" \
: "=r" (reg))

#define RCP15_PRRR(reg)		MRC(reg, p15, 0, c10, c2, 0)
#define RCP15_NMRR(reg) 	MRC(reg, p15, 0, c10, c2, 1)


/* Local type attributes (not the same as VCM) */
#define ARM_MT_NORMAL		2
#define ARM_MT_STRONGLYORDERED	0
#define ARM_MT_DEVICE		1

#define ARM_CP_NONCACHED	0
#define ARM_CP_WB_WA		1
#define ARM_CP_WB_NWA		3
#define ARM_CP_WT_NWA		2

#define smmu_err(a, ...)						\
	pr_err("ERROR %s %i " a, __func__, __LINE__, ##__VA_ARGS__)

#define FL_OFFSET(va)	(((va) & 0xFFF00000) >> 20)
#define SL_OFFSET(va)	(((va) & 0xFF000) >> 12)

int vcm_driver_tex_class[4];

static int find_tex_class(int icp, int ocp, int mt, int nos)
{
	int i = 0;
	unsigned int prrr = 0;
	unsigned int nmrr = 0;
	int c_icp, c_ocp, c_mt, c_nos;

	RCP15_PRRR(prrr);
	RCP15_NMRR(nmrr);

	/* There are only 8 classes on this architecture */
	/* If they add more classes, registers will VASTLY change */
	for (i = 0; i < 8; i++)	{
		c_nos = prrr & (1 << (i + 24)) ? 1 : 0;
		c_mt = (prrr & (3 << (i * 2))) >> (i * 2);
		c_icp = (nmrr & (3 << (i * 2))) >> (i * 2);
		c_ocp = (nmrr & (3 << (i * 2 + 16))) >> (i * 2 + 16);

		if (icp == c_icp && ocp == c_ocp && c_mt == mt && c_nos == nos)
			return i;
	}
	smmu_err("Could not find TEX class for ICP=%d, OCP=%d, MT=%d, NOS=%d\n",
		 icp, ocp, mt, nos);

	/* In reality, we may want to remove this panic. Some classes just */
	/* will not be available, and will fail in smmu_set_attr */
	panic("SMMU: Could not determine TEX attribute mapping.\n");
	return -1;
}


int vcm_setup_tex_classes(void)
{
	unsigned int cpu_prrr;
	unsigned int cpu_nmrr;

	if (!(get_cr() & CR_TRE))	/* No TRE? */
		panic("TEX remap not enabled, but the SMMU driver needs it!\n");

	RCP15_PRRR(cpu_prrr);
	RCP15_NMRR(cpu_nmrr);

	vcm_driver_tex_class[VCM_DEV_ATTR_NONCACHED] =
		find_tex_class(ARM_CP_NONCACHED, ARM_CP_NONCACHED,
			       ARM_MT_NORMAL, 1);

	vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WB_WA] =
		find_tex_class(ARM_CP_WB_WA, ARM_CP_WB_WA,
			       ARM_MT_NORMAL, 1);

	vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WB_NWA] =
		find_tex_class(ARM_CP_WB_NWA, ARM_CP_WB_NWA,
			       ARM_MT_NORMAL, 1);

	vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WT] =
		find_tex_class(ARM_CP_WT_NWA, ARM_CP_WT_NWA,
			       ARM_MT_NORMAL, 1);
#ifdef DEBUG_TEX
	printk(KERN_INFO "VCM driver debug: Using TEX classes: %d %d %d %d\n",
	       vcm_driver_tex_class[VCM_DEV_ATTR_NONCACHED],
	       vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WB_WA],
	       vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WB_NWA],
	       vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WT]);
#endif
	return 0;
}


int set_arm7_pte_attr(unsigned long pt_base, unsigned long va,
					unsigned long len, unsigned int attr)
{
	unsigned long *fl_table = NULL;
	unsigned long *fl_pte = NULL;
	unsigned long fl_offset = 0;
	unsigned long *sl_table = NULL;
	unsigned long *sl_pte = NULL;
	unsigned long sl_offset = 0;
	int i;
	int sh = 0;
	int class = 0;

	/* Alignment */
	if (va & (len-1)) {
		smmu_err("misaligned va: %p\n", (void *) va);
		goto fail;
	}
	if (attr > 7) {
		smmu_err("bad attribute: %d\n", attr);
		goto fail;
	}

	sh = (attr & VCM_DEV_ATTR_SH) ? 1 : 0;
	class = vcm_driver_tex_class[attr & 0x03];

	if (class > 7 || class < 0) {	/* Bad class */
		smmu_err("bad tex class: %d\n", class);
		goto fail;
	}

	if (len != SZ_16M && len != SZ_1M &&
	    len != SZ_64K && len != SZ_4K) {
		smmu_err("bad size: %lu\n", len);
		goto fail;
	}

	fl_table = (unsigned long *) pt_base;

	if (!fl_table) {
		smmu_err("null page table\n");
		goto fail;
	}

	fl_offset = FL_OFFSET(va);	/* Upper 12 bits */
	fl_pte = fl_table + fl_offset;	/* int pointers, 4 bytes */

	if (*fl_pte == 0) {	/* Nothing there! */
		smmu_err("first level pte is 0\n");
		goto fail;
	}

	/* Supersection attributes */
	if (len == SZ_16M) {
		for (i = 0; i < 16; i++) {
			/* Clear the old bits */
			*(fl_pte+i) &= ~(PMD_SECT_S | PMD_SECT_CACHEABLE |
					 PMD_SECT_BUFFERABLE | PMD_SECT_TEX(1));

			/* Assign new class and S bit */
			*(fl_pte+i) |= sh ? PMD_SECT_S : 0;
			*(fl_pte+i) |= class & 0x01 ? PMD_SECT_BUFFERABLE : 0;
			*(fl_pte+i) |= class & 0x02 ? PMD_SECT_CACHEABLE : 0;
			*(fl_pte+i) |= class & 0x04 ? PMD_SECT_TEX(1) : 0;
		}
	} else	if (len == SZ_1M) {

		/* Clear the old bits */
		*(fl_pte) &= ~(PMD_SECT_S | PMD_SECT_CACHEABLE |
			       PMD_SECT_BUFFERABLE | PMD_SECT_TEX(1));

		/* Assign new class and S bit */
		*(fl_pte) |= sh ? PMD_SECT_S : 0;
		*(fl_pte) |= class & 0x01 ? PMD_SECT_BUFFERABLE : 0;
		*(fl_pte) |= class & 0x02 ? PMD_SECT_CACHEABLE : 0;
		*(fl_pte) |= class & 0x04 ? PMD_SECT_TEX(1) : 0;
	}

	sl_table = (unsigned long *) __va(((*fl_pte) & 0xFFFFFC00));
	sl_offset = SL_OFFSET(va);
	sl_pte = sl_table + sl_offset;

	if (len == SZ_64K) {
		for (i = 0; i < 16; i++) {
			/* Clear the old bits */
			*(sl_pte+i) &= ~(PTE_EXT_SHARED | PTE_CACHEABLE |
					 PTE_BUFFERABLE | PTE_EXT_TEX(1));

			/* Assign new class and S bit */
			*(sl_pte+i) |= sh ? PTE_EXT_SHARED : 0;
			*(sl_pte+i) |= class & 0x01 ? PTE_BUFFERABLE : 0;
			*(sl_pte+i) |= class & 0x02 ? PTE_CACHEABLE : 0;
			*(sl_pte+i) |= class & 0x04 ? PTE_EXT_TEX(1) : 0;
		}
	} else 	if (len == SZ_4K) {
		/* Clear the old bits */
		*(sl_pte) &= ~(PTE_EXT_SHARED | PTE_CACHEABLE |
			       PTE_BUFFERABLE | PTE_EXT_TEX(1));

		/* Assign new class and S bit */
		*(sl_pte) |= sh ? PTE_EXT_SHARED : 0;
		*(sl_pte) |= class & 0x01 ? PTE_BUFFERABLE : 0;
		*(sl_pte) |= class & 0x02 ? PTE_CACHEABLE : 0;
		*(sl_pte) |= class & 0x04 ? PTE_EXT_TEX(1) : 0;
	}


	mb();
	return 0;
fail:
	return 1;
}


int cpu_set_attr(unsigned long va, unsigned long len, unsigned int attr)
{
	int ret;
	pgd_t *pgd = init_mm.pgd;

	if (!pgd) {
		smmu_err("null pgd\n");
		goto fail;
	}

	ret = set_arm7_pte_attr((unsigned long)pgd, va, len, attr);

	if (ret != 0) {
		smmu_err("could not set attribute: \
					pgd=%p, va=%p, len=%lu, attr=%d\n",
			 (void *) pgd, (void *) va, len, attr);
		goto fail;
	}
	dmb();
	flush_tlb_all();
	return 0;
fail:
	return -1;
}
