brintos

brintos / linux-shallow public Read only

0
0
Text · 11.2 KiB · e6dbbdc Raw
444 lines · c
1// SPDX-License-Identifier: GPL-2.0+2 3/*4 * Copyright 2020 NXP5 */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			0x023#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			0x2043#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			0x450#define PC_REG_CLR			0x851 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	bool is_available;67};68 69struct imx8qxp_pc {70	struct device *dev;71	struct imx8qxp_pc_channel ch[2];72	struct clk *clk_apb;73	void __iomem *base;74};75 76static inline u32 imx8qxp_pc_read(struct imx8qxp_pc *pc, unsigned int offset)77{78	return readl(pc->base + offset);79}80 81static inline void82imx8qxp_pc_write(struct imx8qxp_pc *pc, unsigned int offset, u32 value)83{84	writel(value, pc->base + offset);85}86 87static inline void88imx8qxp_pc_write_set(struct imx8qxp_pc *pc, unsigned int offset, u32 value)89{90	imx8qxp_pc_write(pc, offset + PC_REG_SET, value);91}92 93static inline void94imx8qxp_pc_write_clr(struct imx8qxp_pc *pc, unsigned int offset, u32 value)95{96	imx8qxp_pc_write(pc, offset + PC_REG_CLR, value);97}98 99static enum drm_mode_status100imx8qxp_pc_bridge_mode_valid(struct drm_bridge *bridge,101			     const struct drm_display_info *info,102			     const struct drm_display_mode *mode)103{104	if (mode->hdisplay > 2560)105		return MODE_BAD_HVALUE;106 107	return MODE_OK;108}109 110static int imx8qxp_pc_bridge_attach(struct drm_bridge *bridge,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(bridge->encoder,123				 ch->next_bridge, bridge,124				 DRM_BRIDGE_ATTACH_NO_CONNECTOR);125}126 127static void128imx8qxp_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 void180imx8qxp_pc_bridge_atomic_disable(struct drm_bridge *bridge,181				 struct drm_bridge_state *old_bridge_state)182{183	struct imx8qxp_pc_channel *ch = bridge->driver_private;184	struct imx8qxp_pc *pc = ch->pc;185	int ret;186 187	ret = pm_runtime_put(pc->dev);188	if (ret < 0)189		DRM_DEV_ERROR(pc->dev, "failed to put runtime PM: %d\n", ret);190}191 192static const u32 imx8qxp_pc_bus_output_fmts[] = {193	MEDIA_BUS_FMT_RGB888_1X36_CPADLO,194	MEDIA_BUS_FMT_RGB666_1X36_CPADLO,195};196 197static bool imx8qxp_pc_bus_output_fmt_supported(u32 fmt)198{199	int i;200 201	for (i = 0; i < ARRAY_SIZE(imx8qxp_pc_bus_output_fmts); i++) {202		if (imx8qxp_pc_bus_output_fmts[i] == fmt)203			return true;204	}205 206	return false;207}208 209static u32 *210imx8qxp_pc_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,211					    struct drm_bridge_state *bridge_state,212					    struct drm_crtc_state *crtc_state,213					    struct drm_connector_state *conn_state,214					    u32 output_fmt,215					    unsigned int *num_input_fmts)216{217	u32 *input_fmts;218 219	if (!imx8qxp_pc_bus_output_fmt_supported(output_fmt))220		return NULL;221 222	*num_input_fmts = 1;223 224	input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);225	if (!input_fmts)226		return NULL;227 228	switch (output_fmt) {229	case MEDIA_BUS_FMT_RGB888_1X36_CPADLO:230		input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X30_CPADLO;231		break;232	case MEDIA_BUS_FMT_RGB666_1X36_CPADLO:233		input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X30_CPADLO;234		break;235	default:236		kfree(input_fmts);237		input_fmts = NULL;238		break;239	}240 241	return input_fmts;242}243 244static u32 *245imx8qxp_pc_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,246					     struct drm_bridge_state *bridge_state,247					     struct drm_crtc_state *crtc_state,248					     struct drm_connector_state *conn_state,249					     unsigned int *num_output_fmts)250{251	*num_output_fmts = ARRAY_SIZE(imx8qxp_pc_bus_output_fmts);252	return kmemdup(imx8qxp_pc_bus_output_fmts,253			sizeof(imx8qxp_pc_bus_output_fmts), GFP_KERNEL);254}255 256static const struct drm_bridge_funcs imx8qxp_pc_bridge_funcs = {257	.atomic_duplicate_state	= drm_atomic_helper_bridge_duplicate_state,258	.atomic_destroy_state	= drm_atomic_helper_bridge_destroy_state,259	.atomic_reset		= drm_atomic_helper_bridge_reset,260	.mode_valid		= imx8qxp_pc_bridge_mode_valid,261	.attach			= imx8qxp_pc_bridge_attach,262	.mode_set		= imx8qxp_pc_bridge_mode_set,263	.atomic_disable		= imx8qxp_pc_bridge_atomic_disable,264	.atomic_get_input_bus_fmts =265				imx8qxp_pc_bridge_atomic_get_input_bus_fmts,266	.atomic_get_output_bus_fmts =267				imx8qxp_pc_bridge_atomic_get_output_bus_fmts,268};269 270static int imx8qxp_pc_bridge_probe(struct platform_device *pdev)271{272	struct imx8qxp_pc *pc;273	struct imx8qxp_pc_channel *ch;274	struct device *dev = &pdev->dev;275	struct device_node *np = dev->of_node;276	struct device_node *child, *remote;277	u32 i;278	int ret;279 280	pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL);281	if (!pc)282		return -ENOMEM;283 284	pc->base = devm_platform_ioremap_resource(pdev, 0);285	if (IS_ERR(pc->base))286		return PTR_ERR(pc->base);287 288	pc->dev = dev;289 290	pc->clk_apb = devm_clk_get(dev, "apb");291	if (IS_ERR(pc->clk_apb)) {292		ret = PTR_ERR(pc->clk_apb);293		if (ret != -EPROBE_DEFER)294			DRM_DEV_ERROR(dev, "failed to get apb clock: %d\n", ret);295		return ret;296	}297 298	platform_set_drvdata(pdev, pc);299	pm_runtime_enable(dev);300 301	for_each_available_child_of_node(np, child) {302		ret = of_property_read_u32(child, "reg", &i);303		if (ret || i > 1) {304			ret = -EINVAL;305			DRM_DEV_ERROR(dev,306				      "invalid channel(%u) node address\n", i);307			goto free_child;308		}309 310		ch = &pc->ch[i];311		ch->pc = pc;312		ch->stream_id = i;313 314		remote = of_graph_get_remote_node(child, 1, 0);315		if (!remote) {316			ret = -ENODEV;317			DRM_DEV_ERROR(dev,318				      "channel%u failed to get port1's remote node: %d\n",319				      i, ret);320			goto free_child;321		}322 323		ch->next_bridge = of_drm_find_bridge(remote);324		if (!ch->next_bridge) {325			of_node_put(remote);326			ret = -EPROBE_DEFER;327			DRM_DEV_DEBUG_DRIVER(dev,328					     "channel%u failed to find next bridge: %d\n",329					     i, ret);330			goto free_child;331		}332 333		of_node_put(remote);334 335		ch->bridge.driver_private = ch;336		ch->bridge.funcs = &imx8qxp_pc_bridge_funcs;337		ch->bridge.of_node = child;338		ch->is_available = true;339 340		drm_bridge_add(&ch->bridge);341	}342 343	return 0;344 345free_child:346	of_node_put(child);347 348	if (i == 1 && pc->ch[0].next_bridge)349		drm_bridge_remove(&pc->ch[0].bridge);350 351	pm_runtime_disable(dev);352	return ret;353}354 355static void imx8qxp_pc_bridge_remove(struct platform_device *pdev)356{357	struct imx8qxp_pc *pc = platform_get_drvdata(pdev);358	struct imx8qxp_pc_channel *ch;359	int i;360 361	for (i = 0; i < 2; i++) {362		ch = &pc->ch[i];363 364		if (!ch->is_available)365			continue;366 367		drm_bridge_remove(&ch->bridge);368		ch->is_available = false;369	}370 371	pm_runtime_disable(&pdev->dev);372}373 374static int __maybe_unused imx8qxp_pc_runtime_suspend(struct device *dev)375{376	struct platform_device *pdev = to_platform_device(dev);377	struct imx8qxp_pc *pc = platform_get_drvdata(pdev);378	int ret;379 380	ret = clk_prepare_enable(pc->clk_apb);381	if (ret)382		DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n",383			      __func__,  ret);384 385	/* Disable pixel combiner by full reset. */386	imx8qxp_pc_write_clr(pc, PC_SW_RESET_REG, PC_FULL_RESET_N);387 388	clk_disable_unprepare(pc->clk_apb);389 390	/* Ensure the reset takes effect. */391	usleep_range(10, 20);392 393	return ret;394}395 396static int __maybe_unused imx8qxp_pc_runtime_resume(struct device *dev)397{398	struct platform_device *pdev = to_platform_device(dev);399	struct imx8qxp_pc *pc = platform_get_drvdata(pdev);400	int ret;401 402	ret = clk_prepare_enable(pc->clk_apb);403	if (ret) {404		DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n",405			      __func__, ret);406		return ret;407	}408 409	/* out of reset */410	imx8qxp_pc_write_set(pc, PC_SW_RESET_REG, PC_FULL_RESET_N);411 412	clk_disable_unprepare(pc->clk_apb);413 414	return ret;415}416 417static const struct dev_pm_ops imx8qxp_pc_pm_ops = {418	SET_RUNTIME_PM_OPS(imx8qxp_pc_runtime_suspend,419			   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_new = imx8qxp_pc_bridge_remove,432	.driver	= {433		.pm = &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);444