*  Derived from ivtv-driver.c
  *
  *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *  Copyright (C) 2008  Andy Walls <awalls@radix.net>
  *
  *  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
                                     -1, -1, -1, -1, -1, -1, -1, -1,
                                     -1, -1, -1, -1, -1, -1, -1, -1,
                                     -1, -1, -1, -1, -1, -1, -1, -1 };
-
+static int mmio_ndelay[CX18_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1,
+                                          -1, -1, -1, -1, -1, -1, -1, -1,
+                                          -1, -1, -1, -1, -1, -1, -1, -1,
+                                          -1, -1, -1, -1, -1, -1, -1, -1 };
 static unsigned cardtype_c = 1;
 static unsigned tuner_c = 1;
 static unsigned radio_c = 1;
+static int mmio_ndelay_c = 1;
 static char pal[] = "--";
 static char secam[] = "--";
 static char ntsc[] = "-";
 module_param_array(tuner, int, &tuner_c, 0644);
 module_param_array(radio, bool, &radio_c, 0644);
 module_param_array(cardtype, int, &cardtype_c, 0644);
+module_param_array(mmio_ndelay, int, &mmio_ndelay_c, 0644);
 module_param_string(pal, pal, sizeof(pal), 0644);
 module_param_string(secam, secam, sizeof(secam), 0644);
 module_param_string(ntsc, ntsc, sizeof(ntsc), 0644);
 MODULE_PARM_DESC(cx18_pci_latency,
                 "Change the PCI latency to 64 if lower: 0 = No, 1 = Yes,\n"
                 "\t\t\tDefault: Yes");
+MODULE_PARM_DESC(mmio_ndelay,
+                "Delay (ns) for each CX23418 memory mapped IO access.\n"
+                "\t\t\tTry larger values that are close to a multiple of the\n"
+                "\t\t\tPCI clock period, 30.3 ns, if your card doesn't work.\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_MMIO_NDELAY));
 MODULE_PARM_DESC(enc_mpg_buffers,
                 "Encoder MPG Buffers (in MB)\n"
                 "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_MPG_BUFFERS));
        cx->options.tuner = tuner[cx->num];
        cx->options.radio = radio[cx->num];
 
+       if (mmio_ndelay[cx->num] < 0)
+               cx->options.mmio_ndelay = CX18_DEFAULT_MMIO_NDELAY;
+       else
+               cx->options.mmio_ndelay = mmio_ndelay[cx->num];
+
        cx->std = cx18_parse_std(cx);
        if (cx->options.cardtype == -1) {
                CX18_INFO("Ignore card\n");
 
 #  error "This driver requires kernel PCI support."
 #endif
 
+/* Default delay to throttle mmio access to the CX23418 so it doesn't choke */
+#define CX18_DEFAULT_MMIO_NDELAY 31 /* 30.3 ns = 1 PCI clock(s) / 33 MHz */
+
 #define CX18_MEM_OFFSET        0x00000000
 #define CX18_MEM_SIZE  0x04000000
 #define CX18_REG_OFFSET        0x02000000
        int cardtype;           /* force card type on load */
        int tuner;              /* set tuner on load */
        int radio;              /* enable/disable radio */
+       unsigned long mmio_ndelay; /* delay in ns after every PCI mmio access */
 };
 
 /* per-buffer bit flags */
 
  */
 
 #include "cx18-driver.h"
+#include "cx18-io.h"
 #include "cx18-irq.h"
 
-void cx18_raw_writel(struct cx18 *cx, u32 val, void __iomem *addr)
-{
-       __raw_writel(val, addr);
-}
-
-u32 cx18_raw_readl(struct cx18 *cx, const void __iomem *addr)
-{
-       return __raw_readl(addr);
-}
-
-u32 cx18_write_sync(struct cx18 *cx, u32 val, void __iomem *addr)
-{
-       writel(val, addr);
-       return readl(addr);
-}
-
-void cx18_writel(struct cx18 *cx, u32 val, void __iomem *addr)
-{
-       writel(val, addr);
-}
-
-u32 cx18_readl(struct cx18 *cx, const void __iomem *addr)
-{
-       return readl(addr);
-}
-
-
-/* Access "register" region of CX23418 memory mapped I/O */
-u32 cx18_read_reg(struct cx18 *cx, u32 reg)
-{
-       return readl(cx->reg_mem + reg);
-}
-
-void cx18_write_reg(struct cx18 *cx, u32 val, u32 reg)
-{
-       writel(val, cx->reg_mem + reg);
-}
-
-u32 cx18_write_reg_sync(struct cx18 *cx, u32 val, u32 reg)
-{
-       return cx18_write_sync(cx, val, cx->reg_mem + reg);
-}
-
-/* Access "encoder memory" region of CX23418 memory mapped I/O */
-u32 cx18_read_enc(struct cx18 *cx, u32 addr)
-{
-       return readl(cx->enc_mem + addr);
-}
-
-void cx18_write_enc(struct cx18 *cx, u32 val, u32 addr)
-{
-       writel(val, cx->enc_mem + addr);
-}
-
-u32 cx18_write_enc_sync(struct cx18 *cx, u32 val, u32 addr)
-{
-       return cx18_write_sync(cx, val, cx->enc_mem + addr);
-}
-
 void cx18_memcpy_fromio(struct cx18 *cx, void *to,
                        const void __iomem *from, unsigned int len)
 {
-       memcpy_fromio(to, from, len);
+       /* Align reads on the CX23418's addresses */
+       if ((len > 0) && ((unsigned)from & 1)) {
+               *((u8 *)to) = cx18_readb(cx, from);
+               len--;
+               to++;
+               from++;
+       }
+       if ((len > 1) && ((unsigned)from & 2)) {
+               *((u16 *)to) = cx18_raw_readw(cx, from);
+               len -= 2;
+               to += 2;
+               from += 2;
+       }
+       while (len > 3) {
+               *((u32 *)to) = cx18_raw_readl(cx, from);
+               len -= 4;
+               to += 4;
+               from += 4;
+       }
+       if (len > 1) {
+               *((u16 *)to) = cx18_raw_readw(cx, from);
+               len -= 2;
+               to += 2;
+               from += 2;
+       }
+       if (len > 0)
+               *((u8 *)to) = cx18_readb(cx, from);
 }
 
 void cx18_memset_io(struct cx18 *cx, void __iomem *addr, int val, size_t count)
 {
-       memset_io(addr, val, count);
+       u16 val2 = val | (val << 8);
+       u32 val4 = val2 | (val2 << 16);
+
+       /* Align writes on the CX23418's addresses */
+       if ((count > 0) && ((unsigned)addr & 1)) {
+               cx18_writeb(cx, (u8) val, addr);
+               count--;
+               addr++;
+       }
+       if ((count > 1) && ((unsigned)addr & 2)) {
+               cx18_writew(cx, val2, addr);
+               count -= 2;
+               addr += 2;
+       }
+       while (count > 3) {
+               cx18_writel(cx, val4, addr);
+               count -= 4;
+               addr += 4;
+       }
+       if (count > 1) {
+               cx18_writew(cx, val2, addr);
+               count -= 2;
+               addr += 2;
+       }
+       if (count > 0)
+               cx18_writeb(cx, (u8) val, addr);
 }
 
 void cx18_sw1_irq_enable(struct cx18 *cx, u32 val)
 
 
 #include "cx18-driver.h"
 
-/* This is a PCI post thing, where if the pci register is not read, then
-   the write doesn't always take effect right away. By reading back the
-   register any pending PCI writes will be performed (in order), and so
-   you can be sure that the writes are guaranteed to be done.
+static inline void cx18_io_delay(struct cx18 *cx)
+{
+       if (cx->options.mmio_ndelay)
+               ndelay(cx->options.mmio_ndelay);
+}
 
-   Rarely needed, only in some timing sensitive cases.
-   Apparently if this is not done some motherboards seem
-   to kill the firmware and get into the broken state until computer is
-   rebooted. */
-u32 cx18_write_sync(struct cx18 *cx, u32 val, void __iomem *addr);
+/* Non byteswapping memory mapped IO */
+static inline void cx18_raw_writel(struct cx18 *cx, u32 val, void __iomem *addr)
+{
+       __raw_writel(val, addr);
+       cx18_io_delay(cx);
+}
 
-void cx18_writel(struct cx18 *cx, u32 val, void __iomem *addr);
-u32 cx18_readl(struct cx18 *cx, const void __iomem *addr);
+static inline u32 cx18_raw_readl(struct cx18 *cx, const void __iomem *addr)
+{
+       u32 ret = __raw_readl(addr);
+       cx18_io_delay(cx);
+       return ret;
+}
 
-/* No endiannes conversion calls */
-void cx18_raw_writel(struct cx18 *cx, u32 val, void __iomem *addr);
-u32 cx18_raw_readl(struct cx18 *cx, const void __iomem *addr);
+static inline u16 cx18_raw_readw(struct cx18 *cx, const void __iomem *addr)
+{
+       u16 ret = __raw_readw(addr);
+       cx18_io_delay(cx);
+       return ret;
+}
 
-/* Access "register" region of CX23418 memory mapped I/O */
-u32 cx18_read_reg(struct cx18 *cx, u32 reg);
-void cx18_write_reg(struct cx18 *cx, u32 val, u32 reg);
-u32 cx18_write_reg_sync(struct cx18 *cx, u32 val, u32 reg);
+/* Normal memory mapped IO */
+static inline void cx18_writel(struct cx18 *cx, u32 val, void __iomem *addr)
+{
+       writel(val, addr);
+       cx18_io_delay(cx);
+}
 
-/* Access "encoder memory" region of CX23418 memory mapped I/O */
-u32 cx18_read_enc(struct cx18 *cx, u32 addr);
-void cx18_write_enc(struct cx18 *cx, u32 val, u32 addr);
-u32 cx18_write_enc_sync(struct cx18 *cx, u32 val, u32 addr);
+static inline void cx18_writew(struct cx18 *cx, u16 val, void __iomem *addr)
+{
+       writew(val, addr);
+       cx18_io_delay(cx);
+}
+
+static inline void cx18_writeb(struct cx18 *cx, u8 val, void __iomem *addr)
+{
+       writeb(val, addr);
+       cx18_io_delay(cx);
+}
+
+static inline u32 cx18_readl(struct cx18 *cx, const void __iomem *addr)
+{
+       u32 ret = readl(addr);
+       cx18_io_delay(cx);
+       return ret;
+}
+
+static inline u8 cx18_readb(struct cx18 *cx, const void __iomem *addr)
+{
+       u8 ret = readb(addr);
+       cx18_io_delay(cx);
+       return ret;
+}
+
+static inline u32 cx18_write_sync(struct cx18 *cx, u32 val, void __iomem *addr)
+{
+       cx18_writel(cx, val, addr);
+       return cx18_readl(cx, addr);
+}
 
 void cx18_memcpy_fromio(struct cx18 *cx, void *to,
                        const void __iomem *from, unsigned int len);
 void cx18_memset_io(struct cx18 *cx, void __iomem *addr, int val, size_t count);
 
+/* Access "register" region of CX23418 memory mapped I/O */
+static inline void cx18_write_reg(struct cx18 *cx, u32 val, u32 reg)
+{
+       cx18_writel(cx, val, cx->reg_mem + reg);
+}
+
+static inline u32 cx18_read_reg(struct cx18 *cx, u32 reg)
+{
+       return cx18_readl(cx, cx->reg_mem + reg);
+}
+
+static inline u32 cx18_write_reg_sync(struct cx18 *cx, u32 val, u32 reg)
+{
+       return cx18_write_sync(cx, val, cx->reg_mem + reg);
+}
+
+/* Access "encoder memory" region of CX23418 memory mapped I/O */
+static inline void cx18_write_enc(struct cx18 *cx, u32 val, u32 addr)
+{
+       cx18_writel(cx, val, cx->enc_mem + addr);
+}
+
+static inline u32 cx18_read_enc(struct cx18 *cx, u32 addr)
+{
+       return cx18_readl(cx, cx->enc_mem + addr);
+}
+
+static inline u32 cx18_write_enc_sync(struct cx18 *cx, u32 val, u32 addr)
+{
+       return cx18_write_sync(cx, val, cx->enc_mem + addr);
+}
+
+
 void cx18_sw1_irq_enable(struct cx18 *cx, u32 val);
 void cx18_sw1_irq_disable(struct cx18 *cx, u32 val);
 void cx18_sw2_irq_enable(struct cx18 *cx, u32 val);
 
                }
                buf->id = cx->buffer_id++;
                INIT_LIST_HEAD(&buf->list);
-               /* FIXME - check for mmio */
                buf->dma_handle = pci_map_single(s->cx->dev,
                                buf->buf, s->buf_size, s->dma);
                cx18_buf_sync_for_cpu(s, buf);
 
        /* empty q_free */
        while ((buf = cx18_dequeue(s, &s->q_free))) {
-               /* FIXME - check for mmio */
                pci_unmap_single(s->cx->dev, buf->dma_handle,
                                s->buf_size, s->dma);
                kfree(buf->buf);
 
 static inline void cx18_buf_sync_for_cpu(struct cx18_stream *s,
        struct cx18_buffer *buf)
 {
-       /* FIXME check IO transfers */
        pci_dma_sync_single_for_cpu(s->cx->dev, buf->dma_handle,
                                s->buf_size, s->dma);
 }
 static inline void cx18_buf_sync_for_device(struct cx18_stream *s,
        struct cx18_buffer *buf)
 {
-       /* FIXME check IO transfers */
        pci_dma_sync_single_for_device(s->cx->dev, buf->dma_handle,
                                s->buf_size, s->dma);
 }