Patchwork [V2,1/4] imx.31 and KZM board support: UART support

login
register
mail settings
Submitter Peter Chubb
Date Nov. 22, 2011, 4:32 a.m.
Message ID <w462icbwug.wl%peter@chubb.wattle.id.au>
Download mbox | patch
Permalink /patch/126996/
State New
Headers show

Comments

Peter Chubb - Nov. 22, 2011, 4:32 a.m.
Implement the FreeScale i.MX UART.  This uart is used in a variety of
SoCs, including some by Motorola, as well as in the FreeScale i.MX
series.

Signed-off-by: Hans Jang <hsjang@ok-labs.com>
Signed-off-by: Adam Clench <adamc@ok-labs.com>
Signed-off-by: Peter Chubb <peter.chubb@nicta.com.au>
---
 hw/imx_serial.c |  307 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 307 insertions(+)
 create mode 100644 hw/imx_serial.c


--
Dr Peter Chubb  http://www.gelato.unsw.edu.au  peterc AT gelato.unsw.edu.au
http://www.ertos.nicta.com.au           ERTOS within National ICT Australia
Peter Maydell - Nov. 24, 2011, 6:53 p.m.
On 22 November 2011 04:32, Peter Chubb <peter.chubb@nicta.com.au> wrote:
>
> Implement the FreeScale i.MX UART.  This uart is used in a variety of
> SoCs, including some by Motorola, as well as in the FreeScale i.MX
> series.
>
> Signed-off-by: Hans Jang <hsjang@ok-labs.com>
> Signed-off-by: Adam Clench <adamc@ok-labs.com>
> Signed-off-by: Peter Chubb <peter.chubb@nicta.com.au>
> ---
>  hw/imx_serial.c |  307 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 307 insertions(+)
>  create mode 100644 hw/imx_serial.c
>
> Index: qemu-working/hw/imx_serial.c
> ===================================================================
> --- /dev/null   1970-01-01 00:00:00.000000000 +0000
> +++ qemu-working/hw/imx_serial.c        2011-11-22 14:47:09.242035743 +1100
> @@ -0,0 +1,307 @@
> +/*
> + * IMX31 UARTS
> + *
> + * Copyright (c) 2008 OKL
> + * Written by Hans
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2.  See
> + * the COPYING file in the top-level directory.

Really v2 only? (you will need an ack from the copyright holder to say
'2 or later' here, though).

> + * This is a `bare-bones' implementation of the IMX series serial ports.
> + * TODO:
> + *  -- implement FIFOs.  The real hardware has 32 word transmit
> + *                       and receive FIFOs

I assume readbuff is currently standing in as a single word fifo?
Might be worth a comment. (You'll break save/restore compatibility
when you add the fifos properly but I don't think we need to worry
about that.)

> + *  -- implement DMA
> + *  -- implement BAUD-rate and modem lines, for when the backend
> + *     is a real serial device.
> + */
> +
> +#include "hw.h"
> +#include "sysbus.h"
> +#include "qemu-char.h"
> +
> +//#define DEBUG_SERIAL 1
> +
> +#ifdef DEBUG_SERIAL
> +#define DPRINTF(fmt, args...) \
> +do { printf("imx_serial: " fmt , ##args); } while (0)
> +#else
> +#define DPRINTF(fmt, args...) do {} while (0)
> +#endif
> +
> +/*
> + * Print a message at most ten times.
> + */
> +#define scream(fmt, args...) \
> +    do { \
> +        static int printable = 10;\
> +        if (printable--) { \
> +            fprintf(stderr, fmt, ##args); \
> +        } \
> +    } while (0)

Drop this.

> +
> +
> +typedef struct {
> +    SysBusDevice busdev;
> +    MemoryRegion iomem;
> +    int32_t readbuff;
> +
> +    uint32_t usr1;
> +    uint32_t usr2;
> +    uint32_t ucr1;
> +    uint32_t uts1;
> +
> +    uint32_t ubrm;
> +    uint32_t ubrc;
> +
> +    qemu_irq irq;
> +    CharDriverState *chr;
> +} imx_state;
> +
> +static const VMStateDescription vmstate_imx_serial  = {
> +    .name = "imx-serial",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .minimum_version_id_old = 1,
> +    .fields = (VMStateField []) {

checkpatch complains about this space before the [].

> +        VMSTATE_UINT32(usr1, imx_state),
> +        VMSTATE_UINT32(usr2, imx_state),
> +        VMSTATE_UINT32(ucr1, imx_state),
> +        VMSTATE_UINT32(uts1, imx_state),
> +        VMSTATE_UINT32(ubrm, imx_state),
> +        VMSTATE_UINT32(ubrc, imx_state),
> +        VMSTATE_END_OF_LIST()

This list seems to be missing readbuff.

> +    },
> +};
> +
> +
> +#define URXD_CHARRDY    (1<<15)   /* character read is valid */
> +
> +#define USR1_TRDY       (1<<13)   /* Xmitter ready */
> +#define USR1_RRDY       (1<<9)    /* receiver ready */
> +
> +#define USR2_TXFE       (1<<14)   /* Transmit FIFO empty */
> +#define USR2_RDR        (1<<0)    /* Receive data ready */
> +#define USR2_TXDC       (1<<3)    /* Transmission complete */
> +
> +#define UCR1_UARTEN     (1<<0)
> +#define UCR1_RRDYEN     (1<<9)
> +#define UCR1_TRDYEN     (1<<13)
> +#define UCR1_TXMPTYEN   (1<<6)

Is there any significance to the order of these?

> +#define UTS1_TXEMPTY    (1<<6)
> +#define UTS1_RXEMPTY    (1<<5)
> +#define UTS1_TXFULL     (1<<4)
> +#define UTS1_RXFULL     (1<<3)
> +
> +static void imx_update(imx_state *s)
> +{
> +    uint32_t flags;
> +
> +    flags = ((s->usr1 & s->ucr1)) & (USR1_TRDY|USR1_RRDY);
> +    if (!(s->ucr1 & UCR1_TXMPTYEN)) {
> +        flags &= ~USR1_TRDY;
> +    }
> +
> +    qemu_set_irq(s->irq, !!flags);
> +}
> +
> +static void imx_serial_reset(imx_state *s)
> +{
> +    s->usr1 = USR1_TRDY;
> +    s->usr2 = USR2_TXFE | USR2_TXDC;
> +    s->uts1 = UTS1_RXEMPTY;
> +    s->ubrm = 0;
> +    s->ubrc = 0;
> +    s->readbuff = 0;
> +}
> +
> +static uint64_t imx_serial_read(void *opaque, target_phys_addr_t offset,
> +                                unsigned size)
> +{
> +    imx_state *s = (imx_state *)opaque;
> +    uint32_t c;
> +
> +    DPRINTF("read(offset=%x)\n", offset >> 2);
> +    switch (offset >> 2) {
> +    case 0x0: /* URXD */
> +        c = s->readbuff;
> +        s->usr1 &= ~USR1_RRDY;
> +        s->usr2 &= ~USR2_RDR;
> +        s->uts1 |= UTS1_RXEMPTY;
> +        imx_update(s);
> +        qemu_chr_accept_input(s->chr);
> +        return c | URXD_CHARRDY;
> +
> +    case 0x20: /* UCR1 */
> +        return s->ucr1;
> +
> +    case 0x21: /* UCR2 */
> +        return 1; /* reset complete */
> +
> +    case 0x25: /* USR1 */
> +        imx_update(s);
> +        return s->usr1;

Why do we need to call imx_update() in the read function?
Surely it should be called only where we update the registers
which affect interrupt status?

> +
> +    case 0x26: /* USR2 */
> +        imx_update(s);
> +        return s->usr2;
> +
> +

Stray blank line. (There are a few of these in the file.)

> +    case 0x2A: /* BRM Modulator */
> +        return s->ubrm;
> +
> +    case 0x2B: /* Baud Rate Count */
> +        return s->ubrc;
> +
> +    case 0x2d: /* UTS1 */
> +        return s->uts1;
> +
> +
> +    case 0x22: /* UCR3 */
> +    case 0x23: /* UCR4 */
> +    case 0x24: /* UFCR */
> +    case 0x29: /* BRM Incremental */
> +        return 0x0; /* TODO */
> +
> +    default:
> +        scream("imx_serial_read: bad offset: 0x%x\n", (int)offset);
> +        return 0;
> +    }
> +}
> +
> +
> +static void imx_serial_write(void *opaque, target_phys_addr_t offset,
> +                      uint64_t value, unsigned size)
> +{
> +    imx_state *s = (imx_state *)opaque;
> +    unsigned char ch;
> +
> +    DPRINTF("write(offset=%x, value = %x)\n", offset >> 2, (unsigned int)value);
> +    switch (offset >> 2) {
> +    case 0x10: /* UTXD */
> +        ch = value;
> +        if (s->chr) {
> +            qemu_chr_fe_write(s->chr, &ch, 1);
> +        }
> +        s->usr1 &= ~USR1_TRDY;
> +        imx_update(s);
> +        s->usr1 |= USR1_TRDY;
> +        imx_update(s);
> +
> +        break;
> +
> +    case 0x20: /* UCR1 */
> +        s->ucr1 = value;
> +        DPRINTF("write(ucr1=%x)\n", (unsigned int)value);
> +        imx_update(s);
> +        break;
> +
> +    case 0x21: /* UCR2 */
> +        if (!(value & 1)) {
> +            imx_serial_reset(s);
> +        }
> +        break;
> +
> +    case 0x26: /* USR2 */
> +       /*
> +        * Writing 1 to some bits clears them; all other
> +        * values are ignored
> +        */
> +        value &= (1<<15) | (1<<13) | (1<<12) | (1<<11) | (1<<10)|
> +            (1<<8) | (1<<7) | (1<<6) | (1<<4) | (1<<2) | (1<<1);
> +        s->usr2 &= ~value;
> +        break;
> +
> +        /* Linux expects to see what it writes here. */
> +        /* We don't currently alter the baud rate */
> +    case 0x29: /* UBIR */
> +        s->ubrc = value;
> +        break;
> +
> +    case 0x2a: /* UBRM */
> +        s->ubrm = value;
> +        break;
> +
> +    case 0x2d: /* UTS1 */
> +    case 0x22: /* UCR3 */
> +    case 0x23: /* UCR4 */
> +    case 0x24: /* UFCR */
> +    case 0x25: /* USR1 */
> +    case 0x2c: /* BIPR1 */
> +        /* TODO */
> +        break;
> +
> +    default:
> +        scream("imx_serial_write: Bad offset 0x%x\n", (int)offset);
> +    }
> +}
> +
> +static int imx_can_receive(void *opaque)
> +{
> +    imx_state *s = (imx_state *)opaque;
> +    return !(s->usr1 & USR1_RRDY);
> +}
> +
> +static void imx_put_data(void *opaque, uint32_t value)
> +{
> +    imx_state *s = (imx_state *)opaque;
> +
> +    s->usr1 |= USR1_RRDY;
> +    s->usr2 |= USR2_RDR;
> +    s->uts1 &= ~UTS1_RXEMPTY;
> +    s->readbuff = value;
> +    imx_update(s);
> +}
> +
> +static void imx_receive(void *opaque, const uint8_t *buf, int size)
> +{
> +    imx_put_data(opaque, *buf);
> +}
> +
> +static void imx_event(void *opaque, int event)
> +{
> +    if (event == CHR_EVENT_BREAK) {
> +        imx_put_data(opaque, 0x400);
> +    }
> +}
> +
> +
> +static const struct MemoryRegionOps imx_serial_ops = {
> +    .read = imx_serial_read,
> +    .write = imx_serial_write,
> +    .endianness = DEVICE_NATIVE_ENDIAN,
> +};
> +
> +static int imx_serial_init(SysBusDevice *dev)
> +{
> +    imx_state *s = FROM_SYSBUS(imx_state, dev);
> +
> +    memory_region_init_io(&s->iomem, &imx_serial_ops, s, "imx-serial", 0x1000);
> +    sysbus_init_mmio_region(dev, &s->iomem);
> +    sysbus_init_irq(dev, &s->irq);
> +    s->chr = qdev_init_chardev(&dev->qdev);
> +    imx_serial_reset(s);

If you put imx_serial_reset in a SysBusDeviceInfo struct as the
.qdev.reset pointer you don't need to call it from your init function.

> +    /*
> +     * enable the uart on boot, so messages from the linux decompresser
> +     * are visible
> +     */
> +    s->ucr1 = UCR1_UARTEN;

Is this matching the hardware's behaviour on cold boot vs warm
boot, or is it just a random hack?

> +    if (s->chr) {
> +        qemu_chr_add_handlers(s->chr, imx_can_receive, imx_receive,
> +                              imx_event, s);
> +    }
> +    vmstate_register(&dev->qdev, -1, &vmstate_imx_serial, s);

Put this in SysBusDeviceInfo .qdev.vmsd, don't call vmstate_register().

> +    return 0;
> +}
> +
> +
> +static void imx_serial_register_devices(void)
> +{
> +    DPRINTF("imx_serial_register_devices\n");
> +    sysbus_register_dev("imx_serial", sizeof(imx_state),
> +                        imx_serial_init);

Use sysbus_register_withprop() so you can pass it a SysBusDeviceInfo.

-- PMM
Peter Chubb - Nov. 24, 2011, 10:18 p.m.
Thanks Peter,
       I've fixed the problems you noted, and have asked the OK-Labs
       folks about Licencing.  When I hear back from them I'll roll a
       new patchset --- I don't expect `GPL v2 or later' to be a
       problem.

       -- Peter C
--
Dr Peter Chubb  http://www.gelato.unsw.edu.au  peterc AT gelato.unsw.edu.au
http://www.ertos.nicta.com.au           ERTOS within National ICT Australia

Patch

Index: qemu-working/hw/imx_serial.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ qemu-working/hw/imx_serial.c	2011-11-22 14:47:09.242035743 +1100
@@ -0,0 +1,307 @@ 
+/*
+ * IMX31 UARTS
+ *
+ * Copyright (c) 2008 OKL
+ * Written by Hans
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ *
+ * This is a `bare-bones' implementation of the IMX series serial ports.
+ * TODO:
+ *  -- implement FIFOs.  The real hardware has 32 word transmit
+ *                       and receive FIFOs
+ *  -- implement DMA
+ *  -- implement BAUD-rate and modem lines, for when the backend
+ *     is a real serial device.
+ */
+
+#include "hw.h"
+#include "sysbus.h"
+#include "qemu-char.h"
+
+//#define DEBUG_SERIAL 1
+
+#ifdef DEBUG_SERIAL
+#define DPRINTF(fmt, args...) \
+do { printf("imx_serial: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+/*
+ * Print a message at most ten times.
+ */
+#define scream(fmt, args...) \
+    do { \
+        static int printable = 10;\
+        if (printable--) { \
+            fprintf(stderr, fmt, ##args); \
+        } \
+    } while (0)
+
+
+typedef struct {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    int32_t readbuff;
+
+    uint32_t usr1;
+    uint32_t usr2;
+    uint32_t ucr1;
+    uint32_t uts1;
+
+    uint32_t ubrm;
+    uint32_t ubrc;
+
+    qemu_irq irq;
+    CharDriverState *chr;
+} imx_state;
+
+static const VMStateDescription vmstate_imx_serial  = {
+    .name = "imx-serial",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField []) {
+        VMSTATE_UINT32(usr1, imx_state),
+        VMSTATE_UINT32(usr2, imx_state),
+        VMSTATE_UINT32(ucr1, imx_state),
+        VMSTATE_UINT32(uts1, imx_state),
+        VMSTATE_UINT32(ubrm, imx_state),
+        VMSTATE_UINT32(ubrc, imx_state),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+
+#define URXD_CHARRDY    (1<<15)   /* character read is valid */
+
+#define USR1_TRDY       (1<<13)   /* Xmitter ready */
+#define USR1_RRDY       (1<<9)    /* receiver ready */
+
+#define USR2_TXFE       (1<<14)   /* Transmit FIFO empty */
+#define USR2_RDR        (1<<0)    /* Receive data ready */
+#define USR2_TXDC       (1<<3)    /* Transmission complete */
+
+#define UCR1_UARTEN     (1<<0)
+#define UCR1_RRDYEN     (1<<9)
+#define UCR1_TRDYEN     (1<<13)
+#define UCR1_TXMPTYEN   (1<<6)
+
+#define UTS1_TXEMPTY    (1<<6)
+#define UTS1_RXEMPTY    (1<<5)
+#define UTS1_TXFULL     (1<<4)
+#define UTS1_RXFULL     (1<<3)
+
+static void imx_update(imx_state *s)
+{
+    uint32_t flags;
+
+    flags = ((s->usr1 & s->ucr1)) & (USR1_TRDY|USR1_RRDY);
+    if (!(s->ucr1 & UCR1_TXMPTYEN)) {
+        flags &= ~USR1_TRDY;
+    }
+
+    qemu_set_irq(s->irq, !!flags);
+}
+
+static void imx_serial_reset(imx_state *s)
+{
+    s->usr1 = USR1_TRDY;
+    s->usr2 = USR2_TXFE | USR2_TXDC;
+    s->uts1 = UTS1_RXEMPTY;
+    s->ubrm = 0;
+    s->ubrc = 0;
+    s->readbuff = 0;
+}
+
+static uint64_t imx_serial_read(void *opaque, target_phys_addr_t offset,
+                                unsigned size)
+{
+    imx_state *s = (imx_state *)opaque;
+    uint32_t c;
+
+    DPRINTF("read(offset=%x)\n", offset >> 2);
+    switch (offset >> 2) {
+    case 0x0: /* URXD */
+        c = s->readbuff;
+        s->usr1 &= ~USR1_RRDY;
+        s->usr2 &= ~USR2_RDR;
+        s->uts1 |= UTS1_RXEMPTY;
+        imx_update(s);
+        qemu_chr_accept_input(s->chr);
+        return c | URXD_CHARRDY;
+
+    case 0x20: /* UCR1 */
+        return s->ucr1;
+
+    case 0x21: /* UCR2 */
+        return 1; /* reset complete */
+
+    case 0x25: /* USR1 */
+        imx_update(s);
+        return s->usr1;
+
+    case 0x26: /* USR2 */
+        imx_update(s);
+        return s->usr2;
+
+
+    case 0x2A: /* BRM Modulator */
+        return s->ubrm;
+
+    case 0x2B: /* Baud Rate Count */
+        return s->ubrc;
+
+    case 0x2d: /* UTS1 */
+        return s->uts1;
+
+
+    case 0x22: /* UCR3 */
+    case 0x23: /* UCR4 */
+    case 0x24: /* UFCR */
+    case 0x29: /* BRM Incremental */
+        return 0x0; /* TODO */
+
+    default:
+        scream("imx_serial_read: bad offset: 0x%x\n", (int)offset);
+        return 0;
+    }
+}
+
+
+static void imx_serial_write(void *opaque, target_phys_addr_t offset,
+                      uint64_t value, unsigned size)
+{
+    imx_state *s = (imx_state *)opaque;
+    unsigned char ch;
+
+    DPRINTF("write(offset=%x, value = %x)\n", offset >> 2, (unsigned int)value);
+    switch (offset >> 2) {
+    case 0x10: /* UTXD */
+        ch = value;
+        if (s->chr) {
+            qemu_chr_fe_write(s->chr, &ch, 1);
+        }
+        s->usr1 &= ~USR1_TRDY;
+        imx_update(s);
+        s->usr1 |= USR1_TRDY;
+        imx_update(s);
+
+        break;
+
+    case 0x20: /* UCR1 */
+        s->ucr1 = value;
+        DPRINTF("write(ucr1=%x)\n", (unsigned int)value);
+        imx_update(s);
+        break;
+
+    case 0x21: /* UCR2 */
+        if (!(value & 1)) {
+            imx_serial_reset(s);
+        }
+        break;
+
+    case 0x26: /* USR2 */
+       /*
+        * Writing 1 to some bits clears them; all other
+        * values are ignored
+        */
+        value &= (1<<15) | (1<<13) | (1<<12) | (1<<11) | (1<<10)|
+            (1<<8) | (1<<7) | (1<<6) | (1<<4) | (1<<2) | (1<<1);
+        s->usr2 &= ~value;
+        break;
+
+        /* Linux expects to see what it writes here. */
+        /* We don't currently alter the baud rate */
+    case 0x29: /* UBIR */
+        s->ubrc = value;
+        break;
+
+    case 0x2a: /* UBRM */
+        s->ubrm = value;
+        break;
+
+    case 0x2d: /* UTS1 */
+    case 0x22: /* UCR3 */
+    case 0x23: /* UCR4 */
+    case 0x24: /* UFCR */
+    case 0x25: /* USR1 */
+    case 0x2c: /* BIPR1 */
+        /* TODO */
+        break;
+
+    default:
+        scream("imx_serial_write: Bad offset 0x%x\n", (int)offset);
+    }
+}
+
+static int imx_can_receive(void *opaque)
+{
+    imx_state *s = (imx_state *)opaque;
+    return !(s->usr1 & USR1_RRDY);
+}
+
+static void imx_put_data(void *opaque, uint32_t value)
+{
+    imx_state *s = (imx_state *)opaque;
+
+    s->usr1 |= USR1_RRDY;
+    s->usr2 |= USR2_RDR;
+    s->uts1 &= ~UTS1_RXEMPTY;
+    s->readbuff = value;
+    imx_update(s);
+}
+
+static void imx_receive(void *opaque, const uint8_t *buf, int size)
+{
+    imx_put_data(opaque, *buf);
+}
+
+static void imx_event(void *opaque, int event)
+{
+    if (event == CHR_EVENT_BREAK) {
+        imx_put_data(opaque, 0x400);
+    }
+}
+
+
+static const struct MemoryRegionOps imx_serial_ops = {
+    .read = imx_serial_read,
+    .write = imx_serial_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int imx_serial_init(SysBusDevice *dev)
+{
+    imx_state *s = FROM_SYSBUS(imx_state, dev);
+
+    memory_region_init_io(&s->iomem, &imx_serial_ops, s, "imx-serial", 0x1000);
+    sysbus_init_mmio_region(dev, &s->iomem);
+    sysbus_init_irq(dev, &s->irq);
+    s->chr = qdev_init_chardev(&dev->qdev);
+    imx_serial_reset(s);
+    /*
+     * enable the uart on boot, so messages from the linux decompresser
+     * are visible
+     */
+    s->ucr1 = UCR1_UARTEN;
+
+    if (s->chr) {
+        qemu_chr_add_handlers(s->chr, imx_can_receive, imx_receive,
+                              imx_event, s);
+    }
+    vmstate_register(&dev->qdev, -1, &vmstate_imx_serial, s);
+    return 0;
+}
+
+
+static void imx_serial_register_devices(void)
+{
+    DPRINTF("imx_serial_register_devices\n");
+    sysbus_register_dev("imx_serial", sizeof(imx_state),
+                        imx_serial_init);
+}
+
+device_init(imx_serial_register_devices)