File Index
Files and directories
Sign in
FILE

imx8qxp-pixel-combiner.c

File preview with syntax highlighting.

linux-6.19.14/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c C
  1// SPDX-License-Identifier: GPL-2.0+
  2
  3/*
  4 * Copyright 2020 NXP
  5 */
  6
  7#include <linux/bitfield.h>
  8#include <linux/clk.h>
  9#include <linux/delay.h>
 10#include <linux/io.h>
 11#include <linux/media-bus-format.h>
 12#include <linux/module.h>
 13#include <linux/of.h>
 14#include <linux/of_graph.h>
 15#include <linux/platform_device.h>
 16#include <linux/pm_runtime.h>
 17
 18#include <drm/drm_atomic_state_helper.h>
 19#include <drm/drm_bridge.h>
 20#include <drm/drm_print.h>
 21
 22#define PC_CTRL_REG			0x0
 23#define  PC_COMBINE_ENABLE		BIT(0)
 24#define  PC_DISP_BYPASS(n)		BIT(1 + 21 * (n))
 25#define  PC_DISP_HSYNC_POLARITY(n)	BIT(2 + 11 * (n))
 26#define  PC_DISP_HSYNC_POLARITY_POS(n)	DISP_HSYNC_POLARITY(n)
 27#define  PC_DISP_VSYNC_POLARITY(n)	BIT(3 + 11 * (n))
 28#define  PC_DISP_VSYNC_POLARITY_POS(n)	DISP_VSYNC_POLARITY(n)
 29#define  PC_DISP_DVALID_POLARITY(n)	BIT(4 + 11 * (n))
 30#define  PC_DISP_DVALID_POLARITY_POS(n)	DISP_DVALID_POLARITY(n)
 31#define  PC_VSYNC_MASK_ENABLE		BIT(5)
 32#define  PC_SKIP_MODE			BIT(6)
 33#define  PC_SKIP_NUMBER_MASK		GENMASK(12, 7)
 34#define  PC_SKIP_NUMBER(n)		FIELD_PREP(PC_SKIP_NUMBER_MASK, (n))
 35#define  PC_DISP0_PIX_DATA_FORMAT_MASK	GENMASK(18, 16)
 36#define  PC_DISP0_PIX_DATA_FORMAT(fmt)	\
 37				FIELD_PREP(PC_DISP0_PIX_DATA_FORMAT_MASK, (fmt))
 38#define  PC_DISP1_PIX_DATA_FORMAT_MASK	GENMASK(21, 19)
 39#define  PC_DISP1_PIX_DATA_FORMAT(fmt)	\
 40				FIELD_PREP(PC_DISP1_PIX_DATA_FORMAT_MASK, (fmt))
 41
 42#define PC_SW_RESET_REG			0x20
 43#define  PC_SW_RESET_N			BIT(0)
 44#define  PC_DISP_SW_RESET_N(n)		BIT(1 + (n))
 45#define  PC_FULL_RESET_N		(PC_SW_RESET_N |		\
 46					 PC_DISP_SW_RESET_N(0) |	\
 47					 PC_DISP_SW_RESET_N(1))
 48
 49#define PC_REG_SET			0x4
 50#define PC_REG_CLR			0x8
 51
 52#define DRIVER_NAME			"imx8qxp-pixel-combiner"
 53
 54enum imx8qxp_pc_pix_data_format {
 55	RGB,
 56	YUV444,
 57	YUV422,
 58	SPLIT_RGB,
 59};
 60
 61struct imx8qxp_pc_channel {
 62	struct drm_bridge bridge;
 63	struct drm_bridge *next_bridge;
 64	struct imx8qxp_pc *pc;
 65	unsigned int stream_id;
 66};
 67
 68struct imx8qxp_pc {
 69	struct device *dev;
 70	struct imx8qxp_pc_channel *ch[2];
 71	struct clk *clk_apb;
 72	void __iomem *base;
 73};
 74
 75static inline u32 imx8qxp_pc_read(struct imx8qxp_pc *pc, unsigned int offset)
 76{
 77	return readl(pc->base + offset);
 78}
 79
 80static inline void
 81imx8qxp_pc_write(struct imx8qxp_pc *pc, unsigned int offset, u32 value)
 82{
 83	writel(value, pc->base + offset);
 84}
 85
 86static inline void
 87imx8qxp_pc_write_set(struct imx8qxp_pc *pc, unsigned int offset, u32 value)
 88{
 89	imx8qxp_pc_write(pc, offset + PC_REG_SET, value);
 90}
 91
 92static inline void
 93imx8qxp_pc_write_clr(struct imx8qxp_pc *pc, unsigned int offset, u32 value)
 94{
 95	imx8qxp_pc_write(pc, offset + PC_REG_CLR, value);
 96}
 97
 98static enum drm_mode_status
 99imx8qxp_pc_bridge_mode_valid(struct drm_bridge *bridge,
100			     const struct drm_display_info *info,
101			     const struct drm_display_mode *mode)
102{
103	if (mode->hdisplay > 2560)
104		return MODE_BAD_HVALUE;
105
106	return MODE_OK;
107}
108
109static int imx8qxp_pc_bridge_attach(struct drm_bridge *bridge,
110				    struct drm_encoder *encoder,
111				    enum drm_bridge_attach_flags flags)
112{
113	struct imx8qxp_pc_channel *ch = bridge->driver_private;
114	struct imx8qxp_pc *pc = ch->pc;
115
116	if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
117		DRM_DEV_ERROR(pc->dev,
118			      "do not support creating a drm_connector\n");
119		return -EINVAL;
120	}
121
122	return drm_bridge_attach(encoder,
123				 ch->next_bridge, bridge,
124				 DRM_BRIDGE_ATTACH_NO_CONNECTOR);
125}
126
127static void
128imx8qxp_pc_bridge_mode_set(struct drm_bridge *bridge,
129			   const struct drm_display_mode *mode,
130			   const struct drm_display_mode *adjusted_mode)
131{
132	struct imx8qxp_pc_channel *ch = bridge->driver_private;
133	struct imx8qxp_pc *pc = ch->pc;
134	u32 val;
135	int ret;
136
137	ret = pm_runtime_get_sync(pc->dev);
138	if (ret < 0)
139		DRM_DEV_ERROR(pc->dev,
140			      "failed to get runtime PM sync: %d\n", ret);
141
142	ret = clk_prepare_enable(pc->clk_apb);
143	if (ret)
144		DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n",
145			      __func__,  ret);
146
147	/* HSYNC to pixel link is active low. */
148	imx8qxp_pc_write_clr(pc, PC_CTRL_REG,
149			     PC_DISP_HSYNC_POLARITY(ch->stream_id));
150
151	/* VSYNC to pixel link is active low. */
152	imx8qxp_pc_write_clr(pc, PC_CTRL_REG,
153			     PC_DISP_VSYNC_POLARITY(ch->stream_id));
154
155	/* Data enable to pixel link is active high. */
156	imx8qxp_pc_write_set(pc, PC_CTRL_REG,
157			     PC_DISP_DVALID_POLARITY(ch->stream_id));
158
159	/* Mask the first frame output which may be incomplete. */
160	imx8qxp_pc_write_set(pc, PC_CTRL_REG, PC_VSYNC_MASK_ENABLE);
161
162	/* Only support RGB currently. */
163	val = imx8qxp_pc_read(pc, PC_CTRL_REG);
164	if (ch->stream_id == 0) {
165		val &= ~PC_DISP0_PIX_DATA_FORMAT_MASK;
166		val |= PC_DISP0_PIX_DATA_FORMAT(RGB);
167	} else {
168		val &= ~PC_DISP1_PIX_DATA_FORMAT_MASK;
169		val |= PC_DISP1_PIX_DATA_FORMAT(RGB);
170	}
171	imx8qxp_pc_write(pc, PC_CTRL_REG, val);
172
173	/* Only support bypass mode currently. */
174	imx8qxp_pc_write_set(pc, PC_CTRL_REG, PC_DISP_BYPASS(ch->stream_id));
175
176	clk_disable_unprepare(pc->clk_apb);
177}
178
179static void imx8qxp_pc_bridge_atomic_disable(struct drm_bridge *bridge,
180					     struct drm_atomic_state *state)
181{
182	struct imx8qxp_pc_channel *ch = bridge->driver_private;
183	struct imx8qxp_pc *pc = ch->pc;
184	int ret;
185
186	ret = pm_runtime_put(pc->dev);
187	if (ret < 0)
188		DRM_DEV_ERROR(pc->dev, "failed to put runtime PM: %d\n", ret);
189}
190
191static const u32 imx8qxp_pc_bus_output_fmts[] = {
192	MEDIA_BUS_FMT_RGB888_1X36_CPADLO,
193	MEDIA_BUS_FMT_RGB666_1X36_CPADLO,
194};
195
196static bool imx8qxp_pc_bus_output_fmt_supported(u32 fmt)
197{
198	int i;
199
200	for (i = 0; i < ARRAY_SIZE(imx8qxp_pc_bus_output_fmts); i++) {
201		if (imx8qxp_pc_bus_output_fmts[i] == fmt)
202			return true;
203	}
204
205	return false;
206}
207
208static u32 *
209imx8qxp_pc_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
210					    struct drm_bridge_state *bridge_state,
211					    struct drm_crtc_state *crtc_state,
212					    struct drm_connector_state *conn_state,
213					    u32 output_fmt,
214					    unsigned int *num_input_fmts)
215{
216	u32 *input_fmts;
217
218	if (!imx8qxp_pc_bus_output_fmt_supported(output_fmt))
219		return NULL;
220
221	*num_input_fmts = 1;
222
223	input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);
224	if (!input_fmts)
225		return NULL;
226
227	switch (output_fmt) {
228	case MEDIA_BUS_FMT_RGB888_1X36_CPADLO:
229		input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X30_CPADLO;
230		break;
231	case MEDIA_BUS_FMT_RGB666_1X36_CPADLO:
232		input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X30_CPADLO;
233		break;
234	default:
235		kfree(input_fmts);
236		input_fmts = NULL;
237		break;
238	}
239
240	return input_fmts;
241}
242
243static u32 *
244imx8qxp_pc_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
245					     struct drm_bridge_state *bridge_state,
246					     struct drm_crtc_state *crtc_state,
247					     struct drm_connector_state *conn_state,
248					     unsigned int *num_output_fmts)
249{
250	*num_output_fmts = ARRAY_SIZE(imx8qxp_pc_bus_output_fmts);
251	return kmemdup(imx8qxp_pc_bus_output_fmts,
252			sizeof(imx8qxp_pc_bus_output_fmts), GFP_KERNEL);
253}
254
255static const struct drm_bridge_funcs imx8qxp_pc_bridge_funcs = {
256	.atomic_duplicate_state	= drm_atomic_helper_bridge_duplicate_state,
257	.atomic_destroy_state	= drm_atomic_helper_bridge_destroy_state,
258	.atomic_reset		= drm_atomic_helper_bridge_reset,
259	.mode_valid		= imx8qxp_pc_bridge_mode_valid,
260	.attach			= imx8qxp_pc_bridge_attach,
261	.mode_set		= imx8qxp_pc_bridge_mode_set,
262	.atomic_disable		= imx8qxp_pc_bridge_atomic_disable,
263	.atomic_get_input_bus_fmts =
264				imx8qxp_pc_bridge_atomic_get_input_bus_fmts,
265	.atomic_get_output_bus_fmts =
266				imx8qxp_pc_bridge_atomic_get_output_bus_fmts,
267};
268
269static int imx8qxp_pc_bridge_probe(struct platform_device *pdev)
270{
271	struct imx8qxp_pc *pc;
272	struct imx8qxp_pc_channel *ch;
273	struct device *dev = &pdev->dev;
274	struct device_node *np = dev->of_node;
275	struct device_node *child, *remote;
276	u32 i;
277	int ret;
278
279	pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL);
280	if (!pc)
281		return -ENOMEM;
282
283	pc->base = devm_platform_ioremap_resource(pdev, 0);
284	if (IS_ERR(pc->base))
285		return PTR_ERR(pc->base);
286
287	pc->dev = dev;
288
289	pc->clk_apb = devm_clk_get(dev, "apb");
290	if (IS_ERR(pc->clk_apb)) {
291		ret = PTR_ERR(pc->clk_apb);
292		if (ret != -EPROBE_DEFER)
293			DRM_DEV_ERROR(dev, "failed to get apb clock: %d\n", ret);
294		return ret;
295	}
296
297	platform_set_drvdata(pdev, pc);
298	pm_runtime_enable(dev);
299
300	for_each_available_child_of_node(np, child) {
301		ret = of_property_read_u32(child, "reg", &i);
302		if (ret || i > 1) {
303			ret = -EINVAL;
304			DRM_DEV_ERROR(dev,
305				      "invalid channel(%u) node address\n", i);
306			goto free_child;
307		}
308
309		ch = devm_drm_bridge_alloc(dev, struct imx8qxp_pc_channel, bridge,
310					   &imx8qxp_pc_bridge_funcs);
311		if (IS_ERR(ch)) {
312			ret = PTR_ERR(ch);
313			goto free_child;
314		}
315
316		pc->ch[i] = ch;
317		ch->pc = pc;
318		ch->stream_id = i;
319
320		remote = of_graph_get_remote_node(child, 1, 0);
321		if (!remote) {
322			ret = -ENODEV;
323			DRM_DEV_ERROR(dev,
324				      "channel%u failed to get port1's remote node: %d\n",
325				      i, ret);
326			goto free_child;
327		}
328
329		ch->next_bridge = of_drm_find_bridge(remote);
330		if (!ch->next_bridge) {
331			of_node_put(remote);
332			ret = -EPROBE_DEFER;
333			DRM_DEV_DEBUG_DRIVER(dev,
334					     "channel%u failed to find next bridge: %d\n",
335					     i, ret);
336			goto free_child;
337		}
338
339		of_node_put(remote);
340
341		ch->bridge.driver_private = ch;
342		ch->bridge.of_node = child;
343
344		drm_bridge_add(&ch->bridge);
345	}
346
347	return 0;
348
349free_child:
350	of_node_put(child);
351
352	if (i == 1 && pc->ch[0]->next_bridge)
353		drm_bridge_remove(&pc->ch[0]->bridge);
354
355	pm_runtime_disable(dev);
356	return ret;
357}
358
359static void imx8qxp_pc_bridge_remove(struct platform_device *pdev)
360{
361	struct imx8qxp_pc *pc = platform_get_drvdata(pdev);
362	struct imx8qxp_pc_channel *ch;
363	int i;
364
365	for (i = 0; i < 2; i++) {
366		ch = pc->ch[i];
367
368		if (ch)
369			drm_bridge_remove(&ch->bridge);
370	}
371
372	pm_runtime_disable(&pdev->dev);
373}
374
375static int imx8qxp_pc_runtime_suspend(struct device *dev)
376{
377	struct platform_device *pdev = to_platform_device(dev);
378	struct imx8qxp_pc *pc = platform_get_drvdata(pdev);
379	int ret;
380
381	ret = clk_prepare_enable(pc->clk_apb);
382	if (ret)
383		DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n",
384			      __func__,  ret);
385
386	/* Disable pixel combiner by full reset. */
387	imx8qxp_pc_write_clr(pc, PC_SW_RESET_REG, PC_FULL_RESET_N);
388
389	clk_disable_unprepare(pc->clk_apb);
390
391	/* Ensure the reset takes effect. */
392	usleep_range(10, 20);
393
394	return ret;
395}
396
397static int imx8qxp_pc_runtime_resume(struct device *dev)
398{
399	struct platform_device *pdev = to_platform_device(dev);
400	struct imx8qxp_pc *pc = platform_get_drvdata(pdev);
401	int ret;
402
403	ret = clk_prepare_enable(pc->clk_apb);
404	if (ret) {
405		DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n",
406			      __func__, ret);
407		return ret;
408	}
409
410	/* out of reset */
411	imx8qxp_pc_write_set(pc, PC_SW_RESET_REG, PC_FULL_RESET_N);
412
413	clk_disable_unprepare(pc->clk_apb);
414
415	return ret;
416}
417
418static const struct dev_pm_ops imx8qxp_pc_pm_ops = {
419	RUNTIME_PM_OPS(imx8qxp_pc_runtime_suspend, imx8qxp_pc_runtime_resume, NULL)
420};
421
422static const struct of_device_id imx8qxp_pc_dt_ids[] = {
423	{ .compatible = "fsl,imx8qm-pixel-combiner", },
424	{ .compatible = "fsl,imx8qxp-pixel-combiner", },
425	{ /* sentinel */ }
426};
427MODULE_DEVICE_TABLE(of, imx8qxp_pc_dt_ids);
428
429static struct platform_driver imx8qxp_pc_bridge_driver = {
430	.probe	= imx8qxp_pc_bridge_probe,
431	.remove = imx8qxp_pc_bridge_remove,
432	.driver	= {
433		.pm = pm_ptr(&imx8qxp_pc_pm_ops),
434		.name = DRIVER_NAME,
435		.of_match_table = imx8qxp_pc_dt_ids,
436	},
437};
438module_platform_driver(imx8qxp_pc_bridge_driver);
439
440MODULE_DESCRIPTION("i.MX8QM/QXP pixel combiner bridge driver");
441MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>");
442MODULE_LICENSE("GPL v2");
443MODULE_ALIAS("platform:" DRIVER_NAME);
~/ Read-only file preview.