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