summaryrefslogtreecommitdiff
path: root/linux/drivers/media
diff options
context:
space:
mode:
Diffstat (limited to 'linux/drivers/media')
-rw-r--r--linux/drivers/media/video/cx18/cx18-driver.c16
-rw-r--r--linux/drivers/media/video/cx18/cx18-driver.h16
-rw-r--r--linux/drivers/media/video/cx18/cx18-dvb.c37
-rw-r--r--linux/drivers/media/video/cx18/cx18-firmware.c35
-rw-r--r--linux/drivers/media/video/cx18/cx18-io.c11
-rw-r--r--linux/drivers/media/video/cx18/cx18-io.h1
-rw-r--r--linux/drivers/media/video/cx18/cx18-irq.c32
-rw-r--r--linux/drivers/media/video/cx18/cx18-irq.h1
-rw-r--r--linux/drivers/media/video/cx18/cx18-mailbox.c209
-rw-r--r--linux/drivers/media/video/cx18/cx18-mailbox.h7
-rw-r--r--linux/drivers/media/video/cx18/cx18-streams.c6
-rw-r--r--linux/drivers/media/video/cx18/cx18-version.h2
12 files changed, 204 insertions, 169 deletions
diff --git a/linux/drivers/media/video/cx18/cx18-driver.c b/linux/drivers/media/video/cx18/cx18-driver.c
index 542beb333..54f0fde42 100644
--- a/linux/drivers/media/video/cx18/cx18-driver.c
+++ b/linux/drivers/media/video/cx18/cx18-driver.c
@@ -187,7 +187,7 @@ MODULE_VERSION(CX18_VERSION);
/* Generic utility functions */
int cx18_msleep_timeout(unsigned int msecs, int intr)
{
- int timeout = msecs_to_jiffies(msecs);
+ long int timeout = msecs_to_jiffies(msecs);
int sig;
do {
@@ -446,15 +446,11 @@ static int __devinit cx18_init_struct1(struct cx18 *cx)
mutex_init(&cx->i2c_bus_lock[0]);
mutex_init(&cx->i2c_bus_lock[1]);
mutex_init(&cx->gpio_lock);
+ mutex_init(&cx->epu2apu_mb_lock);
+ mutex_init(&cx->epu2cpu_mb_lock);
spin_lock_init(&cx->lock);
- cx->work_queue = create_singlethread_workqueue(cx->name);
- if (cx->work_queue == NULL) {
- CX18_ERR("Could not create work queue\n");
- return -1;
- }
-
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
INIT_WORK(&cx->work, cx18_work_handler);
#else
@@ -476,8 +472,6 @@ static int __devinit cx18_init_struct1(struct cx18 *cx)
init_waitqueue_head(&cx->cap_w);
init_waitqueue_head(&cx->mb_apu_waitq);
init_waitqueue_head(&cx->mb_cpu_waitq);
- init_waitqueue_head(&cx->mb_epu_waitq);
- init_waitqueue_head(&cx->mb_hpu_waitq);
init_waitqueue_head(&cx->dma_waitq);
/* VBI */
@@ -841,7 +835,6 @@ free_map:
free_mem:
release_mem_region(cx->base_addr, CX18_MEM_SIZE);
free_workqueue:
- destroy_workqueue(cx->work_queue);
err:
if (retval == 0)
retval = -ENODEV;
@@ -942,8 +935,7 @@ static void cx18_remove(struct pci_dev *pci_dev)
cx18_halt_firmware(cx);
- flush_workqueue(cx->work_queue);
- destroy_workqueue(cx->work_queue);
+ flush_scheduled_work();
cx18_streams_cleanup(cx, 1);
diff --git a/linux/drivers/media/video/cx18/cx18-driver.h b/linux/drivers/media/video/cx18/cx18-driver.h
index fe9e5bb97..ce7680675 100644
--- a/linux/drivers/media/video/cx18/cx18-driver.h
+++ b/linux/drivers/media/video/cx18/cx18-driver.h
@@ -207,7 +207,6 @@ struct cx18_options {
#define CX18_F_I_WORK_HANDLER_DVB 18 /* work to be done for DVB */
#define CX18_F_I_INITED 21 /* set after first open */
#define CX18_F_I_FAILED 22 /* set if first open failed */
-#define CX18_F_I_WORK_INITED 23 /* worker thread initialized */
/* These are the VBI types as they appear in the embedded VBI private packets. */
#define CX18_SLICED_TYPE_TELETEXT_B (1)
@@ -361,6 +360,12 @@ struct cx18_mmio_stats {
atomic_t retried_read[CX18_MAX_MMIO_RD_RETRIES+1];
};
+#define CX18_MAX_MB_ACK_DELAY 100
+
+struct cx18_mbox_stats {
+ atomic_t mb_ack_delay[CX18_MAX_MB_ACK_DELAY+1];
+};
+
/* Struct to hold info about cx18 cards */
struct cx18 {
int num; /* board number, -1 during init! */
@@ -379,7 +384,10 @@ struct cx18 {
u32 v4l2_cap; /* V4L2 capabilities of card */
u32 hw_flags; /* Hardware description of the board */
unsigned mdl_offset;
- struct cx18_scb __iomem *scb; /* pointer to SCB */
+ struct cx18_scb __iomem *scb; /* pointer to SCB */
+ struct mutex epu2apu_mb_lock; /* protect driver to chip mailbox in SCB*/
+ struct mutex epu2cpu_mb_lock; /* protect driver to chip mailbox in SCB*/
+
struct cx18_av_state av_state;
@@ -429,13 +437,10 @@ struct cx18 {
wait_queue_head_t mb_apu_waitq;
wait_queue_head_t mb_cpu_waitq;
- wait_queue_head_t mb_epu_waitq;
- wait_queue_head_t mb_hpu_waitq;
wait_queue_head_t cap_w;
/* when the current DMA is finished this queue is woken up */
wait_queue_head_t dma_waitq;
- struct workqueue_struct *work_queue;
struct work_struct work;
/* i2c */
@@ -453,6 +458,7 @@ struct cx18 {
/* Statistics */
struct cx18_mmio_stats mmio_stats;
+ struct cx18_mbox_stats mbox_stats;
/* v4l2 and User settings */
diff --git a/linux/drivers/media/video/cx18/cx18-dvb.c b/linux/drivers/media/video/cx18/cx18-dvb.c
index 4542e2e5e..4845f732e 100644
--- a/linux/drivers/media/video/cx18/cx18-dvb.c
+++ b/linux/drivers/media/video/cx18/cx18-dvb.c
@@ -109,20 +109,23 @@ static int cx18_dvb_start_feed(struct dvb_demux_feed *feed)
if (!demux->dmx.frontend)
return -EINVAL;
- if (stream) {
- mutex_lock(&stream->dvb.feedlock);
- if (stream->dvb.feeding++ == 0) {
- CX18_DEBUG_INFO("Starting Transport DMA\n");
- ret = cx18_start_v4l2_encode_stream(stream);
- if (ret < 0) {
- CX18_DEBUG_INFO(
- "Failed to start Transport DMA\n");
- stream->dvb.feeding--;
- }
- } else
- ret = 0;
- mutex_unlock(&stream->dvb.feedlock);
- }
+ if (!stream)
+ return -EINVAL;
+
+ mutex_lock(&stream->dvb.feedlock);
+ if (stream->dvb.feeding++ == 0) {
+ CX18_DEBUG_INFO("Starting Transport DMA\n");
+ set_bit(CX18_F_S_STREAMING, &stream->s_flags);
+ ret = cx18_start_v4l2_encode_stream(stream);
+ if (ret < 0) {
+ CX18_DEBUG_INFO("Failed to start Transport DMA\n");
+ stream->dvb.feeding--;
+ if (stream->dvb.feeding == 0)
+ clear_bit(CX18_F_S_STREAMING, &stream->s_flags);
+ }
+ } else
+ ret = 0;
+ mutex_unlock(&stream->dvb.feedlock);
return ret;
}
@@ -313,9 +316,11 @@ void cx18_dvb_work_handler(struct cx18 *cx)
dvb_dmx_swfilter(&s->dvb.demux, buf->buf,
buf->bytesused);
- cx18_enqueue(s, buf, &s->q_free);
cx18_buf_sync_for_device(s, buf);
- if (s->handle == CX18_INVALID_TASK_HANDLE) /* FIXME: improve */
+ cx18_enqueue(s, buf, &s->q_free);
+
+ if (s->handle == CX18_INVALID_TASK_HANDLE ||
+ !test_bit(CX18_F_S_STREAMING, &s->s_flags))
continue;
cx18_vapi(cx, CX18_CPU_DE_SET_MDL, 5, s->handle,
diff --git a/linux/drivers/media/video/cx18/cx18-firmware.c b/linux/drivers/media/video/cx18/cx18-firmware.c
index 7a7c89032..d9c5f55ab 100644
--- a/linux/drivers/media/video/cx18/cx18-firmware.c
+++ b/linux/drivers/media/video/cx18/cx18-firmware.c
@@ -134,7 +134,8 @@ static int load_cpu_fw_direct(const char *fn, u8 __iomem *mem, struct cx18 *cx)
return size;
}
-static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx)
+static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx,
+ u32 *entry_addr)
{
const struct firmware *fw = NULL;
int i, j;
@@ -152,6 +153,7 @@ static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx)
return -ENOMEM;
}
+ *entry_addr = 0;
src = (const u32 *)fw->data;
vers = fw->data + sizeof(seghdr);
sz = fw->size;
@@ -168,10 +170,12 @@ static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx)
}
CX18_DEBUG_INFO("load segment %x-%x\n", seghdr.addr,
seghdr.addr + seghdr.size - 1);
+ if (*entry_addr == 0)
+ *entry_addr = seghdr.addr;
if (offset + seghdr.size > sz)
break;
for (i = 0; i < seghdr.size; i += 4096) {
- cx18_setup_page(cx, offset + i);
+ cx18_setup_page(cx, seghdr.addr + i);
for (j = i; j < seghdr.size && j < i + 4096; j += 4) {
/* no need for endianness conversion on the ppc */
cx18_raw_writel(cx, src[(offset + j) / 4],
@@ -192,8 +196,6 @@ static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx)
fn, apu_version, fw->size);
size = fw->size;
release_firmware(fw);
- /* Clear bit0 for APU to start from 0 */
- cx18_write_reg(cx, cx18_read_reg(cx, 0xc72030) & ~1, 0xc72030);
return size;
}
@@ -338,11 +340,19 @@ int cx18_firmware_init(struct cx18 *cx)
/* Only if the processor is not running */
if (cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 8) {
+ u32 fw_entry_addr = 0;
int sz = load_apu_fw_direct("v4l-cx23418-apu.fw",
- cx->enc_mem, cx);
+ cx->enc_mem, cx, &fw_entry_addr);
+
+ if (sz <= 0)
+ return sz;
+
+ /* Clear bit0 for APU to start from 0 */
+ cx18_write_reg(cx, cx18_read_reg(cx, 0xc72030) & ~1, 0xc72030);
+
+ cx18_write_enc(cx, 0xE51FF004, 0); /* ldr pc, [pc, #-4] */
+ cx18_write_enc(cx, fw_entry_addr, 4);
- cx18_write_enc(cx, 0xE51FF004, 0);
- cx18_write_enc(cx, 0xa00000, 4); /* todo: not hardcoded */
/* Start APU */
cx18_write_reg_expect(cx, 0x00010000, CX18_PROC_SOFT_RESET,
0x00000000, 0x00010001);
@@ -373,6 +383,17 @@ int cx18_firmware_init(struct cx18 *cx)
if (sz <= 0)
return -EIO;
}
+
+ /*
+ * The CPU firmware apparently sets up to receive an interrupt for it's
+ * outgoing IRQ_CPU_TO_EPU_ACK to us (*boggle*). We get an interrupt
+ * when it sends us an ack, but by the time we process it, that flag in
+ * the SW2 status register has been cleared by the CPU firmware.
+ * We'll prevent that not so useful behavior by clearing the CPU's
+ * interrupt enables for Ack IRQ's we want to process.
+ */
+ cx18_sw2_irq_disable_cpu(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK);
+
/* initialize GPIO */
cx18_write_reg_expect(cx, 0x14001400, 0xc78110, 0x00001400, 0x14001400);
return 0;
diff --git a/linux/drivers/media/video/cx18/cx18-io.c b/linux/drivers/media/video/cx18/cx18-io.c
index 0ad8dea3e..3c6485fce 100644
--- a/linux/drivers/media/video/cx18/cx18-io.c
+++ b/linux/drivers/media/video/cx18/cx18-io.c
@@ -37,6 +37,10 @@ void cx18_log_statistics(struct cx18 *cx)
for (i = 0; i <= CX18_MAX_MMIO_RD_RETRIES; i++)
CX18_DEBUG_INFO("retried_read[%d] = %d\n", i,
atomic_read(&cx->mmio_stats.retried_read[i]));
+ for (i = 0; i <= CX18_MAX_MB_ACK_DELAY; i++)
+ if (atomic_read(&cx->mbox_stats.mb_ack_delay[i]))
+ CX18_DEBUG_INFO("mb_ack_delay[%d] = %d\n", i,
+ atomic_read(&cx->mbox_stats.mb_ack_delay[i]));
return;
}
@@ -258,6 +262,13 @@ void cx18_sw2_irq_disable(struct cx18 *cx, u32 val)
cx18_write_reg(cx, r & ~val, SW2_INT_ENABLE_PCI);
}
+void cx18_sw2_irq_disable_cpu(struct cx18 *cx, u32 val)
+{
+ u32 r;
+ r = cx18_read_reg(cx, SW2_INT_ENABLE_CPU);
+ cx18_write_reg(cx, r & ~val, SW2_INT_ENABLE_CPU);
+}
+
void cx18_setup_page(struct cx18 *cx, u32 addr)
{
u32 val;
diff --git a/linux/drivers/media/video/cx18/cx18-io.h b/linux/drivers/media/video/cx18/cx18-io.h
index cb695a596..4486b73fa 100644
--- a/linux/drivers/media/video/cx18/cx18-io.h
+++ b/linux/drivers/media/video/cx18/cx18-io.h
@@ -390,6 +390,7 @@ 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);
void cx18_sw2_irq_disable(struct cx18 *cx, u32 val);
+void cx18_sw2_irq_disable_cpu(struct cx18 *cx, u32 val);
void cx18_setup_page(struct cx18 *cx, u32 addr);
#endif /* CX18_IO_H */
diff --git a/linux/drivers/media/video/cx18/cx18-irq.c b/linux/drivers/media/video/cx18/cx18-irq.c
index aff5a3abd..3e23fc350 100644
--- a/linux/drivers/media/video/cx18/cx18-irq.c
+++ b/linux/drivers/media/video/cx18/cx18-irq.c
@@ -40,17 +40,11 @@ void cx18_work_handler(void *arg)
{
struct cx18 *cx = arg;
#endif
- if (test_and_clear_bit(CX18_F_I_WORK_INITED, &cx->i_flags)) {
- struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 };
- /* This thread must use the FIFO scheduler as it
- * is realtime sensitive. */
- sched_setscheduler(current, SCHED_FIFO, &param);
- }
if (test_and_clear_bit(CX18_F_I_WORK_HANDLER_DVB, &cx->i_flags))
cx18_dvb_work_handler(cx);
}
-static void epu_dma_done(struct cx18 *cx, struct cx18_mailbox *mb)
+static void epu_dma_done(struct cx18 *cx, struct cx18_mailbox *mb, int rpu)
{
u32 handle = mb->args[0];
struct cx18_stream *s = NULL;
@@ -71,7 +65,7 @@ static void epu_dma_done(struct cx18 *cx, struct cx18_mailbox *mb)
" handle %d\n", handle);
mb->error = CXERR_NOT_OPEN;
mb->cmd = 0;
- cx18_mb_ack(cx, mb);
+ cx18_mb_ack(cx, mb, rpu);
return;
}
@@ -98,13 +92,13 @@ static void epu_dma_done(struct cx18 *cx, struct cx18_mailbox *mb)
}
mb->error = 0;
mb->cmd = 0;
- cx18_mb_ack(cx, mb);
+ cx18_mb_ack(cx, mb, rpu);
wake_up(&cx->dma_waitq);
if (s->id != -1)
wake_up(&s->waitq);
}
-static void epu_debug(struct cx18 *cx, struct cx18_mailbox *mb)
+static void epu_debug(struct cx18 *cx, struct cx18_mailbox *mb, int rpu)
{
char str[256] = { 0 };
char *p;
@@ -114,7 +108,7 @@ static void epu_debug(struct cx18 *cx, struct cx18_mailbox *mb)
cx18_memcpy_fromio(cx, str, cx->enc_mem + mb->args[1], 252);
str[252] = 0;
}
- cx18_mb_ack(cx, mb);
+ cx18_mb_ack(cx, mb, rpu);
CX18_DEBUG_INFO("%x %s\n", mb->args[0], str);
p = strchr(str, '.');
if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags) && p && p > str)
@@ -131,10 +125,10 @@ static void epu_cmd(struct cx18 *cx, u32 sw1)
switch (mb.cmd) {
case CX18_EPU_DMA_DONE:
- epu_dma_done(cx, &mb);
+ epu_dma_done(cx, &mb, CPU);
break;
case CX18_EPU_DEBUG:
- epu_debug(cx, &mb);
+ epu_debug(cx, &mb, CPU);
break;
default:
CX18_WARN("Unknown CPU_TO_EPU mailbox command %#08x\n",
@@ -147,11 +141,6 @@ static void epu_cmd(struct cx18 *cx, u32 sw1)
cx18_memcpy_fromio(cx, &mb, &cx->scb->apu2epu_mb, sizeof(mb));
CX18_WARN("Unknown APU_TO_EPU mailbox command %#08x\n", mb.cmd);
}
-
- if (sw1 & IRQ_HPU_TO_EPU) {
- cx18_memcpy_fromio(cx, &mb, &cx->scb->hpu2epu_mb, sizeof(mb));
- CX18_WARN("Unknown HPU_TO_EPU mailbox command %#08x\n", mb.cmd);
- }
}
static void xpu_ack(struct cx18 *cx, u32 sw2)
@@ -160,8 +149,6 @@ static void xpu_ack(struct cx18 *cx, u32 sw2)
wake_up(&cx->mb_cpu_waitq);
if (sw2 & IRQ_APU_TO_EPU_ACK)
wake_up(&cx->mb_apu_waitq);
- if (sw2 & IRQ_HPU_TO_EPU_ACK)
- wake_up(&cx->mb_hpu_waitq);
}
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
@@ -190,7 +177,8 @@ irqreturn_t cx18_irq_handler(int irq, void *dev_id)
cx18_write_reg_expect(cx, hw2, HW2_INT_CLR_STATUS, ~hw2, hw2);
if (sw1 || sw2 || hw2)
- CX18_DEBUG_HI_IRQ("SW1: %x SW2: %x HW2: %x\n", sw1, sw2, hw2);
+ CX18_DEBUG_HI_IRQ("received interrupts "
+ "SW1: %x SW2: %x HW2: %x\n", sw1, sw2, hw2);
/* To do: interrupt-based I2C handling
if (hw2 & (HW2_I2C1_INT|HW2_I2C2_INT)) {
@@ -204,7 +192,7 @@ irqreturn_t cx18_irq_handler(int irq, void *dev_id)
epu_cmd(cx, sw1);
if (test_and_clear_bit(CX18_F_I_HAVE_WORK, &cx->i_flags))
- queue_work(cx->work_queue, &cx->work);
+ schedule_work(&cx->work);
return (sw1 || sw2 || hw2) ? IRQ_HANDLED : IRQ_NONE;
}
diff --git a/linux/drivers/media/video/cx18/cx18-irq.h b/linux/drivers/media/video/cx18/cx18-irq.h
index d1d9d1967..6472e42c6 100644
--- a/linux/drivers/media/video/cx18/cx18-irq.h
+++ b/linux/drivers/media/video/cx18/cx18-irq.h
@@ -28,6 +28,7 @@
#define SW1_INT_ENABLE_PCI 0xc7311c
#define SW2_INT_SET 0xc73140
#define SW2_INT_STATUS 0xc73144
+#define SW2_INT_ENABLE_CPU 0xc73158
#define SW2_INT_ENABLE_PCI 0xc7315c
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
diff --git a/linux/drivers/media/video/cx18/cx18-mailbox.c b/linux/drivers/media/video/cx18/cx18-mailbox.c
index acff7dfb6..35f7188d4 100644
--- a/linux/drivers/media/video/cx18/cx18-mailbox.c
+++ b/linux/drivers/media/video/cx18/cx18-mailbox.c
@@ -30,11 +30,6 @@
#define API_FAST (1 << 2) /* Short timeout */
#define API_SLOW (1 << 3) /* Additional 300ms timeout */
-#define APU 0
-#define CPU 1
-#define EPU 2
-#define HPU 3
-
struct cx18_api_info {
u32 cmd;
u8 flags; /* Flags, see above */
@@ -97,70 +92,12 @@ static const struct cx18_api_info *find_api_info(u32 cmd)
return NULL;
}
-static struct cx18_mailbox __iomem *cx18_mb_is_complete(struct cx18 *cx, int rpu,
- u32 *state, u32 *irq, u32 *req)
+long cx18_mb_ack(struct cx18 *cx, const struct cx18_mailbox *mb, int rpu)
{
- struct cx18_mailbox __iomem *mb = NULL;
- int wait_count = 0;
- u32 ack;
-
- switch (rpu) {
- case APU:
- mb = &cx->scb->epu2apu_mb;
- *state = cx18_readl(cx, &cx->scb->apu_state);
- *irq = cx18_readl(cx, &cx->scb->epu2apu_irq);
- break;
-
- case CPU:
- mb = &cx->scb->epu2cpu_mb;
- *state = cx18_readl(cx, &cx->scb->cpu_state);
- *irq = cx18_readl(cx, &cx->scb->epu2cpu_irq);
- break;
-
- case HPU:
- mb = &cx->scb->epu2hpu_mb;
- *state = cx18_readl(cx, &cx->scb->hpu_state);
- *irq = cx18_readl(cx, &cx->scb->epu2hpu_irq);
- break;
- }
-
- if (mb == NULL)
- return mb;
-
- do {
- *req = cx18_readl(cx, &mb->request);
- ack = cx18_readl(cx, &mb->ack);
- wait_count++;
- } while (*req != ack && wait_count < 600);
-
- if (*req == ack) {
- (*req)++;
- if (*req == 0 || *req == 0xffffffff)
- *req = 1;
- return mb;
- }
- return NULL;
-}
-
-long cx18_mb_ack(struct cx18 *cx, const struct cx18_mailbox *mb)
-{
- const struct cx18_api_info *info = find_api_info(mb->cmd);
struct cx18_mailbox __iomem *ack_mb;
u32 ack_irq;
- u8 rpu = CPU;
-
- if (info == NULL && mb->cmd) {
- CX18_WARN("Cannot ack unknown command %x\n", mb->cmd);
- return -EINVAL;
- }
- if (info)
- rpu = info->rpu;
switch (rpu) {
- case HPU:
- ack_irq = IRQ_EPU_TO_HPU_ACK;
- ack_mb = &cx->scb->hpu2epu_mb;
- break;
case APU:
ack_irq = IRQ_EPU_TO_APU_ACK;
ack_mb = &cx->scb->apu2epu_mb;
@@ -170,7 +107,8 @@ long cx18_mb_ack(struct cx18 *cx, const struct cx18_mailbox *mb)
ack_mb = &cx->scb->cpu2epu_mb;
break;
default:
- CX18_WARN("Unknown RPU for command %x\n", mb->cmd);
+ CX18_WARN("Unhandled RPU (%d) for command %x ack\n",
+ rpu, mb->cmd);
return -EINVAL;
}
@@ -180,16 +118,22 @@ long cx18_mb_ack(struct cx18 *cx, const struct cx18_mailbox *mb)
return 0;
}
+static void cx18_api_log_ack_delay(struct cx18 *cx, int msecs)
+{
+ if (msecs > CX18_MAX_MB_ACK_DELAY)
+ msecs = CX18_MAX_MB_ACK_DELAY;
+ atomic_inc(&cx->mbox_stats.mb_ack_delay[msecs]);
+}
static int cx18_api_call(struct cx18 *cx, u32 cmd, int args, u32 data[])
{
const struct cx18_api_info *info = find_api_info(cmd);
- u32 state = 0, irq = 0, req, oldreq, err;
+ u32 state, irq, req, ack, err;
struct cx18_mailbox __iomem *mb;
+ u32 __iomem *xpu_state;
wait_queue_head_t *waitq;
- int timeout = 100;
- int cnt = 0;
- int sig = 0;
+ struct mutex *mb_lock;
+ long int timeout, ret;
int i;
if (info == NULL) {
@@ -201,50 +145,116 @@ static int cx18_api_call(struct cx18 *cx, u32 cmd, int args, u32 data[])
CX18_DEBUG_HI_API("%s\n", info->name);
else
CX18_DEBUG_API("%s\n", info->name);
- cx18_setup_page(cx, SCB_OFFSET);
- mb = cx18_mb_is_complete(cx, info->rpu, &state, &irq, &req);
- if (mb == NULL) {
- CX18_ERR("mb %s busy\n", info->name);
- return -EBUSY;
+ switch (info->rpu) {
+ case APU:
+ waitq = &cx->mb_apu_waitq;
+ mb_lock = &cx->epu2apu_mb_lock;
+ irq = IRQ_EPU_TO_APU;
+ mb = &cx->scb->epu2apu_mb;
+ xpu_state = &cx->scb->apu_state;
+ break;
+ case CPU:
+ waitq = &cx->mb_cpu_waitq;
+ mb_lock = &cx->epu2cpu_mb_lock;
+ irq = IRQ_EPU_TO_CPU;
+ mb = &cx->scb->epu2cpu_mb;
+ xpu_state = &cx->scb->cpu_state;
+ break;
+ default:
+ CX18_WARN("Unknown RPU (%d) for API call\n", info->rpu);
+ return -EINVAL;
}
- oldreq = req - 1;
+ mutex_lock(mb_lock);
+ cx18_setup_page(cx, SCB_OFFSET);
+
+ /*
+ * Wait for an in-use mailbox to complete
+ *
+ * If the XPU is responding with Ack's, the mailbox shouldn't be in
+ * a busy state, since we serialize access to it on our end.
+ *
+ * If the wait for ack after sending a previous command was interrupted
+ * by a signal, we may get here and find a busy mailbox. After waiting,
+ * mark it "not busy" from our end, if the XPU hasn't ack'ed it still.
+ */
+ state = cx18_readl(cx, xpu_state);
+ req = cx18_readl(cx, &mb->request);
+ timeout = msecs_to_jiffies(20); /* 1 field at 50 Hz vertical refresh */
+ ret = wait_event_timeout(*waitq,
+ (ack = cx18_readl(cx, &mb->ack)) == req,
+ timeout);
+ if (req != ack) {
+ /* waited long enough, make the mbox "not busy" from our end */
+ cx18_writel(cx, req, &mb->ack);
+ CX18_ERR("mbox was found stuck busy when setting up for %s; "
+ "clearing busy and trying to proceed\n", info->name);
+ } else if (ret != timeout)
+ CX18_DEBUG_API("waited %u usecs for busy mbox to be acked\n",
+ jiffies_to_usecs(timeout-ret));
+
+ /* Build the outgoing mailbox */
+ req = ((req & 0xfffffffe) == 0xfffffffe) ? 1 : req + 1;
+
cx18_writel(cx, cmd, &mb->cmd);
for (i = 0; i < args; i++)
cx18_writel(cx, data[i], &mb->args[i]);
cx18_writel(cx, 0, &mb->error);
cx18_writel(cx, req, &mb->request);
+ cx18_writel(cx, req - 1, &mb->ack); /* ensure ack & req are distinct */
- switch (info->rpu) {
- case APU: waitq = &cx->mb_apu_waitq; break;
- case CPU: waitq = &cx->mb_cpu_waitq; break;
- case EPU: waitq = &cx->mb_epu_waitq; break;
- case HPU: waitq = &cx->mb_hpu_waitq; break;
- default: return -EINVAL;
- }
- if (info->flags & API_FAST)
- timeout /= 2;
+ /*
+ * Notify the XPU and wait for it to send an Ack back
+ * 21 ms = ~ 0.5 frames at a frame rate of 24 fps
+ * 42 ms = ~ 1 frame at a frame rate of 24 fps
+ */
+ timeout = msecs_to_jiffies((info->flags & API_FAST) ? 21 : 42);
+
+ CX18_DEBUG_HI_IRQ("sending interrupt SW1: %x to send %s\n",
+ irq, info->name);
cx18_write_reg_expect(cx, irq, SW1_INT_SET, irq, irq);
- while (!sig && cx18_readl(cx, &mb->ack) != cx18_readl(cx, &mb->request)
- && cnt < 660) {
- if (cnt > 200 && !in_atomic())
- sig = cx18_msleep_timeout(10, 1);
- cnt++;
- }
- if (sig)
- return -EINTR;
- if (cnt == 660) {
- cx18_writel(cx, oldreq, &mb->request);
- CX18_ERR("mb %s failed\n", info->name);
+ ret = wait_event_timeout(
+ *waitq,
+ cx18_readl(cx, &mb->ack) == cx18_readl(cx, &mb->request),
+ timeout);
+ if (ret == 0) {
+ /* Timed out */
+ mutex_unlock(mb_lock);
+ i = jiffies_to_msecs(timeout);
+ cx18_api_log_ack_delay(cx, i);
+ CX18_WARN("sending %s timed out waiting %d msecs for RPU "
+ "acknowledgement\n", info->name, i);
return -EINVAL;
+ } else if (ret < 0) {
+ /* Interrupted */
+ mutex_unlock(mb_lock);
+ CX18_WARN("sending %s was interrupted waiting for RPU"
+ "acknowledgement\n", info->name);
+ return -EINTR;
}
+
+ i = jiffies_to_msecs(timeout-ret);
+ cx18_api_log_ack_delay(cx, i);
+ if (ret != timeout)
+ CX18_DEBUG_HI_API("waited %u msecs for %s to be acked\n",
+ i, info->name);
+
+ /* Collect data returned by the XPU */
for (i = 0; i < MAX_MB_ARGUMENTS; i++)
data[i] = cx18_readl(cx, &mb->args[i]);
err = cx18_readl(cx, &mb->error);
- if (!in_atomic() && (info->flags & API_SLOW))
+ mutex_unlock(mb_lock);
+
+ /*
+ * Wait for XPU to perform extra actions for the caller in some cases.
+ * e.g. CX18_CPU_DE_RELEASE_MDL will cause the CPU to send all buffers
+ * back in a burst shortly thereafter
+ */
+ if (info->flags & API_SLOW)
cx18_msleep_timeout(300, 0);
+
if (err)
CX18_DEBUG_API("mailbox error %08x for command %s\n", err,
info->name);
@@ -253,12 +263,7 @@ static int cx18_api_call(struct cx18 *cx, u32 cmd, int args, u32 data[])
int cx18_api(struct cx18 *cx, u32 cmd, int args, u32 data[])
{
- int res = cx18_api_call(cx, cmd, args, data);
-
- /* Allow a single retry, probably already too late though.
- If there is no free mailbox then that is usually an indication
- of a more serious problem. */
- return (res == -EBUSY) ? cx18_api_call(cx, cmd, args, data) : res;
+ return cx18_api_call(cx, cmd, args, data);
}
static int cx18_set_filter_param(struct cx18_stream *s)
diff --git a/linux/drivers/media/video/cx18/cx18-mailbox.h b/linux/drivers/media/video/cx18/cx18-mailbox.h
index d99564153..54758f32d 100644
--- a/linux/drivers/media/video/cx18/cx18-mailbox.h
+++ b/linux/drivers/media/video/cx18/cx18-mailbox.h
@@ -30,6 +30,11 @@
#define MB_RESERVED_HANDLE_0 0
#define MB_RESERVED_HANDLE_1 0xFFFFFFFF
+#define APU 0
+#define CPU 1
+#define EPU 2
+#define HPU 3
+
struct cx18;
/* The cx18_mailbox struct is the mailbox structure which is used for passing
@@ -68,6 +73,6 @@ int cx18_vapi_result(struct cx18 *cx, u32 data[MAX_MB_ARGUMENTS], u32 cmd,
int cx18_vapi(struct cx18 *cx, u32 cmd, int args, ...);
int cx18_api_func(void *priv, u32 cmd, int in, int out,
u32 data[CX2341X_MBOX_MAX_DATA]);
-long cx18_mb_ack(struct cx18 *cx, const struct cx18_mailbox *mb);
+long cx18_mb_ack(struct cx18 *cx, const struct cx18_mailbox *mb, int rpu);
#endif
diff --git a/linux/drivers/media/video/cx18/cx18-streams.c b/linux/drivers/media/video/cx18/cx18-streams.c
index 8ead4025e..d29a0b61b 100644
--- a/linux/drivers/media/video/cx18/cx18-streams.c
+++ b/linux/drivers/media/video/cx18/cx18-streams.c
@@ -587,9 +587,6 @@ int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end)
#endif
}
- /* Tell the CX23418 it can't use our buffers anymore */
- cx18_vapi(cx, CX18_CPU_DE_RELEASE_MDL, 1, s->handle);
-
if (s->type != CX18_ENC_STREAM_TYPE_TS)
atomic_dec(&cx->ana_capturing);
atomic_dec(&cx->tot_capturing);
@@ -597,6 +594,9 @@ int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end)
/* Clear capture and no-read bits */
clear_bit(CX18_F_S_STREAMING, &s->s_flags);
+ /* Tell the CX23418 it can't use our buffers anymore */
+ cx18_vapi(cx, CX18_CPU_DE_RELEASE_MDL, 1, s->handle);
+
cx18_vapi(cx, CX18_DESTROY_TASK, 1, s->handle);
s->handle = CX18_INVALID_TASK_HANDLE;
diff --git a/linux/drivers/media/video/cx18/cx18-version.h b/linux/drivers/media/video/cx18/cx18-version.h
index 9f6be2d45..366cc1472 100644
--- a/linux/drivers/media/video/cx18/cx18-version.h
+++ b/linux/drivers/media/video/cx18/cx18-version.h
@@ -25,7 +25,7 @@
#define CX18_DRIVER_NAME "cx18"
#define CX18_DRIVER_VERSION_MAJOR 1
#define CX18_DRIVER_VERSION_MINOR 0
-#define CX18_DRIVER_VERSION_PATCHLEVEL 1
+#define CX18_DRIVER_VERSION_PATCHLEVEL 2
#define CX18_VERSION __stringify(CX18_DRIVER_VERSION_MAJOR) "." __stringify(CX18_DRIVER_VERSION_MINOR) "." __stringify(CX18_DRIVER_VERSION_PATCHLEVEL)
#define CX18_DRIVER_VERSION KERNEL_VERSION(CX18_DRIVER_VERSION_MAJOR, \