/* MN10300 CPU cache invalidation routines, using automatic purge registers
 *
 * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public Licence
 * as published by the Free Software Foundation; either version
 * 2 of the Licence, or (at your option) any later version.
 */
#include <linux/sys.h>
#include <linux/linkage.h>
#include <asm/smp.h>
#include <asm/page.h>
#include <asm/cache.h>
#include <asm/irqflags.h>
#include <asm/cacheflush.h>

#define mn10300_local_dcache_inv_range_intr_interval \
	+((1 << MN10300_DCACHE_INV_RANGE_INTR_LOG2_INTERVAL) - 1)

#if mn10300_local_dcache_inv_range_intr_interval > 0xff
#error MN10300_DCACHE_INV_RANGE_INTR_LOG2_INTERVAL must be 8 or less
#endif

	.am33_2

#ifndef CONFIG_SMP
	.globl	mn10300_icache_inv
	.globl	mn10300_icache_inv_page
	.globl	mn10300_icache_inv_range
	.globl	mn10300_icache_inv_range2
	.globl	mn10300_dcache_inv
	.globl	mn10300_dcache_inv_page
	.globl	mn10300_dcache_inv_range
	.globl	mn10300_dcache_inv_range2

mn10300_icache_inv		= mn10300_local_icache_inv
mn10300_icache_inv_page		= mn10300_local_icache_inv_page
mn10300_icache_inv_range	= mn10300_local_icache_inv_range
mn10300_icache_inv_range2	= mn10300_local_icache_inv_range2
mn10300_dcache_inv		= mn10300_local_dcache_inv
mn10300_dcache_inv_page		= mn10300_local_dcache_inv_page
mn10300_dcache_inv_range	= mn10300_local_dcache_inv_range
mn10300_dcache_inv_range2	= mn10300_local_dcache_inv_range2

#endif /* !CONFIG_SMP */

###############################################################################
#
# void mn10300_local_icache_inv(void)
# Invalidate the entire icache
#
###############################################################################
	ALIGN
	.globl	mn10300_local_icache_inv
        .type	mn10300_local_icache_inv,@function
mn10300_local_icache_inv:
	mov	CHCTR,a0

	movhu	(a0),d0
	btst	CHCTR_ICEN,d0
	beq	mn10300_local_icache_inv_end

	# invalidate
	or	CHCTR_ICINV,d0
	movhu	d0,(a0)
	movhu	(a0),d0

mn10300_local_icache_inv_end:
	ret	[],0
	.size	mn10300_local_icache_inv,.-mn10300_local_icache_inv

###############################################################################
#
# void mn10300_local_dcache_inv(void)
# Invalidate the entire dcache
#
###############################################################################
	ALIGN
	.globl	mn10300_local_dcache_inv
	.type	mn10300_local_dcache_inv,@function
mn10300_local_dcache_inv:
	mov	CHCTR,a0

	movhu	(a0),d0
	btst	CHCTR_DCEN,d0
	beq	mn10300_local_dcache_inv_end

	# invalidate
	or	CHCTR_DCINV,d0
	movhu	d0,(a0)
	movhu	(a0),d0

mn10300_local_dcache_inv_end:
	ret	[],0
	.size	mn10300_local_dcache_inv,.-mn10300_local_dcache_inv

###############################################################################
#
# void mn10300_local_dcache_inv_range(unsigned long start, unsigned long end)
# void mn10300_local_dcache_inv_range2(unsigned long start, unsigned long size)
# void mn10300_local_dcache_inv_page(unsigned long start)
# Invalidate a range of addresses on a page in the dcache
#
###############################################################################
	ALIGN
	.globl	mn10300_local_dcache_inv_page
	.globl	mn10300_local_dcache_inv_range
	.globl	mn10300_local_dcache_inv_range2
	.type	mn10300_local_dcache_inv_page,@function
	.type	mn10300_local_dcache_inv_range,@function
	.type	mn10300_local_dcache_inv_range2,@function
mn10300_local_dcache_inv_page:
	and	~(PAGE_SIZE-1),d0
	mov	PAGE_SIZE,d1
mn10300_local_dcache_inv_range2:
	add	d0,d1
mn10300_local_dcache_inv_range:
	# If we are in writeback mode we check the start and end alignments,
	# and if they're not cacheline-aligned, we must flush any bits outside
	# the range that share cachelines with stuff inside the range
#ifdef CONFIG_MN10300_CACHE_WBACK
	btst	~(L1_CACHE_BYTES-1),d0
	bne	1f
	btst	~(L1_CACHE_BYTES-1),d1
	beq	2f
1:
	bra	mn10300_local_dcache_flush_inv_range
2:
#endif /* CONFIG_MN10300_CACHE_WBACK */

	movm	[d2,d3,a2],(sp)

	mov	CHCTR,a0
	movhu	(a0),d2
	btst	CHCTR_DCEN,d2
	beq	mn10300_local_dcache_inv_range_end

	# round the addresses out to be full cachelines, unless we're in
	# writeback mode, in which case we would be in flush and invalidate by
	# now
#ifndef CONFIG_MN10300_CACHE_WBACK
	and	L1_CACHE_TAG_ADDRESS|L1_CACHE_TAG_ENTRY,d0	# round start
								# addr down

	mov	L1_CACHE_BYTES-1,d2
	add	d2,d1
	and	L1_CACHE_TAG_ADDRESS|L1_CACHE_TAG_ENTRY,d1	# round end addr up
#endif /* !CONFIG_MN10300_CACHE_WBACK */

	sub	d0,d1,d2		# calculate the total size
	mov	d0,a2			# A2 = start address
	mov	d1,a1			# A1 = end address

	LOCAL_CLI_SAVE(d3)

	mov	DCPGCR,a0		# make sure the purger isn't busy
	setlb
	mov	(a0),d0
	btst	DCPGCR_DCPGBSY,d0
	lne

	# skip initial address alignment calculation if address is zero
	mov	d2,d1
	cmp	0,a2
	beq	1f

dcivloop:
	/* calculate alignsize
	 *
	 * alignsize = L1_CACHE_BYTES;
	 * while (! start & alignsize) {
	 *	alignsize <<=1;
	 * }
	 * d1 = alignsize;
	 */
	mov	L1_CACHE_BYTES,d1
	lsr	1,d1
	setlb
	add	d1,d1
	mov	d1,d0
	and	a2,d0
	leq

1:
	/* calculate invsize
	 *
	 * if (totalsize > alignsize) {
	 *	invsize = alignsize;
	 * } else {
	 *	invsize = totalsize;
	 *	tmp = 0x80000000;
	 *	while (! invsize & tmp) {
	 *		tmp >>= 1;
	 *	}
	 *	invsize = tmp;
	 * }
	 * d1 = invsize
	 */
	cmp	d2,d1
	bns	2f
	mov	d2,d1

	mov	0x80000000,d0		# start from 31bit=1
	setlb
	lsr	1,d0
	mov	d0,e0
	and	d1,e0
	leq
	mov	d0,d1

2:
	/* set mask
	 *
	 * mask = ~(invsize-1);
	 * DCPGMR = mask;
	 */
	mov	d1,d0
	add	-1,d0
	not	d0
	mov	d0,(DCPGMR)

	# invalidate area
	mov	a2,d0
	or	DCPGCR_DCI,d0
	mov	d0,(a0)			# DCPGCR = (mask & start) | DCPGCR_DCI

	setlb				# wait for the purge to complete
	mov	(a0),d0
	btst	DCPGCR_DCPGBSY,d0
	lne

	sub	d1,d2			# decrease size remaining
	add	d1,a2			# increase next start address

	/* check invalidating of end address
	 *
	 * a2 = a2 + invsize
	 * if (a2 < end) {
	 *     goto dcivloop;
	 * } */
	cmp	a1,a2
	bns	dcivloop

	LOCAL_IRQ_RESTORE(d3)

mn10300_local_dcache_inv_range_end:
	ret	[d2,d3,a2],12
	.size	mn10300_local_dcache_inv_page,.-mn10300_local_dcache_inv_page
	.size	mn10300_local_dcache_inv_range,.-mn10300_local_dcache_inv_range
	.size	mn10300_local_dcache_inv_range2,.-mn10300_local_dcache_inv_range2

###############################################################################
#
# void mn10300_local_icache_inv_page(unsigned long start)
# void mn10300_local_icache_inv_range2(unsigned long start, unsigned long size)
# void mn10300_local_icache_inv_range(unsigned long start, unsigned long end)
# Invalidate a range of addresses on a page in the icache
#
###############################################################################
	ALIGN
	.globl	mn10300_local_icache_inv_page
	.globl	mn10300_local_icache_inv_range
	.globl	mn10300_local_icache_inv_range2
	.type	mn10300_local_icache_inv_page,@function
	.type	mn10300_local_icache_inv_range,@function
	.type	mn10300_local_icache_inv_range2,@function
mn10300_local_icache_inv_page:
	and	~(PAGE_SIZE-1),d0
	mov	PAGE_SIZE,d1
mn10300_local_icache_inv_range2:
	add	d0,d1
mn10300_local_icache_inv_range:
	movm	[d2,d3,a2],(sp)

	mov	CHCTR,a0
	movhu	(a0),d2
	btst	CHCTR_ICEN,d2
	beq	mn10300_local_icache_inv_range_reg_end

	/* calculate alignsize
	 *
	 * alignsize = L1_CACHE_BYTES;
	 * for (i = (end - start - 1) / L1_CACHE_BYTES ;  i > 0; i >>= 1) {
	 *     alignsize <<= 1;
	 * }
	 * d2 = alignsize;
	 */
	mov	L1_CACHE_BYTES,d2
	sub	d0,d1,d3
	add	-1,d3
	lsr	L1_CACHE_SHIFT,d3
	beq	2f
1:
	add	d2,d2
	lsr	1,d3
	bne	1b
2:

	/* a1 = end */
	mov	d1,a1

	LOCAL_CLI_SAVE(d3)

	mov	ICIVCR,a0
	/* wait for busy bit of area invalidation */
	setlb
	mov	(a0),d1
	btst	ICIVCR_ICIVBSY,d1
	lne

	/* set mask
	 *
	 * mask = ~(alignsize-1);
	 * ICIVMR = mask;
	 */
	mov	d2,d1
	add	-1,d1
	not	d1
	mov	d1,(ICIVMR)
	/* a2 = mask & start */
	and	d1,d0,a2

icivloop:
	/* area invalidate
	 *
	 * ICIVCR = (mask & start) | ICIVCR_ICI
	 */
	mov	a2,d0
	or	ICIVCR_ICI,d0
	mov	d0,(a0)

	/* wait for busy bit of area invalidation */
	setlb
	mov	(a0),d1
	btst	ICIVCR_ICIVBSY,d1
	lne

	/* check invalidating of end address
	 *
	 * a2 = a2 + alignsize
	 * if (a2 < end) {
	 *     goto icivloop;
	 * } */
	add	d2,a2
	cmp	a1,a2
	bns	icivloop

	LOCAL_IRQ_RESTORE(d3)

mn10300_local_icache_inv_range_reg_end:
	ret	[d2,d3,a2],12
	.size	mn10300_local_icache_inv_page,.-mn10300_local_icache_inv_page
	.size	mn10300_local_icache_inv_range,.-mn10300_local_icache_inv_range
	.size	mn10300_local_icache_inv_range2,.-mn10300_local_icache_inv_range2
