156 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
ARM TCM (Tightly-Coupled Memory) handling in Linux
 | 
						|
----
 | 
						|
Written by Linus Walleij <linus.walleij@stericsson.com>
 | 
						|
 | 
						|
Some ARM SoC:s have a so-called TCM (Tightly-Coupled Memory).
 | 
						|
This is usually just a few (4-64) KiB of RAM inside the ARM
 | 
						|
processor.
 | 
						|
 | 
						|
Due to being embedded inside the CPU The TCM has a
 | 
						|
Harvard-architecture, so there is an ITCM (instruction TCM)
 | 
						|
and a DTCM (data TCM). The DTCM can not contain any
 | 
						|
instructions, but the ITCM can actually contain data.
 | 
						|
The size of DTCM or ITCM is minimum 4KiB so the typical
 | 
						|
minimum configuration is 4KiB ITCM and 4KiB DTCM.
 | 
						|
 | 
						|
ARM CPU:s have special registers to read out status, physical
 | 
						|
location and size of TCM memories. arch/arm/include/asm/cputype.h
 | 
						|
defines a CPUID_TCM register that you can read out from the
 | 
						|
system control coprocessor. Documentation from ARM can be found
 | 
						|
at http://infocenter.arm.com, search for "TCM Status Register"
 | 
						|
to see documents for all CPUs. Reading this register you can
 | 
						|
determine if ITCM (bits 1-0) and/or DTCM (bit 17-16) is present
 | 
						|
in the machine.
 | 
						|
 | 
						|
There is further a TCM region register (search for "TCM Region
 | 
						|
Registers" at the ARM site) that can report and modify the location
 | 
						|
size of TCM memories at runtime. This is used to read out and modify
 | 
						|
TCM location and size. Notice that this is not a MMU table: you
 | 
						|
actually move the physical location of the TCM around. At the
 | 
						|
place you put it, it will mask any underlying RAM from the
 | 
						|
CPU so it is usually wise not to overlap any physical RAM with
 | 
						|
the TCM.
 | 
						|
 | 
						|
The TCM memory can then be remapped to another address again using
 | 
						|
the MMU, but notice that the TCM if often used in situations where
 | 
						|
the MMU is turned off. To avoid confusion the current Linux
 | 
						|
implementation will map the TCM 1 to 1 from physical to virtual
 | 
						|
memory in the location specified by the kernel. Currently Linux
 | 
						|
will map ITCM to 0xfffe0000 and on, and DTCM to 0xfffe8000 and
 | 
						|
on, supporting a maximum of 32KiB of ITCM and 32KiB of DTCM.
 | 
						|
 | 
						|
Newer versions of the region registers also support dividing these
 | 
						|
TCMs in two separate banks, so for example an 8KiB ITCM is divided
 | 
						|
into two 4KiB banks with its own control registers. The idea is to
 | 
						|
be able to lock and hide one of the banks for use by the secure
 | 
						|
world (TrustZone).
 | 
						|
 | 
						|
TCM is used for a few things:
 | 
						|
 | 
						|
- FIQ and other interrupt handlers that need deterministic
 | 
						|
  timing and cannot wait for cache misses.
 | 
						|
 | 
						|
- Idle loops where all external RAM is set to self-refresh
 | 
						|
  retention mode, so only on-chip RAM is accessible by
 | 
						|
  the CPU and then we hang inside ITCM waiting for an
 | 
						|
  interrupt.
 | 
						|
 | 
						|
- Other operations which implies shutting off or reconfiguring
 | 
						|
  the external RAM controller.
 | 
						|
 | 
						|
There is an interface for using TCM on the ARM architecture
 | 
						|
in <asm/tcm.h>. Using this interface it is possible to:
 | 
						|
 | 
						|
- Define the physical address and size of ITCM and DTCM.
 | 
						|
 | 
						|
- Tag functions to be compiled into ITCM.
 | 
						|
 | 
						|
- Tag data and constants to be allocated to DTCM and ITCM.
 | 
						|
 | 
						|
- Have the remaining TCM RAM added to a special
 | 
						|
  allocation pool with gen_pool_create() and gen_pool_add()
 | 
						|
  and provice tcm_alloc() and tcm_free() for this
 | 
						|
  memory. Such a heap is great for things like saving
 | 
						|
  device state when shutting off device power domains.
 | 
						|
 | 
						|
A machine that has TCM memory shall select HAVE_TCM from
 | 
						|
arch/arm/Kconfig for itself. Code that needs to use TCM shall
 | 
						|
#include <asm/tcm.h>
 | 
						|
 | 
						|
Functions to go into itcm can be tagged like this:
 | 
						|
int __tcmfunc foo(int bar);
 | 
						|
 | 
						|
Since these are marked to become long_calls and you may want
 | 
						|
to have functions called locally inside the TCM without
 | 
						|
wasting space, there is also the __tcmlocalfunc prefix that
 | 
						|
will make the call relative.
 | 
						|
 | 
						|
Variables to go into dtcm can be tagged like this:
 | 
						|
int __tcmdata foo;
 | 
						|
 | 
						|
Constants can be tagged like this:
 | 
						|
int __tcmconst foo;
 | 
						|
 | 
						|
To put assembler into TCM just use
 | 
						|
.section ".tcm.text" or .section ".tcm.data"
 | 
						|
respectively.
 | 
						|
 | 
						|
Example code:
 | 
						|
 | 
						|
#include <asm/tcm.h>
 | 
						|
 | 
						|
/* Uninitialized data */
 | 
						|
static u32 __tcmdata tcmvar;
 | 
						|
/* Initialized data */
 | 
						|
static u32 __tcmdata tcmassigned = 0x2BADBABEU;
 | 
						|
/* Constant */
 | 
						|
static const u32 __tcmconst tcmconst = 0xCAFEBABEU;
 | 
						|
 | 
						|
static void __tcmlocalfunc tcm_to_tcm(void)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	for (i = 0; i < 100; i++)
 | 
						|
		tcmvar ++;
 | 
						|
}
 | 
						|
 | 
						|
static void __tcmfunc hello_tcm(void)
 | 
						|
{
 | 
						|
	/* Some abstract code that runs in ITCM */
 | 
						|
	int i;
 | 
						|
	for (i = 0; i < 100; i++) {
 | 
						|
		tcmvar ++;
 | 
						|
	}
 | 
						|
	tcm_to_tcm();
 | 
						|
}
 | 
						|
 | 
						|
static void __init test_tcm(void)
 | 
						|
{
 | 
						|
	u32 *tcmem;
 | 
						|
	int i;
 | 
						|
 | 
						|
	hello_tcm();
 | 
						|
	printk("Hello TCM executed from ITCM RAM\n");
 | 
						|
 | 
						|
	printk("TCM variable from testrun: %u @ %p\n", tcmvar, &tcmvar);
 | 
						|
	tcmvar = 0xDEADBEEFU;
 | 
						|
	printk("TCM variable: 0x%x @ %p\n", tcmvar, &tcmvar);
 | 
						|
 | 
						|
	printk("TCM assigned variable: 0x%x @ %p\n", tcmassigned, &tcmassigned);
 | 
						|
 | 
						|
	printk("TCM constant: 0x%x @ %p\n", tcmconst, &tcmconst);
 | 
						|
 | 
						|
	/* Allocate some TCM memory from the pool */
 | 
						|
	tcmem = tcm_alloc(20);
 | 
						|
	if (tcmem) {
 | 
						|
		printk("TCM Allocated 20 bytes of TCM @ %p\n", tcmem);
 | 
						|
		tcmem[0] = 0xDEADBEEFU;
 | 
						|
		tcmem[1] = 0x2BADBABEU;
 | 
						|
		tcmem[2] = 0xCAFEBABEU;
 | 
						|
		tcmem[3] = 0xDEADBEEFU;
 | 
						|
		tcmem[4] = 0x2BADBABEU;
 | 
						|
		for (i = 0; i < 5; i++)
 | 
						|
			printk("TCM tcmem[%d] = %08x\n", i, tcmem[i]);
 | 
						|
		tcm_free(tcmem, 20);
 | 
						|
	}
 | 
						|
}
 |