1440 lines
35 KiB
C
1440 lines
35 KiB
C
/*
|
|
* Copyright 2015 www.starterkit.ru <info@starterkit.ru>
|
|
*
|
|
* Based on:
|
|
* Driver for Intersil|Techwell TW6869 based DVR cards
|
|
* (c) 2011-12 liran <jli11@intersil.com> [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 <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kmod.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/videodev2.h>
|
|
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-dev.h>
|
|
#include <media/v4l2-ioctl.h>
|
|
#include <media/v4l2-ctrls.h>
|
|
#include <media/v4l2-event.h>
|
|
#include <media/videobuf2-dma-contig.h>
|
|
|
|
#include <sound/core.h>
|
|
#include <sound/initval.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/control.h>
|
|
|
|
#include "tw6869.h"
|
|
|
|
MODULE_DESCRIPTION("tw6869/65 media bridge driver");
|
|
MODULE_AUTHOR("starterkit <info@starterkit.ru>");
|
|
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);
|