blob: 50fa5e1de8fb9f34fec19c4b5e9b18c0ddc3ab33 [file] [log] [blame]
/*
* TI OMAP processors UART emulation.
*
* Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org>
* Copyright (C) 2007-2009 Nokia Corporation
*
* 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 or
* (at your option) version 3 of the License.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu-char.h"
#include "hw.h"
#include "omap.h"
/* We use pc-style serial ports. */
#include "pc.h"
#include "exec-memory.h"
#include "sysbus.h"
/* The OMAP UART functionality is similar to the TI16C752; it is
* an enhanced version of the 16550A and we piggy-back on the 16550
* model.
*
* Currently unmodelled functionality:
* + We should have a 64 byte FIFO but QEMU's SerialState emulation
* always uses a 16 byte FIFO
* + DMA
* + interrupts based on TCR/TLR values
* + XON/XOFF flow control
* + UASR auto-baudrate-detection
*/
struct omap_uart_s {
SysBusDevice busdev;
MemoryRegion iomem;
CharDriverState *chr;
SerialState *serial; /* TODO */
const MemoryRegionOps *serial_ops;
uint32_t mmio_size;
uint32_t baudrate;
qemu_irq tx_drq;
qemu_irq rx_drq;
/* Register access mode, which affects what registers you see */
enum {
regs_operational,
regs_config_a,
regs_config_b
} access_mode;
uint8_t eblr;
uint8_t syscontrol;
uint8_t wkup;
uint8_t cfps;
uint8_t mdr[2];
uint8_t scr;
uint8_t clksel;
uint8_t blr;
uint8_t acreg;
uint8_t mcr_cache;
uint8_t efr;
uint8_t tcr;
uint8_t tlr;
uint8_t xon[2], xoff[2];
};
static int tcr_tlr_mode(struct omap_uart_s *s)
{
/* Return true if registers 0x18 and 0x1c are TCR/TLR
* (as opposed to SPR/MSR/XOFF)
*/
return (s->efr & 0x10) && (s->mcr_cache & 0x40);
}
static void omap_uart_reset(DeviceState *qdev)
{
struct omap_uart_s *s = FROM_SYSBUS(struct omap_uart_s,
sysbus_from_qdev(qdev));
s->eblr = 0x00;
s->syscontrol = 0;
s->wkup = 0x3f;
s->cfps = 0x69;
s->clksel = 0;
s->blr = 0x40;
s->acreg = 0;
s->access_mode = regs_operational;
s->mcr_cache = 0;
s->tcr = 0x0f;
s->tlr = 0;
s->efr = 0;
s->xon[0] = s->xon[1] = 0;
s->xoff[0] = s->xoff[1] = 0;
}
static uint64_t omap_uart_read(void *opaque, target_phys_addr_t addr,
unsigned size)
{
struct omap_uart_s *s = (struct omap_uart_s *) opaque;
addr &= 0xff;
switch (addr) {
case 0x00:
case 0x04:
case 0x0c:
return s->serial_ops->read(s->serial, addr, size);
case 0x08:
if (s->access_mode == regs_config_b) {
return s->efr;
}
return s->serial_ops->read(s->serial, addr, size);
case 0x10:
case 0x14:
if (s->access_mode == regs_config_b) {
return s->xon[(addr & 7) >> 2];
} else if (addr == 0x10) {
/* MCR. Bits 5 and 6 are handled by us, the rest by
* the underlying serial implementation.
*/
return s->serial_ops->read(s->serial, addr, size) | s->mcr_cache;
}
return s->serial_ops->read(s->serial, addr, size);
case 0x18:
case 0x1c:
if (tcr_tlr_mode(s)) {
return (addr == 0x18) ? s->tcr : s->tlr;
}
if (s->access_mode == regs_config_b) {
return s->xoff[(addr & 7) >> 2];
}
return s->serial_ops->read(s->serial, addr, size);
case 0x20: /* MDR1 */
return s->mdr[0];
case 0x24: /* MDR2 */
return s->mdr[1];
case 0x28: /* SFLSR */
return 0;
case 0x2c: /* RESUME */
return 0;
case 0x30: /* SFREGL */
return 0;
case 0x34: /* SFREGH */
return 0;
case 0x38: /* UASR/BLR */
if (s->access_mode != regs_operational) {
return 0; /* FIXME: return correct autodetect value */
}
return s->blr;
case 0x3c: /* ACREG */
return (s->access_mode != regs_operational) ? 0 : s->acreg;
case 0x40: /* SCR */
return s->scr;
case 0x44: /* SSR */
return 0x0;
case 0x48: /* EBLR (OMAP2) */
return s->eblr;
case 0x4C: /* OSC_12M_SEL (OMAP1) */
return s->clksel;
case 0x50: /* MVR */
return 0x30;
case 0x54: /* SYSC (OMAP2) */
return s->syscontrol;
case 0x58: /* SYSS (OMAP2) */
return 1;
case 0x5c: /* WER (OMAP2) */
return s->wkup;
case 0x60: /* CFPS (OMAP2) */
return s->cfps;
}
OMAP_BAD_REG(addr);
return 0;
}
static void omap_uart_write(void *opaque, target_phys_addr_t addr,
uint64_t value, unsigned size)
{
struct omap_uart_s *s = (struct omap_uart_s *) opaque;
addr &= 0xff;
switch (addr) {
case 0x00:
case 0x04:
s->serial_ops->write(s->serial, addr, value, size);
break;
case 0x08:
if (s->access_mode == regs_config_b) {
s->efr = value;
} else {
s->serial_ops->write(s->serial, addr, value, size);
}
break;
case 0x0c:
if ((value & 0xff) == 0xbf) {
s->access_mode = regs_config_b;
} else if (value & 0x80) {
s->access_mode = regs_config_a;
} else {
s->access_mode = regs_operational;
}
s->serial_ops->write(s->serial, addr, value, size);
break;
case 0x10:
case 0x14:
if (s->access_mode == regs_config_b) {
s->xon[(addr & 7) >> 2] = value;
} else {
if (addr == 0x10) {
/* Bits 5 and 6 are handled at this level; they can
* only be written if EFR_REG:ENHANCED_EN is set.
*/
if (s->efr & 0x10) {
s->mcr_cache = value & 0x60;
}
}
s->serial_ops->write(s->serial, addr, value, size);
}
break;
case 0x18:
case 0x1c:
if (tcr_tlr_mode(s)) {
if (addr == 0x18) {
s->tcr = value & 0xff;
} else {
s->tlr = value & 0xff;
}
} else if (s->access_mode == regs_config_b) {
s->xoff[(addr & 7) >> 2] = value;
} else {
s->serial_ops->write(s->serial, addr, value, size);
}
break;
case 0x20: /* MDR1 */
s->mdr[0] = value & 0x7f;
break;
case 0x24: /* MDR2 */
s->mdr[1] = value & 0xff;
break;
case 0x28: /* TXFLL */
case 0x2c: /* TXFLH */
case 0x30: /* RXFLL */
case 0x34: /* RXFLH */
/* ignored */
break;
case 0x38: /* BLR */
if (s->access_mode == regs_operational) {
s->blr = value & 0xc0;
}
break;
case 0x3c: /* ACREG */
if (s->access_mode == regs_operational) {
s->acreg = value & 0xff;
}
break;
case 0x40: /* SCR */
s->scr = value & 0xff;
break;
case 0x44: /* SSR */
OMAP_RO_REG(addr);
break;
case 0x48: /* EBLR (OMAP2) */
s->eblr = value & 0xff;
break;
case 0x4C: /* OSC_12M_SEL (OMAP1) */
s->clksel = value & 1;
break;
case 0x50: /* MVR */
OMAP_RO_REG(addr);
break;
case 0x54: /* SYSC (OMAP2) */
s->syscontrol = value & 0x1d;
if (value & 2) {
/* TODO: reset s->serial also. */
omap_uart_reset(&s->busdev.qdev);
}
break;
case 0x58: /* SYSS (OMAP2) */
OMAP_RO_REG(addr);
break;
case 0x5c: /* WER (OMAP2) */
s->wkup = value & 0x7f;
break;
case 0x60: /* CFPS (OMAP2) */
s->cfps = value & 0xff;
break;
default:
OMAP_BAD_REG(addr);
}
}
static const MemoryRegionOps omap_uart_ops = {
.read = omap_uart_read,
.write = omap_uart_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static int omap_uart_init(SysBusDevice *busdev)
{
struct omap_uart_s *s = FROM_SYSBUS(struct omap_uart_s, busdev);
if (!s->chr) {
s->chr = qemu_chr_new(busdev->qdev.id, "null", NULL);
}
/* TODO: DMA support. Current 16550A emulation does not emulate DMA mode
* transfers via TXRDY/RXRDY pins. We create DMA irq lines here for
* future use nevertheless. */
/* Nasty hackery because trying to extend an existing device is
* not really supported, and the serial driver isn't even qdev.
*/
s->serial = serial_mm_init(NULL, 0, 2, NULL, s->baudrate, s->chr,
DEVICE_NATIVE_ENDIAN);
s->serial_ops = serial_get_memops(DEVICE_NATIVE_ENDIAN);
sysbus_init_irq(busdev, serial_get_irq(s->serial));
sysbus_init_irq(busdev, &s->tx_drq);
sysbus_init_irq(busdev, &s->rx_drq);
memory_region_init_io(&s->iomem, &omap_uart_ops, s, "omap_uart",
s->mmio_size);
sysbus_init_mmio_region(busdev, &s->iomem);
return 0;
}
static SysBusDeviceInfo omap_uart_info = {
.init = omap_uart_init,
.qdev.name = "omap_uart",
.qdev.size = sizeof(struct omap_uart_s),
.qdev.reset = omap_uart_reset,
.qdev.props = (Property[]) {
DEFINE_PROP_UINT32("mmio_size", struct omap_uart_s, mmio_size, 0x400),
DEFINE_PROP_UINT32("baudrate", struct omap_uart_s, baudrate, 0),
DEFINE_PROP_CHR("chardev", struct omap_uart_s, chr),
DEFINE_PROP_END_OF_LIST()
}
};
static void omap_uart_register_device(void)
{
sysbus_register_withprop(&omap_uart_info);
}
void omap_uart_attach(DeviceState *qdev, CharDriverState *chr,
const char *label)
{
struct omap_uart_s *s = FROM_SYSBUS(struct omap_uart_s,
sysbus_from_qdev(qdev));
s->chr = chr ?: qemu_chr_new(label, "null", NULL);
serial_change_char_driver(s->serial, s->chr);
}
device_init(omap_uart_register_device)