diff options
Diffstat (limited to 'drivers/gpu/drm/logicvc')
| -rw-r--r-- | drivers/gpu/drm/logicvc/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/Makefile | 9 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_crtc.c | 280 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_crtc.h | 21 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_drm.c | 496 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_drm.h | 67 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_interface.c | 214 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_interface.h | 28 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_layer.c | 631 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_layer.h | 64 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_mode.c | 80 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_mode.h | 15 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_of.c | 185 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_of.h | 46 | ||||
| -rw-r--r-- | drivers/gpu/drm/logicvc/logicvc_regs.h | 80 | 
15 files changed, 2225 insertions, 0 deletions
| diff --git a/drivers/gpu/drm/logicvc/Kconfig b/drivers/gpu/drm/logicvc/Kconfig new file mode 100644 index 000000000000..300b2be07385 --- /dev/null +++ b/drivers/gpu/drm/logicvc/Kconfig @@ -0,0 +1,9 @@ +config DRM_LOGICVC +	tristate "LogiCVC DRM" +	depends on DRM +	depends on OF || COMPILE_TEST +	select DRM_KMS_HELPER +	select DRM_KMS_CMA_HELPER +	select DRM_GEM_CMA_HELPER +	help +	  DRM display driver for the logiCVC programmable logic block from Xylon diff --git a/drivers/gpu/drm/logicvc/Makefile b/drivers/gpu/drm/logicvc/Makefile new file mode 100644 index 000000000000..6e4b01979d38 --- /dev/null +++ b/drivers/gpu/drm/logicvc/Makefile @@ -0,0 +1,9 @@ +logicvc-drm-y += \ +	logicvc_crtc.o \ +	logicvc_drm.o \ +	logicvc_interface.o \ +	logicvc_layer.o \ +	logicvc_mode.o \ +	logicvc_of.o + +obj-$(CONFIG_DRM_LOGICVC) += logicvc-drm.o 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; +} diff --git a/drivers/gpu/drm/logicvc/logicvc_crtc.h b/drivers/gpu/drm/logicvc/logicvc_crtc.h new file mode 100644 index 000000000000..b122901f2936 --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_crtc.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _LOGICVC_CRTC_H_ +#define _LOGICVC_CRTC_H_ + +struct drm_pending_vblank_event; +struct logicvc_drm; + +struct logicvc_crtc { +	struct drm_crtc drm_crtc; +	struct drm_pending_vblank_event *event; +}; + +void logicvc_crtc_vblank_handler(struct logicvc_drm *logicvc); +int logicvc_crtc_init(struct logicvc_drm *logicvc); + +#endif diff --git a/drivers/gpu/drm/logicvc/logicvc_drm.c b/drivers/gpu/drm/logicvc/logicvc_drm.c new file mode 100644 index 000000000000..65a050176c33 --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_drm.c @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_reserved_mem.h> +#include <linux/regmap.h> +#include <linux/types.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_print.h> + +#include "logicvc_crtc.h" +#include "logicvc_drm.h" +#include "logicvc_interface.h" +#include "logicvc_mode.h" +#include "logicvc_layer.h" +#include "logicvc_of.h" +#include "logicvc_regs.h" + +DEFINE_DRM_GEM_CMA_FOPS(logicvc_drm_fops); + +static int logicvc_drm_gem_cma_dumb_create(struct drm_file *file_priv, +					   struct drm_device *drm_dev, +					   struct drm_mode_create_dumb *args) +{ +	struct logicvc_drm *logicvc = logicvc_drm(drm_dev); + +	/* Stride is always fixed to its configuration value. */ +	args->pitch = logicvc->config.row_stride * DIV_ROUND_UP(args->bpp, 8); + +	return drm_gem_cma_dumb_create_internal(file_priv, drm_dev, args); +} + +static struct drm_driver logicvc_drm_driver = { +	.driver_features		= DRIVER_GEM | DRIVER_MODESET | +					  DRIVER_ATOMIC, + +	.fops				= &logicvc_drm_fops, +	.name				= "logicvc-drm", +	.desc				= "Xylon LogiCVC DRM driver", +	.date				= "20200403", +	.major				= 1, +	.minor				= 0, + +	DRM_GEM_CMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(logicvc_drm_gem_cma_dumb_create), +}; + +static struct regmap_config logicvc_drm_regmap_config = { +	.reg_bits	= 32, +	.val_bits	= 32, +	.reg_stride	= 4, +	.name		= "logicvc-drm", +}; + +static irqreturn_t logicvc_drm_irq_handler(int irq, void *data) +{ +	struct logicvc_drm *logicvc = data; +	irqreturn_t ret = IRQ_NONE; +	u32 stat = 0; + +	/* Get pending interrupt sources. */ +	regmap_read(logicvc->regmap, LOGICVC_INT_STAT_REG, &stat); + +	/* Clear all pending interrupt sources. */ +	regmap_write(logicvc->regmap, LOGICVC_INT_STAT_REG, stat); + +	if (stat & LOGICVC_INT_STAT_V_SYNC) { +		logicvc_crtc_vblank_handler(logicvc); +		ret = IRQ_HANDLED; +	} + +	return ret; +} + +static int logicvc_drm_config_parse(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_drm_config *config = &logicvc->config; +	struct device_node *layers_node; +	int ret; + +	logicvc_of_property_parse_bool(of_node, LOGICVC_OF_PROPERTY_DITHERING, +				       &config->dithering); +	logicvc_of_property_parse_bool(of_node, +				       LOGICVC_OF_PROPERTY_BACKGROUND_LAYER, +				       &config->background_layer); +	logicvc_of_property_parse_bool(of_node, +				       LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE, +				       &config->layers_configurable); + +	ret = logicvc_of_property_parse_u32(of_node, +					    LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE, +					    &config->display_interface); +	if (ret) +		return ret; + +	ret = logicvc_of_property_parse_u32(of_node, +					    LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE, +					    &config->display_colorspace); +	if (ret) +		return ret; + +	ret = logicvc_of_property_parse_u32(of_node, +					    LOGICVC_OF_PROPERTY_DISPLAY_DEPTH, +					    &config->display_depth); +	if (ret) +		return ret; + +	ret = logicvc_of_property_parse_u32(of_node, +					    LOGICVC_OF_PROPERTY_ROW_STRIDE, +					    &config->row_stride); +	if (ret) +		return ret; + +	layers_node = of_get_child_by_name(of_node, "layers"); +	if (!layers_node) { +		drm_err(drm_dev, "Missing non-optional layers node\n"); +		return -EINVAL; +	} + +	config->layers_count = of_get_child_count(layers_node); +	if (!config->layers_count) { +		drm_err(drm_dev, +			"Missing a non-optional layers children node\n"); +		return -EINVAL; +	} + +	return 0; +} + +static int logicvc_clocks_prepare(struct logicvc_drm *logicvc) +{ +	struct drm_device *drm_dev = &logicvc->drm_dev; +	struct device *dev = drm_dev->dev; + +	struct { +		struct clk **clk; +		char *name; +		bool optional; +	} clocks_map[] = { +		{ +			.clk = &logicvc->vclk, +			.name = "vclk", +			.optional = false, +		}, +		{ +			.clk = &logicvc->vclk2, +			.name = "vclk2", +			.optional = true, +		}, +		{ +			.clk = &logicvc->lvdsclk, +			.name = "lvdsclk", +			.optional = true, +		}, +		{ +			.clk = &logicvc->lvdsclkn, +			.name = "lvdsclkn", +			.optional = true, +		}, +	}; +	unsigned int i; +	int ret; + +	for (i = 0; i < ARRAY_SIZE(clocks_map); i++) { +		struct clk *clk; + +		clk = devm_clk_get(dev, clocks_map[i].name); +		if (IS_ERR(clk)) { +			if (PTR_ERR(clk) == -ENOENT && clocks_map[i].optional) +				continue; + +			drm_err(drm_dev, "Missing non-optional clock %s\n", +				clocks_map[i].name); + +			ret = PTR_ERR(clk); +			goto error; +		} + +		ret = clk_prepare_enable(clk); +		if (ret) { +			drm_err(drm_dev, +				"Failed to prepare and enable clock %s\n", +				clocks_map[i].name); +			goto error; +		} + +		*clocks_map[i].clk = clk; +	} + +	return 0; + +error: +	for (i = 0; i < ARRAY_SIZE(clocks_map); i++) { +		if (!*clocks_map[i].clk) +			continue; + +		clk_disable_unprepare(*clocks_map[i].clk); +		*clocks_map[i].clk = NULL; +	} + +	return ret; +} + +static int logicvc_clocks_unprepare(struct logicvc_drm *logicvc) +{ +	struct clk **clocks[] = { +		&logicvc->vclk, +		&logicvc->vclk2, +		&logicvc->lvdsclk, +		&logicvc->lvdsclkn, +	}; +	unsigned int i; + +	for (i = 0; i < ARRAY_SIZE(clocks); i++) { +		if (!*clocks[i]) +			continue; + +		clk_disable_unprepare(*clocks[i]); +		*clocks[i] = NULL; +	} + +	return 0; +} + +static const struct logicvc_drm_caps logicvc_drm_caps[] = { +	{ +		.major		= 3, +		.layer_address	= false, +	}, +	{ +		.major		= 4, +		.layer_address	= true, +	}, +	{ +		.major		= 5, +		.layer_address	= true, +	}, +}; + +static const struct logicvc_drm_caps * +logicvc_drm_caps_match(struct logicvc_drm *logicvc) +{ +	struct drm_device *drm_dev = &logicvc->drm_dev; +	const struct logicvc_drm_caps *caps = NULL; +	unsigned int major, minor; +	char level; +	unsigned int i; +	u32 version; + +	regmap_read(logicvc->regmap, LOGICVC_IP_VERSION_REG, &version); + +	major = FIELD_GET(LOGICVC_IP_VERSION_MAJOR_MASK, version); +	minor = FIELD_GET(LOGICVC_IP_VERSION_MINOR_MASK, version); +	level = FIELD_GET(LOGICVC_IP_VERSION_LEVEL_MASK, version) + 'a'; + +	for (i = 0; i < ARRAY_SIZE(logicvc_drm_caps); i++) { +		if (logicvc_drm_caps[i].major && +		    logicvc_drm_caps[i].major != major) +			continue; + +		if (logicvc_drm_caps[i].minor && +		    logicvc_drm_caps[i].minor != minor) +			continue; + +		if (logicvc_drm_caps[i].level && +		    logicvc_drm_caps[i].level != level) +			continue; + +		caps = &logicvc_drm_caps[i]; +	} + +	drm_info(drm_dev, "LogiCVC version %d.%02d.%c\n", major, minor, level); + +	return caps; +} + +static int logicvc_drm_probe(struct platform_device *pdev) +{ +	struct device_node *of_node = pdev->dev.of_node; +	struct device_node *reserved_mem_node; +	struct reserved_mem *reserved_mem = NULL; +	const struct logicvc_drm_caps *caps; +	struct logicvc_drm *logicvc; +	struct device *dev = &pdev->dev; +	struct drm_device *drm_dev; +	struct regmap *regmap = NULL; +	struct resource res; +	void __iomem *base; +	int irq; +	int ret; + +	ret = of_reserved_mem_device_init(dev); +	if (ret && ret != -ENODEV) { +		dev_err(dev, "Failed to init memory region\n"); +		goto error_early; +	} + +	reserved_mem_node = of_parse_phandle(of_node, "memory-region", 0); +	if (reserved_mem_node) { +		reserved_mem = of_reserved_mem_lookup(reserved_mem_node); +		of_node_put(reserved_mem_node); +	} + +	/* Get regmap from parent if available. */ +	if (of_node->parent) +		regmap = syscon_node_to_regmap(of_node->parent); + +	/* Register our own regmap otherwise. */ +	if (IS_ERR_OR_NULL(regmap)) { +		ret = of_address_to_resource(of_node, 0, &res); +		if (ret) { +			dev_err(dev, "Failed to get resource from address\n"); +			goto error_reserved_mem; +		} + +		base = devm_ioremap_resource(dev, &res); +		if (IS_ERR(base)) { +			dev_err(dev, "Failed to map I/O base\n"); +			ret = PTR_ERR(base); +			goto error_reserved_mem; +		} + +		logicvc_drm_regmap_config.max_register = resource_size(&res) - +							 4; + +		regmap = devm_regmap_init_mmio(dev, base, +					       &logicvc_drm_regmap_config); +		if (IS_ERR(regmap)) { +			dev_err(dev, "Failed to create regmap for I/O\n"); +			ret = PTR_ERR(regmap); +			goto error_reserved_mem; +		} +	} + +	irq = platform_get_irq(pdev, 0); +	if (irq < 0) { +		ret = -ENODEV; +		goto error_reserved_mem; +	} + +	logicvc = devm_drm_dev_alloc(dev, &logicvc_drm_driver, +				     struct logicvc_drm, drm_dev); +	if (IS_ERR(logicvc)) { +		ret = PTR_ERR(logicvc); +		goto error_reserved_mem; +	} + +	platform_set_drvdata(pdev, logicvc); +	drm_dev = &logicvc->drm_dev; + +	logicvc->regmap = regmap; +	INIT_LIST_HEAD(&logicvc->layers_list); + +	caps = logicvc_drm_caps_match(logicvc); +	if (!caps) { +		ret = -EINVAL; +		goto error_reserved_mem; +	} + +	logicvc->caps = caps; + +	if (reserved_mem) +		logicvc->reserved_mem_base = reserved_mem->base; + +	ret = logicvc_clocks_prepare(logicvc); +	if (ret) { +		drm_err(drm_dev, "Failed to prepare clocks\n"); +		goto error_reserved_mem; +	} + +	ret = devm_request_irq(dev, irq, logicvc_drm_irq_handler, 0, +			       dev_name(dev), logicvc); +	if (ret) { +		drm_err(drm_dev, "Failed to request IRQ\n"); +		goto error_clocks; +	} + +	ret = logicvc_drm_config_parse(logicvc); +	if (ret && ret != -ENODEV) { +		drm_err(drm_dev, "Failed to parse config\n"); +		goto error_clocks; +	} + +	ret = drmm_mode_config_init(drm_dev); +	if (ret) { +		drm_err(drm_dev, "Failed to init mode config\n"); +		goto error_clocks; +	} + +	ret = logicvc_layers_init(logicvc); +	if (ret) { +		drm_err(drm_dev, "Failed to initialize layers\n"); +		goto error_clocks; +	} + +	ret = logicvc_crtc_init(logicvc); +	if (ret) { +		drm_err(drm_dev, "Failed to initialize CRTC\n"); +		goto error_clocks; +	} + +	logicvc_layers_attach_crtc(logicvc); + +	ret = logicvc_interface_init(logicvc); +	if (ret) { +		if (ret != -EPROBE_DEFER) +			drm_err(drm_dev, "Failed to initialize interface\n"); + +		goto error_clocks; +	} + +	logicvc_interface_attach_crtc(logicvc); + +	ret = logicvc_mode_init(logicvc); +	if (ret) { +		drm_err(drm_dev, "Failed to initialize KMS\n"); +		goto error_clocks; +	} + +	ret = drm_dev_register(drm_dev, 0); +	if (ret) { +		drm_err(drm_dev, "Failed to register DRM device\n"); +		goto error_mode; +	} + +	drm_fbdev_generic_setup(drm_dev, drm_dev->mode_config.preferred_depth); + +	return 0; + +error_mode: +	logicvc_mode_fini(logicvc); + +error_clocks: +	logicvc_clocks_unprepare(logicvc); + +error_reserved_mem: +	of_reserved_mem_device_release(dev); + +error_early: +	return ret; +} + +static int logicvc_drm_remove(struct platform_device *pdev) +{ +	struct logicvc_drm *logicvc = platform_get_drvdata(pdev); +	struct device *dev = &pdev->dev; +	struct drm_device *drm_dev = &logicvc->drm_dev; + +	drm_dev_unregister(drm_dev); +	drm_atomic_helper_shutdown(drm_dev); + +	logicvc_mode_fini(logicvc); + +	logicvc_clocks_unprepare(logicvc); + +	of_reserved_mem_device_release(dev); + +	return 0; +} + +static const struct of_device_id logicvc_drm_of_table[] = { +	{ .compatible = "xylon,logicvc-3.02.a-display" }, +	{ .compatible = "xylon,logicvc-4.01.a-display" }, +	{}, +}; +MODULE_DEVICE_TABLE(of, logicvc_drm_of_table); + +static struct platform_driver logicvc_drm_platform_driver = { +	.probe		= logicvc_drm_probe, +	.remove		= logicvc_drm_remove, +	.driver		= { +		.name		= "logicvc-drm", +		.of_match_table	= logicvc_drm_of_table, +	}, +}; + +module_platform_driver(logicvc_drm_platform_driver); + +MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>"); +MODULE_DESCRIPTION("Xylon LogiCVC DRM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/logicvc/logicvc_drm.h b/drivers/gpu/drm/logicvc/logicvc_drm.h new file mode 100644 index 000000000000..e0f4787c69f9 --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_drm.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _LOGICVC_DRM_H_ +#define _LOGICVC_DRM_H_ + +#include <linux/regmap.h> +#include <linux/types.h> +#include <drm/drm_device.h> + +#define LOGICVC_DISPLAY_INTERFACE_RGB			0 +#define LOGICVC_DISPLAY_INTERFACE_ITU656		1 +#define LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS		2 +#define LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS_CAMERA	3 +#define LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS		4 +#define LOGICVC_DISPLAY_INTERFACE_DVI			5 + +#define LOGICVC_DISPLAY_COLORSPACE_RGB		0 +#define LOGICVC_DISPLAY_COLORSPACE_YUV422	1 +#define LOGICVC_DISPLAY_COLORSPACE_YUV444	2 + +#define logicvc_drm(d) \ +	container_of(d, struct logicvc_drm, drm_dev) + +struct logicvc_crtc; +struct logicvc_interface; + +struct logicvc_drm_config { +	u32 display_interface; +	u32 display_colorspace; +	u32 display_depth; +	u32 row_stride; +	bool dithering; +	bool background_layer; +	bool layers_configurable; +	u32 layers_count; +}; + +struct logicvc_drm_caps { +	unsigned int major; +	unsigned int minor; +	char level; +	bool layer_address; +}; + +struct logicvc_drm { +	const struct logicvc_drm_caps *caps; +	struct logicvc_drm_config config; + +	struct drm_device drm_dev; +	phys_addr_t reserved_mem_base; +	struct regmap *regmap; + +	struct clk *vclk; +	struct clk *vclk2; +	struct clk *lvdsclk; +	struct clk *lvdsclkn; + +	struct list_head layers_list; +	struct logicvc_crtc *crtc; +	struct logicvc_interface *interface; +}; + +#endif diff --git a/drivers/gpu/drm/logicvc/logicvc_interface.c b/drivers/gpu/drm/logicvc/logicvc_interface.c new file mode 100644 index 000000000000..c73592f6c406 --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_interface.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/types.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_encoder.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "logicvc_crtc.h" +#include "logicvc_drm.h" +#include "logicvc_interface.h" +#include "logicvc_regs.h" + +#define logicvc_interface_from_drm_encoder(c) \ +	container_of(c, struct logicvc_interface, drm_encoder) +#define logicvc_interface_from_drm_connector(c) \ +	container_of(c, struct logicvc_interface, drm_connector) + +static void logicvc_encoder_enable(struct drm_encoder *drm_encoder) +{ +	struct logicvc_drm *logicvc = logicvc_drm(drm_encoder->dev); +	struct logicvc_interface *interface = +		logicvc_interface_from_drm_encoder(drm_encoder); + +	regmap_update_bits(logicvc->regmap, LOGICVC_POWER_CTRL_REG, +			   LOGICVC_POWER_CTRL_VIDEO_ENABLE, +			   LOGICVC_POWER_CTRL_VIDEO_ENABLE); + +	if (interface->drm_panel) { +		drm_panel_prepare(interface->drm_panel); +		drm_panel_enable(interface->drm_panel); +	} +} + +static void logicvc_encoder_disable(struct drm_encoder *drm_encoder) +{ +	struct logicvc_interface *interface = +		logicvc_interface_from_drm_encoder(drm_encoder); + +	if (interface->drm_panel) { +		drm_panel_disable(interface->drm_panel); +		drm_panel_unprepare(interface->drm_panel); +	} +} + +static const struct drm_encoder_helper_funcs logicvc_encoder_helper_funcs = { +	.enable			= logicvc_encoder_enable, +	.disable		= logicvc_encoder_disable, +}; + +static const struct drm_encoder_funcs logicvc_encoder_funcs = { +	.destroy		= drm_encoder_cleanup, +}; + +static int logicvc_connector_get_modes(struct drm_connector *drm_connector) +{ +	struct logicvc_interface *interface = +		logicvc_interface_from_drm_connector(drm_connector); + +	if (interface->drm_panel) +		return drm_panel_get_modes(interface->drm_panel, drm_connector); + +	WARN_ONCE(1, "Retrieving modes from a native connector is not implemented."); + +	return 0; +} + +static const struct drm_connector_helper_funcs logicvc_connector_helper_funcs = { +	.get_modes		= logicvc_connector_get_modes, +}; + +static const struct drm_connector_funcs logicvc_connector_funcs = { +	.reset			= drm_atomic_helper_connector_reset, +	.fill_modes		= drm_helper_probe_single_connector_modes, +	.destroy		= drm_connector_cleanup, +	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state, +	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state, +}; + +static int logicvc_interface_encoder_type(struct logicvc_drm *logicvc) +{ +	switch (logicvc->config.display_interface) { +	case LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS: +	case LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS_CAMERA: +	case LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS: +		return DRM_MODE_ENCODER_LVDS; +	case LOGICVC_DISPLAY_INTERFACE_DVI: +		return DRM_MODE_ENCODER_TMDS; +	case LOGICVC_DISPLAY_INTERFACE_RGB: +		return DRM_MODE_ENCODER_DPI; +	default: +		return DRM_MODE_ENCODER_NONE; +	} +} + +static int logicvc_interface_connector_type(struct logicvc_drm *logicvc) +{ +	switch (logicvc->config.display_interface) { +	case LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS: +	case LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS_CAMERA: +	case LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS: +		return DRM_MODE_CONNECTOR_LVDS; +	case LOGICVC_DISPLAY_INTERFACE_DVI: +		return DRM_MODE_CONNECTOR_DVID; +	case LOGICVC_DISPLAY_INTERFACE_RGB: +		return DRM_MODE_CONNECTOR_DPI; +	default: +		return DRM_MODE_CONNECTOR_Unknown; +	} +} + +static bool logicvc_interface_native_connector(struct logicvc_drm *logicvc) +{ +	switch (logicvc->config.display_interface) { +	case LOGICVC_DISPLAY_INTERFACE_DVI: +		return true; +	default: +		return false; +	} +} + +void logicvc_interface_attach_crtc(struct logicvc_drm *logicvc) +{ +	uint32_t possible_crtcs = drm_crtc_mask(&logicvc->crtc->drm_crtc); + +	logicvc->interface->drm_encoder.possible_crtcs = possible_crtcs; +} + +int logicvc_interface_init(struct logicvc_drm *logicvc) +{ +	struct logicvc_interface *interface; +	struct drm_device *drm_dev = &logicvc->drm_dev; +	struct device *dev = drm_dev->dev; +	struct device_node *of_node = dev->of_node; +	int encoder_type = logicvc_interface_encoder_type(logicvc); +	int connector_type = logicvc_interface_connector_type(logicvc); +	bool native_connector = logicvc_interface_native_connector(logicvc); +	int ret; + +	interface = devm_kzalloc(dev, sizeof(*interface), GFP_KERNEL); +	if (!interface) { +		ret = -ENOMEM; +		goto error_early; +	} + +	ret = drm_of_find_panel_or_bridge(of_node, 0, 0, &interface->drm_panel, +					  &interface->drm_bridge); +	if (ret == -EPROBE_DEFER) +		goto error_early; + +	ret = drm_encoder_init(drm_dev, &interface->drm_encoder, +			       &logicvc_encoder_funcs, encoder_type, NULL); +	if (ret) { +		drm_err(drm_dev, "Failed to initialize encoder\n"); +		goto error_early; +	} + +	drm_encoder_helper_add(&interface->drm_encoder, +			       &logicvc_encoder_helper_funcs); + +	if (native_connector || interface->drm_panel) { +		ret = drm_connector_init(drm_dev, &interface->drm_connector, +					 &logicvc_connector_funcs, +					 connector_type); +		if (ret) { +			drm_err(drm_dev, "Failed to initialize connector\n"); +			goto error_encoder; +		} + +		drm_connector_helper_add(&interface->drm_connector, +					 &logicvc_connector_helper_funcs); + +		ret = drm_connector_attach_encoder(&interface->drm_connector, +						   &interface->drm_encoder); +		if (ret) { +			drm_err(drm_dev, +				"Failed to attach connector to encoder\n"); +			goto error_encoder; +		} +	} + +	if (interface->drm_bridge) { +		ret = drm_bridge_attach(&interface->drm_encoder, +					interface->drm_bridge, NULL, 0); +		if (ret) { +			drm_err(drm_dev, +				"Failed to attach bridge to encoder\n"); +			goto error_encoder; +		} +	} + +	logicvc->interface = interface; + +	return 0; + +error_encoder: +	drm_encoder_cleanup(&interface->drm_encoder); + +error_early: +	return ret; +} diff --git a/drivers/gpu/drm/logicvc/logicvc_interface.h b/drivers/gpu/drm/logicvc/logicvc_interface.h new file mode 100644 index 000000000000..fd709fad54f9 --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_interface.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _LOGICVC_INTERFACE_H_ +#define _LOGICVC_INTERFACE_H_ + +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_encoder.h> +#include <drm/drm_panel.h> + +struct logicvc_drm; + +struct logicvc_interface { +	struct drm_encoder drm_encoder; +	struct drm_connector drm_connector; + +	struct drm_panel *drm_panel; +	struct drm_bridge *drm_bridge; +}; + +void logicvc_interface_attach_crtc(struct logicvc_drm *logicvc); +int logicvc_interface_init(struct logicvc_drm *logicvc); + +#endif diff --git a/drivers/gpu/drm/logicvc/logicvc_layer.c b/drivers/gpu/drm/logicvc/logicvc_layer.c new file mode 100644 index 000000000000..441e3cfce4cf --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_layer.c @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/of.h> +#include <linux/types.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_blend.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_plane.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_print.h> + +#include "logicvc_crtc.h" +#include "logicvc_drm.h" +#include "logicvc_layer.h" +#include "logicvc_of.h" +#include "logicvc_regs.h" + +#define logicvc_layer(p) \ +	container_of(p, struct logicvc_layer, drm_plane) + +static uint32_t logicvc_layer_formats_rgb16[] = { +	DRM_FORMAT_RGB565, +	DRM_FORMAT_BGR565, +	DRM_FORMAT_INVALID, +}; + +static uint32_t logicvc_layer_formats_rgb24[] = { +	DRM_FORMAT_XRGB8888, +	DRM_FORMAT_XBGR8888, +	DRM_FORMAT_INVALID, +}; + +/* + * What we call depth in this driver only counts color components, not alpha. + * This allows us to stay compatible with the LogiCVC bistream definitions. + */ +static uint32_t logicvc_layer_formats_rgb24_alpha[] = { +	DRM_FORMAT_ARGB8888, +	DRM_FORMAT_ABGR8888, +	DRM_FORMAT_INVALID, +}; + +static struct logicvc_layer_formats logicvc_layer_formats[] = { +	{ +		.colorspace	= LOGICVC_LAYER_COLORSPACE_RGB, +		.depth		= 16, +		.formats	= logicvc_layer_formats_rgb16, +	}, +	{ +		.colorspace	= LOGICVC_LAYER_COLORSPACE_RGB, +		.depth		= 24, +		.formats	= logicvc_layer_formats_rgb24, +	}, +	{ +		.colorspace	= LOGICVC_LAYER_COLORSPACE_RGB, +		.depth		= 24, +		.alpha		= true, +		.formats	= logicvc_layer_formats_rgb24_alpha, +	}, +	{ } +}; + +static bool logicvc_layer_format_inverted(uint32_t format) +{ +	switch (format) { +	case DRM_FORMAT_BGR565: +	case DRM_FORMAT_BGR888: +	case DRM_FORMAT_XBGR8888: +	case DRM_FORMAT_ABGR8888: +		return true; +	default: +		return false; +	} +} + +static int logicvc_plane_atomic_check(struct drm_plane *drm_plane, +				      struct drm_atomic_state *state) +{ +	struct drm_device *drm_dev = drm_plane->dev; +	struct logicvc_layer *layer = logicvc_layer(drm_plane); +	struct logicvc_drm *logicvc = logicvc_drm(drm_dev); +	struct drm_plane_state *new_state = +		drm_atomic_get_new_plane_state(state, drm_plane); +	struct drm_crtc_state *crtc_state; +	int min_scale, max_scale; +	bool can_position; +	int ret; + +	if (!new_state->crtc) +		return 0; + +	crtc_state = drm_atomic_get_existing_crtc_state(new_state->state, +							new_state->crtc); +	if (WARN_ON(!crtc_state)) +		return -EINVAL; + +	if (new_state->crtc_x < 0 || new_state->crtc_y < 0) { +		drm_err(drm_dev, +			"Negative on-CRTC positions are not supported.\n"); +		return -EINVAL; +	} + +	if (!logicvc->caps->layer_address) { +		ret = logicvc_layer_buffer_find_setup(logicvc, layer, new_state, +						      NULL); +		if (ret) { +			drm_err(drm_dev, "No viable setup for buffer found.\n"); +			return ret; +		} +	} + +	min_scale = DRM_PLANE_HELPER_NO_SCALING; +	max_scale = DRM_PLANE_HELPER_NO_SCALING; + +	can_position = (drm_plane->type == DRM_PLANE_TYPE_OVERLAY && +			layer->index != (logicvc->config.layers_count - 1) && +			logicvc->config.layers_configurable); + +	ret = drm_atomic_helper_check_plane_state(new_state, crtc_state, +						  min_scale, max_scale, +						  can_position, true); +	if (ret) { +		drm_err(drm_dev, "Invalid plane state\n\n"); +		return ret; +	} + +	return 0; +} + +static void logicvc_plane_atomic_update(struct drm_plane *drm_plane, +					struct drm_atomic_state *state) +{ +	struct logicvc_layer *layer = logicvc_layer(drm_plane); +	struct logicvc_drm *logicvc = logicvc_drm(drm_plane->dev); +	struct drm_device *drm_dev = &logicvc->drm_dev; +	struct drm_plane_state *new_state = +		drm_atomic_get_new_plane_state(state, drm_plane); +	struct drm_crtc *drm_crtc = &logicvc->crtc->drm_crtc; +	struct drm_display_mode *mode = &drm_crtc->state->adjusted_mode; +	struct drm_framebuffer *fb = new_state->fb; +	struct logicvc_layer_buffer_setup setup = {}; +	u32 index = layer->index; +	u32 reg; + +	/* Layer dimensions */ + +	regmap_write(logicvc->regmap, LOGICVC_LAYER_WIDTH_REG(index), +		     new_state->crtc_w - 1); +	regmap_write(logicvc->regmap, LOGICVC_LAYER_HEIGHT_REG(index), +		     new_state->crtc_h - 1); + +	if (logicvc->caps->layer_address) { +		phys_addr_t fb_addr = drm_fb_cma_get_gem_addr(fb, new_state, 0); + +		regmap_write(logicvc->regmap, LOGICVC_LAYER_ADDRESS_REG(index), +			     fb_addr); +	} else { +		/* Rely on offsets to configure the address. */ + +		logicvc_layer_buffer_find_setup(logicvc, layer, new_state, +						&setup); + +		/* Layer memory offsets */ + +		regmap_write(logicvc->regmap, LOGICVC_BUFFER_SEL_REG, +			     LOGICVC_BUFFER_SEL_VALUE(index, setup.buffer_sel)); +		regmap_write(logicvc->regmap, LOGICVC_LAYER_HOFFSET_REG(index), +			     setup.hoffset); +		regmap_write(logicvc->regmap, LOGICVC_LAYER_VOFFSET_REG(index), +			     setup.voffset); +	} + +	/* Layer position */ + +	regmap_write(logicvc->regmap, LOGICVC_LAYER_HPOSITION_REG(index), +		     mode->hdisplay - 1 - new_state->crtc_x); + +	/* Vertical position must be set last to sync layer register changes. */ +	regmap_write(logicvc->regmap, LOGICVC_LAYER_VPOSITION_REG(index), +		     mode->vdisplay - 1 - new_state->crtc_y); + +	/* Layer alpha */ + +	if (layer->config.alpha_mode == LOGICVC_LAYER_ALPHA_LAYER) { +		u32 alpha_bits; +		u32 alpha_max; +		u32 alpha; + +		switch (layer->config.depth) { +		case 8: +			alpha_bits = 3; +			break; +		case 16: +			if (layer->config.colorspace == +			    LOGICVC_LAYER_COLORSPACE_YUV) +				alpha_bits = 8; +			else +				alpha_bits = 6; +			break; +		default: +			alpha_bits = 8; +			break; +		} + +		alpha_max = BIT(alpha_bits) - 1; +		alpha = new_state->alpha * alpha_max / DRM_BLEND_ALPHA_OPAQUE; + +		drm_dbg_kms(drm_dev, "Setting layer %d alpha to %d/%d\n", index, +			    alpha, alpha_max); + +		regmap_write(logicvc->regmap, LOGICVC_LAYER_ALPHA_REG(index), +			     alpha); +	} + +	/* Layer control */ + +	reg = LOGICVC_LAYER_CTRL_ENABLE; + +	if (logicvc_layer_format_inverted(fb->format->format)) +		reg |= LOGICVC_LAYER_CTRL_PIXEL_FORMAT_INVERT; + +	reg |= LOGICVC_LAYER_CTRL_COLOR_KEY_DISABLE; + +	regmap_write(logicvc->regmap, LOGICVC_LAYER_CTRL_REG(index), reg); +} + +static void logicvc_plane_atomic_disable(struct drm_plane *drm_plane, +					 struct drm_atomic_state *state) +{ +	struct logicvc_layer *layer = logicvc_layer(drm_plane); +	struct logicvc_drm *logicvc = logicvc_drm(drm_plane->dev); +	u32 index = layer->index; + +	regmap_write(logicvc->regmap, LOGICVC_LAYER_CTRL_REG(index), 0); +} + +static struct drm_plane_helper_funcs logicvc_plane_helper_funcs = { +	.atomic_check		= logicvc_plane_atomic_check, +	.atomic_update		= logicvc_plane_atomic_update, +	.atomic_disable		= logicvc_plane_atomic_disable, +}; + +static const struct drm_plane_funcs logicvc_plane_funcs = { +	.update_plane		= drm_atomic_helper_update_plane, +	.disable_plane		= drm_atomic_helper_disable_plane, +	.destroy		= drm_plane_cleanup, +	.reset			= drm_atomic_helper_plane_reset, +	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state, +	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state, +}; + +int logicvc_layer_buffer_find_setup(struct logicvc_drm *logicvc, +				    struct logicvc_layer *layer, +				    struct drm_plane_state *state, +				    struct logicvc_layer_buffer_setup *setup) +{ +	struct drm_device *drm_dev = &logicvc->drm_dev; +	struct drm_framebuffer *fb = state->fb; +	/* All the supported formats have a single data plane. */ +	u32 layer_bytespp = fb->format->cpp[0]; +	u32 layer_stride = layer_bytespp * logicvc->config.row_stride; +	u32 base_offset = layer->config.base_offset * layer_stride; +	u32 buffer_offset = layer->config.buffer_offset * layer_stride; +	u8 buffer_sel = 0; +	u16 voffset = 0; +	u16 hoffset = 0; +	phys_addr_t fb_addr; +	u32 fb_offset; +	u32 gap; + +	if (!logicvc->reserved_mem_base) { +		drm_err(drm_dev, "No reserved memory base was registered!\n"); +		return -ENOMEM; +	} + +	fb_addr = drm_fb_cma_get_gem_addr(fb, state, 0); +	if (fb_addr < logicvc->reserved_mem_base) { +		drm_err(drm_dev, +			"Framebuffer memory below reserved memory base!\n"); +		return -EINVAL; +	} + +	fb_offset = (u32) (fb_addr - logicvc->reserved_mem_base); + +	if (fb_offset < base_offset) { +		drm_err(drm_dev, +			"Framebuffer offset below layer base offset!\n"); +		return -EINVAL; +	} + +	gap = fb_offset - base_offset; + +	/* Use the possible video buffers selection. */ +	if (gap && buffer_offset) { +		buffer_sel = gap / buffer_offset; +		if (buffer_sel > LOGICVC_BUFFER_SEL_MAX) +			buffer_sel = LOGICVC_BUFFER_SEL_MAX; + +		gap -= buffer_sel * buffer_offset; +	} + +	/* Use the vertical offset. */ +	if (gap && layer_stride && logicvc->config.layers_configurable) { +		voffset = gap / layer_stride; +		if (voffset > LOGICVC_LAYER_VOFFSET_MAX) +			voffset = LOGICVC_LAYER_VOFFSET_MAX; + +		gap -= voffset * layer_stride; +	} + +	/* Use the horizontal offset. */ +	if (gap && layer_bytespp && logicvc->config.layers_configurable) { +		hoffset = gap / layer_bytespp; +		if (hoffset > LOGICVC_DIMENSIONS_MAX) +			hoffset = LOGICVC_DIMENSIONS_MAX; + +		gap -= hoffset * layer_bytespp; +	} + +	if (gap) { +		drm_err(drm_dev, +			"Unable to find layer %d buffer setup for 0x%x byte gap\n", +			layer->index, fb_offset - base_offset); +		return -EINVAL; +	} + +	drm_dbg_kms(drm_dev, "Found layer %d buffer setup for 0x%x byte gap:\n", +		    layer->index, fb_offset - base_offset); + +	drm_dbg_kms(drm_dev, "- buffer_sel = 0x%x chunks of 0x%x bytes\n", +		    buffer_sel, buffer_offset); +	drm_dbg_kms(drm_dev, "- voffset = 0x%x chunks of 0x%x bytes\n", voffset, +		    layer_stride); +	drm_dbg_kms(drm_dev, "- hoffset = 0x%x chunks of 0x%x bytes\n", hoffset, +		    layer_bytespp); + +	if (setup) { +		setup->buffer_sel = buffer_sel; +		setup->voffset = voffset; +		setup->hoffset = hoffset; +	} + +	return 0; +} + +static struct logicvc_layer_formats *logicvc_layer_formats_lookup(struct logicvc_layer *layer) +{ +	bool alpha; +	unsigned int i = 0; + +	alpha = (layer->config.alpha_mode == LOGICVC_LAYER_ALPHA_PIXEL); + +	while (logicvc_layer_formats[i].formats) { +		if (logicvc_layer_formats[i].colorspace == layer->config.colorspace && +		    logicvc_layer_formats[i].depth == layer->config.depth && +		    logicvc_layer_formats[i].alpha == alpha) +			return &logicvc_layer_formats[i]; + +		i++; +	} + +	return NULL; +} + +static unsigned int logicvc_layer_formats_count(struct logicvc_layer_formats *formats) +{ +	unsigned int count = 0; + +	while (formats->formats[count] != DRM_FORMAT_INVALID) +		count++; + +	return count; +} + +static int logicvc_layer_config_parse(struct logicvc_drm *logicvc, +				      struct logicvc_layer *layer) +{ +	struct device_node *of_node = layer->of_node; +	struct logicvc_layer_config *config = &layer->config; +	int ret; + +	logicvc_of_property_parse_bool(of_node, +				       LOGICVC_OF_PROPERTY_LAYER_PRIMARY, +				       &config->primary); + +	ret = logicvc_of_property_parse_u32(of_node, +					    LOGICVC_OF_PROPERTY_LAYER_COLORSPACE, +					    &config->colorspace); +	if (ret) +		return ret; + +	ret = logicvc_of_property_parse_u32(of_node, +					    LOGICVC_OF_PROPERTY_LAYER_DEPTH, +					    &config->depth); +	if (ret) +		return ret; + +	ret = logicvc_of_property_parse_u32(of_node, +					    LOGICVC_OF_PROPERTY_LAYER_ALPHA_MODE, +					    &config->alpha_mode); +	if (ret) +		return ret; + +	/* +	 * Memory offset is only relevant without layer address configuration. +	 */ +	if (logicvc->caps->layer_address) +		return 0; + +	ret = logicvc_of_property_parse_u32(of_node, +					    LOGICVC_OF_PROPERTY_LAYER_BASE_OFFSET, +					    &config->base_offset); +	if (ret) +		return ret; + +	ret = logicvc_of_property_parse_u32(of_node, +					    LOGICVC_OF_PROPERTY_LAYER_BUFFER_OFFSET, +					    &config->buffer_offset); +	if (ret) +		return ret; + +	return 0; +} + +struct logicvc_layer *logicvc_layer_get_from_index(struct logicvc_drm *logicvc, +						   u32 index) +{ +	struct logicvc_layer *layer; + +	list_for_each_entry(layer, &logicvc->layers_list, list) +		if (layer->index == index) +			return layer; + +	return NULL; +} + +struct logicvc_layer *logicvc_layer_get_from_type(struct logicvc_drm *logicvc, +						  enum drm_plane_type type) +{ +	struct logicvc_layer *layer; + +	list_for_each_entry(layer, &logicvc->layers_list, list) +		if (layer->drm_plane.type == type) +			return layer; + +	return NULL; +} + +struct logicvc_layer *logicvc_layer_get_primary(struct logicvc_drm *logicvc) +{ +	return logicvc_layer_get_from_type(logicvc, DRM_PLANE_TYPE_PRIMARY); +} + +static int logicvc_layer_init(struct logicvc_drm *logicvc, +			      struct device_node *of_node, u32 index) +{ +	struct drm_device *drm_dev = &logicvc->drm_dev; +	struct device *dev = drm_dev->dev; +	struct logicvc_layer *layer = NULL; +	struct logicvc_layer_formats *formats; +	unsigned int formats_count; +	enum drm_plane_type type; +	unsigned int zpos; +	int ret; + +	layer = devm_kzalloc(dev, sizeof(*layer), GFP_KERNEL); +	if (!layer) { +		ret = -ENOMEM; +		goto error; +	} + +	layer->of_node = of_node; +	layer->index = index; + +	ret = logicvc_layer_config_parse(logicvc, layer); +	if (ret) { +		drm_err(drm_dev, "Failed to parse config for layer #%d\n", +			index); +		goto error; +	} + +	formats = logicvc_layer_formats_lookup(layer); +	if (!formats) { +		drm_err(drm_dev, "Failed to lookup formats for layer #%d\n", +			index); +		ret = -EINVAL; +		goto error; +	} + +	formats_count = logicvc_layer_formats_count(formats); + +	/* The final layer can be configured as a background layer. */ +	if (logicvc->config.background_layer && +	    index == (logicvc->config.layers_count - 1)) { +		/* +		 * A zero value for black is only valid for RGB, not for YUV, +		 * so this will need to take the format in account for YUV. +		 */ +		u32 background = 0; + +		drm_dbg_kms(drm_dev, "Using layer #%d as background layer\n", +			    index); + +		regmap_write(logicvc->regmap, LOGICVC_BACKGROUND_COLOR_REG, +			     background); + +		devm_kfree(dev, layer); + +		return 0; +	} + +	if (layer->config.primary) +		type = DRM_PLANE_TYPE_PRIMARY; +	else +		type = DRM_PLANE_TYPE_OVERLAY; + +	ret = drm_universal_plane_init(drm_dev, &layer->drm_plane, 0, +				       &logicvc_plane_funcs, formats->formats, +				       formats_count, NULL, type, NULL); +	if (ret) { +		drm_err(drm_dev, "Failed to initialize layer plane\n"); +		return ret; +	} + +	drm_plane_helper_add(&layer->drm_plane, &logicvc_plane_helper_funcs); + +	zpos = logicvc->config.layers_count - index - 1; +	drm_dbg_kms(drm_dev, "Giving layer #%d zpos %d\n", index, zpos); + +	if (layer->config.alpha_mode == LOGICVC_LAYER_ALPHA_LAYER) +		drm_plane_create_alpha_property(&layer->drm_plane); + +	drm_plane_create_zpos_immutable_property(&layer->drm_plane, zpos); + +	drm_dbg_kms(drm_dev, "Registering layer #%d\n", index); + +	layer->formats = formats; + +	list_add_tail(&layer->list, &logicvc->layers_list); + +	return 0; + +error: +	if (layer) +		devm_kfree(dev, layer); + +	return ret; +} + +static void logicvc_layer_fini(struct logicvc_drm *logicvc, +			       struct logicvc_layer *layer) +{ +	struct device *dev = logicvc->drm_dev.dev; + +	list_del(&layer->list); +	devm_kfree(dev, layer); +} + +void logicvc_layers_attach_crtc(struct logicvc_drm *logicvc) +{ +	uint32_t possible_crtcs = drm_crtc_mask(&logicvc->crtc->drm_crtc); +	struct logicvc_layer *layer; + +	list_for_each_entry(layer, &logicvc->layers_list, list) { +		if (layer->drm_plane.type != DRM_PLANE_TYPE_OVERLAY) +			continue; + +		layer->drm_plane.possible_crtcs = possible_crtcs; +	} +} + +int logicvc_layers_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 device_node *layer_node = NULL; +	struct device_node *layers_node; +	struct logicvc_layer *layer; +	struct logicvc_layer *next; +	int ret = 0; + +	layers_node = of_get_child_by_name(of_node, "layers"); +	if (!layers_node) { +		drm_err(drm_dev, "No layers node found in the description\n"); +		ret = -ENODEV; +		goto error; +	} + +	for_each_child_of_node(layers_node, layer_node) { +		u32 index = 0; + +		if (!logicvc_of_node_is_layer(layer_node)) +			continue; + +		ret = of_property_read_u32(layer_node, "reg", &index); +		if (ret) +			continue; + +		layer = logicvc_layer_get_from_index(logicvc, index); +		if (layer) { +			drm_err(drm_dev, "Duplicated entry for layer #%d\n", +				index); +			continue; +		} + +		ret = logicvc_layer_init(logicvc, layer_node, index); +		if (ret) { +			of_node_put(layers_node); +			goto error; +		} +	} + +	of_node_put(layers_node); + +	return 0; + +error: +	list_for_each_entry_safe(layer, next, &logicvc->layers_list, list) +		logicvc_layer_fini(logicvc, layer); + +	return ret; +} diff --git a/drivers/gpu/drm/logicvc/logicvc_layer.h b/drivers/gpu/drm/logicvc/logicvc_layer.h new file mode 100644 index 000000000000..4a4b02e9b819 --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_layer.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _LOGICVC_LAYER_H_ +#define _LOGICVC_LAYER_H_ + +#include <linux/of.h> +#include <linux/types.h> +#include <drm/drm_plane.h> + +#define LOGICVC_LAYER_COLORSPACE_RGB		0 +#define LOGICVC_LAYER_COLORSPACE_YUV		1 + +#define LOGICVC_LAYER_ALPHA_LAYER		0 +#define LOGICVC_LAYER_ALPHA_PIXEL		1 + +struct logicvc_layer_buffer_setup { +	u8 buffer_sel; +	u16 voffset; +	u16 hoffset; +}; + +struct logicvc_layer_config { +	u32 colorspace; +	u32 depth; +	u32 alpha_mode; +	u32 base_offset; +	u32 buffer_offset; +	bool primary; +}; + +struct logicvc_layer_formats { +	u32 colorspace; +	u32 depth; +	bool alpha; +	uint32_t *formats; +}; + +struct logicvc_layer { +	struct logicvc_layer_config config; +	struct logicvc_layer_formats *formats; +	struct device_node *of_node; + +	struct drm_plane drm_plane; +	struct list_head list; +	u32 index; +}; + +int logicvc_layer_buffer_find_setup(struct logicvc_drm *logicvc, +				    struct logicvc_layer *layer, +				    struct drm_plane_state *state, +				    struct logicvc_layer_buffer_setup *setup); +struct logicvc_layer *logicvc_layer_get_from_index(struct logicvc_drm *logicvc, +						   u32 index); +struct logicvc_layer *logicvc_layer_get_from_type(struct logicvc_drm *logicvc, +						  enum drm_plane_type type); +struct logicvc_layer *logicvc_layer_get_primary(struct logicvc_drm *logicvc); +void logicvc_layers_attach_crtc(struct logicvc_drm *logicvc); +int logicvc_layers_init(struct logicvc_drm *logicvc); + +#endif diff --git a/drivers/gpu/drm/logicvc/logicvc_mode.c b/drivers/gpu/drm/logicvc/logicvc_mode.c new file mode 100644 index 000000000000..11940704f644 --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_mode.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/types.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_mode_config.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "logicvc_drm.h" +#include "logicvc_interface.h" +#include "logicvc_layer.h" +#include "logicvc_mode.h" + +static const struct drm_mode_config_funcs logicvc_mode_config_funcs = { +	.fb_create		= drm_gem_fb_create, +	.output_poll_changed	= drm_fb_helper_output_poll_changed, +	.atomic_check		= drm_atomic_helper_check, +	.atomic_commit		= drm_atomic_helper_commit, +}; + +int logicvc_mode_init(struct logicvc_drm *logicvc) +{ +	struct drm_device *drm_dev = &logicvc->drm_dev; +	struct drm_mode_config *mode_config = &drm_dev->mode_config; +	struct logicvc_layer *layer_primary; +	uint32_t preferred_depth; +	int ret; + +	ret = drm_vblank_init(drm_dev, mode_config->num_crtc); +	if (ret) { +		drm_err(drm_dev, "Failed to initialize vblank\n"); +		return ret; +	} + +	layer_primary = logicvc_layer_get_primary(logicvc); +	if (!layer_primary) { +		drm_err(drm_dev, "Failed to get primary layer\n"); +		return -EINVAL; +	} + +	preferred_depth = layer_primary->formats->depth; + +	/* DRM counts alpha in depth, our driver doesn't. */ +	if (layer_primary->formats->alpha) +		preferred_depth += 8; + +	mode_config->min_width = 64; +	mode_config->max_width = 2048; +	mode_config->min_height = 1; +	mode_config->max_height = 2048; +	mode_config->preferred_depth = preferred_depth; +	mode_config->funcs = &logicvc_mode_config_funcs; + +	drm_mode_config_reset(drm_dev); + +	drm_kms_helper_poll_init(drm_dev); + +	return 0; +} + +void logicvc_mode_fini(struct logicvc_drm *logicvc) +{ +	struct drm_device *drm_dev = &logicvc->drm_dev; + +	drm_kms_helper_poll_fini(drm_dev); +} diff --git a/drivers/gpu/drm/logicvc/logicvc_mode.h b/drivers/gpu/drm/logicvc/logicvc_mode.h new file mode 100644 index 000000000000..fee538ab1b96 --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_mode.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _LOGICVC_MODE_H_ +#define _LOGICVC_MODE_H_ + +struct logicvc_drm; + +int logicvc_mode_init(struct logicvc_drm *logicvc); +void logicvc_mode_fini(struct logicvc_drm *logicvc); + +#endif diff --git a/drivers/gpu/drm/logicvc/logicvc_of.c b/drivers/gpu/drm/logicvc/logicvc_of.c new file mode 100644 index 000000000000..e0687730e039 --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_of.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <drm/drm_print.h> + +#include "logicvc_drm.h" +#include "logicvc_layer.h" +#include "logicvc_of.h" + +static struct logicvc_of_property_sv logicvc_of_display_interface_sv[] = { +	{ "lvds-4bits",	LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS }, +	{ "lvds-3bits",	LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS }, +	{ }, +}; + +static struct logicvc_of_property_sv logicvc_of_display_colorspace_sv[] = { +	{ "rgb",	LOGICVC_DISPLAY_COLORSPACE_RGB }, +	{ "yuv422",	LOGICVC_DISPLAY_COLORSPACE_YUV422 }, +	{ "yuv444",	LOGICVC_DISPLAY_COLORSPACE_YUV444 }, +	{ }, +}; + +static struct logicvc_of_property_sv logicvc_of_layer_colorspace_sv[] = { +	{ "rgb",	LOGICVC_LAYER_COLORSPACE_RGB }, +	{ "yuv",	LOGICVC_LAYER_COLORSPACE_YUV }, +	{ }, +}; + +static struct logicvc_of_property_sv logicvc_of_layer_alpha_mode_sv[] = { +	{ "layer",	LOGICVC_LAYER_ALPHA_LAYER }, +	{ "pixel",	LOGICVC_LAYER_ALPHA_PIXEL }, +	{ }, +}; + +static struct logicvc_of_property logicvc_of_properties[] = { +	[LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE] = { +		.name		= "xylon,display-interface", +		.sv		= logicvc_of_display_interface_sv, +		.range		= { +			LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS, +			LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS, +		}, +	}, +	[LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE] = { +		.name		= "xylon,display-colorspace", +		.sv		= logicvc_of_display_colorspace_sv, +		.range		= { +			LOGICVC_DISPLAY_COLORSPACE_RGB, +			LOGICVC_DISPLAY_COLORSPACE_YUV444, +		}, +	}, +	[LOGICVC_OF_PROPERTY_DISPLAY_DEPTH] = { +		.name		= "xylon,display-depth", +		.range		= { 8, 24 }, +	}, +	[LOGICVC_OF_PROPERTY_ROW_STRIDE] = { +		.name		= "xylon,row-stride", +	}, +	[LOGICVC_OF_PROPERTY_DITHERING] = { +		.name		= "xylon,dithering", +		.optional	= true, +	}, +	[LOGICVC_OF_PROPERTY_BACKGROUND_LAYER] = { +		.name		= "xylon,background-layer", +		.optional	= true, +	}, +	[LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE] = { +		.name		= "xylon,layers-configurable", +		.optional	= true, +	}, +	[LOGICVC_OF_PROPERTY_LAYERS_COUNT] = { +		.name		= "xylon,layers-count", +	}, +	[LOGICVC_OF_PROPERTY_LAYER_DEPTH] = { +		.name		= "xylon,layer-depth", +		.range		= { 8, 24 }, +	}, +	[LOGICVC_OF_PROPERTY_LAYER_COLORSPACE] = { +		.name		= "xylon,layer-colorspace", +		.sv		= logicvc_of_layer_colorspace_sv, +		.range		= { +			LOGICVC_LAYER_COLORSPACE_RGB, +			LOGICVC_LAYER_COLORSPACE_RGB, +		}, +	}, +	[LOGICVC_OF_PROPERTY_LAYER_ALPHA_MODE] = { +		.name		= "xylon,layer-alpha-mode", +		.sv		= logicvc_of_layer_alpha_mode_sv, +		.range		= { +			LOGICVC_LAYER_ALPHA_LAYER, +			LOGICVC_LAYER_ALPHA_PIXEL, +		}, +	}, +	[LOGICVC_OF_PROPERTY_LAYER_BASE_OFFSET] = { +		.name		= "xylon,layer-base-offset", +	}, +	[LOGICVC_OF_PROPERTY_LAYER_BUFFER_OFFSET] = { +		.name		= "xylon,layer-buffer-offset", +	}, +	[LOGICVC_OF_PROPERTY_LAYER_PRIMARY] = { +		.name		= "xylon,layer-primary", +		.optional	= true, +	}, +}; + +static int logicvc_of_property_sv_value(struct logicvc_of_property_sv *sv, +					const char *string, u32 *value) +{ +	unsigned int i = 0; + +	while (sv[i].string) { +		if (!strcmp(sv[i].string, string)) { +			*value = sv[i].value; +			return 0; +		} + +		i++; +	} + +	return -EINVAL; +} + +int logicvc_of_property_parse_u32(struct device_node *of_node, +				  unsigned int index, u32 *target) +{ +	struct logicvc_of_property *property; +	const char *string; +	u32 value; +	int ret; + +	if (index >= LOGICVC_OF_PROPERTY_MAXIMUM) +		return -EINVAL; + +	property = &logicvc_of_properties[index]; + +	if (!property->optional && +	    !of_property_read_bool(of_node, property->name)) +		return -ENODEV; + +	if (property->sv) { +		ret = of_property_read_string(of_node, property->name, &string); +		if (ret) +			return ret; + +		ret = logicvc_of_property_sv_value(property->sv, string, +						   &value); +		if (ret) +			return ret; +	} else { +		ret = of_property_read_u32(of_node, property->name, &value); +		if (ret) +			return ret; +	} + +	if (property->range[0] || property->range[1]) +		if (value < property->range[0] || value > property->range[1]) +			return -ERANGE; + +	*target = value; + +	return 0; +} + +void logicvc_of_property_parse_bool(struct device_node *of_node, +				    unsigned int index, bool *target) +{ +	struct logicvc_of_property *property; + +	if (index >= LOGICVC_OF_PROPERTY_MAXIMUM) { +		/* Fallback. */ +		*target = false; +		return; +	} + +	property = &logicvc_of_properties[index]; +	*target = of_property_read_bool(of_node, property->name); +} + +bool logicvc_of_node_is_layer(struct device_node *of_node) +{ +	return !of_node_cmp(of_node->name, "layer"); +} diff --git a/drivers/gpu/drm/logicvc/logicvc_of.h b/drivers/gpu/drm/logicvc/logicvc_of.h new file mode 100644 index 000000000000..46036e461be9 --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_of.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _LOGICVC_OF_H_ +#define _LOGICVC_OF_H_ + +enum logicvc_of_property_index { +	LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE = 0, +	LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE, +	LOGICVC_OF_PROPERTY_DISPLAY_DEPTH, +	LOGICVC_OF_PROPERTY_ROW_STRIDE, +	LOGICVC_OF_PROPERTY_DITHERING, +	LOGICVC_OF_PROPERTY_BACKGROUND_LAYER, +	LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE, +	LOGICVC_OF_PROPERTY_LAYERS_COUNT, +	LOGICVC_OF_PROPERTY_LAYER_DEPTH, +	LOGICVC_OF_PROPERTY_LAYER_COLORSPACE, +	LOGICVC_OF_PROPERTY_LAYER_ALPHA_MODE, +	LOGICVC_OF_PROPERTY_LAYER_BASE_OFFSET, +	LOGICVC_OF_PROPERTY_LAYER_BUFFER_OFFSET, +	LOGICVC_OF_PROPERTY_LAYER_PRIMARY, +	LOGICVC_OF_PROPERTY_MAXIMUM, +}; + +struct logicvc_of_property_sv { +	const char *string; +	u32 value; +}; + +struct logicvc_of_property { +	char *name; +	bool optional; +	struct logicvc_of_property_sv *sv; +	u32 range[2]; +}; + +int logicvc_of_property_parse_u32(struct device_node *of_node, +				  unsigned int index, u32 *target); +void logicvc_of_property_parse_bool(struct device_node *of_node, +				    unsigned int index, bool *target); +bool logicvc_of_node_is_layer(struct device_node *of_node); + +#endif diff --git a/drivers/gpu/drm/logicvc/logicvc_regs.h b/drivers/gpu/drm/logicvc/logicvc_regs.h new file mode 100644 index 000000000000..4aae27e9ba2b --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_regs.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2019-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + * + * Copyright (C) 2014 Xylon d.o.o. + * Author: Davor Joja <davor.joja@logicbricks.com> + */ + +#ifndef _LOGICVC_REGS_H_ +#define _LOGICVC_REGS_H_ + +#define LOGICVC_DIMENSIONS_MAX		(BIT(16) - 1) + +#define LOGICVC_HSYNC_FRONT_PORCH_REG	0x00 +#define LOGICVC_HSYNC_REG		0x08 +#define LOGICVC_HSYNC_BACK_PORCH_REG	0x10 +#define LOGICVC_HRES_REG		0x18 +#define LOGICVC_VSYNC_FRONT_PORCH_REG	0x20 +#define LOGICVC_VSYNC_REG		0x28 +#define LOGICVC_VSYNC_BACK_PORCH_REG	0x30 +#define LOGICVC_VRES_REG		0x38 + +#define LOGICVC_CTRL_REG		0x40 +#define LOGICVC_CTRL_CLOCK_INVERT	BIT(8) +#define LOGICVC_CTRL_PIXEL_INVERT	BIT(7) +#define LOGICVC_CTRL_DE_INVERT		BIT(5) +#define LOGICVC_CTRL_DE_ENABLE		BIT(4) +#define LOGICVC_CTRL_VSYNC_INVERT	BIT(3) +#define LOGICVC_CTRL_VSYNC_ENABLE	BIT(2) +#define LOGICVC_CTRL_HSYNC_INVERT	BIT(1) +#define LOGICVC_CTRL_HSYNC_ENABLE	BIT(0) + +#define LOGICVC_DTYPE_REG		0x48 +#define LOGICVC_BACKGROUND_COLOR_REG	0x50 + +#define LOGICVC_BUFFER_SEL_REG		0x58 +#define LOGICVC_BUFFER_SEL_VALUE(i, v) \ +	(BIT(10 + (i)) | ((v) << (2 * (i)))) +#define LOGICVC_BUFFER_SEL_MAX		2 + +#define LOGICVC_DOUBLE_CLUT_REG		0x60 + +#define LOGICVC_INT_STAT_REG		0x68 +#define LOGICVC_INT_STAT_V_SYNC		BIT(5) + +#define LOGICVC_INT_MASK_REG		0x70 +#define LOGICVC_INT_MASK_V_SYNC		BIT(5) + +#define LOGICVC_POWER_CTRL_REG		0x78 +#define LOGICVC_POWER_CTRL_BACKLIGHT_ENABLE	BIT(0) +#define LOGICVC_POWER_CTRL_VDD_ENABLE		BIT(1) +#define LOGICVC_POWER_CTRL_VEE_ENABLE		BIT(2) +#define LOGICVC_POWER_CTRL_VIDEO_ENABLE		BIT(3) + +#define LOGICVC_IP_VERSION_REG		0xf8 +#define LOGICVC_IP_VERSION_MAJOR_MASK	GENMASK(16, 11) +#define LOGICVC_IP_VERSION_MINOR_MASK	GENMASK(10, 5) +#define LOGICVC_IP_VERSION_LEVEL_MASK	GENMASK(4, 0) + +#define LOGICVC_LAYER_ADDRESS_REG(i)	(0x100 + (i) * 0x80) +#define LOGICVC_LAYER_HOFFSET_REG(i)	(0x100 + (i) * 0x80) + +#define LOGICVC_LAYER_VOFFSET_REG(i)	(0x108 + (i) * 0x80) +#define LOGICVC_LAYER_VOFFSET_MAX	4095 + +#define LOGICVC_LAYER_HPOSITION_REG(i)	(0x110 + (i) * 0x80) +#define LOGICVC_LAYER_VPOSITION_REG(i)	(0x118 + (i) * 0x80) +#define LOGICVC_LAYER_WIDTH_REG(i)	(0x120 + (i) * 0x80) +#define LOGICVC_LAYER_HEIGHT_REG(i)	(0x128 + (i) * 0x80) +#define LOGICVC_LAYER_ALPHA_REG(i)	(0x130 + (i) * 0x80) + +#define LOGICVC_LAYER_CTRL_REG(i)	(0x138 + (i) * 0x80) +#define LOGICVC_LAYER_CTRL_ENABLE	BIT(0) +#define LOGICVC_LAYER_CTRL_COLOR_KEY_DISABLE	BIT(1) +#define LOGICVC_LAYER_CTRL_PIXEL_FORMAT_INVERT	BIT(4) + +#define LOGICVC_LAYER_COLOR_KEY_REG(i)	(0x140 + (i) * 0x80) + +#endif | 
