/*
 *  linux/arch/arm/mach-mx3/cpu.c
 *
 *  Copyright (C) 2001 Deep Blue Solutions Ltd.
 *  Copyright (C) 2004 FreeScale Semiconductor
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#include <linux/config.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/cpufreq.h>
#include <linux/init.h>
#include <asm/hardware.h>
#include <asm/setup.h>
#include <asm/io.h>
#include <asm/arch/platform.h>
#include <asm/cacheflush.h>

/* L210 related defines */
#define L2_CACHE_LINE_SIZE              32
#define L2_CACHE_CTL_REG                0x100
#define L2_CACHE_SYNC_REG               0x730
#define L2_CACHE_INV_LINE_REG           0x770
#define L2_CACHE_CLEAN_LINE_REG         0x7B0
#define L2_CACHE_CLEAN_INV_LINE_REG     0x7F0

extern void v6_flush_kern_cache_all(void);
extern void v6_flush_kern_cache_all_L2(void);
extern void disable_L2(void);
extern void v6_coherent_kern_range_L2(unsigned long, unsigned long);
extern void v6_coherent_user_range_L2(unsigned long, unsigned long);
extern void v6_flush_kern_dcache_page_L2(void *);
extern void v6_flush_user_cache_all_L2(void);
extern void v6_flush_user_cache_range_L2(unsigned long, unsigned long, 
                                         unsigned int);

/*
 * Here are the L2cache options from the command line. By default the
 * L2 is on with L2 Write back disabled.
 * 
 *       "on" --  L2 cache on
 *       "off" -- L2 cache off
 *       "on1" -- L2 cache - DON'T force write through and read-allocate 
 *                           only behavior
 *       "on2" -- L2 cache - Disable cache linefills 
 *       "on3" -- include L2 flushing in kernel caching functions
 *       "on4" -- L1 cache - Force write through behavior for regions 
 *                           marked as write-back
 *       "on5" -- L1 cache - Disable data cache linefills
*/

static unsigned char l2_cache_on[128] = "on";    /* default */

/*!
 * @file cpu.c
 * 
 * @brief This file contains the CPU initialization code. 
 * 
 * @ingroup System
 */

/*!
 * CPU initialization. It is called by fixup_mx31ads()
 */
void __init mx31_cpu_init(void)
{
        /* Setup Peripheral Port Remap register for AVIC */
        asm("stmfd sp!, {r0}");
        asm("ldr r0, =0x40000015"); 
        asm("mcr p15, 0, r0, c15, c2, 4");
        asm("ldmia sp!, {r0}");
}

#define L2_CACHE_BOOT_NAME                 "L2cache"

/*!
 * parse_args system function passes kernel's parsed arguments
 * to do_l2cache_params in order to verify whether L2cache parameter
 * has been passed or not.
 * 
 * @param  param  pointer to a character string for parameter name
 * @param  val    pointer to a character string for parameter value
 *
 * @return  always 0
 */
static int __init do_l2cache_params(char* param, char* val)
{
        if(strcmp(param, L2_CACHE_BOOT_NAME) == 0) {
                strcpy(l2_cache_on, val);
        }
        return 0;
}

/*!
 * This function search for 'L2cache' in kernel's command line
 * options and checks its value. The l2_cache_on string will be
 * updated afterwards. By default, L2 cache is on.
 *
 * @param   cmdline  pointer to the kernel command line
 * @return  0 always
 */
static int l2_cache_parse_cmdline(const char* cmdline)
{
        char tmp_cmdline[COMMAND_LINE_SIZE];

        strlcpy(tmp_cmdline, cmdline, COMMAND_LINE_SIZE);
        parse_args("L2 cache option", tmp_cmdline, NULL, 0, do_l2cache_params);
        
        return 0;
}

/*!
 * Initialize and enable L2 cache based on boot command line 
 */
static void l2_cache_init(void)
{
        volatile unsigned long aips_reg;

        /* make sure rval/wval in the clkctl module correct for L2 memory */
        writel(0x515, IO_ADDRESS(CLKCTL_BASE_ADDR + 0x10));

	/* 
         * Clear the off platform peripheral modules Supervisor Protect bit
         * for SDMA to access them. 
	  */
        writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x40));
        writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x44));
        writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x48));
        writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x4C));
        aips_reg = readl(IO_ADDRESS(AIPS1_BASE_ADDR + 0x50));
        aips_reg &= 0x00FFFFFF;
        writel(aips_reg, IO_ADDRESS(AIPS1_BASE_ADDR + 0x50));        
        
        writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x40));
        writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x44));
        writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x48));
        writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x4C));
        aips_reg = readl(IO_ADDRESS(AIPS2_BASE_ADDR + 0x50));
        aips_reg &= 0x00FFFFFF;
        writel(aips_reg, IO_ADDRESS(AIPS2_BASE_ADDR + 0x50));

        l2_cache_parse_cmdline((const char*)saved_command_line);

        printk("L2 cache is: ");
        if (strcmp(l2_cache_on, "on") == 0) {
                printk("on");
        } else if (strcmp(l2_cache_on, "on1") == 0) {
                printk("on -- L2: DON'T force WT and read-allocate only behavior");
                asm("mov r0, #0xD1000000");
                asm("mov r1, #0x0");
                asm("str r1, [r0, #0xF40]");
        } else if (strcmp(l2_cache_on, "on2") == 0) {
                printk("on -- L2: Disable cache linefills");
                asm("mov r0, #0xD1000000");
                asm("mov r1, #0x1");
                asm("str r1, [r0, #0xF40]");
        } else if (strcmp(l2_cache_on, "on3") == 0) {
                printk("on -- with replacing kernel caching functions for L2");
                v6_flush_kern_cache_all_L2();
                cpu_cache.flush_kern_all = v6_flush_kern_cache_all_L2;
                cpu_cache.coherent_kern_range = v6_coherent_kern_range_L2;
                cpu_cache.coherent_user_range = v6_coherent_user_range_L2;
                cpu_cache.flush_kern_dcache_page = v6_flush_kern_dcache_page_L2;
                cpu_cache.flush_user_all = v6_flush_user_cache_all_L2;
                cpu_cache.flush_user_range = v6_flush_user_cache_range_L2;
                v6_flush_kern_cache_all_L2();
        } else if (strcmp(l2_cache_on, "on4") == 0) {
                printk("on -- L1: Force WT for regions marked as write-back");
                asm("mov r1, #0x4");  /* works !!!  but 0x2 doesn't work */
                asm("mcr p15, 7, r1, c15, c0, 0");
        } else if (strcmp(l2_cache_on, "on5") == 0) {
                printk("on -- L1: Disable data cache linefills");
                asm("mov r1, #0x1");  /* works !!!  but 0x2 doesn't work */
                asm("mcr p15, 7, r1, c15, c0, 0");
        } else {
                printk("off");
	        v6_flush_kern_cache_all_L2();
                disable_L2();
        }
        
        printk("\n");
}

/*!
 * Post CPU init code
 * 
 * @return 0 always
 */
static int __init post_cpu_init(void)
{
        l2_cache_init();
        return 0;
}

arch_initcall(post_cpu_init);

static void __noinstrument __attribute__((naked))
blk_dma_inv_range_harvard_l2(unsigned long start, unsigned long end)
{
        asm(
    	"mov	r2, r0				@ save r0 -> r2         \n\
        mov	r3, r1				@ save r1 -> r3             \n\
        tst	r0, %0								\n\
    	mcrne	p15, 0, r0, c7, c10, 1		@ clean D line			\n\
    	tst	r1, %0								\n\
    	mcrne	p15, 0, r1, c7, c14, 1		@ clean & invalidate D line	\n\
    	.word	0xec401f06	@ mcrr	p15, 0, r1, r0, c6, 0	@ blocking	\n\
    	mov	r0, #0								\n\
    	mcr	p15, 0, r0, c7, c10, 4		@ drain write buffer		\n\
        \
        \
        @ L2 cache operations: r0, r1 scratch, r2=start, r3=end         \n\
        mov	r0, %1                  @ L2CC_BASE_ADDR_VIRT           \n\
        sub	r2, r2, %2		@ PA of start                   \n\
        sub	r3, r3, %2		@ PA of end                     \n\
        tst	r2, #32 - 1             @ L2_CACHE_LINE_SIZE -1         \n\
        bic	r2, r2, #32 - 1         @ L2_CACHE_LINE_SIZE -1         \n\
        strne	r2, [r0, #0x7B0]	@ L2_CACHE_CLEAN_LINE_REG       \n\
        tst	r3, #32 - 1             @ L2_CACHE_LINE_SIZE -1         \n\
        bic	r3, r3, #32 - 1         @ L2_CACHE_LINE_SIZE -1         \n\
        strne	r3, [r0, #0x7F0]	@ L2_CACHE_CLEAN_INV_LINE_REG   \n\
    1:                                                                  \n\
        str	r2, [r0, #0x770]	@ L2_CACHE_INV_LINE_REG         \n\
        add	r2, r2, #32             @ L2_CACHE_LINE_SIZE            \n\
        cmp	r2, r3                                                  \n\
        blo	1b                                                      \n\
        \
        \
    	mov	pc, lr"
    	:
    	: "I" (L1_CACHE_BYTES - 1), "I" (L2CC_BASE_ADDR_VIRT), 
          "I" (PAGE_OFFSET_ASM - PHYS_OFFSET_ASM));
}

/*
 *	blk_dma_clean_range(start,end)
 *	- start   - virtual start address of region
 *	- end     - virtual end address of region
 */
static void __noinstrument __attribute__((naked))
blk_dma_clean_range_l2(unsigned long start, unsigned long end)
{
    	asm(
    	"mov	r2, r0 \n\
        mov	r3, r1 \n\
        .word	0xec401f0c	@ mcrr	p15, 0, r1, r0, c12, 0	@ blocking	\n\
    	mov	r0, #0								\n\
    	mcr	p15, 0, r0, c7, c10, 4		@ drain write buffer		\n\
        \
        \
        @L2 cache operations: r0, r1 scratch, r2=start, r3=end          \n\
        mov	r0, %0                  @ L2CC_BASE_ADDR_VIRT           \n\
        sub	r2, r2, %1		@ PA of start                   \n\
        sub	r3, r3, %1		@ PA of end                     \n\
        bic	r2, r2, #32 - 1         @ L2_CACHE_LINE_SIZE -1         \n\
    1:                                                                  \n\
        str     r2, [r0, #0x7B0]        @ L2_CACHE_CLEAN_LINE_REG       \n\
        add	r2, r2, #32             @ L2_CACHE_LINE_SIZE            \n\
        cmp	r2, r3                                                  \n\
        blo	1b                                                      \n\
    	\
        \
    	mov	pc, lr"
    	:
    	: "I" (L2CC_BASE_ADDR_VIRT), "I" (PAGE_OFFSET_ASM - PHYS_OFFSET_ASM));
}

/*
 *	blk_dma_flush_range(start,end)
 *	- start   - virtual start address of region
 *	- end     - virtual end address of region
 */
static void __noinstrument __attribute__((naked))
blk_dma_flush_range_l2(unsigned long start, unsigned long end)
{
    	asm(
        "mov	r2, r0 \n\
        mov	r3, r1 \n\
        .word	0xec401f0e	@ mcrr	p15, 0, r1, r0, c14, 0	@ blocking	\n\
        \
        \
        @L2 cache operations: r0, r1 scratch, r2=start, r3=end          \n\
        mov	r0, %0          	@ L2CC_BASE_ADDR_VIRT           \n\
        sub	r2, r2, %1		@ PA of start                   \n\
        sub	r3, r3, %1		@ PA of end                     \n\
        bic	r2, r2, #32 - 1         @ L2_CACHE_LINE_SIZE -1         \n\
    1:                                                                  \n\
        str     r2, [r0, #0x7F0]        @ L2_CACHE_CLEAN_INV_LINE_REG   \n\
        add	r2, r2, #32             @ L2_CACHE_LINE_SIZE            \n\
        cmp	r2, r3                                                  \n\
        blo	1b                                                      \n\
        \
        \
    	mov	pc, lr"
    	:
    	: "I" (L2CC_BASE_ADDR_VIRT), "I" (PAGE_OFFSET_ASM - PHYS_OFFSET_ASM));
}

/*!
 * This function is used to replace the kernel dma-related functions with 
 * specific ones.
 *
 * @return 0 always
 */
static int __init l2_dma_api_register(void)
{
        if (strcmp(l2_cache_on, "off") != 0) {
                printk("Replacing Kernel DMA API for L2 cache\n");
                cpu_cache.dma_inv_range = blk_dma_inv_range_harvard_l2;
                cpu_cache.dma_clean_range = blk_dma_clean_range_l2;
                cpu_cache.dma_flush_range = blk_dma_flush_range_l2;
        }

        return 0;
}

module_init(l2_dma_api_register);
