Commit efeeaefe authored by Paul Kocialkowski's avatar Paul Kocialkowski

drm: Add support for the LogiCVC display controller

Introduces a driver for the LogiCVC display controller, a programmable
logic controller optimized for use in Xilinx Zynq-7000 SoCs and other
Xilinx FPGAs. The controller is mostly configured at logic synthesis
time so only a subset of configuration is left for the driver to
handle.

The following features are implemented and tested:
- LVDS 4-bit interface;
- RGB565 pixel formats;
- Multiple layers and hardware composition;
- Layer-wide alpha mode;

The following features are implemented but untested:
- Other RGB pixel formats;
- Layer framebuffer configuration for version 4;
- Lowest-layer used as background color;
- Per-pixel alpha mode.

The following features are not implemented:
- YUV pixel formats;
- DVI, LVDS 3-bit, ITU656 and camera link interfaces;
- External parallel input for layer;
- Color-keying;
- LUT-based alpha modes.

Additional implementation-specific notes:
- Panels are only enabled after the first page flip to avoid flashing a
  white screen.
- Depth used in context of the LogiCVC driver only counts color components
  to match the definition of the synthesis parameters.

Support is implemented for both version 3 and 4 of the controller.

With version 3, framebuffers are stored in a dedicated contiguous
memory area, with a base address hardcoded for each layer. This requires
using a dedicated CMA pool registered at the base address and tweaking a
few offset-related registers to try to use any buffer allocated from
the pool. This is done on a best-effort basis to have the hardware cope
with the DRM framebuffer allocation model and there is no guarantee
that each buffer allocated by GEM CMA can be used for any layer.
In particular, buffers allocated below the base address for a layer are
guaranteed not to be configurable for that layer. See the implementation of
logicvc_layer_buffer_find_setup for specifics.

Version 4 allows configuring each buffer address directly, which
guarantees that any buffer can be configured.
Signed-off-by: default avatarPaul Kocialkowski <paul.kocialkowski@bootlin.com>
Reviewed-by: default avatarMaxime Ripard <mripard@kernel.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20220520141555.1429041-2-paul.kocialkowski@bootlin.com
parent bdde97ac
...@@ -6212,6 +6212,12 @@ S: Orphan / Obsolete ...@@ -6212,6 +6212,12 @@ S: Orphan / Obsolete
F: drivers/gpu/drm/i810/ F: drivers/gpu/drm/i810/
F: include/uapi/drm/i810_drm.h 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 DRM DRIVER FOR LVDS PANELS
M: Laurent Pinchart <laurent.pinchart@ideasonboard.com> M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
L: dri-devel@lists.freedesktop.org L: dri-devel@lists.freedesktop.org
......
...@@ -351,6 +351,8 @@ source "drivers/gpu/drm/etnaviv/Kconfig" ...@@ -351,6 +351,8 @@ source "drivers/gpu/drm/etnaviv/Kconfig"
source "drivers/gpu/drm/hisilicon/Kconfig" source "drivers/gpu/drm/hisilicon/Kconfig"
source "drivers/gpu/drm/logicvc/Kconfig"
source "drivers/gpu/drm/mediatek/Kconfig" source "drivers/gpu/drm/mediatek/Kconfig"
source "drivers/gpu/drm/mxsfb/Kconfig" source "drivers/gpu/drm/mxsfb/Kconfig"
......
...@@ -121,6 +121,7 @@ obj-$(CONFIG_DRM_STM) += stm/ ...@@ -121,6 +121,7 @@ obj-$(CONFIG_DRM_STM) += stm/
obj-$(CONFIG_DRM_STI) += sti/ obj-$(CONFIG_DRM_STI) += sti/
obj-y += imx/ obj-y += imx/
obj-$(CONFIG_DRM_INGENIC) += ingenic/ obj-$(CONFIG_DRM_INGENIC) += ingenic/
obj-$(CONFIG_DRM_LOGICVC) += logicvc/
obj-$(CONFIG_DRM_MEDIATEK) += mediatek/ obj-$(CONFIG_DRM_MEDIATEK) += mediatek/
obj-$(CONFIG_DRM_MESON) += meson/ obj-$(CONFIG_DRM_MESON) += meson/
obj-y += i2c/ obj-y += i2c/
......
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
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
// 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;
}
/* 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
This diff is collapsed.
/* 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
// 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;
}
/* 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
This diff is collapsed.
/* 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
// 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);
}
/* 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
// 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");
}
/* 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
/* 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment