iBoot/drivers/apple/a7iop/a7iop.c

426 lines
12 KiB
C

/*
* Copyright (C) 2011-2012 Apple Inc. All rights reserved.
*
* This document is the property of Apple Inc.
* It is considered confidential and proprietary.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Apple Inc.
*/
#include <debug.h>
#include <drivers/a7iop/a7iop.h>
#include <platform/int.h>
#include <platform/soc/hwclocks.h>
#include <platform/soc/hwisr.h>
#include <platform/soc/hwregbase.h>
#include <sys.h>
#include <sys/task.h>
#include <platform/timer.h>
#define low_bits(x) ((x) & 0x00000000ffffffffULL)
#define high_bits(x) (((x) & 0xffffffff00000000ULL) >> 32)
#define MAX_CLOCKS (2)
static int32_t _akf_init(const KFWRAPPER_TYPE_T kfw_type);
static void _akf_recv_mbox_nonempty_int_handler(void *arg);
static void _akf_send_mbox_empty_int_handler(void *arg);
static void _akf_set_timebase(addr_t regs_base);
// KFWrappers are indexed by types defined in enum table
static struct akf_wrapper {
uint64_t base;
uint32_t clock_idx[MAX_CLOCKS];
uint32_t inbox_int;
uint32_t outbox_int;
struct task_event recv_mbox_wait_event;
struct task_event send_mbox_wait_event;
bool set_iop_timebase;
} _akf_wrappers[] = {
#ifdef ANS_AKF_BASE_ADDR
#ifdef WITH_ANS_DLL
[KFW_ANS] = { .base = ANS_AKF_BASE_ADDR,
.clock_idx = { CLK_ANS, CLK_ANS_DLL },
.inbox_int = INT_ANS_KF_INBOX_EMPTY,
.outbox_int = INT_ANS_KF_OUTBOX_NOTEMPTY,
.set_iop_timebase = true
},
#else
[KFW_ANS] = { .base = ANS_AKF_BASE_ADDR,
.clock_idx = { CLK_ANS, UINT32_MAX },
.inbox_int = INT_ANS_KF_INBOX_EMPTY,
.outbox_int = INT_ANS_KF_OUTBOX_NOTEMPTY,
.set_iop_timebase = true
},
#endif
#else
[KFW_ANS] = { .base = UINT64_MAX,
.clock_idx = { UINT32_MAX },
.inbox_int = UINT32_MAX,
.outbox_int = UINT32_MAX,
.set_iop_timebase = false
},
#endif
#ifdef ASIO_AKF_BASE_ADDR
[KFW_SIO] = { .base = ASIO_AKF_BASE_ADDR,
.clock_idx = { CLK_SIO, CLK_SIO_P },
.inbox_int = INT_SIO_KF_INBOX_EMPTY,
.outbox_int = INT_SIO_KF_OUTBOX_NOTEMPTY,
.set_iop_timebase = false
},
#else
[KFW_SIO] = { .base = UINT64_MAX,
.clock_idx = { UINT32_MAX },
.inbox_int = UINT32_MAX,
.outbox_int = UINT32_MAX,
.set_iop_timebase = false
},
#endif
#ifdef ASEP_AKF_BASE_ADDR
[KFW_SEP] = { .base = ASEP_AKF_BASE_ADDR,
.clock_idx = { UINT32_MAX, UINT32_MAX },
.inbox_int = INT_SEP_KF_INBOX_EMPTY,
.outbox_int = INT_SEP_KF_OUTBOX_NOTEMPTY
},
#else
[KFW_SEP] = { .base = UINT64_MAX,
.clock_idx = { UINT32_MAX },
.inbox_int = UINT32_MAX,
.outbox_int = UINT32_MAX,
.set_iop_timebase = false
},
#endif
};
/*
* akf_start
*
* Initialize KFWrapper (clocks, interrupts), sets up remap window,
* Enable FIFOs, and boot KF core.
* It assumes firmware address is aligned, and memory allocated is contiguous.
*
*/
int32_t akf_start(const KFWRAPPER_TYPE_T kfw_type, const addr_t firmware_address, const uint64_t firmware_size)
{
addr_t fw_phy_addr;
addr_t akf_base;
uint32_t i;
akf_base = _akf_wrappers[kfw_type].base;
ASSERT(akf_base != 0);
// register for interrupts
install_int_handler(_akf_wrappers[kfw_type].outbox_int, _akf_recv_mbox_nonempty_int_handler, (void *)kfw_type);
install_int_handler(_akf_wrappers[kfw_type].inbox_int, _akf_send_mbox_empty_int_handler, (void *)kfw_type);
// initialize wait events
event_init(&_akf_wrappers[kfw_type].recv_mbox_wait_event, EVENT_FLAG_AUTO_UNSIGNAL, false);
event_init(&_akf_wrappers[kfw_type].send_mbox_wait_event, EVENT_FLAG_AUTO_UNSIGNAL, false);
// enable clock and power
for (i = 0; i < MAX_CLOCKS; i++) {
if (_akf_wrappers[kfw_type].clock_idx[i] == UINT32_MAX)
break;
clock_gate(_akf_wrappers[kfw_type].clock_idx[i], true);
}
// mask all the interrupts
rAKF_AP_MAILBOX_SET(akf_base) = AKF_MAILBOX_O_NONEMPTY_MSK | AKF_MAILBOX_O_EMPTY_MSK |
AKF_MAILBOX_I_NONEMPTY_MSK | AKF_MAILBOX_I_EMPTY_MSK;
// setup remap
rAKF_AXI_START(akf_base) = 0;
rAKF_AXI_START_EXT(akf_base) = 0;
rAKF_AXI_END(akf_base) = (uint32_t)(low_bits(firmware_size));
rAKF_AXI_END_EXT(akf_base) = (uint32_t)(high_bits(firmware_size));
fw_phy_addr = mem_static_map_physical(firmware_address);
rAKF_AXI_BASE(akf_base) = (uint32_t)(low_bits(fw_phy_addr));
rAKF_AXI_BASE_EXT(akf_base) = (uint32_t)(high_bits(fw_phy_addr));
// enable FIFOs
rAKF_AP_INBOX_CTRL(akf_base) |= AKF_AP_INBOX_CTRL_ENABLE;
rAKF_AP_OUTBOX_CTRL(akf_base) |= AKF_AP_OUTBOX_CTRL_ENABLE;
#if AKF_VERSION == 2
rAKF_KIC_MAILBOX_EXT_CLR(akf_base) = 0x1111;
#endif
// if appropriate, set IOP timebase to be close to and in sync with local timebase (from aic),
if (_akf_wrappers[kfw_type].set_iop_timebase) {
_akf_set_timebase(akf_base);
}
// set run bit
rAKF_CPU_CTRL(akf_base) = AKF_CPU_CTRL_RUN;
// let interrupt flow
unmask_int(_akf_wrappers[kfw_type].outbox_int);
unmask_int(_akf_wrappers[kfw_type].inbox_int);
return 0;
}
/*
* akf_start_sep
*
* SEP needs special handling.
* Register and enables interrupts, enable fifos
*
*/
int32_t akf_start_sep(void)
{
// register for interrupts
install_int_handler(_akf_wrappers[KFW_SEP].outbox_int, _akf_recv_mbox_nonempty_int_handler, (void *)KFW_SEP);
install_int_handler(_akf_wrappers[KFW_SEP].inbox_int, _akf_send_mbox_empty_int_handler, (void *)KFW_SEP);
// initialize wait events
event_init(&_akf_wrappers[KFW_SEP].recv_mbox_wait_event, EVENT_FLAG_AUTO_UNSIGNAL, false);
event_init(&_akf_wrappers[KFW_SEP].send_mbox_wait_event, EVENT_FLAG_AUTO_UNSIGNAL, false);
// mask all the interrupts
rAKF_AP_MAILBOX_SET(_akf_wrappers[KFW_SEP].base) = AKF_MAILBOX_O_NONEMPTY_MSK | AKF_MAILBOX_O_EMPTY_MSK |
AKF_MAILBOX_I_NONEMPTY_MSK | AKF_MAILBOX_I_EMPTY_MSK;
// enable FIFOs
rAKF_AP_INBOX_CTRL(_akf_wrappers[KFW_SEP].base) |= AKF_AP_INBOX_CTRL_ENABLE;
rAKF_AP_OUTBOX_CTRL(_akf_wrappers[KFW_SEP].base) |= AKF_AP_OUTBOX_CTRL_ENABLE;
// let interrupt flow
unmask_int(_akf_wrappers[KFW_SEP].outbox_int);
unmask_int(_akf_wrappers[KFW_SEP].inbox_int);
return 0;
}
/*
* akf_recv_mbox
*
* Fetch a message from outbound mailbox.
* If there are no messages in outbox, outbox-non-empty interrupt will be enabled.
* If wait_timeout is UINT32_MAX, driver will wait for ever for a message
* 0: success, -1: outbox empty, -2: timed out
*
*/
int32_t akf_recv_mbox(const KFWRAPPER_TYPE_T kfw_type, uint64_t *msg, uint32_t wait_timeout)
{
int32_t ret;
addr_t akf_base;
akf_base = _akf_wrappers[kfw_type].base;
ASSERT(akf_base != 0);
ret = 0;
enter_critical_section();
while (rAKF_AP_OUTBOX_CTRL(akf_base) & AKF_AP_OUTBOX_CTRL_EMPTY) {
if (!wait_timeout) {
ret = -1;
goto exit;
}
rAKF_AP_MAILBOX_CLR(akf_base) = AKF_MAILBOX_O_NONEMPTY_MSK;
if (wait_timeout == UINT32_MAX) {
event_wait(&_akf_wrappers[kfw_type].recv_mbox_wait_event);
}
else {
// XXX recompute wait timeout here?
if (event_wait_timeout(&_akf_wrappers[kfw_type].recv_mbox_wait_event, wait_timeout) == false) {
ret = -2;
goto exit;
}
}
}
// assert for over-flow and under-flow errors
RELEASE_ASSERT((rAKF_AP_OUTBOX_CTRL(akf_base) & (AKF_AP_OUTBOX_CTRL_UDFL | AKF_AP_OUTBOX_CTRL_OVFL)) == 0);
*msg = akf_read_ap_outbox(akf_base);
exit:
exit_critical_section();
return ret;
}
/*
* akf_send_mbox
*
* Send a message to inbound mailbox.
* If there is no space in the inbox, inbox-empty interrupt will be enabled.
* If wait_timeout is UINT32_MAX, driver will wait for ever for space in the mailbox to stuff a new message.
* 0: success, -1: inbox full, -2: timed out
*
*/
int32_t akf_send_mbox(const KFWRAPPER_TYPE_T kfw_type, const uint64_t msg, uint32_t wait_timeout)
{
int32_t ret;
addr_t akf_base;
akf_base = _akf_wrappers[kfw_type].base;
ASSERT(akf_base != 0);
ret = 0;
enter_critical_section();
while (rAKF_AP_INBOX_CTRL(akf_base) & AKF_AP_INBOX_CTRL_FULL) {
if (!wait_timeout) {
ret = -1;
goto exit;
}
rAKF_AP_MAILBOX_CLR(akf_base) = AKF_MAILBOX_I_EMPTY_MSK;
if (wait_timeout == UINT32_MAX) {
event_wait(&_akf_wrappers[kfw_type].send_mbox_wait_event);
}
else {
// XXX recompute wait timeout here?
if (event_wait_timeout(&_akf_wrappers[kfw_type].send_mbox_wait_event, wait_timeout) == false) {
ret = -2;
goto exit;
}
}
}
// assert for over-flow and under-flow errors
RELEASE_ASSERT((rAKF_AP_INBOX_CTRL(akf_base) & (AKF_AP_INBOX_CTRL_UDFL | AKF_AP_INBOX_CTRL_OVFL)) == 0);
akf_write_ap_inbox(akf_base, msg);
exit:
exit_critical_section();
return ret;
}
/*
* akf_send_nmi
*
* Sends NMI to the KF core.
* KF Timer1 is used for generating FIQ NMI. It setups up FIQ Software source.
*
*/
int32_t akf_send_nmi(const KFWRAPPER_TYPE_T kfw_type)
{
addr_t akf_base;
akf_base = _akf_wrappers[kfw_type].base;
ASSERT(akf_base != 0);
if ((rAKF_KIC_TMR_CFG1(akf_base) & AKF_KIC_TMR_CFG_NMI) == 0) {
dprintf(DEBUG_INFO, "akf_send_nmi: KIC_TMR_CFG1 not configured for NMI\n");
return -1;
}
rAKF_KIC_TMR_STATE_SET1(akf_base) = AKF_KIC_TMR_STATE_SET_SGT;
return 0;
}
/*
* akf_stop
*
* Stops the KF core, mask all FIFO interrupts
* Book-keeping related to shutdown (quiesce clocks, uninstall interrupt handler, clears remap window)
*
*/
int32_t akf_stop(const KFWRAPPER_TYPE_T kfw_type)
{
addr_t akf_base;
uint32_t i;
akf_base = _akf_wrappers[kfw_type].base;
ASSERT(akf_base != 0);
enter_critical_section();
// mask interrupts
mask_int(_akf_wrappers[kfw_type].outbox_int);
mask_int(_akf_wrappers[kfw_type].inbox_int);
// XXX clear any pending interrupts?
// disable FIFOs
rAKF_AP_INBOX_CTRL(akf_base) = (rAKF_AP_INBOX_CTRL(akf_base) & ~AKF_AP_INBOX_CTRL_ENABLE);
rAKF_AP_OUTBOX_CTRL(akf_base) = (rAKF_AP_OUTBOX_CTRL(akf_base) & ~AKF_AP_OUTBOX_CTRL_ENABLE);
// SEP: special handling, clean local states
if (kfw_type == KFW_SEP)
goto done;
// at this time, KF should be idle (not generating any transactions to fabric)
// stop KF and keep it in reset
rAKF_CPU_CTRL(akf_base) = (rAKF_CPU_CTRL(akf_base) & ~AKF_CPU_CTRL_RUN);
// clear remap registers
rAKF_AXI_START(akf_base) = 0;
rAKF_AXI_START_EXT(akf_base) = 0;
rAKF_AXI_END(akf_base) = 0;
rAKF_AXI_END_EXT(akf_base) = 0;
rAKF_AXI_BASE(akf_base) = 0;
rAKF_AXI_BASE_EXT(akf_base) = 0;
// disable clock and power, clock_gate calls takes care of both
for (i = 0; i < MAX_CLOCKS; i++) {
if (_akf_wrappers[kfw_type].clock_idx[i] == UINT32_MAX)
break;
clock_gate(_akf_wrappers[kfw_type].clock_idx[i], false);
}
done:
exit_critical_section();
return 0;
}
static void _akf_recv_mbox_nonempty_int_handler(void *arg)
{
KFWRAPPER_TYPE_T kfw_type = (KFWRAPPER_TYPE_T)arg;
RELEASE_ASSERT(kfw_type < KFW_TYPE_MAX);
ASSERT(_akf_wrappers[kfw_type].base != 0);
rAKF_AP_MAILBOX_SET(_akf_wrappers[kfw_type].base) = AKF_MAILBOX_O_NONEMPTY_MSK;
// NOTE: this akf reg access will ensure that interrupt mask operation above
// will complete before exiting interrupt handling - the read result is unimportant
(void)rAKF_AP_INBOX_CTRL(_akf_wrappers[kfw_type].base);
event_signal(&_akf_wrappers[kfw_type].recv_mbox_wait_event);
}
static void _akf_send_mbox_empty_int_handler(void *arg)
{
KFWRAPPER_TYPE_T kfw_type = (KFWRAPPER_TYPE_T)arg;
RELEASE_ASSERT(kfw_type < KFW_TYPE_MAX);
ASSERT(_akf_wrappers[kfw_type].base != 0);
rAKF_AP_MAILBOX_SET(_akf_wrappers[kfw_type].base) = AKF_MAILBOX_I_EMPTY_MSK;
// NOTE: this akf reg access will ensure that interrupt mask operation above
// will complete before exiting interrupt handling - the read result is unimportant
(void)rAKF_AP_INBOX_CTRL(_akf_wrappers[kfw_type].base);
event_signal(&_akf_wrappers[kfw_type].send_mbox_wait_event);
}
static void _akf_set_timebase(addr_t regs_base)
{
uint32_t reg_value;
uint64_t now;
reg_value = rAKF_KIC_GLB_CFG(regs_base);
enter_critical_section();
rAKF_KIC_GLB_CFG(regs_base) = (reg_value & ~AKF_KIC_GLB_CFG_TEN);
now = timer_get_ticks();
rAKF_KIC_GLB_TIME_BASE_LO(regs_base) = (uint32_t)now;
rAKF_KIC_GLB_TIME_BASE_HI(regs_base) = (uint32_t)(now>>32);
rAKF_KIC_GLB_CFG(regs_base) = (reg_value | AKF_KIC_GLB_CFG_TEN);
exit_critical_section();
}