/* * QEMU model of the Canon DIGIC UART block. * * Copyright (C) 2013 Antony Pavlov * * This model is based on reverse engineering efforts * made by CHDK (http://chdk.wikia.com) and * Magic Lantern (http://www.magiclantern.fm) projects * contributors. * * See "Serial terminal" docs here: * http://magiclantern.wikia.com/wiki/Register_Map#Misc_Registers * * The QEMU model of the Milkymist UART block by Michael Walle * is used as a template. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * */ #include "hw/hw.h" #include "hw/sysbus.h" #include "sysemu/char.h" #include "hw/char/digic-uart.h" enum { ST_RX_RDY = (1 << 0), ST_TX_RDY = (1 << 1), }; static uint64_t digic_uart_read(void *opaque, hwaddr addr, unsigned size) { DigicUartState *s = opaque; uint64_t ret = 0; addr >>= 2; switch (addr) { case R_RX: s->reg_st &= ~(ST_RX_RDY); ret = s->reg_rx; break; case R_ST: ret = s->reg_st; break; default: qemu_log_mask(LOG_UNIMP, "digic-uart: read access to unknown register 0x" TARGET_FMT_plx, addr << 2); } return ret; } static void digic_uart_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { DigicUartState *s = opaque; unsigned char ch = value; addr >>= 2; switch (addr) { case R_TX: if (s->chr) { qemu_chr_fe_write_all(s->chr, &ch, 1); } break; case R_ST: /* * Ignore write to R_ST. * * The point is that this register is actively used * during receiving and transmitting symbols, * but we don't know the function of most of bits. * * Ignoring writes to R_ST is only a simplification * of the model. It has no perceptible side effects * for existing guests. */ break; default: qemu_log_mask(LOG_UNIMP, "digic-uart: write access to unknown register 0x" TARGET_FMT_plx, addr << 2); } } static const MemoryRegionOps uart_mmio_ops = { .read = digic_uart_read, .write = digic_uart_write, .valid = { .min_access_size = 4, .max_access_size = 4, }, .endianness = DEVICE_NATIVE_ENDIAN, }; static int uart_can_rx(void *opaque) { DigicUartState *s = opaque; return !(s->reg_st & ST_RX_RDY); } static void uart_rx(void *opaque, const uint8_t *buf, int size) { DigicUartState *s = opaque; assert(uart_can_rx(opaque)); s->reg_st |= ST_RX_RDY; s->reg_rx = *buf; } static void uart_event(void *opaque, int event) { } static void digic_uart_reset(DeviceState *d) { DigicUartState *s = DIGIC_UART(d); s->reg_rx = 0; s->reg_st = ST_TX_RDY; } static void digic_uart_realize(DeviceState *dev, Error **errp) { DigicUartState *s = DIGIC_UART(dev); s->chr = qemu_char_get_next_serial(); if (s->chr) { qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s); } } static void digic_uart_init(Object *obj) { DigicUartState *s = DIGIC_UART(obj); memory_region_init_io(&s->regs_region, OBJECT(s), &uart_mmio_ops, s, TYPE_DIGIC_UART, 0x18); sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->regs_region); } static const VMStateDescription vmstate_digic_uart = { .name = "digic-uart", .version_id = 1, .minimum_version_id = 1, .minimum_version_id_old = 1, .fields = (VMStateField[]) { VMSTATE_UINT32(reg_rx, DigicUartState), VMSTATE_UINT32(reg_st, DigicUartState), VMSTATE_END_OF_LIST() } }; static void digic_uart_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = digic_uart_realize; dc->reset = digic_uart_reset; dc->vmsd = &vmstate_digic_uart; } static const TypeInfo digic_uart_info = { .name = TYPE_DIGIC_UART, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(DigicUartState), .instance_init = digic_uart_init, .class_init = digic_uart_class_init, }; static void digic_uart_register_types(void) { type_register_static(&digic_uart_info); } type_init(digic_uart_register_types)