diff mbox series

[v6,2/3] media: imx: add a driver for i.MX8MQ mipi csi rx phy and controller

Message ID 20210714111931.324485-3-martin.kepplinger@puri.sm (mailing list archive)
State New, archived
Headers show
Series media: imx: add support for imx8mq MIPI RX | expand

Commit Message

Martin Kepplinger July 14, 2021, 11:19 a.m. UTC
Add a driver to support the i.MX8MQ MIPI CSI receiver. The hardware side
is based on
https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c?h=imx_5.4.70_2.3.0

It's built as part of VIDEO_IMX7_CSI because that's documented to support
i.MX8M platforms. This driver adds i.MX8MQ support where currently only the
i.MX8MM platform has been supported.

Signed-off-by: Martin Kepplinger <martin.kepplinger@puri.sm>
---
 drivers/staging/media/imx/Makefile           |   1 +
 drivers/staging/media/imx/imx8mq-mipi-csi2.c | 949 +++++++++++++++++++
 2 files changed, 950 insertions(+)
 create mode 100644 drivers/staging/media/imx/imx8mq-mipi-csi2.c

Comments

Laurent Pinchart July 14, 2021, 6:24 p.m. UTC | #1
Hi Martin,

Thank you for the patch.

On Wed, Jul 14, 2021 at 01:19:30PM +0200, Martin Kepplinger wrote:
> Add a driver to support the i.MX8MQ MIPI CSI receiver. The hardware side
> is based on
> https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c?h=imx_5.4.70_2.3.0
> 
> It's built as part of VIDEO_IMX7_CSI because that's documented to support
> i.MX8M platforms. This driver adds i.MX8MQ support where currently only the
> i.MX8MM platform has been supported.
> 
> Signed-off-by: Martin Kepplinger <martin.kepplinger@puri.sm>
> ---
>  drivers/staging/media/imx/Makefile           |   1 +
>  drivers/staging/media/imx/imx8mq-mipi-csi2.c | 949 +++++++++++++++++++
>  2 files changed, 950 insertions(+)
>  create mode 100644 drivers/staging/media/imx/imx8mq-mipi-csi2.c
> 
> diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile
> index 6ac33275cc97..19c2fc54d424 100644
> --- a/drivers/staging/media/imx/Makefile
> +++ b/drivers/staging/media/imx/Makefile
> @@ -16,3 +16,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o
>  
>  obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
>  obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o
> +obj-$(CONFIG_VIDEO_IMX7_CSI) += imx8mq-mipi-csi2.o
> diff --git a/drivers/staging/media/imx/imx8mq-mipi-csi2.c b/drivers/staging/media/imx/imx8mq-mipi-csi2.c
> new file mode 100644
> index 000000000000..949b3ef7a20a
> --- /dev/null
> +++ b/drivers/staging/media/imx/imx8mq-mipi-csi2.c
> @@ -0,0 +1,949 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Freescale i.MX8MQ SoC series MIPI-CSI2 receiver driver

Maybe they should be called NXP these days :-)

> + *
> + * Copyright (C) 2021 Purism SPC
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/interconnect.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/reset.h>
> +#include <linux/spinlock.h>
> +
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +
> +#define MIPI_CSI2_DRIVER_NAME			"imx8mq-mipi-csi2"
> +#define MIPI_CSI2_SUBDEV_NAME			MIPI_CSI2_DRIVER_NAME
> +
> +#define MIPI_CSI2_PAD_SINK			0
> +#define MIPI_CSI2_PAD_SOURCE			1
> +#define MIPI_CSI2_PADS_NUM			2
> +
> +#define MIPI_CSI2_DEF_PIX_WIDTH			640
> +#define MIPI_CSI2_DEF_PIX_HEIGHT		480
> +
> +/* Register map definition */
> +
> +/* i.MX8MQ CSI-2 controller CSR */
> +#define CSI2RX_CFG_NUM_LANES			0x100
> +#define CSI2RX_CFG_DISABLE_DATA_LANES		0x104
> +#define CSI2RX_BIT_ERR				0x108
> +#define CSI2RX_IRQ_STATUS			0x10c
> +#define CSI2RX_IRQ_MASK				0x110
> +#define CSI2RX_IRQ_MASK_ALL			0x1ff
> +#define CSI2RX_IRQ_MASK_ULPS_STATUS_CHANGE	0x8
> +#define CSI2RX_ULPS_STATUS			0x114
> +#define CSI2RX_PPI_ERRSOT_HS			0x118
> +#define CSI2RX_PPI_ERRSOTSYNC_HS		0x11c
> +#define CSI2RX_PPI_ERRESC			0x120
> +#define CSI2RX_PPI_ERRSYNCESC			0x124
> +#define CSI2RX_PPI_ERRCONTROL			0x128
> +#define CSI2RX_CFG_DISABLE_PAYLOAD_0		0x12c
> +#define CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL	0x188
> +#define CSI2RX_CFG_DISABLE_PAYLOAD_1		0x130
> +
> +enum {
> +	ST_POWERED	= 1,
> +	ST_STREAMING	= 2,
> +	ST_SUSPENDED	= 4,
> +};
> +
> +static const char * const imx8mq_mipi_csi_clk_id[] = {
> +	"core",
> +	"esc",
> +	"ui",
> +};
> +
> +#define CSI2_NUM_CLKS	ARRAY_SIZE(imx8mq_mipi_csi_clk_id)
> +
> +#define	GPR_CSI2_1_RX_ENABLE		BIT(13)
> +#define	GPR_CSI2_1_VID_INTFC_ENB	BIT(12)
> +#define	GPR_CSI2_1_HSEL			BIT(10)
> +#define	GPR_CSI2_1_CONT_CLK_MODE	BIT(8)
> +#define	GPR_CSI2_1_S_PRG_RXHS_SETTLE(x)	(((x) & 0x3f) << 2)
> +
> +/*
> + * The send level configures the number of entries that must accumulate in
> + * the Pixel FIFO before the data will be transferred to the video output.
> + * See https://community.nxp.com/t5/i-MX-Processors/IMX8M-MIPI-CSI-Host-Controller-send-level/m-p/864005/highlight/true#M131704
> + */
> +#define CSI2RX_SEND_LEVEL			64
> +
> +struct csi_state {
> +	struct device *dev;
> +	void __iomem *regs;
> +	struct clk_bulk_data clks[CSI2_NUM_CLKS];
> +	struct reset_control *rst;
> +	struct regulator *mipi_phy_regulator;
> +
> +	struct v4l2_subdev sd;
> +	struct media_pad pads[MIPI_CSI2_PADS_NUM];
> +	struct v4l2_async_notifier notifier;
> +	struct v4l2_subdev *src_sd;
> +
> +	struct v4l2_fwnode_bus_mipi_csi2 bus;
> +
> +	struct mutex lock; /* Protect csi2_fmt, format_mbus, state, hs_settle*/

Missing space before */

> +	const struct csi2_pix_format *csi2_fmt;
> +	struct v4l2_mbus_framefmt format_mbus[MIPI_CSI2_PADS_NUM];
> +	u32 state;
> +	u32 hs_settle;
> +
> +	struct regmap *phy_gpr;
> +	u8 phy_gpr_reg;
> +
> +	struct icc_path			*icc_path;
> +	s32				icc_path_bw;
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Format helpers
> + */
> +
> +struct csi2_pix_format {
> +	u32 code;
> +	u8 width;
> +};
> +
> +static const struct csi2_pix_format imx8mq_mipi_csi_formats[] = {
> +	/* RAW (Bayer and greyscale) formats. */
> +	{
> +		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
> +		.width = 8,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGBRG8_1X8,
> +		.width = 8,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGRBG8_1X8,
> +		.width = 8,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SRGGB8_1X8,
> +		.width = 8,
> +	}, {
> +		.code = MEDIA_BUS_FMT_Y8_1X8,
> +		.width = 8,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.width = 10,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.width = 10,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.width = 10,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.width = 10,
> +	}, {
> +		.code = MEDIA_BUS_FMT_Y10_1X10,
> +		.width = 10,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SBGGR12_1X12,
> +		.width = 12,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGBRG12_1X12,
> +		.width = 12,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGRBG12_1X12,
> +		.width = 12,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SRGGB12_1X12,
> +		.width = 12,
> +	}, {
> +		.code = MEDIA_BUS_FMT_Y12_1X12,
> +		.width = 12,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SBGGR14_1X14,
> +		.width = 14,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGBRG14_1X14,
> +		.width = 14,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SGRBG14_1X14,
> +		.width = 14,
> +	}, {
> +		.code = MEDIA_BUS_FMT_SRGGB14_1X14,
> +		.width = 14,
> +	}, {
> +	/* YUV formats */
> +		.code = MEDIA_BUS_FMT_YUYV8_2X8,
> +		.width = 16,
> +	}, {
> +		.code = MEDIA_BUS_FMT_YUYV8_1X16,
> +		.width = 16,
> +	}
> +};
> +
> +static const struct csi2_pix_format *find_csi2_format(u32 code)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(imx8mq_mipi_csi_formats); i++)
> +		if (code == imx8mq_mipi_csi_formats[i].code)
> +			return &imx8mq_mipi_csi_formats[i];
> +	return NULL;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Hardware configuration
> + */
> +
> +static inline void imx8mq_mipi_csi_write(struct csi_state *state, u32 reg, u32 val)
> +{
> +	writel(val, state->regs + reg);
> +}
> +
> +static int imx8mq_mipi_csi_sw_reset(struct csi_state *state)
> +{
> +	int ret;
> +
> +	ret = reset_control_assert(state->rst);

That's peculiar, is there no need to deassert reset ?

> +	if (ret < 0) {
> +		dev_err(state->dev, "Failed to assert resets: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void imx8mq_mipi_csi_system_enable(struct csi_state *state, int on)
> +{
> +	if (!on) {
> +		imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, 0xf);
> +		return;
> +	}
> +
> +	regmap_update_bits(state->phy_gpr,
> +			   state->phy_gpr_reg,
> +			   0x3fff,
> +			   GPR_CSI2_1_RX_ENABLE |
> +			   GPR_CSI2_1_VID_INTFC_ENB |
> +			   GPR_CSI2_1_HSEL |
> +			   GPR_CSI2_1_CONT_CLK_MODE |
> +			   GPR_CSI2_1_S_PRG_RXHS_SETTLE(state->hs_settle));
> +}
> +
> +static void imx8mq_mipi_csi_set_params(struct csi_state *state)
> +{
> +	int lanes = state->bus.num_data_lanes;
> +
> +	imx8mq_mipi_csi_write(state, CSI2RX_CFG_NUM_LANES, lanes - 1);
> +	imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES,
> +			      (0xf << lanes) & 0xf);
> +	imx8mq_mipi_csi_write(state, CSI2RX_IRQ_MASK, CSI2RX_IRQ_MASK_ALL);
> +	imx8mq_mipi_csi_write(state, 0x180, 1);
> +	/* vid_vc */
> +	imx8mq_mipi_csi_write(state, 0x184, 1);
> +	imx8mq_mipi_csi_write(state, 0x188, CSI2RX_SEND_LEVEL);
> +}
> +
> +static int imx8mq_mipi_csi_clk_enable(struct csi_state *state)
> +{
> +	return clk_bulk_prepare_enable(CSI2_NUM_CLKS, state->clks);
> +}
> +
> +static void imx8mq_mipi_csi_clk_disable(struct csi_state *state)
> +{
> +	clk_bulk_disable_unprepare(CSI2_NUM_CLKS, state->clks);
> +}
> +
> +static int imx8mq_mipi_csi_clk_get(struct csi_state *state)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < CSI2_NUM_CLKS; i++)
> +		state->clks[i].id = imx8mq_mipi_csi_clk_id[i];
> +
> +	return devm_clk_bulk_get(state->dev, CSI2_NUM_CLKS, state->clks);
> +}
> +
> +static int imx8mq_mipi_csi_calc_hs_settle(struct csi_state *state)
> +{
> +	u32 width = state->format_mbus[MIPI_CSI2_PAD_SINK].width;
> +	u32 height = state->format_mbus[MIPI_CSI2_PAD_SINK].height;
> +	s64 link_freq;
> +	u32 lane_rate;
> +
> +	/* Calculate the line rate from the pixel rate. */
> +	link_freq = v4l2_get_link_freq(state->src_sd->ctrl_handler,
> +				       state->csi2_fmt->width,
> +				       state->bus.num_data_lanes * 2);
> +	if (link_freq < 0) {
> +		dev_err(state->dev, "Unable to obtain link frequency: %d\n",
> +			(int)link_freq);
> +		return link_freq;
> +	}
> +
> +	lane_rate = link_freq * 2;
> +	if (lane_rate < 80000000 || lane_rate > 1500000000) {
> +		dev_dbg(state->dev, "Out-of-bound lane rate %u\n", lane_rate);
> +		return -EINVAL;
> +	}
> +
> +	/* https://community.nxp.com/t5/i-MX-Processors/Explenation-for-HS-SETTLE-parameter-in-MIPI-CSI-D-PHY-registers/m-p/764275/highlight/true#M118744 */
> +	if (lane_rate < 250000000)
> +		state->hs_settle = 0xb;
> +	else if (lane_rate < 500000000)
> +		state->hs_settle = 0x8;
> +	else
> +		state->hs_settle = 0x6;

We could possibly compute this value based on the formula from the table
in that page, but maybe that's overkill ? If you want to give it a try,
it would be along those lines.

	/*
	 * The D-PHY specification requires Ths-settle to be in the range
	 * 85ns + 6*UI to 140ns + 10*UI, with the unit interval UI being half
	 * the clock period.
	 *
	 * The Ths-settle value is expressed in the hardware as a multiple of
	 * the Esc clock period:
	 *
	 * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of RxClkInEsc
	 *
	 * Due to the one cycle inaccuracy introduced by rounding, the
	 * documentation recommends picking a value away from the boundaries.
	 * Let's pick the average.
	 */
	esc_clk_rate = clk_get_rate(...);

	min_ths_settle = 85 + 6 * 1000000 / (lane_rate / 1000);
	max_ths_settle = 140 + 10 * 1000000 / (lane_rate / 1000);
	ths_settle = (min_ths_settle + max_ths_settle) / 2;

	state->hs_settle = ths_settle * esc_clk_rate / 1000000000 - 1;

> +
> +	dev_dbg(state->dev, "start stream: %ux%u lane rate %u hs_settle %u\n",
> +		width, height, lane_rate, state->hs_settle);
> +
> +	return 0;
> +}
> +
> +static int imx8mq_mipi_csi_start_stream(struct csi_state *state)
> +{
> +	int ret;
> +
> +	ret = imx8mq_mipi_csi_sw_reset(state);
> +	if (ret)
> +		return ret;
> +
> +	imx8mq_mipi_csi_set_params(state);
> +	imx8mq_mipi_csi_calc_hs_settle(state);
> +	imx8mq_mipi_csi_system_enable(state, true);
> +
> +	return 0;
> +}
> +
> +static void imx8mq_mipi_csi_stop_stream(struct csi_state *state)
> +{
> +	imx8mq_mipi_csi_system_enable(state, false);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 subdev operations
> + */
> +
> +static struct csi_state *mipi_sd_to_csi2_state(struct v4l2_subdev *sdev)
> +{
> +	return container_of(sdev, struct csi_state, sd);
> +}
> +
> +static int imx8mq_mipi_csi_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> +	struct csi_state *state = mipi_sd_to_csi2_state(sd);
> +	int ret;
> +
> +	imx8mq_mipi_csi_write(state, CSI2RX_IRQ_MASK,
> +			      CSI2RX_IRQ_MASK_ULPS_STATUS_CHANGE);
> +
> +	if (enable) {
> +		ret = pm_runtime_get_sync(state->dev);

You can call pm_runtime_resume_and_get() and drop the
pm_runtime_put_noidle() right below.

> +		if (ret < 0) {
> +			pm_runtime_put_noidle(state->dev);
> +			return ret;
> +		}
> +	}
> +
> +	mutex_lock(&state->lock);
> +
> +	if (enable) {
> +		if (state->state & ST_SUSPENDED) {
> +			ret = -EBUSY;
> +			goto unlock;
> +		}
> +
> +		ret = imx8mq_mipi_csi_start_stream(state);
> +		if (ret < 0)
> +			goto unlock;
> +
> +		ret = v4l2_subdev_call(state->src_sd, video, s_stream, 1);
> +		if (ret < 0)
> +			goto unlock;
> +
> +		state->state |= ST_STREAMING;
> +	} else {
> +		v4l2_subdev_call(state->src_sd, video, s_stream, 0);
> +		imx8mq_mipi_csi_stop_stream(state);
> +		state->state &= ~ST_STREAMING;
> +	}
> +
> +unlock:
> +	mutex_unlock(&state->lock);
> +
> +	if (!enable || ret < 0)
> +		pm_runtime_put(state->dev);
> +
> +	return ret;
> +}
> +
> +static struct v4l2_mbus_framefmt *
> +imx8mq_mipi_csi_get_format(struct csi_state *state,
> +			   struct v4l2_subdev_state *sd_state,
> +			   enum v4l2_subdev_format_whence which,
> +			   unsigned int pad)
> +{
> +	if (which == V4L2_SUBDEV_FORMAT_TRY)
> +		return v4l2_subdev_get_try_format(&state->sd, sd_state, pad);
> +
> +	return &state->format_mbus[pad];
> +}
> +
> +static int imx8mq_mipi_csi_init_cfg(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *sd_state)
> +{
> +	struct csi_state *state = mipi_sd_to_csi2_state(sd);
> +	struct v4l2_mbus_framefmt *fmt_sink;
> +	struct v4l2_mbus_framefmt *fmt_source;
> +	enum v4l2_subdev_format_whence which;
> +
> +	which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
> +	fmt_sink = imx8mq_mipi_csi_get_format(state, sd_state, which,
> +					      MIPI_CSI2_PAD_SINK);
> +
> +	fmt_sink->code = MEDIA_BUS_FMT_SGBRG10_1X10;
> +	fmt_sink->width = MIPI_CSI2_DEF_PIX_WIDTH;
> +	fmt_sink->height = MIPI_CSI2_DEF_PIX_HEIGHT;
> +	fmt_sink->field = V4L2_FIELD_NONE;
> +
> +	fmt_sink->colorspace = V4L2_COLORSPACE_RAW;
> +	fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace);
> +	fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace);
> +	fmt_sink->quantization =
> +		V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace,
> +					      fmt_sink->ycbcr_enc);
> +
> +	/*
> +	 * When called from imx8mq_mipi_csi_subdev_init() to initialize the
> +	 * active configuration, sd_state is NULL, which indicates there's no
> +	 * source pad configuration to set.
> +	 */
> +	if (!sd_state)
> +		return 0;

This isn't true anymore, you should set the source pad format in both
cases.

With these small issues addressed,

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> +
> +	fmt_source = imx8mq_mipi_csi_get_format(state, sd_state, which,
> +						MIPI_CSI2_PAD_SOURCE);
> +	*fmt_source = *fmt_sink;
> +
> +	return 0;
> +}
> +
> +static int imx8mq_mipi_csi_get_fmt(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *sd_state,
> +				   struct v4l2_subdev_format *sdformat)
> +{
> +	struct csi_state *state = mipi_sd_to_csi2_state(sd);
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	fmt = imx8mq_mipi_csi_get_format(state, sd_state, sdformat->which,
> +					 sdformat->pad);
> +
> +	mutex_lock(&state->lock);
> +
> +	sdformat->format = *fmt;
> +
> +	mutex_unlock(&state->lock);
> +
> +	return 0;
> +}
> +
> +static int imx8mq_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
> +					  struct v4l2_subdev_state *sd_state,
> +					  struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	struct csi_state *state = mipi_sd_to_csi2_state(sd);
> +
> +	/*
> +	 * We can't transcode in any way, the source format is identical
> +	 * to the sink format.
> +	 */
> +	if (code->pad == MIPI_CSI2_PAD_SOURCE) {
> +		struct v4l2_mbus_framefmt *fmt;
> +
> +		if (code->index > 0)
> +			return -EINVAL;
> +
> +		fmt = imx8mq_mipi_csi_get_format(state, sd_state, code->which,
> +						 code->pad);
> +		code->code = fmt->code;
> +		return 0;
> +	}
> +
> +	if (code->pad != MIPI_CSI2_PAD_SINK)
> +		return -EINVAL;
> +
> +	if (code->index >= ARRAY_SIZE(imx8mq_mipi_csi_formats))
> +		return -EINVAL;
> +
> +	code->code = imx8mq_mipi_csi_formats[code->index].code;
> +
> +	return 0;
> +}
> +
> +static int imx8mq_mipi_csi_set_fmt(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *sd_state,
> +				   struct v4l2_subdev_format *sdformat)
> +{
> +	struct csi_state *state = mipi_sd_to_csi2_state(sd);
> +	struct csi2_pix_format const *csi2_fmt;
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	/*
> +	 * The device can't transcode in any way, the source format can't be
> +	 * modified.
> +	 */
> +	if (sdformat->pad == MIPI_CSI2_PAD_SOURCE)
> +		return imx8mq_mipi_csi_get_fmt(sd, sd_state, sdformat);
> +
> +	if (sdformat->pad != MIPI_CSI2_PAD_SINK)
> +		return -EINVAL;
> +
> +	csi2_fmt = find_csi2_format(sdformat->format.code);
> +	if (!csi2_fmt)
> +		csi2_fmt = &imx8mq_mipi_csi_formats[0];
> +
> +	fmt = imx8mq_mipi_csi_get_format(state, sd_state, sdformat->which,
> +					 sdformat->pad);
> +
> +	mutex_lock(&state->lock);
> +
> +	fmt->code = csi2_fmt->code;
> +	fmt->width = sdformat->format.width;
> +	fmt->height = sdformat->format.height;
> +
> +	sdformat->format = *fmt;
> +
> +	/* Propagate the format from sink to source. */
> +	fmt = imx8mq_mipi_csi_get_format(state, sd_state, sdformat->which,
> +					 MIPI_CSI2_PAD_SOURCE);
> +	*fmt = sdformat->format;
> +
> +	/* Store the CSI2 format descriptor for active formats. */
> +	if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE)
> +		state->csi2_fmt = csi2_fmt;
> +
> +	mutex_unlock(&state->lock);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_video_ops imx8mq_mipi_csi_video_ops = {
> +	.s_stream	= imx8mq_mipi_csi_s_stream,
> +};
> +
> +static const struct v4l2_subdev_pad_ops imx8mq_mipi_csi_pad_ops = {
> +	.init_cfg		= imx8mq_mipi_csi_init_cfg,
> +	.enum_mbus_code		= imx8mq_mipi_csi_enum_mbus_code,
> +	.get_fmt		= imx8mq_mipi_csi_get_fmt,
> +	.set_fmt		= imx8mq_mipi_csi_set_fmt,
> +};
> +
> +static const struct v4l2_subdev_ops imx8mq_mipi_csi_subdev_ops = {
> +	.video	= &imx8mq_mipi_csi_video_ops,
> +	.pad	= &imx8mq_mipi_csi_pad_ops,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Media entity operations
> + */
> +
> +static const struct media_entity_operations imx8mq_mipi_csi_entity_ops = {
> +	.link_validate	= v4l2_subdev_link_validate,
> +	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Async subdev notifier
> + */
> +
> +static struct csi_state *
> +mipi_notifier_to_csi2_state(struct v4l2_async_notifier *n)
> +{
> +	return container_of(n, struct csi_state, notifier);
> +}
> +
> +static int imx8mq_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,
> +					struct v4l2_subdev *sd,
> +					struct v4l2_async_subdev *asd)
> +{
> +	struct csi_state *state = mipi_notifier_to_csi2_state(notifier);
> +	struct media_pad *sink = &state->sd.entity.pads[MIPI_CSI2_PAD_SINK];
> +
> +	state->src_sd = sd;
> +
> +	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
> +					       MEDIA_LNK_FL_IMMUTABLE);
> +}
> +
> +static const struct v4l2_async_notifier_operations imx8mq_mipi_csi_notify_ops = {
> +	.bound = imx8mq_mipi_csi_notify_bound,
> +};
> +
> +static int imx8mq_mipi_csi_async_register(struct csi_state *state)
> +{
> +	struct v4l2_fwnode_endpoint vep = {
> +		.bus_type = V4L2_MBUS_CSI2_DPHY,
> +	};
> +	struct v4l2_async_subdev *asd;
> +	struct fwnode_handle *ep;
> +	unsigned int i;
> +	int ret;
> +
> +	v4l2_async_notifier_init(&state->notifier);
> +
> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(state->dev), 0, 0,
> +					     FWNODE_GRAPH_ENDPOINT_NEXT);
> +	if (!ep)
> +		return -ENOTCONN;
> +
> +	ret = v4l2_fwnode_endpoint_parse(ep, &vep);
> +	if (ret)
> +		goto err_parse;
> +
> +	for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) {
> +		if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) {
> +			dev_err(state->dev,
> +				"data lanes reordering is not supported");
> +			ret = -EINVAL;
> +			goto err_parse;
> +		}
> +	}
> +
> +	state->bus = vep.bus.mipi_csi2;
> +
> +	dev_dbg(state->dev, "data lanes: %d flags: 0x%08x\n",
> +		state->bus.num_data_lanes,
> +		state->bus.flags);
> +
> +	asd = v4l2_async_notifier_add_fwnode_remote_subdev(&state->notifier,
> +							   ep, struct v4l2_async_subdev);
> +	if (IS_ERR(asd)) {
> +		ret = PTR_ERR(asd);
> +		goto err_parse;
> +	}
> +
> +	fwnode_handle_put(ep);
> +
> +	state->notifier.ops = &imx8mq_mipi_csi_notify_ops;
> +
> +	ret = v4l2_async_subdev_notifier_register(&state->sd, &state->notifier);
> +	if (ret)
> +		return ret;
> +
> +	return v4l2_async_register_subdev(&state->sd);
> +
> +err_parse:
> +	fwnode_handle_put(ep);
> +
> +	return ret;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Suspend/resume
> + */
> +
> +static int imx8mq_mipi_csi_pm_suspend(struct device *dev, bool runtime)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct csi_state *state = mipi_sd_to_csi2_state(sd);
> +	int ret = 0;
> +
> +	mutex_lock(&state->lock);
> +
> +	if (state->state & ST_POWERED) {
> +		imx8mq_mipi_csi_stop_stream(state);
> +		imx8mq_mipi_csi_clk_disable(state);
> +		state->state &= ~ST_POWERED;
> +		if (!runtime)
> +			state->state |= ST_SUSPENDED;
> +	}
> +
> +	mutex_unlock(&state->lock);
> +
> +	ret = icc_set_bw(state->icc_path, 0, 0);
> +	if (ret)
> +		dev_err(dev, "icc_set_bw failed with %d\n", ret);
> +
> +	return ret ? -EAGAIN : 0;
> +}
> +
> +static int imx8mq_mipi_csi_pm_resume(struct device *dev, bool runtime)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(dev);
> +	struct csi_state *state = mipi_sd_to_csi2_state(sd);
> +	int ret = 0;
> +
> +	ret = icc_set_bw(state->icc_path, 0, state->icc_path_bw);
> +	if (ret) {
> +		dev_err(dev, "icc_set_bw failed with %d\n", ret);
> +		return ret;
> +	}
> +
> +	mutex_lock(&state->lock);
> +
> +	if (!runtime && !(state->state & ST_SUSPENDED))
> +		goto unlock;
> +
> +	if (!(state->state & ST_POWERED)) {
> +		state->state |= ST_POWERED;
> +		ret = imx8mq_mipi_csi_clk_enable(state);
> +	}
> +	if (state->state & ST_STREAMING) {
> +		ret = imx8mq_mipi_csi_start_stream(state);
> +		if (ret)
> +			goto unlock;
> +	}
> +
> +	state->state &= ~ST_SUSPENDED;
> +
> +unlock:
> +	mutex_unlock(&state->lock);
> +
> +	return ret ? -EAGAIN : 0;
> +}
> +
> +static int __maybe_unused imx8mq_mipi_csi_suspend(struct device *dev)
> +{
> +	return imx8mq_mipi_csi_pm_suspend(dev, false);
> +}
> +
> +static int __maybe_unused imx8mq_mipi_csi_resume(struct device *dev)
> +{
> +	return imx8mq_mipi_csi_pm_resume(dev, false);
> +}
> +
> +static int __maybe_unused imx8mq_mipi_csi_runtime_suspend(struct device *dev)
> +{
> +	return imx8mq_mipi_csi_pm_suspend(dev, true);
> +}
> +
> +static int __maybe_unused imx8mq_mipi_csi_runtime_resume(struct device *dev)
> +{
> +	return imx8mq_mipi_csi_pm_resume(dev, true);
> +}
> +
> +static const struct dev_pm_ops imx8mq_mipi_csi_pm_ops = {
> +	SET_RUNTIME_PM_OPS(imx8mq_mipi_csi_runtime_suspend,
> +			   imx8mq_mipi_csi_runtime_resume,
> +			   NULL)
> +	SET_SYSTEM_SLEEP_PM_OPS(imx8mq_mipi_csi_suspend, imx8mq_mipi_csi_resume)
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Probe/remove & platform driver
> + */
> +
> +static int imx8mq_mipi_csi_subdev_init(struct csi_state *state)
> +{
> +	struct v4l2_subdev *sd = &state->sd;
> +
> +	v4l2_subdev_init(sd, &imx8mq_mipi_csi_subdev_ops);
> +	sd->owner = THIS_MODULE;
> +	snprintf(sd->name, sizeof(sd->name), "%s %s",
> +		 MIPI_CSI2_SUBDEV_NAME, dev_name(state->dev));
> +
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +
> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	sd->entity.ops = &imx8mq_mipi_csi_entity_ops;
> +
> +	sd->dev = state->dev;
> +
> +	state->csi2_fmt = &imx8mq_mipi_csi_formats[0];
> +	imx8mq_mipi_csi_init_cfg(sd, NULL);
> +
> +	state->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK
> +					 | MEDIA_PAD_FL_MUST_CONNECT;
> +	state->pads[MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE
> +					   | MEDIA_PAD_FL_MUST_CONNECT;
> +	return media_entity_pads_init(&sd->entity, MIPI_CSI2_PADS_NUM,
> +				      state->pads);
> +}
> +
> +static void imx8mq_mipi_csi_release_icc(struct platform_device *pdev)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev);
> +	struct csi_state *state = mipi_sd_to_csi2_state(sd);
> +
> +	icc_put(state->icc_path);
> +}
> +
> +static int imx8mq_mipi_csi_init_icc(struct platform_device *pdev)
> +{
> +	struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev);
> +	struct csi_state *state = mipi_sd_to_csi2_state(sd);
> +
> +	/* Optional interconnect request */
> +	state->icc_path = of_icc_get(&pdev->dev, "dram");
> +	if (IS_ERR_OR_NULL(state->icc_path))
> +		return PTR_ERR_OR_ZERO(state->icc_path);
> +
> +	state->icc_path_bw = MBps_to_icc(700);
> +
> +	return 0;
> +}
> +
> +static int imx8mq_mipi_csi_parse_dt(struct csi_state *state)
> +{
> +	struct device *dev = state->dev;
> +	struct device_node *np = state->dev->of_node;
> +	struct device_node *node;
> +	phandle ph;
> +	u32 out_val[2];
> +	int ret = 0;
> +
> +	state->rst = devm_reset_control_array_get_exclusive(dev);
> +	if (IS_ERR(state->rst)) {
> +		dev_err(dev, "Failed to get reset: %pe\n", state->rst);
> +		return PTR_ERR(state->rst);
> +	}
> +
> +	ret = of_property_read_u32_array(np, "fsl,mipi-phy-gpr", out_val,
> +					 ARRAY_SIZE(out_val));
> +	if (ret) {
> +		dev_err(dev, "no fsl,mipi-phy-gpr property found: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ph = *out_val;
> +
> +	node = of_find_node_by_phandle(ph);
> +	if (!node) {
> +		dev_err(dev, "Error finding node by phandle\n");
> +		return -ENODEV;
> +	}
> +	state->phy_gpr = syscon_node_to_regmap(node);
> +	of_node_put(node);
> +	if (IS_ERR(state->phy_gpr)) {
> +		dev_err(dev, "failed to get gpr regmap: %pe\n", state->phy_gpr);
> +		return PTR_ERR(state->phy_gpr);
> +	}
> +
> +	state->phy_gpr_reg = out_val[1];
> +	dev_dbg(dev, "phy gpr register set to 0x%x\n", state->phy_gpr_reg);
> +
> +	return ret;
> +}
> +
> +static int imx8mq_mipi_csi_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct csi_state *state;
> +	int ret;
> +
> +	state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
> +	if (!state)
> +		return -ENOMEM;
> +
> +	state->dev = dev;
> +
> +	ret = imx8mq_mipi_csi_parse_dt(state);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to parse device tree: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Acquire resources. */
> +	state->regs = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(state->regs))
> +		return PTR_ERR(state->regs);
> +
> +	ret = imx8mq_mipi_csi_clk_get(state);
> +	if (ret < 0)
> +		return ret;
> +
> +	platform_set_drvdata(pdev, &state->sd);
> +
> +	mutex_init(&state->lock);
> +
> +	ret = imx8mq_mipi_csi_subdev_init(state);
> +	if (ret < 0)
> +		goto mutex;
> +
> +	ret = imx8mq_mipi_csi_init_icc(pdev);
> +	if (ret)
> +		goto mutex;
> +
> +	/* Enable runtime PM. */
> +	pm_runtime_enable(dev);
> +	if (!pm_runtime_enabled(dev)) {
> +		ret = imx8mq_mipi_csi_pm_resume(dev, true);
> +		if (ret < 0)
> +			goto icc;
> +	}
> +
> +	ret = imx8mq_mipi_csi_async_register(state);
> +	if (ret < 0)
> +		goto cleanup;
> +
> +	return 0;
> +
> +cleanup:
> +	pm_runtime_disable(&pdev->dev);
> +	imx8mq_mipi_csi_pm_suspend(&pdev->dev, true);
> +
> +	media_entity_cleanup(&state->sd.entity);
> +	v4l2_async_notifier_unregister(&state->notifier);
> +	v4l2_async_notifier_cleanup(&state->notifier);
> +	v4l2_async_unregister_subdev(&state->sd);
> +icc:
> +	imx8mq_mipi_csi_release_icc(pdev);
> +mutex:
> +	mutex_destroy(&state->lock);
> +
> +	return ret;
> +}
> +
> +static int imx8mq_mipi_csi_remove(struct platform_device *pdev)
> +{
> +	struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> +	struct csi_state *state = mipi_sd_to_csi2_state(sd);
> +
> +	v4l2_async_notifier_unregister(&state->notifier);
> +	v4l2_async_notifier_cleanup(&state->notifier);
> +	v4l2_async_unregister_subdev(&state->sd);
> +
> +	pm_runtime_disable(&pdev->dev);
> +	imx8mq_mipi_csi_pm_suspend(&pdev->dev, true);
> +	media_entity_cleanup(&state->sd.entity);
> +	mutex_destroy(&state->lock);
> +	pm_runtime_set_suspended(&pdev->dev);
> +	imx8mq_mipi_csi_release_icc(pdev);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id imx8mq_mipi_csi_of_match[] = {
> +	{ .compatible = "fsl,imx8mq-mipi-csi2", },
> +	{ /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, imx8mq_mipi_csi_of_match);
> +
> +static struct platform_driver imx8mq_mipi_csi_driver = {
> +	.probe		= imx8mq_mipi_csi_probe,
> +	.remove		= imx8mq_mipi_csi_remove,
> +	.driver		= {
> +		.of_match_table = imx8mq_mipi_csi_of_match,
> +		.name		= MIPI_CSI2_DRIVER_NAME,
> +		.pm		= &imx8mq_mipi_csi_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(imx8mq_mipi_csi_driver);
> +
> +MODULE_DESCRIPTION("i.MX8MQ MIPI CSI-2 receiver driver");
> +MODULE_AUTHOR("Martin Kepplinger <martin.kepplinger@puri.sm>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:imx8mq-mipi-csi2");
Martin Kepplinger July 15, 2021, 6:49 a.m. UTC | #2
Am Mittwoch, dem 14.07.2021 um 21:24 +0300 schrieb Laurent Pinchart:
> Hi Martin,
> 
> Thank you for the patch.

thank you for reviewing.

> 
> On Wed, Jul 14, 2021 at 01:19:30PM +0200, Martin Kepplinger wrote:
> > Add a driver to support the i.MX8MQ MIPI CSI receiver. The hardware
> > side
> > is based on
> >  
> > https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c?h=imx_5.4.70_2.3.0
> > 
> > It's built as part of VIDEO_IMX7_CSI because that's documented to
> > support
> > i.MX8M platforms. This driver adds i.MX8MQ support where currently
> > only the
> > i.MX8MM platform has been supported.
> > 
> > Signed-off-by: Martin Kepplinger <martin.kepplinger@puri.sm>
> > ---
> >  drivers/staging/media/imx/Makefile           |   1 +
> >  drivers/staging/media/imx/imx8mq-mipi-csi2.c | 949
> > +++++++++++++++++++
> >  2 files changed, 950 insertions(+)
> >  create mode 100644 drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > 
> > diff --git a/drivers/staging/media/imx/Makefile
> > b/drivers/staging/media/imx/Makefile
> > index 6ac33275cc97..19c2fc54d424 100644
> > --- a/drivers/staging/media/imx/Makefile
> > +++ b/drivers/staging/media/imx/Makefile
> > @@ -16,3 +16,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o
> >  
> >  obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
> >  obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o
> > +obj-$(CONFIG_VIDEO_IMX7_CSI) += imx8mq-mipi-csi2.o
> > diff --git a/drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > b/drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > new file mode 100644
> > index 000000000000..949b3ef7a20a
> > --- /dev/null
> > +++ b/drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > @@ -0,0 +1,949 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Freescale i.MX8MQ SoC series MIPI-CSI2 receiver driver
> 
> Maybe they should be called NXP these days :-)
> 
> > + *
> > + * Copyright (C) 2021 Purism SPC
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/errno.h>
> > +#include <linux/interconnect.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/io.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/syscon.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/of.h>
> > +#include <linux/of_device.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/regmap.h>
> > +#include <linux/regulator/consumer.h>
> > +#include <linux/reset.h>
> > +#include <linux/spinlock.h>
> > +
> > +#include <media/v4l2-common.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-mc.h>
> > +#include <media/v4l2-subdev.h>
> > +
> > +#define MIPI_CSI2_DRIVER_NAME                  "imx8mq-mipi-csi2"
> > +#define
> > MIPI_CSI2_SUBDEV_NAME                  MIPI_CSI2_DRIVER_NAME
> > +
> > +#define MIPI_CSI2_PAD_SINK                     0
> > +#define MIPI_CSI2_PAD_SOURCE                   1
> > +#define MIPI_CSI2_PADS_NUM                     2
> > +
> > +#define MIPI_CSI2_DEF_PIX_WIDTH                        640
> > +#define MIPI_CSI2_DEF_PIX_HEIGHT               480
> > +
> > +/* Register map definition */
> > +
> > +/* i.MX8MQ CSI-2 controller CSR */
> > +#define CSI2RX_CFG_NUM_LANES                   0x100
> > +#define CSI2RX_CFG_DISABLE_DATA_LANES          0x104
> > +#define CSI2RX_BIT_ERR                         0x108
> > +#define CSI2RX_IRQ_STATUS                      0x10c
> > +#define CSI2RX_IRQ_MASK                                0x110
> > +#define CSI2RX_IRQ_MASK_ALL                    0x1ff
> > +#define CSI2RX_IRQ_MASK_ULPS_STATUS_CHANGE     0x8
> > +#define CSI2RX_ULPS_STATUS                     0x114
> > +#define CSI2RX_PPI_ERRSOT_HS                   0x118
> > +#define CSI2RX_PPI_ERRSOTSYNC_HS               0x11c
> > +#define CSI2RX_PPI_ERRESC                      0x120
> > +#define CSI2RX_PPI_ERRSYNCESC                  0x124
> > +#define CSI2RX_PPI_ERRCONTROL                  0x128
> > +#define CSI2RX_CFG_DISABLE_PAYLOAD_0           0x12c
> > +#define CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL       0x188
> > +#define CSI2RX_CFG_DISABLE_PAYLOAD_1           0x130
> > +
> > +enum {
> > +       ST_POWERED      = 1,
> > +       ST_STREAMING    = 2,
> > +       ST_SUSPENDED    = 4,
> > +};
> > +
> > +static const char * const imx8mq_mipi_csi_clk_id[] = {
> > +       "core",
> > +       "esc",
> > +       "ui",
> > +};
> > +
> > +#define CSI2_NUM_CLKS  ARRAY_SIZE(imx8mq_mipi_csi_clk_id)
> > +
> > +#define        GPR_CSI2_1_RX_ENABLE            BIT(13)
> > +#define        GPR_CSI2_1_VID_INTFC_ENB        BIT(12)
> > +#define        GPR_CSI2_1_HSEL                 BIT(10)
> > +#define        GPR_CSI2_1_CONT_CLK_MODE        BIT(8)
> > +#define        GPR_CSI2_1_S_PRG_RXHS_SETTLE(x) (((x) & 0x3f) << 2)
> > +
> > +/*
> > + * The send level configures the number of entries that must
> > accumulate in
> > + * the Pixel FIFO before the data will be transferred to the video
> > output.
> > + * See  
> > https://community.nxp.com/t5/i-MX-Processors/IMX8M-MIPI-CSI-Host-Controller-send-level/m-p/864005/highlight/true#M131704
> > + */
> > +#define CSI2RX_SEND_LEVEL                      64
> > +
> > +struct csi_state {
> > +       struct device *dev;
> > +       void __iomem *regs;
> > +       struct clk_bulk_data clks[CSI2_NUM_CLKS];
> > +       struct reset_control *rst;
> > +       struct regulator *mipi_phy_regulator;
> > +
> > +       struct v4l2_subdev sd;
> > +       struct media_pad pads[MIPI_CSI2_PADS_NUM];
> > +       struct v4l2_async_notifier notifier;
> > +       struct v4l2_subdev *src_sd;
> > +
> > +       struct v4l2_fwnode_bus_mipi_csi2 bus;
> > +
> > +       struct mutex lock; /* Protect csi2_fmt, format_mbus, state,
> > hs_settle*/
> 
> Missing space before */
> 
> > +       const struct csi2_pix_format *csi2_fmt;
> > +       struct v4l2_mbus_framefmt format_mbus[MIPI_CSI2_PADS_NUM];
> > +       u32 state;
> > +       u32 hs_settle;
> > +
> > +       struct regmap *phy_gpr;
> > +       u8 phy_gpr_reg;
> > +
> > +       struct icc_path                 *icc_path;
> > +       s32                             icc_path_bw;
> > +};
> > +
> > +/* ---------------------------------------------------------------
> > --------------
> > + * Format helpers
> > + */
> > +
> > +struct csi2_pix_format {
> > +       u32 code;
> > +       u8 width;
> > +};
> > +
> > +static const struct csi2_pix_format imx8mq_mipi_csi_formats[] = {
> > +       /* RAW (Bayer and greyscale) formats. */
> > +       {
> > +               .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> > +               .width = 8,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> > +               .width = 8,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> > +               .width = 8,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> > +               .width = 8,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_Y8_1X8,
> > +               .width = 8,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SBGGR10_1X10,
> > +               .width = 10,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGBRG10_1X10,
> > +               .width = 10,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGRBG10_1X10,
> > +               .width = 10,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SRGGB10_1X10,
> > +               .width = 10,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_Y10_1X10,
> > +               .width = 10,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SBGGR12_1X12,
> > +               .width = 12,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGBRG12_1X12,
> > +               .width = 12,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGRBG12_1X12,
> > +               .width = 12,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SRGGB12_1X12,
> > +               .width = 12,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_Y12_1X12,
> > +               .width = 12,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SBGGR14_1X14,
> > +               .width = 14,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGBRG14_1X14,
> > +               .width = 14,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGRBG14_1X14,
> > +               .width = 14,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SRGGB14_1X14,
> > +               .width = 14,
> > +       }, {
> > +       /* YUV formats */
> > +               .code = MEDIA_BUS_FMT_YUYV8_2X8,
> > +               .width = 16,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_YUYV8_1X16,
> > +               .width = 16,
> > +       }
> > +};
> > +
> > +static const struct csi2_pix_format *find_csi2_format(u32 code)
> > +{
> > +       unsigned int i;
> > +
> > +       for (i = 0; i < ARRAY_SIZE(imx8mq_mipi_csi_formats); i++)
> > +               if (code == imx8mq_mipi_csi_formats[i].code)
> > +                       return &imx8mq_mipi_csi_formats[i];
> > +       return NULL;
> > +}
> > +
> > +/* ---------------------------------------------------------------
> > --------------
> > + * Hardware configuration
> > + */
> > +
> > +static inline void imx8mq_mipi_csi_write(struct csi_state *state,
> > u32 reg, u32 val)
> > +{
> > +       writel(val, state->regs + reg);
> > +}
> > +
> > +static int imx8mq_mipi_csi_sw_reset(struct csi_state *state)
> > +{
> > +       int ret;
> > +
> > +       ret = reset_control_assert(state->rst);
> 
> That's peculiar, is there no need to deassert reset ?

I tried different things here that would look more intuitive, but in
the end only this worked, which is directly taken from
https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c?h=imx_5.4.70_2.3.0#n105
(actual register value read from DT) that results in exactly the same
register bits set like this assertation.
Martin Kepplinger July 15, 2021, 7:37 a.m. UTC | #3
Am Mittwoch, dem 14.07.2021 um 21:24 +0300 schrieb Laurent Pinchart:
> Hi Martin,
> 
> Thank you for the patch.
> 
> On Wed, Jul 14, 2021 at 01:19:30PM +0200, Martin Kepplinger wrote:
> > Add a driver to support the i.MX8MQ MIPI CSI receiver. The hardware
> > side
> > is based on
> > https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c?h=imx_5.4.70_2.3.0
> > 
> > It's built as part of VIDEO_IMX7_CSI because that's documented to
> > support
> > i.MX8M platforms. This driver adds i.MX8MQ support where currently
> > only the
> > i.MX8MM platform has been supported.
> > 
> > Signed-off-by: Martin Kepplinger <martin.kepplinger@puri.sm>
> > ---
> >  drivers/staging/media/imx/Makefile           |   1 +
> >  drivers/staging/media/imx/imx8mq-mipi-csi2.c | 949
> > +++++++++++++++++++
> >  2 files changed, 950 insertions(+)
> >  create mode 100644 drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > 
> > diff --git a/drivers/staging/media/imx/Makefile
> > b/drivers/staging/media/imx/Makefile
> > index 6ac33275cc97..19c2fc54d424 100644
> > --- a/drivers/staging/media/imx/Makefile
> > +++ b/drivers/staging/media/imx/Makefile
> > @@ -16,3 +16,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o
> >  
> >  obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
> >  obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o
> > +obj-$(CONFIG_VIDEO_IMX7_CSI) += imx8mq-mipi-csi2.o
> > diff --git a/drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > b/drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > new file mode 100644
> > index 000000000000..949b3ef7a20a
> > --- /dev/null
> > +++ b/drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > @@ -0,0 +1,949 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Freescale i.MX8MQ SoC series MIPI-CSI2 receiver driver
> 
> Maybe they should be called NXP these days :-)
> 
> > + *
> > + * Copyright (C) 2021 Purism SPC
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/errno.h>
> > +#include <linux/interconnect.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/io.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/syscon.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/of.h>
> > +#include <linux/of_device.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/regmap.h>
> > +#include <linux/regulator/consumer.h>
> > +#include <linux/reset.h>
> > +#include <linux/spinlock.h>
> > +
> > +#include <media/v4l2-common.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-mc.h>
> > +#include <media/v4l2-subdev.h>
> > +
> > +#define MIPI_CSI2_DRIVER_NAME                  "imx8mq-mipi-csi2"
> > +#define
> > MIPI_CSI2_SUBDEV_NAME                  MIPI_CSI2_DRIVER_NAME
> > +
> > +#define MIPI_CSI2_PAD_SINK                     0
> > +#define MIPI_CSI2_PAD_SOURCE                   1
> > +#define MIPI_CSI2_PADS_NUM                     2
> > +
> > +#define MIPI_CSI2_DEF_PIX_WIDTH                        640
> > +#define MIPI_CSI2_DEF_PIX_HEIGHT               480
> > +
> > +/* Register map definition */
> > +
> > +/* i.MX8MQ CSI-2 controller CSR */
> > +#define CSI2RX_CFG_NUM_LANES                   0x100
> > +#define CSI2RX_CFG_DISABLE_DATA_LANES          0x104
> > +#define CSI2RX_BIT_ERR                         0x108
> > +#define CSI2RX_IRQ_STATUS                      0x10c
> > +#define CSI2RX_IRQ_MASK                                0x110
> > +#define CSI2RX_IRQ_MASK_ALL                    0x1ff
> > +#define CSI2RX_IRQ_MASK_ULPS_STATUS_CHANGE     0x8
> > +#define CSI2RX_ULPS_STATUS                     0x114
> > +#define CSI2RX_PPI_ERRSOT_HS                   0x118
> > +#define CSI2RX_PPI_ERRSOTSYNC_HS               0x11c
> > +#define CSI2RX_PPI_ERRESC                      0x120
> > +#define CSI2RX_PPI_ERRSYNCESC                  0x124
> > +#define CSI2RX_PPI_ERRCONTROL                  0x128
> > +#define CSI2RX_CFG_DISABLE_PAYLOAD_0           0x12c
> > +#define CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL       0x188
> > +#define CSI2RX_CFG_DISABLE_PAYLOAD_1           0x130
> > +
> > +enum {
> > +       ST_POWERED      = 1,
> > +       ST_STREAMING    = 2,
> > +       ST_SUSPENDED    = 4,
> > +};
> > +
> > +static const char * const imx8mq_mipi_csi_clk_id[] = {
> > +       "core",
> > +       "esc",
> > +       "ui",
> > +};
> > +
> > +#define CSI2_NUM_CLKS  ARRAY_SIZE(imx8mq_mipi_csi_clk_id)
> > +
> > +#define        GPR_CSI2_1_RX_ENABLE            BIT(13)
> > +#define        GPR_CSI2_1_VID_INTFC_ENB        BIT(12)
> > +#define        GPR_CSI2_1_HSEL                 BIT(10)
> > +#define        GPR_CSI2_1_CONT_CLK_MODE        BIT(8)
> > +#define        GPR_CSI2_1_S_PRG_RXHS_SETTLE(x) (((x) & 0x3f) << 2)
> > +
> > +/*
> > + * The send level configures the number of entries that must
> > accumulate in
> > + * the Pixel FIFO before the data will be transferred to the video
> > output.
> > + * See 
> > https://community.nxp.com/t5/i-MX-Processors/IMX8M-MIPI-CSI-Host-Controller-send-level/m-p/864005/highlight/true#M131704
> > + */
> > +#define CSI2RX_SEND_LEVEL                      64
> > +
> > +struct csi_state {
> > +       struct device *dev;
> > +       void __iomem *regs;
> > +       struct clk_bulk_data clks[CSI2_NUM_CLKS];
> > +       struct reset_control *rst;
> > +       struct regulator *mipi_phy_regulator;
> > +
> > +       struct v4l2_subdev sd;
> > +       struct media_pad pads[MIPI_CSI2_PADS_NUM];
> > +       struct v4l2_async_notifier notifier;
> > +       struct v4l2_subdev *src_sd;
> > +
> > +       struct v4l2_fwnode_bus_mipi_csi2 bus;
> > +
> > +       struct mutex lock; /* Protect csi2_fmt, format_mbus, state,
> > hs_settle*/
> 
> Missing space before */
> 
> > +       const struct csi2_pix_format *csi2_fmt;
> > +       struct v4l2_mbus_framefmt format_mbus[MIPI_CSI2_PADS_NUM];
> > +       u32 state;
> > +       u32 hs_settle;
> > +
> > +       struct regmap *phy_gpr;
> > +       u8 phy_gpr_reg;
> > +
> > +       struct icc_path                 *icc_path;
> > +       s32                             icc_path_bw;
> > +};
> > +
> > +/* ---------------------------------------------------------------
> > --------------
> > + * Format helpers
> > + */
> > +
> > +struct csi2_pix_format {
> > +       u32 code;
> > +       u8 width;
> > +};
> > +
> > +static const struct csi2_pix_format imx8mq_mipi_csi_formats[] = {
> > +       /* RAW (Bayer and greyscale) formats. */
> > +       {
> > +               .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> > +               .width = 8,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> > +               .width = 8,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> > +               .width = 8,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> > +               .width = 8,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_Y8_1X8,
> > +               .width = 8,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SBGGR10_1X10,
> > +               .width = 10,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGBRG10_1X10,
> > +               .width = 10,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGRBG10_1X10,
> > +               .width = 10,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SRGGB10_1X10,
> > +               .width = 10,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_Y10_1X10,
> > +               .width = 10,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SBGGR12_1X12,
> > +               .width = 12,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGBRG12_1X12,
> > +               .width = 12,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGRBG12_1X12,
> > +               .width = 12,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SRGGB12_1X12,
> > +               .width = 12,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_Y12_1X12,
> > +               .width = 12,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SBGGR14_1X14,
> > +               .width = 14,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGBRG14_1X14,
> > +               .width = 14,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SGRBG14_1X14,
> > +               .width = 14,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_SRGGB14_1X14,
> > +               .width = 14,
> > +       }, {
> > +       /* YUV formats */
> > +               .code = MEDIA_BUS_FMT_YUYV8_2X8,
> > +               .width = 16,
> > +       }, {
> > +               .code = MEDIA_BUS_FMT_YUYV8_1X16,
> > +               .width = 16,
> > +       }
> > +};
> > +
> > +static const struct csi2_pix_format *find_csi2_format(u32 code)
> > +{
> > +       unsigned int i;
> > +
> > +       for (i = 0; i < ARRAY_SIZE(imx8mq_mipi_csi_formats); i++)
> > +               if (code == imx8mq_mipi_csi_formats[i].code)
> > +                       return &imx8mq_mipi_csi_formats[i];
> > +       return NULL;
> > +}
> > +
> > +/* ---------------------------------------------------------------
> > --------------
> > + * Hardware configuration
> > + */
> > +
> > +static inline void imx8mq_mipi_csi_write(struct csi_state *state,
> > u32 reg, u32 val)
> > +{
> > +       writel(val, state->regs + reg);
> > +}
> > +
> > +static int imx8mq_mipi_csi_sw_reset(struct csi_state *state)
> > +{
> > +       int ret;
> > +
> > +       ret = reset_control_assert(state->rst);
> 
> That's peculiar, is there no need to deassert reset ?
> 
> > +       if (ret < 0) {
> > +               dev_err(state->dev, "Failed to assert resets:
> > %d\n", ret);
> > +               return ret;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static void imx8mq_mipi_csi_system_enable(struct csi_state *state,
> > int on)
> > +{
> > +       if (!on) {
> > +               imx8mq_mipi_csi_write(state,
> > CSI2RX_CFG_DISABLE_DATA_LANES, 0xf);
> > +               return;
> > +       }
> > +
> > +       regmap_update_bits(state->phy_gpr,
> > +                          state->phy_gpr_reg,
> > +                          0x3fff,
> > +                          GPR_CSI2_1_RX_ENABLE |
> > +                          GPR_CSI2_1_VID_INTFC_ENB |
> > +                          GPR_CSI2_1_HSEL |
> > +                          GPR_CSI2_1_CONT_CLK_MODE |
> > +                          GPR_CSI2_1_S_PRG_RXHS_SETTLE(state-
> > >hs_settle));
> > +}
> > +
> > +static void imx8mq_mipi_csi_set_params(struct csi_state *state)
> > +{
> > +       int lanes = state->bus.num_data_lanes;
> > +
> > +       imx8mq_mipi_csi_write(state, CSI2RX_CFG_NUM_LANES, lanes -
> > 1);
> > +       imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES,
> > +                             (0xf << lanes) & 0xf);
> > +       imx8mq_mipi_csi_write(state, CSI2RX_IRQ_MASK,
> > CSI2RX_IRQ_MASK_ALL);
> > +       imx8mq_mipi_csi_write(state, 0x180, 1);
> > +       /* vid_vc */
> > +       imx8mq_mipi_csi_write(state, 0x184, 1);
> > +       imx8mq_mipi_csi_write(state, 0x188, CSI2RX_SEND_LEVEL);
> > +}
> > +
> > +static int imx8mq_mipi_csi_clk_enable(struct csi_state *state)
> > +{
> > +       return clk_bulk_prepare_enable(CSI2_NUM_CLKS, state->clks);
> > +}
> > +
> > +static void imx8mq_mipi_csi_clk_disable(struct csi_state *state)
> > +{
> > +       clk_bulk_disable_unprepare(CSI2_NUM_CLKS, state->clks);
> > +}
> > +
> > +static int imx8mq_mipi_csi_clk_get(struct csi_state *state)
> > +{
> > +       unsigned int i;
> > +
> > +       for (i = 0; i < CSI2_NUM_CLKS; i++)
> > +               state->clks[i].id = imx8mq_mipi_csi_clk_id[i];
> > +
> > +       return devm_clk_bulk_get(state->dev, CSI2_NUM_CLKS, state-
> > >clks);
> > +}
> > +
> > +static int imx8mq_mipi_csi_calc_hs_settle(struct csi_state *state)
> > +{
> > +       u32 width = state->format_mbus[MIPI_CSI2_PAD_SINK].width;
> > +       u32 height = state->format_mbus[MIPI_CSI2_PAD_SINK].height;
> > +       s64 link_freq;
> > +       u32 lane_rate;
> > +
> > +       /* Calculate the line rate from the pixel rate. */
> > +       link_freq = v4l2_get_link_freq(state->src_sd->ctrl_handler,
> > +                                      state->csi2_fmt->width,
> > +                                      state->bus.num_data_lanes *
> > 2);
> > +       if (link_freq < 0) {
> > +               dev_err(state->dev, "Unable to obtain link
> > frequency: %d\n",
> > +                       (int)link_freq);
> > +               return link_freq;
> > +       }
> > +
> > +       lane_rate = link_freq * 2;
> > +       if (lane_rate < 80000000 || lane_rate > 1500000000) {
> > +               dev_dbg(state->dev, "Out-of-bound lane rate %u\n",
> > lane_rate);
> > +               return -EINVAL;
> > +       }
> > +
> > +       /* 
> > https://community.nxp.com/t5/i-MX-Processors/Explenation-for-HS-SETTLE-parameter-in-MIPI-CSI-D-PHY-registers/m-p/764275/highlight/true#M118744
> >  */
> > +       if (lane_rate < 250000000)
> > +               state->hs_settle = 0xb;
> > +       else if (lane_rate < 500000000)
> > +               state->hs_settle = 0x8;
> > +       else
> > +               state->hs_settle = 0x6;
> 
> We could possibly compute this value based on the formula from the
> table
> in that page, but maybe that's overkill ? If you want to give it a
> try,
> it would be along those lines.
> 
>         /*
>          * The D-PHY specification requires Ths-settle to be in the
> range
>          * 85ns + 6*UI to 140ns + 10*UI, with the unit interval UI
> being half
>          * the clock period.
>          *
>          * The Ths-settle value is expressed in the hardware as a
> multiple of
>          * the Esc clock period:
>          *
>          * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of RxClkInEsc
>          *
>          * Due to the one cycle inaccuracy introduced by rounding,
> the
>          * documentation recommends picking a value away from the
> boundaries.
>          * Let's pick the average.
>          */
>         esc_clk_rate = clk_get_rate(...);
> 
>         min_ths_settle = 85 + 6 * 1000000 / (lane_rate / 1000);
>         max_ths_settle = 140 + 10 * 1000000 / (lane_rate / 1000);
>         ths_settle = (min_ths_settle + max_ths_settle) / 2;
> 
>         state->hs_settle = ths_settle * esc_clk_rate / 1000000000 -
> 1;
> 

I experimented a bit but would like to leave this as a task for later
if that's ok. it's correct and simple now. also, using clks[i].clk
based on the name string would feel better to submit seperately later.

>
Laurent Pinchart July 15, 2021, 9:47 p.m. UTC | #4
Hi Martin,

On Thu, Jul 15, 2021 at 08:49:51AM +0200, Martin Kepplinger wrote:
> Am Mittwoch, dem 14.07.2021 um 21:24 +0300 schrieb Laurent Pinchart:
> > Hi Martin,
> > 
> > Thank you for the patch.
> 
> thank you for reviewing.
> 
> > On Wed, Jul 14, 2021 at 01:19:30PM +0200, Martin Kepplinger wrote:
> > > Add a driver to support the i.MX8MQ MIPI CSI receiver. The hardware
> > > side
> > > is based on
> > >  
> > > https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c?h=imx_5.4.70_2.3.0
> > > 
> > > It's built as part of VIDEO_IMX7_CSI because that's documented to
> > > support
> > > i.MX8M platforms. This driver adds i.MX8MQ support where currently
> > > only the
> > > i.MX8MM platform has been supported.
> > > 
> > > Signed-off-by: Martin Kepplinger <martin.kepplinger@puri.sm>
> > > ---
> > >  drivers/staging/media/imx/Makefile           |   1 +
> > >  drivers/staging/media/imx/imx8mq-mipi-csi2.c | 949
> > > +++++++++++++++++++
> > >  2 files changed, 950 insertions(+)
> > >  create mode 100644 drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > > 
> > > diff --git a/drivers/staging/media/imx/Makefile
> > > b/drivers/staging/media/imx/Makefile
> > > index 6ac33275cc97..19c2fc54d424 100644
> > > --- a/drivers/staging/media/imx/Makefile
> > > +++ b/drivers/staging/media/imx/Makefile
> > > @@ -16,3 +16,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o
> > >  
> > >  obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
> > >  obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o
> > > +obj-$(CONFIG_VIDEO_IMX7_CSI) += imx8mq-mipi-csi2.o
> > > diff --git a/drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > > b/drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > > new file mode 100644
> > > index 000000000000..949b3ef7a20a
> > > --- /dev/null
> > > +++ b/drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > > @@ -0,0 +1,949 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Freescale i.MX8MQ SoC series MIPI-CSI2 receiver driver
> > 
> > Maybe they should be called NXP these days :-)
> > 
> > > + *
> > > + * Copyright (C) 2021 Purism SPC
> > > + */
> > > +
> > > +#include <linux/clk.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/errno.h>
> > > +#include <linux/interconnect.h>
> > > +#include <linux/interrupt.h>
> > > +#include <linux/io.h>
> > > +#include <linux/kernel.h>
> > > +#include <linux/mfd/syscon.h>
> > > +#include <linux/module.h>
> > > +#include <linux/mutex.h>
> > > +#include <linux/of.h>
> > > +#include <linux/of_device.h>
> > > +#include <linux/platform_device.h>
> > > +#include <linux/pm_runtime.h>
> > > +#include <linux/regmap.h>
> > > +#include <linux/regulator/consumer.h>
> > > +#include <linux/reset.h>
> > > +#include <linux/spinlock.h>
> > > +
> > > +#include <media/v4l2-common.h>
> > > +#include <media/v4l2-device.h>
> > > +#include <media/v4l2-fwnode.h>
> > > +#include <media/v4l2-mc.h>
> > > +#include <media/v4l2-subdev.h>
> > > +
> > > +#define MIPI_CSI2_DRIVER_NAME                  "imx8mq-mipi-csi2"
> > > +#define
> > > MIPI_CSI2_SUBDEV_NAME                  MIPI_CSI2_DRIVER_NAME
> > > +
> > > +#define MIPI_CSI2_PAD_SINK                     0
> > > +#define MIPI_CSI2_PAD_SOURCE                   1
> > > +#define MIPI_CSI2_PADS_NUM                     2
> > > +
> > > +#define MIPI_CSI2_DEF_PIX_WIDTH                        640
> > > +#define MIPI_CSI2_DEF_PIX_HEIGHT               480
> > > +
> > > +/* Register map definition */
> > > +
> > > +/* i.MX8MQ CSI-2 controller CSR */
> > > +#define CSI2RX_CFG_NUM_LANES                   0x100
> > > +#define CSI2RX_CFG_DISABLE_DATA_LANES          0x104
> > > +#define CSI2RX_BIT_ERR                         0x108
> > > +#define CSI2RX_IRQ_STATUS                      0x10c
> > > +#define CSI2RX_IRQ_MASK                                0x110
> > > +#define CSI2RX_IRQ_MASK_ALL                    0x1ff
> > > +#define CSI2RX_IRQ_MASK_ULPS_STATUS_CHANGE     0x8
> > > +#define CSI2RX_ULPS_STATUS                     0x114
> > > +#define CSI2RX_PPI_ERRSOT_HS                   0x118
> > > +#define CSI2RX_PPI_ERRSOTSYNC_HS               0x11c
> > > +#define CSI2RX_PPI_ERRESC                      0x120
> > > +#define CSI2RX_PPI_ERRSYNCESC                  0x124
> > > +#define CSI2RX_PPI_ERRCONTROL                  0x128
> > > +#define CSI2RX_CFG_DISABLE_PAYLOAD_0           0x12c
> > > +#define CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL       0x188
> > > +#define CSI2RX_CFG_DISABLE_PAYLOAD_1           0x130
> > > +
> > > +enum {
> > > +       ST_POWERED      = 1,
> > > +       ST_STREAMING    = 2,
> > > +       ST_SUSPENDED    = 4,
> > > +};
> > > +
> > > +static const char * const imx8mq_mipi_csi_clk_id[] = {
> > > +       "core",
> > > +       "esc",
> > > +       "ui",
> > > +};
> > > +
> > > +#define CSI2_NUM_CLKS  ARRAY_SIZE(imx8mq_mipi_csi_clk_id)
> > > +
> > > +#define        GPR_CSI2_1_RX_ENABLE            BIT(13)
> > > +#define        GPR_CSI2_1_VID_INTFC_ENB        BIT(12)
> > > +#define        GPR_CSI2_1_HSEL                 BIT(10)
> > > +#define        GPR_CSI2_1_CONT_CLK_MODE        BIT(8)
> > > +#define        GPR_CSI2_1_S_PRG_RXHS_SETTLE(x) (((x) & 0x3f) << 2)
> > > +
> > > +/*
> > > + * The send level configures the number of entries that must
> > > accumulate in
> > > + * the Pixel FIFO before the data will be transferred to the video
> > > output.
> > > + * See  
> > > https://community.nxp.com/t5/i-MX-Processors/IMX8M-MIPI-CSI-Host-Controller-send-level/m-p/864005/highlight/true#M131704
> > > + */
> > > +#define CSI2RX_SEND_LEVEL                      64
> > > +
> > > +struct csi_state {
> > > +       struct device *dev;
> > > +       void __iomem *regs;
> > > +       struct clk_bulk_data clks[CSI2_NUM_CLKS];
> > > +       struct reset_control *rst;
> > > +       struct regulator *mipi_phy_regulator;
> > > +
> > > +       struct v4l2_subdev sd;
> > > +       struct media_pad pads[MIPI_CSI2_PADS_NUM];
> > > +       struct v4l2_async_notifier notifier;
> > > +       struct v4l2_subdev *src_sd;
> > > +
> > > +       struct v4l2_fwnode_bus_mipi_csi2 bus;
> > > +
> > > +       struct mutex lock; /* Protect csi2_fmt, format_mbus, state,
> > > hs_settle*/
> > 
> > Missing space before */
> > 
> > > +       const struct csi2_pix_format *csi2_fmt;
> > > +       struct v4l2_mbus_framefmt format_mbus[MIPI_CSI2_PADS_NUM];
> > > +       u32 state;
> > > +       u32 hs_settle;
> > > +
> > > +       struct regmap *phy_gpr;
> > > +       u8 phy_gpr_reg;
> > > +
> > > +       struct icc_path                 *icc_path;
> > > +       s32                             icc_path_bw;
> > > +};
> > > +
> > > +/* ---------------------------------------------------------------
> > > --------------
> > > + * Format helpers
> > > + */
> > > +
> > > +struct csi2_pix_format {
> > > +       u32 code;
> > > +       u8 width;
> > > +};
> > > +
> > > +static const struct csi2_pix_format imx8mq_mipi_csi_formats[] = {
> > > +       /* RAW (Bayer and greyscale) formats. */
> > > +       {
> > > +               .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> > > +               .width = 8,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> > > +               .width = 8,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> > > +               .width = 8,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> > > +               .width = 8,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_Y8_1X8,
> > > +               .width = 8,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SBGGR10_1X10,
> > > +               .width = 10,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SGBRG10_1X10,
> > > +               .width = 10,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SGRBG10_1X10,
> > > +               .width = 10,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SRGGB10_1X10,
> > > +               .width = 10,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_Y10_1X10,
> > > +               .width = 10,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SBGGR12_1X12,
> > > +               .width = 12,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SGBRG12_1X12,
> > > +               .width = 12,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SGRBG12_1X12,
> > > +               .width = 12,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SRGGB12_1X12,
> > > +               .width = 12,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_Y12_1X12,
> > > +               .width = 12,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SBGGR14_1X14,
> > > +               .width = 14,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SGBRG14_1X14,
> > > +               .width = 14,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SGRBG14_1X14,
> > > +               .width = 14,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_SRGGB14_1X14,
> > > +               .width = 14,
> > > +       }, {
> > > +       /* YUV formats */
> > > +               .code = MEDIA_BUS_FMT_YUYV8_2X8,
> > > +               .width = 16,
> > > +       }, {
> > > +               .code = MEDIA_BUS_FMT_YUYV8_1X16,
> > > +               .width = 16,
> > > +       }
> > > +};
> > > +
> > > +static const struct csi2_pix_format *find_csi2_format(u32 code)
> > > +{
> > > +       unsigned int i;
> > > +
> > > +       for (i = 0; i < ARRAY_SIZE(imx8mq_mipi_csi_formats); i++)
> > > +               if (code == imx8mq_mipi_csi_formats[i].code)
> > > +                       return &imx8mq_mipi_csi_formats[i];
> > > +       return NULL;
> > > +}
> > > +
> > > +/* ---------------------------------------------------------------
> > > --------------
> > > + * Hardware configuration
> > > + */
> > > +
> > > +static inline void imx8mq_mipi_csi_write(struct csi_state *state,
> > > u32 reg, u32 val)
> > > +{
> > > +       writel(val, state->regs + reg);
> > > +}
> > > +
> > > +static int imx8mq_mipi_csi_sw_reset(struct csi_state *state)
> > > +{
> > > +       int ret;
> > > +
> > > +       ret = reset_control_assert(state->rst);
> > 
> > That's peculiar, is there no need to deassert reset ?
> 
> I tried different things here that would look more intuitive, but in
> the end only this worked, which is directly taken from
> https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c?h=imx_5.4.70_2.3.0#n105
> (actual register value read from DT) that results in exactly the same
> register bits set like this assertation.

It's very likely that these are self-clearing reset bits.

I would have adviced using reset_control_assert(), but it looks like the
reset controller driver doesn't support that operation. Could you add a
comment here to explain what's going on, maybe with a note to tell that
the reset-imx7 driver should implement the .reset() operation ?
Laurent Pinchart July 15, 2021, 9:52 p.m. UTC | #5
Hi Martin,

On Thu, Jul 15, 2021 at 09:37:24AM +0200, Martin Kepplinger wrote:
> Am Mittwoch, dem 14.07.2021 um 21:24 +0300 schrieb Laurent Pinchart:
> > On Wed, Jul 14, 2021 at 01:19:30PM +0200, Martin Kepplinger wrote:
> > > Add a driver to support the i.MX8MQ MIPI CSI receiver. The hardware side
> > > is based on
> > > https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c?h=imx_5.4.70_2.3.0
> > > 
> > > It's built as part of VIDEO_IMX7_CSI because that's documented to support
> > > i.MX8M platforms. This driver adds i.MX8MQ support where currently only the
> > > i.MX8MM platform has been supported.
> > > 
> > > Signed-off-by: Martin Kepplinger <martin.kepplinger@puri.sm>
> > > ---
> > >  drivers/staging/media/imx/Makefile           |   1 +
> > >  drivers/staging/media/imx/imx8mq-mipi-csi2.c | 949 +++++++++++++++++++
> > >  2 files changed, 950 insertions(+)
> > >  create mode 100644 drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > > 
> > > diff --git a/drivers/staging/media/imx/Makefile
> > > b/drivers/staging/media/imx/Makefile
> > > index 6ac33275cc97..19c2fc54d424 100644
> > > --- a/drivers/staging/media/imx/Makefile
> > > +++ b/drivers/staging/media/imx/Makefile
> > > @@ -16,3 +16,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o

[snip]

> > > +static int imx8mq_mipi_csi_calc_hs_settle(struct csi_state *state)
> > > +{
> > > +       u32 width = state->format_mbus[MIPI_CSI2_PAD_SINK].width;
> > > +       u32 height = state->format_mbus[MIPI_CSI2_PAD_SINK].height;
> > > +       s64 link_freq;
> > > +       u32 lane_rate;
> > > +
> > > +       /* Calculate the line rate from the pixel rate. */
> > > +       link_freq = v4l2_get_link_freq(state->src_sd->ctrl_handler,
> > > +                                      state->csi2_fmt->width,
> > > +                                      state->bus.num_data_lanes * 2);
> > > +       if (link_freq < 0) {
> > > +               dev_err(state->dev, "Unable to obtain link frequency: %d\n",
> > > +                       (int)link_freq);
> > > +               return link_freq;
> > > +       }
> > > +
> > > +       lane_rate = link_freq * 2;
> > > +       if (lane_rate < 80000000 || lane_rate > 1500000000) {
> > > +               dev_dbg(state->dev, "Out-of-bound lane rate %u\n", lane_rate);
> > > +               return -EINVAL;
> > > +       }
> > > +
> > > +       /* https://community.nxp.com/t5/i-MX-Processors/Explenation-for-HS-SETTLE-parameter-in-MIPI-CSI-D-PHY-registers/m-p/764275/highlight/true#M118744 */
> > > +       if (lane_rate < 250000000)
> > > +               state->hs_settle = 0xb;
> > > +       else if (lane_rate < 500000000)
> > > +               state->hs_settle = 0x8;
> > > +       else
> > > +               state->hs_settle = 0x6;
> > 
> > We could possibly compute this value based on the formula from the table
> > in that page, but maybe that's overkill ? If you want to give it a try,
> > it would be along those lines.
> > 
> >         /*
> >          * The D-PHY specification requires Ths-settle to be in the range
> >          * 85ns + 6*UI to 140ns + 10*UI, with the unit interval UI being half
> >          * the clock period.
> >          *
> >          * The Ths-settle value is expressed in the hardware as a multiple of
> >          * the Esc clock period:
> >          *
> >          * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of RxClkInEsc
> >          *
> >          * Due to the one cycle inaccuracy introduced by rounding, the
> >          * documentation recommends picking a value away from the boundaries.
> >          * Let's pick the average.
> >          */
> >         esc_clk_rate = clk_get_rate(...);
> > 
> >         min_ths_settle = 85 + 6 * 1000000 / (lane_rate / 1000);
> >         max_ths_settle = 140 + 10 * 1000000 / (lane_rate / 1000);
> >         ths_settle = (min_ths_settle + max_ths_settle) / 2;
> > 
> >         state->hs_settle = ths_settle * esc_clk_rate / 1000000000 - 1;
> 
> I experimented a bit but would like to leave this as a task for later
> if that's ok. it's correct and simple now. also, using clks[i].clk
> based on the name string would feel better to submit seperately later.

That's OK with me, but I may then submit a patch on top fairly soon :-)
Have you been able to test if this code works on your device ? The main
reason why I think it's better is that it doesn't hardcode a specific
escape clock frequency assumption, so it should be able to accommodate a
wider range of use cases. If we change it later, there's always a risk
of regressions, while if we do this from the start, we'll figure out
quickly if it doesn't work in some cases.
Martin Kepplinger July 16, 2021, 8:47 a.m. UTC | #6
Am Freitag, dem 16.07.2021 um 00:52 +0300 schrieb Laurent Pinchart:
> Hi Martin,
> 
> On Thu, Jul 15, 2021 at 09:37:24AM +0200, Martin Kepplinger wrote:
> > Am Mittwoch, dem 14.07.2021 um 21:24 +0300 schrieb Laurent
> > Pinchart:
> > > On Wed, Jul 14, 2021 at 01:19:30PM +0200, Martin Kepplinger
> > > wrote:
> > > > Add a driver to support the i.MX8MQ MIPI CSI receiver. The
> > > > hardware side
> > > > is based on
> > > > https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c?h=imx_5.4.70_2.3.0
> > > > 
> > > > It's built as part of VIDEO_IMX7_CSI because that's documented
> > > > to support
> > > > i.MX8M platforms. This driver adds i.MX8MQ support where
> > > > currently only the
> > > > i.MX8MM platform has been supported.
> > > > 
> > > > Signed-off-by: Martin Kepplinger <martin.kepplinger@puri.sm>
> > > > ---
> > > >  drivers/staging/media/imx/Makefile           |   1 +
> > > >  drivers/staging/media/imx/imx8mq-mipi-csi2.c | 949
> > > > +++++++++++++++++++
> > > >  2 files changed, 950 insertions(+)
> > > >  create mode 100644 drivers/staging/media/imx/imx8mq-mipi-
> > > > csi2.c
> > > > 
> > > > diff --git a/drivers/staging/media/imx/Makefile
> > > > b/drivers/staging/media/imx/Makefile
> > > > index 6ac33275cc97..19c2fc54d424 100644
> > > > --- a/drivers/staging/media/imx/Makefile
> > > > +++ b/drivers/staging/media/imx/Makefile
> > > > @@ -16,3 +16,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-
> > > > csi2.o
> 
> [snip]
> 
> > > > +static int imx8mq_mipi_csi_calc_hs_settle(struct csi_state
> > > > *state)
> > > > +{
> > > > +       u32 width = state-
> > > > >format_mbus[MIPI_CSI2_PAD_SINK].width;
> > > > +       u32 height = state-
> > > > >format_mbus[MIPI_CSI2_PAD_SINK].height;
> > > > +       s64 link_freq;
> > > > +       u32 lane_rate;
> > > > +
> > > > +       /* Calculate the line rate from the pixel rate. */
> > > > +       link_freq = v4l2_get_link_freq(state->src_sd-
> > > > >ctrl_handler,
> > > > +                                      state->csi2_fmt->width,
> > > > +                                      state-
> > > > >bus.num_data_lanes * 2);
> > > > +       if (link_freq < 0) {
> > > > +               dev_err(state->dev, "Unable to obtain link
> > > > frequency: %d\n",
> > > > +                       (int)link_freq);
> > > > +               return link_freq;
> > > > +       }
> > > > +
> > > > +       lane_rate = link_freq * 2;
> > > > +       if (lane_rate < 80000000 || lane_rate > 1500000000) {
> > > > +               dev_dbg(state->dev, "Out-of-bound lane rate
> > > > %u\n", lane_rate);
> > > > +               return -EINVAL;
> > > > +       }
> > > > +
> > > > +       /*
> > > > https://community.nxp.com/t5/i-MX-Processors/Explenation-for-HS-SETTLE-parameter-in-MIPI-CSI-D-PHY-registers/m-p/764275/highlight/true#M118744
> > > >  */
> > > > +       if (lane_rate < 250000000)
> > > > +               state->hs_settle = 0xb;
> > > > +       else if (lane_rate < 500000000)
> > > > +               state->hs_settle = 0x8;
> > > > +       else
> > > > +               state->hs_settle = 0x6;
> > > 
> > > We could possibly compute this value based on the formula from
> > > the table
> > > in that page, but maybe that's overkill ? If you want to give it
> > > a try,
> > > it would be along those lines.
> > > 
> > >         /*
> > >          * The D-PHY specification requires Ths-settle to be in
> > > the range
> > >          * 85ns + 6*UI to 140ns + 10*UI, with the unit interval
> > > UI being half
> > >          * the clock period.
> > >          *
> > >          * The Ths-settle value is expressed in the hardware as a
> > > multiple of
> > >          * the Esc clock period:
> > >          *
> > >          * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of
> > > RxClkInEsc
> > >          *
> > >          * Due to the one cycle inaccuracy introduced by
> > > rounding, the
> > >          * documentation recommends picking a value away from the
> > > boundaries.
> > >          * Let's pick the average.
> > >          */
> > >         esc_clk_rate = clk_get_rate(...);
> > > 
> > >         min_ths_settle = 85 + 6 * 1000000 / (lane_rate / 1000);
> > >         max_ths_settle = 140 + 10 * 1000000 / (lane_rate / 1000);
> > >         ths_settle = (min_ths_settle + max_ths_settle) / 2;
> > > 
> > >         state->hs_settle = ths_settle * esc_clk_rate / 1000000000
> > > - 1;
> > 
> > I experimented a bit but would like to leave this as a task for
> > later
> > if that's ok. it's correct and simple now. also, using clks[i].clk
> > based on the name string would feel better to submit seperately
> > later.
> 
> That's OK with me, but I may then submit a patch on top fairly soon
> :-)
> Have you been able to test if this code works on your device ? The
> main
> reason why I think it's better is that it doesn't hardcode a specific
> escape clock frequency assumption, so it should be able to
> accommodate a
> wider range of use cases. If we change it later, there's always a
> risk
> of regressions, while if we do this from the start, we'll figure out
> quickly if it doesn't work in some cases.
> 

taking your code basically as-is doesn't yet work, but it helps a bit.
tbh I don't even know how to correctly read that table / calculation:
what is the exact relation of the calculated Ths_settle time inverval
to the hs_settle register bits?

if the 2 of us can't quickly figure it out I can ask NXP via that
community forum issue and I created
https://source.puri.sm/Librem5/linux-next/-/issues/340 so I won't
forget about it.

thanks!
Laurent Pinchart July 16, 2021, 10:47 a.m. UTC | #7
Hi Martin,

On Fri, Jul 16, 2021 at 10:47:14AM +0200, Martin Kepplinger wrote:
> Am Freitag, dem 16.07.2021 um 00:52 +0300 schrieb Laurent Pinchart:
> > On Thu, Jul 15, 2021 at 09:37:24AM +0200, Martin Kepplinger wrote:
> > > Am Mittwoch, dem 14.07.2021 um 21:24 +0300 schrieb Laurent Pinchart:
> > > > On Wed, Jul 14, 2021 at 01:19:30PM +0200, Martin Kepplinger wrote:
> > > > > Add a driver to support the i.MX8MQ MIPI CSI receiver. The hardware side
> > > > > is based on
> > > > > https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c?h=imx_5.4.70_2.3.0
> > > > > 
> > > > > It's built as part of VIDEO_IMX7_CSI because that's documented to support
> > > > > i.MX8M platforms. This driver adds i.MX8MQ support where currently only the
> > > > > i.MX8MM platform has been supported.
> > > > > 
> > > > > Signed-off-by: Martin Kepplinger <martin.kepplinger@puri.sm>
> > > > > ---
> > > > >  drivers/staging/media/imx/Makefile           |   1 +
> > > > >  drivers/staging/media/imx/imx8mq-mipi-csi2.c | 949 +++++++++++++++++++
> > > > >  2 files changed, 950 insertions(+)
> > > > >  create mode 100644 drivers/staging/media/imx/imx8mq-mipi-csi2.c
> > > > > 
> > > > > diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile
> > > > > index 6ac33275cc97..19c2fc54d424 100644
> > > > > --- a/drivers/staging/media/imx/Makefile
> > > > > +++ b/drivers/staging/media/imx/Makefile
> > > > > @@ -16,3 +16,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o
> > 
> > [snip]
> > 
> > > > > +static int imx8mq_mipi_csi_calc_hs_settle(struct csi_state *state)
> > > > > +{
> > > > > +       u32 width = state->format_mbus[MIPI_CSI2_PAD_SINK].width;
> > > > > +       u32 height = state->format_mbus[MIPI_CSI2_PAD_SINK].height;
> > > > > +       s64 link_freq;
> > > > > +       u32 lane_rate;
> > > > > +
> > > > > +       /* Calculate the line rate from the pixel rate. */
> > > > > +       link_freq = v4l2_get_link_freq(state->src_sd->ctrl_handler,
> > > > > +                                      state->csi2_fmt->width,
> > > > > +                                      state->bus.num_data_lanes * 2);
> > > > > +       if (link_freq < 0) {
> > > > > +               dev_err(state->dev, "Unable to obtain link frequency: %d\n",
> > > > > +                       (int)link_freq);
> > > > > +               return link_freq;
> > > > > +       }
> > > > > +
> > > > > +       lane_rate = link_freq * 2;
> > > > > +       if (lane_rate < 80000000 || lane_rate > 1500000000) {
> > > > > +               dev_dbg(state->dev, "Out-of-bound lane rate %u\n", lane_rate);
> > > > > +               return -EINVAL;
> > > > > +       }
> > > > > +
> > > > > +       /* https://community.nxp.com/t5/i-MX-Processors/Explenation-for-HS-SETTLE-parameter-in-MIPI-CSI-D-PHY-registers/m-p/764275/highlight/true#M118744 */
> > > > > +       if (lane_rate < 250000000)
> > > > > +               state->hs_settle = 0xb;
> > > > > +       else if (lane_rate < 500000000)
> > > > > +               state->hs_settle = 0x8;
> > > > > +       else
> > > > > +               state->hs_settle = 0x6;
> > > > 
> > > > We could possibly compute this value based on the formula from the table
> > > > in that page, but maybe that's overkill ? If you want to give it a try,
> > > > it would be along those lines.
> > > > 
> > > >         /*
> > > >          * The D-PHY specification requires Ths-settle to be in the range
> > > >          * 85ns + 6*UI to 140ns + 10*UI, with the unit interval UI being half
> > > >          * the clock period.
> > > >          *
> > > >          * The Ths-settle value is expressed in the hardware as a multiple of
> > > >          * the Esc clock period:
> > > >          *
> > > >          * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of RxClkInEsc
> > > >          *
> > > >          * Due to the one cycle inaccuracy introduced by rounding, the
> > > >          * documentation recommends picking a value away from the boundaries.
> > > >          * Let's pick the average.
> > > >          */
> > > >         esc_clk_rate = clk_get_rate(...);
> > > > 
> > > >         min_ths_settle = 85 + 6 * 1000000 / (lane_rate / 1000);
> > > >         max_ths_settle = 140 + 10 * 1000000 / (lane_rate / 1000);
> > > >         ths_settle = (min_ths_settle + max_ths_settle) / 2;
> > > > 
> > > >         state->hs_settle = ths_settle * esc_clk_rate / 1000000000 - 1;
> > > 
> > > I experimented a bit but would like to leave this as a task for later
> > > if that's ok. it's correct and simple now. also, using clks[i].clk
> > > based on the name string would feel better to submit seperately
> > > later.
> > 
> > That's OK with me, but I may then submit a patch on top fairly soon :-)
> > Have you been able to test if this code works on your device ? The main
> > reason why I think it's better is that it doesn't hardcode a specific
> > escape clock frequency assumption, so it should be able to accommodate a
> > wider range of use cases. If we change it later, there's always a risk
> > of regressions, while if we do this from the start, we'll figure out
> > quickly if it doesn't work in some cases.
> 
> taking your code basically as-is doesn't yet work, but it helps a bit.

Thanks for testing.

> tbh I don't even know how to correctly read that table / calculation:
> what is the exact relation of the calculated Ths_settle time inverval
> to the hs_settle register bits?

The PRG_RXHS_SETTLE field stores a number of timer ticks to cover the
Ths-settle internal. The D-PHY arms the timer when it detects the
transition to LP-00, and ignores transitions on the lane until the timer
expires. The timer is clocked by the escape clock.

What hs_settle value do you currently use, and what value does my code
produce ?

> if the 2 of us can't quickly figure it out I can ask NXP via that
> community forum issue and I created
> https://source.puri.sm/Librem5/linux-next/-/issues/340 so I won't
> forget about it.
Martin Kepplinger July 19, 2021, 10:46 a.m. UTC | #8
Am Freitag, dem 16.07.2021 um 13:47 +0300 schrieb Laurent Pinchart:
> Hi Martin,
> 
> On Fri, Jul 16, 2021 at 10:47:14AM +0200, Martin Kepplinger wrote:
> > Am Freitag, dem 16.07.2021 um 00:52 +0300 schrieb Laurent Pinchart:
> > > On Thu, Jul 15, 2021 at 09:37:24AM +0200, Martin Kepplinger
> > > wrote:
> > > > Am Mittwoch, dem 14.07.2021 um 21:24 +0300 schrieb Laurent
> > > > Pinchart:
> > > > > On Wed, Jul 14, 2021 at 01:19:30PM +0200, Martin Kepplinger
> > > > > wrote:
> > > > > > Add a driver to support the i.MX8MQ MIPI CSI receiver. The
> > > > > > hardware side
> > > > > > is based on
> > > > > > https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c?h=imx_5.4.70_2.3.0
> > > > > > 
> > > > > > It's built as part of VIDEO_IMX7_CSI because that's
> > > > > > documented to support
> > > > > > i.MX8M platforms. This driver adds i.MX8MQ support where
> > > > > > currently only the
> > > > > > i.MX8MM platform has been supported.
> > > > > > 
> > > > > > Signed-off-by: Martin Kepplinger
> > > > > > <martin.kepplinger@puri.sm>
> > > > > > ---
> > > > > >  drivers/staging/media/imx/Makefile           |   1 +
> > > > > >  drivers/staging/media/imx/imx8mq-mipi-csi2.c | 949
> > > > > > +++++++++++++++++++
> > > > > >  2 files changed, 950 insertions(+)
> > > > > >  create mode 100644 drivers/staging/media/imx/imx8mq-mipi-
> > > > > > csi2.c
> > > > > > 
> > > > > > diff --git a/drivers/staging/media/imx/Makefile
> > > > > > b/drivers/staging/media/imx/Makefile
> > > > > > index 6ac33275cc97..19c2fc54d424 100644
> > > > > > --- a/drivers/staging/media/imx/Makefile
> > > > > > +++ b/drivers/staging/media/imx/Makefile
> > > > > > @@ -16,3 +16,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-
> > > > > > csi2.o
> > > 
> > > [snip]
> > > 
> > > > > > +static int imx8mq_mipi_csi_calc_hs_settle(struct csi_state
> > > > > > *state)
> > > > > > +{
> > > > > > +       u32 width = state-
> > > > > > >format_mbus[MIPI_CSI2_PAD_SINK].width;
> > > > > > +       u32 height = state-
> > > > > > >format_mbus[MIPI_CSI2_PAD_SINK].height;
> > > > > > +       s64 link_freq;
> > > > > > +       u32 lane_rate;
> > > > > > +
> > > > > > +       /* Calculate the line rate from the pixel rate. */
> > > > > > +       link_freq = v4l2_get_link_freq(state->src_sd-
> > > > > > >ctrl_handler,
> > > > > > +                                      state->csi2_fmt-
> > > > > > >width,
> > > > > > +                                      state-
> > > > > > >bus.num_data_lanes * 2);
> > > > > > +       if (link_freq < 0) {
> > > > > > +               dev_err(state->dev, "Unable to obtain link
> > > > > > frequency: %d\n",
> > > > > > +                       (int)link_freq);
> > > > > > +               return link_freq;
> > > > > > +       }
> > > > > > +
> > > > > > +       lane_rate = link_freq * 2;
> > > > > > +       if (lane_rate < 80000000 || lane_rate > 1500000000)
> > > > > > {
> > > > > > +               dev_dbg(state->dev, "Out-of-bound lane rate
> > > > > > %u\n", lane_rate);
> > > > > > +               return -EINVAL;
> > > > > > +       }
> > > > > > +
> > > > > > +       /*
> > > > > > https://community.nxp.com/t5/i-MX-Processors/Explenation-for-HS-SETTLE-parameter-in-MIPI-CSI-D-PHY-registers/m-p/764275/highlight/true#M118744
> > > > > >  */
> > > > > > +       if (lane_rate < 250000000)
> > > > > > +               state->hs_settle = 0xb;
> > > > > > +       else if (lane_rate < 500000000)
> > > > > > +               state->hs_settle = 0x8;
> > > > > > +       else
> > > > > > +               state->hs_settle = 0x6;
> > > > > 
> > > > > We could possibly compute this value based on the formula
> > > > > from the table
> > > > > in that page, but maybe that's overkill ? If you want to give
> > > > > it a try,
> > > > > it would be along those lines.
> > > > > 
> > > > >         /*
> > > > >          * The D-PHY specification requires Ths-settle to be
> > > > > in the range
> > > > >          * 85ns + 6*UI to 140ns + 10*UI, with the unit
> > > > > interval UI being half
> > > > >          * the clock period.
> > > > >          *
> > > > >          * The Ths-settle value is expressed in the hardware
> > > > > as a multiple of
> > > > >          * the Esc clock period:
> > > > >          *
> > > > >          * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of
> > > > > RxClkInEsc
> > > > >          *
> > > > >          * Due to the one cycle inaccuracy introduced by
> > > > > rounding, the
> > > > >          * documentation recommends picking a value away from
> > > > > the boundaries.
> > > > >          * Let's pick the average.
> > > > >          */
> > > > >         esc_clk_rate = clk_get_rate(...);
> > > > > 
> > > > >         min_ths_settle = 85 + 6 * 1000000 / (lane_rate /
> > > > > 1000);
> > > > >         max_ths_settle = 140 + 10 * 1000000 / (lane_rate /
> > > > > 1000);
> > > > >         ths_settle = (min_ths_settle + max_ths_settle) / 2;
> > > > > 
> > > > >         state->hs_settle = ths_settle * esc_clk_rate /
> > > > > 1000000000 - 1;
> > > > 
> > > > I experimented a bit but would like to leave this as a task for
> > > > later
> > > > if that's ok. it's correct and simple now. also, using
> > > > clks[i].clk
> > > > based on the name string would feel better to submit seperately
> > > > later.
> > > 
> > > That's OK with me, but I may then submit a patch on top fairly
> > > soon :-)
> > > Have you been able to test if this code works on your device ?
> > > The main
> > > reason why I think it's better is that it doesn't hardcode a
> > > specific
> > > escape clock frequency assumption, so it should be able to
> > > accommodate a
> > > wider range of use cases. If we change it later, there's always a
> > > risk
> > > of regressions, while if we do this from the start, we'll figure
> > > out
> > > quickly if it doesn't work in some cases.
> > 
> > taking your code basically as-is doesn't yet work, but it helps a
> > bit.
> 
> Thanks for testing.
> 
> > tbh I don't even know how to correctly read that table /
> > calculation:
> > what is the exact relation of the calculated Ths_settle time
> > inverval
> > to the hs_settle register bits?
> 
> The PRG_RXHS_SETTLE field stores a number of timer ticks to cover the
> Ths-settle internal. The D-PHY arms the timer when it detects the
> transition to LP-00, and ignores transitions on the lane until the
> timer
> expires. The timer is clocked by the escape clock.
> 
> What hs_settle value do you currently use, and what value does my
> code
> produce ?
> 
> > if the 2 of us can't quickly figure it out I can ask NXP via that
> > community forum issue and I created
> > https://source.puri.sm/Librem5/linux-next/-/issues/340 so I won't
> > forget about it.
> 

hi Laurent,

the below patch for hs_settle works (and calculates either the same or
+/- 1 for the hs_settle value, compared to the table), but getting the
esc clock looks really scary how I do it: how would you do that?


@@ -284,6 +285,9 @@ static int imx8mq_mipi_csi_calc_hs_settle(struct
csi_state *state)
 {
 	s64 link_freq;
 	u32 lane_rate;
+	u32 esc_clk_rate = 0;
+	u32 i, min_ths_settle, max_ths_settle, ths_settle_ns,
esc_clk_period_ns;
+	char *p;
 
 	/* Calculate the line rate from the pixel rate. */
 	link_freq = v4l2_get_link_freq(state->src_sd->ctrl_handler,
@@ -302,20 +306,44 @@ static int imx8mq_mipi_csi_calc_hs_settle(struct
csi_state *state)
 	}
 
 	/*
-	 * The following table is the source for this:
-	 *
https://community.nxp.com/t5/i-MX-Processors/Explenation-for-HS-SETTLE-parameter-in-MIPI-CSI-D-PHY-registers/m-p/764275/highlight/true#M118744
-	 * but it would be even better to calculate the value for any
-	 * given datarate.
+	 * The D-PHY specification requires Ths-settle to be in the
range
+	 * 85ns + 6*UI to 140ns + 10*UI, with the unit interval UI
being half
+	 * the clock period.
+	 *
+	 * The Ths-settle value is expressed in the hardware as a
multiple of
+	 * the Esc clock period:
+	 *
+	 * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of RxClkInEsc
+	 *
+	 * Due to the one cycle inaccuracy introduced by rounding, the
+	 * documentation recommends picking a value away from the
boundaries.
+	 * Let's pick the average.
 	 */
-	if (lane_rate < 250000000)
-		state->hs_settle = 0xb;
-	else if (lane_rate < 500000000)
-		state->hs_settle = 0x8;
-	else
-		state->hs_settle = 0x6;
-
-	dev_dbg(state->dev, "start stream: lane rate %u hs_settle
%u\n",
-		lane_rate, state->hs_settle);
+	for (i = 0; i < CSI2_NUM_CLKS; i++) {
+		p = (char *)__clk_get_name(state->clks[i].clk);
+		/* we're getting csi1_esc here */
+		if (strlen(p) > 7)
+			p += 5;
+
+		dev_dbg(state->dev, "comparing: %s to esc\n", p);
+		if (!strcmp(p, "esc"))
+			esc_clk_rate = clk_get_rate(state-
>clks[i].clk);
+	}
+
+	if (!esc_clk_rate)
+		dev_err(state->dev, "Could not get esc clock rate\n");
+
+	dev_dbg(state->dev, "esc clk rate: %u\n", esc_clk_rate);
+	esc_clk_period_ns = 1000000000 / esc_clk_rate;
+
+	min_ths_settle = 85 + 6 * 1000000 / (lane_rate / 1000);
+	max_ths_settle = 140 + 10 * 1000000 / (lane_rate / 1000);
+	ths_settle_ns = (min_ths_settle + max_ths_settle) / 2;
+
+	state->hs_settle = ths_settle_ns / esc_clk_period_ns - 1;
+
+	dev_dbg(state->dev, "lane rate %u Ths_settle %u hs_settle
%u\n",
+		lane_rate, ths_settle_ns, state->hs_settle);
 
 	return 0;
Martin Kepplinger July 19, 2021, 2:20 p.m. UTC | #9
Am Montag, dem 19.07.2021 um 12:46 +0200 schrieb Martin Kepplinger:
> Am Freitag, dem 16.07.2021 um 13:47 +0300 schrieb Laurent Pinchart:
> > Hi Martin,
> > 
> > On Fri, Jul 16, 2021 at 10:47:14AM +0200, Martin Kepplinger wrote:
> > > Am Freitag, dem 16.07.2021 um 00:52 +0300 schrieb Laurent
> > > Pinchart:
> > > > On Thu, Jul 15, 2021 at 09:37:24AM +0200, Martin Kepplinger
> > > > wrote:
> > > > > Am Mittwoch, dem 14.07.2021 um 21:24 +0300 schrieb Laurent
> > > > > Pinchart:
> > > > > > On Wed, Jul 14, 2021 at 01:19:30PM +0200, Martin Kepplinger
> > > > > > wrote:
> > > > > > > Add a driver to support the i.MX8MQ MIPI CSI receiver.
> > > > > > > The
> > > > > > > hardware side
> > > > > > > is based on
> > > > > > > https://source.codeaurora.org/external/imx/linux-imx/tree/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c?h=imx_5.4.70_2.3.0
> > > > > > > 
> > > > > > > It's built as part of VIDEO_IMX7_CSI because that's
> > > > > > > documented to support
> > > > > > > i.MX8M platforms. This driver adds i.MX8MQ support where
> > > > > > > currently only the
> > > > > > > i.MX8MM platform has been supported.
> > > > > > > 
> > > > > > > Signed-off-by: Martin Kepplinger
> > > > > > > <martin.kepplinger@puri.sm>
> > > > > > > ---
> > > > > > >  drivers/staging/media/imx/Makefile           |   1 +
> > > > > > >  drivers/staging/media/imx/imx8mq-mipi-csi2.c | 949
> > > > > > > +++++++++++++++++++
> > > > > > >  2 files changed, 950 insertions(+)
> > > > > > >  create mode 100644 drivers/staging/media/imx/imx8mq-
> > > > > > > mipi-
> > > > > > > csi2.c
> > > > > > > 
> > > > > > > diff --git a/drivers/staging/media/imx/Makefile
> > > > > > > b/drivers/staging/media/imx/Makefile
> > > > > > > index 6ac33275cc97..19c2fc54d424 100644
> > > > > > > --- a/drivers/staging/media/imx/Makefile
> > > > > > > +++ b/drivers/staging/media/imx/Makefile
> > > > > > > @@ -16,3 +16,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-
> > > > > > > mipi-
> > > > > > > csi2.o
> > > > 
> > > > [snip]
> > > > 
> > > > > > > +static int imx8mq_mipi_csi_calc_hs_settle(struct
> > > > > > > csi_state
> > > > > > > *state)
> > > > > > > +{
> > > > > > > +       u32 width = state-
> > > > > > > > format_mbus[MIPI_CSI2_PAD_SINK].width;
> > > > > > > +       u32 height = state-
> > > > > > > > format_mbus[MIPI_CSI2_PAD_SINK].height;
> > > > > > > +       s64 link_freq;
> > > > > > > +       u32 lane_rate;
> > > > > > > +
> > > > > > > +       /* Calculate the line rate from the pixel rate.
> > > > > > > */
> > > > > > > +       link_freq = v4l2_get_link_freq(state->src_sd-
> > > > > > > > ctrl_handler,
> > > > > > > +                                      state->csi2_fmt-
> > > > > > > > width,
> > > > > > > +                                      state-
> > > > > > > > bus.num_data_lanes * 2);
> > > > > > > +       if (link_freq < 0) {
> > > > > > > +               dev_err(state->dev, "Unable to obtain
> > > > > > > link
> > > > > > > frequency: %d\n",
> > > > > > > +                       (int)link_freq);
> > > > > > > +               return link_freq;
> > > > > > > +       }
> > > > > > > +
> > > > > > > +       lane_rate = link_freq * 2;
> > > > > > > +       if (lane_rate < 80000000 || lane_rate >
> > > > > > > 1500000000)
> > > > > > > {
> > > > > > > +               dev_dbg(state->dev, "Out-of-bound lane
> > > > > > > rate
> > > > > > > %u\n", lane_rate);
> > > > > > > +               return -EINVAL;
> > > > > > > +       }
> > > > > > > +
> > > > > > > +       /*
> > > > > > > https://community.nxp.com/t5/i-MX-Processors/Explenation-for-HS-SETTLE-parameter-in-MIPI-CSI-D-PHY-registers/m-p/764275/highlight/true#M118744
> > > > > > >  */
> > > > > > > +       if (lane_rate < 250000000)
> > > > > > > +               state->hs_settle = 0xb;
> > > > > > > +       else if (lane_rate < 500000000)
> > > > > > > +               state->hs_settle = 0x8;
> > > > > > > +       else
> > > > > > > +               state->hs_settle = 0x6;
> > > > > > 
> > > > > > We could possibly compute this value based on the formula
> > > > > > from the table
> > > > > > in that page, but maybe that's overkill ? If you want to
> > > > > > give
> > > > > > it a try,
> > > > > > it would be along those lines.
> > > > > > 
> > > > > >         /*
> > > > > >          * The D-PHY specification requires Ths-settle to
> > > > > > be
> > > > > > in the range
> > > > > >          * 85ns + 6*UI to 140ns + 10*UI, with the unit
> > > > > > interval UI being half
> > > > > >          * the clock period.
> > > > > >          *
> > > > > >          * The Ths-settle value is expressed in the
> > > > > > hardware
> > > > > > as a multiple of
> > > > > >          * the Esc clock period:
> > > > > >          *
> > > > > >          * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of
> > > > > > RxClkInEsc
> > > > > >          *
> > > > > >          * Due to the one cycle inaccuracy introduced by
> > > > > > rounding, the
> > > > > >          * documentation recommends picking a value away
> > > > > > from
> > > > > > the boundaries.
> > > > > >          * Let's pick the average.
> > > > > >          */
> > > > > >         esc_clk_rate = clk_get_rate(...);
> > > > > > 
> > > > > >         min_ths_settle = 85 + 6 * 1000000 / (lane_rate /
> > > > > > 1000);
> > > > > >         max_ths_settle = 140 + 10 * 1000000 / (lane_rate /
> > > > > > 1000);
> > > > > >         ths_settle = (min_ths_settle + max_ths_settle) / 2;
> > > > > > 
> > > > > >         state->hs_settle = ths_settle * esc_clk_rate /
> > > > > > 1000000000 - 1;
> > > > > 
> > > > > I experimented a bit but would like to leave this as a task
> > > > > for
> > > > > later
> > > > > if that's ok. it's correct and simple now. also, using
> > > > > clks[i].clk
> > > > > based on the name string would feel better to submit
> > > > > seperately
> > > > > later.
> > > > 
> > > > That's OK with me, but I may then submit a patch on top fairly
> > > > soon :-)
> > > > Have you been able to test if this code works on your device ?
> > > > The main
> > > > reason why I think it's better is that it doesn't hardcode a
> > > > specific
> > > > escape clock frequency assumption, so it should be able to
> > > > accommodate a
> > > > wider range of use cases. If we change it later, there's always
> > > > a
> > > > risk
> > > > of regressions, while if we do this from the start, we'll
> > > > figure
> > > > out
> > > > quickly if it doesn't work in some cases.
> > > 
> > > taking your code basically as-is doesn't yet work, but it helps a
> > > bit.
> > 
> > Thanks for testing.
> > 
> > > tbh I don't even know how to correctly read that table /
> > > calculation:
> > > what is the exact relation of the calculated Ths_settle time
> > > inverval
> > > to the hs_settle register bits?
> > 
> > The PRG_RXHS_SETTLE field stores a number of timer ticks to cover
> > the
> > Ths-settle internal. The D-PHY arms the timer when it detects the
> > transition to LP-00, and ignores transitions on the lane until the
> > timer
> > expires. The timer is clocked by the escape clock.
> > 
> > What hs_settle value do you currently use, and what value does my
> > code
> > produce ?
> > 
> > > if the 2 of us can't quickly figure it out I can ask NXP via that
> > > community forum issue and I created
> > > https://source.puri.sm/Librem5/linux-next/-/issues/340 so I won't
> > > forget about it.
> > 
> 
> hi Laurent,
> 
> the below patch for hs_settle works (and calculates either the same
> or
> +/- 1 for the hs_settle value, compared to the table), but getting
> the
> esc clock looks really scary how I do it: how would you do that?
> 
> 
> @@ -284,6 +285,9 @@ static int imx8mq_mipi_csi_calc_hs_settle(struct
> csi_state *state)
>  {
>         s64 link_freq;
>         u32 lane_rate;
> +       u32 esc_clk_rate = 0;
> +       u32 i, min_ths_settle, max_ths_settle, ths_settle_ns,
> esc_clk_period_ns;
> +       char *p;
>  
>         /* Calculate the line rate from the pixel rate. */
>         link_freq = v4l2_get_link_freq(state->src_sd->ctrl_handler,
> @@ -302,20 +306,44 @@ static int
> imx8mq_mipi_csi_calc_hs_settle(struct
> csi_state *state)
>         }
>  
>         /*
> -        * The following table is the source for this:
> -        *
> https://community.nxp.com/t5/i-MX-Processors/Explenation-for-HS-SETTLE-parameter-in-MIPI-CSI-D-PHY-registers/m-p/764275/highlight/true#M118744
> -        * but it would be even better to calculate the value for any
> -        * given datarate.
> +        * The D-PHY specification requires Ths-settle to be in the
> range
> +        * 85ns + 6*UI to 140ns + 10*UI, with the unit interval UI
> being half
> +        * the clock period.
> +        *
> +        * The Ths-settle value is expressed in the hardware as a
> multiple of
> +        * the Esc clock period:
> +        *
> +        * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of RxClkInEsc
> +        *
> +        * Due to the one cycle inaccuracy introduced by rounding,
> the
> +        * documentation recommends picking a value away from the
> boundaries.
> +        * Let's pick the average.
>          */
> -       if (lane_rate < 250000000)
> -               state->hs_settle = 0xb;
> -       else if (lane_rate < 500000000)
> -               state->hs_settle = 0x8;
> -       else
> -               state->hs_settle = 0x6;
> -
> -       dev_dbg(state->dev, "start stream: lane rate %u hs_settle
> %u\n",
> -               lane_rate, state->hs_settle);
> +       for (i = 0; i < CSI2_NUM_CLKS; i++) {
> +               p = (char *)__clk_get_name(state->clks[i].clk);
> +               /* we're getting csi1_esc here */
> +               if (strlen(p) > 7)
> +                       p += 5;
> +
> +               dev_dbg(state->dev, "comparing: %s to esc\n", p);
> +               if (!strcmp(p, "esc"))
> +                       esc_clk_rate = clk_get_rate(state-
> > clks[i].clk);
> +       }
> +
> +       if (!esc_clk_rate)
> +               dev_err(state->dev, "Could not get esc clock
> rate\n");
> +
> +       dev_dbg(state->dev, "esc clk rate: %u\n", esc_clk_rate);
> +       esc_clk_period_ns = 1000000000 / esc_clk_rate;
> +
> +       min_ths_settle = 85 + 6 * 1000000 / (lane_rate / 1000);
> +       max_ths_settle = 140 + 10 * 1000000 / (lane_rate / 1000);
> +       ths_settle_ns = (min_ths_settle + max_ths_settle) / 2;
> +
> +       state->hs_settle = ths_settle_ns / esc_clk_period_ns - 1;
> +
> +       dev_dbg(state->dev, "lane rate %u Ths_settle %u hs_settle
> %u\n",
> +               lane_rate, ths_settle_ns, state->hs_settle);
>  
>         return 0;
> 

the below code works too and looks better to me. Other drivers do it
similarly when looking for a clock:


	for (i = 0; i < CSI2_NUM_CLKS; i++) {
		p = (char *)__clk_get_name(state->clks[i].clk);

		dev_dbg(state->dev, "looking for esc clock: %s\n", p);

		if (!strcmp(p, "esc") || !strcmp(p, "csi1_esc") ||
		    !strcmp(p, "csi2_esc"))
			esc_clk_rate = clk_get_rate(state-
>clks[i].clk);
	}

	if (!esc_clk_rate) {
		dev_err(state->dev, "Could not find esc clock.\n");
		return -EINVAL;
	}

	dev_dbg(state->dev, "esc clk rate: %u\n", esc_clk_rate);
	esc_clk_period_ns = 1000000000 / esc_clk_rate;

	min_ths_settle = 85 + 6 * 1000000 / (lane_rate / 1000);
	max_ths_settle = 140 + 10 * 1000000 / (lane_rate / 1000);
	ths_settle_ns = (min_ths_settle + max_ths_settle) / 2;

	state->hs_settle = ths_settle_ns / esc_clk_period_ns - 1;
diff mbox series

Patch

diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile
index 6ac33275cc97..19c2fc54d424 100644
--- a/drivers/staging/media/imx/Makefile
+++ b/drivers/staging/media/imx/Makefile
@@ -16,3 +16,4 @@  obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o
 
 obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
 obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o
+obj-$(CONFIG_VIDEO_IMX7_CSI) += imx8mq-mipi-csi2.o
diff --git a/drivers/staging/media/imx/imx8mq-mipi-csi2.c b/drivers/staging/media/imx/imx8mq-mipi-csi2.c
new file mode 100644
index 000000000000..949b3ef7a20a
--- /dev/null
+++ b/drivers/staging/media/imx/imx8mq-mipi-csi2.c
@@ -0,0 +1,949 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Freescale i.MX8MQ SoC series MIPI-CSI2 receiver driver
+ *
+ * Copyright (C) 2021 Purism SPC
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/interconnect.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+
+#define MIPI_CSI2_DRIVER_NAME			"imx8mq-mipi-csi2"
+#define MIPI_CSI2_SUBDEV_NAME			MIPI_CSI2_DRIVER_NAME
+
+#define MIPI_CSI2_PAD_SINK			0
+#define MIPI_CSI2_PAD_SOURCE			1
+#define MIPI_CSI2_PADS_NUM			2
+
+#define MIPI_CSI2_DEF_PIX_WIDTH			640
+#define MIPI_CSI2_DEF_PIX_HEIGHT		480
+
+/* Register map definition */
+
+/* i.MX8MQ CSI-2 controller CSR */
+#define CSI2RX_CFG_NUM_LANES			0x100
+#define CSI2RX_CFG_DISABLE_DATA_LANES		0x104
+#define CSI2RX_BIT_ERR				0x108
+#define CSI2RX_IRQ_STATUS			0x10c
+#define CSI2RX_IRQ_MASK				0x110
+#define CSI2RX_IRQ_MASK_ALL			0x1ff
+#define CSI2RX_IRQ_MASK_ULPS_STATUS_CHANGE	0x8
+#define CSI2RX_ULPS_STATUS			0x114
+#define CSI2RX_PPI_ERRSOT_HS			0x118
+#define CSI2RX_PPI_ERRSOTSYNC_HS		0x11c
+#define CSI2RX_PPI_ERRESC			0x120
+#define CSI2RX_PPI_ERRSYNCESC			0x124
+#define CSI2RX_PPI_ERRCONTROL			0x128
+#define CSI2RX_CFG_DISABLE_PAYLOAD_0		0x12c
+#define CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL	0x188
+#define CSI2RX_CFG_DISABLE_PAYLOAD_1		0x130
+
+enum {
+	ST_POWERED	= 1,
+	ST_STREAMING	= 2,
+	ST_SUSPENDED	= 4,
+};
+
+static const char * const imx8mq_mipi_csi_clk_id[] = {
+	"core",
+	"esc",
+	"ui",
+};
+
+#define CSI2_NUM_CLKS	ARRAY_SIZE(imx8mq_mipi_csi_clk_id)
+
+#define	GPR_CSI2_1_RX_ENABLE		BIT(13)
+#define	GPR_CSI2_1_VID_INTFC_ENB	BIT(12)
+#define	GPR_CSI2_1_HSEL			BIT(10)
+#define	GPR_CSI2_1_CONT_CLK_MODE	BIT(8)
+#define	GPR_CSI2_1_S_PRG_RXHS_SETTLE(x)	(((x) & 0x3f) << 2)
+
+/*
+ * The send level configures the number of entries that must accumulate in
+ * the Pixel FIFO before the data will be transferred to the video output.
+ * See https://community.nxp.com/t5/i-MX-Processors/IMX8M-MIPI-CSI-Host-Controller-send-level/m-p/864005/highlight/true#M131704
+ */
+#define CSI2RX_SEND_LEVEL			64
+
+struct csi_state {
+	struct device *dev;
+	void __iomem *regs;
+	struct clk_bulk_data clks[CSI2_NUM_CLKS];
+	struct reset_control *rst;
+	struct regulator *mipi_phy_regulator;
+
+	struct v4l2_subdev sd;
+	struct media_pad pads[MIPI_CSI2_PADS_NUM];
+	struct v4l2_async_notifier notifier;
+	struct v4l2_subdev *src_sd;
+
+	struct v4l2_fwnode_bus_mipi_csi2 bus;
+
+	struct mutex lock; /* Protect csi2_fmt, format_mbus, state, hs_settle*/
+	const struct csi2_pix_format *csi2_fmt;
+	struct v4l2_mbus_framefmt format_mbus[MIPI_CSI2_PADS_NUM];
+	u32 state;
+	u32 hs_settle;
+
+	struct regmap *phy_gpr;
+	u8 phy_gpr_reg;
+
+	struct icc_path			*icc_path;
+	s32				icc_path_bw;
+};
+
+/* -----------------------------------------------------------------------------
+ * Format helpers
+ */
+
+struct csi2_pix_format {
+	u32 code;
+	u8 width;
+};
+
+static const struct csi2_pix_format imx8mq_mipi_csi_formats[] = {
+	/* RAW (Bayer and greyscale) formats. */
+	{
+		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
+		.width = 8,
+	}, {
+		.code = MEDIA_BUS_FMT_SGBRG8_1X8,
+		.width = 8,
+	}, {
+		.code = MEDIA_BUS_FMT_SGRBG8_1X8,
+		.width = 8,
+	}, {
+		.code = MEDIA_BUS_FMT_SRGGB8_1X8,
+		.width = 8,
+	}, {
+		.code = MEDIA_BUS_FMT_Y8_1X8,
+		.width = 8,
+	}, {
+		.code = MEDIA_BUS_FMT_SBGGR10_1X10,
+		.width = 10,
+	}, {
+		.code = MEDIA_BUS_FMT_SGBRG10_1X10,
+		.width = 10,
+	}, {
+		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.width = 10,
+	}, {
+		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
+		.width = 10,
+	}, {
+		.code = MEDIA_BUS_FMT_Y10_1X10,
+		.width = 10,
+	}, {
+		.code = MEDIA_BUS_FMT_SBGGR12_1X12,
+		.width = 12,
+	}, {
+		.code = MEDIA_BUS_FMT_SGBRG12_1X12,
+		.width = 12,
+	}, {
+		.code = MEDIA_BUS_FMT_SGRBG12_1X12,
+		.width = 12,
+	}, {
+		.code = MEDIA_BUS_FMT_SRGGB12_1X12,
+		.width = 12,
+	}, {
+		.code = MEDIA_BUS_FMT_Y12_1X12,
+		.width = 12,
+	}, {
+		.code = MEDIA_BUS_FMT_SBGGR14_1X14,
+		.width = 14,
+	}, {
+		.code = MEDIA_BUS_FMT_SGBRG14_1X14,
+		.width = 14,
+	}, {
+		.code = MEDIA_BUS_FMT_SGRBG14_1X14,
+		.width = 14,
+	}, {
+		.code = MEDIA_BUS_FMT_SRGGB14_1X14,
+		.width = 14,
+	}, {
+	/* YUV formats */
+		.code = MEDIA_BUS_FMT_YUYV8_2X8,
+		.width = 16,
+	}, {
+		.code = MEDIA_BUS_FMT_YUYV8_1X16,
+		.width = 16,
+	}
+};
+
+static const struct csi2_pix_format *find_csi2_format(u32 code)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(imx8mq_mipi_csi_formats); i++)
+		if (code == imx8mq_mipi_csi_formats[i].code)
+			return &imx8mq_mipi_csi_formats[i];
+	return NULL;
+}
+
+/* -----------------------------------------------------------------------------
+ * Hardware configuration
+ */
+
+static inline void imx8mq_mipi_csi_write(struct csi_state *state, u32 reg, u32 val)
+{
+	writel(val, state->regs + reg);
+}
+
+static int imx8mq_mipi_csi_sw_reset(struct csi_state *state)
+{
+	int ret;
+
+	ret = reset_control_assert(state->rst);
+	if (ret < 0) {
+		dev_err(state->dev, "Failed to assert resets: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void imx8mq_mipi_csi_system_enable(struct csi_state *state, int on)
+{
+	if (!on) {
+		imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, 0xf);
+		return;
+	}
+
+	regmap_update_bits(state->phy_gpr,
+			   state->phy_gpr_reg,
+			   0x3fff,
+			   GPR_CSI2_1_RX_ENABLE |
+			   GPR_CSI2_1_VID_INTFC_ENB |
+			   GPR_CSI2_1_HSEL |
+			   GPR_CSI2_1_CONT_CLK_MODE |
+			   GPR_CSI2_1_S_PRG_RXHS_SETTLE(state->hs_settle));
+}
+
+static void imx8mq_mipi_csi_set_params(struct csi_state *state)
+{
+	int lanes = state->bus.num_data_lanes;
+
+	imx8mq_mipi_csi_write(state, CSI2RX_CFG_NUM_LANES, lanes - 1);
+	imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES,
+			      (0xf << lanes) & 0xf);
+	imx8mq_mipi_csi_write(state, CSI2RX_IRQ_MASK, CSI2RX_IRQ_MASK_ALL);
+	imx8mq_mipi_csi_write(state, 0x180, 1);
+	/* vid_vc */
+	imx8mq_mipi_csi_write(state, 0x184, 1);
+	imx8mq_mipi_csi_write(state, 0x188, CSI2RX_SEND_LEVEL);
+}
+
+static int imx8mq_mipi_csi_clk_enable(struct csi_state *state)
+{
+	return clk_bulk_prepare_enable(CSI2_NUM_CLKS, state->clks);
+}
+
+static void imx8mq_mipi_csi_clk_disable(struct csi_state *state)
+{
+	clk_bulk_disable_unprepare(CSI2_NUM_CLKS, state->clks);
+}
+
+static int imx8mq_mipi_csi_clk_get(struct csi_state *state)
+{
+	unsigned int i;
+
+	for (i = 0; i < CSI2_NUM_CLKS; i++)
+		state->clks[i].id = imx8mq_mipi_csi_clk_id[i];
+
+	return devm_clk_bulk_get(state->dev, CSI2_NUM_CLKS, state->clks);
+}
+
+static int imx8mq_mipi_csi_calc_hs_settle(struct csi_state *state)
+{
+	u32 width = state->format_mbus[MIPI_CSI2_PAD_SINK].width;
+	u32 height = state->format_mbus[MIPI_CSI2_PAD_SINK].height;
+	s64 link_freq;
+	u32 lane_rate;
+
+	/* Calculate the line rate from the pixel rate. */
+	link_freq = v4l2_get_link_freq(state->src_sd->ctrl_handler,
+				       state->csi2_fmt->width,
+				       state->bus.num_data_lanes * 2);
+	if (link_freq < 0) {
+		dev_err(state->dev, "Unable to obtain link frequency: %d\n",
+			(int)link_freq);
+		return link_freq;
+	}
+
+	lane_rate = link_freq * 2;
+	if (lane_rate < 80000000 || lane_rate > 1500000000) {
+		dev_dbg(state->dev, "Out-of-bound lane rate %u\n", lane_rate);
+		return -EINVAL;
+	}
+
+	/* https://community.nxp.com/t5/i-MX-Processors/Explenation-for-HS-SETTLE-parameter-in-MIPI-CSI-D-PHY-registers/m-p/764275/highlight/true#M118744 */
+	if (lane_rate < 250000000)
+		state->hs_settle = 0xb;
+	else if (lane_rate < 500000000)
+		state->hs_settle = 0x8;
+	else
+		state->hs_settle = 0x6;
+
+	dev_dbg(state->dev, "start stream: %ux%u lane rate %u hs_settle %u\n",
+		width, height, lane_rate, state->hs_settle);
+
+	return 0;
+}
+
+static int imx8mq_mipi_csi_start_stream(struct csi_state *state)
+{
+	int ret;
+
+	ret = imx8mq_mipi_csi_sw_reset(state);
+	if (ret)
+		return ret;
+
+	imx8mq_mipi_csi_set_params(state);
+	imx8mq_mipi_csi_calc_hs_settle(state);
+	imx8mq_mipi_csi_system_enable(state, true);
+
+	return 0;
+}
+
+static void imx8mq_mipi_csi_stop_stream(struct csi_state *state)
+{
+	imx8mq_mipi_csi_system_enable(state, false);
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev operations
+ */
+
+static struct csi_state *mipi_sd_to_csi2_state(struct v4l2_subdev *sdev)
+{
+	return container_of(sdev, struct csi_state, sd);
+}
+
+static int imx8mq_mipi_csi_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct csi_state *state = mipi_sd_to_csi2_state(sd);
+	int ret;
+
+	imx8mq_mipi_csi_write(state, CSI2RX_IRQ_MASK,
+			      CSI2RX_IRQ_MASK_ULPS_STATUS_CHANGE);
+
+	if (enable) {
+		ret = pm_runtime_get_sync(state->dev);
+		if (ret < 0) {
+			pm_runtime_put_noidle(state->dev);
+			return ret;
+		}
+	}
+
+	mutex_lock(&state->lock);
+
+	if (enable) {
+		if (state->state & ST_SUSPENDED) {
+			ret = -EBUSY;
+			goto unlock;
+		}
+
+		ret = imx8mq_mipi_csi_start_stream(state);
+		if (ret < 0)
+			goto unlock;
+
+		ret = v4l2_subdev_call(state->src_sd, video, s_stream, 1);
+		if (ret < 0)
+			goto unlock;
+
+		state->state |= ST_STREAMING;
+	} else {
+		v4l2_subdev_call(state->src_sd, video, s_stream, 0);
+		imx8mq_mipi_csi_stop_stream(state);
+		state->state &= ~ST_STREAMING;
+	}
+
+unlock:
+	mutex_unlock(&state->lock);
+
+	if (!enable || ret < 0)
+		pm_runtime_put(state->dev);
+
+	return ret;
+}
+
+static struct v4l2_mbus_framefmt *
+imx8mq_mipi_csi_get_format(struct csi_state *state,
+			   struct v4l2_subdev_state *sd_state,
+			   enum v4l2_subdev_format_whence which,
+			   unsigned int pad)
+{
+	if (which == V4L2_SUBDEV_FORMAT_TRY)
+		return v4l2_subdev_get_try_format(&state->sd, sd_state, pad);
+
+	return &state->format_mbus[pad];
+}
+
+static int imx8mq_mipi_csi_init_cfg(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *sd_state)
+{
+	struct csi_state *state = mipi_sd_to_csi2_state(sd);
+	struct v4l2_mbus_framefmt *fmt_sink;
+	struct v4l2_mbus_framefmt *fmt_source;
+	enum v4l2_subdev_format_whence which;
+
+	which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+	fmt_sink = imx8mq_mipi_csi_get_format(state, sd_state, which,
+					      MIPI_CSI2_PAD_SINK);
+
+	fmt_sink->code = MEDIA_BUS_FMT_SGBRG10_1X10;
+	fmt_sink->width = MIPI_CSI2_DEF_PIX_WIDTH;
+	fmt_sink->height = MIPI_CSI2_DEF_PIX_HEIGHT;
+	fmt_sink->field = V4L2_FIELD_NONE;
+
+	fmt_sink->colorspace = V4L2_COLORSPACE_RAW;
+	fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace);
+	fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace);
+	fmt_sink->quantization =
+		V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace,
+					      fmt_sink->ycbcr_enc);
+
+	/*
+	 * When called from imx8mq_mipi_csi_subdev_init() to initialize the
+	 * active configuration, sd_state is NULL, which indicates there's no
+	 * source pad configuration to set.
+	 */
+	if (!sd_state)
+		return 0;
+
+	fmt_source = imx8mq_mipi_csi_get_format(state, sd_state, which,
+						MIPI_CSI2_PAD_SOURCE);
+	*fmt_source = *fmt_sink;
+
+	return 0;
+}
+
+static int imx8mq_mipi_csi_get_fmt(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *sd_state,
+				   struct v4l2_subdev_format *sdformat)
+{
+	struct csi_state *state = mipi_sd_to_csi2_state(sd);
+	struct v4l2_mbus_framefmt *fmt;
+
+	fmt = imx8mq_mipi_csi_get_format(state, sd_state, sdformat->which,
+					 sdformat->pad);
+
+	mutex_lock(&state->lock);
+
+	sdformat->format = *fmt;
+
+	mutex_unlock(&state->lock);
+
+	return 0;
+}
+
+static int imx8mq_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
+					  struct v4l2_subdev_state *sd_state,
+					  struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct csi_state *state = mipi_sd_to_csi2_state(sd);
+
+	/*
+	 * We can't transcode in any way, the source format is identical
+	 * to the sink format.
+	 */
+	if (code->pad == MIPI_CSI2_PAD_SOURCE) {
+		struct v4l2_mbus_framefmt *fmt;
+
+		if (code->index > 0)
+			return -EINVAL;
+
+		fmt = imx8mq_mipi_csi_get_format(state, sd_state, code->which,
+						 code->pad);
+		code->code = fmt->code;
+		return 0;
+	}
+
+	if (code->pad != MIPI_CSI2_PAD_SINK)
+		return -EINVAL;
+
+	if (code->index >= ARRAY_SIZE(imx8mq_mipi_csi_formats))
+		return -EINVAL;
+
+	code->code = imx8mq_mipi_csi_formats[code->index].code;
+
+	return 0;
+}
+
+static int imx8mq_mipi_csi_set_fmt(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *sd_state,
+				   struct v4l2_subdev_format *sdformat)
+{
+	struct csi_state *state = mipi_sd_to_csi2_state(sd);
+	struct csi2_pix_format const *csi2_fmt;
+	struct v4l2_mbus_framefmt *fmt;
+
+	/*
+	 * The device can't transcode in any way, the source format can't be
+	 * modified.
+	 */
+	if (sdformat->pad == MIPI_CSI2_PAD_SOURCE)
+		return imx8mq_mipi_csi_get_fmt(sd, sd_state, sdformat);
+
+	if (sdformat->pad != MIPI_CSI2_PAD_SINK)
+		return -EINVAL;
+
+	csi2_fmt = find_csi2_format(sdformat->format.code);
+	if (!csi2_fmt)
+		csi2_fmt = &imx8mq_mipi_csi_formats[0];
+
+	fmt = imx8mq_mipi_csi_get_format(state, sd_state, sdformat->which,
+					 sdformat->pad);
+
+	mutex_lock(&state->lock);
+
+	fmt->code = csi2_fmt->code;
+	fmt->width = sdformat->format.width;
+	fmt->height = sdformat->format.height;
+
+	sdformat->format = *fmt;
+
+	/* Propagate the format from sink to source. */
+	fmt = imx8mq_mipi_csi_get_format(state, sd_state, sdformat->which,
+					 MIPI_CSI2_PAD_SOURCE);
+	*fmt = sdformat->format;
+
+	/* Store the CSI2 format descriptor for active formats. */
+	if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+		state->csi2_fmt = csi2_fmt;
+
+	mutex_unlock(&state->lock);
+
+	return 0;
+}
+
+static const struct v4l2_subdev_video_ops imx8mq_mipi_csi_video_ops = {
+	.s_stream	= imx8mq_mipi_csi_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops imx8mq_mipi_csi_pad_ops = {
+	.init_cfg		= imx8mq_mipi_csi_init_cfg,
+	.enum_mbus_code		= imx8mq_mipi_csi_enum_mbus_code,
+	.get_fmt		= imx8mq_mipi_csi_get_fmt,
+	.set_fmt		= imx8mq_mipi_csi_set_fmt,
+};
+
+static const struct v4l2_subdev_ops imx8mq_mipi_csi_subdev_ops = {
+	.video	= &imx8mq_mipi_csi_video_ops,
+	.pad	= &imx8mq_mipi_csi_pad_ops,
+};
+
+/* -----------------------------------------------------------------------------
+ * Media entity operations
+ */
+
+static const struct media_entity_operations imx8mq_mipi_csi_entity_ops = {
+	.link_validate	= v4l2_subdev_link_validate,
+	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
+};
+
+/* -----------------------------------------------------------------------------
+ * Async subdev notifier
+ */
+
+static struct csi_state *
+mipi_notifier_to_csi2_state(struct v4l2_async_notifier *n)
+{
+	return container_of(n, struct csi_state, notifier);
+}
+
+static int imx8mq_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,
+					struct v4l2_subdev *sd,
+					struct v4l2_async_subdev *asd)
+{
+	struct csi_state *state = mipi_notifier_to_csi2_state(notifier);
+	struct media_pad *sink = &state->sd.entity.pads[MIPI_CSI2_PAD_SINK];
+
+	state->src_sd = sd;
+
+	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
+					       MEDIA_LNK_FL_IMMUTABLE);
+}
+
+static const struct v4l2_async_notifier_operations imx8mq_mipi_csi_notify_ops = {
+	.bound = imx8mq_mipi_csi_notify_bound,
+};
+
+static int imx8mq_mipi_csi_async_register(struct csi_state *state)
+{
+	struct v4l2_fwnode_endpoint vep = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY,
+	};
+	struct v4l2_async_subdev *asd;
+	struct fwnode_handle *ep;
+	unsigned int i;
+	int ret;
+
+	v4l2_async_notifier_init(&state->notifier);
+
+	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(state->dev), 0, 0,
+					     FWNODE_GRAPH_ENDPOINT_NEXT);
+	if (!ep)
+		return -ENOTCONN;
+
+	ret = v4l2_fwnode_endpoint_parse(ep, &vep);
+	if (ret)
+		goto err_parse;
+
+	for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) {
+		if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) {
+			dev_err(state->dev,
+				"data lanes reordering is not supported");
+			ret = -EINVAL;
+			goto err_parse;
+		}
+	}
+
+	state->bus = vep.bus.mipi_csi2;
+
+	dev_dbg(state->dev, "data lanes: %d flags: 0x%08x\n",
+		state->bus.num_data_lanes,
+		state->bus.flags);
+
+	asd = v4l2_async_notifier_add_fwnode_remote_subdev(&state->notifier,
+							   ep, struct v4l2_async_subdev);
+	if (IS_ERR(asd)) {
+		ret = PTR_ERR(asd);
+		goto err_parse;
+	}
+
+	fwnode_handle_put(ep);
+
+	state->notifier.ops = &imx8mq_mipi_csi_notify_ops;
+
+	ret = v4l2_async_subdev_notifier_register(&state->sd, &state->notifier);
+	if (ret)
+		return ret;
+
+	return v4l2_async_register_subdev(&state->sd);
+
+err_parse:
+	fwnode_handle_put(ep);
+
+	return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Suspend/resume
+ */
+
+static int imx8mq_mipi_csi_pm_suspend(struct device *dev, bool runtime)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+	struct csi_state *state = mipi_sd_to_csi2_state(sd);
+	int ret = 0;
+
+	mutex_lock(&state->lock);
+
+	if (state->state & ST_POWERED) {
+		imx8mq_mipi_csi_stop_stream(state);
+		imx8mq_mipi_csi_clk_disable(state);
+		state->state &= ~ST_POWERED;
+		if (!runtime)
+			state->state |= ST_SUSPENDED;
+	}
+
+	mutex_unlock(&state->lock);
+
+	ret = icc_set_bw(state->icc_path, 0, 0);
+	if (ret)
+		dev_err(dev, "icc_set_bw failed with %d\n", ret);
+
+	return ret ? -EAGAIN : 0;
+}
+
+static int imx8mq_mipi_csi_pm_resume(struct device *dev, bool runtime)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+	struct csi_state *state = mipi_sd_to_csi2_state(sd);
+	int ret = 0;
+
+	ret = icc_set_bw(state->icc_path, 0, state->icc_path_bw);
+	if (ret) {
+		dev_err(dev, "icc_set_bw failed with %d\n", ret);
+		return ret;
+	}
+
+	mutex_lock(&state->lock);
+
+	if (!runtime && !(state->state & ST_SUSPENDED))
+		goto unlock;
+
+	if (!(state->state & ST_POWERED)) {
+		state->state |= ST_POWERED;
+		ret = imx8mq_mipi_csi_clk_enable(state);
+	}
+	if (state->state & ST_STREAMING) {
+		ret = imx8mq_mipi_csi_start_stream(state);
+		if (ret)
+			goto unlock;
+	}
+
+	state->state &= ~ST_SUSPENDED;
+
+unlock:
+	mutex_unlock(&state->lock);
+
+	return ret ? -EAGAIN : 0;
+}
+
+static int __maybe_unused imx8mq_mipi_csi_suspend(struct device *dev)
+{
+	return imx8mq_mipi_csi_pm_suspend(dev, false);
+}
+
+static int __maybe_unused imx8mq_mipi_csi_resume(struct device *dev)
+{
+	return imx8mq_mipi_csi_pm_resume(dev, false);
+}
+
+static int __maybe_unused imx8mq_mipi_csi_runtime_suspend(struct device *dev)
+{
+	return imx8mq_mipi_csi_pm_suspend(dev, true);
+}
+
+static int __maybe_unused imx8mq_mipi_csi_runtime_resume(struct device *dev)
+{
+	return imx8mq_mipi_csi_pm_resume(dev, true);
+}
+
+static const struct dev_pm_ops imx8mq_mipi_csi_pm_ops = {
+	SET_RUNTIME_PM_OPS(imx8mq_mipi_csi_runtime_suspend,
+			   imx8mq_mipi_csi_runtime_resume,
+			   NULL)
+	SET_SYSTEM_SLEEP_PM_OPS(imx8mq_mipi_csi_suspend, imx8mq_mipi_csi_resume)
+};
+
+/* -----------------------------------------------------------------------------
+ * Probe/remove & platform driver
+ */
+
+static int imx8mq_mipi_csi_subdev_init(struct csi_state *state)
+{
+	struct v4l2_subdev *sd = &state->sd;
+
+	v4l2_subdev_init(sd, &imx8mq_mipi_csi_subdev_ops);
+	sd->owner = THIS_MODULE;
+	snprintf(sd->name, sizeof(sd->name), "%s %s",
+		 MIPI_CSI2_SUBDEV_NAME, dev_name(state->dev));
+
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	sd->entity.ops = &imx8mq_mipi_csi_entity_ops;
+
+	sd->dev = state->dev;
+
+	state->csi2_fmt = &imx8mq_mipi_csi_formats[0];
+	imx8mq_mipi_csi_init_cfg(sd, NULL);
+
+	state->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK
+					 | MEDIA_PAD_FL_MUST_CONNECT;
+	state->pads[MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE
+					   | MEDIA_PAD_FL_MUST_CONNECT;
+	return media_entity_pads_init(&sd->entity, MIPI_CSI2_PADS_NUM,
+				      state->pads);
+}
+
+static void imx8mq_mipi_csi_release_icc(struct platform_device *pdev)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev);
+	struct csi_state *state = mipi_sd_to_csi2_state(sd);
+
+	icc_put(state->icc_path);
+}
+
+static int imx8mq_mipi_csi_init_icc(struct platform_device *pdev)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev);
+	struct csi_state *state = mipi_sd_to_csi2_state(sd);
+
+	/* Optional interconnect request */
+	state->icc_path = of_icc_get(&pdev->dev, "dram");
+	if (IS_ERR_OR_NULL(state->icc_path))
+		return PTR_ERR_OR_ZERO(state->icc_path);
+
+	state->icc_path_bw = MBps_to_icc(700);
+
+	return 0;
+}
+
+static int imx8mq_mipi_csi_parse_dt(struct csi_state *state)
+{
+	struct device *dev = state->dev;
+	struct device_node *np = state->dev->of_node;
+	struct device_node *node;
+	phandle ph;
+	u32 out_val[2];
+	int ret = 0;
+
+	state->rst = devm_reset_control_array_get_exclusive(dev);
+	if (IS_ERR(state->rst)) {
+		dev_err(dev, "Failed to get reset: %pe\n", state->rst);
+		return PTR_ERR(state->rst);
+	}
+
+	ret = of_property_read_u32_array(np, "fsl,mipi-phy-gpr", out_val,
+					 ARRAY_SIZE(out_val));
+	if (ret) {
+		dev_err(dev, "no fsl,mipi-phy-gpr property found: %d\n", ret);
+		return ret;
+	}
+
+	ph = *out_val;
+
+	node = of_find_node_by_phandle(ph);
+	if (!node) {
+		dev_err(dev, "Error finding node by phandle\n");
+		return -ENODEV;
+	}
+	state->phy_gpr = syscon_node_to_regmap(node);
+	of_node_put(node);
+	if (IS_ERR(state->phy_gpr)) {
+		dev_err(dev, "failed to get gpr regmap: %pe\n", state->phy_gpr);
+		return PTR_ERR(state->phy_gpr);
+	}
+
+	state->phy_gpr_reg = out_val[1];
+	dev_dbg(dev, "phy gpr register set to 0x%x\n", state->phy_gpr_reg);
+
+	return ret;
+}
+
+static int imx8mq_mipi_csi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct csi_state *state;
+	int ret;
+
+	state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return -ENOMEM;
+
+	state->dev = dev;
+
+	ret = imx8mq_mipi_csi_parse_dt(state);
+	if (ret < 0) {
+		dev_err(dev, "Failed to parse device tree: %d\n", ret);
+		return ret;
+	}
+
+	/* Acquire resources. */
+	state->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(state->regs))
+		return PTR_ERR(state->regs);
+
+	ret = imx8mq_mipi_csi_clk_get(state);
+	if (ret < 0)
+		return ret;
+
+	platform_set_drvdata(pdev, &state->sd);
+
+	mutex_init(&state->lock);
+
+	ret = imx8mq_mipi_csi_subdev_init(state);
+	if (ret < 0)
+		goto mutex;
+
+	ret = imx8mq_mipi_csi_init_icc(pdev);
+	if (ret)
+		goto mutex;
+
+	/* Enable runtime PM. */
+	pm_runtime_enable(dev);
+	if (!pm_runtime_enabled(dev)) {
+		ret = imx8mq_mipi_csi_pm_resume(dev, true);
+		if (ret < 0)
+			goto icc;
+	}
+
+	ret = imx8mq_mipi_csi_async_register(state);
+	if (ret < 0)
+		goto cleanup;
+
+	return 0;
+
+cleanup:
+	pm_runtime_disable(&pdev->dev);
+	imx8mq_mipi_csi_pm_suspend(&pdev->dev, true);
+
+	media_entity_cleanup(&state->sd.entity);
+	v4l2_async_notifier_unregister(&state->notifier);
+	v4l2_async_notifier_cleanup(&state->notifier);
+	v4l2_async_unregister_subdev(&state->sd);
+icc:
+	imx8mq_mipi_csi_release_icc(pdev);
+mutex:
+	mutex_destroy(&state->lock);
+
+	return ret;
+}
+
+static int imx8mq_mipi_csi_remove(struct platform_device *pdev)
+{
+	struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+	struct csi_state *state = mipi_sd_to_csi2_state(sd);
+
+	v4l2_async_notifier_unregister(&state->notifier);
+	v4l2_async_notifier_cleanup(&state->notifier);
+	v4l2_async_unregister_subdev(&state->sd);
+
+	pm_runtime_disable(&pdev->dev);
+	imx8mq_mipi_csi_pm_suspend(&pdev->dev, true);
+	media_entity_cleanup(&state->sd.entity);
+	mutex_destroy(&state->lock);
+	pm_runtime_set_suspended(&pdev->dev);
+	imx8mq_mipi_csi_release_icc(pdev);
+
+	return 0;
+}
+
+static const struct of_device_id imx8mq_mipi_csi_of_match[] = {
+	{ .compatible = "fsl,imx8mq-mipi-csi2", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, imx8mq_mipi_csi_of_match);
+
+static struct platform_driver imx8mq_mipi_csi_driver = {
+	.probe		= imx8mq_mipi_csi_probe,
+	.remove		= imx8mq_mipi_csi_remove,
+	.driver		= {
+		.of_match_table = imx8mq_mipi_csi_of_match,
+		.name		= MIPI_CSI2_DRIVER_NAME,
+		.pm		= &imx8mq_mipi_csi_pm_ops,
+	},
+};
+
+module_platform_driver(imx8mq_mipi_csi_driver);
+
+MODULE_DESCRIPTION("i.MX8MQ MIPI CSI-2 receiver driver");
+MODULE_AUTHOR("Martin Kepplinger <martin.kepplinger@puri.sm>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx8mq-mipi-csi2");