[media] tw686x: Add support for DMA scatter-gather mode
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>
This commit is contained in:
parent
11a1697404
commit
34e2acc8df
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
@ -28,6 +29,10 @@
|
||||
#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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user