mirror of
https://github.com/FreeRTOS/FreeRTOS-Kernel.git
synced 2025-09-01 20:03:50 -04:00
Rename the RISC-V_RV32_SiFive_Hifive1_GCC folder to RISC-V_RV32_SiFive_HiFive1_FreedomStudio as it is built with Freedom Studio.
This commit is contained in:
parent
5306ba245d
commit
18916d5820
172 changed files with 0 additions and 0 deletions
|
@ -0,0 +1,27 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/button.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
struct metal_button* metal_button_get (char *label)
|
||||
{
|
||||
int i;
|
||||
struct metal_button *button;
|
||||
|
||||
if ((__METAL_DT_MAX_BUTTONS == 0) || (label == NULL)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < __METAL_DT_MAX_BUTTONS; i++) {
|
||||
button = (struct metal_button*)__metal_button_table[i];
|
||||
if (button->vtable->button_exist(button, label)) {
|
||||
return button;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern inline struct metal_interrupt*
|
||||
metal_button_interrupt_controller(struct metal_button *button);
|
||||
extern inline int metal_button_get_interrupt_id(struct metal_button *button);
|
|
@ -0,0 +1,8 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/cache.h>
|
||||
|
||||
extern inline void metal_cache_init(struct metal_cache *cache, int ways);
|
||||
extern inline int metal_cache_get_enabled_ways(struct metal_cache *cache);
|
||||
extern inline int metal_cache_set_enabled_ways(struct metal_cache *cache, int ways);
|
|
@ -0,0 +1,9 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/clock.h>
|
||||
|
||||
extern inline long metal_clock_get_rate_hz(const struct metal_clock *clk);
|
||||
extern inline long metal_clock_set_rate_hz(struct metal_clock *clk, long hz);
|
||||
extern inline void metal_clock_register_post_rate_change_callback(struct metal_clock *clk, metal_clock_post_rate_change_callback cb, void *priv);
|
||||
extern inline void metal_clock_register_pre_rate_change_callback(struct metal_clock *clk, metal_clock_pre_rate_change_callback cb, void *priv);
|
|
@ -0,0 +1,59 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/cpu.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
struct metal_cpu* metal_cpu_get(int hartid)
|
||||
{
|
||||
if (hartid < __METAL_DT_MAX_HARTS) {
|
||||
return (struct metal_cpu *)__metal_cpu_table[hartid];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int metal_cpu_get_current_hartid()
|
||||
{
|
||||
#ifdef __riscv
|
||||
int mhartid;
|
||||
asm volatile("csrr %0, mhartid" : "=r" (mhartid));
|
||||
return mhartid;
|
||||
#endif
|
||||
}
|
||||
|
||||
int metal_cpu_get_num_harts()
|
||||
{
|
||||
return __METAL_DT_MAX_HARTS;
|
||||
}
|
||||
|
||||
extern inline unsigned long long metal_cpu_get_timer(struct metal_cpu *cpu);
|
||||
|
||||
extern inline unsigned long long metal_cpu_get_timebase(struct metal_cpu *cpu);
|
||||
|
||||
extern inline unsigned long long metal_cpu_get_mtime(struct metal_cpu *cpu);
|
||||
|
||||
extern inline int metal_cpu_set_mtimecmp(struct metal_cpu *cpu, unsigned long long time);
|
||||
|
||||
extern inline struct metal_interrupt* metal_cpu_timer_interrupt_controller(struct metal_cpu *cpu);
|
||||
|
||||
extern inline int metal_cpu_timer_get_interrupt_id(struct metal_cpu *cpu);
|
||||
|
||||
extern inline struct metal_interrupt* metal_cpu_software_interrupt_controller(struct metal_cpu *cpu);
|
||||
|
||||
extern inline int metal_cpu_software_get_interrupt_id(struct metal_cpu *cpu);
|
||||
|
||||
extern inline int metal_cpu_software_set_ipi(struct metal_cpu *cpu, int hartid);
|
||||
|
||||
extern inline int metal_cpu_software_clear_ipi(struct metal_cpu *cpu, int hartid);
|
||||
|
||||
extern inline int metal_cpu_get_msip(struct metal_cpu *cpu, int hartid);
|
||||
|
||||
extern inline struct metal_interrupt* metal_cpu_interrupt_controller(struct metal_cpu *cpu);
|
||||
|
||||
extern inline int metal_cpu_exception_register(struct metal_cpu *cpu, int ecode, metal_exception_handler_t handler);
|
||||
|
||||
extern inline int metal_cpu_get_instruction_length(struct metal_cpu *cpu, uintptr_t epc);
|
||||
|
||||
extern inline uintptr_t metal_cpu_get_exception_pc(struct metal_cpu *cpu);
|
||||
|
||||
extern inline int metal_cpu_set_exception_pc(struct metal_cpu *cpu, uintptr_t epc);
|
|
@ -0,0 +1,27 @@
|
|||
/* Copyright 2018 SiFive, Inc. */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_FIXED_CLOCK
|
||||
|
||||
#include <metal/drivers/fixed-clock.h>
|
||||
#include <stddef.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
long __metal_driver_fixed_clock_get_rate_hz(const struct metal_clock *gclk)
|
||||
{
|
||||
return __metal_driver_fixed_clock_rate(gclk);
|
||||
}
|
||||
|
||||
long __metal_driver_fixed_clock_set_rate_hz(struct metal_clock *gclk, long target_hz)
|
||||
{
|
||||
return __metal_driver_fixed_clock_get_rate_hz(gclk);
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_fixed_clock) = {
|
||||
.clock.get_rate_hz = __metal_driver_fixed_clock_get_rate_hz,
|
||||
.clock.set_rate_hz = __metal_driver_fixed_clock_set_rate_hz,
|
||||
};
|
||||
|
||||
#endif /* METAL_FIXED_CLOCK */
|
|
@ -0,0 +1,32 @@
|
|||
/* Copyright 2018 SiFive, Inc. */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_FIXED_FACTOR_CLOCK
|
||||
|
||||
#include <metal/drivers/fixed-factor-clock.h>
|
||||
#include <stddef.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
long __metal_driver_fixed_factor_clock_get_rate_hz(const struct metal_clock *gclk)
|
||||
{
|
||||
struct metal_clock *parent = __metal_driver_fixed_factor_clock_parent(gclk);
|
||||
long parent_rate = 1;
|
||||
if(parent) {
|
||||
parent_rate = parent->vtable->get_rate_hz(parent);
|
||||
}
|
||||
|
||||
return __metal_driver_fixed_factor_clock_mult(gclk) * parent_rate / __metal_driver_fixed_factor_clock_div(gclk);
|
||||
}
|
||||
|
||||
long __metal_driver_fixed_factor_clock_set_rate_hz(struct metal_clock *gclk, long target_hz)
|
||||
{
|
||||
return __metal_driver_fixed_factor_clock_get_rate_hz(gclk);
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_fixed_factor_clock) = {
|
||||
.clock.get_rate_hz = __metal_driver_fixed_factor_clock_get_rate_hz,
|
||||
.clock.set_rate_hz = __metal_driver_fixed_factor_clock_set_rate_hz,
|
||||
};
|
||||
#endif /* METAL_FIXED_FACTOR_CLOCK */
|
|
@ -0,0 +1,5 @@
|
|||
/* Copyright 2019 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/inline.h>
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_RISCV_CLINT0
|
||||
|
||||
#include <metal/io.h>
|
||||
#include <metal/cpu.h>
|
||||
#include <metal/drivers/riscv_clint0.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
unsigned long long __metal_clint0_mtime_get (struct __metal_driver_riscv_clint0 *clint)
|
||||
{
|
||||
__metal_io_u32 lo, hi;
|
||||
unsigned long control_base = __metal_driver_sifive_clint0_control_base(&clint->controller);
|
||||
|
||||
/* Guard against rollover when reading */
|
||||
do {
|
||||
hi = __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + METAL_RISCV_CLINT0_MTIME + 4));
|
||||
lo = __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + METAL_RISCV_CLINT0_MTIME));
|
||||
} while (__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + METAL_RISCV_CLINT0_MTIME + 4)) != hi);
|
||||
|
||||
return (((unsigned long long)hi) << 32) | lo;
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_clint0_mtimecmp_set(struct metal_interrupt *controller,
|
||||
int hartid,
|
||||
unsigned long long time)
|
||||
{
|
||||
struct __metal_driver_riscv_clint0 *clint =
|
||||
(struct __metal_driver_riscv_clint0 *)(controller);
|
||||
unsigned long control_base = __metal_driver_sifive_clint0_control_base(&clint->controller);
|
||||
/* Per spec, the RISC-V MTIME/MTIMECMP registers are 64 bit,
|
||||
* and are NOT internally latched for multiword transfers.
|
||||
* Need to be careful about sequencing to avoid triggering
|
||||
* spurious interrupts: For that set the high word to a max
|
||||
* value first.
|
||||
*/
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + (8 * hartid) + METAL_RISCV_CLINT0_MTIMECMP_BASE + 4)) = 0xFFFFFFFF;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + (8 * hartid) + METAL_RISCV_CLINT0_MTIMECMP_BASE)) = (__metal_io_u32)time;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + (8 * hartid) + METAL_RISCV_CLINT0_MTIMECMP_BASE + 4)) = (__metal_io_u32)(time >> 32);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct metal_interrupt *_get_cpu_intc()
|
||||
{
|
||||
int hartid = 0;
|
||||
__asm__ volatile("csrr %[hartid], mhartid"
|
||||
: [hartid] "=r" (hartid) :: "memory");
|
||||
|
||||
struct metal_cpu *cpu = metal_cpu_get(hartid);
|
||||
|
||||
return metal_cpu_interrupt_controller(cpu);
|
||||
}
|
||||
|
||||
void __metal_driver_riscv_clint0_init (struct metal_interrupt *controller)
|
||||
{
|
||||
int num_interrupts = __metal_driver_sifive_clint0_num_interrupts(controller);
|
||||
struct __metal_driver_riscv_clint0 *clint =
|
||||
(struct __metal_driver_riscv_clint0 *)(controller);
|
||||
|
||||
if ( !clint->init_done ) {
|
||||
/* Register its interrupts with with parent controller, aka sw and timerto its default isr */
|
||||
for (int i = 0; i < num_interrupts; i++) {
|
||||
struct metal_interrupt *intc = __metal_driver_sifive_clint0_interrupt_parents(controller, i);
|
||||
int line = __metal_driver_sifive_clint0_interrupt_lines(controller, i);
|
||||
intc->vtable->interrupt_register(intc, line, NULL, controller);
|
||||
}
|
||||
clint->init_done = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_clint0_register (struct metal_interrupt *controller,
|
||||
int id, metal_interrupt_handler_t isr,
|
||||
void *priv)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
struct metal_interrupt *intc = NULL;
|
||||
struct metal_interrupt *cpu_intc = _get_cpu_intc();
|
||||
int num_interrupts = __metal_driver_sifive_clint0_num_interrupts(controller);
|
||||
|
||||
for(int i = 0; i < num_interrupts; i++) {
|
||||
int line = __metal_driver_sifive_clint0_interrupt_lines(controller, i);
|
||||
intc = __metal_driver_sifive_clint0_interrupt_parents(controller, i);
|
||||
if (cpu_intc == intc && id == line) {
|
||||
break;
|
||||
}
|
||||
intc = NULL;
|
||||
}
|
||||
|
||||
/* Register its interrupts with parent controller */
|
||||
if (intc) {
|
||||
rc = intc->vtable->interrupt_register(intc, id, isr, priv);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_clint0_enable (struct metal_interrupt *controller, int id)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
if ( id ) {
|
||||
struct metal_interrupt *intc = NULL;
|
||||
struct metal_interrupt *cpu_intc = _get_cpu_intc();
|
||||
int num_interrupts = __metal_driver_sifive_clint0_num_interrupts(controller);
|
||||
|
||||
for(int i = 0; i < num_interrupts; i++) {
|
||||
int line = __metal_driver_sifive_clint0_interrupt_lines(controller, i);
|
||||
intc = __metal_driver_sifive_clint0_interrupt_parents(controller, i);
|
||||
if(cpu_intc == intc && id == line) {
|
||||
break;
|
||||
}
|
||||
intc = NULL;
|
||||
}
|
||||
|
||||
/* Enable its interrupts with parent controller */
|
||||
if (intc) {
|
||||
rc = intc->vtable->interrupt_enable(intc, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_clint0_disable (struct metal_interrupt *controller, int id)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
if ( id ) {
|
||||
struct metal_interrupt *intc = NULL;
|
||||
struct metal_interrupt *cpu_intc = _get_cpu_intc();
|
||||
int num_interrupts = __metal_driver_sifive_clint0_num_interrupts(controller);
|
||||
|
||||
for(int i = 0; i < num_interrupts; i++) {
|
||||
int line = __metal_driver_sifive_clint0_interrupt_lines(controller, i);
|
||||
intc = __metal_driver_sifive_clint0_interrupt_parents(controller, i);
|
||||
if(cpu_intc == intc && id == line) {
|
||||
break;
|
||||
}
|
||||
intc = NULL;
|
||||
}
|
||||
|
||||
/* Disable its interrupts with parent controller */
|
||||
if (intc) {
|
||||
rc = intc->vtable->interrupt_disable(intc, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_clint0_command_request (struct metal_interrupt *controller,
|
||||
int command, void *data)
|
||||
{
|
||||
int hartid;
|
||||
int rc = -1;
|
||||
struct __metal_driver_riscv_clint0 *clint =
|
||||
(struct __metal_driver_riscv_clint0 *)(controller);
|
||||
unsigned long control_base = __metal_driver_sifive_clint0_control_base(controller);
|
||||
|
||||
switch (command) {
|
||||
case METAL_TIMER_MTIME_GET:
|
||||
if (data) {
|
||||
*(unsigned long long *)data = __metal_clint0_mtime_get(clint);
|
||||
rc = 0;
|
||||
}
|
||||
break;
|
||||
case METAL_SOFTWARE_IPI_CLEAR:
|
||||
if (data) {
|
||||
hartid = *(int *)data;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
(hartid * 4))) = METAL_DISABLE;
|
||||
rc = 0;
|
||||
}
|
||||
break;
|
||||
case METAL_SOFTWARE_IPI_SET:
|
||||
if (data) {
|
||||
hartid = *(int *)data;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
(hartid * 4))) = METAL_ENABLE;
|
||||
/* Callers of this function assume it's blocking, in the sense that
|
||||
* the IPI is guarnteed to have been delivered before the function
|
||||
* returns. We can't really guarnteed it's delivered, but we can
|
||||
* read back the control register after writing it in at least an
|
||||
* attempt to provide some semblence of ordering here. The fence
|
||||
* ensures the read is order after the write -- it wouldn't be
|
||||
* necessary under RVWMO because this is the same address, but we
|
||||
* don't have an IO memory model so I'm being a bit overkill here.
|
||||
*/
|
||||
__METAL_IO_FENCE(o,i);
|
||||
rc = __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
(hartid * 4)));
|
||||
rc = 0;
|
||||
}
|
||||
break;
|
||||
case METAL_SOFTWARE_MSIP_GET:
|
||||
rc = 0;
|
||||
if (data) {
|
||||
hartid = *(int *)data;
|
||||
rc = __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
(hartid * 4)));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_riscv_clint0) = {
|
||||
.clint_vtable.interrupt_init = __metal_driver_riscv_clint0_init,
|
||||
.clint_vtable.interrupt_register = __metal_driver_riscv_clint0_register,
|
||||
.clint_vtable.interrupt_enable = __metal_driver_riscv_clint0_enable,
|
||||
.clint_vtable.interrupt_disable = __metal_driver_riscv_clint0_disable,
|
||||
.clint_vtable.command_request = __metal_driver_riscv_clint0_command_request,
|
||||
.clint_vtable.mtimecmp_set = __metal_driver_riscv_clint0_mtimecmp_set,
|
||||
};
|
||||
|
||||
#endif /* METAL_RISCV_CLINT0 */
|
|
@ -0,0 +1,690 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <metal/io.h>
|
||||
#include <metal/shutdown.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
unsigned long long __metal_driver_cpu_mtime_get(struct metal_cpu *cpu);
|
||||
int __metal_driver_cpu_mtimecmp_set(struct metal_cpu *cpu, unsigned long long time);
|
||||
|
||||
struct metal_cpu *__metal_driver_cpu_get(int hartid)
|
||||
{
|
||||
if (hartid < __METAL_DT_MAX_HARTS) {
|
||||
return &(__metal_cpu_table[hartid]->cpu);
|
||||
}
|
||||
return (struct metal_cpu *)NULL;
|
||||
}
|
||||
|
||||
uintptr_t __metal_myhart_id (void)
|
||||
{
|
||||
uintptr_t myhart;
|
||||
asm volatile ("csrr %0, mhartid" : "=r"(myhart));
|
||||
return myhart;
|
||||
}
|
||||
|
||||
void __metal_zero_memory (unsigned char *base, unsigned int size)
|
||||
{
|
||||
volatile unsigned char *ptr;
|
||||
for (ptr = base; ptr < (base + size); ptr++){
|
||||
*ptr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void __metal_interrupt_global_enable (void) {
|
||||
uintptr_t m;
|
||||
asm volatile ("csrrs %0, mstatus, %1" : "=r"(m) : "r"(METAL_MIE_INTERRUPT));
|
||||
}
|
||||
|
||||
void __metal_interrupt_global_disable (void) {
|
||||
uintptr_t m;
|
||||
asm volatile ("csrrc %0, mstatus, %1" : "=r"(m) : "r"(METAL_MIE_INTERRUPT));
|
||||
}
|
||||
|
||||
void __metal_interrupt_software_enable (void) {
|
||||
uintptr_t m;
|
||||
asm volatile ("csrrs %0, mie, %1" : "=r"(m) : "r"(METAL_LOCAL_INTERRUPT_SW));
|
||||
}
|
||||
|
||||
void __metal_interrupt_software_disable (void) {
|
||||
uintptr_t m;
|
||||
asm volatile ("csrrc %0, mie, %1" : "=r"(m) : "r"(METAL_LOCAL_INTERRUPT_SW));
|
||||
}
|
||||
|
||||
void __metal_interrupt_timer_enable (void) {
|
||||
uintptr_t m;
|
||||
asm volatile ("csrrs %0, mie, %1" : "=r"(m) : "r"(METAL_LOCAL_INTERRUPT_TMR));
|
||||
}
|
||||
|
||||
void __metal_interrupt_timer_disable (void) {
|
||||
uintptr_t m;
|
||||
asm volatile ("csrrc %0, mie, %1" : "=r"(m) : "r"(METAL_LOCAL_INTERRUPT_TMR));
|
||||
}
|
||||
|
||||
void __metal_interrupt_external_enable (void) {
|
||||
uintptr_t m;
|
||||
asm volatile ("csrrs %0, mie, %1" : "=r"(m) : "r"(METAL_LOCAL_INTERRUPT_EXT));
|
||||
}
|
||||
|
||||
void __metal_interrupt_external_disable (void) {
|
||||
unsigned long m;
|
||||
asm volatile ("csrrc %0, mie, %1" : "=r"(m) : "r"(METAL_LOCAL_INTERRUPT_EXT));
|
||||
}
|
||||
|
||||
void __metal_interrupt_local_enable (int id) {
|
||||
uintptr_t b = 1 << id;
|
||||
uintptr_t m;
|
||||
asm volatile ("csrrs %0, mie, %1" : "=r"(m) : "r"(b));
|
||||
}
|
||||
|
||||
void __metal_interrupt_local_disable (int id) {
|
||||
uintptr_t b = 1 << id;
|
||||
uintptr_t m;
|
||||
asm volatile ("csrrc %0, mie, %1" : "=r"(m) : "r"(b));
|
||||
}
|
||||
|
||||
void __metal_default_exception_handler (struct metal_cpu *cpu, int ecode) {
|
||||
metal_shutdown(100);
|
||||
}
|
||||
|
||||
void __metal_default_interrupt_handler (int id, void *priv) {
|
||||
metal_shutdown(200);
|
||||
}
|
||||
|
||||
void __metal_default_sw_handler (int id, void *priv) {
|
||||
uintptr_t mcause;
|
||||
struct __metal_driver_riscv_cpu_intc *intc;
|
||||
struct __metal_driver_cpu *cpu = __metal_cpu_table[__metal_myhart_id()];
|
||||
|
||||
asm volatile ("csrr %0, mcause" : "=r"(mcause));
|
||||
if ( cpu ) {
|
||||
intc = (struct __metal_driver_riscv_cpu_intc *)
|
||||
__metal_driver_cpu_interrupt_controller((struct metal_cpu *)cpu);
|
||||
intc->metal_exception_table[mcause & METAL_MCAUSE_CAUSE]((struct metal_cpu *)cpu, id);
|
||||
}
|
||||
}
|
||||
|
||||
void __metal_default_timer_handler (int id, void *priv) {
|
||||
struct metal_cpu *cpu = __metal_driver_cpu_get(__metal_myhart_id());
|
||||
unsigned long long time = __metal_driver_cpu_mtime_get(cpu);
|
||||
|
||||
/* Set a 10 cycle timer */
|
||||
__metal_driver_cpu_mtimecmp_set(cpu, time + 10);
|
||||
}
|
||||
|
||||
void __metal_exception_handler(void) __attribute__((interrupt, aligned(128)));
|
||||
void __metal_exception_handler (void) {
|
||||
int id;
|
||||
void *priv;
|
||||
uintptr_t mcause, mepc, mtval, mtvec;
|
||||
struct __metal_driver_riscv_cpu_intc *intc;
|
||||
struct __metal_driver_cpu *cpu = __metal_cpu_table[__metal_myhart_id()];
|
||||
|
||||
asm volatile ("csrr %0, mcause" : "=r"(mcause));
|
||||
asm volatile ("csrr %0, mepc" : "=r"(mepc));
|
||||
asm volatile ("csrr %0, mtval" : "=r"(mtval));
|
||||
asm volatile ("csrr %0, mtvec" : "=r"(mtvec));
|
||||
|
||||
if ( cpu ) {
|
||||
intc = (struct __metal_driver_riscv_cpu_intc *)
|
||||
__metal_driver_cpu_interrupt_controller((struct metal_cpu *)cpu);
|
||||
id = mcause & METAL_MCAUSE_CAUSE;
|
||||
if (mcause & METAL_MCAUSE_INTR) {
|
||||
if ((id < METAL_INTERRUPT_ID_LC0) ||
|
||||
((mtvec & METAL_MTVEC_MASK) == METAL_MTVEC_DIRECT)) {
|
||||
priv = intc->metal_int_table[id].exint_data;
|
||||
intc->metal_int_table[id].handler(id, priv);
|
||||
return;
|
||||
}
|
||||
if ((mtvec & METAL_MTVEC_MASK) == METAL_MTVEC_CLIC) {
|
||||
uintptr_t mtvt;
|
||||
metal_interrupt_handler_t mtvt_handler;
|
||||
|
||||
asm volatile ("csrr %0, mtvt" : "=r"(mtvt));
|
||||
priv = intc->metal_int_table[METAL_INTERRUPT_ID_SW].sub_int;
|
||||
mtvt_handler = (metal_interrupt_handler_t)mtvt;
|
||||
mtvt_handler(id, priv);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
intc->metal_exception_table[id]((struct metal_cpu *)cpu, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void __metal_controller_interrupt_vector (metal_vector_mode mode, void *vec_table)
|
||||
{
|
||||
uintptr_t trap_entry, val;
|
||||
|
||||
asm volatile ("csrr %0, mtvec" : "=r"(val));
|
||||
val &= ~(METAL_MTVEC_CLIC_VECTORED | METAL_MTVEC_CLIC_RESERVED);
|
||||
trap_entry = (uintptr_t)vec_table;
|
||||
|
||||
switch (mode) {
|
||||
case METAL_SELECTIVE_VECTOR_MODE:
|
||||
asm volatile ("csrw mtvt, %0" :: "r"(trap_entry | METAL_MTVEC_CLIC));
|
||||
asm volatile ("csrw mtvec, %0" :: "r"(val | METAL_MTVEC_CLIC));
|
||||
break;
|
||||
case METAL_HARDWARE_VECTOR_MODE:
|
||||
asm volatile ("csrw mtvt, %0" :: "r"(trap_entry | METAL_MTVEC_CLIC_VECTORED));
|
||||
asm volatile ("csrw mtvec, %0" :: "r"(val | METAL_MTVEC_CLIC_VECTORED));
|
||||
break;
|
||||
case METAL_VECTOR_MODE:
|
||||
asm volatile ("csrw mtvec, %0" :: "r"(trap_entry | METAL_MTVEC_VECTORED));
|
||||
break;
|
||||
case METAL_DIRECT_MODE:
|
||||
asm volatile ("csrw mtvec, %0" :: "r"(trap_entry & ~METAL_MTVEC_CLIC_VECTORED));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int __metal_valid_interrupt_id (int id)
|
||||
{
|
||||
switch (id) {
|
||||
case METAL_INTERRUPT_ID_SW:
|
||||
case METAL_INTERRUPT_ID_TMR:
|
||||
case METAL_INTERRUPT_ID_EXT:
|
||||
case METAL_INTERRUPT_ID_LC0:
|
||||
case METAL_INTERRUPT_ID_LC1:
|
||||
case METAL_INTERRUPT_ID_LC2:
|
||||
case METAL_INTERRUPT_ID_LC3:
|
||||
case METAL_INTERRUPT_ID_LC4:
|
||||
case METAL_INTERRUPT_ID_LC5:
|
||||
case METAL_INTERRUPT_ID_LC6:
|
||||
case METAL_INTERRUPT_ID_LC7:
|
||||
case METAL_INTERRUPT_ID_LC8:
|
||||
case METAL_INTERRUPT_ID_LC9:
|
||||
case METAL_INTERRUPT_ID_LC10:
|
||||
case METAL_INTERRUPT_ID_LC11:
|
||||
case METAL_INTERRUPT_ID_LC12:
|
||||
case METAL_INTERRUPT_ID_LC13:
|
||||
case METAL_INTERRUPT_ID_LC14:
|
||||
case METAL_INTERRUPT_ID_LC15:
|
||||
return 1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int __metal_local_interrupt_enable (struct metal_interrupt *controller,
|
||||
metal_interrupt_id_e id, int enable)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if ( !controller) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (id) {
|
||||
case METAL_INTERRUPT_ID_BASE:
|
||||
if (enable) {
|
||||
__metal_interrupt_global_enable();
|
||||
} else {
|
||||
__metal_interrupt_global_disable();
|
||||
}
|
||||
break;
|
||||
case METAL_INTERRUPT_ID_SW:
|
||||
if (enable) {
|
||||
__metal_interrupt_software_enable();
|
||||
} else {
|
||||
__metal_interrupt_software_disable();
|
||||
}
|
||||
break;
|
||||
case METAL_INTERRUPT_ID_TMR:
|
||||
if (enable) {
|
||||
__metal_interrupt_timer_enable();
|
||||
} else {
|
||||
__metal_interrupt_timer_disable();
|
||||
}
|
||||
break;
|
||||
case METAL_INTERRUPT_ID_EXT:
|
||||
if (enable) {
|
||||
__metal_interrupt_external_enable();
|
||||
} else {
|
||||
__metal_interrupt_external_disable();
|
||||
}
|
||||
break;
|
||||
case METAL_INTERRUPT_ID_LC0:
|
||||
case METAL_INTERRUPT_ID_LC1:
|
||||
case METAL_INTERRUPT_ID_LC2:
|
||||
case METAL_INTERRUPT_ID_LC3:
|
||||
case METAL_INTERRUPT_ID_LC4:
|
||||
case METAL_INTERRUPT_ID_LC5:
|
||||
case METAL_INTERRUPT_ID_LC6:
|
||||
case METAL_INTERRUPT_ID_LC7:
|
||||
case METAL_INTERRUPT_ID_LC8:
|
||||
case METAL_INTERRUPT_ID_LC9:
|
||||
case METAL_INTERRUPT_ID_LC10:
|
||||
case METAL_INTERRUPT_ID_LC11:
|
||||
case METAL_INTERRUPT_ID_LC12:
|
||||
case METAL_INTERRUPT_ID_LC13:
|
||||
case METAL_INTERRUPT_ID_LC14:
|
||||
case METAL_INTERRUPT_ID_LC15:
|
||||
if (enable) {
|
||||
__metal_interrupt_local_enable(id);
|
||||
} else {
|
||||
__metal_interrupt_local_disable(id);
|
||||
}
|
||||
break;
|
||||
defaut:
|
||||
rc = -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int __metal_exception_register (struct metal_interrupt *controller,
|
||||
int ecode, metal_exception_handler_t isr)
|
||||
{
|
||||
struct __metal_driver_riscv_cpu_intc *intc = (void *)(controller);
|
||||
|
||||
if ((ecode < METAL_MAX_EXCEPTION_CODE) && isr) {
|
||||
intc->metal_exception_table[ecode] = isr;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void __metal_driver_riscv_cpu_controller_interrupt_init (struct metal_interrupt *controller)
|
||||
{
|
||||
struct __metal_driver_riscv_cpu_intc *intc = (void *)(controller);
|
||||
uintptr_t val;
|
||||
|
||||
if ( !intc->init_done ) {
|
||||
/* Disable and clear all interrupt sources */
|
||||
asm volatile ("csrc mie, %0" :: "r"(-1));
|
||||
asm volatile ("csrc mip, %0" :: "r"(-1));
|
||||
|
||||
/* Read the misa CSR to determine if the delegation registers exist */
|
||||
uintptr_t misa;
|
||||
asm volatile ("csrr %0, misa" : "=r" (misa));
|
||||
|
||||
/* The delegation CSRs exist if user mode interrupts (N extension) or
|
||||
* supervisor mode (S extension) are supported */
|
||||
if((misa & METAL_ISA_N_EXTENSIONS) || (misa & METAL_ISA_S_EXTENSIONS)) {
|
||||
/* Disable interrupt and exception delegation */
|
||||
asm volatile ("csrc mideleg, %0" :: "r"(-1));
|
||||
asm volatile ("csrc medeleg, %0" :: "r"(-1));
|
||||
}
|
||||
|
||||
/* The satp CSR exists if supervisor mode (S extension) is supported */
|
||||
if(misa & METAL_ISA_S_EXTENSIONS) {
|
||||
/* Clear the entire CSR to make sure that satp.MODE = 0 */
|
||||
asm volatile ("csrc satp, %0" :: "r"(-1));
|
||||
}
|
||||
|
||||
/* Default to use direct interrupt, setup sw cb table*/
|
||||
for (int i = 0; i < METAL_MAX_MI; i++) {
|
||||
intc->metal_int_table[i].handler = NULL;
|
||||
intc->metal_int_table[i].sub_int = NULL;
|
||||
intc->metal_int_table[i].exint_data = NULL;
|
||||
}
|
||||
for (int i = 0; i < METAL_MAX_ME; i++) {
|
||||
intc->metal_exception_table[i] = __metal_default_exception_handler;
|
||||
}
|
||||
__metal_controller_interrupt_vector(METAL_DIRECT_MODE, &__metal_exception_handler);
|
||||
asm volatile ("csrr %0, misa" : "=r"(val));
|
||||
if (val & (METAL_ISA_D_EXTENSIONS | METAL_ISA_F_EXTENSIONS | METAL_ISA_Q_EXTENSIONS)) {
|
||||
/* Floating point architecture, so turn on FP register saving*/
|
||||
asm volatile ("csrr %0, mstatus" : "=r"(val));
|
||||
asm volatile ("csrw mstatus, %0" :: "r"(val | METAL_MSTATUS_FS_INIT));
|
||||
}
|
||||
intc->init_done = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_cpu_controller_interrupt_register(struct metal_interrupt *controller,
|
||||
int id, metal_interrupt_handler_t isr,
|
||||
void *priv)
|
||||
{
|
||||
int rc = 0;
|
||||
struct __metal_driver_riscv_cpu_intc *intc = (void *)(controller);
|
||||
|
||||
if ( !__metal_valid_interrupt_id(id) ) {
|
||||
return -11;
|
||||
}
|
||||
|
||||
if (isr) {
|
||||
intc->metal_int_table[id].handler = isr;
|
||||
intc->metal_int_table[id].exint_data = priv;
|
||||
} else {
|
||||
switch (id) {
|
||||
case METAL_INTERRUPT_ID_SW:
|
||||
intc->metal_int_table[id].handler = __metal_default_sw_handler;
|
||||
intc->metal_int_table[id].sub_int = priv;
|
||||
break;
|
||||
case METAL_INTERRUPT_ID_TMR:
|
||||
intc->metal_int_table[id].handler = __metal_default_timer_handler;
|
||||
intc->metal_int_table[id].sub_int = priv;
|
||||
break;
|
||||
case METAL_INTERRUPT_ID_EXT:
|
||||
case METAL_INTERRUPT_ID_LC0:
|
||||
case METAL_INTERRUPT_ID_LC1:
|
||||
case METAL_INTERRUPT_ID_LC2:
|
||||
case METAL_INTERRUPT_ID_LC3:
|
||||
case METAL_INTERRUPT_ID_LC4:
|
||||
case METAL_INTERRUPT_ID_LC5:
|
||||
case METAL_INTERRUPT_ID_LC6:
|
||||
case METAL_INTERRUPT_ID_LC7:
|
||||
case METAL_INTERRUPT_ID_LC8:
|
||||
case METAL_INTERRUPT_ID_LC9:
|
||||
case METAL_INTERRUPT_ID_LC10:
|
||||
case METAL_INTERRUPT_ID_LC11:
|
||||
case METAL_INTERRUPT_ID_LC12:
|
||||
case METAL_INTERRUPT_ID_LC13:
|
||||
case METAL_INTERRUPT_ID_LC14:
|
||||
case METAL_INTERRUPT_ID_LC15:
|
||||
intc->metal_int_table[id].handler = __metal_default_interrupt_handler;
|
||||
intc->metal_int_table[id].sub_int = priv;
|
||||
break;
|
||||
defaut:
|
||||
rc = -12;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_cpu_controller_interrupt_enable (struct metal_interrupt *controller,
|
||||
int id)
|
||||
{
|
||||
return __metal_local_interrupt_enable(controller, id, METAL_ENABLE);
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_cpu_controller_interrupt_disable (struct metal_interrupt *controller,
|
||||
int id)
|
||||
{
|
||||
return __metal_local_interrupt_enable(controller, id, METAL_DISABLE);
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_cpu_controller_interrupt_enable_vector(struct metal_interrupt *controller,
|
||||
int id, metal_vector_mode mode)
|
||||
{
|
||||
struct __metal_driver_riscv_cpu_intc *intc = (void *)(controller);
|
||||
|
||||
if (id == METAL_INTERRUPT_ID_BASE) {
|
||||
if (mode == METAL_DIRECT_MODE) {
|
||||
__metal_controller_interrupt_vector(mode, &__metal_exception_handler);
|
||||
return 0;
|
||||
}
|
||||
if (mode == METAL_VECTOR_MODE) {
|
||||
__metal_controller_interrupt_vector(mode, &intc->metal_mtvec_table);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_cpu_controller_interrupt_disable_vector(struct metal_interrupt *controller,
|
||||
int id)
|
||||
{
|
||||
struct __metal_driver_riscv_cpu_intc *intc = (void *)(controller);
|
||||
|
||||
if (id == METAL_INTERRUPT_ID_BASE) {
|
||||
__metal_controller_interrupt_vector(METAL_DIRECT_MODE, &__metal_exception_handler);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_cpu_controller_command_request (struct metal_interrupt *controller,
|
||||
int cmd, void *data)
|
||||
{
|
||||
/* NOP for now, unless local interrupt lines the like of clic, clint, plic */
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern inline int __metal_controller_interrupt_is_selective_vectored(void);
|
||||
|
||||
/* CPU driver !!! */
|
||||
|
||||
unsigned long long __metal_driver_cpu_timer_get(struct metal_cpu *cpu)
|
||||
{
|
||||
unsigned long long val = 0;
|
||||
|
||||
#if __riscv_xlen == 32
|
||||
unsigned long hi, hi1, lo;
|
||||
|
||||
asm volatile ("csrr %0, mcycleh" : "=r"(hi));
|
||||
asm volatile ("csrr %0, mcycle" : "=r"(lo));
|
||||
asm volatile ("csrr %0, mcycleh" : "=r"(hi1));
|
||||
if (hi == hi1) {
|
||||
val = ((unsigned long long)hi << 32) | lo;
|
||||
}
|
||||
#else
|
||||
asm volatile ("csrr %0, mcycle" : "=r"(val));
|
||||
#endif
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
unsigned long long __metal_driver_cpu_timebase_get(struct metal_cpu *cpu)
|
||||
{
|
||||
int timebase;
|
||||
if (!cpu) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
timebase = __metal_driver_cpu_timebase((struct metal_cpu *)cpu);
|
||||
return timebase;
|
||||
}
|
||||
|
||||
unsigned long long __metal_driver_cpu_mtime_get (struct metal_cpu *cpu)
|
||||
{
|
||||
unsigned long long time = 0;
|
||||
struct metal_interrupt *tmr_intc;
|
||||
struct __metal_driver_riscv_cpu_intc *intc =
|
||||
(struct __metal_driver_riscv_cpu_intc *)__metal_driver_cpu_interrupt_controller(cpu);
|
||||
struct __metal_driver_cpu *_cpu = (void *)cpu;
|
||||
|
||||
if (intc) {
|
||||
tmr_intc = intc->metal_int_table[METAL_INTERRUPT_ID_TMR].sub_int;
|
||||
if (tmr_intc) {
|
||||
tmr_intc->vtable->command_request(tmr_intc,
|
||||
METAL_TIMER_MTIME_GET, &time);
|
||||
}
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
int __metal_driver_cpu_mtimecmp_set (struct metal_cpu *cpu, unsigned long long time)
|
||||
{
|
||||
int rc = -1;
|
||||
struct metal_interrupt *tmr_intc;
|
||||
struct __metal_driver_riscv_cpu_intc *intc =
|
||||
(struct __metal_driver_riscv_cpu_intc *)__metal_driver_cpu_interrupt_controller(cpu);
|
||||
struct __metal_driver_cpu *_cpu = (void *)cpu;
|
||||
|
||||
if (intc) {
|
||||
tmr_intc = intc->metal_int_table[METAL_INTERRUPT_ID_TMR].sub_int;
|
||||
if (tmr_intc) {
|
||||
rc = tmr_intc->vtable->mtimecmp_set(tmr_intc,
|
||||
__metal_driver_cpu_hartid(cpu),
|
||||
time);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct metal_interrupt *
|
||||
__metal_driver_cpu_timer_controller_interrupt(struct metal_cpu *cpu)
|
||||
{
|
||||
#ifdef __METAL_DT_RISCV_CLINT0_HANDLE
|
||||
return __METAL_DT_RISCV_CLINT0_HANDLE;
|
||||
#else
|
||||
#ifdef __METAL_DT_SIFIVE_CLIC0_HANDLE
|
||||
return __METAL_DT_SIFIVE_CLIC0_HANDLE;
|
||||
#else
|
||||
#warning "There is no interrupt controller for Timer interrupt"
|
||||
return NULL;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
int __metal_driver_cpu_get_timer_interrupt_id(struct metal_cpu *cpu)
|
||||
{
|
||||
return METAL_INTERRUPT_ID_TMR;
|
||||
}
|
||||
|
||||
struct metal_interrupt *
|
||||
__metal_driver_cpu_sw_controller_interrupt(struct metal_cpu *cpu)
|
||||
{
|
||||
#ifdef __METAL_DT_RISCV_CLINT0_HANDLE
|
||||
return __METAL_DT_RISCV_CLINT0_HANDLE;
|
||||
#else
|
||||
#ifdef __METAL_DT_SIFIVE_CLIC0_HANDLE
|
||||
return __METAL_DT_SIFIVE_CLIC0_HANDLE;
|
||||
#else
|
||||
#warning "There is no interrupt controller for Software interrupt"
|
||||
return NULL;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
int __metal_driver_cpu_get_sw_interrupt_id(struct metal_cpu *cpu)
|
||||
{
|
||||
return METAL_INTERRUPT_ID_SW;
|
||||
}
|
||||
|
||||
int __metal_driver_cpu_set_sw_ipi (struct metal_cpu *cpu, int hartid)
|
||||
{
|
||||
int rc = -1;
|
||||
struct metal_interrupt *sw_intc;
|
||||
struct __metal_driver_riscv_cpu_intc *intc =
|
||||
(struct __metal_driver_riscv_cpu_intc *)__metal_driver_cpu_interrupt_controller(cpu);
|
||||
struct __metal_driver_cpu *_cpu = (void *)cpu;
|
||||
|
||||
if (intc) {
|
||||
sw_intc = intc->metal_int_table[METAL_INTERRUPT_ID_SW].sub_int;
|
||||
if (sw_intc) {
|
||||
rc = sw_intc->vtable->command_request(sw_intc,
|
||||
METAL_SOFTWARE_IPI_SET, &hartid);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int __metal_driver_cpu_clear_sw_ipi (struct metal_cpu *cpu, int hartid)
|
||||
{
|
||||
int rc = -1;
|
||||
struct metal_interrupt *sw_intc;
|
||||
struct __metal_driver_riscv_cpu_intc *intc =
|
||||
(struct __metal_driver_riscv_cpu_intc *)__metal_driver_cpu_interrupt_controller(cpu);
|
||||
struct __metal_driver_cpu *_cpu = (void *)cpu;
|
||||
|
||||
if (intc) {
|
||||
sw_intc = intc->metal_int_table[METAL_INTERRUPT_ID_SW].sub_int;
|
||||
if (sw_intc) {
|
||||
rc = sw_intc->vtable->command_request(sw_intc,
|
||||
METAL_SOFTWARE_IPI_CLEAR, &hartid);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int __metal_driver_cpu_get_msip (struct metal_cpu *cpu, int hartid)
|
||||
{
|
||||
int rc = 0;
|
||||
struct metal_interrupt *sw_intc;
|
||||
struct __metal_driver_riscv_cpu_intc *intc =
|
||||
(struct __metal_driver_riscv_cpu_intc *)__metal_driver_cpu_interrupt_controller(cpu);
|
||||
struct __metal_driver_cpu *_cpu = (void *)cpu;
|
||||
|
||||
if (intc) {
|
||||
sw_intc = intc->metal_int_table[METAL_INTERRUPT_ID_SW].sub_int;
|
||||
if (sw_intc) {
|
||||
rc = sw_intc->vtable->command_request(sw_intc,
|
||||
METAL_SOFTWARE_MSIP_GET, &hartid);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct metal_interrupt *
|
||||
__metal_driver_cpu_controller_interrupt(struct metal_cpu *cpu)
|
||||
{
|
||||
return __metal_driver_cpu_interrupt_controller(cpu);
|
||||
}
|
||||
|
||||
int __metal_driver_cpu_enable_interrupt(struct metal_cpu *cpu, void *priv)
|
||||
{
|
||||
if ( __metal_driver_cpu_interrupt_controller(cpu) ) {
|
||||
/* Only support machine mode for now */
|
||||
__metal_interrupt_global_enable();
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int __metal_driver_cpu_disable_interrupt(struct metal_cpu *cpu, void *priv)
|
||||
{
|
||||
if ( __metal_driver_cpu_interrupt_controller(cpu) ) {
|
||||
/* Only support machine mode for now */
|
||||
__metal_interrupt_global_disable();
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int __metal_driver_cpu_exception_register(struct metal_cpu *cpu, int ecode,
|
||||
metal_exception_handler_t isr)
|
||||
{
|
||||
struct __metal_driver_riscv_cpu_intc *intc =
|
||||
(struct __metal_driver_riscv_cpu_intc *)__metal_driver_cpu_interrupt_controller(cpu);
|
||||
|
||||
if (intc) {
|
||||
return __metal_exception_register((struct metal_interrupt *)intc, ecode, isr);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int __metal_driver_cpu_get_instruction_length(struct metal_cpu *cpu, uintptr_t epc)
|
||||
{
|
||||
/* Per ISA compressed instruction has last two bits of opcode set */
|
||||
return (*(unsigned short*)epc & 3) ? 4 : 2;
|
||||
}
|
||||
|
||||
uintptr_t __metal_driver_cpu_get_exception_pc(struct metal_cpu *cpu)
|
||||
{
|
||||
uintptr_t mepc;
|
||||
asm volatile ("csrr %0, mepc" : "=r"(mepc));
|
||||
return mepc;
|
||||
}
|
||||
|
||||
int __metal_driver_cpu_set_exception_pc(struct metal_cpu *cpu, uintptr_t mepc)
|
||||
{
|
||||
asm volatile ("csrw mepc, %0" :: "r"(mepc));
|
||||
return 0;
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_riscv_cpu_intc) = {
|
||||
.controller_vtable.interrupt_init = __metal_driver_riscv_cpu_controller_interrupt_init,
|
||||
.controller_vtable.interrupt_register = __metal_driver_riscv_cpu_controller_interrupt_register,
|
||||
.controller_vtable.interrupt_enable = __metal_driver_riscv_cpu_controller_interrupt_enable,
|
||||
.controller_vtable.interrupt_disable = __metal_driver_riscv_cpu_controller_interrupt_disable,
|
||||
.controller_vtable.interrupt_vector_enable = __metal_driver_riscv_cpu_controller_interrupt_enable_vector,
|
||||
.controller_vtable.interrupt_vector_disable = __metal_driver_riscv_cpu_controller_interrupt_disable_vector,
|
||||
.controller_vtable.command_request = __metal_driver_riscv_cpu_controller_command_request,
|
||||
};
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_cpu) = {
|
||||
.cpu_vtable.timer_get = __metal_driver_cpu_timer_get,
|
||||
.cpu_vtable.timebase_get = __metal_driver_cpu_timebase_get,
|
||||
.cpu_vtable.mtime_get = __metal_driver_cpu_mtime_get,
|
||||
.cpu_vtable.mtimecmp_set = __metal_driver_cpu_mtimecmp_set,
|
||||
.cpu_vtable.tmr_controller_interrupt = __metal_driver_cpu_timer_controller_interrupt,
|
||||
.cpu_vtable.get_tmr_interrupt_id = __metal_driver_cpu_get_timer_interrupt_id,
|
||||
.cpu_vtable.sw_controller_interrupt = __metal_driver_cpu_sw_controller_interrupt,
|
||||
.cpu_vtable.get_sw_interrupt_id = __metal_driver_cpu_get_sw_interrupt_id,
|
||||
.cpu_vtable.set_sw_ipi = __metal_driver_cpu_set_sw_ipi,
|
||||
.cpu_vtable.clear_sw_ipi = __metal_driver_cpu_clear_sw_ipi,
|
||||
.cpu_vtable.get_msip = __metal_driver_cpu_get_msip,
|
||||
.cpu_vtable.controller_interrupt = __metal_driver_cpu_controller_interrupt,
|
||||
.cpu_vtable.exception_register = __metal_driver_cpu_exception_register,
|
||||
.cpu_vtable.get_ilen = __metal_driver_cpu_get_instruction_length,
|
||||
.cpu_vtable.get_epc = __metal_driver_cpu_get_exception_pc,
|
||||
.cpu_vtable.set_epc = __metal_driver_cpu_set_exception_pc,
|
||||
};
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_RISCV_PLIC0
|
||||
|
||||
#include <metal/io.h>
|
||||
#include <metal/shutdown.h>
|
||||
#include <metal/drivers/riscv_plic0.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
unsigned int __metal_plic0_claim_interrupt (struct __metal_driver_riscv_plic0 *plic)
|
||||
{
|
||||
unsigned long control_base = __metal_driver_sifive_plic0_control_base((struct metal_interrupt *)plic);
|
||||
return __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
METAL_RISCV_PLIC0_CLAIM));
|
||||
}
|
||||
|
||||
void __metal_plic0_complete_interrupt(struct __metal_driver_riscv_plic0 *plic,
|
||||
unsigned int id)
|
||||
{
|
||||
unsigned long control_base = __metal_driver_sifive_plic0_control_base((struct metal_interrupt *)plic);
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
METAL_RISCV_PLIC0_CLAIM)) = id;
|
||||
}
|
||||
|
||||
void __metal_plic0_set_threshold(struct __metal_driver_riscv_plic0 *plic,
|
||||
unsigned int threshold)
|
||||
{
|
||||
unsigned long control_base = __metal_driver_sifive_plic0_control_base((struct metal_interrupt *)plic);
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
METAL_RISCV_PLIC0_THRESHOLD)) = threshold;
|
||||
}
|
||||
|
||||
void __metal_plic0_set_priority(struct __metal_driver_riscv_plic0 *plic,
|
||||
int id, unsigned int priority)
|
||||
{
|
||||
unsigned long control_base = __metal_driver_sifive_plic0_control_base((struct metal_interrupt *)plic);
|
||||
int max_priority = __metal_driver_sifive_plic0_max_priority((struct metal_interrupt *)plic);
|
||||
if ( (max_priority) && (priority < max_priority) ) {
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
METAL_RISCV_PLIC0_PRIORITY_BASE +
|
||||
(id << METAL_PLIC_SOURCE_PRIORITY_SHIFT))) = priority;
|
||||
}
|
||||
}
|
||||
|
||||
void __metal_plic0_enable(struct __metal_driver_riscv_plic0 *plic, int id, int enable)
|
||||
{
|
||||
unsigned int current;
|
||||
unsigned long hartid = __metal_myhart_id();
|
||||
unsigned long control_base = __metal_driver_sifive_plic0_control_base((struct metal_interrupt *)plic);
|
||||
|
||||
current = __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
METAL_RISCV_PLIC0_ENABLE_BASE +
|
||||
(id >> METAL_PLIC_SOURCE_SHIFT) * 4));
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
METAL_RISCV_PLIC0_ENABLE_BASE +
|
||||
((id >> METAL_PLIC_SOURCE_SHIFT) * 4))) =
|
||||
enable ? (current | (1 << (id & METAL_PLIC_SOURCE_MASK)))
|
||||
: (current & ~(1 << (id & METAL_PLIC_SOURCE_MASK)));
|
||||
}
|
||||
|
||||
void __metal_plic0_default_handler (int id, void *priv) {
|
||||
metal_shutdown(300);
|
||||
}
|
||||
|
||||
void __metal_plic0_handler (int id, void *priv)
|
||||
{
|
||||
struct __metal_driver_riscv_plic0 *plic = priv;
|
||||
unsigned int idx = __metal_plic0_claim_interrupt(plic);
|
||||
int num_interrupts = __metal_driver_sifive_plic0_num_interrupts((struct metal_interrupt *)plic);
|
||||
|
||||
if ( (idx < num_interrupts) && (plic->metal_exint_table[idx]) ) {
|
||||
plic->metal_exint_table[idx](idx,
|
||||
plic->metal_exdata_table[idx].exint_data);
|
||||
}
|
||||
|
||||
__metal_plic0_complete_interrupt(plic, idx);
|
||||
}
|
||||
|
||||
void __metal_driver_riscv_plic0_init (struct metal_interrupt *controller)
|
||||
{
|
||||
struct __metal_driver_riscv_plic0 *plic = (void *)(controller);
|
||||
|
||||
if ( !plic->init_done ) {
|
||||
int num_interrupts, line;
|
||||
struct metal_interrupt *intc;
|
||||
|
||||
for(int parent = 0; parent < __METAL_PLIC_NUM_PARENTS; parent++) {
|
||||
num_interrupts = __metal_driver_sifive_plic0_num_interrupts(controller);
|
||||
intc = __metal_driver_sifive_plic0_interrupt_parents(controller, parent);
|
||||
line = __metal_driver_sifive_plic0_interrupt_lines(controller, parent);
|
||||
|
||||
/* Initialize ist parent controller, aka cpu_intc. */
|
||||
intc->vtable->interrupt_init(intc);
|
||||
|
||||
for (int i = 0; i < num_interrupts; i++) {
|
||||
__metal_plic0_enable(plic, i, METAL_DISABLE);
|
||||
__metal_plic0_set_priority(plic, i, 0);
|
||||
plic->metal_exint_table[i] = NULL;
|
||||
plic->metal_exdata_table[i].sub_int = NULL;
|
||||
plic->metal_exdata_table[i].exint_data = NULL;
|
||||
}
|
||||
|
||||
__metal_plic0_set_threshold(plic, 0);
|
||||
|
||||
/* Register plic (ext) interrupt with with parent controller */
|
||||
intc->vtable->interrupt_register(intc, line, NULL, plic);
|
||||
/* Register plic handler for dispatching its device interrupts */
|
||||
intc->vtable->interrupt_register(intc, line, __metal_plic0_handler, plic);
|
||||
/* Enable plic (ext) interrupt with with parent controller */
|
||||
intc->vtable->interrupt_enable(intc, line);
|
||||
}
|
||||
plic->init_done = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_plic0_register (struct metal_interrupt *controller,
|
||||
int id, metal_interrupt_handler_t isr,
|
||||
void *priv)
|
||||
{
|
||||
struct __metal_driver_riscv_plic0 *plic = (void *)(controller);
|
||||
|
||||
if (id >= __metal_driver_sifive_plic0_num_interrupts(controller)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (isr) {
|
||||
__metal_plic0_set_priority(plic ,id, 2);
|
||||
plic->metal_exint_table[id] = isr;
|
||||
plic->metal_exdata_table[id].exint_data = priv;
|
||||
} else {
|
||||
__metal_plic0_set_priority(plic, id, 1);
|
||||
plic->metal_exint_table[id] = __metal_plic0_default_handler;
|
||||
plic->metal_exdata_table[id].sub_int = priv;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_plic0_enable (struct metal_interrupt *controller, int id)
|
||||
{
|
||||
struct __metal_driver_riscv_plic0 *plic = (void *)(controller);
|
||||
|
||||
if (id >= __metal_driver_sifive_plic0_num_interrupts(controller)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
__metal_plic0_enable(plic, id, METAL_ENABLE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_driver_riscv_plic0_disable (struct metal_interrupt *controller, int id)
|
||||
{
|
||||
struct __metal_driver_riscv_plic0 *plic = (void *)(controller);
|
||||
|
||||
if (id >= __metal_driver_sifive_plic0_num_interrupts(controller)) {
|
||||
return -1;
|
||||
}
|
||||
__metal_plic0_enable(plic, id, METAL_DISABLE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_riscv_plic0) = {
|
||||
.plic_vtable.interrupt_init = __metal_driver_riscv_plic0_init,
|
||||
.plic_vtable.interrupt_register = __metal_driver_riscv_plic0_register,
|
||||
.plic_vtable.interrupt_enable = __metal_driver_riscv_plic0_enable,
|
||||
.plic_vtable.interrupt_disable = __metal_driver_riscv_plic0_disable,
|
||||
};
|
||||
|
||||
#endif /* METAL_RISCV_PLIC0 */
|
|
@ -0,0 +1,564 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_CLIC0
|
||||
|
||||
#include <stdint.h>
|
||||
#include <metal/io.h>
|
||||
#include <metal/shutdown.h>
|
||||
#include <metal/drivers/sifive_clic0.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
typedef enum metal_priv_mode_ {
|
||||
METAL_PRIV_M_MODE = 0,
|
||||
METAL_PRIV_MU_MODE = 1,
|
||||
METAL_PRIV_MSU_MODE = 2
|
||||
} metal_priv_mode;
|
||||
|
||||
typedef enum metal_clic_vector_{
|
||||
METAL_CLIC_NONVECTOR = 0,
|
||||
METAL_CLIC_VECTORED = 1
|
||||
} metal_clic_vector;
|
||||
|
||||
struct __metal_clic_cfg {
|
||||
unsigned char : 1,
|
||||
nmbits : 2,
|
||||
nlbits : 4,
|
||||
nvbit : 1;
|
||||
};
|
||||
|
||||
const struct __metal_clic_cfg __metal_clic_defaultcfg = {
|
||||
.nmbits = METAL_PRIV_M_MODE,
|
||||
.nlbits = 0,
|
||||
.nvbit = METAL_CLIC_NONVECTOR
|
||||
};
|
||||
|
||||
struct __metal_clic_cfg __metal_clic0_configuration (struct __metal_driver_sifive_clic0 *clic,
|
||||
struct __metal_clic_cfg *cfg)
|
||||
{
|
||||
volatile unsigned char val;
|
||||
struct __metal_clic_cfg cliccfg;
|
||||
uintptr_t hartid = __metal_myhart_id();
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
|
||||
if ( cfg ) {
|
||||
val = cfg->nmbits << 5 | cfg->nlbits << 1 | cfg->nvbit;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICCFG)) = val;
|
||||
}
|
||||
val = __METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICCFG));
|
||||
cliccfg.nmbits = (val & METAL_SIFIVE_CLIC0_CLICCFG_NMBITS_MASK) >> 5;
|
||||
cliccfg.nlbits = (val & METAL_SIFIVE_CLIC0_CLICCFG_NLBITS_MASK) >> 1;
|
||||
cliccfg.nvbit = val & METAL_SIFIVE_CLIC0_CLICCFG_NVBIT_MASK;
|
||||
return cliccfg;
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_set_mode (struct __metal_driver_sifive_clic0 *clic, int id, int mode)
|
||||
{
|
||||
uint8_t mask, val;
|
||||
struct __metal_clic_cfg cfg = __metal_clic0_configuration(clic, NULL);
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
|
||||
if (mode >= (cfg.nmbits << 1)) {
|
||||
/* Do nothing, mode request same or exceed what configured in CLIC */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mask out nmbits and retain other values */
|
||||
mask = ((uint8_t)(-1)) >> cfg.nmbits;
|
||||
val = __METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTCTL_BASE + id)) & mask;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTCTL_BASE + id)) = val | (mode << (8 - cfg.nmbits));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_set_level (struct __metal_driver_sifive_clic0 *clic, int id, int level)
|
||||
{
|
||||
uint8_t mask, nmmask, nlmask, val;
|
||||
struct __metal_clic_cfg cfg = __metal_clic0_configuration(clic, NULL);
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
|
||||
/* Drop the LSBs that don't fit in nlbits */
|
||||
level = level >> (METAL_CLIC_MAX_NLBITS - cfg.nlbits);
|
||||
|
||||
nmmask = ~( ((uint8_t)(-1)) >> (cfg.nmbits) );
|
||||
nlmask = ((uint8_t)(-1)) >> (cfg.nmbits + cfg.nlbits);
|
||||
mask = ~(nlmask | nmmask);
|
||||
|
||||
val = __METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTCTL_BASE + id));
|
||||
__METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTCTL_BASE + id)) = __METAL_SET_FIELD(val, mask, level);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_get_level (struct __metal_driver_sifive_clic0 *clic, int id)
|
||||
{
|
||||
int level;
|
||||
uint8_t mask, val, freebits, nlbits;
|
||||
struct __metal_clic_cfg cfg = __metal_clic0_configuration(clic, NULL);
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
int num_intbits = __metal_driver_sifive_clic0_num_intbits((struct metal_interrupt *)clic);
|
||||
|
||||
if ((cfg.nmbits + cfg.nlbits) >= num_intbits) {
|
||||
nlbits = num_intbits - cfg.nmbits;
|
||||
} else {
|
||||
nlbits = cfg.nlbits;
|
||||
}
|
||||
|
||||
mask = ((1 << nlbits) - 1) << (8 - (cfg.nmbits + nlbits));
|
||||
freebits = ((1 << METAL_CLIC_MAX_NLBITS) - 1) >> nlbits;
|
||||
|
||||
if (mask == 0) {
|
||||
level = (1 << METAL_CLIC_MAX_NLBITS) - 1;
|
||||
} else {
|
||||
val = __METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTCTL_BASE + id));
|
||||
val = __METAL_GET_FIELD(val, mask);
|
||||
level = (val << (METAL_CLIC_MAX_NLBITS - nlbits)) | freebits;
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_set_priority (struct __metal_driver_sifive_clic0 *clic, int id, int priority)
|
||||
{
|
||||
uint8_t mask, npmask, val, npbits;
|
||||
struct __metal_clic_cfg cfg = __metal_clic0_configuration(clic, NULL);
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
int num_intbits = __metal_driver_sifive_clic0_num_intbits((struct metal_interrupt *)clic);
|
||||
|
||||
if ((cfg.nmbits + cfg.nlbits) < num_intbits) {
|
||||
npbits = num_intbits - (cfg.nmbits + cfg.nlbits);
|
||||
priority = priority >> (8 - npbits);
|
||||
|
||||
mask = ((uint8_t)(-1)) >> (cfg.nmbits + cfg.nlbits + npbits);
|
||||
npmask = ~(((uint8_t)(-1)) >> (cfg.nmbits + cfg.nlbits));
|
||||
mask = ~(mask | npmask);
|
||||
|
||||
val = __METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTCTL_BASE + id));
|
||||
__METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTCTL_BASE + id)) = __METAL_SET_FIELD(val, mask, priority);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_get_priority (struct __metal_driver_sifive_clic0 *clic, int id)
|
||||
{
|
||||
int priority;
|
||||
uint8_t mask, val, freebits, nlbits;
|
||||
struct __metal_clic_cfg cfg = __metal_clic0_configuration(clic, NULL);
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
int num_intbits = __metal_driver_sifive_clic0_num_intbits((struct metal_interrupt *)clic);
|
||||
|
||||
if ((cfg.nmbits + cfg.nlbits) >= num_intbits) {
|
||||
nlbits = num_intbits - cfg.nmbits;
|
||||
} else {
|
||||
nlbits = cfg.nlbits;
|
||||
}
|
||||
|
||||
mask = ((1 << nlbits) - 1) << (8 - (cfg.nmbits + nlbits));
|
||||
freebits = ((1 << METAL_CLIC_MAX_NLBITS) - 1) >> nlbits;
|
||||
|
||||
if (mask == 0) {
|
||||
priority = (1 << METAL_CLIC_MAX_NLBITS) - 1;
|
||||
} else {
|
||||
val = __METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTCTL_BASE + id));
|
||||
priority = __METAL_GET_FIELD(val, freebits);
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_set_vector (struct __metal_driver_sifive_clic0 *clic, int id, int enable)
|
||||
{
|
||||
uint8_t mask, val;
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
int num_intbits = __metal_driver_sifive_clic0_num_intbits((struct metal_interrupt *)clic);
|
||||
|
||||
mask = 1 << (8 - num_intbits);
|
||||
val = __METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTCTL_BASE + id));
|
||||
/* Ensure its value is 1 bit wide */
|
||||
enable &= 0x1;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTCTL_BASE + id)) = __METAL_SET_FIELD(val, mask, enable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_is_vectored (struct __metal_driver_sifive_clic0 *clic, int id)
|
||||
{
|
||||
uint8_t mask, val;
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
int num_intbits = __metal_driver_sifive_clic0_num_intbits((struct metal_interrupt *)clic);
|
||||
|
||||
mask = 1 << (8 - num_intbits);
|
||||
val = __METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTCTL_BASE + id));
|
||||
return __METAL_GET_FIELD(val, mask);
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_enable (struct __metal_driver_sifive_clic0 *clic, int id)
|
||||
{
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
int num_subinterrupts = __metal_driver_sifive_clic0_num_subinterrupts((struct metal_interrupt *)clic);
|
||||
|
||||
if (id >= num_subinterrupts) {
|
||||
return -1;
|
||||
}
|
||||
__METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTIE_BASE + id)) = METAL_ENABLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_disable (struct __metal_driver_sifive_clic0 *clic, int id)
|
||||
{
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
int num_subinterrupts = __metal_driver_sifive_clic0_num_subinterrupts((struct metal_interrupt *)clic);
|
||||
|
||||
if (id >= num_subinterrupts) {
|
||||
return -1;
|
||||
}
|
||||
__METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTIE_BASE + id)) = METAL_DISABLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_is_enabled (struct __metal_driver_sifive_clic0 *clic, int id)
|
||||
{
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
int num_subinterrupts = __metal_driver_sifive_clic0_num_subinterrupts((struct metal_interrupt *)clic);
|
||||
|
||||
if (id >= num_subinterrupts) {
|
||||
return 0;
|
||||
}
|
||||
return __METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTIE_BASE + id));
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_is_pending (struct __metal_driver_sifive_clic0 *clic, int id)
|
||||
{
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
int num_subinterrupts = __metal_driver_sifive_clic0_num_subinterrupts((struct metal_interrupt *)clic);
|
||||
|
||||
if (id >= num_subinterrupts) {
|
||||
return 0;
|
||||
}
|
||||
return __METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTIP_BASE + id));
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_set (struct __metal_driver_sifive_clic0 *clic, int id)
|
||||
{
|
||||
int num_subinterrupts = __metal_driver_sifive_clic0_num_subinterrupts((struct metal_interrupt *)clic);
|
||||
|
||||
if ((id >= METAL_INTERRUPT_ID_LC0) && (id < num_subinterrupts)) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_clic0_interrupt_clear (struct __metal_driver_sifive_clic0 *clic, int id)
|
||||
{
|
||||
int num_subinterrupts = __metal_driver_sifive_clic0_num_subinterrupts((struct metal_interrupt *)clic);
|
||||
|
||||
if ((id >= METAL_INTERRUPT_ID_LC0) && (id < num_subinterrupts)) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __metal_clic0_configure_privilege (struct __metal_driver_sifive_clic0 *clic, metal_priv_mode priv)
|
||||
{
|
||||
struct __metal_clic_cfg cfg = __metal_clic0_configuration(clic, NULL);
|
||||
|
||||
cfg.nmbits = priv;
|
||||
__metal_clic0_configuration(clic, &cfg);
|
||||
}
|
||||
|
||||
void __metal_clic0_configure_level (struct __metal_driver_sifive_clic0 *clic, int level)
|
||||
{
|
||||
struct __metal_clic_cfg cfg = __metal_clic0_configuration(clic, NULL);
|
||||
|
||||
cfg.nlbits = level;
|
||||
__metal_clic0_configuration(clic, &cfg);
|
||||
}
|
||||
|
||||
unsigned long long __metal_clic0_mtime_get (struct __metal_driver_sifive_clic0 *clic)
|
||||
{
|
||||
__metal_io_u32 lo, hi;
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
|
||||
/* Guard against rollover when reading */
|
||||
do {
|
||||
hi = __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + METAL_SIFIVE_CLIC0_MTIME + 4));
|
||||
lo = __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + METAL_SIFIVE_CLIC0_MTIME));
|
||||
} while (__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + METAL_SIFIVE_CLIC0_MTIME + 4)) != hi);
|
||||
|
||||
return (((unsigned long long)hi) << 32) | lo;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_clic0_mtimecmp_set(struct metal_interrupt *controller,
|
||||
int hartid,
|
||||
unsigned long long time)
|
||||
{
|
||||
struct __metal_driver_sifive_clic0 *clic =
|
||||
(struct __metal_driver_sifive_clic0 *)(controller);
|
||||
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base((struct metal_interrupt *)clic);
|
||||
/* Per spec, the RISC-V MTIME/MTIMECMP registers are 64 bit,
|
||||
* and are NOT internally latched for multiword transfers.
|
||||
* Need to be careful about sequencing to avoid triggering
|
||||
* spurious interrupts: For that set the high word to a max
|
||||
* value first.
|
||||
*/
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + (8 * hartid) + METAL_SIFIVE_CLIC0_MTIMECMP_BASE + 4)) = 0xFFFFFFFF;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + (8 * hartid) + METAL_SIFIVE_CLIC0_MTIMECMP_BASE)) = (__metal_io_u32)time;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base + (8 * hartid) + METAL_SIFIVE_CLIC0_MTIMECMP_BASE + 4)) = (__metal_io_u32)(time >> 32);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __metal_clic0_handler(int id, void *priv) __attribute__((aligned(64)));
|
||||
void __metal_clic0_handler (int id, void *priv)
|
||||
{
|
||||
int idx;
|
||||
struct __metal_driver_sifive_clic0 *clic = priv;
|
||||
int num_subinterrupts = __metal_driver_sifive_clic0_num_subinterrupts((struct metal_interrupt *)clic);
|
||||
|
||||
idx = id - METAL_INTERRUPT_ID_LC0;
|
||||
if ( (idx < num_subinterrupts) && (clic->metal_mtvt_table[idx]) ) {
|
||||
clic->metal_mtvt_table[idx](id, clic->metal_exint_table[idx].exint_data);
|
||||
}
|
||||
}
|
||||
|
||||
void __metal_clic0_default_handler (int id, void *priv) {
|
||||
metal_shutdown(300);
|
||||
}
|
||||
|
||||
void __metal_driver_sifive_clic0_init (struct metal_interrupt *controller)
|
||||
{
|
||||
struct __metal_driver_sifive_clic0 *clic =
|
||||
(struct __metal_driver_sifive_clic0 *)(controller);
|
||||
|
||||
if ( !clic->init_done ) {
|
||||
int level, max_levels, line, num_interrupts, num_subinterrupts;
|
||||
struct __metal_clic_cfg cfg = __metal_clic_defaultcfg;
|
||||
struct metal_interrupt *intc =
|
||||
__metal_driver_sifive_clic0_interrupt_parent(controller);
|
||||
|
||||
/* Initialize ist parent controller, aka cpu_intc. */
|
||||
intc->vtable->interrupt_init(intc);
|
||||
__metal_controller_interrupt_vector(METAL_SELECTIVE_VECTOR_MODE,
|
||||
&__metal_clic0_handler);
|
||||
|
||||
/*
|
||||
* Register its interrupts with with parent controller,
|
||||
* aka sw, timer and ext to its default isr
|
||||
*/
|
||||
num_interrupts = __metal_driver_sifive_clic0_num_interrupts(controller);
|
||||
for (int i = 0; i < num_interrupts; i++) {
|
||||
line = __metal_driver_sifive_clic0_interrupt_lines(controller, i);
|
||||
intc->vtable->interrupt_register(intc, line, NULL, clic);
|
||||
}
|
||||
|
||||
/* Default CLIC mode to per dts */
|
||||
max_levels = __metal_driver_sifive_clic0_max_levels(controller);
|
||||
cfg.nlbits = (max_levels > METAL_CLIC_MAX_NLBITS) ?
|
||||
METAL_CLIC_MAX_NLBITS : max_levels;
|
||||
__metal_clic0_configuration(clic, &cfg);
|
||||
|
||||
level = (1 << cfg.nlbits) - 1;
|
||||
num_subinterrupts = __metal_driver_sifive_clic0_num_subinterrupts(controller);
|
||||
for (int i = 0; i < num_subinterrupts; i++) {
|
||||
clic->metal_mtvt_table[i] = NULL;
|
||||
clic->metal_exint_table[i].sub_int = NULL;
|
||||
clic->metal_exint_table[i].exint_data = NULL;
|
||||
__metal_clic0_interrupt_disable(clic, i);
|
||||
__metal_clic0_interrupt_set_level(clic, i, level);
|
||||
}
|
||||
clic->init_done = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_clic0_register (struct metal_interrupt *controller,
|
||||
int id, metal_interrupt_handler_t isr,
|
||||
void *priv)
|
||||
{
|
||||
int rc = -1;
|
||||
int num_subinterrupts;
|
||||
struct __metal_driver_sifive_clic0 *clic =
|
||||
(struct __metal_driver_sifive_clic0 *)(controller);
|
||||
struct metal_interrupt *intc =
|
||||
__metal_driver_sifive_clic0_interrupt_parent(controller);
|
||||
|
||||
/* Register its interrupts with parent controller */
|
||||
if ( id < METAL_INTERRUPT_ID_LC0) {
|
||||
return intc->vtable->interrupt_register(intc, id, isr, priv);
|
||||
}
|
||||
|
||||
/*
|
||||
* CLIC (sub-interrupts) devices interrupts start at 16 but offset from 0
|
||||
* Reset the IDs to reflects this.
|
||||
*/
|
||||
id -= METAL_INTERRUPT_ID_LC0;
|
||||
num_subinterrupts = __metal_driver_sifive_clic0_num_subinterrupts(controller);
|
||||
if (id < num_subinterrupts) {
|
||||
if ( isr) {
|
||||
clic->metal_mtvt_table[id] = isr;
|
||||
clic->metal_exint_table[id].exint_data = priv;
|
||||
} else {
|
||||
clic->metal_mtvt_table[id] = __metal_clic0_default_handler;
|
||||
clic->metal_exint_table[id].sub_int = priv;
|
||||
}
|
||||
rc = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_clic0_enable (struct metal_interrupt *controller, int id)
|
||||
{
|
||||
struct __metal_driver_sifive_clic0 *clic =
|
||||
(struct __metal_driver_sifive_clic0 *)(controller);
|
||||
return __metal_clic0_interrupt_enable(clic, id);
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_clic0_disable (struct metal_interrupt *controller, int id)
|
||||
{
|
||||
struct __metal_driver_sifive_clic0 *clic =
|
||||
(struct __metal_driver_sifive_clic0 *)(controller);
|
||||
return __metal_clic0_interrupt_disable(clic, id);
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_clic0_enable_interrupt_vector(struct metal_interrupt *controller,
|
||||
int id, metal_vector_mode mode)
|
||||
{
|
||||
int num_subinterrupts;
|
||||
struct __metal_driver_sifive_clic0 *clic =
|
||||
(struct __metal_driver_sifive_clic0 *)(controller);
|
||||
|
||||
if (id == METAL_INTERRUPT_ID_BASE) {
|
||||
if (mode == METAL_SELECTIVE_VECTOR_MODE) {
|
||||
__metal_controller_interrupt_vector(mode, &__metal_clic0_handler);
|
||||
return 0;
|
||||
}
|
||||
if (mode == METAL_HARDWARE_VECTOR_MODE) {
|
||||
__metal_controller_interrupt_vector(mode, &clic->metal_mtvt_table);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
num_subinterrupts = __metal_driver_sifive_clic0_num_subinterrupts(controller);
|
||||
if ((id >= METAL_INTERRUPT_ID_LC0) && (id < num_subinterrupts)) {
|
||||
if ((mode == METAL_SELECTIVE_VECTOR_MODE) &&
|
||||
__metal_controller_interrupt_is_selective_vectored()) {
|
||||
__metal_clic0_interrupt_set_vector(clic, id, METAL_ENABLE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_clic0_disable_interrupt_vector(struct metal_interrupt *controller, int id)
|
||||
{
|
||||
int num_subinterrupts;
|
||||
struct __metal_driver_sifive_clic0 *clic =
|
||||
(struct __metal_driver_sifive_clic0 *)(controller);
|
||||
|
||||
if (id == METAL_INTERRUPT_ID_BASE) {
|
||||
__metal_controller_interrupt_vector(METAL_SELECTIVE_VECTOR_MODE, &__metal_clic0_handler);
|
||||
return 0;
|
||||
}
|
||||
num_subinterrupts = __metal_driver_sifive_clic0_num_subinterrupts(controller);
|
||||
if ((id >= METAL_INTERRUPT_ID_LC0) && (id < num_subinterrupts)) {
|
||||
if (__metal_controller_interrupt_is_selective_vectored()) {
|
||||
__metal_clic0_interrupt_set_vector(clic, id, METAL_DISABLE);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_clic0_command_request (struct metal_interrupt *controller,
|
||||
int command, void *data)
|
||||
{
|
||||
int hartid;
|
||||
int rc = -1;
|
||||
struct __metal_driver_sifive_clic0 *clic =
|
||||
(struct __metal_driver_sifive_clic0 *)(controller);
|
||||
unsigned long control_base = __metal_driver_sifive_clic0_control_base(controller);
|
||||
|
||||
switch (command) {
|
||||
case METAL_TIMER_MTIME_GET:
|
||||
if (data) {
|
||||
*(unsigned long long *)data = __metal_clic0_mtime_get(clic);
|
||||
rc = 0;
|
||||
}
|
||||
break;
|
||||
case METAL_SOFTWARE_IPI_CLEAR:
|
||||
if (data) {
|
||||
hartid = *(int *)data;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
(hartid * 4))) = METAL_DISABLE;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTIP_BASE)) = METAL_DISABLE;
|
||||
rc = 0;
|
||||
}
|
||||
break;
|
||||
case METAL_SOFTWARE_IPI_SET:
|
||||
if (data) {
|
||||
hartid = *(int *)data;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
(hartid * 4))) = METAL_ENABLE;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u8 *)(control_base +
|
||||
METAL_SIFIVE_CLIC0_MMODE_APERTURE +
|
||||
METAL_SIFIVE_CLIC0_CLICINTIP_BASE)) = METAL_ENABLE;
|
||||
rc = 0;
|
||||
}
|
||||
break;
|
||||
case METAL_SOFTWARE_MSIP_GET:
|
||||
rc = 0;
|
||||
if (data) {
|
||||
hartid = *(int *)data;
|
||||
rc = __METAL_ACCESS_ONCE((__metal_io_u32 *)(control_base +
|
||||
(hartid * 4)));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_clic0) = {
|
||||
.clic_vtable.interrupt_init = __metal_driver_sifive_clic0_init,
|
||||
.clic_vtable.interrupt_register = __metal_driver_sifive_clic0_register,
|
||||
.clic_vtable.interrupt_enable = __metal_driver_sifive_clic0_enable,
|
||||
.clic_vtable.interrupt_disable = __metal_driver_sifive_clic0_disable,
|
||||
.clic_vtable.interrupt_vector_enable = __metal_driver_sifive_clic0_enable_interrupt_vector,
|
||||
.clic_vtable.interrupt_vector_disable = __metal_driver_sifive_clic0_disable_interrupt_vector,
|
||||
.clic_vtable.command_request = __metal_driver_sifive_clic0_command_request,
|
||||
.clic_vtable.mtimecmp_set = __metal_driver_sifive_clic0_mtimecmp_set,
|
||||
};
|
||||
|
||||
#endif /* METAL_SIFIVE_CLIC0 */
|
|
@ -0,0 +1,42 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_FE310_G000_HFROSC
|
||||
|
||||
#include <metal/drivers/sifive_fe310-g000_hfrosc.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
#define CONFIG_DIVIDER 0x0000003FUL
|
||||
#define CONFIG_TRIM 0x001F0000UL
|
||||
#define CONFIG_ENABLE 0x40000000UL
|
||||
#define CONFIG_READY 0x80000000UL
|
||||
|
||||
long __metal_driver_sifive_fe310_g000_hfrosc_get_rate_hz(const struct metal_clock *clock)
|
||||
{
|
||||
struct metal_clock *ref = __metal_driver_sifive_fe310_g000_hfrosc_ref(clock);
|
||||
long config_offset = __metal_driver_sifive_fe310_g000_hfrosc_config_offset(clock);
|
||||
struct __metal_driver_sifive_fe310_g000_prci *config_base =
|
||||
__metal_driver_sifive_fe310_g000_hfrosc_config_base(clock);
|
||||
const struct __metal_driver_vtable_sifive_fe310_g000_prci *vtable =
|
||||
__metal_driver_sifive_fe310_g000_prci_vtable();
|
||||
long cfg = vtable->get_reg(config_base, config_offset);
|
||||
|
||||
if (cfg & CONFIG_ENABLE == 0)
|
||||
return -1;
|
||||
if (cfg & CONFIG_READY == 0)
|
||||
return -1;
|
||||
return metal_clock_get_rate_hz(ref) / ((cfg & CONFIG_DIVIDER) + 1);
|
||||
}
|
||||
|
||||
long __metal_driver_sifive_fe310_g000_hfrosc_set_rate_hz(struct metal_clock *clock, long rate)
|
||||
{
|
||||
return __metal_driver_sifive_fe310_g000_hfrosc_get_rate_hz(clock);
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_fe310_g000_hfrosc) = {
|
||||
.clock.get_rate_hz = &__metal_driver_sifive_fe310_g000_hfrosc_get_rate_hz,
|
||||
.clock.set_rate_hz = &__metal_driver_sifive_fe310_g000_hfrosc_set_rate_hz,
|
||||
};
|
||||
#endif /* METAL_SIFIVE_FE310_G000_HFROSC */
|
|
@ -0,0 +1,41 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_FE310_G000_HFXOSC
|
||||
|
||||
#include <metal/drivers/sifive_fe310-g000_hfxosc.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
#define CONFIG_ENABLE 0x40000000UL
|
||||
#define CONFIG_READY 0x80000000UL
|
||||
|
||||
long __metal_driver_sifive_fe310_g000_hfxosc_get_rate_hz(const struct metal_clock *clock)
|
||||
{
|
||||
struct metal_clock *ref = __metal_driver_sifive_fe310_g000_hfxosc_ref(clock);
|
||||
long config_offset = __metal_driver_sifive_fe310_g000_hfxosc_config_offset(clock);
|
||||
struct __metal_driver_sifive_fe310_g000_prci *config_base =
|
||||
__metal_driver_sifive_fe310_g000_hfxosc_config_base(clock);
|
||||
const struct __metal_driver_vtable_sifive_fe310_g000_prci *vtable =
|
||||
__metal_driver_sifive_fe310_g000_prci_vtable();
|
||||
long cfg = vtable->get_reg(config_base, config_offset);
|
||||
|
||||
if (cfg & CONFIG_ENABLE == 0)
|
||||
return -1;
|
||||
if (cfg & CONFIG_READY == 0)
|
||||
return -1;
|
||||
return metal_clock_get_rate_hz(ref);
|
||||
}
|
||||
|
||||
long __metal_driver_sifive_fe310_g000_hfxosc_set_rate_hz(struct metal_clock *clock, long rate)
|
||||
{
|
||||
return __metal_driver_sifive_fe310_g000_hfxosc_get_rate_hz(clock);
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_fe310_g000_hfxosc) = {
|
||||
.clock.get_rate_hz = __metal_driver_sifive_fe310_g000_hfxosc_get_rate_hz,
|
||||
.clock.set_rate_hz = __metal_driver_sifive_fe310_g000_hfxosc_set_rate_hz,
|
||||
};
|
||||
|
||||
#endif /* METAL_SIFIVE_FE310_G000_HFXOSC */
|
|
@ -0,0 +1,360 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_FE310_G000_PLL
|
||||
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <metal/machine.h>
|
||||
#include <metal/drivers/sifive_fe310-g000_pll.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define PLL_R 0x00000007UL
|
||||
#define PLL_F 0x000003F0UL
|
||||
#define PLL_Q 0x00000C00UL
|
||||
#define PLL_SEL 0x00010000UL
|
||||
#define PLL_REFSEL 0x00020000UL
|
||||
#define PLL_BYPASS 0x00040000UL
|
||||
#define PLL_LOCK 0x80000000UL
|
||||
|
||||
#define DIV_DIV 0x0000003FUL
|
||||
#define DIV_1 0x00000100UL
|
||||
|
||||
#define PLL_R_SHIFT(r) ((r << 0) & PLL_R)
|
||||
#define PLL_F_SHIFT(f) ((f << 4) & PLL_F)
|
||||
#define PLL_Q_SHIFT(q) ((q << 10) & PLL_Q)
|
||||
#define PLL_DIV_SHIFT(d) ((d << 0) & DIV_DIV)
|
||||
|
||||
struct pll_config_t {
|
||||
unsigned long multiplier;
|
||||
unsigned long divisor;
|
||||
unsigned long min_input_rate;
|
||||
unsigned long max_input_rate;
|
||||
unsigned long r;
|
||||
unsigned long f;
|
||||
unsigned long q;
|
||||
long d; /* < 0 if disabled */
|
||||
};
|
||||
|
||||
static const struct pll_config_t pll_configs[] = {
|
||||
/*
|
||||
* multiplier
|
||||
* ^ divisor
|
||||
* | ^ min_input_rate
|
||||
* | | ^ max_input_rate
|
||||
* | | | ^ r
|
||||
* | | | | ^ f
|
||||
* | | | | | ^ q
|
||||
* | | | | | | ^ d
|
||||
* | | | | | | | ^
|
||||
* | | | | | | | | */
|
||||
{ 1, 32, 12000000, 24000000, 1, 31, 3, 63},
|
||||
{ 1, 32, 24000000, 48000000, 3, 31, 2, 63},
|
||||
{ 1, 16, 6000000, 12000000, 0, 31, 3, 63},
|
||||
{ 1, 16, 12000000, 24000000, 1, 31, 2, 63},
|
||||
{ 1, 16, 24000000, 48000000, 3, 31, 2, 31},
|
||||
{ 1, 8, 6000000, 12000000, 0, 31, 3, 31},
|
||||
{ 1, 8, 12000000, 24000000, 1, 31, 2, 31},
|
||||
{ 1, 8, 24000000, 48000000, 3, 31, 2, 15},
|
||||
{ 1, 4, 6000000, 12000000, 0, 31, 3, 15},
|
||||
{ 1, 4, 12000000, 24000000, 1, 31, 2, 15},
|
||||
{ 1, 4, 24000000, 48000000, 3, 31, 2, 7},
|
||||
{ 1, 2, 6000000, 12000000, 0, 31, 2, 15},
|
||||
{ 1, 2, 12000000, 24000000, 1, 31, 1, 15},
|
||||
{ 1, 2, 24000000, 48000000, 3, 31, 1, 7},
|
||||
{ 2, 1, 6000000, 12000000, 0, 31, 1, 7},
|
||||
{ 2, 1, 12000000, 24000000, 1, 31, 1, 3},
|
||||
{ 2, 1, 24000000, 48000000, 3, 31, 3, -1},
|
||||
{ 4, 1, 6000000, 12000000, 0, 31, 3, 0},
|
||||
{ 4, 1, 12000000, 24000000, 1, 31, 3, -1},
|
||||
{ 4, 1, 24000000, 48000000, 3, 31, 2, -1},
|
||||
{ 6, 1, 6000000, 10666666, 0, 35, 1, 2},
|
||||
{ 6, 1, 10666666, 12000000, 0, 23, 3, -1},
|
||||
{ 6, 1, 12000000, 16000000, 1, 47, 3, -1},
|
||||
{ 6, 1, 16000000, 18000000, 1, 23, 2, -1},
|
||||
{ 6, 1, 18000000, 21333333, 2, 35, 2, -1},
|
||||
{ 8, 1, 6000000, 12000000, 0, 31, 3, -1},
|
||||
{ 8, 1, 12000000, 24000000, 1, 31, 2, -1},
|
||||
{ 8, 1, 24000000, 48000000, 3, 31, 1, -1},
|
||||
{10, 1, 6000000, 9600000, 0, 39, 3, -1},
|
||||
{10, 1, 9600000, 12000000, 0, 19, 2, -1},
|
||||
{10, 1, 12000000, 19200000, 1, 39, 2, -1},
|
||||
{10, 1, 19200000, 24000000, 1, 19, 1, -1},
|
||||
{10, 1, 24000000, 38400000, 3, 39, 1, -1},
|
||||
{12, 1, 6000000, 8000000, 0, 47, 3, -1},
|
||||
{12, 1, 8000000, 12000000, 0, 23, 2, -1},
|
||||
{12, 1, 12000000, 16000000, 1, 47, 2, -1},
|
||||
{12, 1, 16000000, 24000000, 1, 23, 1, -1},
|
||||
{12, 1, 24000000, 30000000, 3, 47, 1, -1},
|
||||
{12, 1, 30000000, 32000000, 3, 47, 1, -1},
|
||||
{14, 1, 6000000, 6857142, 0, 55, 3, -1},
|
||||
{14, 1, 6857143, 12000000, 0, 27, 2, -1},
|
||||
{14, 1, 12000000, 13714285, 1, 55, 2, -1},
|
||||
{14, 1, 13714286, 24000000, 1, 27, 1, -1},
|
||||
{14, 1, 24000000, 27428571, 3, 55, 1, -1},
|
||||
{16, 1, 6000000, 12000000, 0, 31, 2, -1},
|
||||
{16, 1, 12000000, 24000000, 1, 31, 1, -1},
|
||||
{18, 1, 6000000, 10666666, 0, 35, 2, -1},
|
||||
{18, 1, 10666667, 12000000, 0, 17, 1, -1},
|
||||
{18, 1, 12000000, 21333333, 1, 35, 1, -1},
|
||||
{20, 1, 6000000, 9600000, 0, 39, 2, -1},
|
||||
{20, 1, 9600000, 12000000, 0, 19, 1, -1},
|
||||
{20, 1, 12000000, 19200000, 1, 39, 1, -1},
|
||||
{22, 1, 6000000, 8727272, 0, 43, 2, -1},
|
||||
{22, 1, 8727273, 12000000, 0, 21, 1, -1},
|
||||
{22, 1, 12000000, 17454545, 1, 43, 1, -1},
|
||||
{24, 1, 6000000, 8000000, 0, 47, 2, -1},
|
||||
{24, 1, 8000000, 12000000, 0, 23, 1, -1},
|
||||
{24, 1, 12000000, 16000000, 1, 47, 1, -1},
|
||||
{26, 1, 6000000, 7384615, 0, 51, 2, -1},
|
||||
{26, 1, 7384616, 12000000, 0, 25, 1, -1},
|
||||
{26, 1, 12000000, 14768230, 1, 51, 1, -1},
|
||||
{28, 1, 6000000, 6857142, 0, 55, 2, -1},
|
||||
{28, 1, 6857143, 12000000, 0, 27, 1, -1},
|
||||
{28, 1, 12000000, 13714285, 1, 55, 1, -1},
|
||||
{30, 1, 6000000, 6400000, 0, 59, 2, -1},
|
||||
{30, 1, 6400000, 12000000, 0, 29, 1, -1},
|
||||
{30, 1, 12000000, 12800000, 1, 59, 1, -1},
|
||||
{32, 1, 6000000, 12000000, 0, 31, 1, -1}
|
||||
};
|
||||
|
||||
#define PLL_CONFIG_NOT_VALID -1
|
||||
|
||||
void __metal_driver_sifive_fe310_g000_pll_init(struct __metal_driver_sifive_fe310_g000_pll *pll);
|
||||
|
||||
/* Given the rate of the PLL input frequency and a PLL configuration, what
|
||||
* will the resulting PLL output frequency be?
|
||||
* Arguments:
|
||||
* - pll_input_rate the PLL input frequency in hertz
|
||||
* - config the PLL configuration
|
||||
* Returns:
|
||||
* - PLL_CONFIG_NOT_VALID if the configuration is not valid for the input frequency
|
||||
* - the output frequency, in hertz */
|
||||
static long get_pll_config_freq(long pll_input_rate, const struct pll_config_t *config)
|
||||
{
|
||||
if(pll_input_rate < config->min_input_rate || pll_input_rate > config->max_input_rate)
|
||||
return PLL_CONFIG_NOT_VALID;
|
||||
|
||||
return pll_input_rate * config->multiplier / config->divisor;
|
||||
}
|
||||
|
||||
#ifdef __METAL_DT_SIFIVE_FE310_G000_PLL_HANDLE
|
||||
|
||||
static void metal_sifive_fe310_g000_pll_init(void) __attribute__((constructor));
|
||||
static void metal_sifive_fe310_g000_pll_init(void) {
|
||||
long init_rate = __metal_driver_sifive_fe310_g000_pll_init_rate();
|
||||
/* If the PLL init_rate is zero, don't initialize the PLL */
|
||||
if(init_rate != 0)
|
||||
__metal_driver_sifive_fe310_g000_pll_init(__METAL_DT_SIFIVE_FE310_G000_PLL_HANDLE);
|
||||
}
|
||||
|
||||
#endif /* __METAL_DT_SIFIVE_FE310_G000__PLL_HANDLE */
|
||||
|
||||
void __metal_driver_sifive_fe310_g000_pll_init(struct __metal_driver_sifive_fe310_g000_pll *pll) {
|
||||
struct metal_clock *pllref = __metal_driver_sifive_fe310_g000_pll_pllref(&(pll->clock));
|
||||
long init_rate = __metal_driver_sifive_fe310_g000_pll_init_rate();
|
||||
long config_offset = __metal_driver_sifive_fe310_g000_pll_config_offset();
|
||||
long base = __metal_driver_sifive_fe310_g000_prci_base();
|
||||
|
||||
__metal_io_u32 *pllcfg = (__metal_io_u32 *) (base + config_offset);
|
||||
|
||||
/* If the PLL clock has had a _pre_rate_change_callback configured, call it */
|
||||
if(pll->clock._pre_rate_change_callback != NULL)
|
||||
pll->clock._pre_rate_change_callback(pll->clock._pre_rate_change_callback_priv);
|
||||
|
||||
/* If we're running off of the PLL, switch off before we start configuring it*/
|
||||
if((__METAL_ACCESS_ONCE(pllcfg) & PLL_SEL) == 0)
|
||||
__METAL_ACCESS_ONCE(pllcfg) &= ~(PLL_SEL);
|
||||
|
||||
/* Make sure we're running off of the external oscillator for stability */
|
||||
if(pllref != NULL)
|
||||
__METAL_ACCESS_ONCE(pllcfg) |= PLL_REFSEL;
|
||||
|
||||
/* Configure the PLL to run at the requested init frequency.
|
||||
* Using the vtable instead of the user API because we want to control
|
||||
* when the callbacks occur. */
|
||||
pll->clock.vtable->set_rate_hz(&(pll->clock), init_rate);
|
||||
|
||||
/* If the PLL clock has had a rate_change_callback configured, call it */
|
||||
if(pll->clock._post_rate_change_callback != NULL)
|
||||
pll->clock._post_rate_change_callback(pll->clock._post_rate_change_callback_priv);
|
||||
}
|
||||
|
||||
long __metal_driver_sifive_fe310_g000_pll_get_rate_hz(const struct metal_clock *clock)
|
||||
{
|
||||
struct metal_clock *pllref = __metal_driver_sifive_fe310_g000_pll_pllref(clock);
|
||||
struct metal_clock *pllsel0 = __metal_driver_sifive_fe310_g000_pll_pllsel0(clock);
|
||||
long config_offset = __metal_driver_sifive_fe310_g000_pll_config_offset(clock);
|
||||
struct __metal_driver_sifive_fe310_g000_prci *config_base =
|
||||
__metal_driver_sifive_fe310_g000_pll_config_base(clock);
|
||||
long divider_offset = __metal_driver_sifive_fe310_g000_pll_divider_offset(clock);
|
||||
struct __metal_driver_sifive_fe310_g000_prci *divider_base =
|
||||
__metal_driver_sifive_fe310_g000_pll_divider_base(clock);
|
||||
const struct __metal_driver_vtable_sifive_fe310_g000_prci *vtable =
|
||||
__metal_driver_sifive_fe310_g000_prci_vtable();
|
||||
|
||||
long cfg = vtable->get_reg(config_base, config_offset);
|
||||
long div = vtable->get_reg(divider_base, divider_offset);
|
||||
|
||||
/* At the end of the PLL there's one big mux: it either selects the HFROSC
|
||||
* (bypassing the PLL entirely) or uses the PLL. */
|
||||
if (__METAL_GET_FIELD(cfg, PLL_SEL) == 0)
|
||||
return metal_clock_get_rate_hz(pllsel0);
|
||||
|
||||
/* There's a clock mux before the PLL that selects between the HFROSC adn
|
||||
* the HFXOSC as the PLL's input clock. */
|
||||
long ref_hz = metal_clock_get_rate_hz(__METAL_GET_FIELD(cfg, PLL_REFSEL) ? pllref : pllsel0);
|
||||
|
||||
/* It's possible to bypass the PLL, which is an internal bpyass. This
|
||||
* still obays the PLL's input clock mu. */
|
||||
if (__METAL_GET_FIELD(cfg, PLL_BYPASS))
|
||||
return ref_hz;
|
||||
|
||||
/* Logically the PLL is a three stage div-mul-div. */
|
||||
long div_r = __METAL_GET_FIELD(cfg, PLL_R) + 1;
|
||||
long mul_f = 2 * (__METAL_GET_FIELD(cfg, PLL_F) + 1);
|
||||
if (__METAL_GET_FIELD(cfg, PLL_Q) == 0)
|
||||
return -1;
|
||||
long div_q = 1 << __METAL_GET_FIELD(cfg, PLL_Q);
|
||||
|
||||
/* In addition to the dividers inherent in the PLL, there's an additional
|
||||
* clock divider that lives after the PLL and lets us pick a more
|
||||
* interesting range of frequencies. */
|
||||
long pllout = (((ref_hz / div_r) * mul_f) / div_q);
|
||||
if (__METAL_GET_FIELD(div, DIV_1))
|
||||
return pllout;
|
||||
|
||||
return pllout / (2 * (__METAL_GET_FIELD(div, DIV_DIV) + 1));
|
||||
}
|
||||
|
||||
/* Find a valid configuration for the PLL which is closest to the desired
|
||||
* output frequency.
|
||||
* Arguments:
|
||||
* - ref_hz PLL input frequency
|
||||
* - rate desired PLL output frequency
|
||||
* Returns:
|
||||
* -1 if no valid configuration is available
|
||||
* the index into pll_configs of a valid configuration */
|
||||
static int find_closest_config(long ref_hz, long rate)
|
||||
{
|
||||
int closest_index = -1;
|
||||
long closest_diff = LONG_MAX;
|
||||
|
||||
/* We're probably trying for a fast output frequency, so start from
|
||||
* the high end of the configs. */
|
||||
for(int i = (sizeof(pll_configs) / sizeof(pll_configs[0])) - 1; i >= 0; i--)
|
||||
{
|
||||
long config_freq = get_pll_config_freq(ref_hz, &(pll_configs[i]));
|
||||
if(config_freq != PLL_CONFIG_NOT_VALID)
|
||||
{
|
||||
long freq_diff = abs(config_freq - rate);
|
||||
if(freq_diff < closest_diff)
|
||||
{
|
||||
closest_index = i;
|
||||
closest_diff = freq_diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closest_index;
|
||||
}
|
||||
|
||||
/* Configure the PLL and wait for it to lock */
|
||||
static void configure_pll(__metal_io_u32 *pllcfg, __metal_io_u32 *plloutdiv, const struct pll_config_t *config)
|
||||
{
|
||||
__METAL_ACCESS_ONCE(pllcfg) &= ~(PLL_R);
|
||||
__METAL_ACCESS_ONCE(pllcfg) |= PLL_R_SHIFT(config->r);
|
||||
|
||||
__METAL_ACCESS_ONCE(pllcfg) &= ~(PLL_F);
|
||||
__METAL_ACCESS_ONCE(pllcfg) |= PLL_F_SHIFT(config->f);
|
||||
|
||||
__METAL_ACCESS_ONCE(pllcfg) &= ~(PLL_Q);
|
||||
__METAL_ACCESS_ONCE(pllcfg) |= PLL_Q_SHIFT(config->q);
|
||||
|
||||
if(config->d < 0)
|
||||
{
|
||||
/* disable final divider */
|
||||
__METAL_ACCESS_ONCE(plloutdiv) |= DIV_1;
|
||||
|
||||
__METAL_ACCESS_ONCE(plloutdiv) &= ~(DIV_DIV);
|
||||
__METAL_ACCESS_ONCE(plloutdiv) |= PLL_DIV_SHIFT(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
__METAL_ACCESS_ONCE(plloutdiv) &= ~(DIV_1);
|
||||
|
||||
__METAL_ACCESS_ONCE(plloutdiv) &= ~(DIV_DIV);
|
||||
__METAL_ACCESS_ONCE(plloutdiv) |= PLL_DIV_SHIFT(config->d);
|
||||
}
|
||||
|
||||
__METAL_ACCESS_ONCE(pllcfg) &= ~(PLL_BYPASS);
|
||||
|
||||
/* Wait for PLL to lock */
|
||||
while((__METAL_ACCESS_ONCE(pllcfg) & PLL_LOCK) == 0) ;
|
||||
}
|
||||
|
||||
long __metal_driver_sifive_fe310_g000_pll_set_rate_hz(struct metal_clock *clock, long rate)
|
||||
{
|
||||
struct metal_clock *pllref = __metal_driver_sifive_fe310_g000_pll_pllref(clock);
|
||||
struct metal_clock *pllsel0 = __metal_driver_sifive_fe310_g000_pll_pllsel0(clock);
|
||||
long config_offset = __metal_driver_sifive_fe310_g000_pll_config_offset(clock);
|
||||
long divider_offset = __metal_driver_sifive_fe310_g000_pll_divider_offset(clock);
|
||||
long base = __metal_driver_sifive_fe310_g000_prci_base();
|
||||
|
||||
__metal_io_u32 *pllcfg = (__metal_io_u32 *) (base + config_offset);
|
||||
__metal_io_u32 *plloutdiv = (__metal_io_u32 *) (base + divider_offset);
|
||||
|
||||
/* We can't modify the PLL if coreclk is driven by it, so switch it off */
|
||||
if (__METAL_ACCESS_ONCE(pllcfg) & PLL_SEL)
|
||||
__METAL_ACCESS_ONCE(pllcfg) &= ~(PLL_SEL);
|
||||
|
||||
/* There's a clock mux before the PLL that selects between the HFROSC and
|
||||
* the HFXOSC as the PLL's input clock. */
|
||||
long ref_hz = metal_clock_get_rate_hz(__METAL_ACCESS_ONCE(pllcfg) & PLL_REFSEL ? pllref : pllsel0);
|
||||
|
||||
/* if the desired rate is within 75%-125% of the input clock, bypass the PLL */
|
||||
if((ref_hz * 3 / 4) <= rate && (ref_hz * 5 / 4) >= rate)
|
||||
{
|
||||
__METAL_ACCESS_ONCE(pllcfg) |= PLL_BYPASS;
|
||||
}
|
||||
else
|
||||
{
|
||||
int config_index = find_closest_config(ref_hz, rate);
|
||||
if(config_index != -1)
|
||||
{
|
||||
configure_pll(pllcfg, plloutdiv, &(pll_configs[config_index]));
|
||||
}
|
||||
else
|
||||
{
|
||||
/* unable to find a valid configuration */
|
||||
__METAL_ACCESS_ONCE(pllcfg) |= PLL_BYPASS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable the PLL */
|
||||
__METAL_ACCESS_ONCE(pllcfg) |= PLL_SEL;
|
||||
|
||||
return __metal_driver_sifive_fe310_g000_pll_get_rate_hz(clock);
|
||||
}
|
||||
|
||||
#ifdef __METAL_DT_SIFIVE_FE310_G000_PLL_HANDLE
|
||||
static void use_hfxosc(void) __attribute__((constructor));
|
||||
static void use_hfxosc(void)
|
||||
{
|
||||
long init_rate = __metal_driver_sifive_fe310_g000_pll_init_rate();
|
||||
metal_clock_set_rate_hz(
|
||||
&__METAL_DT_SIFIVE_FE310_G000_PLL_HANDLE->clock, init_rate
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_fe310_g000_pll) = {
|
||||
.init = __metal_driver_sifive_fe310_g000_pll_init,
|
||||
.clock.get_rate_hz = __metal_driver_sifive_fe310_g000_pll_get_rate_hz,
|
||||
.clock.set_rate_hz = __metal_driver_sifive_fe310_g000_pll_set_rate_hz,
|
||||
};
|
||||
|
||||
#endif /* METAL_SIFIVE_FE310_G000_PLL */
|
|
@ -0,0 +1,26 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_FE310_G000_PRCI
|
||||
|
||||
#include <metal/drivers/sifive_fe310-g000_prci.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
long __metal_driver_sifive_fe310_g000_prci_get_reg(const struct __metal_driver_sifive_fe310_g000_prci *prci, long offset) {
|
||||
unsigned long base = __metal_driver_sifive_fe310_g000_prci_base();
|
||||
return __METAL_ACCESS_ONCE((__metal_io_u32 *)(base + offset));
|
||||
}
|
||||
|
||||
long __metal_driver_sifive_fe310_g000_prci_set_reg(const struct __metal_driver_sifive_fe310_g000_prci *prci, long offset, long value) {
|
||||
unsigned long base = __metal_driver_sifive_fe310_g000_prci_base();
|
||||
return __METAL_ACCESS_ONCE((__metal_io_u32 *)(base + offset)) = value;
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_fe310_g000_prci) = {
|
||||
.get_reg = __metal_driver_sifive_fe310_g000_prci_get_reg,
|
||||
.set_reg = __metal_driver_sifive_fe310_g000_prci_set_reg,
|
||||
};
|
||||
|
||||
#endif /* METAL_SIFIVE_FE310_G000_PRCI */
|
|
@ -0,0 +1,81 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
#include <metal/drivers/sifive_fu540-c000_l2.h>
|
||||
|
||||
#define L2_CONFIG_WAYS_SHIFT 8
|
||||
#define L2_CONFIG_WAYS_MASK (0xFF << L2_CONFIG_WAYS_SHIFT)
|
||||
|
||||
#ifdef CONFIG_SIFIVE_FU540_C000_L2
|
||||
|
||||
static void metal_driver_sifive_fu540_c000_l2_init(void) __attribute__((constructor));
|
||||
static void metal_driver_sifive_fu540_c000_l2_init(void)
|
||||
{
|
||||
#ifdef __METAL_DT_SIFIVE_FU540_C000_L2_HANDLE
|
||||
/* Get the handle for the L2 cache controller */
|
||||
struct __metal_driver_sifive_fu540_c000_l2 *l2 = __METAL_DT_SIFIVE_FU540_C000_L2_HANDLE;
|
||||
if(!l2) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get the number of available ways per bank */
|
||||
uint32_t ways = __METAL_ACCESS_ONCE((__metal_io_u32 *)(l2->control_base + SIFIVE_FU540_C000_L2_CONFIG));
|
||||
ways = ((ways & L2_CONFIG_WAYS_MASK) >> L2_CONFIG_WAYS_SHIFT);
|
||||
|
||||
/* Enable all the ways */
|
||||
__metal_driver_sifive_fu540_c000_l2_init(l2, ways);
|
||||
#endif
|
||||
}
|
||||
|
||||
void __metal_driver_sifive_fu540_c000_l2_init(struct metal_cache *l2, int ways)
|
||||
{
|
||||
metal_cache_set_enabled_ways(l2, ways);
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_fu540_c000_l2_get_enabled_ways(struct metal_cache *cache)
|
||||
{
|
||||
struct __metal_driver_sifive_fu540_c000_l2 *l2 = (struct __metal_driver_sifive_fu540_c000_l2 *) cache;
|
||||
if(!l2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t way_enable = __METAL_ACCESS_ONCE((__metal_io_u32 *)(l2->control_base + SIFIVE_FU540_C000_L2_WAYENABLE));
|
||||
|
||||
/* The stored number is the index, so add one */
|
||||
return (0xFF & way_enable) + 1;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_fu540_c000_l2_set_enabled_ways(struct metal_cache *cache, int ways)
|
||||
{
|
||||
struct __metal_driver_sifive_fu540_c000_l2 *l2 = (struct __metal_driver_sifive_fu540_c000_l2 *) cache;
|
||||
if(!l2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* We can't decrease the number of enabled ways */
|
||||
if(metal_cache_get_enabled_ways(cache) > ways) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* The stored value is the index, so subtract one */
|
||||
uint32_t value = 0xFF & (ways - 1);
|
||||
|
||||
/* Set the number of enabled ways */
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(l2->control_base + SIFIVE_FU540_C000_L2_WAYENABLE)) = value;
|
||||
|
||||
/* Make sure the number of ways was set correctly */
|
||||
if(metal_cache_get_enabled_ways(cache) != ways) {
|
||||
return -3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_fu540_c000_l2) = {
|
||||
.cache.init = __metal_driver_sifive_fu540_c000_l2_init,
|
||||
.cache.get_enabled_ways = __metal_driver_sifive_fu540_c000_l2_get_enabled_ways,
|
||||
.cache.set_enabled_ways = __metal_driver_sifive_fu540_c000_l2_set_enabled_ways,
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,120 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_GLOBAL_EXTERNAL_INTERRUPTS0
|
||||
|
||||
#include <metal/io.h>
|
||||
#include <metal/shutdown.h>
|
||||
#include <metal/drivers/sifive_global-external-interrupts0.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
void __metal_driver_sifive_global_external_interrupt_init(struct metal_interrupt *controller)
|
||||
{
|
||||
struct __metal_driver_sifive_global_external_interrupts0 *global0;
|
||||
|
||||
global0 = (struct __metal_driver_sifive_global_external_interrupts0 *)(controller);
|
||||
if ( !global0->init_done ) {
|
||||
struct metal_interrupt *intc =
|
||||
__metal_driver_sifive_global_external_interrupts0_interrupt_parent(controller);
|
||||
|
||||
if (intc) {
|
||||
intc->vtable->interrupt_init(intc);
|
||||
/* Register its interrupts with with parent controller */
|
||||
for (int i = 0;
|
||||
i < __metal_driver_sifive_global_external_interrupts0_num_interrupts(controller);
|
||||
i++) {
|
||||
intc->vtable->interrupt_register(intc,
|
||||
__metal_driver_sifive_global_external_interrupts0_interrupt_lines(controller, i),
|
||||
NULL, controller);
|
||||
}
|
||||
global0->init_done = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_global_external_interrupt_register(struct metal_interrupt *controller,
|
||||
int id, metal_interrupt_handler_t isr,
|
||||
void *priv)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
if (id != 0) {
|
||||
struct metal_interrupt *intc =
|
||||
__metal_driver_sifive_global_external_interrupts0_interrupt_parent(controller);
|
||||
|
||||
/* Enable its interrupts with parent controller */
|
||||
if (intc) {
|
||||
rc = intc->vtable->interrupt_register(intc, id, isr, priv);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_global_external_interrupt_enable(struct metal_interrupt *controller, int id)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
if (id != 0) {
|
||||
struct metal_interrupt *intc =
|
||||
__metal_driver_sifive_global_external_interrupts0_interrupt_parent(controller);
|
||||
|
||||
/* Enable its interrupts with parent controller */
|
||||
if (intc) {
|
||||
rc = intc->vtable->interrupt_enable(intc, id);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_global_external_interrupt_disable(struct metal_interrupt *controller, int id)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
if (id != 0) {
|
||||
struct metal_interrupt *intc =
|
||||
__metal_driver_sifive_global_external_interrupts0_interrupt_parent(controller);
|
||||
|
||||
/* Enable its interrupts with parent controller */
|
||||
if (intc) {
|
||||
rc = intc->vtable->interrupt_disable(intc, id);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_global_external_command_request (struct metal_interrupt *controller,
|
||||
int command, void *data)
|
||||
{
|
||||
int idx;
|
||||
int rc = -1;
|
||||
|
||||
switch (command) {
|
||||
case METAL_MAX_INTERRUPT_GET:
|
||||
rc = __metal_driver_sifive_global_external_interrupts0_num_interrupts(controller);
|
||||
break;
|
||||
case METAL_INDEX_INTERRUPT_GET:
|
||||
rc = 0;
|
||||
if (data) {
|
||||
idx = *(int *)data;
|
||||
rc = __metal_driver_sifive_global_external_interrupts0_interrupt_lines(controller, idx);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_global_external_interrupts0) = {
|
||||
.global0_vtable.interrupt_init = __metal_driver_sifive_global_external_interrupt_init,
|
||||
.global0_vtable.interrupt_register = __metal_driver_sifive_global_external_interrupt_register,
|
||||
.global0_vtable.interrupt_enable = __metal_driver_sifive_global_external_interrupt_enable,
|
||||
.global0_vtable.interrupt_disable = __metal_driver_sifive_global_external_interrupt_disable,
|
||||
.global0_vtable.command_request = __metal_driver_sifive_global_external_command_request,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_GPIO_BUTTONS
|
||||
|
||||
#include <string.h>
|
||||
#include <metal/drivers/riscv_cpu.h>
|
||||
#include <metal/drivers/sifive_gpio-buttons.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
int __metal_driver_button_exist (struct metal_button *button, char *label)
|
||||
{
|
||||
if (strcmp(__metal_driver_sifive_gpio_button_label(button), label) == 0) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct metal_interrupt *
|
||||
__metal_driver_button_interrupt_controller(struct metal_button *button)
|
||||
{
|
||||
return __metal_driver_sifive_gpio_button_interrupt_controller(button);
|
||||
}
|
||||
|
||||
int __metal_driver_button_get_interrupt_id(struct metal_button *button)
|
||||
{
|
||||
int irq, max_irq;
|
||||
struct metal_interrupt *irc;
|
||||
|
||||
irq = __metal_driver_sifive_gpio_button_interrupt_line(button);
|
||||
irc = __metal_driver_sifive_gpio_button_interrupt_controller(button);
|
||||
|
||||
if (irc != NULL) {
|
||||
max_irq = _metal_interrupt_command_request(irc,
|
||||
METAL_MAX_INTERRUPT_GET,
|
||||
NULL);
|
||||
|
||||
if (irq < max_irq) {
|
||||
return _metal_interrupt_command_request(irc,
|
||||
METAL_INDEX_INTERRUPT_GET,
|
||||
(void *)&irq);
|
||||
}
|
||||
}
|
||||
return METAL_INTERRUPT_ID_LCMX;
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_button) = {
|
||||
.button_vtable.button_exist = __metal_driver_button_exist,
|
||||
.button_vtable.interrupt_controller = __metal_driver_button_interrupt_controller,
|
||||
.button_vtable.get_interrupt_id = __metal_driver_button_get_interrupt_id,
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,83 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_GPIO_LEDS
|
||||
|
||||
#include <string.h>
|
||||
#include <metal/gpio.h>
|
||||
#include <metal/drivers/sifive_gpio-leds.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
int __metal_driver_led_exist (struct metal_led *led, char *label)
|
||||
{
|
||||
if (strcmp(__metal_driver_sifive_gpio_led_label(led), label) == 0) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __metal_driver_led_enable (struct metal_led *led)
|
||||
{
|
||||
int pin;
|
||||
struct metal_gpio *gpio;
|
||||
|
||||
pin = __metal_driver_sifive_gpio_led_pin(led);
|
||||
gpio = __metal_driver_sifive_gpio_led_gpio(led);
|
||||
|
||||
if (gpio != NULL) {
|
||||
/* Configure LED as output */
|
||||
metal_gpio_disable_input((struct metal_gpio *) gpio, pin);
|
||||
metal_gpio_enable_output((struct metal_gpio *) gpio, pin);
|
||||
}
|
||||
}
|
||||
|
||||
void __metal_driver_led_on (struct metal_led *led)
|
||||
{
|
||||
int pin;
|
||||
struct metal_gpio *gpio;
|
||||
|
||||
pin = __metal_driver_sifive_gpio_led_pin(led);
|
||||
gpio = __metal_driver_sifive_gpio_led_gpio(led);
|
||||
|
||||
if (gpio != NULL) {
|
||||
metal_gpio_set_pin((struct metal_gpio *) gpio, pin, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void __metal_driver_led_off (struct metal_led *led)
|
||||
{
|
||||
int pin;
|
||||
struct metal_gpio *gpio;
|
||||
|
||||
pin = __metal_driver_sifive_gpio_led_pin(led);
|
||||
gpio = __metal_driver_sifive_gpio_led_gpio(led);
|
||||
|
||||
if (gpio != NULL) {
|
||||
metal_gpio_set_pin((struct metal_gpio *) gpio, pin, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void __metal_driver_led_toggle (struct metal_led *led)
|
||||
{
|
||||
int pin;
|
||||
struct metal_gpio *gpio;
|
||||
|
||||
pin = __metal_driver_sifive_gpio_led_pin(led);
|
||||
gpio = __metal_driver_sifive_gpio_led_gpio(led);
|
||||
|
||||
if (gpio != NULL) {
|
||||
metal_gpio_toggle_pin((struct metal_gpio *) gpio, pin);
|
||||
}
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_led) = {
|
||||
.led_vtable.led_exist = __metal_driver_led_exist,
|
||||
.led_vtable.led_enable = __metal_driver_led_enable,
|
||||
.led_vtable.led_on = __metal_driver_led_on,
|
||||
.led_vtable.led_off = __metal_driver_led_off,
|
||||
.led_vtable.led_toggle = __metal_driver_led_toggle,
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,54 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_GPIO_SWITCHES
|
||||
|
||||
#include <string.h>
|
||||
#include <metal/drivers/riscv_cpu.h>
|
||||
#include <metal/drivers/sifive_gpio-switches.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
int __metal_driver_switch_exist (struct metal_switch *flip, char *label)
|
||||
{
|
||||
if (strcmp(__metal_driver_sifive_gpio_switch_label(flip), label) == 0) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct metal_interrupt *
|
||||
__metal_driver_switch_interrupt_controller(struct metal_switch *flip)
|
||||
{
|
||||
return __metal_driver_sifive_gpio_switch_interrupt_controller(flip);
|
||||
}
|
||||
|
||||
int __metal_driver_switch_get_interrupt_id(struct metal_switch *flip)
|
||||
{
|
||||
int irq, max_irq;
|
||||
struct metal_interrupt *irc;
|
||||
|
||||
irq = __metal_driver_sifive_gpio_switch_interrupt_line(flip);
|
||||
irc = __metal_driver_sifive_gpio_switch_interrupt_controller(flip);
|
||||
if (irc != NULL) {
|
||||
max_irq = _metal_interrupt_command_request(irc,
|
||||
METAL_MAX_INTERRUPT_GET,
|
||||
NULL);
|
||||
|
||||
if (irq < max_irq) {
|
||||
return _metal_interrupt_command_request(irc,
|
||||
METAL_INDEX_INTERRUPT_GET,
|
||||
(void *)&irq);
|
||||
}
|
||||
}
|
||||
return METAL_INTERRUPT_ID_LCMX;
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_switch) = {
|
||||
.switch_vtable.switch_exist = __metal_driver_switch_exist,
|
||||
.switch_vtable.interrupt_controller = __metal_driver_switch_interrupt_controller,
|
||||
.switch_vtable.get_interrupt_id = __metal_driver_switch_get_interrupt_id,
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,85 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_GPIO0
|
||||
|
||||
#include <metal/drivers/sifive_gpio0.h>
|
||||
#include <metal/io.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
int __metal_driver_sifive_gpio0_disable_input(struct metal_gpio *ggpio, long source)
|
||||
{
|
||||
long base = __metal_driver_sifive_gpio0_base(ggpio);
|
||||
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(base + METAL_SIFIVE_GPIO0_INPUT_EN)) &= ~source;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
long __metal_driver_sifive_gpio0_output(struct metal_gpio *ggpio)
|
||||
{
|
||||
long base = __metal_driver_sifive_gpio0_base(ggpio);
|
||||
|
||||
return __METAL_ACCESS_ONCE((__metal_io_u32 *)(base + METAL_SIFIVE_GPIO0_PORT));
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_gpio0_enable_output(struct metal_gpio *ggpio, long source)
|
||||
{
|
||||
long base = __metal_driver_sifive_gpio0_base(ggpio);
|
||||
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(base + METAL_SIFIVE_GPIO0_OUTPUT_EN)) |= source;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_gpio0_output_set(struct metal_gpio *ggpio, long value)
|
||||
{
|
||||
long base = __metal_driver_sifive_gpio0_base(ggpio);
|
||||
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(base + METAL_SIFIVE_GPIO0_PORT)) |= value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_gpio0_output_clear(struct metal_gpio *ggpio, long value)
|
||||
{
|
||||
long base = __metal_driver_sifive_gpio0_base(ggpio);
|
||||
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(base + METAL_SIFIVE_GPIO0_PORT)) &= ~value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_gpio0_output_toggle(struct metal_gpio *ggpio, long value)
|
||||
{
|
||||
long base = __metal_driver_sifive_gpio0_base(ggpio);
|
||||
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(base + METAL_SIFIVE_GPIO0_PORT)) =
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(base + METAL_SIFIVE_GPIO0_PORT)) ^ value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_gpio0_enable_io(struct metal_gpio *ggpio, long source, long dest)
|
||||
{
|
||||
long base = __metal_driver_sifive_gpio0_base(ggpio);
|
||||
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(base + METAL_SIFIVE_GPIO0_IOF_SEL)) &= ~source;
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(base + METAL_SIFIVE_GPIO0_IOF_EN)) |= dest;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_gpio0) = {
|
||||
.gpio.disable_input = __metal_driver_sifive_gpio0_disable_input,
|
||||
.gpio.output = __metal_driver_sifive_gpio0_output,
|
||||
.gpio.enable_output = __metal_driver_sifive_gpio0_enable_output,
|
||||
.gpio.output_set = __metal_driver_sifive_gpio0_output_set,
|
||||
.gpio.output_clear = __metal_driver_sifive_gpio0_output_clear,
|
||||
.gpio.output_toggle = __metal_driver_sifive_gpio0_output_toggle,
|
||||
.gpio.enable_io = __metal_driver_sifive_gpio0_enable_io,
|
||||
};
|
||||
|
||||
#endif /* METAL_SIFIVE_GPIO0 */
|
|
@ -0,0 +1,117 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_LOCAL_EXTERNAL_INTERRUPTS0
|
||||
|
||||
#include <metal/io.h>
|
||||
#include <metal/drivers/sifive_local-external-interrupts0.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
void __metal_driver_sifive_local_external_interrupt_init(struct metal_interrupt *controller)
|
||||
{
|
||||
struct __metal_driver_sifive_local_external_interrupts0 *local0;
|
||||
|
||||
local0 = (struct __metal_driver_sifive_local_external_interrupts0 *)(controller);
|
||||
if ( !local0->init_done ) {
|
||||
struct metal_interrupt *intc =
|
||||
__metal_driver_sifive_local_external_interrupts0_interrupt_parent(controller);
|
||||
|
||||
if (intc) {
|
||||
/* Register its interruptswith with parent controller, aka all external to default isr */
|
||||
for (int i = 0;
|
||||
i < __metal_driver_sifive_local_external_interrupts0_num_interrupts(controller);
|
||||
i++) {
|
||||
intc->vtable->interrupt_register(intc,
|
||||
__metal_driver_sifive_local_external_interrupts0_interrupt_lines(controller, i),
|
||||
NULL, controller);
|
||||
}
|
||||
local0->init_done = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_local_external_interrupt_register(struct metal_interrupt *controller,
|
||||
int id, metal_interrupt_handler_t isr,
|
||||
void *priv)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
if (id != 0) {
|
||||
struct metal_interrupt *intc =
|
||||
__metal_driver_sifive_local_external_interrupts0_interrupt_parent(controller);
|
||||
|
||||
/* Enable its interrupts with parent controller */
|
||||
if (intc) {
|
||||
rc = intc->vtable->interrupt_register(intc, id, isr, priv);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_local_external_interrupt_enable(struct metal_interrupt *controller, int id)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
if (id != 0) {
|
||||
struct metal_interrupt *intc =
|
||||
__metal_driver_sifive_local_external_interrupts0_interrupt_parent(controller);
|
||||
|
||||
/* Enable its interrupts with parent controller */
|
||||
if (intc) {
|
||||
rc = intc->vtable->interrupt_enable(intc, id);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_local_external_interrupt_disable(struct metal_interrupt *controller, int id)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
if (id != 0) {
|
||||
struct metal_interrupt *intc =
|
||||
__metal_driver_sifive_local_external_interrupts0_interrupt_parent(controller);
|
||||
|
||||
/* Enable its interrupts with parent controller */
|
||||
if (intc) {
|
||||
rc = intc->vtable->interrupt_disable(intc, id);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_local_external_command_request (struct metal_interrupt *controller,
|
||||
int command, void *data)
|
||||
{
|
||||
int idx;
|
||||
int rc = -1;
|
||||
|
||||
switch (command) {
|
||||
case METAL_MAX_INTERRUPT_GET:
|
||||
rc = __metal_driver_sifive_local_external_interrupts0_num_interrupts(controller);
|
||||
break;
|
||||
case METAL_INDEX_INTERRUPT_GET:
|
||||
rc = 0;
|
||||
if (data) {
|
||||
idx = *(int *)data;
|
||||
rc = __metal_driver_sifive_local_external_interrupts0_interrupt_lines(controller, idx);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_local_external_interrupts0) = {
|
||||
.local0_vtable.interrupt_init = __metal_driver_sifive_local_external_interrupt_init,
|
||||
.local0_vtable.interrupt_register = __metal_driver_sifive_local_external_interrupt_register,
|
||||
.local0_vtable.interrupt_enable = __metal_driver_sifive_local_external_interrupt_enable,
|
||||
.local0_vtable.interrupt_disable = __metal_driver_sifive_local_external_interrupt_disable,
|
||||
.local0_vtable.command_request = __metal_driver_sifive_local_external_command_request,
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,277 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_SPI0
|
||||
#include <metal/drivers/sifive_spi0.h>
|
||||
#include <metal/io.h>
|
||||
#include <metal/machine.h>
|
||||
#include <time.h>
|
||||
|
||||
/* Register fields */
|
||||
#define METAL_SPI_SCKDIV_MASK 0xFFF
|
||||
|
||||
#define METAL_SPI_SCKMODE_PHA_SHIFT 0
|
||||
#define METAL_SPI_SCKMODE_POL_SHIFT 1
|
||||
|
||||
#define METAL_SPI_CSMODE_MASK 3
|
||||
#define METAL_SPI_CSMODE_AUTO 0
|
||||
#define METAL_SPI_CSMODE_HOLD 2
|
||||
#define METAL_SPI_CSMODE_OFF 3
|
||||
|
||||
#define METAL_SPI_PROTO_MASK 3
|
||||
#define METAL_SPI_PROTO_SINGLE 0
|
||||
#define METAL_SPI_PROTO_DUAL 1
|
||||
#define METAL_SPI_PROTO_QUAD 2
|
||||
|
||||
#define METAL_SPI_ENDIAN_LSB 4
|
||||
|
||||
#define METAL_SPI_DISABLE_RX 8
|
||||
|
||||
#define METAL_SPI_FRAME_LEN_SHIFT 16
|
||||
#define METAL_SPI_FRAME_LEN_MASK (0xF << METAL_SPI_FRAME_LEN_SHIFT)
|
||||
|
||||
#define METAL_SPI_TXDATA_FULL (1 << 31)
|
||||
#define METAL_SPI_RXDATA_EMPTY (1 << 31)
|
||||
#define METAL_SPI_TXMARK_MASK 7
|
||||
#define METAL_SPI_TXWM 1
|
||||
#define METAL_SPI_TXRXDATA_MASK (0xFF)
|
||||
|
||||
#define METAL_SPI_INTERVAL_SHIFT 16
|
||||
|
||||
#define METAL_SPI_CONTROL_IO 0
|
||||
#define METAL_SPI_CONTROL_MAPPED 1
|
||||
|
||||
#define METAL_SPI_REG(offset) (((unsigned long)control_base + offset))
|
||||
#define METAL_SPI_REGB(offset) (__METAL_ACCESS_ONCE((__metal_io_u8 *)METAL_SPI_REG(offset)))
|
||||
#define METAL_SPI_REGW(offset) (__METAL_ACCESS_ONCE((__metal_io_u32 *)METAL_SPI_REG(offset)))
|
||||
|
||||
#define METAL_SPI_RXDATA_TIMEOUT 1
|
||||
|
||||
static int configure_spi(struct __metal_driver_sifive_spi0 *spi, struct metal_spi_config *config)
|
||||
{
|
||||
long control_base = __metal_driver_sifive_spi0_control_base((struct metal_spi *)spi);
|
||||
/* Set protocol */
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_PROTO_MASK);
|
||||
switch(config->protocol) {
|
||||
case METAL_SPI_SINGLE:
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_SINGLE;
|
||||
break;
|
||||
case METAL_SPI_DUAL:
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_DUAL;
|
||||
break;
|
||||
case METAL_SPI_QUAD:
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_QUAD;
|
||||
break;
|
||||
default:
|
||||
/* Unsupported value */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set Polarity */
|
||||
if(config->polarity) {
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) |= (1 << METAL_SPI_SCKMODE_PHA_SHIFT);
|
||||
} else {
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) &= ~(1 << METAL_SPI_SCKMODE_PHA_SHIFT);
|
||||
}
|
||||
|
||||
/* Set Phase */
|
||||
if(config->phase) {
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) |= (1 << METAL_SPI_SCKMODE_POL_SHIFT);
|
||||
} else {
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKMODE) &= ~(1 << METAL_SPI_SCKMODE_POL_SHIFT);
|
||||
}
|
||||
|
||||
/* Set Endianness */
|
||||
if(config->little_endian) {
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_ENDIAN_LSB;
|
||||
} else {
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_ENDIAN_LSB);
|
||||
}
|
||||
|
||||
/* Always populate receive FIFO */
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_DISABLE_RX);
|
||||
|
||||
/* Set CS Active */
|
||||
if(config->cs_active_high) {
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSDEF) = 0;
|
||||
} else {
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSDEF) = 1;
|
||||
}
|
||||
|
||||
/* Set frame length */
|
||||
if((METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) & METAL_SPI_FRAME_LEN_MASK) != (8 << METAL_SPI_FRAME_LEN_SHIFT)) {
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_FRAME_LEN_MASK);
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= (8 << METAL_SPI_FRAME_LEN_SHIFT);
|
||||
}
|
||||
|
||||
/* Set CS line */
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSID) = config->csid;
|
||||
|
||||
/* Toggle off memory-mapped SPI flash mode, toggle on programmable IO mode
|
||||
* It seems that with this line uncommented, the debugger cannot have access
|
||||
* to the chip at all because it assumes the chip is in memory-mapped mode.
|
||||
* I have to compile the code with this line commented and launch gdb,
|
||||
* reset cores, reset $pc, set *((int *) 0x20004060) = 0, (set the flash
|
||||
* interface control register to programmable I/O mode) and then continue
|
||||
* Alternative, comment out the "flash" line in openocd.cfg */
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_FCTRL) = METAL_SPI_CONTROL_IO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_spi0_transfer(struct metal_spi *gspi,
|
||||
struct metal_spi_config *config,
|
||||
size_t len,
|
||||
char *tx_buf,
|
||||
char *rx_buf)
|
||||
{
|
||||
struct __metal_driver_sifive_spi0 *spi = (void *)gspi;
|
||||
long control_base = __metal_driver_sifive_spi0_control_base(gspi);
|
||||
int rc = 0;
|
||||
|
||||
rc = configure_spi(spi, config);
|
||||
if(rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Hold the chip select line for all len transferred */
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &= ~(METAL_SPI_CSMODE_MASK);
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) |= METAL_SPI_CSMODE_HOLD;
|
||||
|
||||
unsigned long rxdata;
|
||||
|
||||
/* Declare time_t variables to break out of infinite while loop */
|
||||
time_t endwait;
|
||||
|
||||
for(int i = 0; i < len; i++) {
|
||||
/* Master send bytes to the slave */
|
||||
|
||||
/* Wait for TXFIFO to not be full */
|
||||
while (METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXDATA) & METAL_SPI_TXDATA_FULL);
|
||||
|
||||
/* Transfer byte by modifying the least significant byte in the TXDATA register */
|
||||
if (tx_buf) {
|
||||
METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = tx_buf[i];
|
||||
} else {
|
||||
/* Transfer a 0 byte if the sending buffer is NULL */
|
||||
METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = 0;
|
||||
}
|
||||
|
||||
/* Master receives bytes from the RX FIFO */
|
||||
|
||||
/* Wait for RXFIFO to not be empty, but break the nested loops if timeout
|
||||
* this timeout method needs refining, preferably taking into account
|
||||
* the device specs */
|
||||
endwait = time(NULL) + METAL_SPI_RXDATA_TIMEOUT;
|
||||
|
||||
while ((rxdata = METAL_SPI_REGW(METAL_SIFIVE_SPI0_RXDATA)) & METAL_SPI_RXDATA_EMPTY) {
|
||||
if (time(NULL) > endwait) {
|
||||
/* If timeout, deassert the CS */
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &= ~(METAL_SPI_CSMODE_MASK);
|
||||
|
||||
/* If timeout, return error code 1 immediately */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Only store the dequeued byte if the receive_buffer is not NULL */
|
||||
if (rx_buf) {
|
||||
rx_buf[i] = (char) (rxdata & METAL_SPI_TXRXDATA_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
/* On the last byte, set CSMODE to auto so that the chip select transitions back to high
|
||||
* The reason that CS pin is not deasserted after transmitting out the byte buffer is timing.
|
||||
* The code on the host side likely executes faster than the ability of FIFO to send out bytes.
|
||||
* After the host iterates through the array, fifo is likely not cleared yet. If host deasserts
|
||||
* the CS pin immediately, the following bytes in the output FIFO will not be sent consecutively.
|
||||
* There needs to be a better way to handle this. */
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &= ~(METAL_SPI_CSMODE_MASK);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_spi0_get_baud_rate(struct metal_spi *gspi)
|
||||
{
|
||||
struct __metal_driver_sifive_spi0 *spi = (void *)gspi;
|
||||
return spi->baud_rate;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_spi0_set_baud_rate(struct metal_spi *gspi, int baud_rate)
|
||||
{
|
||||
long control_base = __metal_driver_sifive_spi0_control_base(gspi);
|
||||
struct metal_clock *clock = __metal_driver_sifive_spi0_clock(gspi);
|
||||
struct __metal_driver_sifive_spi0 *spi = (void *)gspi;
|
||||
|
||||
spi->baud_rate = baud_rate;
|
||||
|
||||
if (clock != NULL) {
|
||||
long clock_rate = clock->vtable->get_rate_hz(clock);
|
||||
|
||||
/* Calculate divider */
|
||||
long div = (clock_rate / (2 * baud_rate)) - 1;
|
||||
|
||||
if(div > METAL_SPI_SCKDIV_MASK) {
|
||||
/* The requested baud rate is lower than we can support at
|
||||
* the current clock rate */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set divider */
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKDIV) &= ~METAL_SPI_SCKDIV_MASK;
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_SCKDIV) |= (div & METAL_SPI_SCKDIV_MASK);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pre_rate_change_callback(void *priv)
|
||||
{
|
||||
long control_base = __metal_driver_sifive_spi0_control_base((struct metal_spi *)priv);
|
||||
|
||||
/* Detect when the TXDATA is empty by setting the transmit watermark count
|
||||
* to zero and waiting until an interrupt is pending */
|
||||
METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXMARK) &= ~(METAL_SPI_TXMARK_MASK);
|
||||
|
||||
while((METAL_SPI_REGW(METAL_SIFIVE_SPI0_IP) & METAL_SPI_TXWM) == 0) ;
|
||||
}
|
||||
|
||||
static void post_rate_change_callback(void *priv)
|
||||
{
|
||||
struct __metal_driver_sifive_spi0 *spi = priv;
|
||||
metal_spi_set_baud_rate(&spi->spi, spi->baud_rate);
|
||||
}
|
||||
|
||||
void __metal_driver_sifive_spi0_init(struct metal_spi *gspi, int baud_rate)
|
||||
{
|
||||
struct __metal_driver_sifive_spi0 *spi = (void *)(gspi);
|
||||
struct metal_clock *clock = __metal_driver_sifive_spi0_clock(gspi);
|
||||
struct __metal_driver_sifive_gpio0 *pinmux = __metal_driver_sifive_spi0_pinmux(gspi);
|
||||
|
||||
if(clock != NULL) {
|
||||
metal_clock_register_pre_rate_change_callback(clock, &pre_rate_change_callback, spi);
|
||||
metal_clock_register_post_rate_change_callback(clock, &post_rate_change_callback, spi);
|
||||
}
|
||||
|
||||
metal_spi_set_baud_rate(&(spi->spi), baud_rate);
|
||||
|
||||
if (pinmux != NULL) {
|
||||
long pinmux_output_selector = __metal_driver_sifive_spi0_pinmux_output_selector(gspi);
|
||||
long pinmux_source_selector = __metal_driver_sifive_spi0_pinmux_source_selector(gspi);
|
||||
pinmux->gpio.vtable->enable_io(
|
||||
(struct metal_gpio *) pinmux,
|
||||
pinmux_output_selector,
|
||||
pinmux_source_selector
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_spi0) = {
|
||||
.spi.init = __metal_driver_sifive_spi0_init,
|
||||
.spi.transfer = __metal_driver_sifive_spi0_transfer,
|
||||
.spi.get_baud_rate = __metal_driver_sifive_spi0_get_baud_rate,
|
||||
.spi.set_baud_rate = __metal_driver_sifive_spi0_set_baud_rate,
|
||||
};
|
||||
#endif /* METAL_SIFIVE_SPI0 */
|
|
@ -0,0 +1,26 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_TEST0
|
||||
|
||||
#include <metal/drivers/sifive_test0.h>
|
||||
#include <metal/io.h>
|
||||
#include <stdint.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
void __metal_driver_sifive_test0_exit(const struct __metal_shutdown *sd, int code) __attribute__((noreturn));
|
||||
void __metal_driver_sifive_test0_exit(const struct __metal_shutdown *sd, int code)
|
||||
{
|
||||
long base = __metal_driver_sifive_test0_base();
|
||||
uint32_t out = (code << 16) + (code == 0 ? 0x5555 : 0x3333);
|
||||
while (1) {
|
||||
__METAL_ACCESS_ONCE((__metal_io_u32 *)(base + METAL_SIFIVE_TEST0_FINISHER_OFFSET)) = out;
|
||||
}
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_test0) = {
|
||||
.shutdown.exit = &__metal_driver_sifive_test0_exit,
|
||||
};
|
||||
#endif /* METAL_SIFIVE_TEST0 */
|
|
@ -0,0 +1,151 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine/platform.h>
|
||||
|
||||
#ifdef METAL_SIFIVE_UART0
|
||||
|
||||
#include <metal/drivers/sifive_uart0.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
/* TXDATA Fields */
|
||||
#define UART_TXEN (1 << 0)
|
||||
#define UART_TXFULL (1 << 31)
|
||||
|
||||
/* RXDATA Fields */
|
||||
#define UART_RXEN (1 << 0)
|
||||
#define UART_RXEMPTY (1 << 31)
|
||||
|
||||
/* TXCTRL Fields */
|
||||
#define UART_NSTOP (1 << 1)
|
||||
#define UART_TXCNT(count) ((0x7 & count) << 16)
|
||||
|
||||
/* IP Fields */
|
||||
#define UART_TXWM (1 << 0)
|
||||
|
||||
#define UART_REG(offset) (((unsigned long)control_base + offset))
|
||||
#define UART_REGB(offset) (__METAL_ACCESS_ONCE((__metal_io_u8 *)UART_REG(offset)))
|
||||
#define UART_REGW(offset) (__METAL_ACCESS_ONCE((__metal_io_u32 *)UART_REG(offset)))
|
||||
|
||||
struct metal_interrupt *
|
||||
__metal_driver_sifive_uart0_interrupt_controller(struct metal_uart *uart)
|
||||
{
|
||||
return __metal_driver_sifive_uart0_interrupt_parent(uart);
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_uart0_get_interrupt_id(struct metal_uart *uart)
|
||||
{
|
||||
return (__metal_driver_sifive_uart0_interrupt_line(uart) + METAL_INTERRUPT_ID_GL0);
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_uart0_putc(struct metal_uart *uart, unsigned char c)
|
||||
{
|
||||
long control_base = __metal_driver_sifive_uart0_control_base(uart);
|
||||
|
||||
while ((UART_REGW(METAL_SIFIVE_UART0_TXDATA) & UART_TXFULL) != 0) { }
|
||||
UART_REGW(METAL_SIFIVE_UART0_TXDATA) = c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_uart0_getc(struct metal_uart *uart, unsigned char *c)
|
||||
{
|
||||
uint32_t ch = UART_RXEMPTY;
|
||||
long control_base = __metal_driver_sifive_uart0_control_base(uart);
|
||||
|
||||
while (ch & UART_RXEMPTY) {
|
||||
ch = UART_REGW(METAL_SIFIVE_UART0_RXDATA);
|
||||
}
|
||||
*c = ch & 0xff;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_uart0_get_baud_rate(struct metal_uart *guart)
|
||||
{
|
||||
struct __metal_driver_sifive_uart0 *uart = (void *)guart;
|
||||
return uart->baud_rate;
|
||||
}
|
||||
|
||||
int __metal_driver_sifive_uart0_set_baud_rate(struct metal_uart *guart, int baud_rate)
|
||||
{
|
||||
struct __metal_driver_sifive_uart0 *uart = (void *)guart;
|
||||
long control_base = __metal_driver_sifive_uart0_control_base(guart);
|
||||
struct metal_clock *clock = __metal_driver_sifive_uart0_clock(guart);
|
||||
|
||||
uart->baud_rate = baud_rate;
|
||||
|
||||
if (clock != NULL) {
|
||||
long clock_rate = clock->vtable->get_rate_hz(clock);
|
||||
UART_REGW(METAL_SIFIVE_UART0_DIV) = clock_rate / baud_rate - 1;
|
||||
UART_REGW(METAL_SIFIVE_UART0_TXCTRL) |= UART_TXEN;
|
||||
UART_REGW(METAL_SIFIVE_UART0_RXCTRL) |= UART_RXEN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pre_rate_change_callback(void *priv)
|
||||
{
|
||||
struct __metal_driver_sifive_uart0 *uart = priv;
|
||||
long control_base = __metal_driver_sifive_uart0_control_base((struct metal_uart *)priv);
|
||||
struct metal_clock *clock = __metal_driver_sifive_uart0_clock((struct metal_uart *)priv);
|
||||
|
||||
/* Detect when the TXDATA is empty by setting the transmit watermark count
|
||||
* to one and waiting until an interrupt is pending */
|
||||
|
||||
UART_REGW(METAL_SIFIVE_UART0_TXCTRL) &= ~(UART_TXCNT(0x7));
|
||||
UART_REGW(METAL_SIFIVE_UART0_TXCTRL) |= UART_TXCNT(1);
|
||||
|
||||
while((UART_REGW(METAL_SIFIVE_UART0_IP) & UART_TXWM) == 0) ;
|
||||
|
||||
/* When the TXDATA clears, the UART is still shifting out the last byte.
|
||||
* Calculate the time we must drain to finish transmitting and then wait
|
||||
* that long. */
|
||||
|
||||
long bits_per_symbol = (UART_REGW(METAL_SIFIVE_UART0_TXCTRL) & (1 << 1)) ? 9 : 10;
|
||||
long clk_freq = clock->vtable->get_rate_hz(clock);
|
||||
long cycles_to_wait = bits_per_symbol * clk_freq / uart->baud_rate;
|
||||
|
||||
for(volatile long x = 0; x < cycles_to_wait; x++)
|
||||
asm("nop");
|
||||
}
|
||||
|
||||
static void post_rate_change_callback(void *priv)
|
||||
{
|
||||
struct __metal_driver_sifive_uart0 *uart = priv;
|
||||
metal_uart_set_baud_rate(&uart->uart, uart->baud_rate);
|
||||
}
|
||||
|
||||
void __metal_driver_sifive_uart0_init(struct metal_uart *guart, int baud_rate)
|
||||
{
|
||||
struct __metal_driver_sifive_uart0 *uart = (void *)(guart);
|
||||
struct metal_clock *clock = __metal_driver_sifive_uart0_clock(guart);
|
||||
struct __metal_driver_sifive_gpio0 *pinmux = __metal_driver_sifive_uart0_pinmux(guart);
|
||||
|
||||
if(clock != NULL) {
|
||||
metal_clock_register_pre_rate_change_callback(clock, &pre_rate_change_callback, guart);
|
||||
metal_clock_register_post_rate_change_callback(clock, &post_rate_change_callback, guart);
|
||||
}
|
||||
|
||||
metal_uart_set_baud_rate(&(uart->uart), baud_rate);
|
||||
|
||||
if (pinmux != NULL) {
|
||||
long pinmux_output_selector = __metal_driver_sifive_uart0_pinmux_output_selector(guart);
|
||||
long pinmux_source_selector = __metal_driver_sifive_uart0_pinmux_source_selector(guart);
|
||||
pinmux->gpio.vtable->enable_io(
|
||||
(struct metal_gpio *) pinmux,
|
||||
pinmux_output_selector,
|
||||
pinmux_source_selector
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_uart0) = {
|
||||
.uart.init = __metal_driver_sifive_uart0_init,
|
||||
.uart.putc = __metal_driver_sifive_uart0_putc,
|
||||
.uart.getc = __metal_driver_sifive_uart0_getc,
|
||||
.uart.get_baud_rate = __metal_driver_sifive_uart0_get_baud_rate,
|
||||
.uart.set_baud_rate = __metal_driver_sifive_uart0_set_baud_rate,
|
||||
.uart.controller_interrupt = __metal_driver_sifive_uart0_interrupt_controller,
|
||||
.uart.get_interrupt_id = __metal_driver_sifive_uart0_get_interrupt_id,
|
||||
};
|
||||
|
||||
#endif /* METAL_SIFIVE_UART0 */
|
|
@ -0,0 +1,107 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
/* This code executes before _start, which is contained inside the C library.
|
||||
* In embedded systems we want to ensure that _enter, which contains the first
|
||||
* code to be executed, can be loaded at a specific address. To enable this
|
||||
* feature we provide the '.text.metal.init.enter' section, which is
|
||||
* defined to have the first address being where execution should start. */
|
||||
.section .text.metal.init.enter
|
||||
.global _enter
|
||||
|
||||
_enter:
|
||||
.cfi_startproc
|
||||
|
||||
/* Inform the debugger that there is nowhere to backtrace past _enter. */
|
||||
.cfi_undefined ra
|
||||
|
||||
/* The absolute first thing that must happen is configuring the global
|
||||
* pointer register, which must be done with relaxation disabled because
|
||||
* it's not valid to obtain the address of any symbol without GP
|
||||
* configured. The C environment might go ahead and do this again, but
|
||||
* that's safe as it's a fixed register. */
|
||||
.option push
|
||||
.option norelax
|
||||
la gp, __global_pointer$
|
||||
.option pop
|
||||
|
||||
/* Set up a simple trap vector to catch anything that goes wrong early in
|
||||
* the boot process. */
|
||||
la t0, early_trap_vector
|
||||
csrw mtvec, t0
|
||||
/* enable chicken bit if core is bullet series*/
|
||||
la t0, __metal_chicken_bit
|
||||
beqz t0, 1f
|
||||
csrwi 0x7C1, 0
|
||||
1:
|
||||
|
||||
/* There may be pre-initialization routines inside the MBI code that run in
|
||||
* C, so here we set up a C environment. First we set up a stack pointer,
|
||||
* which is left as a weak reference in order to allow initialization
|
||||
* routines that do not need a stack to be set up to transparently be
|
||||
* called. */
|
||||
.weak __metal_stack_pointer
|
||||
la sp, __metal_stack_pointer
|
||||
|
||||
/* Check for an initialization routine and call it if one exists, otherwise
|
||||
* just skip over the call entirely. Note that __metal_initialize isn't
|
||||
* actually a full C function, as it doesn't end up with the .bss or .data
|
||||
* segments having been initialized. This is done to avoid putting a
|
||||
* burden on systems that can be initialized without having a C environment
|
||||
* set up. */
|
||||
.weak __metal_before_start
|
||||
la ra, __metal_before_start
|
||||
beqz ra, 1f
|
||||
jalr ra
|
||||
1:
|
||||
|
||||
/* At this point we can enter the C runtime's startup file. The arguments
|
||||
* to this function are designed to match those provided to the SEE, just
|
||||
* so we don't have to write another ABI. */
|
||||
csrr a0, mhartid
|
||||
li a1, 0
|
||||
li a2, 0
|
||||
call _start
|
||||
|
||||
/* If we've made it back here then there's probably something wrong. We
|
||||
* allow the METAL to register a handler here. */
|
||||
.weak __metal_after_main
|
||||
la ra, __metal_after_main
|
||||
beqz ra, 1f
|
||||
jalr ra
|
||||
1:
|
||||
|
||||
/* If that handler returns then there's not a whole lot we can do. Just
|
||||
* try to make some noise. */
|
||||
la t0, 1f
|
||||
csrw mtvec, t0
|
||||
1:
|
||||
lw t1, 0(x0)
|
||||
j 1b
|
||||
|
||||
.cfi_endproc
|
||||
|
||||
/* For sanity's sake we set up an early trap vector that just does nothing. If
|
||||
* you end up here then there's a bug in the early boot code somewhere. */
|
||||
.section .text.metal.init.trapvec
|
||||
.align 2
|
||||
early_trap_vector:
|
||||
.cfi_startproc
|
||||
csrr t0, mcause
|
||||
csrr t1, mepc
|
||||
csrr t2, mtval
|
||||
j early_trap_vector
|
||||
.cfi_endproc
|
||||
|
||||
/* The GCC port might not emit a __register_frame_info symbol, which eventually
|
||||
* results in a weak undefined reference that eventually causes crash when it
|
||||
* is dereference early in boot. We really shouldn't need to put this here,
|
||||
* but to deal with what I think is probably a bug in the linker script I'm
|
||||
* going to leave this in for now. At least it's fairly cheap :) */
|
||||
.weak __register_frame_info
|
||||
.global __register_frame_info
|
||||
.section .text.metal.init.__register_frame_info
|
||||
__register_frame_info:
|
||||
.cfi_startproc
|
||||
ret
|
||||
.cfi_endproc
|
|
@ -0,0 +1,22 @@
|
|||
/* Copyright 2019 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine.h>
|
||||
#include <metal/gpio.h>
|
||||
|
||||
extern inline int metal_gpio_disable_input(struct metal_gpio *gpio, int pin);
|
||||
extern inline int metal_gpio_enable_output(struct metal_gpio *gpio, int pin);
|
||||
extern inline int metal_gpio_set_pin(struct metal_gpio *, int pin, int value);
|
||||
extern inline int metal_gpio_get_pin(struct metal_gpio *, int pin);
|
||||
extern inline int metal_gpio_clear_pin(struct metal_gpio *, int pin);
|
||||
extern inline int metal_gpio_toggle_pin(struct metal_gpio *, int pin);
|
||||
extern inline int metal_gpio_enable_pinmux(struct metal_gpio *, int pin, int io_function);
|
||||
|
||||
struct metal_gpio *metal_gpio_get_device(int device_num)
|
||||
{
|
||||
if(device_num > __MEE_DT_MAX_GPIOS) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (struct metal_gpio *) __metal_gpio_table[device_num];
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/interrupt.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
extern inline void metal_interrupt_init(struct metal_interrupt *controller);
|
||||
|
||||
extern inline int metal_interrupt_register_handler(struct metal_interrupt *controller,
|
||||
int id,
|
||||
metal_interrupt_handler_t handler,
|
||||
void *priv);
|
||||
|
||||
extern inline int metal_interrupt_enable(struct metal_interrupt *controller, int id);
|
||||
|
||||
extern inline int metal_interrupt_disable(struct metal_interrupt *controller, int id);
|
||||
|
||||
extern inline int metal_interrupt_vector_enable(struct metal_interrupt *controller,
|
||||
int id, metal_vector_mode mode);
|
||||
|
||||
extern inline int metal_interrupt_vector_disable(struct metal_interrupt *controller, int id);
|
||||
|
||||
extern inline int _metal_interrupt_command_request(struct metal_interrupt *controller,
|
||||
int cmd, void *data);
|
|
@ -0,0 +1,38 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <string.h>
|
||||
#include <metal/led.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
struct metal_led* metal_led_get_rgb (char *label, char *color)
|
||||
{
|
||||
int i;
|
||||
struct metal_led *led;
|
||||
char led_label[100];
|
||||
|
||||
if ((__METAL_DT_MAX_LEDS == 0) ||
|
||||
(label == NULL) || (color == NULL)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strcpy(led_label, label);
|
||||
strcat(led_label, color);
|
||||
for (i = 0; i < __METAL_DT_MAX_LEDS; i++) {
|
||||
led = (struct metal_led*)__metal_led_table[i];
|
||||
if (led->vtable->led_exist(led, led_label)) {
|
||||
return led;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct metal_led* metal_led_get (char *label)
|
||||
{
|
||||
return metal_led_get_rgb(label, "");
|
||||
}
|
||||
|
||||
extern inline void metal_led_enable(struct metal_led *led);
|
||||
extern inline void metal_led_on(struct metal_led *led);
|
||||
extern inline void metal_led_off(struct metal_led *led);
|
||||
extern inline void metal_led_toggle(struct metal_led *led);
|
|
@ -0,0 +1,8 @@
|
|||
/* Copyright 2019 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/lock.h>
|
||||
|
||||
extern inline int metal_lock_init(struct metal_lock *lock);
|
||||
extern inline int metal_lock_take(struct metal_lock *lock);
|
||||
extern inline int metal_lock_give(struct metal_lock *lock);
|
|
@ -0,0 +1,26 @@
|
|||
/* Copyright 2019 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine.h>
|
||||
#include <metal/memory.h>
|
||||
|
||||
struct metal_memory *metal_get_memory_from_address(const uintptr_t address) {
|
||||
for(int i = 0; i < __METAL_DT_MAX_MEMORIES; i++) {
|
||||
struct metal_memory *mem = __metal_memory_table[i];
|
||||
|
||||
uintptr_t lower_bound = metal_memory_get_base_address(mem);
|
||||
uintptr_t upper_bound = lower_bound + metal_memory_get_size(mem);
|
||||
|
||||
if((address >= lower_bound) && (address < upper_bound)) {
|
||||
return mem;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern inline uintptr_t metal_memory_get_base_address(const struct metal_memory *memory);
|
||||
extern inline size_t metal_memory_get_size(const struct metal_memory *memory);
|
||||
extern inline int metal_memory_supports_atomics(const struct metal_memory *memory);
|
||||
extern inline int metal_memory_is_cachable(const struct metal_memory *memory);
|
||||
|
|
@ -0,0 +1,580 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine.h>
|
||||
#include <metal/pmp.h>
|
||||
#include <metal/cpu.h>
|
||||
|
||||
#define CONFIG_TO_INT(_config) (*((size_t *) &(_config)))
|
||||
#define INT_TO_CONFIG(_int) (*((struct metal_pmp_config *) &(_int)))
|
||||
|
||||
struct metal_pmp *metal_pmp_get_device(void)
|
||||
{
|
||||
#ifdef __METAL_DT_PMP_HANDLE
|
||||
return __METAL_DT_PMP_HANDLE;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* This function calculates the minimum granularity from the address
|
||||
* that pmpaddr takes on after writing all ones to pmpaddr when pmpcfg = 0.
|
||||
*
|
||||
* Detect the address granularity based on the position of the
|
||||
* least-significant 1 set in the address.
|
||||
*
|
||||
* For example, if the value read from pmpaddr is 0x3ffffc00, the
|
||||
* least-significant set bit is in bit 10 (counting from 0), resulting
|
||||
* in a detected granularity of 2^(10 + 2) = 4096.
|
||||
*/
|
||||
static uintptr_t _get_detected_granularity(uintptr_t address) {
|
||||
if(address == 0) {
|
||||
return (uintptr_t) -1;
|
||||
}
|
||||
|
||||
/* Get the index of the least significant set bit */
|
||||
int index = 0;
|
||||
while(((address >> index) & 0x1) == 0) {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
/* The granularity is equal to 2^(index + 2) bytes */
|
||||
return (1 << (index + 2));
|
||||
}
|
||||
|
||||
/* This function calculates the granularity requested by the user's provided
|
||||
* value for pmpaddr.
|
||||
*
|
||||
* Calculate the requested granularity based on the position of the
|
||||
* least-significant unset bit.
|
||||
*
|
||||
* For example, if the requested address is 0x20009ff, the least-significant
|
||||
* unset bit is at index 9 (counting from 0), resulting in a requested
|
||||
* granularity of 2^(9 + 3) = 4096.
|
||||
*/
|
||||
static uintptr_t _get_pmpaddr_granularity(uintptr_t address) {
|
||||
/* Get the index of the least significant unset bit */
|
||||
int index = 0;
|
||||
while(((address >> index) & 0x1) == 1) {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
/* The granularity is equal to 2^(index + 3) bytes */
|
||||
return (1 << (index + 3));
|
||||
}
|
||||
|
||||
/* Get the number of pmp regions for the current hart */
|
||||
static int _pmp_regions() {
|
||||
struct metal_cpu *current_cpu = metal_cpu_get(metal_cpu_get_current_hartid());
|
||||
|
||||
return __metal_driver_cpu_num_pmp_regions(current_cpu);
|
||||
}
|
||||
|
||||
|
||||
void metal_pmp_init(struct metal_pmp *pmp) {
|
||||
if(!pmp) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct metal_pmp_config init_config = {
|
||||
.L = METAL_PMP_UNLOCKED,
|
||||
.A = METAL_PMP_OFF,
|
||||
.X = 0,
|
||||
.W = 0,
|
||||
.R = 0,
|
||||
};
|
||||
|
||||
for(unsigned int i = 0; i < _pmp_regions(); i++) {
|
||||
metal_pmp_set_region(pmp, i, init_config, 0);
|
||||
}
|
||||
|
||||
/* Detect the region granularity by writing all 1s to pmpaddr0 while
|
||||
* pmpcfg0 = 0. */
|
||||
if(metal_pmp_set_address(pmp, 0, -1) != 0) {
|
||||
/* Failed to detect granularity */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Calculate the granularity based on the value that pmpaddr0 takes on */
|
||||
pmp->_granularity[metal_cpu_get_current_hartid()] = _get_detected_granularity(metal_pmp_get_address(pmp, 0));
|
||||
|
||||
/* Clear pmpaddr0 */
|
||||
metal_pmp_set_address(pmp, 0, 0);
|
||||
}
|
||||
|
||||
int metal_pmp_set_region(struct metal_pmp *pmp,
|
||||
unsigned int region,
|
||||
struct metal_pmp_config config,
|
||||
size_t address)
|
||||
{
|
||||
struct metal_pmp_config old_config;
|
||||
size_t old_address;
|
||||
size_t cfgmask;
|
||||
size_t pmpcfg;
|
||||
int rc = 0;
|
||||
|
||||
if(!pmp) {
|
||||
/* Device handle cannot be NULL */
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(region > _pmp_regions()) {
|
||||
/* Region outside of supported range */
|
||||
return 2;
|
||||
}
|
||||
|
||||
if(config.A == METAL_PMP_NA4 && pmp->_granularity[metal_cpu_get_current_hartid()] > 4) {
|
||||
/* The requested granularity is too small */
|
||||
return 3;
|
||||
}
|
||||
|
||||
if(config.A == METAL_PMP_NAPOT &&
|
||||
pmp->_granularity[metal_cpu_get_current_hartid()] > _get_pmpaddr_granularity(address))
|
||||
{
|
||||
/* The requested granularity is too small */
|
||||
return 3;
|
||||
}
|
||||
|
||||
rc = metal_pmp_get_region(pmp, region, &old_config, &old_address);
|
||||
if(rc) {
|
||||
/* Error reading region */
|
||||
return rc;
|
||||
}
|
||||
|
||||
if(old_config.L == METAL_PMP_LOCKED) {
|
||||
/* Cannot modify locked region */
|
||||
return 4;
|
||||
}
|
||||
|
||||
/* Update the address first, because if the region is being locked we won't
|
||||
* be able to modify it after we set the config */
|
||||
if(old_address != address) {
|
||||
switch(region) {
|
||||
case 0:
|
||||
asm("csrw pmpaddr0, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 1:
|
||||
asm("csrw pmpaddr1, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 2:
|
||||
asm("csrw pmpaddr2, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 3:
|
||||
asm("csrw pmpaddr3, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 4:
|
||||
asm("csrw pmpaddr4, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 5:
|
||||
asm("csrw pmpaddr5, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 6:
|
||||
asm("csrw pmpaddr6, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 7:
|
||||
asm("csrw pmpaddr7, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 8:
|
||||
asm("csrw pmpaddr8, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 9:
|
||||
asm("csrw pmpaddr9, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 10:
|
||||
asm("csrw pmpaddr10, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 11:
|
||||
asm("csrw pmpaddr11, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 12:
|
||||
asm("csrw pmpaddr12, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 13:
|
||||
asm("csrw pmpaddr13, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 14:
|
||||
asm("csrw pmpaddr14, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
case 15:
|
||||
asm("csrw pmpaddr15, %[addr]"
|
||||
:: [addr] "r" (address) :);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if __riscv_xlen==32
|
||||
if(CONFIG_TO_INT(old_config) != CONFIG_TO_INT(config)) {
|
||||
/* Mask to clear old pmpcfg */
|
||||
cfgmask = (0xFF << (8 * (region % 4)) );
|
||||
pmpcfg = (CONFIG_TO_INT(config) << (8 * (region % 4)) );
|
||||
|
||||
switch(region / 4) {
|
||||
case 0:
|
||||
asm("csrc pmpcfg0, %[mask]"
|
||||
:: [mask] "r" (cfgmask) :);
|
||||
|
||||
asm("csrs pmpcfg0, %[cfg]"
|
||||
:: [cfg] "r" (pmpcfg) :);
|
||||
break;
|
||||
case 1:
|
||||
asm("csrc pmpcfg1, %[mask]"
|
||||
:: [mask] "r" (cfgmask) :);
|
||||
|
||||
asm("csrs pmpcfg1, %[cfg]"
|
||||
:: [cfg] "r" (pmpcfg) :);
|
||||
break;
|
||||
case 2:
|
||||
asm("csrc pmpcfg2, %[mask]"
|
||||
:: [mask] "r" (cfgmask) :);
|
||||
|
||||
asm("csrs pmpcfg2, %[cfg]"
|
||||
:: [cfg] "r" (pmpcfg) :);
|
||||
break;
|
||||
case 3:
|
||||
asm("csrc pmpcfg3, %[mask]"
|
||||
:: [mask] "r" (cfgmask) :);
|
||||
|
||||
asm("csrs pmpcfg3, %[cfg]"
|
||||
:: [cfg] "r" (pmpcfg) :);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#elif __riscv_xlen==64
|
||||
if(CONFIG_TO_INT(old_config) != CONFIG_TO_INT(config)) {
|
||||
/* Mask to clear old pmpcfg */
|
||||
cfgmask = (0xFF << (8 * (region % 8)) );
|
||||
pmpcfg = (CONFIG_TO_INT(config) << (8 * (region % 8)) );
|
||||
|
||||
switch(region / 8) {
|
||||
case 0:
|
||||
asm("csrc pmpcfg0, %[mask]"
|
||||
:: [mask] "r" (cfgmask) :);
|
||||
|
||||
asm("csrs pmpcfg0, %[cfg]"
|
||||
:: [cfg] "r" (pmpcfg) :);
|
||||
break;
|
||||
case 1:
|
||||
asm("csrc pmpcfg2, %[mask]"
|
||||
:: [mask] "r" (cfgmask) :);
|
||||
|
||||
asm("csrs pmpcfg2, %[cfg]"
|
||||
:: [cfg] "r" (pmpcfg) :);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
#error XLEN is not set to supported value for PMP driver
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int metal_pmp_get_region(struct metal_pmp *pmp,
|
||||
unsigned int region,
|
||||
struct metal_pmp_config *config,
|
||||
size_t *address)
|
||||
{
|
||||
size_t pmpcfg = 0;
|
||||
|
||||
if(!pmp || !config || !address) {
|
||||
/* NULL pointers are invalid arguments */
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(region > _pmp_regions()) {
|
||||
/* Region outside of supported range */
|
||||
return 2;
|
||||
}
|
||||
|
||||
#if __riscv_xlen==32
|
||||
switch(region / 4) {
|
||||
case 0:
|
||||
asm("csrr %[cfg], pmpcfg0"
|
||||
: [cfg] "=r" (pmpcfg) ::);
|
||||
break;
|
||||
case 1:
|
||||
asm("csrr %[cfg], pmpcfg1"
|
||||
: [cfg] "=r" (pmpcfg) ::);
|
||||
break;
|
||||
case 2:
|
||||
asm("csrr %[cfg], pmpcfg2"
|
||||
: [cfg] "=r" (pmpcfg) ::);
|
||||
break;
|
||||
case 3:
|
||||
asm("csrr %[cfg], pmpcfg3"
|
||||
: [cfg] "=r" (pmpcfg) ::);
|
||||
break;
|
||||
}
|
||||
|
||||
pmpcfg = (0xFF & (pmpcfg >> (8 * (region % 4)) ) );
|
||||
|
||||
#elif __riscv_xlen==64
|
||||
switch(region / 8) {
|
||||
case 0:
|
||||
asm("csrr %[cfg], pmpcfg0"
|
||||
: [cfg] "=r" (pmpcfg) ::);
|
||||
break;
|
||||
case 1:
|
||||
asm("csrr %[cfg], pmpcfg2"
|
||||
: [cfg] "=r" (pmpcfg) ::);
|
||||
break;
|
||||
}
|
||||
|
||||
pmpcfg = (0xFF & (pmpcfg >> (8 * (region % 8)) ) );
|
||||
|
||||
#else
|
||||
#error XLEN is not set to supported value for PMP driver
|
||||
#endif
|
||||
|
||||
*config = INT_TO_CONFIG(pmpcfg);
|
||||
|
||||
switch(region) {
|
||||
case 0:
|
||||
asm("csrr %[addr], pmpaddr0"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 1:
|
||||
asm("csrr %[addr], pmpaddr1"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 2:
|
||||
asm("csrr %[addr], pmpaddr2"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 3:
|
||||
asm("csrr %[addr], pmpaddr3"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 4:
|
||||
asm("csrr %[addr], pmpaddr4"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 5:
|
||||
asm("csrr %[addr], pmpaddr5"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 6:
|
||||
asm("csrr %[addr], pmpaddr6"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 7:
|
||||
asm("csrr %[addr], pmpaddr7"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 8:
|
||||
asm("csrr %[addr], pmpaddr8"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 9:
|
||||
asm("csrr %[addr], pmpaddr9"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 10:
|
||||
asm("csrr %[addr], pmpaddr10"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 11:
|
||||
asm("csrr %[addr], pmpaddr11"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 12:
|
||||
asm("csrr %[addr], pmpaddr12"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 13:
|
||||
asm("csrr %[addr], pmpaddr13"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 14:
|
||||
asm("csrr %[addr], pmpaddr14"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
case 15:
|
||||
asm("csrr %[addr], pmpaddr15"
|
||||
: [addr] "=r" (*address) ::);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int metal_pmp_lock(struct metal_pmp *pmp, unsigned int region)
|
||||
{
|
||||
struct metal_pmp_config config;
|
||||
size_t address;
|
||||
int rc = 0;
|
||||
|
||||
rc = metal_pmp_get_region(pmp, region, &config, &address);
|
||||
if(rc) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if(config.L == METAL_PMP_LOCKED) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
config.L = METAL_PMP_LOCKED;
|
||||
|
||||
rc = metal_pmp_set_region(pmp, region, config, address);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int metal_pmp_set_address(struct metal_pmp *pmp, unsigned int region, size_t address)
|
||||
{
|
||||
struct metal_pmp_config config;
|
||||
size_t old_address;
|
||||
int rc = 0;
|
||||
|
||||
rc = metal_pmp_get_region(pmp, region, &config, &old_address);
|
||||
if(rc) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = metal_pmp_set_region(pmp, region, config, address);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
size_t metal_pmp_get_address(struct metal_pmp *pmp, unsigned int region)
|
||||
{
|
||||
struct metal_pmp_config config;
|
||||
size_t address = 0;
|
||||
|
||||
metal_pmp_get_region(pmp, region, &config, &address);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
int metal_pmp_set_address_mode(struct metal_pmp *pmp, unsigned int region, enum metal_pmp_address_mode mode)
|
||||
{
|
||||
struct metal_pmp_config config;
|
||||
size_t address;
|
||||
int rc = 0;
|
||||
|
||||
rc = metal_pmp_get_region(pmp, region, &config, &address);
|
||||
if(rc) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
config.A = mode;
|
||||
|
||||
rc = metal_pmp_set_region(pmp, region, config, address);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
enum metal_pmp_address_mode metal_pmp_get_address_mode(struct metal_pmp *pmp, unsigned int region)
|
||||
{
|
||||
struct metal_pmp_config config;
|
||||
size_t address = 0;
|
||||
|
||||
metal_pmp_get_region(pmp, region, &config, &address);
|
||||
|
||||
return config.A;
|
||||
}
|
||||
|
||||
|
||||
int metal_pmp_set_executable(struct metal_pmp *pmp, unsigned int region, int X)
|
||||
{
|
||||
struct metal_pmp_config config;
|
||||
size_t address;
|
||||
int rc = 0;
|
||||
|
||||
rc = metal_pmp_get_region(pmp, region, &config, &address);
|
||||
if(rc) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
config.X = X;
|
||||
|
||||
rc = metal_pmp_set_region(pmp, region, config, address);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int metal_pmp_get_executable(struct metal_pmp *pmp, unsigned int region)
|
||||
{
|
||||
struct metal_pmp_config config;
|
||||
size_t address = 0;
|
||||
|
||||
metal_pmp_get_region(pmp, region, &config, &address);
|
||||
|
||||
return config.X;
|
||||
}
|
||||
|
||||
|
||||
int metal_pmp_set_writeable(struct metal_pmp *pmp, unsigned int region, int W)
|
||||
{
|
||||
struct metal_pmp_config config;
|
||||
size_t address;
|
||||
int rc = 0;
|
||||
|
||||
rc = metal_pmp_get_region(pmp, region, &config, &address);
|
||||
if(rc) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
config.W = W;
|
||||
|
||||
rc = metal_pmp_set_region(pmp, region, config, address);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int metal_pmp_get_writeable(struct metal_pmp *pmp, unsigned int region)
|
||||
{
|
||||
struct metal_pmp_config config;
|
||||
size_t address = 0;
|
||||
|
||||
metal_pmp_get_region(pmp, region, &config, &address);
|
||||
|
||||
return config.W;
|
||||
}
|
||||
|
||||
|
||||
int metal_pmp_set_readable(struct metal_pmp *pmp, unsigned int region, int R)
|
||||
{
|
||||
struct metal_pmp_config config;
|
||||
size_t address;
|
||||
int rc = 0;
|
||||
|
||||
rc = metal_pmp_get_region(pmp, region, &config, &address);
|
||||
if(rc) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
config.R = R;
|
||||
|
||||
rc = metal_pmp_set_region(pmp, region, config, address);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int metal_pmp_get_readable(struct metal_pmp *pmp, unsigned int region)
|
||||
{
|
||||
struct metal_pmp_config config;
|
||||
size_t address = 0;
|
||||
|
||||
metal_pmp_get_region(pmp, region, &config, &address);
|
||||
|
||||
return config.R;
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/* Copyright 2019 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <metal/privilege.h>
|
||||
|
||||
#define METAL_MSTATUS_MIE_OFFSET 3
|
||||
#define METAL_MSTATUS_MPIE_OFFSET 7
|
||||
#define METAL_MSTATUS_SIE_OFFSET 1
|
||||
#define METAL_MSTATUS_SPIE_OFFSET 5
|
||||
#define METAL_MSTATUS_UIE_OFFSET 0
|
||||
#define METAL_MSTATUS_UPIE_OFFSET 4
|
||||
|
||||
#define METAL_MSTATUS_MPP_OFFSET 11
|
||||
#define METAL_MSTATUS_MPP_MASK 3
|
||||
|
||||
void metal_privilege_drop_to_mode(enum metal_privilege_mode mode,
|
||||
struct metal_register_file regfile,
|
||||
metal_privilege_entry_point_t entry_point)
|
||||
{
|
||||
uintptr_t mstatus;
|
||||
asm volatile("csrr %0, mstatus" : "=r" (mstatus));
|
||||
|
||||
/* Set xPIE bits based on current xIE bits */
|
||||
if(mstatus && (1 << METAL_MSTATUS_MIE_OFFSET)) {
|
||||
mstatus |= (1 << METAL_MSTATUS_MPIE_OFFSET);
|
||||
} else {
|
||||
mstatus &= ~(1 << METAL_MSTATUS_MPIE_OFFSET);
|
||||
}
|
||||
if(mstatus && (1 << METAL_MSTATUS_SIE_OFFSET)) {
|
||||
mstatus |= (1 << METAL_MSTATUS_SPIE_OFFSET);
|
||||
} else {
|
||||
mstatus &= ~(1 << METAL_MSTATUS_SPIE_OFFSET);
|
||||
}
|
||||
if(mstatus && (1 << METAL_MSTATUS_UIE_OFFSET)) {
|
||||
mstatus |= (1 << METAL_MSTATUS_UPIE_OFFSET);
|
||||
} else {
|
||||
mstatus &= ~(1 << METAL_MSTATUS_UPIE_OFFSET);
|
||||
}
|
||||
|
||||
/* Set MPP to the requested privilege mode */
|
||||
mstatus &= ~(METAL_MSTATUS_MPP_MASK << METAL_MSTATUS_MPP_OFFSET);
|
||||
mstatus |= (mode << METAL_MSTATUS_MPP_OFFSET);
|
||||
|
||||
asm volatile("csrw mstatus, %0" :: "r" (mstatus));
|
||||
|
||||
/* Set the entry point in MEPC */
|
||||
asm volatile("csrw mepc, %0" :: "r" (entry_point));
|
||||
|
||||
/* Set the register file */
|
||||
asm volatile("mv ra, %0" :: "r" (regfile.ra));
|
||||
asm volatile("mv sp, %0" :: "r" (regfile.sp));
|
||||
|
||||
asm volatile("mret");
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine.h>
|
||||
#include <metal/shutdown.h>
|
||||
|
||||
extern inline void __metal_shutdown_exit(const struct __metal_shutdown *sd, int code);
|
||||
|
||||
#if defined(__METAL_DT_SHUTDOWN_HANDLE)
|
||||
void metal_shutdown(int code)
|
||||
{
|
||||
__metal_shutdown_exit(__METAL_DT_SHUTDOWN_HANDLE, code);
|
||||
}
|
||||
#else
|
||||
# warning "There is no defined shutdown mechanism, metal_shutdown() will spin."
|
||||
void metal_shutdown(int code)
|
||||
{
|
||||
while (1) {
|
||||
__asm__ volatile ("nop");
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,19 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/machine.h>
|
||||
#include <metal/spi.h>
|
||||
|
||||
extern inline void metal_spi_init(struct metal_spi *spi, int baud_rate);
|
||||
extern inline int metal_spi_transfer(struct metal_spi *spi, struct metal_spi_config *config, size_t len, char *tx_buf, char *rx_buf);
|
||||
extern inline int metal_spi_get_baud_rate(struct metal_spi *spi);
|
||||
extern inline int metal_spi_set_baud_rate(struct metal_spi *spi, int baud_rate);
|
||||
|
||||
struct metal_spi *metal_spi_get_device(int device_num)
|
||||
{
|
||||
if(device_num >= __METAL_DT_MAX_SPIS) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (struct metal_spi *) __metal_spi_table[device_num];
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/switch.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
struct metal_switch* metal_switch_get (char *label)
|
||||
{
|
||||
int i;
|
||||
struct metal_switch *flip;
|
||||
|
||||
if ((__METAL_DT_MAX_BUTTONS == 0) || (label == NULL)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < __METAL_DT_MAX_BUTTONS; i++) {
|
||||
flip = (struct metal_switch*)__metal_switch_table[i];
|
||||
if (flip->vtable->switch_exist(flip, label)) {
|
||||
return flip;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern inline struct metal_interrupt*
|
||||
metal_switch_interrupt_controller(struct metal_switch *flip);
|
||||
extern inline int metal_switch_get_interrupt_id(struct metal_switch *flip);
|
|
@ -0,0 +1,80 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <sys/times.h>
|
||||
#include <metal/cpu.h>
|
||||
#include <metal/timer.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
#if defined(__METAL_DT_MAX_HARTS)
|
||||
/* This implementation serves as a small shim that interfaces with the first
|
||||
* timer on a system. */
|
||||
int metal_timer_get_cyclecount(int hartid, unsigned long long *mcc)
|
||||
{
|
||||
struct metal_cpu *cpu = metal_cpu_get(hartid);
|
||||
|
||||
if ( cpu ) {
|
||||
*mcc = metal_cpu_get_timer(cpu);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int metal_timer_get_timebase_frequency(int hartid, unsigned long long *timebase)
|
||||
{
|
||||
struct metal_cpu *cpu = metal_cpu_get(hartid);
|
||||
|
||||
if ( cpu ) {
|
||||
*timebase = metal_cpu_get_timebase(cpu);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int metal_timer_get_machine_time(int hartid)
|
||||
{
|
||||
struct metal_cpu *cpu = metal_cpu_get(hartid);
|
||||
|
||||
if ( cpu ) {
|
||||
return metal_cpu_get_mtime(cpu);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int metal_timer_set_machine_time(int hartid, unsigned long long time)
|
||||
{
|
||||
struct metal_cpu *cpu = metal_cpu_get(hartid);
|
||||
|
||||
if ( cpu ) {
|
||||
return metal_cpu_set_mtimecmp(cpu, time);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/* This implementation of gettimeofday doesn't actually do anything, it's just there to
|
||||
* provide a shim and return 0 so we can ensure that everything can link to _gettimeofday.
|
||||
*/
|
||||
int nop_cyclecount(int id, unsigned long long *c) __attribute__((section(".text.metal.nop.cyclecount")));
|
||||
int nop_cyclecount(int id, unsigned long long *c) { return -1; }
|
||||
int nop_timebase(unsigned long long *t) __attribute__((section(".text.metal.nop.timebase")));
|
||||
int nop_timebase(unsigned long long *t) { return -1; }
|
||||
int nop_tick(int second) __attribute__((section(".text.metal.nop.tick")));
|
||||
int nop_tick(int second) { return -1; }
|
||||
int metal_timer_get_cyclecount(int hartid, unsigned long long *c) __attribute__((weak, alias("nop_cyclecount")))
|
||||
{
|
||||
#warning "There is no default timer device, metal_timer_get_cyclecount() will always return cyclecount -1."
|
||||
}
|
||||
int metal_timer_get_timebase_frequency(unsigned long long *t) __attribute__((weak, alias("nop_timebase")))
|
||||
{
|
||||
#warning "There is no default timer device, metal_timer_get_timebase_frequency() will always return timebase -1."
|
||||
}
|
||||
int metal_timer_set_tick(int second) __attribute__((weak, alias("nop_tick")))
|
||||
{
|
||||
#warning "There is no default timer device, metal_timer_set_tick) will always return -1."
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/* Copyright 2019 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#define METAL_MSTATUS_MIE_SHIFT 8
|
||||
#define METAL_MSTATUS_MPP_M 3
|
||||
#define METAL_MSTATUS_MPP_SHIFT 11
|
||||
|
||||
#define METAL_MTVEC_MODE_MASK 3
|
||||
|
||||
/* void _metal_trap(int ecode)
|
||||
*
|
||||
* Trigger a machine-mode trap with exception code ecode
|
||||
*/
|
||||
.global _metal_trap
|
||||
.type _metal_trap, @function
|
||||
|
||||
_metal_trap:
|
||||
|
||||
/* Store the instruction which called _metal_trap in mepc */
|
||||
addi t0, ra, -1
|
||||
csrw mepc, t0
|
||||
|
||||
/* Set mcause to the desired exception code */
|
||||
csrw mcause, a0
|
||||
|
||||
/* Read mstatus */
|
||||
csrr t0, mstatus
|
||||
|
||||
/* Set MIE=0 */
|
||||
li t1, -1
|
||||
xori t1, t1, METAL_MSTATUS_MIE_SHIFT
|
||||
and t0, t0, t1
|
||||
|
||||
/* Set MPP=M */
|
||||
li t1, METAL_MSTATUS_MPP_M
|
||||
slli t1, t1, METAL_MSTATUS_MPP_SHIFT
|
||||
or t0, t0, t1
|
||||
|
||||
/* Write mstatus */
|
||||
csrw mstatus, t0
|
||||
|
||||
/* Read mtvec */
|
||||
csrr t0, mtvec
|
||||
|
||||
/*
|
||||
* Mask the mtvec MODE bits
|
||||
* Exceptions always jump to mtvec.BASE regradless of the vectoring mode.
|
||||
*/
|
||||
andi t0, t0, METAL_MTVEC_MODE_MASK
|
||||
|
||||
/* Jump to mtvec */
|
||||
jr t0
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/uart.h>
|
||||
#include <metal/machine.h>
|
||||
|
||||
#if defined(__METAL_DT_STDOUT_UART_HANDLE)
|
||||
/* This implementation serves as a small shim that interfaces with the first
|
||||
* UART on a system. */
|
||||
int metal_tty_putc(unsigned char c)
|
||||
{
|
||||
if (c == '\n') {
|
||||
int out = metal_uart_putc(__METAL_DT_STDOUT_UART_HANDLE, '\r');
|
||||
if (out != 0)
|
||||
return out;
|
||||
}
|
||||
return metal_uart_putc(__METAL_DT_STDOUT_UART_HANDLE, c);
|
||||
}
|
||||
|
||||
#ifndef __METAL_DT_STDOUT_UART_BAUD
|
||||
#define __METAL_DT_STDOUT_UART_BAUD 115200
|
||||
#endif
|
||||
|
||||
static void metal_tty_init(void) __attribute__((constructor));
|
||||
static void metal_tty_init(void)
|
||||
{
|
||||
metal_uart_init(__METAL_DT_STDOUT_UART_HANDLE, __METAL_DT_STDOUT_UART_BAUD);
|
||||
}
|
||||
#else
|
||||
/* This implementation of putc doesn't actually do anything, it's just there to
|
||||
* provide a shim that eats all the characters so we can ensure that everything
|
||||
* can link to metal_tty_putc. */
|
||||
int nop_putc(unsigned char c) __attribute__((section(".text.metal.nop.putc")));
|
||||
int nop_putc(unsigned char c) { return -1; }
|
||||
int metal_tty_putc(unsigned char c) __attribute__((weak, alias("nop_putc")));
|
||||
#warning "There is no default output device, metal_tty_putc() will throw away all input."
|
||||
#endif
|
|
@ -0,0 +1,10 @@
|
|||
/* Copyright 2018 SiFive, Inc */
|
||||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include <metal/uart.h>
|
||||
|
||||
extern inline void metal_uart_init(struct metal_uart *uart, int baud_rate);
|
||||
extern inline int metal_uart_putc(struct metal_uart *uart, unsigned char c);
|
||||
extern inline int metal_uart_getc(struct metal_uart *uart, unsigned char *c);
|
||||
extern inline int metal_uart_get_baud_rate(struct metal_uart *uart);
|
||||
extern inline int metal_uart_set_baud_rate(struct metal_uart *uart, int baud_rate);
|
Loading…
Add table
Add a link
Reference in a new issue