milkymist-softusb.c 8.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/*
 *  QEMU model of the Milkymist SoftUSB block.
 *
 *  Copyright (c) 2010 Michael Walle <michael@walle.cc>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Specification available at:
 *   not available yet
 */

#include "hw.h"
#include "sysbus.h"
#include "trace.h"
#include "console.h"
28
#include "hid.h"
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
#include "qemu-error.h"

enum {
    R_CTRL = 0,
    R_MAX
};

enum {
    CTRL_RESET = (1<<0),
};

#define COMLOC_DEBUG_PRODUCE 0x1000
#define COMLOC_DEBUG_BASE    0x1001
#define COMLOC_MEVT_PRODUCE  0x1101
#define COMLOC_MEVT_BASE     0x1102
#define COMLOC_KEVT_PRODUCE  0x1142
#define COMLOC_KEVT_BASE     0x1143

struct MilkymistSoftUsbState {
    SysBusDevice busdev;
49 50
    HIDState hid_kbd;
    HIDState hid_mouse;
51

52 53 54
    MemoryRegion regs_region;
    MemoryRegion pmem;
    MemoryRegion dmem;
55 56 57 58 59 60 61 62 63 64 65 66
    qemu_irq irq;

    /* device properties */
    uint32_t pmem_base;
    uint32_t pmem_size;
    uint32_t dmem_base;
    uint32_t dmem_size;

    /* device registers */
    uint32_t regs[R_MAX];

    /* mouse state */
67
    uint8_t mouse_hid_buffer[4];
68 69

    /* keyboard state */
70
    uint8_t kbd_hid_buffer[8];
71 72 73
};
typedef struct MilkymistSoftUsbState MilkymistSoftUsbState;

74 75
static uint64_t softusb_read(void *opaque, target_phys_addr_t addr,
                             unsigned size)
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
{
    MilkymistSoftUsbState *s = opaque;
    uint32_t r = 0;

    addr >>= 2;
    switch (addr) {
    case R_CTRL:
        r = s->regs[addr];
        break;

    default:
        error_report("milkymist_softusb: read access to unknown register 0x"
                TARGET_FMT_plx, addr << 2);
        break;
    }

    trace_milkymist_softusb_memory_read(addr << 2, r);

    return r;
}

static void
98 99
softusb_write(void *opaque, target_phys_addr_t addr, uint64_t value,
              unsigned size)
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
{
    MilkymistSoftUsbState *s = opaque;

    trace_milkymist_softusb_memory_write(addr, value);

    addr >>= 2;
    switch (addr) {
    case R_CTRL:
        s->regs[addr] = value;
        break;

    default:
        error_report("milkymist_softusb: write access to unknown register 0x"
                TARGET_FMT_plx, addr << 2);
        break;
    }
}

118 119 120 121 122 123 124 125
static const MemoryRegionOps softusb_mmio_ops = {
    .read = softusb_read,
    .write = softusb_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .valid = {
        .min_access_size = 4,
        .max_access_size = 4,
    },
126 127 128 129 130 131 132
};

static inline void softusb_read_dmem(MilkymistSoftUsbState *s,
        uint32_t offset, uint8_t *buf, uint32_t len)
{
    if (offset + len >= s->dmem_size) {
        error_report("milkymist_softusb: read dmem out of bounds "
133
                "at offset 0x%x, len %d", offset, len);
134 135 136 137 138 139 140 141 142 143 144
        return;
    }

    cpu_physical_memory_read(s->dmem_base + offset, buf, len);
}

static inline void softusb_write_dmem(MilkymistSoftUsbState *s,
        uint32_t offset, uint8_t *buf, uint32_t len)
{
    if (offset + len >= s->dmem_size) {
        error_report("milkymist_softusb: write dmem out of bounds "
145
                "at offset 0x%x, len %d", offset, len);
146 147 148 149 150 151 152 153 154 155 156
        return;
    }

    cpu_physical_memory_write(s->dmem_base + offset, buf, len);
}

static inline void softusb_read_pmem(MilkymistSoftUsbState *s,
        uint32_t offset, uint8_t *buf, uint32_t len)
{
    if (offset + len >= s->pmem_size) {
        error_report("milkymist_softusb: read pmem out of bounds "
157
                "at offset 0x%x, len %d", offset, len);
158 159 160 161 162 163 164 165 166 167 168
        return;
    }

    cpu_physical_memory_read(s->pmem_base + offset, buf, len);
}

static inline void softusb_write_pmem(MilkymistSoftUsbState *s,
        uint32_t offset, uint8_t *buf, uint32_t len)
{
    if (offset + len >= s->pmem_size) {
        error_report("milkymist_softusb: write pmem out of bounds "
169
                "at offset 0x%x, len %d", offset, len);
170 171 172 173 174 175 176 177 178 179 180 181
        return;
    }

    cpu_physical_memory_write(s->pmem_base + offset, buf, len);
}

static void softusb_mouse_changed(MilkymistSoftUsbState *s)
{
    uint8_t m;

    softusb_read_dmem(s, COMLOC_MEVT_PRODUCE, &m, 1);
    trace_milkymist_softusb_mevt(m);
182
    softusb_write_dmem(s, COMLOC_MEVT_BASE + 4 * m, s->mouse_hid_buffer, 4);
183 184 185 186 187 188 189 190 191 192 193 194 195
    m = (m + 1) & 0xf;
    softusb_write_dmem(s, COMLOC_MEVT_PRODUCE, &m, 1);

    trace_milkymist_softusb_pulse_irq();
    qemu_irq_pulse(s->irq);
}

static void softusb_kbd_changed(MilkymistSoftUsbState *s)
{
    uint8_t m;

    softusb_read_dmem(s, COMLOC_KEVT_PRODUCE, &m, 1);
    trace_milkymist_softusb_kevt(m);
196
    softusb_write_dmem(s, COMLOC_KEVT_BASE + 8 * m, s->kbd_hid_buffer, 8);
197 198 199 200 201 202 203
    m = (m + 1) & 0x7;
    softusb_write_dmem(s, COMLOC_KEVT_PRODUCE, &m, 1);

    trace_milkymist_softusb_pulse_irq();
    qemu_irq_pulse(s->irq);
}

204
static void softusb_kbd_hid_datain(HIDState *hs)
205
{
206 207
    MilkymistSoftUsbState *s = container_of(hs, MilkymistSoftUsbState, hid_kbd);
    int len;
208 209 210 211 212 213

    /* if device is in reset, do nothing */
    if (s->regs[R_CTRL] & CTRL_RESET) {
        return;
    }

214
    len = hid_keyboard_poll(hs, s->kbd_hid_buffer, sizeof(s->kbd_hid_buffer));
215

216 217 218
    if (len == 8) {
        softusb_kbd_changed(s);
    }
219 220
}

221
static void softusb_mouse_hid_datain(HIDState *hs)
222
{
223 224 225
    MilkymistSoftUsbState *s =
            container_of(hs, MilkymistSoftUsbState, hid_mouse);
    int len;
226

227 228 229 230
    /* if device is in reset, do nothing */
    if (s->regs[R_CTRL] & CTRL_RESET) {
        return;
    }
231

232 233
    len = hid_pointer_poll(hs, s->mouse_hid_buffer,
            sizeof(s->mouse_hid_buffer));
234

235 236 237
    if (len == 4) {
        softusb_mouse_changed(s);
    }
238 239
}

240 241 242 243 244 245 246 247 248
static void milkymist_softusb_reset(DeviceState *d)
{
    MilkymistSoftUsbState *s =
            container_of(d, MilkymistSoftUsbState, busdev.qdev);
    int i;

    for (i = 0; i < R_MAX; i++) {
        s->regs[i] = 0;
    }
249 250 251 252 253
    memset(s->kbd_hid_buffer, 0, sizeof(s->kbd_hid_buffer));
    memset(s->mouse_hid_buffer, 0, sizeof(s->mouse_hid_buffer));

    hid_reset(&s->hid_kbd);
    hid_reset(&s->hid_mouse);
254 255 256 257 258 259 260 261 262 263 264

    /* defaults */
    s->regs[R_CTRL] = CTRL_RESET;
}

static int milkymist_softusb_init(SysBusDevice *dev)
{
    MilkymistSoftUsbState *s = FROM_SYSBUS(typeof(*s), dev);

    sysbus_init_irq(dev, &s->irq);

265 266 267
    memory_region_init_io(&s->regs_region, &softusb_mmio_ops, s,
                          "milkymist-softusb", R_MAX * 4);
    sysbus_init_mmio_region(dev, &s->regs_region);
268 269

    /* register pmem and dmem */
270 271 272 273 274 275
    memory_region_init_ram(&s->pmem, NULL, "milkymist_softusb.pmem",
                           s->pmem_size);
    sysbus_add_memory(dev, s->pmem_base, &s->pmem);
    memory_region_init_ram(&s->dmem, NULL, "milkymist_softusb.dmem",
                           s->dmem_size);
    sysbus_add_memory(dev, s->dmem_base, &s->dmem);
276

277 278
    hid_init(&s->hid_kbd, HID_KEYBOARD, softusb_kbd_hid_datain);
    hid_init(&s->hid_mouse, HID_MOUSE, softusb_mouse_hid_datain);
279 280 281 282 283 284 285 286 287 288 289

    return 0;
}

static const VMStateDescription vmstate_milkymist_softusb = {
    .name = "milkymist-softusb",
    .version_id = 1,
    .minimum_version_id = 1,
    .minimum_version_id_old = 1,
    .fields      = (VMStateField[]) {
        VMSTATE_UINT32_ARRAY(regs, MilkymistSoftUsbState, R_MAX),
290 291 292 293
        VMSTATE_HID_KEYBOARD_DEVICE(hid_kbd, MilkymistSoftUsbState),
        VMSTATE_HID_POINTER_DEVICE(hid_mouse, MilkymistSoftUsbState),
        VMSTATE_BUFFER(kbd_hid_buffer, MilkymistSoftUsbState),
        VMSTATE_BUFFER(mouse_hid_buffer, MilkymistSoftUsbState),
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
        VMSTATE_END_OF_LIST()
    }
};

static SysBusDeviceInfo milkymist_softusb_info = {
    .init = milkymist_softusb_init,
    .qdev.name  = "milkymist-softusb",
    .qdev.size  = sizeof(MilkymistSoftUsbState),
    .qdev.vmsd  = &vmstate_milkymist_softusb,
    .qdev.reset = milkymist_softusb_reset,
    .qdev.props = (Property[]) {
        DEFINE_PROP_UINT32(
                "pmem_base", MilkymistSoftUsbState, pmem_base, 0xa0000000
        ),
        DEFINE_PROP_UINT32(
                "pmem_size", MilkymistSoftUsbState, pmem_size, 0x00001000
        ),
        DEFINE_PROP_UINT32(
                "dmem_base", MilkymistSoftUsbState, dmem_base, 0xa0020000
        ),
        DEFINE_PROP_UINT32(
                "dmem_size", MilkymistSoftUsbState, dmem_size, 0x00002000
        ),
        DEFINE_PROP_END_OF_LIST(),
    }
};

static void milkymist_softusb_register(void)
{
    sysbus_register_withprop(&milkymist_softusb_info);
}

device_init(milkymist_softusb_register)