// SPDX-License-Identifier: GPL-2.0-only
|
/*
|
* DesignWare MIPI DSI Host Controller v1.02 driver
|
*
|
* Copyright (c) 2016 Linaro Limited.
|
* Copyright (c) 2014-2016 Hisilicon Limited.
|
*
|
* Author:
|
* <shizongxuan@huawei.com>
|
* <zhangxiubin@huawei.com>
|
* <lvda3@hisilicon.com>
|
*
|
*/
|
|
#include <linux/clk.h>
|
#include <linux/module.h>
|
#include <linux/component.h>
|
#include <linux/of_graph.h>
|
#include <linux/iopoll.h>
|
#include <video/mipi_display.h>
|
#include <linux/gpio/consumer.h>
|
#include <linux/of_address.h>
|
|
#include <drm/drm_of.h>
|
#include <drm/drm_crtc_helper.h>
|
#include <drm/drm_mipi_dsi.h>
|
#include <drm/drm_encoder_slave.h>
|
#include <drm/drm_atomic_helper.h>
|
#include <drm/drm_panel.h>
|
#include <drm/drm_device.h>
|
#include <drm/drm_sysfs.h>
|
#include <drm/drm_print.h>
|
#include <drm/drm_probe_helper.h>
|
|
#include "../kirin_drm_dsi.h"
|
#include "../dw_dsi_reg.h"
|
#include "../kirin_dpe_reg.h"
|
|
#define DTS_COMP_DSI_NAME "hisilicon,hi3660-dsi"
|
#define DSS_REDUCE(x) ((x) > 0 ? ((x) - 1) : (x))
|
|
#define DEFAULT_MIPI_CLK_RATE (192 * 100000L)
|
#define DEFAULT_PCLK_DSI_RATE (120 * 1000000L)
|
|
struct dss_rect {
|
s32 x;
|
s32 y;
|
s32 w;
|
s32 h;
|
};
|
|
enum {
|
DSI_1_LANES = 0,
|
DSI_2_LANES,
|
DSI_3_LANES,
|
DSI_4_LANES,
|
};
|
|
static void set_reg(char __iomem *addr, uint32_t val, uint8_t bw, uint8_t bs)
|
{
|
u32 mask = (1UL << bw) - 1UL;
|
u32 tmp = 0;
|
|
tmp = readl(addr);
|
tmp &= ~(mask << bs);
|
|
writel(tmp | ((val & mask) << bs), addr);
|
}
|
|
static enum drm_mode_status
|
dsi_encoder_phy_mode_valid(struct drm_encoder *encoder,
|
const struct drm_display_mode *mode)
|
{
|
/* XXX HACK whitelist for now, to move it out of
|
* common adv7511 code. This should be replaced by
|
* something closer to dsi_encoder_phy_mode_valid()
|
* found in:
|
* drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
|
*/
|
DRM_DEBUG_DRIVER("Checking mode %ix%i@%i clock: %i...", mode->hdisplay,
|
mode->vdisplay, drm_mode_vrefresh(mode), mode->clock);
|
if ((mode->hdisplay == 1920 && mode->vdisplay == 1080 && mode->clock == 148500) ||
|
(mode->hdisplay == 1920 && mode->vdisplay == 1080 && mode->clock == 80192) ||
|
(mode->hdisplay == 1920 && mode->vdisplay == 1080 && mode->clock == 74250) ||
|
(mode->hdisplay == 1920 && mode->vdisplay == 1080 && mode->clock == 61855) ||
|
(mode->hdisplay == 1680 && mode->vdisplay == 1050 && mode->clock == 147116) ||
|
(mode->hdisplay == 1680 && mode->vdisplay == 1050 && mode->clock == 146250) ||
|
(mode->hdisplay == 1680 && mode->vdisplay == 1050 && mode->clock == 144589) ||
|
(mode->hdisplay == 1600 && mode->vdisplay == 1200 && mode->clock == 160961) ||
|
(mode->hdisplay == 1600 && mode->vdisplay == 900 && mode->clock == 118963) ||
|
(mode->hdisplay == 1440 && mode->vdisplay == 900 && mode->clock == 126991) ||
|
(mode->hdisplay == 1280 && mode->vdisplay == 1024 && mode->clock == 128946) ||
|
(mode->hdisplay == 1280 && mode->vdisplay == 1024 && mode->clock == 98619) ||
|
(mode->hdisplay == 1280 && mode->vdisplay == 960 && mode->clock == 102081) ||
|
(mode->hdisplay == 1280 && mode->vdisplay == 800 && mode->clock == 83496) ||
|
(mode->hdisplay == 1280 && mode->vdisplay == 720 && mode->clock == 74440) ||
|
(mode->hdisplay == 1280 && mode->vdisplay == 720 && mode->clock == 74250) ||
|
(mode->hdisplay == 1024 && mode->vdisplay == 768 && mode->clock == 78800) ||
|
(mode->hdisplay == 1024 && mode->vdisplay == 768 && mode->clock == 75000) ||
|
(mode->hdisplay == 1024 && mode->vdisplay == 768 && mode->clock == 81833) ||
|
(mode->hdisplay == 800 && mode->vdisplay == 600 && mode->clock == 48907) ||
|
(mode->hdisplay == 800 && mode->vdisplay == 600 && mode->clock == 40000) ||
|
(mode->hdisplay == 800 && mode->vdisplay == 480 && mode->clock == 32000)) {
|
DRM_DEBUG("OK\n");
|
return MODE_OK;
|
}
|
DRM_DEBUG("BAD\n");
|
return MODE_BAD;
|
}
|
|
static enum drm_mode_status
|
dsi_encoder_mode_valid(struct drm_encoder *encoder,
|
const struct drm_display_mode *mode)
|
|
{
|
struct drm_crtc *crtc = NULL;
|
struct drm_display_mode adj_mode;
|
enum drm_mode_status ret;
|
|
/*
|
* The crtc might adjust the mode, so go through the
|
* possible crtcs (technically just one) and call
|
* mode_fixup to figure out the adjusted mode before we
|
* validate it.
|
*/
|
drm_for_each_crtc(crtc, encoder->dev) {
|
/*
|
* reset adj_mode to the mode value each time,
|
* so we don't adjust the mode twice
|
*/
|
drm_mode_copy(&adj_mode, mode);
|
|
#if 0
|
/* XXX - skip this as we're just using a whitelist */
|
crtc_funcs = crtc->helper_private;
|
if (crtc_funcs && crtc_funcs->mode_fixup)
|
if (!crtc_funcs->mode_fixup(crtc, mode, &adj_mode))
|
return MODE_BAD;
|
#endif
|
ret = dsi_encoder_phy_mode_valid(encoder, &adj_mode);
|
if (ret != MODE_OK)
|
return ret;
|
}
|
return MODE_OK;
|
}
|
|
static void get_dsi_phy_ctrl(struct dw_dsi *dsi,
|
struct mipi_phy_params *phy_ctrl)
|
{
|
struct mipi_panel_info *mipi = NULL;
|
struct drm_display_mode *mode = NULL;
|
u32 dphy_req_kHz;
|
int bpp;
|
u32 id = 0;
|
u32 ui = 0;
|
u32 m_pll = 0;
|
u32 n_pll = 0;
|
u32 m_n_fract = 0;
|
u32 m_n_int = 0;
|
u64 lane_clock = 0;
|
u64 vco_div = 1;
|
|
u32 accuracy = 0;
|
u32 unit_tx_byte_clk_hs = 0;
|
u32 clk_post = 0;
|
u32 clk_pre = 0;
|
u32 clk_t_hs_exit = 0;
|
u32 clk_pre_delay = 0;
|
u32 clk_t_hs_prepare = 0;
|
u32 clk_t_lpx = 0;
|
u32 clk_t_hs_zero = 0;
|
u32 clk_t_hs_trial = 0;
|
u32 data_post_delay = 0;
|
u32 data_t_hs_prepare = 0;
|
u32 data_t_hs_zero = 0;
|
u32 data_t_hs_trial = 0;
|
u32 data_t_lpx = 0;
|
u32 clk_pre_delay_reality = 0;
|
u32 clk_t_hs_zero_reality = 0;
|
u32 clk_post_delay_reality = 0;
|
u32 data_t_hs_zero_reality = 0;
|
u32 data_post_delay_reality = 0;
|
u32 data_pre_delay_reality = 0;
|
|
WARN_ON(!phy_ctrl);
|
WARN_ON(!dsi);
|
|
id = dsi->cur_client;
|
mode = &dsi->cur_mode;
|
mipi = &dsi->mipi;
|
|
/*
|
* count phy params
|
*/
|
bpp = mipi_dsi_pixel_format_to_bpp(dsi->client[id].format);
|
if (bpp < 0)
|
return;
|
if (mode->clock > 80000)
|
dsi->client[id].lanes = 4;
|
else
|
dsi->client[id].lanes = 3;
|
if (dsi->client[id].phy_clock)
|
dphy_req_kHz = dsi->client[id].phy_clock;
|
else
|
dphy_req_kHz = mode->clock * bpp / dsi->client[id].lanes;
|
|
lane_clock = dphy_req_kHz / 1000;
|
DRM_DEBUG("Expected : lane_clock = %llu M\n", lane_clock);
|
|
/************************ PLL parameters config *********************/
|
/*
|
* chip spec :
|
* If the output data rate is below 320 Mbps,
|
* RG_BNAD_SEL should be set to 1.
|
* At this mode a post divider of 1/4 will be applied to VCO.
|
*/
|
if (lane_clock >= 320 && lane_clock <= 2500) {
|
phy_ctrl->rg_band_sel = 0; /*0x1E[2]*/
|
vco_div = 1;
|
} else if (lane_clock >= 80 && lane_clock < 320) {
|
phy_ctrl->rg_band_sel = 1;
|
vco_div = 4;
|
} else {
|
DRM_ERROR("80M <= lane_clock< = 2500M, not support lane_clock = %llu M\n",
|
lane_clock);
|
}
|
|
m_n_int = lane_clock * vco_div * 1000000UL / DEFAULT_MIPI_CLK_RATE;
|
m_n_fract = ((lane_clock * vco_div * 1000000UL * 1000UL /
|
DEFAULT_MIPI_CLK_RATE) %
|
1000) *
|
10 / 1000;
|
|
if (m_n_int % 2 == 0) {
|
if (m_n_fract * 6 >= 50) {
|
n_pll = 2;
|
m_pll = (m_n_int + 1) * n_pll;
|
} else if (m_n_fract * 6 >= 30) {
|
n_pll = 3;
|
m_pll = m_n_int * n_pll + 2;
|
} else {
|
n_pll = 1;
|
m_pll = m_n_int * n_pll;
|
}
|
} else {
|
if (m_n_fract * 6 >= 50) {
|
n_pll = 1;
|
m_pll = (m_n_int + 1) * n_pll;
|
} else if (m_n_fract * 6 >= 30) {
|
n_pll = 1;
|
m_pll = (m_n_int + 1) * n_pll;
|
} else if (m_n_fract * 6 >= 10) {
|
n_pll = 3;
|
m_pll = m_n_int * n_pll + 1;
|
} else {
|
n_pll = 2;
|
m_pll = m_n_int * n_pll;
|
}
|
}
|
|
/*if set rg_pll_enswc=1, pll_fbd_s can't be 0*/
|
if (m_pll <= 8) {
|
phy_ctrl->pll_fbd_s = 1;
|
phy_ctrl->rg_pll_enswc = 0;
|
|
if (m_pll % 2 == 0) {
|
phy_ctrl->pll_fbd_p = m_pll / 2;
|
} else {
|
if (n_pll == 1) {
|
n_pll *= 2;
|
phy_ctrl->pll_fbd_p = (m_pll * 2) / 2;
|
} else {
|
DRM_ERROR("phy m_pll not support!m_pll = %d\n",
|
m_pll);
|
return;
|
}
|
}
|
} else if (m_pll <= 300) {
|
if (m_pll % 2 == 0)
|
phy_ctrl->rg_pll_enswc = 0;
|
else
|
phy_ctrl->rg_pll_enswc = 1;
|
|
phy_ctrl->pll_fbd_s = 1;
|
phy_ctrl->pll_fbd_p = m_pll / 2;
|
} else if (m_pll <= 315) {
|
phy_ctrl->pll_fbd_p = 150;
|
phy_ctrl->pll_fbd_s = m_pll - 2 * phy_ctrl->pll_fbd_p;
|
phy_ctrl->rg_pll_enswc = 1;
|
} else {
|
DRM_ERROR("phy m_pll not support!m_pll = %d\n", m_pll);
|
return;
|
}
|
|
phy_ctrl->pll_pre_p = n_pll;
|
|
lane_clock = m_pll * (DEFAULT_MIPI_CLK_RATE / n_pll) / vco_div;
|
DRM_DEBUG("Config : lane_clock = %llu\n", lane_clock);
|
|
/*FIXME :*/
|
phy_ctrl->rg_pll_cp = 1; /*0x16[7:5]*/
|
phy_ctrl->rg_pll_cp_p = 3; /*0x1E[7:5]*/
|
|
/*test_code_0x14 other parameters config*/
|
phy_ctrl->pll_enbwt = 0; /*0x14[2]*/
|
phy_ctrl->rg_pll_chp = 0; /*0x14[1:0]*/
|
|
/*test_code_0x16 other parameters config, 0x16[3:2] reserved*/
|
phy_ctrl->pll_lpf_cs = 0; /*0x16[4]*/
|
phy_ctrl->rg_pll_refsel = 1; /*0x16[1:0]*/
|
|
/*test_code_0x1E other parameters config*/
|
phy_ctrl->reload_sel = 1; /*0x1E[4]*/
|
phy_ctrl->rg_phase_gen_en = 1; /*0x1E[3]*/
|
phy_ctrl->pll_power_down = 0; /*0x1E[1]*/
|
phy_ctrl->pll_register_override = 1; /*0x1E[0]*/
|
|
/*HSTX select VCM VREF*/
|
phy_ctrl->rg_vrefsel_vcm = 0x55;
|
if (mipi->rg_vrefsel_vcm_clk_adjust != 0)
|
phy_ctrl->rg_vrefsel_vcm = (phy_ctrl->rg_vrefsel_vcm & 0x0F) |
|
((mipi->rg_vrefsel_vcm_clk_adjust & 0x0F) << 4);
|
|
if (mipi->rg_vrefsel_vcm_data_adjust != 0)
|
phy_ctrl->rg_vrefsel_vcm = (phy_ctrl->rg_vrefsel_vcm & 0xF0) |
|
(mipi->rg_vrefsel_vcm_data_adjust & 0x0F);
|
|
/*if reload_sel = 1, need to set load_command*/
|
phy_ctrl->load_command = 0x5A;
|
|
/******************** clock/data lane parameters config ******************/
|
accuracy = 10;
|
ui = 10 * 1000000000UL * accuracy / lane_clock;
|
/*unit of measurement*/
|
unit_tx_byte_clk_hs = 8 * ui;
|
|
/* D-PHY Specification : 60ns + 52*UI <= clk_post*/
|
clk_post = 600 * accuracy + 52 * ui + mipi->clk_post_adjust * ui;
|
|
/* D-PHY Specification : clk_pre >= 8*UI*/
|
clk_pre = 8 * ui + mipi->clk_pre_adjust * ui;
|
|
/* D-PHY Specification : clk_t_hs_exit >= 100ns*/
|
clk_t_hs_exit = 1000 * accuracy + mipi->clk_t_hs_exit_adjust * ui;
|
|
/* clocked by TXBYTECLKHS*/
|
clk_pre_delay = 0 + mipi->clk_pre_delay_adjust * ui;
|
|
/* D-PHY Specification : clk_t_hs_trial >= 60ns*/
|
/* clocked by TXBYTECLKHS*/
|
clk_t_hs_trial = 600 * accuracy + 3 * unit_tx_byte_clk_hs +
|
mipi->clk_t_hs_trial_adjust * ui;
|
|
/* D-PHY Specification : 38ns <= clk_t_hs_prepare <= 95ns*/
|
/* clocked by TXBYTECLKHS*/
|
if (mipi->clk_t_hs_prepare_adjust == 0)
|
mipi->clk_t_hs_prepare_adjust = 43;
|
|
clk_t_hs_prepare =
|
((380 * accuracy + mipi->clk_t_hs_prepare_adjust * ui) <=
|
(950 * accuracy - 8 * ui)) ?
|
(380 * accuracy + mipi->clk_t_hs_prepare_adjust * ui) :
|
(950 * accuracy - 8 * ui);
|
|
/* clocked by TXBYTECLKHS*/
|
data_post_delay = 0 + mipi->data_post_delay_adjust * ui;
|
|
/* D-PHY Specification : data_t_hs_trial >= max( n*8*UI, 60ns + n*4*UI ), n = 1*/
|
/* clocked by TXBYTECLKHS*/
|
data_t_hs_trial = ((600 * accuracy + 4 * ui) >= (8 * ui) ?
|
(600 * accuracy + 4 * ui) :
|
(8 * ui)) +
|
8 * ui + 3 * unit_tx_byte_clk_hs +
|
mipi->data_t_hs_trial_adjust * ui;
|
|
/* D-PHY Specification : 40ns + 4*UI <= data_t_hs_prepare <= 85ns + 6*UI*/
|
/* clocked by TXBYTECLKHS*/
|
if (mipi->data_t_hs_prepare_adjust == 0)
|
mipi->data_t_hs_prepare_adjust = 35;
|
|
data_t_hs_prepare = ((400 * accuracy + 4 * ui +
|
mipi->data_t_hs_prepare_adjust * ui) <=
|
(850 * accuracy + 6 * ui - 8 * ui)) ?
|
(400 * accuracy + 4 * ui +
|
mipi->data_t_hs_prepare_adjust * ui) :
|
(850 * accuracy + 6 * ui - 8 * ui);
|
|
/* D-PHY chip spec : clk_t_lpx + clk_t_hs_prepare > 200ns*/
|
/* D-PHY Specification : clk_t_lpx >= 50ns*/
|
/* clocked by TXBYTECLKHS*/
|
clk_t_lpx = (((2000 * accuracy - clk_t_hs_prepare) >= 500 * accuracy) ?
|
((2000 * accuracy - clk_t_hs_prepare)) :
|
(500 * accuracy)) +
|
mipi->clk_t_lpx_adjust * ui;
|
|
/* D-PHY Specification : clk_t_hs_zero + clk_t_hs_prepare >= 300 ns*/
|
/* clocked by TXBYTECLKHS*/
|
clk_t_hs_zero = 3000 * accuracy - clk_t_hs_prepare +
|
3 * unit_tx_byte_clk_hs +
|
mipi->clk_t_hs_zero_adjust * ui;
|
|
/* D-PHY chip spec : data_t_lpx + data_t_hs_prepare > 200ns*/
|
/* D-PHY Specification : data_t_lpx >= 50ns*/
|
/* clocked by TXBYTECLKHS*/
|
data_t_lpx =
|
clk_t_lpx + mipi->data_t_lpx_adjust *
|
ui; /*2000 * accuracy - data_t_hs_prepare;*/
|
|
/* D-PHY Specification : data_t_hs_zero + data_t_hs_prepare >= 145ns + 10*UI*/
|
/* clocked by TXBYTECLKHS*/
|
data_t_hs_zero = 1450 * accuracy + 10 * ui - data_t_hs_prepare +
|
3 * unit_tx_byte_clk_hs +
|
mipi->data_t_hs_zero_adjust * ui;
|
|
phy_ctrl->clk_pre_delay = DIV_ROUND_UP(clk_pre_delay, unit_tx_byte_clk_hs);
|
phy_ctrl->clk_t_hs_prepare =
|
DIV_ROUND_UP(clk_t_hs_prepare, unit_tx_byte_clk_hs);
|
phy_ctrl->clk_t_lpx = DIV_ROUND_UP(clk_t_lpx, unit_tx_byte_clk_hs);
|
phy_ctrl->clk_t_hs_zero = DIV_ROUND_UP(clk_t_hs_zero, unit_tx_byte_clk_hs);
|
phy_ctrl->clk_t_hs_trial = DIV_ROUND_UP(clk_t_hs_trial, unit_tx_byte_clk_hs);
|
|
phy_ctrl->data_post_delay =
|
DIV_ROUND_UP(data_post_delay, unit_tx_byte_clk_hs);
|
phy_ctrl->data_t_hs_prepare =
|
DIV_ROUND_UP(data_t_hs_prepare, unit_tx_byte_clk_hs);
|
phy_ctrl->data_t_lpx = DIV_ROUND_UP(data_t_lpx, unit_tx_byte_clk_hs);
|
phy_ctrl->data_t_hs_zero = DIV_ROUND_UP(data_t_hs_zero, unit_tx_byte_clk_hs);
|
phy_ctrl->data_t_hs_trial =
|
DIV_ROUND_UP(data_t_hs_trial, unit_tx_byte_clk_hs);
|
phy_ctrl->data_t_ta_go = 4;
|
phy_ctrl->data_t_ta_get = 5;
|
|
clk_pre_delay_reality = phy_ctrl->clk_pre_delay + 2;
|
clk_t_hs_zero_reality = phy_ctrl->clk_t_hs_zero + 8;
|
data_t_hs_zero_reality = phy_ctrl->data_t_hs_zero + 4;
|
data_post_delay_reality = phy_ctrl->data_post_delay + 4;
|
|
phy_ctrl->clk_post_delay = phy_ctrl->data_t_hs_trial +
|
DIV_ROUND_UP(clk_post, unit_tx_byte_clk_hs);
|
phy_ctrl->data_pre_delay = clk_pre_delay_reality + phy_ctrl->clk_t_lpx +
|
phy_ctrl->clk_t_hs_prepare +
|
clk_t_hs_zero_reality +
|
DIV_ROUND_UP(clk_pre, unit_tx_byte_clk_hs);
|
|
clk_post_delay_reality = phy_ctrl->clk_post_delay + 4;
|
data_pre_delay_reality = phy_ctrl->data_pre_delay + 2;
|
|
phy_ctrl->clk_lane_lp2hs_time =
|
clk_pre_delay_reality + phy_ctrl->clk_t_lpx +
|
phy_ctrl->clk_t_hs_prepare + clk_t_hs_zero_reality + 3;
|
phy_ctrl->clk_lane_hs2lp_time =
|
clk_post_delay_reality + phy_ctrl->clk_t_hs_trial + 3;
|
phy_ctrl->data_lane_lp2hs_time =
|
data_pre_delay_reality + phy_ctrl->data_t_lpx +
|
phy_ctrl->data_t_hs_prepare + data_t_hs_zero_reality + 3;
|
phy_ctrl->data_lane_hs2lp_time =
|
data_post_delay_reality + phy_ctrl->data_t_hs_trial + 3;
|
phy_ctrl->phy_stop_wait_time =
|
clk_post_delay_reality + phy_ctrl->clk_t_hs_trial +
|
DIV_ROUND_UP(clk_t_hs_exit, unit_tx_byte_clk_hs) -
|
(data_post_delay_reality + phy_ctrl->data_t_hs_trial) + 3;
|
|
phy_ctrl->lane_byte_clk = lane_clock / 8;
|
phy_ctrl->clk_division =
|
(((phy_ctrl->lane_byte_clk / 2) % mipi->max_tx_esc_clk) > 0) ?
|
(phy_ctrl->lane_byte_clk / 2 / mipi->max_tx_esc_clk +
|
1) :
|
(phy_ctrl->lane_byte_clk / 2 / mipi->max_tx_esc_clk);
|
|
DRM_DEBUG("PHY clock_lane and data_lane config :\n"
|
"rg_vrefsel_vcm=%u\n"
|
"clk_pre_delay=%u\n"
|
"clk_post_delay=%u\n"
|
"clk_t_hs_prepare=%u\n"
|
"clk_t_lpx=%u\n"
|
"clk_t_hs_zero=%u\n"
|
"clk_t_hs_trial=%u\n"
|
"data_pre_delay=%u\n"
|
"data_post_delay=%u\n"
|
"data_t_hs_prepare=%u\n"
|
"data_t_lpx=%u\n"
|
"data_t_hs_zero=%u\n"
|
"data_t_hs_trial=%u\n"
|
"data_t_ta_go=%u\n"
|
"data_t_ta_get=%u\n",
|
phy_ctrl->rg_vrefsel_vcm, phy_ctrl->clk_pre_delay,
|
phy_ctrl->clk_post_delay, phy_ctrl->clk_t_hs_prepare,
|
phy_ctrl->clk_t_lpx, phy_ctrl->clk_t_hs_zero,
|
phy_ctrl->clk_t_hs_trial, phy_ctrl->data_pre_delay,
|
phy_ctrl->data_post_delay, phy_ctrl->data_t_hs_prepare,
|
phy_ctrl->data_t_lpx, phy_ctrl->data_t_hs_zero,
|
phy_ctrl->data_t_hs_trial, phy_ctrl->data_t_ta_go,
|
phy_ctrl->data_t_ta_get);
|
DRM_DEBUG("clk_lane_lp2hs_time=%u\n"
|
"clk_lane_hs2lp_time=%u\n"
|
"data_lane_lp2hs_time=%u\n"
|
"data_lane_hs2lp_time=%u\n"
|
"phy_stop_wait_time=%u\n",
|
phy_ctrl->clk_lane_lp2hs_time, phy_ctrl->clk_lane_hs2lp_time,
|
phy_ctrl->data_lane_lp2hs_time, phy_ctrl->data_lane_hs2lp_time,
|
phy_ctrl->phy_stop_wait_time);
|
}
|
|
static void dsi_set_burst_mode(void __iomem *base, unsigned long flags)
|
{
|
u32 val;
|
u32 mode_mask = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
|
MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
|
u32 non_burst_sync_pulse =
|
MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
|
u32 non_burst_sync_event = MIPI_DSI_MODE_VIDEO;
|
|
/*
|
* choose video mode type
|
*/
|
if ((flags & mode_mask) == non_burst_sync_pulse)
|
val = DSI_NON_BURST_SYNC_PULSES;
|
else if ((flags & mode_mask) == non_burst_sync_event)
|
val = DSI_NON_BURST_SYNC_EVENTS;
|
else
|
val = DSI_BURST_SYNC_PULSES_1;
|
|
set_reg(base + MIPIDSI_VID_MODE_CFG_OFFSET, val, 2, 0);
|
}
|
|
/*
|
* dsi phy reg write function
|
*/
|
static void dsi_phy_tst_set(void __iomem *base, u32 reg, u32 val)
|
{
|
u32 reg_write = 0x10000 + reg;
|
|
/*
|
* latch reg first
|
*/
|
writel(reg_write, base + MIPIDSI_PHY_TST_CTRL1_OFFSET);
|
writel(0x02, base + MIPIDSI_PHY_TST_CTRL0_OFFSET);
|
writel(0x00, base + MIPIDSI_PHY_TST_CTRL0_OFFSET);
|
|
/*
|
* then latch value
|
*/
|
writel(val, base + MIPIDSI_PHY_TST_CTRL1_OFFSET);
|
writel(0x02, base + MIPIDSI_PHY_TST_CTRL0_OFFSET);
|
writel(0x00, base + MIPIDSI_PHY_TST_CTRL0_OFFSET);
|
}
|
|
static void dsi_mipi_init(struct dw_dsi *dsi, char __iomem *mipi_dsi_base)
|
{
|
u32 hline_time = 0;
|
u32 hsa_time = 0;
|
u32 hbp_time = 0;
|
u64 pixel_clk = 0;
|
u32 i = 0;
|
u32 id = 0;
|
unsigned long dw_jiffies = 0;
|
u32 tmp = 0;
|
bool is_ready = false;
|
struct mipi_panel_info *mipi = NULL;
|
struct dss_rect rect;
|
u32 cmp_stopstate_val = 0;
|
u32 lanes;
|
|
WARN_ON(!dsi);
|
WARN_ON(!mipi_dsi_base);
|
|
id = dsi->cur_client;
|
mipi = &dsi->mipi;
|
|
if (mipi->max_tx_esc_clk == 0) {
|
DRM_ERROR("max_tx_esc_clk is invalid!");
|
mipi->max_tx_esc_clk = DEFAULT_MAX_TX_ESC_CLK;
|
}
|
|
memset(&dsi->phy, 0, sizeof(struct mipi_phy_params));
|
get_dsi_phy_ctrl(dsi, &dsi->phy);
|
|
rect.x = 0;
|
rect.y = 0;
|
rect.w = dsi->cur_mode.hdisplay;
|
rect.h = dsi->cur_mode.vdisplay;
|
lanes = dsi->client[id].lanes - 1;
|
/***************Configure the DPHY start**************/
|
|
set_reg(mipi_dsi_base + MIPIDSI_PHY_IF_CFG_OFFSET, lanes, 2, 0);
|
set_reg(mipi_dsi_base + MIPIDSI_CLKMGR_CFG_OFFSET,
|
dsi->phy.clk_division, 8, 0);
|
set_reg(mipi_dsi_base + MIPIDSI_CLKMGR_CFG_OFFSET,
|
dsi->phy.clk_division, 8, 8);
|
|
writel(0x00000000, mipi_dsi_base + MIPIDSI_PHY_RSTZ_OFFSET);
|
|
writel(0x00000000, mipi_dsi_base + MIPIDSI_PHY_TST_CTRL0_OFFSET);
|
writel(0x00000001, mipi_dsi_base + MIPIDSI_PHY_TST_CTRL0_OFFSET);
|
writel(0x00000000, mipi_dsi_base + MIPIDSI_PHY_TST_CTRL0_OFFSET);
|
|
/* physical configuration PLL I*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x14, (dsi->phy.pll_fbd_s << 4) +
|
(dsi->phy.rg_pll_enswc << 3) +
|
(dsi->phy.pll_enbwt << 2) +
|
dsi->phy.rg_pll_chp);
|
|
/* physical configuration PLL II, M*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x15, dsi->phy.pll_fbd_p);
|
|
/* physical configuration PLL III*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x16, (dsi->phy.rg_pll_cp << 5) +
|
(dsi->phy.pll_lpf_cs << 4) +
|
dsi->phy.rg_pll_refsel);
|
|
/* physical configuration PLL IV, N*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x17, dsi->phy.pll_pre_p);
|
|
/* sets the analog characteristic of V reference in D-PHY TX*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x1D, dsi->phy.rg_vrefsel_vcm);
|
|
/* MISC AFE Configuration*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x1E, (dsi->phy.rg_pll_cp_p << 5) +
|
(dsi->phy.reload_sel << 4) +
|
(dsi->phy.rg_phase_gen_en << 3) +
|
(dsi->phy.rg_band_sel << 2) +
|
(dsi->phy.pll_power_down << 1) +
|
dsi->phy.pll_register_override);
|
|
/*reload_command*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x1F, dsi->phy.load_command);
|
|
/* pre_delay of clock lane request setting*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x20,
|
DSS_REDUCE(dsi->phy.clk_pre_delay));
|
|
/* post_delay of clock lane request setting*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x21,
|
DSS_REDUCE(dsi->phy.clk_post_delay));
|
|
/* clock lane timing ctrl - t_lpx*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x22, DSS_REDUCE(dsi->phy.clk_t_lpx));
|
|
/* clock lane timing ctrl - t_hs_prepare*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x23,
|
DSS_REDUCE(dsi->phy.clk_t_hs_prepare));
|
|
/* clock lane timing ctrl - t_hs_zero*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x24,
|
DSS_REDUCE(dsi->phy.clk_t_hs_zero));
|
|
/* clock lane timing ctrl - t_hs_trial*/
|
dsi_phy_tst_set(mipi_dsi_base, 0x25, dsi->phy.clk_t_hs_trial);
|
|
for (i = 0; i <= lanes; i++) {
|
/* data lane pre_delay*/
|
tmp = 0x30 + (i << 4);
|
dsi_phy_tst_set(mipi_dsi_base, tmp,
|
DSS_REDUCE(dsi->phy.data_pre_delay));
|
|
/*data lane post_delay*/
|
tmp = 0x31 + (i << 4);
|
dsi_phy_tst_set(mipi_dsi_base, tmp,
|
DSS_REDUCE(dsi->phy.data_post_delay));
|
|
/* data lane timing ctrl - t_lpx*/
|
dsi_phy_tst_set(mipi_dsi_base, tmp,
|
DSS_REDUCE(dsi->phy.data_t_lpx));
|
|
/* data lane timing ctrl - t_hs_prepare*/
|
tmp = 0x33 + (i << 4);
|
dsi_phy_tst_set(mipi_dsi_base, tmp,
|
DSS_REDUCE(dsi->phy.data_t_hs_prepare));
|
|
/* data lane timing ctrl - t_hs_zero*/
|
tmp = 0x34 + (i << 4);
|
dsi_phy_tst_set(mipi_dsi_base, tmp,
|
DSS_REDUCE(dsi->phy.data_t_hs_zero));
|
|
/* data lane timing ctrl - t_hs_trial*/
|
tmp = 0x35 + (i << 4);
|
dsi_phy_tst_set(mipi_dsi_base, tmp,
|
DSS_REDUCE(dsi->phy.data_t_hs_trial));
|
|
/* data lane timing ctrl - t_ta_go*/
|
tmp = 0x36 + (i << 4);
|
dsi_phy_tst_set(mipi_dsi_base, tmp,
|
DSS_REDUCE(dsi->phy.data_t_ta_go));
|
|
/* data lane timing ctrl - t_ta_get*/
|
tmp = 0x37 + (i << 4);
|
dsi_phy_tst_set(mipi_dsi_base, tmp,
|
DSS_REDUCE(dsi->phy.data_t_ta_get));
|
}
|
|
writel(0x00000007, mipi_dsi_base + MIPIDSI_PHY_RSTZ_OFFSET);
|
|
is_ready = false;
|
dw_jiffies = jiffies + HZ / 2;
|
do {
|
tmp = readl(mipi_dsi_base + MIPIDSI_PHY_STATUS_OFFSET);
|
if ((tmp & 0x00000001) == 0x00000001) {
|
is_ready = true;
|
break;
|
}
|
} while (time_after(dw_jiffies, jiffies));
|
|
if (!is_ready)
|
DRM_ERROR("phylock is not ready!MIPIDSI_PHY_STATUS_OFFSET=0x%x.\n",
|
tmp);
|
|
if (lanes >= DSI_4_LANES)
|
cmp_stopstate_val = (BIT(4) | BIT(7) | BIT(9) | BIT(11));
|
else if (lanes >= DSI_3_LANES)
|
cmp_stopstate_val = (BIT(4) | BIT(7) | BIT(9));
|
else if (lanes >= DSI_2_LANES)
|
cmp_stopstate_val = (BIT(4) | BIT(7));
|
else
|
cmp_stopstate_val = (BIT(4));
|
|
is_ready = false;
|
dw_jiffies = jiffies + HZ / 2;
|
do {
|
tmp = readl(mipi_dsi_base + MIPIDSI_PHY_STATUS_OFFSET);
|
if ((tmp & cmp_stopstate_val) == cmp_stopstate_val) {
|
is_ready = true;
|
break;
|
}
|
} while (time_after(dw_jiffies, jiffies));
|
|
if (!is_ready)
|
DRM_ERROR("phystopstateclklane is not ready! MIPIDSI_PHY_STATUS_OFFSET=0x%x.\n",
|
tmp);
|
|
/*************************Configure the DPHY end*************************/
|
|
/* phy_stop_wait_time*/
|
set_reg(mipi_dsi_base + MIPIDSI_PHY_IF_CFG_OFFSET,
|
dsi->phy.phy_stop_wait_time, 8, 8);
|
|
/*--------------configuring the DPI packet transmission----------------*/
|
/*
|
* 2. Configure the DPI Interface:
|
* This defines how the DPI interface interacts with the controller.
|
*/
|
set_reg(mipi_dsi_base + MIPIDSI_DPI_VCID_OFFSET, mipi->vc, 2, 0);
|
set_reg(mipi_dsi_base + MIPIDSI_DPI_COLOR_CODING_OFFSET,
|
mipi->color_mode, 4, 0);
|
|
set_reg(mipi_dsi_base + MIPIDSI_DPI_CFG_POL_OFFSET,
|
dsi->ldi.data_en_plr, 1, 0);
|
set_reg(mipi_dsi_base + MIPIDSI_DPI_CFG_POL_OFFSET, dsi->ldi.vsync_plr,
|
1, 1);
|
set_reg(mipi_dsi_base + MIPIDSI_DPI_CFG_POL_OFFSET, dsi->ldi.hsync_plr,
|
1, 2);
|
set_reg(mipi_dsi_base + MIPIDSI_DPI_CFG_POL_OFFSET, 0x0, 1, 3);
|
set_reg(mipi_dsi_base + MIPIDSI_DPI_CFG_POL_OFFSET, 0x0, 1, 4);
|
|
/*
|
* 3. Select the Video Transmission Mode:
|
* This defines how the processor requires the video line to be
|
* transported through the DSI link.
|
*/
|
/* video mode: low power mode*/
|
set_reg(mipi_dsi_base + MIPIDSI_VID_MODE_CFG_OFFSET, 0x3f, 6, 8);
|
/* set_reg(mipi_dsi_base + MIPIDSI_VID_MODE_CFG_OFFSET, 0x0, 1, 14); */
|
|
/* TODO: fix blank display bug when set backlight*/
|
set_reg(mipi_dsi_base + MIPIDSI_DPI_LP_CMD_TIM_OFFSET, 0x4, 8, 16);
|
/* video mode: send read cmd by lp mode*/
|
set_reg(mipi_dsi_base + MIPIDSI_VID_MODE_CFG_OFFSET, 0x1, 1, 15);
|
|
set_reg(mipi_dsi_base + MIPIDSI_VID_PKT_SIZE_OFFSET, rect.w, 14, 0);
|
|
/* burst mode*/
|
dsi_set_burst_mode(mipi_dsi_base, dsi->client[id].mode_flags);
|
/* for dsi read, BTA enable*/
|
set_reg(mipi_dsi_base + MIPIDSI_PCKHDL_CFG_OFFSET, 0x1, 1, 2);
|
|
/*
|
* 4. Define the DPI Horizontal timing configuration:
|
*
|
* Hsa_time = HSA*(PCLK period/Clk Lane Byte Period);
|
* Hbp_time = HBP*(PCLK period/Clk Lane Byte Period);
|
* Hline_time = (HSA+HBP+HACT+HFP)*(PCLK period/Clk Lane Byte Period);
|
*/
|
pixel_clk = dsi->cur_mode.clock * 1000;
|
/*htot = dsi->cur_mode.htotal;*/
|
/*vtot = dsi->cur_mode.vtotal;*/
|
dsi->ldi.h_front_porch =
|
dsi->cur_mode.hsync_start - dsi->cur_mode.hdisplay;
|
dsi->ldi.h_back_porch = dsi->cur_mode.htotal - dsi->cur_mode.hsync_end;
|
dsi->ldi.h_pulse_width =
|
dsi->cur_mode.hsync_end - dsi->cur_mode.hsync_start;
|
dsi->ldi.v_front_porch =
|
dsi->cur_mode.vsync_start - dsi->cur_mode.vdisplay;
|
dsi->ldi.v_back_porch = dsi->cur_mode.vtotal - dsi->cur_mode.vsync_end;
|
dsi->ldi.v_pulse_width =
|
dsi->cur_mode.vsync_end - dsi->cur_mode.vsync_start;
|
if (dsi->ldi.v_pulse_width > 15) {
|
DRM_DEBUG_DRIVER("vsw exceeded 15\n");
|
dsi->ldi.v_pulse_width = 15;
|
}
|
hsa_time = dsi->ldi.h_pulse_width * dsi->phy.lane_byte_clk / pixel_clk;
|
hbp_time = dsi->ldi.h_back_porch * dsi->phy.lane_byte_clk / pixel_clk;
|
hline_time = DIV_ROUND_UP((dsi->ldi.h_pulse_width + dsi->ldi.h_back_porch +
|
rect.w + dsi->ldi.h_front_porch) *
|
dsi->phy.lane_byte_clk,
|
pixel_clk);
|
|
DRM_DEBUG("hsa_time=%d, hbp_time=%d, hline_time=%d\n", hsa_time,
|
hbp_time, hline_time);
|
DRM_DEBUG("lane_byte_clk=%llu, pixel_clk=%llu\n", dsi->phy.lane_byte_clk,
|
pixel_clk);
|
set_reg(mipi_dsi_base + MIPIDSI_VID_HSA_TIME_OFFSET, hsa_time, 12, 0);
|
set_reg(mipi_dsi_base + MIPIDSI_VID_HBP_TIME_OFFSET, hbp_time, 12, 0);
|
set_reg(mipi_dsi_base + MIPIDSI_VID_HLINE_TIME_OFFSET, hline_time, 15,
|
0);
|
|
/* Define the Vertical line configuration*/
|
set_reg(mipi_dsi_base + MIPIDSI_VID_VSA_LINES_OFFSET,
|
dsi->ldi.v_pulse_width, 10, 0);
|
set_reg(mipi_dsi_base + MIPIDSI_VID_VBP_LINES_OFFSET,
|
dsi->ldi.v_back_porch, 10, 0);
|
set_reg(mipi_dsi_base + MIPIDSI_VID_VFP_LINES_OFFSET,
|
dsi->ldi.v_front_porch, 10, 0);
|
set_reg(mipi_dsi_base + MIPIDSI_VID_VACTIVE_LINES_OFFSET, rect.h, 14,
|
0);
|
set_reg(mipi_dsi_base + MIPIDSI_TO_CNT_CFG_OFFSET, 0x7FF, 16, 0);
|
|
/* Configure core's phy parameters*/
|
set_reg(mipi_dsi_base + MIPIDSI_PHY_TMR_LPCLK_CFG_OFFSET,
|
dsi->phy.clk_lane_lp2hs_time, 10, 0);
|
set_reg(mipi_dsi_base + MIPIDSI_PHY_TMR_LPCLK_CFG_OFFSET,
|
dsi->phy.clk_lane_hs2lp_time, 10, 16);
|
|
set_reg(mipi_dsi_base + MIPIDSI_PHY_TMR_RD_CFG_OFFSET, 0x7FFF, 15, 0);
|
set_reg(mipi_dsi_base + MIPIDSI_PHY_TMR_CFG_OFFSET,
|
dsi->phy.data_lane_lp2hs_time, 10, 0);
|
set_reg(mipi_dsi_base + MIPIDSI_PHY_TMR_CFG_OFFSET,
|
dsi->phy.data_lane_hs2lp_time, 10, 16);
|
|
/* Waking up Core*/
|
set_reg(mipi_dsi_base + MIPIDSI_PWR_UP_OFFSET, 0x1, 1, 0);
|
}
|
|
static int mipi_dsi_on_sub1(struct dw_dsi *dsi, char __iomem *mipi_dsi_base)
|
{
|
/* mipi init */
|
dsi_mipi_init(dsi, mipi_dsi_base);
|
DRM_DEBUG("dsi_mipi_init ok\n");
|
/* switch to cmd mode */
|
set_reg(mipi_dsi_base + MIPIDSI_MODE_CFG_OFFSET, 0x1, 1, 0);
|
/* cmd mode: low power mode */
|
set_reg(mipi_dsi_base + MIPIDSI_CMD_MODE_CFG_OFFSET, 0x7f, 7, 8);
|
set_reg(mipi_dsi_base + MIPIDSI_CMD_MODE_CFG_OFFSET, 0xf, 4, 16);
|
set_reg(mipi_dsi_base + MIPIDSI_CMD_MODE_CFG_OFFSET, 0x1, 1, 24);
|
/* disable generate High Speed clock */
|
/* delete? */
|
set_reg(mipi_dsi_base + MIPIDSI_LPCLK_CTRL_OFFSET, 0x0, 1, 0);
|
|
return 0;
|
}
|
|
static int mipi_dsi_on_sub2(struct dw_dsi *dsi, char __iomem *mipi_dsi_base)
|
{
|
/* switch to video mode */
|
set_reg(mipi_dsi_base + MIPIDSI_MODE_CFG_OFFSET, 0x0, 1, 0);
|
|
/* enable EOTP TX */
|
set_reg(mipi_dsi_base + MIPIDSI_PCKHDL_CFG_OFFSET, 0x1, 1, 0);
|
|
/* enable generate High Speed clock, continue clock */
|
set_reg(mipi_dsi_base + MIPIDSI_LPCLK_CTRL_OFFSET, 0x1, 2, 0);
|
|
return 0;
|
}
|
|
static void dsi_encoder_enable_sub(struct drm_encoder *encoder)
|
{
|
struct dw_dsi *dsi = encoder_to_dsi(encoder);
|
struct dsi_hw_ctx *ctx = dsi->ctx;
|
int ret;
|
|
if (dsi->enable)
|
return;
|
|
ret = clk_prepare_enable(ctx->dss_dphy0_ref_clk);
|
if (ret) {
|
DRM_ERROR("fail to enable dss_dphy0_ref_clk: %d\n", ret);
|
return;
|
}
|
|
ret = clk_prepare_enable(ctx->dss_dphy0_cfg_clk);
|
if (ret) {
|
DRM_ERROR("fail to enable dss_dphy0_cfg_clk: %d\n", ret);
|
return;
|
}
|
|
ret = clk_prepare_enable(ctx->dss_pclk_dsi0_clk);
|
if (ret) {
|
DRM_ERROR("fail to enable dss_pclk_dsi0_clk: %d\n", ret);
|
return;
|
}
|
|
mipi_dsi_on_sub1(dsi, ctx->base);
|
|
mipi_dsi_on_sub2(dsi, ctx->base);
|
}
|
|
static int dsi_host_attach(struct mipi_dsi_host *host,
|
struct mipi_dsi_device *mdsi)
|
{
|
struct dw_dsi *dsi = host_to_dsi(host);
|
u32 id = mdsi->channel >= 1 ? OUT_PANEL : OUT_HDMI;
|
|
if (mdsi->lanes < 1 || mdsi->lanes > 4) {
|
DRM_ERROR("dsi device params invalid\n");
|
return -EINVAL;
|
}
|
|
dsi->client[id].lanes = mdsi->lanes;
|
dsi->client[id].format = mdsi->format;
|
dsi->client[id].mode_flags = mdsi->mode_flags;
|
dsi->client[id].phy_clock = 0; //mdsi->phy_clock;
|
|
DRM_DEBUG("host attach, client name=[%s], id=%d\n", mdsi->name, id);
|
|
return 0;
|
}
|
|
static int dsi_host_detach(struct mipi_dsi_host *host,
|
struct mipi_dsi_device *mdsi)
|
{
|
/* do nothing */
|
return 0;
|
}
|
|
static int dsi_gen_pkt_hdr_write(void __iomem *base, u32 val)
|
{
|
u32 status;
|
int ret;
|
|
ret = readx_poll_timeout(readl, base + CMD_PKT_STATUS, status,
|
!(status & GEN_CMD_FULL), 1000,
|
CMD_PKT_STATUS_TIMEOUT_US);
|
if (ret < 0) {
|
DRM_ERROR("failed to get available command FIFO\n");
|
return ret;
|
}
|
|
writel(val, base + GEN_HDR);
|
|
ret = readx_poll_timeout(readl, base + CMD_PKT_STATUS, status,
|
status & (GEN_CMD_EMPTY | GEN_PLD_W_EMPTY),
|
1000, CMD_PKT_STATUS_TIMEOUT_US);
|
if (ret < 0) {
|
DRM_ERROR("failed to write command FIFO\n");
|
return ret;
|
}
|
|
return 0;
|
}
|
|
static int dsi_dcs_short_write(void __iomem *base,
|
const struct mipi_dsi_msg *msg)
|
{
|
const u16 *tx_buf = msg->tx_buf;
|
u32 val = GEN_HDATA(*tx_buf) | GEN_HTYPE(msg->type);
|
|
if (msg->tx_len > 2) {
|
DRM_ERROR("too long tx buf length %zu for short write\n",
|
msg->tx_len);
|
return -EINVAL;
|
}
|
|
return dsi_gen_pkt_hdr_write(base, val);
|
}
|
|
static int dsi_dcs_long_write(void __iomem *base,
|
const struct mipi_dsi_msg *msg)
|
{
|
const u32 *tx_buf = msg->tx_buf;
|
int len = msg->tx_len, pld_data_bytes = sizeof(*tx_buf), ret;
|
u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
|
u32 remainder = 0;
|
u32 status;
|
|
if (msg->tx_len < 3) {
|
DRM_ERROR("wrong tx buf length %zu for long write\n",
|
msg->tx_len);
|
return -EINVAL;
|
}
|
|
while (DIV_ROUND_UP(len, pld_data_bytes)) {
|
if (len < pld_data_bytes) {
|
memcpy(&remainder, tx_buf, len);
|
writel(remainder, base + GEN_PLD_DATA);
|
len = 0;
|
} else {
|
writel(*tx_buf, base + GEN_PLD_DATA);
|
tx_buf++;
|
len -= pld_data_bytes;
|
}
|
|
ret = readx_poll_timeout(readl, base + CMD_PKT_STATUS, status,
|
!(status & GEN_PLD_W_FULL), 1000,
|
CMD_PKT_STATUS_TIMEOUT_US);
|
if (ret < 0) {
|
DRM_ERROR("failed to get available write payload FIFO\n");
|
return ret;
|
}
|
}
|
|
return dsi_gen_pkt_hdr_write(base, val);
|
}
|
|
static ssize_t dsi_host_transfer(struct mipi_dsi_host *host,
|
const struct mipi_dsi_msg *msg)
|
{
|
struct dw_dsi *dsi = host_to_dsi(host);
|
struct dsi_hw_ctx *ctx = dsi->ctx;
|
void __iomem *base = ctx->base;
|
int ret;
|
|
switch (msg->type) {
|
case MIPI_DSI_DCS_SHORT_WRITE:
|
case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
|
case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE:
|
ret = dsi_dcs_short_write(base, msg);
|
break;
|
case MIPI_DSI_DCS_LONG_WRITE:
|
ret = dsi_dcs_long_write(base, msg);
|
break;
|
default:
|
DRM_ERROR("unsupported message type\n");
|
ret = -EINVAL;
|
}
|
|
return ret;
|
}
|
|
static const struct mipi_dsi_host_ops dsi_host_ops = {
|
.attach = dsi_host_attach,
|
.detach = dsi_host_detach,
|
.transfer = dsi_host_transfer,
|
};
|
|
static int dsi_host_init(struct device *dev, struct dw_dsi *dsi)
|
{
|
struct mipi_dsi_host *host = &dsi->host;
|
struct mipi_panel_info *mipi = &dsi->mipi;
|
int ret;
|
|
host->dev = dev;
|
host->ops = &dsi_host_ops;
|
|
mipi->max_tx_esc_clk = 10 * 1000000UL;
|
mipi->vc = 0;
|
mipi->color_mode = DSI_24BITS_1;
|
mipi->clk_post_adjust = 120;
|
mipi->clk_pre_adjust = 0;
|
mipi->clk_t_hs_prepare_adjust = 0;
|
mipi->clk_t_lpx_adjust = 0;
|
mipi->clk_t_hs_trial_adjust = 0;
|
mipi->clk_t_hs_exit_adjust = 0;
|
mipi->clk_t_hs_zero_adjust = 0;
|
|
dsi->ldi.data_en_plr = 0;
|
dsi->ldi.vsync_plr = 0;
|
dsi->ldi.hsync_plr = 0;
|
|
ret = mipi_dsi_host_register(host);
|
if (ret) {
|
DRM_ERROR("failed to register dsi host\n");
|
return ret;
|
}
|
|
return 0;
|
}
|
|
static int dsi_parse_bridge_endpoint(struct dw_dsi *dsi,
|
struct device_node *endpoint)
|
{
|
struct device_node *bridge_node;
|
struct drm_bridge *bridge;
|
|
bridge_node = of_graph_get_remote_port_parent(endpoint);
|
if (!bridge_node) {
|
DRM_ERROR("no valid bridge node\n");
|
return -ENODEV;
|
}
|
of_node_put(bridge_node);
|
|
bridge = of_drm_find_bridge(bridge_node);
|
if (!bridge) {
|
DRM_DEBUG("wait for external HDMI bridge driver.\n");
|
return -EPROBE_DEFER;
|
}
|
dsi->bridge = bridge;
|
|
return 0;
|
}
|
|
static int dsi_parse_panel_endpoint(struct dw_dsi *dsi,
|
struct device_node *endpoint)
|
{
|
struct device_node *panel_node;
|
struct drm_panel *panel;
|
|
panel_node = of_graph_get_remote_port_parent(endpoint);
|
if (!panel_node) {
|
DRM_ERROR("no valid panel node\n");
|
return -ENODEV;
|
}
|
of_node_put(panel_node);
|
|
panel = of_drm_find_panel(panel_node);
|
if (IS_ERR(panel)) {
|
DRM_DEBUG_DRIVER("skip this panel endpoint.\n");
|
return 0;
|
}
|
dsi->panel = panel;
|
|
return 0;
|
}
|
|
static int dsi_parse_endpoint(struct dw_dsi *dsi, struct device_node *np,
|
enum dsi_output_client client)
|
{
|
struct device_node *ep_node;
|
struct of_endpoint ep;
|
int ret = 0;
|
|
if (client == OUT_MAX)
|
return -EINVAL;
|
|
for_each_endpoint_of_node(np, ep_node) {
|
ret = of_graph_parse_endpoint(ep_node, &ep);
|
if (ret) {
|
of_node_put(ep_node);
|
return ret;
|
}
|
|
/* skip dsi input port, port == 0 is input port */
|
if (ep.port == 0)
|
continue;
|
|
/* parse bridge endpoint */
|
if (client == OUT_HDMI) {
|
if (ep.id == 0) {
|
ret = dsi_parse_bridge_endpoint(dsi, ep_node);
|
if (dsi->bridge)
|
break;
|
}
|
} else { /* parse panel endpoint */
|
if (ep.id > 0) {
|
ret = dsi_parse_panel_endpoint(dsi, ep_node);
|
if (dsi->panel)
|
break;
|
}
|
}
|
|
if (ret) {
|
of_node_put(ep_node);
|
return ret;
|
}
|
}
|
|
if (!dsi->bridge && !dsi->panel) {
|
DRM_ERROR("at least one bridge or panel node is required\n");
|
return -ENODEV;
|
}
|
|
return 0;
|
}
|
|
static int dsi_parse_dt(struct platform_device *pdev, struct dw_dsi *dsi)
|
{
|
struct dsi_hw_ctx *ctx = dsi->ctx;
|
int ret = 0;
|
struct device_node *np = pdev->dev.of_node;
|
|
/* parse HDMI bridge endpoint */
|
ret = dsi_parse_endpoint(dsi, np, OUT_HDMI);
|
if (ret)
|
return ret;
|
|
/* parse panel endpoint */
|
ret = dsi_parse_endpoint(dsi, np, OUT_PANEL);
|
if (ret)
|
return ret;
|
|
np = of_find_compatible_node(NULL, NULL, DTS_COMP_DSI_NAME);
|
if (!np) {
|
DRM_ERROR("NOT FOUND device node %s!\n", DTS_COMP_DSI_NAME);
|
return -ENXIO;
|
}
|
|
ctx->base = of_iomap(np, 0);
|
if (!(ctx->base)) {
|
DRM_ERROR("failed to get base resource.\n");
|
return -ENXIO;
|
}
|
|
ctx->peri_crg_base = of_iomap(np, 1);
|
if (!(ctx->peri_crg_base)) {
|
DRM_ERROR("failed to get peri_crg_base resource.\n");
|
return -ENXIO;
|
}
|
|
dsi->gpio_mux = devm_gpiod_get(&pdev->dev, "mux", GPIOD_OUT_HIGH);
|
if (IS_ERR(dsi->gpio_mux))
|
return PTR_ERR(dsi->gpio_mux);
|
/* set dsi default output to panel */
|
dsi->cur_client = OUT_PANEL;
|
|
/*dis-reset*/
|
/*ip_reset_dis_dsi0, ip_reset_dis_dsi1*/
|
writel(0x30000000, ctx->peri_crg_base + PERRSTDIS3);
|
|
ctx->dss_dphy0_ref_clk = devm_clk_get(&pdev->dev, "clk_txdphy0_ref");
|
if (IS_ERR(ctx->dss_dphy0_ref_clk)) {
|
DRM_ERROR("failed to get dss_dphy0_ref_clk clock\n");
|
return PTR_ERR(ctx->dss_dphy0_ref_clk);
|
}
|
|
ret = clk_set_rate(ctx->dss_dphy0_ref_clk, DEFAULT_MIPI_CLK_RATE);
|
if (ret < 0) {
|
DRM_ERROR("dss_dphy0_ref_clk clk_set_rate(%lu) failed, error=%d!\n",
|
DEFAULT_MIPI_CLK_RATE, ret);
|
return -EINVAL;
|
}
|
|
DRM_DEBUG("dss_dphy0_ref_clk:[%lu]->[%lu].\n", DEFAULT_MIPI_CLK_RATE,
|
clk_get_rate(ctx->dss_dphy0_ref_clk));
|
|
ctx->dss_dphy0_cfg_clk = devm_clk_get(&pdev->dev, "clk_txdphy0_cfg");
|
if (IS_ERR(ctx->dss_dphy0_cfg_clk)) {
|
DRM_ERROR("failed to get dss_dphy0_cfg_clk clock\n");
|
return PTR_ERR(ctx->dss_dphy0_cfg_clk);
|
}
|
|
ret = clk_set_rate(ctx->dss_dphy0_cfg_clk, DEFAULT_MIPI_CLK_RATE);
|
if (ret < 0) {
|
DRM_ERROR(
|
"dss_dphy0_cfg_clk clk_set_rate(%lu) failed, error=%d!\n",
|
DEFAULT_MIPI_CLK_RATE, ret);
|
return -EINVAL;
|
}
|
|
DRM_DEBUG("dss_dphy0_cfg_clk:[%lu]->[%lu].\n", DEFAULT_MIPI_CLK_RATE,
|
clk_get_rate(ctx->dss_dphy0_cfg_clk));
|
|
ctx->dss_pclk_dsi0_clk = devm_clk_get(&pdev->dev, "pclk_dsi0");
|
if (IS_ERR(ctx->dss_pclk_dsi0_clk)) {
|
DRM_ERROR("failed to get dss_pclk_dsi0_clk clock\n");
|
return PTR_ERR(ctx->dss_pclk_dsi0_clk);
|
}
|
|
return 0;
|
}
|
|
const struct kirin_dsi_ops kirin_dsi_960 = {
|
.version = KIRIN960_DSI,
|
.parse_dt = dsi_parse_dt,
|
.host_init = dsi_host_init,
|
.encoder_enable = dsi_encoder_enable_sub,
|
.encoder_valid = dsi_encoder_mode_valid
|
};
|
|
MODULE_DESCRIPTION("DesignWare MIPI DSI Host Controller v1.02 driver");
|
MODULE_LICENSE("GPL v2");
|