diff mbox series

[2/3] media: tc358746: add Toshiba TC358746 Parallel to CSI-2 bridge driver

Message ID 20181218141240.3056-3-m.felsch@pengutronix.de (mailing list archive)
State New, archived
Headers show
Series media: add Toshiba TC358746 Bridge support | expand

Commit Message

Marco Felsch Dec. 18, 2018, 2:12 p.m. UTC
Adding support for the TC358746 bridge. The Bridge can receive images on
the parallel input port and send it to the host using the CSI-TX unit.
Furthermore the Bridge can receive images from the host using the CSI-RX
unit and send it to the parallel output port.

Currently the only the first case is implemented and tested. The bridge
driver needs two information from the connected sensor: hblank time and
pixel-rate. Both information are requested using the v4l2_ctrl interface.
The driver won't create a media-link if one or both information are
missing.

Missing feature:
- Provide mclk on GPIO[0]
- Sending pictures from the host to a parallel display
- v4l_event support

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/media/i2c/Kconfig         |   12 +
 drivers/media/i2c/Makefile        |    1 +
 drivers/media/i2c/tc358746.c      | 1847 +++++++++++++++++++++++++++++
 drivers/media/i2c/tc358746_regs.h |  208 ++++
 4 files changed, 2068 insertions(+)
 create mode 100644 drivers/media/i2c/tc358746.c
 create mode 100644 drivers/media/i2c/tc358746_regs.h

Comments

kernel test robot Dec. 19, 2018, 1:24 a.m. UTC | #1
Hi Marco,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on linuxtv-media/master]
[cannot apply to v4.20-rc7]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Marco-Felsch/media-add-Toshiba-TC358746-Bridge-support/20181219-041919
base:   git://linuxtv.org/media_tree.git master
config: ia64-allmodconfig (attached as .config)
compiler: ia64-linux-gcc (GCC) 8.1.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        GCC_VERSION=8.1.0 make.cross ARCH=ia64 

All warnings (new ones prefixed by >>):

   drivers/media/i2c/tc358746.c: In function 'tc358746_sleep_mode':
>> drivers/media/i2c/tc358746.c:790:30: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709551613' to '4294967293' [-Woverflow]
     i2c_wr16_and_or(sd, SYSCTL, ~SYSCTL_SLEEP_MASK,
   drivers/media/i2c/tc358746.c: In function 'tc358746_enable_stream':
   drivers/media/i2c/tc358746.c:809:32: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709518847' to '4294934527' [-Woverflow]
      i2c_wr16_and_or(sd, PP_MISC, ~PP_MISC_FRMSTOP_MASK,
   drivers/media/i2c/tc358746.c:811:32: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709551551' to '4294967231' [-Woverflow]
      i2c_wr16_and_or(sd, CONFCTL, ~CONFCTL_PPEN_MASK, 0);
   drivers/media/i2c/tc358746.c:812:32: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709535231' to '4294950911' [-Woverflow]
      i2c_wr16_and_or(sd, PP_MISC, ~PP_MISC_RSTPTR_MASK,
   drivers/media/i2c/tc358746.c:820:32: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709551551' to '4294967231' [-Woverflow]
      i2c_wr16_and_or(sd, CONFCTL, ~CONFCTL_PPEN_MASK,
   drivers/media/i2c/tc358746.c: In function 'tc358746_set_pll':
   drivers/media/i2c/tc358746.c:866:32: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709551599' to '4294967279' [-Woverflow]
      i2c_wr16_and_or(sd, PLLCTL1, ~PLLCTL1_CKEN_MASK,
   drivers/media/i2c/tc358746.c: In function 'tc358746_set_csi_color_space':
   drivers/media/i2c/tc358746.c:882:4: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709551374' to '4294967054' [-Woverflow]
       ~(DATAFMT_PDFMT_MASK | DATAFMT_UDT_EN_MASK),
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/media/i2c/tc358746.c:884:31: warning: conversion from 'long unsigned int' to 'u32' {aka 'unsigned int'} changes value from '18446744073709550847' to '4294966527' [-Woverflow]
     i2c_wr16_and_or(sd, CONFCTL, ~CONFCTL_PDATAF_MASK,

vim +790 drivers/media/i2c/tc358746.c

   787	
   788	static inline void tc358746_sleep_mode(struct v4l2_subdev *sd, int enable)
   789	{
 > 790		i2c_wr16_and_or(sd, SYSCTL, ~SYSCTL_SLEEP_MASK,
   791				enable ? SYSCTL_SLEEP_MASK : 0);
   792	}
   793	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
kernel test robot Dec. 20, 2018, 7:37 p.m. UTC | #2
Hi Marco,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on linuxtv-media/master]
[also build test WARNING on next-20181220]
[cannot apply to v4.20-rc7]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Marco-Felsch/media-add-Toshiba-TC358746-Bridge-support/20181219-041919
base:   git://linuxtv.org/media_tree.git master
config: sparc64-allyesconfig (attached as .config)
compiler: sparc64-linux-gnu-gcc (Debian 7.2.0-11) 7.2.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        GCC_VERSION=7.2.0 make.cross ARCH=sparc64 

All warnings (new ones prefixed by >>):

   drivers/media//i2c/tc358746.c: In function 'tc358746_sleep_mode':
>> drivers/media//i2c/tc358746.c:790:30: warning: large integer implicitly truncated to unsigned type [-Woverflow]
     i2c_wr16_and_or(sd, SYSCTL, ~SYSCTL_SLEEP_MASK,
                                 ^
   drivers/media//i2c/tc358746.c: In function 'tc358746_enable_stream':
   drivers/media//i2c/tc358746.c:809:32: warning: large integer implicitly truncated to unsigned type [-Woverflow]
      i2c_wr16_and_or(sd, PP_MISC, ~PP_MISC_FRMSTOP_MASK,
                                   ^
   drivers/media//i2c/tc358746.c:811:32: warning: large integer implicitly truncated to unsigned type [-Woverflow]
      i2c_wr16_and_or(sd, CONFCTL, ~CONFCTL_PPEN_MASK, 0);
                                   ^
   drivers/media//i2c/tc358746.c:812:32: warning: large integer implicitly truncated to unsigned type [-Woverflow]
      i2c_wr16_and_or(sd, PP_MISC, ~PP_MISC_RSTPTR_MASK,
                                   ^
   drivers/media//i2c/tc358746.c:820:32: warning: large integer implicitly truncated to unsigned type [-Woverflow]
      i2c_wr16_and_or(sd, CONFCTL, ~CONFCTL_PPEN_MASK,
                                   ^
   drivers/media//i2c/tc358746.c: In function 'tc358746_set_pll':
   drivers/media//i2c/tc358746.c:866:32: warning: large integer implicitly truncated to unsigned type [-Woverflow]
      i2c_wr16_and_or(sd, PLLCTL1, ~PLLCTL1_CKEN_MASK,
                                   ^
   drivers/media//i2c/tc358746.c: In function 'tc358746_set_csi_color_space':
   drivers/media//i2c/tc358746.c:882:4: warning: large integer implicitly truncated to unsigned type [-Woverflow]
       ~(DATAFMT_PDFMT_MASK | DATAFMT_UDT_EN_MASK),
       ^
   drivers/media//i2c/tc358746.c:884:31: warning: large integer implicitly truncated to unsigned type [-Woverflow]
     i2c_wr16_and_or(sd, CONFCTL, ~CONFCTL_PDATAF_MASK,
                                  ^

vim +790 drivers/media//i2c/tc358746.c

   787	
   788	static inline void tc358746_sleep_mode(struct v4l2_subdev *sd, int enable)
   789	{
 > 790		i2c_wr16_and_or(sd, SYSCTL, ~SYSCTL_SLEEP_MASK,
   791				enable ? SYSCTL_SLEEP_MASK : 0);
   792	}
   793	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
Sakari Ailus Feb. 18, 2019, 11:25 a.m. UTC | #3
Hi Marco,

On Tue, Dec 18, 2018 at 03:12:39PM +0100, Marco Felsch wrote:
> Adding support for the TC358746 bridge. The Bridge can receive images on
> the parallel input port and send it to the host using the CSI-TX unit.
> Furthermore the Bridge can receive images from the host using the CSI-RX
> unit and send it to the parallel output port.
> 
> Currently the only the first case is implemented and tested. The bridge
> driver needs two information from the connected sensor: hblank time and
> pixel-rate. Both information are requested using the v4l2_ctrl interface.
> The driver won't create a media-link if one or both information are
> missing.
> 
> Missing feature:
> - Provide mclk on GPIO[0]
> - Sending pictures from the host to a parallel display
> - v4l_event support
> 
> Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
> ---
>  drivers/media/i2c/Kconfig         |   12 +
>  drivers/media/i2c/Makefile        |    1 +
>  drivers/media/i2c/tc358746.c      | 1847 +++++++++++++++++++++++++++++
>  drivers/media/i2c/tc358746_regs.h |  208 ++++
>  4 files changed, 2068 insertions(+)
>  create mode 100644 drivers/media/i2c/tc358746.c
>  create mode 100644 drivers/media/i2c/tc358746_regs.h
> 
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index 4c936e129500..9995075c3eac 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -394,6 +394,18 @@ config VIDEO_TC358743_CEC
>  	  When selected the tc358743 will support the optional
>  	  HDMI CEC feature.
>  
> +config VIDEO_TC358746
> +	tristate "Toshiba TC358746 decoder"

How about calling it e.g. parallel-CSI2 bridge instead of a decoder?

> +	depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API && MEDIA_CONTROLLER
> +	select V4L2_FWNODE
> +	help
> +	  Support for the Toshiba TC358746 PARALLEL to MIPI CSI-2 bridge.
> +	  The bridge can work in both directions but currenty only the
> +	  parallel-in / csi-out path is supported.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called tc358746.
> +
>  config VIDEO_TVP514X
>  	tristate "Texas Instruments TVP514x video decoder"
>  	depends on VIDEO_V4L2 && I2C
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 65fae7732de0..5cdbdf546627 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -106,6 +106,7 @@ obj-$(CONFIG_VIDEO_I2C)		+= video-i2c.o
>  obj-$(CONFIG_VIDEO_ML86V7667)	+= ml86v7667.o
>  obj-$(CONFIG_VIDEO_OV2659)	+= ov2659.o
>  obj-$(CONFIG_VIDEO_TC358743)	+= tc358743.o
> +obj-$(CONFIG_VIDEO_TC358746)	+= tc358746.o
>  obj-$(CONFIG_VIDEO_IMX214)	+= imx214.o
>  obj-$(CONFIG_VIDEO_IMX258)	+= imx258.o
>  obj-$(CONFIG_VIDEO_IMX274)	+= imx274.o
> diff --git a/drivers/media/i2c/tc358746.c b/drivers/media/i2c/tc358746.c
> new file mode 100644
> index 000000000000..633b0322b85d
> --- /dev/null
> +++ b/drivers/media/i2c/tc358746.c
> @@ -0,0 +1,1847 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * tc358746 - Parallel to CSI-2 bridge
> + *
> + * Copyright 2018 Marco Felsch <kernel@pengutronix.de>
> + *
> + * References:
> + * REF_01:
> + * - TC358746AXBG/TC358748XBG/TC358748IXBG Functional Specification Rev 1.2
> + * REF_02:
> + * - TC358746(A)748XBG_Parallel-CSI2_Tv23p.xlsx, Rev Tv23
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/clk-provider.h>
> +#include <linux/slab.h>
> +#include <linux/i2c.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/interrupt.h>
> +#include <linux/timer.h>
> +#include <linux/property.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-fwnode.h>

Alphabetical order, please.

> +
> +#include "tc358746_regs.h"
> +
> +static int debug;
> +module_param(debug, int, 0644);
> +MODULE_PARM_DESC(debug, "debug level (0-3)");
> +
> +MODULE_DESCRIPTION("Toshiba TC358746 Parallel to CSI-2 bridge driver");
> +MODULE_AUTHOR("Marco Felsch <kernel@pengutronix.de>");
> +MODULE_LICENSE("GPL");

"GPL v2"?

> +
> +#define I2C_MAX_XFER_SIZE	(512 + 2)
> +#define TC358746_MAX_FIFO_SIZE	512
> +#define TC358746_DEF_LINK_FREQ	0
> +
> +#define TC358746_LINEINIT_MIN_US	110
> +#define TC358746_TWAKEUP_MIN_US		1200
> +#define TC358746_LPTXTIME_MIN_NS	55
> +#define TC358746_TCLKZERO_MIN_NS	305
> +#define TC358746_TCLKTRAIL_MIN_NS	65
> +#define TC358746_TCLKPOST_MIN_NS	65
> +#define TC358746_THSZERO_MIN_NS		150
> +#define TC358746_THSTRAIL_MIN_NS	65
> +#define TC358746_THSPREPARE_MIN_NS	45
> +
> +static const struct v4l2_mbus_framefmt tc358746_def_fmt = {
> +	.width		= 640,
> +	.height		= 480,
> +	.code		= MEDIA_BUS_FMT_UYVY8_2X8,
> +	.field		= V4L2_FIELD_NONE,
> +	.colorspace	= V4L2_COLORSPACE_DEFAULT,
> +	.ycbcr_enc	= V4L2_YCBCR_ENC_DEFAULT,
> +	.quantization	= V4L2_QUANTIZATION_DEFAULT,
> +	.xfer_func	= V4L2_XFER_FUNC_DEFAULT,
> +};
> +
> +struct tc358746_csi_param {
> +	unsigned char speed_range;
> +	unsigned int  unit_clk_hz;
> +	unsigned char unit_clk_mul;
> +	unsigned int speed_per_lane; /* bps / lane */
> +	unsigned short lane_num;
> +	bool is_continuous_clk;
> +
> +	/* CSI2-TX Parameters */
> +	u32 lineinitcnt;
> +	u32 lptxtimecnt;
> +	u32 twakeupcnt;
> +	u32 tclk_preparecnt;
> +	u32 tclk_zerocnt;
> +	u32 tclk_trailcnt;
> +	u32 tclk_postcnt;
> +	u32 ths_preparecnt;
> +	u32 ths_zerocnt;
> +	u32 ths_trailcnt;
> +
> +	unsigned int csi_hs_lp_hs_ps;
> +};
> +
> +struct tc358746_state {
> +	struct v4l2_subdev sd;
> +	struct i2c_client *i2c_client;
> +	struct gpio_desc *reset_gpio;
> +
> +	/*
> +	 * Generic
> +	 */
> +	struct media_pad pads[2];
> +	struct mutex confctl_mutex;
> +	struct v4l2_mbus_framefmt fmt;
> +	struct v4l2_ctrl_handler hdl;
> +	bool fmt_changed;
> +	bool test;
> +
> +	/*
> +	 * Chip Clocks
> +	 */
> +	struct clk  *refclk;
> +	/* internal pll */
> +	unsigned int pllinclk_hz;
> +	u16 pll_prd;
> +	u16 pll_fbd;
> +
> +	/*
> +	 * Video Buffer
> +	 */
> +	u16 vb_fifo; /* The FIFO size is 511x32 */
> +
> +	/*
> +	 * CSI TX
> +	 */
> +	struct v4l2_ctrl	  *link_freq;
> +	struct tc358746_csi_param *link_freq_settings;
> +	u64			  *link_frequencies;
> +	unsigned int		   link_frequencies_num;
> +
> +	/*
> +	 * Parallel input
> +	 */
> +	unsigned int pclk;
> +	unsigned int hblank;
> +};
> +
> +struct tc358746_mbus_fmt {
> +	u32 code;
> +	u8 bus_width;
> +	u8 bpp;		 /* total bpp */
> +	u8 pdformat;	 /* peripheral data format */
> +	u8 pdataf;	 /* parallel data format option */
> +	u8 ppp;		 /* pclk per pixel */
> +	bool csitx_only; /* format only in csi-tx mode supported */
> +};
> +
> +/* TODO: Add other formats as required */
> +static const struct tc358746_mbus_fmt tc358746_formats[] = {
> +	{
> +		.code = MEDIA_BUS_FMT_UYVY8_2X8,
> +		.bus_width = 8,
> +		.bpp = 16,
> +		.pdformat = DATAFMT_PDFMT_YCBCRFMT_422_8_BIT,
> +		.pdataf = CONFCTL_PDATAF_MODE0,
> +		.ppp = 2,
> +	}, {
> +		.code = MEDIA_BUS_FMT_UYVY8_1X16,
> +		.bus_width = 16,
> +		.bpp = 16,
> +		.pdformat = DATAFMT_PDFMT_YCBCRFMT_422_8_BIT,
> +		.pdataf = CONFCTL_PDATAF_MODE1,
> +		.ppp = 1,
> +	}, {
> +		.code = MEDIA_BUS_FMT_YUYV8_1X16,
> +		.bus_width = 16,
> +		.bpp = 16,
> +		.pdformat = DATAFMT_PDFMT_YCBCRFMT_422_8_BIT,
> +		.pdataf = CONFCTL_PDATAF_MODE2,
> +		.ppp = 1,
> +	}, {
> +		.code = MEDIA_BUS_FMT_UYVY10_2X10,
> +		.bus_width = 10,
> +		.bpp = 20,
> +		.pdformat = DATAFMT_PDFMT_YCBCRFMT_422_10_BIT,
> +		.pdataf = CONFCTL_PDATAF_MODE0, /* don't care */
> +		.ppp = 2,
> +	}, {
> +		/* in datasheet listed as YUV444 */
> +		.code = MEDIA_BUS_FMT_GBR888_1X24,
> +		.bus_width = 24,
> +		.bpp = 24,
> +		.pdformat = DATAFMT_PDFMT_YCBCRFMT_444,
> +		.pdataf = CONFCTL_PDATAF_MODE0, /* don't care */
> +		.ppp = 2,
> +		.csitx_only = true,
> +	},
> +};
> +
> +/* --------------- HELPERS ------------ */
> +static void
> +tc358746_dump_csi(struct device *dev,
> +		  struct tc358746_csi_param *csi_setting)
> +{
> +	dev_dbg(dev, "Speed-Range value %u\n", csi_setting->speed_range);
> +	dev_dbg(dev, "Unit Clock %u Hz\n", csi_setting->unit_clk_hz);
> +	dev_dbg(dev, "Unit Clock Mul %u\n", csi_setting->unit_clk_mul);
> +	dev_dbg(dev, "CSI speed/lane %u bps/lane\n",
> +		csi_setting->speed_per_lane);
> +	dev_dbg(dev, "CSI lanes %u\n", csi_setting->lane_num);
> +	dev_dbg(dev, "CSI clock during LP %sabled\n",
> +		csi_setting->is_continuous_clk ? "en" : "dis");
> +
> +	dev_dbg(dev, "lineinitcnt %u\n", csi_setting->lineinitcnt);
> +	dev_dbg(dev, "lptxtimecnt %u\n", csi_setting->lptxtimecnt);
> +	dev_dbg(dev, "tclk_preparecnt %u\n", csi_setting->tclk_preparecnt);
> +	dev_dbg(dev, "tclk_zerocnt %u\n", csi_setting->tclk_zerocnt);
> +	dev_dbg(dev, "tclk_trailcnt %u\n", csi_setting->tclk_trailcnt);
> +	dev_dbg(dev, "ths_preparecnt %u\n", csi_setting->ths_preparecnt);
> +	dev_dbg(dev, "ths_zerocnt %u\n", csi_setting->ths_zerocnt);
> +	dev_dbg(dev, "twakeupcnt %u\n", csi_setting->twakeupcnt);
> +	dev_dbg(dev, "tclk_postcnt %u\n", csi_setting->tclk_postcnt);
> +	dev_dbg(dev, "ths_trailcnt %u\n", csi_setting->ths_trailcnt);
> +	dev_dbg(dev, "csi_hs_lp_hs_ps %u (%u us)\n",
> +		csi_setting->csi_hs_lp_hs_ps,
> +		csi_setting->csi_hs_lp_hs_ps / 1000);
> +}
> +
> +static void
> +tc358746_dump_pll(struct device *dev, struct tc358746_state *state)
> +{
> +	dev_dbg(dev, "refclk %lu Hz\n", clk_get_rate(state->refclk));
> +	dev_dbg(dev, "pll input clock %u Hz\n", state->pllinclk_hz);
> +	dev_dbg(dev, "PLL_PRD %u\n", state->pll_prd - 1);
> +	dev_dbg(dev, "PLL_FBD %u\n", state->pll_fbd - 1);
> +}
> +
> +static inline struct tc358746_state *to_state(struct v4l2_subdev *sd)
> +{
> +	return container_of(sd, struct tc358746_state, sd);
> +}
> +
> +/* Find a data format by a pixel code */
> +static int tc358746_format_supported(u32 code)
> +{
> +	int i;

unsigned int?

> +
> +	for (i = 0; i < ARRAY_SIZE(tc358746_formats); i++)
> +		if (tc358746_formats[i].code == code)
> +			return 0;
> +
> +	return -EINVAL;
> +}
> +
> +static struct tc358746_csi_param *
> +tc358746_g_cur_csi_settings(struct tc358746_state *state)
> +{
> +	int cur_freq = v4l2_ctrl_g_ctrl(state->link_freq);

If you'd be holding the mutex already, you could access the value directly.

> +
> +	return &state->link_freq_settings[cur_freq];
> +}
> +
> +static const struct tc358746_mbus_fmt *tc358746_get_format(u32 code)
> +{
> +	int i;

unsigned int.

> +
> +	for (i = 0; i < ARRAY_SIZE(tc358746_formats); i++)
> +		if (tc358746_formats[i].code == code)
> +			return &tc358746_formats[i];
> +
> +	return NULL;
> +}
> +
> +static int
> +tc358746_adjust_fifo_size(struct tc358746_state *state,
> +			  const struct tc358746_mbus_fmt *format,
> +			  struct tc358746_csi_param *csi_settings,
> +			  int width, u16 *fifo_size)

width should be unsigned.

> +{
> +	struct device *dev = &state->i2c_client->dev;
> +	int c_hactive_ps_diff, c_lp_active_ps_diff, c_fifo_delay_ps_diff;
> +	unsigned int p_hactive_ps, p_hblank_ps, p_htotal_ps;
> +	unsigned int c_hactive_ps, c_lp_active_ps, c_fifo_delay_ps;
> +	unsigned int csi_bps, csi_bps_period_ps;
> +	unsigned int csi_hsclk, csi_hsclk_period_ps;
> +	unsigned int pclk_period_ps;
> +	unsigned int _fifo_size;
> +
> +	pclk_period_ps = 1000000000 / (state->pclk / 1000);
> +	csi_bps = csi_settings->speed_per_lane * csi_settings->lane_num;
> +	csi_bps_period_ps = 1000000000 / (csi_bps / 1000);
> +	csi_hsclk = csi_settings->speed_per_lane >> 3;
> +	csi_hsclk_period_ps = 1000000000 / (csi_hsclk / 1000);
> +
> +	/*
> +	 * Calculation:
> +	 * p_hactive_ps = pclk_period_ps * pclk_per_pixel * h_active_pixel
> +	 */
> +	p_hactive_ps = pclk_period_ps * format->ppp * width;
> +
> +	/*
> +	 * Calculation:
> +	 * p_hblank_ps = pclk_period_ps * h_blank_pixel
> +	 */
> +	p_hblank_ps = pclk_period_ps * state->hblank;
> +	p_htotal_ps = p_hblank_ps + p_hactive_ps;
> +
> +	/*
> +	 * Adjust the fifo size to adjust the csi timing. Hopefully we can find
> +	 * a fifo size where the parallel input timings and the csi tx timings
> +	 * fit together.
> +	 */
> +	for (_fifo_size = 1; _fifo_size < TC358746_MAX_FIFO_SIZE;
> +	     _fifo_size++) {
> +		/*
> +		 * Calculation:
> +		 * c_fifo_delay_ps = (fifo_size * 32) / parallel_bus_width *
> +		 *		     pclk_period_ps + 4 * csi_hsclk_period_ps
> +		 */
> +		c_fifo_delay_ps = _fifo_size * 32 * pclk_period_ps;
> +		c_fifo_delay_ps /= format->bus_width;
> +		c_fifo_delay_ps += 4 * csi_hsclk_period_ps;
> +
> +		/*
> +		 * Calculation:
> +		 * c_hactive_ps = csi_bps_period_ps * image_bpp * h_active_pixel
> +		 *		  + c_fifo_delay
> +		 */
> +		c_hactive_ps = csi_bps_period_ps * format->bpp * width;
> +		c_hactive_ps += c_fifo_delay_ps;
> +
> +		/*
> +		 * Calculation:
> +		 * c_lp_active_ps = p_htotal_ps - c_hactive_ps
> +		 */
> +		c_lp_active_ps = p_htotal_ps - c_hactive_ps;
> +
> +		c_hactive_ps_diff = c_hactive_ps - p_hactive_ps;
> +		c_fifo_delay_ps_diff = p_htotal_ps - c_hactive_ps;
> +		c_lp_active_ps_diff =
> +			c_lp_active_ps - csi_settings->csi_hs_lp_hs_ps;
> +
> +		if (c_hactive_ps_diff > 0 &&
> +		    c_fifo_delay_ps_diff > 0 &&
> +		    c_lp_active_ps_diff > 0)
> +			break;
> +	}
> +	/*
> +	 * If we can't transfer the image using this csi link frequency try to
> +	 * use another link freq.
> +	 */
> +
> +	dev_dbg(dev, "%s: found fifo-size %u\n", __func__, _fifo_size);
> +	*fifo_size = _fifo_size;
> +	return _fifo_size == TC358746_MAX_FIFO_SIZE ? -EINVAL : 0;
> +}
> +
> +static int
> +tc358746_adjust_timings(struct tc358746_state *state,
> +			const struct tc358746_mbus_fmt *format,
> +			int *width, u16 *fifo_size)
> +{
> +
> +	int cur_freq = v4l2_ctrl_g_ctrl(state->link_freq);
> +	int freq = cur_freq;

unsigned int?

> +	struct tc358746_csi_param *csi_lane_setting;
> +	int err;
> +	int _width;
> +
> +	/*
> +	 * Adjust timing:
> +	 * 1) Try to use the desired width and the current csi-link-frequency
> +	 * 2) If this doesn't fit try other csi-link-frequencies
> +	 * 3) If this doesn't fit too, reducing the desired width and test
> +	 *    it again width the current csi-link-frequency
> +	 * 4) Goto step 2 if it doesn't fit at all
> +	 */
> +	for (_width = *width; _width > 0; _width -= 10) {
> +		csi_lane_setting = &state->link_freq_settings[cur_freq];
> +		err = tc358746_adjust_fifo_size(state, format, csi_lane_setting,
> +						_width, fifo_size);
> +		if (!err)
> +			goto out;
> +
> +		for (freq = 0; freq < state->link_frequencies_num; freq++) {
> +			if (freq == cur_freq)
> +				continue;
> +
> +			csi_lane_setting = &state->link_freq_settings[freq];
> +			err = tc358746_adjust_fifo_size(state, format,
> +							csi_lane_setting,
> +							_width, fifo_size);
> +			if (!err)
> +				goto out;
> +		}
> +	}
> +
> +out:
> +	*width = _width;
> +	return freq;
> +}
> +
> +static int
> +tc358746_calculate_csi_txtimings(struct tc358746_state *state,
> +				 struct tc358746_csi_param *csi_setting)
> +{
> +	struct device *dev = &state->i2c_client->dev;
> +	unsigned int spl;
> +	unsigned int spl_p_ps, hsclk_p_ps, hfclk_p_ns;
> +	unsigned int hfclk, hsclk; /* SYSCLK */
> +	unsigned int tmp;
> +	unsigned int lptxtime_ps, tclk_post_ps, tclk_trail_ps, tclk_zero_ps,
> +		     ths_trail_ps, ths_zero_ps;
> +
> +	spl = csi_setting->speed_per_lane;
> +	hsclk = spl >> 3; /* spl in bit-per-second, hsclk in byte-per-sercond */
> +	hfclk = hsclk >> 1; /* HFCLK = SYSCLK / 2 */
> +
> +	if (hsclk > 125000000U) {
> +		dev_err(dev, "unsupported HS byte clock %d, must <= 125 MHz\n",
> +			hsclk);
> +		return -EINVAL;
> +	}
> +
> +	hfclk_p_ns = DIV_ROUND_CLOSEST(1000000000, hfclk);
> +	hsclk_p_ps = 1000000000 / (hsclk / 1000);
> +	spl_p_ps   = 1000000000 / (spl / 1000);
> +
> +	/*
> +	 * Calculation:
> +	 * hfclk_p_ns * lineinitcnt > 100us
> +	 * lineinitcnt > 100 * 10^-6s / hfclk_p_ns * 10^-9
> +	 *
> +	 */
> +	csi_setting->lineinitcnt = DIV_ROUND_UP(TC358746_LINEINIT_MIN_US * 1000,
> +					  hfclk_p_ns);
> +
> +	/*
> +	 * Calculation:
> +	 * (lptxtimecnt + 1) * hsclk_p_ps > 50ns
> +	 * 38ns < (tclk_preparecnt + 1) * hsclk_p_ps < 95ns
> +	 */
> +	csi_setting->lptxtimecnt = csi_setting->tclk_preparecnt =
> +		DIV_ROUND_UP(TC358746_LPTXTIME_MIN_NS * 1000, hsclk_p_ps) - 1;
> +
> +	/*
> +	 * Limit:
> +	 * (tclk_zero + tclk_prepar) period > 300ns.
> +	 * Since we have no upper limit and for simplicity:
> +	 * tclk_zero > 300ns.
> +	 *
> +	 * Calculation:
> +	 * tclk_zero = ([2,3] + tclk_zerocnt) * hsclk_p_ps + ([2,3] * spl_p_ps)
> +	 *
> +	 * Note: REF_02 uses
> +	 * tclk_zero = (2.5 + tclk_zerocnt) * hsclk_p_ps + (3.5 * spl_p_ps)
> +	 */
> +	tmp = TC358746_TCLKZERO_MIN_NS * 1000 - 3 * spl_p_ps;
> +	tmp = DIV_ROUND_UP(tmp, hsclk_p_ps);
> +	csi_setting->tclk_zerocnt = tmp - 2;
> +
> +	/*
> +	 * Limit:
> +	 * 40ns + 4 * spl_p_ps < (ths_preparecnt + 1) * hsclk_p_ps
> +	 *		       < 85ns + 6 * spl_p_ps
> +	 */
> +	tmp = TC358746_THSPREPARE_MIN_NS * 1000 + 4 * spl_p_ps;
> +	tmp = DIV_ROUND_UP(tmp, hsclk_p_ps);
> +	csi_setting->ths_preparecnt = tmp - 1;
> +
> +	/*
> +	 * Limit:
> +	 * (ths_zero + ths_prepare) period > 145ns + 10 * spl_p_ps.
> +	 * Since we have no upper limit and for simplicity:
> +	 * ths_zero period > 145ns + 10 * spl_p_ps.
> +	 *
> +	 * Calculation:
> +	 * ths_zero = ([6,8] + ths_zerocnt) * hsclk_p_ps + [3,4] * hsclk_p_ps +
> +	 *	      [13,14] * spl_p_ps
> +	 *
> +	 * Note: REF_02 uses
> +	 * ths_zero = (7 + ths_zerocnt) * hsclk_p_ps + 4 * hsclk_p_ps +
> +	 *	      11 * spl_p_ps
> +	 */
> +	tmp = TC358746_THSZERO_MIN_NS * 1000 - spl_p_ps;
> +	tmp = DIV_ROUND_UP(tmp, hsclk_p_ps);
> +	csi_setting->ths_zerocnt = tmp < 11 ? 0 : tmp - 11;
> +
> +	/*
> +	 * Limit:
> +	 * hsclk_p_ps * (lptxtimecnt + 1) * (twakeupcnt + 1) > 1ms
> +	 *
> +	 * Since we have no upper limit use 1.2ms as lower limit to
> +	 * surley meet the spec limit.
> +	 */
> +	tmp = hsclk_p_ps / 1000; /* tmp = hsclk_p_ns */
> +	csi_setting->twakeupcnt =
> +		DIV_ROUND_UP(TC358746_TWAKEUP_MIN_US * 1000,
> +			     tmp * (csi_setting->lptxtimecnt + 1)) - 1;
> +
> +	/*
> +	 * Limit:
> +	 * 60ns + 4 * spl_p_ps < thstrail < 105ns + 12 * spl_p_ps
> +	 *
> +	 * Calculation:
> +	 * thstrail = (1 + ths_trailcnt) * hsclk_p_ps + [3,4] * hsclk_p_ps -
> +	 *	      [13,14] * spl_p_ps
> +	 *
> +	 * [2] set formula to:
> +	 * thstrail = (1 + ths_trailcnt) * hsclk_p_ps + 4 * hsclk_p_ps -
> +	 *	      11 * spl_p_ps
> +	 */
> +	tmp = TC358746_THSTRAIL_MIN_NS * 1000 + 15 * spl_p_ps;
> +	tmp = DIV_ROUND_UP(tmp, hsclk_p_ps);
> +	csi_setting->ths_trailcnt = tmp - 5;
> +
> +	/*
> +	 * Limit:
> +	 * 60ns < tclk_trail < 105ns + 12 * spl_p_ps
> +	 *
> +	 * Limit used by REF_02:
> +	 * 60ns < tclk_trail < 105ns + 12 * spl_p_ps - 30
> +	 *
> +	 * Calculation:
> +	 * tclk_trail = ([1,2] + tclk_trailcnt) * hsclk_p_ps +
> +	 *		(2 + [1,2]) * hsclk_p_ps - [2,3] * spl_p_ps
> +	 *
> +	 * Calculation used by REF_02:
> +	 * tclk_trail = (1 + tclk_trailcnt) * hsclk_p_ps +
> +	 *		4 * hsclk_p_ps - 3 * spl_p_ps
> +	 */
> +	tmp = TC358746_TCLKTRAIL_MIN_NS * 1000 + 3 * spl_p_ps;
> +	tmp = DIV_ROUND_UP(tmp, hsclk_p_ps);
> +	csi_setting->tclk_trailcnt = tmp < 5 ? 0 : tmp - 5;
> +
> +	/*
> +	 * Limit:
> +	 * tclk_post > 60ns + 52 * spl_p_ps
> +	 *
> +	 * Limit used by REF_02:
> +	 * tclk_post > 60ns + 52 * spl_p_ps
> +	 *
> +	 * Calculation:
> +	 * tclk_post = ([1,2] + (tclk_postcnt + 1)) * hsclk_p_ps + hsclk_p_ps
> +	 *
> +	 * Note REF_02 uses:
> +	 * tclk_post = (2.5 + tclk_postcnt) * hsclk_p_ps + hsclk_p_ps +
> +	 *		2.5 * spl_p_ps
> +	 * To meet the REF_02 validation limits following equation is used:
> +	 * tclk_post = (2 + tclk_postcnt) * hsclk_p_ps + hsclk_p_ps +
> +	 *		3 * spl_p_ps
> +	 */
> +	tmp = TC358746_TCLKPOST_MIN_NS * 1000 + 49 * spl_p_ps;
> +	tmp = DIV_ROUND_UP(tmp, hsclk_p_ps);
> +	csi_setting->tclk_postcnt = tmp - 3;
> +
> +	/*
> +	 * Last calculate the csi hs->lp->hs transistion time in ns. Note REF_02
> +	 * mixed units in the equation for the continuous case. I don't know if
> +	 * this was the intention. The driver drops the last 'multiply all by
> +	 * two' to get nearly the same results.
> +	 */
> +	lptxtime_ps = (csi_setting->lptxtimecnt + 1) * hsclk_p_ps;
> +	tclk_post_ps =
> +		(4 + csi_setting->tclk_postcnt) * hsclk_p_ps + 3 * spl_p_ps;
> +	tclk_trail_ps =
> +		(5 + csi_setting->tclk_trailcnt) * hsclk_p_ps - 3 * spl_p_ps;
> +	tclk_zero_ps =
> +		(2 + csi_setting->tclk_zerocnt) * hsclk_p_ps + 3 * spl_p_ps;
> +	ths_trail_ps =
> +		(5 + csi_setting->ths_trailcnt) * hsclk_p_ps - 11 * spl_p_ps;
> +	ths_zero_ps =
> +		(7 + csi_setting->ths_zerocnt) * hsclk_p_ps + 4 * hsclk_p_ps +
> +		11 * spl_p_ps;
> +
> +	if (csi_setting->is_continuous_clk) {
> +		tmp = 2 * lptxtime_ps;
> +		tmp += 25 * hsclk_p_ps;
> +		tmp += ths_trail_ps;
> +		tmp += ths_zero_ps;
> +	} else {
> +		tmp = 4 * lptxtime_ps;
> +		tmp += ths_trail_ps + tclk_post_ps + tclk_trail_ps +
> +			tclk_zero_ps + ths_zero_ps;
> +		tmp += (13 + csi_setting->lptxtimecnt * 8) * hsclk_p_ps;
> +		tmp += 22 * hsclk_p_ps;
> +		tmp *= 3;
> +		tmp = DIV_ROUND_CLOSEST(tmp, 2);
> +	}
> +	csi_setting->csi_hs_lp_hs_ps = tmp;
> +
> +	return 0;
> +}
> +
> +/* --------------- i2c helper ------------ */
> +
> +static void i2c_rd(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	struct i2c_client *client = state->i2c_client;
> +	int err;
> +	u8 buf[2] = { reg >> 8, reg & 0xff };
> +	u8 data[I2C_MAX_XFER_SIZE];
> +
> +	struct i2c_msg msgs[] = {
> +		{
> +			.addr = client->addr,
> +			.flags = 0,
> +			.len = 2,
> +			.buf = buf,
> +		},
> +		{
> +			.addr = client->addr,
> +			.flags = I2C_M_RD,
> +			.len = n,
> +			.buf = data,
> +		},
> +	};
> +
> +	err = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
> +	if (err != ARRAY_SIZE(msgs)) {
> +		v4l2_err(sd, "%s: reading register 0x%x from 0x%x failed\n",
> +			 __func__, reg, client->addr);
> +	}
> +
> +	switch (n) {
> +	case 1:
> +		values[0] = data[0];
> +		break;
> +	case 2:
> +		values[0] = data[1];
> +		values[1] = data[0];
> +		break;
> +	case 4:
> +		values[0] = data[1];
> +		values[1] = data[0];
> +		values[2] = data[3];
> +		values[3] = data[2];
> +		break;
> +	default:
> +		v4l2_info(sd, "unsupported I2C read %d bytes from address 0x%04x\n",
> +			  n, reg);
> +	}
> +
> +	if (debug < 3)
> +		return;
> +
> +	switch (n) {
> +	case 1:
> +		v4l2_info(sd, "I2C read 0x%04x = 0x%02x",
> +			  reg, data[0]);
> +		break;
> +	case 2:
> +		v4l2_info(sd, "I2C read 0x%04x = 0x%02x%02x",
> +			  reg, data[0], data[1]);
> +		break;
> +	case 4:
> +		v4l2_info(sd, "I2C read 0x%04x = 0x%02x%02x%02x%02x",
> +			  reg, data[2], data[3], data[0], data[1]);
> +		break;
> +	default:
> +		v4l2_info(sd, "I2C unsupported read %d bytes from address 0x%04x\n",
> +			  n, reg);
> +	}
> +}
> +
> +static void i2c_wr(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	struct i2c_client *client = state->i2c_client;
> +	int err;
> +	struct i2c_msg msg;
> +	u8 data[I2C_MAX_XFER_SIZE];
> +
> +	if ((2 + n) > I2C_MAX_XFER_SIZE) {
> +		n = I2C_MAX_XFER_SIZE - 2;
> +		v4l2_warn(sd, "i2c wr reg=%04x: len=%d is too big!\n",
> +			  reg, 2 + n);
> +	}
> +
> +	msg.addr = client->addr;
> +	msg.buf = data;
> +	msg.len = 2 + n;
> +	msg.flags = 0;
> +
> +	data[0] = reg >> 8;
> +	data[1] = reg & 0xff;
> +
> +	switch (n) {
> +	case 1:
> +		data[2 + 0] = values[0];
> +		break;
> +	case 2:
> +		data[2 + 0] = values[1];
> +		data[2 + 1] = values[0];
> +		break;
> +	case 4:
> +		data[2 + 0] = values[1];
> +		data[2 + 1] = values[0];
> +		data[2 + 2] = values[3];
> +		data[2 + 3] = values[2];
> +		break;
> +	default:
> +		v4l2_info(sd, "unsupported I2C write %d bytes from address 0x%04x\n",
> +			  n, reg);
> +	}
> +
> +	err = i2c_transfer(client->adapter, &msg, 1);
> +	if (err != 1) {
> +		v4l2_err(sd, "%s: writing register 0x%x from 0x%x failed\n",
> +			 __func__, reg, client->addr);
> +		return;
> +	}
> +
> +	if (debug < 3)
> +		return;
> +
> +	switch (n) {
> +	case 1:
> +		v4l2_info(sd, "I2C write 0x%04x = 0x%02x", reg, data[2 + 0]);
> +		break;
> +	case 2:
> +		v4l2_info(sd, "I2C write 0x%04x = 0x%02x%02x", reg, data[2 + 0],
> +			  data[2 + 1]);
> +		break;
> +	case 4:
> +		v4l2_info(sd, "I2C write 0x%04x = 0x%02x%02x%02x%02x", reg,
> +			  data[2 + 2], data[2 + 3], data[2 + 0], data[2 + 1]);
> +		break;
> +	default:
> +		v4l2_info(sd, "I2C unsupported write %d bytes from address 0x%04x\n",
> +			  n, reg);
> +	}
> +}
> +
> +static noinline u32 i2c_rdreg(struct v4l2_subdev *sd, u16 reg, u32 n)
> +{
> +	__le32 val = 0;
> +
> +	i2c_rd(sd, reg, (u8 __force *)&val, n);
> +
> +	return le32_to_cpu(val);
> +}
> +
> +static noinline void i2c_wrreg(struct v4l2_subdev *sd, u16 reg, u32 val, u32 n)
> +{
> +	__le32 raw = cpu_to_le32(val);
> +
> +	i2c_wr(sd, reg, (u8 __force *)&raw, n);
> +}
> +
> +static u16 __maybe_unused i2c_rd8(struct v4l2_subdev *sd, u16 reg)
> +{
> +	return i2c_rdreg(sd, reg, 1);
> +}
> +
> +static u16 __maybe_unused i2c_rd16(struct v4l2_subdev *sd, u16 reg)
> +{
> +	return i2c_rdreg(sd, reg, 2);
> +}
> +
> +static u32 __maybe_unused i2c_rd32(struct v4l2_subdev *sd, u16 reg)
> +{
> +	return i2c_rdreg(sd, reg, 4);
> +}
> +
> +static void __maybe_unused i2c_wr8(struct v4l2_subdev *sd, u16 reg, u16 val)
> +{
> +	i2c_wrreg(sd, reg, val, 1);
> +}
> +
> +static void i2c_wr16(struct v4l2_subdev *sd, u16 reg, u16 val)
> +{
> +	i2c_wrreg(sd, reg, val, 2);
> +}
> +
> +static void i2c_wr16_and_or(struct v4l2_subdev *sd, u16 reg, u32 mask, u16 val)
> +{
> +	u16 m = (u16) mask;
> +
> +	i2c_wrreg(sd, reg, (i2c_rd16(sd, reg) & m) | val, 2);
> +}
> +
> +static void i2c_wr32(struct v4l2_subdev *sd, u16 reg, u32 val)
> +{
> +	i2c_wrreg(sd, reg, val, 4);
> +}
> +
> +/* --------------- init --------------- */
> +
> +static void
> +tc358746_wr_csi_control(struct v4l2_subdev *sd, int val)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	u32 _val;
> +
> +	val &= CSI_CONFW_DATA_MASK;
> +	_val = CSI_CONFW_MODE_SET_MASK | CSI_CONFW_ADDRESS_CSI_CONTROL_MASK |
> +		val;
> +
> +	dev_dbg(&state->i2c_client->dev, "CSI_CONFW 0x%04x\n", _val);
> +	i2c_wr32(sd, CSI_CONFW, _val);
> +}
> +
> +static inline void tc358746_sleep_mode(struct v4l2_subdev *sd, int enable)
> +{
> +	i2c_wr16_and_or(sd, SYSCTL, ~SYSCTL_SLEEP_MASK,
> +			enable ? SYSCTL_SLEEP_MASK : 0);
> +}
> +
> +static inline void tc358746_sreset(struct v4l2_subdev *sd)
> +{
> +	i2c_wr16(sd, SYSCTL, SYSCTL_SRESET_MASK);
> +	udelay(10);
> +	i2c_wr16(sd, SYSCTL, 0);
> +}
> +
> +static inline void tc358746_enable_stream(struct v4l2_subdev *sd, int enable)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +
> +	dev_dbg(&state->i2c_client->dev, "%sable\n", enable ? "en" : "dis");
> +
> +	mutex_lock(&state->confctl_mutex);
> +	if (!enable) {
> +		i2c_wr16_and_or(sd, PP_MISC, ~PP_MISC_FRMSTOP_MASK,
> +				PP_MISC_FRMSTOP_MASK);
> +		i2c_wr16_and_or(sd, CONFCTL, ~CONFCTL_PPEN_MASK, 0);
> +		i2c_wr16_and_or(sd, PP_MISC, ~PP_MISC_RSTPTR_MASK,
> +				PP_MISC_RSTPTR_MASK);
> +
> +		i2c_wr32(sd, CSIRESET, (CSIRESET_RESET_CNF_MASK |
> +					CSIRESET_RESET_MODULE_MASK));
> +		i2c_wr16(sd, DBG_ACT_LINE_CNT, 0);
> +	} else {
> +		i2c_wr16(sd, PP_MISC, 0);
> +		i2c_wr16_and_or(sd, CONFCTL, ~CONFCTL_PPEN_MASK,
> +				CONFCTL_PPEN_MASK);
> +	}
> +	mutex_unlock(&state->confctl_mutex);
> +}
> +
> +static void tc358746_set_pll(struct v4l2_subdev *sd)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	struct tc358746_csi_param *csi_setting =
> +		tc358746_g_cur_csi_settings(state);
> +	struct device *dev = &state->i2c_client->dev;
> +	u16 pllctl0 = i2c_rd16(sd, PLLCTL0);
> +	u16 pllctl1 = i2c_rd16(sd, PLLCTL1);
> +	u16 pll_frs = csi_setting->speed_range;
> +	u16 pllctl0_new;
> +
> +	/*
> +	 * Calculation:
> +	 * speed_per_lane = (pllinclk_hz * (fbd + 1)) / 2^frs
> +	 *
> +	 * Calculation used by REF_02:
> +	 * speed_per_lane = (pllinclk_hz * fbd) / 2^frs
> +	 */
> +	state->pll_fbd = csi_setting->speed_per_lane / state->pllinclk_hz;
> +	state->pll_fbd <<= pll_frs;
> +
> +	pllctl0_new = PLLCTL0_PLL_PRD_SET(state->pll_prd) |
> +		      PLLCTL0_PLL_FBD_SET(state->pll_fbd);
> +
> +	/*
> +	 * Only rewrite when needed (new value or disabled), since rewriting
> +	 * triggers another format change event.
> +	 */
> +	if ((pllctl0 != pllctl0_new) ||
> +	    ((pllctl1 & PLLCTL1_PLL_EN_MASK) == 0)) {
> +		u16 pllctl1_mask = (u16) ~(PLLCTL1_PLL_FRS_MASK |
> +					   PLLCTL1_RESETB_MASK  |
> +					   PLLCTL1_PLL_EN_MASK);
> +		u16 pllctl1_val = PLLCTL1_PLL_FRS_SET(pll_frs) |
> +				  PLLCTL1_RESETB_MASK | PLLCTL1_PLL_EN_MASK;
> +
> +		dev_dbg(dev, "updating PLL clock\n");
> +		i2c_wr16(sd, PLLCTL0, pllctl0_new);
> +		i2c_wr16_and_or(sd, PLLCTL1, pllctl1_mask, pllctl1_val);
> +		udelay(1000);
> +		i2c_wr16_and_or(sd, PLLCTL1, ~PLLCTL1_CKEN_MASK,
> +				PLLCTL1_CKEN_MASK);
> +	}
> +
> +	tc358746_dump_pll(dev, state);
> +}
> +
> +static void tc358746_set_csi_color_space(struct v4l2_subdev *sd)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	const struct tc358746_mbus_fmt *tc358746_fmt =
> +		tc358746_get_format(state->fmt.code);
> +
> +	/* currently no self defined csi user data type id's are supported */
> +	mutex_lock(&state->confctl_mutex);
> +	i2c_wr16_and_or(sd, DATAFMT,
> +			~(DATAFMT_PDFMT_MASK | DATAFMT_UDT_EN_MASK),
> +			DATAFMT_PDFMT_SET(tc358746_fmt->pdformat));
> +	i2c_wr16_and_or(sd, CONFCTL, ~CONFCTL_PDATAF_MASK,
> +			CONFCTL_PDATAF_SET(tc358746_fmt->pdataf));
> +	mutex_unlock(&state->confctl_mutex);
> +}
> +
> +static void tc38764_debug_pattern_80(struct v4l2_subdev *sd)
> +{
> +	int i;

unsigned int

> +
> +	i2c_wr16(sd, DBG_ACT_LINE_CNT, 0x8000);
> +	i2c_wr16(sd, DBG_LINE_WIDTH, 0x0396);
> +	i2c_wr16(sd, DBG_VERT_BLANK_LINE_CNT, 0x0000);
> +
> +	for (i = 0; i < 80; i++)
> +		i2c_wr16(sd, DBG_VIDEO_DATA, 0xff7f);
> +	i2c_wr16(sd, DBG_VIDEO_DATA, 0xff00);
> +	for (i = 0; i < 40; i++)
> +		i2c_wr16(sd, DBG_VIDEO_DATA, 0xffff);
> +	i2c_wr16(sd, DBG_VIDEO_DATA, 0xc0ff);
> +	for (i = 0; i < 40; i++)
> +		i2c_wr16(sd, DBG_VIDEO_DATA, 0xc000);
> +	for (i = 0; i < 80; i++)
> +		i2c_wr16(sd, DBG_VIDEO_DATA, 0x7f00);
> +	for (i = 0; i < 80; i++)
> +		i2c_wr16(sd, DBG_VIDEO_DATA, 0x7fff);
> +	i2c_wr16(sd, DBG_VIDEO_DATA, 0x0000);
> +	for (i = 0; i < 40; i++)
> +		i2c_wr16(sd, DBG_VIDEO_DATA, 0x00ff);
> +	i2c_wr16(sd, DBG_VIDEO_DATA, 0x00ff);
> +	for (i = 0; i < 40; i++)
> +		i2c_wr16(sd, DBG_VIDEO_DATA, 0x0000);
> +	i2c_wr16(sd, DBG_VIDEO_DATA, 0x007f);
> +
> +	i2c_wr16(sd, DBG_ACT_LINE_CNT, 0xC1DF);
> +}
> +
> +static void tc358746_enable_csi_lanes(struct v4l2_subdev *sd, int enable)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	struct tc358746_csi_param *csi_setting =
> +		tc358746_g_cur_csi_settings(state);
> +	unsigned int lanes = csi_setting->lane_num;
> +	u32 val = 0;
> +
> +	if (lanes < 1 || !enable)
> +		i2c_wr32(sd, CLW_CNTRL, CLW_CNTRL_CLW_LANEDISABLE_MASK);
> +	if (lanes < 1 || !enable)
> +		i2c_wr32(sd, D0W_CNTRL, D0W_CNTRL_D0W_LANEDISABLE_MASK);
> +	if (lanes < 2 || !enable)
> +		i2c_wr32(sd, D1W_CNTRL, D1W_CNTRL_D1W_LANEDISABLE_MASK);
> +	if (lanes < 3 || !enable)
> +		i2c_wr32(sd, D2W_CNTRL, D2W_CNTRL_D2W_LANEDISABLE_MASK);
> +	if (lanes < 4 || !enable)
> +		i2c_wr32(sd, D3W_CNTRL, D2W_CNTRL_D3W_LANEDISABLE_MASK);
> +
> +	if (lanes > 0 && enable)
> +		val |= HSTXVREGEN_CLM_HSTXVREGEN_MASK |
> +			HSTXVREGEN_D0M_HSTXVREGEN_MASK;
> +	if (lanes > 1 && enable)
> +		val |= HSTXVREGEN_D1M_HSTXVREGEN_MASK;
> +	if (lanes > 2 && enable)
> +		val |= HSTXVREGEN_D2M_HSTXVREGEN_MASK;
> +	if (lanes > 3 && enable)
> +		val |= HSTXVREGEN_D3M_HSTXVREGEN_MASK;
> +
> +	i2c_wr32(sd, HSTXVREGEN, val);
> +}
> +
> +static void tc358746_set_csi(struct v4l2_subdev *sd)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	struct tc358746_csi_param *csi_setting =
> +		tc358746_g_cur_csi_settings(state);
> +	bool en_continuous_clk = csi_setting->is_continuous_clk;
> +	u32 val;
> +
> +	val = TCLK_HEADERCNT_TCLK_ZEROCNT_SET(csi_setting->tclk_zerocnt) |
> +	      TCLK_HEADERCNT_TCLK_PREPARECNT_SET(csi_setting->tclk_preparecnt);
> +	i2c_wr32(sd, TCLK_HEADERCNT, val);
> +	val = THS_HEADERCNT_THS_ZEROCNT_SET(csi_setting->ths_zerocnt) |
> +	      THS_HEADERCNT_THS_PREPARECNT_SET(csi_setting->ths_preparecnt);
> +	i2c_wr32(sd, THS_HEADERCNT, val);
> +	i2c_wr32(sd, TWAKEUP, csi_setting->twakeupcnt);
> +	i2c_wr32(sd, TCLK_POSTCNT, csi_setting->tclk_postcnt);
> +	i2c_wr32(sd, THS_TRAILCNT, csi_setting->ths_trailcnt);
> +	i2c_wr32(sd, LINEINITCNT, csi_setting->lineinitcnt);
> +	i2c_wr32(sd, LPTXTIMECNT, csi_setting->lptxtimecnt);
> +	i2c_wr32(sd, TCLK_TRAILCNT, csi_setting->tclk_trailcnt);
> +	i2c_wr32(sd, TXOPTIONCNTRL,
> +		 en_continuous_clk ? TXOPTIONCNTRL_CONTCLKMODE_MASK : 0);
> +
> +	if (state->test)
> +		tc38764_debug_pattern_80(sd);
> +
> +	tc358746_dump_csi(&state->i2c_client->dev, csi_setting);
> +}
> +
> +static void tc358746_enable_csi_module(struct v4l2_subdev *sd, int enable)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	struct tc358746_csi_param *csi_setting =
> +		tc358746_g_cur_csi_settings(state);
> +	unsigned int lanes = csi_setting->lane_num;
> +	u32 val;
> +
> +	if (!enable)
> +		return;
> +
> +	i2c_wr32(sd, STARTCNTRL, STARTCNTRL_START_MASK);
> +	i2c_wr32(sd, CSI_START, CSI_START_STRT_MASK);
> +
> +	val = CSI_CONTROL_NOL_1_MASK;
> +	if (lanes == 2)
> +		val = CSI_CONTROL_NOL_2_MASK;
> +	else if (lanes == 3)
> +		val = CSI_CONTROL_NOL_3_MASK;
> +	else if (lanes == 4)
> +		val = CSI_CONTROL_NOL_4_MASK;
> +
> +	val |= CSI_CONTROL_CSI_MODE_MASK | CSI_CONTROL_TXHSMD_MASK;
> +	tc358746_wr_csi_control(sd, val);
> +}
> +
> +static void tc358746_set_buffers(struct v4l2_subdev *sd)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	struct device *dev = &state->i2c_client->dev;
> +	const struct tc358746_mbus_fmt *tc358746_mbusfmt =
> +		tc358746_get_format(state->fmt.code);
> +	unsigned int byte_per_line =
> +		(state->fmt.width * tc358746_mbusfmt->bpp) / 8;
> +
> +	i2c_wr16(sd, FIFOCTL, state->vb_fifo);
> +	i2c_wr16(sd, WORDCNT, byte_per_line);
> +	dev_dbg(dev, "FIFOCTL 0x%02x: WORDCNT 0x%02x\n",
> +		state->vb_fifo, byte_per_line);
> +}
> +
> +/* --------------- CORE OPS --------------- */
> +
> +static int tc358746_log_status(struct v4l2_subdev *sd)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	uint16_t sysctl = i2c_rd16(sd, SYSCTL);
> +
> +	v4l2_info(sd, "-----Chip status-----\n");
> +	v4l2_info(sd, "Chip ID: 0x%02lx\n",
> +		  (i2c_rd16(sd, CHIPID) & CHIPID_CHIPID_MASK) >> 8);
> +	v4l2_info(sd, "Chip revision: 0x%02lx\n",
> +		  i2c_rd16(sd, CHIPID) & CHIPID_REVID_MASK);
> +	v4l2_info(sd, "Sleep mode: %s\n", sysctl & SYSCTL_SLEEP_MASK ?
> +		  "on" : "off");
> +
> +	v4l2_info(sd, "-----CSI-TX status-----\n");
> +	v4l2_info(sd, "Waiting for particular sync signal: %s\n",
> +			(i2c_rd16(sd, CSI_STATUS) & CSI_STATUS_S_WSYNC_MASK) ?
> +			"yes" : "no");
> +	v4l2_info(sd, "Transmit mode: %s\n",
> +			(i2c_rd16(sd, CSI_STATUS) & CSI_STATUS_S_TXACT_MASK) ?
> +			"yes" : "no");
> +	v4l2_info(sd, "Stopped: %s\n",
> +			(i2c_rd16(sd, CSI_STATUS) & CSI_STATUS_S_HLT_MASK) ?
> +			"yes" : "no");
> +	v4l2_info(sd, "Color space: %s\n",
> +			state->fmt.code == MEDIA_BUS_FMT_UYVY8_2X8 ?
> +			"YCbCr 422 8-bit" : "Unsupported");
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +static void tc358746_print_register_map(struct v4l2_subdev *sd)
> +{
> +	v4l2_info(sd, "0x0000-0x0050: Global Register\n");
> +	v4l2_info(sd, "0x0056-0x0070: Rx Control Registers\n");
> +	v4l2_info(sd, "0x0080-0x00F8: Rx Status Registers\n");
> +	v4l2_info(sd, "0x0100-0x0150: Tx D-PHY Register\n");
> +	v4l2_info(sd, "0x0204-0x0238: Tx PPI Register\n");
> +	v4l2_info(sd, "0x040c-0x0518: Tx Control Register\n");
> +}
> +
> +static int tc358746_get_reg_size(u16 address)
> +{
> +	if (address <= 0x00ff)
> +		return 2;
> +	else if ((address >= 0x0100) && (address <= 0x05FF))
> +		return 4;
> +	else
> +		return 1;
> +}
> +
> +static int tc358746_g_register(struct v4l2_subdev *sd,
> +			       struct v4l2_dbg_register *reg)
> +{
> +	if (reg->reg > 0xffff) {
> +		tc358746_print_register_map(sd);
> +		return -EINVAL;
> +	}
> +
> +	reg->size = tc358746_get_reg_size(reg->reg);
> +
> +	reg->val = i2c_rdreg(sd, reg->reg, reg->size);
> +
> +	return 0;
> +}
> +
> +static int tc358746_s_register(struct v4l2_subdev *sd,
> +			       const struct v4l2_dbg_register *reg)
> +{
> +	if (reg->reg > 0xffff) {
> +		tc358746_print_register_map(sd);
> +		return -EINVAL;
> +	}
> +
> +	i2c_wrreg(sd, (u16)reg->reg, reg->val,
> +			tc358746_get_reg_size(reg->reg));
> +
> +	return 0;
> +}
> +#endif
> +
> +/* --------------- video ops --------------- */
> +
> +static int tc358746_g_mbus_config(struct v4l2_subdev *sd,
> +			     struct v4l2_mbus_config *cfg)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	struct tc358746_csi_param *csi_setting =
> +		tc358746_g_cur_csi_settings(state);
> +
> +	cfg->type = V4L2_MBUS_CSI2_DPHY;
> +	cfg->flags = csi_setting->is_continuous_clk ?
> +			V4L2_MBUS_CSI2_CONTINUOUS_CLOCK :
> +			V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;

The other device can get this from its firmware data, just as this one.

> +
> +	switch (csi_setting->lane_num) {
> +	case 1:
> +		cfg->flags |= V4L2_MBUS_CSI2_1_LANE;
> +		break;
> +	case 2:
> +		cfg->flags |= V4L2_MBUS_CSI2_2_LANE;
> +		break;
> +	case 3:
> +		cfg->flags |= V4L2_MBUS_CSI2_3_LANE;
> +		break;
> +	case 4:
> +		cfg->flags |= V4L2_MBUS_CSI2_4_LANE;
> +		break;

If you don't have a need to change these dynamically right now, please drop
g_mbus_config(). We'll soon have a better solution (using frame descriptors
Niklas has been working on).

> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int tc358746_s_power(struct v4l2_subdev *sd, int on)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +
> +	/*
> +	 * REF_01:
> +	 * Softreset don't reset configuration registers content but is needed
> +	 * during power-on to trigger a csi LP-11 state change and during
> +	 * power-off to disable the csi-module.
> +	 */
> +	tc358746_sreset(sd);
> +
> +	if (state->fmt_changed) {
> +		tc358746_set_buffers(sd);
> +		tc358746_set_csi(sd);
> +		tc358746_set_csi_color_space(sd);
> +
> +		/* as recommend in REF_01 */
> +		tc358746_sleep_mode(sd, 1);
> +		tc358746_set_pll(sd);
> +		tc358746_sleep_mode(sd, 0);
> +
> +		state->fmt_changed = false;
> +	}
> +
> +	tc358746_enable_csi_lanes(sd, on);
> +	tc358746_enable_csi_module(sd, on);
> +	tc358746_sleep_mode(sd, !on);
> +
> +	return 0;
> +}
> +
> +static int tc358746_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> +	tc358746_enable_stream(sd, enable);

Could you call tc358746_enable_stream() instead of tc358746_s_stream() and
drop tc358746_s_stream()? Maybe the function return type and arguments
need to be changed?

> +
> +	return 0;
> +}
> +
> +/* --------------- pad ops --------------- */
> +
> +static int tc358746_enum_mbus_code(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_pad_config *cfg,
> +				   struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	if (code->index >= ARRAY_SIZE(tc358746_formats))
> +		return -EINVAL;
> +
> +	code->code = tc358746_formats[code->index].code;
> +
> +	return 0;
> +}
> +
> +static struct v4l2_mbus_framefmt *
> +__tc358746_get_pad_format(struct v4l2_subdev *sd,
> +			  struct v4l2_subdev_pad_config *cfg,
> +			  unsigned int pad, u32 which)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +
> +	switch (which) {
> +	case V4L2_SUBDEV_FORMAT_TRY:
> +		return v4l2_subdev_get_try_format(sd, cfg, pad);
> +	case V4L2_SUBDEV_FORMAT_ACTIVE:
> +		return &state->fmt;
> +	default:
> +		return NULL;
> +	}
> +}
> +
> +static int tc358746_get_fmt(struct v4l2_subdev *sd,
> +		struct v4l2_subdev_pad_config *cfg,
> +		struct v4l2_subdev_format *format)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +
> +	if (format->pad != 0 && format->pad != 1)
> +		return -EINVAL;
> +
> +	format->format.code = state->fmt.code;
> +	format->format.width = state->fmt.width;
> +	format->format.height = state->fmt.height;
> +	format->format.field = state->fmt.field;
> +
> +	return 0;
> +}
> +
> +static int tc358746_set_fmt(struct v4l2_subdev *sd,
> +			    struct v4l2_subdev_pad_config *cfg,
> +			    struct v4l2_subdev_format *format)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	struct device *dev = &state->i2c_client->dev;
> +	struct media_pad *pad = &state->pads[format->pad];
> +	struct media_pad *remote_sensor_pad =
> +		media_entity_remote_pad(&state->pads[0]);
> +	struct v4l2_subdev *sensor_sd;
> +	struct v4l2_mbus_framefmt *mbusformat;
> +	const struct tc358746_mbus_fmt *tc358746_mbusformat;
> +	struct v4l2_ctrl *ctrl;
> +	unsigned int pclk, hblank;
> +	int new_freq, cur_freq = v4l2_ctrl_g_ctrl(state->link_freq);
> +	u16 vb_fifo;
> +
> +	if (pad->flags == MEDIA_PAD_FL_SOURCE)
> +		return tc358746_get_fmt(sd, cfg, format);
> +
> +	mbusformat = __tc358746_get_pad_format(sd, cfg, format->pad,
> +					       format->which);
> +	if (!mbusformat)
> +		return -EINVAL;
> +
> +	tc358746_mbusformat = tc358746_get_format(format->format.code);
> +	if (!tc358746_mbusformat) {
> +		format->format.code = tc358746_def_fmt.code;
> +		tc358746_mbusformat = tc358746_get_format(format->format.code);
> +	}
> +
> +	/*
> +	 * Some sensors change their hblank and pclk value on different formats,
> +	 * so we need to request it again.
> +	 */
> +	sensor_sd = media_entity_to_v4l2_subdev(remote_sensor_pad->entity);
> +	ctrl = v4l2_ctrl_find(sensor_sd->ctrl_handler, V4L2_CID_PIXEL_RATE);

ctrl may be NULL.

> +	pclk = v4l2_ctrl_g_ctrl_int64(ctrl);
> +	if (pclk != state->pclk) {
> +		dev_dbg(dev, "Update pclk from %u to %u\n", state->pclk, pclk);
> +		state->pclk = pclk;
> +	}
> +	ctrl = v4l2_ctrl_find(sensor_sd->ctrl_handler, V4L2_CID_HBLANK);

Ditto. There are other such cases below, too, in the link validation
functio.

> +	hblank = v4l2_ctrl_g_ctrl(ctrl);
> +	if (hblank != state->hblank) {
> +		dev_dbg(dev, "Update hblank from %u to %u\n", state->hblank,
> +			hblank);
> +		state->hblank = hblank;
> +	}
> +
> +	/*
> +	 * Normaly the HW has no size limitations but we have to check if the
> +	 * csi timings are valid for this size. The timings can be adjust by the
> +	 * fifo size. If this doesn't work we have to do this check again with a
> +	 * other csi link frequency if it is possible.
> +	 */
> +	new_freq = tc358746_adjust_timings(state, tc358746_mbusformat,
> +					   &format->format.width, &vb_fifo);
> +
> +	/* Currently only a few YUV based formats are supported */
> +	if (tc358746_format_supported(format->format.code))
> +		format->format.code = MEDIA_BUS_FMT_UYVY8_2X8;
> +
> +	/* Currently only non interleaved images are supported */
> +	format->format.field = V4L2_FIELD_NONE;
> +
> +	*mbusformat = format->format;
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> +		state->fmt_changed = true;
> +		state->vb_fifo = vb_fifo;
> +		if (new_freq != cur_freq)
> +			v4l2_ctrl_s_ctrl(state->link_freq, new_freq);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +tc358746_link_validate(struct v4l2_subdev *sd, struct media_link *link,
> +		       struct v4l2_subdev_format *source_fmt,
> +		       struct v4l2_subdev_format *sink_fmt)
> +{
> +	struct tc358746_state *state = to_state(sd);
> +	struct device *dev = &state->i2c_client->dev;
> +	const struct tc358746_mbus_fmt *tc358746_mbusformat;
> +	struct v4l2_subdev *sensor_sd;
> +	struct v4l2_ctrl *ctrl;
> +	unsigned int pclk, pclk_old = state->pclk;
> +	unsigned int hblank, hblank_old = state->hblank;
> +	int new_freq;
> +	u16 vb_fifo;
> +
> +	/*
> +	 * Only validate if the timings are changed, after the link was already
> +	 * initialized. This can be happen if the parallel sensor frame interval
> +	 * is changed. Format checks are perfomed by the common code.
> +	 */
> +
> +	tc358746_mbusformat = tc358746_get_format(sink_fmt->format.code);
> +	if (!tc358746_mbusformat)
> +		return -EINVAL; /* Format was changed too and is invalid */
> +
> +	sensor_sd = media_entity_to_v4l2_subdev(link->source->entity);
> +	ctrl = v4l2_ctrl_find(sensor_sd->ctrl_handler, V4L2_CID_PIXEL_RATE);
> +	pclk = v4l2_ctrl_g_ctrl_int64(ctrl);
> +	if (pclk != state->pclk) {
> +		dev_dbg(dev, "%s pixel rate is changed\n", sensor_sd->name);
> +		state->pclk = pclk;
> +	}
> +
> +	ctrl = v4l2_ctrl_find(sensor_sd->ctrl_handler, V4L2_CID_HBLANK);
> +	hblank = v4l2_ctrl_g_ctrl(ctrl);
> +	if (hblank != state->hblank) {
> +		dev_dbg(dev,
> +			"%s hblank interval is changed\n", sensor_sd->name);
> +		state->hblank = hblank;
> +	}
> +
> +	new_freq = tc358746_adjust_timings(state, tc358746_mbusformat,
> +					   &source_fmt->format.width, &vb_fifo);
> +
> +	if (new_freq != v4l2_ctrl_g_ctrl(state->link_freq)) {
> +		/*
> +		 * This can lead into undefined behaviour, so we don't support
> +		 * dynamic changes due to a to late re-configuration.
> +		 */
> +		dev_err(dev,
> +			"%s format can't be applied re-run the whole s_fmt\n",
> +			sensor_sd->name);
> +		state->pclk = pclk_old;
> +		state->hblank = hblank_old;
> +
> +		return -EINVAL;
> +	}
> +
> +	state->fmt_changed = true;
> +	state->vb_fifo = vb_fifo;
> +
> +	return 0;
> +}
> +
> +static int tc358764_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct tc358746_state *state = container_of(ctrl->handler,
> +					       struct tc358746_state, hdl);
> +	struct device *dev = &state->i2c_client->dev;
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_LINK_FREQ:
> +		dev_info(dev, "Update link-frequency %llu -> %llu\n",
> +			 state->link_frequencies[ctrl->cur.val],
> +			 state->link_frequencies[ctrl->val]);
> +
> +		return 0;
> +	case V4L2_CID_TEST_PATTERN:
> +		state->test = ctrl->val;
> +		return 0;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int tc358746_link_setup(struct media_entity *entity,
> +			       const struct media_pad *local,
> +			       const struct media_pad *remote, u32 flags)
> +{
> +	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
> +	struct v4l2_subdev *ps_sd = media_entity_to_v4l2_subdev(remote->entity);
> +	struct tc358746_state *state = to_state(sd);
> +	struct v4l2_ctrl *ctrl;
> +
> +	/* no special requirements on source pads */
> +	if (local->flags & MEDIA_PAD_FL_SOURCE)
> +		return 0;
> +
> +	dev_dbg(sd->dev, "link setup '%s':%d->'%s':%d[%d]",
> +		remote->entity->name, remote->index, local->entity->name,
> +		local->index, flags & MEDIA_LNK_FL_ENABLED);
> +
> +	/*
> +	 * The remote parallel sensor must support pixel rate and hblank query
> +	 */
> +	ctrl = v4l2_ctrl_find(ps_sd->ctrl_handler, V4L2_CID_PIXEL_RATE);
> +	if (!ctrl) {
> +		dev_err(sd->dev, "Subdev %s must support V4L2_CID_PIXEL_RATE\n",
> +			ps_sd->name);
> +		return -EINVAL;
> +	}
> +	state->pclk = v4l2_ctrl_g_ctrl_int64(ctrl);

The values could well change after enabling a link. You're also using the
controls later on. How about storing the pointer to the control instead?

> +
> +	ctrl = v4l2_ctrl_find(ps_sd->ctrl_handler, V4L2_CID_HBLANK);
> +	if (!ctrl) {
> +		dev_err(sd->dev, "Subdev %s must support V4L2_CID_HBLANK\n",
> +			ps_sd->name);
> +		return -EINVAL;
> +	}
> +	state->hblank = v4l2_ctrl_g_ctrl(ctrl);
> +
> +	return 0;
> +}
> +
> +/* -------------------------------------------------------------------------- */
> +
> +static const struct v4l2_ctrl_ops tc358764_ctrl_ops = {
> +	.s_ctrl = tc358764_s_ctrl,
> +};
> +
> +static const struct v4l2_subdev_core_ops tc358746_core_ops = {
> +	.log_status = tc358746_log_status,
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +	.g_register = tc358746_g_register,
> +	.s_register = tc358746_s_register,
> +#endif
> +	.s_power = tc358746_s_power,
> +};
> +
> +static const struct v4l2_subdev_video_ops tc358746_video_ops = {
> +	.g_mbus_config = tc358746_g_mbus_config,
> +	.s_stream = tc358746_s_stream,
> +};
> +
> +static const struct v4l2_subdev_pad_ops tc358746_pad_ops = {
> +	.enum_mbus_code = tc358746_enum_mbus_code,
> +	.set_fmt = tc358746_set_fmt,
> +	.get_fmt = tc358746_get_fmt,
> +	.link_validate = tc358746_link_validate,
> +};
> +
> +static const struct v4l2_subdev_ops tc358746_ops = {
> +	.core = &tc358746_core_ops,
> +	.video = &tc358746_video_ops,
> +	.pad = &tc358746_pad_ops,
> +};
> +
> +static const struct media_entity_operations tc358746_entity_ops = {
> +	.link_setup = &tc358746_link_setup,
> +	.link_validate = &v4l2_subdev_link_validate,
> +};
> +
> +/* --------------- PROBE / REMOVE --------------- */
> +
> +static int tc358746_set_lane_settings(struct tc358746_state *state,
> +				      struct v4l2_fwnode_endpoint *fw)
> +{
> +	struct device *dev = &state->i2c_client->dev;
> +	int i;
> +
> +	for (i = 0; i < fw->nr_of_link_frequencies; i++) {
> +		struct tc358746_csi_param *s =
> +			&state->link_freq_settings[i];
> +		u32 bps_pr_lane;
> +
> +		state->link_frequencies[i] = fw->link_frequencies[i];
> +
> +		/*
> +		 * The CSI bps per lane must be between 62.5 Mbps and 1 Gbps.
> +		 * bps_pr_lane = 2 * link_freq, because MIPI data lane is double
> +		 * data rate.
> +		 */
> +		bps_pr_lane = 2 * fw->link_frequencies[i];
> +		if (bps_pr_lane < 62500000U || bps_pr_lane > 1000000000U) {
> +			dev_err(dev, "unsupported bps per lane: %u bps\n",
> +				bps_pr_lane);
> +			return -EINVAL;
> +		}
> +
> +		if (bps_pr_lane > 500000000)
> +			s->speed_range = 0;
> +		else if (bps_pr_lane > 250000000)
> +			s->speed_range = 1;
> +		else if (bps_pr_lane > 125000000)
> +			s->speed_range = 2;
> +		else
> +			s->speed_range = 3;
> +
> +		s->unit_clk_hz = state->pllinclk_hz >> s->speed_range;
> +		s->unit_clk_mul = bps_pr_lane / s->unit_clk_hz;
> +		s->speed_per_lane = bps_pr_lane;
> +		s->lane_num = fw->bus.mipi_csi2.num_data_lanes;
> +		s->is_continuous_clk = fw->bus.mipi_csi2.flags &
> +			V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
> +
> +		if (s->speed_per_lane != 432000000U)
> +			dev_warn(dev, "untested bps per lane: %u bps\n",
> +				 s->speed_per_lane);
> +
> +		dev_dbg(dev, "%s: lane setting %d\n", __func__, i);
> +		dev_dbg(dev, "unit_clk %uHz: unit_clk_mul %u: speed_range %u: speed_per_lane(bps/lane) %u: csi_lange_numbers %u\n",
> +			s->unit_clk_hz, s->unit_clk_mul, s->speed_range,
> +			s->speed_per_lane, s->lane_num);
> +	}
> +
> +	state->link_frequencies_num = fw->nr_of_link_frequencies;
> +
> +	return 0;
> +}
> +
> +static void tc358746_gpio_reset(struct tc358746_state *state)
> +{
> +	usleep_range(5000, 10000);
> +	gpiod_set_value(state->reset_gpio, 1);
> +	usleep_range(1000, 2000);
> +	gpiod_set_value(state->reset_gpio, 0);
> +	msleep(20);
> +}
> +
> +static int tc358746_apply_fw(struct tc358746_state *state)
> +{
> +	struct tc358746_csi_param *csi_setting;
> +	int err, i;
> +
> +	for (i = 0; i < state->link_frequencies_num; i++) {
> +		csi_setting = &state->link_freq_settings[i];
> +
> +		err = tc358746_calculate_csi_txtimings(state, csi_setting);
> +		if (err) {
> +			dev_err(&state->i2c_client->dev,
> +				"Failed to calc csi-tx tminings\n");
> +			return err;
> +		}
> +	}
> +
> +	/*
> +	 * Set it to the hw default value. The correct value will be set during
> +	 * set_fmt(), since it depends on the pclk and and the resulution.
> +	 */
> +	state->vb_fifo = 1;
> +
> +	err = clk_prepare_enable(state->refclk);
> +	if (err) {
> +		dev_err(&state->i2c_client->dev, "Failed to enable clock\n");
> +		return err;
> +	}
> +
> +	if (state->reset_gpio)
> +		tc358746_gpio_reset(state);
> +
> +	return 0;
> +}
> +
> +static int tc358746_probe_fw(struct tc358746_state *state)
> +{
> +	struct device *dev = &state->i2c_client->dev;
> +	struct v4l2_fwnode_endpoint endpoint = {
> +		.bus_type = V4L2_MBUS_CSI2_DPHY,
> +	};
> +	struct fwnode_handle *fw_node;
> +	unsigned int refclk, pllinclk;
> +	unsigned char pll_prediv;
> +	int ret = -EINVAL;
> +
> +	/* Parse all clocks */
> +	state->refclk = devm_clk_get(dev, "refclk");
> +	if (IS_ERR(state->refclk)) {
> +		if (PTR_ERR(state->refclk) != -EPROBE_DEFER)
> +			dev_err(dev, "failed to get refclk: %ld\n",
> +				PTR_ERR(state->refclk));
> +		return PTR_ERR(state->refclk);
> +	}
> +
> +	refclk = clk_get_rate(state->refclk);
> +	if (refclk < 6000000 || refclk > 40000000) {
> +		dev_err(dev, "refclk must between 6MHz and 40MHz\n");
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * The PLL input clock is obtained by dividing refclk by pll_prd.
> +	 * It must be between 4 MHz and 40 MHz, lower frequency is better.
> +	 */
> +	pll_prediv = DIV_ROUND_CLOSEST(refclk, 4000000);
> +	if (pll_prediv < 1 || pll_prediv > 16) {
> +		dev_err(dev, "invalid pll pre-divider value: %d\n", pll_prediv);
> +		return -EINVAL;
> +	}
> +	state->pll_prd = pll_prediv;
> +
> +	pllinclk = DIV_ROUND_CLOSEST(refclk, pll_prediv);
> +	if (pllinclk < 4000000 || pllinclk > 40000000) {
> +		dev_err(dev, "invalid pll input clock: %d Hz\n", pllinclk);
> +		return -EINVAL;
> +	}
> +	state->pllinclk_hz = pllinclk;
> +
> +	/* Now parse the fw-node */
> +	fwnode_graph_for_each_endpoint(dev_fwnode(dev), fw_node) {
> +		struct fwnode_endpoint fw_ep;
> +
> +		ret = fwnode_graph_parse_endpoint(fw_node, &fw_ep);
> +		if (ret)
> +			return -EINVAL;
> +
> +		/* get downstream endpoint */
> +		if (fw_ep.port == 1)
> +			break;
> +	}
> +
> +	if (!fw_node) {
> +		dev_err(dev, "missing endpoint node\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = v4l2_fwnode_endpoint_alloc_parse(fw_node, &endpoint);
> +	if (ret) {
> +		dev_err(dev, "failed to parse endpoint %d\n", ret);
> +		return ret;
> +	}
> +
> +	if (endpoint.bus.mipi_csi2.num_data_lanes == 0 ||
> +	    endpoint.nr_of_link_frequencies == 0) {
> +		dev_err(dev, "missing CSI-2 properties in endpoint\n");
> +		ret = -EINVAL;
> +		goto free_ep;
> +	}
> +
> +	if (endpoint.bus.mipi_csi2.num_data_lanes > 4) {
> +		dev_err(dev, "invalid number of lanes\n");
> +		ret = -EINVAL;
> +		goto free_ep;
> +	}
> +
> +	state->link_freq_settings =
> +		devm_kcalloc(dev, endpoint.nr_of_link_frequencies,
> +			     sizeof(*state->link_freq_settings), GFP_KERNEL);
> +	if (!state->link_freq_settings) {
> +		ret = -ENOMEM;
> +		goto free_ep;
> +	}
> +
> +	state->link_frequencies =
> +		devm_kcalloc(dev, endpoint.nr_of_link_frequencies,
> +			     sizeof(*state->link_frequencies), GFP_KERNEL);
> +	if (!state->link_frequencies) {
> +		ret = -ENOMEM;
> +		goto free_ep;
> +	}
> +
> +	ret = tc358746_set_lane_settings(state, &endpoint);
> +	if (ret)
> +		goto free_ep;
> +
> +	state->reset_gpio = devm_gpiod_get_optional(dev, "reset",
> +						    GPIOD_OUT_LOW);
> +	if (IS_ERR(state->reset_gpio)) {
> +		dev_err(dev, "failed to get reset gpio\n");
> +		return PTR_ERR(state->reset_gpio);

Shouldn't the endpoint be freed here, too?

> +	}
> +
> +	ret = 0;
> +
> +free_ep:
> +	v4l2_fwnode_endpoint_free(&endpoint);
> +	return ret;
> +}
> +
> +static int tc358746_parse_endpoint(struct device *dev,
> +				   struct v4l2_fwnode_endpoint *vep,
> +				   struct v4l2_async_subdev *asd)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +
> +	if (!fwnode_device_is_available(asd->match.fwnode)) {
> +		v4l2_err(sd, "remote is not available\n");
> +		return -ENOTCONN;
> +	}
> +
> +	if (vep->bus_type != V4L2_MBUS_PARALLEL) {
> +		v4l2_err(sd, "invalid bus type, must be PARALLEL\n");
> +		return -ENOTCONN;
> +	}
> +
> +	return 0;
> +};
> +
> +static int tc358746_async_register(struct v4l2_subdev *sd)
> +{
> +	unsigned int port = 0;
> +
> +	return v4l2_async_register_fwnode_subdev(
> +		sd, sizeof(struct v4l2_async_subdev), &port, 1,
> +		tc358746_parse_endpoint);
> +
> +}
> +
> +static const char * const tc358764_test_pattern_menu[] = {
> +	"Disabled",
> +	"colorbar 80px",

One or more bars? How about "80 Pixel Color Bars"?

> +};
> +
> +static int tc358746_probe(struct i2c_client *client,
> +			  const struct i2c_device_id *id)
> +{
> +	struct tc358746_state *state;
> +	struct v4l2_subdev *sd;
> +	int err;
> +
> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
> +		return -EIO;
> +
> +	state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL);
> +	if (!state)
> +		return -ENOMEM;
> +
> +	state->i2c_client = client;
> +
> +	/* platform data */
> +	err = tc358746_probe_fw(state);
> +	if (err)
> +		return err;
> +
> +	err = tc358746_apply_fw(state);
> +	if (err)
> +		return err;
> +
> +	sd = &state->sd;
> +	v4l2_i2c_subdev_init(sd, client, &tc358746_ops);
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +
> +	/* i2c access */
> +	if (((i2c_rd16(sd, CHIPID) & CHIPID_CHIPID_MASK) >> 8) != 0x44) {
> +		v4l2_info(sd, "not a TC358746 on address 0x%x\n",
> +			  client->addr << 1);

clk_disable_unprepare(state->refclk);

Perhaps a new label?

> +		return -ENODEV;
> +	}
> +
> +	/* control handlers */
> +	v4l2_ctrl_handler_init(&state->hdl, 1);
> +
> +	v4l2_ctrl_new_std_menu_items(&state->hdl,
> +			&tc358764_ctrl_ops, V4L2_CID_TEST_PATTERN,
> +			ARRAY_SIZE(tc358764_test_pattern_menu) - 1, 0, 0,
> +			tc358764_test_pattern_menu);
> +
> +	state->link_freq =
> +		v4l2_ctrl_new_int_menu(&state->hdl, &tc358764_ctrl_ops,
> +				       V4L2_CID_LINK_FREQ,
> +				       state->link_frequencies_num - 1,
> +				       TC358746_DEF_LINK_FREQ,
> +				       state->link_frequencies);
> +
> +
> +	sd->ctrl_handler = &state->hdl;
> +	if (state->hdl.error) {
> +		err = state->hdl.error;
> +		goto err_hdl;
> +	}
> +
> +	state->pads[1].flags = MEDIA_PAD_FL_SOURCE;
> +	state->pads[0].flags = MEDIA_PAD_FL_SINK;
> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	sd->entity.ops = &tc358746_entity_ops;
> +	err = media_entity_pads_init(&sd->entity, 2, state->pads);
> +	if (err < 0)
> +		goto err_hdl;
> +
> +	mutex_init(&state->confctl_mutex);
> +
> +	state->fmt = tc358746_def_fmt;
> +
> +	/* apply default settings */
> +	tc358746_sreset(sd);
> +	tc358746_set_buffers(sd);
> +	tc358746_set_csi(sd);
> +	tc358746_set_csi_color_space(sd);
> +	tc358746_sleep_mode(sd, 1);
> +	tc358746_set_pll(sd);
> +	tc358746_enable_stream(sd, 0);
> +
> +	err = tc358746_async_register(sd);
> +	if (err < 0)
> +		goto err_hdl;
> +
> +	v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name,
> +		  client->addr << 1, client->adapter->name);

Please try to use either v4l2_*() or dev_*() macros to print debug
messages, but not both.

> +
> +	return 0;
> +
> +err_hdl:

mutex_destroy(&state->confctl_mutex);

You'll probably need one more label for that at least.

> +	media_entity_cleanup(&sd->entity);
> +	v4l2_ctrl_handler_free(&state->hdl);

An extra newline would be nice before return, as you have elsewhere.

> +	return err;
> +}
> +
> +static int tc358746_remove(struct i2c_client *client)
> +{
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct tc358746_state *state = to_state(sd);
> +
> +	v4l2_async_unregister_subdev(sd);
> +	v4l2_device_unregister_subdev(sd);
> +	mutex_destroy(&state->confctl_mutex);
> +	media_entity_cleanup(&sd->entity);
> +	v4l2_ctrl_handler_free(&state->hdl);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id tc358746_id[] = {
> +	{"tc358746", 0},
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, tc358746_id);
> +
> +static const struct of_device_id __maybe_unused tc358746_of_match[] = {
> +	{ .compatible = "toshiba,tc358746" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, tc358746_of_match);
> +
> +static struct i2c_driver tc358746_driver = {
> +	.driver = {
> +		.name = "tc358746",
> +		.of_match_table = of_match_ptr(tc358746_of_match),
> +	},
> +	.probe = tc358746_probe,
> +	.remove = tc358746_remove,
> +	.id_table = tc358746_id,

If you have no need for I²C IDs, then you could switch to probe_new and
ignore the i2c ID table.

> +};
> +
> +module_i2c_driver(tc358746_driver);
> diff --git a/drivers/media/i2c/tc358746_regs.h b/drivers/media/i2c/tc358746_regs.h
> new file mode 100644
> index 000000000000..9232d00d0e92
> --- /dev/null
> +++ b/drivers/media/i2c/tc358746_regs.h
> @@ -0,0 +1,208 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * tc358746 - Toshiba Parallel to CSI-2 bridge - register names and bit masks
> + *
> + * Convention:
> + * <REGISTER>
> + * <REGISTER>_<BITFIELD>_MASK
> + * <REGISTER>_<BITFIELD>_<VALUE>
> + * <REGISTER>_<BITFIELD>_SET(val = <REGISTER>_<BITFIELD>_<VALUE>)
> + *
> + * References:
> + * REF_01:
> + * - TC358746AXBG/TC358748XBG/TC358748IXBG Functional Specification Rev 1.2
> + */
> +
> +#ifndef __TC358746_REGS_H
> +#define __TC358746_REGS_H
> +
> +#define CHIPID			0x0000
> +#define CHIPID_CHIPID_MASK		GENMASK(15, 8)
> +#define CHIPID_REVID_MASK		GENMASK(7, 0)
> +
> +#define SYSCTL			0x0002
> +#define SYSCTL_SLEEP_MASK		BIT(1)
> +#define SYSCTL_SRESET_MASK		BIT(0)
> +
> +#define CONFCTL                 0x0004
> +#define CONFCTL_TRIEN_MASK		BIT(15)
> +#define CONFCTL_INTE2N_MASK		BIT(13)
> +#define CONFCTL_BT656EN_MASK		BIT(12)
> +#define CONFCTL_PDATAF_MASK		GENMASK(9, 8)
> +#define CONFCTL_PDATAF_SET(val)		(((val << 8) & CONFCTL_PDATAF_MASK))
> +#define CONFCTL_PDATAF_MODE0		0
> +#define CONFCTL_PDATAF_MODE1		1
> +#define CONFCTL_PDATAF_MODE2		2
> +#define CONFCTL_PPEN_MASK		BIT(6)
> +#define CONFCTL_VVALIDP_MASK		BIT(5)
> +#define CONFCTL_HVALIDP_MASK		BIT(4)
> +#define CONFCTL_PCLKP_MASK		BIT(3)
> +#define CONFCTL_AUTO_MASK		BIT(2)
> +#define CONFCTL_DATALANE_MASK		GENMASK(1, 0)
> +#define CONFCTL_DATALANE_1		0
> +#define CONFCTL_DATALANE_2		1
> +#define CONFCTL_DATALANE_3		2
> +#define CONFCTL_DATALANE_4		3
> +
> +#define FIFOCTL			0x0006
> +#define DATAFMT			0x0008
> +#define DATAFMT_PDFMT_RAW8		0
> +#define DATAFMT_PDFMT_RAW10		1
> +#define DATAFMT_PDFMT_RAW12		2
> +#define DATAFMT_PDFMT_RGB888		3
> +#define DATAFMT_PDFMT_RGB666		4
> +#define DATAFMT_PDFMT_RGB565		5
> +#define DATAFMT_PDFMT_YCBCRFMT_422_8_BIT 6
> +#define DATAFMT_PDFMT_RAW14		8
> +#define DATAFMT_PDFMT_YCBCRFMT_422_10_BIT 9
> +#define DATAFMT_PDFMT_YCBCRFMT_444	10
> +#define DATAFMT_PDFMT_MASK		GENMASK(7, 4)
> +#define DATAFMT_PDFMT_SET(val)		(((val) << 4) & DATAFMT_PDFMT_MASK)
> +#define DATAFMT_UDT_EN_MASK		BIT(0)
> +
> +#define MCLKCTL			0x000c
> +#define MCLKCTL_MCLK_HIGH_MASK		GENMASK(15, 8)
> +#define MCLKCTL_MCLK_HIGH_SET(val)	((((val) - 1) << 8) & MCLKCTL_MCLK_HIGH_MASK)
> +#define MCLKCTL_MCLK_LOW_MASK		GENMASK(7, 0)
> +#define MCLKCTL_MCLK_LOW_SET(val)	(((val) - 1) & MCLKCTL_MCLK_LOW_MASK)
> +
> +#define PLLCTL0			0x0016
> +#define PLLCTL0_PLL_PRD_MASK		GENMASK(15, 12)
> +#define PLLCTL0_PLL_PRD_SET(prd)	((((prd) - 1) << 12) & PLLCTL0_PLL_PRD_MASK)
> +#define PLLCTL0_PLL_FBD_MASK		GENMASK(8, 0)
> +#define PLLCTL0_PLL_FBD_SET(fbd)        (((fbd) - 1) & PLLCTL0_PLL_FBD_MASK)
> +
> +#define PLLCTL1                 0x0018
> +#define PLLCTL1_PLL_FRS_MASK            GENMASK(11, 10)
> +#define PLLCTL1_PLL_FRS_SET(frs)        (((frs) << 10) & PLLCTL1_PLL_FRS_MASK)
> +#define PLLCTL1_PLL_LBWS_MASK		GENMASK(9, 8)
> +#define PLLCTL1_LFBREN_MASK		BIT(6)
> +#define PLLCTL1_BYPCKEN_MASK		BIT(5)
> +#define PLLCTL1_CKEN_MASK		BIT(4)
> +#define PLLCTL1_RESETB_MASK		BIT(1)
> +#define PLLCTL1_PLL_EN_MASK		BIT(0)
> +
> +#define CLKCTL			0x0020
> +#define CLKCTL_MCLKDIV_MASK		GENMASK(3, 2)
> +#define CLKCTL_MCLKDIV_SET(val)		((val << 2) & CLKCTL_MCLKDIV_MASK)
> +#define CLKCTL_MCLKDIV_8		0
> +#define CLKCTL_MCLKDIV_4		1
> +#define CLKCTL_MCLKDIV_2		2
> +
> +#define WORDCNT			0x0022
> +#define PP_MISC                 0x0032
> +#define PP_MISC_FRMSTOP_MASK		BIT(15)
> +#define PP_MISC_RSTPTR_MASK		BIT(14)
> +
> +#define CSI2TX_DATA_TYPE	0x0050
> +#define MIPI_PHY_STATUS		0x0062
> +#define CSI2_ERROR_STATUS       0x0064
> +#define CSI2_ERR_EN             0x0066
> +#define CSI2_IDID_ERROR         0x006c
> +#define DBG_ACT_LINE_CNT        0x00e0
> +#define DBG_LINE_WIDTH		0x00e2
> +#define DBG_VERT_BLANK_LINE_CNT	0x00e4
> +#define DBG_VIDEO_DATA          0x00e8
> +#define FIFOSTATUS              0x00F8
> +
> +#define CLW_CNTRL               0x0140
> +#define CLW_CNTRL_CLW_LANEDISABLE_MASK	BIT(0)
> +
> +#define D0W_CNTRL               0x0144
> +#define D0W_CNTRL_D0W_LANEDISABLE_MASK	BIT(0)
> +
> +#define D1W_CNTRL		0x0148
> +#define D1W_CNTRL_D1W_LANEDISABLE_MASK	BIT(0)
> +
> +#define D2W_CNTRL		0x014C
> +#define D2W_CNTRL_D2W_LANEDISABLE_MASK	BIT(0)
> +
> +#define D3W_CNTRL		0x0150
> +#define D2W_CNTRL_D3W_LANEDISABLE_MASK	BIT(0)
> +
> +#define STARTCNTRL              0x0204
> +#define STARTCNTRL_START_MASK		BIT(0)
> +
> +#define LINEINITCNT		0x0210
> +#define LPTXTIMECNT		0x0214
> +#define TCLK_HEADERCNT		0x0218
> +#define	TCLK_HEADERCNT_TCLK_ZEROCNT_MASK	GENMASK(15, 8)
> +#define TCLK_HEADERCNT_TCLK_PREPARECNT_MASK	GENMASK(6, 0)
> +#define	TCLK_HEADERCNT_TCLK_ZEROCNT_SET(val)	((val << 8) & TCLK_HEADERCNT_TCLK_ZEROCNT_MASK)
> +#define	TCLK_HEADERCNT_TCLK_PREPARECNT_SET(val)	(val & TCLK_HEADERCNT_TCLK_PREPARECNT_MASK)
> +
> +#define TCLK_TRAILCNT		0x021C
> +#define THS_HEADERCNT		0x0220
> +#define	THS_HEADERCNT_THS_ZEROCNT_MASK		GENMASK(14, 8)
> +#define	THS_HEADERCNT_THS_PREPARECNT_MASK	GENMASK(6, 0)
> +#define	THS_HEADERCNT_THS_ZEROCNT_SET(val)	((val << 8) & THS_HEADERCNT_THS_ZEROCNT_MASK)
> +#define	THS_HEADERCNT_THS_PREPARECNT_SET(val)	(val & THS_HEADERCNT_THS_PREPARECNT_MASK)
> +
> +#define TWAKEUP			0x0224
> +#define TCLK_POSTCNT		0x0228
> +#define THS_TRAILCNT		0x022C
> +#define HSTXVREGCNT		0x0230
> +#define HSTXVREGEN              0x0234
> +#define HSTXVREGEN_D3M_HSTXVREGEN_MASK	BIT(4)
> +#define HSTXVREGEN_D2M_HSTXVREGEN_MASK  BIT(3)
> +#define HSTXVREGEN_D1M_HSTXVREGEN_MASK  BIT(2)
> +#define HSTXVREGEN_D0M_HSTXVREGEN_MASK  BIT(1)
> +#define HSTXVREGEN_CLM_HSTXVREGEN_MASK  BIT(0)
> +
> +#define TXOPTIONCNTRL           0x0238
> +#define TXOPTIONCNTRL_CONTCLKMODE_MASK	BIT(0)
> +
> +#define CSI_CONTROL             0x040C
> +#define CSI_CONTROL_CSI_MODE_MASK       BIT(15)
> +#define CSI_CONTROL_HTXTOEN_MASK        BIT(10)
> +#define CSI_CONTROL_TXHSMD_MASK         BIT(7)
> +#define CSI_CONTROL_NOL_MASK            GENMASK(2, 1)
> +#define CSI_CONTROL_NOL_1_MASK          0
> +#define CSI_CONTROL_NOL_2_MASK          BIT(1)
> +#define CSI_CONTROL_NOL_3_MASK          BIT(2)
> +#define CSI_CONTROL_NOL_4_MASK          (BIT(1) | BIT(2))
> +#define CSI_CONTROL_EOTDIS_MASK         BIT(0)
> +
> +#define CSI_STATUS              0x0410
> +#define CSI_STATUS_S_WSYNC_MASK		BIT(10)
> +#define CSI_STATUS_S_TXACT_MASK		BIT(9)
> +#define CSI_STATUS_S_HLT_MASK		BIT(0)
> +
> +#define CSI_INT			0x0414
> +#define CSI_INT_INTHLT_MASK		BIT(3)
> +#define CSI_INT_INTER_MASK		BIT(2)
> +
> +#define CSI_INT_ENA             0x0418
> +#define CSI_INT_ENA_IENHLT_MASK		BIT(3)
> +#define CSI_INT_ENA_IENER_MASK		BIT(2)
> +
> +#define CSI_ERR                 0x044C
> +#define CSI_ERR_INER_MASK               BIT(9)
> +#define CSI_ERR_WCER_MASK		BIT(8)
> +#define CSI_ERR_QUNK_MASK		BIT(4)
> +#define CSI_ERR_TXBRK_MASK		BIT(1)
> +
> +#define CSI_ERR_INTENA          0x0450
> +#define CSI_ERR_HALT            0x0454
> +#define CSI_CONFW               0x0500
> +#define CSI_CONFW_MODE_MASK			GENMASK(31, 29)
> +#define CSI_CONFW_MODE_SET_MASK			(BIT(31) | BIT(29))
> +#define CSI_CONFW_MODE_CLEAR_MASK		(BIT(31) | BIT(30))
> +#define CSI_CONFW_ADDRESS_MASK			GENMASK(28, 24)
> +#define CSI_CONFW_ADDRESS_CSI_CONTROL_MASK	(BIT(24) | BIT(25))
> +#define CSI_CONFW_ADDRESS_CSI_INT_ENA_MASK	(BIT(25) | BIT(26))
> +#define CSI_CONFW_ADDRESS_CSI_ERR_INTENA_MASK	(BIT(28) | BIT(26))
> +#define CSI_CONFW_ADDRESS_CSI_ERR_HALT_MASK	(BIT(28) | BIT(26) | BIT(24))
> +#define CSI_CONFW_DATA_MASK			GENMASK(15, 0)
> +
> +#define CSIRESET                0x0504
> +#define CSIRESET_RESET_CNF_MASK		BIT(1)
> +#define CSIRESET_RESET_MODULE_MASK	BIT(0)
> +
> +#define CSI_INT_CLR             0x050C
> +#define CSI_INT_CLR_ICRER_MASK		BIT(2)
> +
> +#define CSI_START               0x0518
> +#define CSI_START_STRT_MASK		BIT(0)
> +
> +#endif
diff mbox series

Patch

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 4c936e129500..9995075c3eac 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -394,6 +394,18 @@  config VIDEO_TC358743_CEC
 	  When selected the tc358743 will support the optional
 	  HDMI CEC feature.
 
+config VIDEO_TC358746
+	tristate "Toshiba TC358746 decoder"
+	depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API && MEDIA_CONTROLLER
+	select V4L2_FWNODE
+	help
+	  Support for the Toshiba TC358746 PARALLEL to MIPI CSI-2 bridge.
+	  The bridge can work in both directions but currenty only the
+	  parallel-in / csi-out path is supported.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called tc358746.
+
 config VIDEO_TVP514X
 	tristate "Texas Instruments TVP514x video decoder"
 	depends on VIDEO_V4L2 && I2C
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 65fae7732de0..5cdbdf546627 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -106,6 +106,7 @@  obj-$(CONFIG_VIDEO_I2C)		+= video-i2c.o
 obj-$(CONFIG_VIDEO_ML86V7667)	+= ml86v7667.o
 obj-$(CONFIG_VIDEO_OV2659)	+= ov2659.o
 obj-$(CONFIG_VIDEO_TC358743)	+= tc358743.o
+obj-$(CONFIG_VIDEO_TC358746)	+= tc358746.o
 obj-$(CONFIG_VIDEO_IMX214)	+= imx214.o
 obj-$(CONFIG_VIDEO_IMX258)	+= imx258.o
 obj-$(CONFIG_VIDEO_IMX274)	+= imx274.o
diff --git a/drivers/media/i2c/tc358746.c b/drivers/media/i2c/tc358746.c
new file mode 100644
index 000000000000..633b0322b85d
--- /dev/null
+++ b/drivers/media/i2c/tc358746.c
@@ -0,0 +1,1847 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tc358746 - Parallel to CSI-2 bridge
+ *
+ * Copyright 2018 Marco Felsch <kernel@pengutronix.de>
+ *
+ * References:
+ * REF_01:
+ * - TC358746AXBG/TC358748XBG/TC358748IXBG Functional Specification Rev 1.2
+ * REF_02:
+ * - TC358746(A)748XBG_Parallel-CSI2_Tv23p.xlsx, Rev Tv23
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/clk-provider.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/property.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fwnode.h>
+
+#include "tc358746_regs.h"
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "debug level (0-3)");
+
+MODULE_DESCRIPTION("Toshiba TC358746 Parallel to CSI-2 bridge driver");
+MODULE_AUTHOR("Marco Felsch <kernel@pengutronix.de>");
+MODULE_LICENSE("GPL");
+
+#define I2C_MAX_XFER_SIZE	(512 + 2)
+#define TC358746_MAX_FIFO_SIZE	512
+#define TC358746_DEF_LINK_FREQ	0
+
+#define TC358746_LINEINIT_MIN_US	110
+#define TC358746_TWAKEUP_MIN_US		1200
+#define TC358746_LPTXTIME_MIN_NS	55
+#define TC358746_TCLKZERO_MIN_NS	305
+#define TC358746_TCLKTRAIL_MIN_NS	65
+#define TC358746_TCLKPOST_MIN_NS	65
+#define TC358746_THSZERO_MIN_NS		150
+#define TC358746_THSTRAIL_MIN_NS	65
+#define TC358746_THSPREPARE_MIN_NS	45
+
+static const struct v4l2_mbus_framefmt tc358746_def_fmt = {
+	.width		= 640,
+	.height		= 480,
+	.code		= MEDIA_BUS_FMT_UYVY8_2X8,
+	.field		= V4L2_FIELD_NONE,
+	.colorspace	= V4L2_COLORSPACE_DEFAULT,
+	.ycbcr_enc	= V4L2_YCBCR_ENC_DEFAULT,
+	.quantization	= V4L2_QUANTIZATION_DEFAULT,
+	.xfer_func	= V4L2_XFER_FUNC_DEFAULT,
+};
+
+struct tc358746_csi_param {
+	unsigned char speed_range;
+	unsigned int  unit_clk_hz;
+	unsigned char unit_clk_mul;
+	unsigned int speed_per_lane; /* bps / lane */
+	unsigned short lane_num;
+	bool is_continuous_clk;
+
+	/* CSI2-TX Parameters */
+	u32 lineinitcnt;
+	u32 lptxtimecnt;
+	u32 twakeupcnt;
+	u32 tclk_preparecnt;
+	u32 tclk_zerocnt;
+	u32 tclk_trailcnt;
+	u32 tclk_postcnt;
+	u32 ths_preparecnt;
+	u32 ths_zerocnt;
+	u32 ths_trailcnt;
+
+	unsigned int csi_hs_lp_hs_ps;
+};
+
+struct tc358746_state {
+	struct v4l2_subdev sd;
+	struct i2c_client *i2c_client;
+	struct gpio_desc *reset_gpio;
+
+	/*
+	 * Generic
+	 */
+	struct media_pad pads[2];
+	struct mutex confctl_mutex;
+	struct v4l2_mbus_framefmt fmt;
+	struct v4l2_ctrl_handler hdl;
+	bool fmt_changed;
+	bool test;
+
+	/*
+	 * Chip Clocks
+	 */
+	struct clk  *refclk;
+	/* internal pll */
+	unsigned int pllinclk_hz;
+	u16 pll_prd;
+	u16 pll_fbd;
+
+	/*
+	 * Video Buffer
+	 */
+	u16 vb_fifo; /* The FIFO size is 511x32 */
+
+	/*
+	 * CSI TX
+	 */
+	struct v4l2_ctrl	  *link_freq;
+	struct tc358746_csi_param *link_freq_settings;
+	u64			  *link_frequencies;
+	unsigned int		   link_frequencies_num;
+
+	/*
+	 * Parallel input
+	 */
+	unsigned int pclk;
+	unsigned int hblank;
+};
+
+struct tc358746_mbus_fmt {
+	u32 code;
+	u8 bus_width;
+	u8 bpp;		 /* total bpp */
+	u8 pdformat;	 /* peripheral data format */
+	u8 pdataf;	 /* parallel data format option */
+	u8 ppp;		 /* pclk per pixel */
+	bool csitx_only; /* format only in csi-tx mode supported */
+};
+
+/* TODO: Add other formats as required */
+static const struct tc358746_mbus_fmt tc358746_formats[] = {
+	{
+		.code = MEDIA_BUS_FMT_UYVY8_2X8,
+		.bus_width = 8,
+		.bpp = 16,
+		.pdformat = DATAFMT_PDFMT_YCBCRFMT_422_8_BIT,
+		.pdataf = CONFCTL_PDATAF_MODE0,
+		.ppp = 2,
+	}, {
+		.code = MEDIA_BUS_FMT_UYVY8_1X16,
+		.bus_width = 16,
+		.bpp = 16,
+		.pdformat = DATAFMT_PDFMT_YCBCRFMT_422_8_BIT,
+		.pdataf = CONFCTL_PDATAF_MODE1,
+		.ppp = 1,
+	}, {
+		.code = MEDIA_BUS_FMT_YUYV8_1X16,
+		.bus_width = 16,
+		.bpp = 16,
+		.pdformat = DATAFMT_PDFMT_YCBCRFMT_422_8_BIT,
+		.pdataf = CONFCTL_PDATAF_MODE2,
+		.ppp = 1,
+	}, {
+		.code = MEDIA_BUS_FMT_UYVY10_2X10,
+		.bus_width = 10,
+		.bpp = 20,
+		.pdformat = DATAFMT_PDFMT_YCBCRFMT_422_10_BIT,
+		.pdataf = CONFCTL_PDATAF_MODE0, /* don't care */
+		.ppp = 2,
+	}, {
+		/* in datasheet listed as YUV444 */
+		.code = MEDIA_BUS_FMT_GBR888_1X24,
+		.bus_width = 24,
+		.bpp = 24,
+		.pdformat = DATAFMT_PDFMT_YCBCRFMT_444,
+		.pdataf = CONFCTL_PDATAF_MODE0, /* don't care */
+		.ppp = 2,
+		.csitx_only = true,
+	},
+};
+
+/* --------------- HELPERS ------------ */
+static void
+tc358746_dump_csi(struct device *dev,
+		  struct tc358746_csi_param *csi_setting)
+{
+	dev_dbg(dev, "Speed-Range value %u\n", csi_setting->speed_range);
+	dev_dbg(dev, "Unit Clock %u Hz\n", csi_setting->unit_clk_hz);
+	dev_dbg(dev, "Unit Clock Mul %u\n", csi_setting->unit_clk_mul);
+	dev_dbg(dev, "CSI speed/lane %u bps/lane\n",
+		csi_setting->speed_per_lane);
+	dev_dbg(dev, "CSI lanes %u\n", csi_setting->lane_num);
+	dev_dbg(dev, "CSI clock during LP %sabled\n",
+		csi_setting->is_continuous_clk ? "en" : "dis");
+
+	dev_dbg(dev, "lineinitcnt %u\n", csi_setting->lineinitcnt);
+	dev_dbg(dev, "lptxtimecnt %u\n", csi_setting->lptxtimecnt);
+	dev_dbg(dev, "tclk_preparecnt %u\n", csi_setting->tclk_preparecnt);
+	dev_dbg(dev, "tclk_zerocnt %u\n", csi_setting->tclk_zerocnt);
+	dev_dbg(dev, "tclk_trailcnt %u\n", csi_setting->tclk_trailcnt);
+	dev_dbg(dev, "ths_preparecnt %u\n", csi_setting->ths_preparecnt);
+	dev_dbg(dev, "ths_zerocnt %u\n", csi_setting->ths_zerocnt);
+	dev_dbg(dev, "twakeupcnt %u\n", csi_setting->twakeupcnt);
+	dev_dbg(dev, "tclk_postcnt %u\n", csi_setting->tclk_postcnt);
+	dev_dbg(dev, "ths_trailcnt %u\n", csi_setting->ths_trailcnt);
+	dev_dbg(dev, "csi_hs_lp_hs_ps %u (%u us)\n",
+		csi_setting->csi_hs_lp_hs_ps,
+		csi_setting->csi_hs_lp_hs_ps / 1000);
+}
+
+static void
+tc358746_dump_pll(struct device *dev, struct tc358746_state *state)
+{
+	dev_dbg(dev, "refclk %lu Hz\n", clk_get_rate(state->refclk));
+	dev_dbg(dev, "pll input clock %u Hz\n", state->pllinclk_hz);
+	dev_dbg(dev, "PLL_PRD %u\n", state->pll_prd - 1);
+	dev_dbg(dev, "PLL_FBD %u\n", state->pll_fbd - 1);
+}
+
+static inline struct tc358746_state *to_state(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct tc358746_state, sd);
+}
+
+/* Find a data format by a pixel code */
+static int tc358746_format_supported(u32 code)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(tc358746_formats); i++)
+		if (tc358746_formats[i].code == code)
+			return 0;
+
+	return -EINVAL;
+}
+
+static struct tc358746_csi_param *
+tc358746_g_cur_csi_settings(struct tc358746_state *state)
+{
+	int cur_freq = v4l2_ctrl_g_ctrl(state->link_freq);
+
+	return &state->link_freq_settings[cur_freq];
+}
+
+static const struct tc358746_mbus_fmt *tc358746_get_format(u32 code)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(tc358746_formats); i++)
+		if (tc358746_formats[i].code == code)
+			return &tc358746_formats[i];
+
+	return NULL;
+}
+
+static int
+tc358746_adjust_fifo_size(struct tc358746_state *state,
+			  const struct tc358746_mbus_fmt *format,
+			  struct tc358746_csi_param *csi_settings,
+			  int width, u16 *fifo_size)
+{
+	struct device *dev = &state->i2c_client->dev;
+	int c_hactive_ps_diff, c_lp_active_ps_diff, c_fifo_delay_ps_diff;
+	unsigned int p_hactive_ps, p_hblank_ps, p_htotal_ps;
+	unsigned int c_hactive_ps, c_lp_active_ps, c_fifo_delay_ps;
+	unsigned int csi_bps, csi_bps_period_ps;
+	unsigned int csi_hsclk, csi_hsclk_period_ps;
+	unsigned int pclk_period_ps;
+	unsigned int _fifo_size;
+
+	pclk_period_ps = 1000000000 / (state->pclk / 1000);
+	csi_bps = csi_settings->speed_per_lane * csi_settings->lane_num;
+	csi_bps_period_ps = 1000000000 / (csi_bps / 1000);
+	csi_hsclk = csi_settings->speed_per_lane >> 3;
+	csi_hsclk_period_ps = 1000000000 / (csi_hsclk / 1000);
+
+	/*
+	 * Calculation:
+	 * p_hactive_ps = pclk_period_ps * pclk_per_pixel * h_active_pixel
+	 */
+	p_hactive_ps = pclk_period_ps * format->ppp * width;
+
+	/*
+	 * Calculation:
+	 * p_hblank_ps = pclk_period_ps * h_blank_pixel
+	 */
+	p_hblank_ps = pclk_period_ps * state->hblank;
+	p_htotal_ps = p_hblank_ps + p_hactive_ps;
+
+	/*
+	 * Adjust the fifo size to adjust the csi timing. Hopefully we can find
+	 * a fifo size where the parallel input timings and the csi tx timings
+	 * fit together.
+	 */
+	for (_fifo_size = 1; _fifo_size < TC358746_MAX_FIFO_SIZE;
+	     _fifo_size++) {
+		/*
+		 * Calculation:
+		 * c_fifo_delay_ps = (fifo_size * 32) / parallel_bus_width *
+		 *		     pclk_period_ps + 4 * csi_hsclk_period_ps
+		 */
+		c_fifo_delay_ps = _fifo_size * 32 * pclk_period_ps;
+		c_fifo_delay_ps /= format->bus_width;
+		c_fifo_delay_ps += 4 * csi_hsclk_period_ps;
+
+		/*
+		 * Calculation:
+		 * c_hactive_ps = csi_bps_period_ps * image_bpp * h_active_pixel
+		 *		  + c_fifo_delay
+		 */
+		c_hactive_ps = csi_bps_period_ps * format->bpp * width;
+		c_hactive_ps += c_fifo_delay_ps;
+
+		/*
+		 * Calculation:
+		 * c_lp_active_ps = p_htotal_ps - c_hactive_ps
+		 */
+		c_lp_active_ps = p_htotal_ps - c_hactive_ps;
+
+		c_hactive_ps_diff = c_hactive_ps - p_hactive_ps;
+		c_fifo_delay_ps_diff = p_htotal_ps - c_hactive_ps;
+		c_lp_active_ps_diff =
+			c_lp_active_ps - csi_settings->csi_hs_lp_hs_ps;
+
+		if (c_hactive_ps_diff > 0 &&
+		    c_fifo_delay_ps_diff > 0 &&
+		    c_lp_active_ps_diff > 0)
+			break;
+	}
+	/*
+	 * If we can't transfer the image using this csi link frequency try to
+	 * use another link freq.
+	 */
+
+	dev_dbg(dev, "%s: found fifo-size %u\n", __func__, _fifo_size);
+	*fifo_size = _fifo_size;
+	return _fifo_size == TC358746_MAX_FIFO_SIZE ? -EINVAL : 0;
+}
+
+static int
+tc358746_adjust_timings(struct tc358746_state *state,
+			const struct tc358746_mbus_fmt *format,
+			int *width, u16 *fifo_size)
+{
+
+	int cur_freq = v4l2_ctrl_g_ctrl(state->link_freq);
+	int freq = cur_freq;
+	struct tc358746_csi_param *csi_lane_setting;
+	int err;
+	int _width;
+
+	/*
+	 * Adjust timing:
+	 * 1) Try to use the desired width and the current csi-link-frequency
+	 * 2) If this doesn't fit try other csi-link-frequencies
+	 * 3) If this doesn't fit too, reducing the desired width and test
+	 *    it again width the current csi-link-frequency
+	 * 4) Goto step 2 if it doesn't fit at all
+	 */
+	for (_width = *width; _width > 0; _width -= 10) {
+		csi_lane_setting = &state->link_freq_settings[cur_freq];
+		err = tc358746_adjust_fifo_size(state, format, csi_lane_setting,
+						_width, fifo_size);
+		if (!err)
+			goto out;
+
+		for (freq = 0; freq < state->link_frequencies_num; freq++) {
+			if (freq == cur_freq)
+				continue;
+
+			csi_lane_setting = &state->link_freq_settings[freq];
+			err = tc358746_adjust_fifo_size(state, format,
+							csi_lane_setting,
+							_width, fifo_size);
+			if (!err)
+				goto out;
+		}
+	}
+
+out:
+	*width = _width;
+	return freq;
+}
+
+static int
+tc358746_calculate_csi_txtimings(struct tc358746_state *state,
+				 struct tc358746_csi_param *csi_setting)
+{
+	struct device *dev = &state->i2c_client->dev;
+	unsigned int spl;
+	unsigned int spl_p_ps, hsclk_p_ps, hfclk_p_ns;
+	unsigned int hfclk, hsclk; /* SYSCLK */
+	unsigned int tmp;
+	unsigned int lptxtime_ps, tclk_post_ps, tclk_trail_ps, tclk_zero_ps,
+		     ths_trail_ps, ths_zero_ps;
+
+	spl = csi_setting->speed_per_lane;
+	hsclk = spl >> 3; /* spl in bit-per-second, hsclk in byte-per-sercond */
+	hfclk = hsclk >> 1; /* HFCLK = SYSCLK / 2 */
+
+	if (hsclk > 125000000U) {
+		dev_err(dev, "unsupported HS byte clock %d, must <= 125 MHz\n",
+			hsclk);
+		return -EINVAL;
+	}
+
+	hfclk_p_ns = DIV_ROUND_CLOSEST(1000000000, hfclk);
+	hsclk_p_ps = 1000000000 / (hsclk / 1000);
+	spl_p_ps   = 1000000000 / (spl / 1000);
+
+	/*
+	 * Calculation:
+	 * hfclk_p_ns * lineinitcnt > 100us
+	 * lineinitcnt > 100 * 10^-6s / hfclk_p_ns * 10^-9
+	 *
+	 */
+	csi_setting->lineinitcnt = DIV_ROUND_UP(TC358746_LINEINIT_MIN_US * 1000,
+					  hfclk_p_ns);
+
+	/*
+	 * Calculation:
+	 * (lptxtimecnt + 1) * hsclk_p_ps > 50ns
+	 * 38ns < (tclk_preparecnt + 1) * hsclk_p_ps < 95ns
+	 */
+	csi_setting->lptxtimecnt = csi_setting->tclk_preparecnt =
+		DIV_ROUND_UP(TC358746_LPTXTIME_MIN_NS * 1000, hsclk_p_ps) - 1;
+
+	/*
+	 * Limit:
+	 * (tclk_zero + tclk_prepar) period > 300ns.
+	 * Since we have no upper limit and for simplicity:
+	 * tclk_zero > 300ns.
+	 *
+	 * Calculation:
+	 * tclk_zero = ([2,3] + tclk_zerocnt) * hsclk_p_ps + ([2,3] * spl_p_ps)
+	 *
+	 * Note: REF_02 uses
+	 * tclk_zero = (2.5 + tclk_zerocnt) * hsclk_p_ps + (3.5 * spl_p_ps)
+	 */
+	tmp = TC358746_TCLKZERO_MIN_NS * 1000 - 3 * spl_p_ps;
+	tmp = DIV_ROUND_UP(tmp, hsclk_p_ps);
+	csi_setting->tclk_zerocnt = tmp - 2;
+
+	/*
+	 * Limit:
+	 * 40ns + 4 * spl_p_ps < (ths_preparecnt + 1) * hsclk_p_ps
+	 *		       < 85ns + 6 * spl_p_ps
+	 */
+	tmp = TC358746_THSPREPARE_MIN_NS * 1000 + 4 * spl_p_ps;
+	tmp = DIV_ROUND_UP(tmp, hsclk_p_ps);
+	csi_setting->ths_preparecnt = tmp - 1;
+
+	/*
+	 * Limit:
+	 * (ths_zero + ths_prepare) period > 145ns + 10 * spl_p_ps.
+	 * Since we have no upper limit and for simplicity:
+	 * ths_zero period > 145ns + 10 * spl_p_ps.
+	 *
+	 * Calculation:
+	 * ths_zero = ([6,8] + ths_zerocnt) * hsclk_p_ps + [3,4] * hsclk_p_ps +
+	 *	      [13,14] * spl_p_ps
+	 *
+	 * Note: REF_02 uses
+	 * ths_zero = (7 + ths_zerocnt) * hsclk_p_ps + 4 * hsclk_p_ps +
+	 *	      11 * spl_p_ps
+	 */
+	tmp = TC358746_THSZERO_MIN_NS * 1000 - spl_p_ps;
+	tmp = DIV_ROUND_UP(tmp, hsclk_p_ps);
+	csi_setting->ths_zerocnt = tmp < 11 ? 0 : tmp - 11;
+
+	/*
+	 * Limit:
+	 * hsclk_p_ps * (lptxtimecnt + 1) * (twakeupcnt + 1) > 1ms
+	 *
+	 * Since we have no upper limit use 1.2ms as lower limit to
+	 * surley meet the spec limit.
+	 */
+	tmp = hsclk_p_ps / 1000; /* tmp = hsclk_p_ns */
+	csi_setting->twakeupcnt =
+		DIV_ROUND_UP(TC358746_TWAKEUP_MIN_US * 1000,
+			     tmp * (csi_setting->lptxtimecnt + 1)) - 1;
+
+	/*
+	 * Limit:
+	 * 60ns + 4 * spl_p_ps < thstrail < 105ns + 12 * spl_p_ps
+	 *
+	 * Calculation:
+	 * thstrail = (1 + ths_trailcnt) * hsclk_p_ps + [3,4] * hsclk_p_ps -
+	 *	      [13,14] * spl_p_ps
+	 *
+	 * [2] set formula to:
+	 * thstrail = (1 + ths_trailcnt) * hsclk_p_ps + 4 * hsclk_p_ps -
+	 *	      11 * spl_p_ps
+	 */
+	tmp = TC358746_THSTRAIL_MIN_NS * 1000 + 15 * spl_p_ps;
+	tmp = DIV_ROUND_UP(tmp, hsclk_p_ps);
+	csi_setting->ths_trailcnt = tmp - 5;
+
+	/*
+	 * Limit:
+	 * 60ns < tclk_trail < 105ns + 12 * spl_p_ps
+	 *
+	 * Limit used by REF_02:
+	 * 60ns < tclk_trail < 105ns + 12 * spl_p_ps - 30
+	 *
+	 * Calculation:
+	 * tclk_trail = ([1,2] + tclk_trailcnt) * hsclk_p_ps +
+	 *		(2 + [1,2]) * hsclk_p_ps - [2,3] * spl_p_ps
+	 *
+	 * Calculation used by REF_02:
+	 * tclk_trail = (1 + tclk_trailcnt) * hsclk_p_ps +
+	 *		4 * hsclk_p_ps - 3 * spl_p_ps
+	 */
+	tmp = TC358746_TCLKTRAIL_MIN_NS * 1000 + 3 * spl_p_ps;
+	tmp = DIV_ROUND_UP(tmp, hsclk_p_ps);
+	csi_setting->tclk_trailcnt = tmp < 5 ? 0 : tmp - 5;
+
+	/*
+	 * Limit:
+	 * tclk_post > 60ns + 52 * spl_p_ps
+	 *
+	 * Limit used by REF_02:
+	 * tclk_post > 60ns + 52 * spl_p_ps
+	 *
+	 * Calculation:
+	 * tclk_post = ([1,2] + (tclk_postcnt + 1)) * hsclk_p_ps + hsclk_p_ps
+	 *
+	 * Note REF_02 uses:
+	 * tclk_post = (2.5 + tclk_postcnt) * hsclk_p_ps + hsclk_p_ps +
+	 *		2.5 * spl_p_ps
+	 * To meet the REF_02 validation limits following equation is used:
+	 * tclk_post = (2 + tclk_postcnt) * hsclk_p_ps + hsclk_p_ps +
+	 *		3 * spl_p_ps
+	 */
+	tmp = TC358746_TCLKPOST_MIN_NS * 1000 + 49 * spl_p_ps;
+	tmp = DIV_ROUND_UP(tmp, hsclk_p_ps);
+	csi_setting->tclk_postcnt = tmp - 3;
+
+	/*
+	 * Last calculate the csi hs->lp->hs transistion time in ns. Note REF_02
+	 * mixed units in the equation for the continuous case. I don't know if
+	 * this was the intention. The driver drops the last 'multiply all by
+	 * two' to get nearly the same results.
+	 */
+	lptxtime_ps = (csi_setting->lptxtimecnt + 1) * hsclk_p_ps;
+	tclk_post_ps =
+		(4 + csi_setting->tclk_postcnt) * hsclk_p_ps + 3 * spl_p_ps;
+	tclk_trail_ps =
+		(5 + csi_setting->tclk_trailcnt) * hsclk_p_ps - 3 * spl_p_ps;
+	tclk_zero_ps =
+		(2 + csi_setting->tclk_zerocnt) * hsclk_p_ps + 3 * spl_p_ps;
+	ths_trail_ps =
+		(5 + csi_setting->ths_trailcnt) * hsclk_p_ps - 11 * spl_p_ps;
+	ths_zero_ps =
+		(7 + csi_setting->ths_zerocnt) * hsclk_p_ps + 4 * hsclk_p_ps +
+		11 * spl_p_ps;
+
+	if (csi_setting->is_continuous_clk) {
+		tmp = 2 * lptxtime_ps;
+		tmp += 25 * hsclk_p_ps;
+		tmp += ths_trail_ps;
+		tmp += ths_zero_ps;
+	} else {
+		tmp = 4 * lptxtime_ps;
+		tmp += ths_trail_ps + tclk_post_ps + tclk_trail_ps +
+			tclk_zero_ps + ths_zero_ps;
+		tmp += (13 + csi_setting->lptxtimecnt * 8) * hsclk_p_ps;
+		tmp += 22 * hsclk_p_ps;
+		tmp *= 3;
+		tmp = DIV_ROUND_CLOSEST(tmp, 2);
+	}
+	csi_setting->csi_hs_lp_hs_ps = tmp;
+
+	return 0;
+}
+
+/* --------------- i2c helper ------------ */
+
+static void i2c_rd(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n)
+{
+	struct tc358746_state *state = to_state(sd);
+	struct i2c_client *client = state->i2c_client;
+	int err;
+	u8 buf[2] = { reg >> 8, reg & 0xff };
+	u8 data[I2C_MAX_XFER_SIZE];
+
+	struct i2c_msg msgs[] = {
+		{
+			.addr = client->addr,
+			.flags = 0,
+			.len = 2,
+			.buf = buf,
+		},
+		{
+			.addr = client->addr,
+			.flags = I2C_M_RD,
+			.len = n,
+			.buf = data,
+		},
+	};
+
+	err = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (err != ARRAY_SIZE(msgs)) {
+		v4l2_err(sd, "%s: reading register 0x%x from 0x%x failed\n",
+			 __func__, reg, client->addr);
+	}
+
+	switch (n) {
+	case 1:
+		values[0] = data[0];
+		break;
+	case 2:
+		values[0] = data[1];
+		values[1] = data[0];
+		break;
+	case 4:
+		values[0] = data[1];
+		values[1] = data[0];
+		values[2] = data[3];
+		values[3] = data[2];
+		break;
+	default:
+		v4l2_info(sd, "unsupported I2C read %d bytes from address 0x%04x\n",
+			  n, reg);
+	}
+
+	if (debug < 3)
+		return;
+
+	switch (n) {
+	case 1:
+		v4l2_info(sd, "I2C read 0x%04x = 0x%02x",
+			  reg, data[0]);
+		break;
+	case 2:
+		v4l2_info(sd, "I2C read 0x%04x = 0x%02x%02x",
+			  reg, data[0], data[1]);
+		break;
+	case 4:
+		v4l2_info(sd, "I2C read 0x%04x = 0x%02x%02x%02x%02x",
+			  reg, data[2], data[3], data[0], data[1]);
+		break;
+	default:
+		v4l2_info(sd, "I2C unsupported read %d bytes from address 0x%04x\n",
+			  n, reg);
+	}
+}
+
+static void i2c_wr(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n)
+{
+	struct tc358746_state *state = to_state(sd);
+	struct i2c_client *client = state->i2c_client;
+	int err;
+	struct i2c_msg msg;
+	u8 data[I2C_MAX_XFER_SIZE];
+
+	if ((2 + n) > I2C_MAX_XFER_SIZE) {
+		n = I2C_MAX_XFER_SIZE - 2;
+		v4l2_warn(sd, "i2c wr reg=%04x: len=%d is too big!\n",
+			  reg, 2 + n);
+	}
+
+	msg.addr = client->addr;
+	msg.buf = data;
+	msg.len = 2 + n;
+	msg.flags = 0;
+
+	data[0] = reg >> 8;
+	data[1] = reg & 0xff;
+
+	switch (n) {
+	case 1:
+		data[2 + 0] = values[0];
+		break;
+	case 2:
+		data[2 + 0] = values[1];
+		data[2 + 1] = values[0];
+		break;
+	case 4:
+		data[2 + 0] = values[1];
+		data[2 + 1] = values[0];
+		data[2 + 2] = values[3];
+		data[2 + 3] = values[2];
+		break;
+	default:
+		v4l2_info(sd, "unsupported I2C write %d bytes from address 0x%04x\n",
+			  n, reg);
+	}
+
+	err = i2c_transfer(client->adapter, &msg, 1);
+	if (err != 1) {
+		v4l2_err(sd, "%s: writing register 0x%x from 0x%x failed\n",
+			 __func__, reg, client->addr);
+		return;
+	}
+
+	if (debug < 3)
+		return;
+
+	switch (n) {
+	case 1:
+		v4l2_info(sd, "I2C write 0x%04x = 0x%02x", reg, data[2 + 0]);
+		break;
+	case 2:
+		v4l2_info(sd, "I2C write 0x%04x = 0x%02x%02x", reg, data[2 + 0],
+			  data[2 + 1]);
+		break;
+	case 4:
+		v4l2_info(sd, "I2C write 0x%04x = 0x%02x%02x%02x%02x", reg,
+			  data[2 + 2], data[2 + 3], data[2 + 0], data[2 + 1]);
+		break;
+	default:
+		v4l2_info(sd, "I2C unsupported write %d bytes from address 0x%04x\n",
+			  n, reg);
+	}
+}
+
+static noinline u32 i2c_rdreg(struct v4l2_subdev *sd, u16 reg, u32 n)
+{
+	__le32 val = 0;
+
+	i2c_rd(sd, reg, (u8 __force *)&val, n);
+
+	return le32_to_cpu(val);
+}
+
+static noinline void i2c_wrreg(struct v4l2_subdev *sd, u16 reg, u32 val, u32 n)
+{
+	__le32 raw = cpu_to_le32(val);
+
+	i2c_wr(sd, reg, (u8 __force *)&raw, n);
+}
+
+static u16 __maybe_unused i2c_rd8(struct v4l2_subdev *sd, u16 reg)
+{
+	return i2c_rdreg(sd, reg, 1);
+}
+
+static u16 __maybe_unused i2c_rd16(struct v4l2_subdev *sd, u16 reg)
+{
+	return i2c_rdreg(sd, reg, 2);
+}
+
+static u32 __maybe_unused i2c_rd32(struct v4l2_subdev *sd, u16 reg)
+{
+	return i2c_rdreg(sd, reg, 4);
+}
+
+static void __maybe_unused i2c_wr8(struct v4l2_subdev *sd, u16 reg, u16 val)
+{
+	i2c_wrreg(sd, reg, val, 1);
+}
+
+static void i2c_wr16(struct v4l2_subdev *sd, u16 reg, u16 val)
+{
+	i2c_wrreg(sd, reg, val, 2);
+}
+
+static void i2c_wr16_and_or(struct v4l2_subdev *sd, u16 reg, u32 mask, u16 val)
+{
+	u16 m = (u16) mask;
+
+	i2c_wrreg(sd, reg, (i2c_rd16(sd, reg) & m) | val, 2);
+}
+
+static void i2c_wr32(struct v4l2_subdev *sd, u16 reg, u32 val)
+{
+	i2c_wrreg(sd, reg, val, 4);
+}
+
+/* --------------- init --------------- */
+
+static void
+tc358746_wr_csi_control(struct v4l2_subdev *sd, int val)
+{
+	struct tc358746_state *state = to_state(sd);
+	u32 _val;
+
+	val &= CSI_CONFW_DATA_MASK;
+	_val = CSI_CONFW_MODE_SET_MASK | CSI_CONFW_ADDRESS_CSI_CONTROL_MASK |
+		val;
+
+	dev_dbg(&state->i2c_client->dev, "CSI_CONFW 0x%04x\n", _val);
+	i2c_wr32(sd, CSI_CONFW, _val);
+}
+
+static inline void tc358746_sleep_mode(struct v4l2_subdev *sd, int enable)
+{
+	i2c_wr16_and_or(sd, SYSCTL, ~SYSCTL_SLEEP_MASK,
+			enable ? SYSCTL_SLEEP_MASK : 0);
+}
+
+static inline void tc358746_sreset(struct v4l2_subdev *sd)
+{
+	i2c_wr16(sd, SYSCTL, SYSCTL_SRESET_MASK);
+	udelay(10);
+	i2c_wr16(sd, SYSCTL, 0);
+}
+
+static inline void tc358746_enable_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct tc358746_state *state = to_state(sd);
+
+	dev_dbg(&state->i2c_client->dev, "%sable\n", enable ? "en" : "dis");
+
+	mutex_lock(&state->confctl_mutex);
+	if (!enable) {
+		i2c_wr16_and_or(sd, PP_MISC, ~PP_MISC_FRMSTOP_MASK,
+				PP_MISC_FRMSTOP_MASK);
+		i2c_wr16_and_or(sd, CONFCTL, ~CONFCTL_PPEN_MASK, 0);
+		i2c_wr16_and_or(sd, PP_MISC, ~PP_MISC_RSTPTR_MASK,
+				PP_MISC_RSTPTR_MASK);
+
+		i2c_wr32(sd, CSIRESET, (CSIRESET_RESET_CNF_MASK |
+					CSIRESET_RESET_MODULE_MASK));
+		i2c_wr16(sd, DBG_ACT_LINE_CNT, 0);
+	} else {
+		i2c_wr16(sd, PP_MISC, 0);
+		i2c_wr16_and_or(sd, CONFCTL, ~CONFCTL_PPEN_MASK,
+				CONFCTL_PPEN_MASK);
+	}
+	mutex_unlock(&state->confctl_mutex);
+}
+
+static void tc358746_set_pll(struct v4l2_subdev *sd)
+{
+	struct tc358746_state *state = to_state(sd);
+	struct tc358746_csi_param *csi_setting =
+		tc358746_g_cur_csi_settings(state);
+	struct device *dev = &state->i2c_client->dev;
+	u16 pllctl0 = i2c_rd16(sd, PLLCTL0);
+	u16 pllctl1 = i2c_rd16(sd, PLLCTL1);
+	u16 pll_frs = csi_setting->speed_range;
+	u16 pllctl0_new;
+
+	/*
+	 * Calculation:
+	 * speed_per_lane = (pllinclk_hz * (fbd + 1)) / 2^frs
+	 *
+	 * Calculation used by REF_02:
+	 * speed_per_lane = (pllinclk_hz * fbd) / 2^frs
+	 */
+	state->pll_fbd = csi_setting->speed_per_lane / state->pllinclk_hz;
+	state->pll_fbd <<= pll_frs;
+
+	pllctl0_new = PLLCTL0_PLL_PRD_SET(state->pll_prd) |
+		      PLLCTL0_PLL_FBD_SET(state->pll_fbd);
+
+	/*
+	 * Only rewrite when needed (new value or disabled), since rewriting
+	 * triggers another format change event.
+	 */
+	if ((pllctl0 != pllctl0_new) ||
+	    ((pllctl1 & PLLCTL1_PLL_EN_MASK) == 0)) {
+		u16 pllctl1_mask = (u16) ~(PLLCTL1_PLL_FRS_MASK |
+					   PLLCTL1_RESETB_MASK  |
+					   PLLCTL1_PLL_EN_MASK);
+		u16 pllctl1_val = PLLCTL1_PLL_FRS_SET(pll_frs) |
+				  PLLCTL1_RESETB_MASK | PLLCTL1_PLL_EN_MASK;
+
+		dev_dbg(dev, "updating PLL clock\n");
+		i2c_wr16(sd, PLLCTL0, pllctl0_new);
+		i2c_wr16_and_or(sd, PLLCTL1, pllctl1_mask, pllctl1_val);
+		udelay(1000);
+		i2c_wr16_and_or(sd, PLLCTL1, ~PLLCTL1_CKEN_MASK,
+				PLLCTL1_CKEN_MASK);
+	}
+
+	tc358746_dump_pll(dev, state);
+}
+
+static void tc358746_set_csi_color_space(struct v4l2_subdev *sd)
+{
+	struct tc358746_state *state = to_state(sd);
+	const struct tc358746_mbus_fmt *tc358746_fmt =
+		tc358746_get_format(state->fmt.code);
+
+	/* currently no self defined csi user data type id's are supported */
+	mutex_lock(&state->confctl_mutex);
+	i2c_wr16_and_or(sd, DATAFMT,
+			~(DATAFMT_PDFMT_MASK | DATAFMT_UDT_EN_MASK),
+			DATAFMT_PDFMT_SET(tc358746_fmt->pdformat));
+	i2c_wr16_and_or(sd, CONFCTL, ~CONFCTL_PDATAF_MASK,
+			CONFCTL_PDATAF_SET(tc358746_fmt->pdataf));
+	mutex_unlock(&state->confctl_mutex);
+}
+
+static void tc38764_debug_pattern_80(struct v4l2_subdev *sd)
+{
+	int i;
+
+	i2c_wr16(sd, DBG_ACT_LINE_CNT, 0x8000);
+	i2c_wr16(sd, DBG_LINE_WIDTH, 0x0396);
+	i2c_wr16(sd, DBG_VERT_BLANK_LINE_CNT, 0x0000);
+
+	for (i = 0; i < 80; i++)
+		i2c_wr16(sd, DBG_VIDEO_DATA, 0xff7f);
+	i2c_wr16(sd, DBG_VIDEO_DATA, 0xff00);
+	for (i = 0; i < 40; i++)
+		i2c_wr16(sd, DBG_VIDEO_DATA, 0xffff);
+	i2c_wr16(sd, DBG_VIDEO_DATA, 0xc0ff);
+	for (i = 0; i < 40; i++)
+		i2c_wr16(sd, DBG_VIDEO_DATA, 0xc000);
+	for (i = 0; i < 80; i++)
+		i2c_wr16(sd, DBG_VIDEO_DATA, 0x7f00);
+	for (i = 0; i < 80; i++)
+		i2c_wr16(sd, DBG_VIDEO_DATA, 0x7fff);
+	i2c_wr16(sd, DBG_VIDEO_DATA, 0x0000);
+	for (i = 0; i < 40; i++)
+		i2c_wr16(sd, DBG_VIDEO_DATA, 0x00ff);
+	i2c_wr16(sd, DBG_VIDEO_DATA, 0x00ff);
+	for (i = 0; i < 40; i++)
+		i2c_wr16(sd, DBG_VIDEO_DATA, 0x0000);
+	i2c_wr16(sd, DBG_VIDEO_DATA, 0x007f);
+
+	i2c_wr16(sd, DBG_ACT_LINE_CNT, 0xC1DF);
+}
+
+static void tc358746_enable_csi_lanes(struct v4l2_subdev *sd, int enable)
+{
+	struct tc358746_state *state = to_state(sd);
+	struct tc358746_csi_param *csi_setting =
+		tc358746_g_cur_csi_settings(state);
+	unsigned int lanes = csi_setting->lane_num;
+	u32 val = 0;
+
+	if (lanes < 1 || !enable)
+		i2c_wr32(sd, CLW_CNTRL, CLW_CNTRL_CLW_LANEDISABLE_MASK);
+	if (lanes < 1 || !enable)
+		i2c_wr32(sd, D0W_CNTRL, D0W_CNTRL_D0W_LANEDISABLE_MASK);
+	if (lanes < 2 || !enable)
+		i2c_wr32(sd, D1W_CNTRL, D1W_CNTRL_D1W_LANEDISABLE_MASK);
+	if (lanes < 3 || !enable)
+		i2c_wr32(sd, D2W_CNTRL, D2W_CNTRL_D2W_LANEDISABLE_MASK);
+	if (lanes < 4 || !enable)
+		i2c_wr32(sd, D3W_CNTRL, D2W_CNTRL_D3W_LANEDISABLE_MASK);
+
+	if (lanes > 0 && enable)
+		val |= HSTXVREGEN_CLM_HSTXVREGEN_MASK |
+			HSTXVREGEN_D0M_HSTXVREGEN_MASK;
+	if (lanes > 1 && enable)
+		val |= HSTXVREGEN_D1M_HSTXVREGEN_MASK;
+	if (lanes > 2 && enable)
+		val |= HSTXVREGEN_D2M_HSTXVREGEN_MASK;
+	if (lanes > 3 && enable)
+		val |= HSTXVREGEN_D3M_HSTXVREGEN_MASK;
+
+	i2c_wr32(sd, HSTXVREGEN, val);
+}
+
+static void tc358746_set_csi(struct v4l2_subdev *sd)
+{
+	struct tc358746_state *state = to_state(sd);
+	struct tc358746_csi_param *csi_setting =
+		tc358746_g_cur_csi_settings(state);
+	bool en_continuous_clk = csi_setting->is_continuous_clk;
+	u32 val;
+
+	val = TCLK_HEADERCNT_TCLK_ZEROCNT_SET(csi_setting->tclk_zerocnt) |
+	      TCLK_HEADERCNT_TCLK_PREPARECNT_SET(csi_setting->tclk_preparecnt);
+	i2c_wr32(sd, TCLK_HEADERCNT, val);
+	val = THS_HEADERCNT_THS_ZEROCNT_SET(csi_setting->ths_zerocnt) |
+	      THS_HEADERCNT_THS_PREPARECNT_SET(csi_setting->ths_preparecnt);
+	i2c_wr32(sd, THS_HEADERCNT, val);
+	i2c_wr32(sd, TWAKEUP, csi_setting->twakeupcnt);
+	i2c_wr32(sd, TCLK_POSTCNT, csi_setting->tclk_postcnt);
+	i2c_wr32(sd, THS_TRAILCNT, csi_setting->ths_trailcnt);
+	i2c_wr32(sd, LINEINITCNT, csi_setting->lineinitcnt);
+	i2c_wr32(sd, LPTXTIMECNT, csi_setting->lptxtimecnt);
+	i2c_wr32(sd, TCLK_TRAILCNT, csi_setting->tclk_trailcnt);
+	i2c_wr32(sd, TXOPTIONCNTRL,
+		 en_continuous_clk ? TXOPTIONCNTRL_CONTCLKMODE_MASK : 0);
+
+	if (state->test)
+		tc38764_debug_pattern_80(sd);
+
+	tc358746_dump_csi(&state->i2c_client->dev, csi_setting);
+}
+
+static void tc358746_enable_csi_module(struct v4l2_subdev *sd, int enable)
+{
+	struct tc358746_state *state = to_state(sd);
+	struct tc358746_csi_param *csi_setting =
+		tc358746_g_cur_csi_settings(state);
+	unsigned int lanes = csi_setting->lane_num;
+	u32 val;
+
+	if (!enable)
+		return;
+
+	i2c_wr32(sd, STARTCNTRL, STARTCNTRL_START_MASK);
+	i2c_wr32(sd, CSI_START, CSI_START_STRT_MASK);
+
+	val = CSI_CONTROL_NOL_1_MASK;
+	if (lanes == 2)
+		val = CSI_CONTROL_NOL_2_MASK;
+	else if (lanes == 3)
+		val = CSI_CONTROL_NOL_3_MASK;
+	else if (lanes == 4)
+		val = CSI_CONTROL_NOL_4_MASK;
+
+	val |= CSI_CONTROL_CSI_MODE_MASK | CSI_CONTROL_TXHSMD_MASK;
+	tc358746_wr_csi_control(sd, val);
+}
+
+static void tc358746_set_buffers(struct v4l2_subdev *sd)
+{
+	struct tc358746_state *state = to_state(sd);
+	struct device *dev = &state->i2c_client->dev;
+	const struct tc358746_mbus_fmt *tc358746_mbusfmt =
+		tc358746_get_format(state->fmt.code);
+	unsigned int byte_per_line =
+		(state->fmt.width * tc358746_mbusfmt->bpp) / 8;
+
+	i2c_wr16(sd, FIFOCTL, state->vb_fifo);
+	i2c_wr16(sd, WORDCNT, byte_per_line);
+	dev_dbg(dev, "FIFOCTL 0x%02x: WORDCNT 0x%02x\n",
+		state->vb_fifo, byte_per_line);
+}
+
+/* --------------- CORE OPS --------------- */
+
+static int tc358746_log_status(struct v4l2_subdev *sd)
+{
+	struct tc358746_state *state = to_state(sd);
+	uint16_t sysctl = i2c_rd16(sd, SYSCTL);
+
+	v4l2_info(sd, "-----Chip status-----\n");
+	v4l2_info(sd, "Chip ID: 0x%02lx\n",
+		  (i2c_rd16(sd, CHIPID) & CHIPID_CHIPID_MASK) >> 8);
+	v4l2_info(sd, "Chip revision: 0x%02lx\n",
+		  i2c_rd16(sd, CHIPID) & CHIPID_REVID_MASK);
+	v4l2_info(sd, "Sleep mode: %s\n", sysctl & SYSCTL_SLEEP_MASK ?
+		  "on" : "off");
+
+	v4l2_info(sd, "-----CSI-TX status-----\n");
+	v4l2_info(sd, "Waiting for particular sync signal: %s\n",
+			(i2c_rd16(sd, CSI_STATUS) & CSI_STATUS_S_WSYNC_MASK) ?
+			"yes" : "no");
+	v4l2_info(sd, "Transmit mode: %s\n",
+			(i2c_rd16(sd, CSI_STATUS) & CSI_STATUS_S_TXACT_MASK) ?
+			"yes" : "no");
+	v4l2_info(sd, "Stopped: %s\n",
+			(i2c_rd16(sd, CSI_STATUS) & CSI_STATUS_S_HLT_MASK) ?
+			"yes" : "no");
+	v4l2_info(sd, "Color space: %s\n",
+			state->fmt.code == MEDIA_BUS_FMT_UYVY8_2X8 ?
+			"YCbCr 422 8-bit" : "Unsupported");
+
+	return 0;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static void tc358746_print_register_map(struct v4l2_subdev *sd)
+{
+	v4l2_info(sd, "0x0000-0x0050: Global Register\n");
+	v4l2_info(sd, "0x0056-0x0070: Rx Control Registers\n");
+	v4l2_info(sd, "0x0080-0x00F8: Rx Status Registers\n");
+	v4l2_info(sd, "0x0100-0x0150: Tx D-PHY Register\n");
+	v4l2_info(sd, "0x0204-0x0238: Tx PPI Register\n");
+	v4l2_info(sd, "0x040c-0x0518: Tx Control Register\n");
+}
+
+static int tc358746_get_reg_size(u16 address)
+{
+	if (address <= 0x00ff)
+		return 2;
+	else if ((address >= 0x0100) && (address <= 0x05FF))
+		return 4;
+	else
+		return 1;
+}
+
+static int tc358746_g_register(struct v4l2_subdev *sd,
+			       struct v4l2_dbg_register *reg)
+{
+	if (reg->reg > 0xffff) {
+		tc358746_print_register_map(sd);
+		return -EINVAL;
+	}
+
+	reg->size = tc358746_get_reg_size(reg->reg);
+
+	reg->val = i2c_rdreg(sd, reg->reg, reg->size);
+
+	return 0;
+}
+
+static int tc358746_s_register(struct v4l2_subdev *sd,
+			       const struct v4l2_dbg_register *reg)
+{
+	if (reg->reg > 0xffff) {
+		tc358746_print_register_map(sd);
+		return -EINVAL;
+	}
+
+	i2c_wrreg(sd, (u16)reg->reg, reg->val,
+			tc358746_get_reg_size(reg->reg));
+
+	return 0;
+}
+#endif
+
+/* --------------- video ops --------------- */
+
+static int tc358746_g_mbus_config(struct v4l2_subdev *sd,
+			     struct v4l2_mbus_config *cfg)
+{
+	struct tc358746_state *state = to_state(sd);
+	struct tc358746_csi_param *csi_setting =
+		tc358746_g_cur_csi_settings(state);
+
+	cfg->type = V4L2_MBUS_CSI2_DPHY;
+	cfg->flags = csi_setting->is_continuous_clk ?
+			V4L2_MBUS_CSI2_CONTINUOUS_CLOCK :
+			V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
+
+	switch (csi_setting->lane_num) {
+	case 1:
+		cfg->flags |= V4L2_MBUS_CSI2_1_LANE;
+		break;
+	case 2:
+		cfg->flags |= V4L2_MBUS_CSI2_2_LANE;
+		break;
+	case 3:
+		cfg->flags |= V4L2_MBUS_CSI2_3_LANE;
+		break;
+	case 4:
+		cfg->flags |= V4L2_MBUS_CSI2_4_LANE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int tc358746_s_power(struct v4l2_subdev *sd, int on)
+{
+	struct tc358746_state *state = to_state(sd);
+
+	/*
+	 * REF_01:
+	 * Softreset don't reset configuration registers content but is needed
+	 * during power-on to trigger a csi LP-11 state change and during
+	 * power-off to disable the csi-module.
+	 */
+	tc358746_sreset(sd);
+
+	if (state->fmt_changed) {
+		tc358746_set_buffers(sd);
+		tc358746_set_csi(sd);
+		tc358746_set_csi_color_space(sd);
+
+		/* as recommend in REF_01 */
+		tc358746_sleep_mode(sd, 1);
+		tc358746_set_pll(sd);
+		tc358746_sleep_mode(sd, 0);
+
+		state->fmt_changed = false;
+	}
+
+	tc358746_enable_csi_lanes(sd, on);
+	tc358746_enable_csi_module(sd, on);
+	tc358746_sleep_mode(sd, !on);
+
+	return 0;
+}
+
+static int tc358746_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	tc358746_enable_stream(sd, enable);
+
+	return 0;
+}
+
+/* --------------- pad ops --------------- */
+
+static int tc358746_enum_mbus_code(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_pad_config *cfg,
+				   struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->index >= ARRAY_SIZE(tc358746_formats))
+		return -EINVAL;
+
+	code->code = tc358746_formats[code->index].code;
+
+	return 0;
+}
+
+static struct v4l2_mbus_framefmt *
+__tc358746_get_pad_format(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_pad_config *cfg,
+			  unsigned int pad, u32 which)
+{
+	struct tc358746_state *state = to_state(sd);
+
+	switch (which) {
+	case V4L2_SUBDEV_FORMAT_TRY:
+		return v4l2_subdev_get_try_format(sd, cfg, pad);
+	case V4L2_SUBDEV_FORMAT_ACTIVE:
+		return &state->fmt;
+	default:
+		return NULL;
+	}
+}
+
+static int tc358746_get_fmt(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_format *format)
+{
+	struct tc358746_state *state = to_state(sd);
+
+	if (format->pad != 0 && format->pad != 1)
+		return -EINVAL;
+
+	format->format.code = state->fmt.code;
+	format->format.width = state->fmt.width;
+	format->format.height = state->fmt.height;
+	format->format.field = state->fmt.field;
+
+	return 0;
+}
+
+static int tc358746_set_fmt(struct v4l2_subdev *sd,
+			    struct v4l2_subdev_pad_config *cfg,
+			    struct v4l2_subdev_format *format)
+{
+	struct tc358746_state *state = to_state(sd);
+	struct device *dev = &state->i2c_client->dev;
+	struct media_pad *pad = &state->pads[format->pad];
+	struct media_pad *remote_sensor_pad =
+		media_entity_remote_pad(&state->pads[0]);
+	struct v4l2_subdev *sensor_sd;
+	struct v4l2_mbus_framefmt *mbusformat;
+	const struct tc358746_mbus_fmt *tc358746_mbusformat;
+	struct v4l2_ctrl *ctrl;
+	unsigned int pclk, hblank;
+	int new_freq, cur_freq = v4l2_ctrl_g_ctrl(state->link_freq);
+	u16 vb_fifo;
+
+	if (pad->flags == MEDIA_PAD_FL_SOURCE)
+		return tc358746_get_fmt(sd, cfg, format);
+
+	mbusformat = __tc358746_get_pad_format(sd, cfg, format->pad,
+					       format->which);
+	if (!mbusformat)
+		return -EINVAL;
+
+	tc358746_mbusformat = tc358746_get_format(format->format.code);
+	if (!tc358746_mbusformat) {
+		format->format.code = tc358746_def_fmt.code;
+		tc358746_mbusformat = tc358746_get_format(format->format.code);
+	}
+
+	/*
+	 * Some sensors change their hblank and pclk value on different formats,
+	 * so we need to request it again.
+	 */
+	sensor_sd = media_entity_to_v4l2_subdev(remote_sensor_pad->entity);
+	ctrl = v4l2_ctrl_find(sensor_sd->ctrl_handler, V4L2_CID_PIXEL_RATE);
+	pclk = v4l2_ctrl_g_ctrl_int64(ctrl);
+	if (pclk != state->pclk) {
+		dev_dbg(dev, "Update pclk from %u to %u\n", state->pclk, pclk);
+		state->pclk = pclk;
+	}
+	ctrl = v4l2_ctrl_find(sensor_sd->ctrl_handler, V4L2_CID_HBLANK);
+	hblank = v4l2_ctrl_g_ctrl(ctrl);
+	if (hblank != state->hblank) {
+		dev_dbg(dev, "Update hblank from %u to %u\n", state->hblank,
+			hblank);
+		state->hblank = hblank;
+	}
+
+	/*
+	 * Normaly the HW has no size limitations but we have to check if the
+	 * csi timings are valid for this size. The timings can be adjust by the
+	 * fifo size. If this doesn't work we have to do this check again with a
+	 * other csi link frequency if it is possible.
+	 */
+	new_freq = tc358746_adjust_timings(state, tc358746_mbusformat,
+					   &format->format.width, &vb_fifo);
+
+	/* Currently only a few YUV based formats are supported */
+	if (tc358746_format_supported(format->format.code))
+		format->format.code = MEDIA_BUS_FMT_UYVY8_2X8;
+
+	/* Currently only non interleaved images are supported */
+	format->format.field = V4L2_FIELD_NONE;
+
+	*mbusformat = format->format;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+		state->fmt_changed = true;
+		state->vb_fifo = vb_fifo;
+		if (new_freq != cur_freq)
+			v4l2_ctrl_s_ctrl(state->link_freq, new_freq);
+	}
+
+	return 0;
+}
+
+static int
+tc358746_link_validate(struct v4l2_subdev *sd, struct media_link *link,
+		       struct v4l2_subdev_format *source_fmt,
+		       struct v4l2_subdev_format *sink_fmt)
+{
+	struct tc358746_state *state = to_state(sd);
+	struct device *dev = &state->i2c_client->dev;
+	const struct tc358746_mbus_fmt *tc358746_mbusformat;
+	struct v4l2_subdev *sensor_sd;
+	struct v4l2_ctrl *ctrl;
+	unsigned int pclk, pclk_old = state->pclk;
+	unsigned int hblank, hblank_old = state->hblank;
+	int new_freq;
+	u16 vb_fifo;
+
+	/*
+	 * Only validate if the timings are changed, after the link was already
+	 * initialized. This can be happen if the parallel sensor frame interval
+	 * is changed. Format checks are perfomed by the common code.
+	 */
+
+	tc358746_mbusformat = tc358746_get_format(sink_fmt->format.code);
+	if (!tc358746_mbusformat)
+		return -EINVAL; /* Format was changed too and is invalid */
+
+	sensor_sd = media_entity_to_v4l2_subdev(link->source->entity);
+	ctrl = v4l2_ctrl_find(sensor_sd->ctrl_handler, V4L2_CID_PIXEL_RATE);
+	pclk = v4l2_ctrl_g_ctrl_int64(ctrl);
+	if (pclk != state->pclk) {
+		dev_dbg(dev, "%s pixel rate is changed\n", sensor_sd->name);
+		state->pclk = pclk;
+	}
+
+	ctrl = v4l2_ctrl_find(sensor_sd->ctrl_handler, V4L2_CID_HBLANK);
+	hblank = v4l2_ctrl_g_ctrl(ctrl);
+	if (hblank != state->hblank) {
+		dev_dbg(dev,
+			"%s hblank interval is changed\n", sensor_sd->name);
+		state->hblank = hblank;
+	}
+
+	new_freq = tc358746_adjust_timings(state, tc358746_mbusformat,
+					   &source_fmt->format.width, &vb_fifo);
+
+	if (new_freq != v4l2_ctrl_g_ctrl(state->link_freq)) {
+		/*
+		 * This can lead into undefined behaviour, so we don't support
+		 * dynamic changes due to a to late re-configuration.
+		 */
+		dev_err(dev,
+			"%s format can't be applied re-run the whole s_fmt\n",
+			sensor_sd->name);
+		state->pclk = pclk_old;
+		state->hblank = hblank_old;
+
+		return -EINVAL;
+	}
+
+	state->fmt_changed = true;
+	state->vb_fifo = vb_fifo;
+
+	return 0;
+}
+
+static int tc358764_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct tc358746_state *state = container_of(ctrl->handler,
+					       struct tc358746_state, hdl);
+	struct device *dev = &state->i2c_client->dev;
+
+	switch (ctrl->id) {
+	case V4L2_CID_LINK_FREQ:
+		dev_info(dev, "Update link-frequency %llu -> %llu\n",
+			 state->link_frequencies[ctrl->cur.val],
+			 state->link_frequencies[ctrl->val]);
+
+		return 0;
+	case V4L2_CID_TEST_PATTERN:
+		state->test = ctrl->val;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int tc358746_link_setup(struct media_entity *entity,
+			       const struct media_pad *local,
+			       const struct media_pad *remote, u32 flags)
+{
+	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+	struct v4l2_subdev *ps_sd = media_entity_to_v4l2_subdev(remote->entity);
+	struct tc358746_state *state = to_state(sd);
+	struct v4l2_ctrl *ctrl;
+
+	/* no special requirements on source pads */
+	if (local->flags & MEDIA_PAD_FL_SOURCE)
+		return 0;
+
+	dev_dbg(sd->dev, "link setup '%s':%d->'%s':%d[%d]",
+		remote->entity->name, remote->index, local->entity->name,
+		local->index, flags & MEDIA_LNK_FL_ENABLED);
+
+	/*
+	 * The remote parallel sensor must support pixel rate and hblank query
+	 */
+	ctrl = v4l2_ctrl_find(ps_sd->ctrl_handler, V4L2_CID_PIXEL_RATE);
+	if (!ctrl) {
+		dev_err(sd->dev, "Subdev %s must support V4L2_CID_PIXEL_RATE\n",
+			ps_sd->name);
+		return -EINVAL;
+	}
+	state->pclk = v4l2_ctrl_g_ctrl_int64(ctrl);
+
+	ctrl = v4l2_ctrl_find(ps_sd->ctrl_handler, V4L2_CID_HBLANK);
+	if (!ctrl) {
+		dev_err(sd->dev, "Subdev %s must support V4L2_CID_HBLANK\n",
+			ps_sd->name);
+		return -EINVAL;
+	}
+	state->hblank = v4l2_ctrl_g_ctrl(ctrl);
+
+	return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+static const struct v4l2_ctrl_ops tc358764_ctrl_ops = {
+	.s_ctrl = tc358764_s_ctrl,
+};
+
+static const struct v4l2_subdev_core_ops tc358746_core_ops = {
+	.log_status = tc358746_log_status,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register = tc358746_g_register,
+	.s_register = tc358746_s_register,
+#endif
+	.s_power = tc358746_s_power,
+};
+
+static const struct v4l2_subdev_video_ops tc358746_video_ops = {
+	.g_mbus_config = tc358746_g_mbus_config,
+	.s_stream = tc358746_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops tc358746_pad_ops = {
+	.enum_mbus_code = tc358746_enum_mbus_code,
+	.set_fmt = tc358746_set_fmt,
+	.get_fmt = tc358746_get_fmt,
+	.link_validate = tc358746_link_validate,
+};
+
+static const struct v4l2_subdev_ops tc358746_ops = {
+	.core = &tc358746_core_ops,
+	.video = &tc358746_video_ops,
+	.pad = &tc358746_pad_ops,
+};
+
+static const struct media_entity_operations tc358746_entity_ops = {
+	.link_setup = &tc358746_link_setup,
+	.link_validate = &v4l2_subdev_link_validate,
+};
+
+/* --------------- PROBE / REMOVE --------------- */
+
+static int tc358746_set_lane_settings(struct tc358746_state *state,
+				      struct v4l2_fwnode_endpoint *fw)
+{
+	struct device *dev = &state->i2c_client->dev;
+	int i;
+
+	for (i = 0; i < fw->nr_of_link_frequencies; i++) {
+		struct tc358746_csi_param *s =
+			&state->link_freq_settings[i];
+		u32 bps_pr_lane;
+
+		state->link_frequencies[i] = fw->link_frequencies[i];
+
+		/*
+		 * The CSI bps per lane must be between 62.5 Mbps and 1 Gbps.
+		 * bps_pr_lane = 2 * link_freq, because MIPI data lane is double
+		 * data rate.
+		 */
+		bps_pr_lane = 2 * fw->link_frequencies[i];
+		if (bps_pr_lane < 62500000U || bps_pr_lane > 1000000000U) {
+			dev_err(dev, "unsupported bps per lane: %u bps\n",
+				bps_pr_lane);
+			return -EINVAL;
+		}
+
+		if (bps_pr_lane > 500000000)
+			s->speed_range = 0;
+		else if (bps_pr_lane > 250000000)
+			s->speed_range = 1;
+		else if (bps_pr_lane > 125000000)
+			s->speed_range = 2;
+		else
+			s->speed_range = 3;
+
+		s->unit_clk_hz = state->pllinclk_hz >> s->speed_range;
+		s->unit_clk_mul = bps_pr_lane / s->unit_clk_hz;
+		s->speed_per_lane = bps_pr_lane;
+		s->lane_num = fw->bus.mipi_csi2.num_data_lanes;
+		s->is_continuous_clk = fw->bus.mipi_csi2.flags &
+			V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
+
+		if (s->speed_per_lane != 432000000U)
+			dev_warn(dev, "untested bps per lane: %u bps\n",
+				 s->speed_per_lane);
+
+		dev_dbg(dev, "%s: lane setting %d\n", __func__, i);
+		dev_dbg(dev, "unit_clk %uHz: unit_clk_mul %u: speed_range %u: speed_per_lane(bps/lane) %u: csi_lange_numbers %u\n",
+			s->unit_clk_hz, s->unit_clk_mul, s->speed_range,
+			s->speed_per_lane, s->lane_num);
+	}
+
+	state->link_frequencies_num = fw->nr_of_link_frequencies;
+
+	return 0;
+}
+
+static void tc358746_gpio_reset(struct tc358746_state *state)
+{
+	usleep_range(5000, 10000);
+	gpiod_set_value(state->reset_gpio, 1);
+	usleep_range(1000, 2000);
+	gpiod_set_value(state->reset_gpio, 0);
+	msleep(20);
+}
+
+static int tc358746_apply_fw(struct tc358746_state *state)
+{
+	struct tc358746_csi_param *csi_setting;
+	int err, i;
+
+	for (i = 0; i < state->link_frequencies_num; i++) {
+		csi_setting = &state->link_freq_settings[i];
+
+		err = tc358746_calculate_csi_txtimings(state, csi_setting);
+		if (err) {
+			dev_err(&state->i2c_client->dev,
+				"Failed to calc csi-tx tminings\n");
+			return err;
+		}
+	}
+
+	/*
+	 * Set it to the hw default value. The correct value will be set during
+	 * set_fmt(), since it depends on the pclk and and the resulution.
+	 */
+	state->vb_fifo = 1;
+
+	err = clk_prepare_enable(state->refclk);
+	if (err) {
+		dev_err(&state->i2c_client->dev, "Failed to enable clock\n");
+		return err;
+	}
+
+	if (state->reset_gpio)
+		tc358746_gpio_reset(state);
+
+	return 0;
+}
+
+static int tc358746_probe_fw(struct tc358746_state *state)
+{
+	struct device *dev = &state->i2c_client->dev;
+	struct v4l2_fwnode_endpoint endpoint = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY,
+	};
+	struct fwnode_handle *fw_node;
+	unsigned int refclk, pllinclk;
+	unsigned char pll_prediv;
+	int ret = -EINVAL;
+
+	/* Parse all clocks */
+	state->refclk = devm_clk_get(dev, "refclk");
+	if (IS_ERR(state->refclk)) {
+		if (PTR_ERR(state->refclk) != -EPROBE_DEFER)
+			dev_err(dev, "failed to get refclk: %ld\n",
+				PTR_ERR(state->refclk));
+		return PTR_ERR(state->refclk);
+	}
+
+	refclk = clk_get_rate(state->refclk);
+	if (refclk < 6000000 || refclk > 40000000) {
+		dev_err(dev, "refclk must between 6MHz and 40MHz\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * The PLL input clock is obtained by dividing refclk by pll_prd.
+	 * It must be between 4 MHz and 40 MHz, lower frequency is better.
+	 */
+	pll_prediv = DIV_ROUND_CLOSEST(refclk, 4000000);
+	if (pll_prediv < 1 || pll_prediv > 16) {
+		dev_err(dev, "invalid pll pre-divider value: %d\n", pll_prediv);
+		return -EINVAL;
+	}
+	state->pll_prd = pll_prediv;
+
+	pllinclk = DIV_ROUND_CLOSEST(refclk, pll_prediv);
+	if (pllinclk < 4000000 || pllinclk > 40000000) {
+		dev_err(dev, "invalid pll input clock: %d Hz\n", pllinclk);
+		return -EINVAL;
+	}
+	state->pllinclk_hz = pllinclk;
+
+	/* Now parse the fw-node */
+	fwnode_graph_for_each_endpoint(dev_fwnode(dev), fw_node) {
+		struct fwnode_endpoint fw_ep;
+
+		ret = fwnode_graph_parse_endpoint(fw_node, &fw_ep);
+		if (ret)
+			return -EINVAL;
+
+		/* get downstream endpoint */
+		if (fw_ep.port == 1)
+			break;
+	}
+
+	if (!fw_node) {
+		dev_err(dev, "missing endpoint node\n");
+		return -EINVAL;
+	}
+
+	ret = v4l2_fwnode_endpoint_alloc_parse(fw_node, &endpoint);
+	if (ret) {
+		dev_err(dev, "failed to parse endpoint %d\n", ret);
+		return ret;
+	}
+
+	if (endpoint.bus.mipi_csi2.num_data_lanes == 0 ||
+	    endpoint.nr_of_link_frequencies == 0) {
+		dev_err(dev, "missing CSI-2 properties in endpoint\n");
+		ret = -EINVAL;
+		goto free_ep;
+	}
+
+	if (endpoint.bus.mipi_csi2.num_data_lanes > 4) {
+		dev_err(dev, "invalid number of lanes\n");
+		ret = -EINVAL;
+		goto free_ep;
+	}
+
+	state->link_freq_settings =
+		devm_kcalloc(dev, endpoint.nr_of_link_frequencies,
+			     sizeof(*state->link_freq_settings), GFP_KERNEL);
+	if (!state->link_freq_settings) {
+		ret = -ENOMEM;
+		goto free_ep;
+	}
+
+	state->link_frequencies =
+		devm_kcalloc(dev, endpoint.nr_of_link_frequencies,
+			     sizeof(*state->link_frequencies), GFP_KERNEL);
+	if (!state->link_frequencies) {
+		ret = -ENOMEM;
+		goto free_ep;
+	}
+
+	ret = tc358746_set_lane_settings(state, &endpoint);
+	if (ret)
+		goto free_ep;
+
+	state->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+						    GPIOD_OUT_LOW);
+	if (IS_ERR(state->reset_gpio)) {
+		dev_err(dev, "failed to get reset gpio\n");
+		return PTR_ERR(state->reset_gpio);
+	}
+
+	ret = 0;
+
+free_ep:
+	v4l2_fwnode_endpoint_free(&endpoint);
+	return ret;
+}
+
+static int tc358746_parse_endpoint(struct device *dev,
+				   struct v4l2_fwnode_endpoint *vep,
+				   struct v4l2_async_subdev *asd)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+
+	if (!fwnode_device_is_available(asd->match.fwnode)) {
+		v4l2_err(sd, "remote is not available\n");
+		return -ENOTCONN;
+	}
+
+	if (vep->bus_type != V4L2_MBUS_PARALLEL) {
+		v4l2_err(sd, "invalid bus type, must be PARALLEL\n");
+		return -ENOTCONN;
+	}
+
+	return 0;
+};
+
+static int tc358746_async_register(struct v4l2_subdev *sd)
+{
+	unsigned int port = 0;
+
+	return v4l2_async_register_fwnode_subdev(
+		sd, sizeof(struct v4l2_async_subdev), &port, 1,
+		tc358746_parse_endpoint);
+
+}
+
+static const char * const tc358764_test_pattern_menu[] = {
+	"Disabled",
+	"colorbar 80px",
+};
+
+static int tc358746_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct tc358746_state *state;
+	struct v4l2_subdev *sd;
+	int err;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -EIO;
+
+	state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return -ENOMEM;
+
+	state->i2c_client = client;
+
+	/* platform data */
+	err = tc358746_probe_fw(state);
+	if (err)
+		return err;
+
+	err = tc358746_apply_fw(state);
+	if (err)
+		return err;
+
+	sd = &state->sd;
+	v4l2_i2c_subdev_init(sd, client, &tc358746_ops);
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+	/* i2c access */
+	if (((i2c_rd16(sd, CHIPID) & CHIPID_CHIPID_MASK) >> 8) != 0x44) {
+		v4l2_info(sd, "not a TC358746 on address 0x%x\n",
+			  client->addr << 1);
+		return -ENODEV;
+	}
+
+	/* control handlers */
+	v4l2_ctrl_handler_init(&state->hdl, 1);
+
+	v4l2_ctrl_new_std_menu_items(&state->hdl,
+			&tc358764_ctrl_ops, V4L2_CID_TEST_PATTERN,
+			ARRAY_SIZE(tc358764_test_pattern_menu) - 1, 0, 0,
+			tc358764_test_pattern_menu);
+
+	state->link_freq =
+		v4l2_ctrl_new_int_menu(&state->hdl, &tc358764_ctrl_ops,
+				       V4L2_CID_LINK_FREQ,
+				       state->link_frequencies_num - 1,
+				       TC358746_DEF_LINK_FREQ,
+				       state->link_frequencies);
+
+
+	sd->ctrl_handler = &state->hdl;
+	if (state->hdl.error) {
+		err = state->hdl.error;
+		goto err_hdl;
+	}
+
+	state->pads[1].flags = MEDIA_PAD_FL_SOURCE;
+	state->pads[0].flags = MEDIA_PAD_FL_SINK;
+	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	sd->entity.ops = &tc358746_entity_ops;
+	err = media_entity_pads_init(&sd->entity, 2, state->pads);
+	if (err < 0)
+		goto err_hdl;
+
+	mutex_init(&state->confctl_mutex);
+
+	state->fmt = tc358746_def_fmt;
+
+	/* apply default settings */
+	tc358746_sreset(sd);
+	tc358746_set_buffers(sd);
+	tc358746_set_csi(sd);
+	tc358746_set_csi_color_space(sd);
+	tc358746_sleep_mode(sd, 1);
+	tc358746_set_pll(sd);
+	tc358746_enable_stream(sd, 0);
+
+	err = tc358746_async_register(sd);
+	if (err < 0)
+		goto err_hdl;
+
+	v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name,
+		  client->addr << 1, client->adapter->name);
+
+	return 0;
+
+err_hdl:
+	media_entity_cleanup(&sd->entity);
+	v4l2_ctrl_handler_free(&state->hdl);
+	return err;
+}
+
+static int tc358746_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct tc358746_state *state = to_state(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	v4l2_device_unregister_subdev(sd);
+	mutex_destroy(&state->confctl_mutex);
+	media_entity_cleanup(&sd->entity);
+	v4l2_ctrl_handler_free(&state->hdl);
+
+	return 0;
+}
+
+static const struct i2c_device_id tc358746_id[] = {
+	{"tc358746", 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, tc358746_id);
+
+static const struct of_device_id __maybe_unused tc358746_of_match[] = {
+	{ .compatible = "toshiba,tc358746" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, tc358746_of_match);
+
+static struct i2c_driver tc358746_driver = {
+	.driver = {
+		.name = "tc358746",
+		.of_match_table = of_match_ptr(tc358746_of_match),
+	},
+	.probe = tc358746_probe,
+	.remove = tc358746_remove,
+	.id_table = tc358746_id,
+};
+
+module_i2c_driver(tc358746_driver);
diff --git a/drivers/media/i2c/tc358746_regs.h b/drivers/media/i2c/tc358746_regs.h
new file mode 100644
index 000000000000..9232d00d0e92
--- /dev/null
+++ b/drivers/media/i2c/tc358746_regs.h
@@ -0,0 +1,208 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tc358746 - Toshiba Parallel to CSI-2 bridge - register names and bit masks
+ *
+ * Convention:
+ * <REGISTER>
+ * <REGISTER>_<BITFIELD>_MASK
+ * <REGISTER>_<BITFIELD>_<VALUE>
+ * <REGISTER>_<BITFIELD>_SET(val = <REGISTER>_<BITFIELD>_<VALUE>)
+ *
+ * References:
+ * REF_01:
+ * - TC358746AXBG/TC358748XBG/TC358748IXBG Functional Specification Rev 1.2
+ */
+
+#ifndef __TC358746_REGS_H
+#define __TC358746_REGS_H
+
+#define CHIPID			0x0000
+#define CHIPID_CHIPID_MASK		GENMASK(15, 8)
+#define CHIPID_REVID_MASK		GENMASK(7, 0)
+
+#define SYSCTL			0x0002
+#define SYSCTL_SLEEP_MASK		BIT(1)
+#define SYSCTL_SRESET_MASK		BIT(0)
+
+#define CONFCTL                 0x0004
+#define CONFCTL_TRIEN_MASK		BIT(15)
+#define CONFCTL_INTE2N_MASK		BIT(13)
+#define CONFCTL_BT656EN_MASK		BIT(12)
+#define CONFCTL_PDATAF_MASK		GENMASK(9, 8)
+#define CONFCTL_PDATAF_SET(val)		(((val << 8) & CONFCTL_PDATAF_MASK))
+#define CONFCTL_PDATAF_MODE0		0
+#define CONFCTL_PDATAF_MODE1		1
+#define CONFCTL_PDATAF_MODE2		2
+#define CONFCTL_PPEN_MASK		BIT(6)
+#define CONFCTL_VVALIDP_MASK		BIT(5)
+#define CONFCTL_HVALIDP_MASK		BIT(4)
+#define CONFCTL_PCLKP_MASK		BIT(3)
+#define CONFCTL_AUTO_MASK		BIT(2)
+#define CONFCTL_DATALANE_MASK		GENMASK(1, 0)
+#define CONFCTL_DATALANE_1		0
+#define CONFCTL_DATALANE_2		1
+#define CONFCTL_DATALANE_3		2
+#define CONFCTL_DATALANE_4		3
+
+#define FIFOCTL			0x0006
+#define DATAFMT			0x0008
+#define DATAFMT_PDFMT_RAW8		0
+#define DATAFMT_PDFMT_RAW10		1
+#define DATAFMT_PDFMT_RAW12		2
+#define DATAFMT_PDFMT_RGB888		3
+#define DATAFMT_PDFMT_RGB666		4
+#define DATAFMT_PDFMT_RGB565		5
+#define DATAFMT_PDFMT_YCBCRFMT_422_8_BIT 6
+#define DATAFMT_PDFMT_RAW14		8
+#define DATAFMT_PDFMT_YCBCRFMT_422_10_BIT 9
+#define DATAFMT_PDFMT_YCBCRFMT_444	10
+#define DATAFMT_PDFMT_MASK		GENMASK(7, 4)
+#define DATAFMT_PDFMT_SET(val)		(((val) << 4) & DATAFMT_PDFMT_MASK)
+#define DATAFMT_UDT_EN_MASK		BIT(0)
+
+#define MCLKCTL			0x000c
+#define MCLKCTL_MCLK_HIGH_MASK		GENMASK(15, 8)
+#define MCLKCTL_MCLK_HIGH_SET(val)	((((val) - 1) << 8) & MCLKCTL_MCLK_HIGH_MASK)
+#define MCLKCTL_MCLK_LOW_MASK		GENMASK(7, 0)
+#define MCLKCTL_MCLK_LOW_SET(val)	(((val) - 1) & MCLKCTL_MCLK_LOW_MASK)
+
+#define PLLCTL0			0x0016
+#define PLLCTL0_PLL_PRD_MASK		GENMASK(15, 12)
+#define PLLCTL0_PLL_PRD_SET(prd)	((((prd) - 1) << 12) & PLLCTL0_PLL_PRD_MASK)
+#define PLLCTL0_PLL_FBD_MASK		GENMASK(8, 0)
+#define PLLCTL0_PLL_FBD_SET(fbd)        (((fbd) - 1) & PLLCTL0_PLL_FBD_MASK)
+
+#define PLLCTL1                 0x0018
+#define PLLCTL1_PLL_FRS_MASK            GENMASK(11, 10)
+#define PLLCTL1_PLL_FRS_SET(frs)        (((frs) << 10) & PLLCTL1_PLL_FRS_MASK)
+#define PLLCTL1_PLL_LBWS_MASK		GENMASK(9, 8)
+#define PLLCTL1_LFBREN_MASK		BIT(6)
+#define PLLCTL1_BYPCKEN_MASK		BIT(5)
+#define PLLCTL1_CKEN_MASK		BIT(4)
+#define PLLCTL1_RESETB_MASK		BIT(1)
+#define PLLCTL1_PLL_EN_MASK		BIT(0)
+
+#define CLKCTL			0x0020
+#define CLKCTL_MCLKDIV_MASK		GENMASK(3, 2)
+#define CLKCTL_MCLKDIV_SET(val)		((val << 2) & CLKCTL_MCLKDIV_MASK)
+#define CLKCTL_MCLKDIV_8		0
+#define CLKCTL_MCLKDIV_4		1
+#define CLKCTL_MCLKDIV_2		2
+
+#define WORDCNT			0x0022
+#define PP_MISC                 0x0032
+#define PP_MISC_FRMSTOP_MASK		BIT(15)
+#define PP_MISC_RSTPTR_MASK		BIT(14)
+
+#define CSI2TX_DATA_TYPE	0x0050
+#define MIPI_PHY_STATUS		0x0062
+#define CSI2_ERROR_STATUS       0x0064
+#define CSI2_ERR_EN             0x0066
+#define CSI2_IDID_ERROR         0x006c
+#define DBG_ACT_LINE_CNT        0x00e0
+#define DBG_LINE_WIDTH		0x00e2
+#define DBG_VERT_BLANK_LINE_CNT	0x00e4
+#define DBG_VIDEO_DATA          0x00e8
+#define FIFOSTATUS              0x00F8
+
+#define CLW_CNTRL               0x0140
+#define CLW_CNTRL_CLW_LANEDISABLE_MASK	BIT(0)
+
+#define D0W_CNTRL               0x0144
+#define D0W_CNTRL_D0W_LANEDISABLE_MASK	BIT(0)
+
+#define D1W_CNTRL		0x0148
+#define D1W_CNTRL_D1W_LANEDISABLE_MASK	BIT(0)
+
+#define D2W_CNTRL		0x014C
+#define D2W_CNTRL_D2W_LANEDISABLE_MASK	BIT(0)
+
+#define D3W_CNTRL		0x0150
+#define D2W_CNTRL_D3W_LANEDISABLE_MASK	BIT(0)
+
+#define STARTCNTRL              0x0204
+#define STARTCNTRL_START_MASK		BIT(0)
+
+#define LINEINITCNT		0x0210
+#define LPTXTIMECNT		0x0214
+#define TCLK_HEADERCNT		0x0218
+#define	TCLK_HEADERCNT_TCLK_ZEROCNT_MASK	GENMASK(15, 8)
+#define TCLK_HEADERCNT_TCLK_PREPARECNT_MASK	GENMASK(6, 0)
+#define	TCLK_HEADERCNT_TCLK_ZEROCNT_SET(val)	((val << 8) & TCLK_HEADERCNT_TCLK_ZEROCNT_MASK)
+#define	TCLK_HEADERCNT_TCLK_PREPARECNT_SET(val)	(val & TCLK_HEADERCNT_TCLK_PREPARECNT_MASK)
+
+#define TCLK_TRAILCNT		0x021C
+#define THS_HEADERCNT		0x0220
+#define	THS_HEADERCNT_THS_ZEROCNT_MASK		GENMASK(14, 8)
+#define	THS_HEADERCNT_THS_PREPARECNT_MASK	GENMASK(6, 0)
+#define	THS_HEADERCNT_THS_ZEROCNT_SET(val)	((val << 8) & THS_HEADERCNT_THS_ZEROCNT_MASK)
+#define	THS_HEADERCNT_THS_PREPARECNT_SET(val)	(val & THS_HEADERCNT_THS_PREPARECNT_MASK)
+
+#define TWAKEUP			0x0224
+#define TCLK_POSTCNT		0x0228
+#define THS_TRAILCNT		0x022C
+#define HSTXVREGCNT		0x0230
+#define HSTXVREGEN              0x0234
+#define HSTXVREGEN_D3M_HSTXVREGEN_MASK	BIT(4)
+#define HSTXVREGEN_D2M_HSTXVREGEN_MASK  BIT(3)
+#define HSTXVREGEN_D1M_HSTXVREGEN_MASK  BIT(2)
+#define HSTXVREGEN_D0M_HSTXVREGEN_MASK  BIT(1)
+#define HSTXVREGEN_CLM_HSTXVREGEN_MASK  BIT(0)
+
+#define TXOPTIONCNTRL           0x0238
+#define TXOPTIONCNTRL_CONTCLKMODE_MASK	BIT(0)
+
+#define CSI_CONTROL             0x040C
+#define CSI_CONTROL_CSI_MODE_MASK       BIT(15)
+#define CSI_CONTROL_HTXTOEN_MASK        BIT(10)
+#define CSI_CONTROL_TXHSMD_MASK         BIT(7)
+#define CSI_CONTROL_NOL_MASK            GENMASK(2, 1)
+#define CSI_CONTROL_NOL_1_MASK          0
+#define CSI_CONTROL_NOL_2_MASK          BIT(1)
+#define CSI_CONTROL_NOL_3_MASK          BIT(2)
+#define CSI_CONTROL_NOL_4_MASK          (BIT(1) | BIT(2))
+#define CSI_CONTROL_EOTDIS_MASK         BIT(0)
+
+#define CSI_STATUS              0x0410
+#define CSI_STATUS_S_WSYNC_MASK		BIT(10)
+#define CSI_STATUS_S_TXACT_MASK		BIT(9)
+#define CSI_STATUS_S_HLT_MASK		BIT(0)
+
+#define CSI_INT			0x0414
+#define CSI_INT_INTHLT_MASK		BIT(3)
+#define CSI_INT_INTER_MASK		BIT(2)
+
+#define CSI_INT_ENA             0x0418
+#define CSI_INT_ENA_IENHLT_MASK		BIT(3)
+#define CSI_INT_ENA_IENER_MASK		BIT(2)
+
+#define CSI_ERR                 0x044C
+#define CSI_ERR_INER_MASK               BIT(9)
+#define CSI_ERR_WCER_MASK		BIT(8)
+#define CSI_ERR_QUNK_MASK		BIT(4)
+#define CSI_ERR_TXBRK_MASK		BIT(1)
+
+#define CSI_ERR_INTENA          0x0450
+#define CSI_ERR_HALT            0x0454
+#define CSI_CONFW               0x0500
+#define CSI_CONFW_MODE_MASK			GENMASK(31, 29)
+#define CSI_CONFW_MODE_SET_MASK			(BIT(31) | BIT(29))
+#define CSI_CONFW_MODE_CLEAR_MASK		(BIT(31) | BIT(30))
+#define CSI_CONFW_ADDRESS_MASK			GENMASK(28, 24)
+#define CSI_CONFW_ADDRESS_CSI_CONTROL_MASK	(BIT(24) | BIT(25))
+#define CSI_CONFW_ADDRESS_CSI_INT_ENA_MASK	(BIT(25) | BIT(26))
+#define CSI_CONFW_ADDRESS_CSI_ERR_INTENA_MASK	(BIT(28) | BIT(26))
+#define CSI_CONFW_ADDRESS_CSI_ERR_HALT_MASK	(BIT(28) | BIT(26) | BIT(24))
+#define CSI_CONFW_DATA_MASK			GENMASK(15, 0)
+
+#define CSIRESET                0x0504
+#define CSIRESET_RESET_CNF_MASK		BIT(1)
+#define CSIRESET_RESET_MODULE_MASK	BIT(0)
+
+#define CSI_INT_CLR             0x050C
+#define CSI_INT_CLR_ICRER_MASK		BIT(2)
+
+#define CSI_START               0x0518
+#define CSI_START_STRT_MASK		BIT(0)
+
+#endif