uboot/drivers/video/sunxi/sunxi_dw_hdmi.c
<<
>>
Prefs
   1/*
   2 * Allwinner DW HDMI bridge
   3 *
   4 * (C) Copyright 2017 Jernej Skrabec <jernej.skrabec@siol.net>
   5 *
   6 * SPDX-License-Identifier:     GPL-2.0+
   7 */
   8
   9#include <common.h>
  10#include <display.h>
  11#include <dm.h>
  12#include <dw_hdmi.h>
  13#include <edid.h>
  14#include <asm/io.h>
  15#include <asm/arch/clock.h>
  16#include <asm/arch/lcdc.h>
  17
  18struct sunxi_dw_hdmi_priv {
  19        struct dw_hdmi hdmi;
  20        int mux;
  21};
  22
  23struct sunxi_hdmi_phy {
  24        u32 pol;
  25        u32 res1[3];
  26        u32 read_en;
  27        u32 unscramble;
  28        u32 res2[2];
  29        u32 ctrl;
  30        u32 unk1;
  31        u32 unk2;
  32        u32 pll;
  33        u32 clk;
  34        u32 unk3;
  35        u32 status;
  36};
  37
  38#define HDMI_PHY_OFFS 0x10000
  39
  40static int sunxi_dw_hdmi_get_divider(uint clock)
  41{
  42        /*
  43         * Due to missing documentaion of HDMI PHY, we know correct
  44         * settings only for following four PHY dividers. Select one
  45         * based on clock speed.
  46         */
  47        if (clock <= 27000000)
  48                return 11;
  49        else if (clock <= 74250000)
  50                return 4;
  51        else if (clock <= 148500000)
  52                return 2;
  53        else
  54                return 1;
  55}
  56
  57static void sunxi_dw_hdmi_phy_init(void)
  58{
  59        struct sunxi_hdmi_phy * const phy =
  60                (struct sunxi_hdmi_phy *)(SUNXI_HDMI_BASE + HDMI_PHY_OFFS);
  61        unsigned long tmo;
  62        u32 tmp;
  63
  64        /*
  65         * HDMI PHY settings are taken as-is from Allwinner BSP code.
  66         * There is no documentation.
  67         */
  68        writel(0, &phy->ctrl);
  69        setbits_le32(&phy->ctrl, BIT(0));
  70        udelay(5);
  71        setbits_le32(&phy->ctrl, BIT(16));
  72        setbits_le32(&phy->ctrl, BIT(1));
  73        udelay(10);
  74        setbits_le32(&phy->ctrl, BIT(2));
  75        udelay(5);
  76        setbits_le32(&phy->ctrl, BIT(3));
  77        udelay(40);
  78        setbits_le32(&phy->ctrl, BIT(19));
  79        udelay(100);
  80        setbits_le32(&phy->ctrl, BIT(18));
  81        setbits_le32(&phy->ctrl, 7 << 4);
  82
  83        /* Note that Allwinner code doesn't fail in case of timeout */
  84        tmo = timer_get_us() + 2000;
  85        while ((readl(&phy->status) & 0x80) == 0) {
  86                if (timer_get_us() > tmo) {
  87                        printf("Warning: HDMI PHY init timeout!\n");
  88                        break;
  89                }
  90        }
  91
  92        setbits_le32(&phy->ctrl, 0xf << 8);
  93        setbits_le32(&phy->ctrl, BIT(7));
  94
  95        writel(0x39dc5040, &phy->pll);
  96        writel(0x80084343, &phy->clk);
  97        udelay(10000);
  98        writel(1, &phy->unk3);
  99        setbits_le32(&phy->pll, BIT(25));
 100        udelay(100000);
 101        tmp = (readl(&phy->status) & 0x1f800) >> 11;
 102        setbits_le32(&phy->pll, BIT(31) | BIT(30));
 103        setbits_le32(&phy->pll, tmp);
 104        writel(0x01FF0F7F, &phy->ctrl);
 105        writel(0x80639000, &phy->unk1);
 106        writel(0x0F81C405, &phy->unk2);
 107
 108        /* enable read access to HDMI controller */
 109        writel(0x54524545, &phy->read_en);
 110        /* descramble register offsets */
 111        writel(0x42494E47, &phy->unscramble);
 112}
 113
 114static int sunxi_dw_hdmi_get_plug_in_status(void)
 115{
 116        struct sunxi_hdmi_phy * const phy =
 117                (struct sunxi_hdmi_phy *)(SUNXI_HDMI_BASE + HDMI_PHY_OFFS);
 118
 119        return !!(readl(&phy->status) & (1 << 19));
 120}
 121
 122static int sunxi_dw_hdmi_wait_for_hpd(void)
 123{
 124        ulong start;
 125
 126        start = get_timer(0);
 127        do {
 128                if (sunxi_dw_hdmi_get_plug_in_status())
 129                        return 0;
 130                udelay(100);
 131        } while (get_timer(start) < 300);
 132
 133        return -1;
 134}
 135
 136static void sunxi_dw_hdmi_phy_set(uint clock)
 137{
 138        struct sunxi_hdmi_phy * const phy =
 139                (struct sunxi_hdmi_phy *)(SUNXI_HDMI_BASE + HDMI_PHY_OFFS);
 140        int div = sunxi_dw_hdmi_get_divider(clock);
 141        u32 tmp;
 142
 143        /*
 144         * Unfortunately, we don't know much about those magic
 145         * numbers. They are taken from Allwinner BSP driver.
 146         */
 147        switch (div) {
 148        case 1:
 149                writel(0x30dc5fc0, &phy->pll);
 150                writel(0x800863C0, &phy->clk);
 151                mdelay(10);
 152                writel(0x00000001, &phy->unk3);
 153                setbits_le32(&phy->pll, BIT(25));
 154                mdelay(200);
 155                tmp = (readl(&phy->status) & 0x1f800) >> 11;
 156                setbits_le32(&phy->pll, BIT(31) | BIT(30));
 157                if (tmp < 0x3d)
 158                        setbits_le32(&phy->pll, tmp + 2);
 159                else
 160                        setbits_le32(&phy->pll, 0x3f);
 161                mdelay(100);
 162                writel(0x01FFFF7F, &phy->ctrl);
 163                writel(0x8063b000, &phy->unk1);
 164                writel(0x0F8246B5, &phy->unk2);
 165                break;
 166        case 2:
 167                writel(0x39dc5040, &phy->pll);
 168                writel(0x80084381, &phy->clk);
 169                mdelay(10);
 170                writel(0x00000001, &phy->unk3);
 171                setbits_le32(&phy->pll, BIT(25));
 172                mdelay(100);
 173                tmp = (readl(&phy->status) & 0x1f800) >> 11;
 174                setbits_le32(&phy->pll, BIT(31) | BIT(30));
 175                setbits_le32(&phy->pll, tmp);
 176                writel(0x01FFFF7F, &phy->ctrl);
 177                writel(0x8063a800, &phy->unk1);
 178                writel(0x0F81C485, &phy->unk2);
 179                break;
 180        case 4:
 181                writel(0x39dc5040, &phy->pll);
 182                writel(0x80084343, &phy->clk);
 183                mdelay(10);
 184                writel(0x00000001, &phy->unk3);
 185                setbits_le32(&phy->pll, BIT(25));
 186                mdelay(100);
 187                tmp = (readl(&phy->status) & 0x1f800) >> 11;
 188                setbits_le32(&phy->pll, BIT(31) | BIT(30));
 189                setbits_le32(&phy->pll, tmp);
 190                writel(0x01FFFF7F, &phy->ctrl);
 191                writel(0x8063b000, &phy->unk1);
 192                writel(0x0F81C405, &phy->unk2);
 193                break;
 194        case 11:
 195                writel(0x39dc5040, &phy->pll);
 196                writel(0x8008430a, &phy->clk);
 197                mdelay(10);
 198                writel(0x00000001, &phy->unk3);
 199                setbits_le32(&phy->pll, BIT(25));
 200                mdelay(100);
 201                tmp = (readl(&phy->status) & 0x1f800) >> 11;
 202                setbits_le32(&phy->pll, BIT(31) | BIT(30));
 203                setbits_le32(&phy->pll, tmp);
 204                writel(0x01FFFF7F, &phy->ctrl);
 205                writel(0x8063b000, &phy->unk1);
 206                writel(0x0F81C405, &phy->unk2);
 207                break;
 208        }
 209}
 210
 211static void sunxi_dw_hdmi_pll_set(uint clk_khz)
 212{
 213        int value, n, m, div = 0, diff;
 214        int best_n = 0, best_m = 0, best_diff = 0x0FFFFFFF;
 215
 216        div = sunxi_dw_hdmi_get_divider(clk_khz * 1000);
 217
 218        /*
 219         * Find the lowest divider resulting in a matching clock. If there
 220         * is no match, pick the closest lower clock, as monitors tend to
 221         * not sync to higher frequencies.
 222         */
 223        for (m = 1; m <= 16; m++) {
 224                n = (m * div * clk_khz) / 24000;
 225
 226                if ((n >= 1) && (n <= 128)) {
 227                        value = (24000 * n) / m / div;
 228                        diff = clk_khz - value;
 229                        if (diff < best_diff) {
 230                                best_diff = diff;
 231                                best_m = m;
 232                                best_n = n;
 233                        }
 234                }
 235        }
 236
 237        clock_set_pll3_factors(best_m, best_n);
 238        debug("dotclock: %dkHz = %dkHz: (24MHz * %d) / %d / %d\n",
 239              clk_khz, (clock_get_pll3() / 1000) / div,
 240              best_n, best_m, div);
 241}
 242
 243static void sunxi_dw_hdmi_lcdc_init(int mux, const struct display_timing *edid,
 244                                    int bpp)
 245{
 246        struct sunxi_ccm_reg * const ccm =
 247                (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
 248        int div = sunxi_dw_hdmi_get_divider(edid->pixelclock.typ);
 249        struct sunxi_lcdc_reg *lcdc;
 250
 251        if (mux == 0) {
 252                lcdc = (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
 253
 254                /* Reset off */
 255                setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_LCD0);
 256
 257                /* Clock on */
 258                setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_LCD0);
 259                writel(CCM_LCD0_CTRL_GATE | CCM_LCD0_CTRL_M(div),
 260                       &ccm->lcd0_clk_cfg);
 261        } else {
 262                lcdc = (struct sunxi_lcdc_reg *)SUNXI_LCD1_BASE;
 263
 264                /* Reset off */
 265                setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_LCD1);
 266
 267                /* Clock on */
 268                setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_LCD1);
 269                writel(CCM_LCD1_CTRL_GATE | CCM_LCD1_CTRL_M(div),
 270                       &ccm->lcd1_clk_cfg);
 271        }
 272
 273        lcdc_init(lcdc);
 274        lcdc_tcon1_mode_set(lcdc, edid, false, false);
 275        lcdc_enable(lcdc, bpp);
 276}
 277
 278static int sunxi_dw_hdmi_phy_cfg(struct dw_hdmi *hdmi, uint mpixelclock)
 279{
 280        sunxi_dw_hdmi_pll_set(mpixelclock/1000);
 281        sunxi_dw_hdmi_phy_set(mpixelclock);
 282
 283        return 0;
 284}
 285
 286static int sunxi_dw_hdmi_read_edid(struct udevice *dev, u8 *buf, int buf_size)
 287{
 288        struct sunxi_dw_hdmi_priv *priv = dev_get_priv(dev);
 289
 290        return dw_hdmi_read_edid(&priv->hdmi, buf, buf_size);
 291}
 292
 293static int sunxi_dw_hdmi_enable(struct udevice *dev, int panel_bpp,
 294                                const struct display_timing *edid)
 295{
 296        struct sunxi_hdmi_phy * const phy =
 297                (struct sunxi_hdmi_phy *)(SUNXI_HDMI_BASE + HDMI_PHY_OFFS);
 298        struct sunxi_dw_hdmi_priv *priv = dev_get_priv(dev);
 299        int ret;
 300
 301        ret = dw_hdmi_enable(&priv->hdmi, edid);
 302        if (ret)
 303                return ret;
 304
 305        sunxi_dw_hdmi_lcdc_init(priv->mux, edid, panel_bpp);
 306
 307        if (edid->flags & DISPLAY_FLAGS_HSYNC_LOW)
 308                setbits_le32(&phy->pol, 0x200);
 309
 310        if (edid->flags & DISPLAY_FLAGS_VSYNC_LOW)
 311                setbits_le32(&phy->pol, 0x100);
 312
 313        setbits_le32(&phy->ctrl, 0xf << 12);
 314
 315        /*
 316         * This is last hdmi access before boot, so scramble addresses
 317         * again or othwerwise BSP driver won't work. Dummy read is
 318         * needed or otherwise last write doesn't get written correctly.
 319         */
 320        (void)readb(SUNXI_HDMI_BASE);
 321        writel(0, &phy->unscramble);
 322
 323        return 0;
 324}
 325
 326static int sunxi_dw_hdmi_probe(struct udevice *dev)
 327{
 328        struct display_plat *uc_plat = dev_get_uclass_platdata(dev);
 329        struct sunxi_dw_hdmi_priv *priv = dev_get_priv(dev);
 330        struct sunxi_ccm_reg * const ccm =
 331                (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
 332        int ret;
 333
 334        /* Set pll3 to 297 MHz */
 335        clock_set_pll3(297000000);
 336
 337        /* Set hdmi parent to pll3 */
 338        clrsetbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_PLL_MASK,
 339                        CCM_HDMI_CTRL_PLL3);
 340
 341        /* Set ahb gating to pass */
 342        setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_HDMI);
 343        setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_HDMI2);
 344        setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_HDMI);
 345        setbits_le32(&ccm->hdmi_slow_clk_cfg, CCM_HDMI_SLOW_CTRL_DDC_GATE);
 346
 347        /* Clock on */
 348        setbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_GATE);
 349
 350        sunxi_dw_hdmi_phy_init();
 351
 352        ret = sunxi_dw_hdmi_wait_for_hpd();
 353        if (ret < 0) {
 354                debug("hdmi can not get hpd signal\n");
 355                return -1;
 356        }
 357
 358        priv->hdmi.ioaddr = SUNXI_HDMI_BASE;
 359        priv->hdmi.i2c_clk_high = 0xd8;
 360        priv->hdmi.i2c_clk_low = 0xfe;
 361        priv->hdmi.reg_io_width = 1;
 362        priv->hdmi.phy_set = sunxi_dw_hdmi_phy_cfg;
 363        priv->mux = uc_plat->source_id;
 364
 365        dw_hdmi_init(&priv->hdmi);
 366
 367        return 0;
 368}
 369
 370static const struct dm_display_ops sunxi_dw_hdmi_ops = {
 371        .read_edid = sunxi_dw_hdmi_read_edid,
 372        .enable = sunxi_dw_hdmi_enable,
 373};
 374
 375U_BOOT_DRIVER(sunxi_dw_hdmi) = {
 376        .name   = "sunxi_dw_hdmi",
 377        .id     = UCLASS_DISPLAY,
 378        .ops    = &sunxi_dw_hdmi_ops,
 379        .probe  = sunxi_dw_hdmi_probe,
 380        .priv_auto_alloc_size = sizeof(struct sunxi_dw_hdmi_priv),
 381};
 382
 383U_BOOT_DEVICE(sunxi_dw_hdmi) = {
 384        .name = "sunxi_dw_hdmi"
 385};
 386