Hi Venkateshwar, On Thu, May 12, 2022 at 07:23:13PM +0530, Venkateshwar Rao Gannavarapu wrote: > 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. Thanks for submitting this driver. I have added a few comments in the following that I hope you will find useful to improve the driver. Sam > > 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. It would be nice to have it in some sort of alphabetic order, both in the Kconfig file and also the Makefile. > + > +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 The BACKLIGHT_LCD_SUPPORT symbol is not relevant and can be dropped. > + select BACKLIGHT_CLASS_DEVICE > + select DRM_PANEL_SIMPLE The symbol DRM_PANEL_SIMPLE is used to enable the panel_simple driver and should not be selected here. > + 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> I noticed this is not the mail used in the s-o-b line. > + * > + */ > + > +#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) GENMASK()? Same for other XXX_MASK definitions below > +#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; It looks wrong that both a panel and a panel_bridge is required. We should today only use the panel_bridge. > + 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); > +} When I see implementations like this I wonder if a regmap would be beneficial? > + > +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); >From the documentation of drm_of_find_panel_or_bridge(): * This function is deprecated and should not be used in new drivers. Use * devm_drm_of_get_bridge() instead. Please update accordingly. This will also avoid the panel/panel_bridge confusion. > + 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"); Maybe catch this in a mode_valid() operation? > + > + 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"); DRM_ERROR and friends are deprecated. Use drm_xxx or dev_xxx if possible, otherwise fallback to pr_err. > + 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, >From the documentation of the mode_set operation: * This is deprecated, do not use! * New drivers shall set their mode in the * &drm_bridge_funcs.atomic_enable operation. Please adjust accordingly. > + .atomic_enable = xlnx_dsi_bridge_enable, > + .atomic_disable = xlnx_dsi_bridge_disable, > + .attach = xlnx_dsi_bridge_attach, > + .detach = xlnx_dsi_bridge_detach, > +}; For a new bridge please implement all the mandatory atomic operations. You will need at least: .atomic_get_output_bus_fmts = xlnx_dsi_bridge_get_output_bus_fmts, .atomic_get_input_bus_fmts = xlnx_dsi_bridge_get_input_bus_fmts, .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, }; > + > +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 The driver depends on CONFIG_OF - so no need to check it here. > + 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