123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- // SPDX-License-Identifier: GPL-2.0+
- /*
- * Allwinner LCD driver
- *
- * (C) Copyright 2017 Vasily Khoruzhick <anarsoul@gmail.com>
- */
- #include <common.h>
- #include <display.h>
- #include <log.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 = sizeof(struct sunxi_lcd_priv),
- };
- #ifdef CONFIG_MACH_SUN50I
- U_BOOT_DRVINFO(sunxi_lcd) = {
- .name = "sunxi_lcd"
- };
- #endif
|