/*
* Copyright (c) 2009 Vineeth Pillai
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** @addtogroup arm32qemu_icp
* @{
*/
/** @file
* @brief QEMU icp drivers.
*/
#include <interrupt.h>
#include <ipc/irq.h>
#include <console/chardev.h>
#include <arch/drivers/qemu.h>
#include <arch/drivers/pl050.h>
#include <console/console.h>
#include <sysinfo/sysinfo.h>
#include <print.h>
#include <ddi/device.h>
#include <mm/page.h>
#include <mm/frame.h>
#include <arch/mm/frame.h>
#include <arch/machine.h>
#include <arch/debug/print.h>
#include <genarch/fb/fb.h>
#include <genarch/fb/visuals.h>
/* Addresses of devices. */
#define QEMU_ICP_UART 0x16000000
#define QEMU_ICP_KBD 0x18000000
#define ICP_KBD_STAT 0x04
#define ICP_KBD_DATA 0x08
#define ICP_KBD_INTR_STAT 0x10
#define QEMU_ICP_RTC 0x13000000
#define QEMU_ICP_RTC1_LOAD_OFFSET 0x100
#define QEMU_ICP_RTC1_READ_OFFSET 0x104
#define QEMU_ICP_RTC1_CTL_OFFSET 0x108
#define QEMU_ICP_RTC1_INTRCLR_OFFSET 0x10C
#define QEMU_ICP_RTC1_BGLOAD_OFFSET 0x118
#define QEMU_ICP_RTC_CTL_VALUE 0x00E2
#define QEMU_ICP_IRQC 0x14000000
#define QEMU_ICP_IRQC_MASK_OFFSET 0xC
#define QEMU_ICP_IRQC_UNMASK_OFFSET 0x8
#define QEMU_ICP_FB 0x00800000
#define QEMU_ICP_FB_FRAME (QEMU_ICP_FB >> 12)
#define QEMU_ICP_FB_NUM_FRAME 300
#define ICP_VGA 0xC0000000
#define ICP_CMCR 0x10000000
#define QEMU_ICP_SDRAM_MASK 0x1C
#define QEMU_ICP_SDRAMCR_OFFSET 0x20
/* IRQs */
#define QEMU_ICP_KBD_IRQ 3
#define QEMU_ICP_TIMER_IRQ 6
#define SDRAM_SIZE (sdram[((*(uint32_t *)(ICP_CMCR+QEMU_ICP_SDRAMCR_OFFSET) & QEMU_ICP_SDRAM_MASK) >> 2)])
static qemu_icp_hw_map_t qemu_icp_hw_map;
static irq_t qemu_icp_timer_irq;
static bool hw_map_init_called = false;
static bool vga_init = false;
uint32_t sdram[8] = {
16777216, /* 16mb */
33554432, /* 32mb */
67108864, /* 64mb */
134217728, /* 128mb */
268435456, /* 256mb */
0, /* Reserverd */
0, /* Reserverd */
0 /* Reserverd */
};
void icp_vga_init(void);
/** Initializes the vga
*
*/
void icp_vga_init(void)
{
*(uint32_t*)((char *)(qemu_icp_hw_map.cmcr)+0x14) = 0xA05F0000;
*(uint32_t*)((char *)(qemu_icp_hw_map.cmcr)+0x1C) = 0x12C11000;
*(uint32_t*)qemu_icp_hw_map.vga = 0x3F1F3F9C;
*(uint32_t*)((char *)(qemu_icp_hw_map.vga) + 0x4) = 0x080B61DF;
*(uint32_t*)((char *)(qemu_icp_hw_map.vga) + 0x8) = 0x067F3800;
*(uint32_t*)((char *)(qemu_icp_hw_map.vga) + 0x10) = QEMU_ICP_FB;
*(uint32_t *)((char *)(qemu_icp_hw_map.vga) + 0x1C) = 0x182B;
*(uint32_t*)((char *)(qemu_icp_hw_map.cmcr)+0xC) = 0x33805000;
}
/** Returns the mask of active interrupts. */
static inline uint32_t qemu_icp_irqc_get_sources(void)
{
return *((uint32_t *) qemu_icp_hw_map.irqc);
}
/** Masks interrupt.
*
* @param irq interrupt number
*/
static inline void qemu_icp_irqc_mask(uint32_t irq)
{
*((uint32_t *) qemu_icp_hw_map.irqc_mask) = (1 << irq);
}
/** Unmasks interrupt.
*
* @param irq interrupt number
*/
static inline void qemu_icp_irqc_unmask(uint32_t irq)
{
*((uint32_t *) qemu_icp_hw_map.irqc_unmask) |= (1 << irq);
}
/** Initializes the icp frame buffer */
void qemu_icp_fb_init(void)
{
fb_init(qemu_icp_get_fb_address(), 640, 480, 2560, VISUAL_BGR_8_8_8_0);
}
/** Initializes #qemu_icp_hw_map. */
void qemu_icp_hw_map_init(void)
{
qemu_icp_hw_map.uart = hw_map(QEMU_ICP_UART, PAGE_SIZE);
qemu_icp_hw_map.kbd_ctrl = hw_map(QEMU_ICP_KBD, PAGE_SIZE);
qemu_icp_hw_map.kbd_stat = qemu_icp_hw_map.kbd_ctrl + ICP_KBD_STAT;
qemu_icp_hw_map.kbd_data = qemu_icp_hw_map.kbd_ctrl + ICP_KBD_DATA;
qemu_icp_hw_map.kbd_intstat = qemu_icp_hw_map.kbd_ctrl + ICP_KBD_INTR_STAT;
qemu_icp_hw_map.rtc = hw_map(QEMU_ICP_RTC, PAGE_SIZE);
qemu_icp_hw_map.rtc1_load = qemu_icp_hw_map.rtc + QEMU_ICP_RTC1_LOAD_OFFSET;
qemu_icp_hw_map.rtc1_read = qemu_icp_hw_map.rtc + QEMU_ICP_RTC1_READ_OFFSET;
qemu_icp_hw_map.rtc1_ctl = qemu_icp_hw_map.rtc + QEMU_ICP_RTC1_CTL_OFFSET;
qemu_icp_hw_map.rtc1_intrclr = qemu_icp_hw_map.rtc + QEMU_ICP_RTC1_INTRCLR_OFFSET;
qemu_icp_hw_map.rtc1_bgload = qemu_icp_hw_map.rtc + QEMU_ICP_RTC1_BGLOAD_OFFSET;
qemu_icp_hw_map.irqc = hw_map(QEMU_ICP_IRQC, PAGE_SIZE);
qemu_icp_hw_map.irqc_mask = qemu_icp_hw_map.irqc + QEMU_ICP_IRQC_MASK_OFFSET;
qemu_icp_hw_map.irqc_unmask = qemu_icp_hw_map.irqc + QEMU_ICP_IRQC_UNMASK_OFFSET;
qemu_icp_hw_map.cmcr = hw_map(ICP_CMCR, PAGE_SIZE);
qemu_icp_hw_map.sdramcr = qemu_icp_hw_map.cmcr + QEMU_ICP_SDRAMCR_OFFSET;
qemu_icp_hw_map.vga = hw_map(ICP_VGA, PAGE_SIZE);
hw_map_init_called = true;
}
/** Acquire console back for kernel. */
void qemu_icp_grab_console(void)
{
pl050_grab();
}
/** Return console to userspace. */
void qemu_icp_release_console(void)
{
pl050_release();
}
/** Initializes console object representing qemu_icp console.
*
* @param devno device number.
*/
void qemu_icp_console_init(devno_t devno)
{
qemu_icp_irqc_mask(QEMU_ICP_KBD_IRQ);
pl050_init(devno, QEMU_ICP_KBD_IRQ, QEMU_ICP_KBD, qemu_icp_hw_map.kbd_ctrl);
qemu_icp_irqc_unmask(QEMU_ICP_KBD_IRQ);
}
/** Starts qemu_icp Real Time Clock device, which asserts regular interrupts.
*
* @param frequency Interrupts frequency (0 disables RTC).
*/
static void qemu_icp_timer_start(uint32_t frequency)
{
qemu_icp_irqc_mask(QEMU_ICP_TIMER_IRQ);
*((uint32_t*) qemu_icp_hw_map.rtc1_load) = frequency;
*((uint32_t*) qemu_icp_hw_map.rtc1_bgload) = frequency;
*((uint32_t*) qemu_icp_hw_map.rtc1_ctl) = QEMU_ICP_RTC_CTL_VALUE;
qemu_icp_irqc_unmask(QEMU_ICP_TIMER_IRQ);
}
static irq_ownership_t qemu_icp_timer_claim(void)
{
return IRQ_ACCEPT;
}
/** Timer interrupt handler.
*
* @param irq Interrupt information.
* @param arg Not used.
*/
static void qemu_icp_timer_irq_handler(irq_t *irq, void *arg, ...)
{
/*
* We are holding a lock which prevents preemption.
* Release the lock, call clock() and reacquire the lock again.
*/
*((uint32_t*) qemu_icp_hw_map.rtc1_intrclr) = 1;
spinlock_unlock(&irq->lock);
spinlock_lock(&irq->lock);
}
/** Initializes and registers timer interrupt handler. */
static void qemu_icp_timer_irq_init(void)
{
irq_initialize(&qemu_icp_timer_irq);
qemu_icp_timer_irq.devno = device_assign_devno();
qemu_icp_timer_irq.inr = QEMU_ICP_TIMER_IRQ;
qemu_icp_timer_irq.claim = qemu_icp_timer_claim;
qemu_icp_timer_irq.handler = qemu_icp_timer_irq_handler;
irq_register(&qemu_icp_timer_irq);
}
/** Starts timer.
*
* Initiates regular timer interrupts after initializing
* corresponding interrupt handler.
*/
void qemu_icp_timer_irq_start(void)
{
qemu_icp_timer_irq_init();
qemu_icp_timer_start(QEMU_ICP_TIMER_FREQ);
}
/** Returns the size of emulated memory.
*
* @return Size in bytes.
*/
size_t qemu_icp_get_memory_size(void)
{
if (hw_map_init_called) {
return (sdram[((*(uint32_t *)qemu_icp_hw_map.sdramcr & QEMU_ICP_SDRAM_MASK) >> 2)]);
} else {
return SDRAM_SIZE;
}
}
/** Prints a character.
*
* @param ch Character to be printed.
*/
void qemu_icp_debug_putc(char ch)
{
char *addr = 0;
if (!hw_map_init_called) {
addr = (char *) QEMU_ICP_KBD;
} else {
addr = (char *) qemu_icp_hw_map.uart;
}
if (ch == '\n')
*(addr) = '\r';
*(addr) = ch;
}
/** Stops qemu_icp. */
void qemu_icp_cpu_halt(void)
{
while (1);
}
/** interrupt exception handler.
*
* Determines sources of the interrupt from interrupt controller and
* calls high-level handlers for them.
*
* @param exc_no Interrupt exception number.
* @param istate Saved processor state.
*/
void qemu_icp_irq_exception(int exc_no, istate_t *istate)
{
uint32_t sources = qemu_icp_irqc_get_sources();
int i;
for (i = 0; i < QEMU_ICP_IRQC_MAX_IRQ; i++) {
if (sources & (1 << i)) {
irq_t *irq = irq_dispatch_and_lock(i);
if (irq) {
/* The IRQ handler was found. */
irq->handler(irq, irq->arg);
spinlock_unlock(&irq->lock);
} else {
/* Spurious interrupt.*/
dprintf("cpu%d: spurious interrupt (inum=%d)\n",
CPU->id, i);
}
}
}
}
/** Returns address of framebuffer device.
*
* @return Address of framebuffer device.
*/
uintptr_t qemu_icp_get_fb_address(void)
{
if (!vga_init) {
icp_vga_init();
vga_init = true;
}
return (uintptr_t) QEMU_ICP_FB;
}
/*
* Integrator specific frame initialization
*/
void
qemu_icp_frame_init(void)
{
frame_mark_unavailable(QEMU_ICP_FB_FRAME, QEMU_ICP_FB_NUM_FRAME);
}
/** @}
*/