1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | /* |
4 | * Copyright (C) 2022 Pengutronix, Lucas Stach <kernel@pengutronix.de> |
5 | */ |
6 | |
7 | #include <drm/drm_atomic_helper.h> |
8 | #include <drm/drm_bridge.h> |
9 | #include <drm/drm_crtc.h> |
10 | #include <linux/bitfield.h> |
11 | #include <linux/io.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 | #define HTX_PVI_CTRL 0x0 |
19 | #define PVI_CTRL_OP_VSYNC_POL BIT(18) |
20 | #define PVI_CTRL_OP_HSYNC_POL BIT(17) |
21 | #define PVI_CTRL_OP_DE_POL BIT(16) |
22 | #define PVI_CTRL_INP_VSYNC_POL BIT(14) |
23 | #define PVI_CTRL_INP_HSYNC_POL BIT(13) |
24 | #define PVI_CTRL_INP_DE_POL BIT(12) |
25 | #define PVI_CTRL_MODE_MASK GENMASK(2, 1) |
26 | #define PVI_CTRL_MODE_LCDIF 2 |
27 | #define PVI_CTRL_EN BIT(0) |
28 | |
29 | struct imx8mp_hdmi_pvi { |
30 | struct drm_bridge bridge; |
31 | struct device *dev; |
32 | struct drm_bridge *next_bridge; |
33 | void __iomem *regs; |
34 | }; |
35 | |
36 | static inline struct imx8mp_hdmi_pvi * |
37 | to_imx8mp_hdmi_pvi(struct drm_bridge *bridge) |
38 | { |
39 | return container_of(bridge, struct imx8mp_hdmi_pvi, bridge); |
40 | } |
41 | |
42 | static int imx8mp_hdmi_pvi_bridge_attach(struct drm_bridge *bridge, |
43 | enum drm_bridge_attach_flags flags) |
44 | { |
45 | struct imx8mp_hdmi_pvi *pvi = to_imx8mp_hdmi_pvi(bridge); |
46 | |
47 | return drm_bridge_attach(encoder: bridge->encoder, bridge: pvi->next_bridge, |
48 | previous: bridge, flags); |
49 | } |
50 | |
51 | static void imx8mp_hdmi_pvi_bridge_enable(struct drm_bridge *bridge, |
52 | struct drm_bridge_state *bridge_state) |
53 | { |
54 | struct drm_atomic_state *state = bridge_state->base.state; |
55 | struct imx8mp_hdmi_pvi *pvi = to_imx8mp_hdmi_pvi(bridge); |
56 | struct drm_connector_state *conn_state; |
57 | const struct drm_display_mode *mode; |
58 | struct drm_crtc_state *crtc_state; |
59 | struct drm_connector *connector; |
60 | u32 bus_flags = 0, val; |
61 | |
62 | connector = drm_atomic_get_new_connector_for_encoder(state, encoder: bridge->encoder); |
63 | conn_state = drm_atomic_get_new_connector_state(state, connector); |
64 | crtc_state = drm_atomic_get_new_crtc_state(state, crtc: conn_state->crtc); |
65 | |
66 | if (WARN_ON(pm_runtime_resume_and_get(pvi->dev))) |
67 | return; |
68 | |
69 | mode = &crtc_state->adjusted_mode; |
70 | |
71 | val = FIELD_PREP(PVI_CTRL_MODE_MASK, PVI_CTRL_MODE_LCDIF) | PVI_CTRL_EN; |
72 | |
73 | if (mode->flags & DRM_MODE_FLAG_PVSYNC) |
74 | val |= PVI_CTRL_OP_VSYNC_POL | PVI_CTRL_INP_VSYNC_POL; |
75 | |
76 | if (mode->flags & DRM_MODE_FLAG_PHSYNC) |
77 | val |= PVI_CTRL_OP_HSYNC_POL | PVI_CTRL_INP_HSYNC_POL; |
78 | |
79 | if (pvi->next_bridge->timings) |
80 | bus_flags = pvi->next_bridge->timings->input_bus_flags; |
81 | else if (bridge_state) |
82 | bus_flags = bridge_state->input_bus_cfg.flags; |
83 | |
84 | if (bus_flags & DRM_BUS_FLAG_DE_HIGH) |
85 | val |= PVI_CTRL_OP_DE_POL | PVI_CTRL_INP_DE_POL; |
86 | |
87 | writel(val, addr: pvi->regs + HTX_PVI_CTRL); |
88 | } |
89 | |
90 | static void imx8mp_hdmi_pvi_bridge_disable(struct drm_bridge *bridge, |
91 | struct drm_bridge_state *bridge_state) |
92 | { |
93 | struct imx8mp_hdmi_pvi *pvi = to_imx8mp_hdmi_pvi(bridge); |
94 | |
95 | writel(val: 0x0, addr: pvi->regs + HTX_PVI_CTRL); |
96 | |
97 | pm_runtime_put(dev: pvi->dev); |
98 | } |
99 | |
100 | static u32 * |
101 | imx8mp_hdmi_pvi_bridge_get_input_bus_fmts(struct drm_bridge *bridge, |
102 | struct drm_bridge_state *bridge_state, |
103 | struct drm_crtc_state *crtc_state, |
104 | struct drm_connector_state *conn_state, |
105 | u32 output_fmt, |
106 | unsigned int *num_input_fmts) |
107 | { |
108 | struct imx8mp_hdmi_pvi *pvi = to_imx8mp_hdmi_pvi(bridge); |
109 | struct drm_bridge *next_bridge = pvi->next_bridge; |
110 | struct drm_bridge_state *next_state; |
111 | |
112 | if (!next_bridge->funcs->atomic_get_input_bus_fmts) |
113 | return NULL; |
114 | |
115 | next_state = drm_atomic_get_new_bridge_state(state: crtc_state->state, |
116 | bridge: next_bridge); |
117 | |
118 | return next_bridge->funcs->atomic_get_input_bus_fmts(next_bridge, |
119 | next_state, |
120 | crtc_state, |
121 | conn_state, |
122 | output_fmt, |
123 | num_input_fmts); |
124 | } |
125 | |
126 | static const struct drm_bridge_funcs imx_hdmi_pvi_bridge_funcs = { |
127 | .attach = imx8mp_hdmi_pvi_bridge_attach, |
128 | .atomic_enable = imx8mp_hdmi_pvi_bridge_enable, |
129 | .atomic_disable = imx8mp_hdmi_pvi_bridge_disable, |
130 | .atomic_get_input_bus_fmts = imx8mp_hdmi_pvi_bridge_get_input_bus_fmts, |
131 | .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, |
132 | .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, |
133 | .atomic_reset = drm_atomic_helper_bridge_reset, |
134 | }; |
135 | |
136 | static int imx8mp_hdmi_pvi_probe(struct platform_device *pdev) |
137 | { |
138 | struct device_node *remote; |
139 | struct imx8mp_hdmi_pvi *pvi; |
140 | |
141 | pvi = devm_kzalloc(dev: &pdev->dev, size: sizeof(*pvi), GFP_KERNEL); |
142 | if (!pvi) |
143 | return -ENOMEM; |
144 | |
145 | platform_set_drvdata(pdev, data: pvi); |
146 | pvi->dev = &pdev->dev; |
147 | |
148 | pvi->regs = devm_platform_ioremap_resource(pdev, index: 0); |
149 | if (IS_ERR(ptr: pvi->regs)) |
150 | return PTR_ERR(ptr: pvi->regs); |
151 | |
152 | /* Get the next bridge in the pipeline. */ |
153 | remote = of_graph_get_remote_node(node: pdev->dev.of_node, port: 1, endpoint: -1); |
154 | if (!remote) |
155 | return -EINVAL; |
156 | |
157 | pvi->next_bridge = of_drm_find_bridge(np: remote); |
158 | of_node_put(node: remote); |
159 | |
160 | if (!pvi->next_bridge) |
161 | return dev_err_probe(dev: &pdev->dev, err: -EPROBE_DEFER, |
162 | fmt: "could not find next bridge\n" ); |
163 | |
164 | pm_runtime_enable(dev: &pdev->dev); |
165 | |
166 | /* Register the bridge. */ |
167 | pvi->bridge.funcs = &imx_hdmi_pvi_bridge_funcs; |
168 | pvi->bridge.of_node = pdev->dev.of_node; |
169 | pvi->bridge.timings = pvi->next_bridge->timings; |
170 | |
171 | drm_bridge_add(bridge: &pvi->bridge); |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static int imx8mp_hdmi_pvi_remove(struct platform_device *pdev) |
177 | { |
178 | struct imx8mp_hdmi_pvi *pvi = platform_get_drvdata(pdev); |
179 | |
180 | drm_bridge_remove(bridge: &pvi->bridge); |
181 | |
182 | pm_runtime_disable(dev: &pdev->dev); |
183 | |
184 | return 0; |
185 | } |
186 | |
187 | static const struct of_device_id imx8mp_hdmi_pvi_match[] = { |
188 | { |
189 | .compatible = "fsl,imx8mp-hdmi-pvi" , |
190 | }, { |
191 | /* sentinel */ |
192 | } |
193 | }; |
194 | MODULE_DEVICE_TABLE(of, imx8mp_hdmi_pvi_match); |
195 | |
196 | static struct platform_driver imx8mp_hdmi_pvi_driver = { |
197 | .probe = imx8mp_hdmi_pvi_probe, |
198 | .remove = imx8mp_hdmi_pvi_remove, |
199 | .driver = { |
200 | .name = "imx-hdmi-pvi" , |
201 | .of_match_table = imx8mp_hdmi_pvi_match, |
202 | }, |
203 | }; |
204 | module_platform_driver(imx8mp_hdmi_pvi_driver); |
205 | |
206 | MODULE_DESCRIPTION("i.MX8MP HDMI TX Parallel Video Interface bridge driver" ); |
207 | MODULE_LICENSE("GPL" ); |
208 | |