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; | |
37 | struct drm_connector connector; | |
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 | ||
3424e3a4 | 70 | static int exynos_dp_get_modes(struct analogix_dp_plat_data *plat_data) |
e9474be4 | 71 | { |
3424e3a4 YY |
72 | struct exynos_dp_device *dp = to_dp(plat_data); |
73 | struct drm_connector *connector = &dp->connector; | |
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); |
b6595dc7 | 106 | |
3424e3a4 YY |
107 | /* Pre-empt DP connector creation if there's a bridge */ |
108 | if (dp->ptn_bridge) { | |
109 | bridge->next = dp->ptn_bridge; | |
110 | dp->ptn_bridge->encoder = encoder; | |
111 | ret = drm_bridge_attach(encoder->dev, dp->ptn_bridge); | |
112 | if (ret) { | |
113 | DRM_ERROR("Failed to attach bridge to drm\n"); | |
114 | bridge->next = NULL; | |
115 | return ret; | |
5f1dcd8b AK |
116 | } |
117 | } | |
118 | ||
07c42703 GP |
119 | return 0; |
120 | } | |
121 | ||
07c42703 GP |
122 | static void exynos_dp_mode_set(struct drm_encoder *encoder, |
123 | struct drm_display_mode *mode, | |
124 | struct drm_display_mode *adjusted_mode) | |
125 | { | |
126 | } | |
127 | ||
3424e3a4 | 128 | static void exynos_dp_nop(struct drm_encoder *encoder) |
07c42703 | 129 | { |
3424e3a4 | 130 | /* do nothing */ |
07c42703 GP |
131 | } |
132 | ||
800ba2b5 | 133 | static const struct drm_encoder_helper_funcs exynos_dp_encoder_helper_funcs = { |
2b8376c8 | 134 | .mode_set = exynos_dp_mode_set, |
3424e3a4 YY |
135 | .enable = exynos_dp_nop, |
136 | .disable = exynos_dp_nop, | |
1417f109 SP |
137 | }; |
138 | ||
800ba2b5 | 139 | static const struct drm_encoder_funcs exynos_dp_encoder_funcs = { |
2b8376c8 GP |
140 | .destroy = drm_encoder_cleanup, |
141 | }; | |
142 | ||
1417f109 SP |
143 | static int exynos_dp_dt_parse_panel(struct exynos_dp_device *dp) |
144 | { | |
145 | int ret; | |
146 | ||
27d60e3d | 147 | ret = of_get_videomode(dp->dev->of_node, &dp->vm, OF_USE_NATIVE_MODE); |
1417f109 SP |
148 | if (ret) { |
149 | DRM_ERROR("failed: of_get_videomode() : %d\n", ret); | |
150 | return ret; | |
151 | } | |
152 | return 0; | |
153 | } | |
154 | ||
f37cd5e8 | 155 | static int exynos_dp_bind(struct device *dev, struct device *master, void *data) |
e9474be4 | 156 | { |
1df6e5fb | 157 | struct exynos_dp_device *dp = dev_get_drvdata(dev); |
2b8376c8 | 158 | struct drm_encoder *encoder = &dp->encoder; |
3424e3a4 YY |
159 | struct drm_device *drm_dev = data; |
160 | int pipe, ret; | |
e9474be4 | 161 | |
3424e3a4 YY |
162 | /* |
163 | * Just like the probe function said, we don't need the | |
164 | * device drvrate anymore, we should leave the charge to | |
165 | * analogix dp driver, set the device drvdata to NULL. | |
166 | */ | |
167 | dev_set_drvdata(dev, NULL); | |
e9474be4 | 168 | |
3424e3a4 YY |
169 | dp->dev = dev; |
170 | dp->drm_dev = drm_dev; | |
c4e235c2 | 171 | |
3424e3a4 YY |
172 | dp->plat_data.dev_type = EXYNOS_DP; |
173 | dp->plat_data.power_on = exynos_dp_poweron; | |
174 | dp->plat_data.power_off = exynos_dp_poweroff; | |
175 | dp->plat_data.attach = exynos_dp_bridge_attach; | |
176 | dp->plat_data.get_modes = exynos_dp_get_modes; | |
c4e235c2 | 177 | |
3424e3a4 | 178 | if (!dp->plat_data.panel && !dp->ptn_bridge) { |
5f1dcd8b AK |
179 | ret = exynos_dp_dt_parse_panel(dp); |
180 | if (ret) | |
181 | return ret; | |
182 | } | |
1417f109 | 183 | |
2b8376c8 GP |
184 | pipe = exynos_drm_crtc_get_pipe_from_type(drm_dev, |
185 | EXYNOS_DISPLAY_TYPE_LCD); | |
186 | if (pipe < 0) | |
187 | return pipe; | |
188 | ||
189 | encoder->possible_crtcs = 1 << pipe; | |
190 | ||
191 | DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs); | |
192 | ||
193 | drm_encoder_init(drm_dev, encoder, &exynos_dp_encoder_funcs, | |
13a3d91f | 194 | DRM_MODE_ENCODER_TMDS, NULL); |
2b8376c8 GP |
195 | |
196 | drm_encoder_helper_add(encoder, &exynos_dp_encoder_helper_funcs); | |
a2986e80 | 197 | |
3424e3a4 | 198 | dp->plat_data.encoder = encoder; |
a2986e80 | 199 | |
3424e3a4 | 200 | return analogix_dp_bind(dev, dp->drm_dev, &dp->plat_data); |
e9474be4 JH |
201 | } |
202 | ||
f37cd5e8 | 203 | static void exynos_dp_unbind(struct device *dev, struct device *master, |
bcbb7033 | 204 | void *data) |
e9474be4 | 205 | { |
3424e3a4 | 206 | return analogix_dp_unbind(dev, master, data); |
f37cd5e8 ID |
207 | } |
208 | ||
209 | static const struct component_ops exynos_dp_ops = { | |
210 | .bind = exynos_dp_bind, | |
211 | .unbind = exynos_dp_unbind, | |
212 | }; | |
213 | ||
214 | static int exynos_dp_probe(struct platform_device *pdev) | |
215 | { | |
5f1dcd8b | 216 | struct device *dev = &pdev->dev; |
37e11062 | 217 | struct device_node *np = NULL, *endpoint = NULL; |
5f1dcd8b | 218 | struct exynos_dp_device *dp; |
df5225bc | 219 | |
5f1dcd8b | 220 | dp = devm_kzalloc(&pdev->dev, sizeof(struct exynos_dp_device), |
bcbb7033 | 221 | GFP_KERNEL); |
5f1dcd8b AK |
222 | if (!dp) |
223 | return -ENOMEM; | |
224 | ||
3424e3a4 YY |
225 | /* |
226 | * We just use the drvdata until driver run into component | |
227 | * add function, and then we would set drvdata to null, so | |
228 | * that analogix dp driver would take charge of the drvdata. | |
229 | */ | |
1df6e5fb AH |
230 | platform_set_drvdata(pdev, dp); |
231 | ||
a9fa8528 | 232 | /* This is for the backward compatibility. */ |
37e11062 JMC |
233 | np = of_parse_phandle(dev->of_node, "panel", 0); |
234 | if (np) { | |
3424e3a4 | 235 | dp->plat_data.panel = of_drm_find_panel(np); |
37e11062 | 236 | of_node_put(np); |
3424e3a4 | 237 | if (!dp->plat_data.panel) |
5f1dcd8b | 238 | return -EPROBE_DEFER; |
a9fa8528 | 239 | goto out; |
37e11062 | 240 | } |
a9fa8528 | 241 | |
80185567 AK |
242 | endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); |
243 | if (endpoint) { | |
37e11062 JMC |
244 | np = of_graph_get_remote_port_parent(endpoint); |
245 | if (np) { | |
246 | /* The remote port can be either a panel or a bridge */ | |
3424e3a4 YY |
247 | dp->plat_data.panel = of_drm_find_panel(np); |
248 | if (!dp->plat_data.panel) { | |
37e11062 JMC |
249 | dp->ptn_bridge = of_drm_find_bridge(np); |
250 | if (!dp->ptn_bridge) { | |
251 | of_node_put(np); | |
252 | return -EPROBE_DEFER; | |
253 | } | |
254 | } | |
255 | of_node_put(np); | |
256 | } else { | |
257 | DRM_ERROR("no remote endpoint device node found.\n"); | |
258 | return -EINVAL; | |
259 | } | |
260 | } else { | |
261 | DRM_ERROR("no port endpoint subnode found.\n"); | |
262 | return -EINVAL; | |
80185567 AK |
263 | } |
264 | ||
a9fa8528 | 265 | out: |
3424e3a4 | 266 | return component_add(&pdev->dev, &exynos_dp_ops); |
f37cd5e8 ID |
267 | } |
268 | ||
269 | static int exynos_dp_remove(struct platform_device *pdev) | |
270 | { | |
df5225bc | 271 | component_del(&pdev->dev, &exynos_dp_ops); |
df5225bc | 272 | |
e9474be4 JH |
273 | return 0; |
274 | } | |
275 | ||
613d3853 GP |
276 | #ifdef CONFIG_PM |
277 | static int exynos_dp_suspend(struct device *dev) | |
278 | { | |
3424e3a4 | 279 | return analogix_dp_suspend(dev); |
613d3853 GP |
280 | } |
281 | ||
282 | static int exynos_dp_resume(struct device *dev) | |
283 | { | |
3424e3a4 | 284 | return analogix_dp_resume(dev); |
613d3853 GP |
285 | } |
286 | #endif | |
287 | ||
288 | static const struct dev_pm_ops exynos_dp_pm_ops = { | |
289 | SET_RUNTIME_PM_OPS(exynos_dp_suspend, exynos_dp_resume, NULL) | |
290 | }; | |
291 | ||
c4e235c2 AK |
292 | static const struct of_device_id exynos_dp_match[] = { |
293 | { .compatible = "samsung,exynos5-dp" }, | |
294 | {}, | |
295 | }; | |
bd024b86 | 296 | MODULE_DEVICE_TABLE(of, exynos_dp_match); |
c4e235c2 | 297 | |
1417f109 | 298 | struct platform_driver dp_driver = { |
e9474be4 | 299 | .probe = exynos_dp_probe, |
48c68c4f | 300 | .remove = exynos_dp_remove, |
e9474be4 JH |
301 | .driver = { |
302 | .name = "exynos-dp", | |
303 | .owner = THIS_MODULE, | |
613d3853 | 304 | .pm = &exynos_dp_pm_ops, |
f9b1e013 | 305 | .of_match_table = exynos_dp_match, |
e9474be4 JH |
306 | }, |
307 | }; | |
308 | ||
e9474be4 | 309 | MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); |
3424e3a4 | 310 | MODULE_DESCRIPTION("Samsung Specific Analogix-DP Driver Extension"); |
8f589bba | 311 | MODULE_LICENSE("GPL v2"); |