alistair23-linux/drivers/media/pci/tw5864/tw5864-core.c

351 lines
10 KiB
C
Raw Normal View History

treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 157 Based on 3 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version [author] [graeme] [gregory] [gg]@[slimlogic] [co] [uk] [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] [based] [on] [twl6030]_[usb] [c] [author] [hema] [hk] [hemahk]@[ti] [com] this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1105 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.202006027@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-05-27 00:55:06 -06:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* TW5864 driver - core functions
*
* Copyright (C) 2016 Bluecherry, LLC <maintainers@bluecherrydvr.com>
*/
#include <linux/init.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/kmod.h>
#include <linux/sound.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/pm.h>
#include <linux/pci_ids.h>
#include <linux/jiffies.h>
#include <asm/dma.h>
#include <media/v4l2-dev.h>
#include "tw5864.h"
#include "tw5864-reg.h"
MODULE_DESCRIPTION("V4L2 driver module for tw5864-based multimedia capture & encoding devices");
MODULE_AUTHOR("Bluecherry Maintainers <maintainers@bluecherrydvr.com>");
MODULE_AUTHOR("Andrey Utkin <andrey.utkin@corp.bluecherry.net>");
MODULE_LICENSE("GPL");
/*
* BEWARE OF KNOWN ISSUES WITH VIDEO QUALITY
*
* This driver was developed by Bluecherry LLC by deducing behaviour of
* original manufacturer's driver, from both source code and execution traces.
* It is known that there are some artifacts on output video with this driver:
* - on all known hardware samples: random pixels of wrong color (mostly
* white, red or blue) appearing and disappearing on sequences of P-frames;
* - on some hardware samples (known with H.264 core version e006:2800):
* total madness on P-frames: blocks of wrong luminance; blocks of wrong
* colors "creeping" across the picture.
* There is a workaround for both issues: avoid P-frames by setting GOP size
* to 1. To do that, run this command on device files created by this driver:
*
* v4l2-ctl --device /dev/videoX --set-ctrl=video_gop_size=1
*
* These issues are not decoding errors; all produced H.264 streams are decoded
* properly. Streams without P-frames don't have these artifacts so it's not
* analog-to-digital conversion issues nor internal memory errors; we conclude
* it's internal H.264 encoder issues.
* We cannot even check the original driver's behaviour because it has never
* worked properly at all in our development environment. So these issues may
* be actually related to firmware or hardware. However it may be that there's
* just some more register settings missing in the driver which would please
* the hardware.
* Manufacturer didn't help much on our inquiries, but feel free to disturb
* again the support of Intersil (owner of former Techwell).
*/
/* take first free /dev/videoX indexes by default */
static unsigned int video_nr[] = {[0 ... (TW5864_INPUTS - 1)] = -1 };
module_param_array(video_nr, int, NULL, 0444);
MODULE_PARM_DESC(video_nr, "video devices numbers array");
/*
* Please add any new PCI IDs to: http://pci-ids.ucw.cz. This keeps
* the PCI ID database up to date. Note that the entries must be
* added under vendor 0x1797 (Techwell Inc.) as subsystem IDs.
*/
static const struct pci_device_id tw5864_pci_tbl[] = {
{PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_TECHWELL_5864)},
{0,}
};
void tw5864_irqmask_apply(struct tw5864_dev *dev)
{
tw_writel(TW5864_INTR_ENABLE_L, dev->irqmask & 0xffff);
tw_writel(TW5864_INTR_ENABLE_H, (dev->irqmask >> 16));
}
static void tw5864_interrupts_disable(struct tw5864_dev *dev)
{
unsigned long flags;
spin_lock_irqsave(&dev->slock, flags);
dev->irqmask = 0;
tw5864_irqmask_apply(dev);
spin_unlock_irqrestore(&dev->slock, flags);
}
static void tw5864_timer_isr(struct tw5864_dev *dev);
static void tw5864_h264_isr(struct tw5864_dev *dev);
static irqreturn_t tw5864_isr(int irq, void *dev_id)
{
struct tw5864_dev *dev = dev_id;
u32 status;
status = tw_readl(TW5864_INTR_STATUS_L) |
tw_readl(TW5864_INTR_STATUS_H) << 16;
if (!status)
return IRQ_NONE;
tw_writel(TW5864_INTR_CLR_L, 0xffff);
tw_writel(TW5864_INTR_CLR_H, 0xffff);
if (status & TW5864_INTR_VLC_DONE)
tw5864_h264_isr(dev);
if (status & TW5864_INTR_TIMER)
tw5864_timer_isr(dev);
if (!(status & (TW5864_INTR_TIMER | TW5864_INTR_VLC_DONE))) {
dev_dbg(&dev->pci->dev, "Unknown interrupt, status 0x%08X\n",
status);
}
return IRQ_HANDLED;
}
static void tw5864_h264_isr(struct tw5864_dev *dev)
{
int channel = tw_readl(TW5864_DSP) & TW5864_DSP_ENC_CHN;
struct tw5864_input *input = &dev->inputs[channel];
int cur_frame_index, next_frame_index;
struct tw5864_h264_frame *cur_frame, *next_frame;
unsigned long flags;
spin_lock_irqsave(&dev->slock, flags);
cur_frame_index = dev->h264_buf_w_index;
next_frame_index = (cur_frame_index + 1) % H264_BUF_CNT;
cur_frame = &dev->h264_buf[cur_frame_index];
next_frame = &dev->h264_buf[next_frame_index];
if (next_frame_index != dev->h264_buf_r_index) {
cur_frame->vlc_len = tw_readl(TW5864_VLC_LENGTH) << 2;
cur_frame->checksum = tw_readl(TW5864_VLC_CRC_REG);
cur_frame->input = input;
cur_frame->timestamp = ktime_get_ns();
cur_frame->seqno = input->frame_seqno;
cur_frame->gop_seqno = input->frame_gop_seqno;
dev->h264_buf_w_index = next_frame_index;
tasklet_schedule(&dev->tasklet);
cur_frame = next_frame;
spin_lock(&input->slock);
input->frame_seqno++;
input->frame_gop_seqno++;
if (input->frame_gop_seqno >= input->gop)
input->frame_gop_seqno = 0;
spin_unlock(&input->slock);
} else {
dev_err(&dev->pci->dev,
"Skipped frame on input %d because all buffers busy\n",
channel);
}
dev->encoder_busy = 0;
spin_unlock_irqrestore(&dev->slock, flags);
tw_writel(TW5864_VLC_STREAM_BASE_ADDR, cur_frame->vlc.dma_addr);
tw_writel(TW5864_MV_STREAM_BASE_ADDR, cur_frame->mv.dma_addr);
/* Additional ack for this interrupt */
tw_writel(TW5864_VLC_DSP_INTR, 0x00000001);
tw_writel(TW5864_PCI_INTR_STATUS, TW5864_VLC_DONE_INTR);
}
static void tw5864_input_deadline_update(struct tw5864_input *input)
{
input->new_frame_deadline = jiffies + msecs_to_jiffies(1000);
}
static void tw5864_timer_isr(struct tw5864_dev *dev)
{
unsigned long flags;
int i;
int encoder_busy;
/* Additional ack for this interrupt */
tw_writel(TW5864_PCI_INTR_STATUS, TW5864_TIMER_INTR);
spin_lock_irqsave(&dev->slock, flags);
encoder_busy = dev->encoder_busy;
spin_unlock_irqrestore(&dev->slock, flags);
if (encoder_busy)
return;
/*
* Traversing inputs in round-robin fashion, starting from next to the
* last processed one
*/
for (i = 0; i < TW5864_INPUTS; i++) {
int next_input = (i + dev->next_input) % TW5864_INPUTS;
struct tw5864_input *input = &dev->inputs[next_input];
int raw_buf_id; /* id of internal buf with last raw frame */
spin_lock_irqsave(&input->slock, flags);
if (!input->enabled)
goto next;
/* Check if new raw frame is available */
raw_buf_id = tw_mask_shift_readl(TW5864_SENIF_ORG_FRM_PTR1, 0x3,
2 * input->nr);
if (input->buf_id != raw_buf_id) {
input->buf_id = raw_buf_id;
tw5864_input_deadline_update(input);
spin_unlock_irqrestore(&input->slock, flags);
spin_lock_irqsave(&dev->slock, flags);
dev->encoder_busy = 1;
dev->next_input = (next_input + 1) % TW5864_INPUTS;
spin_unlock_irqrestore(&dev->slock, flags);
tw5864_request_encoded_frame(input);
break;
}
/* No new raw frame; check if channel is stuck */
if (time_is_after_jiffies(input->new_frame_deadline)) {
/* If stuck, request new raw frames again */
tw_mask_shift_writel(TW5864_ENC_BUF_PTR_REC1, 0x3,
2 * input->nr, input->buf_id + 3);
tw5864_input_deadline_update(input);
}
next:
spin_unlock_irqrestore(&input->slock, flags);
}
}
static int tw5864_initdev(struct pci_dev *pci_dev,
const struct pci_device_id *pci_id)
{
struct tw5864_dev *dev;
int err;
dev = devm_kzalloc(&pci_dev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
snprintf(dev->name, sizeof(dev->name), "tw5864:%s", pci_name(pci_dev));
err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev);
if (err)
return err;
/* pci init */
dev->pci = pci_dev;
err = pci_enable_device(pci_dev);
if (err) {
dev_err(&dev->pci->dev, "pci_enable_device() failed\n");
goto unreg_v4l2;
}
pci_set_master(pci_dev);
err = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32));
if (err) {
dev_err(&dev->pci->dev, "32 bit PCI DMA is not supported\n");
goto disable_pci;
}
/* get mmio */
err = pci_request_regions(pci_dev, dev->name);
if (err) {
dev_err(&dev->pci->dev, "Cannot request regions for MMIO\n");
goto disable_pci;
}
dev->mmio = pci_ioremap_bar(pci_dev, 0);
if (!dev->mmio) {
err = -EIO;
dev_err(&dev->pci->dev, "can't ioremap() MMIO memory\n");
goto release_mmio;
}
spin_lock_init(&dev->slock);
dev_info(&pci_dev->dev, "TW5864 hardware version: %04x\n",
tw_readl(TW5864_HW_VERSION));
dev_info(&pci_dev->dev, "TW5864 H.264 core version: %04x:%04x\n",
tw_readl(TW5864_H264REV),
tw_readl(TW5864_UNDECLARED_H264REV_PART2));
err = tw5864_video_init(dev, video_nr);
if (err)
goto unmap_mmio;
/* get irq */
err = devm_request_irq(&pci_dev->dev, pci_dev->irq, tw5864_isr,
IRQF_SHARED, "tw5864", dev);
if (err < 0) {
dev_err(&dev->pci->dev, "can't get IRQ %d\n", pci_dev->irq);
goto fini_video;
}
dev_info(&pci_dev->dev, "Note: there are known video quality issues. For details\n");
dev_info(&pci_dev->dev, "see the comment in drivers/media/pci/tw5864/tw5864-core.c.\n");
return 0;
fini_video:
tw5864_video_fini(dev);
unmap_mmio:
iounmap(dev->mmio);
release_mmio:
pci_release_regions(pci_dev);
disable_pci:
pci_disable_device(pci_dev);
unreg_v4l2:
v4l2_device_unregister(&dev->v4l2_dev);
return err;
}
static void tw5864_finidev(struct pci_dev *pci_dev)
{
struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
struct tw5864_dev *dev =
container_of(v4l2_dev, struct tw5864_dev, v4l2_dev);
/* shutdown subsystems */
tw5864_interrupts_disable(dev);
/* unregister */
tw5864_video_fini(dev);
/* release resources */
iounmap(dev->mmio);
release_mem_region(pci_resource_start(pci_dev, 0),
pci_resource_len(pci_dev, 0));
v4l2_device_unregister(&dev->v4l2_dev);
devm_kfree(&pci_dev->dev, dev);
}
static struct pci_driver tw5864_pci_driver = {
.name = "tw5864",
.id_table = tw5864_pci_tbl,
.probe = tw5864_initdev,
.remove = tw5864_finidev,
};
module_pci_driver(tw5864_pci_driver);