ref: 556618eb07d1ac6e2c629a60acd53c45d53261be
dir: /sys/src/9/imx8/lcd.c/
#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "../port/i2c.h" #define Image IMAGE #include <draw.h> #include <memdraw.h> #include <cursor.h> #include "screen.h" /* system reset controller registers */ enum { SRC_MIPIPHY_RCR = 0x28/4, RCR_MIPI_DSI_PCLK_RESET_N = 1<<5, RCR_MIPI_DSI_ESC_RESET_N = 1<<4, RCR_MIPI_DSI_DPI_RESET_N = 1<<3, RCR_MIPI_DSI_RESET_N = 1<<2, RCR_MIPI_DSI_RESET_BYTE_N = 1<<1, SRC_DISP_RCR = 0x34/4, }; /* pwm controller registers */ enum { Pwmsrcclk = 25*Mhz, PWMCR = 0x00/4, CR_FWM_1 = 0<<26, CR_FWM_2 = 1<<26, CR_FWM_3 = 2<<26, CR_FWM_4 = 3<<26, CR_STOPEN = 1<<25, CR_DOZEN = 1<<24, CR_WAITEN = 1<<23, CR_DBGEN = 1<<22, CR_BCTR = 1<<21, CR_HCTR = 1<<20, CR_POUTC = 1<<18, CR_CLKSRC_OFF = 0<<16, CR_CLKSRC_IPG = 1<<16, CR_CLKSRC_HIGHFREQ = 2<<16, CR_CLKSRC_32K = 3<<16, CR_PRESCALER_SHIFT = 4, CR_SWR = 1<<3, CR_REPEAT_1 = 0<<1, CR_REPEAT_2 = 1<<1, CR_REPEAT_4 = 2<<1, CR_REPEAT_8 = 3<<1, CR_EN = 1<<0, PWMSR = 0x04/4, PWMIR = 0x08/4, PWMSAR = 0x0C/4, SAR_MASK = 0xFFFF, PWMPR = 0x10/4, PR_MASK = 0xFFFF, PWMCNR = 0x14/4, CNR_MASK = 0xFFFF, }; /* dphy registers */ enum { DPHY_PD_PHY = 0x0/4, DPHY_M_PRG_HS_PREPARE = 0x4/4, DPHY_MC_PRG_HS_PREPARE = 0x8/4, DPHY_M_PRG_HS_ZERO = 0xc/4, DPHY_MC_PRG_HS_ZERO = 0x10/4, DPHY_M_PRG_HS_TRAIL = 0x14/4, DPHY_MC_PRG_HS_TRAIL = 0x18/4, DPHY_PD_PLL = 0x1c/4, DPHY_TST = 0x20/4, DPHY_CN = 0x24/4, DPHY_CM = 0x28/4, DPHY_CO = 0x2c/4, DPHY_LOCK = 0x30/4, DPHY_LOCK_BYP = 0x34/4, DPHY_RTERM_SEL = 0x38/4, DPHY_AUTO_PD_EN = 0x3c/4, DPHY_RXLPRP = 0x40/4, DPHY_RXCDR = 0x44/4, DPHY_RXHS_SETTLE = 0x48/4, /* undocumented */ }; /* dsi-host registers */ enum { DSI_HOST_CFG_NUM_LANES = 0x0/4, DSI_HOST_CFG_NONCONTINUOUS_CLK = 0x4/4, DSI_HOST_CFG_AUTOINSERT_EOTP = 0x14/4, DSI_HOST_CFG_T_PRE = 0x8/4, DSI_HOST_CFG_T_POST = 0xc/4, DSI_HOST_CFG_TX_GAP = 0x10/4, DSI_HOST_CFG_EXTRA_CMDS_AFTER_EOTP = 0x18/4, DSI_HOST_CFG_HTX_TO_COUNT = 0x1c/4, DSI_HOST_CFG_LRX_H_TO_COUNT = 0x20/4, DSI_HOST_CFG_BTA_H_TO_COUNT = 0x24/4, DSI_HOST_CFG_TWAKEUP = 0x28/4, DSI_HOST_CFG_DPI_INTERFACE_COLOR_CODING = 0x208/4, DSI_HOST_CFG_DPI_PIXEL_FORMAT = 0x20c/4, DSI_HOST_CFG_DPI_VSYNC_POLARITY = 0x210/4, DSI_HOST_CFG_DPI_HSYNC_POLARITY = 0x214/4, DSI_HOST_CFG_DPI_VIDEO_MODE = 0x218/4, DSI_HOST_CFG_DPI_PIXEL_FIFO_SEND_LEVEL = 0x204/4, DSI_HOST_CFG_DPI_HFP = 0x21c/4, DSI_HOST_CFG_DPI_HBP = 0x220/4, DSI_HOST_CFG_DPI_HSA = 0x224/4, DSI_HOST_CFG_DPI_ENA_BLE_MULT_PKTS = 0x228/4, DSI_HOST_CFG_DPI_BLLP_MODE = 0x234/4, DSI_HOST_CFG_DPI_USE_NULL_PKT_BLLP = 0x238/4, DSI_HOST_CFG_DPI_VC = 0x240/4, DSI_HOST_CFG_DPI_PIXEL_PAYLOAD_SIZE = 0x200/4, DSI_HOST_CFG_DPI_VACTIVE = 0x23c/4, DSI_HOST_CFG_DPI_VBP = 0x22c/4, DSI_HOST_CFG_DPI_VFP = 0x230/4, }; /* lcdif registers */ enum { LCDIF_CTRL = 0x00/4, LCDIF_CTRL_SET = 0x04/4, LCDIF_CTRL_CLR = 0x08/4, LCDIF_CTRL_TOG = 0x0C/4, CTRL_SFTRST = 1<<31, CTRL_CLKGATE = 1<<30, CTRL_YCBCR422_INPUT = 1<<29, CTRL_READ_WEITEB = 1<<28, CTRL_WAIT_FOR_VSYNC_EDGE = 1<<27, CTRL_DATA_SHIFT_DIR = 1<<26, CTRL_SHIFT_NUM_BITS = 0x1F<<21, CTRL_DVI_MODE = 1<<20, CTRL_BYPASS_COUNT = 1<<19, CTRL_VSYNC_MODE = 1<<18, CTRL_DOTCLK_MODE = 1<<17, CTRL_DATA_SELECT = 1<<16, CTRL_INPUT_DATA_NO_SWAP = 0<<14, CTRL_INPUT_DATA_LITTLE_ENDIAN = 0<<14, CTRL_INPUT_DATA_BIG_ENDIAB = 1<<14, CTRL_INPUT_DATA_SWAP_ALL_BYTES = 1<<14, CTRL_INPUT_DATA_HWD_SWAP = 2<<14, CTRL_INPUT_DATA_HWD_BYTE_SWAP = 3<<14, CTRL_CSC_DATA_NO_SWAP = 0<<12, CTRL_CSC_DATA_LITTLE_ENDIAN = 0<<12, CTRL_CSC_DATA_BIG_ENDIAB = 1<<12, CTRL_CSC_DATA_SWAP_ALL_BYTES = 1<<12, CTRL_CSC_DATA_HWD_SWAP = 2<<12, CTRL_CSC_DATA_HWD_BYTE_SWAP = 3<<12, CTRL_LCD_DATABUS_WIDTH_16_BIT = 0<<10, CTRL_LCD_DATABUS_WIDTH_8_BIT = 1<<10, CTRL_LCD_DATABUS_WIDTH_18_BIT = 2<<10, CTRL_LCD_DATABUS_WIDTH_24_BIT = 3<<10, CTRL_WORD_LENGTH_16_BIT = 0<<8, CTRL_WORD_LENGTH_8_BIT = 1<<8, CTRL_WORD_LENGTH_18_BIT = 2<<8, CTRL_WORD_LENGTH_24_BIT = 3<<8, CTRL_RGB_TO_YCBCR422_CSC = 1<<7, CTRL_MASTER = 1<<5, CTRL_DATA_FORMAT_16_BIT = 1<<3, CTRL_DATA_FORMAT_18_BIT = 1<<2, CTRL_DATA_FORMAT_24_BIT = 1<<1, CTRL_RUN = 1<<0, LCDIF_CTRL1 = 0x10/4, LCDIF_CTRL1_SET = 0x14/4, LCDIF_CTRL1_CLR = 0x18/4, LCDIF_CTRL1_TOG = 0x1C/4, CTRL1_COMBINE_MPU_WR_STRB = 1<<27, CTRL1_BM_ERROR_IRQ_EN = 1<<26, CTRL1_BM_ERROR_IRQ = 1<<25, CTRL1_RECOVER_ON_UNDERFLOW = 1<<24, CTRL1_INTERLACE_FIELDS = 1<<23, CTRL1_START_INTERLACE_FROM_SECOND_FIELD = 1<<22, CTRL1_FIFO_CLEAR = 1<<21, CTRL1_IRQ_ON_ALTERNATE_FIELDS = 1<<20, CTRL1_BYTE_PACKING_FORMAT = 0xF<<16, CTRL1_OVERFLOW_IRQ_EN = 1<<15, CTRL1_UNDERFLOW_IRQ_EN = 1<<14, CTRL1_CUR_FRAME_DONE_IRQ_EN = 1<<13, CTRL1_VSYNC_EDGE_IRQ_EN = 1<<12, CTRL1_OVERFLOW_IRQ = 1<<11, CTRL1_UNDERFLOW_IRQ = 1<<10, CTRL1_CUR_FRAME_DONE_IRQ = 1<<9, CTRL1_VSYNC_EDGE_IRQ = 1<<8, CTRL1_BUSY_ENABLE = 1<<2, CTRL1_MODE86 = 1<<1, CTRL1_RESET = 1<<0, LCDIF_CTRL2 = 0x20/4, LCDIF_CTRL2_SET = 0x24/4, LCDIF_CTRL2_CLR = 0x28/4, LCDIF_CTRL2_TOG = 0x2C/4, CTRL2_OUTSTANDING_REQS_REQ_1 = 0<<21, CTRL2_OUTSTANDING_REQS_REQ_2 = 1<<21, CTRL2_OUTSTANDING_REQS_REQ_4 = 2<<21, CTRL2_OUTSTANDING_REQS_REQ_8 = 3<<21, CTRL2_OUTSTANDING_REQS_REQ_16 = 4<<21, CTRL2_BURST_LEN_8 = 1<<20, CTRL2_ODD_LINE_PATTERN_RGB = 0<<16, CTRL2_ODD_LINE_PATTERN_RBG = 1<<16, CTRL2_ODD_LINE_PATTERN_GBR = 2<<16, CTRL2_ODD_LINE_PATTERN_GRB = 3<<16, CTRL2_ODD_LINE_PATTERN_BRG = 4<<16, CTRL2_ODD_LINE_PATTERN_BGR = 5<<16, CTRL2_EVEN_LINE_PATTERN_RGB = 0<<12, CTRL2_EVEN_LINE_PATTERN_RBG = 1<<12, CTRL2_EVEN_LINE_PATTERN_GBR = 2<<12, CTRL2_EVEN_LINE_PATTERN_GRB = 3<<12, CTRL2_EVEN_LINE_PATTERN_BRG = 4<<12, CTRL2_EVEN_LINE_PATTERN_BGR = 5<<12, CTRL2_READ_PACK_DIR = 1<<10, CTRL2_READ_MODE_OUTPUT_IN_RGB_FORMAT = 1<<9, CTRL2_READ_MODE_6_BIT_INPUT = 1<<8, CTRL2_READ_MODE_NUM_PACKED_SUBWORDS = 7<<4, CTRL2_INITIAL_DUMMY_READS = 7<<1, LCDIF_TRANSFER_COUNT= 0x30/4, TRANSFER_COUNT_V_COUNT = 0xFFFF<<16, TRANSFER_COUNT_H_COUNT = 0xFFFF, LCDIF_CUR_BUF = 0x40/4, LCDIF_NEXT_BUF = 0x50/4, LCDIF_TIMING = 0x60/4, TIMING_CMD_HOLD = 0xFF<<24, TIMING_CMD_SETUP = 0xFF<<16, TIMING_DATA_HOLD = 0xFF<<8, TIMING_DATA_SETUP = 0xFF<<0, LCDIF_VDCTRL0 = 0x70/4, VDCTRL0_VSYNC_OEB = 1<<29, VDCTRL0_ENABLE_PRESENT = 1<<28, VDCTRL0_VSYNC_POL = 1<<27, VDCTRL0_HSYNC_POL = 1<<26, VDCTRL0_DOTCLK_POL = 1<<25, VDCTRL0_ENABLE_POL = 1<<24, VDCTRL0_VSYNC_PERIOD_UNIT = 1<<21, VDCTRL0_VSYNC_PULSE_WIDTH_UNIT = 1<<20, VDCTRL0_HALF_LINE = 1<<19, VDCTRL0_HALF_LINE_MODE = 1<<18, VDCTRL0_VSYNC_PULSE_WIDTH = 0x3FFFF, LCDIF_VDCTRL1 = 0x80/4, VDCTRL1_VSYNC_PERIOD = 0xFFFFFFFF, LCDIF_VDCTRL2 = 0x90/4, VDCTRL2_HSYNC_PERIOD = 0x3FFFF, VDCTRL2_HSYNC_PULSE_WIDTH = 0x3FFF<<18, LCDIF_VDCTRL3 = 0xA0/4, VDCTRL3_VERTICAL_WAIT_CNT = 0xFFFF, VDCTRL3_HORIZONTAL_WAIT_CNT = 0xFFF<<16, VDCTRL3_VSYNC_ONLY = 1<<28, VDCTRL3_MUX_SYNC_SIGNALS = 1<<29, LCDIF_VDCTRL4 = 0xB0/4, VDCTRL4_DOTCLK_H_VALID_DATA_CNT = 0x3FFFF, VDCTRL4_SYNC_SIGNALS_ON = 1<<18, VDCTRL4_DOTCLK_DLY_2NS = 0<<29, VDCTRL4_DOTCLK_DLY_4NS = 1<<29, VDCTRL4_DOTCLK_DLY_6NS = 2<<29, VDCTRL4_DOTCLK_DLY_8NS = 3<<29, LCDIF_DATA = 0x180/4, LCDIF_STAT = 0x1B0/4, LCDIF_AS_CTRL = 0x210/4, }; struct video_mode { int pixclk; int hactive; int vactive; int hblank; int vblank; int hso; int vso; int hspw; int vspw; char vsync_pol; char hsync_pol; }; struct dsi_cfg { int lanes; int ref_clk; int hs_clk; int ui_ps; int byte_clk; int byte_t_ps; int tx_esc_clk; int tx_esc_t_ps; int rx_esc_clk; int clk_pre_ps; int clk_prepare_ps; int clk_zero_ps; int hs_prepare_ps; int hs_zero_ps; int hs_trail_ps; int hs_exit_ps; int lpx_ps; vlong wakeup_ps; }; /* base addresses, VIRTIO is at 0x30000000 physical */ static u32int *pwm2 = (u32int*)(VIRTIO + 0x670000); static u32int *resetc= (u32int*)(VIRTIO + 0x390000); static u32int *dsi = (u32int*)(VIRTIO + 0xA00000); static u32int *dphy = (u32int*)(VIRTIO + 0xA00300); static u32int *lcdif = (u32int*)(VIRTIO + 0x320000); static I2Cdev *bridge; static struct { int blank; }lcdifirq; /* shift and mask */ static u32int sm(u32int v, u32int m) { int s; if(m == 0) return 0; s = 0; while((m & 1) == 0){ m >>= 1; s++; } return (v & m) << s; } static u32int rr(u32int *base, int reg) { u32int val = base[reg]; // iprint("%#p+%#.4x -> %#.8ux\n", PADDR(base), reg*4, val); return val; } static void wr(u32int *base, int reg, u32int val) { // iprint("%#p+%#.4x <- %#.8ux\n", PADDR(base), reg*4, val); base[reg] = val; } static void mr(u32int *base, int reg, u32int val, u32int mask) { wr(base, reg, (rr(base, reg) & ~mask) | (val & mask)); } static void dsiparams(struct dsi_cfg *cfg, int lanes, int hs_clk, int ref_clk, int tx_esc_clk, int rx_esc_clk) { cfg->lanes = lanes; cfg->ref_clk = ref_clk; cfg->hs_clk = hs_clk; cfg->ui_ps = (1000000000000LL + (cfg->hs_clk-1)) / cfg->hs_clk; cfg->byte_clk = cfg->hs_clk / 8; cfg->byte_t_ps = cfg->ui_ps * 8; cfg->tx_esc_clk = tx_esc_clk; cfg->tx_esc_t_ps = (1000000000000LL + (cfg->tx_esc_clk-1)) / cfg->tx_esc_clk; cfg->rx_esc_clk = rx_esc_clk; /* min 8*ui */ cfg->clk_pre_ps = 8*cfg->ui_ps; /* min 38ns, max 95ns */ cfg->clk_prepare_ps = 38*1000; /* clk_prepare + clk_zero >= 300ns */ cfg->clk_zero_ps = 300*1000 - cfg->clk_prepare_ps; /* min 40ns + 4*ui, max 85ns + 6*ui */ cfg->hs_prepare_ps = 40*1000 + 4*cfg->ui_ps; /* hs_prepae + hs_zero >= 145ns + 10*ui */ cfg->hs_zero_ps = (145*1000 + 10*cfg->ui_ps) - cfg->hs_prepare_ps; /* min max(n*8*ui, 60ns + n*4*ui); n=1 */ cfg->hs_trail_ps = 60*1000 + 1*4*cfg->ui_ps; if(cfg->hs_trail_ps < 1*8*cfg->ui_ps) cfg->hs_trail_ps = 1*8*cfg->ui_ps; /* min 100ns */ cfg->hs_exit_ps = 100*1000; /* min 50ns */ cfg->lpx_ps = 50*1000; /* min 1ms */ cfg->wakeup_ps = 1000000000000LL; } static void lcdifreset(void) { wr(lcdif, LCDIF_CTRL_SET, CTRL_SFTRST); delay(1); wr(lcdif, LCDIF_CTRL_SET, CTRL_CLKGATE); } static void lcdifinit(struct video_mode *mode, u32int framebuffer) { wr(lcdif, LCDIF_CTRL_CLR, CTRL_SFTRST); while(rr(lcdif, LCDIF_CTRL) & CTRL_SFTRST) ; wr(lcdif, LCDIF_CTRL_CLR, CTRL_CLKGATE); while(rr(lcdif, LCDIF_CTRL) & (CTRL_SFTRST|CTRL_CLKGATE)) ; wr(lcdif, LCDIF_CTRL1_SET, CTRL1_FIFO_CLEAR); wr(lcdif, LCDIF_AS_CTRL, 0); /* enable underflow recovery to fix image shift */ wr(lcdif, LCDIF_CTRL1, sm(7, CTRL1_BYTE_PACKING_FORMAT) | CTRL1_RECOVER_ON_UNDERFLOW | CTRL1_CUR_FRAME_DONE_IRQ_EN); wr(lcdif, LCDIF_CTRL, CTRL_BYPASS_COUNT | CTRL_MASTER | CTRL_LCD_DATABUS_WIDTH_24_BIT | CTRL_WORD_LENGTH_24_BIT); wr(lcdif, LCDIF_TRANSFER_COUNT, sm(mode->vactive, TRANSFER_COUNT_V_COUNT) | sm(mode->hactive, TRANSFER_COUNT_H_COUNT)); wr(lcdif, LCDIF_VDCTRL0, VDCTRL0_ENABLE_PRESENT | VDCTRL0_VSYNC_POL | VDCTRL0_HSYNC_POL | VDCTRL0_VSYNC_PERIOD_UNIT | VDCTRL0_VSYNC_PULSE_WIDTH_UNIT | sm(mode->vspw, VDCTRL0_VSYNC_PULSE_WIDTH)); wr(lcdif, LCDIF_VDCTRL1, sm(mode->vactive + mode->vblank, VDCTRL1_VSYNC_PERIOD)); wr(lcdif, LCDIF_VDCTRL2, sm(mode->hactive + mode->hblank, VDCTRL2_HSYNC_PERIOD) | sm(mode->hspw, VDCTRL2_HSYNC_PULSE_WIDTH)); wr(lcdif, LCDIF_VDCTRL3, sm(mode->vblank - mode->vso, VDCTRL3_VERTICAL_WAIT_CNT) | sm(mode->hblank - mode->hso, VDCTRL3_HORIZONTAL_WAIT_CNT)); wr(lcdif, LCDIF_VDCTRL4, sm(mode->hactive, VDCTRL4_DOTCLK_H_VALID_DATA_CNT)); wr(lcdif, LCDIF_CUR_BUF, framebuffer); wr(lcdif, LCDIF_NEXT_BUF, framebuffer); wr(lcdif, LCDIF_CTRL_SET, CTRL_DOTCLK_MODE); mr(lcdif, LCDIF_VDCTRL4, VDCTRL4_SYNC_SIGNALS_ON, VDCTRL4_SYNC_SIGNALS_ON); wr(lcdif, LCDIF_CTRL_SET, CTRL_RUN); } static void bridgeinit(struct video_mode *mode, struct dsi_cfg *cfg) { int n; // soft reset i2cwritebyte(bridge, 0x09, 1); while(i2creadbyte(bridge, 0x09) & 1) ; // clock derived from dsi clock switch(cfg->hs_clk/2000000){ case 384: default: n = 1 << 1; break; case 416: n = 2 << 1; break; case 468: n = 0 << 1; break; case 486: n = 3 << 1; break; case 461: n = 4 << 1; break; } i2cwritebyte(bridge, 0x0a, n); // single channel A n = 1<<5 | (cfg->lanes-4)<<3 | 3<<1; i2cwritebyte(bridge, 0x10, n); // Enhanced framing and ASSR i2cwritebyte(bridge, 0x5a, 0x05); // 2 DP lanes w/o SSC i2cwritebyte(bridge, 0x93, 0x20); // 2.7Gbps DP data rate i2cwritebyte(bridge, 0x94, 0x80); // Enable PLL and confirm PLL is locked i2cwritebyte(bridge, 0x0d, 0x01); // wait for PLL to lock while((i2creadbyte(bridge, 0x0a) & 0x80) == 0) ; // Enable ASSR on display i2cwritebyte(bridge, 0x64, 0x01); i2cwritebyte(bridge, 0x75, 0x01); i2cwritebyte(bridge, 0x76, 0x0a); i2cwritebyte(bridge, 0x77, 0x01); i2cwritebyte(bridge, 0x78, 0x81); // Train link and confirm trained i2cwritebyte(bridge, 0x96, 0x0a); while(i2creadbyte(bridge, 0x96) != 1) ; // video timings i2cwritebyte(bridge, 0x20, mode->hactive & 0xFF); i2cwritebyte(bridge, 0x21, mode->hactive >> 8); i2cwritebyte(bridge, 0x24, mode->vactive & 0xFF); i2cwritebyte(bridge, 0x25, mode->vactive >> 8); i2cwritebyte(bridge, 0x2c, mode->hspw); i2cwritebyte(bridge, 0x2d, mode->hspw>>8 | (mode->hsync_pol=='-')<<7); i2cwritebyte(bridge, 0x30, mode->vspw); i2cwritebyte(bridge, 0x31, mode->vspw>>8 | (mode->vsync_pol=='-')<<7); i2cwritebyte(bridge, 0x34, mode->hblank - mode->hspw - mode->hso); i2cwritebyte(bridge, 0x36, mode->vblank - mode->vspw - mode->vso); i2cwritebyte(bridge, 0x38, mode->hso); i2cwritebyte(bridge, 0x3a, mode->vso); // Enable video stream, ASSR, enhanced framing i2cwritebyte(bridge, 0x5a, 0x0d); } static char* parseedid128(struct video_mode *mode, uchar edid[128]) { static uchar magic[8] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }; uchar *p, sum; int i; if(memcmp(edid, magic, 8) != 0) return "bad edid magic"; sum = 0; for(i=0; i<128; i++) sum += edid[i]; if(sum != 0) return "bad edid checksum"; /* * Detailed Timings */ p = edid+8+10+2+5+10+3+16; for(i=0; i<4; i++, p+=18){ if((p[0]|p[1])==0) continue; memset(mode, 0, sizeof(*mode)); mode->pixclk = ((p[1]<<8) | p[0]) * 10000; mode->hactive = ((p[4] & 0xF0)<<4) | p[2]; /* horizontal active */ mode->hblank = ((p[4] & 0x0F)<<8) | p[3]; /* horizontal blanking */ mode->vactive = ((p[7] & 0xF0)<<4) | p[5]; /* vertical active */ mode->vblank = ((p[7] & 0x0F)<<8) | p[6]; /* vertical blanking */ mode->hso = ((p[11] & 0xC0)<<2) | p[8]; /* horizontal sync offset */ mode->hspw = ((p[11] & 0x30)<<4) | p[9]; /* horizontal sync pulse width */ mode->vso = ((p[11] & 0x0C)<<2) | ((p[10] & 0xF0)>>4); /* vertical sync offset */ mode->vspw = ((p[11] & 0x03)<<4) | (p[10] & 0x0F); /* vertical sync pulse width */ switch((p[17] & 0x18)>>3) { case 3: /* digital separate sync signal; the norm */ mode->vsync_pol = (p[17] & 0x04) ? '+' : '-'; mode->hsync_pol = (p[17] & 0x02) ? '+' : '-'; break; } return nil; } return "no edid timings available"; } static char* getmode(struct video_mode *mode) { static uchar edid[128]; static I2Cdev aux; aux.bus = bridge->bus; aux.addr = 0x50; aux.subaddr = 1; aux.size = sizeof(edid); /* enable passthru mode for address 0x50 (EDID) */ i2cwritebyte(bridge, 0x60, aux.addr<<1 | 1); addi2cdev(&aux); if(i2crecv(&aux, edid, sizeof(edid), 0) != sizeof(edid)) return "i2crecv failed to get edid bytes"; return parseedid128(mode, edid); } static void dphyinit(struct dsi_cfg *cfg) { int n; /* powerdown */ wr(dphy, DPHY_PD_PLL, 1); wr(dphy, DPHY_PD_PHY, 1); /* magic */ wr(dphy, DPHY_LOCK_BYP, 0); wr(dphy, DPHY_RTERM_SEL, 1); wr(dphy, DPHY_AUTO_PD_EN, 0); wr(dphy, DPHY_RXLPRP, 2); wr(dphy, DPHY_RXCDR, 2); wr(dphy, DPHY_TST, 0x25); /* hs timings */ n = (2*cfg->hs_prepare_ps - cfg->tx_esc_t_ps) / cfg->tx_esc_t_ps; if(n < 0) n = 0; else if(n > 3) n = 3; wr(dphy, DPHY_M_PRG_HS_PREPARE, n); n = (2*cfg->clk_prepare_ps - cfg->tx_esc_t_ps) / cfg->tx_esc_t_ps; if(n < 0) n = 0; else if(n > 1) n = 1; wr(dphy, DPHY_MC_PRG_HS_PREPARE, n); n = ((cfg->hs_zero_ps + (cfg->byte_t_ps-1)) / cfg->byte_t_ps) - 6; if(n < 1) n = 1; wr(dphy, DPHY_M_PRG_HS_ZERO, n); n = ((cfg->clk_zero_ps + (cfg->byte_t_ps-1)) / cfg->byte_t_ps) - 3; if(n < 1) n = 1; wr(dphy, DPHY_MC_PRG_HS_ZERO, n); n = (cfg->hs_trail_ps + (cfg->byte_t_ps-1)) / cfg->byte_t_ps; if(n < 1) n = 1; else if(n > 15) n = 15; wr(dphy, DPHY_M_PRG_HS_TRAIL, n); wr(dphy, DPHY_MC_PRG_HS_TRAIL, n); if(cfg->hs_clk < 80*Mhz) n = 0xD; else if(cfg->hs_clk < 90*Mhz) n = 0xC; else if(cfg->hs_clk < 125*Mhz) n = 0xB; else if(cfg->hs_clk < 150*Mhz) n = 0xA; else if(cfg->hs_clk < 225*Mhz) n = 0x9; else if(cfg->hs_clk < 500*Mhz) n = 0x8; else n = 0x7; wr(dphy, DPHY_RXHS_SETTLE, n); /* hs_clk = ref_clk * (CM / (CN*CO)); just set CN=CO=1 */ n = (cfg->hs_clk + cfg->ref_clk-1) / cfg->ref_clk; /* strange encoding for CM */ if(n < 32) n = 0xE0 | (n - 16); else if(n < 64) n = 0xC0 | (n - 32); else if(n < 128) n = 0x80 | (n - 64); else n = n - 128; wr(dphy, DPHY_CM, n); wr(dphy, DPHY_CN, 0x1F); /* CN==1 */ wr(dphy, DPHY_CO, 0x00); /* CO==1 */ } static void dphypowerup(void) { wr(dphy, DPHY_PD_PLL, 0); while((rr(dphy, DPHY_LOCK) & 1) == 0) ; wr(dphy, DPHY_PD_PHY, 0); } static void dsiinit(struct dsi_cfg *cfg) { int n; wr(dsi, DSI_HOST_CFG_NUM_LANES, cfg->lanes-1); wr(dsi, DSI_HOST_CFG_NONCONTINUOUS_CLK, 0x0); wr(dsi, DSI_HOST_CFG_AUTOINSERT_EOTP, 0x0); n = (cfg->clk_pre_ps + cfg->byte_t_ps-1) / cfg->byte_t_ps; wr(dsi, DSI_HOST_CFG_T_PRE, n); n = (cfg->clk_pre_ps + cfg->lpx_ps + cfg->clk_prepare_ps + cfg->clk_zero_ps + cfg->byte_t_ps-1) / cfg->byte_t_ps; wr(dsi, DSI_HOST_CFG_T_POST, n); n = (cfg->hs_exit_ps + cfg->byte_t_ps-1) / cfg->byte_t_ps; wr(dsi, DSI_HOST_CFG_TX_GAP, n); wr(dsi, DSI_HOST_CFG_EXTRA_CMDS_AFTER_EOTP, 0x1); wr(dsi, DSI_HOST_CFG_HTX_TO_COUNT, 0x0); wr(dsi, DSI_HOST_CFG_LRX_H_TO_COUNT, 0x0); wr(dsi, DSI_HOST_CFG_BTA_H_TO_COUNT, 0x0); n = (cfg->wakeup_ps + cfg->tx_esc_t_ps-1) / cfg->tx_esc_t_ps; wr(dsi, DSI_HOST_CFG_TWAKEUP, n); } static void dpiinit(struct video_mode *mode) { wr(dsi, DSI_HOST_CFG_DPI_INTERFACE_COLOR_CODING, 0x5); // 24-bit wr(dsi, DSI_HOST_CFG_DPI_PIXEL_FORMAT, 0x3); // 24-bit /* this seems wrong */ wr(dsi, DSI_HOST_CFG_DPI_VSYNC_POLARITY, 0); wr(dsi, DSI_HOST_CFG_DPI_HSYNC_POLARITY, 0); wr(dsi, DSI_HOST_CFG_DPI_VIDEO_MODE, 0x1); // non-burst mode with sync events wr(dsi, DSI_HOST_CFG_DPI_PIXEL_FIFO_SEND_LEVEL, mode->hactive); wr(dsi, DSI_HOST_CFG_DPI_HFP, mode->hso); wr(dsi, DSI_HOST_CFG_DPI_HBP, mode->hblank - mode->hspw - mode->hso); wr(dsi, DSI_HOST_CFG_DPI_HSA, mode->hspw); wr(dsi, DSI_HOST_CFG_DPI_ENA_BLE_MULT_PKTS, 0x0); wr(dsi, DSI_HOST_CFG_DPI_BLLP_MODE, 0x1); wr(dsi, DSI_HOST_CFG_DPI_USE_NULL_PKT_BLLP, 0x0); wr(dsi, DSI_HOST_CFG_DPI_VC, 0x0); wr(dsi, DSI_HOST_CFG_DPI_PIXEL_PAYLOAD_SIZE, mode->hactive); wr(dsi, DSI_HOST_CFG_DPI_VACTIVE, mode->vactive - 1); wr(dsi, DSI_HOST_CFG_DPI_VBP, mode->vblank - mode->vspw - mode->vso); wr(dsi, DSI_HOST_CFG_DPI_VFP, mode->vso); } static void backlighton(void) { /* gpio1_io10: for panel backlight enable */ iomuxpad("pad_gpio1_io10", "gpio1_io10", "~LVTTL ~HYS ~PUE ~ODE FAST 45_OHM"); /* gpio1_io10 low: panel backlight off */ gpioout(GPIO_PIN(1, 10), 0); /* pwm2_out: for panel backlight */ iomuxpad("pad_spdif_rx", "pwm2_out", "~LVTTL ~HYS ~PUE ~ODE FAST 45_OHM"); setclkrate("pwm2.ipg_clk_high_freq", "osc_25m_ref_clk", Pwmsrcclk); setclkgate("pwm2.ipg_clk_high_freq", 1); wr(pwm2, PWMIR, 0); wr(pwm2, PWMCR, CR_STOPEN | CR_DOZEN | CR_WAITEN | CR_DBGEN | CR_CLKSRC_HIGHFREQ | 0<<CR_PRESCALER_SHIFT); wr(pwm2, PWMSAR, Pwmsrcclk/150000); wr(pwm2, PWMPR, (Pwmsrcclk/100000)-2); mr(pwm2, PWMCR, CR_EN, CR_EN); /* gpio1_io10 high: panel backlight on */ gpioout(GPIO_PIN(1, 10), 1); } static void blankirq(Ureg *, void *) { wr(lcdif, LCDIF_CTRL1_CLR, CTRL1_CUR_FRAME_DONE_IRQ); if(lcdifirq.blank != 0){ lcdifirq.blank = 0; wr(lcdif, LCDIF_CTRL_CLR, CTRL_DOTCLK_MODE); } } void blankscreen(int blank) { if(blank == 0){ setclkgate("disp.axi_clk", 1); setclkgate("sim_display.mainclk", 1); wr(lcdif, LCDIF_CTRL_SET, CTRL_DOTCLK_MODE); wr(lcdif, LCDIF_CTRL_SET, CTRL_RUN); } /* panel backlight */ gpioout(GPIO_PIN(1, 10), blank == 0); /* PWM2 */ mr(pwm2, PWMCR, (blank == 0)*CR_EN, CR_EN); /* bridge output */ i2cwritebyte(bridge, 0x5a, blank == 0 ? 0x0d : 0x05); if(blank){ lcdifirq.blank = 1; while(rr(lcdif, LCDIF_CTRL) & CTRL_RUN) ; setclkgate("sim_display.mainclk", 0); setclkgate("disp.axi_clk", 0); } } static void lcdmeminit(void) { Physseg seg; memset(&seg, 0, sizeof seg); seg.attr = SG_PHYSICAL | SG_DEVICE | SG_NOEXEC; seg.name = "pwm2"; seg.pa = (uintptr)pwm2 - KZERO; seg.size = BY2PG; addphysseg(&seg); } void lcdinit(void) { struct dsi_cfg dsi_cfg; struct video_mode mode; char *err; void *fb; intrenable(IRQlcdif, blankirq, nil, BUSUNKNOWN, "lcdif"); /* GPR13[MIPI_MUX_SEL]: 0 = LCDIF, 1 = DCSS */ iomuxgpr(13, 0, 1<<2); backlighton(); /* gpio3_io20: sn65dsi86 bridge enable */ iomuxpad("pad_sai5_rxc", "gpio3_io20", "~LVTTL ~HYS ~PUE ~ODE FAST 45_OHM"); /* gpio3_io20 high: bridge on */ gpioout(GPIO_PIN(3, 20), 1); bridge = i2cdev(i2cbus("i2c4"), 0x2C); if(bridge == nil){ err = "could not find bridge"; goto out; } bridge->subaddr = 1; /* power on mipi dsi */ powerup("mipi"); mr(resetc, SRC_MIPIPHY_RCR, 0, RCR_MIPI_DSI_RESET_N); mr(resetc, SRC_MIPIPHY_RCR, 0, RCR_MIPI_DSI_PCLK_RESET_N); mr(resetc, SRC_MIPIPHY_RCR, 0, RCR_MIPI_DSI_ESC_RESET_N); mr(resetc, SRC_MIPIPHY_RCR, 0, RCR_MIPI_DSI_RESET_BYTE_N); mr(resetc, SRC_MIPIPHY_RCR, 0, RCR_MIPI_DSI_DPI_RESET_N); setclkgate("sim_display.mainclk", 0); setclkgate("disp.axi_clk", 0); setclkrate("disp.axi_clk", "system_pll1_clk", 800*Mhz); setclkrate("disp.rtrm_clk", "system_pll1_clk", 400*Mhz); setclkgate("disp.axi_clk", 1); setclkgate("sim_display.mainclk", 1); lcdifreset(); setclkrate("mipi.core", "system_pll1_div3", 266*Mhz); setclkrate("mipi.CLKREF", "system_pll2_clk", 25*Mhz); setclkrate("mipi.RxClkEsc", "system_pll1_clk", 80*Mhz); setclkrate("mipi.TxClkEsc", nil, 20*Mhz); /* dsi parameters are fixed for the bridge */ dsiparams(&dsi_cfg, 4, 2*486*Mhz, getclkrate("mipi.CLKREF"), getclkrate("mipi.TxClkEsc"), getclkrate("mipi.RxClkEsc")); /* release dphy reset */ mr(resetc, SRC_MIPIPHY_RCR, RCR_MIPI_DSI_PCLK_RESET_N, RCR_MIPI_DSI_PCLK_RESET_N); dphyinit(&dsi_cfg); dsiinit(&dsi_cfg); dphypowerup(); /* release mipi clock resets (generated by the dphy) */ mr(resetc, SRC_MIPIPHY_RCR, RCR_MIPI_DSI_ESC_RESET_N, RCR_MIPI_DSI_ESC_RESET_N); mr(resetc, SRC_MIPIPHY_RCR, RCR_MIPI_DSI_RESET_BYTE_N, RCR_MIPI_DSI_RESET_BYTE_N); /* * get mode information from EDID, this can only be done after the clocks * are generated by the DPHY and the clock resets have been released. */ err = getmode(&mode); if(err != nil) goto out; /* allocates the framebuffer */ if((fb = screeninit(mode.hactive, mode.vactive, 32)) == nil){ err = "screeninit failed"; goto out; } /* expose useful segment(s) to the userspace */ lcdmeminit(); /* start the pixel clock */ setclkrate("lcdif.pix_clk", "system_pll1_clk", mode.pixclk); dpiinit(&mode); /* release dpi reset */ mr(resetc, SRC_MIPIPHY_RCR, RCR_MIPI_DSI_DPI_RESET_N, RCR_MIPI_DSI_DPI_RESET_N); /* enable display port bridge */ bridgeinit(&mode, &dsi_cfg); /* send the pixels */ lcdifinit(&mode, (u32int)PADDR(fb)); return; out: print("lcdinit: %s\n", err); }