at91_tc.c 8.54 KB
/*
 * AT91 Timer Counter
 *
 * Copyright (c) 2009 Filip Navara
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "sysbus.h"
#include "qemu-timer.h"
#include "at91.h"

#define TC_SIZE         0x4000

#define TC_CCR          0x00 /* Channel Control Register */
#define TC_CMR          0x04 /* Channel Mode Register */
#define TC_CV           0x10 /* Counter Value Register */
#define TC_RA           0x14 /* Register A */
#define TC_RB           0x18 /* Register B */
#define TC_RC           0x1c /* Register C */
#define TC_SR           0x20 /* Status Register */
#define TC_IER          0x24 /* Interrupt Enable Register */
#define TC_IDR          0x28 /* Interrupt Disable Register */
#define TC_IMR          0x2c /* Interrupt Mask Register */
#define TC_BCR          0xc0 /* Block Control Register */
#define TC_BMR          0xc4 /* Block Mode Register */

#define MR_CLKI         0x08 /* Clock Invert */
#define MR_CPCSTOP      0x40 /* Counter Clock Stopped with RC Compare */
#define MR_CPCDIS       0x80 /* Counter Clock Disable with RC Compare */
#define MR_RCTRIG       0x4000
#define MR_WAVE         0x8000

#define SR_CPAS         0x04
#define SR_CPBS         0x08
#define SR_CPCS         0x10

typedef struct TCChannelState {
    qemu_irq irq;
    ptimer_state *timer;
    uint32_t mr;
    uint16_t cv;
    uint16_t ra;
    uint16_t rb;
    uint16_t rc;
    uint32_t sr;
    uint32_t imr;
} TCChannelState;

typedef struct TCState {
    SysBusDevice busdev;
    TCChannelState channels[3];
} TCState;

static void at91_tc_tick(void *opaque)
{
    TCChannelState *s = opaque;

    s->cv++;
    /* TODO: Overflow check */
    if (s->cv == s->ra) {
        s->sr |= SR_CPAS;
    }
    if (s->cv == s->rb) {
        s->sr |= SR_CPBS;
    }
    if (s->cv == s->rc) {
        s->sr |= SR_CPCS;
        if (s->mr & MR_RCTRIG) {
            s->cv = 0;
        }
    }
    if (s->sr & s->imr) {
        qemu_set_irq(s->irq, 1);
    }
}

static uint32_t at91_tc_channel_read(TCChannelState *s,
                    target_phys_addr_t offset)
{
    uint32_t sr;

    switch (offset) {
    case TC_CMR:
        return s->mr;
    case TC_CV:
        return s->cv;
    case TC_RA:
        return s->ra;
    case TC_RB:
        return s->rb;
    case TC_RC:
        return s->rb;
    case TC_SR:
        sr = s->sr;
        s->sr = 0;
        qemu_set_irq(s->irq, 0);
        return sr;
    case TC_IMR:
        return s->imr;
    default:
        return 0;
    }
}

static void at91_tc_channel_write(TCChannelState *s,
                target_phys_addr_t offset, uint32_t value)
{
    int freq;

    switch (offset) {
    case TC_CCR:
        if ((value & 3) == 1) {
            if (s->mr & MR_WAVE) {
                s->cv = 0;
                ptimer_run(s->timer, 0);
            }
            /* TODO: Counter mode */
        } else if (value & 2) {
            ptimer_stop(s->timer);
        }
        break;
    case TC_CMR:
        if (value & MR_WAVE) {
            switch (value & 7) {
                case 0: freq = at91_master_clock_frequency / 2; break;
                case 1: freq = at91_master_clock_frequency / 8; break;
                case 2: freq = at91_master_clock_frequency / 32; break;
                case 3: freq = at91_master_clock_frequency / 128; break;
                case 4: freq = at91_master_clock_frequency / 1024; break;
                default: /* TODO: External clocks */
                    freq = at91_master_clock_frequency / 16;
                    break;
            }
            ptimer_set_freq(s->timer, freq);
        }
        /* TODO: Counter mode */
        s->mr = value;
        break;
    case TC_RA:
        s->ra = value;
        break;
    case TC_RB:
        s->rb = value;
        break;
    case TC_RC:
        s->rc = value;
        break;
    case TC_IER:
        s->imr |= value;
        break;
    case TC_IDR:
        s->imr &= ~value;
        break;
    }
}

static uint32_t at91_tc_mem_read(void *opaque, target_phys_addr_t offset)
{
    TCState *s = opaque;

    switch (offset) {
    case TC_BMR:
        return 0; /* TODO */
    case 0 ... 0x3f:
        return at91_tc_channel_read(&s->channels[0], offset);
    case 0x40 ... 0x7f:
        return at91_tc_channel_read(&s->channels[1], offset - 0x40);
    case 0x80 ... 0xbf:
        return at91_tc_channel_read(&s->channels[2], offset - 0x80);
    default:
        return 0;
    }
}

static void at91_tc_mem_write(void *opaque, target_phys_addr_t offset,
                uint32_t value)
{
    TCState *s = opaque;

    switch (offset) {
    case TC_BCR:
        return; /* TODO */
    case TC_BMR:
        return; /* TODO */
    case 0 ... 0x3f:
        at91_tc_channel_write(&s->channels[0], offset, value);
        break;
    case 0x40 ... 0x7f:
        at91_tc_channel_write(&s->channels[1], offset - 0x40, value);
        break;
    case 0x80 ... 0xbf:
        at91_tc_channel_write(&s->channels[2], offset - 0x80, value);
        break;
    }
}

static CPUReadMemoryFunc *at91_tc_readfn[] = {
    at91_tc_mem_read,
    at91_tc_mem_read,
    at91_tc_mem_read,
};

static CPUWriteMemoryFunc *at91_tc_writefn[] = {
    at91_tc_mem_write,
    at91_tc_mem_write,
    at91_tc_mem_write,
};

static void at91_tc_save(QEMUFile *f, void *opaque)
{
    TCState *s = opaque;
    int channel;

    for (channel = 0; channel < 3; channel++) {
        qemu_put_ptimer(f, s->channels[channel].timer);
        qemu_put_be32(f, s->channels[channel].mr);
        qemu_put_be16(f, s->channels[channel].cv);
        qemu_put_be16(f, s->channels[channel].ra);
        qemu_put_be16(f, s->channels[channel].rb);
        qemu_put_be16(f, s->channels[channel].rc);
        qemu_put_be32(f, s->channels[channel].sr);
        qemu_put_be32(f, s->channels[channel].imr);
    }
}

static int at91_tc_load(QEMUFile *f, void *opaque, int version_id)
{
    TCState *s = opaque;
    int channel;

    if (version_id != 1)
        return -EINVAL;

    for (channel = 0; channel < 3; channel++) {
        qemu_get_ptimer(f, s->channels[channel].timer);
        s->channels[channel].mr = qemu_get_be32(f);
        s->channels[channel].cv = qemu_get_be16(f);
        s->channels[channel].ra = qemu_get_be16(f);
        s->channels[channel].rb = qemu_get_be16(f);
        s->channels[channel].rc = qemu_get_be16(f);
        s->channels[channel].sr = qemu_get_be32(f);
        s->channels[channel].imr = qemu_get_be32(f);
    }

    return 0;
}

static void at91_tc_reset(void *opaque)
{
    TCState *s = opaque;
    int channel;

    for (channel = 0; channel < 3; channel++) {
        ptimer_stop(s->channels[channel].timer);
        s->channels[channel].mr = 0;
        s->channels[channel].cv = 0;
        s->channels[channel].ra = 0;
        s->channels[channel].rb = 0;
        s->channels[channel].rc = 0;
        s->channels[channel].sr = 0;
        s->channels[channel].imr = 0;
    }
}

static void at91_tc_init(SysBusDevice *dev)
{
    TCState *s = FROM_SYSBUS(typeof (*s), dev);
    QEMUBH *bh;
    int tc_regs;
    int channel;

    for (channel = 0; channel < 3; channel++) {
        bh = qemu_bh_new(at91_tc_tick, &s->channels[channel]);
        s->channels[channel].timer = ptimer_init(bh);
        ptimer_set_limit(s->channels[channel].timer, 1, 1);
        sysbus_init_irq(dev, &s->channels[channel].irq);
    }

    tc_regs = cpu_register_io_memory(at91_tc_readfn,
                  at91_tc_writefn, s);
    sysbus_init_mmio(dev, TC_SIZE, tc_regs);

    qemu_register_reset(at91_tc_reset, s);

    register_savevm("at91_tc", -1, 1, at91_tc_save, at91_tc_load, s);
}

static void at91_tc_register(void)
{
    sysbus_register_dev("at91,tc", sizeof(TCState), at91_tc_init);
}

device_init(at91_tc_register)