162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * r2300.c: R2000 and R3000 specific mmu/cache code.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 1996 David S. Miller (davem@davemloft.net)
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * with a lot of changes to make this thing work for R3000s
862306a36Sopenharmony_ci * Tx39XX R4k style caches added. HK
962306a36Sopenharmony_ci * Copyright (C) 1998, 1999, 2000 Harald Koerfgen
1062306a36Sopenharmony_ci * Copyright (C) 1998 Gleb Raiko & Vladimir Roganov
1162306a36Sopenharmony_ci * Copyright (C) 2001, 2004, 2007  Maciej W. Rozycki
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci#include <linux/kernel.h>
1462306a36Sopenharmony_ci#include <linux/sched.h>
1562306a36Sopenharmony_ci#include <linux/smp.h>
1662306a36Sopenharmony_ci#include <linux/mm.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include <asm/page.h>
1962306a36Sopenharmony_ci#include <asm/mmu_context.h>
2062306a36Sopenharmony_ci#include <asm/isadep.h>
2162306a36Sopenharmony_ci#include <asm/io.h>
2262306a36Sopenharmony_ci#include <asm/bootinfo.h>
2362306a36Sopenharmony_ci#include <asm/cpu.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic unsigned long icache_size, dcache_size;		/* Size in bytes */
2662306a36Sopenharmony_cistatic unsigned long icache_lsize, dcache_lsize;	/* Size in bytes */
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ciunsigned long r3k_cache_size(unsigned long ca_flags)
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	unsigned long flags, status, dummy, size;
3162306a36Sopenharmony_ci	volatile unsigned long *p;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	p = (volatile unsigned long *) KSEG0;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	flags = read_c0_status();
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	/* isolate cache space */
3862306a36Sopenharmony_ci	write_c0_status((ca_flags|flags)&~ST0_IEC);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	*p = 0xa5a55a5a;
4162306a36Sopenharmony_ci	dummy = *p;
4262306a36Sopenharmony_ci	status = read_c0_status();
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	if (dummy != 0xa5a55a5a || (status & ST0_CM)) {
4562306a36Sopenharmony_ci		size = 0;
4662306a36Sopenharmony_ci	} else {
4762306a36Sopenharmony_ci		for (size = 128; size <= 0x40000; size <<= 1)
4862306a36Sopenharmony_ci			*(p + size) = 0;
4962306a36Sopenharmony_ci		*p = -1;
5062306a36Sopenharmony_ci		for (size = 128;
5162306a36Sopenharmony_ci		     (size <= 0x40000) && (*(p + size) == 0);
5262306a36Sopenharmony_ci		     size <<= 1)
5362306a36Sopenharmony_ci			;
5462306a36Sopenharmony_ci		if (size > 0x40000)
5562306a36Sopenharmony_ci			size = 0;
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	write_c0_status(flags);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	return size * sizeof(*p);
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ciunsigned long r3k_cache_lsize(unsigned long ca_flags)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	unsigned long flags, status, lsize, i;
6662306a36Sopenharmony_ci	volatile unsigned long *p;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	p = (volatile unsigned long *) KSEG0;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	flags = read_c0_status();
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/* isolate cache space */
7362306a36Sopenharmony_ci	write_c0_status((ca_flags|flags)&~ST0_IEC);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	for (i = 0; i < 128; i++)
7662306a36Sopenharmony_ci		*(p + i) = 0;
7762306a36Sopenharmony_ci	*(volatile unsigned char *)p = 0;
7862306a36Sopenharmony_ci	for (lsize = 1; lsize < 128; lsize <<= 1) {
7962306a36Sopenharmony_ci		*(p + lsize);
8062306a36Sopenharmony_ci		status = read_c0_status();
8162306a36Sopenharmony_ci		if (!(status & ST0_CM))
8262306a36Sopenharmony_ci			break;
8362306a36Sopenharmony_ci	}
8462306a36Sopenharmony_ci	for (i = 0; i < 128; i += lsize)
8562306a36Sopenharmony_ci		*(volatile unsigned char *)(p + i) = 0;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	write_c0_status(flags);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	return lsize * sizeof(*p);
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic void r3k_probe_cache(void)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	dcache_size = r3k_cache_size(ST0_ISC);
9562306a36Sopenharmony_ci	if (dcache_size)
9662306a36Sopenharmony_ci		dcache_lsize = r3k_cache_lsize(ST0_ISC);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	icache_size = r3k_cache_size(ST0_ISC|ST0_SWC);
9962306a36Sopenharmony_ci	if (icache_size)
10062306a36Sopenharmony_ci		icache_lsize = r3k_cache_lsize(ST0_ISC|ST0_SWC);
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic void r3k_flush_icache_range(unsigned long start, unsigned long end)
10462306a36Sopenharmony_ci{
10562306a36Sopenharmony_ci	unsigned long size, i, flags;
10662306a36Sopenharmony_ci	volatile unsigned char *p;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	size = end - start;
10962306a36Sopenharmony_ci	if (size > icache_size || KSEGX(start) != KSEG0) {
11062306a36Sopenharmony_ci		start = KSEG0;
11162306a36Sopenharmony_ci		size = icache_size;
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci	p = (char *)start;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	flags = read_c0_status();
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	/* isolate cache space */
11862306a36Sopenharmony_ci	write_c0_status((ST0_ISC|ST0_SWC|flags)&~ST0_IEC);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	for (i = 0; i < size; i += 0x080) {
12162306a36Sopenharmony_ci		asm(	"sb\t$0, 0x000(%0)\n\t"
12262306a36Sopenharmony_ci			"sb\t$0, 0x004(%0)\n\t"
12362306a36Sopenharmony_ci			"sb\t$0, 0x008(%0)\n\t"
12462306a36Sopenharmony_ci			"sb\t$0, 0x00c(%0)\n\t"
12562306a36Sopenharmony_ci			"sb\t$0, 0x010(%0)\n\t"
12662306a36Sopenharmony_ci			"sb\t$0, 0x014(%0)\n\t"
12762306a36Sopenharmony_ci			"sb\t$0, 0x018(%0)\n\t"
12862306a36Sopenharmony_ci			"sb\t$0, 0x01c(%0)\n\t"
12962306a36Sopenharmony_ci			"sb\t$0, 0x020(%0)\n\t"
13062306a36Sopenharmony_ci			"sb\t$0, 0x024(%0)\n\t"
13162306a36Sopenharmony_ci			"sb\t$0, 0x028(%0)\n\t"
13262306a36Sopenharmony_ci			"sb\t$0, 0x02c(%0)\n\t"
13362306a36Sopenharmony_ci			"sb\t$0, 0x030(%0)\n\t"
13462306a36Sopenharmony_ci			"sb\t$0, 0x034(%0)\n\t"
13562306a36Sopenharmony_ci			"sb\t$0, 0x038(%0)\n\t"
13662306a36Sopenharmony_ci			"sb\t$0, 0x03c(%0)\n\t"
13762306a36Sopenharmony_ci			"sb\t$0, 0x040(%0)\n\t"
13862306a36Sopenharmony_ci			"sb\t$0, 0x044(%0)\n\t"
13962306a36Sopenharmony_ci			"sb\t$0, 0x048(%0)\n\t"
14062306a36Sopenharmony_ci			"sb\t$0, 0x04c(%0)\n\t"
14162306a36Sopenharmony_ci			"sb\t$0, 0x050(%0)\n\t"
14262306a36Sopenharmony_ci			"sb\t$0, 0x054(%0)\n\t"
14362306a36Sopenharmony_ci			"sb\t$0, 0x058(%0)\n\t"
14462306a36Sopenharmony_ci			"sb\t$0, 0x05c(%0)\n\t"
14562306a36Sopenharmony_ci			"sb\t$0, 0x060(%0)\n\t"
14662306a36Sopenharmony_ci			"sb\t$0, 0x064(%0)\n\t"
14762306a36Sopenharmony_ci			"sb\t$0, 0x068(%0)\n\t"
14862306a36Sopenharmony_ci			"sb\t$0, 0x06c(%0)\n\t"
14962306a36Sopenharmony_ci			"sb\t$0, 0x070(%0)\n\t"
15062306a36Sopenharmony_ci			"sb\t$0, 0x074(%0)\n\t"
15162306a36Sopenharmony_ci			"sb\t$0, 0x078(%0)\n\t"
15262306a36Sopenharmony_ci			"sb\t$0, 0x07c(%0)\n\t"
15362306a36Sopenharmony_ci			: : "r" (p) );
15462306a36Sopenharmony_ci		p += 0x080;
15562306a36Sopenharmony_ci	}
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	write_c0_status(flags);
15862306a36Sopenharmony_ci}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic void r3k_flush_dcache_range(unsigned long start, unsigned long end)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	unsigned long size, i, flags;
16362306a36Sopenharmony_ci	volatile unsigned char *p;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	size = end - start;
16662306a36Sopenharmony_ci	if (size > dcache_size || KSEGX(start) != KSEG0) {
16762306a36Sopenharmony_ci		start = KSEG0;
16862306a36Sopenharmony_ci		size = dcache_size;
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci	p = (char *)start;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	flags = read_c0_status();
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	/* isolate cache space */
17562306a36Sopenharmony_ci	write_c0_status((ST0_ISC|flags)&~ST0_IEC);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	for (i = 0; i < size; i += 0x080) {
17862306a36Sopenharmony_ci		asm(	"sb\t$0, 0x000(%0)\n\t"
17962306a36Sopenharmony_ci			"sb\t$0, 0x004(%0)\n\t"
18062306a36Sopenharmony_ci			"sb\t$0, 0x008(%0)\n\t"
18162306a36Sopenharmony_ci			"sb\t$0, 0x00c(%0)\n\t"
18262306a36Sopenharmony_ci			"sb\t$0, 0x010(%0)\n\t"
18362306a36Sopenharmony_ci			"sb\t$0, 0x014(%0)\n\t"
18462306a36Sopenharmony_ci			"sb\t$0, 0x018(%0)\n\t"
18562306a36Sopenharmony_ci			"sb\t$0, 0x01c(%0)\n\t"
18662306a36Sopenharmony_ci			"sb\t$0, 0x020(%0)\n\t"
18762306a36Sopenharmony_ci			"sb\t$0, 0x024(%0)\n\t"
18862306a36Sopenharmony_ci			"sb\t$0, 0x028(%0)\n\t"
18962306a36Sopenharmony_ci			"sb\t$0, 0x02c(%0)\n\t"
19062306a36Sopenharmony_ci			"sb\t$0, 0x030(%0)\n\t"
19162306a36Sopenharmony_ci			"sb\t$0, 0x034(%0)\n\t"
19262306a36Sopenharmony_ci			"sb\t$0, 0x038(%0)\n\t"
19362306a36Sopenharmony_ci			"sb\t$0, 0x03c(%0)\n\t"
19462306a36Sopenharmony_ci			"sb\t$0, 0x040(%0)\n\t"
19562306a36Sopenharmony_ci			"sb\t$0, 0x044(%0)\n\t"
19662306a36Sopenharmony_ci			"sb\t$0, 0x048(%0)\n\t"
19762306a36Sopenharmony_ci			"sb\t$0, 0x04c(%0)\n\t"
19862306a36Sopenharmony_ci			"sb\t$0, 0x050(%0)\n\t"
19962306a36Sopenharmony_ci			"sb\t$0, 0x054(%0)\n\t"
20062306a36Sopenharmony_ci			"sb\t$0, 0x058(%0)\n\t"
20162306a36Sopenharmony_ci			"sb\t$0, 0x05c(%0)\n\t"
20262306a36Sopenharmony_ci			"sb\t$0, 0x060(%0)\n\t"
20362306a36Sopenharmony_ci			"sb\t$0, 0x064(%0)\n\t"
20462306a36Sopenharmony_ci			"sb\t$0, 0x068(%0)\n\t"
20562306a36Sopenharmony_ci			"sb\t$0, 0x06c(%0)\n\t"
20662306a36Sopenharmony_ci			"sb\t$0, 0x070(%0)\n\t"
20762306a36Sopenharmony_ci			"sb\t$0, 0x074(%0)\n\t"
20862306a36Sopenharmony_ci			"sb\t$0, 0x078(%0)\n\t"
20962306a36Sopenharmony_ci			"sb\t$0, 0x07c(%0)\n\t"
21062306a36Sopenharmony_ci			: : "r" (p) );
21162306a36Sopenharmony_ci		p += 0x080;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	write_c0_status(flags);
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic inline void r3k_flush_cache_all(void)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cistatic inline void r3k___flush_cache_all(void)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	r3k_flush_dcache_range(KSEG0, KSEG0 + dcache_size);
22462306a36Sopenharmony_ci	r3k_flush_icache_range(KSEG0, KSEG0 + icache_size);
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_cistatic void r3k_flush_cache_mm(struct mm_struct *mm)
22862306a36Sopenharmony_ci{
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic void r3k_flush_cache_range(struct vm_area_struct *vma,
23262306a36Sopenharmony_ci				  unsigned long start, unsigned long end)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic void r3k_flush_cache_page(struct vm_area_struct *vma,
23762306a36Sopenharmony_ci				 unsigned long addr, unsigned long pfn)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	unsigned long kaddr = KSEG0ADDR(pfn << PAGE_SHIFT);
24062306a36Sopenharmony_ci	int exec = vma->vm_flags & VM_EXEC;
24162306a36Sopenharmony_ci	struct mm_struct *mm = vma->vm_mm;
24262306a36Sopenharmony_ci	pmd_t *pmdp;
24362306a36Sopenharmony_ci	pte_t *ptep;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	pr_debug("cpage[%08llx,%08lx]\n",
24662306a36Sopenharmony_ci		 cpu_context(smp_processor_id(), mm), addr);
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	/* No ASID => no such page in the cache.  */
24962306a36Sopenharmony_ci	if (cpu_context(smp_processor_id(), mm) == 0)
25062306a36Sopenharmony_ci		return;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	pmdp = pmd_off(mm, addr);
25362306a36Sopenharmony_ci	ptep = pte_offset_kernel(pmdp, addr);
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	/* Invalid => no such page in the cache.  */
25662306a36Sopenharmony_ci	if (!(pte_val(*ptep) & _PAGE_PRESENT))
25762306a36Sopenharmony_ci		return;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	r3k_flush_dcache_range(kaddr, kaddr + PAGE_SIZE);
26062306a36Sopenharmony_ci	if (exec)
26162306a36Sopenharmony_ci		r3k_flush_icache_range(kaddr, kaddr + PAGE_SIZE);
26262306a36Sopenharmony_ci}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_cistatic void r3k_flush_data_cache_page(unsigned long addr)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic void r3k_flush_kernel_vmap_range(unsigned long vaddr, int size)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	BUG();
27162306a36Sopenharmony_ci}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_cistatic void r3k_dma_cache_wback_inv(unsigned long start, unsigned long size)
27462306a36Sopenharmony_ci{
27562306a36Sopenharmony_ci	/* Catch bad driver code */
27662306a36Sopenharmony_ci	BUG_ON(size == 0);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	iob();
27962306a36Sopenharmony_ci	r3k_flush_dcache_range(start, start + size);
28062306a36Sopenharmony_ci}
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_civoid r3k_cache_init(void)
28362306a36Sopenharmony_ci{
28462306a36Sopenharmony_ci	extern void build_clear_page(void);
28562306a36Sopenharmony_ci	extern void build_copy_page(void);
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	r3k_probe_cache();
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	flush_cache_all = r3k_flush_cache_all;
29062306a36Sopenharmony_ci	__flush_cache_all = r3k___flush_cache_all;
29162306a36Sopenharmony_ci	flush_cache_mm = r3k_flush_cache_mm;
29262306a36Sopenharmony_ci	flush_cache_range = r3k_flush_cache_range;
29362306a36Sopenharmony_ci	flush_cache_page = r3k_flush_cache_page;
29462306a36Sopenharmony_ci	flush_icache_range = r3k_flush_icache_range;
29562306a36Sopenharmony_ci	local_flush_icache_range = r3k_flush_icache_range;
29662306a36Sopenharmony_ci	__flush_icache_user_range = r3k_flush_icache_range;
29762306a36Sopenharmony_ci	__local_flush_icache_user_range = r3k_flush_icache_range;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	__flush_kernel_vmap_range = r3k_flush_kernel_vmap_range;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	flush_data_cache_page = r3k_flush_data_cache_page;
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	_dma_cache_wback_inv = r3k_dma_cache_wback_inv;
30462306a36Sopenharmony_ci	_dma_cache_wback = r3k_dma_cache_wback_inv;
30562306a36Sopenharmony_ci	_dma_cache_inv = r3k_dma_cache_wback_inv;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	pr_info("Primary instruction cache %ldkB, linesize %ld bytes.\n",
30862306a36Sopenharmony_ci		icache_size >> 10, icache_lsize);
30962306a36Sopenharmony_ci	pr_info("Primary data cache %ldkB, linesize %ld bytes.\n",
31062306a36Sopenharmony_ci		dcache_size >> 10, dcache_lsize);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	build_clear_page();
31362306a36Sopenharmony_ci	build_copy_page();
31462306a36Sopenharmony_ci}
315