Hi Martin, On Tue, May 18, 2021 at 04:39:00PM +0200, Martin Kepplinger wrote: > Am Sonntag, dem 16.05.2021 um 01:55 +0300 schrieb Laurent Pinchart: > > On Tue, May 04, 2021 at 05:59:39PM +0200, Martin Kepplinger wrote: > > > hi Laurent, again thanks a lot for posting this series! I can't fully test > > > it, but base my work for imx8mq on it now. imx8mq includes > > > yet another mipi phy version than this and below is some very rough testing > > > code. it's not at all something I sign-off on but my following > > > problem is based on it. > > > > Unless I'm mistaken, the CSI-2 receiver in the i.MX8MQ is a completely > > different device. I wouldn't try to support it in the imx7-mipi-csis > > driver, but in a separate driver. > > > > > * configured to use both staging csi drivers > > > * the csi bridge driver at least streams frames together with the > > > nxp "yav" mipi driver > > > > > > media-ctl -p now says the output below, so one link from mipi to > > > csi is missing. > > > > > > Note that > > > > > > media-ctl --set-v4l2 "'csi':0 [fmt:SBGGR10/640x480]" > > > works in that it changes the configured format below, but > > > > > > media-ctl -l "'imx7-mipi-csis.0':1" -> "'csi':0[1]" > > > doesn't create said missing link. > > > > media-ctl can't create links, it can only enable or disable them. Link > > creation is the prerogative of drivers. > > > > > Do I maybe use that wrongly? If now, does anything come to mind that would > > > be missing specifically? > > > > The link should be created by the call to media_create_pad_link() in > > imx_media_capture_device_register(). You'll need to figure out if the > > function is called and returns an error early, or if it doesn't get > > called at all, and why. > > > > > When trying to stream anyway (if that makes sense), I get the > > > following: > > > > > > [ 2008.377470] capture_start_streaming: starting > > > [ 2008.381883] capture_find_format: calling imx_media_find_mbus_format with code 0x2006 > > > [ 2008.389671] imx7-csi 30a90000.csi1_bridge: capture_validate_fmt: capture_find_format err > > > [ 2008.397794] imx7-csi 30a90000.csi1_bridge: capture_validate_fmt: capture_find_format found colorspace 0x1 != 0x0 > > > [ 2008.407999] imx7-csi 30a90000.csi1_bridge: capture format not valid: -32 > > > > > > and if I ignore that (because I'm not yet sure whether that is specific to > > > platforms including an IPU), I get a WARN_ON from vb2_start_streaming() > > > > That I have a fix for, I'll post it as part of an imx7-media-csi > > series. > > Hi Laurent, > > You haven't posted that fix you're talking about, right? Correct. It's now fixed (see "[PATCH] media: imx: imx7-media-csi: Fix buffer return upon stream start failure", I've CC'ed you). > The below > driver (attached; I'll send it as patches after I successfully tested > myself, and cleanup and fixes obviously) Don't forget the DT bindings at that point :-) > results in the same situation I described above: > > * missing link from mipi (entity 10) -> csi (entity 1): The link is supposed to be created by v4l2_create_fwnode_links_to_pad(), called from imx7_csi_notify_bound(). Could you trace the calls and figure out what goes wrong ? > ------------------------------------------------------ > > Device topology > - entity 1: csi (2 pads, 1 link) > type V4L2 subdev subtype Unknown flags 0 > device node name /dev/v4l-subdev0 > pad0: Sink > [fmt:UYVY8_2X8/640x480 field:none colorspace:srgb xfer:srgb ycbcr:601 quantization:lim-range] > pad1: Source > [fmt:UYVY8_2X8/640x480 field:none colorspace:srgb xfer:srgb ycbcr:601 quantization:lim-range] > -> "csi capture":0 [ENABLED,IMMUTABLE] > > - entity 4: csi capture (1 pad, 1 link) > type Node subtype V4L flags 0 > device node name /dev/video0 > pad0: Sink > <- "csi":1 [ENABLED,IMMUTABLE] > > - entity 10: imx8mq-mipi-csis.0 (2 pads, 1 link) > type V4L2 subdev subtype Unknown flags 0 > device node name /dev/v4l-subdev1 > pad0: Sink > <- "hi846 2-0020":0 [] > pad1: Source > > - entity 13: hi846 2-0020 (1 pad, 1 link) > type V4L2 subdev subtype Sensor flags 0 > device node name /dev/v4l-subdev2 > pad0: Source > [fmt:SGBRG10_1X10/640x480 field:none colorspace:raw] > -> "imx8mq-mipi-csis.0":0 [] > > > * and the mentioned vb2 WARN_ON: > -------------------------------- > > [ 56.120834] imx7-csi 30a90000.csi1_bridge: begin graph walk at 'csi capture' > [ 56.120859] imx7-csi 30a90000.csi1_bridge: walk: pushing 'csi' on stack > [ 56.120865] imx7-csi 30a90000.csi1_bridge: walk: skipping entity 'csi capture' (already seen) > [ 56.120871] imx7-csi 30a90000.csi1_bridge: walk: returning entity 'csi' > [ 56.120877] imx7-csi 30a90000.csi1_bridge: walk: returning entity 'csi capture' > [ 56.127415] vb2_common_vm_open: 000000006622b5ef, refcount: 1, vma: ffffabe0b000-ffffabea1000 > [ 56.127438] vb2_dc_mmap: mapped dma addr 0xe8100000 at 0xffffabe0b000, size 614400 > [ 56.127480] vb2_common_vm_open: 00000000e689fd4f, refcount: 1, vma: ffffabd75000-ffffabe0b000 > [ 56.127488] vb2_dc_mmap: mapped dma addr 0xe8200000 at 0xffffabd75000, size 614400 > [ 56.127501] vb2_common_vm_open: 00000000485fa30a, refcount: 1, vma: ffffabcdf000-ffffabd75000 > [ 56.127509] vb2_dc_mmap: mapped dma addr 0xe8300000 at 0xffffabcdf000, size 614400 > [ 56.127522] vb2_common_vm_open: 0000000092607c6a, refcount: 1, vma: ffffabc49000-ffffabcdf000 > [ 56.127529] vb2_dc_mmap: mapped dma addr 0xe8400000 at 0xffffabc49000, size 614400 > [ 56.127579] imx7-csi 30a90000.csi1_bridge: begin graph walk at 'csi' > [ 56.127587] imx7-csi 30a90000.csi1_bridge: walk: pushing 'csi capture' on stack > [ 56.127593] imx7-csi 30a90000.csi1_bridge: walk: skipping entity 'csi' (already seen) > [ 56.127599] imx7-csi 30a90000.csi1_bridge: walk: returning entity 'csi capture' > [ 56.127604] imx7-csi 30a90000.csi1_bridge: walk: returning entity 'csi' > [ 56.128102] imx7-csi 30a90000.csi1_bridge: begin graph walk at 'csi' > [ 56.128111] imx7-csi 30a90000.csi1_bridge: walk: pushing 'csi capture' on stack > [ 56.128117] imx7-csi 30a90000.csi1_bridge: walk: skipping entity 'csi' (already seen) > [ 56.128122] imx7-csi 30a90000.csi1_bridge: walk: returning entity 'csi capture' > [ 56.128127] imx7-csi 30a90000.csi1_bridge: walk: returning entity 'csi' > [ 56.128133] imx7-csi 30a90000.csi1_bridge: pipeline start failed with -19 > [ 56.135091] ------------[ cut here ]------------ > [ 56.135102] WARNING: CPU: 3 PID: 1984 at drivers/media/common/videobuf2/videobuf2-core.c:1568 vb2_start_streaming+0xe4/0x160 [videobuf2_common] > [ 56.135151] Modules linked in: aes_ce_ccm exfat rfcomm algif_hash algif_skcipher af_alg bnep qmi_wwan cdc_wdm option usbnet usb_wwan usbserial mii ofpart mousedev spi_nor caam_jr mtd caamhash_desc caamalg_desc crypto_engine uas redpine_sdio usb_storage redpine_91x bluetooth mac80211 aes_ce_blk crypto_simd crct10dif_ce ghash_ce cfg80211 sha2_ce sha1_ce st_lsm6dsx_spi bq25890_charger pwm_vibra snd_soc_gtm601 snd_soc_simple_card snd_soc_simple_card_utils hi846 s5k3l6xx edt_ft5x06 snd_soc_wm8962 mx6s_capture imx7_media_csi(C) imx_media_common(C) videobuf2_dma_contig imx8mq_mipi_csis(C) mxc_mipi_csi2_yav videobuf2_memops videobuf2_v4l2 tps6598x videobuf2_common vcnl4000 v4l2_fwnode typec industrialio_triggered_buffer leds_lm3560 videodev mc st_lsm6dsx_i2c st_lsm6dsx kfifo_buf gnss_mtk gnss_serial gnss snd_soc_fsl_sai imx_sdma snvs_pwrkey imx_pcm_dma virt_dma snd_soc_core imx2_wdt watchdog snd_pcm_dmaengine snd_pcm snd_timer snd caam soundcore error rfkill_hks rfkill ledtrig_timer usb_f_acm > [ 56.135494] u_serial usb_f_rndis g_multi usb_f_mass_storage u_ether libcomposite ledtrig_pattern fuse ip_tables x_tables ipv6 xhci_plat_hcd xhci_hcd usbcore imx_dcss clk_bd718x7 cdns_mhdp_imx cdns_mhdp_drmcore dwc3 ulpi udc_core roles phy_fsl_imx8mq_usb usb_common > [ 56.135596] CPU: 3 PID: 1984 Comm: v4l2-ctl Tainted: G C 5.12.2-librem5-00049-g99f86eccfeae #335 > [ 56.135607] Hardware name: Purism Librem 5r4 (DT) > [ 56.135613] pstate: 80000005 (Nzcv daif -PAN -UAO -TCO BTYPE=--) > [ 56.135623] pc : vb2_start_streaming+0xe4/0x160 [videobuf2_common] > [ 56.135653] lr : vb2_start_streaming+0x74/0x160 [videobuf2_common] > [ 56.135682] sp : ffff8000148bbba0 > [ 56.135686] x29: ffff8000148bbba0 x28: ffff00001e833f00 > [ 56.135700] x27: 0000000040045612 x26: ffff800008f406a0 > [ 56.135713] x25: 0000000000000000 x24: ffff8000148bbd58 > [ 56.135725] x23: ffff0000be730138 x22: ffff00000230ab00 > [ 56.135738] x21: ffff0000be730330 x20: ffff0000be730348 > [ 56.135751] x19: 00000000ffffffed x18: 0000000000000000 > [ 56.135763] x17: 0000000000000000 x16: 0000000000000000 > [ 56.135776] x15: 0000000000000030 x14: ffffffffffffffff > [ 56.135788] x13: ffff8000948bb737 x12: ffff8000148bb73f > [ 56.135801] x11: ffff80001152a7a0 x10: 00000000ffffe000 > [ 56.135813] x9 : ffff800008f3c900 x8 : ffff80001147a7a0 > [ 56.135826] x7 : ffff80001152a7a0 x6 : 0000000000000000 > [ 56.135838] x5 : 0000000000000000 x4 : 0000000000000000 > [ 56.135850] x3 : ffff0000be730344 x2 : 0000000000000000 > [ 56.135863] x1 : ffff800008fe4000 x0 : ffff0000253d29f0 > [ 56.135877] Call trace: > [ 56.135882] vb2_start_streaming+0xe4/0x160 [videobuf2_common] > [ 56.135912] vb2_core_streamon+0x9c/0x1a0 [videobuf2_common] > [ 56.135940] vb2_ioctl_streamon+0x68/0xbc [videobuf2_v4l2] > [ 56.135964] v4l_streamon+0x30/0x40 [videodev] > [ 56.136063] __video_do_ioctl+0x194/0x3f4 [videodev] > [ 56.136145] video_usercopy+0x1a4/0x770 [videodev] > [ 56.136226] video_ioctl2+0x24/0x40 [videodev] > [ 56.136305] v4l2_ioctl+0x4c/0x70 [videodev] > [ 56.136385] __arm64_sys_ioctl+0xb4/0xfc > [ 56.136401] el0_svc_common.constprop.0+0x68/0x130 > [ 56.136416] do_el0_svc+0x28/0x34 > [ 56.136426] el0_svc+0x2c/0x54 > [ 56.136438] el0_sync_handler+0x1a4/0x1b0 > [ 56.136449] el0_sync+0x174/0x180 > [ 56.136459] ---[ end trace 122c8abc5f14e4e5 ]--- Hopefully the patch mentioned above will fix this. > // SPDX-License-Identifier: GPL-2.0 > /* > * Freescale i.MX8MQ SoC series MIPI-CSI receiver driver > * > * Copyright (C) 2021 Purism SPC > * Copyright (C) 2019 Linaro Ltd > * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved. > * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd. > * > */ > > #include <linux/clk.h> > #include <linux/debugfs.h> > #include <linux/delay.h> > #include <linux/errno.h> > #include <linux/interrupt.h> > #include <linux/io.h> > #include <linux/kernel.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/mfd/syscon.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 CSIS_DRIVER_NAME "imx8mq-mipi-csis" > #define CSIS_SUBDEV_NAME CSIS_DRIVER_NAME > > #define CSIS_PAD_SINK 0 > #define CSIS_PAD_SOURCE 1 > #define CSIS_PADS_NUM 2 > > #define MIPI_CSIS_DEF_PIX_WIDTH 640 > #define MIPI_CSIS_DEF_PIX_HEIGHT 480 > > /* Register map definition */ > > /* i.MX8MQ CSI-2 controller CSR */ > /* TODO 0x100, to dts? */ > #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_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_DISABLE_PAYLOAD_1 0x130 > > enum { > ST_POWERED = 1, > ST_STREAMING = 2, > ST_SUSPENDED = 4, > }; > > static const char * const mipi_csis_clk_id[] = { > "clk_core", > "clk_esc", > "clk_pxl", > "clk_clko2", > }; > > struct csis_imx8mq_hw_reset { > struct regmap *src; > u8 req_src; > u8 rst_val; > }; > > struct csis_imx8mq_phy_gpr { > struct regmap *gpr; > u8 req_src; > }; > > #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) > /* > * rxhs_settle[0] ... <720x480 > * rxhs_settle[1] ... >720*480 > * > * 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 > */ > static u8 rxhs_settle[2] = { 0x14, 0x9 }; > > struct csi_state { > struct device *dev; > void __iomem *regs; > struct clk_bulk_data *clks; > struct reset_control *mrst; > struct regulator *mipi_phy_regulator; > u8 index; > > struct v4l2_subdev sd; > struct media_pad pads[CSIS_PADS_NUM]; > struct v4l2_async_notifier notifier; > struct v4l2_subdev *src_sd; > > struct v4l2_fwnode_bus_mipi_csi2 bus; > u32 hs_settle; > u32 clk_settle; > > struct mutex lock; /* Protect csis_fmt, format_mbus and state */ > u32 state; > > struct dentry *debugfs_root; > bool debug; > > struct csis_imx8mq_hw_reset hw_reset; > struct csis_imx8mq_phy_gpr phy_gpr; > u32 send_level; > }; > > /* ----------------------------------------------------------------------------- > * Format helpers > */ > > /* ----------------------------------------------------------------------------- > * Hardware configuration > */ > > static inline u32 mipi_csis_read(struct csi_state *state, u32 reg) > { > return readl(state->regs + reg); > } > > static inline void mipi_csis_write(struct csi_state *state, u32 reg, u32 val) > { > writel(val, state->regs + reg); > } > > static void mipi_csis_enable_interrupts(struct csi_state *state, bool on) > { > return; > } > > static void mipi_csis_sw_reset(struct csi_state *state) > { > /* TODO yav: mxc_mipi_csi1_phy_reset */ > > struct device *dev = state->dev; > struct device_node *np = dev->of_node; > struct device_node *node; > phandle phandle; > u32 out_val[3]; > int ret; > > dev_dbg(dev, "%s: starting\n", __func__); > > ret = of_property_read_u32_array(np, "csis-phy-reset", out_val, 3); > if (ret) { > dev_info(dev, "no csis-hw-reset property found: %d\n", ret); > return; > } > > phandle = *out_val; > > node = of_find_node_by_phandle(phandle); > if (!node) { > ret = PTR_ERR(node); > dev_dbg(dev, "not find src node by phandle: %d\n", ret); > } > state->hw_reset.src = syscon_node_to_regmap(node); > if (IS_ERR(state->hw_reset.src)) { > ret = PTR_ERR(state->hw_reset.src); > dev_err(dev, "failed to get src regmap: %d\n", ret); > } > of_node_put(node); > if (ret < 0) > return; > > state->hw_reset.req_src = out_val[1]; > state->hw_reset.rst_val = out_val[2]; > > /* reset imx8mq mipi phy */ > regmap_update_bits(state->hw_reset.src, > state->hw_reset.req_src, > state->hw_reset.rst_val, > state->hw_reset.rst_val); > msleep(20); > > dev_dbg(dev, "%s: done\n", __func__); > > return; > } > > static void mipi_csis_system_enable(struct csi_state *state, int on) > { > struct device *dev = state->dev; > struct device_node *np = dev->of_node; > struct device_node *node; > phandle phandle; > u32 out_val[2]; > int ret; > > if (!on) { > /* Disable Data lanes */ > mipi_csis_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, 0xf); > return; > } > > ret = of_property_read_u32_array(np, "phy-gpr", out_val, 2); > if (ret) { > dev_info(dev, "no phy-gpr property found\n"); > return; > } > > phandle = *out_val; > > node = of_find_node_by_phandle(phandle); > if (!node) { > dev_dbg(dev, "not find gpr node by phandle\n"); > ret = PTR_ERR(node); > } > state->phy_gpr.gpr = syscon_node_to_regmap(node); > if (IS_ERR(state->phy_gpr.gpr)) { > dev_err(dev, "failed to get gpr regmap\n"); > ret = PTR_ERR(state->phy_gpr.gpr); > } > of_node_put(node); > if (ret < 0) > return; > > state->phy_gpr.req_src = out_val[1]; > > regmap_update_bits(state->phy_gpr.gpr, > state->phy_gpr.req_src, > 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)); > > dev_dbg(dev, "%s: hs_settle: 0x%X\n", __func__, state->hs_settle); > > return; > } > > static int mipi_csis_calculate_params(struct csi_state *state) > { > s64 link_freq; > u32 lane_rate; > > state->hs_settle = rxhs_settle[0]; > #if 0 > /* Calculate the line rate from the pixel rate. */ > link_freq = v4l2_get_link_freq(state->src_sd->ctrl_handler, > state->csis_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; > } > > /* > * The HSSETTLE counter value is document in a table, but can also > * easily be calculated. Hardcode the CLKSETTLE value to 0 for now > * (which is documented as corresponding to CSI-2 v0.87 to v1.00) until > * we figure out how to compute it correctly. > */ > state->hs_settle = (lane_rate - 5000000) / 45000000; > state->clk_settle = 0; > > dev_dbg(state->dev, "lane rate %u, Tclk_settle %u, Ths_settle %u\n", > lane_rate, state->clk_settle, state->hs_settle); > #endif > return 0; > } > > static void mipi_csis_set_params(struct csi_state *state) > { > int lanes = state->bus.num_data_lanes; > u32 val = 0; > int i; > > /* Lanes */ > mipi_csis_write(state, CSI2RX_CFG_NUM_LANES, lanes - 1); > > dev_err(state->dev, "imx8mq: %d lanes\n", lanes); > > for (i = 0; i < lanes; i++) > val |= (1 << i); > > val = 0xF & ~val; > mipi_csis_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, val); > > dev_err(state->dev, "imx8mq: CSI2RX_CFG_DISABLE_DATA_LANES: 0x%X\n", val); > > /* Mask interrupt */ > // Don't let ULPS (ultra-low power status) interrupts flood > mipi_csis_write(state, CSI2RX_IRQ_MASK, 0x1ff); > > mipi_csis_write(state, 0x180, 1); > /* vid_vc */ > mipi_csis_write(state, 0x184, 1); > mipi_csis_write(state, 0x188, state->send_level); > } > > static int mipi_csis_clk_enable(struct csi_state *state) > { > return clk_bulk_prepare_enable(ARRAY_SIZE(mipi_csis_clk_id), state->clks); > } > > static void mipi_csis_clk_disable(struct csi_state *state) > { > clk_bulk_disable_unprepare(ARRAY_SIZE(mipi_csis_clk_id), state->clks); > } > > static int mipi_csis_clk_get(struct csi_state *state) > { > unsigned int i; > int ret; > > state->clks = devm_kcalloc(state->dev, ARRAY_SIZE(mipi_csis_clk_id), > sizeof(*state->clks), GFP_KERNEL); > > if (!state->clks) > return -ENOMEM; > > for (i = 0; i < ARRAY_SIZE(mipi_csis_clk_id); i++) > state->clks[i].id = mipi_csis_clk_id[i]; > > ret = devm_clk_bulk_get(state->dev, ARRAY_SIZE(mipi_csis_clk_id), > state->clks); > return ret; > } > > static void mipi_csis_start_stream(struct csi_state *state) > { > mipi_csis_sw_reset(state); > mipi_csis_set_params(state); > mipi_csis_system_enable(state, true); > mipi_csis_enable_interrupts(state, true); > } > > static void mipi_csis_stop_stream(struct csi_state *state) > { > mipi_csis_enable_interrupts(state, false); > mipi_csis_system_enable(state, false); > } > > /* ----------------------------------------------------------------------------- > * PHY regulator and reset > */ > > static int mipi_csis_phy_enable(struct csi_state *state) > { > return 0; > } > > static int mipi_csis_phy_disable(struct csi_state *state) > { > return 0; > } > > static void mipi_csis_phy_reset(struct csi_state *state) > { > return; > } > > static int mipi_csis_phy_init(struct csi_state *state) > { > return 0; > } > > /* ----------------------------------------------------------------------------- > * Debug > */ > > static void mipi_csis_clear_counters(struct csi_state *state) > { > return; > } > > static void mipi_csis_log_counters(struct csi_state *state, bool non_errors) > { > return; > } > > static int mipi_csis_dump_regs(struct csi_state *state) > { > return 0; > } > > static int mipi_csis_dump_regs_show(struct seq_file *m, void *private) > { > struct csi_state *state = m->private; > > return mipi_csis_dump_regs(state); > } > DEFINE_SHOW_ATTRIBUTE(mipi_csis_dump_regs); > > static void mipi_csis_debugfs_init(struct csi_state *state) > { > state->debugfs_root = debugfs_create_dir(dev_name(state->dev), NULL); > > debugfs_create_bool("debug_enable", 0600, state->debugfs_root, > &state->debug); > debugfs_create_file("dump_regs", 0600, state->debugfs_root, state, > &mipi_csis_dump_regs_fops); > } > > static void mipi_csis_debugfs_exit(struct csi_state *state) > { > debugfs_remove_recursive(state->debugfs_root); > } > > /* ----------------------------------------------------------------------------- > * V4L2 subdev operations > */ > > static struct csi_state *mipi_sd_to_csis_state(struct v4l2_subdev *sdev) > { > return container_of(sdev, struct csi_state, sd); > } > > static int mipi_csis_s_stream(struct v4l2_subdev *sd, int enable) > { > struct csi_state *state = mipi_sd_to_csis_state(sd); > int ret; > > mipi_csis_write(state, CSI2RX_IRQ_MASK, 0x008); > > dev_dbg(state->dev, "%s: enable: %d\n", __func__, enable); > > if (enable) { > ret = mipi_csis_calculate_params(state); > if (ret < 0) > return ret; > > mipi_csis_clear_counters(state); > > ret = pm_runtime_get_sync(state->dev); > if (ret < 0) { > pm_runtime_put_noidle(state->dev); > return ret; > } > ret = v4l2_subdev_call(state->src_sd, core, s_power, 1); > if (ret < 0 && ret != -ENOIOCTLCMD) > goto done; > } > > mutex_lock(&state->lock); > > if (enable) { > if (state->state & ST_SUSPENDED) { > ret = -EBUSY; > goto unlock; > } > > mipi_csis_start_stream(state); > ret = v4l2_subdev_call(state->src_sd, video, s_stream, 1); > if (ret < 0) > goto unlock; > > mipi_csis_log_counters(state, true); > > state->state |= ST_STREAMING; > } else { > v4l2_subdev_call(state->src_sd, video, s_stream, 0); > ret = v4l2_subdev_call(state->src_sd, core, s_power, 0); > if (ret == -ENOIOCTLCMD) > ret = 0; > mipi_csis_stop_stream(state); > state->state &= ~ST_STREAMING; > if (state->debug) > mipi_csis_log_counters(state, true); > } > > unlock: > mutex_unlock(&state->lock); > > done: > if (!enable || ret < 0) > pm_runtime_put(state->dev); > > return ret; > } > > static int mipi_csis_get_fmt(struct v4l2_subdev *sd, > struct v4l2_subdev_pad_config *cfg, > struct v4l2_subdev_format *sdformat) > { > struct csi_state *state = mipi_sd_to_csis_state(sd); > > return v4l2_subdev_call(state->src_sd, pad, get_fmt, NULL, sdformat); > } > > static int mipi_csis_enum_mbus_code(struct v4l2_subdev *sd, > struct v4l2_subdev_pad_config *cfg, > struct v4l2_subdev_mbus_code_enum *code) > { > struct csi_state *state = mipi_sd_to_csis_state(sd); > > return v4l2_subdev_call(state->src_sd, pad, enum_mbus_code, NULL, code); > } > > static int mipi_csis_set_fmt(struct v4l2_subdev *sd, > struct v4l2_subdev_pad_config *cfg, > struct v4l2_subdev_format *sdformat) > { > struct csi_state *state = mipi_sd_to_csis_state(sd); > > /* > * The CSIS can't transcode in any way, the source format can't be > * modified. > */ > if (sdformat->pad == CSIS_PAD_SOURCE) > return mipi_csis_get_fmt(sd, cfg, sdformat); > > if (sdformat->pad != CSIS_PAD_SINK) > return -EINVAL; > > if (sdformat->format.width * sdformat->format.height > 720 * 480) { > state->hs_settle = rxhs_settle[1]; > } else { > state->hs_settle = rxhs_settle[0]; > } > state->send_level = 64; > > dev_dbg(state->dev, > "%s: format %dx%d send_level %d hs_settle 0x%X\n", __func__, > sdformat->format.width, sdformat->format.height, > state->send_level, state->hs_settle); > > return v4l2_subdev_call(state->src_sd, pad, set_fmt, NULL, sdformat); > } > > static int mipi_csis_log_status(struct v4l2_subdev *sd) > { > struct csi_state *state = mipi_sd_to_csis_state(sd); > > mutex_lock(&state->lock); > mipi_csis_log_counters(state, true); > if (state->debug && (state->state & ST_POWERED)) > mipi_csis_dump_regs(state); > mutex_unlock(&state->lock); > > return 0; > } > > static const struct v4l2_subdev_core_ops mipi_csis_core_ops = { > .log_status = mipi_csis_log_status, > }; > > static const struct v4l2_subdev_video_ops mipi_csis_video_ops = { > .s_stream = mipi_csis_s_stream, > }; > > static const struct v4l2_subdev_pad_ops mipi_csis_pad_ops = { > .enum_mbus_code = mipi_csis_enum_mbus_code, > .get_fmt = mipi_csis_get_fmt, > .set_fmt = mipi_csis_set_fmt, > }; > > static const struct v4l2_subdev_ops mipi_csis_subdev_ops = { > .core = &mipi_csis_core_ops, > .video = &mipi_csis_video_ops, > .pad = &mipi_csis_pad_ops, > }; > > /* ----------------------------------------------------------------------------- > * Media entity operations > */ > > static int mipi_csis_link_setup(struct media_entity *entity, > const struct media_pad *local_pad, > const struct media_pad *remote_pad, u32 flags) > { > struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); > struct csi_state *state = mipi_sd_to_csis_state(sd); > struct v4l2_subdev *remote_sd; > > dev_dbg(state->dev, "link setup %s -> %s", remote_pad->entity->name, > local_pad->entity->name); > > /* We only care about the link to the source. */ > if (!(local_pad->flags & MEDIA_PAD_FL_SINK)) > return 0; > > remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); > > if (flags & MEDIA_LNK_FL_ENABLED) { > if (state->src_sd) > return -EBUSY; > > state->src_sd = remote_sd; > } else { > state->src_sd = NULL; > } > > return 0; > } > > static const struct media_entity_operations mipi_csis_entity_ops = { > .link_setup = mipi_csis_link_setup, > .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_csis_state(struct v4l2_async_notifier *n) > { > return container_of(n, struct csi_state, notifier); > } > > static int mipi_csis_notify_bound(struct v4l2_async_notifier *notifier, > struct v4l2_subdev *sd, > struct v4l2_async_subdev *asd) > { > struct csi_state *state = mipi_notifier_to_csis_state(notifier); > struct media_pad *sink = &state->sd.entity.pads[CSIS_PAD_SINK]; > > return v4l2_create_fwnode_links_to_pad(sd, sink, 0); > } > > static const struct v4l2_async_notifier_operations mipi_csis_notify_ops = { > .bound = mipi_csis_notify_bound, > }; > > static int mipi_csis_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"); > goto err_parse; > } > } > > state->bus = vep.bus.mipi_csi2; > > dev_dbg(state->dev, "data lanes: %d\n", state->bus.num_data_lanes); > dev_dbg(state->dev, "flags: 0x%08x\n", 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 = &mipi_csis_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 mipi_csis_pm_suspend(struct device *dev, bool runtime) > { > struct v4l2_subdev *sd = dev_get_drvdata(dev); > struct csi_state *state = mipi_sd_to_csis_state(sd); > int ret = 0; > > mutex_lock(&state->lock); > if (state->state & ST_POWERED) { > mipi_csis_stop_stream(state); > ret = mipi_csis_phy_disable(state); > if (ret) > goto unlock; > mipi_csis_clk_disable(state); > state->state &= ~ST_POWERED; > if (!runtime) > state->state |= ST_SUSPENDED; > } > > unlock: > mutex_unlock(&state->lock); > > return ret ? -EAGAIN : 0; > } > > static int mipi_csis_pm_resume(struct device *dev, bool runtime) > { > struct v4l2_subdev *sd = dev_get_drvdata(dev); > struct csi_state *state = mipi_sd_to_csis_state(sd); > int ret = 0; > > mutex_lock(&state->lock); > if (!runtime && !(state->state & ST_SUSPENDED)) > goto unlock; > > if (!(state->state & ST_POWERED)) { > ret = mipi_csis_phy_enable(state); > if (ret) > goto unlock; > > state->state |= ST_POWERED; > mipi_csis_clk_enable(state); > } > if (state->state & ST_STREAMING) > mipi_csis_start_stream(state); > > state->state &= ~ST_SUSPENDED; > > unlock: > mutex_unlock(&state->lock); > > return ret ? -EAGAIN : 0; > } > > static int __maybe_unused mipi_csis_suspend(struct device *dev) > { > return mipi_csis_pm_suspend(dev, false); > } > > static int __maybe_unused mipi_csis_resume(struct device *dev) > { > return mipi_csis_pm_resume(dev, false); > } > > static int __maybe_unused mipi_csis_runtime_suspend(struct device *dev) > { > return mipi_csis_pm_suspend(dev, true); > } > > static int __maybe_unused mipi_csis_runtime_resume(struct device *dev) > { > return mipi_csis_pm_resume(dev, true); > } > > static const struct dev_pm_ops mipi_csis_pm_ops = { > SET_RUNTIME_PM_OPS(mipi_csis_runtime_suspend, mipi_csis_runtime_resume, > NULL) > SET_SYSTEM_SLEEP_PM_OPS(mipi_csis_suspend, mipi_csis_resume) > }; > > /* ----------------------------------------------------------------------------- > * Probe/remove & platform driver > */ > > static int mipi_csis_subdev_init(struct csi_state *state) > { > struct v4l2_subdev *sd = &state->sd; > > v4l2_subdev_init(sd, &mipi_csis_subdev_ops); > sd->owner = THIS_MODULE; > snprintf(sd->name, sizeof(sd->name), "%s.%d", > CSIS_SUBDEV_NAME, state->index); > > sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > sd->ctrl_handler = NULL; > > sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; > sd->entity.ops = &mipi_csis_entity_ops; > > sd->dev = state->dev; > > state->pads[CSIS_PAD_SINK].flags = MEDIA_PAD_FL_SINK > | MEDIA_PAD_FL_MUST_CONNECT; > state->pads[CSIS_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE > | MEDIA_PAD_FL_MUST_CONNECT; > return media_entity_pads_init(&sd->entity, CSIS_PADS_NUM, > state->pads); > } > > static int mipi_csis_parse_dt(struct csi_state *state) > { > struct device_node *node = state->dev->of_node; > > return 0; > } > > static int mipi_csis_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; > > mutex_init(&state->lock); > > state->dev = dev; > > /* Parse DT properties. */ > ret = mipi_csis_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 = mipi_csis_phy_init(state); > if (ret < 0) > return ret; > > ret = mipi_csis_clk_get(state); > if (ret < 0) > return ret; > > /* Reset PHY and enable the clocks. */ > mipi_csis_phy_reset(state); > > ret = mipi_csis_clk_enable(state); > if (ret < 0) { > dev_err(state->dev, "failed to enable clocks: %d\n", ret); > return ret; > } > > /* Initialize and register the subdev. */ > ret = mipi_csis_subdev_init(state); > if (ret < 0) > goto disable_clock; > > platform_set_drvdata(pdev, &state->sd); > > ret = mipi_csis_async_register(state); > if (ret < 0) { > dev_err(dev, "async register failed: %d\n", ret); > goto cleanup; > } > > /* Initialize debugfs. */ > mipi_csis_debugfs_init(state); > > /* Enable runtime PM. */ > pm_runtime_enable(dev); > if (!pm_runtime_enabled(dev)) { > ret = mipi_csis_pm_resume(dev, true); > if (ret < 0) > goto unregister_all; > } > > dev_info(dev, "lanes: %d\n", > state->bus.num_data_lanes); > > return 0; > > unregister_all: > mipi_csis_debugfs_exit(state); > cleanup: > media_entity_cleanup(&state->sd.entity); > v4l2_async_notifier_unregister(&state->notifier); > v4l2_async_notifier_cleanup(&state->notifier); > v4l2_async_unregister_subdev(&state->sd); > disable_clock: > mipi_csis_clk_disable(state); > mutex_destroy(&state->lock); > > return ret; > } > > static int mipi_csis_remove(struct platform_device *pdev) > { > struct v4l2_subdev *sd = platform_get_drvdata(pdev); > struct csi_state *state = mipi_sd_to_csis_state(sd); > > mipi_csis_debugfs_exit(state); > v4l2_async_notifier_unregister(&state->notifier); > v4l2_async_notifier_cleanup(&state->notifier); > v4l2_async_unregister_subdev(&state->sd); > > pm_runtime_disable(&pdev->dev); > mipi_csis_pm_suspend(&pdev->dev, true); > mipi_csis_clk_disable(state); > media_entity_cleanup(&state->sd.entity); > mutex_destroy(&state->lock); > pm_runtime_set_suspended(&pdev->dev); > > return 0; > } > > static const struct of_device_id mipi_csis_of_match[] = { > { .compatible = "fsl,imx8mq-mipi-csi2",}, > { /* sentinel */ }, > }; > MODULE_DEVICE_TABLE(of, mipi_csis_of_match); > > static struct platform_driver mipi_csis_driver = { > .probe = mipi_csis_probe, > .remove = mipi_csis_remove, > .driver = { > .of_match_table = mipi_csis_of_match, > .name = CSIS_DRIVER_NAME, > .pm = &mipi_csis_pm_ops, > }, > }; > > module_platform_driver(mipi_csis_driver); > > MODULE_DESCRIPTION("i.MX8MQ MIPI CSI-2 receiver driver"); > MODULE_LICENSE("GPL v2"); > MODULE_ALIAS("platform:imx8mq-mipi-csi2"); -- Regards, Laurent Pinchart