#include <linux/pci.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/compiler.h>
#include <linux/overflow.h>
#include <linux/delay.h>
#include <linux/bits.h>
#include <linux/jiffies.h>
#include <linux/ktime.h>
#include <linux/math64.h>
#include <linux/interrupt.h>
#include <linux/moduleparam.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-event.h>
#include <media/videobuf2-v4l2.h>
#include <media/v4l2-device.h>
#include <media/videobuf2-dma-contig.h>
#include "hws.h"
#include "hws_reg.h"
#include "hws_video.h"
#include "hws_irq.h"
#include "hws_v4l2_ioctl.h"
#define HWS_REMAP_SLOT_OFF(ch) (0x208 + (ch) * 8)
#define HWS_BUF_BASE_OFF(ch) (CVBS_IN_BUF_BASE + (ch) * PCIE_BARADDROFSIZE)
#define HWS_HALF_SZ_OFF(ch) (CVBS_IN_BUF_BASE2 + (ch) * PCIE_BARADDROFSIZE)
static void update_live_resolution(struct hws_pcie_dev *pdx, unsigned int ch,
bool interlace);
static bool hws_read_active_state(struct hws_pcie_dev *pdx, unsigned int ch,
bool *interlace);
static void handle_hwv2_path(struct hws_pcie_dev *hws, unsigned int ch);
static void handle_legacy_path(struct hws_pcie_dev *hws, unsigned int ch);
static u32 hws_calc_sizeimage(struct hws_video *v, u16 w, u16 h,
bool interlaced);
static void hws_program_dma_window(struct hws_video *vid, dma_addr_t dma);
static struct hwsvideo_buffer *
hws_take_queued_buffer_locked(struct hws_video *vid);
static unsigned long long hws_elapsed_us(u64 start_ns)
{
return div_u64(ktime_get_mono_fast_ns() - start_ns, 1000);
}
static inline bool list_node_unlinked(const struct list_head *n)
{
return n->next == LIST_POISON1 || n->prev == LIST_POISON2;
}
static bool dma_window_verify;
module_param_named(dma_window_verify, dma_window_verify, bool, 0644);
MODULE_PARM_DESC(dma_window_verify,
"Read back DMA window registers after programming (debug)");
void hws_set_dma_doorbell(struct hws_pcie_dev *hws, unsigned int ch,
dma_addr_t dma, const char *tag)
{
iowrite32(lower_32_bits(dma), hws->bar0_base + HWS_REG_DMA_ADDR(ch));
dev_dbg(&hws->pdev->dev, "dma_doorbell ch%u: dma=0x%llx tag=%s\n", ch,
(u64)dma, tag ? tag : "");
}
static void hws_program_dma_window(struct hws_video *vid, dma_addr_t dma)
{
const u32 addr_mask = PCI_E_BAR_ADD_MASK;
const u32 addr_low_mask = PCI_E_BAR_ADD_LOWMASK;
struct hws_pcie_dev *hws = vid->parent;
unsigned int ch = vid->channel_index;
u32 table_off = HWS_REMAP_SLOT_OFF(ch);
u32 lo = lower_32_bits(dma);
u32 hi = upper_32_bits(dma);
u32 pci_addr = lo & addr_low_mask;
u32 page_lo = lo & addr_mask;
bool wrote = false;
if (!vid->window_valid || vid->last_dma_hi != hi ||
vid->last_dma_page != page_lo) {
writel(hi, hws->bar0_base + PCI_ADDR_TABLE_BASE + table_off);
writel(page_lo,
hws->bar0_base + PCI_ADDR_TABLE_BASE + table_off +
PCIE_BARADDROFSIZE);
vid->last_dma_hi = hi;
vid->last_dma_page = page_lo;
wrote = true;
}
if (!vid->window_valid || vid->last_pci_addr != pci_addr) {
writel((ch + 1) * PCIEBAR_AXI_BASE + pci_addr,
hws->bar0_base + HWS_BUF_BASE_OFF(ch));
vid->last_pci_addr = pci_addr;
wrote = true;
}
if (!vid->window_valid || vid->last_half16 != vid->pix.half_size / 16) {
writel(vid->pix.half_size / 16,
hws->bar0_base + HWS_HALF_SZ_OFF(ch));
vid->last_half16 = vid->pix.half_size / 16;
wrote = true;
}
vid->window_valid = true;
if (dma_window_verify && wrote) {
u32 r_hi =
readl(hws->bar0_base + PCI_ADDR_TABLE_BASE + table_off);
u32 r_lo =
readl(hws->bar0_base + PCI_ADDR_TABLE_BASE + table_off +
PCIE_BARADDROFSIZE);
u32 r_base = readl(hws->bar0_base + HWS_BUF_BASE_OFF(ch));
u32 r_half = readl(hws->bar0_base + HWS_HALF_SZ_OFF(ch));
dev_dbg(&hws->pdev->dev,
"ch%u remap verify: hi=0x%08x page_lo=0x%08x exp_page=0x%08x base=0x%08x exp_base=0x%08x half16B=0x%08x exp_half=0x%08x\n",
ch, r_hi, r_lo, page_lo, r_base,
(ch + 1) * PCIEBAR_AXI_BASE + pci_addr, r_half,
vid->pix.half_size / 16);
} else if (wrote) {
readl_relaxed(hws->bar0_base + HWS_HALF_SZ_OFF(ch));
}
}
static struct hwsvideo_buffer *
hws_take_queued_buffer_locked(struct hws_video *vid)
{
struct hwsvideo_buffer *buf;
if (!vid || list_empty(&vid->capture_queue))
return NULL;
buf = list_first_entry(&vid->capture_queue,
struct hwsvideo_buffer, list);
list_del_init(&buf->list);
if (vid->queued_count)
vid->queued_count--;
return buf;
}
void hws_prime_next_locked(struct hws_video *vid)
{
struct hws_pcie_dev *hws;
struct hwsvideo_buffer *next;
dma_addr_t dma;
if (!vid)
return;
hws = vid->parent;
if (!hws || !hws->bar0_base)
return;
if (!READ_ONCE(vid->cap_active) || !vid->active || vid->next_prepared)
return;
next = hws_take_queued_buffer_locked(vid);
if (!next)
return;
vid->next_prepared = next;
dma = vb2_dma_contig_plane_dma_addr(&next->vb.vb2_buf, 0);
hws_program_dma_for_addr(hws, vid->channel_index, dma);
iowrite32(lower_32_bits(dma),
hws->bar0_base + HWS_REG_DMA_ADDR(vid->channel_index));
dev_dbg(&hws->pdev->dev,
"ch%u pre-armed next buffer %p dma=0x%llx\n",
vid->channel_index, next, (u64)dma);
}
static bool hws_force_no_signal_frame(struct hws_video *v, const char *tag)
{
struct hws_pcie_dev *hws;
unsigned long flags;
struct hwsvideo_buffer *buf = NULL, *next = NULL;
bool have_next = false;
bool doorbell = false;
if (!v)
return false;
hws = v->parent;
if (!hws || READ_ONCE(v->stop_requested) || !READ_ONCE(v->cap_active))
return false;
spin_lock_irqsave(&v->irq_lock, flags);
if (v->active) {
buf = v->active;
v->active = NULL;
buf->slot = 0;
} else if (!list_empty(&v->capture_queue)) {
buf = list_first_entry(&v->capture_queue,
struct hwsvideo_buffer, list);
list_del_init(&buf->list);
if (v->queued_count)
v->queued_count--;
buf->slot = 0;
}
if (v->next_prepared) {
next = v->next_prepared;
v->next_prepared = NULL;
next->slot = 0;
v->active = next;
have_next = true;
} else if (!list_empty(&v->capture_queue)) {
next = list_first_entry(&v->capture_queue,
struct hwsvideo_buffer, list);
list_del_init(&next->list);
if (v->queued_count)
v->queued_count--;
next->slot = 0;
v->active = next;
have_next = true;
} else {
v->active = NULL;
}
spin_unlock_irqrestore(&v->irq_lock, flags);
if (!buf)
return false;
{
struct vb2_v4l2_buffer *vb2v = &buf->vb;
void *dst = vb2_plane_vaddr(&vb2v->vb2_buf, 0);
if (dst)
memset(dst, 0x10, v->pix.sizeimage);
vb2_set_plane_payload(&vb2v->vb2_buf, 0, v->pix.sizeimage);
vb2v->sequence = (u32)atomic_inc_return(&v->sequence_number);
vb2v->vb2_buf.timestamp = ktime_get_ns();
vb2_buffer_done(&vb2v->vb2_buf, VB2_BUF_STATE_DONE);
}
if (have_next && next) {
dma_addr_t dma =
vb2_dma_contig_plane_dma_addr(&next->vb.vb2_buf, 0);
hws_program_dma_for_addr(hws, v->channel_index, dma);
hws_set_dma_doorbell(hws, v->channel_index, dma,
tag ? tag : "nosignal_zero");
doorbell = true;
}
if (doorbell) {
wmb();
hws_enable_video_capture(hws, v->channel_index, true);
}
return true;
}
static int hws_ctrls_init(struct hws_video *vid)
{
struct v4l2_ctrl_handler *hdl = &vid->control_handler;
v4l2_ctrl_handler_init(hdl, 4);
vid->ctrl_brightness = v4l2_ctrl_new_std(hdl, &hws_ctrl_ops,
V4L2_CID_BRIGHTNESS,
MIN_VAMP_BRIGHTNESS_UNITS,
MAX_VAMP_BRIGHTNESS_UNITS, 1,
HWS_BRIGHTNESS_DEFAULT);
vid->ctrl_contrast =
v4l2_ctrl_new_std(hdl, &hws_ctrl_ops, V4L2_CID_CONTRAST,
MIN_VAMP_CONTRAST_UNITS, MAX_VAMP_CONTRAST_UNITS,
1, HWS_CONTRAST_DEFAULT);
vid->ctrl_saturation = v4l2_ctrl_new_std(hdl, &hws_ctrl_ops,
V4L2_CID_SATURATION,
MIN_VAMP_SATURATION_UNITS,
MAX_VAMP_SATURATION_UNITS, 1,
HWS_SATURATION_DEFAULT);
vid->ctrl_hue = v4l2_ctrl_new_std(hdl, &hws_ctrl_ops, V4L2_CID_HUE,
MIN_VAMP_HUE_UNITS,
MAX_VAMP_HUE_UNITS, 1,
HWS_HUE_DEFAULT);
if (hdl->error) {
int err = hdl->error;
v4l2_ctrl_handler_free(hdl);
return err;
}
return 0;
}
int hws_video_init_channel(struct hws_pcie_dev *pdev, int ch)
{
struct hws_video *vid;
if (!pdev || ch < 0 || ch >= pdev->max_channels)
return -EINVAL;
vid = &pdev->video[ch];
memset(vid, 0, sizeof(*vid));
vid->parent = pdev;
vid->channel_index = ch;
mutex_init(&vid->state_lock);
spin_lock_init(&vid->irq_lock);
INIT_LIST_HEAD(&vid->capture_queue);
atomic_set(&vid->sequence_number, 0);
vid->active = NULL;
vid->timeout_count = 0;
vid->error_count = 0;
vid->queued_count = 0;
vid->window_valid = false;
vid->pix.width = 1920;
vid->pix.height = 1080;
vid->pix.fourcc = V4L2_PIX_FMT_YUYV;
vid->pix.bytesperline = ALIGN(vid->pix.width * 2, 64);
vid->pix.sizeimage = vid->pix.bytesperline * vid->pix.height;
vid->pix.field = V4L2_FIELD_NONE;
vid->pix.colorspace = V4L2_COLORSPACE_REC709;
vid->pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
vid->pix.quantization = V4L2_QUANTIZATION_FULL_RANGE;
vid->pix.xfer_func = V4L2_XFER_FUNC_DEFAULT;
vid->pix.interlaced = false;
vid->pix.half_size = vid->pix.sizeimage / 2;
hws_set_current_dv_timings(vid, vid->pix.width,
vid->pix.height, vid->pix.interlaced);
vid->current_fps = 60;
vid->current_brightness = 0x80;
vid->current_contrast = 0x80;
vid->current_saturation = 0x80;
vid->current_hue = 0x80;
vid->cap_active = false;
vid->stop_requested = false;
vid->last_buf_half_toggle = 0;
vid->half_seen = false;
vid->signal_loss_cnt = 0;
{
int err = hws_ctrls_init(vid);
if (err) {
dev_err(&pdev->pdev->dev,
"v4l2 ctrl init failed on ch%d: %d\n", ch, err);
return err;
}
}
return 0;
}
static void hws_video_drain_queue_locked(struct hws_video *vid)
{
if (vid->active) {
vb2_buffer_done(&vid->active->vb.vb2_buf, VB2_BUF_STATE_ERROR);
vid->active = NULL;
}
while (!list_empty(&vid->capture_queue)) {
struct hwsvideo_buffer *b =
list_first_entry(&vid->capture_queue,
struct hwsvideo_buffer,
list);
list_del_init(&b->list);
vb2_buffer_done(&b->vb.vb2_buf, VB2_BUF_STATE_ERROR);
}
}
static void hws_video_release_registration(struct hws_video *vid)
{
if (vid->buffer_queue.ops) {
vb2_queue_release(&vid->buffer_queue);
vid->buffer_queue.ops = NULL;
}
if (!vid->video_device)
return;
if (video_is_registered(vid->video_device))
vb2_video_unregister_device(vid->video_device);
else
video_device_release(vid->video_device);
vid->video_device = NULL;
}
static void hws_video_collect_done_locked(struct hws_video *vid,
struct list_head *done)
{
struct hwsvideo_buffer *b;
if (vid->active) {
if (!list_node_unlinked(&vid->active->list)) {
list_move_tail(&vid->active->list, done);
} else {
INIT_LIST_HEAD(&vid->active->list);
list_add_tail(&vid->active->list, done);
}
vid->active = NULL;
}
if (vid->next_prepared) {
if (!list_node_unlinked(&vid->next_prepared->list)) {
list_move_tail(&vid->next_prepared->list, done);
} else {
INIT_LIST_HEAD(&vid->next_prepared->list);
list_add_tail(&vid->next_prepared->list, done);
}
vid->next_prepared = NULL;
}
while (!list_empty(&vid->capture_queue)) {
b = list_first_entry(&vid->capture_queue, struct hwsvideo_buffer,
list);
list_move_tail(&b->list, done);
}
vid->queued_count = 0;
}
void hws_video_cleanup_channel(struct hws_pcie_dev *pdev, int ch)
{
struct hws_video *vid;
unsigned long flags;
if (!pdev || ch < 0 || ch >= pdev->max_channels)
return;
vid = &pdev->video[ch];
hws_enable_video_capture(vid->parent, vid->channel_index, false);
WRITE_ONCE(vid->stop_requested, true);
WRITE_ONCE(vid->cap_active, false);
if (vid->parent && vid->parent->irq >= 0)
synchronize_irq(vid->parent->irq);
spin_lock_irqsave(&vid->irq_lock, flags);
hws_video_drain_queue_locked(vid);
spin_unlock_irqrestore(&vid->irq_lock, flags);
hws_video_release_registration(vid);
v4l2_ctrl_handler_free(&vid->control_handler);
mutex_destroy(&vid->state_lock);
INIT_LIST_HEAD(&vid->capture_queue);
vid->active = NULL;
vid->stop_requested = false;
vid->last_buf_half_toggle = 0;
vid->half_seen = false;
vid->signal_loss_cnt = 0;
}
static inline struct hwsvideo_buffer *to_hwsbuf(struct vb2_buffer *vb)
{
return container_of(to_vb2_v4l2_buffer(vb), struct hwsvideo_buffer, vb);
}
static int hws_buf_init(struct vb2_buffer *vb)
{
struct hwsvideo_buffer *b = to_hwsbuf(vb);
INIT_LIST_HEAD(&b->list);
return 0;
}
static void hws_buf_finish(struct vb2_buffer *vb)
{
(void)vb;
}
static void hws_buf_cleanup(struct vb2_buffer *vb)
{
struct hwsvideo_buffer *b = to_hwsbuf(vb);
if (!list_empty(&b->list))
list_del_init(&b->list);
}
void hws_program_dma_for_addr(struct hws_pcie_dev *hws, unsigned int ch,
dma_addr_t dma)
{
struct hws_video *vid = &hws->video[ch];
hws_program_dma_window(vid, dma);
}
void hws_enable_video_capture(struct hws_pcie_dev *hws, unsigned int chan,
bool on)
{
u32 status;
if (!hws || hws->pci_lost || chan >= hws->max_channels)
return;
status = readl(hws->bar0_base + HWS_REG_VCAP_ENABLE);
status = on ? (status | BIT(chan)) : (status & ~BIT(chan));
writel(status, hws->bar0_base + HWS_REG_VCAP_ENABLE);
(void)readl(hws->bar0_base + HWS_REG_VCAP_ENABLE);
WRITE_ONCE(hws->video[chan].cap_active, on);
dev_dbg(&hws->pdev->dev, "vcap %s ch%u (reg=0x%08x)\n",
on ? "ON" : "OFF", chan, status);
}
static void hws_seed_dma_windows(struct hws_pcie_dev *hws)
{
const u32 addr_mask = PCI_E_BAR_ADD_MASK;
const u32 addr_low_mask = PCI_E_BAR_ADD_LOWMASK;
u32 table = 0x208;
unsigned int ch;
if (!hws || !hws->bar0_base)
return;
if (!hws->cur_max_video_ch || hws->cur_max_video_ch > hws->max_channels)
hws->cur_max_video_ch = hws->max_channels;
for (ch = 0; ch < hws->cur_max_video_ch; ch++, table += 8) {
if (!hws->scratch_vid[ch].cpu)
continue;
{
dma_addr_t p = hws->scratch_vid[ch].dma;
u32 lo = lower_32_bits(p) & addr_mask;
u32 hi = upper_32_bits(p);
u32 pci_addr_low = lower_32_bits(p) & addr_low_mask;
writel_relaxed(hi,
hws->bar0_base + PCI_ADDR_TABLE_BASE +
table);
writel_relaxed(lo,
hws->bar0_base + PCI_ADDR_TABLE_BASE +
table + PCIE_BARADDROFSIZE);
writel_relaxed((ch + 1) * PCIEBAR_AXI_BASE +
pci_addr_low,
hws->bar0_base + CVBS_IN_BUF_BASE +
ch * PCIE_BARADDROFSIZE);
{
u32 half_bytes = hws->video[ch].pix.half_size ?
hws->video[ch].pix.half_size :
(u32)(hws->scratch_vid[ch].size / 2);
writel_relaxed(half_bytes / 16,
hws->bar0_base +
CVBS_IN_BUF_BASE2 +
ch * PCIE_BARADDROFSIZE);
}
}
}
(void)readl(hws->bar0_base + HWS_REG_INT_STATUS);
}
static void hws_ack_all_irqs(struct hws_pcie_dev *hws)
{
u32 st = readl(hws->bar0_base + HWS_REG_INT_STATUS);
if (st) {
writel(st, hws->bar0_base + HWS_REG_INT_STATUS);
(void)readl(hws->bar0_base + HWS_REG_INT_STATUS);
}
}
static void hws_open_irq_fabric(struct hws_pcie_dev *hws)
{
writel(0x00000000, hws->bar0_base + PCIE_INT_DEC_REG_BASE);
(void)readl(hws->bar0_base + PCIE_INT_DEC_REG_BASE);
writel(0x00000001, hws->bar0_base + PCIEBR_EN_REG_BASE);
(void)readl(hws->bar0_base + PCIEBR_EN_REG_BASE);
writel(HWS_INT_EN_MASK, hws->bar0_base + INT_EN_REG_BASE);
(void)readl(hws->bar0_base + INT_EN_REG_BASE);
}
void hws_init_video_sys(struct hws_pcie_dev *hws, bool enable)
{
int i;
if (hws->start_run && !enable)
return;
writel(0x00000000, hws->bar0_base + HWS_REG_DEC_MODE);
hws_seed_dma_windows(hws);
if (!enable) {
for (i = 0; i < hws->max_channels; i++) {
hws_enable_video_capture(hws, i, false);
}
}
writel(0x80000000, hws->bar0_base + HWS_REG_DEC_MODE);
writel(0x80FFFFFF, hws->bar0_base + HWS_REG_DEC_MODE);
writel(0x13, hws->bar0_base + HWS_REG_DEC_MODE);
hws_ack_all_irqs(hws);
hws_open_irq_fabric(hws);
hws->start_run = true;
}
int hws_check_card_status(struct hws_pcie_dev *hws)
{
u32 status;
if (!hws || !hws->bar0_base)
return -ENODEV;
status = readl(hws->bar0_base + HWS_REG_SYS_STATUS);
if (status == 0xFFFFFFFF) {
hws->pci_lost = true;
dev_err(&hws->pdev->dev, "PCIe device not responding\n");
return -ENODEV;
}
if (!(status & BIT(0))) {
dev_dbg(&hws->pdev->dev,
"SYS_STATUS not ready (0x%08x), reinitializing\n",
status);
hws_init_video_sys(hws, true);
}
return 0;
}
void check_video_format(struct hws_pcie_dev *pdx)
{
int i;
for (i = 0; i < pdx->cur_max_video_ch; i++) {
bool interlace = false;
if (!hws_read_active_state(pdx, i, &interlace)) {
if (pdx->video[i].signal_loss_cnt == 0)
pdx->video[i].signal_loss_cnt = 1;
if (READ_ONCE(pdx->video[i].cap_active))
hws_force_no_signal_frame(&pdx->video[i],
"monitor_nosignal");
} else {
if (pdx->hw_ver > 0)
handle_hwv2_path(pdx, i);
else
handle_legacy_path(pdx, i);
update_live_resolution(pdx, i, interlace);
pdx->video[i].signal_loss_cnt = 0;
}
}
}
static inline void hws_write_if_diff(struct hws_pcie_dev *hws, u32 reg_off,
u32 new_val)
{
void __iomem *addr;
u32 old;
if (!hws || !hws->bar0_base)
return;
addr = hws->bar0_base + reg_off;
old = readl(addr);
if (old == 0xFFFFFFFF) {
hws->pci_lost = true;
return;
}
if (old != new_val) {
writel(new_val, addr);
(void)readl(addr);
}
}
static bool hws_read_active_state(struct hws_pcie_dev *pdx, unsigned int ch,
bool *interlace)
{
u32 reg;
bool active;
if (ch >= pdx->cur_max_video_ch)
return false;
reg = readl(pdx->bar0_base + HWS_REG_ACTIVE_STATUS);
active = !!(reg & BIT(ch));
if (interlace)
*interlace = !!(reg & BIT(8 + ch));
return active;
}
static void handle_hwv2_path(struct hws_pcie_dev *hws, unsigned int ch)
{
struct hws_video *vid;
u32 reg, in_fps, cur_out_res, want_out_res;
if (!hws || !hws->bar0_base || ch >= hws->max_channels)
return;
vid = &hws->video[ch];
in_fps = readl(hws->bar0_base + HWS_REG_FRAME_RATE(ch));
(void)in_fps;
want_out_res = (vid->pix.height << 16) | vid->pix.width;
cur_out_res = readl(hws->bar0_base + HWS_REG_OUT_RES(ch));
if (cur_out_res != want_out_res)
hws_write_if_diff(hws, HWS_REG_OUT_RES(ch), want_out_res);
reg = readl(hws->bar0_base + HWS_REG_BCHS(ch));
{
u8 br = reg & 0xFF;
u8 co = (reg >> 8) & 0xFF;
u8 hu = (reg >> 16) & 0xFF;
u8 sa = (reg >> 24) & 0xFF;
if (br != vid->current_brightness ||
co != vid->current_contrast || hu != vid->current_hue ||
sa != vid->current_saturation) {
u32 packed = (vid->current_saturation << 24) |
(vid->current_hue << 16) |
(vid->current_contrast << 8) |
vid->current_brightness;
hws_write_if_diff(hws, HWS_REG_BCHS(ch), packed);
}
}
reg = readl(hws->bar0_base + HWS_REG_HDCP_STATUS);
}
static void handle_legacy_path(struct hws_pcie_dev *hws, unsigned int ch)
{
(void)hws;
(void)ch;
}
static void hws_video_apply_mode_change(struct hws_pcie_dev *pdx,
unsigned int ch, u16 w, u16 h,
bool interlaced, u32 fps)
{
struct hws_video *v = &pdx->video[ch];
unsigned long flags;
bool queue_busy;
bool geometry_changed;
struct list_head done;
struct hwsvideo_buffer *b, *tmp;
if (!pdx || !pdx->bar0_base)
return;
if (ch >= pdx->max_channels)
return;
if (!w || !h || w > MAX_VIDEO_HW_W ||
(!interlaced && h > MAX_VIDEO_HW_H) ||
(interlaced && (h * 2) > MAX_VIDEO_HW_H))
return;
if (!fps || fps == 0xFFFFFFFF || fps > 240)
fps = (h == 576) ? 50 : 60;
geometry_changed = w != v->pix.width || h != v->pix.height ||
interlaced != v->pix.interlaced;
if (!geometry_changed && fps == v->current_fps)
return;
if (!geometry_changed) {
mutex_lock(&v->state_lock);
v->pix.interlaced = interlaced;
v->pix.field = interlaced ? V4L2_FIELD_INTERLACED :
V4L2_FIELD_NONE;
hws_set_current_dv_timings(v, w, h, interlaced);
v->current_fps = fps;
mutex_unlock(&v->state_lock);
return;
}
if (!mutex_trylock(&v->state_lock))
return;
INIT_LIST_HEAD(&done);
queue_busy = vb2_is_busy(&v->buffer_queue);
WRITE_ONCE(v->stop_requested, true);
WRITE_ONCE(v->cap_active, false);
smp_wmb();
hws_enable_video_capture(pdx, ch, false);
readl(pdx->bar0_base + HWS_REG_INT_STATUS);
if (v->parent && v->parent->irq >= 0)
synchronize_irq(v->parent->irq);
spin_lock_irqsave(&v->irq_lock, flags);
hws_video_collect_done_locked(v, &done);
spin_unlock_irqrestore(&v->irq_lock, flags);
v->pix.width = w;
v->pix.height = h;
v->pix.interlaced = interlaced;
hws_set_current_dv_timings(v, w, h, interlaced);
v->current_fps = fps;
hws_calc_sizeimage(v, w, h, interlaced);
v->window_valid = false;
if (queue_busy) {
struct v4l2_event ev = {
.type = V4L2_EVENT_SOURCE_CHANGE,
};
ev.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION;
v4l2_event_queue(v->video_device, &ev);
vb2_queue_error(&v->buffer_queue);
} else {
WRITE_ONCE(v->stop_requested, false);
}
hws_write_if_diff(pdx, HWS_REG_OUT_RES(ch), (h << 16) | w);
writel(v->pix.half_size / 16,
pdx->bar0_base + CVBS_IN_BUF_BASE2 + ch * PCIE_BARADDROFSIZE);
(void)readl(pdx->bar0_base + CVBS_IN_BUF_BASE2 +
ch * PCIE_BARADDROFSIZE);
WRITE_ONCE(v->last_buf_half_toggle, 0);
atomic_set(&v->sequence_number, 0);
mutex_unlock(&v->state_lock);
list_for_each_entry_safe(b, tmp, &done, list) {
list_del_init(&b->list);
vb2_buffer_done(&b->vb.vb2_buf, VB2_BUF_STATE_ERROR);
}
}
static void update_live_resolution(struct hws_pcie_dev *pdx, unsigned int ch,
bool interlace)
{
u32 reg = readl(pdx->bar0_base + HWS_REG_IN_RES(ch));
u32 fps = readl(pdx->bar0_base + HWS_REG_FRAME_RATE(ch));
u16 res_w = reg & 0xFFFF;
u16 res_h = (reg >> 16) & 0xFFFF;
struct hws_video *vid = &pdx->video[ch];
bool geometry_changed;
bool fps_changed;
bool within_hw = (res_w <= MAX_VIDEO_HW_W) &&
((!interlace && res_h <= MAX_VIDEO_HW_H) ||
(interlace && (res_h * 2) <= MAX_VIDEO_HW_H));
if (!within_hw)
return;
geometry_changed = res_w != vid->pix.width ||
res_h != vid->pix.height ||
interlace != vid->pix.interlaced;
fps_changed = fps && fps != 0xFFFFFFFF && fps <= 240 &&
fps != vid->current_fps;
if (geometry_changed || fps_changed)
hws_video_apply_mode_change(pdx, ch, res_w, res_h, interlace,
fps);
}
static int hws_open(struct file *file)
{
return v4l2_fh_open(file);
}
static const struct v4l2_file_operations hws_fops = {
.owner = THIS_MODULE,
.open = hws_open,
.release = vb2_fop_release,
.poll = vb2_fop_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = vb2_fop_mmap,
};
static int hws_subscribe_event(struct v4l2_fh *fh,
const struct v4l2_event_subscription *sub)
{
switch (sub->type) {
case V4L2_EVENT_SOURCE_CHANGE:
return v4l2_src_change_event_subscribe(fh, sub);
case V4L2_EVENT_CTRL:
return v4l2_ctrl_subscribe_event(fh, sub);
default:
return -EINVAL;
}
}
static const struct v4l2_ioctl_ops hws_ioctl_fops = {
.vidioc_querycap = hws_vidioc_querycap,
.vidioc_enum_fmt_vid_cap = hws_vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = hws_vidioc_g_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = hws_vidioc_s_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = hws_vidioc_try_fmt_vid_cap,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_expbuf = vb2_ioctl_expbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
.vidioc_enum_input = hws_vidioc_enum_input,
.vidioc_g_input = hws_vidioc_g_input,
.vidioc_s_input = hws_vidioc_s_input,
.vidioc_query_dv_timings = hws_vidioc_query_dv_timings,
.vidioc_enum_dv_timings = hws_vidioc_enum_dv_timings,
.vidioc_g_dv_timings = hws_vidioc_g_dv_timings,
.vidioc_s_dv_timings = hws_vidioc_s_dv_timings,
.vidioc_dv_timings_cap = hws_vidioc_dv_timings_cap,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = hws_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
.vidioc_g_parm = hws_vidioc_g_parm,
};
static u32 hws_calc_sizeimage(struct hws_video *v, u16 w, u16 h,
bool interlaced)
{
u32 lines = h;
u32 bytesperline = ALIGN(w * 2, 64);
u32 sizeimage, half0;
v->pix.bytesperline = bytesperline;
sizeimage = bytesperline * lines;
half0 = sizeimage / 2;
v->pix.sizeimage = sizeimage;
v->pix.half_size = half0;
v->pix.field = interlaced ? V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE;
return v->pix.sizeimage;
}
static int hws_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
unsigned int *nplanes, unsigned int sizes[],
struct device *alloc_devs[])
{
struct hws_video *vid = q->drv_priv;
if (*nplanes) {
if (sizes[0] < vid->pix.sizeimage)
return -EINVAL;
} else {
*nplanes = 1;
sizes[0] = vid->pix.sizeimage;
}
return 0;
}
static int hws_buffer_prepare(struct vb2_buffer *vb)
{
struct hws_video *vid = vb->vb2_queue->drv_priv;
struct hws_pcie_dev *hws = vid->parent;
size_t need = vid->pix.sizeimage;
dma_addr_t dma_addr;
if (vb2_plane_size(vb, 0) < need)
return -EINVAL;
dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
if (dma_addr & 0x3F) {
dev_err(&hws->pdev->dev,
"Buffer DMA address 0x%llx not 64-byte aligned\n",
(unsigned long long)dma_addr);
return -EINVAL;
}
vb2_set_plane_payload(vb, 0, need);
return 0;
}
static void hws_buffer_queue(struct vb2_buffer *vb)
{
struct hws_video *vid = vb->vb2_queue->drv_priv;
struct hwsvideo_buffer *buf = to_hwsbuf(vb);
struct hws_pcie_dev *hws = vid->parent;
unsigned long flags;
dev_dbg(&hws->pdev->dev,
"buffer_queue(ch=%u): vb=%p sizeimage=%u q_active=%d\n",
vid->channel_index, vb, vid->pix.sizeimage,
READ_ONCE(vid->cap_active));
buf->slot = 0;
spin_lock_irqsave(&vid->irq_lock, flags);
list_add_tail(&buf->list, &vid->capture_queue);
vid->queued_count++;
if (READ_ONCE(vid->cap_active) && !vid->active) {
dma_addr_t dma_addr;
dev_dbg(&hws->pdev->dev,
"buffer_queue(ch=%u): priming first vb=%p\n",
vid->channel_index, &buf->vb.vb2_buf);
list_del_init(&buf->list);
vid->queued_count--;
vid->active = buf;
dma_addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
hws_program_dma_for_addr(vid->parent, vid->channel_index,
dma_addr);
iowrite32(lower_32_bits(dma_addr),
hws->bar0_base + HWS_REG_DMA_ADDR(vid->channel_index));
wmb();
hws_enable_video_capture(hws, vid->channel_index, true);
hws_prime_next_locked(vid);
} else if (READ_ONCE(vid->cap_active) && vid->active) {
hws_prime_next_locked(vid);
}
spin_unlock_irqrestore(&vid->irq_lock, flags);
}
static int hws_start_streaming(struct vb2_queue *q, unsigned int count)
{
struct hws_video *v = q->drv_priv;
struct hws_pcie_dev *hws = v->parent;
struct hwsvideo_buffer *to_program = NULL;
struct vb2_buffer *prog_vb2 = NULL;
unsigned long flags;
int ret;
dev_dbg(&hws->pdev->dev, "start_streaming: ch=%u count=%u\n",
v->channel_index, count);
ret = hws_check_card_status(hws);
if (ret) {
struct hwsvideo_buffer *b, *tmp;
unsigned long f;
LIST_HEAD(queued);
spin_lock_irqsave(&v->irq_lock, f);
if (v->active) {
list_add_tail(&v->active->list, &queued);
v->active = NULL;
}
if (v->next_prepared) {
list_add_tail(&v->next_prepared->list, &queued);
v->next_prepared = NULL;
}
while (!list_empty(&v->capture_queue)) {
b = list_first_entry(&v->capture_queue,
struct hwsvideo_buffer, list);
list_move_tail(&b->list, &queued);
}
spin_unlock_irqrestore(&v->irq_lock, f);
list_for_each_entry_safe(b, tmp, &queued, list) {
list_del_init(&b->list);
vb2_buffer_done(&b->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
}
return ret;
}
(void)hws_read_active_state(hws, v->channel_index,
&v->pix.interlaced);
lockdep_assert_held(&v->state_lock);
WRITE_ONCE(v->stop_requested, false);
WRITE_ONCE(v->cap_active, true);
WRITE_ONCE(v->half_seen, false);
WRITE_ONCE(v->last_buf_half_toggle, 0);
spin_lock_irqsave(&v->irq_lock, flags);
if (!v->active && !list_empty(&v->capture_queue)) {
to_program = list_first_entry(&v->capture_queue,
struct hwsvideo_buffer, list);
list_del_init(&to_program->list);
v->queued_count--;
v->active = to_program;
prog_vb2 = &to_program->vb.vb2_buf;
dev_dbg(&hws->pdev->dev,
"start_streaming: ch=%u took buffer %p\n",
v->channel_index, to_program);
}
spin_unlock_irqrestore(&v->irq_lock, flags);
if (to_program) {
if (!prog_vb2)
prog_vb2 = &to_program->vb.vb2_buf;
{
dma_addr_t dma_addr;
dma_addr = vb2_dma_contig_plane_dma_addr(prog_vb2, 0);
hws_program_dma_for_addr(hws, v->channel_index, dma_addr);
iowrite32(lower_32_bits(dma_addr),
hws->bar0_base +
HWS_REG_DMA_ADDR(v->channel_index));
dev_dbg(&hws->pdev->dev,
"start_streaming: ch=%u programmed buffer %p dma=0x%08x\n",
v->channel_index, to_program,
lower_32_bits(dma_addr));
(void)readl(hws->bar0_base + HWS_REG_INT_STATUS);
}
wmb();
hws_enable_video_capture(hws, v->channel_index, true);
{
unsigned long pf;
spin_lock_irqsave(&v->irq_lock, pf);
hws_prime_next_locked(v);
spin_unlock_irqrestore(&v->irq_lock, pf);
}
} else {
dev_dbg(&hws->pdev->dev,
"start_streaming: ch=%u no buffer yet (will arm on QBUF)\n",
v->channel_index);
}
return 0;
}
static void hws_log_video_state(struct hws_video *v, const char *action,
const char *phase)
{
struct hws_pcie_dev *hws = v->parent;
unsigned long flags;
unsigned int queued = 0;
unsigned int tracked = 0;
unsigned int seq = 0;
struct hwsvideo_buffer *b;
bool streaming = vb2_is_streaming(&v->buffer_queue);
bool cap_active;
bool stop_requested;
struct hwsvideo_buffer *active;
struct hwsvideo_buffer *next_prepared;
spin_lock_irqsave(&v->irq_lock, flags);
list_for_each_entry(b, &v->capture_queue, list)
queued++;
cap_active = READ_ONCE(v->cap_active);
stop_requested = READ_ONCE(v->stop_requested);
active = v->active;
next_prepared = v->next_prepared;
tracked = v->queued_count;
seq = (u32)atomic_read(&v->sequence_number);
spin_unlock_irqrestore(&v->irq_lock, flags);
dev_dbg(&hws->pdev->dev,
"video:%s:%s ch=%u streaming=%d cap=%d stop=%d active=%p next=%p queued=%u tracked=%u seq=%u\n",
action, phase, v->channel_index, streaming, cap_active,
stop_requested, active, next_prepared, queued, tracked, seq);
}
static void hws_stop_streaming(struct vb2_queue *q)
{
struct hws_video *v = q->drv_priv;
struct hws_pcie_dev *hws = v->parent;
unsigned long flags;
struct hwsvideo_buffer *b, *tmp;
LIST_HEAD(done);
unsigned int done_cnt = 0;
u64 start_ns = ktime_get_mono_fast_ns();
hws_log_video_state(v, "streamoff", "begin");
lockdep_assert_held(&v->state_lock);
WRITE_ONCE(v->cap_active, false);
WRITE_ONCE(v->stop_requested, true);
hws_enable_video_capture(v->parent, v->channel_index, false);
spin_lock_irqsave(&v->irq_lock, flags);
hws_video_collect_done_locked(v, &done);
spin_unlock_irqrestore(&v->irq_lock, flags);
list_for_each_entry_safe(b, tmp, &done, list) {
list_del_init(&b->list);
vb2_buffer_done(&b->vb.vb2_buf, VB2_BUF_STATE_ERROR);
done_cnt++;
}
dev_dbg(&hws->pdev->dev,
"video:streamoff:done ch=%u completed=%u (%lluus)\n",
v->channel_index, done_cnt, hws_elapsed_us(start_ns));
hws_log_video_state(v, "streamoff", "end");
}
static const struct vb2_ops hwspcie_video_qops = {
.queue_setup = hws_queue_setup,
.buf_prepare = hws_buffer_prepare,
.buf_init = hws_buf_init,
.buf_finish = hws_buf_finish,
.buf_cleanup = hws_buf_cleanup,
.buf_queue = hws_buffer_queue,
.start_streaming = hws_start_streaming,
.stop_streaming = hws_stop_streaming,
};
int hws_video_register(struct hws_pcie_dev *dev)
{
int i, ret;
ret = v4l2_device_register(&dev->pdev->dev, &dev->v4l2_device);
if (ret) {
dev_err(&dev->pdev->dev, "v4l2_device_register failed: %d\n",
ret);
return ret;
}
for (i = 0; i < dev->cur_max_video_ch; i++) {
struct hws_video *ch = &dev->video[i];
struct video_device *vdev;
struct vb2_queue *q;
vdev = video_device_alloc();
if (!vdev) {
dev_err(&dev->pdev->dev,
"video_device_alloc ch%u failed\n", i);
ret = -ENOMEM;
goto err_unwind;
}
ch->video_device = vdev;
snprintf(vdev->name, sizeof(vdev->name), "%s-hdmi%u",
KBUILD_MODNAME, i);
vdev->v4l2_dev = &dev->v4l2_device;
vdev->fops = &hws_fops;
vdev->ioctl_ops = &hws_ioctl_fops;
vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
vdev->lock = &ch->state_lock;
vdev->ctrl_handler = &ch->control_handler;
vdev->vfl_dir = VFL_DIR_RX;
vdev->release = video_device_release;
if (ch->control_handler.error) {
ret = ch->control_handler.error;
goto err_unwind;
}
video_set_drvdata(vdev, ch);
q = &ch->buffer_queue;
memset(q, 0, sizeof(*q));
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_DMABUF;
q->drv_priv = ch;
q->buf_struct_size = sizeof(struct hwsvideo_buffer);
q->ops = &hwspcie_video_qops;
q->mem_ops = &vb2_dma_contig_memops;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->lock = &ch->state_lock;
q->min_queued_buffers = 1;
q->dev = &dev->pdev->dev;
ret = vb2_queue_init(q);
vdev->queue = q;
if (ret) {
dev_err(&dev->pdev->dev,
"vb2_queue_init ch%u failed: %d\n", i, ret);
goto err_unwind;
}
if (ch->control_handler.error) {
ret = ch->control_handler.error;
dev_err(&dev->pdev->dev,
"ctrl handler ch%u error: %d\n", i, ret);
goto err_unwind;
}
v4l2_ctrl_handler_setup(&ch->control_handler);
ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
if (ret) {
dev_err(&dev->pdev->dev,
"video_register_device ch%u failed: %d\n", i,
ret);
goto err_unwind;
}
}
return 0;
err_unwind:
for (; i >= 0; i--) {
struct hws_video *ch = &dev->video[i];
hws_video_release_registration(ch);
}
v4l2_device_unregister(&dev->v4l2_device);
return ret;
}
void hws_video_unregister(struct hws_pcie_dev *dev)
{
int i;
if (!dev)
return;
for (i = 0; i < dev->cur_max_video_ch; i++) {
struct hws_video *ch = &dev->video[i];
hws_video_release_registration(ch);
v4l2_ctrl_handler_free(&ch->control_handler);
}
v4l2_device_unregister(&dev->v4l2_device);
}
int hws_video_quiesce(struct hws_pcie_dev *hws, const char *reason)
{
int i, ret = 0;
u64 start_ns = ktime_get_mono_fast_ns();
dev_dbg(&hws->pdev->dev, "video:%s:begin channels=%u\n", reason,
hws->cur_max_video_ch);
for (i = 0; i < hws->cur_max_video_ch; i++) {
struct hws_video *vid = &hws->video[i];
struct vb2_queue *q = &vid->buffer_queue;
u64 ch_start_ns = ktime_get_mono_fast_ns();
bool streaming;
if (!q || !q->ops) {
dev_dbg(&hws->pdev->dev,
"video:%s:ch=%d skipped queue-unavailable\n",
reason, i);
continue;
}
streaming = vb2_is_streaming(q);
hws_log_video_state(vid, reason, "channel");
if (streaming) {
int r = vb2_streamoff(q, q->type);
dev_dbg(&hws->pdev->dev,
"video:%s:ch=%d streamoff ret=%d (%lluus)\n",
reason, i, r, hws_elapsed_us(ch_start_ns));
if (r && !ret)
ret = r;
} else {
dev_dbg(&hws->pdev->dev,
"video:%s:ch=%d idle (%lluus)\n",
reason, i, hws_elapsed_us(ch_start_ns));
}
}
dev_dbg(&hws->pdev->dev, "video:%s:done ret=%d (%lluus)\n", reason,
ret, hws_elapsed_us(start_ns));
return ret;
}
void hws_video_pm_resume(struct hws_pcie_dev *hws)
{
}