Am Sonntag, dem 16.05.2021 um 01:55 +0300 schrieb Laurent Pinchart: > Hi Martin, > > Sorry for the late reply. > > 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? The below driver (attached; I'll send it as patches after I successfully tested myself, and cleanup and fixes obviously) results in the same situation I described above: * missing link from mipi (entity 10) -> csi (entity 1): ------------------------------------------------------ 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 ]--- thank you very much for your help, martin
// 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");