diff options
-rw-r--r-- | MAINTAINERS | 6 | ||||
-rw-r--r-- | drivers/gpu/drm/Kconfig | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/Makefile | 1 | ||||
-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 | 497 | ||||
-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 | 628 | ||||
-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 |
18 files changed, 2232 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index ede21cc48708..4fcf76fbb6d9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6212,6 +6212,12 @@ S: Orphan / Obsolete F: drivers/gpu/drm/i810/ F: include/uapi/drm/i810_drm.h +DRM DRIVER FOR LOGICVC DISPLAY CONTROLLER +M: Paul Kocialkowski <paul.kocialkowski@bootlin.com> +S: Supported +T: git git://anongit.freedesktop.org/drm/drm-misc +F: drivers/gpu/drm/logicvc/ + DRM DRIVER FOR LVDS PANELS M: Laurent Pinchart <laurent.pinchart@ideasonboard.com> L: dri-devel@lists.freedesktop.org diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index e88c497fa010..22e7fa48d693 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -351,6 +351,8 @@ source "drivers/gpu/drm/etnaviv/Kconfig" source "drivers/gpu/drm/hisilicon/Kconfig" +source "drivers/gpu/drm/logicvc/Kconfig" + source "drivers/gpu/drm/mediatek/Kconfig" source "drivers/gpu/drm/mxsfb/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 15fe3163f822..13ef240b3d2b 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -121,6 +121,7 @@ obj-$(CONFIG_DRM_STM) += stm/ obj-$(CONFIG_DRM_STI) += sti/ obj-y += imx/ obj-$(CONFIG_DRM_INGENIC) += ingenic/ +obj-$(CONFIG_DRM_LOGICVC) += logicvc/ obj-$(CONFIG_DRM_MEDIATEK) += mediatek/ obj-$(CONFIG_DRM_MESON) += meson/ obj-y += i2c/ 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..df1805cf0f95 --- /dev/null +++ b/drivers/gpu/drm/logicvc/logicvc_drm.c @@ -0,0 +1,497 @@ +// 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; + 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) { + dev_err(dev, "Failed to get IRQ\n"); + 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); + } +} + |