// SPDX-License-Identifier: GPL-2.0+ /* * Allwinner LCD driver * * (C) Copyright 2017 Vasily Khoruzhick <anarsoul@gmail.com> */ #include <common.h> #include <display.h> #include <video_bridge.h> #include <backlight.h> #include <dm.h> #include <edid.h> #include <asm/io.h> #include <asm/arch/clock.h> #include <asm/arch/lcdc.h> #include <asm/arch/gpio.h> #include <asm/gpio.h> struct sunxi_lcd_priv { struct display_timing timing; int panel_bpp; }; static void sunxi_lcdc_config_pinmux(void) { #ifdef CONFIG_MACH_SUN50I int pin; for (pin = SUNXI_GPD(0); pin <= SUNXI_GPD(21); pin++) { sunxi_gpio_set_cfgpin(pin, SUNXI_GPD_LCD0); sunxi_gpio_set_drv(pin, 3); } #endif } static int sunxi_lcd_enable(struct udevice *dev, int bpp, const struct display_timing *edid) { struct sunxi_ccm_reg * const ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; struct sunxi_lcdc_reg * const lcdc = (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE; struct sunxi_lcd_priv *priv = dev_get_priv(dev); struct udevice *backlight; int clk_div, clk_double, ret; /* Reset off */ setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_LCD0); /* Clock on */ setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_LCD0); lcdc_init(lcdc); sunxi_lcdc_config_pinmux(); lcdc_pll_set(ccm, 0, edid->pixelclock.typ / 1000, &clk_div, &clk_double, false); lcdc_tcon0_mode_set(lcdc, edid, clk_div, false, priv->panel_bpp, CONFIG_VIDEO_LCD_DCLK_PHASE); lcdc_enable(lcdc, priv->panel_bpp); ret = uclass_get_device(UCLASS_PANEL_BACKLIGHT, 0, &backlight); if (!ret) backlight_enable(backlight); return 0; } static int sunxi_lcd_read_timing(struct udevice *dev, struct display_timing *timing) { struct sunxi_lcd_priv *priv = dev_get_priv(dev); memcpy(timing, &priv->timing, sizeof(struct display_timing)); return 0; } static int sunxi_lcd_probe(struct udevice *dev) { struct udevice *cdev; struct sunxi_lcd_priv *priv = dev_get_priv(dev); int ret; int node, timing_node, val; #ifdef CONFIG_VIDEO_BRIDGE /* Try to get timings from bridge first */ ret = uclass_get_device(UCLASS_VIDEO_BRIDGE, 0, &cdev); if (!ret) { u8 edid[EDID_SIZE]; int channel_bpp; ret = video_bridge_attach(cdev); if (ret) { debug("video bridge attach failed: %d\n", ret); return ret; } ret = video_bridge_read_edid(cdev, edid, EDID_SIZE); if (ret > 0) { ret = edid_get_timing(edid, ret, &priv->timing, &channel_bpp); priv->panel_bpp = channel_bpp * 3; if (!ret) return ret; } } #endif /* Fallback to timings from DT if there's no bridge or * if reading EDID failed */ ret = uclass_get_device(UCLASS_PANEL, 0, &cdev); if (ret) { debug("video panel not found: %d\n", ret); return ret; } if (fdtdec_decode_display_timing(gd->fdt_blob, dev_of_offset(cdev), 0, &priv->timing)) { debug("%s: Failed to decode display timing\n", __func__); return -EINVAL; } timing_node = fdt_subnode_offset(gd->fdt_blob, dev_of_offset(cdev), "display-timings"); node = fdt_first_subnode(gd->fdt_blob, timing_node); val = fdtdec_get_int(gd->fdt_blob, node, "bits-per-pixel", -1); if (val != -1) priv->panel_bpp = val; else priv->panel_bpp = 18; return 0; } static const struct dm_display_ops sunxi_lcd_ops = { .read_timing = sunxi_lcd_read_timing, .enable = sunxi_lcd_enable, }; U_BOOT_DRIVER(sunxi_lcd) = { .name = "sunxi_lcd", .id = UCLASS_DISPLAY, .ops = &sunxi_lcd_ops, .probe = sunxi_lcd_probe, .priv_auto_alloc_size = sizeof(struct sunxi_lcd_priv), }; #ifdef CONFIG_MACH_SUN50I U_BOOT_DEVICE(sunxi_lcd) = { .name = "sunxi_lcd" }; #endif