Parcourir la source

Merge tag 'drm-misc-next-2018-01-08' of git://anongit.freedesktop.org/drm/drm-misc into drm-next

drm-misc-next for 4.16:

Cross-subsystem Changes:

- some dt-binding changes for Ilitek and sun4i devices

Core Changes:

- panel_orientation_quirks: fix tainted kernel

Driver Changes:

- panel changes
- A83T and LVDS support to sun4i

* tag 'drm-misc-next-2018-01-08' of git://anongit.freedesktop.org/drm/drm-misc:
  drm/panel: lvds: Add support for the power-supply property
  dt-bindings: panel: lvds: Document power-supply property
  drm/sun4i: Add A83T support
  drm/sun4i: Add LVDS support
  drm/sun4i: Create minimal multipliers and dividers
  drm/sun4i: Force the mixer rate at 150MHz
  dt-bindings: display: sun4i-drm: Add A83T pipeline
  dt-bindings: display: sun4i-drm: Add LVDS properties
  drm/tinydrm: add driver for ST7735R panels
  dt-bindings: Add binding for Sitronix ST7735R display panels
  dt-bindings: add jianda vendor prefix
  drm/tinydrm: Update ILI9225 compatible string
  dt-bindings: update compatible string for ILI9225
  dt-bindings: Add "vot" vendor prefix
  drm: fix tainted kernel caused by drm_panel_orientation_quirks.c
  drm/panel: Add Ilitek ILI9322 driver
  drm/panel: Add DT bindings for Ilitek ILI9322
Dave Airlie il y a 7 ans
Parent
commit
6213640fae

+ 2 - 2
Documentation/devicetree/bindings/display/ilitek,ili9225.txt

@@ -4,7 +4,7 @@ This binding is for display panels using an Ilitek ILI9225 controller in SPI
 mode.
 
 Required properties:
-- compatible:	"ilitek,ili9225-2.2in-176x220"
+- compatible:	"vot,v220hf01a-t", "ilitek,ili9225"
 - rs-gpios:	Register select signal
 - reset-gpios:	Reset pin
 
@@ -16,7 +16,7 @@ Optional properties:
 
 Example:
 	display@0{
-		compatible = "ilitek,ili9225-2.2in-176x220";
+		compatible = "vot,v220hf01a-t", "ilitek,ili9225";
 		reg = <0>;
 		spi-max-frequency = <12000000>;
 		rs-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;

+ 49 - 0
Documentation/devicetree/bindings/display/panel/ilitek,ili9322.txt

@@ -0,0 +1,49 @@
+Ilitek ILI9322 TFT panel driver with SPI control bus
+
+This is a driver for 320x240 TFT panels, accepting a variety of input
+streams that get adapted and scaled to the panel. The panel output has
+960 TFT source driver pins and 240 TFT gate driver pins, VCOM, VCOML and
+VCOMH outputs.
+
+Required properties:
+  - compatible: "dlink,dir-685-panel", "ilitek,ili9322"
+    (full system-specific compatible is always required to look up configuration)
+  - reg: address of the panel on the SPI bus
+
+Optional properties:
+  - vcc-supply: core voltage supply, see regulator/regulator.txt
+  - iovcc-supply: voltage supply for the interface input/output signals,
+    see regulator/regulator.txt
+  - vci-supply: voltage supply for analog parts, see regulator/regulator.txt
+  - reset-gpios: a GPIO spec for the reset pin, see gpio/gpio.txt
+
+  The following optional properties only apply to RGB and YUV input modes and
+  can be omitted for BT.656 input modes:
+
+  - pixelclk-active: see display/panel/display-timing.txt
+  - de-active: see display/panel/display-timing.txt
+  - hsync-active: see display/panel/display-timing.txt
+  - vsync-active: see display/panel/display-timing.txt
+
+The panel must obey the rules for a SPI slave device as specified in
+spi/spi-bus.txt
+
+The device node can contain one 'port' child node with one child
+'endpoint' node, according to the bindings defined in
+media/video-interfaces.txt. This node should describe panel's video bus.
+
+Example:
+
+panel: display@0 {
+	compatible = "dlink,dir-685-panel", "ilitek,ili9322";
+	reg = <0>;
+	vcc-supply = <&vdisp>;
+	iovcc-supply = <&vdisp>;
+	vci-supply = <&vdisp>;
+
+	port {
+		panel_in: endpoint {
+			remote-endpoint = <&display_out>;
+		};
+	};
+};

+ 10 - 0
Documentation/devicetree/bindings/display/panel/panel-common.txt

@@ -78,6 +78,16 @@ used for panels that implement compatible control signals.
   while active. Active high reset signals can be supported by inverting the
   GPIO specifier polarity flag.
 
+Power
+-----
+
+- power-supply: display panels require power to be supplied. While several
+  panels need more than one power supply with panel-specific constraints
+  governing the order and timings of the power supplies, in many cases a single
+  power supply is sufficient, either because the panel has a single power rail,
+  or because all its power rails can be driven by the same supply. In that case
+  the power-supply property specifies the supply powering the panel as a phandle
+  to a regulator.
 
 Backlight
 ---------

+ 1 - 0
Documentation/devicetree/bindings/display/panel/panel-lvds.txt

@@ -32,6 +32,7 @@ Optional properties:
 - label: See panel-common.txt.
 - gpios: See panel-common.txt.
 - backlight: See panel-common.txt.
+- power-supply: See panel-common.txt.
 - data-mirror: If set, reverse the bit order described in the data mappings
   below on all data lanes, transmitting bits for slots 6 to 0 instead of
   0 to 6.

+ 1 - 1
Documentation/devicetree/bindings/display/panel/simple-panel.txt

@@ -1,7 +1,7 @@
 Simple display panel
 
 Required properties:
-- power-supply: regulator to provide the supply voltage
+- power-supply: See panel-common.txt
 
 Optional properties:
 - ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing

+ 35 - 0
Documentation/devicetree/bindings/display/sitronix,st7735r.txt

@@ -0,0 +1,35 @@
+Sitronix ST7735R display panels
+
+This binding is for display panels using a Sitronix ST7735R controller in SPI
+mode.
+
+Required properties:
+- compatible:	"jianda,jd-t18003-t01", "sitronix,st7735r"
+- dc-gpios:	Display data/command selection (D/CX)
+- reset-gpios:	Reset signal (RSTX)
+
+The node for this driver must be a child node of a SPI controller, hence
+all mandatory properties described in ../spi/spi-bus.txt must be specified.
+
+Optional properties:
+- rotation:	panel rotation in degrees counter clockwise (0,90,180,270)
+- backlight:	phandle of the backlight device attached to the panel
+
+Example:
+
+	backlight: backlight {
+		compatible = "gpio-backlight";
+		gpios = <&gpio 44 GPIO_ACTIVE_HIGH>;
+	}
+
+	...
+
+	display@0{
+		compatible = "jianda,jd-t18003-t01", "sitronix,st7735r";
+		reg = <0>;
+		spi-max-frequency = <32000000>;
+		dc-gpios = <&gpio 43 GPIO_ACTIVE_HIGH>;
+		reset-gpios = <&gpio 80 GPIO_ACTIVE_HIGH>;
+		rotation = <270>;
+		backlight = &backlight;
+	};

+ 11 - 0
Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt

@@ -93,6 +93,7 @@ Required properties:
    * allwinner,sun6i-a31s-tcon
    * allwinner,sun7i-a20-tcon
    * allwinner,sun8i-a33-tcon
+   * allwinner,sun8i-a83t-tcon-lcd
    * allwinner,sun8i-v3s-tcon
  - reg: base address and size of memory-mapped region
  - interrupts: interrupt associated to this IP
@@ -121,6 +122,14 @@ Required properties:
 On SoCs other than the A33 and V3s, there is one more clock required:
    - 'tcon-ch1': The clock driving the TCON channel 1
 
+On SoCs that support LVDS (all SoCs but the A13, H3, H5 and V3s), you
+need one more reset line:
+   - 'lvds': The reset line driving the LVDS logic
+
+And on the A23, A31, A31s and A33, you need one more clock line:
+   - 'lvds-alt': An alternative clock source, separate from the TCON channel 0
+                 clock, that can be used to drive the LVDS clock
+
 DRC
 ---
 
@@ -216,6 +225,7 @@ supported.
 
 Required properties:
   - compatible: value must be one of:
+    * allwinner,sun8i-a83t-de2-mixer-0
     * allwinner,sun8i-v3s-de2-mixer
   - reg: base address and size of the memory-mapped region.
   - clocks: phandles to the clocks feeding the mixer
@@ -245,6 +255,7 @@ Required properties:
     * allwinner,sun6i-a31s-display-engine
     * allwinner,sun7i-a20-display-engine
     * allwinner,sun8i-a33-display-engine
+    * allwinner,sun8i-a83t-display-engine
     * allwinner,sun8i-v3s-display-engine
 
   - allwinner,pipelines: list of phandle to the display engine

+ 2 - 0
Documentation/devicetree/bindings/vendor-prefixes.txt

@@ -173,6 +173,7 @@ itead	ITEAD Intelligent Systems Co.Ltd
 iwave  iWave Systems Technologies Pvt. Ltd.
 jdi	Japan Display Inc.
 jedec	JEDEC Solid State Technology Association
+jianda	Jiandangjing Technology Co., Ltd.
 karo	Ka-Ro electronics GmbH
 keithkoep	Keith & Koep GmbH
 keymile	Keymile GmbH
@@ -380,6 +381,7 @@ virtio	Virtual I/O Device Specification, developed by the OASIS consortium
 vivante	Vivante Corporation
 vocore VoCore Studio
 voipac	Voipac Technologies s.r.o.
+vot	Vision Optical Technology Co., Ltd.
 wd	Western Digital Corp.
 wetek	WeTek Electronics, limited.
 wexler	Wexler

+ 6 - 0
MAINTAINERS

@@ -4554,6 +4554,12 @@ S:	Maintained
 F:	drivers/gpu/drm/tinydrm/st7586.c
 F:	Documentation/devicetree/bindings/display/st7586.txt
 
+DRM DRIVER FOR SITRONIX ST7735R PANELS
+M:	David Lechner <david@lechnology.com>
+S:	Maintained
+F:	drivers/gpu/drm/tinydrm/st7735r.c
+F:	Documentation/devicetree/bindings/display/st7735r.txt
+
 DRM DRIVER FOR TDFX VIDEO CARDS
 S:	Orphan / Obsolete
 F:	drivers/gpu/drm/tdfx/

+ 3 - 0
drivers/gpu/drm/drm_panel_orientation_quirks.c

@@ -9,6 +9,7 @@
  */
 
 #include <linux/dmi.h>
+#include <linux/module.h>
 #include <drm/drm_connector.h>
 
 #ifdef CONFIG_DMI
@@ -172,3 +173,5 @@ int drm_get_panel_orientation_quirk(int width, int height)
 EXPORT_SYMBOL(drm_get_panel_orientation_quirk);
 
 #endif
+
+MODULE_LICENSE("Dual MIT/GPL");

+ 8 - 0
drivers/gpu/drm/panel/Kconfig

@@ -28,6 +28,14 @@ config DRM_PANEL_SIMPLE
 	  that it can be automatically turned off when the panel goes into a
 	  low power state.
 
+config DRM_PANEL_ILITEK_IL9322
+	tristate "Ilitek ILI9322 320x240 QVGA panels"
+	depends on OF && SPI
+	select REGMAP
+	help
+	  Say Y here if you want to enable support for Ilitek IL9322
+	  QVGA (320x240) RGB, YUV and ITU-T BT.656 panels.
+
 config DRM_PANEL_INNOLUX_P079ZCA
 	tristate "Innolux P079ZCA panel"
 	depends on OF

+ 1 - 0
drivers/gpu/drm/panel/Makefile

@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o
 obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o
+obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o
 obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o
 obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o
 obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o

+ 962 - 0
drivers/gpu/drm/panel/panel-ilitek-ili9322.c

@@ -0,0 +1,962 @@
+/*
+ * Ilitek ILI9322 TFT LCD drm_panel driver.
+ *
+ * This panel can be configured to support:
+ * - 8-bit serial RGB interface
+ * - 24-bit parallel RGB interface
+ * - 8-bit ITU-R BT.601 interface
+ * - 8-bit ITU-R BT.656 interface
+ * - Up to 320RGBx240 dots resolution TFT LCD displays
+ * - Scaling, brightness and contrast
+ *
+ * The scaling means that the display accepts a 640x480 or 720x480
+ * input and rescales it to fit to the 320x240 display. So what we
+ * present to the system is something else than what comes out on the
+ * actual display.
+ *
+ * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
+ * Derived from drivers/drm/gpu/panel/panel-samsung-ld9040.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_panel.h>
+
+#include <linux/of_device.h>
+#include <linux/bitops.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#define ILI9322_CHIP_ID			0x00
+#define ILI9322_CHIP_ID_MAGIC		0x96
+
+/*
+ * Voltage on the communication interface, from 0.7 (0x00)
+ * to 1.32 (0x1f) times the VREG1OUT voltage in 2% increments.
+ * 1.00 (0x0f) is the default.
+ */
+#define ILI9322_VCOM_AMP		0x01
+
+/*
+ * High voltage on the communication signals, from 0.37 (0x00) to
+ * 1.0 (0x3f) times the VREGOUT1 voltage in 1% increments.
+ * 0.83 (0x2e) is the default.
+ */
+#define ILI9322_VCOM_HIGH		0x02
+
+/*
+ * VREG1 voltage regulator from 3.6V (0x00) to 6.0V (0x18) in 0.1V
+ * increments. 5.4V (0x12) is the default. This is the reference
+ * voltage for the VCOM levels and the greyscale level.
+ */
+#define ILI9322_VREG1_VOLTAGE		0x03
+
+/* Describes the incoming signal */
+#define ILI9322_ENTRY			0x06
+/* 0 = right-to-left, 1 = left-to-right (default), horizontal flip */
+#define ILI9322_ENTRY_HDIR		BIT(0)
+/* 0 = down-to-up, 1 = up-to-down (default), vertical flip  */
+#define ILI9322_ENTRY_VDIR		BIT(1)
+/* NTSC, PAL or autodetect */
+#define ILI9322_ENTRY_NTSC		(0 << 2)
+#define ILI9322_ENTRY_PAL		(1 << 2)
+#define ILI9322_ENTRY_AUTODETECT	(3 << 2)
+/* Input format */
+#define ILI9322_ENTRY_SERIAL_RGB_THROUGH (0 << 4)
+#define ILI9322_ENTRY_SERIAL_RGB_ALIGNED (1 << 4)
+#define ILI9322_ENTRY_SERIAL_RGB_DUMMY_320X240 (2 << 4)
+#define ILI9322_ENTRY_SERIAL_RGB_DUMMY_360X240 (3 << 4)
+#define ILI9322_ENTRY_DISABLE_1		(4 << 4)
+#define ILI9322_ENTRY_PARALLEL_RGB_THROUGH (5 << 4)
+#define ILI9322_ENTRY_PARALLEL_RGB_ALIGNED (6 << 4)
+#define ILI9322_ENTRY_YUV_640Y_320CBCR_25_54_MHZ (7 << 4)
+#define ILI9322_ENTRY_YUV_720Y_360CBCR_27_MHZ (8 << 4)
+#define ILI9322_ENTRY_DISABLE_2		(9 << 4)
+#define ILI9322_ENTRY_ITU_R_BT_656_720X360 (10 << 4)
+#define ILI9322_ENTRY_ITU_R_BT_656_640X320 (11 << 4)
+
+/* Power control */
+#define ILI9322_POW_CTRL		0x07
+#define ILI9322_POW_CTRL_STB		BIT(0) /* 0 = standby, 1 = normal */
+#define ILI9322_POW_CTRL_VGL		BIT(1) /* 0 = off, 1 = on  */
+#define ILI9322_POW_CTRL_VGH		BIT(2) /* 0 = off, 1 = on  */
+#define ILI9322_POW_CTRL_DDVDH		BIT(3) /* 0 = off, 1 = on  */
+#define ILI9322_POW_CTRL_VCOM		BIT(4) /* 0 = off, 1 = on  */
+#define ILI9322_POW_CTRL_VCL		BIT(5) /* 0 = off, 1 = on  */
+#define ILI9322_POW_CTRL_AUTO		BIT(6) /* 0 = interactive, 1 = auto */
+#define ILI9322_POW_CTRL_STANDBY	(ILI9322_POW_CTRL_VGL | \
+					 ILI9322_POW_CTRL_VGH | \
+					 ILI9322_POW_CTRL_DDVDH | \
+					 ILI9322_POW_CTRL_VCL | \
+					 ILI9322_POW_CTRL_AUTO | \
+					 BIT(7))
+#define ILI9322_POW_CTRL_DEFAULT	(ILI9322_POW_CTRL_STANDBY | \
+					 ILI9322_POW_CTRL_STB)
+
+/* Vertical back porch bits 0..5 */
+#define ILI9322_VBP			0x08
+
+/* Horizontal back porch, 8 bits */
+#define ILI9322_HBP			0x09
+
+/*
+ * Polarity settings:
+ * 1 = positive polarity
+ * 0 = negative polarity
+ */
+#define ILI9322_POL			0x0a
+#define ILI9322_POL_DCLK		BIT(0) /* 1 default */
+#define ILI9322_POL_HSYNC		BIT(1) /* 0 default */
+#define ILI9322_POL_VSYNC		BIT(2) /* 0 default */
+#define ILI9322_POL_DE			BIT(3) /* 1 default */
+/*
+ * 0 means YCBCR are ordered Cb0,Y0,Cr0,Y1,Cb2,Y2,Cr2,Y3 (default)
+ *   in RGB mode this means RGB comes in RGBRGB
+ * 1 means YCBCR are ordered Cr0,Y0,Cb0,Y1,Cr2,Y2,Cb2,Y3
+ *   in RGB mode this means RGB comes in BGRBGR
+ */
+#define ILI9322_POL_YCBCR_MODE		BIT(4)
+/* Formula A for YCbCR->RGB = 0, Formula B = 1 */
+#define ILI9322_POL_FORMULA		BIT(5)
+/* Reverse polarity: 0 = 0..255, 1 = 255..0 */
+#define ILI9322_POL_REV			BIT(6)
+
+#define ILI9322_IF_CTRL			0x0b
+#define ILI9322_IF_CTRL_HSYNC_VSYNC	0x00
+#define ILI9322_IF_CTRL_HSYNC_VSYNC_DE	BIT(2)
+#define ILI9322_IF_CTRL_DE_ONLY		BIT(3)
+#define ILI9322_IF_CTRL_SYNC_DISABLED	(BIT(2) | BIT(3))
+#define ILI9322_IF_CTRL_LINE_INVERSION	BIT(0) /* Not set means frame inv */
+
+#define ILI9322_GLOBAL_RESET		0x04
+#define ILI9322_GLOBAL_RESET_ASSERT	0x00 /* bit 0 = 0 -> reset */
+
+/*
+ * 4+4 bits of negative and positive gamma correction
+ * Upper nybble, bits 4-7 are negative gamma
+ * Lower nybble, bits 0-3 are positive gamma
+ */
+#define ILI9322_GAMMA_1			0x10
+#define ILI9322_GAMMA_2			0x11
+#define ILI9322_GAMMA_3			0x12
+#define ILI9322_GAMMA_4			0x13
+#define ILI9322_GAMMA_5			0x14
+#define ILI9322_GAMMA_6			0x15
+#define ILI9322_GAMMA_7			0x16
+#define ILI9322_GAMMA_8			0x17
+
+/**
+ * enum ili9322_input - the format of the incoming signal to the panel
+ *
+ * The panel can be connected to various input streams and four of them can
+ * be selected by electronic straps on the display. However it is possible
+ * to select another mode or override the electronic default with this
+ * setting.
+ */
+enum ili9322_input {
+	ILI9322_INPUT_SRGB_THROUGH = 0x0,
+	ILI9322_INPUT_SRGB_ALIGNED = 0x1,
+	ILI9322_INPUT_SRGB_DUMMY_320X240 = 0x2,
+	ILI9322_INPUT_SRGB_DUMMY_360X240 = 0x3,
+	ILI9322_INPUT_DISABLED_1 = 0x4,
+	ILI9322_INPUT_PRGB_THROUGH = 0x5,
+	ILI9322_INPUT_PRGB_ALIGNED = 0x6,
+	ILI9322_INPUT_YUV_640X320_YCBCR = 0x7,
+	ILI9322_INPUT_YUV_720X360_YCBCR = 0x8,
+	ILI9322_INPUT_DISABLED_2 = 0x9,
+	ILI9322_INPUT_ITU_R_BT656_720X360_YCBCR = 0xa,
+	ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR = 0xb,
+	ILI9322_INPUT_UNKNOWN = 0xc,
+};
+
+const char *ili9322_inputs[] = {
+	"8 bit serial RGB through",
+	"8 bit serial RGB aligned",
+	"8 bit serial RGB dummy 320x240",
+	"8 bit serial RGB dummy 360x240",
+	"disabled 1",
+	"24 bit parallel RGB through",
+	"24 bit parallel RGB aligned",
+	"24 bit YUV 640Y 320CbCr",
+	"24 bit YUV 720Y 360CbCr",
+	"disabled 2",
+	"8 bit ITU-R BT.656 720Y 360CbCr",
+	"8 bit ITU-R BT.656 640Y 320CbCr",
+};
+
+/**
+ * struct ili9322_config - the system specific ILI9322 configuration
+ * @width_mm: physical panel width [mm]
+ * @height_mm: physical panel height [mm]
+ * @flip_horizontal: flip the image horizontally (right-to-left scan)
+ * (only in RGB and YUV modes)
+ * @flip_vertical: flip the image vertically (down-to-up scan)
+ * (only in RGB and YUV modes)
+ * @input: the input/entry type used in this system, if this is set to
+ * ILI9322_INPUT_UNKNOWN the driver will try to figure it out by probing
+ * the hardware
+ * @vreg1out_mv: the output in microvolts for the VREGOUT1 regulator used
+ * to drive the physical display. Valid ranges are 3600 thru 6000 in 100
+ * microvolt increments. If not specified, hardware defaults will be
+ * used (4.5V).
+ * @vcom_high_percent: the percentage of VREGOUT1 used for the peak
+ * voltage on the communications link. Valid ranges are 37 thru 100
+ * percent. If not specified, hardware defaults will be used (91%).
+ * @vcom_amplitude_percent: the percentage of VREGOUT1 used for the
+ * peak-to-peak amplitude of the communcation signals to the physical
+ * display. Valid ranges are 70 thru 132 percent in increments if two
+ * percent. Odd percentages will be truncated. If not specified, hardware
+ * defaults will be used (114%).
+ * @dclk_active_high: data/pixel clock active high, data will be clocked
+ * in on the rising edge of the DCLK (this is usually the case).
+ * @syncmode: The synchronization mode, what sync signals are emitted.
+ * See the enum for details.
+ * @de_active_high: DE (data entry) is active high
+ * @hsync_active_high: HSYNC is active high
+ * @vsync_active_high: VSYNC is active high
+ * @gamma_corr_pos: a set of 8 nybbles describing positive
+ * gamma correction for voltages V1 thru V8. Valid range 0..15
+ * @gamma_corr_neg: a set of 8 nybbles describing negative
+ * gamma correction for voltages V1 thru V8. Valid range 0..15
+ *
+ * These adjust what grayscale voltage will be output for input data V1 = 0,
+ * V2 = 16, V3 = 48, V4 = 96, V5 = 160, V6 = 208, V7 = 240 and V8 = 255.
+ * The curve is shaped like this:
+ *
+ *  ^
+ *  |                                                        V8
+ *  |                                                   V7
+ *  |                                          V6
+ *  |                               V5
+ *  |                    V4
+ *  |            V3
+ *  |     V2
+ *  | V1
+ *  +----------------------------------------------------------->
+ *    0   16     48      96         160        208      240  255
+ *
+ * The negative and postive gamma values adjust the V1 thru V8 up/down
+ * according to the datasheet specifications. This is a property of the
+ * physical display connected to the display controller and may vary.
+ * If defined, both arrays must be supplied in full. If the properties
+ * are not supplied, hardware defaults will be used.
+ */
+struct ili9322_config {
+	u32 width_mm;
+	u32 height_mm;
+	bool flip_horizontal;
+	bool flip_vertical;
+	enum ili9322_input input;
+	u32 vreg1out_mv;
+	u32 vcom_high_percent;
+	u32 vcom_amplitude_percent;
+	bool dclk_active_high;
+	bool de_active_high;
+	bool hsync_active_high;
+	bool vsync_active_high;
+	u8 syncmode;
+	u8 gamma_corr_pos[8];
+	u8 gamma_corr_neg[8];
+};
+
+struct ili9322 {
+	struct device *dev;
+	const struct ili9322_config *conf;
+	struct drm_panel panel;
+	struct regmap *regmap;
+	struct regulator_bulk_data supplies[3];
+	struct gpio_desc *reset_gpio;
+	enum ili9322_input input;
+	struct videomode vm;
+	u8 gamma[8];
+	u8 vreg1out;
+	u8 vcom_high;
+	u8 vcom_amplitude;
+};
+
+static inline struct ili9322 *panel_to_ili9322(struct drm_panel *panel)
+{
+	return container_of(panel, struct ili9322, panel);
+}
+
+static int ili9322_regmap_spi_write(void *context, const void *data,
+				    size_t count)
+{
+	struct device *dev = context;
+	struct spi_device *spi = to_spi_device(dev);
+	u8 buf[2];
+
+	/* Clear bit 7 to write */
+	memcpy(buf, data, 2);
+	buf[0] &= ~0x80;
+
+	dev_dbg(dev, "WRITE: %02x %02x\n", buf[0], buf[1]);
+	return spi_write_then_read(spi, buf, 2, NULL, 0);
+}
+
+static int ili9322_regmap_spi_read(void *context, const void *reg,
+				   size_t reg_size, void *val, size_t val_size)
+{
+	struct device *dev = context;
+	struct spi_device *spi = to_spi_device(dev);
+	u8 buf[1];
+
+	/* Set bit 7 to 1 to read */
+	memcpy(buf, reg, 1);
+	dev_dbg(dev, "READ: %02x reg size = %zu, val size = %zu\n",
+		buf[0], reg_size, val_size);
+	buf[0] |= 0x80;
+
+	return spi_write_then_read(spi, buf, 1, val, 1);
+}
+
+static struct regmap_bus ili9322_regmap_bus = {
+	.write = ili9322_regmap_spi_write,
+	.read = ili9322_regmap_spi_read,
+	.reg_format_endian_default = REGMAP_ENDIAN_BIG,
+	.val_format_endian_default = REGMAP_ENDIAN_BIG,
+};
+
+static bool ili9322_volatile_reg(struct device *dev, unsigned int reg)
+{
+	return false;
+}
+
+static bool ili9322_writeable_reg(struct device *dev, unsigned int reg)
+{
+	/* Just register 0 is read-only */
+	if (reg == 0x00)
+		return false;
+	return true;
+}
+
+const struct regmap_config ili9322_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0x44,
+	.cache_type = REGCACHE_RBTREE,
+	.volatile_reg = ili9322_volatile_reg,
+	.writeable_reg = ili9322_writeable_reg,
+};
+
+static int ili9322_init(struct drm_panel *panel, struct ili9322 *ili)
+{
+	struct drm_connector *connector = panel->connector;
+	u8 reg;
+	int ret;
+	int i;
+
+	/* Reset display */
+	ret = regmap_write(ili->regmap, ILI9322_GLOBAL_RESET,
+			   ILI9322_GLOBAL_RESET_ASSERT);
+	if (ret) {
+		dev_err(ili->dev, "can't issue GRESET (%d)\n", ret);
+		return ret;
+	}
+
+	/* Set up the main voltage regulator */
+	if (ili->vreg1out != U8_MAX) {
+		ret = regmap_write(ili->regmap, ILI9322_VREG1_VOLTAGE,
+				   ili->vreg1out);
+		if (ret) {
+			dev_err(ili->dev, "can't set up VREG1OUT (%d)\n", ret);
+			return ret;
+		}
+	}
+
+	if (ili->vcom_amplitude != U8_MAX) {
+		ret = regmap_write(ili->regmap, ILI9322_VCOM_AMP,
+				   ili->vcom_amplitude);
+		if (ret) {
+			dev_err(ili->dev,
+				"can't set up VCOM amplitude (%d)\n", ret);
+			return ret;
+		}
+	};
+
+	if (ili->vcom_high != U8_MAX) {
+		ret = regmap_write(ili->regmap, ILI9322_VCOM_HIGH,
+				   ili->vcom_high);
+		if (ret) {
+			dev_err(ili->dev, "can't set up VCOM high (%d)\n", ret);
+			return ret;
+		}
+	};
+
+	/* Set up gamma correction */
+	for (i = 0; i < ARRAY_SIZE(ili->gamma); i++) {
+		ret = regmap_write(ili->regmap, ILI9322_GAMMA_1 + i,
+				   ili->gamma[i]);
+		if (ret) {
+			dev_err(ili->dev,
+				"can't write gamma V%d to 0x%02x (%d)\n",
+				i + 1, ILI9322_GAMMA_1 + i, ret);
+			return ret;
+		}
+	}
+
+	/*
+	 * Polarity and inverted color order for RGB input.
+	 * None of this applies in the BT.656 mode.
+	 */
+	if (ili->conf->dclk_active_high) {
+		reg = ILI9322_POL_DCLK;
+		connector->display_info.bus_flags |=
+			DRM_BUS_FLAG_PIXDATA_POSEDGE;
+	} else {
+		reg = 0;
+		connector->display_info.bus_flags |=
+			DRM_BUS_FLAG_PIXDATA_NEGEDGE;
+	}
+	if (ili->conf->de_active_high) {
+		reg |= ILI9322_POL_DE;
+		connector->display_info.bus_flags |=
+			DRM_BUS_FLAG_DE_HIGH;
+	} else {
+		connector->display_info.bus_flags |=
+			DRM_BUS_FLAG_DE_LOW;
+	}
+	if (ili->conf->hsync_active_high)
+		reg |= ILI9322_POL_HSYNC;
+	if (ili->conf->vsync_active_high)
+		reg |= ILI9322_POL_VSYNC;
+	ret = regmap_write(ili->regmap, ILI9322_POL, reg);
+	if (ret) {
+		dev_err(ili->dev, "can't write POL register (%d)\n", ret);
+		return ret;
+	}
+
+	/*
+	 * Set up interface control.
+	 * This is not used in the BT.656 mode (no H/Vsync or DE signals).
+	 */
+	reg = ili->conf->syncmode;
+	reg |= ILI9322_IF_CTRL_LINE_INVERSION;
+	ret = regmap_write(ili->regmap, ILI9322_IF_CTRL, reg);
+	if (ret) {
+		dev_err(ili->dev, "can't write IF CTRL register (%d)\n", ret);
+		return ret;
+	}
+
+	/* Set up the input mode */
+	reg = (ili->input << 4);
+	/* These are inverted, setting to 1 is the default, clearing flips */
+	if (!ili->conf->flip_horizontal)
+		reg |= ILI9322_ENTRY_HDIR;
+	if (!ili->conf->flip_vertical)
+		reg |= ILI9322_ENTRY_VDIR;
+	reg |= ILI9322_ENTRY_AUTODETECT;
+	ret = regmap_write(ili->regmap, ILI9322_ENTRY, reg);
+	if (ret) {
+		dev_err(ili->dev, "can't write ENTRY reg (%d)\n", ret);
+		return ret;
+	}
+	dev_info(ili->dev, "display is in %s mode, syncmode %02x\n",
+		 ili9322_inputs[ili->input],
+		 ili->conf->syncmode);
+
+	dev_info(ili->dev, "initialized display\n");
+
+	return 0;
+}
+
+/*
+ * This power-on sequence if from the datasheet, page 57.
+ */
+static int ili9322_power_on(struct ili9322 *ili)
+{
+	int ret;
+
+	/* Assert RESET */
+	gpiod_set_value(ili->reset_gpio, 1);
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(ili->supplies), ili->supplies);
+	if (ret < 0) {
+		dev_err(ili->dev, "unable to enable regulators\n");
+		return ret;
+	}
+	msleep(20);
+
+	/* De-assert RESET */
+	gpiod_set_value(ili->reset_gpio, 0);
+
+	msleep(10);
+
+	return 0;
+}
+
+static int ili9322_power_off(struct ili9322 *ili)
+{
+	return regulator_bulk_disable(ARRAY_SIZE(ili->supplies), ili->supplies);
+}
+
+static int ili9322_disable(struct drm_panel *panel)
+{
+	struct ili9322 *ili = panel_to_ili9322(panel);
+	int ret;
+
+	ret = regmap_write(ili->regmap, ILI9322_POW_CTRL,
+			   ILI9322_POW_CTRL_STANDBY);
+	if (ret) {
+		dev_err(ili->dev, "unable to go to standby mode\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ili9322_unprepare(struct drm_panel *panel)
+{
+	struct ili9322 *ili = panel_to_ili9322(panel);
+
+	return ili9322_power_off(ili);
+}
+
+static int ili9322_prepare(struct drm_panel *panel)
+{
+	struct ili9322 *ili = panel_to_ili9322(panel);
+	int ret;
+
+	ret = ili9322_power_on(ili);
+	if (ret < 0)
+		return ret;
+
+	ret = ili9322_init(panel, ili);
+	if (ret < 0)
+		ili9322_unprepare(panel);
+
+	return ret;
+}
+
+static int ili9322_enable(struct drm_panel *panel)
+{
+	struct ili9322 *ili = panel_to_ili9322(panel);
+	int ret;
+
+	ret = regmap_write(ili->regmap, ILI9322_POW_CTRL,
+			   ILI9322_POW_CTRL_DEFAULT);
+	if (ret) {
+		dev_err(ili->dev, "unable to enable panel\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/* Serial RGB modes */
+static const struct drm_display_mode srgb_320x240_mode = {
+	.clock = 2453500,
+	.hdisplay = 320,
+	.hsync_start = 320 + 359,
+	.hsync_end = 320 + 359 + 1,
+	.htotal = 320 + 359 + 1 + 241,
+	.vdisplay = 240,
+	.vsync_start = 240 + 4,
+	.vsync_end = 240 + 4 + 1,
+	.vtotal = 262,
+	.vrefresh = 60,
+	.flags = 0,
+};
+
+static const struct drm_display_mode srgb_360x240_mode = {
+	.clock = 2700000,
+	.hdisplay = 360,
+	.hsync_start = 360 + 35,
+	.hsync_end = 360 + 35 + 1,
+	.htotal = 360 + 35 + 1 + 241,
+	.vdisplay = 240,
+	.vsync_start = 240 + 21,
+	.vsync_end = 240 + 21 + 1,
+	.vtotal = 262,
+	.vrefresh = 60,
+	.flags = 0,
+};
+
+/* This is the only mode listed for parallel RGB in the datasheet */
+static const struct drm_display_mode prgb_320x240_mode = {
+	.clock = 6400000,
+	.hdisplay = 320,
+	.hsync_start = 320 + 38,
+	.hsync_end = 320 + 38 + 1,
+	.htotal = 320 + 38 + 1 + 50,
+	.vdisplay = 240,
+	.vsync_start = 240 + 4,
+	.vsync_end = 240 + 4 + 1,
+	.vtotal = 262,
+	.vrefresh = 60,
+	.flags = 0,
+};
+
+/* YUV modes */
+static const struct drm_display_mode yuv_640x320_mode = {
+	.clock = 2454000,
+	.hdisplay = 640,
+	.hsync_start = 640 + 252,
+	.hsync_end = 640 + 252 + 1,
+	.htotal = 640 + 252 + 1 + 28,
+	.vdisplay = 320,
+	.vsync_start = 320 + 4,
+	.vsync_end = 320 + 4 + 1,
+	.vtotal = 320 + 4 + 1 + 18,
+	.vrefresh = 60,
+	.flags = 0,
+};
+
+static const struct drm_display_mode yuv_720x360_mode = {
+	.clock = 2700000,
+	.hdisplay = 720,
+	.hsync_start = 720 + 252,
+	.hsync_end = 720 + 252 + 1,
+	.htotal = 720 + 252 + 1 + 24,
+	.vdisplay = 360,
+	.vsync_start = 360 + 4,
+	.vsync_end = 360 + 4 + 1,
+	.vtotal = 360 + 4 + 1 + 18,
+	.vrefresh = 60,
+	.flags = 0,
+};
+
+/* BT.656 VGA mode, 640x480 */
+static const struct drm_display_mode itu_r_bt_656_640_mode = {
+	.clock = 2454000,
+	.hdisplay = 640,
+	.hsync_start = 640 + 3,
+	.hsync_end = 640 + 3 + 1,
+	.htotal = 640 + 3 + 1 + 272,
+	.vdisplay = 480,
+	.vsync_start = 480 + 4,
+	.vsync_end = 480 + 4 + 1,
+	.vtotal = 500,
+	.vrefresh = 60,
+	.flags = 0,
+};
+
+/* BT.656 D1 mode 720x480 */
+static const struct drm_display_mode itu_r_bt_656_720_mode = {
+	.clock = 2700000,
+	.hdisplay = 720,
+	.hsync_start = 720 + 3,
+	.hsync_end = 720 + 3 + 1,
+	.htotal = 720 + 3 + 1 + 272,
+	.vdisplay = 480,
+	.vsync_start = 480 + 4,
+	.vsync_end = 480 + 4 + 1,
+	.vtotal = 500,
+	.vrefresh = 60,
+	.flags = 0,
+};
+
+static int ili9322_get_modes(struct drm_panel *panel)
+{
+	struct drm_connector *connector = panel->connector;
+	struct ili9322 *ili = panel_to_ili9322(panel);
+	struct drm_display_mode *mode;
+
+	strncpy(connector->display_info.name, "ILI9322 TFT LCD driver\0",
+		DRM_DISPLAY_INFO_LEN);
+	connector->display_info.width_mm = ili->conf->width_mm;
+	connector->display_info.height_mm = ili->conf->height_mm;
+
+	switch (ili->input) {
+	case ILI9322_INPUT_SRGB_DUMMY_320X240:
+		mode = drm_mode_duplicate(panel->drm, &srgb_320x240_mode);
+		break;
+	case ILI9322_INPUT_SRGB_DUMMY_360X240:
+		mode = drm_mode_duplicate(panel->drm, &srgb_360x240_mode);
+		break;
+	case ILI9322_INPUT_PRGB_THROUGH:
+	case ILI9322_INPUT_PRGB_ALIGNED:
+		mode = drm_mode_duplicate(panel->drm, &prgb_320x240_mode);
+		break;
+	case ILI9322_INPUT_YUV_640X320_YCBCR:
+		mode = drm_mode_duplicate(panel->drm, &yuv_640x320_mode);
+		break;
+	case ILI9322_INPUT_YUV_720X360_YCBCR:
+		mode = drm_mode_duplicate(panel->drm, &yuv_720x360_mode);
+		break;
+	case ILI9322_INPUT_ITU_R_BT656_720X360_YCBCR:
+		mode = drm_mode_duplicate(panel->drm, &itu_r_bt_656_720_mode);
+		break;
+	case ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR:
+		mode = drm_mode_duplicate(panel->drm, &itu_r_bt_656_640_mode);
+		break;
+	default:
+		mode = NULL;
+		break;
+	}
+	if (!mode) {
+		DRM_ERROR("bad mode or failed to add mode\n");
+		return -EINVAL;
+	}
+	drm_mode_set_name(mode);
+	/*
+	 * This is the preferred mode because most people are going
+	 * to want to use the display with VGA type graphics.
+	 */
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+	/* Set up the polarity */
+	if (ili->conf->hsync_active_high)
+		mode->flags |= DRM_MODE_FLAG_PHSYNC;
+	else
+		mode->flags |= DRM_MODE_FLAG_NHSYNC;
+	if (ili->conf->vsync_active_high)
+		mode->flags |= DRM_MODE_FLAG_PVSYNC;
+	else
+		mode->flags |= DRM_MODE_FLAG_NVSYNC;
+
+	mode->width_mm = ili->conf->width_mm;
+	mode->height_mm = ili->conf->height_mm;
+	drm_mode_probed_add(connector, mode);
+
+	return 1; /* Number of modes */
+}
+
+static const struct drm_panel_funcs ili9322_drm_funcs = {
+	.disable = ili9322_disable,
+	.unprepare = ili9322_unprepare,
+	.prepare = ili9322_prepare,
+	.enable = ili9322_enable,
+	.get_modes = ili9322_get_modes,
+};
+
+static int ili9322_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct ili9322 *ili;
+	const struct regmap_config *regmap_config;
+	u8 gamma;
+	u32 val;
+	int ret;
+	int i;
+
+	ili = devm_kzalloc(dev, sizeof(struct ili9322), GFP_KERNEL);
+	if (!ili)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, ili);
+
+	ili->dev = dev;
+
+	/*
+	 * Every new incarnation of this display must have a unique
+	 * data entry for the system in this driver.
+	 */
+	ili->conf = of_device_get_match_data(dev);
+	if (!ili->conf) {
+		dev_err(dev, "missing device configuration\n");
+		return -ENODEV;
+	}
+
+	val = ili->conf->vreg1out_mv;
+	if (!val) {
+		/* Default HW value, do not touch (should be 4.5V) */
+		ili->vreg1out = U8_MAX;
+	} else {
+		if (val < 3600) {
+			dev_err(dev, "too low VREG1OUT\n");
+			return -EINVAL;
+		}
+		if (val > 6000) {
+			dev_err(dev, "too high VREG1OUT\n");
+			return -EINVAL;
+		}
+		if ((val % 100) != 0) {
+			dev_err(dev, "VREG1OUT is no even 100 microvolt\n");
+			return -EINVAL;
+		}
+		val -= 3600;
+		val /= 100;
+		dev_dbg(dev, "VREG1OUT = 0x%02x\n", val);
+		ili->vreg1out = val;
+	}
+
+	val = ili->conf->vcom_high_percent;
+	if (!val) {
+		/* Default HW value, do not touch (should be 91%) */
+		ili->vcom_high = U8_MAX;
+	} else {
+		if (val < 37) {
+			dev_err(dev, "too low VCOM high\n");
+			return -EINVAL;
+		}
+		if (val > 100) {
+			dev_err(dev, "too high VCOM high\n");
+			return -EINVAL;
+		}
+		val -= 37;
+		dev_dbg(dev, "VCOM high = 0x%02x\n", val);
+		ili->vcom_high = val;
+	}
+
+	val = ili->conf->vcom_amplitude_percent;
+	if (!val) {
+		/* Default HW value, do not touch (should be 114%) */
+		ili->vcom_high = U8_MAX;
+	} else {
+		if (val < 70) {
+			dev_err(dev, "too low VCOM amplitude\n");
+			return -EINVAL;
+		}
+		if (val > 132) {
+			dev_err(dev, "too high VCOM amplitude\n");
+			return -EINVAL;
+		}
+		val -= 70;
+		val >>= 1; /* Increments of 2% */
+		dev_dbg(dev, "VCOM amplitude = 0x%02x\n", val);
+		ili->vcom_amplitude = val;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(ili->gamma); i++) {
+		val = ili->conf->gamma_corr_neg[i];
+		if (val > 15) {
+			dev_err(dev, "negative gamma %u > 15, capping\n", val);
+			val = 15;
+		}
+		gamma = val << 4;
+		val = ili->conf->gamma_corr_pos[i];
+		if (val > 15) {
+			dev_err(dev, "positive gamma %u > 15, capping\n", val);
+			val = 15;
+		}
+		gamma |= val;
+		ili->gamma[i] = gamma;
+		dev_dbg(dev, "gamma V%d: 0x%02x\n", i + 1, gamma);
+	}
+
+	ili->supplies[0].supply = "vcc"; /* 2.7-3.6 V */
+	ili->supplies[1].supply = "iovcc"; /* 1.65-3.6V */
+	ili->supplies[2].supply = "vci"; /* 2.7-3.6V */
+	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ili->supplies),
+				      ili->supplies);
+	if (ret < 0)
+		return ret;
+	ret = regulator_set_voltage(ili->supplies[0].consumer,
+				    2700000, 3600000);
+	if (ret)
+		return ret;
+	ret = regulator_set_voltage(ili->supplies[1].consumer,
+				    1650000, 3600000);
+	if (ret)
+		return ret;
+	ret = regulator_set_voltage(ili->supplies[2].consumer,
+				    2700000, 3600000);
+	if (ret)
+		return ret;
+
+	ili->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(ili->reset_gpio)) {
+		dev_err(dev, "failed to get RESET GPIO\n");
+		return PTR_ERR(ili->reset_gpio);
+	}
+
+	spi->bits_per_word = 8;
+	ret = spi_setup(spi);
+	if (ret < 0) {
+		dev_err(dev, "spi setup failed.\n");
+		return ret;
+	}
+	regmap_config = &ili9322_regmap_config;
+	ili->regmap = devm_regmap_init(dev, &ili9322_regmap_bus, dev,
+				       regmap_config);
+	if (IS_ERR(ili->regmap)) {
+		dev_err(dev, "failed to allocate register map\n");
+		return PTR_ERR(ili->regmap);
+	}
+
+	ret = regmap_read(ili->regmap, ILI9322_CHIP_ID, &val);
+	if (ret) {
+		dev_err(dev, "can't get chip ID (%d)\n", ret);
+		return ret;
+	}
+	if (val != ILI9322_CHIP_ID_MAGIC) {
+		dev_err(dev, "chip ID 0x%0x2, expected 0x%02x\n", val,
+			ILI9322_CHIP_ID_MAGIC);
+		return -ENODEV;
+	}
+
+	/* Probe the system to find the display setting */
+	if (ili->conf->input == ILI9322_INPUT_UNKNOWN) {
+		ret = regmap_read(ili->regmap, ILI9322_ENTRY, &val);
+		if (ret) {
+			dev_err(dev, "can't get entry setting (%d)\n", ret);
+			return ret;
+		}
+		/* Input enum corresponds to HW setting */
+		ili->input = (val >> 4) & 0x0f;
+		if (ili->input >= ILI9322_INPUT_UNKNOWN)
+			ili->input = ILI9322_INPUT_UNKNOWN;
+	} else {
+		ili->input = ili->conf->input;
+	}
+
+	drm_panel_init(&ili->panel);
+	ili->panel.dev = dev;
+	ili->panel.funcs = &ili9322_drm_funcs;
+
+	return drm_panel_add(&ili->panel);
+}
+
+static int ili9322_remove(struct spi_device *spi)
+{
+	struct ili9322 *ili = spi_get_drvdata(spi);
+
+	ili9322_power_off(ili);
+	drm_panel_remove(&ili->panel);
+
+	return 0;
+}
+
+/*
+ * The D-Link DIR-685 panel is marked LM918A01-1A SY-B4-091116-E0199
+ */
+static const struct ili9322_config ili9322_dir_685 = {
+	.width_mm = 65,
+	.height_mm = 50,
+	.input = ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR,
+	.vreg1out_mv = 4600,
+	.vcom_high_percent = 91,
+	.vcom_amplitude_percent = 114,
+	.syncmode = ILI9322_IF_CTRL_SYNC_DISABLED,
+	.dclk_active_high = true,
+	.gamma_corr_neg = { 0xa, 0x5, 0x7, 0x7, 0x7, 0x5, 0x1, 0x6 },
+	.gamma_corr_pos = { 0x7, 0x7, 0x3, 0x2, 0x3, 0x5, 0x7, 0x2 },
+};
+
+static const struct of_device_id ili9322_of_match[] = {
+	{
+		.compatible = "dlink,dir-685-panel",
+		.data = &ili9322_dir_685,
+	},
+	{
+		.compatible = "ilitek,ili9322",
+		.data = NULL,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ili9322_of_match);
+
+static struct spi_driver ili9322_driver = {
+	.probe = ili9322_probe,
+	.remove = ili9322_remove,
+	.driver = {
+		.name = "panel-ilitek-ili9322",
+		.of_match_table = ili9322_of_match,
+	},
+};
+module_spi_driver(ili9322_driver);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("ILI9322 LCD panel driver");
+MODULE_LICENSE("GPL v2");

+ 23 - 0
drivers/gpu/drm/panel/panel-lvds.c

@@ -17,6 +17,7 @@
 #include <linux/module.h>
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
 #include <linux/slab.h>
 
 #include <drm/drmP.h>
@@ -39,6 +40,7 @@ struct panel_lvds {
 	bool data_mirror;
 
 	struct backlight_device *backlight;
+	struct regulator *supply;
 
 	struct gpio_desc *enable_gpio;
 	struct gpio_desc *reset_gpio;
@@ -69,6 +71,9 @@ static int panel_lvds_unprepare(struct drm_panel *panel)
 	if (lvds->enable_gpio)
 		gpiod_set_value_cansleep(lvds->enable_gpio, 0);
 
+	if (lvds->supply)
+		regulator_disable(lvds->supply);
+
 	return 0;
 }
 
@@ -76,6 +81,17 @@ static int panel_lvds_prepare(struct drm_panel *panel)
 {
 	struct panel_lvds *lvds = to_panel_lvds(panel);
 
+	if (lvds->supply) {
+		int err;
+
+		err = regulator_enable(lvds->supply);
+		if (err < 0) {
+			dev_err(lvds->dev, "failed to enable supply: %d\n",
+				err);
+			return err;
+		}
+	}
+
 	if (lvds->enable_gpio)
 		gpiod_set_value_cansleep(lvds->enable_gpio, 1);
 
@@ -196,6 +212,13 @@ static int panel_lvds_probe(struct platform_device *pdev)
 	if (ret < 0)
 		return ret;
 
+	lvds->supply = devm_regulator_get_optional(lvds->dev, "power");
+	if (IS_ERR(lvds->supply)) {
+		ret = PTR_ERR(lvds->supply);
+		dev_err(lvds->dev, "failed to request regulator: %d\n", ret);
+		return ret;
+	}
+
 	/* Get GPIOs and backlight controller. */
 	lvds->enable_gpio = devm_gpiod_get_optional(lvds->dev, "enable",
 						     GPIOD_OUT_LOW);

+ 1 - 0
drivers/gpu/drm/sun4i/Makefile

@@ -15,6 +15,7 @@ sun8i-mixer-y			+= sun8i_mixer.o sun8i_ui_layer.o \
 
 sun4i-tcon-y			+= sun4i_crtc.o
 sun4i-tcon-y			+= sun4i_dotclock.o
+sun4i-tcon-y			+= sun4i_lvds.o
 sun4i-tcon-y			+= sun4i_tcon.o
 sun4i-tcon-y			+= sun4i_rgb.o
 

+ 7 - 3
drivers/gpu/drm/sun4i/sun4i_dotclock.c

@@ -17,8 +17,9 @@
 #include "sun4i_dotclock.h"
 
 struct sun4i_dclk {
-	struct clk_hw	hw;
-	struct regmap	*regmap;
+	struct clk_hw		hw;
+	struct regmap		*regmap;
+	struct sun4i_tcon	*tcon;
 };
 
 static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw)
@@ -73,11 +74,13 @@ static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw,
 static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate,
 				  unsigned long *parent_rate)
 {
+	struct sun4i_dclk *dclk = hw_to_dclk(hw);
+	struct sun4i_tcon *tcon = dclk->tcon;
 	unsigned long best_parent = 0;
 	u8 best_div = 1;
 	int i;
 
-	for (i = 6; i <= 127; i++) {
+	for (i = tcon->dclk_min_div; i <= tcon->dclk_max_div; i++) {
 		unsigned long ideal = rate * i;
 		unsigned long rounded;
 
@@ -167,6 +170,7 @@ int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon)
 	dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL);
 	if (!dclk)
 		return -ENOMEM;
+	dclk->tcon = tcon;
 
 	init.name = clk_name;
 	init.ops = &sun4i_dclk_ops;

+ 1 - 0
drivers/gpu/drm/sun4i/sun4i_drv.c

@@ -339,6 +339,7 @@ static const struct of_device_id sun4i_drv_of_table[] = {
 	{ .compatible = "allwinner,sun6i-a31s-display-engine" },
 	{ .compatible = "allwinner,sun7i-a20-display-engine" },
 	{ .compatible = "allwinner,sun8i-a33-display-engine" },
+	{ .compatible = "allwinner,sun8i-a83t-display-engine" },
 	{ .compatible = "allwinner,sun8i-v3s-display-engine" },
 	{ }
 };

+ 177 - 0
drivers/gpu/drm/sun4i/sun4i_lvds.c

@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Free Electrons
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ */
+
+#include <linux/clk.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#include "sun4i_crtc.h"
+#include "sun4i_tcon.h"
+#include "sun4i_lvds.h"
+
+struct sun4i_lvds {
+	struct drm_connector	connector;
+	struct drm_encoder	encoder;
+
+	struct sun4i_tcon	*tcon;
+};
+
+static inline struct sun4i_lvds *
+drm_connector_to_sun4i_lvds(struct drm_connector *connector)
+{
+	return container_of(connector, struct sun4i_lvds,
+			    connector);
+}
+
+static inline struct sun4i_lvds *
+drm_encoder_to_sun4i_lvds(struct drm_encoder *encoder)
+{
+	return container_of(encoder, struct sun4i_lvds,
+			    encoder);
+}
+
+static int sun4i_lvds_get_modes(struct drm_connector *connector)
+{
+	struct sun4i_lvds *lvds =
+		drm_connector_to_sun4i_lvds(connector);
+	struct sun4i_tcon *tcon = lvds->tcon;
+
+	return drm_panel_get_modes(tcon->panel);
+}
+
+static struct drm_connector_helper_funcs sun4i_lvds_con_helper_funcs = {
+	.get_modes	= sun4i_lvds_get_modes,
+};
+
+static void
+sun4i_lvds_connector_destroy(struct drm_connector *connector)
+{
+	struct sun4i_lvds *lvds = drm_connector_to_sun4i_lvds(connector);
+	struct sun4i_tcon *tcon = lvds->tcon;
+
+	drm_panel_detach(tcon->panel);
+	drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs sun4i_lvds_con_funcs = {
+	.fill_modes		= drm_helper_probe_single_connector_modes,
+	.destroy		= sun4i_lvds_connector_destroy,
+	.reset			= drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
+};
+
+static void sun4i_lvds_encoder_enable(struct drm_encoder *encoder)
+{
+	struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder);
+	struct sun4i_tcon *tcon = lvds->tcon;
+
+	DRM_DEBUG_DRIVER("Enabling LVDS output\n");
+
+	if (!IS_ERR(tcon->panel)) {
+		drm_panel_prepare(tcon->panel);
+		drm_panel_enable(tcon->panel);
+	}
+}
+
+static void sun4i_lvds_encoder_disable(struct drm_encoder *encoder)
+{
+	struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder);
+	struct sun4i_tcon *tcon = lvds->tcon;
+
+	DRM_DEBUG_DRIVER("Disabling LVDS output\n");
+
+	if (!IS_ERR(tcon->panel)) {
+		drm_panel_disable(tcon->panel);
+		drm_panel_unprepare(tcon->panel);
+	}
+}
+
+static const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = {
+	.disable	= sun4i_lvds_encoder_disable,
+	.enable		= sun4i_lvds_encoder_enable,
+};
+
+static const struct drm_encoder_funcs sun4i_lvds_enc_funcs = {
+	.destroy	= drm_encoder_cleanup,
+};
+
+int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon)
+{
+	struct drm_encoder *encoder;
+	struct drm_bridge *bridge;
+	struct sun4i_lvds *lvds;
+	int ret;
+
+	lvds = devm_kzalloc(drm->dev, sizeof(*lvds), GFP_KERNEL);
+	if (!lvds)
+		return -ENOMEM;
+	lvds->tcon = tcon;
+	encoder = &lvds->encoder;
+
+	ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0,
+					  &tcon->panel, &bridge);
+	if (ret) {
+		dev_info(drm->dev, "No panel or bridge found... LVDS output disabled\n");
+		return 0;
+	}
+
+	drm_encoder_helper_add(&lvds->encoder,
+			       &sun4i_lvds_enc_helper_funcs);
+	ret = drm_encoder_init(drm,
+			       &lvds->encoder,
+			       &sun4i_lvds_enc_funcs,
+			       DRM_MODE_ENCODER_LVDS,
+			       NULL);
+	if (ret) {
+		dev_err(drm->dev, "Couldn't initialise the lvds encoder\n");
+		goto err_out;
+	}
+
+	/* The LVDS encoder can only work with the TCON channel 0 */
+	lvds->encoder.possible_crtcs = BIT(drm_crtc_index(&tcon->crtc->crtc));
+
+	if (tcon->panel) {
+		drm_connector_helper_add(&lvds->connector,
+					 &sun4i_lvds_con_helper_funcs);
+		ret = drm_connector_init(drm, &lvds->connector,
+					 &sun4i_lvds_con_funcs,
+					 DRM_MODE_CONNECTOR_LVDS);
+		if (ret) {
+			dev_err(drm->dev, "Couldn't initialise the lvds connector\n");
+			goto err_cleanup_connector;
+		}
+
+		drm_mode_connector_attach_encoder(&lvds->connector,
+						  &lvds->encoder);
+
+		ret = drm_panel_attach(tcon->panel, &lvds->connector);
+		if (ret) {
+			dev_err(drm->dev, "Couldn't attach our panel\n");
+			goto err_cleanup_connector;
+		}
+	}
+
+	if (bridge) {
+		ret = drm_bridge_attach(encoder, bridge, NULL);
+		if (ret) {
+			dev_err(drm->dev, "Couldn't attach our bridge\n");
+			goto err_cleanup_connector;
+		}
+	}
+
+	return 0;
+
+err_cleanup_connector:
+	drm_encoder_cleanup(&lvds->encoder);
+err_out:
+	return ret;
+}
+EXPORT_SYMBOL(sun4i_lvds_init);

+ 12 - 0
drivers/gpu/drm/sun4i/sun4i_lvds.h

@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Free Electrons
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ */
+
+#ifndef _SUN4I_LVDS_H_
+#define _SUN4I_LVDS_H_
+
+int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon);
+
+#endif /* _SUN4I_LVDS_H_ */

+ 243 - 1
drivers/gpu/drm/sun4i/sun4i_tcon.c

@@ -31,10 +31,52 @@
 #include "sun4i_crtc.h"
 #include "sun4i_dotclock.h"
 #include "sun4i_drv.h"
+#include "sun4i_lvds.h"
 #include "sun4i_rgb.h"
 #include "sun4i_tcon.h"
 #include "sunxi_engine.h"
 
+static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder)
+{
+	struct drm_connector *connector;
+	struct drm_connector_list_iter iter;
+
+	drm_connector_list_iter_begin(encoder->dev, &iter);
+	drm_for_each_connector_iter(connector, &iter)
+		if (connector->encoder == encoder) {
+			drm_connector_list_iter_end(&iter);
+			return connector;
+		}
+	drm_connector_list_iter_end(&iter);
+
+	return NULL;
+}
+
+static int sun4i_tcon_get_pixel_depth(const struct drm_encoder *encoder)
+{
+	struct drm_connector *connector;
+	struct drm_display_info *info;
+
+	connector = sun4i_tcon_get_connector(encoder);
+	if (!connector)
+		return -EINVAL;
+
+	info = &connector->display_info;
+	if (info->num_bus_formats != 1)
+		return -EINVAL;
+
+	switch (info->bus_formats[0]) {
+	case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+		return 18;
+
+	case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+	case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+		return 24;
+	}
+
+	return -EINVAL;
+}
+
 static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel,
 					  bool enabled)
 {
@@ -65,13 +107,63 @@ static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel,
 		clk_disable_unprepare(clk);
 }
 
+static void sun4i_tcon_lvds_set_status(struct sun4i_tcon *tcon,
+				       const struct drm_encoder *encoder,
+				       bool enabled)
+{
+	if (enabled) {
+		u8 val;
+
+		regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG,
+				   SUN4I_TCON0_LVDS_IF_EN,
+				   SUN4I_TCON0_LVDS_IF_EN);
+
+		/*
+		 * As their name suggest, these values only apply to the A31
+		 * and later SoCs. We'll have to rework this when merging
+		 * support for the older SoCs.
+		 */
+		regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
+			     SUN6I_TCON0_LVDS_ANA0_C(2) |
+			     SUN6I_TCON0_LVDS_ANA0_V(3) |
+			     SUN6I_TCON0_LVDS_ANA0_PD(2) |
+			     SUN6I_TCON0_LVDS_ANA0_EN_LDO);
+		udelay(2);
+
+		regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
+				   SUN6I_TCON0_LVDS_ANA0_EN_MB,
+				   SUN6I_TCON0_LVDS_ANA0_EN_MB);
+		udelay(2);
+
+		regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
+				   SUN6I_TCON0_LVDS_ANA0_EN_DRVC,
+				   SUN6I_TCON0_LVDS_ANA0_EN_DRVC);
+
+		if (sun4i_tcon_get_pixel_depth(encoder) == 18)
+			val = 7;
+		else
+			val = 0xf;
+
+		regmap_write_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
+				  SUN6I_TCON0_LVDS_ANA0_EN_DRVD(0xf),
+				  SUN6I_TCON0_LVDS_ANA0_EN_DRVD(val));
+	} else {
+		regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG,
+				   SUN4I_TCON0_LVDS_IF_EN, 0);
+	}
+}
+
 void sun4i_tcon_set_status(struct sun4i_tcon *tcon,
 			   const struct drm_encoder *encoder,
 			   bool enabled)
 {
+	bool is_lvds = false;
 	int channel;
 
 	switch (encoder->encoder_type) {
+	case DRM_MODE_ENCODER_LVDS:
+		is_lvds = true;
+		/* Fallthrough */
 	case DRM_MODE_ENCODER_NONE:
 		channel = 0;
 		break;
@@ -84,10 +176,16 @@ void sun4i_tcon_set_status(struct sun4i_tcon *tcon,
 		return;
 	}
 
+	if (is_lvds && !enabled)
+		sun4i_tcon_lvds_set_status(tcon, encoder, false);
+
 	regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
 			   SUN4I_TCON_GCTL_TCON_ENABLE,
 			   enabled ? SUN4I_TCON_GCTL_TCON_ENABLE : 0);
 
+	if (is_lvds && enabled)
+		sun4i_tcon_lvds_set_status(tcon, encoder, true);
+
 	sun4i_tcon_channel_set_status(tcon, channel, enabled);
 }
 
@@ -170,6 +268,75 @@ static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon,
 		     SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay));
 }
 
+static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon,
+				      const struct drm_encoder *encoder,
+				      const struct drm_display_mode *mode)
+{
+	unsigned int bp;
+	u8 clk_delay;
+	u32 reg, val = 0;
+
+	tcon->dclk_min_div = 7;
+	tcon->dclk_max_div = 7;
+	sun4i_tcon0_mode_set_common(tcon, mode);
+
+	/* Adjust clock delay */
+	clk_delay = sun4i_tcon_get_clk_delay(mode, 0);
+	regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
+			   SUN4I_TCON0_CTL_CLK_DELAY_MASK,
+			   SUN4I_TCON0_CTL_CLK_DELAY(clk_delay));
+
+	/*
+	 * This is called a backporch in the register documentation,
+	 * but it really is the back porch + hsync
+	 */
+	bp = mode->crtc_htotal - mode->crtc_hsync_start;
+	DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n",
+			 mode->crtc_htotal, bp);
+
+	/* Set horizontal display timings */
+	regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG,
+		     SUN4I_TCON0_BASIC1_H_TOTAL(mode->htotal) |
+		     SUN4I_TCON0_BASIC1_H_BACKPORCH(bp));
+
+	/*
+	 * This is called a backporch in the register documentation,
+	 * but it really is the back porch + hsync
+	 */
+	bp = mode->crtc_vtotal - mode->crtc_vsync_start;
+	DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
+			 mode->crtc_vtotal, bp);
+
+	/* Set vertical display timings */
+	regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG,
+		     SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) |
+		     SUN4I_TCON0_BASIC2_V_BACKPORCH(bp));
+
+	reg = SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0 |
+		SUN4I_TCON0_LVDS_IF_DATA_POL_NORMAL |
+		SUN4I_TCON0_LVDS_IF_CLK_POL_NORMAL;
+	if (sun4i_tcon_get_pixel_depth(encoder) == 24)
+		reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_24BITS;
+	else
+		reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_18BITS;
+
+	regmap_write(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, reg);
+
+	/* Setup the polarity of the various signals */
+	if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
+		val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE;
+
+	if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
+		val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE;
+
+	regmap_write(tcon->regs, SUN4I_TCON0_IO_POL_REG, val);
+
+	/* Map output pins to channel 0 */
+	regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
+			   SUN4I_TCON_GCTL_IOMAP_MASK,
+			   SUN4I_TCON_GCTL_IOMAP_TCON0);
+}
+
 static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon,
 				     const struct drm_display_mode *mode)
 {
@@ -177,6 +344,8 @@ static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon,
 	u8 clk_delay;
 	u32 val = 0;
 
+	tcon->dclk_min_div = 6;
+	tcon->dclk_max_div = 127;
 	sun4i_tcon0_mode_set_common(tcon, mode);
 
 	/* Adjust clock delay */
@@ -334,6 +503,9 @@ void sun4i_tcon_mode_set(struct sun4i_tcon *tcon,
 			 const struct drm_display_mode *mode)
 {
 	switch (encoder->encoder_type) {
+	case DRM_MODE_ENCODER_LVDS:
+		sun4i_tcon0_mode_set_lvds(tcon, encoder, mode);
+		break;
 	case DRM_MODE_ENCODER_NONE:
 		sun4i_tcon0_mode_set_rgb(tcon, mode);
 		sun4i_tcon_set_mux(tcon, 0, encoder);
@@ -665,7 +837,9 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
 	struct drm_device *drm = data;
 	struct sun4i_drv *drv = drm->dev_private;
 	struct sunxi_engine *engine;
+	struct device_node *remote;
 	struct sun4i_tcon *tcon;
+	bool has_lvds_rst, has_lvds_alt, can_lvds;
 	int ret;
 
 	engine = sun4i_tcon_find_engine(drv, dev->of_node);
@@ -696,6 +870,54 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
 		return ret;
 	}
 
+	/*
+	 * This can only be made optional since we've had DT nodes
+	 * without the LVDS reset properties.
+	 *
+	 * If the property is missing, just disable LVDS, and print a
+	 * warning.
+	 */
+	tcon->lvds_rst = devm_reset_control_get_optional(dev, "lvds");
+	if (IS_ERR(tcon->lvds_rst)) {
+		dev_err(dev, "Couldn't get our reset line\n");
+		return PTR_ERR(tcon->lvds_rst);
+	} else if (tcon->lvds_rst) {
+		has_lvds_rst = true;
+		reset_control_reset(tcon->lvds_rst);
+	} else {
+		has_lvds_rst = false;
+	}
+
+	/*
+	 * This can only be made optional since we've had DT nodes
+	 * without the LVDS reset properties.
+	 *
+	 * If the property is missing, just disable LVDS, and print a
+	 * warning.
+	 */
+	if (tcon->quirks->has_lvds_alt) {
+		tcon->lvds_pll = devm_clk_get(dev, "lvds-alt");
+		if (IS_ERR(tcon->lvds_pll)) {
+			if (PTR_ERR(tcon->lvds_pll) == -ENOENT) {
+				has_lvds_alt = false;
+			} else {
+				dev_err(dev, "Couldn't get the LVDS PLL\n");
+				return PTR_ERR(tcon->lvds_rst);
+			}
+		} else {
+			has_lvds_alt = true;
+		}
+	}
+
+	if (!has_lvds_rst || (tcon->quirks->has_lvds_alt && !has_lvds_alt)) {
+		dev_warn(dev,
+			 "Missing LVDS properties, Please upgrade your DT\n");
+		dev_warn(dev, "LVDS output disabled\n");
+		can_lvds = false;
+	} else {
+		can_lvds = true;
+	}
+
 	ret = sun4i_tcon_init_clocks(dev, tcon);
 	if (ret) {
 		dev_err(dev, "Couldn't init our TCON clocks\n");
@@ -727,7 +949,21 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
 		goto err_free_clocks;
 	}
 
-	ret = sun4i_rgb_init(drm, tcon);
+	/*
+	 * If we have an LVDS panel connected to the TCON, we should
+	 * just probe the LVDS connector. Otherwise, just probe RGB as
+	 * we used to.
+	 */
+	remote = of_graph_get_remote_node(dev->of_node, 1, 0);
+	if (of_device_is_compatible(remote, "panel-lvds"))
+		if (can_lvds)
+			ret = sun4i_lvds_init(drm, tcon);
+		else
+			ret = -EINVAL;
+	else
+		ret = sun4i_rgb_init(drm, tcon);
+	of_node_put(remote);
+
 	if (ret < 0)
 		goto err_free_clocks;
 
@@ -877,6 +1113,7 @@ static const struct sun4i_tcon_quirks sun5i_a13_quirks = {
 
 static const struct sun4i_tcon_quirks sun6i_a31_quirks = {
 	.has_channel_1		= true,
+	.has_lvds_alt		= true,
 	.needs_de_be_mux	= true,
 	.set_mux		= sun6i_tcon_set_mux,
 };
@@ -893,6 +1130,10 @@ static const struct sun4i_tcon_quirks sun7i_a20_quirks = {
 };
 
 static const struct sun4i_tcon_quirks sun8i_a33_quirks = {
+	.has_lvds_alt		= true,
+};
+
+static const struct sun4i_tcon_quirks sun8i_a83t_lcd_quirks = {
 	/* nothing is supported */
 };
 
@@ -908,6 +1149,7 @@ const struct of_device_id sun4i_tcon_of_table[] = {
 	{ .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks },
 	{ .compatible = "allwinner,sun7i-a20-tcon", .data = &sun7i_a20_quirks },
 	{ .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks },
+	{ .compatible = "allwinner,sun8i-a83t-tcon-lcd", .data = &sun8i_a83t_lcd_quirks },
 	{ .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks },
 	{ }
 };

+ 31 - 0
drivers/gpu/drm/sun4i/sun4i_tcon.h

@@ -70,7 +70,21 @@
 #define SUN4I_TCON0_TTL2_REG			0x78
 #define SUN4I_TCON0_TTL3_REG			0x7c
 #define SUN4I_TCON0_TTL4_REG			0x80
+
 #define SUN4I_TCON0_LVDS_IF_REG			0x84
+#define SUN4I_TCON0_LVDS_IF_EN				BIT(31)
+#define SUN4I_TCON0_LVDS_IF_BITWIDTH_MASK		BIT(26)
+#define SUN4I_TCON0_LVDS_IF_BITWIDTH_18BITS		(1 << 26)
+#define SUN4I_TCON0_LVDS_IF_BITWIDTH_24BITS		(0 << 26)
+#define SUN4I_TCON0_LVDS_IF_CLK_SEL_MASK		BIT(20)
+#define SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0		(1 << 20)
+#define SUN4I_TCON0_LVDS_IF_CLK_POL_MASK		BIT(4)
+#define SUN4I_TCON0_LVDS_IF_CLK_POL_NORMAL		(1 << 4)
+#define SUN4I_TCON0_LVDS_IF_CLK_POL_INV			(0 << 4)
+#define SUN4I_TCON0_LVDS_IF_DATA_POL_MASK		GENMASK(3, 0)
+#define SUN4I_TCON0_LVDS_IF_DATA_POL_NORMAL		(0xf)
+#define SUN4I_TCON0_LVDS_IF_DATA_POL_INV		(0)
+
 #define SUN4I_TCON0_IO_POL_REG			0x88
 #define SUN4I_TCON0_IO_POL_DCLK_PHASE(phase)		((phase & 3) << 28)
 #define SUN4I_TCON0_IO_POL_HSYNC_POSITIVE		BIT(25)
@@ -131,6 +145,16 @@
 #define SUN4I_TCON_CEU_RANGE_G_REG		0x144
 #define SUN4I_TCON_CEU_RANGE_B_REG		0x148
 #define SUN4I_TCON_MUX_CTRL_REG			0x200
+
+#define SUN4I_TCON0_LVDS_ANA0_REG		0x220
+#define SUN6I_TCON0_LVDS_ANA0_EN_MB			BIT(31)
+#define SUN6I_TCON0_LVDS_ANA0_EN_LDO			BIT(30)
+#define SUN6I_TCON0_LVDS_ANA0_EN_DRVC			BIT(24)
+#define SUN6I_TCON0_LVDS_ANA0_EN_DRVD(x)		(((x) & 0xf) << 20)
+#define SUN6I_TCON0_LVDS_ANA0_C(x)			(((x) & 3) << 17)
+#define SUN6I_TCON0_LVDS_ANA0_V(x)			(((x) & 3) << 8)
+#define SUN6I_TCON0_LVDS_ANA0_PD(x)			(((x) & 3) << 4)
+
 #define SUN4I_TCON1_FILL_CTL_REG		0x300
 #define SUN4I_TCON1_FILL_BEG0_REG		0x304
 #define SUN4I_TCON1_FILL_END0_REG		0x308
@@ -149,6 +173,7 @@ struct sun4i_tcon;
 
 struct sun4i_tcon_quirks {
 	bool	has_channel_1;	/* a33 does not have channel 1 */
+	bool	has_lvds_alt;	/* Does the LVDS clock have a parent other than the TCON clock? */
 	bool	needs_de_be_mux; /* sun6i needs mux to select backend */
 
 	/* callback to handle tcon muxing options */
@@ -167,11 +192,17 @@ struct sun4i_tcon {
 	struct clk			*sclk0;
 	struct clk			*sclk1;
 
+	/* Possible mux for the LVDS clock */
+	struct clk			*lvds_pll;
+
 	/* Pixel clock */
 	struct clk			*dclk;
+	u8				dclk_max_div;
+	u8				dclk_min_div;
 
 	/* Reset control */
 	struct reset_control		*lcd_rst;
+	struct reset_control		*lvds_rst;
 
 	struct drm_panel		*panel;
 

+ 21 - 0
drivers/gpu/drm/sun4i/sun8i_mixer.c

@@ -398,6 +398,15 @@ static int sun8i_mixer_bind(struct device *dev, struct device *master,
 		ret = PTR_ERR(mixer->mod_clk);
 		goto err_disable_bus_clk;
 	}
+
+	/*
+	 * It seems that we need to enforce that rate for whatever
+	 * reason for the mixer to be functional. Make sure it's the
+	 * case.
+	 */
+	if (mixer->cfg->mod_rate)
+		clk_set_rate(mixer->mod_clk, mixer->cfg->mod_rate);
+
 	clk_prepare_enable(mixer->mod_clk);
 
 	list_add_tail(&mixer->engine.list, &drv->engine_list);
@@ -469,14 +478,26 @@ static int sun8i_mixer_remove(struct platform_device *pdev)
 	return 0;
 }
 
+static const struct sun8i_mixer_cfg sun8i_a83t_mixer0_cfg = {
+	.ccsc		= 0,
+	.scaler_mask	= 0xf,
+	.ui_num		= 3,
+	.vi_num		= 1,
+};
+
 static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = {
 	.vi_num = 2,
 	.ui_num = 1,
 	.scaler_mask = 0x3,
 	.ccsc = 0,
+	.mod_rate = 150000000,
 };
 
 static const struct of_device_id sun8i_mixer_of_table[] = {
+	{
+		.compatible = "allwinner,sun8i-a83t-de2-mixer-0",
+		.data = &sun8i_a83t_mixer0_cfg,
+	},
 	{
 		.compatible = "allwinner,sun8i-v3s-de2-mixer",
 		.data = &sun8i_v3s_mixer_cfg,

+ 3 - 0
drivers/gpu/drm/sun4i/sun8i_mixer.h

@@ -121,12 +121,15 @@ struct de2_fmt_info {
  *	Set value to 0 if this is first mixer or second mixer with VEP support.
  *	Set value to 1 if this is second mixer without VEP support. Other values
  *	are invalid.
+ * @mod_rate: module clock rate that needs to be set in order to have
+ *	a functional block.
  */
 struct sun8i_mixer_cfg {
 	int		vi_num;
 	int		ui_num;
 	int		scaler_mask;
 	int		ccsc;
+	unsigned long	mod_rate;
 };
 
 struct sun8i_mixer {

+ 10 - 0
drivers/gpu/drm/tinydrm/Kconfig

@@ -52,3 +52,13 @@ config TINYDRM_ST7586
 	  * LEGO MINDSTORMS EV3
 
 	  If M is selected the module will be called st7586.
+
+config TINYDRM_ST7735R
+	tristate "DRM support for Sitronix ST7735R display panels"
+	depends on DRM_TINYDRM && SPI
+	select TINYDRM_MIPI_DBI
+	help
+	  DRM driver Sitronix ST7735R with one of the following LCDs:
+	  * JD-T18003-T01 1.8" 128x160 TFT
+
+	  If M is selected the module will be called st7735r.

+ 1 - 0
drivers/gpu/drm/tinydrm/Makefile

@@ -8,3 +8,4 @@ obj-$(CONFIG_TINYDRM_ILI9225)		+= ili9225.o
 obj-$(CONFIG_TINYDRM_MI0283QT)		+= mi0283qt.o
 obj-$(CONFIG_TINYDRM_REPAPER)		+= repaper.o
 obj-$(CONFIG_TINYDRM_ST7586)		+= st7586.o
+obj-$(CONFIG_TINYDRM_ST7735R)		+= st7735r.o

+ 2 - 2
drivers/gpu/drm/tinydrm/ili9225.c

@@ -391,13 +391,13 @@ static struct drm_driver ili9225_driver = {
 };
 
 static const struct of_device_id ili9225_of_match[] = {
-	{ .compatible = "ilitek,ili9225-2.2in-176x220" },
+	{ .compatible = "vot,v220hf01a-t" },
 	{},
 };
 MODULE_DEVICE_TABLE(of, ili9225_of_match);
 
 static const struct spi_device_id ili9225_id[] = {
-	{ "ili9225-2.2in-176x220", 0 },
+	{ "v220hf01a-t", 0 },
 	{ },
 };
 MODULE_DEVICE_TABLE(spi, ili9225_id);

+ 215 - 0
drivers/gpu/drm/tinydrm/st7735r.c

@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * DRM driver for Sitronix ST7735R panels
+ *
+ * Copyright 2017 David Lechner <david@lechnology.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-buf.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/spi/spi.h>
+#include <video/mipi_display.h>
+
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/tinydrm/mipi-dbi.h>
+#include <drm/tinydrm/tinydrm-helpers.h>
+
+#define ST7735R_FRMCTR1		0xb1
+#define ST7735R_FRMCTR2		0xb2
+#define ST7735R_FRMCTR3		0xb3
+#define ST7735R_INVCTR		0xb4
+#define ST7735R_PWCTR1		0xc0
+#define ST7735R_PWCTR2		0xc1
+#define ST7735R_PWCTR3		0xc2
+#define ST7735R_PWCTR4		0xc3
+#define ST7735R_PWCTR5		0xc4
+#define ST7735R_VMCTR1		0xc5
+#define ST7735R_GAMCTRP1	0xe0
+#define ST7735R_GAMCTRN1	0xe1
+
+#define ST7735R_MY	BIT(7)
+#define ST7735R_MX	BIT(6)
+#define ST7735R_MV	BIT(5)
+
+static void jd_t18003_t01_pipe_enable(struct drm_simple_display_pipe *pipe,
+				      struct drm_crtc_state *crtc_state)
+{
+	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+	struct device *dev = tdev->drm->dev;
+	int ret;
+	u8 addr_mode;
+
+	DRM_DEBUG_KMS("\n");
+
+	mipi_dbi_hw_reset(mipi);
+
+	ret = mipi_dbi_command(mipi, MIPI_DCS_SOFT_RESET);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "Error sending command %d\n", ret);
+		return;
+	}
+
+	msleep(150);
+
+	mipi_dbi_command(mipi, MIPI_DCS_EXIT_SLEEP_MODE);
+	msleep(500);
+
+	mipi_dbi_command(mipi, ST7735R_FRMCTR1, 0x01, 0x2c, 0x2d);
+	mipi_dbi_command(mipi, ST7735R_FRMCTR2, 0x01, 0x2c, 0x2d);
+	mipi_dbi_command(mipi, ST7735R_FRMCTR3, 0x01, 0x2c, 0x2d, 0x01, 0x2c,
+			 0x2d);
+	mipi_dbi_command(mipi, ST7735R_INVCTR, 0x07);
+	mipi_dbi_command(mipi, ST7735R_PWCTR1, 0xa2, 0x02, 0x84);
+	mipi_dbi_command(mipi, ST7735R_PWCTR2, 0xc5);
+	mipi_dbi_command(mipi, ST7735R_PWCTR3, 0x0a, 0x00);
+	mipi_dbi_command(mipi, ST7735R_PWCTR4, 0x8a, 0x2a);
+	mipi_dbi_command(mipi, ST7735R_PWCTR5, 0x8a, 0xee);
+	mipi_dbi_command(mipi, ST7735R_VMCTR1, 0x0e);
+	mipi_dbi_command(mipi, MIPI_DCS_EXIT_INVERT_MODE);
+	switch (mipi->rotation) {
+	default:
+		addr_mode = ST7735R_MX | ST7735R_MY;
+		break;
+	case 90:
+		addr_mode = ST7735R_MX | ST7735R_MV;
+		break;
+	case 180:
+		addr_mode = 0;
+		break;
+	case 270:
+		addr_mode = ST7735R_MY | ST7735R_MV;
+		break;
+	}
+	mipi_dbi_command(mipi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
+	mipi_dbi_command(mipi, MIPI_DCS_SET_PIXEL_FORMAT,
+			 MIPI_DCS_PIXEL_FMT_16BIT);
+	mipi_dbi_command(mipi, ST7735R_GAMCTRP1, 0x02, 0x1c, 0x07, 0x12, 0x37,
+			 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2b, 0x39, 0x00, 0x01,
+			 0x03, 0x10);
+	mipi_dbi_command(mipi, ST7735R_GAMCTRN1, 0x03, 0x1d, 0x07, 0x06, 0x2e,
+			 0x2c, 0x29, 0x2d, 0x2e, 0x2e, 0x37, 0x3f, 0x00, 0x00,
+			 0x02, 0x10);
+	mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_ON);
+
+	msleep(100);
+
+	mipi_dbi_command(mipi, MIPI_DCS_ENTER_NORMAL_MODE);
+
+	msleep(20);
+
+	mipi_dbi_pipe_enable(pipe, crtc_state);
+}
+
+static const struct drm_simple_display_pipe_funcs jd_t18003_t01_pipe_funcs = {
+	.enable		= jd_t18003_t01_pipe_enable,
+	.disable	= mipi_dbi_pipe_disable,
+	.update		= tinydrm_display_pipe_update,
+	.prepare_fb	= tinydrm_display_pipe_prepare_fb,
+};
+
+static const struct drm_display_mode jd_t18003_t01_mode = {
+	TINYDRM_MODE(128, 160, 28, 35),
+};
+
+DEFINE_DRM_GEM_CMA_FOPS(st7735r_fops);
+
+static struct drm_driver st7735r_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
+				  DRIVER_ATOMIC,
+	.fops			= &st7735r_fops,
+	TINYDRM_GEM_DRIVER_OPS,
+	.lastclose		= drm_fb_helper_lastclose,
+	.debugfs_init		= mipi_dbi_debugfs_init,
+	.name			= "st7735r",
+	.desc			= "Sitronix ST7735R",
+	.date			= "20171128",
+	.major			= 1,
+	.minor			= 0,
+};
+
+static const struct of_device_id st7735r_of_match[] = {
+	{ .compatible = "jianda,jd-t18003-t01" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, st7735r_of_match);
+
+static const struct spi_device_id st7735r_id[] = {
+	{ "jd-t18003-t01", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(spi, st7735r_id);
+
+static int st7735r_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct mipi_dbi *mipi;
+	struct gpio_desc *dc;
+	u32 rotation = 0;
+	int ret;
+
+	mipi = devm_kzalloc(dev, sizeof(*mipi), GFP_KERNEL);
+	if (!mipi)
+		return -ENOMEM;
+
+	mipi->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(mipi->reset)) {
+		DRM_DEV_ERROR(dev, "Failed to get gpio 'reset'\n");
+		return PTR_ERR(mipi->reset);
+	}
+
+	dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW);
+	if (IS_ERR(dc)) {
+		DRM_DEV_ERROR(dev, "Failed to get gpio 'dc'\n");
+		return PTR_ERR(dc);
+	}
+
+	mipi->backlight = tinydrm_of_find_backlight(dev);
+	if (IS_ERR(mipi->backlight))
+		return PTR_ERR(mipi->backlight);
+
+	device_property_read_u32(dev, "rotation", &rotation);
+
+	ret = mipi_dbi_spi_init(spi, mipi, dc);
+	if (ret)
+		return ret;
+
+	/* Cannot read from Adafruit 1.8" display via SPI */
+	mipi->read_commands = NULL;
+
+	ret = mipi_dbi_init(&spi->dev, mipi, &jd_t18003_t01_pipe_funcs,
+			    &st7735r_driver, &jd_t18003_t01_mode, rotation);
+	if (ret)
+		return ret;
+
+	spi_set_drvdata(spi, mipi);
+
+	return devm_tinydrm_register(&mipi->tinydrm);
+}
+
+static void st7735r_shutdown(struct spi_device *spi)
+{
+	struct mipi_dbi *mipi = spi_get_drvdata(spi);
+
+	tinydrm_shutdown(&mipi->tinydrm);
+}
+
+static struct spi_driver st7735r_spi_driver = {
+	.driver = {
+		.name = "st7735r",
+		.owner = THIS_MODULE,
+		.of_match_table = st7735r_of_match,
+	},
+	.id_table = st7735r_id,
+	.probe = st7735r_probe,
+	.shutdown = st7735r_shutdown,
+};
+module_spi_driver(st7735r_spi_driver);
+
+MODULE_DESCRIPTION("Sitronix ST7735R DRM driver");
+MODULE_AUTHOR("David Lechner <david@lechnology.com>");
+MODULE_LICENSE("GPL");