diff options
27 files changed, 1838 insertions, 9 deletions
diff --git a/Documentation/devicetree/bindings/display/ilitek,ili9225.txt b/Documentation/devicetree/bindings/display/ilitek,ili9225.txt index 21607a541c33..a59feb52015b 100644 --- a/Documentation/devicetree/bindings/display/ilitek,ili9225.txt +++ b/Documentation/devicetree/bindings/display/ilitek,ili9225.txt | |||
@@ -4,7 +4,7 @@ This binding is for display panels using an Ilitek ILI9225 controller in SPI | |||
4 | mode. | 4 | mode. |
5 | 5 | ||
6 | Required properties: | 6 | Required properties: |
7 | - compatible: "ilitek,ili9225-2.2in-176x220" | 7 | - compatible: "vot,v220hf01a-t", "ilitek,ili9225" |
8 | - rs-gpios: Register select signal | 8 | - rs-gpios: Register select signal |
9 | - reset-gpios: Reset pin | 9 | - reset-gpios: Reset pin |
10 | 10 | ||
@@ -16,7 +16,7 @@ Optional properties: | |||
16 | 16 | ||
17 | Example: | 17 | Example: |
18 | display@0{ | 18 | display@0{ |
19 | compatible = "ilitek,ili9225-2.2in-176x220"; | 19 | compatible = "vot,v220hf01a-t", "ilitek,ili9225"; |
20 | reg = <0>; | 20 | reg = <0>; |
21 | spi-max-frequency = <12000000>; | 21 | spi-max-frequency = <12000000>; |
22 | rs-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>; | 22 | rs-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>; |
diff --git a/Documentation/devicetree/bindings/display/panel/ilitek,ili9322.txt b/Documentation/devicetree/bindings/display/panel/ilitek,ili9322.txt new file mode 100644 index 000000000000..3d5ce6ad6ec7 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/ilitek,ili9322.txt | |||
@@ -0,0 +1,49 @@ | |||
1 | Ilitek ILI9322 TFT panel driver with SPI control bus | ||
2 | |||
3 | This is a driver for 320x240 TFT panels, accepting a variety of input | ||
4 | streams that get adapted and scaled to the panel. The panel output has | ||
5 | 960 TFT source driver pins and 240 TFT gate driver pins, VCOM, VCOML and | ||
6 | VCOMH outputs. | ||
7 | |||
8 | Required properties: | ||
9 | - compatible: "dlink,dir-685-panel", "ilitek,ili9322" | ||
10 | (full system-specific compatible is always required to look up configuration) | ||
11 | - reg: address of the panel on the SPI bus | ||
12 | |||
13 | Optional properties: | ||
14 | - vcc-supply: core voltage supply, see regulator/regulator.txt | ||
15 | - iovcc-supply: voltage supply for the interface input/output signals, | ||
16 | see regulator/regulator.txt | ||
17 | - vci-supply: voltage supply for analog parts, see regulator/regulator.txt | ||
18 | - reset-gpios: a GPIO spec for the reset pin, see gpio/gpio.txt | ||
19 | |||
20 | The following optional properties only apply to RGB and YUV input modes and | ||
21 | can be omitted for BT.656 input modes: | ||
22 | |||
23 | - pixelclk-active: see display/panel/display-timing.txt | ||
24 | - de-active: see display/panel/display-timing.txt | ||
25 | - hsync-active: see display/panel/display-timing.txt | ||
26 | - vsync-active: see display/panel/display-timing.txt | ||
27 | |||
28 | The panel must obey the rules for a SPI slave device as specified in | ||
29 | spi/spi-bus.txt | ||
30 | |||
31 | The device node can contain one 'port' child node with one child | ||
32 | 'endpoint' node, according to the bindings defined in | ||
33 | media/video-interfaces.txt. This node should describe panel's video bus. | ||
34 | |||
35 | Example: | ||
36 | |||
37 | panel: display@0 { | ||
38 | compatible = "dlink,dir-685-panel", "ilitek,ili9322"; | ||
39 | reg = <0>; | ||
40 | vcc-supply = <&vdisp>; | ||
41 | iovcc-supply = <&vdisp>; | ||
42 | vci-supply = <&vdisp>; | ||
43 | |||
44 | port { | ||
45 | panel_in: endpoint { | ||
46 | remote-endpoint = <&display_out>; | ||
47 | }; | ||
48 | }; | ||
49 | }; | ||
diff --git a/Documentation/devicetree/bindings/display/panel/panel-common.txt b/Documentation/devicetree/bindings/display/panel/panel-common.txt index ec52c472c845..557fa765adcb 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-common.txt +++ b/Documentation/devicetree/bindings/display/panel/panel-common.txt | |||
@@ -78,6 +78,16 @@ used for panels that implement compatible control signals. | |||
78 | while active. Active high reset signals can be supported by inverting the | 78 | while active. Active high reset signals can be supported by inverting the |
79 | GPIO specifier polarity flag. | 79 | GPIO specifier polarity flag. |
80 | 80 | ||
81 | Power | ||
82 | ----- | ||
83 | |||
84 | - power-supply: display panels require power to be supplied. While several | ||
85 | panels need more than one power supply with panel-specific constraints | ||
86 | governing the order and timings of the power supplies, in many cases a single | ||
87 | power supply is sufficient, either because the panel has a single power rail, | ||
88 | or because all its power rails can be driven by the same supply. In that case | ||
89 | the power-supply property specifies the supply powering the panel as a phandle | ||
90 | to a regulator. | ||
81 | 91 | ||
82 | Backlight | 92 | Backlight |
83 | --------- | 93 | --------- |
diff --git a/Documentation/devicetree/bindings/display/panel/panel-lvds.txt b/Documentation/devicetree/bindings/display/panel/panel-lvds.txt index b938269f841e..250850a2150b 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-lvds.txt +++ b/Documentation/devicetree/bindings/display/panel/panel-lvds.txt | |||
@@ -32,6 +32,7 @@ Optional properties: | |||
32 | - label: See panel-common.txt. | 32 | - label: See panel-common.txt. |
33 | - gpios: See panel-common.txt. | 33 | - gpios: See panel-common.txt. |
34 | - backlight: See panel-common.txt. | 34 | - backlight: See panel-common.txt. |
35 | - power-supply: See panel-common.txt. | ||
35 | - data-mirror: If set, reverse the bit order described in the data mappings | 36 | - data-mirror: If set, reverse the bit order described in the data mappings |
36 | below on all data lanes, transmitting bits for slots 6 to 0 instead of | 37 | below on all data lanes, transmitting bits for slots 6 to 0 instead of |
37 | 0 to 6. | 38 | 0 to 6. |
diff --git a/Documentation/devicetree/bindings/display/panel/simple-panel.txt b/Documentation/devicetree/bindings/display/panel/simple-panel.txt index 1341bbf4aa3d..16d8ff088b7d 100644 --- a/Documentation/devicetree/bindings/display/panel/simple-panel.txt +++ b/Documentation/devicetree/bindings/display/panel/simple-panel.txt | |||
@@ -1,7 +1,7 @@ | |||
1 | Simple display panel | 1 | Simple display panel |
2 | 2 | ||
3 | Required properties: | 3 | Required properties: |
4 | - power-supply: regulator to provide the supply voltage | 4 | - power-supply: See panel-common.txt |
5 | 5 | ||
6 | Optional properties: | 6 | Optional properties: |
7 | - ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing | 7 | - ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing |
diff --git a/Documentation/devicetree/bindings/display/sitronix,st7735r.txt b/Documentation/devicetree/bindings/display/sitronix,st7735r.txt new file mode 100644 index 000000000000..f0a5090a3326 --- /dev/null +++ b/Documentation/devicetree/bindings/display/sitronix,st7735r.txt | |||
@@ -0,0 +1,35 @@ | |||
1 | Sitronix ST7735R display panels | ||
2 | |||
3 | This binding is for display panels using a Sitronix ST7735R controller in SPI | ||
4 | mode. | ||
5 | |||
6 | Required properties: | ||
7 | - compatible: "jianda,jd-t18003-t01", "sitronix,st7735r" | ||
8 | - dc-gpios: Display data/command selection (D/CX) | ||
9 | - reset-gpios: Reset signal (RSTX) | ||
10 | |||
11 | The node for this driver must be a child node of a SPI controller, hence | ||
12 | all mandatory properties described in ../spi/spi-bus.txt must be specified. | ||
13 | |||
14 | Optional properties: | ||
15 | - rotation: panel rotation in degrees counter clockwise (0,90,180,270) | ||
16 | - backlight: phandle of the backlight device attached to the panel | ||
17 | |||
18 | Example: | ||
19 | |||
20 | backlight: backlight { | ||
21 | compatible = "gpio-backlight"; | ||
22 | gpios = <&gpio 44 GPIO_ACTIVE_HIGH>; | ||
23 | } | ||
24 | |||
25 | ... | ||
26 | |||
27 | display@0{ | ||
28 | compatible = "jianda,jd-t18003-t01", "sitronix,st7735r"; | ||
29 | reg = <0>; | ||
30 | spi-max-frequency = <32000000>; | ||
31 | dc-gpios = <&gpio 43 GPIO_ACTIVE_HIGH>; | ||
32 | reset-gpios = <&gpio 80 GPIO_ACTIVE_HIGH>; | ||
33 | rotation = <270>; | ||
34 | backlight = &backlight; | ||
35 | }; | ||
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt index 50cc72ee1168..cd626ee1147a 100644 --- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | |||
@@ -93,6 +93,7 @@ Required properties: | |||
93 | * allwinner,sun6i-a31s-tcon | 93 | * allwinner,sun6i-a31s-tcon |
94 | * allwinner,sun7i-a20-tcon | 94 | * allwinner,sun7i-a20-tcon |
95 | * allwinner,sun8i-a33-tcon | 95 | * allwinner,sun8i-a33-tcon |
96 | * allwinner,sun8i-a83t-tcon-lcd | ||
96 | * allwinner,sun8i-v3s-tcon | 97 | * allwinner,sun8i-v3s-tcon |
97 | - reg: base address and size of memory-mapped region | 98 | - reg: base address and size of memory-mapped region |
98 | - interrupts: interrupt associated to this IP | 99 | - interrupts: interrupt associated to this IP |
@@ -121,6 +122,14 @@ Required properties: | |||
121 | On SoCs other than the A33 and V3s, there is one more clock required: | 122 | On SoCs other than the A33 and V3s, there is one more clock required: |
122 | - 'tcon-ch1': The clock driving the TCON channel 1 | 123 | - 'tcon-ch1': The clock driving the TCON channel 1 |
123 | 124 | ||
125 | On SoCs that support LVDS (all SoCs but the A13, H3, H5 and V3s), you | ||
126 | need one more reset line: | ||
127 | - 'lvds': The reset line driving the LVDS logic | ||
128 | |||
129 | And on the A23, A31, A31s and A33, you need one more clock line: | ||
130 | - 'lvds-alt': An alternative clock source, separate from the TCON channel 0 | ||
131 | clock, that can be used to drive the LVDS clock | ||
132 | |||
124 | DRC | 133 | DRC |
125 | --- | 134 | --- |
126 | 135 | ||
@@ -216,6 +225,7 @@ supported. | |||
216 | 225 | ||
217 | Required properties: | 226 | Required properties: |
218 | - compatible: value must be one of: | 227 | - compatible: value must be one of: |
228 | * allwinner,sun8i-a83t-de2-mixer-0 | ||
219 | * allwinner,sun8i-v3s-de2-mixer | 229 | * allwinner,sun8i-v3s-de2-mixer |
220 | - reg: base address and size of the memory-mapped region. | 230 | - reg: base address and size of the memory-mapped region. |
221 | - clocks: phandles to the clocks feeding the mixer | 231 | - clocks: phandles to the clocks feeding the mixer |
@@ -245,6 +255,7 @@ Required properties: | |||
245 | * allwinner,sun6i-a31s-display-engine | 255 | * allwinner,sun6i-a31s-display-engine |
246 | * allwinner,sun7i-a20-display-engine | 256 | * allwinner,sun7i-a20-display-engine |
247 | * allwinner,sun8i-a33-display-engine | 257 | * allwinner,sun8i-a33-display-engine |
258 | * allwinner,sun8i-a83t-display-engine | ||
248 | * allwinner,sun8i-v3s-display-engine | 259 | * allwinner,sun8i-v3s-display-engine |
249 | 260 | ||
250 | - allwinner,pipelines: list of phandle to the display engine | 261 | - allwinner,pipelines: list of phandle to the display engine |
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 41cb1ff07150..159dc5c075c2 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt | |||
@@ -173,6 +173,7 @@ itead ITEAD Intelligent Systems Co.Ltd | |||
173 | iwave iWave Systems Technologies Pvt. Ltd. | 173 | iwave iWave Systems Technologies Pvt. Ltd. |
174 | jdi Japan Display Inc. | 174 | jdi Japan Display Inc. |
175 | jedec JEDEC Solid State Technology Association | 175 | jedec JEDEC Solid State Technology Association |
176 | jianda Jiandangjing Technology Co., Ltd. | ||
176 | karo Ka-Ro electronics GmbH | 177 | karo Ka-Ro electronics GmbH |
177 | keithkoep Keith & Koep GmbH | 178 | keithkoep Keith & Koep GmbH |
178 | keymile Keymile GmbH | 179 | keymile Keymile GmbH |
@@ -380,6 +381,7 @@ virtio Virtual I/O Device Specification, developed by the OASIS consortium | |||
380 | vivante Vivante Corporation | 381 | vivante Vivante Corporation |
381 | vocore VoCore Studio | 382 | vocore VoCore Studio |
382 | voipac Voipac Technologies s.r.o. | 383 | voipac Voipac Technologies s.r.o. |
384 | vot Vision Optical Technology Co., Ltd. | ||
383 | wd Western Digital Corp. | 385 | wd Western Digital Corp. |
384 | wetek WeTek Electronics, limited. | 386 | wetek WeTek Electronics, limited. |
385 | wexler Wexler | 387 | wexler Wexler |
diff --git a/MAINTAINERS b/MAINTAINERS index d4b1635ba1f3..40aea858c7ea 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -4554,6 +4554,12 @@ S: Maintained | |||
4554 | F: drivers/gpu/drm/tinydrm/st7586.c | 4554 | F: drivers/gpu/drm/tinydrm/st7586.c |
4555 | F: Documentation/devicetree/bindings/display/st7586.txt | 4555 | F: Documentation/devicetree/bindings/display/st7586.txt |
4556 | 4556 | ||
4557 | DRM DRIVER FOR SITRONIX ST7735R PANELS | ||
4558 | M: David Lechner <david@lechnology.com> | ||
4559 | S: Maintained | ||
4560 | F: drivers/gpu/drm/tinydrm/st7735r.c | ||
4561 | F: Documentation/devicetree/bindings/display/st7735r.txt | ||
4562 | |||
4557 | DRM DRIVER FOR TDFX VIDEO CARDS | 4563 | DRM DRIVER FOR TDFX VIDEO CARDS |
4558 | S: Orphan / Obsolete | 4564 | S: Orphan / Obsolete |
4559 | F: drivers/gpu/drm/tdfx/ | 4565 | F: drivers/gpu/drm/tdfx/ |
diff --git a/drivers/gpu/drm/drm_panel_orientation_quirks.c b/drivers/gpu/drm/drm_panel_orientation_quirks.c index 901a4e9a87a3..1f2af707ce03 100644 --- a/drivers/gpu/drm/drm_panel_orientation_quirks.c +++ b/drivers/gpu/drm/drm_panel_orientation_quirks.c | |||
@@ -9,6 +9,7 @@ | |||
9 | */ | 9 | */ |
10 | 10 | ||
11 | #include <linux/dmi.h> | 11 | #include <linux/dmi.h> |
12 | #include <linux/module.h> | ||
12 | #include <drm/drm_connector.h> | 13 | #include <drm/drm_connector.h> |
13 | 14 | ||
14 | #ifdef CONFIG_DMI | 15 | #ifdef CONFIG_DMI |
@@ -172,3 +173,5 @@ int drm_get_panel_orientation_quirk(int width, int height) | |||
172 | EXPORT_SYMBOL(drm_get_panel_orientation_quirk); | 173 | EXPORT_SYMBOL(drm_get_panel_orientation_quirk); |
173 | 174 | ||
174 | #endif | 175 | #endif |
176 | |||
177 | MODULE_LICENSE("Dual MIT/GPL"); | ||
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 726f3fb3312d..6ba4031f3919 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig | |||
@@ -28,6 +28,14 @@ config DRM_PANEL_SIMPLE | |||
28 | that it can be automatically turned off when the panel goes into a | 28 | that it can be automatically turned off when the panel goes into a |
29 | low power state. | 29 | low power state. |
30 | 30 | ||
31 | config DRM_PANEL_ILITEK_IL9322 | ||
32 | tristate "Ilitek ILI9322 320x240 QVGA panels" | ||
33 | depends on OF && SPI | ||
34 | select REGMAP | ||
35 | help | ||
36 | Say Y here if you want to enable support for Ilitek IL9322 | ||
37 | QVGA (320x240) RGB, YUV and ITU-T BT.656 panels. | ||
38 | |||
31 | config DRM_PANEL_INNOLUX_P079ZCA | 39 | config DRM_PANEL_INNOLUX_P079ZCA |
32 | tristate "Innolux P079ZCA panel" | 40 | tristate "Innolux P079ZCA panel" |
33 | depends on OF | 41 | depends on OF |
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 2c4e1a93e05f..6d251ebc568c 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile | |||
@@ -1,6 +1,7 @@ | |||
1 | # SPDX-License-Identifier: GPL-2.0 | 1 | # SPDX-License-Identifier: GPL-2.0 |
2 | obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o | 2 | obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o |
3 | obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o | 3 | obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o |
4 | obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o | ||
4 | obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o | 5 | obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o |
5 | obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o | 6 | obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o |
6 | obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o | 7 | obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o |
diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9322.c b/drivers/gpu/drm/panel/panel-ilitek-ili9322.c new file mode 100644 index 000000000000..b4ec0ecff807 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9322.c | |||
@@ -0,0 +1,962 @@ | |||
1 | /* | ||
2 | * Ilitek ILI9322 TFT LCD drm_panel driver. | ||
3 | * | ||
4 | * This panel can be configured to support: | ||
5 | * - 8-bit serial RGB interface | ||
6 | * - 24-bit parallel RGB interface | ||
7 | * - 8-bit ITU-R BT.601 interface | ||
8 | * - 8-bit ITU-R BT.656 interface | ||
9 | * - Up to 320RGBx240 dots resolution TFT LCD displays | ||
10 | * - Scaling, brightness and contrast | ||
11 | * | ||
12 | * The scaling means that the display accepts a 640x480 or 720x480 | ||
13 | * input and rescales it to fit to the 320x240 display. So what we | ||
14 | * present to the system is something else than what comes out on the | ||
15 | * actual display. | ||
16 | * | ||
17 | * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> | ||
18 | * Derived from drivers/drm/gpu/panel/panel-samsung-ld9040.c | ||
19 | * | ||
20 | * This program is free software; you can redistribute it and/or modify | ||
21 | * it under the terms of the GNU General Public License version 2 as | ||
22 | * published by the Free Software Foundation. | ||
23 | */ | ||
24 | |||
25 | #include <drm/drmP.h> | ||
26 | #include <drm/drm_panel.h> | ||
27 | |||
28 | #include <linux/of_device.h> | ||
29 | #include <linux/bitops.h> | ||
30 | #include <linux/gpio/consumer.h> | ||
31 | #include <linux/module.h> | ||
32 | #include <linux/regmap.h> | ||
33 | #include <linux/regulator/consumer.h> | ||
34 | #include <linux/spi/spi.h> | ||
35 | |||
36 | #include <video/mipi_display.h> | ||
37 | #include <video/of_videomode.h> | ||
38 | #include <video/videomode.h> | ||
39 | |||
40 | #define ILI9322_CHIP_ID 0x00 | ||
41 | #define ILI9322_CHIP_ID_MAGIC 0x96 | ||
42 | |||
43 | /* | ||
44 | * Voltage on the communication interface, from 0.7 (0x00) | ||
45 | * to 1.32 (0x1f) times the VREG1OUT voltage in 2% increments. | ||
46 | * 1.00 (0x0f) is the default. | ||
47 | */ | ||
48 | #define ILI9322_VCOM_AMP 0x01 | ||
49 | |||
50 | /* | ||
51 | * High voltage on the communication signals, from 0.37 (0x00) to | ||
52 | * 1.0 (0x3f) times the VREGOUT1 voltage in 1% increments. | ||
53 | * 0.83 (0x2e) is the default. | ||
54 | */ | ||
55 | #define ILI9322_VCOM_HIGH 0x02 | ||
56 | |||
57 | /* | ||
58 | * VREG1 voltage regulator from 3.6V (0x00) to 6.0V (0x18) in 0.1V | ||
59 | * increments. 5.4V (0x12) is the default. This is the reference | ||
60 | * voltage for the VCOM levels and the greyscale level. | ||
61 | */ | ||
62 | #define ILI9322_VREG1_VOLTAGE 0x03 | ||
63 | |||
64 | /* Describes the incoming signal */ | ||
65 | #define ILI9322_ENTRY 0x06 | ||
66 | /* 0 = right-to-left, 1 = left-to-right (default), horizontal flip */ | ||
67 | #define ILI9322_ENTRY_HDIR BIT(0) | ||
68 | /* 0 = down-to-up, 1 = up-to-down (default), vertical flip */ | ||
69 | #define ILI9322_ENTRY_VDIR BIT(1) | ||
70 | /* NTSC, PAL or autodetect */ | ||
71 | #define ILI9322_ENTRY_NTSC (0 << 2) | ||
72 | #define ILI9322_ENTRY_PAL (1 << 2) | ||
73 | #define ILI9322_ENTRY_AUTODETECT (3 << 2) | ||
74 | /* Input format */ | ||
75 | #define ILI9322_ENTRY_SERIAL_RGB_THROUGH (0 << 4) | ||
76 | #define ILI9322_ENTRY_SERIAL_RGB_ALIGNED (1 << 4) | ||
77 | #define ILI9322_ENTRY_SERIAL_RGB_DUMMY_320X240 (2 << 4) | ||
78 | #define ILI9322_ENTRY_SERIAL_RGB_DUMMY_360X240 (3 << 4) | ||
79 | #define ILI9322_ENTRY_DISABLE_1 (4 << 4) | ||
80 | #define ILI9322_ENTRY_PARALLEL_RGB_THROUGH (5 << 4) | ||
81 | #define ILI9322_ENTRY_PARALLEL_RGB_ALIGNED (6 << 4) | ||
82 | #define ILI9322_ENTRY_YUV_640Y_320CBCR_25_54_MHZ (7 << 4) | ||
83 | #define ILI9322_ENTRY_YUV_720Y_360CBCR_27_MHZ (8 << 4) | ||
84 | #define ILI9322_ENTRY_DISABLE_2 (9 << 4) | ||
85 | #define ILI9322_ENTRY_ITU_R_BT_656_720X360 (10 << 4) | ||
86 | #define ILI9322_ENTRY_ITU_R_BT_656_640X320 (11 << 4) | ||
87 | |||
88 | /* Power control */ | ||
89 | #define ILI9322_POW_CTRL 0x07 | ||
90 | #define ILI9322_POW_CTRL_STB BIT(0) /* 0 = standby, 1 = normal */ | ||
91 | #define ILI9322_POW_CTRL_VGL BIT(1) /* 0 = off, 1 = on */ | ||
92 | #define ILI9322_POW_CTRL_VGH BIT(2) /* 0 = off, 1 = on */ | ||
93 | #define ILI9322_POW_CTRL_DDVDH BIT(3) /* 0 = off, 1 = on */ | ||
94 | #define ILI9322_POW_CTRL_VCOM BIT(4) /* 0 = off, 1 = on */ | ||
95 | #define ILI9322_POW_CTRL_VCL BIT(5) /* 0 = off, 1 = on */ | ||
96 | #define ILI9322_POW_CTRL_AUTO BIT(6) /* 0 = interactive, 1 = auto */ | ||
97 | #define ILI9322_POW_CTRL_STANDBY (ILI9322_POW_CTRL_VGL | \ | ||
98 | ILI9322_POW_CTRL_VGH | \ | ||
99 | ILI9322_POW_CTRL_DDVDH | \ | ||
100 | ILI9322_POW_CTRL_VCL | \ | ||
101 | ILI9322_POW_CTRL_AUTO | \ | ||
102 | BIT(7)) | ||
103 | #define ILI9322_POW_CTRL_DEFAULT (ILI9322_POW_CTRL_STANDBY | \ | ||
104 | ILI9322_POW_CTRL_STB) | ||
105 | |||
106 | /* Vertical back porch bits 0..5 */ | ||
107 | #define ILI9322_VBP 0x08 | ||
108 | |||
109 | /* Horizontal back porch, 8 bits */ | ||
110 | #define ILI9322_HBP 0x09 | ||
111 | |||
112 | /* | ||
113 | * Polarity settings: | ||
114 | * 1 = positive polarity | ||
115 | * 0 = negative polarity | ||
116 | */ | ||
117 | #define ILI9322_POL 0x0a | ||
118 | #define ILI9322_POL_DCLK BIT(0) /* 1 default */ | ||
119 | #define ILI9322_POL_HSYNC BIT(1) /* 0 default */ | ||
120 | #define ILI9322_POL_VSYNC BIT(2) /* 0 default */ | ||
121 | #define ILI9322_POL_DE BIT(3) /* 1 default */ | ||
122 | /* | ||
123 | * 0 means YCBCR are ordered Cb0,Y0,Cr0,Y1,Cb2,Y2,Cr2,Y3 (default) | ||
124 | * in RGB mode this means RGB comes in RGBRGB | ||
125 | * 1 means YCBCR are ordered Cr0,Y0,Cb0,Y1,Cr2,Y2,Cb2,Y3 | ||
126 | * in RGB mode this means RGB comes in BGRBGR | ||
127 | */ | ||
128 | #define ILI9322_POL_YCBCR_MODE BIT(4) | ||
129 | /* Formula A for YCbCR->RGB = 0, Formula B = 1 */ | ||
130 | #define ILI9322_POL_FORMULA BIT(5) | ||
131 | /* Reverse polarity: 0 = 0..255, 1 = 255..0 */ | ||
132 | #define ILI9322_POL_REV BIT(6) | ||
133 | |||
134 | #define ILI9322_IF_CTRL 0x0b | ||
135 | #define ILI9322_IF_CTRL_HSYNC_VSYNC 0x00 | ||
136 | #define ILI9322_IF_CTRL_HSYNC_VSYNC_DE BIT(2) | ||
137 | #define ILI9322_IF_CTRL_DE_ONLY BIT(3) | ||
138 | #define ILI9322_IF_CTRL_SYNC_DISABLED (BIT(2) | BIT(3)) | ||
139 | #define ILI9322_IF_CTRL_LINE_INVERSION BIT(0) /* Not set means frame inv */ | ||
140 | |||
141 | #define ILI9322_GLOBAL_RESET 0x04 | ||
142 | #define ILI9322_GLOBAL_RESET_ASSERT 0x00 /* bit 0 = 0 -> reset */ | ||
143 | |||
144 | /* | ||
145 | * 4+4 bits of negative and positive gamma correction | ||
146 | * Upper nybble, bits 4-7 are negative gamma | ||
147 | * Lower nybble, bits 0-3 are positive gamma | ||
148 | */ | ||
149 | #define ILI9322_GAMMA_1 0x10 | ||
150 | #define ILI9322_GAMMA_2 0x11 | ||
151 | #define ILI9322_GAMMA_3 0x12 | ||
152 | #define ILI9322_GAMMA_4 0x13 | ||
153 | #define ILI9322_GAMMA_5 0x14 | ||
154 | #define ILI9322_GAMMA_6 0x15 | ||
155 | #define ILI9322_GAMMA_7 0x16 | ||
156 | #define ILI9322_GAMMA_8 0x17 | ||
157 | |||
158 | /** | ||
159 | * enum ili9322_input - the format of the incoming signal to the panel | ||
160 | * | ||
161 | * The panel can be connected to various input streams and four of them can | ||
162 | * be selected by electronic straps on the display. However it is possible | ||
163 | * to select another mode or override the electronic default with this | ||
164 | * setting. | ||
165 | */ | ||
166 | enum ili9322_input { | ||
167 | ILI9322_INPUT_SRGB_THROUGH = 0x0, | ||
168 | ILI9322_INPUT_SRGB_ALIGNED = 0x1, | ||
169 | ILI9322_INPUT_SRGB_DUMMY_320X240 = 0x2, | ||
170 | ILI9322_INPUT_SRGB_DUMMY_360X240 = 0x3, | ||
171 | ILI9322_INPUT_DISABLED_1 = 0x4, | ||
172 | ILI9322_INPUT_PRGB_THROUGH = 0x5, | ||
173 | ILI9322_INPUT_PRGB_ALIGNED = 0x6, | ||
174 | ILI9322_INPUT_YUV_640X320_YCBCR = 0x7, | ||
175 | ILI9322_INPUT_YUV_720X360_YCBCR = 0x8, | ||
176 | ILI9322_INPUT_DISABLED_2 = 0x9, | ||
177 | ILI9322_INPUT_ITU_R_BT656_720X360_YCBCR = 0xa, | ||
178 | ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR = 0xb, | ||
179 | ILI9322_INPUT_UNKNOWN = 0xc, | ||
180 | }; | ||
181 | |||
182 | const char *ili9322_inputs[] = { | ||
183 | "8 bit serial RGB through", | ||
184 | "8 bit serial RGB aligned", | ||
185 | "8 bit serial RGB dummy 320x240", | ||
186 | "8 bit serial RGB dummy 360x240", | ||
187 | "disabled 1", | ||
188 | "24 bit parallel RGB through", | ||
189 | "24 bit parallel RGB aligned", | ||
190 | "24 bit YUV 640Y 320CbCr", | ||
191 | "24 bit YUV 720Y 360CbCr", | ||
192 | "disabled 2", | ||
193 | "8 bit ITU-R BT.656 720Y 360CbCr", | ||
194 | "8 bit ITU-R BT.656 640Y 320CbCr", | ||
195 | }; | ||
196 | |||
197 | /** | ||
198 | * struct ili9322_config - the system specific ILI9322 configuration | ||
199 | * @width_mm: physical panel width [mm] | ||
200 | * @height_mm: physical panel height [mm] | ||
201 | * @flip_horizontal: flip the image horizontally (right-to-left scan) | ||
202 | * (only in RGB and YUV modes) | ||
203 | * @flip_vertical: flip the image vertically (down-to-up scan) | ||
204 | * (only in RGB and YUV modes) | ||
205 | * @input: the input/entry type used in this system, if this is set to | ||
206 | * ILI9322_INPUT_UNKNOWN the driver will try to figure it out by probing | ||
207 | * the hardware | ||
208 | * @vreg1out_mv: the output in microvolts for the VREGOUT1 regulator used | ||
209 | * to drive the physical display. Valid ranges are 3600 thru 6000 in 100 | ||
210 | * microvolt increments. If not specified, hardware defaults will be | ||
211 | * used (4.5V). | ||
212 | * @vcom_high_percent: the percentage of VREGOUT1 used for the peak | ||
213 | * voltage on the communications link. Valid ranges are 37 thru 100 | ||
214 | * percent. If not specified, hardware defaults will be used (91%). | ||
215 | * @vcom_amplitude_percent: the percentage of VREGOUT1 used for the | ||
216 | * peak-to-peak amplitude of the communcation signals to the physical | ||
217 | * display. Valid ranges are 70 thru 132 percent in increments if two | ||
218 | * percent. Odd percentages will be truncated. If not specified, hardware | ||
219 | * defaults will be used (114%). | ||
220 | * @dclk_active_high: data/pixel clock active high, data will be clocked | ||
221 | * in on the rising edge of the DCLK (this is usually the case). | ||
222 | * @syncmode: The synchronization mode, what sync signals are emitted. | ||
223 | * See the enum for details. | ||
224 | * @de_active_high: DE (data entry) is active high | ||
225 | * @hsync_active_high: HSYNC is active high | ||
226 | * @vsync_active_high: VSYNC is active high | ||
227 | * @gamma_corr_pos: a set of 8 nybbles describing positive | ||
228 | * gamma correction for voltages V1 thru V8. Valid range 0..15 | ||
229 | * @gamma_corr_neg: a set of 8 nybbles describing negative | ||
230 | * gamma correction for voltages V1 thru V8. Valid range 0..15 | ||
231 | * | ||
232 | * These adjust what grayscale voltage will be output for input data V1 = 0, | ||
233 | * V2 = 16, V3 = 48, V4 = 96, V5 = 160, V6 = 208, V7 = 240 and V8 = 255. | ||
234 | * The curve is shaped like this: | ||
235 | * | ||
236 | * ^ | ||
237 | * | V8 | ||
238 | * | V7 | ||
239 | * | V6 | ||
240 | * | V5 | ||
241 | * | V4 | ||
242 | * | V3 | ||
243 | * | V2 | ||
244 | * | V1 | ||
245 | * +-----------------------------------------------------------> | ||
246 | * 0 16 48 96 160 208 240 255 | ||
247 | * | ||
248 | * The negative and postive gamma values adjust the V1 thru V8 up/down | ||
249 | * according to the datasheet specifications. This is a property of the | ||
250 | * physical display connected to the display controller and may vary. | ||
251 | * If defined, both arrays must be supplied in full. If the properties | ||
252 | * are not supplied, hardware defaults will be used. | ||
253 | */ | ||
254 | struct ili9322_config { | ||
255 | u32 width_mm; | ||
256 | u32 height_mm; | ||
257 | bool flip_horizontal; | ||
258 | bool flip_vertical; | ||
259 | enum ili9322_input input; | ||
260 | u32 vreg1out_mv; | ||
261 | u32 vcom_high_percent; | ||
262 | u32 vcom_amplitude_percent; | ||
263 | bool dclk_active_high; | ||
264 | bool de_active_high; | ||
265 | bool hsync_active_high; | ||
266 | bool vsync_active_high; | ||
267 | u8 syncmode; | ||
268 | u8 gamma_corr_pos[8]; | ||
269 | u8 gamma_corr_neg[8]; | ||
270 | }; | ||
271 | |||
272 | struct ili9322 { | ||
273 | struct device *dev; | ||
274 | const struct ili9322_config *conf; | ||
275 | struct drm_panel panel; | ||
276 | struct regmap *regmap; | ||
277 | struct regulator_bulk_data supplies[3]; | ||
278 | struct gpio_desc *reset_gpio; | ||
279 | enum ili9322_input input; | ||
280 | struct videomode vm; | ||
281 | u8 gamma[8]; | ||
282 | u8 vreg1out; | ||
283 | u8 vcom_high; | ||
284 | u8 vcom_amplitude; | ||
285 | }; | ||
286 | |||
287 | static inline struct ili9322 *panel_to_ili9322(struct drm_panel *panel) | ||
288 | { | ||
289 | return container_of(panel, struct ili9322, panel); | ||
290 | } | ||
291 | |||
292 | static int ili9322_regmap_spi_write(void *context, const void *data, | ||
293 | size_t count) | ||
294 | { | ||
295 | struct device *dev = context; | ||
296 | struct spi_device *spi = to_spi_device(dev); | ||
297 | u8 buf[2]; | ||
298 | |||
299 | /* Clear bit 7 to write */ | ||
300 | memcpy(buf, data, 2); | ||
301 | buf[0] &= ~0x80; | ||
302 | |||
303 | dev_dbg(dev, "WRITE: %02x %02x\n", buf[0], buf[1]); | ||
304 | return spi_write_then_read(spi, buf, 2, NULL, 0); | ||
305 | } | ||
306 | |||
307 | static int ili9322_regmap_spi_read(void *context, const void *reg, | ||
308 | size_t reg_size, void *val, size_t val_size) | ||
309 | { | ||
310 | struct device *dev = context; | ||
311 | struct spi_device *spi = to_spi_device(dev); | ||
312 | u8 buf[1]; | ||
313 | |||
314 | /* Set bit 7 to 1 to read */ | ||
315 | memcpy(buf, reg, 1); | ||
316 | dev_dbg(dev, "READ: %02x reg size = %zu, val size = %zu\n", | ||
317 | buf[0], reg_size, val_size); | ||
318 | buf[0] |= 0x80; | ||
319 | |||
320 | return spi_write_then_read(spi, buf, 1, val, 1); | ||
321 | } | ||
322 | |||
323 | static struct regmap_bus ili9322_regmap_bus = { | ||
324 | .write = ili9322_regmap_spi_write, | ||
325 | .read = ili9322_regmap_spi_read, | ||
326 | .reg_format_endian_default = REGMAP_ENDIAN_BIG, | ||
327 | .val_format_endian_default = REGMAP_ENDIAN_BIG, | ||
328 | }; | ||
329 | |||
330 | static bool ili9322_volatile_reg(struct device *dev, unsigned int reg) | ||
331 | { | ||
332 | return false; | ||
333 | } | ||
334 | |||
335 | static bool ili9322_writeable_reg(struct device *dev, unsigned int reg) | ||
336 | { | ||
337 | /* Just register 0 is read-only */ | ||
338 | if (reg == 0x00) | ||
339 | return false; | ||
340 | return true; | ||
341 | } | ||
342 | |||
343 | const struct regmap_config ili9322_regmap_config = { | ||
344 | .reg_bits = 8, | ||
345 | .val_bits = 8, | ||
346 | .max_register = 0x44, | ||
347 | .cache_type = REGCACHE_RBTREE, | ||
348 | .volatile_reg = ili9322_volatile_reg, | ||
349 | .writeable_reg = ili9322_writeable_reg, | ||
350 | }; | ||
351 | |||
352 | static int ili9322_init(struct drm_panel *panel, struct ili9322 *ili) | ||
353 | { | ||
354 | struct drm_connector *connector = panel->connector; | ||
355 | u8 reg; | ||
356 | int ret; | ||
357 | int i; | ||
358 | |||
359 | /* Reset display */ | ||
360 | ret = regmap_write(ili->regmap, ILI9322_GLOBAL_RESET, | ||
361 | ILI9322_GLOBAL_RESET_ASSERT); | ||
362 | if (ret) { | ||
363 | dev_err(ili->dev, "can't issue GRESET (%d)\n", ret); | ||
364 | return ret; | ||
365 | } | ||
366 | |||
367 | /* Set up the main voltage regulator */ | ||
368 | if (ili->vreg1out != U8_MAX) { | ||
369 | ret = regmap_write(ili->regmap, ILI9322_VREG1_VOLTAGE, | ||
370 | ili->vreg1out); | ||
371 | if (ret) { | ||
372 | dev_err(ili->dev, "can't set up VREG1OUT (%d)\n", ret); | ||
373 | return ret; | ||
374 | } | ||
375 | } | ||
376 | |||
377 | if (ili->vcom_amplitude != U8_MAX) { | ||
378 | ret = regmap_write(ili->regmap, ILI9322_VCOM_AMP, | ||
379 | ili->vcom_amplitude); | ||
380 | if (ret) { | ||
381 | dev_err(ili->dev, | ||
382 | "can't set up VCOM amplitude (%d)\n", ret); | ||
383 | return ret; | ||
384 | } | ||
385 | }; | ||
386 | |||
387 | if (ili->vcom_high != U8_MAX) { | ||
388 | ret = regmap_write(ili->regmap, ILI9322_VCOM_HIGH, | ||
389 | ili->vcom_high); | ||
390 | if (ret) { | ||
391 | dev_err(ili->dev, "can't set up VCOM high (%d)\n", ret); | ||
392 | return ret; | ||
393 | } | ||
394 | }; | ||
395 | |||
396 | /* Set up gamma correction */ | ||
397 | for (i = 0; i < ARRAY_SIZE(ili->gamma); i++) { | ||
398 | ret = regmap_write(ili->regmap, ILI9322_GAMMA_1 + i, | ||
399 | ili->gamma[i]); | ||
400 | if (ret) { | ||
401 | dev_err(ili->dev, | ||
402 | "can't write gamma V%d to 0x%02x (%d)\n", | ||
403 | i + 1, ILI9322_GAMMA_1 + i, ret); | ||
404 | return ret; | ||
405 | } | ||
406 | } | ||
407 | |||
408 | /* | ||
409 | * Polarity and inverted color order for RGB input. | ||
410 | * None of this applies in the BT.656 mode. | ||
411 | */ | ||
412 | if (ili->conf->dclk_active_high) { | ||
413 | reg = ILI9322_POL_DCLK; | ||
414 | connector->display_info.bus_flags |= | ||
415 | DRM_BUS_FLAG_PIXDATA_POSEDGE; | ||
416 | } else { | ||
417 | reg = 0; | ||
418 | connector->display_info.bus_flags |= | ||
419 | DRM_BUS_FLAG_PIXDATA_NEGEDGE; | ||
420 | } | ||
421 | if (ili->conf->de_active_high) { | ||
422 | reg |= ILI9322_POL_DE; | ||
423 | connector->display_info.bus_flags |= | ||
424 | DRM_BUS_FLAG_DE_HIGH; | ||
425 | } else { | ||
426 | connector->display_info.bus_flags |= | ||
427 | DRM_BUS_FLAG_DE_LOW; | ||
428 | } | ||
429 | if (ili->conf->hsync_active_high) | ||
430 | reg |= ILI9322_POL_HSYNC; | ||
431 | if (ili->conf->vsync_active_high) | ||
432 | reg |= ILI9322_POL_VSYNC; | ||
433 | ret = regmap_write(ili->regmap, ILI9322_POL, reg); | ||
434 | if (ret) { | ||
435 | dev_err(ili->dev, "can't write POL register (%d)\n", ret); | ||
436 | return ret; | ||
437 | } | ||
438 | |||
439 | /* | ||
440 | * Set up interface control. | ||
441 | * This is not used in the BT.656 mode (no H/Vsync or DE signals). | ||
442 | */ | ||
443 | reg = ili->conf->syncmode; | ||
444 | reg |= ILI9322_IF_CTRL_LINE_INVERSION; | ||
445 | ret = regmap_write(ili->regmap, ILI9322_IF_CTRL, reg); | ||
446 | if (ret) { | ||
447 | dev_err(ili->dev, "can't write IF CTRL register (%d)\n", ret); | ||
448 | return ret; | ||
449 | } | ||
450 | |||
451 | /* Set up the input mode */ | ||
452 | reg = (ili->input << 4); | ||
453 | /* These are inverted, setting to 1 is the default, clearing flips */ | ||
454 | if (!ili->conf->flip_horizontal) | ||
455 | reg |= ILI9322_ENTRY_HDIR; | ||
456 | if (!ili->conf->flip_vertical) | ||
457 | reg |= ILI9322_ENTRY_VDIR; | ||
458 | reg |= ILI9322_ENTRY_AUTODETECT; | ||
459 | ret = regmap_write(ili->regmap, ILI9322_ENTRY, reg); | ||
460 | if (ret) { | ||
461 | dev_err(ili->dev, "can't write ENTRY reg (%d)\n", ret); | ||
462 | return ret; | ||
463 | } | ||
464 | dev_info(ili->dev, "display is in %s mode, syncmode %02x\n", | ||
465 | ili9322_inputs[ili->input], | ||
466 | ili->conf->syncmode); | ||
467 | |||
468 | dev_info(ili->dev, "initialized display\n"); | ||
469 | |||
470 | return 0; | ||
471 | } | ||
472 | |||
473 | /* | ||
474 | * This power-on sequence if from the datasheet, page 57. | ||
475 | */ | ||
476 | static int ili9322_power_on(struct ili9322 *ili) | ||
477 | { | ||
478 | int ret; | ||
479 | |||
480 | /* Assert RESET */ | ||
481 | gpiod_set_value(ili->reset_gpio, 1); | ||
482 | |||
483 | ret = regulator_bulk_enable(ARRAY_SIZE(ili->supplies), ili->supplies); | ||
484 | if (ret < 0) { | ||
485 | dev_err(ili->dev, "unable to enable regulators\n"); | ||
486 | return ret; | ||
487 | } | ||
488 | msleep(20); | ||
489 | |||
490 | /* De-assert RESET */ | ||
491 | gpiod_set_value(ili->reset_gpio, 0); | ||
492 | |||
493 | msleep(10); | ||
494 | |||
495 | return 0; | ||
496 | } | ||
497 | |||
498 | static int ili9322_power_off(struct ili9322 *ili) | ||
499 | { | ||
500 | return regulator_bulk_disable(ARRAY_SIZE(ili->supplies), ili->supplies); | ||
501 | } | ||
502 | |||
503 | static int ili9322_disable(struct drm_panel *panel) | ||
504 | { | ||
505 | struct ili9322 *ili = panel_to_ili9322(panel); | ||
506 | int ret; | ||
507 | |||
508 | ret = regmap_write(ili->regmap, ILI9322_POW_CTRL, | ||
509 | ILI9322_POW_CTRL_STANDBY); | ||
510 | if (ret) { | ||
511 | dev_err(ili->dev, "unable to go to standby mode\n"); | ||
512 | return ret; | ||
513 | } | ||
514 | |||
515 | return 0; | ||
516 | } | ||
517 | |||
518 | static int ili9322_unprepare(struct drm_panel *panel) | ||
519 | { | ||
520 | struct ili9322 *ili = panel_to_ili9322(panel); | ||
521 | |||
522 | return ili9322_power_off(ili); | ||
523 | } | ||
524 | |||
525 | static int ili9322_prepare(struct drm_panel *panel) | ||
526 | { | ||
527 | struct ili9322 *ili = panel_to_ili9322(panel); | ||
528 | int ret; | ||
529 | |||
530 | ret = ili9322_power_on(ili); | ||
531 | if (ret < 0) | ||
532 | return ret; | ||
533 | |||
534 | ret = ili9322_init(panel, ili); | ||
535 | if (ret < 0) | ||
536 | ili9322_unprepare(panel); | ||
537 | |||
538 | return ret; | ||
539 | } | ||
540 | |||
541 | static int ili9322_enable(struct drm_panel *panel) | ||
542 | { | ||
543 | struct ili9322 *ili = panel_to_ili9322(panel); | ||
544 | int ret; | ||
545 | |||
546 | ret = regmap_write(ili->regmap, ILI9322_POW_CTRL, | ||
547 | ILI9322_POW_CTRL_DEFAULT); | ||
548 | if (ret) { | ||
549 | dev_err(ili->dev, "unable to enable panel\n"); | ||
550 | return ret; | ||
551 | } | ||
552 | |||
553 | return 0; | ||
554 | } | ||
555 | |||
556 | /* Serial RGB modes */ | ||
557 | static const struct drm_display_mode srgb_320x240_mode = { | ||
558 | .clock = 2453500, | ||
559 | .hdisplay = 320, | ||
560 | .hsync_start = 320 + 359, | ||
561 | .hsync_end = 320 + 359 + 1, | ||
562 | .htotal = 320 + 359 + 1 + 241, | ||
563 | .vdisplay = 240, | ||
564 | .vsync_start = 240 + 4, | ||
565 | .vsync_end = 240 + 4 + 1, | ||
566 | .vtotal = 262, | ||
567 | .vrefresh = 60, | ||
568 | .flags = 0, | ||
569 | }; | ||
570 | |||
571 | static const struct drm_display_mode srgb_360x240_mode = { | ||
572 | .clock = 2700000, | ||
573 | .hdisplay = 360, | ||
574 | .hsync_start = 360 + 35, | ||
575 | .hsync_end = 360 + 35 + 1, | ||
576 | .htotal = 360 + 35 + 1 + 241, | ||
577 | .vdisplay = 240, | ||
578 | .vsync_start = 240 + 21, | ||
579 | .vsync_end = 240 + 21 + 1, | ||
580 | .vtotal = 262, | ||
581 | .vrefresh = 60, | ||
582 | .flags = 0, | ||
583 | }; | ||
584 | |||
585 | /* This is the only mode listed for parallel RGB in the datasheet */ | ||
586 | static const struct drm_display_mode prgb_320x240_mode = { | ||
587 | .clock = 6400000, | ||
588 | .hdisplay = 320, | ||
589 | .hsync_start = 320 + 38, | ||
590 | .hsync_end = 320 + 38 + 1, | ||
591 | .htotal = 320 + 38 + 1 + 50, | ||
592 | .vdisplay = 240, | ||
593 | .vsync_start = 240 + 4, | ||
594 | .vsync_end = 240 + 4 + 1, | ||
595 | .vtotal = 262, | ||
596 | .vrefresh = 60, | ||
597 | .flags = 0, | ||
598 | }; | ||
599 | |||
600 | /* YUV modes */ | ||
601 | static const struct drm_display_mode yuv_640x320_mode = { | ||
602 | .clock = 2454000, | ||
603 | .hdisplay = 640, | ||
604 | .hsync_start = 640 + 252, | ||
605 | .hsync_end = 640 + 252 + 1, | ||
606 | .htotal = 640 + 252 + 1 + 28, | ||
607 | .vdisplay = 320, | ||
608 | .vsync_start = 320 + 4, | ||
609 | .vsync_end = 320 + 4 + 1, | ||
610 | .vtotal = 320 + 4 + 1 + 18, | ||
611 | .vrefresh = 60, | ||
612 | .flags = 0, | ||
613 | }; | ||
614 | |||
615 | static const struct drm_display_mode yuv_720x360_mode = { | ||
616 | .clock = 2700000, | ||
617 | .hdisplay = 720, | ||
618 | .hsync_start = 720 + 252, | ||
619 | .hsync_end = 720 + 252 + 1, | ||
620 | .htotal = 720 + 252 + 1 + 24, | ||
621 | .vdisplay = 360, | ||
622 | .vsync_start = 360 + 4, | ||
623 | .vsync_end = 360 + 4 + 1, | ||
624 | .vtotal = 360 + 4 + 1 + 18, | ||
625 | .vrefresh = 60, | ||
626 | .flags = 0, | ||
627 | }; | ||
628 | |||
629 | /* BT.656 VGA mode, 640x480 */ | ||
630 | static const struct drm_display_mode itu_r_bt_656_640_mode = { | ||
631 | .clock = 2454000, | ||
632 | .hdisplay = 640, | ||
633 | .hsync_start = 640 + 3, | ||
634 | .hsync_end = 640 + 3 + 1, | ||
635 | .htotal = 640 + 3 + 1 + 272, | ||
636 | .vdisplay = 480, | ||
637 | .vsync_start = 480 + 4, | ||
638 | .vsync_end = 480 + 4 + 1, | ||
639 | .vtotal = 500, | ||
640 | .vrefresh = 60, | ||
641 | .flags = 0, | ||
642 | }; | ||
643 | |||
644 | /* BT.656 D1 mode 720x480 */ | ||
645 | static const struct drm_display_mode itu_r_bt_656_720_mode = { | ||
646 | .clock = 2700000, | ||
647 | .hdisplay = 720, | ||
648 | .hsync_start = 720 + 3, | ||
649 | .hsync_end = 720 + 3 + 1, | ||
650 | .htotal = 720 + 3 + 1 + 272, | ||
651 | .vdisplay = 480, | ||
652 | .vsync_start = 480 + 4, | ||
653 | .vsync_end = 480 + 4 + 1, | ||
654 | .vtotal = 500, | ||
655 | .vrefresh = 60, | ||
656 | .flags = 0, | ||
657 | }; | ||
658 | |||
659 | static int ili9322_get_modes(struct drm_panel *panel) | ||
660 | { | ||
661 | struct drm_connector *connector = panel->connector; | ||
662 | struct ili9322 *ili = panel_to_ili9322(panel); | ||
663 | struct drm_display_mode *mode; | ||
664 | |||
665 | strncpy(connector->display_info.name, "ILI9322 TFT LCD driver\0", | ||
666 | DRM_DISPLAY_INFO_LEN); | ||
667 | connector->display_info.width_mm = ili->conf->width_mm; | ||
668 | connector->display_info.height_mm = ili->conf->height_mm; | ||
669 | |||
670 | switch (ili->input) { | ||
671 | case ILI9322_INPUT_SRGB_DUMMY_320X240: | ||
672 | mode = drm_mode_duplicate(panel->drm, &srgb_320x240_mode); | ||
673 | break; | ||
674 | case ILI9322_INPUT_SRGB_DUMMY_360X240: | ||
675 | mode = drm_mode_duplicate(panel->drm, &srgb_360x240_mode); | ||
676 | break; | ||
677 | case ILI9322_INPUT_PRGB_THROUGH: | ||
678 | case ILI9322_INPUT_PRGB_ALIGNED: | ||
679 | mode = drm_mode_duplicate(panel->drm, &prgb_320x240_mode); | ||
680 | break; | ||
681 | case ILI9322_INPUT_YUV_640X320_YCBCR: | ||
682 | mode = drm_mode_duplicate(panel->drm, &yuv_640x320_mode); | ||
683 | break; | ||
684 | case ILI9322_INPUT_YUV_720X360_YCBCR: | ||
685 | mode = drm_mode_duplicate(panel->drm, &yuv_720x360_mode); | ||
686 | break; | ||
687 | case ILI9322_INPUT_ITU_R_BT656_720X360_YCBCR: | ||
688 | mode = drm_mode_duplicate(panel->drm, &itu_r_bt_656_720_mode); | ||
689 | break; | ||
690 | case ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR: | ||
691 | mode = drm_mode_duplicate(panel->drm, &itu_r_bt_656_640_mode); | ||
692 | break; | ||
693 | default: | ||
694 | mode = NULL; | ||
695 | break; | ||
696 | } | ||
697 | if (!mode) { | ||
698 | DRM_ERROR("bad mode or failed to add mode\n"); | ||
699 | return -EINVAL; | ||
700 | } | ||
701 | drm_mode_set_name(mode); | ||
702 | /* | ||
703 | * This is the preferred mode because most people are going | ||
704 | * to want to use the display with VGA type graphics. | ||
705 | */ | ||
706 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; | ||
707 | |||
708 | /* Set up the polarity */ | ||
709 | if (ili->conf->hsync_active_high) | ||
710 | mode->flags |= DRM_MODE_FLAG_PHSYNC; | ||
711 | else | ||
712 | mode->flags |= DRM_MODE_FLAG_NHSYNC; | ||
713 | if (ili->conf->vsync_active_high) | ||
714 | mode->flags |= DRM_MODE_FLAG_PVSYNC; | ||
715 | else | ||
716 | mode->flags |= DRM_MODE_FLAG_NVSYNC; | ||
717 | |||
718 | mode->width_mm = ili->conf->width_mm; | ||
719 | mode->height_mm = ili->conf->height_mm; | ||
720 | drm_mode_probed_add(connector, mode); | ||
721 | |||
722 | return 1; /* Number of modes */ | ||
723 | } | ||
724 | |||
725 | static const struct drm_panel_funcs ili9322_drm_funcs = { | ||
726 | .disable = ili9322_disable, | ||
727 | .unprepare = ili9322_unprepare, | ||
728 | .prepare = ili9322_prepare, | ||
729 | .enable = ili9322_enable, | ||
730 | .get_modes = ili9322_get_modes, | ||
731 | }; | ||
732 | |||
733 | static int ili9322_probe(struct spi_device *spi) | ||
734 | { | ||
735 | struct device *dev = &spi->dev; | ||
736 | struct ili9322 *ili; | ||
737 | const struct regmap_config *regmap_config; | ||
738 | u8 gamma; | ||
739 | u32 val; | ||
740 | int ret; | ||
741 | int i; | ||
742 | |||
743 | ili = devm_kzalloc(dev, sizeof(struct ili9322), GFP_KERNEL); | ||
744 | if (!ili) | ||
745 | return -ENOMEM; | ||
746 | |||
747 | spi_set_drvdata(spi, ili); | ||
748 | |||
749 | ili->dev = dev; | ||
750 | |||
751 | /* | ||
752 | * Every new incarnation of this display must have a unique | ||
753 | * data entry for the system in this driver. | ||
754 | */ | ||
755 | ili->conf = of_device_get_match_data(dev); | ||
756 | if (!ili->conf) { | ||
757 | dev_err(dev, "missing device configuration\n"); | ||
758 | return -ENODEV; | ||
759 | } | ||
760 | |||
761 | val = ili->conf->vreg1out_mv; | ||
762 | if (!val) { | ||
763 | /* Default HW value, do not touch (should be 4.5V) */ | ||
764 | ili->vreg1out = U8_MAX; | ||
765 | } else { | ||
766 | if (val < 3600) { | ||
767 | dev_err(dev, "too low VREG1OUT\n"); | ||
768 | return -EINVAL; | ||
769 | } | ||
770 | if (val > 6000) { | ||
771 | dev_err(dev, "too high VREG1OUT\n"); | ||
772 | return -EINVAL; | ||
773 | } | ||
774 | if ((val % 100) != 0) { | ||
775 | dev_err(dev, "VREG1OUT is no even 100 microvolt\n"); | ||
776 | return -EINVAL; | ||
777 | } | ||
778 | val -= 3600; | ||
779 | val /= 100; | ||
780 | dev_dbg(dev, "VREG1OUT = 0x%02x\n", val); | ||
781 | ili->vreg1out = val; | ||
782 | } | ||
783 | |||
784 | val = ili->conf->vcom_high_percent; | ||
785 | if (!val) { | ||
786 | /* Default HW value, do not touch (should be 91%) */ | ||
787 | ili->vcom_high = U8_MAX; | ||
788 | } else { | ||
789 | if (val < 37) { | ||
790 | dev_err(dev, "too low VCOM high\n"); | ||
791 | return -EINVAL; | ||
792 | } | ||
793 | if (val > 100) { | ||
794 | dev_err(dev, "too high VCOM high\n"); | ||
795 | return -EINVAL; | ||
796 | } | ||
797 | val -= 37; | ||
798 | dev_dbg(dev, "VCOM high = 0x%02x\n", val); | ||
799 | ili->vcom_high = val; | ||
800 | } | ||
801 | |||
802 | val = ili->conf->vcom_amplitude_percent; | ||
803 | if (!val) { | ||
804 | /* Default HW value, do not touch (should be 114%) */ | ||
805 | ili->vcom_high = U8_MAX; | ||
806 | } else { | ||
807 | if (val < 70) { | ||
808 | dev_err(dev, "too low VCOM amplitude\n"); | ||
809 | return -EINVAL; | ||
810 | } | ||
811 | if (val > 132) { | ||
812 | dev_err(dev, "too high VCOM amplitude\n"); | ||
813 | return -EINVAL; | ||
814 | } | ||
815 | val -= 70; | ||
816 | val >>= 1; /* Increments of 2% */ | ||
817 | dev_dbg(dev, "VCOM amplitude = 0x%02x\n", val); | ||
818 | ili->vcom_amplitude = val; | ||
819 | } | ||
820 | |||
821 | for (i = 0; i < ARRAY_SIZE(ili->gamma); i++) { | ||
822 | val = ili->conf->gamma_corr_neg[i]; | ||
823 | if (val > 15) { | ||
824 | dev_err(dev, "negative gamma %u > 15, capping\n", val); | ||
825 | val = 15; | ||
826 | } | ||
827 | gamma = val << 4; | ||
828 | val = ili->conf->gamma_corr_pos[i]; | ||
829 | if (val > 15) { | ||
830 | dev_err(dev, "positive gamma %u > 15, capping\n", val); | ||
831 | val = 15; | ||
832 | } | ||
833 | gamma |= val; | ||
834 | ili->gamma[i] = gamma; | ||
835 | dev_dbg(dev, "gamma V%d: 0x%02x\n", i + 1, gamma); | ||
836 | } | ||
837 | |||
838 | ili->supplies[0].supply = "vcc"; /* 2.7-3.6 V */ | ||
839 | ili->supplies[1].supply = "iovcc"; /* 1.65-3.6V */ | ||
840 | ili->supplies[2].supply = "vci"; /* 2.7-3.6V */ | ||
841 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ili->supplies), | ||
842 | ili->supplies); | ||
843 | if (ret < 0) | ||
844 | return ret; | ||
845 | ret = regulator_set_voltage(ili->supplies[0].consumer, | ||
846 | 2700000, 3600000); | ||
847 | if (ret) | ||
848 | return ret; | ||
849 | ret = regulator_set_voltage(ili->supplies[1].consumer, | ||
850 | 1650000, 3600000); | ||
851 | if (ret) | ||
852 | return ret; | ||
853 | ret = regulator_set_voltage(ili->supplies[2].consumer, | ||
854 | 2700000, 3600000); | ||
855 | if (ret) | ||
856 | return ret; | ||
857 | |||
858 | ili->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); | ||
859 | if (IS_ERR(ili->reset_gpio)) { | ||
860 | dev_err(dev, "failed to get RESET GPIO\n"); | ||
861 | return PTR_ERR(ili->reset_gpio); | ||
862 | } | ||
863 | |||
864 | spi->bits_per_word = 8; | ||
865 | ret = spi_setup(spi); | ||
866 | if (ret < 0) { | ||
867 | dev_err(dev, "spi setup failed.\n"); | ||
868 | return ret; | ||
869 | } | ||
870 | regmap_config = &ili9322_regmap_config; | ||
871 | ili->regmap = devm_regmap_init(dev, &ili9322_regmap_bus, dev, | ||
872 | regmap_config); | ||
873 | if (IS_ERR(ili->regmap)) { | ||
874 | dev_err(dev, "failed to allocate register map\n"); | ||
875 | return PTR_ERR(ili->regmap); | ||
876 | } | ||
877 | |||
878 | ret = regmap_read(ili->regmap, ILI9322_CHIP_ID, &val); | ||
879 | if (ret) { | ||
880 | dev_err(dev, "can't get chip ID (%d)\n", ret); | ||
881 | return ret; | ||
882 | } | ||
883 | if (val != ILI9322_CHIP_ID_MAGIC) { | ||
884 | dev_err(dev, "chip ID 0x%0x2, expected 0x%02x\n", val, | ||
885 | ILI9322_CHIP_ID_MAGIC); | ||
886 | return -ENODEV; | ||
887 | } | ||
888 | |||
889 | /* Probe the system to find the display setting */ | ||
890 | if (ili->conf->input == ILI9322_INPUT_UNKNOWN) { | ||
891 | ret = regmap_read(ili->regmap, ILI9322_ENTRY, &val); | ||
892 | if (ret) { | ||
893 | dev_err(dev, "can't get entry setting (%d)\n", ret); | ||
894 | return ret; | ||
895 | } | ||
896 | /* Input enum corresponds to HW setting */ | ||
897 | ili->input = (val >> 4) & 0x0f; | ||
898 | if (ili->input >= ILI9322_INPUT_UNKNOWN) | ||
899 | ili->input = ILI9322_INPUT_UNKNOWN; | ||
900 | } else { | ||
901 | ili->input = ili->conf->input; | ||
902 | } | ||
903 | |||
904 | drm_panel_init(&ili->panel); | ||
905 | ili->panel.dev = dev; | ||
906 | ili->panel.funcs = &ili9322_drm_funcs; | ||
907 | |||
908 | return drm_panel_add(&ili->panel); | ||
909 | } | ||
910 | |||
911 | static int ili9322_remove(struct spi_device *spi) | ||
912 | { | ||
913 | struct ili9322 *ili = spi_get_drvdata(spi); | ||
914 | |||
915 | ili9322_power_off(ili); | ||
916 | drm_panel_remove(&ili->panel); | ||
917 | |||
918 | return 0; | ||
919 | } | ||
920 | |||
921 | /* | ||
922 | * The D-Link DIR-685 panel is marked LM918A01-1A SY-B4-091116-E0199 | ||
923 | */ | ||
924 | static const struct ili9322_config ili9322_dir_685 = { | ||
925 | .width_mm = 65, | ||
926 | .height_mm = 50, | ||
927 | .input = ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR, | ||
928 | .vreg1out_mv = 4600, | ||
929 | .vcom_high_percent = 91, | ||
930 | .vcom_amplitude_percent = 114, | ||
931 | .syncmode = ILI9322_IF_CTRL_SYNC_DISABLED, | ||
932 | .dclk_active_high = true, | ||
933 | .gamma_corr_neg = { 0xa, 0x5, 0x7, 0x7, 0x7, 0x5, 0x1, 0x6 }, | ||
934 | .gamma_corr_pos = { 0x7, 0x7, 0x3, 0x2, 0x3, 0x5, 0x7, 0x2 }, | ||
935 | }; | ||
936 | |||
937 | static const struct of_device_id ili9322_of_match[] = { | ||
938 | { | ||
939 | .compatible = "dlink,dir-685-panel", | ||
940 | .data = &ili9322_dir_685, | ||
941 | }, | ||
942 | { | ||
943 | .compatible = "ilitek,ili9322", | ||
944 | .data = NULL, | ||
945 | }, | ||
946 | { } | ||
947 | }; | ||
948 | MODULE_DEVICE_TABLE(of, ili9322_of_match); | ||
949 | |||
950 | static struct spi_driver ili9322_driver = { | ||
951 | .probe = ili9322_probe, | ||
952 | .remove = ili9322_remove, | ||
953 | .driver = { | ||
954 | .name = "panel-ilitek-ili9322", | ||
955 | .of_match_table = ili9322_of_match, | ||
956 | }, | ||
957 | }; | ||
958 | module_spi_driver(ili9322_driver); | ||
959 | |||
960 | MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); | ||
961 | MODULE_DESCRIPTION("ILI9322 LCD panel driver"); | ||
962 | MODULE_LICENSE("GPL v2"); | ||
diff --git a/drivers/gpu/drm/panel/panel-lvds.c b/drivers/gpu/drm/panel/panel-lvds.c index e2d57c01200b..57e38a9e7ab4 100644 --- a/drivers/gpu/drm/panel/panel-lvds.c +++ b/drivers/gpu/drm/panel/panel-lvds.c | |||
@@ -17,6 +17,7 @@ | |||
17 | #include <linux/module.h> | 17 | #include <linux/module.h> |
18 | #include <linux/of_platform.h> | 18 | #include <linux/of_platform.h> |
19 | #include <linux/platform_device.h> | 19 | #include <linux/platform_device.h> |
20 | #include <linux/regulator/consumer.h> | ||
20 | #include <linux/slab.h> | 21 | #include <linux/slab.h> |
21 | 22 | ||
22 | #include <drm/drmP.h> | 23 | #include <drm/drmP.h> |
@@ -39,6 +40,7 @@ struct panel_lvds { | |||
39 | bool data_mirror; | 40 | bool data_mirror; |
40 | 41 | ||
41 | struct backlight_device *backlight; | 42 | struct backlight_device *backlight; |
43 | struct regulator *supply; | ||
42 | 44 | ||
43 | struct gpio_desc *enable_gpio; | 45 | struct gpio_desc *enable_gpio; |
44 | struct gpio_desc *reset_gpio; | 46 | struct gpio_desc *reset_gpio; |
@@ -69,6 +71,9 @@ static int panel_lvds_unprepare(struct drm_panel *panel) | |||
69 | if (lvds->enable_gpio) | 71 | if (lvds->enable_gpio) |
70 | gpiod_set_value_cansleep(lvds->enable_gpio, 0); | 72 | gpiod_set_value_cansleep(lvds->enable_gpio, 0); |
71 | 73 | ||
74 | if (lvds->supply) | ||
75 | regulator_disable(lvds->supply); | ||
76 | |||
72 | return 0; | 77 | return 0; |
73 | } | 78 | } |
74 | 79 | ||
@@ -76,6 +81,17 @@ static int panel_lvds_prepare(struct drm_panel *panel) | |||
76 | { | 81 | { |
77 | struct panel_lvds *lvds = to_panel_lvds(panel); | 82 | struct panel_lvds *lvds = to_panel_lvds(panel); |
78 | 83 | ||
84 | if (lvds->supply) { | ||
85 | int err; | ||
86 | |||
87 | err = regulator_enable(lvds->supply); | ||
88 | if (err < 0) { | ||
89 | dev_err(lvds->dev, "failed to enable supply: %d\n", | ||
90 | err); | ||
91 | return err; | ||
92 | } | ||
93 | } | ||
94 | |||
79 | if (lvds->enable_gpio) | 95 | if (lvds->enable_gpio) |
80 | gpiod_set_value_cansleep(lvds->enable_gpio, 1); | 96 | gpiod_set_value_cansleep(lvds->enable_gpio, 1); |
81 | 97 | ||
@@ -196,6 +212,13 @@ static int panel_lvds_probe(struct platform_device *pdev) | |||
196 | if (ret < 0) | 212 | if (ret < 0) |
197 | return ret; | 213 | return ret; |
198 | 214 | ||
215 | lvds->supply = devm_regulator_get_optional(lvds->dev, "power"); | ||
216 | if (IS_ERR(lvds->supply)) { | ||
217 | ret = PTR_ERR(lvds->supply); | ||
218 | dev_err(lvds->dev, "failed to request regulator: %d\n", ret); | ||
219 | return ret; | ||
220 | } | ||
221 | |||
199 | /* Get GPIOs and backlight controller. */ | 222 | /* Get GPIOs and backlight controller. */ |
200 | lvds->enable_gpio = devm_gpiod_get_optional(lvds->dev, "enable", | 223 | lvds->enable_gpio = devm_gpiod_get_optional(lvds->dev, "enable", |
201 | GPIOD_OUT_LOW); | 224 | GPIOD_OUT_LOW); |
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 82a6ac57fbe3..2b37a6abbb1d 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile | |||
@@ -15,6 +15,7 @@ sun8i-mixer-y += sun8i_mixer.o sun8i_ui_layer.o \ | |||
15 | 15 | ||
16 | sun4i-tcon-y += sun4i_crtc.o | 16 | sun4i-tcon-y += sun4i_crtc.o |
17 | sun4i-tcon-y += sun4i_dotclock.o | 17 | sun4i-tcon-y += sun4i_dotclock.o |
18 | sun4i-tcon-y += sun4i_lvds.o | ||
18 | sun4i-tcon-y += sun4i_tcon.o | 19 | sun4i-tcon-y += sun4i_tcon.o |
19 | sun4i-tcon-y += sun4i_rgb.o | 20 | sun4i-tcon-y += sun4i_rgb.o |
20 | 21 | ||
diff --git a/drivers/gpu/drm/sun4i/sun4i_dotclock.c b/drivers/gpu/drm/sun4i/sun4i_dotclock.c index d401156490f3..023f39bda633 100644 --- a/drivers/gpu/drm/sun4i/sun4i_dotclock.c +++ b/drivers/gpu/drm/sun4i/sun4i_dotclock.c | |||
@@ -17,8 +17,9 @@ | |||
17 | #include "sun4i_dotclock.h" | 17 | #include "sun4i_dotclock.h" |
18 | 18 | ||
19 | struct sun4i_dclk { | 19 | struct sun4i_dclk { |
20 | struct clk_hw hw; | 20 | struct clk_hw hw; |
21 | struct regmap *regmap; | 21 | struct regmap *regmap; |
22 | struct sun4i_tcon *tcon; | ||
22 | }; | 23 | }; |
23 | 24 | ||
24 | static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw) | 25 | 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, | |||
73 | static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate, | 74 | static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate, |
74 | unsigned long *parent_rate) | 75 | unsigned long *parent_rate) |
75 | { | 76 | { |
77 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | ||
78 | struct sun4i_tcon *tcon = dclk->tcon; | ||
76 | unsigned long best_parent = 0; | 79 | unsigned long best_parent = 0; |
77 | u8 best_div = 1; | 80 | u8 best_div = 1; |
78 | int i; | 81 | int i; |
79 | 82 | ||
80 | for (i = 6; i <= 127; i++) { | 83 | for (i = tcon->dclk_min_div; i <= tcon->dclk_max_div; i++) { |
81 | unsigned long ideal = rate * i; | 84 | unsigned long ideal = rate * i; |
82 | unsigned long rounded; | 85 | unsigned long rounded; |
83 | 86 | ||
@@ -167,6 +170,7 @@ int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon) | |||
167 | dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL); | 170 | dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL); |
168 | if (!dclk) | 171 | if (!dclk) |
169 | return -ENOMEM; | 172 | return -ENOMEM; |
173 | dclk->tcon = tcon; | ||
170 | 174 | ||
171 | init.name = clk_name; | 175 | init.name = clk_name; |
172 | init.ops = &sun4i_dclk_ops; | 176 | init.ops = &sun4i_dclk_ops; |
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index 2b4717604eb3..4570da0227b4 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c | |||
@@ -339,6 +339,7 @@ static const struct of_device_id sun4i_drv_of_table[] = { | |||
339 | { .compatible = "allwinner,sun6i-a31s-display-engine" }, | 339 | { .compatible = "allwinner,sun6i-a31s-display-engine" }, |
340 | { .compatible = "allwinner,sun7i-a20-display-engine" }, | 340 | { .compatible = "allwinner,sun7i-a20-display-engine" }, |
341 | { .compatible = "allwinner,sun8i-a33-display-engine" }, | 341 | { .compatible = "allwinner,sun8i-a33-display-engine" }, |
342 | { .compatible = "allwinner,sun8i-a83t-display-engine" }, | ||
342 | { .compatible = "allwinner,sun8i-v3s-display-engine" }, | 343 | { .compatible = "allwinner,sun8i-v3s-display-engine" }, |
343 | { } | 344 | { } |
344 | }; | 345 | }; |
diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.c b/drivers/gpu/drm/sun4i/sun4i_lvds.c new file mode 100644 index 000000000000..be3f14d7746d --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_lvds.c | |||
@@ -0,0 +1,177 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0+ | ||
2 | /* | ||
3 | * Copyright (C) 2017 Free Electrons | ||
4 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
5 | */ | ||
6 | |||
7 | #include <linux/clk.h> | ||
8 | |||
9 | #include <drm/drmP.h> | ||
10 | #include <drm/drm_atomic_helper.h> | ||
11 | #include <drm/drm_crtc_helper.h> | ||
12 | #include <drm/drm_of.h> | ||
13 | #include <drm/drm_panel.h> | ||
14 | |||
15 | #include "sun4i_crtc.h" | ||
16 | #include "sun4i_tcon.h" | ||
17 | #include "sun4i_lvds.h" | ||
18 | |||
19 | struct sun4i_lvds { | ||
20 | struct drm_connector connector; | ||
21 | struct drm_encoder encoder; | ||
22 | |||
23 | struct sun4i_tcon *tcon; | ||
24 | }; | ||
25 | |||
26 | static inline struct sun4i_lvds * | ||
27 | drm_connector_to_sun4i_lvds(struct drm_connector *connector) | ||
28 | { | ||
29 | return container_of(connector, struct sun4i_lvds, | ||
30 | connector); | ||
31 | } | ||
32 | |||
33 | static inline struct sun4i_lvds * | ||
34 | drm_encoder_to_sun4i_lvds(struct drm_encoder *encoder) | ||
35 | { | ||
36 | return container_of(encoder, struct sun4i_lvds, | ||
37 | encoder); | ||
38 | } | ||
39 | |||
40 | static int sun4i_lvds_get_modes(struct drm_connector *connector) | ||
41 | { | ||
42 | struct sun4i_lvds *lvds = | ||
43 | drm_connector_to_sun4i_lvds(connector); | ||
44 | struct sun4i_tcon *tcon = lvds->tcon; | ||
45 | |||
46 | return drm_panel_get_modes(tcon->panel); | ||
47 | } | ||
48 | |||
49 | static struct drm_connector_helper_funcs sun4i_lvds_con_helper_funcs = { | ||
50 | .get_modes = sun4i_lvds_get_modes, | ||
51 | }; | ||
52 | |||
53 | static void | ||
54 | sun4i_lvds_connector_destroy(struct drm_connector *connector) | ||
55 | { | ||
56 | struct sun4i_lvds *lvds = drm_connector_to_sun4i_lvds(connector); | ||
57 | struct sun4i_tcon *tcon = lvds->tcon; | ||
58 | |||
59 | drm_panel_detach(tcon->panel); | ||
60 | drm_connector_cleanup(connector); | ||
61 | } | ||
62 | |||
63 | static const struct drm_connector_funcs sun4i_lvds_con_funcs = { | ||
64 | .fill_modes = drm_helper_probe_single_connector_modes, | ||
65 | .destroy = sun4i_lvds_connector_destroy, | ||
66 | .reset = drm_atomic_helper_connector_reset, | ||
67 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | ||
68 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | ||
69 | }; | ||
70 | |||
71 | static void sun4i_lvds_encoder_enable(struct drm_encoder *encoder) | ||
72 | { | ||
73 | struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); | ||
74 | struct sun4i_tcon *tcon = lvds->tcon; | ||
75 | |||
76 | DRM_DEBUG_DRIVER("Enabling LVDS output\n"); | ||
77 | |||
78 | if (!IS_ERR(tcon->panel)) { | ||
79 | drm_panel_prepare(tcon->panel); | ||
80 | drm_panel_enable(tcon->panel); | ||
81 | } | ||
82 | } | ||
83 | |||
84 | static void sun4i_lvds_encoder_disable(struct drm_encoder *encoder) | ||
85 | { | ||
86 | struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); | ||
87 | struct sun4i_tcon *tcon = lvds->tcon; | ||
88 | |||
89 | DRM_DEBUG_DRIVER("Disabling LVDS output\n"); | ||
90 | |||
91 | if (!IS_ERR(tcon->panel)) { | ||
92 | drm_panel_disable(tcon->panel); | ||
93 | drm_panel_unprepare(tcon->panel); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | static const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = { | ||
98 | .disable = sun4i_lvds_encoder_disable, | ||
99 | .enable = sun4i_lvds_encoder_enable, | ||
100 | }; | ||
101 | |||
102 | static const struct drm_encoder_funcs sun4i_lvds_enc_funcs = { | ||
103 | .destroy = drm_encoder_cleanup, | ||
104 | }; | ||
105 | |||
106 | int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon) | ||
107 | { | ||
108 | struct drm_encoder *encoder; | ||
109 | struct drm_bridge *bridge; | ||
110 | struct sun4i_lvds *lvds; | ||
111 | int ret; | ||
112 | |||
113 | lvds = devm_kzalloc(drm->dev, sizeof(*lvds), GFP_KERNEL); | ||
114 | if (!lvds) | ||
115 | return -ENOMEM; | ||
116 | lvds->tcon = tcon; | ||
117 | encoder = &lvds->encoder; | ||
118 | |||
119 | ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0, | ||
120 | &tcon->panel, &bridge); | ||
121 | if (ret) { | ||
122 | dev_info(drm->dev, "No panel or bridge found... LVDS output disabled\n"); | ||
123 | return 0; | ||
124 | } | ||
125 | |||
126 | drm_encoder_helper_add(&lvds->encoder, | ||
127 | &sun4i_lvds_enc_helper_funcs); | ||
128 | ret = drm_encoder_init(drm, | ||
129 | &lvds->encoder, | ||
130 | &sun4i_lvds_enc_funcs, | ||
131 | DRM_MODE_ENCODER_LVDS, | ||
132 | NULL); | ||
133 | if (ret) { | ||
134 | dev_err(drm->dev, "Couldn't initialise the lvds encoder\n"); | ||
135 | goto err_out; | ||
136 | } | ||
137 | |||
138 | /* The LVDS encoder can only work with the TCON channel 0 */ | ||
139 | lvds->encoder.possible_crtcs = BIT(drm_crtc_index(&tcon->crtc->crtc)); | ||
140 | |||
141 | if (tcon->panel) { | ||
142 | drm_connector_helper_add(&lvds->connector, | ||
143 | &sun4i_lvds_con_helper_funcs); | ||
144 | ret = drm_connector_init(drm, &lvds->connector, | ||
145 | &sun4i_lvds_con_funcs, | ||
146 | DRM_MODE_CONNECTOR_LVDS); | ||
147 | if (ret) { | ||
148 | dev_err(drm->dev, "Couldn't initialise the lvds connector\n"); | ||
149 | goto err_cleanup_connector; | ||
150 | } | ||
151 | |||
152 | drm_mode_connector_attach_encoder(&lvds->connector, | ||
153 | &lvds->encoder); | ||
154 | |||
155 | ret = drm_panel_attach(tcon->panel, &lvds->connector); | ||
156 | if (ret) { | ||
157 | dev_err(drm->dev, "Couldn't attach our panel\n"); | ||
158 | goto err_cleanup_connector; | ||
159 | } | ||
160 | } | ||
161 | |||
162 | if (bridge) { | ||
163 | ret = drm_bridge_attach(encoder, bridge, NULL); | ||
164 | if (ret) { | ||
165 | dev_err(drm->dev, "Couldn't attach our bridge\n"); | ||
166 | goto err_cleanup_connector; | ||
167 | } | ||
168 | } | ||
169 | |||
170 | return 0; | ||
171 | |||
172 | err_cleanup_connector: | ||
173 | drm_encoder_cleanup(&lvds->encoder); | ||
174 | err_out: | ||
175 | return ret; | ||
176 | } | ||
177 | EXPORT_SYMBOL(sun4i_lvds_init); | ||
diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.h b/drivers/gpu/drm/sun4i/sun4i_lvds.h new file mode 100644 index 000000000000..f3e90faa3082 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_lvds.h | |||
@@ -0,0 +1,12 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0+ | ||
2 | /* | ||
3 | * Copyright (C) 2017 Free Electrons | ||
4 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
5 | */ | ||
6 | |||
7 | #ifndef _SUN4I_LVDS_H_ | ||
8 | #define _SUN4I_LVDS_H_ | ||
9 | |||
10 | int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon); | ||
11 | |||
12 | #endif /* _SUN4I_LVDS_H_ */ | ||
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index a1ed462c2430..a897f82d9e66 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c | |||
@@ -31,10 +31,52 @@ | |||
31 | #include "sun4i_crtc.h" | 31 | #include "sun4i_crtc.h" |
32 | #include "sun4i_dotclock.h" | 32 | #include "sun4i_dotclock.h" |
33 | #include "sun4i_drv.h" | 33 | #include "sun4i_drv.h" |
34 | #include "sun4i_lvds.h" | ||
34 | #include "sun4i_rgb.h" | 35 | #include "sun4i_rgb.h" |
35 | #include "sun4i_tcon.h" | 36 | #include "sun4i_tcon.h" |
36 | #include "sunxi_engine.h" | 37 | #include "sunxi_engine.h" |
37 | 38 | ||
39 | static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder) | ||
40 | { | ||
41 | struct drm_connector *connector; | ||
42 | struct drm_connector_list_iter iter; | ||
43 | |||
44 | drm_connector_list_iter_begin(encoder->dev, &iter); | ||
45 | drm_for_each_connector_iter(connector, &iter) | ||
46 | if (connector->encoder == encoder) { | ||
47 | drm_connector_list_iter_end(&iter); | ||
48 | return connector; | ||
49 | } | ||
50 | drm_connector_list_iter_end(&iter); | ||
51 | |||
52 | return NULL; | ||
53 | } | ||
54 | |||
55 | static int sun4i_tcon_get_pixel_depth(const struct drm_encoder *encoder) | ||
56 | { | ||
57 | struct drm_connector *connector; | ||
58 | struct drm_display_info *info; | ||
59 | |||
60 | connector = sun4i_tcon_get_connector(encoder); | ||
61 | if (!connector) | ||
62 | return -EINVAL; | ||
63 | |||
64 | info = &connector->display_info; | ||
65 | if (info->num_bus_formats != 1) | ||
66 | return -EINVAL; | ||
67 | |||
68 | switch (info->bus_formats[0]) { | ||
69 | case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: | ||
70 | return 18; | ||
71 | |||
72 | case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: | ||
73 | case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: | ||
74 | return 24; | ||
75 | } | ||
76 | |||
77 | return -EINVAL; | ||
78 | } | ||
79 | |||
38 | static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel, | 80 | static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel, |
39 | bool enabled) | 81 | bool enabled) |
40 | { | 82 | { |
@@ -65,13 +107,63 @@ static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel, | |||
65 | clk_disable_unprepare(clk); | 107 | clk_disable_unprepare(clk); |
66 | } | 108 | } |
67 | 109 | ||
110 | static void sun4i_tcon_lvds_set_status(struct sun4i_tcon *tcon, | ||
111 | const struct drm_encoder *encoder, | ||
112 | bool enabled) | ||
113 | { | ||
114 | if (enabled) { | ||
115 | u8 val; | ||
116 | |||
117 | regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, | ||
118 | SUN4I_TCON0_LVDS_IF_EN, | ||
119 | SUN4I_TCON0_LVDS_IF_EN); | ||
120 | |||
121 | /* | ||
122 | * As their name suggest, these values only apply to the A31 | ||
123 | * and later SoCs. We'll have to rework this when merging | ||
124 | * support for the older SoCs. | ||
125 | */ | ||
126 | regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, | ||
127 | SUN6I_TCON0_LVDS_ANA0_C(2) | | ||
128 | SUN6I_TCON0_LVDS_ANA0_V(3) | | ||
129 | SUN6I_TCON0_LVDS_ANA0_PD(2) | | ||
130 | SUN6I_TCON0_LVDS_ANA0_EN_LDO); | ||
131 | udelay(2); | ||
132 | |||
133 | regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, | ||
134 | SUN6I_TCON0_LVDS_ANA0_EN_MB, | ||
135 | SUN6I_TCON0_LVDS_ANA0_EN_MB); | ||
136 | udelay(2); | ||
137 | |||
138 | regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, | ||
139 | SUN6I_TCON0_LVDS_ANA0_EN_DRVC, | ||
140 | SUN6I_TCON0_LVDS_ANA0_EN_DRVC); | ||
141 | |||
142 | if (sun4i_tcon_get_pixel_depth(encoder) == 18) | ||
143 | val = 7; | ||
144 | else | ||
145 | val = 0xf; | ||
146 | |||
147 | regmap_write_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, | ||
148 | SUN6I_TCON0_LVDS_ANA0_EN_DRVD(0xf), | ||
149 | SUN6I_TCON0_LVDS_ANA0_EN_DRVD(val)); | ||
150 | } else { | ||
151 | regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, | ||
152 | SUN4I_TCON0_LVDS_IF_EN, 0); | ||
153 | } | ||
154 | } | ||
155 | |||
68 | void sun4i_tcon_set_status(struct sun4i_tcon *tcon, | 156 | void sun4i_tcon_set_status(struct sun4i_tcon *tcon, |
69 | const struct drm_encoder *encoder, | 157 | const struct drm_encoder *encoder, |
70 | bool enabled) | 158 | bool enabled) |
71 | { | 159 | { |
160 | bool is_lvds = false; | ||
72 | int channel; | 161 | int channel; |
73 | 162 | ||
74 | switch (encoder->encoder_type) { | 163 | switch (encoder->encoder_type) { |
164 | case DRM_MODE_ENCODER_LVDS: | ||
165 | is_lvds = true; | ||
166 | /* Fallthrough */ | ||
75 | case DRM_MODE_ENCODER_NONE: | 167 | case DRM_MODE_ENCODER_NONE: |
76 | channel = 0; | 168 | channel = 0; |
77 | break; | 169 | break; |
@@ -84,10 +176,16 @@ void sun4i_tcon_set_status(struct sun4i_tcon *tcon, | |||
84 | return; | 176 | return; |
85 | } | 177 | } |
86 | 178 | ||
179 | if (is_lvds && !enabled) | ||
180 | sun4i_tcon_lvds_set_status(tcon, encoder, false); | ||
181 | |||
87 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, | 182 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, |
88 | SUN4I_TCON_GCTL_TCON_ENABLE, | 183 | SUN4I_TCON_GCTL_TCON_ENABLE, |
89 | enabled ? SUN4I_TCON_GCTL_TCON_ENABLE : 0); | 184 | enabled ? SUN4I_TCON_GCTL_TCON_ENABLE : 0); |
90 | 185 | ||
186 | if (is_lvds && enabled) | ||
187 | sun4i_tcon_lvds_set_status(tcon, encoder, true); | ||
188 | |||
91 | sun4i_tcon_channel_set_status(tcon, channel, enabled); | 189 | sun4i_tcon_channel_set_status(tcon, channel, enabled); |
92 | } | 190 | } |
93 | 191 | ||
@@ -170,6 +268,75 @@ static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon, | |||
170 | SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); | 268 | SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); |
171 | } | 269 | } |
172 | 270 | ||
271 | static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon, | ||
272 | const struct drm_encoder *encoder, | ||
273 | const struct drm_display_mode *mode) | ||
274 | { | ||
275 | unsigned int bp; | ||
276 | u8 clk_delay; | ||
277 | u32 reg, val = 0; | ||
278 | |||
279 | tcon->dclk_min_div = 7; | ||
280 | tcon->dclk_max_div = 7; | ||
281 | sun4i_tcon0_mode_set_common(tcon, mode); | ||
282 | |||
283 | /* Adjust clock delay */ | ||
284 | clk_delay = sun4i_tcon_get_clk_delay(mode, 0); | ||
285 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, | ||
286 | SUN4I_TCON0_CTL_CLK_DELAY_MASK, | ||
287 | SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); | ||
288 | |||
289 | /* | ||
290 | * This is called a backporch in the register documentation, | ||
291 | * but it really is the back porch + hsync | ||
292 | */ | ||
293 | bp = mode->crtc_htotal - mode->crtc_hsync_start; | ||
294 | DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", | ||
295 | mode->crtc_htotal, bp); | ||
296 | |||
297 | /* Set horizontal display timings */ | ||
298 | regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG, | ||
299 | SUN4I_TCON0_BASIC1_H_TOTAL(mode->htotal) | | ||
300 | SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)); | ||
301 | |||
302 | /* | ||
303 | * This is called a backporch in the register documentation, | ||
304 | * but it really is the back porch + hsync | ||
305 | */ | ||
306 | bp = mode->crtc_vtotal - mode->crtc_vsync_start; | ||
307 | DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", | ||
308 | mode->crtc_vtotal, bp); | ||
309 | |||
310 | /* Set vertical display timings */ | ||
311 | regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG, | ||
312 | SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) | | ||
313 | SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); | ||
314 | |||
315 | reg = SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0 | | ||
316 | SUN4I_TCON0_LVDS_IF_DATA_POL_NORMAL | | ||
317 | SUN4I_TCON0_LVDS_IF_CLK_POL_NORMAL; | ||
318 | if (sun4i_tcon_get_pixel_depth(encoder) == 24) | ||
319 | reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_24BITS; | ||
320 | else | ||
321 | reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_18BITS; | ||
322 | |||
323 | regmap_write(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, reg); | ||
324 | |||
325 | /* Setup the polarity of the various signals */ | ||
326 | if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) | ||
327 | val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; | ||
328 | |||
329 | if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) | ||
330 | val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; | ||
331 | |||
332 | regmap_write(tcon->regs, SUN4I_TCON0_IO_POL_REG, val); | ||
333 | |||
334 | /* Map output pins to channel 0 */ | ||
335 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, | ||
336 | SUN4I_TCON_GCTL_IOMAP_MASK, | ||
337 | SUN4I_TCON_GCTL_IOMAP_TCON0); | ||
338 | } | ||
339 | |||
173 | static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon, | 340 | static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon, |
174 | const struct drm_display_mode *mode) | 341 | const struct drm_display_mode *mode) |
175 | { | 342 | { |
@@ -177,6 +344,8 @@ static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon, | |||
177 | u8 clk_delay; | 344 | u8 clk_delay; |
178 | u32 val = 0; | 345 | u32 val = 0; |
179 | 346 | ||
347 | tcon->dclk_min_div = 6; | ||
348 | tcon->dclk_max_div = 127; | ||
180 | sun4i_tcon0_mode_set_common(tcon, mode); | 349 | sun4i_tcon0_mode_set_common(tcon, mode); |
181 | 350 | ||
182 | /* Adjust clock delay */ | 351 | /* Adjust clock delay */ |
@@ -334,6 +503,9 @@ void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, | |||
334 | const struct drm_display_mode *mode) | 503 | const struct drm_display_mode *mode) |
335 | { | 504 | { |
336 | switch (encoder->encoder_type) { | 505 | switch (encoder->encoder_type) { |
506 | case DRM_MODE_ENCODER_LVDS: | ||
507 | sun4i_tcon0_mode_set_lvds(tcon, encoder, mode); | ||
508 | break; | ||
337 | case DRM_MODE_ENCODER_NONE: | 509 | case DRM_MODE_ENCODER_NONE: |
338 | sun4i_tcon0_mode_set_rgb(tcon, mode); | 510 | sun4i_tcon0_mode_set_rgb(tcon, mode); |
339 | sun4i_tcon_set_mux(tcon, 0, encoder); | 511 | sun4i_tcon_set_mux(tcon, 0, encoder); |
@@ -665,7 +837,9 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, | |||
665 | struct drm_device *drm = data; | 837 | struct drm_device *drm = data; |
666 | struct sun4i_drv *drv = drm->dev_private; | 838 | struct sun4i_drv *drv = drm->dev_private; |
667 | struct sunxi_engine *engine; | 839 | struct sunxi_engine *engine; |
840 | struct device_node *remote; | ||
668 | struct sun4i_tcon *tcon; | 841 | struct sun4i_tcon *tcon; |
842 | bool has_lvds_rst, has_lvds_alt, can_lvds; | ||
669 | int ret; | 843 | int ret; |
670 | 844 | ||
671 | engine = sun4i_tcon_find_engine(drv, dev->of_node); | 845 | engine = sun4i_tcon_find_engine(drv, dev->of_node); |
@@ -696,6 +870,54 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, | |||
696 | return ret; | 870 | return ret; |
697 | } | 871 | } |
698 | 872 | ||
873 | /* | ||
874 | * This can only be made optional since we've had DT nodes | ||
875 | * without the LVDS reset properties. | ||
876 | * | ||
877 | * If the property is missing, just disable LVDS, and print a | ||
878 | * warning. | ||
879 | */ | ||
880 | tcon->lvds_rst = devm_reset_control_get_optional(dev, "lvds"); | ||
881 | if (IS_ERR(tcon->lvds_rst)) { | ||
882 | dev_err(dev, "Couldn't get our reset line\n"); | ||
883 | return PTR_ERR(tcon->lvds_rst); | ||
884 | } else if (tcon->lvds_rst) { | ||
885 | has_lvds_rst = true; | ||
886 | reset_control_reset(tcon->lvds_rst); | ||
887 | } else { | ||
888 | has_lvds_rst = false; | ||
889 | } | ||
890 | |||
891 | /* | ||
892 | * This can only be made optional since we've had DT nodes | ||
893 | * without the LVDS reset properties. | ||
894 | * | ||
895 | * If the property is missing, just disable LVDS, and print a | ||
896 | * warning. | ||
897 | */ | ||
898 | if (tcon->quirks->has_lvds_alt) { | ||
899 | tcon->lvds_pll = devm_clk_get(dev, "lvds-alt"); | ||
900 | if (IS_ERR(tcon->lvds_pll)) { | ||
901 | if (PTR_ERR(tcon->lvds_pll) == -ENOENT) { | ||
902 | has_lvds_alt = false; | ||
903 | } else { | ||
904 | dev_err(dev, "Couldn't get the LVDS PLL\n"); | ||
905 | return PTR_ERR(tcon->lvds_rst); | ||
906 | } | ||
907 | } else { | ||
908 | has_lvds_alt = true; | ||
909 | } | ||
910 | } | ||
911 | |||
912 | if (!has_lvds_rst || (tcon->quirks->has_lvds_alt && !has_lvds_alt)) { | ||
913 | dev_warn(dev, | ||
914 | "Missing LVDS properties, Please upgrade your DT\n"); | ||
915 | dev_warn(dev, "LVDS output disabled\n"); | ||
916 | can_lvds = false; | ||
917 | } else { | ||
918 | can_lvds = true; | ||
919 | } | ||
920 | |||
699 | ret = sun4i_tcon_init_clocks(dev, tcon); | 921 | ret = sun4i_tcon_init_clocks(dev, tcon); |
700 | if (ret) { | 922 | if (ret) { |
701 | dev_err(dev, "Couldn't init our TCON clocks\n"); | 923 | 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, | |||
727 | goto err_free_clocks; | 949 | goto err_free_clocks; |
728 | } | 950 | } |
729 | 951 | ||
730 | ret = sun4i_rgb_init(drm, tcon); | 952 | /* |
953 | * If we have an LVDS panel connected to the TCON, we should | ||
954 | * just probe the LVDS connector. Otherwise, just probe RGB as | ||
955 | * we used to. | ||
956 | */ | ||
957 | remote = of_graph_get_remote_node(dev->of_node, 1, 0); | ||
958 | if (of_device_is_compatible(remote, "panel-lvds")) | ||
959 | if (can_lvds) | ||
960 | ret = sun4i_lvds_init(drm, tcon); | ||
961 | else | ||
962 | ret = -EINVAL; | ||
963 | else | ||
964 | ret = sun4i_rgb_init(drm, tcon); | ||
965 | of_node_put(remote); | ||
966 | |||
731 | if (ret < 0) | 967 | if (ret < 0) |
732 | goto err_free_clocks; | 968 | goto err_free_clocks; |
733 | 969 | ||
@@ -877,6 +1113,7 @@ static const struct sun4i_tcon_quirks sun5i_a13_quirks = { | |||
877 | 1113 | ||
878 | static const struct sun4i_tcon_quirks sun6i_a31_quirks = { | 1114 | static const struct sun4i_tcon_quirks sun6i_a31_quirks = { |
879 | .has_channel_1 = true, | 1115 | .has_channel_1 = true, |
1116 | .has_lvds_alt = true, | ||
880 | .needs_de_be_mux = true, | 1117 | .needs_de_be_mux = true, |
881 | .set_mux = sun6i_tcon_set_mux, | 1118 | .set_mux = sun6i_tcon_set_mux, |
882 | }; | 1119 | }; |
@@ -893,6 +1130,10 @@ static const struct sun4i_tcon_quirks sun7i_a20_quirks = { | |||
893 | }; | 1130 | }; |
894 | 1131 | ||
895 | static const struct sun4i_tcon_quirks sun8i_a33_quirks = { | 1132 | static const struct sun4i_tcon_quirks sun8i_a33_quirks = { |
1133 | .has_lvds_alt = true, | ||
1134 | }; | ||
1135 | |||
1136 | static const struct sun4i_tcon_quirks sun8i_a83t_lcd_quirks = { | ||
896 | /* nothing is supported */ | 1137 | /* nothing is supported */ |
897 | }; | 1138 | }; |
898 | 1139 | ||
@@ -908,6 +1149,7 @@ const struct of_device_id sun4i_tcon_of_table[] = { | |||
908 | { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, | 1149 | { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, |
909 | { .compatible = "allwinner,sun7i-a20-tcon", .data = &sun7i_a20_quirks }, | 1150 | { .compatible = "allwinner,sun7i-a20-tcon", .data = &sun7i_a20_quirks }, |
910 | { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, | 1151 | { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, |
1152 | { .compatible = "allwinner,sun8i-a83t-tcon-lcd", .data = &sun8i_a83t_lcd_quirks }, | ||
911 | { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks }, | 1153 | { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks }, |
912 | { } | 1154 | { } |
913 | }; | 1155 | }; |
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index 839266a38505..b761c7b823c5 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h | |||
@@ -70,7 +70,21 @@ | |||
70 | #define SUN4I_TCON0_TTL2_REG 0x78 | 70 | #define SUN4I_TCON0_TTL2_REG 0x78 |
71 | #define SUN4I_TCON0_TTL3_REG 0x7c | 71 | #define SUN4I_TCON0_TTL3_REG 0x7c |
72 | #define SUN4I_TCON0_TTL4_REG 0x80 | 72 | #define SUN4I_TCON0_TTL4_REG 0x80 |
73 | |||
73 | #define SUN4I_TCON0_LVDS_IF_REG 0x84 | 74 | #define SUN4I_TCON0_LVDS_IF_REG 0x84 |
75 | #define SUN4I_TCON0_LVDS_IF_EN BIT(31) | ||
76 | #define SUN4I_TCON0_LVDS_IF_BITWIDTH_MASK BIT(26) | ||
77 | #define SUN4I_TCON0_LVDS_IF_BITWIDTH_18BITS (1 << 26) | ||
78 | #define SUN4I_TCON0_LVDS_IF_BITWIDTH_24BITS (0 << 26) | ||
79 | #define SUN4I_TCON0_LVDS_IF_CLK_SEL_MASK BIT(20) | ||
80 | #define SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0 (1 << 20) | ||
81 | #define SUN4I_TCON0_LVDS_IF_CLK_POL_MASK BIT(4) | ||
82 | #define SUN4I_TCON0_LVDS_IF_CLK_POL_NORMAL (1 << 4) | ||
83 | #define SUN4I_TCON0_LVDS_IF_CLK_POL_INV (0 << 4) | ||
84 | #define SUN4I_TCON0_LVDS_IF_DATA_POL_MASK GENMASK(3, 0) | ||
85 | #define SUN4I_TCON0_LVDS_IF_DATA_POL_NORMAL (0xf) | ||
86 | #define SUN4I_TCON0_LVDS_IF_DATA_POL_INV (0) | ||
87 | |||
74 | #define SUN4I_TCON0_IO_POL_REG 0x88 | 88 | #define SUN4I_TCON0_IO_POL_REG 0x88 |
75 | #define SUN4I_TCON0_IO_POL_DCLK_PHASE(phase) ((phase & 3) << 28) | 89 | #define SUN4I_TCON0_IO_POL_DCLK_PHASE(phase) ((phase & 3) << 28) |
76 | #define SUN4I_TCON0_IO_POL_HSYNC_POSITIVE BIT(25) | 90 | #define SUN4I_TCON0_IO_POL_HSYNC_POSITIVE BIT(25) |
@@ -131,6 +145,16 @@ | |||
131 | #define SUN4I_TCON_CEU_RANGE_G_REG 0x144 | 145 | #define SUN4I_TCON_CEU_RANGE_G_REG 0x144 |
132 | #define SUN4I_TCON_CEU_RANGE_B_REG 0x148 | 146 | #define SUN4I_TCON_CEU_RANGE_B_REG 0x148 |
133 | #define SUN4I_TCON_MUX_CTRL_REG 0x200 | 147 | #define SUN4I_TCON_MUX_CTRL_REG 0x200 |
148 | |||
149 | #define SUN4I_TCON0_LVDS_ANA0_REG 0x220 | ||
150 | #define SUN6I_TCON0_LVDS_ANA0_EN_MB BIT(31) | ||
151 | #define SUN6I_TCON0_LVDS_ANA0_EN_LDO BIT(30) | ||
152 | #define SUN6I_TCON0_LVDS_ANA0_EN_DRVC BIT(24) | ||
153 | #define SUN6I_TCON0_LVDS_ANA0_EN_DRVD(x) (((x) & 0xf) << 20) | ||
154 | #define SUN6I_TCON0_LVDS_ANA0_C(x) (((x) & 3) << 17) | ||
155 | #define SUN6I_TCON0_LVDS_ANA0_V(x) (((x) & 3) << 8) | ||
156 | #define SUN6I_TCON0_LVDS_ANA0_PD(x) (((x) & 3) << 4) | ||
157 | |||
134 | #define SUN4I_TCON1_FILL_CTL_REG 0x300 | 158 | #define SUN4I_TCON1_FILL_CTL_REG 0x300 |
135 | #define SUN4I_TCON1_FILL_BEG0_REG 0x304 | 159 | #define SUN4I_TCON1_FILL_BEG0_REG 0x304 |
136 | #define SUN4I_TCON1_FILL_END0_REG 0x308 | 160 | #define SUN4I_TCON1_FILL_END0_REG 0x308 |
@@ -149,6 +173,7 @@ struct sun4i_tcon; | |||
149 | 173 | ||
150 | struct sun4i_tcon_quirks { | 174 | struct sun4i_tcon_quirks { |
151 | bool has_channel_1; /* a33 does not have channel 1 */ | 175 | bool has_channel_1; /* a33 does not have channel 1 */ |
176 | bool has_lvds_alt; /* Does the LVDS clock have a parent other than the TCON clock? */ | ||
152 | bool needs_de_be_mux; /* sun6i needs mux to select backend */ | 177 | bool needs_de_be_mux; /* sun6i needs mux to select backend */ |
153 | 178 | ||
154 | /* callback to handle tcon muxing options */ | 179 | /* callback to handle tcon muxing options */ |
@@ -167,11 +192,17 @@ struct sun4i_tcon { | |||
167 | struct clk *sclk0; | 192 | struct clk *sclk0; |
168 | struct clk *sclk1; | 193 | struct clk *sclk1; |
169 | 194 | ||
195 | /* Possible mux for the LVDS clock */ | ||
196 | struct clk *lvds_pll; | ||
197 | |||
170 | /* Pixel clock */ | 198 | /* Pixel clock */ |
171 | struct clk *dclk; | 199 | struct clk *dclk; |
200 | u8 dclk_max_div; | ||
201 | u8 dclk_min_div; | ||
172 | 202 | ||
173 | /* Reset control */ | 203 | /* Reset control */ |
174 | struct reset_control *lcd_rst; | 204 | struct reset_control *lcd_rst; |
205 | struct reset_control *lvds_rst; | ||
175 | 206 | ||
176 | struct drm_panel *panel; | 207 | struct drm_panel *panel; |
177 | 208 | ||
diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index 29ceeb016d72..2cbb2de6d39c 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c | |||
@@ -398,6 +398,15 @@ static int sun8i_mixer_bind(struct device *dev, struct device *master, | |||
398 | ret = PTR_ERR(mixer->mod_clk); | 398 | ret = PTR_ERR(mixer->mod_clk); |
399 | goto err_disable_bus_clk; | 399 | goto err_disable_bus_clk; |
400 | } | 400 | } |
401 | |||
402 | /* | ||
403 | * It seems that we need to enforce that rate for whatever | ||
404 | * reason for the mixer to be functional. Make sure it's the | ||
405 | * case. | ||
406 | */ | ||
407 | if (mixer->cfg->mod_rate) | ||
408 | clk_set_rate(mixer->mod_clk, mixer->cfg->mod_rate); | ||
409 | |||
401 | clk_prepare_enable(mixer->mod_clk); | 410 | clk_prepare_enable(mixer->mod_clk); |
402 | 411 | ||
403 | list_add_tail(&mixer->engine.list, &drv->engine_list); | 412 | list_add_tail(&mixer->engine.list, &drv->engine_list); |
@@ -469,15 +478,27 @@ static int sun8i_mixer_remove(struct platform_device *pdev) | |||
469 | return 0; | 478 | return 0; |
470 | } | 479 | } |
471 | 480 | ||
481 | static const struct sun8i_mixer_cfg sun8i_a83t_mixer0_cfg = { | ||
482 | .ccsc = 0, | ||
483 | .scaler_mask = 0xf, | ||
484 | .ui_num = 3, | ||
485 | .vi_num = 1, | ||
486 | }; | ||
487 | |||
472 | static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = { | 488 | static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = { |
473 | .vi_num = 2, | 489 | .vi_num = 2, |
474 | .ui_num = 1, | 490 | .ui_num = 1, |
475 | .scaler_mask = 0x3, | 491 | .scaler_mask = 0x3, |
476 | .ccsc = 0, | 492 | .ccsc = 0, |
493 | .mod_rate = 150000000, | ||
477 | }; | 494 | }; |
478 | 495 | ||
479 | static const struct of_device_id sun8i_mixer_of_table[] = { | 496 | static const struct of_device_id sun8i_mixer_of_table[] = { |
480 | { | 497 | { |
498 | .compatible = "allwinner,sun8i-a83t-de2-mixer-0", | ||
499 | .data = &sun8i_a83t_mixer0_cfg, | ||
500 | }, | ||
501 | { | ||
481 | .compatible = "allwinner,sun8i-v3s-de2-mixer", | 502 | .compatible = "allwinner,sun8i-v3s-de2-mixer", |
482 | .data = &sun8i_v3s_mixer_cfg, | 503 | .data = &sun8i_v3s_mixer_cfg, |
483 | }, | 504 | }, |
diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h index bc58040a88f9..f34e70c42adf 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.h +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h | |||
@@ -121,12 +121,15 @@ struct de2_fmt_info { | |||
121 | * Set value to 0 if this is first mixer or second mixer with VEP support. | 121 | * Set value to 0 if this is first mixer or second mixer with VEP support. |
122 | * Set value to 1 if this is second mixer without VEP support. Other values | 122 | * Set value to 1 if this is second mixer without VEP support. Other values |
123 | * are invalid. | 123 | * are invalid. |
124 | * @mod_rate: module clock rate that needs to be set in order to have | ||
125 | * a functional block. | ||
124 | */ | 126 | */ |
125 | struct sun8i_mixer_cfg { | 127 | struct sun8i_mixer_cfg { |
126 | int vi_num; | 128 | int vi_num; |
127 | int ui_num; | 129 | int ui_num; |
128 | int scaler_mask; | 130 | int scaler_mask; |
129 | int ccsc; | 131 | int ccsc; |
132 | unsigned long mod_rate; | ||
130 | }; | 133 | }; |
131 | 134 | ||
132 | struct sun8i_mixer { | 135 | struct sun8i_mixer { |
diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig index 90c5bd5ef81b..b0e567d416b3 100644 --- a/drivers/gpu/drm/tinydrm/Kconfig +++ b/drivers/gpu/drm/tinydrm/Kconfig | |||
@@ -52,3 +52,13 @@ config TINYDRM_ST7586 | |||
52 | * LEGO MINDSTORMS EV3 | 52 | * LEGO MINDSTORMS EV3 |
53 | 53 | ||
54 | If M is selected the module will be called st7586. | 54 | If M is selected the module will be called st7586. |
55 | |||
56 | config TINYDRM_ST7735R | ||
57 | tristate "DRM support for Sitronix ST7735R display panels" | ||
58 | depends on DRM_TINYDRM && SPI | ||
59 | select TINYDRM_MIPI_DBI | ||
60 | help | ||
61 | DRM driver Sitronix ST7735R with one of the following LCDs: | ||
62 | * JD-T18003-T01 1.8" 128x160 TFT | ||
63 | |||
64 | If M is selected the module will be called st7735r. | ||
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile index 8aeee532474f..49a111929724 100644 --- a/drivers/gpu/drm/tinydrm/Makefile +++ b/drivers/gpu/drm/tinydrm/Makefile | |||
@@ -8,3 +8,4 @@ obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o | |||
8 | obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o | 8 | obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o |
9 | obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o | 9 | obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o |
10 | obj-$(CONFIG_TINYDRM_ST7586) += st7586.o | 10 | obj-$(CONFIG_TINYDRM_ST7586) += st7586.o |
11 | obj-$(CONFIG_TINYDRM_ST7735R) += st7735r.o | ||
diff --git a/drivers/gpu/drm/tinydrm/ili9225.c b/drivers/gpu/drm/tinydrm/ili9225.c index e8f1b3af3852..c0cf49849302 100644 --- a/drivers/gpu/drm/tinydrm/ili9225.c +++ b/drivers/gpu/drm/tinydrm/ili9225.c | |||
@@ -391,13 +391,13 @@ static struct drm_driver ili9225_driver = { | |||
391 | }; | 391 | }; |
392 | 392 | ||
393 | static const struct of_device_id ili9225_of_match[] = { | 393 | static const struct of_device_id ili9225_of_match[] = { |
394 | { .compatible = "ilitek,ili9225-2.2in-176x220" }, | 394 | { .compatible = "vot,v220hf01a-t" }, |
395 | {}, | 395 | {}, |
396 | }; | 396 | }; |
397 | MODULE_DEVICE_TABLE(of, ili9225_of_match); | 397 | MODULE_DEVICE_TABLE(of, ili9225_of_match); |
398 | 398 | ||
399 | static const struct spi_device_id ili9225_id[] = { | 399 | static const struct spi_device_id ili9225_id[] = { |
400 | { "ili9225-2.2in-176x220", 0 }, | 400 | { "v220hf01a-t", 0 }, |
401 | { }, | 401 | { }, |
402 | }; | 402 | }; |
403 | MODULE_DEVICE_TABLE(spi, ili9225_id); | 403 | MODULE_DEVICE_TABLE(spi, ili9225_id); |
diff --git a/drivers/gpu/drm/tinydrm/st7735r.c b/drivers/gpu/drm/tinydrm/st7735r.c new file mode 100644 index 000000000000..98ff447f40b4 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/st7735r.c | |||
@@ -0,0 +1,215 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0+ | ||
2 | /* | ||
3 | * DRM driver for Sitronix ST7735R panels | ||
4 | * | ||
5 | * Copyright 2017 David Lechner <david@lechnology.com> | ||
6 | */ | ||
7 | |||
8 | #include <linux/delay.h> | ||
9 | #include <linux/dma-buf.h> | ||
10 | #include <linux/gpio/consumer.h> | ||
11 | #include <linux/module.h> | ||
12 | #include <linux/property.h> | ||
13 | #include <linux/spi/spi.h> | ||
14 | #include <video/mipi_display.h> | ||
15 | |||
16 | #include <drm/drm_fb_helper.h> | ||
17 | #include <drm/drm_gem_framebuffer_helper.h> | ||
18 | #include <drm/tinydrm/mipi-dbi.h> | ||
19 | #include <drm/tinydrm/tinydrm-helpers.h> | ||
20 | |||
21 | #define ST7735R_FRMCTR1 0xb1 | ||
22 | #define ST7735R_FRMCTR2 0xb2 | ||
23 | #define ST7735R_FRMCTR3 0xb3 | ||
24 | #define ST7735R_INVCTR 0xb4 | ||
25 | #define ST7735R_PWCTR1 0xc0 | ||
26 | #define ST7735R_PWCTR2 0xc1 | ||
27 | #define ST7735R_PWCTR3 0xc2 | ||
28 | #define ST7735R_PWCTR4 0xc3 | ||
29 | #define ST7735R_PWCTR5 0xc4 | ||
30 | #define ST7735R_VMCTR1 0xc5 | ||
31 | #define ST7735R_GAMCTRP1 0xe0 | ||
32 | #define ST7735R_GAMCTRN1 0xe1 | ||
33 | |||
34 | #define ST7735R_MY BIT(7) | ||
35 | #define ST7735R_MX BIT(6) | ||
36 | #define ST7735R_MV BIT(5) | ||
37 | |||
38 | static void jd_t18003_t01_pipe_enable(struct drm_simple_display_pipe *pipe, | ||
39 | struct drm_crtc_state *crtc_state) | ||
40 | { | ||
41 | struct tinydrm_device *tdev = pipe_to_tinydrm(pipe); | ||
42 | struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev); | ||
43 | struct device *dev = tdev->drm->dev; | ||
44 | int ret; | ||
45 | u8 addr_mode; | ||
46 | |||
47 | DRM_DEBUG_KMS("\n"); | ||
48 | |||
49 | mipi_dbi_hw_reset(mipi); | ||
50 | |||
51 | ret = mipi_dbi_command(mipi, MIPI_DCS_SOFT_RESET); | ||
52 | if (ret) { | ||
53 | DRM_DEV_ERROR(dev, "Error sending command %d\n", ret); | ||
54 | return; | ||
55 | } | ||
56 | |||
57 | msleep(150); | ||
58 | |||
59 | mipi_dbi_command(mipi, MIPI_DCS_EXIT_SLEEP_MODE); | ||
60 | msleep(500); | ||
61 | |||
62 | mipi_dbi_command(mipi, ST7735R_FRMCTR1, 0x01, 0x2c, 0x2d); | ||
63 | mipi_dbi_command(mipi, ST7735R_FRMCTR2, 0x01, 0x2c, 0x2d); | ||
64 | mipi_dbi_command(mipi, ST7735R_FRMCTR3, 0x01, 0x2c, 0x2d, 0x01, 0x2c, | ||
65 | 0x2d); | ||
66 | mipi_dbi_command(mipi, ST7735R_INVCTR, 0x07); | ||
67 | mipi_dbi_command(mipi, ST7735R_PWCTR1, 0xa2, 0x02, 0x84); | ||
68 | mipi_dbi_command(mipi, ST7735R_PWCTR2, 0xc5); | ||
69 | mipi_dbi_command(mipi, ST7735R_PWCTR3, 0x0a, 0x00); | ||
70 | mipi_dbi_command(mipi, ST7735R_PWCTR4, 0x8a, 0x2a); | ||
71 | mipi_dbi_command(mipi, ST7735R_PWCTR5, 0x8a, 0xee); | ||
72 | mipi_dbi_command(mipi, ST7735R_VMCTR1, 0x0e); | ||
73 | mipi_dbi_command(mipi, MIPI_DCS_EXIT_INVERT_MODE); | ||
74 | switch (mipi->rotation) { | ||
75 | default: | ||
76 | addr_mode = ST7735R_MX | ST7735R_MY; | ||
77 | break; | ||
78 | case 90: | ||
79 | addr_mode = ST7735R_MX | ST7735R_MV; | ||
80 | break; | ||
81 | case 180: | ||
82 | addr_mode = 0; | ||
83 | break; | ||
84 | case 270: | ||
85 | addr_mode = ST7735R_MY | ST7735R_MV; | ||
86 | break; | ||
87 | } | ||
88 | mipi_dbi_command(mipi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); | ||
89 | mipi_dbi_command(mipi, MIPI_DCS_SET_PIXEL_FORMAT, | ||
90 | MIPI_DCS_PIXEL_FMT_16BIT); | ||
91 | mipi_dbi_command(mipi, ST7735R_GAMCTRP1, 0x02, 0x1c, 0x07, 0x12, 0x37, | ||
92 | 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2b, 0x39, 0x00, 0x01, | ||
93 | 0x03, 0x10); | ||
94 | mipi_dbi_command(mipi, ST7735R_GAMCTRN1, 0x03, 0x1d, 0x07, 0x06, 0x2e, | ||
95 | 0x2c, 0x29, 0x2d, 0x2e, 0x2e, 0x37, 0x3f, 0x00, 0x00, | ||
96 | 0x02, 0x10); | ||
97 | mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_ON); | ||
98 | |||
99 | msleep(100); | ||
100 | |||
101 | mipi_dbi_command(mipi, MIPI_DCS_ENTER_NORMAL_MODE); | ||
102 | |||
103 | msleep(20); | ||
104 | |||
105 | mipi_dbi_pipe_enable(pipe, crtc_state); | ||
106 | } | ||
107 | |||
108 | static const struct drm_simple_display_pipe_funcs jd_t18003_t01_pipe_funcs = { | ||
109 | .enable = jd_t18003_t01_pipe_enable, | ||
110 | .disable = mipi_dbi_pipe_disable, | ||
111 | .update = tinydrm_display_pipe_update, | ||
112 | .prepare_fb = tinydrm_display_pipe_prepare_fb, | ||
113 | }; | ||
114 | |||
115 | static const struct drm_display_mode jd_t18003_t01_mode = { | ||
116 | TINYDRM_MODE(128, 160, 28, 35), | ||
117 | }; | ||
118 | |||
119 | DEFINE_DRM_GEM_CMA_FOPS(st7735r_fops); | ||
120 | |||
121 | static struct drm_driver st7735r_driver = { | ||
122 | .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | | ||
123 | DRIVER_ATOMIC, | ||
124 | .fops = &st7735r_fops, | ||
125 | TINYDRM_GEM_DRIVER_OPS, | ||
126 | .lastclose = drm_fb_helper_lastclose, | ||
127 | .debugfs_init = mipi_dbi_debugfs_init, | ||
128 | .name = "st7735r", | ||
129 | .desc = "Sitronix ST7735R", | ||
130 | .date = "20171128", | ||
131 | .major = 1, | ||
132 | .minor = 0, | ||
133 | }; | ||
134 | |||
135 | static const struct of_device_id st7735r_of_match[] = { | ||
136 | { .compatible = "jianda,jd-t18003-t01" }, | ||
137 | { }, | ||
138 | }; | ||
139 | MODULE_DEVICE_TABLE(of, st7735r_of_match); | ||
140 | |||
141 | static const struct spi_device_id st7735r_id[] = { | ||
142 | { "jd-t18003-t01", 0 }, | ||
143 | { }, | ||
144 | }; | ||
145 | MODULE_DEVICE_TABLE(spi, st7735r_id); | ||
146 | |||
147 | static int st7735r_probe(struct spi_device *spi) | ||
148 | { | ||
149 | struct device *dev = &spi->dev; | ||
150 | struct mipi_dbi *mipi; | ||
151 | struct gpio_desc *dc; | ||
152 | u32 rotation = 0; | ||
153 | int ret; | ||
154 | |||
155 | mipi = devm_kzalloc(dev, sizeof(*mipi), GFP_KERNEL); | ||
156 | if (!mipi) | ||
157 | return -ENOMEM; | ||
158 | |||
159 | mipi->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); | ||
160 | if (IS_ERR(mipi->reset)) { | ||
161 | DRM_DEV_ERROR(dev, "Failed to get gpio 'reset'\n"); | ||
162 | return PTR_ERR(mipi->reset); | ||
163 | } | ||
164 | |||
165 | dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW); | ||
166 | if (IS_ERR(dc)) { | ||
167 | DRM_DEV_ERROR(dev, "Failed to get gpio 'dc'\n"); | ||
168 | return PTR_ERR(dc); | ||
169 | } | ||
170 | |||
171 | mipi->backlight = tinydrm_of_find_backlight(dev); | ||
172 | if (IS_ERR(mipi->backlight)) | ||
173 | return PTR_ERR(mipi->backlight); | ||
174 | |||
175 | device_property_read_u32(dev, "rotation", &rotation); | ||
176 | |||
177 | ret = mipi_dbi_spi_init(spi, mipi, dc); | ||
178 | if (ret) | ||
179 | return ret; | ||
180 | |||
181 | /* Cannot read from Adafruit 1.8" display via SPI */ | ||
182 | mipi->read_commands = NULL; | ||
183 | |||
184 | ret = mipi_dbi_init(&spi->dev, mipi, &jd_t18003_t01_pipe_funcs, | ||
185 | &st7735r_driver, &jd_t18003_t01_mode, rotation); | ||
186 | if (ret) | ||
187 | return ret; | ||
188 | |||
189 | spi_set_drvdata(spi, mipi); | ||
190 | |||
191 | return devm_tinydrm_register(&mipi->tinydrm); | ||
192 | } | ||
193 | |||
194 | static void st7735r_shutdown(struct spi_device *spi) | ||
195 | { | ||
196 | struct mipi_dbi *mipi = spi_get_drvdata(spi); | ||
197 | |||
198 | tinydrm_shutdown(&mipi->tinydrm); | ||
199 | } | ||
200 | |||
201 | static struct spi_driver st7735r_spi_driver = { | ||
202 | .driver = { | ||
203 | .name = "st7735r", | ||
204 | .owner = THIS_MODULE, | ||
205 | .of_match_table = st7735r_of_match, | ||
206 | }, | ||
207 | .id_table = st7735r_id, | ||
208 | .probe = st7735r_probe, | ||
209 | .shutdown = st7735r_shutdown, | ||
210 | }; | ||
211 | module_spi_driver(st7735r_spi_driver); | ||
212 | |||
213 | MODULE_DESCRIPTION("Sitronix ST7735R DRM driver"); | ||
214 | MODULE_AUTHOR("David Lechner <david@lechnology.com>"); | ||
215 | MODULE_LICENSE("GPL"); | ||