// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd * Copyright (C) STMicroelectronics SA 2017 * * Modified by Philippe Cornu <philippe.cornu@st.com> * This generic Synopsys DesignWare MIPI DSI host driver is based on the * Rockchip version from rockchip/dw-mipi-dsi.c with phy & bridge APIs.
*/
/* * Check if either a link to a master or slave is present
*/ staticinlinebool dw_mipi_is_dual_mode(struct dw_mipi_dsi *dsi)
{ return dsi->slave || dsi->master;
}
/* * The controller should generate 2 frames before * preparing the peripheral.
*/ staticvoid dw_mipi_dsi_wait_for_two_frames(conststruct drm_display_mode *mode)
{ int refresh, two_frames;
if (device->lanes > dsi->plat_data->max_data_lanes) {
dev_err(dsi->dev, "the number of data lanes(%u) is too many\n",
device->lanes); return -EINVAL;
}
/* * TODO dw drv improvements * largest packet sizes during hfp or during vsa/vpb/vfp * should be computed according to byte lane, lane number and only * if sending lp cmds in high speed is enable (PHY_TXREQUESTCLKHS)
*/
dsi_write(dsi, DSI_DPI_LP_CMD_TIM, OUTVACT_LPCMD_TIME(16)
| INVACT_LPCMD_TIME(4));
if (msg->flags & MIPI_DSI_MSG_REQ_ACK)
val |= ACK_RQST_EN; if (lpm)
val |= CMD_MODE_ALL_LP;
dsi_write(dsi, DSI_CMD_MODE_CFG, val);
val = dsi_read(dsi, DSI_VID_MODE_CFG); if (lpm)
val |= ENABLE_LOW_POWER_CMD; else
val &= ~ENABLE_LOW_POWER_CMD;
dsi_write(dsi, DSI_VID_MODE_CFG, val);
}
ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
val, !(val & GEN_CMD_FULL), 1000,
CMD_PKT_STATUS_TIMEOUT_US); if (ret) {
dev_err(dsi->dev, "failed to get available command FIFO\n"); return ret;
}
dsi_write(dsi, DSI_GEN_HDR, hdr_val);
mask = GEN_CMD_EMPTY | GEN_PLD_W_EMPTY;
ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
val, (val & mask) == mask,
1000, CMD_PKT_STATUS_TIMEOUT_US); if (ret) {
dev_err(dsi->dev, "failed to write command FIFO\n"); return ret;
}
while (len) { if (len < pld_data_bytes) {
word = 0;
memcpy(&word, tx_buf, len);
dsi_write(dsi, DSI_GEN_PLD_DATA, le32_to_cpu(word));
len = 0;
} else {
memcpy(&word, tx_buf, pld_data_bytes);
dsi_write(dsi, DSI_GEN_PLD_DATA, le32_to_cpu(word));
tx_buf += pld_data_bytes;
len -= pld_data_bytes;
}
ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
val, !(val & GEN_PLD_W_FULL), 1000,
CMD_PKT_STATUS_TIMEOUT_US); if (ret) {
dev_err(dsi->dev, "failed to get available write payload FIFO\n"); return ret;
}
}
word = 0;
memcpy(&word, packet->header, sizeof(packet->header)); return dw_mipi_dsi_gen_pkt_hdr_write(dsi, le32_to_cpu(word));
}
staticint dw_mipi_dsi_read(struct dw_mipi_dsi *dsi, conststruct mipi_dsi_msg *msg)
{ int i, j, ret, len = msg->rx_len;
u8 *buf = msg->rx_buf;
u32 val;
/* Wait end of the read operation */
ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
val, !(val & GEN_RD_CMD_BUSY),
1000, CMD_PKT_STATUS_TIMEOUT_US); if (ret) {
dev_err(dsi->dev, "Timeout during read operation\n"); return ret;
}
for (i = 0; i < len; i += 4) { /* Read fifo must not be empty before all bytes are read */
ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
val, !(val & GEN_PLD_R_EMPTY),
1000, CMD_PKT_STATUS_TIMEOUT_US); if (ret) {
dev_err(dsi->dev, "Read payload FIFO is empty\n"); return ret;
}
val = dsi_read(dsi, DSI_GEN_PLD_DATA); for (j = 0; j < 4 && j + i < len; j++)
buf[i + j] = val >> (8 * j);
}
if (pdata->get_input_bus_fmts) return pdata->get_input_bus_fmts(pdata->priv_data,
bridge, bridge_state,
crtc_state, conn_state,
output_fmt, num_input_fmts);
/* Fall back to MEDIA_BUS_FMT_FIXED as the only input format. */
input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL); if (!input_fmts) return NULL;
input_fmts[0] = MEDIA_BUS_FMT_FIXED;
*num_input_fmts = 1;
/* * TODO dw drv improvements * enabling low power is panel-dependent, we should use the * panel configuration here...
*/
val = ENABLE_LOW_POWER;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
val |= VID_MODE_TYPE_BURST; elseif (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
val |= VID_MODE_TYPE_NON_BURST_SYNC_PULSES; else
val |= VID_MODE_TYPE_NON_BURST_SYNC_EVENTS;
#ifdef CONFIG_DEBUG_FS if (dsi->vpg_defs.vpg) {
val |= VID_MODE_VPG_ENABLE;
val |= dsi->vpg_defs.vpg_horizontal ?
VID_MODE_VPG_HORIZONTAL : 0;
val |= dsi->vpg_defs.vpg_ber_pattern ? VID_MODE_VPG_MODE : 0;
} #endif/* CONFIG_DEBUG_FS */
staticvoid dw_mipi_dsi_init(struct dw_mipi_dsi *dsi)
{ conststruct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops; unsignedint esc_rate; /* in MHz */
u32 esc_clk_division; int ret;
/* * The maximum permitted escape clock is 20MHz and it is derived from * lanebyteclk, which is running at "lane_mbps / 8".
*/ if (phy_ops->get_esc_clk_rate) {
ret = phy_ops->get_esc_clk_rate(dsi->plat_data->priv_data,
&esc_rate); if (ret)
DRM_DEBUG_DRIVER("Phy get_esc_clk_rate() failed\n");
} else
esc_rate = 20; /* Default to 20MHz */
/* * We want : * (lane_mbps >> 3) / esc_clk_division < X * which is: * (lane_mbps >> 3) / X > esc_clk_division
*/
esc_clk_division = (dsi->lane_mbps >> 3) / esc_rate + 1;
dsi_write(dsi, DSI_PWR_UP, RESET);
/* * TODO dw drv improvements * timeout clock division should be computed with the * high speed transmission counter timeout and byte lane...
*/
dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVISION(0) |
TX_ESC_CLK_DIVISION(esc_clk_division));
}
staticvoid dw_mipi_dsi_dpi_config(struct dw_mipi_dsi *dsi, conststruct drm_display_mode *mode)
{
u32 val = 0, color = 0;
switch (dsi->format) { case MIPI_DSI_FMT_RGB888:
color = DPI_COLOR_CODING_24BIT; break; case MIPI_DSI_FMT_RGB666:
color = DPI_COLOR_CODING_18BIT_2 | LOOSELY18_EN; break; case MIPI_DSI_FMT_RGB666_PACKED:
color = DPI_COLOR_CODING_18BIT_1; break; case MIPI_DSI_FMT_RGB565:
color = DPI_COLOR_CODING_16BIT_1; break;
}
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
val |= VSYNC_ACTIVE_LOW; if (mode->flags & DRM_MODE_FLAG_NHSYNC)
val |= HSYNC_ACTIVE_LOW;
if (dsi->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
val &= ~EOTP_TX_EN;
dsi_write(dsi, DSI_PCKHDL_CFG, val);
}
staticvoid dw_mipi_dsi_video_packet_config(struct dw_mipi_dsi *dsi, conststruct drm_display_mode *mode)
{ /* * TODO dw drv improvements * only burst mode is supported here. For non-burst video modes, * we should compute DSI_VID_PKT_SIZE, DSI_VCCR.NUMC & * DSI_VNPCR.NPSIZE... especially because this driver supports * non-burst video modes, see dw_mipi_dsi_video_mode_config()...
*/
ret = phy_ops->get_timing(dsi->plat_data->priv_data,
dsi->lane_mbps, &timing); if (ret)
DRM_DEV_ERROR(dsi->dev, "Retrieving phy timings failed\n");
/* * TODO dw drv improvements * data & clock lane timers should be computed according to panel * blankings and to the automatic clock lane control mode... * note: DSI_PHY_TMR_CFG.MAX_RD_TIME should be in line with * DSI_CMD_MODE_CFG.MAX_RD_PKT_SIZE_LP (see CMD_MODE_ALL_LP)
*/
staticvoid dw_mipi_dsi_dphy_interface_config(struct dw_mipi_dsi *dsi)
{ /* * TODO dw drv improvements * stop wait time should be the maximum between host dsi * and panel stop wait times
*/
dsi_write(dsi, DSI_PHY_IF_CFG, PHY_STOP_WAIT_TIME(0x20) |
N_LANES(dsi->lanes));
}
ret = readl_poll_timeout(dsi->base + DSI_PHY_STATUS, val,
val & PHY_LOCK, 1000, PHY_STATUS_TIMEOUT_US); if (ret)
DRM_DEBUG_DRIVER("failed to wait phy lock state\n");
ret = readl_poll_timeout(dsi->base + DSI_PHY_STATUS,
val, val & PHY_STOP_STATE_CLK_LANE, 1000,
PHY_STATUS_TIMEOUT_US); if (ret)
DRM_DEBUG_DRIVER("failed to wait phy clk lane stop state\n");
}
/* * Switch to command mode before panel-bridge post_disable & * panel unprepare. * Note: panel-bridge disable & panel disable has been called * before by the drm framework.
*/
dw_mipi_dsi_set_mode(dsi, 0);
if (phy_ops->power_off)
phy_ops->power_off(dsi->plat_data->priv_data);
if (dsi->slave) {
dw_mipi_dsi_disable(dsi->slave);
clk_disable_unprepare(dsi->slave->pclk);
pm_runtime_put(dsi->slave->dev);
}
dw_mipi_dsi_disable(dsi);
staticunsignedint dw_mipi_dsi_get_lanes(struct dw_mipi_dsi *dsi)
{ /* this instance is the slave, so add the master's lanes */ if (dsi->master) return dsi->master->lanes + dsi->lanes;
/* this instance is the master, so add the slave's lanes */ if (dsi->slave) return dsi->lanes + dsi->slave->lanes;
/* single-dsi, so no other instance to consider */ return dsi->lanes;
}
/* Switch to video mode for panel-bridge enable & panel enable */
dw_mipi_dsi_set_mode(dsi, MIPI_DSI_MODE_VIDEO); if (dsi->slave)
dw_mipi_dsi_set_mode(dsi->slave, MIPI_DSI_MODE_VIDEO);
}
if (!plat_data->phy_ops->init || !plat_data->phy_ops->get_lane_mbps ||
!plat_data->phy_ops->get_timing) {
DRM_ERROR("Phy not properly configured\n"); return ERR_PTR(-ENODEV);
}
if (!plat_data->base) {
dsi->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(dsi->base)) return ERR_PTR(-ENODEV);
} else {
dsi->base = plat_data->base;
}
dsi->pclk = devm_clk_get(dev, "pclk"); if (IS_ERR(dsi->pclk)) {
ret = PTR_ERR(dsi->pclk);
dev_err(dev, "Unable to get pclk: %d\n", ret); return ERR_PTR(ret);
}
/* * Note that the reset was not defined in the initial device tree, so * we have to be prepared for it not being found.
*/
apb_rst = devm_reset_control_get_optional_exclusive(dev, "apb"); if (IS_ERR(apb_rst)) {
ret = PTR_ERR(apb_rst);
if (ret != -EPROBE_DEFER)
dev_err(dev, "Unable to get reset control: %d\n", ret);
return ERR_PTR(ret);
}
if (apb_rst) {
ret = clk_prepare_enable(dsi->pclk); if (ret) {
dev_err(dev, "%s: Failed to enable pclk\n", __func__); return ERR_PTR(ret);
}
/* * Bind/unbind API, used from platforms based on the component framework.
*/ int dw_mipi_dsi_bind(struct dw_mipi_dsi *dsi, struct drm_encoder *encoder)
{ return drm_bridge_attach(encoder, &dsi->bridge, NULL, 0);
}
EXPORT_SYMBOL_GPL(dw_mipi_dsi_bind);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.