1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright 2019 NXP. |
4 | */ |
5 | |
6 | #include <drm/drm_atomic.h> |
7 | #include <drm/drm_atomic_helper.h> |
8 | #include <drm/drm_vblank.h> |
9 | #include <linux/platform_device.h> |
10 | #include <linux/pm_runtime.h> |
11 | |
12 | #include "dcss-dev.h" |
13 | #include "dcss-kms.h" |
14 | |
15 | static int dcss_enable_vblank(struct drm_crtc *crtc) |
16 | { |
17 | struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc, |
18 | base); |
19 | struct dcss_dev *dcss = crtc->dev->dev_private; |
20 | |
21 | dcss_dtg_vblank_irq_enable(dtg: dcss->dtg, en: true); |
22 | |
23 | dcss_dtg_ctxld_kick_irq_enable(dtg: dcss->dtg, en: true); |
24 | |
25 | enable_irq(irq: dcss_crtc->irq); |
26 | |
27 | return 0; |
28 | } |
29 | |
30 | static void dcss_disable_vblank(struct drm_crtc *crtc) |
31 | { |
32 | struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc, |
33 | base); |
34 | struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private; |
35 | |
36 | disable_irq_nosync(irq: dcss_crtc->irq); |
37 | |
38 | dcss_dtg_vblank_irq_enable(dtg: dcss->dtg, en: false); |
39 | |
40 | if (dcss_crtc->disable_ctxld_kick_irq) |
41 | dcss_dtg_ctxld_kick_irq_enable(dtg: dcss->dtg, en: false); |
42 | } |
43 | |
44 | static const struct drm_crtc_funcs dcss_crtc_funcs = { |
45 | .set_config = drm_atomic_helper_set_config, |
46 | .destroy = drm_crtc_cleanup, |
47 | .page_flip = drm_atomic_helper_page_flip, |
48 | .reset = drm_atomic_helper_crtc_reset, |
49 | .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, |
50 | .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, |
51 | .enable_vblank = dcss_enable_vblank, |
52 | .disable_vblank = dcss_disable_vblank, |
53 | }; |
54 | |
55 | static void dcss_crtc_atomic_begin(struct drm_crtc *crtc, |
56 | struct drm_atomic_state *state) |
57 | { |
58 | drm_crtc_vblank_on(crtc); |
59 | } |
60 | |
61 | static void dcss_crtc_atomic_flush(struct drm_crtc *crtc, |
62 | struct drm_atomic_state *state) |
63 | { |
64 | struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc, |
65 | base); |
66 | struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private; |
67 | |
68 | spin_lock_irq(lock: &crtc->dev->event_lock); |
69 | if (crtc->state->event) { |
70 | WARN_ON(drm_crtc_vblank_get(crtc)); |
71 | drm_crtc_arm_vblank_event(crtc, e: crtc->state->event); |
72 | crtc->state->event = NULL; |
73 | } |
74 | spin_unlock_irq(lock: &crtc->dev->event_lock); |
75 | |
76 | if (dcss_dtg_is_enabled(dtg: dcss->dtg)) |
77 | dcss_ctxld_enable(ctxld: dcss->ctxld); |
78 | } |
79 | |
80 | static void dcss_crtc_atomic_enable(struct drm_crtc *crtc, |
81 | struct drm_atomic_state *state) |
82 | { |
83 | struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, |
84 | crtc); |
85 | struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc, |
86 | base); |
87 | struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private; |
88 | struct drm_display_mode *mode = &crtc->state->adjusted_mode; |
89 | struct drm_display_mode *old_mode = &old_crtc_state->adjusted_mode; |
90 | struct videomode vm; |
91 | |
92 | drm_display_mode_to_videomode(dmode: mode, vm: &vm); |
93 | |
94 | pm_runtime_get_sync(dev: dcss->dev); |
95 | |
96 | vm.pixelclock = mode->crtc_clock * 1000; |
97 | |
98 | dcss_ss_subsam_set(ss: dcss->ss); |
99 | dcss_dtg_css_set(dtg: dcss->dtg); |
100 | |
101 | if (!drm_mode_equal(mode1: mode, mode2: old_mode) || !old_crtc_state->active) { |
102 | dcss_dtg_sync_set(dtg: dcss->dtg, vm: &vm); |
103 | dcss_ss_sync_set(ss: dcss->ss, vm: &vm, |
104 | phsync: mode->flags & DRM_MODE_FLAG_PHSYNC, |
105 | pvsync: mode->flags & DRM_MODE_FLAG_PVSYNC); |
106 | } |
107 | |
108 | dcss_enable_dtg_and_ss(dcss); |
109 | |
110 | dcss_ctxld_enable(ctxld: dcss->ctxld); |
111 | |
112 | /* Allow CTXLD kick interrupt to be disabled when VBLANK is disabled. */ |
113 | dcss_crtc->disable_ctxld_kick_irq = true; |
114 | } |
115 | |
116 | static void dcss_crtc_atomic_disable(struct drm_crtc *crtc, |
117 | struct drm_atomic_state *state) |
118 | { |
119 | struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, |
120 | crtc); |
121 | struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc, |
122 | base); |
123 | struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private; |
124 | struct drm_display_mode *mode = &crtc->state->adjusted_mode; |
125 | struct drm_display_mode *old_mode = &old_crtc_state->adjusted_mode; |
126 | |
127 | drm_atomic_helper_disable_planes_on_crtc(old_crtc_state, atomic: false); |
128 | |
129 | spin_lock_irq(lock: &crtc->dev->event_lock); |
130 | if (crtc->state->event) { |
131 | drm_crtc_send_vblank_event(crtc, e: crtc->state->event); |
132 | crtc->state->event = NULL; |
133 | } |
134 | spin_unlock_irq(lock: &crtc->dev->event_lock); |
135 | |
136 | dcss_dtg_ctxld_kick_irq_enable(dtg: dcss->dtg, en: true); |
137 | |
138 | reinit_completion(x: &dcss->disable_completion); |
139 | |
140 | dcss_disable_dtg_and_ss(dcss); |
141 | |
142 | dcss_ctxld_enable(ctxld: dcss->ctxld); |
143 | |
144 | if (!drm_mode_equal(mode1: mode, mode2: old_mode) || !crtc->state->active) |
145 | if (!wait_for_completion_timeout(x: &dcss->disable_completion, |
146 | timeout: msecs_to_jiffies(m: 100))) |
147 | dev_err(dcss->dev, "Shutting off DTG timed out.\n" ); |
148 | |
149 | /* |
150 | * Do not shut off CTXLD kick interrupt when shutting VBLANK off. It |
151 | * will be needed to commit the last changes, before going to suspend. |
152 | */ |
153 | dcss_crtc->disable_ctxld_kick_irq = false; |
154 | |
155 | drm_crtc_vblank_off(crtc); |
156 | |
157 | pm_runtime_mark_last_busy(dev: dcss->dev); |
158 | pm_runtime_put_autosuspend(dev: dcss->dev); |
159 | } |
160 | |
161 | static const struct drm_crtc_helper_funcs dcss_helper_funcs = { |
162 | .atomic_begin = dcss_crtc_atomic_begin, |
163 | .atomic_flush = dcss_crtc_atomic_flush, |
164 | .atomic_enable = dcss_crtc_atomic_enable, |
165 | .atomic_disable = dcss_crtc_atomic_disable, |
166 | }; |
167 | |
168 | static irqreturn_t dcss_crtc_irq_handler(int irq, void *dev_id) |
169 | { |
170 | struct dcss_crtc *dcss_crtc = dev_id; |
171 | struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private; |
172 | |
173 | if (!dcss_dtg_vblank_irq_valid(dtg: dcss->dtg)) |
174 | return IRQ_NONE; |
175 | |
176 | if (dcss_ctxld_is_flushed(ctxld: dcss->ctxld)) |
177 | drm_crtc_handle_vblank(crtc: &dcss_crtc->base); |
178 | |
179 | dcss_dtg_vblank_irq_clear(dtg: dcss->dtg); |
180 | |
181 | return IRQ_HANDLED; |
182 | } |
183 | |
184 | int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm) |
185 | { |
186 | struct dcss_dev *dcss = drm->dev_private; |
187 | struct platform_device *pdev = to_platform_device(dcss->dev); |
188 | int ret; |
189 | |
190 | crtc->plane[0] = dcss_plane_init(drm, possible_crtcs: drm_crtc_mask(crtc: &crtc->base), |
191 | type: DRM_PLANE_TYPE_PRIMARY, zpos: 0); |
192 | if (IS_ERR(ptr: crtc->plane[0])) |
193 | return PTR_ERR(ptr: crtc->plane[0]); |
194 | |
195 | crtc->base.port = dcss->of_port; |
196 | |
197 | drm_crtc_helper_add(crtc: &crtc->base, funcs: &dcss_helper_funcs); |
198 | ret = drm_crtc_init_with_planes(dev: drm, crtc: &crtc->base, primary: &crtc->plane[0]->base, |
199 | NULL, funcs: &dcss_crtc_funcs, NULL); |
200 | if (ret) { |
201 | dev_err(dcss->dev, "failed to init crtc\n" ); |
202 | return ret; |
203 | } |
204 | |
205 | crtc->irq = platform_get_irq_byname(pdev, "vblank" ); |
206 | if (crtc->irq < 0) |
207 | return crtc->irq; |
208 | |
209 | ret = request_irq(irq: crtc->irq, handler: dcss_crtc_irq_handler, |
210 | flags: 0, name: "dcss_drm" , dev: crtc); |
211 | if (ret) { |
212 | dev_err(dcss->dev, "irq request failed with %d.\n" , ret); |
213 | return ret; |
214 | } |
215 | |
216 | disable_irq(irq: crtc->irq); |
217 | |
218 | return 0; |
219 | } |
220 | |
221 | void dcss_crtc_deinit(struct dcss_crtc *crtc, struct drm_device *drm) |
222 | { |
223 | free_irq(crtc->irq, crtc); |
224 | } |
225 | |