Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
source "drivers/gpu/drm/renesas/rcar-du/Kconfig"
source "drivers/gpu/drm/renesas/rz-du/Kconfig"
source "drivers/gpu/drm/renesas/shmobile/Kconfig"
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-y += rcar-du/
obj-y += rz-du/
obj-$(CONFIG_DRM_SHMOBILE) += shmobile/
@@ -0,0 +1,77 @@
# SPDX-License-Identifier: GPL-2.0
config DRM_RCAR_DU
tristate "DRM Support for R-Car Display Unit"
depends on DRM && OF
depends on ARM || ARM64 || COMPILE_TEST
depends on ARCH_RENESAS || COMPILE_TEST
select DRM_CLIENT_SELECTION
select DRM_KMS_HELPER
select DRM_DISPLAY_HELPER
select DRM_BRIDGE_CONNECTOR
select DRM_GEM_DMA_HELPER
select VIDEOMODE_HELPERS
help
Choose this option if you have an R-Car chipset.
If M is selected the module will be called rcar-du-drm.
config DRM_RCAR_USE_CMM
bool "R-Car DU Color Management Module (CMM) Support"
depends on DRM_RCAR_DU
default DRM_RCAR_DU
help
Enable support for R-Car Color Management Module (CMM).
config DRM_RCAR_CMM
def_tristate DRM_RCAR_DU
depends on DRM_RCAR_USE_CMM
config DRM_RCAR_DW_HDMI
tristate "R-Car Gen3 and RZ/G2 DU HDMI Encoder Support"
depends on DRM && OF
depends on DRM_RCAR_DU || COMPILE_TEST
select DRM_DW_HDMI
help
Enable support for R-Car Gen3 or RZ/G2 internal HDMI encoder.
config DRM_RCAR_USE_LVDS
bool "R-Car DU LVDS Encoder Support"
depends on DRM_BRIDGE && OF
depends on DRM_RCAR_DU || COMPILE_TEST
default DRM_RCAR_DU
help
Enable support for the R-Car Display Unit embedded LVDS encoders.
config DRM_RCAR_LVDS
def_tristate DRM_RCAR_DU
depends on DRM_RCAR_USE_LVDS
depends on PM
select DRM_KMS_HELPER
select DRM_PANEL
select RESET_CONTROLLER
config DRM_RCAR_USE_MIPI_DSI
bool "R-Car DU MIPI DSI Encoder Support"
depends on DRM_BRIDGE && OF
depends on DRM_RCAR_DU || COMPILE_TEST
default DRM_RCAR_DU
help
Enable support for the R-Car Display Unit embedded MIPI DSI encoders.
config DRM_RCAR_MIPI_DSI
def_tristate DRM_RCAR_DU
depends on DRM_RCAR_USE_MIPI_DSI
select DRM_MIPI_DSI
select RESET_CONTROLLER
config DRM_RCAR_VSP
bool "R-Car DU VSP Compositor Support" if ARM
default y if ARM64
depends on DRM_RCAR_DU
depends on VIDEO_RENESAS_VSP1=y || (VIDEO_RENESAS_VSP1 && DRM_RCAR_DU=m)
help
Enable support to expose the R-Car VSP Compositor as KMS planes.
config DRM_RCAR_WRITEBACK
bool
default y if ARM64
depends on DRM_RCAR_DU
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0
rcar-du-drm-y := rcar_du_crtc.o \
rcar_du_drv.o \
rcar_du_encoder.o \
rcar_du_group.o \
rcar_du_kms.o \
rcar_du_plane.o \
rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o
rcar-du-drm-$(CONFIG_DRM_RCAR_WRITEBACK) += rcar_du_writeback.o
obj-$(CONFIG_DRM_RCAR_CMM) += rcar_cmm.o
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o
obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o
obj-$(CONFIG_DRM_RCAR_LVDS) += rcar_lvds.o
obj-$(CONFIG_DRM_RCAR_MIPI_DSI) += rcar_mipi_dsi.o
@@ -0,0 +1,210 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* R-Car Display Unit Color Management Module
*
* Copyright (C) 2019 Jacopo Mondi <jacopo+renesas@jmondi.org>
*/
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <drm/drm_color_mgmt.h>
#include "rcar_cmm.h"
#define CM2_LUT_CTRL 0x0000
#define CM2_LUT_CTRL_LUT_EN BIT(0)
#define CM2_LUT_TBL_BASE 0x0600
#define CM2_LUT_TBL(__i) (CM2_LUT_TBL_BASE + (__i) * 4)
struct rcar_cmm {
void __iomem *base;
/*
* @lut: 1D-LUT state
* @lut.enabled: 1D-LUT enabled flag
*/
struct {
bool enabled;
} lut;
};
static inline void rcar_cmm_write(struct rcar_cmm *rcmm, u32 reg, u32 data)
{
iowrite32(data, rcmm->base + reg);
}
/*
* rcar_cmm_lut_write() - Scale the DRM LUT table entries to hardware precision
* and write to the CMM registers
* @rcmm: Pointer to the CMM device
* @drm_lut: Pointer to the DRM LUT table
*/
static void rcar_cmm_lut_write(struct rcar_cmm *rcmm,
const struct drm_color_lut *drm_lut)
{
unsigned int i;
for (i = 0; i < CM2_LUT_SIZE; ++i) {
u32 entry = drm_color_lut_extract(drm_lut[i].red, 8) << 16
| drm_color_lut_extract(drm_lut[i].green, 8) << 8
| drm_color_lut_extract(drm_lut[i].blue, 8);
rcar_cmm_write(rcmm, CM2_LUT_TBL(i), entry);
}
}
/*
* rcar_cmm_setup() - Configure the CMM unit
* @dev: The device associated with the CMM instance
* @config: The CMM unit configuration
*
* Configure the CMM unit with the given configuration. Currently enabling,
* disabling and programming of the 1-D LUT unit is supported.
*
* As rcar_cmm_setup() accesses the CMM registers the unit should be powered
* and its functional clock enabled. To guarantee this, before any call to
* this function is made, the CMM unit has to be enabled by calling
* rcar_cmm_enable() first.
*
* TODO: Add support for LUT double buffer operations to avoid updating the
* LUT table entries while a frame is being displayed.
*/
int rcar_cmm_setup(struct device *dev,
const struct rcar_cmm_config *config)
{
struct rcar_cmm *rcmm = dev_get_drvdata(dev);
/* Disable LUT if no table is provided. */
if (!config->lut.table) {
if (rcmm->lut.enabled) {
rcar_cmm_write(rcmm, CM2_LUT_CTRL, 0);
rcmm->lut.enabled = false;
}
return 0;
}
/* Enable LUT and program the new gamma table values. */
if (!rcmm->lut.enabled) {
rcar_cmm_write(rcmm, CM2_LUT_CTRL, CM2_LUT_CTRL_LUT_EN);
rcmm->lut.enabled = true;
}
rcar_cmm_lut_write(rcmm, config->lut.table);
return 0;
}
EXPORT_SYMBOL_GPL(rcar_cmm_setup);
/*
* rcar_cmm_enable() - Enable the CMM unit
* @dev: The device associated with the CMM instance
*
* When the output of the corresponding DU channel is routed to the CMM unit,
* the unit shall be enabled before the DU channel is started, and remain
* enabled until the channel is stopped. The CMM unit shall be disabled with
* rcar_cmm_disable().
*
* Calls to rcar_cmm_enable() and rcar_cmm_disable() are not reference-counted.
* It is an error to attempt to enable an already enabled CMM unit, or to
* attempt to disable a disabled unit.
*/
int rcar_cmm_enable(struct device *dev)
{
int ret;
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
return ret;
return 0;
}
EXPORT_SYMBOL_GPL(rcar_cmm_enable);
/*
* rcar_cmm_disable() - Disable the CMM unit
* @dev: The device associated with the CMM instance
*
* See rcar_cmm_enable() for usage information.
*
* Disabling the CMM unit disable all the internal processing blocks. The CMM
* state shall thus be restored with rcar_cmm_setup() when re-enabling the CMM
* unit after the next rcar_cmm_enable() call.
*/
void rcar_cmm_disable(struct device *dev)
{
struct rcar_cmm *rcmm = dev_get_drvdata(dev);
rcar_cmm_write(rcmm, CM2_LUT_CTRL, 0);
rcmm->lut.enabled = false;
pm_runtime_put(dev);
}
EXPORT_SYMBOL_GPL(rcar_cmm_disable);
/*
* rcar_cmm_init() - Initialize the CMM unit
* @dev: The device associated with the CMM instance
*
* Return: 0 on success, -EPROBE_DEFER if the CMM is not available yet,
* -ENODEV if the DRM_RCAR_CMM config option is disabled
*/
int rcar_cmm_init(struct device *dev)
{
struct rcar_cmm *rcmm = dev_get_drvdata(dev);
if (!rcmm)
return -EPROBE_DEFER;
return 0;
}
EXPORT_SYMBOL_GPL(rcar_cmm_init);
static int rcar_cmm_probe(struct platform_device *pdev)
{
struct rcar_cmm *rcmm;
rcmm = devm_kzalloc(&pdev->dev, sizeof(*rcmm), GFP_KERNEL);
if (!rcmm)
return -ENOMEM;
platform_set_drvdata(pdev, rcmm);
rcmm->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(rcmm->base))
return PTR_ERR(rcmm->base);
pm_runtime_enable(&pdev->dev);
return 0;
}
static void rcar_cmm_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static const struct of_device_id rcar_cmm_of_table[] = {
{ .compatible = "renesas,rcar-gen3-cmm", },
{ .compatible = "renesas,rcar-gen2-cmm", },
{ },
};
MODULE_DEVICE_TABLE(of, rcar_cmm_of_table);
static struct platform_driver rcar_cmm_platform_driver = {
.probe = rcar_cmm_probe,
.remove = rcar_cmm_remove,
.driver = {
.name = "rcar-cmm",
.of_match_table = rcar_cmm_of_table,
},
};
module_platform_driver(rcar_cmm_platform_driver);
MODULE_AUTHOR("Jacopo Mondi <jacopo+renesas@jmondi.org>");
MODULE_DESCRIPTION("Renesas R-Car CMM Driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* R-Car Display Unit Color Management Module
*
* Copyright (C) 2019 Jacopo Mondi <jacopo+renesas@jmondi.org>
*/
#ifndef __RCAR_CMM_H__
#define __RCAR_CMM_H__
#define CM2_LUT_SIZE 256
struct device;
struct drm_color_lut;
/**
* struct rcar_cmm_config - CMM configuration
*
* @lut: 1D-LUT configuration
* @lut.table: 1D-LUT table entries. Disable LUT operations when NULL
*/
struct rcar_cmm_config {
struct {
struct drm_color_lut *table;
} lut;
};
#if IS_ENABLED(CONFIG_DRM_RCAR_CMM)
int rcar_cmm_init(struct device *dev);
int rcar_cmm_enable(struct device *dev);
void rcar_cmm_disable(struct device *dev);
int rcar_cmm_setup(struct device *dev,
const struct rcar_cmm_config *config);
#else
static inline int rcar_cmm_init(struct device *dev)
{
return -ENODEV;
}
static inline int rcar_cmm_enable(struct device *dev)
{
return 0;
}
static inline void rcar_cmm_disable(struct device *dev)
{
}
static inline int rcar_cmm_setup(struct device *dev,
const struct rcar_cmm_config *config)
{
return 0;
}
#endif /* IS_ENABLED(CONFIG_DRM_RCAR_CMM) */
#endif /* __RCAR_CMM_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,104 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* R-Car Display Unit CRTCs
*
* Copyright (C) 2013-2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __RCAR_DU_CRTC_H__
#define __RCAR_DU_CRTC_H__
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <drm/drm_crtc.h>
#include <drm/drm_writeback.h>
#include <media/vsp1.h>
struct rcar_du_cmm;
struct rcar_du_group;
struct rcar_du_vsp;
/**
* struct rcar_du_crtc - the CRTC, representing a DU superposition processor
* @crtc: base DRM CRTC
* @dev: the DU device
* @clock: the CRTC functional clock
* @extclock: external pixel dot clock (optional)
* @mmio_offset: offset of the CRTC registers in the DU MMIO block
* @index: CRTC hardware index
* @initialized: whether the CRTC has been initialized and clocks enabled
* @dsysr: cached value of the DSYSR register
* @vblank_enable: whether vblank events are enabled on this CRTC
* @event: event to post when the pending page flip completes
* @flip_wait: wait queue used to signal page flip completion
* @vblank_lock: protects vblank_wait and vblank_count
* @vblank_wait: wait queue used to signal vertical blanking
* @vblank_count: number of vertical blanking interrupts to wait for
* @group: CRTC group this CRTC belongs to
* @cmm: CMM associated with this CRTC
* @vsp: VSP feeding video to this CRTC
* @vsp_pipe: index of the VSP pipeline feeding video to this CRTC
* @writeback: the writeback connector
*/
struct rcar_du_crtc {
struct drm_crtc crtc;
struct rcar_du_device *dev;
struct clk *clock;
struct clk *extclock;
unsigned int mmio_offset;
unsigned int index;
bool initialized;
u32 dsysr;
bool vblank_enable;
struct drm_pending_vblank_event *event;
wait_queue_head_t flip_wait;
spinlock_t vblank_lock;
wait_queue_head_t vblank_wait;
unsigned int vblank_count;
struct rcar_du_group *group;
struct rcar_du_cmm *cmm;
struct rcar_du_vsp *vsp;
unsigned int vsp_pipe;
const char *const *sources;
unsigned int sources_count;
struct drm_writeback_connector writeback;
};
#define to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, crtc)
#define wb_to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, writeback)
/**
* struct rcar_du_crtc_state - Driver-specific CRTC state
* @state: base DRM CRTC state
* @crc: CRC computation configuration
* @outputs: bitmask of the outputs (enum rcar_du_output) driven by this CRTC
*/
struct rcar_du_crtc_state {
struct drm_crtc_state state;
struct vsp1_du_crc_config crc;
unsigned int outputs;
};
#define to_rcar_crtc_state(s) container_of(s, struct rcar_du_crtc_state, state)
int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex,
unsigned int hwindex);
void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc);
void rcar_du_crtc_dsysr_clr_set(struct rcar_du_crtc *rcrtc, u32 clr, u32 set);
#endif /* __RCAR_DU_CRTC_H__ */
@@ -0,0 +1,763 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* R-Car Display Unit DRM driver
*
* Copyright (C) 2013-2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <drm/clients/drm_client_setup.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_dma.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_managed.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include "rcar_du_drv.h"
#include "rcar_du_kms.h"
/* -----------------------------------------------------------------------------
* Device Information
*/
static const struct rcar_du_device_info rzg1_du_r8a7743_info = {
.gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(1) | BIT(0),
.routes = {
/*
* R8A774[34] has one RGB output and one LVDS output
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(1) | BIT(0),
.port = 0,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 1,
},
},
.num_lvds = 1,
.num_rpf = 4,
};
static const struct rcar_du_device_info rzg1_du_r8a7745_info = {
.gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(1) | BIT(0),
.routes = {
/*
* R8A7745 has two RGB outputs
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0),
.port = 0,
},
[RCAR_DU_OUTPUT_DPAD1] = {
.possible_crtcs = BIT(1),
.port = 1,
},
},
.num_rpf = 4,
};
static const struct rcar_du_device_info rzg1_du_r8a77470_info = {
.gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(1) | BIT(0),
.routes = {
/*
* R8A77470 has two RGB outputs, one LVDS output, and
* one (currently unsupported) analog video output
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0),
.port = 0,
},
[RCAR_DU_OUTPUT_DPAD1] = {
.possible_crtcs = BIT(1),
.port = 1,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0) | BIT(1),
.port = 2,
},
},
.num_rpf = 4,
};
static const struct rcar_du_device_info rcar_du_r8a774a1_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_VSP1_SOURCE
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(2) | BIT(1) | BIT(0),
.routes = {
/*
* R8A774A1 has one RGB output, one LVDS output and one HDMI
* output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(2),
.port = 0,
},
[RCAR_DU_OUTPUT_HDMI0] = {
.possible_crtcs = BIT(1),
.port = 1,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 2,
},
},
.num_lvds = 1,
.num_rpf = 5,
.dpll_mask = BIT(1),
};
static const struct rcar_du_device_info rcar_du_r8a774b1_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_VSP1_SOURCE
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(3) | BIT(1) | BIT(0),
.routes = {
/*
* R8A774B1 has one RGB output, one LVDS output and one HDMI
* output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(2),
.port = 0,
},
[RCAR_DU_OUTPUT_HDMI0] = {
.possible_crtcs = BIT(1),
.port = 1,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 2,
},
},
.num_lvds = 1,
.num_rpf = 5,
.dpll_mask = BIT(1),
};
static const struct rcar_du_device_info rcar_du_r8a774c0_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_VSP1_SOURCE,
.channels_mask = BIT(1) | BIT(0),
.routes = {
/*
* R8A774C0 has one RGB output and two LVDS outputs
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0) | BIT(1),
.port = 0,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 1,
},
[RCAR_DU_OUTPUT_LVDS1] = {
.possible_crtcs = BIT(1),
.port = 2,
},
},
.num_lvds = 2,
.num_rpf = 4,
.lvds_clk_mask = BIT(1) | BIT(0),
};
static const struct rcar_du_device_info rcar_du_r8a774e1_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_VSP1_SOURCE
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(3) | BIT(1) | BIT(0),
.routes = {
/*
* R8A774E1 has one RGB output, one LVDS output and one HDMI
* output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(2),
.port = 0,
},
[RCAR_DU_OUTPUT_HDMI0] = {
.possible_crtcs = BIT(1),
.port = 1,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 2,
},
},
.num_lvds = 1,
.num_rpf = 5,
.dpll_mask = BIT(1),
};
static const struct rcar_du_device_info rcar_du_r8a7779_info = {
.gen = 1,
.features = RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(1) | BIT(0),
.routes = {
/*
* R8A7779 has two RGB outputs and one (currently unsupported)
* TCON output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0),
.port = 0,
},
[RCAR_DU_OUTPUT_DPAD1] = {
.possible_crtcs = BIT(1) | BIT(0),
.port = 1,
},
},
};
static const struct rcar_du_device_info rcar_du_r8a7790_info = {
.gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.quirks = RCAR_DU_QUIRK_ALIGN_128B,
.channels_mask = BIT(2) | BIT(1) | BIT(0),
.routes = {
/*
* R8A7742 and R8A7790 each have one RGB output and two LVDS
* outputs. Additionally R8A7790 supports one TCON output
* (currently unsupported by the driver).
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(2) | BIT(1) | BIT(0),
.port = 0,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 1,
},
[RCAR_DU_OUTPUT_LVDS1] = {
.possible_crtcs = BIT(2) | BIT(1),
.port = 2,
},
},
.num_lvds = 2,
.num_rpf = 4,
};
/* M2-W (r8a7791) and M2-N (r8a7793) are identical */
static const struct rcar_du_device_info rcar_du_r8a7791_info = {
.gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(1) | BIT(0),
.routes = {
/*
* R8A779[13] has one RGB output, one LVDS output and one
* (currently unsupported) TCON output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(1) | BIT(0),
.port = 0,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 1,
},
},
.num_lvds = 1,
.num_rpf = 4,
};
static const struct rcar_du_device_info rcar_du_r8a7792_info = {
.gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(1) | BIT(0),
.routes = {
/* R8A7792 has two RGB outputs. */
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0),
.port = 0,
},
[RCAR_DU_OUTPUT_DPAD1] = {
.possible_crtcs = BIT(1),
.port = 1,
},
},
.num_rpf = 4,
};
static const struct rcar_du_device_info rcar_du_r8a7794_info = {
.gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(1) | BIT(0),
.routes = {
/*
* R8A7794 has two RGB outputs and one (currently unsupported)
* TCON output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0),
.port = 0,
},
[RCAR_DU_OUTPUT_DPAD1] = {
.possible_crtcs = BIT(1),
.port = 1,
},
},
.num_rpf = 4,
};
static const struct rcar_du_device_info rcar_du_r8a7795_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_VSP1_SOURCE
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(3) | BIT(2) | BIT(1) | BIT(0),
.routes = {
/*
* R8A7795 has one RGB output, two HDMI outputs and one
* LVDS output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(3),
.port = 0,
},
[RCAR_DU_OUTPUT_HDMI0] = {
.possible_crtcs = BIT(1),
.port = 1,
},
[RCAR_DU_OUTPUT_HDMI1] = {
.possible_crtcs = BIT(2),
.port = 2,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 3,
},
},
.num_lvds = 1,
.num_rpf = 5,
.dpll_mask = BIT(2) | BIT(1),
};
static const struct rcar_du_device_info rcar_du_r8a7796_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_VSP1_SOURCE
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(2) | BIT(1) | BIT(0),
.routes = {
/*
* R8A7796 has one RGB output, one LVDS output and one HDMI
* output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(2),
.port = 0,
},
[RCAR_DU_OUTPUT_HDMI0] = {
.possible_crtcs = BIT(1),
.port = 1,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 2,
},
},
.num_lvds = 1,
.num_rpf = 5,
.dpll_mask = BIT(1),
};
static const struct rcar_du_device_info rcar_du_r8a77965_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_VSP1_SOURCE
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(3) | BIT(1) | BIT(0),
.routes = {
/*
* R8A77965 has one RGB output, one LVDS output and one HDMI
* output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(2),
.port = 0,
},
[RCAR_DU_OUTPUT_HDMI0] = {
.possible_crtcs = BIT(1),
.port = 1,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 2,
},
},
.num_lvds = 1,
.num_rpf = 5,
.dpll_mask = BIT(1),
};
static const struct rcar_du_device_info rcar_du_r8a77970_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_VSP1_SOURCE
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(0),
.routes = {
/*
* R8A77970 and R8A77980 have one RGB output and one LVDS
* output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0),
.port = 0,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 1,
},
},
.num_lvds = 1,
.num_rpf = 5,
};
static const struct rcar_du_device_info rcar_du_r8a7799x_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_CRTC_CLOCK
| RCAR_DU_FEATURE_VSP1_SOURCE,
.channels_mask = BIT(1) | BIT(0),
.routes = {
/*
* R8A77990 and R8A77995 have one RGB output and two LVDS
* outputs.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0) | BIT(1),
.port = 0,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 1,
},
[RCAR_DU_OUTPUT_LVDS1] = {
.possible_crtcs = BIT(1),
.port = 2,
},
},
.num_lvds = 2,
.num_rpf = 5,
.lvds_clk_mask = BIT(1) | BIT(0),
};
static const struct rcar_du_device_info rcar_du_r8a779a0_info = {
.gen = 4,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_VSP1_SOURCE
| RCAR_DU_FEATURE_NO_BLENDING,
.channels_mask = BIT(1) | BIT(0),
.routes = {
/* R8A779A0 has two MIPI DSI outputs. */
[RCAR_DU_OUTPUT_DSI0] = {
.possible_crtcs = BIT(0),
.port = 0,
},
[RCAR_DU_OUTPUT_DSI1] = {
.possible_crtcs = BIT(1),
.port = 1,
},
},
.num_rpf = 5,
.dsi_clk_mask = BIT(1) | BIT(0),
};
static const struct rcar_du_device_info rcar_du_r8a779g0_info = {
.gen = 4,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_VSP1_SOURCE
| RCAR_DU_FEATURE_NO_BLENDING,
.channels_mask = BIT(1) | BIT(0),
.routes = {
/* R8A779G0 has two MIPI DSI outputs. */
[RCAR_DU_OUTPUT_DSI0] = {
.possible_crtcs = BIT(0),
.port = 0,
},
[RCAR_DU_OUTPUT_DSI1] = {
.possible_crtcs = BIT(1),
.port = 1,
},
},
.num_rpf = 5,
.dsi_clk_mask = BIT(1) | BIT(0),
};
static const struct rcar_du_device_info rcar_du_r8a779h0_info = {
.gen = 4,
.features = RCAR_DU_FEATURE_CRTC_IRQ
| RCAR_DU_FEATURE_VSP1_SOURCE
| RCAR_DU_FEATURE_NO_BLENDING,
.channels_mask = BIT(0),
.routes = {
/* R8A779H0 has one MIPI DSI output. */
[RCAR_DU_OUTPUT_DSI0] = {
.possible_crtcs = BIT(0),
.port = 0,
},
},
.num_rpf = 5,
.dsi_clk_mask = BIT(0),
};
static const struct of_device_id rcar_du_of_table[] = {
{ .compatible = "renesas,du-r8a7742", .data = &rcar_du_r8a7790_info },
{ .compatible = "renesas,du-r8a7743", .data = &rzg1_du_r8a7743_info },
{ .compatible = "renesas,du-r8a7744", .data = &rzg1_du_r8a7743_info },
{ .compatible = "renesas,du-r8a7745", .data = &rzg1_du_r8a7745_info },
{ .compatible = "renesas,du-r8a77470", .data = &rzg1_du_r8a77470_info },
{ .compatible = "renesas,du-r8a774a1", .data = &rcar_du_r8a774a1_info },
{ .compatible = "renesas,du-r8a774b1", .data = &rcar_du_r8a774b1_info },
{ .compatible = "renesas,du-r8a774c0", .data = &rcar_du_r8a774c0_info },
{ .compatible = "renesas,du-r8a774e1", .data = &rcar_du_r8a774e1_info },
{ .compatible = "renesas,du-r8a7779", .data = &rcar_du_r8a7779_info },
{ .compatible = "renesas,du-r8a7790", .data = &rcar_du_r8a7790_info },
{ .compatible = "renesas,du-r8a7791", .data = &rcar_du_r8a7791_info },
{ .compatible = "renesas,du-r8a7792", .data = &rcar_du_r8a7792_info },
{ .compatible = "renesas,du-r8a7793", .data = &rcar_du_r8a7791_info },
{ .compatible = "renesas,du-r8a7794", .data = &rcar_du_r8a7794_info },
{ .compatible = "renesas,du-r8a7795", .data = &rcar_du_r8a7795_info },
{ .compatible = "renesas,du-r8a7796", .data = &rcar_du_r8a7796_info },
{ .compatible = "renesas,du-r8a77961", .data = &rcar_du_r8a7796_info },
{ .compatible = "renesas,du-r8a77965", .data = &rcar_du_r8a77965_info },
{ .compatible = "renesas,du-r8a77970", .data = &rcar_du_r8a77970_info },
{ .compatible = "renesas,du-r8a77980", .data = &rcar_du_r8a77970_info },
{ .compatible = "renesas,du-r8a77990", .data = &rcar_du_r8a7799x_info },
{ .compatible = "renesas,du-r8a77995", .data = &rcar_du_r8a7799x_info },
{ .compatible = "renesas,du-r8a779a0", .data = &rcar_du_r8a779a0_info },
{ .compatible = "renesas,du-r8a779g0", .data = &rcar_du_r8a779g0_info },
{ .compatible = "renesas,du-r8a779h0", .data = &rcar_du_r8a779h0_info },
{ }
};
MODULE_DEVICE_TABLE(of, rcar_du_of_table);
const char *rcar_du_output_name(enum rcar_du_output output)
{
static const char * const names[] = {
[RCAR_DU_OUTPUT_DPAD0] = "DPAD0",
[RCAR_DU_OUTPUT_DPAD1] = "DPAD1",
[RCAR_DU_OUTPUT_DSI0] = "DSI0",
[RCAR_DU_OUTPUT_DSI1] = "DSI1",
[RCAR_DU_OUTPUT_HDMI0] = "HDMI0",
[RCAR_DU_OUTPUT_HDMI1] = "HDMI1",
[RCAR_DU_OUTPUT_LVDS0] = "LVDS0",
[RCAR_DU_OUTPUT_LVDS1] = "LVDS1",
[RCAR_DU_OUTPUT_TCON] = "TCON",
};
if (output >= ARRAY_SIZE(names) || !names[output])
return "UNKNOWN";
return names[output];
}
/* -----------------------------------------------------------------------------
* DRM operations
*/
DEFINE_DRM_GEM_DMA_FOPS(rcar_du_fops);
static const struct drm_driver rcar_du_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
.dumb_create = rcar_du_dumb_create,
.gem_prime_import_sg_table = rcar_du_gem_prime_import_sg_table,
DRM_FBDEV_DMA_DRIVER_OPS,
.fops = &rcar_du_fops,
.name = "rcar-du",
.desc = "Renesas R-Car Display Unit",
.major = 1,
.minor = 0,
};
/* -----------------------------------------------------------------------------
* Power management
*/
static int rcar_du_pm_suspend(struct device *dev)
{
struct rcar_du_device *rcdu = dev_get_drvdata(dev);
return drm_mode_config_helper_suspend(&rcdu->ddev);
}
static int rcar_du_pm_resume(struct device *dev)
{
struct rcar_du_device *rcdu = dev_get_drvdata(dev);
return drm_mode_config_helper_resume(&rcdu->ddev);
}
static DEFINE_SIMPLE_DEV_PM_OPS(rcar_du_pm_ops,
rcar_du_pm_suspend, rcar_du_pm_resume);
/* -----------------------------------------------------------------------------
* Platform driver
*/
static void rcar_du_remove(struct platform_device *pdev)
{
struct rcar_du_device *rcdu = platform_get_drvdata(pdev);
struct drm_device *ddev = &rcdu->ddev;
drm_dev_unregister(ddev);
drm_atomic_helper_shutdown(ddev);
drm_kms_helper_poll_fini(ddev);
}
static void rcar_du_shutdown(struct platform_device *pdev)
{
struct rcar_du_device *rcdu = platform_get_drvdata(pdev);
drm_atomic_helper_shutdown(&rcdu->ddev);
}
static int rcar_du_probe(struct platform_device *pdev)
{
struct rcar_du_device *rcdu;
unsigned int mask;
int ret;
if (drm_firmware_drivers_only())
return -ENODEV;
/* Allocate and initialize the R-Car device structure. */
rcdu = devm_drm_dev_alloc(&pdev->dev, &rcar_du_driver,
struct rcar_du_device, ddev);
if (IS_ERR(rcdu))
return PTR_ERR(rcdu);
rcdu->dev = &pdev->dev;
rcdu->info = of_device_get_match_data(rcdu->dev);
platform_set_drvdata(pdev, rcdu);
/* I/O resources */
rcdu->mmio = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(rcdu->mmio))
return PTR_ERR(rcdu->mmio);
/*
* Set the DMA coherent mask to reflect the DU 32-bit DMA address space
* limitations. When sourcing frames from a VSP the DU doesn't perform
* any memory access so set the mask to 40 bits to accept all buffers.
*/
mask = rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE) ? 40 : 32;
ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(mask));
if (ret)
return ret;
/* DRM/KMS objects */
ret = rcar_du_modeset_init(rcdu);
if (ret < 0) {
/*
* Don't use dev_err_probe(), as it would overwrite the probe
* deferral reason recorded in rcar_du_modeset_init().
*/
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev,
"failed to initialize DRM/KMS (%d)\n", ret);
goto error;
}
/*
* Register the DRM device with the core and the connectors with
* sysfs.
*/
ret = drm_dev_register(&rcdu->ddev, 0);
if (ret)
goto error;
drm_info(&rcdu->ddev, "Device %s probed\n", dev_name(&pdev->dev));
drm_client_setup(&rcdu->ddev, NULL);
return 0;
error:
drm_kms_helper_poll_fini(&rcdu->ddev);
return ret;
}
static struct platform_driver rcar_du_platform_driver = {
.probe = rcar_du_probe,
.remove = rcar_du_remove,
.shutdown = rcar_du_shutdown,
.driver = {
.name = "rcar-du",
.pm = pm_sleep_ptr(&rcar_du_pm_ops),
.of_match_table = rcar_du_of_table,
},
};
module_platform_driver(rcar_du_platform_driver);
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,158 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* R-Car Display Unit DRM driver
*
* Copyright (C) 2013-2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __RCAR_DU_DRV_H__
#define __RCAR_DU_DRV_H__
#include <linux/kernel.h>
#include <linux/wait.h>
#include <drm/drm_device.h>
#include "rcar_cmm.h"
#include "rcar_du_crtc.h"
#include "rcar_du_group.h"
#include "rcar_du_vsp.h"
struct clk;
struct device;
struct device_link;
struct drm_bridge;
struct drm_property;
struct rcar_du_device;
#define RCAR_DU_FEATURE_CRTC_IRQ BIT(0) /* Per-CRTC IRQ */
#define RCAR_DU_FEATURE_CRTC_CLOCK BIT(1) /* Per-CRTC clock */
#define RCAR_DU_FEATURE_VSP1_SOURCE BIT(2) /* Has inputs from VSP1 */
#define RCAR_DU_FEATURE_INTERLACED BIT(3) /* HW supports interlaced */
#define RCAR_DU_FEATURE_TVM_SYNC BIT(4) /* Has TV switch/sync modes */
#define RCAR_DU_FEATURE_NO_BLENDING BIT(5) /* PnMR.SPIM does not have ALP nor EOR bits */
#define RCAR_DU_QUIRK_ALIGN_128B BIT(0) /* Align pitches to 128 bytes */
enum rcar_du_output {
RCAR_DU_OUTPUT_DPAD0,
RCAR_DU_OUTPUT_DPAD1,
RCAR_DU_OUTPUT_DSI0,
RCAR_DU_OUTPUT_DSI1,
RCAR_DU_OUTPUT_HDMI0,
RCAR_DU_OUTPUT_HDMI1,
RCAR_DU_OUTPUT_LVDS0,
RCAR_DU_OUTPUT_LVDS1,
RCAR_DU_OUTPUT_TCON,
RCAR_DU_OUTPUT_MAX,
};
/*
* struct rcar_du_output_routing - Output routing specification
* @possible_crtcs: bitmask of possible CRTCs for the output
* @port: device tree port number corresponding to this output route
*
* The DU has 5 possible outputs (DPAD0/1, LVDS0/1, TCON). Output routing data
* specify the valid SoC outputs, which CRTCs can drive the output, and the type
* of in-SoC encoder for the output.
*/
struct rcar_du_output_routing {
unsigned int possible_crtcs;
unsigned int port;
};
/*
* struct rcar_du_device_info - DU model-specific information
* @gen: device generation (2 or 3)
* @features: device features (RCAR_DU_FEATURE_*)
* @quirks: device quirks (RCAR_DU_QUIRK_*)
* @channels_mask: bit mask of available DU channels
* @routes: array of CRTC to output routes, indexed by output (RCAR_DU_OUTPUT_*)
* @num_lvds: number of internal LVDS encoders
* @num_rpf: number of RPFs in VSP
* @dpll_mask: bit mask of DU channels equipped with a DPLL
* @dsi_clk_mask: bitmask of channels that can use the DSI clock as dot clock
* @lvds_clk_mask: bitmask of channels that can use the LVDS clock as dot clock
*/
struct rcar_du_device_info {
unsigned int gen;
unsigned int features;
unsigned int quirks;
unsigned int channels_mask;
struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX];
unsigned int num_lvds;
unsigned int num_rpf;
unsigned int dpll_mask;
unsigned int dsi_clk_mask;
unsigned int lvds_clk_mask;
};
struct rcar_du_cmm {
struct device *dev;
struct device_link *link;
};
#define RCAR_DU_MAX_CRTCS 4
#define RCAR_DU_MAX_GROUPS DIV_ROUND_UP(RCAR_DU_MAX_CRTCS, 2)
#define RCAR_DU_MAX_VSPS 4
#define RCAR_DU_MAX_LVDS 2
#define RCAR_DU_MAX_DSI 2
struct rcar_du_device {
struct device *dev;
const struct rcar_du_device_info *info;
void __iomem *mmio;
struct drm_device ddev;
struct rcar_du_crtc crtcs[RCAR_DU_MAX_CRTCS];
unsigned int num_crtcs;
struct rcar_du_group groups[RCAR_DU_MAX_GROUPS];
struct rcar_du_cmm cmms[RCAR_DU_MAX_CRTCS];
struct rcar_du_vsp vsps[RCAR_DU_MAX_VSPS];
struct drm_bridge *lvds[RCAR_DU_MAX_LVDS];
struct drm_bridge *dsi[RCAR_DU_MAX_DSI];
struct {
struct drm_property *colorkey;
} props;
unsigned int dpad0_source;
unsigned int dpad1_source;
unsigned int vspd1_sink;
};
static inline struct rcar_du_device *to_rcar_du_device(struct drm_device *dev)
{
return container_of(dev, struct rcar_du_device, ddev);
}
static inline bool rcar_du_has(struct rcar_du_device *rcdu,
unsigned int feature)
{
return rcdu->info->features & feature;
}
static inline bool rcar_du_needs(struct rcar_du_device *rcdu,
unsigned int quirk)
{
return rcdu->info->quirks & quirk;
}
static inline u32 rcar_du_read(struct rcar_du_device *rcdu, u32 reg)
{
return ioread32(rcdu->mmio + reg);
}
static inline void rcar_du_write(struct rcar_du_device *rcdu, u32 reg, u32 data)
{
iowrite32(data, rcdu->mmio + reg);
}
const char *rcar_du_output_name(enum rcar_du_output output);
#endif /* __RCAR_DU_DRV_H__ */
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* R-Car Display Unit Encoder
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/export.h>
#include <linux/of.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_panel.h>
#include "rcar_du_drv.h"
#include "rcar_du_encoder.h"
#include "rcar_lvds.h"
/* -----------------------------------------------------------------------------
* Encoder
*/
static unsigned int rcar_du_encoder_count_ports(struct device_node *node)
{
struct device_node *ports;
struct device_node *port;
unsigned int num_ports = 0;
ports = of_get_child_by_name(node, "ports");
if (!ports)
ports = of_node_get(node);
for_each_child_of_node(ports, port) {
if (of_node_name_eq(port, "port"))
num_ports++;
}
of_node_put(ports);
return num_ports;
}
static const struct drm_encoder_funcs rcar_du_encoder_funcs = {
};
int rcar_du_encoder_init(struct rcar_du_device *rcdu,
enum rcar_du_output output,
struct device_node *enc_node)
{
struct rcar_du_encoder *renc;
struct drm_connector *connector;
struct drm_bridge *bridge __free(drm_bridge_put) = NULL;
int ret;
/*
* Locate the DRM bridge from the DT node. For the DPAD outputs, if the
* DT node has a single port, assume that it describes a panel and
* create a panel bridge.
*/
if ((output == RCAR_DU_OUTPUT_DPAD0 ||
output == RCAR_DU_OUTPUT_DPAD1) &&
rcar_du_encoder_count_ports(enc_node) == 1) {
struct drm_panel *panel = of_drm_find_panel(enc_node);
if (IS_ERR(panel))
return PTR_ERR(panel);
bridge = devm_drm_panel_bridge_add_typed(rcdu->dev, panel,
DRM_MODE_CONNECTOR_DPI);
if (IS_ERR(bridge))
return PTR_ERR(no_free_ptr(bridge));
/*
* The reference taken by devm_drm_panel_bridge_add_typed() is
* released automatically. Take a second one for the __free()
* when this function will return.
*/
drm_bridge_get(bridge);
} else {
bridge = of_drm_find_and_get_bridge(enc_node);
if (!bridge)
return -EPROBE_DEFER;
if (output == RCAR_DU_OUTPUT_LVDS0 ||
output == RCAR_DU_OUTPUT_LVDS1)
rcdu->lvds[output - RCAR_DU_OUTPUT_LVDS0] =
drm_bridge_get(bridge);
if (output == RCAR_DU_OUTPUT_DSI0 ||
output == RCAR_DU_OUTPUT_DSI1)
rcdu->dsi[output - RCAR_DU_OUTPUT_DSI0] =
drm_bridge_get(bridge);
}
/*
* Create and initialize the encoder. On Gen3, skip the LVDS1 output if
* the LVDS1 encoder is used as a companion for LVDS0 in dual-link
* mode, or any LVDS output if it isn't connected. The latter may happen
* on D3 or E3 as the LVDS encoders are needed to provide the pixel
* clock to the DU, even when the LVDS outputs are not used.
*/
if (rcdu->info->gen >= 3) {
if (output == RCAR_DU_OUTPUT_LVDS1 &&
rcar_lvds_dual_link(bridge))
return -ENOLINK;
if ((output == RCAR_DU_OUTPUT_LVDS0 ||
output == RCAR_DU_OUTPUT_LVDS1) &&
!rcar_lvds_is_connected(bridge))
return -ENOLINK;
}
dev_dbg(rcdu->dev, "initializing encoder %pOF for output %s\n",
enc_node, rcar_du_output_name(output));
renc = drmm_encoder_alloc(&rcdu->ddev, struct rcar_du_encoder, base,
&rcar_du_encoder_funcs, DRM_MODE_ENCODER_NONE,
NULL);
if (IS_ERR(renc))
return PTR_ERR(renc);
renc->output = output;
/* Attach the bridge to the encoder. */
ret = drm_bridge_attach(&renc->base, bridge, NULL,
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret) {
dev_err(rcdu->dev,
"failed to attach bridge %pOF for output %s (%d)\n",
bridge->of_node, rcar_du_output_name(output), ret);
return ret;
}
/* Create the connector for the chain of bridges. */
connector = drm_bridge_connector_init(&rcdu->ddev, &renc->base);
if (IS_ERR(connector)) {
dev_err(rcdu->dev,
"failed to created connector for output %s (%ld)\n",
rcar_du_output_name(output), PTR_ERR(connector));
return PTR_ERR(connector);
}
return 0;
}
void rcar_du_encoder_cleanup(struct rcar_du_device *rcdu)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(rcdu->lvds); i++)
drm_bridge_put(rcdu->lvds[i]);
for (i = 0; i < ARRAY_SIZE(rcdu->dsi); i++)
drm_bridge_put(rcdu->dsi[i]);
}
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* R-Car Display Unit Encoder
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __RCAR_DU_ENCODER_H__
#define __RCAR_DU_ENCODER_H__
#include <drm/drm_encoder.h>
struct rcar_du_device;
struct rcar_du_encoder {
struct drm_encoder base;
enum rcar_du_output output;
};
#define to_rcar_encoder(e) \
container_of(e, struct rcar_du_encoder, base)
int rcar_du_encoder_init(struct rcar_du_device *rcdu,
enum rcar_du_output output,
struct device_node *enc_node);
void rcar_du_encoder_cleanup(struct rcar_du_device *rcdu);
#endif /* __RCAR_DU_ENCODER_H__ */
@@ -0,0 +1,389 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* R-Car Display Unit Channels Pair
*
* Copyright (C) 2013-2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
/*
* The R8A7779 DU is split in per-CRTC resources (scan-out engine, blending
* unit, timings generator, ...) and device-global resources (start/stop
* control, planes, ...) shared between the two CRTCs.
*
* The R8A7790 introduced a third CRTC with its own set of global resources.
* This would be modeled as two separate DU device instances if it wasn't for
* a handful or resources that are shared between the three CRTCs (mostly
* related to input and output routing). For this reason the R8A7790 DU must be
* modeled as a single device with three CRTCs, two sets of "semi-global"
* resources, and a few device-global resources.
*
* The rcar_du_group object is a driver specific object, without any real
* counterpart in the DU documentation, that models those semi-global resources.
*/
#include <linux/clk.h>
#include <linux/io.h>
#include "rcar_du_drv.h"
#include "rcar_du_group.h"
#include "rcar_du_regs.h"
u32 rcar_du_group_read(struct rcar_du_group *rgrp, u32 reg)
{
return rcar_du_read(rgrp->dev, rgrp->mmio_offset + reg);
}
void rcar_du_group_write(struct rcar_du_group *rgrp, u32 reg, u32 data)
{
rcar_du_write(rgrp->dev, rgrp->mmio_offset + reg, data);
}
static void rcar_du_group_setup_pins(struct rcar_du_group *rgrp)
{
u32 defr6 = DEFR6_CODE;
if (rgrp->channels_mask & BIT(0))
defr6 |= DEFR6_ODPM02_DISP;
if (rgrp->channels_mask & BIT(1))
defr6 |= DEFR6_ODPM12_DISP;
rcar_du_group_write(rgrp, DEFR6, defr6);
}
static void rcar_du_group_setup_defr8(struct rcar_du_group *rgrp)
{
struct rcar_du_device *rcdu = rgrp->dev;
u32 defr8 = DEFR8_CODE;
if (rcdu->info->gen < 3) {
defr8 |= DEFR8_DEFE8;
/*
* On Gen2 the DEFR8 register for the first group also controls
* RGB output routing to DPAD0 and VSPD1 routing to DU0/1/2 for
* DU instances that support it.
*/
if (rgrp->index == 0) {
defr8 |= DEFR8_DRGBS_DU(rcdu->dpad0_source);
if (rgrp->dev->vspd1_sink == 2)
defr8 |= DEFR8_VSCS;
}
} else {
/*
* On Gen3 VSPD routing can't be configured, and DPAD routing
* is set in the group corresponding to the DPAD output (no Gen3
* SoC has multiple DPAD sources belonging to separate groups).
*/
if (rgrp->index == rcdu->dpad0_source / 2)
defr8 |= DEFR8_DRGBS_DU(rcdu->dpad0_source);
}
rcar_du_group_write(rgrp, DEFR8, defr8);
}
static void rcar_du_group_setup_didsr(struct rcar_du_group *rgrp)
{
struct rcar_du_device *rcdu = rgrp->dev;
struct rcar_du_crtc *rcrtc;
unsigned int num_crtcs = 0;
unsigned int i;
u32 didsr;
/*
* Configure input dot clock routing with a hardcoded configuration. If
* the DU channel can use the LVDS encoder output clock as the dot
* clock, do so. Otherwise route DU_DOTCLKINn signal to DUn.
*
* Each channel can then select between the dot clock configured here
* and the clock provided by the CPG through the ESCR register.
*/
if (rcdu->info->gen < 3 && rgrp->index == 0) {
/*
* On Gen2 a single register in the first group controls dot
* clock selection for all channels.
*/
rcrtc = rcdu->crtcs;
num_crtcs = rcdu->num_crtcs;
} else if ((rcdu->info->gen == 3 && rgrp->num_crtcs > 1) ||
rcdu->info->gen == 4) {
/*
* On Gen3 dot clocks are setup through per-group registers,
* only available when the group has two channels.
* On Gen4 the registers are there for single channel too.
*/
rcrtc = &rcdu->crtcs[rgrp->index * 2];
num_crtcs = rgrp->num_crtcs;
}
if (!num_crtcs)
return;
didsr = DIDSR_CODE;
for (i = 0; i < num_crtcs; ++i, ++rcrtc) {
if (rcdu->info->lvds_clk_mask & BIT(rcrtc->index))
didsr |= DIDSR_LDCS_LVDS0(i)
| DIDSR_PDCS_CLK(i, 0);
else if (rcdu->info->dsi_clk_mask & BIT(rcrtc->index))
didsr |= DIDSR_LDCS_DSI(i);
else
didsr |= DIDSR_LDCS_DCLKIN(i)
| DIDSR_PDCS_CLK(i, 0);
}
rcar_du_group_write(rgrp, DIDSR, didsr);
}
static void rcar_du_group_setup(struct rcar_du_group *rgrp)
{
struct rcar_du_device *rcdu = rgrp->dev;
u32 defr7 = DEFR7_CODE;
u32 dorcr;
/* Enable extended features */
rcar_du_group_write(rgrp, DEFR, DEFR_CODE | DEFR_DEFE);
if (rcdu->info->gen < 3) {
rcar_du_group_write(rgrp, DEFR2, DEFR2_CODE | DEFR2_DEFE2G);
rcar_du_group_write(rgrp, DEFR3, DEFR3_CODE | DEFR3_DEFE3);
rcar_du_group_write(rgrp, DEFR4, DEFR4_CODE);
}
rcar_du_group_write(rgrp, DEFR5, DEFR5_CODE | DEFR5_DEFE5);
if (rcdu->info->gen < 4)
rcar_du_group_setup_pins(rgrp);
if (rcdu->info->gen < 4) {
/*
* TODO: Handle routing of the DU output to CMM dynamically, as
* we should bypass CMM completely when no color management
* feature is used.
*/
defr7 |= (rgrp->cmms_mask & BIT(1) ? DEFR7_CMME1 : 0) |
(rgrp->cmms_mask & BIT(0) ? DEFR7_CMME0 : 0);
rcar_du_group_write(rgrp, DEFR7, defr7);
}
if (rcdu->info->gen >= 2) {
if (rcdu->info->gen < 4)
rcar_du_group_setup_defr8(rgrp);
rcar_du_group_setup_didsr(rgrp);
}
if (rcdu->info->gen >= 3)
rcar_du_group_write(rgrp, DEFR10, DEFR10_CODE | DEFR10_DEFE10);
/*
* Use DS1PR and DS2PR to configure planes priorities and connects the
* superposition 0 to DU0 pins. DU1 pins will be configured dynamically.
*
* Groups that have a single channel have a hardcoded configuration. On
* Gen3 and newer, the documentation requires PG1T, DK1S and PG1D_DS1 to
* always be set in this case.
*/
dorcr = DORCR_PG0D_DS0 | DORCR_DPRS;
if (rcdu->info->gen >= 3 && rgrp->num_crtcs == 1)
dorcr |= DORCR_PG1T | DORCR_DK1S | DORCR_PG1D_DS1;
rcar_du_group_write(rgrp, DORCR, dorcr);
/*
* DPTSR is used to select the source for the planes of a group. The
* first source is chosen by writing 0 to the respective bits, and this
* is always the default value of the register. In other words, writing
* DPTSR is only needed if the SoC supports choosing the second source.
*
* The SoCs documentations seems to confirm this, as the DPTSR register
* is not documented if only the first source exists on that SoC.
*/
if (rgrp->channels_mask & BIT(1)) {
mutex_lock(&rgrp->lock);
rcar_du_group_write(rgrp, DPTSR, (rgrp->dptsr_planes << 16) |
rgrp->dptsr_planes);
mutex_unlock(&rgrp->lock);
}
}
/*
* rcar_du_group_get - Acquire a reference to the DU channels group
*
* Acquiring the first reference setups core registers. A reference must be held
* before accessing any hardware registers.
*
* This function must be called with the DRM mode_config lock held.
*
* Return 0 in case of success or a negative error code otherwise.
*/
int rcar_du_group_get(struct rcar_du_group *rgrp)
{
if (rgrp->use_count)
goto done;
rcar_du_group_setup(rgrp);
done:
rgrp->use_count++;
return 0;
}
/*
* rcar_du_group_put - Release a reference to the DU
*
* This function must be called with the DRM mode_config lock held.
*/
void rcar_du_group_put(struct rcar_du_group *rgrp)
{
--rgrp->use_count;
}
static void __rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start)
{
struct rcar_du_device *rcdu = rgrp->dev;
/*
* Group start/stop is controlled by the DRES and DEN bits of DSYSR0
* for the first group and DSYSR2 for the second group. On most DU
* instances, this maps to the first CRTC of the group, and we can just
* use rcar_du_crtc_dsysr_clr_set() to access the correct DSYSR. On
* M3-N, however, DU2 doesn't exist, but DSYSR2 does. We thus need to
* access the register directly using group read/write.
*/
if (rcdu->info->channels_mask & BIT(rgrp->index * 2)) {
struct rcar_du_crtc *rcrtc = &rgrp->dev->crtcs[rgrp->index * 2];
rcar_du_crtc_dsysr_clr_set(rcrtc, DSYSR_DRES | DSYSR_DEN,
start ? DSYSR_DEN : DSYSR_DRES);
} else {
rcar_du_group_write(rgrp, DSYSR,
start ? DSYSR_DEN : DSYSR_DRES);
}
}
void rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start)
{
/*
* Many of the configuration bits are only updated when the display
* reset (DRES) bit in DSYSR is set to 1, disabling *both* CRTCs. Some
* of those bits could be pre-configured, but others (especially the
* bits related to plane assignment to display timing controllers) need
* to be modified at runtime.
*
* Restart the display controller if a start is requested. Sorry for the
* flicker. It should be possible to move most of the "DRES-update" bits
* setup to driver initialization time and minimize the number of cases
* when the display controller will have to be restarted.
*/
if (start) {
if (rgrp->used_crtcs++ != 0)
__rcar_du_group_start_stop(rgrp, false);
__rcar_du_group_start_stop(rgrp, true);
} else {
if (--rgrp->used_crtcs == 0)
__rcar_du_group_start_stop(rgrp, false);
}
}
void rcar_du_group_restart(struct rcar_du_group *rgrp)
{
rgrp->need_restart = false;
__rcar_du_group_start_stop(rgrp, false);
__rcar_du_group_start_stop(rgrp, true);
}
int rcar_du_set_dpad0_vsp1_routing(struct rcar_du_device *rcdu)
{
struct rcar_du_group *rgrp;
struct rcar_du_crtc *crtc;
unsigned int index;
int ret;
if (rcdu->info->gen < 2)
return 0;
/*
* RGB output routing to DPAD0 and VSP1D routing to DU0/1/2 are
* configured in the DEFR8 register of the first group on Gen2 and the
* last group on Gen3. As this function can be called with the DU
* channels of the corresponding CRTCs disabled, we need to enable the
* group clock before accessing the register.
*/
index = rcdu->info->gen < 3 ? 0 : DIV_ROUND_UP(rcdu->num_crtcs, 2) - 1;
rgrp = &rcdu->groups[index];
crtc = &rcdu->crtcs[index * 2];
ret = clk_prepare_enable(crtc->clock);
if (ret < 0)
return ret;
rcar_du_group_setup_defr8(rgrp);
clk_disable_unprepare(crtc->clock);
return 0;
}
static void rcar_du_group_set_dpad_levels(struct rcar_du_group *rgrp)
{
static const u32 doflr_values[2] = {
DOFLR_HSYCFL0 | DOFLR_VSYCFL0 | DOFLR_ODDFL0 |
DOFLR_DISPFL0 | DOFLR_CDEFL0 | DOFLR_RGBFL0,
DOFLR_HSYCFL1 | DOFLR_VSYCFL1 | DOFLR_ODDFL1 |
DOFLR_DISPFL1 | DOFLR_CDEFL1 | DOFLR_RGBFL1,
};
static const u32 dpad_mask = BIT(RCAR_DU_OUTPUT_DPAD1)
| BIT(RCAR_DU_OUTPUT_DPAD0);
struct rcar_du_device *rcdu = rgrp->dev;
u32 doflr = DOFLR_CODE;
unsigned int i;
if (rcdu->info->gen < 2)
return;
/*
* The DPAD outputs can't be controlled directly. However, the parallel
* output of the DU channels routed to DPAD can be set to fixed levels
* through the DOFLR group register. Use this to turn the DPAD on or off
* by driving fixed low-level signals at the output of any DU channel
* not routed to a DPAD output. This doesn't affect the DU output
* signals going to other outputs, such as the internal LVDS and HDMI
* encoders.
*/
for (i = 0; i < rgrp->num_crtcs; ++i) {
struct rcar_du_crtc_state *rstate;
struct rcar_du_crtc *rcrtc;
rcrtc = &rcdu->crtcs[rgrp->index * 2 + i];
rstate = to_rcar_crtc_state(rcrtc->crtc.state);
if (!(rstate->outputs & dpad_mask))
doflr |= doflr_values[i];
}
rcar_du_group_write(rgrp, DOFLR, doflr);
}
int rcar_du_group_set_routing(struct rcar_du_group *rgrp)
{
struct rcar_du_device *rcdu = rgrp->dev;
u32 dorcr = rcar_du_group_read(rgrp, DORCR);
dorcr &= ~(DORCR_PG1T | DORCR_DK1S | DORCR_PG1D_MASK);
/*
* Set the DPAD1 pins sources. Select CRTC 0 if explicitly requested and
* CRTC 1 in all other cases to avoid cloning CRTC 0 to DPAD0 and DPAD1
* by default.
*/
if (rcdu->dpad1_source == rgrp->index * 2)
dorcr |= DORCR_PG1D_DS0;
else
dorcr |= DORCR_PG1T | DORCR_DK1S | DORCR_PG1D_DS1;
rcar_du_group_write(rgrp, DORCR, dorcr);
rcar_du_group_set_dpad_levels(rgrp);
return rcar_du_set_dpad0_vsp1_routing(rgrp->dev);
}
@@ -0,0 +1,65 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* R-Car Display Unit Planes and CRTCs Group
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __RCAR_DU_GROUP_H__
#define __RCAR_DU_GROUP_H__
#include <linux/mutex.h>
#include "rcar_du_plane.h"
struct rcar_du_device;
/*
* struct rcar_du_group - CRTCs and planes group
* @dev: the DU device
* @mmio_offset: registers offset in the device memory map
* @index: group index
* @channels_mask: bitmask of populated DU channels in this group
* @cmms_mask: bitmask of available CMMs in this group
* @num_crtcs: number of CRTCs in this group (1 or 2)
* @use_count: number of users of the group (rcar_du_group_(get|put))
* @used_crtcs: number of CRTCs currently in use
* @lock: protects the dptsr_planes field and the DPTSR register
* @dptsr_planes: bitmask of planes driven by dot-clock and timing generator 1
* @num_planes: number of planes in the group
* @planes: planes handled by the group
* @need_restart: the group needs to be restarted due to a configuration change
*/
struct rcar_du_group {
struct rcar_du_device *dev;
unsigned int mmio_offset;
unsigned int index;
unsigned int channels_mask;
unsigned int cmms_mask;
unsigned int num_crtcs;
unsigned int use_count;
unsigned int used_crtcs;
struct mutex lock;
unsigned int dptsr_planes;
unsigned int num_planes;
struct rcar_du_plane planes[RCAR_DU_NUM_KMS_PLANES];
bool need_restart;
};
u32 rcar_du_group_read(struct rcar_du_group *rgrp, u32 reg);
void rcar_du_group_write(struct rcar_du_group *rgrp, u32 reg, u32 data);
int rcar_du_group_get(struct rcar_du_group *rgrp);
void rcar_du_group_put(struct rcar_du_group *rgrp);
void rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start);
void rcar_du_group_restart(struct rcar_du_group *rgrp);
int rcar_du_group_set_routing(struct rcar_du_group *rgrp);
int rcar_du_set_dpad0_vsp1_routing(struct rcar_du_device *rcdu);
#endif /* __RCAR_DU_GROUP_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,44 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* R-Car Display Unit Mode Setting
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __RCAR_DU_KMS_H__
#define __RCAR_DU_KMS_H__
#include <linux/types.h>
struct dma_buf_attachment;
struct drm_file;
struct drm_device;
struct drm_gem_object;
struct drm_mode_create_dumb;
struct rcar_du_device;
struct sg_table;
struct rcar_du_format_info {
u32 fourcc;
u32 v4l2;
unsigned int bpp;
unsigned int planes;
unsigned int hsub;
unsigned int pnmr;
unsigned int edf;
};
const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc);
int rcar_du_modeset_init(struct rcar_du_device *rcdu);
int rcar_du_dumb_create(struct drm_file *file, struct drm_device *dev,
struct drm_mode_create_dumb *args);
struct drm_gem_object *rcar_du_gem_prime_import_sg_table(struct drm_device *dev,
struct dma_buf_attachment *attach,
struct sg_table *sgt);
#endif /* __RCAR_DU_KMS_H__ */
@@ -0,0 +1,842 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* R-Car Display Unit Planes
*
* Copyright (C) 2013-2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_blend.h>
#include <drm/drm_crtc.h>
#include <drm/drm_device.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include "rcar_du_drv.h"
#include "rcar_du_group.h"
#include "rcar_du_kms.h"
#include "rcar_du_plane.h"
#include "rcar_du_regs.h"
/* -----------------------------------------------------------------------------
* Atomic hardware plane allocator
*
* The hardware plane allocator is solely based on the atomic plane states
* without keeping any external state to avoid races between .atomic_check()
* and .atomic_commit().
*
* The core idea is to avoid using a free planes bitmask that would need to be
* shared between check and commit handlers with a collective knowledge based on
* the allocated hardware plane(s) for each KMS plane. The allocator then loops
* over all plane states to compute the free planes bitmask, allocates hardware
* planes based on that bitmask, and stores the result back in the plane states.
*
* For this to work we need to access the current state of planes not touched by
* the atomic update. To ensure that it won't be modified, we need to lock all
* planes using drm_atomic_get_plane_state(). This effectively serializes atomic
* updates from .atomic_check() up to completion (when swapping the states if
* the check step has succeeded) or rollback (when freeing the states if the
* check step has failed).
*
* Allocation is performed in the .atomic_check() handler and applied
* automatically when the core swaps the old and new states.
*/
static bool rcar_du_plane_needs_realloc(
const struct rcar_du_plane_state *old_state,
const struct rcar_du_plane_state *new_state)
{
/*
* Lowering the number of planes doesn't strictly require reallocation
* as the extra hardware plane will be freed when committing, but doing
* so could lead to more fragmentation.
*/
if (!old_state->format ||
old_state->format->planes != new_state->format->planes)
return true;
/* Reallocate hardware planes if the source has changed. */
if (old_state->source != new_state->source)
return true;
return false;
}
static unsigned int rcar_du_plane_hwmask(struct rcar_du_plane_state *state)
{
unsigned int mask;
if (state->hwindex == -1)
return 0;
mask = 1 << state->hwindex;
if (state->format->planes == 2)
mask |= 1 << ((state->hwindex + 1) % 8);
return mask;
}
/*
* The R8A7790 DU can source frames directly from the VSP1 devices VSPD0 and
* VSPD1. VSPD0 feeds DU0/1 plane 0, and VSPD1 feeds either DU2 plane 0 or
* DU0/1 plane 1.
*
* Allocate the correct fixed plane when sourcing frames from VSPD0 or VSPD1,
* and allocate planes in reverse index order otherwise to ensure maximum
* availability of planes 0 and 1.
*
* The caller is responsible for ensuring that the requested source is
* compatible with the DU revision.
*/
static int rcar_du_plane_hwalloc(struct rcar_du_plane *plane,
struct rcar_du_plane_state *state,
unsigned int free)
{
unsigned int num_planes = state->format->planes;
int fixed = -1;
int i;
if (state->source == RCAR_DU_PLANE_VSPD0) {
/* VSPD0 feeds plane 0 on DU0/1. */
if (plane->group->index != 0)
return -EINVAL;
fixed = 0;
} else if (state->source == RCAR_DU_PLANE_VSPD1) {
/* VSPD1 feeds plane 1 on DU0/1 or plane 0 on DU2. */
fixed = plane->group->index == 0 ? 1 : 0;
}
if (fixed >= 0)
return free & (1 << fixed) ? fixed : -EBUSY;
for (i = RCAR_DU_NUM_HW_PLANES - 1; i >= 0; --i) {
if (!(free & (1 << i)))
continue;
if (num_planes == 1 || free & (1 << ((i + 1) % 8)))
break;
}
return i < 0 ? -EBUSY : i;
}
int rcar_du_atomic_check_planes(struct drm_device *dev,
struct drm_atomic_commit *state)
{
struct rcar_du_device *rcdu = to_rcar_du_device(dev);
unsigned int group_freed_planes[RCAR_DU_MAX_GROUPS] = { 0, };
unsigned int group_free_planes[RCAR_DU_MAX_GROUPS] = { 0, };
bool needs_realloc = false;
unsigned int groups = 0;
unsigned int i;
struct drm_plane *drm_plane;
struct drm_plane_state *old_drm_plane_state;
struct drm_plane_state *new_drm_plane_state;
/* Check if hardware planes need to be reallocated. */
for_each_oldnew_plane_in_state(state, drm_plane, old_drm_plane_state,
new_drm_plane_state, i) {
struct rcar_du_plane_state *old_plane_state;
struct rcar_du_plane_state *new_plane_state;
struct rcar_du_plane *plane;
unsigned int index;
plane = to_rcar_plane(drm_plane);
old_plane_state = to_rcar_plane_state(old_drm_plane_state);
new_plane_state = to_rcar_plane_state(new_drm_plane_state);
dev_dbg(rcdu->dev, "%s: checking plane (%u,%tu)\n", __func__,
plane->group->index, plane - plane->group->planes);
/*
* If the plane is being disabled we don't need to go through
* the full reallocation procedure. Just mark the hardware
* plane(s) as freed.
*/
if (!new_plane_state->format) {
dev_dbg(rcdu->dev, "%s: plane is being disabled\n",
__func__);
index = plane - plane->group->planes;
group_freed_planes[plane->group->index] |= 1 << index;
new_plane_state->hwindex = -1;
continue;
}
/*
* If the plane needs to be reallocated mark it as such, and
* mark the hardware plane(s) as free.
*/
if (rcar_du_plane_needs_realloc(old_plane_state, new_plane_state)) {
dev_dbg(rcdu->dev, "%s: plane needs reallocation\n",
__func__);
groups |= 1 << plane->group->index;
needs_realloc = true;
index = plane - plane->group->planes;
group_freed_planes[plane->group->index] |= 1 << index;
new_plane_state->hwindex = -1;
}
}
if (!needs_realloc)
return 0;
/*
* Grab all plane states for the groups that need reallocation to ensure
* locking and avoid racy updates. This serializes the update operation,
* but there's not much we can do about it as that's the hardware
* design.
*
* Compute the used planes mask for each group at the same time to avoid
* looping over the planes separately later.
*/
while (groups) {
unsigned int index = ffs(groups) - 1;
struct rcar_du_group *group = &rcdu->groups[index];
unsigned int used_planes = 0;
dev_dbg(rcdu->dev, "%s: finding free planes for group %u\n",
__func__, index);
for (i = 0; i < group->num_planes; ++i) {
struct rcar_du_plane *plane = &group->planes[i];
struct rcar_du_plane_state *new_plane_state;
struct drm_plane_state *s;
s = drm_atomic_get_plane_state(state, &plane->plane);
if (IS_ERR(s))
return PTR_ERR(s);
/*
* If the plane has been freed in the above loop its
* hardware planes must not be added to the used planes
* bitmask. However, the current state doesn't reflect
* the free state yet, as we've modified the new state
* above. Use the local freed planes list to check for
* that condition instead.
*/
if (group_freed_planes[index] & (1 << i)) {
dev_dbg(rcdu->dev,
"%s: plane (%u,%tu) has been freed, skipping\n",
__func__, plane->group->index,
plane - plane->group->planes);
continue;
}
new_plane_state = to_rcar_plane_state(s);
used_planes |= rcar_du_plane_hwmask(new_plane_state);
dev_dbg(rcdu->dev,
"%s: plane (%u,%tu) uses %u hwplanes (index %d)\n",
__func__, plane->group->index,
plane - plane->group->planes,
new_plane_state->format ?
new_plane_state->format->planes : 0,
new_plane_state->hwindex);
}
group_free_planes[index] = 0xff & ~used_planes;
groups &= ~(1 << index);
dev_dbg(rcdu->dev, "%s: group %u free planes mask 0x%02x\n",
__func__, index, group_free_planes[index]);
}
/* Reallocate hardware planes for each plane that needs it. */
for_each_oldnew_plane_in_state(state, drm_plane, old_drm_plane_state,
new_drm_plane_state, i) {
struct rcar_du_plane_state *old_plane_state;
struct rcar_du_plane_state *new_plane_state;
struct rcar_du_plane *plane;
unsigned int crtc_planes;
unsigned int free;
int idx;
plane = to_rcar_plane(drm_plane);
old_plane_state = to_rcar_plane_state(old_drm_plane_state);
new_plane_state = to_rcar_plane_state(new_drm_plane_state);
dev_dbg(rcdu->dev, "%s: allocating plane (%u,%tu)\n", __func__,
plane->group->index, plane - plane->group->planes);
/*
* Skip planes that are being disabled or don't need to be
* reallocated.
*/
if (!new_plane_state->format ||
!rcar_du_plane_needs_realloc(old_plane_state, new_plane_state))
continue;
/*
* Try to allocate the plane from the free planes currently
* associated with the target CRTC to avoid restarting the CRTC
* group and thus minimize flicker. If it fails fall back to
* allocating from all free planes.
*/
crtc_planes = to_rcar_crtc(new_plane_state->state.crtc)->index % 2
? plane->group->dptsr_planes
: ~plane->group->dptsr_planes;
free = group_free_planes[plane->group->index];
idx = rcar_du_plane_hwalloc(plane, new_plane_state,
free & crtc_planes);
if (idx < 0)
idx = rcar_du_plane_hwalloc(plane, new_plane_state,
free);
if (idx < 0) {
dev_dbg(rcdu->dev, "%s: no available hardware plane\n",
__func__);
return idx;
}
dev_dbg(rcdu->dev, "%s: allocated %u hwplanes (index %u)\n",
__func__, new_plane_state->format->planes, idx);
new_plane_state->hwindex = idx;
group_free_planes[plane->group->index] &=
~rcar_du_plane_hwmask(new_plane_state);
dev_dbg(rcdu->dev, "%s: group %u free planes mask 0x%02x\n",
__func__, plane->group->index,
group_free_planes[plane->group->index]);
}
return 0;
}
/* -----------------------------------------------------------------------------
* Plane Setup
*/
#define RCAR_DU_COLORKEY_NONE (0 << 24)
#define RCAR_DU_COLORKEY_SOURCE (1 << 24)
#define RCAR_DU_COLORKEY_MASK (1 << 24)
static void rcar_du_plane_write(struct rcar_du_group *rgrp,
unsigned int index, u32 reg, u32 data)
{
rcar_du_write(rgrp->dev, rgrp->mmio_offset + index * PLANE_OFF + reg,
data);
}
static void rcar_du_plane_setup_scanout(struct rcar_du_group *rgrp,
const struct rcar_du_plane_state *state)
{
unsigned int src_x = state->state.src.x1 >> 16;
unsigned int src_y = state->state.src.y1 >> 16;
unsigned int index = state->hwindex;
unsigned int pitch;
bool interlaced;
u32 dma[2];
interlaced = state->state.crtc->state->adjusted_mode.flags
& DRM_MODE_FLAG_INTERLACE;
if (state->source == RCAR_DU_PLANE_MEMORY) {
struct drm_framebuffer *fb = state->state.fb;
struct drm_gem_dma_object *gem;
unsigned int i;
if (state->format->planes == 2)
pitch = fb->pitches[0];
else
pitch = fb->pitches[0] * 8 / state->format->bpp;
for (i = 0; i < state->format->planes; ++i) {
gem = drm_fb_dma_get_gem_obj(fb, i);
dma[i] = gem->dma_addr + fb->offsets[i];
}
} else {
pitch = drm_rect_width(&state->state.src) >> 16;
dma[0] = 0;
dma[1] = 0;
}
/*
* Memory pitch (expressed in pixels). Must be doubled for interlaced
* operation with 32bpp formats.
*/
rcar_du_plane_write(rgrp, index, PnMWR,
(interlaced && state->format->bpp == 32) ?
pitch * 2 : pitch);
/*
* The Y position is expressed in raster line units and must be doubled
* for 32bpp formats, according to the R8A7790 datasheet. No mention of
* doubling the Y position is found in the R8A7779 datasheet, but the
* rule seems to apply there as well.
*
* Despite not being documented, doubling seem not to be needed when
* operating in interlaced mode.
*
* Similarly, for the second plane, NV12 and NV21 formats seem to
* require a halved Y position value, in both progressive and interlaced
* modes.
*/
rcar_du_plane_write(rgrp, index, PnSPXR, src_x);
rcar_du_plane_write(rgrp, index, PnSPYR, src_y *
(!interlaced && state->format->bpp == 32 ? 2 : 1));
rcar_du_plane_write(rgrp, index, PnDSA0R, dma[0]);
if (state->format->planes == 2) {
index = (index + 1) % 8;
rcar_du_plane_write(rgrp, index, PnMWR, pitch);
rcar_du_plane_write(rgrp, index, PnSPXR, src_x);
rcar_du_plane_write(rgrp, index, PnSPYR, src_y *
(state->format->bpp == 16 ? 2 : 1) / 2);
rcar_du_plane_write(rgrp, index, PnDSA0R, dma[1]);
}
}
static void rcar_du_plane_setup_mode(struct rcar_du_group *rgrp,
unsigned int index,
const struct rcar_du_plane_state *state)
{
u32 colorkey;
u32 pnmr;
/*
* The PnALPHAR register controls alpha-blending in 16bpp formats
* (ARGB1555 and XRGB1555).
*
* For ARGB, set the alpha value to 0, and enable alpha-blending when
* the A bit is 0. This maps A=0 to alpha=0 and A=1 to alpha=255.
*
* For XRGB, set the alpha value to the plane-wide alpha value and
* enable alpha-blending regardless of the X bit value.
*/
if (state->format->fourcc != DRM_FORMAT_XRGB1555)
rcar_du_plane_write(rgrp, index, PnALPHAR, PnALPHAR_ABIT_0);
else
rcar_du_plane_write(rgrp, index, PnALPHAR,
PnALPHAR_ABIT_X | state->state.alpha >> 8);
pnmr = PnMR_BM_MD | state->format->pnmr;
/*
* Disable color keying when requested. YUV formats have the
* PnMR_SPIM_TP_OFF bit set in their pnmr field, disabling color keying
* automatically.
*/
if ((state->colorkey & RCAR_DU_COLORKEY_MASK) == RCAR_DU_COLORKEY_NONE)
pnmr |= PnMR_SPIM_TP_OFF;
/* For packed YUV formats we need to select the U/V order. */
if (state->format->fourcc == DRM_FORMAT_YUYV)
pnmr |= PnMR_YCDF_YUYV;
rcar_du_plane_write(rgrp, index, PnMR, pnmr);
switch (state->format->fourcc) {
case DRM_FORMAT_RGB565:
colorkey = ((state->colorkey & 0xf80000) >> 8)
| ((state->colorkey & 0x00fc00) >> 5)
| ((state->colorkey & 0x0000f8) >> 3);
rcar_du_plane_write(rgrp, index, PnTC2R, colorkey);
break;
case DRM_FORMAT_ARGB1555:
case DRM_FORMAT_XRGB1555:
colorkey = ((state->colorkey & 0xf80000) >> 9)
| ((state->colorkey & 0x00f800) >> 6)
| ((state->colorkey & 0x0000f8) >> 3);
rcar_du_plane_write(rgrp, index, PnTC2R, colorkey);
break;
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
rcar_du_plane_write(rgrp, index, PnTC3R,
PnTC3R_CODE | (state->colorkey & 0xffffff));
break;
}
}
static void rcar_du_plane_setup_format_gen2(struct rcar_du_group *rgrp,
unsigned int index,
const struct rcar_du_plane_state *state)
{
u32 ddcr2 = PnDDCR2_CODE;
u32 ddcr4;
/*
* Data format
*
* The data format is selected by the DDDF field in PnMR and the EDF
* field in DDCR4.
*/
rcar_du_plane_setup_mode(rgrp, index, state);
if (state->format->planes == 2) {
if (state->hwindex != index) {
if (state->format->fourcc == DRM_FORMAT_NV12 ||
state->format->fourcc == DRM_FORMAT_NV21)
ddcr2 |= PnDDCR2_Y420;
if (state->format->fourcc == DRM_FORMAT_NV21)
ddcr2 |= PnDDCR2_NV21;
ddcr2 |= PnDDCR2_DIVU;
} else {
ddcr2 |= PnDDCR2_DIVY;
}
}
rcar_du_plane_write(rgrp, index, PnDDCR2, ddcr2);
ddcr4 = state->format->edf | PnDDCR4_CODE;
if (state->source != RCAR_DU_PLANE_MEMORY)
ddcr4 |= PnDDCR4_VSPS;
rcar_du_plane_write(rgrp, index, PnDDCR4, ddcr4);
}
static void rcar_du_plane_setup_format_gen3(struct rcar_du_group *rgrp,
unsigned int index,
const struct rcar_du_plane_state *state)
{
struct rcar_du_device *rcdu = rgrp->dev;
u32 pnmr = state->format->pnmr | PnMR_SPIM_TP_OFF;
if (rcdu->info->features & RCAR_DU_FEATURE_NO_BLENDING) {
/* No blending. ALP and EOR are not supported. */
pnmr &= ~(PnMR_SPIM_ALP | PnMR_SPIM_EOR);
}
rcar_du_plane_write(rgrp, index, PnMR, pnmr);
rcar_du_plane_write(rgrp, index, PnDDCR4,
state->format->edf | PnDDCR4_CODE);
/*
* On Gen3, some DU channels have two planes, each being wired to a
* separate VSPD instance. The DU can then blend two planes. While
* this feature isn't used by the driver, issues related to alpha
* blending (such as incorrect colors or planes being invisible) may
* still occur if the PnALPHAR register has a stale value. Set the
* register to 0 to avoid this.
*/
rcar_du_plane_write(rgrp, index, PnALPHAR, 0);
}
static void rcar_du_plane_setup_format(struct rcar_du_group *rgrp,
unsigned int index,
const struct rcar_du_plane_state *state)
{
struct rcar_du_device *rcdu = rgrp->dev;
const struct drm_rect *dst = &state->state.dst;
if (rcdu->info->gen < 3)
rcar_du_plane_setup_format_gen2(rgrp, index, state);
else
rcar_du_plane_setup_format_gen3(rgrp, index, state);
/* Destination position and size */
rcar_du_plane_write(rgrp, index, PnDSXR, drm_rect_width(dst));
rcar_du_plane_write(rgrp, index, PnDSYR, drm_rect_height(dst));
rcar_du_plane_write(rgrp, index, PnDPXR, dst->x1);
rcar_du_plane_write(rgrp, index, PnDPYR, dst->y1);
if (rcdu->info->gen < 3) {
/* Wrap-around and blinking, disabled */
rcar_du_plane_write(rgrp, index, PnWASPR, 0);
rcar_du_plane_write(rgrp, index, PnWAMWR, 4095);
rcar_du_plane_write(rgrp, index, PnBTR, 0);
rcar_du_plane_write(rgrp, index, PnMLR, 0);
}
}
void __rcar_du_plane_setup(struct rcar_du_group *rgrp,
const struct rcar_du_plane_state *state)
{
struct rcar_du_device *rcdu = rgrp->dev;
rcar_du_plane_setup_format(rgrp, state->hwindex, state);
if (state->format->planes == 2)
rcar_du_plane_setup_format(rgrp, (state->hwindex + 1) % 8,
state);
if (rcdu->info->gen >= 3)
return;
rcar_du_plane_setup_scanout(rgrp, state);
if (state->source == RCAR_DU_PLANE_VSPD1) {
unsigned int vspd1_sink = rgrp->index ? 2 : 0;
if (rcdu->vspd1_sink != vspd1_sink) {
rcdu->vspd1_sink = vspd1_sink;
rcar_du_set_dpad0_vsp1_routing(rcdu);
/*
* Changes to the VSP1 sink take effect on DRES and thus
* need a restart of the group.
*/
rgrp->need_restart = true;
}
}
}
int __rcar_du_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state,
const struct rcar_du_format_info **format)
{
struct drm_device *dev = plane->dev;
struct drm_crtc_state *crtc_state;
int ret;
if (!state->crtc) {
/*
* The visible field is not reset by the DRM core but only
* updated by drm_atomic_helper_check_plane_state(), set it
* manually.
*/
state->visible = false;
*format = NULL;
return 0;
}
crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
ret = drm_atomic_helper_check_plane_state(state, crtc_state,
DRM_PLANE_NO_SCALING,
DRM_PLANE_NO_SCALING,
true, true);
if (ret < 0)
return ret;
if (!state->visible) {
*format = NULL;
return 0;
}
*format = rcar_du_format_info(state->fb->format->format);
if (*format == NULL) {
dev_dbg(dev->dev, "%s: unsupported format %p4cc\n", __func__,
&state->fb->format->format);
return -EINVAL;
}
return 0;
}
static int rcar_du_plane_atomic_check(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
plane);
struct rcar_du_plane_state *rstate = to_rcar_plane_state(new_plane_state);
return __rcar_du_plane_atomic_check(plane, new_plane_state,
&rstate->format);
}
static void rcar_du_plane_atomic_update(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane);
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane);
struct rcar_du_plane *rplane = to_rcar_plane(plane);
struct rcar_du_plane_state *old_rstate;
struct rcar_du_plane_state *new_rstate;
if (!new_state->visible)
return;
rcar_du_plane_setup(rplane);
/*
* Check whether the source has changed from memory to live source or
* from live source to memory. The source has been configured by the
* VSPS bit in the PnDDCR4 register. Although the datasheet states that
* the bit is updated during vertical blanking, it seems that updates
* only occur when the DU group is held in reset through the DSYSR.DRES
* bit. We thus need to restart the group if the source changes.
*/
old_rstate = to_rcar_plane_state(old_state);
new_rstate = to_rcar_plane_state(new_state);
if ((old_rstate->source == RCAR_DU_PLANE_MEMORY) !=
(new_rstate->source == RCAR_DU_PLANE_MEMORY))
rplane->group->need_restart = true;
}
static const struct drm_plane_helper_funcs rcar_du_plane_helper_funcs = {
.atomic_check = rcar_du_plane_atomic_check,
.atomic_update = rcar_du_plane_atomic_update,
};
static const struct drm_plane_helper_funcs rcar_du_primary_plane_helper_funcs = {
.atomic_check = rcar_du_plane_atomic_check,
.atomic_update = rcar_du_plane_atomic_update,
.get_scanout_buffer = drm_fb_dma_get_scanout_buffer,
};
static struct drm_plane_state *
rcar_du_plane_atomic_duplicate_state(struct drm_plane *plane)
{
struct rcar_du_plane_state *state;
struct rcar_du_plane_state *copy;
if (WARN_ON(!plane->state))
return NULL;
state = to_rcar_plane_state(plane->state);
copy = kmemdup(state, sizeof(*state), GFP_KERNEL);
if (copy == NULL)
return NULL;
__drm_atomic_helper_plane_duplicate_state(plane, &copy->state);
return &copy->state;
}
static void rcar_du_plane_atomic_destroy_state(struct drm_plane *plane,
struct drm_plane_state *state)
{
__drm_atomic_helper_plane_destroy_state(state);
kfree(to_rcar_plane_state(state));
}
static void rcar_du_plane_reset(struct drm_plane *plane)
{
struct rcar_du_plane_state *state;
if (plane->state) {
rcar_du_plane_atomic_destroy_state(plane, plane->state);
plane->state = NULL;
}
state = kzalloc_obj(*state);
if (state == NULL)
return;
__drm_atomic_helper_plane_reset(plane, &state->state);
state->hwindex = -1;
state->source = RCAR_DU_PLANE_MEMORY;
state->colorkey = RCAR_DU_COLORKEY_NONE;
}
static int rcar_du_plane_atomic_set_property(struct drm_plane *plane,
struct drm_plane_state *state,
struct drm_property *property,
uint64_t val)
{
struct rcar_du_plane_state *rstate = to_rcar_plane_state(state);
struct rcar_du_device *rcdu = to_rcar_plane(plane)->group->dev;
if (property == rcdu->props.colorkey)
rstate->colorkey = val;
else
return -EINVAL;
return 0;
}
static int rcar_du_plane_atomic_get_property(struct drm_plane *plane,
const struct drm_plane_state *state, struct drm_property *property,
uint64_t *val)
{
const struct rcar_du_plane_state *rstate =
container_of(state, const struct rcar_du_plane_state, state);
struct rcar_du_device *rcdu = to_rcar_plane(plane)->group->dev;
if (property == rcdu->props.colorkey)
*val = rstate->colorkey;
else
return -EINVAL;
return 0;
}
static const struct drm_plane_funcs rcar_du_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.reset = rcar_du_plane_reset,
.destroy = drm_plane_cleanup,
.atomic_duplicate_state = rcar_du_plane_atomic_duplicate_state,
.atomic_destroy_state = rcar_du_plane_atomic_destroy_state,
.atomic_set_property = rcar_du_plane_atomic_set_property,
.atomic_get_property = rcar_du_plane_atomic_get_property,
};
static const uint32_t formats[] = {
DRM_FORMAT_RGB565,
DRM_FORMAT_ARGB1555,
DRM_FORMAT_XRGB1555,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_UYVY,
DRM_FORMAT_YUYV,
DRM_FORMAT_NV12,
DRM_FORMAT_NV21,
DRM_FORMAT_NV16,
};
int rcar_du_planes_init(struct rcar_du_group *rgrp)
{
struct rcar_du_device *rcdu = rgrp->dev;
unsigned int crtcs;
unsigned int i;
int ret;
/*
* Create one primary plane per CRTC in this group and seven overlay
* planes.
*/
rgrp->num_planes = rgrp->num_crtcs + 7;
crtcs = ((1 << rcdu->num_crtcs) - 1) & (3 << (2 * rgrp->index));
for (i = 0; i < rgrp->num_planes; ++i) {
enum drm_plane_type type = i < rgrp->num_crtcs
? DRM_PLANE_TYPE_PRIMARY
: DRM_PLANE_TYPE_OVERLAY;
struct rcar_du_plane *plane = &rgrp->planes[i];
plane->group = rgrp;
ret = drm_universal_plane_init(&rcdu->ddev, &plane->plane,
crtcs, &rcar_du_plane_funcs,
formats, ARRAY_SIZE(formats),
NULL, type, NULL);
if (ret < 0)
return ret;
if (type == DRM_PLANE_TYPE_PRIMARY)
drm_plane_helper_add(&plane->plane,
&rcar_du_primary_plane_helper_funcs);
else
drm_plane_helper_add(&plane->plane,
&rcar_du_plane_helper_funcs);
drm_plane_create_alpha_property(&plane->plane);
if (type == DRM_PLANE_TYPE_PRIMARY) {
drm_plane_create_zpos_immutable_property(&plane->plane,
0);
} else {
drm_object_attach_property(&plane->plane.base,
rcdu->props.colorkey,
RCAR_DU_COLORKEY_NONE);
drm_plane_create_zpos_property(&plane->plane, 1, 1, 7);
}
}
return 0;
}
@@ -0,0 +1,86 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* R-Car Display Unit Planes
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __RCAR_DU_PLANE_H__
#define __RCAR_DU_PLANE_H__
#include <drm/drm_plane.h>
struct rcar_du_format_info;
struct rcar_du_group;
/*
* The R-Car DU has 8 hardware planes, shared between primary and overlay planes.
* As using overlay planes requires at least one of the CRTCs being enabled, no
* more than 7 overlay planes can be available. We thus create 1 primary plane
* per CRTC and 7 overlay planes, for a total of up to 9 KMS planes.
*/
#define RCAR_DU_NUM_KMS_PLANES 9
#define RCAR_DU_NUM_HW_PLANES 8
enum rcar_du_plane_source {
RCAR_DU_PLANE_MEMORY,
RCAR_DU_PLANE_VSPD0,
RCAR_DU_PLANE_VSPD1,
};
struct rcar_du_plane {
struct drm_plane plane;
struct rcar_du_group *group;
};
static inline struct rcar_du_plane *to_rcar_plane(struct drm_plane *plane)
{
return container_of(plane, struct rcar_du_plane, plane);
}
/**
* struct rcar_du_plane_state - Driver-specific plane state
* @state: base DRM plane state
* @format: information about the pixel format used by the plane
* @hwindex: 0-based hardware plane index, -1 means unused
* @colorkey: value of the plane colorkey property
*/
struct rcar_du_plane_state {
struct drm_plane_state state;
const struct rcar_du_format_info *format;
int hwindex;
enum rcar_du_plane_source source;
unsigned int colorkey;
};
static inline struct rcar_du_plane_state *
to_rcar_plane_state(struct drm_plane_state *state)
{
return container_of(state, struct rcar_du_plane_state, state);
}
int rcar_du_atomic_check_planes(struct drm_device *dev,
struct drm_atomic_commit *state);
int __rcar_du_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state,
const struct rcar_du_format_info **format);
int rcar_du_planes_init(struct rcar_du_group *rgrp);
void __rcar_du_plane_setup(struct rcar_du_group *rgrp,
const struct rcar_du_plane_state *state);
static inline void rcar_du_plane_setup(struct rcar_du_plane *plane)
{
struct rcar_du_plane_state *state =
to_rcar_plane_state(plane->plane.state);
return __rcar_du_plane_setup(plane->group, state);
}
#endif /* __RCAR_DU_PLANE_H__ */
@@ -0,0 +1,553 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* R-Car Display Unit Registers Definitions
*
* Copyright (C) 2013-2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __RCAR_DU_REGS_H__
#define __RCAR_DU_REGS_H__
#define DU0_REG_OFFSET 0x00000
#define DU1_REG_OFFSET 0x30000
#define DU2_REG_OFFSET 0x40000
#define DU3_REG_OFFSET 0x70000
/* -----------------------------------------------------------------------------
* Display Control Registers
*/
#define DSYSR 0x00000 /* display 1 */
#define DSYSR_ILTS (1 << 29)
#define DSYSR_DSEC (1 << 20)
#define DSYSR_IUPD (1 << 16)
#define DSYSR_DRES (1 << 9)
#define DSYSR_DEN (1 << 8)
#define DSYSR_TVM_MASTER (0 << 6)
#define DSYSR_TVM_SWITCH (1 << 6)
#define DSYSR_TVM_TVSYNC (2 << 6)
#define DSYSR_TVM_MASK (3 << 6)
#define DSYSR_SCM_INT_NONE (0 << 4)
#define DSYSR_SCM_INT_SYNC (2 << 4)
#define DSYSR_SCM_INT_VIDEO (3 << 4)
#define DSYSR_SCM_MASK (3 << 4)
#define DSMR 0x00004
#define DSMR_VSPM (1 << 28)
#define DSMR_ODPM (1 << 27)
#define DSMR_DIPM_DISP (0 << 25)
#define DSMR_DIPM_CSYNC (1 << 25)
#define DSMR_DIPM_DE (3 << 25)
#define DSMR_DIPM_MASK (3 << 25)
#define DSMR_CSPM (1 << 24)
#define DSMR_DIL (1 << 19)
#define DSMR_VSL (1 << 18)
#define DSMR_HSL (1 << 17)
#define DSMR_DDIS (1 << 16)
#define DSMR_CDEL (1 << 15)
#define DSMR_CDEM_CDE (0 << 13)
#define DSMR_CDEM_LOW (2 << 13)
#define DSMR_CDEM_HIGH (3 << 13)
#define DSMR_CDEM_MASK (3 << 13)
#define DSMR_CDED (1 << 12)
#define DSMR_ODEV (1 << 8)
#define DSMR_CSY_VH_OR (0 << 6)
#define DSMR_CSY_333 (2 << 6)
#define DSMR_CSY_222 (3 << 6)
#define DSMR_CSY_MASK (3 << 6)
#define DSSR 0x00008
#define DSSR_VC1FB_DSA0 (0 << 30)
#define DSSR_VC1FB_DSA1 (1 << 30)
#define DSSR_VC1FB_DSA2 (2 << 30)
#define DSSR_VC1FB_INIT (3 << 30)
#define DSSR_VC1FB_MASK (3 << 30)
#define DSSR_VC0FB_DSA0 (0 << 28)
#define DSSR_VC0FB_DSA1 (1 << 28)
#define DSSR_VC0FB_DSA2 (2 << 28)
#define DSSR_VC0FB_INIT (3 << 28)
#define DSSR_VC0FB_MASK (3 << 28)
#define DSSR_DFB(n) (1 << ((n)+15))
#define DSSR_TVR (1 << 15)
#define DSSR_FRM (1 << 14)
#define DSSR_VBK (1 << 11)
#define DSSR_RINT (1 << 9)
#define DSSR_HBK (1 << 8)
#define DSSR_ADC(n) (1 << ((n)-1))
#define DSRCR 0x0000c
#define DSRCR_TVCL (1 << 15)
#define DSRCR_FRCL (1 << 14)
#define DSRCR_VBCL (1 << 11)
#define DSRCR_RICL (1 << 9)
#define DSRCR_HBCL (1 << 8)
#define DSRCR_ADCL(n) (1 << ((n)-1))
#define DSRCR_MASK 0x0000cbff
#define DIER 0x00010
#define DIER_TVE (1 << 15)
#define DIER_FRE (1 << 14)
#define DIER_VBE (1 << 11)
#define DIER_RIE (1 << 9)
#define DIER_HBE (1 << 8)
#define DIER_ADCE(n) (1 << ((n)-1))
#define CPCR 0x00014
#define CPCR_CP4CE (1 << 19)
#define CPCR_CP3CE (1 << 18)
#define CPCR_CP2CE (1 << 17)
#define CPCR_CP1CE (1 << 16)
#define DPPR 0x00018
#define DPPR_DPE(n) (1 << ((n)*4-1))
#define DPPR_DPS(n, p) (((p)-1) << DPPR_DPS_SHIFT(n))
#define DPPR_DPS_SHIFT(n) (((n)-1)*4)
#define DPPR_BPP16 (DPPR_DPE(8) | DPPR_DPS(8, 1)) /* plane1 */
#define DPPR_BPP32_P1 (DPPR_DPE(7) | DPPR_DPS(7, 1))
#define DPPR_BPP32_P2 (DPPR_DPE(8) | DPPR_DPS(8, 2))
#define DPPR_BPP32 (DPPR_BPP32_P1 | DPPR_BPP32_P2) /* plane1 & 2 */
#define DEFR 0x00020
#define DEFR_CODE (0x7773 << 16)
#define DEFR_EXSL (1 << 12)
#define DEFR_EXVL (1 << 11)
#define DEFR_EXUP (1 << 5)
#define DEFR_VCUP (1 << 4)
#define DEFR_DEFE (1 << 0)
#define DAPCR 0x00024
#define DAPCR_CODE (0x7773 << 16)
#define DAPCR_AP2E (1 << 4)
#define DAPCR_AP1E (1 << 0)
#define DCPCR 0x00028
#define DCPCR_CODE (0x7773 << 16)
#define DCPCR_CA2B (1 << 13)
#define DCPCR_CD2F (1 << 12)
#define DCPCR_DC2E (1 << 8)
#define DCPCR_CAB (1 << 5)
#define DCPCR_CDF (1 << 4)
#define DCPCR_DCE (1 << 0)
#define DEFR2 0x00034
#define DEFR2_CODE (0x7775 << 16)
#define DEFR2_DEFE2G (1 << 0)
#define DEFR3 0x00038
#define DEFR3_CODE (0x7776 << 16)
#define DEFR3_EVDA (1 << 14)
#define DEFR3_EVDM_1 (1 << 12)
#define DEFR3_EVDM_2 (2 << 12)
#define DEFR3_EVDM_3 (3 << 12)
#define DEFR3_VMSM2_EMA (1 << 6)
#define DEFR3_VMSM1_ENA (1 << 4)
#define DEFR3_DEFE3 (1 << 0)
#define DEFR4 0x0003c
#define DEFR4_CODE (0x7777 << 16)
#define DEFR4_LRUO (1 << 5)
#define DEFR4_SPCE (1 << 4)
#define DVCSR 0x000d0
#define DVCSR_VCnFB2_DSA0(n) (0 << ((n)*2+16))
#define DVCSR_VCnFB2_DSA1(n) (1 << ((n)*2+16))
#define DVCSR_VCnFB2_DSA2(n) (2 << ((n)*2+16))
#define DVCSR_VCnFB2_INIT(n) (3 << ((n)*2+16))
#define DVCSR_VCnFB2_MASK(n) (3 << ((n)*2+16))
#define DVCSR_VCnFB_DSA0(n) (0 << ((n)*2))
#define DVCSR_VCnFB_DSA1(n) (1 << ((n)*2))
#define DVCSR_VCnFB_DSA2(n) (2 << ((n)*2))
#define DVCSR_VCnFB_INIT(n) (3 << ((n)*2))
#define DVCSR_VCnFB_MASK(n) (3 << ((n)*2))
#define DEFR5 0x000e0
#define DEFR5_CODE (0x66 << 24)
#define DEFR5_YCRGB2_DIS (0 << 14)
#define DEFR5_YCRGB2_PRI1 (1 << 14)
#define DEFR5_YCRGB2_PRI2 (2 << 14)
#define DEFR5_YCRGB2_PRI3 (3 << 14)
#define DEFR5_YCRGB2_MASK (3 << 14)
#define DEFR5_YCRGB1_DIS (0 << 12)
#define DEFR5_YCRGB1_PRI1 (1 << 12)
#define DEFR5_YCRGB1_PRI2 (2 << 12)
#define DEFR5_YCRGB1_PRI3 (3 << 12)
#define DEFR5_YCRGB1_MASK (3 << 12)
#define DEFR5_DEFE5 (1 << 0)
#define DDLTR 0x000e4
#define DDLTR_CODE (0x7766 << 16)
#define DDLTR_DLAR2 (1 << 6)
#define DDLTR_DLAY2 (1 << 5)
#define DDLTR_DLAY1 (1 << 1)
#define DEFR6 0x000e8
#define DEFR6_CODE (0x7778 << 16)
#define DEFR6_ODPM12_DSMR (0 << 10)
#define DEFR6_ODPM12_DISP (2 << 10)
#define DEFR6_ODPM12_CDE (3 << 10)
#define DEFR6_ODPM12_MASK (3 << 10)
#define DEFR6_ODPM02_DSMR (0 << 8)
#define DEFR6_ODPM02_DISP (2 << 8)
#define DEFR6_ODPM02_CDE (3 << 8)
#define DEFR6_ODPM02_MASK (3 << 8)
#define DEFR6_TCNE1 (1 << 6)
#define DEFR6_TCNE0 (1 << 4)
#define DEFR6_MLOS1 (1 << 2)
#define DEFR6_DEFAULT (DEFR6_CODE | DEFR6_TCNE1)
#define DEFR7 0x000ec
#define DEFR7_CODE (0x7779 << 16)
#define DEFR7_CMME1 BIT(6)
#define DEFR7_CMME0 BIT(4)
/* -----------------------------------------------------------------------------
* R8A7790-only Control Registers
*/
#define DD1SSR 0x20008
#define DD1SSR_TVR (1 << 15)
#define DD1SSR_FRM (1 << 14)
#define DD1SSR_BUF (1 << 12)
#define DD1SSR_VBK (1 << 11)
#define DD1SSR_RINT (1 << 9)
#define DD1SSR_HBK (1 << 8)
#define DD1SSR_ADC(n) (1 << ((n)-1))
#define DD1SRCR 0x2000c
#define DD1SRCR_TVR (1 << 15)
#define DD1SRCR_FRM (1 << 14)
#define DD1SRCR_BUF (1 << 12)
#define DD1SRCR_VBK (1 << 11)
#define DD1SRCR_RINT (1 << 9)
#define DD1SRCR_HBK (1 << 8)
#define DD1SRCR_ADC(n) (1 << ((n)-1))
#define DD1IER 0x20010
#define DD1IER_TVR (1 << 15)
#define DD1IER_FRM (1 << 14)
#define DD1IER_BUF (1 << 12)
#define DD1IER_VBK (1 << 11)
#define DD1IER_RINT (1 << 9)
#define DD1IER_HBK (1 << 8)
#define DD1IER_ADC(n) (1 << ((n)-1))
#define DEFR8 0x20020
#define DEFR8_CODE (0x7790 << 16)
#define DEFR8_VSCS (1 << 6)
#define DEFR8_DRGBS_DU(n) ((n) << 4)
#define DEFR8_DRGBS_MASK (3 << 4)
#define DEFR8_DEFE8 (1 << 0)
#define DOFLR 0x20024
#define DOFLR_CODE (0x7790 << 16)
#define DOFLR_HSYCFL1 (1 << 13)
#define DOFLR_VSYCFL1 (1 << 12)
#define DOFLR_ODDFL1 (1 << 11)
#define DOFLR_DISPFL1 (1 << 10)
#define DOFLR_CDEFL1 (1 << 9)
#define DOFLR_RGBFL1 (1 << 8)
#define DOFLR_HSYCFL0 (1 << 5)
#define DOFLR_VSYCFL0 (1 << 4)
#define DOFLR_ODDFL0 (1 << 3)
#define DOFLR_DISPFL0 (1 << 2)
#define DOFLR_CDEFL0 (1 << 1)
#define DOFLR_RGBFL0 (1 << 0)
#define DIDSR 0x20028
#define DIDSR_CODE (0x7790 << 16)
#define DIDSR_LDCS_DCLKIN(n) (0 << (8 + (n) * 2))
#define DIDSR_LDCS_DSI(n) (2 << (8 + (n) * 2)) /* V3U only */
#define DIDSR_LDCS_LVDS0(n) (2 << (8 + (n) * 2))
#define DIDSR_LDCS_LVDS1(n) (3 << (8 + (n) * 2))
#define DIDSR_LDCS_MASK(n) (3 << (8 + (n) * 2))
#define DIDSR_PDCS_CLK(n, clk) (clk << ((n) * 2))
#define DIDSR_PDCS_MASK(n) (3 << ((n) * 2))
#define DEFR10 0x20038
#define DEFR10_CODE (0x7795 << 16)
#define DEFR10_VSPF1_RGB (0 << 14)
#define DEFR10_VSPF1_YC (1 << 14)
#define DEFR10_DOCF1_RGB (0 << 12)
#define DEFR10_DOCF1_YC (1 << 12)
#define DEFR10_YCDF0_YCBCR444 (0 << 11)
#define DEFR10_YCDF0_YCBCR422 (1 << 11)
#define DEFR10_VSPF0_RGB (0 << 10)
#define DEFR10_VSPF0_YC (1 << 10)
#define DEFR10_DOCF0_RGB (0 << 8)
#define DEFR10_DOCF0_YC (1 << 8)
#define DEFR10_TSEL_H3_TCON1 (0 << 1) /* DEFR102 register only (DU2/DU3) */
#define DEFR10_DEFE10 (1 << 0)
#define DPLLCR 0x20044
#define DPLLCR_CODE (0x95 << 24)
#define DPLLCR_PLCS1 (1 << 23)
#define DPLLCR_PLCS0 (1 << 21)
#define DPLLCR_CLKE (1 << 18)
#define DPLLCR_FDPLL(n) ((n) << 12)
#define DPLLCR_N(n) ((n) << 5)
#define DPLLCR_M(n) ((n) << 3)
#define DPLLCR_STBY (1 << 2)
#define DPLLCR_INCS_DOTCLKIN0 (0 << 0)
#define DPLLCR_INCS_DOTCLKIN1 (1 << 1)
#define DPLLC2R 0x20048
#define DPLLC2R_CODE (0x95 << 24)
#define DPLLC2R_SELC (1 << 12)
#define DPLLC2R_M(n) ((n) << 8)
#define DPLLC2R_FDPLL(n) ((n) << 0)
/* -----------------------------------------------------------------------------
* Display Timing Generation Registers
*/
#define HDSR 0x00040
#define HDER 0x00044
#define VDSR 0x00048
#define VDER 0x0004c
#define HCR 0x00050
#define HSWR 0x00054
#define VCR 0x00058
#define VSPR 0x0005c
#define EQWR 0x00060
#define SPWR 0x00064
#define CLAMPSR 0x00070
#define CLAMPWR 0x00074
#define DESR 0x00078
#define DEWR 0x0007c
/* -----------------------------------------------------------------------------
* Display Attribute Registers
*/
#define CP1TR 0x00080
#define CP2TR 0x00084
#define CP3TR 0x00088
#define CP4TR 0x0008c
#define DOOR 0x00090
#define DOOR_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2))
#define CDER 0x00094
#define CDER_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2))
#define BPOR 0x00098
#define BPOR_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2))
#define RINTOFSR 0x0009c
#define DSHPR 0x000c8
#define DSHPR_CODE (0x7776 << 16)
#define DSHPR_PRIH (0xa << 4)
#define DSHPR_PRIL_BPP16 (0x8 << 0)
#define DSHPR_PRIL_BPP32 (0x9 << 0)
/* -----------------------------------------------------------------------------
* Display Plane Registers
*/
#define PLANE_OFF 0x00100
#define PnMR 0x00100 /* plane 1 */
#define PnMR_VISL_VIN0 (0 << 26) /* use Video Input 0 */
#define PnMR_VISL_VIN1 (1 << 26) /* use Video Input 1 */
#define PnMR_VISL_VIN2 (2 << 26) /* use Video Input 2 */
#define PnMR_VISL_VIN3 (3 << 26) /* use Video Input 3 */
#define PnMR_YCDF_YUYV (1 << 20) /* YUYV format */
#define PnMR_TC_R (0 << 17) /* Tranparent color is PnTC1R */
#define PnMR_TC_CP (1 << 17) /* Tranparent color is color palette */
#define PnMR_WAE (1 << 16) /* Wrap around Enable */
#define PnMR_SPIM_TP (0 << 12) /* Transparent Color */
#define PnMR_SPIM_ALP (1 << 12) /* Alpha Blending */
#define PnMR_SPIM_EOR (2 << 12) /* EOR */
#define PnMR_SPIM_TP_OFF (1 << 14) /* No Transparent Color */
#define PnMR_CPSL_CP1 (0 << 8) /* Color Palette selected 1 */
#define PnMR_CPSL_CP2 (1 << 8) /* Color Palette selected 2 */
#define PnMR_CPSL_CP3 (2 << 8) /* Color Palette selected 3 */
#define PnMR_CPSL_CP4 (3 << 8) /* Color Palette selected 4 */
#define PnMR_DC (1 << 7) /* Display Area Change */
#define PnMR_BM_MD (0 << 4) /* Manual Display Change Mode */
#define PnMR_BM_AR (1 << 4) /* Auto Rendering Mode */
#define PnMR_BM_AD (2 << 4) /* Auto Display Change Mode */
#define PnMR_BM_VC (3 << 4) /* Video Capture Mode */
#define PnMR_DDDF_8BPP (0 << 0) /* 8bit */
#define PnMR_DDDF_16BPP (1 << 0) /* 16bit or 32bit */
#define PnMR_DDDF_ARGB (2 << 0) /* ARGB */
#define PnMR_DDDF_YC (3 << 0) /* YC */
#define PnMR_DDDF_MASK (3 << 0)
#define PnMWR 0x00104
#define PnALPHAR 0x00108
#define PnALPHAR_ABIT_1 (0 << 12)
#define PnALPHAR_ABIT_0 (1 << 12)
#define PnALPHAR_ABIT_X (2 << 12)
#define PnDSXR 0x00110
#define PnDSYR 0x00114
#define PnDPXR 0x00118
#define PnDPYR 0x0011c
#define PnDSA0R 0x00120
#define PnDSA1R 0x00124
#define PnDSA2R 0x00128
#define PnDSA_MASK 0xfffffff0
#define PnSPXR 0x00130
#define PnSPYR 0x00134
#define PnWASPR 0x00138
#define PnWAMWR 0x0013c
#define PnBTR 0x00140
#define PnTC1R 0x00144
#define PnTC2R 0x00148
#define PnTC3R 0x0014c
#define PnTC3R_CODE (0x66 << 24)
#define PnMLR 0x00150
#define PnSWAPR 0x00180
#define PnSWAPR_DIGN (1 << 4)
#define PnSWAPR_SPQW (1 << 3)
#define PnSWAPR_SPLW (1 << 2)
#define PnSWAPR_SPWD (1 << 1)
#define PnSWAPR_SPBY (1 << 0)
#define PnDDCR 0x00184
#define PnDDCR_CODE (0x7775 << 16)
#define PnDDCR_LRGB1 (1 << 11)
#define PnDDCR_LRGB0 (1 << 10)
#define PnDDCR2 0x00188
#define PnDDCR2_CODE (0x7776 << 16)
#define PnDDCR2_NV21 (1 << 5)
#define PnDDCR2_Y420 (1 << 4)
#define PnDDCR2_DIVU (1 << 1)
#define PnDDCR2_DIVY (1 << 0)
#define PnDDCR4 0x00190
#define PnDDCR4_CODE (0x7766 << 16)
#define PnDDCR4_VSPS (1 << 13)
#define PnDDCR4_SDFS_RGB (0 << 4)
#define PnDDCR4_SDFS_YC (5 << 4)
#define PnDDCR4_SDFS_MASK (7 << 4)
#define PnDDCR4_EDF_NONE (0 << 0)
#define PnDDCR4_EDF_ARGB8888 (1 << 0)
#define PnDDCR4_EDF_RGB888 (2 << 0)
#define PnDDCR4_EDF_RGB666 (3 << 0)
#define PnDDCR4_EDF_MASK (7 << 0)
#define APnMR 0x0a100
#define APnMR_WAE (1 << 16) /* Wrap around Enable */
#define APnMR_DC (1 << 7) /* Display Area Change */
#define APnMR_BM_MD (0 << 4) /* Manual Display Change Mode */
#define APnMR_BM_AD (2 << 4) /* Auto Display Change Mode */
#define APnMWR 0x0a104
#define APnDSXR 0x0a110
#define APnDSYR 0x0a114
#define APnDPXR 0x0a118
#define APnDPYR 0x0a11c
#define APnDSA0R 0x0a120
#define APnDSA1R 0x0a124
#define APnDSA2R 0x0a128
#define APnSPXR 0x0a130
#define APnSPYR 0x0a134
#define APnWASPR 0x0a138
#define APnWAMWR 0x0a13c
#define APnBTR 0x0a140
#define APnMLR 0x0a150
#define APnSWAPR 0x0a180
/* -----------------------------------------------------------------------------
* Display Capture Registers
*/
#define DCMR 0x0c100
#define DCMWR 0x0c104
#define DCSAR 0x0c120
#define DCMLR 0x0c150
/* -----------------------------------------------------------------------------
* Color Palette Registers
*/
#define CP1_000R 0x01000
#define CP1_255R 0x013fc
#define CP2_000R 0x02000
#define CP2_255R 0x023fc
#define CP3_000R 0x03000
#define CP3_255R 0x033fc
#define CP4_000R 0x04000
#define CP4_255R 0x043fc
/* -----------------------------------------------------------------------------
* External Synchronization Control Registers
*/
#define ESCR02 0x10000
#define ESCR13 0x01000
#define ESCR_DCLKOINV (1 << 25)
#define ESCR_DCLKSEL_DCLKIN (0 << 20)
#define ESCR_DCLKSEL_CLKS (1 << 20)
#define ESCR_DCLKSEL_MASK (1 << 20)
#define ESCR_DCLKDIS (1 << 16)
#define ESCR_SYNCSEL_OFF (0 << 8)
#define ESCR_SYNCSEL_EXVSYNC (2 << 8)
#define ESCR_SYNCSEL_EXHSYNC (3 << 8)
#define ESCR_FRQSEL_MASK (0x3f << 0)
#define OTAR02 0x10004
#define OTAR13 0x01004
/* -----------------------------------------------------------------------------
* Dual Display Output Control Registers
*/
#define DORCR 0x11000
#define DORCR_PG1T (1 << 30)
#define DORCR_DK1S (1 << 28)
#define DORCR_PG1D_DS0 (0 << 24)
#define DORCR_PG1D_DS1 (1 << 24)
#define DORCR_PG1D_FIX0 (2 << 24)
#define DORCR_PG1D_DOOR (3 << 24)
#define DORCR_PG1D_MASK (3 << 24)
#define DORCR_DR0D (1 << 21)
#define DORCR_PG0D_DS0 (0 << 16)
#define DORCR_PG0D_DS1 (1 << 16)
#define DORCR_PG0D_FIX0 (2 << 16)
#define DORCR_PG0D_DOOR (3 << 16)
#define DORCR_PG0D_MASK (3 << 16)
#define DORCR_RGPV (1 << 4)
#define DORCR_DPRS (1 << 0)
#define DPTSR 0x11004
#define DPTSR_PnDK(n) (1 << ((n) + 16))
#define DPTSR_PnTS(n) (1 << (n))
#define DAPTSR 0x11008
#define DAPTSR_APnDK(n) (1 << ((n) + 16))
#define DAPTSR_APnTS(n) (1 << (n))
#define DS1PR 0x11020
#define DS2PR 0x11024
/* -----------------------------------------------------------------------------
* YC-RGB Conversion Coefficient Registers
*/
#define YNCR 0x11080
#define YNOR 0x11084
#define CRNOR 0x11088
#define CBNOR 0x1108c
#define RCRCR 0x11090
#define GCRCR 0x11094
#define GCBCR 0x11098
#define BCBCR 0x1109c
#endif /* __RCAR_DU_REGS_H__ */
@@ -0,0 +1,553 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* R-Car Display Unit VSP-Based Compositor
*
* Copyright (C) 2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_blend.h>
#include <drm/drm_crtc.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_managed.h>
#include <drm/drm_vblank.h>
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/videodev2.h>
#include <media/vsp1.h>
#include "rcar_du_drv.h"
#include "rcar_du_kms.h"
#include "rcar_du_vsp.h"
#include "rcar_du_writeback.h"
static void rcar_du_vsp_complete(void *private, unsigned int status, u32 crc)
{
struct rcar_du_crtc *crtc = private;
if (crtc->vblank_enable)
drm_crtc_handle_vblank(&crtc->crtc);
if (status & VSP1_DU_STATUS_COMPLETE)
rcar_du_crtc_finish_page_flip(crtc);
if (status & VSP1_DU_STATUS_WRITEBACK)
rcar_du_writeback_complete(crtc);
drm_crtc_add_crc_entry(&crtc->crtc, false, 0, &crc);
}
void rcar_du_vsp_enable(struct rcar_du_crtc *crtc)
{
const struct drm_display_mode *mode = &crtc->crtc.state->adjusted_mode;
struct rcar_du_device *rcdu = crtc->dev;
struct vsp1_du_lif_config cfg = {
.width = mode->hdisplay,
.height = mode->vdisplay,
.interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE,
.callback = rcar_du_vsp_complete,
.callback_data = crtc,
};
struct rcar_du_plane_state state = {
.state = {
.alpha = DRM_BLEND_ALPHA_OPAQUE,
.crtc = &crtc->crtc,
.dst.x1 = 0,
.dst.y1 = 0,
.dst.x2 = mode->hdisplay,
.dst.y2 = mode->vdisplay,
.src.x1 = 0,
.src.y1 = 0,
.src.x2 = mode->hdisplay << 16,
.src.y2 = mode->vdisplay << 16,
.zpos = 0,
},
.format = rcar_du_format_info(DRM_FORMAT_XRGB8888),
.source = RCAR_DU_PLANE_VSPD1,
.colorkey = 0,
};
if (rcdu->info->gen >= 3)
state.hwindex = (crtc->index % 2) ? 2 : 0;
else
state.hwindex = crtc->index % 2;
__rcar_du_plane_setup(crtc->group, &state);
vsp1_du_enable(crtc->vsp->vsp, crtc->vsp_pipe, &cfg);
}
void rcar_du_vsp_disable(struct rcar_du_crtc *crtc)
{
vsp1_du_disable(crtc->vsp->vsp, crtc->vsp_pipe);
}
void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc)
{
vsp1_du_atomic_begin(crtc->vsp->vsp, crtc->vsp_pipe);
}
void rcar_du_vsp_atomic_flush(struct rcar_du_crtc *crtc)
{
struct vsp1_du_atomic_pipe_config cfg = { { 0, } };
struct rcar_du_crtc_state *state;
state = to_rcar_crtc_state(crtc->crtc.state);
cfg.crc = state->crc;
rcar_du_writeback_setup(crtc, &cfg.writeback);
vsp1_du_atomic_flush(crtc->vsp->vsp, crtc->vsp_pipe, &cfg);
}
static const u32 rcar_du_vsp_formats[] = {
DRM_FORMAT_RGB332,
DRM_FORMAT_ARGB4444,
DRM_FORMAT_XRGB4444,
DRM_FORMAT_ARGB1555,
DRM_FORMAT_XRGB1555,
DRM_FORMAT_RGB565,
DRM_FORMAT_BGR888,
DRM_FORMAT_RGB888,
DRM_FORMAT_BGRA8888,
DRM_FORMAT_BGRX8888,
DRM_FORMAT_ABGR8888,
DRM_FORMAT_XBGR8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_UYVY,
DRM_FORMAT_YUYV,
DRM_FORMAT_YVYU,
DRM_FORMAT_NV12,
DRM_FORMAT_NV21,
DRM_FORMAT_NV16,
DRM_FORMAT_NV61,
DRM_FORMAT_YUV420,
DRM_FORMAT_YVU420,
DRM_FORMAT_YUV422,
DRM_FORMAT_YVU422,
DRM_FORMAT_YUV444,
DRM_FORMAT_YVU444,
};
/*
* Gen4 supports the same formats as above, and additionally 2-10-10-10 RGB
* formats and Y210 & Y212 formats.
*/
static const u32 rcar_du_vsp_formats_gen4[] = {
DRM_FORMAT_RGB332,
DRM_FORMAT_ARGB4444,
DRM_FORMAT_XRGB4444,
DRM_FORMAT_ARGB1555,
DRM_FORMAT_XRGB1555,
DRM_FORMAT_RGB565,
DRM_FORMAT_BGR888,
DRM_FORMAT_RGB888,
DRM_FORMAT_BGRA8888,
DRM_FORMAT_BGRX8888,
DRM_FORMAT_ABGR8888,
DRM_FORMAT_XBGR8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_RGBX1010102,
DRM_FORMAT_RGBA1010102,
DRM_FORMAT_ARGB2101010,
DRM_FORMAT_UYVY,
DRM_FORMAT_YUYV,
DRM_FORMAT_YVYU,
DRM_FORMAT_NV12,
DRM_FORMAT_NV21,
DRM_FORMAT_NV16,
DRM_FORMAT_NV61,
DRM_FORMAT_YUV420,
DRM_FORMAT_YVU420,
DRM_FORMAT_YUV422,
DRM_FORMAT_YVU422,
DRM_FORMAT_YUV444,
DRM_FORMAT_YVU444,
DRM_FORMAT_Y210,
DRM_FORMAT_Y212,
};
static u32 rcar_du_vsp_state_get_format(struct rcar_du_vsp_plane_state *state)
{
u32 fourcc = state->format->fourcc;
if (state->state.pixel_blend_mode == DRM_MODE_BLEND_PIXEL_NONE) {
switch (fourcc) {
case DRM_FORMAT_ARGB1555:
fourcc = DRM_FORMAT_XRGB1555;
break;
case DRM_FORMAT_ARGB4444:
fourcc = DRM_FORMAT_XRGB4444;
break;
case DRM_FORMAT_ARGB8888:
fourcc = DRM_FORMAT_XRGB8888;
break;
case DRM_FORMAT_ABGR8888:
fourcc = DRM_FORMAT_XBGR8888;
break;
case DRM_FORMAT_BGRA8888:
fourcc = DRM_FORMAT_BGRX8888;
break;
case DRM_FORMAT_RGBA1010102:
fourcc = DRM_FORMAT_RGBX1010102;
break;
}
}
return fourcc;
}
static void rcar_du_vsp_plane_setup(struct rcar_du_vsp_plane *plane)
{
struct rcar_du_vsp_plane_state *state =
to_rcar_vsp_plane_state(plane->plane.state);
struct rcar_du_crtc *crtc = to_rcar_crtc(state->state.crtc);
struct drm_framebuffer *fb = plane->plane.state->fb;
const struct rcar_du_format_info *format;
struct vsp1_du_atomic_config cfg = {
.pixelformat = 0,
.pitch = fb->pitches[0],
.alpha = state->state.alpha >> 8,
.zpos = state->state.zpos,
};
u32 fourcc = rcar_du_vsp_state_get_format(state);
unsigned int i;
cfg.src.left = state->state.src.x1 >> 16;
cfg.src.top = state->state.src.y1 >> 16;
cfg.src.width = drm_rect_width(&state->state.src) >> 16;
cfg.src.height = drm_rect_height(&state->state.src) >> 16;
cfg.dst.left = state->state.dst.x1;
cfg.dst.top = state->state.dst.y1;
cfg.dst.width = drm_rect_width(&state->state.dst);
cfg.dst.height = drm_rect_height(&state->state.dst);
for (i = 0; i < state->format->planes; ++i)
cfg.mem[i] = sg_dma_address(state->sg_tables[i].sgl)
+ fb->offsets[i];
format = rcar_du_format_info(fourcc);
cfg.pixelformat = format->v4l2;
cfg.premult = state->state.pixel_blend_mode == DRM_MODE_BLEND_PREMULTI;
vsp1_du_atomic_update(plane->vsp->vsp, crtc->vsp_pipe,
plane->index, &cfg);
}
int rcar_du_vsp_map_fb(struct rcar_du_vsp *vsp, struct drm_framebuffer *fb,
struct sg_table sg_tables[3])
{
struct rcar_du_device *rcdu = vsp->dev;
unsigned int i, j;
int ret;
for (i = 0; i < fb->format->num_planes; ++i) {
struct drm_gem_dma_object *gem = drm_fb_dma_get_gem_obj(fb, i);
struct sg_table *sgt = &sg_tables[i];
if (gem->sgt) {
struct scatterlist *src;
struct scatterlist *dst;
/*
* If the GEM buffer has a scatter gather table, it has
* been imported from a dma-buf and has no physical
* address as it might not be physically contiguous.
* Copy the original scatter gather table to map it to
* the VSP.
*/
ret = sg_alloc_table(sgt, gem->sgt->orig_nents,
GFP_KERNEL);
if (ret)
goto fail;
src = gem->sgt->sgl;
dst = sgt->sgl;
for (j = 0; j < gem->sgt->orig_nents; ++j) {
sg_set_page(dst, sg_page(src), src->length,
src->offset);
src = sg_next(src);
dst = sg_next(dst);
}
} else {
ret = dma_get_sgtable(rcdu->dev, sgt, gem->vaddr,
gem->dma_addr, gem->base.size);
if (ret)
goto fail;
}
ret = vsp1_du_map_sg(vsp->vsp, sgt);
if (ret) {
sg_free_table(sgt);
goto fail;
}
}
return 0;
fail:
while (i--) {
struct sg_table *sgt = &sg_tables[i];
vsp1_du_unmap_sg(vsp->vsp, sgt);
sg_free_table(sgt);
}
return ret;
}
static int rcar_du_vsp_plane_prepare_fb(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct rcar_du_vsp_plane_state *rstate = to_rcar_vsp_plane_state(state);
struct rcar_du_vsp *vsp = to_rcar_vsp_plane(plane)->vsp;
int ret;
/*
* There's no need to prepare (and unprepare) the framebuffer when the
* plane is not visible, as it will not be displayed.
*/
if (!state->visible)
return 0;
ret = rcar_du_vsp_map_fb(vsp, state->fb, rstate->sg_tables);
if (ret < 0)
return ret;
return drm_gem_plane_helper_prepare_fb(plane, state);
}
void rcar_du_vsp_unmap_fb(struct rcar_du_vsp *vsp, struct drm_framebuffer *fb,
struct sg_table sg_tables[3])
{
unsigned int i;
for (i = 0; i < fb->format->num_planes; ++i) {
struct sg_table *sgt = &sg_tables[i];
vsp1_du_unmap_sg(vsp->vsp, sgt);
sg_free_table(sgt);
}
}
static void rcar_du_vsp_plane_cleanup_fb(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct rcar_du_vsp_plane_state *rstate = to_rcar_vsp_plane_state(state);
struct rcar_du_vsp *vsp = to_rcar_vsp_plane(plane)->vsp;
if (!state->visible)
return;
rcar_du_vsp_unmap_fb(vsp, state->fb, rstate->sg_tables);
}
static int rcar_du_vsp_plane_atomic_check(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
plane);
struct rcar_du_vsp_plane_state *rstate = to_rcar_vsp_plane_state(new_plane_state);
return __rcar_du_plane_atomic_check(plane, new_plane_state,
&rstate->format);
}
static void rcar_du_vsp_plane_atomic_update(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane);
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane);
struct rcar_du_vsp_plane *rplane = to_rcar_vsp_plane(plane);
struct rcar_du_crtc *crtc = to_rcar_crtc(old_state->crtc);
if (new_state->visible)
rcar_du_vsp_plane_setup(rplane);
else if (old_state->crtc)
vsp1_du_atomic_update(rplane->vsp->vsp, crtc->vsp_pipe,
rplane->index, NULL);
}
static const struct drm_plane_helper_funcs rcar_du_vsp_plane_helper_funcs = {
.prepare_fb = rcar_du_vsp_plane_prepare_fb,
.cleanup_fb = rcar_du_vsp_plane_cleanup_fb,
.atomic_check = rcar_du_vsp_plane_atomic_check,
.atomic_update = rcar_du_vsp_plane_atomic_update,
};
static struct drm_plane_state *
rcar_du_vsp_plane_atomic_duplicate_state(struct drm_plane *plane)
{
struct rcar_du_vsp_plane_state *copy;
if (WARN_ON(!plane->state))
return NULL;
copy = kzalloc_obj(*copy);
if (copy == NULL)
return NULL;
__drm_atomic_helper_plane_duplicate_state(plane, &copy->state);
return &copy->state;
}
static void rcar_du_vsp_plane_atomic_destroy_state(struct drm_plane *plane,
struct drm_plane_state *state)
{
__drm_atomic_helper_plane_destroy_state(state);
kfree(to_rcar_vsp_plane_state(state));
}
static void rcar_du_vsp_plane_reset(struct drm_plane *plane)
{
struct rcar_du_vsp_plane_state *state;
if (plane->state) {
rcar_du_vsp_plane_atomic_destroy_state(plane, plane->state);
plane->state = NULL;
}
state = kzalloc_obj(*state);
if (state == NULL)
return;
__drm_atomic_helper_plane_reset(plane, &state->state);
}
static const struct drm_plane_funcs rcar_du_vsp_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.reset = rcar_du_vsp_plane_reset,
.destroy = drm_plane_cleanup,
.atomic_duplicate_state = rcar_du_vsp_plane_atomic_duplicate_state,
.atomic_destroy_state = rcar_du_vsp_plane_atomic_destroy_state,
};
static void rcar_du_vsp_cleanup(struct drm_device *dev, void *res)
{
struct rcar_du_vsp *vsp = res;
unsigned int i;
for (i = 0; i < vsp->num_planes; ++i) {
struct rcar_du_vsp_plane *plane = &vsp->planes[i];
drm_plane_cleanup(&plane->plane);
}
kfree(vsp->planes);
if (vsp->link)
device_link_del(vsp->link);
put_device(vsp->vsp);
}
int rcar_du_vsp_init(struct rcar_du_vsp *vsp, struct device_node *np,
unsigned int crtcs)
{
struct rcar_du_device *rcdu = vsp->dev;
struct platform_device *pdev;
unsigned int num_crtcs = hweight32(crtcs);
unsigned int num_planes;
unsigned int i;
int ret;
/* Find the VSP device and initialize it. */
pdev = of_find_device_by_node(np);
if (!pdev)
return -ENXIO;
vsp->vsp = &pdev->dev;
ret = drmm_add_action_or_reset(&rcdu->ddev, rcar_du_vsp_cleanup, vsp);
if (ret < 0)
return ret;
/*
* Enforce suspend/resume ordering between the DU (consumer) and the
* VSP (supplier). The DU will be suspended before and resume after the
* VSP.
*/
vsp->link = device_link_add(rcdu->dev, vsp->vsp, DL_FLAG_STATELESS);
if (!vsp->link) {
dev_err(rcdu->dev, "Failed to create device link to VSP %s\n",
dev_name(vsp->vsp));
return -EINVAL;
}
ret = vsp1_du_init(vsp->vsp);
if (ret < 0)
return ret;
num_planes = rcdu->info->num_rpf;
vsp->planes = kzalloc_objs(*vsp->planes, num_planes);
if (!vsp->planes)
return -ENOMEM;
for (i = 0; i < num_planes; ++i) {
enum drm_plane_type type = i < num_crtcs
? DRM_PLANE_TYPE_PRIMARY
: DRM_PLANE_TYPE_OVERLAY;
struct rcar_du_vsp_plane *plane = &vsp->planes[i];
unsigned int num_formats;
const u32 *formats;
if (rcdu->info->gen < 4) {
num_formats = ARRAY_SIZE(rcar_du_vsp_formats);
formats = rcar_du_vsp_formats;
} else {
num_formats = ARRAY_SIZE(rcar_du_vsp_formats_gen4);
formats = rcar_du_vsp_formats_gen4;
}
plane->vsp = vsp;
plane->index = i;
ret = drm_universal_plane_init(&rcdu->ddev, &plane->plane,
crtcs, &rcar_du_vsp_plane_funcs,
formats, num_formats,
NULL, type, NULL);
if (ret < 0)
return ret;
drm_plane_helper_add(&plane->plane,
&rcar_du_vsp_plane_helper_funcs);
drm_plane_create_alpha_property(&plane->plane);
drm_plane_create_zpos_property(&plane->plane, i, 0,
num_planes - 1);
drm_plane_create_blend_mode_property(&plane->plane,
BIT(DRM_MODE_BLEND_PIXEL_NONE) |
BIT(DRM_MODE_BLEND_PREMULTI) |
BIT(DRM_MODE_BLEND_COVERAGE));
vsp->num_planes++;
}
return 0;
}
@@ -0,0 +1,95 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* R-Car Display Unit VSP-Based Compositor
*
* Copyright (C) 2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __RCAR_DU_VSP_H__
#define __RCAR_DU_VSP_H__
#include <drm/drm_plane.h>
struct device_link;
struct drm_framebuffer;
struct rcar_du_format_info;
struct rcar_du_vsp;
struct sg_table;
struct rcar_du_vsp_plane {
struct drm_plane plane;
struct rcar_du_vsp *vsp;
unsigned int index;
};
struct rcar_du_vsp {
unsigned int index;
struct device *vsp;
struct device_link *link;
struct rcar_du_device *dev;
struct rcar_du_vsp_plane *planes;
unsigned int num_planes;
};
static inline struct rcar_du_vsp_plane *to_rcar_vsp_plane(struct drm_plane *p)
{
return container_of(p, struct rcar_du_vsp_plane, plane);
}
/**
* struct rcar_du_vsp_plane_state - Driver-specific plane state
* @state: base DRM plane state
* @format: information about the pixel format used by the plane
* @sg_tables: scatter-gather tables for the frame buffer memory
*/
struct rcar_du_vsp_plane_state {
struct drm_plane_state state;
const struct rcar_du_format_info *format;
struct sg_table sg_tables[3];
};
static inline struct rcar_du_vsp_plane_state *
to_rcar_vsp_plane_state(struct drm_plane_state *state)
{
return container_of(state, struct rcar_du_vsp_plane_state, state);
}
#ifdef CONFIG_DRM_RCAR_VSP
int rcar_du_vsp_init(struct rcar_du_vsp *vsp, struct device_node *np,
unsigned int crtcs);
void rcar_du_vsp_enable(struct rcar_du_crtc *crtc);
void rcar_du_vsp_disable(struct rcar_du_crtc *crtc);
void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc);
void rcar_du_vsp_atomic_flush(struct rcar_du_crtc *crtc);
int rcar_du_vsp_map_fb(struct rcar_du_vsp *vsp, struct drm_framebuffer *fb,
struct sg_table sg_tables[3]);
void rcar_du_vsp_unmap_fb(struct rcar_du_vsp *vsp, struct drm_framebuffer *fb,
struct sg_table sg_tables[3]);
#else
static inline int rcar_du_vsp_init(struct rcar_du_vsp *vsp,
struct device_node *np,
unsigned int crtcs)
{
return -ENXIO;
}
static inline void rcar_du_vsp_enable(struct rcar_du_crtc *crtc) { };
static inline void rcar_du_vsp_disable(struct rcar_du_crtc *crtc) { };
static inline void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc) { };
static inline void rcar_du_vsp_atomic_flush(struct rcar_du_crtc *crtc) { };
static inline int rcar_du_vsp_map_fb(struct rcar_du_vsp *vsp,
struct drm_framebuffer *fb,
struct sg_table sg_tables[3])
{
return -ENXIO;
}
static inline void rcar_du_vsp_unmap_fb(struct rcar_du_vsp *vsp,
struct drm_framebuffer *fb,
struct sg_table sg_tables[3])
{
}
#endif
#endif /* __RCAR_DU_VSP_H__ */
@@ -0,0 +1,246 @@
// SPDX-License-Identifier: GPL-2.0
/*
* R-Car Display Unit Writeback Support
*
* Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
*/
#include <drm/drm_atomic_helper.h>
#include <drm/drm_device.h>
#include <drm/drm_edid.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_writeback.h>
#include "rcar_du_crtc.h"
#include "rcar_du_drv.h"
#include "rcar_du_kms.h"
#include "rcar_du_writeback.h"
/**
* struct rcar_du_wb_conn_state - Driver-specific writeback connector state
* @state: base DRM connector state
* @format: format of the writeback framebuffer
*/
struct rcar_du_wb_conn_state {
struct drm_connector_state state;
const struct rcar_du_format_info *format;
};
#define to_rcar_wb_conn_state(s) \
container_of(s, struct rcar_du_wb_conn_state, state)
/**
* struct rcar_du_wb_job - Driver-private data for writeback jobs
* @sg_tables: scatter-gather tables for the framebuffer memory
*/
struct rcar_du_wb_job {
struct sg_table sg_tables[3];
};
static int rcar_du_wb_conn_get_modes(struct drm_connector *connector)
{
struct drm_device *dev = connector->dev;
return drm_add_modes_noedid(connector, dev->mode_config.max_width,
dev->mode_config.max_height);
}
static int rcar_du_wb_prepare_job(struct drm_writeback_connector *connector,
struct drm_writeback_job *job)
{
struct rcar_du_crtc *rcrtc = wb_to_rcar_crtc(connector);
struct rcar_du_wb_job *rjob;
int ret;
if (!job->fb)
return 0;
rjob = kzalloc_obj(*rjob);
if (!rjob)
return -ENOMEM;
/* Map the framebuffer to the VSP. */
ret = rcar_du_vsp_map_fb(rcrtc->vsp, job->fb, rjob->sg_tables);
if (ret < 0) {
kfree(rjob);
return ret;
}
job->priv = rjob;
return 0;
}
static void rcar_du_wb_cleanup_job(struct drm_writeback_connector *connector,
struct drm_writeback_job *job)
{
struct rcar_du_crtc *rcrtc = wb_to_rcar_crtc(connector);
struct rcar_du_wb_job *rjob = job->priv;
if (!job->fb)
return;
rcar_du_vsp_unmap_fb(rcrtc->vsp, job->fb, rjob->sg_tables);
kfree(rjob);
}
static const struct drm_connector_helper_funcs rcar_du_wb_conn_helper_funcs = {
.get_modes = rcar_du_wb_conn_get_modes,
.prepare_writeback_job = rcar_du_wb_prepare_job,
.cleanup_writeback_job = rcar_du_wb_cleanup_job,
};
static struct drm_connector_state *
rcar_du_wb_conn_duplicate_state(struct drm_connector *connector)
{
struct rcar_du_wb_conn_state *copy;
if (WARN_ON(!connector->state))
return NULL;
copy = kzalloc_obj(*copy);
if (!copy)
return NULL;
__drm_atomic_helper_connector_duplicate_state(connector, &copy->state);
return &copy->state;
}
static void rcar_du_wb_conn_destroy_state(struct drm_connector *connector,
struct drm_connector_state *state)
{
__drm_atomic_helper_connector_destroy_state(state);
kfree(to_rcar_wb_conn_state(state));
}
static void rcar_du_wb_conn_reset(struct drm_connector *connector)
{
struct rcar_du_wb_conn_state *state;
if (connector->state) {
rcar_du_wb_conn_destroy_state(connector, connector->state);
connector->state = NULL;
}
state = kzalloc_obj(*state);
if (state == NULL)
return;
__drm_atomic_helper_connector_reset(connector, &state->state);
}
static const struct drm_connector_funcs rcar_du_wb_conn_funcs = {
.reset = rcar_du_wb_conn_reset,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.atomic_duplicate_state = rcar_du_wb_conn_duplicate_state,
.atomic_destroy_state = rcar_du_wb_conn_destroy_state,
};
static int rcar_du_wb_enc_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct rcar_du_wb_conn_state *wb_state =
to_rcar_wb_conn_state(conn_state);
const struct drm_display_mode *mode = &crtc_state->mode;
struct drm_device *dev = encoder->dev;
struct drm_framebuffer *fb;
if (!conn_state->writeback_job)
return 0;
fb = conn_state->writeback_job->fb;
/*
* Verify that the framebuffer format is supported and that its size
* matches the current mode.
*/
if (fb->width != mode->hdisplay || fb->height != mode->vdisplay) {
dev_dbg(dev->dev, "%s: invalid framebuffer size %ux%u\n",
__func__, fb->width, fb->height);
return -EINVAL;
}
wb_state->format = rcar_du_format_info(fb->format->format);
if (wb_state->format == NULL) {
dev_dbg(dev->dev, "%s: unsupported format %p4cc\n", __func__,
&fb->format->format);
return -EINVAL;
}
return 0;
}
static const struct drm_encoder_helper_funcs rcar_du_wb_enc_helper_funcs = {
.atomic_check = rcar_du_wb_enc_atomic_check,
};
/*
* Only RGB formats are currently supported as the VSP outputs RGB to the DU
* and can't convert to YUV separately for writeback.
*/
static const u32 writeback_formats[] = {
DRM_FORMAT_RGB332,
DRM_FORMAT_ARGB4444,
DRM_FORMAT_XRGB4444,
DRM_FORMAT_ARGB1555,
DRM_FORMAT_XRGB1555,
DRM_FORMAT_RGB565,
DRM_FORMAT_BGR888,
DRM_FORMAT_RGB888,
DRM_FORMAT_BGRA8888,
DRM_FORMAT_BGRX8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_XRGB8888,
};
int rcar_du_writeback_init(struct rcar_du_device *rcdu,
struct rcar_du_crtc *rcrtc)
{
struct drm_writeback_connector *wb_conn = &rcrtc->writeback;
drm_connector_helper_add(&wb_conn->base,
&rcar_du_wb_conn_helper_funcs);
return drm_writeback_connector_init(&rcdu->ddev, wb_conn,
&rcar_du_wb_conn_funcs,
&rcar_du_wb_enc_helper_funcs,
writeback_formats,
ARRAY_SIZE(writeback_formats),
1 << drm_crtc_index(&rcrtc->crtc));
}
void rcar_du_writeback_setup(struct rcar_du_crtc *rcrtc,
struct vsp1_du_writeback_config *cfg)
{
struct rcar_du_wb_conn_state *wb_state;
struct drm_connector_state *state;
struct rcar_du_wb_job *rjob;
struct drm_framebuffer *fb;
unsigned int i;
state = rcrtc->writeback.base.state;
if (!state || !state->writeback_job)
return;
fb = state->writeback_job->fb;
rjob = state->writeback_job->priv;
wb_state = to_rcar_wb_conn_state(state);
cfg->pixelformat = wb_state->format->v4l2;
cfg->pitch = fb->pitches[0];
for (i = 0; i < wb_state->format->planes; ++i)
cfg->mem[i] = sg_dma_address(rjob->sg_tables[i].sgl)
+ fb->offsets[i];
drm_writeback_queue_job(&rcrtc->writeback, state);
}
void rcar_du_writeback_complete(struct rcar_du_crtc *rcrtc)
{
drm_writeback_signal_completion(&rcrtc->writeback, 0);
}
@@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* R-Car Display Unit Writeback Support
*
* Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
*/
#ifndef __RCAR_DU_WRITEBACK_H__
#define __RCAR_DU_WRITEBACK_H__
#include <drm/drm_plane.h>
struct rcar_du_crtc;
struct rcar_du_device;
struct vsp1_du_atomic_pipe_config;
#ifdef CONFIG_DRM_RCAR_WRITEBACK
int rcar_du_writeback_init(struct rcar_du_device *rcdu,
struct rcar_du_crtc *rcrtc);
void rcar_du_writeback_setup(struct rcar_du_crtc *rcrtc,
struct vsp1_du_writeback_config *cfg);
void rcar_du_writeback_complete(struct rcar_du_crtc *rcrtc);
#else
static inline int rcar_du_writeback_init(struct rcar_du_device *rcdu,
struct rcar_du_crtc *rcrtc)
{
return -ENXIO;
}
static inline void
rcar_du_writeback_setup(struct rcar_du_crtc *rcrtc,
struct vsp1_du_writeback_config *cfg)
{
}
static inline void rcar_du_writeback_complete(struct rcar_du_crtc *rcrtc)
{
}
#endif
#endif /* __RCAR_DU_WRITEBACK_H__ */
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* R-Car Gen3 HDMI PHY
*
* Copyright (C) 2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <drm/bridge/dw_hdmi.h>
#include <drm/drm_modes.h>
#define RCAR_HDMI_PHY_OPMODE_PLLCFG 0x06 /* Mode of operation and PLL dividers */
#define RCAR_HDMI_PHY_PLLCURRGMPCTRL 0x10 /* PLL current and Gmp (conductance) */
#define RCAR_HDMI_PHY_PLLDIVCTRL 0x11 /* PLL dividers */
struct rcar_hdmi_phy_params {
unsigned long mpixelclock;
u16 opmode_div; /* Mode of operation and PLL dividers */
u16 curr_gmp; /* PLL current and Gmp (conductance) */
u16 div; /* PLL dividers */
};
static const struct rcar_hdmi_phy_params rcar_hdmi_phy_params[] = {
{ 35500000, 0x0003, 0x0344, 0x0328 },
{ 44900000, 0x0003, 0x0285, 0x0128 },
{ 71000000, 0x0002, 0x1184, 0x0314 },
{ 90000000, 0x0002, 0x1144, 0x0114 },
{ 140250000, 0x0001, 0x20c4, 0x030a },
{ 182750000, 0x0001, 0x2084, 0x010a },
{ 281250000, 0x0000, 0x0084, 0x0305 },
{ 297000000, 0x0000, 0x0084, 0x0105 },
{ ~0UL, 0x0000, 0x0000, 0x0000 },
};
static enum drm_mode_status
rcar_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
const struct drm_display_info *info,
const struct drm_display_mode *mode)
{
/*
* The maximum supported clock frequency is 297 MHz, as shown in the PHY
* parameters table.
*/
if (mode->clock > 297000)
return MODE_CLOCK_HIGH;
return MODE_OK;
}
static int rcar_hdmi_phy_configure(struct dw_hdmi *hdmi, void *data,
unsigned long mpixelclock)
{
const struct rcar_hdmi_phy_params *params = rcar_hdmi_phy_params;
for (; params->mpixelclock != ~0UL; ++params) {
if (mpixelclock <= params->mpixelclock)
break;
}
if (params->mpixelclock == ~0UL)
return -EINVAL;
dw_hdmi_phy_i2c_write(hdmi, params->opmode_div,
RCAR_HDMI_PHY_OPMODE_PLLCFG);
dw_hdmi_phy_i2c_write(hdmi, params->curr_gmp,
RCAR_HDMI_PHY_PLLCURRGMPCTRL);
dw_hdmi_phy_i2c_write(hdmi, params->div, RCAR_HDMI_PHY_PLLDIVCTRL);
return 0;
}
static const struct dw_hdmi_plat_data rcar_dw_hdmi_plat_data = {
.output_port = 1,
.mode_valid = rcar_hdmi_mode_valid,
.configure_phy = rcar_hdmi_phy_configure,
};
static int rcar_dw_hdmi_probe(struct platform_device *pdev)
{
struct dw_hdmi *hdmi;
hdmi = dw_hdmi_probe(pdev, &rcar_dw_hdmi_plat_data);
if (IS_ERR(hdmi))
return PTR_ERR(hdmi);
platform_set_drvdata(pdev, hdmi);
return 0;
}
static void rcar_dw_hdmi_remove(struct platform_device *pdev)
{
struct dw_hdmi *hdmi = platform_get_drvdata(pdev);
dw_hdmi_remove(hdmi);
}
static const struct of_device_id rcar_dw_hdmi_of_table[] = {
{ .compatible = "renesas,rcar-gen3-hdmi" },
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, rcar_dw_hdmi_of_table);
static struct platform_driver rcar_dw_hdmi_platform_driver = {
.probe = rcar_dw_hdmi_probe,
.remove = rcar_dw_hdmi_remove,
.driver = {
.name = "rcar-dw-hdmi",
.of_match_table = rcar_dw_hdmi_of_table,
},
};
module_platform_driver(rcar_dw_hdmi_platform_driver);
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
MODULE_DESCRIPTION("Renesas R-Car Gen3 HDMI Encoder Driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* R-Car LVDS Encoder
*
* Copyright (C) 2013-2018 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __RCAR_LVDS_H__
#define __RCAR_LVDS_H__
struct drm_bridge;
#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
int rcar_lvds_pclk_enable(struct drm_bridge *bridge, unsigned long freq,
bool dot_clk_only);
void rcar_lvds_pclk_disable(struct drm_bridge *bridge, bool dot_clk_only);
bool rcar_lvds_dual_link(struct drm_bridge *bridge);
bool rcar_lvds_is_connected(struct drm_bridge *bridge);
#else
static inline int rcar_lvds_pclk_enable(struct drm_bridge *bridge,
unsigned long freq, bool dot_clk_only)
{
return -ENOSYS;
}
static inline void rcar_lvds_pclk_disable(struct drm_bridge *bridge,
bool dot_clock_only)
{
}
static inline bool rcar_lvds_dual_link(struct drm_bridge *bridge)
{
return false;
}
static inline bool rcar_lvds_is_connected(struct drm_bridge *bridge)
{
return false;
}
#endif /* CONFIG_DRM_RCAR_LVDS */
#endif /* __RCAR_LVDS_H__ */
@@ -0,0 +1,111 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* R-Car LVDS Interface Registers Definitions
*
* Copyright (C) 2013-2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __RCAR_LVDS_REGS_H__
#define __RCAR_LVDS_REGS_H__
#define LVDCR0 0x0000
#define LVDCR0_DUSEL (1 << 15)
#define LVDCR0_DMD (1 << 12) /* Gen2 only */
#define LVDCR0_LVMD_MASK (0xf << 8)
#define LVDCR0_LVMD_SHIFT 8
#define LVDCR0_PLLON (1 << 4)
#define LVDCR0_PWD (1 << 2) /* Gen3 only */
#define LVDCR0_BEN (1 << 2) /* Gen2 only */
#define LVDCR0_LVEN (1 << 1)
#define LVDCR0_LVRES (1 << 0)
#define LVDCR1 0x0004
#define LVDCR1_CKSEL (1 << 15) /* Gen2 only */
#define LVDCR1_CHSTBY(n) (3 << (2 + (n) * 2))
#define LVDCR1_CLKSTBY (3 << 0)
#define LVDPLLCR 0x0008
/* Gen2 & V3M */
#define LVDPLLCR_CEEN (1 << 14)
#define LVDPLLCR_FBEN (1 << 13)
#define LVDPLLCR_COSEL (1 << 12)
#define LVDPLLCR_PLLDLYCNT_150M (0x1bf << 0)
#define LVDPLLCR_PLLDLYCNT_121M (0x22c << 0)
#define LVDPLLCR_PLLDLYCNT_60M (0x77b << 0)
#define LVDPLLCR_PLLDLYCNT_38M (0x69a << 0)
#define LVDPLLCR_PLLDLYCNT_MASK (0x7ff << 0)
/* Gen3 but V3M,D3 and E3 */
#define LVDPLLCR_PLLDIVCNT_42M (0x014cb << 0)
#define LVDPLLCR_PLLDIVCNT_85M (0x00a45 << 0)
#define LVDPLLCR_PLLDIVCNT_128M (0x006c3 << 0)
#define LVDPLLCR_PLLDIVCNT_148M (0x046c1 << 0)
#define LVDPLLCR_PLLDIVCNT_MASK (0x7ffff << 0)
/* D3 and E3 */
#define LVDPLLCR_PLLON (1 << 22)
#define LVDPLLCR_PLLSEL_PLL0 (0 << 20)
#define LVDPLLCR_PLLSEL_LVX (1 << 20)
#define LVDPLLCR_PLLSEL_PLL1 (2 << 20)
#define LVDPLLCR_CKSEL_LVX (1 << 17)
#define LVDPLLCR_CKSEL_EXTAL (3 << 17)
#define LVDPLLCR_CKSEL_DU_DOTCLKIN(n) ((5 + (n) * 2) << 17)
#define LVDPLLCR_OCKSEL (1 << 16)
#define LVDPLLCR_STP_CLKOUTE (1 << 14)
#define LVDPLLCR_OUTCLKSEL (1 << 12)
#define LVDPLLCR_CLKOUT (1 << 11)
#define LVDPLLCR_PLLE(n) ((n) << 10)
#define LVDPLLCR_PLLN(n) ((n) << 3)
#define LVDPLLCR_PLLM(n) ((n) << 0)
#define LVDCTRCR 0x000c
#define LVDCTRCR_CTR3SEL_ZERO (0 << 12)
#define LVDCTRCR_CTR3SEL_ODD (1 << 12)
#define LVDCTRCR_CTR3SEL_CDE (2 << 12)
#define LVDCTRCR_CTR3SEL_MASK (7 << 12)
#define LVDCTRCR_CTR2SEL_DISP (0 << 8)
#define LVDCTRCR_CTR2SEL_ODD (1 << 8)
#define LVDCTRCR_CTR2SEL_CDE (2 << 8)
#define LVDCTRCR_CTR2SEL_HSYNC (3 << 8)
#define LVDCTRCR_CTR2SEL_VSYNC (4 << 8)
#define LVDCTRCR_CTR2SEL_MASK (7 << 8)
#define LVDCTRCR_CTR1SEL_VSYNC (0 << 4)
#define LVDCTRCR_CTR1SEL_DISP (1 << 4)
#define LVDCTRCR_CTR1SEL_ODD (2 << 4)
#define LVDCTRCR_CTR1SEL_CDE (3 << 4)
#define LVDCTRCR_CTR1SEL_HSYNC (4 << 4)
#define LVDCTRCR_CTR1SEL_MASK (7 << 4)
#define LVDCTRCR_CTR0SEL_HSYNC (0 << 0)
#define LVDCTRCR_CTR0SEL_VSYNC (1 << 0)
#define LVDCTRCR_CTR0SEL_DISP (2 << 0)
#define LVDCTRCR_CTR0SEL_ODD (3 << 0)
#define LVDCTRCR_CTR0SEL_CDE (4 << 0)
#define LVDCTRCR_CTR0SEL_MASK (7 << 0)
#define LVDCHCR 0x0010
#define LVDCHCR_CHSEL_CH(n, c) ((((c) - (n)) & 3) << ((n) * 4))
#define LVDCHCR_CHSEL_MASK(n) (3 << ((n) * 4))
/* All registers below are specific to D3 and E3 */
#define LVDSTRIPE 0x0014
#define LVDSTRIPE_ST_TRGSEL_DISP (0 << 2)
#define LVDSTRIPE_ST_TRGSEL_HSYNC_R (1 << 2)
#define LVDSTRIPE_ST_TRGSEL_HSYNC_F (2 << 2)
#define LVDSTRIPE_ST_SWAP (1 << 1)
#define LVDSTRIPE_ST_ON (1 << 0)
#define LVDSCR 0x0018
#define LVDSCR_DEPTH(n) (((n) - 1) << 29)
#define LVDSCR_BANDSET (1 << 28)
#define LVDSCR_TWGCNT(n) ((((n) - 256) / 16) << 24)
#define LVDSCR_SDIV(n) ((n) << 22)
#define LVDSCR_MODE (1 << 21)
#define LVDSCR_RSTN (1 << 20)
#define LVDDIV 0x001c
#define LVDDIV_DIVSEL (1 << 8)
#define LVDDIV_DIVRESET (1 << 7)
#define LVDDIV_DIVSTP (1 << 6)
#define LVDDIV_DIV(n) ((n) << 0)
#endif /* __RCAR_LVDS_REGS_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* R-Car DSI Encoder
*
* Copyright (C) 2022 Renesas Electronics Corporation
*
* Contact: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
*/
#ifndef __RCAR_MIPI_DSI_H__
#define __RCAR_MIPI_DSI_H__
struct drm_atomic_commit;
struct drm_bridge;
#if IS_ENABLED(CONFIG_DRM_RCAR_MIPI_DSI)
void rcar_mipi_dsi_pclk_enable(struct drm_bridge *bridge,
struct drm_atomic_commit *state);
void rcar_mipi_dsi_pclk_disable(struct drm_bridge *bridge);
#else
static inline void rcar_mipi_dsi_pclk_enable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
}
static inline void rcar_mipi_dsi_pclk_disable(struct drm_bridge *bridge)
{
}
#endif /* CONFIG_DRM_RCAR_MIPI_DSI */
#endif /* __RCAR_MIPI_DSI_H__ */
@@ -0,0 +1,319 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* R-Car MIPI DSI Interface Registers Definitions
*
* Copyright (C) 2020 Renesas Electronics Corporation
*/
#ifndef __RCAR_MIPI_DSI_REGS_H__
#define __RCAR_MIPI_DSI_REGS_H__
#define LINKSR 0x010
#define LINKSR_LPBUSY BIT_U32(1)
#define LINKSR_HSBUSY BIT_U32(0)
#define TXSETR 0x100
#define TXSETR_LANECNT_MASK GENMASK_U32(1, 0)
/*
* DSI Command Transfer Registers
*/
#define TXCMSETR 0x110
#define TXCMSETR_SPDTYP BIT_U32(8) /* 0:HS 1:LP */
#define TXCMSETR_LPPDACC BIT_U32(0)
#define TXCMCR 0x120
#define TXCMCR_BTATYP BIT_U32(2)
#define TXCMCR_BTAREQ BIT_U32(1)
#define TXCMCR_TXREQ BIT_U32(0)
#define TXCMSR 0x130
#define TXCMSR_CLSNERR BIT_U32(18)
#define TXCMSR_AXIERR BIT_U32(16)
#define TXCMSR_TXREQEND BIT_U32(0)
#define TXCMSCR 0x134
#define TXCMSCR_CLSNERR BIT_U32(18)
#define TXCMSCR_AXIERR BIT_U32(16)
#define TXCMSCR_TXREQEND BIT_U32(0)
#define TXCMIER 0x138
#define TXCMIER_CLSNERR BIT_U32(18)
#define TXCMIER_AXIERR BIT_U32(16)
#define TXCMIER_TXREQEND BIT_U32(0)
#define TXCMADDRSET0R 0x140
#define TXCMPHDR 0x150
#define TXCMPHDR_FMT BIT_U32(24) /* 0:SP 1:LP */
#define TXCMPHDR_VC_MASK GENMASK_U32(23, 22)
#define TXCMPHDR_VC(n) FIELD_PREP(TXCMPHDR_VC_MASK, (n))
#define TXCMPHDR_DT_MASK GENMASK_U32(21, 16)
#define TXCMPHDR_DT(n) FIELD_PREP(TXCMPHDR_DT_MASK, (n))
#define TXCMPHDR_DATA1_MASK GENMASK_U32(15, 8)
#define TXCMPHDR_DATA1(n) FIELD_PREP(TXCMPHDR_DATA1_MASK, (n))
#define TXCMPHDR_DATA0_MASK GENMASK_U32(7, 0)
#define TXCMPHDR_DATA0(n) FIELD_PREP(TXCMPHDR_DATA0_MASK, (n))
#define TXCMPPD0R 0x160
#define TXCMPPD1R 0x164
#define TXCMPPD2R 0x168
#define TXCMPPD3R 0x16c
#define RXSETR 0x200
#define RXSETR_CRCEN_MASK GENMASK_U32(27, 24)
#define RXSETR_ECCEN_MASK GENMASK_U32(19, 16)
#define RXPSETR 0x210
#define RXPSETR_LPPDACC BIT_U32(0)
#define RXPSR 0x220
#define RXPSR_ECCERR1B BIT_U32(28)
#define RXPSR_UEXTRGERR BIT_U32(25)
#define RXPSR_RESPTOERR BIT_U32(24)
#define RXPSR_OVRERR BIT_U32(23)
#define RXPSR_AXIERR BIT_U32(22)
#define RXPSR_CRCERR BIT_U32(21)
#define RXPSR_WCERR BIT_U32(20)
#define RXPSR_UEXDTERR BIT_U32(19)
#define RXPSR_UEXPKTERR BIT_U32(18)
#define RXPSR_ECCERR BIT_U32(17)
#define RXPSR_MLFERR BIT_U32(16)
#define RXPSR_RCVACK BIT_U32(14)
#define RXPSR_RCVEOT BIT_U32(10)
#define RXPSR_RCVAKE BIT_U32(9)
#define RXPSR_RCVRESP BIT_U32(8)
#define RXPSR_BTAREQEND BIT_U32(0)
#define RXPSCR 0x224
#define RXPSCR_ECCERR1B BIT_U32(28)
#define RXPSCR_UEXTRGERR BIT_U32(25)
#define RXPSCR_RESPTOERR BIT_U32(24)
#define RXPSCR_OVRERR BIT_U32(23)
#define RXPSCR_AXIERR BIT_U32(22)
#define RXPSCR_CRCERR BIT_U32(21)
#define RXPSCR_WCERR BIT_U32(20)
#define RXPSCR_UEXDTERR BIT_U32(19)
#define RXPSCR_UEXPKTERR BIT_U32(18)
#define RXPSCR_ECCERR BIT_U32(17)
#define RXPSCR_MLFERR BIT_U32(16)
#define RXPSCR_RCVACK BIT_U32(14)
#define RXPSCR_RCVEOT BIT_U32(10)
#define RXPSCR_RCVAKE BIT_U32(9)
#define RXPSCR_RCVRESP BIT_U32(8)
#define RXPSCR_BTAREQEND BIT_U32(0)
#define RXPIER 0x228
#define RXPIER_ECCERR1B BIT_U32(28)
#define RXPIER_UEXTRGERR BIT_U32(25)
#define RXPIER_RESPTOERR BIT_U32(24)
#define RXPIER_OVRERR BIT_U32(23)
#define RXPIER_AXIERR BIT_U32(22)
#define RXPIER_CRCERR BIT_U32(21)
#define RXPIER_WCERR BIT_U32(20)
#define RXPIER_UEXDTERR BIT_U32(19)
#define RXPIER_UEXPKTERR BIT_U32(18)
#define RXPIER_ECCERR BIT_U32(17)
#define RXPIER_MLFERR BIT_U32(16)
#define RXPIER_RCVACK BIT_U32(14)
#define RXPIER_RCVEOT BIT_U32(10)
#define RXPIER_RCVAKE BIT_U32(9)
#define RXPIER_RCVRESP BIT_U32(8)
#define RXPIER_BTAREQEND BIT_U32(0)
#define RXPADDRSET0R 0x230
#define RXPSIZESETR 0x238
#define RXPSIZESETR_SIZE_MASK GENMASK_U32(6, 3)
#define RXPHDR 0x240
#define RXPHDR_FMT BIT_U32(24) /* 0:SP 1:LP */
#define RXPHDR_VC_MASK GENMASK_U32(23, 22)
#define RXPHDR_DT_MASK GENMASK_U32(21, 16)
#define RXPHDR_DATA1_MASK GENMASK_U32(15, 8)
#define RXPHDR_DATA0_MASK GENMASK_U32(7, 0)
#define RXPPD0R 0x250
#define RXPPD1R 0x254
#define RXPPD2R 0x258
#define RXPPD3R 0x25c
#define AKEPR 0x300
#define AKEPR_VC_MASK GENMASK_U32(23, 22)
#define AKEPR_DT_MASK GENMASK_U32(21, 16)
#define AKEPR_ERRRPT_MASK GENMASK_U32(15, 0)
#define RXRESPTOSETR 0x400
#define TACR 0x500
#define TASR 0x510
#define TASCR 0x514
#define TAIER 0x518
#define TOSR 0x610
#define TOSR_TATO BIT_U32(2)
#define TOSR_LRXHTO BIT_U32(1)
#define TOSR_HRXTO BIT_U32(0)
#define TOSCR 0x614
#define TOSCR_TATO BIT_U32(2)
#define TOSCR_LRXHTO BIT_U32(1)
#define TOSCR_HRXTO BIT_U32(0)
/*
* Video Mode Register
*/
#define TXVMSETR 0x180
#define TXVMSETR_SYNSEQ_EVENTS BIT_U32(16) /* 0:Pulses 1:Events */
#define TXVMSETR_VSTPM BIT_U32(15)
#define TXVMSETR_PIXWDTH_MASK GENMASK_U32(10, 8)
#define TXVMSETR_PIXWDTH BIT_U32(8) /* Only allowed value */
#define TXVMSETR_VSEN BIT_U32(4)
#define TXVMSETR_HFPBPEN BIT_U32(2)
#define TXVMSETR_HBPBPEN BIT_U32(1)
#define TXVMSETR_HSABPEN BIT_U32(0)
#define TXVMCR 0x190
#define TXVMCR_VFCLR BIT_U32(12)
#define TXVMCR_EN_VIDEO BIT_U32(0)
#define TXVMSR 0x1a0
#define TXVMSR_STR BIT_U32(16)
#define TXVMSR_VFRDY BIT_U32(12)
#define TXVMSR_ACT BIT_U32(8)
#define TXVMSR_RDY BIT_U32(0)
#define TXVMSCR 0x1a4
#define TXVMSCR_STR BIT_U32(16)
#define TXVMPSPHSETR 0x1c0
#define TXVMPSPHSETR_DT_MASK (0x3f << 16)
#define TXVMPSPHSETR_DT_RGB16 FIELD_PREP(TXVMPSPHSETR_DT_MASK, 0x0e)
#define TXVMPSPHSETR_DT_RGB18 FIELD_PREP(TXVMPSPHSETR_DT_MASK, 0x1e)
#define TXVMPSPHSETR_DT_RGB18_LS FIELD_PREP(TXVMPSPHSETR_DT_MASK, 0x2e)
#define TXVMPSPHSETR_DT_RGB24 FIELD_PREP(TXVMPSPHSETR_DT_MASK, 0x3e)
#define TXVMPSPHSETR_DT_YCBCR16 FIELD_PREP(TXVMPSPHSETR_DT_MASK, 0x2c)
#define TXVMVPRMSET0R 0x1d0
#define TXVMVPRMSET0R_HSPOL_LOW BIT_U32(17) /* 0:High 1:Low */
#define TXVMVPRMSET0R_VSPOL_LOW BIT_U32(16) /* 0:High 1:Low */
#define TXVMVPRMSET0R_CSPC_YCbCr BIT_U32(4) /* 0:RGB 1:YCbCr */
#define TXVMVPRMSET0R_BPP_MASK GENMASK_U32(2, 0)
#define TXVMVPRMSET0R_BPP_16 FIELD_PREP(TXVMVPRMSET0R_BPP_MASK, 0)
#define TXVMVPRMSET0R_BPP_18 FIELD_PREP(TXVMVPRMSET0R_BPP_MASK, 1)
#define TXVMVPRMSET0R_BPP_24 FIELD_PREP(TXVMVPRMSET0R_BPP_MASK, 2)
#define TXVMVPRMSET1R 0x1d4
#define TXVMVPRMSET1R_VACTIVE_MASK GENMASK_U32(30, 16)
#define TXVMVPRMSET1R_VACTIVE(n) FIELD_PREP(TXVMVPRMSET1R_VACTIVE_MASK, (n))
#define TXVMVPRMSET1R_VSA_MASK GENMASK_U32(11, 0)
#define TXVMVPRMSET1R_VSA(n) FIELD_PREP(TXVMVPRMSET1R_VSA_MASK, (n))
#define TXVMVPRMSET2R 0x1d8
#define TXVMVPRMSET2R_VFP_MASK GENMASK_U32(28, 16)
#define TXVMVPRMSET2R_VFP(n) FIELD_PREP(TXVMVPRMSET2R_VFP_MASK, (n))
#define TXVMVPRMSET2R_VBP_MASK GENMASK_U32(12, 0)
#define TXVMVPRMSET2R_VBP(n) FIELD_PREP(TXVMVPRMSET2R_VBP_MASK, (n))
#define TXVMVPRMSET3R 0x1dc
#define TXVMVPRMSET3R_HACTIVE_MASK GENMASK_U32(30, 16)
#define TXVMVPRMSET3R_HACTIVE(n) FIELD_PREP(TXVMVPRMSET3R_HACTIVE_MASK, (n))
#define TXVMVPRMSET3R_HSA_MASK GENMASK_U32(11, 0)
#define TXVMVPRMSET3R_HSA(n) FIELD_PREP(TXVMVPRMSET3R_HSA_MASK, (n))
#define TXVMVPRMSET4R 0x1e0
#define TXVMVPRMSET4R_HFP_MASK GENMASK_U32(28, 16)
#define TXVMVPRMSET4R_HFP(n) FIELD_PREP(TXVMVPRMSET4R_HFP_MASK, (n))
#define TXVMVPRMSET4R_HBP_MASK GENMASK_U32(12, 0)
#define TXVMVPRMSET4R_HBP(n) FIELD_PREP(TXVMVPRMSET4R_HBP_MASK, (n))
/*
* PHY-Protocol Interface (PPI) Registers
*/
#define PPISETR 0x700
#define PPISETR_DLEN_MASK GENMASK_U32(3, 0)
#define PPISETR_CLEN BIT_U32(8)
#define PPICLCR 0x710
#define PPICLCR_TXREQHS BIT_U32(8)
#define PPICLCR_TXULPSEXT BIT_U32(1)
#define PPICLCR_TXULPSCLK BIT_U32(0)
#define PPICLSR 0x720
#define PPICLSR_HSTOLP BIT_U32(27)
#define PPICLSR_TOHS BIT_U32(26)
#define PPICLSR_STPST BIT_U32(0)
#define PPICLSCR 0x724
#define PPICLSCR_HSTOLP BIT_U32(27)
#define PPICLSCR_TOHS BIT_U32(26)
#define PPIDL0SR 0x740
#define PPIDL0SR_DIR BIT_U32(10)
#define PPIDL0SR_STPST BIT_U32(6)
#define PPIDLSR 0x760
#define PPIDLSR_STPST GENMASK_U32(3, 0)
/*
* Clocks registers
*/
#define LPCLKSET 0x1000
#define LPCLKSET_CKEN BIT_U32(8)
#define LPCLKSET_LPCLKDIV_MASK GENMASK_U32(5, 0)
#define CFGCLKSET 0x1004
#define CFGCLKSET_CKEN BIT_U32(8)
#define CFGCLKSET_CFGCLKDIV_MASK GENMASK_U32(5, 0)
#define DOTCLKDIV 0x1008
#define DOTCLKDIV_CKEN BIT_U32(8)
#define DOTCLKDIV_DOTCLKDIV_MASK GENMASK_U32(5, 0)
#define VCLKSET 0x100c
#define VCLKSET_CKEN BIT_U32(16)
#define VCLKSET_COLOR_YCC BIT_U32(8) /* 0:RGB 1:YCbCr */
#define VCLKSET_DIV_V3U_MASK GENMASK_U32(5, 4)
#define VCLKSET_DIV_V3U(n) FIELD_PREP(VCLKSET_DIV_V3U_MASK, (n))
#define VCLKSET_DIV_V4H_MASK GENMASK_U32(6, 4)
#define VCLKSET_DIV_V4H(n) FIELD_PREP(VCLKSET_DIV_V4H_MASK, (n))
#define VCLKSET_BPP_MASK GENMASK_U32(3, 2)
#define VCLKSET_BPP_16 FIELD_PREP(VCLKSET_BPP_MASK, 0)
#define VCLKSET_BPP_18 FIELD_PREP(VCLKSET_BPP_MASK, 1)
#define VCLKSET_BPP_18L FIELD_PREP(VCLKSET_BPP_MASK, 2)
#define VCLKSET_BPP_24 FIELD_PREP(VCLKSET_BPP_MASK, 3)
#define VCLKSET_LANE_MASK GENMASK_U32(1, 0)
#define VCLKSET_LANE(n) FIELD_PREP(VCLKSET_LANE_MASK, (n))
#define VCLKEN 0x1010
#define VCLKEN_CKEN BIT_U32(0)
#define PHYSETUP 0x1014
#define PHYSETUP_HSFREQRANGE_MASK GENMASK_U32(22, 16)
#define PHYSETUP_HSFREQRANGE(n) FIELD_PREP(PHYSETUP_HSFREQRANGE_MASK, (n))
#define PHYSETUP_CFGCLKFREQRANGE_MASK GENMASK_U32(13, 8)
#define PHYSETUP_SHUTDOWNZ BIT_U32(1)
#define PHYSETUP_RSTZ BIT_U32(0)
#define CLOCKSET1 0x101c
#define CLOCKSET1_LOCK_PHY BIT_U32(17)
#define CLOCKSET1_CLKSEL BIT_U32(8)
#define CLOCKSET1_CLKINSEL_MASK GENMASK_U32(3, 2)
#define CLOCKSET1_CLKINSEL_EXTAL FIELD_PREP(CLOCKSET1_CLKINSEL_MASK, 0)
#define CLOCKSET1_CLKINSEL_DIG FIELD_PREP(CLOCKSET1_CLKINSEL_MASK, 1)
#define CLOCKSET1_CLKINSEL_DU FIELD_PREP(CLOCKSET1_CLKINSEL_MASK, 2)
#define CLOCKSET1_SHADOW_CLEAR BIT_U32(1)
#define CLOCKSET1_UPDATEPLL BIT_U32(0)
#define CLOCKSET2 0x1020
#define CLOCKSET2_M_MASK GENMASK_U32(27, 16)
#define CLOCKSET2_M(n) FIELD_PREP(CLOCKSET2_M_MASK, (n))
#define CLOCKSET2_VCO_CNTRL_MASK GENMASK_U32(13, 8)
#define CLOCKSET2_VCO_CNTRL(n) FIELD_PREP(CLOCKSET2_VCO_CNTRL_MASK, (n))
#define CLOCKSET2_N_MASK GENMASK_U32(3, 0)
#define CLOCKSET2_N(n) FIELD_PREP(CLOCKSET2_N_MASK, (n))
#define CLOCKSET3 0x1024
#define CLOCKSET3_PROP_CNTRL_MASK GENMASK_U32(29, 24)
#define CLOCKSET3_PROP_CNTRL(n) FIELD_PREP(CLOCKSET3_PROP_CNTRL_MASK, (n))
#define CLOCKSET3_INT_CNTRL_MASK GENMASK_U32(21, 16)
#define CLOCKSET3_INT_CNTRL(n) FIELD_PREP(CLOCKSET3_INT_CNTRL_MASK, (n))
#define CLOCKSET3_CPBIAS_CNTRL_MASK GENMASK_U32(14, 8)
#define CLOCKSET3_CPBIAS_CNTRL(n) FIELD_PREP(CLOCKSET3_CPBIAS_CNTRL_MASK, (n))
#define CLOCKSET3_GMP_CNTRL_MASK GENMASK_U32(1, 0)
#define CLOCKSET3_GMP_CNTRL(n) FIELD_PREP(CLOCKSET3_GMP_CNTRL_MASK, (n))
#define PHTW 0x1034
#define PHTW_DWEN BIT_U32(24)
#define PHTW_TESTDIN_DATA_MASK GENMASK_U32(23, 16)
#define PHTW_CWEN BIT_U32(8)
#define PHTW_TESTDIN_CODE_MASK GENMASK_U32(7, 0)
#define PHTR 0x1038
#define PHTR_TESTDOUT GENMASK_U32(23, 16)
#define PHTR_TESTDOUT_TEST BIT_U32(16)
#define PHTC 0x103c
#define PHTC_TESTCLR BIT_U32(0)
#endif /* __RCAR_MIPI_DSI_REGS_H__ */
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: GPL-2.0
config DRM_RZG2L_DU
tristate "DRM Support for RZ/G2L Display Unit"
depends on ARCH_RENESAS || COMPILE_TEST
depends on DRM && OF
depends on VIDEO_RENESAS_VSP1
select DRM_CLIENT_SELECTION
select DRM_GEM_DMA_HELPER
select DRM_KMS_HELPER
select DRM_DISPLAY_HELPER
select DRM_BRIDGE_CONNECTOR
select VIDEOMODE_HELPERS
help
Choose this option if you have an RZ/G2L alike chipset.
If M is selected the module will be called rzg2l-du-drm.
config DRM_RZG2L_USE_MIPI_DSI
bool "RZ/G2L MIPI DSI Encoder Support"
depends on DRM_BRIDGE && OF
depends on DRM_RZG2L_DU || COMPILE_TEST
default DRM_RZG2L_DU
help
Enable support for the RZ/G2L Display Unit embedded MIPI DSI encoders.
config DRM_RZG2L_MIPI_DSI
def_tristate DRM_RZG2L_DU
depends on DRM_RZG2L_USE_MIPI_DSI
select DRM_MIPI_DSI
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
rzg2l-du-drm-y := rzg2l_du_crtc.o \
rzg2l_du_drv.o \
rzg2l_du_encoder.o \
rzg2l_du_kms.o \
rzg2l-du-drm-$(CONFIG_VIDEO_RENESAS_VSP1) += rzg2l_du_vsp.o
obj-$(CONFIG_DRM_RZG2L_DU) += rzg2l-du-drm.o
obj-$(CONFIG_DRM_RZG2L_MIPI_DSI) += rzg2l_mipi_dsi.o
@@ -0,0 +1,427 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* RZ/G2L Display Unit CRTCs
*
* Copyright (C) 2023 Renesas Electronics Corporation
*
* Based on rcar_du_crtc.c
*/
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_crtc.h>
#include <drm/drm_device.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_vblank.h>
#include "rzg2l_du_crtc.h"
#include "rzg2l_du_drv.h"
#include "rzg2l_du_encoder.h"
#include "rzg2l_du_kms.h"
#include "rzg2l_du_vsp.h"
#define DU_MCR0 0x00
#define DU_MCR0_DPI_EN BIT(0)
#define DU_MCR0_DI_EN BIT(8)
#define DU_DITR0 0x10
#define DU_DITR0_DEMD_HIGH (BIT(8) | BIT(9))
#define DU_DITR0_VSPOL BIT(16)
#define DU_DITR0_HSPOL BIT(17)
#define DU_DITR1 0x14
#define DU_DITR1_VSA(x) ((x) << 0)
#define DU_DITR1_VACTIVE(x) ((x) << 16)
#define DU_DITR2 0x18
#define DU_DITR2_VBP(x) ((x) << 0)
#define DU_DITR2_VFP(x) ((x) << 16)
#define DU_DITR3 0x1c
#define DU_DITR3_HSA(x) ((x) << 0)
#define DU_DITR3_HACTIVE(x) ((x) << 16)
#define DU_DITR4 0x20
#define DU_DITR4_HBP(x) ((x) << 0)
#define DU_DITR4_HFP(x) ((x) << 16)
#define DU_MCR1 0x40
#define DU_MCR1_PB_AUTOCLR BIT(16)
#define DU_PBCR0 0x4c
#define DU_PBCR0_PB_DEP(x) ((x) << 0)
/* -----------------------------------------------------------------------------
* Hardware Setup
*/
static void rzg2l_du_crtc_set_display_timing(struct rzg2l_du_crtc *rcrtc)
{
const struct drm_display_mode *mode = &rcrtc->crtc.state->adjusted_mode;
unsigned long mode_clock = mode->clock * 1000;
u32 ditr0, ditr1, ditr2, ditr3, ditr4, pbcr0;
struct rzg2l_du_device *rcdu = rcrtc->dev;
clk_prepare_enable(rcrtc->rzg2l_clocks.dclk);
clk_set_rate(rcrtc->rzg2l_clocks.dclk, mode_clock);
ditr0 = (DU_DITR0_DEMD_HIGH
| ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? DU_DITR0_VSPOL : 0)
| ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? DU_DITR0_HSPOL : 0));
ditr1 = DU_DITR1_VSA(mode->vsync_end - mode->vsync_start)
| DU_DITR1_VACTIVE(mode->vdisplay);
ditr2 = DU_DITR2_VBP(mode->vtotal - mode->vsync_end)
| DU_DITR2_VFP(mode->vsync_start - mode->vdisplay);
ditr3 = DU_DITR3_HSA(mode->hsync_end - mode->hsync_start)
| DU_DITR3_HACTIVE(mode->hdisplay);
ditr4 = DU_DITR4_HBP(mode->htotal - mode->hsync_end)
| DU_DITR4_HFP(mode->hsync_start - mode->hdisplay);
pbcr0 = DU_PBCR0_PB_DEP(0x1f);
writel(ditr0, rcdu->mmio + DU_DITR0);
writel(ditr1, rcdu->mmio + DU_DITR1);
writel(ditr2, rcdu->mmio + DU_DITR2);
writel(ditr3, rcdu->mmio + DU_DITR3);
writel(ditr4, rcdu->mmio + DU_DITR4);
writel(pbcr0, rcdu->mmio + DU_PBCR0);
/* Enable auto clear */
writel(DU_MCR1_PB_AUTOCLR, rcdu->mmio + DU_MCR1);
}
/* -----------------------------------------------------------------------------
* Page Flip
*/
void rzg2l_du_crtc_finish_page_flip(struct rzg2l_du_crtc *rcrtc)
{
struct drm_pending_vblank_event *event;
struct drm_device *dev = rcrtc->crtc.dev;
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags);
event = rcrtc->event;
rcrtc->event = NULL;
spin_unlock_irqrestore(&dev->event_lock, flags);
if (!event)
return;
spin_lock_irqsave(&dev->event_lock, flags);
drm_crtc_send_vblank_event(&rcrtc->crtc, event);
wake_up(&rcrtc->flip_wait);
spin_unlock_irqrestore(&dev->event_lock, flags);
drm_crtc_vblank_put(&rcrtc->crtc);
}
static bool rzg2l_du_crtc_page_flip_pending(struct rzg2l_du_crtc *rcrtc)
{
struct drm_device *dev = rcrtc->crtc.dev;
unsigned long flags;
bool pending;
spin_lock_irqsave(&dev->event_lock, flags);
pending = rcrtc->event;
spin_unlock_irqrestore(&dev->event_lock, flags);
return pending;
}
static void rzg2l_du_crtc_wait_page_flip(struct rzg2l_du_crtc *rcrtc)
{
struct rzg2l_du_device *rcdu = rcrtc->dev;
if (wait_event_timeout(rcrtc->flip_wait,
!rzg2l_du_crtc_page_flip_pending(rcrtc),
msecs_to_jiffies(50)))
return;
dev_warn(rcdu->dev, "page flip timeout\n");
rzg2l_du_crtc_finish_page_flip(rcrtc);
}
/* -----------------------------------------------------------------------------
* Start/Stop and Suspend/Resume
*/
static void rzg2l_du_crtc_setup(struct rzg2l_du_crtc *rcrtc)
{
/* Configure display timings and output routing */
rzg2l_du_crtc_set_display_timing(rcrtc);
/* Enable the VSP compositor. */
rzg2l_du_vsp_enable(rcrtc);
/* Turn vertical blanking interrupt reporting on. */
drm_crtc_vblank_on(&rcrtc->crtc);
}
static int rzg2l_du_crtc_get(struct rzg2l_du_crtc *rcrtc)
{
int ret;
/*
* Guard against double-get, as the function is called from both the
* .atomic_enable() and .atomic_flush() handlers.
*/
if (rcrtc->initialized)
return 0;
ret = clk_prepare_enable(rcrtc->rzg2l_clocks.aclk);
if (ret < 0)
return ret;
ret = clk_prepare_enable(rcrtc->rzg2l_clocks.pclk);
if (ret < 0)
goto error_bus_clock;
ret = reset_control_deassert(rcrtc->rstc);
if (ret < 0)
goto error_peri_clock;
rzg2l_du_crtc_setup(rcrtc);
rcrtc->initialized = true;
return 0;
error_peri_clock:
clk_disable_unprepare(rcrtc->rzg2l_clocks.pclk);
error_bus_clock:
clk_disable_unprepare(rcrtc->rzg2l_clocks.aclk);
return ret;
}
static void rzg2l_du_crtc_put(struct rzg2l_du_crtc *rcrtc)
{
clk_disable_unprepare(rcrtc->rzg2l_clocks.dclk);
reset_control_assert(rcrtc->rstc);
clk_disable_unprepare(rcrtc->rzg2l_clocks.pclk);
clk_disable_unprepare(rcrtc->rzg2l_clocks.aclk);
rcrtc->initialized = false;
}
static void rzg2l_du_start_stop(struct rzg2l_du_crtc *rcrtc, bool start)
{
struct rzg2l_du_device *rcdu = rcrtc->dev;
u32 val = DU_MCR0_DI_EN;
if (start && rzg2l_du_has(rcdu, RZG2L_DU_FEATURE_DPIO_OE))
val |= DU_MCR0_DPI_EN;
writel(start ? val : 0, rcdu->mmio + DU_MCR0);
}
static void rzg2l_du_crtc_start(struct rzg2l_du_crtc *rcrtc)
{
rzg2l_du_start_stop(rcrtc, true);
}
static void rzg2l_du_crtc_stop(struct rzg2l_du_crtc *rcrtc)
{
struct drm_crtc *crtc = &rcrtc->crtc;
/*
* Disable vertical blanking interrupt reporting. We first need to wait
* for page flip completion before stopping the CRTC as userspace
* expects page flips to eventually complete.
*/
rzg2l_du_crtc_wait_page_flip(rcrtc);
drm_crtc_vblank_off(crtc);
/* Disable the VSP compositor. */
rzg2l_du_vsp_disable(rcrtc);
rzg2l_du_start_stop(rcrtc, false);
}
/* -----------------------------------------------------------------------------
* CRTC Functions
*/
static void rzg2l_du_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc);
rzg2l_du_crtc_get(rcrtc);
rzg2l_du_crtc_start(rcrtc);
}
static void rzg2l_du_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc);
rzg2l_du_crtc_stop(rcrtc);
rzg2l_du_crtc_put(rcrtc);
spin_lock_irq(&crtc->dev->event_lock);
if (crtc->state->event) {
drm_crtc_send_vblank_event(crtc, crtc->state->event);
crtc->state->event = NULL;
}
spin_unlock_irq(&crtc->dev->event_lock);
}
static void rzg2l_du_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc);
struct drm_device *dev = rcrtc->crtc.dev;
unsigned long flags;
WARN_ON(!crtc->state->enable);
if (crtc->state->event) {
WARN_ON(drm_crtc_vblank_get(crtc) != 0);
spin_lock_irqsave(&dev->event_lock, flags);
rcrtc->event = crtc->state->event;
crtc->state->event = NULL;
spin_unlock_irqrestore(&dev->event_lock, flags);
}
rzg2l_du_vsp_atomic_flush(rcrtc);
}
static const struct drm_crtc_helper_funcs crtc_helper_funcs = {
.atomic_flush = rzg2l_du_crtc_atomic_flush,
.atomic_enable = rzg2l_du_crtc_atomic_enable,
.atomic_disable = rzg2l_du_crtc_atomic_disable,
};
static struct drm_crtc_state *
rzg2l_du_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
{
struct rzg2l_du_crtc_state *state;
struct rzg2l_du_crtc_state *copy;
if (WARN_ON(!crtc->state))
return NULL;
state = to_rzg2l_crtc_state(crtc->state);
copy = kmemdup(state, sizeof(*state), GFP_KERNEL);
if (!copy)
return NULL;
__drm_atomic_helper_crtc_duplicate_state(crtc, &copy->state);
return &copy->state;
}
static void rzg2l_du_crtc_atomic_destroy_state(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
__drm_atomic_helper_crtc_destroy_state(state);
kfree(to_rzg2l_crtc_state(state));
}
static void rzg2l_du_crtc_reset(struct drm_crtc *crtc)
{
struct rzg2l_du_crtc_state *state;
if (crtc->state) {
rzg2l_du_crtc_atomic_destroy_state(crtc, crtc->state);
crtc->state = NULL;
}
state = kzalloc_obj(*state);
if (!state)
return;
__drm_atomic_helper_crtc_reset(crtc, &state->state);
}
static int rzg2l_du_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc);
rcrtc->vblank_enable = true;
return 0;
}
static void rzg2l_du_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc);
rcrtc->vblank_enable = false;
}
static const struct drm_crtc_funcs crtc_funcs_rz = {
.reset = rzg2l_du_crtc_reset,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.atomic_duplicate_state = rzg2l_du_crtc_atomic_duplicate_state,
.atomic_destroy_state = rzg2l_du_crtc_atomic_destroy_state,
.enable_vblank = rzg2l_du_crtc_enable_vblank,
.disable_vblank = rzg2l_du_crtc_disable_vblank,
};
/* -----------------------------------------------------------------------------
* Initialization
*/
int rzg2l_du_crtc_create(struct rzg2l_du_device *rcdu)
{
struct rzg2l_du_crtc *rcrtc = &rcdu->crtcs[0];
struct drm_crtc *crtc = &rcrtc->crtc;
struct drm_plane *primary;
int ret;
rcrtc->rstc = devm_reset_control_get_optional_shared(rcdu->dev, NULL);
if (IS_ERR(rcrtc->rstc)) {
dev_err(rcdu->dev, "can't get cpg reset\n");
return PTR_ERR(rcrtc->rstc);
}
rcrtc->rzg2l_clocks.aclk = devm_clk_get(rcdu->dev, "aclk");
if (IS_ERR(rcrtc->rzg2l_clocks.aclk)) {
dev_err(rcdu->dev, "no axi clock for DU\n");
return PTR_ERR(rcrtc->rzg2l_clocks.aclk);
}
rcrtc->rzg2l_clocks.pclk = devm_clk_get(rcdu->dev, "pclk");
if (IS_ERR(rcrtc->rzg2l_clocks.pclk)) {
dev_err(rcdu->dev, "no peripheral clock for DU\n");
return PTR_ERR(rcrtc->rzg2l_clocks.pclk);
}
rcrtc->rzg2l_clocks.dclk = devm_clk_get(rcdu->dev, "vclk");
if (IS_ERR(rcrtc->rzg2l_clocks.dclk)) {
dev_err(rcdu->dev, "no video clock for DU\n");
return PTR_ERR(rcrtc->rzg2l_clocks.dclk);
}
init_waitqueue_head(&rcrtc->flip_wait);
rcrtc->dev = rcdu;
primary = rzg2l_du_vsp_get_drm_plane(rcrtc, rcrtc->vsp_pipe);
if (IS_ERR(primary))
return PTR_ERR(primary);
ret = drmm_crtc_init_with_planes(&rcdu->ddev, crtc, primary, NULL,
&crtc_funcs_rz, NULL);
if (ret < 0)
return ret;
drm_crtc_helper_add(crtc, &crtc_helper_funcs);
return 0;
}
@@ -0,0 +1,89 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* RZ/G2L Display Unit CRTCs
*
* Copyright (C) 2023 Renesas Electronics Corporation
*
* Based on rcar_du_crtc.h
*/
#ifndef __RZG2L_DU_CRTC_H__
#define __RZG2L_DU_CRTC_H__
#include <linux/container_of.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <drm/drm_crtc.h>
#include <drm/drm_writeback.h>
#include <media/vsp1.h>
struct clk;
struct reset_control;
struct rzg2l_du_vsp;
struct rzg2l_du_format_info;
/**
* struct rzg2l_du_crtc - the CRTC, representing a DU superposition processor
* @crtc: base DRM CRTC
* @dev: the DU device
* @initialized: whether the CRTC has been initialized and clocks enabled
* @vblank_enable: whether vblank events are enabled on this CRTC
* @event: event to post when the pending page flip completes
* @flip_wait: wait queue used to signal page flip completion
* @vsp: VSP feeding video to this CRTC
* @vsp_pipe: index of the VSP pipeline feeding video to this CRTC
* @rstc: reset controller
* @rzg2l_clocks: the bus, main and video clock
*/
struct rzg2l_du_crtc {
struct drm_crtc crtc;
struct rzg2l_du_device *dev;
bool initialized;
bool vblank_enable;
struct drm_pending_vblank_event *event;
wait_queue_head_t flip_wait;
struct rzg2l_du_vsp *vsp;
unsigned int vsp_pipe;
const char *const *sources;
unsigned int sources_count;
struct reset_control *rstc;
struct {
struct clk *aclk;
struct clk *pclk;
struct clk *dclk;
} rzg2l_clocks;
};
static inline struct rzg2l_du_crtc *to_rzg2l_crtc(struct drm_crtc *c)
{
return container_of(c, struct rzg2l_du_crtc, crtc);
}
/**
* struct rzg2l_du_crtc_state - Driver-specific CRTC state
* @state: base DRM CRTC state
* @outputs: bitmask of the outputs (enum rzg2l_du_output) driven by this CRTC
*/
struct rzg2l_du_crtc_state {
struct drm_crtc_state state;
unsigned int outputs;
};
static inline struct rzg2l_du_crtc_state *to_rzg2l_crtc_state(struct drm_crtc_state *s)
{
return container_of(s, struct rzg2l_du_crtc_state, state);
}
int rzg2l_du_crtc_create(struct rzg2l_du_device *rcdu);
void rzg2l_du_crtc_finish_page_flip(struct rzg2l_du_crtc *rcrtc);
#endif /* __RZG2L_DU_CRTC_H__ */
@@ -0,0 +1,217 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* RZ/G2L Display Unit DRM driver
*
* Copyright (C) 2023 Renesas Electronics Corporation
*
* Based on rcar_du_drv.c
*/
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <drm/clients/drm_client_setup.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_dma.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include "rzg2l_du_drv.h"
#include "rzg2l_du_kms.h"
/* -----------------------------------------------------------------------------
* Device Information
*/
static const struct rzg2l_du_device_info rzg2l_du_r9a07g043u_info = {
.channels_mask = BIT(0),
.routes = {
[RZG2L_DU_OUTPUT_DPAD0] = {
.possible_outputs = BIT(0),
.port = 0,
},
},
.mode_clock_min = 20875,
.mode_clock_max = 83500,
};
static const struct rzg2l_du_device_info rzg2l_du_r9a07g044_info = {
.channels_mask = BIT(0),
.routes = {
[RZG2L_DU_OUTPUT_DSI0] = {
.possible_outputs = BIT(0),
.port = 0,
},
[RZG2L_DU_OUTPUT_DPAD0] = {
.possible_outputs = BIT(0),
.port = 1,
}
},
.mode_clock_min = 20875,
.mode_clock_max = 83500,
};
static const struct rzg2l_du_device_info rzg2l_du_r9a09g057_info = {
.channels_mask = BIT(0),
.routes = {
[RZG2L_DU_OUTPUT_DSI0] = {
.possible_outputs = BIT(0),
.port = 0,
},
},
};
static const struct rzg2l_du_device_info rzg2l_du_r9a09g077_info = {
.channels_mask = BIT(0),
.routes = {
[RZG2L_DU_OUTPUT_DPAD0] = {
.possible_outputs = BIT(0),
.port = 0,
},
},
.features = RZG2L_DU_FEATURE_DPIO_OE,
.mode_clock_min = 5000,
.mode_clock_max = 100000,
};
static const struct of_device_id rzg2l_du_of_table[] = {
{ .compatible = "renesas,r9a07g043u-du", .data = &rzg2l_du_r9a07g043u_info },
{ .compatible = "renesas,r9a07g044-du", .data = &rzg2l_du_r9a07g044_info },
{ .compatible = "renesas,r9a09g057-du", .data = &rzg2l_du_r9a09g057_info },
{ .compatible = "renesas,r9a09g077-du", .data = &rzg2l_du_r9a09g077_info },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rzg2l_du_of_table);
const char *rzg2l_du_output_name(enum rzg2l_du_output output)
{
static const char * const names[] = {
[RZG2L_DU_OUTPUT_DSI0] = "DSI0",
[RZG2L_DU_OUTPUT_DPAD0] = "DPAD0"
};
if (output >= ARRAY_SIZE(names))
return "UNKNOWN";
return names[output];
}
/* -----------------------------------------------------------------------------
* DRM operations
*/
DEFINE_DRM_GEM_DMA_FOPS(rzg2l_du_fops);
static const struct drm_driver rzg2l_du_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(rzg2l_du_dumb_create),
DRM_FBDEV_DMA_DRIVER_OPS,
.fops = &rzg2l_du_fops,
.name = "rzg2l-du",
.desc = "Renesas RZ/G2L Display Unit",
.major = 1,
.minor = 0,
};
/* -----------------------------------------------------------------------------
* Platform driver
*/
static void rzg2l_du_remove(struct platform_device *pdev)
{
struct rzg2l_du_device *rcdu = platform_get_drvdata(pdev);
struct drm_device *ddev = &rcdu->ddev;
drm_dev_unregister(ddev);
drm_atomic_helper_shutdown(ddev);
drm_kms_helper_poll_fini(ddev);
}
static void rzg2l_du_shutdown(struct platform_device *pdev)
{
struct rzg2l_du_device *rcdu = platform_get_drvdata(pdev);
drm_atomic_helper_shutdown(&rcdu->ddev);
}
static int rzg2l_du_probe(struct platform_device *pdev)
{
struct rzg2l_du_device *rcdu;
int ret;
if (drm_firmware_drivers_only())
return -ENODEV;
/* Allocate and initialize the RZ/G2L device structure. */
rcdu = devm_drm_dev_alloc(&pdev->dev, &rzg2l_du_driver,
struct rzg2l_du_device, ddev);
if (IS_ERR(rcdu))
return PTR_ERR(rcdu);
rcdu->dev = &pdev->dev;
rcdu->info = of_device_get_match_data(rcdu->dev);
platform_set_drvdata(pdev, rcdu);
/* I/O resources */
rcdu->mmio = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(rcdu->mmio))
return PTR_ERR(rcdu->mmio);
ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
/* DRM/KMS objects */
ret = rzg2l_du_modeset_init(rcdu);
if (ret < 0) {
/*
* Don't use dev_err_probe(), as it would overwrite the probe
* deferral reason recorded in rzg2l_du_modeset_init().
*/
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev,
"failed to initialize DRM/KMS (%d)\n", ret);
goto error;
}
/*
* Register the DRM device with the core and the connectors with
* sysfs.
*/
ret = drm_dev_register(&rcdu->ddev, 0);
if (ret)
goto error;
drm_info(&rcdu->ddev, "Device %s probed\n", dev_name(&pdev->dev));
drm_client_setup(&rcdu->ddev, NULL);
return 0;
error:
drm_kms_helper_poll_fini(&rcdu->ddev);
return ret;
}
static struct platform_driver rzg2l_du_platform_driver = {
.probe = rzg2l_du_probe,
.remove = rzg2l_du_remove,
.shutdown = rzg2l_du_shutdown,
.driver = {
.name = "rzg2l-du",
.of_match_table = rzg2l_du_of_table,
},
};
module_platform_driver(rzg2l_du_platform_driver);
MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
MODULE_DESCRIPTION("Renesas RZ/G2L Display Unit DRM Driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,92 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* RZ/G2L Display Unit DRM driver
*
* Copyright (C) 2023 Renesas Electronics Corporation
*
* Based on rcar_du_drv.h
*/
#ifndef __RZG2L_DU_DRV_H__
#define __RZG2L_DU_DRV_H__
#include <linux/kernel.h>
#include <drm/drm_device.h>
#include "rzg2l_du_crtc.h"
#include "rzg2l_du_vsp.h"
struct device;
struct drm_property;
#define RZG2L_DU_FEATURE_DPIO_OE BIT(0) /* Has DPIO output enable control */
enum rzg2l_du_output {
RZG2L_DU_OUTPUT_DSI0,
RZG2L_DU_OUTPUT_DPAD0,
RZG2L_DU_OUTPUT_MAX,
};
/*
* struct rzg2l_du_output_routing - Output routing specification
* @possible_outputs: bitmask of possible outputs
* @port: device tree port number corresponding to this output route
*
* The DU has 2 possible outputs (DPAD0, DSI0). Output routing data
* specify the valid SoC outputs, which CRTC can drive the output, and the type
* of in-SoC encoder for the output.
*/
struct rzg2l_du_output_routing {
unsigned int possible_outputs;
unsigned int port;
};
/*
* struct rzg2l_du_device_info - DU model-specific information
* @channels_mask: bit mask of available DU channels
* @routes: array of CRTC to output routes, indexed by output (RZG2L_DU_OUTPUT_*)
* @mode_clock_min: minimum pixel clock in kHz
* @mode_clock_max: maximum pixel clock in kHz
* @features: device features (RZG2L_DU_FEATURE_*)
*/
struct rzg2l_du_device_info {
unsigned int channels_mask;
struct rzg2l_du_output_routing routes[RZG2L_DU_OUTPUT_MAX];
u32 mode_clock_min;
u32 mode_clock_max;
unsigned int features;
};
#define RZG2L_DU_MAX_CRTCS 1
#define RZG2L_DU_MAX_VSPS 1
#define RZG2L_DU_MAX_DSI 1
struct rzg2l_du_device {
struct device *dev;
const struct rzg2l_du_device_info *info;
void __iomem *mmio;
struct drm_device ddev;
struct rzg2l_du_crtc crtcs[RZG2L_DU_MAX_CRTCS];
unsigned int num_crtcs;
struct rzg2l_du_vsp vsps[RZG2L_DU_MAX_VSPS];
};
static inline struct rzg2l_du_device *to_rzg2l_du_device(struct drm_device *dev)
{
return container_of(dev, struct rzg2l_du_device, ddev);
}
static inline bool rzg2l_du_has(struct rzg2l_du_device *rcdu,
unsigned int feature)
{
return rcdu->info->features & feature;
}
const char *rzg2l_du_output_name(enum rzg2l_du_output output);
#endif /* __RZG2L_DU_DRV_H__ */
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* RZ/G2L Display Unit Encoder
*
* Copyright (C) 2023 Renesas Electronics Corporation
*
* Based on rcar_du_encoder.c
*/
#include <linux/export.h>
#include <linux/of.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_panel.h>
#include "rzg2l_du_drv.h"
#include "rzg2l_du_encoder.h"
/* -----------------------------------------------------------------------------
* Encoder
*/
static unsigned int rzg2l_du_encoder_count_ports(struct device_node *node)
{
struct device_node *ports;
struct device_node *port;
unsigned int num_ports = 0;
ports = of_get_child_by_name(node, "ports");
if (!ports)
ports = of_node_get(node);
for_each_child_of_node(ports, port) {
if (of_node_name_eq(port, "port"))
num_ports++;
}
of_node_put(ports);
return num_ports;
}
static const struct drm_encoder_funcs rzg2l_du_encoder_funcs = {
};
static enum drm_mode_status
rzg2l_du_encoder_mode_valid(struct drm_encoder *encoder,
const struct drm_display_mode *mode)
{
struct rzg2l_du_encoder *renc = to_rzg2l_encoder(encoder);
struct rzg2l_du_device *rcdu = to_rzg2l_du_device(renc->base.dev);
const struct rzg2l_du_device_info *info = rcdu->info;
if (renc->output != RZG2L_DU_OUTPUT_DPAD0)
return MODE_OK;
if (mode->clock < info->mode_clock_min)
return MODE_CLOCK_LOW;
if (mode->clock > info->mode_clock_max)
return MODE_CLOCK_HIGH;
return MODE_OK;
}
static const struct drm_encoder_helper_funcs rzg2l_du_encoder_helper_funcs = {
.mode_valid = rzg2l_du_encoder_mode_valid,
};
int rzg2l_du_encoder_init(struct rzg2l_du_device *rcdu,
enum rzg2l_du_output output,
struct device_node *enc_node)
{
struct rzg2l_du_encoder *renc;
struct drm_connector *connector;
struct drm_bridge *bridge __free(drm_bridge_put) = NULL;
int ret;
/*
* Locate the DRM bridge from the DT node. For the DPAD outputs, if the
* DT node has a single port, assume that it describes a panel and
* create a panel bridge.
*/
if (output == RZG2L_DU_OUTPUT_DPAD0 && rzg2l_du_encoder_count_ports(enc_node) == 1) {
struct drm_panel *panel = of_drm_find_panel(enc_node);
if (IS_ERR(panel))
return PTR_ERR(panel);
bridge = devm_drm_panel_bridge_add_typed(rcdu->dev, panel,
DRM_MODE_CONNECTOR_DPI);
if (IS_ERR(bridge))
return PTR_ERR(no_free_ptr(bridge));
/*
* The reference taken by devm_drm_panel_bridge_add_typed() is
* released automatically. Take a second one for the __free()
* when this function will return.
*/
drm_bridge_get(bridge);
} else {
bridge = of_drm_find_and_get_bridge(enc_node);
if (!bridge)
return -EPROBE_DEFER;
}
dev_dbg(rcdu->dev, "initializing encoder %pOF for output %s\n",
enc_node, rzg2l_du_output_name(output));
renc = drmm_encoder_alloc(&rcdu->ddev, struct rzg2l_du_encoder, base,
&rzg2l_du_encoder_funcs, DRM_MODE_ENCODER_NONE,
NULL);
if (IS_ERR(renc))
return PTR_ERR(renc);
renc->output = output;
drm_encoder_helper_add(&renc->base, &rzg2l_du_encoder_helper_funcs);
/* Attach the bridge to the encoder. */
ret = drm_bridge_attach(&renc->base, bridge, NULL,
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret) {
dev_err(rcdu->dev,
"failed to attach bridge %pOF for output %s (%d)\n",
bridge->of_node, rzg2l_du_output_name(output), ret);
return ret;
}
/* Create the connector for the chain of bridges. */
connector = drm_bridge_connector_init(&rcdu->ddev, &renc->base);
if (IS_ERR(connector)) {
dev_err(rcdu->dev,
"failed to created connector for output %s (%ld)\n",
rzg2l_du_output_name(output), PTR_ERR(connector));
return PTR_ERR(connector);
}
return 0;
}
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* RZ/G2L Display Unit Encoder
*
* Copyright (C) 2023 Renesas Electronics Corporation
*
* Based on rcar_du_encoder.h
*/
#ifndef __RZG2L_DU_ENCODER_H__
#define __RZG2L_DU_ENCODER_H__
#include <drm/drm_encoder.h>
#include <linux/container_of.h>
struct rzg2l_du_device;
struct rzg2l_du_encoder {
struct drm_encoder base;
enum rzg2l_du_output output;
};
static inline struct rzg2l_du_encoder *to_rzg2l_encoder(struct drm_encoder *e)
{
return container_of(e, struct rzg2l_du_encoder, base);
}
int rzg2l_du_encoder_init(struct rzg2l_du_device *rcdu,
enum rzg2l_du_output output,
struct device_node *enc_node);
#endif /* __RZG2L_DU_ENCODER_H__ */
@@ -0,0 +1,479 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* RZ/G2L Display Unit Mode Setting
*
* Copyright (C) 2023 Renesas Electronics Corporation
*
* Based on rcar_du_kms.c
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_device.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_managed.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include "rzg2l_du_crtc.h"
#include "rzg2l_du_drv.h"
#include "rzg2l_du_encoder.h"
#include "rzg2l_du_kms.h"
#include "rzg2l_du_vsp.h"
/* -----------------------------------------------------------------------------
* Format helpers
*/
static const struct rzg2l_du_format_info rzg2l_du_format_infos[] = {
{
.fourcc = DRM_FORMAT_RGB332,
.v4l2 = V4L2_PIX_FMT_RGB332,
.planes = 1,
.hsub = 1,
}, {
.fourcc = DRM_FORMAT_ARGB4444,
.v4l2 = V4L2_PIX_FMT_ARGB444,
.planes = 1,
.hsub = 1,
}, {
.fourcc = DRM_FORMAT_XRGB4444,
.v4l2 = V4L2_PIX_FMT_XRGB444,
.planes = 1,
.hsub = 1,
}, {
.fourcc = DRM_FORMAT_ARGB1555,
.v4l2 = V4L2_PIX_FMT_ARGB555,
.planes = 1,
.hsub = 1,
}, {
.fourcc = DRM_FORMAT_XRGB1555,
.v4l2 = V4L2_PIX_FMT_XRGB555,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_RGB565,
.v4l2 = V4L2_PIX_FMT_RGB565,
.planes = 1,
.hsub = 1,
}, {
.fourcc = DRM_FORMAT_BGR888,
.v4l2 = V4L2_PIX_FMT_RGB24,
.planes = 1,
.hsub = 1,
}, {
.fourcc = DRM_FORMAT_RGB888,
.v4l2 = V4L2_PIX_FMT_BGR24,
.planes = 1,
.hsub = 1,
}, {
.fourcc = DRM_FORMAT_BGRA8888,
.v4l2 = V4L2_PIX_FMT_ARGB32,
.planes = 1,
.hsub = 1,
}, {
.fourcc = DRM_FORMAT_BGRX8888,
.v4l2 = V4L2_PIX_FMT_XRGB32,
.planes = 1,
.hsub = 1,
}, {
.fourcc = DRM_FORMAT_ARGB8888,
.v4l2 = V4L2_PIX_FMT_ABGR32,
.planes = 1,
.hsub = 1,
}, {
.fourcc = DRM_FORMAT_XRGB8888,
.v4l2 = V4L2_PIX_FMT_XBGR32,
.planes = 1,
.hsub = 1,
}, {
.fourcc = DRM_FORMAT_UYVY,
.v4l2 = V4L2_PIX_FMT_UYVY,
.planes = 1,
.hsub = 2,
}, {
.fourcc = DRM_FORMAT_YUYV,
.v4l2 = V4L2_PIX_FMT_YUYV,
.planes = 1,
.hsub = 2,
}, {
.fourcc = DRM_FORMAT_YVYU,
.v4l2 = V4L2_PIX_FMT_YVYU,
.planes = 1,
.hsub = 2,
}, {
.fourcc = DRM_FORMAT_NV12,
.v4l2 = V4L2_PIX_FMT_NV12M,
.planes = 2,
.hsub = 2,
}, {
.fourcc = DRM_FORMAT_NV21,
.v4l2 = V4L2_PIX_FMT_NV21M,
.planes = 2,
.hsub = 2,
}, {
.fourcc = DRM_FORMAT_NV16,
.v4l2 = V4L2_PIX_FMT_NV16M,
.planes = 2,
.hsub = 2,
}, {
.fourcc = DRM_FORMAT_NV61,
.v4l2 = V4L2_PIX_FMT_NV61M,
.planes = 2,
.hsub = 2,
}, {
.fourcc = DRM_FORMAT_YUV420,
.v4l2 = V4L2_PIX_FMT_YUV420M,
.planes = 3,
.hsub = 2,
}, {
.fourcc = DRM_FORMAT_YVU420,
.v4l2 = V4L2_PIX_FMT_YVU420M,
.planes = 3,
.hsub = 2,
}, {
.fourcc = DRM_FORMAT_YUV422,
.v4l2 = V4L2_PIX_FMT_YUV422M,
.planes = 3,
.hsub = 2,
}, {
.fourcc = DRM_FORMAT_YVU422,
.v4l2 = V4L2_PIX_FMT_YVU422M,
.planes = 3,
.hsub = 2,
}, {
.fourcc = DRM_FORMAT_YUV444,
.v4l2 = V4L2_PIX_FMT_YUV444M,
.planes = 3,
.hsub = 1,
}, {
.fourcc = DRM_FORMAT_YVU444,
.v4l2 = V4L2_PIX_FMT_YVU444M,
.planes = 3,
.hsub = 1,
}
};
const struct rzg2l_du_format_info *rzg2l_du_format_info(u32 fourcc)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(rzg2l_du_format_infos); ++i) {
if (rzg2l_du_format_infos[i].fourcc == fourcc)
return &rzg2l_du_format_infos[i];
}
return NULL;
}
/* -----------------------------------------------------------------------------
* Frame buffer
*/
int rzg2l_du_dumb_create(struct drm_file *file, struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
unsigned int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
unsigned int align = 16 * args->bpp / 8;
args->pitch = roundup(min_pitch, align);
return drm_gem_dma_dumb_create_internal(file, dev, args);
}
static struct drm_framebuffer *
rzg2l_du_fb_create(struct drm_device *dev, struct drm_file *file_priv,
const struct drm_format_info *info,
const struct drm_mode_fb_cmd2 *mode_cmd)
{
const struct rzg2l_du_format_info *format;
unsigned int max_pitch;
format = rzg2l_du_format_info(mode_cmd->pixel_format);
if (!format) {
dev_dbg(dev->dev, "unsupported pixel format %p4cc\n",
&mode_cmd->pixel_format);
return ERR_PTR(-EINVAL);
}
/*
* On RZ/G2L the memory interface is handled by the VSP that limits the
* pitch to 65535 bytes.
*/
max_pitch = 65535;
if (mode_cmd->pitches[0] > max_pitch) {
dev_dbg(dev->dev, "invalid pitch value %u\n",
mode_cmd->pitches[0]);
return ERR_PTR(-EINVAL);
}
return drm_gem_fb_create(dev, file_priv, info, mode_cmd);
}
/* -----------------------------------------------------------------------------
* Initialization
*/
static const struct drm_mode_config_helper_funcs rzg2l_du_mode_config_helper = {
.atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
};
static const struct drm_mode_config_funcs rzg2l_du_mode_config_funcs = {
.fb_create = rzg2l_du_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
static int rzg2l_du_encoders_init_one(struct rzg2l_du_device *rcdu,
enum rzg2l_du_output output,
struct of_endpoint *ep)
{
struct device_node *entity;
int ret;
/* Locate the connected entity and initialize the encoder. */
entity = of_graph_get_remote_port_parent(ep->local_node);
if (!entity) {
dev_dbg(rcdu->dev, "unconnected endpoint %pOF, skipping\n",
ep->local_node);
return -ENODEV;
}
if (!of_device_is_available(entity)) {
dev_dbg(rcdu->dev,
"connected entity %pOF is disabled, skipping\n",
entity);
of_node_put(entity);
return -ENODEV;
}
ret = rzg2l_du_encoder_init(rcdu, output, entity);
if (ret && ret != -EPROBE_DEFER && ret != -ENOLINK)
dev_warn(rcdu->dev,
"failed to initialize encoder %pOF on output %s (%d), skipping\n",
entity, rzg2l_du_output_name(output), ret);
of_node_put(entity);
return ret;
}
static int rzg2l_du_encoders_init(struct rzg2l_du_device *rcdu)
{
struct device_node *np = rcdu->dev->of_node;
struct device_node *ep_node;
unsigned int num_encoders = 0;
/*
* Iterate over the endpoints and create one encoder for each output
* pipeline.
*/
for_each_endpoint_of_node(np, ep_node) {
enum rzg2l_du_output output;
struct of_endpoint ep;
unsigned int i;
int ret;
ret = of_graph_parse_endpoint(ep_node, &ep);
if (ret < 0) {
of_node_put(ep_node);
return ret;
}
/* Find the output route corresponding to the port number. */
for (i = 0; i < RZG2L_DU_OUTPUT_MAX; ++i) {
if (rcdu->info->routes[i].possible_outputs &&
rcdu->info->routes[i].port == ep.port) {
output = i;
break;
}
}
if (i == RZG2L_DU_OUTPUT_MAX) {
dev_warn(rcdu->dev,
"port %u references unexisting output, skipping\n",
ep.port);
continue;
}
/* Process the output pipeline. */
ret = rzg2l_du_encoders_init_one(rcdu, output, &ep);
if (ret < 0) {
if (ret == -EPROBE_DEFER) {
of_node_put(ep_node);
return ret;
}
continue;
}
num_encoders++;
}
return num_encoders;
}
static int rzg2l_du_vsps_init(struct rzg2l_du_device *rcdu)
{
const struct device_node *np = rcdu->dev->of_node;
const char *vsps_prop_name = "renesas,vsps";
struct of_phandle_args args;
struct {
struct device_node *np;
unsigned int crtcs_mask;
} vsps[RZG2L_DU_MAX_VSPS] = { { NULL, }, };
unsigned int vsps_count = 0;
unsigned int cells;
unsigned int i;
int ret;
/*
* First parse the DT vsps property to populate the list of VSPs. Each
* entry contains a pointer to the VSP DT node and a bitmask of the
* connected DU CRTCs.
*/
ret = of_property_count_u32_elems(np, vsps_prop_name);
cells = ret / rcdu->num_crtcs - 1;
if (cells != 1)
return -EINVAL;
for (i = 0; i < rcdu->num_crtcs; ++i) {
unsigned int j;
ret = of_parse_phandle_with_fixed_args(np, vsps_prop_name,
cells, i, &args);
if (ret < 0)
goto done;
/*
* Add the VSP to the list or update the corresponding existing
* entry if the VSP has already been added.
*/
for (j = 0; j < vsps_count; ++j) {
if (vsps[j].np == args.np)
break;
}
if (j < vsps_count)
of_node_put(args.np);
else
vsps[vsps_count++].np = args.np;
vsps[j].crtcs_mask |= BIT(i);
/*
* Store the VSP pointer and pipe index in the CRTC. If the
* second cell of the 'renesas,vsps' specifier isn't present,
* default to 0.
*/
rcdu->crtcs[i].vsp = &rcdu->vsps[j];
rcdu->crtcs[i].vsp_pipe = cells >= 1 ? args.args[0] : 0;
}
/*
* Then initialize all the VSPs from the node pointers and CRTCs bitmask
* computed previously.
*/
for (i = 0; i < vsps_count; ++i) {
struct rzg2l_du_vsp *vsp = &rcdu->vsps[i];
vsp->index = i;
vsp->dev = rcdu;
ret = rzg2l_du_vsp_init(vsp, vsps[i].np, vsps[i].crtcs_mask);
if (ret)
goto done;
}
done:
for (i = 0; i < ARRAY_SIZE(vsps); ++i)
of_node_put(vsps[i].np);
return ret;
}
int rzg2l_du_modeset_init(struct rzg2l_du_device *rcdu)
{
struct drm_device *dev = &rcdu->ddev;
struct drm_encoder *encoder;
unsigned int num_encoders;
int ret;
ret = drmm_mode_config_init(dev);
if (ret)
return ret;
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
dev->mode_config.normalize_zpos = true;
dev->mode_config.funcs = &rzg2l_du_mode_config_funcs;
dev->mode_config.helper_private = &rzg2l_du_mode_config_helper;
/*
* The RZ DU was designed to support a frame size of 1920x1200 (landscape)
* or 1200x1920 (portrait).
*/
dev->mode_config.max_width = 1920;
dev->mode_config.max_height = 1920;
rcdu->num_crtcs = hweight8(rcdu->info->channels_mask);
/*
* Initialize vertical blanking interrupts handling. Start with vblank
* disabled for all CRTCs.
*/
ret = drm_vblank_init(dev, rcdu->num_crtcs);
if (ret < 0)
return ret;
/* Initialize the compositors. */
ret = rzg2l_du_vsps_init(rcdu);
if (ret < 0)
return ret;
/* Create the CRTCs. */
ret = rzg2l_du_crtc_create(rcdu);
if (ret < 0)
return ret;
/* Initialize the encoders. */
ret = rzg2l_du_encoders_init(rcdu);
if (ret < 0)
return dev_err_probe(rcdu->dev, ret,
"failed to initialize encoders\n");
if (ret == 0) {
dev_err(rcdu->dev, "error: no encoder could be initialized\n");
return -EINVAL;
}
num_encoders = ret;
/*
* Set the possible CRTCs and possible clones. There's always at least
* one way for all encoders to clone each other, set all bits in the
* possible clones field.
*/
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
struct rzg2l_du_encoder *renc = to_rzg2l_encoder(encoder);
const struct rzg2l_du_output_routing *route =
&rcdu->info->routes[renc->output];
encoder->possible_crtcs = route->possible_outputs;
encoder->possible_clones = (1 << num_encoders) - 1;
}
drm_mode_config_reset(dev);
drm_kms_helper_poll_init(dev);
return 0;
}
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* RZ/G2L Display Unit Mode Setting
*
* Copyright (C) 2023 Renesas Electronics Corporation
*
* Based on rcar_du_kms.h
*/
#ifndef __RZG2L_DU_KMS_H__
#define __RZG2L_DU_KMS_H__
#include <linux/types.h>
struct dma_buf_attachment;
struct drm_file;
struct drm_device;
struct drm_gem_object;
struct drm_mode_create_dumb;
struct rzg2l_du_device;
struct sg_table;
struct rzg2l_du_format_info {
u32 fourcc;
u32 v4l2;
unsigned int planes;
unsigned int hsub;
};
const struct rzg2l_du_format_info *rzg2l_du_format_info(u32 fourcc);
int rzg2l_du_modeset_init(struct rzg2l_du_device *rcdu);
int rzg2l_du_dumb_create(struct drm_file *file, struct drm_device *dev,
struct drm_mode_create_dumb *args);
struct drm_gem_object *
rzg2l_du_gem_prime_import_sg_table(struct drm_device *dev,
struct dma_buf_attachment *attach,
struct sg_table *sgt);
#endif /* __RZG2L_DU_KMS_H__ */
@@ -0,0 +1,371 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* RZ/G2L Display Unit VSP-Based Compositor
*
* Copyright (C) 2023 Renesas Electronics Corporation
*
* Based on rcar_du_vsp.c
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_blend.h>
#include <drm/drm_crtc.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_managed.h>
#include <drm/drm_vblank.h>
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/scatterlist.h>
#include <media/vsp1.h>
#include "rzg2l_du_drv.h"
#include "rzg2l_du_kms.h"
#include "rzg2l_du_vsp.h"
static void rzg2l_du_vsp_complete(void *private, unsigned int status, u32 crc)
{
struct rzg2l_du_crtc *crtc = private;
if (crtc->vblank_enable)
drm_crtc_handle_vblank(&crtc->crtc);
if (status & VSP1_DU_STATUS_COMPLETE)
rzg2l_du_crtc_finish_page_flip(crtc);
drm_crtc_add_crc_entry(&crtc->crtc, false, 0, &crc);
}
void rzg2l_du_vsp_enable(struct rzg2l_du_crtc *crtc)
{
const struct drm_display_mode *mode = &crtc->crtc.state->adjusted_mode;
struct vsp1_du_lif_config cfg = {
.width = mode->hdisplay,
.height = mode->vdisplay,
.interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE,
.callback = rzg2l_du_vsp_complete,
.callback_data = crtc,
};
vsp1_du_enable(crtc->vsp->vsp, crtc->vsp_pipe, &cfg);
}
void rzg2l_du_vsp_disable(struct rzg2l_du_crtc *crtc)
{
vsp1_du_disable(crtc->vsp->vsp, crtc->vsp_pipe);
}
void rzg2l_du_vsp_atomic_flush(struct rzg2l_du_crtc *crtc)
{
struct vsp1_du_atomic_pipe_config cfg = { { 0, } };
vsp1_du_atomic_flush(crtc->vsp->vsp, crtc->vsp_pipe, &cfg);
}
struct drm_plane *rzg2l_du_vsp_get_drm_plane(struct rzg2l_du_crtc *crtc,
unsigned int pipe_index)
{
struct rzg2l_du_device *rcdu = crtc->vsp->dev;
struct drm_plane *plane = NULL;
drm_for_each_plane(plane, &rcdu->ddev) {
struct rzg2l_du_vsp_plane *vsp_plane = to_rzg2l_vsp_plane(plane);
if (vsp_plane->index == pipe_index)
break;
}
return plane ? plane : ERR_PTR(-EINVAL);
}
static const u32 rzg2l_du_vsp_formats[] = {
DRM_FORMAT_RGB332,
DRM_FORMAT_ARGB4444,
DRM_FORMAT_XRGB4444,
DRM_FORMAT_ARGB1555,
DRM_FORMAT_XRGB1555,
DRM_FORMAT_RGB565,
DRM_FORMAT_BGR888,
DRM_FORMAT_RGB888,
DRM_FORMAT_BGRA8888,
DRM_FORMAT_BGRX8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_UYVY,
DRM_FORMAT_YUYV,
DRM_FORMAT_YVYU,
DRM_FORMAT_NV12,
DRM_FORMAT_NV21,
DRM_FORMAT_NV16,
DRM_FORMAT_NV61,
DRM_FORMAT_YUV420,
DRM_FORMAT_YVU420,
DRM_FORMAT_YUV422,
DRM_FORMAT_YVU422,
DRM_FORMAT_YUV444,
DRM_FORMAT_YVU444,
};
static void rzg2l_du_vsp_plane_setup(struct rzg2l_du_vsp_plane *plane)
{
struct rzg2l_du_vsp_plane_state *state =
to_rzg2l_vsp_plane_state(plane->plane.state);
struct rzg2l_du_crtc *crtc = to_rzg2l_crtc(state->state.crtc);
struct drm_framebuffer *fb = plane->plane.state->fb;
const struct rzg2l_du_format_info *format;
struct vsp1_du_atomic_config cfg = {
.pixelformat = 0,
.pitch = fb->pitches[0],
.alpha = state->state.alpha >> 8,
.zpos = state->state.zpos,
};
u32 fourcc = state->format->fourcc;
unsigned int i;
cfg.src.left = state->state.src.x1 >> 16;
cfg.src.top = state->state.src.y1 >> 16;
cfg.src.width = drm_rect_width(&state->state.src) >> 16;
cfg.src.height = drm_rect_height(&state->state.src) >> 16;
cfg.dst.left = state->state.dst.x1;
cfg.dst.top = state->state.dst.y1;
cfg.dst.width = drm_rect_width(&state->state.dst);
cfg.dst.height = drm_rect_height(&state->state.dst);
for (i = 0; i < state->format->planes; ++i) {
struct drm_gem_dma_object *gem;
gem = drm_fb_dma_get_gem_obj(fb, i);
cfg.mem[i] = gem->dma_addr + fb->offsets[i];
}
if (state->state.pixel_blend_mode == DRM_MODE_BLEND_PIXEL_NONE) {
switch (fourcc) {
case DRM_FORMAT_ARGB1555:
fourcc = DRM_FORMAT_XRGB1555;
break;
case DRM_FORMAT_ARGB4444:
fourcc = DRM_FORMAT_XRGB4444;
break;
case DRM_FORMAT_ARGB8888:
fourcc = DRM_FORMAT_XRGB8888;
break;
}
}
format = rzg2l_du_format_info(fourcc);
cfg.pixelformat = format->v4l2;
cfg.premult = state->state.pixel_blend_mode == DRM_MODE_BLEND_PREMULTI;
vsp1_du_atomic_update(plane->vsp->vsp, crtc->vsp_pipe,
plane->index, &cfg);
}
static int __rzg2l_du_vsp_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state,
const struct rzg2l_du_format_info **format)
{
struct drm_crtc_state *crtc_state;
int ret;
if (!state->crtc) {
/*
* The visible field is not reset by the DRM core but only
* updated by drm_atomic_helper_check_plane_state, set it
* manually.
*/
state->visible = false;
*format = NULL;
return 0;
}
crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
ret = drm_atomic_helper_check_plane_state(state, crtc_state,
DRM_PLANE_NO_SCALING,
DRM_PLANE_NO_SCALING,
true, true);
if (ret < 0)
return ret;
if (!state->visible) {
*format = NULL;
return 0;
}
*format = rzg2l_du_format_info(state->fb->format->format);
return 0;
}
static int rzg2l_du_vsp_plane_atomic_check(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
plane);
struct rzg2l_du_vsp_plane_state *rstate = to_rzg2l_vsp_plane_state(new_plane_state);
return __rzg2l_du_vsp_plane_atomic_check(plane, new_plane_state, &rstate->format);
}
static void rzg2l_du_vsp_plane_atomic_update(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane);
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane);
struct rzg2l_du_vsp_plane *rplane = to_rzg2l_vsp_plane(plane);
struct rzg2l_du_crtc *crtc = to_rzg2l_crtc(old_state->crtc);
if (new_state->visible)
rzg2l_du_vsp_plane_setup(rplane);
else if (old_state->crtc)
vsp1_du_atomic_update(rplane->vsp->vsp, crtc->vsp_pipe,
rplane->index, NULL);
}
static const struct drm_plane_helper_funcs rzg2l_du_vsp_plane_helper_funcs = {
.atomic_check = rzg2l_du_vsp_plane_atomic_check,
.atomic_update = rzg2l_du_vsp_plane_atomic_update,
};
static struct drm_plane_state *
rzg2l_du_vsp_plane_atomic_duplicate_state(struct drm_plane *plane)
{
struct rzg2l_du_vsp_plane_state *copy;
if (WARN_ON(!plane->state))
return NULL;
copy = kzalloc_obj(*copy);
if (!copy)
return NULL;
__drm_atomic_helper_plane_duplicate_state(plane, &copy->state);
return &copy->state;
}
static void rzg2l_du_vsp_plane_atomic_destroy_state(struct drm_plane *plane,
struct drm_plane_state *state)
{
__drm_atomic_helper_plane_destroy_state(state);
kfree(to_rzg2l_vsp_plane_state(state));
}
static void rzg2l_du_vsp_plane_reset(struct drm_plane *plane)
{
struct rzg2l_du_vsp_plane_state *state;
if (plane->state) {
rzg2l_du_vsp_plane_atomic_destroy_state(plane, plane->state);
plane->state = NULL;
}
state = kzalloc_obj(*state);
if (!state)
return;
__drm_atomic_helper_plane_reset(plane, &state->state);
}
static const struct drm_plane_funcs rzg2l_du_vsp_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.reset = rzg2l_du_vsp_plane_reset,
.atomic_duplicate_state = rzg2l_du_vsp_plane_atomic_duplicate_state,
.atomic_destroy_state = rzg2l_du_vsp_plane_atomic_destroy_state,
};
static void rzg2l_du_vsp_cleanup(struct drm_device *dev, void *res)
{
struct rzg2l_du_vsp *vsp = res;
if (vsp->link)
device_link_del(vsp->link);
put_device(vsp->vsp);
}
int rzg2l_du_vsp_init(struct rzg2l_du_vsp *vsp, struct device_node *np,
unsigned int crtcs)
{
struct rzg2l_du_device *rcdu = vsp->dev;
struct platform_device *pdev;
unsigned int num_crtcs = hweight32(crtcs);
unsigned int num_planes = 2;
unsigned int i;
int ret;
/* Find the VSP device and initialize it. */
pdev = of_find_device_by_node(np);
if (!pdev)
return -ENXIO;
vsp->vsp = &pdev->dev;
ret = drmm_add_action_or_reset(&rcdu->ddev, rzg2l_du_vsp_cleanup, vsp);
if (ret < 0)
return ret;
/*
* Enforce suspend/resume ordering between the DU (consumer) and the
* VSP (supplier). The DU will be suspended before and resume after the
* VSP.
*/
vsp->link = device_link_add(rcdu->dev, vsp->vsp, DL_FLAG_STATELESS);
if (!vsp->link) {
dev_err(rcdu->dev, "Failed to create device link to VSP %s\n",
dev_name(vsp->vsp));
return -EINVAL;
}
ret = vsp1_du_init(vsp->vsp);
if (ret < 0)
return ret;
for (i = 0; i < num_planes; ++i) {
enum drm_plane_type type = i < num_crtcs
? DRM_PLANE_TYPE_PRIMARY
: DRM_PLANE_TYPE_OVERLAY;
struct rzg2l_du_vsp_plane *plane;
plane = drmm_universal_plane_alloc(&rcdu->ddev, struct rzg2l_du_vsp_plane,
plane, crtcs, &rzg2l_du_vsp_plane_funcs,
rzg2l_du_vsp_formats,
ARRAY_SIZE(rzg2l_du_vsp_formats),
NULL, type, NULL);
if (IS_ERR(plane))
return PTR_ERR(plane);
plane->vsp = vsp;
plane->index = i;
drm_plane_helper_add(&plane->plane,
&rzg2l_du_vsp_plane_helper_funcs);
drm_plane_create_alpha_property(&plane->plane);
drm_plane_create_zpos_property(&plane->plane, i, 0,
num_planes - 1);
drm_plane_create_blend_mode_property(&plane->plane,
BIT(DRM_MODE_BLEND_PIXEL_NONE) |
BIT(DRM_MODE_BLEND_PREMULTI) |
BIT(DRM_MODE_BLEND_COVERAGE));
}
return 0;
}
@@ -0,0 +1,84 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* RZ/G2L Display Unit VSP-Based Compositor
*
* Copyright (C) 2023 Renesas Electronics Corporation
*
* Based on rcar_du_vsp.h
*/
#ifndef __RZG2L_DU_VSP_H__
#define __RZG2L_DU_VSP_H__
#include <drm/drm_plane.h>
#include <linux/container_of.h>
#include <linux/scatterlist.h>
struct device;
struct device_link;
struct drm_framebuffer;
struct rzg2l_du_device;
struct rzg2l_du_format_info;
struct rzg2l_du_vsp;
struct rzg2l_du_vsp_plane {
struct drm_plane plane;
struct rzg2l_du_vsp *vsp;
unsigned int index;
};
struct rzg2l_du_vsp {
unsigned int index;
struct device *vsp;
struct device_link *link;
struct rzg2l_du_device *dev;
};
static inline struct rzg2l_du_vsp_plane *to_rzg2l_vsp_plane(struct drm_plane *p)
{
return container_of(p, struct rzg2l_du_vsp_plane, plane);
}
/**
* struct rzg2l_du_vsp_plane_state - Driver-specific plane state
* @state: base DRM plane state
* @format: information about the pixel format used by the plane
*/
struct rzg2l_du_vsp_plane_state {
struct drm_plane_state state;
const struct rzg2l_du_format_info *format;
};
static inline struct rzg2l_du_vsp_plane_state *
to_rzg2l_vsp_plane_state(struct drm_plane_state *state)
{
return container_of(state, struct rzg2l_du_vsp_plane_state, state);
}
#if IS_ENABLED(CONFIG_VIDEO_RENESAS_VSP1)
int rzg2l_du_vsp_init(struct rzg2l_du_vsp *vsp, struct device_node *np,
unsigned int crtcs);
void rzg2l_du_vsp_enable(struct rzg2l_du_crtc *crtc);
void rzg2l_du_vsp_disable(struct rzg2l_du_crtc *crtc);
void rzg2l_du_vsp_atomic_flush(struct rzg2l_du_crtc *crtc);
struct drm_plane *rzg2l_du_vsp_get_drm_plane(struct rzg2l_du_crtc *crtc,
unsigned int pipe_index);
#else
static inline int rzg2l_du_vsp_init(struct rzg2l_du_vsp *vsp, struct device_node *np,
unsigned int crtcs)
{
return -ENXIO;
}
static inline void rzg2l_du_vsp_enable(struct rzg2l_du_crtc *crtc) { };
static inline void rzg2l_du_vsp_disable(struct rzg2l_du_crtc *crtc) { };
static inline void rzg2l_du_vsp_atomic_flush(struct rzg2l_du_crtc *crtc) { };
static inline struct drm_plane *rzg2l_du_vsp_get_drm_plane(struct rzg2l_du_crtc *crtc,
unsigned int pipe_index)
{
return ERR_PTR(-ENXIO);
}
#endif
#endif /* __RZG2L_DU_VSP_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,237 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* RZ/G2L MIPI DSI Interface Registers Definitions
*
* Copyright (C) 2022 Renesas Electronics Corporation
*/
#ifndef __RZG2L_MIPI_DSI_REGS_H__
#define __RZG2L_MIPI_DSI_REGS_H__
#include <linux/bits.h>
/* DPHY Registers */
#define DSIDPHYCTRL0 0x00
#define DSIDPHYCTRL0_CAL_EN_HSRX_OFS BIT(16)
#define DSIDPHYCTRL0_CMN_MASTER_EN BIT(8)
#define DSIDPHYCTRL0_RE_VDD_DETVCCQLV18 BIT(2)
#define DSIDPHYCTRL0_EN_LDO1200 BIT(1)
#define DSIDPHYCTRL0_EN_BGR BIT(0)
#define DSIDPHYTIM0 0x04
#define DSIDPHYTIM0_TCLK_MISS(x) ((x) << 24)
#define DSIDPHYTIM0_T_INIT(x) ((x) << 0)
#define DSIDPHYTIM1 0x08
#define DSIDPHYTIM1_THS_PREPARE(x) ((x) << 24)
#define DSIDPHYTIM1_TCLK_PREPARE(x) ((x) << 16)
#define DSIDPHYTIM1_THS_SETTLE(x) ((x) << 8)
#define DSIDPHYTIM1_TCLK_SETTLE(x) ((x) << 0)
#define DSIDPHYTIM2 0x0c
#define DSIDPHYTIM2_TCLK_TRAIL(x) ((x) << 24)
#define DSIDPHYTIM2_TCLK_POST(x) ((x) << 16)
#define DSIDPHYTIM2_TCLK_PRE(x) ((x) << 8)
#define DSIDPHYTIM2_TCLK_ZERO(x) ((x) << 0)
#define DSIDPHYTIM3 0x10
#define DSIDPHYTIM3_TLPX(x) ((x) << 24)
#define DSIDPHYTIM3_THS_EXIT(x) ((x) << 16)
#define DSIDPHYTIM3_THS_TRAIL(x) ((x) << 8)
#define DSIDPHYTIM3_THS_ZERO(x) ((x) << 0)
/* RZ/V2H DPHY Registers */
#define PLLENR 0x000
#define PLLENR_PLLEN BIT(0)
#define PHYRSTR 0x004
#define PHYRSTR_PHYMRSTN BIT(0)
#define PLLCLKSET0R 0x010
#define PLLCLKSET0R_PLL_S GENMASK(2, 0)
#define PLLCLKSET0R_PLL_P GENMASK(13, 8)
#define PLLCLKSET0R_PLL_M GENMASK(25, 16)
#define PLLCLKSET1R 0x014
#define PLLCLKSET1R_PLL_K GENMASK(15, 0)
#define PHYTCLKSETR 0x020
#define PHYTCLKSETR_TCLKTRAILCTL GENMASK(7, 0)
#define PHYTCLKSETR_TCLKPOSTCTL GENMASK(15, 8)
#define PHYTCLKSETR_TCLKZEROCTL GENMASK(23, 16)
#define PHYTCLKSETR_TCLKPRPRCTL GENMASK(31, 24)
#define PHYTHSSETR 0x024
#define PHYTHSSETR_THSEXITCTL GENMASK(7, 0)
#define PHYTHSSETR_THSTRAILCTL GENMASK(15, 8)
#define PHYTHSSETR_THSZEROCTL GENMASK(23, 16)
#define PHYTHSSETR_THSPRPRCTL GENMASK(31, 24)
#define PHYTLPXSETR 0x028
#define PHYTLPXSETR_TLPXCTL GENMASK(7, 0)
#define PHYCR 0x030
#define PHYCR_ULPSEXIT GENMASK(9, 0)
/* --------------------------------------------------------*/
/* Link Status Register */
#define LINKSR 0x10
#define LINKSR_LPBUSY BIT(13)
#define LINKSR_HSBUSY BIT(12)
#define LINKSR_VICHRUN1 BIT(8)
#define LINKSR_SQCHRUN1 BIT(4)
#define LINKSR_SQCHRUN0 BIT(0)
/* Tx Set Register */
#define TXSETR 0x100
#define TXSETR_NUMLANECAP (0x3 << 16)
#define TXSETR_DLEN (1 << 9)
#define TXSETR_CLEN (1 << 8)
#define TXSETR_NUMLANEUSE(x) (((x) & 0x3) << 0)
/* HS Clock Set Register */
#define HSCLKSETR 0x104
#define HSCLKSETR_HSCLKMODE_CONT (1 << 1)
#define HSCLKSETR_HSCLKMODE_NON_CONT (0 << 1)
#define HSCLKSETR_HSCLKRUN_HS (1 << 0)
#define HSCLKSETR_HSCLKRUN_LP (0 << 0)
/* Reset Control Register */
#define RSTCR 0x110
#define RSTCR_SWRST BIT(0)
#define RSTCR_FCETXSTP BIT(16)
/* Reset Status Register */
#define RSTSR 0x114
#define RSTSR_DL0DIR (1 << 15)
#define RSTSR_DLSTPST (0xf << 8)
#define RSTSR_SWRSTV1 (1 << 4)
#define RSTSR_SWRSTIB (1 << 3)
#define RSTSR_SWRSTAPB (1 << 2)
#define RSTSR_SWRSTLP (1 << 1)
#define RSTSR_SWRSTHS (1 << 0)
/* DSI Set Register */
#define DSISETR 0x120
#define DSISETR_MRPSZ GENMASK(15, 0)
/* Rx Result Save Slot 0 Register */
#define RXRSS0R 0x240
#define RXRSS0R_RXPKTDFAIL BIT(28)
#define RXRSS0R_RXFAIL BIT(27)
#define RXRSS0R_RXSUC BIT(25)
#define RXRSS0R_DT GENMASK(21, 16)
#define RXRSS0R_DATA1 GENMASK(15, 8)
#define RXRSS0R_DATA0 GENMASK(7, 0)
#define RXRSS0R_WC GENMASK(15, 0) /* Word count for long packet. */
/* Clock Lane Stop Time Set Register */
#define CLSTPTSETR 0x314
#define CLSTPTSETR_CLKKPT(x) ((x) << 24)
#define CLSTPTSETR_CLKBFHT(x) ((x) << 16)
#define CLSTPTSETR_CLKSTPT(x) ((x) << 2)
/* LP Transition Time Set Register */
#define LPTRNSTSETR 0x318
#define LPTRNSTSETR_GOLPBKT(x) ((x) << 0)
/* Physical Lane Status Register */
#define PLSR 0x320
#define PLSR_CLHS2LP BIT(27)
#define PLSR_CLLP2HS BIT(26)
/* Video-Input Channel 1 Set 0 Register */
#define VICH1SET0R 0x400
#define VICH1SET0R_VSEN BIT(12)
#define VICH1SET0R_HFPNOLP BIT(10)
#define VICH1SET0R_HBPNOLP BIT(9)
#define VICH1SET0R_HSANOLP BIT(8)
#define VICH1SET0R_VSTPAFT BIT(1)
#define VICH1SET0R_VSTART BIT(0)
/* Video-Input Channel 1 Set 1 Register */
#define VICH1SET1R 0x404
#define VICH1SET1R_DLY(x) (((x) & 0xfff) << 2)
/* Video-Input Channel 1 Status Register */
#define VICH1SR 0x410
#define VICH1SR_VIRDY BIT(3)
#define VICH1SR_RUNNING BIT(2)
#define VICH1SR_STOP BIT(1)
#define VICH1SR_START BIT(0)
/* Video-Input Channel 1 Pixel Packet Set Register */
#define VICH1PPSETR 0x420
#define VICH1PPSETR_DT_RGB16 (0x0e << 16)
#define VICH1PPSETR_DT_RGB18 (0x1e << 16)
#define VICH1PPSETR_DT_RGB18_LS (0x2e << 16)
#define VICH1PPSETR_DT_RGB24 (0x3e << 16)
#define VICH1PPSETR_TXESYNC_PULSE (1 << 15)
#define VICH1PPSETR_VC(x) ((x) << 22)
/* Video-Input Channel 1 Vertical Size Set Register */
#define VICH1VSSETR 0x428
#define VICH1VSSETR_VACTIVE(x) (((x) & 0x7fff) << 16)
#define VICH1VSSETR_VSPOL_LOW (1 << 15)
#define VICH1VSSETR_VSPOL_HIGH (0 << 15)
#define VICH1VSSETR_VSA(x) (((x) & 0xfff) << 0)
/* Video-Input Channel 1 Vertical Porch Set Register */
#define VICH1VPSETR 0x42c
#define VICH1VPSETR_VFP(x) (((x) & 0x1fff) << 16)
#define VICH1VPSETR_VBP(x) (((x) & 0x1fff) << 0)
/* Video-Input Channel 1 Horizontal Size Set Register */
#define VICH1HSSETR 0x430
#define VICH1HSSETR_HACTIVE(x) (((x) & 0x7fff) << 16)
#define VICH1HSSETR_HSPOL_LOW (1 << 15)
#define VICH1HSSETR_HSPOL_HIGH (0 << 15)
#define VICH1HSSETR_HSA(x) (((x) & 0xfff) << 0)
/* Video-Input Channel 1 Horizontal Porch Set Register */
#define VICH1HPSETR 0x434
#define VICH1HPSETR_HFP(x) (((x) & 0x1fff) << 16)
#define VICH1HPSETR_HBP(x) (((x) & 0x1fff) << 0)
/* Sequence Channel 0 Set 0 Register */
#define SQCH0SET0R 0x5c0
#define SQCH0SET0R_START BIT(0)
/* Sequence Channel 0 Status Register */
#define SQCH0SR 0x5d0
#define SQCH0SR_ADESFIN BIT(8)
/* Sequence Channel 0 Status Clear Register */
#define SQCH0SCR 0x5d4
#define SQCH0SCR_ADESFIN BIT(8)
/* Sequence Channel 0 Descriptor 0-A Register */
#define SQCH0DSC0AR 0x780
#define SQCH0DSC0AR_NXACT_TERM 0 /* Bit 28 */
#define SQCH0DSC0AR_BTA GENMASK(27, 26)
#define SQCH0DSC0AR_BTA_NONE 0
#define SQCH0DSC0AR_BTA_NON_READ 1
#define SQCH0DSC0AR_BTA_READ 2
#define SQCH0DSC0AR_BTA_ONLY 3
#define SQCH0DSC0AR_SPD_HIGH 0
#define SQCH0DSC0AR_SPD_LOW BIT(25)
#define SQCH0DSC0AR_FMT_SHORT 0
#define SQCH0DSC0AR_FMT_LONG BIT(24)
#define SQCH0DSC0AR_DT GENMASK(21, 16)
#define SQCH0DSC0AR_DATA1 GENMASK(15, 8)
#define SQCH0DSC0AR_DATA0 GENMASK(7, 0)
/* Sequence Channel 0 Descriptor 0-B Register */
#define SQCH0DSC0BR 0x784
#define SQCH0DSC0BR_DTSEL_MEM_SPACE BIT(24) /* Use external memory */
/* Sequence Channel 0 Descriptor 0-C Register */
#define SQCH0DSC0CR 0x788
#define SQCH0DSC0CR_FINACT BIT(0)
#define SQCH0DSC0CR_AUXOP BIT(22)
/* Sequence Channel 0 Descriptor 0-D Register */
#define SQCH0DSC0DR 0x78c
#endif /* __RZG2L_MIPI_DSI_REGS_H__ */
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0
config DRM_SHMOBILE
tristate "DRM Support for SH Mobile"
depends on DRM && PM
depends on ARCH_RENESAS || ARCH_SHMOBILE || COMPILE_TEST
select BACKLIGHT_CLASS_DEVICE
select DRM_CLIENT_SELECTION
select DRM_KMS_HELPER
select DRM_DISPLAY_HELPER
select DRM_BRIDGE_CONNECTOR
select DRM_GEM_DMA_HELPER
select VIDEOMODE_HELPERS
help
Choose this option if you have an SH Mobile chipset.
If M is selected the module will be called shmob-drm.
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
shmob-drm-y := shmob_drm_crtc.o \
shmob_drm_drv.o \
shmob_drm_kms.o \
shmob_drm_plane.o
obj-$(CONFIG_DRM_SHMOBILE) += shmob-drm.o
@@ -0,0 +1,620 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* shmob_drm_crtc.c -- SH Mobile DRM CRTCs
*
* Copyright (C) 2012 Renesas Electronics Corporation
*
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/clk.h>
#include <linux/media-bus-format.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/pm_runtime.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_atomic_uapi.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_modeset_helper.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_panel.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_vblank.h>
#include <video/videomode.h>
#include "shmob_drm_crtc.h"
#include "shmob_drm_drv.h"
#include "shmob_drm_kms.h"
#include "shmob_drm_plane.h"
#include "shmob_drm_regs.h"
/* -----------------------------------------------------------------------------
* Page Flip
*/
void shmob_drm_crtc_finish_page_flip(struct shmob_drm_crtc *scrtc)
{
struct drm_pending_vblank_event *event;
struct drm_device *dev = scrtc->base.dev;
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags);
event = scrtc->event;
scrtc->event = NULL;
if (event) {
drm_crtc_send_vblank_event(&scrtc->base, event);
wake_up(&scrtc->flip_wait);
drm_crtc_vblank_put(&scrtc->base);
}
spin_unlock_irqrestore(&dev->event_lock, flags);
}
static bool shmob_drm_crtc_page_flip_pending(struct shmob_drm_crtc *scrtc)
{
struct drm_device *dev = scrtc->base.dev;
unsigned long flags;
bool pending;
spin_lock_irqsave(&dev->event_lock, flags);
pending = scrtc->event != NULL;
spin_unlock_irqrestore(&dev->event_lock, flags);
return pending;
}
static void shmob_drm_crtc_wait_page_flip(struct shmob_drm_crtc *scrtc)
{
struct drm_crtc *crtc = &scrtc->base;
struct shmob_drm_device *sdev = to_shmob_device(crtc->dev);
if (wait_event_timeout(scrtc->flip_wait,
!shmob_drm_crtc_page_flip_pending(scrtc),
msecs_to_jiffies(50)))
return;
dev_warn(sdev->dev, "page flip timeout\n");
shmob_drm_crtc_finish_page_flip(scrtc);
}
/* -----------------------------------------------------------------------------
* CRTC
*/
static const struct {
u32 fmt;
u32 ldmt1r;
} shmob_drm_bus_fmts[] = {
{ MEDIA_BUS_FMT_RGB888_3X8, LDMT1R_MIFTYP_RGB8 },
{ MEDIA_BUS_FMT_RGB666_2X9_BE, LDMT1R_MIFTYP_RGB9 },
{ MEDIA_BUS_FMT_RGB888_2X12_BE, LDMT1R_MIFTYP_RGB12A },
{ MEDIA_BUS_FMT_RGB444_1X12, LDMT1R_MIFTYP_RGB12B },
{ MEDIA_BUS_FMT_RGB565_1X16, LDMT1R_MIFTYP_RGB16 },
{ MEDIA_BUS_FMT_RGB666_1X18, LDMT1R_MIFTYP_RGB18 },
{ MEDIA_BUS_FMT_RGB888_1X24, LDMT1R_MIFTYP_RGB24 },
{ MEDIA_BUS_FMT_UYVY8_1X16, LDMT1R_MIFTYP_YCBCR },
};
static void shmob_drm_crtc_setup_geometry(struct shmob_drm_crtc *scrtc)
{
struct drm_crtc *crtc = &scrtc->base;
struct shmob_drm_device *sdev = to_shmob_device(crtc->dev);
const struct drm_display_info *info = &sdev->connector->display_info;
const struct drm_display_mode *mode = &crtc->mode;
unsigned int i;
u32 value;
if (!info->num_bus_formats || !info->bus_formats) {
dev_warn(sdev->dev, "No bus format reported, using RGB888\n");
value = LDMT1R_MIFTYP_RGB24;
} else {
for (i = 0; i < ARRAY_SIZE(shmob_drm_bus_fmts); i++) {
if (shmob_drm_bus_fmts[i].fmt == info->bus_formats[0])
break;
}
if (i < ARRAY_SIZE(shmob_drm_bus_fmts)) {
value = shmob_drm_bus_fmts[i].ldmt1r;
} else {
dev_warn(sdev->dev,
"unsupported bus format 0x%x, using RGB888\n",
info->bus_formats[0]);
value = LDMT1R_MIFTYP_RGB24;
}
}
if (info->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE)
value |= LDMT1R_DWPOL;
if (info->bus_flags & DRM_BUS_FLAG_DE_LOW)
value |= LDMT1R_DIPOL;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
value |= LDMT1R_VPOL;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
value |= LDMT1R_HPOL;
lcdc_write(sdev, LDMT1R, value);
value = ((mode->hdisplay / 8) << 16) /* HDCN */
| (mode->htotal / 8); /* HTCN */
lcdc_write(sdev, LDHCNR, value);
value = (((mode->hsync_end - mode->hsync_start) / 8) << 16) /* HSYNW */
| (mode->hsync_start / 8); /* HSYNP */
lcdc_write(sdev, LDHSYNR, value);
value = ((mode->hdisplay & 7) << 24) | ((mode->htotal & 7) << 16)
| (((mode->hsync_end - mode->hsync_start) & 7) << 8)
| (mode->hsync_start & 7);
lcdc_write(sdev, LDHAJR, value);
value = ((mode->vdisplay) << 16) /* VDLN */
| mode->vtotal; /* VTLN */
lcdc_write(sdev, LDVLNR, value);
value = ((mode->vsync_end - mode->vsync_start) << 16) /* VSYNW */
| mode->vsync_start; /* VSYNP */
lcdc_write(sdev, LDVSYNR, value);
}
static void shmob_drm_crtc_start_stop(struct shmob_drm_crtc *scrtc, bool start)
{
struct shmob_drm_device *sdev = to_shmob_device(scrtc->base.dev);
u32 value;
value = lcdc_read(sdev, LDCNT2R);
if (start)
lcdc_write(sdev, LDCNT2R, value | LDCNT2R_DO);
else
lcdc_write(sdev, LDCNT2R, value & ~LDCNT2R_DO);
/* Wait until power is applied/stopped. */
while (1) {
value = lcdc_read(sdev, LDPMR) & LDPMR_LPS;
if ((start && value) || (!start && !value))
break;
cpu_relax();
}
if (!start) {
/* Stop the dot clock. */
lcdc_write(sdev, LDDCKSTPR, LDDCKSTPR_DCKSTP);
}
}
static inline struct shmob_drm_crtc *to_shmob_crtc(struct drm_crtc *crtc)
{
return container_of(crtc, struct shmob_drm_crtc, base);
}
static void shmob_drm_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc);
struct shmob_drm_device *sdev = to_shmob_device(crtc->dev);
unsigned int clk_div = sdev->config.clk_div;
struct device *dev = sdev->dev;
u32 value;
int ret;
ret = pm_runtime_resume_and_get(dev);
if (ret)
return;
/* Reset and enable the LCDC. */
lcdc_write(sdev, LDCNT2R, lcdc_read(sdev, LDCNT2R) | LDCNT2R_BR);
lcdc_wait_bit(sdev, LDCNT2R, LDCNT2R_BR, 0);
lcdc_write(sdev, LDCNT2R, LDCNT2R_ME);
/* Stop the LCDC first and disable all interrupts. */
shmob_drm_crtc_start_stop(scrtc, false);
lcdc_write(sdev, LDINTR, 0);
/* Configure power supply, dot clocks and start them. */
lcdc_write(sdev, LDPMR, 0);
value = sdev->lddckr;
if (clk_div) {
/* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider
* denominator.
*/
lcdc_write(sdev, LDDCKPAT1R, 0);
lcdc_write(sdev, LDDCKPAT2R, (1 << (clk_div / 2)) - 1);
if (clk_div == 1)
value |= LDDCKR_MOSEL;
else
value |= clk_div;
}
lcdc_write(sdev, LDDCKR, value);
lcdc_write(sdev, LDDCKSTPR, 0);
lcdc_wait_bit(sdev, LDDCKSTPR, ~0, 0);
/* Setup geometry, format, frame buffer memory and operation mode. */
shmob_drm_crtc_setup_geometry(scrtc);
lcdc_write(sdev, LDSM1R, 0);
/* Enable the display output. */
lcdc_write(sdev, LDCNT1R, LDCNT1R_DE);
shmob_drm_crtc_start_stop(scrtc, true);
/* Turn vertical blank interrupt reporting back on. */
drm_crtc_vblank_on(crtc);
}
static void shmob_drm_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc);
struct shmob_drm_device *sdev = to_shmob_device(crtc->dev);
/*
* Disable vertical blank interrupt reporting. We first need to wait
* for page flip completion before stopping the CRTC as userspace
* expects page flips to eventually complete.
*/
shmob_drm_crtc_wait_page_flip(scrtc);
drm_crtc_vblank_off(crtc);
/* Stop the LCDC. */
shmob_drm_crtc_start_stop(scrtc, false);
/* Disable the display output. */
lcdc_write(sdev, LDCNT1R, 0);
pm_runtime_put(sdev->dev);
}
static void shmob_drm_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct drm_pending_vblank_event *event;
struct drm_device *dev = crtc->dev;
unsigned long flags;
if (crtc->state->event) {
spin_lock_irqsave(&dev->event_lock, flags);
event = crtc->state->event;
crtc->state->event = NULL;
drm_crtc_send_vblank_event(crtc, event);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
static const struct drm_crtc_helper_funcs crtc_helper_funcs = {
.atomic_flush = shmob_drm_crtc_atomic_flush,
.atomic_enable = shmob_drm_crtc_atomic_enable,
.atomic_disable = shmob_drm_crtc_atomic_disable,
};
static int shmob_drm_crtc_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event,
uint32_t page_flip_flags,
struct drm_modeset_acquire_ctx *ctx)
{
struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc);
struct drm_device *dev = scrtc->base.dev;
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags);
if (scrtc->event != NULL) {
spin_unlock_irqrestore(&dev->event_lock, flags);
return -EBUSY;
}
spin_unlock_irqrestore(&dev->event_lock, flags);
drm_atomic_set_fb_for_plane(crtc->primary->state, fb);
if (event) {
event->pipe = 0;
drm_crtc_vblank_get(&scrtc->base);
spin_lock_irqsave(&dev->event_lock, flags);
scrtc->event = event;
spin_unlock_irqrestore(&dev->event_lock, flags);
}
return 0;
}
static void shmob_drm_crtc_enable_vblank(struct shmob_drm_device *sdev,
bool enable)
{
unsigned long flags;
u32 ldintr;
/* Be careful not to acknowledge any pending interrupt. */
spin_lock_irqsave(&sdev->irq_lock, flags);
ldintr = lcdc_read(sdev, LDINTR) | LDINTR_STATUS_MASK;
if (enable)
ldintr |= LDINTR_VEE;
else
ldintr &= ~LDINTR_VEE;
lcdc_write(sdev, LDINTR, ldintr);
spin_unlock_irqrestore(&sdev->irq_lock, flags);
}
static int shmob_drm_enable_vblank(struct drm_crtc *crtc)
{
struct shmob_drm_device *sdev = to_shmob_device(crtc->dev);
shmob_drm_crtc_enable_vblank(sdev, true);
return 0;
}
static void shmob_drm_disable_vblank(struct drm_crtc *crtc)
{
struct shmob_drm_device *sdev = to_shmob_device(crtc->dev);
shmob_drm_crtc_enable_vblank(sdev, false);
}
static const struct drm_crtc_funcs crtc_funcs = {
.reset = drm_atomic_helper_crtc_reset,
.destroy = drm_crtc_cleanup,
.set_config = drm_atomic_helper_set_config,
.page_flip = shmob_drm_crtc_page_flip,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.enable_vblank = shmob_drm_enable_vblank,
.disable_vblank = shmob_drm_disable_vblank,
};
int shmob_drm_crtc_create(struct shmob_drm_device *sdev)
{
struct drm_crtc *crtc = &sdev->crtc.base;
struct drm_plane *primary, *plane;
unsigned int i;
int ret;
init_waitqueue_head(&sdev->crtc.flip_wait);
primary = shmob_drm_plane_create(sdev, DRM_PLANE_TYPE_PRIMARY, 0);
if (IS_ERR(primary))
return PTR_ERR(primary);
for (i = 1; i < 5; ++i) {
plane = shmob_drm_plane_create(sdev, DRM_PLANE_TYPE_OVERLAY, i);
if (IS_ERR(plane))
return PTR_ERR(plane);
}
ret = drm_crtc_init_with_planes(&sdev->ddev, crtc, primary, NULL,
&crtc_funcs, NULL);
if (ret < 0)
return ret;
drm_crtc_helper_add(crtc, &crtc_helper_funcs);
/* Start with vertical blank interrupt reporting disabled. */
drm_crtc_vblank_off(crtc);
return 0;
}
/* -----------------------------------------------------------------------------
* Legacy Encoder
*/
static bool shmob_drm_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct drm_device *dev = encoder->dev;
struct shmob_drm_device *sdev = to_shmob_device(dev);
struct drm_connector *connector = sdev->connector;
const struct drm_display_mode *panel_mode;
if (list_empty(&connector->modes)) {
dev_dbg(dev->dev, "mode_fixup: empty modes list\n");
return false;
}
/* The flat panel mode is fixed, just copy it to the adjusted mode. */
panel_mode = list_first_entry(&connector->modes,
struct drm_display_mode, head);
drm_mode_copy(adjusted_mode, panel_mode);
return true;
}
static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
.mode_fixup = shmob_drm_encoder_mode_fixup,
};
/* -----------------------------------------------------------------------------
* Encoder
*/
int shmob_drm_encoder_create(struct shmob_drm_device *sdev)
{
struct drm_encoder *encoder = &sdev->encoder;
struct drm_bridge *bridge;
int ret;
encoder->possible_crtcs = 1;
ret = drm_simple_encoder_init(&sdev->ddev, encoder,
DRM_MODE_ENCODER_DPI);
if (ret < 0)
return ret;
if (sdev->pdata) {
drm_encoder_helper_add(encoder, &encoder_helper_funcs);
return 0;
}
/* Create a panel bridge */
bridge = devm_drm_of_get_bridge(sdev->dev, sdev->dev->of_node, 0, 0);
if (IS_ERR(bridge))
return PTR_ERR(bridge);
/* Attach the bridge to the encoder */
ret = drm_bridge_attach(encoder, bridge, NULL,
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret) {
dev_err(sdev->dev, "failed to attach bridge: %pe\n",
ERR_PTR(ret));
return ret;
}
return 0;
}
/* -----------------------------------------------------------------------------
* Legacy Connector
*/
static inline struct shmob_drm_connector *to_shmob_connector(struct drm_connector *connector)
{
return container_of(connector, struct shmob_drm_connector, base);
}
static int shmob_drm_connector_get_modes(struct drm_connector *connector)
{
struct shmob_drm_connector *scon = to_shmob_connector(connector);
struct drm_display_mode *mode;
mode = drm_mode_create(connector->dev);
if (mode == NULL)
return 0;
mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER;
drm_display_mode_from_videomode(scon->mode, mode);
drm_mode_probed_add(connector, mode);
return 1;
}
static struct drm_encoder *
shmob_drm_connector_best_encoder(struct drm_connector *connector)
{
struct shmob_drm_connector *scon = to_shmob_connector(connector);
return scon->encoder;
}
static const struct drm_connector_helper_funcs connector_helper_funcs = {
.get_modes = shmob_drm_connector_get_modes,
.best_encoder = shmob_drm_connector_best_encoder,
};
static void shmob_drm_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
kfree(connector);
}
static const struct drm_connector_funcs connector_funcs = {
.reset = drm_atomic_helper_connector_reset,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = shmob_drm_connector_destroy,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static struct drm_connector *
shmob_drm_connector_init(struct shmob_drm_device *sdev,
struct drm_encoder *encoder)
{
u32 bus_fmt = sdev->pdata->iface.bus_fmt;
struct shmob_drm_connector *scon;
struct drm_connector *connector;
struct drm_display_info *info;
unsigned int i;
int ret;
for (i = 0; i < ARRAY_SIZE(shmob_drm_bus_fmts); i++) {
if (shmob_drm_bus_fmts[i].fmt == bus_fmt)
break;
}
if (i == ARRAY_SIZE(shmob_drm_bus_fmts)) {
dev_err(sdev->dev, "unsupported bus format 0x%x\n", bus_fmt);
return ERR_PTR(-EINVAL);
}
scon = kzalloc_obj(*scon);
if (!scon)
return ERR_PTR(-ENOMEM);
connector = &scon->base;
scon->encoder = encoder;
scon->mode = &sdev->pdata->panel.mode;
info = &connector->display_info;
info->width_mm = sdev->pdata->panel.width_mm;
info->height_mm = sdev->pdata->panel.height_mm;
if (scon->mode->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE;
if (scon->mode->flags & DISPLAY_FLAGS_DE_LOW)
info->bus_flags |= DRM_BUS_FLAG_DE_LOW;
ret = drm_display_info_set_bus_formats(info, &bus_fmt, 1);
if (ret < 0) {
kfree(scon);
return ERR_PTR(ret);
}
ret = drm_connector_init(&sdev->ddev, connector, &connector_funcs,
DRM_MODE_CONNECTOR_DPI);
if (ret < 0) {
kfree(scon);
return ERR_PTR(ret);
}
drm_connector_helper_add(connector, &connector_helper_funcs);
ret = drm_connector_attach_encoder(connector, encoder);
if (ret < 0) {
drm_connector_cleanup(connector);
kfree(scon);
return ERR_PTR(ret);
}
return connector;
}
/* -----------------------------------------------------------------------------
* Connector
*/
int shmob_drm_connector_create(struct shmob_drm_device *sdev,
struct drm_encoder *encoder)
{
struct drm_connector *connector;
if (sdev->pdata)
connector = shmob_drm_connector_init(sdev, encoder);
else
connector = drm_bridge_connector_init(&sdev->ddev, encoder);
if (IS_ERR(connector)) {
dev_err(sdev->dev, "failed to created connector: %pe\n",
connector);
return PTR_ERR(connector);
}
connector->dpms = DRM_MODE_DPMS_OFF;
sdev->connector = connector;
return 0;
}
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* shmob_drm_crtc.h -- SH Mobile DRM CRTCs
*
* Copyright (C) 2012 Renesas Electronics Corporation
*
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __SHMOB_DRM_CRTC_H__
#define __SHMOB_DRM_CRTC_H__
#include <drm/drm_crtc.h>
#include <drm/drm_connector.h>
#include <drm/drm_encoder.h>
#include <linux/wait.h>
#include <video/videomode.h>
struct drm_pending_vblank_event;
struct shmob_drm_device;
struct shmob_drm_format_info;
struct shmob_drm_crtc {
struct drm_crtc base;
struct drm_pending_vblank_event *event;
wait_queue_head_t flip_wait;
};
/* Legacy connector */
struct shmob_drm_connector {
struct drm_connector base;
struct drm_encoder *encoder;
const struct videomode *mode;
};
int shmob_drm_crtc_create(struct shmob_drm_device *sdev);
void shmob_drm_crtc_finish_page_flip(struct shmob_drm_crtc *scrtc);
int shmob_drm_encoder_create(struct shmob_drm_device *sdev);
int shmob_drm_connector_create(struct shmob_drm_device *sdev,
struct drm_encoder *encoder);
#endif /* __SHMOB_DRM_CRTC_H__ */
@@ -0,0 +1,297 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* shmob_drm_drv.c -- SH Mobile DRM driver
*
* Copyright (C) 2012 Renesas Electronics Corporation
*
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <drm/clients/drm_client_setup.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_dma.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_modeset_helper.h>
#include <drm/drm_module.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include "shmob_drm_drv.h"
#include "shmob_drm_kms.h"
#include "shmob_drm_plane.h"
#include "shmob_drm_regs.h"
/* -----------------------------------------------------------------------------
* Hardware initialization
*/
static int shmob_drm_setup_clocks(struct shmob_drm_device *sdev,
enum shmob_drm_clk_source clksrc)
{
struct clk *clk;
char *clkname;
switch (clksrc) {
case SHMOB_DRM_CLK_BUS:
clkname = "fck";
sdev->lddckr = LDDCKR_ICKSEL_BUS;
break;
case SHMOB_DRM_CLK_PERIPHERAL:
clkname = "media";
sdev->lddckr = LDDCKR_ICKSEL_MIPI;
break;
case SHMOB_DRM_CLK_EXTERNAL:
clkname = "lclk";
sdev->lddckr = LDDCKR_ICKSEL_HDMI;
break;
default:
return -EINVAL;
}
clk = devm_clk_get(sdev->dev, clkname);
if (IS_ERR(clk)) {
dev_err(sdev->dev, "cannot get dot clock %s\n", clkname);
return PTR_ERR(clk);
}
sdev->clock = clk;
return 0;
}
/* -----------------------------------------------------------------------------
* DRM operations
*/
static irqreturn_t shmob_drm_irq(int irq, void *arg)
{
struct drm_device *dev = arg;
struct shmob_drm_device *sdev = to_shmob_device(dev);
unsigned long flags;
u32 status;
/* Acknowledge interrupts. Putting interrupt enable and interrupt flag
* bits in the same register is really brain-dead design and requires
* taking a spinlock.
*/
spin_lock_irqsave(&sdev->irq_lock, flags);
status = lcdc_read(sdev, LDINTR);
lcdc_write(sdev, LDINTR, status ^ LDINTR_STATUS_MASK);
spin_unlock_irqrestore(&sdev->irq_lock, flags);
if (status & LDINTR_VES) {
drm_crtc_handle_vblank(&sdev->crtc.base);
shmob_drm_crtc_finish_page_flip(&sdev->crtc);
}
return IRQ_HANDLED;
}
DEFINE_DRM_GEM_DMA_FOPS(shmob_drm_fops);
static const struct drm_driver shmob_drm_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
DRM_GEM_DMA_DRIVER_OPS,
DRM_FBDEV_DMA_DRIVER_OPS,
.fops = &shmob_drm_fops,
.name = "shmob-drm",
.desc = "Renesas SH Mobile DRM",
.major = 1,
.minor = 0,
};
/* -----------------------------------------------------------------------------
* Power management
*/
static int shmob_drm_pm_suspend(struct device *dev)
{
struct shmob_drm_device *sdev = dev_get_drvdata(dev);
return drm_mode_config_helper_suspend(&sdev->ddev);
}
static int shmob_drm_pm_resume(struct device *dev)
{
struct shmob_drm_device *sdev = dev_get_drvdata(dev);
return drm_mode_config_helper_resume(&sdev->ddev);
}
static int shmob_drm_pm_runtime_suspend(struct device *dev)
{
struct shmob_drm_device *sdev = dev_get_drvdata(dev);
if (sdev->clock)
clk_disable_unprepare(sdev->clock);
return 0;
}
static int shmob_drm_pm_runtime_resume(struct device *dev)
{
struct shmob_drm_device *sdev = dev_get_drvdata(dev);
int ret;
if (sdev->clock) {
ret = clk_prepare_enable(sdev->clock);
if (ret < 0)
return ret;
}
return 0;
}
static const struct dev_pm_ops shmob_drm_pm_ops = {
SYSTEM_SLEEP_PM_OPS(shmob_drm_pm_suspend, shmob_drm_pm_resume)
RUNTIME_PM_OPS(shmob_drm_pm_runtime_suspend,
shmob_drm_pm_runtime_resume, NULL)
};
/* -----------------------------------------------------------------------------
* Platform driver
*/
static void shmob_drm_remove(struct platform_device *pdev)
{
struct shmob_drm_device *sdev = platform_get_drvdata(pdev);
struct drm_device *ddev = &sdev->ddev;
drm_dev_unregister(ddev);
drm_atomic_helper_shutdown(ddev);
drm_kms_helper_poll_fini(ddev);
}
static void shmob_drm_shutdown(struct platform_device *pdev)
{
struct shmob_drm_device *sdev = platform_get_drvdata(pdev);
drm_atomic_helper_shutdown(&sdev->ddev);
}
static int shmob_drm_probe(struct platform_device *pdev)
{
struct shmob_drm_platform_data *pdata = pdev->dev.platform_data;
const struct shmob_drm_config *config;
struct shmob_drm_device *sdev;
struct drm_device *ddev;
int ret;
config = of_device_get_match_data(&pdev->dev);
if (!config && !pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
/*
* Allocate and initialize the DRM device, driver private data, I/O
* resources and clocks.
*/
sdev = devm_drm_dev_alloc(&pdev->dev, &shmob_drm_driver,
struct shmob_drm_device, ddev);
if (IS_ERR(sdev))
return PTR_ERR(sdev);
ddev = &sdev->ddev;
sdev->dev = &pdev->dev;
if (config) {
sdev->config = *config;
} else {
sdev->pdata = pdata;
sdev->config.clk_source = pdata->clk_source;
sdev->config.clk_div = pdata->iface.clk_div;
}
spin_lock_init(&sdev->irq_lock);
platform_set_drvdata(pdev, sdev);
sdev->mmio = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(sdev->mmio))
return PTR_ERR(sdev->mmio);
ret = shmob_drm_setup_clocks(sdev, sdev->config.clk_source);
if (ret < 0)
return ret;
ret = devm_pm_runtime_enable(&pdev->dev);
if (ret)
return ret;
ret = drm_vblank_init(ddev, 1);
if (ret < 0) {
dev_err(&pdev->dev, "failed to initialize vblank\n");
return ret;
}
ret = shmob_drm_modeset_init(sdev);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret,
"failed to initialize mode setting\n");
ret = platform_get_irq(pdev, 0);
if (ret < 0)
goto err_modeset_cleanup;
sdev->irq = ret;
ret = devm_request_irq(&pdev->dev, sdev->irq, shmob_drm_irq, 0,
ddev->driver->name, ddev);
if (ret < 0) {
dev_err(&pdev->dev, "failed to install IRQ handler\n");
goto err_modeset_cleanup;
}
/*
* Register the DRM device with the core and the connectors with
* sysfs.
*/
ret = drm_dev_register(ddev, 0);
if (ret < 0)
goto err_modeset_cleanup;
drm_client_setup_with_fourcc(ddev, DRM_FORMAT_RGB565);
return 0;
err_modeset_cleanup:
drm_kms_helper_poll_fini(ddev);
return ret;
}
static const struct shmob_drm_config shmob_arm_config = {
.clk_source = SHMOB_DRM_CLK_BUS,
.clk_div = 5,
};
static const struct of_device_id shmob_drm_of_table[] __maybe_unused = {
{ .compatible = "renesas,r8a7740-lcdc", .data = &shmob_arm_config, },
{ .compatible = "renesas,sh73a0-lcdc", .data = &shmob_arm_config, },
{ /* sentinel */ }
};
static struct platform_driver shmob_drm_platform_driver = {
.probe = shmob_drm_probe,
.remove = shmob_drm_remove,
.shutdown = shmob_drm_shutdown,
.driver = {
.name = "shmob-drm",
.of_match_table = of_match_ptr(shmob_drm_of_table),
.pm = &shmob_drm_pm_ops,
},
};
drm_module_platform_driver(shmob_drm_platform_driver);
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
MODULE_DESCRIPTION("Renesas SH Mobile DRM Driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* shmob_drm.h -- SH Mobile DRM driver
*
* Copyright (C) 2012 Renesas Electronics Corporation
*
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __SHMOB_DRM_DRV_H__
#define __SHMOB_DRM_DRV_H__
#include <linux/kernel.h>
#include <linux/platform_data/shmob_drm.h>
#include <linux/spinlock.h>
#include "shmob_drm_crtc.h"
struct clk;
struct device;
struct drm_device;
struct shmob_drm_config {
enum shmob_drm_clk_source clk_source;
unsigned int clk_div;
};
struct shmob_drm_device {
struct device *dev;
const struct shmob_drm_platform_data *pdata;
struct shmob_drm_config config;
void __iomem *mmio;
struct clk *clock;
u32 lddckr;
unsigned int irq;
spinlock_t irq_lock; /* Protects hardware LDINTR register */
struct drm_device ddev;
struct shmob_drm_crtc crtc;
struct drm_encoder encoder;
struct drm_connector *connector;
};
static inline struct shmob_drm_device *to_shmob_device(struct drm_device *dev)
{
return container_of(dev, struct shmob_drm_device, ddev);
}
#endif /* __SHMOB_DRM_DRV_H__ */
@@ -0,0 +1,189 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* shmob_drm_kms.c -- SH Mobile DRM Mode Setting
*
* Copyright (C) 2012 Renesas Electronics Corporation
*
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_probe_helper.h>
#include "shmob_drm_crtc.h"
#include "shmob_drm_drv.h"
#include "shmob_drm_kms.h"
#include "shmob_drm_plane.h"
#include "shmob_drm_regs.h"
/* -----------------------------------------------------------------------------
* Format helpers
*/
static const struct shmob_drm_format_info shmob_drm_format_infos[] = {
{
.fourcc = DRM_FORMAT_RGB565,
.bpp = 16,
.lddfr = LDDFR_PKF_RGB16,
.ldddsr = LDDDSR_LS | LDDDSR_WS,
.ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW |
LDBBSIFR_RY | LDBBSIFR_RPKF_RGB16,
}, {
.fourcc = DRM_FORMAT_RGB888,
.bpp = 24,
.lddfr = LDDFR_PKF_RGB24,
.ldddsr = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS,
.ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW |
LDBBSIFR_SWPB | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB24,
}, {
.fourcc = DRM_FORMAT_ARGB8888,
.bpp = 32,
.lddfr = LDDFR_PKF_ARGB32,
.ldddsr = LDDDSR_LS,
.ldbbsifr = LDBBSIFR_AL_PK | LDBBSIFR_SWPL | LDBBSIFR_RY |
LDBBSIFR_RPKF_ARGB32,
}, {
.fourcc = DRM_FORMAT_XRGB8888,
.bpp = 32,
.lddfr = LDDFR_PKF_ARGB32,
.ldddsr = LDDDSR_LS,
.ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_RY |
LDBBSIFR_RPKF_ARGB32,
}, {
.fourcc = DRM_FORMAT_NV12,
.bpp = 12,
.lddfr = LDDFR_CC | LDDFR_YF_420,
.ldddsr = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS,
.ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW |
LDBBSIFR_SWPB | LDBBSIFR_CHRR_420,
}, {
.fourcc = DRM_FORMAT_NV21,
.bpp = 12,
.lddfr = LDDFR_CC | LDDFR_YF_420,
.ldddsr = LDDDSR_LS | LDDDSR_WS,
.ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW |
LDBBSIFR_CHRR_420,
}, {
.fourcc = DRM_FORMAT_NV16,
.bpp = 16,
.lddfr = LDDFR_CC | LDDFR_YF_422,
.ldddsr = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS,
.ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW |
LDBBSIFR_SWPB | LDBBSIFR_CHRR_422,
}, {
.fourcc = DRM_FORMAT_NV61,
.bpp = 16,
.lddfr = LDDFR_CC | LDDFR_YF_422,
.ldddsr = LDDDSR_LS | LDDDSR_WS,
.ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW |
LDBBSIFR_CHRR_422,
}, {
.fourcc = DRM_FORMAT_NV24,
.bpp = 24,
.lddfr = LDDFR_CC | LDDFR_YF_444,
.ldddsr = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS,
.ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW |
LDBBSIFR_SWPB | LDBBSIFR_CHRR_444,
}, {
.fourcc = DRM_FORMAT_NV42,
.bpp = 24,
.lddfr = LDDFR_CC | LDDFR_YF_444,
.ldddsr = LDDDSR_LS | LDDDSR_WS,
.ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW |
LDBBSIFR_CHRR_444,
},
};
const struct shmob_drm_format_info *shmob_drm_format_info(u32 fourcc)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(shmob_drm_format_infos); ++i) {
if (shmob_drm_format_infos[i].fourcc == fourcc)
return &shmob_drm_format_infos[i];
}
return NULL;
}
/* -----------------------------------------------------------------------------
* Frame buffer
*/
static struct drm_framebuffer *
shmob_drm_fb_create(struct drm_device *dev, struct drm_file *file_priv,
const struct drm_format_info *info,
const struct drm_mode_fb_cmd2 *mode_cmd)
{
const struct shmob_drm_format_info *format;
format = shmob_drm_format_info(mode_cmd->pixel_format);
if (format == NULL) {
dev_dbg(dev->dev, "unsupported pixel format %p4cc\n",
&mode_cmd->pixel_format);
return ERR_PTR(-EINVAL);
}
if (mode_cmd->pitches[0] & 7 || mode_cmd->pitches[0] >= 65536) {
dev_dbg(dev->dev, "invalid pitch value %u\n",
mode_cmd->pitches[0]);
return ERR_PTR(-EINVAL);
}
if (shmob_drm_format_is_yuv(format)) {
unsigned int chroma_cpp = format->bpp == 24 ? 2 : 1;
if (mode_cmd->pitches[1] != mode_cmd->pitches[0] * chroma_cpp) {
dev_dbg(dev->dev,
"luma and chroma pitches do not match\n");
return ERR_PTR(-EINVAL);
}
}
return drm_gem_fb_create(dev, file_priv, info, mode_cmd);
}
static const struct drm_mode_config_funcs shmob_drm_mode_config_funcs = {
.fb_create = shmob_drm_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
int shmob_drm_modeset_init(struct shmob_drm_device *sdev)
{
struct drm_device *dev = &sdev->ddev;
int ret;
ret = drmm_mode_config_init(dev);
if (ret)
return ret;
ret = shmob_drm_crtc_create(sdev);
if (ret < 0)
return ret;
ret = shmob_drm_encoder_create(sdev);
if (ret < 0)
return ret;
ret = shmob_drm_connector_create(sdev, &sdev->encoder);
if (ret < 0)
return ret;
drm_mode_config_reset(dev);
drm_kms_helper_poll_init(dev);
sdev->ddev.mode_config.min_width = 0;
sdev->ddev.mode_config.min_height = 0;
sdev->ddev.mode_config.max_width = 4095;
sdev->ddev.mode_config.max_height = 4095;
sdev->ddev.mode_config.funcs = &shmob_drm_mode_config_funcs;
return 0;
}
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* shmob_drm_kms.h -- SH Mobile DRM Mode Setting
*
* Copyright (C) 2012 Renesas Electronics Corporation
*
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __SHMOB_DRM_KMS_H__
#define __SHMOB_DRM_KMS_H__
#include <linux/types.h>
struct drm_gem_dma_object;
struct shmob_drm_device;
struct shmob_drm_format_info {
u32 fourcc;
u32 lddfr; /* LCD Data Format Register */
u16 ldbbsifr; /* CHn Source Image Format Register low bits */
u8 ldddsr; /* LCDC Input Image Data Swap Register low bits */
u8 bpp;
};
#define shmob_drm_format_is_yuv(format) ((format)->lddfr & LDDFR_CC)
const struct shmob_drm_format_info *shmob_drm_format_info(u32 fourcc);
int shmob_drm_modeset_init(struct shmob_drm_device *sdev);
#endif /* __SHMOB_DRM_KMS_H__ */
@@ -0,0 +1,328 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* shmob_drm_plane.c -- SH Mobile DRM Planes
*
* Copyright (C) 2012 Renesas Electronics Corporation
*
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include "shmob_drm_drv.h"
#include "shmob_drm_kms.h"
#include "shmob_drm_plane.h"
#include "shmob_drm_regs.h"
struct shmob_drm_plane {
struct drm_plane base;
unsigned int index;
};
struct shmob_drm_plane_state {
struct drm_plane_state base;
const struct shmob_drm_format_info *format;
u32 dma[2];
};
static inline struct shmob_drm_plane *to_shmob_plane(struct drm_plane *plane)
{
return container_of(plane, struct shmob_drm_plane, base);
}
static inline struct shmob_drm_plane_state *to_shmob_plane_state(struct drm_plane_state *state)
{
return container_of(state, struct shmob_drm_plane_state, base);
}
static void shmob_drm_plane_compute_base(struct shmob_drm_plane_state *sstate)
{
struct drm_framebuffer *fb = sstate->base.fb;
unsigned int x = sstate->base.src_x >> 16;
unsigned int y = sstate->base.src_y >> 16;
struct drm_gem_dma_object *gem;
unsigned int bpp;
bpp = shmob_drm_format_is_yuv(sstate->format) ? 8 : sstate->format->bpp;
gem = drm_fb_dma_get_gem_obj(fb, 0);
sstate->dma[0] = gem->dma_addr + fb->offsets[0]
+ y * fb->pitches[0] + x * bpp / 8;
if (shmob_drm_format_is_yuv(sstate->format)) {
bpp = sstate->format->bpp - 8;
gem = drm_fb_dma_get_gem_obj(fb, 1);
sstate->dma[1] = gem->dma_addr + fb->offsets[1]
+ y / (bpp == 4 ? 2 : 1) * fb->pitches[1]
+ x * (bpp == 16 ? 2 : 1);
}
}
static void shmob_drm_primary_plane_setup(struct shmob_drm_plane *splane,
struct drm_plane_state *state)
{
struct shmob_drm_plane_state *sstate = to_shmob_plane_state(state);
struct shmob_drm_device *sdev = to_shmob_device(splane->base.dev);
struct drm_framebuffer *fb = state->fb;
/* TODO: Handle YUV colorspaces. Hardcode REC709 for now. */
lcdc_write(sdev, LDDFR, sstate->format->lddfr | LDDFR_CF1);
lcdc_write(sdev, LDMLSR, fb->pitches[0]);
/* Word and long word swap. */
lcdc_write(sdev, LDDDSR, sstate->format->ldddsr);
lcdc_write_mirror(sdev, LDSA1R, sstate->dma[0]);
if (shmob_drm_format_is_yuv(sstate->format))
lcdc_write_mirror(sdev, LDSA2R, sstate->dma[1]);
lcdc_write(sdev, LDRCNTR, lcdc_read(sdev, LDRCNTR) ^ LDRCNTR_MRS);
}
static void shmob_drm_overlay_plane_setup(struct shmob_drm_plane *splane,
struct drm_plane_state *state)
{
struct shmob_drm_plane_state *sstate = to_shmob_plane_state(state);
struct shmob_drm_device *sdev = to_shmob_device(splane->base.dev);
struct drm_framebuffer *fb = state->fb;
u32 format;
/* TODO: Support ROP3 mode */
format = LDBBSIFR_EN | ((state->alpha >> 8) << LDBBSIFR_LAY_SHIFT) |
sstate->format->ldbbsifr;
#define plane_reg_dump(sdev, splane, reg) \
dev_dbg(sdev->ddev.dev, "%s(%u): %s 0x%08x 0x%08x\n", __func__, \
splane->index, #reg, \
lcdc_read(sdev, reg(splane->index)), \
lcdc_read(sdev, reg(splane->index) + LCDC_SIDE_B_OFFSET))
plane_reg_dump(sdev, splane, LDBnBSIFR);
plane_reg_dump(sdev, splane, LDBnBSSZR);
plane_reg_dump(sdev, splane, LDBnBLOCR);
plane_reg_dump(sdev, splane, LDBnBSMWR);
plane_reg_dump(sdev, splane, LDBnBSAYR);
plane_reg_dump(sdev, splane, LDBnBSACR);
lcdc_write(sdev, LDBCR, LDBCR_UPC(splane->index));
dev_dbg(sdev->ddev.dev, "%s(%u): %s 0x%08x\n", __func__, splane->index,
"LDBCR", lcdc_read(sdev, LDBCR));
lcdc_write(sdev, LDBnBSIFR(splane->index), format);
lcdc_write(sdev, LDBnBSSZR(splane->index),
(state->crtc_h << LDBBSSZR_BVSS_SHIFT) |
(state->crtc_w << LDBBSSZR_BHSS_SHIFT));
lcdc_write(sdev, LDBnBLOCR(splane->index),
(state->crtc_y << LDBBLOCR_CVLC_SHIFT) |
(state->crtc_x << LDBBLOCR_CHLC_SHIFT));
lcdc_write(sdev, LDBnBSMWR(splane->index),
fb->pitches[0] << LDBBSMWR_BSMW_SHIFT);
lcdc_write(sdev, LDBnBSAYR(splane->index), sstate->dma[0]);
if (shmob_drm_format_is_yuv(sstate->format))
lcdc_write(sdev, LDBnBSACR(splane->index), sstate->dma[1]);
lcdc_write(sdev, LDBCR,
LDBCR_UPF(splane->index) | LDBCR_UPD(splane->index));
dev_dbg(sdev->ddev.dev, "%s(%u): %s 0x%08x\n", __func__, splane->index,
"LDBCR", lcdc_read(sdev, LDBCR));
plane_reg_dump(sdev, splane, LDBnBSIFR);
plane_reg_dump(sdev, splane, LDBnBSSZR);
plane_reg_dump(sdev, splane, LDBnBLOCR);
plane_reg_dump(sdev, splane, LDBnBSMWR);
plane_reg_dump(sdev, splane, LDBnBSAYR);
plane_reg_dump(sdev, splane, LDBnBSACR);
}
static int shmob_drm_plane_atomic_check(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane);
struct shmob_drm_plane_state *sstate = to_shmob_plane_state(new_plane_state);
struct drm_crtc_state *crtc_state;
bool is_primary = plane->type == DRM_PLANE_TYPE_PRIMARY;
int ret;
if (!new_plane_state->crtc) {
/*
* The visible field is not reset by the DRM core but only
* updated by drm_atomic_helper_check_plane_state(), set it
* manually.
*/
new_plane_state->visible = false;
sstate->format = NULL;
return 0;
}
crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
DRM_PLANE_NO_SCALING,
DRM_PLANE_NO_SCALING,
!is_primary, true);
if (ret < 0)
return ret;
if (!new_plane_state->visible) {
sstate->format = NULL;
return 0;
}
sstate->format = shmob_drm_format_info(new_plane_state->fb->format->format);
if (!sstate->format) {
dev_dbg(plane->dev->dev,
"plane_atomic_check: unsupported format %p4cc\n",
&new_plane_state->fb->format->format);
return -EINVAL;
}
shmob_drm_plane_compute_base(sstate);
return 0;
}
static void shmob_drm_plane_atomic_update(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane);
struct shmob_drm_plane *splane = to_shmob_plane(plane);
if (!new_plane_state->visible)
return;
if (plane->type == DRM_PLANE_TYPE_PRIMARY)
shmob_drm_primary_plane_setup(splane, new_plane_state);
else
shmob_drm_overlay_plane_setup(splane, new_plane_state);
}
static void shmob_drm_plane_atomic_disable(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane);
struct shmob_drm_device *sdev = to_shmob_device(plane->dev);
struct shmob_drm_plane *splane = to_shmob_plane(plane);
if (!old_state->crtc)
return;
if (plane->type != DRM_PLANE_TYPE_OVERLAY)
return;
lcdc_write(sdev, LDBCR, LDBCR_UPC(splane->index));
lcdc_write(sdev, LDBnBSIFR(splane->index), 0);
lcdc_write(sdev, LDBCR,
LDBCR_UPF(splane->index) | LDBCR_UPD(splane->index));
}
static struct drm_plane_state *
shmob_drm_plane_atomic_duplicate_state(struct drm_plane *plane)
{
struct shmob_drm_plane_state *state;
struct shmob_drm_plane_state *copy;
if (WARN_ON(!plane->state))
return NULL;
state = to_shmob_plane_state(plane->state);
copy = kmemdup(state, sizeof(*state), GFP_KERNEL);
if (copy == NULL)
return NULL;
__drm_atomic_helper_plane_duplicate_state(plane, &copy->base);
return &copy->base;
}
static void shmob_drm_plane_atomic_destroy_state(struct drm_plane *plane,
struct drm_plane_state *state)
{
__drm_atomic_helper_plane_destroy_state(state);
kfree(to_shmob_plane_state(state));
}
static void shmob_drm_plane_reset(struct drm_plane *plane)
{
struct shmob_drm_plane_state *state;
if (plane->state) {
shmob_drm_plane_atomic_destroy_state(plane, plane->state);
plane->state = NULL;
}
state = kzalloc_obj(*state);
if (state == NULL)
return;
__drm_atomic_helper_plane_reset(plane, &state->base);
}
static const struct drm_plane_helper_funcs shmob_drm_plane_helper_funcs = {
.atomic_check = shmob_drm_plane_atomic_check,
.atomic_update = shmob_drm_plane_atomic_update,
.atomic_disable = shmob_drm_plane_atomic_disable,
};
static const struct drm_plane_helper_funcs shmob_drm_primary_plane_helper_funcs = {
.atomic_check = shmob_drm_plane_atomic_check,
.atomic_update = shmob_drm_plane_atomic_update,
.atomic_disable = shmob_drm_plane_atomic_disable,
.get_scanout_buffer = drm_fb_dma_get_scanout_buffer,
};
static const struct drm_plane_funcs shmob_drm_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.reset = shmob_drm_plane_reset,
.atomic_duplicate_state = shmob_drm_plane_atomic_duplicate_state,
.atomic_destroy_state = shmob_drm_plane_atomic_destroy_state,
};
static const uint32_t formats[] = {
DRM_FORMAT_RGB565,
DRM_FORMAT_RGB888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_NV12,
DRM_FORMAT_NV21,
DRM_FORMAT_NV16,
DRM_FORMAT_NV61,
DRM_FORMAT_NV24,
DRM_FORMAT_NV42,
};
struct drm_plane *shmob_drm_plane_create(struct shmob_drm_device *sdev,
enum drm_plane_type type,
unsigned int index)
{
struct shmob_drm_plane *splane;
splane = drmm_universal_plane_alloc(&sdev->ddev,
struct shmob_drm_plane, base, 1,
&shmob_drm_plane_funcs, formats,
ARRAY_SIZE(formats), NULL, type,
NULL);
if (IS_ERR(splane))
return ERR_CAST(splane);
splane->index = index;
if (type == DRM_PLANE_TYPE_PRIMARY)
drm_plane_helper_add(&splane->base,
&shmob_drm_primary_plane_helper_funcs);
else
drm_plane_helper_add(&splane->base,
&shmob_drm_plane_helper_funcs);
return &splane->base;
}
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* shmob_drm_plane.h -- SH Mobile DRM Planes
*
* Copyright (C) 2012 Renesas Electronics Corporation
*
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __SHMOB_DRM_PLANE_H__
#define __SHMOB_DRM_PLANE_H__
struct drm_plane;
struct shmob_drm_device;
struct drm_plane *shmob_drm_plane_create(struct shmob_drm_device *sdev,
enum drm_plane_type type,
unsigned int index);
#endif /* __SHMOB_DRM_PLANE_H__ */
@@ -0,0 +1,310 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* shmob_drm_regs.h -- SH Mobile DRM registers
*
* Copyright (C) 2012 Renesas Electronics Corporation
*
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __SHMOB_DRM_REGS_H__
#define __SHMOB_DRM_REGS_H__
#include <linux/io.h>
#include <linux/jiffies.h>
#include "shmob_drm_drv.h"
/* Register definitions */
#define LDDCKPAT1R 0x400
#define LDDCKPAT2R 0x404
#define LDDCKR 0x410
#define LDDCKR_ICKSEL_BUS (0 << 16)
#define LDDCKR_ICKSEL_MIPI (1 << 16)
#define LDDCKR_ICKSEL_HDMI (2 << 16)
#define LDDCKR_ICKSEL_EXT (3 << 16)
#define LDDCKR_ICKSEL_MASK (7 << 16)
#define LDDCKR_MOSEL (1 << 6)
#define LDDCKSTPR 0x414
#define LDDCKSTPR_DCKSTS (1 << 16)
#define LDDCKSTPR_DCKSTP (1 << 0)
#define LDMT1R 0x418
#define LDMT1R_VPOL (1 << 28)
#define LDMT1R_HPOL (1 << 27)
#define LDMT1R_DWPOL (1 << 26)
#define LDMT1R_DIPOL (1 << 25)
#define LDMT1R_DAPOL (1 << 24)
#define LDMT1R_HSCNT (1 << 17)
#define LDMT1R_DWCNT (1 << 16)
#define LDMT1R_IFM (1 << 12)
#define LDMT1R_MIFTYP_RGB8 (0x0 << 0)
#define LDMT1R_MIFTYP_RGB9 (0x4 << 0)
#define LDMT1R_MIFTYP_RGB12A (0x5 << 0)
#define LDMT1R_MIFTYP_RGB12B (0x6 << 0)
#define LDMT1R_MIFTYP_RGB16 (0x7 << 0)
#define LDMT1R_MIFTYP_RGB18 (0xa << 0)
#define LDMT1R_MIFTYP_RGB24 (0xb << 0)
#define LDMT1R_MIFTYP_YCBCR (0xf << 0)
#define LDMT1R_MIFTYP_SYS8A (0x0 << 0)
#define LDMT1R_MIFTYP_SYS8B (0x1 << 0)
#define LDMT1R_MIFTYP_SYS8C (0x2 << 0)
#define LDMT1R_MIFTYP_SYS8D (0x3 << 0)
#define LDMT1R_MIFTYP_SYS9 (0x4 << 0)
#define LDMT1R_MIFTYP_SYS12 (0x5 << 0)
#define LDMT1R_MIFTYP_SYS16A (0x7 << 0)
#define LDMT1R_MIFTYP_SYS16B (0x8 << 0)
#define LDMT1R_MIFTYP_SYS16C (0x9 << 0)
#define LDMT1R_MIFTYP_SYS18 (0xa << 0)
#define LDMT1R_MIFTYP_SYS24 (0xb << 0)
#define LDMT1R_MIFTYP_MASK (0xf << 0)
#define LDMT2R 0x41c
#define LDMT2R_CSUP_MASK (7 << 26)
#define LDMT2R_CSUP_SHIFT 26
#define LDMT2R_RSV (1 << 25)
#define LDMT2R_VSEL (1 << 24)
#define LDMT2R_WCSC_MASK (0xff << 16)
#define LDMT2R_WCSC_SHIFT 16
#define LDMT2R_WCEC_MASK (0xff << 8)
#define LDMT2R_WCEC_SHIFT 8
#define LDMT2R_WCLW_MASK (0xff << 0)
#define LDMT2R_WCLW_SHIFT 0
#define LDMT3R 0x420
#define LDMT3R_RDLC_MASK (0x3f << 24)
#define LDMT3R_RDLC_SHIFT 24
#define LDMT3R_RCSC_MASK (0xff << 16)
#define LDMT3R_RCSC_SHIFT 16
#define LDMT3R_RCEC_MASK (0xff << 8)
#define LDMT3R_RCEC_SHIFT 8
#define LDMT3R_RCLW_MASK (0xff << 0)
#define LDMT3R_RCLW_SHIFT 0
#define LDDFR 0x424
#define LDDFR_CF1 (1 << 18)
#define LDDFR_CF0 (1 << 17)
#define LDDFR_CC (1 << 16)
#define LDDFR_YF_420 (0 << 8)
#define LDDFR_YF_422 (1 << 8)
#define LDDFR_YF_444 (2 << 8)
#define LDDFR_YF_MASK (3 << 8)
#define LDDFR_PKF_ARGB32 (0x00 << 0)
#define LDDFR_PKF_RGB16 (0x03 << 0)
#define LDDFR_PKF_RGB24 (0x0b << 0)
#define LDDFR_PKF_MASK (0x1f << 0)
#define LDSM1R 0x428
#define LDSM1R_OS (1 << 0)
#define LDSM2R 0x42c
#define LDSM2R_OSTRG (1 << 0)
#define LDSA1R 0x430
#define LDSA2R 0x434
#define LDMLSR 0x438
#define LDWBFR 0x43c
#define LDWBCNTR 0x440
#define LDWBAR 0x444
#define LDHCNR 0x448
#define LDHSYNR 0x44c
#define LDVLNR 0x450
#define LDVSYNR 0x454
#define LDHPDR 0x458
#define LDVPDR 0x45c
#define LDPMR 0x460
#define LDPMR_LPS (3 << 0)
#define LDINTR 0x468
#define LDINTR_FE (1 << 10)
#define LDINTR_VSE (1 << 9)
#define LDINTR_VEE (1 << 8)
#define LDINTR_FS (1 << 2)
#define LDINTR_VSS (1 << 1)
#define LDINTR_VES (1 << 0)
#define LDINTR_STATUS_MASK (0xff << 0)
#define LDSR 0x46c
#define LDSR_MSS (1 << 10)
#define LDSR_MRS (1 << 8)
#define LDSR_AS (1 << 1)
#define LDCNT1R 0x470
#define LDCNT1R_DE (1 << 0)
#define LDCNT2R 0x474
#define LDCNT2R_BR (1 << 8)
#define LDCNT2R_MD (1 << 3)
#define LDCNT2R_SE (1 << 2)
#define LDCNT2R_ME (1 << 1)
#define LDCNT2R_DO (1 << 0)
#define LDRCNTR 0x478
#define LDRCNTR_SRS (1 << 17)
#define LDRCNTR_SRC (1 << 16)
#define LDRCNTR_MRS (1 << 1)
#define LDRCNTR_MRC (1 << 0)
#define LDDDSR 0x47c
#define LDDDSR_LS (1 << 2)
#define LDDDSR_WS (1 << 1)
#define LDDDSR_BS (1 << 0)
#define LDHAJR 0x4a0
#define LDDWD0R 0x800
#define LDDWDxR_WDACT (1 << 28)
#define LDDWDxR_RSW (1 << 24)
#define LDDRDR 0x840
#define LDDRDR_RSR (1 << 24)
#define LDDRDR_DRD_MASK (0x3ffff << 0)
#define LDDWAR 0x900
#define LDDWAR_WA (1 << 0)
#define LDDRAR 0x904
#define LDDRAR_RA (1 << 0)
#define LDBCR 0xb00
#define LDBCR_UPC(n) (1 << ((n) + 16))
#define LDBCR_UPF(n) (1 << ((n) + 8))
#define LDBCR_UPD(n) (1 << ((n) + 0))
#define LDBnBSIFR(n) (0xb20 + (n) * 0x20 + 0x00)
#define LDBBSIFR_EN (1 << 31)
#define LDBBSIFR_VS (1 << 29)
#define LDBBSIFR_BRSEL (1 << 28)
#define LDBBSIFR_MX (1 << 27)
#define LDBBSIFR_MY (1 << 26)
#define LDBBSIFR_CV3 (3 << 24)
#define LDBBSIFR_CV2 (2 << 24)
#define LDBBSIFR_CV1 (1 << 24)
#define LDBBSIFR_CV0 (0 << 24)
#define LDBBSIFR_CV_MASK (3 << 24)
#define LDBBSIFR_LAY_MASK (0xff << 16)
#define LDBBSIFR_LAY_SHIFT 16
#define LDBBSIFR_ROP3_MASK (0xff << 16)
#define LDBBSIFR_ROP3_SHIFT 16
#define LDBBSIFR_AL_PL8 (3 << 14)
#define LDBBSIFR_AL_PL1 (2 << 14)
#define LDBBSIFR_AL_PK (1 << 14)
#define LDBBSIFR_AL_1 (0 << 14)
#define LDBBSIFR_AL_MASK (3 << 14)
#define LDBBSIFR_SWPL (1 << 10)
#define LDBBSIFR_SWPW (1 << 9)
#define LDBBSIFR_SWPB (1 << 8)
#define LDBBSIFR_RY (1 << 7)
#define LDBBSIFR_CHRR_420 (2 << 0)
#define LDBBSIFR_CHRR_422 (1 << 0)
#define LDBBSIFR_CHRR_444 (0 << 0)
#define LDBBSIFR_RPKF_ARGB32 (0x00 << 0)
#define LDBBSIFR_RPKF_RGB16 (0x03 << 0)
#define LDBBSIFR_RPKF_RGB24 (0x0b << 0)
#define LDBBSIFR_RPKF_MASK (0x1f << 0)
#define LDBnBSSZR(n) (0xb20 + (n) * 0x20 + 0x04)
#define LDBBSSZR_BVSS_MASK (0xfff << 16)
#define LDBBSSZR_BVSS_SHIFT 16
#define LDBBSSZR_BHSS_MASK (0xfff << 0)
#define LDBBSSZR_BHSS_SHIFT 0
#define LDBnBLOCR(n) (0xb20 + (n) * 0x20 + 0x08)
#define LDBBLOCR_CVLC_MASK (0xfff << 16)
#define LDBBLOCR_CVLC_SHIFT 16
#define LDBBLOCR_CHLC_MASK (0xfff << 0)
#define LDBBLOCR_CHLC_SHIFT 0
#define LDBnBSMWR(n) (0xb20 + (n) * 0x20 + 0x0c)
#define LDBBSMWR_BSMWA_MASK (0xffff << 16)
#define LDBBSMWR_BSMWA_SHIFT 16
#define LDBBSMWR_BSMW_MASK (0xffff << 0)
#define LDBBSMWR_BSMW_SHIFT 0
#define LDBnBSAYR(n) (0xb20 + (n) * 0x20 + 0x10)
#define LDBBSAYR_FG1A_MASK (0xff << 24)
#define LDBBSAYR_FG1A_SHIFT 24
#define LDBBSAYR_FG1R_MASK (0xff << 16)
#define LDBBSAYR_FG1R_SHIFT 16
#define LDBBSAYR_FG1G_MASK (0xff << 8)
#define LDBBSAYR_FG1G_SHIFT 8
#define LDBBSAYR_FG1B_MASK (0xff << 0)
#define LDBBSAYR_FG1B_SHIFT 0
#define LDBnBSACR(n) (0xb20 + (n) * 0x20 + 0x14)
#define LDBBSACR_FG2A_MASK (0xff << 24)
#define LDBBSACR_FG2A_SHIFT 24
#define LDBBSACR_FG2R_MASK (0xff << 16)
#define LDBBSACR_FG2R_SHIFT 16
#define LDBBSACR_FG2G_MASK (0xff << 8)
#define LDBBSACR_FG2G_SHIFT 8
#define LDBBSACR_FG2B_MASK (0xff << 0)
#define LDBBSACR_FG2B_SHIFT 0
#define LDBnBSAAR(n) (0xb20 + (n) * 0x20 + 0x18)
#define LDBBSAAR_AP_MASK (0xff << 24)
#define LDBBSAAR_AP_SHIFT 24
#define LDBBSAAR_R_MASK (0xff << 16)
#define LDBBSAAR_R_SHIFT 16
#define LDBBSAAR_GY_MASK (0xff << 8)
#define LDBBSAAR_GY_SHIFT 8
#define LDBBSAAR_B_MASK (0xff << 0)
#define LDBBSAAR_B_SHIFT 0
#define LDBnBPPCR(n) (0xb20 + (n) * 0x20 + 0x1c)
#define LDBBPPCR_AP_MASK (0xff << 24)
#define LDBBPPCR_AP_SHIFT 24
#define LDBBPPCR_R_MASK (0xff << 16)
#define LDBBPPCR_R_SHIFT 16
#define LDBBPPCR_GY_MASK (0xff << 8)
#define LDBBPPCR_GY_SHIFT 8
#define LDBBPPCR_B_MASK (0xff << 0)
#define LDBBPPCR_B_SHIFT 0
#define LDBnBBGCL(n) (0xb10 + (n) * 0x04)
#define LDBBBGCL_BGA_MASK (0xff << 24)
#define LDBBBGCL_BGA_SHIFT 24
#define LDBBBGCL_BGR_MASK (0xff << 16)
#define LDBBBGCL_BGR_SHIFT 16
#define LDBBBGCL_BGG_MASK (0xff << 8)
#define LDBBBGCL_BGG_SHIFT 8
#define LDBBBGCL_BGB_MASK (0xff << 0)
#define LDBBBGCL_BGB_SHIFT 0
#define LCDC_SIDE_B_OFFSET 0x1000
#define LCDC_MIRROR_OFFSET 0x2000
static inline bool lcdc_is_banked(u32 reg)
{
switch (reg) {
case LDMT1R:
case LDMT2R:
case LDMT3R:
case LDDFR:
case LDSM1R:
case LDSA1R:
case LDSA2R:
case LDMLSR:
case LDWBFR:
case LDWBCNTR:
case LDWBAR:
case LDHCNR:
case LDHSYNR:
case LDVLNR:
case LDVSYNR:
case LDHPDR:
case LDVPDR:
case LDHAJR:
return true;
default:
return reg >= LDBnBBGCL(0) && reg <= LDBnBPPCR(3);
}
}
static inline void lcdc_write_mirror(struct shmob_drm_device *sdev, u32 reg,
u32 data)
{
iowrite32(data, sdev->mmio + reg + LCDC_MIRROR_OFFSET);
}
static inline void lcdc_write(struct shmob_drm_device *sdev, u32 reg, u32 data)
{
iowrite32(data, sdev->mmio + reg);
if (lcdc_is_banked(reg))
iowrite32(data, sdev->mmio + reg + LCDC_SIDE_B_OFFSET);
}
static inline u32 lcdc_read(struct shmob_drm_device *sdev, u32 reg)
{
return ioread32(sdev->mmio + reg);
}
static inline int lcdc_wait_bit(struct shmob_drm_device *sdev, u32 reg,
u32 mask, u32 until)
{
unsigned long timeout = jiffies + msecs_to_jiffies(5);
while ((lcdc_read(sdev, reg) & mask) != until) {
if (time_after(jiffies, timeout))
return -ETIMEDOUT;
cpu_relax();
}
return 0;
}
#endif /* __SHMOB_DRM_REGS_H__ */