aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/xlnx/xlnx_dsi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/xlnx/xlnx_dsi.c')
-rw-r--r--drivers/gpu/drm/xlnx/xlnx_dsi.c1011
1 files changed, 1011 insertions, 0 deletions
diff --git a/drivers/gpu/drm/xlnx/xlnx_dsi.c b/drivers/gpu/drm/xlnx/xlnx_dsi.c
new file mode 100644
index 000000000000..c785b941b037
--- /dev/null
+++ b/drivers/gpu/drm/xlnx/xlnx_dsi.c
@@ -0,0 +1,1011 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx FPGA MIPI DSI Tx Controller driver.
+ *
+ * Copyright (C) 2017 - 2018 Xilinx, Inc.
+ *
+ * Author : Saurabh Sengar <saurabhs@xilinx.com>
+ * : Siva Rajesh J <siva.rajesh.jarugula@xilinx.com>
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/iopoll.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/phy/phy.h>
+#include <video/mipi_display.h>
+#include <video/videomode.h>
+
+#include "xlnx_bridge.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_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_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_LTIME 0x60
+#define XDSI_BLLP_TIME 0x64
+/*
+ * XDSI_NUM_DATA_T represents number of data types in the
+ * enum mipi_dsi_pixel_format in the MIPI DSI part of DRM framework.
+ */
+#define XDSI_NUM_DATA_T 4
+#define XDSI_VIDEO_MODE_SYNC_PULSE 0x0
+#define XDSI_VIDEO_MODE_SYNC_EVENT 0x1
+#define XDSI_VIDEO_MODE_BURST 0x2
+
+#define XDSI_DPHY_CLK_MIN 197000000000UL
+#define XDSI_DPHY_CLK_MAX 203000000000UL
+#define XDSI_DPHY_CLK_REQ 200000000000UL
+
+/* command timeout in usec */
+#define XDSI_CMD_TIMEOUT_VAL (3000)
+
+/**
+ * struct xlnx_dsi - Core configuration DSI Tx subsystem device structure
+ * @encoder: DRM encoder structure
+ * @dsi_host: DSI host device
+ * @connector: DRM connector structure
+ * @panel_node: MIPI DSI device panel node
+ * @panel: DRM panel structure
+ * @dev: device structure
+ * @iomem: Base address of DSI subsystem
+ * @lanes: number of active data lanes supported by DSI controller
+ * @cmdmode: command mode support
+ * @mode_flags: DSI operation mode related flags
+ * @format: pixel format for video mode of DSI controller
+ * @vm: videomode data structure
+ * @mul_factor: multiplication factor for HACT timing parameter
+ * @eotp_prop: configurable EoTP DSI parameter
+ * @bllp_mode_prop: configurable BLLP mode DSI parameter
+ * @bllp_type_prop: configurable BLLP type DSI parameter
+ * @video_mode_prop: configurable Video mode DSI parameter
+ * @bllp_burst_time_prop: Configurable BLLP time for burst mode
+ * @cmd_queue_prop: configurable command queue
+ * @eotp_prop_val: configurable EoTP DSI parameter value
+ * @bllp_mode_prop_val: configurable BLLP mode DSI parameter value
+ * @bllp_type_prop_val: configurable BLLP type DSI parameter value
+ * @video_mode_prop_val: configurable Video mode DSI parameter value
+ * @bllp_burst_time_prop_val: Configurable BLLP time for burst mode value
+ * @cmd_queue_prop_val: configurable command queue value
+ * @bridge: bridge structure
+ * @height_out: configurable bridge output height parameter
+ * @height_out_prop_val: configurable bridge output height parameter value
+ * @width_out: configurable bridge output width parameter
+ * @width_out_prop_val: configurable bridge output width parameter value
+ * @in_fmt: configurable bridge input media format
+ * @in_fmt_prop_val: configurable media bus format value
+ * @out_fmt: configurable bridge output media format
+ * @out_fmt_prop_val: configurable media bus format value
+ * @video_aclk: Video clock
+ * @dphy_clk_200M: 200MHz DPHY clock and AXI Lite clock
+ */
+struct xlnx_dsi {
+ struct drm_encoder encoder;
+ struct mipi_dsi_host dsi_host;
+ struct drm_connector connector;
+ struct device_node *panel_node;
+ struct drm_panel *panel;
+ struct device *dev;
+ void __iomem *iomem;
+ u32 lanes;
+ bool cmdmode;
+ u32 mode_flags;
+ enum mipi_dsi_pixel_format format;
+ struct videomode vm;
+ u32 mul_factor;
+ struct drm_property *eotp_prop;
+ struct drm_property *bllp_mode_prop;
+ struct drm_property *bllp_type_prop;
+ struct drm_property *video_mode_prop;
+ struct drm_property *bllp_burst_time_prop;
+ struct drm_property *cmd_queue_prop;
+ bool eotp_prop_val;
+ bool bllp_mode_prop_val;
+ bool bllp_type_prop_val;
+ u32 video_mode_prop_val;
+ u32 bllp_burst_time_prop_val;
+ u32 cmd_queue_prop_val;
+ struct xlnx_bridge *bridge;
+ struct drm_property *height_out;
+ u32 height_out_prop_val;
+ struct drm_property *width_out;
+ u32 width_out_prop_val;
+ struct drm_property *in_fmt;
+ u32 in_fmt_prop_val;
+ struct drm_property *out_fmt;
+ u32 out_fmt_prop_val;
+ struct clk *video_aclk;
+ struct clk *dphy_clk_200M;
+};
+
+#define host_to_dsi(host) container_of(host, struct xlnx_dsi, dsi_host)
+#define connector_to_dsi(c) container_of(c, struct xlnx_dsi, connector)
+#define encoder_to_dsi(e) container_of(e, struct xlnx_dsi, encoder)
+
+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);
+}
+
+/**
+ * xlnx_dsi_set_config_parameters - Configure DSI Tx registers with parameters
+ * given from user application.
+ * @dsi: DSI structure having the updated user parameters
+ *
+ * This function takes the DSI structure having drm_property parameters
+ * configured from user application and writes them into DSI IP registers.
+ */
+static void xlnx_dsi_set_config_parameters(struct xlnx_dsi *dsi)
+{
+ u32 reg;
+
+ reg = XDSI_PCR_EOTPENABLE(dsi->eotp_prop_val);
+ reg |= XDSI_PCR_VIDEOMODE(dsi->video_mode_prop_val);
+ reg |= XDSI_PCR_BLLPTYPE(dsi->bllp_type_prop_val);
+ reg |= XDSI_PCR_BLLPMODE(dsi->bllp_mode_prop_val);
+
+ xlnx_dsi_writel(dsi->iomem, XDSI_PCR, reg);
+ /*
+ * Configure the burst time if video mode is burst.
+ * HSA of TIME1 register is ignored in this mode.
+ */
+ if (dsi->video_mode_prop_val == XDSI_VIDEO_MODE_BURST) {
+ reg = XDSI_TIME1_BLLP_BURST(dsi->bllp_burst_time_prop_val);
+ xlnx_dsi_writel(dsi->iomem, XDSI_TIME1, reg);
+ }
+
+ reg = XDSI_CMD_QUEUE_PACKET(dsi->cmd_queue_prop_val);
+ xlnx_dsi_writel(dsi->iomem, XDSI_CMD, reg);
+
+ dev_dbg(dsi->dev, "PCR register value is = %x\n",
+ xlnx_dsi_readl(dsi->iomem, XDSI_PCR));
+}
+
+/**
+ * xlnx_dsi_set_display_mode - Configure DSI timing registers
+ * @dsi: DSI structure having the updated user parameters
+ *
+ * This function writes the timing parameters of DSI IP which are
+ * retrieved from panel timing values.
+ */
+static void xlnx_dsi_set_display_mode(struct xlnx_dsi *dsi)
+{
+ struct videomode *vm = &dsi->vm;
+ u32 reg, video_mode;
+
+ reg = xlnx_dsi_readl(dsi->iomem, XDSI_PCR);
+ video_mode = (reg & XDSI_PCR_VIDEOMODE_MASK) >>
+ XDSI_PCR_VIDEOMODE_SHIFT;
+
+ /* configure the HSA value only if non_burst_sync_pluse video mode */
+ if (!video_mode &&
+ (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)) {
+ reg = XDSI_TIME1_HSA(vm->hsync_len);
+ xlnx_dsi_writel(dsi->iomem, XDSI_TIME1, reg);
+ }
+
+ reg = XDSI_TIME4_VFP(vm->vfront_porch) |
+ XDSI_TIME4_VBP(vm->vback_porch) |
+ XDSI_TIME4_VSA(vm->vsync_len);
+ xlnx_dsi_writel(dsi->iomem, XDSI_TIME4, reg);
+
+ reg = XDSI_TIME3_HFP(vm->hfront_porch) |
+ XDSI_TIME3_HBP(vm->hback_porch);
+ xlnx_dsi_writel(dsi->iomem, XDSI_TIME3, reg);
+
+ dev_dbg(dsi->dev, "mul factor for parsed datatype is = %d\n",
+ (dsi->mul_factor) / 100);
+ /*
+ * The HACT parameter received from panel timing values should be
+ * divisible by 4. The reason for this is, the word count given as
+ * input to DSI controller is HACT * mul_factor. The mul_factor is
+ * 3, 2.25, 2.25, 2 respectively for RGB888, RGB666_L, RGB666_P and
+ * RGB565.
+ * e.g. for RGB666_L color format and 1080p, the word count is
+ * 1920*2.25 = 4320 which is divisible by 4 and it is a valid input
+ * to DSI controller. Based on this 2.25 mul factor, we come up with
+ * the division factor of (XDSI_HACT_MULTIPLIER) as 4 for checking
+ */
+ if ((vm->hactive & XDSI_HACT_MULTIPLIER) != 0)
+ dev_warn(dsi->dev, "Incorrect HACT will be programmed\n");
+
+ reg = XDSI_TIME2_HACT((vm->hactive) * (dsi->mul_factor) / 100) |
+ XDSI_TIME2_VACT(vm->vactive);
+ xlnx_dsi_writel(dsi->iomem, XDSI_TIME2, reg);
+
+ dev_dbg(dsi->dev, "LCD size = %dx%d\n", vm->hactive, vm->vactive);
+}
+
+/**
+ * xlnx_dsi_set_display_enable - Enables the DSI Tx IP core enable
+ * register bit
+ * @dsi: DSI structure having the updated user parameters
+ *
+ * This function takes the DSI strucure and enables the core enable bit
+ * of core configuration register.
+ */
+static void xlnx_dsi_set_display_enable(struct xlnx_dsi *dsi)
+{
+ 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");
+}
+
+/**
+ * xlnx_dsi_set_display_disable - Disable the DSI Tx IP core enable
+ * register bit
+ * @dsi: DSI structure having the updated user parameters
+ *
+ * This function takes the DSI strucure and disables the core enable bit
+ * of core configuration register.
+ */
+static void xlnx_dsi_set_display_disable(struct xlnx_dsi *dsi)
+{
+ 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, "DSI Tx is disabled. reset regs to default values\n");
+}
+
+/**
+ * xlnx_dsi_atomic_set_property - implementation of drm_connector_funcs
+ * set_property invoked by IOCTL call to DRM_IOCTL_MODE_OBJ_SETPROPERTY
+ *
+ * @connector: pointer Xilinx DSI connector
+ * @state: DRM connector state
+ * @prop: pointer to the drm_property structure
+ * @val: DSI parameter value that is configured from user application
+ *
+ * This function takes a drm_property name and value given from user application
+ * and update the DSI structure property varabiles with the values.
+ * These values are later used to configure the DSI Rx IP.
+ *
+ * Return: 0 on success OR -EINVAL if setting property fails
+ */
+static int xlnx_dsi_atomic_set_property(struct drm_connector *connector,
+ struct drm_connector_state *state,
+ struct drm_property *prop, u64 val)
+{
+ struct xlnx_dsi *dsi = connector_to_dsi(connector);
+
+ dev_dbg(dsi->dev, "property name = %s, value = %lld\n",
+ prop->name, val);
+
+ if (prop == dsi->eotp_prop)
+ dsi->eotp_prop_val = !!val;
+ else if (prop == dsi->bllp_mode_prop)
+ dsi->bllp_mode_prop_val = !!val;
+ else if (prop == dsi->bllp_type_prop)
+ dsi->bllp_type_prop_val = !!val;
+ else if (prop == dsi->video_mode_prop)
+ dsi->video_mode_prop_val = (unsigned int)val;
+ else if (prop == dsi->bllp_burst_time_prop)
+ dsi->bllp_burst_time_prop_val = (unsigned int)val;
+ else if (prop == dsi->cmd_queue_prop)
+ dsi->cmd_queue_prop_val = (unsigned int)val;
+ else if (prop == dsi->height_out)
+ dsi->height_out_prop_val = (u32)val;
+ else if (prop == dsi->width_out)
+ dsi->width_out_prop_val = (u32)val;
+ else if (prop == dsi->in_fmt)
+ dsi->in_fmt_prop_val = (u32)val;
+ else if (prop == dsi->out_fmt)
+ dsi->out_fmt_prop_val = (u32)val;
+ else
+ return -EINVAL;
+
+ xlnx_dsi_set_config_parameters(dsi);
+
+ return 0;
+}
+
+static int
+xlnx_dsi_atomic_get_property(struct drm_connector *connector,
+ const struct drm_connector_state *state,
+ struct drm_property *prop, uint64_t *val)
+{
+ struct xlnx_dsi *dsi = connector_to_dsi(connector);
+
+ if (prop == dsi->eotp_prop)
+ *val = dsi->eotp_prop_val;
+ else if (prop == dsi->bllp_mode_prop)
+ *val = dsi->bllp_mode_prop_val;
+ else if (prop == dsi->bllp_type_prop)
+ *val = dsi->bllp_type_prop_val;
+ else if (prop == dsi->video_mode_prop)
+ *val = dsi->video_mode_prop_val;
+ else if (prop == dsi->bllp_burst_time_prop)
+ *val = dsi->bllp_burst_time_prop_val;
+ else if (prop == dsi->cmd_queue_prop)
+ *val = dsi->cmd_queue_prop_val;
+ else if (prop == dsi->height_out)
+ *val = dsi->height_out_prop_val;
+ else if (prop == dsi->width_out)
+ *val = dsi->width_out_prop_val;
+ else if (prop == dsi->in_fmt)
+ *val = dsi->in_fmt_prop_val;
+ else if (prop == dsi->out_fmt)
+ *val = dsi->out_fmt_prop_val;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * xlnx_dsi_host_transfer - transfer command to panel
+ * @host: mipi dsi host structure
+ * @msg: mipi dsi msg with type, length and data
+ *
+ * This function is valid only in command mode.
+ * It checks the command fifo empty status and writes into
+ * data or cmd register and waits for the completion status.
+ *
+ * Return: number of bytes, on success and error number on failure
+ */
+static ssize_t xlnx_dsi_host_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ struct xlnx_dsi *dsi = host_to_dsi(host);
+ u32 data0, data1, cmd0, status, val;
+ const char *tx_buf = msg->tx_buf;
+
+ if (!(xlnx_dsi_readl(dsi->iomem, XDSI_CCR) & (XDSI_CCR_COREENB |
+ XDSI_CCR_CMDMODE))) {
+ dev_err(dsi->dev, "dsi command mode not enabled\n");
+ return -EINVAL;
+ }
+
+ if (msg->type == MIPI_DSI_DCS_LONG_WRITE) {
+ status = readl_poll_timeout(dsi->iomem + XDSI_STR, val,
+ ((val & XDSI_STR_LPKT_MASK) ==
+ XDSI_STR_LPKT_MASK), 1,
+ XDSI_CMD_TIMEOUT_VAL);
+ if (status) {
+ dev_err(dsi->dev, "long cmd fifo not empty!\n");
+ return -ETIMEDOUT;
+ }
+ data0 = tx_buf[0] | (tx_buf[1] << 8) | (tx_buf[2] << 16) |
+ (tx_buf[3] << 24);
+ data1 = tx_buf[4] | (tx_buf[5] << 8);
+ cmd0 = msg->type | (MIPI_DSI_DCS_READ << 8);
+
+ xlnx_dsi_writel(dsi->iomem, XDSI_DFR, data0);
+ xlnx_dsi_writel(dsi->iomem, XDSI_DFR, data1);
+ xlnx_dsi_writel(dsi->iomem, XDSI_CMD, cmd0);
+ } else {
+ data0 = tx_buf[0];
+ if (msg->type == MIPI_DSI_DCS_SHORT_WRITE_PARAM)
+ data0 = MIPI_DSI_DCS_SHORT_WRITE_PARAM |
+ (tx_buf[0] << 8) | (tx_buf[1] << 16);
+ else
+ data0 = MIPI_DSI_DCS_SHORT_WRITE | (tx_buf[0] << 8);
+
+ status = readl_poll_timeout(dsi->iomem + XDSI_STR, val,
+ ((val & XDSI_STR_RDY_SHPKT) ==
+ XDSI_STR_RDY_SHPKT), 1,
+ XDSI_CMD_TIMEOUT_VAL);
+ if (status) {
+ dev_err(dsi->dev, "short cmd fifo not empty\n");
+ return -ETIMEDOUT;
+ }
+ xlnx_dsi_writel(dsi->iomem, XDSI_CMD, data0);
+ }
+
+ status = readl_poll_timeout(dsi->iomem + XDSI_STR, val,
+ (!(val & XDSI_STR_CMD_EXE_PGS)), 1,
+ XDSI_CMD_TIMEOUT_VAL);
+ if (status) {
+ dev_err(dsi->dev, "cmd time out\n");
+ return -ETIMEDOUT;
+ }
+
+ return msg->tx_len;
+}
+
+static int xlnx_dsi_host_attach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *device)
+{
+ u32 panel_lanes;
+ struct xlnx_dsi *dsi = host_to_dsi(host);
+
+ panel_lanes = device->lanes;
+ dsi->mode_flags = device->mode_flags;
+ dsi->panel_node = device->dev.of_node;
+
+ if (panel_lanes != dsi->lanes) {
+ dev_err(dsi->dev, "Mismatch of lanes. panel = %d, DSI = %d\n",
+ panel_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 (device->format != dsi->format) {
+ dev_err(dsi->dev, "Mismatch of format. panel = %d, DSI = %d\n",
+ device->format, dsi->format);
+ return -EINVAL;
+ }
+
+ if (dsi->connector.dev)
+ drm_helper_hpd_irq_event(dsi->connector.dev);
+
+ 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);
+
+ dsi->panel = NULL;
+
+ if (dsi->connector.dev)
+ drm_helper_hpd_irq_event(dsi->connector.dev);
+
+ return 0;
+}
+
+static const struct mipi_dsi_host_ops xlnx_dsi_ops = {
+ .attach = xlnx_dsi_host_attach,
+ .detach = xlnx_dsi_host_detach,
+ .transfer = xlnx_dsi_host_transfer,
+};
+
+static int xlnx_dsi_connector_dpms(struct drm_connector *connector, int mode)
+{
+ struct xlnx_dsi *dsi = connector_to_dsi(connector);
+ int ret;
+
+ dev_dbg(dsi->dev, "connector dpms state: %d\n", mode);
+
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ ret = drm_panel_prepare(dsi->panel);
+ if (ret < 0) {
+ dev_err(dsi->dev, "DRM panel not found\n");
+ return ret;
+ }
+
+ ret = drm_panel_enable(dsi->panel);
+ if (ret < 0) {
+ drm_panel_unprepare(dsi->panel);
+ dev_err(dsi->dev, "DRM panel not enabled\n");
+ return ret;
+ }
+ break;
+ default:
+ drm_panel_disable(dsi->panel);
+ drm_panel_unprepare(dsi->panel);
+ break;
+ }
+
+ return drm_helper_connector_dpms(connector, mode);
+}
+
+static enum drm_connector_status
+xlnx_dsi_detect(struct drm_connector *connector, bool force)
+{
+ struct xlnx_dsi *dsi = connector_to_dsi(connector);
+
+ if (!dsi->panel) {
+ dsi->panel = of_drm_find_panel(dsi->panel_node);
+ if (dsi->panel) {
+ drm_panel_attach(dsi->panel, &dsi->connector);
+ if (dsi->cmdmode) {
+ xlnx_dsi_writel(dsi->iomem, XDSI_CCR,
+ XDSI_CCR_CMDMODE |
+ XDSI_CCR_COREENB);
+ drm_panel_prepare(dsi->panel);
+ xlnx_dsi_writel(dsi->iomem, XDSI_CCR, 0);
+ }
+ }
+ } else if (!dsi->panel_node) {
+ xlnx_dsi_connector_dpms(connector, DRM_MODE_DPMS_OFF);
+ drm_panel_detach(dsi->panel);
+ dsi->panel = NULL;
+ }
+
+ if (dsi->panel)
+ return connector_status_connected;
+
+ return connector_status_disconnected;
+}
+
+static void xlnx_dsi_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+ connector->dev = NULL;
+}
+
+static const struct drm_connector_funcs xlnx_dsi_connector_funcs = {
+ .dpms = xlnx_dsi_connector_dpms,
+ .detect = xlnx_dsi_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = xlnx_dsi_connector_destroy,
+ .atomic_set_property = xlnx_dsi_atomic_set_property,
+ .atomic_get_property = xlnx_dsi_atomic_get_property,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+ .reset = drm_atomic_helper_connector_reset,
+};
+
+static int xlnx_dsi_get_modes(struct drm_connector *connector)
+{
+ struct xlnx_dsi *dsi = connector_to_dsi(connector);
+
+ if (dsi->panel)
+ return dsi->panel->funcs->get_modes(dsi->panel);
+
+ return 0;
+}
+
+static struct drm_encoder *
+xlnx_dsi_best_encoder(struct drm_connector *connector)
+{
+ return &(connector_to_dsi(connector)->encoder);
+}
+
+static struct drm_connector_helper_funcs xlnx_dsi_connector_helper_funcs = {
+ .get_modes = xlnx_dsi_get_modes,
+ .best_encoder = xlnx_dsi_best_encoder,
+};
+
+/**
+ * xlnx_dsi_connector_create_property - create DSI connector properties
+ *
+ * @connector: pointer to Xilinx DSI connector
+ *
+ * This function takes the xilinx DSI connector component and defines
+ * the drm_property variables with their default values.
+ */
+static void xlnx_dsi_connector_create_property(struct drm_connector *connector)
+{
+ struct drm_device *dev = connector->dev;
+ struct xlnx_dsi *dsi = connector_to_dsi(connector);
+
+ dsi->eotp_prop = drm_property_create_bool(dev, 0, "eotp");
+ dsi->video_mode_prop = drm_property_create_range(dev, 0, "video_mode",
+ 0, 2);
+ dsi->bllp_mode_prop = drm_property_create_bool(dev, 0, "bllp_mode");
+ dsi->bllp_type_prop = drm_property_create_bool(dev, 0, "bllp_type");
+ dsi->bllp_burst_time_prop =
+ drm_property_create_range(dev, 0, "bllp_burst_time", 0, 0xFFFF);
+ dsi->cmd_queue_prop = drm_property_create_range(dev, 0, "cmd_queue", 0,
+ 0xffffff);
+ dsi->height_out = drm_property_create_range(dev, 0, "height_out",
+ 2, 4096);
+ dsi->width_out = drm_property_create_range(dev, 0, "width_out",
+ 2, 4096);
+ dsi->in_fmt = drm_property_create_range(dev, 0, "in_fmt", 0, 16384);
+ dsi->out_fmt = drm_property_create_range(dev, 0, "out_fmt", 0, 16384);
+}
+
+/**
+ * xlnx_dsi_connector_attach_property - attach DSI connector
+ * properties
+ *
+ * @connector: pointer to Xilinx DSI connector
+ */
+static void xlnx_dsi_connector_attach_property(struct drm_connector *connector)
+{
+ struct xlnx_dsi *dsi = connector_to_dsi(connector);
+ struct drm_mode_object *obj = &connector->base;
+
+ if (dsi->eotp_prop)
+ drm_object_attach_property(obj, dsi->eotp_prop, 1);
+
+ if (dsi->video_mode_prop)
+ drm_object_attach_property(obj, dsi->video_mode_prop, 0);
+
+ if (dsi->bllp_burst_time_prop)
+ drm_object_attach_property(&connector->base,
+ dsi->bllp_burst_time_prop, 0);
+
+ if (dsi->bllp_mode_prop)
+ drm_object_attach_property(&connector->base,
+ dsi->bllp_mode_prop, 0);
+
+ if (dsi->bllp_type_prop)
+ drm_object_attach_property(&connector->base,
+ dsi->bllp_type_prop, 0);
+
+ if (dsi->cmd_queue_prop)
+ drm_object_attach_property(&connector->base,
+ dsi->cmd_queue_prop, 0);
+
+ if (dsi->height_out)
+ drm_object_attach_property(obj, dsi->height_out, 0);
+
+ if (dsi->width_out)
+ drm_object_attach_property(obj, dsi->width_out, 0);
+
+ if (dsi->in_fmt)
+ drm_object_attach_property(obj, dsi->in_fmt, 0);
+
+ if (dsi->out_fmt)
+ drm_object_attach_property(obj, dsi->out_fmt, 0);
+}
+
+static int xlnx_dsi_create_connector(struct drm_encoder *encoder)
+{
+ struct xlnx_dsi *dsi = encoder_to_dsi(encoder);
+ struct drm_connector *connector = &dsi->connector;
+ int ret;
+
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+
+ ret = drm_connector_init(encoder->dev, connector,
+ &xlnx_dsi_connector_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+ if (ret) {
+ dev_err(dsi->dev, "Failed to initialize connector with drm\n");
+ return ret;
+ }
+
+ drm_connector_helper_add(connector, &xlnx_dsi_connector_helper_funcs);
+ drm_connector_register(connector);
+ drm_connector_attach_encoder(connector, encoder);
+ xlnx_dsi_connector_create_property(connector);
+ xlnx_dsi_connector_attach_property(connector);
+
+ return 0;
+}
+
+/**
+ * xlnx_dsi_atomic_mode_set - derive the DSI timing parameters
+ *
+ * @encoder: pointer to Xilinx DRM encoder
+ * @crtc_state: Pointer to drm core crtc state
+ * @connector_state: DSI connector drm state
+ *
+ * This function derives the DSI IP timing parameters from the timing
+ * values given in the attached panel driver.
+ */
+static void
+xlnx_dsi_atomic_mode_set(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *connector_state)
+{
+ struct xlnx_dsi *dsi = encoder_to_dsi(encoder);
+ struct videomode *vm = &dsi->vm;
+ struct drm_display_mode *m = &crtc_state->adjusted_mode;
+
+ /* Set bridge input and output parameters */
+ xlnx_bridge_set_input(dsi->bridge, m->hdisplay, m->vdisplay,
+ dsi->in_fmt_prop_val);
+ xlnx_bridge_set_output(dsi->bridge, dsi->width_out_prop_val,
+ dsi->height_out_prop_val,
+ dsi->out_fmt_prop_val);
+ xlnx_bridge_enable(dsi->bridge);
+
+ vm->hactive = m->hdisplay;
+ vm->vactive = m->vdisplay;
+ vm->vfront_porch = m->vsync_start - m->vdisplay;
+ vm->vback_porch = m->vtotal - m->vsync_end;
+ vm->vsync_len = m->vsync_end - m->vsync_start;
+ vm->hfront_porch = m->hsync_start - m->hdisplay;
+ vm->hback_porch = m->htotal - m->hsync_end;
+ vm->hsync_len = m->hsync_end - m->hsync_start;
+ xlnx_dsi_set_display_mode(dsi);
+}
+
+static void xlnx_dsi_disable(struct drm_encoder *encoder)
+{
+ struct xlnx_dsi *dsi = encoder_to_dsi(encoder);
+
+ if (dsi->bridge)
+ xlnx_bridge_disable(dsi->bridge);
+
+ xlnx_dsi_set_display_disable(dsi);
+}
+
+static void xlnx_dsi_enable(struct drm_encoder *encoder)
+{
+ struct xlnx_dsi *dsi = encoder_to_dsi(encoder);
+
+ xlnx_dsi_set_display_enable(dsi);
+}
+
+static const struct drm_encoder_helper_funcs xlnx_dsi_encoder_helper_funcs = {
+ .atomic_mode_set = xlnx_dsi_atomic_mode_set,
+ .enable = xlnx_dsi_enable,
+ .disable = xlnx_dsi_disable,
+};
+
+static const struct drm_encoder_funcs xlnx_dsi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+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};
+
+ dsi->dphy_clk_200M = devm_clk_get(dev, "dphy_clk_200M");
+ if (IS_ERR(dsi->dphy_clk_200M)) {
+ ret = PTR_ERR(dsi->dphy_clk_200M);
+ dev_err(dev, "failed to get dphy_clk_200M %d\n", ret);
+ return ret;
+ }
+
+ dsi->video_aclk = devm_clk_get(dev, "s_axis_aclk");
+ if (IS_ERR(dsi->video_aclk)) {
+ ret = PTR_ERR(dsi->video_aclk);
+ dev_err(dev, "failed to get video_clk %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * 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 maybe a floating number,
+ * a 100x multiplication factor is used.
+ */
+ ret = of_property_read_u32(node, "xlnx,dsi-num-lanes", &dsi->lanes);
+ if (ret < 0) {
+ dev_err(dsi->dev, "missing xlnx,dsi-num-lanes property\n");
+ return ret;
+ }
+ if (dsi->lanes > 4 || dsi->lanes < 1) {
+ dev_err(dsi->dev, "%d lanes : invalid lanes\n", dsi->lanes);
+ return -EINVAL;
+ }
+ 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];
+
+ dsi->cmdmode = of_property_read_bool(node, "xlnx,dsi-cmd-mode");
+
+ dev_dbg(dsi->dev, "DSI controller num lanes = %d", dsi->lanes);
+ dev_dbg(dsi->dev, "DSI controller datatype = %d\n", datatype);
+ dev_dbg(dsi->dev, "DSI controller cmd mode = %d\n", dsi->cmdmode);
+
+ return 0;
+}
+
+static int xlnx_dsi_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct xlnx_dsi *dsi = dev_get_drvdata(dev);
+ struct drm_encoder *encoder = &dsi->encoder;
+ struct drm_device *drm_dev = data;
+ int ret;
+
+ /*
+ * TODO: The possible CRTCs are 1 now as per current implementation of
+ * DSI tx drivers. DRM framework can support more than one CRTCs and
+ * DSI driver can be enhanced for that.
+ */
+ encoder->possible_crtcs = 1;
+ drm_encoder_init(drm_dev, encoder, &xlnx_dsi_encoder_funcs,
+ DRM_MODE_ENCODER_DSI, NULL);
+ drm_encoder_helper_add(encoder, &xlnx_dsi_encoder_helper_funcs);
+ ret = xlnx_dsi_create_connector(encoder);
+ if (ret) {
+ dev_err(dsi->dev, "fail creating connector, ret = %d\n", ret);
+ drm_encoder_cleanup(encoder);
+ return ret;
+ }
+ ret = mipi_dsi_host_register(&dsi->dsi_host);
+ if (ret) {
+ xlnx_dsi_connector_destroy(&dsi->connector);
+ drm_encoder_cleanup(encoder);
+ return ret;
+ }
+ return 0;
+}
+
+static void xlnx_dsi_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct xlnx_dsi *dsi = dev_get_drvdata(dev);
+
+ xlnx_dsi_disable(&dsi->encoder);
+ mipi_dsi_host_unregister(&dsi->dsi_host);
+ xlnx_bridge_disable(dsi->bridge);
+}
+
+static const struct component_ops xlnx_dsi_component_ops = {
+ .bind = xlnx_dsi_bind,
+ .unbind = xlnx_dsi_unbind,
+};
+
+static int xlnx_dsi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct xlnx_dsi *dsi;
+ struct device_node *vpss_node;
+ int ret;
+ unsigned long rate;
+
+ dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+ if (!dsi)
+ return -ENOMEM;
+
+ dsi->dsi_host.ops = &xlnx_dsi_ops;
+ dsi->dsi_host.dev = dev;
+ dsi->dev = dev;
+
+ ret = xlnx_dsi_parse_dt(dsi);
+ if (ret)
+ return ret;
+
+ 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);
+
+ platform_set_drvdata(pdev, dsi);
+
+ /* Bridge support */
+ vpss_node = of_parse_phandle(dsi->dev->of_node, "xlnx,vpss", 0);
+ if (vpss_node) {
+ dsi->bridge = of_xlnx_bridge_get(vpss_node);
+ if (!dsi->bridge) {
+ dev_info(dsi->dev, "Didn't get bridge instance\n");
+ return -EPROBE_DEFER;
+ }
+ }
+
+ ret = clk_set_rate(dsi->dphy_clk_200M, XDSI_DPHY_CLK_REQ);
+ if (ret) {
+ dev_err(dev, "failed to set dphy clk rate %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dsi->dphy_clk_200M);
+ if (ret) {
+ dev_err(dev, "failed to enable dphy clk %d\n", ret);
+ return ret;
+ }
+
+ rate = clk_get_rate(dsi->dphy_clk_200M);
+ if (rate < XDSI_DPHY_CLK_MIN && rate > XDSI_DPHY_CLK_MAX) {
+ dev_err(dev, "Error DPHY clock = %lu\n", rate);
+ ret = -EINVAL;
+ goto err_disable_dphy_clk;
+ }
+
+ ret = clk_prepare_enable(dsi->video_aclk);
+ if (ret) {
+ dev_err(dev, "failed to enable video clk %d\n", ret);
+ goto err_disable_dphy_clk;
+ }
+
+ ret = component_add(dev, &xlnx_dsi_component_ops);
+ if (ret < 0)
+ goto err_disable_video_clk;
+
+ return ret;
+
+err_disable_video_clk:
+ clk_disable_unprepare(dsi->video_aclk);
+err_disable_dphy_clk:
+ clk_disable_unprepare(dsi->dphy_clk_200M);
+ return ret;
+}
+
+static int xlnx_dsi_remove(struct platform_device *pdev)
+{
+ struct xlnx_dsi *dsi = platform_get_drvdata(pdev);
+
+ component_del(&pdev->dev, &xlnx_dsi_component_ops);
+ clk_disable_unprepare(dsi->video_aclk);
+ clk_disable_unprepare(dsi->dphy_clk_200M);
+
+ return 0;
+}
+
+static const struct of_device_id xlnx_dsi_of_match[] = {
+ { .compatible = "xlnx,dsi"},
+ { }
+};
+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("Siva Rajesh <sivaraj@xilinx.com>");
+MODULE_DESCRIPTION("Xilinx FPGA MIPI DSI Tx Driver");
+MODULE_LICENSE("GPL v2");