diff options
Diffstat (limited to 'drivers/video/nexell/s5pxx18_dp_mipi.c')
-rw-r--r-- | drivers/video/nexell/s5pxx18_dp_mipi.c | 677 |
1 files changed, 677 insertions, 0 deletions
diff --git a/drivers/video/nexell/s5pxx18_dp_mipi.c b/drivers/video/nexell/s5pxx18_dp_mipi.c new file mode 100644 index 0000000000..670272b268 --- /dev/null +++ b/drivers/video/nexell/s5pxx18_dp_mipi.c @@ -0,0 +1,677 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Nexell Co., Ltd. + * + * Author: junghyun, kim <jhkim@nexell.co.kr> + */ + +#include <config.h> +#include <common.h> +#include <errno.h> + +#include <asm/arch/nexell.h> +#include <asm/arch/tieoff.h> +#include <asm/arch/reset.h> +#include <asm/arch/display.h> + +#include "soc/s5pxx18_soc_mipi.h" +#include "soc/s5pxx18_soc_disptop.h" +#include "soc/s5pxx18_soc_disptop_clk.h" + +#define PLLPMS_1000MHZ 0x33E8 +#define BANDCTL_1000MHZ 0xF +#define PLLPMS_960MHZ 0x2280 +#define BANDCTL_960MHZ 0xF +#define PLLPMS_900MHZ 0x2258 +#define BANDCTL_900MHZ 0xE +#define PLLPMS_840MHZ 0x2230 +#define BANDCTL_840MHZ 0xD +#define PLLPMS_750MHZ 0x43E8 +#define BANDCTL_750MHZ 0xC +#define PLLPMS_660MHZ 0x21B8 +#define BANDCTL_660MHZ 0xB +#define PLLPMS_600MHZ 0x2190 +#define BANDCTL_600MHZ 0xA +#define PLLPMS_540MHZ 0x2168 +#define BANDCTL_540MHZ 0x9 +#define PLLPMS_512MHZ 0x03200 +#define BANDCTL_512MHZ 0x9 +#define PLLPMS_480MHZ 0x2281 +#define BANDCTL_480MHZ 0x8 +#define PLLPMS_420MHZ 0x2231 +#define BANDCTL_420MHZ 0x7 +#define PLLPMS_402MHZ 0x2219 +#define BANDCTL_402MHZ 0x7 +#define PLLPMS_330MHZ 0x21B9 +#define BANDCTL_330MHZ 0x6 +#define PLLPMS_300MHZ 0x2191 +#define BANDCTL_300MHZ 0x5 +#define PLLPMS_210MHZ 0x2232 +#define BANDCTL_210MHZ 0x4 +#define PLLPMS_180MHZ 0x21E2 +#define BANDCTL_180MHZ 0x3 +#define PLLPMS_150MHZ 0x2192 +#define BANDCTL_150MHZ 0x2 +#define PLLPMS_100MHZ 0x3323 +#define BANDCTL_100MHZ 0x1 +#define PLLPMS_80MHZ 0x3283 +#define BANDCTL_80MHZ 0x0 + +#define MIPI_INDEX 0 +#define MIPI_EXC_PRE_VALUE 1 +#define MIPI_DSI_IRQ_MASK 29 + +#define __io_address(a) (void *)(uintptr_t)(a) + +struct mipi_xfer_msg { + u8 id, data[2]; + u16 flags; + const u8 *tx_buf; + u16 tx_len; + u8 *rx_buf; + u16 rx_len; +}; + +static void mipi_reset(void) +{ + /* tieoff */ + nx_tieoff_set(NX_TIEOFF_MIPI0_NX_DPSRAM_1R1W_EMAA, 3); + nx_tieoff_set(NX_TIEOFF_MIPI0_NX_DPSRAM_1R1W_EMAB, 3); + + /* reset */ + nx_rstcon_setrst(RESET_ID_MIPI, RSTCON_ASSERT); + nx_rstcon_setrst(RESET_ID_MIPI_DSI, RSTCON_ASSERT); + nx_rstcon_setrst(RESET_ID_MIPI_CSI, RSTCON_ASSERT); + nx_rstcon_setrst(RESET_ID_MIPI_PHY_S, RSTCON_ASSERT); + nx_rstcon_setrst(RESET_ID_MIPI_PHY_M, RSTCON_ASSERT); + + nx_rstcon_setrst(RESET_ID_MIPI, RSTCON_NEGATE); + nx_rstcon_setrst(RESET_ID_MIPI_DSI, RSTCON_NEGATE); + nx_rstcon_setrst(RESET_ID_MIPI_PHY_S, RSTCON_NEGATE); + nx_rstcon_setrst(RESET_ID_MIPI_PHY_M, RSTCON_NEGATE); +} + +static void mipi_init(void) +{ + int clkid = DP_CLOCK_MIPI; + void *base; + + /* + * neet to reset before open + */ + mipi_reset(); + + base = __io_address(nx_disp_top_clkgen_get_physical_address(clkid)); + nx_disp_top_clkgen_set_base_address(clkid, base); + nx_disp_top_clkgen_set_clock_pclk_mode(clkid, nx_pclkmode_always); + + base = __io_address(nx_mipi_get_physical_address(0)); + nx_mipi_set_base_address(0, base); +} + +static int mipi_get_phy_pll(int bitrate, unsigned int *pllpms, + unsigned int *bandctl) +{ + unsigned int pms, ctl; + + switch (bitrate) { + case 1000: + pms = PLLPMS_1000MHZ; + ctl = BANDCTL_1000MHZ; + break; + case 960: + pms = PLLPMS_960MHZ; + ctl = BANDCTL_960MHZ; + break; + case 900: + pms = PLLPMS_900MHZ; + ctl = BANDCTL_900MHZ; + break; + case 840: + pms = PLLPMS_840MHZ; + ctl = BANDCTL_840MHZ; + break; + case 750: + pms = PLLPMS_750MHZ; + ctl = BANDCTL_750MHZ; + break; + case 660: + pms = PLLPMS_660MHZ; + ctl = BANDCTL_660MHZ; + break; + case 600: + pms = PLLPMS_600MHZ; + ctl = BANDCTL_600MHZ; + break; + case 540: + pms = PLLPMS_540MHZ; + ctl = BANDCTL_540MHZ; + break; + case 512: + pms = PLLPMS_512MHZ; + ctl = BANDCTL_512MHZ; + break; + case 480: + pms = PLLPMS_480MHZ; + ctl = BANDCTL_480MHZ; + break; + case 420: + pms = PLLPMS_420MHZ; + ctl = BANDCTL_420MHZ; + break; + case 402: + pms = PLLPMS_402MHZ; + ctl = BANDCTL_402MHZ; + break; + case 330: + pms = PLLPMS_330MHZ; + ctl = BANDCTL_330MHZ; + break; + case 300: + pms = PLLPMS_300MHZ; + ctl = BANDCTL_300MHZ; + break; + case 210: + pms = PLLPMS_210MHZ; + ctl = BANDCTL_210MHZ; + break; + case 180: + pms = PLLPMS_180MHZ; + ctl = BANDCTL_180MHZ; + break; + case 150: + pms = PLLPMS_150MHZ; + ctl = BANDCTL_150MHZ; + break; + case 100: + pms = PLLPMS_100MHZ; + ctl = BANDCTL_100MHZ; + break; + case 80: + pms = PLLPMS_80MHZ; + ctl = BANDCTL_80MHZ; + break; + default: + return -EINVAL; + } + + *pllpms = pms; + *bandctl = ctl; + + return 0; +} + +static int mipi_prepare(int module, int input, + struct dp_sync_info *sync, struct dp_ctrl_info *ctrl, + struct dp_mipi_dev *mipi) +{ + int index = MIPI_INDEX; + u32 esc_pre_value = MIPI_EXC_PRE_VALUE; + int lpm = mipi->lpm_trans; + int ret = 0; + + ret = mipi_get_phy_pll(mipi->hs_bitrate, + &mipi->hs_pllpms, &mipi->hs_bandctl); + if (ret < 0) + return ret; + + ret = mipi_get_phy_pll(mipi->lp_bitrate, + &mipi->lp_pllpms, &mipi->lp_bandctl); + if (ret < 0) + return ret; + + debug("%s: mipi lp:%dmhz:0x%x:0x%x, hs:%dmhz:0x%x:0x%x, %s trans\n", + __func__, mipi->lp_bitrate, mipi->lp_pllpms, mipi->lp_bandctl, + mipi->hs_bitrate, mipi->hs_pllpms, mipi->hs_bandctl, + lpm ? "low" : "high"); + + if (lpm) + nx_mipi_dsi_set_pll(index, 1, 0xFFFFFFFF, + mipi->lp_pllpms, mipi->lp_bandctl, 0, 0); + else + nx_mipi_dsi_set_pll(index, 1, 0xFFFFFFFF, + mipi->hs_pllpms, mipi->hs_bandctl, 0, 0); + +#ifdef CONFIG_ARCH_S5P4418 + /* + * disable the escape clock generating prescaler + * before soft reset. + */ + nx_mipi_dsi_set_clock(index, 0, 0, 1, 1, 1, 0, 0, 0, 0, 10); + mdelay(1); +#endif + + nx_mipi_dsi_software_reset(index); + nx_mipi_dsi_set_clock(index, 0, 0, 1, 1, 1, 0, 0, 0, 1, esc_pre_value); + nx_mipi_dsi_set_phy(index, 0, 1, 1, 0, 0, 0, 0, 0); + + if (lpm) + nx_mipi_dsi_set_escape_lp(index, nx_mipi_dsi_lpmode_lp, + nx_mipi_dsi_lpmode_lp); + else + nx_mipi_dsi_set_escape_lp(index, nx_mipi_dsi_lpmode_hs, + nx_mipi_dsi_lpmode_hs); + mdelay(20); + + return 0; +} + +static int mipi_enable(int module, int input, + struct dp_sync_info *sync, struct dp_ctrl_info *ctrl, + struct dp_mipi_dev *mipi) +{ + struct mipi_dsi_device *dsi = &mipi->dsi; + int clkid = DP_CLOCK_MIPI; + int index = MIPI_INDEX; + int width = sync->h_active_len; + int height = sync->v_active_len; + int HFP = sync->h_front_porch; + int HBP = sync->h_back_porch; + int HS = sync->h_sync_width; + int VFP = sync->v_front_porch; + int VBP = sync->v_back_porch; + int VS = sync->v_sync_width; + int en_prescaler = 1; + u32 esc_pre_value = MIPI_EXC_PRE_VALUE; + + int txhsclock = 1; + int lpm = mipi->lpm_trans; + bool command_mode = mipi->command_mode; + + enum nx_mipi_dsi_format dsi_format; + int data_len = dsi->lanes - 1; + bool burst = dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST ? true : false; + bool eot_enable = dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET ? + false : true; + + /* + * disable the escape clock generating prescaler + * before soft reset. + */ +#ifdef CONFIG_ARCH_S5P4418 + en_prescaler = 0; +#endif + + debug("%s: mode:%s, lanes.%d\n", __func__, + command_mode ? "command" : "video", data_len + 1); + + if (lpm) + nx_mipi_dsi_set_escape_lp(index, + nx_mipi_dsi_lpmode_hs, + nx_mipi_dsi_lpmode_hs); + + nx_mipi_dsi_set_pll(index, 1, 0xFFFFFFFF, + mipi->hs_pllpms, mipi->hs_bandctl, 0, 0); + mdelay(1); + + nx_mipi_dsi_set_clock(index, 0, 0, 1, 1, 1, 0, 0, 0, en_prescaler, 10); + mdelay(1); + + nx_mipi_dsi_software_reset(index); + nx_mipi_dsi_set_clock(index, txhsclock, 0, 1, + 1, 1, 0, 0, 0, 1, esc_pre_value); + + switch (data_len) { + case 0: /* 1 lane */ + nx_mipi_dsi_set_phy(index, data_len, 1, 1, 0, 0, 0, 0, 0); + break; + case 1: /* 2 lane */ + nx_mipi_dsi_set_phy(index, data_len, 1, 1, 1, 0, 0, 0, 0); + break; + case 2: /* 3 lane */ + nx_mipi_dsi_set_phy(index, data_len, 1, 1, 1, 1, 0, 0, 0); + break; + case 3: /* 3 lane */ + nx_mipi_dsi_set_phy(index, data_len, 1, 1, 1, 1, 1, 0, 0); + break; + default: + printf("%s: not support data lanes %d\n", + __func__, data_len + 1); + return -EINVAL; + } + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB565: + dsi_format = nx_mipi_dsi_format_rgb565; + break; + case MIPI_DSI_FMT_RGB666: + dsi_format = nx_mipi_dsi_format_rgb666; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + dsi_format = nx_mipi_dsi_format_rgb666_packed; + break; + case MIPI_DSI_FMT_RGB888: + dsi_format = nx_mipi_dsi_format_rgb888; + break; + default: + printf("%s: not support format %d\n", __func__, dsi->format); + return -EINVAL; + } + + nx_mipi_dsi_set_config_video_mode(index, 1, 0, burst, + nx_mipi_dsi_syncmode_event, + eot_enable, 1, 1, 1, 1, 0, dsi_format, + HFP, HBP, HS, VFP, VBP, VS, 0); + + nx_mipi_dsi_set_size(index, width, height); + + /* set mux */ + nx_disp_top_set_mipimux(1, module); + + /* 0 is spdif, 1 is mipi vclk */ + nx_disp_top_clkgen_set_clock_source(clkid, 1, ctrl->clk_src_lv0); + nx_disp_top_clkgen_set_clock_divisor(clkid, 1, + ctrl->clk_div_lv1 * + ctrl->clk_div_lv0); + + /* SPDIF and MIPI */ + nx_disp_top_clkgen_set_clock_divisor_enable(clkid, 1); + + /* START: CLKGEN, MIPI is started in setup function */ + nx_disp_top_clkgen_set_clock_divisor_enable(clkid, true); + nx_mipi_dsi_set_enable(index, true); + + return 0; +} + +static int nx_mipi_transfer_tx(struct mipi_dsi_device *dsi, + struct mipi_xfer_msg *xfer) +{ + const u8 *txb; + int size, index = 0; + u32 data; + + if (xfer->tx_len > DSI_TX_FIFO_SIZE) + printf("warn: tx %d size over fifo %d\n", + (int)xfer->tx_len, DSI_TX_FIFO_SIZE); + + /* write payload */ + size = xfer->tx_len; + txb = xfer->tx_buf; + + while (size >= 4) { + data = (txb[3] << 24) | (txb[2] << 16) | + (txb[1] << 8) | (txb[0]); + nx_mipi_dsi_write_payload(index, data); + txb += 4, size -= 4; + data = 0; + } + + switch (size) { + case 3: + data |= txb[2] << 16; + case 2: + data |= txb[1] << 8; + case 1: + data |= txb[0]; + nx_mipi_dsi_write_payload(index, data); + break; + case 0: + break; /* no payload */ + } + + /* write packet hdr */ + data = (xfer->data[1] << 16) | (xfer->data[0] << 8) | xfer->id; + + nx_mipi_dsi_write_pkheader(index, data); + + return 0; +} + +static int nx_mipi_transfer_done(struct mipi_dsi_device *dsi) +{ + int index = 0, count = 100; + u32 value; + + do { + mdelay(1); + value = nx_mipi_dsi_read_fifo_status(index); + if (((1 << 22) & value)) + break; + } while (count-- > 0); + + if (count < 0) + return -EINVAL; + + return 0; +} + +static int nx_mipi_transfer_rx(struct mipi_dsi_device *dsi, + struct mipi_xfer_msg *xfer) +{ + u8 *rxb = xfer->rx_buf; + int index = 0, rx_len = 0; + u32 data, count = 0; + u16 size; + int err = -EINVAL; + + nx_mipi_dsi_clear_interrupt_pending(index, 18); + + while (1) { + /* Completes receiving data. */ + if (nx_mipi_dsi_get_interrupt_pending(index, 18)) + break; + + mdelay(1); + + if (count > 500) { + printf("%s: error recevice data\n", __func__); + err = -EINVAL; + goto clear_fifo; + } else { + count++; + } + } + + data = nx_mipi_dsi_read_fifo(index); + + switch (data & 0x3f) { + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + if (xfer->rx_len >= 2) { + rxb[1] = data >> 16; + rx_len++; + } + + /* Fall through */ + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + rxb[0] = data >> 8; + rx_len++; + xfer->rx_len = rx_len; + err = rx_len; + goto clear_fifo; + + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + printf("DSI Error Report: 0x%04x\n", (data >> 8) & 0xffff); + err = rx_len; + goto clear_fifo; + } + + size = (data >> 8) & 0xffff; + + if (size > xfer->rx_len) + size = xfer->rx_len; + else if (size < xfer->rx_len) + xfer->rx_len = size; + + size = xfer->rx_len - rx_len; + rx_len += size; + + /* Receive payload */ + while (size >= 4) { + data = nx_mipi_dsi_read_fifo(index); + rxb[0] = (data >> 0) & 0xff; + rxb[1] = (data >> 8) & 0xff; + rxb[2] = (data >> 16) & 0xff; + rxb[3] = (data >> 24) & 0xff; + rxb += 4, size -= 4; + } + + if (size) { + data = nx_mipi_dsi_read_fifo(index); + switch (size) { + case 3: + rxb[2] = (data >> 16) & 0xff; + case 2: + rxb[1] = (data >> 8) & 0xff; + case 1: + rxb[0] = data & 0xff; + } + } + + if (rx_len == xfer->rx_len) + err = rx_len; + +clear_fifo: + size = DSI_RX_FIFO_SIZE / 4; + do { + data = nx_mipi_dsi_read_fifo(index); + if (data == DSI_RX_FIFO_EMPTY) + break; + } while (--size); + + return err; +} + +#define IS_SHORT(t) (9 > ((t) & 0x0f)) + +static int nx_mipi_transfer(struct mipi_dsi_device *dsi, + const struct mipi_dsi_msg *msg) +{ + struct mipi_xfer_msg xfer; + int err; + + if (!msg->tx_len) + return -EINVAL; + + /* set id */ + xfer.id = msg->type | (msg->channel << 6); + + /* short type msg */ + if (IS_SHORT(msg->type)) { + const char *txb = msg->tx_buf; + + if (msg->tx_len > 2) + return -EINVAL; + + xfer.tx_len = 0; /* no payload */ + xfer.data[0] = txb[0]; + xfer.data[1] = (msg->tx_len == 2) ? txb[1] : 0; + xfer.tx_buf = NULL; + } else { + xfer.tx_len = msg->tx_len; + xfer.data[0] = msg->tx_len & 0xff; + xfer.data[1] = msg->tx_len >> 8; + xfer.tx_buf = msg->tx_buf; + } + + xfer.rx_len = msg->rx_len; + xfer.rx_buf = msg->rx_buf; + xfer.flags = msg->flags; + + err = nx_mipi_transfer_tx(dsi, &xfer); + + if (xfer.rx_len) + err = nx_mipi_transfer_rx(dsi, &xfer); + + nx_mipi_transfer_done(dsi); + + return err; +} + +static ssize_t nx_mipi_write_buffer(struct mipi_dsi_device *dsi, + const void *data, size_t len) +{ + struct mipi_dsi_msg msg = { + .channel = dsi->channel, + .tx_buf = data, + .tx_len = len + }; + + switch (len) { + case 0: + return -EINVAL; + case 1: + msg.type = MIPI_DSI_DCS_SHORT_WRITE; + break; + case 2: + msg.type = MIPI_DSI_DCS_SHORT_WRITE_PARAM; + break; + default: + msg.type = MIPI_DSI_DCS_LONG_WRITE; + break; + } + + if (dsi->mode_flags & MIPI_DSI_MODE_LPM) + msg.flags |= MIPI_DSI_MSG_USE_LPM; + + return nx_mipi_transfer(dsi, &msg); +} + +__weak int nx_mipi_dsi_lcd_bind(struct mipi_dsi_device *dsi) +{ + return 0; +} + +/* + * disply + * MIPI DSI Setting + * (1) Initiallize MIPI(DSIM,DPHY,PLL) + * (2) Initiallize LCD + * (3) ReInitiallize MIPI(DSIM only) + * (4) Turn on display(MLC,DPC,...) + */ +void nx_mipi_display(int module, + struct dp_sync_info *sync, struct dp_ctrl_info *ctrl, + struct dp_plane_top *top, struct dp_plane_info *planes, + struct dp_mipi_dev *dev) +{ + struct dp_plane_info *plane = planes; + struct mipi_dsi_device *dsi = &dev->dsi; + int input = module == 0 ? DP_DEVICE_DP0 : DP_DEVICE_DP1; + int count = top->plane_num; + int i = 0, ret; + + printf("MIPI: dp.%d\n", module); + + /* map mipi-dsi write callback func */ + dsi->write_buffer = nx_mipi_write_buffer; + + ret = nx_mipi_dsi_lcd_bind(dsi); + if (ret) { + printf("Error: bind mipi-dsi lcd driver !\n"); + return; + } + + dp_control_init(module); + dp_plane_init(module); + + mipi_init(); + + /* set plane */ + dp_plane_screen_setup(module, top); + + for (i = 0; count > i; i++, plane++) { + if (!plane->enable) + continue; + dp_plane_layer_setup(module, plane); + dp_plane_layer_enable(module, plane, 1); + } + dp_plane_screen_enable(module, 1); + + /* set mipi */ + mipi_prepare(module, input, sync, ctrl, dev); + + if (dsi->ops && dsi->ops->prepare) + dsi->ops->prepare(dsi); + + if (dsi->ops && dsi->ops->enable) + dsi->ops->enable(dsi); + + mipi_enable(module, input, sync, ctrl, dev); + + /* set dp control */ + dp_control_setup(module, sync, ctrl); + dp_control_enable(module, 1); +} |