brintos

brintos / linux-shallow public Read only

0
0
Text · 24.1 KiB · d4a6c55 Raw
970 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * NXP i.MX8MQ SoC series MIPI-CSI2 receiver driver4 *5 * Copyright (C) 2021 Purism SPC6 */7 8#include <linux/clk.h>9#include <linux/clk-provider.h>10#include <linux/delay.h>11#include <linux/errno.h>12#include <linux/interconnect.h>13#include <linux/interrupt.h>14#include <linux/io.h>15#include <linux/kernel.h>16#include <linux/mfd/syscon.h>17#include <linux/module.h>18#include <linux/mutex.h>19#include <linux/of.h>20#include <linux/platform_device.h>21#include <linux/pm_runtime.h>22#include <linux/regmap.h>23#include <linux/regulator/consumer.h>24#include <linux/reset.h>25#include <linux/spinlock.h>26 27#include <media/v4l2-common.h>28#include <media/v4l2-device.h>29#include <media/v4l2-fwnode.h>30#include <media/v4l2-mc.h>31#include <media/v4l2-subdev.h>32 33#define MIPI_CSI2_DRIVER_NAME			"imx8mq-mipi-csi2"34#define MIPI_CSI2_SUBDEV_NAME			MIPI_CSI2_DRIVER_NAME35 36#define MIPI_CSI2_PAD_SINK			037#define MIPI_CSI2_PAD_SOURCE			138#define MIPI_CSI2_PADS_NUM			239 40#define MIPI_CSI2_DEF_PIX_WIDTH			64041#define MIPI_CSI2_DEF_PIX_HEIGHT		48042 43/* Register map definition */44 45/* i.MX8MQ CSI-2 controller CSR */46#define CSI2RX_CFG_NUM_LANES			0x10047#define CSI2RX_CFG_DISABLE_DATA_LANES		0x10448#define CSI2RX_BIT_ERR				0x10849#define CSI2RX_IRQ_STATUS			0x10c50#define CSI2RX_IRQ_MASK				0x11051#define CSI2RX_IRQ_MASK_ALL			0x1ff52#define CSI2RX_IRQ_MASK_ULPS_STATUS_CHANGE	0x853#define CSI2RX_ULPS_STATUS			0x11454#define CSI2RX_PPI_ERRSOT_HS			0x11855#define CSI2RX_PPI_ERRSOTSYNC_HS		0x11c56#define CSI2RX_PPI_ERRESC			0x12057#define CSI2RX_PPI_ERRSYNCESC			0x12458#define CSI2RX_PPI_ERRCONTROL			0x12859#define CSI2RX_CFG_DISABLE_PAYLOAD_0		0x12c60#define CSI2RX_CFG_VID_VC_IGNORE		0x18061#define CSI2RX_CFG_VID_VC			0x18462#define CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL	0x18863#define CSI2RX_CFG_DISABLE_PAYLOAD_1		0x13064 65enum {66	ST_POWERED	= 1,67	ST_STREAMING	= 2,68	ST_SUSPENDED	= 4,69};70 71enum imx8mq_mipi_csi_clk {72	CSI2_CLK_CORE,73	CSI2_CLK_ESC,74	CSI2_CLK_UI,75	CSI2_NUM_CLKS,76};77 78static const char * const imx8mq_mipi_csi_clk_id[CSI2_NUM_CLKS] = {79	[CSI2_CLK_CORE] = "core",80	[CSI2_CLK_ESC] = "esc",81	[CSI2_CLK_UI] = "ui",82};83 84#define CSI2_NUM_CLKS	ARRAY_SIZE(imx8mq_mipi_csi_clk_id)85 86#define	GPR_CSI2_1_RX_ENABLE		BIT(13)87#define	GPR_CSI2_1_VID_INTFC_ENB	BIT(12)88#define	GPR_CSI2_1_HSEL			BIT(10)89#define	GPR_CSI2_1_CONT_CLK_MODE	BIT(8)90#define	GPR_CSI2_1_S_PRG_RXHS_SETTLE(x)	(((x) & 0x3f) << 2)91 92/*93 * The send level configures the number of entries that must accumulate in94 * the Pixel FIFO before the data will be transferred to the video output.95 * The exact value needed for this configuration is dependent on the rate at96 * which the sensor transfers data to the CSI-2 Controller and the user97 * video clock.98 *99 * The calculation is the classical rate-in rate-out type of problem: If the100 * video bandwidth is 10% faster than the incoming mipi data and the video101 * line length is 500 pixels, then the fifo should be allowed to fill102 * 10% of the line length or 50 pixels. If the gap data is ok, then the level103 * can be set to 16 and ignored.104 */105#define CSI2RX_SEND_LEVEL			64106 107struct csi_state {108	struct device *dev;109	void __iomem *regs;110	struct clk_bulk_data clks[CSI2_NUM_CLKS];111	struct reset_control *rst;112	struct regulator *mipi_phy_regulator;113 114	struct v4l2_subdev sd;115	struct media_pad pads[MIPI_CSI2_PADS_NUM];116	struct v4l2_async_notifier notifier;117	struct v4l2_subdev *src_sd;118 119	struct v4l2_mbus_config_mipi_csi2 bus;120 121	struct mutex lock; /* Protect state */122	u32 state;123 124	struct regmap *phy_gpr;125	u8 phy_gpr_reg;126 127	struct icc_path			*icc_path;128	s32				icc_path_bw;129};130 131/* -----------------------------------------------------------------------------132 * Format helpers133 */134 135struct csi2_pix_format {136	u32 code;137	u8 width;138};139 140static const struct csi2_pix_format imx8mq_mipi_csi_formats[] = {141	/* RAW (Bayer and greyscale) formats. */142	{143		.code = MEDIA_BUS_FMT_SBGGR8_1X8,144		.width = 8,145	}, {146		.code = MEDIA_BUS_FMT_SGBRG8_1X8,147		.width = 8,148	}, {149		.code = MEDIA_BUS_FMT_SGRBG8_1X8,150		.width = 8,151	}, {152		.code = MEDIA_BUS_FMT_SRGGB8_1X8,153		.width = 8,154	}, {155		.code = MEDIA_BUS_FMT_Y8_1X8,156		.width = 8,157	}, {158		.code = MEDIA_BUS_FMT_SBGGR10_1X10,159		.width = 10,160	}, {161		.code = MEDIA_BUS_FMT_SGBRG10_1X10,162		.width = 10,163	}, {164		.code = MEDIA_BUS_FMT_SGRBG10_1X10,165		.width = 10,166	}, {167		.code = MEDIA_BUS_FMT_SRGGB10_1X10,168		.width = 10,169	}, {170		.code = MEDIA_BUS_FMT_Y10_1X10,171		.width = 10,172	}, {173		.code = MEDIA_BUS_FMT_SBGGR12_1X12,174		.width = 12,175	}, {176		.code = MEDIA_BUS_FMT_SGBRG12_1X12,177		.width = 12,178	}, {179		.code = MEDIA_BUS_FMT_SGRBG12_1X12,180		.width = 12,181	}, {182		.code = MEDIA_BUS_FMT_SRGGB12_1X12,183		.width = 12,184	}, {185		.code = MEDIA_BUS_FMT_Y12_1X12,186		.width = 12,187	}, {188		.code = MEDIA_BUS_FMT_SBGGR14_1X14,189		.width = 14,190	}, {191		.code = MEDIA_BUS_FMT_SGBRG14_1X14,192		.width = 14,193	}, {194		.code = MEDIA_BUS_FMT_SGRBG14_1X14,195		.width = 14,196	}, {197		.code = MEDIA_BUS_FMT_SRGGB14_1X14,198		.width = 14,199	},200	/* YUV formats */201	{202		.code = MEDIA_BUS_FMT_YUYV8_1X16,203		.width = 16,204	}, {205		.code = MEDIA_BUS_FMT_UYVY8_1X16,206		.width = 16,207	}208};209 210static const struct csi2_pix_format *find_csi2_format(u32 code)211{212	unsigned int i;213 214	for (i = 0; i < ARRAY_SIZE(imx8mq_mipi_csi_formats); i++)215		if (code == imx8mq_mipi_csi_formats[i].code)216			return &imx8mq_mipi_csi_formats[i];217	return NULL;218}219 220/* -----------------------------------------------------------------------------221 * Hardware configuration222 */223 224static inline void imx8mq_mipi_csi_write(struct csi_state *state, u32 reg, u32 val)225{226	writel(val, state->regs + reg);227}228 229static int imx8mq_mipi_csi_sw_reset(struct csi_state *state)230{231	int ret;232 233	/*234	 * these are most likely self-clearing reset bits. to make it235	 * more clear, the reset-imx7 driver should implement the236	 * .reset() operation.237	 */238	ret = reset_control_assert(state->rst);239	if (ret < 0) {240		dev_err(state->dev, "Failed to assert resets: %d\n", ret);241		return ret;242	}243 244	return 0;245}246 247static void imx8mq_mipi_csi_set_params(struct csi_state *state)248{249	int lanes = state->bus.num_data_lanes;250 251	imx8mq_mipi_csi_write(state, CSI2RX_CFG_NUM_LANES, lanes - 1);252	imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES,253			      (0xf << lanes) & 0xf);254	imx8mq_mipi_csi_write(state, CSI2RX_IRQ_MASK, CSI2RX_IRQ_MASK_ALL);255	/*256	 * 0x180 bit 0 controls the Virtual Channel behaviour: when set the257	 * interface ignores the Virtual Channel (VC) field in received packets;258	 * when cleared it causes the interface to only accept packets whose VC259	 * matches the value to which VC is set at offset 0x184.260	 */261	imx8mq_mipi_csi_write(state, CSI2RX_CFG_VID_VC_IGNORE, 1);262	imx8mq_mipi_csi_write(state, CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL,263			      CSI2RX_SEND_LEVEL);264}265 266static int imx8mq_mipi_csi_clk_enable(struct csi_state *state)267{268	return clk_bulk_prepare_enable(CSI2_NUM_CLKS, state->clks);269}270 271static void imx8mq_mipi_csi_clk_disable(struct csi_state *state)272{273	clk_bulk_disable_unprepare(CSI2_NUM_CLKS, state->clks);274}275 276static int imx8mq_mipi_csi_clk_get(struct csi_state *state)277{278	unsigned int i;279 280	for (i = 0; i < CSI2_NUM_CLKS; i++)281		state->clks[i].id = imx8mq_mipi_csi_clk_id[i];282 283	return devm_clk_bulk_get(state->dev, CSI2_NUM_CLKS, state->clks);284}285 286static int imx8mq_mipi_csi_calc_hs_settle(struct csi_state *state,287					  struct v4l2_subdev_state *sd_state,288					  u32 *hs_settle)289{290	s64 link_freq;291	u32 lane_rate;292	unsigned long esc_clk_rate;293	u32 min_ths_settle, max_ths_settle, ths_settle_ns, esc_clk_period_ns;294	const struct v4l2_mbus_framefmt *fmt;295	const struct csi2_pix_format *csi2_fmt;296 297	/* Calculate the line rate from the pixel rate. */298 299	fmt = v4l2_subdev_state_get_format(sd_state, MIPI_CSI2_PAD_SINK);300	csi2_fmt = find_csi2_format(fmt->code);301 302	link_freq = v4l2_get_link_freq(state->src_sd->ctrl_handler,303				       csi2_fmt->width,304				       state->bus.num_data_lanes * 2);305	if (link_freq < 0) {306		dev_err(state->dev, "Unable to obtain link frequency: %d\n",307			(int)link_freq);308		return link_freq;309	}310 311	lane_rate = link_freq * 2;312	if (lane_rate < 80000000 || lane_rate > 1500000000) {313		dev_dbg(state->dev, "Out-of-bound lane rate %u\n", lane_rate);314		return -EINVAL;315	}316 317	/*318	 * The D-PHY specification requires Ths-settle to be in the range319	 * 85ns + 6*UI to 140ns + 10*UI, with the unit interval UI being half320	 * the clock period.321	 *322	 * The Ths-settle value is expressed in the hardware as a multiple of323	 * the Esc clock period:324	 *325	 * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of RxClkInEsc326	 *327	 * Due to the one cycle inaccuracy introduced by rounding, the328	 * documentation recommends picking a value away from the boundaries.329	 * Let's pick the average.330	 */331	esc_clk_rate = clk_get_rate(state->clks[CSI2_CLK_ESC].clk);332	if (!esc_clk_rate) {333		dev_err(state->dev, "Could not get esc clock rate.\n");334		return -EINVAL;335	}336 337	dev_dbg(state->dev, "esc clk rate: %lu\n", esc_clk_rate);338	esc_clk_period_ns = 1000000000 / esc_clk_rate;339 340	min_ths_settle = 85 + 6 * 1000000 / (lane_rate / 1000);341	max_ths_settle = 140 + 10 * 1000000 / (lane_rate / 1000);342	ths_settle_ns = (min_ths_settle + max_ths_settle) / 2;343 344	*hs_settle = ths_settle_ns / esc_clk_period_ns - 1;345 346	dev_dbg(state->dev, "lane rate %u Ths_settle %u hs_settle %u\n",347		lane_rate, ths_settle_ns, *hs_settle);348 349	return 0;350}351 352static int imx8mq_mipi_csi_start_stream(struct csi_state *state,353					struct v4l2_subdev_state *sd_state)354{355	int ret;356	u32 hs_settle = 0;357 358	ret = imx8mq_mipi_csi_sw_reset(state);359	if (ret)360		return ret;361 362	imx8mq_mipi_csi_set_params(state);363	ret = imx8mq_mipi_csi_calc_hs_settle(state, sd_state, &hs_settle);364	if (ret)365		return ret;366 367	regmap_update_bits(state->phy_gpr,368			   state->phy_gpr_reg,369			   0x3fff,370			   GPR_CSI2_1_RX_ENABLE |371			   GPR_CSI2_1_VID_INTFC_ENB |372			   GPR_CSI2_1_HSEL |373			   GPR_CSI2_1_CONT_CLK_MODE |374			   GPR_CSI2_1_S_PRG_RXHS_SETTLE(hs_settle));375 376	return 0;377}378 379static void imx8mq_mipi_csi_stop_stream(struct csi_state *state)380{381	imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, 0xf);382}383 384/* -----------------------------------------------------------------------------385 * V4L2 subdev operations386 */387 388static struct csi_state *mipi_sd_to_csi2_state(struct v4l2_subdev *sdev)389{390	return container_of(sdev, struct csi_state, sd);391}392 393static int imx8mq_mipi_csi_s_stream(struct v4l2_subdev *sd, int enable)394{395	struct csi_state *state = mipi_sd_to_csi2_state(sd);396	struct v4l2_subdev_state *sd_state;397	int ret = 0;398 399	if (enable) {400		ret = pm_runtime_resume_and_get(state->dev);401		if (ret < 0)402			return ret;403	}404 405	mutex_lock(&state->lock);406 407	if (enable) {408		if (state->state & ST_SUSPENDED) {409			ret = -EBUSY;410			goto unlock;411		}412 413		sd_state = v4l2_subdev_lock_and_get_active_state(sd);414		ret = imx8mq_mipi_csi_start_stream(state, sd_state);415		v4l2_subdev_unlock_state(sd_state);416 417		if (ret < 0)418			goto unlock;419 420		ret = v4l2_subdev_call(state->src_sd, video, s_stream, 1);421		if (ret < 0)422			goto unlock;423 424		state->state |= ST_STREAMING;425	} else {426		v4l2_subdev_call(state->src_sd, video, s_stream, 0);427		imx8mq_mipi_csi_stop_stream(state);428		state->state &= ~ST_STREAMING;429	}430 431unlock:432	mutex_unlock(&state->lock);433 434	if (!enable || ret < 0)435		pm_runtime_put(state->dev);436 437	return ret;438}439 440static int imx8mq_mipi_csi_init_state(struct v4l2_subdev *sd,441				      struct v4l2_subdev_state *sd_state)442{443	struct v4l2_mbus_framefmt *fmt_sink;444	struct v4l2_mbus_framefmt *fmt_source;445 446	fmt_sink = v4l2_subdev_state_get_format(sd_state, MIPI_CSI2_PAD_SINK);447	fmt_source = v4l2_subdev_state_get_format(sd_state,448						  MIPI_CSI2_PAD_SOURCE);449 450	fmt_sink->code = MEDIA_BUS_FMT_SGBRG10_1X10;451	fmt_sink->width = MIPI_CSI2_DEF_PIX_WIDTH;452	fmt_sink->height = MIPI_CSI2_DEF_PIX_HEIGHT;453	fmt_sink->field = V4L2_FIELD_NONE;454 455	fmt_sink->colorspace = V4L2_COLORSPACE_RAW;456	fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace);457	fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace);458	fmt_sink->quantization =459		V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace,460					      fmt_sink->ycbcr_enc);461 462	*fmt_source = *fmt_sink;463 464	return 0;465}466 467static int imx8mq_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,468					  struct v4l2_subdev_state *sd_state,469					  struct v4l2_subdev_mbus_code_enum *code)470{471	/*472	 * We can't transcode in any way, the source format is identical473	 * to the sink format.474	 */475	if (code->pad == MIPI_CSI2_PAD_SOURCE) {476		struct v4l2_mbus_framefmt *fmt;477 478		if (code->index > 0)479			return -EINVAL;480 481		fmt = v4l2_subdev_state_get_format(sd_state, code->pad);482		code->code = fmt->code;483		return 0;484	}485 486	if (code->pad != MIPI_CSI2_PAD_SINK)487		return -EINVAL;488 489	if (code->index >= ARRAY_SIZE(imx8mq_mipi_csi_formats))490		return -EINVAL;491 492	code->code = imx8mq_mipi_csi_formats[code->index].code;493 494	return 0;495}496 497static int imx8mq_mipi_csi_set_fmt(struct v4l2_subdev *sd,498				   struct v4l2_subdev_state *sd_state,499				   struct v4l2_subdev_format *sdformat)500{501	const struct csi2_pix_format *csi2_fmt;502	struct v4l2_mbus_framefmt *fmt;503 504	/*505	 * The device can't transcode in any way, the source format can't be506	 * modified.507	 */508	if (sdformat->pad == MIPI_CSI2_PAD_SOURCE)509		return v4l2_subdev_get_fmt(sd, sd_state, sdformat);510 511	if (sdformat->pad != MIPI_CSI2_PAD_SINK)512		return -EINVAL;513 514	csi2_fmt = find_csi2_format(sdformat->format.code);515	if (!csi2_fmt)516		csi2_fmt = &imx8mq_mipi_csi_formats[0];517 518	fmt = v4l2_subdev_state_get_format(sd_state, sdformat->pad);519 520	fmt->code = csi2_fmt->code;521	fmt->width = sdformat->format.width;522	fmt->height = sdformat->format.height;523 524	sdformat->format = *fmt;525 526	/* Propagate the format from sink to source. */527	fmt = v4l2_subdev_state_get_format(sd_state, MIPI_CSI2_PAD_SOURCE);528	*fmt = sdformat->format;529 530	return 0;531}532 533static const struct v4l2_subdev_video_ops imx8mq_mipi_csi_video_ops = {534	.s_stream	= imx8mq_mipi_csi_s_stream,535};536 537static const struct v4l2_subdev_pad_ops imx8mq_mipi_csi_pad_ops = {538	.enum_mbus_code		= imx8mq_mipi_csi_enum_mbus_code,539	.get_fmt		= v4l2_subdev_get_fmt,540	.set_fmt		= imx8mq_mipi_csi_set_fmt,541};542 543static const struct v4l2_subdev_ops imx8mq_mipi_csi_subdev_ops = {544	.video	= &imx8mq_mipi_csi_video_ops,545	.pad	= &imx8mq_mipi_csi_pad_ops,546};547 548static const struct v4l2_subdev_internal_ops imx8mq_mipi_csi_internal_ops = {549	.init_state		= imx8mq_mipi_csi_init_state,550};551 552/* -----------------------------------------------------------------------------553 * Media entity operations554 */555 556static const struct media_entity_operations imx8mq_mipi_csi_entity_ops = {557	.link_validate	= v4l2_subdev_link_validate,558	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,559};560 561/* -----------------------------------------------------------------------------562 * Async subdev notifier563 */564 565static struct csi_state *566mipi_notifier_to_csi2_state(struct v4l2_async_notifier *n)567{568	return container_of(n, struct csi_state, notifier);569}570 571static int imx8mq_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,572					struct v4l2_subdev *sd,573					struct v4l2_async_connection *asd)574{575	struct csi_state *state = mipi_notifier_to_csi2_state(notifier);576	struct media_pad *sink = &state->sd.entity.pads[MIPI_CSI2_PAD_SINK];577 578	state->src_sd = sd;579 580	return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |581					       MEDIA_LNK_FL_IMMUTABLE);582}583 584static const struct v4l2_async_notifier_operations imx8mq_mipi_csi_notify_ops = {585	.bound = imx8mq_mipi_csi_notify_bound,586};587 588static int imx8mq_mipi_csi_async_register(struct csi_state *state)589{590	struct v4l2_fwnode_endpoint vep = {591		.bus_type = V4L2_MBUS_CSI2_DPHY,592	};593	struct v4l2_async_connection *asd;594	struct fwnode_handle *ep;595	unsigned int i;596	int ret;597 598	v4l2_async_subdev_nf_init(&state->notifier, &state->sd);599 600	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(state->dev), 0, 0,601					     FWNODE_GRAPH_ENDPOINT_NEXT);602	if (!ep)603		return -ENOTCONN;604 605	ret = v4l2_fwnode_endpoint_parse(ep, &vep);606	if (ret)607		goto err_parse;608 609	for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) {610		if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) {611			dev_err(state->dev,612				"data lanes reordering is not supported");613			ret = -EINVAL;614			goto err_parse;615		}616	}617 618	state->bus = vep.bus.mipi_csi2;619 620	dev_dbg(state->dev, "data lanes: %d flags: 0x%08x\n",621		state->bus.num_data_lanes,622		state->bus.flags);623 624	asd = v4l2_async_nf_add_fwnode_remote(&state->notifier, ep,625					      struct v4l2_async_connection);626	if (IS_ERR(asd)) {627		ret = PTR_ERR(asd);628		goto err_parse;629	}630 631	fwnode_handle_put(ep);632 633	state->notifier.ops = &imx8mq_mipi_csi_notify_ops;634 635	ret = v4l2_async_nf_register(&state->notifier);636	if (ret)637		return ret;638 639	return v4l2_async_register_subdev(&state->sd);640 641err_parse:642	fwnode_handle_put(ep);643 644	return ret;645}646 647/* -----------------------------------------------------------------------------648 * Suspend/resume649 */650 651static void imx8mq_mipi_csi_pm_suspend(struct device *dev)652{653	struct v4l2_subdev *sd = dev_get_drvdata(dev);654	struct csi_state *state = mipi_sd_to_csi2_state(sd);655 656	mutex_lock(&state->lock);657 658	if (state->state & ST_POWERED) {659		imx8mq_mipi_csi_stop_stream(state);660		imx8mq_mipi_csi_clk_disable(state);661		state->state &= ~ST_POWERED;662	}663 664	mutex_unlock(&state->lock);665}666 667static int imx8mq_mipi_csi_pm_resume(struct device *dev)668{669	struct v4l2_subdev *sd = dev_get_drvdata(dev);670	struct csi_state *state = mipi_sd_to_csi2_state(sd);671	struct v4l2_subdev_state *sd_state;672	int ret = 0;673 674	mutex_lock(&state->lock);675 676	if (!(state->state & ST_POWERED)) {677		state->state |= ST_POWERED;678		ret = imx8mq_mipi_csi_clk_enable(state);679	}680	if (state->state & ST_STREAMING) {681		sd_state = v4l2_subdev_lock_and_get_active_state(sd);682		ret = imx8mq_mipi_csi_start_stream(state, sd_state);683		v4l2_subdev_unlock_state(sd_state);684		if (ret)685			goto unlock;686	}687 688	state->state &= ~ST_SUSPENDED;689 690unlock:691	mutex_unlock(&state->lock);692 693	return ret ? -EAGAIN : 0;694}695 696static int imx8mq_mipi_csi_suspend(struct device *dev)697{698	struct v4l2_subdev *sd = dev_get_drvdata(dev);699	struct csi_state *state = mipi_sd_to_csi2_state(sd);700 701	imx8mq_mipi_csi_pm_suspend(dev);702 703	state->state |= ST_SUSPENDED;704 705	return 0;706}707 708static int imx8mq_mipi_csi_resume(struct device *dev)709{710	struct v4l2_subdev *sd = dev_get_drvdata(dev);711	struct csi_state *state = mipi_sd_to_csi2_state(sd);712 713	if (!(state->state & ST_SUSPENDED))714		return 0;715 716	return imx8mq_mipi_csi_pm_resume(dev);717}718 719static int imx8mq_mipi_csi_runtime_suspend(struct device *dev)720{721	struct v4l2_subdev *sd = dev_get_drvdata(dev);722	struct csi_state *state = mipi_sd_to_csi2_state(sd);723	int ret;724 725	imx8mq_mipi_csi_pm_suspend(dev);726 727	ret = icc_set_bw(state->icc_path, 0, 0);728	if (ret)729		dev_err(dev, "icc_set_bw failed with %d\n", ret);730 731	return ret;732}733 734static int imx8mq_mipi_csi_runtime_resume(struct device *dev)735{736	struct v4l2_subdev *sd = dev_get_drvdata(dev);737	struct csi_state *state = mipi_sd_to_csi2_state(sd);738	int ret;739 740	ret = icc_set_bw(state->icc_path, 0, state->icc_path_bw);741	if (ret) {742		dev_err(dev, "icc_set_bw failed with %d\n", ret);743		return ret;744	}745 746	return imx8mq_mipi_csi_pm_resume(dev);747}748 749static const struct dev_pm_ops imx8mq_mipi_csi_pm_ops = {750	RUNTIME_PM_OPS(imx8mq_mipi_csi_runtime_suspend,751		       imx8mq_mipi_csi_runtime_resume, NULL)752	SYSTEM_SLEEP_PM_OPS(imx8mq_mipi_csi_suspend, imx8mq_mipi_csi_resume)753};754 755/* -----------------------------------------------------------------------------756 * Probe/remove & platform driver757 */758 759static int imx8mq_mipi_csi_subdev_init(struct csi_state *state)760{761	struct v4l2_subdev *sd = &state->sd;762	int ret;763 764	v4l2_subdev_init(sd, &imx8mq_mipi_csi_subdev_ops);765	sd->internal_ops = &imx8mq_mipi_csi_internal_ops;766	sd->owner = THIS_MODULE;767	snprintf(sd->name, sizeof(sd->name), "%s %s",768		 MIPI_CSI2_SUBDEV_NAME, dev_name(state->dev));769 770	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;771 772	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;773	sd->entity.ops = &imx8mq_mipi_csi_entity_ops;774 775	sd->dev = state->dev;776 777	state->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK778					 | MEDIA_PAD_FL_MUST_CONNECT;779	state->pads[MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE780					   | MEDIA_PAD_FL_MUST_CONNECT;781	ret = media_entity_pads_init(&sd->entity, MIPI_CSI2_PADS_NUM,782				     state->pads);783	if (ret)784		return ret;785 786	ret = v4l2_subdev_init_finalize(sd);787	if (ret) {788		media_entity_cleanup(&sd->entity);789		return ret;790	}791 792	return 0;793}794 795static void imx8mq_mipi_csi_release_icc(struct platform_device *pdev)796{797	struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev);798	struct csi_state *state = mipi_sd_to_csi2_state(sd);799 800	icc_put(state->icc_path);801}802 803static int imx8mq_mipi_csi_init_icc(struct platform_device *pdev)804{805	struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev);806	struct csi_state *state = mipi_sd_to_csi2_state(sd);807 808	/* Optional interconnect request */809	state->icc_path = of_icc_get(&pdev->dev, "dram");810	if (IS_ERR_OR_NULL(state->icc_path))811		return PTR_ERR_OR_ZERO(state->icc_path);812 813	state->icc_path_bw = MBps_to_icc(700);814 815	return 0;816}817 818static int imx8mq_mipi_csi_parse_dt(struct csi_state *state)819{820	struct device *dev = state->dev;821	struct device_node *np = state->dev->of_node;822	struct device_node *node;823	phandle ph;824	u32 out_val[2];825	int ret = 0;826 827	state->rst = devm_reset_control_array_get_exclusive(dev);828	if (IS_ERR(state->rst)) {829		dev_err(dev, "Failed to get reset: %pe\n", state->rst);830		return PTR_ERR(state->rst);831	}832 833	ret = of_property_read_u32_array(np, "fsl,mipi-phy-gpr", out_val,834					 ARRAY_SIZE(out_val));835	if (ret) {836		dev_err(dev, "no fsl,mipi-phy-gpr property found: %d\n", ret);837		return ret;838	}839 840	ph = *out_val;841 842	node = of_find_node_by_phandle(ph);843	if (!node) {844		dev_err(dev, "Error finding node by phandle\n");845		return -ENODEV;846	}847	state->phy_gpr = syscon_node_to_regmap(node);848	of_node_put(node);849	if (IS_ERR(state->phy_gpr)) {850		dev_err(dev, "failed to get gpr regmap: %pe\n", state->phy_gpr);851		return PTR_ERR(state->phy_gpr);852	}853 854	state->phy_gpr_reg = out_val[1];855	dev_dbg(dev, "phy gpr register set to 0x%x\n", state->phy_gpr_reg);856 857	return ret;858}859 860static int imx8mq_mipi_csi_probe(struct platform_device *pdev)861{862	struct device *dev = &pdev->dev;863	struct csi_state *state;864	int ret;865 866	state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);867	if (!state)868		return -ENOMEM;869 870	state->dev = dev;871 872	ret = imx8mq_mipi_csi_parse_dt(state);873	if (ret < 0) {874		dev_err(dev, "Failed to parse device tree: %d\n", ret);875		return ret;876	}877 878	/* Acquire resources. */879	state->regs = devm_platform_ioremap_resource(pdev, 0);880	if (IS_ERR(state->regs))881		return PTR_ERR(state->regs);882 883	ret = imx8mq_mipi_csi_clk_get(state);884	if (ret < 0)885		return ret;886 887	platform_set_drvdata(pdev, &state->sd);888 889	mutex_init(&state->lock);890 891	ret = imx8mq_mipi_csi_subdev_init(state);892	if (ret < 0)893		goto mutex;894 895	ret = imx8mq_mipi_csi_init_icc(pdev);896	if (ret)897		goto mutex;898 899	/* Enable runtime PM. */900	pm_runtime_enable(dev);901	if (!pm_runtime_enabled(dev)) {902		ret = imx8mq_mipi_csi_runtime_resume(dev);903		if (ret < 0)904			goto icc;905	}906 907	ret = imx8mq_mipi_csi_async_register(state);908	if (ret < 0)909		goto cleanup;910 911	return 0;912 913cleanup:914	pm_runtime_disable(&pdev->dev);915	imx8mq_mipi_csi_runtime_suspend(&pdev->dev);916 917	media_entity_cleanup(&state->sd.entity);918	v4l2_subdev_cleanup(&state->sd);919	v4l2_async_nf_unregister(&state->notifier);920	v4l2_async_nf_cleanup(&state->notifier);921	v4l2_async_unregister_subdev(&state->sd);922icc:923	imx8mq_mipi_csi_release_icc(pdev);924mutex:925	mutex_destroy(&state->lock);926 927	return ret;928}929 930static void imx8mq_mipi_csi_remove(struct platform_device *pdev)931{932	struct v4l2_subdev *sd = platform_get_drvdata(pdev);933	struct csi_state *state = mipi_sd_to_csi2_state(sd);934 935	v4l2_async_nf_unregister(&state->notifier);936	v4l2_async_nf_cleanup(&state->notifier);937	v4l2_async_unregister_subdev(&state->sd);938 939	pm_runtime_disable(&pdev->dev);940	imx8mq_mipi_csi_runtime_suspend(&pdev->dev);941	media_entity_cleanup(&state->sd.entity);942	v4l2_subdev_cleanup(&state->sd);943	mutex_destroy(&state->lock);944	pm_runtime_set_suspended(&pdev->dev);945	imx8mq_mipi_csi_release_icc(pdev);946}947 948static const struct of_device_id imx8mq_mipi_csi_of_match[] = {949	{ .compatible = "fsl,imx8mq-mipi-csi2", },950	{ /* sentinel */ },951};952MODULE_DEVICE_TABLE(of, imx8mq_mipi_csi_of_match);953 954static struct platform_driver imx8mq_mipi_csi_driver = {955	.probe		= imx8mq_mipi_csi_probe,956	.remove_new	= imx8mq_mipi_csi_remove,957	.driver		= {958		.of_match_table = imx8mq_mipi_csi_of_match,959		.name		= MIPI_CSI2_DRIVER_NAME,960		.pm		= pm_ptr(&imx8mq_mipi_csi_pm_ops),961	},962};963 964module_platform_driver(imx8mq_mipi_csi_driver);965 966MODULE_DESCRIPTION("i.MX8MQ MIPI CSI-2 receiver driver");967MODULE_AUTHOR("Martin Kepplinger <martin.kepplinger@puri.sm>");968MODULE_LICENSE("GPL v2");969MODULE_ALIAS("platform:imx8mq-mipi-csi2");970