162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2019 NXP.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <drm/drm_atomic.h>
762306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h>
862306a36Sopenharmony_ci#include <drm/drm_vblank.h>
962306a36Sopenharmony_ci#include <linux/platform_device.h>
1062306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include "dcss-dev.h"
1362306a36Sopenharmony_ci#include "dcss-kms.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistatic int dcss_enable_vblank(struct drm_crtc *crtc)
1662306a36Sopenharmony_ci{
1762306a36Sopenharmony_ci	struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
1862306a36Sopenharmony_ci						   base);
1962306a36Sopenharmony_ci	struct dcss_dev *dcss = crtc->dev->dev_private;
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci	dcss_dtg_vblank_irq_enable(dcss->dtg, true);
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci	dcss_dtg_ctxld_kick_irq_enable(dcss->dtg, true);
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci	enable_irq(dcss_crtc->irq);
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	return 0;
2862306a36Sopenharmony_ci}
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic void dcss_disable_vblank(struct drm_crtc *crtc)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
3362306a36Sopenharmony_ci						   base);
3462306a36Sopenharmony_ci	struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	disable_irq_nosync(dcss_crtc->irq);
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	dcss_dtg_vblank_irq_enable(dcss->dtg, false);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	if (dcss_crtc->disable_ctxld_kick_irq)
4162306a36Sopenharmony_ci		dcss_dtg_ctxld_kick_irq_enable(dcss->dtg, false);
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic const struct drm_crtc_funcs dcss_crtc_funcs = {
4562306a36Sopenharmony_ci	.set_config = drm_atomic_helper_set_config,
4662306a36Sopenharmony_ci	.destroy = drm_crtc_cleanup,
4762306a36Sopenharmony_ci	.page_flip = drm_atomic_helper_page_flip,
4862306a36Sopenharmony_ci	.reset = drm_atomic_helper_crtc_reset,
4962306a36Sopenharmony_ci	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
5062306a36Sopenharmony_ci	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
5162306a36Sopenharmony_ci	.enable_vblank = dcss_enable_vblank,
5262306a36Sopenharmony_ci	.disable_vblank = dcss_disable_vblank,
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic void dcss_crtc_atomic_begin(struct drm_crtc *crtc,
5662306a36Sopenharmony_ci				   struct drm_atomic_state *state)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	drm_crtc_vblank_on(crtc);
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic void dcss_crtc_atomic_flush(struct drm_crtc *crtc,
6262306a36Sopenharmony_ci				   struct drm_atomic_state *state)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
6562306a36Sopenharmony_ci						   base);
6662306a36Sopenharmony_ci	struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	spin_lock_irq(&crtc->dev->event_lock);
6962306a36Sopenharmony_ci	if (crtc->state->event) {
7062306a36Sopenharmony_ci		WARN_ON(drm_crtc_vblank_get(crtc));
7162306a36Sopenharmony_ci		drm_crtc_arm_vblank_event(crtc, crtc->state->event);
7262306a36Sopenharmony_ci		crtc->state->event = NULL;
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci	spin_unlock_irq(&crtc->dev->event_lock);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	if (dcss_dtg_is_enabled(dcss->dtg))
7762306a36Sopenharmony_ci		dcss_ctxld_enable(dcss->ctxld);
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic void dcss_crtc_atomic_enable(struct drm_crtc *crtc,
8162306a36Sopenharmony_ci				    struct drm_atomic_state *state)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state,
8462306a36Sopenharmony_ci									      crtc);
8562306a36Sopenharmony_ci	struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
8662306a36Sopenharmony_ci						   base);
8762306a36Sopenharmony_ci	struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private;
8862306a36Sopenharmony_ci	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
8962306a36Sopenharmony_ci	struct drm_display_mode *old_mode = &old_crtc_state->adjusted_mode;
9062306a36Sopenharmony_ci	struct videomode vm;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	drm_display_mode_to_videomode(mode, &vm);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	pm_runtime_get_sync(dcss->dev);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	vm.pixelclock = mode->crtc_clock * 1000;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	dcss_ss_subsam_set(dcss->ss);
9962306a36Sopenharmony_ci	dcss_dtg_css_set(dcss->dtg);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if (!drm_mode_equal(mode, old_mode) || !old_crtc_state->active) {
10262306a36Sopenharmony_ci		dcss_dtg_sync_set(dcss->dtg, &vm);
10362306a36Sopenharmony_ci		dcss_ss_sync_set(dcss->ss, &vm,
10462306a36Sopenharmony_ci				 mode->flags & DRM_MODE_FLAG_PHSYNC,
10562306a36Sopenharmony_ci				 mode->flags & DRM_MODE_FLAG_PVSYNC);
10662306a36Sopenharmony_ci	}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	dcss_enable_dtg_and_ss(dcss);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	dcss_ctxld_enable(dcss->ctxld);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	/* Allow CTXLD kick interrupt to be disabled when VBLANK is disabled. */
11362306a36Sopenharmony_ci	dcss_crtc->disable_ctxld_kick_irq = true;
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic void dcss_crtc_atomic_disable(struct drm_crtc *crtc,
11762306a36Sopenharmony_ci				     struct drm_atomic_state *state)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state,
12062306a36Sopenharmony_ci									      crtc);
12162306a36Sopenharmony_ci	struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
12262306a36Sopenharmony_ci						   base);
12362306a36Sopenharmony_ci	struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private;
12462306a36Sopenharmony_ci	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
12562306a36Sopenharmony_ci	struct drm_display_mode *old_mode = &old_crtc_state->adjusted_mode;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	drm_atomic_helper_disable_planes_on_crtc(old_crtc_state, false);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	spin_lock_irq(&crtc->dev->event_lock);
13062306a36Sopenharmony_ci	if (crtc->state->event) {
13162306a36Sopenharmony_ci		drm_crtc_send_vblank_event(crtc, crtc->state->event);
13262306a36Sopenharmony_ci		crtc->state->event = NULL;
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci	spin_unlock_irq(&crtc->dev->event_lock);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	dcss_dtg_ctxld_kick_irq_enable(dcss->dtg, true);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	reinit_completion(&dcss->disable_completion);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	dcss_disable_dtg_and_ss(dcss);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	dcss_ctxld_enable(dcss->ctxld);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	if (!drm_mode_equal(mode, old_mode) || !crtc->state->active)
14562306a36Sopenharmony_ci		if (!wait_for_completion_timeout(&dcss->disable_completion,
14662306a36Sopenharmony_ci						 msecs_to_jiffies(100)))
14762306a36Sopenharmony_ci			dev_err(dcss->dev, "Shutting off DTG timed out.\n");
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	/*
15062306a36Sopenharmony_ci	 * Do not shut off CTXLD kick interrupt when shutting VBLANK off. It
15162306a36Sopenharmony_ci	 * will be needed to commit the last changes, before going to suspend.
15262306a36Sopenharmony_ci	 */
15362306a36Sopenharmony_ci	dcss_crtc->disable_ctxld_kick_irq = false;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	drm_crtc_vblank_off(crtc);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	pm_runtime_mark_last_busy(dcss->dev);
15862306a36Sopenharmony_ci	pm_runtime_put_autosuspend(dcss->dev);
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic const struct drm_crtc_helper_funcs dcss_helper_funcs = {
16262306a36Sopenharmony_ci	.atomic_begin = dcss_crtc_atomic_begin,
16362306a36Sopenharmony_ci	.atomic_flush = dcss_crtc_atomic_flush,
16462306a36Sopenharmony_ci	.atomic_enable = dcss_crtc_atomic_enable,
16562306a36Sopenharmony_ci	.atomic_disable = dcss_crtc_atomic_disable,
16662306a36Sopenharmony_ci};
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic irqreturn_t dcss_crtc_irq_handler(int irq, void *dev_id)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	struct dcss_crtc *dcss_crtc = dev_id;
17162306a36Sopenharmony_ci	struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	if (!dcss_dtg_vblank_irq_valid(dcss->dtg))
17462306a36Sopenharmony_ci		return IRQ_NONE;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	if (dcss_ctxld_is_flushed(dcss->ctxld))
17762306a36Sopenharmony_ci		drm_crtc_handle_vblank(&dcss_crtc->base);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	dcss_dtg_vblank_irq_clear(dcss->dtg);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	return IRQ_HANDLED;
18262306a36Sopenharmony_ci}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ciint dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm)
18562306a36Sopenharmony_ci{
18662306a36Sopenharmony_ci	struct dcss_dev *dcss = drm->dev_private;
18762306a36Sopenharmony_ci	struct platform_device *pdev = to_platform_device(dcss->dev);
18862306a36Sopenharmony_ci	int ret;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	crtc->plane[0] = dcss_plane_init(drm, drm_crtc_mask(&crtc->base),
19162306a36Sopenharmony_ci					 DRM_PLANE_TYPE_PRIMARY, 0);
19262306a36Sopenharmony_ci	if (IS_ERR(crtc->plane[0]))
19362306a36Sopenharmony_ci		return PTR_ERR(crtc->plane[0]);
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	crtc->base.port = dcss->of_port;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	drm_crtc_helper_add(&crtc->base, &dcss_helper_funcs);
19862306a36Sopenharmony_ci	ret = drm_crtc_init_with_planes(drm, &crtc->base, &crtc->plane[0]->base,
19962306a36Sopenharmony_ci					NULL, &dcss_crtc_funcs, NULL);
20062306a36Sopenharmony_ci	if (ret) {
20162306a36Sopenharmony_ci		dev_err(dcss->dev, "failed to init crtc\n");
20262306a36Sopenharmony_ci		return ret;
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	crtc->irq = platform_get_irq_byname(pdev, "vblank");
20662306a36Sopenharmony_ci	if (crtc->irq < 0)
20762306a36Sopenharmony_ci		return crtc->irq;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	ret = request_irq(crtc->irq, dcss_crtc_irq_handler,
21062306a36Sopenharmony_ci			  0, "dcss_drm", crtc);
21162306a36Sopenharmony_ci	if (ret) {
21262306a36Sopenharmony_ci		dev_err(dcss->dev, "irq request failed with %d.\n", ret);
21362306a36Sopenharmony_ci		return ret;
21462306a36Sopenharmony_ci	}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	disable_irq(crtc->irq);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	return 0;
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_civoid dcss_crtc_deinit(struct dcss_crtc *crtc, struct drm_device *drm)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	free_irq(crtc->irq, crtc);
22462306a36Sopenharmony_ci}
225