/* * Copyright 2015 www.starterkit.ru * * Based on: * Driver for Intersil|Techwell TW6869 based DVR cards * (c) 2011-12 liran [Intersil|Techwell China] * * V4L2 PCI Skeleton Driver * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. * All rights reserved. * * This program is free software; you may redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tw6869.h" MODULE_DESCRIPTION("tw6869/65 media bridge driver"); MODULE_AUTHOR("starterkit "); MODULE_LICENSE("GPL"); MODULE_VERSION("0.3.2"); static const struct pci_device_id tw6869_pci_tbl[] = { {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6869)}, { 0, } }; struct tw6869_buf { struct vb2_buffer vb; struct list_head list; dma_addr_t dma; }; /** * struct tw6869_vch - instance of one video channel * @dev: parent device * @vdev: video channel (v4l2 video device) * @id: DMA id (0...7) * @p_buf: DMA P-buffer * @b_buf: DMA B-buffer * @pb: P-buffer | B-buffer ping-pong state * @mlock: the main serialization lock * @queue: queue maintained by videobuf2 layer * @buf_list: list of buffer in use * @lock: spinlock controlling access to channel * @hdl: handler for control framework * @format: pixel format * @std: video standard (e.g. PAL/NTSC) * @input: input line for video signal * @sequence: sequence number of acquired buffer * @dcount: number of dropped frames * @fps: current frame rate */ struct tw6869_vch { struct tw6869_dev *dev; struct video_device vdev; unsigned int id; struct tw6869_buf *p_buf; struct tw6869_buf *b_buf; unsigned int pb; struct mutex mlock; struct vb2_queue queue; struct list_head buf_list; spinlock_t lock; struct v4l2_ctrl_handler hdl; struct v4l2_pix_format format; v4l2_std_id std; unsigned int input; unsigned int sequence; unsigned int dcount; unsigned int fps; }; /** * struct tw6869_ach - instance of one audio channel * @dev: parent device * @ss: audio channel (pcm substream) * @id: DMA id (8...15) * @p_buf: DMA P-buffer * @b_buf: DMA B-buffer * @pb: P-buffer | B-buffer ping-pong state * @buf: split an contiguous buffer into chunks * @buf_list: chunk list * @lock: spinlock controlling access to channel * @ptr: PCM buffer pointer */ struct tw6869_ach { struct tw6869_dev *dev; struct snd_pcm_substream *ss; unsigned int id; struct tw6869_buf *p_buf; struct tw6869_buf *b_buf; unsigned int pb; struct tw6869_buf buf[TW_APAGE_MAX]; struct list_head buf_list; spinlock_t lock; dma_addr_t ptr; }; /** * struct tw6869_dev - instance of device * @pdev: PCI device * @mmio: hardware base address * @rlock: spinlock controlling access to registers * @ch_max: channels used * @id_err: DMA error counters * @v4l2_dev: device registered in v4l2 layer * @alloc_ctx: context for videobuf2 * @vch: array of video channel instance * @snd_card: device registered in ALSA layer * @ach: array of audio channel instance */ struct tw6869_dev { struct pci_dev *pdev; unsigned char __iomem *mmio; spinlock_t rlock; unsigned int ch_max; unsigned int id_err[2 * TW_CH_MAX]; struct v4l2_device v4l2_dev; struct vb2_alloc_ctx *alloc_ctx; struct tw6869_vch vch[TW_CH_MAX]; struct snd_card *snd_card; struct tw6869_ach ach[TW_CH_MAX]; }; /**********************************************************************/ static inline void tw_write(struct tw6869_dev *dev, unsigned int reg, unsigned int val) { iowrite32(val, dev->mmio + reg); } static inline unsigned int tw_read(struct tw6869_dev *dev, unsigned int reg) { return ioread32(dev->mmio + reg); } static inline void tw_write_mask(struct tw6869_dev *dev, unsigned int reg, unsigned int val, unsigned int mask) { unsigned int v = tw_read(dev, reg); v = (v & ~mask) | (val & mask); tw_write(dev, reg, v); } static inline void tw_clear(struct tw6869_dev *dev, unsigned int reg, unsigned int val) { tw_write_mask(dev, reg, 0, val); } static inline void tw_set(struct tw6869_dev *dev, unsigned int reg, unsigned int val) { tw_write_mask(dev, reg, val, val); } static inline unsigned int tw_id_is_on(struct tw6869_dev *dev, unsigned int id) { unsigned int e = tw_read(dev, R32_DMA_CHANNEL_ENABLE); unsigned int c = tw_read(dev, R32_DMA_CMD); return (c & BIT(31)) && (c & e & BIT_ID(id)); } static inline unsigned int tw_id_is_off(struct tw6869_dev *dev, unsigned int id) { unsigned int e = tw_read(dev, R32_DMA_CHANNEL_ENABLE); unsigned int c = tw_read(dev, R32_DMA_CMD); return !((c | e) & BIT_ID(id)); } static inline void tw_id_on(struct tw6869_dev *dev, unsigned int id) { int c = 3; while (!tw_id_is_on(dev, id) && c--) { tw_set(dev, R32_DMA_CMD, BIT(31) | BIT_ID(id)); tw_set(dev, R32_DMA_CHANNEL_ENABLE, BIT_ID(id)); } } static inline void tw_id_off(struct tw6869_dev *dev, unsigned int id) { int c = 3; while (!tw_id_is_off(dev, id) && c--) { tw_clear(dev, R32_DMA_CMD, BIT_ID(id)); tw_clear(dev, R32_DMA_CHANNEL_ENABLE, BIT_ID(id)); } if (!tw_read(dev, R32_DMA_CHANNEL_ENABLE)) tw_write(dev, R32_DMA_CMD, 0); } static void tw6869_id_dma_cmd(struct tw6869_dev *dev, unsigned int id, unsigned int cmd) { switch (cmd) { case TW_DMA_ON: dev->id_err[ID2ID(id)] = 0; tw_id_on(dev, id); dev_info(&dev->pdev->dev, "DMA %u ON\n", id); break; case TW_DMA_OFF: tw_id_off(dev, id); dev_info(&dev->pdev->dev, "DMA %u OFF\n", id); break; case TW_DMA_RST: if (tw_id_is_on(dev, id)) { tw_id_off(dev, id); if (++dev->id_err[ID2ID(id)] > TW_DMA_ERR_MAX) { dev_err(&dev->pdev->dev, "DMA %u forced OFF\n", id); break; } tw_id_on(dev, id); dev_info(&dev->pdev->dev, "DMA %u RST\n", id); } else { dev_info(&dev->pdev->dev, "DMA %u spurious RST\n", id); } break; default: dev_err(&dev->pdev->dev, "DMA %u unknown cmd %u\n", id, cmd); } } static unsigned int tw6869_virq(struct tw6869_dev *dev, unsigned int id, unsigned int pb, unsigned int err) { struct tw6869_vch *vch = &dev->vch[ID2CH(id)]; struct tw6869_buf *done = NULL; struct tw6869_buf *next = NULL; spin_lock(&vch->lock); if (!vb2_is_streaming(&vch->queue) || !vch->p_buf || !vch->b_buf) { spin_unlock(&vch->lock); return TW_DMA_OFF; } if (err || (vch->pb != pb)) { vch->pb = 0; spin_unlock(&vch->lock); return TW_DMA_RST; } if (!list_empty(&vch->buf_list)) { next = list_first_entry(&vch->buf_list, struct tw6869_buf, list); list_del(&next->list); if (pb) { done = vch->b_buf; vch->b_buf = next; } else { done = vch->p_buf; vch->p_buf = next; } } vch->pb = !pb; spin_unlock(&vch->lock); if (done && next) { tw_write(dev, pb ? R32_DMA_B_ADDR(id) : R32_DMA_P_ADDR(id), next->dma); v4l2_get_timestamp(&done->vb.v4l2_buf.timestamp); done->vb.v4l2_buf.sequence = vch->sequence++; vb2_buffer_done(&done->vb, VB2_BUF_STATE_DONE); } else { dev_info(&dev->pdev->dev, "vch%u NOBUF seq=%u dcount=%u\n", ID2CH(id), vch->sequence, ++vch->dcount); } return 0; } static unsigned int tw6869_airq(struct tw6869_dev *dev, unsigned int id, unsigned int pb) { struct tw6869_ach *ach = &dev->ach[ID2CH(id)]; struct tw6869_buf *done = NULL; struct tw6869_buf *next = NULL; spin_lock(&ach->lock); if (!ach->ss || !ach->p_buf || !ach->b_buf) { spin_unlock(&ach->lock); return TW_DMA_OFF; } if (ach->pb != pb) { ach->pb = 0; spin_unlock(&ach->lock); return TW_DMA_RST; } if (!list_empty(&ach->buf_list)) { next = list_first_entry(&ach->buf_list, struct tw6869_buf, list); list_move_tail(&next->list, &ach->buf_list); if (pb) { done = ach->p_buf; ach->b_buf = next; } else { done = ach->b_buf; ach->p_buf = next; } } ach->pb = !pb; spin_unlock(&ach->lock); if (done && next) { tw_write(dev, pb ? R32_DMA_B_ADDR(id) : R32_DMA_P_ADDR(id), next->dma); ach->ptr = done->dma - ach->buf[0].dma; snd_pcm_period_elapsed(ach->ss); } else { return TW_DMA_OFF; } return 0; } static irqreturn_t tw6869_irq(int irq, void *dev_id) { struct tw6869_dev *dev = dev_id; unsigned int int_sts, fifo_sts, pb_sts, dma_en, id; int_sts = tw_read(dev, R32_INT_STATUS); fifo_sts = tw_read(dev, R32_FIFO_STATUS); pb_sts = tw_read(dev, R32_PB_STATUS); dma_en = tw_read(dev, R32_DMA_CHANNEL_ENABLE); dma_en &= tw_read(dev, R32_DMA_CMD); for (id = 0; id < (2 * TW_CH_MAX); id++) { unsigned int verr = fifo_sts & TW_VERR(id); if ((dma_en & BIT(id)) && ((int_sts & BIT(id)) || verr)) { unsigned int pb = !!(pb_sts & BIT(id)); unsigned int cmd = (BIT(id) & TW_VID) ? tw6869_virq(dev, id, pb, verr) : tw6869_airq(dev, id, pb); if (cmd) { spin_lock(&dev->rlock); tw6869_id_dma_cmd(dev, id, cmd); spin_unlock(&dev->rlock); } else { dev->id_err[id] = 0; } } } return IRQ_HANDLED; } /**********************************************************************/ static inline struct tw6869_buf *to_tw6869_buf(struct vb2_buffer *vb2) { return container_of(vb2, struct tw6869_buf, vb); } static int to_tw6869_pixformat(unsigned int pixelformat) { int ret; switch (pixelformat) { case V4L2_PIX_FMT_YUYV: ret = TW_FMT_YUYV; break; case V4L2_PIX_FMT_UYVY: ret = TW_FMT_UYVY; break; case V4L2_PIX_FMT_RGB565: ret = TW_FMT_RGB565; break; default: ret = -EINVAL; } return ret; } static unsigned int tw6869_fields_map(v4l2_std_id std, unsigned int rate) { unsigned int map[15] = { 0x00000000, 0x00000001, 0x00004001, 0x00104001, 0x00404041, 0x01041041, 0x01104411, 0x01111111, 0x04444445, 0x04511445, 0x05145145, 0x05151515, 0x05515455, 0x05551555, 0x05555555 }; unsigned int std_625_50[26] = { 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 7, 7, 8, 8, 9, 10, 10, 11, 11, 12, 13, 13, 14, 14, 0 }; unsigned int std_525_60[31] = { 0, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 0, 0 }; unsigned int i = (std & V4L2_STD_625_50) ? std_625_50[(rate > 25) ? 25 : rate] : std_525_60[(rate > 30) ? 30 : rate]; return map[i]; } static void tw6869_fill_pix_format(struct tw6869_vch *vch, struct v4l2_pix_format *pix) { pix->width = 720; pix->height = (vch->std & V4L2_STD_625_50) ? 576 : 480; pix->field = V4L2_FIELD_INTERLACED_BT; pix->colorspace = V4L2_COLORSPACE_SMPTE170M; pix->bytesperline = pix->width * 2; pix->sizeimage = pix->bytesperline * pix->height; pix->priv = 0; } static void tw6869_vch_hw_cfg(struct tw6869_vch *vch) { struct tw6869_dev *dev = vch->dev; struct v4l2_pix_format *pix = &vch->format; unsigned int cfg; if (vch->std & V4L2_STD_625_50) tw_set(dev, R32_VIDEO_CONTROL1, BIT_CH(vch->id) << 13); else tw_clear(dev, R32_VIDEO_CONTROL1, BIT_CH(vch->id) << 13); cfg = tw6869_fields_map(vch->std, vch->fps) << 1; cfg |= cfg << 1; if (cfg > 0) cfg |= BIT(31); tw_write(dev, R32_VIDEO_FIELD_CTRL(vch->id), cfg); /* Analog mux input0, no drop, enable master */ cfg = ((vch->input & 0x0) << 30) | BIT(27) | (ID2SC(vch->id) << 25) | ((to_tw6869_pixformat(pix->pixelformat) & 0x7) << 20); tw_write(dev, R32_DMA_CHANNEL_CONFIG(vch->id), cfg); cfg = (((pix->height >> 1) & 0x3FF) << 22) | ((pix->bytesperline & 0x7FF) << 11) | (pix->bytesperline & 0x7FF); tw_write(dev, R32_DMA_WHP(vch->id), cfg); tw_write(dev, R32_DMA_P_ADDR(vch->id), vch->p_buf->dma); tw_write(dev, R32_DMA_B_ADDR(vch->id), vch->b_buf->dma); } /* * Videobuf2 Operations */ static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, unsigned int *nbuffers, unsigned int *nplanes, unsigned int sizes[], void *alloc_ctxs[]) { struct tw6869_vch *vch = vb2_get_drv_priv(vq); struct tw6869_dev *dev = vch->dev; if (vq->num_buffers + *nbuffers < TW_FRAME_MAX) *nbuffers = TW_FRAME_MAX - vq->num_buffers; if (fmt && fmt->fmt.pix.sizeimage < vch->format.sizeimage) return -EINVAL; *nplanes = 1; sizes[0] = fmt ? fmt->fmt.pix.sizeimage : vch->format.sizeimage; alloc_ctxs[0] = dev->alloc_ctx; return 0; } static int buffer_init(struct vb2_buffer *vb) { struct tw6869_buf *buf = to_tw6869_buf(vb); buf->dma = vb2_dma_contig_plane_dma_addr(vb, 0); INIT_LIST_HEAD(&buf->list); { /* pass the buffer physical address */ unsigned int *cpu = vb2_plane_vaddr(vb, 0); *cpu = buf->dma; } return 0; } static int buffer_prepare(struct vb2_buffer *vb) { struct tw6869_vch *vch = vb2_get_drv_priv(vb->vb2_queue); unsigned long size = vch->format.sizeimage; if (vb2_plane_size(vb, 0) < size) { v4l2_err(&vch->dev->v4l2_dev, "buffer too small (%lu < %lu)\n", vb2_plane_size(vb, 0), size); return -EINVAL; } vb2_set_plane_payload(vb, 0, size); return 0; } static void buffer_queue(struct vb2_buffer *vb) { struct tw6869_vch *vch = vb2_get_drv_priv(vb->vb2_queue); struct tw6869_buf *buf = to_tw6869_buf(vb); unsigned long flags; spin_lock_irqsave(&vch->lock, flags); list_add_tail(&buf->list, &vch->buf_list); spin_unlock_irqrestore(&vch->lock, flags); } static int start_streaming(struct vb2_queue *vq, unsigned int count) { struct tw6869_vch *vch = vb2_get_drv_priv(vq); struct tw6869_dev *dev = vch->dev; unsigned long flags; if (count < 2) return -ENOBUFS; spin_lock_irqsave(&vch->lock, flags); vch->p_buf = list_first_entry(&vch->buf_list, struct tw6869_buf, list); list_del(&vch->p_buf->list); vch->b_buf = list_first_entry(&vch->buf_list, struct tw6869_buf, list); list_del(&vch->b_buf->list); vch->sequence = 0; vch->dcount = 0; vch->pb = 0; spin_unlock_irqrestore(&vch->lock, flags); spin_lock_irqsave(&dev->rlock, flags); tw6869_vch_hw_cfg(vch); tw6869_id_dma_cmd(dev, vch->id, TW_DMA_ON); spin_unlock_irqrestore(&dev->rlock, flags); return 0; } static int stop_streaming(struct vb2_queue *vq) { struct tw6869_vch *vch = vb2_get_drv_priv(vq); struct tw6869_dev *dev = vch->dev; struct tw6869_buf *buf, *node; unsigned long flags; spin_lock_irqsave(&dev->rlock, flags); tw6869_id_dma_cmd(dev, vch->id, TW_DMA_OFF); spin_unlock_irqrestore(&dev->rlock, flags); spin_lock_irqsave(&vch->lock, flags); if (vch->p_buf) { buf = vch->p_buf; vch->p_buf = NULL; vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); } if (vch->b_buf) { buf = vch->b_buf; vch->b_buf = NULL; vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); } list_for_each_entry_safe(buf, node, &vch->buf_list, list) { vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); list_del(&buf->list); } spin_unlock_irqrestore(&vch->lock, flags); return 0; } static struct vb2_ops tw6869_qops = { .queue_setup = queue_setup, .buf_init = buffer_init, .buf_prepare = buffer_prepare, .buf_queue = buffer_queue, .start_streaming = start_streaming, .stop_streaming = stop_streaming, .wait_prepare = vb2_ops_wait_prepare, .wait_finish = vb2_ops_wait_finish, }; static int tw6869_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { struct tw6869_vch *vch = video_drvdata(file); struct tw6869_dev *dev = vch->dev; strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); snprintf(cap->card, sizeof(cap->card), "tw6869 vch%u", ID2CH(vch->id)); snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s", pci_name(dev->pdev)); cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } static int tw6869_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct tw6869_vch *vch = video_drvdata(file); struct v4l2_pix_format *pix = &f->fmt.pix; int ret; ret = to_tw6869_pixformat(pix->pixelformat); if (ret < 0) return ret; tw6869_fill_pix_format(vch, pix); return 0; } static int tw6869_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct tw6869_vch *vch = video_drvdata(file); int ret; ret = tw6869_try_fmt_vid_cap(file, priv, f); if (ret) return ret; if (vb2_is_busy(&vch->queue)) return -EBUSY; vch->format = f->fmt.pix; return 0; } static int tw6869_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct tw6869_vch *vch = video_drvdata(file); f->fmt.pix = vch->format; return 0; } static int tw6869_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { if (f->index > 2) return -EINVAL; switch (f->index) { case 1: strcpy(f->description, "4:2:2, packed, YUYV"); f->pixelformat = V4L2_PIX_FMT_YUYV; break; case 2: strcpy(f->description, "16 bpp RGB, le"); f->pixelformat = V4L2_PIX_FMT_RGB565; break; default: strcpy(f->description, "4:2:2, packed, UYVY"); f->pixelformat = V4L2_PIX_FMT_UYVY; } f->flags = 0; return 0; } static int tw6869_enum_framesizes(struct file *file, void *priv, struct v4l2_frmsizeenum *fsize) { struct tw6869_vch *vch = video_drvdata(file); if (fsize->index != 0) return -EINVAL; fsize->discrete.width = vch->format.width; fsize->discrete.height = vch->format.height; return 0; } static int tw6869_querystd(struct file *file, void *priv, v4l2_std_id *std) { struct tw6869_vch *vch = video_drvdata(file); struct tw6869_dev *dev = vch->dev; unsigned int std_now; char *std_str; std_now = (tw_read(dev, R8_STANDARD_SEL(vch->id)) & 0x70) >> 4; switch (std_now) { case TW_STD_PAL_M: std_str = "PAL (M)"; *std = V4L2_STD_525_60; break; case TW_STD_PAL_60: std_str = "PAL 60"; *std = V4L2_STD_525_60; break; case TW_STD_NTSC_M: std_str = "NTSC (M)"; *std = V4L2_STD_525_60; break; case TW_STD_NTSC_443: std_str = "NTSC 4.43"; *std = V4L2_STD_525_60; break; case TW_STD_PAL: std_str = "PAL (B,D,G,H,I)"; *std = V4L2_STD_625_50; break; case TW_STD_PAL_CN: std_str = "PAL (CN)"; *std = V4L2_STD_625_50; break; case TW_STD_SECAM: std_str = "SECAM"; *std = V4L2_STD_625_50; break; default: std_str = "Not valid"; *std = 0; } v4l2_info(&dev->v4l2_dev, "vch%u std %s\n", ID2CH(vch->id), std_str); return 0; } static int tw6869_s_std(struct file *file, void *priv, v4l2_std_id std) { struct tw6869_vch *vch = video_drvdata(file); v4l2_std_id new_std = (std & V4L2_STD_625_50) ? V4L2_STD_625_50 : V4L2_STD_525_60; if (new_std == vch->std) return 0; if (vb2_is_busy(&vch->queue)) return -EBUSY; vch->std = new_std; vch->fps = (new_std & V4L2_STD_625_50) ? 25 : 30; tw6869_fill_pix_format(vch, &vch->format); return 0; } static int tw6869_g_std(struct file *file, void *priv, v4l2_std_id *std) { struct tw6869_vch *vch = video_drvdata(file); v4l2_std_id new_std = 0; tw6869_querystd(file, priv, &new_std); if (new_std) tw6869_s_std(file, priv, new_std); vch->fps = (vch->std & V4L2_STD_625_50) ? 25 : 30; *std = vch->std; return 0; } /* SK-TW6869: only input0 is available */ static int tw6869_enum_input(struct file *file, void *priv, struct v4l2_input *i) { if (i->index >= TW_VIN_MAX) return -EINVAL; i->type = V4L2_INPUT_TYPE_CAMERA; i->std = V4L2_STD_ALL; sprintf(i->name, "Camera %d", i->index); return 0; } static int tw6869_s_input(struct file *file, void *priv, unsigned int i) { struct tw6869_vch *vch = video_drvdata(file); vch->input = i; return 0; } static int tw6869_g_input(struct file *file, void *priv, unsigned int *i) { struct tw6869_vch *vch = video_drvdata(file); *i = vch->input; return 0; } static int tw6869_g_parm(struct file *file, void *priv, struct v4l2_streamparm *sp) { struct tw6869_vch *vch = video_drvdata(file); struct v4l2_captureparm *cp = &sp->parm.capture; cp->capability = V4L2_CAP_TIMEPERFRAME; cp->timeperframe.numerator = 1; cp->timeperframe.denominator = vch->fps; return 0; } static int tw6869_s_parm(struct file *file, void *priv, struct v4l2_streamparm *sp) { struct tw6869_vch *vch = video_drvdata(file); unsigned int denominator = sp->parm.capture.timeperframe.denominator; unsigned int numerator = sp->parm.capture.timeperframe.numerator; unsigned int fps; fps = (!numerator || !denominator) ? 0 : denominator / numerator; if (vch->std & V4L2_STD_625_50) fps = (!fps || fps > 25) ? 25 : fps; else fps = (!fps || fps > 30) ? 30 : fps; vch->fps = fps; v4l2_info(&vch->dev->v4l2_dev, "vch%u fps %u\n", ID2CH(vch->id), vch->fps); return tw6869_g_parm(file, priv, sp); } /* The control handler */ static int tw6869_s_ctrl(struct v4l2_ctrl *ctrl) { struct tw6869_vch *vch = container_of(ctrl->handler, struct tw6869_vch, hdl); struct tw6869_dev *dev = vch->dev; unsigned int id = vch->id; int ret = 0; switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: tw_write(dev, R8_BRIGHT_CTRL(id), ctrl->val); break; case V4L2_CID_CONTRAST: tw_write(dev, R8_CONTRAST_CTRL(id), ctrl->val); break; case V4L2_CID_SATURATION: tw_write(dev, R8_SAT_U_CTRL(id), ctrl->val); tw_write(dev, R8_SAT_V_CTRL(id), ctrl->val); break; case V4L2_CID_HUE: tw_write(dev, R8_HUE_CTRL(id), ctrl->val); break; default: ret = -EINVAL; } return ret; } /* * File operations for the device */ static const struct v4l2_ctrl_ops tw6869_ctrl_ops = { .s_ctrl = tw6869_s_ctrl, }; static const struct v4l2_ioctl_ops tw6869_ioctl_ops = { .vidioc_querycap = tw6869_querycap, .vidioc_try_fmt_vid_cap = tw6869_try_fmt_vid_cap, .vidioc_s_fmt_vid_cap = tw6869_s_fmt_vid_cap, .vidioc_g_fmt_vid_cap = tw6869_g_fmt_vid_cap, .vidioc_enum_fmt_vid_cap = tw6869_enum_fmt_vid_cap, .vidioc_enum_framesizes = tw6869_enum_framesizes, .vidioc_querystd = tw6869_querystd, .vidioc_s_std = tw6869_s_std, .vidioc_g_std = tw6869_g_std, .vidioc_enum_input = tw6869_enum_input, .vidioc_s_input = tw6869_s_input, .vidioc_g_input = tw6869_g_input, .vidioc_g_parm = tw6869_g_parm, .vidioc_s_parm = tw6869_s_parm, .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_create_bufs = vb2_ioctl_create_bufs, .vidioc_querybuf = vb2_ioctl_querybuf, .vidioc_qbuf = vb2_ioctl_qbuf, .vidioc_dqbuf = vb2_ioctl_dqbuf, .vidioc_expbuf = vb2_ioctl_expbuf, .vidioc_streamon = vb2_ioctl_streamon, .vidioc_streamoff = vb2_ioctl_streamoff, .vidioc_log_status = v4l2_ctrl_log_status, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; static const struct v4l2_file_operations tw6869_fops = { .owner = THIS_MODULE, .open = v4l2_fh_open, .release = vb2_fop_release, .unlocked_ioctl = video_ioctl2, .read = vb2_fop_read, .mmap = vb2_fop_mmap, .poll = vb2_fop_poll, }; static int tw6869_vch_register(struct tw6869_vch *vch) { struct tw6869_dev *dev = vch->dev; struct v4l2_ctrl_handler *hdl = &vch->hdl; struct vb2_queue *q = &vch->queue; struct video_device *vdev = &vch->vdev; int ret = 0; /* Add the controls */ v4l2_ctrl_handler_init(hdl, 4); v4l2_ctrl_new_std(hdl, &tw6869_ctrl_ops, V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); v4l2_ctrl_new_std(hdl, &tw6869_ctrl_ops, V4L2_CID_CONTRAST, 0, 255, 1, 100); v4l2_ctrl_new_std(hdl, &tw6869_ctrl_ops, V4L2_CID_SATURATION, 0, 255, 1, 128); v4l2_ctrl_new_std(hdl, &tw6869_ctrl_ops, V4L2_CID_HUE, -128, 127, 1, 0); if (hdl->error) { ret = hdl->error; return ret; } /* Fill in the initial format-related settings */ vch->std = V4L2_STD_525_60; vch->fps = 30; vch->format.pixelformat = V4L2_PIX_FMT_UYVY; tw6869_fill_pix_format(vch, &vch->format); mutex_init(&vch->mlock); /* Initialize the vb2 queue */ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; q->drv_priv = vch; q->buf_struct_size = sizeof(struct tw6869_buf); q->ops = &tw6869_qops; q->mem_ops = &vb2_dma_contig_memops; q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q->lock = &vch->mlock; q->gfp_flags = __GFP_DMA32; ret = vb2_queue_init(q); if (ret) goto free_hdl; spin_lock_init(&vch->lock); INIT_LIST_HEAD(&vch->buf_list); /* Initialize the video_device structure */ strlcpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name)); vdev->release = video_device_release_empty; vdev->fops = &tw6869_fops, vdev->ioctl_ops = &tw6869_ioctl_ops, vdev->lock = &vch->mlock; vdev->queue = q; vdev->v4l2_dev = &dev->v4l2_dev; vdev->ctrl_handler = hdl; vdev->tvnorms = V4L2_STD_ALL; video_set_drvdata(vdev, vch); ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); if (!ret) return 0; free_hdl: v4l2_ctrl_handler_free(hdl); return ret; } static void tw6869_video_unregister(struct tw6869_dev *dev) { unsigned int i; /* Reset and disable all DMA channels */ tw_write(dev, R32_DMA_CMD, 0); tw_write(dev, R32_DMA_CHANNEL_ENABLE, 0); if (!dev->alloc_ctx) return; if (dev->ch_max > TW_CH_MAX) dev->ch_max = TW_CH_MAX; for (i = 0; i < dev->ch_max; i++) { struct tw6869_vch *vch = &dev->vch[ID2CH(i)]; video_unregister_device(&vch->vdev); v4l2_ctrl_handler_free(&vch->hdl); } v4l2_device_unregister(&dev->v4l2_dev); vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); dev->alloc_ctx = NULL; } static int tw6869_video_register(struct tw6869_dev *dev) { struct pci_dev *pdev = dev->pdev; unsigned int i; int ret; /* Initialize the top-level structure */ ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); if (ret) return ret; dev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); if (IS_ERR(dev->alloc_ctx)) { ret = PTR_ERR(dev->alloc_ctx); v4l2_err(&dev->v4l2_dev, "can't allocate buffer context\n"); v4l2_device_unregister(&dev->v4l2_dev); dev->alloc_ctx = NULL; return ret; } for (i = 0; i < TW_CH_MAX; i++) { struct tw6869_vch *vch = &dev->vch[ID2CH(i)]; vch->dev = dev; vch->id = i; ret = tw6869_vch_register(vch); if (ret) { dev->ch_max = i; tw6869_video_unregister(dev); return ret; } dev_info(&pdev->dev, "vch%i registered as %s\n", vch->id, video_device_node_name(&vch->vdev)); } return 0; } /**********************************************************************/ static int tw6869_pcm_hw_params(struct snd_pcm_substream *ss, struct snd_pcm_hw_params *hw_params) { return snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params)); } static int tw6869_pcm_hw_free(struct snd_pcm_substream *ss) { return snd_pcm_lib_free_pages(ss); } static const struct snd_pcm_hardware tw6869_capture_hw = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = SNDRV_PCM_RATE_48000, .rate_min = 48000, .rate_max = 48000, .channels_min = 1, .channels_max = 1, .buffer_bytes_max = TW_PAGE_SIZE * TW_APAGE_MAX, .period_bytes_min = TW_PAGE_SIZE, .period_bytes_max = TW_PAGE_SIZE, .periods_min = 2, .periods_max = TW_APAGE_MAX, }; static int tw6869_pcm_open(struct snd_pcm_substream *ss) { struct tw6869_dev *dev = snd_pcm_substream_chip(ss); struct tw6869_ach *ach = &dev->ach[ID2CH(ss->number)]; struct snd_pcm_runtime *rt = ss->runtime; int ret; rt->hw = tw6869_capture_hw; ret = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS); if (ret < 0) return ret; ach->ss = ss; return 0; } static int tw6869_pcm_close(struct snd_pcm_substream *ss) { struct tw6869_dev *dev = snd_pcm_substream_chip(ss); struct tw6869_ach *ach = &dev->ach[ID2CH(ss->number)]; ach->ss = NULL; return 0; } static int tw6869_pcm_prepare(struct snd_pcm_substream *ss) { struct tw6869_dev *dev = snd_pcm_substream_chip(ss); struct tw6869_ach *ach = &dev->ach[ID2CH(ss->number)]; struct snd_pcm_runtime *rt = ss->runtime; unsigned int period = snd_pcm_lib_period_bytes(ss); unsigned long flags; unsigned int i; spin_lock_irqsave(&ach->lock, flags); ach->p_buf = NULL; ach->b_buf = NULL; if ((period != TW_PAGE_SIZE) || (rt->periods < 2) || (rt->periods > TW_APAGE_MAX)) { spin_unlock_irqrestore(&ach->lock, flags); return -EINVAL; } INIT_LIST_HEAD(&ach->buf_list); for (i = 0; i < rt->periods; i++) { ach->buf[i].dma = rt->dma_addr + period * i; INIT_LIST_HEAD(&ach->buf[i].list); list_add_tail(&ach->buf[i].list, &ach->buf_list); } ach->p_buf = list_first_entry(&ach->buf_list, struct tw6869_buf, list); list_move_tail(&ach->p_buf->list, &ach->buf_list); ach->b_buf = list_first_entry(&ach->buf_list, struct tw6869_buf, list); list_move_tail(&ach->b_buf->list, &ach->buf_list); ach->ptr = 0; ach->pb = 0; spin_unlock_irqrestore(&ach->lock, flags); return 0; } static void tw6869_ach_hw_cfg(struct tw6869_ach *ach) { struct tw6869_dev *dev = ach->dev; tw_write(dev, R32_DMA_P_ADDR(ach->id), ach->p_buf->dma); tw_write(dev, R32_DMA_B_ADDR(ach->id), ach->b_buf->dma); } static int tw6869_pcm_trigger(struct snd_pcm_substream *ss, int cmd) { struct tw6869_dev *dev = snd_pcm_substream_chip(ss); struct tw6869_ach *ach = &dev->ach[ID2CH(ss->number)]; unsigned long flags; int ret = 0; switch (cmd) { case SNDRV_PCM_TRIGGER_START: if (ach->p_buf && ach->b_buf) { spin_lock_irqsave(&dev->rlock, flags); tw6869_ach_hw_cfg(ach); tw6869_id_dma_cmd(dev, ach->id, TW_DMA_ON); spin_unlock_irqrestore(&dev->rlock, flags); } else { ret = -EIO; } break; case SNDRV_PCM_TRIGGER_STOP: spin_lock_irqsave(&dev->rlock, flags); tw6869_id_dma_cmd(dev, ach->id, TW_DMA_OFF); spin_unlock_irqrestore(&dev->rlock, flags); spin_lock_irqsave(&ach->lock, flags); ach->p_buf = NULL; ach->b_buf = NULL; spin_unlock_irqrestore(&ach->lock, flags); break; default: ret = -EINVAL; } return ret; } static snd_pcm_uframes_t tw6869_pcm_pointer(struct snd_pcm_substream *ss) { struct tw6869_dev *dev = snd_pcm_substream_chip(ss); struct tw6869_ach *ach = &dev->ach[ID2CH(ss->number)]; return bytes_to_frames(ss->runtime, ach->ptr); } static struct snd_pcm_ops tw6869_pcm_ops = { .open = tw6869_pcm_open, .close = tw6869_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = tw6869_pcm_hw_params, .hw_free = tw6869_pcm_hw_free, .prepare = tw6869_pcm_prepare, .trigger = tw6869_pcm_trigger, .pointer = tw6869_pcm_pointer, }; static int tw6869_snd_pcm_init(struct tw6869_dev *dev) { struct snd_card *card = dev->snd_card; struct snd_pcm *pcm; struct snd_pcm_substream *ss; int ret; ret = snd_pcm_new(card, card->driver, 0, 0, TW_CH_MAX, &pcm); if (ret < 0) return ret; snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &tw6869_pcm_ops); snd_pcm_chip(pcm) = dev; pcm->info_flags = 0; strcpy(pcm->name, card->shortname); ss = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; while (ss && (ss->next != ss)) { struct tw6869_ach *ach = &dev->ach[ID2CH(ss->number)]; ach->dev = dev; ach->id = ID2CH(ss->number) + TW_CH_MAX; spin_lock_init(&ach->lock); sprintf(ss->name, "vch%u audio", ID2CH(ss->number)); ss = ss->next; } return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(dev->pdev), TW_APAGE_MAX * TW_PAGE_SIZE, TW_APAGE_MAX * TW_PAGE_SIZE); } static void tw6869_audio_unregister(struct tw6869_dev *dev) { /* Reset and disable audio DMA */ tw_clear(dev, R32_DMA_CMD, TW_AID); tw_clear(dev, R32_DMA_CHANNEL_ENABLE, TW_AID); if (!dev->snd_card) return; snd_card_free(dev->snd_card); dev->snd_card = NULL; } /* TODO: mixer controls */ static int tw6869_audio_register(struct tw6869_dev *dev) { struct pci_dev *pdev = dev->pdev; static struct snd_device_ops ops = { NULL }; struct snd_card *card; int ret; ret = snd_card_create(SNDRV_DEFAULT_IDX1, KBUILD_MODNAME, THIS_MODULE, 0, &card); if (ret < 0) return ret; dev->snd_card = card; strcpy(card->driver, KBUILD_MODNAME); strcpy(card->shortname, KBUILD_MODNAME); sprintf(card->longname, "%s on %s IRQ %d", card->shortname, pci_name(pdev), pdev->irq); ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, dev, &ops); if (ret < 0) goto snd_error; snd_card_set_dev(card, &pdev->dev); ret = tw6869_snd_pcm_init(dev); if (ret < 0) goto snd_error; ret = snd_card_register(card); if (!ret) return 0; snd_error: snd_card_free(card); dev->snd_card = NULL; return ret; } /**********************************************************************/ static void tw6869_reset(struct tw6869_dev *dev) { /* Software Reset */ tw_write(dev, R32_SYS_SOFT_RST, 0x01); tw_write(dev, R32_SYS_SOFT_RST, 0x0F); /* Reset Internal audio and video decoders */ tw_write(dev, R8_AVSRST(0), 0x1F); tw_write(dev, R8_AVSRST(4), 0x1F); /* Reset all DMA channels */ tw_write(dev, R32_DMA_CMD, 0); /* Disable all DMA channels */ tw_write(dev, R32_DMA_CHANNEL_ENABLE, 0); /* Enable DMA FIFO overflow and pointer check */ tw_write(dev, R32_DMA_CONFIG, 0x00FFFF04); /* Minimum time span for DMA interrupting host (default: 0x00098968) */ tw_write(dev, R32_DMA_TIMER_INTERVAL, 0x00098968); /* DMA timeout (default: 0x140C8584) */ tw_write(dev, R32_DMA_CHANNEL_TIMEOUT, 0x140C8584); /* Frame mode DMA */ tw_write(dev, R32_PHASE_REF, 0xAAAA144D); /* Show blue background if no signal */ tw_write(dev, R8_MISC_CONTROL1(0), 0xE7); tw_write(dev, R8_MISC_CONTROL1(4), 0xE7); /* Audio DMA 4096 bytes, sampling frequency reference 48 kHz */ tw_write(dev, R32_AUDIO_CONTROL1, 0x80000001 | (0x0A2C << 5)); tw_write(dev, R32_AUDIO_CONTROL2, 0x0A2C2AAA); } static int tw6869_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct tw6869_dev *dev; int ret; /* Allocate a new instance */ dev = devm_kzalloc(&pdev->dev, sizeof(struct tw6869_dev), GFP_KERNEL); if (!dev) return -ENOMEM; ret = pci_enable_device(pdev); if (ret) return ret; pci_set_master(pdev); ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); if (ret) { dev_err(&pdev->dev, "no suitable DMA available\n"); goto disable_pci; } ret = pci_request_regions(pdev, KBUILD_MODNAME); if (ret) goto disable_pci; dev->mmio = pci_iomap(pdev, 0, 0); if (!dev->mmio) { ret = -EIO; goto release_regs; } tw6869_reset(dev); ret = devm_request_irq(&pdev->dev, pdev->irq, tw6869_irq, IRQF_SHARED, KBUILD_MODNAME, dev); if (ret) { dev_err(&pdev->dev, "request_irq failed\n"); goto unmap_regs; } dev->pdev = pdev; dev->ch_max = TW_CH_MAX; spin_lock_init(&dev->rlock); ret = tw6869_video_register(dev); if (ret) goto unmap_regs; ret = tw6869_audio_register(dev); if (ret) goto video_unreg; dev_info(&pdev->dev, "driver loaded\n"); return 0; video_unreg: tw6869_video_unregister(dev); unmap_regs: pci_iounmap(pdev, dev->mmio); release_regs: pci_release_regions(pdev); disable_pci: pci_disable_device(pdev); return ret; } static void tw6869_remove(struct pci_dev *pdev) { struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev); struct tw6869_dev *dev = container_of(v4l2_dev, struct tw6869_dev, v4l2_dev); tw6869_audio_unregister(dev); tw6869_video_unregister(dev); pci_iounmap(pdev, dev->mmio); pci_release_regions(pdev); pci_disable_device(pdev); } /* TODO: PM */ static struct pci_driver tw6869_driver = { .name = KBUILD_MODNAME, .probe = tw6869_probe, .remove = tw6869_remove, .id_table = tw6869_pci_tbl, }; module_pci_driver(tw6869_driver);