1043 lines
29 KiB
C
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");
|