Commit | Line | Data |
---|---|---|
e9474be4 JH |
1 | /* |
2 | * Samsung SoC DP (Display Port) interface driver. | |
3 | * | |
4 | * Copyright (C) 2012 Samsung Electronics Co., Ltd. | |
5 | * Author: Jingoo Han <jg1.han@samsung.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License as published by the | |
9 | * Free Software Foundation; either version 2 of the License, or (at your | |
10 | * option) any later version. | |
11 | */ | |
12 | ||
13 | #include <linux/module.h> | |
14 | #include <linux/platform_device.h> | |
e9474be4 JH |
15 | #include <linux/err.h> |
16 | #include <linux/clk.h> | |
80185567 | 17 | #include <linux/of_graph.h> |
f37cd5e8 | 18 | #include <linux/component.h> |
1417f109 SP |
19 | #include <video/of_display_timing.h> |
20 | #include <video/of_videomode.h> | |
3424e3a4 | 21 | #include <video/videomode.h> |
e9474be4 | 22 | |
1417f109 | 23 | #include <drm/drmP.h> |
caa5d1e5 SP |
24 | #include <drm/drm_crtc.h> |
25 | #include <drm/drm_crtc_helper.h> | |
5f1dcd8b | 26 | #include <drm/drm_panel.h> |
1417f109 | 27 | |
3424e3a4 YY |
28 | #include <drm/bridge/analogix_dp.h> |
29 | #include <drm/exynos_drm.h> | |
30 | ||
2b8376c8 | 31 | #include "exynos_drm_crtc.h" |
e9474be4 | 32 | |
3424e3a4 | 33 | #define to_dp(nm) container_of(nm, struct exynos_dp_device, nm) |
caa5d1e5 | 34 | |
3424e3a4 YY |
35 | struct exynos_dp_device { |
36 | struct drm_encoder encoder; | |
d90efc9e | 37 | struct drm_connector *connector; |
3424e3a4 YY |
38 | struct drm_bridge *ptn_bridge; |
39 | struct drm_device *drm_dev; | |
40 | struct device *dev; | |
1c363c7c | 41 | |
3424e3a4 YY |
42 | struct videomode vm; |
43 | struct analogix_dp_plat_data plat_data; | |
1634ba25 SP |
44 | }; |
45 | ||
3424e3a4 YY |
46 | int exynos_dp_crtc_clock_enable(struct analogix_dp_plat_data *plat_data, |
47 | bool enable) | |
e9474be4 | 48 | { |
3424e3a4 YY |
49 | struct exynos_dp_device *dp = to_dp(plat_data); |
50 | struct drm_encoder *encoder = &dp->encoder; | |
e9474be4 | 51 | |
196e059a AH |
52 | if (!encoder->crtc) |
53 | return -EPERM; | |
e9474be4 | 54 | |
196e059a | 55 | exynos_drm_pipe_clk_enable(to_exynos_crtc(encoder->crtc), enable); |
d5c0eed0 | 56 | |
e9474be4 JH |
57 | return 0; |
58 | } | |
59 | ||
3424e3a4 | 60 | static int exynos_dp_poweron(struct analogix_dp_plat_data *plat_data) |
e9474be4 | 61 | { |
3424e3a4 | 62 | return exynos_dp_crtc_clock_enable(plat_data, true); |
e9474be4 JH |
63 | } |
64 | ||
3424e3a4 | 65 | static int exynos_dp_poweroff(struct analogix_dp_plat_data *plat_data) |
e9474be4 | 66 | { |
3424e3a4 | 67 | return exynos_dp_crtc_clock_enable(plat_data, false); |
e9474be4 JH |
68 | } |
69 | ||
fcc150c5 YY |
70 | static int exynos_dp_get_modes(struct analogix_dp_plat_data *plat_data, |
71 | struct drm_connector *connector) | |
e9474be4 | 72 | { |
3424e3a4 | 73 | struct exynos_dp_device *dp = to_dp(plat_data); |
caa5d1e5 | 74 | struct drm_display_mode *mode; |
3424e3a4 | 75 | int num_modes = 0; |
caa5d1e5 | 76 | |
3424e3a4 YY |
77 | if (dp->plat_data.panel) |
78 | return num_modes; | |
5f1dcd8b | 79 | |
caa5d1e5 SP |
80 | mode = drm_mode_create(connector->dev); |
81 | if (!mode) { | |
82 | DRM_ERROR("failed to create a new display mode.\n"); | |
3424e3a4 | 83 | return num_modes; |
caa5d1e5 SP |
84 | } |
85 | ||
27d60e3d | 86 | drm_display_mode_from_videomode(&dp->vm, mode); |
caa5d1e5 SP |
87 | connector->display_info.width_mm = mode->width_mm; |
88 | connector->display_info.height_mm = mode->height_mm; | |
89 | ||
90 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; | |
91 | drm_mode_set_name(mode); | |
92 | drm_mode_probed_add(connector, mode); | |
1417f109 | 93 | |
3424e3a4 | 94 | return num_modes + 1; |
1634ba25 SP |
95 | } |
96 | ||
3424e3a4 YY |
97 | static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data, |
98 | struct drm_bridge *bridge, | |
99 | struct drm_connector *connector) | |
caa5d1e5 | 100 | { |
3424e3a4 | 101 | struct exynos_dp_device *dp = to_dp(plat_data); |
07c42703 | 102 | struct drm_encoder *encoder = &dp->encoder; |
caa5d1e5 SP |
103 | int ret; |
104 | ||
34ea3d38 | 105 | drm_connector_register(connector); |
d90efc9e | 106 | dp->connector = connector; |
b6595dc7 | 107 | |
3424e3a4 YY |
108 | /* Pre-empt DP connector creation if there's a bridge */ |
109 | if (dp->ptn_bridge) { | |
110 | bridge->next = dp->ptn_bridge; | |
111 | dp->ptn_bridge->encoder = encoder; | |
112 | ret = drm_bridge_attach(encoder->dev, dp->ptn_bridge); | |
113 | if (ret) { | |
114 | DRM_ERROR("Failed to attach bridge to drm\n"); | |
115 | bridge->next = NULL; | |
116 | return ret; | |
5f1dcd8b AK |
117 | } |
118 | } | |
119 | ||
07c42703 GP |
120 | return 0; |
121 | } | |
122 | ||
07c42703 GP |
123 | static void exynos_dp_mode_set(struct drm_encoder *encoder, |
124 | struct drm_display_mode *mode, | |
125 | struct drm_display_mode *adjusted_mode) | |
126 | { | |
127 | } | |
128 | ||
3424e3a4 | 129 | static void exynos_dp_nop(struct drm_encoder *encoder) |
07c42703 | 130 | { |
3424e3a4 | 131 | /* do nothing */ |
07c42703 GP |
132 | } |
133 | ||
800ba2b5 | 134 | static const struct drm_encoder_helper_funcs exynos_dp_encoder_helper_funcs = { |
2b8376c8 | 135 | .mode_set = exynos_dp_mode_set, |
3424e3a4 YY |
136 | .enable = exynos_dp_nop, |
137 | .disable = exynos_dp_nop, | |
1417f109 SP |
138 | }; |
139 | ||
800ba2b5 | 140 | static const struct drm_encoder_funcs exynos_dp_encoder_funcs = { |
2b8376c8 GP |
141 | .destroy = drm_encoder_cleanup, |
142 | }; | |
143 | ||
1417f109 SP |
144 | static int exynos_dp_dt_parse_panel(struct exynos_dp_device *dp) |
145 | { | |
146 | int ret; | |
147 | ||
27d60e3d | 148 | ret = of_get_videomode(dp->dev->of_node, &dp->vm, OF_USE_NATIVE_MODE); |
1417f109 SP |
149 | if (ret) { |
150 | DRM_ERROR("failed: of_get_videomode() : %d\n", ret); | |
151 | return ret; | |
152 | } | |
153 | return 0; | |
154 | } | |
155 | ||
f37cd5e8 | 156 | static int exynos_dp_bind(struct device *dev, struct device *master, void *data) |
e9474be4 | 157 | { |
1df6e5fb | 158 | struct exynos_dp_device *dp = dev_get_drvdata(dev); |
2b8376c8 | 159 | struct drm_encoder *encoder = &dp->encoder; |
3424e3a4 YY |
160 | struct drm_device *drm_dev = data; |
161 | int pipe, ret; | |
e9474be4 | 162 | |
3424e3a4 YY |
163 | /* |
164 | * Just like the probe function said, we don't need the | |
165 | * device drvrate anymore, we should leave the charge to | |
166 | * analogix dp driver, set the device drvdata to NULL. | |
167 | */ | |
168 | dev_set_drvdata(dev, NULL); | |
e9474be4 | 169 | |
3424e3a4 YY |
170 | dp->dev = dev; |
171 | dp->drm_dev = drm_dev; | |
c4e235c2 | 172 | |
3424e3a4 YY |
173 | dp->plat_data.dev_type = EXYNOS_DP; |
174 | dp->plat_data.power_on = exynos_dp_poweron; | |
175 | dp->plat_data.power_off = exynos_dp_poweroff; | |
176 | dp->plat_data.attach = exynos_dp_bridge_attach; | |
177 | dp->plat_data.get_modes = exynos_dp_get_modes; | |
c4e235c2 | 178 | |
3424e3a4 | 179 | if (!dp->plat_data.panel && !dp->ptn_bridge) { |
5f1dcd8b AK |
180 | ret = exynos_dp_dt_parse_panel(dp); |
181 | if (ret) | |
182 | return ret; | |
183 | } | |
1417f109 | 184 | |
2b8376c8 GP |
185 | pipe = exynos_drm_crtc_get_pipe_from_type(drm_dev, |
186 | EXYNOS_DISPLAY_TYPE_LCD); | |
187 | if (pipe < 0) | |
188 | return pipe; | |
189 | ||
190 | encoder->possible_crtcs = 1 << pipe; | |
191 | ||
192 | DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs); | |
193 | ||
194 | drm_encoder_init(drm_dev, encoder, &exynos_dp_encoder_funcs, | |
13a3d91f | 195 | DRM_MODE_ENCODER_TMDS, NULL); |
2b8376c8 GP |
196 | |
197 | drm_encoder_helper_add(encoder, &exynos_dp_encoder_helper_funcs); | |
a2986e80 | 198 | |
3424e3a4 | 199 | dp->plat_data.encoder = encoder; |
a2986e80 | 200 | |
3424e3a4 | 201 | return analogix_dp_bind(dev, dp->drm_dev, &dp->plat_data); |
e9474be4 JH |
202 | } |
203 | ||
f37cd5e8 | 204 | static void exynos_dp_unbind(struct device *dev, struct device *master, |
bcbb7033 | 205 | void *data) |
e9474be4 | 206 | { |
3424e3a4 | 207 | return analogix_dp_unbind(dev, master, data); |
f37cd5e8 ID |
208 | } |
209 | ||
210 | static const struct component_ops exynos_dp_ops = { | |
211 | .bind = exynos_dp_bind, | |
212 | .unbind = exynos_dp_unbind, | |
213 | }; | |
214 | ||
215 | static int exynos_dp_probe(struct platform_device *pdev) | |
216 | { | |
5f1dcd8b | 217 | struct device *dev = &pdev->dev; |
37e11062 | 218 | struct device_node *np = NULL, *endpoint = NULL; |
5f1dcd8b | 219 | struct exynos_dp_device *dp; |
df5225bc | 220 | |
5f1dcd8b | 221 | dp = devm_kzalloc(&pdev->dev, sizeof(struct exynos_dp_device), |
bcbb7033 | 222 | GFP_KERNEL); |
5f1dcd8b AK |
223 | if (!dp) |
224 | return -ENOMEM; | |
225 | ||
3424e3a4 YY |
226 | /* |
227 | * We just use the drvdata until driver run into component | |
228 | * add function, and then we would set drvdata to null, so | |
229 | * that analogix dp driver would take charge of the drvdata. | |
230 | */ | |
1df6e5fb AH |
231 | platform_set_drvdata(pdev, dp); |
232 | ||
a9fa8528 | 233 | /* This is for the backward compatibility. */ |
37e11062 JMC |
234 | np = of_parse_phandle(dev->of_node, "panel", 0); |
235 | if (np) { | |
3424e3a4 | 236 | dp->plat_data.panel = of_drm_find_panel(np); |
37e11062 | 237 | of_node_put(np); |
3424e3a4 | 238 | if (!dp->plat_data.panel) |
5f1dcd8b | 239 | return -EPROBE_DEFER; |
a9fa8528 | 240 | goto out; |
37e11062 | 241 | } |
a9fa8528 | 242 | |
80185567 AK |
243 | endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); |
244 | if (endpoint) { | |
37e11062 JMC |
245 | np = of_graph_get_remote_port_parent(endpoint); |
246 | if (np) { | |
247 | /* The remote port can be either a panel or a bridge */ | |
3424e3a4 YY |
248 | dp->plat_data.panel = of_drm_find_panel(np); |
249 | if (!dp->plat_data.panel) { | |
37e11062 JMC |
250 | dp->ptn_bridge = of_drm_find_bridge(np); |
251 | if (!dp->ptn_bridge) { | |
252 | of_node_put(np); | |
253 | return -EPROBE_DEFER; | |
254 | } | |
255 | } | |
256 | of_node_put(np); | |
257 | } else { | |
258 | DRM_ERROR("no remote endpoint device node found.\n"); | |
259 | return -EINVAL; | |
260 | } | |
261 | } else { | |
262 | DRM_ERROR("no port endpoint subnode found.\n"); | |
263 | return -EINVAL; | |
80185567 AK |
264 | } |
265 | ||
a9fa8528 | 266 | out: |
3424e3a4 | 267 | return component_add(&pdev->dev, &exynos_dp_ops); |
f37cd5e8 ID |
268 | } |
269 | ||
270 | static int exynos_dp_remove(struct platform_device *pdev) | |
271 | { | |
df5225bc | 272 | component_del(&pdev->dev, &exynos_dp_ops); |
df5225bc | 273 | |
e9474be4 JH |
274 | return 0; |
275 | } | |
276 | ||
613d3853 GP |
277 | #ifdef CONFIG_PM |
278 | static int exynos_dp_suspend(struct device *dev) | |
279 | { | |
3424e3a4 | 280 | return analogix_dp_suspend(dev); |
613d3853 GP |
281 | } |
282 | ||
283 | static int exynos_dp_resume(struct device *dev) | |
284 | { | |
3424e3a4 | 285 | return analogix_dp_resume(dev); |
613d3853 GP |
286 | } |
287 | #endif | |
288 | ||
289 | static const struct dev_pm_ops exynos_dp_pm_ops = { | |
290 | SET_RUNTIME_PM_OPS(exynos_dp_suspend, exynos_dp_resume, NULL) | |
291 | }; | |
292 | ||
c4e235c2 AK |
293 | static const struct of_device_id exynos_dp_match[] = { |
294 | { .compatible = "samsung,exynos5-dp" }, | |
295 | {}, | |
296 | }; | |
bd024b86 | 297 | MODULE_DEVICE_TABLE(of, exynos_dp_match); |
c4e235c2 | 298 | |
1417f109 | 299 | struct platform_driver dp_driver = { |
e9474be4 | 300 | .probe = exynos_dp_probe, |
48c68c4f | 301 | .remove = exynos_dp_remove, |
e9474be4 JH |
302 | .driver = { |
303 | .name = "exynos-dp", | |
304 | .owner = THIS_MODULE, | |
613d3853 | 305 | .pm = &exynos_dp_pm_ops, |
f9b1e013 | 306 | .of_match_table = exynos_dp_match, |
e9474be4 JH |
307 | }, |
308 | }; | |
309 | ||
e9474be4 | 310 | MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); |
3424e3a4 | 311 | MODULE_DESCRIPTION("Samsung Specific Analogix-DP Driver Extension"); |
8f589bba | 312 | MODULE_LICENSE("GPL v2"); |