uboot/drivers/video/sunxi/sunxi_lcd.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Allwinner LCD driver
   4 *
   5 * (C) Copyright 2017 Vasily Khoruzhick <anarsoul@gmail.com>
   6 */
   7
   8#include <common.h>
   9#include <display.h>
  10#include <log.h>
  11#include <video_bridge.h>
  12#include <backlight.h>
  13#include <dm.h>
  14#include <edid.h>
  15#include <asm/io.h>
  16#include <asm/arch/clock.h>
  17#include <asm/arch/lcdc.h>
  18#include <asm/global_data.h>
  19#include <asm/gpio.h>
  20
  21struct sunxi_lcd_priv {
  22        struct display_timing timing;
  23        int panel_bpp;
  24};
  25
  26static void sunxi_lcdc_config_pinmux(void)
  27{
  28#ifdef CONFIG_MACH_SUN50I
  29        int pin;
  30
  31        for (pin = SUNXI_GPD(0); pin <= SUNXI_GPD(21); pin++) {
  32                sunxi_gpio_set_cfgpin(pin, SUNXI_GPD_LCD0);
  33                sunxi_gpio_set_drv(pin, 3);
  34        }
  35#endif
  36}
  37
  38static int sunxi_lcd_enable(struct udevice *dev, int bpp,
  39                            const struct display_timing *edid)
  40{
  41        struct sunxi_ccm_reg * const ccm =
  42               (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
  43        struct sunxi_lcdc_reg * const lcdc =
  44               (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
  45        struct sunxi_lcd_priv *priv = dev_get_priv(dev);
  46        struct udevice *backlight;
  47        int clk_div, clk_double, ret;
  48
  49        /* Reset off */
  50        setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_LCD0);
  51        /* Clock on */
  52        setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_LCD0);
  53
  54        lcdc_init(lcdc);
  55        sunxi_lcdc_config_pinmux();
  56        lcdc_pll_set(ccm, 0, edid->pixelclock.typ / 1000,
  57                     &clk_div, &clk_double, false);
  58        lcdc_tcon0_mode_set(lcdc, edid, clk_div, false,
  59                            priv->panel_bpp, CONFIG_VIDEO_LCD_DCLK_PHASE);
  60        lcdc_enable(lcdc, priv->panel_bpp);
  61
  62        ret = uclass_get_device(UCLASS_PANEL_BACKLIGHT, 0, &backlight);
  63        if (!ret)
  64                backlight_enable(backlight);
  65
  66        return 0;
  67}
  68
  69static int sunxi_lcd_read_timing(struct udevice *dev,
  70                                 struct display_timing *timing)
  71{
  72        struct sunxi_lcd_priv *priv = dev_get_priv(dev);
  73
  74        memcpy(timing, &priv->timing, sizeof(struct display_timing));
  75
  76        return 0;
  77}
  78
  79static int sunxi_lcd_probe(struct udevice *dev)
  80{
  81        struct udevice *cdev;
  82        struct sunxi_lcd_priv *priv = dev_get_priv(dev);
  83        int ret;
  84        int node, timing_node, val;
  85
  86#ifdef CONFIG_VIDEO_BRIDGE
  87        /* Try to get timings from bridge first */
  88        ret = uclass_get_device(UCLASS_VIDEO_BRIDGE, 0, &cdev);
  89        if (!ret) {
  90                u8 edid[EDID_SIZE];
  91                int channel_bpp;
  92
  93                ret = video_bridge_attach(cdev);
  94                if (ret) {
  95                        debug("video bridge attach failed: %d\n", ret);
  96                        return ret;
  97                }
  98                ret = video_bridge_read_edid(cdev, edid, EDID_SIZE);
  99                if (ret > 0) {
 100                        ret = edid_get_timing(edid, ret,
 101                                              &priv->timing, &channel_bpp);
 102                        priv->panel_bpp = channel_bpp * 3;
 103                        if (!ret)
 104                                return ret;
 105                }
 106        }
 107#endif
 108
 109        /* Fallback to timings from DT if there's no bridge or
 110         * if reading EDID failed
 111         */
 112        ret = uclass_get_device(UCLASS_PANEL, 0, &cdev);
 113        if (ret) {
 114                debug("video panel not found: %d\n", ret);
 115                return ret;
 116        }
 117
 118        if (fdtdec_decode_display_timing(gd->fdt_blob, dev_of_offset(cdev),
 119                                         0, &priv->timing)) {
 120                debug("%s: Failed to decode display timing\n", __func__);
 121                return -EINVAL;
 122        }
 123        timing_node = fdt_subnode_offset(gd->fdt_blob, dev_of_offset(cdev),
 124                                         "display-timings");
 125        node = fdt_first_subnode(gd->fdt_blob, timing_node);
 126        val = fdtdec_get_int(gd->fdt_blob, node, "bits-per-pixel", -1);
 127        if (val != -1)
 128                priv->panel_bpp = val;
 129        else
 130                priv->panel_bpp = 18;
 131
 132        return 0;
 133}
 134
 135static const struct dm_display_ops sunxi_lcd_ops = {
 136        .read_timing = sunxi_lcd_read_timing,
 137        .enable = sunxi_lcd_enable,
 138};
 139
 140U_BOOT_DRIVER(sunxi_lcd) = {
 141        .name   = "sunxi_lcd",
 142        .id     = UCLASS_DISPLAY,
 143        .ops    = &sunxi_lcd_ops,
 144        .probe  = sunxi_lcd_probe,
 145        .priv_auto      = sizeof(struct sunxi_lcd_priv),
 146};
 147
 148#ifdef CONFIG_MACH_SUN50I
 149U_BOOT_DRVINFO(sunxi_lcd) = {
 150        .name = "sunxi_lcd"
 151};
 152#endif
 153