2018-05-06 21:58:06 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
2017-10-27 04:51:52 +00:00
|
|
|
/*
|
|
|
|
* Allwinner LCD driver
|
|
|
|
*
|
|
|
|
* (C) Copyright 2017 Vasily Khoruzhick <anarsoul@gmail.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <common.h>
|
|
|
|
#include <display.h>
|
2020-05-10 17:40:05 +00:00
|
|
|
#include <log.h>
|
2017-10-27 04:51:52 +00:00
|
|
|
#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>
|
2020-10-31 03:38:53 +00:00
|
|
|
#include <asm/global_data.h>
|
2017-10-27 04:51:52 +00:00
|
|
|
#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,
|
2020-12-03 23:55:17 +00:00
|
|
|
.priv_auto = sizeof(struct sunxi_lcd_priv),
|
2017-10-27 04:51:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_MACH_SUN50I
|
2020-12-29 03:34:54 +00:00
|
|
|
U_BOOT_DRVINFO(sunxi_lcd) = {
|
2017-10-27 04:51:52 +00:00
|
|
|
.name = "sunxi_lcd"
|
|
|
|
};
|
|
|
|
#endif
|