ALT Linux kernel packages development
 help / color / mirror / Atom feed
* [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11
@ 2024-10-14 14:01 Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 01/39] Baikal Electronics SoC family Daniil Gnusarev
                   ` (39 more replies)
  0 siblings, 40 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

Alexey Sheplyakov (17):
  cpufreq-dt: don't load on Baikal-M SoC
  net: fwnode_get_phy_id: consider all compatible strings
  hwmon: bt1-pvt: access registers via pvt_{readl,writel} helpers
  hwmon: bt1-pvt: define pvt_readl/pvt_writel for Baikal-M SoC
  hwmon: bt1-pvt: adjusted probing for Baikal-M SoC
  hwmon: bt1-pvt: added compatible baikal,pvt
  drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a
  dt-bindings: dw-hdmi: added ahb-audio-regshift
  drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M
  drm/panfrost: forcibly set dma-coherent on Baikal-M
  drm/panfrost: disable devfreq on Baikal-M
  pm: disable all sleep states on Baikal-M based boards
  sound: dwc-i2s: paper over RX overrun warnings on Baikal-M
  sound: dwc-i2s: request all IRQs specified in device tree
  usb: dwc3: of-simple: added compatible string for Baikal-M SoC
  serial: 8250_dw: verify clock rate in dw8250_set_termios
  dw-pcie: refuse to load on Baikal-M with recent firmware

Daniil Gnusarev (20):
  Baikal Electronics SoC family
  Clk: Add clock drivers for Baikal BE-M1000 with new firmware
  USB: Add support for Baikal USB PHY
  PCI: Add support for PCIe controller for Baikal BE-M1000
  AHCI SATA: Add support for Baikal BE-M1000
  UART: Add support for UART Baikal BE-M1000
  Sound: add support for Baikal BE-M1000 I2S
  sound: baikal-i2s: paper over RX overrun warnings on Baikal-M
  net: stmmac: support of Baikal-BE1000 SoCs GMAC
  PVT: support register addressing with new firmware
  drm: add Baikal-M SoC video display unit driver
  bmc:  add board management controller driver
  clk: use "cmu-id" if there is no "reg" in devicetree
  pci: baikal-pcie: driver compatibility with SDK earlier than 5.7
  pci: baikal-pcie: driver compatibility with SDK versions 5.4
  drm: baikal-vdu: driver compatibility with SDK earlier than 5.9
  input: tp_serio: catch up API changes
  drm: baikal-m: add vblank events, fix mode switching
  drm: baikal-vdu: disable backlight driver loading
  config-aarch64: enable more configs for baikal-m support

Vadim V. Vlasov (2):
  input: new driver - serdev-serio
  input: added TF307 serio PS/2 emulator driver

 .../display/bridge/synopsys,dw-hdmi.yaml      |    7 +
 arch/arm64/Kconfig.platforms                  |   10 +
 config-aarch64                                |   14 +
 drivers/acpi/pci_mcfg.c                       |   38 +
 drivers/ata/ahci_dwc.c                        |    5 +
 drivers/clk/Makefile                          |    1 +
 drivers/clk/baikal/Makefile                   |    3 +
 drivers/clk/baikal/clk-bm1000.c               |  846 ++++++
 drivers/clk/baikal/clk-bs1000.c               |  504 ++++
 drivers/cpufreq/cpufreq-dt-platdev.c          |    3 +
 drivers/gpu/drm/Kconfig                       |    2 +
 drivers/gpu/drm/Makefile                      |    1 +
 drivers/gpu/drm/baikal/Kconfig                |   12 +
 drivers/gpu/drm/baikal/Makefile               |   11 +
 drivers/gpu/drm/baikal/baikal-hdmi.c          |  123 +
 drivers/gpu/drm/baikal/baikal_vdu_backlight.c |  261 ++
 drivers/gpu/drm/baikal/baikal_vdu_crtc.c      |  476 ++++
 drivers/gpu/drm/baikal/baikal_vdu_debugfs.c   |   95 +
 drivers/gpu/drm/baikal/baikal_vdu_drm.h       |  133 +
 drivers/gpu/drm/baikal/baikal_vdu_drv.c       |  578 +++++
 drivers/gpu/drm/baikal/baikal_vdu_panel.c     |  193 ++
 drivers/gpu/drm/baikal/baikal_vdu_plane.c     |  143 ++
 drivers/gpu/drm/baikal/baikal_vdu_regs.h      |  137 +
 drivers/gpu/drm/bridge/Kconfig                |    7 +
 .../drm/bridge/synopsys/dw-hdmi-ahb-audio.c   |  106 +-
 .../gpu/drm/bridge/synopsys/dw-hdmi-audio.h   |    1 +
 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c     |   19 +
 drivers/gpu/drm/drm_panel.c                   |   37 +
 drivers/gpu/drm/panfrost/panfrost_devfreq.c   |    5 +
 drivers/gpu/drm/panfrost/panfrost_drv.c       |    5 +
 drivers/hwmon/Kconfig                         |    5 +-
 drivers/hwmon/bt1-pvt.c                       |  152 +-
 drivers/hwmon/bt1-pvt.h                       |    8 +
 drivers/input/serio/Kconfig                   |   21 +
 drivers/input/serio/Makefile                  |    2 +
 drivers/input/serio/serdev-serio.c            |  121 +
 drivers/input/serio/tp_serio.c                |  746 ++++++
 drivers/misc/Kconfig                          |   16 +
 drivers/misc/Makefile                         |    1 +
 drivers/misc/tp_bmc.c                         |  768 ++++++
 drivers/net/ethernet/stmicro/stmmac/Kconfig   |    8 +
 drivers/net/ethernet/stmicro/stmmac/Makefile  |    1 +
 .../ethernet/stmicro/stmmac/dwmac-baikal.c    |  542 ++++
 .../ethernet/stmicro/stmmac/dwmac1000_core.c  |    1 +
 .../ethernet/stmicro/stmmac/dwmac1000_dma.c   |   56 +-
 .../ethernet/stmicro/stmmac/dwmac1000_dma.h   |   32 +
 .../net/ethernet/stmicro/stmmac/dwmac_lib.c   |    8 +
 drivers/net/phy/phy_device.c                  |   41 +-
 drivers/pci/controller/dwc/Kconfig            |   32 +
 drivers/pci/controller/dwc/Makefile           |    5 +
 drivers/pci/controller/dwc/pcie-baikal-acpi.c |   23 +
 drivers/pci/controller/dwc/pcie-baikal-core.c | 2287 +++++++++++++++++
 drivers/pci/controller/dwc/pcie-baikal-tune.c |  570 ++++
 drivers/pci/controller/dwc/pcie-baikal.h      |   16 +
 .../pci/controller/dwc/pcie-designware-plat.c |    5 +
 drivers/pci/controller/dwc/pcie-designware.c  |    3 +-
 drivers/pci/controller/dwc/pcie-designware.h  |    1 +
 drivers/phy/Kconfig                           |    1 +
 drivers/phy/Makefile                          |    1 +
 drivers/phy/baikal/Kconfig                    |   10 +
 drivers/phy/baikal/Makefile                   |    3 +
 drivers/phy/baikal/baikal-usb-phy.c           |  305 +++
 drivers/tty/serial/8250/8250_dw.c             |   11 +-
 drivers/usb/dwc3/dwc3-of-simple.c             |    3 +
 include/drm/bridge/dw_hdmi.h                  |    2 +
 include/drm/drm_panel.h                       |    3 +
 include/linux/pci-ecam.h                      |    2 +
 kernel/exit.c                                 |    1 +
 kernel/power/suspend.c                        |   13 +
 net/ethernet/eth.c                            |    1 +
 sound/soc/Kconfig                             |    1 +
 sound/soc/Makefile                            |    1 +
 sound/soc/baikal/Kconfig                      |   21 +
 sound/soc/baikal/Makefile                     |    6 +
 sound/soc/baikal/baikal-i2s.c                 |  809 ++++++
 sound/soc/baikal/baikal-pio-pcm.c             |  264 ++
 sound/soc/baikal/local.h                      |  138 +
 sound/soc/dwc/dwc-i2s.c                       |   23 +-
 sound/soc/dwc/local.h                         |    1 +
 79 files changed, 10745 insertions(+), 122 deletions(-)
 create mode 100644 drivers/clk/baikal/Makefile
 create mode 100644 drivers/clk/baikal/clk-bm1000.c
 create mode 100644 drivers/clk/baikal/clk-bs1000.c
 create mode 100644 drivers/gpu/drm/baikal/Kconfig
 create mode 100644 drivers/gpu/drm/baikal/Makefile
 create mode 100644 drivers/gpu/drm/baikal/baikal-hdmi.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_backlight.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_crtc.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_debugfs.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_drm.h
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_drv.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_panel.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_plane.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_regs.h
 create mode 100644 drivers/input/serio/serdev-serio.c
 create mode 100644 drivers/input/serio/tp_serio.c
 create mode 100644 drivers/misc/tp_bmc.c
 create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c
 create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h
 create mode 100644 drivers/pci/controller/dwc/pcie-baikal-acpi.c
 create mode 100644 drivers/pci/controller/dwc/pcie-baikal-core.c
 create mode 100644 drivers/pci/controller/dwc/pcie-baikal-tune.c
 create mode 100644 drivers/pci/controller/dwc/pcie-baikal.h
 create mode 100644 drivers/phy/baikal/Kconfig
 create mode 100644 drivers/phy/baikal/Makefile
 create mode 100644 drivers/phy/baikal/baikal-usb-phy.c
 create mode 100644 sound/soc/baikal/Kconfig
 create mode 100644 sound/soc/baikal/Makefile
 create mode 100644 sound/soc/baikal/baikal-i2s.c
 create mode 100644 sound/soc/baikal/baikal-pio-pcm.c
 create mode 100644 sound/soc/baikal/local.h

-- 
2.42.2

Подготовлены обновленные патчи для поддержки Байкал-М в ядре un-def для p11.

Основная идея в работоспособности на машинах с новым SDK от Baikal
Electronics, но  с сохранением работоспособности на старых. Проверялось на
SDK с версии SDK-ARM64-5.4 по версию SDK-ARM64-2403-6.6 на плате TF307 MB-S-D
и на имеющихся машинах Эдельвейс, Radeola, Aquarius (уже без изменения версии)

Доработан drm-драйвер для работы с 4k мониторами, исправлено переключение
режимов для X.

Патчи доступны по ссылке:
https://gitlab.basealt.space/altworkstation/kernel-image/-/commit/d72f57bd8a7c2deb79eaede05848f1837edc9ae9


^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 01/39] Baikal Electronics SoC family
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 02/39] Clk: Add clock drivers for Baikal BE-M1000 with new firmware Daniil Gnusarev
                   ` (38 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

Enable support for Baikal Electronics SoC family

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 arch/arm64/Kconfig.platforms | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index 6069120199bbc..f44dd515e758c 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -33,6 +33,16 @@ config ARCH_APPLE
 	  This enables support for Apple's in-house ARM SoC family, starting
 	  with the Apple M1.
 
+config ARCH_BAIKAL
+	bool "Baikal Electronics SoC family"
+	select DW_APB_TIMER_OF
+	select GPIOLIB
+	select GPIO_DWAPB
+	select MULTIPLEXER
+	select PINCTRL
+	help
+	  This enables support for Baikal Electronics SoC family
+
 menuconfig ARCH_BCM
 	bool "Broadcom SoC Support"
 
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 02/39] Clk: Add clock drivers for Baikal BE-M1000 with new firmware
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 01/39] Baikal Electronics SoC family Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 03/39] USB: Add support for Baikal USB PHY Daniil Gnusarev
                   ` (37 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

Add supports for clocks for Baikal BE-M1000 with firmware
from SDK-ARM64-2403-6.6

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Co-developed-by: Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>
---
 drivers/clk/Makefile            |   1 +
 drivers/clk/baikal/Makefile     |   3 +
 drivers/clk/baikal/clk-bm1000.c | 841 ++++++++++++++++++++++++++++++++
 drivers/clk/baikal/clk-bs1000.c | 504 +++++++++++++++++++
 4 files changed, 1349 insertions(+)
 create mode 100644 drivers/clk/baikal/Makefile
 create mode 100644 drivers/clk/baikal/clk-bm1000.c
 create mode 100644 drivers/clk/baikal/clk-bs1000.c

diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 18969cbd4bb1e..12b5f24382413 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -87,6 +87,7 @@ obj-y					+= analogbits/
 obj-$(CONFIG_COMMON_CLK_AT91)		+= at91/
 obj-$(CONFIG_ARCH_ARTPEC)		+= axis/
 obj-$(CONFIG_ARC_PLAT_AXS10X)		+= axs10x/
+obj-$(CONFIG_ARCH_BAIKAL)		+= baikal/
 obj-$(CONFIG_CLK_BAIKAL_T1)		+= baikal-t1/
 obj-y					+= bcm/
 obj-$(CONFIG_ARCH_BERLIN)		+= berlin/
diff --git a/drivers/clk/baikal/Makefile b/drivers/clk/baikal/Makefile
new file mode 100644
index 0000000000000..dc94047aa9131
--- /dev/null
+++ b/drivers/clk/baikal/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-y += clk-bm1000.o
+obj-y += clk-bs1000.o
diff --git a/drivers/clk/baikal/clk-bm1000.c b/drivers/clk/baikal/clk-bm1000.c
new file mode 100644
index 0000000000000..d986c8ea2a633
--- /dev/null
+++ b/drivers/clk/baikal/clk-bm1000.c
@@ -0,0 +1,841 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2015-2023 Baikal Electronics, JSC
+ * Author: Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>
+ */
+
+#include <linux/acpi.h>
+#include <linux/arm-smccc.h>
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BAIKAL_SMC_CMU_CMD	0x82000000
+#define CMU_PLL_SET_RATE	0
+#define CMU_PLL_GET_RATE	1
+#define CMU_PLL_ENABLE		2
+#define CMU_PLL_DISABLE		3
+#define CMU_PLL_ROUND_RATE	4
+#define CMU_PLL_IS_ENABLED	5
+#define CMU_CLK_CH_SET_RATE	6
+#define CMU_CLK_CH_GET_RATE	7
+#define CMU_CLK_CH_ENABLE	8
+#define CMU_CLK_CH_DISABLE	9
+#define CMU_CLK_CH_ROUND_RATE	10
+#define CMU_CLK_CH_IS_ENABLED	11
+
+struct baikal_clk_cmu {
+	struct clk_hw	hw;
+	u32		base;
+	unsigned int	parent;
+	const char	*name;
+	u32		is_clk_ch;
+};
+
+#define to_baikal_cmu(_hw) container_of(_hw, struct baikal_clk_cmu, hw)
+
+static int baikal_clk_enable(struct clk_hw *hw)
+{
+	struct arm_smccc_res res;
+	struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+	u32 cmd;
+
+	if (pclk->is_clk_ch)
+		cmd = CMU_CLK_CH_ENABLE;
+	else
+		cmd = CMU_PLL_ENABLE;
+
+	/* If clock valid */
+	arm_smccc_smc(BAIKAL_SMC_CMU_CMD, pclk->base, cmd, 0,
+		      pclk->parent, 0, 0, 0, &res);
+
+	pr_debug("%s(%s, %s@0x%x): %s\n",
+		 __func__,
+		 pclk->name,
+		 pclk->is_clk_ch ? "clkch" : "pll",
+		 pclk->base,
+		 res.a0 ? "error" : "ok");
+
+	return res.a0;
+}
+
+static void baikal_clk_disable(struct clk_hw *hw)
+{
+	struct arm_smccc_res res;
+	struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+	u32 cmd;
+
+	if (pclk->is_clk_ch)
+		cmd = CMU_CLK_CH_DISABLE;
+	else
+		cmd = CMU_PLL_DISABLE;
+
+	/* If clock valid */
+	arm_smccc_smc(BAIKAL_SMC_CMU_CMD, pclk->base, cmd, 0,
+		      pclk->parent, 0, 0, 0, &res);
+
+	pr_debug("%s(%s, %s@0x%x): %s\n",
+		 __func__,
+		 pclk->name,
+		 pclk->is_clk_ch ? "clkch" : "pll",
+		 pclk->base,
+		 res.a0 ? "error" : "ok");
+}
+
+static int baikal_clk_is_enabled(struct clk_hw *hw)
+{
+	struct arm_smccc_res res;
+	struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+	u32 cmd;
+
+	if (pclk->is_clk_ch)
+		cmd = CMU_CLK_CH_IS_ENABLED;
+	else
+		cmd = CMU_PLL_IS_ENABLED;
+
+	/* If clock valid */
+	arm_smccc_smc(BAIKAL_SMC_CMU_CMD, pclk->base, cmd, 0,
+		      pclk->parent, 0, 0, 0, &res);
+
+	pr_debug("%s(%s, %s@0x%x): %s\n",
+		 __func__,
+		 pclk->name,
+		 pclk->is_clk_ch ? "clkch" : "pll",
+		 pclk->base,
+		 res.a0 ? "true" : "false");
+
+	return res.a0;
+}
+
+static unsigned long baikal_clk_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct arm_smccc_res res;
+	struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+	u32 cmd;
+	unsigned long parent;
+
+	if (pclk->is_clk_ch) {
+		cmd = CMU_CLK_CH_GET_RATE;
+		parent = pclk->parent;
+	} else {
+		cmd = CMU_PLL_GET_RATE;
+		parent = parent_rate;
+	}
+
+	/* If clock valid */
+	arm_smccc_smc(BAIKAL_SMC_CMU_CMD, pclk->base, cmd, 0,
+		      parent, 0, 0, 0, &res);
+
+	pr_debug("%s(%s, %s@0x%x): %ld Hz\n",
+		 __func__,
+		 pclk->name,
+		 pclk->is_clk_ch ? "clkch" : "pll",
+		 pclk->base,
+		 res.a0);
+
+	/* Return actual freq */
+	return res.a0;
+}
+
+static int baikal_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct arm_smccc_res res;
+	struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+	u32 cmd;
+	unsigned long parent;
+
+	if (pclk->is_clk_ch) {
+		cmd = CMU_CLK_CH_SET_RATE;
+		parent = pclk->parent;
+	} else {
+		cmd = CMU_PLL_SET_RATE;
+		parent = parent_rate;
+	}
+
+	arm_smccc_smc(BAIKAL_SMC_CMU_CMD, pclk->base, cmd, rate,
+		      parent, 0, 0, 0, &res);
+
+	pr_debug("%s(%s, %s@0x%x, %ld Hz): %s\n",
+		 __func__,
+		 pclk->name,
+		 pclk->is_clk_ch ? "clkch" : "pll",
+		 pclk->base,
+		 rate,
+		 res.a0 ? "error" : "ok");
+
+	return res.a0;
+}
+
+static long baikal_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *prate)
+{
+	struct arm_smccc_res res;
+	struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+	u32 cmd;
+	unsigned long parent;
+
+	if (pclk->is_clk_ch) {
+		cmd = CMU_CLK_CH_ROUND_RATE;
+		parent = pclk->parent;
+	} else {
+		cmd = CMU_PLL_ROUND_RATE;
+		parent = *prate;
+	}
+
+	/* If clock valid */
+	arm_smccc_smc(BAIKAL_SMC_CMU_CMD, pclk->base, cmd, rate,
+		      parent, 0, 0, 0, &res);
+
+	pr_debug("%s(%s, %s@0x%x): %ld Hz\n",
+		 __func__,
+		 pclk->name,
+		 pclk->is_clk_ch ? "clkch" : "pll",
+		 pclk->base,
+		 res.a0);
+
+	/* Return actual freq */
+	return res.a0;
+}
+
+static const struct clk_ops baikal_clk_ops = {
+	.enable	     = baikal_clk_enable,
+	.disable     = baikal_clk_disable,
+	.is_enabled  = baikal_clk_is_enabled,
+	.recalc_rate = baikal_clk_recalc_rate,
+	.set_rate    = baikal_clk_set_rate,
+	.round_rate  = baikal_clk_round_rate,
+};
+
+static int baikal_clk_probe(struct platform_device *pdev)
+{
+	struct clk_init_data init;
+	struct clk_init_data *init_ch;
+	struct baikal_clk_cmu *cmu;
+	struct baikal_clk_cmu **cmu_ch;
+	struct device_node *node = pdev->dev.of_node;
+
+	struct clk *clk;
+	struct clk_onecell_data *clk_ch;
+
+	int i = 0, number, rc;
+	u32 index;
+	u64 base;
+	struct property *prop;
+	const __be32 *p;
+	const char *clk_ch_name;
+	const char *parent_name;
+
+	cmu = kmalloc(sizeof(*cmu), GFP_KERNEL);
+	if (!cmu)
+		return -ENOMEM;
+
+	of_property_read_string(node, "clock-output-names", &cmu->name);
+	of_property_read_u32(node, "clock-frequency", &cmu->parent);
+	rc = of_property_read_u64(node, "reg", &base);
+	if (rc)
+		return rc;
+
+	cmu->base = base;
+
+	parent_name = of_clk_get_parent_name(node, 0);
+
+	/* Setup clock init structure */
+	init.parent_names = &parent_name;
+	init.num_parents = 1;
+	init.name = cmu->name;
+	init.ops = &baikal_clk_ops;
+	init.flags = CLK_IGNORE_UNUSED;
+
+	cmu->hw.init = &init;
+	cmu->is_clk_ch = 0;
+
+	pr_debug("%s: add %s, parent %s\n",
+		 __func__, cmu->name, parent_name ? parent_name : "null");
+
+	clk = clk_register(NULL, &cmu->hw);
+	if (IS_ERR(clk)) {
+		pr_err("%s: could not register clk %s\n", __func__, cmu->name);
+		return -ENOMEM;
+	}
+
+	/* Register the clock for lookup */
+	rc = clk_register_clkdev(clk, cmu->name, NULL);
+	if (rc != 0) {
+		pr_err("%s: could not register lookup clk %s\n",
+		       __func__, cmu->name);
+	}
+
+	/* FIXME: we probably shouldn't enable it here */
+	clk_prepare_enable(clk);
+
+	number = of_property_count_u32_elems(node, "clock-indices");
+
+	if (number > 0) {
+		clk_ch = kmalloc(sizeof(*clk_ch), GFP_KERNEL);
+		if (!clk_ch)
+			return -ENOMEM;
+
+		/* Get the last index to find out max number of children*/
+		of_property_for_each_u32(node, "clock-indices", prop, p, index) {
+			;
+		}
+
+		clk_ch->clks = kcalloc(index + 1, sizeof(struct clk *), GFP_KERNEL);
+		clk_ch->clk_num = index + 1;
+		cmu_ch = kcalloc((index + 1), sizeof(struct baikal_clk_cmu *), GFP_KERNEL);
+		if (!cmu_ch) {
+			kfree(clk_ch);
+			return -ENOMEM;
+		}
+
+		init_ch = kcalloc((number + 1), sizeof(struct clk_init_data), GFP_KERNEL);
+		if (!init_ch) {
+			kfree(cmu_ch);
+			kfree(clk_ch);
+			return -ENOMEM;
+		}
+
+		of_property_for_each_u32(node, "clock-indices", prop, p, index) {
+			of_property_read_string_index(node, "clock-names",
+						      i, &clk_ch_name);
+			pr_debug("%s: clkch <%s>, index %d, i %d\n", __func__, clk_ch_name, index, i);
+			init_ch[i].parent_names = &cmu->name;
+			init_ch[i].num_parents = 1;
+			init_ch[i].name = clk_ch_name;
+			init_ch[i].ops = &baikal_clk_ops;
+			init_ch[i].flags = CLK_IGNORE_UNUSED;
+
+			cmu_ch[index] = kmalloc(sizeof(*cmu_ch[index]), GFP_KERNEL);
+			cmu_ch[index]->name = clk_ch_name;
+			cmu_ch[index]->base = index;
+			cmu_ch[index]->parent = cmu->base;
+			cmu_ch[index]->is_clk_ch = 1;
+			cmu_ch[index]->hw.init = &init_ch[i];
+			clk_ch->clks[index] = clk_register(NULL, &cmu_ch[index]->hw);
+
+			if (IS_ERR(clk_ch->clks[index])) {
+				pr_err("%s: could not register clk %s\n",
+				       __func__, clk_ch_name);
+			}
+
+			/* Register the clock for lookup */
+			rc = clk_register_clkdev(clk_ch->clks[index], clk_ch_name, NULL);
+			if (rc != 0) {
+				pr_err("%s: could not register lookup clk %s\n",
+				       __func__, clk_ch_name);
+			}
+
+			/* FIXME: we probably shouldn't enable it here */
+			clk_prepare_enable(clk_ch->clks[index]);
+			i++;
+		}
+
+		return of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get, clk_ch);
+	}
+
+	return of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, clk);
+}
+
+static int baikal_clk_remove(struct platform_device *pdev)
+{
+	of_clk_del_provider(pdev->dev.of_node);
+	return 0;
+}
+
+#ifdef CONFIG_ACPI
+const char *baikal_acpi_clk_osc25_str[] = { "osc25" };
+const char *baikal_acpi_clk_osc27_str[] = { "osc27" };
+
+static struct clk *baikal_acpi_clk_osc25;
+static struct clk *baikal_acpi_clk_osc27;
+
+#define BAIKAL_CMU_CLK_CH	0x0
+#define BAIKAL_FIXED_CLK	0x1
+#define BAIKAL_FIXED_FACTOR_CLK	0x2
+#define BAIKAL_CMU_CLK          0xffffffffffffffff
+
+struct baikal_acpi_clk_data {
+	struct clk *cmu_clk;
+	struct clk_lookup *cmu_clk_l;
+	struct clk_lookup **cmu_clk_refs_l;
+	struct clk **clk;
+	struct clk_lookup **clk_l;
+	u8 *type;
+	unsigned int clk_num;
+	unsigned int cmu_clk_refs_num;
+};
+
+static int baikal_acpi_clk_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct acpi_device *ref_dev, *adev = to_acpi_device_node(pdev->dev.fwnode);
+	struct clk_init_data init, *init_ch;
+	struct baikal_clk_cmu *cmu, *cmu_ch;
+	struct baikal_acpi_clk_data *clk_data = NULL;
+	union acpi_object *package, *element;
+	acpi_status status;
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	int osc27, i, ret = 0;
+	char *str, *str2;
+
+	cmu = devm_kzalloc(dev, sizeof(*cmu), GFP_KERNEL);
+	if (!cmu)
+		return -ENOMEM;
+
+	status = acpi_evaluate_object_typed(adev->handle, "PROP", NULL, &buffer, ACPI_TYPE_PACKAGE);
+	if (ACPI_FAILURE(status)) {
+		dev_err(dev, "failed to get PROP data\n");
+		return -ENODEV;
+	}
+
+	package = buffer.pointer;
+	if (package->package.count != 4) {
+		dev_err(dev, "invalid PROP data\n");
+		ret = -EINVAL;
+		goto ret;
+	}
+
+	element = &package->package.elements[0];
+	if (element->type != ACPI_TYPE_INTEGER) {
+		dev_err(dev, "failed to get CMU id\n");
+		ret = -EINVAL;
+		goto ret;
+	}
+
+	cmu->base = element->integer.value;
+
+	element = &package->package.elements[1];
+	if (element->type != ACPI_TYPE_STRING) {
+		dev_err(dev, "failed to get CMU clock name\n");
+		ret = -EINVAL;
+		goto ret;
+	}
+
+	str = devm_kzalloc(dev, element->string.length + 1, GFP_KERNEL);
+	if (!str) {
+		ret = -ENOMEM;
+		goto ret;
+	}
+
+	memcpy(str, element->string.pointer, element->string.length);
+	cmu->name = str;
+
+	element = &package->package.elements[2];
+	if (element->type != ACPI_TYPE_INTEGER) {
+		dev_err(dev, "failed to get CMU frequency\n");
+		ret = -EINVAL;
+		goto ret;
+	}
+
+	cmu->parent = element->integer.value;
+
+	element = &package->package.elements[3];
+	if (element->type != ACPI_TYPE_INTEGER) {
+		dev_err(dev, "failed to get CMU osc type\n");
+		ret = -EINVAL;
+		goto ret;
+	}
+
+	osc27 = element->integer.value ? 1 : 0;
+
+	acpi_os_free(buffer.pointer);
+	buffer.length = ACPI_ALLOCATE_BUFFER;
+	buffer.pointer = NULL;
+
+	init.parent_names = osc27 ? baikal_acpi_clk_osc27_str : baikal_acpi_clk_osc25_str;
+	init.num_parents = 1;
+	init.name = cmu->name;
+	init.ops = &baikal_clk_ops;
+	init.flags = CLK_IGNORE_UNUSED;
+
+	cmu->hw.init = &init;
+	cmu->is_clk_ch = 0;
+
+	clk_data = devm_kzalloc(dev, sizeof(*clk_data), GFP_KERNEL);
+	if (!clk_data)
+		return -ENOMEM;
+
+	clk_data->cmu_clk = clk_register(NULL, &cmu->hw);
+	if (IS_ERR(clk_data->cmu_clk)) {
+		dev_err(dev, "failed to register CMU clock\n");
+		return PTR_ERR(clk_data->cmu_clk);
+	}
+
+	clk_data->cmu_clk_l = clkdev_create(clk_data->cmu_clk, cmu->name, NULL);
+	if (!clk_data->cmu_clk_l) {
+		dev_err(dev, "failed to register CMU clock lookup\n");
+		clk_unregister(clk_data->cmu_clk);
+		return -ENOMEM;
+	}
+
+	clk_prepare_enable(clk_data->cmu_clk);
+
+	platform_set_drvdata(pdev, clk_data);
+
+	/* CPU clock */
+	status = acpi_evaluate_object_typed(adev->handle, "CMU", NULL, &buffer, ACPI_TYPE_PACKAGE);
+	if (ACPI_FAILURE(status)) {
+		buffer.pointer = NULL;
+		goto clk_channels;
+	}
+
+	package = buffer.pointer;
+	if (!package->package.count || package->package.count % 2) {
+		dev_err(dev, "invalid CMU data\n");
+		ret = -EINVAL;
+		goto ret;
+	}
+
+	clk_data->cmu_clk_refs_num = package->package.count >> 1;
+	clk_data->cmu_clk_refs_l = devm_kzalloc(dev,
+						clk_data->cmu_clk_refs_num * sizeof(struct clk_lookup *),
+						GFP_KERNEL);
+	if (!clk_data->cmu_clk_refs_l) {
+		ret = -ENOMEM;
+		goto ret;
+	}
+
+	for (i = 0; i < clk_data->cmu_clk_refs_num; ++i) {
+		ref_dev = NULL;
+
+		element = &package->package.elements[2 * i];
+		if (element->type == ACPI_TYPE_LOCAL_REFERENCE && element->reference.handle)
+			ref_dev = acpi_fetch_acpi_dev(element->reference.handle);
+
+		element = &package->package.elements[2 * i + 1];
+		if (element->type == ACPI_TYPE_STRING) {
+			str2 = devm_kzalloc(dev, element->string.length + 1, GFP_KERNEL);
+			if (str2)
+				memcpy(str2, element->string.pointer, element->string.length);
+		} else {
+			str2 = NULL;
+		}
+
+		if (ref_dev && acpi_get_first_physical_node(ref_dev))
+			clk_data->cmu_clk_refs_l[i] =
+				clkdev_create(clk_data->cmu_clk, str2, "%s",
+					      dev_name(acpi_get_first_physical_node(ref_dev)));
+		else
+			clk_data->cmu_clk_refs_l[i] = clkdev_create(clk_data->cmu_clk, str2, NULL);
+	}
+
+	acpi_os_free(buffer.pointer);
+	buffer.length = ACPI_ALLOCATE_BUFFER;
+	buffer.pointer = NULL;
+
+clk_channels:
+	status = acpi_evaluate_object_typed(adev->handle, "CLKS", NULL, &buffer, ACPI_TYPE_PACKAGE);
+	if (ACPI_FAILURE(status)) {
+		buffer.pointer = NULL;
+		clk_data = NULL;
+		goto ret;
+	}
+
+	package = buffer.pointer;
+	if (!package->package.count || package->package.count % 4) {
+		dev_err(dev, "invalid CLKS data\n");
+		ret = -EINVAL;
+		goto ret;
+	}
+
+	clk_data->clk_num = package->package.count >> 2;
+	clk_data->clk = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct clk *), GFP_KERNEL);
+	if (!clk_data->clk) {
+		ret = -ENOMEM;
+		goto ret;
+	}
+
+	clk_data->clk_l = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct clk_lookup *),
+				       GFP_KERNEL);
+	if (!clk_data->clk_l) {
+		ret = -ENOMEM;
+		goto ret;
+	}
+
+	clk_data->type = devm_kzalloc(dev, clk_data->clk_num * sizeof(u8), GFP_KERNEL);
+	if (!clk_data->type) {
+		ret = -ENOMEM;
+		goto ret;
+	}
+
+	init_ch = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct clk_init_data), GFP_KERNEL);
+	if (!init_ch) {
+		ret = -ENOMEM;
+		goto ret;
+	}
+
+	cmu_ch = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct baikal_clk_cmu), GFP_KERNEL);
+	if (!cmu_ch) {
+		ret = -ENOMEM;
+		goto ret;
+	}
+
+	for (i = 0; i < clk_data->clk_num; ++i) {
+		ref_dev = NULL;
+
+		element = &package->package.elements[4 * i];
+		if (element->type == ACPI_TYPE_LOCAL_REFERENCE && element->reference.handle)
+			ref_dev = acpi_fetch_acpi_dev(element->reference.handle);
+
+		element = &package->package.elements[4 * i + 1];
+		if (element->type == ACPI_TYPE_STRING) {
+			str = devm_kzalloc(dev, element->string.length + 1, GFP_KERNEL);
+			if (str)
+				memcpy(str, element->string.pointer, element->string.length);
+		} else {
+			dev_err(dev, "failed to process clock device name #%i\n", i);
+			continue;
+		}
+
+		element = &package->package.elements[4 * i + 2];
+		if (element->type == ACPI_TYPE_INTEGER) {
+			if (element->integer.value != BAIKAL_CMU_CLK) {
+				init_ch[i].parent_names = &cmu->name;
+				init_ch[i].num_parents = 1;
+				init_ch[i].name = str;
+				init_ch[i].ops = &baikal_clk_ops;
+				init_ch[i].flags = CLK_IGNORE_UNUSED;
+
+				cmu_ch[i].name = str;
+				cmu_ch[i].base = element->integer.value;
+				cmu_ch[i].parent = cmu->base;
+				cmu_ch[i].is_clk_ch = 1;
+				cmu_ch[i].hw.init = &init_ch[i];
+
+				clk_data->type[i] = BAIKAL_CMU_CLK_CH;
+				clk_data->clk[i] = clk_register(ref_dev ? &ref_dev->dev : NULL,
+								&cmu_ch[i].hw);
+				if (IS_ERR(clk_data->clk[i])) {
+					dev_err(dev, "failed to register CMU channel clock #%i\n", i);
+					clk_data->clk[i] = NULL;
+					continue;
+				}
+			}
+		} else if (element->type == ACPI_TYPE_PACKAGE &&
+			   element->package.count == 3 &&
+			   element->package.elements[0].type == ACPI_TYPE_INTEGER &&
+			   element->package.elements[1].type == ACPI_TYPE_INTEGER &&
+			   element->package.elements[2].type == ACPI_TYPE_INTEGER) {
+			/* Fixed clock */
+			struct clk_hw *hw;
+			u64 type = element->package.elements[0].integer.value;
+			u64 val1 = element->package.elements[1].integer.value;
+			u64 val2 = element->package.elements[2].integer.value;
+
+			switch (type) {
+			case BAIKAL_FIXED_CLK:
+				clk_data->type[i] = BAIKAL_FIXED_CLK;
+				hw = clk_hw_register_fixed_rate_with_accuracy(ref_dev ?
+									      &ref_dev->dev : NULL,
+									      str, NULL, 0,
+									      val1, val2);
+				if (IS_ERR(hw)) {
+					dev_err(dev, "failed to register fixed clock #%i\n", i);
+					continue;
+				}
+				clk_data->clk[i] = hw->clk;
+				break;
+			case BAIKAL_FIXED_FACTOR_CLK:
+				clk_data->type[i] = BAIKAL_FIXED_FACTOR_CLK;
+				hw = clk_hw_register_fixed_factor(ref_dev ? &ref_dev->dev : NULL,
+								  str, cmu->name, 0, val1, val2);
+				if (IS_ERR(hw)) {
+					dev_err(dev, "failed to register fixed-factor clock #%i\n", i);
+					continue;
+				}
+				clk_data->clk[i] = hw->clk;
+				break;
+			default:
+				dev_err(dev, "failed to create clock #%i\n", i);
+				continue;
+			}
+		} else {
+			dev_err(dev, "failed to process clock device id #%i\n", i);
+			continue;
+		}
+
+		element = &package->package.elements[4 * i + 3];
+		if (element->type == ACPI_TYPE_STRING) {
+			str2 = devm_kzalloc(dev, element->string.length + 1, GFP_KERNEL);
+			if (str2)
+				memcpy(str2, element->string.pointer, element->string.length);
+		} else {
+			str2 = NULL;
+		}
+
+		if (ref_dev || str2) {
+			if (!clk_data->clk[i]) {
+				if (ref_dev)
+					clk_data->clk_l[i] = clkdev_create(clk_data->cmu_clk,
+									   str2, "%s",
+									   dev_name(&ref_dev->dev));
+				else
+					clk_data->clk_l[i] = clkdev_create(clk_data->cmu_clk,
+									   str2, NULL);
+			} else {
+				if (ref_dev)
+					clk_data->clk_l[i] = clkdev_create(clk_data->clk[i],
+									   str2, "%s",
+									   dev_name(&ref_dev->dev));
+				else
+					clk_data->clk_l[i] = clkdev_create(clk_data->clk[i],
+									   str2, NULL);
+			}
+
+			if (!clk_data->clk_l[i]) {
+				dev_err(dev, "failed to register clock lookup #%i\n", i);
+				clk_unregister(clk_data->clk[i]);
+				clk_data->clk[i] = NULL;
+				continue;
+			}
+		}
+
+		clk_prepare_enable(clk_data->clk[i]);
+	}
+
+	clk_data = NULL;
+
+ret:
+	if (buffer.pointer)
+		acpi_os_free(buffer.pointer);
+
+	if (clk_data) {
+		clk_disable_unprepare(clk_data->cmu_clk);
+		clkdev_drop(clk_data->cmu_clk_l);
+		clk_unregister(clk_data->cmu_clk);
+	}
+
+	return ret;
+}
+
+static int baikal_acpi_clk_remove(struct platform_device *pdev)
+{
+	struct baikal_acpi_clk_data *clk_data = platform_get_drvdata(pdev);
+	int i;
+
+	if (clk_data) {
+		clk_disable_unprepare(clk_data->cmu_clk);
+		clkdev_drop(clk_data->cmu_clk_l);
+		clk_unregister(clk_data->cmu_clk);
+
+		for (i = 0; i < clk_data->cmu_clk_refs_num; ++i) {
+			if (clk_data->cmu_clk_refs_l[i])
+				clkdev_drop(clk_data->cmu_clk_refs_l[i]);
+		}
+
+		for (i = 0; i < clk_data->clk_num; ++i) {
+			if (clk_data->clk_l[i])
+				clkdev_drop(clk_data->clk_l[i]);
+			if (clk_data->clk[i]) {
+				switch (clk_data->type[i]) {
+				case BAIKAL_FIXED_CLK:
+					clk_unregister_fixed_rate(clk_data->clk[i]);
+					break;
+				case BAIKAL_FIXED_FACTOR_CLK:
+					clk_unregister_fixed_factor(clk_data->clk[i]);
+					break;
+				default:
+					clk_unregister(clk_data->clk[i]);
+					break;
+				}
+			}
+		}
+	}
+
+	return 0;
+}
+
+static const struct acpi_device_id baikal_acpi_clk_device_ids[] = {
+	{ "BKLE0001" },
+	{ }
+};
+
+static struct platform_driver baikal_acpi_clk_driver = {
+	.probe		= baikal_acpi_clk_probe,
+	.remove		= baikal_acpi_clk_remove,
+	.driver		= {
+		.name	= "bm1000-cmu-acpi",
+		.acpi_match_table = ACPI_PTR(baikal_acpi_clk_device_ids)
+	}
+};
+
+static int __init bm1000_cmu_driver_acpi_init(void)
+{
+	if (!acpi_disabled) {
+		struct clk_lookup *baikal_acpi_clk_lookup_osc25;
+		struct clk_lookup *baikal_acpi_clk_lookup_osc27;
+
+		baikal_acpi_clk_osc25 = clk_register_fixed_rate(NULL, baikal_acpi_clk_osc25_str[0],
+								NULL, 0, 25000000);
+		if (IS_ERR(baikal_acpi_clk_osc25)) {
+			pr_err("%s: failed to register osc25 clock\n", __func__);
+			return PTR_ERR(baikal_acpi_clk_osc25);
+		}
+
+		baikal_acpi_clk_osc27 = clk_register_fixed_rate(NULL, baikal_acpi_clk_osc27_str[0],
+								NULL, 0, 27000000);
+		if (IS_ERR(baikal_acpi_clk_osc27)) {
+			clk_unregister_fixed_rate(baikal_acpi_clk_osc25);
+			pr_err("%s: failed to register osc27 clock\n", __func__);
+			return PTR_ERR(baikal_acpi_clk_osc27);
+		}
+
+		baikal_acpi_clk_lookup_osc25 = clkdev_create(baikal_acpi_clk_osc25, NULL, "%s",
+							     baikal_acpi_clk_osc25_str[0]);
+		if (!baikal_acpi_clk_lookup_osc25) {
+			clk_unregister_fixed_rate(baikal_acpi_clk_osc27);
+			clk_unregister_fixed_rate(baikal_acpi_clk_osc25);
+			pr_err("%s: failed to register osc25 clock lookup\n", __func__);
+			return -ENOMEM;
+		}
+
+		baikal_acpi_clk_lookup_osc27 = clkdev_create(baikal_acpi_clk_osc27, NULL, "%s",
+							     baikal_acpi_clk_osc27_str[0]);
+		if (!baikal_acpi_clk_lookup_osc27) {
+			clkdev_drop(baikal_acpi_clk_lookup_osc25);
+			clk_unregister_fixed_rate(baikal_acpi_clk_osc27);
+			clk_unregister_fixed_rate(baikal_acpi_clk_osc25);
+			pr_err("%s: failed to register osc27 clock lookup\n", __func__);
+			return -ENOMEM;
+		}
+
+		clk_prepare_enable(baikal_acpi_clk_osc25);
+		clk_prepare_enable(baikal_acpi_clk_osc27);
+
+		return platform_driver_register(&baikal_acpi_clk_driver);
+	}
+
+	return 0;
+}
+
+device_initcall(bm1000_cmu_driver_acpi_init);
+#endif
+
+static const struct of_device_id baikal_clk_of_match[] = {
+	{ .compatible = "baikal,bm1000-cmu" },
+	{ }
+};
+
+static struct platform_driver bm1000_cmu_driver = {
+	.probe	= baikal_clk_probe,
+	.remove	= baikal_clk_remove,
+	.driver	= {
+		.name = "bm1000-cmu",
+		.of_match_table = baikal_clk_of_match
+	}
+};
+module_platform_driver(bm1000_cmu_driver);
+
+MODULE_DESCRIPTION("Baikal BE-M1000 clock driver");
+MODULE_AUTHOR("Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bm1000-cmu");
diff --git a/drivers/clk/baikal/clk-bs1000.c b/drivers/clk/baikal/clk-bs1000.c
new file mode 100644
index 0000000000000..2bec7f9b091da
--- /dev/null
+++ b/drivers/clk/baikal/clk-bs1000.c
@@ -0,0 +1,504 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021-2023 Baikal Electronics, JSC
+ * Author: Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>
+ */
+
+#include <linux/acpi.h>
+#include <linux/arm-smccc.h>
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BAIKAL_SMC_CLK_ROUND		0xC2000400
+#define BAIKAL_SMC_CLK_SET		0xC2000401
+#define BAIKAL_SMC_CLK_GET		0xC2000402
+#define BAIKAL_SMC_CLK_ENABLE		0xC2000403
+#define BAIKAL_SMC_CLK_DISABLE		0xC2000404
+#define BAIKAL_SMC_CLK_IS_ENABLED	0xC2000405
+
+struct baikal_clk {
+	struct clk_hw	hw;
+	u64		base;
+};
+
+#define to_baikal_clk(_hw) container_of(_hw, struct baikal_clk, hw)
+
+static int baikal_clk_enable(struct clk_hw *hw)
+{
+	struct baikal_clk *clk = to_baikal_clk(hw);
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(BAIKAL_SMC_CLK_ENABLE, clk->base, 0, 0, 0, 0, 0, 0, &res);
+	return res.a0;
+}
+
+static void baikal_clk_disable(struct clk_hw *hw)
+{
+	struct baikal_clk *clk = to_baikal_clk(hw);
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(BAIKAL_SMC_CLK_DISABLE, clk->base, 0, 0, 0, 0, 0, 0, &res);
+}
+
+static int baikal_clk_is_enabled(struct clk_hw *hw)
+{
+	struct baikal_clk *clk = to_baikal_clk(hw);
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(BAIKAL_SMC_CLK_IS_ENABLED, clk->base, 0, 0, 0, 0, 0, 0, &res);
+	return res.a0;
+}
+
+static unsigned long baikal_clk_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct baikal_clk *clk = to_baikal_clk(hw);
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(BAIKAL_SMC_CLK_GET, clk->base, 0, 0, 0, 0, 0, 0, &res);
+	return res.a0;
+}
+
+static int baikal_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct baikal_clk *clk = to_baikal_clk(hw);
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(BAIKAL_SMC_CLK_SET, clk->base, rate, 0, 0, 0, 0, 0, &res);
+	return res.a0;
+}
+
+static long baikal_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *prate)
+{
+	struct baikal_clk *clk = to_baikal_clk(hw);
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(BAIKAL_SMC_CLK_ROUND, clk->base, rate, 0, 0, 0, 0, 0, &res);
+	return res.a0;
+}
+
+static const struct clk_ops baikal_clk_ops = {
+	.enable	     = baikal_clk_enable,
+	.disable     = baikal_clk_disable,
+	.is_enabled  = baikal_clk_is_enabled,
+	.recalc_rate = baikal_clk_recalc_rate,
+	.set_rate    = baikal_clk_set_rate,
+	.round_rate  = baikal_clk_round_rate,
+};
+
+static int baikal_clk_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct clk_init_data init;
+	struct baikal_clk *cmu;
+	struct clk_onecell_data *clk_data;
+	const char *clk_name;
+	struct property *prop;
+	const __be32 *p;
+	int clk_index;
+	int clk_index_max;
+	int clk_index_cnt;
+	int clk_name_cnt;
+	int clk_cnt;
+	int i;
+	u64 base;
+	struct clk *clk;
+	int ret;
+	int multi;
+
+	ret = of_property_read_u64(node, "reg", &base);
+	if (ret)
+		base = 0;
+
+	clk_index_cnt = of_property_count_u32_elems(node, "clock-indices");
+	clk_name_cnt = of_property_count_strings(node, "clock-output-names");
+	clk_cnt = clk_index_cnt > clk_name_cnt ? clk_index_cnt : clk_name_cnt;
+	if (clk_cnt < 1)
+		clk_cnt = 1;
+
+	multi = clk_cnt > 1;
+
+	if (multi) {
+		clk_index_max = clk_cnt - 1;
+		of_property_for_each_u32(node, "clock-indices", prop, p, clk_index) {
+			if (clk_index_max < clk_index)
+				clk_index_max = clk_index;
+		}
+
+		clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
+		clk_data->clks = kcalloc(clk_index_max + 1, sizeof(struct clk *), GFP_KERNEL);
+		clk_data->clk_num = clk_index_max + 1;
+	}
+
+	for (i = 0; i < clk_cnt; i++) {
+		ret = of_property_read_u32_index(node, "clock-indices", i, &clk_index);
+		if (ret)
+			clk_index = i;
+
+		ret = of_property_read_string_index(node, "clock-output-names", i, &clk_name);
+		if (ret) {
+			if (multi)
+				init.name = kasprintf(GFP_KERNEL, "%s.%d", node->name, clk_index);
+			else
+				init.name = kasprintf(GFP_KERNEL, "%s",	   node->name);
+		} else {
+			init.name = kasprintf(GFP_KERNEL, "%s.%s", node->name, clk_name);
+		}
+
+		init.ops = &baikal_clk_ops;
+		init.flags = CLK_IGNORE_UNUSED;
+		init.parent_names = NULL;
+		init.num_parents = 0;
+
+		cmu = kmalloc(sizeof(*cmu), GFP_KERNEL);
+		cmu->base = base + 0x10 * clk_index;
+		cmu->hw.init = &init;
+
+		clk = clk_register(NULL, &cmu->hw);
+		if (!IS_ERR(clk)) {
+			clk_register_clkdev(clk, init.name, NULL);
+			if (multi)
+				clk_data->clks[clk_index] = clk;
+		}
+	}
+
+	if (multi)
+		ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get, clk_data);
+	else
+		ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, clk);
+
+	return ret;
+}
+
+static int baikal_clk_remove(struct platform_device *pdev)
+{
+	of_clk_del_provider(pdev->dev.of_node);
+	return 0;
+}
+
+#ifdef CONFIG_ACPI
+const char *baikal_acpi_ref_clk_str[] = { "baikal_ref_clk" };
+
+static struct clk *baikal_acpi_ref_clk;
+
+struct baikal_acpi_clk_data {
+	struct clk *cmu_clk;
+	struct clk_lookup *cmu_clk_l;
+	struct clk **clk;
+	struct clk_lookup **clk_l;
+	unsigned int clk_num;
+};
+
+static int baikal_acpi_clk_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct acpi_device *ref_dev, *adev = to_acpi_device_node(pdev->dev.fwnode);
+	struct clk_init_data init, *init_ch;
+	struct baikal_clk *cmu, *cmu_ch;
+	struct baikal_acpi_clk_data *clk_data = NULL;
+	union acpi_object *package, *element;
+	acpi_status status;
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	int size, i, index, ret = 0;
+	char *str, *str2;
+	const char *cmu_name;
+
+	cmu = devm_kzalloc(dev, sizeof(*cmu), GFP_KERNEL);
+	if (!cmu)
+		return -ENOMEM;
+
+	status = acpi_evaluate_object_typed(adev->handle, "PROP", NULL, &buffer, ACPI_TYPE_PACKAGE);
+	if (ACPI_FAILURE(status)) {
+		dev_err(dev, "failed to get PROP data\n");
+		return -ENODEV;
+	}
+
+	package = buffer.pointer;
+	if (package->package.count != 2) {
+		dev_err(dev, "invalid PROP data\n");
+		ret = -EINVAL;
+		goto ret;
+	}
+
+	element = &package->package.elements[0];
+	if (element->type != ACPI_TYPE_INTEGER) {
+		dev_err(dev, "failed to get CMU id\n");
+		ret = -EINVAL;
+		goto ret;
+	}
+
+	cmu->base = element->integer.value;
+
+	element = &package->package.elements[1];
+	if (element->type != ACPI_TYPE_STRING) {
+		dev_err(dev, "failed to get CMU clock name\n");
+		ret = -EINVAL;
+		goto ret;
+	}
+
+	str = devm_kzalloc(dev, element->string.length + 1, GFP_KERNEL);
+	if (!str) {
+		ret = -ENOMEM;
+		goto ret;
+	}
+
+	memcpy(str, element->string.pointer, element->string.length);
+	cmu_name = str;
+
+	acpi_os_free(buffer.pointer);
+	buffer.length = ACPI_ALLOCATE_BUFFER;
+	buffer.pointer = NULL;
+
+	init.parent_names = baikal_acpi_ref_clk_str;
+	init.num_parents = 1;
+	init.name = cmu_name;
+	init.ops = &baikal_clk_ops;
+	init.flags = CLK_IGNORE_UNUSED;
+
+	cmu->hw.init = &init;
+
+	clk_data = devm_kzalloc(dev, sizeof(*clk_data), GFP_KERNEL);
+	if (!clk_data)
+		return -ENOMEM;
+
+	clk_data->cmu_clk = clk_register(NULL, &cmu->hw);
+	if (IS_ERR(clk_data->cmu_clk)) {
+		dev_err(dev, "failed to register CMU clock\n");
+		return PTR_ERR(clk_data->cmu_clk);
+	}
+
+	clk_data->cmu_clk_l = clkdev_create(clk_data->cmu_clk, cmu_name, NULL);
+	if (!clk_data->cmu_clk_l) {
+		dev_err(dev, "failed to register CMU clock lookup\n");
+		clk_unregister(clk_data->cmu_clk);
+		return -ENOMEM;
+	}
+
+	/*
+	 * FIXME: Clock subsystem disables parent clock if all defined child
+	 * clock channels disabled. PLL clock is enabled here to avoid this.
+	 */
+	clk_prepare_enable(clk_data->cmu_clk);
+
+	platform_set_drvdata(pdev, clk_data);
+
+	status = acpi_evaluate_object_typed(adev->handle, "CLKS", NULL, &buffer, ACPI_TYPE_PACKAGE);
+	if (ACPI_FAILURE(status)) {
+		buffer.pointer = NULL;
+		goto ret;
+	}
+
+	package = buffer.pointer;
+	if (!package->package.count || package->package.count % 4) {
+		dev_err(dev, "invalid CLKS data\n");
+		ret = -EINVAL;
+		goto ret;
+	}
+
+	clk_data->clk_num = package->package.count >> 2;
+	clk_data->clk = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct clk *), GFP_KERNEL);
+	if (!clk_data->clk) {
+		ret = -ENOMEM;
+		goto ret;
+	}
+
+	clk_data->clk_l = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct clk_lookup *), GFP_KERNEL);
+	if (!clk_data->clk_l) {
+		ret = -ENOMEM;
+		goto ret;
+	}
+
+	init_ch = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct clk_init_data), GFP_KERNEL);
+	if (!init_ch) {
+		ret = -ENOMEM;
+		goto ret;
+	}
+
+	cmu_ch = devm_kzalloc(dev, clk_data->clk_num * sizeof(struct baikal_clk), GFP_KERNEL);
+	if (!cmu_ch) {
+		ret = -ENOMEM;
+		goto ret;
+	}
+
+	for (i = 0; i < clk_data->clk_num; ++i) {
+		ref_dev = NULL;
+		size = 0;
+
+		element = &package->package.elements[4 * i];
+		if (element->type == ACPI_TYPE_LOCAL_REFERENCE && element->reference.handle)
+			ref_dev = acpi_fetch_acpi_dev(element->reference.handle);
+
+		element = &package->package.elements[4 * i + 1];
+		if (element->type == ACPI_TYPE_STRING) {
+			if (ref_dev)
+				size = strlen(dev_name(&ref_dev->dev)) + 1;
+
+			str = devm_kzalloc(dev, size + element->string.length + 1, GFP_KERNEL);
+			if (str) {
+				if (ref_dev) {
+					memcpy(str, dev_name(&ref_dev->dev), size - 1);
+					str[size - 1] = '_';
+					memcpy(str + size, element->string.pointer, element->string.length);
+				} else {
+					memcpy(str, element->string.pointer, element->string.length);
+				}
+			}
+		} else {
+			dev_err(dev, "failed to process clock device name #%i\n", i);
+			continue;
+		}
+
+		element = &package->package.elements[4 * i + 2];
+		if (element->type == ACPI_TYPE_INTEGER) {
+			index = element->integer.value;
+		} else {
+			dev_err(dev, "failed to process clock device id #%i\n", i);
+			continue;
+		}
+
+		element = &package->package.elements[4 * i + 3];
+		if (element->type == ACPI_TYPE_STRING) {
+			str2 = devm_kzalloc(dev, element->string.length + 1, GFP_KERNEL);
+			if (str2)
+				memcpy(str2, element->string.pointer, element->string.length);
+		} else {
+			str2 = NULL;
+		}
+
+		init_ch[i].parent_names = &cmu_name;
+		init_ch[i].num_parents = 1;
+		init_ch[i].name = str;
+		init_ch[i].ops = &baikal_clk_ops;
+		init_ch[i].flags = CLK_IGNORE_UNUSED;
+
+		cmu_ch[i].base = cmu->base + 0x20 + 0x10 * index;
+		cmu_ch[i].hw.init = &init_ch[i];
+
+		clk_data->clk[i] = clk_register(ref_dev ? &ref_dev->dev : NULL, &cmu_ch[i].hw);
+		if (IS_ERR(clk_data->clk[i])) {
+			dev_err(dev, "failed to register CMU channel clock #%i\n", i);
+			clk_data->clk[i] = NULL;
+			continue;
+		}
+
+		if (ref_dev)
+			clk_data->clk_l[i] = clkdev_create(clk_data->clk[i], str2, "%s", dev_name(&ref_dev->dev));
+		else
+			clk_data->clk_l[i] = clkdev_create(clk_data->clk[i], str2, NULL);
+
+		if (!clk_data->clk_l[i]) {
+			dev_err(dev, "failed to register CMU channel clock lookup #%i\n", i);
+			clk_unregister(clk_data->clk[i]);
+			clk_data->clk[i] = NULL;
+			continue;
+		}
+	}
+
+	clk_data = NULL;
+
+ret:
+	if (buffer.pointer)
+		acpi_os_free(buffer.pointer);
+
+	if (clk_data) {
+		clk_disable_unprepare(clk_data->cmu_clk);
+		clkdev_drop(clk_data->cmu_clk_l);
+		clk_unregister(clk_data->cmu_clk);
+	}
+
+	return ret;
+}
+
+static int baikal_acpi_clk_remove(struct platform_device *pdev)
+{
+	struct baikal_acpi_clk_data *clk_data = platform_get_drvdata(pdev);
+	int i;
+
+	if (clk_data) {
+		clk_disable_unprepare(clk_data->cmu_clk);
+		clkdev_drop(clk_data->cmu_clk_l);
+		clk_unregister(clk_data->cmu_clk);
+
+		for (i = 0; i < clk_data->clk_num; ++i) {
+			if (clk_data->clk_l[i])
+				clkdev_drop(clk_data->clk_l[i]);
+			if (clk_data->clk[i])
+				clk_unregister(clk_data->clk[i]);
+		}
+	}
+
+	return 0;
+}
+
+static const struct acpi_device_id baikal_acpi_clk_device_ids[] = {
+	{ "BKLE1001" },
+	{ }
+};
+
+static struct platform_driver baikal_acpi_clk_driver = {
+	.probe		= baikal_acpi_clk_probe,
+	.remove		= baikal_acpi_clk_remove,
+	.driver		= {
+		.name	= "bs1000-cmu-acpi",
+		.acpi_match_table = ACPI_PTR(baikal_acpi_clk_device_ids)
+	}
+};
+
+static int __init baikal_acpi_clk_driver_init(void)
+{
+	if (!acpi_disabled) {
+		struct clk_lookup *baikal_acpi_ref_clk_lookup;
+
+		baikal_acpi_ref_clk = clk_register_fixed_rate(NULL, baikal_acpi_ref_clk_str[0], NULL, 0, 25000000);
+		if (IS_ERR(baikal_acpi_ref_clk)) {
+			pr_err("%s: failed to register reference clock\n", __func__);
+			return PTR_ERR(baikal_acpi_ref_clk);
+		}
+
+		baikal_acpi_ref_clk_lookup = clkdev_create(baikal_acpi_ref_clk, NULL, "%s", baikal_acpi_ref_clk_str[0]);
+		if (!baikal_acpi_ref_clk_lookup) {
+			clk_unregister_fixed_rate(baikal_acpi_ref_clk);
+			pr_err("%s: failed to register reference clock lookup\n", __func__);
+			return -ENOMEM;
+		}
+
+		clk_prepare_enable(baikal_acpi_ref_clk);
+
+		return platform_driver_register(&baikal_acpi_clk_driver);
+	}
+
+	return 0;
+}
+
+device_initcall(baikal_acpi_clk_driver_init);
+#endif
+
+static const struct of_device_id baikal_clk_of_match[] = {
+	{ .compatible = "baikal,bs1000-cmu" },
+	{ /* sentinel */ }
+};
+
+static struct platform_driver bs1000_cmu_driver = {
+	.probe	= baikal_clk_probe,
+	.remove	= baikal_clk_remove,
+	.driver	= {
+		.name = "bs1000-cmu",
+		.of_match_table = baikal_clk_of_match
+	}
+};
+module_platform_driver(bs1000_cmu_driver);
+
+MODULE_DESCRIPTION("Baikal BE-S1000 clock driver");
+MODULE_AUTHOR("Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bs1000-cmu");
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 03/39] USB: Add support for Baikal USB PHY
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 01/39] Baikal Electronics SoC family Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 02/39] Clk: Add clock drivers for Baikal BE-M1000 with new firmware Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 04/39] PCI: Add support for PCIe controller for Baikal BE-M1000 Daniil Gnusarev
                   ` (36 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

Add support for USB PHY for Baikal BE-M1000 with firmware
from SDK-ARM64-2403-6.6

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Co-developed-by: Baikal Electronics <info@baikalelectronics.ru>
---
 drivers/phy/Kconfig                 |   1 +
 drivers/phy/Makefile                |   1 +
 drivers/phy/baikal/Kconfig          |  10 +
 drivers/phy/baikal/Makefile         |   3 +
 drivers/phy/baikal/baikal-usb-phy.c | 305 ++++++++++++++++++++++++++++
 5 files changed, 320 insertions(+)
 create mode 100644 drivers/phy/baikal/Kconfig
 create mode 100644 drivers/phy/baikal/Makefile
 create mode 100644 drivers/phy/baikal/baikal-usb-phy.c

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index e4502958fd62d..b590354e7f66c 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -74,6 +74,7 @@ config PHY_CAN_TRANSCEIVER
 
 source "drivers/phy/allwinner/Kconfig"
 source "drivers/phy/amlogic/Kconfig"
+source "drivers/phy/baikal/Kconfig"
 source "drivers/phy/broadcom/Kconfig"
 source "drivers/phy/cadence/Kconfig"
 source "drivers/phy/freescale/Kconfig"
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index fb3dc9de61115..624f6689d9f92 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_PHY_PISTACHIO_USB)		+= phy-pistachio-usb.o
 obj-$(CONFIG_USB_LGM_PHY)		+= phy-lgm-usb.o
 obj-y					+= allwinner/	\
 					   amlogic/	\
+					   baikal/	\
 					   broadcom/	\
 					   cadence/	\
 					   freescale/	\
diff --git a/drivers/phy/baikal/Kconfig b/drivers/phy/baikal/Kconfig
new file mode 100644
index 0000000000000..b8b598d6019e1
--- /dev/null
+++ b/drivers/phy/baikal/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config USB_PHY_BAIKAL
+	tristate "Baikal USB PHY driver"
+	depends on USB_SUPPORT
+	select GENERIC_PHY
+	select USB_PHY
+	help
+	  Enable this to support the USB PHY on Baikal SoCs.
+	  This driver controls both the USB2 PHY and the USB3 PHY.
\ No newline at end of file
diff --git a/drivers/phy/baikal/Makefile b/drivers/phy/baikal/Makefile
new file mode 100644
index 0000000000000..54fcd11988681
--- /dev/null
+++ b/drivers/phy/baikal/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_USB_PHY_BAIKAL)		+= baikal-usb-phy.o
diff --git a/drivers/phy/baikal/baikal-usb-phy.c b/drivers/phy/baikal/baikal-usb-phy.c
new file mode 100644
index 0000000000000..72e7b747914d3
--- /dev/null
+++ b/drivers/phy/baikal/baikal-usb-phy.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Baikal USB PHY driver
+ *
+ * Copyright (C) 2022-2023 Baikal Electronics, JSC
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <dt-bindings/phy/phy.h>
+
+struct phy_baikal_desc {
+	struct phy		*phy;
+	int			index;
+	bool			enable;
+};
+
+struct phy_baikal_priv {
+	struct phy_baikal_desc	**phys;
+	int			nphys;
+	struct clk_bulk_data	*clocks;
+	unsigned int		nclocks;
+};
+
+#ifdef CONFIG_ACPI
+static int phy_baikal_acpi_get_info(struct fwnode_handle *fwnode,
+				    bool *is_usb3, const char **ref_name)
+{
+	struct acpi_device *adev = to_acpi_device_node(fwnode);
+	struct acpi_device *ref_adev;
+	struct device *ref_dev;
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	acpi_status status;
+	int ret = 0;
+
+	*is_usb3 = fwnode_property_read_bool(fwnode, "usb3");
+	status = acpi_evaluate_object_typed(adev->handle, "CTRL", NULL,
+					    &buffer, ACPI_TYPE_PACKAGE);
+	if (ACPI_FAILURE(status)) {
+		dev_err(&adev->dev, "failed to get CTRL data\n");
+		return -ENODEV;
+	}
+
+	obj = buffer.pointer;
+	if (obj->package.count != 1) {
+		dev_err(&adev->dev, "invalid CTRL data\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	obj = &obj->package.elements[0];
+	if (obj->type != ACPI_TYPE_LOCAL_REFERENCE || !obj->reference.handle) {
+		dev_err(&adev->dev, "invalid CTRL reference\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	ref_adev = acpi_fetch_acpi_dev(obj->reference.handle);
+	if (!ref_adev) {
+		dev_err(&adev->dev, "failed to process CTRL reference\n");
+		ret = -ENODEV;
+		goto err;
+	}
+
+	ref_dev = bus_find_device_by_fwnode(&platform_bus_type,
+					    acpi_fwnode_handle(ref_adev));
+	if (!ref_dev) {
+		dev_err(&adev->dev, "failed to get referenced device\n");
+		ret = -ENODEV;
+		goto err;
+	}
+	*ref_name = dev_name(ref_dev);
+
+err:
+	acpi_os_free(buffer.pointer);
+	return ret;
+}
+
+static int phy_baikal_acpi_add(struct device *dev)
+{
+	struct phy_baikal_priv *priv = dev_get_drvdata(dev);
+	struct fwnode_handle *child;
+	const char **ref_name;
+	bool *is_usb3;
+	int count = 0, i, ret;
+
+	ref_name = kcalloc(priv->nphys, sizeof(*ref_name), GFP_KERNEL);
+	if (!ref_name)
+		return -ENOMEM;
+
+	is_usb3 = kcalloc(priv->nphys, sizeof(*is_usb3), GFP_KERNEL);
+	if (!is_usb3) {
+		kfree(ref_name);
+		return -ENOMEM;
+	}
+
+	device_for_each_child_node(dev, child) {
+		ret = phy_baikal_acpi_get_info(child, &is_usb3[count],
+					       &ref_name[count]);
+		if (ret)
+			goto err;
+
+		ret = phy_create_lookup(priv->phys[count]->phy,
+					is_usb3[count] ? "usb3-phy" : "usb2-phy",
+					ref_name[count]);
+		if (ret)
+			goto err;
+
+		++count;
+	}
+
+err:
+	if (ret) {
+		for (i = 0; i < count; ++i)
+			phy_remove_lookup(priv->phys[i]->phy,
+					  is_usb3[i] ? "usb3-phy" : "usb2-phy",
+					  ref_name[i]);
+	}
+
+	kfree(ref_name);
+	kfree(is_usb3);
+	return ret;
+}
+
+static void phy_baikal_acpi_remove(struct device *dev)
+{
+	struct phy_baikal_priv *priv = dev_get_drvdata(dev);
+	struct fwnode_handle *child;
+	const char *ref_name;
+	bool is_usb3;
+	int i, ret;
+
+	device_for_each_child_node(dev, child) {
+		ret = phy_baikal_acpi_get_info(child, &is_usb3, &ref_name);
+		if (ret) {
+			++i;
+			continue;
+		}
+
+		phy_remove_lookup(priv->phys[i++]->phy,
+				  is_usb3 ? "usb3-phy" : "usb2-phy",
+				  ref_name);
+	}
+}
+
+static void phy_baikal_acpi_init_clk(struct phy_baikal_desc *desc)
+{
+	struct device *dev = desc->phy->dev.parent;
+	struct phy_baikal_priv *priv = dev_get_drvdata(dev);
+	struct fwnode_handle *child;
+	int i = 0;
+
+	device_for_each_child_node(dev, child) {
+		if (i++ == desc->index) {
+			priv->clocks[2 * desc->index].clk =
+				devm_clk_get_optional(&to_acpi_device_node(child)->dev, "phy0_clk");
+			priv->clocks[2 * desc->index + 1].clk =
+				devm_clk_get_optional(&to_acpi_device_node(child)->dev, "phy1_clk");
+			break;
+		}
+	}
+}
+#else /* CONFIG_ACPI */
+static inline int phy_baikal_acpi_add(struct device *dev)
+{
+	return 0;
+}
+
+static inline void phy_baikal_acpi_remove(struct device *dev)
+{
+}
+
+static inline void phy_baikal_acpi_init_clk(struct phy_baikal_desc *desc)
+{
+}
+#endif
+
+static int phy_baikal_init(struct phy *phy)
+{
+	struct phy_baikal_priv *priv = dev_get_drvdata(phy->dev.parent);
+	struct phy_baikal_desc *desc = phy_get_drvdata(phy);
+	int n = 2 * desc->index;
+
+	if (!acpi_disabled)
+		phy_baikal_acpi_init_clk(desc);
+
+	if (desc->enable) {
+		clk_prepare_enable(priv->clocks[n + 0].clk);
+		clk_prepare_enable(priv->clocks[n + 1].clk);
+		return 0;
+	} else {
+		clk_disable_unprepare(priv->clocks[n + 0].clk);
+		clk_disable_unprepare(priv->clocks[n + 1].clk);
+		return -1;
+	}
+}
+
+static const struct phy_ops phy_baikal_ops = {
+	.init	= phy_baikal_init,
+	.owner	= THIS_MODULE,
+};
+
+static struct phy *phy_baikal_xlate(struct device *dev, struct of_phandle_args *args)
+{
+	int i;
+	struct phy_baikal_priv *priv = dev_get_drvdata(dev);
+
+	for (i = 0; i < priv->nphys; i++) {
+		if (priv->phys[i]->index == args->args[0])
+			break;
+	}
+
+	return priv->phys[i]->phy;
+}
+
+static int phy_baikal_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct fwnode_handle *child;
+	struct phy *phy;
+	struct phy_baikal_priv *priv;
+	struct phy_baikal_desc *phy_desc;
+	int index;
+	int i = 0;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->nphys = device_get_child_node_count(dev);
+	priv->phys = devm_kcalloc(dev, priv->nphys, sizeof(*priv->phys), GFP_KERNEL);
+	if (!priv->phys)
+		return -ENOMEM;
+
+	if (acpi_disabled) {
+		priv->nclocks = devm_clk_bulk_get_all(dev, &priv->clocks);
+	} else {
+		priv->nclocks = 2 * priv->nphys;
+		priv->clocks = devm_kcalloc(dev, priv->nclocks,
+					    sizeof(*priv->clocks), GFP_KERNEL);
+		if (!priv->clocks)
+			return -ENOMEM;
+	}
+
+	dev_set_drvdata(dev, priv);
+	device_for_each_child_node(dev, child) {
+		fwnode_property_read_u32(child, "reg", &index);
+		phy = devm_phy_create(dev, NULL, &phy_baikal_ops);
+		if (!phy)
+			return -ENOMEM;
+
+		phy_desc = devm_kzalloc(dev, sizeof(*phy_desc), GFP_KERNEL);
+		if (!phy_desc)
+			return -ENOMEM;
+
+		phy_desc->phy = phy;
+		phy_desc->index = index;
+		phy_desc->enable = fwnode_property_read_bool(child, "enable");
+		priv->phys[i++] = phy_desc;
+		phy_set_drvdata(phy, phy_desc);
+	}
+
+	if (acpi_disabled)
+		return PTR_ERR_OR_ZERO(devm_of_phy_provider_register(dev, phy_baikal_xlate));
+	else
+		return phy_baikal_acpi_add(dev);
+}
+
+static int phy_baikal_remove(struct platform_device *pdev)
+{
+	if (!acpi_disabled)
+		phy_baikal_acpi_remove(&pdev->dev);
+
+	return 0;
+}
+
+static const struct of_device_id phy_baikal_table[] = {
+	{ .compatible = "baikal,bm1000-usb-phy" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, phy_baikal_table);
+
+static struct platform_driver phy_baikal_driver = {
+	.probe = phy_baikal_probe,
+	.remove = phy_baikal_remove,
+	.driver = {
+		.name = "baikal,bm1000-usb-phy",
+		.of_match_table = phy_baikal_table,
+	},
+};
+module_platform_driver(phy_baikal_driver);
+
+MODULE_DESCRIPTION("Baikal USB PHY driver");
+MODULE_LICENSE("GPL v2");
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 04/39] PCI: Add support for PCIe controller for Baikal BE-M1000
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (2 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 03/39] USB: Add support for Baikal USB PHY Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 05/39] AHCI SATA: Add support " Daniil Gnusarev
                   ` (35 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

Add support for PCIe controller for Baikal BE-M1000 with
firmware from SDK-ARM64-2403-6.6

Co-developed-by: Pavel Parkhomenko <pavel.parkhomenko@baikalelectronics.ru>
Co-developed-by: Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 drivers/acpi/pci_mcfg.c                       |   38 +
 drivers/pci/controller/dwc/Kconfig            |   32 +
 drivers/pci/controller/dwc/Makefile           |    5 +
 drivers/pci/controller/dwc/pcie-baikal-acpi.c |   23 +
 drivers/pci/controller/dwc/pcie-baikal-core.c | 2225 +++++++++++++++++
 drivers/pci/controller/dwc/pcie-baikal-tune.c |  570 +++++
 drivers/pci/controller/dwc/pcie-baikal.h      |   16 +
 drivers/pci/controller/dwc/pcie-designware.c  |    3 +-
 drivers/pci/controller/dwc/pcie-designware.h  |    1 +
 include/linux/pci-ecam.h                      |    2 +
 10 files changed, 2914 insertions(+), 1 deletion(-)
 create mode 100644 drivers/pci/controller/dwc/pcie-baikal-acpi.c
 create mode 100644 drivers/pci/controller/dwc/pcie-baikal-core.c
 create mode 100644 drivers/pci/controller/dwc/pcie-baikal-tune.c
 create mode 100644 drivers/pci/controller/dwc/pcie-baikal.h

diff --git a/drivers/acpi/pci_mcfg.c b/drivers/acpi/pci_mcfg.c
index 860014b89b8eb..70af3855c8eff 100644
--- a/drivers/acpi/pci_mcfg.c
+++ b/drivers/acpi/pci_mcfg.c
@@ -171,6 +171,44 @@ static struct mcfg_fixup mcfg_quirks[] = {
 	ALTRA_ECAM_QUIRK(1, 13),
 	ALTRA_ECAM_QUIRK(1, 14),
 	ALTRA_ECAM_QUIRK(1, 15),
+
+#define BAIKAL_ECAM(table_id, rev, seg, ops) \
+	{ "BAIKAL", table_id, rev, seg, MCFG_BUS_ANY, ops }
+
+	/* Baikal-M Synopsys DesignWare PCIe */
+	BAIKAL_ECAM("BKLEMCFG", 1, 0, &baikal_m_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 1, 1, &baikal_m_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 1, 2, &baikal_m_pcie_ecam_ops),
+
+	/* Baikal-S Synopsys DesignWare PCIe */
+	BAIKAL_ECAM("BKLEMCFG", 2, 0, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 1, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 2, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 3, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 4, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 5, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 6, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 7, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 8, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 9, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 10, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 11, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 12, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 13, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 14, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 15, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 16, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 17, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 18, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 19, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 20, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 21, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 22, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 23, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 24, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 25, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 26, &baikal_s_pcie_ecam_ops),
+	BAIKAL_ECAM("BKLEMCFG", 2, 27, &baikal_s_pcie_ecam_ops),
 #endif /* ARM64 */
 
 #ifdef CONFIG_LOONGARCH
diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
index ab96da43e0c2e..5c30a5ace0a95 100644
--- a/drivers/pci/controller/dwc/Kconfig
+++ b/drivers/pci/controller/dwc/Kconfig
@@ -61,6 +61,38 @@ config PCIE_ARTPEC6_EP
 	  Enables support for the PCIe controller in the ARTPEC-6 SoC to work in
 	  endpoint mode. This uses the DesignWare core.
 
+config PCIE_BAIKAL
+	bool
+
+config PCIE_BAIKAL_HOST
+	bool "Baikal SoCs PCIe controller - Host Mode"
+	depends on ARCH_BAIKAL || COMPILE_TEST
+	depends on OF || (ACPI && PCI_QUIRKS)
+	depends on PCI_MSI
+	select PCIE_DW_HOST
+	select PCIE_BAIKAL
+	help
+	  Enables support for the PCIe controller in Baikal SoCs to work in
+	  host mode. There are two instances of PCIe controller in Baikal SoCs.
+	  This controller can work either as EP or RC. In order to enable
+	  host-specific features PCIE_BAIKAL_HOST must be selected and in order
+	  to enable device-specific features PCIE_BAIKAL_EP must be selected.
+	  This uses the DesignWare core.
+
+config PCIE_BAIKAL_EP
+	bool "Baikal SoCs PCIe controller - Endpoint Mode"
+	depends on ARCH_BAIKAL || COMPILE_TEST
+	depends on PCI_ENDPOINT
+	select PCIE_DW_EP
+	select PCIE_BAIKAL
+	help
+	  Enables support for the PCIe controller in Baikal SoCs to work in
+	  host mode. There are two instances of PCIe controller in Baikal SoCs.
+	  This controller can work either as EP or RC. In order to enable
+	  host-specific features PCIE_BAIKAL_HOST must be selected and in order
+	  to enable device-specific features PCIE_BAIKAL_EP must be selected.
+	  This uses the DesignWare core.
+
 config PCIE_BT1
 	tristate "Baikal-T1 PCIe controller"
 	depends on MIPS_BAIKAL_T1 || COMPILE_TEST
diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
index bf5c311875a1e..4e213d43a517c 100644
--- a/drivers/pci/controller/dwc/Makefile
+++ b/drivers/pci/controller/dwc/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_PCIE_DW) += pcie-designware.o
 obj-$(CONFIG_PCIE_DW_HOST) += pcie-designware-host.o
 obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
 obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
+obj-$(CONFIG_PCIE_BAIKAL) += pcie-baikal.o
 obj-$(CONFIG_PCIE_BT1) += pcie-bt1.o
 obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
 obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
@@ -27,6 +28,9 @@ obj-$(CONFIG_PCIE_UNIPHIER) += pcie-uniphier.o
 obj-$(CONFIG_PCIE_UNIPHIER_EP) += pcie-uniphier-ep.o
 obj-$(CONFIG_PCIE_VISCONTI_HOST) += pcie-visconti.o
 
+pcie-baikal-objs := pcie-baikal-core.o \
+                    pcie-baikal-tune.o
+
 # The following drivers are for devices that use the generic ACPI
 # pci_root.c driver but don't support standard ECAM config access.
 # They contain MCFG quirks to replace the generic ECAM accessors with
@@ -43,6 +47,7 @@ obj-$(CONFIG_PCI_HISI) += pcie-hisi.o
 ifdef CONFIG_ACPI
 ifdef CONFIG_PCI_QUIRKS
 obj-$(CONFIG_ARM64) += pcie-al.o
+obj-$(CONFIG_ARM64) += pcie-baikal-acpi.o
 obj-$(CONFIG_ARM64) += pcie-hisi.o
 obj-$(CONFIG_ARM64) += pcie-tegra194-acpi.o
 endif
diff --git a/drivers/pci/controller/dwc/pcie-baikal-acpi.c b/drivers/pci/controller/dwc/pcie-baikal-acpi.c
new file mode 100644
index 0000000000000..85da9f8d4e0f8
--- /dev/null
+++ b/drivers/pci/controller/dwc/pcie-baikal-acpi.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021-2023 Baikal Electronics, JSC
+ * Author: Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
+ */
+
+#include <linux/pci-ecam.h>
+
+#ifdef CONFIG_PCIE_BAIKAL
+extern const struct pci_ecam_ops baikal_m_pcie_ecam_ops;
+extern const struct pci_ecam_ops baikal_s_pcie_ecam_ops;
+#else
+const struct pci_ecam_ops baikal_m_pcie_ecam_ops = {
+	.bus_shift	= 20,
+	.pci_ops	= {
+		.map_bus	= pci_ecam_map_bus,
+		.read		= pci_generic_config_read,
+		.write		= pci_generic_config_write
+	}
+};
+
+const struct pci_ecam_ops baikal_s_pcie_ecam_ops = baikal_m_pcie_ecam_ops;
+#endif
diff --git a/drivers/pci/controller/dwc/pcie-baikal-core.c b/drivers/pci/controller/dwc/pcie-baikal-core.c
new file mode 100644
index 0000000000000..8e23af9950494
--- /dev/null
+++ b/drivers/pci/controller/dwc/pcie-baikal-core.c
@@ -0,0 +1,2225 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PCIe controller driver for Baikal Electronics SoCs
+ *
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ * Authors: Pavel Parkhomenko <pavel.parkhomenko@baikalelectronics.ru>
+ *          Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
+ */
+
+#include <linux/acpi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_device.h>
+#include <linux/pci.h>
+#include <linux/pci-ecam.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <acpi/acrestyp.h>
+
+#include "../../pci.h"
+#include "../../hotplug/pciehp.h"
+#include "pcie-designware.h"
+#include "pcie-baikal.h"
+
+#define BAIKAL_PCIE_LTSSM_MASK		0x3f
+#define BAIKAL_PCIE_LTSSM_STATE_L0	0x11
+
+#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
+#define BAIKAL_EDMA_WR_CH		4
+#define BAIKAL_EDMA_RD_CH		4
+
+struct baikal_pcie_acpi_data {
+	u64		mem_base;
+	phys_addr_t	mem_bus_addr;
+	u32		mem_size;
+};
+
+static const struct baikal_pcie_of_data bm1000_pcie_rc_of_data;
+static const struct baikal_pcie_of_data bs1000_pcie_rc_of_data;
+#endif
+
+struct baikal_pcie_of_data {
+	enum dw_pcie_device_mode	mode;
+	struct dw_pcie_ops		dw_pcie_ops;
+	int (*get_resources)(struct platform_device *pdev,
+			     struct dw_pcie *pci,
+			     const struct baikal_pcie_of_data *data);
+	int (*add_pcie_port)(struct platform_device *pdev);
+};
+
+static const char *baikal_pcie_link_speed_str(const unsigned int speed)
+{
+	static const char * const speed_str[] = {"2.5", "5.0", "8.0", "16.0"};
+
+	if (speed > 0 && speed <= ARRAY_SIZE(speed_str))
+		return speed_str[speed - 1];
+
+	return "???";
+}
+
+static void baikal_pcie_link_print_status(struct dw_pcie *pci)
+{
+	struct device *dev = pci->dev;
+	u16 exp_cap_off;
+	u32 reg;
+
+	if (!pci->ops->link_up(pci)) {
+		dev_info(dev, "link is down\n");
+		return;
+	}
+
+	exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+	reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKSTA);
+
+	dev_info(dev, "current link is %s GT/s%s, x%u\n",
+		 baikal_pcie_link_speed_str(FIELD_GET(PCI_EXP_LNKSTA_CLS, reg)),
+		 FIELD_GET(PCI_EXP_LNKSTA_LT, reg) ? " (training)" : "",
+		 FIELD_GET(PCI_EXP_LNKSTA_NLW, reg));
+}
+
+static bool baikal_pcie_link_is_training(struct dw_pcie *pci)
+{
+	u16 exp_cap_off;
+	u32 reg;
+
+	exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+	reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKSTA);
+	return FIELD_GET(PCI_EXP_LNKSTA_LT, reg);
+}
+
+static bool baikal_pcie_link_wait_training_done(struct dw_pcie *pci)
+{
+	struct device *dev = pci->dev;
+	unsigned long start_jiffies = jiffies;
+
+	while (baikal_pcie_link_is_training(pci)) {
+		if (time_after(jiffies, start_jiffies + HZ)) {
+			dev_err(dev, "link training timeout occurred\n");
+			return false;
+		}
+
+		udelay(100);
+	}
+
+	return true;
+}
+
+#define BM1000_PCIE_GPR_RESET_BASE		0x00
+#define BM1000_PCIE_GPR_RESET(x)		(((x) * 0x20) + BM1000_PCIE_GPR_RESET_BASE)
+#define BM1000_PCIE_PHY_RST			BIT(0)
+#define BM1000_PCIE_PIPE_RST			BIT(4) /* x4 controllers only */
+#define BM1000_PCIE_PIPE0_RST			BIT(4) /* x8 controller only */
+#define BM1000_PCIE_PIPE1_RST			BIT(5) /* x8 controller only */
+#define BM1000_PCIE_CORE_RST			BIT(8)
+#define BM1000_PCIE_PWR_RST			BIT(9)
+#define BM1000_PCIE_STICKY_RST			BIT(10)
+#define BM1000_PCIE_NONSTICKY_RST		BIT(11)
+#define BM1000_PCIE_HOT_RST			BIT(12)
+#define BM1000_PCIE_ADB_PWRDWN			BIT(13)
+
+#define BM1000_PCIE_GPR_STATUS_BASE		0x04
+#define BM1000_PCIE_GPR_STATUS(x)		(((x) * 0x20) + BM1000_PCIE_GPR_STATUS_BASE)
+
+#define BM1000_PCIE_GPR_GENCTL_BASE		0x08
+#define BM1000_PCIE_GPR_GENCTL(x)		(((x) * 0x20) + BM1000_PCIE_GPR_GENCTL_BASE)
+#define BM1000_PCIE_LTSSM_ENABLE		BIT(1)
+#define BM1000_PCIE_DBI2_MODE			BIT(2)
+#define BM1000_PCIE_PHY_MGMT_ENABLE		BIT(3)
+
+#define BM1000_PCIE_GPR_MSI_TRANS_CTL2		0xf8
+#define BM1000_PCIE_MSI_TRANS_EN(x)		BIT(9 + (x))
+#define BM1000_PCIE_MSI_TRANS_RCNUM(x)		((x) << (2 * (x)))
+#define BM1000_PCIE_MSI_TRANS_RCNUM_MASK(x)	((3) << (2 * (x)))
+
+#define BM1000_PCIE0_DBI_BASE			0x02200000
+#define BM1000_PCIE1_DBI_BASE			0x02210000
+#define BM1000_PCIE2_DBI_BASE			0x02220000
+
+struct bm1000_pcie {
+	struct dw_pcie		*pci;
+	unsigned int		num;
+	struct regmap		*gpr;
+	union {
+		struct gpio_desc *reset_gpio;
+		struct {
+			u8 num : 5;
+			u8 polarity : 1;
+			u8 is_set : 1;
+		} gpio[2];
+	};
+	char			reset_name[32];
+	bool			retrained;
+};
+
+void bm1000_pcie_phy_enable(struct dw_pcie *pci)
+{
+	struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
+	u32 reg;
+
+	regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
+	reg |= BM1000_PCIE_PHY_MGMT_ENABLE | BM1000_PCIE_DBI2_MODE;
+	regmap_write(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), reg);
+}
+
+void bm1000_pcie_phy_disable(struct dw_pcie *pci)
+{
+	struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
+	u32 reg;
+
+	regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
+	reg &= ~(BM1000_PCIE_PHY_MGMT_ENABLE | BM1000_PCIE_DBI2_MODE);
+	regmap_write(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), reg);
+}
+
+static int bm1000_get_resources(struct platform_device *pdev,
+				struct dw_pcie *pci,
+				const struct baikal_pcie_of_data *data)
+{
+	struct bm1000_pcie *bm = platform_get_drvdata(pdev);
+	struct device *dev = pci->dev;
+	struct resource *res;
+
+	bm->pci = pci;
+	bm->gpr = syscon_regmap_lookup_by_compatible("baikal,bm1000-pcie-gpr");
+	if (IS_ERR(bm->gpr)) {
+		dev_err(dev, "failed to find PCIe GPR registers\n");
+		return PTR_ERR(bm->gpr);
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
+	if (!res) {
+		dev_err(dev, "failed to find \"dbi\" region\n");
+		return -EINVAL;
+	}
+
+	pci->dbi_base = devm_pci_remap_cfg_resource(dev, res);
+	if (IS_ERR(pci->dbi_base))
+		return PTR_ERR(pci->dbi_base);
+
+	if (res->start == BM1000_PCIE0_DBI_BASE) {
+		bm->num = 0;
+	} else if (res->start == BM1000_PCIE1_DBI_BASE) {
+		bm->num = 1;
+	} else if (res->start == BM1000_PCIE2_DBI_BASE) {
+		bm->num = 2;
+	} else {
+		dev_err(dev, "incorrect \"dbi\" base\n");
+		return -EINVAL;
+	}
+
+	pci->link_gen = of_pci_get_max_link_speed(dev->of_node);
+	if (pci->link_gen <= 0 || pci->link_gen > 3)
+		pci->link_gen = 3;
+
+	return 0;
+}
+
+static int bm1000_pcie_link_up(struct dw_pcie *pci)
+{
+	struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
+	u32 reg;
+
+	regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
+	if (!(reg & BM1000_PCIE_LTSSM_ENABLE))
+		return 0;
+
+	regmap_read(bm->gpr, BM1000_PCIE_GPR_STATUS(bm->num), &reg);
+	return (reg & BAIKAL_PCIE_LTSSM_MASK) == BAIKAL_PCIE_LTSSM_STATE_L0;
+}
+
+static int bm1000_pcie_start_link(struct dw_pcie *pci)
+{
+	struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
+	u32 reg;
+
+	regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
+	reg |= BM1000_PCIE_LTSSM_ENABLE;
+	regmap_write(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), reg);
+	return 0;
+}
+
+static void bm1000_pcie_link_speed_fixup(struct bm1000_pcie *bm,
+					 struct pci_bus *bus)
+{
+	struct pci_dev *dev;
+	struct dw_pcie *pci = bm->pci;
+	unsigned int dev_lnkcap_speed;
+	unsigned int dev_lnkcap_width;
+	unsigned int rc_lnkcap_speed;
+	unsigned int rc_lnksta_speed;
+	unsigned int rc_target_speed;
+	u16 exp_cap_off;
+	u32 reg;
+
+	/* Return if the bus has already been retrained */
+	if (bm->retrained)
+		return;
+
+	list_for_each_entry(dev, &bus->devices, bus_list)
+		if (dev->subordinate)
+			bm1000_pcie_link_speed_fixup(bm, dev->subordinate);
+
+	/* Skip root bridge and devices not directly attached to the RC */
+	if (pci_is_root_bus(bus) || !pci_is_root_bus(bus->parent))
+		return;
+
+	dev = list_first_entry_or_null(&bus->devices, struct pci_dev, bus_list);
+	if (!dev)
+		return;
+
+	exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+
+	reg = dw_pcie_readl_dbi(pci, exp_cap_off + PCI_EXP_LNKCAP);
+	rc_lnkcap_speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, reg);
+
+	reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKSTA);
+	rc_lnksta_speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, reg);
+
+	if (acpi_disabled) {
+		pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &reg);
+	} else {
+		struct pci_config_window *cfg;
+		int where = pci_pcie_cap(dev) + PCI_EXP_LNKCAP;
+
+		cfg = dev->bus->sysdata;
+		if (dev->bus->number == cfg->busr.start) {
+			if (PCI_SLOT(dev->devfn) > 0)
+				reg = 0;
+			else
+				reg = readl(pci->dbi_base + where);
+		} else {
+			unsigned int busn = dev->bus->number - cfg->busr.start;
+			unsigned int devfn_shift = cfg->ops->bus_shift - 8;
+
+			reg = readl(cfg->win +
+				    (busn << cfg->ops->bus_shift) +
+				    (dev->devfn << devfn_shift) +
+				    where);
+		}
+	}
+
+	dev_lnkcap_speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, reg);
+	dev_lnkcap_width = FIELD_GET(PCI_EXP_LNKCAP_MLW, reg);
+
+	baikal_pcie_link_print_status(pci);
+
+	/*
+	 * [2.5 -> 8.0 GT/s] is suitable way of retraining.
+	 * [2.5 -> 5.0 GT/s] is used when 8.0 GT/s could not be reached.
+	 * [5.0 -> 8.0 GT/s] causes system freezing sometimes.
+	 */
+	if (rc_lnkcap_speed < dev_lnkcap_speed)
+		rc_target_speed = rc_lnkcap_speed;
+	else
+		rc_target_speed = dev_lnkcap_speed;
+
+	if (rc_target_speed > pci->link_gen)
+		rc_target_speed = pci->link_gen;
+
+	while (rc_lnksta_speed < rc_target_speed) {
+		unsigned long start_jiffies;
+
+		/* Try to change link speed */
+		dev_info(pci->dev, "retrain link to %s GT/s\n",
+			 baikal_pcie_link_speed_str(rc_target_speed));
+
+		/* If link is already training wait for training to complete */
+		baikal_pcie_link_wait_training_done(pci);
+
+		/* Set desired speed */
+		reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKCTL2);
+		reg &= ~PCI_EXP_LNKCTL2_TLS;
+		reg |= rc_target_speed;
+		dw_pcie_writew_dbi(pci, exp_cap_off + PCI_EXP_LNKCTL2, reg);
+
+		/* Deassert and assert PORT_LOGIC_SPEED_CHANGE bit */
+		reg = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
+		reg &= ~PORT_LOGIC_SPEED_CHANGE;
+		dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, reg);
+		reg |=	PORT_LOGIC_SPEED_CHANGE;
+		dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, reg);
+
+		/* Wait for link training */
+		start_jiffies = jiffies;
+		for (;;) {
+			/* Wait for link training begin */
+			if (!baikal_pcie_link_is_training(pci)) {
+				if (time_after(jiffies, start_jiffies + HZ)) {
+					dev_err(pci->dev,
+						"link training has not started\n");
+					/*
+					 * Don't wait for training_done()
+					 * if it hasn't started.
+					 */
+					break;
+				}
+
+				udelay(100);
+				continue;
+			}
+
+			/* Wait for link training end */
+			if (!baikal_pcie_link_wait_training_done(pci))
+				break;
+
+			if (!dw_pcie_wait_for_link(pci)) {
+				/* Wait if link switched to config/recovery */
+				baikal_pcie_link_wait_training_done(pci);
+				baikal_pcie_link_print_status(pci);
+			}
+
+			break;
+		}
+
+		/* Check if the link is down after retrain */
+		if (!bm1000_pcie_link_up(pci)) {
+			/*
+			 * Check if the link has already been down and
+			 * the link is unable to re-establish at 2.5 GT/s
+			 */
+			if (rc_lnksta_speed == 0 &&
+			    rc_target_speed == PCI_EXP_LNKCTL2_TLS_2_5GT)
+				break;
+
+			rc_lnksta_speed = 0;
+			if (rc_target_speed > PCI_EXP_LNKCTL2_TLS_2_5GT) {
+				/* Try to use lower speed */
+				--rc_target_speed;
+			}
+
+			continue;
+		}
+
+		reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKSTA);
+		rc_lnksta_speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, reg);
+
+		/* Check if the targeted speed has not been reached */
+		if (rc_lnksta_speed < rc_target_speed &&
+		    rc_target_speed > PCI_EXP_LNKCTL2_TLS_2_5GT) {
+			/* Try to use lower speed */
+			--rc_target_speed;
+		}
+	}
+
+	bm->retrained = true;
+}
+
+static void bm1000_pcie_set_gpio(u8 num, bool enable)
+{
+	void __iomem *addr;
+	u32 val;
+
+	if (num > 31)
+		return;
+
+	addr = ioremap(0x20200000, 8);
+	if (addr) {
+		val = readl(addr);
+		if (enable)
+			val |= 1 << num;
+		else
+			val &= ~BIT(num);
+		writel(val, addr);
+
+		val = readl(addr + 4);
+		val |= 1 << num;
+		writel(val, addr + 4);
+
+		iounmap(addr);
+	}
+}
+
+static int bm1000_pcie_host_init(struct dw_pcie_rp *pp)
+{
+	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+	struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
+	struct device *dev = pci->dev;
+	int err;
+	int linkup;
+	u16 exp_cap_off;
+	u16 ext_cap_err_off;
+	u32 reg;
+
+	/* Disable access to PHY registers and DBI2 mode */
+	bm1000_pcie_phy_disable(pci);
+
+	bm->retrained = false;
+	linkup = bm1000_pcie_link_up(pci);
+
+	/* If link is not established yet, reset the RC */
+	if (!linkup) {
+		/* Disable link training */
+		regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
+		reg &= ~BM1000_PCIE_LTSSM_ENABLE;
+		regmap_write(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), reg);
+
+		/* Assert PERST pin */
+		if (acpi_disabled) {
+			snprintf(bm->reset_name, sizeof(bm->reset_name),
+				"pcie%u-reset", bm->num);
+
+			bm->reset_gpio = devm_fwnode_gpiod_get(dev,
+				of_fwnode_handle(dev->of_node), "reset",
+				GPIOD_OUT_HIGH, bm->reset_name);
+			err = PTR_ERR_OR_ZERO(bm->reset_gpio);
+			if (err) {
+				if (err != -ENOENT) {
+					dev_err(dev, "request GPIO failed (%d)\n", err);
+					return err;
+				}
+				/* reset gpio is optional */
+				bm->reset_gpio = NULL;
+			}
+		} else if (!acpi_disabled && bm->gpio[0].is_set) {
+			bm1000_pcie_set_gpio(bm->gpio[0].num, ~bm->gpio[0].polarity);
+		}
+
+		/* Reset the RC */
+		regmap_read(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), &reg);
+		reg |= BM1000_PCIE_NONSTICKY_RST |
+		       BM1000_PCIE_STICKY_RST	 |
+		       BM1000_PCIE_PWR_RST	 |
+		       BM1000_PCIE_CORE_RST	 |
+		       BM1000_PCIE_PHY_RST;
+
+		/* If the RC is PCIe x8, reset PIPE0 and PIPE1 */
+		if (bm->num == 2) {
+			reg |= BM1000_PCIE_PIPE0_RST |
+			       BM1000_PCIE_PIPE1_RST;
+		} else {
+			reg |= BM1000_PCIE_PIPE_RST;
+		}
+
+		regmap_write(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), reg);
+
+		if (!acpi_disabled && bm->num == 2 && bm->gpio[1].is_set) {
+			/* Assert PRSNT pin */
+			bm1000_pcie_set_gpio(bm->gpio[1].num, ~bm->gpio[1].polarity);
+		}
+
+		usleep_range(20000, 30000);
+
+		/* Deassert PERST pin */
+		if (bm->reset_gpio && acpi_disabled)
+			gpiod_set_value_cansleep(bm->reset_gpio, 0);
+		else if (!acpi_disabled && bm->gpio[0].is_set)
+			bm1000_pcie_set_gpio(bm->gpio[0].num, bm->gpio[0].polarity);
+
+		/* Deassert PHY reset */
+		regmap_read(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), &reg);
+		reg &= ~BM1000_PCIE_PHY_RST;
+		regmap_write(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), reg);
+
+		/* Deassert all software controlled resets */
+		regmap_read(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), &reg);
+		reg &= ~(BM1000_PCIE_ADB_PWRDWN	   |
+			 BM1000_PCIE_HOT_RST	   |
+			 BM1000_PCIE_NONSTICKY_RST |
+			 BM1000_PCIE_STICKY_RST	   |
+			 BM1000_PCIE_PWR_RST	   |
+			 BM1000_PCIE_CORE_RST	   |
+			 BM1000_PCIE_PHY_RST);
+
+		if (bm->num == 2) {
+			reg &= ~(BM1000_PCIE_PIPE0_RST |
+				 BM1000_PCIE_PIPE1_RST);
+		} else {
+			reg &= ~BM1000_PCIE_PIPE_RST;
+		}
+
+		regmap_write(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), reg);
+	}
+
+	/* Enable error reporting */
+	ext_cap_err_off = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_ERR);
+	reg = dw_pcie_readl_dbi(pci, ext_cap_err_off + PCI_ERR_ROOT_COMMAND);
+	reg |= PCI_ERR_ROOT_CMD_COR_EN	    |
+	       PCI_ERR_ROOT_CMD_NONFATAL_EN |
+	       PCI_ERR_ROOT_CMD_FATAL_EN;
+	dw_pcie_writel_dbi(pci, ext_cap_err_off + PCI_ERR_ROOT_COMMAND, reg);
+
+	exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+	reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_DEVCTL);
+	reg |= PCI_EXP_DEVCTL_CERE  |
+	       PCI_EXP_DEVCTL_NFERE |
+	       PCI_EXP_DEVCTL_FERE  |
+	       PCI_EXP_DEVCTL_URRE;
+	dw_pcie_writew_dbi(pci, exp_cap_off + PCI_EXP_DEVCTL, reg);
+
+	reg = dw_pcie_readl_dbi(pci, exp_cap_off + PCI_EXP_RTCTL);
+	reg |= PCI_EXP_RTCTL_SECEE  |
+	       PCI_EXP_RTCTL_SENFEE |
+	       PCI_EXP_RTCTL_SEFEE  |
+	       PCI_EXP_RTCTL_PMEIE;
+	dw_pcie_writel_dbi(pci, exp_cap_off + PCI_EXP_RTCTL, reg);
+
+	if (linkup) {
+		dev_info(dev, "link is already up\n");
+	} else {
+		/* Use 2.5 GT/s rate for link establishing */
+		reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKCTL2);
+		reg &= ~PCI_EXP_LNKCTL2_TLS;
+		reg |=	PCI_EXP_LNKCTL2_TLS_2_5GT;
+		dw_pcie_writew_dbi(pci, exp_cap_off + PCI_EXP_LNKCTL2, reg);
+	}
+
+	regmap_read(bm->gpr, BM1000_PCIE_GPR_MSI_TRANS_CTL2, &reg);
+	reg &= ~BM1000_PCIE_MSI_TRANS_RCNUM_MASK(bm->num);
+	reg |= BM1000_PCIE_MSI_TRANS_RCNUM(bm->num);
+	reg |= BM1000_PCIE_MSI_TRANS_EN(bm->num);
+	regmap_write(bm->gpr, BM1000_PCIE_GPR_MSI_TRANS_CTL2, reg);
+
+	/* RX/TX equalizers fine tune */
+	bm1000_pcie_tune(pci);
+
+	return 0;
+}
+
+static irqreturn_t bm1000_pcie_aer_irq_handler(int irq, void *arg)
+{
+	struct bm1000_pcie *bm = arg;
+	struct dw_pcie *pci = bm->pci;
+	struct device *dev = pci->dev;
+	u16 exp_cap_off;
+	u16 ext_cap_err_off;
+	u16 dev_sta;
+	u32 cor_err;
+	u32 root_err;
+	u32 uncor_err;
+
+	exp_cap_off	= dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+	ext_cap_err_off = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_ERR);
+
+	uncor_err = dw_pcie_readl_dbi(pci,
+				      ext_cap_err_off + PCI_ERR_UNCOR_STATUS);
+	cor_err	  = dw_pcie_readl_dbi(pci,
+				      ext_cap_err_off + PCI_ERR_COR_STATUS);
+	root_err  = dw_pcie_readl_dbi(pci,
+				      ext_cap_err_off + PCI_ERR_ROOT_STATUS);
+	dev_sta	  = dw_pcie_readw_dbi(pci,
+				      exp_cap_off + PCI_EXP_DEVSTA);
+
+	dw_pcie_writel_dbi(pci,
+			   ext_cap_err_off + PCI_ERR_UNCOR_STATUS, uncor_err);
+	dw_pcie_writel_dbi(pci,
+			   ext_cap_err_off + PCI_ERR_COR_STATUS, cor_err);
+	dw_pcie_writel_dbi(pci,
+			   ext_cap_err_off + PCI_ERR_ROOT_STATUS, root_err);
+	dw_pcie_writew_dbi(pci,
+			   exp_cap_off + PCI_EXP_DEVSTA, dev_sta);
+
+	dev_err(dev,
+		"DevSta:0x%04x RootErr:0x%x UncorErr:0x%x CorErr:0x%x\n",
+		dev_sta, root_err, uncor_err, cor_err);
+
+	return IRQ_HANDLED;
+}
+
+static const struct dw_pcie_host_ops bm1000_pcie_host_ops = {
+	.host_init	 = bm1000_pcie_host_init,
+};
+
+static int bm1000_add_pcie_port(struct platform_device *pdev)
+{
+	struct bm1000_pcie *bm = platform_get_drvdata(pdev);
+	struct device *dev = &pdev->dev;
+	struct dw_pcie *pci = bm->pci;
+	struct dw_pcie_rp *pp = &pci->pp;
+	int ret;
+
+	pp->irq = platform_get_irq_byname(pdev, "aer");
+	if (pp->irq < 0) {
+		dev_err(dev, "failed to get \"aer\" IRQ\n");
+		return pp->irq;
+	}
+
+	ret = devm_request_irq(dev, pp->irq, bm1000_pcie_aer_irq_handler,
+			       IRQF_SHARED, "bm1000-pcie-aer", bm);
+	if (ret) {
+		dev_err(dev, "failed to request IRQ %d\n", pp->irq);
+		return ret;
+	}
+
+	pp->num_vectors = MAX_MSI_IRQS;
+	pp->ops = &bm1000_pcie_host_ops;
+	ret = dw_pcie_host_init(pp);
+	if (ret) {
+		dev_err(dev, "failed to initialize host\n");
+		return ret;
+	}
+
+	bm1000_pcie_link_speed_fixup(bm, pp->bridge->bus);
+	return 0;
+}
+
+#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
+static void bm1000_pcie_prog_outbound_atu(struct dw_pcie *pci, int index,
+					  int type, u64 cpu_addr, u64 pci_addr,
+					  u32 size, u32 flags)
+{
+	u32 retries, val;
+
+	dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT,
+			   PCIE_ATU_REGION_DIR_OB | index);
+	dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE + PCIE_ATU_LOWER_BASE,
+			   lower_32_bits(cpu_addr));
+	dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE + PCIE_ATU_UPPER_BASE,
+			   upper_32_bits(cpu_addr));
+	dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE + PCIE_ATU_LIMIT,
+			   lower_32_bits(cpu_addr + size - 1));
+	dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE + PCIE_ATU_LOWER_TARGET,
+			   lower_32_bits(pci_addr));
+	dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE + PCIE_ATU_UPPER_TARGET,
+			   upper_32_bits(pci_addr));
+	dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE, type);
+	dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT_BASE + PCIE_ATU_REGION_CTRL2,
+			   PCIE_ATU_ENABLE | flags);
+
+	/*
+	 * Make sure ATU enable takes effect before any subsequent config
+	 * and I/O accesses.
+	 */
+	for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; ++retries) {
+		val = dw_pcie_readl_dbi(pci, PCIE_ATU_VIEWPORT_BASE +
+					     PCIE_ATU_REGION_CTRL2);
+		if (val & PCIE_ATU_ENABLE)
+			return;
+
+		mdelay(LINK_WAIT_IATU);
+	}
+	dev_err(pci->dev, "Outbound iATU is not being enabled\n");
+}
+
+#define PCIE_IATU_REGION_CTRL_2_REG_SHIFT_MODE	BIT(28)
+
+static void bm1000_pcie_setup_rc_acpi(struct dw_pcie_rp *pp,
+				      const struct baikal_pcie_acpi_data *mem_data)
+{
+	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+	acpi_status status;
+	u64 lanes;
+	u32 val;
+	int i;
+
+	/*
+	 * Enable DBI read-only registers for writing/updating configuration.
+	 * Write permission gets disabled towards the end of this function.
+	 */
+	dw_pcie_dbi_ro_wr_en(pci);
+
+	val = dw_pcie_readl_dbi(pci, PCIE_PORT_LINK_CONTROL);
+	val &= ~PORT_LINK_FAST_LINK_MODE;
+	val |= PORT_LINK_DLL_LINK_EN;
+	dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val);
+
+	status = acpi_evaluate_integer(to_acpi_device(pci->dev)->handle,
+				       "NUML", NULL, &lanes);
+	if (ACPI_FAILURE(status)) {
+		dev_dbg(pci->dev, "failed to get num-lanes\n");
+	} else {
+		pci->num_lanes = lanes;
+
+		/* Set the number of lanes */
+		val &= ~PORT_LINK_FAST_LINK_MODE;
+		val &= ~PORT_LINK_MODE_MASK;
+		switch (pci->num_lanes) {
+		case 1:
+			val |= PORT_LINK_MODE_1_LANES;
+			break;
+		case 2:
+			val |= PORT_LINK_MODE_2_LANES;
+			break;
+		case 4:
+			val |= PORT_LINK_MODE_4_LANES;
+			break;
+		case 8:
+			val |= PORT_LINK_MODE_8_LANES;
+			break;
+		default:
+			dev_err(pci->dev, "NUML %u: invalid value\n", pci->num_lanes);
+			goto skip_lanes;
+		}
+		dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val);
+
+		/* Set link width speed control register */
+		val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
+		val &= ~PORT_LOGIC_LINK_WIDTH_MASK;
+		switch (pci->num_lanes) {
+		case 1:
+			val |= PORT_LOGIC_LINK_WIDTH_1_LANES;
+			break;
+		case 2:
+			val |= PORT_LOGIC_LINK_WIDTH_2_LANES;
+			break;
+		case 4:
+			val |= PORT_LOGIC_LINK_WIDTH_4_LANES;
+			break;
+		case 8:
+			val |= PORT_LOGIC_LINK_WIDTH_8_LANES;
+			break;
+		}
+		dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
+	}
+
+skip_lanes:
+	/* Setup RC BARs */
+	dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0x00000004);
+	dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_1, 0x00000000);
+
+	/* Setup interrupt pins */
+	val = dw_pcie_readl_dbi(pci, PCI_INTERRUPT_LINE);
+	val &= 0xffff00ff;
+	val |= 0x00000100;
+	dw_pcie_writel_dbi(pci, PCI_INTERRUPT_LINE, val);
+
+	/* Setup bus numbers */
+	val = dw_pcie_readl_dbi(pci, PCI_PRIMARY_BUS);
+	val &= 0xff000000;
+	val |= 0x00ff0100;
+	dw_pcie_writel_dbi(pci, PCI_PRIMARY_BUS, val);
+
+	/* Setup command register */
+	val = dw_pcie_readl_dbi(pci, PCI_COMMAND);
+	val &= 0xffff0000;
+	val |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
+		PCI_COMMAND_MASTER | PCI_COMMAND_SERR;
+	dw_pcie_writel_dbi(pci, PCI_COMMAND, val);
+
+	pci->atu_base = pci->dbi_base + PCIE_ATU_VIEWPORT_BASE;
+	pci->atu_size = PCIE_ATU_VIEWPORT_SIZE;
+	pci->num_ob_windows = 4;
+	pci->num_ib_windows = 0;
+
+	for (i = 0; i < pci->num_ob_windows; ++i)
+		dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_OB, i);
+
+	/* Program ATU */
+	bm1000_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_CFG0,
+				      pp->cfg0_base, 0,
+				      SZ_2M,
+				      PCIE_IATU_REGION_CTRL_2_REG_SHIFT_MODE);
+	bm1000_pcie_prog_outbound_atu(pci, 1, PCIE_ATU_TYPE_CFG1,
+				      pp->cfg0_base, 0,
+				      pp->cfg0_size,
+				      PCIE_IATU_REGION_CTRL_2_REG_SHIFT_MODE);
+	bm1000_pcie_prog_outbound_atu(pci, 2, PCIE_ATU_TYPE_MEM,
+				      mem_data->mem_base, mem_data->mem_bus_addr,
+				      mem_data->mem_size, 0);
+	bm1000_pcie_prog_outbound_atu(pci, 3, PCIE_ATU_TYPE_IO,
+				      pp->io_base, pp->io_bus_addr,
+				      pp->io_size, 0);
+
+	dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0);
+
+	/* Program correct class for RC */
+	dw_pcie_writew_dbi(pci, PCI_CLASS_DEVICE, PCI_CLASS_BRIDGE_PCI);
+
+	val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
+	val |= PORT_LOGIC_SPEED_CHANGE;
+	dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
+
+	dw_pcie_dbi_ro_wr_dis(pci);
+}
+
+struct bm1000_pcie_acpi_gpio {
+	struct list_head node;
+	struct acpi_resource_gpio *ares;
+};
+
+static int bm1000_pcie_acpi_dev_filter_gpio(struct acpi_resource *ares, void *data)
+{
+	struct list_head *list = data;
+	struct bm1000_pcie_acpi_gpio *gpio;
+
+	if (ares->type == ACPI_RESOURCE_TYPE_GPIO) {
+		gpio = kzalloc(sizeof(*gpio), GFP_KERNEL);
+		if (gpio) {
+			INIT_LIST_HEAD(&gpio->node);
+			gpio->ares = &ares->data.gpio;
+			list_add_tail(&gpio->node, list);
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+static int bm1000_pcie_get_res_acpi(struct acpi_device *adev,
+				    struct acpi_device **res_dev,
+				    struct bm1000_pcie *bm,
+				    struct baikal_pcie_acpi_data *mem_data)
+{
+	struct device *dev = &adev->dev;
+	struct dw_pcie_rp *pp = &bm->pci->pp;
+	struct resource_entry *entry;
+	struct list_head list, *pos;
+	struct fwnode_handle *fwnode;
+	struct bm1000_pcie_acpi_gpio *gpio, *tmp;
+	int ret;
+	unsigned long flags = IORESOURCE_MEM;
+
+	fwnode = fwnode_get_named_child_node(&adev->fwnode, "RES0");
+	if (!fwnode) {
+		dev_err(dev, "failed to get RES0 subdevice\n");
+		return -EINVAL;
+	}
+
+	*res_dev = to_acpi_device_node(fwnode);
+	if (!*res_dev) {
+		dev_err(dev, "RES0 is not an acpi device node\n");
+		return -EINVAL;
+	}
+
+	INIT_LIST_HEAD(&list);
+	ret = acpi_dev_get_resources(*res_dev, &list,
+				     acpi_dev_filter_resource_type_cb,
+				     (void *)flags);
+	if (ret < 0) {
+		dev_err(dev, "failed to parse RES0._CRS method, error code %d\n", ret);
+		return ret;
+	}
+
+	if (ret != 2) {
+		dev_err(dev,
+			"invalid number of MEM resources present in RES0._CRS (%i, need 2)\n", ret);
+		return -EINVAL;
+	}
+
+	/* ECAM */
+	pos = list.next;
+	entry = list_entry(pos, struct resource_entry, node);
+	pp->cfg0_size = resource_size(entry->res);
+	pp->cfg0_base = entry->res->start;
+
+	/* DBI */
+	pos = pos->next;
+	entry = list_entry(pos, struct resource_entry, node);
+	if (entry->res->start == BM1000_PCIE0_DBI_BASE) {
+		bm->num = 0;
+	} else if (entry->res->start == BM1000_PCIE1_DBI_BASE) {
+		bm->num = 1;
+	} else if (entry->res->start == BM1000_PCIE2_DBI_BASE) {
+		bm->num = 2;
+	} else {
+		dev_err(dev, "incorrect \"dbi\" base\n");
+		return -EINVAL;
+	}
+	bm->pci->dbi_base = devm_ioremap_resource(dev, entry->res);
+	if (IS_ERR(bm->pci->dbi_base)) {
+		dev_err(dev, "error with dbi ioremap\n");
+		ret = PTR_ERR(bm->pci->dbi_base);
+		return ret;
+	}
+
+	acpi_dev_free_resource_list(&list);
+
+	/* Non-prefetchable memory */
+	INIT_LIST_HEAD(&list);
+	flags = IORESOURCE_MEM;
+	ret = acpi_dev_get_resources(adev, &list,
+				     acpi_dev_filter_resource_type_cb,
+				     (void *)flags);
+	if (ret < 0) {
+		dev_err(dev, "failed to parse _CRS method, error code %d\n", ret);
+		return ret;
+	}
+
+	if (ret != 1) {
+		dev_err(dev, "invalid number of MEM resources present in _CRS (%i, need 1)\n", ret);
+		return -EINVAL;
+	}
+
+	pos = list.next;
+	entry = list_entry(pos, struct resource_entry, node);
+	mem_data->mem_base = entry->res->start;
+	mem_data->mem_size = resource_size(entry->res);
+	mem_data->mem_bus_addr = entry->res->start - entry->offset;
+
+	acpi_dev_free_resource_list(&list);
+
+	/* I/O */
+	INIT_LIST_HEAD(&list);
+	flags = IORESOURCE_IO;
+	ret = acpi_dev_get_resources(adev, &list,
+				     acpi_dev_filter_resource_type_cb,
+				     (void *)flags);
+	if (ret < 0) {
+		dev_err(dev, "failed to parse _CRS method, error code %d\n", ret);
+		return ret;
+	}
+
+	if (ret != 1) {
+		dev_err(dev, "invalid number of IO resources present in _CRS (%i, need 1)\n", ret);
+		return -EINVAL;
+	}
+
+	pos = list.next;
+	entry = list_entry(pos, struct resource_entry, node);
+	pp->io_base = entry->res->start;
+	pp->io_size = resource_size(entry->res);
+	pp->io_bus_addr = entry->res->start - entry->offset;
+
+	acpi_dev_free_resource_list(&list);
+
+	/* GPIO */
+	INIT_LIST_HEAD(&list);
+	ret = acpi_dev_get_resources(*res_dev, &list,
+				     bm1000_pcie_acpi_dev_filter_gpio,
+				     &list);
+	if (ret < 0) {
+		dev_err(dev, "failed to parse RES0._CRS method, error code %d\n", ret);
+		return ret;
+	}
+
+	if (!ret) {
+		u8 count = 0;
+		u8 i;
+
+		list_for_each_entry(gpio, &list, node) {
+			for (i = 0; i < gpio->ares->pin_table_length &&
+			     count < ARRAY_SIZE(bm->gpio); ++i) {
+				bm->gpio[count].num = gpio->ares->pin_table[i] & 0x1f;
+				bm->gpio[count].polarity = 1;
+				bm->gpio[count].is_set = 1;
+				++count;
+			}
+
+			if (count == ARRAY_SIZE(bm->gpio))
+				break;
+		}
+	}
+
+	list_for_each_entry_safe(gpio, tmp, &list, node) {
+		list_del(&gpio->node);
+		kfree(gpio);
+	}
+
+	return 0;
+}
+
+static int bm1000_pcie_get_irq_acpi(struct device *dev,
+				    struct acpi_device *res_dev,
+				    struct bm1000_pcie *bm)
+{
+	struct dw_pcie_rp *pp = &bm->pci->pp;
+	struct resource res;
+	int ret;
+
+	memset(&res, 0, sizeof(res));
+
+	ret = acpi_irq_get(res_dev->handle, 0, &res);
+	if (ret) {
+		dev_err(dev, "failed to get irq %d\n", 0);
+		return ret;
+	}
+
+	if (res.flags & IORESOURCE_BITS) {
+		struct irq_data *irqd;
+
+		irqd = irq_get_irq_data(res.start);
+		if (!irqd)
+			return -ENXIO;
+
+		irqd_set_trigger_type(irqd, res.flags & IORESOURCE_BITS);
+	}
+
+	pp->irq = res.start;
+
+	ret = devm_request_irq(dev, pp->irq, bm1000_pcie_aer_irq_handler,
+			       IRQF_SHARED, "bm1000-pcie-aer", bm);
+	if (ret) {
+		dev_err(dev, "failed to request irq %d\n", pp->irq);
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct regmap *bm1000_regmap;
+
+static const struct regmap_config bm1000_pcie_syscon_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4
+};
+
+static struct regmap *bm1000_pcie_get_gpr_acpi(struct bm1000_pcie *bm)
+{
+	struct device *dev;
+	struct acpi_device *adev;
+	acpi_handle handle;
+	acpi_status status = AE_OK;
+	struct list_head list, *pos;
+	struct resource *res;
+	void __iomem *base;
+	struct regmap *regmap = NULL;
+	struct regmap_config config = bm1000_pcie_syscon_regmap_config;
+	unsigned long flags = IORESOURCE_MEM;
+	int ret;
+
+	if (bm1000_regmap)
+		return bm1000_regmap;
+
+	status = acpi_get_handle(NULL, "\\_SB.PGPR", &handle);
+	if (ACPI_FAILURE(status)) {
+		dev_err(dev, "failed to get PCIe GPR device\n");
+		return ERR_PTR(-ENODEV);
+	}
+
+	adev = acpi_fetch_acpi_dev(handle);
+	if (!adev) {
+		dev_err(dev, "failed to process PCIe GPR handle\n");
+		return ERR_PTR(-ENODEV);
+	}
+
+	dev = &adev->dev;
+	INIT_LIST_HEAD(&list);
+	ret = acpi_dev_get_resources(adev, &list,
+				     acpi_dev_filter_resource_type_cb,
+				     (void *)flags);
+	if (ret < 0) {
+		dev_err(dev, "failed to parse _CRS method, error code %d\n", ret);
+		return NULL;
+	}
+
+	if (ret != 1) {
+		dev_err(dev, "invalid number of MEM resources present in _CRS (%i, need 1)\n", ret);
+		goto ret;
+	}
+
+	pos = list.next;
+	res = list_entry(pos, struct resource_entry, node)->res;
+
+	base = devm_ioremap(dev, res->start, resource_size(res));
+	if (!base) {
+		dev_err(dev, "error with ioremap\n");
+		goto ret;
+	}
+
+	config.max_register = resource_size(res) - 4;
+
+	regmap = devm_regmap_init_mmio(dev, base, &config);
+	if (IS_ERR(regmap)) {
+		dev_err(dev, "regmap init failed\n");
+		devm_iounmap(dev, base);
+		goto ret;
+	}
+
+	dev_dbg(dev, "regmap %pR registered\n", res);
+
+	bm1000_regmap = regmap;
+
+ret:
+	acpi_dev_free_resource_list(&list);
+	return regmap;
+}
+
+static int bm1000_get_acpi_data(struct device *dev, struct bm1000_pcie *bm,
+				struct baikal_pcie_acpi_data *mem_data)
+{
+	struct acpi_device *adev = to_acpi_device(dev), *res_dev;
+	int ret;
+
+	bm->gpr = bm1000_pcie_get_gpr_acpi(bm);
+	if (IS_ERR_OR_NULL(bm->gpr)) {
+		dev_err(dev, "No PCIe GPR specified\n");
+		return -EINVAL;
+	}
+
+	ret = bm1000_pcie_get_res_acpi(adev, &res_dev, bm, mem_data);
+	if (ret) {
+		dev_err(dev, "failed to get resource info\n");
+		return ret;
+	}
+
+	ret = bm1000_pcie_get_irq_acpi(dev, res_dev, bm);
+	if (ret) {
+		dev_err(dev, "failed to get irq info\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int bm1000_pcie_init(struct pci_config_window *cfg)
+{
+	struct device *dev = cfg->parent;
+	struct bm1000_pcie *bm;
+	struct dw_pcie *pci;
+	struct dw_pcie_rp *pp;
+	struct baikal_pcie_acpi_data mem_data = {};
+	int ret;
+
+	pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
+	if (!pci)
+		return -ENOMEM;
+
+	pci->dev = dev;
+	pci->ops = &bm1000_pcie_rc_of_data.dw_pcie_ops;
+
+	bm = devm_kzalloc(dev, sizeof(*bm), GFP_KERNEL);
+	if (!bm)
+		return -ENOMEM;
+
+	cfg->priv = bm;
+	bm->pci = pci;
+	dev_set_drvdata(dev, bm);
+
+	ret = bm1000_get_acpi_data(dev, bm, &mem_data);
+	if (ret) {
+		dev_err(dev, "failed to get data from ACPI\n");
+		return ret;
+	}
+
+	pp = &pci->pp;
+	raw_spin_lock_init(&pp->lock);
+	pp->ops = &bm1000_pcie_host_ops;
+	pp->va_cfg0_base = devm_pci_remap_cfgspace(dev, pp->cfg0_base,
+						   pp->cfg0_size);
+	if (!pp->va_cfg0_base) {
+		dev_err(dev, "error with ioremap\n");
+		return -ENOMEM;
+	}
+
+	ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64));
+	if (ret) {
+		dev_err(dev, "failed to enable DMA\n");
+		return ret;
+	}
+
+	ret = bm1000_pcie_host_init(pp);
+	if (ret) {
+		dev_err(dev, "Failed to initialize host\n");
+		return ret;
+	}
+
+	dw_pcie_version_detect(pci);
+	bm1000_pcie_setup_rc_acpi(pp, &mem_data);
+
+	if (!bm1000_pcie_link_up(bm->pci))
+		bm1000_pcie_start_link(bm->pci);
+
+	/* Link will be retrained by 'bm1000_pcie_map_bus()' */
+	bm->retrained = false;
+	return 0;
+}
+
+static void __iomem *bm1000_pcie_map_bus(struct pci_bus *bus,
+					 unsigned int devfn, int where)
+{
+	struct pci_config_window *cfg = bus->sysdata;
+	struct bm1000_pcie *bm = cfg->priv;
+	unsigned int devfn_shift = cfg->ops->bus_shift - 8;
+	unsigned int busn = bus->number;
+	void __iomem *base;
+
+	if (!bm->retrained) {
+		struct pci_host_bridge **root_bridge = &bm->pci->pp.bridge;
+
+		if (!*root_bridge) {
+			*root_bridge = to_pci_host_bridge(bus->bridge);
+		} else if ((*root_bridge)->bus->is_added) {
+			struct acpi_device *adev = to_acpi_device(cfg->parent);
+			struct acpi_pci_root *root = adev->driver_data;
+
+			if (root->bus == (*root_bridge)->bus) {
+				pci_bus_add_devices(root->bus);
+				bm1000_pcie_link_speed_fixup(bm, root->bus);
+				bm->retrained = true;
+			}
+		}
+	}
+
+	if (bus->number != cfg->busr.start && !bm1000_pcie_link_up(bm->pci))
+		return NULL;
+
+	if (bus->number == cfg->busr.start) {
+		/*
+		 * The DW PCIe core doesn't filter out transactions to other
+		 * devices/functions on the root bus num, so we do this here.
+		 */
+		if (PCI_SLOT(devfn) > 0)
+			return NULL;
+		else
+			return bm->pci->dbi_base + where;
+	}
+
+	if (busn < cfg->busr.start || busn > cfg->busr.end)
+		return NULL;
+
+	busn -= cfg->busr.start;
+	base = cfg->win + (busn << cfg->ops->bus_shift);
+	return base + (devfn << devfn_shift) + where;
+}
+
+const struct pci_ecam_ops baikal_m_pcie_ecam_ops = {
+	.bus_shift	= 20,
+	.init		= bm1000_pcie_init,
+	.pci_ops	= {
+		.map_bus	= bm1000_pcie_map_bus,
+		.read		= pci_generic_config_read,
+		.write		= pci_generic_config_write
+	}
+};
+#endif
+
+#define BS1000_PCIE_APB_PE_GEN_CTRL3			0x58
+#define BS1000_PCIE_APB_PE_GEN_CTRL3_LTSSM_EN		BIT(0)
+
+#define BS1000_PCIE_APB_PE_LINK_DBG2			0xb4
+#define BS1000_PCIE_APB_PE_LINK_DBG2_SMLH_LINK_UP	BIT(6)
+#define BS1000_PCIE_APB_PE_LINK_DBG2_RDLH_LINK_UP	BIT(7)
+
+#define BS1000_PCIE_APB_PE_ERR_STS			0xe0
+#define BS1000_PCIE_APB_PE_INT_STS			0xe8
+
+#define BS1000_PCIE0_P0_DBI_BASE			0x39000000
+#define BS1000_PCIE0_P1_DBI_BASE			0x39400000
+#define BS1000_PCIE1_P0_DBI_BASE			0x3d000000
+#define BS1000_PCIE1_P1_DBI_BASE			0x3d400000
+#define BS1000_PCIE2_P0_DBI_BASE			0x45000000
+#define BS1000_PCIE2_P1_DBI_BASE			0x45400000
+
+#define BS1000_ADDR_IN_CHIP(base)			((base) & 0xffffffff)
+
+struct bs1000_pcie {
+	struct dw_pcie		*pci;
+	void __iomem		*apb_base;
+	u64			cpu_addr_mask;
+#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
+	int			edma_irq[BAIKAL_EDMA_WR_CH + BAIKAL_EDMA_RD_CH];
+#endif
+};
+
+static void bs1000_pcie_ep_init(struct dw_pcie_ep *ep)
+{
+	struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
+	enum pci_barno bar;
+
+	for (bar = BAR_0; bar <= BAR_5; bar++)
+		dw_pcie_ep_reset_bar(pci, bar);
+}
+
+static int bs1000_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no,
+				    enum pci_epc_irq_type type,
+				    u16 interrupt_num)
+{
+	struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
+
+	switch (type) {
+	case PCI_EPC_IRQ_LEGACY:
+		return dw_pcie_ep_raise_legacy_irq(ep, func_no);
+	case PCI_EPC_IRQ_MSI:
+		return dw_pcie_ep_raise_msi_irq(ep, func_no, interrupt_num);
+	default:
+		dev_err(pci->dev, "UNKNOWN IRQ type\n");
+		return -EINVAL;
+	}
+}
+
+static const struct pci_epc_features bs1000_pcie_epc_features = {
+	.linkup_notifier = false,
+	.msi_capable = true,
+	.msix_capable = false,
+	.reserved_bar = BIT(BAR_3) | BIT(BAR_5),
+	.align = SZ_64K,
+};
+
+static const struct pci_epc_features*
+bs1000_pcie_ep_get_features(struct dw_pcie_ep *ep)
+{
+	return &bs1000_pcie_epc_features;
+}
+
+static const struct dw_pcie_ep_ops bs1000_pcie_ep_ops = {
+	.ep_init	= bs1000_pcie_ep_init,
+	.raise_irq	= bs1000_pcie_ep_raise_irq,
+	.get_features	= bs1000_pcie_ep_get_features,
+};
+
+static int bs1000_get_resources(struct platform_device *pdev,
+				struct dw_pcie *pci,
+				const struct baikal_pcie_of_data *data)
+{
+	struct bs1000_pcie *bs = platform_get_drvdata(pdev);
+	struct device *dev = pci->dev;
+	struct resource *res;
+	int ret;
+
+	bs->pci = pci;
+
+	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+	if (ret)
+		return ret;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
+	if (!res) {
+		dev_err(dev, "failed to find \"dbi\" region\n");
+		return -EINVAL;
+	}
+
+	pci->dbi_base = devm_pci_remap_cfg_resource(dev, res);
+	if (IS_ERR(pci->dbi_base))
+		return PTR_ERR(pci->dbi_base);
+
+	if (BS1000_ADDR_IN_CHIP(res->start) == BS1000_PCIE0_P0_DBI_BASE ||
+	    BS1000_ADDR_IN_CHIP(res->start) == BS1000_PCIE0_P1_DBI_BASE ||
+	    BS1000_ADDR_IN_CHIP(res->start) == BS1000_PCIE1_P0_DBI_BASE ||
+	    BS1000_ADDR_IN_CHIP(res->start) == BS1000_PCIE1_P1_DBI_BASE ||
+	    BS1000_ADDR_IN_CHIP(res->start) == BS1000_PCIE2_P0_DBI_BASE ||
+	    BS1000_ADDR_IN_CHIP(res->start) == BS1000_PCIE2_P1_DBI_BASE) {
+		bs->cpu_addr_mask = 0x7fffffffff;
+	} else {
+		bs->cpu_addr_mask = 0xffffffffff;
+	}
+
+	bs->apb_base = devm_platform_ioremap_resource_byname(pdev, "apb");
+	if (IS_ERR(bs->apb_base))
+		return PTR_ERR(bs->apb_base);
+
+	if (data->mode == DW_PCIE_EP_TYPE) {
+		res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "addr_space");
+		if (!res)
+			return -EINVAL;
+
+		pci->ep.phys_base = res->start;
+		pci->ep.addr_size = resource_size(res);
+		pci->ep.ops = &bs1000_pcie_ep_ops;
+		pci->dbi_base2 = pci->dbi_base + 0x100000;
+	}
+
+	return 0;
+}
+
+static u64 bs1000_pcie_cpu_addr_fixup(struct dw_pcie *pci, u64 cpu_addr)
+{
+	struct bs1000_pcie *bs = dev_get_drvdata(pci->dev);
+
+	return cpu_addr & bs->cpu_addr_mask;
+}
+
+static int bs1000_pcie_link_up(struct dw_pcie *pci)
+{
+	struct bs1000_pcie *bs = dev_get_drvdata(pci->dev);
+	u32 reg;
+
+	reg = readl(bs->apb_base + BS1000_PCIE_APB_PE_LINK_DBG2);
+	return ((reg & BAIKAL_PCIE_LTSSM_MASK) == BAIKAL_PCIE_LTSSM_STATE_L0) &&
+		(reg & BS1000_PCIE_APB_PE_LINK_DBG2_SMLH_LINK_UP) &&
+		(reg & BS1000_PCIE_APB_PE_LINK_DBG2_RDLH_LINK_UP);
+}
+
+static int bs1000_pcie_start_link(struct dw_pcie *pci)
+{
+	struct bs1000_pcie *bs = dev_get_drvdata(pci->dev);
+	u32 reg;
+
+	reg = readl(bs->apb_base + BS1000_PCIE_APB_PE_GEN_CTRL3);
+	reg |= BS1000_PCIE_APB_PE_GEN_CTRL3_LTSSM_EN;
+	writel(reg, bs->apb_base + BS1000_PCIE_APB_PE_GEN_CTRL3);
+	return 0;
+}
+
+static int bs1000_pcie_host_init(struct dw_pcie_rp *pp)
+{
+	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+	u16 exp_cap_off;
+	u16 ext_cap_err_off;
+	u32 reg;
+
+	/* Enable error reporting */
+	ext_cap_err_off = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_ERR);
+	reg = dw_pcie_readl_dbi(pci, ext_cap_err_off + PCI_ERR_ROOT_COMMAND);
+	reg |= PCI_ERR_ROOT_CMD_COR_EN	    |
+	       PCI_ERR_ROOT_CMD_NONFATAL_EN |
+	       PCI_ERR_ROOT_CMD_FATAL_EN;
+	dw_pcie_writel_dbi(pci, ext_cap_err_off + PCI_ERR_ROOT_COMMAND, reg);
+
+	exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+	reg = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_DEVCTL);
+	reg |= PCI_EXP_DEVCTL_CERE  |
+	       PCI_EXP_DEVCTL_NFERE |
+	       PCI_EXP_DEVCTL_FERE  |
+	       PCI_EXP_DEVCTL_URRE;
+	dw_pcie_writew_dbi(pci, exp_cap_off + PCI_EXP_DEVCTL, reg);
+
+	reg = dw_pcie_readl_dbi(pci, exp_cap_off + PCI_EXP_RTCTL);
+	reg |= PCI_EXP_RTCTL_SECEE  |
+	       PCI_EXP_RTCTL_SENFEE |
+	       PCI_EXP_RTCTL_SEFEE  |
+	       PCI_EXP_RTCTL_PMEIE;
+	dw_pcie_writel_dbi(pci, exp_cap_off + PCI_EXP_RTCTL, reg);
+
+#ifdef CONFIG_HOTPLUG_PCI_PCIE
+	/*
+	 * BE-S1000 does not support PCIe Hot-Plug interrupts
+	 * so enable PCIe Hot-Plug poll mode by default
+	 */
+	pciehp_poll_mode = true;
+#endif
+
+	return 0;
+}
+
+static irqreturn_t bs1000_pcie_intr_irq_handler(int irq, void *arg)
+{
+	struct bs1000_pcie *bs = arg;
+	struct dw_pcie *pci = bs->pci;
+	struct device *dev = pci->dev;
+	u16 exp_cap_off;
+	u16 ext_cap_err_off;
+	u16 dev_sta;
+	u32 cor_err;
+	u32 root_err;
+	u32 uncor_err;
+	u32 apb_pe_err;
+	u32 apb_pe_int;
+
+	apb_pe_err = readl(bs->apb_base + BS1000_PCIE_APB_PE_ERR_STS);
+	apb_pe_int = readl(bs->apb_base + BS1000_PCIE_APB_PE_INT_STS);
+
+	exp_cap_off	= dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+	ext_cap_err_off = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_ERR);
+
+	uncor_err = dw_pcie_readl_dbi(pci,
+				      ext_cap_err_off + PCI_ERR_UNCOR_STATUS);
+	cor_err	  = dw_pcie_readl_dbi(pci,
+				      ext_cap_err_off + PCI_ERR_COR_STATUS);
+	root_err  = dw_pcie_readl_dbi(pci,
+				      ext_cap_err_off + PCI_ERR_ROOT_STATUS);
+	dev_sta	  = dw_pcie_readw_dbi(pci,
+				      exp_cap_off + PCI_EXP_DEVSTA);
+
+	writel(apb_pe_err, bs->apb_base + BS1000_PCIE_APB_PE_ERR_STS);
+	writel(apb_pe_int, bs->apb_base + BS1000_PCIE_APB_PE_INT_STS);
+
+	dw_pcie_writel_dbi(pci,
+			   ext_cap_err_off + PCI_ERR_UNCOR_STATUS, uncor_err);
+	dw_pcie_writel_dbi(pci,
+			   ext_cap_err_off + PCI_ERR_COR_STATUS, cor_err);
+	dw_pcie_writel_dbi(pci,
+			   ext_cap_err_off + PCI_ERR_ROOT_STATUS, root_err);
+	dw_pcie_writew_dbi(pci,
+			   exp_cap_off + PCI_EXP_DEVSTA, dev_sta);
+
+	dev_err(dev,
+		"DevSta:0x%04x RootErr:0x%x UncorErr:0x%x CorErr:0x%x ApbErr:0x%x ApbInt:0x%x\n",
+		dev_sta, root_err, uncor_err, cor_err, apb_pe_err, apb_pe_int);
+
+	return IRQ_HANDLED;
+}
+
+static u64 bs1000_pcie_edma_address(struct device *dev, phys_addr_t cpu_addr)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct bs1000_pcie *bs = platform_get_drvdata(pdev);
+	struct pci_bus *bus = bs->pci->pp.bridge->bus;
+	struct pci_bus_region region;
+	struct resource res = {
+		.flags = IORESOURCE_MEM,
+		.start = cpu_addr,
+		.end = cpu_addr,
+	};
+
+	pcibios_resource_to_bus(bus, &region, &res);
+	return region.start;
+}
+
+static const struct dw_edma_plat_ops bs1000_pcie_edma_ops = {
+	.irq_vector = dw_pcie_edma_irq_vector,
+	.pci_address = bs1000_pcie_edma_address,
+};
+
+static const struct dw_pcie_host_ops bs1000_pcie_host_ops = {
+	.host_init	 = bs1000_pcie_host_init,
+};
+
+static int bs1000_add_pcie_port(struct platform_device *pdev)
+{
+	struct bs1000_pcie *bs = platform_get_drvdata(pdev);
+	struct device *dev = &pdev->dev;
+	struct dw_pcie *pci = bs->pci;
+	struct dw_pcie_rp *pp = &pci->pp;
+	int ret;
+
+	pp->irq = platform_get_irq_byname_optional(pdev, "intr");
+	if (pp->irq < 0) {
+		/* optional */
+		pp->irq = 0;
+	}
+	else {
+		ret = devm_request_irq(dev, pp->irq, bs1000_pcie_intr_irq_handler,
+					       IRQF_SHARED, "bs1000-pcie-intr", bs);
+		if (ret) {
+			dev_err(dev, "failed to request IRQ %d\n", pp->irq);
+			return ret;
+		}
+	}
+
+	pci->edma.ops = &bs1000_pcie_edma_ops;
+
+	pp->num_vectors = MAX_MSI_IRQS;
+	pp->ops = &bs1000_pcie_host_ops;
+	ret = dw_pcie_host_init(pp);
+	if (ret) {
+		dev_err(dev, "failed to initialize host\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
+static void dw_pcie_writel_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg,
+				     u32 val)
+{
+	dw_pcie_write(pci->atu_base +
+		      PCIE_ATU_UNROLL_BASE(PCIE_ATU_REGION_DIR_OB, index) +
+		      reg, 0x4, val);
+}
+
+static void bs1000_pcie_prog_outbound_atu(struct dw_pcie *pci, int index,
+					  int type, u64 cpu_addr, u64 pci_addr,
+					  u32 size, u32 flags)
+{
+	u32 retries, val;
+
+	cpu_addr = bs1000_pcie_cpu_addr_fixup(pci, cpu_addr);
+
+	dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE,
+				 lower_32_bits(cpu_addr));
+	dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE,
+				 upper_32_bits(cpu_addr));
+	dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_LIMIT,
+				 lower_32_bits(cpu_addr + size - 1));
+	dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
+				 lower_32_bits(pci_addr));
+	dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
+				 upper_32_bits(pci_addr));
+	dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, type);
+	dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
+				 PCIE_ATU_ENABLE | flags);
+
+	/*
+	 * Make sure ATU enable takes effect before any subsequent config
+	 * and I/O accesses.
+	 */
+	for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; ++retries) {
+		dw_pcie_read(pci->atu_base +
+			     PCIE_ATU_UNROLL_BASE(index, PCIE_ATU_REGION_DIR_OB) +
+			     PCIE_ATU_UNR_REGION_CTRL2, 0x4, &val);
+		if (val & PCIE_ATU_ENABLE)
+			return;
+
+		mdelay(LINK_WAIT_IATU);
+	}
+	dev_err(pci->dev, "Outbound iATU is not being enabled\n");
+}
+
+static void bs1000_pcie_setup_rc_acpi(struct dw_pcie_rp *pp,
+				      const struct baikal_pcie_acpi_data *mem_data)
+{
+	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+	acpi_status status;
+	u64 lanes;
+	u32 val;
+	int i;
+
+	/*
+	 * Enable DBI read-only registers for writing/updating configuration.
+	 * Write permission gets disabled towards the end of this function.
+	 */
+	dw_pcie_dbi_ro_wr_en(pci);
+
+	val = dw_pcie_readl_dbi(pci, PCIE_PORT_LINK_CONTROL);
+	val &= ~PORT_LINK_FAST_LINK_MODE;
+	val |= PORT_LINK_DLL_LINK_EN;
+	dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val);
+
+	status = acpi_evaluate_integer(to_acpi_device(pci->dev)->handle,
+				       "NUML", NULL, &lanes);
+	if (ACPI_FAILURE(status)) {
+		dev_dbg(pci->dev, "failed to get num-lanes\n");
+	} else {
+		pci->num_lanes = lanes;
+
+		/* Set the number of lanes */
+		val &= ~PORT_LINK_FAST_LINK_MODE;
+		val &= ~PORT_LINK_MODE_MASK;
+		switch (pci->num_lanes) {
+		case 1:
+			val |= PORT_LINK_MODE_1_LANES;
+			break;
+		case 2:
+			val |= PORT_LINK_MODE_2_LANES;
+			break;
+		case 4:
+			val |= PORT_LINK_MODE_4_LANES;
+			break;
+		case 8:
+			val |= PORT_LINK_MODE_8_LANES;
+			break;
+		default:
+			dev_err(pci->dev, "NUML %u: invalid value\n", pci->num_lanes);
+			goto skip_lanes;
+		}
+		dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val);
+
+		/* Set link width speed control register */
+		val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
+		val &= ~PORT_LOGIC_LINK_WIDTH_MASK;
+		switch (pci->num_lanes) {
+		case 1:
+			val |= PORT_LOGIC_LINK_WIDTH_1_LANES;
+			break;
+		case 2:
+			val |= PORT_LOGIC_LINK_WIDTH_2_LANES;
+			break;
+		case 4:
+			val |= PORT_LOGIC_LINK_WIDTH_4_LANES;
+			break;
+		case 8:
+			val |= PORT_LOGIC_LINK_WIDTH_8_LANES;
+			break;
+		}
+		dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
+	}
+
+skip_lanes:
+	/* Setup RC BARs */
+	dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0x00000004);
+	dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_1, 0x00000000);
+
+	/* Setup interrupt pins */
+	val = dw_pcie_readl_dbi(pci, PCI_INTERRUPT_LINE);
+	val &= 0xffff00ff;
+	val |= 0x00000100;
+	dw_pcie_writel_dbi(pci, PCI_INTERRUPT_LINE, val);
+
+	/* Setup bus numbers */
+	val = dw_pcie_readl_dbi(pci, PCI_PRIMARY_BUS);
+	val &= 0xff000000;
+	val |= 0x00ff0100;
+	dw_pcie_writel_dbi(pci, PCI_PRIMARY_BUS, val);
+
+	/* Setup command register */
+	val = dw_pcie_readl_dbi(pci, PCI_COMMAND);
+	val &= 0xffff0000;
+	val |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
+		PCI_COMMAND_MASTER | PCI_COMMAND_SERR;
+	dw_pcie_writel_dbi(pci, PCI_COMMAND, val);
+
+	dw_pcie_cap_set(pci, IATU_UNROLL);
+	pci->num_ob_windows = 4;
+	pci->num_ib_windows = 0;
+
+	for (i = 0; i < pci->num_ob_windows; ++i)
+		dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_OB, i);
+
+	/* Program ATU */
+	bs1000_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_CFG0,
+				      pp->cfg0_base, 0,
+				      SZ_2M,
+				      PCIE_IATU_REGION_CTRL_2_REG_SHIFT_MODE);
+	bs1000_pcie_prog_outbound_atu(pci, 1, PCIE_ATU_TYPE_CFG1,
+				      pp->cfg0_base, 0,
+				      pp->cfg0_size,
+				      PCIE_IATU_REGION_CTRL_2_REG_SHIFT_MODE);
+	bs1000_pcie_prog_outbound_atu(pci, 2, PCIE_ATU_TYPE_MEM,
+				      mem_data->mem_base, mem_data->mem_bus_addr,
+				      mem_data->mem_size, 0);
+	bs1000_pcie_prog_outbound_atu(pci, 3, PCIE_ATU_TYPE_IO,
+				      pp->io_base, pp->io_bus_addr,
+				      pp->io_size, 0);
+
+	dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0);
+
+	/* Set eDMA region */
+	pci->edma.reg_base = pci->atu_base + DEFAULT_DBI_DMA_OFFSET;
+
+	/* Program correct class for RC */
+	dw_pcie_writew_dbi(pci, PCI_CLASS_DEVICE, PCI_CLASS_BRIDGE_PCI);
+
+	val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
+	val |= PORT_LOGIC_SPEED_CHANGE;
+	dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
+
+	dw_pcie_dbi_ro_wr_dis(pci);
+}
+
+static int bs1000_pcie_get_res_acpi(struct acpi_device *adev,
+				    struct acpi_device **res_dev,
+				    struct bs1000_pcie *bs,
+				    struct baikal_pcie_acpi_data *mem_data)
+{
+	struct device *dev = &adev->dev;
+	struct dw_pcie_rp *pp = &bs->pci->pp;
+	struct resource_entry *entry;
+	struct list_head list, *pos;
+	struct fwnode_handle *fwnode;
+	int ret;
+	unsigned long flags = IORESOURCE_MEM;
+
+	fwnode = fwnode_get_named_child_node(&adev->fwnode, "RES0");
+	if (!fwnode) {
+		dev_err(dev, "failed to get RES0 subdevice\n");
+		return -EINVAL;
+	}
+
+	*res_dev = to_acpi_device_node(fwnode);
+	if (!*res_dev) {
+		dev_err(dev, "RES0 is not an acpi device node\n");
+		return -EINVAL;
+	}
+
+	INIT_LIST_HEAD(&list);
+	ret = acpi_dev_get_resources(*res_dev, &list,
+				     acpi_dev_filter_resource_type_cb,
+				     (void *)flags);
+	if (ret < 0) {
+		dev_err(dev, "failed to parse RES0._CRS method, error code %d\n", ret);
+		return ret;
+	}
+
+	if (ret != 4) {
+		dev_err(dev,
+			"invalid number of MEM resources present in RES0._CRS (%i, need 4)\n", ret);
+		return -EINVAL;
+	}
+
+	/* ECAM */
+	pos = list.next;
+	entry = list_entry(pos, struct resource_entry, node);
+	pp->cfg0_size = resource_size(entry->res);
+	pp->cfg0_base = entry->res->start;
+
+	/* DBI */
+	pos = pos->next;
+	entry = list_entry(pos, struct resource_entry, node);
+	if (BS1000_ADDR_IN_CHIP(entry->res->start) == BS1000_PCIE0_P0_DBI_BASE ||
+	    BS1000_ADDR_IN_CHIP(entry->res->start) == BS1000_PCIE0_P1_DBI_BASE ||
+	    BS1000_ADDR_IN_CHIP(entry->res->start) == BS1000_PCIE1_P0_DBI_BASE ||
+	    BS1000_ADDR_IN_CHIP(entry->res->start) == BS1000_PCIE1_P1_DBI_BASE ||
+	    BS1000_ADDR_IN_CHIP(entry->res->start) == BS1000_PCIE2_P0_DBI_BASE ||
+	    BS1000_ADDR_IN_CHIP(entry->res->start) == BS1000_PCIE2_P1_DBI_BASE) {
+		bs->cpu_addr_mask = 0x7fffffffff;
+	} else {
+		bs->cpu_addr_mask = 0xffffffffff;
+	}
+	bs->pci->dbi_base = devm_ioremap_resource(dev, entry->res);
+	if (IS_ERR(bs->pci->dbi_base)) {
+		dev_err(dev, "error with dbi ioremap\n");
+		ret = PTR_ERR(bs->pci->dbi_base);
+		return ret;
+	}
+
+	/* ATU */
+	pos = pos->next;
+	entry = list_entry(pos, struct resource_entry, node);
+	bs->pci->atu_base = devm_ioremap_resource(dev, entry->res);
+	if (IS_ERR(bs->pci->atu_base)) {
+		dev_err(dev, "error with atu ioremap\n");
+		ret = PTR_ERR(bs->pci->atu_base);
+		return ret;
+	}
+	bs->pci->atu_size = resource_size(entry->res);
+
+	/* APB */
+	pos = pos->next;
+	entry = list_entry(pos, struct resource_entry, node);
+	bs->apb_base = devm_ioremap_resource(dev, entry->res);
+	if (IS_ERR(bs->apb_base)) {
+		dev_err(dev, "error with apb ioremap\n");
+		ret = PTR_ERR(bs->apb_base);
+		return ret;
+	}
+
+	acpi_dev_free_resource_list(&list);
+
+	/* Non-prefetchable memory */
+	INIT_LIST_HEAD(&list);
+	flags = IORESOURCE_MEM;
+	ret = acpi_dev_get_resources(adev, &list,
+				     acpi_dev_filter_resource_type_cb,
+				     (void *)flags);
+	if (ret < 0) {
+		dev_err(dev, "failed to parse _CRS method, error code %d\n", ret);
+		return ret;
+	}
+
+	if (ret != 1) {
+		dev_err(dev, "invalid number of MEM resources present in _CRS (%i, need 1)\n", ret);
+		return -EINVAL;
+	}
+
+	pos = list.next;
+	entry = list_entry(pos, struct resource_entry, node);
+	mem_data->mem_base = entry->res->start;
+	mem_data->mem_size = resource_size(entry->res);
+	mem_data->mem_bus_addr = entry->res->start - entry->offset;
+
+	acpi_dev_free_resource_list(&list);
+
+	/* I/O */
+	INIT_LIST_HEAD(&list);
+	flags = IORESOURCE_IO;
+	ret = acpi_dev_get_resources(adev, &list,
+				     acpi_dev_filter_resource_type_cb,
+				     (void *)flags);
+	if (ret < 0) {
+		dev_err(dev, "failed to parse _CRS method, error code %d\n", ret);
+		return ret;
+	}
+
+	if (ret != 1) {
+		dev_err(dev, "invalid number of IO resources present in _CRS (%i, need 1)\n", ret);
+		return -EINVAL;
+	}
+
+	pos = list.next;
+	entry = list_entry(pos, struct resource_entry, node);
+	pp->io_base = entry->res->start;
+	pp->io_size = resource_size(entry->res);
+	pp->io_bus_addr = entry->res->start - entry->offset;
+
+	acpi_dev_free_resource_list(&list);
+	return 0;
+}
+
+static int bs1000_pcie_get_irq_acpi(struct device *dev,
+				    struct acpi_device *res_dev,
+				    struct bs1000_pcie *bs)
+{
+	struct dw_pcie_rp *pp = &bs->pci->pp;
+	struct resource res;
+	int index, ret = 0;
+
+	memset(&res, 0, sizeof(res));
+
+	/* eDMA interrupts */
+	for (index = 0; index < BAIKAL_EDMA_WR_CH + BAIKAL_EDMA_RD_CH; index++) {
+		ret = acpi_irq_get(res_dev->handle, index, &res);
+		if (ret)
+			break;
+		if (res.flags & IORESOURCE_BITS) {
+			struct irq_data *irqd;
+
+			irqd = irq_get_irq_data(res.start);
+			if (!irqd)
+				return -ENXIO;
+
+			irqd_set_trigger_type(irqd, res.flags & IORESOURCE_BITS);
+		}
+		bs->edma_irq[index] = res.start;
+	}
+
+	/* RC interrupts */
+	if (ret == 0)
+		ret = acpi_irq_get(res_dev->handle, index, &res);
+	if (ret) {
+		/* optional */
+		pp->irq = 0;
+		return 0;
+	}
+
+	if (res.flags & IORESOURCE_BITS) {
+		struct irq_data *irqd;
+
+		irqd = irq_get_irq_data(res.start);
+		if (!irqd)
+			return -ENXIO;
+
+		irqd_set_trigger_type(irqd, res.flags & IORESOURCE_BITS);
+	}
+
+	pp->irq = res.start;
+
+	ret = devm_request_irq(dev, pp->irq, bs1000_pcie_intr_irq_handler,
+			       IRQF_SHARED, "bs1000-pcie-intr", bs);
+	if (ret) {
+		dev_err(dev, "failed to request IRQ %d\n", pp->irq);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int bs1000_get_acpi_data(struct device *dev, struct bs1000_pcie *bs,
+				struct baikal_pcie_acpi_data *mem_data)
+{
+	struct acpi_device *adev = to_acpi_device(dev), *res_dev;
+	int ret;
+
+	ret = bs1000_pcie_get_res_acpi(adev, &res_dev, bs, mem_data);
+	if (ret) {
+		dev_err(dev, "failed to get resource info\n");
+		return ret;
+	}
+
+	ret = bs1000_pcie_get_irq_acpi(dev, res_dev, bs);
+	if (ret) {
+		dev_err(dev, "failed to get irq info\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+int bs1000_pcie_acpi_edma_irq_vector(struct device *dev, unsigned int nr)
+{
+	struct bs1000_pcie *bs = dev_get_drvdata(dev);
+
+	if (nr >= BAIKAL_EDMA_WR_CH + BAIKAL_EDMA_RD_CH)
+		return -EINVAL;
+
+	return bs->edma_irq[nr];
+}
+
+static u64 bs1000_pcie_acpi_edma_address(struct device *dev, phys_addr_t cpu_addr)
+{
+	struct bs1000_pcie *bs = dev_get_drvdata(dev);
+	struct pci_bus *bus = bs->pci->pp.bridge->bus;
+	struct pci_bus_region region;
+	struct resource res = {
+		.flags = IORESOURCE_MEM,
+		.start = cpu_addr,
+		.end = cpu_addr,
+	};
+
+	pcibios_resource_to_bus(bus, &region, &res);
+	return region.start;
+}
+
+static const struct dw_edma_plat_ops bs1000_pcie_acpi_edma_ops = {
+	.irq_vector = bs1000_pcie_acpi_edma_irq_vector,
+	.pci_address = bs1000_pcie_acpi_edma_address,
+};
+
+static int bs1000_pcie_init(struct pci_config_window *cfg)
+{
+	struct device *dev = cfg->parent;
+	struct bs1000_pcie *bs;
+	struct dw_pcie *pci;
+	struct dw_pcie_rp *pp;
+	struct baikal_pcie_acpi_data mem_data = {};
+	int ret;
+
+	pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
+	if (!pci)
+		return -ENOMEM;
+
+	pci->dev = dev;
+	pci->ops = &bs1000_pcie_rc_of_data.dw_pcie_ops;
+
+	bs = devm_kzalloc(dev, sizeof(*bs), GFP_KERNEL);
+	if (!bs)
+		return -ENOMEM;
+
+	cfg->priv = bs;
+	bs->pci = pci;
+	dev_set_drvdata(dev, bs);
+
+	ret = bs1000_get_acpi_data(dev, bs, &mem_data);
+	if (ret) {
+		dev_err(dev, "failed to get data from ACPI\n");
+		return ret;
+	}
+
+	pp = &pci->pp;
+	raw_spin_lock_init(&pp->lock);
+	pp->ops = &bs1000_pcie_host_ops;
+	pp->va_cfg0_base = devm_pci_remap_cfgspace(dev, pp->cfg0_base,
+						   pp->cfg0_size);
+	if (!pp->va_cfg0_base) {
+		dev_err(dev, "error with ioremap\n");
+		return -ENOMEM;
+	}
+
+	ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64));
+	if (ret) {
+		dev_err(dev, "failed to enable DMA\n");
+		return ret;
+	}
+
+	ret = bs1000_pcie_host_init(pp);
+	if (ret) {
+		dev_err(dev, "failed to initialize host\n");
+		return ret;
+	}
+
+	dw_pcie_version_detect(pci);
+	bs1000_pcie_setup_rc_acpi(pp, &mem_data);
+
+	/* eDMA */
+	pci->edma.nr_irqs = BAIKAL_EDMA_WR_CH + BAIKAL_EDMA_RD_CH;
+	pci->edma.ops = &bs1000_pcie_acpi_edma_ops;
+	ret = dw_pcie_edma_detect(pci);
+	if (ret) {
+		dev_err(dev, "failed to initialize eDMA\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static void __iomem *bs1000_pcie_map_bus(struct pci_bus *bus,
+					 unsigned int devfn, int where)
+{
+	struct pci_config_window *cfg = bus->sysdata;
+	struct bs1000_pcie *bs = cfg->priv;
+	unsigned int devfn_shift = cfg->ops->bus_shift - 8;
+	unsigned int busn = bus->number;
+	void __iomem *base;
+
+	if (!bs->pci->pp.bridge)
+		bs->pci->pp.bridge = to_pci_host_bridge(bus->bridge);
+
+	if (bus->number != cfg->busr.start && !bs1000_pcie_link_up(bs->pci))
+		return NULL;
+
+	if (bus->number == cfg->busr.start) {
+		/*
+		 * The DW PCIe core doesn't filter out transactions to other
+		 * devices/functions on the root bus num, so we do this here.
+		 */
+		if (PCI_SLOT(devfn) > 0)
+			return NULL;
+		else
+			return bs->pci->dbi_base + where;
+	}
+
+	if (busn < cfg->busr.start || busn > cfg->busr.end)
+		return NULL;
+
+	busn -= cfg->busr.start;
+	base = cfg->win + (busn << cfg->ops->bus_shift);
+	return base + (devfn << devfn_shift) + where;
+}
+
+const struct pci_ecam_ops baikal_s_pcie_ecam_ops = {
+	.bus_shift	= 20,
+	.init		= bs1000_pcie_init,
+	.pci_ops	= {
+		.map_bus	= bs1000_pcie_map_bus,
+		.read		= pci_generic_config_read,
+		.write		= pci_generic_config_write
+	}
+};
+#endif
+
+union baikal_pcie {
+	struct bm1000_pcie	bm1000_pcie;
+	struct bs1000_pcie	bs1000_pcie;
+};
+
+static int baikal_pcie_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct dw_pcie *pci;
+	union baikal_pcie *bp;
+	const struct baikal_pcie_of_data *data;
+	int ret;
+
+	data = of_device_get_match_data(dev);
+	if (!data)
+		return -EINVAL;
+
+	bp = devm_kzalloc(dev, sizeof(*bp), GFP_KERNEL);
+	if (!bp)
+		return -ENOMEM;
+
+	pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
+	if (!pci)
+		return -ENOMEM;
+
+	pci->dev = dev;
+	pci->ops = &data->dw_pcie_ops;
+
+	platform_set_drvdata(pdev, bp);
+	ret = data->get_resources(pdev, pci, data);
+	if (ret)
+		return ret;
+
+	switch (data->mode) {
+	case DW_PCIE_RC_TYPE:
+		ret = data->add_pcie_port(pdev);
+		if (ret)
+			return ret;
+
+		break;
+	case DW_PCIE_EP_TYPE:
+		ret = dw_pcie_ep_init(&pci->ep);
+		if (ret) {
+			dev_err(dev, "failed to initialize endpoint\n");
+			return ret;
+		}
+
+		break;
+	default:
+		dev_err(dev, "INVALID device type %d\n", data->mode);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct baikal_pcie_of_data bm1000_pcie_rc_of_data = {
+	.mode = DW_PCIE_RC_TYPE,
+	.dw_pcie_ops = {
+		.link_up	= bm1000_pcie_link_up,
+		.start_link	= bm1000_pcie_start_link,
+	},
+	.get_resources = bm1000_get_resources,
+	.add_pcie_port = bm1000_add_pcie_port,
+};
+
+static const struct baikal_pcie_of_data bs1000_pcie_rc_of_data = {
+	.mode = DW_PCIE_RC_TYPE,
+	.dw_pcie_ops = {
+		.cpu_addr_fixup	= bs1000_pcie_cpu_addr_fixup,
+		.link_up	= bs1000_pcie_link_up,
+		.start_link	= bs1000_pcie_start_link,
+	},
+	.get_resources = bs1000_get_resources,
+	.add_pcie_port = bs1000_add_pcie_port,
+};
+
+static const struct baikal_pcie_of_data bs1000_pcie_ep_of_data = {
+	.mode = DW_PCIE_EP_TYPE,
+	.dw_pcie_ops = {
+		.cpu_addr_fixup	= bs1000_pcie_cpu_addr_fixup,
+		.link_up	= bs1000_pcie_link_up,
+		.start_link	= bs1000_pcie_start_link,
+	},
+	.get_resources = bs1000_get_resources,
+};
+
+static const struct of_device_id baikal_pcie_of_match[] = {
+	{
+		.compatible = "baikal,bm1000-pcie",
+		.data = &bm1000_pcie_rc_of_data,
+	},
+	{
+		.compatible = "baikal,bs1000-pcie",
+		.data = &bs1000_pcie_rc_of_data,
+	},
+	{
+		.compatible = "baikal,bs1000-pcie-ep",
+		.data = &bs1000_pcie_ep_of_data,
+	},
+	{ },
+};
+
+static struct platform_driver baikal_pcie_driver = {
+	.driver = {
+		.name = "baikal-pcie",
+		.of_match_table = baikal_pcie_of_match,
+		.suppress_bind_attrs = true,
+	},
+	.probe = baikal_pcie_probe,
+};
+builtin_platform_driver(baikal_pcie_driver);
diff --git a/drivers/pci/controller/dwc/pcie-baikal-tune.c b/drivers/pci/controller/dwc/pcie-baikal-tune.c
new file mode 100644
index 0000000000000..c78dbce2fde94
--- /dev/null
+++ b/drivers/pci/controller/dwc/pcie-baikal-tune.c
@@ -0,0 +1,570 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BM1000 PCIe Controller & Phy Gen3 equalization parameters fine tune.
+ *
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/property.h>
+#include <linux/debugfs.h>
+
+#include "pcie-designware.h"
+#include "pcie-baikal.h"
+
+static int gen3_eq_fb_mode = -1;
+module_param(gen3_eq_fb_mode, int, 0444);
+MODULE_PARM_DESC(gen3_eq_fb_mode, "feedback mode (0 - FOM + DIR, 1 - FOM only)");
+static int gen3_eq_psets = -1;
+module_param(gen3_eq_psets, int, 0444);
+MODULE_PARM_DESC(gen3_eq_psets, "initial presets");
+static int phy_rx_agc = -1;
+module_param(phy_rx_agc, int, 0444);
+MODULE_PARM_DESC(phy_rx_agc,
+		 "Phy RX AGC gain ([7:4] - Pre-CTLE gain, [3:0] - Post-CTLE gain)");
+static int phy_rx_ctle = -1;
+module_param(phy_rx_ctle, int, 0444);
+MODULE_PARM_DESC(phy_rx_ctle, "Phy RX CTLE control ([7:4] - zero, [3:0] - pole)");
+static int phy_rx_dfe = -1;
+module_param(phy_rx_dfe, int, 0444);
+MODULE_PARM_DESC(phy_rx_dfe, "Phy RX DFE control (0 - disable, 1 - enable)");
+static int phy_tx_gain = -1;
+module_param(phy_tx_gain, int, 0444);
+MODULE_PARM_DESC(phy_tx_gain, "Phy TX gain value");
+static int phy_tx_turbo = -1;
+module_param(phy_tx_turbo, int, 0444);
+MODULE_PARM_DESC(phy_tx_turbo, "Phy TX turbo mode (0 - disable, 1 - enable)");
+static int phy_rx_ctle_pole = -1;
+module_param(phy_rx_ctle_pole, int, 0444);
+MODULE_PARM_DESC(phy_rx_ctle_pole,
+		 "Phy RX CTLE pole range for VMA adaptation ([7:4] - max, [3:0] - min)");
+static bool debugfs;
+module_param(debugfs, bool, 0444);
+MODULE_PARM_DESC(debugfs, "Phy debugfs monitor enable");
+static bool notune;
+module_param(notune, bool, 0444);
+MODULE_PARM_DESC(notune, "Gen3 equalization fine tune disable");
+
+static void bm1000_pcie_tune_debugfs_populate(struct dw_pcie *pci);
+
+/* Baikal-M PCIe PHY registers access */
+#define BM1000_PCIE_AXI2MGM_LINENUM			0xd04
+#define BM1000_PCIE_AXI2MGM_LINENUM_LANE_SEL_MASK	GENMASK(7, 0)
+#define BM1000_PCIE_AXI2MGM_ADDRCTL			0xd08
+#define BM1000_PCIE_AXI2MGM_ADDRCTL_BUSY		BIT(31)
+#define BM1000_PCIE_AXI2MGM_ADDRCTL_DONE		BIT(30)
+#define BM1000_PCIE_AXI2MGM_ADDRCTL_RW_FLAG		BIT(29)
+#define BM1000_PCIE_AXI2MGM_ADDRCTL_PHY_ADDR_MASK	GENMASK(20, 0)
+#define BM1000_PCIE_AXI2MGM_WRITEDATA			0xd0c
+#define BM1000_PCIE_AXI2MGM_WRITEDATA_DATA_MASK		GENMASK(15, 0)
+#define BM1000_PCIE_AXI2MGM_READDATA			0xd10
+#define BM1000_PCIE_AXI2MGM_READDATA_DATA_MASK		GENMASK(15, 0)
+
+#define BM1000_PCIE_PHY_REG_RETRIES		10
+#define BM1000_PCIE_PHY_REG_RETRY_TIMEOUT	100
+
+static int bm1000_pcie_phy_done(struct dw_pcie *pci)
+{
+	u32 reg;
+	int retries;
+
+	for (retries = 0; retries < BM1000_PCIE_PHY_REG_RETRIES; ++retries) {
+		reg = dw_pcie_readl_dbi(pci, BM1000_PCIE_AXI2MGM_ADDRCTL);
+		if (reg & BM1000_PCIE_AXI2MGM_ADDRCTL_DONE)
+			return 0;
+		udelay(BM1000_PCIE_PHY_REG_RETRY_TIMEOUT);
+	}
+	return -ETIMEDOUT;
+}
+
+static int bm1000_pcie_phy_read(struct dw_pcie *pci, u8 lane,
+				u32 addr, u16 *value)
+{
+	int ret;
+
+	bm1000_pcie_phy_enable(pci);
+	dw_pcie_writel_dbi(pci, BM1000_PCIE_AXI2MGM_LINENUM, lane);
+	dw_pcie_writel_dbi(pci, BM1000_PCIE_AXI2MGM_ADDRCTL,
+			   addr & BM1000_PCIE_AXI2MGM_ADDRCTL_PHY_ADDR_MASK);
+	ret = bm1000_pcie_phy_done(pci);
+	if (ret == 0)
+		*value = dw_pcie_readl_dbi(pci, BM1000_PCIE_AXI2MGM_READDATA) &
+			 BM1000_PCIE_AXI2MGM_READDATA_DATA_MASK;
+	bm1000_pcie_phy_disable(pci);
+	return ret;
+}
+
+static int bm1000_pcie_phy_write(struct dw_pcie *pci, u8 lanes,
+				 u32 addr, u16 value)
+{
+	int ret;
+
+	bm1000_pcie_phy_enable(pci);
+	dw_pcie_writel_dbi(pci, BM1000_PCIE_AXI2MGM_LINENUM, lanes);
+	dw_pcie_writel_dbi(pci, BM1000_PCIE_AXI2MGM_WRITEDATA, value);
+	dw_pcie_writel_dbi(pci, BM1000_PCIE_AXI2MGM_ADDRCTL,
+			   (addr & BM1000_PCIE_AXI2MGM_ADDRCTL_PHY_ADDR_MASK) |
+			   BM1000_PCIE_AXI2MGM_ADDRCTL_RW_FLAG);
+	ret = bm1000_pcie_phy_done(pci);
+	bm1000_pcie_phy_disable(pci);
+	return ret;
+}
+
+/* Baikal-M PCIe RX/TX equalizers fine tune */
+#define BM1000_PCIE_GEN3_EQ_CONTROL			0x8a8
+#define BM1000_PCIE_GEN3_EQ_FOM_INC_INITIAL_EVAL	BIT(24)
+#define BM1000_PCIE_GEN3_EQ_PSET_REQ_VEC_MASK		GENMASK(23, 8)
+#define BM1000_PCIE_GEN3_EQ_FB_MODE_MASK		GENMASK(3, 0)
+
+#define BM1000_PCIE_PHY_RX_CFG_2					0x18002
+#define BM1000_PCIE_PHY_RX_CFG_2_PCS_SDS_RX_AGC_MVAL			GENMASK(9, 0)
+#define BM1000_PCIE_PHY_RX_CFG_5					0x18005
+#define BM1000_PCIE_PHY_RX_CFG_5_RX_AGC_MEN_OVRRD_EN			BIT(4)
+#define BM1000_PCIE_PHY_RX_CFG_5_RX_AGC_MEN_OVRRD_VAL			BIT(3)
+#define BM1000_PCIE_PHY_RX_LOOP_CTRL					0x18009
+#define BM1000_PCIE_PHY_RX_LOOP_CTRL_CFG_RX_LCTRL_LCTRL_MEN		BIT(8)
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL					0x1800b
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_ZERO_MASK		GENMASK(13, 10)
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL_RX_CTLE_POLE_OVRRD_EN		BIT(9)
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL_RX_CTLE_POLE_OVRRD_VAL_MASK	GENMASK(8, 5)
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_MAX_MASK	GENMASK(4, 3)
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_MIN_MASK	GENMASK(2, 1)
+#define BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_STEP		BIT(0)
+#define BM1000_PCIE_PHY_TX_CFG_1					0x18016
+#define BM1000_PCIE_PHY_TX_CFG_1_TX_VBOOST_EN_OVRRD_EN			BIT(11)
+#define BM1000_PCIE_PHY_TX_CFG_1_TX_TURBO_EN_OVRRD_EN			BIT(10)
+#define BM1000_PCIE_PHY_TX_CFG_3					0x18018
+#define BM1000_PCIE_PHY_TX_CFG_3_CFG_TX_VBOOST_EN			BIT(14)
+#define BM1000_PCIE_PHY_TX_CFG_3_PCS_SDS_TX_GAIN_MASK			GENMASK(6, 4)
+#define BM1000_PCIE_PHY_TX_CFG_3_CFG_TX_TURBO_EN			BIT(0)
+#define BM1000_PCIE_PHY_RX_PWR_MON_1					0x1802a
+#define BM1000_PCIE_PHY_RX_PWR_MON_1_RX_PWRSM_LANE_PWR_OFF		BIT(4)
+#define BM1000_PCIE_PHY_TX_PWR_MON_0					0x1802c
+#define BM1000_PCIE_PHY_TX_PWR_MON_0_TX_PWRSM_LANE_PWR_OFF		BIT(15)
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_0					0x18048
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_1					0x18049
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2					0x1804a
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_OVRD_EN			BIT(5)
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C5_MEN_OVRD_VAL		BIT(4)
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C4_MEN_OVRD_VAL		BIT(3)
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C3_MEN_OVRD_VAL		BIT(2)
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C2_MEN_OVRD_VAL		BIT(1)
+#define BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C1_MEN_OVRD_VAL		BIT(0)
+
+void bm1000_pcie_tune(struct dw_pcie *pci)
+{
+	struct device *dev = pci->dev;
+	u16 exp_cap_off;
+	u8 lane = 0, lanes = 0;
+	int override;
+	int i, n_lanes;
+	int ret;
+
+	if (notune)
+		return;
+
+	/* Search for active lanes */
+	exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+	n_lanes = FIELD_GET(PCI_EXP_LNKCAP_MLW,
+			    dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKCAP));
+	for (i = 0; i < n_lanes; i++) {
+		u16 reg;
+		u8 mask = 1 << i;
+
+		ret = bm1000_pcie_phy_read(pci, mask,
+					   BM1000_PCIE_PHY_RX_PWR_MON_1, &reg);
+		if (ret != 0 ||
+		    FIELD_GET(BM1000_PCIE_PHY_RX_PWR_MON_1_RX_PWRSM_LANE_PWR_OFF,
+			      reg) == 1)
+			continue;
+		ret = bm1000_pcie_phy_read(pci, mask,
+					   BM1000_PCIE_PHY_TX_PWR_MON_0, &reg);
+		if (ret != 0 ||
+		    FIELD_GET(BM1000_PCIE_PHY_TX_PWR_MON_0_TX_PWRSM_LANE_PWR_OFF,
+			      reg) == 1)
+			continue;
+		lanes |= mask;
+		if (lane == 0)
+			lane = mask;
+	}
+
+	/* Feedback mode */
+	override = gen3_eq_fb_mode;
+	if (override == -1)
+		device_property_read_u32(dev, "bm1000,gen3-eq-fb-mode", &override);
+	if (override >= 0) {
+		u32 reg;
+
+		dev_dbg(dev, "Gen3 fb_mode = %d\n", override);
+		reg = dw_pcie_readl_dbi(pci, BM1000_PCIE_GEN3_EQ_CONTROL);
+		reg &= ~BM1000_PCIE_GEN3_EQ_FB_MODE_MASK;
+		reg |= FIELD_PREP(BM1000_PCIE_GEN3_EQ_FB_MODE_MASK, override);
+		dw_pcie_writel_dbi(pci, BM1000_PCIE_GEN3_EQ_CONTROL, reg);
+	}
+	/* Initial presets */
+	override = gen3_eq_psets;
+	if (override == -1)
+		device_property_read_u32(dev, "bm1000,gen3-eq-psets", &override);
+	if (override >= 0) {
+		u32 reg;
+
+		reg = dw_pcie_readl_dbi(pci, BM1000_PCIE_GEN3_EQ_CONTROL);
+		dev_dbg(dev, "Gen3 initial presets = 0x%x\n", override);
+		reg &= ~(BM1000_PCIE_GEN3_EQ_PSET_REQ_VEC_MASK |
+			 BM1000_PCIE_GEN3_EQ_FOM_INC_INITIAL_EVAL);
+		reg |= FIELD_PREP(BM1000_PCIE_GEN3_EQ_PSET_REQ_VEC_MASK,
+				  override);
+		dw_pcie_writel_dbi(pci, BM1000_PCIE_GEN3_EQ_CONTROL, reg);
+	}
+	/* Phy RX AGC */
+	override = phy_rx_agc;
+	if (override == -1)
+		device_property_read_u32(dev, "bm1000,phy-rx-agc", &override);
+	if (override >= 0) {
+		u16 reg;
+
+		ret = bm1000_pcie_phy_read(pci, lane, BM1000_PCIE_PHY_RX_CFG_2,
+					   &reg);
+		if (ret == 0) {
+			reg &= ~BM1000_PCIE_PHY_RX_CFG_2_PCS_SDS_RX_AGC_MVAL;
+			reg |= FIELD_PREP(BM1000_PCIE_PHY_RX_CFG_2_PCS_SDS_RX_AGC_MVAL,
+					  override);
+			ret = bm1000_pcie_phy_write(pci, lanes,
+						    BM1000_PCIE_PHY_RX_CFG_2,
+						    reg);
+		}
+		if (ret == 0)
+			ret = bm1000_pcie_phy_read(pci, lane,
+						   BM1000_PCIE_PHY_RX_CFG_5,
+						   &reg);
+		if (ret == 0) {
+			reg |= BM1000_PCIE_PHY_RX_CFG_5_RX_AGC_MEN_OVRRD_EN |
+			       BM1000_PCIE_PHY_RX_CFG_5_RX_AGC_MEN_OVRRD_VAL;
+			ret = bm1000_pcie_phy_write(pci, lanes,
+						    BM1000_PCIE_PHY_RX_CFG_5,
+						    reg);
+		}
+		dev_dbg(dev, "Phy RX AGC = 0x%04x (%d)\n", override, ret);
+	}
+	/* Rhy RX CTLE */
+	override = phy_rx_ctle;
+	if (override == -1)
+		device_property_read_u32(dev, "bm1000,phy-rx-ctle", &override);
+	if (override >= 0) {
+		u16 reg;
+
+		ret = bm1000_pcie_phy_read(pci, lane,
+					   BM1000_PCIE_PHY_RX_CTLE_CTRL, &reg);
+		if (ret == 0) {
+			reg &= ~(BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_ZERO_MASK |
+				 BM1000_PCIE_PHY_RX_CTLE_CTRL_RX_CTLE_POLE_OVRRD_VAL_MASK);
+			reg |= BM1000_PCIE_PHY_RX_CTLE_CTRL_RX_CTLE_POLE_OVRRD_EN |
+			       FIELD_PREP(BM1000_PCIE_PHY_RX_CTLE_CTRL_RX_CTLE_POLE_OVRRD_VAL_MASK,
+					  override & 0xf) |
+			       FIELD_PREP(BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_ZERO_MASK,
+					  override >> 4);
+			ret = bm1000_pcie_phy_write(pci, lanes,
+						    BM1000_PCIE_PHY_RX_CTLE_CTRL,
+						    reg);
+		}
+		if (ret == 0)
+			ret = bm1000_pcie_phy_read(pci, lane,
+						   BM1000_PCIE_PHY_RX_LOOP_CTRL,
+						   &reg);
+		if (ret == 0) {
+			reg |= BM1000_PCIE_PHY_RX_LOOP_CTRL_CFG_RX_LCTRL_LCTRL_MEN;
+			ret = bm1000_pcie_phy_write(pci, lanes,
+						    BM1000_PCIE_PHY_RX_LOOP_CTRL,
+						    reg);
+		}
+		dev_dbg(dev, "Phy RX CTLE = 0x%04x (%d)\n", override, ret);
+	}
+	/* Phy RX DFE */
+	override = phy_rx_dfe;
+	if (override == -1)
+		device_property_read_u32(dev, "bm1000,phy-rx-dfe", &override);
+	if (override == 0) { /* enabled by default - disable only */
+		u16 reg;
+
+		reg = BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_OVRD_EN;
+		reg |= BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C1_MEN_OVRD_VAL |
+		       BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C2_MEN_OVRD_VAL |
+		       BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C3_MEN_OVRD_VAL |
+		       BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C4_MEN_OVRD_VAL |
+		       BM1000_PCIE_PHY_RX_AEQ_VALBBD_2_RX_DFE_C5_MEN_OVRD_VAL;
+		ret = bm1000_pcie_phy_write(pci, lanes,
+					    BM1000_PCIE_PHY_RX_AEQ_VALBBD_2,
+					    reg);
+		if (ret == 0)
+			ret = bm1000_pcie_phy_read(pci, lane,
+						   BM1000_PCIE_PHY_RX_LOOP_CTRL,
+						   &reg);
+		if (ret == 0) {
+			reg |= BM1000_PCIE_PHY_RX_LOOP_CTRL_CFG_RX_LCTRL_LCTRL_MEN;
+			ret = bm1000_pcie_phy_write(pci, lanes,
+						    BM1000_PCIE_PHY_RX_LOOP_CTRL,
+						    reg);
+		}
+		if (ret == 0) {
+			reg = 0;
+			bm1000_pcie_phy_write(pci, lanes,
+					      BM1000_PCIE_PHY_RX_AEQ_VALBBD_0,
+					      reg);
+			bm1000_pcie_phy_write(pci, lanes,
+					      BM1000_PCIE_PHY_RX_AEQ_VALBBD_1,
+					      reg);
+		}
+		dev_dbg(dev, "Phy RX DFE = %d (%d)\n", override, ret);
+	}
+	/* Phy TX gain */
+	override = phy_tx_gain;
+	if (override == -1)
+		device_property_read_u32(dev, "bm1000,phy-tx-gain", &override);
+	if (override >= 0) {
+		u16 reg;
+
+		ret = bm1000_pcie_phy_read(pci, lane, BM1000_PCIE_PHY_TX_CFG_3,
+					   &reg);
+		if (ret == 0) {
+			reg &= ~BM1000_PCIE_PHY_TX_CFG_3_PCS_SDS_TX_GAIN_MASK;
+			reg |= BM1000_PCIE_PHY_TX_CFG_3_CFG_TX_VBOOST_EN;
+			reg |= FIELD_PREP(BM1000_PCIE_PHY_TX_CFG_3_PCS_SDS_TX_GAIN_MASK,
+					  override);
+			ret = bm1000_pcie_phy_write(pci, lanes,
+						    BM1000_PCIE_PHY_TX_CFG_3,
+						    reg);
+		}
+		if (ret == 0)
+			ret = bm1000_pcie_phy_read(pci, lane,
+						   BM1000_PCIE_PHY_TX_CFG_1,
+						   &reg);
+		if (ret == 0) {
+			reg |= BM1000_PCIE_PHY_TX_CFG_1_TX_VBOOST_EN_OVRRD_EN;
+			ret = bm1000_pcie_phy_write(pci, lanes,
+						    BM1000_PCIE_PHY_TX_CFG_1,
+						    reg);
+		}
+		dev_dbg(dev, "Phy TX gain = 0x%x (%d)\n", override, ret);
+	}
+	/* Phy TX turbo */
+	override = phy_tx_turbo;
+	if (override == -1)
+		device_property_read_u32(dev, "bm1000,phy-tx-turbo", &override);
+	if (override >= 0) {
+		u16 reg;
+
+		ret = bm1000_pcie_phy_read(pci, lane, BM1000_PCIE_PHY_TX_CFG_3,
+					   &reg);
+		if (ret == 0) {
+			if (override == 0)
+				reg &= ~BM1000_PCIE_PHY_TX_CFG_3_CFG_TX_TURBO_EN;
+			else
+				reg |= BM1000_PCIE_PHY_TX_CFG_3_CFG_TX_TURBO_EN;
+			ret = bm1000_pcie_phy_write(pci, lanes,
+						    BM1000_PCIE_PHY_TX_CFG_3,
+						    reg);
+		}
+		if (ret == 0)
+			ret = bm1000_pcie_phy_read(pci, lane,
+						   BM1000_PCIE_PHY_TX_CFG_1,
+						   &reg);
+		if (ret == 0) {
+			reg |= BM1000_PCIE_PHY_TX_CFG_1_TX_TURBO_EN_OVRRD_EN;
+			ret = bm1000_pcie_phy_write(pci, lanes,
+						    BM1000_PCIE_PHY_TX_CFG_1,
+						    reg);
+		}
+		dev_dbg(dev, "Phy TX turbo = %d (%d)\n", override, ret);
+	}
+	/* Phy RX CTLE pole range */
+	override = phy_rx_ctle_pole;
+	if (override == -1)
+		device_property_read_u32(dev, "bm1000,phy-rx-ctle-pole", &override);
+	if (override >= 0) {
+		u16 reg;
+		u8 pole_max = (override >> 4) & 0xf, pole_min = override & 0xf;
+
+		ret = bm1000_pcie_phy_read(pci, lane, BM1000_PCIE_PHY_RX_CTLE_CTRL,
+					   &reg);
+		if (ret == 0) {
+			reg &= ~(BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_MAX_MASK |
+				 BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_MIN_MASK);
+			reg |= FIELD_PREP(BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_MAX_MASK,
+					  pole_max);
+			reg |= FIELD_PREP(BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_MIN_MASK,
+					  pole_min);
+			if (pole_max == pole_min)
+				reg &= ~BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_STEP;
+			else
+				reg |= BM1000_PCIE_PHY_RX_CTLE_CTRL_PCS_SDS_RX_CTLE_POLE_STEP;
+			ret = bm1000_pcie_phy_write(pci, lanes, BM1000_PCIE_PHY_RX_CTLE_CTRL,
+						    reg);
+		}
+		dev_dbg(dev, "Phy RX CTLE pole = 0x%04x (%d)\n", override, ret);
+	}
+
+	/* debugfs populate */
+	if (debugfs)
+		bm1000_pcie_tune_debugfs_populate(pci);
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+#define BM1000_PCIE_PHY_SDS_PIN_MON_1				0x18027
+#define BM1000_PCIE_PHY_SDS_PIN_MON_1_PCS_SDS_TX_SWING		GENMASK(5, 1)
+#define BM1000_PCIE_PHY_SDS_PIN_MON_2				0x18028
+#define BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_VBOOST_EN	BIT(10)
+#define BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_TURBO_EN	BIT(9)
+#define BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_POST_CURSOR	GENMASK(8, 4)
+#define BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_PRE_CURSOR	GENMASK(3, 0)
+#define BM1000_PCIE_PHY_RX_PWR_MON_0				0x18029
+#define BM1000_PCIE_PHY_RX_PWR_MON_0_RX_PWRSM_AGC_EN		BIT(10)
+#define BM1000_PCIE_PHY_RX_PWR_MON_0_RX_PWRSM_DFE_EN		BIT(9)
+#define BM1000_PCIE_PHY_RX_PWR_MON_0_RX_PWRSM_CDR_EN		BIT(8)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_0				0x18050
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_0_DFE_TAP5			GENMASK(9, 5)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_0_DFE_TAP4			GENMASK(4, 0)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_1				0x18051
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_1_DFE_TAP3			GENMASK(14, 10)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_1_DFE_TAP2			GENMASK(9, 5)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_1_DFE_TAP1			GENMASK(4, 0)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_2				0x18052
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_2_PRE_CTLE_GAIN		GENMASK(7, 4)
+#define BM1000_PCIE_PHY_RX_AEQ_OUT_2_POST_CTLE_GAIN		GENMASK(3, 0)
+#define BM1000_PCIE_PHY_RX_VMA_STATUS_0				0x18057
+#define BM1000_PCIE_PHY_RX_VMA_STATUS_0_CTLE_PEAK		GENMASK(5, 2)
+#define BM1000_PCIE_PHY_RX_VMA_STATUS_0_CTLE_POLE		GENMASK(1, 0)
+
+static void print_for_all_lanes(struct dw_pcie *pci, struct seq_file *s,
+				u8 n_lanes, u32 addr, u16 mask)
+{
+	int i;
+
+	for (i = 0; i < n_lanes; i++) {
+		u16 reg;
+		u8 lane = 1 << i;
+
+		if (bm1000_pcie_phy_read(pci, lane, addr, &reg) == 0)
+			seq_put_hex_ll(s, " ", (reg & mask) >> __bf_shf(mask), 0);
+		else
+			seq_puts(s, " ?");
+	}
+}
+
+static int bm1000_pcie_dbgfs_phy_mon_show(struct seq_file *s, void *data)
+{
+	struct dw_pcie *pci = s->private;
+	u8 n_lanes;
+	u16 exp_cap_off;
+
+	exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+	n_lanes = FIELD_GET(PCI_EXP_LNKCAP_MLW,
+			    dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_LNKCAP));
+
+	seq_puts(s, "sds_pin_mon:\n");
+	seq_puts(s, " tx_swing:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_SDS_PIN_MON_1,
+			    BM1000_PCIE_PHY_SDS_PIN_MON_1_PCS_SDS_TX_SWING);
+	seq_puts(s, "\n");
+	seq_puts(s, " pre_cursor:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_SDS_PIN_MON_2,
+			    BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_PRE_CURSOR);
+	seq_puts(s, "\n");
+	seq_puts(s, " post_cursor:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_SDS_PIN_MON_2,
+			    BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_POST_CURSOR);
+	seq_puts(s, "\n");
+	seq_puts(s, " vboost_en:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_SDS_PIN_MON_2,
+			    BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_VBOOST_EN);
+	seq_puts(s, "\n");
+	seq_puts(s, " turbo_en:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_SDS_PIN_MON_2,
+			    BM1000_PCIE_PHY_SDS_PIN_MON_2_PCS_SDS_TX_TURBO_EN);
+	seq_puts(s, "\n");
+	seq_puts(s, "rx_vma_status:\n");
+	seq_puts(s, " ctle_peak:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_VMA_STATUS_0,
+			    BM1000_PCIE_PHY_RX_VMA_STATUS_0_CTLE_PEAK);
+	seq_puts(s, "\n");
+	seq_puts(s, " ctle_pole:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_VMA_STATUS_0,
+			    BM1000_PCIE_PHY_RX_VMA_STATUS_0_CTLE_POLE);
+	seq_puts(s, "\n");
+	seq_puts(s, "rx_aeq_out:\n");
+	seq_puts(s, " pre_ctle_gain:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_2,
+			    BM1000_PCIE_PHY_RX_AEQ_OUT_2_PRE_CTLE_GAIN);
+	seq_puts(s, "\n");
+	seq_puts(s, " post_ctle_gain:");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_2,
+			    BM1000_PCIE_PHY_RX_AEQ_OUT_2_POST_CTLE_GAIN);
+	seq_puts(s, "\n");
+	seq_puts(s, " dfe_tap1:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_1,
+			    BM1000_PCIE_PHY_RX_AEQ_OUT_1_DFE_TAP1);
+	seq_puts(s, "\n");
+	seq_puts(s, " dfe_tap2:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_1,
+			    BM1000_PCIE_PHY_RX_AEQ_OUT_1_DFE_TAP2);
+	seq_puts(s, "\n");
+	seq_puts(s, " dfe_tap3:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_1,
+			    BM1000_PCIE_PHY_RX_AEQ_OUT_1_DFE_TAP3);
+	seq_puts(s, "\n");
+	seq_puts(s, " dfe_tap4:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_0,
+			    BM1000_PCIE_PHY_RX_AEQ_OUT_0_DFE_TAP4);
+	seq_puts(s, "\n");
+	seq_puts(s, " dfe_tap5:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_AEQ_OUT_0,
+			    BM1000_PCIE_PHY_RX_AEQ_OUT_0_DFE_TAP5);
+	seq_puts(s, "\n");
+	seq_puts(s, "pwr_mon:\n");
+	seq_puts(s, " tx_pwr_off:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_TX_PWR_MON_0,
+			    BM1000_PCIE_PHY_TX_PWR_MON_0_TX_PWRSM_LANE_PWR_OFF);
+	seq_puts(s, "\n");
+	seq_puts(s, " rx_pwr_off:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_PWR_MON_1,
+			    BM1000_PCIE_PHY_RX_PWR_MON_1_RX_PWRSM_LANE_PWR_OFF);
+	seq_puts(s, "\n");
+	seq_puts(s, " agc_en:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_PWR_MON_0,
+			    BM1000_PCIE_PHY_RX_PWR_MON_0_RX_PWRSM_AGC_EN);
+	seq_puts(s, "\n");
+	seq_puts(s, " dfe_en:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_PWR_MON_0,
+			    BM1000_PCIE_PHY_RX_PWR_MON_0_RX_PWRSM_DFE_EN);
+	seq_puts(s, "\n");
+	seq_puts(s, " cdr_en:\t");
+	print_for_all_lanes(pci, s, n_lanes, BM1000_PCIE_PHY_RX_PWR_MON_0,
+			    BM1000_PCIE_PHY_RX_PWR_MON_0_RX_PWRSM_CDR_EN);
+	seq_puts(s, "\n");
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(bm1000_pcie_dbgfs_phy_mon);
+
+static void bm1000_pcie_tune_debugfs_populate(struct dw_pcie *pci)
+{
+	struct dentry *root_dir;
+
+	root_dir = debugfs_create_dir(dev_name(pci->dev), NULL);
+	if (!root_dir) {
+		dev_warn(pci->dev, "%s: failed to create debugfs dir\n",
+			 __func__);
+		return;
+	}
+	if (!debugfs_create_file("phy_mon", 0444, root_dir, pci,
+				 &bm1000_pcie_dbgfs_phy_mon_fops))
+		dev_warn(pci->dev, "%s: failed to create phy_mon debugfs file\n",
+			 __func__);
+}
+#else
+static void bm1000_pcie_tune_debugfs_populate(struct dw_pcie *pci)
+{
+}
+#endif /* CONFIG_DEBUG_FS */
diff --git a/drivers/pci/controller/dwc/pcie-baikal.h b/drivers/pci/controller/dwc/pcie-baikal.h
new file mode 100644
index 0000000000000..8a3a0576a26ff
--- /dev/null
+++ b/drivers/pci/controller/dwc/pcie-baikal.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PCIe controller driver for Baikal Electronics SoCs
+ *
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ */
+
+#ifndef _PCIE_BAIKAL_H
+#define _PCIE_BAIKAL_H
+
+void bm1000_pcie_phy_enable(struct dw_pcie *pci);
+void bm1000_pcie_phy_disable(struct dw_pcie *pci);
+
+void bm1000_pcie_tune(struct dw_pcie *pci);
+
+#endif /* _PCIE_BAIKAL_H */
diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
index 2b60d20dfdf59..fca9cc3da8483 100644
--- a/drivers/pci/controller/dwc/pcie-designware.c
+++ b/drivers/pci/controller/dwc/pcie-designware.c
@@ -857,7 +857,7 @@ static u32 dw_pcie_readl_dma(struct dw_pcie *pci, u32 reg)
 	return val;
 }
 
-static int dw_pcie_edma_irq_vector(struct device *dev, unsigned int nr)
+int dw_pcie_edma_irq_vector(struct device *dev, unsigned int nr)
 {
 	struct platform_device *pdev = to_platform_device(dev);
 	char name[6];
@@ -874,6 +874,7 @@ static int dw_pcie_edma_irq_vector(struct device *dev, unsigned int nr)
 
 	return platform_get_irq_byname_optional(pdev, name);
 }
+EXPORT_SYMBOL_GPL(dw_pcie_edma_irq_vector);
 
 static struct dw_edma_plat_ops dw_pcie_edma_ops = {
 	.irq_vector = dw_pcie_edma_irq_vector,
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index ef0b2efa9f93e..8957670f77576 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -442,6 +442,7 @@ void dw_pcie_setup(struct dw_pcie *pci);
 void dw_pcie_iatu_detect(struct dw_pcie *pci);
 int dw_pcie_edma_detect(struct dw_pcie *pci);
 void dw_pcie_edma_remove(struct dw_pcie *pci);
+int dw_pcie_edma_irq_vector(struct device *dev, unsigned int nr);
 
 int dw_pcie_suspend_noirq(struct dw_pcie *pci);
 int dw_pcie_resume_noirq(struct dw_pcie *pci);
diff --git a/include/linux/pci-ecam.h b/include/linux/pci-ecam.h
index 6b1301e2498e9..d2126b26e878a 100644
--- a/include/linux/pci-ecam.h
+++ b/include/linux/pci-ecam.h
@@ -88,6 +88,8 @@ extern const struct pci_ecam_ops xgene_v2_pcie_ecam_ops; /* APM X-Gene PCIe v2.x
 extern const struct pci_ecam_ops al_pcie_ops;	/* Amazon Annapurna Labs PCIe */
 extern const struct pci_ecam_ops tegra194_pcie_ops; /* Tegra194 PCIe */
 extern const struct pci_ecam_ops loongson_pci_ecam_ops; /* Loongson PCIe */
+extern const struct pci_ecam_ops baikal_m_pcie_ecam_ops; /* Baikal-M Synopsys DesignWare PCIe */
+extern const struct pci_ecam_ops baikal_s_pcie_ecam_ops; /* Baikal-S Synopsys DesignWare PCIe */
 #endif
 
 #if IS_ENABLED(CONFIG_PCI_HOST_COMMON)
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 05/39] AHCI SATA: Add support for Baikal BE-M1000
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (3 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 04/39] PCI: Add support for PCIe controller for Baikal BE-M1000 Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 06/39] UART: Add support for UART " Daniil Gnusarev
                   ` (34 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

Add compatible string for AHCI SATA Baikal BE-M1000

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Co-developed-by: Baikal Electronics <info@baikalelectronics.ru>
---
 drivers/ata/ahci_dwc.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/ata/ahci_dwc.c b/drivers/ata/ahci_dwc.c
index ed263de3fd706..37cb631a08518 100644
--- a/drivers/ata/ahci_dwc.c
+++ b/drivers/ata/ahci_dwc.c
@@ -468,10 +468,15 @@ static struct ahci_dwc_plat_data ahci_bt1_plat = {
 	.init = ahci_bt1_init,
 };
 
+static struct ahci_dwc_plat_data ahci_bm1000_plat = {
+	.pflags = AHCI_PLATFORM_GET_RESETS | AHCI_PLATFORM_RST_TRIGGER,
+};
+
 static const struct of_device_id ahci_dwc_of_match[] = {
 	{ .compatible = "snps,dwc-ahci", &ahci_dwc_plat },
 	{ .compatible = "snps,spear-ahci", &ahci_dwc_plat },
 	{ .compatible = "baikal,bt1-ahci", &ahci_bt1_plat },
+	{ .compatible = "baikal,bm1000-ahci", &ahci_bm1000_plat },
 	{},
 };
 MODULE_DEVICE_TABLE(of, ahci_dwc_of_match);
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 06/39] UART: Add support for UART Baikal BE-M1000
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (4 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 05/39] AHCI SATA: Add support " Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 07/39] cpufreq-dt: don't load on Baikal-M SoC Daniil Gnusarev
                   ` (33 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

Add compatible string for DW8250 serial port driver of Baikal BE-M1000

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Co-developed-by: Baikal Electronics <info@baikalelectronics.ru>
---
 drivers/tty/serial/8250/8250_dw.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 8aed33be2ebf4..85b6294c87ba5 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -790,8 +790,14 @@ static const struct dw8250_platform_data dw8250_starfive_jh7100_data = {
 	.quirks = DW_UART_QUIRK_SKIP_SET_RATE,
 };
 
+static const struct dw8250_platform_data dw8250_baikal_bm1000_data = {
+	.usr_reg = DW_UART_USR,
+	.quirks = DW_UART_QUIRK_SKIP_SET_RATE,
+};
+
 static const struct of_device_id dw8250_of_match[] = {
 	{ .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb },
+	{ .compatible = "baikal,bm1000-uart", .data = &dw8250_baikal_bm1000_data },
 	{ .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data },
 	{ .compatible = "marvell,armada-38x-uart", .data = &dw8250_armada_38x_data },
 	{ .compatible = "renesas,rzn1-uart", .data = &dw8250_renesas_rzn1_data },
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 07/39] cpufreq-dt: don't load on Baikal-M SoC
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (5 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 06/39] UART: Add support for UART " Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 08/39] Sound: add support for Baikal BE-M1000 I2S Daniil Gnusarev
                   ` (32 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

Otherwise the system freezes in few minutes after the boot.
Proper cpufreq driver for Baikal-M will be implemented later on.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 drivers/cpufreq/cpufreq-dt-platdev.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c
index fb2875ce1fdd5..5635d25e1850e 100644
--- a/drivers/cpufreq/cpufreq-dt-platdev.c
+++ b/drivers/cpufreq/cpufreq-dt-platdev.c
@@ -109,6 +109,9 @@ static const struct of_device_id blocklist[] __initconst = {
 
 	{ .compatible = "arm,vexpress", },
 
+	{ .compatible = "baikal,baikal-m", },
+	{ .compatible = "baikal,bm1000", },
+
 	{ .compatible = "calxeda,highbank", },
 	{ .compatible = "calxeda,ecx-2000", },
 
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 08/39] Sound: add support for Baikal BE-M1000 I2S
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (6 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 07/39] cpufreq-dt: don't load on Baikal-M SoC Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 09/39] sound: baikal-i2s: paper over RX overrun warnings on Baikal-M Daniil Gnusarev
                   ` (31 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

Add driver for Baikal BE-M1000 ALSA SoC Synopsys I2S Audio Layer

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Co-developed-by: Baikal Electronics <info@baikalelectronics.ru>
---
 sound/soc/Kconfig                 |   1 +
 sound/soc/Makefile                |   1 +
 sound/soc/baikal/Kconfig          |  21 +
 sound/soc/baikal/Makefile         |   6 +
 sound/soc/baikal/baikal-i2s.c     | 804 ++++++++++++++++++++++++++++++
 sound/soc/baikal/baikal-pio-pcm.c | 264 ++++++++++
 sound/soc/baikal/local.h          | 137 +++++
 7 files changed, 1234 insertions(+)
 create mode 100644 sound/soc/baikal/Kconfig
 create mode 100644 sound/soc/baikal/Makefile
 create mode 100644 sound/soc/baikal/baikal-i2s.c
 create mode 100644 sound/soc/baikal/baikal-pio-pcm.c
 create mode 100644 sound/soc/baikal/local.h

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 439fa631c342a..ecf145c014eb6 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -82,6 +82,7 @@ source "sound/soc/amd/Kconfig"
 source "sound/soc/apple/Kconfig"
 source "sound/soc/atmel/Kconfig"
 source "sound/soc/au1x/Kconfig"
+source "sound/soc/baikal/Kconfig"
 source "sound/soc/bcm/Kconfig"
 source "sound/soc/cirrus/Kconfig"
 source "sound/soc/dwc/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 8376fdb217ed1..5a0056b69b7e0 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_SND_SOC)	+= adi/
 obj-$(CONFIG_SND_SOC)	+= amd/
 obj-$(CONFIG_SND_SOC)	+= atmel/
 obj-$(CONFIG_SND_SOC)	+= au1x/
+obj-$(CONFIG_SND_SOC)	+= baikal/
 obj-$(CONFIG_SND_SOC)	+= bcm/
 obj-$(CONFIG_SND_SOC)	+= cirrus/
 obj-$(CONFIG_SND_SOC)	+= dwc/
diff --git a/sound/soc/baikal/Kconfig b/sound/soc/baikal/Kconfig
new file mode 100644
index 0000000000000..550e49a49fadd
--- /dev/null
+++ b/sound/soc/baikal/Kconfig
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SND_BAIKAL_I2S
+	tristate "Baikal SoC I2S Device Driver"
+	depends on HAVE_CLK
+	select SND_SOC_GENERIC_DMAENGINE_PCM
+	select SND_SIMPLE_CARD_UTILS if ACPI
+	help
+	 Say Y or M if you want to add support for I2S driver for
+	 Baikal SoC I2S device. The device supports up to
+	 a maximum of 8 channels each for play and record.
+
+config SND_BAIKAL_PIO_PCM
+	bool "PCM PIO extension for Baikal SoC I2S driver"
+	depends on SND_BAIKAL_I2S
+	help
+	 Say Y or N if you want to add a custom ALSA extension that registers
+	 a PCM and uses PIO to transfer data.
+
+	 This functionality is specially suited for I2S devices that don't have
+	 DMA support.
+
diff --git a/sound/soc/baikal/Makefile b/sound/soc/baikal/Makefile
new file mode 100644
index 0000000000000..6bfe128e9266d
--- /dev/null
+++ b/sound/soc/baikal/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SND_BAIKAL_I2S) += baikal_i2s.o
+
+baikal_i2s-y := baikal-i2s.o
+baikal_i2s-$(CONFIG_SND_BAIKAL_PIO_PCM) += baikal-pio-pcm.o
+
diff --git a/sound/soc/baikal/baikal-i2s.c b/sound/soc/baikal/baikal-i2s.c
new file mode 100644
index 0000000000000..91d8fe742f673
--- /dev/null
+++ b/sound/soc/baikal/baikal-i2s.c
@@ -0,0 +1,804 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ALSA SoC Synopsys I2S Audio Layer
+ *
+ * Based on sound/soc/dwc/designware_i2s.c
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <sound/designware_i2s.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+#include "local.h"
+
+static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val)
+{
+	writel(val, io_base + reg);
+}
+
+static inline u32 i2s_read_reg(void __iomem *io_base, int reg)
+{
+	return readl(io_base + reg);
+}
+
+static inline void i2s_disable_channels(struct dw_i2s_dev *dev, u32 stream)
+{
+	u32 i = 0;
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		for (i = 0; i < 4; i++)
+			i2s_write_reg(dev->i2s_base, TER(i), 0);
+	} else {
+		for (i = 0; i < 4; i++)
+			i2s_write_reg(dev->i2s_base, RER(i), 0);
+	}
+}
+
+static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream)
+{
+	u32 i = 0;
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		for (i = 0; i < 4; i++)
+			i2s_read_reg(dev->i2s_base, TOR(i));
+	} else {
+		for (i = 0; i < 4; i++)
+			i2s_read_reg(dev->i2s_base, ROR(i));
+	}
+}
+
+static inline void i2s_disable_irqs(struct dw_i2s_dev *dev, u32 stream,
+				    int chan_nr)
+{
+	u32 i, irq;
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		for (i = 0; i < (chan_nr / 2); i++) {
+			irq = i2s_read_reg(dev->i2s_base, IMR(i));
+			i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30);
+		}
+	} else {
+		for (i = 0; i < (chan_nr / 2); i++) {
+			irq = i2s_read_reg(dev->i2s_base, IMR(i));
+			i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03);
+		}
+	}
+}
+
+static inline void i2s_enable_irqs(struct dw_i2s_dev *dev, u32 stream,
+				   int chan_nr)
+{
+	u32 i, irq;
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		for (i = 0; i < (chan_nr / 2); i++) {
+			irq = i2s_read_reg(dev->i2s_base, IMR(i));
+			i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30);
+		}
+	} else {
+		for (i = 0; i < (chan_nr / 2); i++) {
+			irq = i2s_read_reg(dev->i2s_base, IMR(i));
+			i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03);
+		}
+	}
+}
+
+static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
+{
+	struct dw_i2s_dev *dev = dev_id;
+	bool irq_valid = false;
+	u32 isr[4];
+	int i;
+
+	for (i = 0; i < 4; i++)
+		isr[i] = i2s_read_reg(dev->i2s_base, ISR(i));
+
+	i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK);
+	i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE);
+
+	for (i = 0; i < 4; i++) {
+		/*
+		 * Check if TX fifo is empty. If empty fill FIFO with samples
+		 * NOTE: Only two channels supported
+		 */
+		if ((isr[i] & ISR_TXFE) && (i == 0) && dev->use_pio) {
+			dw_pcm_push_tx(dev);
+			irq_valid = true;
+		}
+
+		/*
+		 * Data available. Retrieve samples from FIFO
+		 * NOTE: Only two channels supported
+		 */
+		if ((isr[i] & ISR_RXDA) && (i == 0) && dev->use_pio) {
+			dw_pcm_pop_rx(dev);
+			irq_valid = true;
+		}
+
+		/* Error Handling: TX */
+		if (isr[i] & ISR_TXFO) {
+			dev_err(dev->dev, "TX overrun (ch_id=%d)\n", i);
+			irq_valid = true;
+		}
+
+		/* Error Handling: RX */
+		if (isr[i] & ISR_RXFO) {
+			dev_err(dev->dev, "RX overrun (ch_id=%d)\n", i);
+			irq_valid = true;
+		}
+	}
+
+	if (irq_valid)
+		return IRQ_HANDLED;
+	else
+		return IRQ_NONE;
+}
+
+static void i2s_start(struct dw_i2s_dev *dev,
+		      struct snd_pcm_substream *substream)
+{
+	struct i2s_clk_config_data *config = &dev->config;
+
+	i2s_write_reg(dev->i2s_base, IER, 1);
+	i2s_enable_irqs(dev, substream->stream, config->chan_nr);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		i2s_write_reg(dev->i2s_base, ITER, 1);
+	else
+		i2s_write_reg(dev->i2s_base, IRER, 1);
+
+	i2s_write_reg(dev->i2s_base, CER, 1);
+}
+
+static void i2s_stop(struct dw_i2s_dev *dev,
+		struct snd_pcm_substream *substream)
+{
+
+	i2s_clear_irqs(dev, substream->stream);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		i2s_write_reg(dev->i2s_base, ITER, 0);
+	else
+		i2s_write_reg(dev->i2s_base, IRER, 0);
+
+	i2s_disable_irqs(dev, substream->stream, 8);
+
+	if (!dev->active) {
+		i2s_write_reg(dev->i2s_base, CER, 0);
+		i2s_write_reg(dev->i2s_base, IER, 0);
+	}
+}
+
+static int dw_i2s_startup(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *cpu_dai)
+{
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+	union dw_i2s_snd_dma_data *dma_data = NULL;
+
+	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+
+	if (!(dev->capability & DWC_I2S_RECORD) &&
+			(substream->stream == SNDRV_PCM_STREAM_CAPTURE))
+		return -EINVAL;
+
+	if (!(dev->capability & DWC_I2S_PLAY) &&
+			(substream->stream == SNDRV_PCM_STREAM_PLAYBACK))
+		return -EINVAL;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = &dev->play_dma_data;
+	else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		dma_data = &dev->capture_dma_data;
+
+	snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data);
+	snd_soc_dai_set_sysclk(codec_dai, 0, dev->sysclk, SND_SOC_CLOCK_IN);
+
+	return 0;
+}
+
+static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
+{
+	u32 ch_reg;
+	struct i2s_clk_config_data *config = &dev->config;
+
+	i2s_disable_channels(dev, stream);
+
+	for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) {
+		if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			i2s_write_reg(dev->i2s_base, TCR(ch_reg),
+				      dev->xfer_resolution);
+			i2s_write_reg(dev->i2s_base, TFCR(ch_reg),
+				      dev->fifo_th - 1);
+			i2s_write_reg(dev->i2s_base, TER(ch_reg), 1);
+		} else {
+			i2s_write_reg(dev->i2s_base, RCR(ch_reg),
+				      dev->xfer_resolution);
+			i2s_write_reg(dev->i2s_base, RFCR(ch_reg),
+				      dev->fifo_th - 1);
+			i2s_write_reg(dev->i2s_base, RER(ch_reg), 1);
+		}
+
+	}
+}
+
+static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+	struct i2s_clk_config_data *config = &dev->config;
+
+	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+
+	int ret;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		config->data_width = 16;
+		dev->ccr = 0x00;
+		dev->xfer_resolution = 0x02;
+		break;
+
+	case SNDRV_PCM_FORMAT_S24_LE:
+		config->data_width = 24;
+		dev->ccr = 0x08;
+		dev->xfer_resolution = 0x04;
+		break;
+
+	case SNDRV_PCM_FORMAT_S32_LE:
+		config->data_width = 32;
+		dev->ccr = 0x10;
+		dev->xfer_resolution = 0x05;
+		break;
+
+	default:
+		dev_err(dev->dev, "designware-i2s: unsupported PCM fmt");
+		return -EINVAL;
+	}
+
+	config->chan_nr = params_channels(params);
+
+	switch (config->chan_nr) {
+	case EIGHT_CHANNEL_SUPPORT:
+	case SIX_CHANNEL_SUPPORT:
+	case FOUR_CHANNEL_SUPPORT:
+	case TWO_CHANNEL_SUPPORT:
+		break;
+	default:
+		dev_err(dev->dev, "channel not supported\n");
+		return -EINVAL;
+	}
+
+	dw_i2s_config(dev, substream->stream);
+
+	i2s_write_reg(dev->i2s_base, CCR, dev->ccr);
+
+	config->sample_rate = params_rate(params);
+
+	if (dev->capability & DW_I2S_MASTER) {
+		if (dev->i2s_clk_cfg) {
+			ret = dev->i2s_clk_cfg(config);
+			if (ret < 0) {
+				dev_err(dev->dev, "runtime audio clk config fail\n");
+				return ret;
+			}
+		} else {
+			u32 bitclk = config->sample_rate *
+					config->data_width * 2;
+
+			ret = clk_set_rate(dev->clk, bitclk);
+			if (ret) {
+				dev_err(dev->dev, "Can't set I2S clock rate: %d\n",
+					ret);
+				return ret;
+			}
+		}
+	}
+
+	snd_soc_dai_set_pll(codec_dai, 0, 0, dev->sysclk, params_rate(params) * 256);
+
+	return 0;
+}
+
+static void dw_i2s_shutdown(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	snd_soc_dai_set_dma_data(dai, substream, NULL);
+}
+
+static int dw_i2s_prepare(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		i2s_write_reg(dev->i2s_base, TXFFR, 1);
+	else
+		i2s_write_reg(dev->i2s_base, RXFFR, 1);
+
+	return 0;
+}
+
+static int dw_i2s_trigger(struct snd_pcm_substream *substream,
+		int cmd, struct snd_soc_dai *dai)
+{
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		dev->active++;
+		i2s_start(dev, substream);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		dev->active--;
+		i2s_stop(dev, substream);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static int dw_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+	int ret = 0;
+
+	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+	case SND_SOC_DAIFMT_BC_FC:
+		if (dev->capability & DW_I2S_SLAVE)
+			ret = 0;
+		else
+			ret = -EINVAL;
+		break;
+	case SND_SOC_DAIFMT_BP_FP:
+		if (dev->capability & DW_I2S_MASTER)
+			ret = 0;
+		else
+			ret = -EINVAL;
+		break;
+	case SND_SOC_DAIFMT_BC_FP:
+	case SND_SOC_DAIFMT_BP_FC:
+		ret = -EINVAL;
+		break;
+	default:
+		dev_dbg(dev->dev, "dwc : Invalid clock provider format\n");
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static const struct snd_soc_dai_ops dw_i2s_dai_ops = {
+	.startup	= dw_i2s_startup,
+	.shutdown	= dw_i2s_shutdown,
+	.hw_params	= dw_i2s_hw_params,
+	.prepare	= dw_i2s_prepare,
+	.trigger	= dw_i2s_trigger,
+	.set_fmt	= dw_i2s_set_fmt,
+};
+
+#ifdef CONFIG_PM
+static int dw_i2s_runtime_suspend(struct device *dev)
+{
+	struct dw_i2s_dev *dw_dev = dev_get_drvdata(dev);
+
+	if (dw_dev->capability & DW_I2S_MASTER)
+		clk_disable(dw_dev->clk);
+	return 0;
+}
+
+static int dw_i2s_runtime_resume(struct device *dev)
+{
+	struct dw_i2s_dev *dw_dev = dev_get_drvdata(dev);
+	int ret;
+
+	if (dw_dev->capability & DW_I2S_MASTER) {
+		ret = clk_enable(dw_dev->clk);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int dw_i2s_suspend(struct snd_soc_component *component)
+{
+	struct dw_i2s_dev *dev = snd_soc_component_get_drvdata(component);
+
+	if (dev->capability & DW_I2S_MASTER)
+		clk_disable(dev->clk);
+	return 0;
+}
+
+static int dw_i2s_resume(struct snd_soc_component *component)
+{
+	struct dw_i2s_dev *dev = snd_soc_component_get_drvdata(component);
+	struct snd_soc_dai *dai;
+	int stream, ret;
+
+	if (dev->capability & DW_I2S_MASTER) {
+		ret = clk_enable(dev->clk);
+		if (ret)
+			return ret;
+	}
+
+	for_each_component_dais(component, dai) {
+		for_each_pcm_streams(stream)
+			if (snd_soc_dai_stream_active(dai, stream))
+				dw_i2s_config(dev, stream);
+	}
+
+	return 0;
+}
+
+#else
+#define dw_i2s_suspend	NULL
+#define dw_i2s_resume	NULL
+#endif
+
+static const struct snd_soc_component_driver dw_i2s_component = {
+	.name			= "dw-i2s",
+	.suspend		= dw_i2s_suspend,
+	.resume			= dw_i2s_resume,
+	.legacy_dai_naming	= 1,
+};
+
+/*
+ * The following tables allow a direct lookup of various parameters
+ * defined in the I2S block's configuration in terms of sound system
+ * parameters.  Each table is sized to the number of entries possible
+ * according to the number of configuration bits describing an I2S
+ * block parameter.
+ */
+
+/* Maximum bit resolution of a channel - not uniformly spaced */
+static const u32 fifo_width[COMP_MAX_WORDSIZE] = {
+	12, 16, 20, 24, 32, 0, 0, 0
+};
+
+/* Width of (DMA) bus */
+static const u32 bus_widths[COMP_MAX_DATA_WIDTH] = {
+	DMA_SLAVE_BUSWIDTH_1_BYTE,
+	DMA_SLAVE_BUSWIDTH_2_BYTES,
+	DMA_SLAVE_BUSWIDTH_4_BYTES,
+	DMA_SLAVE_BUSWIDTH_UNDEFINED
+};
+
+/* PCM format to support channel resolution */
+static const u32 formats[COMP_MAX_WORDSIZE] = {
+	SNDRV_PCM_FMTBIT_S16_LE,
+	SNDRV_PCM_FMTBIT_S16_LE,
+	SNDRV_PCM_FMTBIT_S24_LE,
+	SNDRV_PCM_FMTBIT_S24_LE,
+	SNDRV_PCM_FMTBIT_S32_LE,
+	0,
+	0,
+	0
+};
+
+static int dw_configure_dai(struct dw_i2s_dev *dev,
+				   struct snd_soc_dai_driver *dw_i2s_dai,
+				   unsigned int rates)
+{
+	/*
+	 * Read component parameter registers to extract
+	 * the I2S block's configuration.
+	 */
+	u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1);
+	u32 comp2 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp2);
+	u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
+	u32 idx;
+
+	if (dev->capability & DWC_I2S_RECORD &&
+			dev->quirks & DW_I2S_QUIRK_COMP_PARAM1)
+		comp1 = comp1 & ~BIT(5);
+
+	if (dev->capability & DWC_I2S_PLAY &&
+			dev->quirks & DW_I2S_QUIRK_COMP_PARAM1)
+		comp1 = comp1 & ~BIT(6);
+
+	if (COMP1_TX_ENABLED(comp1)) {
+		dev_dbg(dev->dev, " designware: play supported\n");
+		idx = COMP1_TX_WORDSIZE_0(comp1);
+		if (WARN_ON(idx >= ARRAY_SIZE(formats)))
+			return -EINVAL;
+		if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE)
+			idx = 1;
+		dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM;
+		dw_i2s_dai->playback.channels_max =
+				1 << (COMP1_TX_CHANNELS(comp1) + 1);
+		dw_i2s_dai->playback.formats = formats[idx];
+		dw_i2s_dai->playback.rates = rates;
+	}
+
+	if (COMP1_RX_ENABLED(comp1)) {
+		dev_dbg(dev->dev, "designware: record supported\n");
+		idx = COMP2_RX_WORDSIZE_0(comp2);
+		if (WARN_ON(idx >= ARRAY_SIZE(formats)))
+			return -EINVAL;
+		if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE)
+			idx = 1;
+		dw_i2s_dai->capture.channels_min = MIN_CHANNEL_NUM;
+		dw_i2s_dai->capture.channels_max =
+				1 << (COMP1_RX_CHANNELS(comp1) + 1);
+		dw_i2s_dai->capture.formats = formats[idx];
+		dw_i2s_dai->capture.rates = rates;
+	}
+
+	if (COMP1_MODE_EN(comp1)) {
+		dev_dbg(dev->dev, "designware: i2s master mode supported\n");
+		dev->capability |= DW_I2S_MASTER;
+	} else {
+		dev_dbg(dev->dev, "designware: i2s slave mode supported\n");
+		dev->capability |= DW_I2S_SLAVE;
+	}
+
+	dev->fifo_th = fifo_depth / 2;
+	return 0;
+}
+
+static int dw_configure_dai_by_pd(struct dw_i2s_dev *dev,
+				  struct snd_soc_dai_driver *dw_i2s_dai,
+				  struct resource *res,
+				  const struct i2s_platform_data *pdata)
+{
+	u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1);
+	u32 idx = COMP1_APB_DATA_WIDTH(comp1);
+	int ret;
+
+	if (WARN_ON(idx >= ARRAY_SIZE(bus_widths)))
+		return -EINVAL;
+
+	ret = dw_configure_dai(dev, dw_i2s_dai, pdata->snd_rates);
+	if (ret < 0)
+		return ret;
+
+	if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE)
+		idx = 1;
+	/* Set DMA slaves info */
+	dev->play_dma_data.pd.data = pdata->play_dma_data;
+	dev->capture_dma_data.pd.data = pdata->capture_dma_data;
+	dev->play_dma_data.pd.addr = res->start + I2S_TXDMA;
+	dev->capture_dma_data.pd.addr = res->start + I2S_RXDMA;
+	dev->play_dma_data.pd.max_burst = 16;
+	dev->capture_dma_data.pd.max_burst = 16;
+	dev->play_dma_data.pd.addr_width = bus_widths[idx];
+	dev->capture_dma_data.pd.addr_width = bus_widths[idx];
+	dev->play_dma_data.pd.filter = pdata->filter;
+	dev->capture_dma_data.pd.filter = pdata->filter;
+
+	return 0;
+}
+
+static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev,
+				  struct snd_soc_dai_driver *dw_i2s_dai,
+				  struct resource *res)
+{
+	u32 comp1 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_1);
+	u32 comp2 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_2);
+	u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
+	u32 idx = COMP1_APB_DATA_WIDTH(comp1);
+	u32 idx2;
+	int ret;
+
+	if (WARN_ON(idx >= ARRAY_SIZE(bus_widths)))
+		return -EINVAL;
+
+	ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_192000);
+	if (ret < 0)
+		return ret;
+
+	if (COMP1_TX_ENABLED(comp1)) {
+		idx2 = COMP1_TX_WORDSIZE_0(comp1);
+
+		dev->capability |= DWC_I2S_PLAY;
+		dev->play_dma_data.dt.addr = res->start + I2S_TXDMA;
+		dev->play_dma_data.dt.addr_width = bus_widths[idx];
+		dev->play_dma_data.dt.fifo_size = fifo_depth *
+			(fifo_width[idx2]) >> 8;
+		dev->play_dma_data.dt.maxburst = 16;
+	}
+	if (COMP1_RX_ENABLED(comp1)) {
+		idx2 = COMP2_RX_WORDSIZE_0(comp2);
+
+		dev->capability |= DWC_I2S_RECORD;
+		dev->capture_dma_data.dt.addr = res->start + I2S_RXDMA;
+		dev->capture_dma_data.dt.addr_width = bus_widths[idx];
+		dev->capture_dma_data.dt.fifo_size = fifo_depth *
+			(fifo_width[idx2] >> 8);
+		dev->capture_dma_data.dt.maxburst = 16;
+	}
+
+	return 0;
+
+}
+
+static int dw_i2s_probe(struct platform_device *pdev)
+{
+	const struct i2s_platform_data *pdata = pdev->dev.platform_data;
+	struct dw_i2s_dev *dev;
+	struct resource *res;
+	int ret, irq, irq_num;
+	struct snd_soc_dai_driver *dw_i2s_dai;
+	const char *clk_id;
+
+	dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	dw_i2s_dai = devm_kzalloc(&pdev->dev, sizeof(*dw_i2s_dai), GFP_KERNEL);
+	if (!dw_i2s_dai)
+		return -ENOMEM;
+
+	dw_i2s_dai->ops = &dw_i2s_dai_ops;
+
+	dev->i2s_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+	if (IS_ERR(dev->i2s_base))
+		return PTR_ERR(dev->i2s_base);
+
+	dev->dev = &pdev->dev;
+
+	irq_num = platform_irq_count(pdev);
+	if (irq_num < 0)
+		return irq_num; /* -EPROBE_DEFER */
+
+	if (!irq_num)
+		dev_err(&pdev->dev, "No irq found on device\n");
+
+	irq_num--;
+	do {
+		irq = platform_get_irq(pdev, irq_num);
+		if (irq >= 0) {
+			dev_info(&pdev->dev, "%s Registered IRQ number %d %d\n",
+				__func__, irq, irq_num);
+			ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler,
+				0, pdev->name, dev);
+			if (ret < 0) {
+				dev_err(&pdev->dev, "failed to request irq\n");
+				return ret;
+			}
+		}
+	} while (irq_num--);
+
+	dev->i2s_reg_comp1 = I2S_COMP_PARAM_1;
+	dev->i2s_reg_comp2 = I2S_COMP_PARAM_2;
+	if (pdata) {
+		dev->capability = pdata->cap;
+		clk_id = NULL;
+		dev->quirks = pdata->quirks;
+		if (dev->quirks & DW_I2S_QUIRK_COMP_REG_OFFSET) {
+			dev->i2s_reg_comp1 = pdata->i2s_reg_comp1;
+			dev->i2s_reg_comp2 = pdata->i2s_reg_comp2;
+		}
+		ret = dw_configure_dai_by_pd(dev, dw_i2s_dai, res, pdata);
+	} else {
+		clk_id = "i2sclk";
+		ret = dw_configure_dai_by_dt(dev, dw_i2s_dai, res);
+	}
+	if (ret < 0)
+		return ret;
+
+	if (dev->capability & DW_I2S_MASTER) {
+		if (pdata) {
+			dev->i2s_clk_cfg = pdata->i2s_clk_cfg;
+			if (!dev->i2s_clk_cfg) {
+				dev_err(&pdev->dev, "no clock configure method\n");
+				return -ENODEV;
+			}
+		}
+
+		if (is_of_node(pdev->dev.fwnode)) {
+			dev->clk = devm_clk_get(&pdev->dev, clk_id);
+
+			if (IS_ERR(dev->clk))
+				return PTR_ERR(dev->clk);
+
+			ret = clk_prepare_enable(dev->clk);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	dev_set_drvdata(&pdev->dev, dev);
+	ret = devm_snd_soc_register_component(&pdev->dev,
+		&dw_i2s_component, dw_i2s_dai, 1);
+	if (ret != 0) {
+		dev_err(&pdev->dev, "not able to register dai\n");
+		goto err_clk_disable;
+	}
+
+	if (!pdata) {
+		if (irq >= 0) {
+			ret = dw_pcm_register(pdev);
+			dev->use_pio = true;
+		} else {
+			ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
+					0);
+			dev->use_pio = false;
+		}
+
+		if (ret) {
+			dev_err(&pdev->dev, "could not register pcm: %d\n",
+					ret);
+			goto err_clk_disable;
+		}
+	}
+
+	ret = device_property_read_u32(&pdev->dev, "system-clock-frequency",
+				       &dev->sysclk);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"'system-clock-frequency': missing or invalid!\n");
+		return -EINVAL;
+	}
+
+	pm_runtime_enable(&pdev->dev);
+	return 0;
+
+err_clk_disable:
+	if (dev->capability & DW_I2S_MASTER)
+		clk_disable_unprepare(dev->clk);
+	return ret;
+}
+
+static int dw_i2s_remove(struct platform_device *pdev)
+{
+	struct dw_i2s_dev *dev = dev_get_drvdata(&pdev->dev);
+
+	if (dev->capability & DW_I2S_MASTER)
+		clk_disable_unprepare(dev->clk);
+
+	pm_runtime_disable(&pdev->dev);
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id dw_i2s_of_match[] = {
+	{ .compatible = "baikal,bm1000-i2s", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, dw_i2s_of_match);
+#endif
+
+static const struct dev_pm_ops dwc_pm_ops = {
+	SET_RUNTIME_PM_OPS(dw_i2s_runtime_suspend, dw_i2s_runtime_resume, NULL)
+};
+
+static struct platform_driver dw_i2s_driver = {
+	.probe	= dw_i2s_probe,
+	.remove	= dw_i2s_remove,
+	.driver	= {
+		.name = "designware-i2s",
+		.of_match_table = of_match_ptr(dw_i2s_of_match),
+		.pm = &dwc_pm_ops,
+	},
+};
+
+module_platform_driver(dw_i2s_driver);
+
+MODULE_AUTHOR("Baikal Electronics <support@baikalelectronics.ru>");
+MODULE_DESCRIPTION("DESIGNWARE I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bm1000_i2s");
diff --git a/sound/soc/baikal/baikal-pio-pcm.c b/sound/soc/baikal/baikal-pio-pcm.c
new file mode 100644
index 0000000000000..60ffa3aadfc99
--- /dev/null
+++ b/sound/soc/baikal/baikal-pio-pcm.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ALSA SoC Synopsys PIO PCM for I2S driver
+ *
+ * Based on sound/soc/dwc/designware_pcm.c
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/io.h>
+#include <linux/rcupdate.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "local.h"
+
+#define BUFFER_BYTES_MAX	(3 * 2 * 8 * PERIOD_BYTES_MIN)
+#define PERIOD_BYTES_MIN	4096
+#define PERIODS_MIN		2
+
+#define dw_pcm_tx_fn(sample_bits) \
+static unsigned int dw_pcm_tx_##sample_bits(struct dw_i2s_dev *dev, \
+		struct snd_pcm_runtime *runtime, unsigned int tx_ptr, \
+		bool *period_elapsed) \
+{ \
+	const u##sample_bits(*p)[2] = (void *)runtime->dma_area; \
+	unsigned int period_pos = tx_ptr % runtime->period_size; \
+	int i; \
+\
+	for (i = 0; i < dev->fifo_th; i++) { \
+		iowrite32(p[tx_ptr][0], dev->i2s_base + LRBR_LTHR(0)); \
+		iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \
+		period_pos++; \
+		if (++tx_ptr >= runtime->buffer_size) \
+			tx_ptr = 0; \
+	} \
+	*period_elapsed = period_pos >= runtime->period_size; \
+	return tx_ptr; \
+}
+
+#define dw_pcm_rx_fn(sample_bits) \
+static unsigned int dw_pcm_rx_##sample_bits(struct dw_i2s_dev *dev, \
+		struct snd_pcm_runtime *runtime, unsigned int rx_ptr, \
+		bool *period_elapsed) \
+{ \
+	u##sample_bits(*p)[2] = (void *)runtime->dma_area; \
+	unsigned int period_pos = rx_ptr % runtime->period_size; \
+	int i; \
+\
+	for (i = 0; i < dev->fifo_th; i++) { \
+		p[rx_ptr][0] = ioread32(dev->i2s_base + LRBR_LTHR(0)); \
+		p[rx_ptr][1] = ioread32(dev->i2s_base + RRBR_RTHR(0)); \
+		period_pos++; \
+		if (++rx_ptr >= runtime->buffer_size) \
+			rx_ptr = 0; \
+	} \
+	*period_elapsed = period_pos >= runtime->period_size; \
+	return rx_ptr; \
+}
+
+dw_pcm_tx_fn(16);
+dw_pcm_tx_fn(32);
+dw_pcm_rx_fn(16);
+dw_pcm_rx_fn(32);
+
+#undef dw_pcm_tx_fn
+#undef dw_pcm_rx_fn
+
+static const struct snd_pcm_hardware dw_pcm_hardware = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER,
+	.rates = SNDRV_PCM_RATE_32000 |
+		 SNDRV_PCM_RATE_44100 |
+		 SNDRV_PCM_RATE_48000,
+	.rate_min = 32000,
+	.rate_max = 48000,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE |
+		   SNDRV_PCM_FMTBIT_S24_LE |
+		   SNDRV_PCM_FMTBIT_S32_LE,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = BUFFER_BYTES_MAX,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
+	.periods_min = PERIODS_MIN,
+	.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+	.fifo_size = 16,
+};
+
+static void dw_pcm_transfer(struct dw_i2s_dev *dev, bool push)
+{
+	struct snd_pcm_substream *substream;
+	bool active, period_elapsed;
+
+	rcu_read_lock();
+	if (push)
+		substream = rcu_dereference(dev->tx_substream);
+	else
+		substream = rcu_dereference(dev->rx_substream);
+	active = substream && snd_pcm_running(substream);
+	if (active) {
+		unsigned int ptr;
+		unsigned int new_ptr;
+
+		if (push) {
+			ptr = READ_ONCE(dev->tx_ptr);
+			new_ptr = dev->tx_fn(dev, substream->runtime, ptr,
+					&period_elapsed);
+			cmpxchg(&dev->tx_ptr, ptr, new_ptr);
+		} else {
+			ptr = READ_ONCE(dev->rx_ptr);
+			new_ptr = dev->rx_fn(dev, substream->runtime, ptr,
+					&period_elapsed);
+			cmpxchg(&dev->rx_ptr, ptr, new_ptr);
+		}
+
+		if (period_elapsed)
+			snd_pcm_period_elapsed(substream);
+	}
+	rcu_read_unlock();
+}
+
+void dw_pcm_push_tx(struct dw_i2s_dev *dev)
+{
+	dw_pcm_transfer(dev, true);
+}
+
+void dw_pcm_pop_rx(struct dw_i2s_dev *dev)
+{
+	dw_pcm_transfer(dev, false);
+}
+
+static int dw_pcm_open(struct snd_soc_component *component,
+		       struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+
+	snd_soc_set_runtime_hwparams(substream, &dw_pcm_hardware);
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	runtime->private_data = dev;
+
+	return 0;
+}
+
+static int dw_pcm_close(struct snd_soc_component *component,
+			struct snd_pcm_substream *substream)
+{
+	synchronize_rcu();
+	return 0;
+}
+
+static int dw_pcm_hw_params(struct snd_soc_component *component,
+			    struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct dw_i2s_dev *dev = runtime->private_data;
+
+	switch (params_channels(hw_params)) {
+	case 2:
+		break;
+	default:
+		dev_err(dev->dev, "invalid channels number\n");
+		return -EINVAL;
+	}
+
+	switch (params_format(hw_params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		dev->tx_fn = dw_pcm_tx_16;
+		dev->rx_fn = dw_pcm_rx_16;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+	case SNDRV_PCM_FORMAT_S32_LE:
+		dev->tx_fn = dw_pcm_tx_32;
+		dev->rx_fn = dw_pcm_rx_32;
+		break;
+	default:
+		dev_err(dev->dev, "invalid format\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int dw_pcm_trigger(struct snd_soc_component *component,
+			  struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct dw_i2s_dev *dev = runtime->private_data;
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			WRITE_ONCE(dev->tx_ptr, 0);
+			rcu_assign_pointer(dev->tx_substream, substream);
+		} else {
+			WRITE_ONCE(dev->rx_ptr, 0);
+			rcu_assign_pointer(dev->rx_substream, substream);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			rcu_assign_pointer(dev->tx_substream, NULL);
+		else
+			rcu_assign_pointer(dev->rx_substream, NULL);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static snd_pcm_uframes_t dw_pcm_pointer(struct snd_soc_component *component,
+					struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct dw_i2s_dev *dev = runtime->private_data;
+	snd_pcm_uframes_t pos;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		pos = READ_ONCE(dev->tx_ptr);
+	else
+		pos = READ_ONCE(dev->rx_ptr);
+
+	return pos < runtime->buffer_size ? pos : 0;
+}
+
+static int dw_pcm_new(struct snd_soc_component *component,
+		      struct snd_soc_pcm_runtime *rtd)
+{
+	size_t size = dw_pcm_hardware.buffer_bytes_max;
+
+	snd_pcm_set_managed_buffer_all(rtd->pcm,
+			SNDRV_DMA_TYPE_CONTINUOUS,
+			NULL, size, size);
+	return 0;
+}
+
+static const struct snd_soc_component_driver dw_pcm_component = {
+	.open		= dw_pcm_open,
+	.close		= dw_pcm_close,
+	.hw_params	= dw_pcm_hw_params,
+	.trigger	= dw_pcm_trigger,
+	.pointer	= dw_pcm_pointer,
+	.pcm_construct	= dw_pcm_new,
+};
+
+int dw_pcm_register(struct platform_device *pdev)
+{
+	return devm_snd_soc_register_component(&pdev->dev,
+		&dw_pcm_component, NULL, 0);
+}
diff --git a/sound/soc/baikal/local.h b/sound/soc/baikal/local.h
new file mode 100644
index 0000000000000..b1754e0f3bd8d
--- /dev/null
+++ b/sound/soc/baikal/local.h
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (ST) 2012 Rajeev Kumar (rajeevkumar.linux@gmail.com)
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef __DESIGNWARE_LOCAL_H
+#define __DESIGNWARE_LOCAL_H
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/types.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/designware_i2s.h>
+
+/* common register for all channel */
+#define IER		0x000
+#define IRER		0x004
+#define ITER		0x008
+#define CER		0x00C
+#define CCR		0x010
+#define RXFFR		0x014
+#define TXFFR		0x018
+
+/* Interrupt status register fields */
+#define ISR_TXFO	BIT(5)
+#define ISR_TXFE	BIT(4)
+#define ISR_RXFO	BIT(1)
+#define ISR_RXDA	BIT(0)
+
+/* I2STxRxRegisters for all channels */
+#define LRBR_LTHR(x)	(0x40 * x + 0x020)
+#define RRBR_RTHR(x)	(0x40 * x + 0x024)
+#define RER(x)		(0x40 * x + 0x028)
+#define TER(x)		(0x40 * x + 0x02C)
+#define RCR(x)		(0x40 * x + 0x030)
+#define TCR(x)		(0x40 * x + 0x034)
+#define ISR(x)		(0x40 * x + 0x038)
+#define IMR(x)		(0x40 * x + 0x03C)
+#define ROR(x)		(0x40 * x + 0x040)
+#define TOR(x)		(0x40 * x + 0x044)
+#define RFCR(x)		(0x40 * x + 0x048)
+#define TFCR(x)		(0x40 * x + 0x04C)
+#define RFF(x)		(0x40 * x + 0x050)
+#define TFF(x)		(0x40 * x + 0x054)
+
+/* I2SCOMPRegisters */
+#define I2S_COMP_PARAM_2	0x01F0
+#define I2S_COMP_PARAM_1	0x01F4
+#define I2S_COMP_VERSION	0x01F8
+#define I2S_COMP_TYPE		0x01FC
+
+/*
+ * Component parameter register fields - define the I2S block's
+ * configuration.
+ */
+#define	COMP1_TX_WORDSIZE_3(r)		(((r) & GENMASK(27, 25)) >> 25)
+#define	COMP1_TX_WORDSIZE_2(r)		(((r) & GENMASK(24, 22)) >> 22)
+#define	COMP1_TX_WORDSIZE_1(r)		(((r) & GENMASK(21, 19)) >> 19)
+#define	COMP1_TX_WORDSIZE_0(r)		(((r) & GENMASK(18, 16)) >> 16)
+#define	COMP1_TX_CHANNELS(r)		(((r) & GENMASK(10, 9)) >> 9)
+#define	COMP1_RX_CHANNELS(r)		(((r) & GENMASK(8, 7)) >> 7)
+#define	COMP1_RX_ENABLED(r)		(((r) & BIT(6)) >> 6)
+#define	COMP1_TX_ENABLED(r)		(((r) & BIT(5)) >> 5)
+#define	COMP1_MODE_EN(r)		(((r) & BIT(4)) >> 4)
+#define	COMP1_FIFO_DEPTH_GLOBAL(r)	(((r) & GENMASK(3, 2)) >> 2)
+#define	COMP1_APB_DATA_WIDTH(r)		(((r) & GENMASK(1, 0)) >> 0)
+
+#define	COMP2_RX_WORDSIZE_3(r)		(((r) & GENMASK(12, 10)) >> 10)
+#define	COMP2_RX_WORDSIZE_2(r)		(((r) & GENMASK(9, 7)) >> 7)
+#define	COMP2_RX_WORDSIZE_1(r)		(((r) & GENMASK(5, 3)) >> 3)
+#define	COMP2_RX_WORDSIZE_0(r)		(((r) & GENMASK(2, 0)) >> 0)
+
+/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */
+#define	COMP_MAX_WORDSIZE	(1 << 3)
+#define	COMP_MAX_DATA_WIDTH	(1 << 2)
+
+#define MAX_CHANNEL_NUM		8
+#define MIN_CHANNEL_NUM		2
+
+union dw_i2s_snd_dma_data {
+	struct i2s_dma_data pd;
+	struct snd_dmaengine_dai_dma_data dt;
+};
+
+struct dw_i2s_dev {
+	void __iomem *i2s_base;
+	struct clk *clk;
+	int active;
+	unsigned int capability;
+	unsigned int quirks;
+	unsigned int i2s_reg_comp1;
+	unsigned int i2s_reg_comp2;
+	unsigned int sysclk;
+	struct device *dev;
+	u32 ccr;
+	u32 xfer_resolution;
+	u32 fifo_th;
+
+	/* data related to DMA transfers b/w i2s and DMAC */
+	union dw_i2s_snd_dma_data play_dma_data;
+	union dw_i2s_snd_dma_data capture_dma_data;
+	struct i2s_clk_config_data config;
+	int (*i2s_clk_cfg)(struct i2s_clk_config_data *config);
+
+	/* data related to PIO transfers */
+	bool use_pio;
+	struct snd_pcm_substream __rcu *tx_substream;
+	struct snd_pcm_substream __rcu *rx_substream;
+	unsigned int (*tx_fn)(struct dw_i2s_dev *dev,
+			struct snd_pcm_runtime *runtime, unsigned int tx_ptr,
+			bool *period_elapsed);
+	unsigned int (*rx_fn)(struct dw_i2s_dev *dev,
+			struct snd_pcm_runtime *runtime, unsigned int rx_ptr,
+			bool *period_elapsed);
+	unsigned int tx_ptr;
+	unsigned int rx_ptr;
+};
+
+#if IS_ENABLED(CONFIG_SND_BAIKAL_PIO_PCM)
+void dw_pcm_push_tx(struct dw_i2s_dev *dev);
+void dw_pcm_pop_rx(struct dw_i2s_dev *dev);
+int dw_pcm_register(struct platform_device *pdev);
+#else
+static inline void dw_pcm_push_tx(struct dw_i2s_dev *dev) { }
+static inline void dw_pcm_pop_rx(struct dw_i2s_dev *dev) { }
+static inline int dw_pcm_register(struct platform_device *pdev)
+{
+	return -EINVAL;
+}
+#endif
+
+#endif
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 09/39] sound: baikal-i2s: paper over RX overrun warnings on Baikal-M
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (7 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 08/39] Sound: add support for Baikal BE-M1000 I2S Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 10/39] net: stmmac: support of Baikal-BE1000 SoCs GMAC Daniil Gnusarev
                   ` (30 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

i2s_irq_handler: avoid flooding system with RX overrun warnings

Co-developed-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 sound/soc/baikal/baikal-i2s.c | 9 +++++++--
 sound/soc/baikal/local.h      | 1 +
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/sound/soc/baikal/baikal-i2s.c b/sound/soc/baikal/baikal-i2s.c
index 91d8fe742f673..d985b3fe4b7d4 100644
--- a/sound/soc/baikal/baikal-i2s.c
+++ b/sound/soc/baikal/baikal-i2s.c
@@ -98,6 +98,7 @@ static inline void i2s_enable_irqs(struct dw_i2s_dev *dev, u32 stream,
 
 static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
 {
+	unsigned int rxor_count;
 	struct dw_i2s_dev *dev = dev_id;
 	bool irq_valid = false;
 	u32 isr[4];
@@ -130,13 +131,17 @@ static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
 
 		/* Error Handling: TX */
 		if (isr[i] & ISR_TXFO) {
-			dev_err(dev->dev, "TX overrun (ch_id=%d)\n", i);
+			dev_err_ratelimited(dev->dev, "TX overrun (ch_id=%d)\n", i);
 			irq_valid = true;
 		}
 
 		/* Error Handling: RX */
 		if (isr[i] & ISR_RXFO) {
-			dev_err(dev->dev, "RX overrun (ch_id=%d)\n", i);
+			rxor_count = READ_ONCE(dev->rx_overrun_count);
+			if (!(rxor_count & 0x3ff))
+				dev_err_ratelimited(dev->dev, "RX overrun (ch_id=%d)\n", i);
+			rxor_count++;
+			WRITE_ONCE(dev->rx_overrun_count, rxor_count);
 			irq_valid = true;
 		}
 	}
diff --git a/sound/soc/baikal/local.h b/sound/soc/baikal/local.h
index b1754e0f3bd8d..d1cd07e540b45 100644
--- a/sound/soc/baikal/local.h
+++ b/sound/soc/baikal/local.h
@@ -119,6 +119,7 @@ struct dw_i2s_dev {
 			bool *period_elapsed);
 	unsigned int tx_ptr;
 	unsigned int rx_ptr;
+	unsigned int rx_overrun_count;
 };
 
 #if IS_ENABLED(CONFIG_SND_BAIKAL_PIO_PCM)
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 10/39] net: stmmac: support of Baikal-BE1000 SoCs GMAC
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (8 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 09/39] sound: baikal-i2s: paper over RX overrun warnings on Baikal-M Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 11/39] net: fwnode_get_phy_id: consider all compatible strings Daniil Gnusarev
                   ` (29 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

The Gigabit Ethernet Controller available in the Baikal-M
SoC is a Synopsys DesignWare MAC IP core, already supported
by the stmmac driver. This add Baikal Electronics DWMAC specific glue layer.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Co-developed-by: Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
Co-developed-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
---
 drivers/net/ethernet/stmicro/stmmac/Kconfig   |   8 +
 drivers/net/ethernet/stmicro/stmmac/Makefile  |   1 +
 .../ethernet/stmicro/stmmac/dwmac-baikal.c    | 542 ++++++++++++++++++
 .../ethernet/stmicro/stmmac/dwmac1000_core.c  |   1 +
 .../ethernet/stmicro/stmmac/dwmac1000_dma.c   |  56 +-
 .../ethernet/stmicro/stmmac/dwmac1000_dma.h   |  32 ++
 .../net/ethernet/stmicro/stmmac/dwmac_lib.c   |   8 +
 net/ethernet/eth.c                            |   1 +
 8 files changed, 626 insertions(+), 23 deletions(-)
 create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c
 create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h

diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig
index 92d7d5a00b84c..34ea1d065fb72 100644
--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
@@ -66,6 +66,14 @@ config DWMAC_ANARION
 
 	  This selects the Anarion SoC glue layer support for the stmmac driver.
 
+config DWMAC_BAIKAL
+	tristate "Baikal Electronics DWMAC support"
+	depends on OF
+	help
+	  Support for Baikal Electronics DWMAC Ethernet.
+
+	  This selects the Baikal SoC glue layer support for the stmmac driver.
+
 config DWMAC_INGENIC
 	tristate "Ingenic MAC support"
 	default MACH_INGENIC
diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile
index 5b57aee19267f..7c39c5a4b4d6f 100644
--- a/drivers/net/ethernet/stmicro/stmmac/Makefile
+++ b/drivers/net/ethernet/stmicro/stmmac/Makefile
@@ -14,6 +14,7 @@ stmmac-$(CONFIG_STMMAC_SELFTESTS) += stmmac_selftests.o
 # Ordering matters. Generic driver must be last.
 obj-$(CONFIG_STMMAC_PLATFORM)	+= stmmac-platform.o
 obj-$(CONFIG_DWMAC_ANARION)	+= dwmac-anarion.o
+obj-$(CONFIG_DWMAC_BAIKAL)	+= dwmac-baikal.o
 obj-$(CONFIG_DWMAC_INGENIC)	+= dwmac-ingenic.o
 obj-$(CONFIG_DWMAC_IPQ806X)	+= dwmac-ipq806x.o
 obj-$(CONFIG_DWMAC_LPC18XX)	+= dwmac-lpc18xx.o
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c
new file mode 100644
index 0000000000000..a9a66068b3f65
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c
@@ -0,0 +1,542 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Baikal Electronics DWMAC specific glue layer
+ *
+ * Copyright (C) 2015-2022 Baikal Electronics, JSC
+ * Authors: Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
+ *          Alexey Sheplyakov <asheplyakov@altlinux.org>
+ */
+
+#include <linux/acpi.h>
+#include <linux/arm-smccc.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include "stmmac.h"
+#include "stmmac_platform.h"
+#include "common.h"
+#include "dwmac_dma.h"
+#include "dwmac1000_dma.h"
+
+#define MAC_GPIO	0x00e0		/* GPIO register */
+#define MAC_GPIO_GPO	(1 << 8)	/* Output port */
+
+#define BAIKAL_SMC_GMAC_DIV2_ENABLE	0xC2000500
+#define BAIKAL_SMC_GMAC_DIV2_DISABLE	0xC2000501
+
+struct baikal_gmac {
+	struct device	*dev;
+	uint64_t	base;
+	struct clk	*axi_clk;
+	struct clk	*tx2_clk;
+	int		has_aux_div2;
+	bool		is_fixed_stmmac_clk;
+};
+
+static int baikal_gmac_dma_reset(void __iomem *ioaddr)
+{
+	int err;
+	u32 value;
+
+	/* DMA SW reset */
+	value = readl(ioaddr + DMA_BUS_MODE);
+	value |= DMA_BUS_MODE_SFT_RESET;
+	writel(value, ioaddr + DMA_BUS_MODE);
+
+	/* Software DMA reset also resets MAC, so GP_OUT is set to zero.
+	 * Which resets PHY as a side effect (if GP_OUT is connected directly
+	 * to PHY reset).
+	 * TODO: read the PHY reset duration from the device tree.
+	 * Meanwhile use 100 milliseconds which seems to be enough for
+	 * most PHYs
+	 */
+	usleep_range(100000, 120000);
+
+	/* Clear PHY reset */
+	value = readl(ioaddr + MAC_GPIO);
+	value |= MAC_GPIO_GPO;
+	writel(value, ioaddr + MAC_GPIO);
+
+	/* Many PHYs need ~100 milliseconds to calm down after PHY reset
+	 * has been cleared. And check for DMA reset below might return
+	 * much earlier (i.e. in ~20 milliseconds). As a result reading
+	 * PHY registers (after this function returns) might return garbage.
+	 * Wait a bit to avoid the problem.
+	 */
+	usleep_range(100000, 150000);
+
+	err = readl_poll_timeout(ioaddr + DMA_BUS_MODE, value,
+				 !(value & DMA_BUS_MODE_SFT_RESET),
+				 10000, 1000000);
+	if (err) {
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static const struct stmmac_dma_ops baikal_gmac_dma_ops = {
+	.reset = baikal_gmac_dma_reset,
+	.init = dwmac1000_dma_init,
+	.init_rx_chan = dwmac1000_dma_init_rx,
+	.init_tx_chan = dwmac1000_dma_init_tx,
+	.axi = dwmac1000_dma_axi,
+	.dump_regs = dwmac1000_dump_dma_regs,
+	.dma_rx_mode = dwmac1000_dma_operation_mode_rx,
+	.dma_tx_mode = dwmac1000_dma_operation_mode_tx,
+	.enable_dma_transmission = dwmac_enable_dma_transmission,
+	.enable_dma_irq = dwmac_enable_dma_irq,
+	.disable_dma_irq = dwmac_disable_dma_irq,
+	.start_tx = dwmac_dma_start_tx,
+	.stop_tx = dwmac_dma_stop_tx,
+	.start_rx = dwmac_dma_start_rx,
+	.stop_rx = dwmac_dma_stop_rx,
+	.dma_interrupt = dwmac_dma_interrupt,
+	.get_hw_feature = dwmac1000_get_hw_feature,
+	.rx_watchdog = dwmac1000_rx_watchdog
+};
+
+static struct mac_device_info *baikal_gmac_setup(void *ppriv)
+{
+	struct mac_device_info *mac, *old_mac;
+	struct stmmac_priv *priv = ppriv;
+	struct gpio_desc *reset_gpio;
+	int err;
+	u32 value;
+
+	mac = devm_kzalloc(priv->device, sizeof(*mac), GFP_KERNEL);
+	if (!mac) {
+		return NULL;
+	}
+
+	/* Clear PHY reset */
+	value = readl(priv->ioaddr + MAC_GPIO);
+	value |= MAC_GPIO_GPO;
+	writel(value, priv->ioaddr + MAC_GPIO);
+	reset_gpio = devm_gpiod_get_optional(priv->device,
+					     "snps,reset",
+					     GPIOD_OUT_LOW);
+
+	err = readl_poll_timeout(priv->ioaddr + DMA_BUS_MODE, value,
+				 !(value & DMA_BUS_MODE_SFT_RESET),
+				 10000, 1000000);
+
+	if (reset_gpio != NULL) {
+		devm_gpiod_put(priv->device, reset_gpio);
+	}
+
+	if (err) {
+		dev_err(priv->device, "SW reset is not cleared: error %d", err);
+		return NULL;
+	}
+
+	mac->dma = &baikal_gmac_dma_ops;
+	old_mac = priv->hw;
+	priv->hw = mac;
+	err = dwmac1000_setup(priv);
+	priv->hw = old_mac;
+	if (err) {
+		dev_err(priv->device,
+			"%s: dwmac1000_setup failed with error %d",
+			__func__, err);
+		return NULL;
+	}
+
+	return mac;
+}
+
+static void baikal_gmac_fix_mac_speed(void *priv, unsigned int speed,
+				      unsigned int mode)
+{
+	struct arm_smccc_res res;
+	struct baikal_gmac *gmac = priv;
+	unsigned long tx2_clk_freq = 0;
+
+	switch (speed) {
+	case SPEED_1000:
+		tx2_clk_freq = 250000000;
+		if (gmac->has_aux_div2) {
+			arm_smccc_smc(BAIKAL_SMC_GMAC_DIV2_DISABLE,
+				      gmac->base, 0, 0, 0, 0, 0, 0, &res);
+		}
+		break;
+	case SPEED_100:
+		tx2_clk_freq = 50000000;
+		if (gmac->has_aux_div2) {
+			arm_smccc_smc(BAIKAL_SMC_GMAC_DIV2_DISABLE,
+				      gmac->base, 0, 0, 0, 0, 0, 0, &res);
+		}
+		break;
+	case SPEED_10:
+		tx2_clk_freq = 5000000;
+		if (gmac->has_aux_div2) {
+			tx2_clk_freq *= 2;
+			arm_smccc_smc(BAIKAL_SMC_GMAC_DIV2_ENABLE,
+				      gmac->base, 0, 0, 0, 0, 0, 0, &res);
+		}
+		break;
+	}
+
+	if (gmac->tx2_clk != NULL && tx2_clk_freq) {
+		clk_set_rate(gmac->tx2_clk, tx2_clk_freq);
+	}
+}
+
+#ifdef CONFIG_ACPI
+static struct plat_stmmacenet_data *baikal_stmmac_probe_config(struct device *dev,
+							       const char **mac,
+							       bool *is_fixed_stmmac_clk)
+{
+	struct plat_stmmacenet_data *plat_dat;
+	u8 nvmem_mac[ETH_ALEN];
+	int ret;
+	bool is_fixed_clk = false;
+
+	plat_dat = devm_kzalloc(dev, sizeof(*plat_dat), GFP_KERNEL);
+	if (!plat_dat) {
+		return ERR_PTR(-ENOMEM);
+	}
+
+	ret = nvmem_get_mac_address(dev, &nvmem_mac);
+	if (ret) {
+		if (ret == -EPROBE_DEFER) {
+			return ERR_PTR(ret);
+		}
+
+		*mac = NULL;
+	} else {
+		*mac = devm_kmemdup(dev, nvmem_mac, ETH_ALEN, GFP_KERNEL);
+	}
+
+	plat_dat->phy_interface = device_get_phy_mode(dev);
+	if (plat_dat->phy_interface < 0) {
+		return NULL;
+	}
+
+	plat_dat->mac_interface = plat_dat->phy_interface;
+
+	if (device_property_read_u32(dev, "max-speed", &plat_dat->max_speed)) {
+		plat_dat->max_speed = -1;
+	}
+
+	plat_dat->bus_id = ACPI_COMPANION(dev)->pnp.instance_no;
+
+	ret = device_property_read_u32(dev, "reg", &plat_dat->phy_addr);
+	if (ret) {
+		dev_err(dev, "couldn't get reg property\n");
+		return ERR_PTR(ret);
+	}
+
+	if (plat_dat->phy_addr >= PHY_MAX_ADDR) {
+		dev_err(dev, "PHY address %i is too large\n",
+				plat_dat->phy_addr);
+		return ERR_PTR(-EINVAL);
+	}
+
+	plat_dat->mdio_bus_data = devm_kzalloc(dev,
+						sizeof(*plat_dat->mdio_bus_data),
+						GFP_KERNEL);
+	if (!plat_dat->mdio_bus_data) {
+		return ERR_PTR(-ENOMEM);
+	}
+
+	plat_dat->mdio_bus_data->needs_reset = true;
+	plat_dat->maxmtu = JUMBO_LEN;
+	plat_dat->multicast_filter_bins = HASH_TABLE_SIZE;
+	plat_dat->unicast_filter_entries = 1;
+	plat_dat->bugged_jumbo = 1; /* TODO: is it really required? */
+
+	plat_dat->dma_cfg = devm_kzalloc(dev,
+					sizeof(*plat_dat->dma_cfg),
+					GFP_KERNEL);
+	if (!plat_dat->dma_cfg) {
+		return ERR_PTR(-ENOMEM);
+	}
+
+	plat_dat->dma_cfg->pbl = DEFAULT_DMA_PBL;
+	device_property_read_u32(dev, "snps,txpbl", &plat_dat->dma_cfg->txpbl);
+	device_property_read_u32(dev, "snps,rxpbl", &plat_dat->dma_cfg->rxpbl);
+	plat_dat->dma_cfg->fixed_burst = device_property_read_bool(dev,
+							"snps,fixed-burst");
+
+	plat_dat->axi = devm_kzalloc(dev, sizeof(*plat_dat->axi), GFP_KERNEL);
+	if (!plat_dat->axi) {
+		return ERR_PTR(-ENOMEM);
+	}
+
+	device_property_read_u32_array(dev, "snps,blen",
+					plat_dat->axi->axi_blen, AXI_BLEN);
+
+	plat_dat->rx_queues_to_use = 1;
+	plat_dat->tx_queues_to_use = 1;
+	plat_dat->rx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB;
+	plat_dat->tx_queues_cfg[0].mode_to_use = MTL_QUEUE_DCB;
+
+	if (device_property_read_u32(dev, "stmmac-clk", &plat_dat->clk_ptp_rate)) {
+		plat_dat->clk_ptp_rate = 50000000;
+	}
+
+	plat_dat->stmmac_clk = devm_clk_get(dev, STMMAC_RESOURCE_NAME);
+	if (IS_ERR(plat_dat->stmmac_clk)) {
+		if (!plat_dat->clk_ptp_rate) {
+			dev_err(dev, "stmmaceth clock and 'stmmac-clk' property are missed simultaneously\n");
+			return ERR_PTR(-EINVAL);
+		}
+
+		plat_dat->stmmac_clk = clk_register_fixed_rate(NULL,
+							dev_name(dev), NULL, 0,
+							plat_dat->clk_ptp_rate);
+		if (IS_ERR(plat_dat->stmmac_clk))
+			return ERR_CAST(plat_dat->stmmac_clk);
+
+		is_fixed_clk = true;
+	} else {
+		if (!plat_dat->clk_ptp_rate)
+			plat_dat->clk_ptp_rate = clk_get_rate(plat_dat->stmmac_clk);
+	}
+
+	plat_dat->clk_ptp_ref = devm_clk_get(dev, "ptp_ref");
+	if (IS_ERR(plat_dat->clk_ptp_ref)) {
+		plat_dat->clk_ptp_ref = NULL;
+	} else {
+		plat_dat->clk_ptp_rate = clk_get_rate(plat_dat->clk_ptp_ref);
+	}
+
+	clk_prepare_enable(plat_dat->stmmac_clk);
+
+	plat_dat->stmmac_rst = devm_reset_control_get(dev,
+						      STMMAC_RESOURCE_NAME);
+	if (IS_ERR(plat_dat->stmmac_rst))
+		plat_dat->stmmac_rst = NULL;
+
+	plat_dat->mdio_bus_data->phy_mask = ~0;
+
+	if (device_get_child_node_count(dev) != 1) {
+		clk_disable_unprepare(plat_dat->stmmac_clk);
+		if (is_fixed_clk)
+			clk_unregister_fixed_rate(plat_dat->stmmac_clk);
+		return ERR_PTR(-EINVAL);
+	}
+
+	*is_fixed_stmmac_clk = is_fixed_clk;
+
+	return plat_dat;
+}
+
+static int baikal_add_mdio_phy(struct device *dev)
+{
+	struct stmmac_priv *priv = netdev_priv(dev_get_drvdata(dev));
+	struct fwnode_handle *fwnode = device_get_next_child_node(dev, NULL);
+	struct phy_device *phy;
+	int ret;
+
+	phy = get_phy_device(priv->mii, priv->plat->phy_addr, 0);
+	if (IS_ERR(phy)) {
+		return PTR_ERR(phy);
+	}
+
+	phy->irq = priv->mii->irq[priv->plat->phy_addr];
+	phy->mdio.dev.fwnode = fwnode;
+
+	ret = phy_device_register(phy);
+	if (ret) {
+		phy_device_free(phy);
+		return ret;
+	}
+
+	return 0;
+}
+#else
+static struct plat_stmmacenet_data *baikal_stmmac_probe_config(struct device *dev,
+							       const char **mac,
+							       bool *is_fixed_stmmac_clk)
+{
+	return NULL;
+}
+
+static int baikal_add_mdio_phy(struct device *dev)
+{
+	return 0;
+}
+#endif
+
+static int baikal_gmac_probe(struct platform_device *pdev)
+{
+	struct plat_stmmacenet_data *plat_dat;
+	struct stmmac_resources stmmac_res;
+	struct resource *res;
+	struct baikal_gmac *gmac;
+	struct device_node *dn = NULL;
+	const char *str = NULL;
+	bool is_fixed_stmmac_clk = false;
+	int ret;
+
+	if (acpi_disabled) {
+		ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+		if (ret) {
+			return ret;
+		}
+	} else {
+		memset(&stmmac_res, 0, sizeof(stmmac_res));
+		stmmac_res.irq = platform_get_irq(pdev, 0);
+		if (stmmac_res.irq < 0) {
+			return stmmac_res.irq;
+		}
+
+		stmmac_res.wol_irq = stmmac_res.irq;
+		stmmac_res.addr = devm_platform_ioremap_resource(pdev, 0);
+		if (IS_ERR(stmmac_res.addr)) {
+			return PTR_ERR(stmmac_res.addr);
+		}
+	}
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	if (ret) {
+		dev_warn(&pdev->dev, "no suitable DMA available\n");
+		return ret;
+	}
+
+	if (pdev->dev.of_node) {
+		plat_dat = stmmac_probe_config_dt(pdev, (u8 *)&stmmac_res.mac);
+		if (IS_ERR(plat_dat)) {
+			dev_err(&pdev->dev, "dt configuration failed\n");
+			return PTR_ERR(plat_dat);
+		}
+	} else if (!acpi_disabled) {
+		plat_dat = baikal_stmmac_probe_config(&pdev->dev,
+						      (const char **)&stmmac_res.mac,
+						      &is_fixed_stmmac_clk);
+		if (IS_ERR(plat_dat)) {
+			dev_err(&pdev->dev, "acpi configuration failed\n");
+			return PTR_ERR(plat_dat);
+		}
+
+		dn = kzalloc(sizeof(struct device_node), GFP_KERNEL);
+		if (!dn) {
+			ret = -ENOMEM;
+			goto err_remove_config_dt;
+		}
+
+		plat_dat->phy_node = dn;
+	} else {
+		plat_dat = dev_get_platdata(&pdev->dev);
+		if (!plat_dat) {
+			dev_err(&pdev->dev, "no platform data provided\n");
+			return -EINVAL;
+		}
+
+		/* Set default value for multicast hash bins */
+		plat_dat->multicast_filter_bins = HASH_TABLE_SIZE;
+
+		/* Set default value for unicast filter entries */
+		plat_dat->unicast_filter_entries = 1;
+	}
+
+	gmac = devm_kzalloc(&pdev->dev, sizeof(*gmac), GFP_KERNEL);
+	if (!gmac) {
+		ret = -ENOMEM;
+		goto err_remove_config_dt;
+	}
+
+	gmac->dev = &pdev->dev;
+	gmac->tx2_clk = devm_clk_get(gmac->dev, "tx2_clk");
+	if (IS_ERR(gmac->tx2_clk)) {
+		dev_warn(&pdev->dev, "couldn't get TX2 clock\n");
+		gmac->tx2_clk = NULL;
+	}
+
+	gmac->axi_clk = devm_clk_get(gmac->dev, "axi_clk");
+	if (IS_ERR(gmac->axi_clk)) {
+		dev_warn(&pdev->dev, "couldn't get AXI clock\n");
+		gmac->axi_clk = NULL;
+	} else {
+		clk_set_rate(gmac->axi_clk, 300000000);
+	}
+
+	if (!acpi_disabled) {
+		device_property_read_string(&pdev->dev, "compatible", &str);
+	}
+
+	if ((gmac->dev->of_node &&
+	     of_device_is_compatible(gmac->dev->of_node, "baikal,bs1000-gmac"))
+	    || (str && strcasecmp(str, "baikal,bs1000-gmac") == 0)) {
+		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+		gmac->base = res->start;
+		gmac->has_aux_div2 = 1;
+	} else {
+		gmac->has_aux_div2 = 0;
+	}
+
+	plat_dat->fix_mac_speed = baikal_gmac_fix_mac_speed;
+	plat_dat->bsp_priv = gmac;
+	plat_dat->has_gmac = 1;
+	plat_dat->bugged_jumbo = 1; /* TODO: is it really required? */
+	plat_dat->setup = baikal_gmac_setup;
+
+	ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+	if (ret) {
+		goto err_remove_config_dt;
+	}
+
+	if (!acpi_disabled) {
+		ret = baikal_add_mdio_phy(&pdev->dev);
+		if (ret) {
+			goto err_remove_config_dt;
+		}
+	}
+
+	gmac->is_fixed_stmmac_clk = is_fixed_stmmac_clk;
+
+	kfree(dn);
+	return 0;
+
+err_remove_config_dt:
+	stmmac_remove_config_dt(pdev, plat_dat);
+
+	if (is_fixed_stmmac_clk)
+		clk_unregister_fixed_rate(plat_dat->stmmac_clk);
+
+	kfree(dn);
+	return ret;
+}
+
+static void baikal_gmac_remove(struct platform_device *pdev)
+{
+	struct stmmac_priv *priv = netdev_priv(dev_get_drvdata(&pdev->dev));
+	struct plat_stmmacenet_data *plat = priv->plat;
+	struct baikal_gmac *gmac = plat->bsp_priv;
+
+	if (gmac->is_fixed_stmmac_clk) {
+		clk_disable_unprepare(plat->stmmac_clk);
+		clk_unregister_fixed_rate(plat->stmmac_clk);
+		plat->stmmac_clk = NULL;
+	}
+
+	stmmac_pltfr_remove(pdev);
+}
+
+static const struct of_device_id baikal_gmac_dwmac_match[] = {
+	{ .compatible = "baikal,bm1000-gmac" },
+	{ .compatible = "baikal,bs1000-gmac" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, baikal_gmac_dwmac_match);
+
+static struct platform_driver baikal_gmac_dwmac_driver = {
+	.probe		= baikal_gmac_probe,
+	.remove_new	= baikal_gmac_remove,
+	.driver		= {
+		.name = "baikal-gmac-dwmac",
+		.pm = &stmmac_pltfr_pm_ops,
+		.of_match_table = of_match_ptr(baikal_gmac_dwmac_match)
+	}
+};
+module_platform_driver(baikal_gmac_dwmac_driver);
+
+MODULE_DESCRIPTION("Baikal DWMAC specific glue driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
index 8555299443f4e..a047cedb74abb 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
@@ -557,3 +557,4 @@ int dwmac1000_setup(struct stmmac_priv *priv)
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(dwmac1000_setup);
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
index daf79cdbd3ecf..1af8bb44034c0 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
@@ -15,8 +15,9 @@
 #include <asm/io.h>
 #include "dwmac1000.h"
 #include "dwmac_dma.h"
+#include "dwmac1000_dma.h"
 
-static void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi)
+void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi)
 {
 	u32 value = readl(ioaddr + DMA_AXI_BUS_MODE);
 	int i;
@@ -69,9 +70,10 @@ static void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi)
 
 	writel(value, ioaddr + DMA_AXI_BUS_MODE);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dma_axi);
 
-static void dwmac1000_dma_init(void __iomem *ioaddr,
-			       struct stmmac_dma_cfg *dma_cfg, int atds)
+void dwmac1000_dma_init(void __iomem *ioaddr,
+			struct stmmac_dma_cfg *dma_cfg, int atds)
 {
 	u32 value = readl(ioaddr + DMA_BUS_MODE);
 	int txpbl = dma_cfg->txpbl ?: dma_cfg->pbl;
@@ -109,24 +111,27 @@ static void dwmac1000_dma_init(void __iomem *ioaddr,
 	/* Mask interrupts by writing to CSR7 */
 	writel(DMA_INTR_DEFAULT_MASK, ioaddr + DMA_INTR_ENA);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dma_init);
 
-static void dwmac1000_dma_init_rx(struct stmmac_priv *priv,
-				  void __iomem *ioaddr,
-				  struct stmmac_dma_cfg *dma_cfg,
-				  dma_addr_t dma_rx_phy, u32 chan)
+void dwmac1000_dma_init_rx(struct stmmac_priv *priv,
+			   void __iomem *ioaddr,
+			   struct stmmac_dma_cfg *dma_cfg,
+			   dma_addr_t dma_rx_phy, u32 chan)
 {
 	/* RX descriptor base address list must be written into DMA CSR3 */
 	writel(lower_32_bits(dma_rx_phy), ioaddr + DMA_RCV_BASE_ADDR);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dma_init_rx);
 
-static void dwmac1000_dma_init_tx(struct stmmac_priv *priv,
-				  void __iomem *ioaddr,
-				  struct stmmac_dma_cfg *dma_cfg,
-				  dma_addr_t dma_tx_phy, u32 chan)
+void dwmac1000_dma_init_tx(struct stmmac_priv *priv,
+			   void __iomem *ioaddr,
+			   struct stmmac_dma_cfg *dma_cfg,
+			   dma_addr_t dma_tx_phy, u32 chan)
 {
 	/* TX descriptor base address list must be written into DMA CSR4 */
 	writel(lower_32_bits(dma_tx_phy), ioaddr + DMA_TX_BASE_ADDR);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dma_init_tx);
 
 static u32 dwmac1000_configure_fc(u32 csr6, int rxfifosz)
 {
@@ -149,9 +154,9 @@ static u32 dwmac1000_configure_fc(u32 csr6, int rxfifosz)
 	return csr6;
 }
 
-static void dwmac1000_dma_operation_mode_rx(struct stmmac_priv *priv,
-					    void __iomem *ioaddr, int mode,
-					    u32 channel, int fifosz, u8 qmode)
+void dwmac1000_dma_operation_mode_rx(struct stmmac_priv *priv,
+				     void __iomem *ioaddr, int mode,
+				     u32 channel, int fifosz, u8 qmode)
 {
 	u32 csr6 = readl(ioaddr + DMA_CONTROL);
 
@@ -177,10 +182,11 @@ static void dwmac1000_dma_operation_mode_rx(struct stmmac_priv *priv,
 
 	writel(csr6, ioaddr + DMA_CONTROL);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dma_operation_mode_rx);
 
-static void dwmac1000_dma_operation_mode_tx(struct stmmac_priv *priv,
-					    void __iomem *ioaddr, int mode,
-					    u32 channel, int fifosz, u8 qmode)
+void dwmac1000_dma_operation_mode_tx(struct stmmac_priv *priv,
+				     void __iomem *ioaddr, int mode,
+				     u32 channel, int fifosz, u8 qmode)
 {
 	u32 csr6 = readl(ioaddr + DMA_CONTROL);
 
@@ -211,9 +217,10 @@ static void dwmac1000_dma_operation_mode_tx(struct stmmac_priv *priv,
 
 	writel(csr6, ioaddr + DMA_CONTROL);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dma_operation_mode_tx);
 
-static void dwmac1000_dump_dma_regs(struct stmmac_priv *priv,
-				    void __iomem *ioaddr, u32 *reg_space)
+void dwmac1000_dump_dma_regs(struct stmmac_priv *priv,
+			     void __iomem *ioaddr, u32 *reg_space)
 {
 	int i;
 
@@ -222,9 +229,10 @@ static void dwmac1000_dump_dma_regs(struct stmmac_priv *priv,
 			reg_space[DMA_BUS_MODE / 4 + i] =
 				readl(ioaddr + DMA_BUS_MODE + i * 4);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dump_dma_regs);
 
-static int dwmac1000_get_hw_feature(void __iomem *ioaddr,
-				    struct dma_features *dma_cap)
+int dwmac1000_get_hw_feature(void __iomem *ioaddr,
+			     struct dma_features *dma_cap)
 {
 	u32 hw_cap = readl(ioaddr + DMA_HW_FEATURE);
 
@@ -267,12 +275,14 @@ static int dwmac1000_get_hw_feature(void __iomem *ioaddr,
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(dwmac1000_get_hw_feature);
 
-static void dwmac1000_rx_watchdog(struct stmmac_priv *priv,
-				  void __iomem *ioaddr, u32 riwt, u32 queue)
+void dwmac1000_rx_watchdog(struct stmmac_priv *priv,
+			   void __iomem *ioaddr, u32 riwt, u32 queue)
 {
 	writel(riwt, ioaddr + DMA_RX_WATCHDOG);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_rx_watchdog);
 
 const struct stmmac_dma_ops dwmac1000_dma_ops = {
 	.reset = dwmac_dma_reset,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h
new file mode 100644
index 0000000000000..34c10eb03a10b
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __DWMAC1000_DMA_H__
+#define __DWMAC1000_DMA_H__
+#include "dwmac1000.h"
+
+void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi);
+void dwmac1000_dma_init(void __iomem *ioaddr,
+			struct stmmac_dma_cfg *dma_cfg, int atds);
+void dwmac1000_dma_init_rx(struct stmmac_priv *priv,
+			   void __iomem *ioaddr,
+			   struct stmmac_dma_cfg *dma_cfg,
+			   dma_addr_t dma_rx_phy, u32 chan);
+void dwmac1000_dma_init_tx(struct stmmac_priv *priv,
+			   void __iomem *ioaddr,
+			   struct stmmac_dma_cfg *dma_cfg,
+			   dma_addr_t dma_tx_phy, u32 chan);
+void dwmac1000_dma_operation_mode_rx(struct stmmac_priv *priv,
+				     void __iomem *ioaddr, int mode,
+				     u32 channel, int fifosz, u8 qmode);
+void dwmac1000_dma_operation_mode_tx(struct stmmac_priv *priv,
+				     void __iomem *ioaddr, int mode,
+				     u32 channel, int fifosz, u8 qmode);
+void dwmac1000_dump_dma_regs(struct stmmac_priv *priv,
+			     void __iomem *ioaddr, u32 *reg_space);
+
+int  dwmac1000_get_hw_feature(void __iomem *ioaddr,
+			      struct dma_features *dma_cap);
+
+void dwmac1000_rx_watchdog(struct stmmac_priv *priv,
+			   void __iomem *ioaddr, u32 riwt, u32 number_chan);
+#endif /* __DWMAC1000_DMA_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
index 85e18f9a22f92..12e7f2c081f93 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
@@ -32,6 +32,7 @@ void dwmac_enable_dma_transmission(void __iomem *ioaddr)
 {
 	writel(1, ioaddr + DMA_XMT_POLL_DEMAND);
 }
+EXPORT_SYMBOL_GPL(dwmac_enable_dma_transmission);
 
 void dwmac_enable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
 			  u32 chan, bool rx, bool tx)
@@ -45,6 +46,7 @@ void dwmac_enable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
 
 	writel(value, ioaddr + DMA_INTR_ENA);
 }
+EXPORT_SYMBOL_GPL(dwmac_enable_dma_irq);
 
 void dwmac_disable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
 			   u32 chan, bool rx, bool tx)
@@ -58,6 +60,7 @@ void dwmac_disable_dma_irq(struct stmmac_priv *priv, void __iomem *ioaddr,
 
 	writel(value, ioaddr + DMA_INTR_ENA);
 }
+EXPORT_SYMBOL_GPL(dwmac_disable_dma_irq);
 
 void dwmac_dma_start_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
 			u32 chan)
@@ -66,6 +69,7 @@ void dwmac_dma_start_tx(struct stmmac_priv *priv, void __iomem *ioaddr,
 	value |= DMA_CONTROL_ST;
 	writel(value, ioaddr + DMA_CONTROL);
 }
+EXPORT_SYMBOL_GPL(dwmac_dma_start_tx);
 
 void dwmac_dma_stop_tx(struct stmmac_priv *priv, void __iomem *ioaddr, u32 chan)
 {
@@ -73,6 +77,7 @@ void dwmac_dma_stop_tx(struct stmmac_priv *priv, void __iomem *ioaddr, u32 chan)
 	value &= ~DMA_CONTROL_ST;
 	writel(value, ioaddr + DMA_CONTROL);
 }
+EXPORT_SYMBOL_GPL(dwmac_dma_stop_tx);
 
 void dwmac_dma_start_rx(struct stmmac_priv *priv, void __iomem *ioaddr,
 			u32 chan)
@@ -81,6 +86,7 @@ void dwmac_dma_start_rx(struct stmmac_priv *priv, void __iomem *ioaddr,
 	value |= DMA_CONTROL_SR;
 	writel(value, ioaddr + DMA_CONTROL);
 }
+EXPORT_SYMBOL_GPL(dwmac_dma_start_rx);
 
 void dwmac_dma_stop_rx(struct stmmac_priv *priv, void __iomem *ioaddr, u32 chan)
 {
@@ -88,6 +94,7 @@ void dwmac_dma_stop_rx(struct stmmac_priv *priv, void __iomem *ioaddr, u32 chan)
 	value &= ~DMA_CONTROL_SR;
 	writel(value, ioaddr + DMA_CONTROL);
 }
+EXPORT_SYMBOL_GPL(dwmac_dma_stop_rx);
 
 #ifdef DWMAC_DMA_DEBUG
 static void show_tx_process_state(unsigned int status)
@@ -239,6 +246,7 @@ int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr,
 
 	return ret;
 }
+EXPORT_SYMBOL_GPL(dwmac_dma_interrupt);
 
 void dwmac_dma_flush_tx_fifo(void __iomem *ioaddr)
 {
diff --git a/net/ethernet/eth.c b/net/ethernet/eth.c
index 049c3adeb8504..643ab458e25ef 100644
--- a/net/ethernet/eth.c
+++ b/net/ethernet/eth.c
@@ -560,6 +560,7 @@ int nvmem_get_mac_address(struct device *dev, void *addrbuf)
 
 	return 0;
 }
+EXPORT_SYMBOL(nvmem_get_mac_address);
 
 static int fwnode_get_mac_addr(struct fwnode_handle *fwnode,
 			       const char *name, char *addr)
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 11/39] net: fwnode_get_phy_id: consider all compatible strings
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (9 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 10/39] net: stmmac: support of Baikal-BE1000 SoCs GMAC Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 12/39] hwmon: bt1-pvt: access registers via pvt_{readl, writel} helpers Daniil Gnusarev
                   ` (28 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

Commit cf99686072a1b7037a1d782b66037b2b722bf2c9 ("of: mdio:
Refactor of_get_phy_id()") has broken Ethernet on TF307 board
(and possibly other boards based on Baikal-M/T1 SoCs).

That commit replaces `of_get_phy_id` with `fwnode_get_phy_id`.
And `fwnode_get_phy_id` considers only the 1st compatible string
to find out phy_id. This works well for all schema compliant device
trees, since the `compatible` property of PHY nodes is supposed
to be "ethernet-phy-idNNNN.MMMM".

However DTB embedded in TF307 firmware describes PHY like this:

gmac0_phy: ethernet-phy@3 {
  compatible = "micrel,ksz9031", "ethernet-phy-id0022.1620", "ethernet-phy-ieee802.3-c22";
  reg = <0x3>;
};

That is, the 1st compatible string is "micrel,ksz9031". Thus
`fwnode_get_phy_id` is unable to parse phy_id, and
`stmmac_mdio_register` fails. As a result Ethernet driver is
unable to attach to PHY, and can't send/receive anything.

To avoid the problem this patch adjusts `fwnode_get_phy_id`
to consider *all* compatible strings.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-DONTUPSTREAM
X-feature-Baikal-M
---
 drivers/net/phy/phy_device.c | 41 ++++++++++++++++++++++++++----------
 1 file changed, 30 insertions(+), 11 deletions(-)

diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 2e4bff6055e22..5b13f5692830e 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -908,18 +908,37 @@ static int get_phy_c22_id(struct mii_bus *bus, int addr, u32 *phy_id)
 int fwnode_get_phy_id(struct fwnode_handle *fwnode, u32 *phy_id)
 {
 	unsigned int upper, lower;
-	const char *cp;
-	int ret;
-
-	ret = fwnode_property_read_string(fwnode, "compatible", &cp);
-	if (ret)
-		return ret;
-
-	if (sscanf(cp, "ethernet-phy-id%4x.%4x", &upper, &lower) != 2)
-		return -EINVAL;
+	const char **compat;
+	int ret, count, i;
+
+	/* FIXME: where is fwnode_property_for_each_string? */
+	count = fwnode_property_read_string_array(fwnode, "compatible", NULL, 0);
+	if (count < 0)
+		return count;
+	else if (count == 0)
+		return -ENODATA;
+
+	compat = kcalloc(count, sizeof(*compat), GFP_KERNEL);
+	if (!compat)
+		return -ENOMEM;
+	ret = fwnode_property_read_string_array(fwnode, "compatible", compat, count);
+	if (ret < 0)
+		goto out;
 
-	*phy_id = ((upper & GENMASK(15, 0)) << 16) | (lower & GENMASK(15, 0));
-	return 0;
+	ret = -EINVAL;
+	for (i = 0; i < count; i++) {
+		pr_info("%s: considering '%s'\n", __func__, compat[i]);
+		if (sscanf(compat[i], "ethernet-phy-id%4x.%4x", &upper, &lower) != 2)
+			continue;
+		else {
+			*phy_id = ((upper & GENMASK(15, 0)) << 16) | (lower & GENMASK(15, 0));
+			ret = 0;
+			break;
+		}
+	}
+out:
+	kfree(compat);
+	return ret;
 }
 EXPORT_SYMBOL(fwnode_get_phy_id);
 
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 12/39] hwmon: bt1-pvt: access registers via pvt_{readl, writel} helpers
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (10 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 11/39] net: fwnode_get_phy_id: consider all compatible strings Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 13/39] hwmon: bt1-pvt: define pvt_readl/pvt_writel for Baikal-M SoC Daniil Gnusarev
                   ` (27 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

Baikal-M SoC is equipped with PVT sensors too. However on Baikal-M
PVT registers (and clocks) are directly accessible to the secure
world only, so Linux has to call into firmware (ARM-TF) to
read/write registers.

This patch replaces readl/writel with pvt_readl/pvt_writel functions.
No functional changes intended. Subsequent patch will define
pvt_readl and pvt_writel functions for Baikal-M SoC.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 drivers/hwmon/bt1-pvt.c | 86 +++++++++++++++++++++++------------------
 1 file changed, 49 insertions(+), 37 deletions(-)

diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c
index b77ebac2e0cea..334d58ffea553 100644
--- a/drivers/hwmon/bt1-pvt.c
+++ b/drivers/hwmon/bt1-pvt.c
@@ -114,12 +114,24 @@ static const struct polynomial poly_N_to_volt = {
 	}
 };
 
-static inline u32 pvt_update(void __iomem *reg, u32 mask, u32 data)
+
+static inline u32 pvt_readl(struct pvt_hwmon const *pvt, int reg) {
+	return readl(pvt->regs + reg);
+}
+
+static inline u32 pvt_readl_relaxed(struct pvt_hwmon const *pvt, int reg) {
+	return readl_relaxed(pvt->regs + reg);
+}
+
+static inline void pvt_writel(u32 data, struct pvt_hwmon const *pvt, int reg) {
+	writel(data, pvt->regs + reg);
+}
+static inline u32 pvt_update(struct pvt_hwmon *pvt, int reg, u32 mask, u32 data)
 {
 	u32 old;
 
-	old = readl_relaxed(reg);
-	writel((old & ~mask) | (data & mask), reg);
+	old = pvt_readl_relaxed(pvt, reg);
+	pvt_writel((old & ~mask) | (data & mask), pvt, reg);
 
 	return old & mask;
 }
@@ -137,8 +149,8 @@ static inline void pvt_set_mode(struct pvt_hwmon *pvt, u32 mode)
 
 	mode = FIELD_PREP(PVT_CTRL_MODE_MASK, mode);
 
-	old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
-	pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_MODE_MASK | PVT_CTRL_EN,
+	old = pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, 0);
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_MODE_MASK | PVT_CTRL_EN,
 		   mode | old);
 }
 
@@ -155,8 +167,8 @@ static inline void pvt_set_trim(struct pvt_hwmon *pvt, u32 trim)
 
 	trim = FIELD_PREP(PVT_CTRL_TRIM_MASK, trim);
 
-	old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
-	pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_TRIM_MASK | PVT_CTRL_EN,
+	old = pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, 0);
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_TRIM_MASK | PVT_CTRL_EN,
 		   trim | old);
 }
 
@@ -164,9 +176,9 @@ static inline void pvt_set_tout(struct pvt_hwmon *pvt, u32 tout)
 {
 	u32 old;
 
-	old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
-	writel(tout, pvt->regs + PVT_TTIMEOUT);
-	pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, old);
+	old = pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, 0);
+	pvt_writel(tout, pvt, PVT_TTIMEOUT);
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, old);
 }
 
 /*
@@ -213,7 +225,7 @@ static irqreturn_t pvt_soft_isr(int irq, void *data)
 	 * status before the next conversion happens. Threshold events will be
 	 * handled a bit later.
 	 */
-	thres_sts = readl(pvt->regs + PVT_RAW_INTR_STAT);
+	thres_sts = pvt_readl(pvt, PVT_RAW_INTR_STAT);
 
 	/*
 	 * Then lets recharge the PVT interface with the next sampling mode.
@@ -236,14 +248,14 @@ static irqreturn_t pvt_soft_isr(int irq, void *data)
 	 */
 	mutex_lock(&pvt->iface_mtx);
 
-	old = pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID,
+	old = pvt_update(pvt, PVT_INTR_MASK, PVT_INTR_DVALID,
 			 PVT_INTR_DVALID);
 
-	val = readl(pvt->regs + PVT_DATA);
+	val = pvt_readl(pvt, PVT_DATA);
 
 	pvt_set_mode(pvt, pvt_info[pvt->sensor].mode);
 
-	pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, old);
+	pvt_update(pvt, PVT_INTR_MASK, PVT_INTR_DVALID, old);
 
 	mutex_unlock(&pvt->iface_mtx);
 
@@ -313,7 +325,7 @@ static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
 	u32 data;
 
 	/* No need in serialization, since it is just read from MMIO. */
-	data = readl(pvt->regs + pvt_info[type].thres_base);
+	data = pvt_readl(pvt, pvt_info[type].thres_base);
 
 	if (is_low)
 		data = FIELD_GET(PVT_THRES_LO_MASK, data);
@@ -348,7 +360,7 @@ static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
 		return ret;
 
 	/* Make sure the upper and lower ranges don't intersect. */
-	limit = readl(pvt->regs + pvt_info[type].thres_base);
+	limit = pvt_readl(pvt, pvt_info[type].thres_base);
 	if (is_low) {
 		limit = FIELD_GET(PVT_THRES_HI_MASK, limit);
 		data = clamp_val(data, PVT_DATA_MIN, limit);
@@ -361,7 +373,7 @@ static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
 		mask = PVT_THRES_HI_MASK;
 	}
 
-	pvt_update(pvt->regs + pvt_info[type].thres_base, mask, data);
+	pvt_update(pvt, pvt_info[type].thres_base, mask, data);
 
 	mutex_unlock(&pvt->iface_mtx);
 
@@ -415,14 +427,14 @@ static irqreturn_t pvt_hard_isr(int irq, void *data)
 	 * Mask the DVALID interrupt so after exiting from the handler a
 	 * repeated conversion wouldn't happen.
 	 */
-	pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID,
+	pvt_update(pvt, PVT_INTR_MASK, PVT_INTR_DVALID,
 		   PVT_INTR_DVALID);
 
 	/*
 	 * Nothing special for alarm-less driver. Just read the data, update
 	 * the cache and notify a waiter of this event.
 	 */
-	val = readl(pvt->regs + PVT_DATA);
+	val = pvt_readl(pvt, PVT_DATA);
 	if (!(val & PVT_DATA_VALID)) {
 		dev_err(pvt->dev, "Got IRQ when data isn't valid\n");
 		return IRQ_HANDLED;
@@ -474,8 +486,8 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
 	 * Unmask the DVALID interrupt and enable the sensors conversions.
 	 * Do the reverse procedure when conversion is done.
 	 */
-	pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, 0);
-	pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
+	pvt_update(pvt, PVT_INTR_MASK, PVT_INTR_DVALID, 0);
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
 
 	/*
 	 * Wait with timeout since in case if the sensor is suddenly powered
@@ -486,8 +498,8 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
 	timeout = 2 * usecs_to_jiffies(ktime_to_us(pvt->timeout));
 	ret = wait_for_completion_timeout(&cache->conversion, timeout);
 
-	pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
-	pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID,
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, 0);
+	pvt_update(pvt, PVT_INTR_MASK, PVT_INTR_DVALID,
 		   PVT_INTR_DVALID);
 
 	data = READ_ONCE(cache->data);
@@ -613,7 +625,7 @@ static int pvt_read_trim(struct pvt_hwmon *pvt, long *val)
 {
 	u32 data;
 
-	data = readl(pvt->regs + PVT_CTRL);
+	data = pvt_readl(pvt, PVT_CTRL);
 	*val = FIELD_GET(PVT_CTRL_TRIM_MASK, data) * PVT_TRIM_STEP;
 
 	return 0;
@@ -950,21 +962,21 @@ static int pvt_check_pwr(struct pvt_hwmon *pvt)
 	 * conversion. In the later case alas we won't be able to detect the
 	 * problem.
 	 */
-	pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL);
-	pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
+	pvt_update(pvt, PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL);
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
 	pvt_set_tout(pvt, 0);
-	readl(pvt->regs + PVT_DATA);
+	pvt_readl(pvt, PVT_DATA);
 
 	tout = PVT_TOUT_MIN / NSEC_PER_USEC;
 	usleep_range(tout, 2 * tout);
 
-	data = readl(pvt->regs + PVT_DATA);
+	data = pvt_readl(pvt, PVT_DATA);
 	if (!(data & PVT_DATA_VALID)) {
 		ret = -ENODEV;
 		dev_err(pvt->dev, "Sensor is powered down\n");
 	}
 
-	pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, 0);
 
 	return ret;
 }
@@ -985,10 +997,10 @@ static int pvt_init_iface(struct pvt_hwmon *pvt)
 	 * accidentally have ISR executed before the driver data is fully
 	 * initialized. Clear the IRQ status as well.
 	 */
-	pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL);
-	pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
-	readl(pvt->regs + PVT_CLR_INTR);
-	readl(pvt->regs + PVT_DATA);
+	pvt_update(pvt, PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL);
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, 0);
+	pvt_readl(pvt, PVT_CLR_INTR);
+	pvt_readl(pvt, PVT_DATA);
 
 	/* Setup default sensor mode, timeout and temperature trim. */
 	pvt_set_mode(pvt, pvt_info[pvt->sensor].mode);
@@ -1072,8 +1084,8 @@ static void pvt_disable_iface(void *data)
 	struct pvt_hwmon *pvt = data;
 
 	mutex_lock(&pvt->iface_mtx);
-	pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
-	pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID,
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, 0);
+	pvt_update(pvt, PVT_INTR_MASK, PVT_INTR_DVALID,
 		   PVT_INTR_DVALID);
 	mutex_unlock(&pvt->iface_mtx);
 }
@@ -1095,8 +1107,8 @@ static int pvt_enable_iface(struct pvt_hwmon *pvt)
 	 * which theoretically may cause races.
 	 */
 	mutex_lock(&pvt->iface_mtx);
-	pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, 0);
-	pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
+	pvt_update(pvt, PVT_INTR_MASK, PVT_INTR_DVALID, 0);
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
 	mutex_unlock(&pvt->iface_mtx);
 
 	return 0;
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 13/39] hwmon: bt1-pvt: define pvt_readl/pvt_writel for Baikal-M SoC
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (11 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 12/39] hwmon: bt1-pvt: access registers via pvt_{readl, writel} helpers Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 14/39] hwmon: bt1-pvt: adjusted probing " Daniil Gnusarev
                   ` (26 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

On Baikal-M Linux has to call into firmware (ARM-TF) to access
PVT registers.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 drivers/hwmon/bt1-pvt.c | 23 +++++++++++++++++++++++
 drivers/hwmon/bt1-pvt.h |  8 ++++++++
 2 files changed, 31 insertions(+)

diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c
index 334d58ffea553..87873548ef7da 100644
--- a/drivers/hwmon/bt1-pvt.c
+++ b/drivers/hwmon/bt1-pvt.c
@@ -30,6 +30,9 @@
 #include <linux/seqlock.h>
 #include <linux/sysfs.h>
 #include <linux/types.h>
+#ifdef CONFIG_ARM64
+#include <linux/arm-smccc.h>
+#endif
 
 #include "bt1-pvt.h"
 
@@ -115,6 +118,7 @@ static const struct polynomial poly_N_to_volt = {
 };
 
 
+#ifdef BT1_PVT_DIRECT_REG_ACCESS
 static inline u32 pvt_readl(struct pvt_hwmon const *pvt, int reg) {
 	return readl(pvt->regs + reg);
 }
@@ -126,6 +130,25 @@ static inline u32 pvt_readl_relaxed(struct pvt_hwmon const *pvt, int reg) {
 static inline void pvt_writel(u32 data, struct pvt_hwmon const *pvt, int reg) {
 	writel(data, pvt->regs + reg);
 }
+#else
+static inline u32 pvt_readl(struct pvt_hwmon const *pvt, int reg) {
+	struct arm_smccc_res res;
+	arm_smccc_smc(BAIKAL_SMC_PVT_ID, PVT_READ, pvt->pvt_id, reg,
+		      0, 0, 0, 0, &res);
+	return res.a0;
+}
+
+static inline u32 pvt_readl_relaxed(struct pvt_hwmon const *pvt, int reg) {
+	return pvt_readl(pvt, reg);
+}
+
+static inline void pvt_writel(u32 data, struct pvt_hwmon const *pvt, int reg) {
+	struct arm_smccc_res res;
+	arm_smccc_smc(BAIKAL_SMC_PVT_ID, PVT_WRITE, pvt->pvt_id, reg,
+		      data, 0, 0, 0, &res);
+}
+#endif
+
 static inline u32 pvt_update(struct pvt_hwmon *pvt, int reg, u32 mask, u32 data)
 {
 	u32 old;
diff --git a/drivers/hwmon/bt1-pvt.h b/drivers/hwmon/bt1-pvt.h
index 93b8dd5e7c944..0cea95b01c13c 100644
--- a/drivers/hwmon/bt1-pvt.h
+++ b/drivers/hwmon/bt1-pvt.h
@@ -101,6 +101,13 @@
 # define PVT_TOUT_DEF		0
 #endif
 
+#define BAIKAL_SMC_PVT_ID 0x82000001
+#define PVT_READ 0
+#define PVT_WRITE 1
+#ifndef CONFIG_ARM64
+#define BT1_PVT_DIRECT_REG_ACCESS
+#endif
+
 /*
  * enum pvt_sensor_type - Baikal-T1 PVT sensor types (correspond to each PVT
  *			  sampling mode)
@@ -217,6 +224,7 @@ struct pvt_hwmon {
 	enum pvt_sensor_type sensor;
 	struct pvt_cache cache[PVT_SENSORS_NUM];
 	ktime_t timeout;
+	int pvt_id;
 };
 
 /*
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 14/39] hwmon: bt1-pvt: adjusted probing for Baikal-M SoC
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (12 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 13/39] hwmon: bt1-pvt: define pvt_readl/pvt_writel for Baikal-M SoC Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 15/39] hwmon: bt1-pvt: added compatible baikal, pvt Daniil Gnusarev
                   ` (25 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

PVT registers and clocks are managed by the firmware (ARM-TF) and
can't be accessed by Linux directly. Therefore skip enabling
(disabling) clocks and ioremapping registers on Baikal-M.

Also a sensor is identified by special `pvt_id' instead of registers
base address. pvt_id is initialized from the device tree.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 drivers/hwmon/Kconfig   |  5 +++--
 drivers/hwmon/bt1-pvt.c | 32 ++++++++++++++++++++++++++------
 2 files changed, 29 insertions(+), 8 deletions(-)

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index ec38c88921589..af5d424357037 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -428,10 +428,11 @@ config SENSORS_ATXP1
 
 config SENSORS_BT1_PVT
 	tristate "Baikal-T1 Process, Voltage, Temperature sensor driver"
-	depends on MIPS_BAIKAL_T1 || COMPILE_TEST
+	depends on MIPS_BAIKAL_T1 || ARCH_BAIKAL || COMPILE_TEST
+	default m if MIPS_BAIKAL_T1 || ARCH_BAIKAL
 	select POLYNOMIAL
 	help
-	  If you say yes here you get support for Baikal-T1 PVT sensor
+	  If you say yes here you get support for Baikal-M or Baikal-T1 PVT sensor
 	  embedded into the SoC.
 
 	  This driver can also be built as a module. If so, the module will be
diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c
index 87873548ef7da..5fc3babb57a90 100644
--- a/drivers/hwmon/bt1-pvt.c
+++ b/drivers/hwmon/bt1-pvt.c
@@ -925,29 +925,39 @@ static struct pvt_hwmon *pvt_create_data(struct platform_device *pdev)
 
 static int pvt_request_regs(struct pvt_hwmon *pvt)
 {
+	int err = 0;
+#ifdef BT1_PVT_DIRECT_REG_ACCESS
 	struct platform_device *pdev = to_platform_device(pvt->dev);
-
 	pvt->regs = devm_platform_ioremap_resource(pdev, 0);
 	if (IS_ERR(pvt->regs))
 		return PTR_ERR(pvt->regs);
+#else
+	err = of_property_read_u32(pvt->dev->of_node, "pvt_id", &(pvt->pvt_id));
+	if (err) {
+		dev_err(pvt->dev, "couldn't find pvt_id\n");
+	}
+#endif
 
-	return 0;
+	return err;
 }
 
+#ifdef BT1_PVT_DIRECT_REG_ACCESS
 static void pvt_disable_clks(void *data)
 {
 	struct pvt_hwmon *pvt = data;
 
 	clk_bulk_disable_unprepare(PVT_CLOCK_NUM, pvt->clks);
 }
+#endif
 
 static int pvt_request_clks(struct pvt_hwmon *pvt)
 {
-	int ret;
+	int ret = 0;
 
 	pvt->clks[PVT_CLOCK_APB].id = "pclk";
 	pvt->clks[PVT_CLOCK_REF].id = "ref";
 
+#ifdef BT1_PVT_DIRECT_REG_ACCESS
 	ret = devm_clk_bulk_get(pvt->dev, PVT_CLOCK_NUM, pvt->clks);
 	if (ret) {
 		dev_err(pvt->dev, "Couldn't get PVT clocks descriptors\n");
@@ -965,8 +975,11 @@ static int pvt_request_clks(struct pvt_hwmon *pvt)
 		dev_err(pvt->dev, "Can't add PVT clocks disable action\n");
 		return ret;
 	}
-
-	return 0;
+#else
+	pvt->clks[PVT_CLOCK_APB].clk = NULL;
+	pvt->clks[PVT_CLOCK_REF].clk = NULL;
+#endif
+	return ret;
 }
 
 static int pvt_check_pwr(struct pvt_hwmon *pvt)
@@ -1006,14 +1019,17 @@ static int pvt_check_pwr(struct pvt_hwmon *pvt)
 
 static int pvt_init_iface(struct pvt_hwmon *pvt)
 {
-	unsigned long rate;
 	u32 trim, temp;
 
+#ifdef BT1_PVT_DIRECT_REG_ACCESS
+	unsigned long rate;
+
 	rate = clk_get_rate(pvt->clks[PVT_CLOCK_REF].clk);
 	if (!rate) {
 		dev_err(pvt->dev, "Invalid reference clock rate\n");
 		return -ENODEV;
 	}
+#endif
 
 	/*
 	 * Make sure all interrupts and controller are disabled so not to
@@ -1042,6 +1058,7 @@ static int pvt_init_iface(struct pvt_hwmon *pvt)
 	 * polled. In that case the formulae will look a bit different:
 	 *   Ttotal = 5 * (N / Fclk + Tmin)
 	 */
+#if defined(BT1_PVT_DIRECT_REG_ACCESS)
 #if defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
 	pvt->timeout = ktime_set(PVT_SENSORS_NUM * PVT_TOUT_DEF, 0);
 	pvt->timeout = ktime_divns(pvt->timeout, rate);
@@ -1051,6 +1068,9 @@ static int pvt_init_iface(struct pvt_hwmon *pvt)
 	pvt->timeout = ktime_divns(pvt->timeout, rate);
 	pvt->timeout = ktime_add_ns(pvt->timeout, PVT_TOUT_MIN);
 #endif
+#else
+	pvt->timeout = ktime_set(0, PVT_TOUT_MIN * PVT_SENSORS_NUM);
+#endif
 
 	trim = PVT_TRIM_DEF;
 	if (!of_property_read_u32(pvt->dev->of_node,
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 15/39] hwmon: bt1-pvt: added compatible baikal, pvt
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (13 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 14/39] hwmon: bt1-pvt: adjusted probing " Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 16/39] PVT: support register addressing with new firmware Daniil Gnusarev
                   ` (24 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

So the driver will be loaded on existing Baikal-M based boards.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 drivers/hwmon/bt1-pvt.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c
index 5fc3babb57a90..0299df1ac325f 100644
--- a/drivers/hwmon/bt1-pvt.c
+++ b/drivers/hwmon/bt1-pvt.c
@@ -1208,6 +1208,8 @@ static int pvt_probe(struct platform_device *pdev)
 
 static const struct of_device_id pvt_of_match[] = {
 	{ .compatible = "baikal,bt1-pvt" },
+	{ .compatible = "baikal,pvt" },
+	{ .compatible = "baikal,bm1000-pvt"},
 	{ }
 };
 MODULE_DEVICE_TABLE(of, pvt_of_match);
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 16/39] PVT: support register addressing with new firmware
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (14 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 15/39] hwmon: bt1-pvt: added compatible baikal, pvt Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 17/39] drm: add Baikal-M SoC video display unit driver Daniil Gnusarev
                   ` (23 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

In the latest SDKs, the addressing of PVT registers has changed.
Instead of an index, a request is made specifying the base address.
If the dts does not have the specified pvt_id, then we access the
registers using the new scheme

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 drivers/hwmon/bt1-pvt.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c
index 0299df1ac325f..71a1c0dea484f 100644
--- a/drivers/hwmon/bt1-pvt.c
+++ b/drivers/hwmon/bt1-pvt.c
@@ -934,7 +934,16 @@ static int pvt_request_regs(struct pvt_hwmon *pvt)
 #else
 	err = of_property_read_u32(pvt->dev->of_node, "pvt_id", &(pvt->pvt_id));
 	if (err) {
-		dev_err(pvt->dev, "couldn't find pvt_id\n");
+		struct resource *res;
+
+		dev_warn(pvt->dev, "couldn't find pvt_id, assume direct addressing\n");
+		res = platform_get_resource(to_platform_device(pvt->dev), IORESOURCE_MEM, 0);
+		if (!res)
+			dev_err(pvt->dev, "couldn't find base address\n");
+			else {
+				pvt->pvt_id = res->start;
+				err = 0;
+			}
 	}
 #endif
 
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 17/39] drm: add Baikal-M SoC video display unit driver
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (15 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 16/39] PVT: support register addressing with new firmware Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:01 ` [d-kernel] [PATCH 18/39] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a Daniil Gnusarev
                   ` (22 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

Add VDU driver for Baikal BE-M1000 with firmware
from SDK version ARM64-2403-6.6

Co-developed-by: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
Bugfixes by Alexey Sheplyakov <asheplyakov@altlinux.org>
Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 drivers/gpu/drm/Kconfig                       |   2 +
 drivers/gpu/drm/Makefile                      |   1 +
 drivers/gpu/drm/baikal/Kconfig                |  12 +
 drivers/gpu/drm/baikal/Makefile               |  11 +
 drivers/gpu/drm/baikal/baikal-hdmi.c          | 123 +++++
 drivers/gpu/drm/baikal/baikal_vdu_backlight.c | 261 +++++++++
 drivers/gpu/drm/baikal/baikal_vdu_crtc.c      | 440 +++++++++++++++
 drivers/gpu/drm/baikal/baikal_vdu_debugfs.c   |  95 ++++
 drivers/gpu/drm/baikal/baikal_vdu_drm.h       | 114 ++++
 drivers/gpu/drm/baikal/baikal_vdu_drv.c       | 516 ++++++++++++++++++
 drivers/gpu/drm/baikal/baikal_vdu_panel.c     | 193 +++++++
 drivers/gpu/drm/baikal/baikal_vdu_plane.c     | 138 +++++
 drivers/gpu/drm/baikal/baikal_vdu_regs.h      | 137 +++++
 drivers/gpu/drm/bridge/Kconfig                |   7 +
 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c     |   9 +
 drivers/gpu/drm/drm_panel.c                   |  37 ++
 include/drm/bridge/dw_hdmi.h                  |   2 +
 include/drm/drm_panel.h                       |   3 +
 18 files changed, 2101 insertions(+)
 create mode 100644 drivers/gpu/drm/baikal/Kconfig
 create mode 100644 drivers/gpu/drm/baikal/Makefile
 create mode 100644 drivers/gpu/drm/baikal/baikal-hdmi.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_backlight.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_crtc.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_debugfs.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_drm.h
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_drv.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_panel.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_plane.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_regs.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index ec4abf9ff47b5..946fb9fac5332 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -261,6 +261,8 @@ source "drivers/gpu/drm/i2c/Kconfig"
 
 source "drivers/gpu/drm/arm/Kconfig"
 
+source "drivers/gpu/drm/baikal/Kconfig"
+
 source "drivers/gpu/drm/radeon/Kconfig"
 
 source "drivers/gpu/drm/amd/amdgpu/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 215e78e791250..f754f002c141d 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -197,4 +197,5 @@ obj-y			+= gud/
 obj-$(CONFIG_DRM_HYPERV) += hyperv/
 obj-y			+= solomon/
 obj-$(CONFIG_DRM_SPRD) += sprd/
+obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal/
 obj-$(CONFIG_DRM_LOONGSON) += loongson/
diff --git a/drivers/gpu/drm/baikal/Kconfig b/drivers/gpu/drm/baikal/Kconfig
new file mode 100644
index 0000000000000..4a18055cd22fa
--- /dev/null
+++ b/drivers/gpu/drm/baikal/Kconfig
@@ -0,0 +1,12 @@
+config DRM_BAIKAL_VDU
+	tristate "DRM Support for Baikal-M VDU"
+	depends on DRM
+	depends on ARM || ARM64 || COMPILE_TEST
+	depends on COMMON_CLK
+	select DRM_KMS_HELPER
+	select DRM_GEM_DMA_HELPER
+	select DRM_PANEL
+	select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
+	help
+	  Choose this option for DRM support for the Baikal-M Video Display Unit (VDU).
+	  If M is selected the module will be called baikal_vdu_drm.
diff --git a/drivers/gpu/drm/baikal/Makefile b/drivers/gpu/drm/baikal/Makefile
new file mode 100644
index 0000000000000..321d3be96b814
--- /dev/null
+++ b/drivers/gpu/drm/baikal/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+baikal_vdu_drm-y +=	baikal_vdu_backlight.o \
+		baikal_vdu_crtc.o \
+		baikal_vdu_drv.o \
+		baikal_vdu_panel.o \
+		baikal_vdu_plane.o
+
+baikal_vdu_drm-$(CONFIG_DEBUG_FS) += baikal_vdu_debugfs.o
+
+obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal_vdu_drm.o
+obj-$(CONFIG_DRM_BAIKAL_HDMI) += baikal-hdmi.o
diff --git a/drivers/gpu/drm/baikal/baikal-hdmi.c b/drivers/gpu/drm/baikal/baikal-hdmi.c
new file mode 100644
index 0000000000000..7d3d0f0c7358e
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal-hdmi.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Baikal Electronics BE-M1000 DesignWare HDMI 2.0 Tx PHY support driver
+ *
+ * Copyright (C) 2019-2022 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <drm/drm_modes.h>
+
+#include <drm/bridge/dw_hdmi.h>
+
+#include "baikal_vdu_drm.h"
+
+int fixed_clock = 0;
+int max_clock = 0;
+
+static const struct dw_hdmi_mpll_config baikal_hdmi_mpll_cfg[] = {
+	/* pixelclk      opmode  gmp         */
+	{  44900000, { { 0x00b3, 0x0000 }, }, },
+	{  90000000, { { 0x0072, 0x0001 }, }, },
+	{ 182750000, { { 0x0051, 0x0002 }, }, },
+	{ 340000000, { { 0x0040, 0x0003 }, }, },
+	{ 594000000, { { 0x1a40, 0x0003 }, }, },
+	{ ~0UL,      { { 0x0000, 0x0000 }, }, }
+};
+
+static const struct dw_hdmi_curr_ctrl baikal_hdmi_cur_ctr[] = {
+	/* pixelclk    current   */
+	{  44900000, { 0x0000, }, },
+	{  90000000, { 0x0008, }, },
+	{ 182750000, { 0x001b, }, },
+	{ 340000000, { 0x0036, }, },
+	{ 594000000, { 0x003f, }, },
+	{ ~0UL,      { 0x0000, }, }
+};
+
+static const struct dw_hdmi_phy_config baikal_hdmi_phy_cfg[] = {
+	/* pixelclk  symbol  term    vlev */
+	{ 148250000, 0x8009, 0x0004, 0x0232},
+	{ 218250000, 0x8009, 0x0004, 0x0230},
+	{ 288000000, 0x8009, 0x0004, 0x0273},
+	{ 340000000, 0x8029, 0x0004, 0x0273},
+	{ 594000000, 0x8039, 0x0004, 0x014a},
+	{ ~0UL,      0x0000, 0x0000, 0x0000}
+};
+
+static enum drm_mode_status
+baikal_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
+                const struct drm_display_info *info,
+                const struct drm_display_mode *mode)
+{
+	if (mode->clock < 13500)
+		return MODE_CLOCK_LOW;
+	if (mode->clock >= 340000)
+		return MODE_CLOCK_HIGH;
+	if (fixed_clock && mode->clock != fixed_clock)
+		return MODE_BAD;
+	if (max_clock && mode->clock > max_clock)
+		return MODE_BAD;
+
+	return MODE_OK;
+}
+
+static struct dw_hdmi_plat_data baikal_dw_hdmi_plat_data = {
+	.mpll_cfg   = baikal_hdmi_mpll_cfg,
+	.cur_ctr    = baikal_hdmi_cur_ctr,
+	.phy_config = baikal_hdmi_phy_cfg,
+	.mode_valid = baikal_hdmi_mode_valid,
+};
+
+static int baikal_dw_hdmi_probe(struct platform_device *pdev)
+{
+	struct baikal_hdmi_bridge *priv;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, priv);
+
+	priv->hdmi = dw_hdmi_probe(pdev, &baikal_dw_hdmi_plat_data);
+	if (IS_ERR(priv->hdmi))
+		return PTR_ERR(priv->hdmi);
+
+	priv->bridge = dw_hdmi_get_bridge(priv->hdmi);
+	return 0;
+}
+
+static int baikal_dw_hdmi_remove(struct platform_device *pdev)
+{
+	struct baikal_hdmi_bridge *priv = platform_get_drvdata(pdev);
+
+	dw_hdmi_remove(priv->hdmi);
+	return 0;
+}
+
+static const struct of_device_id baikal_dw_hdmi_of_table[] = {
+	{ .compatible = "baikal,hdmi" },
+	{ /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, baikal_dw_hdmi_of_table);
+
+static struct platform_driver baikal_dw_hdmi_platform_driver = {
+	.probe		= baikal_dw_hdmi_probe,
+	.remove		= baikal_dw_hdmi_remove,
+	.driver		= {
+		.name	= "baikal-dw-hdmi",
+		.of_match_table = baikal_dw_hdmi_of_table,
+	},
+};
+
+module_param(fixed_clock, int, 0644);
+module_param(max_clock, int, 0644);
+
+module_platform_driver(baikal_dw_hdmi_platform_driver);
+
+MODULE_AUTHOR("Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal BE-M1000 SoC DesignWare HDMI 2.0 Tx + Gen2 PHY Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_backlight.c b/drivers/gpu/drm/baikal/baikal_vdu_backlight.c
new file mode 100644
index 0000000000000..f58568a755c9c
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_backlight.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+/**
+ * baikal_vdu_backlight.c
+ * Implementation of backlight functions for
+ * Baikal Electronics BE-M1000 SoC's VDU
+ */
+
+#include <linux/clk.h>
+#include <linux/input.h>
+#include <linux/module.h>
+
+#include <drm/drm_atomic_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+#define BAIKAL_VDU_MIN_BRIGHTNESS	0
+#define BAIKAL_VDU_DEFAULT_BRIGHTNESS	50
+#define BAIKAL_VDU_BRIGHTNESS_STEP	5
+#define BAIKAL_VDU_DEFAULT_PWM_FREQ	10000
+
+static int baikal_vdu_backlight_update_status(struct backlight_device *bl_dev)
+{
+	struct baikal_vdu_private *priv = bl_get_data(bl_dev);
+	int brightness_on = 1;
+	int brightness = bl_dev->props.brightness;
+	u8 pwmdc;
+
+	if (bl_dev->props.power != FB_BLANK_UNBLANK ||
+	    bl_dev->props.fb_blank != FB_BLANK_UNBLANK ||
+	    bl_dev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) {
+		brightness_on = 0;
+		brightness = priv->min_brightness;
+	}
+
+	if (priv->enable_gpio)
+		gpiod_set_value_cansleep(priv->enable_gpio, brightness_on);
+
+	pwmdc = brightness ? ((brightness << 6) / 25 - 1) : 0;
+
+	writel(pwmdc, priv->regs + PWMDCR);
+
+	return 0;
+}
+
+static const struct backlight_ops baikal_vdu_backlight_ops = {
+	.options        = BL_CORE_SUSPENDRESUME,
+	.update_status	= baikal_vdu_backlight_update_status,
+};
+
+static void baikal_vdu_input_event(struct input_handle *handle,
+				   unsigned int type, unsigned int code,
+				   int value)
+{
+	struct baikal_vdu_private *priv = handle->private;
+	int brightness;
+
+	if (type != EV_KEY || value == 0)
+		return;
+
+	switch (code) {
+	case KEY_BRIGHTNESSDOWN:
+		brightness = priv->bl_dev->props.brightness -
+			     priv->brightness_step;
+		if (brightness >= priv->min_brightness)
+			backlight_device_set_brightness(priv->bl_dev,
+							brightness);
+		break;
+
+	case KEY_BRIGHTNESSUP:
+		brightness = priv->bl_dev->props.brightness +
+			     priv->brightness_step;
+		backlight_device_set_brightness(priv->bl_dev, brightness);
+		break;
+
+	case KEY_BRIGHTNESS_TOGGLE:
+		priv->brightness_on = !priv->brightness_on;
+		if (priv->brightness_on)
+			backlight_enable(priv->bl_dev);
+		else
+			backlight_disable(priv->bl_dev);
+		break;
+
+	default:
+		return;
+	}
+
+	backlight_force_update(priv->bl_dev, BACKLIGHT_UPDATE_HOTKEY);
+}
+
+static int baikal_vdu_input_connect(struct input_handler *handler,
+				    struct input_dev *dev,
+				    const struct input_device_id *id)
+{
+	struct input_handle *handle;
+	int ret;
+
+	handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+	if (!handle)
+		return -ENOMEM;
+
+	handle->private = handler->private;
+	handle->name = KBUILD_MODNAME;
+	handle->dev = dev;
+	handle->handler = handler;
+
+	ret = input_register_handle(handle);
+	if (ret)
+		goto err_free_handle;
+
+	ret = input_open_device(handle);
+	if (ret)
+		goto err_unregister_handle;
+
+	return 0;
+
+err_unregister_handle:
+	input_unregister_handle(handle);
+err_free_handle:
+	kfree(handle);
+	return ret;
+}
+
+static void baikal_vdu_input_disconnect(struct input_handle *handle)
+{
+	input_close_device(handle);
+	input_unregister_handle(handle);
+	kfree(handle);
+}
+
+static const struct input_device_id baikal_vdu_input_ids[] = {
+	{
+		.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+		.evbit = { BIT_MASK(EV_KEY) },
+	},
+
+	{ },    /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(input, baikal_vdu_input_ids);
+
+int baikal_vdu_backlight_create(struct drm_device *drm)
+{
+	struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(drm);
+	struct baikal_vdu_private *priv = &crossbar->lvds;
+	struct device *dev = drm->dev;
+	struct backlight_properties props;
+	struct input_handler *handler;
+	struct fwnode_handle *node;
+	u32 min_brightness = BAIKAL_VDU_MIN_BRIGHTNESS;
+	u32 dfl_brightness = BAIKAL_VDU_DEFAULT_BRIGHTNESS;
+	u32 brightness_step = BAIKAL_VDU_BRIGHTNESS_STEP;
+	u32 pwm_frequency = 0;
+	int ret = 0;
+	unsigned long rate;
+	unsigned int pwmfr = 0;
+
+	priv->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_ASIS);
+	if (IS_ERR(priv->enable_gpio)) {
+		dev_warn(dev, "failed to get ENABLE GPIO\n");
+		priv->enable_gpio = NULL;
+	}
+
+	if (priv->enable_gpio && gpiod_get_direction(priv->enable_gpio) != 0)
+		gpiod_direction_output(priv->enable_gpio, 1);
+
+	node = fwnode_get_named_child_node(dev->fwnode, is_of_node(dev->fwnode) ?
+					   "backlight" : "BCKL");
+	if (!node)
+		return 0;
+
+	fwnode_property_read_u32(node, "min-brightness-level", &min_brightness);
+	fwnode_property_read_u32(node, "default-brightness-level", &dfl_brightness);
+	fwnode_property_read_u32(node, "brightness-level-step", &brightness_step);
+	fwnode_property_read_u32(node, "pwm-frequency", &pwm_frequency);
+
+	if (pwm_frequency == 0) {
+		dev_warn(dev, "using default PWM frequency %u\n",
+			 BAIKAL_VDU_DEFAULT_PWM_FREQ);
+		pwm_frequency = BAIKAL_VDU_DEFAULT_PWM_FREQ;
+	}
+
+	memset(&props, 0, sizeof(props));
+	props.max_brightness = 100;
+	props.type = BACKLIGHT_RAW;
+	props.scale = BACKLIGHT_SCALE_LINEAR;
+
+	if (min_brightness > props.max_brightness) {
+		dev_warn(dev, "invalid min brightness level: %u, using %u\n",
+			 min_brightness, props.max_brightness);
+		min_brightness = props.max_brightness;
+	}
+
+	if (dfl_brightness > props.max_brightness ||
+	    dfl_brightness < min_brightness) {
+		dev_warn(dev,
+			 "invalid default brightness level: %u, using %u\n",
+			 dfl_brightness, props.max_brightness);
+		dfl_brightness = props.max_brightness;
+	}
+
+	priv->min_brightness = min_brightness;
+	priv->brightness_step = brightness_step;
+	priv->brightness_on = true;
+
+	props.brightness = dfl_brightness;
+	props.power = FB_BLANK_UNBLANK;
+
+	priv->bl_dev =
+		devm_backlight_device_register(dev, dev_name(dev), dev, priv,
+					       &baikal_vdu_backlight_ops,
+					       &props);
+	if (IS_ERR(priv->bl_dev)) {
+		dev_err(dev, "failed to register backlight device\n");
+		ret = PTR_ERR(priv->bl_dev);
+		priv->bl_dev = NULL;
+		goto out;
+	}
+
+	handler = devm_kzalloc(dev, sizeof(*handler), GFP_KERNEL);
+	if (!handler) {
+		dev_err(dev, "failed to allocate input handler\n");
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	handler->private = priv;
+	handler->event = baikal_vdu_input_event;
+	handler->connect = baikal_vdu_input_connect;
+	handler->disconnect = baikal_vdu_input_disconnect;
+	handler->name = KBUILD_MODNAME;
+	handler->id_table = baikal_vdu_input_ids;
+
+	ret = input_register_handler(handler);
+	if (ret) {
+		dev_err(dev, "failed to register input handler\n");
+		goto out;
+	}
+
+	/* Hold PWM Clock Domain Reset, disable clocking */
+	writel(0, priv->regs + PWMFR);
+
+	rate = baikal_vdu_crtc_get_rate(priv);
+	pwmfr |= PWMFR_PWMFCD(rate / pwm_frequency - 1) | PWMFR_PWMFCI;
+	writel(pwmfr, priv->regs + PWMFR);
+
+	/* Release PWM Clock Domain Reset, enable clocking */
+	writel(pwmfr | PWMFR_PWMPCR | PWMFR_PWMFCE, priv->regs + PWMFR);
+
+	backlight_update_status(priv->bl_dev);
+out:
+	fwnode_handle_put(node);
+	return ret;
+}
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_crtc.c b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c
new file mode 100644
index 0000000000000..50327813c04b3
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+/**
+ * baikal_vdu_crtc.c
+ * Implementation of the CRTC functions for Baikal Electronics BE-M1000 VDU driver
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/dma-buf.h>
+#include <linux/shmem_fs.h>
+#include <linux/version.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_vblank.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+irqreturn_t baikal_vdu_irq(int irq, void *data)
+{
+	struct baikal_vdu_private *priv = data;
+	irqreturn_t status = IRQ_NONE;
+	u32 raw_stat;
+	u32 irq_stat;
+
+	priv->counters[0]++;
+	irq_stat = readl(priv->regs + IVR);
+	raw_stat = readl(priv->regs + ISR);
+
+	if (raw_stat & INTR_UFU) {
+		priv->counters[4]++;
+		status = IRQ_HANDLED;
+	}
+
+	if (raw_stat & INTR_IFO) {
+		priv->counters[5]++;
+		status = IRQ_HANDLED;
+	}
+
+	if (raw_stat & INTR_OFU) {
+		priv->counters[6]++;
+		status = IRQ_HANDLED;
+	}
+
+	if (irq_stat & INTR_FER) {
+		priv->counters[11]++;
+		priv->counters[12] = readl(priv->regs + DBAR);
+		priv->counters[13] = readl(priv->regs + DCAR);
+		priv->counters[14] = readl(priv->regs + MRR);
+		status = IRQ_HANDLED;
+	}
+
+	priv->counters[3] |= raw_stat;
+
+	/* Clear all interrupts */
+	writel(raw_stat, priv->regs + ISR);
+
+	return status;
+}
+
+static bool baikal_vdu_crtc_is_clk_enabled(struct baikal_vdu_private *priv)
+{
+	if (acpi_disabled) {
+		return __clk_is_enabled(priv->clk);
+#ifdef CONFIG_ACPI
+	} else {
+	        union acpi_object obj = {
+			.type = ACPI_TYPE_INTEGER,
+			.integer.type = ACPI_TYPE_INTEGER,
+			.integer.value = priv->index
+	        };
+	        struct acpi_object_list args = {
+	                .count = 1,
+	                .pointer = &obj
+	        };
+	        union acpi_object *obj2;
+		struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+		acpi_status status;
+		u64 val;
+
+		status = acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle,
+					      "CISE", &args, &buffer);
+		if (ACPI_FAILURE(status))
+			return 0;
+
+		obj2 = buffer.pointer;
+		if (!obj2 || obj2->type != ACPI_TYPE_INTEGER) {
+			kfree(obj2);
+			return 0;
+		}
+
+		val = obj2->integer.value;
+		kfree(obj2);
+
+		return val;
+#endif
+	}
+}
+
+static void baikal_vdu_crtc_clk_enable(struct baikal_vdu_private *priv)
+{
+	if (acpi_disabled) {
+		clk_prepare_enable(priv->clk);
+#ifdef CONFIG_ACPI
+	} else {
+	        union acpi_object obj = {
+			.type = ACPI_TYPE_INTEGER,
+			.integer.type = ACPI_TYPE_INTEGER,
+			.integer.value = priv->index
+	        };
+	        struct acpi_object_list args = {
+	                .count = 1,
+	                .pointer = &obj
+	        };
+
+		acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle,
+				     "CEN", &args, NULL);
+#endif
+	}
+}
+
+static void baikal_vdu_crtc_clk_disable(struct baikal_vdu_private *priv)
+{
+	if (acpi_disabled) {
+		clk_disable_unprepare(priv->clk);
+#ifdef CONFIG_ACPI
+	} else {
+	        union acpi_object obj = {
+			.type = ACPI_TYPE_INTEGER,
+			.integer.type = ACPI_TYPE_INTEGER,
+			.integer.value = priv->index
+	        };
+	        struct acpi_object_list args = {
+	                .count = 1,
+	                .pointer = &obj
+	        };
+
+		acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle,
+				     "CDIS", &args, NULL);
+#endif
+	}
+}
+
+static int baikal_vdu_crtc_set_rate(struct baikal_vdu_private *priv, u32 rate)
+{
+	if (acpi_disabled) {
+		return clk_set_rate(priv->clk, rate);
+#ifdef CONFIG_ACPI
+	} else {
+	        union acpi_object obj[2] = {
+			{
+				.type = ACPI_TYPE_INTEGER,
+				.integer.type = ACPI_TYPE_INTEGER,
+				.integer.value = priv->index
+			},
+			{
+				.type = ACPI_TYPE_INTEGER,
+				.integer.type = ACPI_TYPE_INTEGER,
+				.integer.value = rate
+			}
+	        };
+	        struct acpi_object_list args = {
+	                .count = 2,
+	                .pointer = obj
+	        };
+
+		acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle,
+				     "CSET", &args, NULL);
+		return 0;
+#endif
+	}
+}
+
+u64 baikal_vdu_crtc_get_rate(struct baikal_vdu_private *priv)
+{
+	if (acpi_disabled) {
+		return clk_get_rate(priv->clk);
+#ifdef CONFIG_ACPI
+	} else {
+	        union acpi_object obj = {
+			.type = ACPI_TYPE_INTEGER,
+			.integer.type = ACPI_TYPE_INTEGER,
+			.integer.value = priv->index
+	        };
+	        struct acpi_object_list args = {
+	                .count = 1,
+	                .pointer = &obj
+	        };
+	        union acpi_object *obj2;
+		struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+		acpi_status status;
+		u64 val;
+
+		status = acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle,
+					      "CGET", &args, &buffer);
+		if (ACPI_FAILURE(status))
+			return 0;
+
+		obj2 = buffer.pointer;
+		if (!obj2 || obj2->type != ACPI_TYPE_INTEGER) {
+			kfree(obj2);
+			return 0;
+		}
+
+		val = obj2->integer.value;
+		kfree(obj2);
+
+		return val;
+#endif
+	}
+}
+
+static void baikal_vdu_crtc_helper_mode_set_nofb(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+	const struct drm_display_mode *mode = &crtc->state->mode;
+	unsigned long rate;
+	unsigned int ppl, hsw, hfp, hbp;
+	unsigned int lpp, vsw, vfp, vbp;
+	unsigned int reg;
+	int ret = 0;
+
+	drm_mode_debug_printmodeline(mode);
+
+	rate = mode->clock * 1000;
+
+	if (rate != baikal_vdu_crtc_get_rate(priv)) {
+		DRM_DEV_DEBUG_DRIVER(dev->dev, "Requested pixel clock is %lu Hz\n", rate);
+
+		/* hold clock domain reset; disable clocking */
+		writel(0, priv->regs + PCTR);
+
+		if (baikal_vdu_crtc_is_clk_enabled(priv))
+			baikal_vdu_crtc_clk_disable(priv);
+		ret = baikal_vdu_crtc_set_rate(priv, rate);
+
+		if (ret >= 0) {
+			baikal_vdu_crtc_clk_enable(priv);
+			if (!baikal_vdu_crtc_is_clk_enabled(priv))
+				ret = -1;
+		}
+
+		/* release clock domain reset; enable clocking */
+		reg = readl(priv->regs + PCTR);
+		reg |= PCTR_PCR + PCTR_PCI;
+		writel(reg, priv->regs + PCTR);
+	}
+
+	if (ret < 0)
+		DRM_ERROR("Cannot set desired pixel clock (%lu Hz)\n", rate);
+
+	ppl = mode->hdisplay / 16;
+	if (priv->index == CRTC_LVDS && priv-> num_lanes == 2) {
+		hsw = mode->hsync_end - mode->hsync_start;
+		hfp = mode->hsync_start - mode->hdisplay - 1;
+	} else {
+		hsw = mode->hsync_end - mode->hsync_start - 1;
+		hfp = mode->hsync_start - mode->hdisplay;
+	}
+	hbp = mode->htotal - mode->hsync_end;
+
+	lpp = mode->vdisplay;
+	vsw = mode->vsync_end - mode->vsync_start;
+	vfp = mode->vsync_start - mode->vdisplay;
+	vbp = mode->vtotal - mode->vsync_end;
+
+	writel((HTR_HFP(hfp) & HTR_HFP_MASK) |
+			(HTR_PPL(ppl) & HTR_PPL_MASK) |
+			(HTR_HBP(hbp) & HTR_HBP_MASK) |
+			(HTR_HSW(hsw) & HTR_HSW_MASK),
+			priv->regs + HTR);
+
+	if (mode->hdisplay > 4080 || ppl * 16 != mode->hdisplay)
+		writel((HPPLOR_HPPLO(mode->hdisplay) & HPPLOR_HPPLO_MASK) | HPPLOR_HPOE,
+				priv->regs + HPPLOR);
+
+	writel((VTR1_VSW(vsw) & VTR1_VSW_MASK) |
+			(VTR1_VFP(vfp) & VTR1_VFP_MASK) |
+			(VTR1_VBP(vbp) & VTR1_VBP_MASK),
+			priv->regs + VTR1);
+
+	writel(lpp & VTR2_LPP_MASK, priv->regs + VTR2);
+
+	writel((HVTER_VSWE(vsw >> VTR1_VSW_LSB_WIDTH) & HVTER_VSWE_MASK) |
+			(HVTER_HSWE(hsw >> HTR_HSW_LSB_WIDTH) & HVTER_HSWE_MASK) |
+			(HVTER_VBPE(vbp >> VTR1_VBP_LSB_WIDTH) & HVTER_VBPE_MASK) |
+			(HVTER_VFPE(vfp >> VTR1_VFP_LSB_WIDTH) & HVTER_VFPE_MASK) |
+			(HVTER_HBPE(hbp >> HTR_HBP_LSB_WIDTH) & HVTER_HBPE_MASK) |
+			(HVTER_HFPE(hfp >> HTR_HFP_LSB_WIDTH) & HVTER_HFPE_MASK),
+			priv->regs + HVTER);
+
+	/* Set polarities */
+	reg = readl(priv->regs + CR1);
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		reg |= CR1_HSP;
+	else
+		reg &= ~CR1_HSP;
+	reg &= ~CR1_VSP; // always set VSP to active high
+	reg |= CR1_DEP; // set DE to active high;
+	writel(reg, priv->regs + CR1);
+}
+
+static enum drm_mode_status baikal_vdu_mode_valid(struct drm_crtc *crtc,
+	                const struct drm_display_mode *mode)
+{
+	struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+	if (!priv->mode_override && (mode->hdisplay > 2560 ||
+			mode->vdisplay > 1440))
+		return MODE_BAD;
+	else
+		return MODE_OK;
+}
+
+static void baikal_vdu_crtc_helper_enable(struct drm_crtc *crtc,
+					  struct drm_atomic_state *old_state)
+{
+	struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+	const char *data_mapping = NULL;
+	u32 cntl, gpio;
+
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n");
+	baikal_vdu_crtc_clk_enable(priv);
+
+	/* Set 16-word input FIFO watermark */
+	/* Enable and Power Up */
+	cntl = readl(priv->regs + CR1);
+	cntl &= ~(CR1_FDW_MASK | CR1_OPS_MASK);
+	cntl |= CR1_LCE | CR1_FDW_16_WORDS;
+
+	if (priv->index == CRTC_LVDS) {
+		if (priv->bridge->of_node) {
+			of_property_read_string(priv->bridge->of_node,
+						"data-mapping", &data_mapping);
+		} else {
+			struct fwnode_handle *fwnode;
+
+			fwnode = fwnode_find_reference(priv->bridge->dev->dev->fwnode,
+						       "baikal,lvds-panel", 0);
+			if (!IS_ERR_OR_NULL(fwnode))
+				fwnode_property_read_string(fwnode, "data-mapping",
+							    &data_mapping);
+		}
+
+		if (!data_mapping) {
+			cntl |= CR1_OPS_LCD18;
+		} else if (!strncmp(data_mapping, "vesa-24", 7))
+			cntl |= CR1_OPS_LCD24;
+		else if (!strncmp(data_mapping, "jeida-18", 8))
+			cntl |= CR1_OPS_LCD18;
+		else {
+			dev_warn(crtc->dev->dev, "%s data mapping is not supported, vesa-24 is set\n", data_mapping);
+			cntl |= CR1_OPS_LCD24;
+		}
+		gpio = GPIOR_UHD_ENB;
+		if (priv->num_lanes == 4)
+			gpio |= GPIOR_UHD_QUAD_PORT;
+		else if (priv->num_lanes == 2)
+			gpio |= GPIOR_UHD_DUAL_PORT;
+		else
+			gpio |= GPIOR_UHD_SNGL_PORT;
+		writel(gpio, priv->regs + GPIOR);
+	} else
+		cntl |= CR1_OPS_LCD24;
+	writel(cntl, priv->regs + CR1);
+
+	writel(0x3ffff, priv->regs + ISR);
+	writel(INTR_FER, priv->regs + IMR);
+}
+
+void baikal_vdu_crtc_helper_disable(struct drm_crtc *crtc)
+{
+	struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+
+	writel(0x3ffff, priv->regs + ISR);
+	writel(0, priv->regs + IMR);
+
+	/* Disable clock */
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "disabling pixel clock\n");
+	baikal_vdu_crtc_clk_disable(priv);
+}
+
+static void baikal_vdu_crtc_helper_atomic_flush(struct drm_crtc *crtc,
+					   struct drm_atomic_state *old_state)
+{
+	struct drm_pending_vblank_event *event = crtc->state->event;
+
+	if (event) {
+		crtc->state->event = NULL;
+
+		spin_lock_irq(&crtc->dev->event_lock);
+		if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0)
+			drm_crtc_arm_vblank_event(crtc, event);
+		else
+			drm_crtc_send_vblank_event(crtc, event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+	}
+}
+
+const struct drm_crtc_funcs crtc_funcs = {
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.reset = drm_atomic_helper_crtc_reset,
+	.destroy = drm_crtc_cleanup,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+
+const struct drm_crtc_helper_funcs crtc_helper_funcs = {
+	.mode_set_nofb = baikal_vdu_crtc_helper_mode_set_nofb,
+	.mode_valid = baikal_vdu_mode_valid,
+	.atomic_flush = baikal_vdu_crtc_helper_atomic_flush,
+	.disable = baikal_vdu_crtc_helper_disable,
+	.atomic_enable = baikal_vdu_crtc_helper_enable,
+};
+
+int baikal_vdu_crtc_create(struct baikal_vdu_private *priv)
+{
+	struct drm_device *dev = priv->drm;
+	struct drm_crtc *crtc = &priv->crtc;
+
+	drm_crtc_init_with_planes(dev, crtc,
+				  &priv->primary, NULL,
+				  &crtc_funcs, "primary");
+	drm_crtc_helper_add(crtc, &crtc_helper_funcs);
+
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n");
+	baikal_vdu_crtc_clk_enable(priv);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c b/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c
new file mode 100644
index 0000000000000..1fda8c2173117
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#include <linux/seq_file.h>
+#include <linux/device.h>
+#include <drm/drm_debugfs.h>
+#include <drm/drm_device.h>
+#include <drm/drm_file.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+#define REGDEF(reg) { reg, #reg }
+static const struct {
+	u32 reg;
+	const char *name;
+} baikal_vdu_reg_defs[] = {
+	REGDEF(CR1),
+	REGDEF(HTR),
+	REGDEF(VTR1),
+	REGDEF(VTR2),
+	REGDEF(PCTR),
+	REGDEF(ISR),
+	REGDEF(IMR),
+	REGDEF(IVR),
+	REGDEF(ISCR),
+	REGDEF(DBAR),
+	REGDEF(DCAR),
+	REGDEF(DEAR),
+	REGDEF(PWMFR),
+	REGDEF(PWMDCR),
+	REGDEF(HVTER),
+	REGDEF(HPPLOR),
+	REGDEF(GPIOR),
+	REGDEF(MRR),
+};
+
+#define REGS_HDMI	"regs_hdmi"
+#define REGS_LVDS	"regs_lvds"
+
+int baikal_vdu_debugfs_regs(struct seq_file *m, void *unused)
+{
+	struct drm_info_node *node = (struct drm_info_node *)m->private;
+	struct drm_device *dev = node->minor->dev;
+	struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(dev);
+	char *filename = m->file->f_path.dentry->d_iname;
+	struct baikal_vdu_private *priv = NULL;
+	int i;
+
+	if (!strcmp(REGS_HDMI, filename))
+		priv = &crossbar->hdmi;
+	if (!strcmp(REGS_LVDS, filename))
+		priv = &crossbar->lvds;
+
+	if (!priv || !priv->regs)
+		return 0;
+
+	for (i = 0; i < ARRAY_SIZE(baikal_vdu_reg_defs); i++) {
+		seq_printf(m, "%s (0x%04x): 0x%08x\n",
+			   baikal_vdu_reg_defs[i].name, baikal_vdu_reg_defs[i].reg,
+			   readl(priv->regs + baikal_vdu_reg_defs[i].reg));
+	}
+	for (i = 0; i < ARRAY_SIZE(priv->counters); i++) {
+		seq_printf(m, "COUNTER[%d]: 0x%08x\n", i, priv->counters[i]);
+	}
+
+	return 0;
+}
+
+static const struct drm_info_list baikal_vdu_hdmi_debugfs_list[] = {
+	{REGS_HDMI, baikal_vdu_debugfs_regs, 0},
+};
+
+static const struct drm_info_list baikal_vdu_lvds_debugfs_list[] = {
+	{REGS_LVDS, baikal_vdu_debugfs_regs, 0},
+};
+
+void baikal_vdu_hdmi_debugfs_init(struct drm_minor *minor)
+{
+	drm_debugfs_create_files(baikal_vdu_hdmi_debugfs_list,
+				ARRAY_SIZE(baikal_vdu_hdmi_debugfs_list),
+				minor->debugfs_root, minor);
+}
+
+void baikal_vdu_lvds_debugfs_init(struct drm_minor *minor)
+{
+	drm_debugfs_create_files(baikal_vdu_lvds_debugfs_list,
+				ARRAY_SIZE(baikal_vdu_lvds_debugfs_list),
+				minor->debugfs_root, minor);
+}
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drm.h b/drivers/gpu/drm/baikal/baikal_vdu_drm.h
new file mode 100644
index 0000000000000..432270bebed69
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drm.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#ifndef __BAIKAL_VDU_DRM_H__
+#define __BAIKAL_VDU_DRM_H__
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include <drm/bridge/dw_hdmi.h>
+
+#include <linux/workqueue.h>
+#include <linux/gpio.h>
+#include <linux/backlight.h>
+
+#define CRTC_HDMI	0
+#define CRTC_LVDS	1
+
+#define crtc_to_baikal_vdu(x) \
+	container_of(x, struct baikal_vdu_private, crtc)
+#define bridge_to_baikal_lvds_bridge(x) \
+	container_of(x, struct baikal_lvds_bridge, bridge)
+#define connector_to_baikal_lvds_bridge(x) \
+	container_of(x, struct baikal_lvds_bridge, connector)
+#define drm_to_baikal_vdu_crossbar(x) \
+	container_of(x, struct baikal_vdu_crossbar, drm)
+
+#define VDU_NAME_LEN 5
+
+struct baikal_vdu_private {
+	struct drm_device *drm;
+	struct drm_crtc crtc;
+	struct drm_encoder encoder;
+	struct drm_bridge *bridge;
+	struct drm_plane primary;
+	void *regs;
+	int irq;
+	struct clk *clk;
+	spinlock_t lock;
+	u32 counters[20];
+	int mode_override;
+	int index;
+	char name[VDU_NAME_LEN];
+	char irq_name[VDU_NAME_LEN + 4];
+	char pclk_name[VDU_NAME_LEN + 5];
+	char regs_name[VDU_NAME_LEN + 5];
+	int num_lanes;
+	int data_mapping;
+	int off;
+	int ready;
+
+	/* backlight */
+	struct gpio_desc *enable_gpio;
+	struct backlight_device *bl_dev;
+	int min_brightness;
+	int brightness_step;
+	bool brightness_on;
+};
+
+struct baikal_vdu_crossbar {
+	struct drm_device drm;
+	struct baikal_vdu_private hdmi;
+	struct baikal_vdu_private lvds;
+};
+
+struct baikal_lvds_bridge {
+	struct baikal_vdu_private *vdu;
+	struct drm_bridge bridge;
+	struct drm_connector connector;
+	struct drm_panel *panel;
+	u32 connector_type;
+};
+
+struct baikal_hdmi_bridge {
+	struct dw_hdmi *hdmi;
+	struct drm_bridge *bridge;
+};
+
+/* Generic functions */
+inline void baikal_vdu_switch_on(struct baikal_vdu_private *priv);
+
+inline void baikal_vdu_switch_off(struct baikal_vdu_private *priv);
+
+/* Bridge functions */
+bool bridge_is_baikal_lvds_bridge(const struct drm_bridge *bridge);
+
+struct drm_bridge *devm_baikal_lvds_bridge_add(struct device *dev,
+					     struct drm_panel *panel,
+					     u32 connector_type);
+
+/* CRTC Functions */
+u64 baikal_vdu_crtc_get_rate(struct baikal_vdu_private *priv);
+
+int baikal_vdu_crtc_create(struct baikal_vdu_private *priv);
+
+irqreturn_t baikal_vdu_irq(int irq, void *data);
+
+int baikal_vdu_primary_plane_init(struct baikal_vdu_private *priv);
+
+/* Backlight Functions */
+int baikal_vdu_backlight_create(struct drm_device *drm);
+
+/* Debugfs functions */
+void baikal_vdu_hdmi_debugfs_init(struct drm_minor *minor);
+
+void baikal_vdu_lvds_debugfs_init(struct drm_minor *minor);
+
+#endif /* __BAIKAL_VDU_DRM_H__ */
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drv.c b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
new file mode 100644
index 0000000000000..c9d5cea635628
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
@@ -0,0 +1,516 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ * Bugfixes by Alexey Sheplyakov <asheplyakov@altlinux.org>
+ *
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_aperture.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fbdev_dma.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+#define DRIVER_NAME		"baikal-vdu"
+#define DRIVER_DESC		"Baikal VDU DRM driver"
+#define DRIVER_DATE		"20230221"
+
+#define BAIKAL_SMC_LOG_DISABLE	0xC2000200
+#define IFIFO_SIZE	16384
+#define UHD_FIFO_SIZE	16384
+
+int mode_override = 0;
+int hdmi_off = 0;
+int lvds_off = 0;
+
+static struct drm_driver vdu_drm_driver;
+
+static struct drm_mode_config_funcs mode_config_funcs = {
+	.fb_create = drm_gem_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+const struct drm_encoder_funcs baikal_vdu_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static struct drm_bridge *devm_baikal_get_bridge(struct device *dev,
+						 u32 port, u32 endpoint)
+{
+	struct baikal_hdmi_bridge *priv_hdmi;
+	struct device *tmp;
+	struct drm_bridge *bridge;
+	struct drm_panel *panel;
+	struct fwnode_handle *fwnode;
+	int ret = 0;
+
+	if (is_of_node(dev->fwnode)) {
+		ret = drm_of_find_panel_or_bridge(to_of_node(dev->fwnode), port,
+						  endpoint, &panel, &bridge);
+	} else {
+		if (port == CRTC_HDMI) {
+			fwnode = fwnode_find_reference(dev->fwnode,
+						       "baikal,hdmi-bridge", 0);
+			if (IS_ERR_OR_NULL(fwnode))
+				return ERR_PTR(-ENODEV);
+
+			tmp = bus_find_device_by_fwnode(&platform_bus_type, fwnode);
+			if (IS_ERR_OR_NULL(tmp))
+				return ERR_PTR(-ENODEV);
+
+			priv_hdmi = dev_get_drvdata(tmp);
+			if (!priv_hdmi)
+				return ERR_PTR(-EPROBE_DEFER);
+
+			bridge = priv_hdmi->bridge;
+			panel = NULL;
+		} else if (port == CRTC_LVDS) {
+			fwnode = fwnode_find_reference(dev->fwnode,
+						       "baikal,lvds-panel", 0);
+			if (IS_ERR_OR_NULL(fwnode))
+				return ERR_PTR(-ENODEV);
+
+			panel = fwnode_drm_find_panel(fwnode);
+			if (IS_ERR(panel))
+				return ERR_CAST(panel);
+		} else {
+			return ERR_PTR(-ENODEV);
+		}
+	}
+
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (panel) {
+		bridge = devm_baikal_lvds_bridge_add(dev, panel, DRM_MODE_CONNECTOR_LVDS);
+	}
+
+	return bridge;
+}
+
+static int baikal_vdu_remove_efifb(struct drm_device *dev)
+{
+	int err;
+	err = drm_aperture_remove_framebuffers(&vdu_drm_driver);
+	if (err)
+		dev_warn(dev->dev, "failed to remove firmware framebuffer\n");
+	return err;
+}
+
+inline void baikal_vdu_switch_off(struct baikal_vdu_private *priv)
+{
+	u32 cntl = readl(priv->regs + CR1);
+	cntl &= ~CR1_LCE;
+	writel(cntl, priv->regs + CR1);
+}
+
+inline void baikal_vdu_switch_on(struct baikal_vdu_private *priv)
+{
+	u32 cntl = readl(priv->regs + CR1);
+	cntl |= CR1_LCE;
+	writel(cntl, priv->regs + CR1);
+}
+
+static int baikal_vdu_modeset_init(struct baikal_vdu_private *priv)
+{
+	struct drm_device *dev = priv->drm;
+	struct drm_encoder *encoder;
+	int ret = 0;
+	int index;
+
+	if (priv == NULL)
+		return -EINVAL;
+	index = priv->index;
+
+	ret = baikal_vdu_primary_plane_init(priv);
+	if (ret != 0) {
+		dev_err(dev->dev, "%s: failed to init primary plane\n", priv->name);
+		return ret;
+	}
+
+	ret = baikal_vdu_crtc_create(priv);
+	if (ret) {
+		dev_err(dev->dev, "%s: failed to create CRTC\n", priv->name);
+		return ret;
+	}
+
+	if (priv->bridge) {
+		encoder = &priv->encoder;
+		ret = drm_encoder_init(dev, encoder, &baikal_vdu_encoder_funcs,
+				       DRM_MODE_ENCODER_NONE, NULL);
+		if (ret) {
+			dev_err(dev->dev, "%s: failed to create DRM encoder\n", priv->name);
+			return ret;
+		}
+		encoder->crtc = &priv->crtc;
+		encoder->possible_crtcs = BIT(drm_crtc_index(encoder->crtc));
+		priv->bridge->encoder = encoder;
+		ret = drm_bridge_attach(encoder, priv->bridge, NULL, 0);
+		if (ret) {
+			dev_err(dev->dev, "%s: failed to attach DRM bridge (%d)\n", priv->name, ret);
+			return ret;
+		}
+	} else {
+		dev_err(dev->dev, "%s: no bridge or panel attached\n", priv->name);
+		return -ENODEV;
+	}
+
+	priv->mode_override = mode_override;
+
+	return ret;
+}
+
+static int baikal_dumb_create(struct drm_file *file, struct drm_device *dev,
+			     struct drm_mode_create_dumb *args)
+{
+	args->pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+	args->size = args->pitch * args->height + IFIFO_SIZE + UHD_FIFO_SIZE;
+
+	return drm_gem_dma_dumb_create_internal(file, dev, args);
+}
+
+DEFINE_DRM_GEM_DMA_FOPS(baikal_drm_fops);
+
+static struct drm_driver vdu_drm_driver = {
+	.driver_features = DRIVER_GEM |	DRIVER_MODESET | DRIVER_ATOMIC,
+	DRM_GEM_DMA_DRIVER_OPS,
+	.ioctls = NULL,
+	.fops = &baikal_drm_fops,
+	.name = DRIVER_NAME,
+	.desc = DRIVER_DESC,
+	.date = DRIVER_DATE,
+	.major = 2,
+	.minor = 0,
+	.patchlevel = 0,
+	.dumb_create = baikal_dumb_create,
+	.dumb_map_offset = drm_gem_dumb_map_offset,
+	.gem_prime_import = drm_gem_prime_import,
+	.gem_prime_import_sg_table = drm_gem_dma_prime_import_sg_table,
+};
+
+static int baikal_vdu_allocate_resources(struct platform_device *pdev,
+		struct baikal_vdu_private *priv)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *mem;
+	int ret;
+
+	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, priv->regs_name);
+	if (!mem)
+		mem = platform_get_resource(pdev, IORESOURCE_MEM, priv->index);
+
+	if (!mem) {
+		dev_err(dev, "%s %s: no MMIO resource specified\n", __func__, priv->name);
+		return -EINVAL;
+	}
+
+	priv->regs = devm_ioremap_resource(dev, mem);
+	if (IS_ERR(priv->regs)) {
+		dev_err(dev, "%s %s: MMIO allocation failed\n", __func__, priv->name);
+		return PTR_ERR(priv->regs);
+	}
+
+	if (priv->off) {
+		baikal_vdu_switch_off(priv);
+		return -EPERM;
+	} else {
+		ret = baikal_vdu_modeset_init(priv);
+		if (ret) {
+			dev_err(dev, "%s %s: failed to init modeset\n", __func__, priv->name);
+			if (ret == -ENODEV) {
+				baikal_vdu_switch_off(priv);
+			}
+			return ret;
+		} else {
+			writel(MRR_MAX_VALUE, priv->regs + MRR);
+			spin_lock_init(&priv->lock);
+			return 0;
+		}
+	}
+}
+
+static int baikal_vdu_allocate_irq(struct platform_device *pdev,
+	       struct baikal_vdu_private *priv)
+{
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	priv->irq = fwnode_irq_get_byname(dev->fwnode, priv->irq_name);
+	if (priv->irq < 0) {
+		dev_err(dev, "%s %s: no IRQ resource specified\n", __func__, priv->name);
+		return -EINVAL;
+	}
+
+	/* turn off interrupts before requesting the irq */
+	writel(0, priv->regs + IMR);
+	ret = request_irq(priv->irq, baikal_vdu_irq, IRQF_SHARED, dev->driver->name, priv);
+	if (ret != 0)
+		dev_err(dev, "%s %s: IRQ %d allocation failed\n", __func__, priv->name, priv->irq);
+	return ret;
+}
+
+static void baikal_vdu_free_irq(struct baikal_vdu_private *priv)
+{
+	writel(0, priv->regs + IMR);
+	writel(0x3ffff, priv->regs + ISR);
+	free_irq(priv->irq, priv->drm->dev);
+}
+
+static int baikal_vdu_allocate_clk(struct baikal_vdu_private *priv)
+{
+	if (is_of_node(priv->drm->dev->fwnode)) {
+		priv->clk = clk_get(priv->drm->dev, priv->pclk_name);
+		if (IS_ERR(priv->clk)) {
+			dev_err(priv->drm->dev, "%s: unable to get %s, err %ld\n", priv->name, priv->pclk_name, PTR_ERR(priv->clk));
+			return PTR_ERR(priv->clk);
+		}
+	}
+
+	return 0;
+}
+
+static void baikal_vdu_set_name(struct baikal_vdu_private *priv, int index, const char *name)
+{
+	char *c;
+	int len = sizeof(priv->name) / sizeof(priv->name[0]) - 1;
+	strncpy(priv->name, name, len);
+	for (c = priv->name; c < priv->name + len && *c; c++) {
+		*c = toupper(*c);
+	}
+	sprintf(priv->irq_name, "%s_irq", name);
+	sprintf(priv->pclk_name, "%s_pclk", name);
+	sprintf(priv->regs_name, "%s_regs", name);
+	priv->index = index;
+}
+
+static int baikal_vdu_bridge_init(struct baikal_vdu_private *priv, struct drm_device *drm) {
+	int ret = 0;
+	struct device *dev;
+	struct drm_bridge *bridge;
+	if (!priv || !drm)
+		return -ENODEV;
+	priv->drm = drm;
+	dev = drm->dev;
+	bridge = devm_baikal_get_bridge(dev, priv->index, 0);
+	if (IS_ERR(bridge)) {
+		ret = PTR_ERR(bridge);
+		if (ret == -EPROBE_DEFER) {
+			dev_info(dev, "%s: bridge probe deferred\n", priv->name);
+		}
+		priv->bridge = NULL;
+	} else {
+		priv->bridge = bridge;
+	}
+	return ret;
+}
+
+static int baikal_vdu_resources_init(struct platform_device *pdev, struct baikal_vdu_private *priv)
+{
+	int ret = baikal_vdu_allocate_resources(pdev, priv);
+	if (ret)
+		return 0;
+	ret = baikal_vdu_allocate_irq(pdev, priv);
+	if (ret)
+		return 0;
+	ret = baikal_vdu_allocate_clk(priv);
+	if (ret) {
+		baikal_vdu_free_irq(priv);
+		return 0;
+	} else {
+		return 1;
+	}
+}
+
+static int baikal_vdu_drm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct baikal_lvds_bridge *panel_bridge;
+	struct baikal_vdu_crossbar *crossbar;
+	struct baikal_vdu_private *hdmi;
+	struct baikal_vdu_private *lvds;
+	struct drm_device *drm;
+	struct drm_mode_config *mode_config;
+	struct arm_smccc_res res;
+	int ret;
+
+	crossbar = devm_drm_dev_alloc(dev, &vdu_drm_driver,
+                  struct baikal_vdu_crossbar, drm);
+	if (IS_ERR(crossbar))
+		return PTR_ERR(crossbar);
+
+	drm = &crossbar->drm;
+	platform_set_drvdata(pdev, drm);
+	hdmi = &crossbar->hdmi;
+	baikal_vdu_set_name(hdmi, CRTC_HDMI, "hdmi");
+	lvds = &crossbar->lvds;
+	baikal_vdu_set_name(lvds, CRTC_LVDS, "lvds");
+
+	ret = baikal_vdu_bridge_init(hdmi, drm);
+	if (ret == -EPROBE_DEFER) {
+		goto out_drm;
+	}
+
+	ret = device_property_read_u32(&pdev->dev, "lvds-lanes",
+				       &lvds->num_lanes);
+	if (ret)
+		lvds->num_lanes = 0;
+	if (lvds->num_lanes) {
+		ret = baikal_vdu_bridge_init(lvds, drm);
+		if (ret == -EPROBE_DEFER) {
+			goto out_drm;
+		}
+	}
+
+	drm_mode_config_init(drm);
+	mode_config = &drm->mode_config;
+	mode_config->funcs = &mode_config_funcs;
+	mode_config->min_width = 1;
+	mode_config->max_width = 8192;
+	mode_config->min_height = 1;
+	mode_config->max_height = 8192;
+
+	hdmi->off = hdmi_off;
+	hdmi->ready = baikal_vdu_resources_init(pdev, hdmi);
+	if (lvds->num_lanes) {
+		lvds->off = lvds_off;
+		lvds->ready = baikal_vdu_resources_init(pdev, lvds);
+	} else {
+		lvds->ready = 0;
+		lvds->bridge = NULL;
+		dev_info(dev, "No 'lvds-lanes' property found\n");
+	}
+	if (lvds->ready) {
+		ret = baikal_vdu_backlight_create(drm);
+		if (ret) {
+			dev_err(dev, "LVDS: failed to create backlight\n");
+		}
+		if (bridge_is_baikal_lvds_bridge(lvds->bridge)) {
+			panel_bridge = bridge_to_baikal_lvds_bridge(lvds->bridge);
+			panel_bridge->vdu = lvds;
+		} else {
+		// TODO implement handling of third-party bridges
+		}
+	}
+	if (hdmi->bridge) {
+		// TODO implement functions specific to HDMI bridge
+	}
+
+	hdmi->ready = hdmi->ready & !hdmi->off;
+	lvds->ready = lvds->ready & !lvds->off;
+	dev_info(dev, "%s output %s\n", hdmi->name, hdmi->ready ? "enabled" : "disabled");
+	dev_info(dev, "%s output %s\n", lvds->name, lvds->ready ? "enabled" : "disabled");
+	baikal_vdu_remove_efifb(drm);
+
+	if (hdmi->ready || lvds->ready) {
+		/* Disable SCP debug output as it may affect VDU performance */
+		arm_smccc_smc(BAIKAL_SMC_LOG_DISABLE, 0, 0, 0, 0, 0, 0, 0, &res);
+
+		drm_mode_config_reset(drm);
+		drm_kms_helper_poll_init(drm);
+		ret = drm_dev_register(drm, 0);
+		if (ret) {
+			dev_err(dev, "failed to register DRM device\n");
+			goto out_config;
+		}
+		drm_fbdev_dma_setup(drm, 32);
+#if defined(CONFIG_DEBUG_FS)
+		if (hdmi->ready)
+			baikal_vdu_hdmi_debugfs_init(drm->primary);
+		if (lvds->ready)
+			baikal_vdu_lvds_debugfs_init(drm->primary);
+#endif
+		return 0;
+	} else {
+		dev_err(dev, "no active outputs configured\n");
+		ret = -ENODEV;
+	}
+out_config:
+	drm_mode_config_cleanup(drm);
+out_drm:
+	dev_err(dev, "failed to probe: %d\n", ret);
+	return ret;
+}
+
+static void baikal_vdu_drm_remove(struct platform_device *pdev)
+{
+	struct drm_device *drm = platform_get_drvdata(pdev);
+	struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(drm);
+
+	drm_dev_unregister(drm);
+	drm_mode_config_cleanup(drm);
+	if (crossbar->hdmi.irq)
+		free_irq(crossbar->hdmi.irq, drm->dev);
+	if (crossbar->lvds.irq)
+		free_irq(crossbar->lvds.irq, drm->dev);
+}
+
+static const struct of_device_id baikal_vdu_of_match[] = {
+	{ .compatible = "baikal,vdu" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, baikal_vdu_of_match);
+
+static int baikal_vdu_pm_suspend(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+
+	return drm_mode_config_helper_suspend(drm);
+}
+
+static int baikal_vdu_pm_resume(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+
+	return drm_mode_config_helper_resume(drm);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(baikal_vdu_pm_ops, baikal_vdu_pm_suspend, baikal_vdu_pm_resume);
+
+static struct platform_driver baikal_vdu_platform_driver = {
+	.probe      = baikal_vdu_drm_probe,
+	.remove_new = baikal_vdu_drm_remove,
+	.driver     = {
+		.name   = DRIVER_NAME,
+		.of_match_table = baikal_vdu_of_match,
+		.pm = pm_sleep_ptr(&baikal_vdu_pm_ops)
+	},
+};
+
+module_param(mode_override, int, 0644);
+module_param(hdmi_off, int, 0644);
+module_param(lvds_off, int, 0644);
+
+module_platform_driver(baikal_vdu_platform_driver);
+
+MODULE_AUTHOR("Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal Electronics BE-M1000 Video Display Unit (VDU) DRM Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_SOFTDEP("pre: baikal_hdmi");
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_panel.c b/drivers/gpu/drm/baikal/baikal_vdu_panel.c
new file mode 100644
index 0000000000000..9983f36b94f1e
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_panel.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+static void baikal_lvds_connector_force(struct drm_connector *connector)
+{
+	struct baikal_lvds_bridge *bridge = connector_to_baikal_lvds_bridge(connector);
+	struct baikal_vdu_private *priv = bridge->vdu;
+	u32 cntl = readl(priv->regs + CR1);
+	if (connector->force == DRM_FORCE_OFF)
+		cntl &= ~CR1_LCE;
+	else
+		cntl |= CR1_LCE;
+	writel(cntl, priv->regs + CR1);
+}
+
+static int baikal_lvds_connector_get_modes(struct drm_connector *connector)
+{
+	struct baikal_lvds_bridge *panel_bridge =
+		connector_to_baikal_lvds_bridge(connector);
+
+	return drm_panel_get_modes(panel_bridge->panel, connector);
+}
+
+static const struct drm_connector_helper_funcs
+baikal_lvds_bridge_connector_helper_funcs = {
+	.get_modes = baikal_lvds_connector_get_modes,
+};
+
+static const struct drm_connector_funcs baikal_lvds_bridge_connector_funcs = {
+	.reset = drm_atomic_helper_connector_reset,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_connector_cleanup,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+	.force = baikal_lvds_connector_force,
+};
+
+static int baikal_lvds_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags)
+{
+	struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+	struct drm_connector *connector = &panel_bridge->connector;
+	int ret;
+
+	if (!bridge->encoder) {
+		DRM_ERROR("Missing encoder\n");
+		return -ENODEV;
+	}
+
+	drm_connector_helper_add(connector,
+				 &baikal_lvds_bridge_connector_helper_funcs);
+
+	ret = drm_connector_init(bridge->dev, connector,
+				 &baikal_lvds_bridge_connector_funcs,
+				 panel_bridge->connector_type);
+	if (ret) {
+		DRM_ERROR("Failed to initialize connector\n");
+		return ret;
+	}
+
+	ret = drm_connector_attach_encoder(&panel_bridge->connector,
+					  bridge->encoder);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void baikal_lvds_bridge_pre_enable(struct drm_bridge *bridge)
+{
+	struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+	baikal_vdu_switch_on(panel_bridge->vdu);
+	drm_panel_prepare(panel_bridge->panel);
+}
+
+static void baikal_lvds_bridge_enable(struct drm_bridge *bridge)
+{
+	struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+	drm_panel_enable(panel_bridge->panel);
+}
+
+static void baikal_lvds_bridge_disable(struct drm_bridge *bridge)
+{
+	struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+	drm_panel_disable(panel_bridge->panel);
+}
+
+static void baikal_lvds_bridge_post_disable(struct drm_bridge *bridge)
+{
+	struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+	drm_panel_unprepare(panel_bridge->panel);
+	baikal_vdu_switch_off(panel_bridge->vdu);
+}
+
+static const struct drm_bridge_funcs baikal_lvds_bridge_funcs = {
+	.attach = baikal_lvds_bridge_attach,
+	.pre_enable = baikal_lvds_bridge_pre_enable,
+	.enable = baikal_lvds_bridge_enable,
+	.disable = baikal_lvds_bridge_disable,
+	.post_disable = baikal_lvds_bridge_post_disable,
+};
+
+static struct drm_bridge *baikal_lvds_bridge_add(struct drm_panel *panel,
+					u32 connector_type)
+{
+	struct baikal_lvds_bridge *panel_bridge;
+
+	if (!panel)
+		return ERR_PTR(-EINVAL);
+
+	panel_bridge = devm_kzalloc(panel->dev, sizeof(*panel_bridge),
+				    GFP_KERNEL);
+	if (!panel_bridge)
+		return ERR_PTR(-ENOMEM);
+
+	panel_bridge->connector_type = connector_type;
+	panel_bridge->panel = panel;
+
+	panel_bridge->bridge.funcs = &baikal_lvds_bridge_funcs;
+#ifdef CONFIG_OF
+	panel_bridge->bridge.of_node = panel->dev->of_node;
+#endif
+
+	drm_bridge_add(&panel_bridge->bridge);
+
+	return &panel_bridge->bridge;
+}
+
+static void baikal_lvds_bridge_remove(struct drm_bridge *bridge)
+{
+	struct baikal_lvds_bridge *panel_bridge;
+
+	if (!bridge)
+		return;
+
+	if (bridge->funcs != &baikal_lvds_bridge_funcs)
+		return;
+
+	panel_bridge = bridge_to_baikal_lvds_bridge(bridge);
+
+	drm_bridge_remove(bridge);
+	devm_kfree(panel_bridge->panel->dev, bridge);
+}
+
+static void devm_baikal_lvds_bridge_release(struct device *dev, void *res)
+{
+	struct drm_bridge **bridge = res;
+
+	baikal_lvds_bridge_remove(*bridge);
+}
+
+struct drm_bridge *devm_baikal_lvds_bridge_add(struct device *dev,
+					     struct drm_panel *panel,
+					     u32 connector_type)
+{
+	struct drm_bridge **ptr, *bridge;
+
+	ptr = devres_alloc(devm_baikal_lvds_bridge_release, sizeof(*ptr),
+			   GFP_KERNEL);
+	if (!ptr)
+		return ERR_PTR(-ENOMEM);
+
+	bridge = baikal_lvds_bridge_add(panel, connector_type);
+	if (!IS_ERR(bridge)) {
+		*ptr = bridge;
+		devres_add(dev, ptr);
+	} else {
+		devres_free(ptr);
+	}
+
+	return bridge;
+}
+
+bool bridge_is_baikal_lvds_bridge(const struct drm_bridge *bridge)
+{
+	return bridge && (bridge->funcs == &baikal_lvds_bridge_funcs);
+}
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_plane.c b/drivers/gpu/drm/baikal/baikal_vdu_plane.c
new file mode 100644
index 0000000000000..d9bdb95a8d962
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_plane.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/of_graph.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+static void baikal_vdu_primary_plane_atomic_update(struct drm_plane *plane,
+					      struct drm_atomic_state *old_state)
+{
+	struct baikal_vdu_private *priv;
+	struct drm_plane_state *state = plane->state;
+	struct drm_crtc *crtc = state->crtc;
+	struct drm_framebuffer *fb = state->fb;
+	uint32_t cntl;
+	uint32_t addr;
+	unsigned long flags;
+
+	if (!fb)
+		return;
+
+	priv = crtc_to_baikal_vdu(crtc);
+	addr = drm_fb_dma_get_gem_addr(fb, state, 0);
+
+	spin_lock_irqsave(&priv->lock, flags);
+	writel(addr, priv->regs + DBAR);
+	spin_unlock_irqrestore(&priv->lock, flags);
+	priv->counters[16]++;
+
+	cntl = readl(priv->regs + CR1);
+	cntl &= ~CR1_BPP_MASK;
+
+	/* Note that the the hardware's format reader takes 'r' from
+	 * the low bit, while DRM formats list channels from high bit
+	 * to low bit as you read left to right.
+	 */
+	switch (fb->format->format) {
+	case DRM_FORMAT_BGR888:
+		cntl |= CR1_BPP24 | CR1_FBP | CR1_BGR;
+		break;
+	case DRM_FORMAT_RGB888:
+		cntl |= CR1_BPP24 | CR1_FBP;
+		break;
+	case DRM_FORMAT_ABGR8888:
+	case DRM_FORMAT_XBGR8888:
+		cntl |= CR1_BPP24 | CR1_BGR;
+		break;
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_XRGB8888:
+		cntl |= CR1_BPP24;
+		break;
+	case DRM_FORMAT_BGR565:
+		cntl |= CR1_BPP16_565 | CR1_BGR;
+		break;
+	case DRM_FORMAT_RGB565:
+		cntl |= CR1_BPP16_565;
+		break;
+	case DRM_FORMAT_ABGR1555:
+	case DRM_FORMAT_XBGR1555:
+		cntl |= CR1_BPP16_555 | CR1_BGR;
+		break;
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_XRGB1555:
+		cntl |= CR1_BPP16_555;
+		break;
+	default:
+		WARN_ONCE(true, "Unknown FB format 0x%08x, set XRGB8888 instead\n",
+				fb->format->format);
+		cntl |= CR1_BPP24;
+		break;
+	}
+
+	writel(cntl, priv->regs + CR1);
+}
+
+static const struct drm_plane_helper_funcs baikal_vdu_primary_plane_helper_funcs = {
+	.atomic_update = baikal_vdu_primary_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs baikal_vdu_primary_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.reset = drm_atomic_helper_plane_reset,
+	.destroy = drm_plane_cleanup,
+	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+int baikal_vdu_primary_plane_init(struct baikal_vdu_private *priv)
+{
+	struct drm_device *drm = priv->drm;
+	struct drm_plane *plane = &priv->primary;
+	static const u32 formats[] = {
+		DRM_FORMAT_BGR888,
+		DRM_FORMAT_RGB888,
+		DRM_FORMAT_ABGR8888,
+		DRM_FORMAT_XBGR8888,
+		DRM_FORMAT_ARGB8888,
+		DRM_FORMAT_XRGB8888,
+		DRM_FORMAT_BGR565,
+		DRM_FORMAT_RGB565,
+		DRM_FORMAT_ABGR1555,
+		DRM_FORMAT_XBGR1555,
+		DRM_FORMAT_ARGB1555,
+		DRM_FORMAT_XRGB1555,
+	};
+	int ret;
+
+	ret = drm_universal_plane_init(drm, plane, 0,
+				       &baikal_vdu_primary_plane_funcs,
+				       formats,
+				       ARRAY_SIZE(formats),
+				       NULL,
+				       DRM_PLANE_TYPE_PRIMARY,
+				       NULL);
+	if (ret)
+		return ret;
+
+	drm_plane_helper_add(plane, &baikal_vdu_primary_plane_helper_funcs);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_regs.h b/drivers/gpu/drm/baikal/baikal_vdu_regs.h
new file mode 100644
index 0000000000000..d48ea8d12051f
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_regs.h
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019-2023 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ */
+
+#ifndef __BAIKAL_VDU_REGS_H__
+#define __BAIKAL_VDU_REGS_H__
+
+#define CR1         0x000
+#define HTR         0x008
+#define VTR1        0x00C
+#define VTR2        0x010
+#define PCTR        0x014
+#define ISR         0x018
+#define IMR         0x01C
+#define IVR         0x020
+#define ISCR        0x024
+#define DBAR        0x028
+#define DCAR        0x02C
+#define DEAR        0x030
+#define PWMFR       0x034
+#define PWMDCR      0x038
+#define HVTER       0x044
+#define HPPLOR      0x048
+#define GPIOR       0x1F8
+#define MRR         0xFFC
+
+#define INTR_UFU    BIT(16)
+#define INTR_BAU    BIT(7)
+#define INTR_VCT    BIT(6)
+#define INTR_MBE    BIT(5)
+#define INTR_FER    BIT(4)
+#define INTR_IFO    BIT(3)
+#define INTR_OFU    BIT(0)
+
+#define CR1_FBP             BIT(19)
+#define CR1_FDW_MASK        GENMASK(17, 16)
+#define CR1_FDW_4_WORDS     (0 << 16)
+#define CR1_FDW_8_WORDS     (1 << 16)
+#define CR1_FDW_16_WORDS    (2 << 16)
+#define CR1_OPS_MASK        GENMASK(14, 12)
+#define CR1_OPS_LCD18       (0 << 13)
+#define CR1_OPS_LCD24       (1 << 13)
+#define CR1_OPS_565         (0 << 12)
+#define CR1_OPS_555         (1 << 12)
+#define CR1_VSP             BIT(11)
+#define CR1_HSP             BIT(10)
+#define CR1_DEP             BIT(8)
+#define CR1_BGR             BIT(5)
+#define CR1_BPP_MASK        GENMASK(4, 2)
+#define CR1_BPP1            (0 << 2)
+#define CR1_BPP2            (1 << 2)
+#define CR1_BPP4            (2 << 2)
+#define CR1_BPP8            (3 << 2)
+#define CR1_BPP16           (4 << 2)
+#define CR1_BPP18           (5 << 2)
+#define CR1_BPP24           (6 << 2)
+#define CR1_LCE             BIT(0)
+
+#define CR1_BPP16_555 ((CR1_BPP16) | (CR1_OPS_555))
+#define CR1_BPP16_565 ((CR1_BPP16) | (CR1_OPS_565))
+
+#define VTR1_VBP_MASK       GENMASK(23, 16)
+#define VTR1_VBP(x)         ((x) << 16)
+#define VTR1_VBP_LSB_WIDTH  8
+#define VTR1_VFP_MASK       GENMASK(15, 8)
+#define VTR1_VFP(x)         ((x) << 8)
+#define VTR1_VFP_LSB_WIDTH  8
+#define VTR1_VSW_MASK       GENMASK(7, 0)
+#define VTR1_VSW(x)         ((x) << 0)
+#define VTR1_VSW_LSB_WIDTH  8
+
+#define VTR2_LPP_MASK       GENMASK(11, 0)
+
+#define HTR_HSW_MASK        GENMASK(31, 24)
+#define HTR_HSW(x)          ((x) << 24)
+#define HTR_HSW_LSB_WIDTH   8
+#define HTR_HBP_MASK        GENMASK(23, 16)
+#define HTR_HBP(x)          ((x) << 16)
+#define HTR_HBP_LSB_WIDTH   8
+#define HTR_PPL_MASK        GENMASK(15, 8)
+#define HTR_PPL(x)          ((x) << 8)
+#define HTR_HFP_MASK        GENMASK(7, 0)
+#define HTR_HFP(x)          ((x) << 0)
+#define HTR_HFP_LSB_WIDTH   8
+
+#define PCTR_PCI2           BIT(11)
+#define PCTR_PCR            BIT(10)
+#define PCTR_PCI            BIT(9)
+#define PCTR_PCB            BIT(8)
+#define PCTR_PCD_MASK       GENMASK(7, 0)
+#define PCTR_MAX_PCD        128
+
+#define ISCR_VSC_OFF        0x0
+#define ISCR_VSC_VSW        0x4
+#define ISCR_VSC_VBP        0x5
+#define ISCR_VSC_VACTIVE    0x6
+#define ISCR_VSC_VFP        0x7
+
+#define PWMFR_PWMPCR        BIT(24)
+#define PWMFR_PWMFCI        BIT(23)
+#define PWMFR_PWMFCE        BIT(22)
+#define PWMFR_PWMFCD_MASK   GENMASK(21, 0)
+#define PWMFR_PWMFCD(x)     ((x) << 0)
+
+#define HVTER_VSWE_MASK     GENMASK(25, 24)
+#define HVTER_VSWE(x)       ((x) << 24)
+#define HVTER_HSWE_MASK     GENMASK(17, 16)
+#define HVTER_HSWE(x)       ((x) << 16)
+#define HVTER_VBPE_MASK     GENMASK(13, 12)
+#define HVTER_VBPE(x)       ((x) << 12)
+#define HVTER_VFPE_MASK     GENMASK(9, 8)
+#define HVTER_VFPE(x)       ((x) << 8)
+#define HVTER_HBPE_MASK     GENMASK(5, 4)
+#define HVTER_HBPE(x)       ((x) << 4)
+#define HVTER_HFPE_MASK     GENMASK(1, 0)
+#define HVTER_HFPE(x)       ((x) << 0)
+
+#define HPPLOR_HPOE         BIT(31)
+#define HPPLOR_HPPLO_MASK   GENMASK(11, 0)
+#define HPPLOR_HPPLO(x)     ((x) << 0)
+
+#define GPIOR_UHD_MASK      GENMASK(23, 16)
+#define GPIOR_UHD_SNGL_PORT (0 << 18)
+#define GPIOR_UHD_DUAL_PORT (1 << 18)
+#define GPIOR_UHD_QUAD_PORT (2 << 18)
+#define GPIOR_UHD_ENB       BIT(17)
+
+#define MRR_DEAR_MRR_MASK   GENMASK(31, 3)
+#define MRR_OUTSTND_RQ_MASK GENMASK(2, 0)
+#define MRR_OUTSTND_RQ(x)   ((x >> 1) << 0)
+#define MRR_MAX_VALUE       ((0xffffffff & MRR_DEAR_MRR_MASK) | MRR_OUTSTND_RQ(4))
+
+#endif /* __BAIKAL_VDU_REGS_H__ */
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 3e6a4e2044c0e..6b51a41ee2c6f 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -162,6 +162,13 @@ config DRM_LVDS_CODEC
 	  Support for transparent LVDS encoders and decoders that don't
 	  require any configuration.
 
+config DRM_BAIKAL_HDMI
+	tristate "Baikal-M HDMI transmitter"
+	default y if ARCH_BAIKAL
+	select DRM_DW_HDMI
+	help
+	  Choose this if you want to use HDMI on Baikal-M.
+
 config DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW
 	tristate "MegaChips stdp4028-ge-b850v3-fw and stdp2690-ge-b850v3-fw"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 6c1d794745054..0a7d15e3ba80e 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3691,6 +3691,15 @@ void dw_hdmi_resume(struct dw_hdmi *hdmi)
 }
 EXPORT_SYMBOL_GPL(dw_hdmi_resume);
 
+struct drm_bridge *dw_hdmi_get_bridge(struct dw_hdmi *hdmi)
+{
+	if (IS_ERR_OR_NULL(hdmi))
+		return NULL;
+
+	return &hdmi->bridge;
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_get_bridge);
+
 MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
 MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
 MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index cfbe020de54e0..4ae9218d5da41 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -295,6 +295,43 @@ int drm_panel_get_modes(struct drm_panel *panel,
 }
 EXPORT_SYMBOL(drm_panel_get_modes);
 
+/**
+ * fwnode_drm_find_panel - look up a panel using a fwnode
+ * @fwnode: fwnode of the panel
+ *
+ * Searches the set of registered panels for one that matches the given fwnode.
+ * If a matching panel is found, return a pointer to it.
+ *
+ * Return: A pointer to the panel registered for the specified fwnode or
+ * an ERR_PTR() if no panel matching the fwnode can be found.
+ *
+ * Possible error codes returned by this function:
+ *
+ * - EPROBE_DEFER: the panel device has not been probed yet, and the caller
+ *   should retry later
+ * - ENODEV: the device is not available
+ */
+struct drm_panel *fwnode_drm_find_panel(const struct fwnode_handle *fwnode)
+{
+	struct drm_panel *panel;
+
+	if (!fwnode_device_is_available(fwnode))
+		return ERR_PTR(-ENODEV);
+
+	mutex_lock(&panel_lock);
+
+	list_for_each_entry(panel, &panel_list, list) {
+		if (panel->dev->fwnode == fwnode) {
+			mutex_unlock(&panel_lock);
+			return panel;
+		}
+	}
+
+	mutex_unlock(&panel_lock);
+	return ERR_PTR(-EPROBE_DEFER);
+}
+EXPORT_SYMBOL(fwnode_drm_find_panel);
+
 #ifdef CONFIG_OF
 /**
  * of_drm_find_panel - look up a panel using a device tree node
diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
index 6a46baa0737cd..08aafb5171aae 100644
--- a/include/drm/bridge/dw_hdmi.h
+++ b/include/drm/bridge/dw_hdmi.h
@@ -174,6 +174,8 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
 
 void dw_hdmi_resume(struct dw_hdmi *hdmi);
 
+struct drm_bridge *dw_hdmi_get_bridge(struct dw_hdmi *hdmi);
+
 void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense);
 
 int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn,
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
index 10015891b056f..196cd8176ac44 100644
--- a/include/drm/drm_panel.h
+++ b/include/drm/drm_panel.h
@@ -28,6 +28,7 @@
 #include <linux/errno.h>
 #include <linux/list.h>
 #include <linux/mutex.h>
+#include <linux/fwnode.h>
 
 struct backlight_device;
 struct dentry;
@@ -284,6 +285,8 @@ int drm_panel_disable(struct drm_panel *panel);
 
 int drm_panel_get_modes(struct drm_panel *panel, struct drm_connector *connector);
 
+struct drm_panel *fwnode_drm_find_panel(const struct fwnode_handle *fwnode);
+
 #if defined(CONFIG_OF) && defined(CONFIG_DRM_PANEL)
 struct drm_panel *of_drm_find_panel(const struct device_node *np);
 int of_drm_get_panel_orientation(const struct device_node *np,
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 18/39] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (16 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 17/39] drm: add Baikal-M SoC video display unit driver Daniil Gnusarev
@ 2024-10-14 14:01 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 19/39] dt-bindings: dw-hdmi: added ahb-audio-regshift Daniil Gnusarev
                   ` (21 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:01 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

The hardware needs non-zero register shift (from DTB), and
a special conf0 parameter.
With this patch I can use HDMI audio on Baikal-M SoC.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 .../drm/bridge/synopsys/dw-hdmi-ahb-audio.c   | 106 ++++++++++++------
 .../gpu/drm/bridge/synopsys/dw-hdmi-audio.h   |   1 +
 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c     |   5 +
 3 files changed, 77 insertions(+), 35 deletions(-)

diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
index 67b8d17a722ad..f500f608fc055 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
@@ -132,12 +132,45 @@ struct snd_dw_hdmi {
 	u8 cs[192][8];
 };
 
-static void dw_hdmi_writel(u32 val, void __iomem *ptr)
+static inline void dw_hdmi_writeb_relaxed(u8 value, const struct dw_hdmi_audio_data *data, int offset)
 {
-	writeb_relaxed(val, ptr);
-	writeb_relaxed(val >> 8, ptr + 1);
-	writeb_relaxed(val >> 16, ptr + 2);
-	writeb_relaxed(val >> 24, ptr + 3);
+	void __iomem *base = data->base;
+	if (data->regshift != 0)
+		offset <<= data->regshift;
+	writeb_relaxed(value, base + offset);
+}
+
+static inline void dw_hdmi_writeb(u8 value, const struct dw_hdmi_audio_data *data, int offset)
+{
+	void __iomem *base = data->base;
+	if (data->regshift != 0)
+		offset <<= data->regshift;
+	writeb(value, base + offset);
+}
+
+static inline u8 dw_hdmi_readb(const struct dw_hdmi_audio_data *data, int offset)
+{
+	void __iomem *base = data->base;
+	if (data->regshift != 0)
+		offset <<= data->regshift;
+	return readb(base + offset);
+
+}
+
+static inline u8 dw_hdmi_readb_relaxed(const struct dw_hdmi_audio_data *data, int offset)
+{
+	void __iomem *base = data->base;
+	if (data->regshift != 0)
+		offset <<= data->regshift;
+	return readb_relaxed(base + offset);
+}
+
+static void dw_hdmi_writel(u32 val, const struct dw_hdmi_audio_data *data, int offset)
+{
+	dw_hdmi_writeb_relaxed(val, data, offset);
+	dw_hdmi_writeb_relaxed(val >> 8, data, offset + 1);
+	dw_hdmi_writeb_relaxed(val >> 16, data, offset + 2);
+	dw_hdmi_writeb_relaxed(val >> 24, data, offset + 3);
 }
 
 /*
@@ -232,7 +265,6 @@ static void dw_hdmi_create_cs(struct snd_dw_hdmi *dw,
 
 static void dw_hdmi_start_dma(struct snd_dw_hdmi *dw)
 {
-	void __iomem *base = dw->data.base;
 	unsigned offset = dw->buf_offset;
 	unsigned period = dw->buf_period;
 	u32 start, stop;
@@ -240,18 +272,18 @@ static void dw_hdmi_start_dma(struct snd_dw_hdmi *dw)
 	dw->reformat(dw, offset, period);
 
 	/* Clear all irqs before enabling irqs and starting DMA */
-	writeb_relaxed(HDMI_IH_AHBDMAAUD_STAT0_ALL,
-		       base + HDMI_IH_AHBDMAAUD_STAT0);
+	dw_hdmi_writeb_relaxed(HDMI_IH_AHBDMAAUD_STAT0_ALL,
+			       &dw->data, HDMI_IH_AHBDMAAUD_STAT0);
 
 	start = dw->buf_addr + offset;
 	stop = start + period - 1;
 
 	/* Setup the hardware start/stop addresses */
-	dw_hdmi_writel(start, base + HDMI_AHB_DMA_STRADDR0);
-	dw_hdmi_writel(stop, base + HDMI_AHB_DMA_STPADDR0);
+	dw_hdmi_writel(start, &dw->data, HDMI_AHB_DMA_STRADDR0);
+	dw_hdmi_writel(stop, &dw->data, HDMI_AHB_DMA_STPADDR0);
 
-	writeb_relaxed((u8)~HDMI_AHB_DMA_MASK_DONE, base + HDMI_AHB_DMA_MASK);
-	writeb(HDMI_AHB_DMA_START_START, base + HDMI_AHB_DMA_START);
+	dw_hdmi_writeb_relaxed((u8)~HDMI_AHB_DMA_MASK_DONE, &dw->data, HDMI_AHB_DMA_MASK);
+	dw_hdmi_writeb(HDMI_AHB_DMA_START_START, &dw->data, HDMI_AHB_DMA_START);
 
 	offset += period;
 	if (offset >= dw->buf_size)
@@ -262,8 +294,8 @@ static void dw_hdmi_start_dma(struct snd_dw_hdmi *dw)
 static void dw_hdmi_stop_dma(struct snd_dw_hdmi *dw)
 {
 	/* Disable interrupts before disabling DMA */
-	writeb_relaxed(~0, dw->data.base + HDMI_AHB_DMA_MASK);
-	writeb_relaxed(HDMI_AHB_DMA_STOP_STOP, dw->data.base + HDMI_AHB_DMA_STOP);
+	dw_hdmi_writeb_relaxed(~0, &dw->data, HDMI_AHB_DMA_MASK);
+	dw_hdmi_writeb_relaxed(HDMI_AHB_DMA_STOP_STOP, &dw->data, HDMI_AHB_DMA_STOP);
 }
 
 static irqreturn_t snd_dw_hdmi_irq(int irq, void *data)
@@ -272,11 +304,11 @@ static irqreturn_t snd_dw_hdmi_irq(int irq, void *data)
 	struct snd_pcm_substream *substream;
 	unsigned stat;
 
-	stat = readb_relaxed(dw->data.base + HDMI_IH_AHBDMAAUD_STAT0);
+	stat = dw_hdmi_readb_relaxed(&dw->data, HDMI_IH_AHBDMAAUD_STAT0);
 	if (!stat)
 		return IRQ_NONE;
 
-	writeb_relaxed(stat, dw->data.base + HDMI_IH_AHBDMAAUD_STAT0);
+	dw_hdmi_writeb_relaxed(stat, &dw->data, HDMI_IH_AHBDMAAUD_STAT0);
 
 	substream = dw->substream;
 	if (stat & HDMI_IH_AHBDMAAUD_STAT0_DONE && substream) {
@@ -319,7 +351,6 @@ static int dw_hdmi_open(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct snd_dw_hdmi *dw = substream->private_data;
-	void __iomem *base = dw->data.base;
 	u8 *eld;
 	int ret;
 
@@ -349,16 +380,16 @@ static int dw_hdmi_open(struct snd_pcm_substream *substream)
 		return ret;
 
 	/* Clear FIFO */
-	writeb_relaxed(HDMI_AHB_DMA_CONF0_SW_FIFO_RST,
-		       base + HDMI_AHB_DMA_CONF0);
+	dw_hdmi_writeb_relaxed(HDMI_AHB_DMA_CONF0_SW_FIFO_RST,
+			       &dw->data, HDMI_AHB_DMA_CONF0);
 
 	/* Configure interrupt polarities */
-	writeb_relaxed(~0, base + HDMI_AHB_DMA_POL);
-	writeb_relaxed(~0, base + HDMI_AHB_DMA_BUFFPOL);
+	dw_hdmi_writeb_relaxed(~0, &dw->data, HDMI_AHB_DMA_POL);
+	dw_hdmi_writeb_relaxed(~0, &dw->data, HDMI_AHB_DMA_BUFFPOL);
 
 	/* Keep interrupts masked, and clear any pending */
-	writeb_relaxed(~0, base + HDMI_AHB_DMA_MASK);
-	writeb_relaxed(~0, base + HDMI_IH_AHBDMAAUD_STAT0);
+	dw_hdmi_writeb_relaxed(~0, &dw->data, HDMI_AHB_DMA_MASK);
+	dw_hdmi_writeb_relaxed(~0, &dw->data, HDMI_IH_AHBDMAAUD_STAT0);
 
 	ret = request_irq(dw->data.irq, snd_dw_hdmi_irq, IRQF_SHARED,
 			  "dw-hdmi-audio", dw);
@@ -366,9 +397,9 @@ static int dw_hdmi_open(struct snd_pcm_substream *substream)
 		return ret;
 
 	/* Un-mute done interrupt */
-	writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL &
-		       ~HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE,
-		       base + HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+	dw_hdmi_writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL &
+			       ~HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE,
+			       &dw->data, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
 
 	return 0;
 }
@@ -378,8 +409,8 @@ static int dw_hdmi_close(struct snd_pcm_substream *substream)
 	struct snd_dw_hdmi *dw = substream->private_data;
 
 	/* Mute all interrupts */
-	writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL,
-		       dw->data.base + HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+	dw_hdmi_writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL,
+			       &dw->data, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
 
 	free_irq(dw->data.irq, dw);
 
@@ -420,6 +451,11 @@ static int dw_hdmi_prepare(struct snd_pcm_substream *substream)
 			HDMI_AHB_DMA_CONF0_INCR8;
 		threshold = 128;
 		break;
+	case 0x2a:
+		conf0 = HDMI_AHB_DMA_CONF0_BURST_MODE |
+			HDMI_AHB_DMA_CONF0_INCR16;
+		threshold = 128;
+		break;
 	default:
 		/* NOTREACHED */
 		return -EINVAL;
@@ -434,9 +470,9 @@ static int dw_hdmi_prepare(struct snd_pcm_substream *substream)
 	conf1 = default_hdmi_channel_config[runtime->channels - 2].conf1;
 	ca = default_hdmi_channel_config[runtime->channels - 2].ca;
 
-	writeb_relaxed(threshold, dw->data.base + HDMI_AHB_DMA_THRSLD);
-	writeb_relaxed(conf0, dw->data.base + HDMI_AHB_DMA_CONF0);
-	writeb_relaxed(conf1, dw->data.base + HDMI_AHB_DMA_CONF1);
+	dw_hdmi_writeb_relaxed(threshold, &dw->data, HDMI_AHB_DMA_THRSLD);
+	dw_hdmi_writeb_relaxed(conf0, &dw->data, HDMI_AHB_DMA_CONF0);
+	dw_hdmi_writeb_relaxed(conf1, &dw->data, HDMI_AHB_DMA_CONF1);
 
 	dw_hdmi_set_channel_count(dw->data.hdmi, runtime->channels);
 	dw_hdmi_set_channel_allocation(dw->data.hdmi, ca);
@@ -528,10 +564,10 @@ static int snd_dw_hdmi_probe(struct platform_device *pdev)
 	unsigned revision;
 	int ret;
 
-	writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL,
-		       data->base + HDMI_IH_MUTE_AHBDMAAUD_STAT0);
-	revision = readb_relaxed(data->base + HDMI_REVISION_ID);
-	if (revision != 0x0a && revision != 0x1a) {
+	dw_hdmi_writeb_relaxed(HDMI_IH_MUTE_AHBDMAAUD_STAT0_ALL,
+			       data, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+	revision = dw_hdmi_readb_relaxed(data, HDMI_REVISION_ID);
+	if (revision != 0x0a && revision != 0x1a && revision != 0x2a) {
 		dev_err(dev, "dw-hdmi-audio: unknown revision 0x%02x\n",
 			revision);
 		return -ENXIO;
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h
index f72d27208ebef..3250588d39ff0 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h
@@ -10,6 +10,7 @@ struct dw_hdmi_audio_data {
 	int irq;
 	struct dw_hdmi *hdmi;
 	u8 *(*get_eld)(struct dw_hdmi *hdmi);
+	unsigned regshift;
 };
 
 struct dw_hdmi_i2s_audio_data {
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 0a7d15e3ba80e..627fcff89c6de 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3560,6 +3560,11 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
 		audio.irq = irq;
 		audio.hdmi = hdmi;
 		audio.get_eld = hdmi_audio_get_eld;
+		if (of_property_read_u32(np, "ahb-audio-regshift", &audio.regshift) != 0) {
+			audio.regshift = 0;
+		} else {
+			dev_dbg(dev, "set audio.regshift=%u from DTB\n", audio.regshift);
+		}
 		hdmi->enable_audio = dw_hdmi_ahb_audio_enable;
 		hdmi->disable_audio = dw_hdmi_ahb_audio_disable;
 
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 19/39] dt-bindings: dw-hdmi: added ahb-audio-regshift
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (17 preceding siblings ...)
  2024-10-14 14:01 ` [d-kernel] [PATCH 18/39] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 20/39] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M Daniil Gnusarev
                   ` (20 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

Hardware revision 0x2a needs a register offset. It can't be
auto-detected: to figure out the hardware revision one need
to read HDMI_REVISION_ID register, and to read a register
one need to know the registers offset shift. Hence the correct
register offset shift has to be specified in the device tree.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 .../bindings/display/bridge/synopsys,dw-hdmi.yaml          | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/Documentation/devicetree/bindings/display/bridge/synopsys,dw-hdmi.yaml b/Documentation/devicetree/bindings/display/bridge/synopsys,dw-hdmi.yaml
index 4b7e54a8f037f..85b3ac7d10574 100644
--- a/Documentation/devicetree/bindings/display/bridge/synopsys,dw-hdmi.yaml
+++ b/Documentation/devicetree/bindings/display/bridge/synopsys,dw-hdmi.yaml
@@ -29,6 +29,13 @@ properties:
     enum: [1, 4]
     default: 1
 
+  ahb-audio-regshift:
+    description:
+      AHB audio registers offset shift
+    $ref: /schemas/types.yaml#/definitions/uint32
+    enum: [0, 2]
+    default: 0
+
   clocks:
     minItems: 2
     maxItems: 5
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 20/39] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (18 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 19/39] dt-bindings: dw-hdmi: added ahb-audio-regshift Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 21/39] drm/panfrost: forcibly set dma-coherent on Baikal-M Daniil Gnusarev
                   ` (19 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

Hardware revision 0x2a needs a register offset. It can't be
auto-detected: to figure out the hardware revision one need
to read HDMI_REVISION_ID register, and to read a register
one need to know the register offset shift. Hence the correct
register offset shift has to be specified in the device tree
(supplied by UEFI firmware). Alas the device tree blob passed
by Baikal-M UEFI does not contain this regshift. Hence force
the correct regshift for Baikal-M.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-DONTUPSTREAM
X-feature-Baikal-M
---
 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 627fcff89c6de..34f35dcd9a867 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3565,6 +3565,11 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
 		} else {
 			dev_dbg(dev, "set audio.regshift=%u from DTB\n", audio.regshift);
 		}
+		if (of_device_is_compatible(np, "baikal,hdmi")) {
+			audio.regshift = 2;
+			dev_info(dev, "setting audio.regshift=%d for BE-M1000 SoC\n",
+				 audio.regshift);
+		}
 		hdmi->enable_audio = dw_hdmi_ahb_audio_enable;
 		hdmi->disable_audio = dw_hdmi_ahb_audio_disable;
 
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 21/39] drm/panfrost: forcibly set dma-coherent on Baikal-M
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (19 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 20/39] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 22/39] drm/panfrost: disable devfreq " Daniil Gnusarev
                   ` (18 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

With memattr 0x888d88 (set by arm_mali_lpae_alloc_pgtable) GPU
(Mali T628 r1p0) experiences a lot of DATA_INVALID faults,
unhandled page faults, and other errors. Also the screen goes
black almost immediately.

On the other hand with memattr 0x484d48 (as set by mali_kbase)
the GPU appears to work just fine.

Robin Murphy <robin.murphy@arm.com> explains:

> using the outer-cacheable attribute is deliberate because it is necessary
> for I/O-coherent GPUs to work properly (and should be irrelevant for
> non-coherent integrations)

> I'd note that panfrost has been working OK - to the extent that Mesa
> supports its older ISA - on the T624 (single core group) in Arm's
> Juno SoC for over a year now since commit 268af50f38b1.

> If you have to force outer non-cacheable to avoid getting translation
> faults and other errors that look like the GPU is inexplicably seeing
> the wrong data, I'd check whether you have the same thing where your
> integration is actually I/O-coherent and you're missing the "dma-coherent"
> property in your DT.

Indeed setting the "dma-coherent" property (and adjusting jobs affinity
for dual core group GPU) makes panfrost work just fine on Baikal-M.

However on Baikal-M the FDT is passed to the kernel by the firmware,
and replacing the FDT in the firmware is tricky.
Therefore set `coherent` property when running on Baikal-M (even
if the `dma-coherent` property is missing in the FDT).

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-DONTUPSTREAM
X-feature-Baikal-M
---
 drivers/gpu/drm/panfrost/panfrost_drv.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/gpu/drm/panfrost/panfrost_drv.c b/drivers/gpu/drm/panfrost/panfrost_drv.c
index ddcc8259061bb..bab52e65d80cf 100644
--- a/drivers/gpu/drm/panfrost/panfrost_drv.c
+++ b/drivers/gpu/drm/panfrost/panfrost_drv.c
@@ -568,6 +568,11 @@ static int panfrost_probe(struct platform_device *pdev)
 		return -ENODEV;
 
 	pfdev->coherent = device_get_dma_attr(&pdev->dev) == DEV_DMA_COHERENT;
+	if (!pfdev->coherent && (of_device_is_compatible(of_root, "baikal,baikal-m") ||
+			of_device_is_compatible(of_root, "baikal,bm1000"))) {
+		pfdev->coherent = true;
+		dev_warn(&pdev->dev, "marking as DMA coherent on BE-M1000");
+	}
 
 	/* Allocate and initialize the DRM device. */
 	ddev = drm_dev_alloc(&panfrost_drm_driver, &pdev->dev);
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 22/39] drm/panfrost: disable devfreq on Baikal-M
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (20 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 21/39] drm/panfrost: forcibly set dma-coherent on Baikal-M Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 23/39] bmc: add board management controller driver Daniil Gnusarev
                   ` (17 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

Enabling GPU frequency scaling on Baikal-M cases GPU MMU lockup:

  [   38.108633] panfrost 2a200000.gpu: AS_ACTIVE bit stuck

Since GPU and CPU share the memory this locks up the whole system.
Therefore disable devfreq on Baikal-M.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-DONTUPSTREAM
X-feature-Baikal-M
---
 drivers/gpu/drm/panfrost/panfrost_devfreq.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/gpu/drm/panfrost/panfrost_devfreq.c b/drivers/gpu/drm/panfrost/panfrost_devfreq.c
index e78de99e99335..926f8f9b4187e 100644
--- a/drivers/gpu/drm/panfrost/panfrost_devfreq.c
+++ b/drivers/gpu/drm/panfrost/panfrost_devfreq.c
@@ -126,6 +126,11 @@ int panfrost_devfreq_init(struct panfrost_device *pfdev)
 		DRM_DEV_INFO(dev, "More than 1 supply is not supported yet\n");
 		return 0;
 	}
+	if (of_device_is_compatible(of_root, "baikal,baikal-m") ||
+			of_device_is_compatible(of_root, "baikal,bm1000")) {
+		dev_info(pfdev->dev, "disabling GPU devfreq on BE-M1000\n");
+		return 0;
+	}
 
 	ret = panfrost_read_speedbin(dev);
 	if (ret)
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 23/39] bmc: add board management controller driver
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (21 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 22/39] drm/panfrost: disable devfreq " Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 24/39] pm: disable all sleep states on Baikal-M based boards Daniil Gnusarev
                   ` (16 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

The board management controller (BMC) device is responsible for CPU
kick-starting, controlling power button, and a full board poweroff.
Driver taken from SDK-ARM64-2403-6.6 of Baikal Electronics.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 drivers/misc/Kconfig  |  16 +
 drivers/misc/Makefile |   1 +
 drivers/misc/tp_bmc.c | 768 ++++++++++++++++++++++++++++++++++++++++++
 kernel/exit.c         |   1 +
 4 files changed, 786 insertions(+)
 create mode 100644 drivers/misc/tp_bmc.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index cadd4a820c033..8435a23e886ee 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -519,6 +519,22 @@ config VCPU_STALL_DETECTOR
 
 	  If you do not intend to run this kernel as a guest, say N.
 
+config TP_BMC
+	tristate "T-Platforms Baikal-T(1)/M BMC"
+	depends on I2C && OF
+	select PINCTRL
+	select GENERIC_PINCONF
+	select SERIO
+	help
+	  Say Y here if you want to build a driver for T-Platforms BMC devices
+	  embedded into the boards with Baikal-T(1)/M processors. The device main
+	  purpose is the CPU kick-starting as well as some additional side-way
+	  functionality like power on/off buttons state tracing and full device
+	  powering off.
+
+	  If you choose to build module, its name will be tp-bt-bmc. If unsure,
+	  say N here.
+
 config TMR_MANAGER
 	tristate "Select TMR Manager"
 	depends on MICROBLAZE && MB_MANAGER
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index f2a4d1ff65d46..3933d2c2116a1 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -63,6 +63,7 @@ obj-$(CONFIG_HI6421V600_IRQ)	+= hi6421v600-irq.o
 obj-$(CONFIG_OPEN_DICE)		+= open-dice.o
 obj-$(CONFIG_GP_PCI1XXXX)	+= mchp_pci1xxxx/
 obj-$(CONFIG_VCPU_STALL_DETECTOR)	+= vcpu_stall_detector.o
+obj-$(CONFIG_TP_BMC)		+= tp_bmc.o
 obj-$(CONFIG_TMR_MANAGER)      += xilinx_tmr_manager.o
 obj-$(CONFIG_TMR_INJECT)	+= xilinx_tmr_inject.o
 obj-$(CONFIG_TPS6594_ESM)	+= tps6594-esm.o
diff --git a/drivers/misc/tp_bmc.c b/drivers/misc/tp_bmc.c
new file mode 100644
index 0000000000000..44001eaa4a64f
--- /dev/null
+++ b/drivers/misc/tp_bmc.c
@@ -0,0 +1,768 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/i2c.h>
+#include <linux/bcd.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/input.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/pm.h>
+#include <linux/rtc.h>
+#include <linux/serio.h>
+#include <linux/platform_device.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+
+enum I2C_REGS {
+	R_ID1 = 0,
+	R_ID2,
+	R_ID3,
+	R_ID4,
+	R_SOFTOFF_RQ,
+	R_PWROFF_RQ,
+	R_PWRBTN_STATE,
+	R_VERSION1,
+	R_VERSION2,
+	R_BOOTREASON,
+	R_BOOTREASON_ARG,
+	R_SCRATCH1,
+	R_SCRATCH2,
+	R_SCRATCH3,
+	R_SCRATCH4,
+	R_CAP,
+	R_GPIODIR0,
+	R_GPIODIR1,
+	R_GPIODIR2,
+	R_COUNT
+};
+
+#define BMC_ID1_VAL		0x49
+#define BMC_ID2_VAL		0x54
+#define BMC_ID3_VAL		0x58
+#define BMC_ID4_VAL0		0x32
+#define BMC_ID4_VAL1		0x2
+
+#define BMC_VERSION1		0
+#define BMC_VERSION2		2
+#define BMC_VERSION2_3		3
+
+#define BMC_CAP_PWRBTN		0x1
+#define BMC_CAP_TOUCHPAD	0x2
+#define BMC_CAP_RTC		0x4
+#define BMC_CAP_FRU		0x8
+#define BMC_CAP_GPIODIR		0x10
+
+#define BMC_SERIO_BUFSIZE	7
+
+struct bmc_poll_data {
+	struct i2c_client *c;
+};
+
+static struct i2c_client	*bmc_i2c;
+static struct i2c_client	*rtc_i2c;
+static struct i2c_driver	mitx2_bmc_i2c_driver;
+static struct input_dev		*button_dev;
+static struct bmc_poll_data	poll_data;
+static struct task_struct	*polling_task;
+#ifdef CONFIG_SERIO
+static struct i2c_client	*serio_i2c;
+static struct task_struct	*touchpad_task;
+#endif
+static u8 bmc_proto_version[3];
+static u8 bmc_bootreason[2];
+static u8 bmc_scratch[4];
+static int bmc_cap;
+static const char input_name[] = "BMC input dev";
+static u8 prev_softoff_ret;
+
+static int bmc_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	u8 rtc_buf[8];
+	struct i2c_msg msg;
+	int t;
+	int rc;
+
+	msg.addr = client->addr;
+	msg.flags = I2C_M_RD;
+	msg.len = 8;
+	msg.buf = rtc_buf;
+	rc = i2c_transfer(client->adapter, &msg, 1);
+	if (rc != 1) {
+		dev_err(dev, "rtc_read_time: i2c_transfer error %d\n", rc);
+		return rc;
+	}
+
+	tm->tm_sec  = bcd2bin(rtc_buf[0] & 0x7f);
+	tm->tm_min  = bcd2bin(rtc_buf[1] & 0x7f);
+	tm->tm_hour = bcd2bin(rtc_buf[2] & 0x3f);
+	if (rtc_buf[3] & (1 << 6)) /* PM */
+		tm->tm_hour += 12;
+
+	tm->tm_mday = bcd2bin(rtc_buf[4] & 0x3f);
+	tm->tm_mon  = bcd2bin(rtc_buf[5] & 0x1f);
+	t = rtc_buf[5] >> 5;
+	tm->tm_wday = (t == 7) ? 0 : t;
+	tm->tm_year = bcd2bin(rtc_buf[6]) + 100; /* year since 1900 */
+	tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
+	tm->tm_isdst = 0;
+
+	return rtc_valid_tm(tm);
+}
+
+static int bmc_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	u8 rtc_buf[8];
+	struct i2c_msg msg;
+	int rc;
+	u8 seconds, minutes, hours, wday, mday, month, years;
+
+	seconds	= bin2bcd(tm->tm_sec);
+	minutes	= bin2bcd(tm->tm_min);
+	hours	= bin2bcd(tm->tm_hour);
+	wday	= tm->tm_wday ? tm->tm_wday : 0x7;
+	mday	= bin2bcd(tm->tm_mday);
+	month	= bin2bcd(tm->tm_mon);
+	years	= bin2bcd(tm->tm_year % 100);
+
+	/* TODO: need sanity check? */
+	rtc_buf[0] = seconds;
+	rtc_buf[1] = minutes;
+	rtc_buf[2] = hours;
+	rtc_buf[3] = 0;
+	rtc_buf[4] = mday;
+	rtc_buf[5] = month | (wday << 5);
+	rtc_buf[6] = years;
+	rtc_buf[7] = 0;
+
+	msg.addr = client->addr;
+	msg.flags = 0;
+	msg.len = 8;
+	msg.buf = rtc_buf;
+
+	dev_dbg(dev, "rtc_set_time: %08x-%08x\n",
+		*(uint32_t *)&rtc_buf[0],
+		*(uint32_t *)&rtc_buf[4]);
+
+	rc = i2c_transfer(client->adapter, &msg, 1);
+	if (rc != 1)
+		dev_err(dev, "i2c write: %d\n", rc);
+
+	return rc == 1 ? 0 : -EIO;
+}
+
+static const struct rtc_class_ops bmc_rtc_ops = {
+	.read_time = bmc_rtc_read_time,
+	.set_time  = bmc_rtc_set_time,
+};
+
+#ifdef CONFIG_SERIO
+/* BMC serio (PS/2 touchpad) interface */
+static int bmc_serio_write(struct serio *id, unsigned char val)
+{
+	struct i2c_client *client = id->port_data;
+	u8 buf[4];
+	struct i2c_msg msg;
+	int rc;
+
+	buf[0] = val;
+	msg.addr = client->addr;
+	msg.flags = 0;
+	msg.len = 1;
+	msg.buf = buf;
+	dev_dbg(&client->dev, "%s: 0x%02x\n", __func__, val);
+	rc = i2c_transfer(client->adapter, &msg, 1);
+	if (rc != 1)
+		dev_err(&client->dev, "i2c write: %d\n", rc);
+
+	return rc == 1 ? 0 : -EIO;
+}
+
+/* returns: -1 on error, +1 if more data available, 0 otherwise */
+static int bmc_serio_read(struct i2c_client *client)
+{
+	struct serio *serio = dev_get_drvdata(&client->dev);
+	int i, rc, cnt;
+	u8 buf[BMC_SERIO_BUFSIZE];
+	struct i2c_msg msg;
+
+	msg.addr = client->addr;
+	msg.flags = I2C_M_RD;
+	msg.len = BMC_SERIO_BUFSIZE;
+	msg.buf = buf;
+	rc = i2c_transfer(client->adapter, &msg, 1);
+	if (rc != 1) {
+		dev_err(&client->dev,
+			"%s: i2c_transfer error %d\n", __func__, rc);
+		return -1;
+	}
+
+	cnt = buf[0];
+	rc = 0;
+	if (cnt > BMC_SERIO_BUFSIZE - 1) {
+		cnt = BMC_SERIO_BUFSIZE - 1;
+		rc = 1;
+	}
+
+	for (i = 0; i < cnt; i++)
+		serio_interrupt(serio, buf[i + 1], 0);
+
+	return 0;
+}
+
+int touchpad_poll_fn(void *data)
+{
+	int ret;
+
+	while (1) {
+		if (kthread_should_stop())
+			break;
+
+		while ((ret = bmc_serio_read(serio_i2c)) > 0)
+			;
+
+		if (ret < 0)
+			msleep_interruptible(10000);
+
+		msleep_interruptible(10);
+	}
+
+	return 0;
+}
+#endif /* CONFIG_SERIO */
+
+#ifdef CONFIG_PINCTRL
+static u8 bmc_pincf_state[3];
+#define BMC_NPINS	(sizeof(bmc_pincf_state) * 8)
+
+static struct pinctrl_pin_desc bmc_pin_desc[BMC_NPINS] = {
+	PINCTRL_PIN(0, "P0"),
+	PINCTRL_PIN(1, "P1"),
+	PINCTRL_PIN(2, "P2"),
+	PINCTRL_PIN(3, "P3"),
+	PINCTRL_PIN(4, "P4"),
+	PINCTRL_PIN(5, "P5"),
+	PINCTRL_PIN(6, "P6"),
+	PINCTRL_PIN(7, "P7"),
+	PINCTRL_PIN(8, "P8"),
+	PINCTRL_PIN(9, "P9"),
+	PINCTRL_PIN(10, "P10"),
+	PINCTRL_PIN(11, "P11"),
+	PINCTRL_PIN(12, "P12"),
+	PINCTRL_PIN(13, "P13"),
+	PINCTRL_PIN(14, "P14"),
+	PINCTRL_PIN(15, "P15"),
+	PINCTRL_PIN(16, "P16"),
+	PINCTRL_PIN(17, "P17"),
+	PINCTRL_PIN(18, "P18"),
+	PINCTRL_PIN(19, "P19"),
+	PINCTRL_PIN(20, "P20"),
+	PINCTRL_PIN(21, "P21"),
+	PINCTRL_PIN(22, "P22"),
+	PINCTRL_PIN(23, "P23"),
+};
+
+#define PCTRL_DEV	"bmc_pinctrl"
+
+static int bmc_pin_config_get(struct pinctrl_dev *pctldev,
+			      unsigned int pin,
+			      unsigned long *config)
+{
+	int idx, bit;
+
+	if (pin > BMC_NPINS)
+		return -EINVAL;
+
+	idx = pin >> 3;
+	bit = pin & 7;
+
+	*config = !!(bmc_pincf_state[idx] & (1 << bit));
+	return 0;
+}
+
+static int bmc_pin_config_set(struct pinctrl_dev *pctldev,
+			      unsigned int pin,
+			      unsigned long *config,
+			      unsigned int nc)
+{
+	int idx, bit;
+	enum pin_config_param param;
+	int arg;
+
+	if (pin > BMC_NPINS)
+		return -EINVAL;
+
+	idx = pin >> 3;
+	bit = pin & 7;
+
+	param = pinconf_to_config_param(*config);
+	arg = pinconf_to_config_argument(*config);
+	if (param != PIN_CONFIG_OUTPUT)
+		return -EINVAL;
+
+	if (arg)
+		bmc_pincf_state[idx] |= (1 << bit);
+	else
+		bmc_pincf_state[idx] &= ~(1 << bit);
+
+	dev_dbg(&bmc_i2c->dev,
+		"%s: pin %u, dir %lu\n", __func__, pin, *config);
+
+	return i2c_smbus_write_byte_data(bmc_i2c, R_GPIODIR0 + idx,
+					 bmc_pincf_state[idx]);
+}
+
+void pinconf_generic_dump_config(struct pinctrl_dev *pctldev,
+				 struct seq_file *s, unsigned long config);
+
+void pinctrl_utils_free_map(struct pinctrl_dev *pctldev,
+			    struct pinctrl_map *map, unsigned num_maps);
+
+static const struct pinconf_ops bmc_confops = {
+	.pin_config_get = bmc_pin_config_get,
+	.pin_config_set = bmc_pin_config_set,
+	.pin_config_config_dbg_show = pinconf_generic_dump_config,
+};
+
+static int bmc_groups_count(struct pinctrl_dev *pctldev)
+{
+	return 0;
+}
+
+static const char *bmc_group_name(struct pinctrl_dev *pctldev,
+				  unsigned int selector)
+{
+	return NULL;
+}
+
+static const struct pinctrl_ops bmc_ctrl_ops = {
+	.get_groups_count = bmc_groups_count,
+	.get_group_name = bmc_group_name,
+	.dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
+	.dt_free_map = pinctrl_utils_free_map,
+};
+
+static struct pinctrl_desc bmc_pincrtl_desc = {
+	.name = PCTRL_DEV,
+	.pins = bmc_pin_desc,
+	.pctlops = &bmc_ctrl_ops,
+	.npins = BMC_NPINS,
+	.confops = &bmc_confops,
+};
+
+static struct pinctrl_dev *bmc_pinctrl_dev;
+
+static int bmc_pinctrl_register(struct device *dev)
+{
+	struct pinctrl_dev *pctrl_dev;
+	struct platform_device *pbdev;
+
+	pbdev = platform_device_alloc(PCTRL_DEV, -1);
+	pbdev->dev.parent = dev;
+	pbdev->dev.of_node = of_find_node_by_name(dev->of_node, "bmc_pinctrl");
+	platform_device_add(pbdev);
+	pctrl_dev = devm_pinctrl_register(&pbdev->dev, &bmc_pincrtl_desc, NULL);
+	if (IS_ERR(pctrl_dev)) {
+		dev_err(&pbdev->dev,
+			"Can't register pinctrl (%ld)\n", PTR_ERR(pctrl_dev));
+		return PTR_ERR(pctrl_dev);
+	}
+
+	dev_dbg(&pbdev->dev, "BMC pinctrl registered\n");
+	bmc_pinctrl_dev = pctrl_dev;
+	/* reset all pins to default state */
+	i2c_smbus_write_byte_data(to_i2c_client(dev), R_GPIODIR0, 0);
+	i2c_smbus_write_byte_data(to_i2c_client(dev), R_GPIODIR1, 0);
+	i2c_smbus_write_byte_data(to_i2c_client(dev), R_GPIODIR2, 0);
+	return 0;
+}
+
+static void bmc_pinctrl_unregister(void)
+{
+	if (bmc_pinctrl_dev)
+		devm_pinctrl_unregister(&bmc_i2c->dev, bmc_pinctrl_dev);
+}
+#endif
+
+void bmc_pwroff_rq(void)
+{
+	int ret;
+
+	dev_info(&bmc_i2c->dev, "Write reg R_PWROFF_RQ\n");
+	ret = i2c_smbus_write_byte_data(bmc_i2c, R_PWROFF_RQ, 0x01);
+	dev_info(&bmc_i2c->dev, "ret: %i\n", ret);
+}
+
+int pwroff_rq_poll_fn(void *data)
+{
+	int ret;
+
+	while (1) {
+		if (kthread_should_stop())
+			break;
+
+		dev_dbg(&poll_data.c->dev, "Polling\n");
+		ret = i2c_smbus_read_byte_data(poll_data.c, R_SOFTOFF_RQ);
+		dev_dbg(&poll_data.c->dev, "Polling returned: %i\n", ret);
+
+		if (ret < 0) {
+			dev_err(&poll_data.c->dev,
+				"Could not read register 0x%x\n",
+				R_SOFTOFF_RQ);
+			return -EIO;
+		}
+
+		if (prev_softoff_ret != ret) {
+			dev_info(&poll_data.c->dev, "key change [%i]\n", ret);
+			if (ret != 0) {
+				dev_info(&poll_data.c->dev,
+					 "PWROFF \"irq\" detected [%i]\n", ret);
+				input_event(button_dev, EV_KEY, KEY_POWER, 1);
+			} else {
+				input_event(button_dev, EV_KEY, KEY_POWER, 0);
+			}
+
+			input_sync(button_dev);
+			prev_softoff_ret = ret;
+		}
+
+		msleep_interruptible(100);
+	}
+
+	do_exit(1);
+	return 0;
+}
+
+static int mitx2_bmc_validate(struct i2c_client *client)
+{
+	int ret = 0;
+	int i = 0;
+	static const u8 regs[] = {R_ID1, R_ID2, R_ID3};
+	static const u8 vals[] = {BMC_ID1_VAL, BMC_ID2_VAL, BMC_ID3_VAL};
+
+	bmc_proto_version[0] = 0;
+	bmc_proto_version[1] = 0;
+	bmc_proto_version[2] = 0;
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++) {
+		ret = i2c_smbus_read_byte_data(client, regs[i]);
+		if (ret < 0) {
+			dev_err(&client->dev, "Could not read register 0x%x\n",
+				regs[i]);
+			return -EIO;
+		}
+
+		if (ret != vals[i]) {
+			dev_err(&client->dev,
+				"Bad value [0x%02x] in register 0x%x, should be [0x%02x]\n",
+				ret, regs[i], vals[i]);
+			return -ENODEV;
+		}
+	}
+
+	ret = i2c_smbus_read_byte_data(client, R_ID4);
+	if (ret < 0) {
+		dev_err(&client->dev, "Could not read register 0x%x\n", R_ID4);
+		return -EIO;
+	}
+
+	if (ret == BMC_ID4_VAL0) {
+		bmc_proto_version[0] = 0;
+	} else if (ret == BMC_ID4_VAL1) {
+		bmc_proto_version[0] = 2;
+		ret = i2c_smbus_read_byte_data(client, R_VERSION1);
+		if (ret < 0) {
+			dev_err(&client->dev, "Could not read register 0x%x\n",
+				R_VERSION1);
+			return -EIO;
+		}
+
+		bmc_proto_version[1] = ret;
+
+		ret = i2c_smbus_read_byte_data(client, R_VERSION2);
+		if (ret < 0) {
+			dev_err(&client->dev, "Could not read register 0x%x\n",
+				R_VERSION2);
+			return -EIO;
+		}
+
+		bmc_proto_version[2] = ret;
+
+		ret = i2c_smbus_read_byte_data(client, R_BOOTREASON);
+		if (ret < 0) {
+			dev_err(&client->dev, "Could not read register 0x%x\n",
+				R_BOOTREASON);
+			return -EIO;
+		}
+
+		bmc_bootreason[0] = ret;
+
+		ret = i2c_smbus_read_byte_data(client, R_BOOTREASON_ARG);
+		if (ret < 0) {
+			dev_err(&client->dev, "Could not read register 0x%x\n",
+				R_BOOTREASON_ARG);
+			return -EIO;
+		}
+
+		bmc_bootreason[1] = ret;
+
+		for (i = R_SCRATCH1; i <= R_SCRATCH4; i++) {
+			ret = i2c_smbus_read_byte_data(client, i);
+			if (ret < 0) {
+				dev_err(&client->dev,
+					"Could not read register 0x%x\n", i);
+				return -EIO;
+			}
+
+			bmc_scratch[i - R_SCRATCH1] = ret;
+		}
+
+		if (bmc_proto_version[2] >= BMC_VERSION2_3) {
+			ret = i2c_smbus_read_byte_data(client, R_CAP);
+			if (ret >= 0)
+				bmc_cap = ret;
+		} else {
+			bmc_cap = BMC_CAP_PWRBTN;
+		}
+
+		dev_info(&client->dev,
+			 "bootreason %i:%i, extcap 0x%x\n",
+			 bmc_bootreason[0],
+			 bmc_bootreason[1],
+			 bmc_cap);
+	} else {
+		dev_err(&client->dev, "Bad value [0x%02x] in register 0x%x\n",
+			ret, R_ID4);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int bmc_create_client_devices(struct device *bmc_dev)
+{
+	int ret = 0;
+	struct rtc_device *rtc_dev;
+	struct i2c_client *client = to_i2c_client(bmc_dev);
+	int client_addr = client->addr + 1;
+
+	if (bmc_cap & BMC_CAP_TOUCHPAD) {
+#ifdef CONFIG_SERIO
+		struct serio *serio;
+
+		serio_i2c = i2c_new_ancillary_device(client,
+						     "bmc_serio", client_addr);
+		if (IS_ERR(serio_i2c)) {
+			dev_err(&client->dev, "Can't get serio secondary\n");
+			serio_i2c = NULL;
+			ret = -ENOMEM;
+			goto fail;
+		}
+
+		serio = devm_kzalloc(&serio_i2c->dev,
+				     sizeof(struct serio), GFP_KERNEL);
+		if (!serio) {
+			ret = -ENOMEM;
+			i2c_unregister_device(serio_i2c);
+			serio_i2c = NULL;
+			goto skip_tp;
+		}
+
+		serio->write = bmc_serio_write;
+		serio->port_data = serio_i2c;
+		serio->id.type = SERIO_PS_PSTHRU;
+		serio_register_port(serio);
+		dev_set_drvdata(&serio_i2c->dev, serio);
+		touchpad_task = kthread_run(touchpad_poll_fn, NULL,
+					    "BMC serio poll task");
+skip_tp:
+#endif
+		client_addr++;
+	}
+
+	if (bmc_cap & BMC_CAP_RTC) {
+		rtc_i2c = i2c_new_ancillary_device(client,
+						   "bmc_rtc", client_addr);
+		if (IS_ERR(rtc_i2c)) {
+			dev_err(&client->dev, "Can't get RTC secondary\n");
+			rtc_i2c = NULL;
+			ret = -ENOMEM;
+			goto fail;
+		}
+
+		rtc_dev = devm_rtc_device_register(&rtc_i2c->dev, "bmc_rtc",
+						   &bmc_rtc_ops, THIS_MODULE);
+		if (IS_ERR(rtc_dev)) {
+			ret = PTR_ERR(rtc_dev);
+			dev_err(&client->dev,
+				"Failed to register RTC device: %d\n", ret);
+			i2c_unregister_device(rtc_i2c);
+			rtc_i2c = NULL;
+		}
+fail:
+		client_addr++;
+	}
+#ifdef CONFIG_PINCTRL
+	if (bmc_cap & BMC_CAP_GPIODIR || 1 /*vvv*/)
+		bmc_pinctrl_register(bmc_dev);
+#endif
+	return ret;
+}
+
+static int mitx2_bmc_i2c_probe(struct i2c_client *client)
+{
+	int err = 0;
+	int i = 0;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+		return -ENODEV;
+
+	for (i = 0; i < 10; i++) {
+		err = mitx2_bmc_validate(client);
+		if (!err)
+			break;
+
+		msleep_interruptible(20);
+	}
+
+	if (err)
+		return err;
+
+	if (bmc_cap & BMC_CAP_PWRBTN) {
+		button_dev = input_allocate_device();
+		if (!button_dev) {
+			dev_err(&client->dev, "Not enough memory\n");
+			return -ENOMEM;
+		}
+
+		button_dev->id.bustype = BUS_I2C;
+		button_dev->dev.parent = &client->dev;
+		button_dev->name = input_name;
+		button_dev->phys = "bmc-input0";
+		button_dev->evbit[0] = BIT_MASK(EV_KEY);
+		button_dev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER);
+
+		err = input_register_device(button_dev);
+		if (err) {
+			dev_err(&client->dev, "Failed to register device\n");
+			input_free_device(button_dev);
+			return err;
+		}
+
+		dev_dbg(&client->dev, "Starting polling thread\n");
+		poll_data.c = client;
+		polling_task = kthread_run(pwroff_rq_poll_fn, NULL,
+					   "BMC poll task");
+	}
+
+	if (bmc_cap || 1 /*vvv*/)
+		err = bmc_create_client_devices(&client->dev);
+
+	bmc_i2c = client;
+	/* register as poweroff handler */
+	pm_power_off = bmc_pwroff_rq;
+
+	return 0;
+}
+
+static void mitx2_bmc_i2c_remove(struct i2c_client *client)
+{
+#ifdef CONFIG_SERIO
+	struct serio *serio;
+#endif
+	if (button_dev) {
+		kthread_stop(polling_task);
+		input_unregister_device(button_dev);
+	}
+#ifdef CONFIG_SERIO
+	if (serio_i2c) {
+		kthread_stop(touchpad_task);
+		serio = dev_get_drvdata(&serio_i2c->dev);
+		serio_unregister_port(serio);
+		i2c_unregister_device(serio_i2c);
+	}
+#endif
+	if (rtc_i2c)
+		i2c_unregister_device(rtc_i2c);
+#ifdef CONFIG_PINCTRL
+	bmc_pinctrl_unregister();
+#endif
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id mitx2_bmc_of_match[] = {
+	{ .compatible = "tp,mitx2-bmc" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, mitx2_bmc_of_match);
+#endif
+
+static const struct i2c_device_id mitx2_bmc_i2c_id[] = {
+	{ "mitx2_bmc", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, mitx2_bmc_i2c_id);
+
+static ssize_t version_show(struct kobject *kobj,
+			    struct kobj_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%i.%i.%i\n", bmc_proto_version[0],
+					  bmc_proto_version[1],
+					  bmc_proto_version[2]);
+}
+
+static struct kobj_attribute version_attribute =
+	__ATTR(version, 0664, version_show, NULL);
+
+static ssize_t bootreason_show(struct kobject *kobj,
+			       struct kobj_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%i\n", bmc_bootreason[0] |
+				   (bmc_bootreason[1] << 8));
+}
+
+static struct kobj_attribute bootreason_attribute =
+	__ATTR(bootreason, 0664, bootreason_show, NULL);
+
+static ssize_t scratch_show(struct kobject *kobj,
+			    struct kobj_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%i\n", bmc_scratch[0] |
+				   (bmc_scratch[1] <<  8) |
+				   (bmc_scratch[2] << 16) |
+				   (bmc_scratch[3] << 24));
+}
+
+static struct kobj_attribute scratch_attribute =
+	__ATTR(scratch, 0664, scratch_show, NULL);
+
+static struct attribute *bmc_attrs[] = {
+	&version_attribute.attr,
+	&bootreason_attribute.attr,
+	&scratch_attribute.attr,
+	NULL,
+};
+
+ATTRIBUTE_GROUPS(bmc);
+
+static struct i2c_driver mitx2_bmc_i2c_driver = {
+	.driver	  = {
+		.name = "mitx2-bmc",
+		.of_match_table = of_match_ptr(mitx2_bmc_of_match),
+		.groups = bmc_groups,
+	},
+	.probe	  = mitx2_bmc_i2c_probe,
+	.remove	  = mitx2_bmc_i2c_remove,
+	.id_table = mitx2_bmc_i2c_id,
+};
+module_i2c_driver(mitx2_bmc_i2c_driver);
+
+MODULE_AUTHOR("Konstantin Kirik");
+MODULE_DESCRIPTION("mITX2 BMC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("serial:bmc");
diff --git a/kernel/exit.c b/kernel/exit.c
index 3540b2c9b1b6a..c0d99cae58922 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -924,6 +924,7 @@ void __noreturn do_exit(long code)
 	lockdep_free_task(tsk);
 	do_task_dead();
 }
+EXPORT_SYMBOL(do_exit);
 
 void __noreturn make_task_dead(int signr)
 {
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 24/39] pm: disable all sleep states on Baikal-M based boards
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (22 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 23/39] bmc: add board management controller driver Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 25/39] sound: dwc-i2s: paper over RX overrun warnings on Baikal-M Daniil Gnusarev
                   ` (15 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

These days desktop environments try to put computer into a sleep
state after a certain period of inactivity. TF307 board is able
to enter a sleep state, however it does *NOT* wakeup via power
button or keyboard/mouse.

Apparently the only wakeup sources on TF307 board are

- Real time clock (RTC)
- Ethernet

Surprisingly BMC (board management controller) is NOT a wakeup
source. Also tp_bmc driver does not use interrupts, and polls
the device instead. Perhaps BMC is unable to generate interrupts
at all?

To avoid the problem disable all sleep states (including s2idle)
on Baikal-M systems

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-DONTUPSTREAM
X-feature-Baikal-M
---
 kernel/power/suspend.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c
index 3aae526cc4aac..1fd2b06b2a91c 100644
--- a/kernel/power/suspend.c
+++ b/kernel/power/suspend.c
@@ -30,6 +30,7 @@
 #include <trace/events/power.h>
 #include <linux/compiler.h>
 #include <linux/moduleparam.h>
+#include <linux/of.h>
 
 #include "power.h"
 
@@ -250,6 +251,18 @@ EXPORT_SYMBOL_GPL(suspend_valid_only_mem);
 
 static bool sleep_state_supported(suspend_state_t state)
 {
+#ifdef CONFIG_OF
+	if (of_device_is_compatible(of_root, "baikal,baikal-m") ||
+		of_device_is_compatible(of_root, "baikal,bm1000")) {
+		/* XXX: there are no wakeup sources except RTC and Ethernet
+		 * on BE-M1000 based boards. In other words, no way to wakeup
+		 * system via the keyboard or power button.
+		 * Thus even s2idle is unusable on BE-M1000 systems.
+		 */
+		pr_info("%s: no useful wakeup sources on Baikal-M", __func__);
+		return false;
+	}
+#endif
 	return state == PM_SUSPEND_TO_IDLE ||
 	       (valid_state(state) && !cxl_mem_active());
 }
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 25/39] sound: dwc-i2s: paper over RX overrun warnings on Baikal-M
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (23 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 24/39] pm: disable all sleep states on Baikal-M based boards Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 26/39] sound: dwc-i2s: request all IRQs specified in device tree Daniil Gnusarev
                   ` (14 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

i2s_irq_handler: avoid flooding system with RX overrun warnings

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
X-DONTUPSTREAM
---
 sound/soc/dwc/dwc-i2s.c | 9 +++++++--
 sound/soc/dwc/local.h   | 1 +
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c
index 9ea4be56d3b70..17b5fee698161 100644
--- a/sound/soc/dwc/dwc-i2s.c
+++ b/sound/soc/dwc/dwc-i2s.c
@@ -102,6 +102,7 @@ static inline void i2s_enable_irqs(struct dw_i2s_dev *dev, u32 stream,
 
 static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
 {
+	unsigned int rxor_count;
 	struct dw_i2s_dev *dev = dev_id;
 	bool irq_valid = false;
 	u32 isr[4];
@@ -138,9 +139,13 @@ static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
 			irq_valid = true;
 		}
 
-		/* Error Handling: TX */
+		/* Error Handling: RX */
 		if (isr[i] & ISR_RXFO) {
-			dev_err_ratelimited(dev->dev, "RX overrun (ch_id=%d)\n", i);
+			rxor_count = READ_ONCE(dev->rx_overrun_count);
+			if (!(rxor_count & 0x3ff))
+				dev_err_ratelimited(dev->dev, "RX overrun (ch_id=%d)\n", i);
+			rxor_count++;
+			WRITE_ONCE(dev->rx_overrun_count, rxor_count);
 			irq_valid = true;
 		}
 	}
diff --git a/sound/soc/dwc/local.h b/sound/soc/dwc/local.h
index dce88c9ad5f33..be7a026e5f918 100644
--- a/sound/soc/dwc/local.h
+++ b/sound/soc/dwc/local.h
@@ -149,6 +149,7 @@ struct dw_i2s_dev {
 			bool *period_elapsed);
 	unsigned int tx_ptr;
 	unsigned int rx_ptr;
+	unsigned int rx_overrun_count;
 };
 
 #if IS_ENABLED(CONFIG_SND_DESIGNWARE_PCM)
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 26/39] sound: dwc-i2s: request all IRQs specified in device tree
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (24 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 25/39] sound: dwc-i2s: paper over RX overrun warnings on Baikal-M Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 27/39] usb: dwc3: of-simple: added compatible string for Baikal-M SoC Daniil Gnusarev
                   ` (13 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

Some SoCs need more than one IRQ to use the block properly.
Therefore dw_i2s_probe should requests all IRQs specified
in the device tree.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 sound/soc/dwc/dwc-i2s.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c
index 17b5fee698161..a9b9777ecb89e 100644
--- a/sound/soc/dwc/dwc-i2s.c
+++ b/sound/soc/dwc/dwc-i2s.c
@@ -925,7 +925,7 @@ static int dw_i2s_probe(struct platform_device *pdev)
 	const struct i2s_platform_data *pdata = pdev->dev.platform_data;
 	struct dw_i2s_dev *dev;
 	struct resource *res;
-	int ret, irq;
+	int ret, irq, irq_count;
 	struct snd_soc_dai_driver *dw_i2s_dai;
 	const char *clk_id;
 
@@ -963,10 +963,18 @@ static int dw_i2s_probe(struct platform_device *pdev)
 			return ret;
 	}
 
-	irq = platform_get_irq_optional(pdev, 0);
-	if (irq >= 0) {
+	irq_count = platform_irq_count(pdev);
+	if (irq_count < 0) /* - EPROBE_DEFER */
+		return irq_count;
+
+	for (unsigned i = 0; i < (unsigned)irq_count; i++) {
+		irq = platform_get_irq(pdev, i);
+		if (irq < 0)
+			return irq;
+
 		ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0,
 				pdev->name, dev);
+
 		if (ret < 0) {
 			dev_err(&pdev->dev, "failed to request irq\n");
 			goto err_assert_reset;
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 27/39] usb: dwc3: of-simple: added compatible string for Baikal-M SoC
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (25 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 26/39] sound: dwc-i2s: request all IRQs specified in device tree Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 28/39] serial: 8250_dw: verify clock rate in dw8250_set_termios Daniil Gnusarev
                   ` (12 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 drivers/usb/dwc3/dwc3-of-simple.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c
index d1539fc9eabda..3f87464831b7c 100644
--- a/drivers/usb/dwc3/dwc3-of-simple.c
+++ b/drivers/usb/dwc3/dwc3-of-simple.c
@@ -174,6 +174,9 @@ static const struct of_device_id of_dwc3_simple_match[] = {
 	{ .compatible = "allwinner,sun50i-h6-dwc3" },
 	{ .compatible = "hisilicon,hi3670-dwc3" },
 	{ .compatible = "intel,keembay-dwc3" },
+	{ .compatible = "baikal,bm1000-dwc3" },
+	{ .compatible = "baikal,baikal-dwc3" },
+	{ .compatible = "be,baikal-dwc3" },
 	{ /* Sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, of_dwc3_simple_match);
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 28/39] serial: 8250_dw: verify clock rate in dw8250_set_termios
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (26 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 27/39] usb: dwc3: of-simple: added compatible string for Baikal-M SoC Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 29/39] clk: use "cmu-id" if there is no "reg" in devicetree Daniil Gnusarev
                   ` (11 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

Refuse to change the clock rate if clk_round_rate() returns
a rate which is way too off (i.e. by more than 1/16 from the one
necessary for a given baud rate). In particular this happens if
the requested rate is below the minimum supported by the clock.

Fixes the UART console on Baikal-M SoC. Without this patch the
console gets garbled immediately after loading the driver.
dw8250_set_termios tries to configure the baud rate (115200),
and calls clk_round_rate to figure out the supported rate closest
to 1843200 Hz (which is 115200 * 16). However the (SoC-specific)
clock driver returns 4705882 Hz. This frequency is way too off,
hence after setting it the console gets garbled.

On Baikal-M Linux has no direct control over (most) clocks.
The registers of CMU (clock management unit) are accessible
only from the secure world, therefore clocks are managed by
the firmware (ARM-TF). Linux' driver, clk-baikal, is a shim which
calls into firmware. And that 4705882 Hz is exactly what
the firmware returns.

According to 8250_dw maintainer (Andy Shevchenko) the correct
way to fix the problem is to
1) use DLAB for the baud rate and fixed clock rate,
2) fix the firmware

Neither of these advices can be applied in practice. For one,
both methods require replacing the DTB, which is embedded into
the firmware. Updating firmware is possible only for some types
of (Baikal-M) based boards, and requires special hardware (JTAG
programmer) and skills.
Therfore the only practical way to address the issue is to adjust
the kernel (with this ugly patch).

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
Co-developed-by: Vadim V. Vlasov <vadim.vlasov@elpitech.ru>
X-DONTUPSTREAM
X-feature-Baikal-M
---
 drivers/tty/serial/8250/8250_dw.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 85b6294c87ba5..29bd35069cd3e 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -379,14 +379,15 @@ dw8250_do_pm(struct uart_port *port, unsigned int state, unsigned int old)
 static void dw8250_set_termios(struct uart_port *p, struct ktermios *termios,
 			       const struct ktermios *old)
 {
-	unsigned long newrate = tty_termios_baud_rate(termios) * 16;
+	unsigned long baud = tty_termios_baud_rate(termios);
+	unsigned long newrate = baud * 16;
 	struct dw8250_data *d = to_dw8250_data(p->private_data);
 	long rate;
 	int ret;
 
 	clk_disable_unprepare(d->clk);
 	rate = clk_round_rate(d->clk, newrate);
-	if (rate > 0) {
+	if (rate > 0 && rate >= baud * 15 && rate <= baud * 17) {
 		/*
 		 * Note that any clock-notifer worker will block in
 		 * serial8250_update_uartclk() until we are done.
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 29/39] clk: use "cmu-id" if there is no "reg" in devicetree
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (27 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 28/39] serial: 8250_dw: verify clock rate in dw8250_set_termios Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 30/39] pci: baikal-pcie: driver compatibility with SDK earlier than 5.7 Daniil Gnusarev
                   ` (10 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

In early SDK before version 5.7 there is no parameter "reg" in devicetree,
the specified "cmu-id" is used instead

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 drivers/clk/baikal/clk-bm1000.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/baikal/clk-bm1000.c b/drivers/clk/baikal/clk-bm1000.c
index d986c8ea2a633..9d16224a8f89b 100644
--- a/drivers/clk/baikal/clk-bm1000.c
+++ b/drivers/clk/baikal/clk-bm1000.c
@@ -241,8 +241,12 @@ static int baikal_clk_probe(struct platform_device *pdev)
 	of_property_read_string(node, "clock-output-names", &cmu->name);
 	of_property_read_u32(node, "clock-frequency", &cmu->parent);
 	rc = of_property_read_u64(node, "reg", &base);
-	if (rc)
-		return rc;
+	if (rc) {
+		base = 0;
+		rc = of_property_read_u32(node, "cmu-id", (void *)&base);
+		if (rc)
+			return rc;
+	}
 
 	cmu->base = base;
 
@@ -822,6 +826,7 @@ device_initcall(bm1000_cmu_driver_acpi_init);
 
 static const struct of_device_id baikal_clk_of_match[] = {
 	{ .compatible = "baikal,bm1000-cmu" },
+	{ .compatible = "baikal,cmu" },
 	{ }
 };
 
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 30/39] pci: baikal-pcie: driver compatibility with SDK earlier than 5.7
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (28 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 29/39] clk: use "cmu-id" if there is no "reg" in devicetree Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 31/39] pci: baikal-pcie: driver compatibility with SDK versions 5.4 Daniil Gnusarev
                   ` (9 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

In earlier SDKs before version 5.7, there is no "gpr" parameter in
devicetree for PCIe devices. Instead, the specified "lrcu" is used
with the required offset.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 drivers/pci/controller/dwc/pcie-baikal-core.c | 50 +++++++++++--------
 1 file changed, 30 insertions(+), 20 deletions(-)

diff --git a/drivers/pci/controller/dwc/pcie-baikal-core.c b/drivers/pci/controller/dwc/pcie-baikal-core.c
index 8e23af9950494..19cb589e48db6 100644
--- a/drivers/pci/controller/dwc/pcie-baikal-core.c
+++ b/drivers/pci/controller/dwc/pcie-baikal-core.c
@@ -121,6 +121,8 @@ static bool baikal_pcie_link_wait_training_done(struct dw_pcie *pci)
 #define BM1000_PCIE_HOT_RST			BIT(12)
 #define BM1000_PCIE_ADB_PWRDWN			BIT(13)
 
+#define BM1000_PCIE_GPR_OFFSET			0x50000
+
 #define BM1000_PCIE_GPR_STATUS_BASE		0x04
 #define BM1000_PCIE_GPR_STATUS(x)		(((x) * 0x20) + BM1000_PCIE_GPR_STATUS_BASE)
 
@@ -143,6 +145,7 @@ struct bm1000_pcie {
 	struct dw_pcie		*pci;
 	unsigned int		num;
 	struct regmap		*gpr;
+	uintptr_t		gpr_offset;
 	union {
 		struct gpio_desc *reset_gpio;
 		struct {
@@ -160,9 +163,9 @@ void bm1000_pcie_phy_enable(struct dw_pcie *pci)
 	struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
 	u32 reg;
 
-	regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
+	regmap_read(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
 	reg |= BM1000_PCIE_PHY_MGMT_ENABLE | BM1000_PCIE_DBI2_MODE;
-	regmap_write(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), reg);
+	regmap_write(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_GENCTL(bm->num), reg);
 }
 
 void bm1000_pcie_phy_disable(struct dw_pcie *pci)
@@ -170,9 +173,9 @@ void bm1000_pcie_phy_disable(struct dw_pcie *pci)
 	struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
 	u32 reg;
 
-	regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
+	regmap_read(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
 	reg &= ~(BM1000_PCIE_PHY_MGMT_ENABLE | BM1000_PCIE_DBI2_MODE);
-	regmap_write(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), reg);
+	regmap_write(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_GENCTL(bm->num), reg);
 }
 
 static int bm1000_get_resources(struct platform_device *pdev,
@@ -184,10 +187,16 @@ static int bm1000_get_resources(struct platform_device *pdev,
 	struct resource *res;
 
 	bm->pci = pci;
+	bm->gpr_offset = 0;
 	bm->gpr = syscon_regmap_lookup_by_compatible("baikal,bm1000-pcie-gpr");
 	if (IS_ERR(bm->gpr)) {
-		dev_err(dev, "failed to find PCIe GPR registers\n");
-		return PTR_ERR(bm->gpr);
+		dev_warn(dev, "failed to find PCIe GPR registers, trying LCRU\n");
+		bm->gpr = syscon_regmap_lookup_by_phandle(dev->of_node, "baikal,pcie-lcru");
+		if (IS_ERR(bm->gpr)) {
+			dev_err(dev, "failed to find PCIe LCRU registers\n");
+			return PTR_ERR(bm->gpr);
+		}
+		bm->gpr_offset = BM1000_PCIE_GPR_OFFSET;
 	}
 
 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
@@ -223,11 +232,11 @@ static int bm1000_pcie_link_up(struct dw_pcie *pci)
 	struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
 	u32 reg;
 
-	regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
+	regmap_read(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
 	if (!(reg & BM1000_PCIE_LTSSM_ENABLE))
 		return 0;
 
-	regmap_read(bm->gpr, BM1000_PCIE_GPR_STATUS(bm->num), &reg);
+	regmap_read(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_STATUS(bm->num), &reg);
 	return (reg & BAIKAL_PCIE_LTSSM_MASK) == BAIKAL_PCIE_LTSSM_STATE_L0;
 }
 
@@ -236,9 +245,9 @@ static int bm1000_pcie_start_link(struct dw_pcie *pci)
 	struct bm1000_pcie *bm = dev_get_drvdata(pci->dev);
 	u32 reg;
 
-	regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
+	regmap_read(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
 	reg |= BM1000_PCIE_LTSSM_ENABLE;
-	regmap_write(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), reg);
+	regmap_write(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_GENCTL(bm->num), reg);
 	return 0;
 }
 
@@ -453,9 +462,9 @@ static int bm1000_pcie_host_init(struct dw_pcie_rp *pp)
 	/* If link is not established yet, reset the RC */
 	if (!linkup) {
 		/* Disable link training */
-		regmap_read(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
+		regmap_read(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_GENCTL(bm->num), &reg);
 		reg &= ~BM1000_PCIE_LTSSM_ENABLE;
-		regmap_write(bm->gpr, BM1000_PCIE_GPR_GENCTL(bm->num), reg);
+		regmap_write(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_GENCTL(bm->num), reg);
 
 		/* Assert PERST pin */
 		if (acpi_disabled) {
@@ -479,7 +488,7 @@ static int bm1000_pcie_host_init(struct dw_pcie_rp *pp)
 		}
 
 		/* Reset the RC */
-		regmap_read(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), &reg);
+		regmap_read(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_RESET(bm->num), &reg);
 		reg |= BM1000_PCIE_NONSTICKY_RST |
 		       BM1000_PCIE_STICKY_RST	 |
 		       BM1000_PCIE_PWR_RST	 |
@@ -494,7 +503,7 @@ static int bm1000_pcie_host_init(struct dw_pcie_rp *pp)
 			reg |= BM1000_PCIE_PIPE_RST;
 		}
 
-		regmap_write(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), reg);
+		regmap_write(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_RESET(bm->num), reg);
 
 		if (!acpi_disabled && bm->num == 2 && bm->gpio[1].is_set) {
 			/* Assert PRSNT pin */
@@ -510,12 +519,12 @@ static int bm1000_pcie_host_init(struct dw_pcie_rp *pp)
 			bm1000_pcie_set_gpio(bm->gpio[0].num, bm->gpio[0].polarity);
 
 		/* Deassert PHY reset */
-		regmap_read(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), &reg);
+		regmap_read(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_RESET(bm->num), &reg);
 		reg &= ~BM1000_PCIE_PHY_RST;
-		regmap_write(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), reg);
+		regmap_write(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_RESET(bm->num), reg);
 
 		/* Deassert all software controlled resets */
-		regmap_read(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), &reg);
+		regmap_read(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_RESET(bm->num), &reg);
 		reg &= ~(BM1000_PCIE_ADB_PWRDWN	   |
 			 BM1000_PCIE_HOT_RST	   |
 			 BM1000_PCIE_NONSTICKY_RST |
@@ -531,7 +540,7 @@ static int bm1000_pcie_host_init(struct dw_pcie_rp *pp)
 			reg &= ~BM1000_PCIE_PIPE_RST;
 		}
 
-		regmap_write(bm->gpr, BM1000_PCIE_GPR_RESET(bm->num), reg);
+		regmap_write(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_RESET(bm->num), reg);
 	}
 
 	/* Enable error reporting */
@@ -567,11 +576,11 @@ static int bm1000_pcie_host_init(struct dw_pcie_rp *pp)
 		dw_pcie_writew_dbi(pci, exp_cap_off + PCI_EXP_LNKCTL2, reg);
 	}
 
-	regmap_read(bm->gpr, BM1000_PCIE_GPR_MSI_TRANS_CTL2, &reg);
+	regmap_read(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_MSI_TRANS_CTL2, &reg);
 	reg &= ~BM1000_PCIE_MSI_TRANS_RCNUM_MASK(bm->num);
 	reg |= BM1000_PCIE_MSI_TRANS_RCNUM(bm->num);
 	reg |= BM1000_PCIE_MSI_TRANS_EN(bm->num);
-	regmap_write(bm->gpr, BM1000_PCIE_GPR_MSI_TRANS_CTL2, reg);
+	regmap_write(bm->gpr, bm->gpr_offset + BM1000_PCIE_GPR_MSI_TRANS_CTL2, reg);
 
 	/* RX/TX equalizers fine tune */
 	bm1000_pcie_tune(pci);
@@ -1124,6 +1133,7 @@ static int bm1000_get_acpi_data(struct device *dev, struct bm1000_pcie *bm,
 	struct acpi_device *adev = to_acpi_device(dev), *res_dev;
 	int ret;
 
+	bm->gpr_offset = 0;
 	bm->gpr = bm1000_pcie_get_gpr_acpi(bm);
 	if (IS_ERR_OR_NULL(bm->gpr)) {
 		dev_err(dev, "No PCIe GPR specified\n");
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 31/39] pci: baikal-pcie: driver compatibility with SDK versions 5.4
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (29 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 30/39] pci: baikal-pcie: driver compatibility with SDK earlier than 5.7 Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 32/39] dw-pcie: refuse to load on Baikal-M with recent firmware Daniil Gnusarev
                   ` (8 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

In SDK version 5.4 for PCIe devices the interrupt names required
for the driver are not specified. In this case, interrupts are
determined by index. In addition, the "msi-map" property is used
as in subsequent versions of SDK.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 drivers/pci/controller/dwc/pcie-baikal-core.c | 58 ++++++++++++++++++-
 1 file changed, 55 insertions(+), 3 deletions(-)

diff --git a/drivers/pci/controller/dwc/pcie-baikal-core.c b/drivers/pci/controller/dwc/pcie-baikal-core.c
index 19cb589e48db6..242503bffd888 100644
--- a/drivers/pci/controller/dwc/pcie-baikal-core.c
+++ b/drivers/pci/controller/dwc/pcie-baikal-core.c
@@ -640,10 +640,62 @@ static int bm1000_add_pcie_port(struct platform_device *pdev)
 	struct dw_pcie_rp *pp = &pci->pp;
 	int ret;
 
-	pp->irq = platform_get_irq_byname(pdev, "aer");
+	pp->irq = platform_get_irq_byname_optional(pdev, "aer");
 	if (pp->irq < 0) {
-		dev_err(dev, "failed to get \"aer\" IRQ\n");
-		return pp->irq;
+		dev_warn(dev, "failed to get \"aer\" IRQ, trying to get by index\n");
+		pp->irq = platform_get_irq(pdev, 0);
+		if (pp->irq < 0) {
+			dev_err(dev, "failed to get \"aer\" IRQ\n");
+			return pp->irq;
+		}
+		if (IS_ENABLED(CONFIG_PCI_MSI)) {
+			u32 lcru[2];
+			u32 maps[4];
+			struct of_changeset ocs;
+			struct property *prop;
+
+			pp->msi_irq[0] = platform_get_irq(pdev, 1);
+			if (pp->msi_irq[0] < 0) {
+				dev_err(dev, "failed to get \"msi\" IRQ\n");
+				return pp->msi_irq[0];
+			}
+
+			if (of_property_read_u32_array(dev->of_node, "baikal,pcie-lcru", lcru, 2)) {
+				dev_err(dev, "failed to read LCRU\n");
+				return -EINVAL;
+			}
+			if (of_property_read_u32_array(dev->of_node, "msi-map", maps, 4)) {
+				dev_err(dev, "failed to read msi-map\n");
+				return -EINVAL;
+			}
+
+			of_changeset_init(&ocs);
+
+			prop = of_find_property(dev->of_node, "msi-map", NULL);
+			if (!prop) {
+				dev_err(dev, "failed to find property msi-map\n");
+				return -EINVAL;
+			}
+
+			ret=of_changeset_remove_property(&ocs, dev->of_node, prop);
+			if (ret) {
+				dev_err(dev, "failed to remove property msi-map\n");
+				return ret;
+			}
+
+			maps[2] = lcru[1] * 0x10000;
+			ret=of_changeset_add_prop_u32_array(&ocs,dev->of_node,"msi-map",maps,4);
+			if (ret) {
+				dev_err(dev, "failed to modify property msi-map\n");
+				return ret;
+			}
+
+			ret=of_changeset_apply(&ocs);
+			if (ret) {
+				dev_err(dev, "failed to modify property msi-map\n");
+				return ret;
+			}
+		}
 	}
 
 	ret = devm_request_irq(dev, pp->irq, bm1000_pcie_aer_irq_handler,
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 32/39] dw-pcie: refuse to load on Baikal-M with recent firmware
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (30 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 31/39] pci: baikal-pcie: driver compatibility with SDK versions 5.4 Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 33/39] drm: baikal-vdu: driver compatibility with SDK earlier than 5.9 Daniil Gnusarev
                   ` (7 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

From: Alexey Sheplyakov <asheplyakov@basealt.ru>

Firmware from SDK-M 5.4 is incompatible with dw-pcie driver.
Yet the DTB (passed to kernel by the firmware) claims otherwise.
Hence refuse to load if device node is compatilbe with
`baikal,bm1000-pcie` (earlier versions of Baikal-M firmware used
a different compatible string).

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
X-DONTUPSTREAM
---
 drivers/pci/controller/dwc/Makefile               | 8 ++++----
 drivers/pci/controller/dwc/pcie-designware-plat.c | 5 +++++
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
index 4e213d43a517c..3b1cd5ab98dee 100644
--- a/drivers/pci/controller/dwc/Makefile
+++ b/drivers/pci/controller/dwc/Makefile
@@ -1,9 +1,12 @@
 # SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PCIE_BAIKAL) += pcie-baikal.o
+pcie-baikal-objs := pcie-baikal-core.o \
+                    pcie-baikal-tune.o
+
 obj-$(CONFIG_PCIE_DW) += pcie-designware.o
 obj-$(CONFIG_PCIE_DW_HOST) += pcie-designware-host.o
 obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
 obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
-obj-$(CONFIG_PCIE_BAIKAL) += pcie-baikal.o
 obj-$(CONFIG_PCIE_BT1) += pcie-bt1.o
 obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
 obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
@@ -28,9 +31,6 @@ obj-$(CONFIG_PCIE_UNIPHIER) += pcie-uniphier.o
 obj-$(CONFIG_PCIE_UNIPHIER_EP) += pcie-uniphier-ep.o
 obj-$(CONFIG_PCIE_VISCONTI_HOST) += pcie-visconti.o
 
-pcie-baikal-objs := pcie-baikal-core.o \
-                    pcie-baikal-tune.o
-
 # The following drivers are for devices that use the generic ACPI
 # pci_root.c driver but don't support standard ECAM config access.
 # They contain MCFG quirks to replace the generic ECAM accessors with
diff --git a/drivers/pci/controller/dwc/pcie-designware-plat.c b/drivers/pci/controller/dwc/pcie-designware-plat.c
index b625841e98aa0..96d01ebd16137 100644
--- a/drivers/pci/controller/dwc/pcie-designware-plat.c
+++ b/drivers/pci/controller/dwc/pcie-designware-plat.c
@@ -112,6 +112,11 @@ static int dw_plat_pcie_probe(struct platform_device *pdev)
 	const struct dw_plat_pcie_of_data *data;
 	enum dw_pcie_device_mode mode;
 
+	if (of_device_is_compatible(dev->of_node, "baikal,bm1000-pcie")) {
+		dev_err(dev, "refusing to load on Baikal-M with SDK-M 5.{4,5}\n");
+		return -ENODEV;
+	}
+
 	data = of_device_get_match_data(dev);
 	if (!data)
 		return -EINVAL;
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 33/39] drm: baikal-vdu: driver compatibility with SDK earlier than 5.9
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (31 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 32/39] dw-pcie: refuse to load on Baikal-M with recent firmware Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 34/39] input: new driver - serdev-serio Daniil Gnusarev
                   ` (6 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

In early SDK before version 5.9 in devicetree used separate
description of VDU devices, for HDMI and for LVDS. Later began to
be used jointly. This patch allows to use the driver with old
firmware versions.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 drivers/gpu/drm/baikal/baikal_vdu_drm.h |  1 +
 drivers/gpu/drm/baikal/baikal_vdu_drv.c | 82 ++++++++++++++++++++-----
 2 files changed, 67 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drm.h b/drivers/gpu/drm/baikal/baikal_vdu_drm.h
index 432270bebed69..90dc57bac7ae2 100644
--- a/drivers/gpu/drm/baikal/baikal_vdu_drm.h
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drm.h
@@ -67,6 +67,7 @@ struct baikal_vdu_crossbar {
 	struct drm_device drm;
 	struct baikal_vdu_private hdmi;
 	struct baikal_vdu_private lvds;
+	int legacy;
 };
 
 struct baikal_lvds_bridge {
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drv.c b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
index c9d5cea635628..a951c6661ffa6 100644
--- a/drivers/gpu/drm/baikal/baikal_vdu_drv.c
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
@@ -49,6 +49,9 @@ int mode_override = 0;
 int hdmi_off = 0;
 int lvds_off = 0;
 
+#define legacy_LDVS	1
+#define legacy_HDMI	2
+
 static struct drm_driver vdu_drm_driver;
 
 static struct drm_mode_config_funcs mode_config_funcs = {
@@ -69,10 +72,13 @@ static struct drm_bridge *devm_baikal_get_bridge(struct device *dev,
 	struct drm_bridge *bridge;
 	struct drm_panel *panel;
 	struct fwnode_handle *fwnode;
+	struct drm_device *drm = dev_get_drvdata(dev);
+	struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(drm);
 	int ret = 0;
 
 	if (is_of_node(dev->fwnode)) {
-		ret = drm_of_find_panel_or_bridge(to_of_node(dev->fwnode), port,
+		ret = drm_of_find_panel_or_bridge(to_of_node(dev->fwnode),
+						  crossbar->legacy ? 0 : port,
 						  endpoint, &panel, &bridge);
 	} else {
 		if (port == CRTC_HDMI) {
@@ -219,12 +225,18 @@ static int baikal_vdu_allocate_resources(struct platform_device *pdev,
 		struct baikal_vdu_private *priv)
 {
 	struct device *dev = &pdev->dev;
+	struct drm_device *drm = priv->drm;
+	struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(drm);
 	struct resource *mem;
 	int ret;
 
-	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, priv->regs_name);
-	if (!mem)
-		mem = platform_get_resource(pdev, IORESOURCE_MEM, priv->index);
+	if (crossbar->legacy)
+		mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	else {
+		mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, priv->regs_name);
+		if (!mem)
+			mem = platform_get_resource(pdev, IORESOURCE_MEM, priv->index);
+	}
 
 	if (!mem) {
 		dev_err(dev, "%s %s: no MMIO resource specified\n", __func__, priv->name);
@@ -260,9 +272,14 @@ static int baikal_vdu_allocate_irq(struct platform_device *pdev,
 	       struct baikal_vdu_private *priv)
 {
 	struct device *dev = &pdev->dev;
+	struct drm_device *drm = priv->drm;
+	struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(drm);
 	int ret;
 
-	priv->irq = fwnode_irq_get_byname(dev->fwnode, priv->irq_name);
+	if (crossbar->legacy)
+		priv->irq = fwnode_irq_get(dev->fwnode, 0);
+	else
+		priv->irq = fwnode_irq_get_byname(dev->fwnode, priv->irq_name);
 	if (priv->irq < 0) {
 		dev_err(dev, "%s %s: no IRQ resource specified\n", __func__, priv->name);
 		return -EINVAL;
@@ -358,6 +375,8 @@ static int baikal_vdu_drm_probe(struct platform_device *pdev)
 	struct drm_device *drm;
 	struct drm_mode_config *mode_config;
 	struct arm_smccc_res res;
+	struct device_node *node;
+	int node_count;
 	int ret;
 
 	crossbar = devm_drm_dev_alloc(dev, &vdu_drm_driver,
@@ -367,27 +386,58 @@ static int baikal_vdu_drm_probe(struct platform_device *pdev)
 
 	drm = &crossbar->drm;
 	platform_set_drvdata(pdev, drm);
+	crossbar->legacy = 0;
 	hdmi = &crossbar->hdmi;
 	baikal_vdu_set_name(hdmi, CRTC_HDMI, "hdmi");
 	lvds = &crossbar->lvds;
 	baikal_vdu_set_name(lvds, CRTC_LVDS, "lvds");
 
-	ret = baikal_vdu_bridge_init(hdmi, drm);
-	if (ret == -EPROBE_DEFER) {
-		goto out_drm;
+	node = NULL;
+	node_count = 0;
+	while ((node = of_find_compatible_node(node, NULL, "baikal,vdu")))
+		node_count++;
+
+	if (node_count > 1) {
+		dev_info(dev, "Found separate description of VDU devices\n");
+		if (device_property_present(dev, "lvds-out")) {
+			// LVDS
+			crossbar->legacy = legacy_LDVS;
+			sprintf(lvds->pclk_name, "pclk");
+			lvds->index = CRTC_LVDS;
+		} else {
+			// HDMI
+			crossbar->legacy = legacy_HDMI;
+			sprintf(hdmi->pclk_name, "pclk");
+			hdmi->index = CRTC_HDMI;
+		}
 	}
 
-	ret = device_property_read_u32(&pdev->dev, "lvds-lanes",
-				       &lvds->num_lanes);
-	if (ret)
-		lvds->num_lanes = 0;
-	if (lvds->num_lanes) {
-		ret = baikal_vdu_bridge_init(lvds, drm);
+	if (crossbar->legacy != legacy_LDVS) {
+		ret = baikal_vdu_bridge_init(hdmi, drm);
 		if (ret == -EPROBE_DEFER) {
 			goto out_drm;
 		}
 	}
 
+	if (crossbar->legacy != legacy_HDMI) {
+		ret = device_property_read_u32(&pdev->dev, "lvds-lanes",
+					       &lvds->num_lanes);
+		if (ret) {
+			if (crossbar->legacy) {
+				lvds->num_lanes = of_graph_get_endpoint_count(dev->of_node);
+				if (lvds->num_lanes < 0)
+					lvds->num_lanes = 0;
+			} else
+				lvds->num_lanes = 0;
+		}
+		if (lvds->num_lanes) {
+			ret = baikal_vdu_bridge_init(lvds, drm);
+			if (ret == -EPROBE_DEFER) {
+				goto out_drm;
+			}
+		}
+	}
+
 	drm_mode_config_init(drm);
 	mode_config = &drm->mode_config;
 	mode_config->funcs = &mode_config_funcs;
@@ -397,10 +447,10 @@ static int baikal_vdu_drm_probe(struct platform_device *pdev)
 	mode_config->max_height = 8192;
 
 	hdmi->off = hdmi_off;
-	hdmi->ready = baikal_vdu_resources_init(pdev, hdmi);
+	hdmi->ready = (crossbar->legacy != legacy_LDVS) && baikal_vdu_resources_init(pdev, hdmi);
 	if (lvds->num_lanes) {
 		lvds->off = lvds_off;
-		lvds->ready = baikal_vdu_resources_init(pdev, lvds);
+		lvds->ready = (crossbar->legacy != legacy_HDMI) && baikal_vdu_resources_init(pdev, lvds);
 	} else {
 		lvds->ready = 0;
 		lvds->bridge = NULL;
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 34/39] input: new driver - serdev-serio
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (32 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 33/39] drm: baikal-vdu: driver compatibility with SDK earlier than 5.9 Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 35/39] input: added TF307 serio PS/2 emulator driver Daniil Gnusarev
                   ` (5 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

From: "Vadim V. Vlasov" <vadim.vlasov@elpitech.ru>

The driver provides serio interface from serial device.
Together with ps2mult driver it enables PS/2 support for
Elpitech boards.

This replaces old tp_serio driver which used i2c transport
and suffered from synchronization loss when PS/2 mouse
is used.

X-feature-Baikal-M
---
 drivers/input/serio/Kconfig        |  11 +++
 drivers/input/serio/Makefile       |   1 +
 drivers/input/serio/serdev-serio.c | 121 +++++++++++++++++++++++++++++
 3 files changed, 133 insertions(+)
 create mode 100644 drivers/input/serio/serdev-serio.c

diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index 17edc1597446f..0b93e06384c68 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -319,4 +319,15 @@ config USERIO
 
 	  If you are unsure, say N.
 
+config SERDEV_SERIO
+	tristate "SERDEV SERIO driver"
+	depends on OF && SERIAL_DEV_BUS
+	help
+	  Say Y here if you have the serial peripherals supporting serio
+	  protocol. E.g. PS/2 line multiplexer like the one present on
+	  TQC boards.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called serdev-serio.
+
 endif
diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
index 6d97bad7b844e..48c5114cfdabd 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -33,3 +33,4 @@ obj-$(CONFIG_HYPERV_KEYBOARD)	+= hyperv-keyboard.o
 obj-$(CONFIG_SERIO_SUN4I_PS2)	+= sun4i-ps2.o
 obj-$(CONFIG_SERIO_GPIO_PS2)	+= ps2-gpio.o
 obj-$(CONFIG_USERIO)		+= userio.o
+obj-$(CONFIG_SERDEV_SERIO)	+= serdev-serio.o
diff --git a/drivers/input/serio/serdev-serio.c b/drivers/input/serio/serdev-serio.c
new file mode 100644
index 0000000000000..c2170524d0dba
--- /dev/null
+++ b/drivers/input/serio/serdev-serio.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Serdev Serio driver
+ *
+ * Copyright (C) 2022 Elpitech
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/serio.h>
+#include <linux/serdev.h>
+
+struct serdev_serio {
+	struct serdev_device *serdev;
+	struct serio *serio;
+};
+
+static int ss_serio_write(struct serio *serio, unsigned char data)
+{
+	struct serdev_serio *ss = serio->port_data;
+	struct serdev_device *serdev = ss->serdev;
+
+	dev_dbg(&serdev->dev, "ss_write: data %02x\n", data);
+	serdev_device_write(serdev, &data, 1, 0);
+
+	return 0;
+}
+
+static int ss_receive_buf(struct serdev_device *serdev,
+			  const unsigned char *buf, size_t count)
+{
+	struct serdev_serio *ss = serdev_device_get_drvdata(serdev);
+	int ret = count;
+
+	dev_dbg(&serdev->dev, "ss_receive: count %d, data %02x\n", (int)count, *buf);
+	while (count--)
+		serio_interrupt(ss->serio, *buf++, 0);
+
+	return ret;
+}
+
+static const struct serdev_device_ops ss_serdev_ops = {
+	.receive_buf    = ss_receive_buf,
+	.write_wakeup   = serdev_device_write_wakeup,
+};
+
+static int ss_probe(struct serdev_device *serdev)
+{
+	struct device *dev = &serdev->dev;
+	struct device_node *node = dev->of_node;
+	struct serdev_serio *ss;
+	struct serio *serio;
+	u32 speed = 0, proto;
+	int ret;
+
+	serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+	if (!serio)
+		return -ENOMEM;
+
+	ss = devm_kzalloc(dev, sizeof(*ss), GFP_KERNEL);
+	if (!ss)
+		return -ENOMEM;
+	ss->serdev = serdev;
+	ss->serio = serio;
+	ret = of_property_read_u32(node, "protocol", &proto);
+	if (ret < 0) {
+		dev_err(dev, "Can't read protocol property (ret %d)\n", ret);
+		return ret;
+	}
+	of_property_read_u32(node, "current-speed", &speed);
+	serdev_device_set_drvdata(serdev, ss);
+	serdev_device_set_client_ops(serdev, &ss_serdev_ops);
+	ret = serdev_device_open(serdev);
+	if (ret)
+		return ret;
+
+	if (speed)
+		serdev_device_set_baudrate(serdev, speed);
+	serdev_device_set_flow_control(serdev, false);
+
+	serio->port_data = ss;
+	strlcpy(serio->name, "Serdev Serio", sizeof(serio->name));
+	strlcpy(serio->phys, "serio", sizeof(serio->phys));
+	serio->id.type = SERIO_RS232;
+	serio->id.proto = proto;
+	serio->id.id = SERIO_ANY;
+	serio->id.extra = SERIO_ANY;
+	serio->write = ss_serio_write;
+	serio_register_port(serio);
+
+	return 0;
+}
+
+static void ss_remove(struct serdev_device *serdev)
+{
+	struct serdev_serio *ss = serdev_device_get_drvdata(serdev);
+	serdev_device_close(ss->serdev);
+	serio_unregister_port(ss->serio);
+}
+
+static const struct of_device_id ss_of_match[] = {
+	{ .compatible = "serdev,serio" },
+	{},
+};
+
+static struct serdev_device_driver serdev_serio_drv = {
+	.driver		= {
+		.name	= "serdev_serio",
+		.of_match_table = of_match_ptr(ss_of_match),
+	},
+	.probe  = ss_probe,
+	.remove = ss_remove,
+};
+
+module_serdev_device_driver(serdev_serio_drv);
+
+MODULE_AUTHOR("Vadim V. Vlasov <vvv19xx@gmail.com>");
+MODULE_DESCRIPTION("Serdev Serio driver");
+MODULE_LICENSE("GPL");
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 35/39] input: added TF307 serio PS/2 emulator driver
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (33 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 34/39] input: new driver - serdev-serio Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 36/39] input: tp_serio: catch up API changes Daniil Gnusarev
                   ` (4 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

From: "Vadim V. Vlasov" <vvv19xx@gmail.com>

This provides support for PS/2 devices connected to the BMC (board
management controller) of TF307 board. I2C or SPI link is used as
a transport between BMC and kernel.

X-feature-Baikal-M
---
 drivers/input/serio/Kconfig    |  10 +
 drivers/input/serio/Makefile   |   1 +
 drivers/input/serio/tp_serio.c | 747 +++++++++++++++++++++++++++++++++
 3 files changed, 758 insertions(+)
 create mode 100644 drivers/input/serio/tp_serio.c

diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index 0b93e06384c68..a4a42143e62f7 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -294,6 +294,16 @@ config SERIO_SUN4I_PS2
 	  To compile this driver as a module, choose M here: the
 	  module will be called sun4i-ps2.
 
+config SERIO_TPLATFORMS
+	tristate "T-Plaftorms serio port support"
+	depends on I2C || SPI
+	help
+	  This selects support for PS/2 ports emulated by EC found on
+	  Baikal-M-based Mini-ITX board.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called tp_serio.
+
 config SERIO_GPIO_PS2
 	tristate "GPIO PS/2 bit banging driver"
 	depends on GPIOLIB
diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
index 48c5114cfdabd..8f1be3803fd3e 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -32,5 +32,6 @@ obj-$(CONFIG_SERIO_OLPC_APSP)	+= olpc_apsp.o
 obj-$(CONFIG_HYPERV_KEYBOARD)	+= hyperv-keyboard.o
 obj-$(CONFIG_SERIO_SUN4I_PS2)	+= sun4i-ps2.o
 obj-$(CONFIG_SERIO_GPIO_PS2)	+= ps2-gpio.o
+obj-$(CONFIG_SERIO_TPLATFORMS)	+= tp_serio.o
 obj-$(CONFIG_USERIO)		+= userio.o
 obj-$(CONFIG_SERDEV_SERIO)	+= serdev-serio.o
diff --git a/drivers/input/serio/tp_serio.c b/drivers/input/serio/tp_serio.c
new file mode 100644
index 0000000000000..f530cb50182e4
--- /dev/null
+++ b/drivers/input/serio/tp_serio.c
@@ -0,0 +1,747 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * T-Platforms serio port driver
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/serio.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+
+MODULE_DESCRIPTION("T-Platforms serio port driver");
+MODULE_LICENSE("GPL");
+
+#define TP_SERIO_CHUNK_SIZE 4
+#define TP_SERIO_SPI_SPEED_DEFAULT 500000
+#define TP_SERIO_TX_QUEUE_SIZE 64
+#define TP_SERIO_REQUEST_DELAY 2
+#define TP_SERIO_POLL_READ_DELAY_MIN 1
+#define TP_SERIO_POLL_READ_DELAY_MAX 2
+#define TP_SERIO_POLL_WRITE_DELAY 1
+#define TP_SERIO_POLL_ERROR_DELAY 100
+#define TP_SERIO_POLL_READ_TIMEOUT 8
+#define TP_SERIO_POLL_WAIT_TIMEOUT 100
+#define TP_SERIO_CMD_QUERY 0xFC
+#define TP_SERIO_CMD_RESET 0xFE
+
+static const unsigned char tp_serio_cmd_reset_response[] = {
+	TP_SERIO_CMD_RESET, 'P', 'S', '2'
+};
+
+struct tp_serio_tx {
+	bool has_data;
+	unsigned char data;
+};
+
+struct tp_serio_port {
+	struct serio *serio;
+	struct tp_serio_data *drv;
+	struct tp_serio_tx tx;
+	unsigned int id;
+	bool registered;
+};
+
+struct tp_serio_data {
+	struct i2c_client *dev_i2c;
+	struct spi_device *dev_spi;
+	struct task_struct *poll_task;
+	wait_queue_head_t poll_wq;
+	bool poll_ready;
+	int rx_irq;
+	unsigned int num_ports;
+	struct tp_serio_port *ports;
+};
+
+struct tp_serio_driver {
+#if defined(CONFIG_I2C)
+	struct i2c_driver i2c;
+#endif
+#if defined(CONFIG_SPI)
+	struct spi_driver spi;
+#endif
+};
+
+#if defined(CONFIG_I2C)
+static int tp_serio_i2c_write(struct tp_serio_data *drv,
+		size_t size, void *data)
+{
+	struct i2c_msg m;
+
+	m.addr = drv->dev_i2c->addr;
+	m.flags = 0;
+	m.len = size;
+	m.buf = data;
+	return i2c_transfer(drv->dev_i2c->adapter, &m, 1);
+}
+
+static int tp_serio_i2c_read(struct tp_serio_data *drv,
+		size_t size, void *data)
+{
+	struct i2c_msg m;
+
+	m.addr = drv->dev_i2c->addr;
+	m.flags = I2C_M_RD;
+	m.len = size;
+	m.buf = data;
+	return i2c_transfer(drv->dev_i2c->adapter, &m, 1);
+}
+#endif
+
+#if defined(CONFIG_SPI)
+static int tp_serio_spi_write(struct tp_serio_data *drv,
+		size_t size, void *data)
+{
+	struct spi_transfer t = {
+		.speed_hz = TP_SERIO_SPI_SPEED_DEFAULT,
+		.tx_buf = data,
+		.len = size,
+	};
+	struct spi_message m;
+
+	spi_message_init(&m);
+	spi_message_add_tail(&t, &m);
+	return spi_sync(drv->dev_spi, &m);
+}
+
+static int tp_serio_spi_read(struct tp_serio_data *drv,
+		size_t size, void *data)
+{
+	struct spi_transfer	t = {
+		.speed_hz = TP_SERIO_SPI_SPEED_DEFAULT,
+		.rx_buf = data,
+		.len = size,
+	};
+	struct spi_message m;
+
+	spi_message_init(&m);
+	spi_message_add_tail(&t, &m);
+	return spi_sync(drv->dev_spi, &m);
+}
+#endif
+
+static int tp_serio_request(struct tp_serio_data *drv,
+				unsigned char cmd,
+				unsigned char *response)
+{
+	int result;
+	size_t size;
+	unsigned char message[TP_SERIO_CHUNK_SIZE];
+
+	result = -ENODEV;
+	memset(message, 0, sizeof(message));
+	message[0] = cmd;
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL) {
+		size = sizeof(message);
+		result = tp_serio_spi_write(drv, size, message);
+	} else
+#endif
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL) {
+		size = 1;
+		result = tp_serio_i2c_write(drv, size, message);
+	}
+#endif
+		;
+	if (result < 0)
+		return result;
+	usleep_range(TP_SERIO_REQUEST_DELAY * 1000,
+				TP_SERIO_REQUEST_DELAY * 1000);
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL)
+		result = tp_serio_i2c_read(drv, TP_SERIO_CHUNK_SIZE, response);
+	else
+#endif
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL)
+		result = tp_serio_spi_read(drv, TP_SERIO_CHUNK_SIZE, response);
+#endif
+		;
+	return result;
+}
+
+static int tp_serio_data_read(struct tp_serio_data *drv)
+{
+	int result;
+	size_t size;
+	size_t index;
+	size_t dbg_len;
+	char dbg_line[256];
+	unsigned int port_id;
+	unsigned char message[TP_SERIO_CHUNK_SIZE];
+
+	memset(message, 0, sizeof(message));
+	result = -ENODEV;
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL)
+		result = tp_serio_i2c_read(drv, sizeof(message), message);
+	else
+#endif
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL)
+		result = tp_serio_spi_read(drv, sizeof(message), message);
+#endif
+		;
+	if (result < 0)
+		return result;
+
+#if 0
+	snprintf(dbg_line, ARRAY_SIZE(dbg_line) - 1, "raw read:");
+	for (index = 0; index < ARRAY_SIZE(message); index++) {
+		dbg_len = strlen(dbg_line);
+		snprintf(dbg_line + dbg_len,
+				ARRAY_SIZE(dbg_line) - 1 - dbg_len,
+				" %02x", message[index]);
+	}
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL)
+		dev_dbg(&drv->dev_i2c->dev, "%s\n", dbg_line);
+	else
+#endif
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL)
+		dev_dbg(&drv->dev_spi->dev, "%s\n", dbg_line);
+#endif
+		;
+#endif
+
+	result = 0;
+	size = message[0] & 0x0F;
+	port_id = (message[0] >> 4) & 0x0F;
+	if ((size > 0) && (port_id < drv->num_ports)) {
+		snprintf(dbg_line, ARRAY_SIZE(dbg_line) - 1,
+			"port %u read:", port_id);
+
+		if (size > (ARRAY_SIZE(message) - 1)) {
+			size = ARRAY_SIZE(message) - 1;
+			result = 1;
+		}
+		for (index = 0; index < size; index++) {
+			dbg_len = strlen(dbg_line);
+			snprintf(dbg_line + dbg_len,
+					ARRAY_SIZE(dbg_line) - 1 - dbg_len,
+					" %02x", message[index + 1]);
+			serio_interrupt(drv->ports[port_id].serio,
+				message[index + 1], 0);
+		}
+#if defined(CONFIG_I2C)
+		if (drv->dev_i2c != NULL)
+			dev_dbg(&drv->dev_i2c->dev, "%s\n", dbg_line);
+		else
+#endif
+#if defined(CONFIG_SPI)
+		if (drv->dev_spi != NULL)
+			dev_dbg(&drv->dev_spi->dev, "%s\n", dbg_line);
+#endif
+			;
+	}
+	return result;
+}
+
+static int tp_serio_data_write(struct tp_serio_data *drv,
+		u8 id, unsigned char data)
+{
+	int result;
+	size_t size;
+	unsigned char message[TP_SERIO_CHUNK_SIZE];
+	struct tp_serio_port *port = drv->ports + id;
+
+	result = -ENODEV;
+	memset(message, 0, sizeof(message));
+	message[0] = (port->id << 4) | 0x01;
+	message[1] = data;
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL) {
+		size = sizeof(message);
+		dev_dbg(&drv->dev_spi->dev,
+			"port %u write: %02x\n", port->id, data);
+		result = tp_serio_spi_write(drv, size, message);
+	} else
+#endif
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL) {
+		size = 2;
+		dev_dbg(&drv->dev_i2c->dev,
+			"port %u write: %02x\n", port->id, data);
+		result = tp_serio_i2c_write(drv, size, message);
+	}
+#endif
+		;
+	return result;
+}
+
+static void tp_serio_trigger_tx(struct tp_serio_data *drv)
+{
+	drv->poll_ready = true;
+	wake_up(&drv->poll_wq);
+}
+
+static int tp_serio_write(struct serio *serio, unsigned char data)
+{
+	int result = -EINVAL;
+	struct tp_serio_data *drv;
+	struct tp_serio_port *port = (struct tp_serio_port *)serio->port_data;
+
+	if (port != NULL) {
+		drv = port->drv;
+		if (port->tx.has_data) {
+			result = -ENOMEM;
+		} else {
+			port->tx.data = data;
+			port->tx.has_data = true;
+			result = 0;
+		}
+		tp_serio_trigger_tx(drv);
+	}
+	return result;
+}
+
+static int tp_serio_start(struct serio *serio)
+{
+	struct tp_serio_port *port = (struct tp_serio_port *)serio->port_data;
+
+	if (port != NULL)
+		port->registered = true;
+	return 0;
+}
+
+static void tp_serio_stop(struct serio *serio)
+{
+	struct tp_serio_port *port = (struct tp_serio_port *)serio->port_data;
+
+	if (port != NULL)
+		port->registered = false;
+}
+
+static int tp_serio_create_port(struct tp_serio_data *drv, unsigned int id)
+{
+	struct serio *serio;
+	struct device *dev;
+
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL) {
+		dev = &drv->dev_spi->dev;
+	} else
+#endif
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL) {
+		dev = &drv->dev_i2c->dev;
+	} else
+#endif
+	{
+		return -ENODEV;
+	}
+	serio = devm_kzalloc(dev, sizeof(struct serio), GFP_KERNEL);
+	if (!serio)
+		return -ENOMEM;
+	strlcpy(serio->name, "tp_serio", sizeof(serio->name));
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL) {
+		snprintf(serio->phys, sizeof(serio->phys),
+			 "%s/port%u", dev_name(&drv->dev_spi->dev), id);
+	} else
+#endif
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL) {
+		snprintf(serio->phys, sizeof(serio->phys),
+			 "%s/port%u", dev_name(&drv->dev_i2c->dev), id);
+	}
+#endif
+		;
+	serio->id.type = SERIO_8042;
+	serio->write = tp_serio_write;
+	serio->start = tp_serio_start;
+	serio->stop = tp_serio_stop;
+	serio->port_data = drv->ports + id;
+	drv->ports[id].serio = serio;
+	drv->ports[id].drv = drv;
+	drv->ports[id].id = id;
+	drv->ports[id].registered = false;
+	drv->ports[id].tx.has_data = false;
+	drv->ports[id].tx.data = 0x00;
+	return 0;
+}
+
+static void tp_serio_destroy_port(struct tp_serio_data *drv, unsigned int id)
+{
+	if (drv->ports[id].registered)
+		serio_unregister_port(drv->ports[id].serio);
+}
+
+static void tp_serio_read_error(struct tp_serio_data *drv, int error)
+{
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL)
+		dev_dbg(&drv->dev_i2c->dev,
+			"i2c read failed: %d\n", error);
+	else
+#endif
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL)
+		dev_dbg(&drv->dev_spi->dev,
+			"spi read failed: %d\n", error);
+#endif
+		;
+	msleep_interruptible(TP_SERIO_POLL_ERROR_DELAY);
+}
+
+static void tp_serio_serio_process_tx(struct tp_serio_data *drv)
+{
+	unsigned int index;
+
+	for (index = 0; index < drv->num_ports; index++) {
+		if (drv->ports[index].tx.has_data) {
+			tp_serio_data_write(drv, index,
+					    drv->ports[index].tx.data);
+			drv->ports[index].tx.has_data = false;
+			usleep_range(TP_SERIO_POLL_WRITE_DELAY * 1000,
+					TP_SERIO_POLL_WRITE_DELAY * 1000);
+		}
+	}
+}
+
+static int tp_serio_serio_process_rx(struct tp_serio_data *drv)
+{
+	int ret;
+
+	do {
+		ret = tp_serio_data_read(drv);
+		usleep_range(TP_SERIO_POLL_READ_DELAY_MIN * 1000,
+				TP_SERIO_POLL_READ_DELAY_MAX * 1000);
+	} while (ret > 0);
+	if ((ret < 0) && (ret != -EAGAIN))
+		tp_serio_read_error(drv, ret);
+	return ret;
+}
+
+static int tp_serio_poll(void *data)
+{
+	struct tp_serio_data *drv = (struct tp_serio_data *)data;
+	const unsigned int poll_timeout = (drv->rx_irq < 0) ?
+			TP_SERIO_POLL_READ_TIMEOUT :
+			TP_SERIO_POLL_WAIT_TIMEOUT;
+
+	while (!kthread_should_stop()) {
+		drv->poll_ready = false;
+		tp_serio_serio_process_tx(drv);
+
+		if (drv->rx_irq < 0)
+			while (tp_serio_serio_process_rx(drv))
+				;
+
+		wait_event_interruptible_timeout(drv->poll_wq, drv->poll_ready,
+				msecs_to_jiffies(poll_timeout));
+	}
+	return 0;
+}
+
+static irqreturn_t tp_serio_alert_handler(int irq, void *dev_id)
+{
+	struct tp_serio_data *drv = (struct tp_serio_data *)dev_id;
+
+	while (tp_serio_serio_process_rx(drv))
+		;
+	return IRQ_HANDLED;
+}
+
+static int tp_serio_device_reset(struct tp_serio_data *drv)
+{
+	int result;
+	unsigned char response[TP_SERIO_CHUNK_SIZE];
+
+	memset(response, 0, sizeof(response));
+	result = tp_serio_request(drv, TP_SERIO_CMD_RESET, response);
+	if (result < 0)
+		return result;
+	if (!memcmp(response, tp_serio_cmd_reset_response, sizeof(response)))
+		result = 0;
+	else
+		result = -EINVAL;
+	return result;
+}
+
+static int tp_serio_device_query(struct tp_serio_data *drv)
+{
+	int result;
+	unsigned char response[TP_SERIO_CHUNK_SIZE];
+
+	memset(response, 0, sizeof(response));
+	result = tp_serio_request(drv, TP_SERIO_CMD_QUERY, response);
+	if (result < 0)
+		return result;
+	if (response[0] == TP_SERIO_CMD_QUERY) {
+		drv->num_ports = response[1];
+		result = 0;
+	} else {
+		result = -EINVAL;
+	}
+	return result;
+}
+
+#if defined(CONFIG_I2C)
+static int tp_serio_probe_i2c(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct tp_serio_data *drv;
+	unsigned int index;
+	unsigned int free_index;
+	int error;
+	int irq;
+	struct serio *s;
+
+	drv = devm_kzalloc(&client->dev, sizeof(*drv), GFP_KERNEL);
+	if (drv == NULL)
+		return -ENOMEM;
+	drv->dev_i2c = client;
+#if defined(CONFIG_SPI)
+	drv->dev_spi = NULL;
+#endif
+	if (tp_serio_device_reset(drv) < 0) {
+		dev_err(&client->dev, "no compatible device found at %s\n",
+			dev_name(&client->dev));
+		return -ENODEV;
+	}
+	error = tp_serio_device_query(drv);
+	if (error || (drv->num_ports == 0)) {
+		dev_err(&client->dev, "no available ports found at %s\n",
+			dev_name(&client->dev));
+		return -ENODEV;
+	}
+	drv->ports = devm_kzalloc(&client->dev,
+			sizeof(struct tp_serio_port) * drv->num_ports,
+			GFP_KERNEL);
+	if (drv->ports == NULL)
+		return -ENOMEM;
+	for (index = 0; index < drv->num_ports; index++) {
+		error = tp_serio_create_port(drv, index);
+		if (error)
+			goto err_out;
+	}
+	init_waitqueue_head(&drv->poll_wq);
+	drv->poll_ready = false;
+	drv->rx_irq = -1;
+	dev_set_drvdata(&client->dev, drv);
+
+	for (index = 0; index < drv->num_ports; index++) {
+		s = drv->ports[index].serio;
+		dev_info(&client->dev, "%s port at %s\n", s->name, s->phys);
+		serio_register_port(s);
+	}
+
+	if (client->dev.of_node != NULL) {
+		irq = of_irq_get(client->dev.of_node, 0);
+		if (irq >= 0) {
+			drv->rx_irq = irq;
+			error = devm_request_threaded_irq(&client->dev, irq,
+					NULL, tp_serio_alert_handler,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					"tp_serio", drv);
+			if (error) {
+				dev_set_drvdata(&client->dev, NULL);
+				index = drv->num_ports;
+				goto err_out;
+			} else {
+				tp_serio_alert_handler(drv->rx_irq, drv);
+			}
+		}
+	}
+	drv->poll_task = kthread_run(tp_serio_poll, drv,
+			"tp_serio i2c");
+	return 0;
+err_out:
+	for (free_index = 0; free_index < index; free_index++)
+		tp_serio_destroy_port(drv, free_index);
+	return error;
+}
+
+static void tp_serio_remove_i2c(struct i2c_client *client)
+{
+	struct tp_serio_data *drv =
+		(struct tp_serio_data *)dev_get_drvdata(&client->dev);
+	unsigned int index;
+
+	if (drv != NULL) {
+		kthread_stop(drv->poll_task);
+		for (index = 0; index < drv->num_ports; index++)
+			tp_serio_destroy_port(drv, index);
+		dev_set_drvdata(&client->dev, NULL);
+	}
+}
+#endif
+
+#if defined(CONFIG_SPI)
+static int tp_serio_probe_spi(struct spi_device *spi)
+{
+	struct tp_serio_data *drv;
+	unsigned int index;
+	unsigned int free_index;
+	int error;
+	int irq;
+	struct serio *s;
+
+	drv = devm_kzalloc(&spi->dev, sizeof(*drv), GFP_KERNEL);
+	if (drv == NULL)
+		return -ENOMEM;
+#if defined(CONFIG_I2C)
+	drv->dev_i2c = NULL;
+#endif
+	drv->dev_spi = spi;
+	if (tp_serio_device_reset(drv) < 0) {
+		dev_err(&spi->dev, "no compatible device found at %s\n",
+			dev_name(&spi->dev));
+		return -ENODEV;
+	}
+	error = tp_serio_device_query(drv);
+	if (error || (drv->num_ports == 0)) {
+		dev_err(&spi->dev, "no available ports found at %s\n",
+			dev_name(&spi->dev));
+		return -ENODEV;
+	}
+	drv->ports = devm_kzalloc(&spi->dev,
+			sizeof(struct tp_serio_port) * drv->num_ports,
+			GFP_KERNEL);
+	if (drv->ports == NULL)
+		return -ENOMEM;
+	for (index = 0; index < drv->num_ports; index++) {
+		error = tp_serio_create_port(drv, index);
+		if (error)
+			goto err_out;
+	}
+	init_waitqueue_head(&drv->poll_wq);
+	drv->poll_ready = false;
+	drv->rx_irq = -1;
+	spi_set_drvdata(spi, drv);
+
+	for (index = 0; index < drv->num_ports; index++) {
+		s = drv->ports[index].serio;
+		dev_info(&spi->dev, "%s port at %s\n", s->name, s->phys);
+		serio_register_port(s);
+	}
+
+	if (spi->dev.of_node != NULL) {
+		irq = of_irq_get(spi->dev.of_node, 0);
+		if (irq >= 0) {
+			drv->rx_irq = irq;
+			error = devm_request_threaded_irq(&spi->dev, irq,
+					NULL, tp_serio_alert_handler,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					"tp_serio", drv);
+			if (error) {
+				spi_set_drvdata(spi, NULL);
+				index = drv->num_ports;
+				goto err_out;
+			} else {
+				tp_serio_alert_handler(drv->rx_irq, drv);
+			}
+		}
+	}
+	drv->poll_task = kthread_run(tp_serio_poll, drv,
+			"tp_serio spi");
+	return 0;
+err_out:
+	for (free_index = 0; free_index < index; free_index++)
+		tp_serio_destroy_port(drv, free_index);
+	return error;
+}
+
+static void tp_serio_remove_spi(struct spi_device *spi)
+{
+	struct tp_serio_data *drv =
+		(struct tp_serio_data *)spi_get_drvdata(spi);
+	unsigned int index;
+
+	if (drv != NULL) {
+		kthread_stop(drv->poll_task);
+		for (index = 0; index < drv->num_ports; index++)
+			tp_serio_destroy_port(drv, index);
+		spi_set_drvdata(spi, NULL);
+	}
+}
+#endif
+
+static int tp_serio_register(struct tp_serio_driver *driver)
+{
+	int res = 0;
+#if defined(CONFIG_I2C)
+	res = i2c_register_driver(THIS_MODULE, &driver->i2c);
+#endif
+#if defined(CONFIG_SPI)
+	if (res == 0)
+		res = spi_register_driver(&driver->spi);
+#endif
+	return res;
+}
+
+static void tp_serio_unregister(struct tp_serio_driver *driver)
+{
+#if defined(CONFIG_SPI)
+	spi_unregister_driver(&driver->spi);
+#endif
+#if defined(CONFIG_I2C)
+	i2c_del_driver(&driver->i2c);
+#endif
+}
+
+static const struct of_device_id tp_serio_of_ids[] = {
+	{
+		.compatible = "tp,tp_serio",
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, tp_serio_of_ids);
+
+#if defined(CONFIG_I2C)
+static const struct i2c_device_id tp_serio_i2c_ids[] = {
+	{
+		.name = "tp_serio",
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tp_serio_i2c_ids);
+#endif
+
+#if defined(CONFIG_SPI)
+static const struct spi_device_id tp_serio_spi_ids[] = {
+	{
+		.name = "tp_serio",
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, tp_serio_spi_ids);
+#endif
+
+static struct tp_serio_driver tp_serio_drv = {
+#if defined(CONFIG_I2C)
+	{
+		.driver = {
+			.name = "tp_serio",
+			.owner = THIS_MODULE,
+			.of_match_table = of_match_ptr(tp_serio_of_ids)
+		},
+		.probe = tp_serio_probe_i2c,
+		.remove = tp_serio_remove_i2c,
+		.id_table = tp_serio_i2c_ids
+	},
+#endif
+#if defined(CONFIG_SPI)
+	{
+		.driver = {
+			.name = "tp_serio",
+			.owner = THIS_MODULE,
+			.of_match_table = of_match_ptr(tp_serio_of_ids)
+		},
+		.probe = tp_serio_probe_spi,
+		.remove = tp_serio_remove_spi,
+		.id_table = tp_serio_spi_ids
+	}
+#endif
+};
+
+module_driver(tp_serio_drv, tp_serio_register, tp_serio_unregister)
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 36/39] input: tp_serio: catch up API changes
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (34 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 35/39] input: added TF307 serio PS/2 emulator driver Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 37/39] drm: baikal-m: add vblank events, fix mode switching Daniil Gnusarev
                   ` (3 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 drivers/input/serio/tp_serio.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/drivers/input/serio/tp_serio.c b/drivers/input/serio/tp_serio.c
index f530cb50182e4..26bc9ca10d54c 100644
--- a/drivers/input/serio/tp_serio.c
+++ b/drivers/input/serio/tp_serio.c
@@ -486,8 +486,7 @@ static int tp_serio_device_query(struct tp_serio_data *drv)
 }
 
 #if defined(CONFIG_I2C)
-static int tp_serio_probe_i2c(struct i2c_client *client,
-		const struct i2c_device_id *id)
+static int tp_serio_probe_i2c(struct i2c_client *client)
 {
 	struct tp_serio_data *drv;
 	unsigned int index;
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 37/39] drm: baikal-m: add vblank events, fix mode switching
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (35 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 36/39] input: tp_serio: catch up API changes Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 38/39] drm: baikal-vdu: disable backlight driver loading Daniil Gnusarev
                   ` (2 subsequent siblings)
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

Added support for vblank events, which eliminated the slowness of
the graphical environment. In addition, for the case of several monitors,
the pitch parameter is taken into account, which solves the problem of
only vertical arrangement of screens.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 drivers/gpu/drm/baikal/baikal_vdu_crtc.c  | 58 ++++++++++++++++++-----
 drivers/gpu/drm/baikal/baikal_vdu_drm.h   | 18 +++++++
 drivers/gpu/drm/baikal/baikal_vdu_drv.c   | 16 +++++--
 drivers/gpu/drm/baikal/baikal_vdu_plane.c |  5 ++
 4 files changed, 83 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/baikal/baikal_vdu_crtc.c b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c
index 50327813c04b3..b51231719fc9c 100644
--- a/drivers/gpu/drm/baikal/baikal_vdu_crtc.c
+++ b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c
@@ -36,6 +36,13 @@ irqreturn_t baikal_vdu_irq(int irq, void *data)
 	irq_stat = readl(priv->regs + IVR);
 	raw_stat = readl(priv->regs + ISR);
 
+	if (irq_stat & INTR_VCT) {
+		priv->counters[10]++;
+		if (priv->vblank)
+			drm_crtc_handle_vblank(&priv->crtc);
+		status = IRQ_HANDLED;
+	}
+
 	if (raw_stat & INTR_UFU) {
 		priv->counters[4]++;
 		status = IRQ_HANDLED;
@@ -228,6 +235,7 @@ static void baikal_vdu_crtc_helper_mode_set_nofb(struct drm_crtc *crtc)
 	unsigned int ppl, hsw, hfp, hbp;
 	unsigned int lpp, vsw, vfp, vbp;
 	unsigned int reg;
+	unsigned long flags;
 	int ret = 0;
 
 	drm_mode_debug_printmodeline(mode);
@@ -260,29 +268,31 @@ static void baikal_vdu_crtc_helper_mode_set_nofb(struct drm_crtc *crtc)
 		DRM_ERROR("Cannot set desired pixel clock (%lu Hz)\n", rate);
 
 	ppl = mode->hdisplay / 16;
-	if (priv->index == CRTC_LVDS && priv-> num_lanes == 2) {
+	if (priv->index == CRTC_LVDS && priv->num_lanes == 2) {
 		hsw = mode->hsync_end - mode->hsync_start;
 		hfp = mode->hsync_start - mode->hdisplay - 1;
+		hbp = mode->htotal - mode->hsync_end;
 	} else {
 		hsw = mode->hsync_end - mode->hsync_start - 1;
-		hfp = mode->hsync_start - mode->hdisplay;
+		hfp = mode->hsync_start - mode->hdisplay - 1;
+		hbp = mode->htotal - mode->hsync_end - 1;
 	}
-	hbp = mode->htotal - mode->hsync_end;
 
 	lpp = mode->vdisplay;
 	vsw = mode->vsync_end - mode->vsync_start;
 	vfp = mode->vsync_start - mode->vdisplay;
 	vbp = mode->vtotal - mode->vsync_end;
 
+	spin_lock_irqsave(&priv->lock, flags);
 	writel((HTR_HFP(hfp) & HTR_HFP_MASK) |
 			(HTR_PPL(ppl) & HTR_PPL_MASK) |
 			(HTR_HBP(hbp) & HTR_HBP_MASK) |
 			(HTR_HSW(hsw) & HTR_HSW_MASK),
 			priv->regs + HTR);
-
 	if (mode->hdisplay > 4080 || ppl * 16 != mode->hdisplay)
 		writel((HPPLOR_HPPLO(mode->hdisplay) & HPPLOR_HPPLO_MASK) | HPPLOR_HPOE,
-				priv->regs + HPPLOR);
+			priv->regs + HPPLOR);
+	spin_unlock_irqrestore(&priv->lock, flags);
 
 	writel((VTR1_VSW(vsw) & VTR1_VSW_MASK) |
 			(VTR1_VFP(vfp) & VTR1_VFP_MASK) |
@@ -314,8 +324,8 @@ static enum drm_mode_status baikal_vdu_mode_valid(struct drm_crtc *crtc,
 	                const struct drm_display_mode *mode)
 {
 	struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
-	if (!priv->mode_override && (mode->hdisplay > 2560 ||
-			mode->vdisplay > 1440))
+	if (!priv->mode_override && (mode->hdisplay > 4096 ||
+			mode->vdisplay > 4096))
 		return MODE_BAD;
 	else
 		return MODE_OK;
@@ -329,8 +339,19 @@ static void baikal_vdu_crtc_helper_enable(struct drm_crtc *crtc,
 	u32 cntl, gpio;
 
 	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n");
+
+	writel(ISCR_VSC_VFP, priv->regs + ISCR);
+
+	/* hold clock domain reset; disable clocking */
+	writel(0, priv->regs + PCTR);
+
 	baikal_vdu_crtc_clk_enable(priv);
 
+	/* release clock reset; enable clocking */
+	cntl = readl(priv->regs + PCTR);
+	cntl |= PCTR_PCR + PCTR_PCI;
+	writel(cntl, priv->regs + PCTR);
+
 	/* Set 16-word input FIFO watermark */
 	/* Enable and Power Up */
 	cntl = readl(priv->regs + CR1);
@@ -373,16 +394,14 @@ static void baikal_vdu_crtc_helper_enable(struct drm_crtc *crtc,
 		cntl |= CR1_OPS_LCD24;
 	writel(cntl, priv->regs + CR1);
 
-	writel(0x3ffff, priv->regs + ISR);
-	writel(INTR_FER, priv->regs + IMR);
+	drm_crtc_vblank_on(crtc);
 }
 
 void baikal_vdu_crtc_helper_disable(struct drm_crtc *crtc)
 {
 	struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
 
-	writel(0x3ffff, priv->regs + ISR);
-	writel(0, priv->regs + IMR);
+	drm_crtc_vblank_off(crtc);
 
 	/* Disable clock */
 	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "disabling pixel clock\n");
@@ -406,6 +425,21 @@ static void baikal_vdu_crtc_helper_atomic_flush(struct drm_crtc *crtc,
 	}
 }
 
+static int baikal_vdu_enable_vblank(struct drm_crtc *crtc)
+{
+	struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+
+	baikal_vdu_set_irq(priv, true, true);
+	return 0;
+}
+
+static void baikal_vdu_disable_vblank(struct drm_crtc *crtc)
+{
+	struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc);
+
+	baikal_vdu_set_irq(priv, true, false);
+}
+
 const struct drm_crtc_funcs crtc_funcs = {
 	.set_config = drm_atomic_helper_set_config,
 	.page_flip = drm_atomic_helper_page_flip,
@@ -413,6 +447,8 @@ const struct drm_crtc_funcs crtc_funcs = {
 	.destroy = drm_crtc_cleanup,
 	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
 	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.enable_vblank = baikal_vdu_enable_vblank,
+	.disable_vblank = baikal_vdu_disable_vblank,
 };
 
 const struct drm_crtc_helper_funcs crtc_helper_funcs = {
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drm.h b/drivers/gpu/drm/baikal/baikal_vdu_drm.h
index 90dc57bac7ae2..d5dfde4b6750c 100644
--- a/drivers/gpu/drm/baikal/baikal_vdu_drm.h
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drm.h
@@ -19,6 +19,8 @@
 #include <linux/gpio.h>
 #include <linux/backlight.h>
 
+#include "baikal_vdu_regs.h"
+
 #define CRTC_HDMI	0
 #define CRTC_LVDS	1
 
@@ -54,6 +56,7 @@ struct baikal_vdu_private {
 	int data_mapping;
 	int off;
 	int ready;
+	bool vblank;
 
 	/* backlight */
 	struct gpio_desc *enable_gpio;
@@ -84,6 +87,21 @@ struct baikal_hdmi_bridge {
 };
 
 /* Generic functions */
+
+static inline void baikal_vdu_set_irq(struct baikal_vdu_private *priv, bool irq, bool vblank)
+{
+	u32 val;
+
+	priv->vblank = vblank && irq;
+
+	val = priv->vblank ? INTR_VCT + INTR_FER : INTR_FER;
+	val = irq ? val : 0;
+
+	/* clear interrupt status */
+	writel(0x3ffff, priv->regs + ISR);
+	writel(val, priv->regs + IMR);
+}
+
 inline void baikal_vdu_switch_on(struct baikal_vdu_private *priv);
 
 inline void baikal_vdu_switch_off(struct baikal_vdu_private *priv);
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drv.c b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
index a951c6661ffa6..63a4916b33e93 100644
--- a/drivers/gpu/drm/baikal/baikal_vdu_drv.c
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
@@ -33,6 +33,7 @@
 #include <drm/drm_of.h>
 #include <drm/drm_panel.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
 
 #include "baikal_vdu_drm.h"
 #include "baikal_vdu_regs.h"
@@ -286,7 +287,7 @@ static int baikal_vdu_allocate_irq(struct platform_device *pdev,
 	}
 
 	/* turn off interrupts before requesting the irq */
-	writel(0, priv->regs + IMR);
+	baikal_vdu_set_irq(priv, false, false);
 	ret = request_irq(priv->irq, baikal_vdu_irq, IRQF_SHARED, dev->driver->name, priv);
 	if (ret != 0)
 		dev_err(dev, "%s %s: IRQ %d allocation failed\n", __func__, priv->name, priv->irq);
@@ -295,8 +296,7 @@ static int baikal_vdu_allocate_irq(struct platform_device *pdev,
 
 static void baikal_vdu_free_irq(struct baikal_vdu_private *priv)
 {
-	writel(0, priv->regs + IMR);
-	writel(0x3ffff, priv->regs + ISR);
+	baikal_vdu_set_irq(priv, false, false);
 	free_irq(priv->irq, priv->drm->dev);
 }
 
@@ -476,6 +476,14 @@ static int baikal_vdu_drm_probe(struct platform_device *pdev)
 	lvds->ready = lvds->ready & !lvds->off;
 	dev_info(dev, "%s output %s\n", hdmi->name, hdmi->ready ? "enabled" : "disabled");
 	dev_info(dev, "%s output %s\n", lvds->name, lvds->ready ? "enabled" : "disabled");
+
+	ret = drm_vblank_init(drm, (hdmi->ready ? 1 : 0) +
+				   (lvds->ready ? 1 : 0));
+	if (ret) {
+		dev_err(dev, "failed to init vblank\n");
+		goto out_config;
+	}
+
 	baikal_vdu_remove_efifb(drm);
 
 	if (hdmi->ready || lvds->ready) {
@@ -489,7 +497,9 @@ static int baikal_vdu_drm_probe(struct platform_device *pdev)
 			dev_err(dev, "failed to register DRM device\n");
 			goto out_config;
 		}
+
 		drm_fbdev_dma_setup(drm, 32);
+
 #if defined(CONFIG_DEBUG_FS)
 		if (hdmi->ready)
 			baikal_vdu_hdmi_debugfs_init(drm->primary);
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_plane.c b/drivers/gpu/drm/baikal/baikal_vdu_plane.c
index d9bdb95a8d962..490110f7e50ce 100644
--- a/drivers/gpu/drm/baikal/baikal_vdu_plane.c
+++ b/drivers/gpu/drm/baikal/baikal_vdu_plane.c
@@ -87,6 +87,11 @@ static void baikal_vdu_primary_plane_atomic_update(struct drm_plane *plane,
 	}
 
 	writel(cntl, priv->regs + CR1);
+
+	spin_lock_irqsave(&priv->lock, flags);
+	writel((fb->pitches[0] / DIV_ROUND_UP(drm_format_info_bpp(fb->format, 0), 8)) |
+		HPPLOR_HPOE, priv->regs + HPPLOR);
+	spin_unlock_irqrestore(&priv->lock, flags);
 }
 
 static const struct drm_plane_helper_funcs baikal_vdu_primary_plane_helper_funcs = {
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 38/39] drm: baikal-vdu: disable backlight driver loading
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (36 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 37/39] drm: baikal-m: add vblank events, fix mode switching Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:02 ` [d-kernel] [PATCH 39/39] config-aarch64: enable more configs for baikal-m support Daniil Gnusarev
  2024-10-14 14:28 ` [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Vitaly Chikunov
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

When starting the driver with lvds an error occurs:
[   14.310460] BUG: scheduling while atomic: swapper/3/0/0x00000103
[   14.322780] Modules linked in: .....
[   14.395894] CPU: 3 PID: 0 Comm: swapper/3 Tainted: G W 6.6.41+ #87
[   14.411147] Hardware name: Edelweiss TF307-MB-S-D/BM1BM1-D, BIOS 5.3 01/10/2022
[   14.418471] Call trace:
[   14.420925]  dump_backtrace+0xa4/0x130
[   14.424693]  show_stack+0x20/0x38
[   14.428018]  dump_stack_lvl+0x48/0x60
[   14.431696]  dump_stack+0x18/0x28
[   14.435022]  __schedule_bug+0x58/0x78
[   14.438698]  __schedule+0x1094/0x17d0
[   14.442373]  schedule+0x64/0x108
[   14.445611]  schedule_preempt_disabled+0x2c/0x50
[   14.450241]  rwsem_down_read_slowpath+0x1e0/0x538
[   14.454958]  down_read+0xb8/0xc8
[   14.458198]  kernfs_find_and_get_ns+0x44/0x88
[   14.462570]  sysfs_notify+0xa4/0xc0
[   14.466068]  backlight_force_update+0xb4/0x118
[   14.470528]  baikal_vdu_input_event+0x7c/0x160 [baikal_vdu_drm]

Presumably the error occurs when calling sysfs_notify in the event handler.
The called function tries to sleep when executed, which is undesirable.
Disable the backlight driver until the problem is solved

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 drivers/gpu/drm/baikal/baikal_vdu_drv.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drv.c b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
index 63a4916b33e93..bb37939145c32 100644
--- a/drivers/gpu/drm/baikal/baikal_vdu_drv.c
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
@@ -457,10 +457,12 @@ static int baikal_vdu_drm_probe(struct platform_device *pdev)
 		dev_info(dev, "No 'lvds-lanes' property found\n");
 	}
 	if (lvds->ready) {
+#if 0
 		ret = baikal_vdu_backlight_create(drm);
 		if (ret) {
 			dev_err(dev, "LVDS: failed to create backlight\n");
 		}
+#endif
 		if (bridge_is_baikal_lvds_bridge(lvds->bridge)) {
 			panel_bridge = bridge_to_baikal_lvds_bridge(lvds->bridge);
 			panel_bridge->vdu = lvds;
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* [d-kernel] [PATCH 39/39] config-aarch64: enable more configs for baikal-m support
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (37 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 38/39] drm: baikal-vdu: disable backlight driver loading Daniil Gnusarev
@ 2024-10-14 14:02 ` Daniil Gnusarev
  2024-10-14 14:28 ` [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Vitaly Chikunov
  39 siblings, 0 replies; 41+ messages in thread
From: Daniil Gnusarev @ 2024-10-14 14:02 UTC (permalink / raw)
  To: devel-kernel

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
---
 config-aarch64 | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/config-aarch64 b/config-aarch64
index 7a77c9e9a7b95..98a0563965713 100644
--- a/config-aarch64
+++ b/config-aarch64
@@ -1633,3 +1633,17 @@ CONFIG_USB_SERIAL_PL2303=y
 CONFIG_USB_SERIAL_FTDI_SIO=y
 CONFIG_USB_SISUSBVGA=y
 # CONFIG_DEBUG_INFO_BTF is not set
+CONFIG_ARCH_BAIKAL=y
+CONFIG_USB_PHY_BAIKAL=m
+CONFIG_PCIE_BAIKAL=y
+CONFIG_PCIE_BAIKAL_HOST=y
+CONFIG_PCIE_BAIKAL_EP=y
+CONFIG_SND_BAIKAL_I2S=m
+CONFIG_SND_BAIKAL_PIO_PCM=y
+CONFIG_DWMAC_BAIKAL=m
+CONFIG_SENSORS_BT1_PVT=m
+CONFIG_DRM_BAIKAL_VDU=m
+CONFIG_DRM_BAIKAL_HDMI=m
+CONFIG_TP_BMC=m
+CONFIG_SERIO_TPLATFORMS=m
+CONFIG_SERDEV_SERIO=m
-- 
2.42.2



^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11
  2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
                   ` (38 preceding siblings ...)
  2024-10-14 14:02 ` [d-kernel] [PATCH 39/39] config-aarch64: enable more configs for baikal-m support Daniil Gnusarev
@ 2024-10-14 14:28 ` Vitaly Chikunov
  39 siblings, 0 replies; 41+ messages in thread
From: Vitaly Chikunov @ 2024-10-14 14:28 UTC (permalink / raw)
  To: ALT Linux kernel packages development

On Mon, Oct 14, 2024 at 06:01:41PM +0400, Daniil Gnusarev wrote:
> Alexey Sheplyakov (17):
>   cpufreq-dt: don't load on Baikal-M SoC
>   net: fwnode_get_phy_id: consider all compatible strings
>   hwmon: bt1-pvt: access registers via pvt_{readl,writel} helpers
>   hwmon: bt1-pvt: define pvt_readl/pvt_writel for Baikal-M SoC
>   hwmon: bt1-pvt: adjusted probing for Baikal-M SoC
>   hwmon: bt1-pvt: added compatible baikal,pvt
>   drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a
>   dt-bindings: dw-hdmi: added ahb-audio-regshift
>   drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M
>   drm/panfrost: forcibly set dma-coherent on Baikal-M
>   drm/panfrost: disable devfreq on Baikal-M
>   pm: disable all sleep states on Baikal-M based boards
>   sound: dwc-i2s: paper over RX overrun warnings on Baikal-M
>   sound: dwc-i2s: request all IRQs specified in device tree
>   usb: dwc3: of-simple: added compatible string for Baikal-M SoC
>   serial: 8250_dw: verify clock rate in dw8250_set_termios
>   dw-pcie: refuse to load on Baikal-M with recent firmware
> 
> Daniil Gnusarev (20):
>   Baikal Electronics SoC family
>   Clk: Add clock drivers for Baikal BE-M1000 with new firmware
>   USB: Add support for Baikal USB PHY
>   PCI: Add support for PCIe controller for Baikal BE-M1000
>   AHCI SATA: Add support for Baikal BE-M1000
>   UART: Add support for UART Baikal BE-M1000
>   Sound: add support for Baikal BE-M1000 I2S
>   sound: baikal-i2s: paper over RX overrun warnings on Baikal-M
>   net: stmmac: support of Baikal-BE1000 SoCs GMAC
>   PVT: support register addressing with new firmware
>   drm: add Baikal-M SoC video display unit driver
>   bmc:  add board management controller driver
>   clk: use "cmu-id" if there is no "reg" in devicetree
>   pci: baikal-pcie: driver compatibility with SDK earlier than 5.7
>   pci: baikal-pcie: driver compatibility with SDK versions 5.4
>   drm: baikal-vdu: driver compatibility with SDK earlier than 5.9
>   input: tp_serio: catch up API changes
>   drm: baikal-m: add vblank events, fix mode switching
>   drm: baikal-vdu: disable backlight driver loading
>   config-aarch64: enable more configs for baikal-m support
> 
> Vadim V. Vlasov (2):
>   input: new driver - serdev-serio
>   input: added TF307 serio PS/2 emulator driver
> 
>  .../display/bridge/synopsys,dw-hdmi.yaml      |    7 +
>  arch/arm64/Kconfig.platforms                  |   10 +
>  config-aarch64                                |   14 +
>  drivers/acpi/pci_mcfg.c                       |   38 +
>  drivers/ata/ahci_dwc.c                        |    5 +
>  drivers/clk/Makefile                          |    1 +
>  drivers/clk/baikal/Makefile                   |    3 +
>  drivers/clk/baikal/clk-bm1000.c               |  846 ++++++
>  drivers/clk/baikal/clk-bs1000.c               |  504 ++++
>  drivers/cpufreq/cpufreq-dt-platdev.c          |    3 +
>  drivers/gpu/drm/Kconfig                       |    2 +
>  drivers/gpu/drm/Makefile                      |    1 +
>  drivers/gpu/drm/baikal/Kconfig                |   12 +
>  drivers/gpu/drm/baikal/Makefile               |   11 +
>  drivers/gpu/drm/baikal/baikal-hdmi.c          |  123 +
>  drivers/gpu/drm/baikal/baikal_vdu_backlight.c |  261 ++
>  drivers/gpu/drm/baikal/baikal_vdu_crtc.c      |  476 ++++
>  drivers/gpu/drm/baikal/baikal_vdu_debugfs.c   |   95 +
>  drivers/gpu/drm/baikal/baikal_vdu_drm.h       |  133 +
>  drivers/gpu/drm/baikal/baikal_vdu_drv.c       |  578 +++++
>  drivers/gpu/drm/baikal/baikal_vdu_panel.c     |  193 ++
>  drivers/gpu/drm/baikal/baikal_vdu_plane.c     |  143 ++
>  drivers/gpu/drm/baikal/baikal_vdu_regs.h      |  137 +
>  drivers/gpu/drm/bridge/Kconfig                |    7 +
>  .../drm/bridge/synopsys/dw-hdmi-ahb-audio.c   |  106 +-
>  .../gpu/drm/bridge/synopsys/dw-hdmi-audio.h   |    1 +
>  drivers/gpu/drm/bridge/synopsys/dw-hdmi.c     |   19 +
>  drivers/gpu/drm/drm_panel.c                   |   37 +
>  drivers/gpu/drm/panfrost/panfrost_devfreq.c   |    5 +
>  drivers/gpu/drm/panfrost/panfrost_drv.c       |    5 +
>  drivers/hwmon/Kconfig                         |    5 +-
>  drivers/hwmon/bt1-pvt.c                       |  152 +-
>  drivers/hwmon/bt1-pvt.h                       |    8 +
>  drivers/input/serio/Kconfig                   |   21 +
>  drivers/input/serio/Makefile                  |    2 +
>  drivers/input/serio/serdev-serio.c            |  121 +
>  drivers/input/serio/tp_serio.c                |  746 ++++++
>  drivers/misc/Kconfig                          |   16 +
>  drivers/misc/Makefile                         |    1 +
>  drivers/misc/tp_bmc.c                         |  768 ++++++
>  drivers/net/ethernet/stmicro/stmmac/Kconfig   |    8 +
>  drivers/net/ethernet/stmicro/stmmac/Makefile  |    1 +
>  .../ethernet/stmicro/stmmac/dwmac-baikal.c    |  542 ++++
>  .../ethernet/stmicro/stmmac/dwmac1000_core.c  |    1 +
>  .../ethernet/stmicro/stmmac/dwmac1000_dma.c   |   56 +-
>  .../ethernet/stmicro/stmmac/dwmac1000_dma.h   |   32 +
>  .../net/ethernet/stmicro/stmmac/dwmac_lib.c   |    8 +
>  drivers/net/phy/phy_device.c                  |   41 +-
>  drivers/pci/controller/dwc/Kconfig            |   32 +
>  drivers/pci/controller/dwc/Makefile           |    5 +
>  drivers/pci/controller/dwc/pcie-baikal-acpi.c |   23 +
>  drivers/pci/controller/dwc/pcie-baikal-core.c | 2287 +++++++++++++++++
>  drivers/pci/controller/dwc/pcie-baikal-tune.c |  570 ++++
>  drivers/pci/controller/dwc/pcie-baikal.h      |   16 +
>  .../pci/controller/dwc/pcie-designware-plat.c |    5 +
>  drivers/pci/controller/dwc/pcie-designware.c  |    3 +-
>  drivers/pci/controller/dwc/pcie-designware.h  |    1 +
>  drivers/phy/Kconfig                           |    1 +
>  drivers/phy/Makefile                          |    1 +
>  drivers/phy/baikal/Kconfig                    |   10 +
>  drivers/phy/baikal/Makefile                   |    3 +
>  drivers/phy/baikal/baikal-usb-phy.c           |  305 +++
>  drivers/tty/serial/8250/8250_dw.c             |   11 +-
>  drivers/usb/dwc3/dwc3-of-simple.c             |    3 +
>  include/drm/bridge/dw_hdmi.h                  |    2 +
>  include/drm/drm_panel.h                       |    3 +
>  include/linux/pci-ecam.h                      |    2 +
>  kernel/exit.c                                 |    1 +
>  kernel/power/suspend.c                        |   13 +
>  net/ethernet/eth.c                            |    1 +
>  sound/soc/Kconfig                             |    1 +
>  sound/soc/Makefile                            |    1 +
>  sound/soc/baikal/Kconfig                      |   21 +
>  sound/soc/baikal/Makefile                     |    6 +
>  sound/soc/baikal/baikal-i2s.c                 |  809 ++++++
>  sound/soc/baikal/baikal-pio-pcm.c             |  264 ++
>  sound/soc/baikal/local.h                      |  138 +
>  sound/soc/dwc/dwc-i2s.c                       |   23 +-
>  sound/soc/dwc/local.h                         |    1 +
>  79 files changed, 10745 insertions(+), 122 deletions(-)
>  create mode 100644 drivers/clk/baikal/Makefile
>  create mode 100644 drivers/clk/baikal/clk-bm1000.c
>  create mode 100644 drivers/clk/baikal/clk-bs1000.c
>  create mode 100644 drivers/gpu/drm/baikal/Kconfig
>  create mode 100644 drivers/gpu/drm/baikal/Makefile
>  create mode 100644 drivers/gpu/drm/baikal/baikal-hdmi.c
>  create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_backlight.c
>  create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_crtc.c
>  create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_debugfs.c
>  create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_drm.h
>  create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_drv.c
>  create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_panel.c
>  create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_plane.c
>  create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_regs.h
>  create mode 100644 drivers/input/serio/serdev-serio.c
>  create mode 100644 drivers/input/serio/tp_serio.c
>  create mode 100644 drivers/misc/tp_bmc.c
>  create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c
>  create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h
>  create mode 100644 drivers/pci/controller/dwc/pcie-baikal-acpi.c
>  create mode 100644 drivers/pci/controller/dwc/pcie-baikal-core.c
>  create mode 100644 drivers/pci/controller/dwc/pcie-baikal-tune.c
>  create mode 100644 drivers/pci/controller/dwc/pcie-baikal.h
>  create mode 100644 drivers/phy/baikal/Kconfig
>  create mode 100644 drivers/phy/baikal/Makefile
>  create mode 100644 drivers/phy/baikal/baikal-usb-phy.c
>  create mode 100644 sound/soc/baikal/Kconfig
>  create mode 100644 sound/soc/baikal/Makefile
>  create mode 100644 sound/soc/baikal/baikal-i2s.c
>  create mode 100644 sound/soc/baikal/baikal-pio-pcm.c
>  create mode 100644 sound/soc/baikal/local.h
> 
> -- 
> 2.42.2
> 
> Подготовлены обновленные патчи для поддержки Байкал-М в ядре un-def для p11.
> 
> Основная идея в работоспособности на машинах с новым SDK от Baikal
> Electronics, но  с сохранением работоспособности на старых. Проверялось на
> SDK с версии SDK-ARM64-5.4 по версию SDK-ARM64-2403-6.6 на плате TF307 MB-S-D
> и на имеющихся машинах Эдельвейс, Radeola, Aquarius (уже без изменения версии)
> 
> Доработан drm-драйвер для работы с 4k мониторами, исправлено переключение
> режимов для X.

Applied, thanks

Патчсет можно слать в виде pull request.

> 
> Патчи доступны по ссылке:
> https://gitlab.basealt.space/altworkstation/kernel-image/-/commit/d72f57bd8a7c2deb79eaede05848f1837edc9ae9
> _______________________________________________
> devel-kernel mailing list
> devel-kernel@lists.altlinux.org
> https://lists.altlinux.org/mailman/listinfo/devel-kernel


^ permalink raw reply	[flat|nested] 41+ messages in thread

end of thread, other threads:[~2024-10-14 14:28 UTC | newest]

Thread overview: 41+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-10-14 14:01 [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 01/39] Baikal Electronics SoC family Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 02/39] Clk: Add clock drivers for Baikal BE-M1000 with new firmware Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 03/39] USB: Add support for Baikal USB PHY Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 04/39] PCI: Add support for PCIe controller for Baikal BE-M1000 Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 05/39] AHCI SATA: Add support " Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 06/39] UART: Add support for UART " Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 07/39] cpufreq-dt: don't load on Baikal-M SoC Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 08/39] Sound: add support for Baikal BE-M1000 I2S Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 09/39] sound: baikal-i2s: paper over RX overrun warnings on Baikal-M Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 10/39] net: stmmac: support of Baikal-BE1000 SoCs GMAC Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 11/39] net: fwnode_get_phy_id: consider all compatible strings Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 12/39] hwmon: bt1-pvt: access registers via pvt_{readl, writel} helpers Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 13/39] hwmon: bt1-pvt: define pvt_readl/pvt_writel for Baikal-M SoC Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 14/39] hwmon: bt1-pvt: adjusted probing " Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 15/39] hwmon: bt1-pvt: added compatible baikal, pvt Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 16/39] PVT: support register addressing with new firmware Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 17/39] drm: add Baikal-M SoC video display unit driver Daniil Gnusarev
2024-10-14 14:01 ` [d-kernel] [PATCH 18/39] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 19/39] dt-bindings: dw-hdmi: added ahb-audio-regshift Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 20/39] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 21/39] drm/panfrost: forcibly set dma-coherent on Baikal-M Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 22/39] drm/panfrost: disable devfreq " Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 23/39] bmc: add board management controller driver Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 24/39] pm: disable all sleep states on Baikal-M based boards Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 25/39] sound: dwc-i2s: paper over RX overrun warnings on Baikal-M Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 26/39] sound: dwc-i2s: request all IRQs specified in device tree Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 27/39] usb: dwc3: of-simple: added compatible string for Baikal-M SoC Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 28/39] serial: 8250_dw: verify clock rate in dw8250_set_termios Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 29/39] clk: use "cmu-id" if there is no "reg" in devicetree Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 30/39] pci: baikal-pcie: driver compatibility with SDK earlier than 5.7 Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 31/39] pci: baikal-pcie: driver compatibility with SDK versions 5.4 Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 32/39] dw-pcie: refuse to load on Baikal-M with recent firmware Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 33/39] drm: baikal-vdu: driver compatibility with SDK earlier than 5.9 Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 34/39] input: new driver - serdev-serio Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 35/39] input: added TF307 serio PS/2 emulator driver Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 36/39] input: tp_serio: catch up API changes Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 37/39] drm: baikal-m: add vblank events, fix mode switching Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 38/39] drm: baikal-vdu: disable backlight driver loading Daniil Gnusarev
2024-10-14 14:02 ` [d-kernel] [PATCH 39/39] config-aarch64: enable more configs for baikal-m support Daniil Gnusarev
2024-10-14 14:28 ` [d-kernel] [PATCH 00/39] Support Baikal-M in un-def in p11 Vitaly Chikunov

ALT Linux kernel packages development

This inbox may be cloned and mirrored by anyone:

	git clone --mirror http://lore.altlinux.org/devel-kernel/0 devel-kernel/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 devel-kernel devel-kernel/ http://lore.altlinux.org/devel-kernel \
		devel-kernel@altlinux.org devel-kernel@altlinux.ru devel-kernel@altlinux.com
	public-inbox-index devel-kernel

Example config snippet for mirrors.
Newsgroup available over NNTP:
	nntp://lore.altlinux.org/org.altlinux.lists.devel-kernel


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git