diff --git a/Documentation/kernel.md b/Documentation/kernel.md index 678ad822f..b286c69a5 100644 --- a/Documentation/kernel.md +++ b/Documentation/kernel.md @@ -6,7 +6,7 @@ | Open Virtual Applicance | 4.19.88 | | Raspberry Pi | 4.19.88 | | Tinker Board | 4.19.88 | -| Odroid-C2 | 4.19.88 | -| Odroid-XU4 | 4.19.88 | +| Odroid-C2 | 4.19.72 | +| Odroid-XU4 | 4.19.72 | | Orangepi-Prime | 4.19.88 | | Intel NUC | 4.19.88 | diff --git a/buildroot-external/board/hardkernel/odroid-c2/patches/linux/000-arm64_dts_meson_Fix_IRQ_trigger_type_for_macirq.patch b/buildroot-external/board/hardkernel/odroid-c2/patches/linux/000-arm64_dts_meson_Fix_IRQ_trigger_type_for_macirq.patch deleted file mode 100644 index a3f006641..000000000 --- a/buildroot-external/board/hardkernel/odroid-c2/patches/linux/000-arm64_dts_meson_Fix_IRQ_trigger_type_for_macirq.patch +++ /dev/null @@ -1,50 +0,0 @@ -diff --git a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi -index b160bd1084de..fffd55787981 100644 ---- a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi -+++ b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi -@@ -461,7 +461,7 @@ - compatible = "amlogic,meson-gxbb-dwmac", "snps,dwmac"; - reg = <0x0 0xff3f0000 0x0 0x10000 - 0x0 0xff634540 0x0 0x8>; -- interrupts = ; -+ interrupts = ; - interrupt-names = "macirq"; - clocks = <&clkc CLKID_ETH>, - <&clkc CLKID_FCLK_DIV2>, -diff --git a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi -index ed336c7a98a7..44c5c51ff1fa 100644 ---- a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi -+++ b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi -@@ -467,7 +467,7 @@ - compatible = "amlogic,meson-gx-dwmac", "amlogic,meson-gxbb-dwmac", "snps,dwmac"; - reg = <0x0 0xc9410000 0x0 0x10000 - 0x0 0xc8834540 0x0 0x4>; -- interrupts = ; -+ interrupts = ; - interrupt-names = "macirq"; - status = "disabled"; - }; -diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts -index 00f7be6d83f7..2e1cd5e3a246 100644 ---- a/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts -+++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts -@@ -143,7 +143,6 @@ - interrupt-parent = <&gpio_intc>; - /* MAC_INTR on GPIOZ_15 */ - interrupts = <29 IRQ_TYPE_LEVEL_LOW>; -- eee-broken-1000t; - }; - }; - }; -diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-wetek.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxbb-wetek.dtsi -index 70325b273bd2..ec09bb5792b7 100644 ---- a/arch/arm64/boot/dts/amlogic/meson-gxbb-wetek.dtsi -+++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-wetek.dtsi -@@ -142,7 +142,6 @@ - eth_phy0: ethernet-phy@0 { - /* Realtek RTL8211F (0x001cc916) */ - reg = <0>; -- eee-broken-1000t; - }; - }; - }; diff --git a/buildroot-external/board/hardkernel/odroid-xu4/patches/linux/000-odroid_xu4_modified_strobe_timing_for_emmc_hs400_mode.patch b/buildroot-external/board/hardkernel/odroid-xu4/patches/linux/000-odroid_xu4_modified_strobe_timing_for_emmc_hs400_mode.patch deleted file mode 100644 index 6f30398a3..000000000 --- a/buildroot-external/board/hardkernel/odroid-xu4/patches/linux/000-odroid_xu4_modified_strobe_timing_for_emmc_hs400_mode.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/arch/arm/boot/dts/exynos5422-odroidxu3-common.dtsi b/arch/arm/boot/dts/exynos5422-odroidxu3-common.dtsi -index d0ae8c5315048..3ccf2c5be034c 100755 ---- a/arch/arm/boot/dts/exynos5422-odroidxu3-common.dtsi -+++ b/arch/arm/boot/dts/exynos5422-odroidxu3-common.dtsi -@@ -402,7 +402,7 @@ - samsung,dw-mshc-sdr-timing = <0 4>; - samsung,dw-mshc-ddr-timing = <0 2>; - samsung,dw-mshc-hs400-timing = <0 2>; -- samsung,read-strobe-delay = <90>; -+ samsung,read-strobe-delay = <150>; - pinctrl-names = "default"; - pinctrl-0 = <&sd0_clk &sd0_cmd &sd0_bus1 &sd0_bus4 &sd0_bus8 &sd0_cd &sd0_rclk>; - bus-width = <8>; diff --git a/buildroot-external/board/hardkernel/patches/README.md b/buildroot-external/board/hardkernel/patches/README.md new file mode 100644 index 000000000..09d2ff30d --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/README.md @@ -0,0 +1,2 @@ +# Kernel +https://github.com/akuster/meta-odroid diff --git a/buildroot-external/board/hardkernel/patches/linux/0001-ARM64-defconfig-enable-CEC-support.patch b/buildroot-external/board/hardkernel/patches/linux/0001-ARM64-defconfig-enable-CEC-support.patch new file mode 100644 index 000000000..b34666a0e --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0001-ARM64-defconfig-enable-CEC-support.patch @@ -0,0 +1,47 @@ +From 6763c7964e9cb28e21497eee0032be053461bba5 Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Mon, 13 Nov 2017 12:09:40 +0100 +Subject: [PATCH 01/53] ARM64: defconfig: enable CEC support + +Turn on CONFIG_CEC_SUPPORT and CONFIG_CEC_PLATFORM_DRIVERS +Turn on CONFIG_VIDEO_MESON_AO_CEC as module +Turn on CONFIG_DRM_DW_HDMI_CEC as module + +Signed-off-by: Jerome Brunet +Signed-off-by: Neil Armstrong +--- + arch/arm64/configs/defconfig | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig +index db8d364f8476..ab1cb51319e7 100644 +--- a/arch/arm64/configs/defconfig ++++ b/arch/arm64/configs/defconfig +@@ -413,6 +413,7 @@ CONFIG_MEDIA_SUPPORT=m + CONFIG_MEDIA_CAMERA_SUPPORT=y + CONFIG_MEDIA_ANALOG_TV_SUPPORT=y + CONFIG_MEDIA_DIGITAL_TV_SUPPORT=y ++CONFIG_MEDIA_CEC_SUPPORT=y + CONFIG_MEDIA_CONTROLLER=y + CONFIG_VIDEO_V4L2_SUBDEV_API=y + # CONFIG_DVB_NET is not set +@@ -424,6 +425,8 @@ CONFIG_VIDEO_SAMSUNG_S5P_MFC=m + CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC=m + CONFIG_VIDEO_RENESAS_FCP=m + CONFIG_VIDEO_RENESAS_VSP1=m ++CONFIG_CEC_PLATFORM_DRIVERS=y ++CONFIG_VIDEO_MESON_AO_CEC=m + CONFIG_DRM=m + CONFIG_DRM_NOUVEAU=m + CONFIG_DRM_EXYNOS=m +@@ -444,6 +447,7 @@ CONFIG_DRM_RCAR_LVDS=m + CONFIG_DRM_TEGRA=m + CONFIG_DRM_PANEL_SIMPLE=m + CONFIG_DRM_I2C_ADV7511=m ++CONFIG_DRM_DW_HDMI_CEC=m + CONFIG_DRM_VC4=m + CONFIG_DRM_HISI_HIBMC=m + CONFIG_DRM_HISI_KIRIN=m +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0001-exynos5422-odroidhc1.dts-fix-booting-from-mmc.patch b/buildroot-external/board/hardkernel/patches/linux/0001-exynos5422-odroidhc1.dts-fix-booting-from-mmc.patch new file mode 100644 index 000000000..95d35171d --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0001-exynos5422-odroidhc1.dts-fix-booting-from-mmc.patch @@ -0,0 +1,72 @@ +From 7041ca0d550d3a9caed54857365cf504eaeea756 Mon Sep 17 00:00:00 2001 +From: Armin Kuster +Date: Fri, 23 Mar 2018 09:02:44 -0700 +Subject: [PATCH] exynos5422-odroidhc1.dts: fix booting from mmc + +Signed-off-by: Armin Kuster +--- + arch/arm/boot/dts/exynos5422-odroidhc1.dts | 37 ++++++++++++++++++++++++++++++ + 1 file changed, 37 insertions(+) + +diff --git a/arch/arm/boot/dts/exynos5422-odroidhc1.dts b/arch/arm/boot/dts/exynos5422-odroidhc1.dts +index fb8e8ae..c7adecf 100644 +--- a/arch/arm/boot/dts/exynos5422-odroidhc1.dts ++++ b/arch/arm/boot/dts/exynos5422-odroidhc1.dts +@@ -11,6 +11,7 @@ + */ + + /dts-v1/; ++#include + #include "exynos5422-odroid-core.dtsi" + + / { +@@ -30,6 +31,14 @@ + }; + }; + ++ emmc_pwrseq: pwrseq { ++ pinctrl-0 = <&emmc_nrst_pin>; ++ pinctrl-names = "default"; ++ compatible = "mmc-pwrseq-emmc"; ++ reset-gpios = <&gpd1 0 GPIO_ACTIVE_LOW>; ++ }; ++ ++ + thermal-zones { + cpu0_thermal: cpu0-thermal { + thermal-sensors = <&tmu_cpu0 0>; +@@ -211,3 +220,31 @@ + &usbdrd_dwc3_1 { + dr_mode = "host"; + }; ++ ++&mmc_0 { ++ status = "okay"; ++ mmc-pwrseq = <&emmc_pwrseq>; ++ card-detect-delay = <200>; ++ samsung,dw-mshc-ciu-div = <3>; ++ samsung,dw-mshc-sdr-timing = <0 4>; ++ samsung,dw-mshc-ddr-timing = <0 2>; ++ samsung,dw-mshc-hs400-timing = <0 2>; ++ samsung,read-strobe-delay = <90>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&sd0_clk &sd0_cmd &sd0_bus1 &sd0_bus4 &sd0_bus8 &sd0_cd &sd0_rclk>; ++ bus-width = <8>; ++ cap-mmc-highspeed; ++ mmc-hs200-1_8v; ++ mmc-hs400-1_8v; ++ vmmc-supply = <&ldo18_reg>; ++ vqmmc-supply = <&ldo3_reg>; ++}; ++ ++&pinctrl_1 { ++ emmc_nrst_pin: emmc-nrst { ++ samsung,pins = "gpd1-0"; ++ samsung,pin-function = ; ++ samsung,pin-pud = ; ++ samsung,pin-drv = ; ++ }; ++}; +-- +2.7.4 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0002-ASoC-meson-add-meson-audio-core-driver.patch b/buildroot-external/board/hardkernel/patches/linux/0002-ASoC-meson-add-meson-audio-core-driver.patch new file mode 100644 index 000000000..e9a3240ab --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0002-ASoC-meson-add-meson-audio-core-driver.patch @@ -0,0 +1,290 @@ +From 6b2734923e6bf1d4bd98f918400e2c7a692a8db0 Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Thu, 30 Mar 2017 11:49:55 +0200 +Subject: [PATCH 02/53] ASoC: meson: add meson audio core driver + +This patch adds support for the audio core driver for the Amlogic Meson SoC +family. The purpose of this driver is to properly reset the audio block and +provide register access for the different devices scattered in this address +space. This includes output and input DMAs, pcm, i2s and spdif dai, card +level routing, internal codec for the gxl variant + +For more information, please refer to the section 5 of the public datasheet +of the S905 (gxbb). This datasheet is available here: [0]. + +[0]: http://dn.odroid.com/S905/DataSheet/S905_Public_Datasheet_V1.1.4.pdf + +Signed-off-by: Jerome Brunet +--- + sound/soc/meson/Kconfig | 10 ++ + sound/soc/meson/Makefile | 4 + + sound/soc/meson/audio-core.c | 190 +++++++++++++++++++++++++++++++++++ + sound/soc/meson/audio-core.h | 28 ++++++ + 4 files changed, 232 insertions(+) + create mode 100644 sound/soc/meson/audio-core.c + create mode 100644 sound/soc/meson/audio-core.h + +diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig +index 8af8bc358a90..ed432d488b74 100644 +--- a/sound/soc/meson/Kconfig ++++ b/sound/soc/meson/Kconfig +@@ -63,3 +63,13 @@ config SND_MESON_AXG_SPDIFOUT + in the Amlogic AXG SoC family + + endmenu ++ ++menuconfig SND_SOC_MESON ++ tristate "ASoC support for Amlogic Meson SoCs" ++ depends on ARCH_MESON ++ select MFD_CORE ++ select REGMAP_MMIO ++ help ++ Say Y or M if you want to add support for codecs attached to ++ the Amlogic Meson SoCs Audio interfaces. You will also need to ++ select the audio interfaces to support below. +diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile +index c5e003b093db..768d7c414649 100644 +--- a/sound/soc/meson/Makefile ++++ b/sound/soc/meson/Makefile +@@ -19,3 +19,7 @@ obj-$(CONFIG_SND_MESON_AXG_TDMIN) += snd-soc-meson-axg-tdmin.o + obj-$(CONFIG_SND_MESON_AXG_TDMOUT) += snd-soc-meson-axg-tdmout.o + obj-$(CONFIG_SND_MESON_AXG_SOUND_CARD) += snd-soc-meson-axg-sound-card.o + obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o ++ ++snd-soc-meson-audio-core-objs := audio-core.o ++ ++obj-$(CONFIG_SND_SOC_MESON) += snd-soc-meson-audio-core.o +\ No newline at end of file +diff --git a/sound/soc/meson/audio-core.c b/sound/soc/meson/audio-core.c +new file mode 100644 +index 000000000000..99993ec4a5cc +--- /dev/null ++++ b/sound/soc/meson/audio-core.c +@@ -0,0 +1,190 @@ ++/* ++ * Copyright (C) 2017 BayLibre, SAS ++ * Author: Jerome Brunet ++ * Copyright (C) 2017 Amlogic, Inc. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "audio-core.h" ++ ++#define DRV_NAME "meson-audio-core" ++ ++static const char * const acore_clock_names[] = { "aiu_top", ++ "aiu_glue", ++ "audin" }; ++ ++static int meson_acore_init_clocks(struct device *dev) ++{ ++ struct clk *clock; ++ int i, ret; ++ ++ for (i = 0; i < ARRAY_SIZE(acore_clock_names); i++) { ++ clock = devm_clk_get(dev, acore_clock_names[i]); ++ if (IS_ERR(clock)) { ++ if (PTR_ERR(clock) != -EPROBE_DEFER) ++ dev_err(dev, "Failed to get %s clock\n", ++ acore_clock_names[i]); ++ return PTR_ERR(clock); ++ } ++ ++ ret = clk_prepare_enable(clock); ++ if (ret) { ++ dev_err(dev, "Failed to enable %s clock\n", ++ acore_clock_names[i]); ++ return ret; ++ } ++ ++ ret = devm_add_action_or_reset(dev, ++ (void(*)(void *))clk_disable_unprepare, ++ clock); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const char * const acore_reset_names[] = { "aiu", ++ "audin" }; ++ ++static int meson_acore_init_resets(struct device *dev) ++{ ++ struct reset_control *reset; ++ int i, ret; ++ ++ for (i = 0; i < ARRAY_SIZE(acore_reset_names); i++) { ++ reset = devm_reset_control_get_exclusive(dev, ++ acore_reset_names[i]); ++ if (IS_ERR(reset)) { ++ if (PTR_ERR(reset) != -EPROBE_DEFER) ++ dev_err(dev, "Failed to get %s reset\n", ++ acore_reset_names[i]); ++ return PTR_ERR(reset); ++ } ++ ++ ret = reset_control_reset(reset); ++ if (ret) { ++ dev_err(dev, "Failed to pulse %s reset\n", ++ acore_reset_names[i]); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++static const struct regmap_config meson_acore_regmap_config = { ++ .reg_bits = 32, ++ .val_bits = 32, ++ .reg_stride = 4, ++}; ++ ++static const struct mfd_cell meson_acore_devs[] = { ++ { ++ .name = "meson-i2s-dai", ++ .of_compatible = "amlogic,meson-i2s-dai", ++ }, ++ { ++ .name = "meson-spdif-dai", ++ .of_compatible = "amlogic,meson-spdif-dai", ++ }, ++ { ++ .name = "meson-aiu-i2s-dma", ++ .of_compatible = "amlogic,meson-aiu-i2s-dma", ++ }, ++ { ++ .name = "meson-aiu-spdif-dma", ++ .of_compatible = "amlogic,meson-aiu-spdif-dma", ++ }, ++}; ++ ++static int meson_acore_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct meson_audio_core_data *data; ++ struct resource *res; ++ void __iomem *regs; ++ int ret; ++ ++ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ platform_set_drvdata(pdev, data); ++ ++ ret = meson_acore_init_clocks(dev); ++ if (ret) ++ return ret; ++ ++ ret = meson_acore_init_resets(dev); ++ if (ret) ++ return ret; ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aiu"); ++ regs = devm_ioremap_resource(dev, res); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); ++ ++ data->aiu = devm_regmap_init_mmio(dev, regs, ++ &meson_acore_regmap_config); ++ if (IS_ERR(data->aiu)) { ++ dev_err(dev, "Couldn't create the AIU regmap\n"); ++ return PTR_ERR(data->aiu); ++ } ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audin"); ++ regs = devm_ioremap_resource(dev, res); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); ++ ++ data->audin = devm_regmap_init_mmio(dev, regs, ++ &meson_acore_regmap_config); ++ if (IS_ERR(data->audin)) { ++ dev_err(dev, "Couldn't create the AUDIN regmap\n"); ++ return PTR_ERR(data->audin); ++ } ++ ++ return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, meson_acore_devs, ++ ARRAY_SIZE(meson_acore_devs), NULL, 0, ++ NULL); ++} ++ ++static const struct of_device_id meson_acore_of_match[] = { ++ { .compatible = "amlogic,meson-audio-core", }, ++ { .compatible = "amlogic,meson-gxbb-audio-core", }, ++ { .compatible = "amlogic,meson-gxl-audio-core", }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, meson_acore_of_match); ++ ++static struct platform_driver meson_acore_pdrv = { ++ .probe = meson_acore_probe, ++ .driver = { ++ .name = DRV_NAME, ++ .of_match_table = meson_acore_of_match, ++ }, ++}; ++module_platform_driver(meson_acore_pdrv); ++ ++MODULE_DESCRIPTION("Meson Audio Core Driver"); ++MODULE_AUTHOR("Jerome Brunet "); ++MODULE_LICENSE("GPL v2"); +diff --git a/sound/soc/meson/audio-core.h b/sound/soc/meson/audio-core.h +new file mode 100644 +index 000000000000..6e7a24cdc4a9 +--- /dev/null ++++ b/sound/soc/meson/audio-core.h +@@ -0,0 +1,28 @@ ++/* ++ * Copyright (C) 2017 BayLibre, SAS ++ * Author: Jerome Brunet ++ * Copyright (C) 2017 Amlogic, Inc. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#ifndef _MESON_AUDIO_CORE_H_ ++#define _MESON_AUDIO_CORE_H_ ++ ++struct meson_audio_core_data { ++ struct regmap *aiu; ++ struct regmap *audin; ++}; ++ ++#endif /* _MESON_AUDIO_CORE_H_ */ +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0003-ASoC-meson-add-register-definitions.patch b/buildroot-external/board/hardkernel/patches/linux/0003-ASoC-meson-add-register-definitions.patch new file mode 100644 index 000000000..7266b0dbd --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0003-ASoC-meson-add-register-definitions.patch @@ -0,0 +1,360 @@ +From 0b2aabc632854e317544bb293cbc0c63e120ddfa Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Thu, 30 Mar 2017 12:00:10 +0200 +Subject: [PATCH 03/53] ASoC: meson: add register definitions + +Add the register definition for the AIU and AUDIN blocks + +Signed-off-by: Jerome Brunet +--- + sound/soc/meson/aiu-regs.h | 182 +++++++++++++++++++++++++++++++++++ + sound/soc/meson/audin-regs.h | 148 ++++++++++++++++++++++++++++ + 2 files changed, 330 insertions(+) + create mode 100644 sound/soc/meson/aiu-regs.h + create mode 100644 sound/soc/meson/audin-regs.h + +diff --git a/sound/soc/meson/aiu-regs.h b/sound/soc/meson/aiu-regs.h +new file mode 100644 +index 000000000000..67391e64fe1c +--- /dev/null ++++ b/sound/soc/meson/aiu-regs.h +@@ -0,0 +1,182 @@ ++/* ++ * Copyright (C) 2017 BayLibre, SAS ++ * Author: Jerome Brunet ++ * Copyright (C) 2017 Amlogic, Inc. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#ifndef _AIU_REGS_H_ ++#define _AIU_REGS_H_ ++ ++#define AIU_958_BPF 0x000 ++#define AIU_958_BRST 0x004 ++#define AIU_958_LENGTH 0x008 ++#define AIU_958_PADDSIZE 0x00C ++#define AIU_958_MISC 0x010 ++#define AIU_958_FORCE_LEFT 0x014 /* Unknown */ ++#define AIU_958_DISCARD_NUM 0x018 ++#define AIU_958_DCU_FF_CTRL 0x01C ++#define AIU_958_CHSTAT_L0 0x020 ++#define AIU_958_CHSTAT_L1 0x024 ++#define AIU_958_CTRL 0x028 ++#define AIU_958_RPT 0x02C ++#define AIU_I2S_MUTE_SWAP 0x030 ++#define AIU_I2S_SOURCE_DESC 0x034 ++#define AIU_I2S_MED_CTRL 0x038 ++#define AIU_I2S_MED_THRESH 0x03C ++#define AIU_I2S_DAC_CFG 0x040 ++#define AIU_I2S_SYNC 0x044 /* Unknown */ ++#define AIU_I2S_MISC 0x048 ++#define AIU_I2S_OUT_CFG 0x04C ++#define AIU_I2S_FF_CTRL 0x050 /* Unknown */ ++#define AIU_RST_SOFT 0x054 ++#define AIU_CLK_CTRL 0x058 ++#define AIU_MIX_ADCCFG 0x05C ++#define AIU_MIX_CTRL 0x060 ++#define AIU_CLK_CTRL_MORE 0x064 ++#define AIU_958_POP 0x068 ++#define AIU_MIX_GAIN 0x06C ++#define AIU_958_SYNWORD1 0x070 ++#define AIU_958_SYNWORD2 0x074 ++#define AIU_958_SYNWORD3 0x078 ++#define AIU_958_SYNWORD1_MASK 0x07C ++#define AIU_958_SYNWORD2_MASK 0x080 ++#define AIU_958_SYNWORD3_MASK 0x084 ++#define AIU_958_FFRDOUT_THD 0x088 ++#define AIU_958_LENGTH_PER_PAUSE 0x08C ++#define AIU_958_PAUSE_NUM 0x090 ++#define AIU_958_PAUSE_PAYLOAD 0x094 ++#define AIU_958_AUTO_PAUSE 0x098 ++#define AIU_958_PAUSE_PD_LENGTH 0x09C ++#define AIU_CODEC_DAC_LRCLK_CTRL 0x0A0 ++#define AIU_CODEC_ADC_LRCLK_CTRL 0x0A4 ++#define AIU_HDMI_CLK_DATA_CTRL 0x0A8 ++#define AIU_CODEC_CLK_DATA_CTRL 0x0AC ++#define AIU_ACODEC_CTRL 0x0B0 ++#define AIU_958_CHSTAT_R0 0x0C0 ++#define AIU_958_CHSTAT_R1 0x0C4 ++#define AIU_958_VALID_CTRL 0x0C8 ++#define AIU_AUDIO_AMP_REG0 0x0F0 /* Unknown */ ++#define AIU_AUDIO_AMP_REG1 0x0F4 /* Unknown */ ++#define AIU_AUDIO_AMP_REG2 0x0F8 /* Unknown */ ++#define AIU_AUDIO_AMP_REG3 0x0FC /* Unknown */ ++#define AIU_AIFIFO2_CTRL 0x100 ++#define AIU_AIFIFO2_STATUS 0x104 ++#define AIU_AIFIFO2_GBIT 0x108 ++#define AIU_AIFIFO2_CLB 0x10C ++#define AIU_CRC_CTRL 0x110 ++#define AIU_CRC_STATUS 0x114 ++#define AIU_CRC_SHIFT_REG 0x118 ++#define AIU_CRC_IREG 0x11C ++#define AIU_CRC_CAL_REG1 0x120 ++#define AIU_CRC_CAL_REG0 0x124 ++#define AIU_CRC_POLY_COEF1 0x128 ++#define AIU_CRC_POLY_COEF0 0x12C ++#define AIU_CRC_BIT_SIZE1 0x130 ++#define AIU_CRC_BIT_SIZE0 0x134 ++#define AIU_CRC_BIT_CNT1 0x138 ++#define AIU_CRC_BIT_CNT0 0x13C ++#define AIU_AMCLK_GATE_HI 0x140 ++#define AIU_AMCLK_GATE_LO 0x144 ++#define AIU_AMCLK_MSR 0x148 ++#define AIU_AUDAC_CTRL0 0x14C /* Unknown */ ++#define AIU_DELTA_SIGMA0 0x154 /* Unknown */ ++#define AIU_DELTA_SIGMA1 0x158 /* Unknown */ ++#define AIU_DELTA_SIGMA2 0x15C /* Unknown */ ++#define AIU_DELTA_SIGMA3 0x160 /* Unknown */ ++#define AIU_DELTA_SIGMA4 0x164 /* Unknown */ ++#define AIU_DELTA_SIGMA5 0x168 /* Unknown */ ++#define AIU_DELTA_SIGMA6 0x16C /* Unknown */ ++#define AIU_DELTA_SIGMA7 0x170 /* Unknown */ ++#define AIU_DELTA_SIGMA_LCNTS 0x174 /* Unknown */ ++#define AIU_DELTA_SIGMA_RCNTS 0x178 /* Unknown */ ++#define AIU_MEM_I2S_START_PTR 0x180 ++#define AIU_MEM_I2S_RD_PTR 0x184 ++#define AIU_MEM_I2S_END_PTR 0x188 ++#define AIU_MEM_I2S_MASKS 0x18C ++#define AIU_MEM_I2S_CONTROL 0x190 ++#define AIU_MEM_IEC958_START_PTR 0x194 ++#define AIU_MEM_IEC958_RD_PTR 0x198 ++#define AIU_MEM_IEC958_END_PTR 0x19C ++#define AIU_MEM_IEC958_MASKS 0x1A0 ++#define AIU_MEM_IEC958_CONTROL 0x1A4 ++#define AIU_MEM_AIFIFO2_START_PTR 0x1A8 ++#define AIU_MEM_AIFIFO2_CURR_PTR 0x1AC ++#define AIU_MEM_AIFIFO2_END_PTR 0x1B0 ++#define AIU_MEM_AIFIFO2_BYTES_AVAIL 0x1B4 ++#define AIU_MEM_AIFIFO2_CONTROL 0x1B8 ++#define AIU_MEM_AIFIFO2_MAN_WP 0x1BC ++#define AIU_MEM_AIFIFO2_MAN_RP 0x1C0 ++#define AIU_MEM_AIFIFO2_LEVEL 0x1C4 ++#define AIU_MEM_AIFIFO2_BUF_CNTL 0x1C8 ++#define AIU_MEM_I2S_MAN_WP 0x1CC ++#define AIU_MEM_I2S_MAN_RP 0x1D0 ++#define AIU_MEM_I2S_LEVEL 0x1D4 ++#define AIU_MEM_I2S_BUF_CNTL 0x1D8 ++#define AIU_MEM_I2S_BUF_WRAP_COUNT 0x1DC ++#define AIU_MEM_I2S_MEM_CTL 0x1E0 ++#define AIU_MEM_IEC958_MEM_CTL 0x1E4 ++#define AIU_MEM_IEC958_WRAP_COUNT 0x1E8 ++#define AIU_MEM_IEC958_IRQ_LEVEL 0x1EC ++#define AIU_MEM_IEC958_MAN_WP 0x1F0 ++#define AIU_MEM_IEC958_MAN_RP 0x1F4 ++#define AIU_MEM_IEC958_LEVEL 0x1F8 ++#define AIU_MEM_IEC958_BUF_CNTL 0x1FC ++#define AIU_AIFIFO_CTRL 0x200 ++#define AIU_AIFIFO_STATUS 0x204 ++#define AIU_AIFIFO_GBIT 0x208 ++#define AIU_AIFIFO_CLB 0x20C ++#define AIU_MEM_AIFIFO_START_PTR 0x210 ++#define AIU_MEM_AIFIFO_CURR_PTR 0x214 ++#define AIU_MEM_AIFIFO_END_PTR 0x218 ++#define AIU_MEM_AIFIFO_BYTES_AVAIL 0x21C ++#define AIU_MEM_AIFIFO_CONTROL 0x220 ++#define AIU_MEM_AIFIFO_MAN_WP 0x224 ++#define AIU_MEM_AIFIFO_MAN_RP 0x228 ++#define AIU_MEM_AIFIFO_LEVEL 0x22C ++#define AIU_MEM_AIFIFO_BUF_CNTL 0x230 ++#define AIU_MEM_AIFIFO_BUF_WRAP_COUNT 0x234 ++#define AIU_MEM_AIFIFO2_BUF_WRAP_COUNT 0x238 ++#define AIU_MEM_AIFIFO_MEM_CTL 0x23C ++#define AIFIFO_TIME_STAMP_CNTL 0x240 ++#define AIFIFO_TIME_STAMP_SYNC_0 0x244 ++#define AIFIFO_TIME_STAMP_SYNC_1 0x248 ++#define AIFIFO_TIME_STAMP_0 0x24C ++#define AIFIFO_TIME_STAMP_1 0x250 ++#define AIFIFO_TIME_STAMP_2 0x254 ++#define AIFIFO_TIME_STAMP_3 0x258 ++#define AIFIFO_TIME_STAMP_LENGTH 0x25C ++#define AIFIFO2_TIME_STAMP_CNTL 0x260 ++#define AIFIFO2_TIME_STAMP_SYNC_0 0x264 ++#define AIFIFO2_TIME_STAMP_SYNC_1 0x268 ++#define AIFIFO2_TIME_STAMP_0 0x26C ++#define AIFIFO2_TIME_STAMP_1 0x270 ++#define AIFIFO2_TIME_STAMP_2 0x274 ++#define AIFIFO2_TIME_STAMP_3 0x278 ++#define AIFIFO2_TIME_STAMP_LENGTH 0x27C ++#define IEC958_TIME_STAMP_CNTL 0x280 ++#define IEC958_TIME_STAMP_SYNC_0 0x284 ++#define IEC958_TIME_STAMP_SYNC_1 0x288 ++#define IEC958_TIME_STAMP_0 0x28C ++#define IEC958_TIME_STAMP_1 0x290 ++#define IEC958_TIME_STAMP_2 0x294 ++#define IEC958_TIME_STAMP_3 0x298 ++#define IEC958_TIME_STAMP_LENGTH 0x29C ++#define AIU_MEM_AIFIFO2_MEM_CTL 0x2A0 ++#define AIU_I2S_CBUS_DDR_CNTL 0x2A4 ++#define AIU_I2S_CBUS_DDR_WDATA 0x2A8 ++#define AIU_I2S_CBUS_DDR_ADDR 0x2AC ++ ++#endif /* _AIU_REGS_H_ */ +diff --git a/sound/soc/meson/audin-regs.h b/sound/soc/meson/audin-regs.h +new file mode 100644 +index 000000000000..f224610e80e7 +--- /dev/null ++++ b/sound/soc/meson/audin-regs.h +@@ -0,0 +1,148 @@ ++/* ++ * Copyright (C) 2017 BayLibre, SAS ++ * Author: Jerome Brunet ++ * Copyright (C) 2017 Amlogic, Inc. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#ifndef _AUDIN_REGS_H_ ++#define _AUDIN_REGS_H_ ++ ++/* ++ * Note : ++ * Datasheet issue page 196 ++ * AUDIN_MUTE_VAL 0x35 => impossible: Already assigned to AUDIN_FIFO1_PTR ++ * AUDIN_FIFO1_PTR is more likely to be correct here since surrounding registers ++ * also deal with AUDIN_FIFO1 ++ * ++ * Clarification needed from Amlogic ++ */ ++ ++#define AUDIN_SPDIF_MODE 0x000 ++#define AUDIN_SPDIF_FS_CLK_RLTN 0x004 ++#define AUDIN_SPDIF_CHNL_STS_A 0x008 ++#define AUDIN_SPDIF_CHNL_STS_B 0x00C ++#define AUDIN_SPDIF_MISC 0x010 ++#define AUDIN_SPDIF_NPCM_PCPD 0x014 ++#define AUDIN_SPDIF_END 0x03C /* Unknown */ ++#define AUDIN_I2SIN_CTRL 0x040 ++#define AUDIN_SOURCE_SEL 0x044 ++#define AUDIN_DECODE_FORMAT 0x048 ++#define AUDIN_DECODE_CONTROL_STATUS 0x04C ++#define AUDIN_DECODE_CHANNEL_STATUS_A_0 0x050 ++#define AUDIN_DECODE_CHANNEL_STATUS_A_1 0x054 ++#define AUDIN_DECODE_CHANNEL_STATUS_A_2 0x058 ++#define AUDIN_DECODE_CHANNEL_STATUS_A_3 0x05C ++#define AUDIN_DECODE_CHANNEL_STATUS_A_4 0x060 ++#define AUDIN_DECODE_CHANNEL_STATUS_A_5 0x064 ++#define AUDIN_FIFO0_START 0x080 ++#define AUDIN_FIFO0_END 0x084 ++#define AUDIN_FIFO0_PTR 0x088 ++#define AUDIN_FIFO0_INTR 0x08C ++#define AUDIN_FIFO0_RDPTR 0x090 ++#define AUDIN_FIFO0_CTRL 0x094 ++#define AUDIN_FIFO0_CTRL1 0x098 ++#define AUDIN_FIFO0_LVL0 0x09C ++#define AUDIN_FIFO0_LVL1 0x0A0 ++#define AUDIN_FIFO0_LVL2 0x0A4 ++#define AUDIN_FIFO0_REQID 0x0C0 ++#define AUDIN_FIFO0_WRAP 0x0C4 ++#define AUDIN_FIFO1_START 0x0CC ++#define AUDIN_FIFO1_END 0x0D0 ++#define AUDIN_FIFO1_PTR 0x0D4 ++#define AUDIN_FIFO1_INTR 0x0D8 ++#define AUDIN_FIFO1_RDPTR 0x0DC ++#define AUDIN_FIFO1_CTRL 0x0E0 ++#define AUDIN_FIFO1_CTRL1 0x0E4 ++#define AUDIN_FIFO1_LVL0 0x100 ++#define AUDIN_FIFO1_LVL1 0x104 ++#define AUDIN_FIFO1_LVL2 0x108 ++#define AUDIN_FIFO1_REQID 0x10C ++#define AUDIN_FIFO1_WRAP 0x110 ++#define AUDIN_FIFO2_START 0x114 ++#define AUDIN_FIFO2_END 0x118 ++#define AUDIN_FIFO2_PTR 0x11C ++#define AUDIN_FIFO2_INTR 0x120 ++#define AUDIN_FIFO2_RDPTR 0x124 ++#define AUDIN_FIFO2_CTRL 0x128 ++#define AUDIN_FIFO2_CTRL1 0x12C ++#define AUDIN_FIFO2_LVL0 0x130 ++#define AUDIN_FIFO2_LVL1 0x134 ++#define AUDIN_FIFO2_LVL2 0x138 ++#define AUDIN_FIFO2_REQID 0x13C ++#define AUDIN_FIFO2_WRAP 0x140 ++#define AUDIN_INT_CTRL 0x144 ++#define AUDIN_FIFO_INT 0x148 ++#define PCMIN_CTRL0 0x180 ++#define PCMIN_CTRL1 0x184 ++#define PCMIN1_CTRL0 0x188 ++#define PCMIN1_CTRL1 0x18C ++#define PCMOUT_CTRL0 0x1C0 ++#define PCMOUT_CTRL1 0x1C4 ++#define PCMOUT_CTRL2 0x1C8 ++#define PCMOUT_CTRL3 0x1CC ++#define PCMOUT1_CTRL0 0x1D0 ++#define PCMOUT1_CTRL1 0x1D4 ++#define PCMOUT1_CTRL2 0x1D8 ++#define PCMOUT1_CTRL3 0x1DC ++#define AUDOUT_CTRL 0x200 ++#define AUDOUT_CTRL1 0x204 ++#define AUDOUT_BUF0_STA 0x208 ++#define AUDOUT_BUF0_EDA 0x20C ++#define AUDOUT_BUF0_WPTR 0x210 ++#define AUDOUT_BUF1_STA 0x214 ++#define AUDOUT_BUF1_EDA 0x218 ++#define AUDOUT_BUF1_WPTR 0x21C ++#define AUDOUT_FIFO_RPTR 0x220 ++#define AUDOUT_INTR_PTR 0x224 ++#define AUDOUT_FIFO_STS 0x228 ++#define AUDOUT1_CTRL 0x240 ++#define AUDOUT1_CTRL1 0x244 ++#define AUDOUT1_BUF0_STA 0x248 ++#define AUDOUT1_BUF0_EDA 0x24C ++#define AUDOUT1_BUF0_WPTR 0x250 ++#define AUDOUT1_BUF1_STA 0x254 ++#define AUDOUT1_BUF1_EDA 0x258 ++#define AUDOUT1_BUF1_WPTR 0x25C ++#define AUDOUT1_FIFO_RPTR 0x260 ++#define AUDOUT1_INTR_PTR 0x264 ++#define AUDOUT1_FIFO_STS 0x268 ++#define AUDIN_HDMI_MEAS_CTRL 0x280 ++#define AUDIN_HDMI_MEAS_CYCLES_M1 0x284 ++#define AUDIN_HDMI_MEAS_INTR_MASKN 0x288 ++#define AUDIN_HDMI_MEAS_INTR_STAT 0x28C ++#define AUDIN_HDMI_REF_CYCLES_STAT_0 0x290 ++#define AUDIN_HDMI_REF_CYCLES_STAT_1 0x294 ++#define AUDIN_HDMIRX_AFIFO_STAT 0x298 ++#define AUDIN_FIFO0_PIO_STS 0x2C0 ++#define AUDIN_FIFO0_PIO_RDL 0x2C4 ++#define AUDIN_FIFO0_PIO_RDH 0x2C8 ++#define AUDIN_FIFO1_PIO_STS 0x2CC ++#define AUDIN_FIFO1_PIO_RDL 0x2D0 ++#define AUDIN_FIFO1_PIO_RDH 0x2D4 ++#define AUDIN_FIFO2_PIO_STS 0x2D8 ++#define AUDIN_FIFO2_PIO_RDL 0x2DC ++#define AUDIN_FIFO2_PIO_RDH 0x2E0 ++#define AUDOUT_FIFO_PIO_STS 0x2E4 ++#define AUDOUT_FIFO_PIO_WRL 0x2E8 ++#define AUDOUT_FIFO_PIO_WRH 0x2EC ++#define AUDOUT1_FIFO_PIO_STS 0x2F0 /* Unknown */ ++#define AUDOUT1_FIFO_PIO_WRL 0x2F4 /* Unknown */ ++#define AUDOUT1_FIFO_PIO_WRH 0x2F8 /* Unknown */ ++#define AUD_RESAMPLE_CTRL0 0x2FC ++#define AUD_RESAMPLE_CTRL1 0x300 ++#define AUD_RESAMPLE_STATUS 0x304 ++ ++#endif /* _AUDIN_REGS_H_ */ +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0004-ASoC-meson-add-aiu-i2s-dma-support.patch b/buildroot-external/board/hardkernel/patches/linux/0004-ASoC-meson-add-aiu-i2s-dma-support.patch new file mode 100644 index 000000000..926c8182d --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0004-ASoC-meson-add-aiu-i2s-dma-support.patch @@ -0,0 +1,424 @@ +From 61d387ffa57865531ead1a33d63b1d53a99e808b Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Thu, 30 Mar 2017 12:14:40 +0200 +Subject: [PATCH 04/53] ASoC: meson: add aiu i2s dma support + +Add support for the i2s output dma which is part of the AIU block + +Signed-off-by: Jerome Brunet +--- + sound/soc/meson/Kconfig | 7 + + sound/soc/meson/Makefile | 4 +- + sound/soc/meson/aiu-i2s-dma.c | 370 ++++++++++++++++++++++++++++++++++ + 3 files changed, 380 insertions(+), 1 deletion(-) + create mode 100644 sound/soc/meson/aiu-i2s-dma.c + +diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig +index ed432d488b74..6e030b5c7804 100644 +--- a/sound/soc/meson/Kconfig ++++ b/sound/soc/meson/Kconfig +@@ -73,3 +73,10 @@ menuconfig SND_SOC_MESON + Say Y or M if you want to add support for codecs attached to + the Amlogic Meson SoCs Audio interfaces. You will also need to + select the audio interfaces to support below. ++ ++config SND_SOC_MESON_I2S ++ tristate "Meson i2s interface" ++ depends on SND_SOC_MESON ++ help ++ Say Y or M if you want to add support for i2s dma driver for Amlogic ++ Meson SoCs. +diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile +index 768d7c414649..57960077aab2 100644 +--- a/sound/soc/meson/Makefile ++++ b/sound/soc/meson/Makefile +@@ -21,5 +21,7 @@ obj-$(CONFIG_SND_MESON_AXG_SOUND_CARD) += snd-soc-meson-axg-sound-card.o + obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o + + snd-soc-meson-audio-core-objs := audio-core.o ++snd-soc-meson-aiu-i2s-dma-objs := aiu-i2s-dma.o + +-obj-$(CONFIG_SND_SOC_MESON) += snd-soc-meson-audio-core.o +\ No newline at end of file ++obj-$(CONFIG_SND_SOC_MESON) += snd-soc-meson-audio-core.o ++obj-$(CONFIG_SND_SOC_MESON_I2S) += snd-soc-meson-aiu-i2s-dma.o +\ No newline at end of file +diff --git a/sound/soc/meson/aiu-i2s-dma.c b/sound/soc/meson/aiu-i2s-dma.c +new file mode 100644 +index 000000000000..2684bd0db19e +--- /dev/null ++++ b/sound/soc/meson/aiu-i2s-dma.c +@@ -0,0 +1,370 @@ ++/* ++ * Copyright (C) 2017 BayLibre, SAS ++ * Author: Jerome Brunet ++ * Copyright (C) 2017 Amlogic, Inc. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "aiu-regs.h" ++#include "audio-core.h" ++ ++#define DRV_NAME "meson-aiu-i2s-dma" ++ ++struct aiu_i2s_dma { ++ struct meson_audio_core_data *core; ++ struct clk *fast; ++ int irq; ++}; ++ ++#define AIU_MEM_I2S_BUF_CNTL_INIT BIT(0) ++#define AIU_MEM_I2S_CONTROL_INIT BIT(0) ++#define AIU_MEM_I2S_CONTROL_FILL_EN BIT(1) ++#define AIU_MEM_I2S_CONTROL_EMPTY_EN BIT(2) ++#define AIU_MEM_I2S_CONTROL_MODE_16BIT BIT(6) ++#define AIU_MEM_I2S_CONTROL_BUSY BIT(7) ++#define AIU_MEM_I2S_CONTROL_DATA_READY BIT(8) ++#define AIU_MEM_I2S_CONTROL_LEVEL_CNTL BIT(9) ++#define AIU_MEM_I2S_MASKS_IRQ_BLOCK_MASK GENMASK(31, 16) ++#define AIU_MEM_I2S_MASKS_IRQ_BLOCK(n) ((n) << 16) ++#define AIU_MEM_I2S_MASKS_CH_MEM_MASK GENMASK(15, 8) ++#define AIU_MEM_I2S_MASKS_CH_MEM(ch) ((ch) << 8) ++#define AIU_MEM_I2S_MASKS_CH_RD_MASK GENMASK(7, 0) ++#define AIU_MEM_I2S_MASKS_CH_RD(ch) ((ch) << 0) ++#define AIU_RST_SOFT_I2S_FAST_DOMAIN BIT(0) ++#define AIU_RST_SOFT_I2S_SLOW_DOMAIN BIT(1) ++ ++/* ++ * The DMA works by i2s "blocks" (or DMA burst). The burst size and the memory ++ * layout expected depends on the mode of operation. ++ * ++ * - Normal mode: The channels are expected to be packed in 32 bytes groups ++ * interleaved the buffer. AIU_MEM_I2S_MASKS_CH_MEM is a bitfield representing ++ * the channels present in memory. AIU_MEM_I2S_MASKS_CH_MEM represents the ++ * channels read by the DMA. This is very flexible but the unsual memory layout ++ * makes it less easy to deal with. The burst size is 32 bytes times the number ++ * of channels read. ++ * ++ * - Split mode: ++ * Classical channel interleaved frame organisation. In this mode, ++ * AIU_MEM_I2S_MASKS_CH_MEM and AIU_MEM_I2S_MASKS_CH_MEM must be set to 0xff and ++ * the burst size is fixed to 256 bytes. The input can be either 2 or 8 ++ * channels. ++ * ++ * The following driver implements the split mode. ++ */ ++ ++#define AIU_I2S_DMA_BURST 256 ++ ++static struct snd_pcm_hardware aiu_i2s_dma_hw = { ++ .info = (SNDRV_PCM_INFO_INTERLEAVED | ++ SNDRV_PCM_INFO_MMAP | ++ SNDRV_PCM_INFO_MMAP_VALID | ++ SNDRV_PCM_INFO_PAUSE), ++ ++ .formats = (SNDRV_PCM_FMTBIT_S16_LE | ++ SNDRV_PCM_FMTBIT_S24_LE | ++ SNDRV_PCM_FMTBIT_S32_LE), ++ ++ /* ++ * TODO: The DMA can change the endianness, the msb position ++ * and deal with unsigned - support this later on ++ */ ++ ++ .rate_min = 8000, ++ .rate_max = 192000, ++ .channels_min = 2, ++ .channels_max = 8, ++ .period_bytes_min = AIU_I2S_DMA_BURST, ++ .period_bytes_max = AIU_I2S_DMA_BURST * 65535, ++ .periods_min = 2, ++ .periods_max = UINT_MAX, ++ .buffer_bytes_max = 1 * 1024 * 1024, ++ .fifo_size = 0, ++}; ++ ++static struct aiu_i2s_dma *aiu_i2s_dma_priv(struct snd_pcm_substream *s) ++{ ++ struct snd_soc_pcm_runtime *rtd = s->private_data; ++ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); ++ ++ return snd_soc_component_get_drvdata(component); ++} ++ ++static snd_pcm_uframes_t ++aiu_i2s_dma_pointer(struct snd_pcm_substream *substream) ++{ ++ struct snd_pcm_runtime *runtime = substream->runtime; ++ struct aiu_i2s_dma *priv = aiu_i2s_dma_priv(substream); ++ unsigned int addr; ++ int ret; ++ ++ ret = regmap_read(priv->core->aiu, AIU_MEM_I2S_RD_PTR, ++ &addr); ++ if (ret) ++ return 0; ++ ++ return bytes_to_frames(runtime, addr - (unsigned int)runtime->dma_addr); ++} ++ ++static void __dma_enable(struct aiu_i2s_dma *priv, bool enable) ++{ ++ unsigned int en_mask = (AIU_MEM_I2S_CONTROL_FILL_EN | ++ AIU_MEM_I2S_CONTROL_EMPTY_EN); ++ ++ regmap_update_bits(priv->core->aiu, AIU_MEM_I2S_CONTROL, en_mask, ++ enable ? en_mask : 0); ++ ++} ++ ++static int aiu_i2s_dma_trigger(struct snd_pcm_substream *substream, int cmd) ++{ ++ struct aiu_i2s_dma *priv = aiu_i2s_dma_priv(substream); ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ __dma_enable(priv, true); ++ break; ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ case SNDRV_PCM_TRIGGER_STOP: ++ __dma_enable(priv, false); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static void __dma_init_mem(struct aiu_i2s_dma *priv) ++{ ++ regmap_update_bits(priv->core->aiu, AIU_MEM_I2S_CONTROL, ++ AIU_MEM_I2S_CONTROL_INIT, ++ AIU_MEM_I2S_CONTROL_INIT); ++ regmap_update_bits(priv->core->aiu, AIU_MEM_I2S_BUF_CNTL, ++ AIU_MEM_I2S_BUF_CNTL_INIT, ++ AIU_MEM_I2S_BUF_CNTL_INIT); ++ ++ regmap_update_bits(priv->core->aiu, AIU_MEM_I2S_CONTROL, ++ AIU_MEM_I2S_CONTROL_INIT, ++ 0); ++ regmap_update_bits(priv->core->aiu, AIU_MEM_I2S_BUF_CNTL, ++ AIU_MEM_I2S_BUF_CNTL_INIT, ++ 0); ++} ++ ++static int aiu_i2s_dma_prepare(struct snd_pcm_substream *substream) ++{ ++ struct aiu_i2s_dma *priv = aiu_i2s_dma_priv(substream); ++ ++ __dma_init_mem(priv); ++ ++ return 0; ++} ++ ++static int aiu_i2s_dma_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_pcm_runtime *runtime = substream->runtime; ++ struct aiu_i2s_dma *priv = aiu_i2s_dma_priv(substream); ++ int ret; ++ u32 burst_num, mem_ctl; ++ dma_addr_t end_ptr; ++ ++ ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); ++ if (ret < 0) ++ return ret; ++ ++ /* Setup memory layout */ ++ if (params_physical_width(params) == 16) ++ mem_ctl = AIU_MEM_I2S_CONTROL_MODE_16BIT; ++ else ++ mem_ctl = 0; ++ ++ regmap_update_bits(priv->core->aiu, AIU_MEM_I2S_CONTROL, ++ AIU_MEM_I2S_CONTROL_MODE_16BIT, ++ mem_ctl); ++ ++ /* Initialize memory pointers */ ++ regmap_write(priv->core->aiu, AIU_MEM_I2S_START_PTR, runtime->dma_addr); ++ regmap_write(priv->core->aiu, AIU_MEM_I2S_RD_PTR, runtime->dma_addr); ++ ++ /* The end pointer is the address of the last valid block */ ++ end_ptr = runtime->dma_addr + runtime->dma_bytes - AIU_I2S_DMA_BURST; ++ regmap_write(priv->core->aiu, AIU_MEM_I2S_END_PTR, end_ptr); ++ ++ /* Memory masks */ ++ burst_num = params_period_bytes(params) / AIU_I2S_DMA_BURST; ++ regmap_write(priv->core->aiu, AIU_MEM_I2S_MASKS, ++ AIU_MEM_I2S_MASKS_CH_RD(0xff) | ++ AIU_MEM_I2S_MASKS_CH_MEM(0xff) | ++ AIU_MEM_I2S_MASKS_IRQ_BLOCK(burst_num)); ++ ++ return 0; ++} ++ ++static int aiu_i2s_dma_hw_free(struct snd_pcm_substream *substream) ++{ ++ return snd_pcm_lib_free_pages(substream); ++} ++ ++ ++static irqreturn_t aiu_i2s_dma_irq_block(int irq, void *dev_id) ++{ ++ struct snd_pcm_substream *playback = dev_id; ++ ++ snd_pcm_period_elapsed(playback); ++ ++ return IRQ_HANDLED; ++} ++ ++static int aiu_i2s_dma_open(struct snd_pcm_substream *substream) ++{ ++ struct aiu_i2s_dma *priv = aiu_i2s_dma_priv(substream); ++ int ret; ++ ++ snd_soc_set_runtime_hwparams(substream, &aiu_i2s_dma_hw); ++ ++ /* ++ * Make sure the buffer and period size are multiple of the DMA burst ++ * size ++ */ ++ ret = snd_pcm_hw_constraint_step(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, ++ AIU_I2S_DMA_BURST); ++ if (ret) ++ return ret; ++ ++ ret = snd_pcm_hw_constraint_step(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, ++ AIU_I2S_DMA_BURST); ++ if (ret) ++ return ret; ++ ++ /* Request the I2S DDR irq */ ++ ret = request_irq(priv->irq, aiu_i2s_dma_irq_block, 0, ++ DRV_NAME, substream); ++ if (ret) ++ return ret; ++ ++ /* Power up the i2s fast domain - can't write the registers w/o it */ ++ ret = clk_prepare_enable(priv->fast); ++ if (ret) ++ return ret; ++ ++ /* Make sure the dma is initially disabled */ ++ __dma_enable(priv, false); ++ ++ return 0; ++} ++ ++static int aiu_i2s_dma_close(struct snd_pcm_substream *substream) ++{ ++ struct aiu_i2s_dma *priv = aiu_i2s_dma_priv(substream); ++ ++ clk_disable_unprepare(priv->fast); ++ free_irq(priv->irq, substream); ++ ++ return 0; ++} ++ ++static const struct snd_pcm_ops aiu_i2s_dma_ops = { ++ .open = aiu_i2s_dma_open, ++ .close = aiu_i2s_dma_close, ++ .ioctl = snd_pcm_lib_ioctl, ++ .hw_params = aiu_i2s_dma_hw_params, ++ .hw_free = aiu_i2s_dma_hw_free, ++ .prepare = aiu_i2s_dma_prepare, ++ .pointer = aiu_i2s_dma_pointer, ++ .trigger = aiu_i2s_dma_trigger, ++}; ++ ++static int aiu_i2s_dma_new(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct snd_card *card = rtd->card->snd_card; ++ size_t size = aiu_i2s_dma_hw.buffer_bytes_max; ++ ++ return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, ++ SNDRV_DMA_TYPE_DEV, ++ card->dev, size, size); ++} ++ ++static const struct snd_soc_component_driver aiu_i2s_platform = { ++ .ops = &aiu_i2s_dma_ops, ++ .pcm_new = aiu_i2s_dma_new, ++ .name = DRV_NAME, ++}; ++ ++static int aiu_i2s_dma_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct aiu_i2s_dma *priv; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, priv); ++ priv->core = dev_get_drvdata(dev->parent); ++ ++ priv->fast = devm_clk_get(dev, "fast"); ++ if (IS_ERR(priv->fast)) { ++ if (PTR_ERR(priv->fast) != -EPROBE_DEFER) ++ dev_err(dev, "Can't get i2s fast domain clock\n"); ++ return PTR_ERR(priv->fast); ++ } ++ ++ priv->irq = platform_get_irq(pdev, 0); ++ if (priv->irq <= 0) { ++ dev_err(dev, "Can't get i2s ddr irq\n"); ++ return priv->irq; ++ } ++ ++ return devm_snd_soc_register_component(dev, &aiu_i2s_platform, ++ NULL, 0); ++} ++ ++static const struct of_device_id aiu_i2s_dma_of_match[] = { ++ { .compatible = "amlogic,meson-aiu-i2s-dma", }, ++ { .compatible = "amlogic,meson-gxbb-aiu-i2s-dma", }, ++ { .compatible = "amlogic,meson-gxl-aiu-i2s-dma", }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, aiu_i2s_dma_of_match); ++ ++static struct platform_driver aiu_i2s_dma_pdrv = { ++ .probe = aiu_i2s_dma_probe, ++ .driver = { ++ .name = DRV_NAME, ++ .of_match_table = aiu_i2s_dma_of_match, ++ }, ++}; ++module_platform_driver(aiu_i2s_dma_pdrv); ++ ++MODULE_DESCRIPTION("Meson AIU i2s DMA ASoC Driver"); ++MODULE_AUTHOR("Jerome Brunet "); ++MODULE_LICENSE("GPL v2"); +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0005-ASoC-meson-add-initial-i2s-dai-support.patch b/buildroot-external/board/hardkernel/patches/linux/0005-ASoC-meson-add-initial-i2s-dai-support.patch new file mode 100644 index 000000000..0359ef465 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0005-ASoC-meson-add-initial-i2s-dai-support.patch @@ -0,0 +1,518 @@ +From 91eb80de0a4425e8856484d6480b2e347ccfa83d Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Thu, 30 Mar 2017 12:17:27 +0200 +Subject: [PATCH 05/53] ASoC: meson: add initial i2s dai support + +Add support for the i2s dai found on Amlogic Meson SoC family. +With this initial implementation, only playback is supported. +Capture will be part of furture work. + +Signed-off-by: Jerome Brunet +--- + sound/soc/meson/Kconfig | 2 +- + sound/soc/meson/Makefile | 4 +- + sound/soc/meson/i2s-dai.c | 465 ++++++++++++++++++++++++++++++++++++++ + 3 files changed, 469 insertions(+), 2 deletions(-) + create mode 100644 sound/soc/meson/i2s-dai.c + +diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig +index 6e030b5c7804..5904e9e50569 100644 +--- a/sound/soc/meson/Kconfig ++++ b/sound/soc/meson/Kconfig +@@ -78,5 +78,5 @@ config SND_SOC_MESON_I2S + tristate "Meson i2s interface" + depends on SND_SOC_MESON + help +- Say Y or M if you want to add support for i2s dma driver for Amlogic ++ Say Y or M if you want to add support for i2s driver for Amlogic + Meson SoCs. +diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile +index 57960077aab2..b8641f9f7fc1 100644 +--- a/sound/soc/meson/Makefile ++++ b/sound/soc/meson/Makefile +@@ -22,6 +22,8 @@ obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o + + snd-soc-meson-audio-core-objs := audio-core.o + snd-soc-meson-aiu-i2s-dma-objs := aiu-i2s-dma.o ++snd-soc-meson-i2s-dai-objs := i2s-dai.o + + obj-$(CONFIG_SND_SOC_MESON) += snd-soc-meson-audio-core.o +-obj-$(CONFIG_SND_SOC_MESON_I2S) += snd-soc-meson-aiu-i2s-dma.o +\ No newline at end of file ++obj-$(CONFIG_SND_SOC_MESON_I2S) += snd-soc-meson-aiu-i2s-dma.o ++obj-$(CONFIG_SND_SOC_MESON_I2S) += snd-soc-meson-i2s-dai.o +\ No newline at end of file +diff --git a/sound/soc/meson/i2s-dai.c b/sound/soc/meson/i2s-dai.c +new file mode 100644 +index 000000000000..1008af8d3972 +--- /dev/null ++++ b/sound/soc/meson/i2s-dai.c +@@ -0,0 +1,465 @@ ++/* ++ * Copyright (C) 2017 BayLibre, SAS ++ * Author: Jerome Brunet ++ * Copyright (C) 2017 Amlogic, Inc. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include "aiu-regs.h" ++#include "audio-core.h" ++ ++#define DRV_NAME "meson-i2s-dai" ++ ++struct meson_i2s_dai { ++ struct meson_audio_core_data *core; ++ struct clk *mclk; ++ struct clk *bclks; ++ struct clk *iface; ++ struct clk *fast; ++ bool bclks_idle; ++}; ++ ++#define AIU_CLK_CTRL_I2S_DIV_EN BIT(0) ++#define AIU_CLK_CTRL_I2S_DIV_MASK GENMASK(3, 2) ++#define AIU_CLK_CTRL_AOCLK_POLARITY_MASK BIT(6) ++#define AIU_CLK_CTRL_AOCLK_POLARITY_NORMAL (0 << 6) ++#define AIU_CLK_CTRL_AOCLK_POLARITY_INVERTED (1 << 6) ++#define AIU_CLK_CTRL_ALRCLK_POLARITY_MASK BIT(7) ++#define AIU_CLK_CTRL_ALRCLK_POLARITY_NORMAL (0 << 7) ++#define AIU_CLK_CTRL_ALRCLK_POLARITY_INVERTED (1 << 7) ++#define AIU_CLK_CTRL_ALRCLK_SKEW_MASK GENMASK(9, 8) ++#define AIU_CLK_CTRL_ALRCLK_LEFT_J (0 << 8) ++#define AIU_CLK_CTRL_ALRCLK_I2S (1 << 8) ++#define AIU_CLK_CTRL_ALRCLK_RIGHT_J (2 << 8) ++#define AIU_CLK_CTRL_MORE_I2S_DIV_MASK GENMASK(5, 0) ++#define AIU_CLK_CTRL_MORE_I2S_DIV(div) (((div) - 1) << 0) ++#define AIU_CODEC_DAC_LRCLK_CTRL_DIV_MASK GENMASK(11, 0) ++#define AIU_CODEC_DAC_LRCLK_CTRL_DIV(div) (((div) - 1) << 0) ++#define AIU_I2S_DAC_CFG_PAYLOAD_SIZE_MASK GENMASK(1, 0) ++#define AIU_I2S_DAC_CFG_AOCLK_32 (0 << 0) ++#define AIU_I2S_DAC_CFG_AOCLK_48 (2 << 0) ++#define AIU_I2S_DAC_CFG_AOCLK_64 (3 << 0) ++#define AIU_I2S_MISC_HOLD_EN BIT(2) ++#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) ++#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) ++#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) ++#define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11) ++ ++static void __hold(struct meson_i2s_dai *priv, bool enable) ++{ ++ regmap_update_bits(priv->core->aiu, AIU_I2S_MISC, ++ AIU_I2S_MISC_HOLD_EN, ++ enable ? AIU_I2S_MISC_HOLD_EN : 0); ++} ++ ++static void __divider_enable(struct meson_i2s_dai *priv, bool enable) ++{ ++ regmap_update_bits(priv->core->aiu, AIU_CLK_CTRL, ++ AIU_CLK_CTRL_I2S_DIV_EN, ++ enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0); ++} ++ ++static void __playback_start(struct meson_i2s_dai *priv) ++{ ++ __divider_enable(priv, true); ++ __hold(priv, false); ++} ++ ++static void __playback_stop(struct meson_i2s_dai *priv, bool clk_force) ++{ ++ __hold(priv, true); ++ /* Disable the bit clks if necessary */ ++ if (clk_force || !priv->bclks_idle) ++ __divider_enable(priv, false); ++} ++ ++static int meson_i2s_dai_trigger(struct snd_pcm_substream *substream, int cmd, ++ struct snd_soc_dai *dai) ++{ ++ struct meson_i2s_dai *priv = snd_soc_dai_get_drvdata(dai); ++ bool clk_force_stop = false; ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ __playback_start(priv); ++ return 0; ++ ++ case SNDRV_PCM_TRIGGER_STOP: ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ clk_force_stop = true; ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ __playback_stop(priv, clk_force_stop); ++ return 0; ++ ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int __bclks_set_rate(struct meson_i2s_dai *priv, unsigned int srate, ++ unsigned int width) ++{ ++ unsigned int fs; ++ ++ /* Get the oversampling factor */ ++ fs = DIV_ROUND_CLOSEST(clk_get_rate(priv->mclk), srate); ++ ++ /* ++ * This DAI is usually connected to the dw-hdmi which does not support ++ * bclk being 32 * lrclk or 48 * lrclk ++ * Restrict to blck = 64 * lrclk ++ */ ++ if (fs % 64) ++ return -EINVAL; ++ ++ /* Set the divider between lrclk and bclk */ ++ regmap_update_bits(priv->core->aiu, AIU_I2S_DAC_CFG, ++ AIU_I2S_DAC_CFG_PAYLOAD_SIZE_MASK, ++ AIU_I2S_DAC_CFG_AOCLK_64); ++ ++ regmap_update_bits(priv->core->aiu, AIU_CODEC_DAC_LRCLK_CTRL, ++ AIU_CODEC_DAC_LRCLK_CTRL_DIV_MASK, ++ AIU_CODEC_DAC_LRCLK_CTRL_DIV(64)); ++ ++ /* Use CLK_MORE for the i2s divider */ ++ regmap_update_bits(priv->core->aiu, AIU_CLK_CTRL, ++ AIU_CLK_CTRL_I2S_DIV_MASK, ++ 0); ++ ++ regmap_update_bits(priv->core->aiu, AIU_CLK_CTRL_MORE, ++ AIU_CLK_CTRL_MORE_I2S_DIV_MASK, ++ AIU_CLK_CTRL_MORE_I2S_DIV(fs / 64)); ++ ++ return 0; ++} ++ ++static int __setup_desc(struct meson_i2s_dai *priv, unsigned int width, ++ unsigned int channels) ++{ ++ u32 desc = 0; ++ ++ switch (width) { ++ case 24: ++ /* ++ * For some reason, 24 bits wide audio don't play well ++ * if the 32 bits mode is not set ++ */ ++ desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | ++ AIU_I2S_SOURCE_DESC_MODE_32BIT); ++ break; ++ case 16: ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ switch (channels) { ++ case 2: /* Nothing to do */ ++ break; ++ case 8: ++ /* TODO: Still requires testing ... */ ++ desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(priv->core->aiu, AIU_I2S_SOURCE_DESC, ++ AIU_I2S_SOURCE_DESC_MODE_8CH | ++ AIU_I2S_SOURCE_DESC_MODE_24BIT | ++ AIU_I2S_SOURCE_DESC_MODE_32BIT, ++ desc); ++ ++ return 0; ++} ++ ++static int meson_i2s_dai_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct meson_i2s_dai *priv = snd_soc_dai_get_drvdata(dai); ++ unsigned int width = params_width(params); ++ unsigned int channels = params_channels(params); ++ unsigned int rate = params_rate(params); ++ int ret; ++ ++ ret = __setup_desc(priv, width, channels); ++ if (ret) { ++ dev_err(dai->dev, "Unable set to set i2s description\n"); ++ return ret; ++ } ++ ++ ret = __bclks_set_rate(priv, rate, width); ++ if (ret) { ++ dev_err(dai->dev, "Unable set to the i2s clock rates\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int meson_i2s_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) ++{ ++ struct meson_i2s_dai *priv = snd_soc_dai_get_drvdata(dai); ++ u32 val; ++ ++ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) ++ return -EINVAL; ++ ++ /* DAI output mode */ ++ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { ++ case SND_SOC_DAIFMT_I2S: ++ val = AIU_CLK_CTRL_ALRCLK_I2S; ++ break; ++ case SND_SOC_DAIFMT_LEFT_J: ++ val = AIU_CLK_CTRL_ALRCLK_LEFT_J; ++ break; ++ case SND_SOC_DAIFMT_RIGHT_J: ++ val = AIU_CLK_CTRL_ALRCLK_RIGHT_J; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(priv->core->aiu, AIU_CLK_CTRL, ++ AIU_CLK_CTRL_ALRCLK_SKEW_MASK, ++ val); ++ ++ /* DAI clock polarity */ ++ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { ++ case SND_SOC_DAIFMT_IB_IF: ++ /* Invert both clocks */ ++ val = AIU_CLK_CTRL_ALRCLK_POLARITY_INVERTED | ++ AIU_CLK_CTRL_AOCLK_POLARITY_INVERTED; ++ break; ++ case SND_SOC_DAIFMT_IB_NF: ++ /* Invert bit clock */ ++ val = AIU_CLK_CTRL_ALRCLK_POLARITY_NORMAL | ++ AIU_CLK_CTRL_AOCLK_POLARITY_INVERTED; ++ break; ++ case SND_SOC_DAIFMT_NB_IF: ++ /* Invert frame clock */ ++ val = AIU_CLK_CTRL_ALRCLK_POLARITY_INVERTED | ++ AIU_CLK_CTRL_AOCLK_POLARITY_NORMAL; ++ break; ++ case SND_SOC_DAIFMT_NB_NF: ++ /* Normal clocks */ ++ val = AIU_CLK_CTRL_ALRCLK_POLARITY_NORMAL | ++ AIU_CLK_CTRL_AOCLK_POLARITY_NORMAL; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(priv->core->aiu, AIU_CLK_CTRL, ++ AIU_CLK_CTRL_ALRCLK_POLARITY_MASK | ++ AIU_CLK_CTRL_AOCLK_POLARITY_MASK, ++ val); ++ ++ switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { ++ case SND_SOC_DAIFMT_CONT: ++ priv->bclks_idle = true; ++ break; ++ case SND_SOC_DAIFMT_GATED: ++ priv->bclks_idle = false; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int meson_i2s_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, ++ unsigned int freq, int dir) ++{ ++ struct meson_i2s_dai *priv = snd_soc_dai_get_drvdata(dai); ++ int ret; ++ ++ if (WARN_ON(clk_id != 0)) ++ return -EINVAL; ++ ++ if (dir == SND_SOC_CLOCK_IN) ++ return 0; ++ ++ ret = clk_set_rate(priv->mclk, freq); ++ if (ret) { ++ dev_err(dai->dev, "Failed to set sysclk to %uHz", freq); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int meson_i2s_dai_startup(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct meson_i2s_dai *priv = snd_soc_dai_get_drvdata(dai); ++ int ret; ++ ++ /* Power up the i2s fast domain - can't write the registers w/o it */ ++ ret = clk_prepare_enable(priv->fast); ++ if (ret) ++ goto out_clk_fast; ++ ++ /* Make sure nothing gets out of the DAI yet */ ++ __hold(priv, true); ++ ++ /* I2S encoder needs the mixer interface gate */ ++ ret = clk_prepare_enable(priv->iface); ++ if (ret) ++ goto out_clk_iface; ++ ++ /* Enable the i2s master clock */ ++ ret = clk_prepare_enable(priv->mclk); ++ if (ret) ++ goto out_mclk; ++ ++ /* Enable the bit clock gate */ ++ ret = clk_prepare_enable(priv->bclks); ++ if (ret) ++ goto out_bclks; ++ ++ /* Make sure the interface expect a memory layout we can work with */ ++ regmap_update_bits(priv->core->aiu, AIU_I2S_SOURCE_DESC, ++ AIU_I2S_SOURCE_DESC_MODE_SPLIT, ++ AIU_I2S_SOURCE_DESC_MODE_SPLIT); ++ ++ return 0; ++ ++out_bclks: ++ clk_disable_unprepare(priv->mclk); ++out_mclk: ++ clk_disable_unprepare(priv->iface); ++out_clk_iface: ++ clk_disable_unprepare(priv->fast); ++out_clk_fast: ++ return ret; ++} ++ ++static void meson_i2s_dai_shutdown(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct meson_i2s_dai *priv = snd_soc_dai_get_drvdata(dai); ++ ++ clk_disable_unprepare(priv->bclks); ++ clk_disable_unprepare(priv->mclk); ++ clk_disable_unprepare(priv->iface); ++ clk_disable_unprepare(priv->fast); ++} ++ ++static const struct snd_soc_dai_ops meson_i2s_dai_ops = { ++ .startup = meson_i2s_dai_startup, ++ .shutdown = meson_i2s_dai_shutdown, ++ .trigger = meson_i2s_dai_trigger, ++ .hw_params = meson_i2s_dai_hw_params, ++ .set_fmt = meson_i2s_dai_set_fmt, ++ .set_sysclk = meson_i2s_dai_set_sysclk, ++}; ++ ++static struct snd_soc_dai_driver meson_i2s_dai = { ++ .playback = { ++ .stream_name = "Playback", ++ .channels_min = 2, ++ .channels_max = 8, ++ .rates = SNDRV_PCM_RATE_8000_192000, ++ .formats = (SNDRV_PCM_FMTBIT_S16_LE | ++ SNDRV_PCM_FMTBIT_S24_LE) ++ }, ++ .ops = &meson_i2s_dai_ops, ++}; ++ ++static const struct snd_soc_component_driver meson_i2s_dai_component = { ++ .name = DRV_NAME, ++}; ++ ++static int meson_i2s_dai_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct meson_i2s_dai *priv; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, priv); ++ priv->core = dev_get_drvdata(dev->parent); ++ ++ priv->fast = devm_clk_get(dev, "fast"); ++ if (IS_ERR(priv->fast)) { ++ if (PTR_ERR(priv->fast) != -EPROBE_DEFER) ++ dev_err(dev, "Can't get the i2s fast domain clock\n"); ++ return PTR_ERR(priv->fast); ++ } ++ ++ priv->iface = devm_clk_get(dev, "iface"); ++ if (IS_ERR(priv->iface)) { ++ if (PTR_ERR(priv->iface) != -EPROBE_DEFER) ++ dev_err(dev, "Can't get i2s dai clock gate\n"); ++ return PTR_ERR(priv->iface); ++ } ++ ++ priv->bclks = devm_clk_get(dev, "bclks"); ++ if (IS_ERR(priv->bclks)) { ++ if (PTR_ERR(priv->bclks) != -EPROBE_DEFER) ++ dev_err(dev, "Can't get bit clocks gate\n"); ++ return PTR_ERR(priv->bclks); ++ } ++ ++ priv->mclk = devm_clk_get(dev, "mclk"); ++ if (IS_ERR(priv->mclk)) { ++ if (PTR_ERR(priv->mclk) != -EPROBE_DEFER) ++ dev_err(dev, "failed to get the i2s master clock\n"); ++ return PTR_ERR(priv->mclk); ++ } ++ ++ return devm_snd_soc_register_component(dev, &meson_i2s_dai_component, ++ &meson_i2s_dai, 1); ++} ++ ++static const struct of_device_id meson_i2s_dai_of_match[] = { ++ { .compatible = "amlogic,meson-i2s-dai", }, ++ { .compatible = "amlogic,meson-gxbb-i2s-dai", }, ++ { .compatible = "amlogic,meson-gxl-i2s-dai", }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, meson_i2s_dai_of_match); ++ ++static struct platform_driver meson_i2s_dai_pdrv = { ++ .probe = meson_i2s_dai_probe, ++ .driver = { ++ .name = DRV_NAME, ++ .of_match_table = meson_i2s_dai_of_match, ++ }, ++}; ++module_platform_driver(meson_i2s_dai_pdrv); ++ ++MODULE_DESCRIPTION("Meson i2s DAI ASoC Driver"); ++MODULE_AUTHOR("Jerome Brunet "); ++MODULE_LICENSE("GPL v2"); +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0006-ASoC-meson-add-aiu-spdif-dma-support.patch b/buildroot-external/board/hardkernel/patches/linux/0006-ASoC-meson-add-aiu-spdif-dma-support.patch new file mode 100644 index 000000000..1e93a829f --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0006-ASoC-meson-add-aiu-spdif-dma-support.patch @@ -0,0 +1,445 @@ +From 99e6d5ba97d0615428f88850ee8366a9dc24168e Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Thu, 30 Mar 2017 13:43:52 +0200 +Subject: [PATCH 06/53] ASoC: meson: add aiu spdif dma support + +Add support for the spdif output dma which is part of the AIU block + +Signed-off-by: Jerome Brunet +--- + sound/soc/meson/Kconfig | 7 + + sound/soc/meson/Makefile | 4 +- + sound/soc/meson/aiu-spdif-dma.c | 388 ++++++++++++++++++++++++++++++++ + 3 files changed, 398 insertions(+), 1 deletion(-) + create mode 100644 sound/soc/meson/aiu-spdif-dma.c + +diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig +index 5904e9e50569..712303ff8970 100644 +--- a/sound/soc/meson/Kconfig ++++ b/sound/soc/meson/Kconfig +@@ -80,3 +80,10 @@ config SND_SOC_MESON_I2S + help + Say Y or M if you want to add support for i2s driver for Amlogic + Meson SoCs. ++ ++config SND_SOC_MESON_SPDIF ++ tristate "Meson spdif interface" ++ depends on SND_SOC_MESON ++ help ++ Say Y or M if you want to add support for spdif dma driver for Amlogic ++ Meson SoCs. +diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile +index b8641f9f7fc1..dc5164a7e164 100644 +--- a/sound/soc/meson/Makefile ++++ b/sound/soc/meson/Makefile +@@ -22,8 +22,10 @@ obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o + + snd-soc-meson-audio-core-objs := audio-core.o + snd-soc-meson-aiu-i2s-dma-objs := aiu-i2s-dma.o ++snd-soc-meson-aiu-spdif-dma-objs := aiu-spdif-dma.o + snd-soc-meson-i2s-dai-objs := i2s-dai.o + + obj-$(CONFIG_SND_SOC_MESON) += snd-soc-meson-audio-core.o + obj-$(CONFIG_SND_SOC_MESON_I2S) += snd-soc-meson-aiu-i2s-dma.o +-obj-$(CONFIG_SND_SOC_MESON_I2S) += snd-soc-meson-i2s-dai.o +\ No newline at end of file ++obj-$(CONFIG_SND_SOC_MESON_I2S) += snd-soc-meson-i2s-dai.o ++obj-$(CONFIG_SND_SOC_MESON_SPDIF) += snd-soc-meson-aiu-spdif-dma.o +\ No newline at end of file +diff --git a/sound/soc/meson/aiu-spdif-dma.c b/sound/soc/meson/aiu-spdif-dma.c +new file mode 100644 +index 000000000000..81c3b856fbf9 +--- /dev/null ++++ b/sound/soc/meson/aiu-spdif-dma.c +@@ -0,0 +1,388 @@ ++/* ++ * Copyright (C) 2017 BayLibre, SAS ++ * Author: Jerome Brunet ++ * Copyright (C) 2017 Amlogic, Inc. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "aiu-regs.h" ++#include "audio-core.h" ++ ++#define DRV_NAME "meson-aiu-spdif-dma" ++ ++struct aiu_spdif_dma { ++ struct meson_audio_core_data *core; ++ struct clk *fast; ++ int irq; ++}; ++ ++#define AIU_958_DCU_FF_CTRL_EN BIT(0) ++#define AIU_958_DCU_FF_CTRL_AUTO_DISABLE BIT(1) ++#define AIU_958_DCU_FF_CTRL_IRQ_MODE_MASK GENMASK(3, 2) ++#define AIU_958_DCU_FF_CTRL_IRQ_OUT_THD BIT(2) ++#define AIU_958_DCU_FF_CTRL_IRQ_FRAME_READ BIT(3) ++#define AIU_958_DCU_FF_CTRL_SYNC_HEAD_EN BIT(4) ++#define AIU_958_DCU_FF_CTRL_BYTE_SEEK BIT(5) ++#define AIU_958_DCU_FF_CTRL_CONTINUE BIT(6) ++#define AIU_MEM_IEC958_BUF_CNTL_INIT BIT(0) ++#define AIU_MEM_IEC958_CONTROL_INIT BIT(0) ++#define AIU_MEM_IEC958_CONTROL_FILL_EN BIT(1) ++#define AIU_MEM_IEC958_CONTROL_EMPTY_EN BIT(2) ++#define AIU_MEM_IEC958_CONTROL_ENDIAN_MASK GENMASK(5, 3) ++#define AIU_MEM_IEC958_CONTROL_RD_DDR BIT(6) ++#define AIU_MEM_IEC958_CONTROL_MODE_16BIT BIT(7) ++#define AIU_MEM_IEC958_MASKS_CH_MEM_MASK GENMASK(15, 8) ++#define AIU_MEM_IEC958_MASKS_CH_MEM(ch) ((ch) << 8) ++#define AIU_MEM_IEC958_MASKS_CH_RD_MASK GENMASK(7, 0) ++#define AIU_MEM_IEC958_MASKS_CH_RD(ch) ((ch) << 0) ++ ++#define AIU_SPDIF_DMA_BURST 8 ++#define AIU_SPDIF_BPF_MAX USHRT_MAX ++ ++static struct snd_pcm_hardware aiu_spdif_dma_hw = { ++ .info = (SNDRV_PCM_INFO_INTERLEAVED | ++ SNDRV_PCM_INFO_MMAP | ++ SNDRV_PCM_INFO_MMAP_VALID | ++ SNDRV_PCM_INFO_PAUSE), ++ ++ .formats = (SNDRV_PCM_FMTBIT_S16_LE | ++ SNDRV_PCM_FMTBIT_S24_LE | ++ SNDRV_PCM_FMTBIT_S32_LE), ++ ++ .rates = (SNDRV_PCM_RATE_32000 | ++ SNDRV_PCM_RATE_44100 | ++ SNDRV_PCM_RATE_48000 | ++ SNDRV_PCM_RATE_96000 | ++ SNDRV_PCM_RATE_192000), ++ /* ++ * TODO: The DMA can change the endianness, the msb position ++ * and deal with unsigned - support this later on ++ */ ++ ++ .channels_min = 2, ++ .channels_max = 2, ++ .period_bytes_min = AIU_SPDIF_DMA_BURST, ++ .period_bytes_max = AIU_SPDIF_BPF_MAX, ++ .periods_min = 2, ++ .periods_max = UINT_MAX, ++ .buffer_bytes_max = 1 * 1024 * 1024, ++ .fifo_size = 0, ++}; ++ ++static struct aiu_spdif_dma *aiu_spdif_dma_priv(struct snd_pcm_substream *s) ++{ ++ struct snd_soc_pcm_runtime *rtd = s->private_data; ++ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); ++ ++ return snd_soc_component_get_drvdata(component); ++} ++ ++static snd_pcm_uframes_t ++aiu_spdif_dma_pointer(struct snd_pcm_substream *substream) ++{ ++ struct snd_pcm_runtime *runtime = substream->runtime; ++ struct aiu_spdif_dma *priv = aiu_spdif_dma_priv(substream); ++ unsigned int addr; ++ int ret; ++ ++ ret = regmap_read(priv->core->aiu, AIU_MEM_IEC958_RD_PTR, ++ &addr); ++ if (ret) ++ return 0; ++ ++ return bytes_to_frames(runtime, addr - (unsigned int)runtime->dma_addr); ++} ++ ++static void __dma_enable(struct aiu_spdif_dma *priv, bool enable) ++{ ++ unsigned int en_mask = (AIU_MEM_IEC958_CONTROL_FILL_EN | ++ AIU_MEM_IEC958_CONTROL_EMPTY_EN); ++ ++ regmap_update_bits(priv->core->aiu, AIU_MEM_IEC958_CONTROL, en_mask, ++ enable ? en_mask : 0); ++} ++ ++static void __dcu_fifo_enable(struct aiu_spdif_dma *priv, bool enable) ++{ ++ regmap_update_bits(priv->core->aiu, AIU_958_DCU_FF_CTRL, ++ AIU_958_DCU_FF_CTRL_EN, ++ enable ? AIU_958_DCU_FF_CTRL_EN : 0); ++} ++ ++static int aiu_spdif_dma_trigger(struct snd_pcm_substream *substream, int cmd) ++{ ++ struct aiu_spdif_dma *priv = aiu_spdif_dma_priv(substream); ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ __dcu_fifo_enable(priv, true); ++ __dma_enable(priv, true); ++ break; ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ case SNDRV_PCM_TRIGGER_STOP: ++ __dma_enable(priv, false); ++ __dcu_fifo_enable(priv, false); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static void __dma_init_mem(struct aiu_spdif_dma *priv) ++{ ++ regmap_update_bits(priv->core->aiu, AIU_MEM_IEC958_CONTROL, ++ AIU_MEM_IEC958_CONTROL_INIT, ++ AIU_MEM_IEC958_CONTROL_INIT); ++ regmap_update_bits(priv->core->aiu, AIU_MEM_IEC958_BUF_CNTL, ++ AIU_MEM_IEC958_BUF_CNTL_INIT, ++ AIU_MEM_IEC958_BUF_CNTL_INIT); ++ ++ regmap_update_bits(priv->core->aiu, AIU_MEM_IEC958_CONTROL, ++ AIU_MEM_IEC958_CONTROL_INIT, ++ 0); ++ regmap_update_bits(priv->core->aiu, AIU_MEM_IEC958_BUF_CNTL, ++ AIU_MEM_IEC958_BUF_CNTL_INIT, ++ 0); ++} ++ ++static int aiu_spdif_dma_prepare(struct snd_pcm_substream *substream) ++{ ++ struct aiu_spdif_dma *priv = aiu_spdif_dma_priv(substream); ++ ++ __dma_init_mem(priv); ++ ++ return 0; ++} ++ ++static int __setup_memory_layout(struct aiu_spdif_dma *priv, ++ unsigned int width) ++{ ++ u32 mem_ctl = AIU_MEM_IEC958_CONTROL_RD_DDR; ++ ++ if (width == 16) ++ mem_ctl |= AIU_MEM_IEC958_CONTROL_MODE_16BIT; ++ ++ regmap_update_bits(priv->core->aiu, AIU_MEM_IEC958_CONTROL, ++ AIU_MEM_IEC958_CONTROL_ENDIAN_MASK | ++ AIU_MEM_IEC958_CONTROL_MODE_16BIT | ++ AIU_MEM_IEC958_CONTROL_RD_DDR, ++ mem_ctl); ++ ++ return 0; ++} ++ ++static int aiu_spdif_dma_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_pcm_runtime *runtime = substream->runtime; ++ struct aiu_spdif_dma *priv = aiu_spdif_dma_priv(substream); ++ int ret; ++ dma_addr_t end_ptr; ++ ++ ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); ++ if (ret < 0) ++ return ret; ++ ++ ret = __setup_memory_layout(priv, params_physical_width(params)); ++ if (ret) ++ return ret; ++ ++ /* Initialize memory pointers */ ++ regmap_write(priv->core->aiu, ++ AIU_MEM_IEC958_START_PTR, runtime->dma_addr); ++ regmap_write(priv->core->aiu, ++ AIU_MEM_IEC958_RD_PTR, runtime->dma_addr); ++ ++ /* The end pointer is the address of the last valid block */ ++ end_ptr = runtime->dma_addr + runtime->dma_bytes - AIU_SPDIF_DMA_BURST; ++ regmap_write(priv->core->aiu, AIU_MEM_IEC958_END_PTR, end_ptr); ++ ++ /* Memory masks */ ++ regmap_write(priv->core->aiu, AIU_MEM_IEC958_MASKS, ++ AIU_MEM_IEC958_MASKS_CH_RD(0xff) | ++ AIU_MEM_IEC958_MASKS_CH_MEM(0xff)); ++ ++ /* Setup the number bytes read by the FIFO between each IRQ */ ++ regmap_write(priv->core->aiu, AIU_958_BPF, params_period_bytes(params)); ++ ++ /* ++ * AUTO_DISABLE and SYNC_HEAD are enabled by default but ++ * this should be disabled in PCM (uncompressed) mode ++ */ ++ regmap_update_bits(priv->core->aiu, AIU_958_DCU_FF_CTRL, ++ AIU_958_DCU_FF_CTRL_AUTO_DISABLE | ++ AIU_958_DCU_FF_CTRL_IRQ_MODE_MASK | ++ AIU_958_DCU_FF_CTRL_SYNC_HEAD_EN, ++ AIU_958_DCU_FF_CTRL_IRQ_FRAME_READ); ++ ++ return 0; ++} ++ ++static int aiu_spdif_dma_hw_free(struct snd_pcm_substream *substream) ++{ ++ return snd_pcm_lib_free_pages(substream); ++} ++ ++static irqreturn_t aiu_spdif_dma_irq(int irq, void *dev_id) ++{ ++ struct snd_pcm_substream *playback = dev_id; ++ ++ snd_pcm_period_elapsed(playback); ++ ++ return IRQ_HANDLED; ++} ++ ++static int aiu_spdif_dma_open(struct snd_pcm_substream *substream) ++{ ++ struct aiu_spdif_dma *priv = aiu_spdif_dma_priv(substream); ++ int ret; ++ ++ snd_soc_set_runtime_hwparams(substream, &aiu_spdif_dma_hw); ++ ++ /* ++ * Make sure the buffer and period size are multiple of the DMA burst ++ * size ++ */ ++ ret = snd_pcm_hw_constraint_step(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, ++ AIU_SPDIF_DMA_BURST); ++ if (ret) ++ return ret; ++ ++ ret = snd_pcm_hw_constraint_step(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, ++ AIU_SPDIF_DMA_BURST); ++ if (ret) ++ return ret; ++ ++ /* Request the SPDIF DDR irq */ ++ ret = request_irq(priv->irq, aiu_spdif_dma_irq, 0, ++ DRV_NAME, substream); ++ if (ret) ++ return ret; ++ ++ /* Power up the spdif fast domain - can't write the register w/o it */ ++ ret = clk_prepare_enable(priv->fast); ++ if (ret) ++ return ret; ++ ++ /* Make sure the dma is initially halted */ ++ __dma_enable(priv, false); ++ __dcu_fifo_enable(priv, false); ++ ++ return 0; ++} ++ ++static int aiu_spdif_dma_close(struct snd_pcm_substream *substream) ++{ ++ struct aiu_spdif_dma *priv = aiu_spdif_dma_priv(substream); ++ ++ clk_disable_unprepare(priv->fast); ++ free_irq(priv->irq, substream); ++ ++ return 0; ++} ++ ++static const struct snd_pcm_ops aiu_spdif_dma_ops = { ++ .open = aiu_spdif_dma_open, ++ .close = aiu_spdif_dma_close, ++ .ioctl = snd_pcm_lib_ioctl, ++ .hw_params = aiu_spdif_dma_hw_params, ++ .hw_free = aiu_spdif_dma_hw_free, ++ .prepare = aiu_spdif_dma_prepare, ++ .pointer = aiu_spdif_dma_pointer, ++ .trigger = aiu_spdif_dma_trigger, ++}; ++ ++static int aiu_spdif_dma_new(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct snd_card *card = rtd->card->snd_card; ++ size_t size = aiu_spdif_dma_hw.buffer_bytes_max; ++ ++ return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, ++ SNDRV_DMA_TYPE_DEV, ++ card->dev, size, size); ++} ++ ++static const struct snd_soc_component_driver aiu_spdif_platform = { ++ .ops = &aiu_spdif_dma_ops, ++ .pcm_new = aiu_spdif_dma_new, ++ .name = DRV_NAME, ++}; ++ ++static int aiu_spdif_dma_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct aiu_spdif_dma *priv; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, priv); ++ priv->core = dev_get_drvdata(dev->parent); ++ ++ priv->fast = devm_clk_get(dev, "fast"); ++ if (IS_ERR(priv->fast)) { ++ if (PTR_ERR(priv->fast) != -EPROBE_DEFER) ++ dev_err(dev, "Can't get spdif fast domain clock\n"); ++ return PTR_ERR(priv->fast); ++ } ++ ++ priv->irq = platform_get_irq(pdev, 0); ++ if (priv->irq <= 0) { ++ dev_err(dev, "Can't get spdif ddr irq\n"); ++ return priv->irq; ++ } ++ ++ return devm_snd_soc_register_component(dev, &aiu_spdif_platform, ++ NULL, 0); ++} ++ ++static const struct of_device_id aiu_spdif_dma_of_match[] = { ++ { .compatible = "amlogic,meson-aiu-spdif-dma", }, ++ { .compatible = "amlogic,meson-gxbb-aiu-spdif-dma", }, ++ { .compatible = "amlogic,meson-gxl-aiu-spdif-dma", }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, aiu_spdif_dma_of_match); ++ ++static struct platform_driver aiu_spdif_dma_pdrv = { ++ .probe = aiu_spdif_dma_probe, ++ .driver = { ++ .name = DRV_NAME, ++ .of_match_table = aiu_spdif_dma_of_match, ++ }, ++}; ++module_platform_driver(aiu_spdif_dma_pdrv); ++ ++MODULE_DESCRIPTION("Meson AIU spdif DMA ASoC Driver"); ++MODULE_AUTHOR("Jerome Brunet "); ++MODULE_LICENSE("GPL"); +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0007-ASoC-meson-add-initial-spdif-dai-support.patch b/buildroot-external/board/hardkernel/patches/linux/0007-ASoC-meson-add-initial-spdif-dai-support.patch new file mode 100644 index 000000000..d39cae9b6 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0007-ASoC-meson-add-initial-spdif-dai-support.patch @@ -0,0 +1,432 @@ +From ecabfe253aab181bdc241cc7e16e857a3574e528 Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Thu, 30 Mar 2017 13:46:03 +0200 +Subject: [PATCH 07/53] ASoC: meson: add initial spdif dai support + +Add support for the spdif dai found on Amlogic Meson SoC family. +With this initial implementation, only uncompressed pcm playback +from the spdif dma is supported. Future work will add compressed +support, pcm playback from i2s dma and capture. + +Signed-off-by: Jerome Brunet +--- + sound/soc/meson/Kconfig | 3 +- + sound/soc/meson/Makefile | 4 +- + sound/soc/meson/spdif-dai.c | 374 ++++++++++++++++++++++++++++++++++++ + 3 files changed, 379 insertions(+), 2 deletions(-) + create mode 100644 sound/soc/meson/spdif-dai.c + +diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig +index 712303ff8970..bc3d6f22ed88 100644 +--- a/sound/soc/meson/Kconfig ++++ b/sound/soc/meson/Kconfig +@@ -84,6 +84,7 @@ config SND_SOC_MESON_I2S + config SND_SOC_MESON_SPDIF + tristate "Meson spdif interface" + depends on SND_SOC_MESON ++ select SND_PCM_IEC958 + help +- Say Y or M if you want to add support for spdif dma driver for Amlogic ++ Say Y or M if you want to add support for spdif driver for Amlogic + Meson SoCs. +diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile +index dc5164a7e164..44f79d8b91b7 100644 +--- a/sound/soc/meson/Makefile ++++ b/sound/soc/meson/Makefile +@@ -24,8 +24,10 @@ snd-soc-meson-audio-core-objs := audio-core.o + snd-soc-meson-aiu-i2s-dma-objs := aiu-i2s-dma.o + snd-soc-meson-aiu-spdif-dma-objs := aiu-spdif-dma.o + snd-soc-meson-i2s-dai-objs := i2s-dai.o ++snd-soc-meson-spdif-dai-objs := spdif-dai.o + + obj-$(CONFIG_SND_SOC_MESON) += snd-soc-meson-audio-core.o + obj-$(CONFIG_SND_SOC_MESON_I2S) += snd-soc-meson-aiu-i2s-dma.o + obj-$(CONFIG_SND_SOC_MESON_I2S) += snd-soc-meson-i2s-dai.o +-obj-$(CONFIG_SND_SOC_MESON_SPDIF) += snd-soc-meson-aiu-spdif-dma.o +\ No newline at end of file ++obj-$(CONFIG_SND_SOC_MESON_SPDIF) += snd-soc-meson-aiu-spdif-dma.o ++obj-$(CONFIG_SND_SOC_MESON_SPDIF) += snd-soc-meson-spdif-dai.o +\ No newline at end of file +diff --git a/sound/soc/meson/spdif-dai.c b/sound/soc/meson/spdif-dai.c +new file mode 100644 +index 000000000000..e7630007c84b +--- /dev/null ++++ b/sound/soc/meson/spdif-dai.c +@@ -0,0 +1,374 @@ ++/* ++ * Copyright (C) 2017 BayLibre, SAS ++ * Author: Jerome Brunet ++ * Copyright (C) 2017 Amlogic, Inc. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include "aiu-regs.h" ++#include "audio-core.h" ++ ++#define DRV_NAME "meson-spdif-dai" ++ ++struct meson_spdif_dai { ++ struct meson_audio_core_data *core; ++ struct clk *iface; ++ struct clk *fast; ++ struct clk *mclk_i958; ++ struct clk *mclk; ++}; ++ ++#define AIU_CLK_CTRL_958_DIV_EN BIT(1) ++#define AIU_CLK_CTRL_958_DIV_MASK GENMASK(5, 4) ++#define AIU_CLK_CTRL_958_DIV_MORE BIT(12) ++#define AIU_MEM_IEC958_CONTROL_MODE_LINEAR BIT(8) ++#define AIU_958_CTRL_HOLD_EN BIT(0) ++#define AIU_958_MISC_NON_PCM BIT(0) ++#define AIU_958_MISC_MODE_16BITS BIT(1) ++#define AIU_958_MISC_16BITS_ALIGN_MASK GENMASK(6, 5) ++#define AIU_958_MISC_16BITS_ALIGN(val) ((val) << 5) ++#define AIU_958_MISC_MODE_32BITS BIT(7) ++#define AIU_958_MISC_32BITS_SHIFT_MASK GENMASK(10, 8) ++#define AIU_958_MISC_32BITS_SHIFT(val) ((val) << 8) ++#define AIU_958_MISC_U_FROM_STREAM BIT(12) ++#define AIU_958_MISC_FORCE_LR BIT(13) ++ ++#define AIU_CS_WORD_LEN 4 ++ ++static void __hold(struct meson_spdif_dai *priv, bool enable) ++{ ++ regmap_update_bits(priv->core->aiu, AIU_958_CTRL, ++ AIU_958_CTRL_HOLD_EN, ++ enable ? AIU_958_CTRL_HOLD_EN : 0); ++} ++ ++static void __divider_enable(struct meson_spdif_dai *priv, bool enable) ++{ ++ regmap_update_bits(priv->core->aiu, AIU_CLK_CTRL, ++ AIU_CLK_CTRL_958_DIV_EN, ++ enable ? AIU_CLK_CTRL_958_DIV_EN : 0); ++} ++ ++static void __playback_start(struct meson_spdif_dai *priv) ++{ ++ __divider_enable(priv, true); ++ __hold(priv, false); ++} ++ ++static void __playback_stop(struct meson_spdif_dai *priv) ++{ ++ __hold(priv, true); ++ __divider_enable(priv, false); ++} ++ ++static int meson_spdif_dai_trigger(struct snd_pcm_substream *substream, int cmd, ++ struct snd_soc_dai *dai) ++{ ++ struct meson_spdif_dai *priv = snd_soc_dai_get_drvdata(dai); ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ __playback_start(priv); ++ return 0; ++ ++ case SNDRV_PCM_TRIGGER_STOP: ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ __playback_stop(priv); ++ return 0; ++ ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int __setup_spdif_clk(struct meson_spdif_dai *priv, unsigned int rate) ++{ ++ unsigned int mrate; ++ ++ /* Leave the internal divisor alone */ ++ regmap_update_bits(priv->core->aiu, AIU_CLK_CTRL, ++ AIU_CLK_CTRL_958_DIV_MASK | ++ AIU_CLK_CTRL_958_DIV_MORE, ++ 0); ++ ++ /* 2 * 32bits per subframe * 2 channels = 128 */ ++ mrate = rate * 128; ++ return clk_set_rate(priv->mclk, mrate); ++} ++ ++static int __setup_cs_word(struct meson_spdif_dai *priv, ++ struct snd_pcm_hw_params *params) ++{ ++ u8 cs[AIU_CS_WORD_LEN]; ++ u32 val; ++ int ret; ++ ++ ret = snd_pcm_create_iec958_consumer_hw_params(params, cs, ++ AIU_CS_WORD_LEN); ++ if (ret < 0) ++ return -EINVAL; ++ ++ /* Write the 1st half word */ ++ val = cs[1] | cs[0] << 8; ++ regmap_write(priv->core->aiu, AIU_958_CHSTAT_L0, val); ++ regmap_write(priv->core->aiu, AIU_958_CHSTAT_R0, val); ++ ++ /* Write the 2nd half word */ ++ val = cs[3] | cs[2] << 8; ++ regmap_write(priv->core->aiu, AIU_958_CHSTAT_L1, val); ++ regmap_write(priv->core->aiu, AIU_958_CHSTAT_R1, val); ++ ++ return 0; ++} ++ ++static int __setup_pcm_fmt(struct meson_spdif_dai *priv, ++ unsigned int width) ++{ ++ u32 val = 0; ++ ++ switch (width) { ++ case 16: ++ val |= AIU_958_MISC_MODE_16BITS; ++ val |= AIU_958_MISC_16BITS_ALIGN(2); ++ break; ++ case 32: ++ case 24: ++ /* ++ * Looks like this should only be set for 32bits mode, but the ++ * vendor kernel sets it like this for 24bits as well, let's ++ * try and see ++ */ ++ val |= AIU_958_MISC_MODE_32BITS; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ /* No idea what this actually does, copying the vendor kernel for now */ ++ val |= AIU_958_MISC_FORCE_LR; ++ val |= AIU_958_MISC_U_FROM_STREAM; ++ ++ regmap_update_bits(priv->core->aiu, AIU_958_MISC, ++ AIU_958_MISC_NON_PCM | ++ AIU_958_MISC_MODE_16BITS | ++ AIU_958_MISC_16BITS_ALIGN_MASK | ++ AIU_958_MISC_MODE_32BITS | ++ AIU_958_MISC_FORCE_LR, ++ val); ++ ++ return 0; ++} ++ ++static int meson_spdif_dai_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct meson_spdif_dai *priv = snd_soc_dai_get_drvdata(dai); ++ int ret; ++ ++ ret = __setup_spdif_clk(priv, params_rate(params)); ++ if (ret) { ++ dev_err(dai->dev, "Unable to set the spdif clock\n"); ++ return ret; ++ } ++ ++ ret = __setup_cs_word(priv, params); ++ if (ret) { ++ dev_err(dai->dev, "Unable to set the channel status word\n"); ++ return ret; ++ } ++ ++ ret = __setup_pcm_fmt(priv, params_width(params)); ++ if (ret) { ++ dev_err(dai->dev, "Unable to set the pcm format\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int meson_spdif_dai_startup(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct meson_spdif_dai *priv = snd_soc_dai_get_drvdata(dai); ++ int ret; ++ ++ /* Power up the spdif fast domain - can't write the registers w/o it */ ++ ret = clk_prepare_enable(priv->fast); ++ if (ret) ++ goto out_clk_fast; ++ ++ /* Make sure nothing gets out of the DAI yet*/ ++ __hold(priv, true); ++ ++ ret = clk_set_parent(priv->mclk, priv->mclk_i958); ++ if (ret) ++ return ret; ++ ++ /* Enable the clock gate */ ++ ret = clk_prepare_enable(priv->iface); ++ if (ret) ++ goto out_clk_iface; ++ ++ /* Enable the spdif clock */ ++ ret = clk_prepare_enable(priv->mclk); ++ if (ret) ++ goto out_mclk; ++ ++ /* ++ * Make sure the interface expect a memory layout we can work with ++ * MEM prefixed register usually belong to the DMA, but when the spdif ++ * DAI takes data from the i2s buffer, we need to make sure it works in ++ * split mode and not the "normal mode" (channel samples packed in ++ * 32 bytes groups) ++ */ ++ regmap_update_bits(priv->core->aiu, AIU_MEM_IEC958_CONTROL, ++ AIU_MEM_IEC958_CONTROL_MODE_LINEAR, ++ AIU_MEM_IEC958_CONTROL_MODE_LINEAR); ++ ++ return 0; ++ ++out_mclk: ++ clk_disable_unprepare(priv->iface); ++out_clk_iface: ++ clk_disable_unprepare(priv->fast); ++out_clk_fast: ++ return ret; ++} ++ ++static void meson_spdif_dai_shutdown(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct meson_spdif_dai *priv = snd_soc_dai_get_drvdata(dai); ++ ++ clk_disable_unprepare(priv->iface); ++ clk_disable_unprepare(priv->mclk); ++ clk_disable_unprepare(priv->fast); ++} ++ ++static const struct snd_soc_dai_ops meson_spdif_dai_ops = { ++ .startup = meson_spdif_dai_startup, ++ .shutdown = meson_spdif_dai_shutdown, ++ .trigger = meson_spdif_dai_trigger, ++ .hw_params = meson_spdif_dai_hw_params, ++}; ++ ++static struct snd_soc_dai_driver meson_spdif_dai = { ++ .playback = { ++ .stream_name = "Playback", ++ .channels_min = 2, ++ .channels_max = 2, ++ .rates = (SNDRV_PCM_RATE_32000 | ++ SNDRV_PCM_RATE_44100 | ++ SNDRV_PCM_RATE_48000 | ++ SNDRV_PCM_RATE_96000 | ++ SNDRV_PCM_RATE_192000), ++ .formats = (SNDRV_PCM_FMTBIT_S16_LE | ++ SNDRV_PCM_FMTBIT_S24_LE) ++ }, ++ .ops = &meson_spdif_dai_ops, ++}; ++ ++static const struct snd_soc_component_driver meson_spdif_dai_component = { ++ .name = DRV_NAME, ++}; ++ ++static int meson_spdif_dai_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct meson_spdif_dai *priv; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, priv); ++ priv->core = dev_get_drvdata(dev->parent); ++ ++ priv->fast = devm_clk_get(dev, "fast"); ++ if (IS_ERR(priv->fast)) { ++ if (PTR_ERR(priv->fast) != -EPROBE_DEFER) ++ dev_err(dev, "Can't get spdif fast domain clockt\n"); ++ return PTR_ERR(priv->fast); ++ } ++ ++ priv->iface = devm_clk_get(dev, "iface"); ++ if (IS_ERR(priv->iface)) { ++ if (PTR_ERR(priv->iface) != -EPROBE_DEFER) ++ dev_err(dev, ++ "Can't get the dai clock gate\n"); ++ return PTR_ERR(priv->iface); ++ } ++ ++ priv->mclk_i958 = devm_clk_get(dev, "mclk_i958"); ++ if (IS_ERR(priv->mclk_i958)) { ++ if (PTR_ERR(priv->mclk_i958) != -EPROBE_DEFER) ++ dev_err(dev, "Can't get the spdif master clock\n"); ++ return PTR_ERR(priv->mclk_i958); ++ } ++ ++ /* ++ * TODO: the spdif dai can also get its data from the i2s fifo. ++ * For this use-case, the DAI driver will need to get the i2s master ++ * clock in order to reparent the spdif clock from cts_mclk_i958 to ++ * cts_amclk ++ */ ++ ++ priv->mclk = devm_clk_get(dev, "mclk"); ++ if (IS_ERR(priv->mclk)) { ++ if (PTR_ERR(priv->mclk) != -EPROBE_DEFER) ++ dev_err(dev, "Can't get the spdif input mux clock\n"); ++ return PTR_ERR(priv->mclk); ++ } ++ ++ return devm_snd_soc_register_component(dev, &meson_spdif_dai_component, ++ &meson_spdif_dai, 1); ++} ++ ++static const struct of_device_id meson_spdif_dai_of_match[] = { ++ { .compatible = "amlogic,meson-spdif-dai", }, ++ { .compatible = "amlogic,meson-gxbb-spdif-dai", }, ++ { .compatible = "amlogic,meson-gxl-spdif-dai", }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, meson_spdif_dai_of_match); ++ ++static struct platform_driver meson_spdif_dai_pdrv = { ++ .probe = meson_spdif_dai_probe, ++ .driver = { ++ .name = DRV_NAME, ++ .of_match_table = meson_spdif_dai_of_match, ++ }, ++}; ++module_platform_driver(meson_spdif_dai_pdrv); ++ ++MODULE_DESCRIPTION("Meson spdif DAI ASoC Driver"); ++MODULE_AUTHOR("Jerome Brunet "); ++MODULE_LICENSE("GPL v2"); +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0008-ARM64-defconfig-enable-audio-support-for-meson-SoCs-.patch b/buildroot-external/board/hardkernel/patches/linux/0008-ARM64-defconfig-enable-audio-support-for-meson-SoCs-.patch new file mode 100644 index 000000000..e04c08c03 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0008-ARM64-defconfig-enable-audio-support-for-meson-SoCs-.patch @@ -0,0 +1,31 @@ +From eabd19b9bb8a62764dfd5290205cf7431e0329d6 Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Fri, 31 Mar 2017 15:55:03 +0200 +Subject: [PATCH 08/53] ARM64: defconfig: enable audio support for meson SoCs + as module + +Add audio support for meson SoCs. This includes the audio core +driver and the i2s and spdif output interfaces + +Signed-off-by: Jerome Brunet +--- + arch/arm64/configs/defconfig | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig +index ab1cb51319e7..a4bf54b3b50d 100644 +--- a/arch/arm64/configs/defconfig ++++ b/arch/arm64/configs/defconfig +@@ -464,6 +464,9 @@ CONFIG_SOUND=y + CONFIG_SND=y + CONFIG_SND_SOC=y + CONFIG_SND_BCM2835_SOC_I2S=m ++CONFIG_SND_SOC_MESON=m ++CONFIG_SND_SOC_MESON_I2S=m ++CONFIG_SND_SOC_MESON_SPDIF=m + CONFIG_SND_SOC_ROCKCHIP=m + CONFIG_SND_SOC_ROCKCHIP_I2S=m + CONFIG_SND_SOC_ROCKCHIP_SPDIF=m +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0009-ARM64-dts-meson-gx-add-audio-controller-nodes.patch b/buildroot-external/board/hardkernel/patches/linux/0009-ARM64-dts-meson-gx-add-audio-controller-nodes.patch new file mode 100644 index 000000000..2d5f7ec33 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0009-ARM64-dts-meson-gx-add-audio-controller-nodes.patch @@ -0,0 +1,189 @@ +From 8615d90edac5487f8639c5e4df40312972d7b2c9 Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Thu, 30 Mar 2017 15:19:04 +0200 +Subject: [PATCH 09/53] ARM64: dts: meson-gx: add audio controller nodes + +Add audio controller nodes for Amlogic meson gxbb and gxl. +This includes the audio-core node, the i2s and spdif DAIs, i2s and spdif +aiu DMAs. + +Audio on this SoC family is still a work in progress. More nodes are likely +to be added later on (pcm DAIs, input DMAs, etc ...) + +Signed-off-by: Jerome Brunet +--- + arch/arm64/boot/dts/amlogic/meson-gx.dtsi | 35 ++++++++++++++++++ + arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi | 39 +++++++++++++++++++++ + arch/arm64/boot/dts/amlogic/meson-gxl.dtsi | 38 ++++++++++++++++++++ + 3 files changed, 112 insertions(+) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi +index b8dc4dbb391b..6b64b63f2a68 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi +@@ -203,6 +203,41 @@ + #reset-cells = <1>; + }; + ++ audio: audio@5400 { ++ compatible = "amlogic,meson-audio-core"; ++ reg = <0x0 0x5400 0x0 0x2ac>, ++ <0x0 0xa000 0x0 0x304>; ++ reg-names = "aiu", "audin"; ++ status = "disabled"; ++ ++ aiu_i2s_dma: aiu_i2s_dma { ++ #sound-dai-cells = <0>; ++ compatible = "amlogic,meson-aiu-i2s-dma"; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ aiu_spdif_dma: aiu_spdif_dma { ++ #sound-dai-cells = <0>; ++ compatible = "amlogic,meson-aiu-spdif-dma"; ++ interrupts = ; ++ status = "disabled"; ++ }; ++ ++ i2s_dai: i2s_dai { ++ #sound-dai-cells = <0>; ++ compatible = "amlogic,meson-i2s-dai"; ++ status = "disabled"; ++ }; ++ ++ spdif_dai: spdif_dai { ++ #sound-dai-cells = <0>; ++ compatible = "amlogic,meson-spdif-dai"; ++ status = "disabled"; ++ }; ++ ++ }; ++ + uart_A: serial@84c0 { + compatible = "amlogic,meson-gx-uart"; + reg = <0x0 0x84c0 0x0 0x18>; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi +index 98cbba6809ca..79132496691f 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi +@@ -659,6 +659,35 @@ + }; + }; + ++&audio { ++ clocks = <&clkc CLKID_AIU>, ++ <&clkc CLKID_AIU_GLUE>, ++ <&clkc CLKID_I2S_SPDIF>; ++ clock-names = "aiu_top", "aiu_glue", "audin"; ++ resets = <&reset RESET_AIU>, ++ <&reset RESET_AUDIN>; ++ reset-names = "aiu", "audin"; ++}; ++ ++&aiu_i2s_dma { ++ clocks = <&clkc CLKID_I2S_OUT>; ++ clock-names = "fast"; ++}; ++ ++&aiu_spdif_dma { ++ clocks = <&clkc CLKID_IEC958>; ++ clock-names = "fast"; ++ ++}; ++ ++&i2s_dai { ++ clocks = <&clkc CLKID_I2S_OUT>, ++ <&clkc CLKID_MIXER_IFACE>, ++ <&clkc CLKID_AOCLK_GATE>, ++ <&clkc CLKID_CTS_AMCLK>; ++ clock-names = "fast", "iface", "bclks", "mclk"; ++}; ++ + &pwrc_vpu { + resets = <&reset RESET_VIU>, + <&reset RESET_VENC>, +@@ -741,6 +770,15 @@ + num-cs = <1>; + }; + ++&spdif_dai { ++ clocks = <&clkc CLKID_IEC958>, ++ <&clkc CLKID_IEC958_GATE>, ++ <&clkc CLKID_CTS_MCLK_I958>, ++ <&clkc CLKID_CTS_AMCLK>, ++ <&clkc CLKID_CTS_I958>; ++ clock-names = "fast", "iface", "mclk_i958", "mclk_i2s", "mclk"; ++}; ++ + &spifc { + clocks = <&clkc CLKID_SPI>; + }; +@@ -774,3 +812,4 @@ + compatible = "amlogic,meson-gxbb-vpu", "amlogic,meson-gx-vpu"; + power-domains = <&pwrc_vpu>; + }; ++ +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi +index c87a80e9bcc6..20922cdc2c23 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi +@@ -660,6 +660,34 @@ + }; + }; + ++&audio { ++ clocks = <&clkc CLKID_AIU>, ++ <&clkc CLKID_AIU_GLUE>, ++ <&clkc CLKID_I2S_SPDIF>; ++ clock-names = "aiu_top", "aiu_glue", "audin"; ++ resets = <&reset RESET_AIU>, ++ <&reset RESET_AUDIN>; ++ reset-names = "aiu", "audin"; ++}; ++ ++&aiu_i2s_dma { ++ clocks = <&clkc CLKID_I2S_OUT>; ++ clock-names = "fast"; ++}; ++ ++&aiu_spdif_dma { ++ clocks = <&clkc CLKID_IEC958>; ++ clock-names = "fast"; ++}; ++ ++&i2s_dai { ++ clocks = <&clkc CLKID_I2S_OUT>, ++ <&clkc CLKID_MIXER_IFACE>, ++ <&clkc CLKID_AOCLK_GATE>, ++ <&clkc CLKID_CTS_AMCLK>; ++ clock-names = "fast", "iface", "bclks", "mclk"; ++}; ++ + &pwrc_vpu { + resets = <&reset RESET_VIU>, + <&reset RESET_VENC>, +@@ -742,6 +770,15 @@ + num-cs = <1>; + }; + ++&spdif_dai { ++ clocks = <&clkc CLKID_IEC958>, ++ <&clkc CLKID_IEC958_GATE>, ++ <&clkc CLKID_CTS_MCLK_I958>, ++ <&clkc CLKID_CTS_AMCLK>, ++ <&clkc CLKID_CTS_I958>; ++ clock-names = "fast", "iface", "mclk_i958", "mclk_i2s", "mclk"; ++}; ++ + &spifc { + clocks = <&clkc CLKID_SPI>; + }; +@@ -775,3 +812,4 @@ + compatible = "amlogic,meson-gxl-vpu", "amlogic,meson-gx-vpu"; + power-domains = <&pwrc_vpu>; + }; ++ +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0010-snd-meson-activate-HDMI-audio-path.patch b/buildroot-external/board/hardkernel/patches/linux/0010-snd-meson-activate-HDMI-audio-path.patch new file mode 100644 index 000000000..b41eb405d --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0010-snd-meson-activate-HDMI-audio-path.patch @@ -0,0 +1,55 @@ +From 5608714afb7c71054a01e4ad208b3eaa044041d4 Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Fri, 7 Jul 2017 17:39:21 +0200 +Subject: [PATCH 10/53] snd: meson: activate HDMI audio path + +Signed-off-by: Neil Armstrong +--- + sound/soc/meson/i2s-dai.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/sound/soc/meson/i2s-dai.c b/sound/soc/meson/i2s-dai.c +index 1008af8d3972..63fe098ecf82 100644 +--- a/sound/soc/meson/i2s-dai.c ++++ b/sound/soc/meson/i2s-dai.c +@@ -56,8 +56,19 @@ struct meson_i2s_dai { + #define AIU_CLK_CTRL_ALRCLK_RIGHT_J (2 << 8) + #define AIU_CLK_CTRL_MORE_I2S_DIV_MASK GENMASK(5, 0) + #define AIU_CLK_CTRL_MORE_I2S_DIV(div) (((div) - 1) << 0) ++#define AIU_CLK_CTRL_MORE_HDMI_TX_SEL_MASK BIT(6) ++#define AIU_CLK_CTRL_MORE_HDMI_TX_I958_CLK (0 << 6) ++#define AIU_CLK_CTRL_MORE_HDMI_TX_INT_CLK (1 << 6) + #define AIU_CODEC_DAC_LRCLK_CTRL_DIV_MASK GENMASK(11, 0) + #define AIU_CODEC_DAC_LRCLK_CTRL_DIV(div) (((div) - 1) << 0) ++#define AIU_HDMI_CLK_DATA_CTRL_CLK_SEL_MASK GENMASK(1, 0) ++#define AIU_HDMI_CLK_DATA_CTRL_CLK_DISABLE (0 << 0) ++#define AIU_HDMI_CLK_DATA_CTRL_CLK_PCM (1 << 0) ++#define AIU_HDMI_CLK_DATA_CTRL_CLK_I2S (2 << 0) ++#define AIU_HDMI_CLK_DATA_CTRL_DATA_SEL_MASK GENMASK(5, 4) ++#define AIU_HDMI_CLK_DATA_CTRL_DATA_MUTE (0 << 4) ++#define AIU_HDMI_CLK_DATA_CTRL_DATA_PCM (1 << 4) ++#define AIU_HDMI_CLK_DATA_CTRL_DATA_I2S (2 << 4) + #define AIU_I2S_DAC_CFG_PAYLOAD_SIZE_MASK GENMASK(1, 0) + #define AIU_I2S_DAC_CFG_AOCLK_32 (0 << 0) + #define AIU_I2S_DAC_CFG_AOCLK_48 (2 << 0) +@@ -221,6 +232,17 @@ static int meson_i2s_dai_hw_params(struct snd_pcm_substream *substream, + return ret; + } + ++ /* Quick and dirty hack for HDMI */ ++ regmap_update_bits(priv->core->aiu, AIU_HDMI_CLK_DATA_CTRL, ++ AIU_HDMI_CLK_DATA_CTRL_CLK_SEL_MASK | ++ AIU_HDMI_CLK_DATA_CTRL_DATA_SEL_MASK, ++ AIU_HDMI_CLK_DATA_CTRL_CLK_I2S | ++ AIU_HDMI_CLK_DATA_CTRL_DATA_I2S); ++ ++ regmap_update_bits(priv->core->aiu, AIU_CLK_CTRL_MORE, ++ AIU_CLK_CTRL_MORE_HDMI_TX_SEL_MASK, ++ AIU_CLK_CTRL_MORE_HDMI_TX_INT_CLK); ++ + return 0; + } + +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0011-drm-meson-select-dw-hdmi-i2s-audio-for-meson-hdmi.patch b/buildroot-external/board/hardkernel/patches/linux/0011-drm-meson-select-dw-hdmi-i2s-audio-for-meson-hdmi.patch new file mode 100644 index 000000000..0876bc059 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0011-drm-meson-select-dw-hdmi-i2s-audio-for-meson-hdmi.patch @@ -0,0 +1,22 @@ +From 5b3d41b6ad8275d53b758d6d4b95441b53cd320b Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Tue, 14 Feb 2017 19:18:04 +0100 +Subject: [PATCH 11/53] drm/meson: select dw-hdmi i2s audio for meson hdmi + +Signed-off-by: Jerome Brunet +--- + drivers/gpu/drm/meson/Kconfig | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/gpu/drm/meson/Kconfig b/drivers/gpu/drm/meson/Kconfig +index 3ce51d8dfe1c..02d400b8795c 100644 +--- a/drivers/gpu/drm/meson/Kconfig ++++ b/drivers/gpu/drm/meson/Kconfig +@@ -13,3 +13,4 @@ config DRM_MESON_DW_HDMI + depends on DRM_MESON + default y if DRM_MESON + select DRM_DW_HDMI ++ select DRM_DW_HDMI_I2S_AUDIO +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0012-ARM64-dts-meson-gx-add-sound-dai-cells-to-HDMI-node.patch b/buildroot-external/board/hardkernel/patches/linux/0012-ARM64-dts-meson-gx-add-sound-dai-cells-to-HDMI-node.patch new file mode 100644 index 000000000..eb2208865 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0012-ARM64-dts-meson-gx-add-sound-dai-cells-to-HDMI-node.patch @@ -0,0 +1,38 @@ +From 461a8ba1e73d38b8cd8f8c931a8ae27676cdb085 Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Wed, 20 Sep 2017 18:01:26 +0200 +Subject: [PATCH 12/53] ARM64: dts: meson-gx: add sound-dai-cells to HDMI node + +Signed-off-by: Jerome Brunet +--- + arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi | 1 + + arch/arm64/boot/dts/amlogic/meson-gxl.dtsi | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi +index 79132496691f..2a4d506bad4e 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi +@@ -305,6 +305,7 @@ + <&clkc CLKID_CLK81>, + <&clkc CLKID_GCLK_VENCI_INT0>; + clock-names = "isfr", "iahb", "venci"; ++ #sound-dai-cells = <0>; + }; + + &sysctrl { +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi +index 20922cdc2c23..9f4b6185a61d 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi +@@ -257,6 +257,7 @@ + <&clkc CLKID_CLK81>, + <&clkc CLKID_GCLK_VENCI_INT0>; + clock-names = "isfr", "iahb", "venci"; ++ #sound-dai-cells = <0>; + }; + + &sysctrl { +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0013-ARM64-dts-meson-activate-hdmi-audio-HDMI-enabled-boa.patch b/buildroot-external/board/hardkernel/patches/linux/0013-ARM64-dts-meson-activate-hdmi-audio-HDMI-enabled-boa.patch new file mode 100644 index 000000000..27760c288 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0013-ARM64-dts-meson-activate-hdmi-audio-HDMI-enabled-boa.patch @@ -0,0 +1,864 @@ +From dc4eb517f2800001f77bec852f8f688f0164e51b Mon Sep 17 00:00:00 2001 +From: Jerome Brunet +Date: Wed, 20 Sep 2017 18:10:08 +0200 +Subject: [PATCH 13/53] ARM64: dts: meson: activate hdmi audio HDMI enabled + boards + +This patch activate audio over HDMI on selected boards + +Please note that this audio support is based on WIP changes +This should be considered as preview and it does not reflect +the audio I expect to see merged + +Signed-off-by: Jerome Brunet +Signed-off-by: Neil Armstrong +--- + .../boot/dts/amlogic/meson-gx-p23x-q20x.dtsi | 45 +++++++++++++++++++ + .../boot/dts/amlogic/meson-gxbb-nanopi-k2.dts | 45 +++++++++++++++++++ + .../dts/amlogic/meson-gxbb-nexbox-a95x.dts | 45 +++++++++++++++++++ + .../boot/dts/amlogic/meson-gxbb-odroidc2.dts | 45 +++++++++++++++++++ + .../boot/dts/amlogic/meson-gxbb-p20x.dtsi | 45 +++++++++++++++++++ + .../boot/dts/amlogic/meson-gxbb-wetek.dtsi | 45 +++++++++++++++++++ + .../amlogic/meson-gxl-s905x-khadas-vim.dts | 45 +++++++++++++++++++ + .../amlogic/meson-gxl-s905x-libretech-cc.dts | 45 +++++++++++++++++++ + .../amlogic/meson-gxl-s905x-nexbox-a95x.dts | 45 +++++++++++++++++++ + .../boot/dts/amlogic/meson-gxl-s905x-p212.dts | 45 +++++++++++++++++++ + .../dts/amlogic/meson-gxm-khadas-vim2.dts | 45 +++++++++++++++++++ + .../boot/dts/amlogic/meson-gxm-nexbox-a1.dts | 45 +++++++++++++++++++ + 12 files changed, 540 insertions(+) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi +index 765247bc4f24..fb9ad6faa745 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi +@@ -102,6 +102,39 @@ + }; + }; + }; ++ ++ sound { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "meson-gx-audio"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL2>, ++ <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-parents = <0>, <0>, <0>; ++ assigned-clock-rates = <294912000>, ++ <270950400>, ++ <393216000>; ++ ++ simple-audio-card,dai-link@0 { ++ /* HDMI Output */ ++ format = "i2s"; ++ mclk-fs = <256>; ++ bitclock-master = <&i2s_dai>; ++ frame-master = <&i2s_dai>; ++ ++ plat { ++ sound-dai = <&aiu_i2s_dma>; ++ }; ++ ++ cpu { ++ sound-dai = <&i2s_dai>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi_tx>; ++ }; ++ }; ++ }; + }; + + &cec_AO { +@@ -111,6 +144,14 @@ + hdmi-phandle = <&hdmi_tx>; + }; + ++&audio { ++ status = "okay"; ++}; ++ ++&aiu_i2s_dma { ++ status = "okay"; ++}; ++ + &cvbs_vdac_port { + cvbs_vdac_out: endpoint { + remote-endpoint = <&cvbs_connector_in>; +@@ -133,6 +174,10 @@ + }; + }; + ++&i2s_dai { ++ status = "okay"; ++}; ++ + &ir { + status = "okay"; + pinctrl-0 = <&remote_input_ao_pins>; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-nanopi-k2.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-nanopi-k2.dts +index cbe99bd4e06d..5b10de9a0bad 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-nanopi-k2.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-nanopi-k2.dts +@@ -88,6 +88,39 @@ + clock-names = "ext_clock"; + }; + ++ sound { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "meson-gx-audio"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL2>, ++ <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-parents = <0>, <0>, <0>; ++ assigned-clock-rates = <294912000>, ++ <270950400>, ++ <393216000>; ++ ++ simple-audio-card,dai-link@0 { ++ /* HDMI Output */ ++ format = "i2s"; ++ mclk-fs = <256>; ++ bitclock-master = <&i2s_dai>; ++ frame-master = <&i2s_dai>; ++ ++ plat { ++ sound-dai = <&aiu_i2s_dma>; ++ }; ++ ++ cpu { ++ sound-dai = <&i2s_dai>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi_tx>; ++ }; ++ }; ++ }; ++ + vcc1v8: regulator-vcc1v8 { + compatible = "regulator-fixed"; + regulator-name = "VCC1.8V"; +@@ -131,6 +164,14 @@ + }; + }; + ++&audio { ++ status = "okay"; ++}; ++ ++&aiu_i2s_dma { ++ status = "okay"; ++}; ++ + &cec_AO { + status = "okay"; + pinctrl-0 = <&ao_cec_pins>; +@@ -185,6 +226,10 @@ + }; + }; + ++&i2s_dai { ++ status = "okay"; ++}; ++ + &ir { + status = "okay"; + pinctrl-0 = <&remote_input_ao_pins>; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-nexbox-a95x.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-nexbox-a95x.dts +index 4cf7f6e80c6a..ff87bdc7ddbf 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-nexbox-a95x.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-nexbox-a95x.dts +@@ -119,6 +119,39 @@ + clock-names = "ext_clock"; + }; + ++ sound { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "meson-gx-audio"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL2>, ++ <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-parents = <0>, <0>, <0>; ++ assigned-clock-rates = <294912000>, ++ <270950400>, ++ <393216000>; ++ ++ simple-audio-card,dai-link@0 { ++ /* HDMI Output */ ++ format = "i2s"; ++ mclk-fs = <256>; ++ bitclock-master = <&i2s_dai>; ++ frame-master = <&i2s_dai>; ++ ++ plat { ++ sound-dai = <&aiu_i2s_dma>; ++ }; ++ ++ cpu { ++ sound-dai = <&i2s_dai>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi_tx>; ++ }; ++ }; ++ }; ++ + cvbs-connector { + compatible = "composite-video-connector"; + +@@ -154,6 +187,14 @@ + hdmi-phandle = <&hdmi_tx>; + }; + ++&audio { ++ status = "okay"; ++}; ++ ++&aiu_i2s_dma { ++ status = "okay"; ++}; ++ + ðmac { + status = "okay"; + pinctrl-0 = <ð_rmii_pins>; +@@ -190,6 +231,10 @@ + }; + }; + ++&i2s_dai { ++ status = "okay"; ++}; ++ + &ir { + status = "okay"; + pinctrl-0 = <&remote_input_ao_pins>; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts +index 54954b314a45..3da33090b8fe 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts +@@ -110,6 +110,39 @@ + }; + }; + }; ++ ++ sound { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "meson-gx-audio"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL2>, ++ <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-parents = <0>, <0>, <0>; ++ assigned-clock-rates = <294912000>, ++ <270950400>, ++ <393216000>; ++ ++ simple-audio-card,dai-link@0 { ++ /* HDMI Output */ ++ format = "i2s"; ++ mclk-fs = <256>; ++ bitclock-master = <&i2s_dai>; ++ frame-master = <&i2s_dai>; ++ ++ plat { ++ sound-dai = <&aiu_i2s_dma>; ++ }; ++ ++ cpu { ++ sound-dai = <&i2s_dai>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi_tx>; ++ }; ++ }; ++ }; + }; + + &cec_AO { +@@ -119,6 +152,14 @@ + hdmi-phandle = <&hdmi_tx>; + }; + ++&audio { ++ status = "okay"; ++}; ++ ++&aiu_i2s_dma { ++ status = "okay"; ++}; ++ + ðmac { + status = "okay"; + pinctrl-0 = <ð_rgmii_pins>; +@@ -181,6 +222,10 @@ + pinctrl-names = "default"; + }; + ++&i2s_dai { ++ status = "okay"; ++}; ++ + &ir { + status = "okay"; + pinctrl-0 = <&remote_input_ao_pins>; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-p20x.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxbb-p20x.dtsi +index ce862266b9aa..84eb93b4229f 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-p20x.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-p20x.dtsi +@@ -113,6 +113,39 @@ + }; + }; + }; ++ ++ sound { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "meson-gx-audio"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL2>, ++ <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-parents = <0>, <0>, <0>; ++ assigned-clock-rates = <294912000>, ++ <270950400>, ++ <393216000>; ++ ++ simple-audio-card,dai-link@0 { ++ /* HDMI Output */ ++ format = "i2s"; ++ mclk-fs = <256>; ++ bitclock-master = <&i2s_dai>; ++ frame-master = <&i2s_dai>; ++ ++ plat { ++ sound-dai = <&aiu_i2s_dma>; ++ }; ++ ++ cpu { ++ sound-dai = <&i2s_dai>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi_tx>; ++ }; ++ }; ++ }; + }; + + &cec_AO { +@@ -122,6 +155,14 @@ + hdmi-phandle = <&hdmi_tx>; + }; + ++&audio { ++ status = "okay"; ++}; ++ ++&aiu_i2s_dma { ++ status = "okay"; ++}; ++ + &cvbs_vdac_port { + cvbs_vdac_out: endpoint { + remote-endpoint = <&cvbs_connector_in>; +@@ -140,6 +181,10 @@ + }; + }; + ++&i2s_dai { ++ status = "okay"; ++}; ++ + &ir { + status = "okay"; + pinctrl-0 = <&remote_input_ao_pins>; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-wetek.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxbb-wetek.dtsi +index 70325b273bd2..7d1f1726f29d 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-wetek.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-wetek.dtsi +@@ -105,6 +105,47 @@ + }; + }; + }; ++ ++ sound { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "meson-gx-audio"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL2>, ++ <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-parents = <0>, <0>, <0>; ++ assigned-clock-rates = <294912000>, ++ <270950400>, ++ <393216000>; ++ ++ simple-audio-card,dai-link@0 { ++ /* HDMI Output */ ++ format = "i2s"; ++ mclk-fs = <256>; ++ bitclock-master = <&i2s_dai>; ++ frame-master = <&i2s_dai>; ++ ++ plat { ++ sound-dai = <&aiu_i2s_dma>; ++ }; ++ ++ cpu { ++ sound-dai = <&i2s_dai>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi_tx>; ++ }; ++ }; ++ }; ++}; ++ ++&audio { ++ status = "okay"; ++}; ++ ++&aiu_i2s_dma { ++ status = "okay"; + }; + + &cec_AO { +@@ -159,6 +200,10 @@ + }; + }; + ++&i2s_dai { ++ status = "okay"; ++}; ++ + &ir { + status = "okay"; + pinctrl-0 = <&remote_input_ao_pins>; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts +index d32cf3846370..f053595ebdc4 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts +@@ -65,6 +65,39 @@ + }; + }; + }; ++ ++ sound { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "meson-gx-audio"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL2>, ++ <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-parents = <0>, <0>, <0>; ++ assigned-clock-rates = <294912000>, ++ <270950400>, ++ <393216000>; ++ ++ simple-audio-card,dai-link@0 { ++ /* HDMI Output */ ++ format = "i2s"; ++ mclk-fs = <256>; ++ bitclock-master = <&i2s_dai>; ++ frame-master = <&i2s_dai>; ++ ++ plat { ++ sound-dai = <&aiu_i2s_dma>; ++ }; ++ ++ cpu { ++ sound-dai = <&i2s_dai>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi_tx>; ++ }; ++ }; ++ }; + }; + + &cec_AO { +@@ -74,6 +107,14 @@ + hdmi-phandle = <&hdmi_tx>; + }; + ++&audio { ++ status = "okay"; ++}; ++ ++&aiu_i2s_dma { ++ status = "okay"; ++}; ++ + &hdmi_tx { + status = "okay"; + pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; +@@ -86,6 +127,10 @@ + }; + }; + ++&i2s_dai { ++ status = "okay"; ++}; ++ + &i2c_A { + status = "okay"; + pinctrl-0 = <&i2c_a_pins>; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts +index f63bceb88caa..f56969efffba 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts +@@ -84,6 +84,39 @@ + regulator-always-on; + }; + ++ sound { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "meson-gx-audio"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL2>, ++ <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-parents = <0>, <0>, <0>; ++ assigned-clock-rates = <294912000>, ++ <270950400>, ++ <393216000>; ++ ++ simple-audio-card,dai-link@0 { ++ /* HDMI Output */ ++ format = "i2s"; ++ mclk-fs = <256>; ++ bitclock-master = <&i2s_dai>; ++ frame-master = <&i2s_dai>; ++ ++ plat { ++ sound-dai = <&aiu_i2s_dma>; ++ }; ++ ++ cpu { ++ sound-dai = <&i2s_dai>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi_tx>; ++ }; ++ }; ++ }; ++ + vcc_3v3: regulator-vcc_3v3 { + compatible = "regulator-fixed"; + regulator-name = "VCC_3V3"; +@@ -130,6 +163,14 @@ + hdmi-phandle = <&hdmi_tx>; + }; + ++&audio { ++ status = "okay"; ++}; ++ ++&aiu_i2s_dma { ++ status = "okay"; ++}; ++ + &cvbs_vdac_port { + cvbs_vdac_out: endpoint { + remote-endpoint = <&cvbs_connector_in>; +@@ -151,6 +192,10 @@ + pinctrl-names = "default"; + }; + ++&i2s_dai { ++ status = "okay"; ++}; ++ + &hdmi_tx { + status = "okay"; + pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-nexbox-a95x.dts b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-nexbox-a95x.dts +index 6739697be1de..e3e777f665c0 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-nexbox-a95x.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-nexbox-a95x.dts +@@ -102,6 +102,39 @@ + }; + }; + }; ++ ++ sound { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "meson-gx-audio"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL2>, ++ <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-parents = <0>, <0>, <0>; ++ assigned-clock-rates = <294912000>, ++ <270950400>, ++ <393216000>; ++ ++ simple-audio-card,dai-link@0 { ++ /* HDMI Output */ ++ format = "i2s"; ++ mclk-fs = <256>; ++ bitclock-master = <&i2s_dai>; ++ frame-master = <&i2s_dai>; ++ ++ plat { ++ sound-dai = <&aiu_i2s_dma>; ++ }; ++ ++ cpu { ++ sound-dai = <&i2s_dai>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi_tx>; ++ }; ++ }; ++ }; + }; + + &cec_AO { +@@ -111,6 +144,14 @@ + hdmi-phandle = <&hdmi_tx>; + }; + ++&audio { ++ status = "okay"; ++}; ++ ++&aiu_i2s_dma { ++ status = "okay"; ++}; ++ + &cvbs_vdac_port { + cvbs_vdac_out: endpoint { + remote-endpoint = <&cvbs_connector_in>; +@@ -135,6 +176,10 @@ + }; + }; + ++&i2s_dai { ++ status = "okay"; ++}; ++ + &ir { + status = "okay"; + pinctrl-0 = <&remote_input_ao_pins>; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts +index 5896e8a5d86b..f8c66a7972b3 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts +@@ -32,6 +32,39 @@ + }; + }; + }; ++ ++ sound { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "meson-gx-audio"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL2>, ++ <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-parents = <0>, <0>, <0>; ++ assigned-clock-rates = <294912000>, ++ <270950400>, ++ <393216000>; ++ ++ simple-audio-card,dai-link@0 { ++ /* HDMI Output */ ++ format = "i2s"; ++ mclk-fs = <256>; ++ bitclock-master = <&i2s_dai>; ++ frame-master = <&i2s_dai>; ++ ++ plat { ++ sound-dai = <&aiu_i2s_dma>; ++ }; ++ ++ cpu { ++ sound-dai = <&i2s_dai>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi_tx>; ++ }; ++ }; ++ }; + }; + + &cec_AO { +@@ -41,12 +74,24 @@ + hdmi-phandle = <&hdmi_tx>; + }; + ++&audio { ++ status = "okay"; ++}; ++ ++&aiu_i2s_dma { ++ status = "okay"; ++}; ++ + &cvbs_vdac_port { + cvbs_vdac_out: endpoint { + remote-endpoint = <&cvbs_connector_in>; + }; + }; + ++&i2s_dai { ++ status = "okay"; ++}; ++ + &hdmi_tx { + status = "okay"; + pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts b/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts +index 313f88f8759e..4fbfa5a850cc 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts +@@ -85,6 +85,39 @@ + }; + }; + ++ sound { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "meson-gx-audio"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL2>, ++ <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-parents = <0>, <0>, <0>; ++ assigned-clock-rates = <294912000>, ++ <270950400>, ++ <393216000>; ++ ++ simple-audio-card,dai-link@0 { ++ /* HDMI Output */ ++ format = "i2s"; ++ mclk-fs = <256>; ++ bitclock-master = <&i2s_dai>; ++ frame-master = <&i2s_dai>; ++ ++ plat { ++ sound-dai = <&aiu_i2s_dma>; ++ }; ++ ++ cpu { ++ sound-dai = <&i2s_dai>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi_tx>; ++ }; ++ }; ++ }; ++ + pwmleds { + compatible = "pwm-leds"; + +@@ -205,6 +238,14 @@ + hdmi-phandle = <&hdmi_tx>; + }; + ++&audio { ++ status = "okay"; ++}; ++ ++&aiu_i2s_dma { ++ status = "okay"; ++}; ++ + &cpu0 { + #cooling-cells = <2>; + }; +@@ -279,6 +320,10 @@ + }; + }; + ++&i2s_dai { ++ status = "okay"; ++}; ++ + &i2c_A { + status = "okay"; + pinctrl-0 = <&i2c_a_pins>; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxm-nexbox-a1.dts b/arch/arm64/boot/dts/amlogic/meson-gxm-nexbox-a1.dts +index f7a1cffab4a8..b9c5e6444daa 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxm-nexbox-a1.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxm-nexbox-a1.dts +@@ -75,6 +75,39 @@ + }; + }; + }; ++ ++ sound { ++ compatible = "simple-audio-card"; ++ simple-audio-card,name = "meson-gx-audio"; ++ ++ assigned-clocks = <&clkc CLKID_MPLL2>, ++ <&clkc CLKID_MPLL0>, ++ <&clkc CLKID_MPLL1>; ++ assigned-clock-parents = <0>, <0>, <0>; ++ assigned-clock-rates = <294912000>, ++ <270950400>, ++ <393216000>; ++ ++ simple-audio-card,dai-link@0 { ++ /* HDMI Output */ ++ format = "i2s"; ++ mclk-fs = <256>; ++ bitclock-master = <&i2s_dai>; ++ frame-master = <&i2s_dai>; ++ ++ plat { ++ sound-dai = <&aiu_i2s_dma>; ++ }; ++ ++ cpu { ++ sound-dai = <&i2s_dai>; ++ }; ++ ++ codec { ++ sound-dai = <&hdmi_tx>; ++ }; ++ }; ++ }; + }; + + &cec_AO { +@@ -84,6 +117,14 @@ + hdmi-phandle = <&hdmi_tx>; + }; + ++&audio { ++ status = "okay"; ++}; ++ ++&aiu_i2s_dma { ++ status = "okay"; ++}; ++ + &cvbs_vdac_port { + cvbs_vdac_out: endpoint { + remote-endpoint = <&cvbs_connector_in>; +@@ -129,6 +170,10 @@ + }; + }; + ++&i2s_dai { ++ status = "okay"; ++}; ++ + &ir { + status = "okay"; + pinctrl-0 = <&remote_input_ao_pins>; +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0014-drm-bridge-dw-hdmi-Use-AUTO-CTS-setup-mode-when-non-.patch b/buildroot-external/board/hardkernel/patches/linux/0014-drm-bridge-dw-hdmi-Use-AUTO-CTS-setup-mode-when-non-.patch new file mode 100644 index 000000000..2a84e1aac --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0014-drm-bridge-dw-hdmi-Use-AUTO-CTS-setup-mode-when-non-.patch @@ -0,0 +1,78 @@ +From de9e307aca194c9918a3ace8d809c9f3b18000b9 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Mon, 2 Jul 2018 12:21:55 +0200 +Subject: [PATCH 14/53] drm: bridge: dw-hdmi: Use AUTO CTS setup mode when + non-AHB audio + +Signed-off-by: Neil Armstrong +--- + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 41 ++++++++++++++--------- + 1 file changed, 26 insertions(+), 15 deletions(-) + +diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +index 5971976284bf..1fc12708dbb5 100644 +--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c ++++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +@@ -430,8 +430,12 @@ static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts, + /* nshift factor = 0 */ + hdmi_modb(hdmi, 0, HDMI_AUD_CTS3_N_SHIFT_MASK, HDMI_AUD_CTS3); + +- hdmi_writeb(hdmi, ((cts >> 16) & HDMI_AUD_CTS3_AUDCTS19_16_MASK) | +- HDMI_AUD_CTS3_CTS_MANUAL, HDMI_AUD_CTS3); ++ /* Use Auto CTS mode with CTS is unknown */ ++ if (cts) ++ hdmi_writeb(hdmi, ((cts >> 16) & HDMI_AUD_CTS3_AUDCTS19_16_MASK) | ++ HDMI_AUD_CTS3_CTS_MANUAL, HDMI_AUD_CTS3); ++ else ++ hdmi_writeb(hdmi, 0, HDMI_AUD_CTS3); + hdmi_writeb(hdmi, (cts >> 8) & 0xff, HDMI_AUD_CTS2); + hdmi_writeb(hdmi, cts & 0xff, HDMI_AUD_CTS1); + +@@ -501,24 +505,31 @@ static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi, + { + unsigned long ftdms = pixel_clk; + unsigned int n, cts; ++ u8 config3; + u64 tmp; + + n = hdmi_compute_n(sample_rate, pixel_clk); + +- /* +- * Compute the CTS value from the N value. Note that CTS and N +- * can be up to 20 bits in total, so we need 64-bit math. Also +- * note that our TDMS clock is not fully accurate; it is accurate +- * to kHz. This can introduce an unnecessary remainder in the +- * calculation below, so we don't try to warn about that. +- */ +- tmp = (u64)ftdms * n; +- do_div(tmp, 128 * sample_rate); +- cts = tmp; ++ config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID); + +- dev_dbg(hdmi->dev, "%s: fs=%uHz ftdms=%lu.%03luMHz N=%d cts=%d\n", +- __func__, sample_rate, ftdms / 1000000, (ftdms / 1000) % 1000, +- n, cts); ++ if (config3 & HDMI_CONFIG3_AHBAUDDMA) { ++ /* ++ * Compute the CTS value from the N value. Note that CTS and N ++ * can be up to 20 bits in total, so we need 64-bit math. Also ++ * note that our TDMS clock is not fully accurate; it is ++ * accurate to kHz. This can introduce an unnecessary remainder ++ * in the calculation below, so we don't try to warn about that. ++ */ ++ tmp = (u64)ftdms * n; ++ do_div(tmp, 128 * sample_rate); ++ cts = tmp; ++ ++ dev_dbg(hdmi->dev, "%s: fs=%uHz ftdms=%lu.%03luMHz N=%d cts=%d\n", ++ __func__, sample_rate, ++ ftdms / 1000000, (ftdms / 1000) % 1000, ++ n, cts); ++ } else ++ cts = 0; + + spin_lock_irq(&hdmi->audio_lock); + hdmi->audio_n = n; +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0015-drm-meson-Call-drm_crtc_vblank_on-drm_crtc_vblank_of.patch b/buildroot-external/board/hardkernel/patches/linux/0015-drm-meson-Call-drm_crtc_vblank_on-drm_crtc_vblank_of.patch new file mode 100644 index 000000000..5bef76957 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0015-drm-meson-Call-drm_crtc_vblank_on-drm_crtc_vblank_of.patch @@ -0,0 +1,29 @@ +From ca4d7cc46fc5788da89609691ccb0b001bdbdc2d Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Wed, 28 Feb 2018 16:07:18 +0100 +Subject: [PATCH 15/53] drm/meson: Call drm_crtc_vblank_on / + drm_crtc_vblank_off + +Make sure that the CRTC code will call the enable/disable_vblank hooks. + +Signed-off-by: Neil Armstrong +--- + drivers/gpu/drm/meson/meson_crtc.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/gpu/drm/meson/meson_crtc.c b/drivers/gpu/drm/meson/meson_crtc.c +index 709475d5cc30..2680be54a1d1 100644 +--- a/drivers/gpu/drm/meson/meson_crtc.c ++++ b/drivers/gpu/drm/meson/meson_crtc.c +@@ -104,6 +104,8 @@ static void meson_crtc_atomic_enable(struct drm_crtc *crtc, + drm_crtc_vblank_on(crtc); + + priv->viu.osd1_enabled = true; ++ ++ drm_crtc_vblank_on(crtc); + } + + static void meson_crtc_atomic_disable(struct drm_crtc *crtc, +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0016-soc-amlogic-add-meson-canvas-driver.patch b/buildroot-external/board/hardkernel/patches/linux/0016-soc-amlogic-add-meson-canvas-driver.patch new file mode 100644 index 000000000..7862cb9e5 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0016-soc-amlogic-add-meson-canvas-driver.patch @@ -0,0 +1,316 @@ +From d28f2758958eff3be784b80eff63c144d342539b Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Fri, 20 Apr 2018 13:17:07 +0200 +Subject: [PATCH 16/53] soc: amlogic: add meson-canvas driver + +Amlogic SoCs have a repository of 256 canvas which they use to +describe pixel buffers. + +They contain metadata like width, height, block mode, endianness [..] + +Many IPs within those SoCs like vdec/vpu rely on those canvas to read/write +pixels. + +Reviewed-by: Jerome Brunet +Tested-by: Neil Armstrong +Signed-off-by: Maxime Jourdan +--- + drivers/soc/amlogic/Kconfig | 7 + + drivers/soc/amlogic/Makefile | 1 + + drivers/soc/amlogic/meson-canvas.c | 185 +++++++++++++++++++++++ + include/linux/soc/amlogic/meson-canvas.h | 65 ++++++++ + 4 files changed, 258 insertions(+) + create mode 100644 drivers/soc/amlogic/meson-canvas.c + create mode 100644 include/linux/soc/amlogic/meson-canvas.h + +diff --git a/drivers/soc/amlogic/Kconfig b/drivers/soc/amlogic/Kconfig +index b04f6e4aedbc..2f282b472912 100644 +--- a/drivers/soc/amlogic/Kconfig ++++ b/drivers/soc/amlogic/Kconfig +@@ -1,5 +1,12 @@ + menu "Amlogic SoC drivers" + ++config MESON_CANVAS ++ tristate "Amlogic Meson Canvas driver" ++ depends on ARCH_MESON || COMPILE_TEST ++ default n ++ help ++ Say yes to support the canvas IP for Amlogic SoCs. ++ + config MESON_GX_SOCINFO + bool "Amlogic Meson GX SoC Information driver" + depends on ARCH_MESON || COMPILE_TEST +diff --git a/drivers/soc/amlogic/Makefile b/drivers/soc/amlogic/Makefile +index 8fa321893928..0ab16d35ac36 100644 +--- a/drivers/soc/amlogic/Makefile ++++ b/drivers/soc/amlogic/Makefile +@@ -1,3 +1,4 @@ ++obj-$(CONFIG_MESON_CANVAS) += meson-canvas.o + obj-$(CONFIG_MESON_GX_SOCINFO) += meson-gx-socinfo.o + obj-$(CONFIG_MESON_GX_PM_DOMAINS) += meson-gx-pwrc-vpu.o + obj-$(CONFIG_MESON_MX_SOCINFO) += meson-mx-socinfo.o +diff --git a/drivers/soc/amlogic/meson-canvas.c b/drivers/soc/amlogic/meson-canvas.c +new file mode 100644 +index 000000000000..fce33ca76bb6 +--- /dev/null ++++ b/drivers/soc/amlogic/meson-canvas.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Copyright (C) 2015 Amlogic, Inc. All rights reserved. ++ * Copyright (C) 2014 Endless Mobile ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define NUM_CANVAS 256 ++ ++/* DMC Registers */ ++#define DMC_CAV_LUT_DATAL 0x00 ++ #define CANVAS_WIDTH_LBIT 29 ++ #define CANVAS_WIDTH_LWID 3 ++#define DMC_CAV_LUT_DATAH 0x04 ++ #define CANVAS_WIDTH_HBIT 0 ++ #define CANVAS_HEIGHT_BIT 9 ++ #define CANVAS_WRAP_BIT 22 ++ #define CANVAS_BLKMODE_BIT 24 ++ #define CANVAS_ENDIAN_BIT 26 ++#define DMC_CAV_LUT_ADDR 0x08 ++ #define CANVAS_LUT_WR_EN BIT(9) ++ #define CANVAS_LUT_RD_EN BIT(8) ++ ++struct meson_canvas { ++ struct device *dev; ++ void __iomem *reg_base; ++ spinlock_t lock; /* canvas device lock */ ++ u8 used[NUM_CANVAS]; ++}; ++ ++static void canvas_write(struct meson_canvas *canvas, u32 reg, u32 val) ++{ ++ writel_relaxed(val, canvas->reg_base + reg); ++} ++ ++static u32 canvas_read(struct meson_canvas *canvas, u32 reg) ++{ ++ return readl_relaxed(canvas->reg_base + reg); ++} ++ ++struct meson_canvas *meson_canvas_get(struct device *dev) ++{ ++ struct device_node *canvas_node; ++ struct platform_device *canvas_pdev; ++ ++ canvas_node = of_parse_phandle(dev->of_node, "amlogic,canvas", 0); ++ if (!canvas_node) ++ return ERR_PTR(-ENODEV); ++ ++ canvas_pdev = of_find_device_by_node(canvas_node); ++ if (!canvas_pdev) ++ return ERR_PTR(-EPROBE_DEFER); ++ ++ return dev_get_drvdata(&canvas_pdev->dev); ++} ++EXPORT_SYMBOL_GPL(meson_canvas_get); ++ ++int meson_canvas_config(struct meson_canvas *canvas, u8 canvas_index, ++ u32 addr, u32 stride, u32 height, ++ unsigned int wrap, ++ unsigned int blkmode, ++ unsigned int endian) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&canvas->lock, flags); ++ if (!canvas->used[canvas_index]) { ++ dev_err(canvas->dev, ++ "Trying to setup non allocated canvas %u\n", ++ canvas_index); ++ spin_unlock_irqrestore(&canvas->lock, flags); ++ return -EINVAL; ++ } ++ ++ canvas_write(canvas, DMC_CAV_LUT_DATAL, ++ ((addr + 7) >> 3) | ++ (((stride + 7) >> 3) << CANVAS_WIDTH_LBIT)); ++ ++ canvas_write(canvas, DMC_CAV_LUT_DATAH, ++ ((((stride + 7) >> 3) >> CANVAS_WIDTH_LWID) << ++ CANVAS_WIDTH_HBIT) | ++ (height << CANVAS_HEIGHT_BIT) | ++ (wrap << CANVAS_WRAP_BIT) | ++ (blkmode << CANVAS_BLKMODE_BIT) | ++ (endian << CANVAS_ENDIAN_BIT)); ++ ++ canvas_write(canvas, DMC_CAV_LUT_ADDR, ++ CANVAS_LUT_WR_EN | canvas_index); ++ ++ /* Force a read-back to make sure everything is flushed. */ ++ canvas_read(canvas, DMC_CAV_LUT_DATAH); ++ spin_unlock_irqrestore(&canvas->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(meson_canvas_config); ++ ++int meson_canvas_alloc(struct meson_canvas *canvas, u8 *canvas_index) ++{ ++ int i; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&canvas->lock, flags); ++ for (i = 0; i < NUM_CANVAS; ++i) { ++ if (!canvas->used[i]) { ++ canvas->used[i] = 1; ++ spin_unlock_irqrestore(&canvas->lock, flags); ++ *canvas_index = i; ++ return 0; ++ } ++ } ++ spin_unlock_irqrestore(&canvas->lock, flags); ++ ++ dev_err(canvas->dev, "No more canvas available\n"); ++ return -ENODEV; ++} ++EXPORT_SYMBOL_GPL(meson_canvas_alloc); ++ ++int meson_canvas_free(struct meson_canvas *canvas, u8 canvas_index) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&canvas->lock, flags); ++ if (!canvas->used[canvas_index]) { ++ dev_err(canvas->dev, ++ "Trying to free unused canvas %u\n", canvas_index); ++ spin_unlock_irqrestore(&canvas->lock, flags); ++ return -EINVAL; ++ } ++ canvas->used[canvas_index] = 0; ++ spin_unlock_irqrestore(&canvas->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(meson_canvas_free); ++ ++static int meson_canvas_probe(struct platform_device *pdev) ++{ ++ struct resource *res; ++ struct meson_canvas *canvas; ++ struct device *dev = &pdev->dev; ++ ++ canvas = devm_kzalloc(dev, sizeof(*canvas), GFP_KERNEL); ++ if (!canvas) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ canvas->reg_base = devm_ioremap_resource(dev, res); ++ if (IS_ERR(canvas->reg_base)) ++ return PTR_ERR(canvas->reg_base); ++ ++ canvas->dev = dev; ++ spin_lock_init(&canvas->lock); ++ dev_set_drvdata(dev, canvas); ++ ++ return 0; ++} ++ ++static const struct of_device_id canvas_dt_match[] = { ++ { .compatible = "amlogic,canvas" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, canvas_dt_match); ++ ++static struct platform_driver meson_canvas_driver = { ++ .probe = meson_canvas_probe, ++ .driver = { ++ .name = "amlogic-canvas", ++ .of_match_table = canvas_dt_match, ++ }, ++}; ++module_platform_driver(meson_canvas_driver); ++ ++MODULE_DESCRIPTION("Amlogic Canvas driver"); ++MODULE_AUTHOR("Maxime Jourdan "); ++MODULE_LICENSE("GPL"); +diff --git a/include/linux/soc/amlogic/meson-canvas.h b/include/linux/soc/amlogic/meson-canvas.h +new file mode 100644 +index 000000000000..b4dde2fbeb3f +--- /dev/null ++++ b/include/linux/soc/amlogic/meson-canvas.h +@@ -0,0 +1,65 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ */ ++#ifndef __SOC_MESON_CANVAS_H ++#define __SOC_MESON_CANVAS_H ++ ++#include ++ ++#define MESON_CANVAS_WRAP_NONE 0x00 ++#define MESON_CANVAS_WRAP_X 0x01 ++#define MESON_CANVAS_WRAP_Y 0x02 ++ ++#define MESON_CANVAS_BLKMODE_LINEAR 0x00 ++#define MESON_CANVAS_BLKMODE_32x32 0x01 ++#define MESON_CANVAS_BLKMODE_64x64 0x02 ++ ++#define MESON_CANVAS_ENDIAN_SWAP16 0x1 ++#define MESON_CANVAS_ENDIAN_SWAP32 0x3 ++#define MESON_CANVAS_ENDIAN_SWAP64 0x7 ++#define MESON_CANVAS_ENDIAN_SWAP128 0xf ++ ++struct meson_canvas; ++ ++/** ++ * meson_canvas_get() - get a canvas provider instance ++ * ++ * @dev: consumer device pointer ++ */ ++struct meson_canvas *meson_canvas_get(struct device *dev); ++ ++/** ++ * meson_canvas_alloc() - take ownership of a canvas ++ * ++ * @canvas: canvas provider instance retrieved from meson_canvas_get() ++ * @canvas_index: will be filled with the canvas ID ++ */ ++int meson_canvas_alloc(struct meson_canvas *canvas, u8 *canvas_index); ++ ++/** ++ * meson_canvas_free() - remove ownership from a canvas ++ * ++ * @canvas: canvas provider instance retrieved from meson_canvas_get() ++ * @canvas_index: canvas ID that was obtained via meson_canvas_alloc() ++ */ ++int meson_canvas_free(struct meson_canvas *canvas, u8 canvas_index); ++ ++/** ++ * meson_canvas_config() - configure a canvas ++ * ++ * @canvas: canvas provider instance retrieved from meson_canvas_get() ++ * @canvas_index: canvas ID that was obtained via meson_canvas_alloc() ++ * @addr: physical address to the pixel buffer ++ * @stride: width of the buffer ++ * @height: height of the buffer ++ * @wrap: undocumented ++ * @blkmode: block mode (linear, 32x32, 64x64) ++ * @endian: byte swapping (swap16, swap32, swap64, swap128) ++ */ ++int meson_canvas_config(struct meson_canvas *canvas, u8 canvas_index, ++ u32 addr, u32 stride, u32 height, ++ unsigned int wrap, unsigned int blkmode, ++ unsigned int endian); ++ ++#endif +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0017-ARM64-dts-meson-gx-add-dmcbus-and-canvas-nodes.patch b/buildroot-external/board/hardkernel/patches/linux/0017-ARM64-dts-meson-gx-add-dmcbus-and-canvas-nodes.patch new file mode 100644 index 000000000..b6da4467a --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0017-ARM64-dts-meson-gx-add-dmcbus-and-canvas-nodes.patch @@ -0,0 +1,41 @@ +From 83a293f5c56ec7cb763edba40c9cbf4f79ed6393 Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Fri, 20 Apr 2018 16:09:09 +0200 +Subject: [PATCH 17/53] ARM64: dts: meson-gx: add dmcbus and canvas nodes. + +DMC is a small memory region with various registers, +including the ones needed for the canvas module. + +Reviewed-by: Jerome Brunet +Signed-off-by: Maxime Jourdan +--- + arch/arm64/boot/dts/amlogic/meson-gx.dtsi | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi +index 6b64b63f2a68..fb6435431a94 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi +@@ -458,6 +458,19 @@ + }; + }; + ++ dmcbus: bus@c8838000 { ++ compatible = "simple-bus"; ++ reg = <0x0 0xc8838000 0x0 0x400>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges = <0x0 0x0 0x0 0xc8838000 0x0 0x400>; ++ ++ canvas: video-lut@48 { ++ compatible = "amlogic,canvas"; ++ reg = <0x0 0x48 0x0 0x14>; ++ }; ++ }; ++ + hiubus: bus@c883c000 { + compatible = "simple-bus"; + reg = <0x0 0xc883c000 0x0 0x2000>; +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0018-drm-meson-Use-optional-canvas-provider.patch b/buildroot-external/board/hardkernel/patches/linux/0018-drm-meson-Use-optional-canvas-provider.patch new file mode 100644 index 000000000..48c074fdb --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0018-drm-meson-Use-optional-canvas-provider.patch @@ -0,0 +1,174 @@ +From b1ef71bf75024008c4221e0415f84af57cd128ac Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Mon, 15 Oct 2018 14:37:18 +0200 +Subject: [PATCH 18/53] drm/meson: Use optional canvas provider + +This is the first step into converting the meson/drm driver to use +the canvas module. + +If a canvas provider node is detected in DT, use it. Otherwise, +fall back to what is currently being done. + +Signed-off-by: Maxime Jourdan +--- + drivers/gpu/drm/meson/Kconfig | 1 + + drivers/gpu/drm/meson/meson_crtc.c | 14 ++++++--- + drivers/gpu/drm/meson/meson_drv.c | 46 ++++++++++++++++++----------- + drivers/gpu/drm/meson/meson_drv.h | 4 +++ + drivers/gpu/drm/meson/meson_plane.c | 8 ++++- + 5 files changed, 51 insertions(+), 22 deletions(-) + +diff --git a/drivers/gpu/drm/meson/Kconfig b/drivers/gpu/drm/meson/Kconfig +index 02d400b8795c..892905825fea 100644 +--- a/drivers/gpu/drm/meson/Kconfig ++++ b/drivers/gpu/drm/meson/Kconfig +@@ -7,6 +7,7 @@ config DRM_MESON + select DRM_GEM_CMA_HELPER + select VIDEOMODE_HELPERS + select REGMAP_MMIO ++ select MESON_CANVAS + + config DRM_MESON_DW_HDMI + tristate "HDMI Synopsys Controller support for Amlogic Meson Display" +diff --git a/drivers/gpu/drm/meson/meson_crtc.c b/drivers/gpu/drm/meson/meson_crtc.c +index 2680be54a1d1..910b92def5d2 100644 +--- a/drivers/gpu/drm/meson/meson_crtc.c ++++ b/drivers/gpu/drm/meson/meson_crtc.c +@@ -199,10 +199,16 @@ void meson_crtc_irq(struct meson_drm *priv) + } else + meson_vpp_disable_interlace_vscaler_osd1(priv); + +- meson_canvas_setup(priv, MESON_CANVAS_ID_OSD1, +- priv->viu.osd1_addr, priv->viu.osd1_stride, +- priv->viu.osd1_height, MESON_CANVAS_WRAP_NONE, +- MESON_CANVAS_BLKMODE_LINEAR); ++ if (priv->canvas) ++ meson_canvas_config(priv->canvas, priv->canvas_id_osd1, ++ priv->viu.osd1_addr, priv->viu.osd1_stride, ++ priv->viu.osd1_height, MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR, 0); ++ else ++ meson_canvas_setup(priv, MESON_CANVAS_ID_OSD1, ++ priv->viu.osd1_addr, priv->viu.osd1_stride, ++ priv->viu.osd1_height, MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR); + + /* Enable OSD1 */ + writel_bits_relaxed(VPP_OSD1_POSTBLEND, VPP_OSD1_POSTBLEND, +diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c +index 588b3b0c8315..874c7a74a7c1 100644 +--- a/drivers/gpu/drm/meson/meson_drv.c ++++ b/drivers/gpu/drm/meson/meson_drv.c +@@ -220,24 +220,33 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) + goto free_drm; + } + +- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmc"); +- if (!res) { +- ret = -EINVAL; +- goto free_drm; +- } +- /* Simply ioremap since it may be a shared register zone */ +- regs = devm_ioremap(dev, res->start, resource_size(res)); +- if (!regs) { +- ret = -EADDRNOTAVAIL; +- goto free_drm; +- } ++ priv->canvas = meson_canvas_get(dev); ++ if (!IS_ERR(priv->canvas)) { ++ ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd1); ++ if (ret) ++ goto free_drm; ++ } else { ++ priv->canvas = NULL; + +- priv->dmc = devm_regmap_init_mmio(dev, regs, +- &meson_regmap_config); +- if (IS_ERR(priv->dmc)) { +- dev_err(&pdev->dev, "Couldn't create the DMC regmap\n"); +- ret = PTR_ERR(priv->dmc); +- goto free_drm; ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmc"); ++ if (!res) { ++ ret = -EINVAL; ++ goto free_drm; ++ } ++ /* Simply ioremap since it may be a shared register zone */ ++ regs = devm_ioremap(dev, res->start, resource_size(res)); ++ if (!regs) { ++ ret = -EADDRNOTAVAIL; ++ goto free_drm; ++ } ++ ++ priv->dmc = devm_regmap_init_mmio(dev, regs, ++ &meson_regmap_config); ++ if (IS_ERR(priv->dmc)) { ++ dev_err(&pdev->dev, "Couldn't create the DMC regmap\n"); ++ ret = PTR_ERR(priv->dmc); ++ goto free_drm; ++ } + } + + priv->vsync_irq = platform_get_irq(pdev, 0); +@@ -322,6 +331,9 @@ static void meson_drv_unbind(struct device *dev) + struct meson_drm *priv = dev_get_drvdata(dev); + struct drm_device *drm = priv->drm; + ++ if (priv->canvas) ++ meson_canvas_free(priv->canvas, priv->canvas_id_osd1); ++ + drm_dev_unregister(drm); + drm_irq_uninstall(drm); + drm_kms_helper_poll_fini(drm); +diff --git a/drivers/gpu/drm/meson/meson_drv.h b/drivers/gpu/drm/meson/meson_drv.h +index 8450d6ac8c9b..728d0ca33732 100644 +--- a/drivers/gpu/drm/meson/meson_drv.h ++++ b/drivers/gpu/drm/meson/meson_drv.h +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + #include + + struct meson_drm { +@@ -31,6 +32,9 @@ struct meson_drm { + struct regmap *dmc; + int vsync_irq; + ++ struct meson_canvas *canvas; ++ u8 canvas_id_osd1; ++ + struct drm_device *drm; + struct drm_crtc *crtc; + struct drm_fbdev_cma *fbdev; +diff --git a/drivers/gpu/drm/meson/meson_plane.c b/drivers/gpu/drm/meson/meson_plane.c +index 12c80dfcff59..51bec8e98a39 100644 +--- a/drivers/gpu/drm/meson/meson_plane.c ++++ b/drivers/gpu/drm/meson/meson_plane.c +@@ -90,6 +90,7 @@ static void meson_plane_atomic_update(struct drm_plane *plane, + .y2 = state->crtc_y + state->crtc_h, + }; + unsigned long flags; ++ u8 canvas_id_osd1; + + /* + * Update Coordinates +@@ -104,8 +105,13 @@ static void meson_plane_atomic_update(struct drm_plane *plane, + (0xFF << OSD_GLOBAL_ALPHA_SHIFT) | + OSD_BLK0_ENABLE; + ++ if (priv->canvas) ++ canvas_id_osd1 = priv->canvas_id_osd1; ++ else ++ canvas_id_osd1 = MESON_CANVAS_ID_OSD1; ++ + /* Set up BLK0 to point to the right canvas */ +- priv->viu.osd1_blk0_cfg[0] = ((MESON_CANVAS_ID_OSD1 << OSD_CANVAS_SEL) | ++ priv->viu.osd1_blk0_cfg[0] = ((canvas_id_osd1 << OSD_CANVAS_SEL) | + OSD_ENDIANNESS_LE); + + /* On GXBB, Use the old non-HDR RGB2YUV converter */ +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0019-arm64-dts-meson-gx-Add-canvas-provider-node-to-the-v.patch b/buildroot-external/board/hardkernel/patches/linux/0019-arm64-dts-meson-gx-Add-canvas-provider-node-to-the-v.patch new file mode 100644 index 000000000..7c1cc116a --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0019-arm64-dts-meson-gx-Add-canvas-provider-node-to-the-v.patch @@ -0,0 +1,28 @@ +From c16450ca851dbe9b7ad58464cea210610dd0433c Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Mon, 15 Oct 2018 14:38:24 +0200 +Subject: [PATCH 19/53] arm64: dts: meson-gx: Add canvas provider node to the + vpu + +Allows the vpu driver to optionally use a canvas provider node. + +Signed-off-by: Maxime Jourdan +--- + arch/arm64/boot/dts/amlogic/meson-gx.dtsi | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi +index fb6435431a94..5012607c95d2 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi +@@ -540,6 +540,7 @@ + interrupts = ; + #address-cells = <1>; + #size-cells = <0>; ++ amlogic,canvas = <&canvas>; + + /* CVBS VDAC output port */ + cvbs_vdac_port: port@0 { +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0020-drm-meson-Support-Overlay-plane-for-video-rendering.patch b/buildroot-external/board/hardkernel/patches/linux/0020-drm-meson-Support-Overlay-plane-for-video-rendering.patch new file mode 100644 index 000000000..aca8014f4 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0020-drm-meson-Support-Overlay-plane-for-video-rendering.patch @@ -0,0 +1,1260 @@ +From 6274e2adb018593aa6e5378d990fc9c9f08c3da0 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Thu, 2 Aug 2018 10:00:01 +0200 +Subject: [PATCH 20/53] drm/meson: Support Overlay plane for video rendering + +The Amlogic Meson GX SoCs support an Overlay plane behind the primary +plane for video rendering. + +This Overlay plane support various YUV layouts : +- YUYV +- NV12 / NV21 +- YUV444 / 422 / 420 / 411 / 410 + +The scaler supports a wide range of scaling ratios, but for simplicity, +plane atomic check limits the scaling from x5 to /5 in vertical and +horizontal scaling. + +The z-order is fixed and always behind the primary plane and cannot be changed. + +The scaling parameter algorithm was taken from the Amlogic vendor kernel +code and rewritten to match the atomic universal plane requirements. + +The video rendering using this overlay plane support has been tested using +the new Kodi DRM-KMS Prime rendering path along the in-review V4L2 Mem2Mem +Hardware Video Decoder up to 3840x2160 NV12 frames on various display modes. +--- + drivers/gpu/drm/meson/Makefile | 2 +- + drivers/gpu/drm/meson/meson_canvas.c | 7 +- + drivers/gpu/drm/meson/meson_canvas.h | 11 +- + drivers/gpu/drm/meson/meson_crtc.c | 216 ++++++++- + drivers/gpu/drm/meson/meson_drv.c | 29 +- + drivers/gpu/drm/meson/meson_drv.h | 52 +++ + drivers/gpu/drm/meson/meson_overlay.c | 586 ++++++++++++++++++++++++ + drivers/gpu/drm/meson/meson_overlay.h | 14 + + drivers/gpu/drm/meson/meson_registers.h | 3 + + drivers/gpu/drm/meson/meson_viu.c | 15 + + drivers/gpu/drm/meson/meson_vpp.c | 44 +- + 11 files changed, 971 insertions(+), 8 deletions(-) + create mode 100644 drivers/gpu/drm/meson/meson_overlay.c + create mode 100644 drivers/gpu/drm/meson/meson_overlay.h + +diff --git a/drivers/gpu/drm/meson/Makefile b/drivers/gpu/drm/meson/Makefile +index c5c4cc362f02..7709f2fbb9f7 100644 +--- a/drivers/gpu/drm/meson/Makefile ++++ b/drivers/gpu/drm/meson/Makefile +@@ -1,5 +1,5 @@ + meson-drm-y := meson_drv.o meson_plane.o meson_crtc.o meson_venc_cvbs.o +-meson-drm-y += meson_viu.o meson_vpp.o meson_venc.o meson_vclk.o meson_canvas.o ++meson-drm-y += meson_viu.o meson_vpp.o meson_venc.o meson_vclk.o meson_canvas.o meson_overlay.o + + obj-$(CONFIG_DRM_MESON) += meson-drm.o + obj-$(CONFIG_DRM_MESON_DW_HDMI) += meson_dw_hdmi.o +diff --git a/drivers/gpu/drm/meson/meson_canvas.c b/drivers/gpu/drm/meson/meson_canvas.c +index 08f6073d967e..5de11aa7c775 100644 +--- a/drivers/gpu/drm/meson/meson_canvas.c ++++ b/drivers/gpu/drm/meson/meson_canvas.c +@@ -39,6 +39,7 @@ + #define CANVAS_WIDTH_HBIT 0 + #define CANVAS_HEIGHT_BIT 9 + #define CANVAS_BLKMODE_BIT 24 ++#define CANVAS_ENDIAN_BIT 26 + #define DMC_CAV_LUT_ADDR 0x50 /* 0x14 offset in data sheet */ + #define CANVAS_LUT_WR_EN (0x2 << 8) + #define CANVAS_LUT_RD_EN (0x1 << 8) +@@ -47,7 +48,8 @@ void meson_canvas_setup(struct meson_drm *priv, + uint32_t canvas_index, uint32_t addr, + uint32_t stride, uint32_t height, + unsigned int wrap, +- unsigned int blkmode) ++ unsigned int blkmode, ++ unsigned int endian) + { + unsigned int val; + +@@ -60,7 +62,8 @@ void meson_canvas_setup(struct meson_drm *priv, + CANVAS_WIDTH_HBIT) | + (height << CANVAS_HEIGHT_BIT) | + (wrap << 22) | +- (blkmode << CANVAS_BLKMODE_BIT)); ++ (blkmode << CANVAS_BLKMODE_BIT) | ++ (endian << CANVAS_ENDIAN_BIT)); + + regmap_write(priv->dmc, DMC_CAV_LUT_ADDR, + CANVAS_LUT_WR_EN | canvas_index); +diff --git a/drivers/gpu/drm/meson/meson_canvas.h b/drivers/gpu/drm/meson/meson_canvas.h +index af1759da4b27..85dbf26e2826 100644 +--- a/drivers/gpu/drm/meson/meson_canvas.h ++++ b/drivers/gpu/drm/meson/meson_canvas.h +@@ -23,6 +23,9 @@ + #define __MESON_CANVAS_H + + #define MESON_CANVAS_ID_OSD1 0x4e ++#define MESON_CANVAS_ID_VD1_0 0x60 ++#define MESON_CANVAS_ID_VD1_1 0x61 ++#define MESON_CANVAS_ID_VD1_2 0x62 + + /* Canvas configuration. */ + #define MESON_CANVAS_WRAP_NONE 0x00 +@@ -33,10 +36,16 @@ + #define MESON_CANVAS_BLKMODE_32x32 0x01 + #define MESON_CANVAS_BLKMODE_64x64 0x02 + ++#define MESON_CANVAS_ENDIAN_SWAP16 0x1 ++#define MESON_CANVAS_ENDIAN_SWAP32 0x3 ++#define MESON_CANVAS_ENDIAN_SWAP64 0x7 ++#define MESON_CANVAS_ENDIAN_SWAP128 0xf ++ + void meson_canvas_setup(struct meson_drm *priv, + uint32_t canvas_index, uint32_t addr, + uint32_t stride, uint32_t height, + unsigned int wrap, +- unsigned int blkmode); ++ unsigned int blkmode, ++ unsigned int endian); + + #endif /* __MESON_CANVAS_H */ +diff --git a/drivers/gpu/drm/meson/meson_crtc.c b/drivers/gpu/drm/meson/meson_crtc.c +index 910b92def5d2..b292e9aedf52 100644 +--- a/drivers/gpu/drm/meson/meson_crtc.c ++++ b/drivers/gpu/drm/meson/meson_crtc.c +@@ -25,6 +25,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -98,6 +99,10 @@ static void meson_crtc_atomic_enable(struct drm_crtc *crtc, + writel(crtc_state->mode.hdisplay, + priv->io_base + _REG(VPP_POSTBLEND_H_SIZE)); + ++ /* VD1 Preblend vertical start/end */ ++ writel(FIELD_PREP(GENMASK(11, 0), 2303), ++ priv->io_base + _REG(VPP_PREBLEND_VD1_V_START_END)); ++ + writel_bits_relaxed(VPP_POSTBLEND_ENABLE, VPP_POSTBLEND_ENABLE, + priv->io_base + _REG(VPP_MISC)); + +@@ -116,11 +121,17 @@ static void meson_crtc_atomic_disable(struct drm_crtc *crtc, + + drm_crtc_vblank_off(crtc); + ++ DRM_DEBUG_DRIVER("\n"); ++ + priv->viu.osd1_enabled = false; + priv->viu.osd1_commit = false; + ++ priv->viu.vd1_enabled = false; ++ priv->viu.vd1_commit = false; ++ + /* Disable VPP Postblend */ +- writel_bits_relaxed(VPP_POSTBLEND_ENABLE, 0, ++ writel_bits_relaxed(VPP_OSD1_POSTBLEND | VPP_VD1_POSTBLEND | ++ VPP_VD1_PREBLEND | VPP_POSTBLEND_ENABLE, 0, + priv->io_base + _REG(VPP_MISC)); + + if (crtc->state->event && !crtc->state->active) { +@@ -155,6 +166,7 @@ static void meson_crtc_atomic_flush(struct drm_crtc *crtc, + struct meson_drm *priv = meson_crtc->priv; + + priv->viu.osd1_commit = true; ++ priv->viu.vd1_commit = true; + } + + static const struct drm_crtc_helper_funcs meson_crtc_helper_funcs = { +@@ -208,7 +220,7 @@ void meson_crtc_irq(struct meson_drm *priv) + meson_canvas_setup(priv, MESON_CANVAS_ID_OSD1, + priv->viu.osd1_addr, priv->viu.osd1_stride, + priv->viu.osd1_height, MESON_CANVAS_WRAP_NONE, +- MESON_CANVAS_BLKMODE_LINEAR); ++ MESON_CANVAS_BLKMODE_LINEAR, 0); + + /* Enable OSD1 */ + writel_bits_relaxed(VPP_OSD1_POSTBLEND, VPP_OSD1_POSTBLEND, +@@ -217,6 +229,206 @@ void meson_crtc_irq(struct meson_drm *priv) + priv->viu.osd1_commit = false; + } + ++ /* Update the VD1 registers */ ++ if (priv->viu.vd1_enabled && priv->viu.vd1_commit) { ++ ++ switch (priv->viu.vd1_planes) { ++ case 3: ++ if (priv->canvas) ++ meson_canvas_config(priv->canvas, ++ priv->canvas_id_vd1_2, ++ priv->viu.vd1_addr2, ++ priv->viu.vd1_stride2, ++ priv->viu.vd1_height2, ++ MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR, ++ MESON_CANVAS_ENDIAN_SWAP64); ++ else ++ meson_canvas_setup(priv, MESON_CANVAS_ID_VD1_2, ++ priv->viu.vd1_addr2, ++ priv->viu.vd1_stride2, ++ priv->viu.vd1_height2, ++ MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR, ++ MESON_CANVAS_ENDIAN_SWAP64); ++ /* fallthrough */ ++ case 2: ++ if (priv->canvas) ++ meson_canvas_config(priv->canvas, ++ priv->canvas_id_vd1_1, ++ priv->viu.vd1_addr1, ++ priv->viu.vd1_stride1, ++ priv->viu.vd1_height1, ++ MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR, ++ MESON_CANVAS_ENDIAN_SWAP64); ++ else ++ meson_canvas_setup(priv, MESON_CANVAS_ID_VD1_1, ++ priv->viu.vd1_addr2, ++ priv->viu.vd1_stride2, ++ priv->viu.vd1_height2, ++ MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR, ++ MESON_CANVAS_ENDIAN_SWAP64); ++ /* fallthrough */ ++ case 1: ++ if (priv->canvas) ++ meson_canvas_config(priv->canvas, ++ priv->canvas_id_vd1_0, ++ priv->viu.vd1_addr0, ++ priv->viu.vd1_stride0, ++ priv->viu.vd1_height0, ++ MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR, ++ MESON_CANVAS_ENDIAN_SWAP64); ++ else ++ meson_canvas_setup(priv, MESON_CANVAS_ID_VD1_0, ++ priv->viu.vd1_addr2, ++ priv->viu.vd1_stride2, ++ priv->viu.vd1_height2, ++ MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR, ++ MESON_CANVAS_ENDIAN_SWAP64); ++ }; ++ ++ writel_relaxed(priv->viu.vd1_if0_gen_reg, ++ priv->io_base + _REG(VD1_IF0_GEN_REG)); ++ writel_relaxed(priv->viu.vd1_if0_gen_reg, ++ priv->io_base + _REG(VD2_IF0_GEN_REG)); ++ writel_relaxed(priv->viu.vd1_if0_gen_reg2, ++ priv->io_base + _REG(VD1_IF0_GEN_REG2)); ++ writel_relaxed(priv->viu.viu_vd1_fmt_ctrl, ++ priv->io_base + _REG(VIU_VD1_FMT_CTRL)); ++ writel_relaxed(priv->viu.viu_vd1_fmt_ctrl, ++ priv->io_base + _REG(VIU_VD2_FMT_CTRL)); ++ writel_relaxed(priv->viu.viu_vd1_fmt_w, ++ priv->io_base + _REG(VIU_VD1_FMT_W)); ++ writel_relaxed(priv->viu.viu_vd1_fmt_w, ++ priv->io_base + _REG(VIU_VD2_FMT_W)); ++ writel_relaxed(priv->viu.vd1_if0_canvas0, ++ priv->io_base + _REG(VD1_IF0_CANVAS0)); ++ writel_relaxed(priv->viu.vd1_if0_canvas0, ++ priv->io_base + _REG(VD1_IF0_CANVAS1)); ++ writel_relaxed(priv->viu.vd1_if0_canvas0, ++ priv->io_base + _REG(VD2_IF0_CANVAS0)); ++ writel_relaxed(priv->viu.vd1_if0_canvas0, ++ priv->io_base + _REG(VD2_IF0_CANVAS1)); ++ writel_relaxed(priv->viu.vd1_if0_luma_x0, ++ priv->io_base + _REG(VD1_IF0_LUMA_X0)); ++ writel_relaxed(priv->viu.vd1_if0_luma_x0, ++ priv->io_base + _REG(VD1_IF0_LUMA_X1)); ++ writel_relaxed(priv->viu.vd1_if0_luma_x0, ++ priv->io_base + _REG(VD2_IF0_LUMA_X0)); ++ writel_relaxed(priv->viu.vd1_if0_luma_x0, ++ priv->io_base + _REG(VD2_IF0_LUMA_X1)); ++ writel_relaxed(priv->viu.vd1_if0_luma_y0, ++ priv->io_base + _REG(VD1_IF0_LUMA_Y0)); ++ writel_relaxed(priv->viu.vd1_if0_luma_y0, ++ priv->io_base + _REG(VD1_IF0_LUMA_Y1)); ++ writel_relaxed(priv->viu.vd1_if0_luma_y0, ++ priv->io_base + _REG(VD2_IF0_LUMA_Y0)); ++ writel_relaxed(priv->viu.vd1_if0_luma_y0, ++ priv->io_base + _REG(VD2_IF0_LUMA_Y1)); ++ writel_relaxed(priv->viu.vd1_if0_chroma_x0, ++ priv->io_base + _REG(VD1_IF0_CHROMA_X0)); ++ writel_relaxed(priv->viu.vd1_if0_chroma_x0, ++ priv->io_base + _REG(VD1_IF0_CHROMA_X1)); ++ writel_relaxed(priv->viu.vd1_if0_chroma_x0, ++ priv->io_base + _REG(VD2_IF0_CHROMA_X0)); ++ writel_relaxed(priv->viu.vd1_if0_chroma_x0, ++ priv->io_base + _REG(VD2_IF0_CHROMA_X1)); ++ writel_relaxed(priv->viu.vd1_if0_chroma_y0, ++ priv->io_base + _REG(VD1_IF0_CHROMA_Y0)); ++ writel_relaxed(priv->viu.vd1_if0_chroma_y0, ++ priv->io_base + _REG(VD1_IF0_CHROMA_Y1)); ++ writel_relaxed(priv->viu.vd1_if0_chroma_y0, ++ priv->io_base + _REG(VD2_IF0_CHROMA_Y0)); ++ writel_relaxed(priv->viu.vd1_if0_chroma_y0, ++ priv->io_base + _REG(VD2_IF0_CHROMA_Y1)); ++ writel_relaxed(priv->viu.vd1_if0_repeat_loop, ++ priv->io_base + _REG(VD1_IF0_RPT_LOOP)); ++ writel_relaxed(priv->viu.vd1_if0_repeat_loop, ++ priv->io_base + _REG(VD2_IF0_RPT_LOOP)); ++ writel_relaxed(priv->viu.vd1_if0_luma0_rpt_pat, ++ priv->io_base + _REG(VD1_IF0_LUMA0_RPT_PAT)); ++ writel_relaxed(priv->viu.vd1_if0_luma0_rpt_pat, ++ priv->io_base + _REG(VD2_IF0_LUMA0_RPT_PAT)); ++ writel_relaxed(priv->viu.vd1_if0_luma0_rpt_pat, ++ priv->io_base + _REG(VD1_IF0_LUMA1_RPT_PAT)); ++ writel_relaxed(priv->viu.vd1_if0_luma0_rpt_pat, ++ priv->io_base + _REG(VD2_IF0_LUMA1_RPT_PAT)); ++ writel_relaxed(priv->viu.vd1_if0_chroma0_rpt_pat, ++ priv->io_base + _REG(VD1_IF0_CHROMA0_RPT_PAT)); ++ writel_relaxed(priv->viu.vd1_if0_chroma0_rpt_pat, ++ priv->io_base + _REG(VD2_IF0_CHROMA0_RPT_PAT)); ++ writel_relaxed(priv->viu.vd1_if0_chroma0_rpt_pat, ++ priv->io_base + _REG(VD1_IF0_CHROMA1_RPT_PAT)); ++ writel_relaxed(priv->viu.vd1_if0_chroma0_rpt_pat, ++ priv->io_base + _REG(VD2_IF0_CHROMA1_RPT_PAT)); ++ writel_relaxed(0, priv->io_base + _REG(VD1_IF0_LUMA_PSEL)); ++ writel_relaxed(0, priv->io_base + _REG(VD1_IF0_CHROMA_PSEL)); ++ writel_relaxed(0, priv->io_base + _REG(VD2_IF0_LUMA_PSEL)); ++ writel_relaxed(0, priv->io_base + _REG(VD2_IF0_CHROMA_PSEL)); ++ writel_relaxed(priv->viu.vd1_range_map_y, ++ priv->io_base + _REG(VD1_IF0_RANGE_MAP_Y)); ++ writel_relaxed(priv->viu.vd1_range_map_cb, ++ priv->io_base + _REG(VD1_IF0_RANGE_MAP_CB)); ++ writel_relaxed(priv->viu.vd1_range_map_cr, ++ priv->io_base + _REG(VD1_IF0_RANGE_MAP_CR)); ++ writel_relaxed(0x78404, ++ priv->io_base + _REG(VPP_SC_MISC)); ++ writel_relaxed(priv->viu.vpp_pic_in_height, ++ priv->io_base + _REG(VPP_PIC_IN_HEIGHT)); ++ writel_relaxed(priv->viu.vpp_postblend_vd1_h_start_end, ++ priv->io_base + _REG(VPP_POSTBLEND_VD1_H_START_END)); ++ writel_relaxed(priv->viu.vpp_blend_vd2_h_start_end, ++ priv->io_base + _REG(VPP_BLEND_VD2_H_START_END)); ++ writel_relaxed(priv->viu.vpp_postblend_vd1_v_start_end, ++ priv->io_base + _REG(VPP_POSTBLEND_VD1_V_START_END)); ++ writel_relaxed(priv->viu.vpp_blend_vd2_v_start_end, ++ priv->io_base + _REG(VPP_BLEND_VD2_V_START_END)); ++ writel_relaxed(priv->viu.vpp_hsc_region12_startp, ++ priv->io_base + _REG(VPP_HSC_REGION12_STARTP)); ++ writel_relaxed(priv->viu.vpp_hsc_region34_startp, ++ priv->io_base + _REG(VPP_HSC_REGION34_STARTP)); ++ writel_relaxed(priv->viu.vpp_hsc_region4_endp, ++ priv->io_base + _REG(VPP_HSC_REGION4_ENDP)); ++ writel_relaxed(priv->viu.vpp_hsc_start_phase_step, ++ priv->io_base + _REG(VPP_HSC_START_PHASE_STEP)); ++ writel_relaxed(priv->viu.vpp_hsc_region1_phase_slope, ++ priv->io_base + _REG(VPP_HSC_REGION1_PHASE_SLOPE)); ++ writel_relaxed(priv->viu.vpp_hsc_region3_phase_slope, ++ priv->io_base + _REG(VPP_HSC_REGION3_PHASE_SLOPE)); ++ writel_relaxed(priv->viu.vpp_line_in_length, ++ priv->io_base + _REG(VPP_LINE_IN_LENGTH)); ++ writel_relaxed(priv->viu.vpp_preblend_h_size, ++ priv->io_base + _REG(VPP_PREBLEND_H_SIZE)); ++ writel_relaxed(priv->viu.vpp_vsc_region12_startp, ++ priv->io_base + _REG(VPP_VSC_REGION12_STARTP)); ++ writel_relaxed(priv->viu.vpp_vsc_region34_startp, ++ priv->io_base + _REG(VPP_VSC_REGION34_STARTP)); ++ writel_relaxed(priv->viu.vpp_vsc_region4_endp, ++ priv->io_base + _REG(VPP_VSC_REGION4_ENDP)); ++ writel_relaxed(priv->viu.vpp_vsc_start_phase_step, ++ priv->io_base + _REG(VPP_VSC_START_PHASE_STEP)); ++ writel_relaxed(priv->viu.vpp_vsc_ini_phase, ++ priv->io_base + _REG(VPP_VSC_INI_PHASE)); ++ writel_relaxed(priv->viu.vpp_vsc_phase_ctrl, ++ priv->io_base + _REG(VPP_VSC_PHASE_CTRL)); ++ writel_relaxed(priv->viu.vpp_hsc_phase_ctrl, ++ priv->io_base + _REG(VPP_HSC_PHASE_CTRL)); ++ writel_relaxed(0x42, priv->io_base + _REG(VPP_SCALE_COEF_IDX)); ++ ++ /* Enable VD1 */ ++ writel_bits_relaxed(VPP_VD1_PREBLEND | VPP_VD1_POSTBLEND | ++ VPP_COLOR_MNG_ENABLE, ++ VPP_VD1_PREBLEND | VPP_VD1_POSTBLEND | ++ VPP_COLOR_MNG_ENABLE, ++ priv->io_base + _REG(VPP_MISC)); ++ ++ priv->viu.vd1_commit = false; ++ } ++ + drm_crtc_handle_vblank(priv->crtc); + + spin_lock_irqsave(&priv->drm->event_lock, flags); +diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c +index 874c7a74a7c1..63bb2727b183 100644 +--- a/drivers/gpu/drm/meson/meson_drv.c ++++ b/drivers/gpu/drm/meson/meson_drv.c +@@ -41,6 +41,7 @@ + + #include "meson_drv.h" + #include "meson_plane.h" ++#include "meson_overlay.h" + #include "meson_crtc.h" + #include "meson_venc_cvbs.h" + +@@ -225,6 +226,24 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) + ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd1); + if (ret) + goto free_drm; ++ ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_0); ++ if (ret) { ++ meson_canvas_free(priv->canvas, priv->canvas_id_osd1); ++ goto free_drm; ++ } ++ ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_1); ++ if (ret) { ++ meson_canvas_free(priv->canvas, priv->canvas_id_osd1); ++ meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0); ++ goto free_drm; ++ } ++ ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_2); ++ if (ret) { ++ meson_canvas_free(priv->canvas, priv->canvas_id_osd1); ++ meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0); ++ meson_canvas_free(priv->canvas, priv->canvas_id_vd1_1); ++ goto free_drm; ++ } + } else { + priv->canvas = NULL; + +@@ -286,6 +305,10 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) + if (ret) + goto free_drm; + ++ ret = meson_overlay_create(priv); ++ if (ret) ++ goto free_drm; ++ + ret = meson_crtc_create(priv); + if (ret) + goto free_drm; +@@ -331,8 +354,12 @@ static void meson_drv_unbind(struct device *dev) + struct meson_drm *priv = dev_get_drvdata(dev); + struct drm_device *drm = priv->drm; + +- if (priv->canvas) ++ if (priv->canvas) { + meson_canvas_free(priv->canvas, priv->canvas_id_osd1); ++ meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0); ++ meson_canvas_free(priv->canvas, priv->canvas_id_vd1_1); ++ meson_canvas_free(priv->canvas, priv->canvas_id_vd1_2); ++ } + + drm_dev_unregister(drm); + drm_irq_uninstall(drm); +diff --git a/drivers/gpu/drm/meson/meson_drv.h b/drivers/gpu/drm/meson/meson_drv.h +index 728d0ca33732..c971557d4a48 100644 +--- a/drivers/gpu/drm/meson/meson_drv.h ++++ b/drivers/gpu/drm/meson/meson_drv.h +@@ -34,11 +34,15 @@ struct meson_drm { + + struct meson_canvas *canvas; + u8 canvas_id_osd1; ++ u8 canvas_id_vd1_0; ++ u8 canvas_id_vd1_1; ++ u8 canvas_id_vd1_2; + + struct drm_device *drm; + struct drm_crtc *crtc; + struct drm_fbdev_cma *fbdev; + struct drm_plane *primary_plane; ++ struct drm_plane *overlay_plane; + + /* Components Data */ + struct { +@@ -50,6 +54,54 @@ struct meson_drm { + uint32_t osd1_addr; + uint32_t osd1_stride; + uint32_t osd1_height; ++ ++ bool vd1_enabled; ++ bool vd1_commit; ++ unsigned int vd1_planes; ++ uint32_t vd1_if0_gen_reg; ++ uint32_t vd1_if0_luma_x0; ++ uint32_t vd1_if0_luma_y0; ++ uint32_t vd1_if0_chroma_x0; ++ uint32_t vd1_if0_chroma_y0; ++ uint32_t vd1_if0_repeat_loop; ++ uint32_t vd1_if0_luma0_rpt_pat; ++ uint32_t vd1_if0_chroma0_rpt_pat; ++ uint32_t vd1_range_map_y; ++ uint32_t vd1_range_map_cb; ++ uint32_t vd1_range_map_cr; ++ uint32_t viu_vd1_fmt_w; ++ uint32_t vd1_if0_canvas0; ++ uint32_t vd1_if0_gen_reg2; ++ uint32_t viu_vd1_fmt_ctrl; ++ uint32_t vd1_addr0; ++ uint32_t vd1_addr1; ++ uint32_t vd1_addr2; ++ uint32_t vd1_stride0; ++ uint32_t vd1_stride1; ++ uint32_t vd1_stride2; ++ uint32_t vd1_height0; ++ uint32_t vd1_height1; ++ uint32_t vd1_height2; ++ uint32_t vpp_pic_in_height; ++ uint32_t vpp_postblend_vd1_h_start_end; ++ uint32_t vpp_postblend_vd1_v_start_end; ++ uint32_t vpp_hsc_region12_startp; ++ uint32_t vpp_hsc_region34_startp; ++ uint32_t vpp_hsc_region4_endp; ++ uint32_t vpp_hsc_start_phase_step; ++ uint32_t vpp_hsc_region1_phase_slope; ++ uint32_t vpp_hsc_region3_phase_slope; ++ uint32_t vpp_line_in_length; ++ uint32_t vpp_preblend_h_size; ++ uint32_t vpp_vsc_region12_startp; ++ uint32_t vpp_vsc_region34_startp; ++ uint32_t vpp_vsc_region4_endp; ++ uint32_t vpp_vsc_start_phase_step; ++ uint32_t vpp_vsc_ini_phase; ++ uint32_t vpp_vsc_phase_ctrl; ++ uint32_t vpp_hsc_phase_ctrl; ++ uint32_t vpp_blend_vd2_h_start_end; ++ uint32_t vpp_blend_vd2_v_start_end; + } viu; + + struct { +diff --git a/drivers/gpu/drm/meson/meson_overlay.c b/drivers/gpu/drm/meson/meson_overlay.c +new file mode 100644 +index 000000000000..9aebc5e4b418 +--- /dev/null ++++ b/drivers/gpu/drm/meson/meson_overlay.c +@@ -0,0 +1,586 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Neil Armstrong ++ * Copyright (C) 2015 Amlogic, Inc. All rights reserved. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "meson_overlay.h" ++#include "meson_vpp.h" ++#include "meson_viu.h" ++#include "meson_canvas.h" ++#include "meson_registers.h" ++ ++/* VD1_IF0_GEN_REG */ ++#define VD_URGENT_CHROMA BIT(28) ++#define VD_URGENT_LUMA BIT(27) ++#define VD_HOLD_LINES(lines) FIELD_PREP(GENMASK(24, 19), lines) ++#define VD_DEMUX_MODE_RGB BIT(16) ++#define VD_BYTES_PER_PIXEL(val) FIELD_PREP(GENMASK(15, 14), val) ++#define VD_CHRO_RPT_LASTL_CTRL BIT(6) ++#define VD_LITTLE_ENDIAN BIT(4) ++#define VD_SEPARATE_EN BIT(1) ++#define VD_ENABLE BIT(0) ++ ++/* VD1_IF0_CANVAS0 */ ++#define CANVAS_ADDR2(addr) FIELD_PREP(GENMASK(23, 16), addr) ++#define CANVAS_ADDR1(addr) FIELD_PREP(GENMASK(15, 8), addr) ++#define CANVAS_ADDR0(addr) FIELD_PREP(GENMASK(7, 0), addr) ++ ++/* VD1_IF0_LUMA_X0 VD1_IF0_CHROMA_X0 */ ++#define VD_X_START(value) FIELD_PREP(GENMASK(14, 0), value) ++#define VD_X_END(value) FIELD_PREP(GENMASK(30, 16), value) ++ ++/* VD1_IF0_LUMA_Y0 VD1_IF0_CHROMA_Y0 */ ++#define VD_Y_START(value) FIELD_PREP(GENMASK(12, 0), value) ++#define VD_Y_END(value) FIELD_PREP(GENMASK(28, 16), value) ++ ++/* VD1_IF0_GEN_REG2 */ ++#define VD_COLOR_MAP(value) FIELD_PREP(GENMASK(1, 0), value) ++ ++/* VIU_VD1_FMT_CTRL */ ++#define VD_HORZ_Y_C_RATIO(value) FIELD_PREP(GENMASK(22, 21), value) ++#define VD_HORZ_FMT_EN BIT(20) ++#define VD_VERT_RPT_LINE0 BIT(16) ++#define VD_VERT_INITIAL_PHASE(value) FIELD_PREP(GENMASK(11, 8), value) ++#define VD_VERT_PHASE_STEP(value) FIELD_PREP(GENMASK(7, 1), value) ++#define VD_VERT_FMT_EN BIT(0) ++ ++/* VPP_POSTBLEND_VD1_H_START_END */ ++#define VD_H_END(value) FIELD_PREP(GENMASK(11, 0), value) ++#define VD_H_START(value) FIELD_PREP(GENMASK(27, 16), value) ++ ++/* VPP_POSTBLEND_VD1_V_START_END */ ++#define VD_V_END(value) FIELD_PREP(GENMASK(11, 0), value) ++#define VD_V_START(value) FIELD_PREP(GENMASK(27, 16), value) ++ ++/* VPP_BLEND_VD2_V_START_END */ ++#define VD2_V_END(value) FIELD_PREP(GENMASK(11, 0), value) ++#define VD2_V_START(value) FIELD_PREP(GENMASK(27, 16), value) ++ ++/* VIU_VD1_FMT_W */ ++#define VD_V_WIDTH(value) FIELD_PREP(GENMASK(11, 0), value) ++#define VD_H_WIDTH(value) FIELD_PREP(GENMASK(27, 16), value) ++ ++/* VPP_HSC_REGION12_STARTP VPP_HSC_REGION34_STARTP */ ++#define VD_REGION24_START(value) FIELD_PREP(GENMASK(11, 0), value) ++#define VD_REGION13_END(value) FIELD_PREP(GENMASK(27, 16), value) ++ ++struct meson_overlay { ++ struct drm_plane base; ++ struct meson_drm *priv; ++}; ++#define to_meson_overlay(x) container_of(x, struct meson_overlay, base) ++ ++#define FRAC_16_16(mult, div) (((mult) << 16) / (div)) ++ ++static int meson_overlay_atomic_check(struct drm_plane *plane, ++ struct drm_plane_state *state) ++{ ++ struct drm_crtc_state *crtc_state; ++ ++ if (!state->crtc) ++ return 0; ++ ++ crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc); ++ if (IS_ERR(crtc_state)) ++ return PTR_ERR(crtc_state); ++ ++ return drm_atomic_helper_check_plane_state(state, crtc_state, ++ FRAC_16_16(1, 5), ++ FRAC_16_16(5, 1), ++ true, true); ++} ++ ++/* Takes a fixed 16.16 number and converts it to integer. */ ++static inline int64_t fixed16_to_int(int64_t value) ++{ ++ return value >> 16; ++} ++ ++static const uint8_t skip_tab[6] = { ++ 0x24, 0x04, 0x68, 0x48, 0x28, 0x08, ++}; ++ ++static void meson_overlay_get_vertical_phase(unsigned int ratio_y, int *phase, ++ int *repeat, bool interlace) ++{ ++ int offset_in = 0; ++ int offset_out = 0; ++ int repeat_skip = 0; ++ ++ if (!interlace && ratio_y > (1 << 18)) ++ offset_out = (1 * ratio_y) >> 10; ++ ++ while ((offset_in + (4 << 8)) <= offset_out) { ++ repeat_skip++; ++ offset_in += 4 << 8; ++ } ++ ++ *phase = (offset_out - offset_in) >> 2; ++ ++ if (*phase > 0x100) ++ repeat_skip++; ++ ++ *phase = *phase & 0xff; ++ ++ if (repeat_skip > 5) ++ repeat_skip = 5; ++ ++ *repeat = skip_tab[repeat_skip]; ++} ++ ++static void meson_overlay_setup_scaler_params(struct meson_drm *priv, ++ struct drm_plane *plane, ++ bool interlace_mode) ++{ ++ struct drm_crtc_state *crtc_state = priv->crtc->state; ++ int video_top, video_left, video_width, video_height; ++ struct drm_plane_state *state = plane->state; ++ unsigned int vd_start_lines, vd_end_lines; ++ unsigned int hd_start_lines, hd_end_lines; ++ unsigned int crtc_height, crtc_width; ++ unsigned int vsc_startp, vsc_endp; ++ unsigned int hsc_startp, hsc_endp; ++ unsigned int crop_top, crop_left; ++ int vphase, vphase_repeat_skip; ++ unsigned int ratio_x, ratio_y; ++ int temp_height, temp_width; ++ unsigned int w_in, h_in; ++ int temp, start, end; ++ ++ if (!crtc_state) { ++ DRM_ERROR("Invalid crtc_state\n"); ++ return; ++ } ++ ++ crtc_height = crtc_state->mode.vdisplay; ++ crtc_width = crtc_state->mode.hdisplay; ++ ++ w_in = fixed16_to_int(state->src_w); ++ h_in = fixed16_to_int(state->src_h); ++ crop_top = fixed16_to_int(state->src_x); ++ crop_left = fixed16_to_int(state->src_x); ++ ++ video_top = state->crtc_y; ++ video_left = state->crtc_x; ++ video_width = state->crtc_w; ++ video_height = state->crtc_h; ++ ++ DRM_DEBUG("crtc_width %d crtc_height %d interlace %d\n", ++ crtc_width, crtc_height, interlace_mode); ++ DRM_DEBUG("w_in %d h_in %d crop_top %d crop_left %d\n", ++ w_in, h_in, crop_top, crop_left); ++ DRM_DEBUG("video top %d left %d width %d height %d\n", ++ video_top, video_left, video_width, video_height); ++ ++ ratio_x = (w_in << 18) / video_width; ++ ratio_y = (h_in << 18) / video_height; ++ ++ if (ratio_x * video_width < (w_in << 18)) ++ ratio_x++; ++ ++ DRM_DEBUG("ratio x 0x%x y 0x%x\n", ratio_x, ratio_y); ++ ++ meson_overlay_get_vertical_phase(ratio_y, &vphase, &vphase_repeat_skip, ++ interlace_mode); ++ ++ DRM_DEBUG("vphase 0x%x skip %d\n", vphase, vphase_repeat_skip); ++ ++ /* Vertical */ ++ ++ start = video_top + video_height / 2 - ((h_in << 17) / ratio_y); ++ end = (h_in << 18) / ratio_y + start - 1; ++ ++ if (video_top < 0 && start < 0) ++ vd_start_lines = (-(start) * ratio_y) >> 18; ++ else if (start < video_top) ++ vd_start_lines = ((video_top - start) * ratio_y) >> 18; ++ else ++ vd_start_lines = 0; ++ ++ if (video_top < 0) ++ temp_height = min_t(unsigned int, ++ video_top + video_height - 1, ++ crtc_height - 1); ++ else ++ temp_height = min_t(unsigned int, ++ video_top + video_height - 1, ++ crtc_height - 1) - video_top + 1; ++ ++ temp = vd_start_lines + (temp_height * ratio_y >> 18); ++ vd_end_lines = (temp <= (h_in - 1)) ? temp : (h_in - 1); ++ ++ vd_start_lines += crop_left; ++ vd_end_lines += crop_left; ++ ++ /* ++ * TOFIX: Input frames are handled and scaled like progressive frames, ++ * proper handling of interlaced field input frames need to be figured ++ * out using the proper framebuffer flags set by userspace. ++ */ ++ if (interlace_mode) { ++ start >>= 1; ++ end >>= 1; ++ } ++ ++ vsc_startp = max_t(int, start, ++ max_t(int, 0, video_top)); ++ vsc_endp = min_t(int, end, ++ min_t(int, crtc_height - 1, ++ video_top + video_height - 1)); ++ ++ DRM_DEBUG("vsc startp %d endp %d start_lines %d end_lines %d\n", ++ vsc_startp, vsc_endp, vd_start_lines, vd_end_lines); ++ ++ /* Horizontal */ ++ ++ start = video_left + video_width / 2 - ((w_in << 17) / ratio_x); ++ end = (w_in << 18) / ratio_x + start - 1; ++ ++ if (video_left < 0 && start < 0) ++ hd_start_lines = (-(start) * ratio_x) >> 18; ++ else if (start < video_left) ++ hd_start_lines = ((video_left - start) * ratio_x) >> 18; ++ else ++ hd_start_lines = 0; ++ ++ if (video_left < 0) ++ temp_width = min_t(unsigned int, ++ video_left + video_width - 1, ++ crtc_width - 1); ++ else ++ temp_width = min_t(unsigned int, ++ video_left + video_width - 1, ++ crtc_width - 1) - video_left + 1; ++ ++ temp = hd_start_lines + (temp_width * ratio_x >> 18); ++ hd_end_lines = (temp <= (w_in - 1)) ? temp : (w_in - 1); ++ ++ priv->viu.vpp_line_in_length = hd_end_lines - hd_start_lines + 1; ++ hsc_startp = max_t(int, start, max_t(int, 0, video_left)); ++ hsc_endp = min_t(int, end, min_t(int, crtc_width - 1, ++ video_left + video_width - 1)); ++ ++ hd_start_lines += crop_top; ++ hd_end_lines += crop_top; ++ ++ DRM_DEBUG("hsc startp %d endp %d start_lines %d end_lines %d\n", ++ hsc_startp, hsc_endp, hd_start_lines, hd_end_lines); ++ ++ priv->viu.vpp_vsc_start_phase_step = ratio_y << 6; ++ ++ priv->viu.vpp_vsc_ini_phase = vphase << 8; ++ priv->viu.vpp_vsc_phase_ctrl = (1 << 13) | (4 << 8) | ++ vphase_repeat_skip; ++ ++ priv->viu.vd1_if0_luma_x0 = VD_X_START(hd_start_lines) | ++ VD_X_END(hd_end_lines); ++ priv->viu.vd1_if0_chroma_x0 = VD_X_START(hd_start_lines >> 1) | ++ VD_X_END(hd_end_lines >> 1); ++ ++ priv->viu.viu_vd1_fmt_w = ++ VD_H_WIDTH(hd_end_lines - hd_start_lines + 1) | ++ VD_V_WIDTH(hd_end_lines/2 - hd_start_lines/2 + 1); ++ ++ priv->viu.vd1_if0_luma_y0 = VD_Y_START(vd_start_lines) | ++ VD_Y_END(vd_end_lines); ++ ++ priv->viu.vd1_if0_chroma_y0 = VD_Y_START(vd_start_lines >> 1) | ++ VD_Y_END(vd_end_lines >> 1); ++ ++ priv->viu.vpp_pic_in_height = h_in; ++ ++ priv->viu.vpp_postblend_vd1_h_start_end = VD_H_START(hsc_startp) | ++ VD_H_END(hsc_endp); ++ priv->viu.vpp_blend_vd2_h_start_end = VD_H_START(hd_start_lines) | ++ VD_H_END(hd_end_lines); ++ priv->viu.vpp_hsc_region12_startp = VD_REGION13_END(0) | ++ VD_REGION24_START(hsc_startp); ++ priv->viu.vpp_hsc_region34_startp = ++ VD_REGION13_END(hsc_startp) | ++ VD_REGION24_START(hsc_endp - hsc_startp); ++ priv->viu.vpp_hsc_region4_endp = hsc_endp - hsc_startp; ++ priv->viu.vpp_hsc_start_phase_step = ratio_x << 6; ++ priv->viu.vpp_hsc_region1_phase_slope = 0; ++ priv->viu.vpp_hsc_region3_phase_slope = 0; ++ priv->viu.vpp_hsc_phase_ctrl = (1 << 21) | (4 << 16); ++ ++ priv->viu.vpp_line_in_length = hd_end_lines - hd_start_lines + 1; ++ priv->viu.vpp_preblend_h_size = hd_end_lines - hd_start_lines + 1; ++ ++ priv->viu.vpp_postblend_vd1_v_start_end = VD_V_START(vsc_startp) | ++ VD_V_END(vsc_endp); ++ priv->viu.vpp_blend_vd2_v_start_end = ++ VD2_V_START((vd_end_lines + 1) >> 1) | ++ VD2_V_END(vd_end_lines); ++ ++ priv->viu.vpp_vsc_region12_startp = 0; ++ priv->viu.vpp_vsc_region34_startp = ++ VD_REGION13_END(vsc_endp - vsc_startp) | ++ VD_REGION24_START(vsc_endp - vsc_startp); ++ priv->viu.vpp_vsc_region4_endp = vsc_endp - vsc_startp; ++ priv->viu.vpp_vsc_start_phase_step = ratio_y << 6; ++} ++ ++static void meson_overlay_atomic_update(struct drm_plane *plane, ++ struct drm_plane_state *old_state) ++{ ++ struct meson_overlay *meson_overlay = to_meson_overlay(plane); ++ struct drm_plane_state *state = plane->state; ++ struct drm_framebuffer *fb = state->fb; ++ struct meson_drm *priv = meson_overlay->priv; ++ struct drm_gem_cma_object *gem; ++ unsigned long flags; ++ bool interlace_mode; ++ ++ DRM_DEBUG_DRIVER("\n"); ++ ++ /* Fallback is canvas provider is not available */ ++ if (!priv->canvas) { ++ priv->canvas_id_vd1_0 = MESON_CANVAS_ID_VD1_0; ++ priv->canvas_id_vd1_1 = MESON_CANVAS_ID_VD1_1; ++ priv->canvas_id_vd1_2 = MESON_CANVAS_ID_VD1_2; ++ } ++ ++ interlace_mode = state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE; ++ ++ spin_lock_irqsave(&priv->drm->event_lock, flags); ++ ++ priv->viu.vd1_if0_gen_reg = VD_URGENT_CHROMA | ++ VD_URGENT_LUMA | ++ VD_HOLD_LINES(9) | ++ VD_CHRO_RPT_LASTL_CTRL | ++ VD_ENABLE; ++ ++ /* Setup scaler params */ ++ meson_overlay_setup_scaler_params(priv, plane, interlace_mode); ++ ++ priv->viu.vd1_if0_repeat_loop = 0; ++ priv->viu.vd1_if0_luma0_rpt_pat = interlace_mode ? 8 : 0; ++ priv->viu.vd1_if0_chroma0_rpt_pat = interlace_mode ? 8 : 0; ++ priv->viu.vd1_range_map_y = 0; ++ priv->viu.vd1_range_map_cb = 0; ++ priv->viu.vd1_range_map_cr = 0; ++ ++ /* Default values for RGB888/YUV444 */ ++ priv->viu.vd1_if0_gen_reg2 = 0; ++ priv->viu.viu_vd1_fmt_ctrl = 0; ++ ++ switch (fb->format->format) { ++ /* TOFIX DRM_FORMAT_RGB888 should be supported */ ++ case DRM_FORMAT_YUYV: ++ priv->viu.vd1_if0_gen_reg |= VD_BYTES_PER_PIXEL(1); ++ priv->viu.vd1_if0_canvas0 = ++ CANVAS_ADDR2(priv->canvas_id_vd1_0) | ++ CANVAS_ADDR1(priv->canvas_id_vd1_0) | ++ CANVAS_ADDR0(priv->canvas_id_vd1_0); ++ priv->viu.viu_vd1_fmt_ctrl = VD_HORZ_Y_C_RATIO(1) | /* /2 */ ++ VD_HORZ_FMT_EN | ++ VD_VERT_RPT_LINE0 | ++ VD_VERT_INITIAL_PHASE(12) | ++ VD_VERT_PHASE_STEP(16) | /* /2 */ ++ VD_VERT_FMT_EN; ++ break; ++ case DRM_FORMAT_NV12: ++ case DRM_FORMAT_NV21: ++ priv->viu.vd1_if0_gen_reg |= VD_SEPARATE_EN; ++ priv->viu.vd1_if0_canvas0 = ++ CANVAS_ADDR2(priv->canvas_id_vd1_1) | ++ CANVAS_ADDR1(priv->canvas_id_vd1_1) | ++ CANVAS_ADDR0(priv->canvas_id_vd1_0); ++ if (fb->format->format == DRM_FORMAT_NV12) ++ priv->viu.vd1_if0_gen_reg2 = VD_COLOR_MAP(1); ++ else ++ priv->viu.vd1_if0_gen_reg2 = VD_COLOR_MAP(2); ++ priv->viu.viu_vd1_fmt_ctrl = VD_HORZ_Y_C_RATIO(1) | /* /2 */ ++ VD_HORZ_FMT_EN | ++ VD_VERT_RPT_LINE0 | ++ VD_VERT_INITIAL_PHASE(12) | ++ VD_VERT_PHASE_STEP(8) | /* /4 */ ++ VD_VERT_FMT_EN; ++ break; ++ case DRM_FORMAT_YUV444: ++ case DRM_FORMAT_YUV422: ++ case DRM_FORMAT_YUV420: ++ case DRM_FORMAT_YUV411: ++ case DRM_FORMAT_YUV410: ++ priv->viu.vd1_if0_gen_reg |= VD_SEPARATE_EN; ++ priv->viu.vd1_if0_canvas0 = ++ CANVAS_ADDR2(priv->canvas_id_vd1_2) | ++ CANVAS_ADDR1(priv->canvas_id_vd1_1) | ++ CANVAS_ADDR0(priv->canvas_id_vd1_0); ++ switch (fb->format->format) { ++ case DRM_FORMAT_YUV422: ++ priv->viu.viu_vd1_fmt_ctrl = ++ VD_HORZ_Y_C_RATIO(1) | /* /2 */ ++ VD_HORZ_FMT_EN | ++ VD_VERT_RPT_LINE0 | ++ VD_VERT_INITIAL_PHASE(12) | ++ VD_VERT_PHASE_STEP(16) | /* /2 */ ++ VD_VERT_FMT_EN; ++ break; ++ case DRM_FORMAT_YUV420: ++ priv->viu.viu_vd1_fmt_ctrl = ++ VD_HORZ_Y_C_RATIO(1) | /* /2 */ ++ VD_HORZ_FMT_EN | ++ VD_VERT_RPT_LINE0 | ++ VD_VERT_INITIAL_PHASE(12) | ++ VD_VERT_PHASE_STEP(8) | /* /4 */ ++ VD_VERT_FMT_EN; ++ break; ++ case DRM_FORMAT_YUV411: ++ priv->viu.viu_vd1_fmt_ctrl = ++ VD_HORZ_Y_C_RATIO(2) | /* /4 */ ++ VD_HORZ_FMT_EN | ++ VD_VERT_RPT_LINE0 | ++ VD_VERT_INITIAL_PHASE(12) | ++ VD_VERT_PHASE_STEP(16) | /* /2 */ ++ VD_VERT_FMT_EN; ++ break; ++ case DRM_FORMAT_YUV410: ++ priv->viu.viu_vd1_fmt_ctrl = ++ VD_HORZ_Y_C_RATIO(2) | /* /4 */ ++ VD_HORZ_FMT_EN | ++ VD_VERT_RPT_LINE0 | ++ VD_VERT_INITIAL_PHASE(12) | ++ VD_VERT_PHASE_STEP(8) | /* /4 */ ++ VD_VERT_FMT_EN; ++ break; ++ } ++ break; ++ } ++ ++ /* Update Canvas with buffer address */ ++ priv->viu.vd1_planes = drm_format_num_planes(fb->format->format); ++ ++ switch (priv->viu.vd1_planes) { ++ case 3: ++ gem = drm_fb_cma_get_gem_obj(fb, 2); ++ priv->viu.vd1_addr2 = gem->paddr + fb->offsets[2]; ++ priv->viu.vd1_stride2 = fb->pitches[2]; ++ priv->viu.vd1_height2 = ++ drm_format_plane_height(fb->height, ++ fb->format->format, 2); ++ DRM_DEBUG("plane 2 addr 0x%x stride %d height %d\n", ++ priv->viu.vd1_addr2, ++ priv->viu.vd1_stride2, ++ priv->viu.vd1_height2); ++ /* fallthrough */ ++ case 2: ++ gem = drm_fb_cma_get_gem_obj(fb, 1); ++ priv->viu.vd1_addr1 = gem->paddr + fb->offsets[1]; ++ priv->viu.vd1_stride1 = fb->pitches[1]; ++ priv->viu.vd1_height1 = ++ drm_format_plane_height(fb->height, ++ fb->format->format, 1); ++ DRM_DEBUG("plane 1 addr 0x%x stride %d height %d\n", ++ priv->viu.vd1_addr1, ++ priv->viu.vd1_stride1, ++ priv->viu.vd1_height1); ++ /* fallthrough */ ++ case 1: ++ gem = drm_fb_cma_get_gem_obj(fb, 0); ++ priv->viu.vd1_addr0 = gem->paddr + fb->offsets[0]; ++ priv->viu.vd1_stride0 = fb->pitches[0]; ++ priv->viu.vd1_height0 = ++ drm_format_plane_height(fb->height, ++ fb->format->format, 0); ++ DRM_DEBUG("plane 0 addr 0x%x stride %d height %d\n", ++ priv->viu.vd1_addr0, ++ priv->viu.vd1_stride0, ++ priv->viu.vd1_height0); ++ } ++ ++ priv->viu.vd1_enabled = true; ++ ++ spin_unlock_irqrestore(&priv->drm->event_lock, flags); ++ ++ DRM_DEBUG_DRIVER("\n"); ++} ++ ++static void meson_overlay_atomic_disable(struct drm_plane *plane, ++ struct drm_plane_state *old_state) ++{ ++ struct meson_overlay *meson_overlay = to_meson_overlay(plane); ++ struct meson_drm *priv = meson_overlay->priv; ++ ++ DRM_DEBUG_DRIVER("\n"); ++ ++ priv->viu.vd1_enabled = false; ++ ++ /* Disable VD1 */ ++ writel_bits_relaxed(VPP_VD1_POSTBLEND | VPP_VD1_PREBLEND, 0, ++ priv->io_base + _REG(VPP_MISC)); ++ ++} ++ ++static const struct drm_plane_helper_funcs meson_overlay_helper_funcs = { ++ .atomic_check = meson_overlay_atomic_check, ++ .atomic_disable = meson_overlay_atomic_disable, ++ .atomic_update = meson_overlay_atomic_update, ++}; ++ ++static const struct drm_plane_funcs meson_overlay_funcs = { ++ .update_plane = drm_atomic_helper_update_plane, ++ .disable_plane = drm_atomic_helper_disable_plane, ++ .destroy = drm_plane_cleanup, ++ .reset = drm_atomic_helper_plane_reset, ++ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, ++ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, ++}; ++ ++static const uint32_t supported_drm_formats[] = { ++ DRM_FORMAT_YUYV, ++ DRM_FORMAT_NV12, ++ DRM_FORMAT_NV21, ++ DRM_FORMAT_YUV444, ++ DRM_FORMAT_YUV422, ++ DRM_FORMAT_YUV420, ++ DRM_FORMAT_YUV411, ++ DRM_FORMAT_YUV410, ++}; ++ ++int meson_overlay_create(struct meson_drm *priv) ++{ ++ struct meson_overlay *meson_overlay; ++ struct drm_plane *plane; ++ ++ DRM_DEBUG_DRIVER("\n"); ++ ++ meson_overlay = devm_kzalloc(priv->drm->dev, sizeof(*meson_overlay), ++ GFP_KERNEL); ++ if (!meson_overlay) ++ return -ENOMEM; ++ ++ meson_overlay->priv = priv; ++ plane = &meson_overlay->base; ++ ++ drm_universal_plane_init(priv->drm, plane, 0xFF, ++ &meson_overlay_funcs, ++ supported_drm_formats, ++ ARRAY_SIZE(supported_drm_formats), ++ NULL, ++ DRM_PLANE_TYPE_OVERLAY, "meson_overlay_plane"); ++ ++ drm_plane_helper_add(plane, &meson_overlay_helper_funcs); ++ ++ priv->overlay_plane = plane; ++ ++ DRM_DEBUG_DRIVER("\n"); ++ ++ return 0; ++} +diff --git a/drivers/gpu/drm/meson/meson_overlay.h b/drivers/gpu/drm/meson/meson_overlay.h +new file mode 100644 +index 000000000000..dae24f5ac63d +--- /dev/null ++++ b/drivers/gpu/drm/meson/meson_overlay.h +@@ -0,0 +1,14 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Neil Armstrong ++ */ ++ ++#ifndef __MESON_OVERLAY_H ++#define __MESON_OVERLAY_H ++ ++#include "meson_drv.h" ++ ++int meson_overlay_create(struct meson_drm *priv); ++ ++#endif /* __MESON_OVERLAY_H */ +diff --git a/drivers/gpu/drm/meson/meson_registers.h b/drivers/gpu/drm/meson/meson_registers.h +index bca87143e548..5c7e02c703bc 100644 +--- a/drivers/gpu/drm/meson/meson_registers.h ++++ b/drivers/gpu/drm/meson/meson_registers.h +@@ -286,6 +286,7 @@ + #define VIU_OSD1_MATRIX_COEF22_30 0x1a9d + #define VIU_OSD1_MATRIX_COEF31_32 0x1a9e + #define VIU_OSD1_MATRIX_COEF40_41 0x1a9f ++#define VD1_IF0_GEN_REG3 0x1aa7 + #define VIU_OSD1_EOTF_CTL 0x1ad4 + #define VIU_OSD1_EOTF_COEF00_01 0x1ad5 + #define VIU_OSD1_EOTF_COEF02_10 0x1ad6 +@@ -297,6 +298,7 @@ + #define VIU_OSD1_OETF_CTL 0x1adc + #define VIU_OSD1_OETF_LUT_ADDR_PORT 0x1add + #define VIU_OSD1_OETF_LUT_DATA_PORT 0x1ade ++#define AFBC_ENABLE 0x1ae0 + + /* vpp */ + #define VPP_DUMMY_DATA 0x1d00 +@@ -349,6 +351,7 @@ + #define VPP_VD2_PREBLEND BIT(15) + #define VPP_OSD1_PREBLEND BIT(16) + #define VPP_OSD2_PREBLEND BIT(17) ++#define VPP_COLOR_MNG_ENABLE BIT(28) + #define VPP_OFIFO_SIZE 0x1d27 + #define VPP_FIFO_STATUS 0x1d28 + #define VPP_SMOKE_CTRL 0x1d29 +diff --git a/drivers/gpu/drm/meson/meson_viu.c b/drivers/gpu/drm/meson/meson_viu.c +index 26a0857878bf..90d9ae3c2b81 100644 +--- a/drivers/gpu/drm/meson/meson_viu.c ++++ b/drivers/gpu/drm/meson/meson_viu.c +@@ -329,6 +329,21 @@ void meson_viu_init(struct meson_drm *priv) + 0xff << OSD_REPLACE_SHIFT, + priv->io_base + _REG(VIU_OSD2_CTRL_STAT2)); + ++ /* Disable VD1 AFBC */ ++ /* di_mif0_en=0 mif0_to_vpp_en=0 di_mad_en=0 */ ++ writel_bits_relaxed(0x7 << 16, 0, ++ priv->io_base + _REG(VIU_MISC_CTRL0)); ++ /* afbc vd1 set=0 */ ++ writel_bits_relaxed(BIT(20), 0, ++ priv->io_base + _REG(VIU_MISC_CTRL0)); ++ writel_relaxed(0, priv->io_base + _REG(AFBC_ENABLE)); ++ ++ writel_relaxed(0x00FF00C0, ++ priv->io_base + _REG(VD1_IF0_LUMA_FIFO_SIZE)); ++ writel_relaxed(0x00FF00C0, ++ priv->io_base + _REG(VD2_IF0_LUMA_FIFO_SIZE)); ++ ++ + priv->viu.osd1_enabled = false; + priv->viu.osd1_commit = false; + priv->viu.osd1_interlace = false; +diff --git a/drivers/gpu/drm/meson/meson_vpp.c b/drivers/gpu/drm/meson/meson_vpp.c +index 27356f81a0ab..5dc24a99e978 100644 +--- a/drivers/gpu/drm/meson/meson_vpp.c ++++ b/drivers/gpu/drm/meson/meson_vpp.c +@@ -122,6 +122,31 @@ static void meson_vpp_write_scaling_filter_coefs(struct meson_drm *priv, + priv->io_base + _REG(VPP_OSD_SCALE_COEF)); + } + ++static const uint32_t vpp_filter_coefs_bicubic[] = { ++ 0x00800000, 0x007f0100, 0xff7f0200, 0xfe7f0300, ++ 0xfd7e0500, 0xfc7e0600, 0xfb7d0800, 0xfb7c0900, ++ 0xfa7b0b00, 0xfa7a0dff, 0xf9790fff, 0xf97711ff, ++ 0xf87613ff, 0xf87416fe, 0xf87218fe, 0xf8701afe, ++ 0xf76f1dfd, 0xf76d1ffd, 0xf76b21fd, 0xf76824fd, ++ 0xf76627fc, 0xf76429fc, 0xf7612cfc, 0xf75f2ffb, ++ 0xf75d31fb, 0xf75a34fb, 0xf75837fa, 0xf7553afa, ++ 0xf8523cfa, 0xf8503ff9, 0xf84d42f9, 0xf84a45f9, ++ 0xf84848f8 ++}; ++ ++static void meson_vpp_write_vd_scaling_filter_coefs(struct meson_drm *priv, ++ const unsigned int *coefs, ++ bool is_horizontal) ++{ ++ int i; ++ ++ writel_relaxed(is_horizontal ? BIT(8) : 0, ++ priv->io_base + _REG(VPP_SCALE_COEF_IDX)); ++ for (i = 0; i < 33; i++) ++ writel_relaxed(coefs[i], ++ priv->io_base + _REG(VPP_SCALE_COEF)); ++} ++ + void meson_vpp_init(struct meson_drm *priv) + { + /* set dummy data default YUV black */ +@@ -150,17 +175,34 @@ void meson_vpp_init(struct meson_drm *priv) + + /* Force all planes off */ + writel_bits_relaxed(VPP_OSD1_POSTBLEND | VPP_OSD2_POSTBLEND | +- VPP_VD1_POSTBLEND | VPP_VD2_POSTBLEND, 0, ++ VPP_VD1_POSTBLEND | VPP_VD2_POSTBLEND | ++ VPP_VD1_PREBLEND | VPP_VD2_PREBLEND, 0, + priv->io_base + _REG(VPP_MISC)); + ++ /* Setup default VD settings */ ++ writel_relaxed(4096, ++ priv->io_base + _REG(VPP_PREBLEND_VD1_H_START_END)); ++ writel_relaxed(4096, ++ priv->io_base + _REG(VPP_BLEND_VD2_H_START_END)); ++ + /* Disable Scalers */ + writel_relaxed(0, priv->io_base + _REG(VPP_OSD_SC_CTRL0)); + writel_relaxed(0, priv->io_base + _REG(VPP_OSD_VSC_CTRL0)); + writel_relaxed(0, priv->io_base + _REG(VPP_OSD_HSC_CTRL0)); ++ writel_relaxed(4 | (4 << 8) | BIT(15), ++ priv->io_base + _REG(VPP_SC_MISC)); ++ ++ writel_relaxed(1, priv->io_base + _REG(VPP_VADJ_CTRL)); + + /* Write in the proper filter coefficients. */ + meson_vpp_write_scaling_filter_coefs(priv, + vpp_filter_coefs_4point_bspline, false); + meson_vpp_write_scaling_filter_coefs(priv, + vpp_filter_coefs_4point_bspline, true); ++ ++ /* Write the VD proper filter coefficients. */ ++ meson_vpp_write_vd_scaling_filter_coefs(priv, vpp_filter_coefs_bicubic, ++ false); ++ meson_vpp_write_vd_scaling_filter_coefs(priv, vpp_filter_coefs_bicubic, ++ true); + } +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0021-drm-meson-move-OSD-scaler-management-into-plane-atom.patch b/buildroot-external/board/hardkernel/patches/linux/0021-drm-meson-move-OSD-scaler-management-into-plane-atom.patch new file mode 100644 index 000000000..6c2736092 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0021-drm-meson-move-OSD-scaler-management-into-plane-atom.patch @@ -0,0 +1,200 @@ +From 1d7b86b1151ec8ad46c689138977c57053c99608 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Mon, 29 Oct 2018 17:04:05 +0100 +Subject: [PATCH 21/53] drm/meson: move OSD scaler management into plane atomic + update + +In preparation to support the Primary Plane scaling, move the basic +OSD Interlace-Only scaler setup code into the primary plane atomic +update callback and handle the vsync scaler update like the overlay +plane scaling registers update. +--- + drivers/gpu/drm/meson/meson_crtc.c | 35 ++++++++++++---------- + drivers/gpu/drm/meson/meson_drv.h | 10 +++++++ + drivers/gpu/drm/meson/meson_plane.c | 39 +++++++++++++++++++++++- + drivers/gpu/drm/meson/meson_vpp.c | 46 ----------------------------- + 4 files changed, 68 insertions(+), 62 deletions(-) + +diff --git a/drivers/gpu/drm/meson/meson_crtc.c b/drivers/gpu/drm/meson/meson_crtc.c +index b292e9aedf52..23df4abd95c9 100644 +--- a/drivers/gpu/drm/meson/meson_crtc.c ++++ b/drivers/gpu/drm/meson/meson_crtc.c +@@ -195,21 +195,26 @@ void meson_crtc_irq(struct meson_drm *priv) + priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W3)); + writel_relaxed(priv->viu.osd1_blk0_cfg[4], + priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W4)); +- +- /* If output is interlace, make use of the Scaler */ +- if (priv->viu.osd1_interlace) { +- struct drm_plane *plane = priv->primary_plane; +- struct drm_plane_state *state = plane->state; +- struct drm_rect dest = { +- .x1 = state->crtc_x, +- .y1 = state->crtc_y, +- .x2 = state->crtc_x + state->crtc_w, +- .y2 = state->crtc_y + state->crtc_h, +- }; +- +- meson_vpp_setup_interlace_vscaler_osd1(priv, &dest); +- } else +- meson_vpp_disable_interlace_vscaler_osd1(priv); ++ writel_relaxed(priv->viu.osd_sc_ctrl0, ++ priv->io_base + _REG(VPP_OSD_SC_CTRL0)); ++ writel_relaxed(priv->viu.osd_sc_i_wh_m1, ++ priv->io_base + _REG(VPP_OSD_SCI_WH_M1)); ++ writel_relaxed(priv->viu.osd_sc_o_h_start_end, ++ priv->io_base + _REG(VPP_OSD_SCO_H_START_END)); ++ writel_relaxed(priv->viu.osd_sc_o_v_start_end, ++ priv->io_base + _REG(VPP_OSD_SCO_V_START_END)); ++ writel_relaxed(priv->viu.osd_sc_v_ini_phase, ++ priv->io_base + _REG(VPP_OSD_VSC_INI_PHASE)); ++ writel_relaxed(priv->viu.osd_sc_v_phase_step, ++ priv->io_base + _REG(VPP_OSD_VSC_PHASE_STEP)); ++ writel_relaxed(priv->viu.osd_sc_h_ini_phase, ++ priv->io_base + _REG(VPP_OSD_HSC_INI_PHASE)); ++ writel_relaxed(priv->viu.osd_sc_h_phase_step, ++ priv->io_base + _REG(VPP_OSD_HSC_PHASE_STEP)); ++ writel_relaxed(priv->viu.osd_sc_h_ctrl0, ++ priv->io_base + _REG(VPP_OSD_HSC_CTRL0)); ++ writel_relaxed(priv->viu.osd_sc_v_ctrl0, ++ priv->io_base + _REG(VPP_OSD_VSC_CTRL0)); + + if (priv->canvas) + meson_canvas_config(priv->canvas, priv->canvas_id_osd1, +diff --git a/drivers/gpu/drm/meson/meson_drv.h b/drivers/gpu/drm/meson/meson_drv.h +index c971557d4a48..a955354711ce 100644 +--- a/drivers/gpu/drm/meson/meson_drv.h ++++ b/drivers/gpu/drm/meson/meson_drv.h +@@ -54,6 +54,16 @@ struct meson_drm { + uint32_t osd1_addr; + uint32_t osd1_stride; + uint32_t osd1_height; ++ uint32_t osd_sc_ctrl0; ++ uint32_t osd_sc_i_wh_m1; ++ uint32_t osd_sc_o_h_start_end; ++ uint32_t osd_sc_o_v_start_end; ++ uint32_t osd_sc_v_ini_phase; ++ uint32_t osd_sc_v_phase_step; ++ uint32_t osd_sc_h_ini_phase; ++ uint32_t osd_sc_h_phase_step; ++ uint32_t osd_sc_h_ctrl0; ++ uint32_t osd_sc_v_ctrl0; + + bool vd1_enabled; + bool vd1_commit; +diff --git a/drivers/gpu/drm/meson/meson_plane.c b/drivers/gpu/drm/meson/meson_plane.c +index 51bec8e98a39..f915a79ae81c 100644 +--- a/drivers/gpu/drm/meson/meson_plane.c ++++ b/drivers/gpu/drm/meson/meson_plane.c +@@ -143,13 +143,50 @@ static void meson_plane_atomic_update(struct drm_plane *plane, + break; + }; + ++ /* ++ * When the output is interlaced, the OSD must switch between ++ * each field using the INTERLACE_SEL_ODD (0) of VIU_OSD1_BLK0_CFG_W0 ++ * at each vsync. ++ * But the vertical scaler can provide such funtionnality if ++ * is configured for 2:1 scaling with interlace options enabled. ++ */ + if (state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE) { + priv->viu.osd1_interlace = true; + + dest.y1 /= 2; + dest.y2 /= 2; +- } else ++ ++ priv->viu.osd_sc_ctrl0 = BIT(3)| /* Enable scaler */ ++ BIT(2); /* Select OSD1 */ ++ ++ /* 2:1 scaling */ ++ priv->viu.osd_sc_i_wh_m1 = ((drm_rect_width(&dest) - 1) << 16) | ++ (drm_rect_height(&dest) - 1); ++ priv->viu.osd_sc_o_h_start_end = (dest.x1 << 16) | dest.x2; ++ priv->viu.osd_sc_o_v_start_end = (dest.y1 << 16) | dest.y2; ++ ++ /* 2:1 vertical scaling values */ ++ priv->viu.osd_sc_v_ini_phase = BIT(16); ++ priv->viu.osd_sc_v_phase_step = BIT(25); ++ priv->viu.osd_sc_v_ctrl0 = ++ (4 << 0) | /* osd_vsc_bank_length */ ++ (4 << 3) | /* osd_vsc_top_ini_rcv_num0 */ ++ (1 << 8) | /* osd_vsc_top_rpt_p0_num0 */ ++ (6 << 11) | /* osd_vsc_bot_ini_rcv_num0 */ ++ (2 << 16) | /* osd_vsc_bot_rpt_p0_num0 */ ++ BIT(23) | /* osd_prog_interlace */ ++ BIT(24); /* Enable vertical scaler */ ++ ++ /* No horizontal scaling */ ++ priv->viu.osd_sc_h_ini_phase = 0; ++ priv->viu.osd_sc_h_phase_step = 0; ++ priv->viu.osd_sc_h_ctrl0 = 0; ++ } else { + priv->viu.osd1_interlace = false; ++ priv->viu.osd_sc_ctrl0 = 0; ++ priv->viu.osd_sc_h_ctrl0 = 0; ++ priv->viu.osd_sc_v_ctrl0 = 0; ++ } + + /* + * The format of these registers is (x2 << 16 | x1), +diff --git a/drivers/gpu/drm/meson/meson_vpp.c b/drivers/gpu/drm/meson/meson_vpp.c +index 5dc24a99e978..f9efb431e953 100644 +--- a/drivers/gpu/drm/meson/meson_vpp.c ++++ b/drivers/gpu/drm/meson/meson_vpp.c +@@ -51,52 +51,6 @@ void meson_vpp_setup_mux(struct meson_drm *priv, unsigned int mux) + writel(mux, priv->io_base + _REG(VPU_VIU_VENC_MUX_CTRL)); + } + +-/* +- * When the output is interlaced, the OSD must switch between +- * each field using the INTERLACE_SEL_ODD (0) of VIU_OSD1_BLK0_CFG_W0 +- * at each vsync. +- * But the vertical scaler can provide such funtionnality if +- * is configured for 2:1 scaling with interlace options enabled. +- */ +-void meson_vpp_setup_interlace_vscaler_osd1(struct meson_drm *priv, +- struct drm_rect *input) +-{ +- writel_relaxed(BIT(3) /* Enable scaler */ | +- BIT(2), /* Select OSD1 */ +- priv->io_base + _REG(VPP_OSD_SC_CTRL0)); +- +- writel_relaxed(((drm_rect_width(input) - 1) << 16) | +- (drm_rect_height(input) - 1), +- priv->io_base + _REG(VPP_OSD_SCI_WH_M1)); +- /* 2:1 scaling */ +- writel_relaxed(((input->x1) << 16) | (input->x2), +- priv->io_base + _REG(VPP_OSD_SCO_H_START_END)); +- writel_relaxed(((input->y1 >> 1) << 16) | (input->y2 >> 1), +- priv->io_base + _REG(VPP_OSD_SCO_V_START_END)); +- +- /* 2:1 scaling values */ +- writel_relaxed(BIT(16), priv->io_base + _REG(VPP_OSD_VSC_INI_PHASE)); +- writel_relaxed(BIT(25), priv->io_base + _REG(VPP_OSD_VSC_PHASE_STEP)); +- +- writel_relaxed(0, priv->io_base + _REG(VPP_OSD_HSC_CTRL0)); +- +- writel_relaxed((4 << 0) /* osd_vsc_bank_length */ | +- (4 << 3) /* osd_vsc_top_ini_rcv_num0 */ | +- (1 << 8) /* osd_vsc_top_rpt_p0_num0 */ | +- (6 << 11) /* osd_vsc_bot_ini_rcv_num0 */ | +- (2 << 16) /* osd_vsc_bot_rpt_p0_num0 */ | +- BIT(23) /* osd_prog_interlace */ | +- BIT(24), /* Enable vertical scaler */ +- priv->io_base + _REG(VPP_OSD_VSC_CTRL0)); +-} +- +-void meson_vpp_disable_interlace_vscaler_osd1(struct meson_drm *priv) +-{ +- writel_relaxed(0, priv->io_base + _REG(VPP_OSD_SC_CTRL0)); +- writel_relaxed(0, priv->io_base + _REG(VPP_OSD_VSC_CTRL0)); +- writel_relaxed(0, priv->io_base + _REG(VPP_OSD_HSC_CTRL0)); +-} +- + static unsigned int vpp_filter_coefs_4point_bspline[] = { + 0x15561500, 0x14561600, 0x13561700, 0x12561800, + 0x11551a00, 0x11541b00, 0x10541c00, 0x0f541d00, +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0022-drm-meson-Add-primary-plane-scaling.patch b/buildroot-external/board/hardkernel/patches/linux/0022-drm-meson-Add-primary-plane-scaling.patch new file mode 100644 index 000000000..abbd87949 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0022-drm-meson-Add-primary-plane-scaling.patch @@ -0,0 +1,287 @@ +From 166c3c3d19c030becc0c403cb638560d3165ff14 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Tue, 30 Oct 2018 14:29:10 +0100 +Subject: [PATCH 22/53] drm/meson: Add primary plane scaling + +This patch adds support for the Primary Plane scaling. + +On the Amlogic GX SoCs, the primary plane is used as On-Screen-Display +layer on top of video, and it's needed to keep the OSD layer to a lower +size as the physical display size to : +- lower the memory bandwidth +- lower the OSD rendering +- lower the memory usage + +This use-case is used when setting the display mode to 3840x2160 and the +OSD layer is rendered using the GPU. In this case, the GXBB & GXL cannot +work on more than 2000x2000 buffer, thus needing the OSD layer to be kept +at 1920x1080 and upscaled to 3840x2160 in hardware. + +The primary plane atomic check still allow 1:1 scaling, allowing native +3840x2160 if needed by user-space applications. +--- + drivers/gpu/drm/meson/meson_plane.c | 186 +++++++++++++++++++++------- + 1 file changed, 141 insertions(+), 45 deletions(-) + +diff --git a/drivers/gpu/drm/meson/meson_plane.c b/drivers/gpu/drm/meson/meson_plane.c +index f915a79ae81c..12a47b4f65a5 100644 +--- a/drivers/gpu/drm/meson/meson_plane.c ++++ b/drivers/gpu/drm/meson/meson_plane.c +@@ -24,6 +24,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -39,12 +40,50 @@ + #include "meson_canvas.h" + #include "meson_registers.h" + ++/* OSD_SCI_WH_M1 */ ++#define SCI_WH_M1_W(w) FIELD_PREP(GENMASK(28, 16), w) ++#define SCI_WH_M1_H(h) FIELD_PREP(GENMASK(12, 0), h) ++ ++/* OSD_SCO_H_START_END */ ++/* OSD_SCO_V_START_END */ ++#define SCO_HV_START(start) FIELD_PREP(GENMASK(27, 16), start) ++#define SCO_HV_END(end) FIELD_PREP(GENMASK(11, 0), end) ++ ++/* OSD_SC_CTRL0 */ ++#define SC_CTRL0_PATH_EN BIT(3) ++#define SC_CTRL0_SEL_OSD1 BIT(2) ++ ++/* OSD_VSC_CTRL0 */ ++#define VSC_BANK_LEN(value) FIELD_PREP(GENMASK(2, 0), value) ++#define VSC_TOP_INI_RCV_NUM(value) FIELD_PREP(GENMASK(6, 3), value) ++#define VSC_TOP_RPT_L0_NUM(value) FIELD_PREP(GENMASK(9, 8), value) ++#define VSC_BOT_INI_RCV_NUM(value) FIELD_PREP(GENMASK(14, 11), value) ++#define VSC_BOT_RPT_L0_NUM(value) FIELD_PREP(GENMASK(17, 16), value) ++#define VSC_PROG_INTERLACE BIT(23) ++#define VSC_VERTICAL_SCALER_EN BIT(24) ++ ++/* OSD_VSC_INI_PHASE */ ++#define VSC_INI_PHASE_BOT(bottom) FIELD_PREP(GENMASK(31, 16), bottom) ++#define VSC_INI_PHASE_TOP(top) FIELD_PREP(GENMASK(15, 0), top) ++ ++/* OSD_HSC_CTRL0 */ ++#define HSC_BANK_LENGTH(value) FIELD_PREP(GENMASK(2, 0), value) ++#define HSC_INI_RCV_NUM0(value) FIELD_PREP(GENMASK(6, 3), value) ++#define HSC_RPT_P0_NUM0(value) FIELD_PREP(GENMASK(9, 8), value) ++#define HSC_HORIZ_SCALER_EN BIT(22) ++ ++/* VPP_OSD_VSC_PHASE_STEP */ ++/* VPP_OSD_HSC_PHASE_STEP */ ++#define SC_PHASE_STEP(value) FIELD_PREP(GENMASK(27, 0), value) ++ + struct meson_plane { + struct drm_plane base; + struct meson_drm *priv; + }; + #define to_meson_plane(x) container_of(x, struct meson_plane, base) + ++#define FRAC_16_16(mult, div) (((mult) << 16) / (div)) ++ + static int meson_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) + { +@@ -57,10 +96,15 @@ static int meson_plane_atomic_check(struct drm_plane *plane, + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + ++ /* ++ * Only allow : ++ * - Upscaling up to 5x, vertical and horizontal ++ * - Final coordinates must match crtc size ++ */ + return drm_atomic_helper_check_plane_state(state, crtc_state, ++ FRAC_16_16(1, 5), + DRM_PLANE_HELPER_NO_SCALING, +- DRM_PLANE_HELPER_NO_SCALING, +- true, true); ++ false, true); + } + + /* Takes a fixed 16.16 number and converts it to integer. */ +@@ -74,22 +118,19 @@ static void meson_plane_atomic_update(struct drm_plane *plane, + { + struct meson_plane *meson_plane = to_meson_plane(plane); + struct drm_plane_state *state = plane->state; +- struct drm_framebuffer *fb = state->fb; ++ struct drm_rect dest = drm_plane_state_dest(state); + struct meson_drm *priv = meson_plane->priv; ++ struct drm_framebuffer *fb = state->fb; + struct drm_gem_cma_object *gem; +- struct drm_rect src = { +- .x1 = (state->src_x), +- .y1 = (state->src_y), +- .x2 = (state->src_x + state->src_w), +- .y2 = (state->src_y + state->src_h), +- }; +- struct drm_rect dest = { +- .x1 = state->crtc_x, +- .y1 = state->crtc_y, +- .x2 = state->crtc_x + state->crtc_w, +- .y2 = state->crtc_y + state->crtc_h, +- }; + unsigned long flags; ++ int vsc_ini_rcv_num, vsc_ini_rpt_p0_num; ++ int vsc_bot_rcv_num, vsc_bot_rpt_p0_num; ++ int hsc_ini_rcv_num, hsc_ini_rpt_p0_num; ++ int hf_phase_step, vf_phase_step; ++ int src_w, src_h, dst_w, dst_h; ++ int bot_ini_phase; ++ int hf_bank_len; ++ int vf_bank_len; + u8 canvas_id_osd1; + + /* +@@ -143,6 +184,27 @@ static void meson_plane_atomic_update(struct drm_plane *plane, + break; + }; + ++ /* Default scaler parameters */ ++ vsc_bot_rcv_num = 0; ++ vsc_bot_rpt_p0_num = 0; ++ hf_bank_len = 4; ++ vf_bank_len = 4; ++ ++ if (state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE) { ++ vsc_bot_rcv_num = 6; ++ vsc_bot_rpt_p0_num = 2; ++ } ++ ++ hsc_ini_rcv_num = hf_bank_len; ++ vsc_ini_rcv_num = vf_bank_len; ++ hsc_ini_rpt_p0_num = (hf_bank_len / 2) - 1; ++ vsc_ini_rpt_p0_num = (vf_bank_len / 2) - 1; ++ ++ src_w = fixed16_to_int(state->src_w); ++ src_h = fixed16_to_int(state->src_h); ++ dst_w = state->crtc_w; ++ dst_h = state->crtc_h; ++ + /* + * When the output is interlaced, the OSD must switch between + * each field using the INTERLACE_SEL_ODD (0) of VIU_OSD1_BLK0_CFG_W0 +@@ -151,41 +213,73 @@ static void meson_plane_atomic_update(struct drm_plane *plane, + * is configured for 2:1 scaling with interlace options enabled. + */ + if (state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE) { +- priv->viu.osd1_interlace = true; +- + dest.y1 /= 2; + dest.y2 /= 2; ++ dst_h /= 2; ++ } + +- priv->viu.osd_sc_ctrl0 = BIT(3)| /* Enable scaler */ +- BIT(2); /* Select OSD1 */ ++ hf_phase_step = ((src_w << 18) / dst_w) << 6; ++ vf_phase_step = (src_h << 20) / dst_h; + +- /* 2:1 scaling */ +- priv->viu.osd_sc_i_wh_m1 = ((drm_rect_width(&dest) - 1) << 16) | +- (drm_rect_height(&dest) - 1); +- priv->viu.osd_sc_o_h_start_end = (dest.x1 << 16) | dest.x2; +- priv->viu.osd_sc_o_v_start_end = (dest.y1 << 16) | dest.y2; ++ if (state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE) ++ bot_ini_phase = ((vf_phase_step / 2) >> 4); ++ else ++ bot_ini_phase = 0; ++ ++ vf_phase_step = (vf_phase_step << 4); ++ ++ /* In interlaced mode, scaler is always active */ ++ if (src_h != dst_h || src_w != dst_w) { ++ priv->viu.osd_sc_i_wh_m1 = SCI_WH_M1_W(src_w - 1) | ++ SCI_WH_M1_H(src_h - 1); ++ priv->viu.osd_sc_o_h_start_end = SCO_HV_START(dest.x1) | ++ SCO_HV_END(dest.x2 - 1); ++ priv->viu.osd_sc_o_v_start_end = SCO_HV_START(dest.y1) | ++ SCO_HV_END(dest.y2 - 1); ++ /* Enable OSD Scaler */ ++ priv->viu.osd_sc_ctrl0 = SC_CTRL0_PATH_EN | SC_CTRL0_SEL_OSD1; ++ } else { ++ priv->viu.osd_sc_i_wh_m1 = 0; ++ priv->viu.osd_sc_o_h_start_end = 0; ++ priv->viu.osd_sc_o_v_start_end = 0; ++ priv->viu.osd_sc_ctrl0 = 0; ++ } + +- /* 2:1 vertical scaling values */ +- priv->viu.osd_sc_v_ini_phase = BIT(16); +- priv->viu.osd_sc_v_phase_step = BIT(25); ++ /* In interlaced mode, vertical scaler is always active */ ++ if (src_h != dst_h) { + priv->viu.osd_sc_v_ctrl0 = +- (4 << 0) | /* osd_vsc_bank_length */ +- (4 << 3) | /* osd_vsc_top_ini_rcv_num0 */ +- (1 << 8) | /* osd_vsc_top_rpt_p0_num0 */ +- (6 << 11) | /* osd_vsc_bot_ini_rcv_num0 */ +- (2 << 16) | /* osd_vsc_bot_rpt_p0_num0 */ +- BIT(23) | /* osd_prog_interlace */ +- BIT(24); /* Enable vertical scaler */ +- +- /* No horizontal scaling */ ++ VSC_BANK_LEN(vf_bank_len) | ++ VSC_TOP_INI_RCV_NUM(vsc_ini_rcv_num) | ++ VSC_TOP_RPT_L0_NUM(vsc_ini_rpt_p0_num) | ++ VSC_VERTICAL_SCALER_EN; ++ ++ if (state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE) ++ priv->viu.osd_sc_v_ctrl0 |= ++ VSC_BOT_INI_RCV_NUM(vsc_bot_rcv_num) | ++ VSC_BOT_RPT_L0_NUM(vsc_bot_rpt_p0_num) | ++ VSC_PROG_INTERLACE; ++ ++ priv->viu.osd_sc_v_phase_step = SC_PHASE_STEP(vf_phase_step); ++ priv->viu.osd_sc_v_ini_phase = VSC_INI_PHASE_BOT(bot_ini_phase); ++ } else { ++ priv->viu.osd_sc_v_ctrl0 = 0; ++ priv->viu.osd_sc_v_phase_step = 0; ++ priv->viu.osd_sc_v_ini_phase = 0; ++ } ++ ++ /* Horizontal scaler is only used if width does not match */ ++ if (src_w != dst_w) { ++ priv->viu.osd_sc_h_ctrl0 = ++ HSC_BANK_LENGTH(hf_bank_len) | ++ HSC_INI_RCV_NUM0(hsc_ini_rcv_num) | ++ HSC_RPT_P0_NUM0(hsc_ini_rpt_p0_num) | ++ HSC_HORIZ_SCALER_EN; ++ priv->viu.osd_sc_h_phase_step = SC_PHASE_STEP(hf_phase_step); + priv->viu.osd_sc_h_ini_phase = 0; +- priv->viu.osd_sc_h_phase_step = 0; +- priv->viu.osd_sc_h_ctrl0 = 0; + } else { +- priv->viu.osd1_interlace = false; +- priv->viu.osd_sc_ctrl0 = 0; + priv->viu.osd_sc_h_ctrl0 = 0; +- priv->viu.osd_sc_v_ctrl0 = 0; ++ priv->viu.osd_sc_h_phase_step = 0; ++ priv->viu.osd_sc_h_ini_phase = 0; + } + + /* +@@ -193,10 +287,12 @@ static void meson_plane_atomic_update(struct drm_plane *plane, + * where x2 is exclusive. + * e.g. +30x1920 would be (1919 << 16) | 30 + */ +- priv->viu.osd1_blk0_cfg[1] = ((fixed16_to_int(src.x2) - 1) << 16) | +- fixed16_to_int(src.x1); +- priv->viu.osd1_blk0_cfg[2] = ((fixed16_to_int(src.y2) - 1) << 16) | +- fixed16_to_int(src.y1); ++ priv->viu.osd1_blk0_cfg[1] = ++ ((fixed16_to_int(state->src.x2) - 1) << 16) | ++ fixed16_to_int(state->src.x1); ++ priv->viu.osd1_blk0_cfg[2] = ++ ((fixed16_to_int(state->src.y2) - 1) << 16) | ++ fixed16_to_int(state->src.y1); + priv->viu.osd1_blk0_cfg[3] = ((dest.x2 - 1) << 16) | dest.x1; + priv->viu.osd1_blk0_cfg[4] = ((dest.y2 - 1) << 16) | dest.y1; + +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0023-pinctrl-meson-gxl-remove-invalid-GPIOX-tsin_a-pins.patch b/buildroot-external/board/hardkernel/patches/linux/0023-pinctrl-meson-gxl-remove-invalid-GPIOX-tsin_a-pins.patch new file mode 100644 index 000000000..6440a08d4 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0023-pinctrl-meson-gxl-remove-invalid-GPIOX-tsin_a-pins.patch @@ -0,0 +1,57 @@ +From 47a84b0f881a1e52f95b4b31cf7ca01a88b469d4 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Wed, 7 Nov 2018 11:34:47 +0100 +Subject: [PATCH 23/53] pinctrl: meson-gxl: remove invalid GPIOX tsin_a pins + +The GPIOX tsin_a pins wrongly uses the SDCard pinctrl bits, this +patch completely removes these pins entries until we find out what +are the correct bits and registers to be used instead. + +Fixes: 5a6ae9b80139 ("pinctrl: meson-gxl: add tsin_a pins") +--- + drivers/pinctrl/meson/pinctrl-meson-gxl.c | 12 ++---------- + 1 file changed, 2 insertions(+), 10 deletions(-) + +diff --git a/drivers/pinctrl/meson/pinctrl-meson-gxl.c b/drivers/pinctrl/meson/pinctrl-meson-gxl.c +index 158f618f1695..0c0a5018102b 100644 +--- a/drivers/pinctrl/meson/pinctrl-meson-gxl.c ++++ b/drivers/pinctrl/meson/pinctrl-meson-gxl.c +@@ -239,13 +239,9 @@ static const unsigned int eth_link_led_pins[] = { GPIOZ_14 }; + static const unsigned int eth_act_led_pins[] = { GPIOZ_15 }; + + static const unsigned int tsin_a_d0_pins[] = { GPIODV_0 }; +-static const unsigned int tsin_a_d0_x_pins[] = { GPIOX_10 }; + static const unsigned int tsin_a_clk_pins[] = { GPIODV_8 }; +-static const unsigned int tsin_a_clk_x_pins[] = { GPIOX_11 }; + static const unsigned int tsin_a_sop_pins[] = { GPIODV_9 }; +-static const unsigned int tsin_a_sop_x_pins[] = { GPIOX_8 }; + static const unsigned int tsin_a_d_valid_pins[] = { GPIODV_10 }; +-static const unsigned int tsin_a_d_valid_x_pins[] = { GPIOX_9 }; + static const unsigned int tsin_a_fail_pins[] = { GPIODV_11 }; + static const unsigned int tsin_a_dp_pins[] = { + GPIODV_1, GPIODV_2, GPIODV_3, GPIODV_4, GPIODV_5, GPIODV_6, GPIODV_7, +@@ -432,10 +428,6 @@ static struct meson_pmx_group meson_gxl_periphs_groups[] = { + GROUP(spi_miso, 5, 2), + GROUP(spi_ss0, 5, 1), + GROUP(spi_sclk, 5, 0), +- GROUP(tsin_a_sop_x, 6, 3), +- GROUP(tsin_a_d_valid_x, 6, 2), +- GROUP(tsin_a_d0_x, 6, 1), +- GROUP(tsin_a_clk_x, 6, 0), + + /* Bank Z */ + GROUP(eth_mdio, 4, 23), +@@ -698,8 +690,8 @@ static const char * const eth_led_groups[] = { + }; + + static const char * const tsin_a_groups[] = { +- "tsin_a_clk", "tsin_a_clk_x", "tsin_a_sop", "tsin_a_sop_x", +- "tsin_a_d_valid", "tsin_a_d_valid_x", "tsin_a_d0", "tsin_a_d0_x", ++ "tsin_a_clk", "tsin_a_sop", ++ "tsin_a_d_valid", "tsin_a_d0", + "tsin_a_dp", "tsin_a_fail", + }; + +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0024-arm64-dts-meson-gx-Add-hdmi_5v-regulator-as-hdmi-tx-.patch b/buildroot-external/board/hardkernel/patches/linux/0024-arm64-dts-meson-gx-Add-hdmi_5v-regulator-as-hdmi-tx-.patch new file mode 100644 index 000000000..8cacd82b9 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0024-arm64-dts-meson-gx-Add-hdmi_5v-regulator-as-hdmi-tx-.patch @@ -0,0 +1,82 @@ +From 0e580116d8afa1dcab823eb31ca415c4714bf48a Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Thu, 8 Nov 2018 14:24:38 +0100 +Subject: [PATCH 24/53] arm64: dts: meson-gx: Add hdmi_5v regulator as hdmi tx + supply + +The hdmi_5v regulator must be enabled to provide power to the physical HDMI +PHY and enables the HDMI 5V presence loopback for the monitor. + +Fixes: b409f625a6d5 ("ARM64: dts: meson-gx: Add HDMI_5V regulator on selected boards") +Signed-off-by: Neil Armstrong +--- + arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi | 1 + + arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts | 1 + + arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts | 1 + + arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts | 1 + + arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts | 1 + + 5 files changed, 5 insertions(+) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi +index fb9ad6faa745..774f8afd2c65 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi +@@ -166,6 +166,7 @@ + status = "okay"; + pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; + pinctrl-names = "default"; ++ hdmi-supply = <&hdmi_5v>; + }; + + &hdmi_tx_tmds_port { +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts +index f053595ebdc4..e5ef9b0b91c1 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts +@@ -119,6 +119,7 @@ + status = "okay"; + pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; + pinctrl-names = "default"; ++ hdmi-supply = <&hdmi_5v>; + }; + + &hdmi_tx_tmds_port { +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts +index f56969efffba..ca0228e0d585 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts +@@ -200,6 +200,7 @@ + status = "okay"; + pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; + pinctrl-names = "default"; ++ hdmi-supply = <&hdmi_5v>; + }; + + &hdmi_tx_tmds_port { +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts +index f8c66a7972b3..29c9837bd7ea 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts +@@ -96,6 +96,7 @@ + status = "okay"; + pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; + pinctrl-names = "default"; ++ hdmi-supply = <&hdmi_5v>; + }; + + &hdmi_tx_tmds_port { +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts b/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts +index 4fbfa5a850cc..fe8e726a4210 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxm-khadas-vim2.dts +@@ -312,6 +312,7 @@ + status = "okay"; + pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; + pinctrl-names = "default"; ++ hdmi-supply = <&hdmi_5v>; + }; + + &hdmi_tx_tmds_port { +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0025-arm64-dts-meson-gxl-libretech-cc-fix-GPIO-lines-name.patch b/buildroot-external/board/hardkernel/patches/linux/0025-arm64-dts-meson-gxl-libretech-cc-fix-GPIO-lines-name.patch new file mode 100644 index 000000000..83123dea1 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0025-arm64-dts-meson-gxl-libretech-cc-fix-GPIO-lines-name.patch @@ -0,0 +1,41 @@ +From 15611dee35c448dd0409a1e06a4f87f0dbf59876 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Wed, 7 Nov 2018 11:45:47 +0100 +Subject: [PATCH 25/53] arm64: dts: meson-gxl-libretech-cc: fix GPIO lines + names + +The gpio line names were set in the pinctrl node instead of the gpio node, +at the time it was merged, it worked, but was obviously wrong. +This patch moves the properties to the gpio nodes. + +Fixes: 47884c5c746e ("ARM64: dts: meson-gxl-libretech-cc: Add GPIO lines names") +Signed-off-by: Neil Armstrong +--- + arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts +index ca0228e0d585..bb2a8c750589 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dts +@@ -209,7 +209,7 @@ + }; + }; + +-&pinctrl_aobus { ++&gpio_ao { + gpio-line-names = "UART TX", + "UART RX", + "Blue LED", +@@ -224,7 +224,7 @@ + "7J1 Header Pin15"; + }; + +-&pinctrl_periphs { ++&gpio { + gpio-line-names = /* Bank GPIOZ */ + "", "", "", "", "", "", "", + "", "", "", "", "", "", "", +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0026-arm64-dts-meson-gxbb-nanopi-k2-fix-GPIO-lines-names.patch b/buildroot-external/board/hardkernel/patches/linux/0026-arm64-dts-meson-gxbb-nanopi-k2-fix-GPIO-lines-names.patch new file mode 100644 index 000000000..6bcde2948 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0026-arm64-dts-meson-gxbb-nanopi-k2-fix-GPIO-lines-names.patch @@ -0,0 +1,40 @@ +From 706947bf855b187bbd8d8b5786b38e84e571ca9b Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Wed, 7 Nov 2018 11:45:48 +0100 +Subject: [PATCH 26/53] arm64: dts: meson-gxbb-nanopi-k2: fix GPIO lines names + +The gpio line names were set in the pinctrl node instead of the gpio node, +at the time it was merged, it worked, but was obviously wrong. +This patch moves the properties to the gpio nodes. + +Fixes: 12ada0513d7a ("ARM64: dts: meson-gxbb-nanopi-k2: Add GPIO lines names") +Signed-off-by: Neil Armstrong +--- + arch/arm64/boot/dts/amlogic/meson-gxbb-nanopi-k2.dts | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-nanopi-k2.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-nanopi-k2.dts +index 5b10de9a0bad..8ea5ed5a1c62 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-nanopi-k2.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-nanopi-k2.dts +@@ -236,7 +236,7 @@ + pinctrl-names = "default"; + }; + +-&pinctrl_aobus { ++&gpio_ao { + gpio-line-names = "UART TX", "UART RX", "Power Control", "Power Key In", + "VCCK En", "CON1 Header Pin31", + "I2S Header Pin6", "IR In", "I2S Header Pin7", +@@ -246,7 +246,7 @@ + ""; + }; + +-&pinctrl_periphs { ++&gpio { + gpio-line-names = /* Bank GPIOZ */ + "Eth MDIO", "Eth MDC", "Eth RGMII RX Clk", + "Eth RX DV", "Eth RX D0", "Eth RX D1", "Eth RX D2", +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0027-arm64-dts-meson-gxbb-odroidc2-fix-GPIO-lines-names.patch b/buildroot-external/board/hardkernel/patches/linux/0027-arm64-dts-meson-gxbb-odroidc2-fix-GPIO-lines-names.patch new file mode 100644 index 000000000..4da4c22f9 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0027-arm64-dts-meson-gxbb-odroidc2-fix-GPIO-lines-names.patch @@ -0,0 +1,40 @@ +From 62b3002dc67232b0a8cc5f51a5df991a48f062c7 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Wed, 7 Nov 2018 11:45:49 +0100 +Subject: [PATCH 27/53] arm64: dts: meson-gxbb-odroidc2: fix GPIO lines names + +The gpio line names were set in the pinctrl node instead of the gpio node, +at the time it was merged, it worked, but was obviously wrong. +This patch moves the properties to the gpio nodes. + +Fixes: b03c7d6438bb ("ARM64: dts: meson-gxbb-odroidc2: Add GPIO lines names") +Signed-off-by: Neil Armstrong +--- + arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts +index 3da33090b8fe..73cc80143c04 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts +@@ -232,7 +232,7 @@ + pinctrl-names = "default"; + }; + +-&pinctrl_aobus { ++&gpio_ao { + gpio-line-names = "UART TX", "UART RX", "VCCK En", "TF 3V3/1V8 En", + "USB HUB nRESET", "USB OTG Power En", + "J7 Header Pin2", "IR In", "J7 Header Pin4", +@@ -242,7 +242,7 @@ + ""; + }; + +-&pinctrl_periphs { ++&gpio { + gpio-line-names = /* Bank GPIOZ */ + "Eth MDIO", "Eth MDC", "Eth RGMII RX Clk", + "Eth RX DV", "Eth RX D0", "Eth RX D1", "Eth RX D2", +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0028-arm64-dts-meson-gxl-khadas-vim-fix-GPIO-lines-names.patch b/buildroot-external/board/hardkernel/patches/linux/0028-arm64-dts-meson-gxl-khadas-vim-fix-GPIO-lines-names.patch new file mode 100644 index 000000000..e6ad08950 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0028-arm64-dts-meson-gxl-khadas-vim-fix-GPIO-lines-names.patch @@ -0,0 +1,40 @@ +From db194607c28989a307f60f2fd89a4996b6ae4f02 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Wed, 7 Nov 2018 11:45:50 +0100 +Subject: [PATCH 28/53] arm64: dts: meson-gxl-khadas-vim: fix GPIO lines names + +The gpio line names were set in the pinctrl node instead of the gpio node, +at the time it was merged, it worked, but was obviously wrong. +This patch moves the properties to the gpio nodes. + +Fixes: 60795933b709 ("ARM64: dts: meson-gxl-khadas-vim: Add GPIO lines names") +Signed-off-by: Neil Armstrong +--- + arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts +index e5ef9b0b91c1..1a4b3f3b8ace 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts ++++ b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-khadas-vim.dts +@@ -158,7 +158,7 @@ + linux,rc-map-name = "rc-geekbox"; + }; + +-&pinctrl_aobus { ++&gpio_ao { + gpio-line-names = "UART TX", + "UART RX", + "Power Key In", +@@ -173,7 +173,7 @@ + ""; + }; + +-&pinctrl_periphs { ++&gpio { + gpio-line-names = /* Bank GPIOZ */ + "", "", "", "", "", "", "", + "", "", "", "", "", "", "", +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0029-drm-meson-Add-support-for-VIC-alternate-timings.patch b/buildroot-external/board/hardkernel/patches/linux/0029-drm-meson-Add-support-for-VIC-alternate-timings.patch new file mode 100644 index 000000000..1ee57824d --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0029-drm-meson-Add-support-for-VIC-alternate-timings.patch @@ -0,0 +1,330 @@ +From b63cbf7b44a26e219c55da750662e1d0ae9f565b Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Tue, 6 Nov 2018 11:54:35 +0100 +Subject: [PATCH 29/53] drm/meson: Add support for VIC alternate timings + +This change is an attempt to handle the alternate clock for the CEA mode. +60Hz vs. 59.94Hz, 30Hz vs 29.97Hz or 24Hz vs 23.97Hz on the Amlogic Meson SoC +DRM Driver pixel clock generation. + +The actual clock generation will be moved to the Common Clock framework once +all the video clock are handled by the Amlogic Meson SoC clock driver, +then these alternate timings will be handled in the same time in a cleaner +fashion. + +Signed-off-by: Neil Armstrong +--- + drivers/gpu/drm/meson/meson_dw_hdmi.c | 12 +-- + drivers/gpu/drm/meson/meson_vclk.c | 127 +++++++++++++++++--------- + drivers/gpu/drm/meson/meson_vclk.h | 2 + + 3 files changed, 89 insertions(+), 52 deletions(-) + +diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c +index 2cb2ad26d716..807111ebfdd9 100644 +--- a/drivers/gpu/drm/meson/meson_dw_hdmi.c ++++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c +@@ -594,17 +594,7 @@ dw_hdmi_mode_valid(struct drm_connector *connector, + dev_dbg(connector->dev->dev, "%s: vclk:%d venc=%d hdmi=%d\n", __func__, + vclk_freq, venc_freq, hdmi_freq); + +- /* Finally filter by configurable vclk frequencies for VIC modes */ +- switch (vclk_freq) { +- case 54000: +- case 74250: +- case 148500: +- case 297000: +- case 594000: +- return MODE_OK; +- } +- +- return MODE_CLOCK_RANGE; ++ return meson_vclk_vic_supported_freq(vclk_freq); + } + + /* Encoder */ +diff --git a/drivers/gpu/drm/meson/meson_vclk.c b/drivers/gpu/drm/meson/meson_vclk.c +index ae5473257f72..5acccebd026d 100644 +--- a/drivers/gpu/drm/meson/meson_vclk.c ++++ b/drivers/gpu/drm/meson/meson_vclk.c +@@ -117,6 +117,8 @@ + #define HDMI_PLL_RESET BIT(28) + #define HDMI_PLL_LOCK BIT(31) + ++#define FREQ_1000_1001(_freq) DIV_ROUND_CLOSEST(_freq * 1000, 1001) ++ + /* VID PLL Dividers */ + enum { + VID_PLL_DIV_1 = 0, +@@ -323,7 +325,7 @@ static void meson_venci_cvbs_clock_config(struct meson_drm *priv) + enum { + /* PLL O1 O2 O3 VP DV EN TX */ + /* 4320 /4 /4 /1 /5 /1 => /2 /2 */ +- MESON_VCLK_HDMI_ENCI_54000 = 1, ++ MESON_VCLK_HDMI_ENCI_54000 = 0, + /* 4320 /4 /4 /1 /5 /1 => /1 /2 */ + MESON_VCLK_HDMI_DDR_54000, + /* 2970 /4 /1 /1 /5 /1 => /1 /2 */ +@@ -339,6 +341,7 @@ enum { + }; + + struct meson_vclk_params { ++ unsigned int pixel_freq; + unsigned int pll_base_freq; + unsigned int pll_od1; + unsigned int pll_od2; +@@ -347,6 +350,7 @@ struct meson_vclk_params { + unsigned int vclk_div; + } params[] = { + [MESON_VCLK_HDMI_ENCI_54000] = { ++ .pixel_freq = 54000, + .pll_base_freq = 4320000, + .pll_od1 = 4, + .pll_od2 = 4, +@@ -355,6 +359,7 @@ struct meson_vclk_params { + .vclk_div = 1, + }, + [MESON_VCLK_HDMI_DDR_54000] = { ++ .pixel_freq = 54000, + .pll_base_freq = 4320000, + .pll_od1 = 4, + .pll_od2 = 4, +@@ -363,6 +368,7 @@ struct meson_vclk_params { + .vclk_div = 1, + }, + [MESON_VCLK_HDMI_DDR_148500] = { ++ .pixel_freq = 148500, + .pll_base_freq = 2970000, + .pll_od1 = 4, + .pll_od2 = 1, +@@ -371,6 +377,7 @@ struct meson_vclk_params { + .vclk_div = 1, + }, + [MESON_VCLK_HDMI_74250] = { ++ .pixel_freq = 74250, + .pll_base_freq = 2970000, + .pll_od1 = 2, + .pll_od2 = 2, +@@ -379,6 +386,7 @@ struct meson_vclk_params { + .vclk_div = 1, + }, + [MESON_VCLK_HDMI_148500] = { ++ .pixel_freq = 148500, + .pll_base_freq = 2970000, + .pll_od1 = 1, + .pll_od2 = 2, +@@ -387,6 +395,7 @@ struct meson_vclk_params { + .vclk_div = 1, + }, + [MESON_VCLK_HDMI_297000] = { ++ .pixel_freq = 297000, + .pll_base_freq = 2970000, + .pll_od1 = 1, + .pll_od2 = 1, +@@ -395,6 +404,7 @@ struct meson_vclk_params { + .vclk_div = 2, + }, + [MESON_VCLK_HDMI_594000] = { ++ .pixel_freq = 594000, + .pll_base_freq = 5940000, + .pll_od1 = 1, + .pll_od2 = 1, +@@ -402,6 +412,7 @@ struct meson_vclk_params { + .vid_pll_div = VID_PLL_DIV_5, + .vclk_div = 1, + }, ++ { /* sentinel */ }, + }; + + static inline unsigned int pll_od_to_reg(unsigned int od) +@@ -626,12 +637,37 @@ static void meson_hdmi_pll_generic_set(struct meson_drm *priv, + pll_freq); + } + ++enum drm_mode_status ++meson_vclk_vic_supported_freq(unsigned int freq) ++{ ++ int i; ++ ++ DRM_DEBUG_DRIVER("freq = %d\n", freq); ++ ++ for (i = 0 ; params[i].pixel_freq ; ++i) { ++ DRM_DEBUG_DRIVER("i = %d pixel_freq = %d alt = %d\n", ++ i, params[i].pixel_freq, ++ FREQ_1000_1001(params[i].pixel_freq)); ++ /* Match strict frequency */ ++ if (freq == params[i].pixel_freq) ++ return MODE_OK; ++ /* Match 1000/1001 variant */ ++ if (freq == FREQ_1000_1001(params[i].pixel_freq)) ++ return MODE_OK; ++ } ++ ++ return MODE_CLOCK_RANGE; ++} ++EXPORT_SYMBOL_GPL(meson_vclk_vic_supported_freq); ++ + static void meson_vclk_set(struct meson_drm *priv, unsigned int pll_base_freq, + unsigned int od1, unsigned int od2, unsigned int od3, + unsigned int vid_pll_div, unsigned int vclk_div, + unsigned int hdmi_tx_div, unsigned int venc_div, +- bool hdmi_use_enci) ++ bool hdmi_use_enci, bool vic_alternate_clock) + { ++ unsigned int m, frac; ++ + /* Set HDMI-TX sys clock */ + regmap_update_bits(priv->hhi, HHI_HDMI_CLK_CNTL, + CTS_HDMI_SYS_SEL_MASK, 0); +@@ -646,34 +682,38 @@ static void meson_vclk_set(struct meson_drm *priv, unsigned int pll_base_freq, + } else if (meson_vpu_is_compatible(priv, "amlogic,meson-gxbb-vpu")) { + switch (pll_base_freq) { + case 2970000: +- meson_hdmi_pll_set_params(priv, 0x3d, 0xe00, +- od1, od2, od3); ++ m = 0x3d; ++ frac = vic_alternate_clock ? 0xd02 : 0xe00; + break; + case 4320000: +- meson_hdmi_pll_set_params(priv, 0x5a, 0, +- od1, od2, od3); ++ m = vic_alternate_clock ? 0x59 : 0x5a; ++ frac = vic_alternate_clock ? 0xe8f : 0; + break; + case 5940000: +- meson_hdmi_pll_set_params(priv, 0x7b, 0xc00, +- od1, od2, od3); ++ m = 0x7b; ++ frac = vic_alternate_clock ? 0xa05 : 0xc00; + break; + } ++ ++ meson_hdmi_pll_set_params(priv, m, frac, od1, od2, od3); + } else if (meson_vpu_is_compatible(priv, "amlogic,meson-gxm-vpu") || + meson_vpu_is_compatible(priv, "amlogic,meson-gxl-vpu")) { + switch (pll_base_freq) { + case 2970000: +- meson_hdmi_pll_set_params(priv, 0x7b, 0x300, +- od1, od2, od3); ++ m = 0x7b; ++ frac = vic_alternate_clock ? 0x281 : 0x300; + break; + case 4320000: +- meson_hdmi_pll_set_params(priv, 0xb4, 0, +- od1, od2, od3); ++ m = vic_alternate_clock ? 0xb3 : 0xb4; ++ frac = vic_alternate_clock ? 0x347 : 0; + break; + case 5940000: +- meson_hdmi_pll_set_params(priv, 0xf7, 0x200, +- od1, od2, od3); ++ m = 0xf7; ++ frac = vic_alternate_clock ? 0x102 : 0x200; + break; + } ++ ++ meson_hdmi_pll_set_params(priv, m, frac, od1, od2, od3); + } + + /* Setup vid_pll divider */ +@@ -826,6 +866,7 @@ void meson_vclk_setup(struct meson_drm *priv, unsigned int target, + unsigned int vclk_freq, unsigned int venc_freq, + unsigned int dac_freq, bool hdmi_use_enci) + { ++ bool vic_alternate_clock = false; + unsigned int freq; + unsigned int hdmi_tx_div; + unsigned int venc_div; +@@ -843,7 +884,7 @@ void meson_vclk_setup(struct meson_drm *priv, unsigned int target, + * - encp encoder + */ + meson_vclk_set(priv, vclk_freq * 10, 0, 0, 0, +- VID_PLL_DIV_5, 2, 1, 1, false); ++ VID_PLL_DIV_5, 2, 1, 1, false, false); + return; + } + +@@ -863,31 +904,35 @@ void meson_vclk_setup(struct meson_drm *priv, unsigned int target, + return; + } + +- switch (vclk_freq) { +- case 54000: +- if (hdmi_use_enci) +- freq = MESON_VCLK_HDMI_ENCI_54000; +- else +- freq = MESON_VCLK_HDMI_DDR_54000; +- break; +- case 74250: +- freq = MESON_VCLK_HDMI_74250; +- break; +- case 148500: +- if (dac_freq != 148500) +- freq = MESON_VCLK_HDMI_DDR_148500; +- else +- freq = MESON_VCLK_HDMI_148500; +- break; +- case 297000: +- freq = MESON_VCLK_HDMI_297000; +- break; +- case 594000: +- freq = MESON_VCLK_HDMI_594000; +- break; +- default: +- pr_err("Fatal Error, invalid HDMI vclk freq %d\n", +- vclk_freq); ++ for (freq = 0 ; params[freq].pixel_freq ; ++freq) { ++ if (vclk_freq == params[freq].pixel_freq || ++ vclk_freq == FREQ_1000_1001(params[freq].pixel_freq)) { ++ if (vclk_freq != params[freq].pixel_freq) ++ vic_alternate_clock = true; ++ else ++ vic_alternate_clock = false; ++ ++ if (freq == MESON_VCLK_HDMI_ENCI_54000 && ++ !hdmi_use_enci) ++ continue; ++ ++ if (freq == MESON_VCLK_HDMI_DDR_54000 && ++ hdmi_use_enci) ++ continue; ++ ++ if (freq == MESON_VCLK_HDMI_DDR_148500 && ++ dac_freq == vclk_freq) ++ continue; ++ ++ if (freq == MESON_VCLK_HDMI_148500 && ++ dac_freq != vclk_freq) ++ continue; ++ break; ++ } ++ } ++ ++ if (!params[freq].pixel_freq) { ++ pr_err("Fatal Error, invalid HDMI vclk freq %d\n", vclk_freq); + return; + } + +@@ -895,6 +940,6 @@ void meson_vclk_setup(struct meson_drm *priv, unsigned int target, + params[freq].pll_od1, params[freq].pll_od2, + params[freq].pll_od3, params[freq].vid_pll_div, + params[freq].vclk_div, hdmi_tx_div, venc_div, +- hdmi_use_enci); ++ hdmi_use_enci, vic_alternate_clock); + } + EXPORT_SYMBOL_GPL(meson_vclk_setup); +diff --git a/drivers/gpu/drm/meson/meson_vclk.h b/drivers/gpu/drm/meson/meson_vclk.h +index 869fa3a3073e..4bd8752da02a 100644 +--- a/drivers/gpu/drm/meson/meson_vclk.h ++++ b/drivers/gpu/drm/meson/meson_vclk.h +@@ -32,6 +32,8 @@ enum { + + enum drm_mode_status + meson_vclk_dmt_supported_freq(struct meson_drm *priv, unsigned int freq); ++enum drm_mode_status ++meson_vclk_vic_supported_freq(unsigned int freq); + + void meson_vclk_setup(struct meson_drm *priv, unsigned int target, + unsigned int vclk_freq, unsigned int venc_freq, +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0030-media-meson-add-v4l2-m2m-video-decoder-driver.patch b/buildroot-external/board/hardkernel/patches/linux/0030-media-meson-add-v4l2-m2m-video-decoder-driver.patch new file mode 100644 index 000000000..1299b91c3 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0030-media-meson-add-v4l2-m2m-video-decoder-driver.patch @@ -0,0 +1,2971 @@ +From 21a5d2da6ccf31ae511ea34c073f3218141b7a92 Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Wed, 29 Aug 2018 15:17:22 +0200 +Subject: [PATCH 30/53] media: meson: add v4l2 m2m video decoder driver + +Amlogic SoCs feature a powerful video decoder unit able to +decode many formats, with a performance of usually up to 4k60. + +This is a driver for this IP that is based around the v4l2 m2m framework. + +It features decoding for: +- MPEG 1 +- MPEG 2 + +Supported SoCs are: GXBB (S905), GXL (S905X/W/D), GXM (S912) + +There is also a hardware bitstream parser (ESPARSER) that is handled here. + +Signed-off-by: Maxime Jourdan +--- + drivers/media/platform/Kconfig | 10 + + drivers/media/platform/meson/Makefile | 1 + + drivers/media/platform/meson/vdec/Makefile | 8 + + .../media/platform/meson/vdec/codec_mpeg12.c | 209 ++++ + .../media/platform/meson/vdec/codec_mpeg12.h | 14 + + drivers/media/platform/meson/vdec/dos_regs.h | 98 ++ + drivers/media/platform/meson/vdec/esparser.c | 322 +++++ + drivers/media/platform/meson/vdec/esparser.h | 32 + + drivers/media/platform/meson/vdec/vdec.c | 1034 +++++++++++++++++ + drivers/media/platform/meson/vdec/vdec.h | 251 ++++ + drivers/media/platform/meson/vdec/vdec_1.c | 231 ++++ + drivers/media/platform/meson/vdec/vdec_1.h | 14 + + .../media/platform/meson/vdec/vdec_helpers.c | 412 +++++++ + .../media/platform/meson/vdec/vdec_helpers.h | 48 + + .../media/platform/meson/vdec/vdec_platform.c | 101 ++ + .../media/platform/meson/vdec/vdec_platform.h | 30 + + 16 files changed, 2815 insertions(+) + create mode 100644 drivers/media/platform/meson/vdec/Makefile + create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.c + create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.h + create mode 100644 drivers/media/platform/meson/vdec/dos_regs.h + create mode 100644 drivers/media/platform/meson/vdec/esparser.c + create mode 100644 drivers/media/platform/meson/vdec/esparser.h + create mode 100644 drivers/media/platform/meson/vdec/vdec.c + create mode 100644 drivers/media/platform/meson/vdec/vdec.h + create mode 100644 drivers/media/platform/meson/vdec/vdec_1.c + create mode 100644 drivers/media/platform/meson/vdec/vdec_1.h + create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.c + create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.h + create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.c + create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.h + +diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig +index 54fe90acb5b2..6bffb0ceff58 100644 +--- a/drivers/media/platform/Kconfig ++++ b/drivers/media/platform/Kconfig +@@ -482,6 +482,16 @@ config VIDEO_QCOM_VENUS + on various Qualcomm SoCs. + To compile this driver as a module choose m here. + ++config VIDEO_MESON_VDEC ++ tristate "Amlogic video decoder driver" ++ depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA ++ depends on ARCH_MESON || COMPILE_TEST ++ select VIDEOBUF2_DMA_CONTIG ++ select V4L2_MEM2MEM_DEV ++ select MESON_CANVAS ++ help ++ Support for the video decoder found in gxbb/gxl/gxm chips. ++ + endif # V4L_MEM2MEM_DRIVERS + + # TI VIDEO PORT Helper Modules +diff --git a/drivers/media/platform/meson/Makefile b/drivers/media/platform/meson/Makefile +index 597beb8f34d1..f7c6e1031f25 100644 +--- a/drivers/media/platform/meson/Makefile ++++ b/drivers/media/platform/meson/Makefile +@@ -1 +1,2 @@ + obj-$(CONFIG_VIDEO_MESON_AO_CEC) += ao-cec.o ++obj-$(CONFIG_VIDEO_MESON_VDEC) += vdec/ +diff --git a/drivers/media/platform/meson/vdec/Makefile b/drivers/media/platform/meson/vdec/Makefile +new file mode 100644 +index 000000000000..6bea129084b7 +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/Makefile +@@ -0,0 +1,8 @@ ++# SPDX-License-Identifier: GPL-2.0 ++# Makefile for Amlogic meson video decoder driver ++ ++meson-vdec-objs = esparser.o vdec.o vdec_helpers.o vdec_platform.o ++meson-vdec-objs += vdec_1.o ++meson-vdec-objs += codec_mpeg12.o ++ ++obj-$(CONFIG_VIDEO_MESON_VDEC) += meson-vdec.o +diff --git a/drivers/media/platform/meson/vdec/codec_mpeg12.c b/drivers/media/platform/meson/vdec/codec_mpeg12.c +new file mode 100644 +index 000000000000..1bd6fb7d531d +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/codec_mpeg12.c +@@ -0,0 +1,209 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ */ ++ ++#include ++#include ++ ++#include "vdec_helpers.h" ++#include "dos_regs.h" ++ ++#define SIZE_WORKSPACE SZ_128K ++/* Offset substracted by the firmware from the workspace paddr */ ++#define WORKSPACE_OFFSET (5 * SZ_1K) ++ ++/* map firmware registers to known MPEG1/2 functions */ ++#define MREG_SEQ_INFO AV_SCRATCH_4 ++ #define MPEG2_SEQ_DAR_MASK GENMASK(3, 0) ++ #define MPEG2_DAR_4_3 2 ++ #define MPEG2_DAR_16_9 3 ++ #define MPEG2_DAR_221_100 4 ++#define MREG_PIC_INFO AV_SCRATCH_5 ++#define MREG_PIC_WIDTH AV_SCRATCH_6 ++#define MREG_PIC_HEIGHT AV_SCRATCH_7 ++#define MREG_BUFFERIN AV_SCRATCH_8 ++#define MREG_BUFFEROUT AV_SCRATCH_9 ++#define MREG_CMD AV_SCRATCH_A ++#define MREG_CO_MV_START AV_SCRATCH_B ++#define MREG_ERROR_COUNT AV_SCRATCH_C ++#define MREG_FRAME_OFFSET AV_SCRATCH_D ++#define MREG_WAIT_BUFFER AV_SCRATCH_E ++#define MREG_FATAL_ERROR AV_SCRATCH_F ++ ++#define PICINFO_PROG 0x00008000 ++#define PICINFO_TOP_FIRST 0x00002000 ++ ++struct codec_mpeg12 { ++ /* Buffer for the MPEG1/2 Workspace */ ++ void *workspace_vaddr; ++ dma_addr_t workspace_paddr; ++}; ++ ++static const u8 eos_sequence[SZ_1K] = { 0x00, 0x00, 0x01, 0xB7 }; ++ ++static const u8 *codec_mpeg12_eos_sequence(u32 *len) ++{ ++ *len = ARRAY_SIZE(eos_sequence); ++ return eos_sequence; ++} ++ ++static int codec_mpeg12_can_recycle(struct amvdec_core *core) ++{ ++ return !amvdec_read_dos(core, MREG_BUFFERIN); ++} ++ ++static void codec_mpeg12_recycle(struct amvdec_core *core, u32 buf_idx) ++{ ++ amvdec_write_dos(core, MREG_BUFFERIN, buf_idx + 1); ++} ++ ++static int codec_mpeg12_start(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ struct codec_mpeg12 *mpeg12 = sess->priv; ++ int ret; ++ ++ mpeg12 = kzalloc(sizeof(*mpeg12), GFP_KERNEL); ++ if (!mpeg12) ++ return -ENOMEM; ++ ++ /* Allocate some memory for the MPEG1/2 decoder's state */ ++ mpeg12->workspace_vaddr = dma_alloc_coherent(core->dev, SIZE_WORKSPACE, ++ &mpeg12->workspace_paddr, ++ GFP_KERNEL); ++ if (!mpeg12->workspace_vaddr) { ++ dev_err(core->dev, "Failed to request MPEG 1/2 Workspace\n"); ++ ret = -ENOMEM; ++ goto free_mpeg12; ++ } ++ ++ ret = amvdec_set_canvases(sess, (u32[]){ AV_SCRATCH_0, 0 }, ++ (u32[]){ 8, 0 }); ++ if (ret) ++ goto free_workspace; ++ ++ amvdec_write_dos(core, POWER_CTL_VLD, BIT(4)); ++ amvdec_write_dos(core, MREG_CO_MV_START, ++ mpeg12->workspace_paddr + WORKSPACE_OFFSET); ++ ++ amvdec_write_dos(core, MPEG1_2_REG, 0); ++ amvdec_write_dos(core, PSCALE_CTRL, 0); ++ amvdec_write_dos(core, PIC_HEAD_INFO, 0x380); ++ amvdec_write_dos(core, M4_CONTROL_REG, 0); ++ amvdec_write_dos(core, MREG_BUFFERIN, 0); ++ amvdec_write_dos(core, MREG_BUFFEROUT, 0); ++ amvdec_write_dos(core, MREG_CMD, (sess->width << 16) | sess->height); ++ amvdec_write_dos(core, MREG_ERROR_COUNT, 0); ++ amvdec_write_dos(core, MREG_FATAL_ERROR, 0); ++ amvdec_write_dos(core, MREG_WAIT_BUFFER, 0); ++ ++ sess->keyframe_found = 1; ++ sess->priv = mpeg12; ++ ++ return 0; ++ ++free_workspace: ++ dma_free_coherent(core->dev, SIZE_WORKSPACE, mpeg12->workspace_vaddr, ++ mpeg12->workspace_paddr); ++free_mpeg12: ++ kfree(mpeg12); ++ ++ return ret; ++} ++ ++static int codec_mpeg12_stop(struct amvdec_session *sess) ++{ ++ struct codec_mpeg12 *mpeg12 = sess->priv; ++ struct amvdec_core *core = sess->core; ++ ++ if (mpeg12->workspace_vaddr) ++ dma_free_coherent(core->dev, SIZE_WORKSPACE, ++ mpeg12->workspace_vaddr, ++ mpeg12->workspace_paddr); ++ ++ return 0; ++} ++ ++static void codec_mpeg12_update_dar(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ u32 seq = amvdec_read_dos(core, MREG_SEQ_INFO); ++ u32 ar = seq & MPEG2_SEQ_DAR_MASK; ++ ++ switch (ar) { ++ case MPEG2_DAR_4_3: ++ amvdec_set_par_from_dar(sess, 4, 3); ++ break; ++ case MPEG2_DAR_16_9: ++ amvdec_set_par_from_dar(sess, 16, 9); ++ break; ++ case MPEG2_DAR_221_100: ++ amvdec_set_par_from_dar(sess, 221, 100); ++ break; ++ default: ++ sess->pixelaspect.numerator = 1; ++ sess->pixelaspect.denominator = 1; ++ break; ++ }; ++} ++ ++static irqreturn_t codec_mpeg12_threaded_isr(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ u32 reg; ++ u32 pic_info; ++ u32 is_progressive; ++ u32 buffer_index; ++ u32 field = V4L2_FIELD_NONE; ++ u32 offset; ++ ++ amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1); ++ reg = amvdec_read_dos(core, MREG_FATAL_ERROR); ++ if (reg == 1) { ++ dev_err(core->dev, "MPEG1/2 fatal error\n"); ++ amvdec_abort(sess); ++ return IRQ_HANDLED; ++ } ++ ++ reg = amvdec_read_dos(core, MREG_BUFFEROUT); ++ if (!reg) ++ return IRQ_HANDLED; ++ ++ /* Unclear what this means */ ++ if ((reg & GENMASK(23, 17)) == GENMASK(23, 17)) ++ goto end; ++ ++ pic_info = amvdec_read_dos(core, MREG_PIC_INFO); ++ is_progressive = pic_info & PICINFO_PROG; ++ ++ if (!is_progressive) ++ field = (pic_info & PICINFO_TOP_FIRST) ? ++ V4L2_FIELD_INTERLACED_TB : ++ V4L2_FIELD_INTERLACED_BT; ++ ++ codec_mpeg12_update_dar(sess); ++ buffer_index = ((reg & 0xf) - 1) & 7; ++ offset = amvdec_read_dos(core, MREG_FRAME_OFFSET); ++ amvdec_dst_buf_done_idx(sess, buffer_index, offset, field); ++ ++end: ++ amvdec_write_dos(core, MREG_BUFFEROUT, 0); ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t codec_mpeg12_isr(struct amvdec_session *sess) ++{ ++ return IRQ_WAKE_THREAD; ++} ++ ++struct amvdec_codec_ops codec_mpeg12_ops = { ++ .start = codec_mpeg12_start, ++ .stop = codec_mpeg12_stop, ++ .isr = codec_mpeg12_isr, ++ .threaded_isr = codec_mpeg12_threaded_isr, ++ .can_recycle = codec_mpeg12_can_recycle, ++ .recycle = codec_mpeg12_recycle, ++ .eos_sequence = codec_mpeg12_eos_sequence, ++}; +diff --git a/drivers/media/platform/meson/vdec/codec_mpeg12.h b/drivers/media/platform/meson/vdec/codec_mpeg12.h +new file mode 100644 +index 000000000000..43cab5f39ca0 +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/codec_mpeg12.h +@@ -0,0 +1,14 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ */ ++ ++#ifndef __MESON_VDEC_CODEC_MPEG12_H_ ++#define __MESON_VDEC_CODEC_MPEG12_H_ ++ ++#include "vdec.h" ++ ++extern struct amvdec_codec_ops codec_mpeg12_ops; ++ ++#endif +diff --git a/drivers/media/platform/meson/vdec/dos_regs.h b/drivers/media/platform/meson/vdec/dos_regs.h +new file mode 100644 +index 000000000000..abd810542dbb +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/dos_regs.h +@@ -0,0 +1,98 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ */ ++ ++#ifndef __MESON_VDEC_DOS_REGS_H_ ++#define __MESON_VDEC_DOS_REGS_H_ ++ ++/* DOS registers */ ++#define VDEC_ASSIST_AMR1_INT8 0x00b4 ++ ++#define ASSIST_MBOX1_CLR_REG 0x01d4 ++#define ASSIST_MBOX1_MASK 0x01d8 ++ ++#define MPSR 0x0c04 ++#define MCPU_INTR_MSK 0x0c10 ++#define CPSR 0x0c84 ++ ++#define IMEM_DMA_CTRL 0x0d00 ++#define IMEM_DMA_ADR 0x0d04 ++#define IMEM_DMA_COUNT 0x0d08 ++#define LMEM_DMA_CTRL 0x0d40 ++ ++#define MC_STATUS0 0x2424 ++#define MC_CTRL1 0x242c ++ ++#define PSCALE_RST 0x2440 ++#define PSCALE_CTRL 0x2444 ++#define PSCALE_BMEM_ADDR 0x247c ++#define PSCALE_BMEM_DAT 0x2480 ++ ++#define DBLK_CTRL 0x2544 ++#define DBLK_STATUS 0x254c ++ ++#define GCLK_EN 0x260c ++#define MDEC_PIC_DC_CTRL 0x2638 ++#define MDEC_PIC_DC_STATUS 0x263c ++#define ANC0_CANVAS_ADDR 0x2640 ++#define MDEC_PIC_DC_THRESH 0x26e0 ++ ++/* Firmware interface registers */ ++#define AV_SCRATCH_0 0x2700 ++#define AV_SCRATCH_1 0x2704 ++#define AV_SCRATCH_2 0x2708 ++#define AV_SCRATCH_3 0x270c ++#define AV_SCRATCH_4 0x2710 ++#define AV_SCRATCH_5 0x2714 ++#define AV_SCRATCH_6 0x2718 ++#define AV_SCRATCH_7 0x271c ++#define AV_SCRATCH_8 0x2720 ++#define AV_SCRATCH_9 0x2724 ++#define AV_SCRATCH_A 0x2728 ++#define AV_SCRATCH_B 0x272c ++#define AV_SCRATCH_C 0x2730 ++#define AV_SCRATCH_D 0x2734 ++#define AV_SCRATCH_E 0x2738 ++#define AV_SCRATCH_F 0x273c ++#define AV_SCRATCH_G 0x2740 ++#define AV_SCRATCH_H 0x2744 ++#define AV_SCRATCH_I 0x2748 ++#define AV_SCRATCH_J 0x274c ++#define AV_SCRATCH_K 0x2750 ++#define AV_SCRATCH_L 0x2754 ++ ++#define MPEG1_2_REG 0x3004 ++#define PIC_HEAD_INFO 0x300c ++#define POWER_CTL_VLD 0x3020 ++#define M4_CONTROL_REG 0x30a4 ++ ++/* Stream Buffer (stbuf) regs */ ++#define VLD_MEM_VIFIFO_START_PTR 0x3100 ++#define VLD_MEM_VIFIFO_CURR_PTR 0x3104 ++#define VLD_MEM_VIFIFO_END_PTR 0x3108 ++#define VLD_MEM_VIFIFO_CONTROL 0x3110 ++ #define MEM_FIFO_CNT_BIT 16 ++ #define MEM_FILL_ON_LEVEL BIT(10) ++ #define MEM_CTRL_EMPTY_EN BIT(2) ++ #define MEM_CTRL_FILL_EN BIT(1) ++#define VLD_MEM_VIFIFO_WP 0x3114 ++#define VLD_MEM_VIFIFO_RP 0x3118 ++#define VLD_MEM_VIFIFO_LEVEL 0x311c ++#define VLD_MEM_VIFIFO_BUF_CNTL 0x3120 ++ #define MEM_BUFCTRL_MANUAL BIT(1) ++#define VLD_MEM_VIFIFO_WRAP_COUNT 0x3144 ++ ++#define DCAC_DMA_CTRL 0x3848 ++ ++#define DOS_SW_RESET0 0xfc00 ++#define DOS_GCLK_EN0 0xfc04 ++#define DOS_GEN_CTRL0 0xfc08 ++#define DOS_MEM_PD_VDEC 0xfcc0 ++#define DOS_MEM_PD_HEVC 0xfccc ++#define DOS_SW_RESET3 0xfcd0 ++#define DOS_GCLK_EN3 0xfcd4 ++#define DOS_VDEC_MCRCC_STALL_CTRL 0xfd00 ++ ++#endif +diff --git a/drivers/media/platform/meson/vdec/esparser.c b/drivers/media/platform/meson/vdec/esparser.c +new file mode 100644 +index 000000000000..9498812243ca +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/esparser.c +@@ -0,0 +1,322 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ * ++ * The Elementary Stream Parser is a HW bitstream parser. ++ * It reads bitstream buffers and feeds them to the VIFIFO ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "dos_regs.h" ++#include "esparser.h" ++#include "vdec_helpers.h" ++ ++/* PARSER REGS (CBUS) */ ++#define PARSER_CONTROL 0x00 ++ #define ES_PACK_SIZE_BIT 8 ++ #define ES_WRITE BIT(5) ++ #define ES_SEARCH BIT(1) ++ #define ES_PARSER_START BIT(0) ++#define PARSER_FETCH_ADDR 0x4 ++#define PARSER_FETCH_CMD 0x8 ++#define PARSER_CONFIG 0x14 ++ #define PS_CFG_MAX_FETCH_CYCLE_BIT 0 ++ #define PS_CFG_STARTCODE_WID_24_BIT 10 ++ #define PS_CFG_MAX_ES_WR_CYCLE_BIT 12 ++ #define PS_CFG_PFIFO_EMPTY_CNT_BIT 16 ++#define PFIFO_WR_PTR 0x18 ++#define PFIFO_RD_PTR 0x1c ++#define PARSER_SEARCH_PATTERN 0x24 ++ #define ES_START_CODE_PATTERN 0x00000100 ++#define PARSER_SEARCH_MASK 0x28 ++ #define ES_START_CODE_MASK 0xffffff00 ++ #define FETCH_ENDIAN_BIT 27 ++#define PARSER_INT_ENABLE 0x2c ++ #define PARSER_INT_HOST_EN_BIT 8 ++#define PARSER_INT_STATUS 0x30 ++ #define PARSER_INTSTAT_SC_FOUND 1 ++#define PARSER_ES_CONTROL 0x5c ++#define PARSER_VIDEO_START_PTR 0x80 ++#define PARSER_VIDEO_END_PTR 0x84 ++#define PARSER_VIDEO_WP 0x88 ++#define PARSER_VIDEO_HOLE 0x90 ++ ++#define SEARCH_PATTERN_LEN 512 ++ ++static DECLARE_WAIT_QUEUE_HEAD(wq); ++static int search_done; ++ ++static irqreturn_t esparser_isr(int irq, void *dev) ++{ ++ int int_status; ++ struct amvdec_core *core = dev; ++ ++ int_status = amvdec_read_parser(core, PARSER_INT_STATUS); ++ amvdec_write_parser(core, PARSER_INT_STATUS, int_status); ++ ++ if (int_status & PARSER_INTSTAT_SC_FOUND) { ++ amvdec_write_parser(core, PFIFO_RD_PTR, 0); ++ amvdec_write_parser(core, PFIFO_WR_PTR, 0); ++ search_done = 1; ++ wake_up_interruptible(&wq); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++/* Pad the packet to at least 4KiB bytes otherwise the VDEC unit won't trigger ++ * ISRs. ++ * Also append a start code 000001ff at the end to trigger ++ * the ESPARSER interrupt. ++ */ ++static u32 esparser_pad_start_code(struct vb2_buffer *vb) ++{ ++ u32 payload_size = vb2_get_plane_payload(vb, 0); ++ u32 pad_size = 0; ++ u8 *vaddr = vb2_plane_vaddr(vb, 0) + payload_size; ++ ++ if (payload_size < ESPARSER_MIN_PACKET_SIZE) { ++ pad_size = ESPARSER_MIN_PACKET_SIZE - payload_size; ++ memset(vaddr, 0, pad_size); ++ } ++ ++ memset(vaddr + pad_size, 0, SEARCH_PATTERN_LEN); ++ vaddr[pad_size] = 0x00; ++ vaddr[pad_size + 1] = 0x00; ++ vaddr[pad_size + 2] = 0x01; ++ vaddr[pad_size + 3] = 0xff; ++ ++ return pad_size; ++} ++ ++static int ++esparser_write_data(struct amvdec_core *core, dma_addr_t addr, u32 size) ++{ ++ amvdec_write_parser(core, PFIFO_RD_PTR, 0); ++ amvdec_write_parser(core, PFIFO_WR_PTR, 0); ++ amvdec_write_parser(core, PARSER_CONTROL, ++ ES_WRITE | ++ ES_PARSER_START | ++ ES_SEARCH | ++ (size << ES_PACK_SIZE_BIT)); ++ ++ amvdec_write_parser(core, PARSER_FETCH_ADDR, addr); ++ amvdec_write_parser(core, PARSER_FETCH_CMD, ++ (7 << FETCH_ENDIAN_BIT) | ++ (size + SEARCH_PATTERN_LEN)); ++ ++ search_done = 0; ++ return wait_event_interruptible_timeout(wq, search_done, (HZ / 5)); ++} ++ ++static u32 esparser_vififo_get_free_space(struct amvdec_session *sess) ++{ ++ u32 vififo_usage; ++ struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops; ++ struct amvdec_core *core = sess->core; ++ ++ vififo_usage = vdec_ops->vififo_level(sess); ++ vififo_usage += amvdec_read_parser(core, PARSER_VIDEO_HOLE); ++ vififo_usage += (6 * SZ_1K); // 6 KiB internal fifo ++ ++ if (vififo_usage > sess->vififo_size) { ++ dev_warn(sess->core->dev, ++ "VIFIFO usage (%u) > VIFIFO size (%u)\n", ++ vififo_usage, sess->vififo_size); ++ return 0; ++ } ++ ++ return sess->vififo_size - vififo_usage; ++} ++ ++int esparser_queue_eos(struct amvdec_core *core, const u8 *data, u32 len) ++{ ++ struct device *dev = core->dev; ++ void *eos_vaddr; ++ dma_addr_t eos_paddr; ++ int ret; ++ ++ eos_vaddr = dma_alloc_coherent(dev, ++ len + SEARCH_PATTERN_LEN, ++ &eos_paddr, GFP_KERNEL); ++ if (!eos_vaddr) ++ return -ENOMEM; ++ ++ memset(eos_vaddr, 0, len + SEARCH_PATTERN_LEN); ++ memcpy(eos_vaddr, data, len); ++ ret = esparser_write_data(core, eos_paddr, len); ++ dma_free_coherent(dev, len + SEARCH_PATTERN_LEN, ++ eos_vaddr, eos_paddr); ++ ++ return ret; ++} ++ ++static u32 esparser_get_offset(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ u32 offset = amvdec_read_parser(core, PARSER_VIDEO_WP) - ++ sess->vififo_paddr; ++ ++ if (offset < sess->last_offset) ++ sess->wrap_count++; ++ ++ sess->last_offset = offset; ++ offset += (sess->wrap_count * sess->vififo_size); ++ ++ return offset; ++} ++ ++static int ++esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf) ++{ ++ int ret; ++ struct vb2_buffer *vb = &vbuf->vb2_buf; ++ struct amvdec_core *core = sess->core; ++ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; ++ u32 num_dst_bufs = 0; ++ u32 payload_size = vb2_get_plane_payload(vb, 0); ++ dma_addr_t phy = vb2_dma_contig_plane_dma_addr(vb, 0); ++ u32 offset; ++ u32 pad_size; ++ ++ if (codec_ops->num_pending_bufs) ++ num_dst_bufs = codec_ops->num_pending_bufs(sess); ++ ++ num_dst_bufs += v4l2_m2m_num_dst_bufs_ready(sess->m2m_ctx); ++ ++ if (esparser_vififo_get_free_space(sess) < payload_size || ++ atomic_read(&sess->esparser_queued_bufs) >= num_dst_bufs) ++ return -EAGAIN; ++ ++ v4l2_m2m_src_buf_remove_by_buf(sess->m2m_ctx, vbuf); ++ ++ offset = esparser_get_offset(sess); ++ ++ amvdec_add_ts_reorder(sess, vb->timestamp, offset); ++ dev_dbg(core->dev, "esparser: ts = %llu pld_size = %u offset = %08X\n", ++ vb->timestamp, payload_size, offset); ++ ++ pad_size = esparser_pad_start_code(vb); ++ ret = esparser_write_data(core, phy, payload_size + pad_size); ++ ++ if (ret <= 0) { ++ dev_warn(core->dev, "esparser: input parsing error\n"); ++ amvdec_remove_ts(sess, vb->timestamp); ++ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); ++ amvdec_write_parser(core, PARSER_FETCH_CMD, 0); ++ ++ return 0; ++ } ++ ++ /* We need to wait until we parse the first keyframe. ++ * All buffers prior to the first keyframe must be dropped. ++ */ ++ if (!sess->keyframe_found) ++ usleep_range(1000, 2000); ++ ++ if (sess->keyframe_found) ++ atomic_inc(&sess->esparser_queued_bufs); ++ else ++ amvdec_remove_ts(sess, vb->timestamp); ++ ++ vbuf->flags = 0; ++ vbuf->field = V4L2_FIELD_NONE; ++ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); ++ ++ return 0; ++} ++ ++void esparser_queue_all_src(struct work_struct *work) ++{ ++ struct v4l2_m2m_buffer *buf, *n; ++ struct amvdec_session *sess = ++ container_of(work, struct amvdec_session, esparser_queue_work); ++ ++ mutex_lock(&sess->lock); ++ v4l2_m2m_for_each_src_buf_safe(sess->m2m_ctx, buf, n) { ++ if (esparser_queue(sess, &buf->vb) < 0) ++ break; ++ } ++ mutex_unlock(&sess->lock); ++} ++ ++int esparser_power_up(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops; ++ ++ reset_control_reset(core->esparser_reset); ++ amvdec_write_parser(core, PARSER_CONFIG, ++ (10 << PS_CFG_PFIFO_EMPTY_CNT_BIT) | ++ (1 << PS_CFG_MAX_ES_WR_CYCLE_BIT) | ++ (16 << PS_CFG_MAX_FETCH_CYCLE_BIT)); ++ ++ amvdec_write_parser(core, PFIFO_RD_PTR, 0); ++ amvdec_write_parser(core, PFIFO_WR_PTR, 0); ++ ++ amvdec_write_parser(core, PARSER_SEARCH_PATTERN, ++ ES_START_CODE_PATTERN); ++ amvdec_write_parser(core, PARSER_SEARCH_MASK, ES_START_CODE_MASK); ++ ++ amvdec_write_parser(core, PARSER_CONFIG, ++ (10 << PS_CFG_PFIFO_EMPTY_CNT_BIT) | ++ (1 << PS_CFG_MAX_ES_WR_CYCLE_BIT) | ++ (16 << PS_CFG_MAX_FETCH_CYCLE_BIT) | ++ (2 << PS_CFG_STARTCODE_WID_24_BIT)); ++ ++ amvdec_write_parser(core, PARSER_CONTROL, ++ (ES_SEARCH | ES_PARSER_START)); ++ ++ amvdec_write_parser(core, PARSER_VIDEO_START_PTR, sess->vififo_paddr); ++ amvdec_write_parser(core, PARSER_VIDEO_END_PTR, ++ sess->vififo_paddr + sess->vififo_size - 8); ++ amvdec_write_parser(core, PARSER_ES_CONTROL, ++ amvdec_read_parser(core, PARSER_ES_CONTROL) & ~1); ++ ++ if (vdec_ops->conf_esparser) ++ vdec_ops->conf_esparser(sess); ++ ++ amvdec_write_parser(core, PARSER_INT_STATUS, 0xffff); ++ amvdec_write_parser(core, PARSER_INT_ENABLE, ++ BIT(PARSER_INT_HOST_EN_BIT)); ++ ++ return 0; ++} ++ ++int esparser_init(struct platform_device *pdev, struct amvdec_core *core) ++{ ++ struct device *dev = &pdev->dev; ++ int ret; ++ int irq; ++ ++ irq = platform_get_irq_byname(pdev, "esparser"); ++ if (irq < 0) { ++ dev_err(dev, "Failed getting ESPARSER IRQ from dtb\n"); ++ return irq; ++ } ++ ++ ret = devm_request_irq(dev, irq, esparser_isr, IRQF_SHARED, ++ "esparserirq", core); ++ if (ret) { ++ dev_err(dev, "Failed requesting ESPARSER IRQ\n"); ++ return ret; ++ } ++ ++ core->esparser_reset = ++ devm_reset_control_get_exclusive(dev, "esparser"); ++ if (IS_ERR(core->esparser_reset)) { ++ dev_err(dev, "Failed to get esparser_reset\n"); ++ return PTR_ERR(core->esparser_reset); ++ } ++ ++ return 0; ++} +diff --git a/drivers/media/platform/meson/vdec/esparser.h b/drivers/media/platform/meson/vdec/esparser.h +new file mode 100644 +index 000000000000..ff51fe7fda66 +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/esparser.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ */ ++ ++#ifndef __MESON_VDEC_ESPARSER_H_ ++#define __MESON_VDEC_ESPARSER_H_ ++ ++#include ++ ++#include "vdec.h" ++ ++int esparser_init(struct platform_device *pdev, struct amvdec_core *core); ++int esparser_power_up(struct amvdec_session *sess); ++ ++/** ++ * esparser_queue_eos() - write End Of Stream sequence to the ESPARSER ++ * ++ * @core vdec core struct ++ */ ++int esparser_queue_eos(struct amvdec_core *core, const u8 *data, u32 len); ++ ++/** ++ * esparser_queue_all_src() - work handler that writes as many src buffers ++ * as possible to the ESPARSER ++ */ ++void esparser_queue_all_src(struct work_struct *work); ++ ++#define ESPARSER_MIN_PACKET_SIZE SZ_4K ++ ++#endif +diff --git a/drivers/media/platform/meson/vdec/vdec.c b/drivers/media/platform/meson/vdec/vdec.c +new file mode 100644 +index 000000000000..d8db52c01fbe +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/vdec.c +@@ -0,0 +1,1034 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "vdec.h" ++#include "esparser.h" ++#include "vdec_helpers.h" ++ ++struct dummy_buf { ++ struct vb2_v4l2_buffer vb; ++ struct list_head list; ++}; ++ ++/* 16 MiB for parsed bitstream swap exchange */ ++#define SIZE_VIFIFO SZ_16M ++ ++static u32 get_output_size(u32 width, u32 height) ++{ ++ return ALIGN(width * height, SZ_64K); ++} ++ ++u32 amvdec_get_output_size(struct amvdec_session *sess) ++{ ++ return get_output_size(sess->width, sess->height); ++} ++EXPORT_SYMBOL_GPL(amvdec_get_output_size); ++ ++static int vdec_codec_needs_recycle(struct amvdec_session *sess) ++{ ++ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; ++ ++ return codec_ops->can_recycle && codec_ops->recycle; ++} ++ ++static int vdec_recycle_thread(void *data) ++{ ++ struct amvdec_session *sess = data; ++ struct amvdec_core *core = sess->core; ++ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; ++ struct amvdec_buffer *tmp, *n; ++ ++ while (!kthread_should_stop()) { ++ mutex_lock(&sess->bufs_recycle_lock); ++ list_for_each_entry_safe(tmp, n, &sess->bufs_recycle, list) { ++ if (!codec_ops->can_recycle(core)) ++ break; ++ ++ codec_ops->recycle(core, tmp->vb->index); ++ list_del(&tmp->list); ++ kfree(tmp); ++ } ++ mutex_unlock(&sess->bufs_recycle_lock); ++ ++ usleep_range(5000, 10000); ++ } ++ ++ return 0; ++} ++ ++static int vdec_poweron(struct amvdec_session *sess) ++{ ++ int ret; ++ struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops; ++ ++ ret = clk_prepare_enable(sess->core->dos_parser_clk); ++ if (ret) ++ return ret; ++ ++ ret = clk_prepare_enable(sess->core->dos_clk); ++ if (ret) ++ goto disable_dos_parser; ++ ++ ret = vdec_ops->start(sess); ++ if (ret) ++ goto disable_dos; ++ ++ esparser_power_up(sess); ++ ++ return 0; ++ ++disable_dos: ++ clk_disable_unprepare(sess->core->dos_clk); ++disable_dos_parser: ++ clk_disable_unprepare(sess->core->dos_parser_clk); ++ ++ return ret; ++} ++ ++static void vdec_wait_inactive(struct amvdec_session *sess) ++{ ++ /* We consider 50ms with no IRQ to be inactive. */ ++ while (time_is_after_jiffies64(sess->last_irq_jiffies + ++ msecs_to_jiffies(50))) ++ msleep(25); ++} ++ ++static void vdec_poweroff(struct amvdec_session *sess) ++{ ++ struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops; ++ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; ++ ++ vdec_wait_inactive(sess); ++ if (codec_ops->drain) ++ codec_ops->drain(sess); ++ ++ vdec_ops->stop(sess); ++ clk_disable_unprepare(sess->core->dos_clk); ++ clk_disable_unprepare(sess->core->dos_parser_clk); ++} ++ ++static void ++vdec_queue_recycle(struct amvdec_session *sess, struct vb2_buffer *vb) ++{ ++ struct amvdec_buffer *new_buf; ++ ++ new_buf = kmalloc(sizeof(*new_buf), GFP_KERNEL); ++ new_buf->vb = vb; ++ ++ mutex_lock(&sess->bufs_recycle_lock); ++ list_add_tail(&new_buf->list, &sess->bufs_recycle); ++ mutex_unlock(&sess->bufs_recycle_lock); ++} ++ ++static void vdec_m2m_device_run(void *priv) ++{ ++ struct amvdec_session *sess = priv; ++ ++ schedule_work(&sess->esparser_queue_work); ++} ++ ++static void vdec_m2m_job_abort(void *priv) ++{ ++ struct amvdec_session *sess = priv; ++ ++ v4l2_m2m_job_finish(sess->m2m_dev, sess->m2m_ctx); ++} ++ ++static const struct v4l2_m2m_ops vdec_m2m_ops = { ++ .device_run = vdec_m2m_device_run, ++ .job_abort = vdec_m2m_job_abort, ++}; ++ ++static int vdec_queue_setup(struct vb2_queue *q, ++ unsigned int *num_buffers, unsigned int *num_planes, ++ unsigned int sizes[], struct device *alloc_devs[]) ++{ ++ struct amvdec_session *sess = vb2_get_drv_priv(q); ++ const struct amvdec_format *fmt_out = sess->fmt_out; ++ u32 output_size = amvdec_get_output_size(sess); ++ u32 buffers_total; ++ ++ if (*num_planes) { ++ switch (q->type) { ++ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: ++ if (*num_planes != 1 || sizes[0] < output_size) ++ return -EINVAL; ++ break; ++ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: ++ switch (sess->pixfmt_cap) { ++ case V4L2_PIX_FMT_NV12M: ++ if (*num_planes != 2 || ++ sizes[0] < output_size || ++ sizes[1] < output_size / 2) ++ return -EINVAL; ++ break; ++ case V4L2_PIX_FMT_YUV420M: ++ if (*num_planes != 3 || ++ sizes[0] < output_size || ++ sizes[1] < output_size / 4 || ++ sizes[2] < output_size / 4) ++ return -EINVAL; ++ break; ++ default: ++ return -EINVAL; ++ } ++ break; ++ } ++ ++ return 0; ++ } ++ ++ switch (q->type) { ++ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: ++ sizes[0] = amvdec_get_output_size(sess); ++ *num_planes = 1; ++ break; ++ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: ++ switch (sess->pixfmt_cap) { ++ case V4L2_PIX_FMT_NV12M: ++ sizes[0] = output_size; ++ sizes[1] = output_size / 2; ++ *num_planes = 2; ++ break; ++ case V4L2_PIX_FMT_YUV420M: ++ sizes[0] = output_size; ++ sizes[1] = output_size / 4; ++ sizes[2] = output_size / 4; ++ *num_planes = 3; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ buffers_total = q->num_buffers + *num_buffers; ++ ++ if (buffers_total < fmt_out->min_buffers) ++ *num_buffers = fmt_out->min_buffers - q->num_buffers; ++ if (buffers_total > fmt_out->max_buffers) ++ *num_buffers = fmt_out->max_buffers - q->num_buffers; ++ ++ /* We need to program the complete CAPTURE buffer list ++ * in registers during start_streaming, and the firmwares ++ * are free to choose any of them to write frames to. As such, ++ * we need all of them to be queued into the driver ++ */ ++ q->min_buffers_needed = q->num_buffers + *num_buffers; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static void vdec_vb2_buf_queue(struct vb2_buffer *vb) ++{ ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ struct amvdec_session *sess = vb2_get_drv_priv(vb->vb2_queue); ++ struct v4l2_m2m_ctx *m2m_ctx = sess->m2m_ctx; ++ ++ v4l2_m2m_buf_queue(m2m_ctx, vbuf); ++ ++ if (!sess->streamon_out || !sess->streamon_cap) ++ return; ++ ++ if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && ++ vdec_codec_needs_recycle(sess)) ++ vdec_queue_recycle(sess, vb); ++ ++ schedule_work(&sess->esparser_queue_work); ++} ++ ++static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) ++{ ++ struct amvdec_session *sess = vb2_get_drv_priv(q); ++ struct amvdec_core *core = sess->core; ++ struct vb2_v4l2_buffer *buf; ++ int ret; ++ ++ if (core->cur_sess && core->cur_sess != sess) { ++ ret = -EBUSY; ++ goto bufs_done; ++ } ++ ++ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ++ sess->streamon_out = 1; ++ else ++ sess->streamon_cap = 1; ++ ++ if (!sess->streamon_out || !sess->streamon_cap) ++ return 0; ++ ++ sess->vififo_size = SIZE_VIFIFO; ++ sess->vififo_vaddr = ++ dma_alloc_coherent(sess->core->dev, sess->vififo_size, ++ &sess->vififo_paddr, GFP_KERNEL); ++ if (!sess->vififo_vaddr) { ++ dev_err(sess->core->dev, "Failed to request VIFIFO buffer\n"); ++ ret = -ENOMEM; ++ goto bufs_done; ++ } ++ ++ sess->should_stop = 0; ++ sess->keyframe_found = 0; ++ sess->last_offset = 0; ++ sess->wrap_count = 0; ++ sess->pixelaspect.numerator = 1; ++ sess->pixelaspect.denominator = 1; ++ atomic_set(&sess->esparser_queued_bufs, 0); ++ ++ ret = vdec_poweron(sess); ++ if (ret) ++ goto vififo_free; ++ ++ sess->sequence_cap = 0; ++ if (vdec_codec_needs_recycle(sess)) ++ sess->recycle_thread = kthread_run(vdec_recycle_thread, sess, ++ "vdec_recycle"); ++ ++ core->cur_sess = sess; ++ ++ return 0; ++ ++vififo_free: ++ dma_free_coherent(sess->core->dev, sess->vififo_size, ++ sess->vififo_vaddr, sess->vififo_paddr); ++bufs_done: ++ while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) ++ v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); ++ while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) ++ v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); ++ ++ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ++ sess->streamon_out = 0; ++ else ++ sess->streamon_cap = 0; ++ ++ return ret; ++} ++ ++static void vdec_free_canvas(struct amvdec_session *sess) ++{ ++ int i; ++ ++ for (i = 0; i < sess->canvas_num; ++i) ++ meson_canvas_free(sess->core->canvas, sess->canvas_alloc[i]); ++ ++ sess->canvas_num = 0; ++} ++ ++static void vdec_reset_timestamps(struct amvdec_session *sess) ++{ ++ struct amvdec_timestamp *tmp, *n; ++ ++ list_for_each_entry_safe(tmp, n, &sess->timestamps, list) { ++ list_del(&tmp->list); ++ kfree(tmp); ++ } ++} ++ ++static void vdec_reset_bufs_recycle(struct amvdec_session *sess) ++{ ++ struct amvdec_buffer *tmp, *n; ++ ++ list_for_each_entry_safe(tmp, n, &sess->bufs_recycle, list) { ++ list_del(&tmp->list); ++ kfree(tmp); ++ } ++} ++ ++static void vdec_stop_streaming(struct vb2_queue *q) ++{ ++ struct amvdec_session *sess = vb2_get_drv_priv(q); ++ struct amvdec_core *core = sess->core; ++ struct vb2_v4l2_buffer *buf; ++ ++ if (sess->streamon_out && sess->streamon_cap) { ++ if (vdec_codec_needs_recycle(sess)) ++ kthread_stop(sess->recycle_thread); ++ ++ vdec_poweroff(sess); ++ vdec_free_canvas(sess); ++ dma_free_coherent(sess->core->dev, sess->vififo_size, ++ sess->vififo_vaddr, sess->vififo_paddr); ++ vdec_reset_timestamps(sess); ++ vdec_reset_bufs_recycle(sess); ++ kfree(sess->priv); ++ sess->priv = NULL; ++ core->cur_sess = NULL; ++ } ++ ++ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { ++ while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) ++ v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR); ++ ++ sess->streamon_out = 0; ++ } else { ++ while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) ++ v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR); ++ ++ sess->streamon_cap = 0; ++ } ++} ++ ++static const struct vb2_ops vdec_vb2_ops = { ++ .queue_setup = vdec_queue_setup, ++ .start_streaming = vdec_start_streaming, ++ .stop_streaming = vdec_stop_streaming, ++ .buf_queue = vdec_vb2_buf_queue, ++ .wait_prepare = vb2_ops_wait_prepare, ++ .wait_finish = vb2_ops_wait_finish, ++}; ++ ++static int ++vdec_querycap(struct file *file, void *fh, struct v4l2_capability *cap) ++{ ++ strscpy(cap->driver, "meson-vdec", sizeof(cap->driver)); ++ strscpy(cap->card, "Amlogic Video Decoder", sizeof(cap->card)); ++ strscpy(cap->bus_info, "platform:meson-vdec", sizeof(cap->bus_info)); ++ ++ return 0; ++} ++ ++static const struct amvdec_format * ++find_format(const struct amvdec_format *fmts, u32 size, u32 pixfmt) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < size; i++) { ++ if (fmts[i].pixfmt == pixfmt) ++ return &fmts[i]; ++ } ++ ++ return NULL; ++} ++ ++static unsigned int ++vdec_supports_pixfmt_cap(const struct amvdec_format *fmt_out, u32 pixfmt_cap) ++{ ++ int i; ++ ++ for (i = 0; fmt_out->pixfmts_cap[i]; i++) ++ if (fmt_out->pixfmts_cap[i] == pixfmt_cap) ++ return 1; ++ ++ return 0; ++} ++ ++static const struct amvdec_format * ++vdec_try_fmt_common(struct amvdec_session *sess, u32 size, ++ struct v4l2_format *f) ++{ ++ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; ++ struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt; ++ const struct amvdec_format *fmts = sess->core->platform->formats; ++ const struct amvdec_format *fmt_out; ++ ++ memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved)); ++ memset(pixmp->reserved, 0, sizeof(pixmp->reserved)); ++ ++ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { ++ fmt_out = find_format(fmts, size, pixmp->pixelformat); ++ if (!fmt_out) { ++ pixmp->pixelformat = V4L2_PIX_FMT_MPEG2; ++ fmt_out = find_format(fmts, size, pixmp->pixelformat); ++ } ++ ++ pfmt[0].sizeimage = ++ get_output_size(pixmp->width, pixmp->height); ++ pfmt[0].bytesperline = 0; ++ pixmp->num_planes = 1; ++ } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { ++ fmt_out = sess->fmt_out; ++ if (!vdec_supports_pixfmt_cap(fmt_out, pixmp->pixelformat)) ++ pixmp->pixelformat = fmt_out->pixfmts_cap[0]; ++ ++ memset(pfmt[1].reserved, 0, sizeof(pfmt[1].reserved)); ++ if (pixmp->pixelformat == V4L2_PIX_FMT_NV12M) { ++ pfmt[0].sizeimage = ++ get_output_size(pixmp->width, pixmp->height); ++ pfmt[0].bytesperline = ALIGN(pixmp->width, 64); ++ ++ pfmt[1].sizeimage = ++ get_output_size(pixmp->width, pixmp->height) / 2; ++ pfmt[1].bytesperline = ALIGN(pixmp->width, 64); ++ pixmp->num_planes = 2; ++ } else if (pixmp->pixelformat == V4L2_PIX_FMT_YUV420M) { ++ pfmt[0].sizeimage = ++ get_output_size(pixmp->width, pixmp->height); ++ pfmt[0].bytesperline = ALIGN(pixmp->width, 64); ++ ++ pfmt[1].sizeimage = ++ get_output_size(pixmp->width, pixmp->height) / 4; ++ pfmt[1].bytesperline = ALIGN(pixmp->width, 64) / 2; ++ ++ pfmt[2].sizeimage = ++ get_output_size(pixmp->width, pixmp->height) / 4; ++ pfmt[2].bytesperline = ALIGN(pixmp->width, 64) / 2; ++ pixmp->num_planes = 3; ++ } ++ } else { ++ return NULL; ++ } ++ ++ pixmp->width = clamp(pixmp->width, (u32)256, fmt_out->max_width); ++ pixmp->height = clamp(pixmp->height, (u32)144, fmt_out->max_height); ++ ++ if (pixmp->field == V4L2_FIELD_ANY) ++ pixmp->field = V4L2_FIELD_NONE; ++ ++ return fmt_out; ++} ++ ++static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f) ++{ ++ struct amvdec_session *sess = ++ container_of(file->private_data, struct amvdec_session, fh); ++ ++ vdec_try_fmt_common(sess, sess->core->platform->num_formats, f); ++ ++ return 0; ++} ++ ++static int vdec_g_fmt(struct file *file, void *fh, struct v4l2_format *f) ++{ ++ struct amvdec_session *sess = ++ container_of(file->private_data, struct amvdec_session, fh); ++ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; ++ ++ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) ++ pixmp->pixelformat = sess->pixfmt_cap; ++ else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ++ pixmp->pixelformat = sess->fmt_out->pixfmt; ++ ++ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { ++ pixmp->width = sess->width; ++ pixmp->height = sess->height; ++ pixmp->colorspace = sess->colorspace; ++ pixmp->ycbcr_enc = sess->ycbcr_enc; ++ pixmp->quantization = sess->quantization; ++ pixmp->xfer_func = sess->xfer_func; ++ } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { ++ pixmp->width = sess->width; ++ pixmp->height = sess->height; ++ } ++ ++ vdec_try_fmt_common(sess, sess->core->platform->num_formats, f); ++ ++ return 0; ++} ++ ++static int vdec_s_fmt(struct file *file, void *fh, struct v4l2_format *f) ++{ ++ struct amvdec_session *sess = ++ container_of(file->private_data, struct amvdec_session, fh); ++ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; ++ u32 num_formats = sess->core->platform->num_formats; ++ const struct amvdec_format *fmt_out; ++ struct v4l2_pix_format_mplane orig_pixmp; ++ struct v4l2_format format; ++ u32 pixfmt_out = 0, pixfmt_cap = 0; ++ ++ orig_pixmp = *pixmp; ++ ++ fmt_out = vdec_try_fmt_common(sess, num_formats, f); ++ ++ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { ++ pixfmt_out = pixmp->pixelformat; ++ pixfmt_cap = sess->pixfmt_cap; ++ } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { ++ pixfmt_cap = pixmp->pixelformat; ++ pixfmt_out = sess->fmt_out->pixfmt; ++ } ++ ++ memset(&format, 0, sizeof(format)); ++ ++ format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; ++ format.fmt.pix_mp.pixelformat = pixfmt_out; ++ format.fmt.pix_mp.width = orig_pixmp.width; ++ format.fmt.pix_mp.height = orig_pixmp.height; ++ vdec_try_fmt_common(sess, num_formats, &format); ++ ++ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { ++ sess->width = format.fmt.pix_mp.width; ++ sess->height = format.fmt.pix_mp.height; ++ sess->colorspace = pixmp->colorspace; ++ sess->ycbcr_enc = pixmp->ycbcr_enc; ++ sess->quantization = pixmp->quantization; ++ sess->xfer_func = pixmp->xfer_func; ++ } ++ ++ memset(&format, 0, sizeof(format)); ++ ++ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; ++ format.fmt.pix_mp.pixelformat = pixfmt_cap; ++ format.fmt.pix_mp.width = orig_pixmp.width; ++ format.fmt.pix_mp.height = orig_pixmp.height; ++ vdec_try_fmt_common(sess, num_formats, &format); ++ ++ sess->width = format.fmt.pix_mp.width; ++ sess->height = format.fmt.pix_mp.height; ++ ++ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ++ sess->fmt_out = fmt_out; ++ else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) ++ sess->pixfmt_cap = format.fmt.pix_mp.pixelformat; ++ ++ return 0; ++} ++ ++static int vdec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) ++{ ++ struct amvdec_session *sess = ++ container_of(file->private_data, struct amvdec_session, fh); ++ const struct vdec_platform *platform = sess->core->platform; ++ const struct amvdec_format *fmt_out; ++ ++ memset(f->reserved, 0, sizeof(f->reserved)); ++ ++ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { ++ if (f->index >= platform->num_formats) ++ return -EINVAL; ++ ++ fmt_out = &platform->formats[f->index]; ++ f->pixelformat = fmt_out->pixfmt; ++ } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { ++ fmt_out = sess->fmt_out; ++ if (f->index >= 4 || !fmt_out->pixfmts_cap[f->index]) ++ return -EINVAL; ++ ++ f->pixelformat = fmt_out->pixfmts_cap[f->index]; ++ } else { ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int vdec_enum_framesizes(struct file *file, void *fh, ++ struct v4l2_frmsizeenum *fsize) ++{ ++ struct amvdec_session *sess = ++ container_of(file->private_data, struct amvdec_session, fh); ++ const struct amvdec_format *formats = sess->core->platform->formats; ++ const struct amvdec_format *fmt; ++ u32 num_formats = sess->core->platform->num_formats; ++ ++ fmt = find_format(formats, num_formats, fsize->pixel_format); ++ if (!fmt || fsize->index) ++ return -EINVAL; ++ ++ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; ++ ++ fsize->stepwise.min_width = 256; ++ fsize->stepwise.max_width = fmt->max_width; ++ fsize->stepwise.step_width = 1; ++ fsize->stepwise.min_height = 144; ++ fsize->stepwise.max_height = fmt->max_height; ++ fsize->stepwise.step_height = 1; ++ ++ return 0; ++} ++ ++static int ++vdec_try_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd) ++{ ++ switch (cmd->cmd) { ++ case V4L2_DEC_CMD_STOP: ++ if (cmd->flags & V4L2_DEC_CMD_STOP_TO_BLACK) ++ return -EINVAL; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int ++vdec_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd) ++{ ++ struct amvdec_session *sess = ++ container_of(file->private_data, struct amvdec_session, fh); ++ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; ++ struct device *dev = sess->core->dev; ++ int ret; ++ ++ ret = vdec_try_decoder_cmd(file, fh, cmd); ++ if (ret) ++ return ret; ++ ++ if (!(sess->streamon_out & sess->streamon_cap)) ++ return 0; ++ ++ dev_dbg(dev, "Received V4L2_DEC_CMD_STOP\n"); ++ sess->should_stop = 1; ++ ++ vdec_wait_inactive(sess); ++ ++ if (codec_ops->drain) { ++ codec_ops->drain(sess); ++ } else if (codec_ops->eos_sequence) { ++ u32 len; ++ const u8 *data = codec_ops->eos_sequence(&len); ++ ++ esparser_queue_eos(sess->core, data, len); ++ } ++ ++ return ret; ++} ++ ++static int vdec_subscribe_event(struct v4l2_fh *fh, ++ const struct v4l2_event_subscription *sub) ++{ ++ switch (sub->type) { ++ case V4L2_EVENT_EOS: ++ return v4l2_event_subscribe(fh, sub, 2, NULL); ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int vdec_cropcap(struct file *file, void *fh, ++ struct v4l2_cropcap *crop) ++{ ++ struct amvdec_session *sess = ++ container_of(file->private_data, struct amvdec_session, fh); ++ ++ if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) ++ return -EINVAL; ++ ++ crop->pixelaspect = sess->pixelaspect; ++ return 0; ++} ++ ++static const struct v4l2_ioctl_ops vdec_ioctl_ops = { ++ .vidioc_querycap = vdec_querycap, ++ .vidioc_enum_fmt_vid_cap_mplane = vdec_enum_fmt, ++ .vidioc_enum_fmt_vid_out_mplane = vdec_enum_fmt, ++ .vidioc_s_fmt_vid_cap_mplane = vdec_s_fmt, ++ .vidioc_s_fmt_vid_out_mplane = vdec_s_fmt, ++ .vidioc_g_fmt_vid_cap_mplane = vdec_g_fmt, ++ .vidioc_g_fmt_vid_out_mplane = vdec_g_fmt, ++ .vidioc_try_fmt_vid_cap_mplane = vdec_try_fmt, ++ .vidioc_try_fmt_vid_out_mplane = vdec_try_fmt, ++ .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, ++ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, ++ .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, ++ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, ++ .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, ++ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, ++ .vidioc_streamon = v4l2_m2m_ioctl_streamon, ++ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, ++ .vidioc_enum_framesizes = vdec_enum_framesizes, ++ .vidioc_subscribe_event = vdec_subscribe_event, ++ .vidioc_unsubscribe_event = v4l2_event_unsubscribe, ++ .vidioc_try_decoder_cmd = vdec_try_decoder_cmd, ++ .vidioc_decoder_cmd = vdec_decoder_cmd, ++ .vidioc_cropcap = vdec_cropcap, ++}; ++ ++static int m2m_queue_init(void *priv, struct vb2_queue *src_vq, ++ struct vb2_queue *dst_vq) ++{ ++ struct amvdec_session *sess = priv; ++ int ret; ++ ++ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; ++ src_vq->io_modes = VB2_MMAP | VB2_DMABUF; ++ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; ++ src_vq->ops = &vdec_vb2_ops; ++ src_vq->mem_ops = &vb2_dma_contig_memops; ++ src_vq->drv_priv = sess; ++ src_vq->buf_struct_size = sizeof(struct dummy_buf); ++ src_vq->min_buffers_needed = 1; ++ src_vq->dev = sess->core->dev; ++ src_vq->lock = &sess->lock; ++ ret = vb2_queue_init(src_vq); ++ if (ret) ++ return ret; ++ ++ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; ++ dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; ++ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; ++ dst_vq->ops = &vdec_vb2_ops; ++ dst_vq->mem_ops = &vb2_dma_contig_memops; ++ dst_vq->drv_priv = sess; ++ dst_vq->buf_struct_size = sizeof(struct dummy_buf); ++ dst_vq->min_buffers_needed = 1; ++ dst_vq->dev = sess->core->dev; ++ dst_vq->lock = &sess->lock; ++ ret = vb2_queue_init(dst_vq); ++ if (ret) { ++ vb2_queue_release(src_vq); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int vdec_open(struct file *file) ++{ ++ struct amvdec_core *core = video_drvdata(file); ++ struct device *dev = core->dev; ++ const struct amvdec_format *formats = core->platform->formats; ++ struct amvdec_session *sess; ++ int ret; ++ ++ sess = kzalloc(sizeof(*sess), GFP_KERNEL); ++ if (!sess) ++ return -ENOMEM; ++ ++ sess->core = core; ++ ++ sess->m2m_dev = v4l2_m2m_init(&vdec_m2m_ops); ++ if (IS_ERR(sess->m2m_dev)) { ++ dev_err(dev, "Fail to v4l2_m2m_init\n"); ++ ret = PTR_ERR(sess->m2m_dev); ++ goto err_free_sess; ++ } ++ ++ sess->m2m_ctx = v4l2_m2m_ctx_init(sess->m2m_dev, sess, m2m_queue_init); ++ if (IS_ERR(sess->m2m_ctx)) { ++ dev_err(dev, "Fail to v4l2_m2m_ctx_init\n"); ++ ret = PTR_ERR(sess->m2m_ctx); ++ goto err_m2m_release; ++ } ++ ++ sess->pixfmt_cap = formats[0].pixfmts_cap[0]; ++ sess->fmt_out = &formats[0]; ++ sess->width = 1280; ++ sess->height = 720; ++ sess->pixelaspect.numerator = 1; ++ sess->pixelaspect.denominator = 1; ++ ++ INIT_LIST_HEAD(&sess->timestamps); ++ INIT_LIST_HEAD(&sess->bufs_recycle); ++ INIT_WORK(&sess->esparser_queue_work, esparser_queue_all_src); ++ mutex_init(&sess->lock); ++ mutex_init(&sess->bufs_recycle_lock); ++ spin_lock_init(&sess->ts_spinlock); ++ ++ v4l2_fh_init(&sess->fh, core->vdev_dec); ++ v4l2_fh_add(&sess->fh); ++ sess->fh.m2m_ctx = sess->m2m_ctx; ++ file->private_data = &sess->fh; ++ ++ return 0; ++ ++err_m2m_release: ++ v4l2_m2m_release(sess->m2m_dev); ++err_free_sess: ++ kfree(sess); ++ return ret; ++} ++ ++static int vdec_close(struct file *file) ++{ ++ struct amvdec_session *sess = ++ container_of(file->private_data, struct amvdec_session, fh); ++ ++ v4l2_m2m_ctx_release(sess->m2m_ctx); ++ v4l2_m2m_release(sess->m2m_dev); ++ v4l2_fh_del(&sess->fh); ++ v4l2_fh_exit(&sess->fh); ++ ++ mutex_destroy(&sess->lock); ++ mutex_destroy(&sess->bufs_recycle_lock); ++ ++ kfree(sess); ++ ++ return 0; ++} ++ ++static const struct v4l2_file_operations vdec_fops = { ++ .owner = THIS_MODULE, ++ .open = vdec_open, ++ .release = vdec_close, ++ .unlocked_ioctl = video_ioctl2, ++ .poll = v4l2_m2m_fop_poll, ++ .mmap = v4l2_m2m_fop_mmap, ++}; ++ ++static irqreturn_t vdec_isr(int irq, void *data) ++{ ++ struct amvdec_core *core = data; ++ struct amvdec_session *sess = core->cur_sess; ++ ++ sess->last_irq_jiffies = get_jiffies_64(); ++ ++ return sess->fmt_out->codec_ops->isr(sess); ++} ++ ++static irqreturn_t vdec_threaded_isr(int irq, void *data) ++{ ++ struct amvdec_core *core = data; ++ struct amvdec_session *sess = core->cur_sess; ++ ++ return sess->fmt_out->codec_ops->threaded_isr(sess); ++} ++ ++static const struct of_device_id vdec_dt_match[] = { ++ { .compatible = "amlogic,gxbb-vdec", ++ .data = &vdec_platform_gxbb }, ++ { .compatible = "amlogic,gxm-vdec", ++ .data = &vdec_platform_gxm }, ++ { .compatible = "amlogic,gxl-vdec", ++ .data = &vdec_platform_gxl }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, vdec_dt_match); ++ ++static int vdec_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct video_device *vdev; ++ struct amvdec_core *core; ++ struct resource *r; ++ const struct of_device_id *of_id; ++ int irq; ++ int ret; ++ ++ core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); ++ if (!core) ++ return -ENOMEM; ++ ++ core->dev = dev; ++ platform_set_drvdata(pdev, core); ++ ++ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dos"); ++ core->dos_base = devm_ioremap_resource(dev, r); ++ if (IS_ERR(core->dos_base)) { ++ dev_err(dev, "Couldn't remap DOS memory\n"); ++ return PTR_ERR(core->dos_base); ++ } ++ ++ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "esparser"); ++ core->esparser_base = devm_ioremap_resource(dev, r); ++ if (IS_ERR(core->esparser_base)) { ++ dev_err(dev, "Couldn't remap ESPARSER memory\n"); ++ return PTR_ERR(core->esparser_base); ++ } ++ ++ core->regmap_ao = syscon_regmap_lookup_by_phandle(dev->of_node, ++ "amlogic,ao-sysctrl"); ++ if (IS_ERR(core->regmap_ao)) { ++ dev_err(dev, "Couldn't regmap AO sysctrl\n"); ++ return PTR_ERR(core->regmap_ao); ++ } ++ ++ core->canvas = meson_canvas_get(dev); ++ if (!core->canvas) ++ return PTR_ERR(core->canvas); ++ ++ core->dos_parser_clk = devm_clk_get(dev, "dos_parser"); ++ if (IS_ERR(core->dos_parser_clk)) ++ return -EPROBE_DEFER; ++ ++ core->dos_clk = devm_clk_get(dev, "dos"); ++ if (IS_ERR(core->dos_clk)) ++ return -EPROBE_DEFER; ++ ++ core->vdec_1_clk = devm_clk_get(dev, "vdec_1"); ++ if (IS_ERR(core->vdec_1_clk)) ++ return -EPROBE_DEFER; ++ ++ core->vdec_hevc_clk = devm_clk_get(dev, "vdec_hevc"); ++ if (IS_ERR(core->vdec_hevc_clk)) ++ return -EPROBE_DEFER; ++ ++ irq = platform_get_irq_byname(pdev, "vdec"); ++ if (irq < 0) ++ return irq; ++ ++ ret = devm_request_threaded_irq(core->dev, irq, vdec_isr, ++ vdec_threaded_isr, IRQF_ONESHOT, ++ "vdec", core); ++ if (ret) ++ return ret; ++ ++ ret = esparser_init(pdev, core); ++ if (ret) ++ return ret; ++ ++ ret = v4l2_device_register(dev, &core->v4l2_dev); ++ if (ret) { ++ dev_err(dev, "Couldn't register v4l2 device\n"); ++ return -ENOMEM; ++ } ++ ++ vdev = video_device_alloc(); ++ if (!vdev) { ++ ret = -ENOMEM; ++ goto err_vdev_release; ++ } ++ ++ of_id = of_match_node(vdec_dt_match, dev->of_node); ++ core->platform = of_id->data; ++ core->vdev_dec = vdev; ++ core->dev_dec = dev; ++ mutex_init(&core->lock); ++ ++ strscpy(vdev->name, "meson-video-decoder", sizeof(vdev->name)); ++ vdev->release = video_device_release; ++ vdev->fops = &vdec_fops; ++ vdev->ioctl_ops = &vdec_ioctl_ops; ++ vdev->vfl_dir = VFL_DIR_M2M; ++ vdev->v4l2_dev = &core->v4l2_dev; ++ vdev->lock = &core->lock; ++ vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; ++ ++ video_set_drvdata(vdev, core); ++ ++ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); ++ if (ret) { ++ dev_err(dev, "Failed registering video device\n"); ++ goto err_vdev_release; ++ } ++ ++ return 0; ++ ++err_vdev_release: ++ video_device_release(vdev); ++ return ret; ++} ++ ++static int vdec_remove(struct platform_device *pdev) ++{ ++ struct amvdec_core *core = platform_get_drvdata(pdev); ++ ++ video_unregister_device(core->vdev_dec); ++ ++ return 0; ++} ++ ++static struct platform_driver meson_vdec_driver = { ++ .probe = vdec_probe, ++ .remove = vdec_remove, ++ .driver = { ++ .name = "meson-vdec", ++ .of_match_table = vdec_dt_match, ++ }, ++}; ++module_platform_driver(meson_vdec_driver); ++ ++MODULE_DESCRIPTION("Meson video decoder driver for GXBB/GXL/GXM"); ++MODULE_AUTHOR("Maxime Jourdan "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/media/platform/meson/vdec/vdec.h b/drivers/media/platform/meson/vdec/vdec.h +new file mode 100644 +index 000000000000..4e8c3f1742ac +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/vdec.h +@@ -0,0 +1,251 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ */ ++ ++#ifndef __MESON_VDEC_CORE_H_ ++#define __MESON_VDEC_CORE_H_ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "vdec_platform.h" ++ ++/* 32 buffers in 3-plane YUV420 */ ++#define MAX_CANVAS (32 * 3) ++ ++struct amvdec_buffer { ++ struct list_head list; ++ struct vb2_buffer *vb; ++}; ++ ++/** ++ * struct amvdec_timestamp - stores a src timestamp along with a VIFIFO offset ++ * ++ * @list: used to make lists out of this struct ++ * @ts: timestamp ++ * @offset: offset in the VIFIFO where the associated packet was written ++ */ ++struct amvdec_timestamp { ++ struct list_head list; ++ u64 ts; ++ u32 offset; ++}; ++ ++struct amvdec_session; ++ ++/** ++ * struct amvdec_core - device parameters, singleton ++ * ++ * @dos_base: DOS memory base address ++ * @esparser_base: PARSER memory base address ++ * @regmap_ao: regmap for the AO bus ++ * @dev: core device ++ * @dev_dec: decoder device ++ * @platform: platform-specific data ++ * @canvas: canvas provider reference ++ * @dos_parser_clk: DOS_PARSER clock ++ * @dos_clk: DOS clock ++ * @vdec_1_clk: VDEC_1 clock ++ * @vdec_hevc_clk: VDEC_HEVC clock ++ * @esparser_reset: RESET for the PARSER ++ * @vdec_dec: video device for the decoder ++ * @v4l2_dev: v4l2 device ++ * @cur_sess: current decoding session ++ * @lock: lock for this structure ++ */ ++struct amvdec_core { ++ void __iomem *dos_base; ++ void __iomem *esparser_base; ++ struct regmap *regmap_ao; ++ ++ struct device *dev; ++ struct device *dev_dec; ++ const struct vdec_platform *platform; ++ ++ struct meson_canvas *canvas; ++ ++ struct clk *dos_parser_clk; ++ struct clk *dos_clk; ++ struct clk *vdec_1_clk; ++ struct clk *vdec_hevc_clk; ++ ++ struct reset_control *esparser_reset; ++ ++ struct video_device *vdev_dec; ++ struct v4l2_device v4l2_dev; ++ ++ struct amvdec_session *cur_sess; ++ struct mutex lock; ++}; ++ ++/** ++ * struct amvdec_ops - vdec operations ++ * ++ * @start: mandatory call when the vdec needs to initialize ++ * @stop: mandatory call when the vdec needs to stop ++ * @conf_esparser: mandatory call to let the vdec configure the ESPARSER ++ * @vififo_level: mandatory call to get the current amount of data ++ * in the VIFIFO ++ * @use_offsets: mandatory call. Returns 1 if the VDEC supports vififo offsets ++ */ ++struct amvdec_ops { ++ int (*start)(struct amvdec_session *sess); ++ int (*stop)(struct amvdec_session *sess); ++ void (*conf_esparser)(struct amvdec_session *sess); ++ u32 (*vififo_level)(struct amvdec_session *sess); ++}; ++ ++/** ++ * struct amvdec_codec_ops - codec operations ++ * ++ * @start: mandatory call when the codec needs to initialize ++ * @stop: mandatory call when the codec needs to stop ++ * @load_extended_firmware: optional call to load additional firmware bits ++ * @num_pending_bufs: optional call to get the number of dst buffers on hold ++ * @can_recycle: optional call to know if the codec is ready to recycle ++ * a dst buffer ++ * @recycle: optional call to tell the codec to recycle a dst buffer. Must go ++ * in pair with @can_recycle ++ * @drain: optional call if the codec has a custom way of draining ++ * @eos_sequence: optional call to get an end sequence to send to esparser ++ * for flush. Mutually exclusive with @drain. ++ * @isr: mandatory call when the ISR triggers ++ * @threaded_isr: mandatory call for the threaded ISR ++ */ ++struct amvdec_codec_ops { ++ int (*start)(struct amvdec_session *sess); ++ int (*stop)(struct amvdec_session *sess); ++ int (*load_extended_firmware)(struct amvdec_session *sess, ++ const u8 *data, u32 len); ++ u32 (*num_pending_bufs)(struct amvdec_session *sess); ++ int (*can_recycle)(struct amvdec_core *core); ++ void (*recycle)(struct amvdec_core *core, u32 buf_idx); ++ void (*drain)(struct amvdec_session *sess); ++ const u8 * (*eos_sequence)(u32 *len); ++ irqreturn_t (*isr)(struct amvdec_session *sess); ++ irqreturn_t (*threaded_isr)(struct amvdec_session *sess); ++}; ++ ++/** ++ * struct amvdec_format - describes one of the OUTPUT (src) format supported ++ * ++ * @pixfmt: V4L2 pixel format ++ * @min_buffers: minimum amount of CAPTURE (dst) buffers ++ * @max_buffers: maximum amount of CAPTURE (dst) buffers ++ * @max_width: maximum picture width supported ++ * @max_height: maximum picture height supported ++ * @vdec_ops: the VDEC operations that support this format ++ * @codec_ops: the codec operations that support this format ++ * @firmware_path: Path to the firmware that supports this format ++ * @pixfmts_cap: list of CAPTURE pixel formats available with pixfmt ++ */ ++struct amvdec_format { ++ u32 pixfmt; ++ u32 min_buffers; ++ u32 max_buffers; ++ u32 max_width; ++ u32 max_height; ++ ++ struct amvdec_ops *vdec_ops; ++ struct amvdec_codec_ops *codec_ops; ++ ++ char *firmware_path; ++ u32 pixfmts_cap[4]; ++}; ++ ++/** ++ * struct amvdec_session - decoding session parameters ++ * ++ * @core: reference to the vdec core struct ++ * @fh: v4l2 file handle ++ * @m2m_dev: v4l2 m2m device ++ * @m2m_ctx: v4l2 m2m context ++ * @lock: session lock ++ * @fmt_out: vdec pixel format for the OUTPUT queue ++ * @pixfmt_cap: V4L2 pixel format for the CAPTURE queue ++ * @width: current picture width ++ * @height: current picture height ++ * @colorspace: current colorspace ++ * @ycbcr_enc: current ycbcr_enc ++ * @quantization: current quantization ++ * @xfer_func: current transfer function ++ * @pixelaspect: Pixel Aspect Ratio reported by the decoder ++ * @esparser_queued_bufs: number of buffers currently queued into ESPARSER ++ * @esparser_queue_work: work struct for the ESPARSER to process src buffers ++ * @streamon_cap: stream on flag for capture queue ++ * @streamon_out: stream on flag for output queue ++ * @sequence_cap: capture sequence counter ++ * @should_stop: flag set if userspace signaled EOS via command ++ * or empty buffer ++ * @keyframe_found: flag set once a keyframe has been parsed ++ * @canvas_alloc: array of all the canvas IDs allocated ++ * @canvas_num: number of canvas IDs allocated ++ * @vififo_vaddr: virtual address for the VIFIFO ++ * @vififo_paddr: physical address for the VIFIFO ++ * @vififo_size: size of the VIFIFO dma alloc ++ * @bufs_recycle: list of buffers that need to be recycled ++ * @bufs_recycle_lock: lock for the bufs_recycle list ++ * @recycle_thread: task struct for the recycling thread ++ * @timestamps: chronological list of src timestamps ++ * @ts_spinlock: spinlock for the timestamps list ++ * @last_irq_jiffies: tracks last time the vdec triggered an IRQ ++ * @priv: codec private data ++ */ ++struct amvdec_session { ++ struct amvdec_core *core; ++ ++ struct v4l2_fh fh; ++ struct v4l2_m2m_dev *m2m_dev; ++ struct v4l2_m2m_ctx *m2m_ctx; ++ struct mutex lock; ++ ++ const struct amvdec_format *fmt_out; ++ u32 pixfmt_cap; ++ ++ u32 width; ++ u32 height; ++ u32 colorspace; ++ u8 ycbcr_enc; ++ u8 quantization; ++ u8 xfer_func; ++ ++ struct v4l2_fract pixelaspect; ++ ++ atomic_t esparser_queued_bufs; ++ struct work_struct esparser_queue_work; ++ ++ unsigned int streamon_cap, streamon_out; ++ unsigned int sequence_cap; ++ unsigned int should_stop; ++ unsigned int keyframe_found; ++ ++ u8 canvas_alloc[MAX_CANVAS]; ++ u32 canvas_num; ++ ++ void *vififo_vaddr; ++ dma_addr_t vififo_paddr; ++ u32 vififo_size; ++ ++ struct list_head bufs_recycle; ++ struct mutex bufs_recycle_lock; ++ struct task_struct *recycle_thread; ++ ++ struct list_head timestamps; ++ spinlock_t ts_spinlock; ++ ++ u64 last_irq_jiffies; ++ u32 last_offset; ++ u32 wrap_count; ++ ++ void *priv; ++}; ++ ++u32 amvdec_get_output_size(struct amvdec_session *sess); ++ ++#endif +diff --git a/drivers/media/platform/meson/vdec/vdec_1.c b/drivers/media/platform/meson/vdec/vdec_1.c +new file mode 100644 +index 000000000000..88b8bed9441e +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/vdec_1.c +@@ -0,0 +1,231 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ * ++ * VDEC_1 is a video decoding block that allows decoding of ++ * MPEG 1/2/4, H.263, H.264, MJPEG, VC1 ++ */ ++ ++#include ++#include ++ ++#include "vdec_1.h" ++#include "vdec_helpers.h" ++#include "dos_regs.h" ++ ++/* AO Registers */ ++#define AO_RTI_GEN_PWR_SLEEP0 0xe8 ++#define AO_RTI_GEN_PWR_ISO0 0xec ++ #define GEN_PWR_VDEC_1 (BIT(3) | BIT(2)) ++ ++#define MC_SIZE (4096 * 4) ++ ++static int ++vdec_1_load_firmware(struct amvdec_session *sess, const char *fwname) ++{ ++ const struct firmware *fw; ++ struct amvdec_core *core = sess->core; ++ struct device *dev = core->dev_dec; ++ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; ++ static void *mc_addr; ++ static dma_addr_t mc_addr_map; ++ int ret; ++ u32 i = 1000; ++ ++ ret = request_firmware(&fw, fwname, dev); ++ if (ret < 0) ++ return -EINVAL; ++ ++ if (fw->size < MC_SIZE) { ++ dev_err(dev, "Firmware size %zu is too small. Expected %u.\n", ++ fw->size, MC_SIZE); ++ ret = -EINVAL; ++ goto release_firmware; ++ } ++ ++ mc_addr = dma_alloc_coherent(core->dev, MC_SIZE, ++ &mc_addr_map, GFP_KERNEL); ++ if (!mc_addr) { ++ dev_err(dev, ++ "Failed allocating memory for firmware loading\n"); ++ ret = -ENOMEM; ++ goto release_firmware; ++ } ++ ++ memcpy(mc_addr, fw->data, MC_SIZE); ++ ++ amvdec_write_dos(core, MPSR, 0); ++ amvdec_write_dos(core, CPSR, 0); ++ ++ amvdec_clear_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(31)); ++ ++ amvdec_write_dos(core, IMEM_DMA_ADR, mc_addr_map); ++ amvdec_write_dos(core, IMEM_DMA_COUNT, MC_SIZE / 4); ++ amvdec_write_dos(core, IMEM_DMA_CTRL, (0x8000 | (7 << 16))); ++ ++ while (--i && amvdec_read_dos(core, IMEM_DMA_CTRL) & 0x8000) { } ++ ++ if (i == 0) { ++ dev_err(dev, "Firmware load fail (DMA hang?)\n"); ++ ret = -EINVAL; ++ goto free_mc; ++ } ++ ++ if (codec_ops->load_extended_firmware) ++ ret = codec_ops->load_extended_firmware(sess, ++ fw->data + MC_SIZE, ++ fw->size - MC_SIZE); ++ ++free_mc: ++ dma_free_coherent(core->dev, MC_SIZE, mc_addr, mc_addr_map); ++release_firmware: ++ release_firmware(fw); ++ return ret; ++} ++ ++int vdec_1_stbuf_power_up(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ ++ amvdec_write_dos(core, VLD_MEM_VIFIFO_CONTROL, 0); ++ amvdec_write_dos(core, VLD_MEM_VIFIFO_WRAP_COUNT, 0); ++ amvdec_write_dos(core, POWER_CTL_VLD, BIT(4)); ++ ++ amvdec_write_dos(core, VLD_MEM_VIFIFO_START_PTR, sess->vififo_paddr); ++ amvdec_write_dos(core, VLD_MEM_VIFIFO_CURR_PTR, sess->vififo_paddr); ++ amvdec_write_dos(core, VLD_MEM_VIFIFO_END_PTR, ++ sess->vififo_paddr + sess->vififo_size - 8); ++ ++ amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_CONTROL, 1); ++ amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_CONTROL, 1); ++ ++ amvdec_write_dos(core, VLD_MEM_VIFIFO_BUF_CNTL, MEM_BUFCTRL_MANUAL); ++ amvdec_write_dos(core, VLD_MEM_VIFIFO_WP, sess->vififo_paddr); ++ ++ amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1); ++ amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1); ++ ++ amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_CONTROL, ++ (0x11 << MEM_FIFO_CNT_BIT) | MEM_FILL_ON_LEVEL | ++ MEM_CTRL_FILL_EN | MEM_CTRL_EMPTY_EN); ++ ++ return 0; ++} ++ ++static void vdec_1_conf_esparser(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ ++ /* VDEC_1 specific ESPARSER stuff */ ++ amvdec_write_dos(core, DOS_GEN_CTRL0, 0); ++ amvdec_write_dos(core, VLD_MEM_VIFIFO_BUF_CNTL, 1); ++ amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1); ++} ++ ++static u32 vdec_1_vififo_level(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ ++ return amvdec_read_dos(core, VLD_MEM_VIFIFO_LEVEL); ++} ++ ++static int vdec_1_stop(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; ++ ++ amvdec_write_dos(core, MPSR, 0); ++ amvdec_write_dos(core, CPSR, 0); ++ amvdec_write_dos(core, ASSIST_MBOX1_MASK, 0); ++ ++ amvdec_write_dos(core, DOS_SW_RESET0, BIT(12) | BIT(11)); ++ amvdec_write_dos(core, DOS_SW_RESET0, 0); ++ amvdec_read_dos(core, DOS_SW_RESET0); ++ ++ /* enable vdec1 isolation */ ++ regmap_write(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, 0xc0); ++ /* power off vdec1 memories */ ++ amvdec_write_dos(core, DOS_MEM_PD_VDEC, 0xffffffff); ++ /* power off vdec1 */ ++ regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, ++ GEN_PWR_VDEC_1, GEN_PWR_VDEC_1); ++ ++ clk_disable_unprepare(core->vdec_1_clk); ++ ++ if (sess->priv) ++ codec_ops->stop(sess); ++ ++ return 0; ++} ++ ++static int vdec_1_start(struct amvdec_session *sess) ++{ ++ int ret; ++ struct amvdec_core *core = sess->core; ++ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; ++ ++ /* Configure the vdec clk to the maximum available */ ++ clk_set_rate(core->vdec_1_clk, 666666666); ++ ret = clk_prepare_enable(core->vdec_1_clk); ++ if (ret) ++ return ret; ++ ++ regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, ++ GEN_PWR_VDEC_1, 0); ++ udelay(10); ++ ++ /* Reset VDEC1 */ ++ amvdec_write_dos(core, DOS_SW_RESET0, 0xfffffffc); ++ amvdec_write_dos(core, DOS_SW_RESET0, 0x00000000); ++ ++ amvdec_write_dos(core, DOS_GCLK_EN0, 0x3ff); ++ ++ /* enable VDEC Memories */ ++ amvdec_write_dos(core, DOS_MEM_PD_VDEC, 0); ++ /* Remove VDEC1 Isolation */ ++ regmap_write(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, 0); ++ /* Reset DOS top registers */ ++ amvdec_write_dos(core, DOS_VDEC_MCRCC_STALL_CTRL, 0); ++ ++ amvdec_write_dos(core, GCLK_EN, 0x3ff); ++ amvdec_clear_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(31)); ++ ++ vdec_1_stbuf_power_up(sess); ++ ++ ret = vdec_1_load_firmware(sess, sess->fmt_out->firmware_path); ++ if (ret) ++ goto stop; ++ ++ ret = codec_ops->start(sess); ++ if (ret) ++ goto stop; ++ ++ /* Enable IRQ */ ++ amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1); ++ amvdec_write_dos(core, ASSIST_MBOX1_MASK, 1); ++ ++ /* Enable 2-plane output */ ++ if (sess->pixfmt_cap == V4L2_PIX_FMT_NV12M) ++ amvdec_write_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(17)); ++ else ++ amvdec_clear_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(17)); ++ ++ /* Enable firmware processor */ ++ amvdec_write_dos(core, MPSR, 1); ++ /* Let the firmware settle */ ++ udelay(10); ++ ++ return 0; ++ ++stop: ++ vdec_1_stop(sess); ++ return ret; ++} ++ ++struct amvdec_ops vdec_1_ops = { ++ .start = vdec_1_start, ++ .stop = vdec_1_stop, ++ .conf_esparser = vdec_1_conf_esparser, ++ .vififo_level = vdec_1_vififo_level, ++}; +diff --git a/drivers/media/platform/meson/vdec/vdec_1.h b/drivers/media/platform/meson/vdec/vdec_1.h +new file mode 100644 +index 000000000000..042d930c40d7 +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/vdec_1.h +@@ -0,0 +1,14 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ */ ++ ++#ifndef __MESON_VDEC_VDEC_1_H_ ++#define __MESON_VDEC_VDEC_1_H_ ++ ++#include "vdec.h" ++ ++extern struct amvdec_ops vdec_1_ops; ++ ++#endif +diff --git a/drivers/media/platform/meson/vdec/vdec_helpers.c b/drivers/media/platform/meson/vdec/vdec_helpers.c +new file mode 100644 +index 000000000000..02090c5b089e +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/vdec_helpers.c +@@ -0,0 +1,412 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "vdec_helpers.h" ++ ++#define NUM_CANVAS_NV12 2 ++#define NUM_CANVAS_YUV420 3 ++ ++u32 amvdec_read_dos(struct amvdec_core *core, u32 reg) ++{ ++ return readl_relaxed(core->dos_base + reg); ++} ++EXPORT_SYMBOL_GPL(amvdec_read_dos); ++ ++void amvdec_write_dos(struct amvdec_core *core, u32 reg, u32 val) ++{ ++ writel_relaxed(val, core->dos_base + reg); ++} ++EXPORT_SYMBOL_GPL(amvdec_write_dos); ++ ++void amvdec_write_dos_bits(struct amvdec_core *core, u32 reg, u32 val) ++{ ++ amvdec_write_dos(core, reg, amvdec_read_dos(core, reg) | val); ++} ++EXPORT_SYMBOL_GPL(amvdec_write_dos_bits); ++ ++void amvdec_clear_dos_bits(struct amvdec_core *core, u32 reg, u32 val) ++{ ++ amvdec_write_dos(core, reg, amvdec_read_dos(core, reg) & ~val); ++} ++EXPORT_SYMBOL_GPL(amvdec_clear_dos_bits); ++ ++u32 amvdec_read_parser(struct amvdec_core *core, u32 reg) ++{ ++ return readl_relaxed(core->esparser_base + reg); ++} ++EXPORT_SYMBOL_GPL(amvdec_read_parser); ++ ++void amvdec_write_parser(struct amvdec_core *core, u32 reg, u32 val) ++{ ++ writel_relaxed(val, core->esparser_base + reg); ++} ++EXPORT_SYMBOL_GPL(amvdec_write_parser); ++ ++static int canvas_alloc(struct amvdec_session *sess, u8 *canvas_id) ++{ ++ int ret; ++ ++ if (sess->canvas_num >= MAX_CANVAS) { ++ dev_err(sess->core->dev, "Reached max number of canvas\n"); ++ return -ENOMEM; ++ } ++ ++ ret = meson_canvas_alloc(sess->core->canvas, canvas_id); ++ if (ret) ++ return ret; ++ ++ sess->canvas_alloc[sess->canvas_num++] = *canvas_id; ++ return 0; ++} ++ ++static int set_canvas_yuv420m(struct amvdec_session *sess, ++ struct vb2_buffer *vb, u32 width, ++ u32 height, u32 reg) ++{ ++ struct amvdec_core *core = sess->core; ++ u8 canvas_id[NUM_CANVAS_YUV420]; /* Y U V */ ++ dma_addr_t buf_paddr[NUM_CANVAS_YUV420]; /* Y U V */ ++ int ret, i; ++ ++ for (i = 0; i < NUM_CANVAS_YUV420; ++i) { ++ ret = canvas_alloc(sess, &canvas_id[i]); ++ if (ret) ++ return ret; ++ ++ buf_paddr[i] = ++ vb2_dma_contig_plane_dma_addr(vb, i); ++ } ++ ++ /* Y plane */ ++ meson_canvas_config(core->canvas, canvas_id[0], buf_paddr[0], ++ width, height, MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR, ++ MESON_CANVAS_ENDIAN_SWAP64); ++ ++ /* U plane */ ++ meson_canvas_config(core->canvas, canvas_id[1], buf_paddr[1], ++ width / 2, height / 2, MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR, ++ MESON_CANVAS_ENDIAN_SWAP64); ++ ++ /* V plane */ ++ meson_canvas_config(core->canvas, canvas_id[2], buf_paddr[2], ++ width / 2, height / 2, MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR, ++ MESON_CANVAS_ENDIAN_SWAP64); ++ ++ amvdec_write_dos(core, reg, ++ ((canvas_id[2]) << 16) | ++ ((canvas_id[1]) << 8) | ++ (canvas_id[0])); ++ ++ return 0; ++} ++ ++static int set_canvas_nv12m(struct amvdec_session *sess, ++ struct vb2_buffer *vb, u32 width, ++ u32 height, u32 reg) ++{ ++ struct amvdec_core *core = sess->core; ++ u8 canvas_id[NUM_CANVAS_NV12]; /* Y U/V */ ++ dma_addr_t buf_paddr[NUM_CANVAS_NV12]; /* Y U/V */ ++ int ret, i; ++ ++ for (i = 0; i < NUM_CANVAS_NV12; ++i) { ++ ret = canvas_alloc(sess, &canvas_id[i]); ++ if (ret) ++ return ret; ++ ++ buf_paddr[i] = ++ vb2_dma_contig_plane_dma_addr(vb, i); ++ } ++ ++ /* Y plane */ ++ meson_canvas_config(core->canvas, canvas_id[0], buf_paddr[0], ++ width, height, MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR, ++ MESON_CANVAS_ENDIAN_SWAP64); ++ ++ /* U/V plane */ ++ meson_canvas_config(core->canvas, canvas_id[1], buf_paddr[1], ++ width, height / 2, MESON_CANVAS_WRAP_NONE, ++ MESON_CANVAS_BLKMODE_LINEAR, ++ MESON_CANVAS_ENDIAN_SWAP64); ++ ++ amvdec_write_dos(core, reg, ++ ((canvas_id[1]) << 16) | ++ ((canvas_id[1]) << 8) | ++ (canvas_id[0])); ++ ++ return 0; ++} ++ ++int amvdec_set_canvases(struct amvdec_session *sess, ++ u32 reg_base[], u32 reg_num[]) ++{ ++ struct v4l2_m2m_buffer *buf; ++ u32 pixfmt = sess->pixfmt_cap; ++ u32 width = ALIGN(sess->width, 64); ++ u32 height = ALIGN(sess->height, 64); ++ u32 reg_cur = reg_base[0]; ++ u32 reg_num_cur = 0; ++ u32 reg_base_cur = 0; ++ int ret; ++ ++ v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { ++ if (!reg_base[reg_base_cur]) ++ return -EINVAL; ++ ++ reg_cur = reg_base[reg_base_cur] + reg_num_cur * 4; ++ ++ switch (pixfmt) { ++ case V4L2_PIX_FMT_NV12M: ++ ret = set_canvas_nv12m(sess, &buf->vb.vb2_buf, width, ++ height, reg_cur); ++ if (ret) ++ return ret; ++ break; ++ case V4L2_PIX_FMT_YUV420M: ++ ret = set_canvas_yuv420m(sess, &buf->vb.vb2_buf, width, ++ height, reg_cur); ++ if (ret) ++ return ret; ++ break; ++ default: ++ dev_err(sess->core->dev, "Unsupported pixfmt %08X\n", ++ pixfmt); ++ return -EINVAL; ++ }; ++ ++ reg_num_cur++; ++ if (reg_num_cur >= reg_num[reg_base_cur]) { ++ reg_base_cur++; ++ reg_num_cur = 0; ++ } ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(amvdec_set_canvases); ++ ++void amvdec_add_ts_reorder(struct amvdec_session *sess, u64 ts, u32 offset) ++{ ++ struct amvdec_timestamp *new_ts, *tmp; ++ unsigned long flags; ++ ++ new_ts = kmalloc(sizeof(*new_ts), GFP_KERNEL); ++ new_ts->ts = ts; ++ new_ts->offset = offset; ++ ++ spin_lock_irqsave(&sess->ts_spinlock, flags); ++ ++ if (list_empty(&sess->timestamps)) ++ goto add_tail; ++ ++ list_for_each_entry(tmp, &sess->timestamps, list) { ++ if (ts <= tmp->ts) { ++ list_add_tail(&new_ts->list, &tmp->list); ++ goto unlock; ++ } ++ } ++ ++add_tail: ++ list_add_tail(&new_ts->list, &sess->timestamps); ++unlock: ++ spin_unlock_irqrestore(&sess->ts_spinlock, flags); ++} ++EXPORT_SYMBOL_GPL(amvdec_add_ts_reorder); ++ ++void amvdec_remove_ts(struct amvdec_session *sess, u64 ts) ++{ ++ struct amvdec_timestamp *tmp; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&sess->ts_spinlock, flags); ++ list_for_each_entry(tmp, &sess->timestamps, list) { ++ if (tmp->ts == ts) { ++ list_del(&tmp->list); ++ kfree(tmp); ++ goto unlock; ++ } ++ } ++ dev_warn(sess->core->dev_dec, ++ "Couldn't remove buffer with timestamp %llu from list\n", ts); ++ ++unlock: ++ spin_unlock_irqrestore(&sess->ts_spinlock, flags); ++} ++EXPORT_SYMBOL_GPL(amvdec_remove_ts); ++ ++static void dst_buf_done(struct amvdec_session *sess, ++ struct vb2_v4l2_buffer *vbuf, ++ u32 field, ++ u64 timestamp) ++{ ++ struct device *dev = sess->core->dev_dec; ++ u32 output_size = amvdec_get_output_size(sess); ++ ++ switch (sess->pixfmt_cap) { ++ case V4L2_PIX_FMT_NV12M: ++ vbuf->vb2_buf.planes[0].bytesused = output_size; ++ vbuf->vb2_buf.planes[1].bytesused = output_size / 2; ++ break; ++ case V4L2_PIX_FMT_YUV420M: ++ vbuf->vb2_buf.planes[0].bytesused = output_size; ++ vbuf->vb2_buf.planes[1].bytesused = output_size / 4; ++ vbuf->vb2_buf.planes[2].bytesused = output_size / 4; ++ break; ++ } ++ ++ vbuf->vb2_buf.timestamp = timestamp; ++ vbuf->sequence = sess->sequence_cap++; ++ ++ if (sess->should_stop && ++ atomic_read(&sess->esparser_queued_bufs) <= 2) { ++ const struct v4l2_event ev = { .type = V4L2_EVENT_EOS }; ++ ++ dev_dbg(dev, "Signaling EOS\n"); ++ v4l2_event_queue_fh(&sess->fh, &ev); ++ vbuf->flags |= V4L2_BUF_FLAG_LAST; ++ } else if (sess->should_stop) ++ dev_dbg(dev, "should_stop, %u bufs remain\n", ++ atomic_read(&sess->esparser_queued_bufs)); ++ ++ dev_dbg(dev, "Buffer %u done\n", vbuf->vb2_buf.index); ++ vbuf->field = field; ++ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); ++ ++ /* Buffer done probably means the vififo got freed */ ++ schedule_work(&sess->esparser_queue_work); ++} ++ ++void amvdec_dst_buf_done(struct amvdec_session *sess, ++ struct vb2_v4l2_buffer *vbuf, u32 field) ++{ ++ struct device *dev = sess->core->dev_dec; ++ struct amvdec_timestamp *tmp; ++ struct list_head *timestamps = &sess->timestamps; ++ u64 timestamp; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&sess->ts_spinlock, flags); ++ if (list_empty(timestamps)) { ++ dev_err(dev, "Buffer %u done but list is empty\n", ++ vbuf->vb2_buf.index); ++ ++ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); ++ spin_unlock_irqrestore(&sess->ts_spinlock, flags); ++ return; ++ } ++ ++ tmp = list_first_entry(timestamps, struct amvdec_timestamp, list); ++ timestamp = tmp->ts; ++ list_del(&tmp->list); ++ kfree(tmp); ++ spin_unlock_irqrestore(&sess->ts_spinlock, flags); ++ ++ dst_buf_done(sess, vbuf, field, timestamp); ++ atomic_dec(&sess->esparser_queued_bufs); ++} ++EXPORT_SYMBOL_GPL(amvdec_dst_buf_done); ++ ++static void amvdec_dst_buf_done_offset(struct amvdec_session *sess, ++ struct vb2_v4l2_buffer *vbuf, ++ u32 offset, ++ u32 field) ++{ ++ struct device *dev = sess->core->dev_dec; ++ struct amvdec_timestamp *match = NULL; ++ struct amvdec_timestamp *tmp, *n; ++ u64 timestamp = 0; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&sess->ts_spinlock, flags); ++ ++ /* Look for our vififo offset to get the corresponding timestamp. */ ++ list_for_each_entry_safe(tmp, n, &sess->timestamps, list) { ++ s64 delta = (s64)offset - tmp->offset; ++ ++ /* Offsets reported by codecs usually differ slightly, ++ * so we need some wiggle room. ++ * 4KiB being the minimum packet size, there is no risk here. ++ */ ++ if (delta > (-1 * (s32)SZ_4K) && delta < SZ_4K) { ++ match = tmp; ++ break; ++ } ++ ++ /* Delete any timestamp entry that appears before our target ++ * (not all src packets/timestamps lead to a frame) ++ */ ++ if (delta > 0 || delta < -1 * (s32)sess->vififo_size) { ++ atomic_dec(&sess->esparser_queued_bufs); ++ list_del(&tmp->list); ++ kfree(tmp); ++ } ++ } ++ ++ if (!match) { ++ dev_dbg(dev, "Buffer %u done but can't match offset (%08X)\n", ++ vbuf->vb2_buf.index, offset); ++ } else { ++ timestamp = match->ts; ++ list_del(&match->list); ++ kfree(match); ++ } ++ spin_unlock_irqrestore(&sess->ts_spinlock, flags); ++ ++ dst_buf_done(sess, vbuf, field, timestamp); ++ if (match) ++ atomic_dec(&sess->esparser_queued_bufs); ++} ++ ++void amvdec_dst_buf_done_idx(struct amvdec_session *sess, ++ u32 buf_idx, u32 offset, u32 field) ++{ ++ struct vb2_v4l2_buffer *vbuf; ++ struct device *dev = sess->core->dev_dec; ++ ++ vbuf = v4l2_m2m_dst_buf_remove_by_idx(sess->m2m_ctx, buf_idx); ++ if (!vbuf) { ++ dev_err(dev, ++ "Buffer %u done but it doesn't exist in m2m_ctx\n", ++ buf_idx); ++ return; ++ } ++ ++ if (offset != -1) ++ amvdec_dst_buf_done_offset(sess, vbuf, offset, field); ++ else ++ amvdec_dst_buf_done(sess, vbuf, field); ++} ++EXPORT_SYMBOL_GPL(amvdec_dst_buf_done_idx); ++ ++void amvdec_set_par_from_dar(struct amvdec_session *sess, ++ u32 dar_num, u32 dar_den) ++{ ++ u32 div; ++ ++ sess->pixelaspect.numerator = sess->height * dar_num; ++ sess->pixelaspect.denominator = sess->width * dar_den; ++ div = gcd(sess->pixelaspect.numerator, sess->pixelaspect.denominator); ++ sess->pixelaspect.numerator /= div; ++ sess->pixelaspect.denominator /= div; ++} ++EXPORT_SYMBOL_GPL(amvdec_set_par_from_dar); ++ ++void amvdec_abort(struct amvdec_session *sess) ++{ ++ dev_info(sess->core->dev, "Aborting decoding session!\n"); ++ vb2_queue_error(&sess->m2m_ctx->cap_q_ctx.q); ++ vb2_queue_error(&sess->m2m_ctx->out_q_ctx.q); ++} ++EXPORT_SYMBOL_GPL(amvdec_abort); +diff --git a/drivers/media/platform/meson/vdec/vdec_helpers.h b/drivers/media/platform/meson/vdec/vdec_helpers.h +new file mode 100644 +index 000000000000..b9250a8157c4 +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/vdec_helpers.h +@@ -0,0 +1,48 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ */ ++ ++#ifndef __MESON_VDEC_HELPERS_H_ ++#define __MESON_VDEC_HELPERS_H_ ++ ++#include "vdec.h" ++ ++/** ++ * amvdec_set_canvases() - Map VB2 buffers to canvases ++ * ++ * @sess: current session ++ * @reg_base: Registry bases of where to write the canvas indexes ++ * @reg_num: number of contiguous registers after each reg_base (including it) ++ */ ++int amvdec_set_canvases(struct amvdec_session *sess, ++ u32 reg_base[], u32 reg_num[]); ++ ++u32 amvdec_read_dos(struct amvdec_core *core, u32 reg); ++void amvdec_write_dos(struct amvdec_core *core, u32 reg, u32 val); ++void amvdec_write_dos_bits(struct amvdec_core *core, u32 reg, u32 val); ++void amvdec_clear_dos_bits(struct amvdec_core *core, u32 reg, u32 val); ++u32 amvdec_read_parser(struct amvdec_core *core, u32 reg); ++void amvdec_write_parser(struct amvdec_core *core, u32 reg, u32 val); ++ ++void amvdec_dst_buf_done_idx(struct amvdec_session *sess, u32 buf_idx, ++ u32 offset, u32 field); ++void amvdec_dst_buf_done(struct amvdec_session *sess, ++ struct vb2_v4l2_buffer *vbuf, u32 field); ++ ++/** ++ * amvdec_add_ts_reorder() - Add a timestamp to the list in chronological order ++ * ++ * @sess: current session ++ * @ts: timestamp to add ++ * @offset: offset in the VIFIFO where the associated packet was written ++ */ ++void amvdec_add_ts_reorder(struct amvdec_session *sess, u64 ts, u32 offset); ++void amvdec_remove_ts(struct amvdec_session *sess, u64 ts); ++ ++void amvdec_set_par_from_dar(struct amvdec_session *sess, ++ u32 dar_num, u32 dar_den); ++ ++void amvdec_abort(struct amvdec_session *sess); ++#endif +diff --git a/drivers/media/platform/meson/vdec/vdec_platform.c b/drivers/media/platform/meson/vdec/vdec_platform.c +new file mode 100644 +index 000000000000..46eeb7426f54 +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/vdec_platform.c +@@ -0,0 +1,101 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ */ ++ ++#include "vdec_platform.h" ++#include "vdec.h" ++ ++#include "vdec_1.h" ++#include "codec_mpeg12.h" ++ ++static const struct amvdec_format vdec_formats_gxbb[] = { ++ { ++ .pixfmt = V4L2_PIX_FMT_MPEG1, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg12_ops, ++ .firmware_path = "meson/gx/vmpeg12_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { ++ .pixfmt = V4L2_PIX_FMT_MPEG2, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg12_ops, ++ .firmware_path = "meson/gx/vmpeg12_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, ++}; ++ ++static const struct amvdec_format vdec_formats_gxl[] = { ++ { ++ .pixfmt = V4L2_PIX_FMT_MPEG1, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg12_ops, ++ .firmware_path = "meson/gx/vmpeg12_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { ++ .pixfmt = V4L2_PIX_FMT_MPEG2, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg12_ops, ++ .firmware_path = "meson/gx/vmpeg12_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, ++}; ++ ++static const struct amvdec_format vdec_formats_gxm[] = { ++ { ++ .pixfmt = V4L2_PIX_FMT_MPEG1, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg12_ops, ++ .firmware_path = "meson/gx/vmpeg12_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { ++ .pixfmt = V4L2_PIX_FMT_MPEG2, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg12_ops, ++ .firmware_path = "meson/gx/vmpeg12_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, ++}; ++ ++const struct vdec_platform vdec_platform_gxbb = { ++ .formats = vdec_formats_gxbb, ++ .num_formats = ARRAY_SIZE(vdec_formats_gxbb), ++ .revision = VDEC_REVISION_GXBB, ++}; ++ ++const struct vdec_platform vdec_platform_gxl = { ++ .formats = vdec_formats_gxl, ++ .num_formats = ARRAY_SIZE(vdec_formats_gxl), ++ .revision = VDEC_REVISION_GXL, ++}; ++ ++const struct vdec_platform vdec_platform_gxm = { ++ .formats = vdec_formats_gxm, ++ .num_formats = ARRAY_SIZE(vdec_formats_gxm), ++ .revision = VDEC_REVISION_GXM, ++}; +diff --git a/drivers/media/platform/meson/vdec/vdec_platform.h b/drivers/media/platform/meson/vdec/vdec_platform.h +new file mode 100644 +index 000000000000..f6025326db1d +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/vdec_platform.h +@@ -0,0 +1,30 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright (C) 2018 BayLibre, SAS ++ * Author: Maxime Jourdan ++ */ ++ ++#ifndef __MESON_VDEC_PLATFORM_H_ ++#define __MESON_VDEC_PLATFORM_H_ ++ ++#include "vdec.h" ++ ++struct amvdec_format; ++ ++enum vdec_revision { ++ VDEC_REVISION_GXBB, ++ VDEC_REVISION_GXL, ++ VDEC_REVISION_GXM, ++}; ++ ++struct vdec_platform { ++ const struct amvdec_format *formats; ++ const u32 num_formats; ++ enum vdec_revision revision; ++}; ++ ++extern const struct vdec_platform vdec_platform_gxbb; ++extern const struct vdec_platform vdec_platform_gxm; ++extern const struct vdec_platform vdec_platform_gxl; ++ ++#endif +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0031-MAINTAINERS-Add-meson-video-decoder.patch b/buildroot-external/board/hardkernel/patches/linux/0031-MAINTAINERS-Add-meson-video-decoder.patch new file mode 100644 index 000000000..f243cd27b --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0031-MAINTAINERS-Add-meson-video-decoder.patch @@ -0,0 +1,34 @@ +From c8482bffc8ced44e3e22f403413b23c7b20af1be Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Tue, 4 Sep 2018 10:07:08 +0200 +Subject: [PATCH 31/53] MAINTAINERS: Add meson video decoder + +Add an entry for the meson video decoder for amlogic SoCs. + +Signed-off-by: Maxime Jourdan +--- + MAINTAINERS | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/MAINTAINERS b/MAINTAINERS +index 11a59e82d92e..d2c0c0f8b406 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -9526,6 +9526,14 @@ F: drivers/media/platform/meson/ao-cec.c + F: Documentation/devicetree/bindings/media/meson-ao-cec.txt + T: git git://linuxtv.org/media_tree.git + ++MESON VIDEO DECODER DRIVER FOR AMLOGIC SOCS ++M: Maxime Jourdan ++L: linux-media@lists.freedesktop.org ++L: linux-amlogic@lists.infradead.org ++S: Supported ++F: drivers/media/platform/meson/vdec/ ++T: git git://linuxtv.org/media_tree.git ++ + MICROBLAZE ARCHITECTURE + M: Michal Simek + W: http://www.monstr.eu/fdt/ +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0032-arm64-dts-meson-gx-add-vdec-entry.patch b/buildroot-external/board/hardkernel/patches/linux/0032-arm64-dts-meson-gx-add-vdec-entry.patch new file mode 100644 index 000000000..0e4dc0070 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0032-arm64-dts-meson-gx-add-vdec-entry.patch @@ -0,0 +1,40 @@ +From 91e2dc23af4fb673e609a4fddf2b813ea3f833b8 Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Wed, 29 Aug 2018 15:24:02 +0200 +Subject: [PATCH 32/53] arm64: dts: meson-gx: add vdec entry + +Add the video decoder dts entry + +Signed-off-by: Maxime Jourdan +--- + arch/arm64/boot/dts/amlogic/meson-gx.dtsi | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi +index 5012607c95d2..5d2820ef9a88 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi +@@ -445,6 +445,20 @@ + }; + }; + ++ vdec: video-decoder@c8820000 { ++ compatible = "amlogic,gx-vdec"; ++ reg = <0x0 0xc8820000 0x0 0x10000>, ++ <0x0 0xc110a580 0x0 0xe4>; ++ reg-names = "dos", "esparser"; ++ ++ interrupts = , ++ ; ++ interrupt-names = "vdec", "esparser"; ++ ++ amlogic,ao-sysctrl = <&sysctrl_AO>; ++ amlogic,canvas = <&canvas>; ++ }; ++ + periphs: periphs@c8834000 { + compatible = "simple-bus"; + reg = <0x0 0xc8834000 0x0 0x2000>; +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0033-arm64-dts-meson-add-vdec-entries.patch b/buildroot-external/board/hardkernel/patches/linux/0033-arm64-dts-meson-add-vdec-entries.patch new file mode 100644 index 000000000..06f40c051 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0033-arm64-dts-meson-add-vdec-entries.patch @@ -0,0 +1,65 @@ +From f9f1d0b0b197f94502ea2a13a27d6d4534f8150f Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Wed, 29 Aug 2018 15:24:22 +0200 +Subject: [PATCH 33/53] arm64: dts: meson: add vdec entries + +This enables the video decoder for gxbb, gxl and gxm chips + +Signed-off-by: Maxime Jourdan +--- + arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi | 10 ++++++++++ + arch/arm64/boot/dts/amlogic/meson-gxl.dtsi | 10 ++++++++++ + arch/arm64/boot/dts/amlogic/meson-gxm.dtsi | 4 ++++ + 3 files changed, 24 insertions(+) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi +index 2a4d506bad4e..96145e49ea44 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi +@@ -814,3 +814,13 @@ + power-domains = <&pwrc_vpu>; + }; + ++&vdec { ++ compatible = "amlogic,gxbb-vdec"; ++ clocks = <&clkc CLKID_DOS_PARSER>, ++ <&clkc CLKID_DOS>, ++ <&clkc CLKID_VDEC_1>, ++ <&clkc CLKID_VDEC_HEVC>; ++ clock-names = "dos_parser", "dos", "vdec_1", "vdec_hevc"; ++ resets = <&reset RESET_PARSER>; ++ reset-names = "esparser"; ++}; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi +index 9f4b6185a61d..6ca93ae1e496 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi +@@ -814,3 +814,13 @@ + power-domains = <&pwrc_vpu>; + }; + ++&vdec { ++ compatible = "amlogic,gxl-vdec"; ++ clocks = <&clkc CLKID_DOS_PARSER>, ++ <&clkc CLKID_DOS>, ++ <&clkc CLKID_VDEC_1>, ++ <&clkc CLKID_VDEC_HEVC>; ++ clock-names = "dos_parser", "dos", "vdec_1", "vdec_hevc"; ++ resets = <&reset RESET_PARSER>; ++ reset-names = "esparser"; ++}; +diff --git a/arch/arm64/boot/dts/amlogic/meson-gxm.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxm.dtsi +index 247888d68a3a..2f356495be5e 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gxm.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gxm.dtsi +@@ -117,3 +117,7 @@ + &dwc3 { + phys = <&usb3_phy>, <&usb2_phy0>, <&usb2_phy1>, <&usb2_phy2>; + }; ++ ++&vdec { ++ compatible = "amlogic,gxm-vdec"; ++}; +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0034-meson-vdec-introduce-controls-and-V4L2_CID_MIN_BUFFE.patch b/buildroot-external/board/hardkernel/patches/linux/0034-meson-vdec-introduce-controls-and-V4L2_CID_MIN_BUFFE.patch new file mode 100644 index 000000000..d9ec6cdb7 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0034-meson-vdec-introduce-controls-and-V4L2_CID_MIN_BUFFE.patch @@ -0,0 +1,156 @@ +From c5ad2d518874fe080e249c2a11497064c28d9b1b Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Wed, 10 Oct 2018 17:22:27 +0200 +Subject: [PATCH 34/53] meson: vdec: introduce controls and + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE + +--- + drivers/media/platform/meson/vdec/Makefile | 2 +- + drivers/media/platform/meson/vdec/vdec.c | 7 +++ + drivers/media/platform/meson/vdec/vdec.h | 2 + + .../media/platform/meson/vdec/vdec_ctrls.c | 45 +++++++++++++++++++ + .../media/platform/meson/vdec/vdec_ctrls.h | 8 ++++ + 5 files changed, 63 insertions(+), 1 deletion(-) + create mode 100644 drivers/media/platform/meson/vdec/vdec_ctrls.c + create mode 100644 drivers/media/platform/meson/vdec/vdec_ctrls.h + +diff --git a/drivers/media/platform/meson/vdec/Makefile b/drivers/media/platform/meson/vdec/Makefile +index 6bea129084b7..eba86083aadb 100644 +--- a/drivers/media/platform/meson/vdec/Makefile ++++ b/drivers/media/platform/meson/vdec/Makefile +@@ -1,7 +1,7 @@ + # SPDX-License-Identifier: GPL-2.0 + # Makefile for Amlogic meson video decoder driver + +-meson-vdec-objs = esparser.o vdec.o vdec_helpers.o vdec_platform.o ++meson-vdec-objs = esparser.o vdec.o vdec_ctrls.o vdec_helpers.o vdec_platform.o + meson-vdec-objs += vdec_1.o + meson-vdec-objs += codec_mpeg12.o + +diff --git a/drivers/media/platform/meson/vdec/vdec.c b/drivers/media/platform/meson/vdec/vdec.c +index d8db52c01fbe..1c5d3e912bee 100644 +--- a/drivers/media/platform/meson/vdec/vdec.c ++++ b/drivers/media/platform/meson/vdec/vdec.c +@@ -21,6 +21,7 @@ + #include "vdec.h" + #include "esparser.h" + #include "vdec_helpers.h" ++#include "vdec_ctrls.h" + + struct dummy_buf { + struct vb2_v4l2_buffer vb; +@@ -290,6 +291,7 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) + sess->keyframe_found = 0; + sess->last_offset = 0; + sess->wrap_count = 0; ++ sess->dpb_size = 0; + sess->pixelaspect.numerator = 1; + sess->pixelaspect.denominator = 1; + atomic_set(&sess->esparser_queued_bufs, 0); +@@ -812,6 +814,10 @@ static int vdec_open(struct file *file) + goto err_m2m_release; + } + ++ ret = amvdec_init_ctrls(&sess->ctrl_handler); ++ if (ret) ++ goto err_m2m_release; ++ + sess->pixfmt_cap = formats[0].pixfmts_cap[0]; + sess->fmt_out = &formats[0]; + sess->width = 1280; +@@ -827,6 +833,7 @@ static int vdec_open(struct file *file) + spin_lock_init(&sess->ts_spinlock); + + v4l2_fh_init(&sess->fh, core->vdev_dec); ++ sess->fh.ctrl_handler = &sess->ctrl_handler; + v4l2_fh_add(&sess->fh); + sess->fh.m2m_ctx = sess->m2m_ctx; + file->private_data = &sess->fh; +diff --git a/drivers/media/platform/meson/vdec/vdec.h b/drivers/media/platform/meson/vdec/vdec.h +index 4e8c3f1742ac..6be7de2849b6 100644 +--- a/drivers/media/platform/meson/vdec/vdec.h ++++ b/drivers/media/platform/meson/vdec/vdec.h +@@ -203,6 +203,7 @@ struct amvdec_session { + struct v4l2_fh fh; + struct v4l2_m2m_dev *m2m_dev; + struct v4l2_m2m_ctx *m2m_ctx; ++ struct v4l2_ctrl_handler ctrl_handler; + struct mutex lock; + + const struct amvdec_format *fmt_out; +@@ -242,6 +243,7 @@ struct amvdec_session { + u64 last_irq_jiffies; + u32 last_offset; + u32 wrap_count; ++ u32 dpb_size; + + void *priv; + }; +diff --git a/drivers/media/platform/meson/vdec/vdec_ctrls.c b/drivers/media/platform/meson/vdec/vdec_ctrls.c +new file mode 100644 +index 000000000000..cd6dd6d87172 +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/vdec_ctrls.c +@@ -0,0 +1,45 @@ ++#include "vdec_ctrls.h" ++ ++static int vdec_op_g_volatile_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct amvdec_session *sess = ++ container_of(ctrl->handler, struct amvdec_session, ctrl_handler); ++ ++ switch (ctrl->id) { ++ case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: ++ ctrl->val = sess->dpb_size; ++ break; ++ default: ++ return -EINVAL; ++ }; ++ ++ return 0; ++} ++ ++static const struct v4l2_ctrl_ops vdec_ctrl_ops = { ++ .g_volatile_ctrl = vdec_op_g_volatile_ctrl, ++}; ++ ++int amvdec_init_ctrls(struct v4l2_ctrl_handler *ctrl_handler) ++{ ++ int ret; ++ struct v4l2_ctrl *ctrl; ++ ++ ret = v4l2_ctrl_handler_init(ctrl_handler, 1); ++ if (ret) ++ return ret; ++ ++ ctrl = v4l2_ctrl_new_std(ctrl_handler, &vdec_ctrl_ops, ++ V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 32, 1, 1); ++ if (ctrl) ++ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; ++ ++ ret = ctrl_handler->error; ++ if (ret) { ++ v4l2_ctrl_handler_free(ctrl_handler); ++ return ret; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(amvdec_init_ctrls); +diff --git a/drivers/media/platform/meson/vdec/vdec_ctrls.h b/drivers/media/platform/meson/vdec/vdec_ctrls.h +new file mode 100644 +index 000000000000..4bcc5e68865c +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/vdec_ctrls.h +@@ -0,0 +1,8 @@ ++#ifndef __MESON_VDEC_CTRLS_H_ ++#define __MESON_VDEC_CTRLS_H_ ++ ++#include "vdec.h" ++ ++int amvdec_init_ctrls(struct v4l2_ctrl_handler *ctrl_handler); ++ ++#endif +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0035-media-videodev2-add-V4L2_FMT_FLAG_NO_SOURCE_CHANGE.patch b/buildroot-external/board/hardkernel/patches/linux/0035-media-videodev2-add-V4L2_FMT_FLAG_NO_SOURCE_CHANGE.patch new file mode 100644 index 000000000..fbda82ca1 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0035-media-videodev2-add-V4L2_FMT_FLAG_NO_SOURCE_CHANGE.patch @@ -0,0 +1,51 @@ +From 2688d1304cbb41d4e2c8514ec2bdcd2b48f2f88a Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Thu, 4 Oct 2018 15:37:39 +0200 +Subject: [PATCH 35/53] media: videodev2: add V4L2_FMT_FLAG_NO_SOURCE_CHANGE + +When a v4l2 driver exposes V4L2_EVENT_SOURCE_CHANGE, some (usually +OUTPUT) formats may not be able to trigger this event. + +Add a enum_fmt format flag to tag those specific formats. + +Signed-off-by: Maxime Jourdan +--- + Documentation/media/uapi/v4l/vidioc-enum-fmt.rst | 5 +++++ + include/uapi/linux/videodev2.h | 5 +++-- + 2 files changed, 8 insertions(+), 2 deletions(-) + +diff --git a/Documentation/media/uapi/v4l/vidioc-enum-fmt.rst b/Documentation/media/uapi/v4l/vidioc-enum-fmt.rst +index 019c513df217..e0040b36ac43 100644 +--- a/Documentation/media/uapi/v4l/vidioc-enum-fmt.rst ++++ b/Documentation/media/uapi/v4l/vidioc-enum-fmt.rst +@@ -116,6 +116,11 @@ one until ``EINVAL`` is returned. + - This format is not native to the device but emulated through + software (usually libv4l2), where possible try to use a native + format instead for better performance. ++ * - ``V4L2_FMT_FLAG_NO_SOURCE_CHANGE`` ++ - 0x0004 ++ - The event ``V4L2_EVENT_SOURCE_CHANGE`` is not supported ++ for this format. ++ + + + Return Value +diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h +index 1aae2e4b8f10..f44bdef62d0b 100644 +--- a/include/uapi/linux/videodev2.h ++++ b/include/uapi/linux/videodev2.h +@@ -733,8 +733,9 @@ struct v4l2_fmtdesc { + __u32 reserved[4]; + }; + +-#define V4L2_FMT_FLAG_COMPRESSED 0x0001 +-#define V4L2_FMT_FLAG_EMULATED 0x0002 ++#define V4L2_FMT_FLAG_COMPRESSED 0x0001 ++#define V4L2_FMT_FLAG_EMULATED 0x0002 ++#define V4L2_FMT_FLAG_NO_SOURCE_CHANGE 0x0004 + + /* Frame Size and frame rate enumeration */ + /* +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0036-meson-vdec-allow-subscribing-to-V4L2_EVENT_SOURCE_CH.patch b/buildroot-external/board/hardkernel/patches/linux/0036-meson-vdec-allow-subscribing-to-V4L2_EVENT_SOURCE_CH.patch new file mode 100644 index 000000000..69d7299f6 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0036-meson-vdec-allow-subscribing-to-V4L2_EVENT_SOURCE_CH.patch @@ -0,0 +1,273 @@ +From b1d313fb821e3a4196bb32c86c585b8be23d491a Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Wed, 10 Oct 2018 15:44:56 +0200 +Subject: [PATCH 36/53] meson: vdec: allow subscribing to + V4L2_EVENT_SOURCE_CHANGE + +Flag MPEG1/MPEG2 as NO_SOURCE_CHANGE. +--- + drivers/media/platform/meson/vdec/vdec.c | 20 +++++++++++-- + drivers/media/platform/meson/vdec/vdec.h | 13 +++++++++ + .../media/platform/meson/vdec/vdec_helpers.c | 28 +++++++++++++++++++ + .../media/platform/meson/vdec/vdec_helpers.h | 1 + + .../media/platform/meson/vdec/vdec_platform.c | 6 ++++ + 5 files changed, 66 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/platform/meson/vdec/vdec.c b/drivers/media/platform/meson/vdec/vdec.c +index 1c5d3e912bee..ca6404546eb8 100644 +--- a/drivers/media/platform/meson/vdec/vdec.c ++++ b/drivers/media/platform/meson/vdec/vdec.c +@@ -230,7 +230,8 @@ static int vdec_queue_setup(struct vb2_queue *q, + * are free to choose any of them to write frames to. As such, + * we need all of them to be queued into the driver + */ +- q->min_buffers_needed = q->num_buffers + *num_buffers; ++ sess->num_dst_bufs = q->num_buffers + *num_buffers; ++ q->min_buffers_needed = sess->num_dst_bufs; + break; + default: + return -EINVAL; +@@ -260,6 +261,7 @@ static void vdec_vb2_buf_queue(struct vb2_buffer *vb) + static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) + { + struct amvdec_session *sess = vb2_get_drv_priv(q); ++ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + struct amvdec_core *core = sess->core; + struct vb2_v4l2_buffer *buf; + int ret; +@@ -277,6 +279,13 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) + if (!sess->streamon_out || !sess->streamon_cap) + return 0; + ++ if (sess->status == STATUS_NEEDS_RESUME && ++ q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { ++ codec_ops->resume(sess); ++ sess->status = STATUS_RUNNING; ++ return 0; ++ } ++ + sess->vififo_size = SIZE_VIFIFO; + sess->vififo_vaddr = + dma_alloc_coherent(sess->core->dev, sess->vififo_size, +@@ -305,6 +314,7 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) + sess->recycle_thread = kthread_run(vdec_recycle_thread, sess, + "vdec_recycle"); + ++ sess->status = STATUS_RUNNING; + core->cur_sess = sess; + + return 0; +@@ -362,7 +372,9 @@ static void vdec_stop_streaming(struct vb2_queue *q) + struct amvdec_core *core = sess->core; + struct vb2_v4l2_buffer *buf; + +- if (sess->streamon_out && sess->streamon_cap) { ++ if (sess->status == STATUS_RUNNING || ++ (sess->status == STATUS_NEEDS_RESUME && ++ (!sess->streamon_out || !sess->streamon_cap))) { + if (vdec_codec_needs_recycle(sess)) + kthread_stop(sess->recycle_thread); + +@@ -375,6 +387,7 @@ static void vdec_stop_streaming(struct vb2_queue *q) + kfree(sess->priv); + sess->priv = NULL; + core->cur_sess = NULL; ++ sess->status = STATUS_STOPPED; + } + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { +@@ -611,6 +624,7 @@ static int vdec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) + + fmt_out = &platform->formats[f->index]; + f->pixelformat = fmt_out->pixfmt; ++ f->flags = fmt_out->flags; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + fmt_out = sess->fmt_out; + if (f->index >= 4 || !fmt_out->pixfmts_cap[f->index]) +@@ -703,6 +717,8 @@ static int vdec_subscribe_event(struct v4l2_fh *fh, + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 2, NULL); ++ case V4L2_EVENT_SOURCE_CHANGE: ++ return v4l2_src_change_event_subscribe(fh, sub); + default: + return -EINVAL; + } +diff --git a/drivers/media/platform/meson/vdec/vdec.h b/drivers/media/platform/meson/vdec/vdec.h +index 6be7de2849b6..8f8ce629c698 100644 +--- a/drivers/media/platform/meson/vdec/vdec.h ++++ b/drivers/media/platform/meson/vdec/vdec.h +@@ -101,6 +101,7 @@ struct amvdec_ops { + u32 (*vififo_level)(struct amvdec_session *sess); + }; + ++ + /** + * struct amvdec_codec_ops - codec operations + * +@@ -127,6 +128,7 @@ struct amvdec_codec_ops { + int (*can_recycle)(struct amvdec_core *core); + void (*recycle)(struct amvdec_core *core, u32 buf_idx); + void (*drain)(struct amvdec_session *sess); ++ void (*resume)(struct amvdec_session *sess); + const u8 * (*eos_sequence)(u32 *len); + irqreturn_t (*isr)(struct amvdec_session *sess); + irqreturn_t (*threaded_isr)(struct amvdec_session *sess); +@@ -140,6 +142,7 @@ struct amvdec_codec_ops { + * @max_buffers: maximum amount of CAPTURE (dst) buffers + * @max_width: maximum picture width supported + * @max_height: maximum picture height supported ++ * @flags: enum flags associated with this pixfmt + * @vdec_ops: the VDEC operations that support this format + * @codec_ops: the codec operations that support this format + * @firmware_path: Path to the firmware that supports this format +@@ -151,6 +154,7 @@ struct amvdec_format { + u32 max_buffers; + u32 max_width; + u32 max_height; ++ u32 flags; + + struct amvdec_ops *vdec_ops; + struct amvdec_codec_ops *codec_ops; +@@ -159,6 +163,12 @@ struct amvdec_format { + u32 pixfmts_cap[4]; + }; + ++enum amvdec_status { ++ STATUS_STOPPED, ++ STATUS_RUNNING, ++ STATUS_NEEDS_RESUME, ++}; ++ + /** + * struct amvdec_session - decoding session parameters + * +@@ -195,6 +205,7 @@ struct amvdec_format { + * @timestamps: chronological list of src timestamps + * @ts_spinlock: spinlock for the timestamps list + * @last_irq_jiffies: tracks last time the vdec triggered an IRQ ++ * @status: current decoding status + * @priv: codec private data + */ + struct amvdec_session { +@@ -225,6 +236,7 @@ struct amvdec_session { + unsigned int sequence_cap; + unsigned int should_stop; + unsigned int keyframe_found; ++ unsigned int num_dst_bufs; + + u8 canvas_alloc[MAX_CANVAS]; + u32 canvas_num; +@@ -245,6 +257,7 @@ struct amvdec_session { + u32 wrap_count; + u32 dpb_size; + ++ enum amvdec_status status; + void *priv; + }; + +diff --git a/drivers/media/platform/meson/vdec/vdec_helpers.c b/drivers/media/platform/meson/vdec/vdec_helpers.c +index 02090c5b089e..b982b2869fd2 100644 +--- a/drivers/media/platform/meson/vdec/vdec_helpers.c ++++ b/drivers/media/platform/meson/vdec/vdec_helpers.c +@@ -403,6 +403,34 @@ void amvdec_set_par_from_dar(struct amvdec_session *sess, + } + EXPORT_SYMBOL_GPL(amvdec_set_par_from_dar); + ++void amvdec_src_change(struct amvdec_session *sess, u32 width, u32 height, u32 dpb_size) ++{ ++ static const struct v4l2_event ev = { ++ .type = V4L2_EVENT_SOURCE_CHANGE, ++ .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION }; ++ ++ sess->dpb_size = dpb_size; ++ ++ /* Check if the capture queue is already configured well for our ++ * usecase. If so, keep decoding with it and do not send the event ++ */ ++ if (sess->width == width && ++ sess->height == height && ++ dpb_size <= sess->num_dst_bufs) { ++ sess->fmt_out->codec_ops->resume(sess); ++ return; ++ } ++ ++ sess->width = width; ++ sess->height = height; ++ sess->status = STATUS_NEEDS_RESUME; ++ ++ dev_dbg(sess->core->dev, "Res. changed (%ux%u), DPB size %u\n", ++ width, height, dpb_size); ++ v4l2_event_queue_fh(&sess->fh, &ev); ++} ++EXPORT_SYMBOL_GPL(amvdec_src_change); ++ + void amvdec_abort(struct amvdec_session *sess) + { + dev_info(sess->core->dev, "Aborting decoding session!\n"); +diff --git a/drivers/media/platform/meson/vdec/vdec_helpers.h b/drivers/media/platform/meson/vdec/vdec_helpers.h +index b9250a8157c4..060799b5e4d4 100644 +--- a/drivers/media/platform/meson/vdec/vdec_helpers.h ++++ b/drivers/media/platform/meson/vdec/vdec_helpers.h +@@ -44,5 +44,6 @@ void amvdec_remove_ts(struct amvdec_session *sess, u64 ts); + void amvdec_set_par_from_dar(struct amvdec_session *sess, + u32 dar_num, u32 dar_den); + ++void amvdec_src_change(struct amvdec_session *sess, u32 width, u32 height, u32 dpb_size); + void amvdec_abort(struct amvdec_session *sess); + #endif +diff --git a/drivers/media/platform/meson/vdec/vdec_platform.c b/drivers/media/platform/meson/vdec/vdec_platform.c +index 46eeb7426f54..291f1eeb27d9 100644 +--- a/drivers/media/platform/meson/vdec/vdec_platform.c ++++ b/drivers/media/platform/meson/vdec/vdec_platform.c +@@ -17,6 +17,7 @@ static const struct amvdec_format vdec_formats_gxbb[] = { + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, ++ .flags = V4L2_FMT_FLAG_NO_SOURCE_CHANGE, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/gx/vmpeg12_mc", +@@ -27,6 +28,7 @@ static const struct amvdec_format vdec_formats_gxbb[] = { + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, ++ .flags = V4L2_FMT_FLAG_NO_SOURCE_CHANGE, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/gx/vmpeg12_mc", +@@ -41,6 +43,7 @@ static const struct amvdec_format vdec_formats_gxl[] = { + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, ++ .flags = V4L2_FMT_FLAG_NO_SOURCE_CHANGE, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/gx/vmpeg12_mc", +@@ -51,6 +54,7 @@ static const struct amvdec_format vdec_formats_gxl[] = { + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, ++ .flags = V4L2_FMT_FLAG_NO_SOURCE_CHANGE, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/gx/vmpeg12_mc", +@@ -65,6 +69,7 @@ static const struct amvdec_format vdec_formats_gxm[] = { + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, ++ .flags = V4L2_FMT_FLAG_NO_SOURCE_CHANGE, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/gx/vmpeg12_mc", +@@ -75,6 +80,7 @@ static const struct amvdec_format vdec_formats_gxm[] = { + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, ++ .flags = V4L2_FMT_FLAG_NO_SOURCE_CHANGE, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/gx/vmpeg12_mc", +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0037-media-meson-vdec-add-H.264-decoding-support.patch b/buildroot-external/board/hardkernel/patches/linux/0037-media-meson-vdec-add-H.264-decoding-support.patch new file mode 100644 index 000000000..ea4486e36 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0037-media-meson-vdec-add-H.264-decoding-support.patch @@ -0,0 +1,593 @@ +From fecc45672255e63d4e99b9eaf24ac00083a5d4b6 Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Wed, 29 Aug 2018 15:42:56 +0200 +Subject: [PATCH 37/53] media: meson: vdec: add H.264 decoding support + +Add support for V4L2_PIX_FMT_H264 +--- + drivers/media/platform/meson/vdec/Makefile | 2 +- + .../media/platform/meson/vdec/codec_h264.c | 478 ++++++++++++++++++ + .../media/platform/meson/vdec/codec_h264.h | 13 + + .../media/platform/meson/vdec/vdec_platform.c | 31 ++ + 4 files changed, 523 insertions(+), 1 deletion(-) + create mode 100644 drivers/media/platform/meson/vdec/codec_h264.c + create mode 100644 drivers/media/platform/meson/vdec/codec_h264.h + +diff --git a/drivers/media/platform/meson/vdec/Makefile b/drivers/media/platform/meson/vdec/Makefile +index eba86083aadb..01dc9603abdd 100644 +--- a/drivers/media/platform/meson/vdec/Makefile ++++ b/drivers/media/platform/meson/vdec/Makefile +@@ -3,6 +3,6 @@ + + meson-vdec-objs = esparser.o vdec.o vdec_ctrls.o vdec_helpers.o vdec_platform.o + meson-vdec-objs += vdec_1.o +-meson-vdec-objs += codec_mpeg12.o ++meson-vdec-objs += codec_mpeg12.o codec_h264.o + + obj-$(CONFIG_VIDEO_MESON_VDEC) += meson-vdec.o +diff --git a/drivers/media/platform/meson/vdec/codec_h264.c b/drivers/media/platform/meson/vdec/codec_h264.c +new file mode 100644 +index 000000000000..6ac0115afaa3 +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/codec_h264.c +@@ -0,0 +1,478 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2018 Maxime Jourdan ++ */ ++ ++#include ++#include ++ ++#include "vdec_helpers.h" ++#include "dos_regs.h" ++ ++#define SIZE_EXT_FW (20 * SZ_1K) ++#define SIZE_WORKSPACE 0x1ee000 ++#define SIZE_SEI (8 * SZ_1K) ++ ++/* Offset added by the firmware which must be substracted ++ * from the workspace phyaddr ++ */ ++#define WORKSPACE_BUF_OFFSET 0x1000000 ++ ++/* ISR status */ ++#define CMD_MASK GENMASK(7, 0) ++#define CMD_SRC_CHANGE 1 ++#define CMD_FRAMES_READY 2 ++#define CMD_FATAL_ERROR 6 ++#define CMD_BAD_WIDTH 7 ++#define CMD_BAD_HEIGHT 8 ++ ++#define SEI_DATA_READY BIT(15) ++ ++/* Picture type */ ++#define PIC_TOP_BOT 5 ++#define PIC_BOT_TOP 6 ++ ++/* Size of Motion Vector per macroblock */ ++#define MB_MV_SIZE 96 ++ ++/* Frame status data */ ++#define PIC_STRUCT_BIT 5 ++#define PIC_STRUCT_MASK GENMASK(2, 0) ++#define BUF_IDX_MASK GENMASK(4, 0) ++#define ERROR_FLAG BIT(9) ++#define OFFSET_BIT 16 ++#define OFFSET_MASK GENMASK(15, 0) ++ ++/* Bitstream parsed data */ ++#define MB_TOTAL_BIT 8 ++#define MB_TOTAL_MASK GENMASK(15, 0) ++#define MB_WIDTH_MASK GENMASK(7, 0) ++#define MAX_REF_BIT 24 ++#define MAX_REF_MASK GENMASK(6, 0) ++#define AR_IDC_BIT 16 ++#define AR_IDC_MASK GENMASK(7, 0) ++#define AR_PRESENT_FLAG BIT(0) ++#define AR_EXTEND 0xff ++ ++/* Buffer to send to the ESPARSER to signal End Of Stream for H.264. ++ * This is a 16x16 encoded picture that will trigger drain firmware-side. ++ * There is no known alternative. ++ */ ++static const u8 eos_sequence[SZ_1K] = { ++ 0x00, 0x00, 0x00, 0x01, 0x06, 0x05, 0xff, 0xe4, 0xdc, 0x45, 0xe9, 0xbd, ++ 0xe6, 0xd9, 0x48, 0xb7, 0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef, ++ 0x78, 0x32, 0x36, 0x34, 0x20, 0x2d, 0x20, 0x63, 0x6f, 0x72, 0x65, 0x20, ++ 0x36, 0x37, 0x20, 0x72, 0x31, 0x31, 0x33, 0x30, 0x20, 0x38, 0x34, 0x37, ++ 0x35, 0x39, 0x37, 0x37, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x34, ++ 0x2f, 0x4d, 0x50, 0x45, 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56, 0x43, 0x20, ++ 0x63, 0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79, ++ 0x6c, 0x65, 0x66, 0x74, 0x20, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x30, ++ 0x30, 0x39, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, ++ 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c, 0x61, 0x6e, ++ 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x78, 0x32, 0x36, 0x34, 0x2e, 0x68, 0x74, ++ 0x6d, 0x6c, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, ++ 0x3a, 0x20, 0x63, 0x61, 0x62, 0x61, 0x63, 0x3d, 0x31, 0x20, 0x72, 0x65, ++ 0x66, 0x3d, 0x31, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d, ++ 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, ++ 0x65, 0x3d, 0x30, 0x78, 0x31, 0x3a, 0x30, 0x78, 0x31, 0x31, 0x31, 0x20, ++ 0x6d, 0x65, 0x3d, 0x68, 0x65, 0x78, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, ++ 0x3d, 0x36, 0x20, 0x70, 0x73, 0x79, 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e, ++ 0x30, 0x3a, 0x30, 0x2e, 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, ++ 0x72, 0x65, 0x66, 0x3d, 0x30, 0x20, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, ++ 0x67, 0x65, 0x3d, 0x31, 0x36, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, ++ 0x5f, 0x6d, 0x65, 0x3d, 0x31, 0x20, 0x74, 0x72, 0x65, 0x6c, 0x6c, 0x69, ++ 0x73, 0x3d, 0x30, 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d, 0x30, ++ 0x20, 0x63, 0x71, 0x6d, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x61, 0x64, 0x7a, ++ 0x6f, 0x6e, 0x65, 0x3d, 0x32, 0x31, 0x2c, 0x31, 0x31, 0x20, 0x63, 0x68, ++ 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x71, 0x70, 0x5f, 0x6f, 0x66, 0x66, 0x73, ++ 0x65, 0x74, 0x3d, 0x2d, 0x32, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, ++ 0x73, 0x3d, 0x31, 0x20, 0x6e, 0x72, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x63, ++ 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20, 0x6d, 0x62, 0x61, 0x66, ++ 0x66, 0x3d, 0x30, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, ++ 0x30, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x30, ++ 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x3d, ++ 0x32, 0x35, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d, ++ 0x34, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x61, 0x62, 0x72, 0x20, 0x62, 0x69, ++ 0x74, 0x72, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x30, 0x20, 0x72, 0x61, 0x74, ++ 0x65, 0x74, 0x6f, 0x6c, 0x3d, 0x31, 0x2e, 0x30, 0x20, 0x71, 0x63, 0x6f, ++ 0x6d, 0x70, 0x3d, 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x69, ++ 0x6e, 0x3d, 0x31, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x35, ++ 0x31, 0x20, 0x71, 0x70, 0x73, 0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x69, ++ 0x70, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30, ++ 0x20, 0x61, 0x71, 0x3d, 0x31, 0x3a, 0x31, 0x2e, 0x30, 0x30, 0x00, 0x80, ++ 0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x40, 0x0a, 0x9a, 0x74, 0xf4, 0x20, ++ 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x06, 0x51, 0xe2, 0x44, 0xd4, ++ 0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x32, 0xc8, 0x00, 0x00, 0x00, 0x01, ++ 0x65, 0x88, 0x80, 0x20, 0x00, 0x08, 0x7f, 0xea, 0x6a, 0xe2, 0x99, 0xb6, ++ 0x57, 0xae, 0x49, 0x30, 0xf5, 0xfe, 0x5e, 0x46, 0x0b, 0x72, 0x44, 0xc4, ++ 0xe1, 0xfc, 0x62, 0xda, 0xf1, 0xfb, 0xa2, 0xdb, 0xd6, 0xbe, 0x5c, 0xd7, ++ 0x24, 0xa3, 0xf5, 0xb9, 0x2f, 0x57, 0x16, 0x49, 0x75, 0x47, 0x77, 0x09, ++ 0x5c, 0xa1, 0xb4, 0xc3, 0x4f, 0x60, 0x2b, 0xb0, 0x0c, 0xc8, 0xd6, 0x66, ++ 0xba, 0x9b, 0x82, 0x29, 0x33, 0x92, 0x26, 0x99, 0x31, 0x1c, 0x7f, 0x9b ++}; ++ ++static const u8 *codec_h264_eos_sequence(u32 *len) ++{ ++ *len = ARRAY_SIZE(eos_sequence); ++ return eos_sequence; ++} ++ ++struct codec_h264 { ++ /* H.264 decoder requires an extended firmware */ ++ void *ext_fw_vaddr; ++ dma_addr_t ext_fw_paddr; ++ ++ /* Buffer for the H.264 Workspace */ ++ void *workspace_vaddr; ++ dma_addr_t workspace_paddr; ++ ++ /* Buffer for the H.264 references MV */ ++ void *ref_vaddr; ++ dma_addr_t ref_paddr; ++ u32 ref_size; ++ ++ /* Buffer for parsed SEI data */ ++ void *sei_vaddr; ++ dma_addr_t sei_paddr; ++ ++ u32 mb_width; ++ u32 mb_height; ++ u32 max_refs; ++}; ++ ++static int codec_h264_can_recycle(struct amvdec_core *core) ++{ ++ return !amvdec_read_dos(core, AV_SCRATCH_7) || ++ !amvdec_read_dos(core, AV_SCRATCH_8); ++} ++ ++static void codec_h264_recycle(struct amvdec_core *core, u32 buf_idx) ++{ ++ /* Tell the decoder he can recycle this buffer. ++ * AV_SCRATCH_8 serves the same purpose. ++ */ ++ if (!amvdec_read_dos(core, AV_SCRATCH_7)) ++ amvdec_write_dos(core, AV_SCRATCH_7, buf_idx + 1); ++ else ++ amvdec_write_dos(core, AV_SCRATCH_8, buf_idx + 1); ++} ++ ++static int codec_h264_start(struct amvdec_session *sess) { ++ u32 workspace_offset; ++ struct amvdec_core *core = sess->core; ++ struct codec_h264 *h264 = sess->priv; ++ ++ /* Allocate some memory for the H.264 decoder's state */ ++ h264->workspace_vaddr = dma_alloc_coherent(core->dev, SIZE_WORKSPACE, ++ &h264->workspace_paddr, GFP_KERNEL); ++ if (!h264->workspace_vaddr) { ++ dev_err(core->dev, "Failed to alloc H.264 Workspace\n"); ++ return -ENOMEM; ++ } ++ ++ /* Allocate some memory for the H.264 SEI dump */ ++ h264->sei_vaddr = dma_alloc_coherent(core->dev, SIZE_SEI, ++ &h264->sei_paddr, GFP_KERNEL); ++ if (!h264->sei_vaddr) { ++ dev_err(core->dev, "Failed to alloc H.264 SEI\n"); ++ return -ENOMEM; ++ } ++ ++ amvdec_write_dos_bits(core, POWER_CTL_VLD, BIT(9) | BIT(6)); ++ ++ workspace_offset = h264->workspace_paddr - WORKSPACE_BUF_OFFSET; ++ amvdec_write_dos(core, AV_SCRATCH_1, workspace_offset); ++ amvdec_write_dos(core, AV_SCRATCH_G, h264->ext_fw_paddr); ++ amvdec_write_dos(core, AV_SCRATCH_I, h264->sei_paddr - workspace_offset); ++ ++ /* Enable "error correction" */ ++ amvdec_write_dos(core, AV_SCRATCH_F, ++ (amvdec_read_dos(core, AV_SCRATCH_F) & 0xffffffc3) | ++ BIT(4) | BIT(7)); ++ ++ amvdec_write_dos(core, MDEC_PIC_DC_THRESH, 0x404038aa); ++ ++ return 0; ++} ++ ++static int codec_h264_stop(struct amvdec_session *sess) ++{ ++ struct codec_h264 *h264 = sess->priv; ++ struct amvdec_core *core = sess->core; ++ ++ if (h264->ext_fw_vaddr) ++ dma_free_coherent(core->dev, SIZE_EXT_FW, ++ h264->ext_fw_vaddr, h264->ext_fw_paddr); ++ ++ if (h264->workspace_vaddr) ++ dma_free_coherent(core->dev, SIZE_WORKSPACE, ++ h264->workspace_vaddr, h264->workspace_paddr); ++ ++ if (h264->ref_vaddr) ++ dma_free_coherent(core->dev, h264->ref_size, ++ h264->ref_vaddr, h264->ref_paddr); ++ ++ if (h264->sei_vaddr) ++ dma_free_coherent(core->dev, SIZE_SEI, ++ h264->sei_vaddr, h264->sei_paddr); ++ ++ return 0; ++} ++ ++static int codec_h264_load_extended_firmware(struct amvdec_session *sess, ++ const u8 *data, u32 len) ++{ ++ struct codec_h264 *h264; ++ struct amvdec_core *core = sess->core; ++ ++ if (len < SIZE_EXT_FW) ++ return -EINVAL; ++ ++ h264 = kzalloc(sizeof(*h264), GFP_KERNEL); ++ if (!h264) ++ return -ENOMEM; ++ ++ h264->ext_fw_vaddr = dma_alloc_coherent(core->dev, SIZE_EXT_FW, ++ &h264->ext_fw_paddr, GFP_KERNEL); ++ if (!h264->ext_fw_vaddr) { ++ dev_err(core->dev, "Failed to alloc H.264 extended fw\n"); ++ kfree(h264); ++ return -ENOMEM; ++ } ++ ++ memcpy(h264->ext_fw_vaddr, data, SIZE_EXT_FW); ++ sess->priv = h264; ++ ++ return 0; ++} ++ ++static const struct v4l2_fract par_table[] = { ++ { 1, 1 }, { 1, 1 }, { 12, 11 }, { 10, 11 }, ++ { 16, 11 }, { 40, 33 }, { 24, 11 }, { 20, 11 }, ++ { 32, 11 }, { 80, 33 }, { 18, 11 }, { 15, 11 }, ++ { 64, 33 }, { 160, 99 }, { 4, 3 }, { 3, 2 }, ++ { 2, 1 } ++}; ++ ++static void codec_h264_set_par(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ u32 seq_info = amvdec_read_dos(core, AV_SCRATCH_2); ++ u32 ar_idc = (seq_info >> AR_IDC_BIT) & AR_IDC_MASK; ++ ++ if (!(seq_info & AR_PRESENT_FLAG)) ++ return; ++ ++ if (ar_idc == AR_EXTEND) { ++ u32 ar_info = amvdec_read_dos(core, AV_SCRATCH_3); ++ sess->pixelaspect.numerator = ar_info & 0xffff; ++ sess->pixelaspect.denominator = (ar_info >> 16) & 0xffff; ++ return; ++ } ++ ++ if (ar_idc >= ARRAY_SIZE(par_table)) ++ return; ++ ++ sess->pixelaspect = par_table[ar_idc]; ++} ++ ++static void codec_h264_resume(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ struct codec_h264 *h264 = sess->priv; ++ u32 mb_width, mb_height, mb_total; ++ ++ amvdec_set_canvases(sess, (u32[]){ ANC0_CANVAS_ADDR, 0 }, ++ (u32[]){ 24, 0 }); ++ ++ dev_dbg(core->dev, ++ "max_refs = %u; actual_dpb_size = %u\n", ++ h264->max_refs, sess->num_dst_bufs); ++ ++ /* Align to a multiple of 4 macroblocks */ ++ mb_width = ALIGN(h264->mb_width, 4); ++ mb_height = ALIGN(h264->mb_height, 4); ++ mb_total = mb_width * mb_height; ++ ++ h264->ref_size = mb_total * MB_MV_SIZE * h264->max_refs; ++ h264->ref_vaddr = dma_alloc_coherent(core->dev, h264->ref_size, ++ &h264->ref_paddr, GFP_KERNEL); ++ if (!h264->ref_vaddr) { ++ dev_err(core->dev, "Failed to alloc refs (%u)\n", ++ h264->ref_size); ++ amvdec_abort(sess); ++ return; ++ } ++ ++ /* Address to store the references' MVs */ ++ amvdec_write_dos(core, AV_SCRATCH_1, h264->ref_paddr); ++ /* End of ref MV */ ++ amvdec_write_dos(core, AV_SCRATCH_4, h264->ref_paddr + h264->ref_size); ++ ++ amvdec_write_dos(core, AV_SCRATCH_0, (h264->max_refs << 24) | ++ (sess->num_dst_bufs << 16) | ++ ((h264->max_refs - 1) << 8)); ++} ++ ++/* Configure the H.264 decoder when the parser detected a parameter set change ++ */ ++static void codec_h264_src_change(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ struct codec_h264 *h264 = sess->priv; ++ u32 parsed_info, mb_total; ++ u32 crop_infor, crop_bottom, crop_right; ++ u32 frame_width, frame_height; ++ ++ sess->keyframe_found = 1; ++ ++ parsed_info = amvdec_read_dos(core, AV_SCRATCH_1); ++ ++ /* Total number of 16x16 macroblocks */ ++ mb_total = (parsed_info >> MB_TOTAL_BIT) & MB_TOTAL_MASK; ++ /* Number of macroblocks per line */ ++ h264->mb_width = parsed_info & MB_WIDTH_MASK; ++ /* Number of macroblock lines */ ++ h264->mb_height = mb_total / h264->mb_width; ++ ++ h264->max_refs = ((parsed_info >> MAX_REF_BIT) & MAX_REF_MASK) + 1; ++ ++ crop_infor = amvdec_read_dos(core, AV_SCRATCH_6); ++ crop_bottom = (crop_infor & 0xff); ++ crop_right = (crop_infor >> 16) & 0xff; ++ ++ frame_width = h264->mb_width * 16 - crop_right; ++ frame_height = h264->mb_height * 16 - crop_bottom; ++ ++ dev_info(core->dev, "frame: %ux%u; crop: %u %u\n", ++ frame_width, frame_height, crop_right, crop_bottom); ++ ++ codec_h264_set_par(sess); ++ amvdec_src_change(sess, frame_width, frame_height, h264->max_refs + 5); ++} ++ ++/** ++ * The offset is split in half in 2 different registers ++ */ ++static u32 get_offset_msb(struct amvdec_core *core, int frame_num) ++{ ++ int take_msb = frame_num % 2; ++ int reg_offset = (frame_num / 2) * 4; ++ u32 offset_msb = amvdec_read_dos(core, AV_SCRATCH_A + reg_offset); ++ ++ if (take_msb) ++ return offset_msb & 0xffff0000; ++ ++ return (offset_msb & 0x0000ffff) << 16; ++} ++ ++static void codec_h264_frames_ready(struct amvdec_session *sess, u32 status) ++{ ++ struct amvdec_core *core = sess->core; ++ int error_count; ++ int num_frames; ++ int i; ++ ++ error_count = amvdec_read_dos(core, AV_SCRATCH_D); ++ num_frames = (status >> 8) & 0xff; ++ if (error_count) { ++ dev_warn(core->dev, ++ "decoder error(s) happened, count %d\n", error_count); ++ amvdec_write_dos(core, AV_SCRATCH_D, 0); ++ } ++ ++ for (i = 0; i < num_frames; i++) { ++ u32 frame_status = amvdec_read_dos(core, AV_SCRATCH_1 + i * 4); ++ u32 buffer_index = frame_status & BUF_IDX_MASK; ++ u32 pic_struct = (frame_status >> PIC_STRUCT_BIT) & ++ PIC_STRUCT_MASK; ++ u32 offset = (frame_status >> OFFSET_BIT) & OFFSET_MASK; ++ u32 field = V4L2_FIELD_NONE; ++ ++ /* A buffer decode error means it was decoded, ++ * but part of the picture will have artifacts. ++ * Typical reason is a temporarily corrupted bitstream ++ */ ++ if (frame_status & ERROR_FLAG) ++ dev_dbg(core->dev, "Buffer %d decode error\n", ++ buffer_index); ++ ++ if (pic_struct == PIC_TOP_BOT) ++ field = V4L2_FIELD_INTERLACED_TB; ++ else if (pic_struct == PIC_BOT_TOP) ++ field = V4L2_FIELD_INTERLACED_BT; ++ ++ offset |= get_offset_msb(core, i); ++ amvdec_dst_buf_done_idx(sess, buffer_index, offset, field); ++ } ++} ++ ++static irqreturn_t codec_h264_threaded_isr(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ u32 status; ++ u32 size; ++ u8 cmd; ++ ++ status = amvdec_read_dos(core, AV_SCRATCH_0); ++ cmd = status & CMD_MASK; ++ ++ switch (cmd) { ++ case CMD_SRC_CHANGE: ++ codec_h264_src_change(sess); ++ break; ++ case CMD_FRAMES_READY: ++ codec_h264_frames_ready(sess, status); ++ break; ++ case CMD_FATAL_ERROR: ++ dev_err(core->dev, "H.264 decoder fatal error\n"); ++ goto abort; ++ case CMD_BAD_WIDTH: ++ size = (amvdec_read_dos(core, AV_SCRATCH_1) + 1) * 16; ++ dev_err(core->dev, "Unsupported video width: %u\n", size); ++ goto abort; ++ case CMD_BAD_HEIGHT: ++ size = (amvdec_read_dos(core, AV_SCRATCH_1) + 1) * 16; ++ dev_err(core->dev, "Unsupported video height: %u\n", size); ++ goto abort; ++ case 0: /* Unused but not worth printing for */ ++ case 9: ++ break; ++ default: ++ dev_info(core->dev, "Unexpected H264 ISR: %08X\n", cmd); ++ break; ++ } ++ ++ if (cmd && cmd != CMD_SRC_CHANGE) ++ amvdec_write_dos(core, AV_SCRATCH_0, 0); ++ ++ /* Decoder has some SEI data for us ; ignore */ ++ if (amvdec_read_dos(core, AV_SCRATCH_J) & SEI_DATA_READY) ++ amvdec_write_dos(core, AV_SCRATCH_J, 0); ++ ++ return IRQ_HANDLED; ++abort: ++ amvdec_abort(sess); ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t codec_h264_isr(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ ++ amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1); ++ ++ return IRQ_WAKE_THREAD; ++} ++ ++struct amvdec_codec_ops codec_h264_ops = { ++ .start = codec_h264_start, ++ .stop = codec_h264_stop, ++ .load_extended_firmware = codec_h264_load_extended_firmware, ++ .isr = codec_h264_isr, ++ .threaded_isr = codec_h264_threaded_isr, ++ .can_recycle = codec_h264_can_recycle, ++ .recycle = codec_h264_recycle, ++ .eos_sequence = codec_h264_eos_sequence, ++ .resume = codec_h264_resume, ++}; +diff --git a/drivers/media/platform/meson/vdec/codec_h264.h b/drivers/media/platform/meson/vdec/codec_h264.h +new file mode 100644 +index 000000000000..7a1597611faf +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/codec_h264.h +@@ -0,0 +1,13 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright (C) 2018 Maxime Jourdan ++ */ ++ ++#ifndef __MESON_VDEC_CODEC_H264_H_ ++#define __MESON_VDEC_CODEC_H264_H_ ++ ++#include "vdec.h" ++ ++extern struct amvdec_codec_ops codec_h264_ops; ++ ++#endif +\ No newline at end of file +diff --git a/drivers/media/platform/meson/vdec/vdec_platform.c b/drivers/media/platform/meson/vdec/vdec_platform.c +index 291f1eeb27d9..baecf5921d56 100644 +--- a/drivers/media/platform/meson/vdec/vdec_platform.c ++++ b/drivers/media/platform/meson/vdec/vdec_platform.c +@@ -9,9 +9,20 @@ + + #include "vdec_1.h" + #include "codec_mpeg12.h" ++#include "codec_h264.h" + + static const struct amvdec_format vdec_formats_gxbb[] = { + { ++ .pixfmt = V4L2_PIX_FMT_H264, ++ .min_buffers = 2, ++ .max_buffers = 24, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_h264_ops, ++ .firmware_path = "meson/gxbb/vh264_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, ++ }, { + .pixfmt = V4L2_PIX_FMT_MPEG1, + .min_buffers = 8, + .max_buffers = 8, +@@ -38,6 +49,16 @@ static const struct amvdec_format vdec_formats_gxbb[] = { + + static const struct amvdec_format vdec_formats_gxl[] = { + { ++ .pixfmt = V4L2_PIX_FMT_H264, ++ .min_buffers = 2, ++ .max_buffers = 24, ++ .max_width = 3840, ++ .max_height = 2160, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_h264_ops, ++ .firmware_path = "meson/gxl/vh264_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, ++ }, { + .pixfmt = V4L2_PIX_FMT_MPEG1, + .min_buffers = 8, + .max_buffers = 8, +@@ -64,6 +85,16 @@ static const struct amvdec_format vdec_formats_gxl[] = { + + static const struct amvdec_format vdec_formats_gxm[] = { + { ++ .pixfmt = V4L2_PIX_FMT_H264, ++ .min_buffers = 2, ++ .max_buffers = 24, ++ .max_width = 3840, ++ .max_height = 2160, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_h264_ops, ++ .firmware_path = "meson/gxm/vh264_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, ++ }, { + .pixfmt = V4L2_PIX_FMT_MPEG1, + .min_buffers = 8, + .max_buffers = 8, +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0038-media-meson-vdec-add-MPEG4-decoding-support.patch b/buildroot-external/board/hardkernel/patches/linux/0038-media-meson-vdec-add-MPEG4-decoding-support.patch new file mode 100644 index 000000000..0dbac0a1b --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0038-media-meson-vdec-add-MPEG4-decoding-support.patch @@ -0,0 +1,315 @@ +From cbb607b67a4a169b94884436208b05062bd2f93b Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Wed, 29 Aug 2018 16:01:55 +0200 +Subject: [PATCH 38/53] media: meson: vdec: add MPEG4 decoding support + +Add support for V4L2_PIX_FMT_MPEG4, V4L2_PIX_FMT_XVID and +V4L2_PIX_FMT_H.263 +--- + drivers/media/platform/meson/vdec/Makefile | 2 +- + .../media/platform/meson/vdec/codec_mpeg4.c | 139 ++++++++++++++++++ + .../media/platform/meson/vdec/codec_mpeg4.h | 13 ++ + .../media/platform/meson/vdec/vdec_platform.c | 91 ++++++++++++ + 4 files changed, 244 insertions(+), 1 deletion(-) + create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg4.c + create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg4.h + +diff --git a/drivers/media/platform/meson/vdec/Makefile b/drivers/media/platform/meson/vdec/Makefile +index 01dc9603abdd..bb7a134e2728 100644 +--- a/drivers/media/platform/meson/vdec/Makefile ++++ b/drivers/media/platform/meson/vdec/Makefile +@@ -3,6 +3,6 @@ + + meson-vdec-objs = esparser.o vdec.o vdec_ctrls.o vdec_helpers.o vdec_platform.o + meson-vdec-objs += vdec_1.o +-meson-vdec-objs += codec_mpeg12.o codec_h264.o ++meson-vdec-objs += codec_mpeg12.o codec_h264.o codec_mpeg4.o + + obj-$(CONFIG_VIDEO_MESON_VDEC) += meson-vdec.o +diff --git a/drivers/media/platform/meson/vdec/codec_mpeg4.c b/drivers/media/platform/meson/vdec/codec_mpeg4.c +new file mode 100644 +index 000000000000..1d574e576112 +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/codec_mpeg4.c +@@ -0,0 +1,139 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2018 Maxime Jourdan ++ */ ++ ++#include ++#include ++ ++#include "vdec_helpers.h" ++#include "dos_regs.h" ++ ++#define SIZE_WORKSPACE SZ_1M ++/* Offset added by firmware, to substract from workspace paddr */ ++#define DCAC_BUFF_START_IP 0x02b00000 ++ ++/* map firmware registers to known MPEG4 functions */ ++#define MREG_BUFFERIN AV_SCRATCH_8 ++#define MREG_BUFFEROUT AV_SCRATCH_9 ++#define MP4_NOT_CODED_CNT AV_SCRATCH_A ++#define MP4_OFFSET_REG AV_SCRATCH_C ++#define MEM_OFFSET_REG AV_SCRATCH_F ++#define MREG_FATAL_ERROR AV_SCRATCH_L ++ ++#define BUF_IDX_MASK GENMASK(2, 0) ++#define INTERLACE_FLAG BIT(7) ++#define TOP_FIELD_FIRST_FLAG BIT(6) ++ ++struct codec_mpeg4 { ++ /* Buffer for the MPEG4 Workspace */ ++ void *workspace_vaddr; ++ dma_addr_t workspace_paddr; ++}; ++ ++static int codec_mpeg4_can_recycle(struct amvdec_core *core) ++{ ++ return !amvdec_read_dos(core, MREG_BUFFERIN); ++} ++ ++static void codec_mpeg4_recycle(struct amvdec_core *core, u32 buf_idx) ++{ ++ amvdec_write_dos(core, MREG_BUFFERIN, ~BIT(buf_idx)); ++} ++ ++static int codec_mpeg4_start(struct amvdec_session *sess) { ++ struct amvdec_core *core = sess->core; ++ struct codec_mpeg4 *mpeg4 = sess->priv; ++ int ret; ++ ++ mpeg4 = kzalloc(sizeof(*mpeg4), GFP_KERNEL); ++ if (!mpeg4) ++ return -ENOMEM; ++ ++ /* Allocate some memory for the MPEG4 decoder's state */ ++ mpeg4->workspace_vaddr = dma_alloc_coherent(core->dev, SIZE_WORKSPACE, ++ &mpeg4->workspace_paddr, ++ GFP_KERNEL); ++ if (!mpeg4->workspace_vaddr) { ++ dev_err(core->dev, "Failed to request MPEG4 Workspace\n"); ++ ret = -ENOMEM; ++ goto free_mpeg4; ++ } ++ ++ /* Canvas regs: AV_SCRATCH_0-AV_SCRATCH_4;AV_SCRATCH_G-AV_SCRATCH_J */ ++ amvdec_set_canvases(sess, (u32[]){ AV_SCRATCH_0, AV_SCRATCH_G, 0 }, ++ (u32[]){ 4, 4, 0 }); ++ ++ amvdec_write_dos(core, MEM_OFFSET_REG, ++ mpeg4->workspace_paddr - DCAC_BUFF_START_IP); ++ amvdec_write_dos(core, PSCALE_CTRL, 0); ++ amvdec_write_dos(core, MP4_NOT_CODED_CNT, 0); ++ amvdec_write_dos(core, MREG_BUFFERIN, 0); ++ amvdec_write_dos(core, MREG_BUFFEROUT, 0); ++ amvdec_write_dos(core, MREG_FATAL_ERROR, 0); ++ amvdec_write_dos(core, MDEC_PIC_DC_THRESH, 0x404038aa); ++ ++ sess->keyframe_found = 1; ++ sess->priv = mpeg4; ++ ++ return 0; ++ ++free_mpeg4: ++ kfree(mpeg4); ++ return ret; ++} ++ ++static int codec_mpeg4_stop(struct amvdec_session *sess) ++{ ++ struct codec_mpeg4 *mpeg4 = sess->priv; ++ struct amvdec_core *core = sess->core; ++ ++ if (mpeg4->workspace_vaddr) { ++ dma_free_coherent(core->dev, SIZE_WORKSPACE, ++ mpeg4->workspace_vaddr, ++ mpeg4->workspace_paddr); ++ mpeg4->workspace_vaddr = 0; ++ } ++ ++ return 0; ++} ++ ++static irqreturn_t codec_mpeg4_isr(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ u32 reg; ++ u32 buffer_index; ++ u32 field = V4L2_FIELD_NONE; ++ ++ reg = amvdec_read_dos(core, MREG_FATAL_ERROR); ++ if (reg == 1) { ++ dev_err(core->dev, "mpeg4 fatal error\n"); ++ amvdec_abort(sess); ++ return IRQ_HANDLED; ++ } ++ ++ reg = amvdec_read_dos(core, MREG_BUFFEROUT); ++ if (!reg) ++ goto end; ++ ++ buffer_index = reg & BUF_IDX_MASK; ++ if (reg & INTERLACE_FLAG) ++ field = (reg & TOP_FIELD_FIRST_FLAG) ? ++ V4L2_FIELD_INTERLACED_TB : ++ V4L2_FIELD_INTERLACED_BT; ++ ++ amvdec_dst_buf_done_idx(sess, buffer_index, -1, field); ++ amvdec_write_dos(core, MREG_BUFFEROUT, 0); ++ ++end: ++ amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1); ++ return IRQ_HANDLED; ++} ++ ++struct amvdec_codec_ops codec_mpeg4_ops = { ++ .start = codec_mpeg4_start, ++ .stop = codec_mpeg4_stop, ++ .isr = codec_mpeg4_isr, ++ .can_recycle = codec_mpeg4_can_recycle, ++ .recycle = codec_mpeg4_recycle, ++}; +diff --git a/drivers/media/platform/meson/vdec/codec_mpeg4.h b/drivers/media/platform/meson/vdec/codec_mpeg4.h +new file mode 100644 +index 000000000000..b91b26413185 +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/codec_mpeg4.h +@@ -0,0 +1,13 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright (C) 2018 Maxime Jourdan ++ */ ++ ++#ifndef __MESON_VDEC_CODEC_MPEG4_H_ ++#define __MESON_VDEC_CODEC_MPEG4_H_ ++ ++#include "vdec.h" ++ ++extern struct amvdec_codec_ops codec_mpeg4_ops; ++ ++#endif +\ No newline at end of file +diff --git a/drivers/media/platform/meson/vdec/vdec_platform.c b/drivers/media/platform/meson/vdec/vdec_platform.c +index baecf5921d56..80b43fd5d01f 100644 +--- a/drivers/media/platform/meson/vdec/vdec_platform.c ++++ b/drivers/media/platform/meson/vdec/vdec_platform.c +@@ -10,9 +10,40 @@ + #include "vdec_1.h" + #include "codec_mpeg12.h" + #include "codec_h264.h" ++#include "codec_mpeg4.h" + + static const struct amvdec_format vdec_formats_gxbb[] = { + { ++ .pixfmt = V4L2_PIX_FMT_MPEG4, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg4_ops, ++ .firmware_path = "meson/gx/vmpeg4_mc_5", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { ++ .pixfmt = V4L2_PIX_FMT_H263, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg4_ops, ++ .firmware_path = "meson/gx/h263_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { ++ .pixfmt = V4L2_PIX_FMT_XVID, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg4_ops, ++ .firmware_path = "meson/gx/vmpeg4_mc_5", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, +@@ -49,6 +80,36 @@ static const struct amvdec_format vdec_formats_gxbb[] = { + + static const struct amvdec_format vdec_formats_gxl[] = { + { ++ .pixfmt = V4L2_PIX_FMT_MPEG4, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg4_ops, ++ .firmware_path = "meson/gx/vmpeg4_mc_5", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { ++ .pixfmt = V4L2_PIX_FMT_H263, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg4_ops, ++ .firmware_path = "meson/gx/h263_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { ++ .pixfmt = V4L2_PIX_FMT_XVID, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg4_ops, ++ .firmware_path = "meson/gx/vmpeg4_mc_5", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, +@@ -85,6 +146,36 @@ static const struct amvdec_format vdec_formats_gxl[] = { + + static const struct amvdec_format vdec_formats_gxm[] = { + { ++ .pixfmt = V4L2_PIX_FMT_MPEG4, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg4_ops, ++ .firmware_path = "meson/gx/vmpeg4_mc_5", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { ++ .pixfmt = V4L2_PIX_FMT_H263, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg4_ops, ++ .firmware_path = "meson/gx/h263_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { ++ .pixfmt = V4L2_PIX_FMT_XVID, ++ .min_buffers = 8, ++ .max_buffers = 8, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mpeg4_ops, ++ .firmware_path = "meson/gx/vmpeg4_mc_5", ++ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0039-media-meson-vdec-add-MJPEG-decoding-support.patch b/buildroot-external/board/hardkernel/patches/linux/0039-media-meson-vdec-add-MJPEG-decoding-support.patch new file mode 100644 index 000000000..26f0758e6 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0039-media-meson-vdec-add-MJPEG-decoding-support.patch @@ -0,0 +1,255 @@ +From 1434c8fcc8ae00e748225ca4922c0d0b7bd15b02 Mon Sep 17 00:00:00 2001 +From: Maxime Jourdan +Date: Sun, 21 Oct 2018 15:14:27 +0200 +Subject: [PATCH 39/53] media: meson: vdec: add MJPEG decoding support + +Add support for V4L2_PIX_FMT_MJPEG +--- + drivers/media/platform/meson/vdec/Makefile | 2 +- + .../media/platform/meson/vdec/codec_mjpeg.c | 140 ++++++++++++++++++ + .../media/platform/meson/vdec/codec_mjpeg.h | 13 ++ + .../media/platform/meson/vdec/vdec_platform.c | 31 ++++ + 4 files changed, 185 insertions(+), 1 deletion(-) + create mode 100644 drivers/media/platform/meson/vdec/codec_mjpeg.c + create mode 100644 drivers/media/platform/meson/vdec/codec_mjpeg.h + +diff --git a/drivers/media/platform/meson/vdec/Makefile b/drivers/media/platform/meson/vdec/Makefile +index bb7a134e2728..acf07f3c3dac 100644 +--- a/drivers/media/platform/meson/vdec/Makefile ++++ b/drivers/media/platform/meson/vdec/Makefile +@@ -3,6 +3,6 @@ + + meson-vdec-objs = esparser.o vdec.o vdec_ctrls.o vdec_helpers.o vdec_platform.o + meson-vdec-objs += vdec_1.o +-meson-vdec-objs += codec_mpeg12.o codec_h264.o codec_mpeg4.o ++meson-vdec-objs += codec_mpeg12.o codec_h264.o codec_mpeg4.o codec_mjpeg.o + + obj-$(CONFIG_VIDEO_MESON_VDEC) += meson-vdec.o +diff --git a/drivers/media/platform/meson/vdec/codec_mjpeg.c b/drivers/media/platform/meson/vdec/codec_mjpeg.c +new file mode 100644 +index 000000000000..abea9e3f944c +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/codec_mjpeg.c +@@ -0,0 +1,140 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Copyright (C) 2018 Maxime Jourdan ++ */ ++ ++#include ++#include ++ ++#include "vdec_helpers.h" ++#include "dos_regs.h" ++ ++/* map FW registers to known MJPEG functions */ ++#define MREG_DECODE_PARAM AV_SCRATCH_2 ++#define MREG_TO_AMRISC AV_SCRATCH_8 ++#define MREG_FROM_AMRISC AV_SCRATCH_9 ++#define MREG_FRAME_OFFSET AV_SCRATCH_A ++ ++static int codec_mjpeg_can_recycle(struct amvdec_core *core) ++{ ++ return !amvdec_read_dos(core, MREG_TO_AMRISC); ++} ++ ++static void codec_mjpeg_recycle(struct amvdec_core *core, u32 buf_idx) ++{ ++ amvdec_write_dos(core, MREG_TO_AMRISC, buf_idx + 1); ++} ++ ++/* 4 point triangle */ ++static const uint32_t filt_coef[] = { ++ 0x20402000, 0x20402000, 0x1f3f2101, 0x1f3f2101, ++ 0x1e3e2202, 0x1e3e2202, 0x1d3d2303, 0x1d3d2303, ++ 0x1c3c2404, 0x1c3c2404, 0x1b3b2505, 0x1b3b2505, ++ 0x1a3a2606, 0x1a3a2606, 0x19392707, 0x19392707, ++ 0x18382808, 0x18382808, 0x17372909, 0x17372909, ++ 0x16362a0a, 0x16362a0a, 0x15352b0b, 0x15352b0b, ++ 0x14342c0c, 0x14342c0c, 0x13332d0d, 0x13332d0d, ++ 0x12322e0e, 0x12322e0e, 0x11312f0f, 0x11312f0f, ++ 0x10303010 ++}; ++ ++static void codec_mjpeg_init_scaler(struct amvdec_core *core) ++{ ++ int i; ++ ++ /* PSCALE cbus bmem enable */ ++ amvdec_write_dos(core, PSCALE_CTRL, 0xc000); ++ ++ amvdec_write_dos(core, PSCALE_BMEM_ADDR, 0); ++ for (i = 0; i < ARRAY_SIZE(filt_coef); ++i) { ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, filt_coef[i]); ++ } ++ ++ amvdec_write_dos(core, PSCALE_BMEM_ADDR, 74); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0x0008); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0x60000000); ++ ++ amvdec_write_dos(core, PSCALE_BMEM_ADDR, 82); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0x0008); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0x60000000); ++ ++ amvdec_write_dos(core, PSCALE_BMEM_ADDR, 78); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0x0008); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0x60000000); ++ ++ amvdec_write_dos(core, PSCALE_BMEM_ADDR, 86); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0x0008); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0x60000000); ++ ++ amvdec_write_dos(core, PSCALE_BMEM_ADDR, 73); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0x10000); ++ amvdec_write_dos(core, PSCALE_BMEM_ADDR, 81); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0x10000); ++ ++ amvdec_write_dos(core, PSCALE_BMEM_ADDR, 77); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0x10000); ++ amvdec_write_dos(core, PSCALE_BMEM_ADDR, 85); ++ amvdec_write_dos(core, PSCALE_BMEM_DAT, 0x10000); ++ ++ amvdec_write_dos(core, PSCALE_RST, 0x7); ++ amvdec_write_dos(core, PSCALE_RST, 0); ++} ++ ++static int codec_mjpeg_start(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ ++ amvdec_write_dos(core, AV_SCRATCH_0, 12); ++ amvdec_write_dos(core, AV_SCRATCH_1, 0x031a); ++ ++ amvdec_set_canvases(sess, (u32[]){ AV_SCRATCH_4, 0 }, ++ (u32[]){ 4, 0 }); ++ codec_mjpeg_init_scaler(core); ++ ++ amvdec_write_dos(core, MREG_TO_AMRISC, 0); ++ amvdec_write_dos(core, MREG_FROM_AMRISC, 0); ++ amvdec_write_dos(core, MCPU_INTR_MSK, 0xffff); ++ amvdec_write_dos(core, MREG_DECODE_PARAM, ++ (sess->height << 4) | 0x8000); ++ amvdec_write_dos(core, VDEC_ASSIST_AMR1_INT8, 8); ++ ++ /* Intra-only codec */ ++ sess->keyframe_found = 1; ++ ++ return 0; ++} ++ ++static int codec_mjpeg_stop(struct amvdec_session *sess) ++{ ++ return 0; ++} ++ ++static irqreturn_t codec_mjpeg_isr(struct amvdec_session *sess) ++{ ++ struct amvdec_core *core = sess->core; ++ u32 reg; ++ u32 buffer_index; ++ u32 offset; ++ ++ amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1); ++ ++ reg = amvdec_read_dos(core, MREG_FROM_AMRISC); ++ if (!(reg & 0x7)) ++ return IRQ_HANDLED; ++ ++ buffer_index = ((reg & 0x7) - 1) & 3; ++ offset = amvdec_read_dos(core, MREG_FRAME_OFFSET); ++ amvdec_dst_buf_done_idx(sess, buffer_index, offset, V4L2_FIELD_NONE); ++ ++ amvdec_write_dos(core, MREG_FROM_AMRISC, 0); ++ return IRQ_HANDLED; ++} ++ ++struct amvdec_codec_ops codec_mjpeg_ops = { ++ .start = codec_mjpeg_start, ++ .stop = codec_mjpeg_stop, ++ .isr = codec_mjpeg_isr, ++ .can_recycle = codec_mjpeg_can_recycle, ++ .recycle = codec_mjpeg_recycle, ++}; +diff --git a/drivers/media/platform/meson/vdec/codec_mjpeg.h b/drivers/media/platform/meson/vdec/codec_mjpeg.h +new file mode 100644 +index 000000000000..cc1cf731050d +--- /dev/null ++++ b/drivers/media/platform/meson/vdec/codec_mjpeg.h +@@ -0,0 +1,13 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Copyright (C) 2018 Maxime Jourdan ++ */ ++ ++#ifndef __MESON_VDEC_CODEC_MJPEG_H_ ++#define __MESON_VDEC_CODEC_MJPEG_H_ ++ ++#include "vdec.h" ++ ++extern struct amvdec_codec_ops codec_mjpeg_ops; ++ ++#endif +\ No newline at end of file +diff --git a/drivers/media/platform/meson/vdec/vdec_platform.c b/drivers/media/platform/meson/vdec/vdec_platform.c +index 80b43fd5d01f..61def155a5fd 100644 +--- a/drivers/media/platform/meson/vdec/vdec_platform.c ++++ b/drivers/media/platform/meson/vdec/vdec_platform.c +@@ -11,9 +11,20 @@ + #include "codec_mpeg12.h" + #include "codec_h264.h" + #include "codec_mpeg4.h" ++#include "codec_mjpeg.h" + + static const struct amvdec_format vdec_formats_gxbb[] = { + { ++ .pixfmt = V4L2_PIX_FMT_MJPEG, ++ .min_buffers = 4, ++ .max_buffers = 4, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mjpeg_ops, ++ .firmware_path = "meson/gx/vmjpeg_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { + .pixfmt = V4L2_PIX_FMT_MPEG4, + .min_buffers = 8, + .max_buffers = 8, +@@ -80,6 +91,16 @@ static const struct amvdec_format vdec_formats_gxbb[] = { + + static const struct amvdec_format vdec_formats_gxl[] = { + { ++ .pixfmt = V4L2_PIX_FMT_MJPEG, ++ .min_buffers = 4, ++ .max_buffers = 4, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mjpeg_ops, ++ .firmware_path = "meson/gx/vmjpeg_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { + .pixfmt = V4L2_PIX_FMT_MPEG4, + .min_buffers = 8, + .max_buffers = 8, +@@ -146,6 +167,16 @@ static const struct amvdec_format vdec_formats_gxl[] = { + + static const struct amvdec_format vdec_formats_gxm[] = { + { ++ .pixfmt = V4L2_PIX_FMT_MJPEG, ++ .min_buffers = 4, ++ .max_buffers = 4, ++ .max_width = 1920, ++ .max_height = 1080, ++ .vdec_ops = &vdec_1_ops, ++ .codec_ops = &codec_mjpeg_ops, ++ .firmware_path = "meson/gx/vmjpeg_mc", ++ .pixfmts_cap = { V4L2_PIX_FMT_YUV420M, 0 }, ++ }, { + .pixfmt = V4L2_PIX_FMT_MPEG4, + .min_buffers = 8, + .max_buffers = 8, +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0040-clk-meson-gxbb-set-fclk_div3-as-CLK_IS_CRITICAL.patch b/buildroot-external/board/hardkernel/patches/linux/0040-clk-meson-gxbb-set-fclk_div3-as-CLK_IS_CRITICAL.patch new file mode 100644 index 000000000..8f6e7bfd9 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0040-clk-meson-gxbb-set-fclk_div3-as-CLK_IS_CRITICAL.patch @@ -0,0 +1,44 @@ +From 8bb24f445a3b532a06a1d5bde70daad0c4f3da4f Mon Sep 17 00:00:00 2001 +From: Christian Hewitt +Date: Sat, 13 Oct 2018 14:04:46 +0400 +Subject: [PATCH 40/53] clk: meson-gxbb: set fclk_div3 as CLK_IS_CRITICAL + +On the Khadas VIM2 (GXM) and LePotato (GXL) board there are problems +with reboot; e.g. a ~60 second delay between issuing reboot and the +board power cycling (and in some OS configurations reboot will fail +and require manual power cycling). + +Similar to 'commit c987ac6f1f088663b6dad39281071aeb31d450a8 ("clk: +meson-gxbb: set fclk_div2 as CLK_IS_CRITICAL")' the SCPI Cortex-M4 +Co-Processor seems to depend on FCLK_DIV3 being operational. + +Bisect gives 'commit 05f814402d6174369b3b29832cbb5eb5ed287059 ("clk: +meson: add fdiv clock gates") between 4.16 and 4.16-rc1 as the first +bad commit. This added support for the missing clock gates before the +fixed PLL fixed dividers (FCLK_DIVx) and the clock framework which +disabled all the unused fixed dividers, thus it disabled a critical +clock path for the SCPI Co-Processor. + +This change simply sets the FCLK_DIV3 gate as critical to ensure +nothing can disable it. + +Signed-off-by: Christian Hewitt +--- + drivers/clk/meson/gxbb.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/clk/meson/gxbb.c b/drivers/clk/meson/gxbb.c +index 4d4f6d842c31..a6fae1c00df3 100644 +--- a/drivers/clk/meson/gxbb.c ++++ b/drivers/clk/meson/gxbb.c +@@ -513,6 +513,7 @@ static struct clk_fixed_factor gxbb_fclk_div3_div = { + .ops = &clk_fixed_factor_ops, + .parent_names = (const char *[]){ "fixed_pll" }, + .num_parents = 1, ++ .flags = CLK_IS_CRITICAL, + }, + }; + +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0041-drm-meson-Add-HDMI-1.4-4k-modes.patch b/buildroot-external/board/hardkernel/patches/linux/0041-drm-meson-Add-HDMI-1.4-4k-modes.patch new file mode 100644 index 000000000..4dda76064 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0041-drm-meson-Add-HDMI-1.4-4k-modes.patch @@ -0,0 +1,167 @@ +From cb1d14ed8b5d2dc67b8d7769b5314fd5b9010f23 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Fri, 20 Jul 2018 15:29:18 +0200 +Subject: [PATCH 41/53] drm/meson: Add HDMI 1.4 4k modes + +Add the timings for the HDMI 1.4 4K modes support : +- 3840x2160@30 +- 3840x2160@25 +- 3840x2160@24 + +Since the 297000Hz pixel clock is already managed and the modes are +compatible with the HDMI 1.4 current HDMI PHY+Controller support, only +the missing timings values needs to be added. +--- + drivers/gpu/drm/meson/meson_venc.c | 129 +++++++++++++++++++++++++++++ + 1 file changed, 129 insertions(+) + +diff --git a/drivers/gpu/drm/meson/meson_venc.c b/drivers/gpu/drm/meson/meson_venc.c +index 7a3a6ed9f27b..0fbe525b94c8 100644 +--- a/drivers/gpu/drm/meson/meson_venc.c ++++ b/drivers/gpu/drm/meson/meson_venc.c +@@ -698,6 +698,132 @@ union meson_hdmi_venc_mode meson_hdmi_encp_mode_1080p60 = { + }, + }; + ++union meson_hdmi_venc_mode meson_hdmi_encp_mode_2160p24 = { ++ .encp = { ++ .dvi_settings = 0x1, ++ .video_mode = 0x4040, ++ .video_mode_adv = 0x8, ++ /* video_sync_mode */ ++ /* video_yc_dly */ ++ /* video_rgb_ctrl */ ++ .video_filt_ctrl = 0x1000, ++ .video_filt_ctrl_present = true, ++ /* video_ofld_voav_ofst */ ++ .yfp1_htime = 140, ++ .yfp2_htime = 140+3840, ++ .max_pxcnt = 3840+1660-1, ++ .hspuls_begin = 2156+1920, ++ .hspuls_end = 44, ++ .hspuls_switch = 44, ++ .vspuls_begin = 140, ++ .vspuls_end = 2059+1920, ++ .vspuls_bline = 0, ++ .vspuls_eline = 4, ++ .havon_begin = 148, ++ .havon_end = 3987, ++ .vavon_bline = 89, ++ .vavon_eline = 2248, ++ /* eqpuls_begin */ ++ /* eqpuls_end */ ++ /* eqpuls_bline */ ++ /* eqpuls_eline */ ++ .hso_begin = 44, ++ .hso_end = 2156+1920, ++ .vso_begin = 2100+1920, ++ .vso_end = 2164+1920, ++ .vso_bline = 51, ++ .vso_eline = 53, ++ .vso_eline_present = true, ++ /* sy_val */ ++ /* sy2_val */ ++ .max_lncnt = 2249, ++ }, ++}; ++ ++union meson_hdmi_venc_mode meson_hdmi_encp_mode_2160p25 = { ++ .encp = { ++ .dvi_settings = 0x1, ++ .video_mode = 0x4040, ++ .video_mode_adv = 0x8, ++ /* video_sync_mode */ ++ /* video_yc_dly */ ++ /* video_rgb_ctrl */ ++ .video_filt_ctrl = 0x1000, ++ .video_filt_ctrl_present = true, ++ /* video_ofld_voav_ofst */ ++ .yfp1_htime = 140, ++ .yfp2_htime = 140+3840, ++ .max_pxcnt = 3840+1440-1, ++ .hspuls_begin = 2156+1920, ++ .hspuls_end = 44, ++ .hspuls_switch = 44, ++ .vspuls_begin = 140, ++ .vspuls_end = 2059+1920, ++ .vspuls_bline = 0, ++ .vspuls_eline = 4, ++ .havon_begin = 148, ++ .havon_end = 3987, ++ .vavon_bline = 89, ++ .vavon_eline = 2248, ++ /* eqpuls_begin */ ++ /* eqpuls_end */ ++ /* eqpuls_bline */ ++ /* eqpuls_eline */ ++ .hso_begin = 44, ++ .hso_end = 2156+1920, ++ .vso_begin = 2100+1920, ++ .vso_end = 2164+1920, ++ .vso_bline = 51, ++ .vso_eline = 53, ++ .vso_eline_present = true, ++ /* sy_val */ ++ /* sy2_val */ ++ .max_lncnt = 2249, ++ }, ++}; ++ ++union meson_hdmi_venc_mode meson_hdmi_encp_mode_2160p30 = { ++ .encp = { ++ .dvi_settings = 0x1, ++ .video_mode = 0x4040, ++ .video_mode_adv = 0x8, ++ /* video_sync_mode */ ++ /* video_yc_dly */ ++ /* video_rgb_ctrl */ ++ .video_filt_ctrl = 0x1000, ++ .video_filt_ctrl_present = true, ++ /* video_ofld_voav_ofst */ ++ .yfp1_htime = 140, ++ .yfp2_htime = 140+3840, ++ .max_pxcnt = 3840+560-1, ++ .hspuls_begin = 2156+1920, ++ .hspuls_end = 44, ++ .hspuls_switch = 44, ++ .vspuls_begin = 140, ++ .vspuls_end = 2059+1920, ++ .vspuls_bline = 0, ++ .vspuls_eline = 4, ++ .havon_begin = 148, ++ .havon_end = 3987, ++ .vavon_bline = 89, ++ .vavon_eline = 2248, ++ /* eqpuls_begin */ ++ /* eqpuls_end */ ++ /* eqpuls_bline */ ++ /* eqpuls_eline */ ++ .hso_begin = 44, ++ .hso_end = 2156+1920, ++ .vso_begin = 2100+1920, ++ .vso_end = 2164+1920, ++ .vso_bline = 51, ++ .vso_eline = 53, ++ .vso_eline_present = true, ++ /* sy_val */ ++ /* sy2_val */ ++ .max_lncnt = 2249, ++ }, ++}; ++ + struct meson_hdmi_venc_vic_mode { + unsigned int vic; + union meson_hdmi_venc_mode *mode; +@@ -719,6 +845,9 @@ struct meson_hdmi_venc_vic_mode { + { 34, &meson_hdmi_encp_mode_1080p30 }, + { 31, &meson_hdmi_encp_mode_1080p50 }, + { 16, &meson_hdmi_encp_mode_1080p60 }, ++ { 93, &meson_hdmi_encp_mode_2160p24 }, ++ { 94, &meson_hdmi_encp_mode_2160p25 }, ++ { 95, &meson_hdmi_encp_mode_2160p30 }, + { 0, NULL}, /* sentinel */ + }; + +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0042-drm-meson-Use-drm_fbdev_generic_setup.patch b/buildroot-external/board/hardkernel/patches/linux/0042-drm-meson-Use-drm_fbdev_generic_setup.patch new file mode 100644 index 000000000..2c37cd33c --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0042-drm-meson-Use-drm_fbdev_generic_setup.patch @@ -0,0 +1,94 @@ +From 040f80b511f207308bbd7c177e148551cbd2c110 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= +Date: Sat, 8 Sep 2018 15:46:33 +0200 +Subject: [PATCH 42/53] drm/meson: Use drm_fbdev_generic_setup() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The CMA helper is already using the drm_fb_helper_generic_probe part of +the generic fbdev emulation. This patch makes full use of the generic +fbdev emulation by using its drm_client callbacks. This means that +drm_mode_config_funcs->output_poll_changed and drm_driver->lastclose are +now handled by the emulation code. Additionally fbdev unregister happens +automatically on drm_dev_unregister(). + +The drm_fbdev_generic_setup() call is put after drm_dev_register() in the +driver. This is done to highlight the fact that fbdev emulation is an +internal client that makes use of the driver, it is not part of the +driver as such. If fbdev setup fails, an error is printed, but the driver +succeeds probing. + +Cc: Neil Armstrong +Signed-off-by: Noralf Trønnes +--- + drivers/gpu/drm/meson/meson_drv.c | 18 ++---------------- + drivers/gpu/drm/meson/meson_drv.h | 1 - + 2 files changed, 2 insertions(+), 17 deletions(-) + +diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c +index 63bb2727b183..a13704ab5d11 100644 +--- a/drivers/gpu/drm/meson/meson_drv.c ++++ b/drivers/gpu/drm/meson/meson_drv.c +@@ -69,15 +69,7 @@ + * - Powering Up HDMI controller and PHY + */ + +-static void meson_fb_output_poll_changed(struct drm_device *dev) +-{ +- struct meson_drm *priv = dev->dev_private; +- +- drm_fbdev_cma_hotplug_event(priv->fbdev); +-} +- + static const struct drm_mode_config_funcs meson_mode_config_funcs = { +- .output_poll_changed = meson_fb_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, + .fb_create = drm_gem_fb_create, +@@ -319,13 +311,6 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) + + drm_mode_config_reset(drm); + +- priv->fbdev = drm_fbdev_cma_init(drm, 32, +- drm->mode_config.num_connector); +- if (IS_ERR(priv->fbdev)) { +- ret = PTR_ERR(priv->fbdev); +- goto free_drm; +- } +- + drm_kms_helper_poll_init(drm); + + platform_set_drvdata(pdev, priv); +@@ -334,6 +319,8 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) + if (ret) + goto uninstall_irq; + ++ drm_fbdev_generic_setup(drm, 32); ++ + return 0; + + uninstall_irq: +@@ -364,7 +351,6 @@ static void meson_drv_unbind(struct device *dev) + drm_dev_unregister(drm); + drm_irq_uninstall(drm); + drm_kms_helper_poll_fini(drm); +- drm_fbdev_cma_fini(priv->fbdev); + drm_mode_config_cleanup(drm); + drm_dev_put(drm); + +diff --git a/drivers/gpu/drm/meson/meson_drv.h b/drivers/gpu/drm/meson/meson_drv.h +index a955354711ce..4dccf4cd042a 100644 +--- a/drivers/gpu/drm/meson/meson_drv.h ++++ b/drivers/gpu/drm/meson/meson_drv.h +@@ -40,7 +40,6 @@ struct meson_drm { + + struct drm_device *drm; + struct drm_crtc *crtc; +- struct drm_fbdev_cma *fbdev; + struct drm_plane *primary_plane; + struct drm_plane *overlay_plane; + +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0043-drm-bridge-dw-hdmi-Add-SCDC-and-TMDS-Scrambling-supp.patch b/buildroot-external/board/hardkernel/patches/linux/0043-drm-bridge-dw-hdmi-Add-SCDC-and-TMDS-Scrambling-supp.patch new file mode 100644 index 000000000..22fa32024 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0043-drm-bridge-dw-hdmi-Add-SCDC-and-TMDS-Scrambling-supp.patch @@ -0,0 +1,150 @@ +From 3514e950490879dcdd75c74196e26eab8f5b740d Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Wed, 14 Nov 2018 16:48:50 +0100 +Subject: [PATCH 43/53] drm/bridge: dw-hdmi: Add SCDC and TMDS Scrambling + support + +Add support for SCDC Setup for TMDS Clock > 3.4GHz and enable TMDS +Scrambling when supported or mandatory. + +This patch also adds an helper to setup the control bit to support +the hight TMDS Bit Period/TMDS Clock-Period Ratio as required with +TMDS Clock > 3.4GHz for HDMI2.0 3840x2160@60/50 modes. + +These changes were based on work done by Huicong Xu +and Nickey Yang to support HDMI2.0 modes +on the Rockchip 4.4 BSP kernel at [1] + +[1] https://github.com/rockchip-linux/kernel/tree/release-4.4 + +Cc: Nickey Yang +Cc: Huicong Xu +Signed-off-by: Neil Armstrong +--- + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 45 +++++++++++++++++++++-- + drivers/gpu/drm/bridge/synopsys/dw-hdmi.h | 1 + + include/drm/bridge/dw_hdmi.h | 1 + + 3 files changed, 44 insertions(+), 3 deletions(-) + +diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +index 1fc12708dbb5..2a30d8393477 100644 +--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c ++++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +@@ -28,6 +28,7 @@ + #include + #include + #include ++#include + #include + + #include +@@ -1026,6 +1027,20 @@ void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data, + } + EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_write); + ++void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi) ++{ ++ unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mpixelclock; ++ ++ /* Control for TMDS Bit Period/TMDS Clock-Period Ratio */ ++ if (hdmi->connector.display_info.hdmi.scdc.supported) { ++ if (mtmdsclock > 340000000) ++ drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 1); ++ else ++ drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 0); ++ } ++} ++EXPORT_SYMBOL_GPL(dw_hdmi_set_high_tmds_clock_ratio); ++ + static void dw_hdmi_phy_enable_powerdown(struct dw_hdmi *hdmi, bool enable) + { + hdmi_mask_writeb(hdmi, !enable, HDMI_PHY_CONF0, +@@ -1351,11 +1366,12 @@ static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi) + + static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) + { ++ bool is_hdmi2_sink = hdmi->connector.display_info.hdmi.scdc.supported; + struct hdmi_avi_infoframe frame; + u8 val; + + /* Initialise info frame from DRM mode */ +- drm_hdmi_avi_infoframe_from_display_mode(&frame, mode, false); ++ drm_hdmi_avi_infoframe_from_display_mode(&frame, mode, is_hdmi2_sink); + + if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) + frame.colorspace = HDMI_COLORSPACE_YUV444; +@@ -1514,7 +1530,8 @@ static void hdmi_config_vendor_specific_infoframe(struct dw_hdmi *hdmi, + static void hdmi_av_composer(struct dw_hdmi *hdmi, + const struct drm_display_mode *mode) + { +- u8 inv_val; ++ u8 inv_val, bytes; ++ struct drm_hdmi_info *hdmi_info = &hdmi->connector.display_info.hdmi; + struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; + int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len; + unsigned int vdisplay; +@@ -1524,7 +1541,9 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, + dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock); + + /* Set up HDMI_FC_INVIDCONF */ +- inv_val = (hdmi->hdmi_data.hdcp_enable ? ++ inv_val = (hdmi->hdmi_data.hdcp_enable || ++ vmode->mpixelclock > 340000000 || ++ hdmi_info->scdc.scrambling.low_rates ? + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE : + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE); + +@@ -1573,6 +1592,26 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, + vsync_len /= 2; + } + ++ /* Scrambling Control */ ++ if (hdmi_info->scdc.supported) { ++ if (vmode->mpixelclock > 340000000 || ++ hdmi_info->scdc.scrambling.low_rates) { ++ drm_scdc_readb(&hdmi->i2c->adap, SCDC_SINK_VERSION, ++ &bytes); ++ drm_scdc_writeb(&hdmi->i2c->adap, SCDC_SOURCE_VERSION, ++ bytes); ++ drm_scdc_set_scrambling(&hdmi->i2c->adap, 1); ++ hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, ++ HDMI_MC_SWRSTZ); ++ hdmi_writeb(hdmi, 1, HDMI_FC_SCRAMBLER_CTRL); ++ } else { ++ hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL); ++ hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, ++ HDMI_MC_SWRSTZ); ++ drm_scdc_set_scrambling(&hdmi->i2c->adap, 0); ++ } ++ } ++ + /* Set up horizontal active pixel width */ + hdmi_writeb(hdmi, mode->hdisplay >> 8, HDMI_FC_INHACTV1); + hdmi_writeb(hdmi, mode->hdisplay, HDMI_FC_INHACTV0); +diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h +index 9d90eb9c46e5..3f3c616eba97 100644 +--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h ++++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h +@@ -255,6 +255,7 @@ + #define HDMI_FC_MASK2 0x10DA + #define HDMI_FC_POL2 0x10DB + #define HDMI_FC_PRCONF 0x10E0 ++#define HDMI_FC_SCRAMBLER_CTRL 0x10E1 + + #define HDMI_FC_GMD_STAT 0x1100 + #define HDMI_FC_GMD_EN 0x1101 +diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h +index ccb5aa8468e0..d7cc5d094270 100644 +--- a/include/drm/bridge/dw_hdmi.h ++++ b/include/drm/bridge/dw_hdmi.h +@@ -156,6 +156,7 @@ void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense); + void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate); + void dw_hdmi_audio_enable(struct dw_hdmi *hdmi); + void dw_hdmi_audio_disable(struct dw_hdmi *hdmi); ++void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi); + + /* PHY configuration */ + void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address); +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0044-drm-meson-add-HDMI-div40-TMDS-mode.patch b/buildroot-external/board/hardkernel/patches/linux/0044-drm-meson-add-HDMI-div40-TMDS-mode.patch new file mode 100644 index 000000000..d95c48cd6 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0044-drm-meson-add-HDMI-div40-TMDS-mode.patch @@ -0,0 +1,71 @@ +From 9f4886b1df0a93a313bc8a238ca6f020fbe8ae90 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Mon, 12 Nov 2018 16:08:13 +0100 +Subject: [PATCH 44/53] drm/meson: add HDMI div40 TMDS mode + +Add support for TMDS Clock > 3.4GHz for HDMI2.0 display modes. + +Signed-off-by: Neil Armstrong +--- + drivers/gpu/drm/meson/meson_dw_hdmi.c | 24 ++++++++++++++++++++---- + 1 file changed, 20 insertions(+), 4 deletions(-) + +diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c +index 807111ebfdd9..b8775102b100 100644 +--- a/drivers/gpu/drm/meson/meson_dw_hdmi.c ++++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c +@@ -365,7 +365,8 @@ static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, + unsigned int wr_clk = + readl_relaxed(priv->io_base + _REG(VPU_HDMI_SETTING)); + +- DRM_DEBUG_DRIVER("%d:\"%s\"\n", mode->base.id, mode->name); ++ DRM_DEBUG_DRIVER("%d:\"%s\" div%d\n", mode->base.id, mode->name, ++ mode->clock > 340000 ? 40 : 10); + + /* Enable clocks */ + regmap_update_bits(priv->hhi, HHI_HDMI_CLK_CNTL, 0xffff, 0x100); +@@ -385,9 +386,17 @@ static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, + /* Enable normal output to PHY */ + dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_BIST_CNTL, BIT(12)); + +- /* TMDS pattern setup (TOFIX pattern for 4k2k scrambling) */ +- dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, 0x001f001f); +- dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, 0x001f001f); ++ /* TMDS pattern setup (TOFIX Handle the YUV420 case) */ ++ if (mode->clock > 340000) { ++ dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, 0); ++ dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, ++ 0x03ff03ff); ++ } else { ++ dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, ++ 0x001f001f); ++ dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, ++ 0x001f001f); ++ } + + /* Load TMDS pattern */ + dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x1); +@@ -413,6 +422,8 @@ static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, + /* Disable clock, fifo, fifo_wr */ + regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0); + ++ dw_hdmi_set_high_tmds_clock_ratio(hdmi); ++ + msleep(100); + + /* Reset PHY 3 times in a row */ +@@ -562,6 +573,11 @@ dw_hdmi_mode_valid(struct drm_connector *connector, + mode->vdisplay, mode->vsync_start, + mode->vsync_end, mode->vtotal, mode->type, mode->flags); + ++ /* If sink max TMDS clock < 340MHz, we reject the HDMI2.0 modes */ ++ if (mode->clock > 340000 && ++ connector->display_info.max_tmds_clock < 340000) ++ return MODE_BAD; ++ + /* Check against non-VIC supported modes */ + if (!vic) { + status = meson_venc_hdmi_supported_mode(mode); +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0045-drm-meson-add-support-for-HDMI2.0-2160p-modes.patch b/buildroot-external/board/hardkernel/patches/linux/0045-drm-meson-add-support-for-HDMI2.0-2160p-modes.patch new file mode 100644 index 000000000..57d6db364 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0045-drm-meson-add-support-for-HDMI2.0-2160p-modes.patch @@ -0,0 +1,30 @@ +From 954b1e933ad1dd534c3f5b01fde7b52a62b78973 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Mon, 12 Nov 2018 16:10:07 +0100 +Subject: [PATCH 45/53] drm/meson: add support for HDMI2.0 2160p modes + +Now we support the TMDS Clock > 3.4GHz and support the SCDC Control +operation in the DW-HDMI Controller, we can enable support for the +HDMI2.0 3840x2160@60/50 RGB444 display modes. + +Signed-off-by: Neil Armstrong +--- + drivers/gpu/drm/meson/meson_venc.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/gpu/drm/meson/meson_venc.c b/drivers/gpu/drm/meson/meson_venc.c +index 0fbe525b94c8..1bcd642b6e42 100644 +--- a/drivers/gpu/drm/meson/meson_venc.c ++++ b/drivers/gpu/drm/meson/meson_venc.c +@@ -848,6 +848,8 @@ struct meson_hdmi_venc_vic_mode { + { 93, &meson_hdmi_encp_mode_2160p24 }, + { 94, &meson_hdmi_encp_mode_2160p25 }, + { 95, &meson_hdmi_encp_mode_2160p30 }, ++ { 96, &meson_hdmi_encp_mode_2160p25 }, ++ { 97, &meson_hdmi_encp_mode_2160p30 }, + { 0, NULL}, /* sentinel */ + }; + +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0046-drm-bridge-dw-hdmi-add-support-for-YUV420-output.patch b/buildroot-external/board/hardkernel/patches/linux/0046-drm-bridge-dw-hdmi-add-support-for-YUV420-output.patch new file mode 100644 index 000000000..706544fb4 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0046-drm-bridge-dw-hdmi-add-support-for-YUV420-output.patch @@ -0,0 +1,200 @@ +From afdd89304db8f3e858ee32cefaf29ed0be12500e Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Wed, 14 Nov 2018 17:19:36 +0100 +Subject: [PATCH 46/53] drm/bridge: dw-hdmi: add support for YUV420 output + +In order to support the HDMI2.0 YUV420 display modes, this patch +adds support for the YUV420 TMDS Clock divided by 2 and the controller +passthrough mode. + +This patch is based on work from Zheng Yang in +the Rockchip Linux 4.4 BSP at [1] + +[1] https://github.com/rockchip-linux/kernel/tree/release-4.4 + +Cc: Zheng Yang +Signed-off-by: Neil Armstrong +--- + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 63 ++++++++++++++++++----- + 1 file changed, 50 insertions(+), 13 deletions(-) + +diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +index 2a30d8393477..c3e4ed1e2d1c 100644 +--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c ++++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +@@ -94,6 +94,7 @@ struct hdmi_vmode { + unsigned int mpixelclock; + unsigned int mpixelrepetitioninput; + unsigned int mpixelrepetitionoutput; ++ unsigned int mtmdsclock; + }; + + struct hdmi_data_info { +@@ -549,7 +550,7 @@ static void hdmi_init_clk_regenerator(struct dw_hdmi *hdmi) + static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi) + { + mutex_lock(&hdmi->audio_mutex); +- hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock, ++ hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock, + hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); + } +@@ -558,7 +559,7 @@ void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate) + { + mutex_lock(&hdmi->audio_mutex); + hdmi->sample_rate = rate; +- hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock, ++ hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock, + hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); + } +@@ -659,6 +660,20 @@ static bool hdmi_bus_fmt_is_yuv422(unsigned int bus_format) + } + } + ++static bool hdmi_bus_fmt_is_yuv420(unsigned int bus_format) ++{ ++ switch (bus_format) { ++ case MEDIA_BUS_FMT_UYYVYY8_0_5X24: ++ case MEDIA_BUS_FMT_UYYVYY10_0_5X30: ++ case MEDIA_BUS_FMT_UYYVYY12_0_5X36: ++ case MEDIA_BUS_FMT_UYYVYY16_0_5X48: ++ return true; ++ ++ default: ++ return false; ++ } ++} ++ + static int hdmi_bus_fmt_color_depth(unsigned int bus_format) + { + switch (bus_format) { +@@ -888,7 +903,8 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi) + u8 val, vp_conf; + + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format) || +- hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) { ++ hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format) || ++ hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) { + switch (hdmi_bus_fmt_color_depth( + hdmi->hdmi_data.enc_out_bus_format)) { + case 8: +@@ -1029,7 +1045,7 @@ EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_write); + + void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi) + { +- unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mpixelclock; ++ unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mtmdsclock; + + /* Control for TMDS Bit Period/TMDS Clock-Period Ratio */ + if (hdmi->connector.display_info.hdmi.scdc.supported) { +@@ -1370,6 +1386,9 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) + struct hdmi_avi_infoframe frame; + u8 val; + ++ if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) ++ is_hdmi2_sink = true; ++ + /* Initialise info frame from DRM mode */ + drm_hdmi_avi_infoframe_from_display_mode(&frame, mode, is_hdmi2_sink); + +@@ -1377,6 +1396,8 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) + frame.colorspace = HDMI_COLORSPACE_YUV444; + else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) + frame.colorspace = HDMI_COLORSPACE_YUV422; ++ else if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) ++ frame.colorspace = HDMI_COLORSPACE_YUV420; + else + frame.colorspace = HDMI_COLORSPACE_RGB; + +@@ -1534,15 +1555,18 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, + struct drm_hdmi_info *hdmi_info = &hdmi->connector.display_info.hdmi; + struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; + int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len; +- unsigned int vdisplay; ++ unsigned int vdisplay, hdisplay; + +- vmode->mpixelclock = mode->clock * 1000; ++ vmode->mtmdsclock = vmode->mpixelclock = mode->clock * 1000; + + dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock); + ++ if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) ++ vmode->mtmdsclock /= 2; ++ + /* Set up HDMI_FC_INVIDCONF */ + inv_val = (hdmi->hdmi_data.hdcp_enable || +- vmode->mpixelclock > 340000000 || ++ vmode->mtmdsclock > 340000000 || + hdmi_info->scdc.scrambling.low_rates ? + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE : + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE); +@@ -1576,6 +1600,22 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, + + hdmi_writeb(hdmi, inv_val, HDMI_FC_INVIDCONF); + ++ hdisplay = mode->hdisplay; ++ hblank = mode->htotal - mode->hdisplay; ++ h_de_hs = mode->hsync_start - mode->hdisplay; ++ hsync_len = mode->hsync_end - mode->hsync_start; ++ ++ /* ++ * When we're setting a YCbCr420 mode, we need ++ * to adjust the horizontal timing to suit. ++ */ ++ if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) { ++ hdisplay /= 2; ++ hblank /= 2; ++ h_de_hs /= 2; ++ hsync_len /= 2; ++ } ++ + vdisplay = mode->vdisplay; + vblank = mode->vtotal - mode->vdisplay; + v_de_vs = mode->vsync_start - mode->vdisplay; +@@ -1594,7 +1634,7 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, + + /* Scrambling Control */ + if (hdmi_info->scdc.supported) { +- if (vmode->mpixelclock > 340000000 || ++ if (vmode->mtmdsclock > 340000000 || + hdmi_info->scdc.scrambling.low_rates) { + drm_scdc_readb(&hdmi->i2c->adap, SCDC_SINK_VERSION, + &bytes); +@@ -1613,15 +1653,14 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, + } + + /* Set up horizontal active pixel width */ +- hdmi_writeb(hdmi, mode->hdisplay >> 8, HDMI_FC_INHACTV1); +- hdmi_writeb(hdmi, mode->hdisplay, HDMI_FC_INHACTV0); ++ hdmi_writeb(hdmi, hdisplay >> 8, HDMI_FC_INHACTV1); ++ hdmi_writeb(hdmi, hdisplay, HDMI_FC_INHACTV0); + + /* Set up vertical active lines */ + hdmi_writeb(hdmi, vdisplay >> 8, HDMI_FC_INVACTV1); + hdmi_writeb(hdmi, vdisplay, HDMI_FC_INVACTV0); + + /* Set up horizontal blanking pixel region width */ +- hblank = mode->htotal - mode->hdisplay; + hdmi_writeb(hdmi, hblank >> 8, HDMI_FC_INHBLANK1); + hdmi_writeb(hdmi, hblank, HDMI_FC_INHBLANK0); + +@@ -1629,7 +1668,6 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, + hdmi_writeb(hdmi, vblank, HDMI_FC_INVBLANK); + + /* Set up HSYNC active edge delay width (in pixel clks) */ +- h_de_hs = mode->hsync_start - mode->hdisplay; + hdmi_writeb(hdmi, h_de_hs >> 8, HDMI_FC_HSYNCINDELAY1); + hdmi_writeb(hdmi, h_de_hs, HDMI_FC_HSYNCINDELAY0); + +@@ -1637,7 +1675,6 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, + hdmi_writeb(hdmi, v_de_vs, HDMI_FC_VSYNCINDELAY); + + /* Set up HSYNC active pulse width (in pixel clks) */ +- hsync_len = mode->hsync_end - mode->hsync_start; + hdmi_writeb(hdmi, hsync_len >> 8, HDMI_FC_HSYNCINWIDTH1); + hdmi_writeb(hdmi, hsync_len, HDMI_FC_HSYNCINWIDTH0); + +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0047-drm-bridge-dw-hdmi-support-dynamically-get-input-out.patch b/buildroot-external/board/hardkernel/patches/linux/0047-drm-bridge-dw-hdmi-support-dynamically-get-input-out.patch new file mode 100644 index 000000000..1cdbe1360 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0047-drm-bridge-dw-hdmi-support-dynamically-get-input-out.patch @@ -0,0 +1,104 @@ +From c65b7b6a68ad5ccae7e861f840f1b45d79396929 Mon Sep 17 00:00:00 2001 +From: Zheng Yang +Date: Tue, 27 Jun 2017 16:22:01 +0800 +Subject: [PATCH 47/53] drm/bridge: dw-hdmi: support dynamically get input/out + color info + +To get input/output bus_format/enc_format dynamically, this patch +introduce following funstion in plat_data: + - get_input_bus_format + - get_output_bus_format + - get_enc_in_encoding + - get_enc_out_encoding + +Signed-off-by: Zheng Yang +Signed-off-by: Neil Armstrong +--- + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 28 +++++++++++++++++------ + include/drm/bridge/dw_hdmi.h | 5 ++++ + 2 files changed, 26 insertions(+), 7 deletions(-) + +diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +index c3e4ed1e2d1c..6473df3068ce 100644 +--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c ++++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +@@ -1774,6 +1774,7 @@ static void hdmi_disable_overflow_interrupts(struct dw_hdmi *hdmi) + static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) + { + int ret; ++ void *data = hdmi->plat_data->phy_data; + + hdmi_disable_overflow_interrupts(hdmi); + +@@ -1785,10 +1786,13 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) + dev_dbg(hdmi->dev, "CEA mode used vic=%d\n", hdmi->vic); + } + +- if ((hdmi->vic == 6) || (hdmi->vic == 7) || +- (hdmi->vic == 21) || (hdmi->vic == 22) || +- (hdmi->vic == 2) || (hdmi->vic == 3) || +- (hdmi->vic == 17) || (hdmi->vic == 18)) ++ if (hdmi->plat_data->get_enc_out_encoding) ++ hdmi->hdmi_data.enc_out_encoding = ++ hdmi->plat_data->get_enc_out_encoding(data); ++ else if ((hdmi->vic == 6) || (hdmi->vic == 7) || ++ (hdmi->vic == 21) || (hdmi->vic == 22) || ++ (hdmi->vic == 2) || (hdmi->vic == 3) || ++ (hdmi->vic == 17) || (hdmi->vic == 18)) + hdmi->hdmi_data.enc_out_encoding = V4L2_YCBCR_ENC_601; + else + hdmi->hdmi_data.enc_out_encoding = V4L2_YCBCR_ENC_709; +@@ -1797,21 +1801,31 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) + hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0; + + /* TOFIX: Get input format from plat data or fallback to RGB888 */ +- if (hdmi->plat_data->input_bus_format) ++ if (hdmi->plat_data->get_input_bus_format) ++ hdmi->hdmi_data.enc_in_bus_format = ++ hdmi->plat_data->get_input_bus_format(data); ++ else if (hdmi->plat_data->input_bus_format) + hdmi->hdmi_data.enc_in_bus_format = + hdmi->plat_data->input_bus_format; + else + hdmi->hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + /* TOFIX: Get input encoding from plat data or fallback to none */ +- if (hdmi->plat_data->input_bus_encoding) ++ if (hdmi->plat_data->get_enc_in_encoding) ++ hdmi->hdmi_data.enc_in_encoding = ++ hdmi->plat_data->get_enc_in_encoding(data); ++ else if (hdmi->plat_data->input_bus_encoding) + hdmi->hdmi_data.enc_in_encoding = + hdmi->plat_data->input_bus_encoding; + else + hdmi->hdmi_data.enc_in_encoding = V4L2_YCBCR_ENC_DEFAULT; + + /* TOFIX: Default to RGB888 output format */ +- hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; ++ if (hdmi->plat_data->get_output_bus_format) ++ hdmi->hdmi_data.enc_out_bus_format = ++ hdmi->plat_data->get_output_bus_format(data); ++ else ++ hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + hdmi->hdmi_data.pix_repet_factor = 0; + hdmi->hdmi_data.hdcp_enable = 0; +diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h +index d7cc5d094270..27f9cce66b6a 100644 +--- a/include/drm/bridge/dw_hdmi.h ++++ b/include/drm/bridge/dw_hdmi.h +@@ -141,6 +141,11 @@ struct dw_hdmi_plat_data { + int (*configure_phy)(struct dw_hdmi *hdmi, + const struct dw_hdmi_plat_data *pdata, + unsigned long mpixelclock); ++ ++ unsigned long (*get_input_bus_format)(void *data); ++ unsigned long (*get_output_bus_format)(void *data); ++ unsigned long (*get_enc_in_encoding)(void *data); ++ unsigned long (*get_enc_out_encoding)(void *data); + }; + + struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0048-drm-bridge-dw-hdmi-allow-ycbcr420-modes-for-0x200a.patch b/buildroot-external/board/hardkernel/patches/linux/0048-drm-bridge-dw-hdmi-allow-ycbcr420-modes-for-0x200a.patch new file mode 100644 index 000000000..7a1c4338a --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0048-drm-bridge-dw-hdmi-allow-ycbcr420-modes-for-0x200a.patch @@ -0,0 +1,48 @@ +From 725830ef41b23e35b282ecf78f682c0c131a0042 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Wed, 14 Nov 2018 17:39:46 +0100 +Subject: [PATCH 48/53] drm/bridge: dw-hdmi: allow ycbcr420 modes for >= 0x200a + +Now the DW-HDMI Controller supports the HDMI2.0 modes, enable support +for these modes in the connector if the platform supports them. +We limit these modes to DW-HDMI IP version >= 0x200a which +are designed to support HDMI2.0 display modes. + +Signed-off-by: Neil Armstrong +--- + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 6 ++++++ + include/drm/bridge/dw_hdmi.h | 1 + + 2 files changed, 7 insertions(+) + +diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +index 6473df3068ce..d10277f9ef0b 100644 +--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c ++++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +@@ -2575,6 +2575,12 @@ __dw_hdmi_probe(struct platform_device *pdev, + if (hdmi->phy.ops->setup_hpd) + hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data); + ++ if (hdmi->version >= 0x200a) ++ hdmi->connector.ycbcr_420_allowed = ++ hdmi->plat_data->ycbcr_420_allowed; ++ else ++ hdmi->connector.ycbcr_420_allowed = false; ++ + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = dev; + pdevinfo.id = PLATFORM_DEVID_AUTO; +diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h +index 27f9cce66b6a..c04f497a919b 100644 +--- a/include/drm/bridge/dw_hdmi.h ++++ b/include/drm/bridge/dw_hdmi.h +@@ -128,6 +128,7 @@ struct dw_hdmi_plat_data { + const struct drm_display_mode *mode); + unsigned long input_bus_format; + unsigned long input_bus_encoding; ++ bool ycbcr_420_allowed; + + /* Vendor PHY support */ + const struct dw_hdmi_phy_ops *phy_ops; +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0049-drm-meson-Add-YUV420-output-support.patch b/buildroot-external/board/hardkernel/patches/linux/0049-drm-meson-Add-YUV420-output-support.patch new file mode 100644 index 000000000..7c98e9793 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0049-drm-meson-Add-YUV420-output-support.patch @@ -0,0 +1,584 @@ +From d27e2f1dac6e94f845d83725481adf9fc1c9bb21 Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Thu, 15 Nov 2018 16:41:23 +0100 +Subject: [PATCH 49/53] drm/meson: Add YUV420 output support + +This patch adds support for the YUV420 output from the Amlogic Meson SoCs +Video Processing Unit to the HDMI Controller. + +The YUV420 is obtained by generating a YUV444 pixel stream like +the classic HDMI display modes, but then the Video Encoder output +can be configured to down-sample the YUV444 pixel stream to a YUV420 +stream. +In addition if pixel stream down-sampling, the Y Cb Cr components must +also be mapped differently to align with the HDMI2.0 specifications. + +This mode needs a different clock generation scheme since the TMDS PHY +clock must match the 10x ration with the YUV420 pixel clock, but +the video encoder must run at 2x the pixel clock. + +This patch adds the TMDS PHY clock value in all the video clock setup +in order to better support these specific uses cases and switch +to the Common Clock framework for clocks handling in the future. + +Signed-off-by: Neil Armstrong +--- + drivers/gpu/drm/meson/meson_dw_hdmi.c | 108 ++++++++++++++++++++---- + drivers/gpu/drm/meson/meson_vclk.c | 95 ++++++++++++++++----- + drivers/gpu/drm/meson/meson_vclk.h | 7 +- + drivers/gpu/drm/meson/meson_venc.c | 6 +- + drivers/gpu/drm/meson/meson_venc.h | 11 +++ + drivers/gpu/drm/meson/meson_venc_cvbs.c | 3 +- + 6 files changed, 184 insertions(+), 46 deletions(-) + +diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c +index b8775102b100..83360f37d9ce 100644 +--- a/drivers/gpu/drm/meson/meson_dw_hdmi.c ++++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c +@@ -141,6 +141,8 @@ struct meson_dw_hdmi { + struct regulator *hdmi_supply; + u32 irq_stat; + struct dw_hdmi *hdmi; ++ unsigned long input_bus_format; ++ unsigned long output_bus_format; + }; + #define encoder_to_meson_dw_hdmi(x) \ + container_of(x, struct meson_dw_hdmi, encoder) +@@ -323,25 +325,36 @@ static void dw_hdmi_set_vclk(struct meson_dw_hdmi *dw_hdmi, + { + struct meson_drm *priv = dw_hdmi->priv; + int vic = drm_match_cea_mode(mode); ++ unsigned int phy_freq; + unsigned int vclk_freq; + unsigned int venc_freq; + unsigned int hdmi_freq; + + vclk_freq = mode->clock; + ++ /* For 420, pixel clock is half unlike venc clock */ ++ if (dw_hdmi->input_bus_format == MEDIA_BUS_FMT_UYYVYY8_0_5X24) ++ vclk_freq /= 2; ++ ++ /* TMDS clock is pixel_clock * 10 */ ++ phy_freq = vclk_freq * 10; ++ + if (!vic) { +- meson_vclk_setup(priv, MESON_VCLK_TARGET_DMT, vclk_freq, +- vclk_freq, vclk_freq, false); ++ meson_vclk_setup(priv, MESON_VCLK_TARGET_DMT, phy_freq, ++ vclk_freq, vclk_freq, vclk_freq, false); + return; + } + ++ /* 480i/576i needs global pixel doubling */ + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + vclk_freq *= 2; + + venc_freq = vclk_freq; + hdmi_freq = vclk_freq; + +- if (meson_venc_hdmi_venc_repeat(vic)) ++ /* VENC double pixels for 1080i, 720p and YUV420 modes */ ++ if (meson_venc_hdmi_venc_repeat(vic) || ++ dw_hdmi->input_bus_format == MEDIA_BUS_FMT_UYYVYY8_0_5X24) + venc_freq *= 2; + + vclk_freq = max(venc_freq, hdmi_freq); +@@ -349,11 +362,11 @@ static void dw_hdmi_set_vclk(struct meson_dw_hdmi *dw_hdmi, + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + venc_freq /= 2; + +- DRM_DEBUG_DRIVER("vclk:%d venc=%d hdmi=%d enci=%d\n", +- vclk_freq, venc_freq, hdmi_freq, ++ DRM_DEBUG_DRIVER("vclk:%d phy=%d venc=%d hdmi=%d enci=%d\n", ++ phy_freq, vclk_freq, venc_freq, hdmi_freq, + priv->venc.hdmi_use_enci); + +- meson_vclk_setup(priv, MESON_VCLK_TARGET_HDMI, vclk_freq, ++ meson_vclk_setup(priv, MESON_VCLK_TARGET_HDMI, phy_freq, vclk_freq, + venc_freq, hdmi_freq, priv->venc.hdmi_use_enci); + } + +@@ -387,7 +400,8 @@ static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, + dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_BIST_CNTL, BIT(12)); + + /* TMDS pattern setup (TOFIX Handle the YUV420 case) */ +- if (mode->clock > 340000) { ++ if (mode->clock > 340000 && ++ dw_hdmi->input_bus_format == MEDIA_BUS_FMT_YUV8_1X24) { + dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, 0); + dw_hdmi_top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, + 0x03ff03ff); +@@ -560,6 +574,8 @@ dw_hdmi_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode) + { + struct meson_drm *priv = connector->dev->dev_private; ++ bool is_hdmi2_sink = connector->display_info.hdmi.scdc.supported; ++ unsigned int phy_freq; + unsigned int vclk_freq; + unsigned int venc_freq; + unsigned int hdmi_freq; +@@ -573,9 +589,11 @@ dw_hdmi_mode_valid(struct drm_connector *connector, + mode->vdisplay, mode->vsync_start, + mode->vsync_end, mode->vtotal, mode->type, mode->flags); + +- /* If sink max TMDS clock < 340MHz, we reject the HDMI2.0 modes */ ++ /* If sink does not support 540MHz, reject the non-420 HDMI2 modes */ + if (mode->clock > 340000 && +- connector->display_info.max_tmds_clock < 340000) ++ connector->display_info.max_tmds_clock < 340000 && ++ !drm_mode_is_420_only(&connector->display_info, mode) && ++ !drm_mode_is_420_also(&connector->display_info, mode)) + return MODE_BAD; + + /* Check against non-VIC supported modes */ +@@ -591,6 +609,15 @@ dw_hdmi_mode_valid(struct drm_connector *connector, + + vclk_freq = mode->clock; + ++ /* For 420, pixel clock is half unlike venc clock */ ++ if (drm_mode_is_420_only(&connector->display_info, mode) || ++ (!is_hdmi2_sink && ++ drm_mode_is_420_also(&connector->display_info, mode))) ++ vclk_freq /= 2; ++ ++ /* TMDS clock is pixel_clock * 10 */ ++ phy_freq = vclk_freq * 10; ++ + /* 480i/576i needs global pixel doubling */ + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + vclk_freq *= 2; +@@ -598,8 +625,11 @@ dw_hdmi_mode_valid(struct drm_connector *connector, + venc_freq = vclk_freq; + hdmi_freq = vclk_freq; + +- /* VENC double pixels for 1080i and 720p modes */ +- if (meson_venc_hdmi_venc_repeat(vic)) ++ /* VENC double pixels for 1080i, 720p and YUV420 modes */ ++ if (meson_venc_hdmi_venc_repeat(vic) || ++ drm_mode_is_420_only(&connector->display_info, mode) || ++ (!is_hdmi2_sink && ++ drm_mode_is_420_also(&connector->display_info, mode))) + venc_freq *= 2; + + vclk_freq = max(venc_freq, hdmi_freq); +@@ -607,10 +637,10 @@ dw_hdmi_mode_valid(struct drm_connector *connector, + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + venc_freq /= 2; + +- dev_dbg(connector->dev->dev, "%s: vclk:%d venc=%d hdmi=%d\n", __func__, +- vclk_freq, venc_freq, hdmi_freq); ++ dev_dbg(connector->dev->dev, "%s: vclk:%d phy=%d venc=%d hdmi=%d\n", ++ __func__, phy_freq, vclk_freq, venc_freq, hdmi_freq); + +- return meson_vclk_vic_supported_freq(vclk_freq); ++ return meson_vclk_vic_supported_freq(phy_freq, vclk_freq); + } + + /* Encoder */ +@@ -628,6 +658,21 @@ static int meson_venc_hdmi_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) + { ++ struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder); ++ struct drm_display_info *info = &conn_state->connector->display_info; ++ struct drm_display_mode *mode = &crtc_state->mode; ++ bool is_hdmi2_sink = ++ conn_state->connector->display_info.hdmi.scdc.supported; ++ ++ if (drm_mode_is_420_only(info, mode) || ++ (!is_hdmi2_sink && drm_mode_is_420_also(info, mode))) { ++ dw_hdmi->input_bus_format = MEDIA_BUS_FMT_UYYVYY8_0_5X24; ++ dw_hdmi->output_bus_format = MEDIA_BUS_FMT_UYYVYY8_0_5X24; ++ } else { ++ dw_hdmi->input_bus_format = MEDIA_BUS_FMT_YUV8_1X24; ++ dw_hdmi->output_bus_format = MEDIA_BUS_FMT_RGB888_1X24; ++ } ++ + return 0; + } + +@@ -665,18 +710,30 @@ static void meson_venc_hdmi_encoder_mode_set(struct drm_encoder *encoder, + struct meson_dw_hdmi *dw_hdmi = encoder_to_meson_dw_hdmi(encoder); + struct meson_drm *priv = dw_hdmi->priv; + int vic = drm_match_cea_mode(mode); ++ unsigned int ycrcb_map = MESON_VENC_MAP_CB_Y_CR; ++ bool yuv420_mode = false; + + DRM_DEBUG_DRIVER("%d:\"%s\" vic %d\n", + mode->base.id, mode->name, vic); + ++ if (dw_hdmi->input_bus_format == MEDIA_BUS_FMT_UYYVYY8_0_5X24) { ++ ycrcb_map = MESON_VENC_MAP_CR_Y_CB; ++ yuv420_mode = true; ++ } ++ + /* VENC + VENC-DVI Mode setup */ +- meson_venc_hdmi_mode_set(priv, vic, mode); ++ meson_venc_hdmi_mode_set(priv, vic, ycrcb_map, yuv420_mode, mode); + + /* VCLK Set clock */ + dw_hdmi_set_vclk(dw_hdmi, mode); + +- /* Setup YUV444 to HDMI-TX, no 10bit diphering */ +- writel_relaxed(0, priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); ++ if (dw_hdmi->input_bus_format == MEDIA_BUS_FMT_UYYVYY8_0_5X24) ++ /* Setup YUV420 to HDMI-TX, no 10bit diphering */ ++ writel_relaxed(2 | (2 << 2), ++ priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); ++ else ++ /* Setup YUV444 to HDMI-TX, no 10bit diphering */ ++ writel_relaxed(0, priv->io_base + _REG(VPU_HDMI_FMT_CTRL)); + } + + static const struct drm_encoder_helper_funcs +@@ -715,6 +772,20 @@ static const struct regmap_config meson_dw_hdmi_regmap_config = { + .fast_io = true, + }; + ++static unsigned long meson_dw_hdmi_get_in_bus_format(void *data) ++{ ++ struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; ++ ++ return dw_hdmi->input_bus_format; ++} ++ ++static unsigned long meson_dw_hdmi_get_out_bus_format(void *data) ++{ ++ struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; ++ ++ return dw_hdmi->output_bus_format; ++} ++ + static bool meson_hdmi_connector_is_available(struct device *dev) + { + struct device_node *ep, *remote; +@@ -891,6 +962,9 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, + dw_plat_data->phy_data = meson_dw_hdmi; + dw_plat_data->input_bus_format = MEDIA_BUS_FMT_YUV8_1X24; + dw_plat_data->input_bus_encoding = V4L2_YCBCR_ENC_709; ++ dw_plat_data->get_input_bus_format = meson_dw_hdmi_get_in_bus_format; ++ dw_plat_data->get_output_bus_format = meson_dw_hdmi_get_out_bus_format; ++ dw_plat_data->ycbcr_420_allowed = true; + + platform_set_drvdata(pdev, meson_dw_hdmi); + +diff --git a/drivers/gpu/drm/meson/meson_vclk.c b/drivers/gpu/drm/meson/meson_vclk.c +index 5acccebd026d..27c9c5ead234 100644 +--- a/drivers/gpu/drm/meson/meson_vclk.c ++++ b/drivers/gpu/drm/meson/meson_vclk.c +@@ -337,12 +337,17 @@ enum { + /* 2970 /1 /1 /1 /5 /2 => /1 /1 */ + MESON_VCLK_HDMI_297000, + /* 5940 /1 /1 /2 /5 /1 => /1 /1 */ +- MESON_VCLK_HDMI_594000 ++ MESON_VCLK_HDMI_594000, ++/* 2970 /1 /1 /1 /5 /1 => /1 /2 */ ++ MESON_VCLK_HDMI_594000_YUV420, + }; + + struct meson_vclk_params { ++ unsigned int pll_freq; ++ unsigned int phy_freq; ++ unsigned int vclk_freq; ++ unsigned int venc_freq; + unsigned int pixel_freq; +- unsigned int pll_base_freq; + unsigned int pll_od1; + unsigned int pll_od2; + unsigned int pll_od3; +@@ -350,8 +355,11 @@ struct meson_vclk_params { + unsigned int vclk_div; + } params[] = { + [MESON_VCLK_HDMI_ENCI_54000] = { ++ .pll_freq = 4320000, ++ .phy_freq = 270000, ++ .vclk_freq = 54000, ++ .venc_freq = 54000, + .pixel_freq = 54000, +- .pll_base_freq = 4320000, + .pll_od1 = 4, + .pll_od2 = 4, + .pll_od3 = 1, +@@ -359,8 +367,11 @@ struct meson_vclk_params { + .vclk_div = 1, + }, + [MESON_VCLK_HDMI_DDR_54000] = { +- .pixel_freq = 54000, +- .pll_base_freq = 4320000, ++ .pll_freq = 4320000, ++ .phy_freq = 270000, ++ .vclk_freq = 54000, ++ .venc_freq = 54000, ++ .pixel_freq = 27000, + .pll_od1 = 4, + .pll_od2 = 4, + .pll_od3 = 1, +@@ -368,8 +379,11 @@ struct meson_vclk_params { + .vclk_div = 1, + }, + [MESON_VCLK_HDMI_DDR_148500] = { +- .pixel_freq = 148500, +- .pll_base_freq = 2970000, ++ .pll_freq = 2970000, ++ .phy_freq = 742500, ++ .vclk_freq = 148500, ++ .venc_freq = 148500, ++ .pixel_freq = 74250, + .pll_od1 = 4, + .pll_od2 = 1, + .pll_od3 = 1, +@@ -377,8 +391,11 @@ struct meson_vclk_params { + .vclk_div = 1, + }, + [MESON_VCLK_HDMI_74250] = { ++ .pll_freq = 2970000, ++ .phy_freq = 742500, ++ .vclk_freq = 74250, ++ .venc_freq = 74250, + .pixel_freq = 74250, +- .pll_base_freq = 2970000, + .pll_od1 = 2, + .pll_od2 = 2, + .pll_od3 = 2, +@@ -386,8 +403,11 @@ struct meson_vclk_params { + .vclk_div = 1, + }, + [MESON_VCLK_HDMI_148500] = { ++ .pll_freq = 2970000, ++ .phy_freq = 1485000, ++ .vclk_freq = 148500, ++ .venc_freq = 148500, + .pixel_freq = 148500, +- .pll_base_freq = 2970000, + .pll_od1 = 1, + .pll_od2 = 2, + .pll_od3 = 2, +@@ -395,8 +415,11 @@ struct meson_vclk_params { + .vclk_div = 1, + }, + [MESON_VCLK_HDMI_297000] = { ++ .pll_freq = 2970000, ++ .phy_freq = 2970000, ++ .venc_freq = 297000, ++ .vclk_freq = 297000, + .pixel_freq = 297000, +- .pll_base_freq = 2970000, + .pll_od1 = 1, + .pll_od2 = 1, + .pll_od3 = 1, +@@ -404,14 +427,29 @@ struct meson_vclk_params { + .vclk_div = 2, + }, + [MESON_VCLK_HDMI_594000] = { ++ .pll_freq = 5940000, ++ .phy_freq = 5940000, ++ .venc_freq = 594000, ++ .vclk_freq = 594000, + .pixel_freq = 594000, +- .pll_base_freq = 5940000, + .pll_od1 = 1, + .pll_od2 = 1, + .pll_od3 = 2, + .vid_pll_div = VID_PLL_DIV_5, + .vclk_div = 1, + }, ++ [MESON_VCLK_HDMI_594000_YUV420] = { ++ .pll_freq = 2970000, ++ .phy_freq = 2970000, ++ .venc_freq = 594000, ++ .vclk_freq = 594000, ++ .pixel_freq = 297000, ++ .pll_od1 = 1, ++ .pll_od2 = 1, ++ .pll_od3 = 1, ++ .vid_pll_div = VID_PLL_DIV_5, ++ .vclk_div = 1, ++ }, + { /* sentinel */ }, + }; + +@@ -616,6 +654,7 @@ static void meson_hdmi_pll_generic_set(struct meson_drm *priv, + unsigned int od, m, frac, od1, od2, od3; + + if (meson_hdmi_pll_find_params(priv, pll_freq, &m, &frac, &od)) { ++ /* OD2 goes to the PHY, and needs to be *10, so keep OD3=1 */ + od3 = 1; + if (od < 4) { + od1 = 2; +@@ -638,21 +677,28 @@ static void meson_hdmi_pll_generic_set(struct meson_drm *priv, + } + + enum drm_mode_status +-meson_vclk_vic_supported_freq(unsigned int freq) ++meson_vclk_vic_supported_freq(unsigned int phy_freq, ++ unsigned int vclk_freq) + { + int i; + +- DRM_DEBUG_DRIVER("freq = %d\n", freq); ++ DRM_DEBUG_DRIVER("phy_freq = %d vclk_freq = %d\n", ++ phy_freq, vclk_freq); + + for (i = 0 ; params[i].pixel_freq ; ++i) { + DRM_DEBUG_DRIVER("i = %d pixel_freq = %d alt = %d\n", + i, params[i].pixel_freq, + FREQ_1000_1001(params[i].pixel_freq)); ++ DRM_DEBUG_DRIVER("i = %d phy_freq = %d alt = %d\n", ++ i, params[i].phy_freq, ++ FREQ_1000_1001(params[i].phy_freq/10)*10); + /* Match strict frequency */ +- if (freq == params[i].pixel_freq) ++ if (phy_freq == params[i].phy_freq && ++ vclk_freq == params[i].vclk_freq) + return MODE_OK; + /* Match 1000/1001 variant */ +- if (freq == FREQ_1000_1001(params[i].pixel_freq)) ++ if (phy_freq == (FREQ_1000_1001(params[i].phy_freq/10)*10) && ++ vclk_freq == FREQ_1000_1001(params[i].vclk_freq)) + return MODE_OK; + } + +@@ -666,7 +712,7 @@ static void meson_vclk_set(struct meson_drm *priv, unsigned int pll_base_freq, + unsigned int hdmi_tx_div, unsigned int venc_div, + bool hdmi_use_enci, bool vic_alternate_clock) + { +- unsigned int m, frac; ++ unsigned int m = 0, frac = 0; + + /* Set HDMI-TX sys clock */ + regmap_update_bits(priv->hhi, HHI_HDMI_CLK_CNTL, +@@ -863,8 +909,9 @@ static void meson_vclk_set(struct meson_drm *priv, unsigned int pll_base_freq, + } + + void meson_vclk_setup(struct meson_drm *priv, unsigned int target, +- unsigned int vclk_freq, unsigned int venc_freq, +- unsigned int dac_freq, bool hdmi_use_enci) ++ unsigned int phy_freq, unsigned int vclk_freq, ++ unsigned int venc_freq, unsigned int dac_freq, ++ bool hdmi_use_enci) + { + bool vic_alternate_clock = false; + unsigned int freq; +@@ -883,7 +930,7 @@ void meson_vclk_setup(struct meson_drm *priv, unsigned int target, + * - venc_div = 1 + * - encp encoder + */ +- meson_vclk_set(priv, vclk_freq * 10, 0, 0, 0, ++ meson_vclk_set(priv, phy_freq, 0, 0, 0, + VID_PLL_DIV_5, 2, 1, 1, false, false); + return; + } +@@ -905,9 +952,11 @@ void meson_vclk_setup(struct meson_drm *priv, unsigned int target, + } + + for (freq = 0 ; params[freq].pixel_freq ; ++freq) { +- if (vclk_freq == params[freq].pixel_freq || +- vclk_freq == FREQ_1000_1001(params[freq].pixel_freq)) { +- if (vclk_freq != params[freq].pixel_freq) ++ if ((phy_freq == params[freq].phy_freq || ++ phy_freq == FREQ_1000_1001(params[freq].phy_freq/10)*10) && ++ (vclk_freq == params[freq].vclk_freq || ++ vclk_freq == FREQ_1000_1001(params[freq].vclk_freq))) { ++ if (vclk_freq != params[freq].vclk_freq) + vic_alternate_clock = true; + else + vic_alternate_clock = false; +@@ -936,7 +985,7 @@ void meson_vclk_setup(struct meson_drm *priv, unsigned int target, + return; + } + +- meson_vclk_set(priv, params[freq].pll_base_freq, ++ meson_vclk_set(priv, params[freq].pll_freq, + params[freq].pll_od1, params[freq].pll_od2, + params[freq].pll_od3, params[freq].vid_pll_div, + params[freq].vclk_div, hdmi_tx_div, venc_div, +diff --git a/drivers/gpu/drm/meson/meson_vclk.h b/drivers/gpu/drm/meson/meson_vclk.h +index 4bd8752da02a..c4d19ddfcd79 100644 +--- a/drivers/gpu/drm/meson/meson_vclk.h ++++ b/drivers/gpu/drm/meson/meson_vclk.h +@@ -33,10 +33,11 @@ enum { + enum drm_mode_status + meson_vclk_dmt_supported_freq(struct meson_drm *priv, unsigned int freq); + enum drm_mode_status +-meson_vclk_vic_supported_freq(unsigned int freq); ++meson_vclk_vic_supported_freq(unsigned int phy_freq, unsigned int vclk_freq); + + void meson_vclk_setup(struct meson_drm *priv, unsigned int target, +- unsigned int vclk_freq, unsigned int venc_freq, +- unsigned int dac_freq, bool hdmi_use_enci); ++ unsigned int phy_freq, unsigned int vclk_freq, ++ unsigned int venc_freq, unsigned int dac_freq, ++ bool hdmi_use_enci); + + #endif /* __MESON_VCLK_H */ +diff --git a/drivers/gpu/drm/meson/meson_venc.c b/drivers/gpu/drm/meson/meson_venc.c +index 1bcd642b6e42..ab72ddda242d 100644 +--- a/drivers/gpu/drm/meson/meson_venc.c ++++ b/drivers/gpu/drm/meson/meson_venc.c +@@ -956,6 +956,8 @@ bool meson_venc_hdmi_venc_repeat(int vic) + EXPORT_SYMBOL_GPL(meson_venc_hdmi_venc_repeat); + + void meson_venc_hdmi_mode_set(struct meson_drm *priv, int vic, ++ unsigned int ycrcb_map, ++ bool yuv420_mode, + struct drm_display_mode *mode) + { + union meson_hdmi_venc_mode *vmode = NULL; +@@ -1505,8 +1507,8 @@ void meson_venc_hdmi_mode_set(struct meson_drm *priv, int vic, + writel_relaxed((use_enci ? 1 : 2) | + (mode->flags & DRM_MODE_FLAG_PHSYNC ? 1 << 2 : 0) | + (mode->flags & DRM_MODE_FLAG_PVSYNC ? 1 << 3 : 0) | +- 4 << 5 | +- (venc_repeat ? 1 << 8 : 0) | ++ (ycrcb_map << 5) | ++ (venc_repeat || yuv420_mode ? 1 << 8 : 0) | + (hdmi_repeat ? 1 << 12 : 0), + priv->io_base + _REG(VPU_HDMI_SETTING)); + +diff --git a/drivers/gpu/drm/meson/meson_venc.h b/drivers/gpu/drm/meson/meson_venc.h +index 97eaebbfa0c4..5580bf38e381 100644 +--- a/drivers/gpu/drm/meson/meson_venc.h ++++ b/drivers/gpu/drm/meson/meson_venc.h +@@ -33,6 +33,15 @@ enum { + MESON_VENC_MODE_HDMI, + }; + ++enum { ++ MESON_VENC_MAP_CR_Y_CB = 0, ++ MESON_VENC_MAP_Y_CB_CR, ++ MESON_VENC_MAP_Y_CR_CB, ++ MESON_VENC_MAP_CB_CR_Y, ++ MESON_VENC_MAP_CB_Y_CR, ++ MESON_VENC_MAP_CR_CB_Y, ++}; ++ + struct meson_cvbs_enci_mode { + unsigned int mode_tag; + unsigned int hso_begin; /* HSO begin position */ +@@ -70,6 +79,8 @@ extern struct meson_cvbs_enci_mode meson_cvbs_enci_ntsc; + void meson_venci_cvbs_mode_set(struct meson_drm *priv, + struct meson_cvbs_enci_mode *mode); + void meson_venc_hdmi_mode_set(struct meson_drm *priv, int vic, ++ unsigned int ycrcb_map, ++ bool yuv420_mode, + struct drm_display_mode *mode); + unsigned int meson_venci_get_field(struct meson_drm *priv); + +diff --git a/drivers/gpu/drm/meson/meson_venc_cvbs.c b/drivers/gpu/drm/meson/meson_venc_cvbs.c +index f7945bae3b4a..38a1117b1183 100644 +--- a/drivers/gpu/drm/meson/meson_venc_cvbs.c ++++ b/drivers/gpu/drm/meson/meson_venc_cvbs.c +@@ -207,7 +207,8 @@ static void meson_venc_cvbs_encoder_mode_set(struct drm_encoder *encoder, + /* Setup 27MHz vclk2 for ENCI and VDAC */ + meson_vclk_setup(priv, MESON_VCLK_TARGET_CVBS, + MESON_VCLK_CVBS, MESON_VCLK_CVBS, +- MESON_VCLK_CVBS, true); ++ MESON_VCLK_CVBS, MESON_VCLK_CVBS, ++ true); + break; + } + } +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0050-drm-meson-Output-in-YUV444-if-sink-supports-it.patch b/buildroot-external/board/hardkernel/patches/linux/0050-drm-meson-Output-in-YUV444-if-sink-supports-it.patch new file mode 100644 index 000000000..fe2c26c22 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0050-drm-meson-Output-in-YUV444-if-sink-supports-it.patch @@ -0,0 +1,33 @@ +From 2b420ad1ebade69b262cb1cf36668134d7bd785c Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Sun, 18 Nov 2018 14:06:11 +0100 +Subject: [PATCH 50/53] drm/meson: Output in YUV444 if sink supports it + +With the YUV420 handling, we can no dynamically setup the HDMI output +pixel format depending on the mode and connector info. +So now, we can output in YUV444, which is the native video pipeline +format, directly the the HDMI Sink it it's supported, without +involving the HDMI Controller CSC. +--- + drivers/gpu/drm/meson/meson_dw_hdmi.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c +index 83360f37d9ce..1b7092ab1be8 100644 +--- a/drivers/gpu/drm/meson/meson_dw_hdmi.c ++++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c +@@ -670,7 +670,10 @@ static int meson_venc_hdmi_encoder_atomic_check(struct drm_encoder *encoder, + dw_hdmi->output_bus_format = MEDIA_BUS_FMT_UYYVYY8_0_5X24; + } else { + dw_hdmi->input_bus_format = MEDIA_BUS_FMT_YUV8_1X24; +- dw_hdmi->output_bus_format = MEDIA_BUS_FMT_RGB888_1X24; ++ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444) ++ dw_hdmi->output_bus_format = MEDIA_BUS_FMT_YUV8_1X24; ++ else ++ dw_hdmi->output_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + } + + return 0; +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0051-drm-meson-Fix-an-Alpha-Primary-Plane-bug-on-Meson-GX.patch b/buildroot-external/board/hardkernel/patches/linux/0051-drm-meson-Fix-an-Alpha-Primary-Plane-bug-on-Meson-GX.patch new file mode 100644 index 000000000..550a4bb9a --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0051-drm-meson-Fix-an-Alpha-Primary-Plane-bug-on-Meson-GX.patch @@ -0,0 +1,126 @@ +From fa6cb8f89a7f9387a0299f6b55bc0cd54233aefd Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Thu, 22 Nov 2018 17:27:20 +0100 +Subject: [PATCH 51/53] drm/meson: Fix an Alpha Primary Plane bug on Meson + GXL/GXM SoCs + +On the Amlogic GXL & GXM SoCs, a bug occurs in the OSD1 plane when +alpha is used where the alpha is not aligned with the pixel content. + +The woraround Amlogic implemented is the reset the OSD1 plane hardware +block each time the plane is updated, solving the issue. + +In the reset, we still need to save the content of 2 registers which +depends on the status of the plane, in addition to reload the scaler +conversion matrix in the same time. + +Signed-off-by: Neil Armstrong +--- + drivers/gpu/drm/meson/meson_crtc.c | 1 + + drivers/gpu/drm/meson/meson_plane.c | 12 ++++++++++++ + drivers/gpu/drm/meson/meson_viu.c | 27 +++++++++++++++++++++++++++ + drivers/gpu/drm/meson/meson_viu.h | 1 + + 4 files changed, 41 insertions(+) + +diff --git a/drivers/gpu/drm/meson/meson_crtc.c b/drivers/gpu/drm/meson/meson_crtc.c +index 23df4abd95c9..f13e5b6b7a50 100644 +--- a/drivers/gpu/drm/meson/meson_crtc.c ++++ b/drivers/gpu/drm/meson/meson_crtc.c +@@ -183,6 +183,7 @@ void meson_crtc_irq(struct meson_drm *priv) + + /* Update the OSD registers */ + if (priv->viu.osd1_enabled && priv->viu.osd1_commit) { ++ + writel_relaxed(priv->viu.osd1_ctrl_stat, + priv->io_base + _REG(VIU_OSD1_CTRL_STAT)); + writel_relaxed(priv->viu.osd1_blk0_cfg[0], +diff --git a/drivers/gpu/drm/meson/meson_plane.c b/drivers/gpu/drm/meson/meson_plane.c +index 12a47b4f65a5..837228847675 100644 +--- a/drivers/gpu/drm/meson/meson_plane.c ++++ b/drivers/gpu/drm/meson/meson_plane.c +@@ -79,6 +79,7 @@ + struct meson_plane { + struct drm_plane base; + struct meson_drm *priv; ++ bool enabled; + }; + #define to_meson_plane(x) container_of(x, struct meson_plane, base) + +@@ -303,6 +304,15 @@ static void meson_plane_atomic_update(struct drm_plane *plane, + priv->viu.osd1_stride = fb->pitches[0]; + priv->viu.osd1_height = fb->height; + ++ if (!meson_plane->enabled) { ++ /* Reset OSD1 at updates on GXL+ SoCs */ ++ if (meson_vpu_is_compatible(priv, "amlogic,meson-gxm-vpu") || ++ meson_vpu_is_compatible(priv, "amlogic,meson-gxl-vpu")) ++ meson_viu_reset(priv); ++ ++ meson_plane->enabled = true; ++ } ++ + spin_unlock_irqrestore(&priv->drm->event_lock, flags); + } + +@@ -316,6 +326,8 @@ static void meson_plane_atomic_disable(struct drm_plane *plane, + writel_bits_relaxed(VPP_OSD1_POSTBLEND, 0, + priv->io_base + _REG(VPP_MISC)); + ++ meson_plane->enabled = false; ++ + } + + static const struct drm_plane_helper_funcs meson_plane_helper_funcs = { +diff --git a/drivers/gpu/drm/meson/meson_viu.c b/drivers/gpu/drm/meson/meson_viu.c +index 90d9ae3c2b81..366f7e523d15 100644 +--- a/drivers/gpu/drm/meson/meson_viu.c ++++ b/drivers/gpu/drm/meson/meson_viu.c +@@ -296,6 +296,33 @@ static void meson_viu_load_matrix(struct meson_drm *priv) + true); + } + ++/* VIU OSD1 Reset as workaround for GXL+ Alpha OSD Bug */ ++void meson_viu_reset(struct meson_drm *priv) ++{ ++ uint32_t osd1_fifo_ctrl_stat, osd1_ctrl_stat2; ++ ++ /* Save these 2 registers state */ ++ osd1_fifo_ctrl_stat = readl_relaxed( ++ priv->io_base + _REG(VIU_OSD1_FIFO_CTRL_STAT)); ++ osd1_ctrl_stat2 = readl_relaxed( ++ priv->io_base + _REG(VIU_OSD1_CTRL_STAT2)); ++ ++ /* Reset OSD1 */ ++ writel_bits_relaxed(BIT(0), BIT(0), ++ priv->io_base + _REG(VIU_SW_RESET)); ++ writel_bits_relaxed(BIT(0), 0, ++ priv->io_base + _REG(VIU_SW_RESET)); ++ ++ /* Rewrite these registers state lost in the reset */ ++ writel_relaxed(osd1_fifo_ctrl_stat, ++ priv->io_base + _REG(VIU_OSD1_FIFO_CTRL_STAT)); ++ writel_relaxed(osd1_ctrl_stat2, ++ priv->io_base + _REG(VIU_OSD1_CTRL_STAT2)); ++ ++ /* Reload the conversion matrix */ ++ meson_viu_load_matrix(priv); ++} ++ + void meson_viu_init(struct meson_drm *priv) + { + uint32_t reg; +diff --git a/drivers/gpu/drm/meson/meson_viu.h b/drivers/gpu/drm/meson/meson_viu.h +index 073b1910bd1b..e4a6e2fba8fb 100644 +--- a/drivers/gpu/drm/meson/meson_viu.h ++++ b/drivers/gpu/drm/meson/meson_viu.h +@@ -59,6 +59,7 @@ + #define OSD_REPLACE_EN BIT(14) + #define OSD_REPLACE_SHIFT 6 + ++void meson_viu_reset(struct meson_drm *priv); + void meson_viu_init(struct meson_drm *priv); + + #endif /* __MESON_VIU_H */ +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0052-arm64-dts-meson-Fix-IRQ-trigger-type-for-macirq.patch b/buildroot-external/board/hardkernel/patches/linux/0052-arm64-dts-meson-Fix-IRQ-trigger-type-for-macirq.patch new file mode 100644 index 000000000..abfb0d895 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0052-arm64-dts-meson-Fix-IRQ-trigger-type-for-macirq.patch @@ -0,0 +1,51 @@ +From 5e2e26abc73f9bfae1e5edbcdea7e53e9822f2fb Mon Sep 17 00:00:00 2001 +From: Carlo Caione +Date: Tue, 4 Dec 2018 16:04:46 +0000 +Subject: [PATCH 52/53] arm64: dts: meson: Fix IRQ trigger type for macirq + +A long running stress test on a custom board shipping an AXG SoCs and a +Realtek RTL8211F PHY revealed that after a few hours the connection +speed would drop drastically, from ~1000Mbps to ~3Mbps. At the same time +the 'macirq' (eth0) IRQ would stop being triggered at all and as +consequence the GMAC IRQs never ACKed. + +After a painful investigation the problem seemed to be due to a wrong +defined IRQ type for the GMAC IRQ that should be LEVEL_HIGH instead of +EDGE_RISING. + +Signed-off-by: Carlo Caione +Acked-by: Neil Armstrong +--- + arch/arm64/boot/dts/amlogic/meson-axg.dtsi | 2 +- + arch/arm64/boot/dts/amlogic/meson-gx.dtsi | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi +index c518130e5ce7..81dcbde9e674 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi +@@ -461,7 +461,7 @@ + compatible = "amlogic,meson-gxbb-dwmac", "snps,dwmac"; + reg = <0x0 0xff3f0000 0x0 0x10000 + 0x0 0xff634540 0x0 0x8>; +- interrupts = ; ++ interrupts = ; + interrupt-names = "macirq"; + clocks = <&clkc CLKID_ETH>, + <&clkc CLKID_FCLK_DIV2>, +diff --git a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi +index 5d2820ef9a88..d03737acbae1 100644 +--- a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi ++++ b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi +@@ -511,7 +511,7 @@ + compatible = "amlogic,meson-gx-dwmac", "amlogic,meson-gxbb-dwmac", "snps,dwmac"; + reg = <0x0 0xc9410000 0x0 0x10000 + 0x0 0xc8834540 0x0 0x4>; +- interrupts = ; ++ interrupts = ; + interrupt-names = "macirq"; + status = "disabled"; + }; +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/0053-drm-meson-fix-max-mode_config-height-width.patch b/buildroot-external/board/hardkernel/patches/linux/0053-drm-meson-fix-max-mode_config-height-width.patch new file mode 100644 index 000000000..19845bf4f --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/0053-drm-meson-fix-max-mode_config-height-width.patch @@ -0,0 +1,36 @@ +From 47a653adc82602d0d8b21065807164f87c89c82b Mon Sep 17 00:00:00 2001 +From: Neil Armstrong +Date: Thu, 4 Oct 2018 10:42:43 +0200 +Subject: [PATCH 53/53] drm/meson: fix max mode_config height/width + +The mode_config max_width/max_height determines the maximum framebuffer +size the pixel reader can handle. But the values were set thinking they +were determining the maximum screen dimensions. + +This patch changes the values to the maximum height/width the CANVAS block +can handle rounded to some coherent values. + +Fixes: a41e82e6c457 ("drm/meson: Add support for components") +Signed-off-by: Neil Armstrong +--- + drivers/gpu/drm/meson/meson_drv.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c +index a13704ab5d11..960b8b08756e 100644 +--- a/drivers/gpu/drm/meson/meson_drv.c ++++ b/drivers/gpu/drm/meson/meson_drv.c +@@ -267,8 +267,8 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) + goto free_drm; + + drm_mode_config_init(drm); +- drm->mode_config.max_width = 3840; +- drm->mode_config.max_height = 2160; ++ drm->mode_config.max_width = 16384; ++ drm->mode_config.max_height = 8192; + drm->mode_config.funcs = &meson_mode_config_funcs; + drm->mode_config.helper_private = &meson_mode_config_helpers; + +-- +2.17.1 + diff --git a/buildroot-external/board/hardkernel/patches/linux/mali_odroid-xu.patch b/buildroot-external/board/hardkernel/patches/linux/mali_odroid-xu.patch new file mode 100644 index 000000000..9270a4e84 --- /dev/null +++ b/buildroot-external/board/hardkernel/patches/linux/mali_odroid-xu.patch @@ -0,0 +1,92 @@ +ARM: dts: exynos5420: add mali dt bindings and enable mali on Odroid XU3/4 + +Signed-off-by: memeka + +https://github.com/hardkernel/linux/commit/27f16b364e195daefdb8839344c02870ceaf48f2#diff-80dd4bb1cf404a3774bbe37bcfe945c2 + +Upstream-Status: Backport + +Signed-off-by: Armin Kuster + +Index: kernel-source/arch/arm/boot/dts/exynos5422-odroid-core.dtsi +=================================================================== +--- kernel-source.orig/arch/arm/boot/dts/exynos5422-odroid-core.dtsi ++++ kernel-source/arch/arm/boot/dts/exynos5422-odroid-core.dtsi +@@ -417,6 +417,11 @@ + vtmu-supply = <&ldo7_reg>; + }; + ++&mali { ++ mali-supply = <&buck4_reg>; ++ status = "okay"; ++}; ++ + &rtc { + status = "okay"; + clocks = <&clock CLK_RTC>, <&s2mps11_osc S2MPS11_CLK_AP>; +Index: kernel-source/arch/arm/boot/dts/exynos5420.dtsi +=================================================================== +--- kernel-source.orig/arch/arm/boot/dts/exynos5420.dtsi ++++ kernel-source/arch/arm/boot/dts/exynos5420.dtsi +@@ -1317,6 +1317,61 @@ + }; + }; + ++ mali: mali@11800000 { ++ compatible = "samsung,exynos5422-mali", "arm,malit6xx", "arm,mali-midgard"; ++ reg = <0x11800000 0x5000>; ++ interrupts = , , ; ++ interrupt-names = "JOB", "MMU", "GPU"; ++ ++ clocks = <&clock CLK_FOUT_VPLL>, <&clock CLK_DOUT_ACLK_G3D>, <&clock CLK_G3D>; ++ clock-names = "fout_vpll", "dout_aclk_g3d", "clk_mali"; ++ ++ operating-points-v2 = <&gpu_opp_table>; ++ ++ status = "disabled"; ++ ++ power_model@0 { ++ compatible = "arm,mali-simple-power-model"; ++ static-coefficient = <2427750>; ++ dynamic-coefficient = <4687>; ++ ts = <20000 2000 (-20) 2>; ++ thermal-zone = "gpu-thermal"; ++ }; ++ }; ++ ++ gpu_opp_table: opp_table0 { ++ compatible = "operating-points-v2"; ++ ++ opp@600000000 { ++ opp-hz = /bits/ 64 <600000000>; ++ opp-microvolt = <1150000>; ++ }; ++ opp@543000000 { ++ opp-hz = /bits/ 64 <543000000>; ++ opp-microvolt = <1037500>; ++ }; ++ opp@480000000 { ++ opp-hz = /bits/ 64 <480000000>; ++ opp-microvolt = <1000000>; ++ }; ++ opp@420000000 { ++ opp-hz = /bits/ 64 <420000000>; ++ opp-microvolt = <962500>; ++ }; ++ opp@350000000 { ++ opp-hz = /bits/ 64 <350000000>; ++ opp-microvolt = <912500>; ++ }; ++ opp@266000000 { ++ opp-hz = /bits/ 64 <266000000>; ++ opp-microvolt = <862500>; ++ }; ++ opp@177000000 { ++ opp-hz = /bits/ 64 <177000000>; ++ opp-microvolt = <812500>; ++ }; ++ }; ++ + thermal-zones { + cpu0_thermal: cpu0-thermal { + thermal-sensors = <&tmu_cpu0>; diff --git a/buildroot-external/configs/odroid_c2_defconfig b/buildroot-external/configs/odroid_c2_defconfig index f776c762d..8ea323da0 100644 --- a/buildroot-external/configs/odroid_c2_defconfig +++ b/buildroot-external/configs/odroid_c2_defconfig @@ -2,7 +2,7 @@ BR2_aarch64=y BR2_DL_DIR="/cache/dl" BR2_CCACHE=y BR2_CCACHE_DIR="/cache/cc" -BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_HASSOS_PATH)/patches $(BR2_EXTERNAL_HASSOS_PATH)/board/hardkernel/odroid-c2/patches" +BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_HASSOS_PATH)/patches $(BR2_EXTERNAL_HASSOS_PATH)/board/hardkernel/patches" BR2_TOOLCHAIN_BUILDROOT_GLIBC=y BR2_GCC_VERSION_7_X=y BR2_TOOLCHAIN_BUILDROOT_CXX=y @@ -19,7 +19,7 @@ BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_HASSOS_PATH)/scripts/post-image.sh" BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_EXTERNAL_HASSOS_PATH)/board/hardkernel/odroid-c2 $(BR2_EXTERNAL_HASSOS_PATH)/board/hardkernel/odroid-c2/hassos-hook.sh" BR2_LINUX_KERNEL=y BR2_LINUX_KERNEL_CUSTOM_VERSION=y -BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="4.19.88" +BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="4.19.72" BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_HASSOS_PATH)/board/hardkernel/odroid-c2/kernel.config" BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_HASSOS_PATH)/kernel/hassos.config $(BR2_EXTERNAL_HASSOS_PATH)/kernel/docker.config $(BR2_EXTERNAL_HASSOS_PATH)/kernel/device-support.config" @@ -77,7 +77,7 @@ BR2_TARGET_ROOTFS_SQUASHFS4_LZ4=y BR2_TARGET_UBOOT=y BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y BR2_TARGET_UBOOT_CUSTOM_VERSION=y -BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2018.07" +BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2019.10" BR2_TARGET_UBOOT_BOARD_DEFCONFIG="odroid-c2" BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_HASSOS_PATH)/bootloader/uboot.config $(BR2_EXTERNAL_HASSOS_PATH)/board/hardkernel/odroid-c2/uboot.config" BR2_TARGET_UBOOT_BOOT_SCRIPT=y diff --git a/buildroot-external/configs/odroid_xu4_defconfig b/buildroot-external/configs/odroid_xu4_defconfig index 3c8818ad1..92c06c739 100644 --- a/buildroot-external/configs/odroid_xu4_defconfig +++ b/buildroot-external/configs/odroid_xu4_defconfig @@ -3,7 +3,7 @@ BR2_cortex_a7=y BR2_DL_DIR="/cache/dl" BR2_CCACHE=y BR2_CCACHE_DIR="/cache/cc" -BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_HASSOS_PATH)/patches $(BR2_EXTERNAL_HASSOS_PATH)/board/hardkernel/odroid-xu4/patches" +BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_HASSOS_PATH)/patches $(BR2_EXTERNAL_HASSOS_PATH)/board/hardkernel/patches" BR2_TOOLCHAIN_BUILDROOT_GLIBC=y BR2_GCC_VERSION_7_X=y BR2_TOOLCHAIN_BUILDROOT_CXX=y @@ -20,7 +20,7 @@ BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_HASSOS_PATH)/scripts/post-image.sh" BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_EXTERNAL_HASSOS_PATH)/board/hardkernel/odroid-xu4 $(BR2_EXTERNAL_HASSOS_PATH)/board/hardkernel/odroid-xu4/hassos-hook.sh" BR2_LINUX_KERNEL=y BR2_LINUX_KERNEL_CUSTOM_VERSION=y -BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="4.19.88" +BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="4.19.72" BR2_LINUX_KERNEL_DTS_SUPPORT=y BR2_LINUX_KERNEL_INTREE_DTS_NAME="exynos5422-odroidxu4" BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y @@ -80,7 +80,7 @@ BR2_TARGET_ROOTFS_SQUASHFS4_LZ4=y BR2_TARGET_UBOOT=y BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y BR2_TARGET_UBOOT_CUSTOM_VERSION=y -BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2018.07" +BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2019.10" BR2_TARGET_UBOOT_BOARD_DEFCONFIG="odroid-xu3" BR2_TARGET_UBOOT_NEEDS_DTC=y BR2_TARGET_UBOOT_FORMAT_DTB_BIN=y