154 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			154 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			C
		
	
	
	
// 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/global_data.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
 |