Hello,
This patch series adds initial drivers and Device Trees for Sunplus Plus1
series (codename Pentagram) SP7021 SoC and Banana Pi BPI-F2S SBC.
First, minimal Kconfig, DT and earlycon driver are prepared to get serial
output at all. Next, interrupt controller and full serial driver are added
that allow to boot into an initrd with interactive serial console.
Device Tree files added are for the CPU-Chip (aka A-Chip) with quad Cortex-A7,
but the file split prepares for also adding the Peripheral-Chip (B-Chip) with
ARM9 later. However, for now this is not reflected in the .dts filename; this
corresponds to the vf610- vs. vf610m4- naming scheme, whereas an alternative
would be to use sp7021-cchip- vs. -pchip- prefix (as sp7021-cpu- looks weird).
It is assumed we can reuse the same SoC and board bindings for CA7 and ARM9
and only differ for IP blocks where needed.
My inquiry to Sunplus about their GIC (anticipating complaints from Marc)
remained unanswered, so I've added the two extra regions and irq myself,
without being able to test KVM due to BSP U-Boot not booting in HYP mode.
According to Sunplus the mode can be changed in U-Boot (but where/how?).
Similarly, the architectural timer is not properly initialized in BSP U-Boot,
so that I currently have a mach- hack in my tree below. Unlike RTD1195,
we do have U-Boot sources (v2019.04 based), so should be able to fix this
in the bootloader rather than in the kernel, thus not included as patch here.
Based on SoC online manual [1] and downstream BPI-F2S BSP tree [2] as well as
my previous Actions serial and Realtek irqchip drivers and DTs.
More details at:
https://en.opensuse.org/HCL:BananaPi_F2S
Latest experimental patches at:
https://github.com/afaerber/linux/commits/f2s-next
Have a lot of fun!
Cheers,
Andreas
[1] https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/pages/470450252/SP7021+Technical+Manual
[2] https://github.com/BPI-SINOVOIP/BPI-F2S-bsp
Cc: [email protected]
Cc: Rob Herring <[email protected]>
Cc: Greg Kroah-Hartman <[email protected]>
Cc: [email protected]
Cc: Thomas Gleixner <[email protected]>
Cc: Jason Cooper <[email protected]>
Cc: Marc Zyngier <[email protected]>
Cc: Wells Lu 呂芳騰 <[email protected]>
Cc: Dvorkin Dmitry <[email protected]>
Andreas Färber (11):
dt-bindings: vendor-prefixes: Add Sunplus
dt-bindings: arm: Add Sunplus SP7021 and Banana Pi BPI-F2S
ARM: Prepare Sunplus Plus1 SoC family
dt-bindings: interrupt-controller: Add Sunplus SP7021 mux
dt-bindings: serial: Add Sunplus SP7021 UART
tty: serial: Add Sunplus Plus1 UART earlycon
ARM: dts: Add Sunplus Plus1 SP7021 and Banana Pi F2S
tty: serial: sunplus: Implement full UART driver
irqchip: Add Sunplus SP7021 interrupt (mux) controller
ARM: dts: sp7021-cpu: Add interrupt controller node
ARM: dts: sp7021-cpu: Add dummy UART0 clock and interrupt
Documentation/devicetree/bindings/arm/sunplus.yaml | 22 +
.../sunplus,pentagram-intc.yaml | 50 ++
.../bindings/serial/sunplus,pentagram-uart.yaml | 24 +
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
arch/arm/Kconfig | 2 +
arch/arm/Makefile | 1 +
arch/arm/boot/dts/Makefile | 2 +
arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts | 29 +
arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi | 93 +++
arch/arm/boot/dts/pentagram-sp7021.dtsi | 61 ++
arch/arm/mach-sunplus/Kconfig | 10 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-sp7021.c | 285 ++++++++
drivers/tty/serial/Kconfig | 19 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/sunplus-uart.c | 770 +++++++++++++++++++++
include/uapi/linux/serial_core.h | 3 +
17 files changed, 1375 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/sunplus.yaml
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/sunplus,pentagram-intc.yaml
create mode 100644 Documentation/devicetree/bindings/serial/sunplus,pentagram-uart.yaml
create mode 100644 arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts
create mode 100644 arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi
create mode 100644 arch/arm/boot/dts/pentagram-sp7021.dtsi
create mode 100644 arch/arm/mach-sunplus/Kconfig
create mode 100644 drivers/irqchip/irq-sp7021.c
create mode 100644 drivers/tty/serial/sunplus-uart.c
--
2.16.4
The Sunplus SP7021 SoC has an interrupt mux.
Cc: Wells Lu 呂芳騰 <[email protected]>
Signed-off-by: Andreas Färber <[email protected]>
---
.../sunplus,pentagram-intc.yaml | 50 ++++++++++++++++++++++
1 file changed, 50 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/sunplus,pentagram-intc.yaml
diff --git a/Documentation/devicetree/bindings/interrupt-controller/sunplus,pentagram-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/sunplus,pentagram-intc.yaml
new file mode 100644
index 000000000000..baaf7bcd4a71
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/sunplus,pentagram-intc.yaml
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/sunplus,pentagram-intc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus Pentagram SoC Interrupt Controller
+
+maintainers:
+ - Andreas Färber <[email protected]>
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+properties:
+ compatible:
+ const: sunplus,sp7021-intc
+
+ reg:
+ maxItems: 2
+
+ interrupts:
+ maxItems: 2
+
+ interrupt-controller: true
+
+ "#interrupt-cells":
+ const: 2
+
+required:
+ - compatible
+ - reg
+ - interrupt-controller
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ interrupt-controller@9c000780 {
+ compatible = "sunplus,sp7021-intc";
+ reg = <0x9c000780 0x80>,
+ <0x9c000a80 0x80>;
+ interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+...
--
2.16.4
Signed-off-by: Andreas Färber <[email protected]>
---
arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts | 7 +++++++
arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi | 5 +++++
2 files changed, 12 insertions(+)
diff --git a/arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts b/arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts
index 3c25b6e79fe2..455416ce9d82 100644
--- a/arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts
+++ b/arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts
@@ -15,8 +15,15 @@
chosen {
stdout-path = "serial0:115200n8";
};
+
+ uart0_clk: clk {
+ compatible = "fixed-clock";
+ clock-frequency = <27000000>;
+ #clock-cells = <0>;
+ };
};
&uart0 {
status = "okay";
+ clocks = <&uart0_clk>;
};
diff --git a/arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi b/arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi
index 7e424baa9214..48c5986a31ed 100644
--- a/arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi
+++ b/arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi
@@ -86,3 +86,8 @@
#interrupt-cells = <2>;
};
};
+
+&uart0 {
+ interrupt-parent = <&intc>;
+ interrupts = <53 IRQ_TYPE_LEVEL_HIGH>;
+};
--
2.16.4
Signed-off-by: Andreas Färber <[email protected]>
---
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-sp7021.c | 285 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 286 insertions(+)
create mode 100644 drivers/irqchip/irq-sp7021.c
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index eae0d78cbf22..a6b70d666739 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -105,3 +105,4 @@ obj-$(CONFIG_MADERA_IRQ) += irq-madera.o
obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o
obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o
obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
+obj-$(CONFIG_ARCH_SUNPLUS) += irq-sp7021.o
diff --git a/drivers/irqchip/irq-sp7021.c b/drivers/irqchip/irq-sp7021.c
new file mode 100644
index 000000000000..a0b7972f2abb
--- /dev/null
+++ b/drivers/irqchip/irq-sp7021.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Sunplus Plus1 SP7021 SoC interrupt controller
+ *
+ * Copyright (c) 2020 Andreas Färber
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+
+#define REG_INTC_INTR_TYPE(i) (0x0 + (i) * 4)
+#define REG_INTC_INTR_POLARITY(i) (0x1c + (i) * 4)
+#define REG_INTC_INTR_PRIO(i) (0x38 + (i) * 4)
+#define REG_INTC_INTR_MASK(i) (0x54 + (i) * 4)
+
+#define REG_INTC_INTR_CLR(i) (0x0 + (i) * 4)
+#define REG_INTC_MASKED_FIQS(i) (0x1c + (i) * 4)
+#define REG_INTC_MASKED_IRQS(i) (0x38 + (i) * 4)
+#define REG_INTC_INTR_GROUP 0x7c
+
+#define INTC_INTR_GROUP_FIQ GENMASK(6, 0)
+#define INTC_INTR_GROUP_IRQ GENMASK(14, 8)
+
+struct sp7021_intc_data {
+ void __iomem *base0;
+ void __iomem *base1;
+ int ext0_irq;
+ int ext1_irq;
+ struct irq_chip chip;
+ struct irq_domain *domain;
+ raw_spinlock_t lock;
+};
+
+static void sp7021_intc_ext0_irq_handle(struct irq_desc *desc)
+{
+ struct sp7021_intc_data *s = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ u32 mask, masked;
+ int i, j;
+
+ chained_irq_enter(chip, desc);
+
+ mask = readl_relaxed(s->base1 + REG_INTC_INTR_GROUP);
+ mask = FIELD_GET(INTC_INTR_GROUP_IRQ, mask);
+ while (mask) {
+ i = fls(mask) - 1;
+ mask &= ~BIT(i);
+
+ masked = readl_relaxed(s->base1 + REG_INTC_MASKED_IRQS(i));
+ while (masked) {
+ j = fls(masked) - 1;
+ masked &= ~BIT(j);
+
+ generic_handle_irq(irq_find_mapping(s->domain,
+ i * 32 + j));
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static void sp7021_intc_ext1_irq_handle(struct irq_desc *desc)
+{
+ struct sp7021_intc_data *s = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ u32 mask, masked;
+ int i, j;
+
+ chained_irq_enter(chip, desc);
+
+ mask = readl_relaxed(s->base1 + REG_INTC_INTR_GROUP);
+ mask = FIELD_GET(INTC_INTR_GROUP_FIQ, mask);
+ while (mask) {
+ i = fls(mask) - 1;
+ mask &= ~BIT(i);
+
+ masked = readl_relaxed(s->base1 + REG_INTC_MASKED_FIQS(i));
+ while (masked) {
+ j = fls(masked) - 1;
+ masked &= ~BIT(j);
+
+ generic_handle_irq(irq_find_mapping(s->domain,
+ i * 32 + j));
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static void sp7021_intc_ack_irq(struct irq_data *data)
+{
+ struct sp7021_intc_data *s = irq_data_get_irq_chip_data(data);
+ unsigned int idx;
+ u32 mask;
+
+ idx = data->hwirq / 32;
+
+ mask = BIT(data->hwirq % 32);
+ writel_relaxed(mask, s->base1 + REG_INTC_INTR_CLR(idx));
+}
+
+static void sp7021_intc_mask_irq(struct irq_data *data)
+{
+ struct sp7021_intc_data *s = irq_data_get_irq_chip_data(data);
+ unsigned long flags;
+ unsigned int idx;
+ u32 mask;
+
+ idx = data->hwirq / 32;
+
+ raw_spin_lock_irqsave(&s->lock, flags);
+
+ mask = readl_relaxed(s->base0 + REG_INTC_INTR_MASK(idx));
+ mask &= ~BIT(data->hwirq % 32);
+ writel_relaxed(mask, s->base0 + REG_INTC_INTR_MASK(idx));
+
+ raw_spin_unlock_irqrestore(&s->lock, flags);
+}
+
+static void sp7021_intc_unmask_irq(struct irq_data *data)
+{
+ struct sp7021_intc_data *s = irq_data_get_irq_chip_data(data);
+ unsigned long flags;
+ unsigned int idx;
+ u32 mask;
+
+ idx = data->hwirq / 32;
+
+ raw_spin_lock_irqsave(&s->lock, flags);
+
+ mask = readl_relaxed(s->base0 + REG_INTC_INTR_MASK(idx));
+ mask |= BIT(data->hwirq % 32);
+ writel_relaxed(mask, s->base0 + REG_INTC_INTR_MASK(idx));
+
+ raw_spin_unlock_irqrestore(&s->lock, flags);
+}
+
+static int sp7021_intc_set_irq_type(struct irq_data *data, unsigned int flow_type)
+{
+ struct sp7021_intc_data *s = irq_data_get_irq_chip_data(data);
+ unsigned long flags;
+ unsigned int idx;
+ u32 mask, type, polarity;
+
+ idx = data->hwirq / 32;
+ mask = BIT(data->hwirq % 32);
+
+ if (flow_type & IRQ_TYPE_LEVEL_MASK)
+ irq_set_chip_handler_name_locked(data, &s->chip, handle_level_irq, NULL);
+ else
+ irq_set_chip_handler_name_locked(data, &s->chip, handle_edge_irq, NULL);
+
+ raw_spin_lock_irqsave(&s->lock, flags);
+
+ type = readl_relaxed(s->base0 + REG_INTC_INTR_TYPE(idx));
+ polarity = readl_relaxed(s->base0 + REG_INTC_INTR_POLARITY(idx));
+
+ switch (flow_type) {
+ case IRQ_TYPE_EDGE_RISING:
+ type |= mask;
+ polarity &= ~mask;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ type |= mask;
+ polarity |= mask;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ type &= ~mask;
+ polarity &= ~mask;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ type &= ~mask;
+ polarity |= mask;
+ break;
+ default:
+ raw_spin_unlock_irqrestore(&s->lock, flags);
+ return -EBADR;
+ }
+
+ writel_relaxed(type, s->base0 + REG_INTC_INTR_TYPE(idx));
+ writel_relaxed(polarity, s->base0 + REG_INTC_INTR_POLARITY(idx));
+
+ raw_spin_unlock_irqrestore(&s->lock, flags);
+
+ return IRQ_SET_MASK_OK;
+}
+
+static const struct irq_chip sp7021_intc_irq_chip = {
+ .name = "SP7021-A",
+ .irq_ack = sp7021_intc_ack_irq,
+ .irq_mask = sp7021_intc_mask_irq,
+ .irq_unmask = sp7021_intc_unmask_irq,
+ .irq_set_type = sp7021_intc_set_irq_type,
+};
+
+static int sp7021_intc_irq_domain_map(struct irq_domain *d,
+ unsigned int irq, irq_hw_number_t hw)
+{
+ struct sp7021_intc_data *s = d->host_data;
+ unsigned int idx;
+ u32 mask, type;
+
+ idx = hw / 32;
+ mask = BIT(hw % 32);
+
+ type = readl_relaxed(s->base0 + REG_INTC_INTR_TYPE(idx));
+
+ irq_set_chip_and_handler(irq, &s->chip, (type & mask) ? handle_edge_irq : handle_level_irq);
+ irq_set_chip_data(irq, s);
+ irq_set_probe(irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops sp7021_intc_domain_ops = {
+ .xlate = irq_domain_xlate_onecell,
+ .map = sp7021_intc_irq_domain_map,
+};
+
+int __init sp7021_intc_init(struct device_node *node, struct device_node *parent)
+{
+ struct sp7021_intc_data *s;
+ void __iomem *base0, *base1;
+ int i;
+
+ base0 = of_iomap(node, 0);
+ if (!base0)
+ return -EIO;
+
+ base1 = of_iomap(node, 1);
+ if (!base1)
+ return -EIO;
+
+ s = kzalloc(sizeof(*s), GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+
+ s->base0 = base0;
+ s->base1 = base1;
+ s->chip = sp7021_intc_irq_chip;
+
+ s->ext0_irq = irq_of_parse_and_map(node, 0);
+ if (s->ext0_irq <= 0) {
+ kfree(s);
+ return -EINVAL;
+ }
+
+ s->ext1_irq = irq_of_parse_and_map(node, 1);
+ if (s->ext1_irq <= 0) {
+ kfree(s);
+ return -EINVAL;
+ }
+
+ raw_spin_lock_init(&s->lock);
+
+ for (i = 0; i < 7; i++) {
+ writel_relaxed(0, s->base0 + REG_INTC_INTR_MASK(i));
+ writel_relaxed(~0, s->base0 + REG_INTC_INTR_TYPE(i));
+ writel_relaxed(0, s->base0 + REG_INTC_INTR_POLARITY(i));
+
+ /* irq, not fiq */
+ writel_relaxed(~0, s->base0 + REG_INTC_INTR_PRIO(i));
+
+ writel_relaxed(~0, s->base1 + REG_INTC_INTR_CLR(i));
+ }
+
+ s->domain = irq_domain_add_linear(node, 200, &sp7021_intc_domain_ops,
+ s);
+ if (!s->domain) {
+ kfree(s);
+ return -ENOMEM;
+ }
+
+ irq_set_chained_handler_and_data(s->ext0_irq, sp7021_intc_ext0_irq_handle, s);
+ irq_set_chained_handler_and_data(s->ext1_irq, sp7021_intc_ext1_irq_handle, s);
+
+ return 0;
+}
+IRQCHIP_DECLARE(sp7021_intc, "sunplus,sp7021-intc", sp7021_intc_init);
--
2.16.4
Prepare Device Trees for Sunplus Plus1 (aka Pentagram) SP7021's
CPU-Chip aka A-Chip (Cortex-A7) as well as the Banana Pi BPI-F2S SBC.
Cc: Wells Lu 呂芳騰 <[email protected]>
Signed-off-by: Andreas Färber <[email protected]>
---
arch/arm/boot/dts/Makefile | 2 +
arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts | 22 ++++++++
arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi | 75 ++++++++++++++++++++++++++
arch/arm/boot/dts/pentagram-sp7021.dtsi | 61 +++++++++++++++++++++
4 files changed, 160 insertions(+)
create mode 100644 arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts
create mode 100644 arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi
create mode 100644 arch/arm/boot/dts/pentagram-sp7021.dtsi
diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
index 566c6d1cfc46..b514ead139f3 100644
--- a/arch/arm/boot/dts/Makefile
+++ b/arch/arm/boot/dts/Makefile
@@ -846,6 +846,8 @@ dtb-$(CONFIG_ARCH_ACTIONS) += \
owl-s500-cubieboard6.dtb \
owl-s500-guitar-bb-rev-b.dtb \
owl-s500-sparky.dtb
+dtb-$(CONFIG_ARCH_SUNPLUS) += \
+ pentagram-sp7021-bpi-f2s.dtb
dtb-$(CONFIG_ARCH_PRIMA2) += \
prima2-evb.dtb
dtb-$(CONFIG_ARCH_PXA) += \
diff --git a/arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts b/arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts
new file mode 100644
index 000000000000..3c25b6e79fe2
--- /dev/null
+++ b/arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause
+
+/dts-v1/;
+
+#include "pentagram-sp7021-cpu.dtsi"
+
+/ {
+ compatible = "bananapi,bpi-f2s", "sunplus,sp7021";
+ model = "Banana Pi BPI-F2S";
+
+ aliases {
+ serial0 = &uart0;
+ };
+
+ chosen {
+ stdout-path = "serial0:115200n8";
+ };
+};
+
+&uart0 {
+ status = "okay";
+};
diff --git a/arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi b/arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi
new file mode 100644
index 000000000000..ae58bf5ffadf
--- /dev/null
+++ b/arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause
+/*
+ * SunPlus Plus1 (Pentagram) SP7021 CPU-Chip a.k.a. A-Chip (CA7)
+ *
+ * Copyright (c) 2020 Andreas Färber
+ */
+
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+#include "pentagram-sp7021.dtsi"
+
+/ {
+ interrupt-parent = <&gic>;
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cpu0: cpu@0 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a7";
+ reg = <0>;
+ };
+
+ cpu1: cpu@1{
+ device_type = "cpu";
+ compatible = "arm,cortex-a7";
+ reg = <1>;
+ };
+
+ cpu2: cpu@2 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a7";
+ reg = <2>;
+ };
+
+ cpu3: cpu@3 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a7";
+ reg = <3>;
+ };
+ };
+
+ arm-pmu {
+ compatible = "arm,cortex-a7-pmu";
+ interrupts = <GIC_SPI 219 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 220 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 221 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 222 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-affinity = <&cpu0>, <&cpu1>, <&cpu2>, <&cpu3>;
+ };
+
+ timer: timer {
+ compatible = "arm,armv7-timer";
+ interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
+ <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
+ <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
+ <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
+ clock-frequency = <27000000>;
+ arm,cpu-registers-not-fw-configured;
+ };
+
+ soc {
+ gic: interrupt-controller@9f101000 {
+ compatible = "arm,cortex-a7-gic";
+ reg = <0x9f101000 0x1000>,
+ <0x9f102000 0x2000>,
+ <0x9f104000 0x2000>,
+ <0x9f106000 0x2000>;
+ interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
+ interrupt-controller;
+ #interrupt-cells = <3>;
+ };
+ };
+};
diff --git a/arch/arm/boot/dts/pentagram-sp7021.dtsi b/arch/arm/boot/dts/pentagram-sp7021.dtsi
new file mode 100644
index 000000000000..7dd44901cf4a
--- /dev/null
+++ b/arch/arm/boot/dts/pentagram-sp7021.dtsi
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause
+/*
+ * Copyright (c) 2020 Andreas Färber
+ */
+
+/ {
+ compatible = "sunplus,sp7021";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ soc {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0x20000000 0x20000000 0xe0000000>;
+
+ rgst: bus@9c000000 {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0x0 0x9c000000 0x00100000>;
+
+ uart2: serial@800 {
+ compatible = "sunplus,sp7021-uart";
+ reg = <0x800 0x80>;
+ status = "disabled";
+ };
+
+ uart3: serial@880 {
+ compatible = "sunplus,sp7021-uart";
+ reg = <0x880 0x80>;
+ status = "disabled";
+ };
+
+ uart0: serial@900 {
+ compatible = "sunplus,sp7021-uart";
+ reg = <0x900 0x80>;
+ status = "disabled";
+ };
+
+ uart1: serial@980 {
+ compatible = "sunplus,sp7021-uart";
+ reg = <0x980 0x80>;
+ status = "disabled";
+ };
+
+ uart4: serial@8780 {
+ compatible = "sunplus,sp7021-uart";
+ reg = <0x8780 0x80>;
+ status = "disabled";
+ };
+ };
+
+ amba: bus@9c100000 {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0x0 0x9c100000 0x01ef0000>;
+ };
+ };
+};
--
2.16.4
Signed-off-by: Andreas Färber <[email protected]>
---
arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi b/arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi
index ae58bf5ffadf..7e424baa9214 100644
--- a/arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi
+++ b/arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi
@@ -73,3 +73,16 @@
};
};
};
+
+&rgst {
+ intc: interrupt-controller@780 {
+ compatible = "sunplus,sp7021-intc";
+ reg = <0x780 0x80>, /* G15 */
+ <0xa80 0x80>; /* G21 */
+ interrupt-parent = <&gic>;
+ interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+};
--
2.16.4
Signed-off-by: Andreas Färber <[email protected]>
---
drivers/tty/serial/Kconfig | 4 +-
drivers/tty/serial/sunplus-uart.c | 680 ++++++++++++++++++++++++++++++++++++++
include/uapi/linux/serial_core.h | 3 +
3 files changed, 685 insertions(+), 2 deletions(-)
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index ac6dce75c2f3..a178500a6bb5 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1563,7 +1563,7 @@ config SERIAL_MILBEAUT_USIO_CONSOLE
single user mode).
config SERIAL_PLUS1
- bool "Sunplus Plus1 serial port support"
+ tristate "Sunplus Plus1 serial port support"
depends on ARCH_SUNPLUS || COMPILE_TEST
select SERIAL_CORE
help
@@ -1579,7 +1579,7 @@ config SERIAL_PLUS1_CONSOLE
default y
help
Say 'Y' here if you wish to use Sunplus SP7021 UART
- as the system console. Only earlycon is implemented currently.
+ as the system console.
endmenu
diff --git a/drivers/tty/serial/sunplus-uart.c b/drivers/tty/serial/sunplus-uart.c
index 959dfab5e07b..9b9442acde9a 100644
--- a/drivers/tty/serial/sunplus-uart.c
+++ b/drivers/tty/serial/sunplus-uart.c
@@ -4,6 +4,7 @@
*/
#include <linux/bitops.h>
+#include <linux/clk.h>
#include <linux/console.h>
#include <linux/io.h>
#include <linux/module.h>
@@ -11,11 +12,71 @@
#include <linux/platform_device.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+
+#define PLUS1_UART_PORT_NUM 5
+#define PLUS1_UART_DEV_NAME "ttySP"
#define REG_DATA 0x00
#define REG_LSR 0x04
+#define REG_MSR 0x08
+#define REG_LCR 0x0c
+#define REG_MCR 0x10
+#define REG_DIV_L 0x14
+#define REG_DIV_H 0x18
+#define REG_ISC 0x1c
+#define REG_TX_RESIDUE 0x20
+#define REG_RX_RESIDUE 0x24
+#define REG_RX_THRESHOLD 0x28
+#define REG_CLK_SRC 0x2c
#define PLUS1_UART_LSR_TX BIT(0)
+#define PLUS1_UART_LSR_RX BIT(1)
+#define PLUS1_UART_LSR_PE BIT(2)
+#define PLUS1_UART_LSR_OE BIT(3)
+#define PLUS1_UART_LSR_FE BIT(4)
+#define PLUS1_UART_LSR_BC BIT(5)
+#define PLUS1_UART_LSR_TXE BIT(6)
+
+#define PLUS1_UART_LCR_WL_MASK GENMASK(1, 0)
+#define PLUS1_UART_LCR_WL5 (0x0 << 0)
+#define PLUS1_UART_LCR_WL6 (0x1 << 0)
+#define PLUS1_UART_LCR_WL7 (0x2 << 0)
+#define PLUS1_UART_LCR_WL8 (0x3 << 0)
+#define PLUS1_UART_LCR_ST BIT(2)
+#define PLUS1_UART_LCR_PE BIT(3)
+#define PLUS1_UART_LCR_PR BIT(4)
+#define PLUS1_UART_LCR_BC BIT(5)
+
+#define PLUS1_UART_MCR_DTS BIT(0)
+#define PLUS1_UART_MCR_RTS BIT(1)
+#define PLUS1_UART_MCR_DCD BIT(2)
+#define PLUS1_UART_MCR_RI BIT(3)
+#define PLUS1_UART_MCR_LB BIT(4)
+#define PLUS1_UART_MCR_AR BIT(5)
+#define PLUS1_UART_MCR_AC BIT(6)
+#define PLUS1_UART_MCR_AT BIT(7)
+
+#define PLUS1_UART_ISC_TX BIT(0)
+#define PLUS1_UART_ISC_RX BIT(1)
+#define PLUS1_UART_ISC_LS BIT(2)
+#define PLUS1_UART_ISC_MS BIT(3)
+#define PLUS1_UART_ISC_TXM BIT(4)
+#define PLUS1_UART_ISC_RXM BIT(5)
+#define PLUS1_UART_ISC_LSM BIT(6)
+#define PLUS1_UART_ISC_MSM BIT(7)
+
+static struct uart_driver plus1_uart_driver;
+
+struct plus1_uart_port {
+ struct uart_port port;
+ struct clk *clk;
+};
+
+#define to_plus1_uart_port(prt) container_of(prt, struct plus1_uart_port, prt)
+
+static struct plus1_uart_port *plus1_uart_ports[PLUS1_UART_PORT_NUM];
static inline void plus1_uart_write(struct uart_port *port, unsigned int off, u32 val)
{
@@ -27,6 +88,447 @@ static inline u32 plus1_uart_read(struct uart_port *port, unsigned int off)
return readl_relaxed(port->membase + off);
}
+static void plus1_uart_break_ctl(struct uart_port *port, int ctl)
+{
+ unsigned long flags;
+ u32 lcr;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ lcr = plus1_uart_read(port, REG_LCR);
+ if (ctl)
+ lcr |= PLUS1_UART_LCR_BC;
+ else
+ lcr &= ~PLUS1_UART_LCR_BC;
+ plus1_uart_write(port, REG_LCR, lcr);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static void plus1_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+ u32 mcr;
+
+ mcr = plus1_uart_read(port, REG_MCR);
+
+ if (mctrl & TIOCM_DTR)
+ mcr |= PLUS1_UART_MCR_DTS;
+ else
+ mcr &= ~PLUS1_UART_MCR_DTS;
+
+ if (mctrl & TIOCM_RTS)
+ mcr |= PLUS1_UART_MCR_RTS;
+ else
+ mcr &= ~PLUS1_UART_MCR_RTS;
+
+ if (mctrl & TIOCM_CAR)
+ mcr |= PLUS1_UART_MCR_DCD;
+ else
+ mcr &= ~PLUS1_UART_MCR_DCD;
+
+ if (mctrl & TIOCM_RI)
+ mcr |= PLUS1_UART_MCR_RI;
+ else
+ mcr &= ~PLUS1_UART_MCR_RI;
+
+ if (mctrl & TIOCM_LOOP)
+ mcr |= PLUS1_UART_MCR_LB;
+ else
+ mcr &= ~PLUS1_UART_MCR_LB;
+
+ plus1_uart_write(port, REG_MCR, mcr);
+}
+
+static unsigned int plus1_uart_get_mctrl(struct uart_port *port)
+{
+ unsigned int mctrl = 0;
+ u32 mcr;
+
+ mcr = plus1_uart_read(port, REG_MCR);
+ if (mcr & PLUS1_UART_MCR_DTS)
+ mctrl |= TIOCM_DTR;
+ if (mcr & PLUS1_UART_MCR_RTS)
+ mctrl |= TIOCM_RTS;
+ if (mcr & PLUS1_UART_MCR_DCD)
+ mctrl |= TIOCM_CAR;
+ if (mcr & PLUS1_UART_MCR_RI)
+ mctrl |= TIOCM_RI;
+ if (mcr & PLUS1_UART_MCR_LB)
+ mctrl |= TIOCM_LOOP;
+ if (mcr & PLUS1_UART_MCR_AC)
+ mctrl |= TIOCM_CTS;
+ return mctrl;
+}
+
+static unsigned int plus1_uart_tx_empty(struct uart_port *port)
+{
+ unsigned long flags;
+ unsigned int ret;
+ u32 lsr;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ lsr = plus1_uart_read(port, REG_LSR);
+ ret = (lsr & PLUS1_UART_LSR_TXE) ? TIOCSER_TEMT : 0;
+
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ return ret;
+}
+
+static void plus1_uart_stop_rx(struct uart_port *port)
+{
+ u32 isc;
+
+ isc = plus1_uart_read(port, REG_ISC);
+ isc &= ~PLUS1_UART_ISC_RXM;
+ plus1_uart_write(port, REG_ISC, isc);
+}
+
+static void plus1_uart_stop_tx(struct uart_port *port)
+{
+ u32 isc;
+
+ isc = plus1_uart_read(port, REG_ISC);
+ isc &= ~PLUS1_UART_ISC_TXM;
+ plus1_uart_write(port, REG_ISC, isc);
+}
+
+static void plus1_uart_start_tx(struct uart_port *port)
+{
+ u32 isc;
+
+ if (uart_tx_stopped(port)) {
+ plus1_uart_stop_tx(port);
+ return;
+ }
+
+ isc = plus1_uart_read(port, REG_ISC);
+ isc |= PLUS1_UART_ISC_TXM;
+ plus1_uart_write(port, REG_ISC, isc);
+}
+
+static void plus1_uart_send_chars(struct uart_port *port)
+{
+ struct circ_buf *xmit = &port->state->xmit;
+ unsigned int ch;
+
+ if (uart_tx_stopped(port))
+ return;
+
+ if (port->x_char) {
+ //while (!(plus1_uart_read(port, REG_LSR) & PLUS1_UART_LSR_TX))
+ // cpu_relax();
+ plus1_uart_write(port, REG_DATA, port->x_char);
+ port->icount.tx++;
+ port->x_char = 0;
+ }
+
+ while (plus1_uart_read(port, REG_LSR) & PLUS1_UART_LSR_TX) {
+ if (uart_circ_empty(xmit))
+ break;
+
+ ch = xmit->buf[xmit->tail];
+ plus1_uart_write(port, REG_DATA, ch);
+ xmit->tail = (xmit->tail + 1) & (SERIAL_XMIT_SIZE - 1);
+ port->icount.tx++;
+ }
+
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(port);
+
+ if (uart_circ_empty(xmit))
+ plus1_uart_stop_tx(port);
+}
+
+static void plus1_uart_receive_chars(struct uart_port *port)
+{
+ u32 lsr, val;
+
+ lsr = plus1_uart_read(port, REG_LSR);
+ while (lsr & PLUS1_UART_LSR_RX) {
+ char flag = TTY_NORMAL;
+
+ if (lsr & PLUS1_UART_LSR_OE)
+ port->icount.overrun++;
+
+ if (lsr & PLUS1_UART_LSR_BC) {
+ port->icount.brk++;
+ flag = TTY_BREAK;
+ } else if (lsr & PLUS1_UART_LSR_PE) {
+ port->icount.parity++;
+ flag = TTY_PARITY;
+ } else if (lsr & PLUS1_UART_LSR_FE) {
+ port->icount.frame++;
+
+ lsr &= port->read_status_mask;
+ if (lsr & PLUS1_UART_LSR_FE)
+ flag = TTY_PARITY;
+ } else
+ port->icount.rx++;
+
+ val = plus1_uart_read(port, REG_DATA);
+ val &= 0xff;
+
+ if ((lsr & port->ignore_status_mask) == 0)
+ tty_insert_flip_char(&port->state->port, val, flag);
+
+ lsr = plus1_uart_read(port, REG_LSR);
+ }
+
+ spin_unlock(&port->lock);
+ tty_flip_buffer_push(&port->state->port);
+ spin_lock(&port->lock);
+}
+
+static irqreturn_t plus1_uart_irq(int irq, void *dev_id)
+{
+ struct uart_port *port = dev_id;
+ unsigned long flags;
+ u32 isc;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ isc = plus1_uart_read(port, REG_ISC);
+ if (isc & PLUS1_UART_ISC_RX)
+ plus1_uart_receive_chars(port);
+
+ isc = plus1_uart_read(port, REG_ISC);
+ if (isc & PLUS1_UART_ISC_TX)
+ plus1_uart_send_chars(port);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static void plus1_uart_shutdown(struct uart_port *port)
+{
+ unsigned long flags;
+ u32 mcr;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ plus1_uart_write(port, REG_ISC, 0);
+
+ mcr = plus1_uart_read(port, REG_MCR);
+ mcr &= ~PLUS1_UART_MCR_AC;
+ plus1_uart_write(port, REG_MCR, mcr);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ free_irq(port->irq, port);
+}
+
+static int plus1_uart_startup(struct uart_port *port)
+{
+ unsigned long flags;
+ int ret;
+
+ ret = request_irq(port->irq, plus1_uart_irq, IRQF_TRIGGER_NONE,
+ "plus1-uart", port);
+ if (ret)
+ return ret;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ plus1_uart_write(port, REG_ISC, PLUS1_UART_ISC_RXM);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ return 0;
+}
+
+static void plus1_uart_change_baudrate(struct plus1_uart_port *sp_port,
+ unsigned long baud)
+{
+ struct uart_port *port = &sp_port->port;
+ unsigned int clk;
+ unsigned int div, ext;
+
+ plus1_uart_write(port, REG_CLK_SRC, (baud > 115200) ? 0 : BIT(0));
+ if (baud > 115200)
+ clk = 202500000;
+ else
+ clk = port->uartclk;
+ clk += baud >> 1;
+ div = clk / baud;
+ ext = div & 0xf;
+ div >>= 4;
+ div--;
+ plus1_uart_write(port, REG_DIV_L, (ext << 12) | (div & 0xff));
+ plus1_uart_write(port, REG_DIV_H, div >> 8);
+}
+
+static void plus1_uart_set_termios(struct uart_port *port,
+ struct ktermios *termios,
+ struct ktermios *old)
+{
+ struct plus1_uart_port *sp_port = to_plus1_uart_port(port);
+ unsigned int baud;
+ u32 lcr, mcr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ lcr = plus1_uart_read(port, REG_LCR);
+
+ lcr &= ~PLUS1_UART_LCR_WL_MASK;
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ lcr |= PLUS1_UART_LCR_WL5;
+ break;
+ case CS6:
+ lcr |= PLUS1_UART_LCR_WL6;
+ break;
+ case CS7:
+ lcr |= PLUS1_UART_LCR_WL7;
+ break;
+ case CS8:
+ default:
+ lcr |= PLUS1_UART_LCR_WL8;
+ break;
+ }
+
+ if (termios->c_cflag & CSTOPB)
+ lcr |= PLUS1_UART_LCR_ST;
+ else
+ lcr &= ~PLUS1_UART_LCR_ST;
+
+ if (termios->c_cflag & PARENB) {
+ lcr |= PLUS1_UART_LCR_PE;
+ } else
+ lcr &= ~PLUS1_UART_LCR_PE;
+
+ if ((termios->c_cflag & PARENB) && !(termios->c_cflag & PARODD))
+ lcr |= PLUS1_UART_LCR_PR;
+ else
+ lcr &= ~PLUS1_UART_LCR_PR;
+
+ plus1_uart_write(port, REG_LCR, lcr);
+
+ mcr = plus1_uart_read(port, REG_MCR);
+
+ if (termios->c_cflag & CRTSCTS)
+ mcr |= PLUS1_UART_MCR_AC | PLUS1_UART_MCR_AR;
+ else
+ mcr &= ~(PLUS1_UART_MCR_AC | PLUS1_UART_MCR_AR);
+
+ plus1_uart_write(port, REG_MCR, mcr);
+
+ baud = uart_get_baud_rate(port, termios, old, 0, 202500000 >> 4);
+ plus1_uart_change_baudrate(sp_port, baud);
+
+ /* Don't rewrite B0 */
+ if (tty_termios_baud_rate(termios))
+ tty_termios_encode_baud_rate(termios, baud, baud);
+
+ port->read_status_mask = 0;
+ if (termios->c_iflag & INPCK)
+ port->read_status_mask |= PLUS1_UART_LSR_PE | PLUS1_UART_LSR_FE;
+ if ((termios->c_iflag & BRKINT) || (termios->c_iflag & PARMRK))
+ port->read_status_mask |= PLUS1_UART_LSR_BC;
+
+ port->ignore_status_mask = 0;
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= PLUS1_UART_LSR_PE | PLUS1_UART_LSR_FE;
+ if (termios->c_iflag & IGNBRK) {
+ port->ignore_status_mask |= PLUS1_UART_LSR_BC;
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= PLUS1_UART_LSR_OE;
+ }
+
+ if (!(termios->c_cflag & CREAD))
+ plus1_uart_write(port, REG_RX_RESIDUE, 0);
+
+ uart_update_timeout(port, termios->c_cflag, baud);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static void plus1_uart_release_port(struct uart_port *port)
+{
+ struct platform_device *pdev = to_platform_device(port->dev);
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return;
+
+ if (port->flags & UPF_IOREMAP) {
+ devm_release_mem_region(port->dev, port->mapbase,
+ resource_size(res));
+ devm_iounmap(port->dev, port->membase);
+ port->membase = NULL;
+ }
+}
+
+static int plus1_uart_request_port(struct uart_port *port)
+{
+ struct platform_device *pdev = to_platform_device(port->dev);
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENXIO;
+
+ if (!devm_request_mem_region(port->dev, port->mapbase,
+ resource_size(res), dev_name(port->dev)))
+ return -EBUSY;
+
+ if (port->flags & UPF_IOREMAP) {
+ port->membase = devm_ioremap(port->dev, port->mapbase,
+ resource_size(res));
+ if (!port->membase)
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static const char *plus1_uart_type(struct uart_port *port)
+{
+ return (port->type == PORT_PLUS1) ? "plus1-uart" : NULL;
+}
+
+static int plus1_uart_verify_port(struct uart_port *port,
+ struct serial_struct *ser)
+{
+ if (port->type != PORT_PLUS1)
+ return -EINVAL;
+
+ if (port->irq != ser->irq)
+ return -EINVAL;
+
+ return 0;
+}
+
+static void plus1_uart_config_port(struct uart_port *port, int flags)
+{
+ if (flags & UART_CONFIG_TYPE) {
+ port->type = PORT_PLUS1;
+ plus1_uart_request_port(port);
+ }
+}
+
+static const struct uart_ops plus1_uart_ops = {
+ .break_ctl = plus1_uart_break_ctl,
+ .set_mctrl = plus1_uart_set_mctrl,
+ .get_mctrl = plus1_uart_get_mctrl,
+ .tx_empty = plus1_uart_tx_empty,
+ .start_tx = plus1_uart_start_tx,
+ .stop_rx = plus1_uart_stop_rx,
+ .stop_tx = plus1_uart_stop_tx,
+ .startup = plus1_uart_startup,
+ .shutdown = plus1_uart_shutdown,
+ .set_termios = plus1_uart_set_termios,
+ .type = plus1_uart_type,
+ .config_port = plus1_uart_config_port,
+ .request_port = plus1_uart_request_port,
+ .release_port = plus1_uart_release_port,
+ .verify_port = plus1_uart_verify_port,
+};
+
#ifdef CONFIG_SERIAL_PLUS1_CONSOLE
static void plus1_console_putchar(struct uart_port *port, int ch)
@@ -65,6 +567,57 @@ static void plus1_uart_port_write(struct uart_port *port, const char *s,
local_irq_restore(flags);
}
+static void plus1_uart_console_write(struct console *co, const char *s,
+ u_int count)
+{
+ struct plus1_uart_port *sp_port;
+
+ sp_port = plus1_uart_ports[co->index];
+ if (!sp_port)
+ return;
+
+ plus1_uart_port_write(&sp_port->port, s, count);
+}
+
+static int plus1_uart_console_setup(struct console *co, char *options)
+{
+ struct plus1_uart_port *sp_port;
+ int baud = 115200;
+ int bits = 8;
+ int parity = 'n';
+ int flow = 'n';
+
+ if (co->index < 0 || co->index >= PLUS1_UART_PORT_NUM)
+ return -EINVAL;
+
+ sp_port = plus1_uart_ports[co->index];
+ if (!sp_port || !sp_port->port.membase)
+ return -ENODEV;
+
+ if (options)
+ uart_parse_options(options, &baud, &parity, &bits, &flow);
+
+ return uart_set_options(&sp_port->port, co, baud, parity, bits, flow);
+}
+
+static struct console plus1_uart_console = {
+ .name = PLUS1_UART_DEV_NAME,
+ .write = plus1_uart_console_write,
+ .device = uart_console_device,
+ .setup = plus1_uart_console_setup,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+ .data = &plus1_uart_driver,
+};
+
+static int __init plus1_uart_console_init(void)
+{
+ register_console(&plus1_uart_console);
+
+ return 0;
+}
+console_initcall(plus1_uart_console_init);
+
static void plus1_uart_early_console_write(struct console *co,
const char *s,
u_int count)
@@ -87,4 +640,131 @@ plus1_uart_early_console_setup(struct earlycon_device *device, const char *opt)
OF_EARLYCON_DECLARE(sunplus, "sunplus,sp7021-uart",
plus1_uart_early_console_setup);
+#define PLUS1_UART_CONSOLE (&plus1_uart_console)
+#else
+#define PLUS1_UART_CONSOLE NULL
#endif
+
+static struct uart_driver plus1_uart_driver = {
+ .owner = THIS_MODULE,
+ .driver_name = "plus1-uart",
+ .dev_name = PLUS1_UART_DEV_NAME,
+ .nr = PLUS1_UART_PORT_NUM,
+ .cons = PLUS1_UART_CONSOLE,
+};
+
+static int plus1_uart_probe(struct platform_device *pdev)
+{
+ struct resource *res_mem;
+ struct plus1_uart_port *sp_port;
+ int ret, irq;
+
+ if (pdev->dev.of_node)
+ pdev->id = of_alias_get_id(pdev->dev.of_node, "serial");
+
+ if (pdev->id < 0 || pdev->id >= PLUS1_UART_PORT_NUM) {
+ dev_err(&pdev->dev, "id %d out of range\n", pdev->id);
+ return -EINVAL;
+ }
+
+ res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res_mem) {
+ dev_err(&pdev->dev, "could not get mem\n");
+ return -ENODEV;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ if (plus1_uart_ports[pdev->id]) {
+ dev_err(&pdev->dev, "port %d already allocated\n", pdev->id);
+ return -EBUSY;
+ }
+
+ sp_port = devm_kzalloc(&pdev->dev, sizeof(*sp_port), GFP_KERNEL);
+ if (!sp_port)
+ return -ENOMEM;
+
+ sp_port->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(sp_port->clk)) {
+ dev_err(&pdev->dev, "could not get clk\n");
+ return PTR_ERR(sp_port->clk);
+ }
+
+ sp_port->port.dev = &pdev->dev;
+ sp_port->port.line = pdev->id;
+ sp_port->port.type = PORT_PLUS1;
+ sp_port->port.iotype = UPIO_MEM;
+ sp_port->port.mapbase = res_mem->start;
+ sp_port->port.irq = irq;
+ sp_port->port.uartclk = clk_get_rate(sp_port->clk);
+ if (sp_port->port.uartclk == 0) {
+ dev_err(&pdev->dev, "clock rate is zero\n");
+ return -EINVAL;
+ }
+ sp_port->port.flags = UPF_BOOT_AUTOCONF | UPF_IOREMAP | UPF_LOW_LATENCY;
+ sp_port->port.x_char = 0;
+ sp_port->port.fifosize = 16;
+ sp_port->port.ops = &plus1_uart_ops;
+
+ plus1_uart_ports[pdev->id] = sp_port;
+ platform_set_drvdata(pdev, sp_port);
+
+ ret = uart_add_one_port(&plus1_uart_driver, &sp_port->port);
+ if (ret)
+ plus1_uart_ports[pdev->id] = NULL;
+
+ return ret;
+}
+
+static int plus1_uart_remove(struct platform_device *pdev)
+{
+ struct plus1_uart_port *sp_port = platform_get_drvdata(pdev);
+
+ uart_remove_one_port(&plus1_uart_driver, &sp_port->port);
+ plus1_uart_ports[pdev->id] = NULL;
+
+ return 0;
+}
+
+static const struct of_device_id plus1_uart_dt_matches[] = {
+ { .compatible = "sunplus,sp7021-uart" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, plus1_uart_dt_matches);
+
+static struct platform_driver plus1_uart_platform_driver = {
+ .probe = plus1_uart_probe,
+ .remove = plus1_uart_remove,
+ .driver = {
+ .name = "plus1-uart",
+ .of_match_table = plus1_uart_dt_matches,
+ },
+};
+
+static int __init plus1_uart_init(void)
+{
+ int ret;
+
+ ret = uart_register_driver(&plus1_uart_driver);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&plus1_uart_platform_driver);
+ if (ret)
+ uart_unregister_driver(&plus1_uart_driver);
+
+ return ret;
+}
+
+static void __exit plus1_uart_exit(void)
+{
+ platform_driver_unregister(&plus1_uart_platform_driver);
+ uart_unregister_driver(&plus1_uart_driver);
+}
+
+module_init(plus1_uart_init);
+module_exit(plus1_uart_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 8ec3dd742ea4..54c611b06735 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -293,4 +293,7 @@
/* Freescale LINFlexD UART */
#define PORT_LINFLEXUART 122
+/* SunPlus Plus1 UART */
+#define PORT_PLUS1 123
+
#endif /* _UAPILINUX_SERIAL_CORE_H */
--
2.16.4
The Sunplus Plus1 (aka Pentagram) SP7021 SoC has five UARTs.
Cc: Wells Lu 呂芳騰 <[email protected]>
Signed-off-by: Andreas Färber <[email protected]>
---
.../bindings/serial/sunplus,pentagram-uart.yaml | 24 ++++++++++++++++++++++
1 file changed, 24 insertions(+)
create mode 100644 Documentation/devicetree/bindings/serial/sunplus,pentagram-uart.yaml
diff --git a/Documentation/devicetree/bindings/serial/sunplus,pentagram-uart.yaml b/Documentation/devicetree/bindings/serial/sunplus,pentagram-uart.yaml
new file mode 100644
index 000000000000..9d1641232a4c
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/sunplus,pentagram-uart.yaml
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serial/sunplus,pentagram-uart.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus Pentagram SoC UART Serial Interface
+
+maintainers:
+ - Andreas Färber <[email protected]>
+
+properties:
+ compatible:
+ const: sunplus,sp7021-uart
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+...
--
2.16.4
Sunplus Technology Co., Ltd. is a Taiwanese IC vendor.
Assign vendor prefix "sunplus".
Cc: Wells Lu 呂芳騰 <[email protected]>
Signed-off-by: Andreas Färber <[email protected]>
---
Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 6e09fc7362d5..2c764b0941b4 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -955,6 +955,8 @@ patternProperties:
description: Summit microelectronics
"^sunchip,.*":
description: Shenzhen Sunchip Technology Co., Ltd
+ "^sunplus,.*":
+ description: Sunplus Technology Co., Ltd.
"^SUNW,.*":
description: Sun Microsystems, Inc
"^swir,.*":
--
2.16.4
Cc: Wells Lu 呂芳騰 <[email protected]>
Signed-off-by: Andreas Färber <[email protected]>
---
Documentation/devicetree/bindings/arm/sunplus.yaml | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/sunplus.yaml
diff --git a/Documentation/devicetree/bindings/arm/sunplus.yaml b/Documentation/devicetree/bindings/arm/sunplus.yaml
new file mode 100644
index 000000000000..c88856a00983
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/sunplus.yaml
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/sunplus.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus platforms device tree bindings
+
+maintainers:
+ - Andreas Färber <[email protected]>
+
+properties:
+ $nodename:
+ const: '/'
+ compatible:
+ oneOf:
+ # SP7021 SoC based boards
+ - items:
+ - enum:
+ - bananapi,bpi-f2s # Banana Pi BPI-F2S
+ - const: sunplus,sp7021
+...
--
2.16.4
Prepare an earlycon driver for Sunplus Plus1 SP7021 SoC.
Based on BPI-F2S-bsp tree.
Signed-off-by: Andreas Färber <[email protected]>
---
drivers/tty/serial/Kconfig | 19 +++++++++
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/sunplus-uart.c | 90 +++++++++++++++++++++++++++++++++++++++
3 files changed, 110 insertions(+)
create mode 100644 drivers/tty/serial/sunplus-uart.c
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 880b96201530..ac6dce75c2f3 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1562,6 +1562,25 @@ config SERIAL_MILBEAUT_USIO_CONSOLE
receives all kernel messages and warnings and which allows logins in
single user mode).
+config SERIAL_PLUS1
+ bool "Sunplus Plus1 serial port support"
+ depends on ARCH_SUNPLUS || COMPILE_TEST
+ select SERIAL_CORE
+ help
+ This driver is for Sunplus Plus1 SP7021 SoC's UART.
+ Say 'Y' here if you wish to use the on-board serial port.
+ Otherwise, say 'N'.
+
+config SERIAL_PLUS1_CONSOLE
+ bool "Console on Sunplus Plus1 serial port"
+ depends on SERIAL_PLUS1=y
+ select SERIAL_CORE_CONSOLE
+ select SERIAL_EARLYCON
+ default y
+ help
+ Say 'Y' here if you wish to use Sunplus SP7021 UART
+ as the system console. Only earlycon is implemented currently.
+
endmenu
config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index d056ee6cca33..fed40fb19ed0 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -89,6 +89,7 @@ obj-$(CONFIG_SERIAL_OWL) += owl-uart.o
obj-$(CONFIG_SERIAL_RDA) += rda-uart.o
obj-$(CONFIG_SERIAL_MILBEAUT_USIO) += milbeaut_usio.o
obj-$(CONFIG_SERIAL_SIFIVE) += sifive.o
+obj-$(CONFIG_SERIAL_PLUS1) += sunplus-uart.o
# GPIOLIB helpers for modem control lines
obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
diff --git a/drivers/tty/serial/sunplus-uart.c b/drivers/tty/serial/sunplus-uart.c
new file mode 100644
index 000000000000..959dfab5e07b
--- /dev/null
+++ b/drivers/tty/serial/sunplus-uart.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2020 Andreas Färber
+ */
+
+#include <linux/bitops.h>
+#include <linux/console.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/serial.h>
+#include <linux/serial_core.h>
+
+#define REG_DATA 0x00
+#define REG_LSR 0x04
+
+#define PLUS1_UART_LSR_TX BIT(0)
+
+static inline void plus1_uart_write(struct uart_port *port, unsigned int off, u32 val)
+{
+ writel_relaxed(val, port->membase + off);
+}
+
+static inline u32 plus1_uart_read(struct uart_port *port, unsigned int off)
+{
+ return readl_relaxed(port->membase + off);
+}
+
+#ifdef CONFIG_SERIAL_PLUS1_CONSOLE
+
+static void plus1_console_putchar(struct uart_port *port, int ch)
+{
+ if (!port->membase)
+ return;
+
+ while (!(plus1_uart_read(port, REG_LSR) & PLUS1_UART_LSR_TX))
+ cpu_relax();
+
+ plus1_uart_write(port, REG_DATA, ch);
+}
+
+static void plus1_uart_port_write(struct uart_port *port, const char *s,
+ u_int count)
+{
+ unsigned long flags;
+ int locked;
+
+ local_irq_save(flags);
+
+ if (port->sysrq)
+ locked = 0;
+ else if (oops_in_progress)
+ locked = spin_trylock(&port->lock);
+ else {
+ spin_lock(&port->lock);
+ locked = 1;
+ }
+
+ uart_console_write(port, s, count, plus1_console_putchar);
+
+ if (locked)
+ spin_unlock(&port->lock);
+
+ local_irq_restore(flags);
+}
+
+static void plus1_uart_early_console_write(struct console *co,
+ const char *s,
+ u_int count)
+{
+ struct earlycon_device *dev = co->data;
+
+ plus1_uart_port_write(&dev->port, s, count);
+}
+
+static int __init
+plus1_uart_early_console_setup(struct earlycon_device *device, const char *opt)
+{
+ if (!device->port.membase)
+ return -ENODEV;
+
+ device->con->write = plus1_uart_early_console_write;
+
+ return 0;
+}
+OF_EARLYCON_DECLARE(sunplus, "sunplus,sp7021-uart",
+ plus1_uart_early_console_setup);
+
+#endif
--
2.16.4
Signed-off-by: Andreas Färber <[email protected]>
---
arch/arm/Kconfig | 2 ++
arch/arm/Makefile | 1 +
arch/arm/mach-sunplus/Kconfig | 10 ++++++++++
3 files changed, 13 insertions(+)
create mode 100644 arch/arm/mach-sunplus/Kconfig
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 1dcc64bd3621..f946140952b6 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -722,6 +722,8 @@ source "arch/arm/mach-sti/Kconfig"
source "arch/arm/mach-stm32/Kconfig"
+source "arch/arm/mach-sunplus/Kconfig"
+
source "arch/arm/mach-sunxi/Kconfig"
source "arch/arm/mach-tango/Kconfig"
diff --git a/arch/arm/Makefile b/arch/arm/Makefile
index 9397fe766b31..f050eca6b3b6 100644
--- a/arch/arm/Makefile
+++ b/arch/arm/Makefile
@@ -158,6 +158,7 @@ textofs-$(CONFIG_ARCH_MSM8X60) := 0x00208000
textofs-$(CONFIG_ARCH_MSM8960) := 0x00208000
textofs-$(CONFIG_ARCH_MESON) := 0x00208000
textofs-$(CONFIG_ARCH_AXXIA) := 0x00308000
+textofs-$(CONFIG_ARCH_SUNPLUS) := 0x00308000
# Machine directory name. This list is sorted alphanumerically
# by CONFIG_* macro name.
diff --git a/arch/arm/mach-sunplus/Kconfig b/arch/arm/mach-sunplus/Kconfig
new file mode 100644
index 000000000000..c20b338e50c8
--- /dev/null
+++ b/arch/arm/mach-sunplus/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+menuconfig ARCH_SUNPLUS
+ bool "Sunplus SoCs"
+ depends on ARCH_MULTI_V7
+ select ARM_GIC
+ select ARM_GLOBAL_TIMER
+ select CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK
+ select GENERIC_IRQ_CHIP
+ help
+ This enables support for the Sunplus Plus1 (SP7021) SoC family.
--
2.16.4
On Sun, 08 Mar 2020 16:32:27 +0000,
Andreas Färber <[email protected]> wrote:
>
> Signed-off-by: Andreas Färber <[email protected]>
Since you've now given me full permission to complain, can I trouble
you for a meaningful commit message. It's not like it is the first
time you write a patch...
> ---
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-sp7021.c | 285 +++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 286 insertions(+)
> create mode 100644 drivers/irqchip/irq-sp7021.c
>
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index eae0d78cbf22..a6b70d666739 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -105,3 +105,4 @@ obj-$(CONFIG_MADERA_IRQ) += irq-madera.o
> obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o
> obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o
> obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
> +obj-$(CONFIG_ARCH_SUNPLUS) += irq-sp7021.o
> diff --git a/drivers/irqchip/irq-sp7021.c b/drivers/irqchip/irq-sp7021.c
> new file mode 100644
> index 000000000000..a0b7972f2abb
> --- /dev/null
> +++ b/drivers/irqchip/irq-sp7021.c
> @@ -0,0 +1,285 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Sunplus Plus1 SP7021 SoC interrupt controller
> + *
> + * Copyright (c) 2020 Andreas Färber
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/irq.h>
> +#include <linux/irqchip.h>
> +#include <linux/irqchip/chained_irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +
> +#define REG_INTC_INTR_TYPE(i) (0x0 + (i) * 4)
> +#define REG_INTC_INTR_POLARITY(i) (0x1c + (i) * 4)
> +#define REG_INTC_INTR_PRIO(i) (0x38 + (i) * 4)
> +#define REG_INTC_INTR_MASK(i) (0x54 + (i) * 4)
> +
> +#define REG_INTC_INTR_CLR(i) (0x0 + (i) * 4)
> +#define REG_INTC_MASKED_FIQS(i) (0x1c + (i) * 4)
> +#define REG_INTC_MASKED_IRQS(i) (0x38 + (i) * 4)
> +#define REG_INTC_INTR_GROUP 0x7c
> +
> +#define INTC_INTR_GROUP_FIQ GENMASK(6, 0)
> +#define INTC_INTR_GROUP_IRQ GENMASK(14, 8)
> +
> +struct sp7021_intc_data {
> + void __iomem *base0;
> + void __iomem *base1;
> + int ext0_irq;
> + int ext1_irq;
> + struct irq_chip chip;
Why do you need this irq_chip on a per instance basis?
> + struct irq_domain *domain;
> + raw_spinlock_t lock;
> +};
> +
> +static void sp7021_intc_ext0_irq_handle(struct irq_desc *desc)
> +{
> + struct sp7021_intc_data *s = irq_desc_get_handler_data(desc);
> + struct irq_chip *chip = irq_desc_get_chip(desc);
> + u32 mask, masked;
> + int i, j;
> +
> + chained_irq_enter(chip, desc);
> +
> + mask = readl_relaxed(s->base1 + REG_INTC_INTR_GROUP);
> + mask = FIELD_GET(INTC_INTR_GROUP_IRQ, mask);
> + while (mask) {
> + i = fls(mask) - 1;
> + mask &= ~BIT(i);
> +
> + masked = readl_relaxed(s->base1 + REG_INTC_MASKED_IRQS(i));
> + while (masked) {
> + j = fls(masked) - 1;
> + masked &= ~BIT(j);
> +
> + generic_handle_irq(irq_find_mapping(s->domain,
> + i * 32 + j));
> + }
> + }
> +
> + chained_irq_exit(chip, desc);
> +}
> +
> +static void sp7021_intc_ext1_irq_handle(struct irq_desc *desc)
> +{
> + struct sp7021_intc_data *s = irq_desc_get_handler_data(desc);
> + struct irq_chip *chip = irq_desc_get_chip(desc);
> + u32 mask, masked;
> + int i, j;
> +
> + chained_irq_enter(chip, desc);
> +
> + mask = readl_relaxed(s->base1 + REG_INTC_INTR_GROUP);
> + mask = FIELD_GET(INTC_INTR_GROUP_FIQ, mask);
> + while (mask) {
> + i = fls(mask) - 1;
> + mask &= ~BIT(i);
> +
> + masked = readl_relaxed(s->base1 + REG_INTC_MASKED_FIQS(i));
> + while (masked) {
> + j = fls(masked) - 1;
> + masked &= ~BIT(j);
> +
> + generic_handle_irq(irq_find_mapping(s->domain,
> + i * 32 + j));
> + }
> + }
> +
> + chained_irq_exit(chip, desc);
> +}
Given that the only difference between these two functions is
INTC_INTR_GROUP_IRQ vs INTC_INTR_GROUP_FIQ (whatever that means for
something connected to SPIs...), surely you can devise a way to make
this common code.
> +
> +static void sp7021_intc_ack_irq(struct irq_data *data)
> +{
> + struct sp7021_intc_data *s = irq_data_get_irq_chip_data(data);
> + unsigned int idx;
> + u32 mask;
> +
> + idx = data->hwirq / 32;
You have this construct everywhere. How about making all your offset
helpers take a hwirq number instead? It would certainly make the whole
thing look a little less fragile.
> +
> + mask = BIT(data->hwirq % 32);
> + writel_relaxed(mask, s->base1 + REG_INTC_INTR_CLR(idx));
> +}
> +
> +static void sp7021_intc_mask_irq(struct irq_data *data)
> +{
> + struct sp7021_intc_data *s = irq_data_get_irq_chip_data(data);
> + unsigned long flags;
> + unsigned int idx;
> + u32 mask;
> +
> + idx = data->hwirq / 32;
> +
> + raw_spin_lock_irqsave(&s->lock, flags);
> +
> + mask = readl_relaxed(s->base0 + REG_INTC_INTR_MASK(idx));
> + mask &= ~BIT(data->hwirq % 32);
> + writel_relaxed(mask, s->base0 + REG_INTC_INTR_MASK(idx));
> +
> + raw_spin_unlock_irqrestore(&s->lock, flags);
> +}
> +
> +static void sp7021_intc_unmask_irq(struct irq_data *data)
> +{
> + struct sp7021_intc_data *s = irq_data_get_irq_chip_data(data);
> + unsigned long flags;
> + unsigned int idx;
> + u32 mask;
> +
> + idx = data->hwirq / 32;
> +
> + raw_spin_lock_irqsave(&s->lock, flags);
> +
> + mask = readl_relaxed(s->base0 + REG_INTC_INTR_MASK(idx));
> + mask |= BIT(data->hwirq % 32);
> + writel_relaxed(mask, s->base0 + REG_INTC_INTR_MASK(idx));
> +
> + raw_spin_unlock_irqrestore(&s->lock, flags);
> +}
> +
> +static int sp7021_intc_set_irq_type(struct irq_data *data, unsigned int flow_type)
> +{
> + struct sp7021_intc_data *s = irq_data_get_irq_chip_data(data);
> + unsigned long flags;
> + unsigned int idx;
> + u32 mask, type, polarity;
> +
> + idx = data->hwirq / 32;
> + mask = BIT(data->hwirq % 32);
> +
> + if (flow_type & IRQ_TYPE_LEVEL_MASK)
> + irq_set_chip_handler_name_locked(data, &s->chip, handle_level_irq, NULL);
> + else
> + irq_set_chip_handler_name_locked(data, &s->chip, handle_edge_irq, NULL);
Now you've changed the flow type even if the checks below end-up
failing. Don't do that. Also, please use irq_set_chip_handler_locked()
instead. You don't need to change the irqchip.
> +
> + raw_spin_lock_irqsave(&s->lock, flags);
> +
> + type = readl_relaxed(s->base0 + REG_INTC_INTR_TYPE(idx));
> + polarity = readl_relaxed(s->base0 + REG_INTC_INTR_POLARITY(idx));
> +
> + switch (flow_type) {
> + case IRQ_TYPE_EDGE_RISING:
> + type |= mask;
> + polarity &= ~mask;
> + break;
> + case IRQ_TYPE_EDGE_FALLING:
> + type |= mask;
> + polarity |= mask;
> + break;
> + case IRQ_TYPE_LEVEL_HIGH:
> + type &= ~mask;
> + polarity &= ~mask;
> + break;
> + case IRQ_TYPE_LEVEL_LOW:
> + type &= ~mask;
> + polarity |= mask;
> + break;
> + default:
> + raw_spin_unlock_irqrestore(&s->lock, flags);
> + return -EBADR;
The canonical error to use is -EINVAL.
> + }
> +
> + writel_relaxed(type, s->base0 + REG_INTC_INTR_TYPE(idx));
> + writel_relaxed(polarity, s->base0 + REG_INTC_INTR_POLARITY(idx));
> +
> + raw_spin_unlock_irqrestore(&s->lock, flags);
> +
> + return IRQ_SET_MASK_OK;
> +}
> +
> +static const struct irq_chip sp7021_intc_irq_chip = {
> + .name = "SP7021-A",
> + .irq_ack = sp7021_intc_ack_irq,
> + .irq_mask = sp7021_intc_mask_irq,
> + .irq_unmask = sp7021_intc_unmask_irq,
> + .irq_set_type = sp7021_intc_set_irq_type,
I assume this SoC is SMP. You need a set_affinity method that returns
-EINVAL.
> +};
> +
> +static int sp7021_intc_irq_domain_map(struct irq_domain *d,
> + unsigned int irq, irq_hw_number_t hw)
> +{
> + struct sp7021_intc_data *s = d->host_data;
> + unsigned int idx;
> + u32 mask, type;
> +
> + idx = hw / 32;
> + mask = BIT(hw % 32);
> +
> + type = readl_relaxed(s->base0 + REG_INTC_INTR_TYPE(idx));
> +
> + irq_set_chip_and_handler(irq, &s->chip, (type & mask) ? handle_edge_irq : handle_level_irq);
> + irq_set_chip_data(irq, s);
> + irq_set_probe(irq);
> +
> + return 0;
> +}
> +
> +static const struct irq_domain_ops sp7021_intc_domain_ops = {
> + .xlate = irq_domain_xlate_onecell,
If it is a single cell, how do you express the interrupt polarity in
the DT? Specially when the DT binding says that #interrupt-cells = <2>.
Clearly, you have never tested this.
> + .map = sp7021_intc_irq_domain_map,
> +};
> +
> +int __init sp7021_intc_init(struct device_node *node, struct device_node *parent)
> +{
> + struct sp7021_intc_data *s;
> + void __iomem *base0, *base1;
> + int i;
> +
> + base0 = of_iomap(node, 0);
> + if (!base0)
> + return -EIO;
-ENOMEM.
> +
> + base1 = of_iomap(node, 1);
> + if (!base1)
> + return -EIO;
Leaking mapping.
> +
> + s = kzalloc(sizeof(*s), GFP_KERNEL);
> + if (!s)
> + return -ENOMEM;
Again.
> +
> + s->base0 = base0;
> + s->base1 = base1;
> + s->chip = sp7021_intc_irq_chip;
That's exactly what I was complaining about earlier: pointlessly
storing a copy of a static const data structure that never gets
updated. Drop this.
> +
> + s->ext0_irq = irq_of_parse_and_map(node, 0);
> + if (s->ext0_irq <= 0) {
> + kfree(s);
> + return -EINVAL;
> + }
> +
> + s->ext1_irq = irq_of_parse_and_map(node, 1);
> + if (s->ext1_irq <= 0) {
> + kfree(s);
> + return -EINVAL;
> + }
> +
> + raw_spin_lock_init(&s->lock);
> +
> + for (i = 0; i < 7; i++) {
> + writel_relaxed(0, s->base0 + REG_INTC_INTR_MASK(i));
> + writel_relaxed(~0, s->base0 + REG_INTC_INTR_TYPE(i));
> + writel_relaxed(0, s->base0 + REG_INTC_INTR_POLARITY(i));
> +
> + /* irq, not fiq */
> + writel_relaxed(~0, s->base0 + REG_INTC_INTR_PRIO(i));
> +
> + writel_relaxed(~0, s->base1 + REG_INTC_INTR_CLR(i));
> + }
> +
> + s->domain = irq_domain_add_linear(node, 200, &sp7021_intc_domain_ops,
Magic numbers.
> + s);
> + if (!s->domain) {
> + kfree(s);
> + return -ENOMEM;
> + }
> +
> + irq_set_chained_handler_and_data(s->ext0_irq, sp7021_intc_ext0_irq_handle, s);
> + irq_set_chained_handler_and_data(s->ext1_irq, sp7021_intc_ext1_irq_handle, s);
> +
> + return 0;
> +}
> +IRQCHIP_DECLARE(sp7021_intc, "sunplus,sp7021-intc", sp7021_intc_init);
So yes, I complained a lot. Thanks for giving me the opportunity.
M.
--
Jazz is not dead, it just smells funny.
Added Jie Zhang and Jian Qin in to cc list.
Jie Zhang is software leader of Sunplus Plus 1 (SP7021) platform.
-----Original Message-----
From: Andreas Färber <[email protected]>
Sent: Monday, March 09, 2020 12:32 AM
To: [email protected]
Cc: Wells Lu 呂芳騰 <[email protected]>; Dvorkin Dmitry <[email protected]>; [email protected]; Andreas Färber <[email protected]>; [email protected]; Rob Herring <[email protected]>; Greg Kroah-Hartman <[email protected]>; [email protected]; Thomas Gleixner <[email protected]>; Jason Cooper <[email protected]>; Marc Zyngier <[email protected]>
Subject: [RFC 00/11] ARM: Initial Sunplus Plus1 SP7021 and BPI-F2S support
Hello,
This patch series adds initial drivers and Device Trees for Sunplus Plus1 series (codename Pentagram) SP7021 SoC and Banana Pi BPI-F2S SBC.
First, minimal Kconfig, DT and earlycon driver are prepared to get serial output at all. Next, interrupt controller and full serial driver are added that allow to boot into an initrd with interactive serial console.
Device Tree files added are for the CPU-Chip (aka A-Chip) with quad Cortex-A7, but the file split prepares for also adding the Peripheral-Chip (B-Chip) with
ARM9 later. However, for now this is not reflected in the .dts filename; this corresponds to the vf610- vs. vf610m4- naming scheme, whereas an alternative would be to use sp7021-cchip- vs. -pchip- prefix (as sp7021-cpu- looks weird).
It is assumed we can reuse the same SoC and board bindings for CA7 and ARM9 and only differ for IP blocks where needed.
My inquiry to Sunplus about their GIC (anticipating complaints from Marc) remained unanswered, so I've added the two extra regions and irq myself, without being able to test KVM due to BSP U-Boot not booting in HYP mode.
According to Sunplus the mode can be changed in U-Boot (but where/how?).
Similarly, the architectural timer is not properly initialized in BSP U-Boot, so that I currently have a mach- hack in my tree below. Unlike RTD1195, we do have U-Boot sources (v2019.04 based), so should be able to fix this in the bootloader rather than in the kernel, thus not included as patch here.
Based on SoC online manual [1] and downstream BPI-F2S BSP tree [2] as well as my previous Actions serial and Realtek irqchip drivers and DTs.
More details at:
https://en.opensuse.org/HCL:BananaPi_F2S
Latest experimental patches at:
https://github.com/afaerber/linux/commits/f2s-next
Have a lot of fun!
Cheers,
Andreas
[1] https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/pages/470450252/SP7021+Technical+Manual
[2] https://github.com/BPI-SINOVOIP/BPI-F2S-bsp
Cc: [email protected]
Cc: Rob Herring <[email protected]>
Cc: Greg Kroah-Hartman <[email protected]>
Cc: [email protected]
Cc: Thomas Gleixner <[email protected]>
Cc: Jason Cooper <[email protected]>
Cc: Marc Zyngier <[email protected]>
Cc: Wells Lu 呂芳騰 <[email protected]>
Cc: Dvorkin Dmitry <[email protected]>
Andreas Färber (11):
dt-bindings: vendor-prefixes: Add Sunplus
dt-bindings: arm: Add Sunplus SP7021 and Banana Pi BPI-F2S
ARM: Prepare Sunplus Plus1 SoC family
dt-bindings: interrupt-controller: Add Sunplus SP7021 mux
dt-bindings: serial: Add Sunplus SP7021 UART
tty: serial: Add Sunplus Plus1 UART earlycon
ARM: dts: Add Sunplus Plus1 SP7021 and Banana Pi F2S
tty: serial: sunplus: Implement full UART driver
irqchip: Add Sunplus SP7021 interrupt (mux) controller
ARM: dts: sp7021-cpu: Add interrupt controller node
ARM: dts: sp7021-cpu: Add dummy UART0 clock and interrupt
Documentation/devicetree/bindings/arm/sunplus.yaml | 22 +
.../sunplus,pentagram-intc.yaml | 50 ++
.../bindings/serial/sunplus,pentagram-uart.yaml | 24 +
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
arch/arm/Kconfig | 2 +
arch/arm/Makefile | 1 +
arch/arm/boot/dts/Makefile | 2 +
arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts | 29 +
arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi | 93 +++
arch/arm/boot/dts/pentagram-sp7021.dtsi | 61 ++
arch/arm/mach-sunplus/Kconfig | 10 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-sp7021.c | 285 ++++++++
drivers/tty/serial/Kconfig | 19 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/sunplus-uart.c | 770 +++++++++++++++++++++
include/uapi/linux/serial_core.h | 3 +
17 files changed, 1375 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/sunplus.yaml
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/sunplus,pentagram-intc.yaml
create mode 100644 Documentation/devicetree/bindings/serial/sunplus,pentagram-uart.yaml
create mode 100644 arch/arm/boot/dts/pentagram-sp7021-bpi-f2s.dts
create mode 100644 arch/arm/boot/dts/pentagram-sp7021-cpu.dtsi
create mode 100644 arch/arm/boot/dts/pentagram-sp7021.dtsi
create mode 100644 arch/arm/mach-sunplus/Kconfig create mode 100644 drivers/irqchip/irq-sp7021.c create mode 100644 drivers/tty/serial/sunplus-uart.c
--
2.16.4
On Sun, Mar 08, 2020 at 05:32:22PM +0100, Andreas Färber wrote:
> The Sunplus SP7021 SoC has an interrupt mux.
>
> Cc: Wells Lu 呂芳騰 <[email protected]>
> Signed-off-by: Andreas Färber <[email protected]>
> ---
> .../sunplus,pentagram-intc.yaml | 50 ++++++++++++++++++++++
> 1 file changed, 50 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/sunplus,pentagram-intc.yaml
>
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/sunplus,pentagram-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/sunplus,pentagram-intc.yaml
> new file mode 100644
> index 000000000000..baaf7bcd4a71
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/sunplus,pentagram-intc.yaml
> @@ -0,0 +1,50 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/interrupt-controller/sunplus,pentagram-intc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Sunplus Pentagram SoC Interrupt Controller
> +
> +maintainers:
> + - Andreas Färber <[email protected]>
> +
> +allOf:
> + - $ref: /schemas/interrupt-controller.yaml#
No need for this. It's applied based on the node name.
> +
> +properties:
> + compatible:
> + const: sunplus,sp7021-intc
> +
> + reg:
> + maxItems: 2
Need to define what each one is.
> +
> + interrupts:
> + maxItems: 2
Same here.
> +
> + interrupt-controller: true
> +
> + "#interrupt-cells":
> + const: 2
> +
> +required:
> + - compatible
> + - reg
> + - interrupt-controller
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> + interrupt-controller@9c000780 {
> + compatible = "sunplus,sp7021-intc";
> + reg = <0x9c000780 0x80>,
> + <0x9c000a80 0x80>;
> + interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>,
> + <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>;
> + interrupt-controller;
> + #interrupt-cells = <2>;
> + };
> +...
> --
> 2.16.4
>
On Sun, Mar 08, 2020 at 05:32:23PM +0100, Andreas Färber wrote:
> The Sunplus Plus1 (aka Pentagram) SP7021 SoC has five UARTs.
>
> Cc: Wells Lu 呂芳騰 <[email protected]>
> Signed-off-by: Andreas Färber <[email protected]>
> ---
> .../bindings/serial/sunplus,pentagram-uart.yaml | 24 ++++++++++++++++++++++
> 1 file changed, 24 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/serial/sunplus,pentagram-uart.yaml
>
> diff --git a/Documentation/devicetree/bindings/serial/sunplus,pentagram-uart.yaml b/Documentation/devicetree/bindings/serial/sunplus,pentagram-uart.yaml
> new file mode 100644
> index 000000000000..9d1641232a4c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/serial/sunplus,pentagram-uart.yaml
> @@ -0,0 +1,24 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/serial/sunplus,pentagram-uart.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Sunplus Pentagram SoC UART Serial Interface
> +
> +maintainers:
> + - Andreas Färber <[email protected]>
> +
Soon this will need to reference serial.yaml which is getting added to
5.7.
> +properties:
> + compatible:
> + const: sunplus,sp7021-uart
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + clocks:
> + maxItems: 1
> +...
> --
> 2.16.4
>