Add Baikal-M support

Config values were inherited from kernel-5.15
This commit is contained in:
Mikhail Novosyolov 2023-03-15 17:53:05 +03:00
parent 15369c07df
commit 6f3cc7b4d9
36 changed files with 14009 additions and 3 deletions

View file

@ -0,0 +1,406 @@
From f7568f6bf018adb0ef07070d1022c9310aca0e7b Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Tue, 25 Jan 2022 17:57:05 +0400
Subject: [PATCH 600/631] clk: added Baikal-M clock management unit driver
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.35.2

View file

@ -0,0 +1,31 @@
From 1a1db98b37f950973da4ce1b0eb9244a45a1e34e Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Tue, 25 Jan 2022 17:57:20 +0400
Subject: [PATCH 601/631] cpufreq-dt: don't load on Baikal-M SoC
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.35.2

View file

@ -0,0 +1,72 @@
From b065db83f381e46d7df0d1dede700be5e44a4e18 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Tue, 11 Jan 2022 15:49:19 +0400
Subject: [PATCH 602/631] serial: 8250_dw: verify clock rate in
dw8250_set_termios
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.35.2

View file

@ -0,0 +1,29 @@
From 9e8d664c48a3137739aa6947bd27ac0e70638fe8 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Tue, 23 Nov 2021 19:55:35 +0400
Subject: [PATCH 603/631] usb: dwc3: of-simple: added compatible string for
Baikal-M SoC
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.35.2

View file

@ -0,0 +1,38 @@
From b059a8f7b58415e29d9d827aae47b1f7e654bda7 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Tue, 21 Jun 2022 20:28:28 +0400
Subject: [PATCH 604/631] dw-pcie: refuse to load on Baikal-M with recent
firmware
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.35.2

View file

@ -0,0 +1,42 @@
From 2ebd4524bfb28d1f949e4780b8988c1fffae6112 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Tue, 25 Jan 2022 17:58:48 +0400
Subject: [PATCH 605/631] arm64: Enable armv8 based Baikal-M SoC support
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.35.2

View file

@ -0,0 +1,48 @@
From 478429dcecb7032afe0e357dcd2dd1040d65b510 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 23 May 2022 19:28:42 +0400
Subject: [PATCH 606/631] efi-rtc: avoid calling efi.get_time on Baikal-M SoC
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.35.2

View file

@ -0,0 +1,147 @@
From ebb9492030235f2badcd528b6b2d829458cfa682 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 23 May 2022 19:28:24 +0400
Subject: [PATCH 607/631] arm64-stub: fixed secondary cores boot on Baikal-M
SoC
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.35.2

View file

@ -0,0 +1,64 @@
From 8002a8ec2680413a0abf5b09c181fdc9589eaf7d Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 23 May 2022 19:28:59 +0400
Subject: [PATCH 608/631] pm: disable all sleep states on Baikal-M based boards
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.35.2

View file

@ -0,0 +1,94 @@
From e308039232eb8c4a8f1916fb3f3a15bbdc894b67 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 23 May 2022 19:29:40 +0400
Subject: [PATCH 609/631] net: fwnode_get_phy_id: consider all compatible
strings
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.35.2

View file

@ -0,0 +1,539 @@
From 72d70b4c7ce2e495fde39503063b898013832489 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 24 Jan 2022 13:23:05 +0400
Subject: [PATCH 610/631] net: stmmac: inital support of Baikal-T1/M SoCs GMAC
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.35.2

View file

@ -0,0 +1,28 @@
From 7e299bf82714ff6a2ce3dac5555e14fca1f08fd6 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 24 Jan 2022 15:53:55 +0400
Subject: [PATCH 611/631] dt-bindings: dwmac: Add bindings for Baikal-T1/M SoCs
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.35.2

View file

@ -0,0 +1,29 @@
From 26d6af7901248c32437f7fc6ea0474f4e1f68380 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 23 May 2022 19:31:00 +0400
Subject: [PATCH 612/631] net: dwmac-baikal: added compatible strings...
... 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.35.2

View file

@ -0,0 +1,808 @@
From b24fd04d7e9210b18e0d823b7e9ef8b14e8cae4e Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 23 May 2022 19:47:39 +0400
Subject: [PATCH 613/631] Added TF307/TF306 board management controller driver
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.35.2

View file

@ -0,0 +1,257 @@
From 99329f67a1be714f1b6b67a99f33e0c54277dbf4 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Tue, 25 Jan 2022 17:54:33 +0400
Subject: [PATCH 614/631] hwmon: bt1-pvt: access registers via
pvt_{readl,writel} helpers
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.35.2

View file

@ -0,0 +1,93 @@
From 72ad36ce7e7933180893deff7c0496856b40a5fd Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Tue, 25 Jan 2022 17:55:03 +0400
Subject: [PATCH 615/631] hwmon: bt1-pvt: define pvt_readl/pvt_writel for
Baikal-M SoC
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.35.2

View file

@ -0,0 +1,143 @@
From dd5e2188d83e6900fd35c346e134e4f0fa55f1f8 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Tue, 25 Jan 2022 17:55:26 +0400
Subject: [PATCH 616/631] hwmon: bt1-pvt: adjusted probing for Baikal-M SoC
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.35.2

View file

@ -0,0 +1,29 @@
From 00ece69befa036262efc7bc1179a3867e30e2f5a Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 23 May 2022 19:31:38 +0400
Subject: [PATCH 617/631] hwmon: bt1-pvt: added compatible baikal,pvt
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.35.2

View file

@ -0,0 +1,544 @@
From b737d0826dcedab258f7806cc36f8e7859203151 Mon Sep 17 00:00:00 2001
From: "Vadim V. Vlasov" <vvv19xx@gmail.com>
Date: Thu, 1 Oct 2020 20:27:58 +0300
Subject: [PATCH 618/631] drm: new bridge driver - stdp4028
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.35.2

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,257 @@
From 4853d8fe871a41a874b9ec3dfd25958f29de0f31 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Thu, 2 Jun 2022 18:21:23 +0400
Subject: [PATCH 620/631] drm/bridge: dw-hdmi: support ahb audio hw revision
0x2a
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.35.2

View file

@ -0,0 +1,38 @@
From 1e7845b12e50b387eeea2cb6da0cb1626498a742 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 6 Jun 2022 16:46:44 +0400
Subject: [PATCH 621/631] dt-bindings: dw-hdmi: added ahb-audio-regshift
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.35.2

View file

@ -0,0 +1,41 @@
From 74646506e93f8ea85cfe23e4f6beee5f4de2f9dd Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Thu, 2 Jun 2022 18:30:31 +0400
Subject: [PATCH 622/631] drm/bridge: dw-hdmi: force ahb audio register offset
for Baikal-M
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.35.2

View file

@ -0,0 +1,63 @@
From 6880b73834ecf09d3b1feeaff8ee430972e5eacc Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 23 May 2022 19:32:47 +0400
Subject: [PATCH 623/631] drm/panfrost: forcibly set dma-coherent on Baikal-M
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.35.2

View file

@ -0,0 +1,38 @@
From fed148f1453de5a632d40e6b81c355c678d9a273 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 23 May 2022 19:34:31 +0400
Subject: [PATCH 624/631] drm/panfrost: disable devfreq on Baikal-M
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.35.2

View file

@ -0,0 +1,706 @@
From c81a31d66827b1d3188527e7666f29669de00e6e Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 23 May 2022 19:34:48 +0400
Subject: [PATCH 625/631] ALSA: hda: Baikal-M support
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.35.2

View file

@ -0,0 +1,813 @@
From d5239cf1f14f0d1410c80a1384e6af2d7a100195 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Wed, 22 Jun 2022 11:41:58 +0400
Subject: [PATCH 626/631] PCI: pcie-baikal: driver for Baikal-M with new
firmware
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.35.2

View file

@ -0,0 +1,102 @@
From 843884b7313d3976054b6a697e41203be5d75ffc Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Mon, 23 May 2022 19:35:08 +0400
Subject: [PATCH 627/631] (BROKEN) dwc-i2s: support Baikal-M SoC
* 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.35.2

View file

@ -0,0 +1,804 @@
From 78a6cda8dc02f10d5040a779aeee2c5c4e1c3bce Mon Sep 17 00:00:00 2001
From: "Vadim V. Vlasov" <vvv19xx@gmail.com>
Date: Fri, 2 Oct 2020 15:30:34 +0300
Subject: [PATCH 628/631] input: added TF307 serio PS/2 emulator driver
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.35.2

View file

@ -0,0 +1,180 @@
From d9269254dcd782acbcd84ee30b16db15fa1029bc Mon Sep 17 00:00:00 2001
From: "Vadim V. Vlasov" <vadim.vlasov@elpitech.ru>
Date: Mon, 28 Mar 2022 17:23:11 +0300
Subject: [PATCH 629/631] input: new driver - serdev-serio
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.35.2

View file

@ -0,0 +1,147 @@
From 83f15f827744a6b14446188d72f77759c6837054 Mon Sep 17 00:00:00 2001
From: Alexey Sheplyakov <asheplyakov@basealt.ru>
Date: Wed, 12 Oct 2022 13:08:04 +0400
Subject: [PATCH 630/631] phy: realtek: leds configuration for RTL8211f
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.35.2

File diff suppressed because it is too large Load diff

View file

@ -316,6 +316,7 @@ CONFIG_ARCH_ACTIONS=y
CONFIG_ARCH_SUNXI=y CONFIG_ARCH_SUNXI=y
CONFIG_ARCH_ALPINE=y CONFIG_ARCH_ALPINE=y
# CONFIG_ARCH_APPLE is not set # CONFIG_ARCH_APPLE is not set
CONFIG_ARCH_BAIKAL=y
CONFIG_ARCH_BCM=y CONFIG_ARCH_BCM=y
CONFIG_ARCH_BCM2835=y CONFIG_ARCH_BCM2835=y
CONFIG_ARCH_BCM_IPROC=y CONFIG_ARCH_BCM_IPROC=y
@ -2165,6 +2166,7 @@ CONFIG_PCIE_ROCKCHIP_DW_HOST=y
CONFIG_PCIE_KEEMBAY=y CONFIG_PCIE_KEEMBAY=y
CONFIG_PCIE_KEEMBAY_HOST=y CONFIG_PCIE_KEEMBAY_HOST=y
CONFIG_PCIE_KEEMBAY_EP=y CONFIG_PCIE_KEEMBAY_EP=y
CONFIG_PCI_BAIKAL=y
CONFIG_PCIE_KIRIN=y CONFIG_PCIE_KIRIN=y
CONFIG_PCIE_HISI_STB=y CONFIG_PCIE_HISI_STB=y
CONFIG_PCI_MESON=y CONFIG_PCI_MESON=y
@ -2772,6 +2774,7 @@ CONFIG_MISC_RTSX=m
CONFIG_HISI_HIKEY_USB=m CONFIG_HISI_HIKEY_USB=m
CONFIG_OPEN_DICE=m CONFIG_OPEN_DICE=m
CONFIG_VCPU_STALL_DETECTOR=m CONFIG_VCPU_STALL_DETECTOR=m
CONFIG_TP_BMC=y
CONFIG_C2PORT=m CONFIG_C2PORT=m
# #
@ -3636,6 +3639,7 @@ CONFIG_STMMAC_ETH=m
CONFIG_STMMAC_PLATFORM=m CONFIG_STMMAC_PLATFORM=m
CONFIG_DWMAC_DWC_QOS_ETH=m CONFIG_DWMAC_DWC_QOS_ETH=m
CONFIG_DWMAC_GENERIC=m CONFIG_DWMAC_GENERIC=m
CONFIG_DWMAC_BAIKAL=m
CONFIG_DWMAC_IPQ806X=m CONFIG_DWMAC_IPQ806X=m
CONFIG_DWMAC_MEDIATEK=m CONFIG_DWMAC_MEDIATEK=m
CONFIG_DWMAC_MESON=m CONFIG_DWMAC_MESON=m
@ -4671,8 +4675,10 @@ CONFIG_SERIO_PS2MULT=m
CONFIG_SERIO_ARC_PS2=m CONFIG_SERIO_ARC_PS2=m
CONFIG_SERIO_APBPS2=m CONFIG_SERIO_APBPS2=m
CONFIG_SERIO_SUN4I_PS2=m CONFIG_SERIO_SUN4I_PS2=m
CONFIG_SERIO_TPLATFORMS=m
CONFIG_SERIO_GPIO_PS2=m CONFIG_SERIO_GPIO_PS2=m
CONFIG_USERIO=m CONFIG_USERIO=m
CONFIG_SERDEV_SERIO=m
CONFIG_GAMEPORT=m CONFIG_GAMEPORT=m
CONFIG_GAMEPORT_NS558=m CONFIG_GAMEPORT_NS558=m
CONFIG_GAMEPORT_L4=m CONFIG_GAMEPORT_L4=m
@ -5723,6 +5729,8 @@ CONFIG_SENSORS_AXI_FAN_CONTROL=m
CONFIG_SENSORS_ARM_SCMI=m CONFIG_SENSORS_ARM_SCMI=m
CONFIG_SENSORS_ARM_SCPI=m CONFIG_SENSORS_ARM_SCPI=m
CONFIG_SENSORS_ATXP1=m CONFIG_SENSORS_ATXP1=m
CONFIG_SENSORS_BT1_PVT=m
CONFIG_SENSORS_BT1_PVT_ALARMS=y
CONFIG_SENSORS_CORSAIR_CPRO=m CONFIG_SENSORS_CORSAIR_CPRO=m
CONFIG_SENSORS_CORSAIR_PSU=m CONFIG_SENSORS_CORSAIR_PSU=m
CONFIG_SENSORS_DRIVETEMP=m CONFIG_SENSORS_DRIVETEMP=m
@ -7502,6 +7510,7 @@ CONFIG_DRM_MALI_DISPLAY=m
CONFIG_DRM_KOMEDA=m CONFIG_DRM_KOMEDA=m
# end of ARM devices # end of ARM devices
CONFIG_DRM_BAIKAL_VDU=m
CONFIG_DRM_RADEON=m CONFIG_DRM_RADEON=m
# CONFIG_DRM_RADEON_USERPTR is not set # CONFIG_DRM_RADEON_USERPTR is not set
CONFIG_DRM_AMDGPU=m CONFIG_DRM_AMDGPU=m
@ -7718,6 +7727,7 @@ CONFIG_DRM_LONTIUM_LT9611=m
CONFIG_DRM_LONTIUM_LT9611UXC=m CONFIG_DRM_LONTIUM_LT9611UXC=m
CONFIG_DRM_ITE_IT66121=m CONFIG_DRM_ITE_IT66121=m
CONFIG_DRM_LVDS_CODEC=m CONFIG_DRM_LVDS_CODEC=m
CONFIG_DRM_BAIKAL_HDMI=m
CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW=m CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW=m
CONFIG_DRM_NWL_MIPI_DSI=m CONFIG_DRM_NWL_MIPI_DSI=m
CONFIG_DRM_NXP_PTN3460=m CONFIG_DRM_NXP_PTN3460=m
@ -7738,6 +7748,7 @@ CONFIG_DRM_TI_TFP410=m
CONFIG_DRM_TI_SN65DSI83=m CONFIG_DRM_TI_SN65DSI83=m
CONFIG_DRM_TI_SN65DSI86=m CONFIG_DRM_TI_SN65DSI86=m
CONFIG_DRM_TI_TPD12S015=m CONFIG_DRM_TI_TPD12S015=m
CONFIG_DRM_STDP4028=m
CONFIG_DRM_ANALOGIX_ANX6345=m CONFIG_DRM_ANALOGIX_ANX6345=m
CONFIG_DRM_ANALOGIX_ANX78XX=m CONFIG_DRM_ANALOGIX_ANX78XX=m
CONFIG_DRM_ANALOGIX_DP=m CONFIG_DRM_ANALOGIX_DP=m
@ -8094,6 +8105,7 @@ CONFIG_SND_HDA=m
CONFIG_SND_HDA_GENERIC_LEDS=y CONFIG_SND_HDA_GENERIC_LEDS=y
CONFIG_SND_HDA_INTEL=m CONFIG_SND_HDA_INTEL=m
CONFIG_SND_HDA_TEGRA=m CONFIG_SND_HDA_TEGRA=m
CONFIG_SND_HDA_BAIKAL_M=m
CONFIG_SND_HDA_HWDEP=y CONFIG_SND_HDA_HWDEP=y
CONFIG_SND_HDA_RECONFIG=y CONFIG_SND_HDA_RECONFIG=y
CONFIG_SND_HDA_INPUT_BEEP=y CONFIG_SND_HDA_INPUT_BEEP=y

View file

@ -4421,6 +4421,7 @@ CONFIG_SERIO_ALTERA_PS2=m
CONFIG_SERIO_PS2MULT=m CONFIG_SERIO_PS2MULT=m
CONFIG_SERIO_ARC_PS2=m CONFIG_SERIO_ARC_PS2=m
CONFIG_HYPERV_KEYBOARD=m CONFIG_HYPERV_KEYBOARD=m
CONFIG_SERIO_TPLATFORMS=m
CONFIG_SERIO_GPIO_PS2=m CONFIG_SERIO_GPIO_PS2=m
CONFIG_USERIO=m CONFIG_USERIO=m
CONFIG_GAMEPORT=m CONFIG_GAMEPORT=m
@ -6836,7 +6837,6 @@ CONFIG_DRM_I915_STOP_TIMEOUT=100
CONFIG_DRM_I915_TIMESLICE_DURATION=1 CONFIG_DRM_I915_TIMESLICE_DURATION=1
# end of drm/i915 Profile Guided Optimisation # end of drm/i915 Profile Guided Optimisation
CONFIG_DRM_I915_GVT=y
CONFIG_DRM_VGEM=m CONFIG_DRM_VGEM=m
CONFIG_DRM_VKMS=m CONFIG_DRM_VKMS=m
CONFIG_DRM_VMWGFX=m CONFIG_DRM_VMWGFX=m
@ -6863,6 +6863,7 @@ CONFIG_DRM_PANEL_BRIDGE=y
# #
# Display Interface Bridges # Display Interface Bridges
# #
# CONFIG_DRM_BAIKAL_HDMI is not set
CONFIG_DRM_ANALOGIX_ANX78XX=m CONFIG_DRM_ANALOGIX_ANX78XX=m
CONFIG_DRM_ANALOGIX_DP=m CONFIG_DRM_ANALOGIX_DP=m
# end of Display Interface Bridges # end of Display Interface Bridges
@ -10595,7 +10596,6 @@ CONFIG_AUFS_BRANCH_MAX_1023=y
CONFIG_AUFS_SBILIST=y CONFIG_AUFS_SBILIST=y
# CONFIG_AUFS_HNOTIFY is not set # CONFIG_AUFS_HNOTIFY is not set
CONFIG_AUFS_EXPORT=y CONFIG_AUFS_EXPORT=y
CONFIG_AUFS_INO_T_64=y
CONFIG_AUFS_XATTR=y CONFIG_AUFS_XATTR=y
# CONFIG_AUFS_FHSM is not set # CONFIG_AUFS_FHSM is not set
# CONFIG_AUFS_RDU is not set # CONFIG_AUFS_RDU is not set

View file

@ -4395,6 +4395,7 @@ CONFIG_SERIO_ALTERA_PS2=m
CONFIG_SERIO_PS2MULT=m CONFIG_SERIO_PS2MULT=m
CONFIG_SERIO_ARC_PS2=m CONFIG_SERIO_ARC_PS2=m
CONFIG_HYPERV_KEYBOARD=m CONFIG_HYPERV_KEYBOARD=m
CONFIG_SERIO_TPLATFORMS=m
CONFIG_SERIO_GPIO_PS2=m CONFIG_SERIO_GPIO_PS2=m
CONFIG_USERIO=m CONFIG_USERIO=m
CONFIG_GAMEPORT=m CONFIG_GAMEPORT=m
@ -6765,6 +6766,7 @@ CONFIG_DRM_PANEL_BRIDGE=y
# #
# Display Interface Bridges # Display Interface Bridges
# #
# CONFIG_DRM_BAIKAL_HDMI is not set
CONFIG_DRM_ANALOGIX_ANX78XX=m CONFIG_DRM_ANALOGIX_ANX78XX=m
CONFIG_DRM_ANALOGIX_DP=m CONFIG_DRM_ANALOGIX_DP=m
# end of Display Interface Bridges # end of Display Interface Bridges

View file

@ -35,7 +35,7 @@
%define sublevel 19 %define sublevel 19
# Release number. Increase this before a rebuild. # Release number. Increase this before a rebuild.
%define rpmrel 2 %define rpmrel 3
%define fullrpmrel %{rpmrel} %define fullrpmrel %{rpmrel}
%define rpmtag %{disttag} %define rpmtag %{disttag}
@ -348,6 +348,41 @@ Patch0401: 0401-ASoC-es8316-Use-increased-GPIO-debounce-time.patch
Patch0402: 0402-ASoC-Intel-sof_es8336-Add-more-quirks-for-Russian-ha.patch Patch0402: 0402-ASoC-Intel-sof_es8336-Add-more-quirks-for-Russian-ha.patch
Patch0403: 0403-ASoC-Intel-sof_es8336-Add-a-quirk-for-Aquarius-NS685.patch Patch0403: 0403-ASoC-Intel-sof_es8336-Add-a-quirk-for-Aquarius-NS685.patch
# Support Baikal-M (aarch64)
# From https://github.com/asheplyakov/linux/commits/baikalm-6.1.y
Patch0600: 0600-clk-added-Baikal-M-clock-management-unit-driver.patch
Patch0601: 0601-cpufreq-dt-don-t-load-on-Baikal-M-SoC.patch
Patch0602: 0602-serial-8250_dw-verify-clock-rate-in-dw8250_set_termi.patch
Patch0603: 0603-usb-dwc3-of-simple-added-compatible-string-for-Baika.patch
Patch0604: 0604-dw-pcie-refuse-to-load-on-Baikal-M-with-recent-firmw.patch
Patch0605: 0605-arm64-Enable-armv8-based-Baikal-M-SoC-support.patch
Patch0606: 0606-efi-rtc-avoid-calling-efi.get_time-on-Baikal-M-SoC.patch
Patch0607: 0607-arm64-stub-fixed-secondary-cores-boot-on-Baikal-M-So.patch
Patch0608: 0608-pm-disable-all-sleep-states-on-Baikal-M-based-boards.patch
Patch0609: 0609-net-fwnode_get_phy_id-consider-all-compatible-string.patch
Patch0610: 0610-net-stmmac-inital-support-of-Baikal-T1-M-SoCs-GMAC.patch
Patch0611: 0611-dt-bindings-dwmac-Add-bindings-for-Baikal-T1-M-SoCs.patch
Patch0612: 0612-net-dwmac-baikal-added-compatible-strings.patch
Patch0613: 0613-Added-TF307-TF306-board-management-controller-driver.patch
Patch0614: 0614-hwmon-bt1-pvt-access-registers-via-pvt_-readl-writel.patch
Patch0615: 0615-hwmon-bt1-pvt-define-pvt_readl-pvt_writel-for-Baikal.patch
Patch0616: 0616-hwmon-bt1-pvt-adjusted-probing-for-Baikal-M-SoC.patch
Patch0617: 0617-hwmon-bt1-pvt-added-compatible-baikal-pvt.patch
Patch0618: 0618-drm-new-bridge-driver-stdp4028.patch
Patch0619: 0619-drm-added-Baikal-M-SoC-video-display-unit-driver.patch
Patch0620: 0620-drm-bridge-dw-hdmi-support-ahb-audio-hw-revision-0x2.patch
Patch0621: 0621-dt-bindings-dw-hdmi-added-ahb-audio-regshift.patch
Patch0622: 0622-drm-bridge-dw-hdmi-force-ahb-audio-register-offset-f.patch
Patch0623: 0623-drm-panfrost-forcibly-set-dma-coherent-on-Baikal-M.patch
Patch0624: 0624-drm-panfrost-disable-devfreq-on-Baikal-M.patch
Patch0625: 0625-ALSA-hda-Baikal-M-support.patch
Patch0626: 0626-PCI-pcie-baikal-driver-for-Baikal-M-with-new-firmwar.patch
Patch0627: 0627-BROKEN-dwc-i2s-support-Baikal-M-SoC.patch
Patch0628: 0628-input-added-TF307-serio-PS-2-emulator-driver.patch
Patch0629: 0629-input-new-driver-serdev-serio.patch
Patch0630: 0630-phy-realtek-leds-configuration-for-RTL8211f.patch
Patch0631: 0631-arm64-defconfig-for-Baikal-M-testing.patch
# Disable AutoReq # Disable AutoReq
AutoReq: 0 AutoReq: 0
# but keep autoprov for kmod(xxx) # but keep autoprov for kmod(xxx)