Skip to content

Commit

Permalink
media: bcm2835-unicam: Use dummy buffer if none have been queued
Browse files Browse the repository at this point in the history
If no buffer has been queued by a userland application, we use an
internal dummy buffer for the hardware to spin in. This will allow
the driver to release the existing userland buffer back to the
application for processing.

Signed-off-by: Naushir Patuck <[email protected]>
  • Loading branch information
naushir authored and pelwell committed Apr 16, 2020
1 parent 4876c09 commit dfc2817
Showing 1 changed file with 110 additions and 50 deletions.
160 changes: 110 additions & 50 deletions drivers/media/platform/bcm2835/bcm2835-unicam.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
Expand Down Expand Up @@ -112,6 +113,12 @@ MODULE_PARM_DESC(debug, "Debug level 0-3");
/* Default size of the embedded buffer */
#define UNICAM_EMBEDDED_SIZE 8192

/*
* Size of the dummy buffer. Can be any size really, but the DMA
* allocation works in units of page sizes.
*/
#define DUMMY_BUF_SIZE (PAGE_SIZE)

enum pad_types {
IMAGE_PAD,
METADATA_PAD,
Expand Down Expand Up @@ -390,6 +397,12 @@ struct unicam_node {
struct media_pad pad;
struct v4l2_ctrl_handler ctrl_handler;
unsigned int embedded_lines;
/*
* Dummy buffer intended to be used by unicam
* if we have no other queued buffers to swap to.
*/
void *dummy_buf_cpu_addr;
dma_addr_t dummy_buf_dma_addr;
};

struct unicam_device {
Expand Down Expand Up @@ -661,27 +674,24 @@ static int unicam_reset_format(struct unicam_node *node)
return 0;
}

static void unicam_wr_dma_addr(struct unicam_device *dev, dma_addr_t dmaaddr,
int pad_id)
static void unicam_wr_dma_addr(struct unicam_cfg *cfg, dma_addr_t dmaaddr,
unsigned int buffer_size, int pad_id)
{
dma_addr_t endaddr;
dma_addr_t endaddr = dmaaddr + buffer_size;

/*
* dmaaddr should be a 32-bit address with the top two bits set to 0x3
* to signify uncached access through the Videocore memory controller.
* dmaaddr and endaddr should be a 32-bit address with the top two bits
* set to 0x3 to signify uncached access through the Videocore memory
* controller.
*/
BUG_ON((dmaaddr >> 30) != 0x3);
BUG_ON((dmaaddr >> 30) != 0x3 && (endaddr >> 30) != 0x3);

if (pad_id == IMAGE_PAD) {
endaddr = dmaaddr +
dev->node[IMAGE_PAD].v_fmt.fmt.pix.sizeimage;
reg_write(&dev->cfg, UNICAM_IBSA0, dmaaddr);
reg_write(&dev->cfg, UNICAM_IBEA0, endaddr);
reg_write(cfg, UNICAM_IBSA0, dmaaddr);
reg_write(cfg, UNICAM_IBEA0, endaddr);
} else {
endaddr = dmaaddr +
dev->node[METADATA_PAD].v_fmt.fmt.meta.buffersize;
reg_write(&dev->cfg, UNICAM_DBSA0, dmaaddr);
reg_write(&dev->cfg, UNICAM_DBEA0, endaddr);
reg_write(cfg, UNICAM_DBSA0, dmaaddr);
reg_write(cfg, UNICAM_DBEA0, endaddr);
}
}

Expand All @@ -704,14 +714,31 @@ static inline void unicam_schedule_next_buffer(struct unicam_node *node)
struct unicam_device *dev = node->dev;
struct unicam_dmaqueue *dma_q = &node->dma_queue;
struct unicam_buffer *buf;
unsigned int size;
dma_addr_t addr;

buf = list_entry(dma_q->active.next, struct unicam_buffer, list);
node->next_frm = buf;
list_del(&buf->list);

addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
unicam_wr_dma_addr(dev, addr, node->pad_id);
size = (node->pad_id == IMAGE_PAD) ?
dev->node[IMAGE_PAD].v_fmt.fmt.pix.sizeimage :
dev->node[METADATA_PAD].v_fmt.fmt.meta.buffersize;

unicam_wr_dma_addr(&dev->cfg, addr, size, node->pad_id);
}

static inline void unicam_schedule_dummy_buffer(struct unicam_node *node)
{
struct unicam_device *dev = node->dev;
dma_addr_t addr = node->dummy_buf_dma_addr;

unicam_dbg(3, dev, "Scheduling dummy buffer for node %d\n",
node->pad_id);

unicam_wr_dma_addr(&dev->cfg, addr, DUMMY_BUF_SIZE, node->pad_id);
node->next_frm = NULL;
}

static inline void unicam_process_buffer_complete(struct unicam_node *node,
Expand All @@ -721,7 +748,6 @@ static inline void unicam_process_buffer_complete(struct unicam_node *node,
node->cur_frm->vb.sequence = sequence;

vb2_buffer_done(&node->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE);
node->cur_frm = node->next_frm;
}

static int unicam_num_nodes_streaming(struct unicam_device *dev)
Expand Down Expand Up @@ -788,6 +814,28 @@ static irqreturn_t unicam_isr(int irq, void *dev)
if (!(sta && (UNICAM_IS | UNICAM_PI0)))
return IRQ_HANDLED;

/*
* We must run the frame end handler first. If we have a valid next_frm
* and we get a simultaneout FE + FS interrupt, running the FS handler
* first would null out the next_frm ptr and we would have lost the
* buffer forever.
*/
if (ista & UNICAM_FEI || sta & UNICAM_PI0) {
/*
* Ensure we have swapped buffers already as we can't
* stop the peripheral. If no buffer is available, use a
* dummy buffer to dump out frames until we get a new buffer
* to use.
*/
for (i = 0; i < num_nodes_streaming; i++) {
if (unicam->node[i].cur_frm)
unicam_process_buffer_complete(&unicam->node[i],
sequence);
unicam->node[i].cur_frm = unicam->node[i].next_frm;
}
unicam->sequence++;
}

if (ista & UNICAM_FSI) {
/*
* Timestamp is to be when the first data byte was captured,
Expand All @@ -798,32 +846,24 @@ static irqreturn_t unicam_isr(int irq, void *dev)
if (unicam->node[i].cur_frm)
unicam->node[i].cur_frm->vb.vb2_buf.timestamp =
ts;
/*
* Set the next frame output to go to a dummy frame
* if we have not managed to obtain another frame
* from the queue.
*/
unicam_schedule_dummy_buffer(&unicam->node[i]);
}
}
if (ista & UNICAM_FEI || sta & UNICAM_PI0) {
/*
* Ensure we have swapped buffers already as we can't
* stop the peripheral. Overwrite the frame we've just
* captured instead.
*/
for (i = 0; i < num_nodes_streaming; i++) {
if (unicam->node[i].cur_frm &&
unicam->node[i].cur_frm != unicam->node[i].next_frm)
unicam_process_buffer_complete(&unicam->node[i],
sequence);
}
unicam->sequence++;
}

/* Cannot swap buffer at frame end, there may be a race condition
/*
* Cannot swap buffer at frame end, there may be a race condition
* where the HW does not actually swap it if the new frame has
* already started.
*/
if (ista & (UNICAM_FSI | UNICAM_LCI) && !(ista & UNICAM_FEI)) {
for (i = 0; i < num_nodes_streaming; i++) {
spin_lock(&unicam->node[i].dma_queue_lock);
if (!list_empty(&unicam->node[i].dma_queue.active) &&
unicam->node[i].cur_frm == unicam->node[i].next_frm)
!unicam->node[i].next_frm)
unicam_schedule_next_buffer(&unicam->node[i]);
spin_unlock(&unicam->node[i].dma_queue_lock);
}
Expand Down Expand Up @@ -1352,7 +1392,7 @@ static void unicam_start_rx(struct unicam_device *dev, dma_addr_t *addr)
{
struct unicam_cfg *cfg = &dev->cfg;
int line_int_freq = dev->node[IMAGE_PAD].v_fmt.fmt.pix.height >> 2;
unsigned int i;
unsigned int size, i;
u32 val;

if (line_int_freq < 128)
Expand Down Expand Up @@ -1413,7 +1453,7 @@ static void unicam_start_rx(struct unicam_device *dev, dma_addr_t *addr)
reg_write_field(cfg, UNICAM_ANA, 0, UNICAM_DDL);

/* Always start in trigger frame capture mode (UNICAM_FCM set) */
val = UNICAM_FSIE | UNICAM_FEIE | UNICAM_FCM;
val = UNICAM_FSIE | UNICAM_FEIE | UNICAM_FCM | UNICAM_IBOB;
set_field(&val, line_int_freq, UNICAM_LCIE_MASK);
reg_write(cfg, UNICAM_ICTL, val);
reg_write(cfg, UNICAM_STA, UNICAM_STA_MASK_ALL);
Expand Down Expand Up @@ -1501,7 +1541,8 @@ static void unicam_start_rx(struct unicam_device *dev, dma_addr_t *addr)

reg_write(&dev->cfg, UNICAM_IBLS,
dev->node[IMAGE_PAD].v_fmt.fmt.pix.bytesperline);
unicam_wr_dma_addr(dev, addr[IMAGE_PAD], IMAGE_PAD);
size = dev->node[IMAGE_PAD].v_fmt.fmt.pix.sizeimage;
unicam_wr_dma_addr(&dev->cfg, addr[IMAGE_PAD], size, IMAGE_PAD);
unicam_set_packing_config(dev);
unicam_cfg_image_id(dev);

Expand All @@ -1511,8 +1552,10 @@ static void unicam_start_rx(struct unicam_device *dev, dma_addr_t *addr)
reg_write(cfg, UNICAM_MISC, val);

if (dev->node[METADATA_PAD].streaming && dev->sensor_embedded_data) {
size = dev->node[METADATA_PAD].v_fmt.fmt.meta.buffersize;
unicam_enable_ed(dev);
unicam_wr_dma_addr(dev, addr[METADATA_PAD], METADATA_PAD);
unicam_wr_dma_addr(&dev->cfg, addr[METADATA_PAD], size,
METADATA_PAD);
}

/* Enable peripheral */
Expand Down Expand Up @@ -1686,13 +1729,14 @@ static void unicam_stop_streaming(struct vb2_queue *vq)
unicam_runtime_put(dev);

} else if (node->pad_id == METADATA_PAD) {
/* Null out the embedded data buffer address so the HW does
* not use it. This is only really needed if the embedded data
* pad is disabled before the image pad. The 0x3 in the top two
* bits signifies uncached accesses through the Videocore
* memory controller.
/* Allow the hardware to spin in the dummy buffer.
* This is only really needed if the embedded data pad is
* disabled before the image pad. The 0x3 in the top two bits
* signifies uncached accesses through the Videocore memory
* controller.
*/
unicam_wr_dma_addr(dev, 0xc0000000, METADATA_PAD);
unicam_wr_dma_addr(&dev->cfg, node->dummy_buf_dma_addr,
DUMMY_BUF_SIZE, METADATA_PAD);
}

/* Clear all queued buffers for the node */
Expand Down Expand Up @@ -2321,6 +2365,15 @@ static int register_node(struct unicam_device *unicam, struct unicam_node *node,
video_set_drvdata(vdev, node);
vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT;

node->dummy_buf_cpu_addr = dma_alloc_coherent(&unicam->pdev->dev,
DUMMY_BUF_SIZE,
&node->dummy_buf_dma_addr,
GFP_ATOMIC);
if (!node->dummy_buf_cpu_addr) {
unicam_err(unicam, "Unable to allocate dummy buffer.\n");
return -ENOMEM;
}

if (node->pad_id == METADATA_PAD ||
!v4l2_subdev_has_op(unicam->sensor, video, s_std)) {
v4l2_disable_ioctl(&node->video_dev, VIDIOC_S_STD);
Expand Down Expand Up @@ -2376,13 +2429,20 @@ static int register_node(struct unicam_device *unicam, struct unicam_node *node,

static void unregister_nodes(struct unicam_device *unicam)
{
if (unicam->node[IMAGE_PAD].registered) {
video_unregister_device(&unicam->node[IMAGE_PAD].video_dev);
unicam->node[IMAGE_PAD].registered = 0;
}
if (unicam->node[METADATA_PAD].registered) {
video_unregister_device(&unicam->node[METADATA_PAD].video_dev);
unicam->node[METADATA_PAD].registered = 0;
struct unicam_node *node;
int i;

for (i = 0; i < MAX_NODES; i++) {
node = &unicam->node[i];
if (node->dummy_buf_cpu_addr) {
dma_free_coherent(&unicam->pdev->dev, DUMMY_BUF_SIZE,
node->dummy_buf_cpu_addr,
node->dummy_buf_dma_addr);
}
if (node->registered) {
video_unregister_device(&node->video_dev);
node->registered = 0;
}
}
}

Expand Down

0 comments on commit dfc2817

Please sign in to comment.