162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci#include <linux/clk.h> 362306a36Sopenharmony_ci#include <linux/component.h> 462306a36Sopenharmony_ci#include <linux/delay.h> 562306a36Sopenharmony_ci#include <linux/io.h> 662306a36Sopenharmony_ci#include <linux/mfd/syscon.h> 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/of.h> 962306a36Sopenharmony_ci#include <linux/platform_device.h> 1062306a36Sopenharmony_ci#include <linux/regmap.h> 1162306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 1262306a36Sopenharmony_ci#include <video/mipi_display.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h> 1562306a36Sopenharmony_ci#include <drm/drm_bridge.h> 1662306a36Sopenharmony_ci#include <drm/drm_device.h> 1762306a36Sopenharmony_ci#include <drm/drm_drv.h> 1862306a36Sopenharmony_ci#include <drm/drm_encoder.h> 1962306a36Sopenharmony_ci#include <drm/drm_mipi_dsi.h> 2062306a36Sopenharmony_ci#include <drm/drm_modeset_helper_vtables.h> 2162306a36Sopenharmony_ci#include <drm/drm_of.h> 2262306a36Sopenharmony_ci#include <drm/drm_panel.h> 2362306a36Sopenharmony_ci#include <drm/drm_print.h> 2462306a36Sopenharmony_ci#include <drm/drm_probe_helper.h> 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#include "mcde_drm.h" 2762306a36Sopenharmony_ci#include "mcde_dsi_regs.h" 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define DSI_DEFAULT_LP_FREQ_HZ 19200000 3062306a36Sopenharmony_ci#define DSI_DEFAULT_HS_FREQ_HZ 420160000 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci/* PRCMU DSI reset registers */ 3362306a36Sopenharmony_ci#define PRCM_DSI_SW_RESET 0x324 3462306a36Sopenharmony_ci#define PRCM_DSI_SW_RESET_DSI0_SW_RESETN BIT(0) 3562306a36Sopenharmony_ci#define PRCM_DSI_SW_RESET_DSI1_SW_RESETN BIT(1) 3662306a36Sopenharmony_ci#define PRCM_DSI_SW_RESET_DSI2_SW_RESETN BIT(2) 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistruct mcde_dsi { 3962306a36Sopenharmony_ci struct device *dev; 4062306a36Sopenharmony_ci struct mcde *mcde; 4162306a36Sopenharmony_ci struct drm_bridge bridge; 4262306a36Sopenharmony_ci struct drm_panel *panel; 4362306a36Sopenharmony_ci struct drm_bridge *bridge_out; 4462306a36Sopenharmony_ci struct mipi_dsi_host dsi_host; 4562306a36Sopenharmony_ci struct mipi_dsi_device *mdsi; 4662306a36Sopenharmony_ci const struct drm_display_mode *mode; 4762306a36Sopenharmony_ci struct clk *hs_clk; 4862306a36Sopenharmony_ci struct clk *lp_clk; 4962306a36Sopenharmony_ci unsigned long hs_freq; 5062306a36Sopenharmony_ci unsigned long lp_freq; 5162306a36Sopenharmony_ci bool unused; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci void __iomem *regs; 5462306a36Sopenharmony_ci struct regmap *prcmu; 5562306a36Sopenharmony_ci}; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic inline struct mcde_dsi *bridge_to_mcde_dsi(struct drm_bridge *bridge) 5862306a36Sopenharmony_ci{ 5962306a36Sopenharmony_ci return container_of(bridge, struct mcde_dsi, bridge); 6062306a36Sopenharmony_ci} 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic inline struct mcde_dsi *host_to_mcde_dsi(struct mipi_dsi_host *h) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci return container_of(h, struct mcde_dsi, dsi_host); 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cibool mcde_dsi_irq(struct mipi_dsi_device *mdsi) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci struct mcde_dsi *d; 7062306a36Sopenharmony_ci u32 val; 7162306a36Sopenharmony_ci bool te_received = false; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci d = host_to_mcde_dsi(mdsi->host); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci dev_dbg(d->dev, "%s called\n", __func__); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci val = readl(d->regs + DSI_DIRECT_CMD_STS_FLAG); 7862306a36Sopenharmony_ci if (val) 7962306a36Sopenharmony_ci dev_dbg(d->dev, "DSI_DIRECT_CMD_STS_FLAG = %08x\n", val); 8062306a36Sopenharmony_ci if (val & DSI_DIRECT_CMD_STS_WRITE_COMPLETED) 8162306a36Sopenharmony_ci dev_dbg(d->dev, "direct command write completed\n"); 8262306a36Sopenharmony_ci if (val & DSI_DIRECT_CMD_STS_TE_RECEIVED) { 8362306a36Sopenharmony_ci te_received = true; 8462306a36Sopenharmony_ci dev_dbg(d->dev, "direct command TE received\n"); 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci if (val & DSI_DIRECT_CMD_STS_ACKNOWLEDGE_WITH_ERR_RECEIVED) 8762306a36Sopenharmony_ci dev_err(d->dev, "direct command ACK ERR received\n"); 8862306a36Sopenharmony_ci if (val & DSI_DIRECT_CMD_STS_READ_COMPLETED_WITH_ERR) 8962306a36Sopenharmony_ci dev_err(d->dev, "direct command read ERR received\n"); 9062306a36Sopenharmony_ci /* Mask off the ACK value and clear status */ 9162306a36Sopenharmony_ci writel(val, d->regs + DSI_DIRECT_CMD_STS_CLR); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci val = readl(d->regs + DSI_CMD_MODE_STS_FLAG); 9462306a36Sopenharmony_ci if (val) 9562306a36Sopenharmony_ci dev_dbg(d->dev, "DSI_CMD_MODE_STS_FLAG = %08x\n", val); 9662306a36Sopenharmony_ci if (val & DSI_CMD_MODE_STS_ERR_NO_TE) 9762306a36Sopenharmony_ci /* This happens all the time (safe to ignore) */ 9862306a36Sopenharmony_ci dev_dbg(d->dev, "CMD mode no TE\n"); 9962306a36Sopenharmony_ci if (val & DSI_CMD_MODE_STS_ERR_TE_MISS) 10062306a36Sopenharmony_ci /* This happens all the time (safe to ignore) */ 10162306a36Sopenharmony_ci dev_dbg(d->dev, "CMD mode TE miss\n"); 10262306a36Sopenharmony_ci if (val & DSI_CMD_MODE_STS_ERR_SDI1_UNDERRUN) 10362306a36Sopenharmony_ci dev_err(d->dev, "CMD mode SD1 underrun\n"); 10462306a36Sopenharmony_ci if (val & DSI_CMD_MODE_STS_ERR_SDI2_UNDERRUN) 10562306a36Sopenharmony_ci dev_err(d->dev, "CMD mode SD2 underrun\n"); 10662306a36Sopenharmony_ci if (val & DSI_CMD_MODE_STS_ERR_UNWANTED_RD) 10762306a36Sopenharmony_ci dev_err(d->dev, "CMD mode unwanted RD\n"); 10862306a36Sopenharmony_ci writel(val, d->regs + DSI_CMD_MODE_STS_CLR); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci val = readl(d->regs + DSI_DIRECT_CMD_RD_STS_FLAG); 11162306a36Sopenharmony_ci if (val) 11262306a36Sopenharmony_ci dev_dbg(d->dev, "DSI_DIRECT_CMD_RD_STS_FLAG = %08x\n", val); 11362306a36Sopenharmony_ci writel(val, d->regs + DSI_DIRECT_CMD_RD_STS_CLR); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci val = readl(d->regs + DSI_TG_STS_FLAG); 11662306a36Sopenharmony_ci if (val) 11762306a36Sopenharmony_ci dev_dbg(d->dev, "DSI_TG_STS_FLAG = %08x\n", val); 11862306a36Sopenharmony_ci writel(val, d->regs + DSI_TG_STS_CLR); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci val = readl(d->regs + DSI_VID_MODE_STS_FLAG); 12162306a36Sopenharmony_ci if (val) 12262306a36Sopenharmony_ci dev_dbg(d->dev, "DSI_VID_MODE_STS_FLAG = %08x\n", val); 12362306a36Sopenharmony_ci if (val & DSI_VID_MODE_STS_VSG_RUNNING) 12462306a36Sopenharmony_ci dev_dbg(d->dev, "VID mode VSG running\n"); 12562306a36Sopenharmony_ci if (val & DSI_VID_MODE_STS_ERR_MISSING_DATA) 12662306a36Sopenharmony_ci dev_err(d->dev, "VID mode missing data\n"); 12762306a36Sopenharmony_ci if (val & DSI_VID_MODE_STS_ERR_MISSING_HSYNC) 12862306a36Sopenharmony_ci dev_err(d->dev, "VID mode missing HSYNC\n"); 12962306a36Sopenharmony_ci if (val & DSI_VID_MODE_STS_ERR_MISSING_VSYNC) 13062306a36Sopenharmony_ci dev_err(d->dev, "VID mode missing VSYNC\n"); 13162306a36Sopenharmony_ci if (val & DSI_VID_MODE_STS_REG_ERR_SMALL_LENGTH) 13262306a36Sopenharmony_ci dev_err(d->dev, "VID mode less bytes than expected between two HSYNC\n"); 13362306a36Sopenharmony_ci if (val & DSI_VID_MODE_STS_REG_ERR_SMALL_HEIGHT) 13462306a36Sopenharmony_ci dev_err(d->dev, "VID mode less lines than expected between two VSYNC\n"); 13562306a36Sopenharmony_ci if (val & (DSI_VID_MODE_STS_ERR_BURSTWRITE | 13662306a36Sopenharmony_ci DSI_VID_MODE_STS_ERR_LINEWRITE | 13762306a36Sopenharmony_ci DSI_VID_MODE_STS_ERR_LONGREAD)) 13862306a36Sopenharmony_ci dev_err(d->dev, "VID mode read/write error\n"); 13962306a36Sopenharmony_ci if (val & DSI_VID_MODE_STS_ERR_VRS_WRONG_LENGTH) 14062306a36Sopenharmony_ci dev_err(d->dev, "VID mode received packets differ from expected size\n"); 14162306a36Sopenharmony_ci if (val & DSI_VID_MODE_STS_VSG_RECOVERY) 14262306a36Sopenharmony_ci dev_err(d->dev, "VID mode VSG in recovery mode\n"); 14362306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_MODE_STS_CLR); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci return te_received; 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic void mcde_dsi_attach_to_mcde(struct mcde_dsi *d) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci d->mcde->mdsi = d->mdsi; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci /* 15362306a36Sopenharmony_ci * Select the way the DSI data flow is pushing to the display: 15462306a36Sopenharmony_ci * currently we just support video or command mode depending 15562306a36Sopenharmony_ci * on the type of display. Video mode defaults to using the 15662306a36Sopenharmony_ci * formatter itself for synchronization (stateless video panel). 15762306a36Sopenharmony_ci * 15862306a36Sopenharmony_ci * FIXME: add flags to struct mipi_dsi_device .flags to indicate 15962306a36Sopenharmony_ci * displays that require BTA (bus turn around) so we can handle 16062306a36Sopenharmony_ci * such displays as well. Figure out how to properly handle 16162306a36Sopenharmony_ci * single frame on-demand updates with DRM for command mode 16262306a36Sopenharmony_ci * displays (MCDE_COMMAND_ONESHOT_FLOW). 16362306a36Sopenharmony_ci */ 16462306a36Sopenharmony_ci if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) 16562306a36Sopenharmony_ci d->mcde->flow_mode = MCDE_VIDEO_FORMATTER_FLOW; 16662306a36Sopenharmony_ci else 16762306a36Sopenharmony_ci d->mcde->flow_mode = MCDE_COMMAND_TE_FLOW; 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic int mcde_dsi_host_attach(struct mipi_dsi_host *host, 17162306a36Sopenharmony_ci struct mipi_dsi_device *mdsi) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci struct mcde_dsi *d = host_to_mcde_dsi(host); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci if (mdsi->lanes < 1 || mdsi->lanes > 2) { 17662306a36Sopenharmony_ci DRM_ERROR("dsi device params invalid, 1 or 2 lanes supported\n"); 17762306a36Sopenharmony_ci return -EINVAL; 17862306a36Sopenharmony_ci } 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci dev_info(d->dev, "attached DSI device with %d lanes\n", mdsi->lanes); 18162306a36Sopenharmony_ci /* MIPI_DSI_FMT_RGB88 etc */ 18262306a36Sopenharmony_ci dev_info(d->dev, "format %08x, %dbpp\n", mdsi->format, 18362306a36Sopenharmony_ci mipi_dsi_pixel_format_to_bpp(mdsi->format)); 18462306a36Sopenharmony_ci dev_info(d->dev, "mode flags: %08lx\n", mdsi->mode_flags); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci d->mdsi = mdsi; 18762306a36Sopenharmony_ci if (d->mcde) 18862306a36Sopenharmony_ci mcde_dsi_attach_to_mcde(d); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci return 0; 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic int mcde_dsi_host_detach(struct mipi_dsi_host *host, 19462306a36Sopenharmony_ci struct mipi_dsi_device *mdsi) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci struct mcde_dsi *d = host_to_mcde_dsi(host); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci d->mdsi = NULL; 19962306a36Sopenharmony_ci if (d->mcde) 20062306a36Sopenharmony_ci d->mcde->mdsi = NULL; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci return 0; 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci#define MCDE_DSI_HOST_IS_READ(type) \ 20662306a36Sopenharmony_ci ((type == MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM) || \ 20762306a36Sopenharmony_ci (type == MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM) || \ 20862306a36Sopenharmony_ci (type == MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM) || \ 20962306a36Sopenharmony_ci (type == MIPI_DSI_DCS_READ)) 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_cistatic int mcde_dsi_execute_transfer(struct mcde_dsi *d, 21262306a36Sopenharmony_ci const struct mipi_dsi_msg *msg) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci const u32 loop_delay_us = 10; /* us */ 21562306a36Sopenharmony_ci u32 loop_counter; 21662306a36Sopenharmony_ci size_t txlen = msg->tx_len; 21762306a36Sopenharmony_ci size_t rxlen = msg->rx_len; 21862306a36Sopenharmony_ci int i; 21962306a36Sopenharmony_ci u32 val; 22062306a36Sopenharmony_ci int ret; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci writel(~0, d->regs + DSI_DIRECT_CMD_STS_CLR); 22362306a36Sopenharmony_ci writel(~0, d->regs + DSI_CMD_MODE_STS_CLR); 22462306a36Sopenharmony_ci /* Send command */ 22562306a36Sopenharmony_ci writel(1, d->regs + DSI_DIRECT_CMD_SEND); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci loop_counter = 1000 * 1000 / loop_delay_us; 22862306a36Sopenharmony_ci if (MCDE_DSI_HOST_IS_READ(msg->type)) { 22962306a36Sopenharmony_ci /* Read command */ 23062306a36Sopenharmony_ci while (!(readl(d->regs + DSI_DIRECT_CMD_STS) & 23162306a36Sopenharmony_ci (DSI_DIRECT_CMD_STS_READ_COMPLETED | 23262306a36Sopenharmony_ci DSI_DIRECT_CMD_STS_READ_COMPLETED_WITH_ERR)) 23362306a36Sopenharmony_ci && --loop_counter) 23462306a36Sopenharmony_ci usleep_range(loop_delay_us, (loop_delay_us * 3) / 2); 23562306a36Sopenharmony_ci if (!loop_counter) { 23662306a36Sopenharmony_ci dev_err(d->dev, "DSI read timeout!\n"); 23762306a36Sopenharmony_ci /* Set exit code and retry */ 23862306a36Sopenharmony_ci return -ETIME; 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci } else { 24162306a36Sopenharmony_ci /* Writing only */ 24262306a36Sopenharmony_ci while (!(readl(d->regs + DSI_DIRECT_CMD_STS) & 24362306a36Sopenharmony_ci DSI_DIRECT_CMD_STS_WRITE_COMPLETED) 24462306a36Sopenharmony_ci && --loop_counter) 24562306a36Sopenharmony_ci usleep_range(loop_delay_us, (loop_delay_us * 3) / 2); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci if (!loop_counter) { 24862306a36Sopenharmony_ci /* Set exit code and retry */ 24962306a36Sopenharmony_ci dev_err(d->dev, "DSI write timeout!\n"); 25062306a36Sopenharmony_ci return -ETIME; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci val = readl(d->regs + DSI_DIRECT_CMD_STS); 25562306a36Sopenharmony_ci if (val & DSI_DIRECT_CMD_STS_READ_COMPLETED_WITH_ERR) { 25662306a36Sopenharmony_ci dev_err(d->dev, "read completed with error\n"); 25762306a36Sopenharmony_ci writel(1, d->regs + DSI_DIRECT_CMD_RD_INIT); 25862306a36Sopenharmony_ci return -EIO; 25962306a36Sopenharmony_ci } 26062306a36Sopenharmony_ci if (val & DSI_DIRECT_CMD_STS_ACKNOWLEDGE_WITH_ERR_RECEIVED) { 26162306a36Sopenharmony_ci val >>= DSI_DIRECT_CMD_STS_ACK_VAL_SHIFT; 26262306a36Sopenharmony_ci dev_err(d->dev, "error during transmission: %04x\n", 26362306a36Sopenharmony_ci val); 26462306a36Sopenharmony_ci return -EIO; 26562306a36Sopenharmony_ci } 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci if (!MCDE_DSI_HOST_IS_READ(msg->type)) { 26862306a36Sopenharmony_ci /* Return number of bytes written */ 26962306a36Sopenharmony_ci ret = txlen; 27062306a36Sopenharmony_ci } else { 27162306a36Sopenharmony_ci /* OK this is a read command, get the response */ 27262306a36Sopenharmony_ci u32 rdsz; 27362306a36Sopenharmony_ci u32 rddat; 27462306a36Sopenharmony_ci u8 *rx = msg->rx_buf; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci rdsz = readl(d->regs + DSI_DIRECT_CMD_RD_PROPERTY); 27762306a36Sopenharmony_ci rdsz &= DSI_DIRECT_CMD_RD_PROPERTY_RD_SIZE_MASK; 27862306a36Sopenharmony_ci rddat = readl(d->regs + DSI_DIRECT_CMD_RDDAT); 27962306a36Sopenharmony_ci if (rdsz < rxlen) { 28062306a36Sopenharmony_ci dev_err(d->dev, "read error, requested %zd got %d\n", 28162306a36Sopenharmony_ci rxlen, rdsz); 28262306a36Sopenharmony_ci return -EIO; 28362306a36Sopenharmony_ci } 28462306a36Sopenharmony_ci /* FIXME: read more than 4 bytes */ 28562306a36Sopenharmony_ci for (i = 0; i < 4 && i < rxlen; i++) 28662306a36Sopenharmony_ci rx[i] = (rddat >> (i * 8)) & 0xff; 28762306a36Sopenharmony_ci ret = rdsz; 28862306a36Sopenharmony_ci } 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci /* Successful transmission */ 29162306a36Sopenharmony_ci return ret; 29262306a36Sopenharmony_ci} 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_cistatic ssize_t mcde_dsi_host_transfer(struct mipi_dsi_host *host, 29562306a36Sopenharmony_ci const struct mipi_dsi_msg *msg) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci struct mcde_dsi *d = host_to_mcde_dsi(host); 29862306a36Sopenharmony_ci const u8 *tx = msg->tx_buf; 29962306a36Sopenharmony_ci size_t txlen = msg->tx_len; 30062306a36Sopenharmony_ci size_t rxlen = msg->rx_len; 30162306a36Sopenharmony_ci unsigned int retries = 0; 30262306a36Sopenharmony_ci u32 val; 30362306a36Sopenharmony_ci int ret; 30462306a36Sopenharmony_ci int i; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci if (txlen > 16) { 30762306a36Sopenharmony_ci dev_err(d->dev, 30862306a36Sopenharmony_ci "dunno how to write more than 16 bytes yet\n"); 30962306a36Sopenharmony_ci return -EIO; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci if (rxlen > 4) { 31262306a36Sopenharmony_ci dev_err(d->dev, 31362306a36Sopenharmony_ci "dunno how to read more than 4 bytes yet\n"); 31462306a36Sopenharmony_ci return -EIO; 31562306a36Sopenharmony_ci } 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci dev_dbg(d->dev, 31862306a36Sopenharmony_ci "message to channel %d, write %zd bytes read %zd bytes\n", 31962306a36Sopenharmony_ci msg->channel, txlen, rxlen); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci /* Command "nature" */ 32262306a36Sopenharmony_ci if (MCDE_DSI_HOST_IS_READ(msg->type)) 32362306a36Sopenharmony_ci /* MCTL_MAIN_DATA_CTL already set up */ 32462306a36Sopenharmony_ci val = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_READ; 32562306a36Sopenharmony_ci else 32662306a36Sopenharmony_ci val = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_WRITE; 32762306a36Sopenharmony_ci /* 32862306a36Sopenharmony_ci * More than 2 bytes will not fit in a single packet, so it's 32962306a36Sopenharmony_ci * time to set the "long not short" bit. One byte is used by 33062306a36Sopenharmony_ci * the MIPI DCS command leaving just one byte for the payload 33162306a36Sopenharmony_ci * in a short package. 33262306a36Sopenharmony_ci */ 33362306a36Sopenharmony_ci if (mipi_dsi_packet_format_is_long(msg->type)) 33462306a36Sopenharmony_ci val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LONGNOTSHORT; 33562306a36Sopenharmony_ci val |= 0 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT; 33662306a36Sopenharmony_ci val |= txlen << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT; 33762306a36Sopenharmony_ci val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN; 33862306a36Sopenharmony_ci val |= msg->type << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT; 33962306a36Sopenharmony_ci writel(val, d->regs + DSI_DIRECT_CMD_MAIN_SETTINGS); 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci /* MIPI DCS command is part of the data */ 34262306a36Sopenharmony_ci if (txlen > 0) { 34362306a36Sopenharmony_ci val = 0; 34462306a36Sopenharmony_ci for (i = 0; i < 4 && i < txlen; i++) 34562306a36Sopenharmony_ci val |= tx[i] << (i * 8); 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci writel(val, d->regs + DSI_DIRECT_CMD_WRDAT0); 34862306a36Sopenharmony_ci if (txlen > 4) { 34962306a36Sopenharmony_ci val = 0; 35062306a36Sopenharmony_ci for (i = 0; i < 4 && (i + 4) < txlen; i++) 35162306a36Sopenharmony_ci val |= tx[i + 4] << (i * 8); 35262306a36Sopenharmony_ci writel(val, d->regs + DSI_DIRECT_CMD_WRDAT1); 35362306a36Sopenharmony_ci } 35462306a36Sopenharmony_ci if (txlen > 8) { 35562306a36Sopenharmony_ci val = 0; 35662306a36Sopenharmony_ci for (i = 0; i < 4 && (i + 8) < txlen; i++) 35762306a36Sopenharmony_ci val |= tx[i + 8] << (i * 8); 35862306a36Sopenharmony_ci writel(val, d->regs + DSI_DIRECT_CMD_WRDAT2); 35962306a36Sopenharmony_ci } 36062306a36Sopenharmony_ci if (txlen > 12) { 36162306a36Sopenharmony_ci val = 0; 36262306a36Sopenharmony_ci for (i = 0; i < 4 && (i + 12) < txlen; i++) 36362306a36Sopenharmony_ci val |= tx[i + 12] << (i * 8); 36462306a36Sopenharmony_ci writel(val, d->regs + DSI_DIRECT_CMD_WRDAT3); 36562306a36Sopenharmony_ci } 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci while (retries < 3) { 36862306a36Sopenharmony_ci ret = mcde_dsi_execute_transfer(d, msg); 36962306a36Sopenharmony_ci if (ret >= 0) 37062306a36Sopenharmony_ci break; 37162306a36Sopenharmony_ci retries++; 37262306a36Sopenharmony_ci } 37362306a36Sopenharmony_ci if (ret < 0 && retries) 37462306a36Sopenharmony_ci dev_err(d->dev, "gave up after %d retries\n", retries); 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci /* Clear any errors */ 37762306a36Sopenharmony_ci writel(~0, d->regs + DSI_DIRECT_CMD_STS_CLR); 37862306a36Sopenharmony_ci writel(~0, d->regs + DSI_CMD_MODE_STS_CLR); 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci return ret; 38162306a36Sopenharmony_ci} 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_cistatic const struct mipi_dsi_host_ops mcde_dsi_host_ops = { 38462306a36Sopenharmony_ci .attach = mcde_dsi_host_attach, 38562306a36Sopenharmony_ci .detach = mcde_dsi_host_detach, 38662306a36Sopenharmony_ci .transfer = mcde_dsi_host_transfer, 38762306a36Sopenharmony_ci}; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci/* This sends a direct (short) command to request TE */ 39062306a36Sopenharmony_civoid mcde_dsi_te_request(struct mipi_dsi_device *mdsi) 39162306a36Sopenharmony_ci{ 39262306a36Sopenharmony_ci struct mcde_dsi *d; 39362306a36Sopenharmony_ci u32 val; 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci d = host_to_mcde_dsi(mdsi->host); 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci /* Command "nature" TE request */ 39862306a36Sopenharmony_ci val = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_TE_REQ; 39962306a36Sopenharmony_ci val |= 0 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT; 40062306a36Sopenharmony_ci val |= 2 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT; 40162306a36Sopenharmony_ci val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN; 40262306a36Sopenharmony_ci val |= MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM << 40362306a36Sopenharmony_ci DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT; 40462306a36Sopenharmony_ci writel(val, d->regs + DSI_DIRECT_CMD_MAIN_SETTINGS); 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci /* Clear TE reveived and error status bits and enables them */ 40762306a36Sopenharmony_ci writel(DSI_DIRECT_CMD_STS_CLR_TE_RECEIVED_CLR | 40862306a36Sopenharmony_ci DSI_DIRECT_CMD_STS_CLR_ACKNOWLEDGE_WITH_ERR_RECEIVED_CLR, 40962306a36Sopenharmony_ci d->regs + DSI_DIRECT_CMD_STS_CLR); 41062306a36Sopenharmony_ci val = readl(d->regs + DSI_DIRECT_CMD_STS_CTL); 41162306a36Sopenharmony_ci val |= DSI_DIRECT_CMD_STS_CTL_TE_RECEIVED_EN; 41262306a36Sopenharmony_ci val |= DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_WITH_ERR_EN; 41362306a36Sopenharmony_ci writel(val, d->regs + DSI_DIRECT_CMD_STS_CTL); 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci /* Clear and enable no TE or TE missing status */ 41662306a36Sopenharmony_ci writel(DSI_CMD_MODE_STS_CLR_ERR_NO_TE_CLR | 41762306a36Sopenharmony_ci DSI_CMD_MODE_STS_CLR_ERR_TE_MISS_CLR, 41862306a36Sopenharmony_ci d->regs + DSI_CMD_MODE_STS_CLR); 41962306a36Sopenharmony_ci val = readl(d->regs + DSI_CMD_MODE_STS_CTL); 42062306a36Sopenharmony_ci val |= DSI_CMD_MODE_STS_CTL_ERR_NO_TE_EN; 42162306a36Sopenharmony_ci val |= DSI_CMD_MODE_STS_CTL_ERR_TE_MISS_EN; 42262306a36Sopenharmony_ci writel(val, d->regs + DSI_CMD_MODE_STS_CTL); 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci /* Send this TE request command */ 42562306a36Sopenharmony_ci writel(1, d->regs + DSI_DIRECT_CMD_SEND); 42662306a36Sopenharmony_ci} 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_cistatic void mcde_dsi_setup_video_mode(struct mcde_dsi *d, 42962306a36Sopenharmony_ci const struct drm_display_mode *mode) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci /* cpp, characters per pixel, number of bytes per pixel */ 43262306a36Sopenharmony_ci u8 cpp = mipi_dsi_pixel_format_to_bpp(d->mdsi->format) / 8; 43362306a36Sopenharmony_ci u64 pclk; 43462306a36Sopenharmony_ci u64 bpl; 43562306a36Sopenharmony_ci int hfp; 43662306a36Sopenharmony_ci int hbp; 43762306a36Sopenharmony_ci int hsa; 43862306a36Sopenharmony_ci u32 blkline_pck, line_duration; 43962306a36Sopenharmony_ci u32 val; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci val = 0; 44262306a36Sopenharmony_ci if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) 44362306a36Sopenharmony_ci val |= DSI_VID_MAIN_CTL_BURST_MODE; 44462306a36Sopenharmony_ci if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { 44562306a36Sopenharmony_ci val |= DSI_VID_MAIN_CTL_SYNC_PULSE_ACTIVE; 44662306a36Sopenharmony_ci val |= DSI_VID_MAIN_CTL_SYNC_PULSE_HORIZONTAL; 44762306a36Sopenharmony_ci } 44862306a36Sopenharmony_ci /* RGB header and pixel mode */ 44962306a36Sopenharmony_ci switch (d->mdsi->format) { 45062306a36Sopenharmony_ci case MIPI_DSI_FMT_RGB565: 45162306a36Sopenharmony_ci val |= MIPI_DSI_PACKED_PIXEL_STREAM_16 << 45262306a36Sopenharmony_ci DSI_VID_MAIN_CTL_HEADER_SHIFT; 45362306a36Sopenharmony_ci val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_16BITS; 45462306a36Sopenharmony_ci break; 45562306a36Sopenharmony_ci case MIPI_DSI_FMT_RGB666_PACKED: 45662306a36Sopenharmony_ci val |= MIPI_DSI_PACKED_PIXEL_STREAM_18 << 45762306a36Sopenharmony_ci DSI_VID_MAIN_CTL_HEADER_SHIFT; 45862306a36Sopenharmony_ci val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS; 45962306a36Sopenharmony_ci break; 46062306a36Sopenharmony_ci case MIPI_DSI_FMT_RGB666: 46162306a36Sopenharmony_ci val |= MIPI_DSI_PIXEL_STREAM_3BYTE_18 46262306a36Sopenharmony_ci << DSI_VID_MAIN_CTL_HEADER_SHIFT; 46362306a36Sopenharmony_ci val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS_LOOSE; 46462306a36Sopenharmony_ci break; 46562306a36Sopenharmony_ci case MIPI_DSI_FMT_RGB888: 46662306a36Sopenharmony_ci val |= MIPI_DSI_PACKED_PIXEL_STREAM_24 << 46762306a36Sopenharmony_ci DSI_VID_MAIN_CTL_HEADER_SHIFT; 46862306a36Sopenharmony_ci val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_24BITS; 46962306a36Sopenharmony_ci break; 47062306a36Sopenharmony_ci default: 47162306a36Sopenharmony_ci dev_err(d->dev, "unknown pixel mode\n"); 47262306a36Sopenharmony_ci return; 47362306a36Sopenharmony_ci } 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci /* TODO: TVG (test video generator) could be enabled here */ 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci /* 47862306a36Sopenharmony_ci * During vertical blanking: go to LP mode 47962306a36Sopenharmony_ci * Like with the EOL setting, if this is not set, the EOL area will be 48062306a36Sopenharmony_ci * filled with NULL or blanking packets in the vblank area. 48162306a36Sopenharmony_ci * FIXME: some Samsung phones and display panels such as s6e63m0 use 48262306a36Sopenharmony_ci * DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_BLANKING here instead, 48362306a36Sopenharmony_ci * figure out how to properly configure that from the panel. 48462306a36Sopenharmony_ci */ 48562306a36Sopenharmony_ci val |= DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_LP_0; 48662306a36Sopenharmony_ci /* 48762306a36Sopenharmony_ci * During EOL: go to LP mode. If this is not set, the EOL area will be 48862306a36Sopenharmony_ci * filled with NULL or blanking packets. 48962306a36Sopenharmony_ci */ 49062306a36Sopenharmony_ci val |= DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0; 49162306a36Sopenharmony_ci /* Recovery mode 1 */ 49262306a36Sopenharmony_ci val |= 1 << DSI_VID_MAIN_CTL_RECOVERY_MODE_SHIFT; 49362306a36Sopenharmony_ci /* All other fields zero */ 49462306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_MAIN_CTL); 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci /* Vertical frame parameters are pretty straight-forward */ 49762306a36Sopenharmony_ci val = mode->vdisplay << DSI_VID_VSIZE_VACT_LENGTH_SHIFT; 49862306a36Sopenharmony_ci /* vertical front porch */ 49962306a36Sopenharmony_ci val |= (mode->vsync_start - mode->vdisplay) 50062306a36Sopenharmony_ci << DSI_VID_VSIZE_VFP_LENGTH_SHIFT; 50162306a36Sopenharmony_ci /* vertical sync active */ 50262306a36Sopenharmony_ci val |= (mode->vsync_end - mode->vsync_start) 50362306a36Sopenharmony_ci << DSI_VID_VSIZE_VSA_LENGTH_SHIFT; 50462306a36Sopenharmony_ci /* vertical back porch */ 50562306a36Sopenharmony_ci val |= (mode->vtotal - mode->vsync_end) 50662306a36Sopenharmony_ci << DSI_VID_VSIZE_VBP_LENGTH_SHIFT; 50762306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_VSIZE); 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci /* 51062306a36Sopenharmony_ci * Horizontal frame parameters: 51162306a36Sopenharmony_ci * horizontal resolution is given in pixels but must be re-calculated 51262306a36Sopenharmony_ci * into bytes since this is what the hardware expects, these registers 51362306a36Sopenharmony_ci * define the payload size of the packet. 51462306a36Sopenharmony_ci * 51562306a36Sopenharmony_ci * hfp = horizontal front porch in bytes 51662306a36Sopenharmony_ci * hbp = horizontal back porch in bytes 51762306a36Sopenharmony_ci * hsa = horizontal sync active in bytes 51862306a36Sopenharmony_ci * 51962306a36Sopenharmony_ci * 6 + 2 is HFP header + checksum 52062306a36Sopenharmony_ci */ 52162306a36Sopenharmony_ci hfp = (mode->hsync_start - mode->hdisplay) * cpp - 6 - 2; 52262306a36Sopenharmony_ci if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { 52362306a36Sopenharmony_ci /* 52462306a36Sopenharmony_ci * Use sync pulse for sync: explicit HSA time 52562306a36Sopenharmony_ci * 6 is HBP header + checksum 52662306a36Sopenharmony_ci * 4 is RGB header + checksum 52762306a36Sopenharmony_ci */ 52862306a36Sopenharmony_ci hbp = (mode->htotal - mode->hsync_end) * cpp - 4 - 6; 52962306a36Sopenharmony_ci /* 53062306a36Sopenharmony_ci * 6 is HBP header + checksum 53162306a36Sopenharmony_ci * 4 is HSW packet bytes 53262306a36Sopenharmony_ci * 4 is RGB header + checksum 53362306a36Sopenharmony_ci */ 53462306a36Sopenharmony_ci hsa = (mode->hsync_end - mode->hsync_start) * cpp - 4 - 4 - 6; 53562306a36Sopenharmony_ci } else { 53662306a36Sopenharmony_ci /* 53762306a36Sopenharmony_ci * Use event for sync: HBP includes both back porch and sync 53862306a36Sopenharmony_ci * 6 is HBP header + checksum 53962306a36Sopenharmony_ci * 4 is HSW packet bytes 54062306a36Sopenharmony_ci * 4 is RGB header + checksum 54162306a36Sopenharmony_ci */ 54262306a36Sopenharmony_ci hbp = (mode->htotal - mode->hsync_start) * cpp - 4 - 4 - 6; 54362306a36Sopenharmony_ci /* HSA is not present in this mode and set to 0 */ 54462306a36Sopenharmony_ci hsa = 0; 54562306a36Sopenharmony_ci } 54662306a36Sopenharmony_ci if (hfp < 0) { 54762306a36Sopenharmony_ci dev_info(d->dev, "hfp negative, set to 0\n"); 54862306a36Sopenharmony_ci hfp = 0; 54962306a36Sopenharmony_ci } 55062306a36Sopenharmony_ci if (hbp < 0) { 55162306a36Sopenharmony_ci dev_info(d->dev, "hbp negative, set to 0\n"); 55262306a36Sopenharmony_ci hbp = 0; 55362306a36Sopenharmony_ci } 55462306a36Sopenharmony_ci if (hsa < 0) { 55562306a36Sopenharmony_ci dev_info(d->dev, "hsa negative, set to 0\n"); 55662306a36Sopenharmony_ci hsa = 0; 55762306a36Sopenharmony_ci } 55862306a36Sopenharmony_ci dev_dbg(d->dev, "hfp: %u, hbp: %u, hsa: %u bytes\n", 55962306a36Sopenharmony_ci hfp, hbp, hsa); 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_ci /* Frame parameters: horizontal sync active */ 56262306a36Sopenharmony_ci val = hsa << DSI_VID_HSIZE1_HSA_LENGTH_SHIFT; 56362306a36Sopenharmony_ci /* horizontal back porch */ 56462306a36Sopenharmony_ci val |= hbp << DSI_VID_HSIZE1_HBP_LENGTH_SHIFT; 56562306a36Sopenharmony_ci /* horizontal front porch */ 56662306a36Sopenharmony_ci val |= hfp << DSI_VID_HSIZE1_HFP_LENGTH_SHIFT; 56762306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_HSIZE1); 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci /* RGB data length (visible bytes on one scanline) */ 57062306a36Sopenharmony_ci val = mode->hdisplay * cpp; 57162306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_HSIZE2); 57262306a36Sopenharmony_ci dev_dbg(d->dev, "RGB length, visible area on a line: %u bytes\n", val); 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci /* 57562306a36Sopenharmony_ci * Calculate the time between two pixels in picoseconds using 57662306a36Sopenharmony_ci * the supplied refresh rate and total resolution including 57762306a36Sopenharmony_ci * porches and sync. 57862306a36Sopenharmony_ci */ 57962306a36Sopenharmony_ci /* (ps/s) / (pixels/s) = ps/pixels */ 58062306a36Sopenharmony_ci pclk = DIV_ROUND_UP_ULL(1000000000000, (mode->clock * 1000)); 58162306a36Sopenharmony_ci dev_dbg(d->dev, "picoseconds between two pixels: %llu\n", 58262306a36Sopenharmony_ci pclk); 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci /* 58562306a36Sopenharmony_ci * How many bytes per line will this update frequency yield? 58662306a36Sopenharmony_ci * 58762306a36Sopenharmony_ci * Calculate the number of picoseconds for one scanline (1), then 58862306a36Sopenharmony_ci * divide by 1000000000000 (2) to get in pixels per second we 58962306a36Sopenharmony_ci * want to output. 59062306a36Sopenharmony_ci * 59162306a36Sopenharmony_ci * Multiply with number of bytes per second at this video display 59262306a36Sopenharmony_ci * frequency (3) to get number of bytes transferred during this 59362306a36Sopenharmony_ci * time. Notice that we use the frequency the display wants, 59462306a36Sopenharmony_ci * not what we actually get from the DSI PLL, which is hs_freq. 59562306a36Sopenharmony_ci * 59662306a36Sopenharmony_ci * These arithmetics are done in a different order to avoid 59762306a36Sopenharmony_ci * overflow. 59862306a36Sopenharmony_ci */ 59962306a36Sopenharmony_ci bpl = pclk * mode->htotal; /* (1) picoseconds per line */ 60062306a36Sopenharmony_ci dev_dbg(d->dev, "picoseconds per line: %llu\n", bpl); 60162306a36Sopenharmony_ci /* Multiply with bytes per second (3) */ 60262306a36Sopenharmony_ci bpl *= (d->mdsi->hs_rate / 8); 60362306a36Sopenharmony_ci /* Pixels per second (2) */ 60462306a36Sopenharmony_ci bpl = DIV_ROUND_DOWN_ULL(bpl, 1000000); /* microseconds */ 60562306a36Sopenharmony_ci bpl = DIV_ROUND_DOWN_ULL(bpl, 1000000); /* seconds */ 60662306a36Sopenharmony_ci /* parallel transactions in all lanes */ 60762306a36Sopenharmony_ci bpl *= d->mdsi->lanes; 60862306a36Sopenharmony_ci dev_dbg(d->dev, 60962306a36Sopenharmony_ci "calculated bytes per line: %llu @ %d Hz with HS %lu Hz\n", 61062306a36Sopenharmony_ci bpl, drm_mode_vrefresh(mode), d->mdsi->hs_rate); 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci /* 61362306a36Sopenharmony_ci * 6 is header + checksum, header = 4 bytes, checksum = 2 bytes 61462306a36Sopenharmony_ci * 4 is short packet for vsync/hsync 61562306a36Sopenharmony_ci */ 61662306a36Sopenharmony_ci if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { 61762306a36Sopenharmony_ci /* Set the event packet size to 0 (not used) */ 61862306a36Sopenharmony_ci writel(0, d->regs + DSI_VID_BLKSIZE1); 61962306a36Sopenharmony_ci /* 62062306a36Sopenharmony_ci * FIXME: isn't the hsync width in pixels? The porch and 62162306a36Sopenharmony_ci * sync area size is in pixels here, but this -6 62262306a36Sopenharmony_ci * seems to be for bytes. It looks like this in the vendor 62362306a36Sopenharmony_ci * code though. Is it completely untested? 62462306a36Sopenharmony_ci */ 62562306a36Sopenharmony_ci blkline_pck = bpl - (mode->hsync_end - mode->hsync_start) - 6; 62662306a36Sopenharmony_ci val = blkline_pck << DSI_VID_BLKSIZE2_BLKLINE_PULSE_PCK_SHIFT; 62762306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_BLKSIZE2); 62862306a36Sopenharmony_ci } else { 62962306a36Sopenharmony_ci /* Set the sync pulse packet size to 0 (not used) */ 63062306a36Sopenharmony_ci writel(0, d->regs + DSI_VID_BLKSIZE2); 63162306a36Sopenharmony_ci /* Specifying payload size in bytes (-4-6 from manual) */ 63262306a36Sopenharmony_ci blkline_pck = bpl - 4 - 6; 63362306a36Sopenharmony_ci if (blkline_pck > 0x1FFF) 63462306a36Sopenharmony_ci dev_err(d->dev, "blkline_pck too big %d bytes\n", 63562306a36Sopenharmony_ci blkline_pck); 63662306a36Sopenharmony_ci val = blkline_pck << DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_SHIFT; 63762306a36Sopenharmony_ci val &= DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_MASK; 63862306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_BLKSIZE1); 63962306a36Sopenharmony_ci } 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci /* 64262306a36Sopenharmony_ci * The line duration is used to scale back the frequency from 64362306a36Sopenharmony_ci * the max frequency supported by the HS clock to the desired 64462306a36Sopenharmony_ci * update frequency in vrefresh. 64562306a36Sopenharmony_ci */ 64662306a36Sopenharmony_ci line_duration = blkline_pck + 6; 64762306a36Sopenharmony_ci /* 64862306a36Sopenharmony_ci * The datasheet contains this complex condition to decreasing 64962306a36Sopenharmony_ci * the line duration by 1 under very specific circumstances. 65062306a36Sopenharmony_ci * Here we also imply that LP is used during burst EOL. 65162306a36Sopenharmony_ci */ 65262306a36Sopenharmony_ci if (d->mdsi->lanes == 2 && (hsa & 0x01) && (hfp & 0x01) 65362306a36Sopenharmony_ci && (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)) 65462306a36Sopenharmony_ci line_duration--; 65562306a36Sopenharmony_ci line_duration = DIV_ROUND_CLOSEST(line_duration, d->mdsi->lanes); 65662306a36Sopenharmony_ci dev_dbg(d->dev, "line duration %u bytes\n", line_duration); 65762306a36Sopenharmony_ci val = line_duration << DSI_VID_DPHY_TIME_REG_LINE_DURATION_SHIFT; 65862306a36Sopenharmony_ci /* 65962306a36Sopenharmony_ci * This is the time to perform LP->HS on D-PHY 66062306a36Sopenharmony_ci * FIXME: nowhere to get this from: DT property on the DSI? 66162306a36Sopenharmony_ci * The manual says this is "system dependent". 66262306a36Sopenharmony_ci * values like 48 and 72 seen in the vendor code. 66362306a36Sopenharmony_ci */ 66462306a36Sopenharmony_ci val |= 48 << DSI_VID_DPHY_TIME_REG_WAKEUP_TIME_SHIFT; 66562306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_DPHY_TIME); 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci /* 66862306a36Sopenharmony_ci * See the manual figure 657 page 2203 for understanding the impact 66962306a36Sopenharmony_ci * of the different burst mode settings. 67062306a36Sopenharmony_ci */ 67162306a36Sopenharmony_ci if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) { 67262306a36Sopenharmony_ci int blkeol_pck, blkeol_duration; 67362306a36Sopenharmony_ci /* 67462306a36Sopenharmony_ci * Packet size at EOL for burst mode, this is only used 67562306a36Sopenharmony_ci * if DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0 is NOT set, 67662306a36Sopenharmony_ci * but we instead send NULL or blanking packets at EOL. 67762306a36Sopenharmony_ci * This is given in number of bytes. 67862306a36Sopenharmony_ci * 67962306a36Sopenharmony_ci * See the manual page 2198 for the 13 reg_blkeol_pck bits. 68062306a36Sopenharmony_ci */ 68162306a36Sopenharmony_ci blkeol_pck = bpl - (mode->htotal * cpp) - 6; 68262306a36Sopenharmony_ci if (blkeol_pck < 0) { 68362306a36Sopenharmony_ci dev_err(d->dev, "video block does not fit on line!\n"); 68462306a36Sopenharmony_ci dev_err(d->dev, 68562306a36Sopenharmony_ci "calculated bytes per line: %llu @ %d Hz\n", 68662306a36Sopenharmony_ci bpl, drm_mode_vrefresh(mode)); 68762306a36Sopenharmony_ci dev_err(d->dev, 68862306a36Sopenharmony_ci "bytes per line (blkline_pck) %u bytes\n", 68962306a36Sopenharmony_ci blkline_pck); 69062306a36Sopenharmony_ci dev_err(d->dev, 69162306a36Sopenharmony_ci "blkeol_pck becomes %d bytes\n", blkeol_pck); 69262306a36Sopenharmony_ci return; 69362306a36Sopenharmony_ci } 69462306a36Sopenharmony_ci dev_dbg(d->dev, "BLKEOL packet: %d bytes\n", blkeol_pck); 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_ci val = readl(d->regs + DSI_VID_BLKSIZE1); 69762306a36Sopenharmony_ci val &= ~DSI_VID_BLKSIZE1_BLKEOL_PCK_MASK; 69862306a36Sopenharmony_ci val |= blkeol_pck << DSI_VID_BLKSIZE1_BLKEOL_PCK_SHIFT; 69962306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_BLKSIZE1); 70062306a36Sopenharmony_ci /* Use the same value for exact burst limit */ 70162306a36Sopenharmony_ci val = blkeol_pck << 70262306a36Sopenharmony_ci DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_SHIFT; 70362306a36Sopenharmony_ci val &= DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_MASK; 70462306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_VCA_SETTING2); 70562306a36Sopenharmony_ci /* 70662306a36Sopenharmony_ci * This BLKEOL duration is claimed to be the duration in clock 70762306a36Sopenharmony_ci * cycles of the BLLP end-of-line (EOL) period for each line if 70862306a36Sopenharmony_ci * DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0 is set. 70962306a36Sopenharmony_ci * 71062306a36Sopenharmony_ci * It is hard to trust the manuals' claim that this is in clock 71162306a36Sopenharmony_ci * cycles as we mimic the behaviour of the vendor code, which 71262306a36Sopenharmony_ci * appears to write a number of bytes that would have been 71362306a36Sopenharmony_ci * transferred on a single lane. 71462306a36Sopenharmony_ci * 71562306a36Sopenharmony_ci * See the manual figure 657 page 2203 and page 2198 for the 13 71662306a36Sopenharmony_ci * reg_blkeol_duration bits. 71762306a36Sopenharmony_ci * 71862306a36Sopenharmony_ci * FIXME: should this also be set up also for non-burst mode 71962306a36Sopenharmony_ci * according to figure 565 page 2202? 72062306a36Sopenharmony_ci */ 72162306a36Sopenharmony_ci blkeol_duration = DIV_ROUND_CLOSEST(blkeol_pck + 6, 72262306a36Sopenharmony_ci d->mdsi->lanes); 72362306a36Sopenharmony_ci dev_dbg(d->dev, "BLKEOL duration: %d clock cycles\n", 72462306a36Sopenharmony_ci blkeol_duration); 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ci val = readl(d->regs + DSI_VID_PCK_TIME); 72762306a36Sopenharmony_ci val &= ~DSI_VID_PCK_TIME_BLKEOL_DURATION_MASK; 72862306a36Sopenharmony_ci val |= blkeol_duration << 72962306a36Sopenharmony_ci DSI_VID_PCK_TIME_BLKEOL_DURATION_SHIFT; 73062306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_PCK_TIME); 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci /* Max burst limit, this is given in bytes */ 73362306a36Sopenharmony_ci val = readl(d->regs + DSI_VID_VCA_SETTING1); 73462306a36Sopenharmony_ci val &= ~DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_MASK; 73562306a36Sopenharmony_ci val |= (blkeol_pck - 6) << 73662306a36Sopenharmony_ci DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_SHIFT; 73762306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_VCA_SETTING1); 73862306a36Sopenharmony_ci } 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_ci /* Maximum line limit */ 74162306a36Sopenharmony_ci val = readl(d->regs + DSI_VID_VCA_SETTING2); 74262306a36Sopenharmony_ci val &= ~DSI_VID_VCA_SETTING2_MAX_LINE_LIMIT_MASK; 74362306a36Sopenharmony_ci val |= (blkline_pck - 6) << 74462306a36Sopenharmony_ci DSI_VID_VCA_SETTING2_MAX_LINE_LIMIT_SHIFT; 74562306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_VCA_SETTING2); 74662306a36Sopenharmony_ci dev_dbg(d->dev, "blkline pck: %d bytes\n", blkline_pck - 6); 74762306a36Sopenharmony_ci} 74862306a36Sopenharmony_ci 74962306a36Sopenharmony_cistatic void mcde_dsi_start(struct mcde_dsi *d) 75062306a36Sopenharmony_ci{ 75162306a36Sopenharmony_ci unsigned long hs_freq; 75262306a36Sopenharmony_ci u32 val; 75362306a36Sopenharmony_ci int i; 75462306a36Sopenharmony_ci 75562306a36Sopenharmony_ci /* No integration mode */ 75662306a36Sopenharmony_ci writel(0, d->regs + DSI_MCTL_INTEGRATION_MODE); 75762306a36Sopenharmony_ci 75862306a36Sopenharmony_ci /* Enable the DSI port, from drivers/video/mcde/dsilink_v2.c */ 75962306a36Sopenharmony_ci val = DSI_MCTL_MAIN_DATA_CTL_LINK_EN | 76062306a36Sopenharmony_ci DSI_MCTL_MAIN_DATA_CTL_BTA_EN | 76162306a36Sopenharmony_ci DSI_MCTL_MAIN_DATA_CTL_READ_EN | 76262306a36Sopenharmony_ci DSI_MCTL_MAIN_DATA_CTL_REG_TE_EN; 76362306a36Sopenharmony_ci if (!(d->mdsi->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)) 76462306a36Sopenharmony_ci val |= DSI_MCTL_MAIN_DATA_CTL_HOST_EOT_GEN; 76562306a36Sopenharmony_ci writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL); 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ci /* Set a high command timeout, clear other fields */ 76862306a36Sopenharmony_ci val = 0x3ff << DSI_CMD_MODE_CTL_TE_TIMEOUT_SHIFT; 76962306a36Sopenharmony_ci writel(val, d->regs + DSI_CMD_MODE_CTL); 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_ci /* 77262306a36Sopenharmony_ci * UI_X4 is described as "unit interval times four" 77362306a36Sopenharmony_ci * I guess since DSI packets are 4 bytes wide, one unit 77462306a36Sopenharmony_ci * is one byte. 77562306a36Sopenharmony_ci */ 77662306a36Sopenharmony_ci hs_freq = clk_get_rate(d->hs_clk); 77762306a36Sopenharmony_ci hs_freq /= 1000000; /* MHz */ 77862306a36Sopenharmony_ci val = 4000 / hs_freq; 77962306a36Sopenharmony_ci dev_dbg(d->dev, "UI value: %d\n", val); 78062306a36Sopenharmony_ci val <<= DSI_MCTL_DPHY_STATIC_UI_X4_SHIFT; 78162306a36Sopenharmony_ci val &= DSI_MCTL_DPHY_STATIC_UI_X4_MASK; 78262306a36Sopenharmony_ci writel(val, d->regs + DSI_MCTL_DPHY_STATIC); 78362306a36Sopenharmony_ci 78462306a36Sopenharmony_ci /* 78562306a36Sopenharmony_ci * Enable clocking: 0x0f (something?) between each burst, 78662306a36Sopenharmony_ci * enable the second lane if needed, enable continuous clock if 78762306a36Sopenharmony_ci * needed, enable switch into ULPM (ultra-low power mode) on 78862306a36Sopenharmony_ci * all the lines. 78962306a36Sopenharmony_ci */ 79062306a36Sopenharmony_ci val = 0x0f << DSI_MCTL_MAIN_PHY_CTL_WAIT_BURST_TIME_SHIFT; 79162306a36Sopenharmony_ci if (d->mdsi->lanes == 2) 79262306a36Sopenharmony_ci val |= DSI_MCTL_MAIN_PHY_CTL_LANE2_EN; 79362306a36Sopenharmony_ci if (!(d->mdsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)) 79462306a36Sopenharmony_ci val |= DSI_MCTL_MAIN_PHY_CTL_CLK_CONTINUOUS; 79562306a36Sopenharmony_ci val |= DSI_MCTL_MAIN_PHY_CTL_CLK_ULPM_EN | 79662306a36Sopenharmony_ci DSI_MCTL_MAIN_PHY_CTL_DAT1_ULPM_EN | 79762306a36Sopenharmony_ci DSI_MCTL_MAIN_PHY_CTL_DAT2_ULPM_EN; 79862306a36Sopenharmony_ci writel(val, d->regs + DSI_MCTL_MAIN_PHY_CTL); 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_ci val = (1 << DSI_MCTL_ULPOUT_TIME_CKLANE_ULPOUT_TIME_SHIFT) | 80162306a36Sopenharmony_ci (1 << DSI_MCTL_ULPOUT_TIME_DATA_ULPOUT_TIME_SHIFT); 80262306a36Sopenharmony_ci writel(val, d->regs + DSI_MCTL_ULPOUT_TIME); 80362306a36Sopenharmony_ci 80462306a36Sopenharmony_ci writel(DSI_DPHY_LANES_TRIM_DPHY_SPECS_90_81B_0_90, 80562306a36Sopenharmony_ci d->regs + DSI_DPHY_LANES_TRIM); 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci /* High PHY timeout */ 80862306a36Sopenharmony_ci val = (0x0f << DSI_MCTL_DPHY_TIMEOUT_CLK_DIV_SHIFT) | 80962306a36Sopenharmony_ci (0x3fff << DSI_MCTL_DPHY_TIMEOUT_HSTX_TO_VAL_SHIFT) | 81062306a36Sopenharmony_ci (0x3fff << DSI_MCTL_DPHY_TIMEOUT_LPRX_TO_VAL_SHIFT); 81162306a36Sopenharmony_ci writel(val, d->regs + DSI_MCTL_DPHY_TIMEOUT); 81262306a36Sopenharmony_ci 81362306a36Sopenharmony_ci val = DSI_MCTL_MAIN_EN_PLL_START | 81462306a36Sopenharmony_ci DSI_MCTL_MAIN_EN_CKLANE_EN | 81562306a36Sopenharmony_ci DSI_MCTL_MAIN_EN_DAT1_EN | 81662306a36Sopenharmony_ci DSI_MCTL_MAIN_EN_IF1_EN; 81762306a36Sopenharmony_ci if (d->mdsi->lanes == 2) 81862306a36Sopenharmony_ci val |= DSI_MCTL_MAIN_EN_DAT2_EN; 81962306a36Sopenharmony_ci writel(val, d->regs + DSI_MCTL_MAIN_EN); 82062306a36Sopenharmony_ci 82162306a36Sopenharmony_ci /* Wait for the PLL to lock and the clock and data lines to come up */ 82262306a36Sopenharmony_ci i = 0; 82362306a36Sopenharmony_ci val = DSI_MCTL_MAIN_STS_PLL_LOCK | 82462306a36Sopenharmony_ci DSI_MCTL_MAIN_STS_CLKLANE_READY | 82562306a36Sopenharmony_ci DSI_MCTL_MAIN_STS_DAT1_READY; 82662306a36Sopenharmony_ci if (d->mdsi->lanes == 2) 82762306a36Sopenharmony_ci val |= DSI_MCTL_MAIN_STS_DAT2_READY; 82862306a36Sopenharmony_ci while ((readl(d->regs + DSI_MCTL_MAIN_STS) & val) != val) { 82962306a36Sopenharmony_ci /* Sleep for a millisecond */ 83062306a36Sopenharmony_ci usleep_range(1000, 1500); 83162306a36Sopenharmony_ci if (i++ == 100) { 83262306a36Sopenharmony_ci dev_warn(d->dev, "DSI lanes did not start up\n"); 83362306a36Sopenharmony_ci return; 83462306a36Sopenharmony_ci } 83562306a36Sopenharmony_ci } 83662306a36Sopenharmony_ci 83762306a36Sopenharmony_ci /* TODO needed? */ 83862306a36Sopenharmony_ci 83962306a36Sopenharmony_ci /* Command mode, clear IF1 ID */ 84062306a36Sopenharmony_ci val = readl(d->regs + DSI_CMD_MODE_CTL); 84162306a36Sopenharmony_ci /* 84262306a36Sopenharmony_ci * If we enable low-power mode here, 84362306a36Sopenharmony_ci * then display updates become really slow. 84462306a36Sopenharmony_ci */ 84562306a36Sopenharmony_ci if (d->mdsi->mode_flags & MIPI_DSI_MODE_LPM) 84662306a36Sopenharmony_ci val |= DSI_CMD_MODE_CTL_IF1_LP_EN; 84762306a36Sopenharmony_ci val &= ~DSI_CMD_MODE_CTL_IF1_ID_MASK; 84862306a36Sopenharmony_ci writel(val, d->regs + DSI_CMD_MODE_CTL); 84962306a36Sopenharmony_ci 85062306a36Sopenharmony_ci /* Wait for DSI PHY to initialize */ 85162306a36Sopenharmony_ci usleep_range(100, 200); 85262306a36Sopenharmony_ci dev_info(d->dev, "DSI link enabled\n"); 85362306a36Sopenharmony_ci} 85462306a36Sopenharmony_ci 85562306a36Sopenharmony_ci/* 85662306a36Sopenharmony_ci * Notice that this is called from inside the display controller 85762306a36Sopenharmony_ci * and not from the bridge callbacks. 85862306a36Sopenharmony_ci */ 85962306a36Sopenharmony_civoid mcde_dsi_enable(struct drm_bridge *bridge) 86062306a36Sopenharmony_ci{ 86162306a36Sopenharmony_ci struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); 86262306a36Sopenharmony_ci unsigned long hs_freq, lp_freq; 86362306a36Sopenharmony_ci u32 val; 86462306a36Sopenharmony_ci int ret; 86562306a36Sopenharmony_ci 86662306a36Sopenharmony_ci /* Copy maximum clock frequencies */ 86762306a36Sopenharmony_ci if (d->mdsi->lp_rate) 86862306a36Sopenharmony_ci lp_freq = d->mdsi->lp_rate; 86962306a36Sopenharmony_ci else 87062306a36Sopenharmony_ci lp_freq = DSI_DEFAULT_LP_FREQ_HZ; 87162306a36Sopenharmony_ci if (d->mdsi->hs_rate) 87262306a36Sopenharmony_ci hs_freq = d->mdsi->hs_rate; 87362306a36Sopenharmony_ci else 87462306a36Sopenharmony_ci hs_freq = DSI_DEFAULT_HS_FREQ_HZ; 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci /* Enable LP (Low Power, Energy Save, ES) and HS (High Speed) clocks */ 87762306a36Sopenharmony_ci d->lp_freq = clk_round_rate(d->lp_clk, lp_freq); 87862306a36Sopenharmony_ci ret = clk_set_rate(d->lp_clk, d->lp_freq); 87962306a36Sopenharmony_ci if (ret) 88062306a36Sopenharmony_ci dev_err(d->dev, "failed to set LP clock rate %lu Hz\n", 88162306a36Sopenharmony_ci d->lp_freq); 88262306a36Sopenharmony_ci 88362306a36Sopenharmony_ci d->hs_freq = clk_round_rate(d->hs_clk, hs_freq); 88462306a36Sopenharmony_ci ret = clk_set_rate(d->hs_clk, d->hs_freq); 88562306a36Sopenharmony_ci if (ret) 88662306a36Sopenharmony_ci dev_err(d->dev, "failed to set HS clock rate %lu Hz\n", 88762306a36Sopenharmony_ci d->hs_freq); 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_ci /* Start clocks */ 89062306a36Sopenharmony_ci ret = clk_prepare_enable(d->lp_clk); 89162306a36Sopenharmony_ci if (ret) 89262306a36Sopenharmony_ci dev_err(d->dev, "failed to enable LP clock\n"); 89362306a36Sopenharmony_ci else 89462306a36Sopenharmony_ci dev_info(d->dev, "DSI LP clock rate %lu Hz\n", 89562306a36Sopenharmony_ci d->lp_freq); 89662306a36Sopenharmony_ci ret = clk_prepare_enable(d->hs_clk); 89762306a36Sopenharmony_ci if (ret) 89862306a36Sopenharmony_ci dev_err(d->dev, "failed to enable HS clock\n"); 89962306a36Sopenharmony_ci else 90062306a36Sopenharmony_ci dev_info(d->dev, "DSI HS clock rate %lu Hz\n", 90162306a36Sopenharmony_ci d->hs_freq); 90262306a36Sopenharmony_ci 90362306a36Sopenharmony_ci /* Assert RESET through the PRCMU, active low */ 90462306a36Sopenharmony_ci /* FIXME: which DSI block? */ 90562306a36Sopenharmony_ci regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET, 90662306a36Sopenharmony_ci PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0); 90762306a36Sopenharmony_ci 90862306a36Sopenharmony_ci usleep_range(100, 200); 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_ci /* De-assert RESET again */ 91162306a36Sopenharmony_ci regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET, 91262306a36Sopenharmony_ci PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 91362306a36Sopenharmony_ci PRCM_DSI_SW_RESET_DSI0_SW_RESETN); 91462306a36Sopenharmony_ci 91562306a36Sopenharmony_ci /* Start up the hardware */ 91662306a36Sopenharmony_ci mcde_dsi_start(d); 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) { 91962306a36Sopenharmony_ci /* Set up the video mode from the DRM mode */ 92062306a36Sopenharmony_ci mcde_dsi_setup_video_mode(d, d->mode); 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_ci /* Put IF1 into video mode */ 92362306a36Sopenharmony_ci val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL); 92462306a36Sopenharmony_ci val |= DSI_MCTL_MAIN_DATA_CTL_IF1_MODE; 92562306a36Sopenharmony_ci writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL); 92662306a36Sopenharmony_ci 92762306a36Sopenharmony_ci /* Disable command mode on IF1 */ 92862306a36Sopenharmony_ci val = readl(d->regs + DSI_CMD_MODE_CTL); 92962306a36Sopenharmony_ci val &= ~DSI_CMD_MODE_CTL_IF1_LP_EN; 93062306a36Sopenharmony_ci writel(val, d->regs + DSI_CMD_MODE_CTL); 93162306a36Sopenharmony_ci 93262306a36Sopenharmony_ci /* Enable some error interrupts */ 93362306a36Sopenharmony_ci val = readl(d->regs + DSI_VID_MODE_STS_CTL); 93462306a36Sopenharmony_ci val |= DSI_VID_MODE_STS_CTL_ERR_MISSING_VSYNC; 93562306a36Sopenharmony_ci val |= DSI_VID_MODE_STS_CTL_ERR_MISSING_DATA; 93662306a36Sopenharmony_ci writel(val, d->regs + DSI_VID_MODE_STS_CTL); 93762306a36Sopenharmony_ci 93862306a36Sopenharmony_ci /* Enable video mode */ 93962306a36Sopenharmony_ci val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL); 94062306a36Sopenharmony_ci val |= DSI_MCTL_MAIN_DATA_CTL_VID_EN; 94162306a36Sopenharmony_ci writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL); 94262306a36Sopenharmony_ci } else { 94362306a36Sopenharmony_ci /* Command mode, clear IF1 ID */ 94462306a36Sopenharmony_ci val = readl(d->regs + DSI_CMD_MODE_CTL); 94562306a36Sopenharmony_ci /* 94662306a36Sopenharmony_ci * If we enable low-power mode here 94762306a36Sopenharmony_ci * the display updates become really slow. 94862306a36Sopenharmony_ci */ 94962306a36Sopenharmony_ci if (d->mdsi->mode_flags & MIPI_DSI_MODE_LPM) 95062306a36Sopenharmony_ci val |= DSI_CMD_MODE_CTL_IF1_LP_EN; 95162306a36Sopenharmony_ci val &= ~DSI_CMD_MODE_CTL_IF1_ID_MASK; 95262306a36Sopenharmony_ci writel(val, d->regs + DSI_CMD_MODE_CTL); 95362306a36Sopenharmony_ci } 95462306a36Sopenharmony_ci 95562306a36Sopenharmony_ci dev_info(d->dev, "enabled MCDE DSI master\n"); 95662306a36Sopenharmony_ci} 95762306a36Sopenharmony_ci 95862306a36Sopenharmony_cistatic void mcde_dsi_bridge_mode_set(struct drm_bridge *bridge, 95962306a36Sopenharmony_ci const struct drm_display_mode *mode, 96062306a36Sopenharmony_ci const struct drm_display_mode *adj) 96162306a36Sopenharmony_ci{ 96262306a36Sopenharmony_ci struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); 96362306a36Sopenharmony_ci 96462306a36Sopenharmony_ci if (!d->mdsi) { 96562306a36Sopenharmony_ci dev_err(d->dev, "no DSI device attached to encoder!\n"); 96662306a36Sopenharmony_ci return; 96762306a36Sopenharmony_ci } 96862306a36Sopenharmony_ci 96962306a36Sopenharmony_ci d->mode = mode; 97062306a36Sopenharmony_ci 97162306a36Sopenharmony_ci dev_info(d->dev, "set DSI master to %dx%d %u Hz %s mode\n", 97262306a36Sopenharmony_ci mode->hdisplay, mode->vdisplay, mode->clock * 1000, 97362306a36Sopenharmony_ci (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? "VIDEO" : "CMD" 97462306a36Sopenharmony_ci ); 97562306a36Sopenharmony_ci} 97662306a36Sopenharmony_ci 97762306a36Sopenharmony_cistatic void mcde_dsi_wait_for_command_mode_stop(struct mcde_dsi *d) 97862306a36Sopenharmony_ci{ 97962306a36Sopenharmony_ci u32 val; 98062306a36Sopenharmony_ci int i; 98162306a36Sopenharmony_ci 98262306a36Sopenharmony_ci /* 98362306a36Sopenharmony_ci * Wait until we get out of command mode 98462306a36Sopenharmony_ci * CSM = Command State Machine 98562306a36Sopenharmony_ci */ 98662306a36Sopenharmony_ci i = 0; 98762306a36Sopenharmony_ci val = DSI_CMD_MODE_STS_CSM_RUNNING; 98862306a36Sopenharmony_ci while ((readl(d->regs + DSI_CMD_MODE_STS) & val) == val) { 98962306a36Sopenharmony_ci /* Sleep for a millisecond */ 99062306a36Sopenharmony_ci usleep_range(1000, 2000); 99162306a36Sopenharmony_ci if (i++ == 100) { 99262306a36Sopenharmony_ci dev_warn(d->dev, 99362306a36Sopenharmony_ci "could not get out of command mode\n"); 99462306a36Sopenharmony_ci return; 99562306a36Sopenharmony_ci } 99662306a36Sopenharmony_ci } 99762306a36Sopenharmony_ci} 99862306a36Sopenharmony_ci 99962306a36Sopenharmony_cistatic void mcde_dsi_wait_for_video_mode_stop(struct mcde_dsi *d) 100062306a36Sopenharmony_ci{ 100162306a36Sopenharmony_ci u32 val; 100262306a36Sopenharmony_ci int i; 100362306a36Sopenharmony_ci 100462306a36Sopenharmony_ci /* Wait until we get out og video mode */ 100562306a36Sopenharmony_ci i = 0; 100662306a36Sopenharmony_ci val = DSI_VID_MODE_STS_VSG_RUNNING; 100762306a36Sopenharmony_ci while ((readl(d->regs + DSI_VID_MODE_STS) & val) == val) { 100862306a36Sopenharmony_ci /* Sleep for a millisecond */ 100962306a36Sopenharmony_ci usleep_range(1000, 2000); 101062306a36Sopenharmony_ci if (i++ == 100) { 101162306a36Sopenharmony_ci dev_warn(d->dev, 101262306a36Sopenharmony_ci "could not get out of video mode\n"); 101362306a36Sopenharmony_ci return; 101462306a36Sopenharmony_ci } 101562306a36Sopenharmony_ci } 101662306a36Sopenharmony_ci} 101762306a36Sopenharmony_ci 101862306a36Sopenharmony_ci/* 101962306a36Sopenharmony_ci * Notice that this is called from inside the display controller 102062306a36Sopenharmony_ci * and not from the bridge callbacks. 102162306a36Sopenharmony_ci */ 102262306a36Sopenharmony_civoid mcde_dsi_disable(struct drm_bridge *bridge) 102362306a36Sopenharmony_ci{ 102462306a36Sopenharmony_ci struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); 102562306a36Sopenharmony_ci u32 val; 102662306a36Sopenharmony_ci 102762306a36Sopenharmony_ci if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) { 102862306a36Sopenharmony_ci /* Stop video mode */ 102962306a36Sopenharmony_ci val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL); 103062306a36Sopenharmony_ci val &= ~DSI_MCTL_MAIN_DATA_CTL_VID_EN; 103162306a36Sopenharmony_ci writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL); 103262306a36Sopenharmony_ci mcde_dsi_wait_for_video_mode_stop(d); 103362306a36Sopenharmony_ci } else { 103462306a36Sopenharmony_ci /* Stop command mode */ 103562306a36Sopenharmony_ci mcde_dsi_wait_for_command_mode_stop(d); 103662306a36Sopenharmony_ci } 103762306a36Sopenharmony_ci 103862306a36Sopenharmony_ci /* 103962306a36Sopenharmony_ci * Stop clocks and terminate any DSI traffic here so the panel can 104062306a36Sopenharmony_ci * send commands to shut down the display using DSI direct write until 104162306a36Sopenharmony_ci * this point. 104262306a36Sopenharmony_ci */ 104362306a36Sopenharmony_ci 104462306a36Sopenharmony_ci /* Disable all error interrupts */ 104562306a36Sopenharmony_ci writel(0, d->regs + DSI_VID_MODE_STS_CTL); 104662306a36Sopenharmony_ci clk_disable_unprepare(d->hs_clk); 104762306a36Sopenharmony_ci clk_disable_unprepare(d->lp_clk); 104862306a36Sopenharmony_ci} 104962306a36Sopenharmony_ci 105062306a36Sopenharmony_cistatic int mcde_dsi_bridge_attach(struct drm_bridge *bridge, 105162306a36Sopenharmony_ci enum drm_bridge_attach_flags flags) 105262306a36Sopenharmony_ci{ 105362306a36Sopenharmony_ci struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); 105462306a36Sopenharmony_ci struct drm_device *drm = bridge->dev; 105562306a36Sopenharmony_ci 105662306a36Sopenharmony_ci if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) { 105762306a36Sopenharmony_ci dev_err(d->dev, "we need atomic updates\n"); 105862306a36Sopenharmony_ci return -ENOTSUPP; 105962306a36Sopenharmony_ci } 106062306a36Sopenharmony_ci 106162306a36Sopenharmony_ci /* Attach the DSI bridge to the output (panel etc) bridge */ 106262306a36Sopenharmony_ci return drm_bridge_attach(bridge->encoder, d->bridge_out, bridge, flags); 106362306a36Sopenharmony_ci} 106462306a36Sopenharmony_ci 106562306a36Sopenharmony_cistatic const struct drm_bridge_funcs mcde_dsi_bridge_funcs = { 106662306a36Sopenharmony_ci .attach = mcde_dsi_bridge_attach, 106762306a36Sopenharmony_ci .mode_set = mcde_dsi_bridge_mode_set, 106862306a36Sopenharmony_ci}; 106962306a36Sopenharmony_ci 107062306a36Sopenharmony_cistatic int mcde_dsi_bind(struct device *dev, struct device *master, 107162306a36Sopenharmony_ci void *data) 107262306a36Sopenharmony_ci{ 107362306a36Sopenharmony_ci struct drm_device *drm = data; 107462306a36Sopenharmony_ci struct mcde *mcde = to_mcde(drm); 107562306a36Sopenharmony_ci struct mcde_dsi *d = dev_get_drvdata(dev); 107662306a36Sopenharmony_ci struct device_node *child; 107762306a36Sopenharmony_ci struct drm_panel *panel = NULL; 107862306a36Sopenharmony_ci struct drm_bridge *bridge = NULL; 107962306a36Sopenharmony_ci 108062306a36Sopenharmony_ci if (!of_get_available_child_count(dev->of_node)) { 108162306a36Sopenharmony_ci dev_info(dev, "unused DSI interface\n"); 108262306a36Sopenharmony_ci d->unused = true; 108362306a36Sopenharmony_ci return 0; 108462306a36Sopenharmony_ci } 108562306a36Sopenharmony_ci d->mcde = mcde; 108662306a36Sopenharmony_ci /* If the display attached before binding, set this up */ 108762306a36Sopenharmony_ci if (d->mdsi) 108862306a36Sopenharmony_ci mcde_dsi_attach_to_mcde(d); 108962306a36Sopenharmony_ci 109062306a36Sopenharmony_ci /* Obtain the clocks */ 109162306a36Sopenharmony_ci d->hs_clk = devm_clk_get(dev, "hs"); 109262306a36Sopenharmony_ci if (IS_ERR(d->hs_clk)) { 109362306a36Sopenharmony_ci dev_err(dev, "unable to get HS clock\n"); 109462306a36Sopenharmony_ci return PTR_ERR(d->hs_clk); 109562306a36Sopenharmony_ci } 109662306a36Sopenharmony_ci 109762306a36Sopenharmony_ci d->lp_clk = devm_clk_get(dev, "lp"); 109862306a36Sopenharmony_ci if (IS_ERR(d->lp_clk)) { 109962306a36Sopenharmony_ci dev_err(dev, "unable to get LP clock\n"); 110062306a36Sopenharmony_ci return PTR_ERR(d->lp_clk); 110162306a36Sopenharmony_ci } 110262306a36Sopenharmony_ci 110362306a36Sopenharmony_ci /* Look for a panel as a child to this node */ 110462306a36Sopenharmony_ci for_each_available_child_of_node(dev->of_node, child) { 110562306a36Sopenharmony_ci panel = of_drm_find_panel(child); 110662306a36Sopenharmony_ci if (IS_ERR(panel)) { 110762306a36Sopenharmony_ci dev_err(dev, "failed to find panel try bridge (%ld)\n", 110862306a36Sopenharmony_ci PTR_ERR(panel)); 110962306a36Sopenharmony_ci panel = NULL; 111062306a36Sopenharmony_ci 111162306a36Sopenharmony_ci bridge = of_drm_find_bridge(child); 111262306a36Sopenharmony_ci if (!bridge) { 111362306a36Sopenharmony_ci dev_err(dev, "failed to find bridge\n"); 111462306a36Sopenharmony_ci of_node_put(child); 111562306a36Sopenharmony_ci return -EINVAL; 111662306a36Sopenharmony_ci } 111762306a36Sopenharmony_ci } 111862306a36Sopenharmony_ci } 111962306a36Sopenharmony_ci if (panel) { 112062306a36Sopenharmony_ci bridge = drm_panel_bridge_add_typed(panel, 112162306a36Sopenharmony_ci DRM_MODE_CONNECTOR_DSI); 112262306a36Sopenharmony_ci if (IS_ERR(bridge)) { 112362306a36Sopenharmony_ci dev_err(dev, "error adding panel bridge\n"); 112462306a36Sopenharmony_ci return PTR_ERR(bridge); 112562306a36Sopenharmony_ci } 112662306a36Sopenharmony_ci dev_info(dev, "connected to panel\n"); 112762306a36Sopenharmony_ci d->panel = panel; 112862306a36Sopenharmony_ci } else if (bridge) { 112962306a36Sopenharmony_ci /* TODO: AV8100 HDMI encoder goes here for example */ 113062306a36Sopenharmony_ci dev_info(dev, "connected to non-panel bridge (unsupported)\n"); 113162306a36Sopenharmony_ci return -ENODEV; 113262306a36Sopenharmony_ci } else { 113362306a36Sopenharmony_ci dev_err(dev, "no panel or bridge\n"); 113462306a36Sopenharmony_ci return -ENODEV; 113562306a36Sopenharmony_ci } 113662306a36Sopenharmony_ci 113762306a36Sopenharmony_ci d->bridge_out = bridge; 113862306a36Sopenharmony_ci 113962306a36Sopenharmony_ci /* Create a bridge for this DSI channel */ 114062306a36Sopenharmony_ci d->bridge.funcs = &mcde_dsi_bridge_funcs; 114162306a36Sopenharmony_ci d->bridge.of_node = dev->of_node; 114262306a36Sopenharmony_ci drm_bridge_add(&d->bridge); 114362306a36Sopenharmony_ci 114462306a36Sopenharmony_ci /* TODO: first come first serve, use a list */ 114562306a36Sopenharmony_ci mcde->bridge = &d->bridge; 114662306a36Sopenharmony_ci 114762306a36Sopenharmony_ci dev_info(dev, "initialized MCDE DSI bridge\n"); 114862306a36Sopenharmony_ci 114962306a36Sopenharmony_ci return 0; 115062306a36Sopenharmony_ci} 115162306a36Sopenharmony_ci 115262306a36Sopenharmony_cistatic void mcde_dsi_unbind(struct device *dev, struct device *master, 115362306a36Sopenharmony_ci void *data) 115462306a36Sopenharmony_ci{ 115562306a36Sopenharmony_ci struct mcde_dsi *d = dev_get_drvdata(dev); 115662306a36Sopenharmony_ci 115762306a36Sopenharmony_ci if (d->panel) 115862306a36Sopenharmony_ci drm_panel_bridge_remove(d->bridge_out); 115962306a36Sopenharmony_ci regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET, 116062306a36Sopenharmony_ci PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0); 116162306a36Sopenharmony_ci} 116262306a36Sopenharmony_ci 116362306a36Sopenharmony_cistatic const struct component_ops mcde_dsi_component_ops = { 116462306a36Sopenharmony_ci .bind = mcde_dsi_bind, 116562306a36Sopenharmony_ci .unbind = mcde_dsi_unbind, 116662306a36Sopenharmony_ci}; 116762306a36Sopenharmony_ci 116862306a36Sopenharmony_cistatic int mcde_dsi_probe(struct platform_device *pdev) 116962306a36Sopenharmony_ci{ 117062306a36Sopenharmony_ci struct device *dev = &pdev->dev; 117162306a36Sopenharmony_ci struct mcde_dsi *d; 117262306a36Sopenharmony_ci struct mipi_dsi_host *host; 117362306a36Sopenharmony_ci u32 dsi_id; 117462306a36Sopenharmony_ci int ret; 117562306a36Sopenharmony_ci 117662306a36Sopenharmony_ci d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL); 117762306a36Sopenharmony_ci if (!d) 117862306a36Sopenharmony_ci return -ENOMEM; 117962306a36Sopenharmony_ci d->dev = dev; 118062306a36Sopenharmony_ci platform_set_drvdata(pdev, d); 118162306a36Sopenharmony_ci 118262306a36Sopenharmony_ci /* Get a handle on the PRCMU so we can do reset */ 118362306a36Sopenharmony_ci d->prcmu = 118462306a36Sopenharmony_ci syscon_regmap_lookup_by_compatible("stericsson,db8500-prcmu"); 118562306a36Sopenharmony_ci if (IS_ERR(d->prcmu)) { 118662306a36Sopenharmony_ci dev_err(dev, "no PRCMU regmap\n"); 118762306a36Sopenharmony_ci return PTR_ERR(d->prcmu); 118862306a36Sopenharmony_ci } 118962306a36Sopenharmony_ci 119062306a36Sopenharmony_ci d->regs = devm_platform_ioremap_resource(pdev, 0); 119162306a36Sopenharmony_ci if (IS_ERR(d->regs)) 119262306a36Sopenharmony_ci return PTR_ERR(d->regs); 119362306a36Sopenharmony_ci 119462306a36Sopenharmony_ci dsi_id = readl(d->regs + DSI_ID_REG); 119562306a36Sopenharmony_ci dev_info(dev, "HW revision 0x%08x\n", dsi_id); 119662306a36Sopenharmony_ci 119762306a36Sopenharmony_ci host = &d->dsi_host; 119862306a36Sopenharmony_ci host->dev = dev; 119962306a36Sopenharmony_ci host->ops = &mcde_dsi_host_ops; 120062306a36Sopenharmony_ci ret = mipi_dsi_host_register(host); 120162306a36Sopenharmony_ci if (ret < 0) { 120262306a36Sopenharmony_ci dev_err(dev, "failed to register DSI host: %d\n", ret); 120362306a36Sopenharmony_ci return ret; 120462306a36Sopenharmony_ci } 120562306a36Sopenharmony_ci dev_info(dev, "registered DSI host\n"); 120662306a36Sopenharmony_ci 120762306a36Sopenharmony_ci platform_set_drvdata(pdev, d); 120862306a36Sopenharmony_ci return component_add(dev, &mcde_dsi_component_ops); 120962306a36Sopenharmony_ci} 121062306a36Sopenharmony_ci 121162306a36Sopenharmony_cistatic void mcde_dsi_remove(struct platform_device *pdev) 121262306a36Sopenharmony_ci{ 121362306a36Sopenharmony_ci struct mcde_dsi *d = platform_get_drvdata(pdev); 121462306a36Sopenharmony_ci 121562306a36Sopenharmony_ci component_del(&pdev->dev, &mcde_dsi_component_ops); 121662306a36Sopenharmony_ci mipi_dsi_host_unregister(&d->dsi_host); 121762306a36Sopenharmony_ci} 121862306a36Sopenharmony_ci 121962306a36Sopenharmony_cistatic const struct of_device_id mcde_dsi_of_match[] = { 122062306a36Sopenharmony_ci { 122162306a36Sopenharmony_ci .compatible = "ste,mcde-dsi", 122262306a36Sopenharmony_ci }, 122362306a36Sopenharmony_ci {}, 122462306a36Sopenharmony_ci}; 122562306a36Sopenharmony_ci 122662306a36Sopenharmony_cistruct platform_driver mcde_dsi_driver = { 122762306a36Sopenharmony_ci .driver = { 122862306a36Sopenharmony_ci .name = "mcde-dsi", 122962306a36Sopenharmony_ci .of_match_table = mcde_dsi_of_match, 123062306a36Sopenharmony_ci }, 123162306a36Sopenharmony_ci .probe = mcde_dsi_probe, 123262306a36Sopenharmony_ci .remove_new = mcde_dsi_remove, 123362306a36Sopenharmony_ci}; 1234