Commit f62de9be authored by Ben Collins's avatar Ben Collins

solo6x10: Conversion to videobuf-dma-sg (from dma-cont)

Full rewrite of the P2M DMA Engine to support scatter gather and take
advantage of some of the features of the hardware. This includes using
repeat DMA operations and line-mode transfers (for copying OSG and
video display buffers).

What isn't working: For some reason, descriptor mode is not working. I've
implemented a psuedo version (still has one-interrupt per DMA operation),
but we would get huge improvements if we could hand off a ring of
descriptors to the P2M and get back one interrupt when it was done with
all of them.

Documentation is very vague on this, and even the ODM example code
half attempts to get it right, but comments it out of the driver
because it just doesn't work *sigh*

Converts all v4l2 to dma-sg. So long slow dma-contiguous, but hello
more interrupts :(
Signed-off-by: default avatarBen Collins <bcollins@bluecherry.net>
parent 1194cf43
config SOLO6X10
tristate "Softlogic 6x10 MPEG codec cards"
depends on PCI && VIDEO_DEV && SND
select VIDEOBUF_DMA_CONTIG
select VIDEOBUF_DMA_SG
---help---
This driver supports the Softlogic based MPEG-4 and h.264 codec
codec cards.
......@@ -136,6 +136,7 @@ static int __devinit solo6010_pci_probe(struct pci_dev *pdev,
int ret;
int sdram;
u8 chip_id;
solo_dev = kzalloc(sizeof(*solo_dev), GFP_KERNEL);
if (solo_dev == NULL)
return -ENOMEM;
......@@ -261,13 +262,18 @@ static void __devexit solo6010_pci_remove(struct pci_dev *pdev)
}
static struct pci_device_id solo6010_id_table[] = {
/* 6010 based cards */
{PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6010)},
{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_4)},
{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_9)},
{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_16)},
{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_COMMSOLO_4)},
{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_COMMSOLO_9)},
{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_COMMSOLO_16)},
{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_4)},
{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_9)},
{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_16)},
/* 6110 based cards */
{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_4)},
{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_8)},
{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_16)},
{0,}
};
......
......@@ -227,7 +227,7 @@ static int solo_i2c_master_xfer(struct i2c_adapter *adap,
if (i == SOLO_I2C_ADAPTERS)
return num; // XXX Right return value for failure?
down(&solo_dev->i2c_sem);
mutex_lock(&solo_dev->i2c_mutex);
solo_dev->i2c_id = i;
solo_dev->i2c_msg = msgs;
solo_dev->i2c_msg_num = num;
......@@ -258,7 +258,7 @@ static int solo_i2c_master_xfer(struct i2c_adapter *adap,
solo_dev->i2c_state = IIC_STATE_IDLE;
solo_dev->i2c_id = -1;
up(&solo_dev->i2c_sem);
mutex_unlock(&solo_dev->i2c_mutex);
return ret;
}
......@@ -284,7 +284,7 @@ int solo_i2c_init(struct solo6010_dev *solo_dev)
solo_dev->i2c_id = -1;
solo_dev->i2c_state = IIC_STATE_IDLE;
init_waitqueue_head(&solo_dev->i2c_wait);
sema_init(&solo_dev->i2c_sem, 1);
mutex_init(&solo_dev->i2c_mutex);
for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
struct i2c_adapter *adap = &solo_dev->i2c_adap[i];
......
......@@ -18,6 +18,7 @@
*/
#include <linux/kernel.h>
#include <linux/scatterlist.h>
#include "solo6010.h"
......@@ -30,8 +31,9 @@ int solo_p2m_dma(struct solo6010_dev *solo_dev, u8 id, int wr,
int ret;
WARN_ON(!size);
WARN_ON(id >= SOLO_NR_P2M);
if (!size || id >= SOLO_NR_P2M)
BUG_ON(id >= SOLO_NR_P2M);
if (!size)
return -EINVAL;
dma_addr = pci_map_single(solo_dev->pdev, sys_addr, size,
......@@ -47,42 +49,118 @@ int solo_p2m_dma(struct solo6010_dev *solo_dev, u8 id, int wr,
int solo_p2m_dma_t(struct solo6010_dev *solo_dev, u8 id, int wr,
dma_addr_t dma_addr, u32 ext_addr, u32 size)
{
struct p2m_desc desc;
solo_p2m_push_desc(&desc, wr, dma_addr, ext_addr, size, 0, 0);
return solo_p2m_dma_desc(solo_dev, id, &desc, 1);
}
void solo_p2m_push_desc(struct p2m_desc *desc, int wr, dma_addr_t dma_addr,
u32 ext_addr, u32 size, int repeat, u32 ext_size)
{
desc->ta = dma_addr;
desc->fa = ext_addr;
desc->ext = SOLO_P2M_COPY_SIZE(size >> 2);
desc->ctrl = SOLO_P2M_BURST_SIZE(SOLO_P2M_BURST_256) |
(wr ? SOLO_P2M_WRITE : 0) | SOLO_P2M_TRANS_ON;
/* Ext size only matters when we're repeating */
if (repeat) {
desc->ext |= SOLO_P2M_EXT_INC(ext_size >> 2);
desc->ctrl |= SOLO_P2M_PCI_INC(size >> 2) |
SOLO_P2M_REPEAT(repeat);
}
}
int solo_p2m_dma_desc(struct solo6010_dev *solo_dev, u8 id,
struct p2m_desc *desc, int desc_count)
{
struct solo_p2m_dev *p2m_dev;
unsigned int timeout = 0;
unsigned int timeout;
int ret = 0;
WARN_ON(!size);
WARN_ON(id >= SOLO_NR_P2M);
if (!size || id >= SOLO_NR_P2M)
return -EINVAL;
BUG_ON(id >= SOLO_NR_P2M);
BUG_ON(desc_count > SOLO_NR_P2M_DESC);
p2m_dev = &solo_dev->p2m_dev[id];
down(&p2m_dev->sem);
mutex_lock(&p2m_dev->mutex);
start_dma:
INIT_COMPLETION(p2m_dev->completion);
p2m_dev->error = 0;
solo_reg_write(solo_dev, SOLO_P2M_TAR_ADR(id), dma_addr);
solo_reg_write(solo_dev, SOLO_P2M_EXT_ADR(id), ext_addr);
solo_reg_write(solo_dev, SOLO_P2M_EXT_CFG(id),
SOLO_P2M_COPY_SIZE(size >> 2));
solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id),
SOLO_P2M_BURST_SIZE(SOLO_P2M_BURST_256) |
(wr ? SOLO_P2M_WRITE : 0) | SOLO_P2M_TRANS_ON);
/* Setup the descriptor count and base address */
p2m_dev->num_descs = desc_count;
p2m_dev->descs = desc;
p2m_dev->desc_idx = 0;
/* We plug in the first descriptor here. The isr will take
* over from desc[1] after this. */
solo_reg_write(solo_dev, SOLO_P2M_TAR_ADR(id), desc[0].ta);
solo_reg_write(solo_dev, SOLO_P2M_EXT_ADR(id), desc[0].fa);
solo_reg_write(solo_dev, SOLO_P2M_EXT_CFG(id), desc[0].ext);
solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), desc[0].ctrl);
/* Should have all descriptors completed from one interrupt */
timeout = wait_for_completion_timeout(&p2m_dev->completion, HZ);
solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), 0);
/* XXX Really looks to me like we will get stuck here if a
* real PCI P2M error occurs */
if (p2m_dev->error)
goto start_dma;
ret = -EIO;
else if (timeout == 0)
ret = -EAGAIN;
mutex_unlock(&p2m_dev->mutex);
WARN_ON_ONCE(ret);
return ret;
}
int solo_p2m_dma_sg(struct solo6010_dev *solo_dev, u8 id,
struct p2m_desc *pdesc, int wr,
struct scatterlist *sg, u32 sg_off,
u32 ext_addr, u32 size)
{
int i;
int idx;
up(&p2m_dev->sem);
BUG_ON(id >= SOLO_NR_P2M);
return (timeout == 0) ? -EAGAIN : 0;
if (WARN_ON_ONCE(!size))
return -EINVAL;
for (i = idx = 0; i < SOLO_NR_P2M_DESC && sg && size > 0;
i++, sg = sg_next(sg)) {
struct p2m_desc *desc = &pdesc[i];
u32 sg_len = sg_dma_len(sg);
u32 len;
if (sg_off >= sg_len) {
sg_off -= sg_len;
continue;
}
sg_len -= sg_off;
len = min(sg_len, size);
solo_p2m_push_desc(desc, wr, sg_dma_address(sg) + sg_off,
ext_addr, len, 0, 0);
size -= len;
ext_addr += len;
idx++;
sg_off = 0;
}
WARN_ON_ONCE(size || i >= SOLO_NR_P2M_DESC);
return solo_p2m_dma_desc(solo_dev, id, pdesc, idx);
}
#ifdef SOLO_TEST_P2M
......@@ -152,8 +230,27 @@ static void run_p2m_test(struct solo6010_dev *solo_dev)
void solo_p2m_isr(struct solo6010_dev *solo_dev, int id)
{
struct solo_p2m_dev *p2m_dev = &solo_dev->p2m_dev[id];
struct p2m_desc *desc;
solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_P2M(id));
complete(&solo_dev->p2m_dev[id].completion);
p2m_dev->desc_idx++;
if (p2m_dev->desc_idx >= p2m_dev->num_descs) {
complete(&p2m_dev->completion);
return;
}
/* Reset the p2m and start the next one */
solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), 0);
desc = &p2m_dev->descs[p2m_dev->desc_idx];
solo_reg_write(solo_dev, SOLO_P2M_TAR_ADR(id), desc->ta);
solo_reg_write(solo_dev, SOLO_P2M_EXT_ADR(id), desc->fa);
solo_reg_write(solo_dev, SOLO_P2M_EXT_CFG(id), desc->ext);
solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), desc->ctrl);
}
void solo_p2m_error_isr(struct solo6010_dev *solo_dev, u32 status)
......@@ -188,16 +285,13 @@ int solo_p2m_init(struct solo6010_dev *solo_dev)
for (i = 0; i < SOLO_NR_P2M; i++) {
p2m_dev = &solo_dev->p2m_dev[i];
sema_init(&p2m_dev->sem, 1);
mutex_init(&p2m_dev->mutex);
init_completion(&p2m_dev->completion);
solo_reg_write(solo_dev, SOLO_P2M_DES_ADR(i),
__pa(p2m_dev->desc));
solo_reg_write(solo_dev, SOLO_P2M_CONTROL(i), 0);
solo_reg_write(solo_dev, SOLO_P2M_CONFIG(i),
SOLO_P2M_CSC_16BIT_565 |
SOLO_P2M_DMA_INTERVAL(0) |
SOLO_P2M_DMA_INTERVAL(3) |
SOLO_P2M_PCI_MASTER_MODE);
solo6010_irq_on(solo_dev, SOLO_IRQ_P2M(i));
}
......
......@@ -24,14 +24,13 @@
#include <media/v4l2-ioctl.h>
#include <media/v4l2-common.h>
#include <media/videobuf-dma-contig.h>
#include <media/videobuf-dma-sg.h>
#include "solo6010.h"
#include "solo6010-tw28.h"
#define SOLO_HW_BPL 2048
#define SOLO_DISP_PIX_FIELD V4L2_FIELD_INTERLACED
#define SOLO_DISP_BUF_SIZE (64 * 1024) // 64k
/* Image size is two fields, SOLO_HW_BPL is one horizontal line */
#define solo_vlines(__solo) (__solo->video_vsize * 2)
......@@ -49,6 +48,8 @@ struct solo_filehandle {
spinlock_t slock;
int old_write;
struct list_head vidq_active;
struct p2m_desc desc[SOLO_NR_P2M_DESC];
int desc_idx;
};
unsigned video_nr = -1;
......@@ -203,50 +204,146 @@ static int solo_v4l2_set_ch(struct solo6010_dev *solo_dev, u8 ch)
return 0;
}
static void disp_reset_desc(struct solo_filehandle *fh)
{
fh->desc_idx = 0;
}
static int disp_flush_descs(struct solo_filehandle *fh)
{
int ret;
if (!fh->desc_idx)
return 0;
ret = solo_p2m_dma_desc(fh->solo_dev, SOLO_P2M_DMA_ID_DISP,
fh->desc, fh->desc_idx);
disp_reset_desc(fh);
return ret;
}
static int disp_push_desc(struct solo_filehandle *fh, dma_addr_t dma_addr,
u32 ext_addr, int size, int repeat, int ext_size)
{
if (fh->desc_idx >= SOLO_NR_P2M_DESC) {
int ret = disp_flush_descs(fh);
if (ret)
return ret;
}
solo_p2m_push_desc(&fh->desc[fh->desc_idx], 0, dma_addr, ext_addr,
size, repeat, ext_size);
fh->desc_idx++;
return 0;
}
static void solo_fillbuf(struct solo_filehandle *fh,
struct videobuf_buffer *vb)
{
struct solo6010_dev *solo_dev = fh->solo_dev;
dma_addr_t vbuf;
struct videobuf_dmabuf* vbuf;
unsigned int fdma_addr;
int frame_size;
int error = 1;
int i;
struct scatterlist* sg;
dma_addr_t sg_dma;
int sg_size_left;
if (!(vbuf = videobuf_to_dma_contig(vb)))
if (!(vbuf = videobuf_to_dma(vb)))
goto finish_buf;
if (erase_off(solo_dev)) {
void *p = videobuf_queue_to_vaddr(&fh->vidq, vb);
int image_size = solo_image_size(solo_dev);
for (i = 0; i < image_size; i += 2) {
int i;
/* Just blit to the entire sg list, ignoring size */
for_each_sg(vbuf->sglist, sg, vbuf->sglen, i) {
void *p = sg_virt(sg);
size_t len = sg_dma_len(sg);
for (i = 0; i < len; i += 2) {
((u8 *)p)[i] = 0x80;
((u8 *)p)[i + 1] = 0x00;
}
}
error = 0;
goto finish_buf;
}
frame_size = SOLO_HW_BPL * solo_vlines(solo_dev);
fdma_addr = SOLO_DISP_EXT_ADDR(solo_dev) + (fh->old_write * frame_size);
disp_reset_desc(fh);
sg = vbuf->sglist;
sg_dma = sg_dma_address(sg);
sg_size_left = sg_dma_len(sg);
fdma_addr = SOLO_DISP_EXT_ADDR(solo_dev) + (fh->old_write *
(SOLO_HW_BPL * solo_vlines(solo_dev)));
for (i = 0; i < frame_size / SOLO_DISP_BUF_SIZE; i++) {
int j;
for (j = 0; j < (SOLO_DISP_BUF_SIZE / SOLO_HW_BPL); j++) {
if (solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_DISP, 0,
vbuf, fdma_addr + (j * SOLO_HW_BPL),
solo_bytesperline(solo_dev)))
for (i = 0; i < solo_vlines(solo_dev); i++) {
int line_len = solo_bytesperline(solo_dev);
int lines;
if (!sg_size_left) {
sg = sg_next(sg);
if (sg == NULL)
goto finish_buf;
vbuf += solo_bytesperline(solo_dev);
sg_dma = sg_dma_address(sg);
sg_size_left = sg_dma_len(sg);
}
fdma_addr += SOLO_DISP_BUF_SIZE;
/* No room for an entire line, so chunk it up */
if (sg_size_left < line_len) {
int this_addr = fdma_addr;
while (line_len > 0) {
int this_write;
if (!sg_size_left) {
sg = sg_next(sg);
if (sg == NULL)
goto finish_buf;
sg_dma = sg_dma_address(sg);
sg_size_left = sg_dma_len(sg);
}
error = 0;
this_write = min(sg_size_left, line_len);
if (disp_push_desc(fh, sg_dma, this_addr,
this_write, 0, 0))
goto finish_buf;
line_len -= this_write;
sg_size_left -= this_write;
sg_dma += this_write;
this_addr += this_write;
}
fdma_addr += SOLO_HW_BPL;
continue;
}
/* Shove as many lines into a repeating descriptor as possible */
lines = min(sg_size_left / line_len,
solo_vlines(solo_dev) - i);
if (disp_push_desc(fh, sg_dma, fdma_addr, line_len,
lines - 1, SOLO_HW_BPL))
goto finish_buf;
i += lines - 1;
fdma_addr += SOLO_HW_BPL * lines;
sg_dma += lines * line_len;
sg_size_left -= lines * line_len;
}
error = disp_flush_descs(fh);
finish_buf:
if (error) {
vb->state = VIDEOBUF_ERROR;
} else {
vb->size = solo_vlines(solo_dev) * solo_bytesperline(solo_dev);
vb->state = VIDEOBUF_DONE;
vb->field_count++;
do_gettimeofday(&vb->ts);
......@@ -364,7 +461,9 @@ static int solo_buf_prepare(struct videobuf_queue *vq,
if (vb->state == VIDEOBUF_NEEDS_INIT) {
int rc = videobuf_iolock(vq, vb, NULL);
if (rc < 0) {
videobuf_dma_contig_free(vq, vb);
struct videobuf_dmabuf *dma = videobuf_to_dma(vb);
videobuf_dma_unmap(vq, dma);
videobuf_dma_free(dma);
vb->state = VIDEOBUF_NEEDS_INIT;
return rc;
}
......@@ -388,7 +487,10 @@ static void solo_buf_queue(struct videobuf_queue *vq,
static void solo_buf_release(struct videobuf_queue *vq,
struct videobuf_buffer *vb)
{
videobuf_dma_contig_free(vq, vb);
struct videobuf_dmabuf *dma = videobuf_to_dma(vb);
videobuf_dma_unmap(vq, dma);
videobuf_dma_free(dma);
vb->state = VIDEOBUF_NEEDS_INIT;
}
......@@ -433,7 +535,7 @@ static int solo_v4l2_open(struct file *file)
return ret;
}
videobuf_queue_dma_contig_init(&fh->vidq, &solo_video_qops,
videobuf_queue_sg_init(&fh->vidq, &solo_video_qops,
&solo_dev->pdev->dev, &fh->slock,
V4L2_BUF_TYPE_VIDEO_CAPTURE,
SOLO_DISP_PIX_FIELD,
......@@ -530,7 +632,7 @@ static int solo_enum_input(struct file *file, void *priv,
if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC)
input->std = V4L2_STD_NTSC_M;
else
input->std = V4L2_STD_PAL_M;
input->std = V4L2_STD_PAL_B;
return 0;
}
......@@ -795,7 +897,7 @@ static struct video_device solo_v4l2_template = {
.minor = -1,
.release = video_device_release,
.tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL_M,
.tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL_B,
.current_norm = V4L2_STD_NTSC_M,
};
......
......@@ -26,8 +26,8 @@
#include <linux/semaphore.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/atomic.h>
......@@ -48,10 +48,14 @@
#define PCI_DEVICE_ID_NEUSOLO_4 0x4304
#define PCI_DEVICE_ID_NEUSOLO_9 0x4309
#define PCI_DEVICE_ID_NEUSOLO_16 0x4310
/* Commell Softlogic 6010 based cards */
#define PCI_DEVICE_ID_COMMSOLO_4 0x4E04
#define PCI_DEVICE_ID_COMMSOLO_9 0x4E09
#define PCI_DEVICE_ID_COMMSOLO_16 0x4E10
/* Bluecherry Softlogic 6010 based cards */
#define PCI_DEVICE_ID_BC_SOLO_4 0x4E04
#define PCI_DEVICE_ID_BC_SOLO_9 0x4E09
#define PCI_DEVICE_ID_BC_SOLO_16 0x4E10
/* Bluecherry Softlogic 6110 based cards */
#define PCI_DEVICE_ID_BC_6110_4 0x5304
#define PCI_DEVICE_ID_BC_6110_8 0x5308
#define PCI_DEVICE_ID_BC_6110_16 0x5310
#endif /* Bluecherry */
#define SOLO6010_NAME "solo6010"
......@@ -78,7 +82,6 @@
/* DMA Engine setup */
#define SOLO_NR_P2M 4
#define SOLO_NR_P2M_DESC 256
#define SOLO_P2M_DESC_SIZE (SOLO_NR_P2M_DESC * 16)
/* MPEG and JPEG share the same interrupt and locks so they must be together
* in the same dma channel. */
#define SOLO_P2M_DMA_ID_MP4E 0
......@@ -123,11 +126,20 @@ enum SOLO_I2C_STATE {
IIC_STATE_STOP
};
struct p2m_desc {
u32 ctrl;
u32 ext;
u32 ta;
u32 fa;
};
struct solo_p2m_dev {
struct semaphore sem;
struct mutex mutex;
struct completion completion;
int error;
u8 desc[SOLO_P2M_DESC_SIZE];
int num_descs;
int desc_idx;
struct p2m_desc *descs;
};
#define OSD_TEXT_MAX 30
......@@ -185,7 +197,7 @@ struct solo6010_dev {
/* i2c related items */
struct i2c_adapter i2c_adap[SOLO_I2C_ADAPTERS];
enum SOLO_I2C_STATE i2c_state;
struct semaphore i2c_sem;
struct mutex i2c_mutex;
int i2c_id;
wait_queue_head_t i2c_wait;
struct i2c_msg *i2c_msg;
......@@ -306,6 +318,14 @@ int solo_p2m_dma_t(struct solo6010_dev *solo_dev, u8 id, int wr,
dma_addr_t dma_addr, u32 ext_addr, u32 size);
int solo_p2m_dma(struct solo6010_dev *solo_dev, u8 id, int wr,
void *sys_addr, u32 ext_addr, u32 size);
int solo_p2m_dma_sg(struct solo6010_dev *solo_dev, u8 id,
struct p2m_desc *pdesc, int wr,
struct scatterlist *sglist, u32 sg_off,
u32 ext_addr, u32 size);
void solo_p2m_push_desc(struct p2m_desc *desc, int wr, dma_addr_t dma_addr,
u32 ext_addr, u32 size, int repeat, u32 ext_size);
int solo_p2m_dma_desc(struct solo6010_dev *solo_dev, u8 id,
struct p2m_desc *desc, int desc_count);
/* Set the threshold for motion detection */
void solo_set_motion_threshold(struct solo6010_dev *solo_dev, u8 ch, u16 val);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment