summaryrefslogtreecommitdiffstats
path: root/drivers/video
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video')
-rw-r--r--drivers/video/Kconfig88
-rw-r--r--drivers/video/Makefile4
-rw-r--r--drivers/video/cfb_console.c39
-rw-r--r--drivers/video/hitachi_tx18d42vm_lcd.c81
-rw-r--r--drivers/video/hitachi_tx18d42vm_lcd.h9
-rw-r--r--drivers/video/sed13806.c5
-rw-r--r--drivers/video/ssd2828.c436
-rw-r--r--drivers/video/ssd2828.h128
-rw-r--r--drivers/video/sunxi_display.c213
-rw-r--r--drivers/video/tegra.c54
-rw-r--r--drivers/video/vesa_fb.c64
-rw-r--r--drivers/video/x86_fb.c38
12 files changed, 1037 insertions, 122 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index fdbf3f64f2..51728b366f 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -1,8 +1,90 @@
-config VIDEO_X86
- bool "Enable x86 video driver support"
+config VIDEO_VESA
+ bool "Enable VESA video driver support"
depends on X86
default n
help
Turn on this option to enable a very simple driver which uses vesa
to discover the video mode and then provides a frame buffer for use
- by U-Boot.
+ by U-Boot. This can in principle be used with any platform that
+ supports PCI and video cards that support VESA BIOS Extension (VBE).
+
+config VIDEO_LCD_SSD2828
+ bool "SSD2828 bridge chip"
+ default n
+ ---help---
+ Support for the SSD2828 bridge chip, which can take pixel data coming
+ from a parallel LCD interface and translate it on the fly into MIPI DSI
+ interface for driving a MIPI compatible LCD panel. It uses SPI for
+ configuration.
+
+config VIDEO_LCD_SSD2828_TX_CLK
+ int "SSD2828 TX_CLK frequency (in MHz)"
+ depends on VIDEO_LCD_SSD2828
+ default 0
+ ---help---
+ The frequency of the crystal, which is clocking SSD2828. It may be
+ anything in the 8MHz-30MHz range and the exact value should be
+ retrieved from the board schematics. Or in the case of Allwinner
+ hardware, it can be usually found as 'lcd_xtal_freq' variable in
+ FEX files. It can be also set to 0 for selecting PCLK from the
+ parallel LCD interface instead of TX_CLK as the PLL clock source.
+
+config VIDEO_LCD_SSD2828_RESET
+ string "RESET pin of SSD2828"
+ depends on VIDEO_LCD_SSD2828
+ default ""
+ ---help---
+ The reset pin of SSD2828 chip. This takes a string in the format
+ understood by 'name_to_gpio' function, e.g. PH1 for pin 1 of port H.
+
+config VIDEO_LCD_HITACHI_TX18D42VM
+ bool "Hitachi tx18d42vm LVDS LCD panel support"
+ depends on VIDEO
+ default n
+ ---help---
+ Support for Hitachi tx18d42vm LVDS LCD panels, these panels have a
+ lcd controller which needs to be initialized over SPI, once that is
+ done they work like a regular LVDS panel.
+
+config VIDEO_LCD_SPI_CS
+ string "SPI CS pin for LCD related config job"
+ depends on VIDEO_LCD_SSD2828 || VIDEO_LCD_HITACHI_TX18D42VM
+ default ""
+ ---help---
+ This is one of the SPI communication pins, involved in setting up a
+ working LCD configuration. The exact role of SPI may differ for
+ different hardware setups. The option takes a string in the format
+ understood by 'name_to_gpio' function, e.g. PH1 for pin 1 of port H.
+
+config VIDEO_LCD_SPI_SCLK
+ string "SPI SCLK pin for LCD related config job"
+ depends on VIDEO_LCD_SSD2828 || VIDEO_LCD_HITACHI_TX18D42VM
+ default ""
+ ---help---
+ This is one of the SPI communication pins, involved in setting up a
+ working LCD configuration. The exact role of SPI may differ for
+ different hardware setups. The option takes a string in the format
+ understood by 'name_to_gpio' function, e.g. PH1 for pin 1 of port H.
+
+config VIDEO_LCD_SPI_MOSI
+ string "SPI MOSI pin for LCD related config job"
+ depends on VIDEO_LCD_SSD2828 || VIDEO_LCD_HITACHI_TX18D42VM
+ default ""
+ ---help---
+ This is one of the SPI communication pins, involved in setting up a
+ working LCD configuration. The exact role of SPI may differ for
+ different hardware setups. The option takes a string in the format
+ understood by 'name_to_gpio' function, e.g. PH1 for pin 1 of port H.
+
+config VIDEO_LCD_SPI_MISO
+ string "SPI MISO pin for LCD related config job (optional)"
+ depends on VIDEO_LCD_SSD2828
+ default ""
+ ---help---
+ This is one of the SPI communication pins, involved in setting up a
+ working LCD configuration. The exact role of SPI may differ for
+ different hardware setups. If wired up, this pin may provide additional
+ useful functionality. Such as bi-directional communication with the
+ hardware and LCD panel id retrieval (if the panel can report it). The
+ option takes a string in the format understood by 'name_to_gpio'
+ function, e.g. PH1 for pin 1 of port H.
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 42b1eaaf76..af2d47bd75 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -29,6 +29,8 @@ obj-$(CONFIG_VIDEO_COREBOOT) += coreboot_fb.o
obj-$(CONFIG_VIDEO_CT69000) += ct69000.o videomodes.o
obj-$(CONFIG_VIDEO_DA8XX) += da8xx-fb.o videomodes.o
obj-$(CONFIG_VIDEO_IMX25LCDC) += imx25lcdc.o videomodes.o
+obj-$(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM) += hitachi_tx18d42vm_lcd.o
+obj-$(CONFIG_VIDEO_LCD_SSD2828) += ssd2828.o
obj-$(CONFIG_VIDEO_MB862xx) += mb862xx.o videomodes.o
obj-$(CONFIG_VIDEO_MB86R0xGDC) += mb86r0xgdc.o videomodes.o
obj-$(CONFIG_VIDEO_MX3) += mx3fb.o videomodes.o
@@ -42,7 +44,7 @@ obj-$(CONFIG_VIDEO_SMI_LYNXEM) += smiLynxEM.o videomodes.o
obj-$(CONFIG_VIDEO_SUNXI) += sunxi_display.o videomodes.o
obj-$(CONFIG_VIDEO_TEGRA) += tegra.o
obj-$(CONFIG_VIDEO_VCXK) += bus_vcxk.o
-obj-$(CONFIG_VIDEO_X86) += x86_fb.o
+obj-$(CONFIG_VIDEO_VESA) += vesa_fb.o
obj-$(CONFIG_FORMIKE) += formike.o
obj-$(CONFIG_AM335X_LCD) += am335x-fb.o
obj-$(CONFIG_VIDEO_PARADE) += parade.o
diff --git a/drivers/video/cfb_console.c b/drivers/video/cfb_console.c
index a653bb4168..a81affa333 100644
--- a/drivers/video/cfb_console.c
+++ b/drivers/video/cfb_console.c
@@ -117,24 +117,11 @@
* Defines for the SED13806 driver
*/
#ifdef CONFIG_VIDEO_SED13806
-
-#ifndef CONFIG_TOTAL5200
#define VIDEO_FB_LITTLE_ENDIAN
-#endif
#define VIDEO_HW_RECTFILL
#define VIDEO_HW_BITBLT
#endif
-/*
- * Defines for the SED13806 driver
- */
-#ifdef CONFIG_VIDEO_SM501
-
-#ifdef CONFIG_HH405
-#define VIDEO_FB_LITTLE_ENDIAN
-#endif
-#endif
-
#ifdef CONFIG_VIDEO_MXS
#define VIDEO_FB_16BPP_WORD_SWAP
#endif
@@ -312,7 +299,11 @@ void console_cursor(int state);
#define CONSOLE_ROW_SECOND (video_console_address + CONSOLE_ROW_SIZE)
#define CONSOLE_ROW_LAST (video_console_address + CONSOLE_SIZE - CONSOLE_ROW_SIZE)
#define CONSOLE_SIZE (CONSOLE_ROW_SIZE * CONSOLE_ROWS)
-#define CONSOLE_SCROLL_SIZE (CONSOLE_SIZE - CONSOLE_ROW_SIZE)
+
+/* By default we scroll by a single line */
+#ifndef CONFIG_CONSOLE_SCROLL_LINES
+#define CONFIG_CONSOLE_SCROLL_LINES 1
+#endif
/* Macros */
#ifdef VIDEO_FB_LITTLE_ENDIAN
@@ -753,26 +744,33 @@ static void console_clear_line(int line, int begin, int end)
static void console_scrollup(void)
{
+ const int rows = CONFIG_CONSOLE_SCROLL_LINES;
+ int i;
+
/* copy up rows ignoring the first one */
#ifdef VIDEO_HW_BITBLT
video_hw_bitblt(VIDEO_PIXEL_SIZE, /* bytes per pixel */
0, /* source pos x */
video_logo_height +
- VIDEO_FONT_HEIGHT, /* source pos y */
+ VIDEO_FONT_HEIGHT * rows, /* source pos y */
0, /* dest pos x */
video_logo_height, /* dest pos y */
VIDEO_VISIBLE_COLS, /* frame width */
VIDEO_VISIBLE_ROWS
- video_logo_height
- - VIDEO_FONT_HEIGHT /* frame height */
+ - VIDEO_FONT_HEIGHT * rows /* frame height */
);
#else
- memcpyl(CONSOLE_ROW_FIRST, CONSOLE_ROW_SECOND,
- CONSOLE_SCROLL_SIZE >> 2);
+ memcpyl(CONSOLE_ROW_FIRST, CONSOLE_ROW_FIRST + rows * CONSOLE_ROW_SIZE,
+ (CONSOLE_SIZE - CONSOLE_ROW_SIZE * rows) >> 2);
#endif
/* clear the last one */
- console_clear_line(CONSOLE_ROWS - 1, 0, CONSOLE_COLS - 1);
+ for (i = 1; i <= rows; i++)
+ console_clear_line(CONSOLE_ROWS - i, 0, CONSOLE_COLS - 1);
+
+ /* Decrement row number */
+ console_row -= rows;
}
static void console_back(void)
@@ -884,9 +882,6 @@ static void console_newline(int n)
if (console_row >= CONSOLE_ROWS) {
/* Scroll everything up */
console_scrollup();
-
- /* Decrement row number */
- console_row = CONSOLE_ROWS - 1;
}
}
diff --git a/drivers/video/hitachi_tx18d42vm_lcd.c b/drivers/video/hitachi_tx18d42vm_lcd.c
new file mode 100644
index 0000000000..1ce4a8c93e
--- /dev/null
+++ b/drivers/video/hitachi_tx18d42vm_lcd.c
@@ -0,0 +1,81 @@
+/*
+ * Hitachi tx18d42vm LVDS LCD panel driver
+ *
+ * (C) Copyright 2015 Hans de Goede <hdegoede@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+
+#include <asm/gpio.h>
+#include <errno.h>
+
+/*
+ * Very simple write only SPI support, this does not use the generic SPI infra
+ * because that assumes R/W SPI, requiring a MISO pin. Also the necessary glue
+ * code alone would be larger then this minimal version.
+ */
+static void lcd_panel_spi_write(int cs, int clk, int mosi,
+ unsigned int data, int bits)
+{
+ int i, offset;
+
+ gpio_direction_output(cs, 0);
+ for (i = 0; i < bits; i++) {
+ gpio_direction_output(clk, 0);
+ offset = (bits - 1) - i;
+ gpio_direction_output(mosi, (data >> offset) & 1);
+ udelay(2);
+ gpio_direction_output(clk, 1);
+ udelay(2);
+ }
+ gpio_direction_output(cs, 1);
+ udelay(2);
+}
+
+int hitachi_tx18d42vm_init(void)
+{
+ const u16 init_data[] = {
+ 0x0029, /* reset */
+ 0x0025, /* standby */
+ 0x0840, /* enable normally black */
+ 0x0430, /* enable FRC/dither */
+ 0x385f, /* enter test mode(1) */
+ 0x3ca4, /* enter test mode(2) */
+ 0x3409, /* enable SDRRS, enlarge OE width */
+ 0x4041, /* adopt 2 line / 1 dot */
+ };
+ int i, cs, clk, mosi, ret = 0;
+
+ cs = name_to_gpio(CONFIG_VIDEO_LCD_SPI_CS);
+ clk = name_to_gpio(CONFIG_VIDEO_LCD_SPI_SCLK);
+ mosi = name_to_gpio(CONFIG_VIDEO_LCD_SPI_MOSI);
+
+ if (cs == -1 || clk == -1 || mosi == 1) {
+ printf("Error tx18d42vm spi gpio config is invalid\n");
+ return -EINVAL;
+ }
+
+ if (gpio_request(cs, "tx18d42vm-spi-cs") != 0 ||
+ gpio_request(clk, "tx18d42vm-spi-clk") != 0 ||
+ gpio_request(mosi, "tx18d42vm-spi-mosi") != 0) {
+ printf("Error cannot request tx18d42vm spi gpios\n");
+ ret = -EBUSY;
+ goto out;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(init_data); i++)
+ lcd_panel_spi_write(cs, clk, mosi, init_data[i], 16);
+
+ mdelay(50); /* All the tx18d42vm drivers have a delay here ? */
+
+ lcd_panel_spi_write(cs, clk, mosi, 0x00ad, 16); /* display on */
+
+out:
+ gpio_free(mosi);
+ gpio_free(clk);
+ gpio_free(cs);
+
+ return ret;
+}
diff --git a/drivers/video/hitachi_tx18d42vm_lcd.h b/drivers/video/hitachi_tx18d42vm_lcd.h
new file mode 100644
index 0000000000..1b728005f6
--- /dev/null
+++ b/drivers/video/hitachi_tx18d42vm_lcd.h
@@ -0,0 +1,9 @@
+/*
+ * Hitachi tx18d42vm LVDS LCD panel driver
+ *
+ * (C) Copyright 2015 Hans de Goede <hdegoede@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+void hitachi_tx18d42vm_init(void);
diff --git a/drivers/video/sed13806.c b/drivers/video/sed13806.c
index da653c0f51..cd7fac6f97 100644
--- a/drivers/video/sed13806.c
+++ b/drivers/video/sed13806.c
@@ -18,13 +18,8 @@
#define writeByte(ptrReg,value) \
*(volatile unsigned char *)(sed13806.isaBase + ptrReg) = value
-#ifdef CONFIG_TOTAL5200
-#define writeWord(ptrReg,value) \
- (*(volatile unsigned short *)(sed13806.isaBase + ptrReg) = value)
-#else
#define writeWord(ptrReg,value) \
(*(volatile unsigned short *)(sed13806.isaBase + ptrReg) = ((value >> 8 ) & 0xff) | ((value << 8) & 0xff00))
-#endif
GraphicDevice sed13806;
diff --git a/drivers/video/ssd2828.c b/drivers/video/ssd2828.c
new file mode 100644
index 0000000000..8b09082254
--- /dev/null
+++ b/drivers/video/ssd2828.c
@@ -0,0 +1,436 @@
+/*
+ * (C) 2015 Siarhei Siamashka <siarhei.siamashka@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/*
+ * Support for the SSD2828 bridge chip, which can take pixel data coming
+ * from a parallel LCD interface and translate it on the flight into MIPI DSI
+ * interface for driving a MIPI compatible TFT display.
+ */
+
+#include <common.h>
+#include <mipi_display.h>
+#include <asm/arch/gpio.h>
+#include <asm/gpio.h>
+
+#include "videomodes.h"
+#include "ssd2828.h"
+
+#define SSD2828_DIR 0xB0
+#define SSD2828_VICR1 0xB1
+#define SSD2828_VICR2 0xB2
+#define SSD2828_VICR3 0xB3
+#define SSD2828_VICR4 0xB4
+#define SSD2828_VICR5 0xB5
+#define SSD2828_VICR6 0xB6
+#define SSD2828_CFGR 0xB7
+#define SSD2828_VCR 0xB8
+#define SSD2828_PCR 0xB9
+#define SSD2828_PLCR 0xBA
+#define SSD2828_CCR 0xBB
+#define SSD2828_PSCR1 0xBC
+#define SSD2828_PSCR2 0xBD
+#define SSD2828_PSCR3 0xBE
+#define SSD2828_PDR 0xBF
+#define SSD2828_OCR 0xC0
+#define SSD2828_MRSR 0xC1
+#define SSD2828_RDCR 0xC2
+#define SSD2828_ARSR 0xC3
+#define SSD2828_LCR 0xC4
+#define SSD2828_ICR 0xC5
+#define SSD2828_ISR 0xC6
+#define SSD2828_ESR 0xC7
+#define SSD2828_DAR1 0xC9
+#define SSD2828_DAR2 0xCA
+#define SSD2828_DAR3 0xCB
+#define SSD2828_DAR4 0xCC
+#define SSD2828_DAR5 0xCD
+#define SSD2828_DAR6 0xCE
+#define SSD2828_HTTR1 0xCF
+#define SSD2828_HTTR2 0xD0
+#define SSD2828_LRTR1 0xD1
+#define SSD2828_LRTR2 0xD2
+#define SSD2828_TSR 0xD3
+#define SSD2828_LRR 0xD4
+#define SSD2828_PLLR 0xD5
+#define SSD2828_TR 0xD6
+#define SSD2828_TECR 0xD7
+#define SSD2828_ACR1 0xD8
+#define SSD2828_ACR2 0xD9
+#define SSD2828_ACR3 0xDA
+#define SSD2828_ACR4 0xDB
+#define SSD2828_IOCR 0xDC
+#define SSD2828_VICR7 0xDD
+#define SSD2828_LCFR 0xDE
+#define SSD2828_DAR7 0xDF
+#define SSD2828_PUCR1 0xE0
+#define SSD2828_PUCR2 0xE1
+#define SSD2828_PUCR3 0xE2
+#define SSD2828_CBCR1 0xE9
+#define SSD2828_CBCR2 0xEA
+#define SSD2828_CBSR 0xEB
+#define SSD2828_ECR 0xEC
+#define SSD2828_VSDR 0xED
+#define SSD2828_TMR 0xEE
+#define SSD2828_GPIO1 0xEF
+#define SSD2828_GPIO2 0xF0
+#define SSD2828_DLYA01 0xF1
+#define SSD2828_DLYA23 0xF2
+#define SSD2828_DLYB01 0xF3
+#define SSD2828_DLYB23 0xF4
+#define SSD2828_DLYC01 0xF5
+#define SSD2828_DLYC23 0xF6
+#define SSD2828_ACR5 0xF7
+#define SSD2828_RR 0xFF
+
+#define SSD2828_CFGR_HS (1 << 0)
+#define SSD2828_CFGR_CKE (1 << 1)
+#define SSD2828_CFGR_SLP (1 << 2)
+#define SSD2828_CFGR_VEN (1 << 3)
+#define SSD2828_CFGR_HCLK (1 << 4)
+#define SSD2828_CFGR_CSS (1 << 5)
+#define SSD2828_CFGR_DCS (1 << 6)
+#define SSD2828_CFGR_REN (1 << 7)
+#define SSD2828_CFGR_ECD (1 << 8)
+#define SSD2828_CFGR_EOT (1 << 9)
+#define SSD2828_CFGR_LPE (1 << 10)
+#define SSD2828_CFGR_TXD (1 << 11)
+
+#define SSD2828_VIDEO_MODE_NON_BURST_WITH_SYNC_PULSES (0 << 2)
+#define SSD2828_VIDEO_MODE_NON_BURST_WITH_SYNC_EVENTS (1 << 2)
+#define SSD2828_VIDEO_MODE_BURST (2 << 2)
+
+#define SSD2828_VIDEO_PIXEL_FORMAT_16BPP 0
+#define SSD2828_VIDEO_PIXEL_FORMAT_18BPP_PACKED 1
+#define SSD2828_VIDEO_PIXEL_FORMAT_18BPP_LOOSELY_PACKED 2
+#define SSD2828_VIDEO_PIXEL_FORMAT_24BPP 3
+
+#define SSD2828_LP_CLOCK_DIVIDER(n) (((n) - 1) & 0x3F)
+
+/*
+ * SPI transfer, using the "24-bit 3 wire" mode (that's how it is called in
+ * the SSD2828 documentation). The 'dout' input parameter specifies 24-bits
+ * of data to be written to SSD2828. Returns the lowest 16-bits of data,
+ * that is received back.
+ */
+static u32 soft_spi_xfer_24bit_3wire(const struct ssd2828_config *drv, u32 dout)
+{
+ int j, bitlen = 24;
+ u32 tmpdin = 0;
+ /*
+ * According to the "24 Bit 3 Wire SPI Interface Timing Characteristics"
+ * and "TX_CLK Timing Characteristics" tables in the SSD2828 datasheet,
+ * the lowest possible 'tx_clk' clock frequency is 8MHz, and SPI runs
+ * at 1/8 of that after reset. So using 1 microsecond delays is safe in
+ * the main loop. But the delays around chip select pin manipulations
+ * need to be longer (up to 16 'tx_clk' cycles, or 2 microseconds in
+ * the worst case).
+ */
+ const int spi_delay_us = 1;
+ const int spi_cs_delay_us = 2;
+
+ gpio_set_value(drv->csx_pin, 0);
+ udelay(spi_cs_delay_us);
+ for (j = bitlen - 1; j >= 0; j--) {
+ gpio_set_value(drv->sck_pin, 0);
+ gpio_set_value(drv->sdi_pin, (dout & (1 << j)) != 0);
+ udelay(spi_delay_us);
+ if (drv->sdo_pin != -1)
+ tmpdin = (tmpdin << 1) | gpio_get_value(drv->sdo_pin);
+ gpio_set_value(drv->sck_pin, 1);
+ udelay(spi_delay_us);
+ }
+ udelay(spi_cs_delay_us);
+ gpio_set_value(drv->csx_pin, 1);
+ udelay(spi_cs_delay_us);
+ return tmpdin & 0xFFFF;
+}
+
+/*
+ * Read from a SSD2828 hardware register (regnum >= 0xB0)
+ */
+static u32 read_hw_register(const struct ssd2828_config *cfg, u8 regnum)
+{
+ soft_spi_xfer_24bit_3wire(cfg, 0x700000 | regnum);
+ return soft_spi_xfer_24bit_3wire(cfg, 0x730000);
+}
+
+/*
+ * Write to a SSD2828 hardware register (regnum >= 0xB0)
+ */
+static void write_hw_register(const struct ssd2828_config *cfg, u8 regnum,
+ u16 val)
+{
+ soft_spi_xfer_24bit_3wire(cfg, 0x700000 | regnum);
+ soft_spi_xfer_24bit_3wire(cfg, 0x720000 | val);
+}
+
+/*
+ * Send MIPI command to the LCD panel (cmdnum < 0xB0)
+ */
+static void send_mipi_dcs_command(const struct ssd2828_config *cfg, u8 cmdnum)
+{
+ /* Set packet size to 1 (a single command with no parameters) */
+ write_hw_register(cfg, SSD2828_PSCR1, 1);
+ /* Send the command */
+ write_hw_register(cfg, SSD2828_PDR, cmdnum);
+}
+
+/*
+ * Reset SSD2828
+ */
+static void ssd2828_reset(const struct ssd2828_config *cfg)
+{
+ /* RESET needs 10 milliseconds according to the datasheet */
+ gpio_set_value(cfg->reset_pin, 0);
+ mdelay(10);
+ gpio_set_value(cfg->reset_pin, 1);
+ mdelay(10);
+}
+
+static int ssd2828_enable_gpio(const struct ssd2828_config *cfg)
+{
+ if (gpio_request(cfg->csx_pin, "ssd2828_csx")) {
+ printf("SSD2828: request for 'ssd2828_csx' pin failed\n");
+ return 1;
+ }
+ if (gpio_request(cfg->sck_pin, "ssd2828_sck")) {
+ gpio_free(cfg->csx_pin);
+ printf("SSD2828: request for 'ssd2828_sck' pin failed\n");
+ return 1;
+ }
+ if (gpio_request(cfg->sdi_pin, "ssd2828_sdi")) {
+ gpio_free(cfg->csx_pin);
+ gpio_free(cfg->sck_pin);
+ printf("SSD2828: request for 'ssd2828_sdi' pin failed\n");
+ return 1;
+ }
+ if (gpio_request(cfg->reset_pin, "ssd2828_reset")) {
+ gpio_free(cfg->csx_pin);
+ gpio_free(cfg->sck_pin);
+ gpio_free(cfg->sdi_pin);
+ printf("SSD2828: request for 'ssd2828_reset' pin failed\n");
+ return 1;
+ }
+ if (cfg->sdo_pin != -1 && gpio_request(cfg->sdo_pin, "ssd2828_sdo")) {
+ gpio_free(cfg->csx_pin);
+ gpio_free(cfg->sck_pin);
+ gpio_free(cfg->sdi_pin);
+ gpio_free(cfg->reset_pin);
+ printf("SSD2828: request for 'ssd2828_sdo' pin failed\n");
+ return 1;
+ }
+ gpio_direction_output(cfg->reset_pin, 0);
+ gpio_direction_output(cfg->csx_pin, 1);
+ gpio_direction_output(cfg->sck_pin, 1);
+ gpio_direction_output(cfg->sdi_pin, 1);
+ if (cfg->sdo_pin != -1)
+ gpio_direction_input(cfg->sdo_pin);
+
+ return 0;
+}
+
+static int ssd2828_free_gpio(const struct ssd2828_config *cfg)
+{
+ gpio_free(cfg->csx_pin);
+ gpio_free(cfg->sck_pin);
+ gpio_free(cfg->sdi_pin);
+ gpio_free(cfg->reset_pin);
+ if (cfg->sdo_pin != -1)
+ gpio_free(cfg->sdo_pin);
+ return 1;
+}
+
+/*
+ * PLL configuration register settings.
+ *
+ * See the "PLL Configuration Register Description" in the SSD2828 datasheet.
+ */
+static u32 construct_pll_config(u32 desired_pll_freq_kbps,
+ u32 reference_freq_khz)
+{
+ u32 div_factor = 1, mul_factor, fr = 0;
+ u32 output_freq_kbps;
+
+ /* The intermediate clock after division can't be less than 5MHz */
+ while (reference_freq_khz / (div_factor + 1) >= 5000)
+ div_factor++;
+ if (div_factor > 31)
+ div_factor = 31;
+
+ mul_factor = DIV_ROUND_UP(desired_pll_freq_kbps * div_factor,
+ reference_freq_khz);
+
+ output_freq_kbps = reference_freq_khz * mul_factor / div_factor;
+
+ if (output_freq_kbps >= 501000)
+ fr = 3;
+ else if (output_freq_kbps >= 251000)
+ fr = 2;
+ else if (output_freq_kbps >= 126000)
+ fr = 1;
+
+ return (fr << 14) | (div_factor << 8) | mul_factor;
+}
+
+static u32 decode_pll_config(u32 pll_config, u32 reference_freq_khz)
+{
+ u32 mul_factor = pll_config & 0xFF;
+ u32 div_factor = (pll_config >> 8) & 0x1F;
+ if (mul_factor == 0)
+ mul_factor = 1;
+ if (div_factor == 0)
+ div_factor = 1;
+ return reference_freq_khz * mul_factor / div_factor;
+}
+
+static int ssd2828_configure_video_interface(const struct ssd2828_config *cfg,
+ const struct ctfb_res_modes *mode)
+{
+ u32 val;
+
+ /* RGB Interface Control Register 1 */
+ write_hw_register(cfg, SSD2828_VICR1, (mode->vsync_len << 8) |
+ (mode->hsync_len));
+
+ /* RGB Interface Control Register 2 */
+ u32 vbp = mode->vsync_len + mode->upper_margin;
+ u32 hbp = mode->hsync_len + mode->left_margin;
+ write_hw_register(cfg, SSD2828_VICR2, (vbp << 8) | hbp);
+
+ /* RGB Interface Control Register 3 */
+ write_hw_register(cfg, SSD2828_VICR3, (mode->lower_margin << 8) |
+ (mode->right_margin));
+
+ /* RGB Interface Control Register 4 */
+ write_hw_register(cfg, SSD2828_VICR4, mode->xres);
+
+ /* RGB Interface Control Register 5 */
+ write_hw_register(cfg, SSD2828_VICR5, mode->yres);
+
+ /* RGB Interface Control Register 6 */
+ val = SSD2828_VIDEO_MODE_BURST;
+ switch (cfg->ssd2828_color_depth) {
+ case 16:
+ val |= SSD2828_VIDEO_PIXEL_FORMAT_16BPP;
+ break;
+ case 18:
+ val |= cfg->mipi_dsi_loosely_packed_pixel_format ?
+ SSD2828_VIDEO_PIXEL_FORMAT_18BPP_LOOSELY_PACKED :
+ SSD2828_VIDEO_PIXEL_FORMAT_18BPP_PACKED;
+ break;
+ case 24:
+ val |= SSD2828_VIDEO_PIXEL_FORMAT_24BPP;
+ break;
+ default:
+ printf("SSD2828: unsupported color depth\n");
+ return 1;
+ }
+ write_hw_register(cfg, SSD2828_VICR6, val);
+
+ /* Lane Configuration Register */
+ write_hw_register(cfg, SSD2828_LCFR,
+ cfg->mipi_dsi_number_of_data_lanes - 1);
+
+ return 0;
+}
+
+int ssd2828_init(const struct ssd2828_config *cfg,
+ const struct ctfb_res_modes *mode)
+{
+ u32 lp_div, pll_freq_kbps, reference_freq_khz, pll_config;
+ /* The LP clock speed is limited by 10MHz */
+ const u32 mipi_dsi_low_power_clk_khz = 10000;
+ /*
+ * This is just the reset default value of CFGR register (0x301).
+ * Because we are not always able to read back from SPI, have
+ * it initialized here.
+ */
+ u32 cfgr_reg = SSD2828_CFGR_EOT | /* EOT Packet Enable */
+ SSD2828_CFGR_ECD | /* Disable ECC and CRC */
+ SSD2828_CFGR_HS; /* Data lanes are in HS mode */
+
+ /* Initialize the pins */
+ if (ssd2828_enable_gpio(cfg) != 0)
+ return 1;
+
+ /* Reset the chip */
+ ssd2828_reset(cfg);
+
+ /*
+ * If there is a pin to read data back from SPI, then we are lucky. Try
+ * to check if SPI is configured correctly and SSD2828 is actually able
+ * to talk back.
+ */
+ if (cfg->sdo_pin != -1) {
+ if (read_hw_register(cfg, SSD2828_DIR) != 0x2828 ||
+ read_hw_register(cfg, SSD2828_CFGR) != cfgr_reg) {
+ printf("SSD2828: SPI communication failed.\n");
+ ssd2828_free_gpio(cfg);
+ return 1;
+ }
+ }
+
+ /*
+ * Pick the reference clock for PLL. If we know the exact 'tx_clk'
+ * clock speed, then everything is good. If not, then we can fallback
+ * to 'pclk' (pixel clock from the parallel LCD interface). In the
+ * case of using this fallback, it is necessary to have parallel LCD
+ * already initialized and running at this point.
+ */
+ reference_freq_khz = cfg->ssd2828_tx_clk_khz;
+ if (reference_freq_khz == 0) {
+ reference_freq_khz = mode->pixclock_khz;
+ /* Use 'pclk' as the reference clock for PLL */
+ cfgr_reg |= SSD2828_CFGR_CSS;
+ }
+
+ /*
+ * Setup the parallel LCD timings in the appropriate registers.
+ */
+ if (ssd2828_configure_video_interface(cfg, mode) != 0) {
+ ssd2828_free_gpio(cfg);
+ return 1;
+ }
+
+ /* Configuration Register */
+ cfgr_reg &= ~SSD2828_CFGR_HS; /* Data lanes are in LP mode */
+ cfgr_reg |= SSD2828_CFGR_CKE; /* Clock lane is in HS mode */
+ cfgr_reg |= SSD2828_CFGR_DCS; /* Only use DCS packets */
+ write_hw_register(cfg, SSD2828_CFGR, cfgr_reg);
+
+ /* PLL Configuration Register */
+ pll_config = construct_pll_config(
+ cfg->mipi_dsi_bitrate_per_data_lane_mbps * 1000,
+ reference_freq_khz);
+ write_hw_register(cfg, SSD2828_PLCR, pll_config);
+
+ pll_freq_kbps = decode_pll_config(pll_config, reference_freq_khz);
+ lp_div = DIV_ROUND_UP(pll_freq_kbps, mipi_dsi_low_power_clk_khz * 8);
+
+ /* VC Control Register */
+ write_hw_register(cfg, SSD2828_VCR, 0);
+
+ /* Clock Control Register */
+ write_hw_register(cfg, SSD2828_CCR, SSD2828_LP_CLOCK_DIVIDER(lp_div));
+
+ /* PLL Control Register */
+ write_hw_register(cfg, SSD2828_PCR, 1); /* Enable PLL */
+
+ /* Wait for PLL lock */
+ udelay(500);
+
+ send_mipi_dcs_command(cfg, MIPI_DCS_EXIT_SLEEP_MODE);
+ mdelay(cfg->mipi_dsi_delay_after_exit_sleep_mode_ms);
+
+ send_mipi_dcs_command(cfg, MIPI_DCS_SET_DISPLAY_ON);
+ mdelay(cfg->mipi_dsi_delay_after_set_display_on_ms);
+
+ cfgr_reg |= SSD2828_CFGR_HS; /* Enable HS mode for data lanes */
+ cfgr_reg |= SSD2828_CFGR_VEN; /* Enable video pipeline */
+ write_hw_register(cfg, SSD2828_CFGR, cfgr_reg);
+
+ return 0;
+}
diff --git a/drivers/video/ssd2828.h b/drivers/video/ssd2828.h
new file mode 100644
index 0000000000..1af6fa4023
--- /dev/null
+++ b/drivers/video/ssd2828.h
@@ -0,0 +1,128 @@
+/*
+ * (C) 2015 Siarhei Siamashka <siarhei.siamashka@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/*
+ * Support for the SSD2828 bridge chip, which can take pixel data coming
+ * from a parallel LCD interface and translate it on the flight into MIPI DSI
+ * interface for driving a MIPI compatible TFT display.
+ *
+ * Implemented as a utility function. To be used from display drivers, which are
+ * responsible for driving parallel LCD hardware in front of the video pipeline.
+ */
+
+#ifndef _SSD2828_H
+#define _SSD2828_H
+
+struct ctfb_res_modes;
+
+struct ssd2828_config {
+ /*********************************************************************/
+ /* SSD2828 configuration */
+ /*********************************************************************/
+
+ /*
+ * The pins, which are used for SPI communication. This is only used
+ * for configuring SSD2828, so the performance is irrelevant (only
+ * around a hundred of bytes is moved). Also these can be any arbitrary
+ * GPIO pins (not necessarily the pins having hardware SPI function).
+ * Moreover, the 'sdo' pin may be even not wired up in some devices.
+ *
+ * These configuration variables need to be set as pin numbers for
+ * the standard u-boot GPIO interface (gpio_get_value/gpio_set_value
+ * functions). Note that -1 value can be used for the pins, which are
+ * not really wired up.
+ */
+ int csx_pin;
+ int sck_pin;
+ int sdi_pin;
+ int sdo_pin;
+ /* SSD2828 reset pin (shared with LCD panel reset) */
+ int reset_pin;
+
+ /*
+ * The SSD2828 has its own dedicated clock source 'tx_clk' (connected
+ * to TX_CLK_XIO/TX_CLK_XIN pins), which is necessary at least for
+ * clocking SPI after reset. The exact clock speed is not strictly,
+ * defined, but the datasheet says that it must be somewhere in the
+ * 8MHz - 30MHz range (see "TX_CLK Timing" section). It can be also
+ * used as a reference clock for PLL. If the exact clock frequency
+ * is known, then it can be specified here. If it is unknown, or the
+ * information is not trustworthy, then it can be set to 0.
+ *
+ * If unsure, set to 0.
+ */
+ int ssd2828_tx_clk_khz;
+
+ /*
+ * This is not a property of the used LCD panel, but more like a
+ * property of the SSD2828 wiring. See the "SSD2828QN4 RGB data
+ * arrangement" table in the datasheet. The SSD2828 pins are arranged
+ * in such a way that 18bpp and 24bpp configurations are completely
+ * incompatible with each other.
+ *
+ * Depending on the color depth, this must be set to 16, 18 or 24.
+ */
+ int ssd2828_color_depth;
+
+ /*********************************************************************/
+ /* LCD panel configuration */
+ /*********************************************************************/
+
+ /*
+ * The number of lanes in the MIPI DSI interface. May vary from 1 to 4.
+ *
+ * This information can be found in the LCD panel datasheet.
+ */
+ int mipi_dsi_number_of_data_lanes;
+
+ /*
+ * Data transfer bit rate per lane. Please note that it is expected
+ * to be higher than the pixel clock rate of the used video mode when
+ * multiplied by the number of lanes. This is perfectly normal because
+ * MIPI DSI handles data transfers in periodic bursts, and uses the
+ * idle time between bursts for sending configuration information and
+ * commands. Or just for saving power.
+ *
+ * The necessary Mbps/lane information can be found in the LCD panel
+ * datasheet. Note that the transfer rate can't be always set precisely
+ * and it may be rounded *up* (introducing no more than 10Mbps error).
+ */
+ int mipi_dsi_bitrate_per_data_lane_mbps;
+
+ /*
+ * Setting this to 1 enforces packing of 18bpp pixel data in 24bpp
+ * envelope when sending it over the MIPI DSI link.
+ *
+ * If unsure, set to 0.
+ */
+ int mipi_dsi_loosely_packed_pixel_format;
+
+ /*
+ * According to the "Example for system sleep in and out" section in
+ * the SSD2828 datasheet, some LCD panel specific delays are necessary
+ * after MIPI DCS commands EXIT_SLEEP_MODE and SET_DISPLAY_ON.
+ *
+ * For example, Allwinner uses 100 milliseconds delay after
+ * EXIT_SLEEP_MODE and 200 milliseconds delay after SET_DISPLAY_ON.
+ */
+ int mipi_dsi_delay_after_exit_sleep_mode_ms;
+ int mipi_dsi_delay_after_set_display_on_ms;
+};
+
+/*
+ * Initialize the SSD2828 chip. It needs the 'ssd2828_config' structure
+ * and also the video mode timings.
+ *
+ * The right place to insert this function call is after the parallel LCD
+ * interface is initialized and before turning on the backlight. This is
+ * advised in the "Example for system sleep in and out" section of the
+ * SSD2828 datasheet. And also SS2828 may use 'pclk' as the clock source
+ * for PLL, which means that the input signal must be already there.
+ */
+int ssd2828_init(const struct ssd2828_config *cfg,
+ const struct ctfb_res_modes *mode);
+
+#endif
diff --git a/drivers/video/sunxi_display.c b/drivers/video/sunxi_display.c
index d92dfa8863..f5f24fc020 100644
--- a/drivers/video/sunxi_display.c
+++ b/drivers/video/sunxi_display.c
@@ -20,6 +20,16 @@
#include <fdt_support.h>
#include <video_fb.h>
#include "videomodes.h"
+#include "hitachi_tx18d42vm_lcd.h"
+#include "ssd2828.h"
+
+#ifdef CONFIG_VIDEO_LCD_BL_PWM_ACTIVE_LOW
+#define PWM_ON 0
+#define PWM_OFF 1
+#else
+#define PWM_ON 1
+#define PWM_OFF 0
+#endif
DECLARE_GLOBAL_DATA_PTR;
@@ -270,6 +280,114 @@ static int sunxi_hdmi_edid_get_mode(struct ctfb_res_modes *mode)
#endif /* CONFIG_VIDEO_HDMI */
+#ifdef CONFIG_MACH_SUN4I
+/*
+ * Testing has shown that on sun4i the display backend engine does not have
+ * deep enough fifo-s causing flickering / tearing in full-hd mode due to
+ * fifo underruns. So on sun4i we use the display frontend engine to do the
+ * dma from memory, as the frontend does have deep enough fifo-s.
+ */
+
+static const u32 sun4i_vert_coef[32] = {
+ 0x00004000, 0x000140ff, 0x00033ffe, 0x00043ffd,
+ 0x00063efc, 0xff083dfc, 0x000a3bfb, 0xff0d39fb,
+ 0xff0f37fb, 0xff1136fa, 0xfe1433fb, 0xfe1631fb,
+ 0xfd192ffb, 0xfd1c2cfb, 0xfd1f29fb, 0xfc2127fc,
+ 0xfc2424fc, 0xfc2721fc, 0xfb291ffd, 0xfb2c1cfd,
+ 0xfb2f19fd, 0xfb3116fe, 0xfb3314fe, 0xfa3611ff,
+ 0xfb370fff, 0xfb390dff, 0xfb3b0a00, 0xfc3d08ff,
+ 0xfc3e0600, 0xfd3f0400, 0xfe3f0300, 0xff400100,
+};
+
+static const u32 sun4i_horz_coef[64] = {
+ 0x40000000, 0x00000000, 0x40fe0000, 0x0000ff03,
+ 0x3ffd0000, 0x0000ff05, 0x3ffc0000, 0x0000ff06,
+ 0x3efb0000, 0x0000ff08, 0x3dfb0000, 0x0000ff09,
+ 0x3bfa0000, 0x0000fe0d, 0x39fa0000, 0x0000fe0f,
+ 0x38fa0000, 0x0000fe10, 0x36fa0000, 0x0000fe12,
+ 0x33fa0000, 0x0000fd16, 0x31fa0000, 0x0000fd18,
+ 0x2ffa0000, 0x0000fd1a, 0x2cfa0000, 0x0000fc1e,
+ 0x29fa0000, 0x0000fc21, 0x27fb0000, 0x0000fb23,
+ 0x24fb0000, 0x0000fb26, 0x21fb0000, 0x0000fb29,
+ 0x1ffc0000, 0x0000fa2b, 0x1cfc0000, 0x0000fa2e,
+ 0x19fd0000, 0x0000fa30, 0x16fd0000, 0x0000fa33,
+ 0x14fd0000, 0x0000fa35, 0x11fe0000, 0x0000fa37,
+ 0x0ffe0000, 0x0000fa39, 0x0dfe0000, 0x0000fa3b,
+ 0x0afe0000, 0x0000fa3e, 0x08ff0000, 0x0000fb3e,
+ 0x06ff0000, 0x0000fb40, 0x05ff0000, 0x0000fc40,
+ 0x03ff0000, 0x0000fd41, 0x01ff0000, 0x0000fe42,
+};
+
+static void sunxi_frontend_init(void)
+{
+ struct sunxi_ccm_reg * const ccm =
+ (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+ struct sunxi_de_fe_reg * const de_fe =
+ (struct sunxi_de_fe_reg *)SUNXI_DE_FE0_BASE;
+ int i;
+
+ /* Clocks on */
+ setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_DE_FE0);
+ setbits_le32(&ccm->dram_clk_gate, 1 << CCM_DRAM_GATE_OFFSET_DE_FE0);
+ clock_set_de_mod_clock(&ccm->fe0_clk_cfg, 300000000);
+
+ setbits_le32(&de_fe->enable, SUNXI_DE_FE_ENABLE_EN);
+
+ for (i = 0; i < 32; i++) {
+ writel(sun4i_horz_coef[2 * i], &de_fe->ch0_horzcoef0[i]);
+ writel(sun4i_horz_coef[2 * i + 1], &de_fe->ch0_horzcoef1[i]);
+ writel(sun4i_vert_coef[i], &de_fe->ch0_vertcoef[i]);
+ writel(sun4i_horz_coef[2 * i], &de_fe->ch1_horzcoef0[i]);
+ writel(sun4i_horz_coef[2 * i + 1], &de_fe->ch1_horzcoef1[i]);
+ writel(sun4i_vert_coef[i], &de_fe->ch1_vertcoef[i]);
+ }
+
+ setbits_le32(&de_fe->frame_ctrl, SUNXI_DE_FE_FRAME_CTRL_COEF_RDY);
+}
+
+static void sunxi_frontend_mode_set(const struct ctfb_res_modes *mode,
+ unsigned int address)
+{
+ struct sunxi_de_fe_reg * const de_fe =
+ (struct sunxi_de_fe_reg *)SUNXI_DE_FE0_BASE;
+
+ setbits_le32(&de_fe->bypass, SUNXI_DE_FE_BYPASS_CSC_BYPASS);
+ writel(CONFIG_SYS_SDRAM_BASE + address, &de_fe->ch0_addr);
+ writel(mode->xres * 4, &de_fe->ch0_stride);
+ writel(SUNXI_DE_FE_INPUT_FMT_ARGB8888, &de_fe->input_fmt);
+ writel(SUNXI_DE_FE_OUTPUT_FMT_ARGB8888, &de_fe->output_fmt);
+
+ writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
+ &de_fe->ch0_insize);
+ writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
+ &de_fe->ch0_outsize);
+ writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch0_horzfact);
+ writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch0_vertfact);
+
+ writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
+ &de_fe->ch1_insize);
+ writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
+ &de_fe->ch1_outsize);
+ writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch1_horzfact);
+ writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch1_vertfact);
+
+ setbits_le32(&de_fe->frame_ctrl, SUNXI_DE_FE_FRAME_CTRL_REG_RDY);
+}
+
+static void sunxi_frontend_enable(void)
+{
+ struct sunxi_de_fe_reg * const de_fe =
+ (struct sunxi_de_fe_reg *)SUNXI_DE_FE0_BASE;
+
+ setbits_le32(&de_fe->frame_ctrl, SUNXI_DE_FE_FRAME_CTRL_FRM_START);
+}
+#else
+static void sunxi_frontend_init(void) {}
+static void sunxi_frontend_mode_set(const struct ctfb_res_modes *mode,
+ unsigned int address) {}
+static void sunxi_frontend_enable(void) {}
+#endif
+
/*
* This is the entity that mixes and matches the different layers and inputs.
* Allwinner calls it the back-end, but i like composer better.
@@ -282,6 +400,8 @@ static void sunxi_composer_init(void)
(struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
int i;
+ sunxi_frontend_init();
+
#if defined CONFIG_MACH_SUN6I || defined CONFIG_MACH_SUN8I
/* Reset off */
setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_DE_BE0);
@@ -289,7 +409,9 @@ static void sunxi_composer_init(void)
/* Clocks on */
setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_DE_BE0);
+#ifndef CONFIG_MACH_SUN4I /* On sun4i the frontend does the dma */
setbits_le32(&ccm->dram_clk_gate, 1 << CCM_DRAM_GATE_OFFSET_DE_BE0);
+#endif
clock_set_de_mod_clock(&ccm->be0_clk_cfg, 300000000);
/* Engine bug, clear registers after reset */
@@ -305,13 +427,19 @@ static void sunxi_composer_mode_set(const struct ctfb_res_modes *mode,
struct sunxi_de_be_reg * const de_be =
(struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
+ sunxi_frontend_mode_set(mode, address);
+
writel(SUNXI_DE_BE_HEIGHT(mode->yres) | SUNXI_DE_BE_WIDTH(mode->xres),
&de_be->disp_size);
writel(SUNXI_DE_BE_HEIGHT(mode->yres) | SUNXI_DE_BE_WIDTH(mode->xres),
&de_be->layer0_size);
+#ifndef CONFIG_MACH_SUN4I /* On sun4i the frontend does the dma */
writel(SUNXI_DE_BE_LAYER_STRIDE(mode->xres), &de_be->layer0_stride);
writel(address << 3, &de_be->layer0_addr_low32b);
writel(address >> 29, &de_be->layer0_addr_high4b);
+#else
+ writel(SUNXI_DE_BE_LAYER_ATTR0_SRC_FE0, &de_be->layer0_attr0_ctrl);
+#endif
writel(SUNXI_DE_BE_LAYER_ATTR1_FMT_XRGB8888, &de_be->layer0_attr1_ctrl);
setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_LAYER0_ENABLE);
@@ -322,6 +450,8 @@ static void sunxi_composer_enable(void)
struct sunxi_de_be_reg * const de_be =
(struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
+ sunxi_frontend_enable();
+
setbits_le32(&de_be->reg_ctrl, SUNXI_DE_BE_REG_CTRL_LOAD_REGS);
setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_START);
}
@@ -476,8 +606,7 @@ static void sunxi_lcdc_panel_enable(void)
pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_PWM);
if (pin != -1) {
gpio_request(pin, "lcd_backlight_pwm");
- /* backlight pwm is inverted, set to 1 to disable backlight */
- gpio_direction_output(pin, 1);
+ gpio_direction_output(pin, PWM_OFF);
}
/* Give the backlight some time to turn off and power up the panel. */
@@ -504,10 +633,8 @@ static void sunxi_lcdc_backlight_enable(void)
gpio_direction_output(pin, 1);
pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_PWM);
- if (pin != -1) {
- /* backlight pwm is inverted, set to 0 to enable backlight */
- gpio_direction_output(pin, 0);
- }
+ if (pin != -1)
+ gpio_direction_output(pin, PWM_ON);
}
static int sunxi_lcdc_get_clk_delay(const struct ctfb_res_modes *mode)
@@ -518,7 +645,8 @@ static int sunxi_lcdc_get_clk_delay(const struct ctfb_res_modes *mode)
return (delay > 30) ? 30 : delay;
}
-static void sunxi_lcdc_tcon0_mode_set(const struct ctfb_res_modes *mode)
+static void sunxi_lcdc_tcon0_mode_set(const struct ctfb_res_modes *mode,
+ bool for_ext_vga_dac)
{
struct sunxi_lcdc_reg * const lcdc =
(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
@@ -587,16 +715,16 @@ static void sunxi_lcdc_tcon0_mode_set(const struct ctfb_res_modes *mode)
&lcdc->tcon0_frm_ctrl);
}
-#ifdef CONFIG_VIDEO_LCD_IF_PARALLEL
- val = SUNXI_LCDC_TCON0_IO_POL_DCLK_PHASE0;
-#endif
-#ifdef CONFIG_VIDEO_LCD_IF_LVDS
- val = SUNXI_LCDC_TCON0_IO_POL_DCLK_PHASE60;
-#endif
+ val = SUNXI_LCDC_TCON0_IO_POL_DCLK_PHASE(CONFIG_VIDEO_LCD_DCLK_PHASE);
if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT))
val |= SUNXI_LCDC_TCON_HSYNC_MASK;
if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT))
val |= SUNXI_LCDC_TCON_VSYNC_MASK;
+
+#ifdef CONFIG_VIDEO_VGA_VIA_LCD_FORCE_SYNC_ACTIVE_HIGH
+ if (for_ext_vga_dac)
+ val = 0;
+#endif
writel(val, &lcdc->tcon0_io_polarity);
writel(0, &lcdc->tcon0_io_tristate);
@@ -826,6 +954,40 @@ static void sunxi_vga_external_dac_enable(void)
}
#endif /* CONFIG_VIDEO_VGA_VIA_LCD */
+#ifdef CONFIG_VIDEO_LCD_SSD2828
+static int sunxi_ssd2828_init(const struct ctfb_res_modes *mode)
+{
+ struct ssd2828_config cfg = {
+ .csx_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_CS),
+ .sck_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_SCLK),
+ .sdi_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_MOSI),
+ .sdo_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_MISO),
+ .reset_pin = name_to_gpio(CONFIG_VIDEO_LCD_SSD2828_RESET),
+ .ssd2828_tx_clk_khz = CONFIG_VIDEO_LCD_SSD2828_TX_CLK * 1000,
+ .ssd2828_color_depth = 24,
+#ifdef CONFIG_VIDEO_LCD_PANEL_MIPI_4_LANE_513_MBPS_VIA_SSD2828
+ .mipi_dsi_number_of_data_lanes = 4,
+ .mipi_dsi_bitrate_per_data_lane_mbps = 513,
+ .mipi_dsi_delay_after_exit_sleep_mode_ms = 100,
+ .mipi_dsi_delay_after_set_display_on_ms = 200
+#else
+#error MIPI LCD panel needs configuration parameters
+#endif
+ };
+
+ if (cfg.csx_pin == -1 || cfg.sck_pin == -1 || cfg.sdi_pin == -1) {
+ printf("SSD2828: SPI pins are not properly configured\n");
+ return 1;
+ }
+ if (cfg.reset_pin == -1) {
+ printf("SSD2828: Reset pin is not properly configured\n");
+ return 1;
+ }
+
+ return ssd2828_init(&cfg, mode);
+}
+#endif /* CONFIG_VIDEO_LCD_SSD2828 */
+
static void sunxi_engines_init(void)
{
sunxi_composer_init();
@@ -854,10 +1016,17 @@ static void sunxi_mode_set(const struct ctfb_res_modes *mode,
break;
case sunxi_monitor_lcd:
sunxi_lcdc_panel_enable();
+ if (IS_ENABLED(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM)) {
+ mdelay(50); /* Wait for lcd controller power on */
+ hitachi_tx18d42vm_init();
+ }
sunxi_composer_mode_set(mode, address);
- sunxi_lcdc_tcon0_mode_set(mode);
+ sunxi_lcdc_tcon0_mode_set(mode, false);
sunxi_composer_enable();
sunxi_lcdc_enable();
+#ifdef CONFIG_VIDEO_LCD_SSD2828
+ sunxi_ssd2828_init(mode);
+#endif
sunxi_lcdc_backlight_enable();
break;
case sunxi_monitor_vga:
@@ -870,7 +1039,7 @@ static void sunxi_mode_set(const struct ctfb_res_modes *mode,
sunxi_vga_enable();
#elif defined CONFIG_VIDEO_VGA_VIA_LCD
sunxi_composer_mode_set(mode, address);
- sunxi_lcdc_tcon0_mode_set(mode);
+ sunxi_lcdc_tcon0_mode_set(mode, true);
sunxi_composer_enable();
sunxi_lcdc_enable();
sunxi_vga_external_dac_enable();
@@ -1027,21 +1196,27 @@ int sunxi_simplefb_setup(void *blob)
int offset, ret;
const char *pipeline = NULL;
+#ifdef CONFIG_MACH_SUN4I
+#define PIPELINE_PREFIX "de_fe0-"
+#else
+#define PIPELINE_PREFIX
+#endif
+
switch (sunxi_display.monitor) {
case sunxi_monitor_none:
return 0;
case sunxi_monitor_dvi:
case sunxi_monitor_hdmi:
- pipeline = "de_be0-lcd0-hdmi";
+ pipeline = PIPELINE_PREFIX "de_be0-lcd0-hdmi";
break;
case sunxi_monitor_lcd:
- pipeline = "de_be0-lcd0";
+ pipeline = PIPELINE_PREFIX "de_be0-lcd0";
break;
case sunxi_monitor_vga:
#ifdef CONFIG_VIDEO_VGA
- pipeline = "de_be0-lcd0-tve0";
+ pipeline = PIPELINE_PREFIX "de_be0-lcd0-tve0";
#elif defined CONFIG_VIDEO_VGA_VIA_LCD
- pipeline = "de_be0-lcd0";
+ pipeline = PIPELINE_PREFIX "de_be0-lcd0";
#endif
break;
}
diff --git a/drivers/video/tegra.c b/drivers/video/tegra.c
index 57cb0074e2..b8f3431f24 100644
--- a/drivers/video/tegra.c
+++ b/drivers/video/tegra.c
@@ -149,14 +149,18 @@ static int fdt_decode_lcd(const void *blob, struct fdt_panel_config *config)
FDT_LCD_CACHE_WRITE_BACK_FLUSH);
/* These GPIOs are all optional */
- fdtdec_decode_gpio(blob, display_node, "nvidia,backlight-enable-gpios",
- &config->backlight_en);
- fdtdec_decode_gpio(blob, display_node, "nvidia,lvds-shutdown-gpios",
- &config->lvds_shutdown);
- fdtdec_decode_gpio(blob, display_node, "nvidia,backlight-vdd-gpios",
- &config->backlight_vdd);
- fdtdec_decode_gpio(blob, display_node, "nvidia,panel-vdd-gpios",
- &config->panel_vdd);
+ gpio_request_by_name_nodev(blob, display_node,
+ "nvidia,backlight-enable-gpios", 0,
+ &config->backlight_en, GPIOD_IS_OUT);
+ gpio_request_by_name_nodev(blob, display_node,
+ "nvidia,lvds-shutdown-gpios", 0,
+ &config->lvds_shutdown, GPIOD_IS_OUT);
+ gpio_request_by_name_nodev(blob, display_node,
+ "nvidia,backlight-vdd-gpios", 0,
+ &config->backlight_vdd, GPIOD_IS_OUT);
+ gpio_request_by_name_nodev(blob, display_node,
+ "nvidia,panel-vdd-gpios", 0,
+ &config->panel_vdd, GPIOD_IS_OUT);
return fdtdec_get_int_array(blob, display_node, "nvidia,panel-timings",
config->panel_timings, FDT_LCD_TIMINGS);
@@ -196,36 +200,18 @@ static int handle_stage(const void *blob)
*/
funcmux_select(PERIPH_ID_DISP1, FUNCMUX_DEFAULT);
-
- fdtdec_setup_gpio(&config.panel_vdd);
- fdtdec_setup_gpio(&config.lvds_shutdown);
- fdtdec_setup_gpio(&config.backlight_vdd);
- fdtdec_setup_gpio(&config.backlight_en);
-
- /*
- * TODO: If fdt includes output flag we can omit this code
- * since fdtdec_setup_gpio will do it for us.
- */
- if (fdt_gpio_isvalid(&config.panel_vdd))
- gpio_direction_output(config.panel_vdd.gpio, 0);
- if (fdt_gpio_isvalid(&config.lvds_shutdown))
- gpio_direction_output(config.lvds_shutdown.gpio, 0);
- if (fdt_gpio_isvalid(&config.backlight_vdd))
- gpio_direction_output(config.backlight_vdd.gpio, 0);
- if (fdt_gpio_isvalid(&config.backlight_en))
- gpio_direction_output(config.backlight_en.gpio, 0);
break;
case STAGE_PANEL_VDD:
- if (fdt_gpio_isvalid(&config.panel_vdd))
- gpio_direction_output(config.panel_vdd.gpio, 1);
+ if (dm_gpio_is_valid(&config.panel_vdd))
+ dm_gpio_set_value(&config.panel_vdd, 1);
break;
case STAGE_LVDS:
- if (fdt_gpio_isvalid(&config.lvds_shutdown))
- gpio_set_value(config.lvds_shutdown.gpio, 1);
+ if (dm_gpio_is_valid(&config.lvds_shutdown))
+ dm_gpio_set_value(&config.lvds_shutdown, 1);
break;
case STAGE_BACKLIGHT_VDD:
- if (fdt_gpio_isvalid(&config.backlight_vdd))
- gpio_set_value(config.backlight_vdd.gpio, 1);
+ if (dm_gpio_is_valid(&config.backlight_vdd))
+ dm_gpio_set_value(&config.backlight_vdd, 1);
break;
case STAGE_PWM:
/* Enable PWM at 15/16 high, 32768 Hz with divider 1 */
@@ -235,8 +221,8 @@ static int handle_stage(const void *blob)
pwm_enable(config.pwm_channel, 32768, 0xdf, 1);
break;
case STAGE_BACKLIGHT_EN:
- if (fdt_gpio_isvalid(&config.backlight_en))
- gpio_set_value(config.backlight_en.gpio, 1);
+ if (dm_gpio_is_valid(&config.backlight_en))
+ dm_gpio_set_value(&config.backlight_en, 1);
break;
case STAGE_DONE:
break;
diff --git a/drivers/video/vesa_fb.c b/drivers/video/vesa_fb.c
new file mode 100644
index 0000000000..3dacafd6bf
--- /dev/null
+++ b/drivers/video/vesa_fb.c
@@ -0,0 +1,64 @@
+/*
+ *
+ * Vesa frame buffer driver for x86
+ *
+ * Copyright (C) 2014 Google, Inc
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <pci_rom.h>
+#include <video_fb.h>
+#include <vbe.h>
+
+/*
+ * The Graphic Device
+ */
+GraphicDevice ctfb;
+
+/* Devices to allow - only the last one works fully */
+struct pci_device_id vesa_video_ids[] = {
+ { .vendor = 0x102b, .device = 0x0525 },
+ { .vendor = 0x1002, .device = 0x5159 },
+ { .vendor = 0x1002, .device = 0x4752 },
+ { .vendor = 0x1002, .device = 0x5452 },
+ {},
+};
+
+void *video_hw_init(void)
+{
+ GraphicDevice *gdev = &ctfb;
+ int bits_per_pixel;
+ pci_dev_t dev;
+ int ret;
+
+ printf("Video: ");
+ if (vbe_get_video_info(gdev)) {
+ /* TODO: Should we look these up by class? */
+ dev = pci_find_devices(vesa_video_ids, 0);
+ if (dev == -1) {
+ printf("no card detected\n");
+ return NULL;
+ }
+ printf("bdf %x\n", dev);
+ ret = pci_run_vga_bios(dev, NULL, true);
+ if (ret) {
+ printf("failed to run video BIOS: %d\n", ret);
+ return NULL;
+ }
+ }
+
+ if (vbe_get_video_info(gdev)) {
+ printf("No video mode configured\n");
+ return NULL;
+ }
+
+ bits_per_pixel = gdev->gdfBytesPP * 8;
+ sprintf(gdev->modeIdent, "%dx%dx%d", gdev->winSizeX, gdev->winSizeY,
+ bits_per_pixel);
+ printf("%s\n", gdev->modeIdent);
+ debug("Framex buffer at %x\n", gdev->pciBase);
+
+ return (void *)gdev;
+}
diff --git a/drivers/video/x86_fb.c b/drivers/video/x86_fb.c
deleted file mode 100644
index 6641033a5d..0000000000
--- a/drivers/video/x86_fb.c
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- *
- * Vesa frame buffer driver for x86
- *
- * Copyright (C) 2014 Google, Inc
- *
- * SPDX-License-Identifier: GPL-2.0+
- */
-
-#include <common.h>
-#include <video_fb.h>
-#include <vbe.h>
-#include "videomodes.h"
-
-/*
- * The Graphic Device
- */
-GraphicDevice ctfb;
-
-void *video_hw_init(void)
-{
- GraphicDevice *gdev = &ctfb;
- int bits_per_pixel;
-
- printf("Video: ");
- if (vbe_get_video_info(gdev)) {
- printf("No video mode configured\n");
- return NULL;
- }
-
- bits_per_pixel = gdev->gdfBytesPP * 8;
- sprintf(gdev->modeIdent, "%dx%dx%d", gdev->winSizeX, gdev->winSizeY,
- bits_per_pixel);
- printf("%s\n", gdev->modeIdent);
- debug("Frame buffer at %x\n", gdev->frameAdrs);
-
- return (void *)gdev;
-}
OpenPOWER on IntegriCloud