drm: Add RP1 DSI driver

Add support for the RP1 DSI hardware.

Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>

drm/rp1: depends on, instead of select, MFD_RP1

According to kconfig-language.txt [1], select should be used only for
"non-visible symbols ... and for symbols with no dependencies". Since
MFD_RP1 both is visible and has a dependency, "select" should not be
used and "depends on" should be used instead.

In particular, this fixes the build of this kernel tree on NixOS, where
its kernel config system will try to answer 'M' to as many config as
possible.

[1] https://www.kernel.org/doc/html/latest/kbuild/kconfig-language.html

Signed-off-by: Ratchanan Srirattanamet <peathot@hotmail.com>
This commit is contained in:
Nick Hollinghurst
2023-02-14 14:58:33 +00:00
committed by Dom Cobley
parent a3ef46de46
commit dbd19aeb30
10 changed files with 2605 additions and 0 deletions

View File

@@ -392,6 +392,8 @@ source "drivers/gpu/drm/v3d/Kconfig"
source "drivers/gpu/drm/vc4/Kconfig"
source "drivers/gpu/drm/rp1/Kconfig"
source "drivers/gpu/drm/loongson/Kconfig"
source "drivers/gpu/drm/etnaviv/Kconfig"

View File

@@ -218,3 +218,4 @@ obj-y += solomon/
obj-$(CONFIG_DRM_SPRD) += sprd/
obj-$(CONFIG_DRM_LOONGSON) += loongson/
obj-$(CONFIG_DRM_POWERVR) += imagination/
obj-y += rp1/

View File

@@ -0,0 +1,5 @@
source "drivers/gpu/drm/rp1/rp1-dsi/Kconfig"
source "drivers/gpu/drm/rp1/rp1-dpi/Kconfig"
source "drivers/gpu/drm/rp1/rp1-vec/Kconfig"

View File

@@ -0,0 +1,4 @@
obj-$(CONFIG_DRM_RP1_DSI) += rp1-dsi/
obj-$(CONFIG_DRM_RP1_DPI) += rp1-dpi/
obj-$(CONFIG_DRM_RP1_VEC) += rp1-vec/

View File

@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_RP1_DSI
tristate "DRM Support for RP1 DSI"
depends on DRM && MFD_RP1
select DRM_GEM_DMA_HELPER
select DRM_KMS_HELPER
select DRM_MIPI_DSI
select DRM_VRAM_HELPER
select DRM_TTM
select DRM_TTM_HELPER
select GENERIC_PHY
select GENERIC_PHY_MIPI_DPHY
help
Choose this option to enable DSI display on RP1

View File

@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
drm-rp1-dsi-y := rp1_dsi.o rp1_dsi_dma.o rp1_dsi_dsi.o
obj-$(CONFIG_DRM_RP1_DSI) += drm-rp1-dsi.o

View File

@@ -0,0 +1,534 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DRM Driver for DSI output on Raspberry Pi RP1
*
* Copyright (c) 2023 Raspberry Pi Limited.
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/phy/phy-mipi-dphy.h>
#include <linux/string.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_encoder.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_fbdev_ttm.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_managed.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_vblank.h>
#include "rp1_dsi.h"
static inline struct rp1_dsi *
bridge_to_rp1_dsi(struct drm_bridge *bridge)
{
return container_of(bridge, struct rp1_dsi, bridge);
}
static void rp1_dsi_bridge_pre_enable(struct drm_bridge *bridge,
struct drm_bridge_state *old_state)
{
struct rp1_dsi *dsi = bridge_to_rp1_dsi(bridge);
rp1dsi_dsi_setup(dsi, &dsi->pipe.crtc.state->adjusted_mode);
}
static void rp1_dsi_bridge_enable(struct drm_bridge *bridge,
struct drm_bridge_state *old_state)
{
}
static void rp1_dsi_bridge_disable(struct drm_bridge *bridge,
struct drm_bridge_state *state)
{
}
static void rp1_dsi_bridge_post_disable(struct drm_bridge *bridge,
struct drm_bridge_state *state)
{
struct rp1_dsi *dsi = bridge_to_rp1_dsi(bridge);
if (dsi->dsi_running) {
rp1dsi_dsi_stop(dsi);
dsi->dsi_running = false;
}
}
static int rp1_dsi_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
struct rp1_dsi *dsi = bridge_to_rp1_dsi(bridge);
/* Attach the panel or bridge to the dsi bridge */
return drm_bridge_attach(bridge->encoder, dsi->out_bridge,
&dsi->bridge, flags);
return 0;
}
static const struct drm_bridge_funcs rp1_dsi_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
.atomic_pre_enable = rp1_dsi_bridge_pre_enable,
.atomic_enable = rp1_dsi_bridge_enable,
.atomic_disable = rp1_dsi_bridge_disable,
.atomic_post_disable = rp1_dsi_bridge_post_disable,
.attach = rp1_dsi_bridge_attach,
};
static void rp1dsi_pipe_update(struct drm_simple_display_pipe *pipe,
struct drm_plane_state *old_state)
{
struct drm_pending_vblank_event *event;
unsigned long flags;
struct drm_framebuffer *fb = pipe->plane.state->fb;
struct rp1_dsi *dsi = pipe->crtc.dev->dev_private;
struct drm_gem_object *gem = fb ? drm_gem_fb_get_obj(fb, 0) : NULL;
struct drm_gem_dma_object *dma_obj = gem ? to_drm_gem_dma_obj(gem) : NULL;
bool can_update = fb && dma_obj && dsi && dsi->pipe_enabled;
/* (Re-)start DSI,DMA where required; and update FB address */
if (can_update) {
if (!dsi->dma_running || fb->format->format != dsi->cur_fmt) {
if (dsi->dma_running && fb->format->format != dsi->cur_fmt) {
rp1dsi_dma_stop(dsi);
dsi->dma_running = false;
}
if (!dsi->dma_running) {
rp1dsi_dma_setup(dsi,
fb->format->format, dsi->display_format,
&pipe->crtc.state->adjusted_mode);
dsi->dma_running = true;
}
dsi->cur_fmt = fb->format->format;
drm_crtc_vblank_on(&pipe->crtc);
}
rp1dsi_dma_update(dsi, dma_obj->dma_addr, fb->offsets[0], fb->pitches[0]);
}
/* Arm VBLANK event (or call it immediately in some error cases) */
spin_lock_irqsave(&pipe->crtc.dev->event_lock, flags);
event = pipe->crtc.state->event;
if (event) {
pipe->crtc.state->event = NULL;
if (can_update && drm_crtc_vblank_get(&pipe->crtc) == 0)
drm_crtc_arm_vblank_event(&pipe->crtc, event);
else
drm_crtc_send_vblank_event(&pipe->crtc, event);
}
spin_unlock_irqrestore(&pipe->crtc.dev->event_lock, flags);
}
static inline struct rp1_dsi *
encoder_to_rp1_dsi(struct drm_encoder *encoder)
{
struct drm_simple_display_pipe *pipe =
container_of(encoder, struct drm_simple_display_pipe, encoder);
return container_of(pipe, struct rp1_dsi, pipe);
}
static void rp1dsi_encoder_enable(struct drm_encoder *encoder)
{
struct rp1_dsi *dsi = encoder_to_rp1_dsi(encoder);
/* Put DSI into video mode before starting video */
rp1dsi_dsi_set_cmdmode(dsi, 0);
/* Start DMA -> DPI */
dsi->pipe_enabled = true;
dsi->cur_fmt = 0xdeadbeef;
rp1dsi_pipe_update(&dsi->pipe, 0);
}
static void rp1dsi_encoder_disable(struct drm_encoder *encoder)
{
struct rp1_dsi *dsi = encoder_to_rp1_dsi(encoder);
drm_crtc_vblank_off(&dsi->pipe.crtc);
if (dsi->dma_running) {
rp1dsi_dma_stop(dsi);
dsi->dma_running = false;
}
dsi->pipe_enabled = false;
/* Return to command mode after stopping video */
rp1dsi_dsi_set_cmdmode(dsi, 1);
}
static const struct drm_encoder_helper_funcs rp1_dsi_encoder_funcs = {
.enable = rp1dsi_encoder_enable,
.disable = rp1dsi_encoder_disable,
};
static void rp1dsi_pipe_enable(struct drm_simple_display_pipe *pipe,
struct drm_crtc_state *crtc_state,
struct drm_plane_state *plane_state)
{
}
static void rp1dsi_pipe_disable(struct drm_simple_display_pipe *pipe)
{
}
static int rp1dsi_pipe_enable_vblank(struct drm_simple_display_pipe *pipe)
{
struct rp1_dsi *dsi = pipe->crtc.dev->dev_private;
if (dsi)
rp1dsi_dma_vblank_ctrl(dsi, 1);
return 0;
}
static void rp1dsi_pipe_disable_vblank(struct drm_simple_display_pipe *pipe)
{
struct rp1_dsi *dsi = pipe->crtc.dev->dev_private;
if (dsi)
rp1dsi_dma_vblank_ctrl(dsi, 0);
}
static const struct drm_simple_display_pipe_funcs rp1dsi_pipe_funcs = {
.enable = rp1dsi_pipe_enable,
.update = rp1dsi_pipe_update,
.disable = rp1dsi_pipe_disable,
.enable_vblank = rp1dsi_pipe_enable_vblank,
.disable_vblank = rp1dsi_pipe_disable_vblank,
};
static const struct drm_mode_config_funcs rp1dsi_mode_funcs = {
.fb_create = drm_gem_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
static const u32 rp1dsi_formats[] = {
DRM_FORMAT_XRGB8888,
DRM_FORMAT_XBGR8888,
DRM_FORMAT_RGB888,
DRM_FORMAT_BGR888,
DRM_FORMAT_RGB565
};
static void rp1dsi_stopall(struct drm_device *drm)
{
if (drm->dev_private) {
struct rp1_dsi *dsi = drm->dev_private;
if (dsi->dma_running || rp1dsi_dma_busy(dsi)) {
rp1dsi_dma_stop(dsi);
dsi->dma_running = false;
}
if (dsi->dsi_running) {
rp1dsi_dsi_stop(dsi);
dsi->dsi_running = false;
}
if (dsi->clocks[RP1DSI_CLOCK_CFG])
clk_disable_unprepare(dsi->clocks[RP1DSI_CLOCK_CFG]);
}
}
DEFINE_DRM_GEM_DMA_FOPS(rp1dsi_fops);
static struct drm_driver rp1dsi_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
.fops = &rp1dsi_fops,
.name = "drm-rp1-dsi",
.desc = "drm-rp1-dsi",
.date = "0",
.major = 1,
.minor = 0,
DRM_GEM_DMA_DRIVER_OPS,
.release = rp1dsi_stopall,
};
static int rp1dsi_bind(struct rp1_dsi *dsi)
{
struct platform_device *pdev = dsi->pdev;
struct drm_device *drm = dsi->drm;
int ret;
dsi->out_bridge = drmm_of_get_bridge(drm, pdev->dev.of_node, 0, 0);
if (IS_ERR(dsi->out_bridge))
return PTR_ERR(dsi->out_bridge);
ret = drmm_mode_config_init(drm);
if (ret)
goto rtn;
drm->mode_config.max_width = 4096;
drm->mode_config.max_height = 4096;
drm->mode_config.preferred_depth = 32;
drm->mode_config.prefer_shadow = 0;
drm->mode_config.quirk_addfb_prefer_host_byte_order = true;
drm->mode_config.funcs = &rp1dsi_mode_funcs;
drm_vblank_init(drm, 1);
ret = drm_simple_display_pipe_init(drm,
&dsi->pipe,
&rp1dsi_pipe_funcs,
rp1dsi_formats,
ARRAY_SIZE(rp1dsi_formats),
NULL, NULL);
if (ret)
goto rtn;
/* We need slightly more complex encoder handling (enabling/disabling
* video mode), so add encoder helper functions.
*/
drm_encoder_helper_add(&dsi->pipe.encoder, &rp1_dsi_encoder_funcs);
ret = drm_simple_display_pipe_attach_bridge(&dsi->pipe, &dsi->bridge);
if (ret)
goto rtn;
drm_bridge_add(&dsi->bridge);
drm_mode_config_reset(drm);
if (dsi->clocks[RP1DSI_CLOCK_CFG])
clk_prepare_enable(dsi->clocks[RP1DSI_CLOCK_CFG]);
ret = drm_dev_register(drm, 0);
if (ret == 0)
drm_fbdev_ttm_setup(drm, 32);
rtn:
if (ret)
dev_err(&pdev->dev, "%s returned %d\n", __func__, ret);
else
dev_info(&pdev->dev, "%s succeeded", __func__);
return ret;
}
static void rp1dsi_unbind(struct rp1_dsi *dsi)
{
struct drm_device *drm = dsi->drm;
rp1dsi_stopall(drm);
drm_dev_unregister(drm);
drm_atomic_helper_shutdown(drm);
}
static int rp1dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *dsi_dev)
{
struct rp1_dsi *dsi = container_of(host, struct rp1_dsi, dsi_host);
dev_info(&dsi->pdev->dev, "%s: Attach DSI device name=%s channel=%d lanes=%d format=%d flags=0x%lx hs_rate=%lu lp_rate=%lu",
__func__, dsi_dev->name, dsi_dev->channel, dsi_dev->lanes,
dsi_dev->format, dsi_dev->mode_flags, dsi_dev->hs_rate,
dsi_dev->lp_rate);
dsi->vc = dsi_dev->channel & 3;
dsi->lanes = dsi_dev->lanes;
switch (dsi_dev->format) {
case MIPI_DSI_FMT_RGB666:
case MIPI_DSI_FMT_RGB666_PACKED:
case MIPI_DSI_FMT_RGB565:
case MIPI_DSI_FMT_RGB888:
break;
default:
return -EINVAL;
}
dsi->display_format = dsi_dev->format;
dsi->display_flags = dsi_dev->mode_flags;
dsi->display_hs_rate = dsi_dev->hs_rate;
dsi->display_lp_rate = dsi_dev->lp_rate;
/*
* Previously, we added a separate component to handle panel/bridge
* discovery and DRM registration, but now it's just a function call.
* The downstream/attaching device should deal with -EPROBE_DEFER
*/
return rp1dsi_bind(dsi);
}
static int rp1dsi_host_detach(struct mipi_dsi_host *host, struct mipi_dsi_device *dsi_dev)
{
struct rp1_dsi *dsi = container_of(host, struct rp1_dsi, dsi_host);
/*
* Unregister the DRM driver.
* TODO: Check we are cleaning up correctly and not doing things multiple times!
*/
rp1dsi_unbind(dsi);
return 0;
}
static ssize_t rp1dsi_host_transfer(struct mipi_dsi_host *host, const struct mipi_dsi_msg *msg)
{
struct rp1_dsi *dsi = container_of(host, struct rp1_dsi, dsi_host);
struct mipi_dsi_packet packet;
int ret = 0;
/* Write */
ret = mipi_dsi_create_packet(&packet, msg);
if (ret) {
dev_err(dsi->drm->dev, "RP1DSI: failed to create packet: %d\n", ret);
return ret;
}
rp1dsi_dsi_send(dsi, *(u32 *)(&packet.header), packet.payload_length, packet.payload);
/* Optional read back */
if (msg->rx_len && msg->rx_buf)
ret = rp1dsi_dsi_recv(dsi, msg->rx_len, msg->rx_buf);
return (ssize_t)ret;
}
static const struct mipi_dsi_host_ops rp1dsi_mipi_dsi_host_ops = {
.attach = rp1dsi_host_attach,
.detach = rp1dsi_host_detach,
.transfer = rp1dsi_host_transfer
};
static int rp1dsi_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct drm_device *drm;
struct rp1_dsi *dsi;
int i, ret;
drm = drm_dev_alloc(&rp1dsi_driver, dev);
if (IS_ERR(drm)) {
ret = PTR_ERR(drm);
return ret;
}
dsi = drmm_kzalloc(drm, sizeof(*dsi), GFP_KERNEL);
if (!dsi) {
ret = -ENOMEM;
goto err_free_drm;
}
init_completion(&dsi->finished);
dsi->drm = drm;
dsi->pdev = pdev;
drm->dev_private = dsi;
platform_set_drvdata(pdev, drm);
dsi->bridge.funcs = &rp1_dsi_bridge_funcs;
dsi->bridge.of_node = dev->of_node;
dsi->bridge.type = DRM_MODE_CONNECTOR_DSI;
/* Safe default values for DSI mode */
dsi->lanes = 1;
dsi->display_format = MIPI_DSI_FMT_RGB888;
dsi->display_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM;
/* Hardware resources */
for (i = 0; i < RP1DSI_NUM_CLOCKS; i++) {
static const char * const myclocknames[RP1DSI_NUM_CLOCKS] = {
"cfgclk", "dpiclk", "byteclk", "refclk"
};
dsi->clocks[i] = devm_clk_get(dev, myclocknames[i]);
if (IS_ERR(dsi->clocks[i])) {
ret = PTR_ERR(dsi->clocks[i]);
dev_err(dev, "Error getting clocks[%d]\n", i);
goto err_free_drm;
}
}
for (i = 0; i < RP1DSI_NUM_HW_BLOCKS; i++) {
dsi->hw_base[i] =
devm_ioremap_resource(dev,
platform_get_resource(dsi->pdev,
IORESOURCE_MEM,
i));
if (IS_ERR(dsi->hw_base[i])) {
ret = PTR_ERR(dsi->hw_base[i]);
dev_err(dev, "Error memory mapping regs[%d]\n", i);
goto err_free_drm;
}
}
ret = platform_get_irq(dsi->pdev, 0);
if (ret > 0)
ret = devm_request_irq(dev, ret, rp1dsi_dma_isr,
IRQF_SHARED, "rp1-dsi", dsi);
if (ret) {
dev_err(dev, "Unable to request interrupt\n");
ret = -EINVAL;
goto err_free_drm;
}
rp1dsi_mipicfg_setup(dsi);
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
/* Create the MIPI DSI Host and wait for the panel/bridge to attach to it */
dsi->dsi_host.ops = &rp1dsi_mipi_dsi_host_ops;
dsi->dsi_host.dev = dev;
ret = mipi_dsi_host_register(&dsi->dsi_host);
if (ret)
goto err_free_drm;
return ret;
err_free_drm:
dev_err(dev, "%s fail %d\n", __func__, ret);
drm_dev_put(drm);
return ret;
}
static void rp1dsi_platform_remove(struct platform_device *pdev)
{
struct drm_device *drm = platform_get_drvdata(pdev);
struct rp1_dsi *dsi = drm->dev_private;
mipi_dsi_host_unregister(&dsi->dsi_host);
}
static void rp1dsi_platform_shutdown(struct platform_device *pdev)
{
struct drm_device *drm = platform_get_drvdata(pdev);
rp1dsi_stopall(drm);
}
static const struct of_device_id rp1dsi_of_match[] = {
{
.compatible = "raspberrypi,rp1dsi",
},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rp1dsi_of_match);
static struct platform_driver rp1dsi_platform_driver = {
.probe = rp1dsi_platform_probe,
.remove = rp1dsi_platform_remove,
.shutdown = rp1dsi_platform_shutdown,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = rp1dsi_of_match,
},
};
module_platform_driver(rp1dsi_platform_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MIPI DSI driver for Raspberry Pi RP1");
MODULE_AUTHOR("Nick Hollinghurst");

View File

@@ -0,0 +1,94 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* DRM Driver for DSI output on Raspberry Pi RP1
*
* Copyright (c) 2023 Raspberry Pi Limited.
*/
#ifndef _RP1_DSI_H_
#define _RP1_DSI_H_
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/types.h>
#include <drm/drm_bridge.h>
#include <drm/drm_device.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_simple_kms_helper.h>
#define MODULE_NAME "drm-rp1-dsi"
#define DRIVER_NAME "drm-rp1-dsi"
/* ---------------------------------------------------------------------- */
#define RP1DSI_HW_BLOCK_DMA 0
#define RP1DSI_HW_BLOCK_DSI 1
#define RP1DSI_HW_BLOCK_CFG 2
#define RP1DSI_NUM_HW_BLOCKS 3
#define RP1DSI_CLOCK_CFG 0
#define RP1DSI_CLOCK_DPI 1
#define RP1DSI_CLOCK_BYTE 2
#define RP1DSI_CLOCK_REF 3
#define RP1DSI_NUM_CLOCKS 4
/* ---------------------------------------------------------------------- */
struct rp1_dsi {
/* DRM and platform device pointers */
struct drm_device *drm;
struct platform_device *pdev;
/* Framework and helper objects */
struct drm_simple_display_pipe pipe;
struct drm_bridge bridge;
struct drm_bridge *out_bridge;
struct mipi_dsi_host dsi_host;
/* Clocks. We need DPI clock; the others are frequency references */
struct clk *clocks[RP1DSI_NUM_CLOCKS];
/* Block (DSI DMA, DSI Host) base addresses, and current state */
void __iomem *hw_base[RP1DSI_NUM_HW_BLOCKS];
u32 cur_fmt;
bool dsi_running, dma_running, pipe_enabled;
struct completion finished;
/* Attached display parameters (from mipi_dsi_device) */
unsigned long display_flags, display_hs_rate, display_lp_rate;
enum mipi_dsi_pixel_format display_format;
u8 vc;
u8 lanes;
/* DPHY */
u8 hsfreq_index;
};
/* ---------------------------------------------------------------------- */
/* Functions to control the DSI/DPI/DMA block */
void rp1dsi_dma_setup(struct rp1_dsi *dsi,
u32 in_format, enum mipi_dsi_pixel_format out_format,
struct drm_display_mode const *mode);
void rp1dsi_dma_update(struct rp1_dsi *dsi, dma_addr_t addr, u32 offset, u32 stride);
void rp1dsi_dma_stop(struct rp1_dsi *dsi);
int rp1dsi_dma_busy(struct rp1_dsi *dsi);
irqreturn_t rp1dsi_dma_isr(int irq, void *dev);
void rp1dsi_dma_vblank_ctrl(struct rp1_dsi *dsi, int enable);
/* ---------------------------------------------------------------------- */
/* Functions to control the MIPICFG block and check RP1 platform */
void rp1dsi_mipicfg_setup(struct rp1_dsi *dsi);
/* ---------------------------------------------------------------------- */
/* Functions to control the SNPS D-PHY and DSI block setup */
void rp1dsi_dsi_setup(struct rp1_dsi *dsi, struct drm_display_mode const *mode);
void rp1dsi_dsi_send(struct rp1_dsi *dsi, u32 header, int len, const u8 *buf);
int rp1dsi_dsi_recv(struct rp1_dsi *dsi, int len, u8 *buf);
void rp1dsi_dsi_set_cmdmode(struct rp1_dsi *dsi, int cmd_mode);
void rp1dsi_dsi_stop(struct rp1_dsi *dsi);
#endif

View File

@@ -0,0 +1,443 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DRM Driver for DSI output on Raspberry Pi RP1
*
* Copyright (c) 2023 Raspberry Pi Limited.
*/
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
#include "rp1_dsi.h"
// --- DPI DMA REGISTERS (derived from Argon firmware, via RP1 drivers/mipi, with corrections) ---
// Control
#define DPI_DMA_CONTROL 0x0
#define DPI_DMA_CONTROL_ARM_SHIFT 0
#define DPI_DMA_CONTROL_ARM_MASK BIT(DPI_DMA_CONTROL_ARM_SHIFT)
#define DPI_DMA_CONTROL_ALIGN16_SHIFT 2
#define DPI_DMA_CONTROL_ALIGN16_MASK BIT(DPI_DMA_CONTROL_ALIGN16_SHIFT)
#define DPI_DMA_CONTROL_AUTO_REPEAT_SHIFT 1
#define DPI_DMA_CONTROL_AUTO_REPEAT_MASK BIT(DPI_DMA_CONTROL_AUTO_REPEAT_SHIFT)
#define DPI_DMA_CONTROL_HIGH_WATER_SHIFT 3
#define DPI_DMA_CONTROL_HIGH_WATER_MASK (0x1FF << DPI_DMA_CONTROL_HIGH_WATER_SHIFT)
#define DPI_DMA_CONTROL_DEN_POL_SHIFT 12
#define DPI_DMA_CONTROL_DEN_POL_MASK BIT(DPI_DMA_CONTROL_DEN_POL_SHIFT)
#define DPI_DMA_CONTROL_HSYNC_POL_SHIFT 13
#define DPI_DMA_CONTROL_HSYNC_POL_MASK BIT(DPI_DMA_CONTROL_HSYNC_POL_SHIFT)
#define DPI_DMA_CONTROL_VSYNC_POL_SHIFT 14
#define DPI_DMA_CONTROL_VSYNC_POL_MASK BIT(DPI_DMA_CONTROL_VSYNC_POL_SHIFT)
#define DPI_DMA_CONTROL_COLORM_SHIFT 15
#define DPI_DMA_CONTROL_COLORM_MASK BIT(DPI_DMA_CONTROL_COLORM_SHIFT)
#define DPI_DMA_CONTROL_SHUTDN_SHIFT 16
#define DPI_DMA_CONTROL_SHUTDN_MASK BIT(DPI_DMA_CONTROL_SHUTDN_SHIFT)
#define DPI_DMA_CONTROL_HBP_EN_SHIFT 17
#define DPI_DMA_CONTROL_HBP_EN_MASK BIT(DPI_DMA_CONTROL_HBP_EN_SHIFT)
#define DPI_DMA_CONTROL_HFP_EN_SHIFT 18
#define DPI_DMA_CONTROL_HFP_EN_MASK BIT(DPI_DMA_CONTROL_HFP_EN_SHIFT)
#define DPI_DMA_CONTROL_VBP_EN_SHIFT 19
#define DPI_DMA_CONTROL_VBP_EN_MASK BIT(DPI_DMA_CONTROL_VBP_EN_SHIFT)
#define DPI_DMA_CONTROL_VFP_EN_SHIFT 20
#define DPI_DMA_CONTROL_VFP_EN_MASK BIT(DPI_DMA_CONTROL_VFP_EN_SHIFT)
#define DPI_DMA_CONTROL_HSYNC_EN_SHIFT 21
#define DPI_DMA_CONTROL_HSYNC_EN_MASK BIT(DPI_DMA_CONTROL_HSYNC_EN_SHIFT)
#define DPI_DMA_CONTROL_VSYNC_EN_SHIFT 22
#define DPI_DMA_CONTROL_VSYNC_EN_MASK BIT(DPI_DMA_CONTROL_VSYNC_EN_SHIFT)
#define DPI_DMA_CONTROL_FORCE_IMMED_SHIFT 23
#define DPI_DMA_CONTROL_FORCE_IMMED_MASK BIT(DPI_DMA_CONTROL_FORCE_IMMED_SHIFT)
#define DPI_DMA_CONTROL_FORCE_DRAIN_SHIFT 24
#define DPI_DMA_CONTROL_FORCE_DRAIN_MASK BIT(DPI_DMA_CONTROL_FORCE_DRAIN_SHIFT)
#define DPI_DMA_CONTROL_FORCE_EMPTY_SHIFT 25
#define DPI_DMA_CONTROL_FORCE_EMPTY_MASK BIT(DPI_DMA_CONTROL_FORCE_EMPTY_SHIFT)
// IRQ_ENABLES
#define DPI_DMA_IRQ_EN 0x04
#define DPI_DMA_IRQ_EN_DMA_READY_SHIFT 0
#define DPI_DMA_IRQ_EN_DMA_READY_MASK BIT(DPI_DMA_IRQ_EN_DMA_READY_SHIFT)
#define DPI_DMA_IRQ_EN_UNDERFLOW_SHIFT 1
#define DPI_DMA_IRQ_EN_UNDERFLOW_MASK BIT(DPI_DMA_IRQ_EN_UNDERFLOW_SHIFT)
#define DPI_DMA_IRQ_EN_FRAME_START_SHIFT 2
#define DPI_DMA_IRQ_EN_FRAME_START_MASK BIT(DPI_DMA_IRQ_EN_FRAME_START_SHIFT)
#define DPI_DMA_IRQ_EN_AFIFO_EMPTY_SHIFT 3
#define DPI_DMA_IRQ_EN_AFIFO_EMPTY_MASK BIT(DPI_DMA_IRQ_EN_AFIFO_EMPTY_SHIFT)
#define DPI_DMA_IRQ_EN_TE_SHIFT 4
#define DPI_DMA_IRQ_EN_TE_MASK BIT(DPI_DMA_IRQ_EN_TE_SHIFT)
#define DPI_DMA_IRQ_EN_ERROR_SHIFT 5
#define DPI_DMA_IRQ_EN_ERROR_MASK BIT(DPI_DMA_IRQ_EN_ERROR_SHIFT)
#define DPI_DMA_IRQ_EN_MATCH_SHIFT 6
#define DPI_DMA_IRQ_EN_MATCH_MASK BIT(DPI_DMA_IRQ_EN_MATCH_SHIFT)
#define DPI_DMA_IRQ_EN_MATCH_LINE_SHIFT 16
#define DPI_DMA_IRQ_EN_MATCH_LINE_MASK (0xFFF << DPI_DMA_IRQ_EN_MATCH_LINE_SHIFT)
// IRQ_FLAGS
#define DPI_DMA_IRQ_FLAGS 0x08
#define DPI_DMA_IRQ_FLAGS_DMA_READY_SHIFT 0
#define DPI_DMA_IRQ_FLAGS_DMA_READY_MASK BIT(DPI_DMA_IRQ_FLAGS_DMA_READY_SHIFT)
#define DPI_DMA_IRQ_FLAGS_UNDERFLOW_SHIFT 1
#define DPI_DMA_IRQ_FLAGS_UNDERFLOW_MASK BIT(DPI_DMA_IRQ_FLAGS_UNDERFLOW_SHIFT)
#define DPI_DMA_IRQ_FLAGS_FRAME_START_SHIFT 2
#define DPI_DMA_IRQ_FLAGS_FRAME_START_MASK BIT(DPI_DMA_IRQ_FLAGS_FRAME_START_SHIFT)
#define DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_SHIFT 3
#define DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_MASK BIT(DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_SHIFT)
#define DPI_DMA_IRQ_FLAGS_TE_SHIFT 4
#define DPI_DMA_IRQ_FLAGS_TE_MASK BIT(DPI_DMA_IRQ_FLAGS_TE_SHIFT)
#define DPI_DMA_IRQ_FLAGS_ERROR_SHIFT 5
#define DPI_DMA_IRQ_FLAGS_ERROR_MASK BIT(DPI_DMA_IRQ_FLAGS_ERROR_SHIFT)
#define DPI_DMA_IRQ_FLAGS_MATCH_SHIFT 6
#define DPI_DMA_IRQ_FLAGS_MATCH_MASK BIT(DPI_DMA_IRQ_FLAGS_MATCH_SHIFT)
// QOS
#define DPI_DMA_QOS 0xC
#define DPI_DMA_QOS_DQOS_SHIFT 0
#define DPI_DMA_QOS_DQOS_MASK (0xF << DPI_DMA_QOS_DQOS_SHIFT)
#define DPI_DMA_QOS_ULEV_SHIFT 4
#define DPI_DMA_QOS_ULEV_MASK (0xF << DPI_DMA_QOS_ULEV_SHIFT)
#define DPI_DMA_QOS_UQOS_SHIFT 8
#define DPI_DMA_QOS_UQOS_MASK (0xF << DPI_DMA_QOS_UQOS_SHIFT)
#define DPI_DMA_QOS_LLEV_SHIFT 12
#define DPI_DMA_QOS_LLEV_MASK (0xF << DPI_DMA_QOS_LLEV_SHIFT)
#define DPI_DMA_QOS_LQOS_SHIFT 16
#define DPI_DMA_QOS_LQOS_MASK (0xF << DPI_DMA_QOS_LQOS_SHIFT)
// Panics
#define DPI_DMA_PANICS 0x38
#define DPI_DMA_PANICS_UPPER_COUNT_SHIFT 0
#define DPI_DMA_PANICS_UPPER_COUNT_MASK \
(0x0000FFFF << DPI_DMA_PANICS_UPPER_COUNT_SHIFT)
#define DPI_DMA_PANICS_LOWER_COUNT_SHIFT 16
#define DPI_DMA_PANICS_LOWER_COUNT_MASK \
(0x0000FFFF << DPI_DMA_PANICS_LOWER_COUNT_SHIFT)
// DMA Address Lower:
#define DPI_DMA_DMA_ADDR_L 0x10
// DMA Address Upper:
#define DPI_DMA_DMA_ADDR_H 0x40
// DMA stride
#define DPI_DMA_DMA_STRIDE 0x14
// Visible Area
#define DPI_DMA_VISIBLE_AREA 0x18
#define DPI_DMA_VISIBLE_AREA_ROWSM1_SHIFT 0
#define DPI_DMA_VISIBLE_AREA_ROWSM1_MASK (0x0FFF << DPI_DMA_VISIBLE_AREA_ROWSM1_SHIFT)
#define DPI_DMA_VISIBLE_AREA_COLSM1_SHIFT 16
#define DPI_DMA_VISIBLE_AREA_COLSM1_MASK (0x0FFF << DPI_DMA_VISIBLE_AREA_COLSM1_SHIFT)
// Sync width
#define DPI_DMA_SYNC_WIDTH 0x1C
#define DPI_DMA_SYNC_WIDTH_ROWSM1_SHIFT 0
#define DPI_DMA_SYNC_WIDTH_ROWSM1_MASK (0x0FFF << DPI_DMA_SYNC_WIDTH_ROWSM1_SHIFT)
#define DPI_DMA_SYNC_WIDTH_COLSM1_SHIFT 16
#define DPI_DMA_SYNC_WIDTH_COLSM1_MASK (0x0FFF << DPI_DMA_SYNC_WIDTH_COLSM1_SHIFT)
// Back porch
#define DPI_DMA_BACK_PORCH 0x20
#define DPI_DMA_BACK_PORCH_ROWSM1_SHIFT 0
#define DPI_DMA_BACK_PORCH_ROWSM1_MASK (0x0FFF << DPI_DMA_BACK_PORCH_ROWSM1_SHIFT)
#define DPI_DMA_BACK_PORCH_COLSM1_SHIFT 16
#define DPI_DMA_BACK_PORCH_COLSM1_MASK (0x0FFF << DPI_DMA_BACK_PORCH_COLSM1_SHIFT)
// Front porch
#define DPI_DMA_FRONT_PORCH 0x24
#define DPI_DMA_FRONT_PORCH_ROWSM1_SHIFT 0
#define DPI_DMA_FRONT_PORCH_ROWSM1_MASK (0x0FFF << DPI_DMA_FRONT_PORCH_ROWSM1_SHIFT)
#define DPI_DMA_FRONT_PORCH_COLSM1_SHIFT 16
#define DPI_DMA_FRONT_PORCH_COLSM1_MASK (0x0FFF << DPI_DMA_FRONT_PORCH_COLSM1_SHIFT)
// Input masks
#define DPI_DMA_IMASK 0x2C
#define DPI_DMA_IMASK_R_SHIFT 0
#define DPI_DMA_IMASK_R_MASK (0x3FF << DPI_DMA_IMASK_R_SHIFT)
#define DPI_DMA_IMASK_G_SHIFT 10
#define DPI_DMA_IMASK_G_MASK (0x3FF << DPI_DMA_IMASK_G_SHIFT)
#define DPI_DMA_IMASK_B_SHIFT 20
#define DPI_DMA_IMASK_B_MASK (0x3FF << DPI_DMA_IMASK_B_SHIFT)
// Output Masks
#define DPI_DMA_OMASK 0x30
#define DPI_DMA_OMASK_R_SHIFT 0
#define DPI_DMA_OMASK_R_MASK (0x3FF << DPI_DMA_OMASK_R_SHIFT)
#define DPI_DMA_OMASK_G_SHIFT 10
#define DPI_DMA_OMASK_G_MASK (0x3FF << DPI_DMA_OMASK_G_SHIFT)
#define DPI_DMA_OMASK_B_SHIFT 20
#define DPI_DMA_OMASK_B_MASK (0x3FF << DPI_DMA_OMASK_B_SHIFT)
// Shifts
#define DPI_DMA_SHIFT 0x28
#define DPI_DMA_SHIFT_IR_SHIFT 0
#define DPI_DMA_SHIFT_IR_MASK (0x1F << DPI_DMA_SHIFT_IR_SHIFT)
#define DPI_DMA_SHIFT_IG_SHIFT 5
#define DPI_DMA_SHIFT_IG_MASK (0x1F << DPI_DMA_SHIFT_IG_SHIFT)
#define DPI_DMA_SHIFT_IB_SHIFT 10
#define DPI_DMA_SHIFT_IB_MASK (0x1F << DPI_DMA_SHIFT_IB_SHIFT)
#define DPI_DMA_SHIFT_OR_SHIFT 15
#define DPI_DMA_SHIFT_OR_MASK (0x1F << DPI_DMA_SHIFT_OR_SHIFT)
#define DPI_DMA_SHIFT_OG_SHIFT 20
#define DPI_DMA_SHIFT_OG_MASK (0x1F << DPI_DMA_SHIFT_OG_SHIFT)
#define DPI_DMA_SHIFT_OB_SHIFT 25
#define DPI_DMA_SHIFT_OB_MASK (0x1F << DPI_DMA_SHIFT_OB_SHIFT)
// Scaling
#define DPI_DMA_RGBSZ 0x34
#define DPI_DMA_RGBSZ_BPP_SHIFT 16
#define DPI_DMA_RGBSZ_BPP_MASK (0x3 << DPI_DMA_RGBSZ_BPP_SHIFT)
#define DPI_DMA_RGBSZ_R_SHIFT 0
#define DPI_DMA_RGBSZ_R_MASK (0xF << DPI_DMA_RGBSZ_R_SHIFT)
#define DPI_DMA_RGBSZ_G_SHIFT 4
#define DPI_DMA_RGBSZ_G_MASK (0xF << DPI_DMA_RGBSZ_G_SHIFT)
#define DPI_DMA_RGBSZ_B_SHIFT 8
#define DPI_DMA_RGBSZ_B_MASK (0xF << DPI_DMA_RGBSZ_B_SHIFT)
// Status
#define DPI_DMA_STATUS 0x3c
#define BITS(field, val) (((val) << (field ## _SHIFT)) & (field ## _MASK))
static unsigned int rp1dsi_dma_read(struct rp1_dsi *dsi, unsigned int reg)
{
void __iomem *addr = dsi->hw_base[RP1DSI_HW_BLOCK_DMA] + reg;
return readl(addr);
}
static void rp1dsi_dma_write(struct rp1_dsi *dsi, unsigned int reg, unsigned int val)
{
void __iomem *addr = dsi->hw_base[RP1DSI_HW_BLOCK_DMA] + reg;
writel(val, addr);
}
int rp1dsi_dma_busy(struct rp1_dsi *dsi)
{
return (rp1dsi_dma_read(dsi, DPI_DMA_STATUS) & 0xF8F) ? 1 : 0;
}
/* Table of supported input (in-memory/DMA) pixel formats. */
struct rp1dsi_ipixfmt {
u32 format; /* DRM format code */
u32 mask; /* RGB masks (10 bits each, left justified) */
u32 shift; /* RGB MSB positions in the memory word */
u32 rgbsz; /* Shifts used for scaling; also (BPP/8-1) */
};
#define IMASK_RGB(r, g, b) (BITS(DPI_DMA_IMASK_R, r) | \
BITS(DPI_DMA_IMASK_G, g) | \
BITS(DPI_DMA_IMASK_B, b))
#define ISHIFT_RGB(r, g, b) (BITS(DPI_DMA_SHIFT_IR, r) | \
BITS(DPI_DMA_SHIFT_IG, g) | \
BITS(DPI_DMA_SHIFT_IB, b))
static const struct rp1dsi_ipixfmt my_formats[] = {
{
.format = DRM_FORMAT_XRGB8888,
.mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
.shift = ISHIFT_RGB(23, 15, 7),
.rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3),
},
{
.format = DRM_FORMAT_XBGR8888,
.mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
.shift = ISHIFT_RGB(7, 15, 23),
.rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3),
},
{
.format = DRM_FORMAT_RGB888,
.mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
.shift = ISHIFT_RGB(23, 15, 7),
.rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 2),
},
{
.format = DRM_FORMAT_BGR888,
.mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
.shift = ISHIFT_RGB(7, 15, 23),
.rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 2),
},
{
.format = DRM_FORMAT_RGB565,
.mask = IMASK_RGB(0x3e0, 0x3f0, 0x3e0),
.shift = ISHIFT_RGB(15, 10, 4),
.rgbsz = BITS(DPI_DMA_RGBSZ_R, 5) | BITS(DPI_DMA_RGBSZ_G, 6) |
BITS(DPI_DMA_RGBSZ_B, 5) | BITS(DPI_DMA_RGBSZ_BPP, 1),
}
};
/* Choose the internal on-the-bus DPI format as expected by DSI Host. */
static u32 get_omask_oshift(enum mipi_dsi_pixel_format fmt, u32 *oshift)
{
switch (fmt) {
case MIPI_DSI_FMT_RGB565:
*oshift = BITS(DPI_DMA_SHIFT_OR, 15) |
BITS(DPI_DMA_SHIFT_OG, 10) |
BITS(DPI_DMA_SHIFT_OB, 4);
return BITS(DPI_DMA_OMASK_R, 0x3e0) |
BITS(DPI_DMA_OMASK_G, 0x3f0) |
BITS(DPI_DMA_OMASK_B, 0x3e0);
case MIPI_DSI_FMT_RGB666_PACKED:
*oshift = BITS(DPI_DMA_SHIFT_OR, 17) |
BITS(DPI_DMA_SHIFT_OG, 11) |
BITS(DPI_DMA_SHIFT_OB, 5);
return BITS(DPI_DMA_OMASK_R, 0x3f0) |
BITS(DPI_DMA_OMASK_G, 0x3f0) |
BITS(DPI_DMA_OMASK_B, 0x3f0);
case MIPI_DSI_FMT_RGB666:
*oshift = BITS(DPI_DMA_SHIFT_OR, 21) |
BITS(DPI_DMA_SHIFT_OG, 13) |
BITS(DPI_DMA_SHIFT_OB, 5);
return BITS(DPI_DMA_OMASK_R, 0x3f0) |
BITS(DPI_DMA_OMASK_G, 0x3f0) |
BITS(DPI_DMA_OMASK_B, 0x3f0);
default:
*oshift = BITS(DPI_DMA_SHIFT_OR, 23) |
BITS(DPI_DMA_SHIFT_OG, 15) |
BITS(DPI_DMA_SHIFT_OB, 7);
return BITS(DPI_DMA_OMASK_R, 0x3fc) |
BITS(DPI_DMA_OMASK_G, 0x3fc) |
BITS(DPI_DMA_OMASK_B, 0x3fc);
}
}
void rp1dsi_dma_setup(struct rp1_dsi *dsi,
u32 in_format, enum mipi_dsi_pixel_format out_format,
struct drm_display_mode const *mode)
{
u32 oshift;
int i;
/*
* Configure all DSI/DPI/DMA block registers, except base address.
* DMA will not actually start until a FB base address is specified
* using rp1dsi_dma_update().
*/
rp1dsi_dma_write(dsi, DPI_DMA_VISIBLE_AREA,
BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, mode->vdisplay - 1) |
BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1));
rp1dsi_dma_write(dsi, DPI_DMA_SYNC_WIDTH,
BITS(DPI_DMA_SYNC_WIDTH_ROWSM1, mode->vsync_end - mode->vsync_start - 1) |
BITS(DPI_DMA_SYNC_WIDTH_COLSM1, mode->hsync_end - mode->hsync_start - 1));
/* In the DPIDMA registers, "back porch" time includes sync width */
rp1dsi_dma_write(dsi, DPI_DMA_BACK_PORCH,
BITS(DPI_DMA_BACK_PORCH_ROWSM1, mode->vtotal - mode->vsync_start - 1) |
BITS(DPI_DMA_BACK_PORCH_COLSM1, mode->htotal - mode->hsync_start - 1));
rp1dsi_dma_write(dsi, DPI_DMA_FRONT_PORCH,
BITS(DPI_DMA_FRONT_PORCH_ROWSM1, mode->vsync_start - mode->vdisplay - 1) |
BITS(DPI_DMA_FRONT_PORCH_COLSM1, mode->hsync_start - mode->hdisplay - 1));
/* Input to output pixel format conversion */
for (i = 0; i < ARRAY_SIZE(my_formats); ++i) {
if (my_formats[i].format == in_format)
break;
}
if (i >= ARRAY_SIZE(my_formats)) {
drm_err(dsi->drm, "%s: bad input format\n", __func__);
i = 0;
}
rp1dsi_dma_write(dsi, DPI_DMA_IMASK, my_formats[i].mask);
rp1dsi_dma_write(dsi, DPI_DMA_OMASK, get_omask_oshift(out_format, &oshift));
rp1dsi_dma_write(dsi, DPI_DMA_SHIFT, my_formats[i].shift | oshift);
if (out_format == MIPI_DSI_FMT_RGB888)
rp1dsi_dma_write(dsi, DPI_DMA_RGBSZ, my_formats[i].rgbsz);
else
rp1dsi_dma_write(dsi, DPI_DMA_RGBSZ, my_formats[i].rgbsz & DPI_DMA_RGBSZ_BPP_MASK);
rp1dsi_dma_write(dsi, DPI_DMA_QOS,
BITS(DPI_DMA_QOS_DQOS, 0x0) |
BITS(DPI_DMA_QOS_ULEV, 0xb) |
BITS(DPI_DMA_QOS_UQOS, 0x2) |
BITS(DPI_DMA_QOS_LLEV, 0x8) |
BITS(DPI_DMA_QOS_LQOS, 0x7));
rp1dsi_dma_write(dsi, DPI_DMA_IRQ_FLAGS, -1);
rp1dsi_dma_vblank_ctrl(dsi, 1);
i = rp1dsi_dma_busy(dsi);
if (i)
drm_err(dsi->drm, "RP1DSI: Unexpectedly busy at start!");
rp1dsi_dma_write(dsi, DPI_DMA_CONTROL,
BITS(DPI_DMA_CONTROL_ARM, (i == 0)) |
BITS(DPI_DMA_CONTROL_AUTO_REPEAT, 1) |
BITS(DPI_DMA_CONTROL_HIGH_WATER, 448) |
BITS(DPI_DMA_CONTROL_DEN_POL, 0) |
BITS(DPI_DMA_CONTROL_HSYNC_POL, 0) |
BITS(DPI_DMA_CONTROL_VSYNC_POL, 0) |
BITS(DPI_DMA_CONTROL_COLORM, 0) |
BITS(DPI_DMA_CONTROL_SHUTDN, 0) |
BITS(DPI_DMA_CONTROL_HBP_EN, 1) |
BITS(DPI_DMA_CONTROL_HFP_EN, 1) |
BITS(DPI_DMA_CONTROL_VBP_EN, 1) |
BITS(DPI_DMA_CONTROL_VFP_EN, 1) |
BITS(DPI_DMA_CONTROL_HSYNC_EN, 1) |
BITS(DPI_DMA_CONTROL_VSYNC_EN, 1));
}
void rp1dsi_dma_update(struct rp1_dsi *dsi, dma_addr_t addr, u32 offset, u32 stride)
{
/*
* Update STRIDE, DMAH and DMAL only. When called after rp1dsi_dma_setup(),
* DMA starts immediately; if already running, the buffer will flip at
* the next vertical sync event.
*/
u64 a = addr + offset;
rp1dsi_dma_write(dsi, DPI_DMA_DMA_STRIDE, stride);
rp1dsi_dma_write(dsi, DPI_DMA_DMA_ADDR_H, a >> 32);
rp1dsi_dma_write(dsi, DPI_DMA_DMA_ADDR_L, a & 0xFFFFFFFFu);
}
void rp1dsi_dma_stop(struct rp1_dsi *dsi)
{
/*
* Stop DMA by turning off the Auto-Repeat flag, and wait up to 100ms for
* the current and any queued frame to end. "Force drain" flags are not used,
* as they seem to prevent DMA from re-starting properly; it's safer to wait.
*/
u32 ctrl;
reinit_completion(&dsi->finished);
ctrl = rp1dsi_dma_read(dsi, DPI_DMA_CONTROL);
ctrl &= ~(DPI_DMA_CONTROL_ARM_MASK | DPI_DMA_CONTROL_AUTO_REPEAT_MASK);
rp1dsi_dma_write(dsi, DPI_DMA_CONTROL, ctrl);
if (!wait_for_completion_timeout(&dsi->finished, HZ / 10))
drm_err(dsi->drm, "%s: timed out waiting for idle\n", __func__);
rp1dsi_dma_write(dsi, DPI_DMA_IRQ_EN, 0);
}
void rp1dsi_dma_vblank_ctrl(struct rp1_dsi *dsi, int enable)
{
rp1dsi_dma_write(dsi, DPI_DMA_IRQ_EN,
BITS(DPI_DMA_IRQ_EN_AFIFO_EMPTY, 1) |
BITS(DPI_DMA_IRQ_EN_UNDERFLOW, 1) |
BITS(DPI_DMA_IRQ_EN_DMA_READY, !!enable) |
BITS(DPI_DMA_IRQ_EN_MATCH_LINE, 4095));
}
irqreturn_t rp1dsi_dma_isr(int irq, void *dev)
{
struct rp1_dsi *dsi = dev;
u32 u = rp1dsi_dma_read(dsi, DPI_DMA_IRQ_FLAGS);
if (u) {
rp1dsi_dma_write(dsi, DPI_DMA_IRQ_FLAGS, u);
if (dsi) {
if (u & DPI_DMA_IRQ_FLAGS_UNDERFLOW_MASK)
drm_err_ratelimited(dsi->drm,
"Underflow! (panics=0x%08x)\n",
rp1dsi_dma_read(dsi, DPI_DMA_PANICS));
if (u & DPI_DMA_IRQ_FLAGS_DMA_READY_MASK)
drm_crtc_handle_vblank(&dsi->pipe.crtc);
if (u & DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_MASK)
complete(&dsi->finished);
}
}
return u ? IRQ_HANDLED : IRQ_NONE;
}

File diff suppressed because it is too large Load Diff