wolfBoot/hal/mpfs250.c

542 lines
16 KiB
C

/* mpfs250.c
*
* Copyright (C) 2025 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
/* Microchip PolarFire SoC MPFS250T HAL for wolfBoot */
/* Supports:
* RISC-V 64-bit architecture
* External flash operations
* UART communication
* System initialization
*/
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include "target.h"
#include "mpfs250.h"
#include "riscv.h"
#include "image.h"
#ifndef ARCH_RISCV64
# error "wolfBoot mpfs250 HAL: wrong architecture selected. Please compile with ARCH=RISCV64."
#endif
#include "printf.h"
#include "loader.h"
#include "hal.h"
#include "gpt.h"
#include "fdt.h"
#if defined(DISK_SDCARD) || defined(DISK_EMMC)
#include "sdhci.h"
#endif
void hal_init(void)
{
#if defined(DEBUG_UART) && defined(__WOLFBOOT)
#ifdef WOLFBOOT_REPRODUCIBLE_BUILD
wolfBoot_printf("wolfBoot Version: %s\n", LIBWOLFBOOT_VERSION_STRING);
#else
wolfBoot_printf("wolfBoot Version: %s (%s %s)\n",
LIBWOLFBOOT_VERSION_STRING,__DATE__, __TIME__);
#endif
#endif
}
/* ============================================================================
* System Controller Mailbox Functions
*
* The MPFS system controller provides various system services via a mailbox
* interface. Commands are sent by writing the opcode to the control register
* and responses are read from the mailbox RAM.
* ============================================================================ */
/**
* mpfs_scb_mailbox_busy - Check if the system controller mailbox is busy
*
* Returns: non-zero if busy, 0 if ready
*/
static int mpfs_scb_mailbox_busy(void)
{
return (SCBCTRL_REG(SERVICES_SR_OFFSET) & SERVICES_SR_BUSY_MASK);
}
/**
* mpfs_read_serial_number - Read the device serial number via system services
* @serial: Buffer to store the 16-byte device serial number
*
* This function sends a serial number request (opcode 0x00) to the system
* controller and reads the 16-byte response from the mailbox RAM.
*
* Returns: 0 on success, negative error code on failure
*/
static int mpfs_read_serial_number(uint8_t *serial)
{
uint32_t cmd, status;
int i, timeout;
if (serial == NULL) {
return -1;
}
/* Check if mailbox is busy */
if (mpfs_scb_mailbox_busy()) {
wolfBoot_printf("SCB mailbox busy\n");
return -2;
}
/* Send serial number request command (opcode 0x00)
* Command format: [31:16] = opcode, [0] = request bit */
cmd = (SYS_SERV_CMD_SERIAL_NUMBER << SERVICES_CR_COMMAND_SHIFT) |
SERVICES_CR_REQ_MASK;
SCBCTRL_REG(SERVICES_CR_OFFSET) = cmd;
/* Wait for request bit to clear (command accepted) */
timeout = 10000;
while ((SCBCTRL_REG(SERVICES_CR_OFFSET) & SERVICES_CR_REQ_MASK) && timeout > 0) {
timeout--;
}
if (timeout == 0) {
wolfBoot_printf("SCB mailbox request timeout\n");
return -3;
}
/* Wait for busy bit to clear (command completed) */
timeout = 10000;
while (mpfs_scb_mailbox_busy() && timeout > 0) {
timeout--;
}
if (timeout == 0) {
wolfBoot_printf("SCB mailbox busy timeout\n");
return -4;
}
/* Check status (upper 16 bits of status register) */
status = (SCBCTRL_REG(SERVICES_SR_OFFSET) >> SERVICES_SR_STATUS_SHIFT) & 0xFFFF;
if (status != 0) {
wolfBoot_printf("SCB mailbox error: 0x%x\n", status);
return -5;
}
/* Read serial number from mailbox RAM (16 bytes) */
for (i = 0; i < DEVICE_SERIAL_NUMBER_SIZE; i++) {
serial[i] = SCBMBOX_BYTE(i);
}
return 0;
}
/* Linux kernel command line arguments */
#ifndef LINUX_BOOTARGS
#ifndef LINUX_BOOTARGS_ROOT
#define LINUX_BOOTARGS_ROOT "/dev/mmcblk0p4"
#endif
#define LINUX_BOOTARGS \
"earlycon root="LINUX_BOOTARGS_ROOT" rootwait uio_pdrv_genirq.of_id=generic-uio"
#endif
/* Microchip OUI (Organizationally Unique Identifier) for MAC address */
#define MICROCHIP_OUI_0 0x00
#define MICROCHIP_OUI_1 0x04
#define MICROCHIP_OUI_2 0xA3
int hal_dts_fixup(void* dts_addr)
{
int off, ret;
struct fdt_header *fdt = (struct fdt_header *)dts_addr;
uint8_t device_serial_number[DEVICE_SERIAL_NUMBER_SIZE];
uint8_t mac_addr[6];
/* Verify FDT header */
ret = fdt_check_header(dts_addr);
if (ret != 0) {
wolfBoot_printf("FDT: Invalid header! %d\n", ret);
return ret;
}
wolfBoot_printf("FDT: Version %d, Size %d\n",
fdt_version(fdt), fdt_totalsize(fdt));
/* Expand total size to allow adding/modifying properties */
fdt_set_totalsize(fdt, fdt_totalsize(fdt) + 512);
/* Find /chosen node */
off = fdt_find_node_offset(fdt, -1, "chosen");
if (off < 0) {
/* Create /chosen node if it doesn't exist */
off = fdt_add_subnode(fdt, 0, "chosen");
}
if (off >= 0) {
/* Set bootargs property */
fdt_fixup_str(fdt, off, "chosen", "bootargs", LINUX_BOOTARGS);
}
/* Read device serial number from system controller */
ret = mpfs_read_serial_number(device_serial_number);
if (ret != 0) {
wolfBoot_printf("FDT: Failed to read serial number (%d)\n", ret);
/* Continue without setting MAC addresses */
return 0;
}
wolfBoot_printf("FDT: Device serial: %02x%02x%02x%02x-%02x%02x%02x%02x-"
"%02x%02x%02x%02x-%02x%02x%02x%02x\n",
device_serial_number[15], device_serial_number[14],
device_serial_number[13], device_serial_number[12],
device_serial_number[11], device_serial_number[10],
device_serial_number[9], device_serial_number[8],
device_serial_number[7], device_serial_number[6],
device_serial_number[5], device_serial_number[4],
device_serial_number[3], device_serial_number[2],
device_serial_number[1], device_serial_number[0]);
/* Build MAC address: Microchip OUI + lower 3 bytes of serial number
* Format: {0x00, 0x04, 0xA3, serial[2], serial[1], serial[0]} */
mac_addr[0] = MICROCHIP_OUI_0;
mac_addr[1] = MICROCHIP_OUI_1;
mac_addr[2] = MICROCHIP_OUI_2;
mac_addr[3] = device_serial_number[2];
mac_addr[4] = device_serial_number[1];
mac_addr[5] = device_serial_number[0];
wolfBoot_printf("FDT: MAC0 = %02x:%02x:%02x:%02x:%02x:%02x\n",
mac_addr[0], mac_addr[1], mac_addr[2],
mac_addr[3], mac_addr[4], mac_addr[5]);
/* Set local-mac-address for ethernet@20110000 (mac0) */
off = fdt_find_node_offset(fdt, -1, "ethernet@20110000");
if (off >= 0) {
ret = fdt_setprop(fdt, off, "local-mac-address", mac_addr, 6);
if (ret != 0) {
wolfBoot_printf("FDT: Failed to set mac0 address (%d)\n", ret);
}
}
else {
wolfBoot_printf("FDT: ethernet@20110000 not found\n");
}
/* Set local-mac-address for ethernet@20112000 (mac1)
* Use MAC address + 1 for the second interface */
mac_addr[5] = device_serial_number[0] + 1;
wolfBoot_printf("FDT: MAC1 = %02x:%02x:%02x:%02x:%02x:%02x\n",
mac_addr[0], mac_addr[1], mac_addr[2],
mac_addr[3], mac_addr[4], mac_addr[5]);
off = fdt_find_node_offset(fdt, -1, "ethernet@20112000");
if (off >= 0) {
ret = fdt_setprop(fdt, off, "local-mac-address", mac_addr, 6);
if (ret != 0) {
wolfBoot_printf("FDT: Failed to set mac1 address (%d)\n", ret);
}
}
else {
wolfBoot_printf("FDT: ethernet@20112000 not found\n");
}
return 0;
}
void hal_prepare_boot(void)
{
/* reset the eMMC/SD card? */
}
void RAMFUNCTION hal_flash_unlock(void)
{
}
void RAMFUNCTION hal_flash_lock(void)
{
}
int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
{
(void)address;
(void)data;
(void)len;
return 0;
}
int RAMFUNCTION hal_flash_erase(uint32_t address, int len)
{
(void)address;
(void)len;
return 0;
}
#ifdef EXT_FLASH
/* External flash support */
void ext_flash_lock(void)
{
/* TODO: Lock external flash */
}
void ext_flash_unlock(void)
{
/* TODO: Unlock external flash */
}
int ext_flash_write(uintptr_t address, const uint8_t *data, int len)
{
/* TODO: Write to external flash */
(void)address;
(void)data;
(void)len;
return 0;
}
int ext_flash_read(uintptr_t address, uint8_t *data, int len)
{
/* TODO: Read from external flash */
(void)address;
(void)data;
(void)len;
return 0;
}
int ext_flash_erase(uintptr_t address, int len)
{
/* TODO: Erase external flash sectors */
(void)address;
(void)len;
return 0;
}
#endif /* EXT_FLASH */
#if defined(MMU) && !defined(WOLFBOOT_NO_PARTITIONS)
void* hal_get_dts_address(void)
{
return (void*)WOLFBOOT_DTS_BOOT_ADDRESS;
}
#endif
#if defined(DISK_SDCARD) || defined(DISK_EMMC)
/* ============================================================================
* SDHCI Platform HAL Implementation
* ============================================================================ */
/* Register access functions for generic SDHCI driver */
uint32_t sdhci_reg_read(uint32_t offset)
{
return *((volatile uint32_t*)(EMMC_SD_BASE + offset));
}
void sdhci_reg_write(uint32_t offset, uint32_t val)
{
*((volatile uint32_t*)(EMMC_SD_BASE + offset)) = val;
}
#endif /* DISK_SDCARD || DISK_EMMC */
/* ============================================================================
* PLIC - Platform-Level Interrupt Controller (MPFS250-specific)
*
* Generic PLIC functions are in src/boot_riscv.c
* Platform must provide:
* - plic_get_context(): Map current hart to PLIC context
* - plic_dispatch_irq(): Dispatch IRQ to appropriate handler
* ============================================================================ */
/* Get the PLIC context for the current hart in S-mode */
extern unsigned long get_boot_hartid(void);
uint32_t plic_get_context(void)
{
uint32_t hart_id = get_boot_hartid();
/* Get S-mode context for a given hart (1-4 for U54 cores) */
return hart_id * 2;
}
/* Forward declaration of SDHCI IRQ handler */
#if defined(DISK_SDCARD) || defined(DISK_EMMC)
extern void sdhci_irq_handler(void);
#endif
/* Dispatch IRQ to appropriate platform handler */
void plic_dispatch_irq(uint32_t irq)
{
switch (irq) {
#if defined(DISK_SDCARD) || defined(DISK_EMMC)
case PLIC_INT_MMC_MAIN:
sdhci_irq_handler();
break;
#endif
default:
/* Unknown interrupt - ignore */
break;
}
}
#if defined(DISK_SDCARD) || defined(DISK_EMMC)
/* ============================================================================
* SDHCI Platform HAL Functions
* ============================================================================ */
/* Platform initialization - called from sdhci_init() */
void sdhci_platform_init(void)
{
/* Release MMC controller from reset */
SYSREG_SOFT_RESET_CR &= ~SYSREG_SOFT_RESET_CR_MMC;
}
/* Platform interrupt setup - called from sdhci_init() */
void sdhci_platform_irq_init(void)
{
/* Set priority for MMC main interrupt */
plic_set_priority(PLIC_INT_MMC_MAIN, PLIC_PRIORITY_DEFAULT);
/* Set threshold to 0 (allow all priorities > 0) */
plic_set_threshold(0);
/* Enable MMC interrupt for this hart */
plic_enable_interrupt(PLIC_INT_MMC_MAIN);
#ifdef DEBUG_SDHCI
wolfBoot_printf("sdhci_platform_irq_init: hart %d, context %d, irq %d enabled\n",
get_boot_hartid(), plic_get_context(), PLIC_INT_MMC_MAIN);
#endif
}
/* Platform bus mode selection - called from sdhci_init() */
void sdhci_platform_set_bus_mode(int is_emmc)
{
(void)is_emmc;
/* Nothing additional needed for MPFS - mode is set in generic driver */
}
#endif /* DISK_SDCARD || DISK_EMMC */
/* ============================================================================
* DEBUG UART Functions
* ============================================================================ */
#ifdef DEBUG_UART
#ifndef DEBUG_UART_BASE
#define DEBUG_UART_BASE MSS_UART1_LO_BASE
#endif
/* Configure baud divisors with fractional baud rate support.
*
* UART baud rate divisor formula: divisor = PCLK / (baudrate * 16)
*
* To support fractional divisors (6-bit, 0-63), we scale up the calculation:
* divisor_x128 = (PCLK * 8) / baudrate (128x scaled for rounding precision)
* divisor_x64 = divisor_x128 / 2 (64x scaled for 6-bit fractional)
* integer_div = divisor_x64 / 64 (integer portion of divisor)
* frac_div = divisor_x64 % 64 (fractional portion, 0-63)
*
* The fractional part is then adjusted using the x128 value for rounding.
*/
static void uart_config_clk(uint32_t baudrate)
{
const uint64_t pclk = MSS_APB_AHB_CLK;
/* Scale up for precision: (PCLK * 128) / (baudrate * 16) */
uint32_t div_x128 = (uint32_t)((8UL * pclk) / baudrate);
uint32_t div_x64 = div_x128 / 2u;
/* Extract integer and fractional parts */
uint32_t div_int = div_x64 / 64u;
uint32_t div_frac = div_x64 - (div_int * 64u);
/* Apply rounding correction from x128 calculation */
div_frac += (div_x128 - (div_int * 128u)) - (div_frac * 2u);
if (div_int > (uint32_t)UINT16_MAX)
return;
/* Write 16-bit divisor: set DLAB, write high/low bytes, clear DLAB */
MMUART_LCR(DEBUG_UART_BASE) |= DLAB_MASK;
MMUART_DMR(DEBUG_UART_BASE) = (uint8_t)(div_int >> 8);
MMUART_DLR(DEBUG_UART_BASE) = (uint8_t)div_int;
MMUART_LCR(DEBUG_UART_BASE) &= ~DLAB_MASK;
/* Enable fractional divisor if integer divisor > 1 */
if (div_int > 1u) {
MMUART_MM0(DEBUG_UART_BASE) |= EFBR_MASK;
MMUART_DFR(DEBUG_UART_BASE) = (uint8_t)div_frac;
}
else {
MMUART_MM0(DEBUG_UART_BASE) &= ~EFBR_MASK;
}
}
void uart_init(void)
{
/* Disable special modes: LIN, IrDA, SmartCard */
MMUART_MM0(DEBUG_UART_BASE) &= ~ELIN_MASK;
MMUART_MM1(DEBUG_UART_BASE) &= ~EIRD_MASK;
MMUART_MM2(DEBUG_UART_BASE) &= ~EERR_MASK;
/* Disable interrupts */
MMUART_IER(DEBUG_UART_BASE) = 0u;
/* Reset and configure FIFOs, enable RXRDYN/TXRDYN pins */
MMUART_FCR(DEBUG_UART_BASE) = 0u;
MMUART_FCR(DEBUG_UART_BASE) |= CLEAR_RX_FIFO_MASK | CLEAR_TX_FIFO_MASK;
MMUART_FCR(DEBUG_UART_BASE) |= RXRDY_TXRDYN_EN_MASK;
/* Disable loopback (local and remote) */
MMUART_MCR(DEBUG_UART_BASE) &= ~(LOOP_MASK | RLOOP_MASK);
/* Set LSB-first for TX/RX */
MMUART_MM1(DEBUG_UART_BASE) &= ~(E_MSB_TX_MASK | E_MSB_RX_MASK);
/* Disable AFM, single wire mode */
MMUART_MM2(DEBUG_UART_BASE) &= ~(EAFM_MASK | ESWM_MASK);
/* Disable TX time guard, RX timeout, fractional baud */
MMUART_MM0(DEBUG_UART_BASE) &= ~(ETTG_MASK | ERTO_MASK | EFBR_MASK);
/* Clear timing registers */
MMUART_GFR(DEBUG_UART_BASE) = 0u;
MMUART_TTG(DEBUG_UART_BASE) = 0u;
MMUART_RTO(DEBUG_UART_BASE) = 0u;
/* Configure baud rate (115200) */
uart_config_clk(115200);
/* Set line config: 8N1 */
MMUART_LCR(DEBUG_UART_BASE) = MSS_UART_DATA_8_BITS |
MSS_UART_NO_PARITY |
MSS_UART_ONE_STOP_BIT;
}
void uart_write(const char* buf, unsigned int sz)
{
uint32_t pos = 0;
while (sz-- > 0) {
char c = buf[pos++];
if (c == '\n') { /* handle CRLF */
while ((MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_THRE) == 0);
MMUART_THR(DEBUG_UART_BASE) = '\r';
}
while ((MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_THRE) == 0);
MMUART_THR(DEBUG_UART_BASE) = c;
}
}
#endif /* DEBUG_UART */