Re: [PATCH v6 2/3] media: imx: add a driver for i.MX8MQ mipi csi rx phy and controller
From: Martin Kepplinger <hidden>
Date: 2021-07-15 07:37:37
Also in:
linux-devicetree, linux-media, linux-staging, lkml, phone-devel
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:quoted
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 <redacted> --- 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.cdiff --git a/drivers/staging/media/imx/Makefileb/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.oobj-$(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.odiff --git a/drivers/staging/media/imx/imx8mq-mipi-csi2.cb/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 driverMaybe they should be called NXP these days :-)quoted
+ * + * 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 */quoted
+ 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 ?quoted
+ 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-quoted
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-quoted
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.
_______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel