ALT Linux kernel packages development
 help / color / mirror / Atom feed
* [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver
@ 2022-12-14 13:18 Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 02/32] cpufreq-dt: don't load on Baikal-M SoC Alexey Sheplyakov
                   ` (30 more replies)
  0 siblings, 31 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:18 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

On Baikal-M SoC clock management unit (CMU) is controled by
the firmware (ARM-TF), since the registers of CMU are accessible
only to the secure world. This drivers is a shim which calls into
the firmware.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
Co-developed-by: Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>
X-feature-Baikal-M
---
 drivers/clk/Makefile              |   1 +
 drivers/clk/baikal-m/Makefile     |   1 +
 drivers/clk/baikal-m/clk-baikal.c | 356 ++++++++++++++++++++++++++++++
 3 files changed, 358 insertions(+)
 create mode 100644 drivers/clk/baikal-m/Makefile
 create mode 100644 drivers/clk/baikal-m/clk-baikal.c

diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index e3ca0d058a25..37615a5ff451 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -83,6 +83,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-m/
 obj-$(CONFIG_CLK_BAIKAL_T1)		+= baikal-t1/
 obj-y					+= bcm/
 obj-$(CONFIG_ARCH_BERLIN)		+= berlin/
diff --git a/drivers/clk/baikal-m/Makefile b/drivers/clk/baikal-m/Makefile
new file mode 100644
index 000000000000..56aa4de4081c
--- /dev/null
+++ b/drivers/clk/baikal-m/Makefile
@@ -0,0 +1 @@
+obj-y += clk-baikal.o
\ No newline at end of file
diff --git a/drivers/clk/baikal-m/clk-baikal.c b/drivers/clk/baikal-m/clk-baikal.c
new file mode 100644
index 000000000000..b3adcb727458
--- /dev/null
+++ b/drivers/clk/baikal-m/clk-baikal.c
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * clk-baikal.c - Baikal-M clock driver.
+ *
+ * Copyright (C) 2015,2016,2020,2021 Baikal Electronics JSC
+ * Authors:
+ *   Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>
+ *   Alexey Sheplyakov <asheplyakov@basealt.ru>
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.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;
+	uint32_t	cmu_id;
+	unsigned int	parent;
+	const char	*name;
+	uint32_t	is_clk_ch;
+};
+
+#define to_baikal_cmu(_hw) container_of(_hw, struct baikal_clk_cmu, hw)
+
+/* Pointer to the place on handling SMC CMU calls in monitor */
+#define BAIKAL_SMC_LCRU_ID	0x82000000
+
+static int baikal_clk_enable(struct clk_hw *hw)
+{
+	struct arm_smccc_res res;
+	struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
+	uint32_t cmd;
+
+	if (pclk->is_clk_ch) {
+		cmd = CMU_CLK_CH_ENABLE;
+	} else {
+		cmd = CMU_PLL_ENABLE;
+	}
+
+	arm_smccc_smc(BAIKAL_SMC_LCRU_ID, pclk->cmu_id, 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->cmu_id,
+			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);
+	uint32_t cmd;
+
+	if (pclk->is_clk_ch) {
+		cmd = CMU_CLK_CH_DISABLE;
+	} else {
+		cmd = CMU_PLL_DISABLE;
+	}
+
+	arm_smccc_smc(BAIKAL_SMC_LCRU_ID, pclk->cmu_id, 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->cmu_id,
+			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);
+	uint32_t cmd;
+
+	if (pclk->is_clk_ch) {
+		cmd = CMU_CLK_CH_IS_ENABLED;
+	} else {
+		cmd = CMU_PLL_IS_ENABLED;
+	}
+
+	arm_smccc_smc(BAIKAL_SMC_LCRU_ID, pclk->cmu_id, 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->cmu_id,
+			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);
+	uint32_t 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;
+	}
+
+	arm_smccc_smc(BAIKAL_SMC_LCRU_ID, pclk->cmu_id, 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->cmu_id,
+			res.a0);
+
+	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);
+	uint32_t 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_LCRU_ID, pclk->cmu_id, 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->cmu_id,
+			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);
+	unsigned long parent;
+	uint32_t cmd;
+
+	if (pclk->is_clk_ch) {
+		cmd = CMU_CLK_CH_ROUND_RATE;
+		parent = pclk->parent;
+	} else {
+		cmd = CMU_PLL_ROUND_RATE;
+		parent = *prate;
+	}
+
+	arm_smccc_smc(BAIKAL_SMC_LCRU_ID, pclk->cmu_id, 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->cmu_id,
+			res.a0);
+
+	return res.a0;
+}
+
+static const struct clk_ops be_clk_pll_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 __init baikal_clk_probe(struct device_node *node)
+{
+	struct clk_init_data init;
+	struct clk_init_data *init_ch;
+	struct baikal_clk_cmu *cmu;
+	struct baikal_clk_cmu **cmu_ch;
+
+	struct clk *clk;
+	struct clk_onecell_data *clk_ch;
+
+	int number, i = 0;
+	u32 rc, index;
+	struct property *prop;
+	const __be32 *p;
+	const char *clk_ch_name;
+	const char *parent_name;
+
+	cmu = kzalloc(sizeof(struct baikal_clk_cmu), GFP_KERNEL);
+	if (!cmu) {
+		pr_err("%s: could not allocate CMU clk\n", __func__);
+		return -ENOMEM;
+	}
+
+	of_property_read_string(node, "clock-output-names", &cmu->name);
+	of_property_read_u32(node, "clock-frequency", &cmu->parent);
+	of_property_read_u32(node, "cmu-id", &cmu->cmu_id);
+
+	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 = &be_clk_pll_ops;
+	init.flags = CLK_IGNORE_UNUSED;
+
+	cmu->hw.init = &init;
+	cmu->is_clk_ch = 0;
+
+	/* Register the clock */
+	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);
+	}
+
+	clk_prepare_enable(clk);
+
+	number = of_property_count_u32_elems(node, "clock-indices");
+
+	if (number > 0) {
+		clk_ch = kmalloc(sizeof(struct clk_onecell_data), GFP_KERNEL);
+ 		if (!clk_ch) {
+			pr_err("%s: could not allocate CMU clk channel\n", __func__);
+			return -ENOMEM;
+ 		}
+
+		/* Get the last index to find out max number of children*/
+		of_property_for_each_u32(node, "clock-indices", prop, p, index) {
+			;
+		}
+
+		clk_ch->clks = kcalloc(index + 1, sizeof(struct clk *), GFP_KERNEL);
+		clk_ch->clk_num = index + 1;
+		cmu_ch = kcalloc((index + 1), sizeof(struct baikal_clk_cmu *), GFP_KERNEL);
+		if (!cmu_ch) {
+			kfree(clk_ch);
+			return -ENOMEM;
+		}
+		init_ch = kcalloc((number + 1), sizeof(struct clk_init_data), GFP_KERNEL);
+		if (!init_ch) {
+			pr_err("%s: could not allocate CMU init structure \n", __func__);
+			kfree(cmu_ch);
+			kfree(clk_ch);
+			return -ENOMEM;
+		}
+
+		of_property_for_each_u32(node, "clock-indices", prop, p, index) {
+			of_property_read_string_index(node, "clock-names",
+							i, &clk_ch_name);
+			pr_info("%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 = &be_clk_pll_ops;
+			init_ch[i].flags = CLK_IGNORE_UNUSED;
+
+			cmu_ch[index] = kzalloc(sizeof(struct baikal_clk_cmu), GFP_KERNEL);
+			if (!cmu_ch[index]) {
+				pr_err("%s: could not allocate baikal_clk_cmu structure\n", __func__);
+				return -ENOMEM;
+			}
+			cmu_ch[index]->name = clk_ch_name;
+			cmu_ch[index]->cmu_id = index;
+			cmu_ch[index]->parent = cmu->cmu_id;
+			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);
+			}
+
+			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);
+			}
+
+			clk_prepare_enable(clk_ch->clks[index]);
+			i++;
+		}
+
+		return of_clk_add_provider(node, of_clk_src_onecell_get, clk_ch);
+	}
+
+	return of_clk_add_provider(node, of_clk_src_simple_get, clk);
+}
+
+static void __init baikal_clk_init(struct device_node *np)
+{
+	int err;
+	err = baikal_clk_probe(np);
+	if (err) {
+		panic("%s: failed to probe clock %pOF: %d\n", __func__, np, err);
+	} else {
+		pr_info("%s: successfully probed %pOF\n", __func__, np);
+	}
+}
+CLK_OF_DECLARE_DRIVER(baikal_cmu, "baikal,cmu", baikal_clk_init);
+CLK_OF_DECLARE_DRIVER(bm1000_cmu, "baikal,bm1000-cmu", baikal_clk_init);
-- 
2.33.5



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

* [d-kernel] [PATCH 02/32] cpufreq-dt: don't load on Baikal-M SoC
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
@ 2022-12-14 13:18 ` Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 03/32] serial: 8250_dw: verify clock rate in dw8250_set_termios Alexey Sheplyakov
                   ` (29 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:18 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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 6ac3800db450..9b036ec1128b 100644
--- a/drivers/cpufreq/cpufreq-dt-platdev.c
+++ b/drivers/cpufreq/cpufreq-dt-platdev.c
@@ -105,6 +105,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.33.5



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

* [d-kernel] [PATCH 03/32] serial: 8250_dw: verify clock rate in dw8250_set_termios
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 02/32] cpufreq-dt: don't load on Baikal-M SoC Alexey Sheplyakov
@ 2022-12-14 13:18 ` Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 04/32] usb: dwc3: of-simple: added compatible string for Baikal-M SoC Alexey Sheplyakov
                   ` (28 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:18 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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

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

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

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

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

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

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



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

* [d-kernel] [PATCH 04/32] usb: dwc3: of-simple: added compatible string for Baikal-M SoC
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 02/32] cpufreq-dt: don't load on Baikal-M SoC Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 03/32] serial: 8250_dw: verify clock rate in dw8250_set_termios Alexey Sheplyakov
@ 2022-12-14 13:18 ` Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 05/32] dw-pcie: refuse to load on Baikal-M with recent firmware Alexey Sheplyakov
                   ` (27 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:18 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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 71fd620c5161..5ddb5a38e5a9 100644
--- a/drivers/usb/dwc3/dwc3-of-simple.c
+++ b/drivers/usb/dwc3/dwc3-of-simple.c
@@ -177,6 +177,9 @@ static const struct of_device_id of_dwc3_simple_match[] = {
 	{ .compatible = "allwinner,sun50i-h6-dwc3" },
 	{ .compatible = "hisilicon,hi3670-dwc3" },
 	{ .compatible = "intel,keembay-dwc3" },
+	{ .compatible = "baikal,bm1000-dwc3" },
+	{ .compatible = "baikal,baikal-dwc3" },
+	{ .compatible = "be,baikal-dwc3" },
 	{ /* Sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, of_dwc3_simple_match);
-- 
2.33.5



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

* [d-kernel] [PATCH 05/32] dw-pcie: refuse to load on Baikal-M with recent firmware
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (2 preceding siblings ...)
  2022-12-14 13:18 ` [d-kernel] [PATCH 04/32] usb: dwc3: of-simple: added compatible string for Baikal-M SoC Alexey Sheplyakov
@ 2022-12-14 13:18 ` Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 06/32] arm64: Enable armv8 based Baikal-M SoC support Alexey Sheplyakov
                   ` (26 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:18 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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 1fcfb840f238..4041f330d082 100644
--- a/drivers/pci/controller/dwc/pcie-designware-plat.c
+++ b/drivers/pci/controller/dwc/pcie-designware-plat.c
@@ -112,6 +112,11 @@ static int dw_plat_pcie_probe(struct platform_device *pdev)
 	const struct dw_plat_pcie_of_data *data;
 	enum dw_pcie_device_mode mode;
 
+	if (of_device_is_compatible(dev->of_node, "baikal,bm1000-pcie")) {
+		dev_err(dev, "refusing to load on Baikal-M with SDK-M 5.{4,5}\n");
+		return -ENODEV;
+	}
+
 	data = of_device_get_match_data(dev);
 	if (!data)
 		return -EINVAL;
-- 
2.33.5



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

* [d-kernel] [PATCH 06/32] arm64: Enable armv8 based Baikal-M SoC support
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (3 preceding siblings ...)
  2022-12-14 13:18 ` [d-kernel] [PATCH 05/32] dw-pcie: refuse to load on Baikal-M with recent firmware Alexey Sheplyakov
@ 2022-12-14 13:18 ` Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 07/32] efi-rtc: avoid calling efi.get_time on Baikal-M SoC Alexey Sheplyakov
                   ` (25 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:18 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

This patch adds Kconfig entries necessary to enable
Baikal Electronics' Baikal-M (also known as BE-M1000) SoC
support. Also it enables pinctrl, timers and GPIO drivers.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 arch/arm64/Kconfig.platforms | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index 76580b932e44..6436000d0472 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -33,6 +33,19 @@ config ARCH_APPLE
 	  This enables support for Apple's in-house ARM SoC family, starting
 	  with the Apple M1.
 
+config ARCH_BAIKAL
+	bool "Baikal Electronics Baikal-M SoC Family"
+	select GPIOLIB
+	select PINCTRL
+	select OF_GPIO
+	select GPIO_SYSFS
+	select GPIO_DWAPB
+	select GPIO_GENERIC
+	select DW_APB_TIMER
+	select DW_APB_TIMER_OF
+	help
+	  This enables support for Baikal Electronics Baikal-M SoC Family
+
 menuconfig ARCH_BCM
 	bool "Broadcom SoC Support"
 
-- 
2.33.5



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

* [d-kernel] [PATCH 07/32] efi-rtc: avoid calling efi.get_time on Baikal-M SoC
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (4 preceding siblings ...)
  2022-12-14 13:18 ` [d-kernel] [PATCH 06/32] arm64: Enable armv8 based Baikal-M SoC support Alexey Sheplyakov
@ 2022-12-14 13:18 ` Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 08/32] arm64-stub: fixed secondary cores boot " Alexey Sheplyakov
                   ` (24 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:18 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

Old versions of Baikal-M UEFI (before SDK-M 4.4) do NOT provide
get_time at the runtime (not even as a stub), hence calling it
results in an Oops.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-DONTUPSTREAM
X-legacy
X-feature-Baikal-M
---
 drivers/rtc/rtc-efi.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/rtc/rtc-efi.c b/drivers/rtc/rtc-efi.c
index 11850c2880ad..84fcd6ff092e 100644
--- a/drivers/rtc/rtc-efi.c
+++ b/drivers/rtc/rtc-efi.c
@@ -17,6 +17,7 @@
 #include <linux/platform_device.h>
 #include <linux/rtc.h>
 #include <linux/efi.h>
+#include <linux/of.h>
 
 #define EFI_ISDST (EFI_TIME_ADJUST_DAYLIGHT|EFI_TIME_IN_DAYLIGHT)
 
@@ -257,6 +258,15 @@ static int __init efi_rtc_probe(struct platform_device *dev)
 	efi_time_t eft;
 	efi_time_cap_t cap;
 
+#ifdef CONFIG_OF
+	/* efi.get_time is not always safe to call since some UEFI
+	 * implementations do not privde get_time at runtime. */
+	if (of_device_is_compatible(of_root, "baikal,baikal-m") ||
+			of_device_is_compatible(of_root, "baikal,bm1000")) {
+		dev_err(&dev->dev, "Baikal-M UEFI has no get_time\n");
+		return -ENODEV;
+	}
+#endif
 	/* First check if the RTC is usable */
 	if (efi.get_time(&eft, &cap) != EFI_SUCCESS)
 		return -ENODEV;
-- 
2.33.5



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

* [d-kernel] [PATCH 08/32] arm64-stub: fixed secondary cores boot on Baikal-M SoC
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (5 preceding siblings ...)
  2022-12-14 13:18 ` [d-kernel] [PATCH 07/32] efi-rtc: avoid calling efi.get_time on Baikal-M SoC Alexey Sheplyakov
@ 2022-12-14 13:18 ` Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 09/32] pm: disable all sleep states on Baikal-M based boards Alexey Sheplyakov
                   ` (23 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:18 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

Old versions of Baikal-M firmware (ARM-TF) deny execution attempts
outside of the (physical) address ranges
[0x80000000, 0x8FFFFFFF] and [0xA0000000, 0xBFFFFFFF]
Thus PSCI calls to boot secondary cores fail unless the kernel image
resides in one of these address ranges. However UEFI PE/COFF loader
puts the kernel image into the forbidden range. Since the alignment
is good enough EFI stub does not try to relocate the kernel.
As a result secondary CPUs fail to boot.

Relocation to a random address is not going to work either.
Therefore automatically disable kaslr on "known bad" systems (for
now only Baikal-M) and forcibly relocate the kernel to a low(er)
address.

This patch is necessary only for old firmware (pre SDK-M 5.1) and
prevents kalsr from working on Baikal-M systems.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-DONTUPSTREAM
X-legacy
X-feature-Baikal-M
---
 drivers/firmware/efi/libstub/arm64-stub.c | 62 ++++++++++++++++++++++-
 1 file changed, 61 insertions(+), 1 deletion(-)

diff --git a/drivers/firmware/efi/libstub/arm64-stub.c b/drivers/firmware/efi/libstub/arm64-stub.c
index f9de5217ea65..665a59a287dc 100644
--- a/drivers/firmware/efi/libstub/arm64-stub.c
+++ b/drivers/firmware/efi/libstub/arm64-stub.c
@@ -11,6 +11,7 @@
 #include <asm/efi.h>
 #include <asm/memory.h>
 #include <asm/sections.h>
+#include <linux/libfdt.h>
 #include <asm/sysreg.h>
 
 #include "efistub.h"
@@ -57,6 +58,32 @@ efi_status_t check_platform_features(void)
 	return EFI_SUCCESS;
 }
 
+static const char* machines_need_low_alloc[] = {
+	"baikal,baikal-m",
+	"baikal,bm1000",
+};
+
+static bool need_low_alloc(void) {
+	size_t i;
+	const void *fdt;
+	const char *match;
+
+	fdt = get_efi_config_table(DEVICE_TREE_GUID);
+	if (!fdt) {
+		efi_info("failed to retrive FDT from EFI\n");
+		return false;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(machines_need_low_alloc); i++) {
+		match = machines_need_low_alloc[i];
+		if (fdt_node_check_compatible(fdt, 0, match) == 0) {
+			efi_info("machine %s: forcing kernel relocation to low address\n", match);
+			return true;
+		}
+	}
+	return false;
+}
+
 /*
  * Distro versions of GRUB may ignore the BSS allocation entirely (i.e., fail
  * to provide space, and fail to zero it). Check for this condition by double
@@ -93,6 +120,18 @@ static bool check_image_region(u64 base, u64 size)
 	return ret;
 }
 
+static inline efi_status_t efi_low_alloc(unsigned long size, unsigned long align,
+					 unsigned long *addr)
+{
+	/*
+	 * Don't allocate at 0x0. It will confuse code that
+	 * checks pointers against NULL. Skip the first 8
+	 * bytes so we start at a nice even number.
+	 */
+	return efi_low_alloc_above(size, align, addr, 0x8);
+}
+
+
 efi_status_t handle_kernel_image(unsigned long *image_addr,
 				 unsigned long *image_size,
 				 unsigned long *reserve_addr,
@@ -114,12 +153,21 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
 	 */
 	u64 min_kimg_align = efi_nokaslr ? MIN_KIMG_ALIGN : EFI_KIMG_ALIGN;
 
+	bool force_low_reloc = need_low_alloc();
+	if (force_low_reloc) {
+		if (!efi_nokaslr) {
+			efi_info("booting on a broken firmware, KASLR will be disabled\n");
+			efi_nokaslr = true;
+		}
+	}
+
 	if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
 		efi_guid_t li_fixed_proto = LINUX_EFI_LOADED_IMAGE_FIXED_GUID;
 		void *p;
 
 		if (efi_nokaslr) {
-			efi_info("KASLR disabled on kernel command line\n");
+			if (!force_low_reloc)
+				efi_info("KASLR disabled on kernel command line\n");
 		} else if (efi_bs_call(handle_protocol, image_handle,
 				       &li_fixed_proto, &p) == EFI_SUCCESS) {
 			efi_info("Image placement fixed by loader\n");
@@ -161,6 +209,15 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
 		status = EFI_OUT_OF_RESOURCES;
 	}
 
+	if (force_low_reloc) {
+		status = efi_low_alloc(*reserve_size,
+				       min_kimg_align,
+				       reserve_addr);
+		if (status != EFI_SUCCESS) {
+			efi_err("Failed to relocate kernel, expect secondary CPUs boot failure\n");
+		}
+	}
+
 	if (status != EFI_SUCCESS) {
 		if (!check_image_region((u64)_text, kernel_memsize)) {
 			efi_err("FIRMWARE BUG: Image BSS overlaps adjacent EFI memory region\n");
@@ -185,6 +242,9 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
 	}
 
 	*image_addr = *reserve_addr;
+	if (efi_nokaslr) {
+		efi_info("relocating kernel to 0x%lx\n", *image_addr);
+	}
 	memcpy((void *)*image_addr, _text, kernel_size);
 
 	return EFI_SUCCESS;
-- 
2.33.5



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

* [d-kernel] [PATCH 09/32] pm: disable all sleep states on Baikal-M based boards
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (6 preceding siblings ...)
  2022-12-14 13:18 ` [d-kernel] [PATCH 08/32] arm64-stub: fixed secondary cores boot " Alexey Sheplyakov
@ 2022-12-14 13:18 ` Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 10/32] net: fwnode_get_phy_id: consider all compatible strings Alexey Sheplyakov
                   ` (22 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:18 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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 fa3bf161d13f..1a48542ad680 100644
--- a/kernel/power/suspend.c
+++ b/kernel/power/suspend.c
@@ -30,6 +30,7 @@
 #include <trace/events/power.h>
 #include <linux/compiler.h>
 #include <linux/moduleparam.h>
+#include <linux/of.h>
 
 #include "power.h"
 
@@ -243,6 +244,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.33.5



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

* [d-kernel] [PATCH 10/32] net: fwnode_get_phy_id: consider all compatible strings
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (7 preceding siblings ...)
  2022-12-14 13:18 ` [d-kernel] [PATCH 09/32] pm: disable all sleep states on Baikal-M based boards Alexey Sheplyakov
@ 2022-12-14 13:18 ` Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 11/32] net: stmmac: inital support of Baikal-T1/M SoCs GMAC Alexey Sheplyakov
                   ` (21 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:18 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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 8cff61dbc4b5..2431a320c5ee 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -875,18 +875,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.33.5



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

* [d-kernel] [PATCH 11/32] net: stmmac: inital support of Baikal-T1/M SoCs GMAC
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (8 preceding siblings ...)
  2022-12-14 13:18 ` [d-kernel] [PATCH 10/32] net: fwnode_get_phy_id: consider all compatible strings Alexey Sheplyakov
@ 2022-12-14 13:18 ` Alexey Sheplyakov
  2022-12-14 13:18 ` [d-kernel] [PATCH 12/32] dt-bindings: dwmac: Add bindings for Baikal-T1/M SoCs Alexey Sheplyakov
                   ` (20 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:18 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

The gigabit Ethernet controller available in Baikal-T1 and Baikal-M
SoCs is a Synopsys DesignWare MAC IP core, already supported by
the stmmac driver.

This patch implements some SoC specific operations (DMA reset and
speed fixup) necessary (but in general not sufficient) for
Baikal-T1/M variants.

Note that this driver does NOT cover all the IP blocks and platform
setup peculiarities. It's known to work on some Baikal-T1 boards
(including BFK3.1 reference board) and some Baikal-M based boards:
(TF307 revision D, LGP-16, AQBM1000), however it might or might not
work with other boards.

Changes since v2:

* Clearly explained the status of the driver (initial support),
  mentioned the boards it known to work with.
* Increased timeouts in baikal_dma_reset so they are enough for
  many PHYs, explained why such timeouts are necessary.

Changes since v1:

* The code compiles with -Werror

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
Co-developed-by: Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
Co-developed-by: Vasiliy Vinogradov <v.vinogradov@aq.ru>
Tested-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 drivers/net/ethernet/stmicro/stmmac/Kconfig   |  11 +
 drivers/net/ethernet/stmicro/stmmac/Makefile  |   1 +
 .../ethernet/stmicro/stmmac/dwmac-baikal.c    | 215 ++++++++++++++++++
 .../ethernet/stmicro/stmmac/dwmac1000_core.c  |   1 +
 .../ethernet/stmicro/stmmac/dwmac1000_dma.c   |  46 ++--
 .../ethernet/stmicro/stmmac/dwmac1000_dma.h   |  26 +++
 .../net/ethernet/stmicro/stmmac/dwmac_lib.c   |   8 +
 7 files changed, 290 insertions(+), 18 deletions(-)
 create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c
 create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h

diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig
index 31ff35174034..569f000e1fcc 100644
--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
@@ -66,6 +66,17 @@ config DWMAC_ANARION
 
 	  This selects the Anarion SoC glue layer support for the stmmac driver.
 
+config DWMAC_BAIKAL
+	tristate "Baikal Electronics GMAC support"
+	default MIPS_BAIKAL_T1
+	depends on OF && (MIPS || ARM64 || COMPILE_TEST)
+	help
+	  Support for gigabit ethernet controller on Baikal Electronics SoCs.
+
+	  This selects the Baikal Electronics SoCs glue layer support for
+	  the stmmac driver. This driver is used for Baikal-T1 and Baikal-M
+	  SoCs gigabit ethernet controller.
+
 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 d4e12e9ace4f..ad138062e199 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 000000000000..9def4ea7fb3b
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Baikal-T1/M SoCs DWMAC glue layer
+ *
+ * Copyright (C) 2015,2016,2021 Baikal Electronics JSC
+ * Copyright (C) 2020-2022 BaseALT Ltd
+ * Authors: Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
+ *          Alexey Sheplyakov <asheplyakov@basealt.ru>
+ */
+
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include "stmmac.h"
+#include "stmmac_platform.h"
+#include "common.h"
+#include "dwmac_dma.h"
+#include "dwmac1000_dma.h"
+
+#define MAC_GPIO	0x00e0	/* GPIO register */
+#define MAC_GPIO_GPO	BIT(8)	/* Output port */
+
+struct baikal_dwmac {
+	struct device	*dev;
+	struct clk	*tx2_clk;
+};
+
+static int baikal_dwmac_dma_reset(void __iomem *ioaddr)
+{
+	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.
+	 * TODO: read PHY post-reset delay from the device tree.
+	 */
+	usleep_range(100000, 150000);
+
+	return readl_poll_timeout(ioaddr + DMA_BUS_MODE, value,
+				  !(value & DMA_BUS_MODE_SFT_RESET),
+				  10000, 1000000);
+}
+
+static const struct stmmac_dma_ops baikal_dwmac_dma_ops = {
+	.reset = baikal_dwmac_dma_reset,
+	.init = dwmac1000_dma_init,
+	.init_rx_chan = dwmac1000_dma_init_rx,
+	.init_tx_chan = dwmac1000_dma_init_tx,
+	.axi = dwmac1000_dma_axi,
+	.dump_regs = dwmac1000_dump_dma_regs,
+	.dma_rx_mode = dwmac1000_dma_operation_mode_rx,
+	.dma_tx_mode = dwmac1000_dma_operation_mode_tx,
+	.enable_dma_transmission = dwmac_enable_dma_transmission,
+	.enable_dma_irq = dwmac_enable_dma_irq,
+	.disable_dma_irq = dwmac_disable_dma_irq,
+	.start_tx = dwmac_dma_start_tx,
+	.stop_tx = dwmac_dma_stop_tx,
+	.start_rx = dwmac_dma_start_rx,
+	.stop_rx = dwmac_dma_stop_rx,
+	.dma_interrupt = dwmac_dma_interrupt,
+	.get_hw_feature = dwmac1000_get_hw_feature,
+	.rx_watchdog = dwmac1000_rx_watchdog
+};
+
+static struct mac_device_info *baikal_dwmac_setup(void *ppriv)
+{
+	struct mac_device_info *mac;
+	struct stmmac_priv *priv = ppriv;
+	int ret;
+	u32 value;
+
+	mac = devm_kzalloc(priv->device, sizeof(*mac), GFP_KERNEL);
+	if (!mac)
+		return NULL;
+
+	/* Clear PHY reset */
+	value = readl(priv->ioaddr + MAC_GPIO);
+	value |= MAC_GPIO_GPO;
+	writel(value, priv->ioaddr + MAC_GPIO);
+
+	mac->dma = &baikal_dwmac_dma_ops;
+	priv->hw = mac;
+	ret = dwmac1000_setup(priv);
+	if (ret) {
+		dev_err(priv->device, "dwmac1000_setup: error %d", ret);
+		return NULL;
+	}
+
+	return mac;
+}
+
+static void baikal_dwmac_fix_mac_speed(void *priv, unsigned int speed)
+{
+	struct baikal_dwmac *dwmac = priv;
+	unsigned long tx2_clk_freq;
+
+	switch (speed) {
+	case SPEED_1000:
+		tx2_clk_freq = 250000000;
+		break;
+	case SPEED_100:
+		tx2_clk_freq = 50000000;
+		break;
+	case SPEED_10:
+		tx2_clk_freq = 5000000;
+		break;
+	default:
+		dev_warn(dwmac->dev, "invalid speed: %u\n", speed);
+		return;
+	}
+	dev_dbg(dwmac->dev, "speed %u, setting TX2 clock frequency to %lu\n",
+		speed, tx2_clk_freq);
+	clk_set_rate(dwmac->tx2_clk, tx2_clk_freq);
+}
+
+static int dwmac_baikal_probe(struct platform_device *pdev)
+{
+	struct plat_stmmacenet_data *plat_dat;
+	struct stmmac_resources stmmac_res;
+	struct baikal_dwmac *dwmac;
+	int ret;
+
+	dwmac = devm_kzalloc(&pdev->dev, sizeof(*dwmac), GFP_KERNEL);
+	if (!dwmac)
+		return -ENOMEM;
+
+	ret = stmmac_get_platform_resources(pdev, &stmmac_res);
+	if (ret)
+		return ret;
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	if (ret) {
+		dev_err(&pdev->dev, "no suitable DMA available\n");
+		return ret;
+	}
+
+	plat_dat = stmmac_probe_config_dt(pdev, stmmac_res.mac);
+	if (IS_ERR(plat_dat)) {
+		dev_err(&pdev->dev, "dt configuration failed\n");
+		return PTR_ERR(plat_dat);
+	}
+
+	dwmac->dev = &pdev->dev;
+	dwmac->tx2_clk = devm_clk_get_optional(dwmac->dev, "tx2_clk");
+	if (IS_ERR(dwmac->tx2_clk)) {
+		ret = PTR_ERR(dwmac->tx2_clk);
+		dev_err(&pdev->dev, "couldn't get TX2 clock: %d\n", ret);
+		goto err_remove_config_dt;
+	}
+
+	if (dwmac->tx2_clk)
+		plat_dat->fix_mac_speed = baikal_dwmac_fix_mac_speed;
+	plat_dat->bsp_priv = dwmac;
+	plat_dat->has_gmac = 1;
+	plat_dat->enh_desc = 1;
+	plat_dat->tx_coe = 1;
+	plat_dat->rx_coe = 1;
+	plat_dat->clk_csr = 3;
+	plat_dat->setup = baikal_dwmac_setup;
+
+	ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
+	if (ret)
+		goto err_remove_config_dt;
+
+	return 0;
+
+err_remove_config_dt:
+	stmmac_remove_config_dt(pdev, plat_dat);
+	return ret;
+}
+
+static const struct of_device_id dwmac_baikal_match[] = {
+	{ .compatible = "baikal,bm1000-gmac" },
+	{ .compatible = "baikal,dwmac" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, dwmac_baikal_match);
+
+static struct platform_driver dwmac_baikal_driver = {
+	.probe	= dwmac_baikal_probe,
+	.remove	= stmmac_pltfr_remove,
+	.driver	= {
+		.name = "baikal-dwmac",
+		.pm = &stmmac_pltfr_pm_ops,
+		.of_match_table = of_match_ptr(dwmac_baikal_match)
+	}
+};
+module_platform_driver(dwmac_baikal_driver);
+
+MODULE_DESCRIPTION("Baikal-T1/M DWMAC 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 0e00dd83d027..d02c20eaba86 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
@@ -554,3 +554,4 @@ int dwmac1000_setup(struct stmmac_priv *priv)
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(dwmac1000_setup);
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
index f5581db0ba9b..1782a65cc9af 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c
@@ -15,8 +15,9 @@
 #include <asm/io.h>
 #include "dwmac1000.h"
 #include "dwmac_dma.h"
+#include "dwmac1000_dma.h"
 
-static void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi)
+void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi)
 {
 	u32 value = readl(ioaddr + DMA_AXI_BUS_MODE);
 	int i;
@@ -69,9 +70,10 @@ static void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi)
 
 	writel(value, ioaddr + DMA_AXI_BUS_MODE);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dma_axi);
 
-static void dwmac1000_dma_init(void __iomem *ioaddr,
-			       struct stmmac_dma_cfg *dma_cfg, int atds)
+void dwmac1000_dma_init(void __iomem *ioaddr,
+			struct stmmac_dma_cfg *dma_cfg, int atds)
 {
 	u32 value = readl(ioaddr + DMA_BUS_MODE);
 	int txpbl = dma_cfg->txpbl ?: dma_cfg->pbl;
@@ -109,22 +111,25 @@ static void dwmac1000_dma_init(void __iomem *ioaddr,
 	/* Mask interrupts by writing to CSR7 */
 	writel(DMA_INTR_DEFAULT_MASK, ioaddr + DMA_INTR_ENA);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dma_init);
 
-static void dwmac1000_dma_init_rx(void __iomem *ioaddr,
-				  struct stmmac_dma_cfg *dma_cfg,
-				  dma_addr_t dma_rx_phy, u32 chan)
+void dwmac1000_dma_init_rx(void __iomem *ioaddr,
+			   struct stmmac_dma_cfg *dma_cfg,
+			   dma_addr_t dma_rx_phy, u32 chan)
 {
 	/* RX descriptor base address list must be written into DMA CSR3 */
 	writel(lower_32_bits(dma_rx_phy), ioaddr + DMA_RCV_BASE_ADDR);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dma_init_rx);
 
-static void dwmac1000_dma_init_tx(void __iomem *ioaddr,
-				  struct stmmac_dma_cfg *dma_cfg,
-				  dma_addr_t dma_tx_phy, u32 chan)
+void dwmac1000_dma_init_tx(void __iomem *ioaddr,
+			   struct stmmac_dma_cfg *dma_cfg,
+			   dma_addr_t dma_tx_phy, u32 chan)
 {
 	/* TX descriptor base address list must be written into DMA CSR4 */
 	writel(lower_32_bits(dma_tx_phy), ioaddr + DMA_TX_BASE_ADDR);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dma_init_tx);
 
 static u32 dwmac1000_configure_fc(u32 csr6, int rxfifosz)
 {
@@ -147,8 +152,8 @@ static u32 dwmac1000_configure_fc(u32 csr6, int rxfifosz)
 	return csr6;
 }
 
-static void dwmac1000_dma_operation_mode_rx(void __iomem *ioaddr, int mode,
-					    u32 channel, int fifosz, u8 qmode)
+void dwmac1000_dma_operation_mode_rx(void __iomem *ioaddr, int mode,
+				     u32 channel, int fifosz, u8 qmode)
 {
 	u32 csr6 = readl(ioaddr + DMA_CONTROL);
 
@@ -174,9 +179,10 @@ static void dwmac1000_dma_operation_mode_rx(void __iomem *ioaddr, int mode,
 
 	writel(csr6, ioaddr + DMA_CONTROL);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dma_operation_mode_rx);
 
-static void dwmac1000_dma_operation_mode_tx(void __iomem *ioaddr, int mode,
-					    u32 channel, int fifosz, u8 qmode)
+void dwmac1000_dma_operation_mode_tx(void __iomem *ioaddr, int mode,
+				     u32 channel, int fifosz, u8 qmode)
 {
 	u32 csr6 = readl(ioaddr + DMA_CONTROL);
 
@@ -207,8 +213,9 @@ static void dwmac1000_dma_operation_mode_tx(void __iomem *ioaddr, int mode,
 
 	writel(csr6, ioaddr + DMA_CONTROL);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dma_operation_mode_tx);
 
-static void dwmac1000_dump_dma_regs(void __iomem *ioaddr, u32 *reg_space)
+void dwmac1000_dump_dma_regs(void __iomem *ioaddr, u32 *reg_space)
 {
 	int i;
 
@@ -217,9 +224,10 @@ static void dwmac1000_dump_dma_regs(void __iomem *ioaddr, u32 *reg_space)
 			reg_space[DMA_BUS_MODE / 4 + i] =
 				readl(ioaddr + DMA_BUS_MODE + i * 4);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_dump_dma_regs);
 
-static int dwmac1000_get_hw_feature(void __iomem *ioaddr,
-				    struct dma_features *dma_cap)
+int dwmac1000_get_hw_feature(void __iomem *ioaddr,
+			     struct dma_features *dma_cap)
 {
 	u32 hw_cap = readl(ioaddr + DMA_HW_FEATURE);
 
@@ -262,12 +270,14 @@ static int dwmac1000_get_hw_feature(void __iomem *ioaddr,
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(dwmac1000_get_hw_feature);
 
-static void dwmac1000_rx_watchdog(void __iomem *ioaddr, u32 riwt,
-				  u32 queue)
+void dwmac1000_rx_watchdog(void __iomem *ioaddr, u32 riwt,
+			   u32 queue)
 {
 	writel(riwt, ioaddr + DMA_RX_WATCHDOG);
 }
+EXPORT_SYMBOL_GPL(dwmac1000_rx_watchdog);
 
 const struct stmmac_dma_ops dwmac1000_dma_ops = {
 	.reset = dwmac_dma_reset,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h
new file mode 100644
index 000000000000..b254a0734447
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __DWMAC1000_DMA_H__
+#define __DWMAC1000_DMA_H__
+#include "dwmac1000.h"
+
+void dwmac1000_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi);
+void dwmac1000_dma_init(void __iomem *ioaddr,
+			struct stmmac_dma_cfg *dma_cfg, int atds);
+void dwmac1000_dma_init_rx(void __iomem *ioaddr,
+			   struct stmmac_dma_cfg *dma_cfg,
+			   dma_addr_t dma_rx_phy, u32 chan);
+void dwmac1000_dma_init_tx(void __iomem *ioaddr,
+			   struct stmmac_dma_cfg *dma_cfg,
+			   dma_addr_t dma_tx_phy, u32 chan);
+void dwmac1000_dma_operation_mode_rx(void __iomem *ioaddr, int mode,
+				     u32 channel, int fifosz, u8 qmode);
+void dwmac1000_dma_operation_mode_tx(void __iomem *ioaddr, int mode,
+				     u32 channel, int fifosz, u8 qmode);
+void dwmac1000_dump_dma_regs(void __iomem *ioaddr, u32 *reg_space);
+
+int  dwmac1000_get_hw_feature(void __iomem *ioaddr,
+			      struct dma_features *dma_cap);
+
+void dwmac1000_rx_watchdog(void __iomem *ioaddr, u32 riwt, u32 number_chan);
+#endif /* __DWMAC1000_DMA_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
index 9b6138b11776..d54825484f54 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
@@ -31,6 +31,7 @@ void dwmac_enable_dma_transmission(void __iomem *ioaddr)
 {
 	writel(1, ioaddr + DMA_XMT_POLL_DEMAND);
 }
+EXPORT_SYMBOL_GPL(dwmac_enable_dma_transmission);
 
 void dwmac_enable_dma_irq(void __iomem *ioaddr, u32 chan, bool rx, bool tx)
 {
@@ -43,6 +44,7 @@ void dwmac_enable_dma_irq(void __iomem *ioaddr, u32 chan, bool rx, bool tx)
 
 	writel(value, ioaddr + DMA_INTR_ENA);
 }
+EXPORT_SYMBOL_GPL(dwmac_enable_dma_irq);
 
 void dwmac_disable_dma_irq(void __iomem *ioaddr, u32 chan, bool rx, bool tx)
 {
@@ -55,6 +57,7 @@ void dwmac_disable_dma_irq(void __iomem *ioaddr, u32 chan, bool rx, bool tx)
 
 	writel(value, ioaddr + DMA_INTR_ENA);
 }
+EXPORT_SYMBOL_GPL(dwmac_disable_dma_irq);
 
 void dwmac_dma_start_tx(void __iomem *ioaddr, u32 chan)
 {
@@ -62,6 +65,7 @@ void dwmac_dma_start_tx(void __iomem *ioaddr, u32 chan)
 	value |= DMA_CONTROL_ST;
 	writel(value, ioaddr + DMA_CONTROL);
 }
+EXPORT_SYMBOL_GPL(dwmac_dma_start_tx);
 
 void dwmac_dma_stop_tx(void __iomem *ioaddr, u32 chan)
 {
@@ -69,6 +73,7 @@ void dwmac_dma_stop_tx(void __iomem *ioaddr, u32 chan)
 	value &= ~DMA_CONTROL_ST;
 	writel(value, ioaddr + DMA_CONTROL);
 }
+EXPORT_SYMBOL_GPL(dwmac_dma_stop_tx);
 
 void dwmac_dma_start_rx(void __iomem *ioaddr, u32 chan)
 {
@@ -76,6 +81,7 @@ void dwmac_dma_start_rx(void __iomem *ioaddr, u32 chan)
 	value |= DMA_CONTROL_SR;
 	writel(value, ioaddr + DMA_CONTROL);
 }
+EXPORT_SYMBOL_GPL(dwmac_dma_start_rx);
 
 void dwmac_dma_stop_rx(void __iomem *ioaddr, u32 chan)
 {
@@ -83,6 +89,7 @@ void dwmac_dma_stop_rx(void __iomem *ioaddr, u32 chan)
 	value &= ~DMA_CONTROL_SR;
 	writel(value, ioaddr + DMA_CONTROL);
 }
+EXPORT_SYMBOL_GPL(dwmac_dma_stop_rx);
 
 #ifdef DWMAC_DMA_DEBUG
 static void show_tx_process_state(unsigned int status)
@@ -230,6 +237,7 @@ int dwmac_dma_interrupt(void __iomem *ioaddr,
 
 	return ret;
 }
+EXPORT_SYMBOL_GPL(dwmac_dma_interrupt);
 
 void dwmac_dma_flush_tx_fifo(void __iomem *ioaddr)
 {
-- 
2.33.5



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

* [d-kernel] [PATCH 12/32] dt-bindings: dwmac: Add bindings for Baikal-T1/M SoCs
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (9 preceding siblings ...)
  2022-12-14 13:18 ` [d-kernel] [PATCH 11/32] net: stmmac: inital support of Baikal-T1/M SoCs GMAC Alexey Sheplyakov
@ 2022-12-14 13:18 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 13/32] net: dwmac-baikal: added compatible strings Alexey Sheplyakov
                   ` (19 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:18 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

Added dwmac bindings for Baikal-T1 and Baikal-M SoCs

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 Documentation/devicetree/bindings/net/snps,dwmac.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/net/snps,dwmac.yaml b/Documentation/devicetree/bindings/net/snps,dwmac.yaml
index 13b984076af5..d428ddf08027 100644
--- a/Documentation/devicetree/bindings/net/snps,dwmac.yaml
+++ b/Documentation/devicetree/bindings/net/snps,dwmac.yaml
@@ -58,6 +58,7 @@ properties:
         - amlogic,meson8m2-dwmac
         - amlogic,meson-gxbb-dwmac
         - amlogic,meson-axg-dwmac
+        - baikal,dwmac
         - ingenic,jz4775-mac
         - ingenic,x1000-mac
         - ingenic,x1600-mac
-- 
2.33.5



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

* [d-kernel] [PATCH 13/32] net: dwmac-baikal: added compatible strings...
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (10 preceding siblings ...)
  2022-12-14 13:18 ` [d-kernel] [PATCH 12/32] dt-bindings: dwmac: Add bindings for Baikal-T1/M SoCs Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 14/32] Added TF307/TF306 board management controller driver Alexey Sheplyakov
                   ` (18 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

... for AQBM1000 board, TF307 board with FDT from SDK-M 5.3

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c
index 9def4ea7fb3b..4d720ef4fb75 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-baikal.c
@@ -196,6 +196,8 @@ static int dwmac_baikal_probe(struct platform_device *pdev)
 static const struct of_device_id dwmac_baikal_match[] = {
 	{ .compatible = "baikal,bm1000-gmac" },
 	{ .compatible = "baikal,dwmac" },
+	{ .compatible = "be,dwmac" },
+	{ .compatible = "aq,dwmac" },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, dwmac_baikal_match);
-- 
2.33.5



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

* [d-kernel] [PATCH 14/32] Added TF307/TF306 board management controller driver
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (11 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 13/32] net: dwmac-baikal: added compatible strings Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 15/32] hwmon: bt1-pvt: access registers via pvt_{readl, writel} helpers Alexey Sheplyakov
                   ` (17 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

The board management controller (BMC) device is responsible for CPU
kick-starting, controlling power button, and a full board poweroff.

X-feature-Baikal-M
---
 drivers/misc/Kconfig  |  18 +
 drivers/misc/Makefile |   1 +
 drivers/misc/tp_bmc.c | 745 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 764 insertions(+)
 create mode 100644 drivers/misc/tp_bmc.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 358ad56f6524..75029780c4fc 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -496,6 +496,24 @@ config VCPU_STALL_DETECTOR
 
 	  If you do not intend to run this kernel as a guest, say N.
 
+config TP_BMC
+	tristate "TF307/TF306 board management controller"
+	depends on I2C
+	depends on OF
+	select PINCTRL
+	select GENERIC_PINCONF
+	select SERIO
+	default y if ARCH_BAIKAL
+	help
+	  Say Y here if you want to build a driver for BMC devices embedded into
+          some boards with Baikal-M and Baikal-T1 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-bmc. If unsure,
+	  say N here.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index ac9b3e757ba1..93453052ed92 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -62,3 +62,4 @@ 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
diff --git a/drivers/misc/tp_bmc.c b/drivers/misc/tp_bmc.c
new file mode 100644
index 000000000000..fd9b02a9bd29
--- /dev/null
+++ b/drivers/misc/tp_bmc.c
@@ -0,0 +1,745 @@
+#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
+
+#define POLL_JIFFIES 100
+
+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_ret;
+
+/* BMC RTC */
+static int
+bmc_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	uint8_t 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);
+	uint8_t rtc_buf[8];
+	struct i2c_msg msg;
+	int rc;
+	uint8_t 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);
+
+	/* 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;
+	uint8_t 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, "bmc_serio_write: %02x\n", 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;
+	uint8_t 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, "bmc_serio_read: i2c_transfer error %d\n", rc);
+		return -1;
+	}
+
+	cnt = buf[0];
+	rc = 0;
+	if (cnt > BMC_SERIO_BUFSIZE - 1) {
+		cnt = BMC_SERIO_BUFSIZE - 1;
+		rc = 1;
+	}
+
+	for (i = 0; i < cnt; i++) {
+		serio_interrupt(serio, buf[i + 1], 0);
+	}
+
+	return 0;
+}
+
+int
+touchpad_poll_fn(void *data) {
+	int ret;
+
+	while (1) {
+		if (kthread_should_stop())
+			break;
+		while ((ret = bmc_serio_read(serio_i2c)) > 0)
+			;
+		if (ret < 0) {
+			msleep_interruptible(10000);
+		}
+		msleep_interruptible(10);
+	}
+	return 0;
+}
+#endif /* CONFIG_SERIO */
+
+#ifdef CONFIG_PINCTRL
+static uint8_t 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 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 pin,
+			      unsigned long *config,
+			      unsigned nc)
+{
+	int idx, bit;
+	enum pin_config_param param;
+	int arg;
+
+	if (pin > BMC_NPINS)
+		return -EINVAL;
+
+	idx = pin >> 3;
+	bit = pin & 7;
+
+	param = pinconf_to_config_param (*config);
+	arg = pinconf_to_config_argument (*config);
+	if (param != PIN_CONFIG_OUTPUT)
+		return -EINVAL;
+
+	if (arg)
+		bmc_pincf_state[idx] |= (1 << bit);
+	else
+		bmc_pincf_state[idx] &= ~(1 << bit);
+dev_dbg(&bmc_i2c->dev, "bmc_pin_config_set: pin %u, dir %lu\n", 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 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);
+	} else {
+		dev_info(&pbdev->dev, "BMC pinctrl registered\n");
+		bmc_pinctrl_dev = pctrl_dev;
+	}
+	/* reset all pins to default state */
+	i2c_smbus_write_byte_data(to_i2c_client(dev), R_GPIODIR0, 0);
+	i2c_smbus_write_byte_data(to_i2c_client(dev), R_GPIODIR1, 0);
+	i2c_smbus_write_byte_data(to_i2c_client(dev), R_GPIODIR2, 0);
+	return 0;
+}
+
+static void bmc_pinctrl_unregister(void)
+{
+	if (bmc_pinctrl_dev)
+		devm_pinctrl_unregister(&bmc_i2c->dev, bmc_pinctrl_dev);
+}
+
+#endif
+
+void
+bmc_pwroff_rq(void) {
+	int ret = 0;
+
+	dev_info(&bmc_i2c->dev, "Write reg R_PWROFF_RQ\n");
+	ret = i2c_smbus_write_byte_data(bmc_i2c, R_PWROFF_RQ, 0x01);
+	dev_info(&bmc_i2c->dev, "ret: %i\n", ret);
+}
+
+int
+pwroff_rq_poll_fn(void *data) {
+	int ret;
+
+	while (1) {
+		if (kthread_should_stop())
+			break;
+		dev_dbg(&poll_data.c->dev, "Polling\n");
+		ret = i2c_smbus_read_byte_data(poll_data.c, R_SOFTOFF_RQ);
+		dev_dbg(&poll_data.c->dev, "Polling returned: %i\n", ret);
+		if (prev_ret != ret) {
+			dev_info(&poll_data.c->dev, "key change [%i]\n", ret);
+			if (ret < 0) {
+				dev_err(&poll_data.c->dev,
+					"Could not read register %x\n",
+					R_SOFTOFF_RQ);
+				return -EIO;
+			} else 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_ret = ret;
+
+		msleep_interruptible(100);
+	}
+	do_exit(1);
+	return 0;
+}
+
+static int
+mitx2_bmc_validate(struct i2c_client *client) {
+	int ret = 0;
+	int i = 0;
+	static const u8 regs[] = {R_ID1, R_ID2, R_ID3};
+	static const u8 vals[] = {BMC_ID1_VAL, BMC_ID2_VAL, BMC_ID3_VAL};
+
+	bmc_proto_version[0] = 0;
+	bmc_proto_version[1] = 0;
+	bmc_proto_version[2] = 0;
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++) {
+		ret = i2c_smbus_read_byte_data(client, regs[i]);
+		if (ret < 0) {
+			dev_err(&client->dev, "Could not read register %x\n",
+				regs[i]);
+			return -EIO;
+		}
+		if (ret != vals[i]) {
+			dev_err(&client->dev,
+				"Bad value [0x%02x] in register 0x%02x, 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 %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 %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 %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 %x\n",
+				R_BOOTREASON);
+			return -EIO;
+		}
+		bmc_bootreason[0] = ret;
+		dev_info(&client->dev, "BMC bootreason[0]->%i\n", ret);
+		ret = i2c_smbus_read_byte_data(client, R_BOOTREASON_ARG);
+		if (ret < 0) {
+			dev_err(&client->dev, "Could not read register %x\n",
+				R_BOOTREASON_ARG);
+			return -EIO;
+		}
+		bmc_bootreason[1] = ret;
+		dev_info(&client->dev, "BMC bootreason[1]->%i\n", 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 %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;
+			dev_info(&client->dev,
+				 "BMC extended capabilities %x\n", bmc_cap);
+		} else {
+			bmc_cap = BMC_CAP_PWRBTN;
+		}
+	} else {
+		dev_err(&client->dev, "Bad value [0x%02x] in register 0x%02x\n",
+			ret, R_ID4);
+		return -ENODEV;
+	}
+	dev_info(&client->dev, "BMC seems to be valid\n");
+	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) {
+			dev_err(&serio_i2c->dev, "Can't allocate serio\n");
+			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,
+		    const struct i2c_device_id *id)
+{
+	int err = 0;
+	int i = 0;
+
+	dev_info(&client->dev, "mitx2 bmc probe\n");
+
+	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_info(&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.33.5



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

* [d-kernel] [PATCH 15/32] hwmon: bt1-pvt: access registers via pvt_{readl, writel} helpers
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (12 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 14/32] Added TF307/TF306 board management controller driver Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 16/32] hwmon: bt1-pvt: define pvt_readl/pvt_writel for Baikal-M SoC Alexey Sheplyakov
                   ` (16 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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

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

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

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



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

* [d-kernel] [PATCH 16/32] hwmon: bt1-pvt: define pvt_readl/pvt_writel for Baikal-M SoC
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (13 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 15/32] hwmon: bt1-pvt: access registers via pvt_{readl, writel} helpers Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 17/32] hwmon: bt1-pvt: adjusted probing " Alexey Sheplyakov
                   ` (15 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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

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

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



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

* [d-kernel] [PATCH 17/32] hwmon: bt1-pvt: adjusted probing for Baikal-M SoC
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (14 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 16/32] hwmon: bt1-pvt: define pvt_readl/pvt_writel for Baikal-M SoC Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 18/32] hwmon: bt1-pvt: added compatible baikal, pvt Alexey Sheplyakov
                   ` (14 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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

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

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

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



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

* [d-kernel] [PATCH 18/32] hwmon: bt1-pvt: added compatible baikal, pvt
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (15 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 17/32] hwmon: bt1-pvt: adjusted probing " Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 19/32] drm: new bridge driver - stdp4028 Alexey Sheplyakov
                   ` (13 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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

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

diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c
index 90f29521f1af..a5a17179f5a4 100644
--- a/drivers/hwmon/bt1-pvt.c
+++ b/drivers/hwmon/bt1-pvt.c
@@ -1217,6 +1217,8 @@ static int pvt_probe(struct platform_device *pdev)
 
 static const struct of_device_id pvt_of_match[] = {
 	{ .compatible = "baikal,bt1-pvt" },
+	{ .compatible = "baikal,pvt" },
+	{ .compatible = "baikal,bm1000-pvt"},
 	{ }
 };
 MODULE_DEVICE_TABLE(of, pvt_of_match);
-- 
2.33.5



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

* [d-kernel] [PATCH 19/32] drm: new bridge driver - stdp4028
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (16 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 18/32] hwmon: bt1-pvt: added compatible baikal, pvt Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 20/32] drm: added Baikal-M SoC video display unit driver Alexey Sheplyakov
                   ` (12 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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

MegaChips stdp4028 is LVDS to DP bridge.

The driver can work in interrupt or poll mode.
Videomodes may be specified in the devicetree or read from EDID.

Co-developed-by: Vadim V. Vlasov <vvv19xx@gmail.com>
Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 drivers/gpu/drm/bridge/Kconfig    |   8 +
 drivers/gpu/drm/bridge/Makefile   |   1 +
 drivers/gpu/drm/bridge/stdp4028.c | 484 ++++++++++++++++++++++++++++++
 3 files changed, 493 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/stdp4028.c

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 57946d80b02d..b6b83c4e9083 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -379,6 +379,14 @@ config DRM_TI_TPD12S015
 	  Texas Instruments TPD12S015 HDMI level shifter and ESD protection
 	  driver.
 
+config DRM_STDP4028
+	tristate "MegaChips STDP4028 DP bridge"
+	depends on OF
+	select DRM_KMS_HELPER
+	select DRM_PANEL
+	help
+	  MegaChips STDP4028 DP bridge driver
+
 source "drivers/gpu/drm/bridge/analogix/Kconfig"
 
 source "drivers/gpu/drm/bridge/adv7511/Kconfig"
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 1884803c6860..e8312848293f 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
 obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o
 obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-dsi.o
 obj-$(CONFIG_DRM_ITE_IT66121) += ite-it66121.o
+obj-$(CONFIG_DRM_STDP4028) += stdp4028.o
 
 obj-y += analogix/
 obj-y += cadence/
diff --git a/drivers/gpu/drm/bridge/stdp4028.c b/drivers/gpu/drm/bridge/stdp4028.c
new file mode 100644
index 000000000000..5a27db85ad4b
--- /dev/null
+++ b/drivers/gpu/drm/bridge/stdp4028.c
@@ -0,0 +1,484 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for MegaChips STDP4028 LVDS to DP display bridge
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+/* video modes */
+#include <video/display_timing.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+#define MAX_PIXEL_CLOCK 330000
+
+#define EDID_EXT_BLOCK_CNT 0x7E
+
+#define STDP4028_PRODUCT_ID_REG 0x00
+#define STDP4028_IRQ_OUT_CONF_REG 0x02
+#define STDP4028_IRQ_STS_REG 0x03
+#define STDP4028_I2C_CTRL_REG 0x08
+#define STDP4028_LVDS_FMT_REG 0x0B
+#define STDP4028_LVDS_CTRL0_REG 0x0C
+#define STDP4028_DPTX_IRQ_EN_REG 0x3C
+#define STDP4028_DPTX_IRQ_STS_REG 0x3D
+#define STDP4028_DPTX_STS_REG 0x3E
+
+#define STDP4028_DPTX_DP_IRQ_EN 0x10
+
+#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x04
+#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x20
+#define STDP4028_DPTX_IRQ_CONFIG \
+	(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
+
+#define STDP4028_DPTX_HOTPLUG_STS 0x02
+#define STDP4028_DPTX_LINK_STS 0x10
+#define STDP4028_CON_STATE_CONNECTED \
+	(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
+
+#define STDP4028_DPTX_HOTPLUG_CH_STS 0x04
+#define STDP4028_DPTX_LINK_CH_STS 0x20
+#define STDP4028_DPTX_IRQ_CLEAR \
+	(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
+
+struct stdp4028 {
+	struct drm_connector connector;
+	struct drm_bridge bridge;
+	struct i2c_client *stdp4028_i2c;
+	struct i2c_client *edid_i2c;
+	struct edid *edid;
+	struct gpio_desc *reset_gpio;
+	struct mutex lock;
+	int channels;
+	int chan_cfg;
+};
+
+static inline int stdp_read(struct stdp4028 *stdp, int reg)
+{
+	int ret;
+
+	ret = i2c_smbus_read_word_data(stdp->stdp4028_i2c, reg);
+	if (ret < 0)
+		return ret;
+	return be16_to_cpu(ret);
+}
+
+static inline int stdp_write(struct stdp4028 *stdp, int reg, u16 val)
+{
+	val = cpu_to_be16(val);
+	return i2c_smbus_write_word_data(stdp->stdp4028_i2c, reg, val);
+}
+
+#define bridge_to_stdp4028(bridge) \
+	container_of(bridge, struct stdp4028, bridge)
+
+#define connector_to_stdp4028(connector) \
+	container_of(connector, struct stdp4028, connector)
+
+static u8 *stdp4028_get_edid(struct i2c_client *client)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	unsigned char start = 0x00;
+	unsigned int total_size;
+	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
+
+	struct i2c_msg msgs[] = {
+		{
+			.addr   = client->addr,
+			.flags  = 0,
+			.len    = 1,
+			.buf    = &start,
+		}, {
+			.addr   = client->addr,
+			.flags  = I2C_M_RD,
+			.len    = EDID_LENGTH,
+			.buf    = block,
+		}
+	};
+
+	if (!block)
+		return NULL;
+
+	if (i2c_transfer(adapter, msgs, 2) != 2) {
+		DRM_ERROR("Unable to read EDID.\n");
+		goto err;
+	}
+
+	if (!drm_edid_block_valid(block, 0, false, NULL)) {
+		DRM_ERROR("Invalid EDID data\n");
+		goto err;
+	}
+
+	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
+	if (total_size > EDID_LENGTH) {
+		kfree(block);
+		block = kmalloc(total_size, GFP_KERNEL);
+		if (!block)
+			return NULL;
+
+		/* Yes, read the entire buffer, and do not skip the first
+		 * EDID_LENGTH bytes.
+		 */
+		start = 0x00;
+		msgs[1].len = total_size;
+		msgs[1].buf = block;
+
+		if (i2c_transfer(adapter, msgs, 2) != 2) {
+			DRM_ERROR("Unable to read EDID extension blocks.\n");
+			goto err;
+		}
+	}
+
+	return block;
+
+err:
+	kfree(block);
+	return NULL;
+}
+
+/*
+ * Get videomode specified in the devicetree.
+ * Return 1 on success, 0 otherwise.
+ */
+static int stdp4028_get_of_modes(struct drm_connector *connector)
+{
+	struct stdp4028 *stdp = connector_to_stdp4028(connector);
+	struct i2c_client *client = stdp->stdp4028_i2c;
+	struct drm_display_mode *mode;
+	struct device_node *np = client->dev.of_node;
+	struct display_timing timing;
+	struct videomode video_mode;
+	int ret;
+
+	ret = of_get_display_timing(np, "panel-timing", &timing);
+	if (ret < 0)
+		return 0;
+
+	videomode_from_timing(&timing, &video_mode);
+
+	mode = drm_mode_create(connector->dev);
+	if (!mode)
+		return 0;
+	drm_display_mode_from_videomode(&video_mode, mode);
+	mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+	drm_mode_probed_add(connector, mode);
+	return 1;
+}
+
+static int stdp4028_get_modes(struct drm_connector *connector)
+{
+	struct stdp4028 *stdp;
+	struct i2c_client *client;
+	int num_modes = 0;
+
+	stdp = connector_to_stdp4028(connector);
+	client = stdp->edid_i2c;
+
+	mutex_lock(&stdp->lock);
+
+	num_modes = stdp4028_get_of_modes(connector);
+	if (num_modes > 0) {
+		mutex_unlock(&stdp->lock);
+		return num_modes;
+	}
+
+	kfree(stdp->edid);
+	stdp->edid = (struct edid *) stdp4028_get_edid(client);
+
+	if (stdp->edid) {
+		drm_connector_update_edid_property(connector, stdp->edid);
+		num_modes = drm_add_edid_modes(connector, stdp->edid);
+	}
+
+	mutex_unlock(&stdp->lock);
+
+	return num_modes;
+}
+
+
+static enum drm_mode_status stdp4028_mode_valid(
+		 struct drm_connector *connector, struct drm_display_mode *mode)
+{
+	if (mode->clock > MAX_PIXEL_CLOCK) {
+		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
+			 mode->name);
+		return MODE_CLOCK_HIGH;
+	}
+
+	return MODE_OK;
+}
+
+static const struct
+drm_connector_helper_funcs stdp4028_connector_helper_funcs = {
+	.get_modes = stdp4028_get_modes,
+	.mode_valid = stdp4028_mode_valid,
+};
+
+static enum drm_connector_status stdp4028_detect(
+		 struct drm_connector *connector, bool force)
+{
+	struct stdp4028 *stdp = connector_to_stdp4028(connector);
+	s32 link_state;
+
+	link_state = stdp_read(stdp, STDP4028_DPTX_STS_REG);
+
+	if (link_state == STDP4028_CON_STATE_CONNECTED)
+		return connector_status_connected;
+
+	if (link_state == 0)
+		return connector_status_disconnected;
+
+	return connector_status_unknown;
+}
+
+static const struct drm_connector_funcs stdp4028_connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = stdp4028_detect,
+	.destroy = drm_connector_cleanup,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static irqreturn_t stdp4028_irq_handler(int irq, void *dev_id)
+{
+	struct stdp4028 *stdp = dev_id;
+
+	mutex_lock(&stdp->lock);
+
+	stdp_write(stdp, STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
+
+	mutex_unlock(&stdp->lock);
+
+	if (stdp->connector.dev)
+		drm_kms_helper_hotplug_event(stdp->connector.dev);
+
+	return IRQ_HANDLED;
+}
+
+static int stdp4028_create_connector(struct drm_bridge *bridge)
+{
+	int ret;
+	struct stdp4028 *stdp
+			  = bridge_to_stdp4028(bridge);
+	struct drm_connector *connector = &stdp->connector;
+
+	if (!bridge->encoder) {
+		DRM_ERROR("Parent encoder object not found");
+		return -ENODEV;
+	}
+
+	if (stdp->stdp4028_i2c->irq)
+		connector->polled = DRM_CONNECTOR_POLL_HPD;
+	else
+		connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+				    DRM_CONNECTOR_POLL_DISCONNECT;
+
+	drm_connector_helper_add(connector, &stdp4028_connector_helper_funcs);
+
+	ret = drm_connector_init(bridge->dev, connector,
+				 &stdp4028_connector_funcs,
+				 DRM_MODE_CONNECTOR_DisplayPort);
+	if (ret) {
+		DRM_ERROR("Failed to initialize connector with drm\n");
+		return ret;
+	}
+
+	return drm_connector_attach_encoder(connector, bridge->encoder);
+}
+
+static int stdp4028_attach(struct drm_bridge *bridge,
+			   enum drm_bridge_attach_flags flags)
+{
+	struct stdp4028 *stdp
+			  = bridge_to_stdp4028(bridge);
+
+	/* Configures the bridge to re-enable interrupts after each ack. */
+	stdp_write(stdp, STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
+
+	/* Enable interrupts */
+	stdp_write(stdp, STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
+
+	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
+		return 0;
+
+	return stdp4028_create_connector(bridge);
+}
+
+static const struct drm_bridge_funcs stdp4028_funcs = {
+	.attach = stdp4028_attach,
+};
+
+static int stdp4028_probe(struct i2c_client *stdp4028_i2c,
+			  const struct i2c_device_id *id)
+{
+	struct device *dev = &stdp4028_i2c->dev;
+	struct stdp4028 *bridge;
+	int ret;
+	u32 edid_i2c_reg, channels, chan_cfg;
+	enum of_gpio_flags flags;
+	int reset_gpio, i;
+	int reg;
+
+	bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL);
+	if (!bridge)
+		return -ENOMEM;
+
+	mutex_init(&bridge->lock);
+
+	bridge->stdp4028_i2c = stdp4028_i2c;
+	bridge->bridge.driver_private = bridge;
+	i2c_set_clientdata(stdp4028_i2c, bridge);
+
+	reset_gpio = of_get_named_gpio_flags(dev->of_node,
+					     "reset-gpios", 0, &flags);
+	if (gpio_is_valid(reset_gpio)) {
+		unsigned long gpio_flags;
+
+		/*
+		 * We will set GPIO to "inactive" state instead of toggling
+		 * reset. If the chip is not ready we will return -EPROBE_DEFER
+		 * and retry later.
+		 */
+		if (!(flags & OF_GPIO_ACTIVE_LOW))
+			gpio_flags = GPIOF_ACTIVE_LOW | GPIOF_OUT_INIT_LOW;
+		else
+			gpio_flags = GPIOF_OUT_INIT_HIGH;
+		ret = devm_gpio_request_one(dev, reset_gpio, gpio_flags,
+					    "stdp-reset");
+		if (ret) {
+			dev_err(dev, "request GPIO failed (%d)\n", ret);
+			/* continue anyway */
+		} else {
+			bridge->reset_gpio = gpio_to_desc(reset_gpio);
+			udelay(100);
+		}
+	} else if (reset_gpio == -EPROBE_DEFER) {
+		return -EPROBE_DEFER;
+	}
+
+	ret = of_property_read_u32(dev->of_node, "channels", &channels);
+	if (ret)
+		channels = 1;
+	bridge->channels = channels;
+
+	ret = of_property_read_u32(dev->of_node, "chan-cfg", &chan_cfg);
+	if (ret)
+		chan_cfg = 0;
+	bridge->chan_cfg = chan_cfg;
+
+	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
+	if (ret) {
+		dev_warn(dev, "edid-reg not specified, assuming 0x50...\n");
+		edid_i2c_reg = 0x50;
+	}
+
+	/* Configure stdp registers */
+	reg = stdp_read(bridge, STDP4028_PRODUCT_ID_REG);
+	if (reg < 0) {
+		dev_err(dev, "Can't read stdp id (%d)\n", reg);
+		return -EPROBE_DEFER; /* probably, reset not complete */
+	}
+
+	dev_info(dev, "stdp id word: %x\n", reg);
+
+	for (i = 0; i < 10; i++) {
+		reg = stdp_read(bridge, STDP4028_IRQ_STS_REG);
+		if (reg > 0 && reg & 0x800)
+			break;
+		usleep_range(1000, 1500);
+	}
+	dev_dbg(dev, "STDP status word %x (i = %d)\n", reg, i);
+	stdp_write(bridge, STDP4028_IRQ_STS_REG, 0x800); //clear
+	/* enable edid addr */
+	stdp_write(bridge, STDP4028_I2C_CTRL_REG, (edid_i2c_reg << 1) | 0x400);
+
+	if (channels == 4)
+		reg = 2;
+	else if (channels == 2)
+		reg = 1;
+	else
+		reg = 0;
+	reg |= chan_cfg << 2;
+	stdp_write(bridge, STDP4028_LVDS_CTRL0_REG, reg);
+
+	bridge->edid_i2c = i2c_new_dummy_device(stdp4028_i2c->adapter, edid_i2c_reg);
+
+	if (!bridge->edid_i2c)
+		return -ENOMEM;
+
+	bridge->bridge.funcs = &stdp4028_funcs;
+	bridge->bridge.of_node = dev->of_node;
+	drm_bridge_add(&bridge->bridge);
+
+	/* Clear pending interrupts since power up. */
+	stdp_write(bridge, STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
+
+	if (stdp4028_i2c->irq) {
+		ret = devm_request_threaded_irq(&stdp4028_i2c->dev,
+					stdp4028_i2c->irq, NULL,
+					stdp4028_irq_handler,
+					IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+					"stdp-lvds-dp", bridge);
+		if (ret)
+			return ret;
+
+		/* enable DPTX IRQs */
+		stdp_write(bridge, STDP4028_IRQ_OUT_CONF_REG,
+			   STDP4028_DPTX_DP_IRQ_EN);
+		stdp_write(bridge, STDP4028_DPTX_IRQ_EN_REG,
+			   STDP4028_DPTX_IRQ_CONFIG);
+	}
+
+	return 0;
+}
+
+static void stdp4028_remove(struct i2c_client *stdp4028_i2c)
+{
+	struct stdp4028 *stdp =	i2c_get_clientdata(stdp4028_i2c);
+
+	drm_bridge_remove(&stdp->bridge);
+	i2c_unregister_device(stdp->edid_i2c);
+
+	kfree(stdp->edid);
+}
+
+static const struct i2c_device_id stdp4028_i2c_table[] = {
+	{"stdp4028-lvds-dp", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, stdp4028_i2c_table);
+
+static const struct of_device_id stdp4028_match[] = {
+	{ .compatible = "megachips,stdp4028-lvds-dp" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, stdp4028_match);
+
+static struct i2c_driver stdp4028_driver = {
+	.id_table	= stdp4028_i2c_table,
+	.probe		= stdp4028_probe,
+	.remove		= stdp4028_remove,
+	.driver		= {
+		.name		= "stdp4028-lvds-dp",
+		.of_match_table	= stdp4028_match,
+	},
+};
+module_i2c_driver(stdp4028_driver);
+
+MODULE_AUTHOR("Vadim V. Vlasov <vvv19xx at gmail.com>");
+MODULE_DESCRIPTION("STDP4028 LVDS to DP display bridge)");
+MODULE_LICENSE("GPL v2");
-- 
2.33.5



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

* [d-kernel] [PATCH 20/32] drm: added Baikal-M SoC video display unit driver
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (17 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 19/32] drm: new bridge driver - stdp4028 Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 21/32] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a Alexey Sheplyakov
                   ` (11 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

Due to hardware peculiarities using both HDMI and DP outputs is
possible only with some constraints:

- Resolution of both displays should be exactly the same
- HDMI viewport must be above or below the display port one

Co-developed-by: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 drivers/gpu/drm/Kconfig                       |   1 +
 drivers/gpu/drm/Makefile                      |   1 +
 drivers/gpu/drm/baikal/Kconfig                |  15 +
 drivers/gpu/drm/baikal/Makefile               |  10 +
 drivers/gpu/drm/baikal/baikal-hdmi.c          | 119 ++++++
 drivers/gpu/drm/baikal/baikal_vdu_connector.c | 118 ++++++
 drivers/gpu/drm/baikal/baikal_vdu_crtc.c      | 345 +++++++++++++++++
 drivers/gpu/drm/baikal/baikal_vdu_debugfs.c   |  87 +++++
 drivers/gpu/drm/baikal/baikal_vdu_drm.h       |  65 ++++
 drivers/gpu/drm/baikal/baikal_vdu_drv.c       | 364 ++++++++++++++++++
 drivers/gpu/drm/baikal/baikal_vdu_plane.c     | 210 ++++++++++
 drivers/gpu/drm/baikal/baikal_vdu_regs.h      | 139 +++++++
 drivers/gpu/drm/bridge/Kconfig                |   7 +
 13 files changed, 1481 insertions(+)
 create mode 100644 drivers/gpu/drm/baikal/Kconfig
 create mode 100644 drivers/gpu/drm/baikal/Makefile
 create mode 100644 drivers/gpu/drm/baikal/baikal-hdmi.c
 create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_connector.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_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 34f5a092c99e..e0738ba51430 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -232,6 +232,7 @@ config DRM_SCHED
 source "drivers/gpu/drm/i2c/Kconfig"
 
 source "drivers/gpu/drm/arm/Kconfig"
+source "drivers/gpu/drm/baikal/Kconfig"
 
 config DRM_RADEON
 	tristate "ATI Radeon"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 0b283e46f28b..db0456c3dd20 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -148,3 +148,4 @@ obj-y			+= gud/
 obj-$(CONFIG_DRM_HYPERV) += hyperv/
 obj-y			+= solomon/
 obj-$(CONFIG_DRM_SPRD) += sprd/
+obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal/
diff --git a/drivers/gpu/drm/baikal/Kconfig b/drivers/gpu/drm/baikal/Kconfig
new file mode 100644
index 000000000000..a514fd430ee6
--- /dev/null
+++ b/drivers/gpu/drm/baikal/Kconfig
@@ -0,0 +1,15 @@
+config DRM_BAIKAL_VDU
+	tristate "DRM Support for Baikal-M VDU"
+	depends on DRM
+	depends on ARM || ARM64 || COMPILE_TEST
+	depends on COMMON_CLK
+	default y if ARCH_BAIKAL
+	select DRM_KMS_HELPER
+	select DRM_KMS_DMA_HELPER
+	select DRM_GEM_DMA_HELPER
+	select DRM_PANEL
+	select DRM_BAIKAL_HDMI
+	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 000000000000..eb029494e823
--- /dev/null
+++ b/drivers/gpu/drm/baikal/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+baikal_vdu_drm-y +=	baikal_vdu_connector.o \
+		baikal_vdu_crtc.o \
+		baikal_vdu_drv.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 000000000000..6a55d03d93f8
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal-hdmi.c
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Baikal Electronics BE-M1000 DesignWare HDMI 2.0 Tx PHY support driver
+ *
+ * Copyright (C) 2019-2021 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ *
+ * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <drm/drm_modes.h>
+
+#include <drm/bridge/dw_hdmi.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 dw_hdmi *hdmi;
+	hdmi = dw_hdmi_probe(pdev, &baikal_dw_hdmi_plat_data);
+	if (IS_ERR(hdmi)) {
+		return PTR_ERR(hdmi);
+	} else {
+		return 0;
+	}
+}
+
+static int baikal_dw_hdmi_remove(struct platform_device *pdev)
+{
+	struct dw_hdmi *hdmi = platform_get_drvdata(pdev);
+	dw_hdmi_remove(hdmi);
+	return 0;
+}
+
+static const struct of_device_id baikal_dw_hdmi_of_table[] = {
+	{ .compatible = "baikal,hdmi" },
+	{ /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, baikal_dw_hdmi_of_table);
+
+static struct platform_driver baikal_dw_hdmi_platform_driver = {
+	.probe		= baikal_dw_hdmi_probe,
+	.remove		= baikal_dw_hdmi_remove,
+	.driver		= {
+		.name	= "baikal-dw-hdmi",
+		.of_match_table = baikal_dw_hdmi_of_table,
+	},
+};
+
+module_param(fixed_clock, int, 0644);
+module_param(max_clock, int, 0644);
+
+module_platform_driver(baikal_dw_hdmi_platform_driver);
+
+MODULE_AUTHOR("Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal BE-M1000 SoC DesignWare HDMI 2.0 Tx + Gen2 PHY Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_connector.c b/drivers/gpu/drm/baikal/baikal_vdu_connector.c
new file mode 100644
index 000000000000..2f20cf3da627
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_connector.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019-2020 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+/**
+ * baikal_vdu_connector.c
+ * Implementation of the connector functions for Baikal Electronics BE-M1000 SoC's VDU
+ */
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+#define to_baikal_vdu_private(x) \
+	container_of(x, struct baikal_vdu_private, connector)
+
+static void baikal_vdu_drm_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static enum drm_connector_status baikal_vdu_drm_connector_detect(
+		struct drm_connector *connector, bool force)
+{
+	struct baikal_vdu_private *priv = to_baikal_vdu_private(connector);
+
+	return (priv->panel ?
+		connector_status_connected :
+		connector_status_disconnected);
+}
+
+static int baikal_vdu_drm_connector_helper_get_modes(
+		struct drm_connector *connector)
+{
+	struct baikal_vdu_private *priv = to_baikal_vdu_private(connector);
+
+	if (!priv->panel)
+		return 0;
+
+	return drm_panel_get_modes(priv->panel, connector);
+}
+
+const struct drm_connector_funcs connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = baikal_vdu_drm_connector_destroy,
+	.detect = baikal_vdu_drm_connector_detect,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+const struct drm_connector_helper_funcs connector_helper_funcs = {
+	.get_modes = baikal_vdu_drm_connector_helper_get_modes,
+};
+
+static const struct drm_encoder_funcs encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+int baikal_vdu_lvds_connector_create(struct drm_device *dev)
+{
+	struct baikal_vdu_private *priv = dev->dev_private;
+	struct drm_connector *connector = &priv->connector;
+	struct drm_encoder *encoder = &priv->encoder;
+	int ret = 0;
+
+	ret = drm_connector_init(dev, connector, &connector_funcs,
+			DRM_MODE_CONNECTOR_LVDS);
+	if (ret) {
+		dev_err(dev->dev, "drm_connector_init failed: %d\n", ret);
+		goto out;
+	}
+	drm_connector_helper_add(connector, &connector_helper_funcs);
+	ret = drm_encoder_init(dev, encoder, &encoder_funcs,
+			       DRM_MODE_ENCODER_LVDS, NULL);
+	if (ret) {
+		dev_err(dev->dev, "drm_encoder_init failed: %d\n", ret);
+		goto out;
+	}
+	encoder->crtc = &priv->crtc;
+	encoder->possible_crtcs = drm_crtc_mask(encoder->crtc);
+	ret = drm_connector_attach_encoder(connector, encoder);
+	if (ret) {
+		dev_err(dev->dev, "drm_connector_attach_encoder failed: %d\n", ret);
+		goto out;
+	}
+	ret = drm_connector_register(connector);
+	if (ret) {
+		dev_err(dev->dev, "drm_connector_register failed: %d\n", ret);
+		goto out;
+	}
+out:
+	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 000000000000..e338ff8b3080
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2019-2020 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+/**
+ * baikal_vdu_crtc.c
+ * Implementation of the CRTC functions for Baikal Electronics BE-M1000 VDU driver
+ */
+#include <linux/clk.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.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"
+
+struct baikal_vdu_crtc_mode_fixup {
+	int vdisplay;
+	int vfp_add;
+};
+
+static const struct baikal_vdu_crtc_mode_fixup mode_fixups[] = {
+	{ 480, 38 },
+	{ 600, 8 },
+	{ 720, 43 },
+	{ 768, 43 },
+	{ 800, 71 },
+	{ 864, 71 },
+	{ 900, 71 },
+	{ 960, 71 },
+	{ 1024, 25 },
+	{ 1050, 25 },
+	{ 1080, 8 },
+	{ 1200, 32 },
+	{ 1440, 27 },
+	{ ~0U },
+};
+
+irqreturn_t baikal_vdu_irq(int irq, void *data)
+{
+	struct drm_device *drm = data;
+	struct baikal_vdu_private *priv = drm->dev_private;
+	irqreturn_t status = IRQ_NONE;
+	u32 raw_stat;
+	u32 irq_stat;
+
+	irq_stat = readl(priv->regs + IVR);
+	raw_stat = readl(priv->regs + ISR);
+
+	if (irq_stat & INTR_VCT) {
+		priv->counters[10]++;
+		drm_crtc_handle_vblank(&priv->crtc);
+		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(irq_stat, priv->regs + ISR);
+
+	return status;
+}
+
+bool baikal_vdu_crtc_mode_fixup(struct drm_crtc *crtc,
+					const struct drm_display_mode *mode,
+					struct drm_display_mode *adjusted_mode)
+{
+	struct baikal_vdu_private *priv = crtc->dev->dev_private;
+
+	memcpy(adjusted_mode, mode, sizeof(*mode));
+
+	if (!priv->mode_fixup)
+		return true;
+
+	if (priv->mode_fixup == -1) {
+		const struct baikal_vdu_crtc_mode_fixup *fixups = mode_fixups;
+		for (; fixups && fixups->vdisplay != ~0U; ++fixups) {
+			if (mode->vdisplay <= fixups->vdisplay)
+				break;
+		}
+		if (fixups->vdisplay == ~0U)
+			return true;
+		else
+			priv->mode_fixup = fixups->vfp_add;
+	}
+
+	adjusted_mode->vtotal += priv->mode_fixup;
+	adjusted_mode->vsync_start += priv->mode_fixup;
+	adjusted_mode->vsync_end += priv->mode_fixup;
+	adjusted_mode->clock = mode->clock * adjusted_mode->vtotal / mode->vtotal;
+
+	return true;
+}
+
+static void baikal_vdu_crtc_helper_mode_set_nofb(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct baikal_vdu_private *priv = dev->dev_private;
+	const struct drm_display_mode *orig_mode = &crtc->state->mode;
+	const struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+	unsigned int ppl, hsw, hfp, hbp;
+	unsigned int lpp, vsw, vfp, vbp;
+	unsigned int reg;
+
+	drm_mode_debug_printmodeline(orig_mode);
+	drm_mode_debug_printmodeline(mode);
+
+	ppl = mode->hdisplay / 16;
+	if (priv->type == VDU_TYPE_LVDS) {
+		hsw = mode->hsync_end - mode->hsync_start;
+		hfp = mode->hsync_start - mode->hdisplay - 1;
+		hbp = mode->htotal - mode->hsync_end;
+	} else {
+		hsw = mode->hsync_end - mode->hsync_start - 1;
+		hfp = mode->hsync_start - mode->hdisplay;
+		hbp = mode->htotal - mode->hsync_end - 1;
+	}
+
+	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_VSP;
+	else
+		reg &= ~CR1_VSP;
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		reg |= CR1_HSP;
+	else
+		reg &= ~CR1_HSP;
+	reg |= CR1_DEP; // set DE to active high;
+	writel(reg, priv->regs + CR1);
+
+	crtc->hwmode = crtc->state->adjusted_mode;
+}
+
+static void baikal_vdu_crtc_helper_enable(struct drm_crtc *crtc,
+                      struct drm_atomic_state *state)
+{
+	struct baikal_vdu_private *priv = crtc->dev->dev_private;
+	struct drm_panel *panel = priv->panel;
+	const char *data_mapping = NULL;
+	u32 cntl, gpio;
+
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "priv = %px\n", priv);
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n");
+	clk_prepare_enable(priv->clk);
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "panel = %px\n", panel);
+	drm_panel_prepare(panel);
+
+	writel(ISCR_VSC_VFP, priv->regs + ISCR);
+
+	/* release clock reset; enable clocking */
+	cntl = readl(priv->regs + PCTR);
+	cntl |= PCTR_PCR + PCTR_PCI;
+	writel(cntl, priv->regs + PCTR);
+
+	/* Set 16-word input FIFO watermark */
+	/* Enable and Power Up */
+	cntl = readl(priv->regs + CR1);
+	cntl &= ~CR1_FDW_MASK;
+	cntl |= CR1_LCE + CR1_FDW_16_WORDS;
+
+	if (priv->type == VDU_TYPE_LVDS) {
+		if (panel) {
+			of_property_read_string(panel->dev->of_node,
+						"data-mapping", &data_mapping);
+		}
+		if (!data_mapping) {
+			cntl |= CR1_OPS_LCD18;
+			dev_dbg(crtc->dev->dev, "data mapping not specified, using jeida-18");
+		} else if (!strncmp(data_mapping, "vesa-24", 7)) {
+			cntl |= CR1_OPS_LCD24;
+			dev_dbg(crtc->dev->dev, "using vesa-24 mapping\n");
+		} else if (!strncmp(data_mapping, "jeida-18", 8)) {
+				cntl |= CR1_OPS_LCD18;
+				dev_dbg(crtc->dev->dev, "using jeida-18 mapping\n");
+		} else {
+			dev_warn(crtc->dev->dev, "unsupported data mapping '%s', using vesa-24\n", data_mapping);
+			cntl |= CR1_OPS_LCD24;
+		}
+
+		gpio = GPIOR_UHD_ENB;
+		if (priv->ep_count == 4)
+			gpio |= GPIOR_UHD_QUAD_PORT;
+		else if (priv->ep_count == 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);
+
+	drm_panel_enable(priv->panel);
+	drm_crtc_vblank_on(crtc);
+}
+
+void baikal_vdu_crtc_helper_disable(struct drm_crtc *crtc)
+{
+	struct baikal_vdu_private *priv = crtc->dev->dev_private;
+
+	drm_crtc_vblank_off(crtc);
+	drm_panel_disable(priv->panel);
+
+	drm_panel_unprepare(priv->panel);
+
+	/* Disable clock */
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "disabling pixel clock\n");
+	clk_disable_unprepare(priv->clk);
+}
+
+static void baikal_vdu_crtc_helper_atomic_flush(struct drm_crtc *crtc,
+                       struct drm_atomic_state *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);
+	}
+}
+
+static int baikal_vdu_enable_vblank(struct drm_crtc *crtc)
+{
+	struct baikal_vdu_private *priv = crtc->dev->dev_private;
+
+	/* clear interrupt status */
+	writel(0x3ffff, priv->regs + ISR);
+
+	writel(INTR_VCT + INTR_FER, priv->regs + IMR);
+
+	return 0;
+}
+
+static void baikal_vdu_disable_vblank(struct drm_crtc *crtc)
+{
+	struct baikal_vdu_private *priv = crtc->dev->dev_private;
+
+	/* clear interrupt status */
+	writel(0x3ffff, priv->regs + ISR);
+
+	writel(INTR_FER, priv->regs + IMR);
+}
+
+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,
+	.enable_vblank = baikal_vdu_enable_vblank,
+	.disable_vblank = baikal_vdu_disable_vblank,
+};
+
+const struct drm_crtc_helper_funcs crtc_helper_funcs = {
+	.mode_fixup = baikal_vdu_crtc_mode_fixup,
+	.mode_set_nofb = baikal_vdu_crtc_helper_mode_set_nofb,
+	.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 drm_device *dev)
+{
+	struct baikal_vdu_private *priv = dev->dev_private;
+	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);
+
+	/* XXX: The runtime clock disabling still results in
+	 * occasional system hangs, and needs debugging.
+	 */
+
+	DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n");
+	clk_prepare_enable(priv->clk);
+
+	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 000000000000..77be6aa588dc
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019-2020 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ *  Copyright © 2017 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#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(HVTER),
+	REGDEF(HPPLOR),
+	REGDEF(GPIOR),
+	REGDEF(OWER),
+	REGDEF(OWXSER0),
+	REGDEF(OWYSER0),
+	REGDEF(OWDBAR0),
+	REGDEF(OWDCAR0),
+	REGDEF(OWDEAR0),
+	REGDEF(OWXSER1),
+	REGDEF(OWYSER1),
+	REGDEF(OWDBAR1),
+	REGDEF(OWDCAR1),
+	REGDEF(OWDEAR1),
+	REGDEF(MRR),
+};
+
+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_private *priv = dev->dev_private;
+	int i;
+
+	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_debugfs_list[] = {
+	{"regs", baikal_vdu_debugfs_regs, 0},
+};
+
+void baikal_vdu_debugfs_init(struct drm_minor *minor)
+{
+	drm_debugfs_create_files(baikal_vdu_debugfs_list,
+				 ARRAY_SIZE(baikal_vdu_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 000000000000..755d4abeedf7
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drm.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019-2020 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+#ifndef __BAIKAL_VDU_DRM_H__
+#define __BAIKAL_VDU_DRM_H__
+
+#include <drm/drm_gem.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#define VDU_TYPE_HDMI	0
+#define VDU_TYPE_LVDS	1
+
+struct baikal_vdu_private {
+	struct drm_device *drm;
+
+	unsigned int irq;
+	bool irq_enabled;
+
+	struct drm_connector connector;
+	struct drm_crtc crtc;
+	struct drm_encoder encoder;
+	struct drm_panel *panel;
+	struct drm_bridge *bridge;
+	struct drm_plane primary;
+
+	void *regs;
+	struct clk *clk;
+	u32 counters[20];
+	int mode_fixup;
+	int type;
+	u32 ep_count;
+	u32 fb_addr;
+	u32 fb_end;
+
+	struct gpio_desc *enable_gpio;
+};
+
+/* CRTC Functions */
+int baikal_vdu_crtc_create(struct drm_device *dev);
+irqreturn_t baikal_vdu_irq(int irq, void *data);
+
+int baikal_vdu_primary_plane_init(struct drm_device *dev);
+
+/* Connector Functions */
+int baikal_vdu_lvds_connector_create(struct drm_device *dev);
+
+void baikal_vdu_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 000000000000..18fa1762dab0
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2019-2020 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ * All bugs by Alexey Sheplyakov <asheplyakov@altlinux.org>
+ *
+ * This driver is based on ARM PL111 DRM driver
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_aperture.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_fb_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+#define DRIVER_NAME                 "baikal-vdu"
+#define DRIVER_DESC                 "DRM module for Baikal VDU"
+#define DRIVER_DATE                 "20200131"
+
+#define BAIKAL_SMC_SCP_LOG_DISABLE  0x82000200
+
+int mode_fixup = 0;
+
+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,
+};
+
+static const struct drm_encoder_funcs baikal_vdu_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+DEFINE_DRM_GEM_DMA_FOPS(drm_fops);
+
+static struct drm_driver vdu_drm_driver = {
+	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+	.ioctls = NULL,
+	.fops = &drm_fops,
+	.name = DRIVER_NAME,
+	.desc = DRIVER_DESC,
+	.date = DRIVER_DATE,
+	.major = 1,
+	.minor = 0,
+	.patchlevel = 0,
+	DRM_GEM_DMA_DRIVER_OPS,
+#if defined(CONFIG_DEBUG_FS)
+	.debugfs_init = baikal_vdu_debugfs_init,
+#endif
+};
+
+static int vdu_modeset_init(struct drm_device *dev)
+{
+	struct drm_mode_config *mode_config;
+	struct baikal_vdu_private *priv = dev->dev_private;
+	struct arm_smccc_res res;
+	int ret = 0, ep_count = 0;
+
+	if (priv == NULL)
+		return -EINVAL;
+
+	drm_mode_config_init(dev);
+	mode_config = &dev->mode_config;
+	mode_config->funcs = &mode_config_funcs;
+	mode_config->min_width = 1;
+	mode_config->max_width = 4095;
+	mode_config->min_height = 1;
+	mode_config->max_height = 4095;
+
+	ret = baikal_vdu_primary_plane_init(dev);
+	if (ret != 0) {
+		dev_err(dev->dev, "Failed to init primary plane\n");
+		goto out_config;
+	}
+
+	ret = baikal_vdu_crtc_create(dev);
+	if (ret) {
+		dev_err(dev->dev, "Failed to create crtc\n");
+		goto out_config;
+	}
+
+	ret = drm_of_find_panel_or_bridge(dev->dev->of_node, -1, -1,
+					  &priv->panel,
+					  &priv->bridge);
+	if (ret == -EPROBE_DEFER) {
+		dev_info(dev->dev, "Bridge probe deferred\n");
+		goto out_config;
+	}
+	ep_count = of_graph_get_endpoint_count(dev->dev->of_node);
+	if (ep_count <= 0) {
+		dev_err(dev->dev, "no endpoints connected to panel/bridge\n");
+		goto out_config;
+	}
+	priv->ep_count = ep_count;
+	dev_dbg(dev->dev, "panel/bridge has %d endpoints\n", priv->ep_count);
+
+	if (priv->bridge) {
+		struct drm_encoder *encoder = &priv->encoder;
+		ret = drm_encoder_init(dev, encoder, &baikal_vdu_encoder_funcs,
+				       DRM_MODE_ENCODER_NONE, NULL);
+		if (ret) {
+			dev_err(dev->dev, "failed to create DRM encoder\n");
+			goto out_config;
+		}
+		encoder->crtc = &priv->crtc;
+		encoder->possible_crtcs = drm_crtc_mask(encoder->crtc);
+		priv->bridge->encoder = &priv->encoder;
+		ret = drm_bridge_attach(&priv->encoder, priv->bridge, NULL, 0);
+		if (ret) {
+			dev_err(dev->dev, "Failed to attach DRM bridge %d\n", ret);
+			goto out_config;
+		}
+	} else if (priv->panel) {
+		dev_dbg(dev->dev, "panel has %d endpoints\n", priv->ep_count);
+		ret = baikal_vdu_lvds_connector_create(dev);
+		if (ret) {
+			dev_err(dev->dev, "Failed to create DRM connector\n");
+			goto out_config;
+		}
+	} else
+		ret = -EINVAL;
+
+	if (ret) {
+		dev_err(dev->dev, "No bridge or panel attached!\n");
+		goto out_config;
+	}
+
+	priv->clk = clk_get(dev->dev, "pclk");
+	if (IS_ERR(priv->clk)) {
+		dev_err(dev->dev, "fatal: unable to get pclk, err %ld\n", PTR_ERR(priv->clk));
+		ret = PTR_ERR(priv->clk);
+		goto out_config;
+	}
+
+	priv->mode_fixup = mode_fixup;
+
+	drm_aperture_remove_framebuffers(false, &vdu_drm_driver);
+
+	ret = drm_vblank_init(dev, 1);
+	if (ret != 0) {
+		dev_err(dev->dev, "Failed to init vblank\n");
+		goto out_clk;
+	}
+
+	arm_smccc_smc(BAIKAL_SMC_SCP_LOG_DISABLE, 0, 0, 0, 0, 0, 0, 0, &res);
+
+	drm_mode_config_reset(dev);
+
+	drm_kms_helper_poll_init(dev);
+
+	ret = drm_dev_register(dev, 0);
+	if (ret)
+		goto out_clk;
+
+	drm_fbdev_generic_setup(dev, 32);
+	goto finish;
+
+out_clk:
+	clk_put(priv->clk);
+out_config:
+	drm_mode_config_cleanup(dev);
+finish:
+	return ret;
+}
+
+
+static int baikal_vdu_irq_install(struct baikal_vdu_private *priv, int irq)
+{
+	int ret;
+	ret= request_irq(irq, baikal_vdu_irq, 0, DRIVER_NAME, priv->drm);
+	if (ret < 0)
+		return ret;
+	priv->irq_enabled = true;
+	return 0;
+}
+
+static void baikal_vdu_irq_uninstall(struct baikal_vdu_private *priv)
+{
+	if (priv->irq_enabled) {
+		priv->irq_enabled = false;
+		disable_irq(priv->irq);
+		free_irq(priv->irq, priv->drm);
+	}
+}
+
+static int vdu_maybe_enable_lvds(struct baikal_vdu_private *vdu)
+{
+	int err = 0;
+	struct device *dev;
+	if (!vdu->drm) {
+		pr_err("%s: vdu->drm is NULL\n", __func__);
+		return -EINVAL;
+	}
+	dev = vdu->drm->dev;
+
+	vdu->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
+	if (IS_ERR(vdu->enable_gpio)) {
+		err = (int)PTR_ERR(vdu->enable_gpio);
+		dev_err(dev, "failed to get enable-gpios, error %d\n", err);
+		vdu->enable_gpio = NULL;
+		return err;
+	}
+	if (vdu->enable_gpio) {
+		dev_dbg(dev, "%s: setting enable-gpio\n", __func__);
+		gpiod_set_value_cansleep(vdu->enable_gpio, 1);
+	} else {
+		dev_dbg(dev, "%s: no enable-gpios, assuming it's handled by panel-lvds\n", __func__);
+	}
+	return 0;
+}
+
+static int baikal_vdu_drm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct baikal_vdu_private *priv;
+	struct drm_device *drm;
+	struct resource *mem;
+	int irq;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	drm = drm_dev_alloc(&vdu_drm_driver, dev);
+	if (IS_ERR(drm))
+		return PTR_ERR(drm);
+	platform_set_drvdata(pdev, drm);
+	priv->drm = drm;
+	drm->dev_private = priv;
+
+	if (!(mem = platform_get_resource(pdev, IORESOURCE_MEM, 0))) {
+		dev_err(dev, "%s no MMIO resource specified\n", __func__);
+		return -EINVAL;
+	}
+
+	priv->regs = devm_ioremap_resource(dev, mem);
+	if (IS_ERR(priv->regs)) {
+		dev_err(dev, "%s MMIO allocation failed\n", __func__);
+		return PTR_ERR(priv->regs);
+	}
+
+	/* turn off interrupts before requesting the irq */
+	writel(0, priv->regs + IMR);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "%s no IRQ resource specified\n", __func__);
+		return -EINVAL;
+	}
+	priv->irq = irq;
+
+	ret = baikal_vdu_irq_install(priv, irq);
+	if (ret != 0) {
+		dev_err(dev, "%s IRQ %d allocation failed\n", __func__, irq);
+		return ret;
+	}
+
+	if (pdev->dev.of_node && of_property_read_bool(pdev->dev.of_node, "lvds-out")) {
+		priv->type = VDU_TYPE_LVDS;
+		if (of_property_read_u32(pdev->dev.of_node, "num-lanes", &priv->ep_count))
+			priv->ep_count = 1;
+	}
+	else
+		priv->type = VDU_TYPE_HDMI;
+
+	ret = vdu_modeset_init(drm);
+	if (ret != 0) {
+		dev_err(dev, "Failed to init modeset\n");
+		goto dev_unref;
+	}
+
+	ret = vdu_maybe_enable_lvds(priv);
+	if (ret != 0) {
+		dev_err(dev, "failed to enable LVDS\n");
+	}
+
+	return 0;
+
+dev_unref:
+	writel(0, priv->regs + IMR);
+	writel(0x3ffff, priv->regs + ISR);
+	baikal_vdu_irq_uninstall(priv);
+	drm->dev_private = NULL;
+	drm_dev_put(drm);
+	return ret;
+}
+
+static int baikal_vdu_drm_remove(struct platform_device *pdev)
+{
+	struct drm_device *drm;
+	struct baikal_vdu_private *priv;
+
+	drm = platform_get_drvdata(pdev);
+	if (!drm) {
+		return -1;
+	}
+	priv = drm->dev_private;
+
+	drm_dev_unregister(drm);
+	drm_mode_config_cleanup(drm);
+	baikal_vdu_irq_uninstall(priv);
+	drm->dev_private = NULL;
+	drm_dev_put(drm);
+
+	return 0;
+}
+
+static const struct of_device_id baikal_vdu_of_match[] = {
+    { .compatible = "baikal,vdu" },
+    { },
+};
+MODULE_DEVICE_TABLE(of, baikal_vdu_of_match);
+
+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,
+    },
+};
+
+module_param(mode_fixup, 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_plane.c b/drivers/gpu/drm/baikal/baikal_vdu_plane.c
new file mode 100644
index 000000000000..0be1e0967914
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_plane.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2019-2020 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (C) 2011 Texas Instruments
+ * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms of
+ * such GNU licence.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.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_framebuffer.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "baikal_vdu_drm.h"
+#include "baikal_vdu_regs.h"
+
+static int baikal_vdu_primary_plane_atomic_check(struct drm_plane *plane,
+						 struct drm_atomic_state *atomic_state)
+{
+	struct drm_device *dev = plane->dev;
+	struct baikal_vdu_private *priv = dev->dev_private;
+	struct drm_crtc_state *crtc_state;
+	struct drm_plane_state *state;
+	struct drm_display_mode *mode;
+	int rate, ret;
+	u32 cntl;
+
+	state = drm_atomic_get_new_plane_state(atomic_state, plane);
+	if (!state || !state->crtc)
+		return 0;
+
+	crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
+	if (IS_ERR(crtc_state)) {
+		ret = PTR_ERR(crtc_state);
+		dev_warn(dev->dev, "failed to get crtc_state: %d\n", ret);
+		return ret;
+	}
+	mode = &crtc_state->adjusted_mode;
+	rate = mode->clock * 1000;
+	if (rate == clk_get_rate(priv->clk))
+		return 0;
+
+	/* hold clock domain reset; disable clocking */
+	writel(0, priv->regs + PCTR);
+
+	if (__clk_is_enabled(priv->clk))
+		clk_disable_unprepare(priv->clk);
+	ret = clk_set_rate(priv->clk, rate);
+	DRM_DEV_DEBUG_DRIVER(dev->dev, "Requested pixel clock is %d Hz\n", rate);
+
+	if (ret < 0) {
+		DRM_ERROR("Cannot set desired pixel clock (%d Hz)\n",
+			  rate);
+		ret = -EINVAL;
+	} else {
+		clk_prepare_enable(priv->clk);
+		if (__clk_is_enabled(priv->clk))
+			ret = 0;
+		else {
+			DRM_ERROR("PLL could not lock at desired frequency (%d Hz)\n",
+			  rate);
+			ret = -EINVAL;
+		}
+	}
+
+	/* release clock domain reset; enable clocking */
+	cntl = readl(priv->regs + PCTR);
+	cntl |= PCTR_PCR + PCTR_PCI;
+	writel(cntl, priv->regs + PCTR);
+
+	return ret;
+}
+
+static void baikal_vdu_primary_plane_atomic_update(struct drm_plane *plane,
+						   struct drm_atomic_state *old_state)
+{
+	struct drm_device *dev = plane->dev;
+	struct baikal_vdu_private *priv = dev->dev_private;
+	struct drm_plane_state *state = plane->state;
+	struct drm_framebuffer *fb = state->fb;
+	u32 cntl, addr, end;
+
+	if (!fb)
+		return;
+
+	addr = drm_fb_dma_get_gem_addr(fb, state, 0);
+	priv->fb_addr = addr & 0xfffffff8;
+
+	cntl = readl(priv->regs + CR1);
+	cntl &= ~CR1_BPP_MASK;
+
+	/* Note that the the hardware's format reader takes 'r' from
+	 * the low bit, while DRM formats list channels from high bit
+	 * to low bit as you read left to right.
+	 */
+	switch (fb->format->format) {
+	case DRM_FORMAT_BGR888:
+		cntl |= CR1_BPP24 | CR1_FBP | CR1_BGR;
+		break;
+	case DRM_FORMAT_RGB888:
+		cntl |= CR1_BPP24 | CR1_FBP;
+		break;
+	case DRM_FORMAT_ABGR8888:
+	case DRM_FORMAT_XBGR8888:
+		cntl |= CR1_BPP24 | CR1_BGR;
+		break;
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_XRGB8888:
+		cntl |= CR1_BPP24;
+		break;
+	case DRM_FORMAT_BGR565:
+		cntl |= CR1_BPP16_565 | CR1_BGR;
+		break;
+	case DRM_FORMAT_RGB565:
+		cntl |= CR1_BPP16_565;
+		break;
+	case DRM_FORMAT_ABGR1555:
+	case DRM_FORMAT_XBGR1555:
+		cntl |= CR1_BPP16_555 | CR1_BGR;
+		break;
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_XRGB1555:
+		cntl |= CR1_BPP16_555;
+		break;
+	default:
+		WARN_ONCE(true, "Unknown FB format 0x%08x, set XRGB8888 instead\n",
+				fb->format->format);
+		cntl |= CR1_BPP24;
+		break;
+	}
+
+	writel(priv->fb_addr, priv->regs + DBAR);
+	end = ((priv->fb_addr + fb->height * fb->pitches[0] - 1) & MRR_DEAR_MRR_MASK) | \
+		MRR_OUTSTND_RQ(4);
+
+	if (priv->fb_end < end) {
+		writel(end, priv->regs + MRR);
+		priv->fb_end = end;
+	}
+	writel(cntl, priv->regs + CR1);
+}
+
+static const struct drm_plane_helper_funcs baikal_vdu_primary_plane_helper_funcs = {
+	.atomic_check = baikal_vdu_primary_plane_atomic_check,
+	.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 drm_device *drm)
+{
+	struct baikal_vdu_private *priv = drm->dev_private;
+	struct drm_plane *plane = &priv->primary;
+	static const u32 formats[] = {
+		DRM_FORMAT_BGR888,
+		DRM_FORMAT_RGB888,
+		DRM_FORMAT_ABGR8888,
+		DRM_FORMAT_XBGR8888,
+		DRM_FORMAT_ARGB8888,
+		DRM_FORMAT_XRGB8888,
+		DRM_FORMAT_BGR565,
+		DRM_FORMAT_RGB565,
+		DRM_FORMAT_ABGR1555,
+		DRM_FORMAT_XBGR1555,
+		DRM_FORMAT_ARGB1555,
+		DRM_FORMAT_XRGB1555,
+	};
+	int ret;
+
+	ret = drm_universal_plane_init(drm, plane, 0,
+				       &baikal_vdu_primary_plane_funcs,
+				       formats,
+				       ARRAY_SIZE(formats),
+				       NULL,
+				       DRM_PLANE_TYPE_PRIMARY,
+				       NULL);
+	if (ret)
+		return ret;
+
+	drm_plane_helper_add(plane, &baikal_vdu_primary_plane_helper_funcs);
+
+	return 0;
+}
+
+
diff --git a/drivers/gpu/drm/baikal/baikal_vdu_regs.h b/drivers/gpu/drm/baikal/baikal_vdu_regs.h
new file mode 100644
index 000000000000..5553fcac5fec
--- /dev/null
+++ b/drivers/gpu/drm/baikal/baikal_vdu_regs.h
@@ -0,0 +1,139 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019-2021 Baikal Electronics JSC
+ *
+ * Author: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
+ *
+ * Parts of this file were based on sources as follows:
+ *
+ *   David A Rusling
+ *   Copyright (C) 2001 ARM Limited
+ */
+
+#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 HVTER       0x044
+#define HPPLOR      0x048
+#define GPIOR       0x1F8
+#define OWER        0x600
+#define OWXSER0     0x604
+#define OWYSER0     0x608
+#define OWDBAR0     0x60C
+#define OWDCAR0     0x610
+#define OWDEAR0     0x614
+#define OWXSER1     0x618
+#define OWYSER1     0x61C
+#define OWDBAR1     0x620
+#define OWDCAR1     0x624
+#define OWDEAR1     0x628
+#define MRR         0xFFC
+
+#define INTR_BAU    BIT(7)
+#define INTR_VCT    BIT(6)
+#define INTR_MBE    BIT(5)
+#define INTR_FER    BIT(4)
+
+#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_LCD18       (0 << 13)
+#define CR1_OPS_LCD24       (1 << 13)
+#define CR1_OPS_565         (0 << 12)
+#define CR1_OPS_555         (1 << 12)
+#define CR1_VSP             BIT(11)
+#define CR1_HSP             BIT(10)
+#define CR1_DEP             BIT(8)
+#define CR1_BGR             BIT(5)
+#define CR1_BPP_MASK        GENMASK(4, 2)
+#define CR1_BPP1            (0 << 2)
+#define CR1_BPP2            (1 << 2)
+#define CR1_BPP4            (2 << 2)
+#define CR1_BPP8            (3 << 2)
+#define CR1_BPP16           (4 << 2)
+#define CR1_BPP18           (5 << 2)
+#define CR1_BPP24           (6 << 2)
+#define CR1_LCE             BIT(0)
+
+#define CR1_BPP16_555 ((CR1_BPP16) | (CR1_OPS_555))
+#define CR1_BPP16_565 ((CR1_BPP16) | (CR1_OPS_565))
+
+#define VTR1_VBP_MASK       GENMASK(23, 16)
+#define VTR1_VBP(x)         ((x) << 16)
+#define VTR1_VBP_LSB_WIDTH  8
+#define VTR1_VFP_MASK       GENMASK(15, 8)
+#define VTR1_VFP(x)         ((x) << 8)
+#define VTR1_VFP_LSB_WIDTH  8
+#define VTR1_VSW_MASK       GENMASK(7, 0)
+#define VTR1_VSW(x)         ((x) << 0)
+#define VTR1_VSW_LSB_WIDTH  8
+
+#define VTR2_LPP_MASK       GENMASK(11, 0)
+
+#define HTR_HSW_MASK        GENMASK(31, 24)
+#define HTR_HSW(x)          ((x) << 24)
+#define HTR_HSW_LSB_WIDTH   8
+#define HTR_HBP_MASK        GENMASK(23, 16)
+#define HTR_HBP(x)          ((x) << 16)
+#define HTR_HBP_LSB_WIDTH   8
+#define HTR_PPL_MASK        GENMASK(15, 8)
+#define HTR_PPL(x)          ((x) << 8)
+#define HTR_HFP_MASK        GENMASK(7, 0)
+#define HTR_HFP(x)          ((x) << 0)
+#define HTR_HFP_LSB_WIDTH   8
+
+#define PCTR_PCI2           BIT(11)
+#define PCTR_PCR            BIT(10)
+#define PCTR_PCI            BIT(9)
+#define PCTR_PCB            BIT(8)
+#define PCTR_PCD_MASK       GENMASK(7, 0)
+#define PCTR_MAX_PCD        128
+
+#define ISCR_VSC_OFF        0x0
+#define ISCR_VSC_VSW        0x4
+#define ISCR_VSC_VBP        0x5
+#define ISCR_VSC_VACTIVE    0x6
+#define ISCR_VSC_VFP        0x7
+
+#define 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)
+
+#endif /* __BAIKAL_VDU_REGS_H__ */
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index b6b83c4e9083..4ca1a26630a2 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -173,6 +173,13 @@ config DRM_LVDS_CODEC
 	  Support for transparent LVDS encoders and decoders that don't
 	  require any configuration.
 
+config DRM_BAIKAL_HDMI
+	tristate "Baikal-M HDMI transmitter"
+	default y if ARCH_BAIKAL
+	select DRM_DW_HDMI
+	help
+	  Choose this if you want to use HDMI on Baikal-M.
+
 config DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW
 	tristate "MegaChips stdp4028-ge-b850v3-fw and stdp2690-ge-b850v3-fw"
 	depends on OF
-- 
2.33.5



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

* [d-kernel] [PATCH 21/32] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (18 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 20/32] drm: added Baikal-M SoC video display unit driver Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 22/32] dt-bindings: dw-hdmi: added ahb-audio-regshift Alexey Sheplyakov
                   ` (10 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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



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

* [d-kernel] [PATCH 22/32] dt-bindings: dw-hdmi: added ahb-audio-regshift
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (19 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 21/32] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 23/32] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M Alexey Sheplyakov
                   ` (9 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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 4b7e54a8f037..85b3ac7d1057 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.33.5



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

* [d-kernel] [PATCH 23/32] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (20 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 22/32] dt-bindings: dw-hdmi: added ahb-audio-regshift Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 24/32] drm/panfrost: forcibly set dma-coherent on Baikal-M Alexey Sheplyakov
                   ` (8 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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 49612efe0f1d..161a0105d18c 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3577,6 +3577,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.33.5



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

* [d-kernel] [PATCH 24/32] drm/panfrost: forcibly set dma-coherent on Baikal-M
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (21 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 23/32] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 25/32] drm/panfrost: disable devfreq " Alexey Sheplyakov
                   ` (7 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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 2fa5afe21288..38ba755a0c3d 100644
--- a/drivers/gpu/drm/panfrost/panfrost_drv.c
+++ b/drivers/gpu/drm/panfrost/panfrost_drv.c
@@ -563,6 +563,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.33.5



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

* [d-kernel] [PATCH 25/32] drm/panfrost: disable devfreq on Baikal-M
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (22 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 24/32] drm/panfrost: forcibly set dma-coherent on Baikal-M Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 26/32] ALSA: hda: Baikal-M support Alexey Sheplyakov
                   ` (6 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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 fe5f12f16a63..a0eef1309f54 100644
--- a/drivers/gpu/drm/panfrost/panfrost_devfreq.c
+++ b/drivers/gpu/drm/panfrost/panfrost_devfreq.c
@@ -100,6 +100,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 = devm_pm_opp_set_regulators(dev, pfdev->comp->supply_names);
 	if (ret) {
-- 
2.33.5



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

* [d-kernel] [PATCH 26/32] ALSA: hda: Baikal-M support
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (23 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 25/32] drm/panfrost: disable devfreq " Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 27/32] PCI: pcie-baikal: driver for Baikal-M with new firmware Alexey Sheplyakov
                   ` (5 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

Known issues:
* Probe fails to detect any outputs if headphones are connected
  during the probe.
* Device must be configured as output only if no microphone is
  connected. Otherwise a process which tries to *output* audio
  blocks forever.

Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-feature-Baikal-M
---
 sound/hda/hdac_controller.c    |  19 +-
 sound/pci/hda/Kconfig          |  14 +
 sound/pci/hda/Makefile         |   2 +
 sound/pci/hda/hda_baikal.c     | 525 +++++++++++++++++++++++++++++++++
 sound/pci/hda/hda_controller.c |  19 +-
 5 files changed, 573 insertions(+), 6 deletions(-)
 create mode 100644 sound/pci/hda/hda_baikal.c

diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c
index 9a60bfdb39ba..de5608ae5570 100644
--- a/sound/hda/hdac_controller.c
+++ b/sound/hda/hdac_controller.c
@@ -6,6 +6,7 @@
 #include <linux/kernel.h>
 #include <linux/delay.h>
 #include <linux/export.h>
+#include <linux/of.h>
 #include <sound/core.h>
 #include <sound/hdaudio.h>
 #include <sound/hda_register.h>
@@ -31,7 +32,8 @@ static void azx_clear_corbrp(struct hdac_bus *bus)
 			break;
 		udelay(1);
 	}
-	if (timeout <= 0)
+	if (timeout <= 0
+			&& !of_device_is_compatible(bus->dev->of_node, "be,cw-hda"))
 		dev_err(bus->dev, "CORB reset timeout#2, CORBRP = %d\n",
 			snd_hdac_chip_readw(bus, CORBRP));
 }
@@ -42,6 +44,7 @@ static void azx_clear_corbrp(struct hdac_bus *bus)
  */
 void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus)
 {
+	u8 rirbctl;
 	WARN_ON_ONCE(!bus->rb.area);
 
 	spin_lock_irq(&bus->reg_lock);
@@ -78,8 +81,13 @@ void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus)
 	snd_hdac_chip_writew(bus, RIRBWP, AZX_RIRBWP_RST);
 	/* set N=1, get RIRB response interrupt for new entry */
 	snd_hdac_chip_writew(bus, RINTCNT, 1);
-	/* enable rirb dma and response irq */
-	snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN);
+	rirbctl = AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN;
+	if (of_device_is_compatible(bus->dev->of_node, "be,cw-hda")) {
+		/* response IRQ does not work in Baikal-M HDA controller */
+		rirbctl = AZX_RBCTL_DMA_EN;
+	}
+	/* enable rirb dma and response irq (if supported) */
+	snd_hdac_chip_writeb(bus, RIRBCTL, rirbctl);
 	/* Accept unsolicited responses */
 	snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL);
 	spin_unlock_irq(&bus->reg_lock);
@@ -144,6 +152,11 @@ int snd_hdac_bus_send_cmd(struct hdac_bus *bus, unsigned int val)
 	unsigned int addr = azx_command_addr(val);
 	unsigned int wp, rp;
 
+	if (of_device_is_compatible(bus->dev->of_node, "be,cw-hda")) {
+		/* force first codec address because wrong codec init */
+		val |= 0x10000000U;
+	}
+
 	spin_lock_irq(&bus->reg_lock);
 
 	bus->last_cmd[azx_command_addr(val)] = val;
diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig
index a8e8cf98befa..74f1245bd726 100644
--- a/sound/pci/hda/Kconfig
+++ b/sound/pci/hda/Kconfig
@@ -42,6 +42,20 @@ 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_M
+	tristate "Baikal-M 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
+	  Baikalm-M SoC
+
+
+	  This option enables support for the HD Audio controller
+	  present in Baikal-M SoC, used to communicate audio
+	  to the mezzanine board outputs.
+
 if SND_HDA
 
 config SND_HDA_HWDEP
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile
index 00d306104484..4aca7a2ab85f 100644
--- a/sound/pci/hda/Makefile
+++ b/sound/pci/hda/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 snd-hda-intel-objs := hda_intel.o
 snd-hda-tegra-objs := hda_tegra.o
+snd-hda-baikal-m-objs := hda_baikal.o
 
 snd-hda-codec-y := hda_bind.o hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o
 snd-hda-codec-y += hda_controller.o
@@ -62,3 +63,4 @@ obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.o
 # when built in kernel
 obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o
 obj-$(CONFIG_SND_HDA_TEGRA) += snd-hda-tegra.o
+obj-$(CONFIG_SND_HDA_BAIKAL_M) += snd-hda-baikal-m.o
diff --git a/sound/pci/hda/hda_baikal.c b/sound/pci/hda/hda_baikal.c
new file mode 100644
index 000000000000..5032d78e4f7f
--- /dev/null
+++ b/sound/pci/hda/hda_baikal.c
@@ -0,0 +1,525 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *
+ * Implementation of primary ALSA driver code base for Baikal-M HDA controller.
+ */
+
+#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/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 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";
+	struct device_node *np = pdev->dev.of_node;
+
+	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);
+
+	/* force polling mode, because RIRB interrupts don't working */
+	bus->polling_mode = 1;
+
+	/* 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 */
+	sname = of_get_property(np, "baikal,model", NULL);
+	if (!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 void hda_baikal_probe_work(struct work_struct *work);
+
+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 = 3; /* two codecs: first and second bits */
+
+	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.core.needs_damn_long_delay = 1;
+	chip->bus.core.aligned_mmio = 1;
+
+	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;
+}
+
+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 platform_device *pdev = to_platform_device(hda->dev);
+	int err;
+
+	pm_runtime_get_sync(hda->dev);
+	err = hda_baikal_first_init(chip, pdev);
+	if (err < 0)
+		goto out_free;
+
+	/* create codec instances */
+	err = azx_probe_codecs(chip, 1);
+	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 int hda_baikal_remove(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = snd_card_free(dev_get_drvdata(&pdev->dev));
+	pm_runtime_disable(&pdev->dev);
+
+	return ret;
+}
+
+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 = "be,cw-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 v2");
diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c
index 0ff286b7b66b..f588e961f45d 100644
--- a/sound/pci/hda/hda_controller.c
+++ b/sound/pci/hda/hda_controller.c
@@ -1194,21 +1194,25 @@ int azx_probe_codecs(struct azx *chip, unsigned int max_slots)
 {
 	struct hdac_bus *bus = azx_bus(chip);
 	int c, codecs, err;
+	int retry_count, max_probe_retries = 1;
 
 	codecs = 0;
 	if (!max_slots)
 		max_slots = AZX_DEFAULT_CODECS;
 
+	if (of_device_is_compatible(chip->card->dev->of_node, "be,cw-hda"))
+		max_probe_retries = 100;
+
 	/* First try to probe all given codec slots */
 	for (c = 0; c < max_slots; c++) {
 		if ((bus->codec_mask & (1 << c)) & chip->codec_probe_mask) {
+			retry_count = 0;
+probe_retry:
 			if (probe_codec(chip, c) < 0) {
+				retry_count++;
 				/* Some BIOSen give you wrong codec addresses
 				 * that don't exist
 				 */
-				dev_warn(chip->card->dev,
-					 "Codec #%d probe error; disabling it...\n", c);
-				bus->codec_mask &= ~(1 << c);
 				/* More badly, accessing to a non-existing
 				 * codec often screws up the controller chip,
 				 * and disturbs the further communications.
@@ -1218,6 +1222,15 @@ int azx_probe_codecs(struct azx *chip, unsigned int max_slots)
 				 */
 				azx_stop_chip(chip);
 				azx_init_chip(chip, true);
+				if (retry_count < max_probe_retries)
+					goto probe_retry;
+				dev_warn(chip->card->dev,
+					 "Codec #%d probe error; disabling it...\n", c);
+				bus->codec_mask &= ~(1 << c);
+			} else {
+				dev_info(chip->card->dev,
+					 "Codec #%d successfully probed, retry count = %d\n",
+					 c, retry_count);
 			}
 		}
 	}
-- 
2.33.5



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

* [d-kernel] [PATCH 27/32] PCI: pcie-baikal: driver for Baikal-M with new firmware
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (24 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 26/32] ALSA: hda: Baikal-M support Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 28/32] (BROKEN) dwc-i2s: support Baikal-M SoC Alexey Sheplyakov
                   ` (4 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

This driver works with firmware from SDK-M 5.5. dw-pcie should be
used with older firmware (such as SDK-M 5.3)

Note: this driver uses `baikal,bm1000-pcie` as a compatible string.
Hence the kernel with this driver can run on Baikal-M boards with
older firmware (and use dw-pcie driver instead of this one).

Known to work with Delta Computers' Rhodeola board, firmware
5.5_rdl_220425_1_debug.cap

Co-developed-by: Pavel Parkhomenko <pavel.parkhomenko@baikalelectronics.ru>
Co-developed-by: Mikhail Ivanov <michail.ivanov@baikalelectronics.ru>
Co-developed-by: Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
X-DONTUPSTREAM
X-feature-Baikal-M
---
 drivers/pci/controller/dwc/Kconfig       |  12 +
 drivers/pci/controller/dwc/Makefile      |   1 +
 drivers/pci/controller/dwc/pcie-baikal.c | 740 +++++++++++++++++++++++
 3 files changed, 753 insertions(+)
 create mode 100644 drivers/pci/controller/dwc/pcie-baikal.c

diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
index 62ce3abf0f19..b6a1245122d9 100644
--- a/drivers/pci/controller/dwc/Kconfig
+++ b/drivers/pci/controller/dwc/Kconfig
@@ -272,6 +272,18 @@ config PCIE_KEEMBAY_EP
 	  The PCIe controller is based on DesignWare Hardware and uses
 	  DesignWare core functions.
 
+config PCI_BAIKAL
+	bool "Baikal SoC PCIe controller"
+	depends on ARCH_BAIKAL
+	depends on OF && HAS_IOMEM
+	select PCIE_DW_HOST
+	select PCI_MSI_IRQ_DOMAIN
+	select PCI_QUIRKS if ACPI
+	help
+	  Enables support for the PCIe controller in the Baikal SoC. There are
+	  three instances of PCIe controller in Baikal-M. Two of the controllers
+	  support PCIe 3.0 x4 and the remaining one supports PCIe 3.0 x8.
+
 config PCIE_KIRIN
 	depends on OF && (ARM64 || COMPILE_TEST)
 	tristate "HiSilicon Kirin series SoCs PCIe controllers"
diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
index 8ba7b67f5e50..db5f526a60f0 100644
--- a/drivers/pci/controller/dwc/Makefile
+++ b/drivers/pci/controller/dwc/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_PCIE_TEGRA194) += pcie-tegra194.o
 obj-$(CONFIG_PCIE_UNIPHIER) += pcie-uniphier.o
 obj-$(CONFIG_PCIE_UNIPHIER_EP) += pcie-uniphier-ep.o
 obj-$(CONFIG_PCIE_VISCONTI_HOST) += pcie-visconti.o
+obj-$(CONFIG_PCI_BAIKAL) += pcie-baikal.o
 
 # The following drivers are for devices that use the generic ACPI
 # pci_root.c driver but don't support standard ECAM config access.
diff --git a/drivers/pci/controller/dwc/pcie-baikal.c b/drivers/pci/controller/dwc/pcie-baikal.c
new file mode 100644
index 000000000000..340a6b12cd4e
--- /dev/null
+++ b/drivers/pci/controller/dwc/pcie-baikal.c
@@ -0,0 +1,740 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PCIe RC driver for Baikal-M SoC
+ *
+ * Copyright (C) 2019-2021 Baikal Electronics, JSC
+ * Authors: Pavel Parkhomenko <pavel.parkhomenko@baikalelectronics.ru>
+ *          Mikhail Ivanov <michail.ivanov@baikalelectronics.ru>
+ *          Aleksandr Efimov <alexander.efimov@baikalelectronics.ru>
+ */
+
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irqchip/arm-gic-v3.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/pci.h>
+#include <linux/pci-ecam.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/resource.h>
+
+#include "pcie-designware.h"
+
+struct baikal_pcie_rc {
+	struct dw_pcie	 *pcie;
+	unsigned	 num;
+	struct regmap	 *lcru;
+	struct gpio_desc *reset_gpio;
+	bool		 reset_active_low;
+	char		 reset_name[32];
+	bool		 retrained;
+};
+
+#define to_baikal_pcie_rc(x)	dev_get_drvdata((x)->dev)
+
+#define PCIE_DEVICE_CONTROL_DEVICE_STATUS_REG	0x78
+#define PCIE_CAP_CORR_ERR_REPORT_EN		BIT(0)
+#define PCIE_CAP_NON_FATAL_ERR_REPORT_EN	BIT(1)
+#define PCIE_CAP_FATAL_ERR_REPORT_EN		BIT(2)
+#define PCIE_CAP_UNSUPPORT_REQ_REP_EN		BIT(3)
+
+#define PCIE_LINK_CAPABILITIES_REG		0x7c
+
+#define PCIE_LINK_CONTROL_LINK_STATUS_REG	0x80
+#define PCIE_CAP_LINK_SPEED_MASK		0xf0000
+#define PCIE_CAP_LINK_SPEED_SHIFT		16
+#define PCIE_CAP_NEGO_LINK_WIDTH_MASK		0x3f00000
+#define PCIE_CAP_NEGO_LINK_WIDTH_SHIFT		20
+#define PCIE_CAP_LINK_TRAINING			BIT(27)
+
+#define PCIE_ROOT_CONTROL_ROOT_CAPABILITIES_REG	0x8c
+#define PCIE_CAP_SYS_ERR_ON_CORR_ERR_EN		BIT(0)
+#define PCIE_CAP_SYS_ERR_ON_NON_FATAL_ERR_EN	BIT(1)
+#define PCIE_CAP_SYS_ERR_ON_FATAL_ERR_EN	BIT(2)
+#define PCIE_CAP_PME_INT_EN			BIT(3)
+
+#define PCIE_LINK_CONTROL2_LINK_STATUS2_REG	0xa0
+#define PCIE_CAP_TARGET_LINK_SPEED_MASK		0xf
+
+#define PCIE_UNCORR_ERR_STATUS_REG		0x104
+#define PCIE_CORR_ERR_STATUS_REG		0x110
+
+#define PCIE_ROOT_ERR_CMD_REG			0x12c
+#define PCIE_CORR_ERR_REPORTING_EN		BIT(0)
+#define PCIE_NON_FATAL_ERR_REPORTING_EN		BIT(1)
+#define PCIE_FATAL_ERR_REPORTING_EN		BIT(2)
+
+#define PCIE_ROOT_ERR_STATUS_REG		0x130
+
+#define PCIE_GEN2_CTRL_REG			0x80c
+#define PCIE_DIRECT_SPEED_CHANGE		BIT(17)
+
+#define PCIE_IATU_VIEWPORT_REG			0x900
+#define PCIE_IATU_REGION_OUTBOUND		0
+#define PCIE_IATU_REGION_CTRL_2_REG		0x908
+#define PCIE_IATU_REGION_CTRL_2_REG_SHIFT_MODE	BIT(28)
+
+#define BAIKAL_LCRU_PCIE_RESET_BASE		0x50000
+#define BAIKAL_LCRU_PCIE_RESET(x)		((x * 0x20) + BAIKAL_LCRU_PCIE_RESET_BASE)
+#define BAIKAL_PCIE_PHY_RST			BIT(0)
+#define BAIKAL_PCIE_PIPE_RST			BIT(4) /* x4 controllers only */
+#define BAIKAL_PCIE_PIPE0_RST			BIT(4) /* x8 controller only */
+#define BAIKAL_PCIE_PIPE1_RST			BIT(5) /* x8 controller only */
+#define BAIKAL_PCIE_CORE_RST			BIT(8)
+#define BAIKAL_PCIE_PWR_RST			BIT(9)
+#define BAIKAL_PCIE_STICKY_RST			BIT(10)
+#define BAIKAL_PCIE_NONSTICKY_RST		BIT(11)
+#define BAIKAL_PCIE_HOT_RST			BIT(12)
+#define BAIKAL_PCIE_ADB_PWRDWN			BIT(13)
+
+#define BAIKAL_LCRU_PCIE_STATUS_BASE		0x50004
+#define BAIKAL_LCRU_PCIE_STATUS(x)		((x * 0x20) + BAIKAL_LCRU_PCIE_STATUS_BASE)
+#define BAIKAL_PCIE_LTSSM_MASK			0x3f
+#define BAIKAL_PCIE_LTSSM_STATE_L0		0x11
+
+#define BAIKAL_LCRU_PCIE_GEN_CTL_BASE		0x50008
+#define BAIKAL_LCRU_PCIE_GEN_CTL(x)		((x * 0x20) + BAIKAL_LCRU_PCIE_GEN_CTL_BASE)
+#define BAIKAL_PCIE_LTSSM_ENABLE		BIT(1)
+#define BAIKAL_PCIE_DBI2_MODE			BIT(2)
+#define BAIKAL_PCIE_PHY_MGMT_ENABLE		BIT(3)
+
+#define BAIKAL_LCRU_PCIE_MSI_TRANS_CTL2		0x500f8
+#define BAIKAL_LCRU_PCIE_MSI_TRANS_CTL2_PCIE_MSI_TRANS_EN(x)	BIT(9 + (x))
+#define BAIKAL_LCRU_PCIE_MSI_TRANS_CTL2_PCIE_RCNUM(x)		((x) << (2 * (x)))
+#define BAIKAL_LCRU_PCIE_MSI_TRANS_CTL2_PCIE_RCNUM_MASK(x)	((3) << (2 * (x)))
+
+static int baikal_pcie_link_up(struct dw_pcie *pcie)
+{
+	struct baikal_pcie_rc *rc = to_baikal_pcie_rc(pcie);
+	u32 reg;
+
+	regmap_read(rc->lcru, BAIKAL_LCRU_PCIE_GEN_CTL(rc->num), &reg);
+	if (!(reg & BAIKAL_PCIE_LTSSM_ENABLE)) {
+		return 0;
+	}
+
+	regmap_read(rc->lcru, BAIKAL_LCRU_PCIE_STATUS(rc->num), &reg);
+	return (reg & BAIKAL_PCIE_LTSSM_MASK) == BAIKAL_PCIE_LTSSM_STATE_L0;
+}
+
+static int baikal_pcie_host_init(struct dw_pcie_rp *pp)
+{
+	struct dw_pcie *pcie = to_dw_pcie_from_pp(pp);
+	struct baikal_pcie_rc *rc = to_baikal_pcie_rc(pcie);
+	struct device *dev = pcie->dev;
+	int err;
+	int linkup;
+	unsigned idx;
+	u32 reg;
+
+	/* Disable access to PHY registers and DBI2 mode */
+	regmap_read(rc->lcru, BAIKAL_LCRU_PCIE_GEN_CTL(rc->num), &reg);
+	reg &= ~(BAIKAL_PCIE_PHY_MGMT_ENABLE |
+		 BAIKAL_PCIE_DBI2_MODE);
+	regmap_write(rc->lcru, BAIKAL_LCRU_PCIE_GEN_CTL(rc->num), reg);
+
+	rc->retrained = false;
+	linkup = baikal_pcie_link_up(pcie);
+
+	/* If link is not established yet, reset the RC */
+	if (!linkup) {
+		/* Disable link training */
+		regmap_read(rc->lcru, BAIKAL_LCRU_PCIE_GEN_CTL(rc->num), &reg);
+		reg &= ~BAIKAL_PCIE_LTSSM_ENABLE;
+		regmap_write(rc->lcru, BAIKAL_LCRU_PCIE_GEN_CTL(rc->num), reg);
+
+		/* Assert PERST pin */
+		if (rc->reset_gpio != NULL) {
+			unsigned long gpio_flags;
+
+			if (rc->reset_active_low) {
+				gpio_flags = GPIOF_ACTIVE_LOW |
+					     GPIOF_OUT_INIT_LOW;
+			} else {
+				gpio_flags = GPIOF_OUT_INIT_HIGH;
+			}
+
+			err = devm_gpio_request_one(dev,
+						   desc_to_gpio(rc->reset_gpio),
+						   gpio_flags, rc->reset_name);
+			if (err) {
+				dev_err(dev, "request GPIO failed (%d)\n", err);
+				return err;
+			}
+		}
+
+		/* Reset the RC */
+		regmap_read(rc->lcru, BAIKAL_LCRU_PCIE_RESET(rc->num), &reg);
+		reg |= BAIKAL_PCIE_NONSTICKY_RST |
+		       BAIKAL_PCIE_STICKY_RST	 |
+		       BAIKAL_PCIE_PWR_RST	 |
+		       BAIKAL_PCIE_CORE_RST	 |
+		       BAIKAL_PCIE_PHY_RST;
+
+		/* If the RC is PCIe x8, reset PIPE0 and PIPE1 */
+		if (rc->num == 2) {
+			reg |= BAIKAL_PCIE_PIPE0_RST |
+			       BAIKAL_PCIE_PIPE1_RST;
+		} else {
+			reg |= BAIKAL_PCIE_PIPE_RST;
+		}
+
+		regmap_write(rc->lcru, BAIKAL_LCRU_PCIE_RESET(rc->num), reg);
+
+		usleep_range(20000, 30000);
+
+		if (rc->reset_gpio != NULL) {
+			/* Deassert PERST pin */
+			gpiod_set_value_cansleep(rc->reset_gpio, 0);
+		}
+
+		/* Deassert PHY reset */
+		regmap_read(rc->lcru, BAIKAL_LCRU_PCIE_RESET(rc->num), &reg);
+		reg &= ~BAIKAL_PCIE_PHY_RST;
+		regmap_write(rc->lcru, BAIKAL_LCRU_PCIE_RESET(rc->num), reg);
+
+		/* Deassert all software controlled resets */
+		regmap_read(rc->lcru, BAIKAL_LCRU_PCIE_RESET(rc->num), &reg);
+		reg &= ~(BAIKAL_PCIE_ADB_PWRDWN	   |
+			 BAIKAL_PCIE_HOT_RST	   |
+			 BAIKAL_PCIE_NONSTICKY_RST |
+			 BAIKAL_PCIE_STICKY_RST	   |
+			 BAIKAL_PCIE_PWR_RST	   |
+			 BAIKAL_PCIE_CORE_RST	   |
+			 BAIKAL_PCIE_PHY_RST);
+
+		if (rc->num == 2) {
+			reg &= ~(BAIKAL_PCIE_PIPE0_RST |
+				 BAIKAL_PCIE_PIPE1_RST);
+		} else {
+			reg &= ~BAIKAL_PCIE_PIPE_RST;
+		}
+
+		regmap_write(rc->lcru, BAIKAL_LCRU_PCIE_RESET(rc->num), reg);
+	}
+
+	/* Deinitialise all iATU regions */
+	for (idx = 0; idx < pcie->num_ob_windows; ++idx) {
+		dw_pcie_writel_dbi(pcie, PCIE_IATU_VIEWPORT_REG,
+					 PCIE_IATU_REGION_OUTBOUND | idx);
+		dw_pcie_writel_dbi(pcie, PCIE_IATU_REGION_CTRL_2_REG, 0);
+	}
+
+	dw_pcie_setup_rc(pp);
+
+	/* Set prog-if 01 [subtractive decode] */
+	dw_pcie_dbi_ro_wr_en(pcie);
+	reg = dw_pcie_readl_dbi(pcie, PCI_CLASS_REVISION);
+	reg = (reg & 0xffff00ff) | (1 << 8);
+	dw_pcie_writel_dbi(pcie, PCI_CLASS_REVISION, reg);
+	dw_pcie_dbi_ro_wr_dis(pcie);
+
+	/* Enable error reporting */
+	reg = dw_pcie_readl_dbi(pcie, PCIE_ROOT_ERR_CMD_REG);
+	reg |= PCIE_CORR_ERR_REPORTING_EN      |
+	       PCIE_NON_FATAL_ERR_REPORTING_EN |
+	       PCIE_FATAL_ERR_REPORTING_EN;
+	dw_pcie_writel_dbi(pcie, PCIE_ROOT_ERR_CMD_REG, reg);
+
+	reg = dw_pcie_readl_dbi(pcie, PCIE_DEVICE_CONTROL_DEVICE_STATUS_REG);
+	reg |= PCIE_CAP_CORR_ERR_REPORT_EN	|
+	       PCIE_CAP_NON_FATAL_ERR_REPORT_EN	|
+	       PCIE_CAP_FATAL_ERR_REPORT_EN	|
+	       PCIE_CAP_UNSUPPORT_REQ_REP_EN;
+	dw_pcie_writel_dbi(pcie, PCIE_DEVICE_CONTROL_DEVICE_STATUS_REG, reg);
+
+	reg = dw_pcie_readl_dbi(pcie, PCIE_ROOT_CONTROL_ROOT_CAPABILITIES_REG);
+	reg |= PCIE_CAP_SYS_ERR_ON_CORR_ERR_EN	    |
+	       PCIE_CAP_SYS_ERR_ON_NON_FATAL_ERR_EN |
+	       PCIE_CAP_SYS_ERR_ON_FATAL_ERR_EN	    |
+	       PCIE_CAP_PME_INT_EN;
+	dw_pcie_writel_dbi(pcie, PCIE_ROOT_CONTROL_ROOT_CAPABILITIES_REG, reg);
+
+	if (linkup) {
+		dev_info(dev, "link is already up\n");
+	} else {
+		/* Use Gen1 mode for link establishing */
+		reg = dw_pcie_readl_dbi(pcie,
+					PCIE_LINK_CONTROL2_LINK_STATUS2_REG);
+		reg &= ~PCIE_CAP_TARGET_LINK_SPEED_MASK;
+		reg |= 1;
+		dw_pcie_writel_dbi(pcie,
+				   PCIE_LINK_CONTROL2_LINK_STATUS2_REG, reg);
+
+		/*
+		 * Clear DIRECT_SPEED_CHANGE bit. It has been set by
+		 * dw_pcie_setup_rc(pp). This bit causes link retraining. But
+		 * link retraining should be performed later by calling the
+		 * baikal_pcie_link_speed_fixup().
+		 */
+		reg = dw_pcie_readl_dbi(pcie, PCIE_GEN2_CTRL_REG);
+		reg &= ~PCIE_DIRECT_SPEED_CHANGE;
+		dw_pcie_writel_dbi(pcie, PCIE_GEN2_CTRL_REG, reg);
+
+		/* Establish link */
+		regmap_read(rc->lcru, BAIKAL_LCRU_PCIE_GEN_CTL(rc->num), &reg);
+		reg |= BAIKAL_PCIE_LTSSM_ENABLE;
+		regmap_write(rc->lcru, BAIKAL_LCRU_PCIE_GEN_CTL(rc->num), reg);
+
+		dw_pcie_wait_for_link(pcie);
+	}
+
+	regmap_read(rc->lcru, BAIKAL_LCRU_PCIE_MSI_TRANS_CTL2, &reg);
+	reg &= ~BAIKAL_LCRU_PCIE_MSI_TRANS_CTL2_PCIE_RCNUM_MASK(rc->num);
+	reg |=	BAIKAL_LCRU_PCIE_MSI_TRANS_CTL2_PCIE_RCNUM(rc->num);
+	reg |=	BAIKAL_LCRU_PCIE_MSI_TRANS_CTL2_PCIE_MSI_TRANS_EN(rc->num);
+	regmap_write(rc->lcru, BAIKAL_LCRU_PCIE_MSI_TRANS_CTL2, reg);
+
+	return 0;
+}
+
+static const struct dw_pcie_host_ops baikal_pcie_host_ops = {
+	.host_init = baikal_pcie_host_init
+};
+
+static void baikal_pcie_link_print_status(struct baikal_pcie_rc *rc)
+{
+	struct dw_pcie *pcie = rc->pcie;
+	struct device *dev = pcie->dev;
+	u32 reg;
+	unsigned speed, width;
+
+	if (!baikal_pcie_link_up(pcie)) {
+		dev_info(dev, "link is down\n");
+		return;
+	}
+
+	reg = dw_pcie_readl_dbi(pcie, PCIE_LINK_CONTROL_LINK_STATUS_REG);
+	speed = (reg & PCIE_CAP_LINK_SPEED_MASK) >> PCIE_CAP_LINK_SPEED_SHIFT;
+	width = (reg & PCIE_CAP_NEGO_LINK_WIDTH_MASK) >>
+		PCIE_CAP_NEGO_LINK_WIDTH_SHIFT;
+
+	dev_info(dev, "link status is Gen%u%s, x%u\n", speed,
+		 reg & PCIE_CAP_LINK_TRAINING ? " (training)" : "", width);
+}
+
+static unsigned baikal_pcie_link_is_training(struct baikal_pcie_rc *rc)
+{
+	struct dw_pcie *pcie = rc->pcie;
+	return dw_pcie_readl_dbi(pcie, PCIE_LINK_CONTROL_LINK_STATUS_REG) &
+	       PCIE_CAP_LINK_TRAINING;
+}
+
+static bool baikal_pcie_link_wait_training_done(struct baikal_pcie_rc *rc)
+{
+	struct dw_pcie *pcie = rc->pcie;
+	struct device *dev = pcie->dev;
+	unsigned long start_jiffies = jiffies;
+
+	while (baikal_pcie_link_is_training(rc)) {
+		if (time_after(jiffies, start_jiffies + HZ)) {
+			dev_err(dev, "link training timeout occured\n");
+			return false;
+		}
+		udelay(100);
+	}
+	return true;
+}
+
+static void baikal_pcie_link_retrain(struct baikal_pcie_rc *rc,
+				     int target_speed)
+{
+	struct dw_pcie *pcie = rc->pcie;
+	struct device *dev = pcie->dev;
+	u32 reg;
+	unsigned long start_jiffies;
+
+	dev_info(dev, "retrain link to Gen%u\n", target_speed);
+
+	/* In case link is already training wait for training to complete */
+	baikal_pcie_link_wait_training_done(rc);
+
+	/* Set desired speed */
+	reg = dw_pcie_readl_dbi(pcie, PCIE_LINK_CONTROL2_LINK_STATUS2_REG);
+	reg &= ~PCIE_CAP_TARGET_LINK_SPEED_MASK;
+	reg |= target_speed;
+	dw_pcie_writel_dbi(pcie, PCIE_LINK_CONTROL2_LINK_STATUS2_REG, reg);
+
+	/* Deassert and assert DIRECT_SPEED_CHANGE bit */
+	reg = dw_pcie_readl_dbi(pcie, PCIE_GEN2_CTRL_REG);
+	reg &= ~PCIE_DIRECT_SPEED_CHANGE;
+	dw_pcie_writel_dbi(pcie, PCIE_GEN2_CTRL_REG, reg);
+	reg |= PCIE_DIRECT_SPEED_CHANGE;
+	dw_pcie_writel_dbi(pcie, PCIE_GEN2_CTRL_REG, reg);
+
+	/* Wait for link training begin */
+	start_jiffies = jiffies;
+	while (!baikal_pcie_link_is_training(rc)) {
+		if (time_after(jiffies, start_jiffies + HZ)) {
+			dev_err(dev, "link training has not started\n");
+			/* Don't wait for training_done() if it hasn't started */
+			return;
+		}
+		udelay(100);
+	}
+
+	/* Wait for link training end */
+	if (!baikal_pcie_link_wait_training_done(rc)) {
+		return;
+	}
+
+	if (!dw_pcie_wait_for_link(pcie)) {
+		/* Wait if link has switched to configuration/recovery state */
+		baikal_pcie_link_wait_training_done(rc);
+		baikal_pcie_link_print_status(rc);
+	}
+}
+
+static void baikal_pcie_link_speed_fixup(struct pci_dev *pdev)
+{
+	struct dw_pcie_rp *pp = pdev->bus->sysdata;
+	struct dw_pcie *pcie = to_dw_pcie_from_pp(pp);
+	struct baikal_pcie_rc *rc = to_baikal_pcie_rc(pcie);
+	unsigned dev_lnkcap_speed;
+	unsigned dev_lnkcap_width;
+	unsigned rc_lnkcap_speed;
+	unsigned rc_lnksta_speed;
+	unsigned rc_target_speed;
+	u32 reg;
+
+	/* Skip Root Bridge */
+	if (!pdev->bus->self) {
+		return;
+	}
+
+	/* Skip any devices not directly connected to the RC */
+	if (pdev->bus->self->bus->number != pp->bridge->bus->number) {
+		return;
+	}
+
+	/* Skip if the bus has already been retrained */
+	if (rc->retrained) {
+		return;
+	}
+
+	reg = dw_pcie_readl_dbi(pcie, PCIE_LINK_CAPABILITIES_REG);
+	rc_lnkcap_speed = reg & PCI_EXP_LNKCAP_SLS;
+
+	reg = dw_pcie_readl_dbi(pcie, PCIE_LINK_CONTROL_LINK_STATUS_REG);
+	rc_lnksta_speed = (reg & PCIE_CAP_LINK_SPEED_MASK) >>
+			  PCIE_CAP_LINK_SPEED_SHIFT;
+
+	pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, &reg);
+	dev_lnkcap_speed = (reg & PCI_EXP_LNKCAP_SLS);
+	dev_lnkcap_width = (reg & PCI_EXP_LNKCAP_MLW) >>
+			   PCI_EXP_LNKSTA_NLW_SHIFT;
+
+	baikal_pcie_link_print_status(rc);
+	dev_info(&pdev->dev, "device link capability is Gen%u, x%u\n",
+		 dev_lnkcap_speed, dev_lnkcap_width);
+
+	/*
+	 * Gen1->Gen3 is suitable way of retraining.
+	 * Gen1->Gen2 is used when Gen3 could not be reached.
+	 * Gen2->Gen3 causes system freezing sometimes.
+	 */
+	if (rc_lnkcap_speed < dev_lnkcap_speed) {
+		rc_target_speed = rc_lnkcap_speed;
+	} else {
+		rc_target_speed = dev_lnkcap_speed;
+	}
+
+	while (rc_lnksta_speed < rc_target_speed) {
+		/* Try to change link speed */
+		baikal_pcie_link_retrain(rc, rc_target_speed);
+
+		/* Check if the link is down after retrain */
+		if (!baikal_pcie_link_up(pcie)) {
+			/*
+			 * Check if the link has already been down and
+			 * the link is not re-established at Gen1.
+			 */
+			if (rc_lnksta_speed == 0 && rc_target_speed == 1) {
+				/* Unable to re-establish the link */
+				break;
+			}
+
+			rc_lnksta_speed = 0;
+			if (rc_target_speed > 1) {
+				/* Try to use lower speed */
+				--rc_target_speed;
+			}
+
+			continue;
+		}
+
+		reg = dw_pcie_readl_dbi(pcie,
+					PCIE_LINK_CONTROL_LINK_STATUS_REG);
+		rc_lnksta_speed = (reg & PCIE_CAP_LINK_SPEED_MASK) >>
+				  PCIE_CAP_LINK_SPEED_SHIFT;
+		/* Check if the targeted speed has not been reached */
+		if (rc_lnksta_speed < rc_target_speed && rc_target_speed > 1) {
+			/* Try to use lower speed */
+			--rc_target_speed;
+		}
+	}
+
+	rc->retrained = true;
+}
+
+static void baikal_pcie_link_retrain_bus(const struct pci_bus *bus)
+{
+	struct pci_dev *dev;
+
+	list_for_each_entry(dev, &bus->devices, bus_list) {
+		baikal_pcie_link_speed_fixup(dev);
+	}
+
+	list_for_each_entry(dev, &bus->devices, bus_list) {
+		if (dev->subordinate) {
+			baikal_pcie_link_retrain_bus(dev->subordinate);
+		}
+	}
+}
+
+static irqreturn_t baikal_pcie_aer_irq_handler(int irq, void *arg)
+{
+	struct baikal_pcie_rc *rc = arg;
+	struct dw_pcie *pcie = rc->pcie;
+	struct device *dev = pcie->dev;
+	u32 corr_err_status;
+	u32 dev_ctrl_dev_status;
+	u32 root_err_status;
+	u32 uncorr_err_status;
+
+	uncorr_err_status   = dw_pcie_readl_dbi(pcie,
+					 PCIE_UNCORR_ERR_STATUS_REG);
+	corr_err_status	    = dw_pcie_readl_dbi(pcie,
+					 PCIE_CORR_ERR_STATUS_REG);
+	root_err_status     = dw_pcie_readl_dbi(pcie,
+					 PCIE_ROOT_ERR_STATUS_REG);
+	dev_ctrl_dev_status = dw_pcie_readl_dbi(pcie,
+					 PCIE_DEVICE_CONTROL_DEVICE_STATUS_REG);
+	dev_err(dev,
+		"dev_err:0x%x root_err:0x%x uncorr_err:0x%x corr_err:0x%x\n",
+		(dev_ctrl_dev_status & 0xf0000) >> 16,
+		root_err_status, uncorr_err_status, corr_err_status);
+
+	dw_pcie_writel_dbi(pcie,
+		    PCIE_UNCORR_ERR_STATUS_REG, uncorr_err_status);
+	dw_pcie_writel_dbi(pcie,
+		    PCIE_CORR_ERR_STATUS_REG, corr_err_status);
+	dw_pcie_writel_dbi(pcie,
+		    PCIE_ROOT_ERR_STATUS_REG, root_err_status);
+	dw_pcie_writel_dbi(pcie,
+		    PCIE_DEVICE_CONTROL_DEVICE_STATUS_REG, dev_ctrl_dev_status);
+
+	return IRQ_HANDLED;
+}
+
+static int baikal_pcie_add_pcie_port(struct baikal_pcie_rc *rc,
+				     struct platform_device *pdev)
+{
+	struct dw_pcie *pcie = rc->pcie;
+	struct dw_pcie_rp *pp = &pcie->pp;
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	pp->irq = platform_get_irq(pdev, 0);
+	if (pp->irq < 0) {
+		return pp->irq;
+	}
+
+	ret = devm_request_irq(dev, pp->irq, baikal_pcie_aer_irq_handler,
+			       IRQF_SHARED, "bm1000-pcie-aer", rc);
+
+	if (ret) {
+		dev_err(dev, "failed to request irq %d\n", pp->irq);
+		return ret;
+	}
+
+	if (IS_ENABLED(CONFIG_PCI_MSI)) {
+		pp->msi_irq[0] = platform_get_irq(pdev, 1);
+		if (pp->msi_irq[0] < 0) {
+			return pp->msi_irq[0];
+		}
+	}
+
+	pp->ops = &baikal_pcie_host_ops;
+
+	ret = dw_pcie_host_init(pp);
+	if (ret) {
+		dev_err(dev, "Failed to initialize host\n");
+		return ret;
+	}
+
+	baikal_pcie_link_retrain_bus(pp->bridge->bus);
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int baikal_pcie_pm_resume(struct device *dev)
+{
+	struct baikal_pcie_rc *rc = dev_get_drvdata(dev);
+	struct dw_pcie *pcie = rc->pcie;
+	u32 reg;
+
+	/* Set Memory Space Enable (MSE) bit */
+	reg = dw_pcie_readl_dbi(pcie, PCI_COMMAND);
+	reg |= PCI_COMMAND_MEMORY;
+	dw_pcie_writel_dbi(pcie, PCI_COMMAND, reg);
+	return 0;
+}
+
+static int baikal_pcie_pm_resume_noirq(struct device *dev)
+{
+	return 0;
+}
+
+static int baikal_pcie_pm_suspend(struct device *dev)
+{
+	struct baikal_pcie_rc *rc = dev_get_drvdata(dev);
+	struct dw_pcie *pcie = rc->pcie;
+	u32 reg;
+
+	/* Clear Memory Space Enable (MSE) bit */
+	reg = dw_pcie_readl_dbi(pcie, PCI_COMMAND);
+	reg &= ~PCI_COMMAND_MEMORY;
+	dw_pcie_writel_dbi(pcie, PCI_COMMAND, reg);
+	return 0;
+}
+
+static int baikal_pcie_pm_suspend_noirq(struct device *dev)
+{
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops baikal_pcie_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(baikal_pcie_pm_suspend,
+				baikal_pcie_pm_resume)
+	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(baikal_pcie_pm_suspend_noirq,
+				      baikal_pcie_pm_resume_noirq)
+};
+
+static const struct of_device_id of_baikal_pcie_match[] = {
+	{ .compatible = "baikal,bm1000-pcie" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_baikal_pcie_match);
+
+static const struct dw_pcie_ops baikal_pcie_ops = {
+	.link_up = baikal_pcie_link_up
+};
+
+static int baikal_pcie_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct baikal_pcie_rc *rc;
+	struct dw_pcie *pcie;
+	int ret;
+	u32 idx[2];
+	enum of_gpio_flags gpio_flags;
+	int reset_gpio;
+	struct resource *res;
+
+	if (!of_match_device(of_baikal_pcie_match, dev)) {
+		dev_err(dev, "device can't be handled by pcie-baikal\n");
+		return -EINVAL;
+	}
+
+	pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
+	if (!pcie) {
+		return -ENOMEM;
+	}
+
+	pcie->dev = dev;
+	pcie->ops = &baikal_pcie_ops;
+
+	rc = devm_kzalloc(dev, sizeof(*rc), GFP_KERNEL);
+	if (!rc) {
+		return -ENOMEM;
+	}
+
+	rc->pcie = pcie;
+	rc->lcru = syscon_regmap_lookup_by_phandle(dev->of_node,
+						   "baikal,pcie-lcru");
+	if (IS_ERR(rc->lcru)) {
+		dev_err(dev, "No LCRU phandle specified\n");
+		rc->lcru = NULL;
+		return -EINVAL;
+	}
+
+	if (of_property_read_u32_array(dev->of_node, "baikal,pcie-lcru", idx, 2)) {
+		dev_err(dev, "failed to read LCRU\n");
+		rc->lcru = NULL;
+		return -EINVAL;
+	}
+
+	if (idx[1] > 2) {
+		dev_err(dev, "incorrect pcie-lcru index\n");
+		rc->lcru = NULL;
+		return -EINVAL;
+	}
+
+	rc->num = idx[1];
+	reset_gpio = of_get_named_gpio_flags(dev->of_node, "reset-gpios", 0,
+					     &gpio_flags);
+	if (gpio_is_valid(reset_gpio)) {
+		rc->reset_gpio = gpio_to_desc(reset_gpio);
+		rc->reset_active_low = !!(gpio_flags & OF_GPIO_ACTIVE_LOW);
+		snprintf(rc->reset_name, sizeof(rc->reset_name), "pcie%u-reset",
+			 rc->num);
+	} else {
+		rc->reset_gpio = NULL;
+	}
+
+	pm_runtime_enable(dev);
+	ret = pm_runtime_get_sync(dev);
+	if (ret < 0) {
+		dev_err(dev, "pm_runtime_get_sync failed\n");
+		goto err_pm_disable;
+	}
+
+	platform_set_drvdata(pdev, rc);
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
+	if (res) {
+		devm_request_resource(dev, &iomem_resource, res);
+		pcie->dbi_base = devm_ioremap_resource(dev, res);
+		if (IS_ERR(pcie->dbi_base)) {
+			dev_err(dev, "error with ioremap\n");
+			ret = PTR_ERR(pcie->dbi_base);
+			goto err_pm_put;
+		}
+	} else {
+		dev_err(dev, "missing *dbi* reg space\n");
+		ret = -EINVAL;
+		goto err_pm_put;
+	}
+
+	ret = baikal_pcie_add_pcie_port(rc, pdev);
+	if (ret < 0) {
+		goto err_pm_put;
+	}
+
+	return 0;
+
+err_pm_put:
+	pm_runtime_put(dev);
+err_pm_disable:
+	pm_runtime_disable(dev);
+	return ret;
+}
+
+static struct platform_driver baikal_pcie_driver = {
+	.driver = {
+		.name = "baikal-pcie",
+		.of_match_table = of_baikal_pcie_match,
+		.suppress_bind_attrs = true,
+		.pm = &baikal_pcie_pm_ops
+	},
+	.probe = baikal_pcie_probe
+};
+
+module_platform_driver(baikal_pcie_driver);
+MODULE_DESCRIPTION("Baikal PCIe host controller driver");
+MODULE_LICENSE("GPL v2");
-- 
2.33.5



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

* [d-kernel] [PATCH 28/32] (BROKEN) dwc-i2s: support Baikal-M SoC
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (25 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 27/32] PCI: pcie-baikal: driver for Baikal-M with new firmware Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 29/32] input: added TF307 serio PS/2 emulator driver Alexey Sheplyakov
                   ` (3 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

* dw_i2s_probe: request all IRQs specified in device tree
* i2s_irq_handler: avoid flooding system with RX overrun warnings

Note that the sound frequency is distorted (i.e. playing 440 Hz
sine wave results in 467 Hz)

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

diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c
index 7f7dd07c63b2..1568d82166f3 100644
--- a/sound/soc/dwc/dwc-i2s.c
+++ b/sound/soc/dwc/dwc-i2s.c
@@ -100,6 +100,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];
@@ -136,9 +137,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(dev->dev, "RX overrun (ch_id=%d)\n", i);
+			rxor_count = READ_ONCE(dev->rx_overrun_count);
+			if (!(rxor_count & 0x3ff))
+				dev_dbg(dev->dev, "RX overrun (ch_id=%d)\n", i);
+			rxor_count++;
+			WRITE_ONCE(dev->rx_overrun_count, rxor_count);
 			irq_valid = true;
 		}
 	}
@@ -630,7 +635,8 @@ 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;
+	unsigned idx;
 	struct snd_soc_dai_driver *dw_i2s_dai;
 	const char *clk_id;
 
@@ -650,13 +656,23 @@ static int dw_i2s_probe(struct platform_device *pdev)
 
 	dev->dev = &pdev->dev;
 
-	irq = platform_get_irq_optional(pdev, 0);
-	if (irq >= 0) {
-		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;
+	irq_count = platform_irq_count(pdev);
+	if (irq_count < 0) /* - EPROBE_DEFER */
+		return irq_count;
+	else if (!irq_count) {
+		dev_err(&pdev->dev, "no IRQs found for device\n");
+		return -ENODEV;
+	}
+
+	for (idx = 0; idx < (unsigned)irq_count; idx++) {
+		irq = platform_get_irq_optional(pdev, idx);
+		if (irq >= 0) {
+			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;
+			}
 		}
 	}
 
diff --git a/sound/soc/dwc/local.h b/sound/soc/dwc/local.h
index 1c361eb6127e..1d6b6fd870ca 100644
--- a/sound/soc/dwc/local.h
+++ b/sound/soc/dwc/local.h
@@ -117,6 +117,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.33.5



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

* [d-kernel] [PATCH 29/32] input: added TF307 serio PS/2 emulator driver
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (26 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 28/32] (BROKEN) dwc-i2s: support Baikal-M SoC Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 30/32] input: new driver - serdev-serio Alexey Sheplyakov
                   ` (2 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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

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

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

diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index f39b7b3f7942..8e6b8b3ef478 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -293,6 +293,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 6d97bad7b844..a47319040c19 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -32,4 +32,5 @@ 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
diff --git a/drivers/input/serio/tp_serio.c b/drivers/input/serio/tp_serio.c
new file mode 100644
index 000000000000..f530cb50182e
--- /dev/null
+++ b/drivers/input/serio/tp_serio.c
@@ -0,0 +1,747 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * T-Platforms serio port driver
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/serio.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+
+MODULE_DESCRIPTION("T-Platforms serio port driver");
+MODULE_LICENSE("GPL");
+
+#define TP_SERIO_CHUNK_SIZE 4
+#define TP_SERIO_SPI_SPEED_DEFAULT 500000
+#define TP_SERIO_TX_QUEUE_SIZE 64
+#define TP_SERIO_REQUEST_DELAY 2
+#define TP_SERIO_POLL_READ_DELAY_MIN 1
+#define TP_SERIO_POLL_READ_DELAY_MAX 2
+#define TP_SERIO_POLL_WRITE_DELAY 1
+#define TP_SERIO_POLL_ERROR_DELAY 100
+#define TP_SERIO_POLL_READ_TIMEOUT 8
+#define TP_SERIO_POLL_WAIT_TIMEOUT 100
+#define TP_SERIO_CMD_QUERY 0xFC
+#define TP_SERIO_CMD_RESET 0xFE
+
+static const unsigned char tp_serio_cmd_reset_response[] = {
+	TP_SERIO_CMD_RESET, 'P', 'S', '2'
+};
+
+struct tp_serio_tx {
+	bool has_data;
+	unsigned char data;
+};
+
+struct tp_serio_port {
+	struct serio *serio;
+	struct tp_serio_data *drv;
+	struct tp_serio_tx tx;
+	unsigned int id;
+	bool registered;
+};
+
+struct tp_serio_data {
+	struct i2c_client *dev_i2c;
+	struct spi_device *dev_spi;
+	struct task_struct *poll_task;
+	wait_queue_head_t poll_wq;
+	bool poll_ready;
+	int rx_irq;
+	unsigned int num_ports;
+	struct tp_serio_port *ports;
+};
+
+struct tp_serio_driver {
+#if defined(CONFIG_I2C)
+	struct i2c_driver i2c;
+#endif
+#if defined(CONFIG_SPI)
+	struct spi_driver spi;
+#endif
+};
+
+#if defined(CONFIG_I2C)
+static int tp_serio_i2c_write(struct tp_serio_data *drv,
+		size_t size, void *data)
+{
+	struct i2c_msg m;
+
+	m.addr = drv->dev_i2c->addr;
+	m.flags = 0;
+	m.len = size;
+	m.buf = data;
+	return i2c_transfer(drv->dev_i2c->adapter, &m, 1);
+}
+
+static int tp_serio_i2c_read(struct tp_serio_data *drv,
+		size_t size, void *data)
+{
+	struct i2c_msg m;
+
+	m.addr = drv->dev_i2c->addr;
+	m.flags = I2C_M_RD;
+	m.len = size;
+	m.buf = data;
+	return i2c_transfer(drv->dev_i2c->adapter, &m, 1);
+}
+#endif
+
+#if defined(CONFIG_SPI)
+static int tp_serio_spi_write(struct tp_serio_data *drv,
+		size_t size, void *data)
+{
+	struct spi_transfer t = {
+		.speed_hz = TP_SERIO_SPI_SPEED_DEFAULT,
+		.tx_buf = data,
+		.len = size,
+	};
+	struct spi_message m;
+
+	spi_message_init(&m);
+	spi_message_add_tail(&t, &m);
+	return spi_sync(drv->dev_spi, &m);
+}
+
+static int tp_serio_spi_read(struct tp_serio_data *drv,
+		size_t size, void *data)
+{
+	struct spi_transfer	t = {
+		.speed_hz = TP_SERIO_SPI_SPEED_DEFAULT,
+		.rx_buf = data,
+		.len = size,
+	};
+	struct spi_message m;
+
+	spi_message_init(&m);
+	spi_message_add_tail(&t, &m);
+	return spi_sync(drv->dev_spi, &m);
+}
+#endif
+
+static int tp_serio_request(struct tp_serio_data *drv,
+				unsigned char cmd,
+				unsigned char *response)
+{
+	int result;
+	size_t size;
+	unsigned char message[TP_SERIO_CHUNK_SIZE];
+
+	result = -ENODEV;
+	memset(message, 0, sizeof(message));
+	message[0] = cmd;
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL) {
+		size = sizeof(message);
+		result = tp_serio_spi_write(drv, size, message);
+	} else
+#endif
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL) {
+		size = 1;
+		result = tp_serio_i2c_write(drv, size, message);
+	}
+#endif
+		;
+	if (result < 0)
+		return result;
+	usleep_range(TP_SERIO_REQUEST_DELAY * 1000,
+				TP_SERIO_REQUEST_DELAY * 1000);
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL)
+		result = tp_serio_i2c_read(drv, TP_SERIO_CHUNK_SIZE, response);
+	else
+#endif
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL)
+		result = tp_serio_spi_read(drv, TP_SERIO_CHUNK_SIZE, response);
+#endif
+		;
+	return result;
+}
+
+static int tp_serio_data_read(struct tp_serio_data *drv)
+{
+	int result;
+	size_t size;
+	size_t index;
+	size_t dbg_len;
+	char dbg_line[256];
+	unsigned int port_id;
+	unsigned char message[TP_SERIO_CHUNK_SIZE];
+
+	memset(message, 0, sizeof(message));
+	result = -ENODEV;
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL)
+		result = tp_serio_i2c_read(drv, sizeof(message), message);
+	else
+#endif
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL)
+		result = tp_serio_spi_read(drv, sizeof(message), message);
+#endif
+		;
+	if (result < 0)
+		return result;
+
+#if 0
+	snprintf(dbg_line, ARRAY_SIZE(dbg_line) - 1, "raw read:");
+	for (index = 0; index < ARRAY_SIZE(message); index++) {
+		dbg_len = strlen(dbg_line);
+		snprintf(dbg_line + dbg_len,
+				ARRAY_SIZE(dbg_line) - 1 - dbg_len,
+				" %02x", message[index]);
+	}
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL)
+		dev_dbg(&drv->dev_i2c->dev, "%s\n", dbg_line);
+	else
+#endif
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL)
+		dev_dbg(&drv->dev_spi->dev, "%s\n", dbg_line);
+#endif
+		;
+#endif
+
+	result = 0;
+	size = message[0] & 0x0F;
+	port_id = (message[0] >> 4) & 0x0F;
+	if ((size > 0) && (port_id < drv->num_ports)) {
+		snprintf(dbg_line, ARRAY_SIZE(dbg_line) - 1,
+			"port %u read:", port_id);
+
+		if (size > (ARRAY_SIZE(message) - 1)) {
+			size = ARRAY_SIZE(message) - 1;
+			result = 1;
+		}
+		for (index = 0; index < size; index++) {
+			dbg_len = strlen(dbg_line);
+			snprintf(dbg_line + dbg_len,
+					ARRAY_SIZE(dbg_line) - 1 - dbg_len,
+					" %02x", message[index + 1]);
+			serio_interrupt(drv->ports[port_id].serio,
+				message[index + 1], 0);
+		}
+#if defined(CONFIG_I2C)
+		if (drv->dev_i2c != NULL)
+			dev_dbg(&drv->dev_i2c->dev, "%s\n", dbg_line);
+		else
+#endif
+#if defined(CONFIG_SPI)
+		if (drv->dev_spi != NULL)
+			dev_dbg(&drv->dev_spi->dev, "%s\n", dbg_line);
+#endif
+			;
+	}
+	return result;
+}
+
+static int tp_serio_data_write(struct tp_serio_data *drv,
+		u8 id, unsigned char data)
+{
+	int result;
+	size_t size;
+	unsigned char message[TP_SERIO_CHUNK_SIZE];
+	struct tp_serio_port *port = drv->ports + id;
+
+	result = -ENODEV;
+	memset(message, 0, sizeof(message));
+	message[0] = (port->id << 4) | 0x01;
+	message[1] = data;
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL) {
+		size = sizeof(message);
+		dev_dbg(&drv->dev_spi->dev,
+			"port %u write: %02x\n", port->id, data);
+		result = tp_serio_spi_write(drv, size, message);
+	} else
+#endif
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL) {
+		size = 2;
+		dev_dbg(&drv->dev_i2c->dev,
+			"port %u write: %02x\n", port->id, data);
+		result = tp_serio_i2c_write(drv, size, message);
+	}
+#endif
+		;
+	return result;
+}
+
+static void tp_serio_trigger_tx(struct tp_serio_data *drv)
+{
+	drv->poll_ready = true;
+	wake_up(&drv->poll_wq);
+}
+
+static int tp_serio_write(struct serio *serio, unsigned char data)
+{
+	int result = -EINVAL;
+	struct tp_serio_data *drv;
+	struct tp_serio_port *port = (struct tp_serio_port *)serio->port_data;
+
+	if (port != NULL) {
+		drv = port->drv;
+		if (port->tx.has_data) {
+			result = -ENOMEM;
+		} else {
+			port->tx.data = data;
+			port->tx.has_data = true;
+			result = 0;
+		}
+		tp_serio_trigger_tx(drv);
+	}
+	return result;
+}
+
+static int tp_serio_start(struct serio *serio)
+{
+	struct tp_serio_port *port = (struct tp_serio_port *)serio->port_data;
+
+	if (port != NULL)
+		port->registered = true;
+	return 0;
+}
+
+static void tp_serio_stop(struct serio *serio)
+{
+	struct tp_serio_port *port = (struct tp_serio_port *)serio->port_data;
+
+	if (port != NULL)
+		port->registered = false;
+}
+
+static int tp_serio_create_port(struct tp_serio_data *drv, unsigned int id)
+{
+	struct serio *serio;
+	struct device *dev;
+
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL) {
+		dev = &drv->dev_spi->dev;
+	} else
+#endif
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL) {
+		dev = &drv->dev_i2c->dev;
+	} else
+#endif
+	{
+		return -ENODEV;
+	}
+	serio = devm_kzalloc(dev, sizeof(struct serio), GFP_KERNEL);
+	if (!serio)
+		return -ENOMEM;
+	strlcpy(serio->name, "tp_serio", sizeof(serio->name));
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL) {
+		snprintf(serio->phys, sizeof(serio->phys),
+			 "%s/port%u", dev_name(&drv->dev_spi->dev), id);
+	} else
+#endif
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL) {
+		snprintf(serio->phys, sizeof(serio->phys),
+			 "%s/port%u", dev_name(&drv->dev_i2c->dev), id);
+	}
+#endif
+		;
+	serio->id.type = SERIO_8042;
+	serio->write = tp_serio_write;
+	serio->start = tp_serio_start;
+	serio->stop = tp_serio_stop;
+	serio->port_data = drv->ports + id;
+	drv->ports[id].serio = serio;
+	drv->ports[id].drv = drv;
+	drv->ports[id].id = id;
+	drv->ports[id].registered = false;
+	drv->ports[id].tx.has_data = false;
+	drv->ports[id].tx.data = 0x00;
+	return 0;
+}
+
+static void tp_serio_destroy_port(struct tp_serio_data *drv, unsigned int id)
+{
+	if (drv->ports[id].registered)
+		serio_unregister_port(drv->ports[id].serio);
+}
+
+static void tp_serio_read_error(struct tp_serio_data *drv, int error)
+{
+#if defined(CONFIG_I2C)
+	if (drv->dev_i2c != NULL)
+		dev_dbg(&drv->dev_i2c->dev,
+			"i2c read failed: %d\n", error);
+	else
+#endif
+#if defined(CONFIG_SPI)
+	if (drv->dev_spi != NULL)
+		dev_dbg(&drv->dev_spi->dev,
+			"spi read failed: %d\n", error);
+#endif
+		;
+	msleep_interruptible(TP_SERIO_POLL_ERROR_DELAY);
+}
+
+static void tp_serio_serio_process_tx(struct tp_serio_data *drv)
+{
+	unsigned int index;
+
+	for (index = 0; index < drv->num_ports; index++) {
+		if (drv->ports[index].tx.has_data) {
+			tp_serio_data_write(drv, index,
+					    drv->ports[index].tx.data);
+			drv->ports[index].tx.has_data = false;
+			usleep_range(TP_SERIO_POLL_WRITE_DELAY * 1000,
+					TP_SERIO_POLL_WRITE_DELAY * 1000);
+		}
+	}
+}
+
+static int tp_serio_serio_process_rx(struct tp_serio_data *drv)
+{
+	int ret;
+
+	do {
+		ret = tp_serio_data_read(drv);
+		usleep_range(TP_SERIO_POLL_READ_DELAY_MIN * 1000,
+				TP_SERIO_POLL_READ_DELAY_MAX * 1000);
+	} while (ret > 0);
+	if ((ret < 0) && (ret != -EAGAIN))
+		tp_serio_read_error(drv, ret);
+	return ret;
+}
+
+static int tp_serio_poll(void *data)
+{
+	struct tp_serio_data *drv = (struct tp_serio_data *)data;
+	const unsigned int poll_timeout = (drv->rx_irq < 0) ?
+			TP_SERIO_POLL_READ_TIMEOUT :
+			TP_SERIO_POLL_WAIT_TIMEOUT;
+
+	while (!kthread_should_stop()) {
+		drv->poll_ready = false;
+		tp_serio_serio_process_tx(drv);
+
+		if (drv->rx_irq < 0)
+			while (tp_serio_serio_process_rx(drv))
+				;
+
+		wait_event_interruptible_timeout(drv->poll_wq, drv->poll_ready,
+				msecs_to_jiffies(poll_timeout));
+	}
+	return 0;
+}
+
+static irqreturn_t tp_serio_alert_handler(int irq, void *dev_id)
+{
+	struct tp_serio_data *drv = (struct tp_serio_data *)dev_id;
+
+	while (tp_serio_serio_process_rx(drv))
+		;
+	return IRQ_HANDLED;
+}
+
+static int tp_serio_device_reset(struct tp_serio_data *drv)
+{
+	int result;
+	unsigned char response[TP_SERIO_CHUNK_SIZE];
+
+	memset(response, 0, sizeof(response));
+	result = tp_serio_request(drv, TP_SERIO_CMD_RESET, response);
+	if (result < 0)
+		return result;
+	if (!memcmp(response, tp_serio_cmd_reset_response, sizeof(response)))
+		result = 0;
+	else
+		result = -EINVAL;
+	return result;
+}
+
+static int tp_serio_device_query(struct tp_serio_data *drv)
+{
+	int result;
+	unsigned char response[TP_SERIO_CHUNK_SIZE];
+
+	memset(response, 0, sizeof(response));
+	result = tp_serio_request(drv, TP_SERIO_CMD_QUERY, response);
+	if (result < 0)
+		return result;
+	if (response[0] == TP_SERIO_CMD_QUERY) {
+		drv->num_ports = response[1];
+		result = 0;
+	} else {
+		result = -EINVAL;
+	}
+	return result;
+}
+
+#if defined(CONFIG_I2C)
+static int tp_serio_probe_i2c(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct tp_serio_data *drv;
+	unsigned int index;
+	unsigned int free_index;
+	int error;
+	int irq;
+	struct serio *s;
+
+	drv = devm_kzalloc(&client->dev, sizeof(*drv), GFP_KERNEL);
+	if (drv == NULL)
+		return -ENOMEM;
+	drv->dev_i2c = client;
+#if defined(CONFIG_SPI)
+	drv->dev_spi = NULL;
+#endif
+	if (tp_serio_device_reset(drv) < 0) {
+		dev_err(&client->dev, "no compatible device found at %s\n",
+			dev_name(&client->dev));
+		return -ENODEV;
+	}
+	error = tp_serio_device_query(drv);
+	if (error || (drv->num_ports == 0)) {
+		dev_err(&client->dev, "no available ports found at %s\n",
+			dev_name(&client->dev));
+		return -ENODEV;
+	}
+	drv->ports = devm_kzalloc(&client->dev,
+			sizeof(struct tp_serio_port) * drv->num_ports,
+			GFP_KERNEL);
+	if (drv->ports == NULL)
+		return -ENOMEM;
+	for (index = 0; index < drv->num_ports; index++) {
+		error = tp_serio_create_port(drv, index);
+		if (error)
+			goto err_out;
+	}
+	init_waitqueue_head(&drv->poll_wq);
+	drv->poll_ready = false;
+	drv->rx_irq = -1;
+	dev_set_drvdata(&client->dev, drv);
+
+	for (index = 0; index < drv->num_ports; index++) {
+		s = drv->ports[index].serio;
+		dev_info(&client->dev, "%s port at %s\n", s->name, s->phys);
+		serio_register_port(s);
+	}
+
+	if (client->dev.of_node != NULL) {
+		irq = of_irq_get(client->dev.of_node, 0);
+		if (irq >= 0) {
+			drv->rx_irq = irq;
+			error = devm_request_threaded_irq(&client->dev, irq,
+					NULL, tp_serio_alert_handler,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					"tp_serio", drv);
+			if (error) {
+				dev_set_drvdata(&client->dev, NULL);
+				index = drv->num_ports;
+				goto err_out;
+			} else {
+				tp_serio_alert_handler(drv->rx_irq, drv);
+			}
+		}
+	}
+	drv->poll_task = kthread_run(tp_serio_poll, drv,
+			"tp_serio i2c");
+	return 0;
+err_out:
+	for (free_index = 0; free_index < index; free_index++)
+		tp_serio_destroy_port(drv, free_index);
+	return error;
+}
+
+static void tp_serio_remove_i2c(struct i2c_client *client)
+{
+	struct tp_serio_data *drv =
+		(struct tp_serio_data *)dev_get_drvdata(&client->dev);
+	unsigned int index;
+
+	if (drv != NULL) {
+		kthread_stop(drv->poll_task);
+		for (index = 0; index < drv->num_ports; index++)
+			tp_serio_destroy_port(drv, index);
+		dev_set_drvdata(&client->dev, NULL);
+	}
+}
+#endif
+
+#if defined(CONFIG_SPI)
+static int tp_serio_probe_spi(struct spi_device *spi)
+{
+	struct tp_serio_data *drv;
+	unsigned int index;
+	unsigned int free_index;
+	int error;
+	int irq;
+	struct serio *s;
+
+	drv = devm_kzalloc(&spi->dev, sizeof(*drv), GFP_KERNEL);
+	if (drv == NULL)
+		return -ENOMEM;
+#if defined(CONFIG_I2C)
+	drv->dev_i2c = NULL;
+#endif
+	drv->dev_spi = spi;
+	if (tp_serio_device_reset(drv) < 0) {
+		dev_err(&spi->dev, "no compatible device found at %s\n",
+			dev_name(&spi->dev));
+		return -ENODEV;
+	}
+	error = tp_serio_device_query(drv);
+	if (error || (drv->num_ports == 0)) {
+		dev_err(&spi->dev, "no available ports found at %s\n",
+			dev_name(&spi->dev));
+		return -ENODEV;
+	}
+	drv->ports = devm_kzalloc(&spi->dev,
+			sizeof(struct tp_serio_port) * drv->num_ports,
+			GFP_KERNEL);
+	if (drv->ports == NULL)
+		return -ENOMEM;
+	for (index = 0; index < drv->num_ports; index++) {
+		error = tp_serio_create_port(drv, index);
+		if (error)
+			goto err_out;
+	}
+	init_waitqueue_head(&drv->poll_wq);
+	drv->poll_ready = false;
+	drv->rx_irq = -1;
+	spi_set_drvdata(spi, drv);
+
+	for (index = 0; index < drv->num_ports; index++) {
+		s = drv->ports[index].serio;
+		dev_info(&spi->dev, "%s port at %s\n", s->name, s->phys);
+		serio_register_port(s);
+	}
+
+	if (spi->dev.of_node != NULL) {
+		irq = of_irq_get(spi->dev.of_node, 0);
+		if (irq >= 0) {
+			drv->rx_irq = irq;
+			error = devm_request_threaded_irq(&spi->dev, irq,
+					NULL, tp_serio_alert_handler,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					"tp_serio", drv);
+			if (error) {
+				spi_set_drvdata(spi, NULL);
+				index = drv->num_ports;
+				goto err_out;
+			} else {
+				tp_serio_alert_handler(drv->rx_irq, drv);
+			}
+		}
+	}
+	drv->poll_task = kthread_run(tp_serio_poll, drv,
+			"tp_serio spi");
+	return 0;
+err_out:
+	for (free_index = 0; free_index < index; free_index++)
+		tp_serio_destroy_port(drv, free_index);
+	return error;
+}
+
+static void tp_serio_remove_spi(struct spi_device *spi)
+{
+	struct tp_serio_data *drv =
+		(struct tp_serio_data *)spi_get_drvdata(spi);
+	unsigned int index;
+
+	if (drv != NULL) {
+		kthread_stop(drv->poll_task);
+		for (index = 0; index < drv->num_ports; index++)
+			tp_serio_destroy_port(drv, index);
+		spi_set_drvdata(spi, NULL);
+	}
+}
+#endif
+
+static int tp_serio_register(struct tp_serio_driver *driver)
+{
+	int res = 0;
+#if defined(CONFIG_I2C)
+	res = i2c_register_driver(THIS_MODULE, &driver->i2c);
+#endif
+#if defined(CONFIG_SPI)
+	if (res == 0)
+		res = spi_register_driver(&driver->spi);
+#endif
+	return res;
+}
+
+static void tp_serio_unregister(struct tp_serio_driver *driver)
+{
+#if defined(CONFIG_SPI)
+	spi_unregister_driver(&driver->spi);
+#endif
+#if defined(CONFIG_I2C)
+	i2c_del_driver(&driver->i2c);
+#endif
+}
+
+static const struct of_device_id tp_serio_of_ids[] = {
+	{
+		.compatible = "tp,tp_serio",
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, tp_serio_of_ids);
+
+#if defined(CONFIG_I2C)
+static const struct i2c_device_id tp_serio_i2c_ids[] = {
+	{
+		.name = "tp_serio",
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tp_serio_i2c_ids);
+#endif
+
+#if defined(CONFIG_SPI)
+static const struct spi_device_id tp_serio_spi_ids[] = {
+	{
+		.name = "tp_serio",
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, tp_serio_spi_ids);
+#endif
+
+static struct tp_serio_driver tp_serio_drv = {
+#if defined(CONFIG_I2C)
+	{
+		.driver = {
+			.name = "tp_serio",
+			.owner = THIS_MODULE,
+			.of_match_table = of_match_ptr(tp_serio_of_ids)
+		},
+		.probe = tp_serio_probe_i2c,
+		.remove = tp_serio_remove_i2c,
+		.id_table = tp_serio_i2c_ids
+	},
+#endif
+#if defined(CONFIG_SPI)
+	{
+		.driver = {
+			.name = "tp_serio",
+			.owner = THIS_MODULE,
+			.of_match_table = of_match_ptr(tp_serio_of_ids)
+		},
+		.probe = tp_serio_probe_spi,
+		.remove = tp_serio_remove_spi,
+		.id_table = tp_serio_spi_ids
+	}
+#endif
+};
+
+module_driver(tp_serio_drv, tp_serio_register, tp_serio_unregister)
-- 
2.33.5



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

* [d-kernel] [PATCH 30/32] input: new driver - serdev-serio
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (27 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 29/32] input: added TF307 serio PS/2 emulator driver Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 13:19 ` [d-kernel] [PATCH 31/32] phy: realtek: leds configuration for RTL8211f Alexey Sheplyakov
  2022-12-14 15:06 ` [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Vitaly Chikunov
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

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

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

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

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

diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index 8e6b8b3ef478..967b5828ac50 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -328,4 +328,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 a47319040c19..8f1be3803fd3 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -34,3 +34,4 @@ 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/serdev-serio.c b/drivers/input/serio/serdev-serio.c
new file mode 100644
index 000000000000..c2170524d0db
--- /dev/null
+++ b/drivers/input/serio/serdev-serio.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Serdev Serio driver
+ *
+ * Copyright (C) 2022 Elpitech
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/serio.h>
+#include <linux/serdev.h>
+
+struct serdev_serio {
+	struct serdev_device *serdev;
+	struct serio *serio;
+};
+
+static int ss_serio_write(struct serio *serio, unsigned char data)
+{
+	struct serdev_serio *ss = serio->port_data;
+	struct serdev_device *serdev = ss->serdev;
+
+	dev_dbg(&serdev->dev, "ss_write: data %02x\n", data);
+	serdev_device_write(serdev, &data, 1, 0);
+
+	return 0;
+}
+
+static int ss_receive_buf(struct serdev_device *serdev,
+			  const unsigned char *buf, size_t count)
+{
+	struct serdev_serio *ss = serdev_device_get_drvdata(serdev);
+	int ret = count;
+
+	dev_dbg(&serdev->dev, "ss_receive: count %d, data %02x\n", (int)count, *buf);
+	while (count--)
+		serio_interrupt(ss->serio, *buf++, 0);
+
+	return ret;
+}
+
+static const struct serdev_device_ops ss_serdev_ops = {
+	.receive_buf    = ss_receive_buf,
+	.write_wakeup   = serdev_device_write_wakeup,
+};
+
+static int ss_probe(struct serdev_device *serdev)
+{
+	struct device *dev = &serdev->dev;
+	struct device_node *node = dev->of_node;
+	struct serdev_serio *ss;
+	struct serio *serio;
+	u32 speed = 0, proto;
+	int ret;
+
+	serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+	if (!serio)
+		return -ENOMEM;
+
+	ss = devm_kzalloc(dev, sizeof(*ss), GFP_KERNEL);
+	if (!ss)
+		return -ENOMEM;
+	ss->serdev = serdev;
+	ss->serio = serio;
+	ret = of_property_read_u32(node, "protocol", &proto);
+	if (ret < 0) {
+		dev_err(dev, "Can't read protocol property (ret %d)\n", ret);
+		return ret;
+	}
+	of_property_read_u32(node, "current-speed", &speed);
+	serdev_device_set_drvdata(serdev, ss);
+	serdev_device_set_client_ops(serdev, &ss_serdev_ops);
+	ret = serdev_device_open(serdev);
+	if (ret)
+		return ret;
+
+	if (speed)
+		serdev_device_set_baudrate(serdev, speed);
+	serdev_device_set_flow_control(serdev, false);
+
+	serio->port_data = ss;
+	strlcpy(serio->name, "Serdev Serio", sizeof(serio->name));
+	strlcpy(serio->phys, "serio", sizeof(serio->phys));
+	serio->id.type = SERIO_RS232;
+	serio->id.proto = proto;
+	serio->id.id = SERIO_ANY;
+	serio->id.extra = SERIO_ANY;
+	serio->write = ss_serio_write;
+	serio_register_port(serio);
+
+	return 0;
+}
+
+static void ss_remove(struct serdev_device *serdev)
+{
+	struct serdev_serio *ss = serdev_device_get_drvdata(serdev);
+	serdev_device_close(ss->serdev);
+	serio_unregister_port(ss->serio);
+}
+
+static const struct of_device_id ss_of_match[] = {
+	{ .compatible = "serdev,serio" },
+	{},
+};
+
+static struct serdev_device_driver serdev_serio_drv = {
+	.driver		= {
+		.name	= "serdev_serio",
+		.of_match_table = of_match_ptr(ss_of_match),
+	},
+	.probe  = ss_probe,
+	.remove = ss_remove,
+};
+
+module_serdev_device_driver(serdev_serio_drv);
+
+MODULE_AUTHOR("Vadim V. Vlasov <vvv19xx@gmail.com>");
+MODULE_DESCRIPTION("Serdev Serio driver");
+MODULE_LICENSE("GPL");
-- 
2.33.5



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

* [d-kernel] [PATCH 31/32] phy: realtek: leds configuration for RTL8211f
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (28 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 30/32] input: new driver - serdev-serio Alexey Sheplyakov
@ 2022-12-14 13:19 ` Alexey Sheplyakov
  2022-12-14 15:06 ` [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Vitaly Chikunov
  30 siblings, 0 replies; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-14 13:19 UTC (permalink / raw)
  To: devel-kernel
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

Configure leds according to 'realtek,led-mode', 'realtek,led[0-2]-control'
knobs (specified in DTB). Note that *all* of these parameters must be
set in DTB for this to work.
See https://my.basealt.space/issues/85133

X-feature-Baikal-M
DONTUPSTREAM
---
 drivers/net/phy/realtek.c                     | 55 +++++++++++++++++++
 .../dt-bindings/net/realtek-phy-rtl8211f.h    | 26 +++++++++
 2 files changed, 81 insertions(+)
 create mode 100644 include/dt-bindings/net/realtek-phy-rtl8211f.h

diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c
index 3d99fd6664d7..dd343b07ec8d 100644
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -9,6 +9,7 @@
  */
 #include <linux/bitops.h>
 #include <linux/of.h>
+#include <dt-bindings/net/realtek-phy-rtl8211f.h>
 #include <linux/phy.h>
 #include <linux/module.h>
 #include <linux/delay.h>
@@ -30,6 +31,7 @@
 #define RTL8211F_PHYCR1				0x18
 #define RTL8211F_PHYCR2				0x19
 #define RTL8211F_INSR				0x1d
+#define RTL8211F_LCR				0x10
 
 #define RTL8211F_TX_DELAY			BIT(8)
 #define RTL8211F_RX_DELAY			BIT(3)
@@ -328,6 +330,56 @@ static int rtl8211_config_aneg(struct phy_device *phydev)
 	return 0;
 }
 
+static void rtl8211f_config_led(struct phy_device *phydev)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct device_node *of_node = dev->of_node;
+	u16 val;
+	u32 led_mode, led0_ctrl, led1_ctrl, led2_ctrl;
+	int ret;
+
+	ret = of_property_read_u32(of_node, "realtek,led-mode", &led_mode);
+	if (ret < 0) {
+		dev_dbg(dev, "refusing to reconfigure leds: no 'realtek,led-mode' in dtb\n");
+		return;
+	}
+	ret = of_property_read_u32(of_node, "realtek,led0-control", &led0_ctrl);
+	if (ret < 0) {
+		dev_dbg(dev, "refusing to reconfigure leds: no 'realtek,led0-control' in dtb\n");
+		return;
+	}
+	ret = of_property_read_u32(of_node, "realtek,led1-control", &led1_ctrl);
+	if (ret < 0) {
+		dev_dbg(dev, "refusing to reconfigure leds: no 'realtek,led1-control' in dtb\n");
+		return;
+	}
+	ret = of_property_read_u32(of_node, "realtek,led2-control", &led2_ctrl);
+	if (ret < 0) {
+		dev_dbg(dev, "refusing to reconfigure leds: no 'realtek,led-control' in dtb\n");
+		return;
+	}
+
+	val = (led_mode << 15) | (led2_ctrl << 10) |
+	      (led1_ctrl << 5) | led0_ctrl;
+
+	ret = phy_write_paged(phydev, 0xd04, RTL8211F_LCR, val);
+	if (ret < 0)
+		dev_dbg(dev, "Failed to update the LED control register\n");
+}
+
+static int rtl8211f_config_aneg(struct phy_device *phydev)
+{
+	int ret;
+
+	rtl8211f_config_led(phydev);
+
+	ret = genphy_config_aneg(phydev);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
 static int rtl8211c_config_init(struct phy_device *phydev)
 {
 	/* RTL8211C has an issue when operating in Gigabit slave mode */
@@ -342,6 +394,8 @@ static int rtl8211f_config_init(struct phy_device *phydev)
 	u16 val_txdly, val_rxdly;
 	int ret;
 
+	rtl8211f_config_led(phydev);
+
 	ret = phy_modify_paged_changed(phydev, 0xa43, RTL8211F_PHYCR1,
 				       RTL8211F_ALDPS_PLL_OFF | RTL8211F_ALDPS_ENABLE | RTL8211F_ALDPS_XTAL_OFF,
 				       priv->phycr1);
@@ -923,6 +977,7 @@ static struct phy_driver realtek_drvs[] = {
 		PHY_ID_MATCH_EXACT(0x001cc916),
 		.name		= "RTL8211F Gigabit Ethernet",
 		.probe		= rtl821x_probe,
+		.config_aneg	= rtl8211f_config_aneg,
 		.config_init	= &rtl8211f_config_init,
 		.read_status	= rtlgen_read_status,
 		.config_intr	= &rtl8211f_config_intr,
diff --git a/include/dt-bindings/net/realtek-phy-rtl8211f.h b/include/dt-bindings/net/realtek-phy-rtl8211f.h
new file mode 100644
index 000000000000..46d17644119d
--- /dev/null
+++ b/include/dt-bindings/net/realtek-phy-rtl8211f.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _DT_BINDINGS_REALTEK_PHY_RTL8211F_H
+#define _DT_BINDINGS_REALTEK_PHY_RTL8211F_H
+
+/* LED modes for RTL8211F PHY */
+
+#define RTL8211F_LED_MODE_A	0
+#define RTL8211F_LED_MODE_B	1
+
+#define RTL8211F_LINK_10			1
+#define RTL8211F_LINK_100			2
+#define RTL8211F_LINK_10_100			3
+#define RTL8211F_LINK_1000			8
+#define RTL8211F_LINK_10_1000			9
+#define RTL8211F_LINK_100_1000			10
+#define RTL8211F_LINK_10_100_1000		11
+#define RTL8211F_ACTIVITY			16
+#define RTL8211F_LINK_10_ACTIVITY		17
+#define RTL8211F_LINK_100_ACTIVITY		18
+#define RTL8211F_LINK_10_100_ACTIVITY		19
+#define RTL8211F_LINK_1000_ACTIVITY		24
+#define RTL8211F_LINK_10_1000_ACTIVITY		25
+#define RTL8211F_LINK_100_1000_ACTIVITY		26
+#define RTL8211F_LINK_10_100_1000_ACTIVITY	27
+
+#endif
-- 
2.33.5



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

* Re: [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver
  2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
                   ` (29 preceding siblings ...)
  2022-12-14 13:19 ` [d-kernel] [PATCH 31/32] phy: realtek: leds configuration for RTL8211f Alexey Sheplyakov
@ 2022-12-14 15:06 ` Vitaly Chikunov
  2022-12-16  9:54   ` Alexey Sheplyakov
  30 siblings, 1 reply; 35+ messages in thread
From: Vitaly Chikunov @ 2022-12-14 15:06 UTC (permalink / raw)
  To: ALT Linux kernel packages development
  Cc: Роман
	Ставцев,
	Дмитрий
	Терёхин,
	Игорь
	Чудов,
	Евгений
	Синельников

Видимо, это для v6.1. Пропущен patch 32/32 и cover letter. Было бы
удобнее получать этот patchset в виде pull request.

Спасибо.

On Wed, Dec 14, 2022 at 05:18:48PM +0400, Alexey Sheplyakov wrote:
> On Baikal-M SoC clock management unit (CMU) is controled by
> the firmware (ARM-TF), since the registers of CMU are accessible
> only to the secure world. This drivers is a shim which calls into
> the firmware.
> 
> Signed-off-by: Alexey Sheplyakov <asheplyakov@basealt.ru>
> Co-developed-by: Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>
> X-feature-Baikal-M
> ---
>  drivers/clk/Makefile              |   1 +
>  drivers/clk/baikal-m/Makefile     |   1 +
>  drivers/clk/baikal-m/clk-baikal.c | 356 ++++++++++++++++++++++++++++++
>  3 files changed, 358 insertions(+)
>  create mode 100644 drivers/clk/baikal-m/Makefile
>  create mode 100644 drivers/clk/baikal-m/clk-baikal.c
> 
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index e3ca0d058a25..37615a5ff451 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -83,6 +83,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-m/
>  obj-$(CONFIG_CLK_BAIKAL_T1)		+= baikal-t1/
>  obj-y					+= bcm/
>  obj-$(CONFIG_ARCH_BERLIN)		+= berlin/
> diff --git a/drivers/clk/baikal-m/Makefile b/drivers/clk/baikal-m/Makefile
> new file mode 100644
> index 000000000000..56aa4de4081c
> --- /dev/null
> +++ b/drivers/clk/baikal-m/Makefile
> @@ -0,0 +1 @@
> +obj-y += clk-baikal.o
> \ No newline at end of file
> diff --git a/drivers/clk/baikal-m/clk-baikal.c b/drivers/clk/baikal-m/clk-baikal.c
> new file mode 100644
> index 000000000000..b3adcb727458
> --- /dev/null
> +++ b/drivers/clk/baikal-m/clk-baikal.c
> @@ -0,0 +1,356 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * clk-baikal.c - Baikal-M clock driver.
> + *
> + * Copyright (C) 2015,2016,2020,2021 Baikal Electronics JSC
> + * Authors:
> + *   Ekaterina Skachko <ekaterina.skachko@baikalelectronics.ru>
> + *   Alexey Sheplyakov <asheplyakov@basealt.ru>
> + */
> +
> +#include <linux/arm-smccc.h>
> +#include <linux/clk-provider.h>
> +#include <linux/clk.h>
> +#include <linux/clkdev.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.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;
> +	uint32_t	cmu_id;
> +	unsigned int	parent;
> +	const char	*name;
> +	uint32_t	is_clk_ch;
> +};
> +
> +#define to_baikal_cmu(_hw) container_of(_hw, struct baikal_clk_cmu, hw)
> +
> +/* Pointer to the place on handling SMC CMU calls in monitor */
> +#define BAIKAL_SMC_LCRU_ID	0x82000000
> +
> +static int baikal_clk_enable(struct clk_hw *hw)
> +{
> +	struct arm_smccc_res res;
> +	struct baikal_clk_cmu *pclk = to_baikal_cmu(hw);
> +	uint32_t cmd;
> +
> +	if (pclk->is_clk_ch) {
> +		cmd = CMU_CLK_CH_ENABLE;
> +	} else {
> +		cmd = CMU_PLL_ENABLE;
> +	}
> +
> +	arm_smccc_smc(BAIKAL_SMC_LCRU_ID, pclk->cmu_id, 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->cmu_id,
> +			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);
> +	uint32_t cmd;
> +
> +	if (pclk->is_clk_ch) {
> +		cmd = CMU_CLK_CH_DISABLE;
> +	} else {
> +		cmd = CMU_PLL_DISABLE;
> +	}
> +
> +	arm_smccc_smc(BAIKAL_SMC_LCRU_ID, pclk->cmu_id, 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->cmu_id,
> +			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);
> +	uint32_t cmd;
> +
> +	if (pclk->is_clk_ch) {
> +		cmd = CMU_CLK_CH_IS_ENABLED;
> +	} else {
> +		cmd = CMU_PLL_IS_ENABLED;
> +	}
> +
> +	arm_smccc_smc(BAIKAL_SMC_LCRU_ID, pclk->cmu_id, 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->cmu_id,
> +			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);
> +	uint32_t 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;
> +	}
> +
> +	arm_smccc_smc(BAIKAL_SMC_LCRU_ID, pclk->cmu_id, 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->cmu_id,
> +			res.a0);
> +
> +	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);
> +	uint32_t 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_LCRU_ID, pclk->cmu_id, 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->cmu_id,
> +			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);
> +	unsigned long parent;
> +	uint32_t cmd;
> +
> +	if (pclk->is_clk_ch) {
> +		cmd = CMU_CLK_CH_ROUND_RATE;
> +		parent = pclk->parent;
> +	} else {
> +		cmd = CMU_PLL_ROUND_RATE;
> +		parent = *prate;
> +	}
> +
> +	arm_smccc_smc(BAIKAL_SMC_LCRU_ID, pclk->cmu_id, 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->cmu_id,
> +			res.a0);
> +
> +	return res.a0;
> +}
> +
> +static const struct clk_ops be_clk_pll_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 __init baikal_clk_probe(struct device_node *node)
> +{
> +	struct clk_init_data init;
> +	struct clk_init_data *init_ch;
> +	struct baikal_clk_cmu *cmu;
> +	struct baikal_clk_cmu **cmu_ch;
> +
> +	struct clk *clk;
> +	struct clk_onecell_data *clk_ch;
> +
> +	int number, i = 0;
> +	u32 rc, index;
> +	struct property *prop;
> +	const __be32 *p;
> +	const char *clk_ch_name;
> +	const char *parent_name;
> +
> +	cmu = kzalloc(sizeof(struct baikal_clk_cmu), GFP_KERNEL);
> +	if (!cmu) {
> +		pr_err("%s: could not allocate CMU clk\n", __func__);
> +		return -ENOMEM;
> +	}
> +
> +	of_property_read_string(node, "clock-output-names", &cmu->name);
> +	of_property_read_u32(node, "clock-frequency", &cmu->parent);
> +	of_property_read_u32(node, "cmu-id", &cmu->cmu_id);
> +
> +	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 = &be_clk_pll_ops;
> +	init.flags = CLK_IGNORE_UNUSED;
> +
> +	cmu->hw.init = &init;
> +	cmu->is_clk_ch = 0;
> +
> +	/* Register the clock */
> +	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);
> +	}
> +
> +	clk_prepare_enable(clk);
> +
> +	number = of_property_count_u32_elems(node, "clock-indices");
> +
> +	if (number > 0) {
> +		clk_ch = kmalloc(sizeof(struct clk_onecell_data), GFP_KERNEL);
> + 		if (!clk_ch) {
> +			pr_err("%s: could not allocate CMU clk channel\n", __func__);
> +			return -ENOMEM;
> + 		}
> +
> +		/* Get the last index to find out max number of children*/
> +		of_property_for_each_u32(node, "clock-indices", prop, p, index) {
> +			;
> +		}
> +
> +		clk_ch->clks = kcalloc(index + 1, sizeof(struct clk *), GFP_KERNEL);
> +		clk_ch->clk_num = index + 1;
> +		cmu_ch = kcalloc((index + 1), sizeof(struct baikal_clk_cmu *), GFP_KERNEL);
> +		if (!cmu_ch) {
> +			kfree(clk_ch);
> +			return -ENOMEM;
> +		}
> +		init_ch = kcalloc((number + 1), sizeof(struct clk_init_data), GFP_KERNEL);
> +		if (!init_ch) {
> +			pr_err("%s: could not allocate CMU init structure \n", __func__);
> +			kfree(cmu_ch);
> +			kfree(clk_ch);
> +			return -ENOMEM;
> +		}
> +
> +		of_property_for_each_u32(node, "clock-indices", prop, p, index) {
> +			of_property_read_string_index(node, "clock-names",
> +							i, &clk_ch_name);
> +			pr_info("%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 = &be_clk_pll_ops;
> +			init_ch[i].flags = CLK_IGNORE_UNUSED;
> +
> +			cmu_ch[index] = kzalloc(sizeof(struct baikal_clk_cmu), GFP_KERNEL);
> +			if (!cmu_ch[index]) {
> +				pr_err("%s: could not allocate baikal_clk_cmu structure\n", __func__);
> +				return -ENOMEM;
> +			}
> +			cmu_ch[index]->name = clk_ch_name;
> +			cmu_ch[index]->cmu_id = index;
> +			cmu_ch[index]->parent = cmu->cmu_id;
> +			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);
> +			}
> +
> +			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);
> +			}
> +
> +			clk_prepare_enable(clk_ch->clks[index]);
> +			i++;
> +		}
> +
> +		return of_clk_add_provider(node, of_clk_src_onecell_get, clk_ch);
> +	}
> +
> +	return of_clk_add_provider(node, of_clk_src_simple_get, clk);
> +}
> +
> +static void __init baikal_clk_init(struct device_node *np)
> +{
> +	int err;
> +	err = baikal_clk_probe(np);
> +	if (err) {
> +		panic("%s: failed to probe clock %pOF: %d\n", __func__, np, err);
> +	} else {
> +		pr_info("%s: successfully probed %pOF\n", __func__, np);
> +	}
> +}
> +CLK_OF_DECLARE_DRIVER(baikal_cmu, "baikal,cmu", baikal_clk_init);
> +CLK_OF_DECLARE_DRIVER(bm1000_cmu, "baikal,bm1000-cmu", baikal_clk_init);
> -- 
> 2.33.5
> 
> _______________________________________________
> devel-kernel mailing list
> devel-kernel@lists.altlinux.org
> https://lists.altlinux.org/mailman/listinfo/devel-kernel


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

* Re: [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver
  2022-12-14 15:06 ` [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Vitaly Chikunov
@ 2022-12-16  9:54   ` Alexey Sheplyakov
  2022-12-16 12:34     ` Vitaly Chikunov
  0 siblings, 1 reply; 35+ messages in thread
From: Alexey Sheplyakov @ 2022-12-16  9:54 UTC (permalink / raw)
  To: ALT Linux kernel packages development
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

Здравствуйте!

On Wed, Dec 14, 2022 at 06:06:20PM +0300, Vitaly Chikunov wrote:

> Видимо, это для v6.1.

Верно

> Пропущен patch 32/32

В этом патче был baikal_defconfig_minimal. Он не влез в лимит
120 КБ на сообщение. Для сборки std-def/un-def не используется.
Полезен тем, что можно поглядеть, что нужно включить для поддержки
плат на Байкал-М.

> Было бы удобнее получать этот patchset в виде pull request.

1. Патчи можно забрать отсюда:
   https://gitflic.ru/project/alt-linux/linux.git baikalm-6.1.y

2. Для pull request нужна какая-то базовая ветка. Какая?
 
 
Всего доброго,
	Алексей


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

* Re: [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver
  2022-12-16  9:54   ` Alexey Sheplyakov
@ 2022-12-16 12:34     ` Vitaly Chikunov
  2022-12-16 12:40       ` Vitaly Chikunov
  0 siblings, 1 reply; 35+ messages in thread
From: Vitaly Chikunov @ 2022-12-16 12:34 UTC (permalink / raw)
  To: ALT Linux kernel packages development
  Cc: Роман
	Ставцев,
	Дмитрий
	Терёхин,
	Игорь
	Чудов,
	Евгений
	Синельников

On Fri, Dec 16, 2022 at 01:54:18PM +0400, Alexey Sheplyakov wrote:
> Здравствуйте!
> 
> On Wed, Dec 14, 2022 at 06:06:20PM +0300, Vitaly Chikunov wrote:
> 
> > Видимо, это для v6.1.
> 
> Верно
> 
> > Пропущен patch 32/32
> 
> В этом патче был baikal_defconfig_minimal. Он не влез в лимит
> 120 КБ на сообщение. Для сборки std-def/un-def не используется.
> Полезен тем, что можно поглядеть, что нужно включить для поддержки
> плат на Байкал-М.
> 
> > Было бы удобнее получать этот patchset в виде pull request.
> 
> 1. Патчи можно забрать отсюда:
>    https://gitflic.ru/project/alt-linux/linux.git baikalm-6.1.y
> 
> 2. Для pull request нужна какая-то базовая ветка. Какая?

Лучше с последнего апстримного тега этой ветки, в данном случае, так как
stable релизов её нет, то это v6.1.

Спасибо.

>  
>  
> Всего доброго,
> 	Алексей
> _______________________________________________
> devel-kernel mailing list
> devel-kernel@lists.altlinux.org
> https://lists.altlinux.org/mailman/listinfo/devel-kernel


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

* Re: [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver
  2022-12-16 12:34     ` Vitaly Chikunov
@ 2022-12-16 12:40       ` Vitaly Chikunov
  0 siblings, 0 replies; 35+ messages in thread
From: Vitaly Chikunov @ 2022-12-16 12:40 UTC (permalink / raw)
  To: ALT Linux kernel packages development
  Cc: Роман
	Ставцев,
	Игорь
	Чудов,
	Евгений
	Синельников,
	Дмитрий
	Терёхин

On Fri, Dec 16, 2022 at 03:34:58PM +0300, Vitaly Chikunov wrote:
> On Fri, Dec 16, 2022 at 01:54:18PM +0400, Alexey Sheplyakov wrote:
> > Здравствуйте!
> > 
> > On Wed, Dec 14, 2022 at 06:06:20PM +0300, Vitaly Chikunov wrote:
> > 
> > > Видимо, это для v6.1.
> > 
> > Верно
> > 
> > > Пропущен patch 32/32
> > 
> > В этом патче был baikal_defconfig_minimal. Он не влез в лимит
> > 120 КБ на сообщение. Для сборки std-def/un-def не используется.
> > Полезен тем, что можно поглядеть, что нужно включить для поддержки
> > плат на Байкал-М.
> > 
> > > Было бы удобнее получать этот patchset в виде pull request.
> > 
> > 1. Патчи можно забрать отсюда:
> >    https://gitflic.ru/project/alt-linux/linux.git baikalm-6.1.y

Pulled, thanks.

> > 
> > 2. Для pull request нужна какая-то базовая ветка. Какая?
> 
> Лучше с последнего апстримного тега этой ветки, в данном случае, так как
> stable релизов её нет, то это v6.1.
> 
> Спасибо.
> 
> >  
> >  
> > Всего доброго,
> > 	Алексей
> > _______________________________________________
> > devel-kernel mailing list
> > devel-kernel@lists.altlinux.org
> > https://lists.altlinux.org/mailman/listinfo/devel-kernel
> _______________________________________________
> devel-kernel mailing list
> devel-kernel@lists.altlinux.org
> https://lists.altlinux.org/mailman/listinfo/devel-kernel


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

end of thread, other threads:[~2022-12-16 12:40 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-12-14 13:18 [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Alexey Sheplyakov
2022-12-14 13:18 ` [d-kernel] [PATCH 02/32] cpufreq-dt: don't load on Baikal-M SoC Alexey Sheplyakov
2022-12-14 13:18 ` [d-kernel] [PATCH 03/32] serial: 8250_dw: verify clock rate in dw8250_set_termios Alexey Sheplyakov
2022-12-14 13:18 ` [d-kernel] [PATCH 04/32] usb: dwc3: of-simple: added compatible string for Baikal-M SoC Alexey Sheplyakov
2022-12-14 13:18 ` [d-kernel] [PATCH 05/32] dw-pcie: refuse to load on Baikal-M with recent firmware Alexey Sheplyakov
2022-12-14 13:18 ` [d-kernel] [PATCH 06/32] arm64: Enable armv8 based Baikal-M SoC support Alexey Sheplyakov
2022-12-14 13:18 ` [d-kernel] [PATCH 07/32] efi-rtc: avoid calling efi.get_time on Baikal-M SoC Alexey Sheplyakov
2022-12-14 13:18 ` [d-kernel] [PATCH 08/32] arm64-stub: fixed secondary cores boot " Alexey Sheplyakov
2022-12-14 13:18 ` [d-kernel] [PATCH 09/32] pm: disable all sleep states on Baikal-M based boards Alexey Sheplyakov
2022-12-14 13:18 ` [d-kernel] [PATCH 10/32] net: fwnode_get_phy_id: consider all compatible strings Alexey Sheplyakov
2022-12-14 13:18 ` [d-kernel] [PATCH 11/32] net: stmmac: inital support of Baikal-T1/M SoCs GMAC Alexey Sheplyakov
2022-12-14 13:18 ` [d-kernel] [PATCH 12/32] dt-bindings: dwmac: Add bindings for Baikal-T1/M SoCs Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 13/32] net: dwmac-baikal: added compatible strings Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 14/32] Added TF307/TF306 board management controller driver Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 15/32] hwmon: bt1-pvt: access registers via pvt_{readl, writel} helpers Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 16/32] hwmon: bt1-pvt: define pvt_readl/pvt_writel for Baikal-M SoC Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 17/32] hwmon: bt1-pvt: adjusted probing " Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 18/32] hwmon: bt1-pvt: added compatible baikal, pvt Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 19/32] drm: new bridge driver - stdp4028 Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 20/32] drm: added Baikal-M SoC video display unit driver Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 21/32] drm/bridge: dw-hdmi: support ahb audio hw revision 0x2a Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 22/32] dt-bindings: dw-hdmi: added ahb-audio-regshift Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 23/32] drm/bridge: dw-hdmi: force ahb audio register offset for Baikal-M Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 24/32] drm/panfrost: forcibly set dma-coherent on Baikal-M Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 25/32] drm/panfrost: disable devfreq " Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 26/32] ALSA: hda: Baikal-M support Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 27/32] PCI: pcie-baikal: driver for Baikal-M with new firmware Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 28/32] (BROKEN) dwc-i2s: support Baikal-M SoC Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 29/32] input: added TF307 serio PS/2 emulator driver Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 30/32] input: new driver - serdev-serio Alexey Sheplyakov
2022-12-14 13:19 ` [d-kernel] [PATCH 31/32] phy: realtek: leds configuration for RTL8211f Alexey Sheplyakov
2022-12-14 15:06 ` [d-kernel] [PATCH 01/32] clk: added Baikal-M clock management unit driver Vitaly Chikunov
2022-12-16  9:54   ` Alexey Sheplyakov
2022-12-16 12:34     ` Vitaly Chikunov
2022-12-16 12:40       ` Vitaly Chikunov

ALT Linux kernel packages development

This inbox may be cloned and mirrored by anyone:

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

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

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


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