From patchwork Mon Feb 3 20:45:55 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Bosch X-Patchwork-Id: 235887 List-Id: U-Boot discussion From: stefan_b at posteo.net (Stefan Bosch) Date: Mon, 3 Feb 2020 21:45:55 +0100 Subject: [RFC PATCH 08/10] video: add nexell video driver (display/video driver) In-Reply-To: <1580758773-5287-1-git-send-email-stefan_b@posteo.net> References: <1580758773-5287-1-git-send-email-stefan_b@posteo.net> Message-ID: <1580762755-6509-1-git-send-email-stefan_b@posteo.net> Changes in relation to FriendlyARM's U-Boot nanopi2-v2016.01: - nexell_display.c: Changed to DM, CONFIG_FB_ADDR can not be used anymore because framebuffer is allocated by video_reserve() in video-uclass.c. Therefore code changed appropriately. Signed-off-by: Stefan Bosch --- drivers/video/Kconfig | 10 + drivers/video/Makefile | 1 + drivers/video/nexell/Kconfig | 27 ++ drivers/video/nexell/Makefile | 12 + drivers/video/nexell/s5pxx18_dp.c | 338 ++++++++++++++++ drivers/video/nexell/s5pxx18_dp_hdmi.c | 543 ++++++++++++++++++++++++++ drivers/video/nexell/s5pxx18_dp_lvds.c | 274 +++++++++++++ drivers/video/nexell/s5pxx18_dp_mipi.c | 677 +++++++++++++++++++++++++++++++++ drivers/video/nexell/s5pxx18_dp_rgb.c | 69 ++++ drivers/video/nexell_display.c | 658 ++++++++++++++++++++++++++++++++ 10 files changed, 2609 insertions(+) create mode 100644 drivers/video/nexell/Kconfig create mode 100644 drivers/video/nexell/Makefile create mode 100644 drivers/video/nexell/s5pxx18_dp.c create mode 100644 drivers/video/nexell/s5pxx18_dp_hdmi.c create mode 100644 drivers/video/nexell/s5pxx18_dp_lvds.c create mode 100644 drivers/video/nexell/s5pxx18_dp_mipi.c create mode 100644 drivers/video/nexell/s5pxx18_dp_rgb.c create mode 100644 drivers/video/nexell_display.c diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 50ab365..dd224ab 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -563,6 +563,16 @@ source "drivers/video/bridge/Kconfig" source "drivers/video/imx/Kconfig" +config VIDEO_NX + bool "Enable video support on Nexell SoC" + depends on ARCH_S5P6818 || ARCH_S5P4418 + help + Nexell SoC supports many video output options including eDP and + HDMI. This option enables this support which can be used on devices + which have an eDP display connected. + +source "drivers/video/nexell/Kconfig" + config VIDEO bool "Enable legacy video support" depends on !DM_VIDEO diff --git a/drivers/video/Makefile b/drivers/video/Makefile index df7119d..3bacc64 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -61,6 +61,7 @@ obj-${CONFIG_VIDEO_MIPI_DSI} += mipi_dsi.o obj-$(CONFIG_VIDEO_MVEBU) += mvebu_lcd.o obj-$(CONFIG_VIDEO_MX3) += mx3fb.o videomodes.o obj-$(CONFIG_VIDEO_MXS) += mxsfb.o videomodes.o +obj-$(CONFIG_VIDEO_NX) += nexell_display.o videomodes.o nexell/ obj-$(CONFIG_VIDEO_OMAP3) += omap3_dss.o obj-$(CONFIG_VIDEO_DSI_HOST_SANDBOX) += sandbox_dsi_host.o obj-$(CONFIG_VIDEO_SANDBOX_SDL) += sandbox_sdl.o diff --git a/drivers/video/nexell/Kconfig b/drivers/video/nexell/Kconfig new file mode 100644 index 0000000..54b8ccb --- /dev/null +++ b/drivers/video/nexell/Kconfig @@ -0,0 +1,27 @@ +if VIDEO_NX + +menu "LCD select" + +config VIDEO_NX_RGB + bool "RGB LCD" + help + Support for RGB lcd output. + +config VIDEO_NX_LVDS + bool "LVDS LCD" + help + Support for LVDS lcd output. + +config VIDEO_NX_MIPI + bool "MiPi" + help + Support for MiPi lcd output. + +config VIDEO_NX_HDMI + bool "HDMI" + help + Support for hdmi output. + +endmenu + +endif diff --git a/drivers/video/nexell/Makefile b/drivers/video/nexell/Makefile new file mode 100644 index 0000000..111ab45 --- /dev/null +++ b/drivers/video/nexell/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2016 Nexell +# Junghyun, kim + +obj-$(CONFIG_VIDEO_NX) += s5pxx18_dp.o +obj-$(CONFIG_VIDEO_NX) += soc/ + +obj-$(CONFIG_VIDEO_NX_RGB) += s5pxx18_dp_rgb.o +obj-$(CONFIG_VIDEO_NX_LVDS) += s5pxx18_dp_lvds.o +obj-$(CONFIG_VIDEO_NX_MIPI) += s5pxx18_dp_mipi.o +obj-$(CONFIG_VIDEO_NX_HDMI) += s5pxx18_dp_hdmi.o diff --git a/drivers/video/nexell/s5pxx18_dp.c b/drivers/video/nexell/s5pxx18_dp.c new file mode 100644 index 0000000..c69b69c --- /dev/null +++ b/drivers/video/nexell/s5pxx18_dp.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Nexell Co., Ltd. + * + * Author: junghyun, kim + */ + +#include +#include +#include +#include +#include +#include + +#include "soc/s5pxx18_soc_disptop.h" +#include "soc/s5pxx18_soc_dpc.h" +#include "soc/s5pxx18_soc_mlc.h" + +#define MLC_LAYER_RGB_0 0 /* number of RGB layer 0 */ +#define MLC_LAYER_RGB_1 1 /* number of RGB layer 1 */ +#define MLC_LAYER_VIDEO 3 /* number of Video layer: 3 = VIDEO */ + +#define __io_address(a) (void *)(uintptr_t)(a) + +void dp_control_init(int module) +{ + void *base; + + /* top */ + base = __io_address(nx_disp_top_get_physical_address()); + nx_disp_top_set_base_address(base); + + /* control */ + base = __io_address(nx_dpc_get_physical_address(module)); + nx_dpc_set_base_address(module, base); + + /* top controller */ + nx_rstcon_setrst(RESET_ID_DISP_TOP, RSTCON_ASSERT); + nx_rstcon_setrst(RESET_ID_DISP_TOP, RSTCON_NEGATE); + + /* display controller */ + nx_rstcon_setrst(RESET_ID_DISPLAY, RSTCON_ASSERT); + nx_rstcon_setrst(RESET_ID_DISPLAY, RSTCON_NEGATE); + + nx_dpc_set_clock_pclk_mode(module, nx_pclkmode_always); +} + +int dp_control_setup(int module, + struct dp_sync_info *sync, struct dp_ctrl_info *ctrl) +{ + unsigned int out_format; + unsigned int delay_mask; + int rgb_pvd = 0, hsync_cp1 = 7, vsync_fram = 7, de_cp2 = 7; + int v_vso = 1, v_veo = 1, e_vso = 1, e_veo = 1; + + int interlace = 0; + int invert_field; + int swap_rb; + unsigned int yc_order; + int vck_select; + int vclk_invert; + int emb_sync; + + enum nx_dpc_dither r_dither, g_dither, b_dither; + int rgb_mode = 0; + + if (NULL == sync || NULL == ctrl) { + debug("error, dp.%d not set sync or pad clock info !!!\n", + module); + return -EINVAL; + } + + out_format = ctrl->out_format; + delay_mask = ctrl->delay_mask; + interlace = sync->interlace; + invert_field = ctrl->invert_field; + swap_rb = ctrl->swap_RB; + yc_order = ctrl->yc_order; + vck_select = ctrl->vck_select; + vclk_invert = ctrl->clk_inv_lv0 | ctrl->clk_inv_lv1; + emb_sync = (out_format == DPC_FORMAT_CCIR656 ? 1 : 0); + + /* set delay mask */ + if (delay_mask & DP_SYNC_DELAY_RGB_PVD) + rgb_pvd = ctrl->d_rgb_pvd; + if (delay_mask & DP_SYNC_DELAY_HSYNC_CP1) + hsync_cp1 = ctrl->d_hsync_cp1; + if (delay_mask & DP_SYNC_DELAY_VSYNC_FRAM) + vsync_fram = ctrl->d_vsync_fram; + if (delay_mask & DP_SYNC_DELAY_DE_CP) + de_cp2 = ctrl->d_de_cp2; + + if (ctrl->vs_start_offset != 0 || + ctrl->vs_end_offset != 0 || + ctrl->ev_start_offset != 0 || ctrl->ev_end_offset != 0) { + v_vso = ctrl->vs_start_offset; + v_veo = ctrl->vs_end_offset; + e_vso = ctrl->ev_start_offset; + e_veo = ctrl->ev_end_offset; + } + + if (nx_dpc_format_rgb555 == out_format || + nx_dpc_format_mrgb555a == out_format || + nx_dpc_format_mrgb555b == out_format) { + r_dither = nx_dpc_dither_5bit; + g_dither = nx_dpc_dither_5bit; + b_dither = nx_dpc_dither_5bit; + rgb_mode = 1; + } else if (nx_dpc_format_rgb565 == out_format || + nx_dpc_format_mrgb565 == out_format) { + r_dither = nx_dpc_dither_5bit; + b_dither = nx_dpc_dither_5bit; + g_dither = nx_dpc_dither_6bit, rgb_mode = 1; + } else if ((nx_dpc_format_rgb666 == out_format) || + (nx_dpc_format_mrgb666 == out_format)) { + r_dither = nx_dpc_dither_6bit; + g_dither = nx_dpc_dither_6bit; + b_dither = nx_dpc_dither_6bit; + rgb_mode = 1; + } else { + r_dither = nx_dpc_dither_bypass; + g_dither = nx_dpc_dither_bypass; + b_dither = nx_dpc_dither_bypass; + rgb_mode = 1; + } + + /* CLKGEN0/1 */ + nx_dpc_set_clock_source(module, 0, ctrl->clk_src_lv0 == 3 ? + 6 : ctrl->clk_src_lv0); + nx_dpc_set_clock_divisor(module, 0, ctrl->clk_div_lv0); + nx_dpc_set_clock_source(module, 1, ctrl->clk_src_lv1); + nx_dpc_set_clock_divisor(module, 1, ctrl->clk_div_lv1); + nx_dpc_set_clock_out_delay(module, 0, ctrl->clk_delay_lv0); + nx_dpc_set_clock_out_delay(module, 1, ctrl->clk_delay_lv1); + + /* LCD out */ + nx_dpc_set_mode(module, out_format, interlace, invert_field, + rgb_mode, swap_rb, yc_order, emb_sync, emb_sync, + vck_select, vclk_invert, 0); + nx_dpc_set_hsync(module, sync->h_active_len, sync->h_sync_width, + sync->h_front_porch, sync->h_back_porch, + sync->h_sync_invert); + nx_dpc_set_vsync(module, sync->v_active_len, sync->v_sync_width, + sync->v_front_porch, sync->v_back_porch, + sync->v_sync_invert, sync->v_active_len, + sync->v_sync_width, sync->v_front_porch, + sync->v_back_porch); + nx_dpc_set_vsync_offset(module, v_vso, v_veo, e_vso, e_veo); + nx_dpc_set_delay(module, rgb_pvd, hsync_cp1, vsync_fram, de_cp2); + nx_dpc_set_dither(module, r_dither, g_dither, b_dither); + +#ifdef CONFIG_MACH_S5P6818 + /* Set TFT_CLKCTRL (offset : 1030h) + * Field name : DPC0_CLKCTRL, DPC1_CLKCRL + * Default value : clk_inv_lv0/1 = 0 : PADCLK_InvCLK + * Invert case : clk_inv_lv0/1 = 1 : PADCLK_CLK + */ + if (module == 0 && ctrl->clk_inv_lv0) + nx_disp_top_set_padclock(padmux_primary_mlc, padclk_clk); + if (module == 1 && ctrl->clk_inv_lv1) + nx_disp_top_set_padclock(padmux_secondary_mlc, padclk_clk); +#endif + + debug("%s: dp.%d x:%4d, hf:%3d, hb:%3d, hs:%3d, hi=%d\n", + __func__, module, sync->h_active_len, sync->h_front_porch, + sync->h_back_porch, sync->h_sync_width, sync->h_sync_invert); + debug("%s: dp.%d y:%4d, vf:%3d, vb:%3d, vs:%3d, vi=%d\n", + __func__, module, sync->v_active_len, sync->v_front_porch, + sync->v_back_porch, sync->v_sync_width, sync->h_sync_invert); + debug("%s: dp.%d ck.0:%d:%d:%d, ck.1:%d:%d:%d\n", + __func__, module, + ctrl->clk_src_lv0, ctrl->clk_div_lv0, ctrl->clk_inv_lv0, + ctrl->clk_src_lv1, ctrl->clk_div_lv1, ctrl->clk_inv_lv1); + debug("%s: dp.%d vs:%d, ve:%d, es:%d, ee:%d\n", + __func__, module, v_vso, v_veo, e_vso, e_veo); + debug("%s: dp.%d delay RGB:%d, hs:%d, vs:%d, de:%d, fmt:0x%x\n", + __func__, module, rgb_pvd, hsync_cp1, vsync_fram, de_cp2, + out_format); + + return 0; +} + +void dp_control_enable(int module, int on) +{ + debug("%s: dp.%d top %s\n", __func__, module, on ? "ON" : "OFF"); + + nx_dpc_set_dpc_enable(module, on); + nx_dpc_set_clock_divisor_enable(module, on); +} + +void dp_plane_init(int module) +{ + void *base = __io_address(nx_mlc_get_physical_address(module)); + + nx_mlc_set_base_address(module, base); + nx_mlc_set_clock_pclk_mode(module, nx_pclkmode_always); + nx_mlc_set_clock_bclk_mode(module, nx_bclkmode_always); +} + +int dp_plane_screen_setup(int module, struct dp_plane_top *top) +{ + int width = top->screen_width; + int height = top->screen_height; + int interlace = top->interlace; + int video_prior = top->video_prior; + unsigned int bg_color = top->back_color; + + /* MLC TOP layer */ + nx_mlc_set_screen_size(module, width, height); + nx_mlc_set_layer_priority(module, video_prior); + nx_mlc_set_background(module, bg_color); + nx_mlc_set_field_enable(module, interlace); + nx_mlc_set_rgblayer_gama_table_power_mode(module, 0, 0, 0); + nx_mlc_set_rgblayer_gama_table_sleep_mode(module, 1, 1, 1); + nx_mlc_set_rgblayer_gamma_enable(module, 0); + nx_mlc_set_dither_enable_when_using_gamma(module, 0); + nx_mlc_set_gamma_priority(module, 0); + nx_mlc_set_top_power_mode(module, 1); + nx_mlc_set_top_sleep_mode(module, 0); + + debug("%s: dp.%d screen %dx%d, %s, priority:%d, bg:0x%x\n", + __func__, module, width, height, + interlace ? "Interlace" : "Progressive", + video_prior, bg_color); + + return 0; +} + +void dp_plane_screen_enable(int module, int on) +{ + /* enable top screen */ + nx_mlc_set_mlc_enable(module, on); + nx_mlc_set_top_dirty_flag(module); + debug("%s: dp.%d top %s\n", __func__, module, on ? "ON" : "OFF"); +} + +int dp_plane_layer_setup(int module, struct dp_plane_info *plane) +{ + int sx = plane->left; + int sy = plane->top; + int ex = sx + plane->width - 1; + int ey = sy + plane->height - 1; + int pixel_byte = plane->pixel_byte; + int mem_lock_size = 16; /* fix mem lock size */ + int layer = plane->layer; + unsigned int format = plane->format; + + if (!plane->enable) + return -EINVAL; + + /* MLC layer */ + nx_mlc_set_lock_size(module, layer, mem_lock_size); + nx_mlc_set_alpha_blending(module, layer, 0, 15); + nx_mlc_set_transparency(module, layer, 0, 0); + nx_mlc_set_color_inversion(module, layer, 0, 0); + nx_mlc_set_rgblayer_invalid_position(module, layer, 0, 0, 0, 0, 0, 0); + nx_mlc_set_rgblayer_invalid_position(module, layer, 1, 0, 0, 0, 0, 0); + nx_mlc_set_format_rgb(module, layer, format); + nx_mlc_set_position(module, layer, sx, sy, ex, ey); + nx_mlc_set_rgblayer_stride(module, layer, pixel_byte, + plane->width * pixel_byte); + nx_mlc_set_rgblayer_address(module, layer, plane->fb_base); + + debug("%s: dp.%d.%d %d * %d, %dbpp, fmt:0x%x\n", + __func__, module, layer, plane->width, plane->height, + pixel_byte * 8, format); + debug("%s: b:0x%x, l:%d, t:%d, r:%d, b:%d, hs:%d, vs:%d\n", + __func__, plane->fb_base, sx, sy, ex, ey, + plane->width * pixel_byte, pixel_byte); + + return 0; +} + +int dp_plane_set_enable(int module, int layer, int on) +{ + int hl, hc; + int vl, vc; + + debug("%s: dp.%d.%d %s:%s\n", + __func__, module, layer, + layer == MLC_LAYER_VIDEO ? "Video" : "RGB", + on ? "ON" : "OFF"); + + if (layer != MLC_LAYER_VIDEO) { + nx_mlc_set_layer_enable(module, layer, on); + nx_mlc_set_dirty_flag(module, layer); + return 0; + } + + /* video layer */ + if (on) { + nx_mlc_set_video_layer_line_buffer_power_mode(module, 1); + nx_mlc_set_video_layer_line_buffer_sleep_mode(module, 0); + nx_mlc_set_layer_enable(module, layer, 1); + nx_mlc_set_dirty_flag(module, layer); + } else { + nx_mlc_set_layer_enable(module, layer, 0); + nx_mlc_set_dirty_flag(module, layer); + nx_mlc_get_video_layer_scale_filter(module, + &hl, &hc, &vl, &vc); + if (hl || hc || vl || vc) + nx_mlc_set_video_layer_scale_filter(module, 0, 0, 0, 0); + nx_mlc_set_video_layer_line_buffer_power_mode(module, 0); + nx_mlc_set_video_layer_line_buffer_sleep_mode(module, 1); + nx_mlc_set_dirty_flag(module, layer); + } + + return 0; +} + +void dp_plane_layer_enable(int module, + struct dp_plane_info *plane, int on) +{ + dp_plane_set_enable(module, plane->layer, on); +} + +int dp_plane_set_address(int module, int layer, unsigned int address) +{ + nx_mlc_set_rgblayer_address(module, layer, address); + nx_mlc_set_dirty_flag(module, layer); + + return 0; +} + +int dp_plane_wait_vsync(int module, int layer, int fps) +{ + int cnt = 0; + + if (fps == 0) + return (int)nx_mlc_get_dirty_flag(module, layer); + + while (fps > cnt++) { + while (nx_mlc_get_dirty_flag(module, layer)) + ; + nx_mlc_set_dirty_flag(module, layer); + } + return 0; +} diff --git a/drivers/video/nexell/s5pxx18_dp_hdmi.c b/drivers/video/nexell/s5pxx18_dp_hdmi.c new file mode 100644 index 0000000..5d72d1e --- /dev/null +++ b/drivers/video/nexell/s5pxx18_dp_hdmi.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Nexell Co., Ltd. + * + * Author: junghyun, kim + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "soc/s5pxx18_soc_dpc.h" +#include "soc/s5pxx18_soc_hdmi.h" +#include "soc/s5pxx18_soc_disptop.h" +#include "soc/s5pxx18_soc_disptop_clk.h" + +#define __io_address(a) (void *)(uintptr_t)(a) + +static const u8 hdmiphy_preset74_25[32] = { + 0xd1, 0x1f, 0x10, 0x40, 0x40, 0xf8, 0xc8, 0x81, + 0xe8, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80, 0x0a, + 0x80, 0x09, 0x84, 0x05, 0x22, 0x24, 0x86, 0x54, + 0xa5, 0x24, 0x01, 0x00, 0x00, 0x01, 0x10, 0x80, +}; + +static const u8 hdmiphy_preset148_5[32] = { + 0xd1, 0x1f, 0x00, 0x40, 0x40, 0xf8, 0xc8, 0x81, + 0xe8, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80, 0x0a, + 0x80, 0x09, 0x84, 0x05, 0x22, 0x24, 0x86, 0x54, + 0x4b, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, +}; + +#define HDMIPHY_PRESET_TABLE_SIZE (32) + +enum NXP_HDMI_PRESET { + NXP_HDMI_PRESET_720P = 0, /* 1280 x 720 */ + NXP_HDMI_PRESET_1080P, /* 1920 x 1080 */ + NXP_HDMI_PRESET_MAX +}; + +static void hdmi_reset(void) +{ + nx_rstcon_setrst(RESET_ID_HDMI_VIDEO, RSTCON_ASSERT); + nx_rstcon_setrst(RESET_ID_HDMI_SPDIF, RSTCON_ASSERT); + nx_rstcon_setrst(RESET_ID_HDMI_TMDS, RSTCON_ASSERT); + nx_rstcon_setrst(RESET_ID_HDMI_VIDEO, RSTCON_NEGATE); + nx_rstcon_setrst(RESET_ID_HDMI_SPDIF, RSTCON_NEGATE); + nx_rstcon_setrst(RESET_ID_HDMI_TMDS, RSTCON_NEGATE); +} + +static int hdmi_phy_enable(int preset, int enable) +{ + const u8 *table = NULL; + int size = 0; + u32 addr, i = 0; + + if (!enable) + return 0; + + switch (preset) { + case NXP_HDMI_PRESET_720P: + table = hdmiphy_preset74_25; + size = 32; + break; + case NXP_HDMI_PRESET_1080P: + table = hdmiphy_preset148_5; + size = 31; + break; + default: + printf("hdmi: phy not support preset %d\n", preset); + return -EINVAL; + } + + nx_hdmi_set_reg(0, HDMI_PHY_REG7C, (0 << 7)); + nx_hdmi_set_reg(0, HDMI_PHY_REG7C, (0 << 7)); + nx_hdmi_set_reg(0, HDMI_PHY_REG04, (0 << 4)); + nx_hdmi_set_reg(0, HDMI_PHY_REG04, (0 << 4)); + nx_hdmi_set_reg(0, HDMI_PHY_REG24, (1 << 7)); + nx_hdmi_set_reg(0, HDMI_PHY_REG24, (1 << 7)); + + for (i = 0, addr = HDMI_PHY_REG04; size > i; i++, addr += 4) { + nx_hdmi_set_reg(0, addr, table[i]); + nx_hdmi_set_reg(0, addr, table[i]); + } + + nx_hdmi_set_reg(0, HDMI_PHY_REG7C, 0x80); + nx_hdmi_set_reg(0, HDMI_PHY_REG7C, 0x80); + nx_hdmi_set_reg(0, HDMI_PHY_REG7C, (1 << 7)); + nx_hdmi_set_reg(0, HDMI_PHY_REG7C, (1 << 7)); + debug("%s: preset = %d\n", __func__, preset); + + return 0; +} + +static inline int hdmi_wait_phy_ready(void) +{ + int count = 500; + + do { + u32 val = nx_hdmi_get_reg(0, HDMI_LINK_PHY_STATUS_0); + + if (val & 0x01) { + printf("HDMI: phy ready...\n"); + return 1; + } + mdelay(10); + } while (count--); + + return 0; +} + +static inline int hdmi_get_vsync(int preset, + struct dp_sync_info *sync, + struct dp_ctrl_info *ctrl) +{ + switch (preset) { + case NXP_HDMI_PRESET_720P: /* 720p: 1280x720 */ + sync->h_active_len = 1280; + sync->h_sync_width = 40; + sync->h_back_porch = 220; + sync->h_front_porch = 110; + sync->h_sync_invert = 0; + sync->v_active_len = 720; + sync->v_sync_width = 5; + sync->v_back_porch = 20; + sync->v_front_porch = 5; + sync->v_sync_invert = 0; + break; + + case NXP_HDMI_PRESET_1080P: /* 1080p: 1920x1080 */ + sync->h_active_len = 1920; + sync->h_sync_width = 44; + sync->h_back_porch = 148; + sync->h_front_porch = 88; + sync->h_sync_invert = 0; + sync->v_active_len = 1080; + sync->v_sync_width = 5; + sync->v_back_porch = 36; + sync->v_front_porch = 4; + sync->v_sync_invert = 0; + break; + default: + printf("HDMI: not support preset sync %d\n", preset); + return -EINVAL; + } + + ctrl->clk_src_lv0 = 4; + ctrl->clk_div_lv0 = 1; + ctrl->clk_src_lv1 = 7; + ctrl->clk_div_lv1 = 1; + + ctrl->out_format = outputformat_rgb888; + ctrl->delay_mask = (DP_SYNC_DELAY_RGB_PVD | DP_SYNC_DELAY_HSYNC_CP1 | + DP_SYNC_DELAY_VSYNC_FRAM | DP_SYNC_DELAY_DE_CP); + ctrl->d_rgb_pvd = 0; + ctrl->d_hsync_cp1 = 0; + ctrl->d_vsync_fram = 0; + ctrl->d_de_cp2 = 7; + + /* HFP + HSW + HBP + AVWidth-VSCLRPIXEL- 1; */ + ctrl->vs_start_offset = (sync->h_front_porch + sync->h_sync_width + + sync->h_back_porch + sync->h_active_len - 1); + ctrl->vs_end_offset = 0; + + /* HFP + HSW + HBP + AVWidth-EVENVSCLRPIXEL- 1 */ + ctrl->ev_start_offset = (sync->h_front_porch + sync->h_sync_width + + sync->h_back_porch + sync->h_active_len - 1); + ctrl->ev_end_offset = 0; + debug("%s: preset: %d\n", __func__, preset); + + return 0; +} + +static void hdmi_clock(void) +{ + void *base = + __io_address(nx_disp_top_clkgen_get_physical_address + (to_mipi_clkgen)); + + nx_disp_top_clkgen_set_base_address(to_mipi_clkgen, base); + nx_disp_top_clkgen_set_clock_divisor_enable(to_mipi_clkgen, 0); + nx_disp_top_clkgen_set_clock_pclk_mode(to_mipi_clkgen, + nx_pclkmode_always); + nx_disp_top_clkgen_set_clock_source(to_mipi_clkgen, HDMI_SPDIF_CLKOUT, + 2); + nx_disp_top_clkgen_set_clock_divisor(to_mipi_clkgen, HDMI_SPDIF_CLKOUT, + 2); + nx_disp_top_clkgen_set_clock_source(to_mipi_clkgen, 1, 7); + nx_disp_top_clkgen_set_clock_divisor_enable(to_mipi_clkgen, 1); + + /* must initialize this !!! */ + nx_disp_top_hdmi_set_vsync_hsstart_end(0, 0); + nx_disp_top_hdmi_set_vsync_start(0); + nx_disp_top_hdmi_set_hactive_start(0); + nx_disp_top_hdmi_set_hactive_end(0); +} + +static void hdmi_vsync(struct dp_sync_info *sync) +{ + int width = sync->h_active_len; + int hsw = sync->h_sync_width; + int hbp = sync->h_back_porch; + int height = sync->v_active_len; + int vsw = sync->v_sync_width; + int vbp = sync->v_back_porch; + + int v_sync_s = vsw + vbp + height - 1; + int h_active_s = hsw + hbp; + int h_active_e = width + hsw + hbp; + int v_sync_hs_se0 = hsw + hbp + 1; + int v_sync_hs_se1 = hsw + hbp + 2; + + nx_disp_top_hdmi_set_vsync_start(v_sync_s); + nx_disp_top_hdmi_set_hactive_start(h_active_s); + nx_disp_top_hdmi_set_hactive_end(h_active_e); + nx_disp_top_hdmi_set_vsync_hsstart_end(v_sync_hs_se0, v_sync_hs_se1); +} + +static int hdmi_prepare(struct dp_sync_info *sync) +{ + int width = sync->h_active_len; + int hsw = sync->h_sync_width; + int hfp = sync->h_front_porch; + int hbp = sync->h_back_porch; + int height = sync->v_active_len; + int vsw = sync->v_sync_width; + int vfp = sync->v_front_porch; + int vbp = sync->v_back_porch; + + u32 h_blank, h_line, h_sync_start, h_sync_end; + u32 v_blank, v2_blank, v_line; + u32 v_sync_line_bef_1, v_sync_line_bef_2; + + u32 fixed_ffff = 0xffff; + + /* calculate sync variables */ + h_blank = hfp + hsw + hbp; + v_blank = vfp + vsw + vbp; + v2_blank = height + vfp + vsw + vbp; + v_line = height + vfp + vsw + vbp; /* total v */ + h_line = width + hfp + hsw + hbp; /* total h */ + h_sync_start = hfp; + h_sync_end = hfp + hsw; + v_sync_line_bef_1 = vfp; + v_sync_line_bef_2 = vfp + vsw; + + /* no blue screen mode, encoding order as it is */ + nx_hdmi_set_reg(0, HDMI_LINK_HDMI_CON_0, (0 << 5) | (1 << 4)); + + /* set HDMI_LINK_BLUE_SCREEN_* to 0x0 */ + nx_hdmi_set_reg(0, HDMI_LINK_BLUE_SCREEN_R_0, 0x5555); + nx_hdmi_set_reg(0, HDMI_LINK_BLUE_SCREEN_R_1, 0x5555); + nx_hdmi_set_reg(0, HDMI_LINK_BLUE_SCREEN_G_0, 0x5555); + nx_hdmi_set_reg(0, HDMI_LINK_BLUE_SCREEN_G_1, 0x5555); + nx_hdmi_set_reg(0, HDMI_LINK_BLUE_SCREEN_B_0, 0x5555); + nx_hdmi_set_reg(0, HDMI_LINK_BLUE_SCREEN_B_1, 0x5555); + + /* set HDMI_CON_1 to 0x0 */ + nx_hdmi_set_reg(0, HDMI_LINK_HDMI_CON_1, 0x0); + nx_hdmi_set_reg(0, HDMI_LINK_HDMI_CON_2, 0x0); + + /* set interrupt : enable hpd_plug, hpd_unplug */ + nx_hdmi_set_reg(0, HDMI_LINK_INTC_CON_0, + (1 << 6) | (1 << 3) | (1 << 2)); + + /* set STATUS_EN to 0x17 */ + nx_hdmi_set_reg(0, HDMI_LINK_STATUS_EN, 0x17); + + /* TODO set HDP to 0x0 : later check hpd */ + nx_hdmi_set_reg(0, HDMI_LINK_HPD, 0x0); + + /* set MODE_SEL to 0x02 */ + nx_hdmi_set_reg(0, HDMI_LINK_MODE_SEL, 0x2); + + /* set H_BLANK_*, V1_BLANK_*, V2_BLANK_*, V_LINE_*, + * H_LINE_*, H_SYNC_START_*, H_SYNC_END_ * + * V_SYNC_LINE_BEF_1_*, V_SYNC_LINE_BEF_2_* + */ + nx_hdmi_set_reg(0, HDMI_LINK_H_BLANK_0, h_blank % 256); + nx_hdmi_set_reg(0, HDMI_LINK_H_BLANK_1, h_blank >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V1_BLANK_0, v_blank % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V1_BLANK_1, v_blank >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V2_BLANK_0, v2_blank % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V2_BLANK_1, v2_blank >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_LINE_0, v_line % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_LINE_1, v_line >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_H_LINE_0, h_line % 256); + nx_hdmi_set_reg(0, HDMI_LINK_H_LINE_1, h_line >> 8); + + if (width == 1280) { + nx_hdmi_set_reg(0, HDMI_LINK_HSYNC_POL, 0x1); + nx_hdmi_set_reg(0, HDMI_LINK_VSYNC_POL, 0x1); + } else { + nx_hdmi_set_reg(0, HDMI_LINK_HSYNC_POL, 0x0); + nx_hdmi_set_reg(0, HDMI_LINK_VSYNC_POL, 0x0); + } + + nx_hdmi_set_reg(0, HDMI_LINK_INT_PRO_MODE, 0x0); + + nx_hdmi_set_reg(0, HDMI_LINK_H_SYNC_START_0, (h_sync_start % 256) - 2); + nx_hdmi_set_reg(0, HDMI_LINK_H_SYNC_START_1, h_sync_start >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_H_SYNC_END_0, (h_sync_end % 256) - 2); + nx_hdmi_set_reg(0, HDMI_LINK_H_SYNC_END_1, h_sync_end >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_BEF_1_0, + v_sync_line_bef_1 % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_BEF_1_1, + v_sync_line_bef_1 >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_BEF_2_0, + v_sync_line_bef_2 % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_BEF_2_1, + v_sync_line_bef_2 >> 8); + + /* Set V_SYNC_LINE_AFT*, V_SYNC_LINE_AFT_PXL*, VACT_SPACE* */ + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_1_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_1_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_2_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_2_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_3_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_3_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_4_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_4_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_5_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_5_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_6_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_6_1, fixed_ffff >> 8); + + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_PXL_1_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_PXL_1_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_PXL_2_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_PXL_2_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_PXL_3_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_PXL_3_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_PXL_4_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_PXL_4_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_PXL_5_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_PXL_5_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_PXL_6_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_V_SYNC_LINE_AFT_PXL_6_1, fixed_ffff >> 8); + + nx_hdmi_set_reg(0, HDMI_LINK_VACT_SPACE1_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_VACT_SPACE1_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_VACT_SPACE2_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_VACT_SPACE2_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_VACT_SPACE3_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_VACT_SPACE3_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_VACT_SPACE4_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_VACT_SPACE4_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_VACT_SPACE5_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_VACT_SPACE5_1, fixed_ffff >> 8); + nx_hdmi_set_reg(0, HDMI_LINK_VACT_SPACE6_0, fixed_ffff % 256); + nx_hdmi_set_reg(0, HDMI_LINK_VACT_SPACE6_1, fixed_ffff >> 8); + + nx_hdmi_set_reg(0, HDMI_LINK_CSC_MUX, 0x0); + nx_hdmi_set_reg(0, HDMI_LINK_SYNC_GEN_MUX, 0x0); + + nx_hdmi_set_reg(0, HDMI_LINK_SEND_START_0, 0xfd); + nx_hdmi_set_reg(0, HDMI_LINK_SEND_START_1, 0x01); + nx_hdmi_set_reg(0, HDMI_LINK_SEND_END_0, 0x0d); + nx_hdmi_set_reg(0, HDMI_LINK_SEND_END_1, 0x3a); + nx_hdmi_set_reg(0, HDMI_LINK_SEND_END_2, 0x08); + + /* Set DC_CONTROL to 0x00 */ + nx_hdmi_set_reg(0, HDMI_LINK_DC_CONTROL, 0x0); + +#if CONFIG_HDMI_PATTERN + nx_hdmi_set_reg(0, HDMI_LINK_VIDEO_PATTERN_GEN, 0x1); +#else + nx_hdmi_set_reg(0, HDMI_LINK_VIDEO_PATTERN_GEN, 0x0); +#endif + + nx_hdmi_set_reg(0, HDMI_LINK_GCP_CON, 0x0a); + return 0; +} + +static void hdmi_init(void) +{ + void *base; + /** + * [SEQ 2] set the HDMI CLKGEN's PCLKMODE to always enabled + */ + base = + __io_address(nx_disp_top_clkgen_get_physical_address(hdmi_clkgen)); + nx_disp_top_clkgen_set_base_address(hdmi_clkgen, base); + nx_disp_top_clkgen_set_clock_pclk_mode(hdmi_clkgen, nx_pclkmode_always); + + base = __io_address(nx_hdmi_get_physical_address(0)); + nx_hdmi_set_base_address(0, base); + + /** + * [SEQ 3] set the 0xC001100C[0] to 1 + */ + nx_tieoff_set(NX_TIEOFF_DISPLAYTOP0_i_HDMI_PHY_REFCLK_SEL, 1); + + /** + * [SEQ 4] release the resets of HDMI.i_PHY_nRST and HDMI.i_nRST + */ + nx_rstcon_setrst(RESET_ID_HDMI_PHY, RSTCON_ASSERT); + nx_rstcon_setrst(RESET_ID_HDMI, RSTCON_ASSERT); + nx_rstcon_setrst(RESET_ID_HDMI_PHY, RSTCON_NEGATE); + nx_rstcon_setrst(RESET_ID_HDMI, RSTCON_NEGATE); +} + +void hdmi_enable(int input, int preset, struct dp_sync_info *sync, int enable) +{ + if (enable) { + nx_hdmi_set_reg(0, HDMI_LINK_HDMI_CON_0, + (nx_hdmi_get_reg(0, HDMI_LINK_HDMI_CON_0) | + 0x1)); + hdmi_vsync(sync); + } else { + hdmi_phy_enable(preset, 0); + } +} + +static int hdmi_setup(int input, int preset, + struct dp_sync_info *sync, struct dp_ctrl_info *ctrl) +{ + u32 HDMI_SEL = 0; + int ret; + + switch (input) { + case DP_DEVICE_DP0: + HDMI_SEL = primary_mlc; + break; + case DP_DEVICE_DP1: + HDMI_SEL = secondary_mlc; + break; + case DP_DEVICE_RESCONV: + HDMI_SEL = resolution_conv; + break; + default: + printf("HDMI: not support source device %d\n", input); + return -EINVAL; + } + + /** + * [SEQ 5] set up the HDMI PHY to specific video clock. + */ + ret = hdmi_phy_enable(preset, 1); + if (ret < 0) + return ret; + + /** + * [SEQ 6] I2S (or SPDIFTX) configuration for the source audio data + * this is done in another user app - ex> Android Audio HAL + */ + + /** + * [SEQ 7] Wait for ECID ready + */ + + /** + * [SEQ 8] release the resets of HDMI.i_VIDEO_nRST and HDMI.i_SPDIF_nRST + * and HDMI.i_TMDS_nRST + */ + hdmi_reset(); + + /** + * [SEQ 9] Wait for HDMI PHY ready (wait until 0xC0200020.[0], 1) + */ + if (hdmi_wait_phy_ready() == 0) { + printf("%s: failed to wait for hdmiphy ready\n", __func__); + hdmi_phy_enable(preset, 0); + return -EIO; + } + /* set mux */ + nx_disp_top_set_hdmimux(1, HDMI_SEL); + + /** + * [SEC 10] Set the DPC CLKGEN's Source Clock to HDMI_CLK & + * Set Sync Parameter + */ + hdmi_clock(); + /* set hdmi link clk to clkgen vs default is hdmi phy clk */ + + /** + * [SEQ 11] Set up the HDMI Converter parameters + */ + hdmi_get_vsync(preset, sync, ctrl); + hdmi_prepare(sync); + + return 0; +} + +void nx_hdmi_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_hdmi_dev *dev) +{ + struct dp_plane_info *plane = planes; + int input = module == 0 ? DP_DEVICE_DP0 : DP_DEVICE_DP1; + int count = top->plane_num; + int preset = dev->preset; + int i = 0; + + debug("HDMI: display.%d\n", module); + + switch (preset) { + case 0: + top->screen_width = 1280; + top->screen_height = 720; + sync->h_active_len = 1280; + sync->v_active_len = 720; + break; + case 1: + top->screen_width = 1920; + top->screen_height = 1080; + sync->h_active_len = 1920; + sync->v_active_len = 1080; + break; + default: + printf("hdmi not support preset %d\n", preset); + return; + } + + printf("HDMI: display.%d, preset %d (%4d * %4d)\n", + module, preset, top->screen_width, top->screen_height); + + dp_control_init(module); + dp_plane_init(module); + + hdmi_init(); + hdmi_setup(input, preset, sync, ctrl); + + 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); + + dp_control_setup(module, sync, ctrl); + dp_control_enable(module, 1); + + hdmi_enable(input, preset, sync, 1); +} diff --git a/drivers/video/nexell/s5pxx18_dp_lvds.c b/drivers/video/nexell/s5pxx18_dp_lvds.c new file mode 100644 index 0000000..f8ea63f --- /dev/null +++ b/drivers/video/nexell/s5pxx18_dp_lvds.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Nexell Co., Ltd. + * + * Author: junghyun, kim + */ + +#include +#include +#include + +#include +#include +#include + +#include "soc/s5pxx18_soc_lvds.h" +#include "soc/s5pxx18_soc_disptop.h" +#include "soc/s5pxx18_soc_disptop_clk.h" + +#define __io_address(a) (void *)(uintptr_t)(a) + +static void lvds_phy_reset(void) +{ + nx_rstcon_setrst(RESET_ID_LVDS, RSTCON_ASSERT); + nx_rstcon_setrst(RESET_ID_LVDS, RSTCON_NEGATE); +} + +static void lvds_init(void) +{ + int clkid = DP_CLOCK_LVDS; + int index = 0; + void *base; + + base = __io_address(nx_disp_top_clkgen_get_physical_address(clkid)); + nx_disp_top_clkgen_set_base_address(clkid, base); + + nx_lvds_initialize(); + + for (index = 0; nx_lvds_get_number_of_module() > index; index++) + nx_lvds_set_base_address(index, + (void *)__io_address(nx_lvds_get_physical_address(index))); + + nx_disp_top_clkgen_set_clock_pclk_mode(clkid, nx_pclkmode_always); +} + +static void lvds_enable(int enable) +{ + int clkid = DP_CLOCK_LVDS; + int on = (enable ? 1 : 0); + + nx_disp_top_clkgen_set_clock_divisor_enable(clkid, on); +} + +static int lvds_setup(int module, int input, + struct dp_sync_info *sync, struct dp_ctrl_info *ctrl, + struct dp_lvds_dev *dev) +{ + unsigned int val; + int clkid = DP_CLOCK_LVDS; + enum dp_lvds_format format = DP_LVDS_FORMAT_JEIDA; + u32 voltage = DEF_VOLTAGE_LEVEL; + + if (dev) { + format = dev->lvds_format; + voltage = dev->voltage_level; + } + + printf("LVDS: "); + printf("%s, ", format == DP_LVDS_FORMAT_VESA ? "VESA" : + format == DP_LVDS_FORMAT_JEIDA ? "JEIDA" : "LOC"); + printf("voltage LV:0x%x\n", voltage); + + /* + *-------- predefined type. + * only change iTA to iTE in VESA mode + * wire [34:0] loc_VideoIn = + * {4'hf, 4'h0, i_VDEN, i_VSYNC, i_HSYNC, i_VD[23:0] }; + */ + u32 VSYNC = 25; + u32 HSYNC = 24; + u32 VDEN = 26; /* bit position */ + u32 ONE = 34; + u32 ZERO = 27; + + /*==================================================== + * current not use location mode + *==================================================== + */ + u32 LOC_A[7] = {ONE, ONE, ONE, ONE, ONE, ONE, ONE}; + u32 LOC_B[7] = {ONE, ONE, ONE, ONE, ONE, ONE, ONE}; + u32 LOC_C[7] = {VDEN, VSYNC, HSYNC, ONE, HSYNC, VSYNC, VDEN}; + u32 LOC_D[7] = {ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO}; + u32 LOC_E[7] = {ZERO, ZERO, ZERO, ZERO, ZERO, ZERO, ZERO}; + + switch (input) { + case DP_DEVICE_DP0: + input = 0; + break; + case DP_DEVICE_DP1: + input = 1; + break; + case DP_DEVICE_RESCONV: + input = 2; + break; + default: + return -EINVAL; + } + + /* + * select TOP MUX + */ + nx_disp_top_clkgen_set_clock_divisor_enable(clkid, 0); + nx_disp_top_clkgen_set_clock_source(clkid, 0, ctrl->clk_src_lv0); + nx_disp_top_clkgen_set_clock_divisor(clkid, 0, ctrl->clk_div_lv0); + nx_disp_top_clkgen_set_clock_source(clkid, 1, ctrl->clk_src_lv1); + nx_disp_top_clkgen_set_clock_divisor(clkid, 1, ctrl->clk_div_lv1); + + /* + * LVDS Control Pin Setting + */ + val = (0 << 30) | /* CPU_I_VBLK_FLAG_SEL */ + (0 << 29) | /* CPU_I_BVLK_FLAG */ + (1 << 28) | /* SKINI_BST */ + (1 << 27) | /* DLYS_BST */ + (0 << 26) | /* I_AUTO_SEL */ + (format << 19) | /* JEiDA data packing */ + (0x1B << 13) | /* I_LOCK_PPM_SET, PPM setting for PLL lock */ + (0x638 << 1); /* I_DESKEW_CNT_SEL, period of de-skew region */ + nx_lvds_set_lvdsctrl0(0, val); + + val = (0 << 28) | /* I_ATE_MODE, function mode */ + (0 << 27) | /* I_TEST_CON_MODE, DA (test ctrl mode) */ + (0 << 24) | /* I_TX4010X_DUMMY */ + (0 << 15) | /* SKCCK 0 */ + (0 << 12) | /* SKC4 (TX output skew control pin at ODD ch4) */ + (0 << 9) | /* SKC3 (TX output skew control pin at ODD ch3) */ + (0 << 6) | /* SKC2 (TX output skew control pin at ODD ch2) */ + (0 << 3) | /* SKC1 (TX output skew control pin at ODD ch1) */ + (0 << 0); /* SKC0 (TX output skew control pin at ODD ch0) */ + nx_lvds_set_lvdsctrl1(0, val); + + val = (0 << 15) | /* CK_POL_SEL, Input clock, bypass */ + (0 << 14) | /* VSEL, VCO Freq. range. 0: Low(40MHz~90MHz), + * 1: High(90MHz~160MHz) */ + (0x1 << 12) | /* S (Post-scaler) */ + (0xA << 6) | /* M (Main divider) */ + (0xA << 0); /* P (Pre-divider) */ + + nx_lvds_set_lvdsctrl2(0, val); + val = (0x03 << 6) | /* SK_BIAS, Bias current ctrl pin */ + (0 << 5) | /* SKEWINI, skew selection pin, 0: bypass, + * 1: skew enable */ + (0 << 4) | /* SKEW_EN_H, skew block power down, 0: power down, + * 1: operating */ + (1 << 3) | /* CNTB_TDLY, delay control pin */ + (0 << 2) | /* SEL_DATABF, input clock 1/2 division cont. pin */ + (0x3 << 0); /* SKEW_REG_CUR, regulator bias current selection + * in SKEW block */ + + nx_lvds_set_lvdsctrl3(0, val); + val = (0 << 28) | /* FLT_CNT, filter control pin for PLL */ + (0 << 27) | /* VOD_ONLY_CNT, the pre-emphasis's pre-diriver + * control pin (VOD only) */ + (0 << 26) | /* CNNCT_MODE_SEL, connectivity mode selection, + * 0:TX operating, 1:con check */ + (0 << 24) | /* CNNCT_CNT, connectivity ctrl pin, + * 0: tx operating, 1: con check */ + (0 << 23) | /* VOD_HIGH_S, VOD control pin, 1: Vod only */ + (0 << 22) | /* SRC_TRH, source termination resistor sel. pin */ + (voltage << 14) | + (0x01 << 6) | /* CNT_PEN_H, TX driver pre-emphasis level cont. */ + (0x4 << 3) | /* FC_CODE, vos control pin */ + (0 << 2) | /* OUTCON, TX Driver state selectioin pin, 0:Hi-z, + * 1:Low */ + (0 << 1) | /* LOCK_CNT, Lock signal selection pin, enable */ + (0 << 0); /* AUTO_DSK_SEL, auto deskew sel. pin, normal */ + nx_lvds_set_lvdsctrl4(0, val); + + val = (0 << 24) | /* I_BIST_RESETB */ + (0 << 23) | /* I_BIST_EN */ + (0 << 21) | /* I_BIST_PAT_SEL */ + (0 << 14) | /* I_BIST_USER_PATTERN */ + (0 << 13) | /* I_BIST_FORCE_ERROR */ + (0 << 7) | /* I_BIST_SKEW_CTRL */ + (0 << 5) | /* I_BIST_CLK_INV */ + (0 << 3) | /* I_BIST_DATA_INV */ + (0 << 0); /* I_BIST_CH_SEL */ + nx_lvds_set_lvdstmode0(0, val); + + /* user do not need to modify this codes. */ + val = (LOC_A[4] << 24) | (LOC_A[3] << 18) | (LOC_A[2] << 12) | + (LOC_A[1] << 6) | (LOC_A[0] << 0); + nx_lvds_set_lvdsloc0(0, val); + + val = (LOC_B[2] << 24) | (LOC_B[1] << 18) | (LOC_B[0] << 12) | + (LOC_A[6] << 6) | (LOC_A[5] << 0); + nx_lvds_set_lvdsloc1(0, val); + + val = (LOC_C[0] << 24) | (LOC_B[6] << 18) | (LOC_B[5] << 12) | + (LOC_B[4] << 6) | (LOC_B[3] << 0); + nx_lvds_set_lvdsloc2(0, val); + + val = (LOC_C[5] << 24) | (LOC_C[4] << 18) | (LOC_C[3] << 12) | + (LOC_C[2] << 6) | (LOC_C[1] << 0); + nx_lvds_set_lvdsloc3(0, val); + + val = (LOC_D[3] << 24) | (LOC_D[2] << 18) | (LOC_D[1] << 12) | + (LOC_D[0] << 6) | (LOC_C[6] << 0); + nx_lvds_set_lvdsloc4(0, val); + + val = (LOC_E[1] << 24) | (LOC_E[0] << 18) | (LOC_D[6] << 12) | + (LOC_D[5] << 6) | (LOC_D[4] << 0); + nx_lvds_set_lvdsloc5(0, val); + + val = (LOC_E[6] << 24) | (LOC_E[5] << 18) | (LOC_E[4] << 12) | + (LOC_E[3] << 6) | (LOC_E[2] << 0); + nx_lvds_set_lvdsloc6(0, val); + + nx_lvds_set_lvdslocmask0(0, 0xffffffff); + nx_lvds_set_lvdslocmask1(0, 0xffffffff); + + nx_lvds_set_lvdslocpol0(0, (0 << 19) | (0 << 18)); + + /* + * select TOP MUX + */ + nx_disp_top_set_lvdsmux(1, input); + + /* + * LVDS PHY Reset, make sure last. + */ + lvds_phy_reset(); + + return 0; +} + +void nx_lvds_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_lvds_dev *dev) +{ + struct dp_plane_info *plane = planes; + int input = module == 0 ? DP_DEVICE_DP0 : DP_DEVICE_DP1; + int count = top->plane_num; + int i = 0; + + printf("LVDS: dp.%d\n", module); + + dp_control_init(module); + dp_plane_init(module); + + lvds_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 lvds */ + lvds_setup(module, input, sync, ctrl, dev); + + lvds_enable(1); + + /* set dp control */ + dp_control_setup(module, sync, ctrl); + dp_control_enable(module, 1); +} diff --git a/drivers/video/nexell/s5pxx18_dp_mipi.c b/drivers/video/nexell/s5pxx18_dp_mipi.c new file mode 100644 index 0000000..670272b --- /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 + */ + +#include +#include +#include + +#include +#include +#include +#include + +#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); +} diff --git a/drivers/video/nexell/s5pxx18_dp_rgb.c b/drivers/video/nexell/s5pxx18_dp_rgb.c new file mode 100644 index 0000000..44e8edb --- /dev/null +++ b/drivers/video/nexell/s5pxx18_dp_rgb.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Nexell Co., Ltd. + * + * Author: junghyun, kim + */ + +#include +#include +#include + +#include + +#include "soc/s5pxx18_soc_disptop.h" + +static int rgb_switch(int module, int input, struct dp_sync_info *sync, + struct dp_rgb_dev *dev) +{ + int mpu = dev->lcd_mpu_type; + int rsc = 0, sel = 0; + + switch (module) { + case 0: + sel = mpu ? 1 : 0; + break; + case 1: + sel = rsc ? 3 : 2; + break; + default: + printf("Fail, %s nuknown module %d\n", __func__, module); + return -1; + } + + nx_disp_top_set_primary_mux(sel); + return 0; +} + +void nx_rgb_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_rgb_dev *dev) +{ + struct dp_plane_info *plane = planes; + int input = module == 0 ? DP_DEVICE_DP0 : DP_DEVICE_DP1; + int count = top->plane_num; + int i = 0; + + printf("RGB: dp.%d\n", module); + + dp_control_init(module); + dp_plane_init(module); + + /* 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); + + rgb_switch(module, input, sync, dev); + + dp_control_setup(module, sync, ctrl); + dp_control_enable(module, 1); +} diff --git a/drivers/video/nexell_display.c b/drivers/video/nexell_display.c new file mode 100644 index 0000000..7ff4651 --- /dev/null +++ b/drivers/video/nexell_display.c @@ -0,0 +1,658 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Nexell Co., Ltd. + * + * Author: junghyun, kim + * + * Copyright (C) 2019 Stefan Bosch + */ + +#include +#include +#include +#include +#include +#include +#include +#include /* For struct video_uc_platdata */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "videomodes.h" + +DECLARE_GLOBAL_DATA_PTR; + +#if !defined(CONFIG_DM) && !defined(CONFIG_OF_CONTROL) +static struct nx_display_dev *dp_dev; +#endif + +static char *const dp_dev_str[] = { + [DP_DEVICE_RESCONV] = "RESCONV", + [DP_DEVICE_RGBLCD] = "LCD", + [DP_DEVICE_HDMI] = "HDMI", + [DP_DEVICE_MIPI] = "MiPi", + [DP_DEVICE_LVDS] = "LVDS", + [DP_DEVICE_CVBS] = "TVOUT", + [DP_DEVICE_DP0] = "DP0", + [DP_DEVICE_DP1] = "DP1", +}; + +#if CONFIG_IS_ENABLED(OF_CONTROL) +static void nx_display_parse_dp_sync(const void *blob, int node, + struct dp_sync_info *sync) +{ + sync->h_active_len = fdtdec_get_int(blob, node, "h_active_len", 0); + sync->h_sync_width = fdtdec_get_int(blob, node, "h_sync_width", 0); + sync->h_back_porch = fdtdec_get_int(blob, node, "h_back_porch", 0); + sync->h_front_porch = fdtdec_get_int(blob, node, "h_front_porch", 0); + sync->h_sync_invert = fdtdec_get_int(blob, node, "h_sync_invert", 0); + sync->v_active_len = fdtdec_get_int(blob, node, "v_active_len", 0); + sync->v_sync_width = fdtdec_get_int(blob, node, "v_sync_width", 0); + sync->v_back_porch = fdtdec_get_int(blob, node, "v_back_porch", 0); + sync->v_front_porch = fdtdec_get_int(blob, node, "v_front_porch", 0); + sync->v_sync_invert = fdtdec_get_int(blob, node, "v_sync_invert", 0); + sync->pixel_clock_hz = fdtdec_get_int(blob, node, "pixel_clock_hz", 0); + + debug("DP: sync ->\n"); + debug("ha:%d, hs:%d, hb:%d, hf:%d, hi:%d\n", + sync->h_active_len, sync->h_sync_width, + sync->h_back_porch, sync->h_front_porch, sync->h_sync_invert); + debug("va:%d, vs:%d, vb:%d, vf:%d, vi:%d\n", + sync->v_active_len, sync->v_sync_width, + sync->v_back_porch, sync->v_front_porch, sync->v_sync_invert); +} + +static void nx_display_parse_dp_ctrl(const void *blob, int node, + struct dp_ctrl_info *ctrl) +{ + /* clock gen */ + ctrl->clk_src_lv0 = fdtdec_get_int(blob, node, "clk_src_lv0", 0); + ctrl->clk_div_lv0 = fdtdec_get_int(blob, node, "clk_div_lv0", 0); + ctrl->clk_src_lv1 = fdtdec_get_int(blob, node, "clk_src_lv1", 0); + ctrl->clk_div_lv1 = fdtdec_get_int(blob, node, "clk_div_lv1", 0); + + /* scan format */ + ctrl->interlace = fdtdec_get_int(blob, node, "interlace", 0); + + /* syncgen format */ + ctrl->out_format = fdtdec_get_int(blob, node, "out_format", 0); + ctrl->invert_field = fdtdec_get_int(blob, node, "invert_field", 0); + ctrl->swap_RB = fdtdec_get_int(blob, node, "swap_RB", 0); + ctrl->yc_order = fdtdec_get_int(blob, node, "yc_order", 0); + + /* extern sync delay */ + ctrl->delay_mask = fdtdec_get_int(blob, node, "delay_mask", 0); + ctrl->d_rgb_pvd = fdtdec_get_int(blob, node, "d_rgb_pvd", 0); + ctrl->d_hsync_cp1 = fdtdec_get_int(blob, node, "d_hsync_cp1", 0); + ctrl->d_vsync_fram = fdtdec_get_int(blob, node, "d_vsync_fram", 0); + ctrl->d_de_cp2 = fdtdec_get_int(blob, node, "d_de_cp2", 0); + + /* extern sync delay */ + ctrl->vs_start_offset = + fdtdec_get_int(blob, node, "vs_start_offset", 0); + ctrl->vs_end_offset = fdtdec_get_int(blob, node, "vs_end_offset", 0); + ctrl->ev_start_offset = + fdtdec_get_int(blob, node, "ev_start_offset", 0); + ctrl->ev_end_offset = fdtdec_get_int(blob, node, "ev_end_offset", 0); + + /* pad clock seletor */ + ctrl->vck_select = fdtdec_get_int(blob, node, "vck_select", 0); + ctrl->clk_inv_lv0 = fdtdec_get_int(blob, node, "clk_inv_lv0", 0); + ctrl->clk_delay_lv0 = fdtdec_get_int(blob, node, "clk_delay_lv0", 0); + ctrl->clk_inv_lv1 = fdtdec_get_int(blob, node, "clk_inv_lv1", 0); + ctrl->clk_delay_lv1 = fdtdec_get_int(blob, node, "clk_delay_lv1", 0); + ctrl->clk_sel_div1 = fdtdec_get_int(blob, node, "clk_sel_div1", 0); + + debug("DP: ctrl [%s] ->\n", + ctrl->interlace ? "Interlace" : " Progressive"); + debug("cs0:%d, cd0:%d, cs1:%d, cd1:%d\n", + ctrl->clk_src_lv0, ctrl->clk_div_lv0, + ctrl->clk_src_lv1, ctrl->clk_div_lv1); + debug("fmt:0x%x, inv:%d, swap:%d, yb:0x%x\n", + ctrl->out_format, ctrl->invert_field, + ctrl->swap_RB, ctrl->yc_order); + debug("dm:0x%x, drp:%d, dhs:%d, dvs:%d, dde:0x%x\n", + ctrl->delay_mask, ctrl->d_rgb_pvd, + ctrl->d_hsync_cp1, ctrl->d_vsync_fram, ctrl->d_de_cp2); + debug("vss:%d, vse:%d, evs:%d, eve:%d\n", + ctrl->vs_start_offset, ctrl->vs_end_offset, + ctrl->ev_start_offset, ctrl->ev_end_offset); + debug("sel:%d, i0:%d, d0:%d, i1:%d, d1:%d, s1:%d\n", + ctrl->vck_select, ctrl->clk_inv_lv0, ctrl->clk_delay_lv0, + ctrl->clk_inv_lv1, ctrl->clk_delay_lv1, ctrl->clk_sel_div1); +} + +static void nx_display_parse_dp_top_layer(const void *blob, int node, + struct dp_plane_top *top) +{ + top->screen_width = fdtdec_get_int(blob, node, "screen_width", 0); + top->screen_height = fdtdec_get_int(blob, node, "screen_height", 0); + top->video_prior = fdtdec_get_int(blob, node, "video_prior", 0); + top->interlace = fdtdec_get_int(blob, node, "interlace", 0); + top->back_color = fdtdec_get_int(blob, node, "back_color", 0); + top->plane_num = DP_PLANS_NUM; + + debug("DP: top [%s] ->\n", + top->interlace ? "Interlace" : " Progressive"); + debug("w:%d, h:%d, prior:%d, bg:0x%x\n", + top->screen_width, top->screen_height, + top->video_prior, top->back_color); +} + +static void nx_display_parse_dp_layer(const void *blob, int node, + struct dp_plane_info *plane) +{ + plane->left = fdtdec_get_int(blob, node, "left", 0); + plane->width = fdtdec_get_int(blob, node, "width", 0); + plane->top = fdtdec_get_int(blob, node, "top", 0); + plane->height = fdtdec_get_int(blob, node, "height", 0); + plane->pixel_byte = fdtdec_get_int(blob, node, "pixel_byte", 0); + plane->format = fdtdec_get_int(blob, node, "format", 0); + plane->alpha_on = fdtdec_get_int(blob, node, "alpha_on", 0); + plane->alpha_depth = fdtdec_get_int(blob, node, "alpha", 0); + plane->tp_on = fdtdec_get_int(blob, node, "tp_on", 0); + plane->tp_color = fdtdec_get_int(blob, node, "tp_color", 0); + + /* enable layer */ + if (plane->fb_base) + plane->enable = 1; + else + plane->enable = 0; + + if (plane->fb_base == 0) { + printf("fail : dp plane.%d invalid fb base [0x%x] ->\n", + plane->layer, plane->fb_base); + return; + } + + debug("DP: plane.%d [0x%x] ->\n", plane->layer, plane->fb_base); + debug("f:0x%x, l:%d, t:%d, %d * %d, bpp:%d, a:%d(%d), t:%d(0x%x)\n", + plane->format, plane->left, plane->top, plane->width, + plane->height, plane->pixel_byte, plane->alpha_on, + plane->alpha_depth, plane->tp_on, plane->tp_color); +} + +static void nx_display_parse_dp_planes(const void *blob, int node, + struct nx_display_dev *dp, + struct video_uc_platdata *plat) +{ + const char *name; + + for (node = fdt_first_subnode(blob, node); + node > 0; node = fdt_next_subnode(blob, node)) { + name = fdt_get_name(blob, node, NULL); + + if (strcmp(name, "layer_top") == 0) + nx_display_parse_dp_top_layer(blob, node, &dp->top); + + /* + * TODO: Is it sure that only one layer is used? Otherwise + * fb_base must be different? + */ + if (strcmp(name, "layer_0") == 0) { + dp->planes[0].fb_base = + (uint)map_sysmem(plat->base, plat->size); + debug("%s(): dp->planes[0].fb_base == 0x%x\n", __func__, + (uint)dp->planes[0].fb_base); + nx_display_parse_dp_layer(blob, node, &dp->planes[0]); + } + + if (strcmp(name, "layer_1") == 0) { + dp->planes[1].fb_base = + (uint)map_sysmem(plat->base, plat->size); + debug("%s(): dp->planes[1].fb_base == 0x%x\n", __func__, + (uint)dp->planes[1].fb_base); + nx_display_parse_dp_layer(blob, node, &dp->planes[1]); + } + + if (strcmp(name, "layer_2") == 0) { + dp->planes[2].fb_base = + (uint)map_sysmem(plat->base, plat->size); + debug("%s(): dp->planes[2].fb_base == 0x%x\n", __func__, + (uint)dp->planes[2].fb_base); + nx_display_parse_dp_layer(blob, node, &dp->planes[2]); + } + } +} + +static int nx_display_parse_dp_lvds(const void *blob, int node, + struct nx_display_dev *dp) +{ + struct dp_lvds_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL); + + if (!dev) { + printf("failed to allocate display LVDS object.\n"); + return -ENOMEM; + } + + dp->device = dev; + + dev->lvds_format = fdtdec_get_int(blob, node, "format", 0); + dev->pol_inv_hs = fdtdec_get_int(blob, node, "pol_inv_hs", 0); + dev->pol_inv_vs = fdtdec_get_int(blob, node, "pol_inv_vs", 0); + dev->pol_inv_de = fdtdec_get_int(blob, node, "pol_inv_de", 0); + dev->pol_inv_ck = fdtdec_get_int(blob, node, "pol_inv_ck", 0); + dev->voltage_level = fdtdec_get_int(blob, node, "voltage_level", 0); + + if (!dev->voltage_level) + dev->voltage_level = DEF_VOLTAGE_LEVEL; + + debug("DP: LVDS -> %s, voltage LV:0x%x\n", + dev->lvds_format == DP_LVDS_FORMAT_VESA ? "VESA" : + dev->lvds_format == DP_LVDS_FORMAT_JEIDA ? "JEIDA" : "LOC", + dev->voltage_level); + debug("pol inv hs:%d, vs:%d, de:%d, ck:%d\n", + dev->pol_inv_hs, dev->pol_inv_vs, + dev->pol_inv_de, dev->pol_inv_ck); + + return 0; +} + +static int nx_display_parse_dp_rgb(const void *blob, int node, + struct nx_display_dev *dp) +{ + struct dp_rgb_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL); + + if (!dev) { + printf("failed to allocate display RGB LCD object.\n"); + return -ENOMEM; + } + dp->device = dev; + + dev->lcd_mpu_type = fdtdec_get_int(blob, node, "lcd_mpu_type", 0); + + debug("DP: RGB -> MPU[%s]\n", dev->lcd_mpu_type ? "O" : "X"); + return 0; +} + +static int nx_display_parse_dp_mipi(const void *blob, int node, + struct nx_display_dev *dp) +{ + struct dp_mipi_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL); + + if (!dev) { + printf("failed to allocate display MiPi object.\n"); + return -ENOMEM; + } + dp->device = dev; + + dev->lp_bitrate = fdtdec_get_int(blob, node, "lp_bitrate", 0); + dev->hs_bitrate = fdtdec_get_int(blob, node, "hs_bitrate", 0); + dev->lpm_trans = 1; + dev->command_mode = 0; + + debug("DP: MIPI ->\n"); + debug("lp:%dmhz, hs:%dmhz\n", dev->lp_bitrate, dev->hs_bitrate); + + return 0; +} + +static int nx_display_parse_dp_hdmi(const void *blob, int node, + struct nx_display_dev *dp) +{ + struct dp_hdmi_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL); + + if (!dev) { + printf("failed to allocate display HDMI object.\n"); + return -ENOMEM; + } + dp->device = dev; + + dev->preset = fdtdec_get_int(blob, node, "preset", 0); + + debug("DP: HDMI -> %d\n", dev->preset); + + return 0; +} + +static int nx_display_parse_dp_lcds(const void *blob, int node, + const char *type, struct nx_display_dev *dp) +{ + if (strcmp(type, "lvds") == 0) { + dp->dev_type = DP_DEVICE_LVDS; + return nx_display_parse_dp_lvds(blob, node, dp); + } else if (strcmp(type, "rgb") == 0) { + dp->dev_type = DP_DEVICE_RGBLCD; + return nx_display_parse_dp_rgb(blob, node, dp); + } else if (strcmp(type, "mipi") == 0) { + dp->dev_type = DP_DEVICE_MIPI; + return nx_display_parse_dp_mipi(blob, node, dp); + } else if (strcmp(type, "hdmi") == 0) { + dp->dev_type = DP_DEVICE_HDMI; + return nx_display_parse_dp_hdmi(blob, node, dp); + } + + printf("%s: node %s unknown display type\n", __func__, + fdt_get_name(blob, node, NULL)); + return -EINVAL; + + return 0; +} + +#define DT_SYNC (1 << 0) +#define DT_CTRL (1 << 1) +#define DT_PLANES (1 << 2) +#define DT_DEVICE (1 << 3) + +static int nx_display_parse_dt(const void *blob, + struct nx_display_dev *dp, int node, + struct video_uc_platdata *plat) +{ + const char *name, *dtype; + int ret = 0; + unsigned int dt_status = 0; + + if (!node) + return -ENODEV; + + dp->module = fdtdec_get_int(blob, node, "module", -1); + if (dp->module == -1) + dp->module = fdtdec_get_int(blob, node, "index", 0); + + dtype = fdt_getprop(blob, node, "lcd-type", NULL); + + for (node = fdt_first_subnode(blob, node); + node > 0; node = fdt_next_subnode(blob, node)) { + name = fdt_get_name(blob, node, NULL); + + if (strcmp("dp-sync", name) == 0) { + dt_status |= DT_SYNC; + nx_display_parse_dp_sync(blob, node, &dp->sync); + } + + if (strcmp("dp-ctrl", name) == 0) { + dt_status |= DT_CTRL; + nx_display_parse_dp_ctrl(blob, node, &dp->ctrl); + } + + if (strcmp("dp-planes", name) == 0) { + dt_status |= DT_PLANES; + nx_display_parse_dp_planes(blob, node, dp, plat); + } + + if (strcmp("dp-device", name) == 0) { + dt_status |= DT_DEVICE; + ret = nx_display_parse_dp_lcds(blob, node, dtype, dp); + } + } + + if (dt_status != (DT_SYNC | DT_CTRL | DT_PLANES | DT_DEVICE)) { + printf("Not enough DT config for display [0x%x]\n", dt_status); + return -ENODEV; + } + + return ret; +} +#endif + +__weak int nx_display_fixup_dp(struct nx_display_dev *dp) +{ + return 0; +} + +static struct nx_display_dev *nx_display_setup(void) +{ + struct nx_display_dev *dp; + int i, ret; + int node = 0; + struct video_uc_platdata *plat = NULL; + + struct udevice *dev; + + /* call driver probe */ + debug("DT: uclass device call...\n"); + + ret = uclass_get_device(UCLASS_VIDEO, 0, &dev); + if (ret) { + debug("%s(): uclass_get_device(UCLASS_VIDEO, 0, &dev) != 0 --> return NULL\n", __func__); + return NULL; + } + plat = dev_get_uclass_platdata(dev); + if (!dev) { + debug("%s(): dev_get_uclass_platdata(dev) == NULL --> return NULL\n", __func__); + return NULL; + } + dp = dev_get_priv(dev); + if (!dp) { + debug("%s(): dev_get_priv(dev) == NULL --> return NULL\n", __func__); + return NULL; + } + node = dev->node.of_offset; + +#if CONFIG_IS_ENABLED(OF_CONTROL) + ret = nx_display_parse_dt(gd->fdt_blob, dp, node, plat); + if (ret) + goto err_setup; +#endif + + nx_display_fixup_dp(dp); + + for (i = 0; dp->top.plane_num > i; i++) { + dp->planes[i].layer = i; + if (dp->planes[i].enable && !dp->fb_plane) { + dp->fb_plane = &dp->planes[i]; + dp->fb_addr = dp->fb_plane->fb_base; + dp->depth = dp->fb_plane->pixel_byte; + } + } + + switch (dp->dev_type) { +#ifdef CONFIG_VIDEO_NX_RGB + case DP_DEVICE_RGBLCD: + nx_rgb_display(dp->module, + &dp->sync, &dp->ctrl, &dp->top, + dp->planes, (struct dp_rgb_dev *)dp->device); + break; +#endif +#ifdef CONFIG_VIDEO_NX_LVDS + case DP_DEVICE_LVDS: + nx_lvds_display(dp->module, + &dp->sync, &dp->ctrl, &dp->top, + dp->planes, (struct dp_lvds_dev *)dp->device); + break; +#endif +#ifdef CONFIG_VIDEO_NX_MIPI + case DP_DEVICE_MIPI: + nx_mipi_display(dp->module, + &dp->sync, &dp->ctrl, &dp->top, + dp->planes, (struct dp_mipi_dev *)dp->device); + break; +#endif +#ifdef CONFIG_VIDEO_NX_HDMI + case DP_DEVICE_HDMI: + nx_hdmi_display(dp->module, + &dp->sync, &dp->ctrl, &dp->top, + dp->planes, (struct dp_hdmi_dev *)dp->device); + break; +#endif + default: + printf("fail : not support lcd type %d !!!\n", dp->dev_type); + goto err_setup; + }; + + printf("LCD: [%s] dp.%d.%d %dx%d %dbpp FB:0x%08x\n", + dp_dev_str[dp->dev_type], dp->module, dp->fb_plane->layer, + dp->fb_plane->width, dp->fb_plane->height, dp->depth * 8, + dp->fb_addr); + + return dp; + +err_setup: + kfree(dp); + + return NULL; +} + +#if defined CONFIG_LCD + +/* default lcd */ +struct vidinfo panel_info = { + .vl_col = 320, .vl_row = 240, .vl_bpix = 32, +}; + +void lcd_ctrl_init(void *lcdbase) +{ + vidinfo_t *pi = &panel_info; + struct nx_display_dev *dp; + int bpix; + + dp = nx_display_setup(); + if (!dp) + return NULL; + + switch (dp->depth) { + case 2: + bpix = LCD_COLOR16; + break; + case 3: + case 4: + bpix = LCD_COLOR32; + break; + default: + printf("fail : not support LCD bit per pixel %d\n", + dp->depth * 8); + return NULL; + } + + dp->panel_info = pi; + + /* set resolution with config */ + pi->vl_bpix = bpix; + pi->vl_col = dp->fb_plane->width; + pi->vl_row = dp->fb_plane->height; + pi->priv = dp; + gd->fb_base = dp->fb_addr; +} + +void lcd_setcolreg(ushort regno, ushort red, ushort green, ushort blue) +{ +} + +__weak void lcd_enable(void) +{ +} +#endif + +static int nx_display_dev_probe_uclass(struct udevice *dev) +{ + struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev); + struct video_priv *uc_priv = dev_get_uclass_priv(dev); + struct nx_display_platdata *plat = dev_get_platdata(dev); + static GraphicDevice *graphic_device; + char addr[64]; + + debug("%s()\n", __func__); + + if (!dev) + return -EINVAL; + + if (!uc_plat) { + debug("%s(): video_uc_platdata *plat == NULL --> return -EINVAL\n", __func__); + return -EINVAL; + } + + if (!uc_priv) { + debug("%s(): video_priv *uc_priv == NULL --> return -EINVAL\n", __func__); + return -EINVAL; + } + + if (!plat) { + debug("%s(): nx_display_platdata *plat == NULL --> return -EINVAL\n", __func__); + return -EINVAL; + } + + struct nx_display_dev *dp; + unsigned int pp_index = 0; + + dp = nx_display_setup(); + if (!dp) { + debug("%s(): nx_display_setup() == 0 --> return -EINVAL\n", __func__); + return -EINVAL; + } + + switch (dp->depth) { + case 2: + pp_index = GDF_16BIT_565RGB; + uc_priv->bpix = VIDEO_BPP16; + break; + case 3: + /* There is no VIDEO_BPP24 because these values are of + * type video_log2_bpp + */ + case 4: + pp_index = GDF_32BIT_X888RGB; + uc_priv->bpix = VIDEO_BPP32; + break; + default: + printf("fail : not support LCD bit per pixel %d\n", + dp->depth * 8); + return -EINVAL; + } + + uc_priv->xsize = dp->fb_plane->width; + uc_priv->ysize = dp->fb_plane->height; + uc_priv->rot = 0; + + graphic_device = &dp->graphic_device; + graphic_device->frameAdrs = dp->fb_addr; + graphic_device->gdfIndex = pp_index; + graphic_device->gdfBytesPP = dp->depth; + graphic_device->winSizeX = dp->fb_plane->width; + graphic_device->winSizeY = dp->fb_plane->height; + graphic_device->plnSizeX = + graphic_device->winSizeX * graphic_device->gdfBytesPP; + + /* + * set environment variable "fb_addr" (frame buffer address), required + * for splash image. Because drv_video_init() in common/stdio.c is only + * called when CONFIG_VIDEO is set (and not when CONFIG_DM_VIDEO is set). + */ + sprintf(addr, "0x%x", dp->fb_addr); + debug("%s(): env_set(\"fb_addr\", %s) ...\n", __func__, addr); + env_set("fb_addr", addr); + + return 0; +} + +static int nx_display_bind(struct udevice *dev) +{ + struct video_uc_platdata *plat = dev_get_uclass_platdata(dev); + + debug("%s()\n", __func__); + + /* Datasheet S5p4418: + * Resolution up to 2048 x 1280, up to 12 Bit per color (HDMI) + * Actual (max.) size is 0x1000000 because in U-Boot nanopi2-2016.01 + * "#define CONFIG_FB_ADDR 0x77000000" and next address is + * "#define BMP_LOAD_ADDR 0x78000000" + */ + plat->size = 0x1000000; + + return 0; +} + +static int nx_display_probe(struct udevice *dev) +{ + return nx_display_dev_probe_uclass(dev); +} + +static const struct udevice_id nx_display_ids[] = { + {.compatible = "nexell,nexell-display", }, + {} +}; + +U_BOOT_DRIVER(nexell_display) = { + .name = "nexell-display", + .id = UCLASS_VIDEO, + .of_match = nx_display_ids, + .platdata_auto_alloc_size = + sizeof(struct nx_display_platdata), + .bind = nx_display_bind, + .probe = nx_display_probe, + .priv_auto_alloc_size = sizeof(struct nx_display_dev), +};