mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
media: pci: add AVMatrix HWS capture driver
Add an in-tree AVMatrix HWS PCIe capture driver. The driver supports up to four HDMI inputs and exposes the video capture path through V4L2 with vb2-dma-contig streaming, DV timings, and per-input controls. Audio support is intentionally omitted from this submission. This patch also adds the MAINTAINERS entry for the new driver. This driver is derived from a GPL out-of-tree driver. Changes since v6: - v6 accidently contained legacy history, resubmitting with latest - remove an unused mode-change label reported by W=1 Changes since v5: - keep queue_setup() and alloc_sizeimage on the logical sizeimage value - drop the dead queue_setup() fallback that rebuilt pix.sizeimage on the fly - clarify that hws_calc_sizeimage() models the packed-YUYV-only path - add Assisted-by attribution for Codex Changes since v4: - replace plain 64-bit elapsed-time divisions in debug logging with div_u64() so i386 module builds do not emit __udivdi3 references Changes since v3: - fold the MAINTAINERS update into this patch so per-patch CI sees the new file pattern - wrap the validation text for checkpatch Changes since v2: - keep scratch DMA allocation on a single probe-owned path - avoid double-freeing V4L2 control handlers on register unwind - drop the extra per-node resolution sysfs ABI - turn live geometry changes into explicit SOURCE_CHANGE renegotiation - report live DV timings and reject attempts to retime a live source - stop advertising RESOLUTION source changes for fps-only updates - keep live fps state across harmless S_FMT restarts - stop exposing an unvalidated DV RX power-present signal - clean the imported sources for checkpatch and W=1 builds Validation: - build-tested with W=1 against a local kernel build tree - compiled the driver with ARCH=i386 allmodconfig and verified the resulting hws_pci.o, hws_video.o, and hws.o do not reference __udivdi3 - v4l2-compliance 1.33.0-5459 from v4l-utils commit 4a0d2c3b4f523406cb9a6f4c541ef14f72f19f3d on /dev/video2: 48 tests succeeded, 0 failed, 1 warning DV_RX_POWER_PRESENT is intentionally left unsupported in this revision because current hardware evidence does not expose a validated receiver-side power-detect signal distinct from active video presence. Reported-by: kernel test robot <lkp@intel.com> Closes: https://lore.kernel.org/oe-kbuild-all/202604020522.z22eZuW8-lkp@intel.com/ Assisted-by: Codex:gpt-5.5 Signed-off-by: Ben Hoff <hoff.benjamin.k@gmail.com> Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
This commit is contained in:
@@ -4317,6 +4317,12 @@ S: Maintained
|
||||
F: Documentation/devicetree/bindings/iio/adc/avia-hx711.yaml
|
||||
F: drivers/iio/adc/hx711.c
|
||||
|
||||
AVMATRIX HWS CAPTURE DRIVER
|
||||
M: Ben Hoff <hoff.benjamin.k@gmail.com>
|
||||
L: linux-media@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/media/pci/hws/
|
||||
|
||||
AWINIC AW99706 WLED BACKLIGHT DRIVER
|
||||
M: Junjie Cao <caojunjie650@gmail.com>
|
||||
S: Maintained
|
||||
|
||||
@@ -13,6 +13,7 @@ if MEDIA_PCI_SUPPORT
|
||||
if MEDIA_CAMERA_SUPPORT
|
||||
comment "Media capture support"
|
||||
|
||||
source "drivers/media/pci/hws/Kconfig"
|
||||
source "drivers/media/pci/mgb4/Kconfig"
|
||||
source "drivers/media/pci/solo6x10/Kconfig"
|
||||
source "drivers/media/pci/tw5864/Kconfig"
|
||||
|
||||
@@ -29,6 +29,7 @@ obj-$(CONFIG_VIDEO_CX23885) += cx23885/
|
||||
obj-$(CONFIG_VIDEO_CX25821) += cx25821/
|
||||
obj-$(CONFIG_VIDEO_CX88) += cx88/
|
||||
obj-$(CONFIG_VIDEO_DT3155) += dt3155/
|
||||
obj-$(CONFIG_VIDEO_HWS) += hws/
|
||||
obj-$(CONFIG_VIDEO_IVTV) += ivtv/
|
||||
obj-$(CONFIG_VIDEO_MGB4) += mgb4/
|
||||
obj-$(CONFIG_VIDEO_SAA7134) += saa7134/
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config VIDEO_HWS
|
||||
tristate "AVMatrix HWS PCIe capture devices"
|
||||
depends on PCI && VIDEO_DEV
|
||||
select VIDEOBUF2_DMA_CONTIG
|
||||
help
|
||||
Enable support for AVMatrix HWS series multi-channel PCIe capture
|
||||
devices that provide HDMI video capture.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be named hws.
|
||||
@@ -0,0 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
hws-objs := hws_pci.o hws_irq.o hws_video.o hws_v4l2_ioctl.o
|
||||
|
||||
obj-$(CONFIG_VIDEO_HWS) += hws.o
|
||||
@@ -0,0 +1,173 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef HWS_PCIE_H
|
||||
#define HWS_PCIE_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/atomic.h>
|
||||
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-device.h>
|
||||
#include <media/v4l2-dv-timings.h>
|
||||
#include <media/videobuf2-dma-sg.h>
|
||||
|
||||
#include "hws_reg.h"
|
||||
|
||||
struct hwsmem_param {
|
||||
u32 index;
|
||||
u32 type;
|
||||
u32 status;
|
||||
};
|
||||
|
||||
struct hws_pix_state {
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 fourcc; /* V4L2_PIX_FMT_* (YUYV only here) */
|
||||
u32 bytesperline; /* stride */
|
||||
u32 sizeimage; /* full frame */
|
||||
enum v4l2_field field; /* V4L2_FIELD_NONE or INTERLACED */
|
||||
enum v4l2_colorspace colorspace; /* e.g., REC709 */
|
||||
enum v4l2_ycbcr_encoding ycbcr_enc; /* V4L2_YCBCR_ENC_DEFAULT */
|
||||
enum v4l2_quantization quantization; /* V4L2_QUANTIZATION_LIM_RANGE */
|
||||
enum v4l2_xfer_func xfer_func; /* V4L2_XFER_FUNC_DEFAULT */
|
||||
bool interlaced; /* cached hardware state */
|
||||
u32 half_size; /* hardware half-frame size */
|
||||
};
|
||||
|
||||
#define UNSET (-1U)
|
||||
|
||||
struct hws_pcie_dev;
|
||||
struct hws_adapter;
|
||||
struct hws_video;
|
||||
|
||||
struct hwsvideo_buffer {
|
||||
struct vb2_v4l2_buffer vb;
|
||||
struct list_head list;
|
||||
int slot;
|
||||
};
|
||||
|
||||
struct hws_video {
|
||||
/* Linkage */
|
||||
struct hws_pcie_dev *parent;
|
||||
struct video_device *video_device;
|
||||
|
||||
struct vb2_queue buffer_queue;
|
||||
struct list_head capture_queue;
|
||||
struct hwsvideo_buffer *active;
|
||||
struct hwsvideo_buffer *next_prepared;
|
||||
|
||||
/* Locking */
|
||||
struct mutex state_lock;
|
||||
spinlock_t irq_lock; /* Protects capture_queue and active buffers. */
|
||||
|
||||
/* Indices */
|
||||
int channel_index;
|
||||
|
||||
/* Color controls */
|
||||
int current_brightness;
|
||||
int current_contrast;
|
||||
int current_saturation;
|
||||
int current_hue;
|
||||
|
||||
/* V4L2 controls */
|
||||
struct v4l2_ctrl_handler control_handler;
|
||||
struct v4l2_ctrl *ctrl_brightness;
|
||||
struct v4l2_ctrl *ctrl_contrast;
|
||||
struct v4l2_ctrl *ctrl_saturation;
|
||||
struct v4l2_ctrl *ctrl_hue;
|
||||
|
||||
/* Capture queue status */
|
||||
struct hws_pix_state pix;
|
||||
struct v4l2_dv_timings cur_dv_timings; /* last configured/notified DV timings */
|
||||
u32 current_fps; /* Hz, updated by mode changes, not by read-only queries */
|
||||
|
||||
/* Per-channel capture state */
|
||||
bool cap_active;
|
||||
bool stop_requested;
|
||||
u8 last_buf_half_toggle;
|
||||
bool half_seen;
|
||||
atomic_t sequence_number;
|
||||
u32 queued_count;
|
||||
|
||||
/* Timeout and error handling */
|
||||
u32 timeout_count;
|
||||
u32 error_count;
|
||||
|
||||
bool window_valid;
|
||||
u32 last_dma_hi;
|
||||
u32 last_dma_page;
|
||||
u32 last_pci_addr;
|
||||
u32 last_half16;
|
||||
|
||||
/* Misc counters */
|
||||
int signal_loss_cnt;
|
||||
};
|
||||
|
||||
static inline void hws_set_current_dv_timings(struct hws_video *vid,
|
||||
u32 width, u32 height,
|
||||
bool interlaced)
|
||||
{
|
||||
if (!vid)
|
||||
return;
|
||||
|
||||
vid->cur_dv_timings = (struct v4l2_dv_timings) {
|
||||
.type = V4L2_DV_BT_656_1120,
|
||||
.bt = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
.interlaced = interlaced,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
struct hws_scratch_dma {
|
||||
void *cpu;
|
||||
dma_addr_t dma;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct hws_pcie_dev {
|
||||
/* Core objects */
|
||||
struct pci_dev *pdev;
|
||||
struct hws_video video[MAX_VID_CHANNELS];
|
||||
|
||||
/* BAR and workqueues */
|
||||
void __iomem *bar0_base;
|
||||
|
||||
/* Device identity and capabilities */
|
||||
u16 vendor_id;
|
||||
u16 device_id;
|
||||
u16 device_ver;
|
||||
u16 hw_ver;
|
||||
u32 sub_ver;
|
||||
u32 port_id;
|
||||
/* Tri-state support flag used by set_video_format_size(). */
|
||||
u32 support_yv12;
|
||||
u32 max_hw_video_buf_sz;
|
||||
u8 max_channels;
|
||||
u8 cur_max_video_ch;
|
||||
bool start_run;
|
||||
|
||||
bool buf_allocated;
|
||||
|
||||
/* V4L2 framework objects */
|
||||
struct v4l2_device v4l2_device;
|
||||
|
||||
/* Kernel thread */
|
||||
struct task_struct *main_task;
|
||||
struct hws_scratch_dma scratch_vid[MAX_VID_CHANNELS];
|
||||
|
||||
bool suspended;
|
||||
int irq;
|
||||
|
||||
/* Error flags */
|
||||
int pci_lost;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,269 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#include <media/videobuf2-dma-contig.h>
|
||||
|
||||
#include "hws_irq.h"
|
||||
#include "hws_reg.h"
|
||||
#include "hws_video.h"
|
||||
#include "hws.h"
|
||||
|
||||
#define MAX_INT_LOOPS 100
|
||||
|
||||
static bool hws_toggle_debug;
|
||||
module_param_named(toggle_debug, hws_toggle_debug, bool, 0644);
|
||||
MODULE_PARM_DESC(toggle_debug,
|
||||
"Read toggle registers in IRQ handler for debug logging");
|
||||
|
||||
static int hws_arm_next(struct hws_pcie_dev *hws, u32 ch)
|
||||
{
|
||||
struct hws_video *v = &hws->video[ch];
|
||||
unsigned long flags;
|
||||
struct hwsvideo_buffer *buf;
|
||||
|
||||
dev_dbg(&hws->pdev->dev,
|
||||
"arm_next(ch=%u): stop=%d cap=%d queued=%d\n",
|
||||
ch, READ_ONCE(v->stop_requested), READ_ONCE(v->cap_active),
|
||||
!list_empty(&v->capture_queue));
|
||||
|
||||
if (READ_ONCE(hws->suspended)) {
|
||||
dev_dbg(&hws->pdev->dev, "arm_next(ch=%u): suspended\n", ch);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (READ_ONCE(v->stop_requested) || !READ_ONCE(v->cap_active)) {
|
||||
dev_dbg(&hws->pdev->dev,
|
||||
"arm_next(ch=%u): stop=%d cap=%d -> cancel\n", ch,
|
||||
v->stop_requested, v->cap_active);
|
||||
return -ECANCELED;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&v->irq_lock, flags);
|
||||
if (list_empty(&v->capture_queue)) {
|
||||
spin_unlock_irqrestore(&v->irq_lock, flags);
|
||||
dev_dbg(&hws->pdev->dev, "arm_next(ch=%u): queue empty\n", ch);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
buf = list_first_entry(&v->capture_queue, struct hwsvideo_buffer, list);
|
||||
list_del_init(&buf->list); /* keep buffer safe for later cleanup */
|
||||
if (v->queued_count)
|
||||
v->queued_count--;
|
||||
v->active = buf;
|
||||
spin_unlock_irqrestore(&v->irq_lock, flags);
|
||||
dev_dbg(&hws->pdev->dev, "arm_next(ch=%u): picked buffer %p\n", ch,
|
||||
buf);
|
||||
|
||||
/* Publish descriptor(s) before doorbell/MMIO kicks. */
|
||||
wmb();
|
||||
|
||||
/* Avoid MMIO during suspend */
|
||||
if (READ_ONCE(hws->suspended)) {
|
||||
unsigned long f;
|
||||
|
||||
dev_dbg(&hws->pdev->dev,
|
||||
"arm_next(ch=%u): suspended after pick\n", ch);
|
||||
spin_lock_irqsave(&v->irq_lock, f);
|
||||
if (v->active) {
|
||||
list_add(&buf->list, &v->capture_queue);
|
||||
v->queued_count++;
|
||||
v->active = NULL;
|
||||
}
|
||||
spin_unlock_irqrestore(&v->irq_lock, f);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* Also program the DMA address register directly */
|
||||
{
|
||||
dma_addr_t dma_addr =
|
||||
vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
|
||||
hws_program_dma_for_addr(hws, ch, dma_addr);
|
||||
iowrite32(lower_32_bits(dma_addr),
|
||||
hws->bar0_base + HWS_REG_DMA_ADDR(ch));
|
||||
}
|
||||
|
||||
dev_dbg(&hws->pdev->dev, "arm_next(ch=%u): programmed buffer %p\n", ch,
|
||||
buf);
|
||||
spin_lock_irqsave(&v->irq_lock, flags);
|
||||
hws_prime_next_locked(v);
|
||||
spin_unlock_irqrestore(&v->irq_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hws_video_handle_vdone(struct hws_video *v)
|
||||
{
|
||||
struct hws_pcie_dev *hws = v->parent;
|
||||
unsigned int ch = v->channel_index;
|
||||
struct hwsvideo_buffer *done;
|
||||
unsigned long flags;
|
||||
bool promoted = false;
|
||||
|
||||
dev_dbg(&hws->pdev->dev,
|
||||
"bh_video(ch=%u): stop=%d cap=%d active=%p\n",
|
||||
ch, READ_ONCE(v->stop_requested), READ_ONCE(v->cap_active),
|
||||
v->active);
|
||||
|
||||
int ret;
|
||||
|
||||
dev_dbg(&hws->pdev->dev,
|
||||
"bh_video(ch=%u): entry stop=%d cap=%d\n", ch,
|
||||
v->stop_requested, v->cap_active);
|
||||
if (READ_ONCE(hws->suspended))
|
||||
return;
|
||||
|
||||
if (READ_ONCE(v->stop_requested) || !READ_ONCE(v->cap_active))
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&v->irq_lock, flags);
|
||||
done = v->active;
|
||||
if (done && v->next_prepared) {
|
||||
v->active = v->next_prepared;
|
||||
v->next_prepared = NULL;
|
||||
promoted = true;
|
||||
}
|
||||
spin_unlock_irqrestore(&v->irq_lock, flags);
|
||||
|
||||
/* 1) Complete the buffer the HW just finished (if any) */
|
||||
if (done) {
|
||||
struct vb2_v4l2_buffer *vb2v = &done->vb;
|
||||
size_t expected = v->pix.sizeimage;
|
||||
size_t plane_size = vb2_plane_size(&vb2v->vb2_buf, 0);
|
||||
|
||||
if (expected > plane_size) {
|
||||
dev_warn_ratelimited(&hws->pdev->dev,
|
||||
"bh_video(ch=%u): sizeimage %zu > plane %zu, dropping seq=%u\n",
|
||||
ch, expected, plane_size,
|
||||
(u32)atomic_read(&v->sequence_number) + 1);
|
||||
vb2_buffer_done(&vb2v->vb2_buf, VB2_BUF_STATE_ERROR);
|
||||
goto arm_next;
|
||||
}
|
||||
vb2_set_plane_payload(&vb2v->vb2_buf, 0, expected);
|
||||
|
||||
dma_rmb(); /* device writes visible before userspace sees it */
|
||||
|
||||
vb2v->sequence = (u32)atomic_inc_return(&v->sequence_number);
|
||||
vb2v->vb2_buf.timestamp = ktime_get_ns();
|
||||
dev_dbg(&hws->pdev->dev,
|
||||
"bh_video(ch=%u): DONE buf=%p seq=%u half_seen=%d toggle=%u\n",
|
||||
ch, done, vb2v->sequence, v->half_seen,
|
||||
v->last_buf_half_toggle);
|
||||
|
||||
if (!promoted)
|
||||
v->active = NULL; /* channel no longer owns this buffer */
|
||||
vb2_buffer_done(&vb2v->vb2_buf, VB2_BUF_STATE_DONE);
|
||||
}
|
||||
|
||||
if (READ_ONCE(hws->suspended))
|
||||
return;
|
||||
|
||||
if (promoted) {
|
||||
dev_dbg(&hws->pdev->dev,
|
||||
"bh_video(ch=%u): promoted pre-armed buffer active=%p\n",
|
||||
ch, v->active);
|
||||
spin_lock_irqsave(&v->irq_lock, flags);
|
||||
hws_prime_next_locked(v);
|
||||
spin_unlock_irqrestore(&v->irq_lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
arm_next:
|
||||
/* 2) Immediately arm the next queued buffer (if present) */
|
||||
ret = hws_arm_next(hws, ch);
|
||||
if (ret == -EAGAIN) {
|
||||
dev_dbg(&hws->pdev->dev,
|
||||
"bh_video(ch=%u): no queued buffer to arm\n", ch);
|
||||
return;
|
||||
}
|
||||
dev_dbg(&hws->pdev->dev,
|
||||
"bh_video(ch=%u): armed next buffer, active=%p\n", ch,
|
||||
v->active);
|
||||
/* On success the engine now points at v->active's DMA address */
|
||||
}
|
||||
|
||||
irqreturn_t hws_irq_handler(int irq, void *info)
|
||||
{
|
||||
struct hws_pcie_dev *pdx = info;
|
||||
u32 int_state;
|
||||
|
||||
dev_dbg(&pdx->pdev->dev, "irq: entry\n");
|
||||
if (pdx->bar0_base) {
|
||||
dev_dbg(&pdx->pdev->dev,
|
||||
"irq: INT_EN=0x%08x INT_STATUS=0x%08x\n",
|
||||
readl(pdx->bar0_base + INT_EN_REG_BASE),
|
||||
readl(pdx->bar0_base + HWS_REG_INT_STATUS));
|
||||
}
|
||||
|
||||
/* Fast path: if suspended, quietly ack and exit */
|
||||
if (READ_ONCE(pdx->suspended)) {
|
||||
int_state = readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
|
||||
if (int_state) {
|
||||
writel(int_state, pdx->bar0_base + HWS_REG_INT_STATUS);
|
||||
(void)readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
|
||||
}
|
||||
return int_state ? IRQ_HANDLED : IRQ_NONE;
|
||||
}
|
||||
int_state = readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
|
||||
if (!int_state || int_state == 0xFFFFFFFF) {
|
||||
dev_dbg(&pdx->pdev->dev,
|
||||
"irq: spurious or device-gone int_state=0x%08x\n",
|
||||
int_state);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
dev_dbg(&pdx->pdev->dev, "irq: entry INT_STATUS=0x%08x\n", int_state);
|
||||
|
||||
/* Loop until all pending bits are serviced (max 100 iterations) */
|
||||
for (u32 cnt = 0; int_state && cnt < MAX_INT_LOOPS; ++cnt) {
|
||||
for (unsigned int ch = 0; ch < pdx->cur_max_video_ch; ++ch) {
|
||||
u32 vbit = HWS_INT_VDONE_BIT(ch);
|
||||
|
||||
if (!(int_state & vbit))
|
||||
continue;
|
||||
|
||||
if (READ_ONCE(pdx->video[ch].cap_active) &&
|
||||
!READ_ONCE(pdx->video[ch].stop_requested)) {
|
||||
if (hws_toggle_debug) {
|
||||
u32 toggle =
|
||||
readl_relaxed(pdx->bar0_base +
|
||||
HWS_REG_VBUF_TOGGLE(ch)) & 0x01;
|
||||
WRITE_ONCE(pdx->video[ch].last_buf_half_toggle,
|
||||
toggle);
|
||||
}
|
||||
dma_rmb();
|
||||
WRITE_ONCE(pdx->video[ch].half_seen, true);
|
||||
dev_dbg(&pdx->pdev->dev,
|
||||
"irq: VDONE ch=%u toggle=%u handling inline (cap=%d)\n",
|
||||
ch,
|
||||
READ_ONCE(pdx->video[ch].last_buf_half_toggle),
|
||||
READ_ONCE(pdx->video[ch].cap_active));
|
||||
hws_video_handle_vdone(&pdx->video[ch]);
|
||||
} else {
|
||||
dev_dbg(&pdx->pdev->dev,
|
||||
"irq: VDONE ch=%u ignored (cap=%d stop=%d)\n",
|
||||
ch,
|
||||
READ_ONCE(pdx->video[ch].cap_active),
|
||||
READ_ONCE(pdx->video[ch].stop_requested));
|
||||
}
|
||||
|
||||
writel(vbit, pdx->bar0_base + HWS_REG_INT_STATUS);
|
||||
(void)readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
|
||||
}
|
||||
|
||||
/* Re-read in case new interrupt bits popped while processing */
|
||||
int_state = readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
|
||||
dev_dbg(&pdx->pdev->dev,
|
||||
"irq: loop cnt=%u new INT_STATUS=0x%08x\n", cnt,
|
||||
int_state);
|
||||
if (cnt + 1 == MAX_INT_LOOPS)
|
||||
dev_warn_ratelimited(&pdx->pdev->dev,
|
||||
"IRQ storm? status=0x%08x\n",
|
||||
int_state);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef HWS_INTERRUPT_H
|
||||
#define HWS_INTERRUPT_H
|
||||
|
||||
#include <linux/pci.h>
|
||||
#include "hws.h"
|
||||
|
||||
irqreturn_t hws_irq_handler(int irq, void *info);
|
||||
|
||||
#endif /* HWS_INTERRUPT_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,136 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef _HWS_PCIE_REG_H
|
||||
#define _HWS_PCIE_REG_H
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#define XDMA_CHANNEL_NUM_MAX (1)
|
||||
#define MAX_NUM_ENGINES (XDMA_CHANNEL_NUM_MAX * 2)
|
||||
|
||||
#define PCIE_BARADDROFSIZE 4u
|
||||
|
||||
#define PCI_BUS_ACCESS_BASE 0x00000000U
|
||||
#define INT_EN_REG_BASE (PCI_BUS_ACCESS_BASE + 0x0134U)
|
||||
#define PCIEBR_EN_REG_BASE (PCI_BUS_ACCESS_BASE + 0x0148U)
|
||||
#define PCIE_INT_DEC_REG_BASE (PCI_BUS_ACCESS_BASE + 0x0138U)
|
||||
|
||||
#define HWS_INT_EN_MASK 0x0003FFFFU
|
||||
|
||||
#define PCIEBAR_AXI_BASE 0x20000000U
|
||||
|
||||
#define CTL_REG_ACC_BASE 0x0
|
||||
#define PCI_ADDR_TABLE_BASE CTL_REG_ACC_BASE
|
||||
|
||||
#define CVBS_IN_BASE 0x00004000U
|
||||
#define CVBS_IN_BUF_BASE (CVBS_IN_BASE + (16U * PCIE_BARADDROFSIZE))
|
||||
#define CVBS_IN_BUF_BASE2 (CVBS_IN_BASE + (50U * PCIE_BARADDROFSIZE))
|
||||
|
||||
/* 2 Mib */
|
||||
#define MAX_L_VIDEO_SIZE 0x200000U
|
||||
|
||||
#define PCI_E_BAR_PAGE_SIZE 0x20000000
|
||||
#define PCI_E_BAR_ADD_MASK 0xE0000000
|
||||
#define PCI_E_BAR_ADD_LOWMASK 0x1FFFFFFF
|
||||
|
||||
#define MAX_VID_CHANNELS 4
|
||||
|
||||
#define MAX_MM_VIDEO_SIZE SZ_4M
|
||||
|
||||
#define MAX_VIDEO_HW_W 1920
|
||||
#define MAX_VIDEO_HW_H 1080
|
||||
#define MAX_VIDEO_SCALER_SIZE (1920U * 1080U * 2U)
|
||||
|
||||
#define MIN_VAMP_BRIGHTNESS_UNITS 0
|
||||
#define MAX_VAMP_BRIGHTNESS_UNITS 0xff
|
||||
|
||||
#define MIN_VAMP_CONTRAST_UNITS 0
|
||||
#define MAX_VAMP_CONTRAST_UNITS 0xff
|
||||
|
||||
#define MIN_VAMP_SATURATION_UNITS 0
|
||||
#define MAX_VAMP_SATURATION_UNITS 0xff
|
||||
|
||||
#define MIN_VAMP_HUE_UNITS 0
|
||||
#define MAX_VAMP_HUE_UNITS 0xff
|
||||
|
||||
#define HWS_BRIGHTNESS_DEFAULT 0x80
|
||||
#define HWS_CONTRAST_DEFAULT 0x80
|
||||
#define HWS_SATURATION_DEFAULT 0x80
|
||||
#define HWS_HUE_DEFAULT 0x00
|
||||
|
||||
/* Core/global status. */
|
||||
#define HWS_REG_SYS_STATUS (CVBS_IN_BASE + 0 * PCIE_BARADDROFSIZE)
|
||||
/* bit3: DMA busy, bit2: int, ... */
|
||||
|
||||
#define HWS_SYS_DMA_BUSY_BIT BIT(3) /* 0x08 = DMA busy flag */
|
||||
|
||||
#define HWS_REG_DEC_MODE (CVBS_IN_BASE + 0 * PCIE_BARADDROFSIZE)
|
||||
/* Main control register */
|
||||
#define HWS_REG_CTL (CVBS_IN_BASE + 4 * PCIE_BARADDROFSIZE)
|
||||
#define HWS_CTL_IRQ_ENABLE_BIT BIT(0) /* Global interrupt enable bit */
|
||||
/* Write 0x00 to fully reset decoder,
|
||||
* set bit 31=1 to "start run",
|
||||
* low byte=0x13 selects YUYV/BT.709/etc,
|
||||
* in ReadChipId() we also write 0x00 and 0x10 here for chip-ID sequencing.
|
||||
*/
|
||||
|
||||
/* Per-channel done flags. */
|
||||
#define HWS_REG_INT_STATUS (CVBS_IN_BASE + 1 * PCIE_BARADDROFSIZE)
|
||||
#define HWS_SYS_BUSY_BIT BIT(2) /* matches old 0x04 test */
|
||||
|
||||
/* Capture enable switches. */
|
||||
/* bit0-3: CH0-CH3 video enable */
|
||||
#define HWS_REG_VCAP_ENABLE (CVBS_IN_BASE + 2 * PCIE_BARADDROFSIZE)
|
||||
/* bits0-3: signal present, bits8-11: interlace */
|
||||
#define HWS_REG_ACTIVE_STATUS (CVBS_IN_BASE + 5 * PCIE_BARADDROFSIZE)
|
||||
/* bits0-3: HDCP detected */
|
||||
#define HWS_REG_HDCP_STATUS (CVBS_IN_BASE + 8 * PCIE_BARADDROFSIZE)
|
||||
#define HWS_REG_DMA_MAX_SIZE (CVBS_IN_BASE + 9 * PCIE_BARADDROFSIZE)
|
||||
|
||||
/* Buffer addresses (written once during init/reset). */
|
||||
/* Base of host-visible buffer. */
|
||||
#define HWS_REG_VBUF1_ADDR (CVBS_IN_BASE + 25 * PCIE_BARADDROFSIZE)
|
||||
/* Per-channel DMA address. */
|
||||
#define HWS_REG_DMA_ADDR(ch) (CVBS_IN_BASE + (26 + (ch)) * PCIE_BARADDROFSIZE)
|
||||
|
||||
/* Per-channel live buffer toggles (read-only). */
|
||||
#define HWS_REG_VBUF_TOGGLE(ch) (CVBS_IN_BASE + (32 + (ch)) * PCIE_BARADDROFSIZE)
|
||||
/*
|
||||
* Returns 0 or 1 = which half of the video ring the DMA engine is
|
||||
* currently filling for channel *ch* (0-3).
|
||||
*/
|
||||
|
||||
/* Per-interrupt bits (video 0-3). */
|
||||
#define HWS_INT_VDONE_BIT(ch) BIT(ch) /* 0x01,0x02,0x04,0x08 */
|
||||
|
||||
#define HWS_REG_INT_ACK (CVBS_IN_BASE + 0x4000 + 1 * PCIE_BARADDROFSIZE)
|
||||
|
||||
/* 16-bit W | 16-bit H. */
|
||||
#define HWS_REG_IN_RES(ch) (CVBS_IN_BASE + (90 + (ch) * 2) * PCIE_BARADDROFSIZE)
|
||||
/* B|C|H|S packed bytes. */
|
||||
#define HWS_REG_BCHS(ch) (CVBS_IN_BASE + (91 + (ch) * 2) * PCIE_BARADDROFSIZE)
|
||||
|
||||
/* Input fps. */
|
||||
#define HWS_REG_FRAME_RATE(ch) (CVBS_IN_BASE + (110 + (ch)) * PCIE_BARADDROFSIZE)
|
||||
/* Programmed out W|H. */
|
||||
#define HWS_REG_OUT_RES(ch) (CVBS_IN_BASE + (120 + (ch)) * PCIE_BARADDROFSIZE)
|
||||
/* Programmed out fps. */
|
||||
#define HWS_REG_OUT_FRAME_RATE(ch) (CVBS_IN_BASE + (130 + (ch)) * PCIE_BARADDROFSIZE)
|
||||
|
||||
/* Device version/port ID/subversion register. */
|
||||
#define HWS_REG_DEVICE_INFO (CVBS_IN_BASE + 88 * PCIE_BARADDROFSIZE)
|
||||
/*
|
||||
* Reading this 32-bit word returns:
|
||||
* bits 7:0 = "device version"
|
||||
* bits 15:8 = "device sub-version"
|
||||
* bits 23:24 = "HW key / port ID" etc.
|
||||
* bits 31:28 = "support YV12" flags
|
||||
*/
|
||||
|
||||
/* Convenience aliases for individual channels. */
|
||||
#define HWS_REG_VBUF_TOGGLE_CH0 HWS_REG_VBUF_TOGGLE(0)
|
||||
#define HWS_REG_VBUF_TOGGLE_CH1 HWS_REG_VBUF_TOGGLE(1)
|
||||
#define HWS_REG_VBUF_TOGGLE_CH2 HWS_REG_VBUF_TOGGLE(2)
|
||||
#define HWS_REG_VBUF_TOGGLE_CH3 HWS_REG_VBUF_TOGGLE(3)
|
||||
|
||||
#endif /* _HWS_PCIE_REG_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef HWS_V4L2_IOCTL_H
|
||||
#define HWS_V4L2_IOCTL_H
|
||||
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
extern const struct v4l2_ctrl_ops hws_ctrl_ops;
|
||||
|
||||
int hws_vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap);
|
||||
int hws_vidioc_enum_fmt_vid_cap(struct file *file, void *priv_fh, struct v4l2_fmtdesc *f);
|
||||
int hws_vidioc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt);
|
||||
int hws_vidioc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f);
|
||||
int hws_vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorms);
|
||||
int hws_vidioc_s_std(struct file *file, void *priv, v4l2_std_id tvnorms);
|
||||
int hws_vidioc_g_parm(struct file *file, void *fh, struct v4l2_streamparm *setfps);
|
||||
int hws_vidioc_enum_input(struct file *file, void *priv, struct v4l2_input *i);
|
||||
int hws_vidioc_g_input(struct file *file, void *priv, unsigned int *i);
|
||||
int hws_vidioc_s_input(struct file *file, void *priv, unsigned int i);
|
||||
int hws_vidioc_g_ctrl(struct file *file, void *fh, struct v4l2_control *a);
|
||||
int hws_vidioc_s_ctrl(struct file *file, void *fh, struct v4l2_control *a);
|
||||
int hws_vidioc_dv_timings_cap(struct file *file, void *fh,
|
||||
struct v4l2_dv_timings_cap *cap);
|
||||
int hws_vidioc_s_dv_timings(struct file *file, void *fh,
|
||||
struct v4l2_dv_timings *timings);
|
||||
|
||||
int hws_vidioc_queryctrl(struct file *file, void *fh, struct v4l2_queryctrl *a);
|
||||
int hws_vidioc_g_dv_timings(struct file *file, void *fh,
|
||||
struct v4l2_dv_timings *timings);
|
||||
int hws_vidioc_enum_dv_timings(struct file *file, void *fh,
|
||||
struct v4l2_enum_dv_timings *edv);
|
||||
int hws_vidioc_query_dv_timings(struct file *file, void *fh,
|
||||
struct v4l2_dv_timings *timings);
|
||||
int hws_vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef HWS_VIDEO_H
|
||||
#define HWS_VIDEO_H
|
||||
|
||||
struct hws_video;
|
||||
|
||||
int hws_video_register(struct hws_pcie_dev *dev);
|
||||
void hws_video_unregister(struct hws_pcie_dev *dev);
|
||||
void hws_enable_video_capture(struct hws_pcie_dev *hws,
|
||||
unsigned int chan,
|
||||
bool on);
|
||||
void hws_prime_next_locked(struct hws_video *vid);
|
||||
|
||||
int hws_video_init_channel(struct hws_pcie_dev *pdev, int ch);
|
||||
void hws_video_cleanup_channel(struct hws_pcie_dev *pdev, int ch);
|
||||
void check_video_format(struct hws_pcie_dev *pdx);
|
||||
int hws_check_card_status(struct hws_pcie_dev *hws);
|
||||
void hws_init_video_sys(struct hws_pcie_dev *hws, bool enable);
|
||||
|
||||
void hws_program_dma_for_addr(struct hws_pcie_dev *hws,
|
||||
unsigned int ch,
|
||||
dma_addr_t dma);
|
||||
void hws_set_dma_doorbell(struct hws_pcie_dev *hws, unsigned int ch,
|
||||
dma_addr_t dma, const char *tag);
|
||||
|
||||
int hws_video_quiesce(struct hws_pcie_dev *hws, const char *reason);
|
||||
void hws_video_pm_resume(struct hws_pcie_dev *hws);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user