1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2015 Free Electrons |
4 | * Copyright (C) 2015 NextThing Co |
5 | * |
6 | * Maxime Ripard <maxime.ripard@free-electrons.com> |
7 | */ |
8 | |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/ioport.h> |
11 | #include <linux/of_address.h> |
12 | #include <linux/of_graph.h> |
13 | #include <linux/of_irq.h> |
14 | #include <linux/regmap.h> |
15 | |
16 | #include <video/videomode.h> |
17 | |
18 | #include <drm/drm_atomic.h> |
19 | #include <drm/drm_atomic_helper.h> |
20 | #include <drm/drm_crtc.h> |
21 | #include <drm/drm_modes.h> |
22 | #include <drm/drm_print.h> |
23 | #include <drm/drm_probe_helper.h> |
24 | #include <drm/drm_vblank.h> |
25 | |
26 | #include "sun4i_backend.h" |
27 | #include "sun4i_crtc.h" |
28 | #include "sun4i_drv.h" |
29 | #include "sunxi_engine.h" |
30 | #include "sun4i_tcon.h" |
31 | |
32 | /* |
33 | * While this isn't really working in the DRM theory, in practice we |
34 | * can only ever have one encoder per TCON since we have a mux in our |
35 | * TCON. |
36 | */ |
37 | static struct drm_encoder *sun4i_crtc_get_encoder(struct drm_crtc *crtc) |
38 | { |
39 | struct drm_encoder *encoder; |
40 | |
41 | drm_for_each_encoder(encoder, crtc->dev) |
42 | if (encoder->crtc == crtc) |
43 | return encoder; |
44 | |
45 | return NULL; |
46 | } |
47 | |
48 | static int sun4i_crtc_atomic_check(struct drm_crtc *crtc, |
49 | struct drm_atomic_state *state) |
50 | { |
51 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, |
52 | crtc); |
53 | struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); |
54 | struct sunxi_engine *engine = scrtc->engine; |
55 | int ret = 0; |
56 | |
57 | if (engine && engine->ops && engine->ops->atomic_check) |
58 | ret = engine->ops->atomic_check(engine, crtc_state); |
59 | |
60 | return ret; |
61 | } |
62 | |
63 | static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc, |
64 | struct drm_atomic_state *state) |
65 | { |
66 | struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, |
67 | crtc); |
68 | struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); |
69 | struct drm_device *dev = crtc->dev; |
70 | struct sunxi_engine *engine = scrtc->engine; |
71 | unsigned long flags; |
72 | |
73 | if (crtc->state->event) { |
74 | WARN_ON(drm_crtc_vblank_get(crtc) != 0); |
75 | |
76 | spin_lock_irqsave(&dev->event_lock, flags); |
77 | scrtc->event = crtc->state->event; |
78 | spin_unlock_irqrestore(lock: &dev->event_lock, flags); |
79 | crtc->state->event = NULL; |
80 | } |
81 | |
82 | if (engine->ops->atomic_begin) |
83 | engine->ops->atomic_begin(engine, old_state); |
84 | } |
85 | |
86 | static void sun4i_crtc_atomic_flush(struct drm_crtc *crtc, |
87 | struct drm_atomic_state *state) |
88 | { |
89 | struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); |
90 | struct drm_pending_vblank_event *event = crtc->state->event; |
91 | |
92 | DRM_DEBUG_DRIVER("Committing plane changes\n" ); |
93 | |
94 | sunxi_engine_commit(engine: scrtc->engine); |
95 | |
96 | if (event) { |
97 | crtc->state->event = NULL; |
98 | |
99 | spin_lock_irq(lock: &crtc->dev->event_lock); |
100 | if (drm_crtc_vblank_get(crtc) == 0) |
101 | drm_crtc_arm_vblank_event(crtc, e: event); |
102 | else |
103 | drm_crtc_send_vblank_event(crtc, e: event); |
104 | spin_unlock_irq(lock: &crtc->dev->event_lock); |
105 | } |
106 | } |
107 | |
108 | static void sun4i_crtc_atomic_disable(struct drm_crtc *crtc, |
109 | struct drm_atomic_state *state) |
110 | { |
111 | struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc); |
112 | struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); |
113 | |
114 | DRM_DEBUG_DRIVER("Disabling the CRTC\n" ); |
115 | |
116 | drm_crtc_vblank_off(crtc); |
117 | |
118 | sun4i_tcon_set_status(crtc: scrtc->tcon, encoder, enable: false); |
119 | |
120 | if (crtc->state->event && !crtc->state->active) { |
121 | spin_lock_irq(lock: &crtc->dev->event_lock); |
122 | drm_crtc_send_vblank_event(crtc, e: crtc->state->event); |
123 | spin_unlock_irq(lock: &crtc->dev->event_lock); |
124 | |
125 | crtc->state->event = NULL; |
126 | } |
127 | } |
128 | |
129 | static void sun4i_crtc_atomic_enable(struct drm_crtc *crtc, |
130 | struct drm_atomic_state *state) |
131 | { |
132 | struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc); |
133 | struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); |
134 | |
135 | DRM_DEBUG_DRIVER("Enabling the CRTC\n" ); |
136 | |
137 | sun4i_tcon_set_status(crtc: scrtc->tcon, encoder, enable: true); |
138 | |
139 | drm_crtc_vblank_on(crtc); |
140 | } |
141 | |
142 | static void sun4i_crtc_mode_set_nofb(struct drm_crtc *crtc) |
143 | { |
144 | struct drm_display_mode *mode = &crtc->state->adjusted_mode; |
145 | struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc); |
146 | struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); |
147 | |
148 | sun4i_tcon_mode_set(tcon: scrtc->tcon, encoder, mode); |
149 | sunxi_engine_mode_set(engine: scrtc->engine, mode); |
150 | } |
151 | |
152 | static const struct drm_crtc_helper_funcs sun4i_crtc_helper_funcs = { |
153 | .atomic_check = sun4i_crtc_atomic_check, |
154 | .atomic_begin = sun4i_crtc_atomic_begin, |
155 | .atomic_flush = sun4i_crtc_atomic_flush, |
156 | .atomic_enable = sun4i_crtc_atomic_enable, |
157 | .atomic_disable = sun4i_crtc_atomic_disable, |
158 | .mode_set_nofb = sun4i_crtc_mode_set_nofb, |
159 | }; |
160 | |
161 | static int sun4i_crtc_enable_vblank(struct drm_crtc *crtc) |
162 | { |
163 | struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); |
164 | |
165 | DRM_DEBUG_DRIVER("Enabling VBLANK on crtc %p\n" , crtc); |
166 | |
167 | sun4i_tcon_enable_vblank(tcon: scrtc->tcon, enable: true); |
168 | |
169 | return 0; |
170 | } |
171 | |
172 | static void sun4i_crtc_disable_vblank(struct drm_crtc *crtc) |
173 | { |
174 | struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); |
175 | |
176 | DRM_DEBUG_DRIVER("Disabling VBLANK on crtc %p\n" , crtc); |
177 | |
178 | sun4i_tcon_enable_vblank(tcon: scrtc->tcon, enable: false); |
179 | } |
180 | |
181 | static const struct drm_crtc_funcs sun4i_crtc_funcs = { |
182 | .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, |
183 | .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, |
184 | .destroy = drm_crtc_cleanup, |
185 | .page_flip = drm_atomic_helper_page_flip, |
186 | .reset = drm_atomic_helper_crtc_reset, |
187 | .set_config = drm_atomic_helper_set_config, |
188 | .enable_vblank = sun4i_crtc_enable_vblank, |
189 | .disable_vblank = sun4i_crtc_disable_vblank, |
190 | }; |
191 | |
192 | struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm, |
193 | struct sunxi_engine *engine, |
194 | struct sun4i_tcon *tcon) |
195 | { |
196 | struct sun4i_crtc *scrtc; |
197 | struct drm_plane **planes; |
198 | struct drm_plane *primary = NULL, *cursor = NULL; |
199 | int ret, i; |
200 | |
201 | scrtc = devm_kzalloc(dev: drm->dev, size: sizeof(*scrtc), GFP_KERNEL); |
202 | if (!scrtc) |
203 | return ERR_PTR(error: -ENOMEM); |
204 | scrtc->engine = engine; |
205 | scrtc->tcon = tcon; |
206 | |
207 | /* Create our layers */ |
208 | planes = sunxi_engine_layers_init(drm, engine); |
209 | if (IS_ERR(ptr: planes)) { |
210 | dev_err(drm->dev, "Couldn't create the planes\n" ); |
211 | return NULL; |
212 | } |
213 | |
214 | /* find primary and cursor planes for drm_crtc_init_with_planes */ |
215 | for (i = 0; planes[i]; i++) { |
216 | struct drm_plane *plane = planes[i]; |
217 | |
218 | switch (plane->type) { |
219 | case DRM_PLANE_TYPE_PRIMARY: |
220 | primary = plane; |
221 | break; |
222 | case DRM_PLANE_TYPE_CURSOR: |
223 | cursor = plane; |
224 | break; |
225 | default: |
226 | break; |
227 | } |
228 | } |
229 | |
230 | ret = drm_crtc_init_with_planes(dev: drm, crtc: &scrtc->crtc, |
231 | primary, |
232 | cursor, |
233 | funcs: &sun4i_crtc_funcs, |
234 | NULL); |
235 | if (ret) { |
236 | dev_err(drm->dev, "Couldn't init DRM CRTC\n" ); |
237 | return ERR_PTR(error: ret); |
238 | } |
239 | |
240 | drm_crtc_helper_add(crtc: &scrtc->crtc, funcs: &sun4i_crtc_helper_funcs); |
241 | |
242 | /* Set crtc.port to output port node of the tcon */ |
243 | scrtc->crtc.port = of_graph_get_port_by_id(node: scrtc->tcon->dev->of_node, |
244 | id: 1); |
245 | |
246 | /* Set possible_crtcs to this crtc for overlay planes */ |
247 | for (i = 0; planes[i]; i++) { |
248 | uint32_t possible_crtcs = drm_crtc_mask(crtc: &scrtc->crtc); |
249 | struct drm_plane *plane = planes[i]; |
250 | |
251 | if (plane->type == DRM_PLANE_TYPE_OVERLAY) |
252 | plane->possible_crtcs = possible_crtcs; |
253 | } |
254 | |
255 | return scrtc; |
256 | } |
257 | |