/*
 *  linux/arch/arm/mach-omap/lbmmu.c
 *
 * BRIEF MODULE DESCRIPTION
 *   OMAP local bus MMU fault handler
 *
 * Copyright (C) 2004 Archos SA.
 * Author:
 *         Matthias Welwarsky <welwarsky@archos.com>
 *
 *  This program is free software; you can redistribute  it and/or modify it
 *  under  the terms of  the GNU General  Public License as published by the
 *  Free Software Foundation;  either version 2 of the  License, or (at your
 *  option) any later version.
 *
 *  THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED
 *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
 *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  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.,
 *  675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <linux/config.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/interrupt.h>

#include <asm/hardware.h>
#include <asm/irq.h>
#include <asm/mach/irq.h>
#include <asm/arch/irq.h>
#include <asm/io.h>

#define OMAP1510_LB_OFFSET (0x30000000UL)

#define phys_to_lb(x) \
	((x) ? (((unsigned long) (x)) - PHYS_OFFSET + OMAP1510_LB_OFFSET) : 0)
#define lb_to_phys(x) \
	((x) ? (((unsigned long) (x)) /*- OMAP1510_LB_OFFSET*/ + PHYS_OFFSET) : 0)

#define lb_mmu_cntl_reg		(volatile unsigned short *) 0xfffec208
#define lb_mmu_lock_reg		(volatile unsigned short *) 0xfffec224
#define lb_mmu_ld_tlb_reg	(volatile unsigned short *) 0xfffec228
#define lb_mmu_cam_h_reg	(volatile unsigned short *) 0xfffec22c
#define lb_mmu_cam_l_reg	(volatile unsigned short *) 0xfffec230
#define lb_mmu_ram_h_reg	(volatile unsigned short *) 0xfffec234
#define lb_mmu_ram_l_reg	(volatile unsigned short *) 0xfffec238
#define lb_mmu_it_ack_reg	(volatile unsigned short *) 0xfffec218
#define lb_mmu_fault_ad_l_reg	(volatile unsigned short *) 0xfffec210
#define lb_mmu_fault_ad_h_reg	(volatile unsigned short *) 0xfffec20c
#define lb_mmu_fault_status_reg	(volatile unsigned short *) 0xfffec214

static int victim_counter;

static void omap1510_lbmmu_isr(int irq, void *dev_id, struct pt_regs *regs);

static struct irqaction omap1510_lbmmu_irq = {
	.name		= "OMAP1510 LBMMU IRQ",
	.handler	= omap1510_lbmmu_isr,
	.flags		= SA_INTERRUPT
};

void lbmmu_enable(int status)
{
	if (status)
		*lb_mmu_cntl_reg = 0x3;		// mmu enabled
	else
		*lb_mmu_cntl_reg = 0x1;		// mmu disabled, but not in reset
}

static void lbmmu_add_translation(unsigned long handle, size_t size)
{
	unsigned long physaddr    = handle        & 0xfff00000;
	unsigned long paddr_end   = (handle+size) & 0xfff00000;

	do { 
		unsigned long lbaddr = phys_to_lb(physaddr);
		
		*lb_mmu_cam_h_reg  = (lbaddr & 0x0fffffff) >> 22;
		*lb_mmu_cam_l_reg  = ((lbaddr & 0x003ffc00) >> 6) | 0x4;
		*lb_mmu_ram_h_reg  = physaddr >> 16;
		*lb_mmu_ram_l_reg  = (physaddr & 0x0000fc00) | 0x300;
		*lb_mmu_lock_reg   = victim_counter << 4;
		*lb_mmu_ld_tlb_reg = 0x1;
			
		victim_counter++;
		victim_counter &= 31;
		
		physaddr += 0x00100000;
		
	} while (physaddr < paddr_end);
}

static void omap1510_lbmmu_isr(int irq, void *dev_id, struct pt_regs *regs)
{
	unsigned long lbaddr   = ((unsigned long)(*lb_mmu_fault_ad_h_reg) << 16) | (*lb_mmu_fault_ad_l_reg);
	unsigned long physaddr = lb_to_phys(lbaddr);
	unsigned short status  = *lb_mmu_fault_status_reg;
	
	lbmmu_add_translation(physaddr, 0);
	*lb_mmu_it_ack_reg = 1;
}

int __init omap1510_lbmmu_init(void)
{
	victim_counter = 0;
	setup_arm_irq(INT_LB_MMU, &omap1510_lbmmu_irq);
	lbmmu_enable(1);
	printk(KERN_INFO "OMAP1510 local bus MMU fault handler initialized\n");

	return 0;
}

__initcall(omap1510_lbmmu_init);
