diff options
Diffstat (limited to 'drivers/gpu/drm/logicvc/logicvc_crtc.c')
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_crtc.c | 280 | 
1 files changed, 280 insertions, 0 deletions
diff --git a/drivers/gpu/drm/logicvc/logicvc_crtc.c b/drivers/gpu/drm/logicvc/logicvc_crtc.c new file mode 100644 index 000000000000..c94bb9bb456b --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_crtc.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_drv.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_print.h> +#include <drm/drm_vblank.h> + +#include "logicvc_crtc.h" +#include "logicvc_drm.h" +#include "logicvc_interface.h" +#include "logicvc_layer.h" +#include "logicvc_regs.h" + +#define logicvc_crtc(c) \ +	container_of(c, struct logicvc_crtc, drm_crtc) + +static enum drm_mode_status +logicvc_crtc_mode_valid(struct drm_crtc *drm_crtc, +			const struct drm_display_mode *mode) +{ +	if (mode->flags & DRM_MODE_FLAG_INTERLACE) +		return -EINVAL; + +	return 0; +} + +static void logicvc_crtc_atomic_begin(struct drm_crtc *drm_crtc, +				      struct drm_atomic_state *state) +{ +	struct logicvc_crtc *crtc = logicvc_crtc(drm_crtc); +	struct drm_crtc_state *old_state = +		drm_atomic_get_old_crtc_state(state, drm_crtc); +	struct drm_device *drm_dev = drm_crtc->dev; +	unsigned long flags; + +	/* +	 * We need to grab the pending event here if vblank was already enabled +	 * since we won't get a call to atomic_enable to grab it. +	 */ +	if (drm_crtc->state->event && old_state->active) { +		spin_lock_irqsave(&drm_dev->event_lock, flags); +		WARN_ON(drm_crtc_vblank_get(drm_crtc) != 0); + +		crtc->event = drm_crtc->state->event; +		drm_crtc->state->event = NULL; + +		spin_unlock_irqrestore(&drm_dev->event_lock, flags); +	} +} + +static void logicvc_crtc_atomic_enable(struct drm_crtc *drm_crtc, +				       struct drm_atomic_state *state) +{ +	struct logicvc_crtc *crtc = logicvc_crtc(drm_crtc); +	struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev); +	struct drm_crtc_state *old_state = +		drm_atomic_get_old_crtc_state(state, drm_crtc); +	struct drm_crtc_state *new_state = +		drm_atomic_get_new_crtc_state(state, drm_crtc); +	struct drm_display_mode *mode = &new_state->adjusted_mode; + +	struct drm_device *drm_dev = drm_crtc->dev; +	unsigned int hact, hfp, hsl, hbp; +	unsigned int vact, vfp, vsl, vbp; +	unsigned long flags; +	u32 ctrl; + +	/* Timings */ + +	hact = mode->hdisplay; +	hfp = mode->hsync_start - mode->hdisplay; +	hsl = mode->hsync_end - mode->hsync_start; +	hbp = mode->htotal - mode->hsync_end; + +	vact = mode->vdisplay; +	vfp = mode->vsync_start - mode->vdisplay; +	vsl = mode->vsync_end - mode->vsync_start; +	vbp = mode->vtotal - mode->vsync_end; + +	regmap_write(logicvc->regmap, LOGICVC_HSYNC_FRONT_PORCH_REG, hfp - 1); +	regmap_write(logicvc->regmap, LOGICVC_HSYNC_REG, hsl - 1); +	regmap_write(logicvc->regmap, LOGICVC_HSYNC_BACK_PORCH_REG, hbp - 1); +	regmap_write(logicvc->regmap, LOGICVC_HRES_REG, hact - 1); + +	regmap_write(logicvc->regmap, LOGICVC_VSYNC_FRONT_PORCH_REG, vfp - 1); +	regmap_write(logicvc->regmap, LOGICVC_VSYNC_REG, vsl - 1); +	regmap_write(logicvc->regmap, LOGICVC_VSYNC_BACK_PORCH_REG, vbp - 1); +	regmap_write(logicvc->regmap, LOGICVC_VRES_REG, vact - 1); + +	/* Signals */ + +	ctrl = LOGICVC_CTRL_HSYNC_ENABLE | LOGICVC_CTRL_VSYNC_ENABLE | +	       LOGICVC_CTRL_DE_ENABLE; + +	if (mode->flags & DRM_MODE_FLAG_NHSYNC) +		ctrl |= LOGICVC_CTRL_HSYNC_INVERT; + +	if (mode->flags & DRM_MODE_FLAG_NVSYNC) +		ctrl |= LOGICVC_CTRL_VSYNC_INVERT; + +	if (logicvc->interface) { +		struct drm_connector *connector = +			&logicvc->interface->drm_connector; +		struct drm_display_info *display_info = +			&connector->display_info; + +		if (display_info->bus_flags & DRM_BUS_FLAG_DE_LOW) +			ctrl |= LOGICVC_CTRL_DE_INVERT; + +		if (display_info->bus_flags & +		    DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) +			ctrl |= LOGICVC_CTRL_CLOCK_INVERT; +	} + +	regmap_update_bits(logicvc->regmap, LOGICVC_CTRL_REG, +			   LOGICVC_CTRL_HSYNC_ENABLE | +			   LOGICVC_CTRL_HSYNC_INVERT | +			   LOGICVC_CTRL_VSYNC_ENABLE | +			   LOGICVC_CTRL_VSYNC_INVERT | +			   LOGICVC_CTRL_DE_ENABLE | +			   LOGICVC_CTRL_DE_INVERT | +			   LOGICVC_CTRL_PIXEL_INVERT | +			   LOGICVC_CTRL_CLOCK_INVERT, ctrl); + +	/* Generate internal state reset. */ +	regmap_write(logicvc->regmap, LOGICVC_DTYPE_REG, 0); + +	drm_crtc_vblank_on(drm_crtc); + +	/* Register our event after vblank is enabled. */ +	if (drm_crtc->state->event && !old_state->active) { +		spin_lock_irqsave(&drm_dev->event_lock, flags); +		WARN_ON(drm_crtc_vblank_get(drm_crtc) != 0); + +		crtc->event = drm_crtc->state->event; +		drm_crtc->state->event = NULL; +		spin_unlock_irqrestore(&drm_dev->event_lock, flags); +	} +} + +static void logicvc_crtc_atomic_disable(struct drm_crtc *drm_crtc, +					struct drm_atomic_state *state) +{ +	struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev); +	struct drm_device *drm_dev = drm_crtc->dev; + +	drm_crtc_vblank_off(drm_crtc); + +	/* Disable and clear CRTC bits. */ +	regmap_update_bits(logicvc->regmap, LOGICVC_CTRL_REG, +			   LOGICVC_CTRL_HSYNC_ENABLE | +			   LOGICVC_CTRL_HSYNC_INVERT | +			   LOGICVC_CTRL_VSYNC_ENABLE | +			   LOGICVC_CTRL_VSYNC_INVERT | +			   LOGICVC_CTRL_DE_ENABLE | +			   LOGICVC_CTRL_DE_INVERT | +			   LOGICVC_CTRL_PIXEL_INVERT | +			   LOGICVC_CTRL_CLOCK_INVERT, 0); + +	/* Generate internal state reset. */ +	regmap_write(logicvc->regmap, LOGICVC_DTYPE_REG, 0); + +	/* Consume any leftover event since vblank is now disabled. */ +	if (drm_crtc->state->event && !drm_crtc->state->active) { +		spin_lock_irq(&drm_dev->event_lock); + +		drm_crtc_send_vblank_event(drm_crtc, drm_crtc->state->event); +		drm_crtc->state->event = NULL; +		spin_unlock_irq(&drm_dev->event_lock); +	} +} + +static const struct drm_crtc_helper_funcs logicvc_crtc_helper_funcs = { +	.mode_valid		= logicvc_crtc_mode_valid, +	.atomic_begin		= logicvc_crtc_atomic_begin, +	.atomic_enable		= logicvc_crtc_atomic_enable, +	.atomic_disable		= logicvc_crtc_atomic_disable, +}; + +static int logicvc_crtc_enable_vblank(struct drm_crtc *drm_crtc) +{ +	struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev); + +	/* Clear any pending V_SYNC interrupt. */ +	regmap_write_bits(logicvc->regmap, LOGICVC_INT_STAT_REG, +			  LOGICVC_INT_STAT_V_SYNC, LOGICVC_INT_STAT_V_SYNC); + +	/* Unmask V_SYNC interrupt. */ +	regmap_write_bits(logicvc->regmap, LOGICVC_INT_MASK_REG, +			  LOGICVC_INT_MASK_V_SYNC, 0); + +	return 0; +} + +static void logicvc_crtc_disable_vblank(struct drm_crtc *drm_crtc) +{ +	struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev); + +	/* Mask V_SYNC interrupt. */ +	regmap_write_bits(logicvc->regmap, LOGICVC_INT_MASK_REG, +			  LOGICVC_INT_MASK_V_SYNC, LOGICVC_INT_MASK_V_SYNC); +} + +static const struct drm_crtc_funcs logicvc_crtc_funcs = { +	.reset			= drm_atomic_helper_crtc_reset, +	.destroy		= drm_crtc_cleanup, +	.set_config		= drm_atomic_helper_set_config, +	.page_flip		= drm_atomic_helper_page_flip, +	.atomic_duplicate_state	= drm_atomic_helper_crtc_duplicate_state, +	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state, +	.enable_vblank		= logicvc_crtc_enable_vblank, +	.disable_vblank		= logicvc_crtc_disable_vblank, +}; + +void logicvc_crtc_vblank_handler(struct logicvc_drm *logicvc) +{ +	struct drm_device *drm_dev = &logicvc->drm_dev; +	struct logicvc_crtc *crtc = logicvc->crtc; +	unsigned long flags; + +	if (!crtc) +		return; + +	drm_crtc_handle_vblank(&crtc->drm_crtc); + +	if (crtc->event) { +		spin_lock_irqsave(&drm_dev->event_lock, flags); +		drm_crtc_send_vblank_event(&crtc->drm_crtc, crtc->event); +		drm_crtc_vblank_put(&crtc->drm_crtc); +		crtc->event = NULL; +		spin_unlock_irqrestore(&drm_dev->event_lock, flags); +	} +} + +int logicvc_crtc_init(struct logicvc_drm *logicvc) +{ +	struct drm_device *drm_dev = &logicvc->drm_dev; +	struct device *dev = drm_dev->dev; +	struct device_node *of_node = dev->of_node; +	struct logicvc_crtc *crtc; +	struct logicvc_layer *layer_primary; +	int ret; + +	crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL); +	if (!crtc) +		return -ENOMEM; + +	layer_primary = logicvc_layer_get_primary(logicvc); +	if (!layer_primary) { +		drm_err(drm_dev, "Failed to get primary layer\n"); +		return -EINVAL; +	} + +	ret = drm_crtc_init_with_planes(drm_dev, &crtc->drm_crtc, +					&layer_primary->drm_plane, NULL, +					&logicvc_crtc_funcs, NULL); +	if (ret) { +		drm_err(drm_dev, "Failed to initialize CRTC\n"); +		return ret; +	} + +	drm_crtc_helper_add(&crtc->drm_crtc, &logicvc_crtc_helper_funcs); + +	crtc->drm_crtc.port = of_graph_get_port_by_id(of_node, 1); + +	logicvc->crtc = crtc; + +	return 0; +}  | 
