1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Samsung SoC DP (Display Port) interface driver. |
4 | * |
5 | * Copyright (C) 2012 Samsung Electronics Co., Ltd. |
6 | * Author: Jingoo Han <jg1.han@samsung.com> |
7 | */ |
8 | |
9 | #include <linux/clk.h> |
10 | #include <linux/component.h> |
11 | #include <linux/err.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 | #include <video/of_display_timing.h> |
18 | #include <video/of_videomode.h> |
19 | #include <video/videomode.h> |
20 | |
21 | #include <drm/bridge/analogix_dp.h> |
22 | #include <drm/drm_atomic_helper.h> |
23 | #include <drm/drm_bridge.h> |
24 | #include <drm/drm_crtc.h> |
25 | #include <drm/drm_of.h> |
26 | #include <drm/drm_panel.h> |
27 | #include <drm/drm_print.h> |
28 | #include <drm/drm_probe_helper.h> |
29 | #include <drm/drm_simple_kms_helper.h> |
30 | #include <drm/exynos_drm.h> |
31 | |
32 | #include "exynos_drm_crtc.h" |
33 | |
34 | #define to_dp(nm) container_of(nm, struct exynos_dp_device, nm) |
35 | |
36 | struct exynos_dp_device { |
37 | struct drm_encoder encoder; |
38 | struct drm_connector *connector; |
39 | struct drm_bridge *ptn_bridge; |
40 | struct drm_device *drm_dev; |
41 | struct device *dev; |
42 | |
43 | struct videomode vm; |
44 | struct analogix_dp_device *adp; |
45 | struct analogix_dp_plat_data plat_data; |
46 | }; |
47 | |
48 | static int exynos_dp_crtc_clock_enable(struct analogix_dp_plat_data *plat_data, |
49 | bool enable) |
50 | { |
51 | struct exynos_dp_device *dp = to_dp(plat_data); |
52 | struct drm_encoder *encoder = &dp->encoder; |
53 | |
54 | if (!encoder->crtc) |
55 | return -EPERM; |
56 | |
57 | exynos_drm_pipe_clk_enable(to_exynos_crtc(encoder->crtc), enable); |
58 | |
59 | return 0; |
60 | } |
61 | |
62 | static int exynos_dp_poweron(struct analogix_dp_plat_data *plat_data) |
63 | { |
64 | return exynos_dp_crtc_clock_enable(plat_data, enable: true); |
65 | } |
66 | |
67 | static int exynos_dp_poweroff(struct analogix_dp_plat_data *plat_data) |
68 | { |
69 | return exynos_dp_crtc_clock_enable(plat_data, enable: false); |
70 | } |
71 | |
72 | static int exynos_dp_get_modes(struct analogix_dp_plat_data *plat_data, |
73 | struct drm_connector *connector) |
74 | { |
75 | struct exynos_dp_device *dp = to_dp(plat_data); |
76 | struct drm_display_mode *mode; |
77 | |
78 | if (dp->plat_data.panel) |
79 | return 0; |
80 | |
81 | mode = drm_mode_create(dev: connector->dev); |
82 | if (!mode) { |
83 | DRM_DEV_ERROR(dp->dev, |
84 | "failed to create a new display mode.\n" ); |
85 | return 0; |
86 | } |
87 | |
88 | drm_display_mode_from_videomode(vm: &dp->vm, dmode: mode); |
89 | connector->display_info.width_mm = mode->width_mm; |
90 | connector->display_info.height_mm = mode->height_mm; |
91 | |
92 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
93 | drm_mode_set_name(mode); |
94 | drm_mode_probed_add(connector, mode); |
95 | |
96 | return 1; |
97 | } |
98 | |
99 | static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data, |
100 | struct drm_bridge *bridge, |
101 | struct drm_connector *connector) |
102 | { |
103 | struct exynos_dp_device *dp = to_dp(plat_data); |
104 | int ret; |
105 | |
106 | dp->connector = connector; |
107 | |
108 | /* Pre-empt DP connector creation if there's a bridge */ |
109 | if (dp->ptn_bridge) { |
110 | ret = drm_bridge_attach(encoder: &dp->encoder, bridge: dp->ptn_bridge, previous: bridge, |
111 | flags: 0); |
112 | if (ret) |
113 | return ret; |
114 | } |
115 | |
116 | return 0; |
117 | } |
118 | |
119 | static void exynos_dp_mode_set(struct drm_encoder *encoder, |
120 | struct drm_display_mode *mode, |
121 | struct drm_display_mode *adjusted_mode) |
122 | { |
123 | } |
124 | |
125 | static void exynos_dp_nop(struct drm_encoder *encoder) |
126 | { |
127 | /* do nothing */ |
128 | } |
129 | |
130 | static const struct drm_encoder_helper_funcs exynos_dp_encoder_helper_funcs = { |
131 | .mode_set = exynos_dp_mode_set, |
132 | .enable = exynos_dp_nop, |
133 | .disable = exynos_dp_nop, |
134 | }; |
135 | |
136 | static int exynos_dp_dt_parse_panel(struct exynos_dp_device *dp) |
137 | { |
138 | int ret; |
139 | |
140 | ret = of_get_videomode(np: dp->dev->of_node, vm: &dp->vm, OF_USE_NATIVE_MODE); |
141 | if (ret) { |
142 | DRM_DEV_ERROR(dp->dev, |
143 | "failed: of_get_videomode() : %d\n" , ret); |
144 | return ret; |
145 | } |
146 | return 0; |
147 | } |
148 | |
149 | static int exynos_dp_bind(struct device *dev, struct device *master, void *data) |
150 | { |
151 | struct exynos_dp_device *dp = dev_get_drvdata(dev); |
152 | struct drm_encoder *encoder = &dp->encoder; |
153 | struct drm_device *drm_dev = data; |
154 | int ret; |
155 | |
156 | dp->drm_dev = drm_dev; |
157 | |
158 | if (!dp->plat_data.panel && !dp->ptn_bridge) { |
159 | ret = exynos_dp_dt_parse_panel(dp); |
160 | if (ret) |
161 | return ret; |
162 | } |
163 | |
164 | drm_simple_encoder_init(dev: drm_dev, encoder, DRM_MODE_ENCODER_TMDS); |
165 | |
166 | drm_encoder_helper_add(encoder, funcs: &exynos_dp_encoder_helper_funcs); |
167 | |
168 | ret = exynos_drm_set_possible_crtcs(encoder, out_type: EXYNOS_DISPLAY_TYPE_LCD); |
169 | if (ret < 0) |
170 | return ret; |
171 | |
172 | dp->plat_data.encoder = encoder; |
173 | |
174 | ret = analogix_dp_bind(dp: dp->adp, drm_dev: dp->drm_dev); |
175 | if (ret) |
176 | dp->encoder.funcs->destroy(&dp->encoder); |
177 | |
178 | return ret; |
179 | } |
180 | |
181 | static void exynos_dp_unbind(struct device *dev, struct device *master, |
182 | void *data) |
183 | { |
184 | struct exynos_dp_device *dp = dev_get_drvdata(dev); |
185 | |
186 | analogix_dp_unbind(dp: dp->adp); |
187 | dp->encoder.funcs->destroy(&dp->encoder); |
188 | } |
189 | |
190 | static const struct component_ops exynos_dp_ops = { |
191 | .bind = exynos_dp_bind, |
192 | .unbind = exynos_dp_unbind, |
193 | }; |
194 | |
195 | static int exynos_dp_probe(struct platform_device *pdev) |
196 | { |
197 | struct device *dev = &pdev->dev; |
198 | struct device_node *np; |
199 | struct exynos_dp_device *dp; |
200 | struct drm_panel *panel; |
201 | struct drm_bridge *bridge; |
202 | int ret; |
203 | |
204 | dp = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct exynos_dp_device), |
205 | GFP_KERNEL); |
206 | if (!dp) |
207 | return -ENOMEM; |
208 | |
209 | dp->dev = dev; |
210 | /* |
211 | * We just use the drvdata until driver run into component |
212 | * add function, and then we would set drvdata to null, so |
213 | * that analogix dp driver would take charge of the drvdata. |
214 | */ |
215 | platform_set_drvdata(pdev, data: dp); |
216 | |
217 | /* This is for the backward compatibility. */ |
218 | np = of_parse_phandle(np: dev->of_node, phandle_name: "panel" , index: 0); |
219 | if (np) { |
220 | dp->plat_data.panel = of_drm_find_panel(np); |
221 | |
222 | of_node_put(node: np); |
223 | if (IS_ERR(ptr: dp->plat_data.panel)) |
224 | return PTR_ERR(ptr: dp->plat_data.panel); |
225 | |
226 | goto out; |
227 | } |
228 | |
229 | ret = drm_of_find_panel_or_bridge(np: dev->of_node, port: 0, endpoint: 0, panel: &panel, bridge: &bridge); |
230 | if (ret) |
231 | return ret; |
232 | |
233 | /* The remote port can be either a panel or a bridge */ |
234 | dp->plat_data.panel = panel; |
235 | dp->plat_data.dev_type = EXYNOS_DP; |
236 | dp->plat_data.power_on_start = exynos_dp_poweron; |
237 | dp->plat_data.power_off = exynos_dp_poweroff; |
238 | dp->plat_data.attach = exynos_dp_bridge_attach; |
239 | dp->plat_data.get_modes = exynos_dp_get_modes; |
240 | dp->plat_data.skip_connector = !!bridge; |
241 | |
242 | dp->ptn_bridge = bridge; |
243 | |
244 | out: |
245 | dp->adp = analogix_dp_probe(dev, plat_data: &dp->plat_data); |
246 | if (IS_ERR(ptr: dp->adp)) |
247 | return PTR_ERR(ptr: dp->adp); |
248 | |
249 | return component_add(&pdev->dev, &exynos_dp_ops); |
250 | } |
251 | |
252 | static void exynos_dp_remove(struct platform_device *pdev) |
253 | { |
254 | struct exynos_dp_device *dp = platform_get_drvdata(pdev); |
255 | |
256 | component_del(&pdev->dev, &exynos_dp_ops); |
257 | analogix_dp_remove(dp: dp->adp); |
258 | } |
259 | |
260 | static int exynos_dp_suspend(struct device *dev) |
261 | { |
262 | struct exynos_dp_device *dp = dev_get_drvdata(dev); |
263 | |
264 | return analogix_dp_suspend(dp: dp->adp); |
265 | } |
266 | |
267 | static int exynos_dp_resume(struct device *dev) |
268 | { |
269 | struct exynos_dp_device *dp = dev_get_drvdata(dev); |
270 | |
271 | return analogix_dp_resume(dp: dp->adp); |
272 | } |
273 | |
274 | static DEFINE_RUNTIME_DEV_PM_OPS(exynos_dp_pm_ops, exynos_dp_suspend, |
275 | exynos_dp_resume, NULL); |
276 | |
277 | static const struct of_device_id exynos_dp_match[] = { |
278 | { .compatible = "samsung,exynos5-dp" }, |
279 | {}, |
280 | }; |
281 | MODULE_DEVICE_TABLE(of, exynos_dp_match); |
282 | |
283 | struct platform_driver dp_driver = { |
284 | .probe = exynos_dp_probe, |
285 | .remove_new = exynos_dp_remove, |
286 | .driver = { |
287 | .name = "exynos-dp" , |
288 | .owner = THIS_MODULE, |
289 | .pm = pm_ptr(&exynos_dp_pm_ops), |
290 | .of_match_table = exynos_dp_match, |
291 | }, |
292 | }; |
293 | |
294 | MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>" ); |
295 | MODULE_DESCRIPTION("Samsung Specific Analogix-DP Driver Extension" ); |
296 | MODULE_LICENSE("GPL v2" ); |
297 | |