1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/atxmega/ebi/ebi.c

376 lines
11 KiB
C
Raw Normal View History

/*
* Copyright (C) 2021 Gerson Fernando Budke <nandojve@gmail.com>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup cpu_atxmega
* @ingroup cpu_atxmega_periph
* @{
*
* @file
* @brief Low-level EBI (External BUS Interface) driver implementation
*
* @author Gerson Fernando Budke <nandojve@gmail.com>
* https://www.avrfreaks.net/forum/xmega-ebi-and-sram
* https://www.avrfreaks.net/forum/xmega-au-four-port-ebi
* https://community.atmel.com/forum/location-variable-specified-address
* @}
*/
#include <avr/io.h>
#include "assert.h"
#include "periph_conf.h"
#include "cpu_pm.h"
#include "cpu_ebi.h"
#define ENABLE_DEBUG 0
#include "debug.h"
void ebi_init(void) __attribute__((naked, section(".init1"), used));
/**
* @brief Set up the I/O ports for use by the EBI.
*
* @note In SDRAM mode the \a sram_ale and \a lpc_ale parameters are ignored
* by the hardware.
*/
void ebi_init(void)
{
EBI_CS_t *cs;
uint8_t mode;
uint8_t sd_ctrl = 0;
uint8_t i;
uint8_t expand_sram = 0;
/*
* This is a mandatory configuration. Whowever, to complete disable module
* just configure it as:
*
* static const ebi_conf_t ebi_config = { 0 };
*
* or, for a temporary disable, set addr_bits to 0 at periph_conf.h:
*
* .addr_bits = 0,
*/
if (ebi_config.addr_bits == 0) {
return;
}
/*
* Set address and control lines as outputs, and active-low control lines
* initially high.
*/
if (ebi_config.flags & EBI_PORT_SDRAM) {
/* With SDRAM, the configuration is fairly fixed. */
PORTH.OUT = 0x0f;
PORTH.DIR = 0xff;
PORTJ.DIR = 0xf0;
PORTK.DIR = 0xff;
} else {
uint8_t ale_mask = ebi_config.sram_ale | ebi_config.lpc_ale;
uint8_t port_mask;
/*
* Set PORTH initial state, set WE and CAS/RE high by default.
* Set chip selects high by default if enabled.
*/
port_mask = 0x03;
if (ebi_config.flags & EBI_PORT_CS0) {
port_mask |= 0x10;
}
if (ebi_config.flags & EBI_PORT_CS1) {
port_mask |= 0x20;
}
if (ebi_config.flags & EBI_PORT_CS2) {
port_mask |= 0x40;
}
if (ebi_config.flags & EBI_PORT_CS3) {
port_mask |= 0x80;
}
PORTH.OUT = port_mask;
/*
* Set PORTH direction, enable WE, CAS/RE and RAS/ALE1 to
* output by default. Set chip select direction if enabled.
*/
port_mask = 0x07;
/* If two latches are in use, enable the ALE2 pin as well. */
if (ale_mask & 0x02) {
port_mask |= 0x08;
}
if (ebi_config.flags & EBI_PORT_CS0 || ebi_config.addr_bits > 16) {
port_mask |= 0x10;
}
if (ebi_config.flags & EBI_PORT_CS1 || ebi_config.addr_bits > 17) {
port_mask |= 0x20;
}
if (ebi_config.flags & EBI_PORT_CS2 || ebi_config.addr_bits > 18) {
port_mask |= 0x40;
}
if (ebi_config.flags & EBI_PORT_CS3 || ebi_config.addr_bits > 19) {
port_mask |= 0x80;
}
PORTH.DIR = port_mask;
/*
* PORTJ is always used for data, direction and value is controlled by
* the EBI module.
*/
/* PORTK is only used in 3-port mode */
if (ebi_config.flags & EBI_PORT_3PORT) {
port_mask = 0x00;
if (ebi_config.flags & EBI_PORT_SRAM) {
/*
* Bits 0..7 go here, so if we have 8 lines or more, enable all
* lines. Otherwise, enable as many as we need.
*/
if (ebi_config.addr_bits < 8) {
port_mask = (1 << ebi_config.addr_bits) - 1;
}
else {
port_mask = 0xff;
}
}
else {
/*
* Bits 8..15 go here, so if we have less than 16 address lines,
* disable the ones that we don't need. If we have 8 lines or
* less, disable all address lines on this port.
*/
if (ebi_config.addr_bits <= 8) {
port_mask = 0x00;
}
else if (ebi_config.addr_bits < 16) {
port_mask = (1 << (ebi_config.addr_bits - 8)) - 1;
}
else {
port_mask = 0xff;
}
}
PORTK.DIR = port_mask;
}
}
if (ebi_config.flags & EBI_PORT_3PORT) {
mode = EBI_IFMODE_3PORT_gc;
}
else {
mode = EBI_IFMODE_2PORT_gc;
}
if (ebi_config.sram_ale == 1) {
mode |= EBI_SRMODE_ALE1_gc;
}
else if (ebi_config.sram_ale == 2) {
mode |= EBI_SRMODE_ALE12_gc;
}
else {
mode |= EBI_SRMODE_NOALE_gc;
}
if (ebi_config.lpc_ale > 0) {
mode |= (ebi_config.lpc_ale << EBI_LPCMODE_gp);
}
if (ebi_config.sdram.cas_latency == EBI_SDRAM_CAS_LAT_3CLK) {
sd_ctrl |= EBI_SDCAS_bm;
}
if (ebi_config.sdram.row_bits == EBI_SDRAM_ROW_BITS_12) {
sd_ctrl |= EBI_SDROW_bm;
}
/* Enable EBI periph clock */
PR.PRGEN &= ~PR_EBI_bm;
/* 8-bit SDRAM requires 4-port EBI, which we don't have. */
EBI.CTRL = EBI_SDDATAW_4BIT_gc
| mode;
EBI.SDRAMCTRLA = sd_ctrl
| ebi_config.sdram.column_bits;
EBI.SDRAMCTRLB = ebi_config.sdram.ld_mode_dly
| ebi_config.sdram.row_cycle_dly
| ebi_config.sdram.row_prechage_dly;
EBI.SDRAMCTRLC = ebi_config.sdram.write_recovery_dly
| ebi_config.sdram.exit_self_rfsh_dly
| ebi_config.sdram.row_to_column_dly;
EBI.REFRESH = ebi_config.sdram.refresh_period & 0x0FFF;
EBI.INITDLY = ebi_config.sdram.init_dly & 0x3FFF;
/* IRQ are disabled here */
cs = (EBI_CS_t *)&EBI.CS0;
for (i = 0; i < PERIPH_EBI_MAX_CS; i++) {
if (ebi_config.cs[i].mode != EBI_CS_MODE_DISABLED_gc &&
ebi_config.cs[i].mode != EBI_CS_MODE_SDRAM_gc) {
/* Configure */
cs[i].CTRLA = ebi_config.cs[i].space;
cs[i].CTRLB = ebi_config.cs[i].wait;
cs[i].BASEADDR = ((ebi_config.cs[i].address >> 8) & 0xfff0);
/* Enable */
cs[i].CTRLA = ebi_config.cs[i].space | ebi_config.cs[i].mode;
if (ebi_config.cs[i].address == 0) {
expand_sram = 1;
}
}
}
/**
* Only CS[3] can be configured as SDRAM.
* CS structure is little bit different too.
*/
if (ebi_config.cs[3].mode == EBI_CS_MODE_SDRAM_gc) {
cs[3].CTRLA = ebi_config.cs[3].space;
cs[3].CTRLB = ebi_config.sdram.mode
| (ebi_config.sdram.refresh ? EBI_CS_SDSREN_bm : 0);
cs[3].BASEADDR = ((ebi_config.cs[3].address >> 8) & 0xfff0);
cs[3].CTRLA = ebi_config.cs[3].space | ebi_config.cs[3].mode;
if (ebi_config.cs[3].address == 0) {
expand_sram = 1;
}
while (!(cs[3].CTRLB & EBI_CS_SDINITDONE_bm)) {};
}
if (expand_sram > 0) {
/**
* @brief Set new Stack Pointer
*/
__asm__ volatile (
"out __SP_L__, %A[stack] \n\t"
"out __SP_H__, %B[stack] \n\t"
: /* no output */
: [stack] "r"(RAM_LEN)
: "memory"
);
};
}
uint16_t hugemem_read16(const hugemem_ptr_t from)
{
uint16_t value;
__asm__ volatile (
"movw r30, %A[from] \n\t"
"out %[rampz], %C[from] \n\t"
"ld %A[dest], Z+ \n\t"
"ld %B[dest], Z \n\t"
"out %[rampz], __zero_reg__ \n\t"
: [dest] "=r"(value)
: [from] "r"(from),
[rampz] "i"(&RAMPZ)
: "r30", "r31"
);
return value;
}
void hugemem_write16(hugemem_ptr_t to, uint16_t val)
{
__asm__ volatile (
"movw r30, %A[to] \n\t"
"out %[rampz], %C[to] \n\t"
"st Z+, %A[val] \n\t"
"st Z, %B[val] \n\t"
"out %[rampz], __zero_reg__ \n\t"
: /* no output */
: [to] "r"(to),
[val] "r"(val),
[rampz] "i"(&RAMPZ)
: "r30", "r31"
);
}
uint32_t hugemem_read32(const hugemem_ptr_t from)
{
uint32_t value;
__asm__ volatile (
"movw r30, %A[from] \n\t"
"out %[rampz], %C[from] \n\t"
"ld %A[dest], Z+ \n\t"
"ld %B[dest], Z+ \n\t"
"ld %C[dest], Z+ \n\t"
"ld %D[dest], Z \n\t"
"out %[rampz], __zero_reg__ \n\t"
: [dest] "=r"(value)
: [from] "r"(from),
[rampz] "i"(&RAMPZ)
: "r30", "r31"
);
return value;
}
void hugemem_write32(hugemem_ptr_t to, uint32_t val)
{
__asm__ volatile (
"movw r30, %A[to] \n\t"
"out %[rampz], %C[to] \n\t"
"st Z+, %A[val] \n\t"
"st Z+, %B[val] \n\t"
"st Z+, %C[val] \n\t"
"st Z, %D[val] \n\t"
"out %[rampz], __zero_reg__ \n\t"
: /* no output */
: [to] "r"(to),
[val] "r"(val),
[rampz] "i"(&RAMPZ)
: "r30", "r31"
);
}
void hugemem_read_block(void *to, const hugemem_ptr_t from, size_t size)
{
if (size > 0) {
__asm__ volatile (
"movw r30, %A[from] \n\t"
"out %[rampz], %C[from] \n\t"
"get_%=: \n\t"
"ld __tmp_reg__, Z+ \n\t"
"st X+, __tmp_reg__ \n\t"
"sbiw %A[size], 1 \n\t"
"brne get_%= \n\t"
"out %[rampz], __zero_reg__ \n\t"
: [to] "+x"(to),
[size] "+w"(size)
: [from] "r"(from),
[rampz] "i"(&RAMPZ)
: "r30", "r31"
);
}
}
void hugemem_write_block(hugemem_ptr_t to, const void *from, size_t size)
{
if (size > 0) {
__asm__ volatile (
"movw r30, %A[from] \n\t"
"out %[rampz], %C[from] \n\t"
"put_%=: \n\t"
"ld __tmp_reg__, X+ \n\t"
"st Z+, __tmp_reg__ \n\t"
"sbiw %A[size], 1 \n\t"
"brne put_%= \n\t"
"out %[rampz], __zero_reg__ \n\t"
: [from] "+x"(from),
[size] "+w"(size)
: [to] "r"(to),
[rampz] "i"(&RAMPZ)
: "r30", "r31"
);
}
}