提交 704a84cc 编写于 作者: E Ezequiel Garcia 提交者: Mauro Carvalho Chehab

[media] media: Support Intersil/Techwell TW686x-based video capture cards

This commit introduces the support for the Techwell TW686x video
capture IC. This hardware supports a few DMA modes, including
scatter-gather and frame (contiguous).

This commit makes little use of the DMA engine and instead has
a memcpy based implementation. DMA frame and scatter-gather modes
support may be added in the future.

Currently supported chips:
- TW6864 (4 video channels),
- TW6865 (4 video channels, not tested, second generation chip),
- TW6868 (8 video channels but only 4 first channels using
           built-in video decoder are supported, not tested),
- TW6869 (8 video channels, second generation chip).

[mchehab@osg.samsung.com: make checkpatch happy by using "unsigned int"
  instead  of just "unsigned"]
Cc: Krzysztof Halasa <khalasa@piap.pl>
Signed-off-by: NEzequiel Garcia <ezequiel@vanguardiasur.com.ar>
Signed-off-by: NHans Verkuil <hans.verkuil@cisco.com>
Tested-by: NHans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: NMauro Carvalho Chehab <mchehab@osg.samsung.com>
上级 0ff59f31
......@@ -11258,6 +11258,14 @@ W: https://linuxtv.org
S: Odd Fixes
F: drivers/media/pci/tw68/
TW686X VIDEO4LINUX DRIVER
M: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
L: linux-media@vger.kernel.org
T: git git://linuxtv.org/media_tree.git
W: http://linuxtv.org
S: Maintained
F: drivers/media/pci/tw686x/
TPM DEVICE DRIVER
M: Peter Huewe <peterhuewe@gmx.de>
M: Marcel Selhorst <tpmdd@selhorst.net>
......
......@@ -14,6 +14,7 @@ source "drivers/media/pci/meye/Kconfig"
source "drivers/media/pci/solo6x10/Kconfig"
source "drivers/media/pci/sta2x11/Kconfig"
source "drivers/media/pci/tw68/Kconfig"
source "drivers/media/pci/tw686x/Kconfig"
source "drivers/media/pci/zoran/Kconfig"
endif
......
......@@ -25,6 +25,7 @@ obj-$(CONFIG_VIDEO_BT848) += bt8xx/
obj-$(CONFIG_VIDEO_SAA7134) += saa7134/
obj-$(CONFIG_VIDEO_SAA7164) += saa7164/
obj-$(CONFIG_VIDEO_TW68) += tw68/
obj-$(CONFIG_VIDEO_TW686X) += tw686x/
obj-$(CONFIG_VIDEO_DT3155) += dt3155/
obj-$(CONFIG_VIDEO_MEYE) += meye/
obj-$(CONFIG_STA2X11_VIP) += sta2x11/
......
config VIDEO_TW686X
tristate "Intersil/Techwell TW686x video capture cards"
depends on PCI && VIDEO_DEV && VIDEO_V4L2 && SND
depends on HAS_DMA
select VIDEOBUF2_VMALLOC
select SND_PCM
help
Support for Intersil/Techwell TW686x-based frame grabber cards.
Currently supported chips:
- TW6864 (4 video channels),
- TW6865 (4 video channels, not tested, second generation chip),
- TW6868 (8 video channels but only 4 first channels using
built-in video decoder are supported, not tested),
- TW6869 (8 video channels, second generation chip).
To compile this driver as a module, choose M here: the module
will be named tw686x.
tw686x-objs := tw686x-core.o tw686x-video.o tw686x-audio.o
obj-$(CONFIG_VIDEO_TW686X) += tw686x.o
/*
* Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar
*
* Based on the audio support from the tw6869 driver:
* 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]
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*/
#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/delay.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/control.h>
#include "tw686x.h"
#include "tw686x-regs.h"
#define AUDIO_CHANNEL_OFFSET 8
void tw686x_audio_irq(struct tw686x_dev *dev, unsigned long requests,
unsigned int pb_status)
{
unsigned long flags;
unsigned int ch, pb;
for_each_set_bit(ch, &requests, max_channels(dev)) {
struct tw686x_audio_channel *ac = &dev->audio_channels[ch];
struct tw686x_audio_buf *done = NULL;
struct tw686x_audio_buf *next = NULL;
struct tw686x_dma_desc *desc;
pb = !!(pb_status & BIT(AUDIO_CHANNEL_OFFSET + ch));
spin_lock_irqsave(&ac->lock, flags);
/* Sanity check */
if (!ac->ss || !ac->curr_bufs[0] || !ac->curr_bufs[1]) {
spin_unlock_irqrestore(&ac->lock, flags);
continue;
}
if (!list_empty(&ac->buf_list)) {
next = list_first_entry(&ac->buf_list,
struct tw686x_audio_buf, list);
list_move_tail(&next->list, &ac->buf_list);
done = ac->curr_bufs[!pb];
ac->curr_bufs[pb] = next;
}
spin_unlock_irqrestore(&ac->lock, flags);
desc = &ac->dma_descs[pb];
if (done && next && desc->virt) {
memcpy(done->virt, desc->virt, desc->size);
ac->ptr = done->dma - ac->buf[0].dma;
snd_pcm_period_elapsed(ac->ss);
}
}
}
static int tw686x_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 tw686x_pcm_hw_free(struct snd_pcm_substream *ss)
{
return snd_pcm_lib_free_pages(ss);
}
/*
* The audio device rate is global and shared among all
* capture channels. The driver makes no effort to prevent
* rate modifications. User is free change the rate, but it
* means changing the rate for all capture sub-devices.
*/
static const struct snd_pcm_hardware tw686x_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_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 1,
.buffer_bytes_max = TW686X_AUDIO_PAGE_MAX * TW686X_AUDIO_PAGE_SZ,
.period_bytes_min = TW686X_AUDIO_PAGE_SZ,
.period_bytes_max = TW686X_AUDIO_PAGE_SZ,
.periods_min = TW686X_AUDIO_PERIODS_MIN,
.periods_max = TW686X_AUDIO_PERIODS_MAX,
};
static int tw686x_pcm_open(struct snd_pcm_substream *ss)
{
struct tw686x_dev *dev = snd_pcm_substream_chip(ss);
struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number];
struct snd_pcm_runtime *rt = ss->runtime;
int err;
ac->ss = ss;
rt->hw = tw686x_capture_hw;
err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
if (err < 0)
return err;
return 0;
}
static int tw686x_pcm_close(struct snd_pcm_substream *ss)
{
struct tw686x_dev *dev = snd_pcm_substream_chip(ss);
struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number];
ac->ss = NULL;
return 0;
}
static int tw686x_pcm_prepare(struct snd_pcm_substream *ss)
{
struct tw686x_dev *dev = snd_pcm_substream_chip(ss);
struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number];
struct snd_pcm_runtime *rt = ss->runtime;
unsigned int period_size = snd_pcm_lib_period_bytes(ss);
struct tw686x_audio_buf *p_buf, *b_buf;
unsigned long flags;
int i;
spin_lock_irqsave(&dev->lock, flags);
tw686x_disable_channel(dev, AUDIO_CHANNEL_OFFSET + ac->ch);
spin_unlock_irqrestore(&dev->lock, flags);
if (dev->audio_rate != rt->rate) {
u32 reg;
dev->audio_rate = rt->rate;
reg = ((125000000 / rt->rate) << 16) +
((125000000 % rt->rate) << 16) / rt->rate;
reg_write(dev, AUDIO_CONTROL2, reg);
}
if (period_size != TW686X_AUDIO_PAGE_SZ ||
rt->periods < TW686X_AUDIO_PERIODS_MIN ||
rt->periods > TW686X_AUDIO_PERIODS_MAX) {
return -EINVAL;
}
spin_lock_irqsave(&ac->lock, flags);
INIT_LIST_HEAD(&ac->buf_list);
for (i = 0; i < rt->periods; i++) {
ac->buf[i].dma = rt->dma_addr + period_size * i;
ac->buf[i].virt = rt->dma_area + period_size * i;
INIT_LIST_HEAD(&ac->buf[i].list);
list_add_tail(&ac->buf[i].list, &ac->buf_list);
}
p_buf = list_first_entry(&ac->buf_list, struct tw686x_audio_buf, list);
list_move_tail(&p_buf->list, &ac->buf_list);
b_buf = list_first_entry(&ac->buf_list, struct tw686x_audio_buf, list);
list_move_tail(&b_buf->list, &ac->buf_list);
ac->curr_bufs[0] = p_buf;
ac->curr_bufs[1] = b_buf;
ac->ptr = 0;
spin_unlock_irqrestore(&ac->lock, flags);
return 0;
}
static int tw686x_pcm_trigger(struct snd_pcm_substream *ss, int cmd)
{
struct tw686x_dev *dev = snd_pcm_substream_chip(ss);
struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number];
unsigned long flags;
int err = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
if (ac->curr_bufs[0] && ac->curr_bufs[1]) {
spin_lock_irqsave(&dev->lock, flags);
tw686x_enable_channel(dev,
AUDIO_CHANNEL_OFFSET + ac->ch);
spin_unlock_irqrestore(&dev->lock, flags);
mod_timer(&dev->dma_delay_timer,
jiffies + msecs_to_jiffies(100));
} else {
err = -EIO;
}
break;
case SNDRV_PCM_TRIGGER_STOP:
spin_lock_irqsave(&dev->lock, flags);
tw686x_disable_channel(dev, AUDIO_CHANNEL_OFFSET + ac->ch);
spin_unlock_irqrestore(&dev->lock, flags);
spin_lock_irqsave(&ac->lock, flags);
ac->curr_bufs[0] = NULL;
ac->curr_bufs[1] = NULL;
spin_unlock_irqrestore(&ac->lock, flags);
break;
default:
err = -EINVAL;
}
return err;
}
static snd_pcm_uframes_t tw686x_pcm_pointer(struct snd_pcm_substream *ss)
{
struct tw686x_dev *dev = snd_pcm_substream_chip(ss);
struct tw686x_audio_channel *ac = &dev->audio_channels[ss->number];
return bytes_to_frames(ss->runtime, ac->ptr);
}
static struct snd_pcm_ops tw686x_pcm_ops = {
.open = tw686x_pcm_open,
.close = tw686x_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = tw686x_pcm_hw_params,
.hw_free = tw686x_pcm_hw_free,
.prepare = tw686x_pcm_prepare,
.trigger = tw686x_pcm_trigger,
.pointer = tw686x_pcm_pointer,
};
static int tw686x_snd_pcm_init(struct tw686x_dev *dev)
{
struct snd_card *card = dev->snd_card;
struct snd_pcm *pcm;
struct snd_pcm_substream *ss;
unsigned int i;
int err;
err = snd_pcm_new(card, card->driver, 0, 0, max_channels(dev), &pcm);
if (err < 0)
return err;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &tw686x_pcm_ops);
snd_pcm_chip(pcm) = dev;
pcm->info_flags = 0;
strlcpy(pcm->name, "tw686x PCM", sizeof(pcm->name));
for (i = 0, ss = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
ss; ss = ss->next, i++)
snprintf(ss->name, sizeof(ss->name), "vch%u audio", i);
return snd_pcm_lib_preallocate_pages_for_all(pcm,
SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data(dev->pci_dev),
TW686X_AUDIO_PAGE_MAX * TW686X_AUDIO_PAGE_SZ,
TW686X_AUDIO_PAGE_MAX * TW686X_AUDIO_PAGE_SZ);
}
static void tw686x_audio_dma_free(struct tw686x_dev *dev,
struct tw686x_audio_channel *ac)
{
int pb;
for (pb = 0; pb < 2; pb++) {
if (!ac->dma_descs[pb].virt)
continue;
pci_free_consistent(dev->pci_dev, ac->dma_descs[pb].size,
ac->dma_descs[pb].virt,
ac->dma_descs[pb].phys);
ac->dma_descs[pb].virt = NULL;
}
}
static int tw686x_audio_dma_alloc(struct tw686x_dev *dev,
struct tw686x_audio_channel *ac)
{
int pb;
for (pb = 0; pb < 2; pb++) {
u32 reg = pb ? ADMA_B_ADDR[ac->ch] : ADMA_P_ADDR[ac->ch];
void *virt;
virt = pci_alloc_consistent(dev->pci_dev, TW686X_AUDIO_PAGE_SZ,
&ac->dma_descs[pb].phys);
if (!virt) {
dev_err(&dev->pci_dev->dev,
"dma%d: unable to allocate audio DMA %s-buffer\n",
ac->ch, pb ? "B" : "P");
return -ENOMEM;
}
ac->dma_descs[pb].virt = virt;
ac->dma_descs[pb].size = TW686X_AUDIO_PAGE_SZ;
reg_write(dev, reg, ac->dma_descs[pb].phys);
}
return 0;
}
void tw686x_audio_free(struct tw686x_dev *dev)
{
unsigned long flags;
u32 dma_ch_mask;
u32 dma_cmd;
spin_lock_irqsave(&dev->lock, flags);
dma_cmd = reg_read(dev, DMA_CMD);
dma_ch_mask = reg_read(dev, DMA_CHANNEL_ENABLE);
reg_write(dev, DMA_CMD, dma_cmd & ~0xff00);
reg_write(dev, DMA_CHANNEL_ENABLE, dma_ch_mask & ~0xff00);
spin_unlock_irqrestore(&dev->lock, flags);
if (!dev->snd_card)
return;
snd_card_free(dev->snd_card);
dev->snd_card = NULL;
}
int tw686x_audio_init(struct tw686x_dev *dev)
{
struct pci_dev *pci_dev = dev->pci_dev;
struct snd_card *card;
int err, ch;
/*
* AUDIO_CONTROL1
* DMA byte length [31:19] = 4096 (i.e. ALSA period)
* External audio enable [0] = enabled
*/
reg_write(dev, AUDIO_CONTROL1, 0x80000001);
err = snd_card_new(&pci_dev->dev, SNDRV_DEFAULT_IDX1,
SNDRV_DEFAULT_STR1,
THIS_MODULE, 0, &card);
if (err < 0)
return err;
dev->snd_card = card;
strlcpy(card->driver, "tw686x", sizeof(card->driver));
strlcpy(card->shortname, "tw686x", sizeof(card->shortname));
strlcpy(card->longname, pci_name(pci_dev), sizeof(card->longname));
snd_card_set_dev(card, &pci_dev->dev);
for (ch = 0; ch < max_channels(dev); ch++) {
struct tw686x_audio_channel *ac;
ac = &dev->audio_channels[ch];
spin_lock_init(&ac->lock);
ac->dev = dev;
ac->ch = ch;
err = tw686x_audio_dma_alloc(dev, ac);
if (err < 0)
goto err_cleanup;
}
err = tw686x_snd_pcm_init(dev);
if (err < 0)
goto err_cleanup;
err = snd_card_register(card);
if (!err)
return 0;
err_cleanup:
for (ch = 0; ch < max_channels(dev); ch++) {
if (!dev->audio_channels[ch].dev)
continue;
tw686x_audio_dma_free(dev, &dev->audio_channels[ch]);
}
snd_card_free(card);
dev->snd_card = NULL;
return err;
}
/*
* Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar
*
* Based on original driver by Krzysztof Ha?asa:
* Copyright (C) 2015 Industrial Research Institute for Automation
* and Measurements PIAP
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*
* Notes
* -----
*
* 1. Under stress-testing, it has been observed that the PCIe link
* goes down, without reason. Therefore, the driver takes special care
* to allow device hot-unplugging.
*
* 2. TW686X devices are capable of setting a few different DMA modes,
* including: scatter-gather, field and frame modes. However,
* under stress testings it has been found that the machine can
* freeze completely if DMA registers are programmed while streaming
* is active.
* This driver tries to access hardware registers as infrequently
* as possible by:
* i. allocating fixed DMA buffers and memcpy'ing into
* vmalloc'ed buffers
* ii. using a timer to mitigate the rate of DMA reset operations,
* on DMA channels error.
*/
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci_ids.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include "tw686x.h"
#include "tw686x-regs.h"
/*
* This module parameter allows to control the DMA_TIMER_INTERVAL value.
* The DMA_TIMER_INTERVAL register controls the minimum DMA interrupt
* time span (iow, the maximum DMA interrupt rate) thus allowing for
* IRQ coalescing.
*
* The chip datasheet does not mention a time unit for this value, so
* users wanting fine-grain control over the interrupt rate should
* determine the desired value through testing.
*/
static u32 dma_interval = 0x00098968;
module_param(dma_interval, int, 0444);
MODULE_PARM_DESC(dma_interval, "Minimum time span for DMA interrupting host");
void tw686x_disable_channel(struct tw686x_dev *dev, unsigned int channel)
{
u32 dma_en = reg_read(dev, DMA_CHANNEL_ENABLE);
u32 dma_cmd = reg_read(dev, DMA_CMD);
dma_en &= ~BIT(channel);
dma_cmd &= ~BIT(channel);
/* Must remove it from pending too */
dev->pending_dma_en &= ~BIT(channel);
dev->pending_dma_cmd &= ~BIT(channel);
/* Stop DMA if no channels are enabled */
if (!dma_en)
dma_cmd = 0;
reg_write(dev, DMA_CHANNEL_ENABLE, dma_en);
reg_write(dev, DMA_CMD, dma_cmd);
}
void tw686x_enable_channel(struct tw686x_dev *dev, unsigned int channel)
{
u32 dma_en = reg_read(dev, DMA_CHANNEL_ENABLE);
u32 dma_cmd = reg_read(dev, DMA_CMD);
dev->pending_dma_en |= dma_en | BIT(channel);
dev->pending_dma_cmd |= dma_cmd | DMA_CMD_ENABLE | BIT(channel);
}
/*
* The purpose of this awful hack is to avoid enabling the DMA
* channels "too fast" which makes some TW686x devices very
* angry and freeze the CPU (see note 1).
*/
static void tw686x_dma_delay(unsigned long data)
{
struct tw686x_dev *dev = (struct tw686x_dev *)data;
unsigned long flags;
spin_lock_irqsave(&dev->lock, flags);
reg_write(dev, DMA_CHANNEL_ENABLE, dev->pending_dma_en);
reg_write(dev, DMA_CMD, dev->pending_dma_cmd);
dev->pending_dma_en = 0;
dev->pending_dma_cmd = 0;
spin_unlock_irqrestore(&dev->lock, flags);
}
static void tw686x_reset_channels(struct tw686x_dev *dev, unsigned int ch_mask)
{
u32 dma_en, dma_cmd;
dma_en = reg_read(dev, DMA_CHANNEL_ENABLE);
dma_cmd = reg_read(dev, DMA_CMD);
/*
* Save pending register status, the timer will
* restore them.
*/
dev->pending_dma_en |= dma_en;
dev->pending_dma_cmd |= dma_cmd;
/* Disable the reset channels */
reg_write(dev, DMA_CHANNEL_ENABLE, dma_en & ~ch_mask);
if ((dma_en & ~ch_mask) == 0) {
dev_dbg(&dev->pci_dev->dev, "reset: stopping DMA\n");
dma_cmd &= ~DMA_CMD_ENABLE;
}
reg_write(dev, DMA_CMD, dma_cmd & ~ch_mask);
}
static irqreturn_t tw686x_irq(int irq, void *dev_id)
{
struct tw686x_dev *dev = (struct tw686x_dev *)dev_id;
unsigned int video_requests, audio_requests, reset_ch;
u32 fifo_status, fifo_signal, fifo_ov, fifo_bad, fifo_errors;
u32 int_status, dma_en, video_en, pb_status;
unsigned long flags;
int_status = reg_read(dev, INT_STATUS); /* cleared on read */
fifo_status = reg_read(dev, VIDEO_FIFO_STATUS);
/* INT_STATUS does not include FIFO_STATUS errors! */
if (!int_status && !TW686X_FIFO_ERROR(fifo_status))
return IRQ_NONE;
if (int_status & INT_STATUS_DMA_TOUT) {
dev_dbg(&dev->pci_dev->dev,
"DMA timeout. Resetting DMA for all channels\n");
reset_ch = ~0;
goto reset_channels;
}
spin_lock_irqsave(&dev->lock, flags);
dma_en = reg_read(dev, DMA_CHANNEL_ENABLE);
spin_unlock_irqrestore(&dev->lock, flags);
video_en = dma_en & 0xff;
fifo_signal = ~(fifo_status & 0xff) & video_en;
fifo_ov = fifo_status >> 24;
fifo_bad = fifo_status >> 16;
/* Mask of channels with signal and FIFO errors */
fifo_errors = fifo_signal & (fifo_ov | fifo_bad);
reset_ch = 0;
pb_status = reg_read(dev, PB_STATUS);
/* Coalesce video frame/error events */
video_requests = (int_status & video_en) | fifo_errors;
audio_requests = (int_status & dma_en) >> 8;
if (video_requests)
tw686x_video_irq(dev, video_requests, pb_status,
fifo_status, &reset_ch);
if (audio_requests)
tw686x_audio_irq(dev, audio_requests, pb_status);
reset_channels:
if (reset_ch) {
spin_lock_irqsave(&dev->lock, flags);
tw686x_reset_channels(dev, reset_ch);
spin_unlock_irqrestore(&dev->lock, flags);
mod_timer(&dev->dma_delay_timer,
jiffies + msecs_to_jiffies(100));
}
return IRQ_HANDLED;
}
static void tw686x_dev_release(struct v4l2_device *v4l2_dev)
{
struct tw686x_dev *dev = container_of(v4l2_dev, struct tw686x_dev,
v4l2_dev);
unsigned int ch;
for (ch = 0; ch < max_channels(dev); ch++)
v4l2_ctrl_handler_free(&dev->video_channels[ch].ctrl_handler);
v4l2_device_unregister(&dev->v4l2_dev);
kfree(dev->audio_channels);
kfree(dev->video_channels);
kfree(dev);
}
static int tw686x_probe(struct pci_dev *pci_dev,
const struct pci_device_id *pci_id)
{
struct tw686x_dev *dev;
int err;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->type = pci_id->driver_data;
sprintf(dev->name, "tw%04X", pci_dev->device);
dev->video_channels = kcalloc(max_channels(dev),
sizeof(*dev->video_channels), GFP_KERNEL);
if (!dev->video_channels) {
err = -ENOMEM;
goto free_dev;
}
dev->audio_channels = kcalloc(max_channels(dev),
sizeof(*dev->audio_channels), GFP_KERNEL);
if (!dev->audio_channels) {
err = -ENOMEM;
goto free_video;
}
pr_info("%s: PCI %s, IRQ %d, MMIO 0x%lx\n", dev->name,
pci_name(pci_dev), pci_dev->irq,
(unsigned long)pci_resource_start(pci_dev, 0));
dev->pci_dev = pci_dev;
if (pci_enable_device(pci_dev)) {
err = -EIO;
goto free_audio;
}
pci_set_master(pci_dev);
err = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32));
if (err) {
dev_err(&pci_dev->dev, "32-bit PCI DMA not supported\n");
err = -EIO;
goto disable_pci;
}
err = pci_request_regions(pci_dev, dev->name);
if (err) {
dev_err(&pci_dev->dev, "unable to request PCI region\n");
goto disable_pci;
}
dev->mmio = pci_ioremap_bar(pci_dev, 0);
if (!dev->mmio) {
dev_err(&pci_dev->dev, "unable to remap PCI region\n");
err = -ENOMEM;
goto free_region;
}
/* Reset all subsystems */
reg_write(dev, SYS_SOFT_RST, 0x0f);
mdelay(1);
reg_write(dev, SRST[0], 0x3f);
if (max_channels(dev) > 4)
reg_write(dev, SRST[1], 0x3f);
/* Disable the DMA engine */
reg_write(dev, DMA_CMD, 0);
reg_write(dev, DMA_CHANNEL_ENABLE, 0);
/* Enable DMA FIFO overflow and pointer check */
reg_write(dev, DMA_CONFIG, 0xffffff04);
reg_write(dev, DMA_CHANNEL_TIMEOUT, 0x140c8584);
reg_write(dev, DMA_TIMER_INTERVAL, dma_interval);
spin_lock_init(&dev->lock);
err = request_irq(pci_dev->irq, tw686x_irq, IRQF_SHARED,
dev->name, dev);
if (err < 0) {
dev_err(&pci_dev->dev, "unable to request interrupt\n");
goto iounmap;
}
setup_timer(&dev->dma_delay_timer,
tw686x_dma_delay, (unsigned long) dev);
/*
* This must be set right before initializing v4l2_dev.
* It's used to release resources after the last handle
* held is released.
*/
dev->v4l2_dev.release = tw686x_dev_release;
err = tw686x_video_init(dev);
if (err) {
dev_err(&pci_dev->dev, "can't register video\n");
goto free_irq;
}
err = tw686x_audio_init(dev);
if (err)
dev_warn(&pci_dev->dev, "can't register audio\n");
pci_set_drvdata(pci_dev, dev);
return 0;
free_irq:
free_irq(pci_dev->irq, dev);
iounmap:
pci_iounmap(pci_dev, dev->mmio);
free_region:
pci_release_regions(pci_dev);
disable_pci:
pci_disable_device(pci_dev);
free_audio:
kfree(dev->audio_channels);
free_video:
kfree(dev->video_channels);
free_dev:
kfree(dev);
return err;
}
static void tw686x_remove(struct pci_dev *pci_dev)
{
struct tw686x_dev *dev = pci_get_drvdata(pci_dev);
unsigned long flags;
/* This guarantees the IRQ handler is no longer running,
* which means we can kiss good-bye some resources.
*/
free_irq(pci_dev->irq, dev);
tw686x_video_free(dev);
tw686x_audio_free(dev);
del_timer_sync(&dev->dma_delay_timer);
pci_iounmap(pci_dev, dev->mmio);
pci_release_regions(pci_dev);
pci_disable_device(pci_dev);
/*
* Setting pci_dev to NULL allows to detect hardware is no longer
* available and will be used by vb2_ops. This is required because
* the device sometimes hot-unplugs itself as the result of a PCIe
* link down.
* The lock is really important here.
*/
spin_lock_irqsave(&dev->lock, flags);
dev->pci_dev = NULL;
spin_unlock_irqrestore(&dev->lock, flags);
/*
* This calls tw686x_dev_release if it's the last reference.
* Otherwise, release is postponed until there are no users left.
*/
v4l2_device_put(&dev->v4l2_dev);
}
/*
* On TW6864 and TW6868, all channels share the pair of video DMA SG tables,
* with 10-bit start_idx and end_idx determining start and end of frame buffer
* for particular channel.
* TW6868 with all its 8 channels would be problematic (only 127 SG entries per
* channel) but we support only 4 channels on this chip anyway (the first
* 4 channels are driven with internal video decoder, the other 4 would require
* an external TW286x part).
*
* On TW6865 and TW6869, each channel has its own DMA SG table, with indexes
* starting with 0. Both chips have complete sets of internal video decoders
* (respectively 4 or 8-channel).
*
* All chips have separate SG tables for two video frames.
*/
/* driver_data is number of A/V channels */
static const struct pci_device_id tw686x_pci_tbl[] = {
{
PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6864),
.driver_data = 4
},
{
PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6865), /* not tested */
.driver_data = 4 | TYPE_SECOND_GEN
},
/*
* TW6868 supports 8 A/V channels with an external TW2865 chip;
* not supported by the driver.
*/
{
PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6868), /* not tested */
.driver_data = 4
},
{
PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, 0x6869),
.driver_data = 8 | TYPE_SECOND_GEN},
{}
};
MODULE_DEVICE_TABLE(pci, tw686x_pci_tbl);
static struct pci_driver tw686x_pci_driver = {
.name = "tw686x",
.id_table = tw686x_pci_tbl,
.probe = tw686x_probe,
.remove = tw686x_remove,
};
module_pci_driver(tw686x_pci_driver);
MODULE_DESCRIPTION("Driver for video frame grabber cards based on Intersil/Techwell TW686[4589]");
MODULE_AUTHOR("Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>");
MODULE_AUTHOR("Krzysztof Ha?asa <khalasa@piap.pl>");
MODULE_LICENSE("GPL v2");
/* DMA controller registers */
#define REG8_1(a0) ((const u16[8]) { a0, a0 + 1, a0 + 2, a0 + 3, \
a0 + 4, a0 + 5, a0 + 6, a0 + 7})
#define REG8_2(a0) ((const u16[8]) { a0, a0 + 2, a0 + 4, a0 + 6, \
a0 + 8, a0 + 0xa, a0 + 0xc, a0 + 0xe})
#define REG8_8(a0) ((const u16[8]) { a0, a0 + 8, a0 + 0x10, a0 + 0x18, \
a0 + 0x20, a0 + 0x28, a0 + 0x30, \
a0 + 0x38})
#define INT_STATUS 0x00
#define PB_STATUS 0x01
#define DMA_CMD 0x02
#define VIDEO_FIFO_STATUS 0x03
#define VIDEO_CHANNEL_ID 0x04
#define VIDEO_PARSER_STATUS 0x05
#define SYS_SOFT_RST 0x06
#define DMA_PAGE_TABLE0_ADDR ((const u16[8]) { 0x08, 0xd0, 0xd2, 0xd4, \
0xd6, 0xd8, 0xda, 0xdc })
#define DMA_PAGE_TABLE1_ADDR ((const u16[8]) { 0x09, 0xd1, 0xd3, 0xd5, \
0xd7, 0xd9, 0xdb, 0xdd })
#define DMA_CHANNEL_ENABLE 0x0a
#define DMA_CONFIG 0x0b
#define DMA_TIMER_INTERVAL 0x0c
#define DMA_CHANNEL_TIMEOUT 0x0d
#define VDMA_CHANNEL_CONFIG REG8_1(0x10)
#define ADMA_P_ADDR REG8_2(0x18)
#define ADMA_B_ADDR REG8_2(0x19)
#define DMA10_P_ADDR 0x28
#define DMA10_B_ADDR 0x29
#define VIDEO_CONTROL1 0x2a
#define VIDEO_CONTROL2 0x2b
#define AUDIO_CONTROL1 0x2c
#define AUDIO_CONTROL2 0x2d
#define PHASE_REF 0x2e
#define GPIO_REG 0x2f
#define INTL_HBAR_CTRL REG8_1(0x30)
#define AUDIO_CONTROL3 0x38
#define VIDEO_FIELD_CTRL REG8_1(0x39)
#define HSCALER_CTRL REG8_1(0x42)
#define VIDEO_SIZE REG8_1(0x4A)
#define VIDEO_SIZE_F2 REG8_1(0x52)
#define MD_CONF REG8_1(0x60)
#define MD_INIT REG8_1(0x68)
#define MD_MAP0 REG8_1(0x70)
#define VDMA_P_ADDR REG8_8(0x80) /* not used in DMA SG mode */
#define VDMA_WHP REG8_8(0x81)
#define VDMA_B_ADDR REG8_8(0x82)
#define VDMA_F2_P_ADDR REG8_8(0x84)
#define VDMA_F2_WHP REG8_8(0x85)
#define VDMA_F2_B_ADDR REG8_8(0x86)
#define EP_REG_ADDR 0xfe
#define EP_REG_DATA 0xff
/* Video decoder registers */
#define VDREG8(a0) ((const u16[8]) { \
a0 + 0x000, a0 + 0x010, a0 + 0x020, a0 + 0x030, \
a0 + 0x100, a0 + 0x110, a0 + 0x120, a0 + 0x130})
#define VIDSTAT VDREG8(0x100)
#define BRIGHT VDREG8(0x101)
#define CONTRAST VDREG8(0x102)
#define SHARPNESS VDREG8(0x103)
#define SAT_U VDREG8(0x104)
#define SAT_V VDREG8(0x105)
#define HUE VDREG8(0x106)
#define CROP_HI VDREG8(0x107)
#define VDELAY_LO VDREG8(0x108)
#define VACTIVE_LO VDREG8(0x109)
#define HDELAY_LO VDREG8(0x10a)
#define HACTIVE_LO VDREG8(0x10b)
#define MVSN VDREG8(0x10c)
#define STATUS2 VDREG8(0x10d)
#define SDT VDREG8(0x10e)
#define SDT_EN VDREG8(0x10f)
#define VSCALE_LO VDREG8(0x144)
#define SCALE_HI VDREG8(0x145)
#define HSCALE_LO VDREG8(0x146)
#define F2CROP_HI VDREG8(0x147)
#define F2VDELAY_LO VDREG8(0x148)
#define F2VACTIVE_LO VDREG8(0x149)
#define F2HDELAY_LO VDREG8(0x14a)
#define F2HACTIVE_LO VDREG8(0x14b)
#define F2VSCALE_LO VDREG8(0x14c)
#define F2SCALE_HI VDREG8(0x14d)
#define F2HSCALE_LO VDREG8(0x14e)
#define F2CNT VDREG8(0x14f)
#define VDREG2(a0) ((const u16[2]) { a0, a0 + 0x100 })
#define SRST VDREG2(0x180)
#define ACNTL VDREG2(0x181)
#define ACNTL2 VDREG2(0x182)
#define CNTRL1 VDREG2(0x183)
#define CKHY VDREG2(0x184)
#define SHCOR VDREG2(0x185)
#define CORING VDREG2(0x186)
#define CLMPG VDREG2(0x187)
#define IAGC VDREG2(0x188)
#define VCTRL1 VDREG2(0x18f)
#define MISC1 VDREG2(0x194)
#define LOOP VDREG2(0x195)
#define MISC2 VDREG2(0x196)
#define CLMD VDREG2(0x197)
#define ANPWRDOWN VDREG2(0x1ce)
#define AIGAIN ((const u16[8]) { 0x1d0, 0x1d1, 0x1d2, 0x1d3, \
0x2d0, 0x2d1, 0x2d2, 0x2d3 })
#define SYS_MODE_DMA_SHIFT 13
#define DMA_CMD_ENABLE BIT(31)
#define INT_STATUS_DMA_TOUT BIT(17)
#define TW686X_VIDSTAT_HLOCK BIT(6)
#define TW686X_VIDSTAT_VDLOSS BIT(7)
#define TW686X_STD_NTSC_M 0
#define TW686X_STD_PAL 1
#define TW686X_STD_SECAM 2
#define TW686X_STD_NTSC_443 3
#define TW686X_STD_PAL_M 4
#define TW686X_STD_PAL_CN 5
#define TW686X_STD_PAL_60 6
#define TW686X_FIFO_ERROR(x) (x & ~(0xff))
此差异已折叠。
/*
* Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar
*
* Copyright (C) 2015 Industrial Research Institute for Automation
* and Measurements PIAP
* Written by Krzysztof Ha?asa
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*/
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/timer.h>
#include <linux/videodev2.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/videobuf2-v4l2.h>
#include <sound/pcm.h>
#include "tw686x-regs.h"
#define TYPE_MAX_CHANNELS 0x0f
#define TYPE_SECOND_GEN 0x10
#define TW686X_DEF_PHASE_REF 0x1518
#define TW686X_FIELD_MODE 0x3
#define TW686X_FRAME_MODE 0x2
/* 0x1 is reserved */
#define TW686X_SG_MODE 0x0
#define TW686X_AUDIO_PAGE_SZ 4096
#define TW686X_AUDIO_PAGE_MAX 16
#define TW686X_AUDIO_PERIODS_MIN 2
#define TW686X_AUDIO_PERIODS_MAX TW686X_AUDIO_PAGE_MAX
struct tw686x_format {
char *name;
unsigned int fourcc;
unsigned int depth;
unsigned int mode;
};
struct tw686x_dma_desc {
dma_addr_t phys;
void *virt;
unsigned int size;
};
struct tw686x_audio_buf {
dma_addr_t dma;
void *virt;
struct list_head list;
};
struct tw686x_v4l2_buf {
struct vb2_v4l2_buffer vb;
struct list_head list;
};
struct tw686x_audio_channel {
struct tw686x_dev *dev;
struct snd_pcm_substream *ss;
unsigned int ch;
struct tw686x_audio_buf *curr_bufs[2];
struct tw686x_dma_desc dma_descs[2];
dma_addr_t ptr;
struct tw686x_audio_buf buf[TW686X_AUDIO_PAGE_MAX];
struct list_head buf_list;
spinlock_t lock;
};
struct tw686x_video_channel {
struct tw686x_dev *dev;
struct vb2_queue vidq;
struct list_head vidq_queued;
struct video_device *device;
struct tw686x_v4l2_buf *curr_bufs[2];
struct tw686x_dma_desc dma_descs[2];
struct v4l2_ctrl_handler ctrl_handler;
const struct tw686x_format *format;
struct mutex vb_mutex;
spinlock_t qlock;
v4l2_std_id video_standard;
unsigned int width, height;
unsigned int h_halve, v_halve;
unsigned int ch;
unsigned int num;
unsigned int fps;
unsigned int input;
unsigned int sequence;
unsigned int pb;
bool no_signal;
};
/**
* struct tw686x_dev - global device status
* @lock: spinlock controlling access to the
* shared device registers (DMA enable/disable).
*/
struct tw686x_dev {
spinlock_t lock;
struct v4l2_device v4l2_dev;
struct snd_card *snd_card;
char name[32];
unsigned int type;
struct pci_dev *pci_dev;
__u32 __iomem *mmio;
void *alloc_ctx;
struct tw686x_video_channel *video_channels;
struct tw686x_audio_channel *audio_channels;
int audio_rate; /* per-device value */
struct timer_list dma_delay_timer;
u32 pending_dma_en; /* must be protected by lock */
u32 pending_dma_cmd; /* must be protected by lock */
};
static inline uint32_t reg_read(struct tw686x_dev *dev, unsigned int reg)
{
return readl(dev->mmio + reg);
}
static inline void reg_write(struct tw686x_dev *dev, unsigned int reg,
uint32_t value)
{
writel(value, dev->mmio + reg);
}
static inline unsigned int max_channels(struct tw686x_dev *dev)
{
return dev->type & TYPE_MAX_CHANNELS; /* 4 or 8 channels */
}
void tw686x_enable_channel(struct tw686x_dev *dev, unsigned int channel);
void tw686x_disable_channel(struct tw686x_dev *dev, unsigned int channel);
int tw686x_video_init(struct tw686x_dev *dev);
void tw686x_video_free(struct tw686x_dev *dev);
void tw686x_video_irq(struct tw686x_dev *dev, unsigned long requests,
unsigned int pb_status, unsigned int fifo_status,
unsigned int *reset_ch);
int tw686x_audio_init(struct tw686x_dev *dev);
void tw686x_audio_free(struct tw686x_dev *dev);
void tw686x_audio_irq(struct tw686x_dev *dev, unsigned long requests,
unsigned int pb_status);
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册