blob: 30c8e28269a52a28c48f05cc67276d4f1410a598 [file] [log] [blame]
/*
* Copyright (C) 2016 Freescale Semiconductor, Inc. All Rights Reserved.
* Copyright 2017 NXP
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <malloc.h>
#include <asm/arch/clock.h>
#include <asm/arch/sys_proto.h>
#include <linux/errno.h>
#include <asm/io.h>
#include <linux/string.h>
#include "mipi_dsi_northwest_regs.h"
#include <mipi_dsi_northwest.h>
#include <mipi_display.h>
#define MIPI_LCD_SLEEP_MODE_DELAY (120)
#define MIPI_FIFO_TIMEOUT 250000 /* 250ms */
enum mipi_dsi_mode {
DSI_COMMAND_MODE,
DSI_VIDEO_MODE
};
#define DSI_LP_MODE 0
#define DSI_HS_MODE 1
enum mipi_dsi_payload {
DSI_PAYLOAD_CMD,
DSI_PAYLOAD_VIDEO,
};
/**
* board_mipi_panel_reset - give a reset cycle for mipi dsi panel
*
* Target board specific, like use gpio to reset the dsi panel
* Machine board file overrides board_mipi_panel_reset
*
* Return: 0 Success
*/
int __weak board_mipi_panel_reset(void)
{
return 0;
}
/**
* board_mipi_panel_shutdown - Shut down the mipi dsi panel
*
* Target board specific, like use gpio to shut down the dsi panel
* Machine board file overrides board_mipi_panel_shutdown
*
* Return: 0 Success
*/
int __weak board_mipi_panel_shutdown(void)
{
return 0;
}
static void mipi_dsi_set_mode(struct mipi_dsi_northwest_info *mipi_dsi,
uint8_t mode);
static int mipi_dsi_dcs_cmd(struct mipi_dsi_northwest_info *mipi_dsi,
u8 cmd, const u32 *param, int num);
static void mipi_dsi_set_mode(struct mipi_dsi_northwest_info *mipi_dsi,
uint8_t mode)
{
switch (mode) {
case DSI_LP_MODE:
writel(0x1, mipi_dsi->mmio_base + HOST_CFG_NONCONTINUOUS_CLK);
break;
case DSI_HS_MODE:
writel(0x0, mipi_dsi->mmio_base + HOST_CFG_NONCONTINUOUS_CLK);
break;
default:
printf("invalid dsi mode\n");
return;
}
mdelay(1);
}
static int mipi_dsi_dphy_init(struct mipi_dsi_northwest_info *mipi_dsi)
{
uint32_t time_out = 100;
uint32_t CN, CM, CO;
uint32_t lock;
setbits_le32(mipi_dsi->sim_base + SIM_SOPT1, MIPI_ISO_DISABLE);
/* According to the RM, the dpi_pclk and clk_byte frequencies are related by the following formula:
* clk_byte_freq >= dpi_pclk_freq * DPI_pixel_size / ( 8 * (cfg_num_lanes + 1))
*/
/* PLL out clock = refclk * CM / (CN * CO)
* refclock = 24MHz
* pll vco = 24 * 40 / (3 * 1) = 320MHz
*/
CN = 0x10; /* 3 */
CM = 0xc8; /* 40 */
CO = 0x0; /* 1 */
writel(CN, mipi_dsi->mmio_base + DPHY_CN);
writel(CM, mipi_dsi->mmio_base + DPHY_CM);
writel(CO, mipi_dsi->mmio_base + DPHY_CO);
writel(0x25, mipi_dsi->mmio_base + DPHY_TST);
writel(0x0, mipi_dsi->mmio_base + DPHY_PD_PLL);
while (!(lock = readl(mipi_dsi->mmio_base + DPHY_LOCK))) {
udelay(10);
time_out--;
if (time_out == 0) {
printf("cannot get the dphy lock = 0x%x\n", lock);
return -EINVAL;
}
}
debug("%s: dphy lock = 0x%x\n", __func__, lock);
writel(0x0, mipi_dsi->mmio_base + DPHY_LOCK_BYP);
writel(0x1, mipi_dsi->mmio_base + DPHY_RTERM_SEL);
writel(0x0, mipi_dsi->mmio_base + DPHY_AUTO_PD_EN);
writel(0x1, mipi_dsi->mmio_base + DPHY_RXLPRP);
writel(0x1, mipi_dsi->mmio_base + DPHY_RXCDRP);
writel(0x0, mipi_dsi->mmio_base + DPHY_M_PRG_HS_PREPARE);
writel(0x0, mipi_dsi->mmio_base + DPHY_MC_PRG_HS_PREPARE);
writel(0x9, mipi_dsi->mmio_base + DPHY_M_PRG_HS_ZERO);
writel(0x20, mipi_dsi->mmio_base + DPHY_MC_PRG_HS_ZERO);
writel(0x5, mipi_dsi->mmio_base + DPHY_M_PRG_HS_TRAIL);
writel(0x5, mipi_dsi->mmio_base + DPHY_MC_PRG_HS_TRAIL);
writel(0x0, mipi_dsi->mmio_base + DPHY_PD_DPHY);
setbits_le32(mipi_dsi->sim_base + SIM_SOPT1CFG, DSI_PLL_EN);
return 0;
}
static int mipi_dsi_host_init(struct mipi_dsi_northwest_info *mipi_dsi)
{
uint32_t lane_num;
switch (mipi_dsi->dsi_panel_dev->data_lane_num) {
case 1:
lane_num = 0x0;
break;
case 2:
lane_num = 0x1;
break;
default:
/* Invalid lane num */
return -EINVAL;
}
writel(lane_num, mipi_dsi->mmio_base + HOST_CFG_NUM_LANES);
writel(0x1, mipi_dsi->mmio_base + HOST_CFG_NONCONTINUOUS_CLK);
writel(0x1, mipi_dsi->mmio_base + HOST_CFG_T_PRE);
writel(52, mipi_dsi->mmio_base + HOST_CFG_T_POST);
writel(13, mipi_dsi->mmio_base + HOST_CFG_TX_GAP);
writel(0x1, mipi_dsi->mmio_base + HOST_CFG_AUTOINSERT_EOTP);
writel(0x0, mipi_dsi->mmio_base + HOST_CFG_EXTRA_CMDS_AFTER_EOTP);
writel(0x0, mipi_dsi->mmio_base + HOST_CFG_HTX_TO_COUNT);
writel(0x0, mipi_dsi->mmio_base + HOST_CFG_LRX_H_TO_COUNT);
writel(0x0, mipi_dsi->mmio_base + HOST_CFG_BTA_H_TO_COUNT);
writel(0x3A98, mipi_dsi->mmio_base + HOST_CFG_TWAKEUP);
return 0;
}
static int mipi_dsi_dpi_init(struct mipi_dsi_northwest_info *mipi_dsi)
{
uint32_t bpp, color_coding, pixel_fmt;
struct fb_videomode *mode = &(mipi_dsi->dsi_panel_dev->mode);
bpp = mipi_dsi->dsi_panel_dev->bpp;
writel(mode->xres, mipi_dsi->mmio_base + DPI_PIXEL_PAYLOAD_SIZE);
writel(mode->xres, mipi_dsi->mmio_base + DPI_PIXEL_FIFO_SEND_LEVEL);
switch (bpp) {
case 24:
color_coding = 5;
pixel_fmt = 3;
break;
case 16:
case 18:
default:
/* Not supported */
return -EINVAL;
}
writel(color_coding, mipi_dsi->mmio_base + DPI_INTERFACE_COLOR_CODING);
writel(pixel_fmt, mipi_dsi->mmio_base + DPI_PIXEL_FORMAT);
writel(0x0, mipi_dsi->mmio_base + DPI_VSYNC_POLARITY);
writel(0x0, mipi_dsi->mmio_base + DPI_HSYNC_POLARITY);
writel(0x2, mipi_dsi->mmio_base + DPI_VIDEO_MODE);
writel(mode->right_margin * (bpp >> 3), mipi_dsi->mmio_base + DPI_HFP);
writel(mode->left_margin * (bpp >> 3), mipi_dsi->mmio_base + DPI_HBP);
writel(mode->hsync_len * (bpp >> 3), mipi_dsi->mmio_base + DPI_HSA);
writel(0x0, mipi_dsi->mmio_base + DPI_ENABLE_MULT_PKTS);
writel(mode->upper_margin, mipi_dsi->mmio_base + DPI_VBP);
writel(mode->lower_margin, mipi_dsi->mmio_base + DPI_VFP);
writel(0x1, mipi_dsi->mmio_base + DPI_BLLP_MODE);
writel(0x0, mipi_dsi->mmio_base + DPI_USE_NULL_PKT_BLLP);
writel(mode->yres - 1, mipi_dsi->mmio_base + DPI_VACTIVE);
writel(0x0, mipi_dsi->mmio_base + DPI_VC);
return 0;
}
static void mipi_dsi_init_interrupt(struct mipi_dsi_northwest_info *mipi_dsi)
{
/* disable all the irqs */
writel(0xffffffff, mipi_dsi->mmio_base + HOST_IRQ_MASK);
writel(0x7, mipi_dsi->mmio_base + HOST_IRQ_MASK2);
}
static int mipi_display_enter_sleep(struct mipi_dsi_northwest_info *mipi_dsi)
{
int err;
err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_SET_DISPLAY_OFF,
NULL, 0);
if (err)
return -EINVAL;
mdelay(50);
err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_ENTER_SLEEP_MODE,
NULL, 0);
if (err)
printf("MIPI DSI DCS Command sleep in error!\n");
mdelay(MIPI_LCD_SLEEP_MODE_DELAY);
return err;
}
static int mipi_dsi_enable(struct mipi_dsi_northwest_info *mipi_dsi)
{
int ret;
/* Assert resets */
/* escape domain */
clrbits_le32(mipi_dsi->sim_base + SIM_SOPT1CFG, DSI_RST_ESC_N);
/* byte domain */
clrbits_le32(mipi_dsi->sim_base + SIM_SOPT1CFG, DSI_RST_BYTE_N);
/* dpi domain */
clrbits_le32(mipi_dsi->sim_base + SIM_SOPT1CFG, DSI_RST_DPI_N);
/* Enable mipi relevant clocks */
enable_mipi_dsi_clk(1);
ret = mipi_dsi_dphy_init(mipi_dsi);
if (ret < 0)
return ret;
ret = mipi_dsi_host_init(mipi_dsi);
if (ret < 0)
return ret;
ret = mipi_dsi_dpi_init(mipi_dsi);
if (ret < 0)
return ret;
/* Deassert resets */
/* escape domain */
setbits_le32(mipi_dsi->sim_base + SIM_SOPT1CFG, DSI_RST_ESC_N);
/* byte domain */
setbits_le32(mipi_dsi->sim_base + SIM_SOPT1CFG, DSI_RST_BYTE_N);
/* dpi domain */
setbits_le32(mipi_dsi->sim_base + SIM_SOPT1CFG, DSI_RST_DPI_N);
/* display_en */
clrbits_le32(mipi_dsi->sim_base + SIM_SOPT1CFG, DSI_SD);
/* normal cm */
clrbits_le32(mipi_dsi->sim_base + SIM_SOPT1CFG, DSI_CM);
mdelay(20);
/* Reset mipi panel */
board_mipi_panel_reset();
mdelay(60);
/* Disable all interrupts, since we use polling */
mipi_dsi_init_interrupt(mipi_dsi);
/* Call panel driver's setup */
if (mipi_dsi->dsi_panel_drv->mipi_panel_setup) {
ret = mipi_dsi->dsi_panel_drv->mipi_panel_setup(mipi_dsi->dsi_panel_dev);
if (ret < 0) {
printf("failed to init mipi lcd.\n");
return ret;
}
}
/* Enter the HS mode for video stream */
mipi_dsi_set_mode(mipi_dsi, DSI_HS_MODE);
return 0;
}
static void mipi_dsi_wr_tx_header(struct mipi_dsi_northwest_info *mipi_dsi,
u8 di, u8 data0, u8 data1, u8 mode, u8 need_bta)
{
uint32_t pkt_control = 0;
uint16_t word_count = 0;
word_count = data0 | (data1 << 8);
pkt_control = HOST_PKT_CONTROL_WC(word_count) |
HOST_PKT_CONTROL_VC(0) |
HOST_PKT_CONTROL_DT(di) |
HOST_PKT_CONTROL_HS_SEL(mode) |
HOST_PKT_CONTROL_BTA_TX(need_bta);
debug("pkt_control = %x\n", pkt_control);
writel(pkt_control, mipi_dsi->mmio_base + HOST_PKT_CONTROL);
}
static void mipi_dsi_wr_tx_data(struct mipi_dsi_northwest_info *mipi_dsi,
uint32_t tx_data)
{
writel(tx_data, mipi_dsi->mmio_base + HOST_TX_PAYLOAD);
}
static void mipi_dsi_long_data_wr(struct mipi_dsi_northwest_info *mipi_dsi,
const uint8_t *data0, uint32_t data_size)
{
uint32_t data_cnt = 0, payload = 0;
/* in case that data count is more than 4 */
for (data_cnt = 0; data_cnt < data_size; data_cnt += 4) {
/*
* after sending 4bytes per one time,
* send remainder data less then 4.
*/
if ((data_size - data_cnt) < 4) {
if ((data_size - data_cnt) == 3) {
payload = data0[data_cnt] |
(data0[data_cnt + 1] << 8) |
(data0[data_cnt + 2] << 16);
debug("count = 3 payload = %x, %x %x %x\n",
payload, data0[data_cnt], data0[data_cnt + 1], data0[data_cnt + 2]);
} else if ((data_size - data_cnt) == 2) {
payload = data0[data_cnt] |
(data0[data_cnt + 1] << 8);
debug("count = 2 payload = %x, %x %x\n",
payload, data0[data_cnt], data0[data_cnt + 1]);
} else if ((data_size - data_cnt) == 1) {
payload = data0[data_cnt];
debug("count = 1 payload = %x, %x\n",
payload, data0[data_cnt]);
}
mipi_dsi_wr_tx_data(mipi_dsi, payload);
} else {
payload = data0[data_cnt] |
(data0[data_cnt + 1] << 8) |
(data0[data_cnt + 2] << 16) |
(data0[data_cnt + 3] << 24);
debug("count = 4 payload = %x, %x %x %x %x\n",
payload, *(u8 *)(data0 + data_cnt),
data0[data_cnt + 1],
data0[data_cnt + 2],
data0[data_cnt + 3]);
mipi_dsi_wr_tx_data(mipi_dsi, payload);
}
}
}
static int wait_for_pkt_done(struct mipi_dsi_northwest_info *mipi_dsi, unsigned long timeout)
{
uint32_t irq_status;
do {
irq_status = readl(mipi_dsi->mmio_base + HOST_PKT_STATUS);
if (irq_status & HOST_IRQ_STATUS_TX_PKT_DONE)
return timeout;
udelay(1);
} while (--timeout);
return 0;
}
static int mipi_dsi_pkt_write(struct mipi_dsi_northwest_info *mipi_dsi,
u8 data_type, const u32 *buf, int len)
{
int ret = 0;
const uint8_t *data = (const uint8_t *)buf;
debug("mipi_dsi_pkt_write data_type 0x%x, buf 0x%x, len %u\n", data_type, (u32)buf, len);
if (len == 0)
/* handle generic long write command */
mipi_dsi_wr_tx_header(mipi_dsi, data_type, data[0], data[1], DSI_LP_MODE, 0);
else {
/* handle generic long write command */
mipi_dsi_long_data_wr(mipi_dsi, data, len);
mipi_dsi_wr_tx_header(mipi_dsi, data_type, len & 0xff,
(len & 0xff00) >> 8, DSI_LP_MODE, 0);
}
/* send packet */
writel(0x1, mipi_dsi->mmio_base + HOST_SEND_PACKET);
ret = wait_for_pkt_done(mipi_dsi, MIPI_FIFO_TIMEOUT);
if (!ret) {
printf("wait tx done timeout!\n");
return -ETIMEDOUT;
}
mdelay(10);
return 0;
}
static int mipi_dsi_dcs_cmd(struct mipi_dsi_northwest_info *mipi_dsi,
u8 cmd, const u32 *param, int num)
{
int err = 0;
u32 buf[DSI_CMD_BUF_MAXSIZE];
switch (cmd) {
case MIPI_DCS_EXIT_SLEEP_MODE:
case MIPI_DCS_ENTER_SLEEP_MODE:
case MIPI_DCS_SET_DISPLAY_ON:
case MIPI_DCS_SET_DISPLAY_OFF:
buf[0] = cmd;
buf[1] = 0x0;
err = mipi_dsi_pkt_write(mipi_dsi,
MIPI_DSI_DCS_SHORT_WRITE, buf, 0);
break;
default:
printf("MIPI DSI DCS Command:0x%x Not supported!\n", cmd);
break;
}
return err;
}
static void mipi_dsi_shutdown(struct mipi_dsi_northwest_info *mipi_dsi)
{
mipi_display_enter_sleep(mipi_dsi);
writel(0x1, mipi_dsi->mmio_base + DPHY_PD_PLL);
writel(0x1, mipi_dsi->mmio_base + DPHY_PD_DPHY);
enable_mipi_dsi_clk(0);
/* Assert resets */
/* escape domain */
clrbits_le32(mipi_dsi->sim_base + SIM_SOPT1CFG, DSI_RST_ESC_N);
/* byte domain */
clrbits_le32(mipi_dsi->sim_base + SIM_SOPT1CFG, DSI_RST_BYTE_N);
/* dpi domain */
clrbits_le32(mipi_dsi->sim_base + SIM_SOPT1CFG, DSI_RST_DPI_N);
}
struct mipi_dsi_northwest_info *dsi_info = NULL;
int mipi_dsi_northwest_setup(u32 base_addr, u32 sim_addr)
{
if (dsi_info != NULL) {
printf("mipi_dsi_northwest has been initialized.\n");
return -EBUSY;
}
dsi_info = (struct mipi_dsi_northwest_info *)malloc(sizeof(struct mipi_dsi_northwest_info));
if (!dsi_info) {
printf("failed to allocate mipi_dsi_northwest_info object.\n");
return -ENOMEM;
}
dsi_info->mmio_base = base_addr;
dsi_info->sim_base = sim_addr;
dsi_info->mipi_dsi_pkt_write = &mipi_dsi_pkt_write;
dsi_info->dsi_panel_dev = NULL;
dsi_info->dsi_panel_drv = NULL;
dsi_info->enabled = 0;
return 0;
}
/* Register a LCD panel device */
int mipi_dsi_northwest_register_panel_device(struct mipi_dsi_northwest_panel_device *panel_dev)
{
if (!panel_dev) {
printf("mipi_dsi_northwest_panel_device is NULL.\n");
return -EFAULT;
}
if (!panel_dev->name) {
printf("mipi_dsi_northwest_panel_device name is NULL.\n");
return -EFAULT;
}
if (!dsi_info) {
printf("mipi_dsi_northwest is not initialized\n");
return -EFAULT;
}
if (dsi_info->dsi_panel_drv) {
if (strcmp(panel_dev->name, dsi_info->dsi_panel_drv->name)) {
printf("The panel device name %s is not for LCD driver %s\n",
panel_dev->name, dsi_info->dsi_panel_drv->name);
return -EFAULT;
}
}
dsi_info->dsi_panel_dev = panel_dev;
panel_dev->host = dsi_info;
return 0;
}
/* Register a LCD panel driver, will search the panel device to bind with them */
int mipi_dsi_northwest_register_panel_driver(struct mipi_dsi_northwest_panel_driver *panel_drv)
{
if (!panel_drv) {
printf("mipi_dsi_northwest_panel_driver is NULL.\n");
return -EFAULT;
}
if (!panel_drv->name) {
printf("mipi_dsi_northwest_panel_driver name is NULL.\n");
return -EFAULT;
}
if (!dsi_info) {
printf("mipi_dsi_northwest is not initialized\n");
return -EFAULT;
}
if (dsi_info->dsi_panel_dev) {
if (strcmp(panel_drv->name, dsi_info->dsi_panel_dev->name)) {
printf("The panel driver name %s is not for LCD device %s\n",
panel_drv->name, dsi_info->dsi_panel_dev->name);
return -EFAULT;
}
}
dsi_info->dsi_panel_drv = panel_drv;
return 0;
}
/* Enable the mipi dsi display */
int mipi_dsi_northwest_enable(void)
{
if (!dsi_info->dsi_panel_dev || !dsi_info->dsi_panel_drv)
return -ENODEV;
mipi_dsi_enable(dsi_info);
dsi_info->enabled = 1;
return 0;
}
/* Disable and shutdown the mipi dsi display */
int mipi_dsi_northwest_shutdown(void)
{
if (!dsi_info->enabled)
return 0;
mipi_dsi_shutdown(dsi_info);
board_mipi_panel_shutdown();
dsi_info->enabled = 0;
return 0;
}