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@xxxxxxx> > > --- > > 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. >