|  | /* | 
|  | * Copyright 2010-2011 Freescale Semiconductor, Inc. | 
|  | * Authors: Timur Tabi <timur@freescale.com> | 
|  | * | 
|  | * FSL DIU Framebuffer driver | 
|  | * | 
|  | * SPDX-License-Identifier:	GPL-2.0+ | 
|  | */ | 
|  |  | 
|  | #include <common.h> | 
|  | #include <command.h> | 
|  | #include <linux/ctype.h> | 
|  | #include <asm/io.h> | 
|  | #include <stdio_dev.h> | 
|  | #include <video_fb.h> | 
|  | #include "../common/ngpixis.h" | 
|  | #include <fsl_diu_fb.h> | 
|  |  | 
|  | /* The CTL register is called 'csr' in the ngpixis_t structure */ | 
|  | #define PX_CTL_ALTACC		0x80 | 
|  |  | 
|  | #define PX_BRDCFG0_ELBC_SPI_MASK	0xc0 | 
|  | #define PX_BRDCFG0_ELBC_SPI_ELBC	0x00 | 
|  | #define PX_BRDCFG0_ELBC_SPI_NULL	0xc0 | 
|  | #define PX_BRDCFG0_ELBC_DIU		0x02 | 
|  |  | 
|  | #define PX_BRDCFG1_DVIEN	0x80 | 
|  | #define PX_BRDCFG1_DFPEN	0x40 | 
|  | #define PX_BRDCFG1_BACKLIGHT	0x20 | 
|  |  | 
|  | #define PMUXCR_ELBCDIU_MASK	0xc0000000 | 
|  | #define PMUXCR_ELBCDIU_NOR16	0x80000000 | 
|  | #define PMUXCR_ELBCDIU_DIU	0x40000000 | 
|  |  | 
|  | /* | 
|  | * DIU Area Descriptor | 
|  | * | 
|  | * Note that we need to byte-swap the value before it's written to the AD | 
|  | * register.  So even though the registers don't look like they're in the same | 
|  | * bit positions as they are on the MPC8610, the same value is written to the | 
|  | * AD register on the MPC8610 and on the P1022. | 
|  | */ | 
|  | #define AD_BYTE_F		0x10000000 | 
|  | #define AD_ALPHA_C_SHIFT	25 | 
|  | #define AD_BLUE_C_SHIFT		23 | 
|  | #define AD_GREEN_C_SHIFT	21 | 
|  | #define AD_RED_C_SHIFT		19 | 
|  | #define AD_PIXEL_S_SHIFT	16 | 
|  | #define AD_COMP_3_SHIFT		12 | 
|  | #define AD_COMP_2_SHIFT		8 | 
|  | #define AD_COMP_1_SHIFT		4 | 
|  | #define AD_COMP_0_SHIFT		0 | 
|  |  | 
|  | /* | 
|  | * Variables used by the DIU/LBC switching code.  It's safe to makes these | 
|  | * global, because the DIU requires DDR, so we'll only run this code after | 
|  | * relocation. | 
|  | */ | 
|  | static u8 px_brdcfg0; | 
|  | static u32 pmuxcr; | 
|  | static void *lbc_lcs0_ba; | 
|  | static void *lbc_lcs1_ba; | 
|  | static u32 old_br0, old_or0, old_br1, old_or1; | 
|  | static u32 new_br0, new_or0, new_br1, new_or1; | 
|  |  | 
|  | void diu_set_pixel_clock(unsigned int pixclock) | 
|  | { | 
|  | ccsr_gur_t *gur = (void *)(CONFIG_SYS_MPC85xx_GUTS_ADDR); | 
|  | unsigned long speed_ccb, temp; | 
|  | u32 pixval; | 
|  |  | 
|  | speed_ccb = get_bus_freq(0); | 
|  | temp = 1000000000 / pixclock; | 
|  | temp *= 1000; | 
|  | pixval = speed_ccb / temp; | 
|  | debug("DIU pixval = %u\n", pixval); | 
|  |  | 
|  | /* Modify PXCLK in GUTS CLKDVDR */ | 
|  | temp = in_be32(&gur->clkdvdr) & 0x2000FFFF; | 
|  | out_be32(&gur->clkdvdr, temp);			/* turn off clock */ | 
|  | out_be32(&gur->clkdvdr, temp | 0x80000000 | ((pixval & 0x1F) << 16)); | 
|  | } | 
|  |  | 
|  | int platform_diu_init(unsigned int xres, unsigned int yres, const char *port) | 
|  | { | 
|  | ccsr_gur_t *gur = (void *)(CONFIG_SYS_MPC85xx_GUTS_ADDR); | 
|  | const char *name; | 
|  | u32 pixel_format; | 
|  | u8 temp; | 
|  | phys_addr_t phys0, phys1; /* BR0/BR1 physical addresses */ | 
|  |  | 
|  | /* | 
|  | * Indirect mode requires both BR0 and BR1 to be set to "GPCM", | 
|  | * otherwise writes to these addresses won't actually appear on the | 
|  | * local bus, and so the PIXIS won't see them. | 
|  | * | 
|  | * In FCM mode, writes go to the NAND controller, which does not pass | 
|  | * them to the localbus directly.  So we force BR0 and BR1 into GPCM | 
|  | * mode, since we don't care about what's behind the localbus any | 
|  | * more.  However, we save those registers first, so that we can | 
|  | * restore them when necessary. | 
|  | */ | 
|  | new_br0 = old_br0 = get_lbc_br(0); | 
|  | new_br1 = old_br1 = get_lbc_br(1); | 
|  | new_or0 = old_or0 = get_lbc_or(0); | 
|  | new_or1 = old_or1 = get_lbc_or(1); | 
|  |  | 
|  | /* | 
|  | * Use the existing BRx/ORx values if it's already GPCM. Otherwise, | 
|  | * force the values to simple 32KB GPCM windows with the most | 
|  | * conservative timing. | 
|  | */ | 
|  | if ((old_br0 & BR_MSEL) != BR_MS_GPCM) { | 
|  | new_br0 = (get_lbc_br(0) & BR_BA) | BR_V; | 
|  | new_or0 = OR_AM_32KB | 0xFF7; | 
|  | set_lbc_br(0, new_br0); | 
|  | set_lbc_or(0, new_or0); | 
|  | } | 
|  | if ((old_br1 & BR_MSEL) != BR_MS_GPCM) { | 
|  | new_br1 = (get_lbc_br(1) & BR_BA) | BR_V; | 
|  | new_or1 = OR_AM_32KB | 0xFF7; | 
|  | set_lbc_br(1, new_br1); | 
|  | set_lbc_or(1, new_or1); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Determine the physical addresses for Chip Selects 0 and 1.  The | 
|  | * BR0/BR1 registers contain the truncated physical addresses for the | 
|  | * chip selects, mapped via the localbus LAW.  Since the BRx registers | 
|  | * only contain the lower 32 bits of the address, we have to determine | 
|  | * the upper 4 bits some other way.  The proper way is to scan the LAW | 
|  | * table looking for a matching localbus address. Instead, we cheat. | 
|  | * We know that the upper bits are 0 for 32-bit addressing, or 0xF for | 
|  | * 36-bit addressing. | 
|  | */ | 
|  | #ifdef CONFIG_PHYS_64BIT | 
|  | phys0 = 0xf00000000ULL | (old_br0 & old_or0 & BR_BA); | 
|  | phys1 = 0xf00000000ULL | (old_br1 & old_or1 & BR_BA); | 
|  | #else | 
|  | phys0 = old_br0 & old_or0 & BR_BA; | 
|  | phys1 = old_br1 & old_or1 & BR_BA; | 
|  | #endif | 
|  |  | 
|  | /* Save the LBC LCS0 and LCS1 addresses for the DIU mux functions */ | 
|  | lbc_lcs0_ba = map_physmem(phys0, 1, 0); | 
|  | lbc_lcs1_ba = map_physmem(phys1, 1, 0); | 
|  |  | 
|  | pixel_format = cpu_to_le32(AD_BYTE_F | (3 << AD_ALPHA_C_SHIFT) | | 
|  | (0 << AD_BLUE_C_SHIFT) | (1 << AD_GREEN_C_SHIFT) | | 
|  | (2 << AD_RED_C_SHIFT) | (8 << AD_COMP_3_SHIFT) | | 
|  | (8 << AD_COMP_2_SHIFT) | (8 << AD_COMP_1_SHIFT) | | 
|  | (8 << AD_COMP_0_SHIFT) | (3 << AD_PIXEL_S_SHIFT)); | 
|  |  | 
|  | temp = in_8(&pixis->brdcfg1); | 
|  |  | 
|  | if (strncmp(port, "lvds", 4) == 0) { | 
|  | /* Single link LVDS */ | 
|  | temp &= ~PX_BRDCFG1_DVIEN; | 
|  | /* | 
|  | * LVDS also needs backlight enabled, otherwise the display | 
|  | * will be blank. | 
|  | */ | 
|  | temp |= (PX_BRDCFG1_DFPEN | PX_BRDCFG1_BACKLIGHT); | 
|  | name = "Single-Link LVDS"; | 
|  | } else {	/* DVI */ | 
|  | /* Enable the DVI port, disable the DFP and the backlight */ | 
|  | temp &= ~(PX_BRDCFG1_DFPEN | PX_BRDCFG1_BACKLIGHT); | 
|  | temp |= PX_BRDCFG1_DVIEN; | 
|  | name = "DVI"; | 
|  | } | 
|  |  | 
|  | printf("DIU:   Switching to %s monitor @ %ux%u\n", name, xres, yres); | 
|  | out_8(&pixis->brdcfg1, temp); | 
|  |  | 
|  | /* | 
|  | * Enable PIXIS indirect access mode.  This is a hack that allows us to | 
|  | * access PIXIS registers even when the LBC pins have been muxed to the | 
|  | * DIU. | 
|  | */ | 
|  | setbits_8(&pixis->csr, PX_CTL_ALTACC); | 
|  |  | 
|  | /* | 
|  | * Route the LAD pins to the DIU.  This will disable access to the eLBC, | 
|  | * which means we won't be able to read/write any NOR flash addresses! | 
|  | */ | 
|  | out_8(lbc_lcs0_ba, offsetof(ngpixis_t, brdcfg0)); | 
|  | px_brdcfg0 = in_8(lbc_lcs1_ba); | 
|  | out_8(lbc_lcs1_ba, px_brdcfg0 | PX_BRDCFG0_ELBC_DIU); | 
|  | in_8(lbc_lcs1_ba); | 
|  |  | 
|  | /* Set PMUXCR to switch the muxed pins from the LBC to the DIU */ | 
|  | clrsetbits_be32(&gur->pmuxcr, PMUXCR_ELBCDIU_MASK, PMUXCR_ELBCDIU_DIU); | 
|  | pmuxcr = in_be32(&gur->pmuxcr); | 
|  |  | 
|  | return fsl_diu_init(xres, yres, pixel_format, 0); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * set_mux_to_lbc - disable the DIU so that we can read/write to elbc | 
|  | * | 
|  | * On the Freescale P1022, the DIU video signal and the LBC address/data lines | 
|  | * share the same pins, which means that when the DIU is active (e.g. the | 
|  | * console is on the DVI display), NOR flash cannot be accessed.  So we use the | 
|  | * weak accessor feature of the CFI flash code to temporarily switch the pin | 
|  | * mux from DIU to LBC whenever we want to read or write flash.  This has a | 
|  | * significant performance penalty, but it's the only way to make it work. | 
|  | * | 
|  | * There are two muxes: one on the chip, and one on the board. The chip mux | 
|  | * controls whether the pins are used for the DIU or the LBC, and it is | 
|  | * set via PMUXCR.  The board mux controls whether those signals go to | 
|  | * the video connector or the NOR flash chips, and it is set via the ngPIXIS. | 
|  | */ | 
|  | static int set_mux_to_lbc(void) | 
|  | { | 
|  | ccsr_gur_t *gur = (void *)CONFIG_SYS_MPC85xx_GUTS_ADDR; | 
|  |  | 
|  | /* Switch the muxes only if they're currently set to DIU mode */ | 
|  | if ((in_be32(&gur->pmuxcr) & PMUXCR_ELBCDIU_MASK) != | 
|  | PMUXCR_ELBCDIU_NOR16) { | 
|  | /* | 
|  | * In DIU mode, the PIXIS can only be accessed indirectly | 
|  | * since we can't read/write the LBC directly. | 
|  | */ | 
|  | /* Set the board mux to LBC.  This will disable the display. */ | 
|  | out_8(lbc_lcs0_ba, offsetof(ngpixis_t, brdcfg0)); | 
|  | out_8(lbc_lcs1_ba, px_brdcfg0); | 
|  | in_8(lbc_lcs1_ba); | 
|  |  | 
|  | /* Disable indirect PIXIS mode */ | 
|  | out_8(lbc_lcs0_ba, offsetof(ngpixis_t, csr)); | 
|  | clrbits_8(lbc_lcs1_ba, PX_CTL_ALTACC); | 
|  |  | 
|  | /* Set the chip mux to LBC mode, so that writes go to flash. */ | 
|  | out_be32(&gur->pmuxcr, (pmuxcr & ~PMUXCR_ELBCDIU_MASK) | | 
|  | PMUXCR_ELBCDIU_NOR16); | 
|  | in_be32(&gur->pmuxcr); | 
|  |  | 
|  | /* Restore the BR0 and BR1 settings */ | 
|  | set_lbc_br(0, old_br0); | 
|  | set_lbc_or(0, old_or0); | 
|  | set_lbc_br(1, old_br1); | 
|  | set_lbc_or(1, old_or1); | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * set_mux_to_diu - re-enable the DIU muxing | 
|  | * | 
|  | * This function restores the chip and board muxing to point to the DIU. | 
|  | */ | 
|  | static void set_mux_to_diu(void) | 
|  | { | 
|  | ccsr_gur_t *gur = (void *)CONFIG_SYS_MPC85xx_GUTS_ADDR; | 
|  |  | 
|  | /* Set BR0 and BR1 to GPCM mode */ | 
|  | set_lbc_br(0, new_br0); | 
|  | set_lbc_or(0, new_or0); | 
|  | set_lbc_br(1, new_br1); | 
|  | set_lbc_or(1, new_or1); | 
|  |  | 
|  | /* Enable indirect PIXIS mode */ | 
|  | setbits_8(&pixis->csr, PX_CTL_ALTACC); | 
|  |  | 
|  | /* Set the board mux to DIU.  This will enable the display. */ | 
|  | out_8(lbc_lcs0_ba, offsetof(ngpixis_t, brdcfg0)); | 
|  | out_8(lbc_lcs1_ba, px_brdcfg0 | PX_BRDCFG0_ELBC_DIU); | 
|  | in_8(lbc_lcs1_ba); | 
|  |  | 
|  | /* Set the chip mux to DIU mode. */ | 
|  | out_be32(&gur->pmuxcr, pmuxcr); | 
|  | in_be32(&gur->pmuxcr); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * pixis_read - board-specific function to read from the PIXIS | 
|  | * | 
|  | * This function overrides the generic pixis_read() function, so that it can | 
|  | * use PIXIS indirect mode if necessary. | 
|  | */ | 
|  | u8 pixis_read(unsigned int reg) | 
|  | { | 
|  | ccsr_gur_t *gur = (void *)CONFIG_SYS_MPC85xx_GUTS_ADDR; | 
|  |  | 
|  | /* Use indirect mode if the mux is currently set to DIU mode */ | 
|  | if ((in_be32(&gur->pmuxcr) & PMUXCR_ELBCDIU_MASK) != | 
|  | PMUXCR_ELBCDIU_NOR16) { | 
|  | out_8(lbc_lcs0_ba, reg); | 
|  | return in_8(lbc_lcs1_ba); | 
|  | } else { | 
|  | void *p = (void *)PIXIS_BASE; | 
|  |  | 
|  | return in_8(p + reg); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * pixis_write - board-specific function to write to the PIXIS | 
|  | * | 
|  | * This function overrides the generic pixis_write() function, so that it can | 
|  | * use PIXIS indirect mode if necessary. | 
|  | */ | 
|  | void pixis_write(unsigned int reg, u8 value) | 
|  | { | 
|  | ccsr_gur_t *gur = (void *)CONFIG_SYS_MPC85xx_GUTS_ADDR; | 
|  |  | 
|  | /* Use indirect mode if the mux is currently set to DIU mode */ | 
|  | if ((in_be32(&gur->pmuxcr) & PMUXCR_ELBCDIU_MASK) != | 
|  | PMUXCR_ELBCDIU_NOR16) { | 
|  | out_8(lbc_lcs0_ba, reg); | 
|  | out_8(lbc_lcs1_ba, value); | 
|  | /* Do a read-back to ensure the write completed */ | 
|  | in_8(lbc_lcs1_ba); | 
|  | } else { | 
|  | void *p = (void *)PIXIS_BASE; | 
|  |  | 
|  | out_8(p + reg, value); | 
|  | } | 
|  | } | 
|  |  | 
|  | void pixis_bank_reset(void) | 
|  | { | 
|  | /* | 
|  | * For some reason, a PIXIS bank reset does not work if the PIXIS is | 
|  | * in indirect mode, so switch to direct mode first. | 
|  | */ | 
|  | set_mux_to_lbc(); | 
|  |  | 
|  | out_8(&pixis->vctl, 0); | 
|  | out_8(&pixis->vctl, 1); | 
|  |  | 
|  | while (1); | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_CFI_FLASH_USE_WEAK_ACCESSORS | 
|  |  | 
|  | void flash_write8(u8 value, void *addr) | 
|  | { | 
|  | int sw = set_mux_to_lbc(); | 
|  |  | 
|  | __raw_writeb(value, addr); | 
|  | if (sw) { | 
|  | /* | 
|  | * To ensure the post-write is completed to eLBC, software must | 
|  | * perform a dummy read from one valid address from eLBC space | 
|  | * before changing the eLBC_DIU from NOR mode to DIU mode. | 
|  | * set_mux_to_diu() includes a sync that will ensure the | 
|  | * __raw_readb() completes before it switches the mux. | 
|  | */ | 
|  | __raw_readb(addr); | 
|  | set_mux_to_diu(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void flash_write16(u16 value, void *addr) | 
|  | { | 
|  | int sw = set_mux_to_lbc(); | 
|  |  | 
|  | __raw_writew(value, addr); | 
|  | if (sw) { | 
|  | /* | 
|  | * To ensure the post-write is completed to eLBC, software must | 
|  | * perform a dummy read from one valid address from eLBC space | 
|  | * before changing the eLBC_DIU from NOR mode to DIU mode. | 
|  | * set_mux_to_diu() includes a sync that will ensure the | 
|  | * __raw_readb() completes before it switches the mux. | 
|  | */ | 
|  | __raw_readb(addr); | 
|  | set_mux_to_diu(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void flash_write32(u32 value, void *addr) | 
|  | { | 
|  | int sw = set_mux_to_lbc(); | 
|  |  | 
|  | __raw_writel(value, addr); | 
|  | if (sw) { | 
|  | /* | 
|  | * To ensure the post-write is completed to eLBC, software must | 
|  | * perform a dummy read from one valid address from eLBC space | 
|  | * before changing the eLBC_DIU from NOR mode to DIU mode. | 
|  | * set_mux_to_diu() includes a sync that will ensure the | 
|  | * __raw_readb() completes before it switches the mux. | 
|  | */ | 
|  | __raw_readb(addr); | 
|  | set_mux_to_diu(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void flash_write64(u64 value, void *addr) | 
|  | { | 
|  | int sw = set_mux_to_lbc(); | 
|  | uint32_t *p = addr; | 
|  |  | 
|  | /* | 
|  | * There is no __raw_writeq(), so do the write manually.  We don't trust | 
|  | * the compiler, so we use inline assembly. | 
|  | */ | 
|  | __asm__ __volatile__( | 
|  | "stw%U0%X0 %2,%0;\n" | 
|  | "stw%U1%X1 %3,%1;\n" | 
|  | : "=m" (*p), "=m" (*(p + 1)) | 
|  | : "r" ((uint32_t) (value >> 32)), "r" ((uint32_t) (value))); | 
|  |  | 
|  | if (sw) { | 
|  | /* | 
|  | * To ensure the post-write is completed to eLBC, software must | 
|  | * perform a dummy read from one valid address from eLBC space | 
|  | * before changing the eLBC_DIU from NOR mode to DIU mode.  We | 
|  | * read addr+4 because we just wrote to addr+4, so that's how we | 
|  | * maintain execution order.  set_mux_to_diu() includes a sync | 
|  | * that will ensure the __raw_readb() completes before it | 
|  | * switches the mux. | 
|  | */ | 
|  | __raw_readb(addr + 4); | 
|  | set_mux_to_diu(); | 
|  | } | 
|  | } | 
|  |  | 
|  | u8 flash_read8(void *addr) | 
|  | { | 
|  | u8 ret; | 
|  |  | 
|  | int sw = set_mux_to_lbc(); | 
|  |  | 
|  | ret = __raw_readb(addr); | 
|  | if (sw) | 
|  | set_mux_to_diu(); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | u16 flash_read16(void *addr) | 
|  | { | 
|  | u16 ret; | 
|  |  | 
|  | int sw = set_mux_to_lbc(); | 
|  |  | 
|  | ret = __raw_readw(addr); | 
|  | if (sw) | 
|  | set_mux_to_diu(); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | u32 flash_read32(void *addr) | 
|  | { | 
|  | u32 ret; | 
|  |  | 
|  | int sw = set_mux_to_lbc(); | 
|  |  | 
|  | ret = __raw_readl(addr); | 
|  | if (sw) | 
|  | set_mux_to_diu(); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | u64 flash_read64(void *addr) | 
|  | { | 
|  | u64 ret; | 
|  |  | 
|  | int sw = set_mux_to_lbc(); | 
|  |  | 
|  | /* There is no __raw_readq(), so do the read manually */ | 
|  | ret = *(volatile u64 *)addr; | 
|  | if (sw) | 
|  | set_mux_to_diu(); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | #endif |