1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/ |
4 | * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> |
5 | */ |
6 | |
7 | #include <drm/drm_atomic.h> |
8 | #include <drm/drm_atomic_helper.h> |
9 | #include <drm/drm_bridge.h> |
10 | #include <drm/drm_gem_framebuffer_helper.h> |
11 | #include <drm/drm_of.h> |
12 | #include <drm/drm_panel.h> |
13 | #include <drm/drm_vblank.h> |
14 | |
15 | #include "tidss_crtc.h" |
16 | #include "tidss_dispc.h" |
17 | #include "tidss_drv.h" |
18 | #include "tidss_encoder.h" |
19 | #include "tidss_kms.h" |
20 | #include "tidss_plane.h" |
21 | |
22 | static void tidss_atomic_commit_tail(struct drm_atomic_state *old_state) |
23 | { |
24 | struct drm_device *ddev = old_state->dev; |
25 | struct tidss_device *tidss = to_tidss(ddev); |
26 | |
27 | dev_dbg(ddev->dev, "%s\n" , __func__); |
28 | |
29 | tidss_runtime_get(tidss); |
30 | |
31 | drm_atomic_helper_commit_modeset_disables(dev: ddev, state: old_state); |
32 | drm_atomic_helper_commit_planes(dev: ddev, state: old_state, DRM_PLANE_COMMIT_ACTIVE_ONLY); |
33 | drm_atomic_helper_commit_modeset_enables(dev: ddev, old_state); |
34 | |
35 | drm_atomic_helper_commit_hw_done(state: old_state); |
36 | drm_atomic_helper_wait_for_flip_done(dev: ddev, old_state); |
37 | |
38 | drm_atomic_helper_cleanup_planes(dev: ddev, old_state); |
39 | |
40 | tidss_runtime_put(tidss); |
41 | } |
42 | |
43 | static const struct drm_mode_config_helper_funcs mode_config_helper_funcs = { |
44 | .atomic_commit_tail = tidss_atomic_commit_tail, |
45 | }; |
46 | |
47 | static int tidss_atomic_check(struct drm_device *ddev, |
48 | struct drm_atomic_state *state) |
49 | { |
50 | struct drm_plane_state *opstate; |
51 | struct drm_plane_state *npstate; |
52 | struct drm_plane *plane; |
53 | struct drm_crtc_state *cstate; |
54 | struct drm_crtc *crtc; |
55 | int ret, i; |
56 | |
57 | ret = drm_atomic_helper_check(dev: ddev, state); |
58 | if (ret) |
59 | return ret; |
60 | |
61 | /* |
62 | * Add all active planes on a CRTC to the atomic state, if |
63 | * x/y/z position or activity of any plane on that CRTC |
64 | * changes. This is needed for updating the plane positions in |
65 | * tidss_crtc_position_planes() which is called from |
66 | * crtc_atomic_enable() and crtc_atomic_flush(). We have an |
67 | * extra flag to mark x,y-position changes and together |
68 | * with zpos_changed the condition recognizes all the above |
69 | * cases. |
70 | */ |
71 | for_each_oldnew_plane_in_state(state, plane, opstate, npstate, i) { |
72 | if (!npstate->crtc || !npstate->visible) |
73 | continue; |
74 | |
75 | if (!opstate->crtc || opstate->crtc_x != npstate->crtc_x || |
76 | opstate->crtc_y != npstate->crtc_y) { |
77 | cstate = drm_atomic_get_crtc_state(state, |
78 | crtc: npstate->crtc); |
79 | if (IS_ERR(ptr: cstate)) |
80 | return PTR_ERR(ptr: cstate); |
81 | to_tidss_crtc_state(cstate)->plane_pos_changed = true; |
82 | } |
83 | } |
84 | |
85 | for_each_new_crtc_in_state(state, crtc, cstate, i) { |
86 | if (to_tidss_crtc_state(cstate)->plane_pos_changed || |
87 | cstate->zpos_changed) { |
88 | ret = drm_atomic_add_affected_planes(state, crtc); |
89 | if (ret) |
90 | return ret; |
91 | } |
92 | } |
93 | |
94 | return 0; |
95 | } |
96 | |
97 | static const struct drm_mode_config_funcs mode_config_funcs = { |
98 | .fb_create = drm_gem_fb_create, |
99 | .atomic_check = tidss_atomic_check, |
100 | .atomic_commit = drm_atomic_helper_commit, |
101 | }; |
102 | |
103 | static int tidss_dispc_modeset_init(struct tidss_device *tidss) |
104 | { |
105 | struct device *dev = tidss->dev; |
106 | unsigned int fourccs_len; |
107 | const u32 *fourccs = dispc_plane_formats(dispc: tidss->dispc, len: &fourccs_len); |
108 | unsigned int i; |
109 | |
110 | struct pipe { |
111 | u32 hw_videoport; |
112 | struct drm_bridge *bridge; |
113 | u32 enc_type; |
114 | }; |
115 | |
116 | const struct dispc_features *feat = tidss->feat; |
117 | u32 max_vps = feat->num_vps; |
118 | u32 max_planes = feat->num_planes; |
119 | |
120 | struct pipe pipes[TIDSS_MAX_PORTS]; |
121 | u32 num_pipes = 0; |
122 | u32 crtc_mask; |
123 | |
124 | /* first find all the connected panels & bridges */ |
125 | |
126 | for (i = 0; i < max_vps; i++) { |
127 | struct drm_panel *panel; |
128 | struct drm_bridge *bridge; |
129 | u32 enc_type = DRM_MODE_ENCODER_NONE; |
130 | int ret; |
131 | |
132 | ret = drm_of_find_panel_or_bridge(np: dev->of_node, port: i, endpoint: 0, |
133 | panel: &panel, bridge: &bridge); |
134 | if (ret == -ENODEV) { |
135 | dev_dbg(dev, "no panel/bridge for port %d\n" , i); |
136 | continue; |
137 | } else if (ret) { |
138 | dev_dbg(dev, "port %d probe returned %d\n" , i, ret); |
139 | return ret; |
140 | } |
141 | |
142 | if (panel) { |
143 | u32 conn_type; |
144 | |
145 | dev_dbg(dev, "Setting up panel for port %d\n" , i); |
146 | |
147 | switch (feat->vp_bus_type[i]) { |
148 | case DISPC_VP_OLDI: |
149 | enc_type = DRM_MODE_ENCODER_LVDS; |
150 | conn_type = DRM_MODE_CONNECTOR_LVDS; |
151 | break; |
152 | case DISPC_VP_DPI: |
153 | enc_type = DRM_MODE_ENCODER_DPI; |
154 | conn_type = DRM_MODE_CONNECTOR_DPI; |
155 | break; |
156 | default: |
157 | WARN_ON(1); |
158 | return -EINVAL; |
159 | } |
160 | |
161 | if (panel->connector_type != conn_type) { |
162 | dev_err(dev, |
163 | "%s: Panel %s has incompatible connector type for vp%d (%d != %d)\n" , |
164 | __func__, dev_name(panel->dev), i, |
165 | panel->connector_type, conn_type); |
166 | return -EINVAL; |
167 | } |
168 | |
169 | bridge = devm_drm_panel_bridge_add(dev, panel); |
170 | if (IS_ERR(ptr: bridge)) { |
171 | dev_err(dev, |
172 | "failed to set up panel bridge for port %d\n" , |
173 | i); |
174 | return PTR_ERR(ptr: bridge); |
175 | } |
176 | } |
177 | |
178 | pipes[num_pipes].hw_videoport = i; |
179 | pipes[num_pipes].bridge = bridge; |
180 | pipes[num_pipes].enc_type = enc_type; |
181 | num_pipes++; |
182 | } |
183 | |
184 | /* all planes can be on any crtc */ |
185 | crtc_mask = (1 << num_pipes) - 1; |
186 | |
187 | /* then create a plane, a crtc and an encoder for each panel/bridge */ |
188 | |
189 | for (i = 0; i < num_pipes; ++i) { |
190 | struct tidss_plane *tplane; |
191 | struct tidss_crtc *tcrtc; |
192 | u32 hw_plane_id = feat->vid_order[tidss->num_planes]; |
193 | int ret; |
194 | |
195 | tplane = tidss_plane_create(tidss, hw_plane_id, |
196 | plane_type: DRM_PLANE_TYPE_PRIMARY, crtc_mask, |
197 | formats: fourccs, num_formats: fourccs_len); |
198 | if (IS_ERR(ptr: tplane)) { |
199 | dev_err(tidss->dev, "plane create failed\n" ); |
200 | return PTR_ERR(ptr: tplane); |
201 | } |
202 | |
203 | tidss->planes[tidss->num_planes++] = &tplane->plane; |
204 | |
205 | tcrtc = tidss_crtc_create(tidss, hw_videoport: pipes[i].hw_videoport, |
206 | primary: &tplane->plane); |
207 | if (IS_ERR(ptr: tcrtc)) { |
208 | dev_err(tidss->dev, "crtc create failed\n" ); |
209 | return PTR_ERR(ptr: tcrtc); |
210 | } |
211 | |
212 | tidss->crtcs[tidss->num_crtcs++] = &tcrtc->crtc; |
213 | |
214 | ret = tidss_encoder_create(tidss, next_bridge: pipes[i].bridge, |
215 | encoder_type: pipes[i].enc_type, |
216 | possible_crtcs: 1 << tcrtc->crtc.index); |
217 | if (ret) { |
218 | dev_err(tidss->dev, "encoder create failed\n" ); |
219 | return ret; |
220 | } |
221 | } |
222 | |
223 | /* create overlay planes of the leftover planes */ |
224 | |
225 | while (tidss->num_planes < max_planes) { |
226 | struct tidss_plane *tplane; |
227 | u32 hw_plane_id = feat->vid_order[tidss->num_planes]; |
228 | |
229 | tplane = tidss_plane_create(tidss, hw_plane_id, |
230 | plane_type: DRM_PLANE_TYPE_OVERLAY, crtc_mask, |
231 | formats: fourccs, num_formats: fourccs_len); |
232 | |
233 | if (IS_ERR(ptr: tplane)) { |
234 | dev_err(tidss->dev, "plane create failed\n" ); |
235 | return PTR_ERR(ptr: tplane); |
236 | } |
237 | |
238 | tidss->planes[tidss->num_planes++] = &tplane->plane; |
239 | } |
240 | |
241 | return 0; |
242 | } |
243 | |
244 | int tidss_modeset_init(struct tidss_device *tidss) |
245 | { |
246 | struct drm_device *ddev = &tidss->ddev; |
247 | int ret; |
248 | |
249 | dev_dbg(tidss->dev, "%s\n" , __func__); |
250 | |
251 | ret = drmm_mode_config_init(dev: ddev); |
252 | if (ret) |
253 | return ret; |
254 | |
255 | ddev->mode_config.min_width = 8; |
256 | ddev->mode_config.min_height = 8; |
257 | ddev->mode_config.max_width = 8096; |
258 | ddev->mode_config.max_height = 8096; |
259 | ddev->mode_config.normalize_zpos = true; |
260 | ddev->mode_config.funcs = &mode_config_funcs; |
261 | ddev->mode_config.helper_private = &mode_config_helper_funcs; |
262 | |
263 | ret = tidss_dispc_modeset_init(tidss); |
264 | if (ret) |
265 | return ret; |
266 | |
267 | ret = drm_vblank_init(dev: ddev, num_crtcs: tidss->num_crtcs); |
268 | if (ret) |
269 | return ret; |
270 | |
271 | drm_mode_config_reset(dev: ddev); |
272 | |
273 | dev_dbg(tidss->dev, "%s done\n" , __func__); |
274 | |
275 | return 0; |
276 | } |
277 | |