|
@@ -20,6 +20,7 @@
|
|
#include <media/v4l2-common.h>
|
|
#include <media/v4l2-common.h>
|
|
#include <media/v4l2-event.h>
|
|
#include <media/v4l2-event.h>
|
|
#include <media/videobuf2-dma-contig.h>
|
|
#include <media/videobuf2-dma-contig.h>
|
|
|
|
+#include <media/videobuf2-dma-sg.h>
|
|
#include <media/videobuf2-vmalloc.h>
|
|
#include <media/videobuf2-vmalloc.h>
|
|
#include "tw686x.h"
|
|
#include "tw686x.h"
|
|
#include "tw686x-regs.h"
|
|
#include "tw686x-regs.h"
|
|
@@ -28,6 +29,10 @@
|
|
#define TW686X_VIDEO_WIDTH 720
|
|
#define TW686X_VIDEO_WIDTH 720
|
|
#define TW686X_VIDEO_HEIGHT(id) ((id & V4L2_STD_525_60) ? 480 : 576)
|
|
#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[] = {
|
|
static const struct tw686x_format formats[] = {
|
|
{
|
|
{
|
|
.fourcc = V4L2_PIX_FMT_UYVY,
|
|
.fourcc = V4L2_PIX_FMT_UYVY,
|
|
@@ -196,6 +201,174 @@ const struct tw686x_dma_ops contig_dma_ops = {
|
|
.field = V4L2_FIELD_INTERLACED,
|
|
.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 unsigned int tw686x_fields_map(v4l2_std_id std, unsigned int fps)
|
|
{
|
|
{
|
|
static const unsigned int map[15] = {
|
|
static const unsigned int map[15] = {
|
|
@@ -554,6 +727,19 @@ static int tw686x_s_fmt_vid_cap(struct file *file, void *priv,
|
|
else
|
|
else
|
|
val &= ~BIT(24);
|
|
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 &= ~(0x7 << 20);
|
|
val |= vc->format->mode << 20;
|
|
val |= vc->format->mode << 20;
|
|
reg_write(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch], val);
|
|
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;
|
|
dev->dma_ops = &memcpy_dma_ops;
|
|
else if (dev->dma_mode == TW686X_DMA_MODE_CONTIG)
|
|
else if (dev->dma_mode == TW686X_DMA_MODE_CONTIG)
|
|
dev->dma_ops = &contig_dma_ops;
|
|
dev->dma_ops = &contig_dma_ops;
|
|
|
|
+ else if (dev->dma_mode == TW686X_DMA_MODE_SG)
|
|
|
|
+ dev->dma_ops = &sg_dma_ops;
|
|
else
|
|
else
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
|
|
|