[media] tw686x: Add support for DMA scatter-gather mode
authorEzequiel Garcia <ezequiel@vanguardiasur.com.ar>
Sat, 4 Jun 2016 23:47:17 +0000 (20:47 -0300)
committerMauro Carvalho Chehab <mchehab@s-opensource.com>
Tue, 28 Jun 2016 10:50:09 +0000 (07:50 -0300)
Now that the driver has the infrastructure to support more
DMA modes, let's add the DMA scatter-gather mode.

In this mode, the device delivers sequential top-bottom
frames. The scatter-gather logic is based on staging's
tw686x-kh driver (by Krzysztof Ha?asa).

Tested-by: Tim Harvey <tharvey@gateworks.com>
Signed-off-by: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
drivers/media/pci/tw686x/Kconfig
drivers/media/pci/tw686x/tw686x-core.c
drivers/media/pci/tw686x/tw686x-video.c
drivers/media/pci/tw686x/tw686x.h

index ef8ca85..34ff377 100644 (file)
@@ -4,6 +4,7 @@ config VIDEO_TW686X
        depends on HAS_DMA
        select VIDEOBUF2_VMALLOC
        select VIDEOBUF2_DMA_CONTIG
+       select VIDEOBUF2_DMA_SG
        select SND_PCM
        help
          Support for Intersil/Techwell TW686x-based frame grabber cards.
index 9a7646c..586bc67 100644 (file)
@@ -65,6 +65,8 @@ static const char *dma_mode_name(unsigned int mode)
                return "memcpy";
        case TW686X_DMA_MODE_CONTIG:
                return "contig";
+       case TW686X_DMA_MODE_SG:
+               return "sg";
        default:
                return "unknown";
        }
@@ -81,6 +83,8 @@ static int tw686x_dma_mode_set(const char *val, struct kernel_param *kp)
                dma_mode = TW686X_DMA_MODE_MEMCPY;
        else if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_CONTIG)))
                dma_mode = TW686X_DMA_MODE_CONTIG;
+       else if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_SG)))
+               dma_mode = TW686X_DMA_MODE_SG;
        else
                return -EINVAL;
        return 0;
index b5cb385..0e839f6 100644 (file)
@@ -20,6 +20,7 @@
 #include <media/v4l2-common.h>
 #include <media/v4l2-event.h>
 #include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-dma-sg.h>
 #include <media/videobuf2-vmalloc.h>
 #include "tw686x.h"
 #include "tw686x-regs.h"
 #define TW686X_VIDEO_WIDTH             720
 #define TW686X_VIDEO_HEIGHT(id)                ((id & V4L2_STD_525_60) ? 480 : 576)
 
+#define TW686X_MAX_SG_ENTRY_SIZE       4096
+#define TW686X_MAX_SG_DESC_COUNT       256 /* PAL 720x576 needs 203 4-KB pages */
+#define TW686X_SG_TABLE_SIZE           (TW686X_MAX_SG_DESC_COUNT * sizeof(struct tw686x_sg_desc))
+
 static const struct tw686x_format formats[] = {
        {
                .fourcc = V4L2_PIX_FMT_UYVY,
@@ -196,6 +201,174 @@ const struct tw686x_dma_ops contig_dma_ops = {
        .field          = V4L2_FIELD_INTERLACED,
 };
 
+static int tw686x_sg_desc_fill(struct tw686x_sg_desc *descs,
+                              struct tw686x_v4l2_buf *buf,
+                              unsigned int buf_len)
+{
+       struct sg_table *vbuf = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0);
+       unsigned int len, entry_len;
+       struct scatterlist *sg;
+       int i, count;
+
+       /* Clear the scatter-gather table */
+       memset(descs, 0, TW686X_SG_TABLE_SIZE);
+
+       count = 0;
+       for_each_sg(vbuf->sgl, sg, vbuf->nents, i) {
+               dma_addr_t phys = sg_dma_address(sg);
+               len = sg_dma_len(sg);
+
+               while (len && buf_len) {
+
+                       if (count == TW686X_MAX_SG_DESC_COUNT)
+                               return -ENOMEM;
+
+                       entry_len = min_t(unsigned int, len,
+                                         TW686X_MAX_SG_ENTRY_SIZE);
+                       entry_len = min_t(unsigned int, entry_len, buf_len);
+                       descs[count].phys = cpu_to_le32(phys);
+                       descs[count++].flags_length =
+                                       cpu_to_le32(BIT(30) | entry_len);
+                       phys += entry_len;
+                       len -= entry_len;
+                       buf_len -= entry_len;
+               }
+
+               if (!buf_len)
+                       return 0;
+       }
+
+       return -ENOMEM;
+}
+
+static void tw686x_sg_buf_refill(struct tw686x_video_channel *vc,
+                                unsigned int pb)
+{
+       struct tw686x_dev *dev = vc->dev;
+       struct tw686x_v4l2_buf *buf;
+
+       while (!list_empty(&vc->vidq_queued)) {
+               unsigned int buf_len;
+
+               buf = list_first_entry(&vc->vidq_queued,
+                       struct tw686x_v4l2_buf, list);
+               list_del(&buf->list);
+
+               buf_len = (vc->width * vc->height * vc->format->depth) >> 3;
+               if (tw686x_sg_desc_fill(vc->sg_descs[pb], buf, buf_len)) {
+                       v4l2_err(&dev->v4l2_dev,
+                                "dma%d: unable to fill %s-buffer\n",
+                                vc->ch, pb ? "B" : "P");
+                       vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+                       continue;
+               }
+
+               buf->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE;
+               vc->curr_bufs[pb] = buf;
+               return;
+       }
+
+       vc->curr_bufs[pb] = NULL;
+}
+
+static void tw686x_sg_dma_free(struct tw686x_video_channel *vc,
+                              unsigned int pb)
+{
+       struct tw686x_dma_desc *desc = &vc->dma_descs[pb];
+       struct tw686x_dev *dev = vc->dev;
+
+       if (desc->size) {
+               pci_free_consistent(dev->pci_dev, desc->size,
+                                   desc->virt, desc->phys);
+               desc->virt = NULL;
+       }
+
+       vc->sg_descs[pb] = NULL;
+}
+
+static int tw686x_sg_dma_alloc(struct tw686x_video_channel *vc,
+                              unsigned int pb)
+{
+       struct tw686x_dma_desc *desc = &vc->dma_descs[pb];
+       struct tw686x_dev *dev = vc->dev;
+       u32 reg = pb ? DMA_PAGE_TABLE1_ADDR[vc->ch] :
+                      DMA_PAGE_TABLE0_ADDR[vc->ch];
+       void *virt;
+
+       if (desc->size) {
+
+               virt = pci_alloc_consistent(dev->pci_dev, desc->size,
+                                           &desc->phys);
+               if (!virt) {
+                       v4l2_err(&dev->v4l2_dev,
+                                "dma%d: unable to allocate %s-buffer\n",
+                                vc->ch, pb ? "B" : "P");
+                       return -ENOMEM;
+               }
+               desc->virt = virt;
+               reg_write(dev, reg, desc->phys);
+       } else {
+               virt = dev->video_channels[0].dma_descs[pb].virt +
+                      vc->ch * TW686X_SG_TABLE_SIZE;
+       }
+
+       vc->sg_descs[pb] = virt;
+       return 0;
+}
+
+static void tw686x_sg_cleanup(struct tw686x_dev *dev)
+{
+       vb2_dma_sg_cleanup_ctx(dev->alloc_ctx);
+}
+
+static int tw686x_sg_setup(struct tw686x_dev *dev)
+{
+       unsigned int sg_table_size, pb, ch, channels;
+
+       dev->alloc_ctx = vb2_dma_sg_init_ctx(&dev->pci_dev->dev);
+       if (IS_ERR(dev->alloc_ctx)) {
+               dev_err(&dev->pci_dev->dev, "unable to init DMA context\n");
+               return PTR_ERR(dev->alloc_ctx);
+       }
+
+       if (is_second_gen(dev)) {
+               /*
+                * TW6865/TW6869: each channel needs a pair of
+                * P-B descriptor tables.
+                */
+               channels = max_channels(dev);
+               sg_table_size = TW686X_SG_TABLE_SIZE;
+       } else {
+               /*
+                * TW6864/TW6868: we need to allocate a pair of
+                * P-B descriptor tables, common for all channels.
+                * Each table will be bigger than 4 KB.
+                */
+               channels = 1;
+               sg_table_size = max_channels(dev) * TW686X_SG_TABLE_SIZE;
+       }
+
+       for (ch = 0; ch < channels; ch++) {
+               struct tw686x_video_channel *vc = &dev->video_channels[ch];
+
+               for (pb = 0; pb < 2; pb++)
+                       vc->dma_descs[pb].size = sg_table_size;
+       }
+
+       return 0;
+}
+
+const struct tw686x_dma_ops sg_dma_ops = {
+       .setup          = tw686x_sg_setup,
+       .cleanup        = tw686x_sg_cleanup,
+       .alloc          = tw686x_sg_dma_alloc,
+       .free           = tw686x_sg_dma_free,
+       .buf_refill     = tw686x_sg_buf_refill,
+       .mem_ops        = &vb2_dma_sg_memops,
+       .hw_dma_mode    = TW686X_SG_MODE,
+       .field          = V4L2_FIELD_SEQ_TB,
+};
+
 static unsigned int tw686x_fields_map(v4l2_std_id std, unsigned int fps)
 {
        static const unsigned int map[15] = {
@@ -554,6 +727,19 @@ static int tw686x_s_fmt_vid_cap(struct file *file, void *priv,
        else
                val &= ~BIT(24);
 
+       val &= ~0x7ffff;
+
+       /* Program the DMA scatter-gather */
+       if (dev->dma_mode == TW686X_DMA_MODE_SG) {
+               u32 start_idx, end_idx;
+
+               start_idx = is_second_gen(dev) ?
+                               0 : vc->ch * TW686X_MAX_SG_DESC_COUNT;
+               end_idx = start_idx + TW686X_MAX_SG_DESC_COUNT - 1;
+
+               val |= (end_idx << 10) | start_idx;
+       }
+
        val &= ~(0x7 << 20);
        val |= vc->format->mode << 20;
        reg_write(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch], val);
@@ -891,6 +1077,8 @@ int tw686x_video_init(struct tw686x_dev *dev)
                dev->dma_ops = &memcpy_dma_ops;
        else if (dev->dma_mode == TW686X_DMA_MODE_CONTIG)
                dev->dma_ops = &contig_dma_ops;
+       else if (dev->dma_mode == TW686X_DMA_MODE_SG)
+               dev->dma_ops = &sg_dma_ops;
        else
                return -EINVAL;
 
index 5f4c213..25e3c84 100644 (file)
@@ -34,6 +34,7 @@
 
 #define TW686X_DMA_MODE_MEMCPY         0
 #define TW686X_DMA_MODE_CONTIG         1
+#define TW686X_DMA_MODE_SG             2
 
 struct tw686x_format {
        char *name;
@@ -48,6 +49,12 @@ struct tw686x_dma_desc {
        unsigned int size;
 };
 
+struct tw686x_sg_desc {
+       /* 3 MSBits for flags, 13 LSBits for length */
+       __le32 flags_length;
+       __le32 phys;
+};
+
 struct tw686x_audio_buf {
        dma_addr_t dma;
        void *virt;
@@ -80,6 +87,7 @@ struct tw686x_video_channel {
        struct video_device *device;
        struct tw686x_v4l2_buf *curr_bufs[2];
        struct tw686x_dma_desc dma_descs[2];
+       struct tw686x_sg_desc *sg_descs[2];
 
        struct v4l2_ctrl_handler ctrl_handler;
        const struct tw686x_format *format;
@@ -154,6 +162,12 @@ static inline unsigned int max_channels(struct tw686x_dev *dev)
        return dev->type & TYPE_MAX_CHANNELS; /* 4 or 8 channels */
 }
 
+static inline unsigned is_second_gen(struct tw686x_dev *dev)
+{
+       /* each channel has its own DMA SG table */
+       return dev->type & TYPE_SECOND_GEN;
+}
+
 void tw686x_enable_channel(struct tw686x_dev *dev, unsigned int channel);
 void tw686x_disable_channel(struct tw686x_dev *dev, unsigned int channel);