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 |
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");
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.
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. >
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 ?
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.
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!
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.
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;
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 --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");
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