// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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");