Add mode selection to iMX6 IPU driver

- Configure ipu1_di0 tob e sourced from the VIDEO_PLL(PLL5) and hardcode
  frequency to (455000000/3)Mhz. This value, further divided, can yield
  frequencies close enough to support 1080p, 720p, 1024x768, and 640x480
  modes. This is not ideal but it's an improvement comparing to the only
  hardcoded 1024x768 mode.

- Fix memory leaks if attach method failed
- Print EDID when -v passed to the kernel
This commit is contained in:
Oleksandr Tymoshenko 2020-06-10 22:00:31 +00:00
parent cbc596d6bf
commit da21a623dd
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=362030
4 changed files with 182 additions and 40 deletions

View File

@ -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

View File

@ -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)

View File

@ -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);
}

View File

@ -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);