lift_indicator_suite/bsp/sdram/src/sdram.c

336 lines
13 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file sdram.c
* @brief Подъём SEMC и верификация внешней SDRAM MT48LC16M16A2 (32 МБ, 16 бит).
*
* Два независимых куска:
*
* bsp_sdram_configure() — поднимает SEMC. Побитовый порт проверенной DCD-последовательности
* (tools/host/dcd/dcd.bin)
*
* bsp_sdram_init() — верификация уже поднятой памяти (DCD или configure).
* Регистры SEMC не модифицирует.
*
* Кэш: SDRAM настроена как Normal Write-Back cacheable (MPU Region 8).
* Верификация требует явного cache maintenance перед readback — иначе
* чтение попадает в кэш и физическая SDRAM не тестируется.
*/
#include "bsp/sdram.h"
#include "bsp/tick.h"
#include "fsl_clock.h"
#include "fsl_semc.h"
#include <stdbool.h>
#include <stdint.h>
/* ── Константы верификации ─────────────────────────────────────────────── */
/** @brief Таймаут ожидания готовности SEMC контроллера, мс. */
#define SDRAM_SEMC_IDLE_TIMEOUT_MS 10U
/**
* @brief Размер кэш-линии Cortex-M7, байт.
*
* SCB_CleanDCache_by_Addr / SCB_InvalidateDCache_by_Addr требуют
* выравнивания адреса и размера по кэш-линии.
* BSP_SDRAM_TEST_BASE_ADDR = 0x80200000 — кратен 32 байтам. ✓
*/
#define SDRAM_CACHE_LINE_BYTES 32U
/** @brief Эталонный паттерн верификации (чередующиеся биты). */
#define SDRAM_VERIFY_PATTERN_A 0xA5A5A5A5UL
/** @brief Инверсия эталонного паттерна. */
#define SDRAM_VERIFY_PATTERN_B 0x5A5A5A5AUL
/* ── Константы SEMC (значения из DCD, см. tools/host/dcd/dcd.bin) */
/** @brief PFD2 FRAC из DCD (PFD_528=0x00230000): 528 МГц ×18/35 ≈ 271.54 МГц. */
#define SDRAM_SEMC_PFD2_FRAC 35U
/**
* @brief Индекс пина GPIO_EMC_39 (SEMC_DQS) в массивах IOMUXC.
*
* SW_MUX_CTL_PAD[]/SW_PAD_CTL_PAD[] начинаются с GPIO_EMC_00 (индекс 0), шаг 1.
* DQS — единственный пин, которому DCD ставит SION (вход strobe должен быть
* принудительно включён, чтобы SEMC читал собственный строб).
*/
#define SDRAM_EMC_PAD_FIRST 0U
#define SDRAM_EMC_PAD_LAST 41U /* GPIO_EMC_41 — последний EMC-пин */
#define SDRAM_DQS_PAD_INDEX 39U /* GPIO_EMC_39 = SEMC_DQS */
/** @brief SION (Software Input On), бит 4 SW_MUX_CTL_PAD — только для DQS. */
#define SDRAM_MUX_SION 0x00000010UL
/** @brief SW_PAD_CTL для всех EMC-пинов (DCD: 0x000110F9 на каждый). */
#define SDRAM_PAD_CTL_VALUE 0x000110F9UL
/** @brief Адрес SDRAM для IP-команд SEMC (совпадает с BR0 = базой SDRAM). */
#define SDRAM_SEMC_IPCMD_ADDR 0x80000000UL
/**
* @brief Значение mode-register SDRAM (DCD IPTXDAT=0x33).
*
* M[2:0]=011 (burst length 8), M3=0 (sequential), M[6:4]=011 (CAS latency 3) —
* согласуется с SDRAMCR0 (BL8/CL3) и даташитом MT48LC16M16A2.
*/
#define SDRAM_SEMC_MODE_REG 0x00000033UL
/*
* AXI-QoS регистры арбитража доступа к SDRAM — NIC-301 GPV (Global
* Programmer's View). Не влияют на корректность самой SDRAM — только на арбитраж при
* конкуренции за шину (LCD/CPU vs SDRAM). */
#define SDRAM_QOS_LCD_READ (*(volatile uint32_t *) 0x41044100UL)
#define SDRAM_QOS_LCD_WRITE (*(volatile uint32_t *) 0x41044104UL)
#define SDRAM_QOS_M7_READ (*(volatile uint32_t *) 0x41442100UL)
#define SDRAM_QOS_M7_WRITE (*(volatile uint32_t *) 0x41442104UL)
/* ── Состояние модуля ──────────────────────────────────────────────────── */
static bool g_s_initialised = false;
/**
* @brief Поднять тактовую цепочку SEMC (порт секции «Clock Init» DCD).
*
*/
static void sdram_configure_clock(void)
{
/* DCD PLL_SYS=0x00002001 → Fout = 24 МГц × (20 + 2×loopDivider + num/denom)
* = 24 × (20 + 2 + 0) = 528 МГц. */
static const clock_sys_pll_config_t SYS_PLL = {
.loopDivider = 1, .numerator = 0, .denominator = 1, .src = 0, /* 24 МГц OSC */
};
/* Гейтим SEMC на время переключения его источника (идиома clock_config.c). */
CLOCK_DisableClock(kCLOCK_Semc);
CLOCK_InitSysPll(&SYS_PLL);
CLOCK_InitSysPfd(kCLOCK_Pfd2, SDRAM_SEMC_PFD2_FRAC);
/* DCD CBCDR=0x00010D40 → SEMC_CLK_SEL=1 (alt), SEMC_ALT_CLK_SEL=0 (→PFD2),
* SEMC_PODF=1 (÷2). */
CLOCK_SetMux(kCLOCK_SemcAltMux, 0); /* alt = PLL2 PFD2 */
CLOCK_SetMux(kCLOCK_SemcMux, 1); /* SEMC clock = alt (PFD2), не periph_clk */
CLOCK_SetDiv(kCLOCK_SemcDiv, kCLOCK_SemcDivBy2);
CLOCK_EnableClock(kCLOCK_Semc);
}
/* ── SEMC: пины ─────────────────────────────────────────────────── */
/**
* @brief Замуксить пины GPIO_EMC на функцию SEMC + PAD-настройки (порт DCD).
*
*/
static void sdram_configure_pins(void)
{
for (uint32_t i = SDRAM_EMC_PAD_FIRST; i <= SDRAM_EMC_PAD_LAST; i++)
{
IOMUXC->SW_MUX_CTL_PAD[i] = 0UL; /* ALT0 = функция SEMC */
IOMUXC->SW_PAD_CTL_PAD[i] = SDRAM_PAD_CTL_VALUE;
}
/* DQS: ALT0 + SION (DCD пишет сюда 0x10 вместо 0x00). */
IOMUXC->SW_MUX_CTL_PAD[SDRAM_DQS_PAD_INDEX] = SDRAM_MUX_SION;
}
/* ── SEMC: регистры контроллера ─────────────────────────────────── */
/**
* @brief Записать регистры контроллера SEMC (дословно «блок 2» DCD).
*
*/
static void sdram_configure_controller(void)
{
SEMC->MCR = 0x10000004UL; /* модуль вкл (MDIS=0), DQSMD=1 (DQS с пина), BTO=16 */
SEMC->BMCR0 = 0x00000081UL; /* веса AXI-очереди A */
SEMC->BMCR1 = 0x00000081UL; /* веса AXI-очереди B */
/* Базовые регистры регионов SEMC. BR0 — сама SDRAM (0x80000000, 32 МБ,
* VLD=1). BR1..BR8 — прочие регионы из проверенного DCD, переносятся как
* есть (bootloader их не использует, но конфиг источника истины не режем). */
SEMC->BR[0] = 0x8000001BUL;
SEMC->BR[1] = 0x8200001BUL;
SEMC->BR[2] = 0x8400001BUL;
SEMC->BR[3] = 0x8600001BUL;
SEMC->BR[4] = 0x90000021UL;
SEMC->BR[5] = 0xA0000019UL;
SEMC->BR[6] = 0xA8000017UL;
SEMC->BR[7] = 0xA900001BUL;
SEMC->BR[8] = 0x00000021UL;
SEMC->IOCR = 0x000079A8UL; /* внутренний pinmux SEMC */
/* Геометрия и тайминги SDRAM. SDRAMCR0: PS=16бит, BL=8, COL=9бит, CL=3 —
* MT48LC16M16A2 (даташит: 512 колонок = 9 адресных бит). */
SEMC->SDRAMCR0 = 0x00000F31UL;
SEMC->SDRAMCR1 = 0x00652922UL;
SEMC->SDRAMCR2 = 0x00020201UL;
SEMC->SDRAMCR3 = 0x08193D0FUL; /* тайминги refresh; REN включим в конце */
/* DBICR0/DBICR1 — DCD их пишет, хотя DBI-устройства на плате нет и
* SEMC_ConfigureSDRAM() их не трогает. Инертны (регион DBI не включён),
* но переносятся дословно ради полного соответствия проверенному DCD. */
SEMC->DBICR0 = 0x00000021UL;
SEMC->DBICR1 = 0x00888888UL;
/* Параметры IP-команд: DATSZ=2 байта (запись mode-register 16-бит шиной). */
SEMC->IPCR1 = 0x00000002UL;
SEMC->IPCR2 = 0x00000000UL;
}
/* ── SEMC: командная последовательность инициализации SDRAM ──────── */
/**
* @brief Прогнать init-последовательность SDRAM через IP-команды SEMC.
*
* precharge-all → 2×auto-refresh → mode-set → включение авто-refresh.
*
* @retval BSP_OK все команды завершились успешно.
* @retval BSP_ERR_INIT IP-команда SEMC вернула ошибку.
*/
static bsp_status_t sdram_issue_init_sequence(void)
{
const uint32_t ADDR = SDRAM_SEMC_IPCMD_ADDR;
if (SEMC_SendIPCommand(SEMC, kSEMC_MemType_SDRAM, ADDR, (uint32_t) kSEMC_SDRAMCM_Prechargeall,
0, NULL) != kStatus_Success)
{
return BSP_ERR_INIT;
}
for (uint32_t i = 0U; i < 2U; i++)
{
if (SEMC_SendIPCommand(SEMC, kSEMC_MemType_SDRAM, ADDR,
(uint32_t) kSEMC_SDRAMCM_AutoRefresh, 0, NULL) != kStatus_Success)
{
return BSP_ERR_INIT;
}
}
if (SEMC_SendIPCommand(SEMC, kSEMC_MemType_SDRAM, ADDR, (uint32_t) kSEMC_SDRAMCM_Modeset,
SDRAM_SEMC_MODE_REG, NULL) != kStatus_Success)
{
return BSP_ERR_INIT;
}
/* Включить авто-refresh + перейти на рабочие параметры refresh. DCD пишет
* SDRAMCR3 повторно другим значением (не только бит REN) — переносим как
* есть; это финальный «operational» refresh-конфиг после инициализации. */
SEMC->SDRAMCR3 = 0x50210A09UL;
return BSP_OK;
}
/* ── SEMC: AXI-QoS арбитраж ─ */
/**
* @brief Настроить приоритеты доступа мастеров к SDRAM
*
* Требуют board_mpu_init() Region 11
*/
static void sdram_configure_axi_qos(void)
{
SDRAM_QOS_LCD_READ = 6UL;
SDRAM_QOS_LCD_WRITE = 6UL;
SDRAM_QOS_M7_READ = 7UL;
SDRAM_QOS_M7_WRITE = 7UL;
}
/* ── Внутренние функции верификации ────────────────────────────────────── */
/**
* @brief Дождаться перехода SEMC в состояние IDLE.
*
* @return BSP_OK при успехе, BSP_ERR_TIMEOUT если SEMC не ответил.
*/
static bsp_status_t wait_semc_idle(void)
{
const uint32_t START_MS = bsp_tick_get_ms();
while ((SEMC->STS0 & SEMC_STS0_IDLE_MASK) == 0U)
{
if ((bsp_tick_get_ms() - START_MS) >= SDRAM_SEMC_IDLE_TIMEOUT_MS)
{
return BSP_ERR_TIMEOUT;
}
}
return BSP_OK;
}
/**
* @brief Сбросить кэш-линию по адресу тестового региона.
*
* Clean (запись dirty линии в SDRAM) + Invalidate (следующее чтение
* пойдёт в SDRAM, не в кэш) + DSB (барьер завершения операции).
*/
static void flush_cache_at_test_base(void)
{
uint32_t *const P_ADDR = (uint32_t *) BSP_SDRAM_TEST_BASE_ADDR;
SCB_CleanDCache_by_Addr(P_ADDR, (int32_t) SDRAM_CACHE_LINE_BYTES);
SCB_InvalidateDCache_by_Addr(P_ADDR, (int32_t) SDRAM_CACHE_LINE_BYTES);
__DSB();
}
/**
* @brief Записать слово в тестовый адрес, сбросить кэш, прочитать обратно.
*
* @param[in] pattern Значение для записи и верификации.
* @return BSP_OK если readback совпал, BSP_ERR_INIT при расхождении.
*/
static bsp_status_t verify_word(uint32_t pattern)
{
volatile uint32_t *const P_TEST = (volatile uint32_t *) BSP_SDRAM_TEST_BASE_ADDR;
*P_TEST = pattern;
flush_cache_at_test_base();
return (*P_TEST == pattern) ? BSP_OK : BSP_ERR_INIT;
}
/* ── Public API ────────────────────────────────────────────────────────── */
bsp_status_t bsp_sdram_configure(void)
{
sdram_configure_clock();
sdram_configure_pins();
sdram_configure_controller();
bsp_status_t status = sdram_issue_init_sequence();
if (status != BSP_OK)
{
return status;
}
#if defined(__NIC301_EXPERIMENTS_)
sdram_configure_axi_qos();
#endif
return BSP_OK;
}
bsp_status_t bsp_sdram_init(void)
{
bsp_status_t status = wait_semc_idle();
if (status != BSP_OK)
{
return status;
}
status = verify_word(SDRAM_VERIFY_PATTERN_A);
if (status != BSP_OK)
{
return BSP_ERR_INIT;
}
status = verify_word(SDRAM_VERIFY_PATTERN_B);
if (status != BSP_OK)
{
return BSP_ERR_INIT;
}
g_s_initialised = true;
return BSP_OK;
}