diff --git a/sys/arm/freescale/imx/imx6_ccm.c b/sys/arm/freescale/imx/imx6_ccm.c index 939ece04ac5b..94c0420e6737 100644 --- a/sys/arm/freescale/imx/imx6_ccm.c +++ b/sys/arm/freescale/imx/imx6_ccm.c @@ -393,6 +393,53 @@ imx_ccm_ahb_hz(void) return (132000000); } +int +imx_ccm_pll_video_enable(void) +{ + uint32_t reg; + int timeout; + + /* Power down PLL */ + reg = RD4(ccm_sc, CCM_ANALOG_PLL_VIDEO); + reg &= ~CCM_ANALOG_PLL_VIDEO_POWERDOWN; + WR4(ccm_sc, CCM_ANALOG_PLL_VIDEO, reg); + + /* + * Fvideo = Fref * (37 + 11/12) / 2 + * Fref = 24MHz, Fvideo = 455MHz + */ + reg &= ~CCM_ANALOG_PLL_VIDEO_POST_DIV_SELECT_MASK; + reg |= CCM_ANALOG_PLL_VIDEO_POST_DIV_2; + reg &= ~CCM_ANALOG_PLL_VIDEO_DIV_SELECT_MASK; + reg |= 37 << CCM_ANALOG_PLL_VIDEO_DIV_SELECT_SHIFT; + WR4(ccm_sc, CCM_ANALOG_PLL_VIDEO, reg); + + WR4(ccm_sc, CCM_ANALOG_PLL_VIDEO_NUM, 11); + WR4(ccm_sc, CCM_ANALOG_PLL_VIDEO_DENOM, 12); + + /* Power up and wait for PLL lock down */ + reg = RD4(ccm_sc, CCM_ANALOG_PLL_VIDEO); + reg &= ~CCM_ANALOG_PLL_VIDEO_POWERDOWN; + WR4(ccm_sc, CCM_ANALOG_PLL_VIDEO, reg); + + for (timeout = 100000; timeout > 0; timeout--) { + if (RD4(ccm_sc, CCM_ANALOG_PLL_VIDEO) & + CCM_ANALOG_PLL_VIDEO_LOCK) { + break; + } + } + if (timeout <= 0) { + return ETIMEDOUT; + } + + /* Enable the PLL */ + reg |= CCM_ANALOG_PLL_VIDEO_ENABLE; + reg &= ~CCM_ANALOG_PLL_VIDEO_BYPASS; + WR4(ccm_sc, CCM_ANALOG_PLL_VIDEO, reg); + + return (0); +} + void imx_ccm_ipu_enable(int ipu) { @@ -406,6 +453,24 @@ imx_ccm_ipu_enable(int ipu) else reg |= CCGR3_IPU2_IPU | CCGR3_IPU2_DI0; WR4(sc, CCM_CCGR3, reg); + + /* Set IPU1_DI0 clock to source from PLL5 and divide it by 3 */ + reg = RD4(sc, CCM_CHSCCDR); + reg &= ~(CHSCCDR_IPU1_DI0_PRE_CLK_SEL_MASK | + CHSCCDR_IPU1_DI0_PODF_MASK | CHSCCDR_IPU1_DI0_CLK_SEL_MASK); + reg |= (CHSCCDR_PODF_DIVIDE_BY_3 << CHSCCDR_IPU1_DI0_PODF_SHIFT); + reg |= (CHSCCDR_IPU_PRE_CLK_PLL5 << CHSCCDR_IPU1_DI0_PRE_CLK_SEL_SHIFT); + WR4(sc, CCM_CHSCCDR, reg); + + reg |= (CHSCCDR_CLK_SEL_PREMUXED << CHSCCDR_IPU1_DI0_CLK_SEL_SHIFT); + WR4(sc, CCM_CHSCCDR, reg); +} + +uint32_t +imx_ccm_ipu_hz(void) +{ + + return (455000000 / 3); } void @@ -418,16 +483,6 @@ imx_ccm_hdmi_enable(void) reg = RD4(sc, CCM_CCGR2); reg |= CCGR2_HDMI_TX | CCGR2_HDMI_TX_ISFR; WR4(sc, CCM_CCGR2, reg); - - /* Set HDMI clock to 280MHz */ - reg = RD4(sc, CCM_CHSCCDR); - reg &= ~(CHSCCDR_IPU1_DI0_PRE_CLK_SEL_MASK | - CHSCCDR_IPU1_DI0_PODF_MASK | CHSCCDR_IPU1_DI0_CLK_SEL_MASK); - reg |= (CHSCCDR_PODF_DIVIDE_BY_3 << CHSCCDR_IPU1_DI0_PODF_SHIFT); - reg |= (CHSCCDR_IPU_PRE_CLK_540M_PFD << CHSCCDR_IPU1_DI0_PRE_CLK_SEL_SHIFT); - WR4(sc, CCM_CHSCCDR, reg); - reg |= (CHSCCDR_CLK_SEL_LDB_DI0 << CHSCCDR_IPU1_DI0_CLK_SEL_SHIFT); - WR4(sc, CCM_CHSCCDR, reg); } uint32_t diff --git a/sys/arm/freescale/imx/imx6_ccmreg.h b/sys/arm/freescale/imx/imx6_ccmreg.h index 69e028debae6..cbe107f1fba7 100644 --- a/sys/arm/freescale/imx/imx6_ccmreg.h +++ b/sys/arm/freescale/imx/imx6_ccmreg.h @@ -64,9 +64,12 @@ #define CHSCCDR_IPU1_DI0_PODF_SHIFT 3 #define CHSCCDR_IPU1_DI0_CLK_SEL_MASK (0x7) #define CHSCCDR_IPU1_DI0_CLK_SEL_SHIFT 0 +#define CHSCCDR_CLK_SEL_PREMUXED 0 #define CHSCCDR_CLK_SEL_LDB_DI0 3 #define CHSCCDR_PODF_DIVIDE_BY_3 2 +#define CHSCCDR_PODF_DIVIDE_BY_1 0 #define CHSCCDR_IPU_PRE_CLK_540M_PFD 5 +#define CHSCCDR_IPU_PRE_CLK_PLL5 2 #define CCM_CSCDR2 0x038 #define CCM_CLPCR 0x054 #define CCM_CLPCR_LPM_MASK 0x03 @@ -138,6 +141,19 @@ #define CCGR6_USDHC3 (0x3 << 6) #define CCGR6_USDHC4 (0x3 << 8) #define CCM_CMEOR 0x088 + +#define CCM_ANALOG_PLL_VIDEO 0x000040a0 +#define CCM_ANALOG_PLL_VIDEO_LOCK (1u << 31) +#define CCM_ANALOG_PLL_VIDEO_BYPASS (1u << 16) +#define CCM_ANALOG_PLL_VIDEO_ENABLE (1u << 13) +#define CCM_ANALOG_PLL_VIDEO_POWERDOWN (1u << 12) +#define CCM_ANALOG_PLL_VIDEO_POST_DIV_SELECT_MASK (3u << 19) +#define CCM_ANALOG_PLL_VIDEO_POST_DIV_2 (1u << 19) +#define CCM_ANALOG_PLL_VIDEO_DIV_SELECT_MASK (0x7f << 0) +#define CCM_ANALOG_PLL_VIDEO_DIV_SELECT_SHIFT 0 + +#define CCM_ANALOG_PLL_VIDEO_NUM 0x000040b0 +#define CCM_ANALOG_PLL_VIDEO_DENOM 0x000040c0 #define CCM_ANALOG_PLL_ENET 0x000040e0 #define CCM_ANALOG_PLL_ENET_LOCK (1u << 31) diff --git a/sys/arm/freescale/imx/imx6_ipu.c b/sys/arm/freescale/imx/imx6_ipu.c index 831a13988bba..417f8f64a28c 100644 --- a/sys/arm/freescale/imx/imx6_ipu.c +++ b/sys/arm/freescale/imx/imx6_ipu.c @@ -61,12 +61,8 @@ __FBSDID("$FreeBSD$"); #include "fb_if.h" #include "hdmi_if.h" -#define EDID_DEBUG_not - static int have_ipu = 0; -#define LDB_CLOCK_RATE 280000000 - #define MODE_HBP(mode) ((mode)->htotal - (mode)->hsync_end) #define MODE_HFP(mode) ((mode)->hsync_start - (mode)->hdisplay) #define MODE_HSW(mode) ((mode)->hsync_end - (mode)->hsync_start) @@ -77,11 +73,6 @@ static int have_ipu = 0; #define MODE_BPP 16 #define MODE_PIXEL_CLOCK_INVERT 1 -#define M(nm,hr,vr,clk,hs,he,ht,vs,ve,vt,f) \ - { clk, hr, hs, he, ht, vr, vs, ve, vt, f, nm } - -static struct videomode mode1024x768 = M("1024x768x60",1024,768,65000,1048,1184,1344,771,777,806,VID_NHSYNC|VID_PHSYNC); - #define DMA_CHANNEL 23 #define DC_CHAN5 5 #define DI_PORT 0 @@ -384,7 +375,7 @@ struct ipu_softc { void *sc_intr_hl; struct mtx sc_mtx; struct fb_info sc_fb_info; - struct videomode *sc_mode; + const struct videomode *sc_mode; /* Framebuffer */ bus_dma_tag_t sc_dma_tag; @@ -634,10 +625,30 @@ ipu_init_microcode_template(struct ipu_softc *sc, int di, int map) } } +static uint32_t +ipu_calc_divisor(uint32_t reference, uint32_t freq) +{ + uint32_t div, i; + uint32_t delta, min_delta; + + min_delta = freq; + div = 255; + + for (i = 1; i < 255; i++) { + delta = abs(reference/i - freq); + if (delta < min_delta) { + div = i; + min_delta = delta; + } + } + + return (div); +} + static void ipu_config_timing(struct ipu_softc *sc, int di) { - int div; + uint32_t div; uint32_t di_scr_conf; uint32_t gen_offset, gen; uint32_t as_gen_offset, as_gen; @@ -645,10 +656,11 @@ ipu_config_timing(struct ipu_softc *sc, int di) uint32_t dw_set_offset, dw_set; uint32_t bs_clkgen_offset; int map; + uint32_t freq; - /* TODO: check mode restrictions / fixup */ - /* TODO: enable timers, get divisors */ - div = 1; + freq = sc->sc_mode->dot_clock * 1000; + + div = ipu_calc_divisor(imx_ccm_ipu_hz(), freq); map = 0; bs_clkgen_offset = di ? IPU_DI1_BS_CLKGEN0 : IPU_DI0_BS_CLKGEN0; @@ -656,14 +668,6 @@ ipu_config_timing(struct ipu_softc *sc, int di) /* half of the divider */ IPU_WRITE4(sc, bs_clkgen_offset + 4, DI_BS_CLKGEN1_DOWN(div / 2, div % 2)); - /* - * TODO: Configure LLDB clock by changing following fields - * in CCM fields: - * CS2CDR_LDB_DI0_CLK_SEL - * CSCMR2_LDB_DI0_IPU_DIV - * CBCDR_MMDC_CH1_AXI_PODF - */ - /* Setup wave generator */ dw_gen_offset = di ? IPU_DI1_DW_GEN_0 : IPU_DI0_DW_GEN_0; dw_gen = DW_GEN_DI_ACCESS_SIZE(div - 1) | DW_GEN_DI_COMPONENT_SIZE(div - 1); @@ -768,8 +772,6 @@ ipu_dc_enable(struct ipu_softc *sc) conf &= ~WRITE_CH_CONF_PROG_CHAN_TYP_MASK; conf |= WRITE_CH_CONF_PROG_CHAN_NORMAL; IPU_WRITE4(sc, DC_WRITE_CH_CONF_5, conf); - - /* TODO: enable clock */ } static void @@ -1063,15 +1065,55 @@ ipu_init(struct ipu_softc *sc) return (err); } +static int +ipu_mode_is_valid(const struct videomode *mode) +{ + if ((mode->dot_clock < 13500) || (mode->dot_clock > 216000)) + return (0); + + return (1); +} + +static const struct videomode * +ipu_pick_mode(struct edid_info *ei) +{ + const struct videomode *videomode; + const struct videomode *m; + int n; + + videomode = NULL; + + /* + * Pick a mode. + */ + if (ei->edid_preferred_mode != NULL) { + if (ipu_mode_is_valid(ei->edid_preferred_mode)) + videomode = ei->edid_preferred_mode; + } + + if (videomode == NULL) { + m = ei->edid_modes; + + sort_modes(ei->edid_modes, + &ei->edid_preferred_mode, + ei->edid_nmodes); + for (n = 0; n < ei->edid_nmodes; n++) + if (ipu_mode_is_valid(&m[n])) { + videomode = &m[n]; + break; + } + } + + return videomode; +} + static void ipu_hdmi_event(void *arg, device_t hdmi_dev) { struct ipu_softc *sc; uint8_t *edid; uint32_t edid_len; -#ifdef EDID_DEBUG struct edid_info ei; -#endif const struct videomode *videomode; sc = arg; @@ -1084,14 +1126,28 @@ ipu_hdmi_event(void *arg, device_t hdmi_dev) videomode = NULL; -#ifdef EDID_DEBUG if ( edid && (edid_parse(edid, &ei) == 0)) { - edid_print(&ei); + if (bootverbose) + edid_print(&ei); + videomode = ipu_pick_mode(&ei); } else device_printf(sc->sc_dev, "failed to parse EDID\n"); -#endif - sc->sc_mode = &mode1024x768; + /* Use standard VGA as fallback */ + if (videomode == NULL) + videomode = pick_mode_by_ref(640, 480, 60); + + if (videomode == NULL) { + device_printf(sc->sc_dev, "failed to find usable videomode\n"); + return; + } + + sc->sc_mode = videomode; + + if (bootverbose) + device_printf(sc->sc_dev, "detected videomode: %dx%d\n", + videomode->hdisplay, videomode->vdisplay); + ipu_init(sc); HDMI_SET_VIDEOMODE(hdmi_dev, sc->sc_mode); @@ -1145,9 +1201,22 @@ ipu_attach(device_t dev) } /* Enable IPU1 */ + if (imx_ccm_pll_video_enable() != 0) { + bus_release_resource(dev, SYS_RES_MEMORY, + sc->sc_mem_rid, sc->sc_mem_res); + bus_release_resource(dev, SYS_RES_IRQ, + sc->sc_irq_rid, sc->sc_irq_res); + device_printf(dev, "failed to set up video PLL\n"); + return (ENXIO); + } + imx_ccm_ipu_enable(1); if (src_reset_ipu() != 0) { + bus_release_resource(dev, SYS_RES_MEMORY, + sc->sc_mem_rid, sc->sc_mem_res); + bus_release_resource(dev, SYS_RES_IRQ, + sc->sc_irq_rid, sc->sc_irq_res); device_printf(dev, "failed to reset IPU\n"); return (ENXIO); } diff --git a/sys/arm/freescale/imx/imx_ccmvar.h b/sys/arm/freescale/imx/imx_ccmvar.h index 5636729ace68..f15c61bd96e7 100644 --- a/sys/arm/freescale/imx/imx_ccmvar.h +++ b/sys/arm/freescale/imx/imx_ccmvar.h @@ -49,10 +49,12 @@ uint32_t imx_ccm_perclk_hz(void); uint32_t imx_ccm_sdhci_hz(void); uint32_t imx_ccm_uart_hz(void); uint32_t imx_ccm_ahb_hz(void); +uint32_t imx_ccm_ipu_hz(void); void imx_ccm_usb_enable(device_t _usbdev); void imx_ccm_usbphy_enable(device_t _phydev); void imx_ccm_ssi_configure(device_t _ssidev); +int imx_ccm_pll_video_enable(void); void imx_ccm_hdmi_enable(void); void imx_ccm_ipu_enable(int ipu); int imx6_ccm_sata_enable(void);