1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Exynos DRM Parallel output support. |
4 | * |
5 | * Copyright (c) 2014 Samsung Electronics Co., Ltd |
6 | * |
7 | * Contacts: Andrzej Hajda <a.hajda@samsung.com> |
8 | */ |
9 | |
10 | #include <linux/of.h> |
11 | #include <linux/of_graph.h> |
12 | #include <linux/regulator/consumer.h> |
13 | |
14 | #include <drm/drm_atomic_helper.h> |
15 | #include <drm/drm_panel.h> |
16 | #include <drm/drm_print.h> |
17 | #include <drm/drm_probe_helper.h> |
18 | #include <drm/drm_simple_kms_helper.h> |
19 | |
20 | #include <video/of_videomode.h> |
21 | #include <video/videomode.h> |
22 | |
23 | #include "exynos_drm_crtc.h" |
24 | |
25 | struct exynos_dpi { |
26 | struct drm_encoder encoder; |
27 | struct device *dev; |
28 | struct device_node *panel_node; |
29 | |
30 | struct drm_panel *panel; |
31 | struct drm_connector connector; |
32 | |
33 | struct videomode *vm; |
34 | }; |
35 | |
36 | #define connector_to_dpi(c) container_of(c, struct exynos_dpi, connector) |
37 | |
38 | static inline struct exynos_dpi *encoder_to_dpi(struct drm_encoder *e) |
39 | { |
40 | return container_of(e, struct exynos_dpi, encoder); |
41 | } |
42 | |
43 | static enum drm_connector_status |
44 | exynos_dpi_detect(struct drm_connector *connector, bool force) |
45 | { |
46 | return connector_status_connected; |
47 | } |
48 | |
49 | static void exynos_dpi_connector_destroy(struct drm_connector *connector) |
50 | { |
51 | drm_connector_unregister(connector); |
52 | drm_connector_cleanup(connector); |
53 | } |
54 | |
55 | static const struct drm_connector_funcs exynos_dpi_connector_funcs = { |
56 | .detect = exynos_dpi_detect, |
57 | .fill_modes = drm_helper_probe_single_connector_modes, |
58 | .destroy = exynos_dpi_connector_destroy, |
59 | .reset = drm_atomic_helper_connector_reset, |
60 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
61 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
62 | }; |
63 | |
64 | static int exynos_dpi_get_modes(struct drm_connector *connector) |
65 | { |
66 | struct exynos_dpi *ctx = connector_to_dpi(connector); |
67 | |
68 | /* fimd timings gets precedence over panel modes */ |
69 | if (ctx->vm) { |
70 | struct drm_display_mode *mode; |
71 | |
72 | mode = drm_mode_create(dev: connector->dev); |
73 | if (!mode) { |
74 | DRM_DEV_ERROR(ctx->dev, |
75 | "failed to create a new display mode\n" ); |
76 | return 0; |
77 | } |
78 | drm_display_mode_from_videomode(vm: ctx->vm, dmode: mode); |
79 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
80 | drm_mode_probed_add(connector, mode); |
81 | return 1; |
82 | } |
83 | |
84 | if (ctx->panel) |
85 | return drm_panel_get_modes(panel: ctx->panel, connector); |
86 | |
87 | return 0; |
88 | } |
89 | |
90 | static const struct drm_connector_helper_funcs exynos_dpi_connector_helper_funcs = { |
91 | .get_modes = exynos_dpi_get_modes, |
92 | }; |
93 | |
94 | static int exynos_dpi_create_connector(struct drm_encoder *encoder) |
95 | { |
96 | struct exynos_dpi *ctx = encoder_to_dpi(e: encoder); |
97 | struct drm_connector *connector = &ctx->connector; |
98 | int ret; |
99 | |
100 | connector->polled = DRM_CONNECTOR_POLL_HPD; |
101 | |
102 | ret = drm_connector_init(dev: encoder->dev, connector, |
103 | funcs: &exynos_dpi_connector_funcs, |
104 | DRM_MODE_CONNECTOR_DPI); |
105 | if (ret) { |
106 | DRM_DEV_ERROR(ctx->dev, |
107 | "failed to initialize connector with drm\n" ); |
108 | return ret; |
109 | } |
110 | |
111 | drm_connector_helper_add(connector, funcs: &exynos_dpi_connector_helper_funcs); |
112 | drm_connector_attach_encoder(connector, encoder); |
113 | |
114 | return 0; |
115 | } |
116 | |
117 | static void exynos_dpi_mode_set(struct drm_encoder *encoder, |
118 | struct drm_display_mode *mode, |
119 | struct drm_display_mode *adjusted_mode) |
120 | { |
121 | } |
122 | |
123 | static void exynos_dpi_enable(struct drm_encoder *encoder) |
124 | { |
125 | struct exynos_dpi *ctx = encoder_to_dpi(e: encoder); |
126 | |
127 | if (ctx->panel) { |
128 | drm_panel_prepare(panel: ctx->panel); |
129 | drm_panel_enable(panel: ctx->panel); |
130 | } |
131 | } |
132 | |
133 | static void exynos_dpi_disable(struct drm_encoder *encoder) |
134 | { |
135 | struct exynos_dpi *ctx = encoder_to_dpi(e: encoder); |
136 | |
137 | if (ctx->panel) { |
138 | drm_panel_disable(panel: ctx->panel); |
139 | drm_panel_unprepare(panel: ctx->panel); |
140 | } |
141 | } |
142 | |
143 | static const struct drm_encoder_helper_funcs exynos_dpi_encoder_helper_funcs = { |
144 | .mode_set = exynos_dpi_mode_set, |
145 | .enable = exynos_dpi_enable, |
146 | .disable = exynos_dpi_disable, |
147 | }; |
148 | |
149 | enum { |
150 | FIMD_PORT_IN0, |
151 | FIMD_PORT_IN1, |
152 | FIMD_PORT_IN2, |
153 | FIMD_PORT_RGB, |
154 | FIMD_PORT_WRB, |
155 | }; |
156 | |
157 | static int exynos_dpi_parse_dt(struct exynos_dpi *ctx) |
158 | { |
159 | struct device *dev = ctx->dev; |
160 | struct device_node *dn = dev->of_node; |
161 | struct device_node *np; |
162 | |
163 | ctx->panel_node = of_graph_get_remote_node(node: dn, port: FIMD_PORT_RGB, endpoint: 0); |
164 | |
165 | np = of_get_child_by_name(node: dn, name: "display-timings" ); |
166 | if (np) { |
167 | struct videomode *vm; |
168 | int ret; |
169 | |
170 | of_node_put(node: np); |
171 | |
172 | vm = devm_kzalloc(dev, size: sizeof(*ctx->vm), GFP_KERNEL); |
173 | if (!vm) |
174 | return -ENOMEM; |
175 | |
176 | ret = of_get_videomode(np: dn, vm, index: 0); |
177 | if (ret < 0) { |
178 | devm_kfree(dev, p: vm); |
179 | return ret; |
180 | } |
181 | |
182 | ctx->vm = vm; |
183 | |
184 | return 0; |
185 | } |
186 | |
187 | if (!ctx->panel_node) |
188 | return -EINVAL; |
189 | |
190 | return 0; |
191 | } |
192 | |
193 | int exynos_dpi_bind(struct drm_device *dev, struct drm_encoder *encoder) |
194 | { |
195 | int ret; |
196 | |
197 | drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_TMDS); |
198 | |
199 | drm_encoder_helper_add(encoder, funcs: &exynos_dpi_encoder_helper_funcs); |
200 | |
201 | ret = exynos_drm_set_possible_crtcs(encoder, out_type: EXYNOS_DISPLAY_TYPE_LCD); |
202 | if (ret < 0) |
203 | return ret; |
204 | |
205 | ret = exynos_dpi_create_connector(encoder); |
206 | if (ret) { |
207 | DRM_DEV_ERROR(encoder_to_dpi(encoder)->dev, |
208 | "failed to create connector ret = %d\n" , ret); |
209 | drm_encoder_cleanup(encoder); |
210 | return ret; |
211 | } |
212 | |
213 | return 0; |
214 | } |
215 | |
216 | struct drm_encoder *exynos_dpi_probe(struct device *dev) |
217 | { |
218 | struct exynos_dpi *ctx; |
219 | int ret; |
220 | |
221 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
222 | if (!ctx) |
223 | return ERR_PTR(error: -ENOMEM); |
224 | |
225 | ctx->dev = dev; |
226 | |
227 | ret = exynos_dpi_parse_dt(ctx); |
228 | if (ret < 0) { |
229 | devm_kfree(dev, p: ctx); |
230 | return NULL; |
231 | } |
232 | |
233 | if (ctx->panel_node) { |
234 | ctx->panel = of_drm_find_panel(np: ctx->panel_node); |
235 | if (IS_ERR(ptr: ctx->panel)) |
236 | return ERR_CAST(ptr: ctx->panel); |
237 | } |
238 | |
239 | return &ctx->encoder; |
240 | } |
241 | |
242 | int exynos_dpi_remove(struct drm_encoder *encoder) |
243 | { |
244 | struct exynos_dpi *ctx = encoder_to_dpi(e: encoder); |
245 | |
246 | exynos_dpi_disable(encoder: &ctx->encoder); |
247 | |
248 | return 0; |
249 | } |
250 | |