plan9fox/sys/src/9/imx8/lcd.c
2022-07-09 14:13:07 +00:00

918 lines
22 KiB
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"
extern Memimage *gscreen;
/* 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);
/* 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
lcdifinit(struct video_mode *mode)
{
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);
wr(lcdif, LCDIF_CTRL1, sm(7, CTRL1_BYTE_PACKING_FORMAT));
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, PADDR(gscreen->data->bdata));
wr(lcdif, LCDIF_NEXT_BUF, PADDR(gscreen->data->bdata));
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(I2Cdev *dev, struct video_mode *mode, struct dsi_cfg *cfg)
{
int n;
// 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(dev, 0x0a, n);
// single channel A
n = 1<<5 | (cfg->lanes-4)<<3 | 3<<1;
i2cwritebyte(dev, 0x10, n);
// Enhanced framing and ASSR
i2cwritebyte(dev, 0x5a, 0x05);
// 2 DP lanes w/o SSC
i2cwritebyte(dev, 0x93, 0x20);
// 2.7Gbps DP data rate
i2cwritebyte(dev, 0x94, 0x80);
// Enable PLL and confirm PLL is locked
i2cwritebyte(dev, 0x0d, 0x01);
// wait for PLL to lock
while((i2creadbyte(dev, 0x0a) & 0x80) == 0)
;
// Enable ASSR on display
i2cwritebyte(dev, 0x64, 0x01);
i2cwritebyte(dev, 0x75, 0x01);
i2cwritebyte(dev, 0x76, 0x0a);
i2cwritebyte(dev, 0x77, 0x01);
i2cwritebyte(dev, 0x78, 0x81);
// Train link and confirm trained
i2cwritebyte(dev, 0x96, 0x0a);
while(i2creadbyte(dev, 0x96) != 1)
;
// video timings
i2cwritebyte(dev, 0x20, mode->hactive & 0xFF);
i2cwritebyte(dev, 0x21, mode->hactive >> 8);
i2cwritebyte(dev, 0x24, mode->vactive & 0xFF);
i2cwritebyte(dev, 0x25, mode->vactive >> 8);
i2cwritebyte(dev, 0x2c, mode->hspw);
i2cwritebyte(dev, 0x2d, mode->hspw>>8 | (mode->hsync_pol=='-')<<7);
i2cwritebyte(dev, 0x30, mode->vspw);
i2cwritebyte(dev, 0x31, mode->vspw>>8 | (mode->vsync_pol=='-')<<7);
i2cwritebyte(dev, 0x34, mode->hblank - mode->hspw - mode->hso);
i2cwritebyte(dev, 0x36, mode->vblank - mode->vspw - mode->vso);
i2cwritebyte(dev, 0x38, mode->hso);
i2cwritebyte(dev, 0x3a, mode->vso);
// Enable video stream, ASSR, enhanced framing
i2cwritebyte(dev, 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(I2Cdev *dev, struct video_mode *mode)
{
static uchar edid[128];
static I2Cdev aux;
aux.bus = dev->bus;
aux.addr = 0x50;
aux.subaddr = 1;
aux.size = sizeof(edid);
/* enable passthru mode for address 0x50 (EDID) */
i2cwritebyte(dev, 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);
}
void
blankscreen(int blank)
{
gpioout(GPIO_PIN(1, 10), blank == 0);
}
void
lcdinit(void)
{
struct dsi_cfg dsi_cfg;
struct video_mode mode;
I2Cdev *bridge;
char *err;
/* 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)
return;
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);
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(bridge, &mode);
if(err != nil)
goto out;
/* allocates the framebuffer (gscreen->data->bdata) */
if(screeninit(mode.hactive, mode.vactive, 32) < 0){
err = "screeninit failed";
goto out;
}
/*
* start the pixel clock. running at the actual pixel clock
* causes the screen to shift horizontally after a while.
* using 80% seems to fix it - for now.
*/
setclkrate("lcdif.pix_clk", "system_pll1_clk", (mode.pixclk*8)/10);
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(bridge, &mode, &dsi_cfg);
/* send the pixels */
lcdifinit(&mode);
return;
out:
print("lcdinit: %s\n", err);
}