// SPDX-License-Identifier: GPL-2.0-or-later /* * tw68 functions to handle video data * * Much of this code is derived from the cx88 and sa7134 drivers, which * were in turn derived from the bt87x driver. The original work was by * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, * Hans Verkuil, Andy Walls and many others. Their work is gratefully * acknowledged. Full credit goes to them - any problems within this code * are mine. * * Copyright (C) 2009 William M. Brack * * Refactored and updated to the latest v4l core frameworks: * * Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
*/
/* * The following table is searched by tw68_s_std, first for a specific * match, then for an entry which contains the desired id. The table * entries should therefore be ordered in ascending order of specificity.
*/ staticconststruct tw68_tvnorm tvnorms[] = {
{
.name = "PAL", /* autodetect */
.id = V4L2_STD_PAL,
NORM_625_50,
for (i = 0; i < FORMATS; i++) if (formats[i].fourcc == fourcc) return formats+i; return NULL;
}
/* ------------------------------------------------------------------ */ /* * Note that the cropping rectangles are described in terms of a single * frame, i.e. line positions are only 1/2 the interlaced equivalent
*/ staticvoid set_tvnorm(struct tw68_dev *dev, conststruct tw68_tvnorm *norm)
{ if (norm != dev->tvnorm) {
dev->width = 720;
dev->height = (norm->id & V4L2_STD_525_60) ? 480 : 576;
dev->tvnorm = norm;
tw68_set_tvnorm_hw(dev);
}
}
/* * tw68_set_scale * * Scaling and Cropping for video decoding * * We are working with 3 values for horizontal and vertical - scale, * delay and active. * * HACTIVE represent the actual number of pixels in the "usable" image, * before scaling. HDELAY represents the number of pixels skipped * between the start of the horizontal sync and the start of the image. * HSCALE is calculated using the formula * HSCALE = (HACTIVE / (#pixels desired)) * 256 * * The vertical registers are similar, except based upon the total number * of lines in the image, and the first line of the image (i.e. ignoring * vertical sync and VBI). * * Note that the number of bytes reaching the FIFO (and hence needing * to be processed by the DMAP program) is completely dependent upon * these values, especially HSCALE. * * Parameters: * @dev pointer to the device structure, needed for * getting current norm (as well as debug print) * @width actual image width (from user buffer) * @height actual image height * @field indicates Top, Bottom or Interlaced
*/ staticint tw68_set_scale(struct tw68_dev *dev, unsignedint width, unsignedint height, enum v4l2_field field)
{ conststruct tw68_tvnorm *norm = dev->tvnorm; /* set individually for debugging clarity */ int hactive, hdelay, hscale; int vactive, vdelay, vscale; int comb;
if (V4L2_FIELD_HAS_BOTH(field)) /* if field is interlaced */
height /= 2; /* we must set for 1-frame */
int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_buf *buf)
{ /* Set cropping and scaling */
tw68_set_scale(dev, dev->width, dev->height, dev->field); /* * Set start address for RISC program. Note that if the DMAP * processor is currently running, it must be stopped before * a new address can be set.
*/
tw_clearl(TW68_DMAC, TW68_DMAP_EN);
tw_writel(TW68_DMAP_SA, buf->dma); /* Clear any pending interrupts */
tw_writel(TW68_INTSTAT, dev->board_virqmask); /* Enable the risc engine and the fifo */
tw_andorl(TW68_DMAC, 0xff, dev->fmt->twformat |
ColorFormatGamma | TW68_DMAP_EN | TW68_FIFO_EN);
dev->pci_irqmask |= dev->board_virqmask;
tw_setl(TW68_INTMASK, dev->pci_irqmask); return 0;
}
/* calc max # of buffers from size (must not exceed the 4MB virtual
* address space per DMA channel) */ staticint tw68_buffer_count(unsignedint size, unsignedint count)
{ unsignedint maxcount;
if (tot_bufs < 2)
tot_bufs = 2;
tot_bufs = tw68_buffer_count(size, tot_bufs);
*num_buffers = tot_bufs - q_num_bufs; /* * We allow create_bufs, but only if the sizeimage is >= as the * current sizeimage. The tw68_buffer_count calculation becomes quite * difficult otherwise.
*/ if (*num_planes) return sizes[0] < size ? -EINVAL : 0;
*num_planes = 1;
sizes[0] = size;
return 0;
}
/* * The risc program for each buffers works as follows: it starts with a simple * 'JUMP to addr + 8', which is effectively a NOP. Then the program to DMA the * buffer follows and at the end we have a JUMP back to the start + 8 (skipping * the initial JUMP). * * This is the program of the first buffer to be queued if the active list is * empty and it just keeps DMAing this buffer without generating any interrupts. * * If a new buffer is added then the initial JUMP in the program generates an * interrupt as well which signals that the previous buffer has been DMAed * successfully and that it can be returned to userspace. * * It also sets the final jump of the previous buffer to the start of the new * buffer, thus chaining the new buffer into the DMA chain. This is a single * atomic u32 write, so there is no race condition. * * The end-result of all this that you only get an interrupt when a buffer * is ready, so the control flow is very easy.
*/ staticvoid tw68_buf_queue(struct vb2_buffer *vb)
{ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct vb2_queue *vq = vb->vb2_queue; struct tw68_dev *dev = vb2_get_drv_priv(vq); struct tw68_buf *buf = container_of(vbuf, struct tw68_buf, vb); struct tw68_buf *prev; unsignedlong flags;
spin_lock_irqsave(&dev->slock, flags);
/* append a 'JUMP to start of buffer' to the buffer risc program */
buf->jmp[0] = cpu_to_le32(RISC_JUMP);
buf->jmp[1] = cpu_to_le32(buf->dma + 8);
/* * buffer_prepare * * Set the ancillary information into the buffer structure. This * includes generating the necessary risc program if it hasn't already * been done for the current buffer format. * The structure fh contains the details of the format requested by the * user - type, width, height and #fields. This is compared with the * last format set for the current buffer. If they differ, the risc * code (which controls the filling of the buffer) is (re-)generated.
*/ staticint tw68_buf_prepare(struct vb2_buffer *vb)
{ int ret; struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct vb2_queue *vq = vb->vb2_queue; struct tw68_dev *dev = vb2_get_drv_priv(vq); struct tw68_buf *buf = container_of(vbuf, struct tw68_buf, vb); struct sg_table *dma = vb2_dma_sg_plane_desc(vb, 0); unsigned size, bpl;
/* * Note that this routine returns what is stored in the fh structure, and * does not interrogate any of the device registers.
*/ staticint tw68_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{ struct tw68_dev *dev = video_drvdata(file);
/* * Note that tw68_s_fmt_vid_cap sets the information into the fh structure, * and it will be used for all future new buffers. However, there could be * some number of buffers on the "active" chain which will be filled before * the change takes place.
*/ staticint tw68_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{ struct tw68_dev *dev = video_drvdata(file); int err;
/* Look for match on complete norm id (may have mult bits) */ for (i = 0; i < TVNORMS; i++) { if (id == tvnorms[i].id) break;
}
/* If no exact match, look for norm which contains this one */ if (i == TVNORMS) { for (i = 0; i < TVNORMS; i++) if (id & tvnorms[i].id) break;
} /* If still not matched, give up */ if (i == TVNORMS) return -EINVAL;
set_tvnorm(dev, &tvnorms[i]); /* do the actual setting */ return 0;
}
/* * Used strictly for internal development and debugging, this routine * prints out the current register contents for the tw68xx device.
*/ staticvoid tw68_dump_regs(struct tw68_dev *dev)
{ unsignedchar line[80]; int i, j, k; unsignedchar *cptr;
pr_info("Full dump of TW68 registers:\n"); /* First we do the PCI regs, 8 4-byte regs per line */ for (i = 0; i < 0x100; i += 32) {
cptr = line;
cptr += sprintf(cptr, "%03x ", i); /* j steps through the next 4 words */ for (j = i; j < i + 16; j += 4)
cptr += sprintf(cptr, "%08x ", tw_readl(j));
*cptr++ = ' '; for (; j < i + 32; j += 4)
cptr += sprintf(cptr, "%08x ", tw_readl(j));
*cptr++ = '\n';
*cptr = 0;
pr_info("%s", line);
} /* Next the control regs, which are single-byte, address mod 4 */ while (i < 0x400) {
cptr = line;
cptr += sprintf(cptr, "%03x ", i); /* Print out 4 groups of 4 bytes */ for (j = 0; j < 4; j++) { for (k = 0; k < 4; k++) {
cptr += sprintf(cptr, "%02x ",
tw_readb(i));
i += 4;
}
*cptr++ = ' ';
}
*cptr++ = '\n';
*cptr = 0;
pr_info("%s", line);
}
}
/* reset interrupts handled by this routine */
tw_writel(TW68_INTSTAT, status); /* * Check most likely first * * DMAPI shows we have reached the end of the risc code * for the current buffer.
*/ if (status & TW68_DMAPI) { struct tw68_buf *buf;
spin_lock(&dev->slock);
buf = list_entry(dev->active.next, struct tw68_buf, list);
list_del(&buf->list);
spin_unlock(&dev->slock);
buf->vb.vb2_buf.timestamp = ktime_get_ns();
buf->vb.field = dev->field;
buf->vb.sequence = dev->seqnr++;
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
status &= ~(TW68_DMAPI); if (0 == status) return;
} if (status & (TW68_VLOCK | TW68_HLOCK))
dev_dbg(&dev->pci->dev, "Lost sync\n"); if (status & TW68_PABORT)
dev_err(&dev->pci->dev, "PABORT interrupt\n"); if (status & TW68_DMAPERR)
dev_err(&dev->pci->dev, "DMAPERR interrupt\n"); /* * On TW6800, FDMIS is apparently generated if video input is switched * during operation. Therefore, it is not enabled for that chip.
*/ if (status & TW68_FDMIS)
dev_dbg(&dev->pci->dev, "FDMIS interrupt\n"); if (status & TW68_FFOF) { /* probably a logic error */
reg = tw_readl(TW68_DMAC) & TW68_FIFO_EN;
tw_clearl(TW68_DMAC, TW68_FIFO_EN);
dev_dbg(&dev->pci->dev, "FFOF interrupt\n");
tw_setl(TW68_DMAC, reg);
} if (status & TW68_FFERR)
dev_dbg(&dev->pci->dev, "FFERR interrupt\n");
}
Messung V0.5
¤ Dauer der Verarbeitung: 0.6 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.