/* * UART emulation for PIC32. * * Copyright (C) 2011 Serge Vakulenko * * This file is part of the virtualmips distribution. * See LICENSE file for terms of the license. */ #define _GNU_SOURCE #include #include #include #include #include #include #include "device.h" #include "mips_memory.h" #include "pic32.h" #include "cpu.h" #include "vp_timer.h" #define UART_REG_SIZE 0x50 #define UART_TIME_OUT 1 struct pic32_uart_data { struct vdevice *dev; vtty_t *vtty; vm_instance_t *vm; vp_timer_t *uart_timer; u_int output; u_int irq; /* base irq number */ #define IRQ_ERR 0 /* error interrupt */ #define IRQ_RX 1 /* receiver interrupt */ #define IRQ_TX 2 /* transmitter interrupt */ m_uint32_t mode; /* 0x00 - mode */ m_uint32_t sta; /* 0x10 - status and control */ m_uint32_t txreg; /* 0x20 - transmit */ m_uint32_t rxreg; /* 0x30 - receive */ m_uint32_t brg; /* 0x40 - baud rate */ }; extern cpu_mips_t *current_cpu; static void pic32_tty_con_input (vtty_t * vtty) { struct pic32_uart_data *d = vtty->priv_data; if (d->mode & PIC32_UMODE_ON) { /* UART enabled. */ if (d->sta & PIC32_USTA_URXEN) { /* Receiver enabled - activate interrupt. */ d->vm->set_irq (d->vm, d->irq + IRQ_RX); /* Receive data available */ d->sta |= PIC32_USTA_URXDA; } } } void *dev_pic32_uart_access (cpu_mips_t * cpu, struct vdevice *dev, m_uint32_t offset, u_int op_size, u_int op_type, m_reg_t * data, m_uint8_t * has_set_value) { struct pic32_uart_data *d = dev->priv_data; unsigned newval; if (offset >= UART_REG_SIZE) { *data = 0; return NULL; } if (op_type == MTS_READ) { /* * Reading UART registers. */ switch (offset) { case PIC32_U1RXREG & 0xff: /* Receive data */ *data = vtty_get_char (d->vtty); if (vtty_is_char_avail (d->vtty)) { d->sta |= PIC32_USTA_URXDA; } else { d->sta &= ~PIC32_USTA_URXDA; d->vm->clear_irq (d->vm, d->irq + IRQ_RX); } break; case PIC32_U1BRG & 0xff: /* Baud rate */ *data = d->brg; break; case PIC32_U1MODE & 0xff: /* Mode */ *data = d->mode; break; case PIC32_U1STA & 0xff: /* Status and control */ d->sta |= PIC32_USTA_RIDLE | /* Receiver is idle */ PIC32_USTA_TRMT; /* Transmit shift register is empty */ if (vtty_is_char_avail (d->vtty)) d->sta |= PIC32_USTA_URXDA; *data = d->sta; #if 0 printf ("<%x>", d->sta); fflush (stdout); #endif break; case PIC32_U1TXREG & 0xff: /* Transmit */ case PIC32_U1MODECLR & 0xff: case PIC32_U1MODESET & 0xff: case PIC32_U1MODEINV & 0xff: case PIC32_U1STACLR & 0xff: case PIC32_U1STASET & 0xff: case PIC32_U1STAINV & 0xff: case PIC32_U1BRGCLR & 0xff: case PIC32_U1BRGSET & 0xff: case PIC32_U1BRGINV & 0xff: *data = 0; break; default: ASSERT (0, "reading unknown uart offset %x\n", offset); } *has_set_value = TRUE; #if 0 printf ("--- uart: read %02x -> %08x\n", offset, *data); fflush (stdout); #endif } else { /* * Writing UART registers. */ #if 0 printf ("--- uart: write %02x := %08x\n", offset, *data); fflush (stdout); #endif switch (offset) { case PIC32_U1TXREG & 0xff: /* Transmit */ /* Don't skip ^M. */ vtty_put_char (d->vtty, (char) (*data)); if ((d->mode & PIC32_UMODE_ON) && (d->sta & PIC32_USTA_UTXEN) && (d->output == 0)) { /* * yajin. * * In order to put the next data more quickly, * just set irq not waiting for * host_alarm_handler to set irq. Sorry uart, * too much work for you. * * Sometimes, linux kernel prints "serial8250: * too much work for irq9" if we print large * data on screen. Please patch the kernel. * comment "printk(KERN_ERR "serial8250: too * much work for " "irq%d\n", irq);" qemu has * some question. * http://lkml.org/lkml/2008/1/12/135 * http://kerneltrap.org/mailarchive/linux-ker * nel/2008/2/7/769924 * * If jit is used in future, we may not need to * set irq here because simulation is quick * enough. Then we have no "too much work for * irq9" problem. */ d->output = TRUE; d->vm->set_irq (d->vm, d->irq + IRQ_TX); } break; case PIC32_U1MODE & 0xff: /* Mode */ newval = *data; write_mode: d->mode = newval; if (!(d->mode & PIC32_UMODE_ON)) { d->vm->clear_irq (d->vm, d->irq + IRQ_RX); d->vm->clear_irq (d->vm, d->irq + IRQ_TX); d->sta &= ~PIC32_USTA_URXDA; d->sta &= ~(PIC32_USTA_URXDA | PIC32_USTA_FERR | PIC32_USTA_PERR | PIC32_USTA_UTXBF); d->sta |= PIC32_USTA_RIDLE | PIC32_USTA_TRMT; } break; case PIC32_U1MODECLR & 0xff: newval = d->mode & ~*data; goto write_mode; case PIC32_U1MODESET & 0xff: newval = d->mode | *data; goto write_mode; case PIC32_U1MODEINV & 0xff: newval = d->mode ^ *data; goto write_mode; case PIC32_U1STA & 0xff: /* Status and control */ newval = *data; write_sta: d->sta &= PIC32_USTA_URXDA | PIC32_USTA_FERR | PIC32_USTA_PERR | PIC32_USTA_RIDLE | PIC32_USTA_TRMT | PIC32_USTA_UTXBF; d->sta |= newval & ~(PIC32_USTA_URXDA | PIC32_USTA_FERR | PIC32_USTA_PERR | PIC32_USTA_RIDLE | PIC32_USTA_TRMT | PIC32_USTA_UTXBF); if (!(d->sta & PIC32_USTA_URXEN)) { d->vm->clear_irq (d->vm, d->irq + IRQ_RX); d->sta &= ~(PIC32_USTA_URXDA | PIC32_USTA_FERR | PIC32_USTA_PERR); } if (!(d->sta & PIC32_USTA_UTXEN)) { d->vm->clear_irq (d->vm, d->irq + IRQ_TX); d->sta &= ~PIC32_USTA_UTXBF; d->sta |= PIC32_USTA_TRMT; } break; case PIC32_U1STACLR & 0xff: newval = d->sta & ~*data; goto write_sta; case PIC32_U1STASET & 0xff: newval = d->sta | *data; goto write_sta; case PIC32_U1STAINV & 0xff: newval = d->sta ^ *data; goto write_sta; case PIC32_U1BRG & 0xff: /* Baud rate */ newval = *data; write_brg: d->brg = newval; break; case PIC32_U1BRGCLR & 0xff: newval = d->brg & ~*data; goto write_brg; case PIC32_U1BRGSET & 0xff: newval = d->brg | *data; goto write_brg; case PIC32_U1BRGINV & 0xff: newval = d->brg ^ *data; goto write_brg; case PIC32_U1RXREG & 0xff: /* Receive */ /* Ignore */ break; default: ASSERT (0, "writing unknown uart offset %x\n", offset); } *has_set_value = TRUE; } return NULL; } void dev_pic32_uart_reset (cpu_mips_t * cpu, struct vdevice *dev) { struct pic32_uart_data *d = dev->priv_data; d->mode = 0; d->sta = PIC32_USTA_RIDLE | /* Receiver is idle */ PIC32_USTA_TRMT; /* Transmit shift register is empty */ d->txreg = 0; d->rxreg = 0; d->brg = 0; } void dev_pic32_uart_cb (void *opaque) { struct pic32_uart_data *d = (struct pic32_uart_data *) opaque; d->output = 0; if (d->mode & PIC32_UMODE_ON) { /* UART enabled. */ if ((d->sta & PIC32_USTA_URXEN) && vtty_is_char_avail (d->vtty)) { /* Receive data available */ d->sta |= PIC32_USTA_URXDA; /* Activate receive interrupt. */ d->vm->set_irq (d->vm, d->irq + IRQ_RX); vp_mod_timer (d->uart_timer, vp_get_clock (rt_clock) + UART_TIME_OUT); return; } if ((d->sta & PIC32_USTA_UTXEN) && (d->output == 0)) { /* Activate transmit interrupt. */ d->output = TRUE; d->vm->set_irq (d->vm, d->irq + IRQ_TX); vp_mod_timer (d->uart_timer, vp_get_clock (rt_clock) + UART_TIME_OUT); return; } } vp_mod_timer (d->uart_timer, vp_get_clock (rt_clock) + UART_TIME_OUT); } int dev_pic32_uart_init (vm_instance_t *vm, char *name, unsigned paddr, unsigned irq, vtty_t *vtty) { struct pic32_uart_data *d; /* allocate the private data structure */ d = malloc (sizeof (*d)); if (!d) { fprintf (stderr, "PIC32 UART: unable to create device.\n"); return (-1); } memset (d, 0, sizeof (*d)); d->dev = dev_create (name); if (!d->dev) { free (d); return (-1); } d->dev->priv_data = d; d->dev->phys_addr = paddr; d->dev->phys_len = UART_REG_SIZE; d->dev->flags = VDEVICE_FLAG_NO_MTS_MMAP; d->vm = vm; (*d).vtty = vtty; d->irq = irq; vtty->priv_data = d; d->dev->handler = dev_pic32_uart_access; d->dev->reset_handler = dev_pic32_uart_reset; (*d).vtty->read_notifier = pic32_tty_con_input; d->uart_timer = vp_new_timer (rt_clock, dev_pic32_uart_cb, d); vp_mod_timer (d->uart_timer, vp_get_clock (rt_clock) + UART_TIME_OUT); vm_bind_device (vm, d->dev); return (0); }