// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021-2023 Digiteq Automotive
* author: Martin Tuma <martin.tuma@digiteqautomotive.com>
*
* This is the v4l2 input device module. It initializes the signal deserializers
* and creates the v4l2 video devices. The input signal can change at any time
* which is handled by the "timings" callbacks and an IRQ based watcher, that
* emits the V4L2_EVENT_SOURCE_CHANGE event in case of a signal source change.
*
* When the device is in loopback mode (a direct, in HW, in->out frame passing
* mode) the card's frame queue must be running regardless of whether a v4l2
* stream is running and the output parameters like frame buffers padding must
* be in sync with the input parameters.
*/
#include <linux/pci.h>
#include <linux/workqueue.h>
#include <linux/align.h>
#include <linux/dma/amd_xdma.h>
#include <linux/v4l2-dv-timings.h>
#include <media/v4l2-ioctl.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-dma-sg.h>
#include <media/v4l2-dv-timings.h>
#include <media/v4l2-event.h>
#include "mgb4_core.h"
#include "mgb4_dma.h"
#include "mgb4_sysfs.h"
#include "mgb4_io.h"
#include "mgb4_vout.h"
#include "mgb4_vin.h"
ATTRIBUTE_GROUPS(mgb4_fpdl3_in);
ATTRIBUTE_GROUPS(mgb4_gmsl_in);
static const struct mgb4_vin_config vin_cfg[] = {
{0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28, 0xE8}},
{1, 1, 1, 7, {0x40, 0x30, 0x34, 0x38, 0x4C, 0x44, 0x48, 0x50, 0x54, 0x58, 0xEC}}
};
static const struct i2c_board_info fpdl3_deser_info[] = {
{I2C_BOARD_INFO("deserializer1", 0x38)},
{I2C_BOARD_INFO("deserializer2", 0x36)},
};
static const struct i2c_board_info gmsl_deser_info[] = {
{I2C_BOARD_INFO("deserializer1", 0x4C)},
{I2C_BOARD_INFO("deserializer2", 0x2A)},
};
static const struct mgb4_i2c_kv fpdl3_i2c[] = {
{0x06, 0xFF, 0x04}, {0x07, 0xFF, 0x01}, {0x45, 0xFF, 0xE8},
{0x49, 0xFF, 0x00}, {0x34, 0xFF, 0x00}, {0x23, 0xFF, 0x00}
};
static const struct mgb4_i2c_kv gmsl_i2c[] = {
{0x01, 0x03, 0x03}, {0x300, 0x0C, 0x0C}, {0x03, 0xC0, 0xC0},
{0x1CE, 0x0E, 0x0E}, {0x11, 0x05, 0x00}, {0x05, 0xC0, 0x40},
{0x307, 0x0F, 0x00}, {0xA0, 0x03, 0x00}, {0x3E0, 0x07, 0x07},
{0x308, 0x01, 0x01}, {0x10, 0x20, 0x20}, {0x300, 0x40, 0x40}
};
static const struct v4l2_dv_timings_cap video_timings_cap = {
.type = V4L2_DV_BT_656_1120,
.bt = {
.min_width = 320,
.max_width = 4096,
.min_height = 240,
.max_height = 2160,
.min_pixelclock = 1843200, /* 320 x 240 x 24Hz */
.max_pixelclock = 530841600, /* 4096 x 2160 x 60Hz */
.standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF,
.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
V4L2_DV_BT_CAP_CUSTOM,
},
};
/* Dummy timings when no signal present */
static const struct v4l2_dv_timings cea1080p60 = V4L2_DV_BT_CEA_1920X1080P60;
/*
* Returns the video output connected with the given video input if the input
* is in loopback mode.
*/
static struct mgb4_vout_dev *loopback_dev(struct mgb4_vin_dev *vindev, int i)
{
struct mgb4_vout_dev *voutdev;
u32 config;
voutdev = vindev->mgbdev->vout[i];
if (!voutdev)
return NULL;
config = mgb4_read_reg(&voutdev->mgbdev->video,
voutdev->config->regs.config);
if ((config & 0xc) >> 2 == vindev->config->id)
return voutdev;
return NULL;
}
/*
* Check, whether the loopback mode - a HW INPUT->OUTPUT transmission - is
* enabled on the given input.
*/
static int loopback_active(struct mgb4_vin_dev *vindev)
{
int i;
for (i = 0; i < MGB4_VOUT_DEVICES; i++)
if (loopback_dev(vindev, i))
return 1;
return 0;
}
/*
* Set the output frame buffer padding of all outputs connected with the given
* input when the video input is set to loopback mode. The paddings must be
* the same for the loopback to work properly.
*/
static void set_loopback_padding(struct mgb4_vin_dev *vindev, u32 padding)
{
struct mgb4_regs *video = &vindev->mgbdev->video;
struct mgb4_vout_dev *voutdev;
int i;
for (i = 0; i < MGB4_VOUT_DEVICES; i++) {
voutdev = loopback_dev(vindev, i);
if (voutdev)
mgb4_write_reg(video, voutdev->config->regs.padding,
padding);
}
}
static int get_timings(struct mgb4_vin_dev *vindev,
struct v4l2_dv_timings *timings)
{
struct mgb4_regs *video = &vindev->mgbdev->video;
const struct mgb4_vin_regs *regs = &vindev->config->regs;
u32 status = mgb4_read_reg(video, regs->status);
u32 pclk = mgb4_read_reg(video, regs->pclk);
u32 signal = mgb4_read_reg(video, regs->signal);
u32 signal2 = mgb4_read_reg(