diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/media/pci/tw686x | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/media/pci/tw686x')
-rw-r--r-- | drivers/media/pci/tw686x/Kconfig | 20 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/Makefile | 4 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/tw686x-audio.c | 413 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/tw686x-core.c | 452 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/tw686x-regs.h | 132 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/tw686x-video.c | 1308 | ||||
-rw-r--r-- | drivers/media/pci/tw686x/tw686x.h | 179 |
7 files changed, 2508 insertions, 0 deletions
diff --git a/drivers/media/pci/tw686x/Kconfig b/drivers/media/pci/tw686x/Kconfig new file mode 100644 index 000000000..a4edad6aa --- /dev/null +++ b/drivers/media/pci/tw686x/Kconfig @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_TW686X + tristate "Intersil/Techwell TW686x video capture cards" + depends on PCI && VIDEO_DEV && SND + select VIDEOBUF2_VMALLOC + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_DMA_SG + 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. diff --git a/drivers/media/pci/tw686x/Makefile b/drivers/media/pci/tw686x/Makefile new file mode 100644 index 000000000..1795dffb5 --- /dev/null +++ b/drivers/media/pci/tw686x/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +tw686x-objs := tw686x-core.o tw686x-video.o tw686x-audio.o + +obj-$(CONFIG_VIDEO_TW686X) += tw686x.o diff --git a/drivers/media/pci/tw686x/tw686x-audio.c b/drivers/media/pci/tw686x/tw686x-audio.c new file mode 100644 index 000000000..74cba1368 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x-audio.c @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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] + */ + +#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); + + if (!done || !next) + continue; + /* + * Checking for a non-nil dma_desc[pb]->virt buffer is + * the same as checking for memcpy DMA mode. + */ + desc = &ac->dma_descs[pb]; + if (desc->virt) { + memcpy(done->virt, desc->virt, + dev->period_size); + } else { + u32 reg = pb ? ADMA_B_ADDR[ch] : ADMA_P_ADDR[ch]; + reg_write(dev, reg, next->dma); + } + ac->ptr = done->dma - ac->buf[0].dma; + snd_pcm_period_elapsed(ac->ss); + } +} + +/* + * Audio parameters are global and shared among all + * capture channels. The driver prevents changes to + * the parameters if any audio channel is capturing. + */ +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 * AUDIO_DMA_SIZE_MAX, + .period_bytes_min = AUDIO_DMA_SIZE_MIN, + .period_bytes_max = AUDIO_DMA_SIZE_MAX, + .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); + /* + * Given the audio parameters are global (i.e. shared across + * DMA channels), we need to check new params are allowed. + */ + if (((dev->audio_rate != rt->rate) || + (dev->period_size != period_size)) && dev->audio_enabled) + goto err_audio_busy; + + 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 (dev->period_size != period_size) { + u32 reg; + + dev->period_size = period_size; + reg = reg_read(dev, AUDIO_CONTROL1); + reg &= ~(AUDIO_DMA_SIZE_MASK << AUDIO_DMA_SIZE_SHIFT); + reg |= period_size << AUDIO_DMA_SIZE_SHIFT; + + reg_write(dev, AUDIO_CONTROL1, reg); + } + + if (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; + + if (dev->dma_mode != TW686X_DMA_MODE_MEMCPY) { + reg_write(dev, ADMA_P_ADDR[ac->ch], p_buf->dma); + reg_write(dev, ADMA_B_ADDR[ac->ch], b_buf->dma); + } + + spin_unlock_irqrestore(&ac->lock, flags); + + return 0; + +err_audio_busy: + spin_unlock_irqrestore(&dev->lock, flags); + return -EBUSY; +} + +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); + dev->audio_enabled = 1; + 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); + dev->audio_enabled = 0; + 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 const struct snd_pcm_ops tw686x_pcm_ops = { + .open = tw686x_pcm_open, + .close = tw686x_pcm_close, + .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; + strscpy(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); + + snd_pcm_set_managed_buffer_all(pcm, + SNDRV_DMA_TYPE_DEV, + &dev->pci_dev->dev, + TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX, + TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX); + return 0; +} + +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; + dma_free_coherent(&dev->pci_dev->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; + + /* + * In the memcpy DMA mode we allocate a coherent buffer + * and use it for the DMA capture. Otherwise, DMA + * acts on the ALSA buffers as received in pcm_prepare. + */ + if (dev->dma_mode != TW686X_DMA_MODE_MEMCPY) + return 0; + + for (pb = 0; pb < 2; pb++) { + u32 reg = pb ? ADMA_B_ADDR[ac->ch] : ADMA_P_ADDR[ac->ch]; + void *virt; + + virt = dma_alloc_coherent(&dev->pci_dev->dev, + AUDIO_DMA_SIZE_MAX, + &ac->dma_descs[pb].phys, GFP_KERNEL); + 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 = AUDIO_DMA_SIZE_MAX; + 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; + + /* Enable external audio */ + reg_write(dev, AUDIO_CONTROL1, BIT(0)); + + 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; + strscpy(card->driver, "tw686x", sizeof(card->driver)); + strscpy(card->shortname, "tw686x", sizeof(card->shortname)); + strscpy(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; +} diff --git a/drivers/media/pci/tw686x/tw686x-core.c b/drivers/media/pci/tw686x/tw686x-core.c new file mode 100644 index 000000000..c53099c95 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x-core.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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 + * + * 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. + * + * Therefore, driver implements a dma_mode called 'memcpy' which + * avoids cycling the DMA buffers, and insteads allocates extra DMA buffers + * and then copies into vmalloc'ed user buffers. + * + * In addition to this, when streaming is on, the driver tries to access + * hardware registers as infrequently as possible. This is done by using + * a timer to limit the rate at which DMA is reset 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"); + +static unsigned int dma_mode = TW686X_DMA_MODE_MEMCPY; +static const char *dma_mode_name(unsigned int mode) +{ + switch (mode) { + case TW686X_DMA_MODE_MEMCPY: + return "memcpy"; + case TW686X_DMA_MODE_CONTIG: + return "contig"; + case TW686X_DMA_MODE_SG: + return "sg"; + default: + return "unknown"; + } +} + +static int tw686x_dma_mode_get(char *buffer, const struct kernel_param *kp) +{ + return sprintf(buffer, "%s", dma_mode_name(dma_mode)); +} + +static int tw686x_dma_mode_set(const char *val, const struct kernel_param *kp) +{ + if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_MEMCPY))) + dma_mode = TW686X_DMA_MODE_MEMCPY; + else if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_CONTIG))) + dma_mode = TW686X_DMA_MODE_CONTIG; + else if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_SG))) + dma_mode = TW686X_DMA_MODE_SG; + else + return -EINVAL; + return 0; +} +module_param_call(dma_mode, tw686x_dma_mode_set, tw686x_dma_mode_get, + &dma_mode, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(dma_mode, "DMA operation mode (memcpy/contig/sg, default=memcpy)"); + +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(struct timer_list *t) +{ + struct tw686x_dev *dev = from_timer(dev, t, dma_delay_timer); + 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; + dev->dma_mode = dma_mode; + 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 (%s mode)\n", dev->name, + pci_name(pci_dev), pci_dev->irq, + (unsigned long)pci_resource_start(pci_dev, 0), + dma_mode_name(dma_mode)); + + dev->pci_dev = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto free_audio; + } + + pci_set_master(pci_dev); + err = dma_set_mask(&pci_dev->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); + + timer_setup(&dev->dma_delay_timer, tw686x_dma_delay, 0); + + /* + * 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 iounmap; + } + + err = tw686x_audio_init(dev); + if (err) + dev_warn(&pci_dev->dev, "can't register audio\n"); + + 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 tw686x_free; + } + + pci_set_drvdata(pci_dev, dev); + return 0; + +tw686x_free: + tw686x_video_free(dev); + tw686x_audio_free(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"); diff --git a/drivers/media/pci/tw686x/tw686x-regs.h b/drivers/media/pci/tw686x/tw686x-regs.h new file mode 100644 index 000000000..8adacc928 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x-regs.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* 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 AUDIO_DMA_SIZE_SHIFT 19 +#define AUDIO_DMA_SIZE_MIN SZ_512 +#define AUDIO_DMA_SIZE_MAX SZ_4K +#define AUDIO_DMA_SIZE_MASK (SZ_8K - 1) + +#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_FIELD_MODE 0x3 +#define TW686X_FRAME_MODE 0x2 +/* 0x1 is reserved */ +#define TW686X_SG_MODE 0x0 + +#define TW686X_FIFO_ERROR(x) (x & ~(0xff)) diff --git a/drivers/media/pci/tw686x/tw686x-video.c b/drivers/media/pci/tw686x/tw686x-video.c new file mode 100644 index 000000000..3ebf7a2c9 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x-video.c @@ -0,0 +1,1308 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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 + */ + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <media/v4l2-common.h> +#include <media/v4l2-event.h> +#include <media/videobuf2-dma-contig.h> +#include <media/videobuf2-dma-sg.h> +#include <media/videobuf2-vmalloc.h> +#include "tw686x.h" +#include "tw686x-regs.h" + +#define TW686X_INPUTS_PER_CH 4 +#define TW686X_VIDEO_WIDTH 720 +#define TW686X_VIDEO_HEIGHT(id) ((id & V4L2_STD_525_60) ? 480 : 576) +#define TW686X_MAX_FPS(id) ((id & V4L2_STD_525_60) ? 30 : 25) + +#define TW686X_MAX_SG_ENTRY_SIZE 4096 +#define TW686X_MAX_SG_DESC_COUNT 256 /* PAL 720x576 needs 203 4-KB pages */ +#define TW686X_SG_TABLE_SIZE (TW686X_MAX_SG_DESC_COUNT * sizeof(struct tw686x_sg_desc)) + +static const struct tw686x_format formats[] = { + { + .fourcc = V4L2_PIX_FMT_UYVY, + .mode = 0, + .depth = 16, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .mode = 5, + .depth = 16, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .mode = 6, + .depth = 16, + } +}; + +static void tw686x_buf_done(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + struct vb2_v4l2_buffer *vb; + struct vb2_buffer *vb2_buf; + + if (vc->curr_bufs[pb]) { + vb = &vc->curr_bufs[pb]->vb; + + vb->field = dev->dma_ops->field; + vb->sequence = vc->sequence++; + vb2_buf = &vb->vb2_buf; + + if (dev->dma_mode == TW686X_DMA_MODE_MEMCPY) + memcpy(vb2_plane_vaddr(vb2_buf, 0), desc->virt, + desc->size); + vb2_buf->timestamp = ktime_get_ns(); + vb2_buffer_done(vb2_buf, VB2_BUF_STATE_DONE); + } + + vc->pb = !pb; +} + +/* + * We can call this even when alloc_dma failed for the given channel + */ +static void tw686x_memcpy_dma_free(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + + /* Check device presence. Shouldn't really happen! */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (!pci_dev) { + WARN(1, "trying to deallocate on missing device\n"); + return; + } + + if (desc->virt) { + dma_free_coherent(&dev->pci_dev->dev, desc->size, desc->virt, + desc->phys); + desc->virt = NULL; + } +} + +static int tw686x_memcpy_dma_alloc(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dev *dev = vc->dev; + u32 reg = pb ? VDMA_B_ADDR[vc->ch] : VDMA_P_ADDR[vc->ch]; + unsigned int len; + void *virt; + + WARN(vc->dma_descs[pb].virt, + "Allocating buffer but previous still here\n"); + + len = (vc->width * vc->height * vc->format->depth) >> 3; + virt = dma_alloc_coherent(&dev->pci_dev->dev, len, + &vc->dma_descs[pb].phys, GFP_KERNEL); + if (!virt) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to allocate %s-buffer\n", + vc->ch, pb ? "B" : "P"); + return -ENOMEM; + } + vc->dma_descs[pb].size = len; + vc->dma_descs[pb].virt = virt; + reg_write(dev, reg, vc->dma_descs[pb].phys); + + return 0; +} + +static void tw686x_memcpy_buf_refill(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_v4l2_buf *buf; + + while (!list_empty(&vc->vidq_queued)) { + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + + vc->curr_bufs[pb] = buf; + return; + } + vc->curr_bufs[pb] = NULL; +} + +static const struct tw686x_dma_ops memcpy_dma_ops = { + .alloc = tw686x_memcpy_dma_alloc, + .free = tw686x_memcpy_dma_free, + .buf_refill = tw686x_memcpy_buf_refill, + .mem_ops = &vb2_vmalloc_memops, + .hw_dma_mode = TW686X_FRAME_MODE, + .field = V4L2_FIELD_INTERLACED, +}; + +static void tw686x_contig_buf_refill(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_v4l2_buf *buf; + + while (!list_empty(&vc->vidq_queued)) { + u32 reg = pb ? VDMA_B_ADDR[vc->ch] : VDMA_P_ADDR[vc->ch]; + dma_addr_t phys; + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + + phys = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + reg_write(vc->dev, reg, phys); + + buf->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE; + vc->curr_bufs[pb] = buf; + return; + } + vc->curr_bufs[pb] = NULL; +} + +static const struct tw686x_dma_ops contig_dma_ops = { + .buf_refill = tw686x_contig_buf_refill, + .mem_ops = &vb2_dma_contig_memops, + .hw_dma_mode = TW686X_FRAME_MODE, + .field = V4L2_FIELD_INTERLACED, +}; + +static int tw686x_sg_desc_fill(struct tw686x_sg_desc *descs, + struct tw686x_v4l2_buf *buf, + unsigned int buf_len) +{ + struct sg_table *vbuf = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0); + unsigned int len, entry_len; + struct scatterlist *sg; + int i, count; + + /* Clear the scatter-gather table */ + memset(descs, 0, TW686X_SG_TABLE_SIZE); + + count = 0; + for_each_sg(vbuf->sgl, sg, vbuf->nents, i) { + dma_addr_t phys = sg_dma_address(sg); + len = sg_dma_len(sg); + + while (len && buf_len) { + + if (count == TW686X_MAX_SG_DESC_COUNT) + return -ENOMEM; + + entry_len = min_t(unsigned int, len, + TW686X_MAX_SG_ENTRY_SIZE); + entry_len = min_t(unsigned int, entry_len, buf_len); + descs[count].phys = cpu_to_le32(phys); + descs[count++].flags_length = + cpu_to_le32(BIT(30) | entry_len); + phys += entry_len; + len -= entry_len; + buf_len -= entry_len; + } + + if (!buf_len) + return 0; + } + + return -ENOMEM; +} + +static void tw686x_sg_buf_refill(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dev *dev = vc->dev; + struct tw686x_v4l2_buf *buf; + + while (!list_empty(&vc->vidq_queued)) { + unsigned int buf_len; + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + + buf_len = (vc->width * vc->height * vc->format->depth) >> 3; + if (tw686x_sg_desc_fill(vc->sg_descs[pb], buf, buf_len)) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to fill %s-buffer\n", + vc->ch, pb ? "B" : "P"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + continue; + } + + buf->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE; + vc->curr_bufs[pb] = buf; + return; + } + + vc->curr_bufs[pb] = NULL; +} + +static void tw686x_sg_dma_free(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + + if (desc->size) { + dma_free_coherent(&dev->pci_dev->dev, desc->size, desc->virt, + desc->phys); + desc->virt = NULL; + } + + vc->sg_descs[pb] = NULL; +} + +static int tw686x_sg_dma_alloc(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + u32 reg = pb ? DMA_PAGE_TABLE1_ADDR[vc->ch] : + DMA_PAGE_TABLE0_ADDR[vc->ch]; + void *virt; + + if (desc->size) { + virt = dma_alloc_coherent(&dev->pci_dev->dev, desc->size, + &desc->phys, GFP_KERNEL); + if (!virt) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to allocate %s-buffer\n", + vc->ch, pb ? "B" : "P"); + return -ENOMEM; + } + desc->virt = virt; + reg_write(dev, reg, desc->phys); + } else { + virt = dev->video_channels[0].dma_descs[pb].virt + + vc->ch * TW686X_SG_TABLE_SIZE; + } + + vc->sg_descs[pb] = virt; + return 0; +} + +static int tw686x_sg_setup(struct tw686x_dev *dev) +{ + unsigned int sg_table_size, pb, ch, channels; + + if (is_second_gen(dev)) { + /* + * TW6865/TW6869: each channel needs a pair of + * P-B descriptor tables. + */ + channels = max_channels(dev); + sg_table_size = TW686X_SG_TABLE_SIZE; + } else { + /* + * TW6864/TW6868: we need to allocate a pair of + * P-B descriptor tables, common for all channels. + * Each table will be bigger than 4 KB. + */ + channels = 1; + sg_table_size = max_channels(dev) * TW686X_SG_TABLE_SIZE; + } + + for (ch = 0; ch < channels; ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + + for (pb = 0; pb < 2; pb++) + vc->dma_descs[pb].size = sg_table_size; + } + + return 0; +} + +static const struct tw686x_dma_ops sg_dma_ops = { + .setup = tw686x_sg_setup, + .alloc = tw686x_sg_dma_alloc, + .free = tw686x_sg_dma_free, + .buf_refill = tw686x_sg_buf_refill, + .mem_ops = &vb2_dma_sg_memops, + .hw_dma_mode = TW686X_SG_MODE, + .field = V4L2_FIELD_SEQ_TB, +}; + +static const unsigned int fps_map[15] = { + /* + * bit 31 enables selecting the field control register + * bits 0-29 are a bitmask with fields that will be output. + * For NTSC (and PAL-M, PAL-60), all 30 bits are used. + * For other PAL standards, only the first 25 bits are used. + */ + 0x00000000, /* output all fields */ + 0x80000006, /* 2 fps (60Hz), 2 fps (50Hz) */ + 0x80018006, /* 4 fps (60Hz), 4 fps (50Hz) */ + 0x80618006, /* 6 fps (60Hz), 6 fps (50Hz) */ + 0x81818186, /* 8 fps (60Hz), 8 fps (50Hz) */ + 0x86186186, /* 10 fps (60Hz), 8 fps (50Hz) */ + 0x86619866, /* 12 fps (60Hz), 10 fps (50Hz) */ + 0x86666666, /* 14 fps (60Hz), 12 fps (50Hz) */ + 0x9999999e, /* 16 fps (60Hz), 14 fps (50Hz) */ + 0x99e6799e, /* 18 fps (60Hz), 16 fps (50Hz) */ + 0x9e79e79e, /* 20 fps (60Hz), 16 fps (50Hz) */ + 0x9e7e7e7e, /* 22 fps (60Hz), 18 fps (50Hz) */ + 0x9fe7f9fe, /* 24 fps (60Hz), 20 fps (50Hz) */ + 0x9ffe7ffe, /* 26 fps (60Hz), 22 fps (50Hz) */ + 0x9ffffffe, /* 28 fps (60Hz), 24 fps (50Hz) */ +}; + +static unsigned int tw686x_real_fps(unsigned int index, unsigned int max_fps) +{ + unsigned long mask; + + if (!index || index >= ARRAY_SIZE(fps_map)) + return max_fps; + + mask = GENMASK(max_fps - 1, 0); + return hweight_long(fps_map[index] & mask); +} + +static unsigned int tw686x_fps_idx(unsigned int fps, unsigned int max_fps) +{ + unsigned int idx, real_fps; + int delta; + + /* First guess */ + idx = (12 + 15 * fps) / max_fps; + + /* Minimal possible framerate is 2 frames per second */ + if (!idx) + return 1; + + /* Check if the difference is bigger than abs(1) and adjust */ + real_fps = tw686x_real_fps(idx, max_fps); + delta = real_fps - fps; + if (delta < -1) + idx++; + else if (delta > 1) + idx--; + + /* Max framerate */ + if (idx >= 15) + return 0; + + return idx; +} + +static void tw686x_set_framerate(struct tw686x_video_channel *vc, + unsigned int fps) +{ + unsigned int i; + + i = tw686x_fps_idx(fps, TW686X_MAX_FPS(vc->video_standard)); + reg_write(vc->dev, VIDEO_FIELD_CTRL[vc->ch], fps_map[i]); + vc->fps = tw686x_real_fps(i, TW686X_MAX_FPS(vc->video_standard)); +} + +static const struct tw686x_format *format_by_fourcc(unsigned int fourcc) +{ + unsigned int cnt; + + for (cnt = 0; cnt < ARRAY_SIZE(formats); cnt++) + if (formats[cnt].fourcc == fourcc) + return &formats[cnt]; + return NULL; +} + +static int tw686x_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vq); + unsigned int szimage = + (vc->width * vc->height * vc->format->depth) >> 3; + + /* + * Let's request at least three buffers: two for the + * DMA engine and one for userspace. + */ + if (vq->num_buffers + *nbuffers < 3) + *nbuffers = 3 - vq->num_buffers; + + if (*nplanes) { + if (*nplanes != 1 || sizes[0] < szimage) + return -EINVAL; + return 0; + } + + sizes[0] = szimage; + *nplanes = 1; + return 0; +} + +static void tw686x_buf_queue(struct vb2_buffer *vb) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vb->vb2_queue); + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct tw686x_v4l2_buf *buf = + container_of(vbuf, struct tw686x_v4l2_buf, vb); + + /* Check device presence */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (!pci_dev) { + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + return; + } + + spin_lock_irqsave(&vc->qlock, flags); + list_add_tail(&buf->list, &vc->vidq_queued); + spin_unlock_irqrestore(&vc->qlock, flags); +} + +static void tw686x_clear_queue(struct tw686x_video_channel *vc, + enum vb2_buffer_state state) +{ + unsigned int pb; + + while (!list_empty(&vc->vidq_queued)) { + struct tw686x_v4l2_buf *buf; + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + + for (pb = 0; pb < 2; pb++) { + if (vc->curr_bufs[pb]) + vb2_buffer_done(&vc->curr_bufs[pb]->vb.vb2_buf, state); + vc->curr_bufs[pb] = NULL; + } +} + +static int tw686x_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vq); + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + int pb, err; + + /* Check device presence */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (!pci_dev) { + err = -ENODEV; + goto err_clear_queue; + } + + spin_lock_irqsave(&vc->qlock, flags); + + /* Sanity check */ + if (dev->dma_mode == TW686X_DMA_MODE_MEMCPY && + (!vc->dma_descs[0].virt || !vc->dma_descs[1].virt)) { + spin_unlock_irqrestore(&vc->qlock, flags); + v4l2_err(&dev->v4l2_dev, + "video%d: refusing to start without DMA buffers\n", + vc->num); + err = -ENOMEM; + goto err_clear_queue; + } + + for (pb = 0; pb < 2; pb++) + dev->dma_ops->buf_refill(vc, pb); + spin_unlock_irqrestore(&vc->qlock, flags); + + vc->sequence = 0; + vc->pb = 0; + + spin_lock_irqsave(&dev->lock, flags); + tw686x_enable_channel(dev, vc->ch); + spin_unlock_irqrestore(&dev->lock, flags); + + mod_timer(&dev->dma_delay_timer, jiffies + msecs_to_jiffies(100)); + + return 0; + +err_clear_queue: + spin_lock_irqsave(&vc->qlock, flags); + tw686x_clear_queue(vc, VB2_BUF_STATE_QUEUED); + spin_unlock_irqrestore(&vc->qlock, flags); + return err; +} + +static void tw686x_stop_streaming(struct vb2_queue *vq) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vq); + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + + /* Check device presence */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (pci_dev) + tw686x_disable_channel(dev, vc->ch); + + spin_lock_irqsave(&vc->qlock, flags); + tw686x_clear_queue(vc, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&vc->qlock, flags); +} + +static int tw686x_buf_prepare(struct vb2_buffer *vb) +{ + struct tw686x_video_channel *vc = vb2_get_drv_priv(vb->vb2_queue); + unsigned int size = + (vc->width * vc->height * vc->format->depth) >> 3; + + if (vb2_plane_size(vb, 0) < size) + return -EINVAL; + vb2_set_plane_payload(vb, 0, size); + return 0; +} + +static const struct vb2_ops tw686x_video_qops = { + .queue_setup = tw686x_queue_setup, + .buf_queue = tw686x_buf_queue, + .buf_prepare = tw686x_buf_prepare, + .start_streaming = tw686x_start_streaming, + .stop_streaming = tw686x_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int tw686x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct tw686x_video_channel *vc; + struct tw686x_dev *dev; + unsigned int ch; + + vc = container_of(ctrl->handler, struct tw686x_video_channel, + ctrl_handler); + dev = vc->dev; + ch = vc->ch; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + reg_write(dev, BRIGHT[ch], ctrl->val & 0xff); + return 0; + + case V4L2_CID_CONTRAST: + reg_write(dev, CONTRAST[ch], ctrl->val); + return 0; + + case V4L2_CID_SATURATION: + reg_write(dev, SAT_U[ch], ctrl->val); + reg_write(dev, SAT_V[ch], ctrl->val); + return 0; + + case V4L2_CID_HUE: + reg_write(dev, HUE[ch], ctrl->val & 0xff); + return 0; + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops ctrl_ops = { + .s_ctrl = tw686x_s_ctrl, +}; + +static int tw686x_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; + + f->fmt.pix.width = vc->width; + f->fmt.pix.height = vc->height; + f->fmt.pix.field = dev->dma_ops->field; + f->fmt.pix.pixelformat = vc->format->fourcc; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + f->fmt.pix.bytesperline = (f->fmt.pix.width * vc->format->depth) / 8; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + return 0; +} + +static int tw686x_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; + unsigned int video_height = TW686X_VIDEO_HEIGHT(vc->video_standard); + const struct tw686x_format *format; + + format = format_by_fourcc(f->fmt.pix.pixelformat); + if (!format) { + format = &formats[0]; + f->fmt.pix.pixelformat = format->fourcc; + } + + if (f->fmt.pix.width <= TW686X_VIDEO_WIDTH / 2) + f->fmt.pix.width = TW686X_VIDEO_WIDTH / 2; + else + f->fmt.pix.width = TW686X_VIDEO_WIDTH; + + if (f->fmt.pix.height <= video_height / 2) + f->fmt.pix.height = video_height / 2; + else + f->fmt.pix.height = video_height; + + f->fmt.pix.bytesperline = (f->fmt.pix.width * format->depth) / 8; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + f->fmt.pix.field = dev->dma_ops->field; + + return 0; +} + +static int tw686x_set_format(struct tw686x_video_channel *vc, + unsigned int pixelformat, unsigned int width, + unsigned int height, bool realloc) +{ + struct tw686x_dev *dev = vc->dev; + u32 val, dma_width, dma_height, dma_line_width; + int err, pb; + + vc->format = format_by_fourcc(pixelformat); + vc->width = width; + vc->height = height; + + /* We need new DMA buffers if the framesize has changed */ + if (dev->dma_ops->alloc && realloc) { + for (pb = 0; pb < 2; pb++) + dev->dma_ops->free(vc, pb); + + for (pb = 0; pb < 2; pb++) { + err = dev->dma_ops->alloc(vc, pb); + if (err) { + if (pb > 0) + dev->dma_ops->free(vc, 0); + return err; + } + } + } + + val = reg_read(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch]); + + if (vc->width <= TW686X_VIDEO_WIDTH / 2) + val |= BIT(23); + else + val &= ~BIT(23); + + if (vc->height <= TW686X_VIDEO_HEIGHT(vc->video_standard) / 2) + val |= BIT(24); + else + val &= ~BIT(24); + + val &= ~0x7ffff; + + /* Program the DMA scatter-gather */ + if (dev->dma_mode == TW686X_DMA_MODE_SG) { + u32 start_idx, end_idx; + + start_idx = is_second_gen(dev) ? + 0 : vc->ch * TW686X_MAX_SG_DESC_COUNT; + end_idx = start_idx + TW686X_MAX_SG_DESC_COUNT - 1; + + val |= (end_idx << 10) | start_idx; + } + + val &= ~(0x7 << 20); + val |= vc->format->mode << 20; + reg_write(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch], val); + + /* Program the DMA frame size */ + dma_width = (vc->width * 2) & 0x7ff; + dma_height = vc->height / 2; + dma_line_width = (vc->width * 2) & 0x7ff; + val = (dma_height << 22) | (dma_line_width << 11) | dma_width; + reg_write(vc->dev, VDMA_WHP[vc->ch], val); + return 0; +} + +static int tw686x_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + unsigned long area; + bool realloc; + int err; + + if (vb2_is_busy(&vc->vidq)) + return -EBUSY; + + area = vc->width * vc->height; + err = tw686x_try_fmt_vid_cap(file, priv, f); + if (err) + return err; + + realloc = area != (f->fmt.pix.width * f->fmt.pix.height); + return tw686x_set_format(vc, f->fmt.pix.pixelformat, + f->fmt.pix.width, f->fmt.pix.height, + realloc); +} + +static int tw686x_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; + + strscpy(cap->driver, "tw686x", sizeof(cap->driver)); + strscpy(cap->card, dev->name, sizeof(cap->card)); + return 0; +} + +static int tw686x_set_standard(struct tw686x_video_channel *vc, v4l2_std_id id) +{ + u32 val; + + if (id & V4L2_STD_NTSC) + val = 0; + else if (id & V4L2_STD_PAL) + val = 1; + else if (id & V4L2_STD_SECAM) + val = 2; + else if (id & V4L2_STD_NTSC_443) + val = 3; + else if (id & V4L2_STD_PAL_M) + val = 4; + else if (id & V4L2_STD_PAL_Nc) + val = 5; + else if (id & V4L2_STD_PAL_60) + val = 6; + else + return -EINVAL; + + vc->video_standard = id; + reg_write(vc->dev, SDT[vc->ch], val); + + val = reg_read(vc->dev, VIDEO_CONTROL1); + if (id & V4L2_STD_525_60) + val &= ~(1 << (SYS_MODE_DMA_SHIFT + vc->ch)); + else + val |= (1 << (SYS_MODE_DMA_SHIFT + vc->ch)); + reg_write(vc->dev, VIDEO_CONTROL1, val); + + return 0; +} + +static int tw686x_s_std(struct file *file, void *priv, v4l2_std_id id) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct v4l2_format f; + int ret; + + if (vc->video_standard == id) + return 0; + + if (vb2_is_busy(&vc->vidq)) + return -EBUSY; + + ret = tw686x_set_standard(vc, id); + if (ret) + return ret; + /* + * Adjust format after V4L2_STD_525_60/V4L2_STD_625_50 change, + * calling g_fmt and s_fmt will sanitize the height + * according to the standard. + */ + tw686x_g_fmt_vid_cap(file, priv, &f); + tw686x_s_fmt_vid_cap(file, priv, &f); + + /* + * Frame decimation depends on the chosen standard, + * so reset it to the current value. + */ + tw686x_set_framerate(vc, vc->fps); + return 0; +} + +static int tw686x_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; + unsigned int old_std, detected_std = 0; + unsigned long end; + + if (vb2_is_streaming(&vc->vidq)) + return -EBUSY; + + /* Enable and start standard detection */ + old_std = reg_read(dev, SDT[vc->ch]); + reg_write(dev, SDT[vc->ch], 0x7); + reg_write(dev, SDT_EN[vc->ch], 0xff); + + end = jiffies + msecs_to_jiffies(500); + while (time_is_after_jiffies(end)) { + + detected_std = reg_read(dev, SDT[vc->ch]); + if (!(detected_std & BIT(7))) + break; + msleep(100); + } + reg_write(dev, SDT[vc->ch], old_std); + + /* Exit if still busy */ + if (detected_std & BIT(7)) + return 0; + + detected_std = (detected_std >> 4) & 0x7; + switch (detected_std) { + case TW686X_STD_NTSC_M: + *std &= V4L2_STD_NTSC; + break; + case TW686X_STD_NTSC_443: + *std &= V4L2_STD_NTSC_443; + break; + case TW686X_STD_PAL_M: + *std &= V4L2_STD_PAL_M; + break; + case TW686X_STD_PAL_60: + *std &= V4L2_STD_PAL_60; + break; + case TW686X_STD_PAL: + *std &= V4L2_STD_PAL; + break; + case TW686X_STD_PAL_CN: + *std &= V4L2_STD_PAL_Nc; + break; + case TW686X_STD_SECAM: + *std &= V4L2_STD_SECAM; + break; + default: + *std = 0; + } + return 0; +} + +static int tw686x_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + + *id = vc->video_standard; + return 0; +} + +static int tw686x_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + + if (fsize->index) + return -EINVAL; + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.max_width = TW686X_VIDEO_WIDTH; + fsize->stepwise.min_width = fsize->stepwise.max_width / 2; + fsize->stepwise.step_width = fsize->stepwise.min_width; + fsize->stepwise.max_height = TW686X_VIDEO_HEIGHT(vc->video_standard); + fsize->stepwise.min_height = fsize->stepwise.max_height / 2; + fsize->stepwise.step_height = fsize->stepwise.min_height; + return 0; +} + +static int tw686x_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *ival) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + int max_fps = TW686X_MAX_FPS(vc->video_standard); + int max_rates = DIV_ROUND_UP(max_fps, 2); + + if (ival->index >= max_rates) + return -EINVAL; + + ival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + ival->discrete.numerator = 1; + if (ival->index < (max_rates - 1)) + ival->discrete.denominator = (ival->index + 1) * 2; + else + ival->discrete.denominator = max_fps; + return 0; +} + +static int tw686x_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct v4l2_captureparm *cp = &sp->parm.capture; + + if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + sp->parm.capture.readbuffers = 3; + + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = 1; + cp->timeperframe.denominator = vc->fps; + return 0; +} + +static int tw686x_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + struct v4l2_captureparm *cp = &sp->parm.capture; + unsigned int denominator = cp->timeperframe.denominator; + unsigned int numerator = cp->timeperframe.numerator; + unsigned int fps; + + if (vb2_is_busy(&vc->vidq)) + return -EBUSY; + + fps = (!numerator || !denominator) ? 0 : denominator / numerator; + if (vc->fps != fps) + tw686x_set_framerate(vc, fps); + return tw686x_g_parm(file, priv, sp); +} + +static int tw686x_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(formats)) + return -EINVAL; + f->pixelformat = formats[f->index].fourcc; + return 0; +} + +static void tw686x_set_input(struct tw686x_video_channel *vc, unsigned int i) +{ + u32 val; + + vc->input = i; + + val = reg_read(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch]); + val &= ~(0x3 << 30); + val |= i << 30; + reg_write(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch], val); +} + +static int tw686x_s_input(struct file *file, void *priv, unsigned int i) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + + if (i >= TW686X_INPUTS_PER_CH) + return -EINVAL; + if (i == vc->input) + return 0; + /* + * Not sure we are able to support on the fly input change + */ + if (vb2_is_busy(&vc->vidq)) + return -EBUSY; + + tw686x_set_input(vc, i); + return 0; +} + +static int tw686x_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + + *i = vc->input; + return 0; +} + +static int tw686x_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct tw686x_video_channel *vc = video_drvdata(file); + unsigned int vidstat; + + if (i->index >= TW686X_INPUTS_PER_CH) + return -EINVAL; + + snprintf(i->name, sizeof(i->name), "Composite%d", i->index); + i->type = V4L2_INPUT_TYPE_CAMERA; + i->std = vc->device->tvnorms; + i->capabilities = V4L2_IN_CAP_STD; + + vidstat = reg_read(vc->dev, VIDSTAT[vc->ch]); + i->status = 0; + if (vidstat & TW686X_VIDSTAT_VDLOSS) + i->status |= V4L2_IN_ST_NO_SIGNAL; + if (!(vidstat & TW686X_VIDSTAT_HLOCK)) + i->status |= V4L2_IN_ST_NO_H_LOCK; + + return 0; +} + +static const struct v4l2_file_operations tw686x_video_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .unlocked_ioctl = video_ioctl2, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, +}; + +static const struct v4l2_ioctl_ops tw686x_video_ioctl_ops = { + .vidioc_querycap = tw686x_querycap, + .vidioc_g_fmt_vid_cap = tw686x_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = tw686x_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = tw686x_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = tw686x_try_fmt_vid_cap, + + .vidioc_querystd = tw686x_querystd, + .vidioc_g_std = tw686x_g_std, + .vidioc_s_std = tw686x_s_std, + + .vidioc_g_parm = tw686x_g_parm, + .vidioc_s_parm = tw686x_s_parm, + .vidioc_enum_framesizes = tw686x_enum_framesizes, + .vidioc_enum_frameintervals = tw686x_enum_frameintervals, + + .vidioc_enum_input = tw686x_enum_input, + .vidioc_g_input = tw686x_g_input, + .vidioc_s_input = tw686x_s_input, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +void tw686x_video_irq(struct tw686x_dev *dev, unsigned long requests, + unsigned int pb_status, unsigned int fifo_status, + unsigned int *reset_ch) +{ + struct tw686x_video_channel *vc; + unsigned long flags; + unsigned int ch, pb; + + for_each_set_bit(ch, &requests, max_channels(dev)) { + vc = &dev->video_channels[ch]; + + /* + * This can either be a blue frame (with signal-lost bit set) + * or a good frame (with signal-lost bit clear). If we have just + * got signal, then this channel needs resetting. + */ + if (vc->no_signal && !(fifo_status & BIT(ch))) { + v4l2_printk(KERN_DEBUG, &dev->v4l2_dev, + "video%d: signal recovered\n", vc->num); + vc->no_signal = false; + *reset_ch |= BIT(ch); + vc->pb = 0; + continue; + } + vc->no_signal = !!(fifo_status & BIT(ch)); + + /* Check FIFO errors only if there's signal */ + if (!vc->no_signal) { + u32 fifo_ov, fifo_bad; + + fifo_ov = (fifo_status >> 24) & BIT(ch); + fifo_bad = (fifo_status >> 16) & BIT(ch); + if (fifo_ov || fifo_bad) { + /* Mark this channel for reset */ + v4l2_printk(KERN_DEBUG, &dev->v4l2_dev, + "video%d: FIFO error\n", vc->num); + *reset_ch |= BIT(ch); + vc->pb = 0; + continue; + } + } + + pb = !!(pb_status & BIT(ch)); + if (vc->pb != pb) { + /* Mark this channel for reset */ + v4l2_printk(KERN_DEBUG, &dev->v4l2_dev, + "video%d: unexpected p-b buffer!\n", + vc->num); + *reset_ch |= BIT(ch); + vc->pb = 0; + continue; + } + + spin_lock_irqsave(&vc->qlock, flags); + tw686x_buf_done(vc, pb); + dev->dma_ops->buf_refill(vc, pb); + spin_unlock_irqrestore(&vc->qlock, flags); + } +} + +void tw686x_video_free(struct tw686x_dev *dev) +{ + unsigned int ch, pb; + + for (ch = 0; ch < max_channels(dev); ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + + video_unregister_device(vc->device); + + if (dev->dma_ops->free) + for (pb = 0; pb < 2; pb++) + dev->dma_ops->free(vc, pb); + } +} + +int tw686x_video_init(struct tw686x_dev *dev) +{ + unsigned int ch, val; + int err; + + if (dev->dma_mode == TW686X_DMA_MODE_MEMCPY) + dev->dma_ops = &memcpy_dma_ops; + else if (dev->dma_mode == TW686X_DMA_MODE_CONTIG) + dev->dma_ops = &contig_dma_ops; + else if (dev->dma_mode == TW686X_DMA_MODE_SG) + dev->dma_ops = &sg_dma_ops; + else + return -EINVAL; + + err = v4l2_device_register(&dev->pci_dev->dev, &dev->v4l2_dev); + if (err) + return err; + + if (dev->dma_ops->setup) { + err = dev->dma_ops->setup(dev); + if (err) + return err; + } + + /* Initialize vc->dev and vc->ch for the error path */ + for (ch = 0; ch < max_channels(dev); ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + + vc->dev = dev; + vc->ch = ch; + } + + for (ch = 0; ch < max_channels(dev); ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + struct video_device *vdev; + + mutex_init(&vc->vb_mutex); + spin_lock_init(&vc->qlock); + INIT_LIST_HEAD(&vc->vidq_queued); + + /* default settings */ + err = tw686x_set_standard(vc, V4L2_STD_NTSC); + if (err) + goto error; + + err = tw686x_set_format(vc, formats[0].fourcc, + TW686X_VIDEO_WIDTH, + TW686X_VIDEO_HEIGHT(vc->video_standard), + true); + if (err) + goto error; + + tw686x_set_input(vc, 0); + tw686x_set_framerate(vc, 30); + reg_write(dev, VDELAY_LO[ch], 0x14); + reg_write(dev, HACTIVE_LO[ch], 0xd0); + reg_write(dev, VIDEO_SIZE[ch], 0); + + vc->vidq.io_modes = VB2_READ | VB2_MMAP | VB2_DMABUF; + vc->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vc->vidq.drv_priv = vc; + vc->vidq.buf_struct_size = sizeof(struct tw686x_v4l2_buf); + vc->vidq.ops = &tw686x_video_qops; + vc->vidq.mem_ops = dev->dma_ops->mem_ops; + vc->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vc->vidq.min_buffers_needed = 2; + vc->vidq.lock = &vc->vb_mutex; + vc->vidq.gfp_flags = dev->dma_mode != TW686X_DMA_MODE_MEMCPY ? + GFP_DMA32 : 0; + vc->vidq.dev = &dev->pci_dev->dev; + + err = vb2_queue_init(&vc->vidq); + if (err) { + v4l2_err(&dev->v4l2_dev, + "dma%d: cannot init vb2 queue\n", ch); + goto error; + } + + err = v4l2_ctrl_handler_init(&vc->ctrl_handler, 4); + if (err) { + v4l2_err(&dev->v4l2_dev, + "dma%d: cannot init ctrl handler\n", ch); + goto error; + } + v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 100); + v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(&vc->ctrl_handler, &ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + err = vc->ctrl_handler.error; + if (err) + goto error; + + err = v4l2_ctrl_handler_setup(&vc->ctrl_handler); + if (err) + goto error; + + vdev = video_device_alloc(); + if (!vdev) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to allocate device\n", ch); + err = -ENOMEM; + goto error; + } + + snprintf(vdev->name, sizeof(vdev->name), "%s video", dev->name); + vdev->fops = &tw686x_video_fops; + vdev->ioctl_ops = &tw686x_video_ioctl_ops; + vdev->release = video_device_release; + vdev->v4l2_dev = &dev->v4l2_dev; + vdev->queue = &vc->vidq; + vdev->tvnorms = V4L2_STD_525_60 | V4L2_STD_625_50; + vdev->minor = -1; + vdev->lock = &vc->vb_mutex; + vdev->ctrl_handler = &vc->ctrl_handler; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; + vc->device = vdev; + video_set_drvdata(vdev, vc); + + err = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (err < 0) { + video_device_release(vdev); + goto error; + } + vc->num = vdev->num; + } + + val = TW686X_DEF_PHASE_REF; + for (ch = 0; ch < max_channels(dev); ch++) + val |= dev->dma_ops->hw_dma_mode << (16 + ch * 2); + reg_write(dev, PHASE_REF, val); + + reg_write(dev, MISC2[0], 0xe7); + reg_write(dev, VCTRL1[0], 0xcc); + reg_write(dev, LOOP[0], 0xa5); + if (max_channels(dev) > 4) { + reg_write(dev, VCTRL1[1], 0xcc); + reg_write(dev, LOOP[1], 0xa5); + reg_write(dev, MISC2[1], 0xe7); + } + return 0; + +error: + tw686x_video_free(dev); + return err; +} diff --git a/drivers/media/pci/tw686x/tw686x.h b/drivers/media/pci/tw686x/tw686x.h new file mode 100644 index 000000000..21a989950 --- /dev/null +++ b/drivers/media/pci/tw686x/tw686x.h @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2015 VanguardiaSur - www.vanguardiasur.com.ar + * + * Copyright (C) 2015 Industrial Research Institute for Automation + * and Measurements PIAP + * Written by Krzysztof Ha?asa + */ + +#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_AUDIO_PAGE_MAX 16 +#define TW686X_AUDIO_PERIODS_MIN 2 +#define TW686X_AUDIO_PERIODS_MAX TW686X_AUDIO_PAGE_MAX + +#define TW686X_DMA_MODE_MEMCPY 0 +#define TW686X_DMA_MODE_CONTIG 1 +#define TW686X_DMA_MODE_SG 2 + +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_sg_desc { + /* 3 MSBits for flags, 13 LSBits for length */ + __le32 flags_length; + __le32 phys; +}; + +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 tw686x_sg_desc *sg_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_dma_ops { + int (*setup)(struct tw686x_dev *dev); + int (*alloc)(struct tw686x_video_channel *vc, unsigned int pb); + void (*free)(struct tw686x_video_channel *vc, unsigned int pb); + void (*buf_refill)(struct tw686x_video_channel *vc, unsigned int pb); + const struct vb2_mem_ops *mem_ops; + enum v4l2_field field; + u32 hw_dma_mode; +}; + +/* struct tw686x_dev - global device status */ +struct tw686x_dev { + /* + * spinlock controlling access to the shared device registers + * (DMA enable/disable) + */ + spinlock_t lock; + + struct v4l2_device v4l2_dev; + struct snd_card *snd_card; + + char name[32]; + unsigned int type; + unsigned int dma_mode; + struct pci_dev *pci_dev; + __u32 __iomem *mmio; + + const struct tw686x_dma_ops *dma_ops; + struct tw686x_video_channel *video_channels; + struct tw686x_audio_channel *audio_channels; + + /* Per-device audio parameters */ + int audio_rate; + int period_size; + int audio_enabled; + + 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 */ +} + +static inline unsigned is_second_gen(struct tw686x_dev *dev) +{ + /* each channel has its own DMA SG table */ + return dev->type & TYPE_SECOND_GEN; +} + +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); |