diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 6ba353360b4f9ccd2175f20da814bbfebd263ff1..2f3c80ebab159332079d7746d8cda4889f7b55e9 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -48,7 +48,7 @@ ifneq ($(CONFIG_SPL_BUILD),y) obj-$(CONFIG_FSL_MFGPROT) += cmd_mfgprot.o endif endif -ifeq ($(SOC),$(filter $(SOC),mx5 mx6 mx7)) +ifeq ($(SOC),$(filter $(SOC),mx5 mx6 mx7 imx8m)) obj-$(CONFIG_IMX_VIDEO_SKIP) += video.o endif ifeq ($(SOC),$(filter $(SOC),mx6 mx7)) diff --git a/arch/arm/mach-imx/imx8m/clock_imx8mm.c b/arch/arm/mach-imx/imx8m/clock_imx8mm.c index 1cfa2cf6803667eff84e40a6484aeb0eb5893598..890d08bd8e1c7bdfa4a795fb43bb22f4fb5244a2 100644 --- a/arch/arm/mach-imx/imx8m/clock_imx8mm.c +++ b/arch/arm/mach-imx/imx8m/clock_imx8mm.c @@ -301,7 +301,11 @@ int intpll_configure(enum pll_clocks pll, ulong freq) return 0; } +#ifdef CONFIG_IMX8MP #define VIDEO_PLL_RATE 1039500000U +#else +#define VIDEO_PLL_RATE 594000000U +#endif void mxs_set_lcdclk_type(u32 base_addr, u32 freq, enum lcdifv3_type type) { diff --git a/board/seco/common/seco_fuse.h b/board/seco/common/seco_fuse.h index 50a33b7798d99a65fad63d3866db430f5d857921..e6a6c66a2d6e69e363d18f5ac01f200b2eba9a27 100644 --- a/board/seco/common/seco_fuse.h +++ b/board/seco/common/seco_fuse.h @@ -57,6 +57,12 @@ enum SECO_FUSE_MODULE_TYPE { }; int seco_fuse_GetModule(void); +// clang-format off +#define MODULE_IS_MYON_TYPE(m) (((m == SECO_FUSE_MODULE_MYON2) ||(m == SECO_FUSE_MODULE_MYON2NANO)) ? 1 : 0) +#define MODULE_IS_TRIZEPS8MINI_TYPE(m) (((m == SECO_FUSE_MODULE_TRIZEPS8MINI)||(m == SECO_FUSE_MODULE_TRIZEPS8NANO)) ? 1 : 0) +#define MODULE_IS_TRIZEPS8PLUS_TYPE(m) ( (m == SECO_FUSE_MODULE_TRIZEPS8PLUS) ? 1 : 0) +// clang-format on + enum SECO_FUSE_RAMWIDTH { SECO_FUSE_RAMWIDTH_UNKNOWN = -1, SECO_FUSE_RAMWIDTH_8BIT = 8, diff --git a/board/seco/imx8mm_myon2/Makefile b/board/seco/imx8mm_myon2/Makefile index f8d782b9641a62af2f07f7167268562a083745bd..af75b6c970ccad856a3ec28eeeaa5772d1ccadd3 100644 --- a/board/seco/imx8mm_myon2/Makefile +++ b/board/seco/imx8mm_myon2/Makefile @@ -5,6 +5,7 @@ # obj-y += imx8mm_myon2.o +obj-$(CONFIG_VIDEO_MXS) += display.o ifdef CONFIG_SPL_BUILD obj-y += spl.o diff --git a/board/seco/imx8mm_myon2/README b/board/seco/imx8mm_myon2/README index 8249a5432b94ab51fec9ccdefc3d4d7f9a32bd0f..374d61ab290ff793f0064bcb2c73c0ad55007d53 100644 --- a/board/seco/imx8mm_myon2/README +++ b/board/seco/imx8mm_myon2/README @@ -38,6 +38,28 @@ https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads II. Implementation notes ======================== +Video output and logo +--------------------- + +List of supported outputs: + * HDMI_1920x1080 + * RGB_ATM0700D6J + * LVDS_ATM0700L61 + * LVDS_SCF1001C44GGU05 + * PCONXS + * LVDS_AM19201080D1 + * RGB18_ATM0700D6J + * LVDS_G156HAN02.1 + * GUF_LVDS_SCF1001C44GGU05 + +The video output is controlled by the "panel" environment variable. +To select a specific output, set the value to the one of the supported outputs (see above). +If "panel" is not set, auto detection is performed. +To disable video output, set the "panel" to "none". + +The splash screen image (splash.bmp) is located on the first partition of the boot device, +and can be easily changed by replacing the file with a new one. + Universal Serial Bus -------------------- diff --git a/board/seco/imx8mm_myon2/display.c b/board/seco/imx8mm_myon2/display.c new file mode 100644 index 0000000000000000000000000000000000000000..c63bd7f50c079801749930a00000cc6dcae31067 --- /dev/null +++ b/board/seco/imx8mm_myon2/display.c @@ -0,0 +1,1144 @@ +/* + * Copyright 2025 SECO + */ + +/* + * Display detection routine for SECO Nother Europe hardware. + * Try to detect the display/baseboard based on the detection of + * the used touch or other board unique devices. +*/ + +#include <common.h> +#include <asm-generic/gpio.h> +#include <asm/io.h> +#include <asm/arch/sys_proto.h> +#include <asm/mach-imx/gpio.h> +#include <asm/mach-imx/video.h> +#include <linux/arm-smccc.h> +#include <linux/delay.h> +#include <dm/uclass.h> +#include <env.h> +#include <i2c.h> +#include <imx_sip.h> +#include <imx_mipi_dsi_bridge.h> +#include <sec_mipi_dsim.h> +#include "../common/seco_fuse.h" + +// clang-format off +#define FPGA_ID 0x41 +#define LVDS_ID 0x2C + +#define ADV7535_MAIN 0x39 +#define ADV7535_DSI_CEC 0x3C +// clang-format on + +static int pConXS_detected = 0; + +static const struct sec_mipi_dsim_plat_data imx8mm_mipi_dsim_plat_data = { + .version = 0x1060200, + .max_data_lanes = 4, + .max_data_rate = 1500000000ULL, + .reg_base = MIPI_DSI_BASE_ADDR, + .gpr_base = CSI_BASE_ADDR + 0x8000, +}; + +static void fpga_init(uint8_t val) +{ + struct udevice *bus, *main_dev; + int i2c_bus = 2; + int ret; + + ret = uclass_get_device_by_seq(UCLASS_I2C, i2c_bus, &bus); + if (ret) { + printf("%s: No bus %d\n", __func__, i2c_bus); + return; + } + + ret = dm_i2c_probe(bus, FPGA_ID, 0, &main_dev); + if (ret) { + printf("%s: Can't find device id=0x%x, on bus %d\n", __func__, FPGA_ID, i2c_bus); + return; + } + + ret = dm_i2c_write(main_dev, 0x10, &val, 1); +} + +static void lvds_init(struct display_info_t const *dev) +{ + struct udevice *bus, *main_dev; + int i2c_bus = 2; + int ret; + uint8_t i = 0; + + uint8_t addr[] = { 0x09, 0x0A, 0x0A, 0x0B, 0x0D, 0x10, 0x11, 0x12, 0x13, 0x18, 0x19, 0x1A, + 0x1B, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, + 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x0D, 0xFF }; + uint8_t valb[] = { 0x01, 0x00, 0x05, 0x10, 0x00, 0x26, 0x00, 0x27, 0x00, 0x78, 0x00, 0x03, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x21, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF }; + + int module; + module = seco_fuse_GetModule(); + + if (strncmp(dev->mode.name, "LVDS_SCF1001C44GGU05", strlen(dev->mode.name)) == 0) { + //DataImage + valb[7] = 0x2A; + valb[14] = 0x05; + valb[18] = 0x00; + valb[25] = 0x3C; + valb[29] = 0x03; + valb[33] = 0x30; + } else if (strncmp(dev->mode.name, "GUF_LVDS_SCF1001C44GGU05", strlen(dev->mode.name)) == + 0) { + //SIS 9255 + valb[7] = 0x2A; + valb[14] = 0x05; + valb[18] = 0x00; + valb[25] = 0x3C; + valb[29] = 0x03; + valb[33] = 0x30; + } else if (strncmp(dev->mode.name, "PCONXS", strlen(dev->mode.name)) == 0) { + //printf("%s: -> PCONXS\n", __func__); + valb[2] = 0x03; + valb[3] = 0x14; + valb[5] = 0x20; + valb[7] = 0x3D; + valb[17] = 0x58; + valb[18] = 0x02; + valb[21] = 0x20; + valb[25] = 0x04; + valb[29] = 0x01; + valb[33] = 0xA0; + valb[37] = 0xA0; + valb[39] = 0x0C; + } else if (strncmp(dev->mode.name, "LVDS_ATM0700D6J", strlen(dev->mode.name)) == 0) { + //printf("%s: -> IPANM7\n", __func__); + valb[2] = 0x03; + valb[3] = 0x14; + valb[5] = 0x20; + valb[7] = 0x3D; + valb[17] = 0x58; + valb[18] = 0x02; + valb[21] = 0x20; + valb[25] = 0x04; + valb[29] = 0x01; + valb[33] = 0xA0; + valb[37] = 0xA0; + valb[39] = 0x0C; + } else if (strncmp(dev->mode.name, "LVDS_ATM0700L61", strlen(dev->mode.name)) == 0) { + //printf("%s: -> LVDS_ATM0700L61\n", __func__); + valb[2] = 0x03; + valb[3] = 0x14; + valb[5] = 0x20; + valb[7] = 0x3D; + valb[17] = 0x58; + valb[18] = 0x02; + valb[21] = 0x20; + valb[25] = 0x04; + valb[29] = 0x01; + valb[33] = 0xA0; + valb[37] = 0xA0; + valb[39] = 0x0C; + + gpio_request(IMX_GPIO_NR(3, 22), "BACKLIGHT EN"); + if (module == SECO_FUSE_MODULE_TRIZEPS8MINI) + gpio_direction_output(IMX_GPIO_NR(3, 22), 0); + else + gpio_direction_output(IMX_GPIO_NR(3, 22), 1); + } else if (strncmp(dev->mode.name, "LVDS_AM19201080D1", strlen(dev->mode.name)) == 0) { + //printf("%s: -> AM19201080D1\n", __func__); + valb[2] = 0x83; + valb[3] = 0x28; + valb[7] = 0x48; + valb[9] = 0x6C; + valb[13] = 0x80; + valb[14] = 0x07; + valb[17] = 0x38; + valb[18] = 0x04; + valb[25] = 0x23; + valb[30] = 0x0A; + valb[33] = 0x23; + valb[37] = 0x46; + valb[39] = 0x0A; + } else if (strncmp(dev->mode.name, "LVDS_G156HAN02.1", strlen(dev->mode.name)) == 0) { + printf("%s: -> LVDS_G156HAN02.1\n", __func__); + valb[2] = 0x83; + valb[3] = 0x28; + valb[7] = 0x48; + valb[9] = 0x6C; + valb[13] = 0x80; + valb[14] = 0x07; + valb[17] = 0x38; + valb[18] = 0x04; + valb[25] = 0x23; + valb[30] = 0x0A; + valb[33] = 0x23; + valb[37] = 0x46; + valb[39] = 0x0A; + } + + ret = uclass_get_device_by_seq(UCLASS_I2C, i2c_bus, &bus); + if (ret) { + printf("%s: No bus %d\n", __func__, i2c_bus); + return; + } + + ret = dm_i2c_probe(bus, LVDS_ID, 0, &main_dev); + if (ret) { + printf("%s: Can't find device id=0x%x, on bus %d\n", __func__, LVDS_ID, i2c_bus); + return; + } + + do { + //printf("[0x%x] = 0x%x\n", addr[i], valb[i]); + dm_i2c_write(main_dev, addr[i], &valb[i], 1); + i++; + } while (addr[i] != 0xFF); + + return; +} + +static int adv7535_i2c_reg_write(struct udevice *dev, uint addr, uint mask, uint data) +{ + uint8_t valb; + int err; + + if (mask != 0xff) { + err = dm_i2c_read(dev, addr, &valb, 1); + if (err) + return err; + + valb &= ~mask; + valb |= data; + } else { + valb = data; + } + + err = dm_i2c_write(dev, addr, &valb, 1); + return err; +} + +static int adv7535_i2c_reg_read(struct udevice *dev, uint8_t addr, uint8_t *data) +{ + uint8_t valb; + int err; + + err = dm_i2c_read(dev, addr, &valb, 1); + if (err) + return err; + + *data = (int)valb; + return 0; +} + +typedef struct stADV753XReg { + uint8_t addr; + uint8_t reg; + uint8_t val; +} ADV753XREG, *PADV753XREG; + +static ADV753XREG advinit[] = { + { 0x39, 0x41, 0x10 }, { 0xff, 0x05, 0x00 }, { 0x39, 0xd6, 0x48 }, { 0xff, 0x05, 0x00 }, + { 0x3c, 0x03, 0x89 }, { 0x39, 0x16, 0x20 }, { 0x39, 0x9a, 0xe0 }, { 0x39, 0xba, 0x70 }, + { 0x39, 0xde, 0x82 }, { 0x39, 0xe4, 0xc0 }, { 0x39, 0xe5, 0x80 }, { 0x3c, 0x15, 0xd0 }, + { 0x3c, 0x17, 0xd0 }, { 0x3c, 0x24, 0x20 }, { 0x3c, 0x57, 0x11 }, { 0x39, 0xaf, 0x06 }, + { 0x39, 0x40, 0x80 }, { 0x39, 0x4c, 0x04 }, { 0x39, 0x49, 0x02 }, { 0x39, 0x0d, 0x40 }, + { 0x3c, 0x1c, 0x40 }, { 0x39, 0x17, 0x02 }, { 0x3c, 0x16, 0x00 }, { 0x3c, 0x27, 0xCB }, + { 0x3c, 0x28, 0x89 }, { 0x3c, 0x29, 0x80 }, { 0x3c, 0x2a, 0x02 }, { 0x3c, 0x2b, 0xc0 }, + { 0x3c, 0x2c, 0x05 }, { 0x3c, 0x2d, 0x80 }, { 0x3c, 0x2e, 0x09 }, { 0x3c, 0x2f, 0x40 }, + { 0x3c, 0x30, 0x46 }, { 0x3c, 0x31, 0x50 }, { 0x3c, 0x32, 0x00 }, { 0x3c, 0x33, 0x50 }, + { 0x3c, 0x34, 0x00 }, { 0x3c, 0x35, 0x40 }, { 0x3c, 0x36, 0x02 }, { 0x3c, 0x37, 0x40 }, + { 0x3c, 0x27, 0xCB }, { 0x3c, 0x27, 0x8B }, { 0xff, 0x05, 0x00 }, { 0x3c, 0x27, 0xCB }, + { 0xff, 0x64, 0x00 }, { 0x3c, 0x55, 0x00 }, { 0x3c, 0x03, 0x09 }, { 0xff, 0x05, 0x00 }, + { 0x3c, 0x03, 0x89 }, { 0x39, 0x12, 0x20 }, { 0x39, 0x13, 0x00 }, { 0x39, 0x14, 0x02 }, + { 0x39, 0x15, 0x20 }, { 0x39, 0x0a, 0x41 }, { 0x39, 0x0c, 0xbc }, { 0x39, 0x0d, 0x18 }, + { 0x39, 0x03, 0x00 }, { 0x39, 0x02, 0x18 }, { 0x39, 0x01, 0x00 }, { 0x39, 0x09, 0x70 }, + { 0x39, 0x08, 0x62 }, { 0x39, 0x07, 0x00 }, { 0x39, 0x73, 0x01 }, { 0x39, 0x76, 0x00 }, + { 0x3c, 0x05, 0xC8 }, { 0x00, 0x00, 0x00 } +}; + +static int adv7535_init(struct display_info_t const *dev) +{ + struct udevice *bus, *main_dev, *cec_dev; + int i2c_bus = 1; + int ret; + uint8_t val; + PADV753XREG padvseq = &advinit[0]; + + for (i2c_bus = 1; i2c_bus <= 2; i2c_bus++) { + ret = uclass_get_device_by_seq(UCLASS_I2C, i2c_bus, &bus); + if (ret) { + //printf("%s: No bus %d\n", __func__, i2c_bus); + continue; + } + + ret = dm_i2c_probe(bus, ADV7535_MAIN, 0, &main_dev); + if (ret) { + //printf("%s: Can't find device id=0x%x, on bus %d\n", + // __func__, ADV7535_MAIN, i2c_bus); + continue; + } + + ret = dm_i2c_probe(bus, ADV7535_DSI_CEC, 0, &cec_dev); + if (ret) { + //printf("%s: Can't find device id=0x%x, on bus %d\n", + // __func__, ADV7535_MAIN, i2c_bus); + continue; + } + + if (!ret) { + break; + } + } + if (ret) { + //printf("%s: Can't find ADV7535\n", __func__); + return 0; + } + + //printf("%s: ADV7535 found\n", __func__); + adv7535_i2c_reg_read(main_dev, 0x00, &val); + if (val != 0x14) { + printf("Chip revision: 0x%x (expected: 0x14)\n", val); + return 0; + } + adv7535_i2c_reg_read(cec_dev, 0x00, &val); + if (val != 0x75) { + printf("Chip ID MSB: 0x%x (expected: 0x75)\n", val); + return 0; + } + adv7535_i2c_reg_read(cec_dev, 0x01, &val); + if (val != 0x33) { + printf("Chip ID LSB: 0x%x (expected: 0x33)\n", val); + return 0; + } + + while (padvseq->addr != 0) { + if (padvseq->addr == 0xFF) { + mdelay(padvseq->reg); + } else if (padvseq->addr == ADV7535_MAIN) { + adv7535_i2c_reg_write(main_dev, padvseq->reg, 0xff, padvseq->val); + } else if (padvseq->addr == ADV7535_DSI_CEC) { + adv7535_i2c_reg_write(cec_dev, padvseq->reg, 0xff, padvseq->val); + } else { + printf("Unknown ADV7535 address\n"); + } + padvseq++; + } + + return 1; +} + +// clang-format off +#define DISPLAY_MIX_SFT_RSTN_CSR 0x00 +#define DISPLAY_MIX_CLK_EN_CSR 0x04 + +/* 'DISP_MIX_SFT_RSTN_CSR' bit fields */ +#define BUS_RSTN_BLK_SYNC_SFT_EN BIT(6) + +/* 'DISP_MIX_CLK_EN_CSR' bit fields */ +#define LCDIF_PIXEL_CLK_SFT_EN BIT(7) +#define LCDIF_APB_CLK_SFT_EN BIT(6) +// clang-format on + +void disp_mix_bus_rstn_reset(ulong gpr_base, bool reset) +{ + if (!reset) + /* release reset */ + setbits_le32(gpr_base + DISPLAY_MIX_SFT_RSTN_CSR, BUS_RSTN_BLK_SYNC_SFT_EN); + else + /* hold reset */ + clrbits_le32(gpr_base + DISPLAY_MIX_SFT_RSTN_CSR, BUS_RSTN_BLK_SYNC_SFT_EN); +} + +void disp_mix_lcdif_clks_enable(ulong gpr_base, bool enable) +{ + if (enable) + /* enable lcdif clks */ + setbits_le32(gpr_base + DISPLAY_MIX_CLK_EN_CSR, + LCDIF_PIXEL_CLK_SFT_EN | LCDIF_APB_CLK_SFT_EN); + else + /* disable lcdif clks */ + clrbits_le32(gpr_base + DISPLAY_MIX_CLK_EN_CSR, + LCDIF_PIXEL_CLK_SFT_EN | LCDIF_APB_CLK_SFT_EN); +} + +struct mipi_dsi_client_dev seco_fuse_panel_drv = { + .channel = 0, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE, + .name = "default", +}; + +struct mipi_dsi_client_dev adv7535_dev = { + .channel = 0, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE, + .name = "ADV7535", +}; + +// clang-format off +#define DISPMIX 9 +#define MIPI 10 +// clang-format on + +void do_enable_mipi2rgb(struct display_info_t const *dev) +{ + struct arm_smccc_res res; + + printf("%s: Enable %s \n", __func__, dev->mode.name); + + gpio_request(IMX_GPIO_NR(1, 5), "DISPLAY_EN"); + gpio_direction_output(IMX_GPIO_NR(1, 5), 1); + + gpio_request(IMX_GPIO_NR(1, 1), "BACKLIGHT_PWM"); + + gpio_request(IMX_GPIO_NR(3, 22), "BACKLIGHT_EN"); + gpio_direction_output(IMX_GPIO_NR(3, 22), 1); + + if (!(strncmp(dev->mode.name, "RGB18_ATM0700D6J", strlen(dev->mode.name)))) { + gpio_direction_output(IMX_GPIO_NR(1, 1), 0); + fpga_init(0x89); + } else { + gpio_direction_output(IMX_GPIO_NR(1, 1), 1); + fpga_init(0x8D); + } + + /* enable the dispmix & mipi phy power domain */ + arm_smccc_smc(IMX_SIP_GPC, IMX_SIP_GPC_PM_DOMAIN, + DISPMIX, true, 0, 0, 0, 0, &res); + arm_smccc_smc(IMX_SIP_GPC, IMX_SIP_GPC_PM_DOMAIN, + MIPI, true, 0, 0, 0, 0, &res); + + /* Put lcdif out of reset */ + disp_mix_bus_rstn_reset(imx8mm_mipi_dsim_plat_data.gpr_base, false); + disp_mix_lcdif_clks_enable(imx8mm_mipi_dsim_plat_data.gpr_base, true); + + /* Setup mipi dsim */ + sec_mipi_dsim_setup(&imx8mm_mipi_dsim_plat_data); + + imx_mipi_dsi_bridge_attach(&seco_fuse_panel_drv); +} + +void do_display_default(struct display_info_t const *dev) +{ + int module; + module = seco_fuse_GetModule(); + + printf("%s: Enable %s \n", __func__, dev->mode.name); + + switch (module) { + case SECO_FUSE_MODULE_SBCSOM8MINI: + case SECO_FUSE_MODULE_SBCSOM8NANO: + gpio_request(IMX_GPIO_NR(3, 22), "BACKLIGHT_ENABLE"); + gpio_request(IMX_GPIO_NR(1, 1), "BACKLIGHT_PWM"); + gpio_request(IMX_GPIO_NR(1, 5), "DISPLAY_ENABLE"); + + gpio_direction_output(IMX_GPIO_NR(3, 22), 0); + gpio_direction_output(IMX_GPIO_NR(1, 1), 0); + gpio_direction_output(IMX_GPIO_NR(1, 5), 0); + break; + case SECO_FUSE_MODULE_MYON2: + gpio_request(IMX_GPIO_NR(1, 5), "DISPLAY_EN"); + gpio_request(IMX_GPIO_NR(1, 1), "BACKLIGHT_PWM"); + gpio_request(IMX_GPIO_NR(3, 22), "BACKLIGHT_EN"); + gpio_request(IMX_GPIO_NR(1, 4), "LVDS EN"); + + gpio_direction_output(IMX_GPIO_NR(1, 4), 0); + gpio_direction_output(IMX_GPIO_NR(1, 5), 0); + gpio_direction_output(IMX_GPIO_NR(1, 1), 0); + gpio_direction_output(IMX_GPIO_NR(3, 22), 0); + break; + case SECO_FUSE_MODULE_TRIZEPS8MINI: + gpio_request(IMX_GPIO_NR(1, 5), "DISPLAY_EN"); + gpio_request(IMX_GPIO_NR(1, 1), "BACKLIGHT_PWM"); + gpio_request(IMX_GPIO_NR(3, 22), "BACKLIGHT_EN"); + gpio_request(IMX_GPIO_NR(1, 4), "LVDS EN"); + + gpio_direction_output(IMX_GPIO_NR(1, 4), 0); + gpio_direction_output(IMX_GPIO_NR(1, 5), 0); + gpio_direction_output(IMX_GPIO_NR(1, 1), 0); + gpio_direction_output(IMX_GPIO_NR(3, 22), 0); + break; + default: + break; + } +} + +void do_enable_mipi2lvds(struct display_info_t const *dev) +{ + struct arm_smccc_res res; + + //printf("%s: Enable %s \n", __func__, dev->mode.name); + + gpio_request(IMX_GPIO_NR(1, 5), "DISPLAY_EN"); + gpio_direction_output(IMX_GPIO_NR(1, 5), 1); + + gpio_request(IMX_GPIO_NR(1, 4), "LVDS EN"); + gpio_direction_output(IMX_GPIO_NR(1, 4), 1); + + gpio_request(IMX_GPIO_NR(1, 1), "BACKLIGHT PWM"); + gpio_direction_output(IMX_GPIO_NR(1, 1), 1); + //gpio_direction_output(IMX_GPIO_NR(1, 1), 0); + + //printf("do_enable_mipi2lvds pConXS_detected=%d\n", pConXS_detected); + if (pConXS_detected) { + // ToDo find out wether code runs on pConXS.... + gpio_request(IMX_GPIO_NR(3, 22), + "BACKLIGHT EN"); // Needed for pConXS + gpio_direction_output(IMX_GPIO_NR(3, 22), + 1); // Crashes iPANT7 FF + } + + if (seco_fuse_GetModule() == SECO_FUSE_MODULE_TRIZEPS8MINI) { + if (seco_fuse_GetPCBrevision() <= SECO_FUSE_PCBREV_V1R3) { + lvds_init(dev); + } + } + + /* enable the dispmix & mipi phy power domain */ + arm_smccc_smc(IMX_SIP_GPC, IMX_SIP_GPC_PM_DOMAIN, + DISPMIX, true, 0, 0, 0, 0, &res); + arm_smccc_smc(IMX_SIP_GPC, IMX_SIP_GPC_PM_DOMAIN, + MIPI, true, 0, 0, 0, 0, &res); + + /* Put lcdif out of reset */ + disp_mix_bus_rstn_reset(imx8mm_mipi_dsim_plat_data.gpr_base, false); + disp_mix_lcdif_clks_enable(imx8mm_mipi_dsim_plat_data.gpr_base, true); + + /* Setup mipi dsim */ + sec_mipi_dsim_setup(&imx8mm_mipi_dsim_plat_data); + + imx_mipi_dsi_bridge_attach(&seco_fuse_panel_drv); +} + +void do_enable_mipi2hdmi(struct display_info_t const *dev) +{ + struct arm_smccc_res res; + + /* ADV7353 initialization */ + adv7535_init(dev); + + /* enable the dispmix & mipi phy power domain */ + arm_smccc_smc(IMX_SIP_GPC, IMX_SIP_GPC_PM_DOMAIN, + DISPMIX, true, 0, 0, 0, 0, &res); + arm_smccc_smc(IMX_SIP_GPC, IMX_SIP_GPC_PM_DOMAIN, + MIPI, true, 0, 0, 0, 0, &res); + + /* Put lcdif out of reset */ + disp_mix_bus_rstn_reset(imx8mm_mipi_dsim_plat_data.gpr_base, false); + disp_mix_lcdif_clks_enable(imx8mm_mipi_dsim_plat_data.gpr_base, true); + + /* Setup mipi dsim */ + sec_mipi_dsim_setup(&imx8mm_mipi_dsim_plat_data); + imx_mipi_dsi_bridge_attach(&adv7535_dev); /* attach adv7535 device */ +} + +// clang-format off +#define FOCALTECH_ID 0x38 +#define DATAIMAGE_ID 0x5C +#define GOODIX_ID 0x5D +#define ILITEK_ID 0x41 +#define EGALAX_ID 0x2A // eGalax Touch / G156HAN02.1 Display (15.6" GuF) +#define SIS9255_ID 0x5C // SIS 9255 Touch / SFC1001 10.1" Display + +#define FT5X06_OP_REG_CHIPID 0xA3 /* vendor’s chip id */ +#define FT5X06_OP_REG_FIRMID 0xA6 /* the firmware id of the application */ +#define FT5X06_OP_REG_FT5201ID 0xA8 +// clang-format on + +static int detect_ipant7(struct display_info_t const *dev) +{ + struct udevice *bus, *main_dev; + int i2c_bus = 1; + int ret; + uint8_t touch_id = 0, chip_id = 0, firm_id = 0; + + printf("%s: %s \n", __func__, dev->mode.name); + + gpio_request(IMX_GPIO_NR(3, 23), "TOUCH EN"); + gpio_direction_output(IMX_GPIO_NR(3, 23), 1); + + mdelay(100); + + ret = uclass_get_device_by_seq(UCLASS_I2C, 1, &bus); + if (ret) { + printf("%s: No bus %d\n", __func__, 1); + return 0; + } + + ret = dm_i2c_probe(bus, 0x4E, 0, &main_dev); + if (!ret) { + printf("%s: Can't find device id=0x%x, on bus %d\n", __func__, 0x4E, i2c_bus); + return 0; + } + + ret = uclass_get_device_by_seq(UCLASS_I2C, i2c_bus, &bus); + if (ret) { + //printf("%s: No bus %d\n", __func__, i2c_bus); + return 0; + } + + ret = dm_i2c_probe(bus, FOCALTECH_ID, 0, &main_dev); + if (ret) { + //printf("%s: Can't find device id=0x%x, on bus %d\n", + // __func__, FOCALTECH_ID, i2c_bus); + return 0; + } + + //Check Version + ret = dm_i2c_read(main_dev, FT5X06_OP_REG_CHIPID, &chip_id, 1); + ret = dm_i2c_read(main_dev, FT5X06_OP_REG_FIRMID, &firm_id, 1); + ret = dm_i2c_read(main_dev, FT5X06_OP_REG_FT5201ID, &touch_id, 1); + + printf("%s: ChipID 0x%x, FirmwareID 0x%x, TouchID 0x%x\n", __func__, chip_id, firm_id, + touch_id); + + if ((chip_id == 0x14 && firm_id == 0x06 && touch_id == 0x51) && + (!(strncmp(dev->mode.name, "RGB_ATM0700D6J", strlen(dev->mode.name))))) { + printf("%s: Version 1 AZ Coverlens Display \n", __func__); + seco_fuse_panel_drv.lanes = 2; + seco_fuse_panel_drv.name = "RGB_ATM0700D6J"; + return 1; + } else if ((chip_id == 0x0A && firm_id == 0x3 && touch_id == 0x79) && + (!(strncmp(dev->mode.name, "RGB_ATM0700D6J", strlen(dev->mode.name))))) { + printf("%s: Version 1 AZ Display \n", __func__); + seco_fuse_panel_drv.lanes = 2; + seco_fuse_panel_drv.name = "RGB_ATM0700D6J"; + return 1; + } else if (((chip_id == 0x0A && firm_id == 0x08 && touch_id == 0x79) && + (!(strncmp(dev->mode.name, "RGB_ATM0700D6J", strlen(dev->mode.name)))))) { + printf("%s: Version 1 HT Display \n", __func__); + //TODO + return 0; + } else if (chip_id == 0x54 && (firm_id == 0x01 || firm_id == 0x02 || firm_id == 0x03) && + touch_id == 0x79 && + (!(strncmp(dev->mode.name, "LVDS_ATM0700L61", strlen(dev->mode.name))))) { + printf("%s: Version 2 \n", __func__); + seco_fuse_panel_drv.lanes = 4; + seco_fuse_panel_drv.name = "LVDS_ATM0700L61"; + return 1; + } else { + printf("%s: unknown touch ChipID 0x%x, FirmwareID 0x%x, TouchID 0x%x \n", __func__, + chip_id, firm_id, touch_id); + return 0; + } + + return 0; +} + +static int detect_ipanm7(struct display_info_t const *dev) +{ + struct udevice *bus, *main_dev; + int i2c_bus = 2; + int ret; + + //printf("%s: %s \n", __func__, dev->mode.name); + //Check LVDS + + gpio_request(IMX_GPIO_NR(1, 4), "LVDS EN"); + gpio_direction_output(IMX_GPIO_NR(1, 4), 1); +#if 1 + /* */ + mdelay(60); // when adding this line: u-boot stops. See below: + /* + CPU: Commercial temperature grade (0C to 95C)Error in parsing TMU FDT -22 + .... + Some drivers failed to bind + initcall sequence 00000000fffb21c8 failed at call 000000004021c21c (err=-11) + ### ERROR ### Please RESET the board ### + */ + // CONFIG_OF_EMBED helps. Also #define DEBUG 1 in drivers/core/root.c (dm_init() problem) + /* */ +#endif + + ret = uclass_get_device_by_seq(UCLASS_I2C, i2c_bus, &bus); + if (ret) { + // printf("%s: No bus %d\n", __func__, i2c_bus); + return 0; + } + + ret = dm_i2c_probe(bus, LVDS_ID, 0, &main_dev); + if (ret) { + //printf("%s: Can't find device id=0x%x, on bus %d\n", + // __func__, LVDS_ID, i2c_bus); + return 0; + } + + gpio_request(IMX_GPIO_NR(3, 23), "TOUCH EN"); + gpio_direction_output(IMX_GPIO_NR(3, 23), 1); + + mdelay(10); + i2c_bus = 1; + ret = uclass_get_device_by_seq(UCLASS_I2C, i2c_bus, &bus); + if (ret) { + //printf("%s: No bus %d\n", __func__, i2c_bus); + return 0; + } + + ret = dm_i2c_probe(bus, FOCALTECH_ID, 0, &main_dev); + if (ret) { + //printf("%s: Can't find device id=0x%x, on bus %d\n", + // __func__, FOCALTECH_ID, i2c_bus); + return 0; + } + + seco_fuse_panel_drv.lanes = 4; + seco_fuse_panel_drv.name = "LVDS_ATM0700D6J"; + + return 1; +} + +static int detect_ipant10(struct display_info_t const *dev) +{ + struct udevice *bus, *main_dev; + int i2c_bus = 1; + int ret; + + //printf("%s: %s \n", __func__, dev->mode.name); + + gpio_request(IMX_GPIO_NR(3, 23), "TOUCH EN"); + gpio_direction_output(IMX_GPIO_NR(3, 23), 0); + + mdelay(10); + + ret = uclass_get_device_by_seq(UCLASS_I2C, i2c_bus, &bus); + if (ret) { + //printf("%s: No bus %d\n", __func__, i2c_bus); + return 0; + } + + ret = dm_i2c_probe(bus, DATAIMAGE_ID, 0, &main_dev); + if (ret) { + //printf("%s: Can't find device id=0x%x, on bus %d\n", + // __func__, DATAIMAGE_ID, i2c_bus); + return 0; + } + + seco_fuse_panel_drv.lanes = 4; + seco_fuse_panel_drv.name = "LVDS_SCF1001C44GGU05"; + + return 1; +} + +static int detect_pconxs(struct display_info_t const *dev, int touch) +{ + struct udevice *bus, *main_dev; + int i2c_bus = 2; + int ret; + + gpio_request(IMX_GPIO_NR(3, 23), "TOUCH_EN"); + gpio_direction_output(IMX_GPIO_NR(3, 23), 1); + + gpio_request(IMX_GPIO_NR(1, 5), "DISPLAY_EN"); + gpio_direction_output(IMX_GPIO_NR(1, 5), 1); + + gpio_request(IMX_GPIO_NR(4, 14), "BL_EN"); + gpio_direction_output(IMX_GPIO_NR(4, 14), 1); + + gpio_request(IMX_GPIO_NR(1, 1), "BACKLIGHT_PWM"); + //gpio_direction_output(IMX_GPIO_NR(1, 1), 1); + gpio_direction_output(IMX_GPIO_NR(1, 1), 0); + + mdelay(10); + + //Check LM75 + i2c_bus = 0; + ret = uclass_get_device_by_seq(UCLASS_I2C, i2c_bus, &bus); + if (ret) { + printf("%s: No bus %d\n", __func__, 1); + return 0; + } + + ret = dm_i2c_probe(bus, 0x4E, 0, &main_dev); + if (ret) { + printf("%s: Can't find device id=0x%x, on bus %d\n", __func__, 0x4E, i2c_bus); + return 0; + } + + for (i2c_bus = 0; i2c_bus <= 1; i2c_bus++) { + printf("%s: bus %d\n", __func__, i2c_bus); + ret = uclass_get_device_by_seq(UCLASS_I2C, i2c_bus, &bus); + if (ret) { + printf("%s: No bus %d\n", __func__, i2c_bus); + //return 0; + } + + ret = dm_i2c_probe(bus, touch, 0, &main_dev); + if (ret) { + printf("%s: Can't find device id=0x%x, on bus %d\n", __func__, touch, + i2c_bus); + //return 0; + } + if (!ret) + break; + } + + if (ret) { + return 0; + } + + seco_fuse_panel_drv.lanes = 4; + seco_fuse_panel_drv.name = dev->mode.name; + pConXS_detected = 1; + return 1; +} + +static int detect_display(struct display_info_t const *dev) +{ + struct udevice *bus; + int ret; + int i2c_bus = 1; + char *s; + int module; + + s = env_get("display"); + if (s) { + //printk("%s: Environment display=%s \n", __func__, s); + if (!(strncmp(s, dev->mode.name, strlen(s)))) { + //printf("%s: Choose %s \n", __func__, dev->mode.name); + + ret = uclass_get_device_by_seq(UCLASS_I2C, i2c_bus, &bus); + if (ret) { + //printf("%s: No bus %d\n", __func__, i2c_bus); + return 0; + } + + if (!(strncmp(s, "IPANT10", strlen(s)))) { + gpio_request(IMX_GPIO_NR(3, 23), "TOUCH EN"); + gpio_direction_output(IMX_GPIO_NR(3, 23), 0); + + seco_fuse_panel_drv.lanes = 4; + seco_fuse_panel_drv.name = "LVDS_SCF1001C44GGU05"; + } else if (!(strncmp(s, "IPANT7", strlen(s)))) { + gpio_request(IMX_GPIO_NR(3, 23), "TOUCH EN"); + gpio_direction_output(IMX_GPIO_NR(3, 23), 1); + + seco_fuse_panel_drv.lanes = 2; + seco_fuse_panel_drv.name = "RGB_ATM0700D6J"; + } else if (!(strncmp(s, "PCONXS", strlen(s)))) { + gpio_request(IMX_GPIO_NR(3, 23), "TOUCH EN"); + gpio_direction_output(IMX_GPIO_NR(3, 23), 0); + + gpio_request(IMX_GPIO_NR(4, 14), "BL_EN"); + gpio_direction_output(IMX_GPIO_NR(4, 14), 1); + + seco_fuse_panel_drv.lanes = 4; + seco_fuse_panel_drv.name = "PCONXS"; + } else if (!(strncmp(s, "IPANM7", strlen(s)))) { + gpio_request(IMX_GPIO_NR(3, 23), "TOUCH EN"); + gpio_direction_output(IMX_GPIO_NR(3, 23), 1); + + seco_fuse_panel_drv.lanes = 4; + seco_fuse_panel_drv.name = "LVDS_ATM0700D6J"; + } else if (!(strncmp(s, "IPANT7_V2", strlen(s)))) { + gpio_request(IMX_GPIO_NR(3, 23), "TOUCH EN"); + gpio_direction_output(IMX_GPIO_NR(3, 23), 1); + + seco_fuse_panel_drv.lanes = 4; + seco_fuse_panel_drv.name = "LVDS_ATM0700L61"; + } else if (!(strncmp(s, "LVDS_AM19201080D1", strlen(s)))) { + gpio_request(IMX_GPIO_NR(4, 14), "BL_EN"); + gpio_direction_output(IMX_GPIO_NR(4, 14), 1); + + gpio_request(IMX_GPIO_NR(3, 23), "TOUCH EN"); + gpio_direction_output(IMX_GPIO_NR(3, 23), 1); + + seco_fuse_panel_drv.lanes = 4; + seco_fuse_panel_drv.name = "LVDS_AM19201080D1"; + } else if (!(strncmp(s, "LVDS_G156HAN02.1", strlen(s)))) { + gpio_request(IMX_GPIO_NR(4, 14), "BL_EN"); + gpio_direction_output(IMX_GPIO_NR(4, 14), 1); + + gpio_request(IMX_GPIO_NR(3, 23), "TOUCH EN"); + gpio_direction_output(IMX_GPIO_NR(3, 23), 1); + + gpio_request(IMX_GPIO_NR(3, 7), "PCIE_EN"); + gpio_direction_output(IMX_GPIO_NR(3, 7), 1); + seco_fuse_panel_drv.lanes = 4; + seco_fuse_panel_drv.name = "LVDS_G156HAN02.1"; + } + + return 1; + } + if (strncmp(s, "auto", strlen(s))) { + return 0; + } + } + printk("%s: Try autodetect... %s \n", __func__, dev->mode.name); + module = seco_fuse_GetModule(); + if (MODULE_IS_TRIZEPS8MINI_TYPE(module)) { + if ((!(strncmp(dev->mode.name, "RGB_ATM0700D6J", strlen(dev->mode.name))))) { + return detect_ipant7(dev); + } else if ((!(strncmp(dev->mode.name, "LVDS_ATM0700L61", strlen(dev->mode.name))))) { + return detect_ipant7(dev); + } else if (!(strncmp(dev->mode.name, "LVDS_SCF1001C44GGU05", + strlen(dev->mode.name)))) { + return detect_ipant10(dev); + } else if (!(strncmp(dev->mode.name, "PCONXS", strlen(dev->mode.name)))) { + if (detect_pconxs(dev, FOCALTECH_ID)) { + return 1; + } else + return 0; + } else if (!(strncmp(dev->mode.name, "LVDS_AM19201080D1", + strlen(dev->mode.name)))) { + if (detect_pconxs(dev, ILITEK_ID)) { + gpio_request(IMX_GPIO_NR(4, 14), "BL_EN"); + gpio_direction_output(IMX_GPIO_NR(4, 14), 1); + + return 1; + } else + return 0; + } else if ((!(strncmp(dev->mode.name, "RGB18_ATM0700D6J", + strlen(dev->mode.name))))) { + if (detect_pconxs(dev, FOCALTECH_ID)) { + seco_fuse_panel_drv.lanes = 2; + seco_fuse_panel_drv.name = "RGB18_ATM0700D6J"; + return 1; + } else + return 0; + } else if ((!(strncmp(dev->mode.name, "LVDS_G156HAN02.1", + strlen(dev->mode.name))))) { + if (detect_pconxs(dev, EGALAX_ID)) { + gpio_request(IMX_GPIO_NR(3, 7), "PCIE_EN"); + gpio_direction_output(IMX_GPIO_NR(3, 7), 1); + + seco_fuse_panel_drv.lanes = 4; + seco_fuse_panel_drv.name = "LVDS_G156HAN02.1"; + return 1; + } else + return 0; + } else if ((!(strncmp(dev->mode.name, "GUF_LVDS_SCF1001C44GGU05", + strlen(dev->mode.name))))) { + if (detect_pconxs(dev, SIS9255_ID)) { + gpio_request(IMX_GPIO_NR(3, 7), "PCIE_EN"); + gpio_direction_output(IMX_GPIO_NR(3, 7), 1); + + seco_fuse_panel_drv.lanes = 4; + seco_fuse_panel_drv.name = "GUF_LVDS_SCF1001C44GGU05"; + return 1; + } else + return 0; + } + } + if ((!(strncmp(dev->mode.name, "LVDS_ATM0700D6J", strlen(dev->mode.name)))) && + MODULE_IS_MYON_TYPE(module)) { + return detect_ipanm7(dev); + } else if (!(strncmp(dev->mode.name, "HDMI_1920x1080", strlen(dev->mode.name)))) { + return adv7535_init(dev); + } + + return 0; +} + +// clang-format off +struct display_info_t const displays[] = { + { .bus = LCDIF_BASE_ADDR, + .addr = 0, + .pixfmt = 24, + .detect = detect_display, + .enable = do_enable_mipi2lvds, + .mode = { .name = "G104XVN01", + .refresh = 60, + .xres = 1024, + .yres = 768, + .pixclock = 16835, /* 59400000 // 65000000 */ + .left_margin = 156, + .right_margin = 156, + .upper_margin = 21, + .lower_margin = 7, + .hsync_len = 8, + .vsync_len = 10, + .sync = FB_SYNC_EXT, + .vmode = FB_VMODE_NONINTERLACED } }, + { .bus = LCDIF_BASE_ADDR, + .addr = 0, + .pixfmt = 24, + .detect = detect_display, + .enable = do_enable_mipi2lvds, + .mode = { .name = "PCONXS", + .refresh = 60, + .xres = 1024, + .yres = 600, + .pixclock = 16835, /* 59400000 // 65000000 */ + .left_margin = 156, + .right_margin = 156, + .upper_margin = 21, + .lower_margin = 7, + .hsync_len = 8, + .vsync_len = 10, + .sync = FB_SYNC_EXT, + .vmode = FB_VMODE_NONINTERLACED } }, + { .bus = LCDIF_BASE_ADDR, + .addr = 0, + .pixfmt = 24, + .detect = detect_display, + .enable = do_enable_mipi2lvds, + .mode = { .name = "LVDS_SCF1001C44GGU05", + .refresh = 60, + .xres = 1280, + .yres = 800, + .pixclock = 24390, /* 41000000 */ + .left_margin = 48, + .right_margin = 52, + .upper_margin = 10, + .lower_margin = 10, + .hsync_len = 60, + .vsync_len = 3, + .sync = FB_SYNC_EXT, + .vmode = FB_VMODE_NONINTERLACED } }, + { .bus = LCDIF_BASE_ADDR, + .addr = 0, + .pixfmt = 24, + .detect = detect_display, + .enable = do_enable_mipi2lvds, + .mode = { .name = "GUF_LVDS_SCF1001C44GGU05", + .refresh = 60, + .xres = 1280, + .yres = 800, + .pixclock = 24390, /* 41000000 */ + .left_margin = 48, + .right_margin = 52, + .upper_margin = 10, + .lower_margin = 10, + .hsync_len = 60, + .vsync_len = 3, + .sync = FB_SYNC_EXT, + .vmode = FB_VMODE_NONINTERLACED } }, + { .bus = LCDIF_BASE_ADDR, + .addr = 0, + .pixfmt = 24, + .detect = detect_display, + .enable = do_enable_mipi2lvds, + .mode = { .name = "LVDS_ATM0700L61", + .refresh = 60, + .xres = 1024, + .yres = 600, + .pixclock = 16835, /* 59400000 // 65000000 */ + .left_margin = 156, + .right_margin = 156, + .upper_margin = 21, + .lower_margin = 7, + .hsync_len = 8, + .vsync_len = 10, + .sync = FB_SYNC_EXT, + .vmode = FB_VMODE_NONINTERLACED } }, + { .bus = LCDIF_BASE_ADDR, + .addr = 0, + .pixfmt = 24, + .detect = detect_display, + .enable = do_enable_mipi2rgb, + .mode = { .name = "RGB18_ATM0700D6J", + .refresh = 60, + .xres = 800, + .yres = 480, + .pixclock = 33300, /* 33300000 */ + .left_margin = 40, + .right_margin = 210, + .upper_margin = 20, + .lower_margin = 22, + .hsync_len = 6, + .vsync_len = 3, + .sync = FB_SYNC_EXT, + .vmode = FB_VMODE_NONINTERLACED } }, + { .bus = LCDIF_BASE_ADDR, + .addr = 0, + .pixfmt = 24, + .detect = detect_display, + .enable = do_enable_mipi2rgb, + .mode = { .name = "RGB_ATM0700D6J", + .refresh = 60, + .xres = 800, + .yres = 480, + .pixclock = 33300, /* 33300000 */ + .left_margin = 40, + .right_margin = 210, + .upper_margin = 20, + .lower_margin = 22, + .hsync_len = 6, + .vsync_len = 3, + .sync = FB_SYNC_EXT, + .vmode = FB_VMODE_NONINTERLACED } }, + { .bus = LCDIF_BASE_ADDR, + .addr = 0, + .pixfmt = 24, + .detect = detect_display, + .enable = do_enable_mipi2lvds, + .mode = { .name = "LVDS_ATM0700D6J", + .refresh = 60, + .xres = 800, + .yres = 480, + .pixclock = 16835, /* 59400000 // 65000000 */ + .left_margin = 156, + .right_margin = 156, + .upper_margin = 21, + .lower_margin = 7, + .hsync_len = 8, + .vsync_len = 10, + .sync = FB_SYNC_EXT, + .vmode = FB_VMODE_NONINTERLACED } }, + { .bus = LCDIF_BASE_ADDR, + .addr = 0, + .pixfmt = 24, + .detect = detect_display, + .enable = do_enable_mipi2hdmi, + .mode = { .name = "HDMI_1920x1080", + .refresh = 60, + .xres = 1920, + .yres = 1080, + .pixclock = 6734, /* 148500000 */ + .left_margin = 148, + .right_margin = 88, + .upper_margin = 36, + .lower_margin = 4, + .hsync_len = 44, + .vsync_len = 5, + .sync = FB_SYNC_EXT, + .vmode = FB_VMODE_NONINTERLACED } }, + { .bus = LCDIF_BASE_ADDR, + .addr = 0, + .pixfmt = 24, + .detect = detect_display, + .enable = do_enable_mipi2lvds, + .mode = { .name = "LVDS_AM19201080D1", + .refresh = 60, + .xres = 1920, + .yres = 1080, + .pixclock = 6734, /* 148500000 */ + .left_margin = 148, + .right_margin = 88, + .upper_margin = 36, + .lower_margin = 4, + .hsync_len = 44, + .vsync_len = 5, + .sync = FB_SYNC_EXT, + .vmode = FB_VMODE_NONINTERLACED } }, + { .bus = LCDIF_BASE_ADDR, + .addr = 0, + .pixfmt = 24, + .detect = detect_display, + .enable = do_enable_mipi2lvds, + .mode = { .name = "LVDS_G156HAN02.1", + .refresh = 60, + .xres = 1920, + .yres = 1080, + .pixclock = 6734, /* 68.5 > 70.5 MHz > 74.5 */ + .left_margin = 91, + .right_margin = 91, + .upper_margin = 8, + .lower_margin = 8, + .hsync_len = 40, + .vsync_len = 4, + .sync = FB_SYNC_EXT, + .vmode = FB_VMODE_NONINTERLACED + } }, + { .detect = detect_display, + .enable = do_display_default, + .mode = { .name = "none", } + } } ; +// clang-format on +size_t display_count = ARRAY_SIZE(displays); diff --git a/board/seco/imx8mm_myon2/spl.c b/board/seco/imx8mm_myon2/spl.c index b2e091e90c08bb80f839318538d70859fe66d733..c83ea1fdb098c446aef494b03dea3a904d22bd81 100644 --- a/board/seco/imx8mm_myon2/spl.c +++ b/board/seco/imx8mm_myon2/spl.c @@ -342,6 +342,7 @@ void board_init_f(ulong dummy) /* DDR initialization */ spl_dram_init(); +#if 0 if (seco_fuse_GetPCBrevision() < SECO_FUSE_PCBREV_V2R2) /* Hardcode spread spectrum on VideoPLL1 30kHz, 2%, centered * used for lvds signals via MIPI-to-LVDS converter @@ -350,6 +351,7 @@ void board_init_f(ulong dummy) ssc_state_video_pll(true, 0x80019052); else ssc_state_video_pll(false, 0x80019052); +#endif max_freq = get_cpu_speed_grade_hz(); if (max_freq) { diff --git a/board/seco/imx8mm_myon2/video_callgraph b/board/seco/imx8mm_myon2/video_callgraph new file mode 100644 index 0000000000000000000000000000000000000000..a60f9e57866beb3b08555ddfbdd1ec95262a7fa7 --- /dev/null +++ b/board/seco/imx8mm_myon2/video_callgraph @@ -0,0 +1,30 @@ +stdio_add_devices() common/stdio.c + | + -> drv_video_init() drivers/video/cfb_console.c !defined(CONFIG_DM_VIDEO) && (defined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE)) + | + +> board_video_skip() arch/arm/mach-imx/video.c + | | + | +-> detect_display() board/seco/imx8mm_myon2/display.c + | | + | -> do_enable_mipi2lvds() board/seco/imx8mm_myon2/display.c + | | + | +> sec_mipi_dsim_setup() drivers/video/sec_mipi_dsim.c + | | | + | | -> imx_mipi_dsi_bridge_register_driver() + | | + | -> imx_mipi_dsi_bridge_attach() + | + -> cfg_video_init() drivers/video/cfb_console.c + | + +> video_hw_init() drivers/video/mxsfb.c + | | + | +> imx_mipi_dsi_bridge_mode_set() + | | + | +> imx_mipi_dsi_bridge_enable() + | | + | -> mxs_lcd_init() drivers/video/mxsfb.c + | + -> video_logo() drivers/video/cfb_console.c defined(CONFIG_VIDEO_LOGO) + | + -> logo_plot() + diff --git a/configs/seco_imx8mm_myon2_defconfig b/configs/seco_imx8mm_myon2_defconfig index 284973a3e883ffc4448a7900f883453858358cec..017fe65cb55fbf9df8be3d6a731d6ca42799de5d 100644 --- a/configs/seco_imx8mm_myon2_defconfig +++ b/configs/seco_imx8mm_myon2_defconfig @@ -117,6 +117,8 @@ CONFIG_USB_GADGET_DOWNLOAD=y CONFIG_USB_FUNCTION_SDP=y CONFIG_USB_HOST_ETHER=y CONFIG_USB_ETHER_SMSC95XX=y +CONFIG_VIDEO=y +CONFIG_IMX_SEC_MIPI_DSI=y CONFIG_IMX_WATCHDOG=y CONFIG_WDT=y CONFIG_PANIC_HANG=y diff --git a/configs/seco_imx8mm_myon2_fastboot_defconfig b/configs/seco_imx8mm_myon2_fastboot_defconfig index a710ef0627d82817f00428a592c84bdaa96be20d..a4ec7600311a43e5ff2d636e560d51ddb5cba316 100644 --- a/configs/seco_imx8mm_myon2_fastboot_defconfig +++ b/configs/seco_imx8mm_myon2_fastboot_defconfig @@ -121,6 +121,8 @@ CONFIG_USB_GADGET_DOWNLOAD=y CONFIG_USB_FUNCTION_SDP=y CONFIG_USB_HOST_ETHER=y CONFIG_USB_ETHER_SMSC95XX=y +CONFIG_VIDEO=y +CONFIG_IMX_SEC_MIPI_DSI=y CONFIG_IMX_WATCHDOG=y CONFIG_WDT=y CONFIG_PANIC_HANG=y diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 759434763066226ea3308e1d2e1261593193f318..1b63f4000cbe9b58310bb15bdf2122aaf2d21ee2 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -695,6 +695,19 @@ config VIDEO model. Video drivers typically provide a colour text console and cursor. +config IMX_MIPI_DSI_BRIDGE + bool + help + Enable MIPI DSI bridge interface for display controller. + +config IMX_SEC_MIPI_DSI + bool "i.MX Samsung's MIPI DSI" + default n + select IMX_MIPI_DSI_BRIDGE + depends on VIDEO && (IMX8MM || IMX8MN || MX7 || IMX8MP) + help + Support for i.MX7, i.MX8MM and i.MX8MN MIPI DSI controller. + config MXC_EPDC bool "i.MX EPDC support" depends on LCD && (MX7 || MX6) diff --git a/drivers/video/Makefile b/drivers/video/Makefile index c4da9e5e20fffb2fb8aa029d5745baef5d28758b..4710c72f0efce68e21deb5158b63c0e3a727f62c 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -82,6 +82,9 @@ obj-$(CONFIG_VIDEO_CSI) += mxc_csi.o obj-$(CONFIG_VIDEO_PXP) += mxc_pxp.o obj-$(CONFIG_VIDEO_GIS) += mxc_gis.o +obj-$(CONFIG_IMX_MIPI_DSI_BRIDGE) += imx_mipi_dsi_bridge.o +obj-$(CONFIG_IMX_SEC_MIPI_DSI) += sec_mipi_dsim.o + obj-y += bridge/ obj-y += sunxi/ obj-y += nxp/ diff --git a/drivers/video/imx_mipi_dsi_bridge.c b/drivers/video/imx_mipi_dsi_bridge.c new file mode 100644 index 0000000000000000000000000000000000000000..a3e9e26317d876d026a57fc270bdbae280a3fe79 --- /dev/null +++ b/drivers/video/imx_mipi_dsi_bridge.c @@ -0,0 +1,104 @@ +/* + * Copyright 2018 NXP + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <asm/io.h> +#include <linux/errno.h> +#include <imx_mipi_dsi_bridge.h> + +static struct mipi_dsi_bridge_driver *registered_driver = NULL; + +int imx_mipi_dsi_bridge_attach(struct mipi_dsi_client_dev *dsi_dev) +{ + int ret = 0; + + if (!registered_driver) + return -EPERM; + + if (registered_driver->attach) + ret = registered_driver->attach(registered_driver, dsi_dev); + + return ret; +} + +int imx_mipi_dsi_bridge_mode_set(struct fb_videomode *pvmode) +{ + int ret = 0; + + if (!registered_driver) + return -EPERM; + + if (registered_driver->mode_set) + ret = registered_driver->mode_set(registered_driver, pvmode); + + return ret; +} + +int imx_mipi_dsi_bridge_enable(void) +{ + int ret = 0; + + if (!registered_driver) + return -EPERM; + + if (registered_driver->enable) + ret = registered_driver->enable(registered_driver); + + return ret; +} + +int imx_mipi_dsi_bridge_disable(void) +{ + int ret = 0; + + if (!registered_driver) + return -EPERM; + + if (registered_driver->disable) + ret = registered_driver->disable(registered_driver); + + return ret; +} + +int imx_mipi_dsi_bridge_pkt_write(u8 data_type, const u8 *buf, int len) +{ + int ret = 0; + + if (!registered_driver) + return -EPERM; + + if (registered_driver->pkt_write) + ret = registered_driver->pkt_write(registered_driver, data_type, buf, len); + + return ret; +} + +int imx_mipi_dsi_bridge_add_client_driver(struct mipi_dsi_client_driver *client_driver) +{ + int ret = 0; + + if (!registered_driver) + return -EPERM; + + if (registered_driver->add_client_driver) + ret = registered_driver->add_client_driver(registered_driver, client_driver); + + return ret; +} + +int imx_mipi_dsi_bridge_register_driver(struct mipi_dsi_bridge_driver *driver) +{ + if (!driver) + return -EINVAL; + + if (registered_driver) + return -EBUSY; + + registered_driver = driver; + + return 0; +} diff --git a/drivers/video/mxsfb.c b/drivers/video/mxsfb.c index 4757f3ec753d833b6ecb05a8caf723ef1e36f7be..df546dc90fb03a720e9e8bc56c0b6aae4a5bc675 100644 --- a/drivers/video/mxsfb.c +++ b/drivers/video/mxsfb.c @@ -44,6 +44,10 @@ #include <gis.h> #endif +#ifdef CONFIG_IMX_MIPI_DSI_BRIDGE +#include <imx_mipi_dsi_bridge.h> +#endif + #define PS2KHZ(ps) (1000000000UL / (ps)) #define HZ2PS(hz) (1000000000UL / ((hz) / 1000)) @@ -360,7 +364,22 @@ void *video_hw_init(void) printf("%s\n", panel.modeIdent); video_ctfb_mode_to_display_timing(&mode, &timings); + +#ifdef CONFIG_IMX_MIPI_DSI_BRIDGE + int dsi_ret; + imx_mipi_dsi_bridge_mode_set(&fbmode); + dsi_ret = imx_mipi_dsi_bridge_enable(); + if (dsi_ret) { + printf("Enable DSI bridge failed, err %d\n", dsi_ret); + return NULL; + } +#endif + +#ifdef CONFIG_IMX_SEC_MIPI_DSI + timings.flags |= DISPLAY_FLAGS_DE_LOW; +#else timings.flags |= DISPLAY_FLAGS_DE_HIGH; /* Force enable pol */ +#endif ret = mxs_probe_common(panel.isaBase, &timings, bpp, (u32)fb, false); if (ret) diff --git a/drivers/video/sec_mipi_dsim.c b/drivers/video/sec_mipi_dsim.c new file mode 100644 index 0000000000000000000000000000000000000000..e8922115b1d68422ed5f314774f48f8f9fad1561 --- /dev/null +++ b/drivers/video/sec_mipi_dsim.c @@ -0,0 +1,1024 @@ +/* + * Copyright 2018 NXP + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <asm/io.h> +#include <linux/errno.h> +#include <asm/arch/clock.h> +#include <asm/arch/imx-regs.h> +#include <asm/arch/sys_proto.h> +#include <div64.h> +#include <i2c.h> +#include <malloc.h> +#include <linux/delay.h> +#include <sec_mipi_dsim.h> +#include <imx_mipi_dsi_bridge.h> +#include <asm-generic/gpio.h> +#include <asm/mach-imx/gpio.h> +#include "../../board/seco/common/seco_fuse.h" + +#define BX5_ID 0x64 + +#define MIPI_FIFO_TIMEOUT 250000 /* 250ms */ + +#define DRIVER_NAME "imx_sec_mipi_dsim" + +/* dsim registers */ +#define DSIM_VERSION 0x00 +#define DSIM_STATUS 0x04 +#define DSIM_RGB_STATUS 0x08 +#define DSIM_SWRST 0x0c +#define DSIM_CLKCTRL 0x10 +#define DSIM_TIMEOUT 0x14 +#define DSIM_CONFIG 0x18 +#define DSIM_ESCMODE 0x1c +#define DSIM_MDRESOL 0x20 +#define DSIM_MVPORCH 0x24 +#define DSIM_MHPORCH 0x28 +#define DSIM_MSYNC 0x2c +#define DSIM_SDRESOL 0x30 +#define DSIM_INTSRC 0x34 +#define DSIM_INTMSK 0x38 + +/* packet */ +#define DSIM_PKTHDR 0x3c +#define DSIM_PAYLOAD 0x40 +#define DSIM_RXFIFO 0x44 +#define DSIM_FIFOTHLD 0x48 +#define DSIM_FIFOCTRL 0x4c +#define DSIM_MEMACCHR 0x50 +#define DSIM_MULTI_PKT 0x78 + +/* pll control */ +#define DSIM_PLLCTRL_1G 0x90 +#define DSIM_PLLCTRL 0x94 +#define DSIM_PLLCTRL1 0x98 +#define DSIM_PLLCTRL2 0x9c +#define DSIM_PLLTMR 0xa0 + +/* dphy */ +#define DSIM_PHYTIMING 0xb4 +#define DSIM_PHYTIMING1 0xb8 +#define DSIM_PHYTIMING2 0xbc + +/* reg bit manipulation */ +#define REG_MASK(e, s) (((1 << ((e) - (s) + 1)) - 1) << (s)) +#define REG_PUT(x, e, s) (((x) << (s)) & REG_MASK(e, s)) +#define REG_GET(x, e, s) (((x) & REG_MASK(e, s)) >> (s)) + +/* register bit fields */ +#define STATUS_PLLSTABLE BIT(31) +#define STATUS_SWRSTRLS BIT(20) +#define STATUS_TXREADYHSCLK BIT(10) +#define STATUS_ULPSCLK BIT(9) +#define STATUS_STOPSTATECLK BIT(8) +#define STATUS_GET_ULPSDAT(x) REG_GET(x, 7, 4) +#define STATUS_GET_STOPSTATEDAT(x) REG_GET(x, 3, 0) + +#define RGB_STATUS_CMDMODE_INSEL BIT(31) +#define RGB_STATUS_GET_RGBSTATE(x) REG_GET(x, 12, 0) + +#define CLKCTRL_TXREQUESTHSCLK BIT(31) +#define CLKCTRL_DPHY_SEL_1G BIT(29) +#define CLKCTRL_DPHY_SEL_1P5G (0x0 << 29) +#define CLKCTRL_ESCCLKEN BIT(28) +#define CLKCTRL_PLLBYPASS BIT(29) +#define CLKCTRL_BYTECLKSRC_DPHY_PLL REG_PUT(0, 26, 25) +#define CLKCTRL_BYTECLKEN BIT(24) +#define CLKCTRL_SET_LANEESCCLKEN(x) REG_PUT(x, 23, 19) +#define CLKCTRL_SET_ESCPRESCALER(x) REG_PUT(x, 15, 0) + +#define TIMEOUT_SET_BTAOUT(x) REG_PUT(x, 23, 16) +#define TIMEOUT_SET_LPDRTOUT(x) REG_PUT(x, 15, 0) + +#define CONFIG_NON_CONTINOUS_CLOCK_LANE BIT(31) +#define CONFIG_CLKLANE_STOP_START BIT(30) +#define CONFIG_MFLUSH_VS BIT(29) +#define CONFIG_EOT_R03 BIT(28) +#define CONFIG_SYNCINFORM BIT(27) +#define CONFIG_BURSTMODE BIT(26) +#define CONFIG_VIDEOMODE BIT(25) +#define CONFIG_AUTOMODE BIT(24) +#define CONFIG_HSEDISABLEMODE BIT(23) +#define CONFIG_HFPDISABLEMODE BIT(22) +#define CONFIG_HBPDISABLEMODE BIT(21) +#define CONFIG_HSADISABLEMODE BIT(20) +#define CONFIG_SET_MAINVC(x) REG_PUT(x, 19, 18) +#define CONFIG_SET_SUBVC(x) REG_PUT(x, 17, 16) +#define CONFIG_SET_MAINPIXFORMAT(x) REG_PUT(x, 14, 12) +#define CONFIG_SET_SUBPIXFORMAT(x) REG_PUT(x, 10, 8) +#define CONFIG_SET_NUMOFDATLANE(x) REG_PUT(x, 6, 5) +#define CONFIG_SET_LANEEN(x) REG_PUT(x, 4, 0) + +#define MDRESOL_MAINSTANDBY BIT(31) +#define MDRESOL_SET_MAINVRESOL(x) REG_PUT(x, 27, 16) +#define MDRESOL_SET_MAINHRESOL(x) REG_PUT(x, 11, 0) + +#define MVPORCH_SET_CMDALLOW(x) REG_PUT(x, 31, 28) +#define MVPORCH_SET_STABLEVFP(x) REG_PUT(x, 26, 16) +#define MVPORCH_SET_MAINVBP(x) REG_PUT(x, 10, 0) + +#define MHPORCH_SET_MAINHFP(x) REG_PUT(x, 31, 16) +#define MHPORCH_SET_MAINHBP(x) REG_PUT(x, 15, 0) + +#define MSYNC_SET_MAINVSA(x) REG_PUT(x, 31, 22) +#define MSYNC_SET_MAINHSA(x) REG_PUT(x, 15, 0) + +#define INTSRC_PLLSTABLE BIT(31) +#define INTSRC_SWRSTRELEASE BIT(30) +#define INTSRC_SFRPLFIFOEMPTY BIT(29) +#define INTSRC_SFRPHFIFOEMPTY BIT(28) +#define INTSRC_FRAMEDONE BIT(24) +#define INTSRC_LPDRTOUT BIT(21) +#define INTSRC_TATOUT BIT(20) +#define INTSRC_RXDATDONE BIT(18) +#define INTSRC_MASK (INTSRC_PLLSTABLE | \ + INTSRC_SWRSTRELEASE | \ + INTSRC_SFRPLFIFOEMPTY | \ + INTSRC_SFRPHFIFOEMPTY | \ + INTSRC_FRAMEDONE | \ + INTSRC_LPDRTOUT | \ + INTSRC_TATOUT | \ + INTSRC_RXDATDONE) + +#define INTMSK_MSKPLLSTABLE BIT(31) +#define INTMSK_MSKSWRELEASE BIT(30) +#define INTMSK_MSKSFRPLFIFOEMPTY BIT(29) +#define INTMSK_MSKSFRPHFIFOEMPTY BIT(28) +#define INTMSK_MSKFRAMEDONE BIT(24) +#define INTMSK_MSKLPDRTOUT BIT(21) +#define INTMSK_MSKTATOUT BIT(20) +#define INTMSK_MSKRXDATDONE BIT(18) + +#define PLLCTRL_DPDNSWAP_CLK BIT(25) +#define PLLCTRL_DPDNSWAP_DAT BIT(24) +#define PLLCTRL_PLLEN BIT(23) +#define PLLCTRL_SET_PMS(x) REG_PUT(x, 19, 1) + +#define PHYTIMING_SET_M_TLPXCTL(x) REG_PUT(x, 15, 8) +#define PHYTIMING_SET_M_THSEXITCTL(x) REG_PUT(x, 7, 0) + +#define PHYTIMING1_SET_M_TCLKPRPRCTL(x) REG_PUT(x, 31, 24) +#define PHYTIMING1_SET_M_TCLKZEROCTL(x) REG_PUT(x, 23, 16) +#define PHYTIMING1_SET_M_TCLKPOSTCTL(x) REG_PUT(x, 15, 8) +#define PHYTIMING1_SET_M_TCLKTRAILCTL(x) REG_PUT(x, 7, 0) + +#define PHYTIMING2_SET_M_THSPRPRCTL(x) REG_PUT(x, 23, 16) +#define PHYTIMING2_SET_M_THSZEROCTL(x) REG_PUT(x, 15, 8) +#define PHYTIMING2_SET_M_THSTRAILCTL(x) REG_PUT(x, 7, 0) + +#define dsim_read(dsim, reg) readl(dsim->base + reg) +#define dsim_write(dsim, val, reg) writel(val, dsim->base + reg) + +/* fixed phy ref clk rate */ +#define PHY_REF_CLK 27000000 + +#define MAX_MAIN_HRESOL 2047 +#define MAX_MAIN_VRESOL 2047 +#define MAX_SUB_HRESOL 1024 +#define MAX_SUB_VRESOL 1024 + +/* in KHZ */ +#define MAX_ESC_CLK_FREQ 20000 + +/* dsim all irqs index */ +#define PLLSTABLE 1 +#define SWRSTRELEASE 2 +#define SFRPLFIFOEMPTY 3 +#define SFRPHFIFOEMPTY 4 +#define SYNCOVERRIDE 5 +#define BUSTURNOVER 6 +#define FRAMEDONE 7 +#define LPDRTOUT 8 +#define TATOUT 9 +#define RXDATDONE 10 +#define RXTE 11 +#define RXACK 12 +#define ERRRXECC 13 +#define ERRRXCRC 14 +#define ERRESC3 15 +#define ERRESC2 16 +#define ERRESC1 17 +#define ERRESC0 18 +#define ERRSYNC3 19 +#define ERRSYNC2 20 +#define ERRSYNC1 21 +#define ERRSYNC0 22 +#define ERRCONTROL3 23 +#define ERRCONTROL2 24 +#define ERRCONTROL1 25 +#define ERRCONTROL0 26 + +#define MIPI_HFP_PKT_OVERHEAD 6 +#define MIPI_HBP_PKT_OVERHEAD 6 +#define MIPI_HSA_PKT_OVERHEAD 6 + +/* Dispmix Control & GPR Registers */ +#define DISPLAY_MIX_SFT_RSTN_CSR 0x00 +#ifdef CONFIG_IMX8MN +#define MIPI_DSI_I_PRESETn_SFT_EN BIT(0) | BIT(1) +#else +#define MIPI_DSI_I_PRESETn_SFT_EN BIT(5) +#endif +#define DISPLAY_MIX_CLK_EN_CSR 0x04 + +#ifdef CONFIG_IMX8MN +#define MIPI_DSI_PCLK_SFT_EN BIT(0) +#define MIPI_DSI_CLKREF_SFT_EN BIT(1) +#else +#define MIPI_DSI_PCLK_SFT_EN BIT(8) +#define MIPI_DSI_CLKREF_SFT_EN BIT(9) +#endif +#define GPR_MIPI_RESET_DIV 0x08 +/* Clock & Data lanes reset: Active Low */ +#define GPR_MIPI_S_RESETN BIT(16) +#define GPR_MIPI_M_RESETN BIT(17) + +#define PS2KHZ(ps) (1000000000UL / (ps)) + +/* DSIM PLL configuration from spec: + * + * Fout(DDR) = (M * Fin) / (P * 2^S), so Fout / Fin = M / (P * 2^S) + * Fin_pll = Fin / P (6 ~ 12 MHz) + * S: [2:0], M: [12:3], P: [18:13], so + * TODO: 'S' is in [0 ~ 3], 'M' is in, 'P' is in [1 ~ 33] + * + */ + +// Initialize QuickLogic ArcticLink III Bx5 with auto-generated array +static void bx5_init(void) +{ + printf("Bx5 Init Sequence\n"); + struct udevice *bus, *bx5_dev; + int i2c_bus = 2; + int ret; + uint8_t CONTROL_BYTE_GEN = 0x09; + uint8_t i2c_telegram[10] = { 0x29, 0x05, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + // Generated by SECO_ATM0700L61-CT18_U-Boot.xlsm (18.08.2022 16:16:20) + struct Addr_data { + int addr; + int data; + } Vx_addr_data[] = { + { 0x700, 0x6C000040 }, { 0x704, 0x3009A }, { 0x70C, 0x00004604 }, + { 0x710, 0x00CD000B }, { 0x714, 0x0 }, { 0x718, 0x00000101 }, + { 0x71C, 0x2F }, { 0x720, 0x0 }, { 0x154, 0x00000000 }, + { 0x154, 0x80000000 }, { 0x700, 0x6C000040 }, { 0x70C, 0x00004646 }, + { 0x718, 0x00000001 }, { 0x154, 0x00000000 }, { 0x154, 0x80000000 }, + { 0x120, 0x5 }, { 0x124, 0x212C400 }, { 0x128, 0x101C15 }, + { 0x12C, 0x99 }, { 0x130, 0x3C18 }, { 0x134, 0x00000015 }, + { 0x138, 0xFF8000 }, { 0x13C, 0x0 }, { 0x140, 0x10000 }, + { 0x20C, 0x124 }, { 0x21C, 0x3E8 }, { 0x224, 0x0 }, + { 0x228, 0x10000 }, { 0x228, 0x10000 }, { 0x22C, 0xFF03 }, + { 0x230, 0x1 }, { 0x234, 0xCA033E10 }, { 0x238, 0x00000060 }, + { 0x244, 0x00120285 }, { 0x258, 0xA0008 }, { 0x158, 0x0 }, + { 0x158, 0x1 }, { 0x37C, 0x00001063 }, { 0x380, 0x82A86030 }, + { 0x384, 0x2861408B }, { 0x388, 0x00130285 }, { 0x38C, 0x10630009 }, + { 0x394, 0x400B82A8 }, { 0x600, 0x16CC78C }, { 0x604, 0x3FFFFFE0 }, + { 0x608, 0xFEC }, { 0x154, 0x00000000 }, { 0x154, 0x80000000 }, + }; + + ret = uclass_get_device_by_seq(UCLASS_I2C, i2c_bus, &bus); + if (ret) { + printf("%s: No bus %d\n", __func__, i2c_bus); + return; + } + ret = dm_i2c_probe(bus, BX5_ID, 0, &bx5_dev); + if (ret) { + printf("%s: Can't find Bx5 device id=0x%x, on bus %d\n", __func__, BX5_ID, i2c_bus); + return; + } else { + printf("QuickLogic ArcticLink III Bx5 (0x%x) found\n", BX5_ID); + } + + for (int i = 0; i < (sizeof(Vx_addr_data) / 8); i++) { + i2c_telegram[4] = (Vx_addr_data[i].addr) & 0xFF; + i2c_telegram[5] = (Vx_addr_data[i].addr >> 8) & 0xFF; + i2c_telegram[6] = (Vx_addr_data[i].data) & 0xFF; + i2c_telegram[7] = (Vx_addr_data[i].data >> 8) & 0xFF; + i2c_telegram[8] = (Vx_addr_data[i].data >> 16) & 0xFF; + i2c_telegram[9] = (Vx_addr_data[i].data >> 24) & 0xFF; + + dm_i2c_write(bx5_dev, CONTROL_BYTE_GEN, i2c_telegram, sizeof(i2c_telegram)); + + if (((Vx_addr_data[i].addr == 0x154) && (Vx_addr_data[i].data == 0x80000000)) || + ((Vx_addr_data[i].addr == 0x158) && (Vx_addr_data[i].data == 0x00000001))) + udelay(1000); + } +} + +struct sec_mipi_dsim { + void __iomem *base; + void __iomem *disp_mix_gpr_base; + + /* kHz clocks */ + uint64_t pix_clk; + uint64_t bit_clk; + + unsigned int lanes; + unsigned int channel; /* virtual channel */ + enum mipi_dsi_pixel_format format; + unsigned long mode_flags; + unsigned int pms; + unsigned int p; + unsigned int m; + unsigned int s; + struct fb_videomode vmode; + + const struct sec_mipi_dsim_plat_data *pdata; + + struct mipi_dsi_client_dev *dsi_panel_dev; + struct mipi_dsi_client_driver *dsi_panel_drv; +}; + +#ifndef CONFIG_IMX8MP +static void disp_mix_dsim_soft_reset_release(struct sec_mipi_dsim *dsim, bool release) +{ + if (release) + /* release dsi blk reset */ + setbits_le32(dsim->disp_mix_gpr_base + DISPLAY_MIX_SFT_RSTN_CSR, + MIPI_DSI_I_PRESETn_SFT_EN); + + else + clrbits_le32(dsim->disp_mix_gpr_base + DISPLAY_MIX_SFT_RSTN_CSR, + MIPI_DSI_I_PRESETn_SFT_EN); +} + +static void disp_mix_dsim_clks_enable(struct sec_mipi_dsim *dsim, bool enable) +{ + if (enable) + setbits_le32(dsim->disp_mix_gpr_base + DISPLAY_MIX_CLK_EN_CSR, + MIPI_DSI_PCLK_SFT_EN | MIPI_DSI_CLKREF_SFT_EN); + else + clrbits_le32(dsim->disp_mix_gpr_base + DISPLAY_MIX_CLK_EN_CSR, + MIPI_DSI_PCLK_SFT_EN | MIPI_DSI_CLKREF_SFT_EN); +} + +static void disp_mix_dsim_lanes_reset(struct sec_mipi_dsim *dsim, bool reset) +{ + if (!reset) + /* release lanes reset */ + setbits_le32(dsim->disp_mix_gpr_base + GPR_MIPI_RESET_DIV, GPR_MIPI_M_RESETN); + else + /* reset lanes */ + clrbits_le32(dsim->disp_mix_gpr_base + GPR_MIPI_RESET_DIV, GPR_MIPI_M_RESETN); +} +#endif + +static void sec_mipi_dsim_wr_tx_header(struct sec_mipi_dsim *dsim, u8 di, u8 data0, u8 data1) +{ + unsigned int reg; + + reg = (data1 << 16) | (data0 << 8) | ((di & 0x3f) << 0); + dsim_write(dsim, reg, DSIM_PKTHDR); +} + +static void sec_mipi_dsim_wr_tx_data(struct sec_mipi_dsim *dsim, unsigned int tx_data) +{ + dsim_write(dsim, tx_data, DSIM_PAYLOAD); +} + +static void sec_mipi_dsim_long_data_wr(struct sec_mipi_dsim *dsim, const unsigned char *data0, + unsigned int data_size) +{ + unsigned int data_cnt = 0, payload = 0; + + /* in case that data count is more then 4 */ + for (data_cnt = 0; data_cnt < data_size; data_cnt += 4) { + /* + * after sending 4 bytes 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]; + } + + sec_mipi_dsim_wr_tx_data(dsim, payload); + /* send 4 bytes per one time. */ + } 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]); + + sec_mipi_dsim_wr_tx_data(dsim, payload); + } + } +} + +static int sec_mipi_dsim_wait_for_pkt_done(struct sec_mipi_dsim *dsim, unsigned long timeout) +{ + uint32_t intsrc; + + do { + intsrc = dsim_read(dsim, DSIM_INTSRC); + if (intsrc & INTSRC_SFRPLFIFOEMPTY) { + dsim_write(dsim, INTSRC_SFRPLFIFOEMPTY, DSIM_INTSRC); + return 0; + } + + udelay(1); + } while (--timeout); + + return -ETIMEDOUT; +} + +static int sec_mipi_dsim_pkt_write(struct sec_mipi_dsim *dsim, u8 data_type, const u8 *buf, int len) +{ + int ret = 0; + const unsigned char *data = (const unsigned char *)buf; + + if (len == 0) + /* handle generic short write command */ + sec_mipi_dsim_wr_tx_header(dsim, data_type, data[0], data[1]); + else { + /* handle generic long write command */ + sec_mipi_dsim_long_data_wr(dsim, data, len); + sec_mipi_dsim_wr_tx_header(dsim, data_type, len & 0xff, (len & 0xff00) >> 8); + + ret = sec_mipi_dsim_wait_for_pkt_done(dsim, MIPI_FIFO_TIMEOUT); + if (ret) { + printf("wait tx done timeout!\n"); + return -ETIMEDOUT; + } + } + mdelay(10); + + return 0; +} + +static int sec_mipi_dsim_wait_pll_stable(struct sec_mipi_dsim *dsim) +{ + uint32_t status; + ulong start; + + start = get_timer(0); /* Get current timestamp */ + + do { + status = dsim_read(dsim, DSIM_STATUS); + if (status & STATUS_PLLSTABLE) + return 0; + } while (get_timer(0) < (start + 100)); /* Wait 100ms */ + + return -ETIMEDOUT; +} + +static int sec_mipi_dsim_config_pll(struct sec_mipi_dsim *dsim) +{ + int ret; + uint32_t pllctrl = 0, status, data_lanes_en, stop; + + dsim_write(dsim, 0x8000, DSIM_PLLTMR); + + /* TODO: config dp/dn swap if requires */ + + pllctrl |= PLLCTRL_SET_PMS(dsim->pms) | PLLCTRL_PLLEN; + dsim_write(dsim, pllctrl, DSIM_PLLCTRL); + + ret = sec_mipi_dsim_wait_pll_stable(dsim); + if (ret) { + printf("wait for pll stable time out\n"); + return ret; + } + + /* wait for clk & data lanes to go to stop state */ + mdelay(1); + + data_lanes_en = (0x1 << dsim->lanes) - 1; + status = dsim_read(dsim, DSIM_STATUS); + if (!(status & STATUS_STOPSTATECLK)) { + printf("clock is not in stop state\n"); + return -EBUSY; + } + + stop = STATUS_GET_STOPSTATEDAT(status); + if ((stop & data_lanes_en) != data_lanes_en) { + printf("one or more data lanes is not in stop state\n"); + return -EBUSY; + } + + return 0; +} + +static void sec_mipi_dsim_set_main_mode(struct sec_mipi_dsim *dsim) +{ + uint32_t bpp, hfp_wc, hbp_wc, hsa_wc, wc; + uint32_t mdresol = 0, mvporch = 0, mhporch = 0, msync = 0; + struct fb_videomode *vmode = &dsim->vmode; + + mdresol |= MDRESOL_SET_MAINVRESOL(vmode->yres) | + MDRESOL_SET_MAINHRESOL(vmode->xres); + dsim_write(dsim, mdresol, DSIM_MDRESOL); + + mvporch |= MVPORCH_SET_MAINVBP(vmode->upper_margin) | + MVPORCH_SET_STABLEVFP(vmode->lower_margin) | + MVPORCH_SET_CMDALLOW(0x0); + dsim_write(dsim, mvporch, DSIM_MVPORCH); + + bpp = mipi_dsi_pixel_format_to_bpp(dsim->format); + + /* calculate hfp & hbp word counts */ + if (dsim->dsi_panel_drv) { + /* Panel driver is registered, will work with panel */ + wc = DIV_ROUND_UP(vmode->right_margin * (bpp >> 3), + dsim->lanes); + hfp_wc = wc > MIPI_HFP_PKT_OVERHEAD ? + wc - MIPI_HFP_PKT_OVERHEAD : vmode->right_margin; + wc = DIV_ROUND_UP(vmode->left_margin * (bpp >> 3), + dsim->lanes); + hbp_wc = wc > MIPI_HBP_PKT_OVERHEAD ? + wc - MIPI_HBP_PKT_OVERHEAD : vmode->left_margin; + } else { + hfp_wc = vmode->right_margin * (bpp >> 3) / dsim->lanes - 6; + hbp_wc = vmode->left_margin * (bpp >> 3) / dsim->lanes - 6; + } + + mhporch |= MHPORCH_SET_MAINHFP(hfp_wc) | + MHPORCH_SET_MAINHBP(hbp_wc); + + dsim_write(dsim, mhporch, DSIM_MHPORCH); + + /* calculate hsa word counts */ + if (dsim->dsi_panel_drv) { + wc = DIV_ROUND_UP(vmode->hsync_len * (bpp >> 3), + dsim->lanes); + hsa_wc = wc > MIPI_HSA_PKT_OVERHEAD ? + wc - MIPI_HSA_PKT_OVERHEAD : vmode->hsync_len; + } else { + hsa_wc = vmode->hsync_len * (bpp >> 3) / dsim->lanes - 6; + } + + msync |= MSYNC_SET_MAINVSA(vmode->vsync_len) | + MSYNC_SET_MAINHSA(hsa_wc); + + debug("hfp_wc %u hbp_wc %u hsa_wc %u\n", hfp_wc, hbp_wc, hsa_wc); + + dsim_write(dsim, msync, DSIM_MSYNC); +} + +static void sec_mipi_dsim_config_dpi(struct sec_mipi_dsim *dsim) +{ + uint32_t config = 0, rgb_status = 0, data_lanes_en; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) + rgb_status &= ~RGB_STATUS_CMDMODE_INSEL; + else + rgb_status |= RGB_STATUS_CMDMODE_INSEL; + + dsim_write(dsim, rgb_status, DSIM_RGB_STATUS); + + if (dsim->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) + config |= CONFIG_CLKLANE_STOP_START; + + if (dsim->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH) + config |= CONFIG_MFLUSH_VS; + + /* disable EoT packets in HS mode */ + if (dsim->mode_flags & MIPI_DSI_MODE_EOT_PACKET) + config |= CONFIG_EOT_R03; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) { + config |= CONFIG_VIDEOMODE; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) + config |= CONFIG_BURSTMODE; + + else if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + config |= CONFIG_SYNCINFORM; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT) + config |= CONFIG_AUTOMODE; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HSE) + config |= CONFIG_HSEDISABLEMODE; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HFP) + config |= CONFIG_HFPDISABLEMODE; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HBP) + config |= CONFIG_HBPDISABLEMODE; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HSA) + config |= CONFIG_HSADISABLEMODE; + } + + config |= CONFIG_SET_MAINVC(dsim->channel); + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) { + switch (dsim->format) { + case MIPI_DSI_FMT_RGB565: + config |= CONFIG_SET_MAINPIXFORMAT(0x4); + break; + case MIPI_DSI_FMT_RGB666_PACKED: + config |= CONFIG_SET_MAINPIXFORMAT(0x5); + break; + case MIPI_DSI_FMT_RGB666: + config |= CONFIG_SET_MAINPIXFORMAT(0x6); + break; + case MIPI_DSI_FMT_RGB888: + config |= CONFIG_SET_MAINPIXFORMAT(0x7); + break; + default: + config |= CONFIG_SET_MAINPIXFORMAT(0x7); + break; + } + } + + /* config data lanes number and enable lanes */ + data_lanes_en = (0x1 << dsim->lanes) - 1; + config |= CONFIG_SET_NUMOFDATLANE(dsim->lanes - 1); + config |= CONFIG_SET_LANEEN(0x1 | data_lanes_en << 1); + + dsim_write(dsim, config, DSIM_CONFIG); + + if (seco_fuse_GetModule() == SECO_FUSE_MODULE_TRIZEPS8MINI) { + if (seco_fuse_GetPCBrevision() > SECO_FUSE_PCBREV_V1R3) { + bx5_init(); + } + } +} + +static void sec_mipi_dsim_config_dphy(struct sec_mipi_dsim *dsim) +{ + uint32_t phytiming = 0, phytiming1 = 0, phytiming2 = 0, timeout = 0; + struct fb_videomode *vmode = &dsim->vmode; + + /* TODO: add a PHY timing table arranged by the pll Fout */ + if (vmode->pixclock == 6734) { + phytiming |= PHYTIMING_SET_M_TLPXCTL(6) | + PHYTIMING_SET_M_THSEXITCTL(11); + dsim_write(dsim, phytiming, DSIM_PHYTIMING); + + phytiming1 |= PHYTIMING1_SET_M_TCLKPRPRCTL(7) | + PHYTIMING1_SET_M_TCLKZEROCTL(38) | + PHYTIMING1_SET_M_TCLKPOSTCTL(13) | + PHYTIMING1_SET_M_TCLKTRAILCTL(8); + dsim_write(dsim, phytiming1, DSIM_PHYTIMING1); + + phytiming2 |= PHYTIMING2_SET_M_THSPRPRCTL(8) | + PHYTIMING2_SET_M_THSZEROCTL(13) | + PHYTIMING2_SET_M_THSTRAILCTL(11); + dsim_write(dsim, phytiming2, DSIM_PHYTIMING2); + + timeout |= TIMEOUT_SET_BTAOUT(0xf) | + TIMEOUT_SET_LPDRTOUT(0xf); + dsim_write(dsim, timeout, DSIM_TIMEOUT); + } else if (vmode->pixclock == 33300) { + dsim_write(dsim, 0x305, DSIM_PHYTIMING); + dsim_write(dsim, 0x3110904, DSIM_PHYTIMING1); + dsim_write(dsim, 0x30406, DSIM_PHYTIMING2); + dsim_write(dsim, 0xff00ff, DSIM_TIMEOUT); + } else if (vmode->pixclock == 16835) { + dsim_write(dsim, 0x204, DSIM_PHYTIMING); + dsim_write(dsim, 0x20f0903, DSIM_PHYTIMING1); + dsim_write(dsim, 0x30406, DSIM_PHYTIMING2); + dsim_write(dsim, 0xff00ff, DSIM_TIMEOUT); + } else if (vmode->pixclock == 24390) { + dsim_write(dsim, 0x103, DSIM_PHYTIMING); + dsim_write(dsim, 0x10a0802, DSIM_PHYTIMING1); + dsim_write(dsim, 0x20205, DSIM_PHYTIMING2); + dsim_write(dsim, 0xff00ff, DSIM_TIMEOUT); + } else { + printf("TODO: add config dphy calc \r\n"); + } +} + +static void sec_mipi_dsim_config_clkctrl(struct sec_mipi_dsim *dsim) +{ + uint32_t clkctrl = 0, data_lanes_en; + uint64_t byte_clk, esc_prescaler; + + clkctrl |= CLKCTRL_TXREQUESTHSCLK; + + /* using 1.5Gbps PHY */ + clkctrl |= CLKCTRL_DPHY_SEL_1P5G; + + clkctrl |= CLKCTRL_ESCCLKEN; + + clkctrl &= ~CLKCTRL_PLLBYPASS; + + clkctrl |= CLKCTRL_BYTECLKSRC_DPHY_PLL; + + clkctrl |= CLKCTRL_BYTECLKEN; + + data_lanes_en = (0x1 << dsim->lanes) - 1; + clkctrl |= CLKCTRL_SET_LANEESCCLKEN(0x1 | data_lanes_en << 1); + + /* calculate esc prescaler from byte clock: + * EscClk = ByteClk / EscPrescaler; + */ + byte_clk = dsim->bit_clk >> 3; + esc_prescaler = DIV_ROUND_UP_ULL(byte_clk, MAX_ESC_CLK_FREQ); + + clkctrl |= CLKCTRL_SET_ESCPRESCALER(esc_prescaler); + + dsim_write(dsim, clkctrl, DSIM_CLKCTRL); +} + +static void sec_mipi_dsim_set_standby(struct sec_mipi_dsim *dsim, bool standby) +{ + uint32_t mdresol = 0; + + mdresol = dsim_read(dsim, DSIM_MDRESOL); + + if (standby) + mdresol |= MDRESOL_MAINSTANDBY; + else + mdresol &= ~MDRESOL_MAINSTANDBY; + + dsim_write(dsim, mdresol, DSIM_MDRESOL); +} + +static void sec_mipi_dsim_disable_clkctrl(struct sec_mipi_dsim *dsim) +{ + uint32_t clkctrl; + + clkctrl = dsim_read(dsim, DSIM_CLKCTRL); + + clkctrl &= ~CLKCTRL_TXREQUESTHSCLK; + + clkctrl &= ~CLKCTRL_ESCCLKEN; + + clkctrl &= ~CLKCTRL_BYTECLKEN; + + dsim_write(dsim, clkctrl, DSIM_CLKCTRL); +} + +static void sec_mipi_dsim_disable_pll(struct sec_mipi_dsim *dsim) +{ + uint32_t pllctrl; + + pllctrl = dsim_read(dsim, DSIM_PLLCTRL); + + pllctrl &= ~PLLCTRL_PLLEN; + + dsim_write(dsim, pllctrl, DSIM_PLLCTRL); +} + +/* For now, dsim only support one device attached */ +static int sec_mipi_dsim_bridge_attach(struct mipi_dsi_bridge_driver *bridge_driver, + struct mipi_dsi_client_dev *dsi_dev) +{ + struct sec_mipi_dsim *dsim_host = (struct sec_mipi_dsim *)bridge_driver->driver_private; + + if (!dsi_dev->lanes || dsi_dev->lanes > dsim_host->pdata->max_data_lanes) { + printf("invalid data lanes number\n"); + return -EINVAL; + } + + if (!(dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO) || + !((dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) || + (dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE))) { + printf("unsupported dsi mode\n"); + return -EINVAL; + } + + if (dsi_dev->format != MIPI_DSI_FMT_RGB888 && + dsi_dev->format != MIPI_DSI_FMT_RGB565 && + dsi_dev->format != MIPI_DSI_FMT_RGB666 && + dsi_dev->format != MIPI_DSI_FMT_RGB666_PACKED) { + printf("unsupported pixel format: %#x\n", dsi_dev->format); + return -EINVAL; + } + + if (!dsi_dev->name) { + printf("panel_device name is NULL.\n"); + return -EFAULT; + } + + if (dsim_host->dsi_panel_drv) { + if (strcmp(dsi_dev->name, dsim_host->dsi_panel_drv->name)) { + printf("The panel device name %s is not for LCD driver %s\n", + dsi_dev->name, dsim_host->dsi_panel_drv->name); + return -EFAULT; + } + } + + dsim_host->dsi_panel_dev = dsi_dev; + + dsim_host->lanes = dsi_dev->lanes; + dsim_host->channel = dsi_dev->channel; + dsim_host->format = dsi_dev->format; + dsim_host->mode_flags = dsi_dev->mode_flags; + + return 0; +} + +static int sec_mipi_dsim_bridge_enable(struct mipi_dsi_bridge_driver *bridge_driver) +{ + int ret; + struct sec_mipi_dsim *dsim_host = (struct sec_mipi_dsim *)bridge_driver->driver_private; + + /* At this moment, the dsim bridge's preceding encoder has + * already been enabled. So the dsim can be configed here + */ + + /* config main display mode */ + sec_mipi_dsim_set_main_mode(dsim_host); + + /* config dsim dpi */ + sec_mipi_dsim_config_dpi(dsim_host); + + /* config dsim pll */ + ret = sec_mipi_dsim_config_pll(dsim_host); + if (ret) { + printf("dsim pll config failed: %d\n", ret); + return ret; + } + + /* config dphy timings */ + sec_mipi_dsim_config_dphy(dsim_host); + + /* config esc clock, byte clock and etc */ + sec_mipi_dsim_config_clkctrl(dsim_host); + + /* enable data transfer of dsim */ + sec_mipi_dsim_set_standby(dsim_host, true); + + /* Call panel driver's setup */ + if (dsim_host->dsi_panel_drv && dsim_host->dsi_panel_drv->dsi_client_setup) { + ret = dsim_host->dsi_panel_drv->dsi_client_setup(dsim_host->dsi_panel_dev); + if (ret < 0) { + printf("failed to init mipi lcd.\n"); + return ret; + } + } + + return 0; +} + +static int sec_mipi_dsim_bridge_disable(struct mipi_dsi_bridge_driver *bridge_driver) +{ + uint32_t intsrc; + struct sec_mipi_dsim *dsim_host = (struct sec_mipi_dsim *)bridge_driver->driver_private; + + /* disable data transfer of dsim */ + sec_mipi_dsim_set_standby(dsim_host, false); + + /* disable esc clock & byte clock */ + sec_mipi_dsim_disable_clkctrl(dsim_host); + + /* disable dsim pll */ + sec_mipi_dsim_disable_pll(dsim_host); + + /* Clear all intsrc */ + intsrc = dsim_read(dsim_host, DSIM_INTSRC); + dsim_write(dsim_host, intsrc, DSIM_INTSRC); + + return 0; +} + +static int sec_mipi_dsim_bridge_mode_set(struct mipi_dsi_bridge_driver *bridge_driver, + struct fb_videomode *fbmode) +{ + int bpp; + uint64_t pix_clk, bit_clk; + struct sec_mipi_dsim *dsim_host = (struct sec_mipi_dsim *)bridge_driver->driver_private; + + dsim_host->vmode = *fbmode; + + bpp = mipi_dsi_pixel_format_to_bpp(dsim_host->format); + if (bpp < 0) + return -EINVAL; + + pix_clk = PS2KHZ(fbmode->pixclock) * 1000; + bit_clk = DIV_ROUND_UP_ULL(pix_clk * bpp, dsim_host->lanes); + + if (bit_clk > dsim_host->pdata->max_data_rate) { + printf("request bit clk freq exceeds lane's maximum value\n"); + return -EINVAL; + } + + dsim_host->pix_clk = DIV_ROUND_UP_ULL(pix_clk, 1000); + dsim_host->bit_clk = DIV_ROUND_UP_ULL(bit_clk, 1000); + + if (dsim_host->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + /* TODO: add PMS calculate and check + * Only support '1080p@60Hz' for now, + * add other modes support later + */ + if (fbmode->pixclock == 6734) + dsim_host->pms = 0x4210; + else if (fbmode->pixclock == 33300) + dsim_host->pms = 0xA942; + else if (fbmode->pixclock == 16835) + dsim_host->pms = 0xA842; + else if (fbmode->pixclock == 24390) + dsim_host->pms = 0x13483; + } + + debug("%s: bitclk %llu pixclk %llu\n", __func__, dsim_host->bit_clk, dsim_host->pix_clk); + + return 0; +} + +/* Add a LCD panel driver, will search the panel device to bind with them */ +int sec_mipi_dsim_bridge_add_client_driver(struct mipi_dsi_bridge_driver *bridge_driver, + struct mipi_dsi_client_driver *panel_drv) +{ + struct sec_mipi_dsim *dsim_host = (struct sec_mipi_dsim *)bridge_driver->driver_private; + + 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 (dsim_host->dsi_panel_dev) { + if (strcmp(panel_drv->name, dsim_host->dsi_panel_dev->name)) { + printf("The panel driver name %s is not for LCD device %s\n", + panel_drv->name, dsim_host->dsi_panel_dev->name); + return -EFAULT; + } + } + + dsim_host->dsi_panel_drv = panel_drv; + + return 0; +} + +static int sec_mipi_dsim_bridge_pkt_write(struct mipi_dsi_bridge_driver *bridge_driver, + u8 data_type, const u8 *buf, int len) +{ + struct sec_mipi_dsim *dsim_host = (struct sec_mipi_dsim *)bridge_driver->driver_private; + +#ifdef DEBUG + int i = 0; + printf("sec_mipi_dsim_bridge_pkt_write, data_type %u, len %d buf: \n", data_type, len); + + if (len == 0) + len = 2; + + for (i; i < len; i++) { + printf("0x%.2x ", buf[i]); + } + printf("\n"); +#endif + + return sec_mipi_dsim_pkt_write(dsim_host, data_type, buf, len); +} + +struct mipi_dsi_bridge_driver imx_sec_dsim_driver = { + .attach = sec_mipi_dsim_bridge_attach, + .enable = sec_mipi_dsim_bridge_enable, + .disable = sec_mipi_dsim_bridge_disable, + .mode_set = sec_mipi_dsim_bridge_mode_set, + .pkt_write = sec_mipi_dsim_bridge_pkt_write, + .add_client_driver = sec_mipi_dsim_bridge_add_client_driver, + .name = DRIVER_NAME, +}; + +int sec_mipi_dsim_setup(const struct sec_mipi_dsim_plat_data *plat_data) +{ + struct sec_mipi_dsim *dsim_host; + + if (!plat_data) { + printf("Invalid platform data \n"); + return -EINVAL; + } + + dsim_host = (struct sec_mipi_dsim *)malloc(sizeof(struct sec_mipi_dsim)); + if (!dsim_host) { + printf("failed to allocate sec_mipi_dsim object.\n"); + return -ENOMEM; + } + + dsim_host->base = (void __iomem *)plat_data->reg_base; + dsim_host->disp_mix_gpr_base = (void __iomem *)plat_data->gpr_base; + dsim_host->pdata = plat_data; + dsim_host->dsi_panel_drv = NULL; + dsim_host->dsi_panel_dev = NULL; + +#ifndef CONFIG_IMX8MP + /* Pull dsim out of reset */ + disp_mix_dsim_soft_reset_release(dsim_host, true); + disp_mix_dsim_clks_enable(dsim_host, true); + disp_mix_dsim_lanes_reset(dsim_host, false); +#endif + + imx_sec_dsim_driver.driver_private = dsim_host; + return imx_mipi_dsi_bridge_register_driver(&imx_sec_dsim_driver); +} diff --git a/include/configs/seco_imx8mm_myon2.h b/include/configs/seco_imx8mm_myon2.h index e8cb87cbe4bd52b8ac2d4dd4be6cdffd5c419b9f..c8a08f8ba5808042a3f7bbe7c166ff7b5fd5f27d 100644 --- a/include/configs/seco_imx8mm_myon2.h +++ b/include/configs/seco_imx8mm_myon2.h @@ -223,17 +223,20 @@ #define CONFIG_MXC_USB_PORTSC (PORT_PTS_UTMI | PORT_PTS_PTW) -#ifdef CONFIG_DM_VIDEO +#ifdef CONFIG_VIDEO #define CONFIG_VIDEO_MXS #define CONFIG_VIDEO_LOGO #define CONFIG_SPLASH_SCREEN #define CONFIG_SPLASH_SCREEN_ALIGN +#define CONFIG_SPLASH_SOURCE #define CONFIG_CMD_BMP #define CONFIG_BMP_16BPP #define CONFIG_BMP_24BPP #define CONFIG_BMP_32BPP #define CONFIG_VIDEO_BMP_RLE8 #define CONFIG_VIDEO_BMP_LOGO +#define CONFIG_IMX_VIDEO_SKIP +#define SPLASHIMAGE_ADDR 0x50000000 #endif /* ____________________________________________________________________________ diff --git a/include/imx_mipi_dsi_bridge.h b/include/imx_mipi_dsi_bridge.h new file mode 100644 index 0000000000000000000000000000000000000000..c03cef4bd7ec2580a0715a9079eb0ec591005b18 --- /dev/null +++ b/include/imx_mipi_dsi_bridge.h @@ -0,0 +1,98 @@ +/* + * Copyright 2018 NXP + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef __IMX_MIPI_DSIM_BRIDGE_H__ +#define __IMX_MIPI_DSIM_BRIDGE_H__ + +#include <linux/fb.h> + +/* video mode */ +#define MIPI_DSI_MODE_VIDEO BIT(0) +/* video burst mode */ +#define MIPI_DSI_MODE_VIDEO_BURST BIT(1) +/* video pulse mode */ +#define MIPI_DSI_MODE_VIDEO_SYNC_PULSE BIT(2) +/* enable auto vertical count mode */ +#define MIPI_DSI_MODE_VIDEO_AUTO_VERT BIT(3) +/* enable hsync-end packets in vsync-pulse and v-porch area */ +#define MIPI_DSI_MODE_VIDEO_HSE BIT(4) +/* disable hfront-porch area */ +#define MIPI_DSI_MODE_VIDEO_HFP BIT(5) +/* disable hback-porch area */ +#define MIPI_DSI_MODE_VIDEO_HBP BIT(6) +/* disable hsync-active area */ +#define MIPI_DSI_MODE_VIDEO_HSA BIT(7) +/* flush display FIFO on vsync pulse */ +#define MIPI_DSI_MODE_VSYNC_FLUSH BIT(8) +/* disable EoT packets in HS mode */ +#define MIPI_DSI_MODE_EOT_PACKET BIT(9) +/* device supports non-continuous clock behavior (DSI spec 5.6.1) */ +#define MIPI_DSI_CLOCK_NON_CONTINUOUS BIT(10) +/* transmit data in low power */ +#define MIPI_DSI_MODE_LPM BIT(11) + +#define DSI_CMD_BUF_MAXSIZE (128) + +enum mipi_dsi_pixel_format { + MIPI_DSI_FMT_RGB888, + MIPI_DSI_FMT_RGB666, + MIPI_DSI_FMT_RGB666_PACKED, + MIPI_DSI_FMT_RGB565, +}; + +struct mipi_dsi_client_dev { + unsigned int channel; + unsigned int lanes; + enum mipi_dsi_pixel_format format; + unsigned long mode_flags; + const char *name; +}; + +struct mipi_dsi_client_driver { + int (*dsi_client_setup)(struct mipi_dsi_client_dev *panel_dev); + const char *name; +}; + +struct mipi_dsi_bridge_driver { + int (*attach)(struct mipi_dsi_bridge_driver *bridge_driver, + struct mipi_dsi_client_dev *dsi_dev); + int (*enable)(struct mipi_dsi_bridge_driver *bridge_driver); + int (*disable)(struct mipi_dsi_bridge_driver *bridge_driver); + int (*mode_set)(struct mipi_dsi_bridge_driver *bridge_driver, struct fb_videomode *pvmode); + int (*pkt_write)(struct mipi_dsi_bridge_driver *bridge_driver, u8 data_type, const u8 *buf, + int len); + int (*add_client_driver)(struct mipi_dsi_bridge_driver *bridge_driver, + struct mipi_dsi_client_driver *client_driver); + const char *name; + void *driver_private; +}; + +static inline int mipi_dsi_pixel_format_to_bpp(enum mipi_dsi_pixel_format fmt) +{ + switch (fmt) { + case MIPI_DSI_FMT_RGB888: + case MIPI_DSI_FMT_RGB666: + return 24; + + case MIPI_DSI_FMT_RGB666_PACKED: + return 18; + + case MIPI_DSI_FMT_RGB565: + return 16; + } + + return -EINVAL; +} + +int imx_mipi_dsi_bridge_attach(struct mipi_dsi_client_dev *dsi_dev); +int imx_mipi_dsi_bridge_mode_set(struct fb_videomode *pvmode); +int imx_mipi_dsi_bridge_enable(void); +int imx_mipi_dsi_bridge_disable(void); +int imx_mipi_dsi_bridge_pkt_write(u8 data_type, const u8 *buf, int len); +int imx_mipi_dsi_bridge_add_client_driver(struct mipi_dsi_client_driver *client_driver); +int imx_mipi_dsi_bridge_register_driver(struct mipi_dsi_bridge_driver *driver); + +#endif /* __IMX_MIPI_DSIM_BRIDGE_H__ */ diff --git a/include/sec_mipi_dsim.h b/include/sec_mipi_dsim.h new file mode 100644 index 0000000000000000000000000000000000000000..3716fa5ce99d1473b61cc245c899b014c97b7b4f --- /dev/null +++ b/include/sec_mipi_dsim.h @@ -0,0 +1,20 @@ +/* + * Copyright 2018 NXP + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef __SEC_MIPI_DSIM_H__ +#define __SEC_MIPI_DSIM_H__ + +struct sec_mipi_dsim_plat_data { + uint32_t version; + uint32_t max_data_lanes; + uint64_t max_data_rate; + ulong reg_base; + ulong gpr_base; +}; + +int sec_mipi_dsim_setup(const struct sec_mipi_dsim_plat_data *plat_data); + +#endif /* __SEC_MIPI_DSIM_H__ */