Patchwork [v3,02/20] arm: add Faraday a369 SoC platform support

login
register
mail settings
Submitter Igor Mitsyanko
Date Feb. 7, 2013, 5:25 p.m.
Message ID <CA+x0pt7AhZGVAD1oufJ3QTUbV8GzyPGA5CcBc+nLSnExsjcvjQ@mail.gmail.com>
Download mbox | patch
Permalink /patch/218966/
State New
Headers show

Comments

Igor Mitsyanko - Feb. 7, 2013, 5:25 p.m.
On 02/06/2013 01:45 PM, Kuo-Jung Su wrote:

From: Kuo-Jung Su <dantesu@faraday-tech.com> <dantesu@faraday-tech.com>

The Faraday A369 EVB is a Faraday SoC platform evalution board used for
Faraday IP functional verification based on the well-known ARM AMBA 2.0
architecture.

Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com> <dantesu@faraday-tech.com>
---
 hw/arm/Makefile.objs         |    1 +
 hw/arm/faraday_a369.c        |  161 +++++++++++++++++++++++++++++
 hw/arm/faraday_a369_keypad.c |  234 ++++++++++++++++++++++++++++++++++++++++++
 hw/arm/faraday_a369_scu.c    |  188 +++++++++++++++++++++++++++++++++
 hw/arm/ftkbc010.h            |   26 +++++
 5 files changed, 610 insertions(+)
 create mode 100644 hw/arm/faraday_a369.c
 create mode 100644 hw/arm/faraday_a369_keypad.c
 create mode 100644 hw/arm/faraday_a369_scu.c
 create mode 100644 hw/arm/ftkbc010.h
Kuo-Jung Su - Feb. 18, 2013, 3:28 a.m.
2013/2/8 Igor Mitsyanko <i.mitsyanko@gmail.com>
>
>
> On 02/06/2013 01:45 PM, Kuo-Jung Su wrote:
>
> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>
> The Faraday A369 EVB is a Faraday SoC platform evalution board used for
> Faraday IP functional verification based on the well-known ARM AMBA 2.0
> architecture.
>
> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
> ---
>  hw/arm/Makefile.objs         |    1 +
>  hw/arm/faraday_a369.c        |  161 +++++++++++++++++++++++++++++
>  hw/arm/faraday_a369_keypad.c |  234 ++++++++++++++++++++++++++++++++++++++++++
>  hw/arm/faraday_a369_scu.c    |  188 +++++++++++++++++++++++++++++++++
>  hw/arm/ftkbc010.h            |   26 +++++
>  5 files changed, 610 insertions(+)
>  create mode 100644 hw/arm/faraday_a369.c
>  create mode 100644 hw/arm/faraday_a369_keypad.c
>  create mode 100644 hw/arm/faraday_a369_scu.c
>  create mode 100644 hw/arm/ftkbc010.h
>
> diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
> index 59d7023..02d1a7b 100644
> --- a/hw/arm/Makefile.objs
> +++ b/hw/arm/Makefile.objs
> @@ -34,3 +34,4 @@ obj-$(CONFIG_FDT) += ../device_tree.o
>
>  obj-y := $(addprefix ../,$(obj-y))
>  obj-y += faraday_a360.o faraday_a360_pmu.o
> +obj-y += faraday_a369.o faraday_a369_scu.o faraday_a369_keypad.o
> diff --git a/hw/arm/faraday_a369.c b/hw/arm/faraday_a369.c
> new file mode 100644
> index 0000000..e32dc7f
> --- /dev/null
> +++ b/hw/arm/faraday_a369.c
> @@ -0,0 +1,161 @@
> +/*
> + * Faraday A369 Evalution Board
> + *
> + * Copyright (c) 2012 Faraday Technology
> + * Written by Dante Su <dantesu@faraday-tech.com>
> + *
> + * This code is licensed under GNU GPL v2+.
> + */
> +
> +#include <hw/sysbus.h>
> +#include <hw/arm-misc.h>
> +#include <hw/devices.h>
> +#include <hw/i2c.h>
> +#include <hw/boards.h>
> +#include <hw/flash.h>
> +#include <hw/serial.h>
> +#include <hw/ssi.h>
> +#include <net/net.h>
> +#include <sysemu/sysemu.h>
> +#include <sysemu/blockdev.h>
> +#include <exec/address-spaces.h>
> +
> +#include "faraday.h"
> +
> +typedef FaradayMachState    A369State;
> +
> +/* Board init.  */
> +
> +static void
> +a369_device_init(A369State *s)
> +{
> +    /* Serial (FTUART010 which is 16550A compatible) */
> +    if (serial_hds[0]) {
> +        serial_mm_init(s->as,
> +                       0x92b00000,
> +                       2,
> +                       NULL,
> +                       18432000 / 16,
> +                       serial_hds[0],
> +                       DEVICE_LITTLE_ENDIAN);
> +    }
> +    if (serial_hds[1]) {
> +        serial_mm_init(s->as,
> +                       0x92c00000,
> +                       2,
> +                       NULL,
> +                       18432000 / 16,
> +                       serial_hds[1],
> +                       DEVICE_LITTLE_ENDIAN);
> +    }
> +
> +    /* ftscu010 */
> +    s->scu = sysbus_create_simple("a369.scu", 0x92000000, NULL);
> +
> +    /* ftkbc010 */
> +    sysbus_create_simple("a369.keypad", 0x92f00000, NULL);
> +}
> +
> +static void
> +a369_board_init(QEMUMachineInitArgs *args)
> +{
> +    DriveInfo *dinfo;
> +    struct arm_boot_info *bi = NULL;
> +    A369State *s = g_new(A369State, 1);
> +
> +    s->as = get_system_memory();
> +    s->ram = g_new(MemoryRegion, 1);
> +    s->sram = g_new(MemoryRegion, 1);
> +
> +    /* CPU */
> +    if (!args->cpu_model) {
> +        args->cpu_model = "fa626te";
> +    }
> +
> +    s->cpu = cpu_arm_init(args->cpu_model);
> +    if (!s->cpu) {
> +        args->cpu_model = "arm926";
> +        s->cpu = cpu_arm_init(args->cpu_model);
> +        if (!s->cpu) {
> +            hw_error("a369: Unable to find CPU definition\n");
> +            exit(1);
> +        }
> +    }
> +
> +    s->ahb_slave4 = 0x00080000; /* ROM: base=0x00000000, size=256MB */
> +    s->ahb_slave6 = 0x10090000; /* RAM: base=0x10000000, size=512MB */
>
>
> Does this register provide information on max allowable RAM size or amount of RAM actually accessible in a system? I mean,
> should this register be modified accordingly if args->ram_size value is less then 512MB?
>
>

Yes, the values to the registers define both the base address and max. size
to the corresponding salve devices.

Although these registers are all R/W, they should be treated as read-only to
the softwares.
Modifying the setting to slave devices would alter the system memory mapped
and might cause un-predictable issues.

P.S:
These registers are designed to be writable for Faraday internal test only.
Sometimes we'll have A36x mounted with a new external AHB daughter board
with built-in CPU and peripheral, and make A36X  as an expansion bus.

Only in such case, we'll update the AHB slave settings for FPGA test.

> +
> +    /* A369 supports upto 512MB ram space */
> +    if (args->ram_size > 0x20000000) {
> +        args->ram_size = 0x20000000;
> +    }
>
> +
> +    /* Use parallel NOR flash for ROM emulation */
> +    dinfo = drive_get_next(IF_PFLASH);
> +    s->rom = pflash_cfi01_register(
> +                    0,      /* base address */
> +                    NULL,
> +                    "a369.rom",
> +                    6144,   /* 6 KB */
> +                    dinfo ? dinfo->bdrv : NULL,
>
>
> I think you should also consider a case when we're booting with bootstrap code in ROM (no kernel_image specified). Right now,
> if no rom image is specified, QEMU will abort with "trying to execute code outside RAM" error. You could check for this case here
> and abort QEMU with a more descriptive error.
>

got it, thanks

>
> +                    1024,   /* 1 KB sector */
> +                    6,      /* 6 sector per chip */
> +                    4,      /* 32 bits */
> +                    0, 0, 0, 0, /* id */
> +                    0       /* Little Endian */);
> +    if (!s->rom) {
> +        hw_error("a369: failed to init ROM device.\n");
> +        exit(1);
> +    }
> +
> +    /* Embedded RAM Init */
> +    memory_region_init_ram(s->sram, "a369.sram", 0x4000);
> +    vmstate_register_ram_global(s->sram);
> +    memory_region_add_subregion(s->as, 0xA0000000, s->sram);
> +
> +    /* RAM Init */
> +    memory_region_init_ram(s->ram, "a369.ram", args->ram_size);
> +    vmstate_register_ram_global(s->ram);
> +
> +    a369_device_init(s);
> +
> +    if (args->kernel_filename) {
> +        bi = g_new0(struct arm_boot_info, 1);
> +
> +        /* RAM Address Binding */
> +        memory_region_add_subregion(s->as, 0x00000000, s->ram);
> +
>
>
> pflash_cfi01_register() already mapped ROM at address 0x0, this line causes both RAM and ROM to be mapped
> at 0x0. You should unmap ROM first.
> Even though we have memory_region_add_subregion() and memory_region_add_subregion_overlap(), looks like currently there is no difference in behaviour
> of memregions initialised with either of these two functions.
>
>

Got it, thanks
>
> +        /* Boot Info */
> +        bi->ram_size = args->ram_size;
> +        bi->kernel_filename = args->kernel_filename;
> +        bi->kernel_cmdline = args->kernel_cmdline;
> +        bi->initrd_filename = args->initrd_filename;
> +        bi->board_id = 0xa369;
> +        arm_load_kernel(s->cpu, bi);
>
>
> So, I assume this case models operation just after debootstrap loaded uboot (or kernel) into memory and remapped ROM and RAM?
> Shouldn't you set ahb_remapped and ddr_inited here then, or uboot/kernel will do it anyway?
>
>

Yes, both the ahb_remapped and ddr_inited should be set here.
The u-boot/linux-kernel would never do the AHB remap, instead the bootstrap
would do it for them.

> +    } else {
> +        /* ROM Address Binding */
> +        sysbus_mmio_map(SYS_BUS_DEVICE(s->rom), 0, 0x00000000);
> +        /* Partial RAM (before ahb remapped) Address Binding */
> +        s->ram_alias = g_new(MemoryRegion, 1);
> +        /* The address window is restricted to 256MB before remap */
> +        memory_region_init_alias(s->ram_alias, "a369.ram_alias",
> +                                 s->ram,
> +                                 0,
> +                                 MIN(0x10000000, args->ram_size));
>
>
>
> +    }
> +}
> +
> +static QEMUMachine a369_machine = {
> +    .name = "a369",
> +    .desc = "Faraday A369 (fa626te)",
> +    .init = a369_board_init,
> +    DEFAULT_MACHINE_OPTIONS,
> +};
> +
> +static void
> +a369_machine_init(void)
> +{
> +    qemu_register_machine(&a369_machine);
> +}
> +
> +machine_init(a369_machine_init);
> diff --git a/hw/arm/faraday_a369_keypad.c b/hw/arm/faraday_a369_keypad.c
> new file mode 100644
> index 0000000..0983d7e
> --- /dev/null
> +++ b/hw/arm/faraday_a369_keypad.c
> @@ -0,0 +1,234 @@
> +/*
> + * Faraday FTKBC010 emulator for A369.
> + *
> + * Copyright (c) 2012 Faraday Technology
> + * Written by Dante Su <dantesu@faraday-tech.com>
> + *
> + * The FTKBC010 is configured as a keypad controller for A369.
> + * It's a group of hard wired buttons on the board, each of them
> + * is monitored by the FTKBC010, and coordinated as (x, y).
> + * However in A369, there is a pinmux issue that the Y-axis usually
> + * malfunctioned, so there are only 3 button emulated here.
> + *
> + * This code is licensed under GNU GPL v2+
> + */
> +
> +#include <hw/hw.h>
> +#include <hw/sysbus.h>
> +#include <hw/devices.h>
> +#include <ui/console.h>
> +#include <qemu/timer.h>
> +#include <sysemu/sysemu.h>
> +
> +#include "ftkbc010.h"
> +
> +/* Key codes */
> +#define KEYCODE_ESC             1
> +#define KEYCODE_BACKSPACE       14
> +#define KEYCODE_ENTER           28
> +#define KEYCODE_SPACE           57
> +#define KEYCODE_MENU            139    /* Menu (show menu) */
> +
> +#define TYPE_FTKBC010           "a369.keypad"
> +
> +typedef struct Ftkbc010State {
> +    SysBusDevice busdev;
> +    MemoryRegion iomem;
> +    qemu_irq irq;
> +
> +    int x;
> +    int y;
> +
> +    /* HW registers */
> +    uint32_t cr;
> +    uint32_t isr;
> +} Ftkbc010State;
> +
> +#define FTKBC010(obj) \
> +    OBJECT_CHECK(Ftkbc010State, obj, TYPE_FTKBC010)
> +
> +static void ftkbc010_update(Ftkbc010State *s)
> +{
> +    uint32_t ier = 0;
> +
> +    /* keypad interrupt */
> +    ier |= (s->cr & (1 << 8)) ? (1 << 2) : 0;
> +    /* tx interrupt */
> +    ier |= (s->cr & (1 << 3)) ? (1 << 1) : 0;
> +    /* rx interrupt */
> +    ier |= (s->cr & (1 << 4)) ? (1 << 0) : 0;
> +
> +    if (ier & s->isr) {
> +        qemu_irq_raise(s->irq);
> +    } else {
> +        qemu_irq_lower(s->irq);
> +    }
> +}
> +
> +static uint64_t ftkbc010_mem_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    Ftkbc010State *s = FTKBC010(opaque);
> +
> +    switch (addr) {
> +    case REG_CR:
> +        return s->cr;
> +    case REG_ISR:
> +        return s->isr;
> +    case REG_KPDXR:
> +        return ~(1 << s->x);
> +    case REG_KPDYR:
> +        return ~(1 << s->y);
> +    case REG_REVR:
> +        return 0x00010403;  /* rev 1.4.3 */
> +    case REG_FEAR:
> +        return 0x00000808;  /* 8x8 scan code for keypad */
> +    default:
> +        break;
> +    }
> +
> +    return 0;
> +}
> +
> +static void ftkbc010_mem_write(void    *opaque,
> +                               hwaddr   addr,
> +                               uint64_t val,
> +                               unsigned size)
> +{
> +    Ftkbc010State *s = FTKBC010(opaque);
> +
> +    switch (addr) {
> +    case REG_CR:
> +        s->cr = (uint32_t)val;
> +        /* if ftkbc010 enabled */
> +        if (!(s->cr & (1 << 2))) {
> +            break;
> +        }
> +        /* if keypad interrupt cleared */
> +        if (s->cr & (1 << 10)) {
> +            s->cr &= ~(1 << 10);
> +            s->isr &= ~(1 << 2);
> +        }
> +        /* if rx interrupt cleared */
> +        if (s->cr & (1 << 7)) {
> +            s->cr &= ~(1 << 7);
> +            s->isr &= ~(1 << 0);
> +        }
> +        /* if tx interrupt cleared */
> +        if (s->cr & (1 << 6)) {
> +            s->cr &= ~(1 << 6);
> +            s->isr &= ~(1 << 1);
> +        }
> +        ftkbc010_update(s);
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +static const MemoryRegionOps ftkbc010_mem_ops = {
> +    .read  = ftkbc010_mem_read,
> +    .write = ftkbc010_mem_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +};
> +
> +static void ftkbc010_key_event(void *opaque, int scancode)
> +{
> +    Ftkbc010State *s = FTKBC010(opaque);
> +    int released = 0;
> +
> +    /* key release from qemu */
> +    if (scancode & 0x80) {
> +        released = 1;
> +    }
> +
> +    /* strip qemu key release bit */
> +    scancode &= ~0x80;
> +
> +    /* keypad interrupt */
> +    if (!released && (s->cr & (1 << 8))) {
> +        switch (scancode) {
> +        case KEYCODE_ESC:
> +        case KEYCODE_BACKSPACE:
> +            s->x = 1;
> +            break;
> +        case KEYCODE_ENTER:
> +        case KEYCODE_MENU:
> +        case KEYCODE_SPACE:
> +            s->x = 3;
> +            break;
> +        default:
> +            s->x = 2;    /* KEY_HOME */
> +            break;
> +        }
> +        s->y = 0;
> +        s->isr |= (1 << 2);
> +        ftkbc010_update(s);
> +    }
> +}
> +
> +static void ftkbc010_reset(DeviceState *ds)
> +{
> +    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
> +    Ftkbc010State *s = FTKBC010(FROM_SYSBUS(Ftkbc010State, busdev));
> +
> +    qemu_irq_lower(s->irq);
> +}
> +
> +static int ftkbc010_init(SysBusDevice *dev)
> +{
> +    Ftkbc010State *s = FTKBC010(FROM_SYSBUS(Ftkbc010State, dev));
> +
> +    s->cr  = 0;
> +    s->isr = 0;
> +    s->x   = 0;
> +    s->y   = 0;
> +
> +    memory_region_init_io(&s->iomem,
> +                          &ftkbc010_mem_ops,
> +                          s,
> +                          TYPE_FTKBC010,
> +                          0x1000);
> +    sysbus_init_mmio(dev, &s->iomem);
> +    sysbus_init_irq(dev, &s->irq);
> +
> +    qemu_add_kbd_event_handler(ftkbc010_key_event, s);
> +
> +    return 0;
> +}
> +
> +static const VMStateDescription vmstate_ftkbc010 = {
> +    .name = TYPE_FTKBC010,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .minimum_version_id_old = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(cr, Ftkbc010State),
> +        VMSTATE_UINT32(isr, Ftkbc010State),
> +        VMSTATE_END_OF_LIST(),
> +    }
> +};
> +
> +static void ftkbc010_class_init(ObjectClass *klass, void *data)
> +{
> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    k->init   = ftkbc010_init;
> +    dc->desc  = TYPE_FTKBC010;
> +    dc->vmsd  = &vmstate_ftkbc010;
> +    dc->reset = ftkbc010_reset;
> +}
> +
> +static const TypeInfo ftkbc010_info = {
> +    .name          = TYPE_FTKBC010,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(Ftkbc010State),
> +    .class_init    = ftkbc010_class_init,
> +};
> +
> +static void ftkbc010_register_types(void)
> +{
> +    type_register_static(&ftkbc010_info);
> +}
> +
> +type_init(ftkbc010_register_types)
> diff --git a/hw/arm/faraday_a369_scu.c b/hw/arm/faraday_a369_scu.c
> new file mode 100644
> index 0000000..b1c34e8
> --- /dev/null
> +++ b/hw/arm/faraday_a369_scu.c
> @@ -0,0 +1,188 @@
> +/*
> + * Faraday A369 SCU
> + *
> + * Copyright (c) 2012 Faraday Technology
> + * Written by Dante Su <dantesu@faraday-tech.com>
> + *
> + * The system control unit (SCU) is responsible for
> + * power, clock and pinmux management. Since most of
> + * the features are useless to QEMU, only partial clock
> + * and pinmux management are implemented as a set of R/W values.
> + *
> + * This code is licensed under GNU GPL v2+
> + */
> +
> +#include <hw/hw.h>
> +#include <hw/sysbus.h>
> +#include <hw/devices.h>
> +#include <ui/console.h>
> +#include <qemu/timer.h>
> +#include <sysemu/sysemu.h>
> +
> +#include "faraday.h"
> +
> +#define REG_CHIPID      0x000   /* SoC chip id */
> +#define REG_REVISON     0x004   /* SCU revision */
> +#define REG_HWCFG       0x008   /* HW configuration strap */
> +#define REG_PLL1CR      0x020   /* PLL1 control register */
> +#define REG_GPINMUX     0x200   /* General PINMUX */
> +#define REG_EXTHWCFG    0x204   /* Extended HW configuration strap */
> +#define REG_CLKCFG0     0x228   /* Clock configuration 0 */
> +#define REG_CLKCFG1     0x22C   /* Clock configuration 1 */
> +#define REG_MFPINMUX0   0x238   /* Multi-function pinmux 0 */
> +#define REG_MFPINMUX1   0x23C   /* Multi-function pinmux 1 */
> +
> +#define TYPE_A369SCU    "a369.scu"
> +
> +typedef struct A369SCUState {
> +    SysBusDevice busdev;
> +    MemoryRegion iomem;
> +
> +    /* HW registers */
> +    uint32_t general_cfg;
> +    uint32_t sclk_cfg0;
> +    uint32_t sclk_cfg1;
> +    uint32_t mfpsr0;
> +    uint32_t mfpsr1;
> +} A369SCUState;
> +
> +#define A369SCU(obj) \
> +    OBJECT_CHECK(A369SCUState, obj, TYPE_A369SCU)
> +
> +static uint64_t
> +a369scu_mem_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    A369SCUState *s = A369SCU(opaque);
> +    uint64_t ret = 0;
> +
> +    switch (addr) {
> +    case REG_CHIPID:
> +        ret = 0x00003369;   /* FIE3369 = A369 */
> +        break;
> +    case REG_REVISON:
> +        ret = 0x00010000;
> +        break;
> +    case REG_HWCFG:
> +        ret = 0x00000c10;
> +        break;
> +    case REG_PLL1CR:
> +        ret = 0x20010003;
> +        break;
> +    case REG_GPINMUX:
> +        ret = s->general_cfg;
> +        break;
> +    case REG_EXTHWCFG:
> +        ret = 0x00001cc8;
> +        break;
> +    case REG_CLKCFG0:
> +        ret = s->sclk_cfg0;
> +        break;
> +    case REG_CLKCFG1:
> +        ret = s->sclk_cfg1;
> +        break;
> +    case REG_MFPINMUX0:
> +        ret = s->mfpsr0;
> +        break;
> +    case REG_MFPINMUX1:
> +        ret = s->mfpsr1;
> +        break;
> +    }
> +
> +    return ret;
> +}
> +
> +static void
> +a369scu_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
> +{
> +    A369SCUState *s = A369SCU(opaque);
> +
> +    switch (addr) {
> +    case REG_GPINMUX:
> +        s->general_cfg = (uint32_t)val;
> +        break;
> +    case REG_CLKCFG0:
> +        s->sclk_cfg0 = (uint32_t)val;
> +        break;
> +    case REG_CLKCFG1:
> +        s->sclk_cfg1 = (uint32_t)val;
> +        break;
> +    case REG_MFPINMUX0:
> +        s->mfpsr0 = (uint32_t)val;
> +        break;
> +    case REG_MFPINMUX1:
> +        s->mfpsr1 = (uint32_t)val;
> +        break;
> +    }
> +}
> +
> +static const MemoryRegionOps a369scu_mem_ops = {
> +    .read  = a369scu_mem_read,
> +    .write = a369scu_mem_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +};
> +
> +static void a369scu_reset(DeviceState *ds)
> +{
> +    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
> +    A369SCUState *s = A369SCU(FROM_SYSBUS(A369SCUState, busdev));
> +
> +    s->general_cfg = 0x00001078;
> +    s->sclk_cfg0   = 0x26877330;
> +    s->sclk_cfg1   = 0x000a0a0a;
> +    s->mfpsr0      = 0x00000241;
> +    s->mfpsr1      = 0x00000000;
> +}
> +
> +static int a369scu_init(SysBusDevice *dev)
> +{
> +    A369SCUState *s = A369SCU(FROM_SYSBUS(A369SCUState, dev));
> +
> +    memory_region_init_io(&s->iomem,
> +                          &a369scu_mem_ops,
> +                          s,
> +                          TYPE_A369SCU,
> +                          0x1000);
> +    sysbus_init_mmio(dev, &s->iomem);
> +    return 0;
> +}
> +
> +static const VMStateDescription vmstate_a369scu = {
> +    .name = TYPE_A369SCU,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .minimum_version_id_old = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(general_cfg, A369SCUState),
> +        VMSTATE_UINT32(sclk_cfg0, A369SCUState),
> +        VMSTATE_UINT32(sclk_cfg1, A369SCUState),
> +        VMSTATE_UINT32(mfpsr0, A369SCUState),
> +        VMSTATE_UINT32(mfpsr1, A369SCUState),
> +        VMSTATE_END_OF_LIST(),
> +    }
> +};
> +
> +static void a369scu_class_init(ObjectClass *klass, void *data)
> +{
> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    k->init   = a369scu_init;
> +    dc->desc  = TYPE_A369SCU;
> +    dc->vmsd  = &vmstate_a369scu;
> +    dc->reset = a369scu_reset;
> +    dc->no_user = 1;
> +}
> +
> +static const TypeInfo a369scu_info = {
> +    .name          = TYPE_A369SCU,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(A369SCUState),
> +    .class_init    = a369scu_class_init,
> +};
> +
> +static void a369scu_register_types(void)
> +{
> +    type_register_static(&a369scu_info);
> +}
> +
> +type_init(a369scu_register_types)
> diff --git a/hw/arm/ftkbc010.h b/hw/arm/ftkbc010.h
> new file mode 100644
> index 0000000..5d25ffa
> --- /dev/null
> +++ b/hw/arm/ftkbc010.h
> @@ -0,0 +1,26 @@
> +/*
> + * Faraday FTKBC010 Keyboard/Keypad Controller
> + *
> + * Copyright (c) 2012 Faraday Technology
> + * Written by Dante Su <dantesu@faraday-tech.com>
> + *
> + * This code is licensed under GNU GPL v2+
> + */
> +#ifndef HW_ARM_FTKBC010_H
> +#define HW_ARM_FTKBC010_H
> +
> +#define REG_CR      0x00    /* control register */
> +#define REG_SRDR    0x04    /* sample rate division register */
> +#define REG_RSCR    0x08    /* request to send counter register */
> +#define REG_SR      0x0C    /* status register */
> +#define REG_ISR     0x10    /* interrupt status register */
> +#define REG_KBDRR   0x14    /* keyboard receive register */
> +#define REG_KBDTR   0x18    /* keyboard transmit register */
> +#define REG_IMR     0x1C    /* interrupt mask register */
> +#define REG_KPDXR   0x30    /* keypad X-Axis register */
> +#define REG_KPDYR   0x34    /* keypad Y-Axis register */
> +#define REG_ASPR    0x38    /* auto-scan period register */
> +#define REG_REVR    0x50    /* revision register */
> +#define REG_FEAR    0x54    /* feature register */
> +
> +#endif
>
>



--
Best wishes,
Kuo-Jung Su

Patch

diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index 59d7023..02d1a7b 100644
--- a/hw/arm/Makefile.objs
+++ b/hw/arm/Makefile.objs
@@ -34,3 +34,4 @@  obj-$(CONFIG_FDT) += ../device_tree.o

 obj-y := $(addprefix ../,$(obj-y))
 obj-y += faraday_a360.o faraday_a360_pmu.o
+obj-y += faraday_a369.o faraday_a369_scu.o faraday_a369_keypad.o
diff --git a/hw/arm/faraday_a369.c b/hw/arm/faraday_a369.c
new file mode 100644
index 0000000..e32dc7f
--- /dev/null
+++ b/hw/arm/faraday_a369.c
@@ -0,0 +1,161 @@ 
+/*
+ * Faraday A369 Evalution Board
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com> <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under GNU GPL v2+.
+ */
+
+#include <hw/sysbus.h>
+#include <hw/arm-misc.h>
+#include <hw/devices.h>
+#include <hw/i2c.h>
+#include <hw/boards.h>
+#include <hw/flash.h>
+#include <hw/serial.h>
+#include <hw/ssi.h>
+#include <net/net.h>
+#include <sysemu/sysemu.h>
+#include <sysemu/blockdev.h>
+#include <exec/address-spaces.h>
+
+#include "faraday.h"
+
+typedef FaradayMachState    A369State;
+
+/* Board init.  */
+
+static void
+a369_device_init(A369State *s)
+{
+    /* Serial (FTUART010 which is 16550A compatible) */
+    if (serial_hds[0]) {
+        serial_mm_init(s->as,
+                       0x92b00000,
+                       2,
+                       NULL,
+                       18432000 / 16,
+                       serial_hds[0],
+                       DEVICE_LITTLE_ENDIAN);
+    }
+    if (serial_hds[1]) {
+        serial_mm_init(s->as,
+                       0x92c00000,
+                       2,
+                       NULL,
+                       18432000 / 16,
+                       serial_hds[1],
+                       DEVICE_LITTLE_ENDIAN);
+    }
+
+    /* ftscu010 */
+    s->scu = sysbus_create_simple("a369.scu", 0x92000000, NULL);
+
+    /* ftkbc010 */
+    sysbus_create_simple("a369.keypad", 0x92f00000, NULL);
+}
+
+static void
+a369_board_init(QEMUMachineInitArgs *args)
+{
+    DriveInfo *dinfo;
+    struct arm_boot_info *bi = NULL;
+    A369State *s = g_new(A369State, 1);
+
+    s->as = get_system_memory();
+    s->ram = g_new(MemoryRegion, 1);
+    s->sram = g_new(MemoryRegion, 1);
+
+    /* CPU */
+    if (!args->cpu_model) {
+        args->cpu_model = "fa626te";
+    }
+
+    s->cpu = cpu_arm_init(args->cpu_model);
+    if (!s->cpu) {
+        args->cpu_model = "arm926";
+        s->cpu = cpu_arm_init(args->cpu_model);
+        if (!s->cpu) {
+            hw_error("a369: Unable to find CPU definition\n");
+            exit(1);
+        }
+    }
+
+    s->ahb_slave4 = 0x00080000; /* ROM: base=0x00000000, size=256MB */
+    s->ahb_slave6 = 0x10090000; /* RAM: base=0x10000000, size=512MB */


Does this register provide information on max allowable RAM size or amount
of RAM actually accessible in a system? I mean,
should this register be modified accordingly if args->ram_size value is
less then 512MB?

 +
+    /* A369 supports upto 512MB ram space */
+    if (args->ram_size > 0x20000000) {
+        args->ram_size = 0x20000000;
+    }

 +
+    /* Use parallel NOR flash for ROM emulation */
+    dinfo = drive_get_next(IF_PFLASH);
+    s->rom = pflash_cfi01_register(
+                    0,      /* base address */
+                    NULL,
+                    "a369.rom",
+                    6144,   /* 6 KB */
+                    dinfo ? dinfo->bdrv : NULL,


I think you should also consider a case when we're booting with bootstrap
code in ROM (no kernel_image specified). Right now,
if no rom image is specified, QEMU will abort with "trying to execute code
outside RAM" error. You could check for this case here
and abort QEMU with a more descriptive error.


 +                    1024,   /* 1 KB sector */
+                    6,      /* 6 sector per chip */
+                    4,      /* 32 bits */
+                    0, 0, 0, 0, /* id */
+                    0       /* Little Endian */);
+    if (!s->rom) {
+        hw_error("a369: failed to init ROM device.\n");
+        exit(1);
+    }
+
+    /* Embedded RAM Init */
+    memory_region_init_ram(s->sram, "a369.sram", 0x4000);
+    vmstate_register_ram_global(s->sram);
+    memory_region_add_subregion(s->as, 0xA0000000, s->sram);
+
+    /* RAM Init */
+    memory_region_init_ram(s->ram, "a369.ram", args->ram_size);
+    vmstate_register_ram_global(s->ram);
+
+    a369_device_init(s);
+
+    if (args->kernel_filename) {
+        bi = g_new0(struct arm_boot_info, 1);
+
+        /* RAM Address Binding */
+        memory_region_add_subregion(s->as, 0x00000000, s->ram);
+


pflash_cfi01_register() already mapped ROM at address 0x0, this line
causes both RAM and ROM to be mapped
at 0x0. You should unmap ROM first.
Even though we have memory_region_add_subregion() and
memory_region_add_subregion_overlap(), looks like currently there is
no difference in behaviour
of memregions initialised with either of these two functions.



 +        /* Boot Info */
+        bi->ram_size = args->ram_size;
+        bi->kernel_filename = args->kernel_filename;
+        bi->kernel_cmdline = args->kernel_cmdline;
+        bi->initrd_filename = args->initrd_filename;
+        bi->board_id = 0xa369;
+        arm_load_kernel(s->cpu, bi);


So, I assume this case models operation just after debootstrap loaded uboot
(or kernel) into memory and remapped ROM and RAM?
Shouldn't you set ahb_remapped and ddr_inited here then, or uboot/kernel
will do it anyway?

 +    } else {
+        /* ROM Address Binding */
+        sysbus_mmio_map(SYS_BUS_DEVICE(s->rom), 0, 0x00000000);
+        /* Partial RAM (before ahb remapped) Address Binding */
+        s->ram_alias = g_new(MemoryRegion, 1);
+        /* The address window is restricted to 256MB before remap */
+        memory_region_init_alias(s->ram_alias, "a369.ram_alias",
+                                 s->ram,
+                                 0,
+                                 MIN(0x10000000, args->ram_size));



 +    }
+}
+
+static QEMUMachine a369_machine = {
+    .name = "a369",
+    .desc = "Faraday A369 (fa626te)",
+    .init = a369_board_init,
+    DEFAULT_MACHINE_OPTIONS,
+};
+
+static void
+a369_machine_init(void)
+{
+    qemu_register_machine(&a369_machine);
+}
+
+machine_init(a369_machine_init);
diff --git a/hw/arm/faraday_a369_keypad.c b/hw/arm/faraday_a369_keypad.c
new file mode 100644
index 0000000..0983d7e
--- /dev/null
+++ b/hw/arm/faraday_a369_keypad.c
@@ -0,0 +1,234 @@ 
+/*
+ * Faraday FTKBC010 emulator for A369.
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com> <dantesu@faraday-tech.com>
+ *
+ * The FTKBC010 is configured as a keypad controller for A369.
+ * It's a group of hard wired buttons on the board, each of them
+ * is monitored by the FTKBC010, and coordinated as (x, y).
+ * However in A369, there is a pinmux issue that the Y-axis usually
+ * malfunctioned, so there are only 3 button emulated here.
+ *
+ * This code is licensed under GNU GPL v2+
+ */
+
+#include <hw/hw.h>
+#include <hw/sysbus.h>
+#include <hw/devices.h>
+#include <ui/console.h>
+#include <qemu/timer.h>
+#include <sysemu/sysemu.h>
+
+#include "ftkbc010.h"
+
+/* Key codes */
+#define KEYCODE_ESC             1
+#define KEYCODE_BACKSPACE       14
+#define KEYCODE_ENTER           28
+#define KEYCODE_SPACE           57
+#define KEYCODE_MENU            139    /* Menu (show menu) */
+
+#define TYPE_FTKBC010           "a369.keypad"
+
+typedef struct Ftkbc010State {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    qemu_irq irq;
+
+    int x;
+    int y;
+
+    /* HW registers */
+    uint32_t cr;
+    uint32_t isr;
+} Ftkbc010State;
+
+#define FTKBC010(obj) \
+    OBJECT_CHECK(Ftkbc010State, obj, TYPE_FTKBC010)
+
+static void ftkbc010_update(Ftkbc010State *s)
+{
+    uint32_t ier = 0;
+
+    /* keypad interrupt */
+    ier |= (s->cr & (1 << 8)) ? (1 << 2) : 0;
+    /* tx interrupt */
+    ier |= (s->cr & (1 << 3)) ? (1 << 1) : 0;
+    /* rx interrupt */
+    ier |= (s->cr & (1 << 4)) ? (1 << 0) : 0;
+
+    if (ier & s->isr) {
+        qemu_irq_raise(s->irq);
+    } else {
+        qemu_irq_lower(s->irq);
+    }
+}
+
+static uint64_t ftkbc010_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    Ftkbc010State *s = FTKBC010(opaque);
+
+    switch (addr) {
+    case REG_CR:
+        return s->cr;
+    case REG_ISR:
+        return s->isr;
+    case REG_KPDXR:
+        return ~(1 << s->x);
+    case REG_KPDYR:
+        return ~(1 << s->y);
+    case REG_REVR:
+        return 0x00010403;  /* rev 1.4.3 */
+    case REG_FEAR:
+        return 0x00000808;  /* 8x8 scan code for keypad */
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+static void ftkbc010_mem_write(void    *opaque,
+                               hwaddr   addr,
+                               uint64_t val,
+                               unsigned size)
+{
+    Ftkbc010State *s = FTKBC010(opaque);
+
+    switch (addr) {
+    case REG_CR:
+        s->cr = (uint32_t)val;
+        /* if ftkbc010 enabled */
+        if (!(s->cr & (1 << 2))) {
+            break;
+        }
+        /* if keypad interrupt cleared */
+        if (s->cr & (1 << 10)) {
+            s->cr &= ~(1 << 10);
+            s->isr &= ~(1 << 2);
+        }
+        /* if rx interrupt cleared */
+        if (s->cr & (1 << 7)) {
+            s->cr &= ~(1 << 7);
+            s->isr &= ~(1 << 0);
+        }
+        /* if tx interrupt cleared */
+        if (s->cr & (1 << 6)) {
+            s->cr &= ~(1 << 6);
+            s->isr &= ~(1 << 1);
+        }
+        ftkbc010_update(s);
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps ftkbc010_mem_ops = {
+    .read  = ftkbc010_mem_read,
+    .write = ftkbc010_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ftkbc010_key_event(void *opaque, int scancode)
+{
+    Ftkbc010State *s = FTKBC010(opaque);
+    int released = 0;
+
+    /* key release from qemu */
+    if (scancode & 0x80) {
+        released = 1;
+    }
+
+    /* strip qemu key release bit */
+    scancode &= ~0x80;
+
+    /* keypad interrupt */
+    if (!released && (s->cr & (1 << 8))) {
+        switch (scancode) {
+        case KEYCODE_ESC:
+        case KEYCODE_BACKSPACE:
+            s->x = 1;
+            break;
+        case KEYCODE_ENTER:
+        case KEYCODE_MENU:
+        case KEYCODE_SPACE:
+            s->x = 3;
+            break;
+        default:
+            s->x = 2;    /* KEY_HOME */
+            break;
+        }
+        s->y = 0;
+        s->isr |= (1 << 2);
+        ftkbc010_update(s);
+    }
+}
+
+static void ftkbc010_reset(DeviceState *ds)
+{
+    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+    Ftkbc010State *s = FTKBC010(FROM_SYSBUS(Ftkbc010State, busdev));
+
+    qemu_irq_lower(s->irq);
+}
+
+static int ftkbc010_init(SysBusDevice *dev)
+{
+    Ftkbc010State *s = FTKBC010(FROM_SYSBUS(Ftkbc010State, dev));
+
+    s->cr  = 0;
+    s->isr = 0;
+    s->x   = 0;
+    s->y   = 0;
+
+    memory_region_init_io(&s->iomem,
+                          &ftkbc010_mem_ops,
+                          s,
+                          TYPE_FTKBC010,
+                          0x1000);
+    sysbus_init_mmio(dev, &s->iomem);
+    sysbus_init_irq(dev, &s->irq);
+
+    qemu_add_kbd_event_handler(ftkbc010_key_event, s);
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_ftkbc010 = {
+    .name = TYPE_FTKBC010,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(cr, Ftkbc010State),
+        VMSTATE_UINT32(isr, Ftkbc010State),
+        VMSTATE_END_OF_LIST(),
+    }
+};
+
+static void ftkbc010_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    k->init   = ftkbc010_init;
+    dc->desc  = TYPE_FTKBC010;
+    dc->vmsd  = &vmstate_ftkbc010;
+    dc->reset = ftkbc010_reset;
+}
+
+static const TypeInfo ftkbc010_info = {
+    .name          = TYPE_FTKBC010,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Ftkbc010State),
+    .class_init    = ftkbc010_class_init,
+};
+
+static void ftkbc010_register_types(void)
+{
+    type_register_static(&ftkbc010_info);
+}
+
+type_init(ftkbc010_register_types)
diff --git a/hw/arm/faraday_a369_scu.c b/hw/arm/faraday_a369_scu.c
new file mode 100644
index 0000000..b1c34e8
--- /dev/null
+++ b/hw/arm/faraday_a369_scu.c
@@ -0,0 +1,188 @@ 
+/*
+ * Faraday A369 SCU
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com> <dantesu@faraday-tech.com>
+ *
+ * The system control unit (SCU) is responsible for
+ * power, clock and pinmux management. Since most of
+ * the features are useless to QEMU, only partial clock
+ * and pinmux management are implemented as a set of R/W values.
+ *
+ * This code is licensed under GNU GPL v2+
+ */
+
+#include <hw/hw.h>
+#include <hw/sysbus.h>
+#include <hw/devices.h>
+#include <ui/console.h>
+#include <qemu/timer.h>
+#include <sysemu/sysemu.h>
+
+#include "faraday.h"
+
+#define REG_CHIPID      0x000   /* SoC chip id */
+#define REG_REVISON     0x004   /* SCU revision */
+#define REG_HWCFG       0x008   /* HW configuration strap */
+#define REG_PLL1CR      0x020   /* PLL1 control register */
+#define REG_GPINMUX     0x200   /* General PINMUX */
+#define REG_EXTHWCFG    0x204   /* Extended HW configuration strap */
+#define REG_CLKCFG0     0x228   /* Clock configuration 0 */
+#define REG_CLKCFG1     0x22C   /* Clock configuration 1 */
+#define REG_MFPINMUX0   0x238   /* Multi-function pinmux 0 */
+#define REG_MFPINMUX1   0x23C   /* Multi-function pinmux 1 */
+
+#define TYPE_A369SCU    "a369.scu"
+
+typedef struct A369SCUState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    /* HW registers */
+    uint32_t general_cfg;
+    uint32_t sclk_cfg0;
+    uint32_t sclk_cfg1;
+    uint32_t mfpsr0;
+    uint32_t mfpsr1;
+} A369SCUState;
+
+#define A369SCU(obj) \
+    OBJECT_CHECK(A369SCUState, obj, TYPE_A369SCU)
+
+static uint64_t
+a369scu_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    A369SCUState *s = A369SCU(opaque);
+    uint64_t ret = 0;
+
+    switch (addr) {
+    case REG_CHIPID:
+        ret = 0x00003369;   /* FIE3369 = A369 */
+        break;
+    case REG_REVISON:
+        ret = 0x00010000;
+        break;
+    case REG_HWCFG:
+        ret = 0x00000c10;
+        break;
+    case REG_PLL1CR:
+        ret = 0x20010003;
+        break;
+    case REG_GPINMUX:
+        ret = s->general_cfg;
+        break;
+    case REG_EXTHWCFG:
+        ret = 0x00001cc8;
+        break;
+    case REG_CLKCFG0:
+        ret = s->sclk_cfg0;
+        break;
+    case REG_CLKCFG1:
+        ret = s->sclk_cfg1;
+        break;
+    case REG_MFPINMUX0:
+        ret = s->mfpsr0;
+        break;
+    case REG_MFPINMUX1:
+        ret = s->mfpsr1;
+        break;
+    }
+
+    return ret;
+}
+
+static void
+a369scu_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+    A369SCUState *s = A369SCU(opaque);
+
+    switch (addr) {
+    case REG_GPINMUX:
+        s->general_cfg = (uint32_t)val;
+        break;
+    case REG_CLKCFG0:
+        s->sclk_cfg0 = (uint32_t)val;
+        break;
+    case REG_CLKCFG1:
+        s->sclk_cfg1 = (uint32_t)val;
+        break;
+    case REG_MFPINMUX0:
+        s->mfpsr0 = (uint32_t)val;
+        break;
+    case REG_MFPINMUX1:
+        s->mfpsr1 = (uint32_t)val;
+        break;
+    }
+}
+
+static const MemoryRegionOps a369scu_mem_ops = {
+    .read  = a369scu_mem_read,
+    .write = a369scu_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void a369scu_reset(DeviceState *ds)
+{
+    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+    A369SCUState *s = A369SCU(FROM_SYSBUS(A369SCUState, busdev));
+
+    s->general_cfg = 0x00001078;
+    s->sclk_cfg0   = 0x26877330;
+    s->sclk_cfg1   = 0x000a0a0a;
+    s->mfpsr0      = 0x00000241;
+    s->mfpsr1      = 0x00000000;
+}
+
+static int a369scu_init(SysBusDevice *dev)
+{
+    A369SCUState *s = A369SCU(FROM_SYSBUS(A369SCUState, dev));
+
+    memory_region_init_io(&s->iomem,
+                          &a369scu_mem_ops,
+                          s,
+                          TYPE_A369SCU,
+                          0x1000);
+    sysbus_init_mmio(dev, &s->iomem);
+    return 0;
+}
+
+static const VMStateDescription vmstate_a369scu = {
+    .name = TYPE_A369SCU,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(general_cfg, A369SCUState),
+        VMSTATE_UINT32(sclk_cfg0, A369SCUState),
+        VMSTATE_UINT32(sclk_cfg1, A369SCUState),
+        VMSTATE_UINT32(mfpsr0, A369SCUState),
+        VMSTATE_UINT32(mfpsr1, A369SCUState),
+        VMSTATE_END_OF_LIST(),
+    }
+};
+
+static void a369scu_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    k->init   = a369scu_init;
+    dc->desc  = TYPE_A369SCU;
+    dc->vmsd  = &vmstate_a369scu;
+    dc->reset = a369scu_reset;
+    dc->no_user = 1;
+}
+
+static const TypeInfo a369scu_info = {
+    .name          = TYPE_A369SCU,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(A369SCUState),
+    .class_init    = a369scu_class_init,
+};
+
+static void a369scu_register_types(void)
+{
+    type_register_static(&a369scu_info);
+}
+
+type_init(a369scu_register_types)
diff --git a/hw/arm/ftkbc010.h b/hw/arm/ftkbc010.h
new file mode 100644
index 0000000..5d25ffa
--- /dev/null
+++ b/hw/arm/ftkbc010.h
@@ -0,0 +1,26 @@ 
+/*
+ * Faraday FTKBC010 Keyboard/Keypad Controller
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com> <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under GNU GPL v2+
+ */
+#ifndef HW_ARM_FTKBC010_H
+#define HW_ARM_FTKBC010_H
+
+#define REG_CR      0x00    /* control register */
+#define REG_SRDR    0x04    /* sample rate division register */
+#define REG_RSCR    0x08    /* request to send counter register */
+#define REG_SR      0x0C    /* status register */
+#define REG_ISR     0x10    /* interrupt status register */
+#define REG_KBDRR   0x14    /* keyboard receive register */
+#define REG_KBDTR   0x18    /* keyboard transmit register */
+#define REG_IMR     0x1C    /* interrupt mask register */
+#define REG_KPDXR   0x30    /* keypad X-Axis register */
+#define REG_KPDYR   0x34    /* keypad Y-Axis register */
+#define REG_ASPR    0x38    /* auto-scan period register */
+#define REG_REVR    0x50    /* revision register */
+#define REG_FEAR    0x54    /* feature register */
+
+#endif