The Xilinx MIPI DSI Tx Subsystem soft IP is used to display video data from AXI-4 stream interface. It supports upto 4 lanes, optional register interface for the DPHY and multiple RGB color formats. This is a MIPI-DSI host driver and provides DSI bus for panels. This driver also helps to communicate with its panel using panel framework. Signed-off-by: Venkateshwar Rao Gannavarapu <venkateshwar.rao.gannavarapu@xxxxxxxxxx> --- drivers/gpu/drm/xlnx/Kconfig | 14 ++ drivers/gpu/drm/xlnx/Makefile | 1 + drivers/gpu/drm/xlnx/xlnx_dsi.c | 456 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 471 insertions(+) create mode 100644 drivers/gpu/drm/xlnx/xlnx_dsi.c diff --git a/drivers/gpu/drm/xlnx/Kconfig b/drivers/gpu/drm/xlnx/Kconfig index c3d0826..caa632b 100644 --- a/drivers/gpu/drm/xlnx/Kconfig +++ b/drivers/gpu/drm/xlnx/Kconfig @@ -14,3 +14,17 @@ config DRM_ZYNQMP_DPSUB This is a DRM/KMS driver for ZynqMP DisplayPort controller. Choose this option if you have a Xilinx ZynqMP SoC with DisplayPort subsystem. + +config DRM_XLNX_DSI + tristate "Xilinx DRM DSI Subsystem Driver" + depends on DRM && OF + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL + select BACKLIGHT_LCD_SUPPORT + select BACKLIGHT_CLASS_DEVICE + select DRM_PANEL_SIMPLE + help + DRM bridge driver for Xilinx programmable DSI subsystem controller. + choose this option if you hava a Xilinx MIPI-DSI Tx subsytem in + video pipeline. diff --git a/drivers/gpu/drm/xlnx/Makefile b/drivers/gpu/drm/xlnx/Makefile index 51c24b7..1e97fbe 100644 --- a/drivers/gpu/drm/xlnx/Makefile +++ b/drivers/gpu/drm/xlnx/Makefile @@ -1,2 +1,3 @@ zynqmp-dpsub-y := zynqmp_disp.o zynqmp_dpsub.o zynqmp_dp.o obj-$(CONFIG_DRM_ZYNQMP_DPSUB) += zynqmp-dpsub.o +obj-$(CONFIG_DRM_XLNX_DSI) += xlnx_dsi.o diff --git a/drivers/gpu/drm/xlnx/xlnx_dsi.c b/drivers/gpu/drm/xlnx/xlnx_dsi.c new file mode 100644 index 0000000..a5291f3 --- /dev/null +++ b/drivers/gpu/drm/xlnx/xlnx_dsi.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * + * Xilinx FPGA MIPI DSI Tx Controller driver. + * + * Copyright (C) 2022 Xilinx, Inc. + * + * Author: Venkateshwar Rao G <vgannava@xxxxxxxxxx> + * + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of_device.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> + +/* DSI Tx IP registers */ +#define XDSI_CCR 0x00 +#define XDSI_CCR_COREENB BIT(0) +#define XDSI_CCR_SOFTRST BIT(1) +#define XDSI_CCR_CRREADY BIT(2) +#define XDSI_CCR_CMDMODE BIT(3) +#define XDSI_CCR_DFIFORST BIT(4) +#define XDSI_CCR_CMDFIFORST BIT(5) +#define XDSI_PCR 0x04 +#define XDSI_PCR_LANES_MASK 3 +#define XDSI_PCR_VIDEOMODE(x) (((x) & 0x3) << 3) +#define XDSI_PCR_VIDEOMODE_MASK (0x3 << 3) +#define XDSI_PCR_VIDEOMODE_SHIFT 3 +#define XDSI_PCR_BLLPTYPE(x) ((x) << 5) +#define XDSI_PCR_BLLPMODE(x) ((x) << 6) +#define XDSI_PCR_PIXELFORMAT_MASK (0x3 << 11) +#define XDSI_PCR_PIXELFORMAT_SHIFT 11 +#define XDSI_PCR_EOTPENABLE(x) ((x) << 13) +#define XDSI_GIER 0x20 +#define XDSI_ISR 0x24 +#define XDSI_IER 0x28 +#define XDSI_STR 0x2C +#define XDSI_STR_RDY_SHPKT BIT(6) +#define XDSI_STR_RDY_LNGPKT BIT(7) +#define XDSI_STR_DFIFO_FULL BIT(8) +#define XDSI_STR_DFIFO_EMPTY BIT(9) +#define XDSI_STR_WAITFR_DATA BIT(10) +#define XDSI_STR_CMD_EXE_PGS BIT(11) +#define XDSI_STR_CCMD_PROC BIT(12) +#define XDSI_STR_LPKT_MASK (0x5 << 7) +#define XDSI_CMD 0x30 +#define XDSI_CMD_QUEUE_PACKET(x) ((x) & GENMASK(23, 0)) +#define XDSI_DFR 0x34 +#define XDSI_TIME1 0x50 +#define XDSI_TIME1_BLLP_BURST(x) ((x) & GENMASK(15, 0)) +#define XDSI_TIME1_HSA(x) (((x) & GENMASK(15, 0)) << 16) +#define XDSI_TIME2 0x54 +#define XDSI_TIME2_VACT(x) ((x) & GENMASK(15, 0)) +#define XDSI_TIME2_HACT(x) (((x) & GENMASK(15, 0)) << 16) +#define XDSI_HACT_MULTIPLIER GENMASK(1, 0) +#define XDSI_TIME3 0x58 +#define XDSI_TIME3_HFP(x) ((x) & GENMASK(15, 0)) +#define XDSI_TIME3_HBP(x) (((x) & GENMASK(15, 0)) << 16) +#define XDSI_TIME4 0x5c +#define XDSI_TIME4_VFP(x) ((x) & GENMASK(7, 0)) +#define XDSI_TIME4_VBP(x) (((x) & GENMASK(7, 0)) << 8) +#define XDSI_TIME4_VSA(x) (((x) & GENMASK(7, 0)) << 16) +#define XDSI_NUM_DATA_T 4 + +/** + * struct xlnx_dsi - Xilinx DSI-TX core + * @bridge: DRM bridge structure + * @dsi_host: DSI host device + * @panel_bridge: Panel bridge structure + * @panel: DRM panel structure + * @dev: device structure + * @clks: clock source structure + * @iomem: Base address of DSI subsystem + * @mode_flags: DSI operation mode related flags + * @lanes: number of active data lanes supported by DSI controller + * @mul_factor: multiplication factor for HACT timing + * @format: pixel format for video mode of DSI controller + * @device_found: Flag to indicate device presence + */ +struct xlnx_dsi { + struct drm_bridge bridge; + struct mipi_dsi_host dsi_host; + struct drm_bridge *panel_bridge; + struct drm_panel *panel; + struct device *dev; + struct clk_bulk_data *clks; + void __iomem *iomem; + unsigned long mode_flags; + u32 lanes; + u32 mul_factor; + enum mipi_dsi_pixel_format format; + bool device_found; +}; + +static const struct clk_bulk_data xdsi_clks[] = { + { .id = "s_axis_aclk" }, + { .id = "dphy_clk_200M" }, +}; + +static inline struct xlnx_dsi *host_to_dsi(struct mipi_dsi_host *host) +{ + return container_of(host, struct xlnx_dsi, dsi_host); +} + +static inline struct xlnx_dsi *bridge_to_dsi(struct drm_bridge *bridge) +{ + return container_of(bridge, struct xlnx_dsi, bridge); +} + +static inline void xlnx_dsi_writel(void __iomem *base, int offset, u32 val) +{ + writel(val, base + offset); +} + +static inline u32 xlnx_dsi_readl(void __iomem *base, int offset) +{ + return readl(base + offset); +} + +static int xlnx_dsi_panel_or_bridge(struct xlnx_dsi *dsi, + struct device_node *node) +{ + struct drm_bridge *panel_bridge; + struct drm_panel *panel; + struct device *dev = dsi->dev; + struct device_node *endpoint = dev->of_node; + int ret; + + ret = drm_of_find_panel_or_bridge(endpoint, 1, 0, &panel, &panel_bridge); + if (ret < 0) { + dev_err(dsi->dev, "failed to find panel / bridge\n"); + return ret; + } + + if (panel) { + panel_bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(panel_bridge)) + return PTR_ERR(panel_bridge); + dsi->panel = panel; + } + + dsi->panel_bridge = panel_bridge; + + if (!dsi->panel_bridge) { + dev_err(dsi->dev, "panel not found\n"); + return -EPROBE_DEFER; + } + + return 0; +} + +static int xlnx_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct xlnx_dsi *dsi = host_to_dsi(host); + u32 reg; + + reg = xlnx_dsi_readl(dsi->iomem, XDSI_PCR); + dsi->lanes = reg & XDSI_PCR_LANES_MASK; + dsi->format = (reg & XDSI_PCR_PIXELFORMAT_MASK) >> + XDSI_PCR_PIXELFORMAT_SHIFT; + dsi->mode_flags = device->mode_flags; + + if (dsi->lanes != device->lanes) { + dev_err(dsi->dev, "Mismatch of lanes. panel = %d, DSI = %d\n", + device->lanes, dsi->lanes); + return -EINVAL; + } + + if (dsi->lanes > 4 || dsi->lanes < 1) { + dev_err(dsi->dev, "%d lanes : invalid xlnx,dsi-num-lanes\n", + dsi->lanes); + return -EINVAL; + } + + if (dsi->format != device->format) { + dev_err(dsi->dev, "Mismatch of format. panel = %d, DSI = %d\n", + device->format, dsi->format); + return -EINVAL; + } + + return 0; +} + +static int xlnx_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct xlnx_dsi *dsi = host_to_dsi(host); + + if (dsi->panel) { + drm_panel_disable(dsi->panel); + dsi->panel = NULL; + } + + return 0; +} + +static const struct mipi_dsi_host_ops xlnx_dsi_ops = { + .attach = xlnx_dsi_host_attach, + .detach = xlnx_dsi_host_detach, +}; + +static void +xlnx_dsi_bridge_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct xlnx_dsi *dsi = bridge_to_dsi(bridge); + u32 reg = xlnx_dsi_readl(dsi->iomem, XDSI_CCR); + + reg &= ~XDSI_CCR_COREENB; + xlnx_dsi_writel(dsi->iomem, XDSI_CCR, reg); + dev_dbg(dsi->dev, "DSI-Tx is disabled\n"); +} + +static void +xlnx_dsi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct xlnx_dsi *dsi = bridge_to_dsi(bridge); + u32 reg, video_mode; + + reg = xlnx_dsi_readl(dsi->iomem, XDSI_PCR); + video_mode = (reg & XDSI_PCR_VIDEOMODE_MASK) >> XDSI_PCR_VIDEOMODE_SHIFT; + + if (!video_mode && (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)) { + reg = XDSI_TIME1_HSA(adjusted_mode->hsync_end - + adjusted_mode->hsync_start); + xlnx_dsi_writel(dsi->iomem, XDSI_TIME1, reg); + } + + reg = XDSI_TIME4_VFP(adjusted_mode->vsync_start - + adjusted_mode->vdisplay) | + XDSI_TIME4_VBP(adjusted_mode->vtotal - + adjusted_mode->vsync_end) | + XDSI_TIME4_VSA(adjusted_mode->vsync_end - + adjusted_mode->vsync_start); + xlnx_dsi_writel(dsi->iomem, XDSI_TIME4, reg); + + reg = XDSI_TIME3_HFP(adjusted_mode->hsync_start - + adjusted_mode->hdisplay) | + XDSI_TIME3_HBP(adjusted_mode->htotal - + adjusted_mode->hsync_end); + xlnx_dsi_writel(dsi->iomem, XDSI_TIME3, reg); + dev_dbg(dsi->dev, "mul factor for parsed datatype is = %d\n", + (dsi->mul_factor) / 100); + + if ((adjusted_mode->hdisplay & XDSI_HACT_MULTIPLIER) != 0) + dev_warn(dsi->dev, "Incorrect HACT will be programmed\n"); + + reg = XDSI_TIME2_HACT((adjusted_mode->hdisplay) * (dsi->mul_factor) / 100) | + XDSI_TIME2_VACT(adjusted_mode->vdisplay); + + xlnx_dsi_writel(dsi->iomem, XDSI_PCR, XDSI_PCR_VIDEOMODE(BIT(0))); +} + +static void xlnx_dsi_bridge_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct xlnx_dsi *dsi = bridge_to_dsi(bridge); + u32 reg; + + reg = xlnx_dsi_readl(dsi->iomem, XDSI_CCR); + reg |= XDSI_CCR_COREENB; + xlnx_dsi_writel(dsi->iomem, XDSI_CCR, reg); + dev_dbg(dsi->dev, "MIPI DSI Tx controller is enabled.\n"); +} + +static int xlnx_dsi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct xlnx_dsi *dsi = bridge_to_dsi(bridge); + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found\n"); + return -ENODEV; + } + + /* Set the encoder type as caller does not know it */ + bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI; + + if (!dsi->device_found) { + int ret; + + ret = xlnx_dsi_panel_or_bridge(dsi, dsi->dev->of_node); + if (ret) { + dev_err(dsi->dev, "dsi_panel_or_bridge failed\n"); + return ret; + } + + dsi->device_found = true; + } + + /* Attach the panel-bridge to the dsi bridge */ + return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge, + flags); +} + +static void xlnx_dsi_bridge_detach(struct drm_bridge *bridge) +{ + struct xlnx_dsi *dsi = bridge_to_dsi(bridge); + + drm_of_panel_bridge_remove(dsi->dev->of_node, 1, 0); +} + +static const struct drm_bridge_funcs xlnx_dsi_bridge_funcs = { + .mode_set = xlnx_dsi_bridge_mode_set, + .atomic_enable = xlnx_dsi_bridge_enable, + .atomic_disable = xlnx_dsi_bridge_disable, + .attach = xlnx_dsi_bridge_attach, + .detach = xlnx_dsi_bridge_detach, +}; + +static int xlnx_dsi_parse_dt(struct xlnx_dsi *dsi) +{ + struct device *dev = dsi->dev; + struct device_node *node = dev->of_node; + int ret; + u32 datatype; + static const int xdsi_mul_fact[XDSI_NUM_DATA_T] = {300, 225, 225, 200}; + + /* + * Used as a multiplication factor for HACT based on used + * DSI data type. + * + * e.g. for RGB666_L datatype and 1920x1080 resolution, + * the Hact (WC) would be as follows - + * 1920 pixels * 18 bits per pixel / 8 bits per byte + * = 1920 pixels * 2.25 bytes per pixel = 4320 bytes. + * + * Data Type - Multiplication factor + * RGB888 - 3 + * RGB666_L - 2.25 +- * RGB666_P - 2.25 + * RGB565 - 2 + * + * Since the multiplication factor is a floating number, + * a 100x multiplication factor is used. + */ + ret = of_property_read_u32(node, "xlnx,dsi-data-type", &datatype); + if (ret < 0) { + dev_err(dsi->dev, "missing xlnx,dsi-data-type property\n"); + return ret; + } + dsi->format = datatype; + if (datatype > MIPI_DSI_FMT_RGB565) { + dev_err(dsi->dev, "Invalid xlnx,dsi-data-type string\n"); + return -EINVAL; + } + dsi->mul_factor = xdsi_mul_fact[datatype]; + + dev_dbg(dsi->dev, "DSI controller num lanes = %d", dsi->lanes); + dev_dbg(dsi->dev, "DSI controller datatype = %d\n", datatype); + + return 0; +} + +static int xlnx_dsi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct xlnx_dsi *dsi; + int num_clks = ARRAY_SIZE(xdsi_clks); + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + + dsi->dev = dev; + dsi->clks = devm_kmemdup(dev, xdsi_clks, sizeof(xdsi_clks), + GFP_KERNEL); + if (!dsi->clks) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dsi->iomem = devm_ioremap_resource(dev, res); + if (IS_ERR(dsi->iomem)) + return PTR_ERR(dsi->iomem); + + ret = clk_bulk_get(dev, num_clks, dsi->clks); + if (ret) + return ret; + + ret = xlnx_dsi_parse_dt(dsi); + if (ret) + return ret; + + ret = clk_bulk_prepare_enable(num_clks, dsi->clks); + if (ret) + goto err_clk_put; + + platform_set_drvdata(pdev, dsi); + dsi->dsi_host.ops = &xlnx_dsi_ops; + dsi->dsi_host.dev = dev; + + ret = mipi_dsi_host_register(&dsi->dsi_host); + if (ret) { + dev_err(dev, "Failed to register MIPI host: %d\n", ret); + goto err_clk_put; + } + + dsi->bridge.driver_private = dsi; + dsi->bridge.funcs = &xlnx_dsi_bridge_funcs; +#ifdef CONFIG_OF + dsi->bridge.of_node = pdev->dev.of_node; +#endif + + drm_bridge_add(&dsi->bridge); + +err_clk_put: + clk_bulk_put(num_clks, dsi->clks); + + return ret; +} + +static int xlnx_dsi_remove(struct platform_device *pdev) +{ + struct xlnx_dsi *dsi = platform_get_drvdata(pdev); + int num_clks = ARRAY_SIZE(xdsi_clks); + + mipi_dsi_host_unregister(&dsi->dsi_host); + clk_bulk_disable_unprepare(num_clks, dsi->clks); + clk_bulk_put(num_clks, dsi->clks); + + return 0; +} + +static const struct of_device_id xlnx_dsi_of_match[] = { + { .compatible = "xlnx,dsi-tx-v2.0"}, + { } +}; +MODULE_DEVICE_TABLE(of, xlnx_dsi_of_match); + +static struct platform_driver dsi_driver = { + .probe = xlnx_dsi_probe, + .remove = xlnx_dsi_remove, + .driver = { + .name = "xlnx-dsi", + .of_match_table = xlnx_dsi_of_match, + }, +}; + +module_platform_driver(dsi_driver); + +MODULE_AUTHOR("Venkateshwar Rao G <vgannava@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Xilinx MIPI DSI host controller driver"); +MODULE_LICENSE("GPL"); -- 1.8.3.1