diff --git a/drivers/media/platform/broadcom/bcm2835-unicam.c b/drivers/media/platform/broadcom/bcm2835-unicam.c index 1a6a0eacd49d9f..856efc6c97be7d 100644 --- a/drivers/media/platform/broadcom/bcm2835-unicam.c +++ b/drivers/media/platform/broadcom/bcm2835-unicam.c @@ -550,7 +550,8 @@ unicam_find_format_by_fourcc(u32 fourcc, u32 pad) } for (i = 0; i < num_formats; ++i) { - if (formats[i].fourcc == fourcc) + if (formats[i].fourcc == fourcc || + formats[i].unpacked_fourcc == fourcc) return &formats[i]; } @@ -640,9 +641,9 @@ static inline void unicam_reg_write_field(struct unicam_device *unicam, u32 offs } static void unicam_wr_dma_addr(struct unicam_node *node, - struct unicam_buffer *buf) + struct unicam_buffer *buf, unsigned int size) { - dma_addr_t endaddr = buf->dma_addr + buf->size; + dma_addr_t endaddr = buf->dma_addr + size; if (node->id == UNICAM_IMAGE_NODE) { unicam_reg_write(node->dev, UNICAM_IBSA0, buf->dma_addr); @@ -675,7 +676,7 @@ static void unicam_schedule_next_buffer(struct unicam_node *node) node->next_frm = buf; list_del(&buf->list); - unicam_wr_dma_addr(node, buf); + unicam_wr_dma_addr(node, buf, buf->size); } static void unicam_schedule_dummy_buffer(struct unicam_node *node) @@ -683,8 +684,13 @@ static void unicam_schedule_dummy_buffer(struct unicam_node *node) int node_id = is_image_node(node) ? UNICAM_IMAGE_NODE : UNICAM_METADATA_NODE; dev_dbg(node->dev->dev, "Scheduling dummy buffer for node %d\n", node_id); - - unicam_wr_dma_addr(node, &node->dummy_buf); + /* + * Due to a HW bug causing buffer overruns in circular buffer mode under + * certain (not yet fully known) conditions, the dummy buffer allocation + * is set to a a single page size, but the hardware gets programmed with + * a buffer size of 0. + */ + unicam_wr_dma_addr(node, &node->dummy_buf, 0); node->next_frm = NULL; } @@ -771,10 +777,25 @@ static irqreturn_t unicam_isr(int irq, void *dev) * as complete, as the HW will reuse that buffer. */ if (node->cur_frm && node->cur_frm != node->next_frm) { + /* + * This condition checks if FE + FS for the same + * frame has occurred. In such cases, we cannot + * return out the frame, as no buffer handling + * or timestamping has yet been done as part of + * the FS handler. + */ + if (!node->cur_frm->vb.vb2_buf.timestamp) { + dev_dbg(unicam->dev, "ISR: FE without FS, dropping frame\n"); + continue; + } + unicam_process_buffer_complete(node, sequence); + node->cur_frm = node->next_frm; + node->next_frm = NULL; inc_seq = true; + } else { + node->cur_frm = node->next_frm; } - node->cur_frm = node->next_frm; } /* @@ -807,17 +828,32 @@ static irqreturn_t unicam_isr(int irq, void *dev) continue; if (node->cur_frm) - node->cur_frm->vb.vb2_buf.timestamp = ts; + node->cur_frm->vb.vb2_buf.timestamp = + ts; else - dev_dbg(unicam->v4l2_dev.dev, - "ISR: [%d] Dropping frame, buffer not available at FS\n", + dev_dbg(unicam->dev, "ISR: [%d] Dropping frame, buffer not available at FS\n", i); /* * Set the next frame output to go to a dummy frame - * if we have not managed to obtain another frame - * from the queue. + * if no buffer currently queued. */ - unicam_schedule_dummy_buffer(node); + if (!node->next_frm || + node->next_frm == node->cur_frm) { + unicam_schedule_dummy_buffer(node); + } else if (unicam->node[i].cur_frm) { + /* + * Repeated FS without FE. Hardware will have + * swapped buffers, but the cur_frm doesn't + * contain valid data. Return cur_frm to the + * queue. + */ + spin_lock(&node->dma_queue_lock); + list_add_tail(&node->cur_frm->list, + &node->dma_queue); + spin_unlock(&node->dma_queue_lock); + node->cur_frm = node->next_frm; + node->next_frm = NULL; + } } unicam_queue_event_sof(unicam); @@ -843,11 +879,6 @@ static irqreturn_t unicam_isr(int irq, void *dev) } } - if (unicam_reg_read(unicam, UNICAM_ICTL) & UNICAM_FCM) { - /* Switch out of trigger mode if selected */ - unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC); - unicam_reg_write_field(unicam, UNICAM_ICTL, 0, UNICAM_FCM); - } return IRQ_HANDLED; } @@ -1011,8 +1042,7 @@ static void unicam_start_rx(struct unicam_device *unicam, unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_DDL); - /* Always start in trigger frame capture mode (UNICAM_FCM set) */ - val = UNICAM_FSIE | UNICAM_FEIE | UNICAM_FCM | UNICAM_IBOB; + val = UNICAM_FSIE | UNICAM_FEIE | UNICAM_IBOB; line_int_freq = max(fmt->height >> 2, 128); unicam_set_field(&val, line_int_freq, UNICAM_LCIE_MASK); unicam_reg_write(unicam, UNICAM_ICTL, val); @@ -1101,7 +1131,7 @@ static void unicam_start_rx(struct unicam_device *unicam, unicam_reg_write(unicam, UNICAM_IBLS, node->fmt.fmt.pix.bytesperline); - unicam_wr_dma_addr(node, node->cur_frm); + unicam_wr_dma_addr(node, node->cur_frm, node->cur_frm->size); unicam_set_packing_config(unicam, fmtinfo); ret = unicam_get_image_vc_dt(unicam, state, &vc, &dt); @@ -1139,7 +1169,7 @@ static void unicam_start_metadata(struct unicam_device *unicam) struct unicam_node *node = &unicam->node[UNICAM_METADATA_NODE]; unicam_enable_ed(unicam); - unicam_wr_dma_addr(node, node->cur_frm); + unicam_wr_dma_addr(node, node->cur_frm, node->cur_frm->size); unicam_reg_write_field(unicam, UNICAM_DCS, 1, UNICAM_LDP); }