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