ALT Linux kernel packages development
 help / color / mirror / Atom feed
* [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC
@ 2026-02-27 10:32 Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 01/35] Baikal Electronics SoC family Daniil Gnusarev
                   ` (34 more replies)
  0 siblings, 35 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

Патчи для СнК BE-M1000.
Основаны на серии патчей для ядра 6.12. Часть драйверов обновлена
из SDK-ARM64-2509-6.12.
В данной серии отсутствуют драйверы для шины PCIe и будут в отдельной.

Проверена работа на машинах Auqarius, Rhadeola, Эдельвейс.
Для платы TF307 проверена на firmware версий:
SDK-ARM64-5.4
SDK-ARM64-5.5
SDK-ARM64-5.6
SDK-ARM64-5.7
SDK-ARM64-5.8
SDK-ARM64-5.9
SDK-ARM64-5.10
SDK-ARM64-5.11
SDK-ARM64-5.12
SDK-ARM64-6.1
SDK-ARM64-6.2
SDK-ARM64-6.2
SDK-ARM64-6.3
SDK-ARM64-6.4
SDK-ARM64-2403-6.6
SDK-ARM64-2406-6.6
SDK-ARM64-2409-6.6
SDK-ARM64-2412-6.6
SDK-ARM64-2503-6.12
SDK-ARM64-2506-6.12
SDK-ARM64-2509-6.12



Alexey Sheplyakov (13):
  usb: dwc3: of-simple: added compatible string for Baikal-M SoC
  serial: 8250_dw: verify clock rate in dw8250_set_termios
  cpufreq-dt: don't load on Baikal-M SoC
  net: fwnode_get_phy_id: consider all compatible strings
  drm/panfrost: forcibly set dma-coherent on Baikal-M
  drm/panfrost: disable devfreq on Baikal-M
  sound: dwc-i2s: request all IRQs specified in device tree
  sound: dwc-i2s: paper over RX overrun warnings on Baikal-M
  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
  pm: disable all sleep states on Baikal-M based boards
  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
  clk: baikal-m: old firmware: use "cmu-id" if there is no "reg" in
    devicetree
  usb: add support for Baikal USB PHY
  uart: add support for UART Baikal BE-M1000
  net: stmmac: support of Baikal-BE1000 SoCs GMAC
  ata: ahci: add support for Baikal BE-M1000
  drm: add Baikal-M SoC video display unit driver
  drm: baikal-vdu: driver compatibility with SDK earlier than 5.9
  drm: baikal-vdu: disable backlight driver loading
  sound: add support for Baikal BE-M1000 I2S
  sound: baikal-i2s: paper over RX overrun warnings on Baikal-M
  dw-hdmi: add flag SNDRV_PCM_INFO_BATCH for audio via hdmi on Baikal-M
  bmc:  add board management controller driver
  sound: hda: add driver for HDA controller on Baikal-M
  sound: hda: enable jack detection in polling mode on Baikal-M
  input: serio: add an alias to the sersev-serio driver
  hwmon: add Baikal-M monitoring driver
  hwmon: baikal-pvt: support work on machines with old firmware
  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                                |   15 +
 drivers/ata/ahci_dwc.c                        |    5 +
 drivers/clk/Makefile                          |    1 +
 drivers/clk/baikal/Makefile                   |    3 +
 drivers/clk/baikal/clk-bm1000.c               |  847 ++++++++++++
 drivers/clk/baikal/clk-bs1000.c               |  498 +++++++
 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          |  122 ++
 drivers/gpu/drm/baikal/baikal_vdu_backlight.c |  259 ++++
 drivers/gpu/drm/baikal/baikal_vdu_crtc.c      |  443 +++++++
 drivers/gpu/drm/baikal/baikal_vdu_debugfs.c   |   94 ++
 drivers/gpu/drm/baikal/baikal_vdu_drm.h       |  115 ++
 drivers/gpu/drm/baikal/baikal_vdu_drv.c       |  569 ++++++++
 drivers/gpu/drm/baikal/baikal_vdu_panel.c     |  195 +++
 drivers/gpu/drm/baikal/baikal_vdu_plane.c     |  118 ++
 drivers/gpu/drm/baikal/baikal_vdu_regs.h      |  136 ++
 drivers/gpu/drm/bridge/synopsys/Kconfig       |    6 +
 .../drm/bridge/synopsys/dw-hdmi-ahb-audio.c   |  108 +-
 .../gpu/drm/bridge/synopsys/dw-hdmi-audio.h   |    2 +
 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c     |   12 +
 drivers/gpu/drm/drm_panel.c                   |    3 +-
 drivers/gpu/drm/panfrost/panfrost_devfreq.c   |    5 +
 drivers/gpu/drm/panfrost/panfrost_drv.c       |    5 +
 drivers/hwmon/Kconfig                         |   11 +
 drivers/hwmon/Makefile                        |    2 +
 drivers/hwmon/baikal-pvt-core.c               | 1160 +++++++++++++++++
 drivers/hwmon/baikal-pvt.h                    |  274 ++++
 drivers/hwmon/bm1000-pvt-hwmon.c              |  206 +++
 drivers/input/serio/Kconfig                   |   21 +
 drivers/input/serio/Makefile                  |    2 +
 drivers/input/serio/serdev-serio.c            |  123 ++
 drivers/input/serio/tp_serio.c                |  746 +++++++++++
 drivers/misc/Kconfig                          |   16 +
 drivers/misc/Makefile                         |    1 +
 drivers/misc/tp_bmc.c                         |  767 +++++++++++
 drivers/net/ethernet/stmicro/stmmac/Kconfig   |    8 +
 drivers/net/ethernet/stmicro/stmmac/Makefile  |    1 +
 .../ethernet/stmicro/stmmac/dwmac-baikal.c    |  500 +++++++
 .../ethernet/stmicro/stmmac/dwmac1000_core.c  |    1 +
 drivers/net/phy/phy_device.c                  |   41 +-
 .../pci/controller/dwc/pcie-designware-plat.c |    5 +
 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           |  303 +++++
 drivers/tty/serial/8250/8250_dw.c             |   11 +-
 drivers/usb/dwc3/dwc3-of-simple.c             |    3 +
 include/drm/drm_panel.h                       |    6 +
 include/linux/firmware/baikal/baikal-smc.h    |   51 +
 include/sound/hdaudio.h                       |    1 +
 kernel/power/suspend.c                        |   13 +
 net/ethernet/eth.c                            |    1 +
 sound/hda/controllers/Kconfig                 |   12 +
 sound/hda/controllers/Makefile                |    2 +
 sound/hda/controllers/hda_baikal.c            |  628 +++++++++
 sound/hda/core/controller.c                   |    7 +
 sound/soc/Kconfig                             |    1 +
 sound/soc/Makefile                            |    1 +
 sound/soc/baikal/Kconfig                      |   21 +
 sound/soc/baikal/Makefile                     |    6 +
 sound/soc/baikal/baikal-i2s.c                 |  808 ++++++++++++
 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 +
 72 files changed, 9754 insertions(+), 54 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/hwmon/baikal-pvt-core.c
 create mode 100644 drivers/hwmon/baikal-pvt.h
 create mode 100644 drivers/hwmon/bm1000-pvt-hwmon.c
 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/phy/baikal/Kconfig
 create mode 100644 drivers/phy/baikal/Makefile
 create mode 100644 drivers/phy/baikal/baikal-usb-phy.c
 create mode 100644 include/linux/firmware/baikal/baikal-smc.h
 create mode 100644 sound/hda/controllers/hda_baikal.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



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

* [d-kernel] [PATCH 01/35] Baikal Electronics SoC family
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 02/35] clk: Add clock drivers for Baikal BE-M1000 Daniil Gnusarev
                   ` (33 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

Enable support for Baikal Electronics SoC family

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Do-not-upstream: this is a feature of Baikal-M
---
 arch/arm64/Kconfig.platforms | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index 13173795c43d4f..a33695537c6e2b 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -53,6 +53,16 @@ config ARCH_AXIADO
 	help
 	  This enables support for Axiado SoC family like AX3000
 
+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] 36+ messages in thread

* [d-kernel] [PATCH 02/35] clk: Add clock drivers for Baikal BE-M1000
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 01/35] Baikal Electronics SoC family Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 03/35] clk: baikal-m: old firmware: use "cmu-id" if there is no "reg" in devicetree Daniil Gnusarev
                   ` (32 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

Add supports for clocks for Baikal BE-M1000.
Taken from SDK-ARM64-2509-6.12.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Co-developed-by: Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>
Do-not-upstream: this is a feature of Baikal-M
---
 drivers/clk/Makefile                       |   1 +
 drivers/clk/baikal/Makefile                |   3 +
 drivers/clk/baikal/clk-bm1000.c            | 842 +++++++++++++++++++++
 drivers/clk/baikal/clk-bs1000.c            | 498 ++++++++++++
 include/linux/firmware/baikal/baikal-smc.h |  51 ++
 5 files changed, 1395 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
 create mode 100644 include/linux/firmware/baikal/baikal-smc.h

diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 61ec08404442b4..ab8b0599811447 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -117,6 +117,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 00000000000000..dc94047aa91316
--- /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 00000000000000..1b4a0c7ea5a5c0
--- /dev/null
+++ b/drivers/clk/baikal/clk-bm1000.c
@@ -0,0 +1,842 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2015-2025 Baikal Electronics, JSC
+ * Author: Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>
+ */
+
+#include <linux/acpi.h>
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/firmware/baikal/baikal-smc.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 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_cur;
+	u32 index;
+	u64 base;
+	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*/
+		index = number - 1;
+		of_property_for_each_u32(node, "clock-indices", index_cur) {
+			if (index < index_cur)
+				index = index_cur;
+		}
+
+		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", 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 void baikal_clk_remove(struct platform_device *pdev)
+{
+	of_clk_del_provider(pdev->dev.of_node);
+}
+
+#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 void 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;
+				}
+			}
+		}
+	}
+}
+
+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 00000000000000..ee8af64ad28e84
--- /dev/null
+++ b/drivers/clk/baikal/clk-bs1000.c
@@ -0,0 +1,498 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021-2025 Baikal Electronics, JSC
+ * Author: Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>
+ */
+
+#include <linux/acpi.h>
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/firmware/baikal/baikal-smc.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>
+
+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;
+	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", 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 void baikal_clk_remove(struct platform_device *pdev)
+{
+	of_clk_del_provider(pdev->dev.of_node);
+}
+
+#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 void 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]);
+		}
+	}
+}
+
+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");
diff --git a/include/linux/firmware/baikal/baikal-smc.h b/include/linux/firmware/baikal/baikal-smc.h
new file mode 100644
index 00000000000000..648b1e9507de48
--- /dev/null
+++ b/include/linux/firmware/baikal/baikal-smc.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025, Baikal Electronics, JSC
+ */
+
+#ifndef __BAIKAL_SMC_H
+#define __BAIKAL_SMC_H
+
+#include <linux/arm-smccc.h>
+
+#define BAIKALL_SIP_SMC_FAST_CALL_VAL(func_num) \
+	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
+	ARM_SMCCC_OWNER_SIP, (func_num))
+
+#define BAIKAL_SMC_CMU_CMD		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0000)
+#define BAIKAL_SMC_PVT_CMD		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0001)
+#define BAIKAL_SMC_FLASH_WRITE		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0002)
+#define BAIKAL_SMC_FLASH_READ		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0003)
+#define BAIKAL_SMC_FLASH_ERASE		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0004)
+#define BAIKAL_SMC_FLASH_PUSH		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0005)
+#define BAIKAL_SMC_FLASH_PULL		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0006)
+#define BAIKAL_SMC_FLASH_POSITION	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0007)
+#define BAIKAL_SMC_FLASH_INIT		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0008)
+#define BAIKAL_SMC_FLASH_LOCK		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0009)
+#define BAIKAL_SMC_VDU_UPDATE		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0100)
+#define BAIKAL_SMC_SCP_LOG_DISABLE	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0200)
+#define BAIKAL_SMC_SCP_LOG_ENABLE	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0201)
+#define BAIKAL_SMC_EFUSE_GET_LOT	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0202)
+#define BAIKAL_SMC_EFUSE_GET_SERIAL	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0203)
+#define BAIKAL_SMC_EFUSE_GET_MAC	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0204)
+#define BAIKAL_SMC_EFUSE_GET_BINNING	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0205)
+#define BAIKAL_SMC_EFUSE_GET_PROCESS	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0206)
+#define BAIKAL_SMC_EFUSE_GET_PROJECT	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0207)
+#define BAIKAL_SMC_EFUSE_GET_REVISION	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0208)
+#define BAIKAL_SMC_VDEC_SMMU_SET_CACHE	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0300)
+#define BAIKAL_SMC_VDEC_SMMU_GET_CACHE	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0301)
+#define BAIKAL_SMC_CLK_ROUND		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0400)
+#define BAIKAL_SMC_CLK_SET		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0401)
+#define BAIKAL_SMC_CLK_GET		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0402)
+#define BAIKAL_SMC_CLK_ENABLE		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0403)
+#define BAIKAL_SMC_CLK_DISABLE		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0404)
+#define BAIKAL_SMC_CLK_IS_ENABLED	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0405)
+#define BAIKAL_SMC_GMAC_DIV2_ENABLE	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0500)
+#define BAIKAL_SMC_GMAC_DIV2_DISABLE	BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0501)
+#define BAIKAL_SMC_RST_SET		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0700)
+#define BAIKAL_SMC_RST_CLR		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0701)
+#define BAIKAL_SMC_PM			BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0800)
+#define BAIKAL_SMC_WDT			BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0900)
+#define BAIKAL_SMC_THROTTLE		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x1000)
+
+#endif
-- 
2.42.2



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

* [d-kernel] [PATCH 03/35] clk: baikal-m: old firmware: use "cmu-id" if there is no "reg" in devicetree
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 01/35] Baikal Electronics SoC family Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 02/35] clk: Add clock drivers for Baikal BE-M1000 Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 04/35] usb: add support for Baikal USB PHY Daniil Gnusarev
                   ` (31 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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>
Do-not-upstream: this is a feature of Baikal-M
---
 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 1b4a0c7ea5a5c0..c4f76e0f6b55dd 100644
--- a/drivers/clk/baikal/clk-bm1000.c
+++ b/drivers/clk/baikal/clk-bm1000.c
@@ -239,8 +239,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;
 
@@ -823,6 +827,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] 36+ messages in thread

* [d-kernel] [PATCH 04/35] usb: add support for Baikal USB PHY
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (2 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 03/35] clk: baikal-m: old firmware: use "cmu-id" if there is no "reg" in devicetree Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 05/35] usb: dwc3: of-simple: added compatible string for Baikal-M SoC Daniil Gnusarev
                   ` (30 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

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

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Co-developed-by: Baikal Electronics <info@baikalelectronics.ru>
Do-not-upstream: this is a feature of Baikal-M
---
 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 | 303 ++++++++++++++++++++++++++++
 5 files changed, 318 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 678dd0452f0aa0..cc552f59dac7a5 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -103,6 +103,7 @@ config PHY_NXP_PTN3222
 
 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 bfb27fb5a49428..980659c8c34532 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_PHY_AIROHA_PCIE)		+= phy-airoha-pcie.o
 obj-$(CONFIG_PHY_NXP_PTN3222)		+= phy-nxp-ptn3222.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 00000000000000..b8b598d6019e14
--- /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 00000000000000..54fcd11988681f
--- /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 00000000000000..8fe2b01a35ef4b
--- /dev/null
+++ b/drivers/phy/baikal/baikal-usb-phy.c
@@ -0,0 +1,303 @@
+// 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 = 0, 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, const 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 void phy_baikal_remove(struct platform_device *pdev)
+{
+	if (!acpi_disabled)
+		phy_baikal_acpi_remove(&pdev->dev);
+}
+
+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] 36+ messages in thread

* [d-kernel] [PATCH 05/35] usb: dwc3: of-simple: added compatible string for Baikal-M SoC
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (3 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 04/35] usb: add support for Baikal USB PHY Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 06/35] uart: add support for UART Baikal BE-M1000 Daniil Gnusarev
                   ` (29 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 c116143335d9f9..b9350431dacae0 100644
--- a/drivers/usb/dwc3/dwc3-of-simple.c
+++ b/drivers/usb/dwc3/dwc3-of-simple.c
@@ -175,6 +175,9 @@ static const struct of_device_id of_dwc3_simple_match[] = {
 	{ .compatible = "hisilicon,hi3670-dwc3" },
 	{ .compatible = "hisilicon,hi3798mv200-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] 36+ messages in thread

* [d-kernel] [PATCH 06/35] uart: add support for UART Baikal BE-M1000
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (4 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 05/35] usb: dwc3: of-simple: added compatible string for Baikal-M SoC Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 07/35] serial: 8250_dw: verify clock rate in dw8250_set_termios Daniil Gnusarev
                   ` (28 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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>
Do-not-upstream: this is a feature of Baikal-M
---
 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 710ae4d40aec44..c61903df1b8644 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -780,8 +780,14 @@ static const struct dw8250_platform_data dw8250_skip_set_rate_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] 36+ messages in thread

* [d-kernel] [PATCH 07/35] serial: 8250_dw: verify clock rate in dw8250_set_termios
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (5 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 06/35] uart: add support for UART Baikal BE-M1000 Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 08/35] cpufreq-dt: don't load on Baikal-M SoC Daniil Gnusarev
                   ` (27 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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.
Therefore 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 c61903df1b8644..1f4e6baf085029 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -383,14 +383,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-notifier worker will block in
 		 * serial8250_update_uartclk() until we are done.
-- 
2.42.2



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

* [d-kernel] [PATCH 08/35] cpufreq-dt: don't load on Baikal-M SoC
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (6 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 07/35] serial: 8250_dw: verify clock rate in dw8250_set_termios Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 09/35] net: stmmac: support of Baikal-BE1000 SoCs GMAC Daniil Gnusarev
                   ` (26 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 dc11b62399ad5b..2422d4fb3c0972 100644
--- a/drivers/cpufreq/cpufreq-dt-platdev.c
+++ b/drivers/cpufreq/cpufreq-dt-platdev.c
@@ -117,6 +117,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] 36+ messages in thread

* [d-kernel] [PATCH 09/35] net: stmmac: support of Baikal-BE1000 SoCs GMAC
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (7 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 08/35] cpufreq-dt: don't load on Baikal-M SoC Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 10/35] net: fwnode_get_phy_id: consider all compatible strings Daniil Gnusarev
                   ` (25 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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. Taken from SDK ARM64-2403-6.6. Updated for version 6.18.

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>
Do-not-upstream: this is a feature of Baikal-M
---
 drivers/net/ethernet/stmicro/stmmac/Kconfig   |   8 +
 drivers/net/ethernet/stmicro/stmmac/Makefile  |   1 +
 .../ethernet/stmicro/stmmac/dwmac-baikal.c    | 500 ++++++++++++++++++
 .../ethernet/stmicro/stmmac/dwmac1000_core.c  |   1 +
 net/ethernet/eth.c                            |   1 +
 5 files changed, 511 insertions(+)
 create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c

diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig
index 9507131875b2ca..3f1a3248fe15d6 100644
--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
@@ -67,6 +67,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 51e068e26ce499..4dada88828ef3a 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 00000000000000..788ad01e623615
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c
@@ -0,0 +1,500 @@
+// 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"
+
+#define MAC_GPIO	0x00e0		/* GPIO register */
+#define MAC_GPIO_GPO	BIT(8)		/* Output port */
+
+#define BAIKAL_SMC_GMAC_DIV2_ENABLE	0xC2000500
+#define BAIKAL_SMC_GMAC_DIV2_DISABLE	0xC2000501
+
+struct baikal_gmac {
+	struct device	*dev;
+	u64		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 struct mac_device_info *baikal_gmac_setup(void *ppriv)
+{
+	struct stmmac_dma_ops *dma;
+	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;
+
+	dma = devm_kzalloc(priv->device, sizeof(*dma), GFP_KERNEL);
+	if (!dma)
+		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)
+		devm_gpiod_put(priv->device, reset_gpio);
+
+	if (err) {
+		dev_err(priv->device, "SW reset is not cleared: error %d", err);
+		return NULL;
+	}
+
+	*dma = dwmac1000_dma_ops;
+	dma->reset = baikal_gmac_dma_reset;
+	mac->dma = dma;
+	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, 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 && 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;
+	u32 clock_rate;
+	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;
+
+	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", &clock_rate))
+		plat_dat->clk_ptp_rate = 50000000;
+	else
+		plat_dat->clk_ptp_rate = clock_rate;
+
+	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 = devm_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->core_type = DWMAC_CORE_GMAC;
+	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:
+	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	= 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 fe776ddf688952..a430f3e348ba33 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
@@ -539,6 +539,7 @@ int dwmac1000_setup(struct stmmac_priv *priv)
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(dwmac1000_setup);
 
 /* DWMAC 1000 HW Timestaming ops */
 
diff --git a/net/ethernet/eth.c b/net/ethernet/eth.c
index 43e211e611b169..890d7c99336813 100644
--- a/net/ethernet/eth.c
+++ b/net/ethernet/eth.c
@@ -558,6 +558,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] 36+ messages in thread

* [d-kernel] [PATCH 10/35] net: fwnode_get_phy_id: consider all compatible strings
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (8 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 09/35] net: stmmac: support of Baikal-BE1000 SoCs GMAC Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 11/35] drm/panfrost: forcibly set dma-coherent on Baikal-M Daniil Gnusarev
                   ` (24 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 7a67c900e79a51..8f7127e2b331b4 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1070,18 +1070,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] 36+ messages in thread

* [d-kernel] [PATCH 11/35] drm/panfrost: forcibly set dma-coherent on Baikal-M
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (9 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 10/35] net: fwnode_get_phy_id: consider all compatible strings Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 12/35] drm/panfrost: disable devfreq " Daniil Gnusarev
                   ` (23 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 1ea6c509a5d599..3b5687912050c5 100644
--- a/drivers/gpu/drm/panfrost/panfrost_drv.c
+++ b/drivers/gpu/drm/panfrost/panfrost_drv.c
@@ -740,6 +740,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] 36+ messages in thread

* [d-kernel] [PATCH 12/35] drm/panfrost: disable devfreq on Baikal-M
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (10 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 11/35] drm/panfrost: forcibly set dma-coherent on Baikal-M Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 13/35] ata: ahci: add support for Baikal BE-M1000 Daniil Gnusarev
                   ` (22 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 5d0dce10336ba3..c0542b53ca2bf1 100644
--- a/drivers/gpu/drm/panfrost/panfrost_devfreq.c
+++ b/drivers/gpu/drm/panfrost/panfrost_devfreq.c
@@ -133,6 +133,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] 36+ messages in thread

* [d-kernel] [PATCH 13/35] ata: ahci: add support for Baikal BE-M1000
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (11 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 12/35] drm/panfrost: disable devfreq " Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 14/35] drm: add Baikal-M SoC video display unit driver Daniil Gnusarev
                   ` (21 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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>
Do-not-upstream: this is a feature of Baikal-M
---
 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 aec6d793f51a9c..a8cc3427ba46e3 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] 36+ messages in thread

* [d-kernel] [PATCH 14/35] drm: add Baikal-M SoC video display unit driver
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (12 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 13/35] ata: ahci: add support for Baikal BE-M1000 Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 15/35] drm: baikal-vdu: driver compatibility with SDK earlier than 5.9 Daniil Gnusarev
                   ` (20 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

Add VDU driver for Baikal BE-M1000
Taken from SDK ARM64-2509-6.12. Updated for version 6.18

Co-developed-by: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
Bugfixes by Alexey Sheplyakov <asheplyakov@altlinux.org>
Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Do-not-upstream: this is a feature of Baikal-M
---
 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          | 122 +++++
 drivers/gpu/drm/baikal/baikal_vdu_backlight.c | 259 +++++++++
 drivers/gpu/drm/baikal/baikal_vdu_crtc.c      | 443 +++++++++++++++
 drivers/gpu/drm/baikal/baikal_vdu_debugfs.c   |  94 ++++
 drivers/gpu/drm/baikal/baikal_vdu_drm.h       | 114 ++++
 drivers/gpu/drm/baikal/baikal_vdu_drv.c       | 517 ++++++++++++++++++
 drivers/gpu/drm/baikal/baikal_vdu_panel.c     | 195 +++++++
 drivers/gpu/drm/baikal/baikal_vdu_plane.c     | 118 ++++
 drivers/gpu/drm/baikal/baikal_vdu_regs.h      | 136 +++++
 drivers/gpu/drm/bridge/synopsys/Kconfig       |   6 +
 drivers/gpu/drm/drm_panel.c                   |   3 +-
 include/drm/drm_panel.h                       |   6 +
 16 files changed, 2038 insertions(+), 1 deletion(-)
 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 ed85d0ceee3ba5..d375f33e329a89 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -269,6 +269,8 @@ source "drivers/gpu/drm/sysfb/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 742f0d590c5afa..e8722bba5a6bd3 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -232,6 +232,7 @@ obj-$(CONFIG_DRM_HYPERV) += hyperv/
 obj-y			+= sitronix/
 obj-y			+= solomon/
 obj-$(CONFIG_DRM_SPRD) += sprd/
+obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal/
 obj-$(CONFIG_DRM_LOONGSON) += loongson/
 obj-$(CONFIG_DRM_POWERVR) += imagination/
 
diff --git a/drivers/gpu/drm/baikal/Kconfig b/drivers/gpu/drm/baikal/Kconfig
new file mode 100644
index 00000000000000..4a18055cd22fae
--- /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 00000000000000..321d3be96b8147
--- /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 00000000000000..40030cb42833b4
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal-hdmi.c
@@ -0,0 +1,122 @@
+// 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 = of_drm_find_bridge(pdev->dev.of_node);
+	return 0;
+}
+
+static void baikal_dw_hdmi_remove(struct platform_device *pdev)
+{
+	struct baikal_hdmi_bridge *priv = platform_get_drvdata(pdev);
+
+	dw_hdmi_remove(priv->hdmi);
+}
+
+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 00000000000000..84136001f2b187
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_backlight.c
@@ -0,0 +1,259 @@
+// 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 (backlight_is_blank(bl_dev)) {
+		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 00000000000000..87684a3e35d4d7
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c
@@ -0,0 +1,443 @@
+// 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);
+}
+
+static 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 00000000000000..f3f4bfd1d4ac10
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c
@@ -0,0 +1,94 @@
+// 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"
+
+static 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 00000000000000..432270bebed69b
--- /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 00000000000000..e2a5e6ff03e37d
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
@@ -0,0 +1,517 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2025 Baikal Electronics, JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ * Bugfixes by Alexey Sheplyakov <asheplyakov@altlinux.org>
+ *
+ */
+
+#include <linux/firmware/baikal/baikal-smc.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 <linux/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 <drm/clients/drm_client_setup.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 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 = find_panel_by_fwnode(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 = aperture_remove_all_conflicting_devices(vdu_drm_driver.name);
+	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;
+
+	if (priv == NULL)
+		return -EINVAL;
+
+	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,
+	DRM_FBDEV_DMA_DRIVER_OPS,
+	.ioctls = NULL,
+	.fops = &baikal_drm_fops,
+	.name = DRIVER_NAME,
+	.desc = DRIVER_DESC,
+	.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_SCP_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_client_setup(drm, NULL);
+#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	= 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 00000000000000..e511bd453dcb1e
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_panel.c
@@ -0,0 +1,195 @@
+// 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,
+				     struct drm_encoder *encoder,
+				     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_drm_bridge_alloc(panel->dev, struct baikal_lvds_bridge, bridge,
+					     &baikal_lvds_bridge_funcs);
+	if (IS_ERR(panel_bridge))
+		return ERR_PTR(-ENOMEM);
+
+	panel_bridge->connector_type = connector_type;
+	panel_bridge->panel = panel;
+
+#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 00000000000000..daa8d152e0f487
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_plane.c
@@ -0,0 +1,118 @@
+// 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 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_RGB888:
+		cntl |= CR1_BPP24 | CR1_FBP;
+		break;
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_XRGB8888:
+		cntl |= CR1_BPP24;
+		break;
+	case DRM_FORMAT_RGB565:
+		cntl |= CR1_BPP16_565;
+		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_RGB888,
+		DRM_FORMAT_ARGB8888,
+		DRM_FORMAT_XRGB8888,
+		DRM_FORMAT_RGB565,
+		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 00000000000000..fa174397dc7dd9
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_regs.h
@@ -0,0 +1,136 @@
+/* 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_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/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig
index 2c5e532410de9e..8ffa13e4c7da06 100644
--- a/drivers/gpu/drm/bridge/synopsys/Kconfig
+++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
@@ -72,3 +72,9 @@ config DRM_DW_MIPI_DSI2
 	select DRM_KMS_HELPER
 	select DRM_MIPI_DSI
 	select DRM_PANEL_BRIDGE
+
+config DRM_BAIKAL_HDMI
+	tristate "Baikal-M HDMI transmitter"
+	select DRM_DW_HDMI
+	help
+	  Choose this if you want to use HDMI on Baikal-M.
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index d1e6598ea3bc02..73512e170f1c7f 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -513,7 +513,7 @@ EXPORT_SYMBOL(of_drm_get_panel_orientation);
 #endif
 
 /* Find panel by fwnode. This should be identical to of_drm_find_panel(). */
-static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode)
+struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode)
 {
 	struct drm_panel *panel;
 
@@ -533,6 +533,7 @@ static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode
 
 	return ERR_PTR(-EPROBE_DEFER);
 }
+EXPORT_SYMBOL(find_panel_by_fwnode);
 
 /* Find panel by follower device */
 static struct drm_panel *find_panel_by_dev(struct device *follower_dev)
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
index 2407bfa60236f8..4866dc3f0cd887 100644
--- a/include/drm/drm_panel.h
+++ b/include/drm/drm_panel.h
@@ -362,6 +362,7 @@ int drm_panel_add_follower(struct device *follower_dev,
 void drm_panel_remove_follower(struct drm_panel_follower *follower);
 int devm_drm_panel_add_follower(struct device *follower_dev,
 				struct drm_panel_follower *follower);
+struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode);
 #else
 static inline bool drm_is_panel_follower(struct device *dev)
 {
@@ -380,6 +381,11 @@ static inline int devm_drm_panel_add_follower(struct device *follower_dev,
 {
 	return -ENODEV;
 }
+
+static inline struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode)
+{
+	return -ENODEV;
+}
 #endif
 
 #if IS_ENABLED(CONFIG_DRM_PANEL) && (IS_BUILTIN(CONFIG_BACKLIGHT_CLASS_DEVICE) || \
-- 
2.42.2



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

* [d-kernel] [PATCH 15/35] drm: baikal-vdu: driver compatibility with SDK earlier than 5.9
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (13 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 14/35] drm: add Baikal-M SoC video display unit driver Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 16/35] drm: baikal-vdu: disable backlight driver loading Daniil Gnusarev
                   ` (19 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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>
Do-not-upstream: this is a feature of Baikal-M
---
 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 432270bebed69b..90dc57bac7ae2c 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 e2a5e6ff03e37d..db7ef4c2027c6c 100644
--- a/drivers/gpu/drm/baikal/baikal_vdu_drv.c
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
@@ -50,6 +50,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 = {
@@ -70,10 +73,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) {
@@ -221,12 +227,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);
@@ -261,9 +273,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;
@@ -362,6 +379,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,
@@ -371,25 +390,54 @@ 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);
+			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;
@@ -399,10 +447,12 @@ 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] 36+ messages in thread

* [d-kernel] [PATCH 16/35] drm: baikal-vdu: disable backlight driver loading
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (14 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 15/35] drm: baikal-vdu: driver compatibility with SDK earlier than 5.9 Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 17/35] sound: add support for Baikal BE-M1000 I2S Daniil Gnusarev
                   ` (18 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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>
Do-not-upstream: this is a feature of Baikal-M
---
 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 db7ef4c2027c6c..2494bc9d364c00 100644
--- a/drivers/gpu/drm/baikal/baikal_vdu_drv.c
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
@@ -459,9 +459,11 @@ 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] 36+ messages in thread

* [d-kernel] [PATCH 17/35] sound: add support for Baikal BE-M1000 I2S
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (15 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 16/35] drm: baikal-vdu: disable backlight driver loading Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 18/35] sound: dwc-i2s: request all IRQs specified in device tree Daniil Gnusarev
                   ` (17 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

Add driver for Baikal BE-M1000 ALSA SoC Synopsys I2S Audio Layer
Taken from SDK ARM64-2403-6.6, adapted to version 6.18 with changed API

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Co-developed-by: Baikal Electronics <info@baikalelectronics.ru>
Do-not-upstream: this is a feature of Baikal-M
---
 sound/soc/Kconfig                 |   1 +
 sound/soc/Makefile                |   1 +
 sound/soc/baikal/Kconfig          |  21 +
 sound/soc/baikal/Makefile         |   6 +
 sound/soc/baikal/baikal-i2s.c     | 803 ++++++++++++++++++++++++++++++
 sound/soc/baikal/baikal-pio-pcm.c | 264 ++++++++++
 sound/soc/baikal/local.h          | 137 +++++
 7 files changed, 1233 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 ce74818bd7152d..6fa4d146f6d6c8 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -107,6 +107,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 462322c38aa42d..b0207d8148221f 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -49,6 +49,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 00000000000000..550e49a49fadde
--- /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 00000000000000..6bfe128e9266d6
--- /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 00000000000000..db10cff1215952
--- /dev/null
+++ b/sound/soc/baikal/baikal-i2s.c
@@ -0,0 +1,803 @@
+// 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 = snd_soc_substream_to_rtd(substream);
+	struct snd_soc_dai *codec_dai = snd_soc_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 = snd_soc_substream_to_rtd(substream);
+	struct snd_soc_dai *codec_dai = snd_soc_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 void 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);
+}
+
+#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 00000000000000..c42d089df14dd0
--- /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 = snd_soc_substream_to_rtd(substream);
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(snd_soc_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 00000000000000..b1754e0f3bd8d8
--- /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] 36+ messages in thread

* [d-kernel] [PATCH 18/35] sound: dwc-i2s: request all IRQs specified in device tree
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (16 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 17/35] sound: add support for Baikal BE-M1000 I2S Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 19/35] sound: dwc-i2s: paper over RX overrun warnings on Baikal-M Daniil Gnusarev
                   ` (16 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 28001e9857d9dc..5d2e8c2f51c27f 100644
--- a/sound/soc/dwc/dwc-i2s.c
+++ b/sound/soc/dwc/dwc-i2s.c
@@ -919,7 +919,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;
 
@@ -957,10 +957,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] 36+ messages in thread

* [d-kernel] [PATCH 19/35] sound: dwc-i2s: paper over RX overrun warnings on Baikal-M
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (17 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 18/35] sound: dwc-i2s: request all IRQs specified in device tree Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 20/35] sound: baikal-i2s: " Daniil Gnusarev
                   ` (15 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 5d2e8c2f51c27f..6df7e54dec6567 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 dce88c9ad5f333..be7a026e5f9188 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] 36+ messages in thread

* [d-kernel] [PATCH 20/35] sound: baikal-i2s: paper over RX overrun warnings on Baikal-M
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (18 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 19/35] sound: dwc-i2s: paper over RX overrun warnings on Baikal-M Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 21/35] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a Daniil Gnusarev
                   ` (14 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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>
Do-not-upstream: this is a feature of Baikal-M
---
 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 db10cff1215952..622c0353028136 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 b1754e0f3bd8d8..d1cd07e540b457 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] 36+ messages in thread

* [d-kernel] [PATCH 21/35] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (19 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 20/35] sound: baikal-i2s: " Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 22/35] dt-bindings: dw-hdmi: added ahb-audio-regshift Daniil Gnusarev
                   ` (13 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 cf1f66b7b192c9..e0215e635473c3 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
@@ -133,12 +133,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);
 }
 
 /*
@@ -233,7 +266,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;
@@ -241,18 +273,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)
@@ -263,8 +295,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)
@@ -273,11 +305,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) {
@@ -320,7 +352,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;
 
@@ -350,16 +381,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);
@@ -367,9 +398,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;
 }
@@ -379,8 +410,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);
 
@@ -442,6 +473,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;
@@ -456,9 +492,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);
@@ -550,10 +586,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 f72d27208ebef5..3250588d39ff03 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 206b099a35e9a5..ade0218ce0a15a 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3522,6 +3522,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] 36+ messages in thread

* [d-kernel] [PATCH 22/35] dt-bindings: dw-hdmi: added ahb-audio-regshift
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (20 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 21/35] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 23/35] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M Daniil Gnusarev
                   ` (12 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 33481381cccc1c..22f17759227878 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] 36+ messages in thread

* [d-kernel] [PATCH 23/35] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (21 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 22/35] dt-bindings: dw-hdmi: added ahb-audio-regshift Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 24/35] dw-hdmi: add flag SNDRV_PCM_INFO_BATCH for audio via hdmi on Baikal-M Daniil Gnusarev
                   ` (11 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 ade0218ce0a15a..87d03939ed1c10 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3527,6 +3527,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] 36+ messages in thread

* [d-kernel] [PATCH 24/35] dw-hdmi: add flag SNDRV_PCM_INFO_BATCH for audio via hdmi on Baikal-M
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (22 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 23/35] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 25/35] bmc: add board management controller driver Daniil Gnusarev
                   ` (10 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

There is an unstable sound output via HDMI with the dw-hdmi-ahb-audiо
driver when working on Baikal-M. Additional setting of
the SNDRV_PCM_INFO_BATCH flag solves this problem. Let's use it.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Do-not-upstream: this is a feature of Baikal-M
---
 drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c | 2 ++
 drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h     | 1 +
 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c           | 4 +++-
 3 files changed, 6 insertions(+), 1 deletion(-)

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 e0215e635473c3..9afa3ee2b0ee8c 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
@@ -356,6 +356,8 @@ static int dw_hdmi_open(struct snd_pcm_substream *substream)
 	int ret;
 
 	runtime->hw = dw_hdmi_hw;
+	if (dw->data.batch_mode)
+		runtime->hw.info |= SNDRV_PCM_INFO_BATCH;
 
 	eld = dw->data.get_eld(dw->data.hdmi);
 	if (eld) {
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h
index 3250588d39ff03..aa9f470fcd03a6 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h
@@ -11,6 +11,7 @@ struct dw_hdmi_audio_data {
 	struct dw_hdmi *hdmi;
 	u8 *(*get_eld)(struct dw_hdmi *hdmi);
 	unsigned regshift;
+	bool batch_mode;
 };
 
 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 87d03939ed1c10..d1911b94650ece 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3531,7 +3531,9 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
 			audio.regshift = 2;
 			dev_info(dev, "setting audio.regshift=%d for BE-M1000 SoC\n",
 				 audio.regshift);
-		}
+			audio.batch_mode = true;
+		} else
+			audio.batch_mode = false;
 		hdmi->enable_audio = dw_hdmi_ahb_audio_enable;
 		hdmi->disable_audio = dw_hdmi_ahb_audio_disable;
 
-- 
2.42.2



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

* [d-kernel] [PATCH 25/35] bmc: add board management controller driver
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (23 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 24/35] dw-hdmi: add flag SNDRV_PCM_INFO_BATCH for audio via hdmi on Baikal-M Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 26/35] pm: disable all sleep states on Baikal-M based boards Daniil Gnusarev
                   ` (9 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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>
Do-not-upstream: this is a feature of Baikal-M
---
 drivers/misc/Kconfig  |  16 +
 drivers/misc/Makefile |   1 +
 drivers/misc/tp_bmc.c | 767 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 784 insertions(+)
 create mode 100644 drivers/misc/tp_bmc.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b9c11f67315f0b..dbbcaddf3acf55 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -551,6 +551,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 b32a2597d2467b..8255a0283d920f 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 00000000000000..19dcdcd11902af
--- /dev/null
+++ b/drivers/misc/tp_bmc.c
@@ -0,0 +1,767 @@
+// 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;
+}
+
+static 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_LEVEL)
+		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
+
+static 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);
+}
+
+static 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);
+	}
+
+	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");
-- 
2.42.2



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

* [d-kernel] [PATCH 26/35] pm: disable all sleep states on Baikal-M based boards
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (24 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 25/35] bmc: add board management controller driver Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 27/35] sound: hda: add driver for HDA controller on Baikal-M Daniil Gnusarev
                   ` (8 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 3d4ebedad69f6a..f2875a1a5ce469 100644
--- a/kernel/power/suspend.c
+++ b/kernel/power/suspend.c
@@ -31,6 +31,7 @@
 #include <linux/compiler.h>
 #include <linux/moduleparam.h>
 #include <linux/fs.h>
+#include <linux/of.h>
 
 #include "power.h"
 
@@ -257,6 +258,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] 36+ messages in thread

* [d-kernel] [PATCH 27/35] sound: hda: add driver for HDA controller on Baikal-M
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (25 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 26/35] pm: disable all sleep states on Baikal-M based boards Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 28/35] sound: hda: enable jack detection in polling mode " Daniil Gnusarev
                   ` (7 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

This is the HDA controller driver for the BE-M1000
from SDK-ARM64-2509-6.12. The codec addressing feature
has been slightly reworked.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Do-not-upstream: this is a feature of Baikal-M
---
 include/sound/hdaudio.h            |   1 +
 sound/hda/controllers/Kconfig      |  12 +
 sound/hda/controllers/Makefile     |   2 +
 sound/hda/controllers/hda_baikal.c | 627 +++++++++++++++++++++++++++++
 sound/hda/core/controller.c        |   7 +
 5 files changed, 649 insertions(+)
 create mode 100644 sound/hda/controllers/hda_baikal.c

diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h
index 4e0c1d8af09f76..66a79a10caf76b 100644
--- a/include/sound/hdaudio.h
+++ b/include/sound/hdaudio.h
@@ -352,6 +352,7 @@ struct hdac_bus {
 	bool not_use_interrupts:1;	/* prohibiting the RIRB IRQ */
 	bool access_sdnctl_in_dword:1;	/* accessing the sdnctl register by dword */
 	bool use_pio_for_commands:1;	/* Use PIO instead of CORB for commands */
+	bool baikal_cad_quirk:1;	/* The codec addressing quirk in the BE-M1000 */
 
 	int poll_count;
 
diff --git a/sound/hda/controllers/Kconfig b/sound/hda/controllers/Kconfig
index 34721f50b055af..c463b09f6eb986 100644
--- a/sound/hda/controllers/Kconfig
+++ b/sound/hda/controllers/Kconfig
@@ -30,6 +30,18 @@ config SND_HDA_TEGRA
 	  To compile this driver as a module, choose M here: the module
 	  will be called snd-hda-tegra.
 
+config SND_HDA_BAIKAL
+	tristate "Baikal HD Audio"
+	depends on ARCH_BAIKAL
+	select SND_HDA
+	select SND_HDA_ALIGNED_MMIO
+	help
+	  Say Y here to support the HDA controller present in Baikal
+	  SoCs
+
+	  This options enables support for the HD Audio controller
+	  present in Baikal SoCs.
+
 config SND_HDA_ACPI
 	tristate "HD Audio ACPI"
 	depends on ACPI
diff --git a/sound/hda/controllers/Makefile b/sound/hda/controllers/Makefile
index a4bcd055e9aef9..c2126546a2b653 100644
--- a/sound/hda/controllers/Makefile
+++ b/sound/hda/controllers/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 snd-hda-intel-y := intel.o
 snd-hda-tegra-y := tegra.o
+snd-hda-baikal-y := hda_baikal.o
 snd-hda-acpi-y := acpi.o
 
 subdir-ccflags-y += -I$(src)/../common
@@ -10,4 +11,5 @@ CFLAGS_intel.o := -I$(src)
 
 obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o
 obj-$(CONFIG_SND_HDA_TEGRA) += snd-hda-tegra.o
+obj-$(CONFIG_SND_HDA_BAIKAL) += snd-hda-baikal.o
 obj-$(CONFIG_SND_HDA_ACPI) += snd-hda-acpi.o
diff --git a/sound/hda/controllers/hda_baikal.c b/sound/hda/controllers/hda_baikal.c
new file mode 100644
index 00000000000000..faf029d60662c9
--- /dev/null
+++ b/sound/hda/controllers/hda_baikal.c
@@ -0,0 +1,627 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of primary ALSA driver code base for Baikal-M HDA controller.
+ *
+ * Copyright (C) 2021-2023 Baikal Electronics, JSC
+ * Author: Baikal Electronics <support@baikalelectronics.ru>
+ *
+ * Based on:
+ *	 hda_intel.c, Copyright(c) 2004 Intel Corporation. All rights reserved.
+ *	 hda_tegra.c
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clocksource.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/string.h>
+#include <linux/pm_runtime.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/hda_codec.h>
+
+#include "hda_controller.h"
+
+#ifdef CONFIG_PM
+static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT;
+module_param(power_save, bint, 0644);
+MODULE_PARM_DESC(power_save,
+		 "Automatic power-saving timeout (in seconds, 0 = disable).");
+#else
+#define power_save	0
+#endif
+
+/* max number of SDs */
+#define NUM_CAPTURE_SD	4
+#define NUM_PLAYBACK_SD	4
+
+struct hda_baikal {
+	struct azx chip;
+	struct device *dev;
+	void __iomem *regs;
+	struct work_struct probe_work;
+	struct work_struct irq_pending_work;
+	unsigned int irq_pending_warned:1;
+};
+
+static void hda_baikal_probe_work(struct work_struct *work);
+
+static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev);
+static const struct hda_controller_ops hda_baikal_ops;
+
+/* calculate runtime delay from LPIB */
+static int azx_get_delay_from_lpib(struct azx *chip, struct azx_dev *azx_dev,
+				   unsigned int pos)
+{
+	struct snd_pcm_substream *substream = azx_dev->core.substream;
+	int stream = substream->stream;
+	unsigned int lpib_pos = azx_get_pos_lpib(chip, azx_dev);
+	int delay;
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+		delay = pos - lpib_pos;
+	else
+		delay = lpib_pos - pos;
+	if (delay < 0) {
+		if (delay >= azx_dev->core.delay_negative_threshold)
+			delay = 0;
+		else
+			delay += azx_dev->core.bufsize;
+	}
+
+	if (delay >= azx_dev->core.period_bytes) {
+		dev_info(chip->card->dev,
+			 "Unstable LPIB (%d >= %d); disabling LPIB delay counting\n",
+			 delay, azx_dev->core.period_bytes);
+		delay = 0;
+		chip->driver_caps &= ~AZX_DCAPS_COUNT_LPIB_DELAY;
+		chip->get_delay[stream] = NULL;
+	}
+
+	return bytes_to_frames(substream->runtime, delay);
+}
+
+/* called from IRQ */
+static int azx_position_check(struct azx *chip, struct azx_dev *azx_dev)
+{
+	struct hda_baikal *hda = container_of(chip, struct hda_baikal, chip);
+	int ok;
+
+	ok = azx_position_ok(chip, azx_dev);
+
+	if (ok == 1) {
+		azx_dev->irq_pending = 0;
+		return ok;
+	} else if (ok == 0) {
+		/* bogus IRQ, process it later */
+		azx_dev->irq_pending = 1;
+		schedule_work(&hda->irq_pending_work);
+	}
+	return 0;
+}
+
+/*
+ * Check whether the current DMA position is acceptable for updating
+ * periods.  Returns non-zero if it's OK.
+ *
+ * Many HD-audio controllers appear pretty inaccurate about
+ * the update-IRQ timing.  The IRQ is issued before actually the
+ * data is processed.  So, we need to process it afterwords in a
+ * workqueue.
+ */
+static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev)
+{
+	struct snd_pcm_substream *substream = azx_dev->core.substream;
+	int stream = substream->stream;
+	u32 wallclk;
+	unsigned int pos;
+
+	wallclk = azx_readl(chip, WALLCLK) - azx_dev->core.start_wallclk;
+	if (wallclk < (azx_dev->core.period_wallclk * 2) / 3)
+		return -1;	/* bogus (too early) interrupt */
+
+	if (chip->get_position[stream])
+		pos = chip->get_position[stream](chip, azx_dev);
+	else { /* use the position buffer as default */
+		pos = azx_get_pos_posbuf(chip, azx_dev);
+		if (!pos || pos == (u32)-1) {
+			dev_info(chip->card->dev,
+				 "Invalid position buffer, using LPIB read method instead.\n");
+			chip->get_position[stream] = azx_get_pos_lpib;
+			if (chip->get_position[0] == azx_get_pos_lpib &&
+			    chip->get_position[1] == azx_get_pos_lpib)
+				azx_bus(chip)->use_posbuf = false;
+			pos = azx_get_pos_lpib(chip, azx_dev);
+			chip->get_delay[stream] = NULL;
+		} else {
+			chip->get_position[stream] = azx_get_pos_posbuf;
+			if (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)
+				chip->get_delay[stream] = azx_get_delay_from_lpib;
+		}
+	}
+
+	if (pos >= azx_dev->core.bufsize)
+		pos = 0;
+
+	if (WARN_ONCE(!azx_dev->core.period_bytes,
+		      "hda-baikal: zero azx_dev->period_bytes"))
+		return -1; /* this shouldn't happen! */
+	if (wallclk < (azx_dev->core.period_wallclk * 5) / 4 &&
+	    pos % azx_dev->core.period_bytes > azx_dev->core.period_bytes / 2)
+		/* NG - it's below the first next period boundary */
+		return chip->bdl_pos_adj ? 0 : -1;
+	azx_dev->core.start_wallclk += wallclk;
+	return 1; /* OK, it's fine */
+}
+
+/* The work for pending PCM period updates */
+static void azx_irq_pending_work(struct work_struct *work)
+{
+	struct hda_baikal *hda = container_of(work, struct hda_baikal, irq_pending_work);
+	struct azx *chip = &hda->chip;
+	struct hdac_bus *bus = azx_bus(chip);
+	struct hdac_stream *s;
+	int pending, ok;
+
+	if (!hda->irq_pending_warned) {
+		dev_info(chip->card->dev,
+			 "IRQ timing workaround is activated for card #%d. Suggest a bigger bdl_pos_adj.\n",
+			 chip->card->number);
+		hda->irq_pending_warned = 1;
+	}
+
+	for (;;) {
+		pending = 0;
+		spin_lock_irq(&bus->reg_lock);
+		list_for_each_entry(s, &bus->stream_list, list) {
+			struct azx_dev *azx_dev = stream_to_azx_dev(s);
+
+			if (!azx_dev->irq_pending ||
+			    !s->substream ||
+			    !s->running)
+				continue;
+			ok = azx_position_ok(chip, azx_dev);
+			if (ok > 0) {
+				azx_dev->irq_pending = 0;
+				spin_unlock(&bus->reg_lock);
+				snd_pcm_period_elapsed(s->substream);
+				spin_lock(&bus->reg_lock);
+			} else if (ok < 0) {
+				pending = 0; /* too early */
+			} else
+				pending++;
+		}
+		spin_unlock_irq(&bus->reg_lock);
+		if (!pending)
+			return;
+		msleep(1);
+	}
+}
+
+/* clear irq_pending flags and assure no on-going workq */
+static void azx_clear_irq_pending(struct azx *chip)
+{
+	struct hdac_bus *bus = azx_bus(chip);
+	struct hdac_stream *s;
+
+	spin_lock_irq(&bus->reg_lock);
+	list_for_each_entry(s, &bus->stream_list, list) {
+		struct azx_dev *azx_dev = stream_to_azx_dev(s);
+
+		azx_dev->irq_pending = 0;
+	}
+	spin_unlock_irq(&bus->reg_lock);
+}
+
+static int hda_baikal_dev_disconnect(struct snd_device *device)
+{
+	struct azx *chip = device->device_data;
+
+	chip->bus.shutdown = 1;
+	return 0;
+}
+
+static int hda_baikal_dev_free(struct snd_device *device)
+{
+	struct azx *chip = device->device_data;
+	struct hda_baikal *hda = container_of(chip, struct hda_baikal, chip);
+
+	cancel_work_sync(&hda->probe_work);
+	if (azx_bus(chip)->chip_init) {
+		azx_clear_irq_pending(chip);
+		azx_stop_all_streams(chip);
+		azx_stop_chip(chip);
+	}
+
+	azx_free_stream_pages(chip);
+	azx_free_streams(chip);
+	snd_hdac_bus_exit(azx_bus(chip));
+
+	return 0;
+}
+
+static int hda_baikal_init_chip(struct azx *chip, struct platform_device *pdev)
+{
+	struct hda_baikal *hda = container_of(chip, struct hda_baikal, chip);
+	struct hdac_bus *bus = azx_bus(chip);
+	struct device *dev = hda->dev;
+	struct resource *res;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	hda->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(hda->regs))
+		return PTR_ERR(hda->regs);
+
+	bus->remap_addr = hda->regs;
+	bus->addr = res->start;
+
+	return 0;
+}
+
+static int hda_baikal_first_init(struct azx *chip, struct platform_device *pdev)
+{
+	struct hdac_bus *bus = azx_bus(chip);
+	struct snd_card *card = chip->card;
+	int err;
+	unsigned short gcap;
+	int irq_id = platform_get_irq(pdev, 0);
+	const char *sname, *drv_name = "baikal-hda";
+
+	err = hda_baikal_init_chip(chip, pdev);
+	if (err)
+		return err;
+
+	err = devm_request_irq(chip->card->dev, irq_id, azx_interrupt,
+			     IRQF_SHARED, KBUILD_MODNAME, chip);
+	if (err) {
+		dev_err(chip->card->dev,
+			"unable to request IRQ %d, disabling device\n",
+			irq_id);
+		return err;
+	}
+	bus->irq = irq_id;
+
+	synchronize_irq(bus->irq);
+
+	gcap = azx_readw(chip, GCAP);
+	dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap);
+
+	/*
+	 * Read number of streams from GCAP register instead of using
+	 * hardcoded value
+	 */
+	chip->capture_streams = (gcap >> 8) & 0x0f;
+	chip->playback_streams = (gcap >> 12) & 0x0f;
+	if (!chip->playback_streams && !chip->capture_streams) {
+		/* gcap didn't give any info, switching to old method */
+		chip->playback_streams = NUM_PLAYBACK_SD;
+		chip->capture_streams = NUM_CAPTURE_SD;
+	}
+	chip->capture_index_offset = 0;
+	chip->playback_index_offset = chip->capture_streams;
+	chip->num_streams = chip->playback_streams + chip->capture_streams;
+
+	/* initialize streams */
+	err = azx_init_streams(chip);
+	if (err < 0) {
+		dev_err(card->dev, "failed to initialize streams: %d\n", err);
+		return err;
+	}
+
+	err = azx_alloc_stream_pages(chip);
+	if (err < 0) {
+		dev_err(card->dev, "failed to allocate stream pages: %d\n",
+			err);
+		return err;
+	}
+
+	/* initialize chip */
+	azx_init_chip(chip, 1);
+
+	/* codec detection */
+	if (!bus->codec_mask) {
+		dev_err(card->dev, "no codecs found!\n");
+		return -ENODEV;
+	}
+
+	/* driver name */
+	strncpy(card->driver, drv_name, sizeof(card->driver));
+	/* shortname for card */
+	if (device_property_read_string(&pdev->dev, "baikal,model", &sname))
+		sname = drv_name;
+	if (strlen(sname) > sizeof(card->shortname))
+		dev_info(card->dev, "truncating shortname for card\n");
+	strncpy(card->shortname, sname, sizeof(card->shortname));
+
+	/* longname for card */
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s at 0x%lx irq %i",
+		 card->shortname, bus->addr, bus->irq);
+
+	return 0;
+}
+
+static int hda_baikal_create(struct snd_card *card,
+			    unsigned int driver_caps,
+			    struct hda_baikal *hda)
+{
+	static struct snd_device_ops ops = {
+		.dev_disconnect = hda_baikal_dev_disconnect,
+		.dev_free = hda_baikal_dev_free,
+	};
+	struct azx *chip;
+	int err;
+
+	chip = &hda->chip;
+
+	mutex_init(&chip->open_mutex);
+	chip->card = card;
+	chip->ops = &hda_baikal_ops;
+	chip->driver_caps = driver_caps;
+	chip->driver_type = driver_caps & 0xff;
+	chip->dev_index = 0;
+	INIT_LIST_HEAD(&chip->pcm_list);
+	INIT_WORK(&hda->irq_pending_work, azx_irq_pending_work);
+
+	chip->codec_probe_mask = 0x3; /* two codecs */
+
+	chip->single_cmd = false;
+	chip->snoop = true;
+
+	chip->get_position[0] = chip->get_position[1] = azx_get_pos_lpib;
+	chip->get_delay[0] = chip->get_delay[1] = azx_get_delay_from_lpib;
+
+	INIT_WORK(&hda->probe_work, hda_baikal_probe_work);
+
+	err = azx_bus_init(chip, NULL);
+	if (err < 0)
+		return err;
+
+	//chip->bus.needs_damn_long_delay = 1;
+	chip->bus.core.aligned_mmio = 1;
+
+	/* force polling mode, because RIRB interrupts don't working */
+	if (device_property_read_bool(hda->dev, "force-polling-mode"))
+		chip->bus.core.polling_mode = 1;
+	else
+		chip->bus.core.polling_mode = 0;
+
+	/* don't use response irq */
+	if (device_property_read_bool(hda->dev, "broken-response-irq"))
+		chip->bus.core.not_use_interrupts = 1;
+	else
+		chip->bus.core.not_use_interrupts = 0;
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0) {
+		dev_err(card->dev, "Error creating device\n");
+		return err;
+	}
+
+	return 0;
+}
+
+static int hda_baikal_probe(struct platform_device *pdev)
+{
+	const unsigned int driver_flags = AZX_DCAPS_PM_RUNTIME |
+					  AZX_DCAPS_NO_64BIT |
+					  AZX_DCAPS_4K_BDLE_BOUNDARY |
+					  AZX_DCAPS_COUNT_LPIB_DELAY;
+	struct snd_card *card;
+	struct azx *chip;
+	struct hda_baikal *hda;
+	int err;
+
+	hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL);
+	if (!hda)
+		return -ENOMEM;
+	hda->dev = &pdev->dev;
+	chip = &hda->chip;
+
+	err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+			   THIS_MODULE, 0, &card);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Error creating card!\n");
+		return err;
+	}
+
+	err = hda_baikal_create(card, driver_flags, hda);
+	if (err < 0)
+		goto out_free;
+	card->private_data = chip;
+
+	dev_set_drvdata(&pdev->dev, card);
+
+	pm_runtime_enable(hda->dev);
+	if (!azx_has_pm_runtime(chip))
+		pm_runtime_forbid(hda->dev);
+
+	schedule_work(&hda->probe_work);
+
+	return 0;
+
+out_free:
+	snd_card_free(card);
+	return err;
+}
+
+/* Probe the given codec address */
+static int baikal_probe_codec(struct azx *chip, int addr)
+{
+	unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) |
+		(AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID;
+	struct hdac_bus *bus = azx_bus(chip);
+	int err;
+	unsigned int res = -1;
+
+	mutex_lock(&bus->cmd_mutex);
+	chip->probing = 1;
+	bus->ops->command(bus, cmd);
+	err = bus->ops->get_response(bus, addr, &res);
+	chip->probing = 0;
+	mutex_unlock(&bus->cmd_mutex);
+	if (err < 0 || res == -1)
+		return -EIO;
+	dev_dbg(chip->card->dev, "codec #%d probed OK\n", addr);
+	return 0;
+}
+
+/* Probe codecs */
+static int azx_baikal_probe_codecs(struct azx *chip, unsigned int max_slots)
+{
+	struct hdac_bus *bus = azx_bus(chip);
+	int c, codecs, err;
+
+	int probe_retry;
+
+	codecs = 0;
+	if (!max_slots)
+		max_slots = AZX_DEFAULT_CODECS;
+
+	for (c = 0; c < max_slots; c++) {
+		if ((bus->codec_mask & (1 << c)) & chip->codec_probe_mask) {
+			for (probe_retry = 0; probe_retry < 100; probe_retry++) {
+				if (baikal_probe_codec(chip, c) < 0) {
+					azx_stop_chip(chip);
+					azx_init_chip(chip, true);
+					continue;
+				} else {
+					dev_warn(chip->card->dev,
+						"Codec #%d probe success; retry count = %d\n",
+						c, probe_retry);
+					break;
+				}
+				bus->codec_mask &= ~(1 << c);
+				dev_warn(chip->card->dev,
+					"Codec #%d probe error; disabling it...\n", c);
+			}
+		}
+	}
+
+	/* Then create codec instances */
+	for (c = 0; c < max_slots; c++) {
+		if ((bus->codec_mask & (1 << c)) & chip->codec_probe_mask) {
+			struct hda_codec *codec;
+
+			err = snd_hda_codec_new(&chip->bus, chip->card, c, &codec);
+			if (err < 0)
+				continue;
+			codec->jackpoll_interval = chip->jackpoll_interval;
+			codec->beep_mode = chip->beep_mode;
+			codecs++;
+		}
+	}
+	if (!codecs) {
+		dev_err(chip->card->dev, "no codecs initialized\n");
+		return -ENXIO;
+	}
+	return 0;
+}
+
+static void hda_baikal_probe_work(struct work_struct *work)
+{
+	struct hda_baikal *hda = container_of(work, struct hda_baikal,
+					probe_work);
+	struct azx *chip = &hda->chip;
+	struct hdac_bus *bus = azx_bus(chip);
+	struct platform_device *pdev = to_platform_device(hda->dev);
+	int max_slots;
+	int err;
+
+	pm_runtime_get_sync(hda->dev);
+	err = hda_baikal_first_init(chip, pdev);
+	if (err < 0)
+		goto out_free;
+
+	switch (bus->codec_mask) {
+	case 0x1:
+		max_slots = 1;
+		break;
+	case 0x2:
+		max_slots = 2;
+		break;
+	case 0x3:
+		max_slots = 2;
+		break;
+	}
+
+	/* create codec instances */
+	if (device_property_read_bool(bus->dev, "cyclic-codec-probe"))
+		err = azx_baikal_probe_codecs(chip, max_slots);
+	else
+		err = azx_probe_codecs(chip, max_slots);
+
+	if (err < 0)
+		goto out_free;
+
+	err = azx_codec_configure(chip);
+	if (err < 0)
+		goto out_free;
+
+	err = snd_card_register(chip->card);
+	if (err < 0)
+		goto out_free;
+
+	chip->running = 1;
+
+	snd_hda_set_power_save(&chip->bus, power_save * 1000);
+
+ out_free:
+	pm_runtime_put(hda->dev);
+	return; /* no error return from async probe */
+}
+
+static void hda_baikal_remove(struct platform_device *pdev)
+{
+	snd_card_free(dev_get_drvdata(&pdev->dev));
+	pm_runtime_disable(&pdev->dev);
+}
+
+static void hda_baikal_shutdown(struct platform_device *pdev)
+{
+	struct snd_card *card = dev_get_drvdata(&pdev->dev);
+	struct azx *chip;
+
+	if (!card)
+		return;
+	chip = card->private_data;
+	if (chip && chip->running)
+		azx_stop_chip(chip);
+}
+
+static const struct hda_controller_ops hda_baikal_ops = {
+	.position_check = azx_position_check,
+};
+
+static const struct of_device_id hda_baikal_match[] = {
+	{ .compatible = "baikal,bm1000-hda" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, hda_baikal_match);
+
+static struct platform_driver baikal_platform_hda = {
+	.driver = {
+		.name = "baikal-hda",
+		.of_match_table = hda_baikal_match,
+	},
+	.probe = hda_baikal_probe,
+	.remove = hda_baikal_remove,
+	.shutdown = hda_baikal_shutdown,
+};
+module_platform_driver(baikal_platform_hda);
+
+MODULE_DESCRIPTION("Baikal HDA bus driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c
index a7c00ad801170c..c3b7e8ab713043 100644
--- a/sound/hda/core/controller.c
+++ b/sound/hda/core/controller.c
@@ -3,6 +3,7 @@
  * HD-audio controller helpers
  */
 
+#include <linux/property.h>
 #include <linux/kernel.h>
 #include <linux/delay.h>
 #include <linux/export.h>
@@ -44,6 +45,9 @@ void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus)
 {
 	WARN_ON_ONCE(!bus->rb.area);
 
+	bus->baikal_cad_quirk = device_is_compatible(bus->dev, "baikal,bm1000-hda") &&
+			device_property_read_bool(bus->dev, "increment-codec-address");
+
 	guard(spinlock_irq)(&bus->reg_lock);
 	/* CORB set up */
 	bus->corb.addr = bus->rb.addr;
@@ -223,6 +227,9 @@ static int snd_hdac_bus_send_cmd_corb(struct hdac_bus *bus, unsigned int val)
 
 	guard(spinlock_irq)(&bus->reg_lock);
 
+	if (bus->baikal_cad_quirk)
+		val = val + 0x10000000;
+
 	bus->last_cmd[azx_command_addr(val)] = val;
 
 	/* add command to corb */
-- 
2.42.2



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

* [d-kernel] [PATCH 28/35] sound: hda: enable jack detection in polling mode on Baikal-M
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (26 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 27/35] sound: hda: add driver for HDA controller on Baikal-M Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 29/35] input: new driver - serdev-serio Daniil Gnusarev
                   ` (6 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

Since interrupts are not used, manual polling of codecs
is required to detect new connections.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Do-not-upstream: this is a feature of Baikal-M
---
 sound/hda/controllers/hda_baikal.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sound/hda/controllers/hda_baikal.c b/sound/hda/controllers/hda_baikal.c
index faf029d60662c9..86f6fa69c568e8 100644
--- a/sound/hda/controllers/hda_baikal.c
+++ b/sound/hda/controllers/hda_baikal.c
@@ -377,6 +377,7 @@ static int hda_baikal_create(struct snd_card *card,
 	INIT_WORK(&hda->irq_pending_work, azx_irq_pending_work);
 
 	chip->codec_probe_mask = 0x3; /* two codecs */
+	chip->jackpoll_interval = msecs_to_jiffies(1000); /* 1000ms */
 
 	chip->single_cmd = false;
 	chip->snoop = true;
-- 
2.42.2



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

* [d-kernel] [PATCH 29/35] input: new driver - serdev-serio
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (27 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 28/35] sound: hda: enable jack detection in polling mode " Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 30/35] input: serio: add an alias to the sersev-serio driver Daniil Gnusarev
                   ` (5 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 | 122 +++++++++++++++++++++++++++++
 3 files changed, 134 insertions(+)
 create mode 100644 drivers/input/serio/serdev-serio.c

diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index c7ef347a4dffc7..f07f7c310d1af3 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 6d97bad7b844ec..48c5114cfdabd2 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 00000000000000..5a0f666ef948da
--- /dev/null
+++ b/drivers/input/serio/serdev-serio.c
@@ -0,0 +1,122 @@
+// 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 size_t ss_receive_buf(struct serdev_device *serdev,
+			  const unsigned char *buf, size_t count)
+{
+	struct serdev_serio *ss = serdev_device_get_drvdata(serdev);
+	size_t 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;
+	strscpy(serio->name, "Serdev Serio", sizeof(serio->name));
+	strscpy(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] 36+ messages in thread

* [d-kernel] [PATCH 30/35] input: serio: add an alias to the sersev-serio driver
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (28 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 29/35] input: new driver - serdev-serio Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 31/35] input: added TF307 serio PS/2 emulator driver Daniil Gnusarev
                   ` (4 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

This will allow the driver to run as a module.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Do-not-upstream: this is a feature of Baikal-M
---
 drivers/input/serio/serdev-serio.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/input/serio/serdev-serio.c b/drivers/input/serio/serdev-serio.c
index 5a0f666ef948da..2e0b4e0097b5b3 100644
--- a/drivers/input/serio/serdev-serio.c
+++ b/drivers/input/serio/serdev-serio.c
@@ -105,6 +105,7 @@ static const struct of_device_id ss_of_match[] = {
 	{ .compatible = "serdev,serio" },
 	{},
 };
+MODULE_DEVICE_TABLE(of, ss_of_match);
 
 static struct serdev_device_driver serdev_serio_drv = {
 	.driver		= {
-- 
2.42.2



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

* [d-kernel] [PATCH 31/35] input: added TF307 serio PS/2 emulator driver
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (29 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 30/35] input: serio: add an alias to the sersev-serio driver Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 32/35] hwmon: add Baikal-M monitoring driver Daniil Gnusarev
                   ` (3 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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 | 746 +++++++++++++++++++++++++++++++++
 3 files changed, 757 insertions(+)
 create mode 100644 drivers/input/serio/tp_serio.c

diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index f07f7c310d1af3..e9f64e1af13eb7 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 48c5114cfdabd2..8f1be3803fd3ee 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 00000000000000..8af397a78bda6b
--- /dev/null
+++ b/drivers/input/serio/tp_serio.c
@@ -0,0 +1,746 @@
+// 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;
+	strscpy(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)
+{
+	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] 36+ messages in thread

* [d-kernel] [PATCH 32/35] hwmon: add Baikal-M monitoring driver
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (30 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 31/35] input: added TF307 serio PS/2 emulator driver Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 33/35] hwmon: baikal-pvt: support work on machines with old firmware Daniil Gnusarev
                   ` (2 subsequent siblings)
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

Baikal BE-M1000 SoC Process, Voltage, Temperature sensor driver
Taken from SDK ARM64-2509-6.12.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Do-not-upstream: this is a feature of Baikal-M
---
 drivers/hwmon/Kconfig            |   11 +
 drivers/hwmon/Makefile           |    2 +
 drivers/hwmon/baikal-pvt-core.c  | 1160 ++++++++++++++++++++++++++++++
 drivers/hwmon/baikal-pvt.h       |  273 +++++++
 drivers/hwmon/bm1000-pvt-hwmon.c |  201 ++++++
 5 files changed, 1647 insertions(+)
 create mode 100644 drivers/hwmon/baikal-pvt-core.c
 create mode 100644 drivers/hwmon/baikal-pvt.h
 create mode 100644 drivers/hwmon/bm1000-pvt-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2760feb9f83b5d..823c2c1d4de098 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -457,6 +457,17 @@ config SENSORS_ATXP1
 	  This driver can also be built as a module. If so, the module
 	  will be called atxp1.
 
+config SENSORS_BM1000_PVT
+	tristate "Baikal BE-M1000 SoC Process, Voltage, Temperature sensor driver"
+	depends on ARCH_BAIKAL || COMPILE_TEST
+	select POLYNOMIAL
+	help
+	  If you say yes here you get support for Baikal BE-M1000 SoC embedded
+	  PVT sensors.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called bm1000-pvt.
+
 config SENSORS_BT1_PVT
 	tristate "Baikal-T1 Process, Voltage, Temperature sensor driver"
 	depends on MIPS_BAIKAL_T1 || COMPILE_TEST
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 73b2abdcc6dd9c..098086d3034d29 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -58,6 +58,8 @@ obj-$(CONFIG_SENSORS_ASPEED_G6) += aspeed-g6-pwm-tach.o
 obj-$(CONFIG_SENSORS_ASUS_ROG_RYUJIN)	+= asus_rog_ryujin.o
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
 obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
+bm1000-pvt-objs := baikal-pvt-core.o bm1000-pvt-hwmon.o
+obj-$(CONFIG_SENSORS_BM1000_PVT) += bm1000-pvt.o
 obj-$(CONFIG_SENSORS_BT1_PVT)	+= bt1-pvt.o
 obj-$(CONFIG_SENSORS_CGBC)	+= cgbc-hwmon.o
 obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o
diff --git a/drivers/hwmon/baikal-pvt-core.c b/drivers/hwmon/baikal-pvt-core.c
new file mode 100644
index 00000000000000..7a649b65768f72
--- /dev/null
+++ b/drivers/hwmon/baikal-pvt-core.c
@@ -0,0 +1,1160 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2020-2025 BAIKAL ELECTRONICS, JSC
+ *
+ * Authors:
+ *   Maxim Kaurkin <maxim.kaurkin@baikalelectronics.ru>
+ *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
+ *
+ * Baikal SoCs Process, Voltage, Temperature sensor drivers common code
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/limits.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/seqlock.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#include "baikal-pvt.h"
+
+#define PVT_THERMAL_POLLING_DELAY	5000
+
+#define PVT_THERMAL_CRIT	90000	/* 90 degrees default critical temperature limit */
+#define PVT_THERMAL_CRIT_HYST	2000
+
+static inline u32 pvt_update(struct pvt_hwmon *pvt, u32 reg, u32 mask, u32 data)
+{
+	u32 old;
+
+	old = pvt->ops->read(pvt, reg);
+	pvt->ops->write(pvt, reg, (old & ~mask) | (data & mask));
+
+	return old & mask;
+}
+
+/*
+ * Baikal SoCs PVT mode can be updated only when the controller is disabled.
+ * So first we disable it, then set the new mode together with the controller
+ * getting back enabled. The same concerns the temperature trim and
+ * measurements timeout. If it is necessary the interface mutex is supposed
+ * to be locked at the time the operations are performed.
+ */
+static inline void pvt_set_mode(struct pvt_hwmon *pvt, u32 mode)
+{
+	u32 old;
+
+	mode = FIELD_PREP(PVT_CTRL_MODE_MASK, mode);
+
+	old = pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, 0);
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_MODE_MASK | PVT_CTRL_EN, mode | old);
+}
+
+static inline u32 pvt_calc_trim(long temp)
+{
+	temp = clamp_val(temp, 0, PVT_TRIM_TEMP);
+
+	return DIV_ROUND_UP(temp, PVT_TRIM_STEP);
+}
+
+static inline void pvt_set_trim(struct pvt_hwmon *pvt, u32 trim)
+{
+	u32 old;
+
+	trim = FIELD_PREP(PVT_CTRL_TRIM_MASK, trim);
+
+	old = pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, 0);
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_TRIM_MASK | PVT_CTRL_EN, trim | old);
+}
+
+static inline void pvt_set_tout(struct pvt_hwmon *pvt, u32 tout)
+{
+	u32 old;
+
+	old = pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, 0);
+	pvt->ops->write(pvt, PVT_TTIMEOUT, tout);
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, old);
+}
+
+/*
+ * This driver can optionally provide the hwmon alarms for each sensor the PVT
+ * controller supports. The alarms functionality is made compile-time
+ * configurable due to the hardware interface implementation peculiarity
+ * described further in this comment. So in case if alarms are unnecessary in
+ * your system design it's recommended to have them disabled to prevent the PVT
+ * IRQs being periodically raised to get the data cache/alarms status up to
+ * date.
+ *
+ * Baikal SoCs PVT embedded controller is based on the Analog Bits PVT sensor,
+ * but is equipped with a dedicated control wrapper. It exposes the PVT
+ * sub-block registers space via the APB3 bus. In addition the wrapper provides
+ * a common interrupt vector of the sensors conversion completion events and
+ * threshold value alarms. Alas the wrapper interface hasn't been fully thought
+ * through. There is only one sensor can be activated at a time, for which the
+ * thresholds comparator is enabled right after the data conversion is
+ * completed. Due to this if alarms need to be implemented for all available
+ * sensors we can't just set the thresholds and enable the interrupts. We need
+ * to enable the sensors one after another and let the controller to detect
+ * the alarms by itself at each conversion. This also makes pointless to handle
+ * the alarms interrupts, since in occasion they happen synchronously with
+ * data conversion completion. The best driver design would be to have the
+ * completion interrupts enabled only and keep the converted value in the
+ * driver data cache. This solution is implemented if hwmon alarms are enabled
+ * in this driver. In case if the alarms are disabled, the conversion is
+ * performed on demand at the time a sensors input file is read.
+ */
+
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+
+#define pvt_hard_isr NULL
+
+static irqreturn_t pvt_soft_isr(int irq, void *data)
+{
+	const struct pvt_sensor_info *info;
+	struct pvt_hwmon *pvt = data;
+	struct pvt_cache *cache;
+	u32 val, thres_sts, old;
+
+	/*
+	 * DVALID bit will be cleared by reading the data. We need to save the
+	 * status before the next conversion happens. Threshold events will be
+	 * handled a bit later.
+	 */
+	thres_sts = pvt->ops->read(pvt, PVT_RAW_INTR_STAT);
+
+	/*
+	 * Then lets recharge the PVT interface with the next sampling mode.
+	 * Lock the interface mutex to serialize trim, timeouts and alarm
+	 * thresholds settings.
+	 */
+	cache = &pvt->cache[pvt->sensor];
+	info = &pvt->info[pvt->sensor];
+	pvt->sensor = (pvt->sensor == PVT_SENSOR_LAST) ?
+		      PVT_SENSOR_FIRST : (pvt->sensor + 1);
+
+	/*
+	 * For some reason we have to mask the interrupt before changing the
+	 * mode, otherwise sometimes the temperature mode doesn't get
+	 * activated even though the actual mode in the ctrl register
+	 * corresponds to one. Then we read the data. By doing so we also
+	 * recharge the data conversion. After this the mode corresponding
+	 * to the next sensor in the row is set. Finally we enable the
+	 * interrupts back.
+	 */
+	mutex_lock(&pvt->iface_mtx);
+
+	old = pvt_update(pvt, PVT_INTR_MASK, PVT_INTR_DVALID, PVT_INTR_DVALID);
+
+	val = pvt->ops->read(pvt, PVT_DATA);
+
+	pvt_set_mode(pvt, pvt->info[pvt->sensor].mode);
+
+	pvt_update(pvt, PVT_INTR_MASK, PVT_INTR_DVALID, old);
+
+	mutex_unlock(&pvt->iface_mtx);
+
+	/*
+	 * We can now update the data cache with data just retrieved from the
+	 * sensor. Lock write-seqlock to make sure the reader has a coherent
+	 * data.
+	 */
+	write_seqlock(&cache->data_seqlock);
+
+	cache->data = FIELD_GET(PVT_DATA_DATA_MASK, val);
+
+	write_sequnlock(&cache->data_seqlock);
+
+	/*
+	 * While PVT core is doing the next mode data conversion, we'll check
+	 * whether the alarms were triggered for the current sensor. Note that
+	 * according to the documentation only one threshold IRQ status can be
+	 * set at a time, that's why if-else statement is utilized.
+	 */
+	if ((thres_sts & info->thres_sts_lo) ^ cache->thres_sts_lo) {
+		WRITE_ONCE(cache->thres_sts_lo, thres_sts & info->thres_sts_lo);
+		hwmon_notify_event(pvt->hwmon, info->type, info->attr_min_alarm,
+				   info->channel);
+		if (info->type == hwmon_temp)
+			thermal_zone_device_update(pvt->tzd, THERMAL_EVENT_UNSPECIFIED);
+	} else if ((thres_sts & info->thres_sts_hi) ^ cache->thres_sts_hi) {
+		WRITE_ONCE(cache->thres_sts_hi, thres_sts & info->thres_sts_hi);
+		hwmon_notify_event(pvt->hwmon, info->type, info->attr_max_alarm,
+				   info->channel);
+		if (info->type == hwmon_temp)
+			thermal_zone_device_update(pvt->tzd, THERMAL_EVENT_UNSPECIFIED);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static inline umode_t pvt_limit_is_visible(enum pvt_sensor_type type)
+{
+	return 0644;
+}
+
+static inline umode_t pvt_alarm_is_visible(enum pvt_sensor_type type)
+{
+	return 0444;
+}
+
+static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
+			 long *val)
+{
+	struct pvt_cache *cache = &pvt->cache[type];
+	unsigned int seq;
+	u32 data;
+
+	do {
+		seq = read_seqbegin(&cache->data_seqlock);
+		data = cache->data;
+	} while (read_seqretry(&cache->data_seqlock, seq));
+
+	*val = pvt->ops->from_pvt(pvt, type, data);
+
+	return 0;
+}
+
+static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
+			  bool is_low, long *val)
+{
+	u32 data;
+
+	/* No need in serialization, since it is just read from MMIO. */
+	data = pvt->ops->read(pvt, pvt->info[type].thres_base);
+
+	if (is_low)
+		data = FIELD_GET(PVT_THRES_LO_MASK, data);
+	else
+		data = FIELD_GET(PVT_THRES_HI_MASK, data);
+
+	*val = pvt->ops->from_pvt(pvt, type, data);
+
+	return 0;
+}
+
+static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
+			   bool is_low, long val)
+{
+	u32 data, limit, mask;
+	int ret;
+
+	val = clamp(val, pvt->info[type].value_min, pvt->info[type].value_max);
+	data = pvt->ops->to_pvt(pvt, type, val);
+
+	/* Serialize limit update, since a part of the register is changed. */
+	ret = mutex_lock_interruptible(&pvt->iface_mtx);
+	if (ret)
+		return ret;
+
+	/* Make sure the upper and lower ranges don't intersect. */
+	limit = pvt->ops->read(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);
+		data = FIELD_PREP(PVT_THRES_LO_MASK, data);
+		mask = PVT_THRES_LO_MASK;
+	} else {
+		limit = FIELD_GET(PVT_THRES_LO_MASK, limit);
+		data = clamp_val(data, limit, PVT_DATA_MAX);
+		data = FIELD_PREP(PVT_THRES_HI_MASK, data);
+		mask = PVT_THRES_HI_MASK;
+	}
+
+	pvt_update(pvt, pvt->info[type].thres_base, mask, data);
+
+	mutex_unlock(&pvt->iface_mtx);
+
+	return 0;
+}
+
+static int pvt_read_alarm(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
+			  bool is_low, long *val)
+{
+	if (is_low)
+		*val = !!READ_ONCE(pvt->cache[type].thres_sts_lo);
+	else
+		*val = !!READ_ONCE(pvt->cache[type].thres_sts_hi);
+
+	return 0;
+}
+
+#else /* !CONFIG_SENSORS_BAIKAL_PVT_ALARMS */
+
+static irqreturn_t pvt_hard_isr(int irq, void *data)
+{
+	struct pvt_hwmon *pvt = data;
+	struct pvt_cache *cache;
+	u32 val;
+
+	/*
+	 * Mask the DVALID interrupt so after exiting from the handler a
+	 * repeated conversion wouldn't happen.
+	 */
+	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 = pvt->ops->read(pvt, PVT_DATA);
+	if (!(val & PVT_DATA_VALID)) {
+		dev_err(pvt->dev, "Got IRQ when data isn't valid\n");
+		return IRQ_HANDLED;
+	}
+
+	cache = &pvt->cache[pvt->sensor];
+
+	WRITE_ONCE(cache->data, FIELD_GET(PVT_DATA_DATA_MASK, val));
+
+	complete(&cache->conversion);
+
+	return IRQ_HANDLED;
+}
+
+#define pvt_soft_isr NULL
+
+static inline umode_t pvt_limit_is_visible(enum pvt_sensor_type type)
+{
+	return 0;
+}
+
+static inline umode_t pvt_alarm_is_visible(enum pvt_sensor_type type)
+{
+	return 0;
+}
+
+static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
+			 long *val)
+{
+	struct pvt_cache *cache = &pvt->cache[type];
+	unsigned long timeout;
+	u32 data;
+	int ret;
+
+	/*
+	 * Lock PVT conversion interface until data cache is updated. The
+	 * data read procedure is following: set the requested PVT sensor
+	 * mode, enable IRQ and conversion, wait until conversion is finished,
+	 * then disable conversion and IRQ, and read the cached data.
+	 */
+	ret = mutex_lock_interruptible(&pvt->iface_mtx);
+	if (ret)
+		return ret;
+
+	pvt->sensor = type;
+	pvt_set_mode(pvt, pvt->info[type].mode);
+
+	/*
+	 * Unmask the DVALID interrupt and enable the sensors conversions.
+	 * Do the reverse procedure when conversion is done.
+	 */
+	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
+	 * down the request won't be completed and the caller will hang up on
+	 * this procedure until the power is back up again. Multiply the
+	 * timeout by the factor of two to prevent a false timeout.
+	 */
+	timeout = 2 * usecs_to_jiffies(ktime_to_us(pvt->timeout));
+	ret = wait_for_completion_timeout(&cache->conversion, timeout);
+
+	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);
+
+	mutex_unlock(&pvt->iface_mtx);
+
+	if (!ret)
+		return -ETIMEDOUT;
+
+	*val = pvt->ops->from_pvt(pvt, type, data);
+
+	return 0;
+}
+
+static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
+			  bool is_low, long *val)
+{
+	return -EOPNOTSUPP;
+}
+
+static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
+			   bool is_low, long val)
+{
+	return -EOPNOTSUPP;
+}
+
+static int pvt_read_alarm(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
+			  bool is_low, long *val)
+{
+	return -EOPNOTSUPP;
+}
+
+#endif /* !CONFIG_SENSORS_BAIKAL_PVT_ALARMS */
+
+static inline bool pvt_hwmon_channel_is_valid(enum hwmon_sensor_types type,
+					      int ch)
+{
+	switch (type) {
+	case hwmon_temp:
+		if (ch < 0 || ch >= PVT_TEMP_CHS)
+			return false;
+		break;
+	case hwmon_in:
+		if (ch < 0 || ch >= PVT_VOLT_CHS)
+			return false;
+		break;
+	default:
+		break;
+	}
+
+	/* The rest of the types are independent from the channel number. */
+	return true;
+}
+
+static umode_t pvt_hwmon_is_visible(const void *data,
+				    enum hwmon_sensor_types type,
+				    u32 attr, int ch)
+{
+	if (!pvt_hwmon_channel_is_valid(type, ch))
+		return 0;
+
+	switch (type) {
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			return 0644;
+		}
+		break;
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_input:
+		case hwmon_temp_type:
+		case hwmon_temp_label:
+			return 0444;
+		case hwmon_temp_min:
+		case hwmon_temp_max:
+			return pvt_limit_is_visible(PVT_TEMP + ch);
+		case hwmon_temp_min_alarm:
+		case hwmon_temp_max_alarm:
+			return pvt_alarm_is_visible(PVT_TEMP + ch);
+		case hwmon_temp_offset:
+			return 0644;
+		}
+		break;
+	case hwmon_in:
+		switch (attr) {
+		case hwmon_in_input:
+		case hwmon_in_label:
+			return 0444;
+		case hwmon_in_min:
+		case hwmon_in_max:
+			return pvt_limit_is_visible(PVT_VOLT + ch);
+		case hwmon_in_min_alarm:
+		case hwmon_in_max_alarm:
+			return pvt_alarm_is_visible(PVT_VOLT + ch);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int pvt_read_trim(struct pvt_hwmon *pvt, long *val)
+{
+	u32 data;
+
+	data = pvt->ops->read(pvt, PVT_CTRL);
+	*val = FIELD_GET(PVT_CTRL_TRIM_MASK, data) * PVT_TRIM_STEP;
+
+	return 0;
+}
+
+static int pvt_write_trim(struct pvt_hwmon *pvt, long val)
+{
+	u32 trim;
+	int ret;
+
+	/*
+	 * Serialize trim update, since a part of the register is changed and
+	 * the controller is supposed to be disabled during this operation.
+	 */
+	ret = mutex_lock_interruptible(&pvt->iface_mtx);
+	if (ret)
+		return ret;
+
+	trim = pvt_calc_trim(val);
+	pvt_set_trim(pvt, trim);
+
+	mutex_unlock(&pvt->iface_mtx);
+
+	return 0;
+}
+
+static int pvt_read_timeout(struct pvt_hwmon *pvt, long *val)
+{
+	int ret;
+
+	ret = mutex_lock_interruptible(&pvt->iface_mtx);
+	if (ret)
+		return ret;
+
+	/* Return the result in msec as hwmon sysfs interface requires. */
+	*val = ktime_to_ms(pvt->timeout);
+
+	mutex_unlock(&pvt->iface_mtx);
+
+	return 0;
+}
+
+static u32 pvt_calc_tout(ktime_t timeout, unsigned long rate)
+{
+	ktime_t kt;
+	u32 tout;
+
+	/*
+	 * Subtract a constant lag, which always persists due to the limited
+	 * PVT sampling rate. Make sure the timeout is not negative.
+	 */
+	kt = ktime_sub_ns(timeout, PVT_TOUT_MIN);
+	if (ktime_to_ns(kt) < 0)
+		kt = ktime_set(0, 0);
+
+	/*
+	 * Recalculate the timeout in terms of the reference clock
+	 * period.
+	 */
+	tout = ktime_divns(kt * rate, NSEC_PER_SEC);
+
+	return tout;
+}
+
+static int pvt_write_timeout(struct pvt_hwmon *pvt, long val)
+{
+	unsigned long rate;
+	ktime_t kt, cache;
+	u32 data;
+	int ret;
+
+	rate = clk_get_rate(pvt->clks[PVT_CLOCK_REF].clk);
+	if (!rate)
+		return -ENODEV;
+
+	/*
+	 * If alarms are enabled, the requested timeout must be divided
+	 * between all available sensors to have the requested delay
+	 * applicable to each individual sensor.
+	 */
+	cache = kt = ms_to_ktime(val);
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+	kt = ktime_divns(kt, PVT_SENSORS_NUM);
+#endif
+
+	data = pvt_calc_tout(kt, rate);
+
+	/*
+	 * Update the measurements delay, but lock the interface first, since
+	 * we have to disable PVT in order to have the new delay actually
+	 * updated.
+	 */
+	ret = mutex_lock_interruptible(&pvt->iface_mtx);
+	if (ret)
+		return ret;
+
+	pvt_set_tout(pvt, data);
+	pvt->timeout = cache;
+
+	mutex_unlock(&pvt->iface_mtx);
+
+	return 0;
+}
+
+static int pvt_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+			  u32 attr, int ch, long *val)
+{
+	struct pvt_hwmon *pvt = dev_get_drvdata(dev);
+
+	if (!pvt_hwmon_channel_is_valid(type, ch))
+		return -EINVAL;
+
+	switch (type) {
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			return pvt_read_timeout(pvt, val);
+		}
+		break;
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_input:
+			return pvt_read_data(pvt, PVT_TEMP + ch, val);
+		case hwmon_temp_type:
+			*val = 1;
+			return 0;
+		case hwmon_temp_min:
+			return pvt_read_limit(pvt, PVT_TEMP + ch, true, val);
+		case hwmon_temp_max:
+			return pvt_read_limit(pvt, PVT_TEMP + ch, false, val);
+		case hwmon_temp_min_alarm:
+			return pvt_read_alarm(pvt, PVT_TEMP + ch, true, val);
+		case hwmon_temp_max_alarm:
+			return pvt_read_alarm(pvt, PVT_TEMP + ch, false, val);
+		case hwmon_temp_offset:
+			return pvt_read_trim(pvt, val);
+		}
+		break;
+	case hwmon_in:
+		switch (attr) {
+		case hwmon_in_input:
+			return pvt_read_data(pvt, PVT_VOLT + ch, val);
+		case hwmon_in_min:
+			return pvt_read_limit(pvt, PVT_VOLT + ch, true, val);
+		case hwmon_in_max:
+			return pvt_read_limit(pvt, PVT_VOLT + ch, false, val);
+		case hwmon_in_min_alarm:
+			return pvt_read_alarm(pvt, PVT_VOLT + ch, true, val);
+		case hwmon_in_max_alarm:
+			return pvt_read_alarm(pvt, PVT_VOLT + ch, false, val);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int pvt_hwmon_read_string(struct device *dev,
+				 enum hwmon_sensor_types type,
+				 u32 attr, int ch, const char **str)
+{
+	struct pvt_hwmon *pvt = dev_get_drvdata(dev);
+
+	if (!pvt_hwmon_channel_is_valid(type, ch))
+		return -EINVAL;
+
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_label:
+			*str = pvt->info[PVT_TEMP + ch].label;
+			return 0;
+		}
+		break;
+	case hwmon_in:
+		switch (attr) {
+		case hwmon_in_label:
+			*str = pvt->info[PVT_VOLT + ch].label;
+			return 0;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int pvt_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+			   u32 attr, int ch, long val)
+{
+	struct pvt_hwmon *pvt = dev_get_drvdata(dev);
+
+	if (!pvt_hwmon_channel_is_valid(type, ch))
+		return -EINVAL;
+
+	switch (type) {
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			return pvt_write_timeout(pvt, val);
+		}
+		break;
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_min:
+			return pvt_write_limit(pvt, PVT_TEMP + ch, true, val);
+		case hwmon_temp_max:
+			return pvt_write_limit(pvt, PVT_TEMP + ch, false, val);
+		case hwmon_temp_offset:
+			return pvt_write_trim(pvt, val);
+		}
+		break;
+	case hwmon_in:
+		switch (attr) {
+		case hwmon_in_min:
+			return pvt_write_limit(pvt, PVT_VOLT + ch, true, val);
+		case hwmon_in_max:
+			return pvt_write_limit(pvt, PVT_VOLT + ch, false, val);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static const struct hwmon_ops pvt_hwmon_ops = {
+	.is_visible = pvt_hwmon_is_visible,
+	.read = pvt_hwmon_read,
+	.read_string = pvt_hwmon_read_string,
+	.write = pvt_hwmon_write
+};
+
+static struct hwmon_chip_info pvt_hwmon_info = {
+	.ops = &pvt_hwmon_ops,
+};
+
+static int pvt_thermal_get_temp(struct thermal_zone_device *tzd, int *temp)
+{
+	struct pvt_hwmon *pvt = thermal_zone_device_priv(tzd);
+	long t;
+	int err;
+
+	err = pvt_read_data(pvt, PVT_TEMP, &t);
+	if (err)
+		return err;
+
+	*temp = t;
+
+	return 0;
+}
+
+static int pvt_thermal_set_trips(struct thermal_zone_device *tzd,
+				 int low, int high)
+{
+	struct pvt_hwmon *pvt = thermal_zone_device_priv(tzd);
+	int err;
+
+	err = pvt_write_limit(pvt, PVT_TEMP, true, low);
+	if (err)
+		return err;
+	return pvt_write_limit(pvt, PVT_TEMP, false, high);
+}
+
+static struct thermal_zone_device_ops pvt_thermal_ops = {
+	.get_temp = pvt_thermal_get_temp,
+	.set_trips = pvt_thermal_set_trips,
+};
+
+static const struct thermal_trip pvt_trips[] = {
+	{
+		.type = THERMAL_TRIP_CRITICAL,
+		.temperature = PVT_THERMAL_CRIT,
+		.hysteresis = PVT_THERMAL_CRIT_HYST,
+	},
+};
+
+static void pvt_thermal_unregister(void *data)
+{
+	struct thermal_zone_device *tzd = data;
+
+	thermal_zone_device_disable(tzd);
+	thermal_zone_device_unregister(tzd);
+}
+
+static struct thermal_zone_device *pvt_thermal_register(struct pvt_hwmon *pvt)
+{
+	struct thermal_zone_device *tzd;
+	struct thermal_zone_params *tzp;
+	struct thermal_trip *trips;
+	int err;
+
+	trips = devm_kmemdup(pvt->dev, pvt_trips, sizeof(pvt_trips),
+			     GFP_KERNEL);
+	if (!trips)
+		return ERR_PTR(-ENOMEM);
+
+	tzp = devm_kzalloc(pvt->dev, sizeof(*tzp), GFP_KERNEL);
+	if (!tzp) {
+		devm_kfree(pvt->dev, trips);
+		return ERR_PTR(-ENOMEM);
+	}
+	tzp->no_hwmon = true;
+	tzp->slope = 1;
+	tzp->offset = 0;
+
+	tzd = thermal_zone_device_register_with_trips("pvt_thermal",
+		trips, ARRAY_SIZE(pvt_trips), pvt, &pvt_thermal_ops,
+		tzp, 0, PVT_THERMAL_POLLING_DELAY);
+	if (IS_ERR(tzd)) {
+		devm_kfree(pvt->dev, tzp);
+		devm_kfree(pvt->dev, trips);
+		return tzd;
+	}
+	err = thermal_zone_device_enable(tzd);
+	if (err)
+		goto out_unregister;
+	err = devm_add_action(pvt->dev, pvt_thermal_unregister, tzd);
+	if (err)
+		goto out_disable;
+	return tzd;
+
+out_disable:
+	thermal_zone_device_disable(tzd);
+out_unregister:
+	thermal_zone_device_unregister(tzd);
+	devm_kfree(pvt->dev, tzp);
+	devm_kfree(pvt->dev, trips);
+	return ERR_PTR(err);
+}
+
+static void pvt_clear_data(void *data)
+{
+	struct pvt_hwmon *pvt = data;
+#if !defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+	int idx;
+
+	for (idx = 0; idx < PVT_SENSORS_NUM; ++idx)
+		complete_all(&pvt->cache[idx].conversion);
+#endif
+
+	mutex_destroy(&pvt->iface_mtx);
+}
+
+static struct pvt_hwmon *pvt_create_data(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct pvt_hwmon *pvt;
+	int ret, idx;
+
+	pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL);
+	if (!pvt)
+		return ERR_PTR(-ENOMEM);
+
+	ret = devm_add_action(dev, pvt_clear_data, pvt);
+	if (ret) {
+		dev_err(dev, "Can't add PVT data clear action\n");
+		return ERR_PTR(ret);
+	}
+
+	pvt->dev = dev;
+	pvt->sensor = PVT_SENSOR_FIRST;
+	mutex_init(&pvt->iface_mtx);
+
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+	for (idx = 0; idx < PVT_SENSORS_NUM; ++idx)
+		seqlock_init(&pvt->cache[idx].data_seqlock);
+#else
+	for (idx = 0; idx < PVT_SENSORS_NUM; ++idx)
+		init_completion(&pvt->cache[idx].conversion);
+#endif
+
+	return pvt;
+}
+
+static int pvt_request_regs(struct pvt_hwmon *pvt)
+{
+	struct platform_device *pdev = to_platform_device(pvt->dev);
+
+	if (!pvt->regs) {
+		pvt->regs = devm_platform_ioremap_resource(pdev, 0);
+		if (IS_ERR(pvt->regs))
+			return PTR_ERR(pvt->regs);
+	}
+
+	return 0;
+}
+
+static void pvt_disable_clks(void *data)
+{
+	struct pvt_hwmon *pvt = data;
+
+	clk_bulk_disable_unprepare(PVT_CLOCK_NUM, pvt->clks);
+}
+
+static int pvt_request_clks(struct pvt_hwmon *pvt)
+{
+	int ret;
+
+	pvt->clks[PVT_CLOCK_APB].id = "pclk";
+	pvt->clks[PVT_CLOCK_REF].id = "ref";
+
+	ret = devm_clk_bulk_get_optional(pvt->dev, PVT_CLOCK_NUM, pvt->clks);
+	if (ret) {
+		dev_err(pvt->dev, "Couldn't get PVT clocks descriptors\n");
+		return ret;
+	}
+
+	ret = clk_bulk_prepare_enable(PVT_CLOCK_NUM, pvt->clks);
+	if (ret) {
+		dev_err(pvt->dev, "Couldn't enable the PVT clocks\n");
+		return ret;
+	}
+
+	ret = devm_add_action_or_reset(pvt->dev, pvt_disable_clks, pvt);
+	if (ret) {
+		dev_err(pvt->dev, "Can't add PVT clocks disable action\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int pvt_check_pwr(struct pvt_hwmon *pvt)
+{
+	unsigned long tout;
+	int ret = 0;
+	u32 data;
+
+	/*
+	 * Test out the sensor conversion functionality. If it is not done on
+	 * time then the domain must have been unpowered and we won't be able
+	 * to use the device later in this driver.
+	 * Note If the power source is lost during the normal driver work the
+	 * data read procedure will either return -ETIMEDOUT (for the
+	 * alarm-less driver configuration) or just stop the repeated
+	 * conversion. In the later case alas we won't be able to detect the
+	 * problem.
+	 */
+	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);
+	pvt->ops->read(pvt, PVT_DATA);
+
+	tout = PVT_TOUT_MIN / NSEC_PER_USEC;
+	usleep_range(tout, 2 * tout);
+
+	data = pvt->ops->read(pvt, PVT_DATA);
+	if (!(data & PVT_DATA_VALID)) {
+		ret = -ENODEV;
+		dev_err(pvt->dev, "Sensor is powered down\n");
+	}
+
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, 0);
+
+	return ret;
+}
+
+static int pvt_init_iface(struct pvt_hwmon *pvt)
+{
+	unsigned long rate;
+	u32 trim, temp, tout;
+
+	rate = clk_get_rate(pvt->clks[PVT_CLOCK_REF].clk);
+	if (!rate) {
+		dev_err(pvt->dev, "Invalid reference clock rate\n");
+		return -ENODEV;
+	}
+
+	/*
+	 * Make sure all interrupts and controller are disabled so not to
+	 * accidentally have ISR executed before the driver data is fully
+	 * initialized. Clear the IRQ status as well.
+	 */
+	pvt_update(pvt, PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL);
+	pvt_update(pvt, PVT_CTRL, PVT_CTRL_EN, 0);
+	pvt->ops->read(pvt, PVT_CLR_INTR);
+	pvt->ops->read(pvt, PVT_DATA);
+
+	/*
+	 * Preserve the current ref-clock based delay (Ttotal) between the
+	 * sensors data samples in the driver data so not to recalculate it
+	 * each time on the data requests and timeout reads. It consists of the
+	 * delay introduced by the internal ref-clock timer (N / Fclk) and the
+	 * constant timeout caused by each conversion latency (Tmin):
+	 *   Ttotal = N / Fclk + Tmin
+	 * If alarms are enabled the sensors are polled one after another and
+	 * in order to get the next measurement of a particular sensor the
+	 * caller will have to wait for at most until all the others are
+	 * polled. In that case the formulae will look a bit different:
+	 *   Ttotal = 5 * (N / Fclk + Tmin)
+	 */
+	pvt->timeout = ktime_set(0, PVT_TOUT_DEF);
+	tout = pvt_calc_tout(
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+		ktime_divns(pvt->timeout, PVT_SENSORS_NUM)
+#else
+		pvt->timeout
+#endif
+		, rate);
+
+	/* Setup default sensor mode, timeout and temperature trim. */
+	pvt_set_mode(pvt, pvt->info[pvt->sensor].mode);
+	pvt_set_tout(pvt, tout);
+
+	trim = PVT_TRIM_DEF;
+	if (!of_property_read_u32(pvt->dev->of_node,
+	     "baikal,pvt-temp-offset-millicelsius", &temp))
+		trim = pvt_calc_trim(temp);
+
+	pvt_set_trim(pvt, trim);
+
+	return 0;
+}
+
+static int pvt_create_hwmon(struct pvt_hwmon *pvt,
+			    const struct hwmon_channel_info * const *hwmon_info)
+{
+	pvt_hwmon_info.info = hwmon_info;
+	pvt->hwmon = devm_hwmon_device_register_with_info(pvt->dev, "pvt", pvt,
+		&pvt_hwmon_info, NULL);
+	if (IS_ERR(pvt->hwmon)) {
+		dev_err(pvt->dev, "Couldn't create hwmon device\n");
+		return PTR_ERR(pvt->hwmon);
+	}
+
+	if (__is_defined(CONFIG_THERMAL_OF) && acpi_disabled) {
+		pvt->tzd = devm_thermal_of_zone_register(pvt->dev, 0, pvt,
+							 &pvt_thermal_ops);
+		if (IS_ERR(pvt->tzd) && PTR_ERR(pvt->tzd) == -ENODEV) {
+			dev_info(pvt->dev,
+				"temp0_input not attached to any thermal zone\n");
+			return 0;
+		}
+	} else {
+		pvt->tzd = pvt_thermal_register(pvt);
+	}
+	if (IS_ERR(pvt->tzd)) {
+		dev_err(pvt->dev, "Couldn't register to thermal\n");
+		return PTR_ERR(pvt->tzd);
+	}
+
+	return 0;
+}
+
+static int pvt_request_irq(struct pvt_hwmon *pvt)
+{
+	struct platform_device *pdev = to_platform_device(pvt->dev);
+	int ret;
+
+	pvt->irq = platform_get_irq(pdev, 0);
+	if (pvt->irq < 0)
+		return pvt->irq;
+
+	ret = devm_request_threaded_irq(pvt->dev, pvt->irq,
+					pvt_hard_isr,
+					pvt_soft_isr,
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+					IRQF_SHARED |
+					IRQF_TRIGGER_HIGH |
+					IRQF_ONESHOT,
+#else
+					IRQF_SHARED |
+					IRQF_TRIGGER_HIGH,
+#endif
+					"pvt", pvt);
+	if (ret) {
+		dev_err(pvt->dev, "Couldn't request PVT IRQ\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+
+static void pvt_disable_iface(void *data)
+{
+	struct pvt_hwmon *pvt = data;
+
+	mutex_lock(&pvt->iface_mtx);
+	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);
+}
+
+static int pvt_enable_iface(struct pvt_hwmon *pvt)
+{
+	int ret;
+
+	ret = devm_add_action(pvt->dev, pvt_disable_iface, pvt);
+	if (ret) {
+		dev_err(pvt->dev, "Can't add PVT disable interface action\n");
+		return ret;
+	}
+
+	/*
+	 * Enable sensors data conversion and IRQ. We need to lock the
+	 * interface mutex since hwmon has just been created and the
+	 * corresponding sysfs files are accessible from user-space,
+	 * which theoretically may cause races.
+	 */
+	mutex_lock(&pvt->iface_mtx);
+	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;
+}
+
+#else /* !CONFIG_SENSORS_BAIKAL_PVT_ALARMS */
+
+static int pvt_enable_iface(struct pvt_hwmon *pvt)
+{
+	return 0;
+}
+
+#endif /* !CONFIG_SENSORS_BAIKAL_PVT_ALARMS */
+
+int baikal_pvt_create(struct platform_device *pdev, struct pvt_ops *ops,
+		      const struct pvt_sensor_info *info,
+		      const struct hwmon_channel_info * const *hwmon_info)
+{
+	struct pvt_hwmon *pvt;
+	int ret;
+
+	pvt = pvt_create_data(pdev);
+	if (IS_ERR(pvt))
+		return PTR_ERR(pvt);
+
+	pvt->ops = ops;
+	pvt->info = info;
+	platform_set_drvdata(pdev, pvt);
+
+	if (ops->init) {
+		ret = ops->init(pvt);
+		if (ret)
+			return ret;
+	}
+
+	ret = pvt_request_regs(pvt);
+	if (ret)
+		return ret;
+
+	ret = pvt_request_clks(pvt);
+	if (ret)
+		return ret;
+
+	ret = pvt_check_pwr(pvt);
+	if (ret)
+		return ret;
+
+	ret = pvt_init_iface(pvt);
+	if (ret)
+		return ret;
+
+	ret = pvt_create_hwmon(pvt, hwmon_info);
+	if (ret)
+		return ret;
+
+	ret = pvt_request_irq(pvt);
+	if (ret)
+		return ret;
+
+	ret = pvt_enable_iface(pvt);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL(baikal_pvt_create);
diff --git a/drivers/hwmon/baikal-pvt.h b/drivers/hwmon/baikal-pvt.h
new file mode 100644
index 00000000000000..ca1c786f872dfe
--- /dev/null
+++ b/drivers/hwmon/baikal-pvt.h
@@ -0,0 +1,273 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2020-2025 BAIKAL ELECTRONICS, JSC
+ *
+ * Baikal SoCs Process, Voltage, Temperature sensor driver
+ */
+#ifndef __HWMON_BAIKAL_PVT_H__
+#define __HWMON_BAIKAL_PVT_H__
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/seqlock.h>
+#include <linux/thermal.h>
+
+/* Baikal SoC's PVT registers and their bitfields */
+#define PVT_CTRL			0x00
+#define PVT_CTRL_EN			BIT(0)
+#define PVT_CTRL_MODE_FLD		1
+#define PVT_CTRL_MODE_MASK		GENMASK(3, PVT_CTRL_MODE_FLD)
+#define PVT_CTRL_MODE_TEMP		0x0
+#define PVT_CTRL_MODE_VOLT		0x1
+#define PVT_CTRL_MODE_LVT		0x2
+#define PVT_CTRL_MODE_HVT		0x4
+#define PVT_CTRL_MODE_ULVT		PVT_CTRL_MODE_HVT
+#define PVT_CTRL_MODE_SVT		0x6
+#define PVT_CTRL_TRIM_FLD		4
+#define PVT_CTRL_TRIM_MASK		GENMASK(8, PVT_CTRL_TRIM_FLD)
+#define PVT_DATA			0x04
+#define PVT_DATA_VALID			BIT(10)
+#define PVT_DATA_DATA_FLD		0
+#define PVT_DATA_DATA_MASK		GENMASK(9, PVT_DATA_DATA_FLD)
+#define PVT_TTHRES			0x08
+#define PVT_VTHRES			0x0C
+#define PVT_LTHRES			0x10
+#define PVT_HTHRES			0x14
+#define PVT_UTHRES			PVT_HTHRES
+#define PVT_STHRES			0x18
+#define PVT_THRES_LO_FLD		0
+#define PVT_THRES_LO_MASK		GENMASK(9, PVT_THRES_LO_FLD)
+#define PVT_THRES_HI_FLD		10
+#define PVT_THRES_HI_MASK		GENMASK(19, PVT_THRES_HI_FLD)
+#define PVT_TTIMEOUT			0x1C
+#define PVT_INTR_STAT			0x20
+#define PVT_INTR_MASK			0x24
+#define PVT_RAW_INTR_STAT		0x28
+#define PVT_INTR_DVALID			BIT(0)
+#define PVT_INTR_TTHRES_LO		BIT(1)
+#define PVT_INTR_TTHRES_HI		BIT(2)
+#define PVT_INTR_VTHRES_LO		BIT(3)
+#define PVT_INTR_VTHRES_HI		BIT(4)
+#define PVT_INTR_LTHRES_LO		BIT(5)
+#define PVT_INTR_LTHRES_HI		BIT(6)
+#define PVT_INTR_HTHRES_LO		BIT(7)
+#define PVT_INTR_HTHRES_HI		BIT(8)
+#define PVT_INTR_UTHRES_LO		PVT_INTR_HTHRES_LO
+#define PVT_INTR_UTHRES_HI		PVT_INTR_HTHRES_HI
+#define PVT_INTR_STHRES_LO		BIT(9)
+#define PVT_INTR_STHRES_HI		BIT(10)
+#define PVT_INTR_ALL			GENMASK(10, 0)
+#define PVT_CLR_INTR			0x2C
+
+/*
+ * PVT sensors-related limits and default values
+ * @PVT_TEMP_MIN: Minimal temperature in millidegrees of Celsius.
+ * @PVT_TEMP_MAX: Maximal temperature in millidegrees of Celsius.
+ * @PVT_TEMP_CHS: Number of temperature hwmon channels.
+ * @PVT_VOLT_MIN: Minimal voltage in mV.
+ * @PVT_VOLT_MAX: Maximal voltage in mV.
+ * @PVT_VOLT_CHS: Number of voltage hwmon channels.
+ * @PVT_DATA_MIN: Minimal PVT raw data value.
+ * @PVT_DATA_MAX: Maximal PVT raw data value.
+ * @PVT_TRIM_MIN: Minimal temperature sensor trim value.
+ * @PVT_TRIM_MAX: Maximal temperature sensor trim value.
+ * @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value
+ *		  when one is determined for Baikal SoC).
+ * @PVT_TRIM_TEMP: Maximum temperature encoded by the trim factor.
+ * @PVT_TRIM_STEP: Temperature stride corresponding to the trim value.
+ * @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds.
+ * @PVT_TOUT_DEF: Default data measurements timeout in nanoseconds.
+ *		  In case if alarms are activated the PVT IRQ is enabled to be
+ *		  raised after each conversion in order to have the thresholds
+ *		  checked and the converted value cached. Too frequent
+ *		  conversions may cause the system CPU overload. Lets set the
+ *		  50ms delay between them by default to prevent this.
+ */
+#define PVT_TEMP_MIN		-48380L
+#define PVT_TEMP_MAX		147438L
+#define PVT_TEMP_CHS		1
+#define PVT_VOLT_MIN		620L
+#define PVT_VOLT_MAX		1168L
+#define PVT_LVT_MIN		PVT_VOLT_MIN
+#define PVT_LVT_MAX		PVT_VOLT_MAX
+#define PVT_HVT_MIN		PVT_VOLT_MIN
+#define PVT_HVT_MAX		PVT_VOLT_MAX
+#define PVT_ULVT_MIN		PVT_VOLT_MIN
+#define PVT_ULVT_MAX		PVT_VOLT_MAX
+#define PVT_SVT_MIN		PVT_VOLT_MIN
+#define PVT_SVT_MAX		PVT_VOLT_MAX
+#define PVT_VOLT_CHS		4
+#define PVT_DATA_MIN		0
+#define PVT_DATA_MAX		(PVT_DATA_DATA_MASK >> PVT_DATA_DATA_FLD)
+#define PVT_TRIM_MIN		0
+#define PVT_TRIM_MAX		(PVT_CTRL_TRIM_MASK >> PVT_CTRL_TRIM_FLD)
+#define PVT_TRIM_TEMP		7130
+#define PVT_TRIM_STEP		(PVT_TRIM_TEMP / PVT_TRIM_MAX)
+#define PVT_TRIM_DEF		0
+#define PVT_TOUT_MIN		(NSEC_PER_SEC / 3000)
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+# define PVT_TOUT_DEF		250000000
+#else
+# define PVT_TOUT_DEF		PVT_TOUT_MIN
+#endif
+
+/*
+ * enum pvt_sensor_type - Baikal SoC PVT sensor types (correspond to each PVT
+ *			  sampling mode)
+ * @PVT_SENSOR*: helpers to traverse the sensors in loops.
+ * @PVT_TEMP: PVT Temperature sensor.
+ * @PVT_VOLT: PVT Voltage sensor.
+ * @PVT_LVT: PVT Low-Voltage threshold sensor.
+ * @PVT_HVT: PVT High-Voltage threshold sensor.
+ * @PVT_ULVT: PVT Ultra-Low-Voltage threshold sensor.
+ * @PVT_SVT: PVT Standard-Voltage threshold sensor.
+ */
+enum pvt_sensor_type {
+	PVT_SENSOR_FIRST,
+	PVT_TEMP = PVT_SENSOR_FIRST,
+	PVT_VOLT,
+	PVT_LVT,
+	PVT_HVT,
+	PVT_ULVT = PVT_HVT,
+	PVT_SVT,
+	PVT_SENSOR_LAST = PVT_SVT,
+	PVT_SENSORS_NUM
+};
+
+/*
+ * enum pvt_clock_type - Baikal SoC PVT clocks.
+ * @PVT_CLOCK_APB: APB clock.
+ * @PVT_CLOCK_REF: PVT reference clock.
+ */
+enum pvt_clock_type {
+	PVT_CLOCK_APB,
+	PVT_CLOCK_REF,
+	PVT_CLOCK_NUM
+};
+
+/*
+ * struct pvt_sensor_info - Baikal SoC PVT sensor informational structure
+ * @channel: Sensor channel ID.
+ * @label: hwmon sensor label.
+ * @mode: PVT mode corresponding to the channel.
+ * @thres_base: upper and lower threshold values of the sensor.
+ * @thres_sts_lo: low threshold status bitfield.
+ * @thres_sts_hi: high threshold status bitfield.
+ * @type: Sensor type.
+ * @value_min: minimal sensor value.
+ * @value_max: maximal sensor value.
+ * @attr_min_alarm: Min alarm attribute ID.
+ * @attr_min_alarm: Max alarm attribute ID.
+ */
+struct pvt_sensor_info {
+	int channel;
+	const char *label;
+	u32 mode;
+	unsigned long thres_base;
+	u32 thres_sts_lo;
+	u32 thres_sts_hi;
+	enum hwmon_sensor_types type;
+	long value_min;
+	long value_max;
+	u32 attr_min_alarm;
+	u32 attr_max_alarm;
+};
+
+#define PVT_SENSOR_INFO(_ch, _label, _type, _mode, _thres)	\
+	{							\
+		.channel = _ch,					\
+		.label = _label,				\
+		.mode = PVT_CTRL_MODE_ ##_mode,			\
+		.thres_base = PVT_ ##_thres,			\
+		.thres_sts_lo = PVT_INTR_ ##_thres## _LO,	\
+		.thres_sts_hi = PVT_INTR_ ##_thres## _HI,	\
+		.type = _type,					\
+		.value_min = PVT_ ##_mode## _MIN,		\
+		.value_max = PVT_ ##_mode## _MAX,		\
+		.attr_min_alarm = _type## _min_alarm,		\
+		.attr_max_alarm = _type## _max_alarm,		\
+	}
+
+/*
+ * struct pvt_cache - PVT sensors data cache
+ * @data: data cache in raw format.
+ * @thres_sts_lo: low threshold status saved on the previous data conversion.
+ * @thres_sts_hi: high threshold status saved on the previous data conversion.
+ * @data_seqlock: cached data seq-lock.
+ * @conversion: data conversion completion.
+ */
+struct pvt_cache {
+	u32 data;
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+	seqlock_t data_seqlock;
+	u32 thres_sts_lo;
+	u32 thres_sts_hi;
+#else
+	struct completion conversion;
+#endif
+};
+
+struct pvt_hwmon;
+
+/*
+ * struct pvt_ops - PVT sensor operations
+ * @read_pvt: read PVT register.
+ * @write_pvt: write PVT register.
+ * @to_pvt: convert temperature/voltage to PVT data.
+ * @from_pvt: convert PVT data to temperature/voltage.
+ */
+struct pvt_ops {
+	int  (*init)(struct pvt_hwmon *);
+	u32  (*read)(struct pvt_hwmon *, u32);
+	int  (*write)(struct pvt_hwmon *, u32, u32);
+	u32  (*to_pvt)(struct pvt_hwmon *, enum pvt_sensor_type, long);
+	long (*from_pvt)(struct pvt_hwmon *, enum pvt_sensor_type, u32);
+};
+
+/*
+ * struct pvt_hwmon - Baikal SoC PVT private data
+ * @dev: device structure of the PVT platform device.
+ * @hwmon: hwmon device structure.
+ * @regs: pointer to the Baikal SoC PVT registers region.
+ * @irq: PVT events IRQ number.
+ * @clks: Array of the PVT clocks descriptor (APB/ref clocks).
+ * @iface_mtx: Generic interface mutex (used to lock the alarm registers
+ *	       when the alarms enabled, or the data conversion interface
+ *	       if alarms are disabled).
+ * @sensor: current PVT sensor the data conversion is being performed for.
+ * @cache: data cache descriptor.
+ * @timeout: conversion timeout cache.
+ * @ops: PVT operations.
+ * @tzd: thermal zone.
+ */
+struct pvt_hwmon {
+	struct device *dev;
+	struct device *hwmon;
+
+	void __iomem *regs;
+	int irq;
+
+	struct clk_bulk_data clks[PVT_CLOCK_NUM];
+
+	struct mutex iface_mtx;
+	enum pvt_sensor_type sensor;
+	struct pvt_cache cache[PVT_SENSORS_NUM];
+	ktime_t timeout;
+
+	struct pvt_ops *ops;
+
+	const struct pvt_sensor_info *info;
+
+	struct thermal_zone_device *tzd;
+};
+
+int baikal_pvt_create(struct platform_device *pdev, struct pvt_ops *ops,
+		      const struct pvt_sensor_info *info,
+		      const struct hwmon_channel_info * const *hwmon_info);
+
+#endif /* __HWMON_BAIKAL_PVT_H__ */
diff --git a/drivers/hwmon/bm1000-pvt-hwmon.c b/drivers/hwmon/bm1000-pvt-hwmon.c
new file mode 100644
index 00000000000000..5b955da5996972
--- /dev/null
+++ b/drivers/hwmon/bm1000-pvt-hwmon.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2020-2025 BAIKAL ELECTRONICS, JSC
+ *
+ * Authors:
+ *   Maxim Kaurkin <maxim.kaurkin@baikalelectronics.ru>
+ *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
+ *
+ * Baikal BE-M1000 SoC Process, Voltage, Temperature sensor driver.
+ */
+
+#include <linux/firmware/baikal/baikal-smc.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/polynomial.h>
+
+#include "baikal-pvt.h"
+
+#define PVT_READ		0
+#define PVT_WRITE		1
+
+/*
+ * For the sake of the code simplification we created the sensors info table
+ * with the sensor names, activation modes, threshold registers base address
+ * and the thresholds bit fields.
+ */
+static const struct pvt_sensor_info pvt_info[] = {
+	PVT_SENSOR_INFO(0, "CPU Core Temperature", hwmon_temp, TEMP, TTHRES),
+	PVT_SENSOR_INFO(0, "CPU Core Voltage", hwmon_in, VOLT, VTHRES),
+	PVT_SENSOR_INFO(1, "CPU Core Low-Vt", hwmon_in, LVT, LTHRES),
+	PVT_SENSOR_INFO(2, "CPU Core High-Vt", hwmon_in, HVT, HTHRES),
+	PVT_SENSOR_INFO(3, "CPU Core Standard-Vt", hwmon_in, SVT, STHRES),
+};
+
+static const struct hwmon_channel_info * const pvt_channel_info[] = {
+	HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL),
+	HWMON_CHANNEL_INFO(temp,
+			   HWMON_T_INPUT | HWMON_T_TYPE | HWMON_T_LABEL |
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+			   HWMON_T_MIN | HWMON_T_MIN_ALARM |
+			   HWMON_T_MAX | HWMON_T_MAX_ALARM |
+#endif
+			   HWMON_T_OFFSET),
+	HWMON_CHANNEL_INFO(in,
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+			   HWMON_I_MIN | HWMON_I_MIN_ALARM |
+			   HWMON_I_MAX | HWMON_I_MAX_ALARM |
+#endif
+			   HWMON_I_INPUT | HWMON_I_LABEL,
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+			   HWMON_I_MIN | HWMON_I_MIN_ALARM |
+			   HWMON_I_MAX | HWMON_I_MAX_ALARM |
+#endif
+			   HWMON_I_INPUT | HWMON_I_LABEL,
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+			   HWMON_I_MIN | HWMON_I_MIN_ALARM |
+			   HWMON_I_MAX | HWMON_I_MAX_ALARM |
+#endif
+			   HWMON_I_INPUT | HWMON_I_LABEL,
+#if defined(CONFIG_SENSORS_BAIKAL_PVT_ALARMS)
+			   HWMON_I_MIN | HWMON_I_MIN_ALARM |
+			   HWMON_I_MAX | HWMON_I_MAX_ALARM |
+#endif
+			   HWMON_I_INPUT | HWMON_I_LABEL),
+	NULL
+};
+
+static const struct polynomial bm1000_poly_temp_to_N = {
+	.total_divider = 10000,
+	.terms = {
+		{4,   18322, 10000, 10000},
+		{3,    2343, 10000,    10},
+		{2,   87018, 10000,    10},
+		{1,   39269,  1000,     1},
+		{0, 1720400,     1,     1}
+	}
+};
+
+static const struct polynomial bm1000_poly_N_to_temp = {
+	.total_divider = 1,
+	.terms = {
+		{4,  -16743, 1000, 1},
+		{3,   81542, 1000, 1},
+		{2, -182010, 1000, 1},
+		{1,  310200, 1000, 1},
+		{0,  -48380,    1, 1}
+	}
+};
+
+static const struct polynomial bm1000_poly_volt_to_N = {
+	.total_divider = 10,
+	.terms = {
+		{1,  18658, 1000, 1},
+		{0, -11572,    1, 1}
+	}
+};
+
+static const struct polynomial bm1000_poly_N_to_volt = {
+	.total_divider = 10,
+	.terms = {
+		{1,    100000, 18658,     1},
+		{0, 115720000,     1, 18658}
+	}
+};
+
+static const struct baikal_pvt_data {
+	const struct polynomial *temp_to_N;
+	const struct polynomial *N_to_temp;
+	const struct polynomial *volt_to_N;
+	const struct polynomial *N_to_volt;
+} baikal_pvt = {
+	.temp_to_N = &bm1000_poly_temp_to_N,
+	.N_to_temp = &bm1000_poly_N_to_temp,
+	.volt_to_N = &bm1000_poly_volt_to_N,
+	.N_to_volt = &bm1000_poly_N_to_volt
+};
+
+static int baikal_init_pvt(struct pvt_hwmon *pvt)
+{
+	struct platform_device *pdev = to_platform_device(pvt->dev);
+	struct resource *res;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+	pvt->regs = (void __iomem *)res->start;
+
+	return 0;
+}
+
+static u32 baikal_read_pvt(struct pvt_hwmon *pvt, u32 reg)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(BAIKAL_SMC_PVT_CMD, PVT_READ, (unsigned long)pvt->regs,
+		      reg, 0, 0, 0, 0, &res);
+
+	return res.a0;
+}
+
+static int baikal_write_pvt(struct pvt_hwmon *pvt, u32 reg, u32 data)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(BAIKAL_SMC_PVT_CMD, PVT_WRITE, (unsigned long)pvt->regs,
+		      reg, data, 0, 0, 0, &res);
+	return res.a0;
+}
+
+static u32 baikal_to_pvt(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
+			 long val)
+{
+	const struct polynomial *poly = (type == PVT_TEMP) ?
+					baikal_pvt.temp_to_N :
+					baikal_pvt.volt_to_N;
+
+	return polynomial_calc(poly, val);
+}
+
+static long baikal_from_pvt(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
+			    u32 data)
+{
+	const struct polynomial *poly = (type == PVT_TEMP) ?
+					baikal_pvt.N_to_temp :
+					baikal_pvt.N_to_volt;
+
+	return polynomial_calc(poly, data);
+}
+
+static struct pvt_ops baikal_pvt_ops = {
+	.init     = baikal_init_pvt,
+	.read     = baikal_read_pvt,
+	.write    = baikal_write_pvt,
+	.to_pvt   = baikal_to_pvt,
+	.from_pvt = baikal_from_pvt,
+};
+
+static int pvt_probe(struct platform_device *pdev)
+{
+	return baikal_pvt_create(pdev, &baikal_pvt_ops, pvt_info, pvt_channel_info);
+}
+
+static const struct of_device_id pvt_of_match[] = {
+	{ .compatible = "baikal,bm1000-pvt" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pvt_of_match);
+
+static struct platform_driver pvt_driver = {
+	.probe  = pvt_probe,
+	.driver = {
+		.name = "bm1000-pvt",
+		.of_match_table = pvt_of_match
+	}
+};
+module_platform_driver(pvt_driver);
+
+MODULE_AUTHOR("Maxim Kaurkin <maxim.kaurkin@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal BE-M1000 SoC PVT driver");
+MODULE_LICENSE("GPL v2");
-- 
2.42.2



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

* [d-kernel] [PATCH 33/35] hwmon: baikal-pvt: support work on machines with old firmware
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (31 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 32/35] hwmon: add Baikal-M monitoring driver Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 34/35] dw-pcie: refuse to load on Baikal-M with recent firmware Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 35/35] config-aarch64: enable more configs for Baikal-M support Daniil Gnusarev
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

The first firmware versions used PVT channel indices, while
the latest versions have switched to direct addressing. Depending
on the presence of the "pvt_id" property in the DTS, the corresponding
addressing scheme is used.
Also, if the "clock-names" property is missing in the DTS, the value
25000000 / 21 is used instead of determining the operating frequency.
SMC32 calls are also used.

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Do-not-upstream: this is a feature of Baikal-M
---
 drivers/hwmon/baikal-pvt-core.c            |  6 +++---
 drivers/hwmon/baikal-pvt.h                 |  1 +
 drivers/hwmon/bm1000-pvt-hwmon.c           | 11 ++++++++---
 include/linux/firmware/baikal/baikal-smc.h |  2 +-
 4 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/drivers/hwmon/baikal-pvt-core.c b/drivers/hwmon/baikal-pvt-core.c
index 7a649b65768f72..2853a841135917 100644
--- a/drivers/hwmon/baikal-pvt-core.c
+++ b/drivers/hwmon/baikal-pvt-core.c
@@ -556,7 +556,7 @@ static int pvt_write_timeout(struct pvt_hwmon *pvt, long val)
 
 	rate = clk_get_rate(pvt->clks[PVT_CLOCK_REF].clk);
 	if (!rate)
-		return -ENODEV;
+		rate = 25000000 / 21;
 
 	/*
 	 * If alarms are enabled, the requested timeout must be divided
@@ -952,8 +952,8 @@ static int pvt_init_iface(struct pvt_hwmon *pvt)
 
 	rate = clk_get_rate(pvt->clks[PVT_CLOCK_REF].clk);
 	if (!rate) {
-		dev_err(pvt->dev, "Invalid reference clock rate\n");
-		return -ENODEV;
+		dev_warn(pvt->dev, "Invalid reference clock rate, default value is used\n");
+		rate = 25000000 / 21;
 	}
 
 	/*
diff --git a/drivers/hwmon/baikal-pvt.h b/drivers/hwmon/baikal-pvt.h
index ca1c786f872dfe..b419f847e67834 100644
--- a/drivers/hwmon/baikal-pvt.h
+++ b/drivers/hwmon/baikal-pvt.h
@@ -250,6 +250,7 @@ struct pvt_hwmon {
 	struct device *hwmon;
 
 	void __iomem *regs;
+	int pvt_id;
 	int irq;
 
 	struct clk_bulk_data clks[PVT_CLOCK_NUM];
diff --git a/drivers/hwmon/bm1000-pvt-hwmon.c b/drivers/hwmon/bm1000-pvt-hwmon.c
index 5b955da5996972..f355fce876037a 100644
--- a/drivers/hwmon/bm1000-pvt-hwmon.c
+++ b/drivers/hwmon/bm1000-pvt-hwmon.c
@@ -126,6 +126,9 @@ static int baikal_init_pvt(struct pvt_hwmon *pvt)
 		return -ENODEV;
 	pvt->regs = (void __iomem *)res->start;
 
+	pvt->pvt_id = -1;
+	of_property_read_u32(pvt->dev->of_node, "pvt_id", &(pvt->pvt_id));
+
 	return 0;
 }
 
@@ -133,9 +136,9 @@ static u32 baikal_read_pvt(struct pvt_hwmon *pvt, u32 reg)
 {
 	struct arm_smccc_res res;
 
-	arm_smccc_smc(BAIKAL_SMC_PVT_CMD, PVT_READ, (unsigned long)pvt->regs,
+	arm_smccc_smc(BAIKAL_SMC_PVT_CMD, PVT_READ,
+		      (pvt->pvt_id >= 0) ? (unsigned long)pvt->pvt_id : (unsigned long)pvt->regs,
 		      reg, 0, 0, 0, 0, &res);
-
 	return res.a0;
 }
 
@@ -143,7 +146,8 @@ static int baikal_write_pvt(struct pvt_hwmon *pvt, u32 reg, u32 data)
 {
 	struct arm_smccc_res res;
 
-	arm_smccc_smc(BAIKAL_SMC_PVT_CMD, PVT_WRITE, (unsigned long)pvt->regs,
+	arm_smccc_smc(BAIKAL_SMC_PVT_CMD, PVT_WRITE,
+		      (pvt->pvt_id >= 0) ? (unsigned long)pvt->pvt_id : (unsigned long)pvt->regs,
 		      reg, data, 0, 0, 0, &res);
 	return res.a0;
 }
@@ -183,6 +187,7 @@ static int pvt_probe(struct platform_device *pdev)
 
 static const struct of_device_id pvt_of_match[] = {
 	{ .compatible = "baikal,bm1000-pvt" },
+	{ .compatible = "baikal,pvt" },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, pvt_of_match);
diff --git a/include/linux/firmware/baikal/baikal-smc.h b/include/linux/firmware/baikal/baikal-smc.h
index 648b1e9507de48..1ff7b775975238 100644
--- a/include/linux/firmware/baikal/baikal-smc.h
+++ b/include/linux/firmware/baikal/baikal-smc.h
@@ -9,7 +9,7 @@
 #include <linux/arm-smccc.h>
 
 #define BAIKALL_SIP_SMC_FAST_CALL_VAL(func_num) \
-	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
+	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \
 	ARM_SMCCC_OWNER_SIP, (func_num))
 
 #define BAIKAL_SMC_CMU_CMD		BAIKALL_SIP_SMC_FAST_CALL_VAL(0x0000)
-- 
2.42.2



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

* [d-kernel] [PATCH 34/35] dw-pcie: refuse to load on Baikal-M with recent firmware
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (32 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 33/35] hwmon: baikal-pvt: support work on machines with old firmware Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  2026-02-27 10:32 ` [d-kernel] [PATCH 35/35] config-aarch64: enable more configs for Baikal-M support Daniil Gnusarev
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, 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/pcie-designware-plat.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/pci/controller/dwc/pcie-designware-plat.c b/drivers/pci/controller/dwc/pcie-designware-plat.c
index 12f41886c65d1e..dbd7b9375b01cc 100644
--- a/drivers/pci/controller/dwc/pcie-designware-plat.c
+++ b/drivers/pci/controller/dwc/pcie-designware-plat.c
@@ -110,6 +110,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] 36+ messages in thread

* [d-kernel] [PATCH 35/35] config-aarch64: enable more configs for Baikal-M support
  2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
                   ` (33 preceding siblings ...)
  2026-02-27 10:32 ` [d-kernel] [PATCH 34/35] dw-pcie: refuse to load on Baikal-M with recent firmware Daniil Gnusarev
@ 2026-02-27 10:32 ` Daniil Gnusarev
  34 siblings, 0 replies; 36+ messages in thread
From: Daniil Gnusarev @ 2026-02-27 10:32 UTC (permalink / raw)
  To: gnusarevda, devel-kernel

Signed-off-by: Daniil Gnusarev <gnusarevda@basealt.ru>
Do-not-upstream: this is a feature of Baikal-M
---
 config-aarch64 | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/config-aarch64 b/config-aarch64
index 35def2a447fc24..44201d5b178390 100644
--- a/config-aarch64
+++ b/config-aarch64
@@ -1876,3 +1876,18 @@ CONFIG_BT_QCOMSMD=m
 CONFIG_SCSI_UFS_QCOM=m
 CONFIG_SCSI_UFS_ROCKCHIP=m
 CONFIG_BACKLIGHT_QCOM_WLED=m
+CONFIG_ARCH_BAIKAL=y
+CONFIG_SND_HDA_BAIKAL=m
+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_BM1000_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] 36+ messages in thread

end of thread, other threads:[~2026-02-27 10:32 UTC | newest]

Thread overview: 36+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-02-27 10:32 [d-kernel] [PATCH 00/35] Kernel 6.18 with support for the Baikal-M SoC Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 01/35] Baikal Electronics SoC family Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 02/35] clk: Add clock drivers for Baikal BE-M1000 Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 03/35] clk: baikal-m: old firmware: use "cmu-id" if there is no "reg" in devicetree Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 04/35] usb: add support for Baikal USB PHY Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 05/35] usb: dwc3: of-simple: added compatible string for Baikal-M SoC Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 06/35] uart: add support for UART Baikal BE-M1000 Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 07/35] serial: 8250_dw: verify clock rate in dw8250_set_termios Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 08/35] cpufreq-dt: don't load on Baikal-M SoC Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 09/35] net: stmmac: support of Baikal-BE1000 SoCs GMAC Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 10/35] net: fwnode_get_phy_id: consider all compatible strings Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 11/35] drm/panfrost: forcibly set dma-coherent on Baikal-M Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 12/35] drm/panfrost: disable devfreq " Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 13/35] ata: ahci: add support for Baikal BE-M1000 Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 14/35] drm: add Baikal-M SoC video display unit driver Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 15/35] drm: baikal-vdu: driver compatibility with SDK earlier than 5.9 Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 16/35] drm: baikal-vdu: disable backlight driver loading Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 17/35] sound: add support for Baikal BE-M1000 I2S Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 18/35] sound: dwc-i2s: request all IRQs specified in device tree Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 19/35] sound: dwc-i2s: paper over RX overrun warnings on Baikal-M Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 20/35] sound: baikal-i2s: " Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 21/35] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 22/35] dt-bindings: dw-hdmi: added ahb-audio-regshift Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 23/35] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 24/35] dw-hdmi: add flag SNDRV_PCM_INFO_BATCH for audio via hdmi on Baikal-M Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 25/35] bmc: add board management controller driver Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 26/35] pm: disable all sleep states on Baikal-M based boards Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 27/35] sound: hda: add driver for HDA controller on Baikal-M Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 28/35] sound: hda: enable jack detection in polling mode " Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 29/35] input: new driver - serdev-serio Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 30/35] input: serio: add an alias to the sersev-serio driver Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 31/35] input: added TF307 serio PS/2 emulator driver Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 32/35] hwmon: add Baikal-M monitoring driver Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 33/35] hwmon: baikal-pvt: support work on machines with old firmware Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 34/35] dw-pcie: refuse to load on Baikal-M with recent firmware Daniil Gnusarev
2026-02-27 10:32 ` [d-kernel] [PATCH 35/35] config-aarch64: enable more configs for Baikal-M support Daniil Gnusarev

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