Files

1043 lines
29 KiB
C

// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
/*
* Copyright (C) 2025 Amlogic, Inc. All rights reserved
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/version.h>
#include <linux/delay.h>
#include <media/mipi-csi2.h>
#include <media/v4l2-async.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-mc.h>
#include <media/v4l2-subdev.h>
/*
* Adapter Block Diagram
* ---------------------
*
* +--------------------------------------------+
* | Adapter |
* |--------------------------------------------|
* +------------+ | | | | | +-----+
* | MIPI CSI-2 |--->| Adapt_in -> Afifo -> Router -> Formatter |--->| ISP |
* +------------+ | | | | | +-----+
* +--------------------------------------------+
*
*/
#define C4_ADAP_CLOCK_NUM_MAX 3
#define C4_ADAP_SUBDEV_NAME "c4-adap"
#define V4L2_CID_C4_ADAP_BASE (V4L2_CID_USER_BASE + 0xe000)
#define V4L2_CID_C4_ADAP_IMAGE_MODE (V4L2_CID_C4_ADAP_BASE + 1)
enum c4_adap_image_mode {
C4_ADAP_IMAGE_SDR,
C4_ADAP_IMAGE_HDR_VC_INFO,
C4_ADAP_IMAGE_HDR_LINE_INFO,
C4_ADAP_IMAGE_MAX
};
/* C4 Adapter register */
#define CSI2_CLK_RESET (0x00 << 2)
#define CSI2_CLK_RESET_SW_RESET_MASK BIT(0)
#define CSI2_CLK_RESET_SW_RESET_APPLY BIT(0)
#define CSI2_CLK_RESET_SW_RESET_RELEASE (0 << 0)
#define CSI2_CLK_RESET_CLK_ENABLE_MASK BIT(1)
#define CSI2_CLK_RESET_CLK_ENABLE_EN BIT(1)
#define CSI2_CLK_RESET_CLK_ENABLE_DIS (0 << 1)
#define CSI2_CLK_RESET_CLK_AUTO_GATE_MASK BIT(2)
#define CSI2_CLK_RESET_CLK_AUTO_GATE_EN (0 << 2)
#define CSI2_CLK_RESET_CLK_AUTO_GATE_DIS BIT(2)
#define CSI2_GEN_CTRL0 (0x01 << 2)
#define CSI2_GEN_CTRL0_VC0_MASK BIT(0)
#define CSI2_GEN_CTRL0_VC0_EN BIT(0)
#define CSI2_GEN_CTRL0_VC0_DIS (0 << 0)
#define CSI2_GEN_CTRL0_VC1_MASK BIT(1)
#define CSI2_GEN_CTRL0_VC1_EN BIT(1)
#define CSI2_GEN_CTRL0_VC1_DIS (0 << 1)
#define CSI2_GEN_CTRL0_VC2_MASK BIT(2)
#define CSI2_GEN_CTRL0_VC2_EN BIT(2)
#define CSI2_GEN_CTRL0_VC2_DIS (0 << 2)
#define CSI2_GEN_CTRL0_VC3_MASK BIT(3)
#define CSI2_GEN_CTRL0_VC3_EN BIT(3)
#define CSI2_GEN_CTRL0_VC3_DIS (0 << 3)
#define CSI2_GEN_CTRL0_RAW_EN_MASK BIT(16)
#define CSI2_GEN_CTRL0_RAW_EN BIT(16)
#define CSI2_GEN_CTRL0_RAW_DIS (0 << 16)
#define CSI2_GEN_CTRL0_YUV_EN_MASK BIT(17)
#define CSI2_GEN_CTRL0_YUV_EN BIT(17)
#define CSI2_GEN_CTRL0_YUV_DIS (0 << 17)
#define CSI2_GEN_CTRL1 (0x02 << 2)
#define CSI2_VC_MODE (0x07 << 2)
#define CSI2_VC_MODE_ASSIGN_VC_MODE_MASK GENMASK(1, 0)
#define CSI2_VC_MODE_ASSIGN_VC_MODE_NO_VC_REASSIGN (0 << 0)
#define CSI2_VC_MODE_ASSIGN_VC_MODE_EVEN_ODD_VC_VAL BIT(0)
#define CSI2_VC_MODE_ASSIGN_VC_MODE_FIRST_64BITS_VC_VAL (2 << 0)
#define CSI2_VC_MODE_ASSIGN_VC_MODE_REASSIGN_VC_MODE3 (3 << 0)
#define CSI2_VC_MODE_VC_VAL0_MASK GENMASK(3, 2)
#define CSI2_VC_MODE_VC_VAL0_0 (0 << 2)
#define CSI2_VC_MODE_VC_VAL1_MASK GENMASK(5, 4)
#define CSI2_VC_MODE_VC_VAL1_1 BIT(4)
#define CSI2_VC_MODE_VS_ISP_SEL_VC_MASK GENMASK(19, 16)
#define CSI2_VC_MODE_VS_ISP_SEL_VC_ALL (0xf << 16)
#define CSI2_VC_MODE_HS_ISP_SEL_VC_MASK GENMASK(23, 20)
#define CSI2_VC_MODE_HS_ISP_SEL_VC_ALL (0xf << 20)
#define CSI2_VC_MODE_VS_MEM_SEL_VC_MASK GENMASK(27, 24)
#define CSI2_VC_MODE_VS_MEM_SEL_VC_DIS (0 << 24)
#define CSI2_VC_MODE_VS_NONE_PIX_VC_MASK GENMASK(31, 28)
#define CSI2_VC_MODE_VS_NONE_PIX_VC_DIS (0 << 28)
#define CSI2_VC_MODE2_MATCH_MASK_A_L (0x08 << 2)
#define CSI2_VC_MODE2_MATCH_MASK_A_L_MASK GENMASK(31, 0)
#define CSI2_VC_MODE2_MATCH_MASK_A_L_RAW10 (0x6e6e6e6e)
#define CSI2_VC_MODE2_MATCH_MASK_A_L_RAW12 (0xff000101)
#define CSI2_VC_MODE2_MATCH_MASK_A_H (0x09 << 2)
#define CSI2_VC_MODE2_MATCH_MASK_A_H_MASK GENMASK(31, 0)
#define CSI2_VC_MODE2_MATCH_MASK_A_H_RAW10 (0xffffff00)
#define CSI2_VC_MODE2_MATCH_MASK_A_H_RAW12 (0xffffffff)
#define CSI2_VC_MODE2_MATCH_A_L (0x0a << 2)
#define CSI2_VC_MODE2_MATCH_A_L_MASK GENMASK(31, 0)
#define CSI2_VC_MODE2_MATCH_A_L_RAW10 (0x90909090)
#define CSI2_VC_MODE2_MATCH_A_L_RAW12 (0x00112424)
#define CSI2_VC_MODE2_MATCH_A_H (0x0b << 2)
#define CSI2_VC_MODE2_MATCH_A_H_MASK GENMASK(31, 0)
#define CSI2_VC_MODE2_MATCH_A_H_RAW10 (0x00000055)
#define CSI2_VC_MODE2_MATCH_A_H_RAW12 (0x00000000)
#define CSI2_VC_MODE2_MATCH_B_L (0x0c << 2)
#define CSI2_VC_MODE2_MATCH_B_L_MASK GENMASK(31, 0)
#define CSI2_VC_MODE2_MATCH_B_L_RAW10 (0x90909090)
#define CSI2_VC_MODE2_MATCH_B_L_RAW12 (0x00222424)
#define CSI2_VC_MODE2_MATCH_B_H (0x0d << 2)
#define CSI2_VC_MODE2_MATCH_B_H_MASK GENMASK(31, 0)
#define CSI2_VC_MODE2_MATCH_B_H_RAW10 (0x000000aa)
#define CSI2_VC_MODE2_MATCH_B_H_RAW12 (0x00000000)
#define CSI2_DDR_MAX_BYTES_OTHER (0x13 << 2)
#define CSI2_INTERRUPT_CTRL_STAT (0x14 << 2)
#define CSI2_VC_MODE2_MATCH_MASK_B_L (0x15 << 2)
#define CSI2_VC_MODE2_MATCH_MASK_B_L_MASK GENMASK(31, 0)
#define CSI2_VC_MODE2_MATCH_MASK_B_L_RAW10 (0x6e6e6e6e)
#define CSI2_VC_MODE2_MATCH_MASK_B_L_RAW12 (0xff000101)
#define CSI2_VC_MODE2_MATCH_MASK_B_H (0x16 << 2)
#define CSI2_VC_MODE2_MATCH_MASK_B_H_MASK GENMASK(31, 0)
#define CSI2_VC_MODE2_MATCH_MASK_B_H_RAW10 (0xffffff00)
#define CSI2_VC_MODE2_MATCH_MASK_B_H_RAW12 (0xffffffff)
#define CSI2_ROUTER_CTRL0 (0x17 << 2)
#define CSI2_ROUTER_CTRL0_VC0_SEL_VS_MASK GENMASK(22, 21)
#define CSI2_ROUTER_CTRL0_VC0_SEL_VS_0 (0 << 21)
#define CSI2_ROUTER_CTRL0_VC1_SEL_VS_MASK GENMASK(24, 23)
#define CSI2_ROUTER_CTRL0_VC1_SEL_VS_0 (0 << 23)
#define CSI2_ROUTER_CTRL0_VC1_SEL_VS_1 BIT(23)
#define CSI2_ROUTER_CTRL1 (0x18 << 2)
#define CSI2_FMT_CTRL0 (0x19 << 2)
#define CSI2_FMT_CTRL1 (0x1a << 2)
#define CSI2_ADAPT_IN_CTRL0 (0x1b << 2)
#define CSI2_ROUTER_CTRL2 (0x1c << 2)
#define CSI2_FMT_CTRL2 (0x1d << 2)
#define CSI2_ROUTER_CTRL3 (0x1e << 2)
#define CSI2_ROUTER_CTRL4 (0x1f << 2)
#define CSI2_GEN_STAT0 (0x20 << 2)
#define CSI2_ERR_STAT0 (0x21 << 2)
#define CSI2_PIC_SIZE_STAT (0x22 << 2)
#define CSI2_STAT_GEN_SHORT_08 (0x28 << 2)
#define CSI2_STAT_GEN_SHORT_09 (0x29 << 2)
#define CSI2_STAT_GEN_SHORT_0A (0x2a << 2)
#define CSI2_STAT_GEN_SHORT_0B (0x2b << 2)
#define CSI2_STAT_GEN_SHORT_0C (0x2c << 2)
#define CSI2_STAT_GEN_SHORT_0D (0x2d << 2)
#define CSI2_STAT_GEN_SHORT_0E (0x2e << 2)
#define CSI2_STAT_GEN_SHORT_0F (0x2f << 2)
#define CSI2_STAT_TYPE_RCVD_L (0x30 << 2)
#define CSI2_STAT_TYPE_RCVD_H (0x31 << 2)
#define CSI2_DDR_WPTR_STAT_PIX_B (0x35 << 2)
#define CSI2_STAT_MEM_3 (0x36 << 2)
#define CSI2_DDR_OTHER_STRIDE_PIX (0x37 << 2)
#define C4_ADAP_MAX_WIDTH 4656
#define C4_ADAP_MIN_WIDTH 160
#define C4_ADAP_MAX_HEIGHT 3496
#define C4_ADAP_MIN_HEIGHT 120
#define C4_ADAP_DEFAULT_WIDTH 1920
#define C4_ADAP_DEFAULT_HEIGHT 1080
#define C4_ADAP_DEFAULT_FMT MEDIA_BUS_FMT_SRGGB10_1X10
enum c4_adap_id {
C4_ADAP_0,
C4_ADAP_1,
C4_ADAP_2,
C4_ADAP_3,
C4_ADAP_MAX
};
/* C4 adapter pad list */
enum {
C4_ADAP_PAD_SINK,
C4_ADAP_PAD_SRC,
C4_ADAP_PAD_MAX
};
struct c4_adap_match_info {
enum c4_adap_id id;
char *clocks[C4_ADAP_CLOCK_NUM_MAX];
u32 clock_num;
};
struct c4_adap_device {
struct device *dev;
void __iomem *base;
struct clk_bulk_data clks[C4_ADAP_CLOCK_NUM_MAX];
struct v4l2_subdev sd;
struct media_pad pads[C4_ADAP_PAD_MAX];
struct v4l2_async_notifier notifier;
struct media_pad *src_pad;
struct v4l2_ctrl_handler ctrls;
enum c4_adap_image_mode img_mode;
const struct c4_adap_match_info *info;
struct proc_dir_entry *proc_entry;
};
struct c4_adap_pix_format {
u32 code;
u8 type;
};
static const struct c4_adap_pix_format c4_adap_formats[] = {
{ MEDIA_BUS_FMT_SBGGR10_1X10, MIPI_CSI2_DT_RAW10 },
{ MEDIA_BUS_FMT_SGBRG10_1X10, MIPI_CSI2_DT_RAW10 },
{ MEDIA_BUS_FMT_SGRBG10_1X10, MIPI_CSI2_DT_RAW10 },
{ MEDIA_BUS_FMT_SRGGB10_1X10, MIPI_CSI2_DT_RAW10 },
{ MEDIA_BUS_FMT_SBGGR12_1X12, MIPI_CSI2_DT_RAW12 },
{ MEDIA_BUS_FMT_SGBRG12_1X12, MIPI_CSI2_DT_RAW12 },
{ MEDIA_BUS_FMT_SGRBG12_1X12, MIPI_CSI2_DT_RAW12 },
{ MEDIA_BUS_FMT_SRGGB12_1X12, MIPI_CSI2_DT_RAW12 },
{ MEDIA_BUS_FMT_UYVY8_2X8, MIPI_CSI2_DT_YUV422_8B},
};
static const struct c4_adap_pix_format *c4_adap_find_format(u32 code)
{
for (unsigned int i = 0; i < ARRAY_SIZE(c4_adap_formats); i++)
if (code == c4_adap_formats[i].code)
return &c4_adap_formats[i];
return NULL;
}
static u32 c4_adap_read(struct c4_adap_device *adap, u32 reg)
{
return readl(adap->base + reg);
}
static void c4_adap_write(struct c4_adap_device *adap, u32 reg, u32 val)
{
writel(val, adap->base + reg);
}
static u32 *c4_adap_fe_status(struct c4_adap_device *adap)
{
static u32 adap_debuginfo[4];
adap_debuginfo[0] = c4_adap_read(adap, CSI2_ERR_STAT0);
adap_debuginfo[1] = c4_adap_read(adap, CSI2_PIC_SIZE_STAT);
adap_debuginfo[2] = c4_adap_read(adap, CSI2_VC_MODE);
adap_debuginfo[3] = c4_adap_read(adap, CSI2_INTERRUPT_CTRL_STAT);
return adap_debuginfo;
}
static void c4_adap_update_bits(struct c4_adap_device *adap, u32 reg,
u32 mask, u32 val)
{
void __iomem *addr = adap->base + reg;
u32 orig, tmp;
orig = readl(addr);
tmp = orig & ~mask;
tmp |= val & mask;
if (tmp != orig)
writel(tmp, addr);
}
static void c4_adap_cfg_reset(struct c4_adap_device *adap)
{
c4_adap_update_bits(adap, CSI2_CLK_RESET,
CSI2_CLK_RESET_SW_RESET_MASK,
CSI2_CLK_RESET_SW_RESET_APPLY);
c4_adap_update_bits(adap, CSI2_CLK_RESET,
CSI2_CLK_RESET_SW_RESET_MASK,
CSI2_CLK_RESET_SW_RESET_RELEASE);
c4_adap_update_bits(adap, CSI2_CLK_RESET,
CSI2_CLK_RESET_CLK_ENABLE_MASK,
CSI2_CLK_RESET_CLK_ENABLE_EN);
c4_adap_update_bits(adap, CSI2_CLK_RESET,
CSI2_CLK_RESET_CLK_AUTO_GATE_MASK,
CSI2_CLK_RESET_CLK_AUTO_GATE_DIS);
}
static void c4_adap_cfg_lineinfo_raw10(struct c4_adap_device *adap)
{
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_MASK_A_L,
CSI2_VC_MODE2_MATCH_MASK_A_L_RAW10);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_MASK_A_H,
CSI2_VC_MODE2_MATCH_MASK_A_H_RAW10);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_A_L,
CSI2_VC_MODE2_MATCH_A_L_RAW10);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_A_H,
CSI2_VC_MODE2_MATCH_A_H_RAW10);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_MASK_B_L,
CSI2_VC_MODE2_MATCH_MASK_B_L_RAW10);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_MASK_B_H,
CSI2_VC_MODE2_MATCH_MASK_B_H_RAW10);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_B_L,
CSI2_VC_MODE2_MATCH_B_L_RAW10);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_B_H,
CSI2_VC_MODE2_MATCH_B_H_RAW10);
}
static void c4_adap_cfg_lineinfo_raw12(struct c4_adap_device *adap)
{
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_MASK_A_L,
CSI2_VC_MODE2_MATCH_MASK_A_L_RAW12);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_MASK_A_H,
CSI2_VC_MODE2_MATCH_MASK_A_H_RAW12);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_A_L,
CSI2_VC_MODE2_MATCH_A_L_RAW12);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_A_H,
CSI2_VC_MODE2_MATCH_A_H_RAW12);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_MASK_B_L,
CSI2_VC_MODE2_MATCH_MASK_B_L_RAW12);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_MASK_B_H,
CSI2_VC_MODE2_MATCH_MASK_B_H_RAW12);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_B_L,
CSI2_VC_MODE2_MATCH_B_L_RAW12);
c4_adap_write(adap, CSI2_VC_MODE2_MATCH_B_H,
CSI2_VC_MODE2_MATCH_B_H_RAW12);
}
static void c4_adap_cfg_with_lineinfo(struct c4_adap_device *adap,
struct v4l2_mbus_framefmt *fmt)
{
const struct c4_adap_pix_format *pix;
if (adap->img_mode != C4_ADAP_IMAGE_HDR_LINE_INFO)
return;
pix = c4_adap_find_format(fmt->code);
c4_adap_update_bits(adap, CSI2_VC_MODE,
CSI2_VC_MODE_ASSIGN_VC_MODE_MASK,
CSI2_VC_MODE_ASSIGN_VC_MODE_FIRST_64BITS_VC_VAL);
c4_adap_update_bits(adap, CSI2_VC_MODE,
CSI2_VC_MODE_VC_VAL0_MASK,
CSI2_VC_MODE_VC_VAL0_0);
c4_adap_update_bits(adap, CSI2_VC_MODE,
CSI2_VC_MODE_VC_VAL1_MASK,
CSI2_VC_MODE_VC_VAL1_1);
c4_adap_update_bits(adap, CSI2_ROUTER_CTRL0,
CSI2_ROUTER_CTRL0_VC0_SEL_VS_MASK,
CSI2_ROUTER_CTRL0_VC0_SEL_VS_0);
c4_adap_update_bits(adap, CSI2_ROUTER_CTRL0,
CSI2_ROUTER_CTRL0_VC1_SEL_VS_MASK,
CSI2_ROUTER_CTRL0_VC1_SEL_VS_0);
if (pix->type == MIPI_CSI2_DT_RAW10)
c4_adap_cfg_lineinfo_raw10(adap);
else if (pix->type == MIPI_CSI2_DT_RAW12)
c4_adap_cfg_lineinfo_raw12(adap);
else
dev_err(adap->dev, "Unsupport date type: 0x%x\n", pix->type);
}
static void c4_adap_cfg_with_vc(struct c4_adap_device *adap)
{
if (adap->img_mode != C4_ADAP_IMAGE_HDR_VC_INFO)
return;
c4_adap_update_bits(adap, CSI2_VC_MODE,
CSI2_VC_MODE_ASSIGN_VC_MODE_MASK,
CSI2_VC_MODE_ASSIGN_VC_MODE_NO_VC_REASSIGN);
}
static void c4_adap_cfg_vc_mode(struct c4_adap_device *adap,
struct v4l2_mbus_framefmt *fmt)
{
c4_adap_update_bits(adap, CSI2_VC_MODE,
CSI2_VC_MODE_VS_MEM_SEL_VC_MASK,
0xf << 24);
c4_adap_update_bits(adap, CSI2_VC_MODE,
CSI2_VC_MODE_VS_NONE_PIX_VC_MASK,
0xf << 28);
c4_adap_update_bits(adap, CSI2_VC_MODE,
CSI2_VC_MODE_VS_ISP_SEL_VC_MASK,
CSI2_VC_MODE_VS_ISP_SEL_VC_ALL);
c4_adap_update_bits(adap, CSI2_VC_MODE,
CSI2_VC_MODE_HS_ISP_SEL_VC_MASK,
CSI2_VC_MODE_HS_ISP_SEL_VC_ALL);
c4_adap_cfg_with_vc(adap);
c4_adap_cfg_with_lineinfo(adap, fmt);
dev_info(adap->dev, "Config mode %u: 0x%x-%u-%u\n",
adap->img_mode, fmt->code, fmt->width, fmt->height);
}
static void c4_adap_cfg_ctrl(struct c4_adap_device *adap)
{
c4_adap_update_bits(adap, CSI2_GEN_CTRL0, CSI2_GEN_CTRL0_VC0_MASK,
CSI2_GEN_CTRL0_VC0_EN);
c4_adap_update_bits(adap, CSI2_GEN_CTRL0, CSI2_GEN_CTRL0_VC1_MASK,
CSI2_GEN_CTRL0_VC1_EN);
c4_adap_update_bits(adap, CSI2_GEN_CTRL0, CSI2_GEN_CTRL0_VC2_MASK,
CSI2_GEN_CTRL0_VC2_EN);
c4_adap_update_bits(adap, CSI2_GEN_CTRL0, CSI2_GEN_CTRL0_VC3_MASK,
CSI2_GEN_CTRL0_VC3_EN);
c4_adap_update_bits(adap, CSI2_GEN_CTRL0, CSI2_GEN_CTRL0_RAW_EN_MASK,
CSI2_GEN_CTRL0_RAW_EN);
c4_adap_update_bits(adap, CSI2_GEN_CTRL0, CSI2_GEN_CTRL0_YUV_EN_MASK,
CSI2_GEN_CTRL0_YUV_EN);
c4_adap_update_bits(adap, CSI2_GEN_CTRL0, GENMASK(20, 19), 0 << 19);
}
static void c4_adap_stop_stream(struct c4_adap_device *adap)
{
c4_adap_cfg_reset(adap);
c4_adap_update_bits(adap, CSI2_GEN_CTRL0, CSI2_GEN_CTRL0_VC0_MASK,
CSI2_GEN_CTRL0_VC0_DIS);
c4_adap_update_bits(adap, CSI2_GEN_CTRL0, CSI2_GEN_CTRL0_VC1_MASK,
CSI2_GEN_CTRL0_VC1_DIS);
c4_adap_update_bits(adap, CSI2_GEN_CTRL0, CSI2_GEN_CTRL0_VC2_MASK,
CSI2_GEN_CTRL0_VC2_DIS);
c4_adap_update_bits(adap, CSI2_GEN_CTRL0, CSI2_GEN_CTRL0_VC3_MASK,
CSI2_GEN_CTRL0_VC3_DIS);
}
/* V4L2 subdev operations */
static int c4_adap_enable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
u32 pad, u64 streams_mask)
{
struct c4_adap_device *adap = v4l2_get_subdevdata(sd);
struct v4l2_mbus_framefmt *fmt;
struct media_pad *sink_pad;
struct v4l2_subdev *src_sd;
int ret;
sink_pad = &adap->pads[C4_ADAP_PAD_SINK];
adap->src_pad = media_pad_remote_pad_unique(sink_pad);
if (IS_ERR(adap->src_pad)) {
dev_dbg(adap->dev, "Failed to get source pad for MIPI adap\n");
return -EPIPE;
}
src_sd = media_entity_to_v4l2_subdev(adap->src_pad->entity);
pm_runtime_resume_and_get(adap->dev);
fmt = v4l2_subdev_state_get_format(state, C4_ADAP_PAD_SINK);
c4_adap_cfg_reset(adap);
c4_adap_cfg_vc_mode(adap, fmt);
c4_adap_cfg_ctrl(adap);
ret = v4l2_subdev_enable_streams(src_sd, adap->src_pad->index, BIT(0));
if (ret) {
pm_runtime_put(adap->dev);
return ret;
}
return 0;
}
static int c4_adap_disable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
u32 pad, u64 streams_mask)
{
struct c4_adap_device *adap = v4l2_get_subdevdata(sd);
struct v4l2_subdev *src_sd;
c4_adap_stop_stream(adap);
if (adap->src_pad) {
src_sd = media_entity_to_v4l2_subdev(adap->src_pad->entity);
v4l2_subdev_disable_streams(src_sd, adap->src_pad->index,
BIT(0));
}
adap->src_pad = NULL;
// sleep for 30ms to ensure all data have been processed
// before free isp resource(buffer, clk)
usleep_range(30*1000, 40*1000);
pm_runtime_put(adap->dev);
return 0;
}
static int c4_adap_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_mbus_code_enum *code)
{
struct v4l2_mbus_framefmt *fmt;
switch (code->pad) {
case C4_ADAP_PAD_SINK:
if (code->index >= ARRAY_SIZE(c4_adap_formats))
return -EINVAL;
code->code = c4_adap_formats[code->index].code;
break;
case C4_ADAP_PAD_SRC:
if (code->index)
return -EINVAL;
fmt = v4l2_subdev_state_get_format(state, code->pad);
code->code = fmt->code;
break;
default:
return -EINVAL;
}
return 0;
}
static int c4_adap_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *format)
{
struct v4l2_mbus_framefmt *fmt;
const struct c4_adap_pix_format *pix_format;
if (format->pad != C4_ADAP_PAD_SINK)
return v4l2_subdev_get_fmt(sd, state, format);
pix_format = c4_adap_find_format(format->format.code);
if (!pix_format)
pix_format = &c4_adap_formats[0];
fmt = v4l2_subdev_state_get_format(state, format->pad);
fmt->code = pix_format->code;
fmt->width = clamp_t(u32, format->format.width,
C4_ADAP_MIN_WIDTH, C4_ADAP_MAX_WIDTH);
fmt->height = clamp_t(u32, format->format.height,
C4_ADAP_MIN_HEIGHT, C4_ADAP_MAX_HEIGHT);
fmt->colorspace = V4L2_COLORSPACE_RAW;
fmt->xfer_func = V4L2_XFER_FUNC_NONE;
fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
format->format = *fmt;
/* Synchronize the format to source pad */
fmt = v4l2_subdev_state_get_format(state, C4_ADAP_PAD_SRC);
*fmt = format->format;
return 0;
}
static int c4_adap_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state)
{
struct v4l2_mbus_framefmt *sink_fmt;
struct v4l2_mbus_framefmt *src_fmt;
sink_fmt = v4l2_subdev_state_get_format(state, C4_ADAP_PAD_SINK);
src_fmt = v4l2_subdev_state_get_format(state, C4_ADAP_PAD_SRC);
sink_fmt->width = C4_ADAP_DEFAULT_WIDTH;
sink_fmt->height = C4_ADAP_DEFAULT_HEIGHT;
sink_fmt->field = V4L2_FIELD_NONE;
sink_fmt->code = C4_ADAP_DEFAULT_FMT;
sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE;
sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
*src_fmt = *sink_fmt;
return 0;
}
static const struct v4l2_subdev_pad_ops c4_adap_pad_ops = {
.enum_mbus_code = c4_adap_enum_mbus_code,
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = c4_adap_set_fmt,
.enable_streams = c4_adap_enable_streams,
.disable_streams = c4_adap_disable_streams,
};
static const struct v4l2_subdev_ops c4_adap_subdev_ops = {
.pad = &c4_adap_pad_ops,
};
static const struct v4l2_subdev_internal_ops c4_adap_internal_ops = {
.init_state = c4_adap_init_state,
};
/* Media entity operations */
static const struct media_entity_operations c4_adap_entity_ops = {
.link_validate = v4l2_subdev_link_validate,
};
/* PM runtime */
static int c4_adap_runtime_suspend(struct device *dev)
{
struct c4_adap_device *adap = dev_get_drvdata(dev);
clk_bulk_disable_unprepare(adap->info->clock_num, adap->clks);
return 0;
}
static int c4_adap_runtime_resume(struct device *dev)
{
struct c4_adap_device *adap = dev_get_drvdata(dev);
return clk_bulk_prepare_enable(adap->info->clock_num, adap->clks);
}
static const struct dev_pm_ops c4_adap_pm_ops = {
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
RUNTIME_PM_OPS(c4_adap_runtime_suspend,
c4_adap_runtime_resume, NULL)
};
static int c4_adap_set_ctrl(struct v4l2_ctrl *ctrl)
{
int ret = 0;
struct c4_adap_device *adap = container_of(ctrl->handler,
struct c4_adap_device, ctrls);
switch (ctrl->id) {
case V4L2_CID_C4_ADAP_IMAGE_MODE:
adap->img_mode = ctrl->val;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static const struct v4l2_ctrl_ops c4_adap_ctrl_ops = {
.s_ctrl = c4_adap_set_ctrl,
};
static const struct v4l2_ctrl_config c4_adap_image_mode_cfg = {
.ops = &c4_adap_ctrl_ops,
.id = V4L2_CID_C4_ADAP_IMAGE_MODE,
.name = "C4 adapter image mode",
.type = V4L2_CTRL_TYPE_INTEGER,
.min = C4_ADAP_IMAGE_SDR,
.max = C4_ADAP_IMAGE_MAX - 1,
.step = 1,
.def = C4_ADAP_IMAGE_SDR,
};
static int c4_adap_ctrl_init(struct c4_adap_device *adap)
{
struct v4l2_ctrl *ctrl;
adap->img_mode = C4_ADAP_IMAGE_SDR;
v4l2_ctrl_handler_init(&adap->ctrls, 1);
ctrl = v4l2_ctrl_new_custom(&adap->ctrls, &c4_adap_image_mode_cfg, NULL);
if (!ctrl) {
dev_err(adap->dev, "Failed to set mode ctrl\n");
return -EINVAL;
}
adap->sd.ctrl_handler = &adap->ctrls;
return 0;
}
static void c4_adap_ctrl_deinit(struct c4_adap_device *adap)
{
v4l2_ctrl_handler_free(&adap->ctrls);
}
/* Probe/remove & platform driver */
static int c4_adap_subdev_init(struct c4_adap_device *adap)
{
struct v4l2_subdev *sd = &adap->sd;
int ret;
v4l2_subdev_init(sd, &c4_adap_subdev_ops);
sd->owner = THIS_MODULE;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
sd->internal_ops = &c4_adap_internal_ops;
snprintf(sd->name, sizeof(sd->name), "%s", C4_ADAP_SUBDEV_NAME);
sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
sd->entity.ops = &c4_adap_entity_ops;
sd->dev = adap->dev;
v4l2_set_subdevdata(sd, adap);
adap->pads[C4_ADAP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
adap->pads[C4_ADAP_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&sd->entity, C4_ADAP_PAD_MAX, adap->pads);
if (ret)
return ret;
ret = c4_adap_ctrl_init(adap);
if (ret) {
media_entity_cleanup(&sd->entity);
return ret;
}
ret = v4l2_subdev_init_finalize(sd);
if (ret) {
c4_adap_ctrl_deinit(adap);
media_entity_cleanup(&sd->entity);
return ret;
}
return 0;
}
static void c4_adap_subdev_deinit(struct c4_adap_device *adap)
{
v4l2_subdev_cleanup(&adap->sd);
c4_adap_ctrl_deinit(adap);
media_entity_cleanup(&adap->sd.entity);
}
/* Subdev notifier register */
static int c4_adap_notify_bound(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd,
struct v4l2_async_connection *asc)
{
struct c4_adap_device *adap = v4l2_get_subdevdata(notifier->sd);
struct media_pad *sink = &adap->sd.entity.pads[C4_ADAP_PAD_SINK];
return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
MEDIA_LNK_FL_IMMUTABLE);
}
static const struct v4l2_async_notifier_operations c4_adap_notify_ops = {
.bound = c4_adap_notify_bound,
};
static int c4_adap_async_register(struct c4_adap_device *adap)
{
struct v4l2_async_connection *asc;
struct fwnode_handle *ep;
int ret;
v4l2_async_subdev_nf_init(&adap->notifier, &adap->sd);
ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(adap->dev), 0, 0,
FWNODE_GRAPH_ENDPOINT_NEXT);
if (!ep)
return -ENOTCONN;
asc = v4l2_async_nf_add_fwnode_remote(&adap->notifier, ep,
struct v4l2_async_connection);
if (IS_ERR(asc)) {
ret = PTR_ERR(asc);
goto err_put_handle;
}
adap->notifier.ops = &c4_adap_notify_ops;
ret = v4l2_async_nf_register(&adap->notifier);
if (ret)
goto err_cleanup_nf;
ret = v4l2_async_register_subdev(&adap->sd);
if (ret)
goto err_unregister_nf;
fwnode_handle_put(ep);
return 0;
err_unregister_nf:
v4l2_async_nf_unregister(&adap->notifier);
err_cleanup_nf:
v4l2_async_nf_cleanup(&adap->notifier);
err_put_handle:
fwnode_handle_put(ep);
return ret;
}
static void c4_adap_async_unregister(struct c4_adap_device *adap)
{
v4l2_async_unregister_subdev(&adap->sd);
v4l2_async_nf_unregister(&adap->notifier);
v4l2_async_nf_cleanup(&adap->notifier);
}
static int c4_adap_get_clocks(struct c4_adap_device *adap)
{
const struct c4_adap_match_info *info = adap->info;
for (unsigned int i = 0; i < info->clock_num; i++)
adap->clks[i].id = info->clocks[i];
return devm_clk_bulk_get(adap->dev, info->clock_num, adap->clks);
}
static const char *c4_adap_img_mode_str(enum c4_adap_image_mode mode)
{
switch (mode) {
case C4_ADAP_IMAGE_SDR:
return "SDR";
case C4_ADAP_IMAGE_HDR_VC_INFO:
return "HDR_VC_INFO";
case C4_ADAP_IMAGE_HDR_LINE_INFO:
return "HDR_LINE_INFO";
default:
return "UNKNOWN";
}
}
static int c4_adap_proc_show(struct seq_file *m, void *v)
{
struct c4_adap_device *adap = m->private;
u32 *adap_info = c4_adap_fe_status(adap);
struct v4l2_subdev_format fmt = {
.which = V4L2_SUBDEV_FORMAT_ACTIVE,
.pad = C4_ADAP_PAD_SINK,
};
u32 width = 0, height = 0;
if (!v4l2_subdev_call_state_active(&adap->sd, pad, get_fmt, &fmt)) {
width = fmt.format.width;
height = fmt.format.height;
}
seq_printf(m, "\n******************* ADAPTER MODULE PARAM *******************\n");
seq_printf(m, " ------- PubAttr Info ------- \n");
seq_printf(m, "ImgWidth\tImgheight\tWDRMode\n");
seq_printf(m, "%u \t\t %u \t\t %s \n\n",
width, height, c4_adap_img_mode_str(adap->img_mode));
seq_printf(m, " ------- Frontend Status ------- \n");
seq_printf(m, "CSI2_ERR_STAT0\tCSI2_PIC_SIZE_STAT\n");
seq_printf(m, "0x%x \t\t 0x%x \n\n", adap_info[0], adap_info[1]);
seq_printf(m, "CSI2_VC_MODE\tCSI2_INTERRUPT_CTRL_STAT\n");
seq_printf(m, "0x%x \t\t 0x%x \n\n", adap_info[2], adap_info[3]);
seq_printf(m, " ******************* PROC END ******************* \n\n");
return 0;
}
static int c4_adap_debug_open(struct inode *inode, struct file *file)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
return single_open(file, c4_adap_proc_show, pde_data(inode));
#else
return single_open(file, c4_adap_proc_show, PDE_DATA(inode));
#endif
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
static const struct proc_ops c4_adap_proc_file_ops = {
.proc_open = c4_adap_debug_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
#else
static const struct file_operations c4_adap_proc_file_ops = {
.open = c4_adap_debug_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#endif
static int c4_adap_proc_init(struct c4_adap_device *adap)
{
char name[16];
snprintf(name, sizeof(name), "adapter%u", adap->info->id);
adap->proc_entry = proc_create_data(name, 0644, NULL,
&c4_adap_proc_file_ops, adap);
if (!adap->proc_entry) {
dev_err(adap->dev, "adapter%u create proc failed\n",
adap->info->id);
return -ENOMEM;
}
return 0;
}
static void c4_adap_proc_remove(struct c4_adap_device *adap)
{
if (adap->proc_entry) {
proc_remove(adap->proc_entry);
adap->proc_entry = NULL;
}
}
static int c4_adap_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct c4_adap_device *adap;
int ret;
adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL);
if (!adap)
return -ENOMEM;
adap->info = of_device_get_match_data(dev);
adap->dev = dev;
adap->base = devm_platform_ioremap_resource_byname(pdev, "adap");
if (IS_ERR(adap->base)) {
ret = PTR_ERR(adap->base);
return dev_err_probe(dev, ret, "Failed to ioremap resource\n");
}
ret = c4_adap_get_clocks(adap);
if (ret)
return dev_err_probe(dev, ret, "Failed to get clocks\n");
platform_set_drvdata(pdev, adap);
pm_runtime_enable(dev);
ret = c4_adap_subdev_init(adap);
if (ret)
goto err_disable_runtime_pm;
ret = c4_adap_async_register(adap);
if (ret)
goto err_deinit_subdev;
ret = c4_adap_proc_init(adap);
if (ret)
goto err_async_unregister;
return 0;
err_async_unregister:
c4_adap_async_unregister(adap);
err_deinit_subdev:
c4_adap_subdev_deinit(adap);
err_disable_runtime_pm:
pm_runtime_disable(dev);
return ret;
};
static void c4_adap_remove(struct platform_device *pdev)
{
struct c4_adap_device *adap = platform_get_drvdata(pdev);
c4_adap_proc_remove(adap);
c4_adap_async_unregister(adap);
c4_adap_subdev_deinit(adap);
pm_runtime_put(adap->dev);
pm_runtime_disable(&pdev->dev);
};
static const struct c4_adap_match_info c4_adap_info_0 = {
.id = C4_ADAP_0,
.clocks = {"isp"},
.clock_num = 1,
};
static const struct c4_adap_match_info c4_adap_info_1 = {
.id = C4_ADAP_1,
.clocks = {"isp"},
.clock_num = 1,
};
static const struct c4_adap_match_info c4_adap_info_2 = {
.id = C4_ADAP_2,
.clocks = {"isp"},
.clock_num = 1,
};
static const struct c4_adap_match_info c4_adap_info_3 = {
.id = C4_ADAP_3,
.clocks = {"isp"},
.clock_num = 1,
};
static const struct of_device_id c4_adap_of_match[] = {
{
.compatible = "amlogic,adap0",
.data = &c4_adap_info_0,
}, {
.compatible = "amlogic,adap1",
.data = &c4_adap_info_1,
}, {
.compatible = "amlogic,adap2",
.data = &c4_adap_info_2,
}, {
.compatible = "amlogic,adap3",
.data = &c4_adap_info_3,
},
{ },
};
MODULE_DEVICE_TABLE(of, c4_adap_of_match);
static struct platform_driver c4_adap_driver = {
.probe = c4_adap_probe,
.remove = c4_adap_remove,
.driver = {
.name = "c4-adap",
.of_match_table = c4_adap_of_match,
.pm = pm_ptr(&c4_adap_pm_ops),
},
};
module_platform_driver(c4_adap_driver);
MODULE_AUTHOR("Keke Li <[email protected]>");
MODULE_DESCRIPTION("Amlogic C4 adapter");
MODULE_LICENSE("GPL");