From ccb667c35ffdea7a2bc1304750e6b22368cd55f7 Mon Sep 17 00:00:00 2001 From: Jan-Simon Möller Date: Tue, 4 Jun 2019 19:25:25 +0200 Subject: Fix rpi touchscreen support on master MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We seem to miss a few bits that changed between kernel 4.9 and kernel 4.14 regarding the 7'' touchscreen. One addition is the rpi-backlight.dtbo which we also need to use in our create-combined-dtb recipe for CI use. We also seem to require https://github.com/raspberrypi/linux/pull/2693#issue-217750943 . Thanks to Scott Murray for the help digging through this. v2 and v3: add devicetree changes v4 and v5: fix name of dtbo v6: move append to machine include - we need it as a global var v7: add to config.txt for sdcard boot case Bug-AGL: SPEC-2465 Change-Id: I2bb3cd974b74a790292e1f36ffdca034928e0bca Signed-off-by: Jan-Simon Möller --- ...ree-support-for-RaspberryPi-7-panel-over-.patch | 351 +++++++++++++++++ ...e-DSI-call-into-the-bridge-after-the-DSI-.patch | 102 +++++ ...-up-the-DSI-host-at-pdev-probe-time-not-c.patch | 217 +++++++++++ ...ackport-4.15-support-for-the-Raspberry-Pi.patch | 418 +++++++++++++++++++++ 4 files changed, 1088 insertions(+) create mode 100644 meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0001-Add-devicetree-support-for-RaspberryPi-7-panel-over-.patch create mode 100644 meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0002-drm-vc4-Make-DSI-call-into-the-bridge-after-the-DSI-.patch create mode 100644 meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0003-drm-vc4-Set-up-the-DSI-host-at-pdev-probe-time-not-c.patch create mode 100644 meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0004-drm-panel-Backport-4.15-support-for-the-Raspberry-Pi.patch (limited to 'meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi') diff --git a/meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0001-Add-devicetree-support-for-RaspberryPi-7-panel-over-.patch b/meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0001-Add-devicetree-support-for-RaspberryPi-7-panel-over-.patch new file mode 100644 index 000000000..fce687290 --- /dev/null +++ b/meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0001-Add-devicetree-support-for-RaspberryPi-7-panel-over-.patch @@ -0,0 +1,351 @@ +From 380fd9f0da44ed62dff59a7c7e06f1190b58aa64 Mon Sep 17 00:00:00 2001 +From: Kevin Quigley +Date: Mon, 24 Sep 2018 12:22:31 +0000 +Subject: [PATCH 1/4] Add devicetree support for RaspberryPi 7" panel over DSI + I2C + +--- + arch/arm/boot/dts/bcm2708-rpi-b-plus.dts | 5 +++ + arch/arm/boot/dts/bcm2708-rpi-b.dts | 6 +++ + arch/arm/boot/dts/bcm2708-rpi.dtsi | 21 ++++++++++ + arch/arm/boot/dts/bcm2709-rpi-2-b.dts | 5 +++ + arch/arm/boot/dts/bcm270x.dtsi | 39 +++++++++++++++++ + arch/arm/boot/dts/bcm2710-rpi-3-b.dts | 6 +++ + arch/arm/boot/dts/bcm2835-rpi-b-plus.dts | 5 +++ + arch/arm/boot/dts/bcm2835-rpi-b-rev2.dts | 5 +++ + arch/arm/boot/dts/bcm2835-rpi-b.dts | 5 +++ + arch/arm/boot/dts/bcm2835-rpi.dtsi | 2 + + arch/arm/boot/dts/bcm2836-rpi-2-b.dts | 5 +++ + arch/arm/boot/dts/bcm2837-rpi-3-b.dts | 5 +++ + arch/arm/boot/dts/bcm283x.dtsi | 16 ++++++- + arch/arm/boot/dts/overlays/Makefile | 1 + + .../boot/dts/overlays/rpi-7inch-overlay.dts | 42 +++++++++++++++++++ + 15 files changed, 167 insertions(+), 1 deletion(-) + create mode 100644 arch/arm/boot/dts/overlays/rpi-7inch-overlay.dts + +diff --git a/arch/arm/boot/dts/bcm2708-rpi-b-plus.dts b/arch/arm/boot/dts/bcm2708-rpi-b-plus.dts +index ef0beea3a3a3..83b95b9a6ec3 100644 +--- a/arch/arm/boot/dts/bcm2708-rpi-b-plus.dts ++++ b/arch/arm/boot/dts/bcm2708-rpi-b-plus.dts +@@ -121,3 +121,8 @@ + pwr_led_trigger = <&pwr_led>,"linux,default-trigger"; + }; + }; ++ ++&i2c_dsi { ++ gpios = <&gpio 28 0 ++ &gpio 29 0>; ++}; +diff --git a/arch/arm/boot/dts/bcm2708-rpi-b.dts b/arch/arm/boot/dts/bcm2708-rpi-b.dts +index dea70fae90e6..ea5de651d59d 100644 +--- a/arch/arm/boot/dts/bcm2708-rpi-b.dts ++++ b/arch/arm/boot/dts/bcm2708-rpi-b.dts +@@ -111,3 +111,9 @@ + act_led_trigger = <&act_led>,"linux,default-trigger"; + }; + }; ++ ++ ++&i2c_dsi { ++ gpios = <&gpio 2 0 ++ &gpio 3 0>; ++}; +diff --git a/arch/arm/boot/dts/bcm2708-rpi.dtsi b/arch/arm/boot/dts/bcm2708-rpi.dtsi +index b13632932192..2d8421f6681c 100644 +--- a/arch/arm/boot/dts/bcm2708-rpi.dtsi ++++ b/arch/arm/boot/dts/bcm2708-rpi.dtsi +@@ -132,6 +132,27 @@ + power-domains = <&power RPI_POWER_DOMAIN_USB>; + }; + ++&v3d { ++ power-domains = <&power RPI_POWER_DOMAIN_V3D>; ++}; ++ ++&vec { ++ power-domains = <&power RPI_POWER_DOMAIN_VEC>; ++ status = "okay"; ++}; ++ ++&dsi0 { ++ power-domains = <&power RPI_POWER_DOMAIN_DSI0>; ++ status = "okay"; ++}; ++ ++&dsi1 { ++ power-domains = <&power RPI_POWER_DOMAIN_DSI1>; ++ status = "okay"; ++}; ++ ++ ++ + &clocks { + firmware = <&firmware>; + }; +diff --git a/arch/arm/boot/dts/bcm2709-rpi-2-b.dts b/arch/arm/boot/dts/bcm2709-rpi-2-b.dts +index 34659505055a..c23f79395791 100644 +--- a/arch/arm/boot/dts/bcm2709-rpi-2-b.dts ++++ b/arch/arm/boot/dts/bcm2709-rpi-2-b.dts +@@ -122,3 +122,8 @@ + pwr_led_trigger = <&pwr_led>,"linux,default-trigger"; + }; + }; ++ ++&i2c_dsi { ++ gpios = <&gpio 28 0 ++ &gpio 29 0>; ++}; +diff --git a/arch/arm/boot/dts/bcm270x.dtsi b/arch/arm/boot/dts/bcm270x.dtsi +index f086ff0deec5..0b1ef47b18b0 100644 +--- a/arch/arm/boot/dts/bcm270x.dtsi ++++ b/arch/arm/boot/dts/bcm270x.dtsi +@@ -84,6 +84,26 @@ + status = "disabled"; + }; + ++ dsi1: dsi@7e700000 { ++ compatible = "brcm,bcm2835-dsi1"; ++ reg = <0x7e700000 0x8c>; ++ interrupts = <2 12>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ #clock-cells = <1>; ++ ++ clocks = <&clocks BCM2835_PLLD_DSI1>, ++ <&clocks BCM2835_CLOCK_DSI1E>, ++ <&clocks BCM2835_CLOCK_DSI1P>; ++ clock-names = "phy", "escape", "pixel"; ++ ++ clock-output-names = "dsi1_byte", ++ "dsi1_ddr2", ++ "dsi1_ddr"; ++ ++ status = "disabled"; ++ }; ++ + firmwarekms: firmwarekms@7e600000 { + compatible = "raspberrypi,rpi-firmware-kms"; + /* SMI interrupt reg */ +@@ -93,6 +113,21 @@ + status = "disabled"; + }; + ++ i2c_dsi: i2cdsi { ++ /* We have to use i2c-gpio because the ++ * firmware is also polling another device ++ * using the only hardware I2C bus that could ++ * connect to these pins. ++ */ ++ compatible = "i2c-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ status = "disabled"; ++ gpios = <&gpio 28 0 ++ &gpio 29 0>; ++ ++ }; ++ + smi: smi@7e600000 { + compatible = "brcm,bcm2835-smi"; + reg = <0x7e600000 0x100>; +@@ -187,3 +222,7 @@ + &vc4 { + status = "disabled"; + }; ++ ++&dsi1 { ++ power-domains = <&power RPI_POWER_DOMAIN_DSI1>; ++}; +diff --git a/arch/arm/boot/dts/bcm2710-rpi-3-b.dts b/arch/arm/boot/dts/bcm2710-rpi-3-b.dts +index 37aed344cbbf..6ec9b28999f3 100644 +--- a/arch/arm/boot/dts/bcm2710-rpi-3-b.dts ++++ b/arch/arm/boot/dts/bcm2710-rpi-3-b.dts +@@ -190,3 +190,9 @@ + pwr_led_trigger = <&pwr_led>,"linux,default-trigger"; + }; + }; ++ ++&i2c_dsi { ++ gpios = <&gpio 44 0 ++ &gpio 45 0>; ++}; ++ +diff --git a/arch/arm/boot/dts/bcm2835-rpi-b-plus.dts b/arch/arm/boot/dts/bcm2835-rpi-b-plus.dts +index a462de2ce37e..346efbd4a95f 100644 +--- a/arch/arm/boot/dts/bcm2835-rpi-b-plus.dts ++++ b/arch/arm/boot/dts/bcm2835-rpi-b-plus.dts +@@ -109,3 +109,8 @@ + pinctrl-0 = <&uart0_gpio14>; + status = "okay"; + }; ++ ++&i2c_dsi { ++ gpios = <&gpio 28 0 ++ &gpio 29 0>; ++}; +diff --git a/arch/arm/boot/dts/bcm2835-rpi-b-rev2.dts b/arch/arm/boot/dts/bcm2835-rpi-b-rev2.dts +index 264c84e94329..8b9ebf3670c2 100644 +--- a/arch/arm/boot/dts/bcm2835-rpi-b-rev2.dts ++++ b/arch/arm/boot/dts/bcm2835-rpi-b-rev2.dts +@@ -102,3 +102,8 @@ + pinctrl-0 = <&uart0_gpio14>; + status = "okay"; + }; ++ ++&i2c_dsi { ++ gpios = <&gpio 2 0 ++ &gpio 3 0>; ++}; +diff --git a/arch/arm/boot/dts/bcm2835-rpi-b.dts b/arch/arm/boot/dts/bcm2835-rpi-b.dts +index 03e3a7a7dc5e..4020e79fc989 100644 +--- a/arch/arm/boot/dts/bcm2835-rpi-b.dts ++++ b/arch/arm/boot/dts/bcm2835-rpi-b.dts +@@ -97,3 +97,8 @@ + pinctrl-0 = <&uart0_gpio14>; + status = "okay"; + }; ++ ++&i2c_dsi { ++ gpios = <&gpio 28 0 ++ &gpio 29 0>; ++}; +diff --git a/arch/arm/boot/dts/bcm2835-rpi.dtsi b/arch/arm/boot/dts/bcm2835-rpi.dtsi +index 356d4e6a17e5..0599f0a27e5f 100644 +--- a/arch/arm/boot/dts/bcm2835-rpi.dtsi ++++ b/arch/arm/boot/dts/bcm2835-rpi.dtsi +@@ -101,10 +101,12 @@ + + &dsi0 { + power-domains = <&power RPI_POWER_DOMAIN_DSI0>; ++ status = "okay"; + }; + + &dsi1 { + power-domains = <&power RPI_POWER_DOMAIN_DSI1>; ++ status = "okay"; + }; + + &csi0 { +diff --git a/arch/arm/boot/dts/bcm2836-rpi-2-b.dts b/arch/arm/boot/dts/bcm2836-rpi-2-b.dts +index 59e70f4a19e5..6c4c9c8d8ed0 100644 +--- a/arch/arm/boot/dts/bcm2836-rpi-2-b.dts ++++ b/arch/arm/boot/dts/bcm2836-rpi-2-b.dts +@@ -47,3 +47,8 @@ + pinctrl-0 = <&uart0_gpio14>; + status = "okay"; + }; ++ ++&i2c_dsi { ++ gpios = <&gpio 28 0 ++ &gpio 29 0>; ++}; +diff --git a/arch/arm/boot/dts/bcm2837-rpi-3-b.dts b/arch/arm/boot/dts/bcm2837-rpi-3-b.dts +index 8703d006a206..c99f70cefa61 100644 +--- a/arch/arm/boot/dts/bcm2837-rpi-3-b.dts ++++ b/arch/arm/boot/dts/bcm2837-rpi-3-b.dts +@@ -56,3 +56,8 @@ + status = "okay"; + bus-width = <4>; + }; ++ ++&i2c_dsi { ++ gpios = <&gpio 44 0 ++ &gpio 45 0>; ++}; +diff --git a/arch/arm/boot/dts/bcm283x.dtsi b/arch/arm/boot/dts/bcm283x.dtsi +index 22eca6fad427..3ee99963f827 100644 +--- a/arch/arm/boot/dts/bcm283x.dtsi ++++ b/arch/arm/boot/dts/bcm283x.dtsi +@@ -551,7 +551,6 @@ + clock-output-names = "dsi1_byte", + "dsi1_ddr2", + "dsi1_ddr"; +- + status = "disabled"; + }; + +@@ -652,6 +651,21 @@ + vc4: gpu { + compatible = "brcm,bcm2835-vc4"; + }; ++ ++ i2c_dsi: i2cdsi { ++ /* We have to use i2c-gpio because the ++ * firmware is also polling another device ++ * using the only hardware I2C bus that could ++ * connect to these pins. ++ */ ++ compatible = "i2c-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ status = "disabled"; ++ gpios = <&gpio 28 0 ++ &gpio 29 0>; ++ ++ }; + }; + + clocks { +diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile +index e0bdd84d21c5..f983d0180b8b 100644 +--- a/arch/arm/boot/dts/overlays/Makefile ++++ b/arch/arm/boot/dts/overlays/Makefile +@@ -110,6 +110,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \ + rpi-cirrus-wm5102.dtbo \ + rpi-dac.dtbo \ + rpi-display.dtbo \ ++ rpi-7inch.dtbo \ + rpi-ft5406.dtbo \ + rpi-poe.dtbo \ + rpi-proto.dtbo \ +diff --git a/arch/arm/boot/dts/overlays/rpi-7inch-overlay.dts b/arch/arm/boot/dts/overlays/rpi-7inch-overlay.dts +new file mode 100644 +index 000000000000..3bb882d79086 +--- /dev/null ++++ b/arch/arm/boot/dts/overlays/rpi-7inch-overlay.dts +@@ -0,0 +1,42 @@ ++/* ++ * Device Tree overlay for RaspberryPi 7" Touchscreen panel ++ * ++ */ ++ ++/dts-v1/; ++/plugin/; ++ ++/ { ++ compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709"; ++ ++ fragment@1 { ++ target = <&dsi1>; ++ __overlay__ { ++ #address-cells = <1>; size-cells = <0>; ++ status = "okay"; ++ port { ++ dsi_out_port: endpoint { ++ remote-endpoint = <&panel_dsi_port>; ++ }; ++ }; ++ }; ++ }; ++ ++ fragment@2 { ++ target = <&i2c_dsi>; ++ __overlay__ { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ status = "okay"; ++ lcd@45 { ++ compatible = "raspberrypi,7inch-touchscreen-panel"; ++ reg = <0x45>; ++ port { ++ panel_dsi_port: endpoint { ++ remote-endpoint = <&dsi_out_port>; ++ }; ++ }; ++ }; ++ }; ++ }; ++}; +-- +2.21.0 + diff --git a/meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0002-drm-vc4-Make-DSI-call-into-the-bridge-after-the-DSI-.patch b/meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0002-drm-vc4-Make-DSI-call-into-the-bridge-after-the-DSI-.patch new file mode 100644 index 000000000..13d821418 --- /dev/null +++ b/meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0002-drm-vc4-Make-DSI-call-into-the-bridge-after-the-DSI-.patch @@ -0,0 +1,102 @@ +From ab06860f2361a3750ec3a62347e0fdd7873cb30d Mon Sep 17 00:00:00 2001 +From: Kevin Quigley +Date: Mon, 24 Sep 2018 18:07:40 +0000 +Subject: [PATCH 2/4] drm/vc4: Make DSI call into the bridge after the DSI link + is enabled. bring this patch https://patchwork.kernel.org/patch/10480937/ to + Linux rpi-4.14.y + +This allows panels or bridges that need to send DSI commands during +pre_enable() to successfully send them. We delay DISP0 (aka the +actual display) enabling until after pre_enable so that pixels aren't +streaming before then. + +v2: Just clear out the encoder->bridge value to disable the midlayer + calls (idea by Andrzej Hajda). +--- + drivers/gpu/drm/vc4/vc4_dsi.c | 48 ++++++++++++++++++++++++----------- + 1 file changed, 33 insertions(+), 15 deletions(-) + +diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c +index a3f416cbd1d6..3fa2db18d70f 100644 +--- a/drivers/gpu/drm/vc4/vc4_dsi.c ++++ b/drivers/gpu/drm/vc4/vc4_dsi.c +@@ -814,7 +814,9 @@ static void vc4_dsi_encoder_disable(struct drm_encoder *encoder) + struct vc4_dsi *dsi = vc4_encoder->dsi; + struct device *dev = &dsi->pdev->dev; + ++ drm_bridge_disable(dsi->bridge); + vc4_dsi_ulps(dsi, true); ++ drm_bridge_post_disable(dsi->bridge); + + clk_disable_unprepare(dsi->pll_phy_clock); + clk_disable_unprepare(dsi->escape_clock); +@@ -1089,21 +1091,6 @@ static void vc4_dsi_encoder_enable(struct drm_encoder *encoder) + /* Display reset sequence timeout */ + DSI_PORT_WRITE(PR_TO_CNT, 100000); + +- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) { +- DSI_PORT_WRITE(DISP0_CTRL, +- VC4_SET_FIELD(dsi->divider, +- DSI_DISP0_PIX_CLK_DIV) | +- VC4_SET_FIELD(dsi->format, DSI_DISP0_PFORMAT) | +- VC4_SET_FIELD(DSI_DISP0_LP_STOP_PERFRAME, +- DSI_DISP0_LP_STOP_CTRL) | +- DSI_DISP0_ST_END | +- DSI_DISP0_ENABLE); +- } else { +- DSI_PORT_WRITE(DISP0_CTRL, +- DSI_DISP0_COMMAND_MODE | +- DSI_DISP0_ENABLE); +- } +- + /* Set up DISP1 for transferring long command payloads through + * the pixfifo. + */ +@@ -1128,6 +1115,25 @@ static void vc4_dsi_encoder_enable(struct drm_encoder *encoder) + + vc4_dsi_ulps(dsi, false); + ++ drm_bridge_pre_enable(dsi->bridge); ++ ++ if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) { ++ DSI_PORT_WRITE(DISP0_CTRL, ++ VC4_SET_FIELD(dsi->divider, ++ DSI_DISP0_PIX_CLK_DIV) | ++ VC4_SET_FIELD(dsi->format, DSI_DISP0_PFORMAT) | ++ VC4_SET_FIELD(DSI_DISP0_LP_STOP_PERFRAME, ++ DSI_DISP0_LP_STOP_CTRL) | ++ DSI_DISP0_ST_END | ++ DSI_DISP0_ENABLE); ++ } else { ++ DSI_PORT_WRITE(DISP0_CTRL, ++ DSI_DISP0_COMMAND_MODE | ++ DSI_DISP0_ENABLE); ++ } ++ ++ drm_bridge_enable(dsi->bridge); ++ + if (debug_dump_regs) { + DRM_INFO("DSI regs after:\n"); + vc4_dsi_dump_regs(dsi); +@@ -1656,6 +1662,18 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) + + dev_set_drvdata(dev, dsi); + ++ ret = drm_bridge_attach(dsi->encoder, dsi->bridge, NULL); ++ if (ret) { ++ dev_err(dev, "bridge attach failed: %d\n", ret); ++ return ret; ++ } ++ /* Disable the atomic helper calls into the bridge. We ++ * manually call the bridge pre_enable / enable / etc. calls ++ * from our driver, since we need to sequence them within the ++ * encoder's enable/disable paths. ++ */ ++ dsi->encoder->bridge = NULL; ++ + pm_runtime_enable(dev); + + return 0; +-- +2.21.0 + diff --git a/meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0003-drm-vc4-Set-up-the-DSI-host-at-pdev-probe-time-not-c.patch b/meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0003-drm-vc4-Set-up-the-DSI-host-at-pdev-probe-time-not-c.patch new file mode 100644 index 000000000..0e6e068fe --- /dev/null +++ b/meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0003-drm-vc4-Set-up-the-DSI-host-at-pdev-probe-time-not-c.patch @@ -0,0 +1,217 @@ +From 2ccd080392adfb1f077f2f4d289651b6b15e0951 Mon Sep 17 00:00:00 2001 +From: Kevin Quigley +Date: Mon, 24 Sep 2018 18:09:52 +0000 +Subject: [PATCH 3/4] drm/vc4: Set up the DSI host at pdev probe time, not + component bind. bring https://patchwork.kernel.org/patch/9902623/ to Linux + 4.14.y + +We need the following things to happen in sequence: + +DSI host creation +DSI device creation in the panel driver (needs DSI host) +DSI device attach from panel to host. +DSI drm_panel_add() +DSI encoder creation +DSI encoder's DRM panel/bridge attach + +Unless we allow device creation while the host isn't up yet, we need +to break the -EPROBE_DEFER deadlock between the panel driver looking +up the host and the host driver looking up the panel. We can do so by +moving the DSI host creation outside of the component bind loop, and +the panel/bridge lookup/attach into the component bind process. +--- + drivers/gpu/drm/vc4/vc4_dsi.c | 93 ++++++++++++++++++++--------------- + 1 file changed, 53 insertions(+), 40 deletions(-) + +diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c +index 3fa2db18d70f..faf38f17ec26 100644 +--- a/drivers/gpu/drm/vc4/vc4_dsi.c ++++ b/drivers/gpu/drm/vc4/vc4_dsi.c +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -504,7 +505,6 @@ struct vc4_dsi { + struct mipi_dsi_host dsi_host; + struct drm_encoder *encoder; + struct drm_bridge *bridge; +- bool is_panel_bridge; + + void __iomem *regs; + +@@ -1300,7 +1300,6 @@ static int vc4_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) + { + struct vc4_dsi *dsi = host_to_dsi(host); +- int ret = 0; + + dsi->lanes = device->lanes; + dsi->channel = device->channel; +@@ -1335,33 +1334,12 @@ static int vc4_dsi_host_attach(struct mipi_dsi_host *host, + return 0; + } + +- dsi->bridge = of_drm_find_bridge(device->dev.of_node); +- if (!dsi->bridge) { +- struct drm_panel *panel = +- of_drm_find_panel(device->dev.of_node); +- +- dsi->bridge = drm_panel_bridge_add(panel, +- DRM_MODE_CONNECTOR_DSI); +- if (IS_ERR(dsi->bridge)) { +- ret = PTR_ERR(dsi->bridge); +- dsi->bridge = NULL; +- return ret; +- } +- dsi->is_panel_bridge = true; +- } +- +- return drm_bridge_attach(dsi->encoder, dsi->bridge, NULL); ++ return 0; + } + + static int vc4_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) + { +- struct vc4_dsi *dsi = host_to_dsi(host); +- +- if (dsi->is_panel_bridge) { +- drm_panel_bridge_remove(dsi->bridge); +- dsi->bridge = NULL; +- } + + return 0; + } +@@ -1525,16 +1503,13 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = dev_get_drvdata(master); + struct vc4_dev *vc4 = to_vc4_dev(drm); +- struct vc4_dsi *dsi; ++ struct vc4_dsi *dsi = dev_get_drvdata(dev); + struct vc4_dsi_encoder *vc4_dsi_encoder; ++ struct drm_panel *panel; + const struct of_device_id *match; + dma_cap_mask_t dma_mask; + int ret; + +- dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); +- if (!dsi) +- return -ENOMEM; +- + match = of_match_device(vc4_dsi_dt_match, dev); + if (!match) + return -ENODEV; +@@ -1549,7 +1524,6 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) + vc4_dsi_encoder->dsi = dsi; + dsi->encoder = &vc4_dsi_encoder->base.base; + +- dsi->pdev = pdev; + dsi->regs = vc4_ioremap_regs(pdev, 0); + if (IS_ERR(dsi->regs)) + return PTR_ERR(dsi->regs); +@@ -1637,6 +1611,18 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) + return ret; + } + ++ ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0, ++ &panel, &dsi->bridge); ++ if (ret) ++ return ret; ++ ++ if (panel) { ++ dsi->bridge = devm_drm_panel_bridge_add(dev, panel, ++ DRM_MODE_CONNECTOR_DSI); ++ if (IS_ERR(dsi->bridge)) ++ return PTR_ERR(dsi->bridge); ++ } ++ + /* The esc clock rate is supposed to always be 100Mhz. */ + ret = clk_set_rate(dsi->escape_clock, 100 * 1000000); + if (ret) { +@@ -1655,13 +1641,6 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) + DRM_MODE_ENCODER_DSI, NULL); + drm_encoder_helper_add(dsi->encoder, &vc4_dsi_encoder_helper_funcs); + +- dsi->dsi_host.ops = &vc4_dsi_host_ops; +- dsi->dsi_host.dev = dev; +- +- mipi_dsi_host_register(&dsi->dsi_host); +- +- dev_set_drvdata(dev, dsi); +- + ret = drm_bridge_attach(dsi->encoder, dsi->bridge, NULL); + if (ret) { + dev_err(dev, "bridge attach failed: %d\n", ret); +@@ -1672,6 +1651,7 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) + * from our driver, since we need to sequence them within the + * encoder's enable/disable paths. + */ ++ + dsi->encoder->bridge = NULL; + + pm_runtime_enable(dev); +@@ -1690,8 +1670,6 @@ static void vc4_dsi_unbind(struct device *dev, struct device *master, + + vc4_dsi_encoder_destroy(dsi->encoder); + +- mipi_dsi_host_unregister(&dsi->dsi_host); +- + if (dsi->port == 1) + vc4->dsi1 = NULL; + } +@@ -1703,12 +1681,47 @@ static const struct component_ops vc4_dsi_ops = { + + static int vc4_dsi_dev_probe(struct platform_device *pdev) + { +- return component_add(&pdev->dev, &vc4_dsi_ops); ++ struct device *dev = &pdev->dev; ++ struct vc4_dsi *dsi; ++ int ret; ++ ++ dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); ++ if (!dsi) ++ return -ENOMEM; ++ dev_set_drvdata(dev, dsi); ++ ++ dsi->pdev = pdev; ++ ++ /* Note, the initialization sequence for DSI and panels is ++ * tricky. The component bind above won't get past its ++ * -EPROBE_DEFER until the panel/bridge probes. The ++ * panel/bridge will return -EPROBE_DEFER until it has a ++ * mipi_dsi_host to register its device to. So, we register ++ * the host during pdev probe time, so vc4 as a whole can then ++ * -EPROBE_DEFER its component bind process until the panel ++ * successfully attaches. ++ */ ++ dsi->dsi_host.ops = &vc4_dsi_host_ops; ++ dsi->dsi_host.dev = dev; ++ mipi_dsi_host_register(&dsi->dsi_host); ++ ++ ret = component_add(&pdev->dev, &vc4_dsi_ops); ++ if (ret) { ++ mipi_dsi_host_unregister(&dsi->dsi_host); ++ return ret; ++ } ++ ++ return 0; + } + + static int vc4_dsi_dev_remove(struct platform_device *pdev) + { ++ struct device *dev = &pdev->dev; ++ struct vc4_dsi *dsi = dev_get_drvdata(dev); ++ + component_del(&pdev->dev, &vc4_dsi_ops); ++ mipi_dsi_host_unregister(&dsi->dsi_host); ++ + return 0; + } + +-- +2.21.0 + diff --git a/meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0004-drm-panel-Backport-4.15-support-for-the-Raspberry-Pi.patch b/meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0004-drm-panel-Backport-4.15-support-for-the-Raspberry-Pi.patch new file mode 100644 index 000000000..17c32750b --- /dev/null +++ b/meta-agl-bsp/meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi/dsi/0004-drm-panel-Backport-4.15-support-for-the-Raspberry-Pi.patch @@ -0,0 +1,418 @@ +From 7f745c7e13b50911424b9bbb4181d4dc72ef155f Mon Sep 17 00:00:00 2001 +From: Kevin Quigley +Date: Mon, 24 Sep 2018 12:14:41 +0000 +Subject: [PATCH 4/4] drm/panel: Backport 4.15 support for the Raspberry Pi 7" + Touchscreen + +--- + .../drm/panel/panel-raspberrypi-touchscreen.c | 242 ++++++++---------- + 1 file changed, 111 insertions(+), 131 deletions(-) + +diff --git a/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c +index 128523d8d518..2c9c9722734f 100644 +--- a/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c ++++ b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c +@@ -1,5 +1,5 @@ + /* +- * Copyright © 2016 Broadcom ++ * Copyright © 2016-2017 Broadcom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as +@@ -30,14 +30,15 @@ + */ + + /** +- * DOC: Raspberry Pi 7" touchscreen panel driver. ++ * Raspberry Pi 7" touchscreen panel driver. + * + * The 7" touchscreen consists of a DPI LCD panel, a Toshiba + * TC358762XBG DSI-DPI bridge, and an I2C-connected Atmel ATTINY88-MUR +- * controlling power management, the LCD PWM, and the touchscreen. ++ * controlling power management, the LCD PWM, and initial register ++ * setup of the Tohsiba. + * +- * This driver presents this device as a MIPI DSI panel to the DRM +- * driver, and should expose the touchscreen as a HID device. ++ * This driver controls the TC358762 and ATTINY88, presenting a DSI ++ * device with a drm_panel. + */ + + #include +@@ -58,10 +59,12 @@ + #include + #include + ++#define RPI_DSI_DRIVER_NAME "rpi-ts-dsi" ++ + /* I2C registers of the Atmel microcontroller. */ + enum REG_ADDR { + REG_ID = 0x80, +- REG_PORTA, // BIT(2) for horizontal flip, BIT(3) for vertical flip ++ REG_PORTA, /* BIT(2) for horizontal flip, BIT(3) for vertical flip */ + REG_PORTB, + REG_PORTC, + REG_PORTD, +@@ -81,9 +84,6 @@ enum REG_ADDR { + REG_ID2, + }; + +-/* We only turn the PWM on or off, without varying values. */ +-#define RPI_TOUCHSCREEN_MAX_BRIGHTNESS 1 +- + /* DSI D-PHY Layer Registers */ + #define D0W_DPHYCONTTX 0x0004 + #define CLW_DPHYCONTRX 0x0020 +@@ -100,8 +100,6 @@ enum REG_ADDR { + #define PPI_BUSYPPI 0x0108 + #define PPI_LINEINITCNT 0x0110 + #define PPI_LPTXTIMECNT 0x0114 +-//#define PPI_LANEENABLE 0x0134 +-//#define PPI_TX_RX_TA 0x013C + #define PPI_CLS_ATMR 0x0140 + #define PPI_D0S_ATMR 0x0144 + #define PPI_D1S_ATMR 0x0148 +@@ -197,43 +195,23 @@ enum REG_ADDR { + struct rpi_touchscreen { + struct drm_panel base; + struct mipi_dsi_device *dsi; +- struct i2c_client *bridge_i2c; +- +- /* Version of the firmware on the bridge chip */ +- int atmel_ver; ++ struct i2c_client *i2c; + }; + + static const struct drm_display_mode rpi_touchscreen_modes[] = { + { +- /* The DSI PLL can only integer divide from the 2Ghz +- * PLLD, giving us few choices. We pick a divide by 3 +- * as our DSI HS clock, giving us a pixel clock of +- * that divided by 24 bits. Pad out HFP to get our +- * panel to refresh at 60Hz, even if that doesn't +- * match the datasheet. +- */ +-#define PIXEL_CLOCK ((2000000000 / 3) / 24) +-#define VREFRESH 60 +-#define VTOTAL (480 + 7 + 2 + 21) +-#define HACT 800 +-#define HSW 2 +-#define HBP 46 +-#define HFP ((PIXEL_CLOCK / (VTOTAL * VREFRESH)) - (HACT + HSW + HBP)) +- +- /* Round up the pixel clock a bit (10khz), so that the +- * "don't run things faster than the requested clock +- * rate" rule of the clk driver doesn't reject the +- * divide-by-3 mode due to rounding error. ++ /* Modeline comes from the Raspberry Pi firmware, with HFP=1 ++ * plugged in and clock re-computed from that. + */ +- .clock = PIXEL_CLOCK / 1000 + 10, +- .hdisplay = HACT, +- .hsync_start = HACT + HFP, +- .hsync_end = HACT + HFP + HSW, +- .htotal = HACT + HFP + HSW + HBP, ++ .clock = 25979400 / 1000, ++ .hdisplay = 800, ++ .hsync_start = 800 + 1, ++ .hsync_end = 800 + 1 + 2, ++ .htotal = 800 + 1 + 2 + 46, + .vdisplay = 480, + .vsync_start = 480 + 7, + .vsync_end = 480 + 7 + 2, +- .vtotal = VTOTAL, ++ .vtotal = 480 + 7 + 2 + 21, + .vrefresh = 60, + }, + }; +@@ -245,7 +223,7 @@ static struct rpi_touchscreen *panel_to_ts(struct drm_panel *panel) + + static int rpi_touchscreen_i2c_read(struct rpi_touchscreen *ts, u8 reg) + { +- return i2c_smbus_read_byte_data(ts->bridge_i2c, reg); ++ return i2c_smbus_read_byte_data(ts->i2c, reg); + } + + static void rpi_touchscreen_i2c_write(struct rpi_touchscreen *ts, +@@ -253,19 +231,13 @@ static void rpi_touchscreen_i2c_write(struct rpi_touchscreen *ts, + { + int ret; + +- ret = i2c_smbus_write_byte_data(ts->bridge_i2c, reg, val); ++ ret = i2c_smbus_write_byte_data(ts->i2c, reg, val); + if (ret) + dev_err(&ts->dsi->dev, "I2C write failed: %d\n", ret); + } + + static int rpi_touchscreen_write(struct rpi_touchscreen *ts, u16 reg, u32 val) + { +-#if 0 +- /* The firmware uses LP DSI transactions like this to bring up +- * the hardware, which should be faster than using I2C to then +- * pass to the Toshiba. However, I was unable to get it to +- * work. +- */ + u8 msg[] = { + reg, + reg >> 8, +@@ -275,13 +247,7 @@ static int rpi_touchscreen_write(struct rpi_touchscreen *ts, u16 reg, u32 val) + val >> 24, + }; + +- mipi_dsi_dcs_write_buffer(ts->dsi, msg, sizeof(msg)); +-#else +- rpi_touchscreen_i2c_write(ts, REG_WR_ADDRH, reg >> 8); +- rpi_touchscreen_i2c_write(ts, REG_WR_ADDRL, reg); +- rpi_touchscreen_i2c_write(ts, REG_WRITEH, val >> 8); +- rpi_touchscreen_i2c_write(ts, REG_WRITEL, val); +-#endif ++ mipi_dsi_generic_write(ts->dsi, msg, sizeof(msg)); + + return 0; + } +@@ -317,8 +283,7 @@ static int rpi_touchscreen_enable(struct drm_panel *panel) + + rpi_touchscreen_write(ts, DSI_LANEENABLE, + DSI_LANEENABLE_CLOCK | +- DSI_LANEENABLE_D0 | +- (ts->dsi->lanes > 1 ? DSI_LANEENABLE_D1 : 0)); ++ DSI_LANEENABLE_D0); + rpi_touchscreen_write(ts, PPI_D0S_CLRSIPOCOUNT, 0x05); + rpi_touchscreen_write(ts, PPI_D1S_CLRSIPOCOUNT, 0x05); + rpi_touchscreen_write(ts, PPI_D0S_ATMR, 0x00); +@@ -352,6 +317,7 @@ static int rpi_touchscreen_get_modes(struct drm_panel *panel) + struct drm_connector *connector = panel->connector; + struct drm_device *drm = panel->drm; + unsigned int i, num = 0; ++ static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + for (i = 0; i < ARRAY_SIZE(rpi_touchscreen_modes); i++) { + const struct drm_display_mode *m = &rpi_touchscreen_modes[i]; +@@ -378,6 +344,8 @@ static int rpi_touchscreen_get_modes(struct drm_panel *panel) + connector->display_info.bpc = 8; + connector->display_info.width_mm = 154; + connector->display_info.height_mm = 86; ++ drm_display_info_set_bus_formats(&connector->display_info, ++ &bus_format, 1); + + return num; + } +@@ -390,51 +358,27 @@ static const struct drm_panel_funcs rpi_touchscreen_funcs = { + .get_modes = rpi_touchscreen_get_modes, + }; + +-static struct i2c_client *rpi_touchscreen_get_i2c(struct device *dev, +- const char *name) +-{ +- struct device_node *node; +- struct i2c_client *client; +- +- node = of_parse_phandle(dev->of_node, name, 0); +- if (!node) +- return ERR_PTR(-ENODEV); +- +- client = of_find_i2c_device_by_node(node); +- +- of_node_put(node); +- +- if (!client) +- return ERR_PTR(-EPROBE_DEFER); +- +- return client; +-} +- +-static int rpi_touchscreen_dsi_probe(struct mipi_dsi_device *dsi) ++static int rpi_touchscreen_probe(struct i2c_client *i2c, ++ const struct i2c_device_id *id) + { +- struct device *dev = &dsi->dev; ++ struct device *dev = &i2c->dev; + struct rpi_touchscreen *ts; ++ struct device_node *endpoint, *dsi_host_node; ++ struct mipi_dsi_host *host; + int ret, ver; ++ struct mipi_dsi_device_info info = { ++ .type = RPI_DSI_DRIVER_NAME, ++ .channel = 0, ++ .node = NULL, ++ }; + + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + +- dev_set_drvdata(dev, ts); ++ i2c_set_clientdata(i2c, ts); + +- ts->dsi = dsi; +- dsi->mode_flags = (MIPI_DSI_MODE_VIDEO | +- MIPI_DSI_MODE_VIDEO_SYNC_PULSE | +- MIPI_DSI_MODE_LPM); +- dsi->format = MIPI_DSI_FMT_RGB888; +- dsi->lanes = 1; +- +- ts->bridge_i2c = +- rpi_touchscreen_get_i2c(dev, "raspberrypi,touchscreen-bridge"); +- if (IS_ERR(ts->bridge_i2c)) { +- ret = -EPROBE_DEFER; +- return ret; +- } ++ ts->i2c = i2c; + + ver = rpi_touchscreen_i2c_read(ts, REG_ID); + if (ver < 0) { +@@ -443,11 +387,8 @@ static int rpi_touchscreen_dsi_probe(struct mipi_dsi_device *dsi) + } + + switch (ver) { +- case 0xde: +- ts->atmel_ver = 1; +- break; +- case 0xc3: +- ts->atmel_ver = 2; ++ case 0xde: /* ver 1 */ ++ case 0xc3: /* ver 2 */ + break; + default: + dev_err(dev, "Unknown Atmel firmware revision: 0x%02x\n", ver); +@@ -457,65 +398,104 @@ static int rpi_touchscreen_dsi_probe(struct mipi_dsi_device *dsi) + /* Turn off at boot, so we can cleanly sequence powering on. */ + rpi_touchscreen_i2c_write(ts, REG_POWERON, 0); + +- drm_panel_init(&ts->base); ++ /* Look up the DSI host. It needs to probe before we do. */ ++ endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); ++ dsi_host_node = of_graph_get_remote_port_parent(endpoint); ++ host = of_find_mipi_dsi_host_by_node(dsi_host_node); ++ of_node_put(dsi_host_node); ++ if (!host) { ++ of_node_put(endpoint); ++ return -EPROBE_DEFER; ++ } ++ ++ info.node = of_graph_get_remote_port(endpoint); ++ of_node_put(endpoint); ++ ++ ts->dsi = mipi_dsi_device_register_full(host, &info); ++ if (IS_ERR(ts->dsi)) { ++ dev_err(dev, "DSI device registration failed: %ld\n", ++ PTR_ERR(ts->dsi)); ++ return PTR_ERR(ts->dsi); ++ } ++ + ts->base.dev = dev; + ts->base.funcs = &rpi_touchscreen_funcs; + ++ /* This appears last, as it's what will unblock the DSI host ++ * driver's component bind function. ++ */ + ret = drm_panel_add(&ts->base); +- if (ret < 0) +- goto err_release_bridge; +- +- return mipi_dsi_attach(dsi); ++ if (ret) ++ return ret; + +-err_release_bridge: +- put_device(&ts->bridge_i2c->dev); +- return ret; ++ return 0; + } + +-static int rpi_touchscreen_dsi_remove(struct mipi_dsi_device *dsi) ++static int rpi_touchscreen_remove(struct i2c_client *i2c) + { +- struct device *dev = &dsi->dev; +- struct rpi_touchscreen *ts = dev_get_drvdata(dev); +- int ret; ++ struct rpi_touchscreen *ts = i2c_get_clientdata(i2c); + +- ret = mipi_dsi_detach(dsi); +- if (ret < 0) { +- dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); +- return ret; +- } ++ mipi_dsi_detach(ts->dsi); + +- drm_panel_detach(&ts->base); + drm_panel_remove(&ts->base); + +- put_device(&ts->bridge_i2c->dev); ++ mipi_dsi_device_unregister(ts->dsi); ++ kfree(ts->dsi); + + return 0; + } + +-static void rpi_touchscreen_dsi_shutdown(struct mipi_dsi_device *dsi) ++static int rpi_touchscreen_dsi_probe(struct mipi_dsi_device *dsi) + { +- struct device *dev = &dsi->dev; +- struct rpi_touchscreen *ts = dev_get_drvdata(dev); ++ int ret; + +- rpi_touchscreen_i2c_write(ts, REG_POWERON, 0); ++ dsi->mode_flags = (MIPI_DSI_MODE_VIDEO | ++ MIPI_DSI_MODE_VIDEO_SYNC_PULSE | ++ MIPI_DSI_MODE_LPM); ++ dsi->format = MIPI_DSI_FMT_RGB888; ++ dsi->lanes = 1; ++ ++ ret = mipi_dsi_attach(dsi); ++ ++ if (ret) ++ dev_err(&dsi->dev, "failed to attach dsi to host: %d\n", ret); ++ ++ return ret; + } + +-static const struct of_device_id rpi_touchscreen_of_match[] = { +- { .compatible = "raspberrypi,touchscreen" }, ++static struct mipi_dsi_driver rpi_touchscreen_dsi_driver = { ++ .driver.name = RPI_DSI_DRIVER_NAME, ++ .probe = rpi_touchscreen_dsi_probe, ++}; ++ ++static const struct of_device_id rpi_touchscreen_of_ids[] = { ++ { .compatible = "raspberrypi,7inch-touchscreen-panel" }, + { } /* sentinel */ + }; +-MODULE_DEVICE_TABLE(of, rpi_touchscreen_of_match); ++MODULE_DEVICE_TABLE(of, rpi_touchscreen_of_ids); + +-static struct mipi_dsi_driver rpi_touchscreen_driver = { ++static struct i2c_driver rpi_touchscreen_driver = { + .driver = { +- .name = "raspberrypi-touchscreen", +- .of_match_table = rpi_touchscreen_of_match, ++ .name = "rpi_touchscreen", ++ .of_match_table = rpi_touchscreen_of_ids, + }, +- .probe = rpi_touchscreen_dsi_probe, +- .remove = rpi_touchscreen_dsi_remove, +- .shutdown = rpi_touchscreen_dsi_shutdown, ++ .probe = rpi_touchscreen_probe, ++ .remove = rpi_touchscreen_remove, + }; +-module_mipi_dsi_driver(rpi_touchscreen_driver); ++ ++static int __init rpi_touchscreen_init(void) ++{ ++ mipi_dsi_driver_register(&rpi_touchscreen_dsi_driver); ++ return i2c_add_driver(&rpi_touchscreen_driver); ++} ++module_init(rpi_touchscreen_init); ++ ++static void __exit rpi_touchscreen_exit(void) ++{ ++ i2c_del_driver(&rpi_touchscreen_driver); ++ mipi_dsi_driver_unregister(&rpi_touchscreen_dsi_driver); ++} ++module_exit(rpi_touchscreen_exit); + + MODULE_AUTHOR("Eric Anholt "); + MODULE_DESCRIPTION("Raspberry Pi 7-inch touchscreen driver"); +-- +2.21.0 + -- cgit 1.2.3-korg