On Fri, 29 Oct 2021 09:44:34 +0100,
Qin Jian <[email protected]> wrote:
>
> Add interrupt driver for Sunplus SP7021 SoC.
>
> This is the interrupt controller in P chip which collects all interrupt
> sources in P-chip and routes them to C-chip. C-chip adopts ARM CA7
> interrupt controller (compitable = "arm,cortex-a7-gic"). It is a parent
This information isn't relevant.
> interrupt controller of P-chip interrupt controller.
>
> Signed-off-by: Qin Jian <[email protected]>
> ---
> MAINTAINERS | 1 +
> drivers/irqchip/Kconfig | 9 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-sp7021-intc.c | 324 ++++++++++++++++++++++++++++++
> 4 files changed, 335 insertions(+)
> create mode 100644 drivers/irqchip/irq-sp7021-intc.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index be0334d6a..bfa891d86 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2665,6 +2665,7 @@ F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
> F: Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
> F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
> F: drivers/clk/clk-sp7021.c
> +F: drivers/irqchip/irq-sp7021-intc.c
> F: drivers/reset/reset-sunplus.c
> F: include/dt-bindings/clock/sp-sp7021.h
> F: include/dt-bindings/interrupt-controller/sp7021-intc.h
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index aca7b595c..8a58dfb88 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -602,4 +602,13 @@ config APPLE_AIC
> Support for the Apple Interrupt Controller found on Apple Silicon SoCs,
> such as the M1.
>
> +config SUNPLUS_SP7021_INTC
> + bool "Sunplus SP7021 interrupt controller"
> + default SOC_SP7021
> + help
> + Support for the Sunplus SP7021 Interrupt Controller IP core.
> + This is used as a primary controller with SP7021 ChipP and can also
> + be used as a secondary chained controller on SP7021 ChipC.
> + This is selected automatically by platform config.
If it is selected, drop the default.
> +
> endmenu
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index f88cbf36a..75411f654 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -116,3 +116,4 @@ obj-$(CONFIG_MACH_REALTEK_RTL) += irq-realtek-rtl.o
> obj-$(CONFIG_WPCM450_AIC) += irq-wpcm450-aic.o
> obj-$(CONFIG_IRQ_IDT3243X) += irq-idt3243x.o
> obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o
> +obj-$(CONFIG_SUNPLUS_SP7021_INTC) += irq-sp7021-intc.o
> diff --git a/drivers/irqchip/irq-sp7021-intc.c b/drivers/irqchip/irq-sp7021-intc.c
> new file mode 100644
> index 000000000..3431ec746
> --- /dev/null
> +++ b/drivers/irqchip/irq-sp7021-intc.c
> @@ -0,0 +1,324 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +/*
> + * Copyright (C) Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +#include <linux/irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/io.h>
> +#include <linux/irqchip.h>
> +#include <linux/irqchip/chained_irq.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +
> +#include <asm/exception.h>
> +#include <dt-bindings/interrupt-controller/sp7021-intc.h>
> +
> +#define SP_INTC_HWIRQ_MIN 0
> +#define SP_INTC_HWIRQ_MAX 223
> +
> +/* Interrupt G0/G1 offset */
> +#define INTR_REG_SIZE (7 * 4)
Why isn't that derived from the number of interrupts?
> +
> +#define G0_INTR_TYPE (0)
> +#define G0_INTR_POLARITY (G0_INTR_TYPE + INTR_REG_SIZE)
> +#define G0_INTR_PRIORITY (G0_INTR_POLARITY + INTR_REG_SIZE)
> +#define G0_INTR_MASK (G0_INTR_PRIORITY + INTR_REG_SIZE)
> +
> +#define G1_INTR_CLR (0)
> +#define G1_MASKED_EXT1 (G1_INTR_CLR + INTR_REG_SIZE)
> +#define G1_MASKED_EXT0 (G1_MASKED_EXT1 + INTR_REG_SIZE)
> +#define G1_INTR_GROUP (31 * 4)
> +#define G1_INTR_MASK (0x7F)
> +#define G1_EXT1_SHIFT (0)
> +#define G1_EXT0_SHIFT (8)
> +
> +static struct sp_intctl {
> + void __iomem *g0;
> + void __iomem *g1;
What is G0? what is G1?
> + struct irq_domain *domain;
> + struct device_node *node;
> + raw_spinlock_t lock;
> + int virq[2];
> +} sp_intc;
> +
> +/* GPIO INT EDGE BUG WORKAROUND */
> +#define GPIO_INT0_HWIRQ 120
> +#define GPIO_INT7_HWIRQ 127
> +#define GPIO_INT_EDGE_ACTIVE BIT(31)
> +#define IS_GPIO_INT(n) (((n) >= GPIO_INT0_HWIRQ) && ((n) <= GPIO_INT7_HWIRQ))
> +/* array to hold which interrupt needs to workaround the bug
> + * INT_TYPE_NONE: no need
> + * INT_TYPE_EDGE_FALLING/INT_TYPE_EDGE_RISING: need to workaround
> + * GPIO_INT_EDGE_ACTIVING: workaround is on going
Please describe the nature of the workaround. s/ACTIVING/ACTIVE/.
> + */
> +static u32 edge_trigger[SP_INTC_HWIRQ_MAX];
4 states per interrupt, and yet you use a u32 for each of them...
Also, why isn't this part of your sp_intc data structure? You also
have enough space for 200+ interrupts (ignoring the obvious off-by-one
bug), and yet you are only concerned with 8 of them. Do you spot a
trend here?
> +
> +static struct irq_chip sp_intc_chip;
> +
> +static void sp_intc_assign_bit(u32 hwirq, void __iomem *base, u32 value)
If value describes a single bit, why is it a u32?
> +{
> + u32 offset, mask;
> + unsigned long flags;
> + void __iomem *reg;
> +
> + offset = (hwirq / 32) * 4;
> + reg = base + offset;
> +
> + raw_spin_lock_irqsave(&sp_intc.lock, flags);
> + mask = readl_relaxed(reg);
> + if (value)
> + mask |= BIT(hwirq % 32);
> + else
> + mask &= ~BIT(hwirq % 32);
> + writel_relaxed(mask, reg);
> + raw_spin_unlock_irqrestore(&sp_intc.lock, flags);
> +}
> +
> +static void sp_intc_ack_irq(struct irq_data *d)
> +{
> + u32 hwirq = d->hwirq;
> +
> + if (edge_trigger[hwirq] != IRQ_TYPE_NONE) {
Why don't you just check the irq number instead?
> + sp_intc_assign_bit(hwirq, sp_intc.g0 + G0_INTR_POLARITY,
> + (edge_trigger[hwirq] == IRQ_TYPE_EDGE_RISING));
> + edge_trigger[hwirq] |= GPIO_INT_EDGE_ACTIVE;
The whole thing is terrible. For each of the 8 interrupts, you only
need to know whether:
- it is rising or falling
- it is active or not
That's a grand total of 16 bits instead of almost a 1kB worth of
nothing. Use atomic bitops, and this is done.
> + }
> +
> + sp_intc_assign_bit(hwirq, sp_intc.g1 + G1_INTR_CLR, 1);
> +}
> +
> +static void sp_intc_mask_irq(struct irq_data *d)
> +{
> + sp_intc_assign_bit(d->hwirq, sp_intc.g0 + G0_INTR_MASK, 0);
> +}
> +
> +static void sp_intc_unmask_irq(struct irq_data *d)
> +{
> + sp_intc_assign_bit(d->hwirq, sp_intc.g0 + G0_INTR_MASK, 1);
> +}
> +
> +static void sp_intc_set_priority(u32 hwirq, u32 priority)
> +{
> + sp_intc_assign_bit(hwirq, sp_intc.g0 + G0_INTR_PRIORITY, priority);
> +}
> +
> +static int sp_intc_set_type(struct irq_data *d, unsigned int type)
> +{
> + u32 intr_type; /* 0: level 1: edge */
> + u32 intr_polarity; /* 0: high/rising 1: low/falling */
So how about giving the variables sensible names and types:
bool is_level, is_high;
> + u32 hwirq = d->hwirq;
> +
> + /* update the chip/handler */
> + if (type & IRQ_TYPE_LEVEL_MASK)
> + irq_set_chip_handler_name_locked(d, &sp_intc_chip,
> + handle_level_irq, NULL);
> + else
> + irq_set_chip_handler_name_locked(d, &sp_intc_chip,
> + handle_edge_irq, NULL);
> +
> + edge_trigger[hwirq] = IRQ_TYPE_NONE;
> +
> + if (type & IRQ_TYPE_LEVEL_MASK)
> + intr_type = 0;
> + else if (IS_GPIO_INT(hwirq)) {
> + intr_type = 0;
> + edge_trigger[hwirq] = type;
> + } else
> + intr_type = 1;
Coding style.
> +
> + intr_polarity = (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_EDGE_FALLING);
> +
> + sp_intc_assign_bit(hwirq, sp_intc.g0 + G0_INTR_TYPE, intr_type);
> + sp_intc_assign_bit(hwirq, sp_intc.g0 + G0_INTR_POLARITY, intr_polarity);
> +
> + return IRQ_SET_MASK_OK;
> +}
> +
> +static int sp_intc_get_ext_irq(int ext_num)
> +{
> + u32 hwirq, mask;
> + u32 i;
> +
> + i = readl_relaxed(sp_intc.g1 + G1_INTR_GROUP);
> + if (ext_num)
> + mask = (i >> G1_EXT1_SHIFT) & G1_INTR_MASK;
> + else
> + mask = (i >> G1_EXT0_SHIFT) & G1_INTR_MASK;
> + if (mask) {
> + i = fls(mask) - 1;
> + if (ext_num)
> + mask = readl_relaxed(sp_intc.g1 + G1_MASKED_EXT1 + i * 4);
> + else
> + mask = readl_relaxed(sp_intc.g1 + G1_MASKED_EXT0 + i * 4);
> + if (mask) {
> + hwirq = (i << 5) + fls(mask) - 1;
> + return hwirq;
> + }
> + }
What a terrible control flow. Also, variable names are cheap, and you
don't need to reuse them when they mean something different.
if (ext_num) {
shift = G1_EXT1_SHIFT;
offset = G1_MASKED_EXT1;
} else {
[[ same thing with G0 ]
}
status = readl_relaxed();
pending_summary = (status >> shift) & G1_INTR_MASK;
if (!pending_summary)
return -1;
index = fls(pending_summary) - 1;
pending = readl_relaxed(g1 + offset + index * 4);
if (!pending)
return -1;
return (index << 5) | (fls(pending) - 1);
Look: no nesting, obvious names, anyone can understand it.
> + return -1; /* No interrupt */
> +}
> +
> +static void sp_intc_handle_ext_cascaded(struct irq_desc *desc)
> +{
> + struct irq_chip *host_chip = irq_desc_get_chip(desc);
> + int ext_num = (int)irq_desc_get_handler_data(desc);
> + int hwirq;
> +
> + chained_irq_enter(host_chip, desc);
> +
> + while ((hwirq = sp_intc_get_ext_irq(ext_num)) >= 0) {
> + if (edge_trigger[hwirq] & GPIO_INT_EDGE_ACTIVE) {
> + edge_trigger[hwirq] &= ~GPIO_INT_EDGE_ACTIVE;
> + sp_intc_assign_bit(hwirq, sp_intc.g0 + G0_INTR_POLARITY,
> + (edge_trigger[hwirq] != IRQ_TYPE_EDGE_RISING));
> + } else
> + generic_handle_domain_irq(sp_intc.domain, hwirq);
Coding style.
> + }
> +
> + chained_irq_exit(host_chip, desc);
> +}
> +
> +static void __exception_irq_entry sp_intc_handle_irq(struct pt_regs *regs)
> +{
> + int hwirq;
> +
> + while ((hwirq = sp_intc_get_ext_irq(0)) >= 0)
> + generic_handle_domain_irq(sp_intc.domain, hwirq);
> +}
> +
> +static void __init sp_intc_chip_init(void __iomem *base0, void __iomem *base1)
> +{
> + int i;
> +
> + sp_intc.g0 = base0;
> + sp_intc.g1 = base1;
> +
> + for (i = 0; i < 7; i++) {
> + /* all mask */
> + writel_relaxed(0, sp_intc.g0 + G0_INTR_MASK + i * 4);
> + /* all edge */
> + writel_relaxed(~0, sp_intc.g0 + G0_INTR_TYPE + i * 4);
> + /* all high-active */
> + writel_relaxed(0, sp_intc.g0 + G0_INTR_POLARITY + i * 4);
> + /* all irq */
> + writel_relaxed(~0, sp_intc.g0 + G0_INTR_PRIORITY + i * 4);
> + /* all clear */
> + writel_relaxed(~0, sp_intc.g1 + G1_INTR_CLR + i * 4);
> + }
> +}
> +
> +int sp_intc_xlate_of(struct irq_domain *d, struct device_node *node,
> + const u32 *intspec, unsigned int intsize,
> + irq_hw_number_t *out_hwirq, unsigned int *out_type)
> +{
> + int ret;
> +
> + ret = irq_domain_xlate_twocell(d, node,
> + intspec, intsize, out_hwirq, out_type);
> + if (!ret) {
> + /* intspec[1]: IRQ_TYPE | SP_INTC_EXT_INT
> + * SP_INTC_EXT_INT: 0-1,
> + * to indicate route to which parent irq: EXT_INT0/EXT_INT1
> + */
Why is that in the DT? If that's a SW decision, it doesn't belong there.
> + u32 ext_int = (intspec[1] & SP_INTC_EXT_INT_MASK) >> SP_INTC_EXT_INT_SHFIT;
> +
> + /* priority = 0, route to EXT_INT1
> + * otherwise, route to EXT_INT0
> + */
> + sp_intc_set_priority(*out_hwirq, 1 - ext_int);
I already said in the initial review that changing the HW didn't
belong in the translate callback, but should be done at map() time.
> + }
> +
> + return ret;
> +}
> +
> +static struct irq_chip sp_intc_chip = {
> + .name = "sp_intc",
> + .irq_ack = sp_intc_ack_irq,
> + .irq_mask = sp_intc_mask_irq,
> + .irq_unmask = sp_intc_unmask_irq,
> + .irq_set_type = sp_intc_set_type,
> +};
> +
> +static int sp_intc_irq_domain_map(struct irq_domain *domain,
> + unsigned int irq, irq_hw_number_t hwirq)
> +{
> + irq_set_chip_and_handler(irq, &sp_intc_chip, handle_level_irq);
> + irq_set_chip_data(irq, &sp_intc_chip);
> + irq_set_noprobe(irq);
> +
> + return 0;
> +}
> +
> +static const struct irq_domain_ops sp_intc_dm_ops = {
> + .xlate = sp_intc_xlate_of,
> + .map = sp_intc_irq_domain_map,
> +};
> +
> +#ifdef CONFIG_OF
Why the #ifdef? This thing doesn't work without OF anyway.
> +static int sp_intc_irq_map(struct device_node *node, int i)
> +{
> + sp_intc.virq[i] = irq_of_parse_and_map(node, i);
> + if (!sp_intc.virq[i]) {
> + pr_err("%s: missed EXT_INT%d in DT\n", __func__, i);
-ENOENT is enough, no need to shout on the console.
> + return -ENOENT;
> + }
> +
> + pr_info("%s: EXT_INT%d = %d\n", __func__, i, sp_intc.virq[i]);
Nobody cares about this. Get rid of it.
> + irq_set_chained_handler_and_data(sp_intc.virq[i],
> + sp_intc_handle_ext_cascaded, (void *)i);
> +
> + return 0;
> +}
> +
> +int __init sp_intc_init_dt(
> + struct device_node *node, struct device_node *parent)
Single line.
> +{
> + void __iomem *base0, *base1;
> +
> + base0 = of_iomap(node, 0);
> + if (!base0) {
> + pr_err("unable to map sp-intc base 0\n");
> + return -EINVAL;
> + }
> +
> + base1 = of_iomap(node, 1);
> + if (!base1) {
> + pr_err("unable to map sp-intc base 1\n");
> + return -EINVAL;
> + }
> +
> + sp_intc.node = node;
> +
> + sp_intc_chip_init(base0, base1);
> +
> + sp_intc.domain = irq_domain_add_linear(node,
> + SP_INTC_HWIRQ_MAX - SP_INTC_HWIRQ_MIN,
> + &sp_intc_dm_ops, &sp_intc);
> + if (!sp_intc.domain) {
> + pr_err("%s: unable to create linear domain\n", __func__);
Drop the error message.
> + return -EINVAL;
> + }
> +
> + raw_spin_lock_init(&sp_intc.lock);
> +
> + if (parent) {
> + /* secondary chained controller */
> + if (sp_intc_irq_map(node, 0)) // EXT_INT0
> + return -ENOENT;
Just return whatever the helper returned. You don't need to
reinterpret it.
> +
> + if (sp_intc_irq_map(node, 1)) // EXT_INT1
> + return -ENOENT;
> + } else {
> + /* primary controller */
> + set_handle_irq(sp_intc_handle_irq);
> + }
So what happens if you have *two* of these blocks? One as the root
controller, and another implementing the chained stuff?
> +
> + return 0;
> +}
> +IRQCHIP_DECLARE(sp_intc, "sunplus,sp7021-intc", sp_intc_init_dt);
> +#endif
> +
> +MODULE_AUTHOR("Qin Jian <[email protected]>");
> +MODULE_DESCRIPTION("Sunplus SP7021 Interrupt Controller Driver");
> +MODULE_LICENSE("GPL v2");
You are using IRQCHIP_DECLARE(), so this isn't a module. Drop this.
Thankfully, it is too late for 5.16, so you have a full cycle to give
this code another major cleanup.
M.
--
Without deviation from the norm, progress is not possible.
This patch series add Sunplus SP7021 SoC support.
Sunplus SP7021 is an ARM Cortex A7 (4 cores) based SoC. It integrates many
peripherals (ex: UART, I2C, SPI, SDIO, eMMC, USB, SD card and etc.) into a
single chip. It is designed for industrial control.
SP7021 consists of two chips (dies) in a package. One is called C-chip
(computing chip). It is a 4-core ARM Cortex A7 CPU. It adopts high-level
process (22 nm) for high performance computing. The other is called P-
chip (peripheral chip). It has many peripherals and an ARM A926 added
especially for real-time control. P-chip is made for customers. It adopts
low-level process (ex: 0.11 um) to reduce cost.
Refer to (for documentations):
https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
Refer to (applications):
https://tibbo.com/store/plus1.html
Refer to (applications):
http://www.sinovoip.com.cn/ecp_view.asp?id=586
Changes in v3:
- sp7021-intc: remove primary controller mode due to P-chip running Linux
not supported any more.
- sp7021-intc.h: removed, not set ext through the DT but sp_intc_set_ext()
- sunplus,sp7021-intc.yaml: update descriptions for above changes
- irq-sp7021-intc.c: more cleanup based on Marc's review
- all driver's Kconfig removed default, it's selected by platform config
Changes in v2:
- sunplus,sp7021-intc.yaml: add descrption for "#interrupt-cells", interrupts
- sunplus,sp7021-intc.yaml: drop "ext0-mask"/"ext1-mask" from DT
- sunplus,sp7021-intc.yaml: fix example.dt too long error
- irq-sp7021-intc.c: major rewrite
- all files with dual license
Qin Jian (8):
dt-bindings: vendor-prefixes: Add Sunplus
dt-bindings: arm: sunplus: Add bindings for Sunplus SP7021 SoC boards
dt-bindings: reset: Add bindings for SP7021 reset driver
reset: Add Sunplus SP7021 reset driver
dt-bindings: clock: Add bindings for SP7021 clock driver
clk: Add Sunplus SP7021 clock driver
dt-bindings: interrupt-controller: Add bindings for SP7021 interrupt
controller
irqchip: Add Sunplus SP7021 interrupt controller driver
.../bindings/arm/sunplus,sp7021.yaml | 27 +
.../bindings/clock/sunplus,sp7021-clkc.yaml | 38 +
.../sunplus,sp7021-intc.yaml | 62 ++
.../bindings/reset/sunplus,reset.yaml | 40 +
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
MAINTAINERS | 15 +
drivers/clk/Kconfig | 9 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-sp7021.c | 770 ++++++++++++++++++
drivers/irqchip/Kconfig | 9 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-sp7021-intc.c | 279 +++++++
drivers/reset/Kconfig | 8 +
drivers/reset/Makefile | 1 +
drivers/reset/reset-sunplus.c | 159 ++++
include/dt-bindings/clock/sp-sp7021.h | 112 +++
include/dt-bindings/reset/sp-sp7021.h | 99 +++
17 files changed, 1632 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
create mode 100644 Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
create mode 100644 Documentation/devicetree/bindings/reset/sunplus,reset.yaml
create mode 100644 drivers/clk/clk-sp7021.c
create mode 100644 drivers/irqchip/irq-sp7021-intc.c
create mode 100644 drivers/reset/reset-sunplus.c
create mode 100644 include/dt-bindings/clock/sp-sp7021.h
create mode 100644 include/dt-bindings/reset/sp-sp7021.h
--
2.33.1
Add documentation to describe Sunplus SP7021 reset driver bindings.
Signed-off-by: Qin Jian <[email protected]>
---
.../bindings/reset/sunplus,reset.yaml | 40 ++++++++
MAINTAINERS | 2 +
include/dt-bindings/reset/sp-sp7021.h | 99 +++++++++++++++++++
3 files changed, 141 insertions(+)
create mode 100644 Documentation/devicetree/bindings/reset/sunplus,reset.yaml
create mode 100644 include/dt-bindings/reset/sp-sp7021.h
diff --git a/Documentation/devicetree/bindings/reset/sunplus,reset.yaml b/Documentation/devicetree/bindings/reset/sunplus,reset.yaml
new file mode 100644
index 000000000..bf55f4ee2
--- /dev/null
+++ b/Documentation/devicetree/bindings/reset/sunplus,reset.yaml
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/reset/sunplus,reset.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Sunplus SoC Reset Controller
+
+maintainers:
+ - Qin Jian <[email protected]>
+
+properties:
+ compatible:
+ enum:
+ - sunplus,sp7021-reset # Reset Controller on SP7021 and compatible SoCs
+ - sunplus,q645-reset # Reset Controller on Q645 and compatible SoCs
+
+ "#reset-cells":
+ const: 1
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - "#reset-cells"
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ rstc: reset@9c000054 {
+ compatible = "sunplus,sp7021-reset";
+ #reset-cells = <1>;
+ reg = <0x9c000054 0x28>;
+ };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 6a5422f10..652f42cab 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2661,6 +2661,8 @@ L: [email protected] (moderated for mon-subscribers)
S: Maintained
W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
+F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
+F: include/dt-bindings/reset/sp-sp7021.h
ARM/Synaptics SoC support
M: Jisheng Zhang <[email protected]>
diff --git a/include/dt-bindings/reset/sp-sp7021.h b/include/dt-bindings/reset/sp-sp7021.h
new file mode 100644
index 000000000..8823d25ce
--- /dev/null
+++ b/include/dt-bindings/reset/sp-sp7021.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+#ifndef _DT_BINDINGS_RST_SUNPLUS_SP7021_H
+#define _DT_BINDINGS_RST_SUNPLUS_SP7021_H
+
+/* mo_reset0 ~ mo_reset9 */
+#define RST_SYSTEM 0x00
+#define RST_RTC 0x02
+#define RST_IOCTL 0x03
+#define RST_IOP 0x04
+#define RST_OTPRX 0x05
+#define RST_NOC 0x06
+#define RST_BR 0x07
+#define RST_RBUS_L00 0x08
+#define RST_SPIFL 0x09
+#define RST_SDCTRL0 0x0a
+#define RST_PERI0 0x0b
+#define RST_A926 0x0d
+#define RST_UMCTL2 0x0e
+#define RST_PERI1 0x0f
+
+#define RST_DDR_PHY0 0x10
+#define RST_ACHIP 0x12
+#define RST_STC0 0x14
+#define RST_STC_AV0 0x15
+#define RST_STC_AV1 0x16
+#define RST_STC_AV2 0x17
+#define RST_UA0 0x18
+#define RST_UA1 0x19
+#define RST_UA2 0x1a
+#define RST_UA3 0x1b
+#define RST_UA4 0x1c
+#define RST_HWUA 0x1d
+#define RST_DDC0 0x1e
+#define RST_UADMA 0x1f
+
+#define RST_CBDMA0 0x20
+#define RST_CBDMA1 0x21
+#define RST_SPI_COMBO_0 0x22
+#define RST_SPI_COMBO_1 0x23
+#define RST_SPI_COMBO_2 0x24
+#define RST_SPI_COMBO_3 0x25
+#define RST_AUD 0x26
+#define RST_USBC0 0x2a
+#define RST_USBC1 0x2b
+#define RST_UPHY0 0x2d
+#define RST_UPHY1 0x2e
+
+#define RST_I2CM0 0x30
+#define RST_I2CM1 0x31
+#define RST_I2CM2 0x32
+#define RST_I2CM3 0x33
+#define RST_PMC 0x3d
+#define RST_CARD_CTL0 0x3e
+#define RST_CARD_CTL1 0x3f
+
+#define RST_CARD_CTL4 0x42
+#define RST_BCH 0x44
+#define RST_DDFCH 0x4b
+#define RST_CSIIW0 0x4c
+#define RST_CSIIW1 0x4d
+#define RST_MIPICSI0 0x4e
+#define RST_MIPICSI1 0x4f
+
+#define RST_HDMI_TX 0x50
+#define RST_VPOST 0x55
+
+#define RST_TGEN 0x60
+#define RST_DMIX 0x61
+#define RST_TCON 0x6a
+#define RST_INTERRUPT 0x6f
+
+#define RST_RGST 0x70
+#define RST_GPIO 0x73
+#define RST_RBUS_TOP 0x74
+
+#define RST_MAILBOX 0x86
+#define RST_SPIND 0x8a
+#define RST_I2C2CBUS 0x8b
+#define RST_SEC 0x8d
+#define RST_DVE 0x8e
+#define RST_GPOST0 0x8f
+
+#define RST_OSD0 0x90
+#define RST_DISP_PWM 0x92
+#define RST_UADBG 0x93
+#define RST_DUMMY_MASTER 0x94
+#define RST_FIO_CTL 0x95
+#define RST_FPGA 0x96
+#define RST_L2SW 0x97
+#define RST_ICM 0x98
+#define RST_AXI_GLOBAL 0x99
+
+#define RST_MAX 0xA0
+
+#endif
--
2.33.1
Add documentation to describe Sunplus SP7021 interrupt controller bindings.
Signed-off-by: Qin Jian <[email protected]>
---
.../sunplus,sp7021-intc.yaml | 62 +++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 63 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
diff --git a/Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
new file mode 100644
index 000000000..5daeab63c
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
@@ -0,0 +1,62 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/sunplus,sp7021-intc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus SP7021 SoC Interrupt Controller Device Tree Bindings
+
+maintainers:
+ - Qin Jian <[email protected]>
+
+properties:
+ compatible:
+ items:
+ - const: sunplus,sp7021-intc
+
+ interrupt-controller: true
+
+ "#interrupt-cells":
+ const: 2
+ description:
+ The first cell is the IRQ number, the second cell is the trigger
+ type as defined in interrupt.txt in this directory.
+
+ reg:
+ maxItems: 2
+ description:
+ Specifies base physical address(s) and size of the controller regs.
+ The 1st region include type/polarity/priority/mask regs.
+ The 2nd region include clear/masked_ext0/masked_ext1/group regs.
+
+ interrupts:
+ maxItems: 2
+ description:
+ EXT_INT0 & EXT_INT1, 2 interrupts references to primary interrupt
+ controller.
+
+required:
+ - compatible
+ - interrupt-controller
+ - "#interrupt-cells"
+ - reg
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ intc: interrupt-controller@9c000780 {
+ compatible = "sunplus,sp7021-intc";
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ reg = <0x9c000780 0x80>, <0x9c000a80 0x80>;
+ interrupt-parent = <&gic>;
+ interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>, /* EXT_INT0 */
+ <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>; /* EXT_INT1 */
+ };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 5069f552f..6b3bbe021 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2662,6 +2662,7 @@ S: Maintained
W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
+F: Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
F: drivers/clk/clk-sp7021.c
F: drivers/reset/reset-sunplus.c
--
2.33.1
Add clock driver for Sunplus SP7021 SoC.
Signed-off-by: Qin Jian <[email protected]>
---
MAINTAINERS | 1 +
drivers/clk/Kconfig | 9 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-sp7021.c | 770 +++++++++++++++++++++++++++++++++++++++
4 files changed, 781 insertions(+)
create mode 100644 drivers/clk/clk-sp7021.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 90ebb823f..5069f552f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2663,6 +2663,7 @@ W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
+F: drivers/clk/clk-sp7021.c
F: drivers/reset/reset-sunplus.c
F: include/dt-bindings/clock/sp-sp7021.h
F: include/dt-bindings/reset/sp-sp7021.h
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index c5b3dc973..1cd7ae7a3 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -334,6 +334,15 @@ config COMMON_CLK_VC5
This driver supports the IDT VersaClock 5 and VersaClock 6
programmable clock generators.
+config COMMON_CLK_SP7021
+ bool "Clock driver for Sunplus SP7021 SoC"
+ help
+ This driver supports the Sunplus SP7021 SoC clocks.
+ It implemented SP7021 PLLs/gate.
+ Not all features of the PLL are currently supported
+ by the driver.
+ This driver is selected automatically by platform config.
+
config COMMON_CLK_STM32MP157
def_bool COMMON_CLK && MACH_STM32MP157
help
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index e42312121..f15bb5070 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
obj-$(CONFIG_COMMON_CLK_SI514) += clk-si514.o
obj-$(CONFIG_COMMON_CLK_SI544) += clk-si544.o
obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o
+obj-$(CONFIG_COMMON_CLK_SP7021) += clk-sp7021.o
obj-$(CONFIG_COMMON_CLK_STM32F) += clk-stm32f4.o
obj-$(CONFIG_COMMON_CLK_STM32H7) += clk-stm32h7.o
obj-$(CONFIG_COMMON_CLK_STM32MP157) += clk-stm32mp1.o
diff --git a/drivers/clk/clk-sp7021.c b/drivers/clk/clk-sp7021.c
new file mode 100644
index 000000000..39ff84825
--- /dev/null
+++ b/drivers/clk/clk-sp7021.c
@@ -0,0 +1,770 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+//#define DEBUG
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <dt-bindings/clock/sp-sp7021.h>
+
+//#define TRACE pr_info("### %s:%d (%d)\n", __func__, __LINE__, (clk->reg - REG(4, 0)) / 4)
+#define TRACE
+
+#ifndef clk_readl
+#define clk_readl readl
+#define clk_writel writel
+#endif
+
+#define MASK_SET(shift, width, value) \
+({ \
+ u32 m = ((1 << (width)) - 1) << (shift); \
+ (m << 16) | (((value) << (shift)) & m); \
+})
+#define MASK_GET(shift, width, value) (((value) >> (shift)) & ((1 << (width)) - 1))
+
+#define REG(i) (pll_regs + (i) * 4)
+
+#define PLLA_CTL REG(7)
+#define PLLE_CTL REG(12)
+#define PLLF_CTL REG(13)
+#define PLLTV_CTL REG(14)
+#define PLLSYS_CTL REG(26)
+
+#define EXTCLK "extclk"
+
+/* speical div_width values for PLLTV/PLLA */
+#define DIV_TV 33
+#define DIV_A 34
+
+/* PLLTV parameters */
+enum {
+ SEL_FRA,
+ SDM_MOD,
+ PH_SEL,
+ NFRA,
+ DIVR,
+ DIVN,
+ DIVM,
+ P_MAX
+};
+
+struct sp_pll {
+ struct clk_hw hw;
+ void __iomem *reg;
+ spinlock_t *lock;
+ int pd_bit; /* power down bit idx */
+ int bp_bit; /* bypass bit idx */
+ unsigned long brate; /* base rate, FIXME: replace brate with muldiv */
+ int div_shift;
+ int div_width;
+ u32 p[P_MAX]; /* for hold PLLTV/PLLA parameters */
+};
+#define to_sp_pll(_hw) container_of(_hw, struct sp_pll, hw)
+
+#define clk_regs (moon_regs + 0x000) /* G0 ~ CLKEN */
+#define pll_regs (moon_regs + 0x200) /* G4 ~ PLL */
+static void __iomem *moon_regs;
+
+#define P_EXTCLK (1 << 16)
+static const char * const parents[] = {
+ "pllsys",
+ "extclk",
+};
+
+/* FIXME: parent clk incorrect cause clk_get_rate got error value */
+static const u32 gates[] = {
+ SYSTEM,
+ RTC,
+ IOCTL,
+ IOP,
+ OTPRX,
+ NOC,
+ BR,
+ RBUS_L00,
+ SPIFL,
+ SDCTRL0,
+ PERI0 | P_EXTCLK,
+ A926,
+ UMCTL2,
+ PERI1 | P_EXTCLK,
+
+ DDR_PHY0,
+ ACHIP,
+ STC0,
+ STC_AV0,
+ STC_AV1,
+ STC_AV2,
+ UA0 | P_EXTCLK,
+ UA1 | P_EXTCLK,
+ UA2 | P_EXTCLK,
+ UA3 | P_EXTCLK,
+ UA4 | P_EXTCLK,
+ HWUA | P_EXTCLK,
+ DDC0,
+ UADMA | P_EXTCLK,
+
+ CBDMA0,
+ CBDMA1,
+ SPI_COMBO_0,
+ SPI_COMBO_1,
+ SPI_COMBO_2,
+ SPI_COMBO_3,
+ AUD,
+ USBC0,
+ USBC1,
+ UPHY0,
+ UPHY1,
+
+ I2CM0,
+ I2CM1,
+ I2CM2,
+ I2CM3,
+ PMC,
+ CARD_CTL0,
+ CARD_CTL1,
+
+ CARD_CTL4,
+ BCH,
+ DDFCH,
+ CSIIW0,
+ CSIIW1,
+ MIPICSI0,
+ MIPICSI1,
+
+ HDMI_TX,
+ VPOST,
+
+ TGEN,
+ DMIX,
+ TCON,
+ INTERRUPT,
+
+ RGST,
+ GPIO,
+ RBUS_TOP,
+
+ MAILBOX,
+ SPIND,
+ I2C2CBUS,
+ SEC,
+ GPOST0,
+ DVE,
+
+ OSD0,
+ DISP_PWM,
+ UADBG,
+ DUMMY_MASTER,
+ FIO_CTL,
+ FPGA,
+ L2SW,
+ ICM,
+ AXI_GLOBAL,
+};
+static struct clk *clks[CLK_MAX];
+static struct clk_onecell_data clk_data;
+
+static DEFINE_SPINLOCK(plla_lock);
+static DEFINE_SPINLOCK(plle_lock);
+static DEFINE_SPINLOCK(pllf_lock);
+static DEFINE_SPINLOCK(pllsys_lock);
+static DEFINE_SPINLOCK(plltv_lock);
+
+#define _M 1000000UL
+#define F_27M (27 * _M)
+
+/******************************************** PLL_TV *******************************************/
+
+//#define PLLTV_STEP_DIR (?) /* Unit: HZ */
+
+/* TODO: set proper FVCO range */
+#define FVCO_MIN (100 * _M)
+#define FVCO_MAX (200 * _M)
+
+#define F_MIN (FVCO_MIN / 8)
+#define F_MAX (FVCO_MAX)
+
+static long plltv_integer_div(struct sp_pll *clk, unsigned long freq)
+{
+ /* valid m values: 27M must be divisible by m, 0 means end */
+ static const u32 m_table[] = {
+ 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32, 0
+ };
+ u32 m, n, r;
+#ifdef PLLTV_STEP_DIR
+ u32 step = (PLLTV_STEP_DIR > 0) ? PLLTV_STEP_DIR : -PLLTV_STEP_DIR;
+ int calc_times = 1000000 / step;
+#endif
+ unsigned long fvco, nf;
+
+ TRACE;
+
+ /* check freq */
+ if (freq < F_MIN) {
+ pr_warn("[%s:%d] freq:%lu < F_MIN:%lu, round up\n",
+ __func__, __LINE__, freq, F_MIN);
+ freq = F_MIN;
+ } else if (freq > F_MAX) {
+ pr_warn("[%s:%d] freq:%lu > F_MAX:%lu, round down\n",
+ __func__, __LINE__, freq, F_MAX);
+ freq = F_MAX;
+ }
+
+#ifdef PLLTV_STEP_DIR
+ if ((freq % step) != 0)
+ freq += step - (freq % step) + ((PLLTV_STEP_DIR > 0) ? 0 : PLLTV_STEP_DIR);
+#endif
+
+#ifdef PLLTV_STEP_DIR
+CALC:
+ if (!calc_times) {
+ pr_err("[%s:%d] freq:%lu out of recalc times\n", __func__, __LINE__, freq);
+ return -ETIMEOUT;
+ }
+#endif
+
+ /* DIVR 0~3 */
+ for (r = 0; r <= 3; r++) {
+ fvco = freq << r;
+ if (fvco <= FVCO_MAX)
+ break;
+ }
+
+ /* DIVM */
+ for (m = 0; m_table[m]; m++) {
+ nf = fvco * m_table[m];
+ n = nf / F_27M;
+ if ((n * F_27M) == nf)
+ break;
+ }
+ m = m_table[m];
+
+ if (!m) {
+#ifdef PLLTV_STEP_DIR
+ freq += PLLTV_STEP_DIR;
+ calc_times--;
+ goto CALC;
+#else
+ pr_err("[%s:%d] freq:%lu not found a valid setting\n", __func__, __LINE__, freq);
+ return -EINVAL;
+#endif
+ }
+
+ /* save parameters */
+ clk->p[SEL_FRA] = 0;
+ clk->p[DIVR] = r;
+ clk->p[DIVN] = n;
+ clk->p[DIVM] = m;
+
+ pr_info("[%s:%d] M:%u N:%u R:%u CKREF:%lu FVCO:%lu FCKOUT:%lu\n",
+ __func__, __LINE__, m, n, r, (fvco / m), fvco, freq);
+
+ return freq;
+}
+
+/* parameters for PLLTV fractional divider */
+/* FIXME: better parameter naming */
+static const u32 pt[][5] = {
+ /* conventional fractional */
+ {
+ 1, // factor
+ 5, // 5 * p0 (nint)
+ 1, // 1 * p0
+ F_27M, // F_27M / p0
+ 1, // p0 / p2
+ },
+ /* phase rotation */
+ {
+ 10, // factor
+ 54, // 5.4 * p0 (nint)
+ 2, // 0.2 * p0
+ F_27M / 10, // F_27M / p0
+ 5, // p0 / p2
+ },
+};
+static const u32 mods[] = { 91, 55 }; /* SDM_MOD mod values */
+
+static long plltv_fractional_div(struct sp_pll *clk, unsigned long freq)
+{
+ u32 m, r;
+ u32 nint, nfra;
+ u32 diff_min_quotient = 210000000, diff_min_remainder = 0;
+ u32 diff_min_sign = 0;
+ unsigned long fvco, nf, f, fout = 0;
+ int sdm, ph;
+
+ TRACE;
+ /* check freq */
+ if (freq < F_MIN) {
+ pr_warn("[%s:%d] freq:%lu < F_MIN:%lu, round up\n",
+ __func__, __LINE__, freq, F_MIN);
+ freq = F_MIN;
+ } else if (freq > F_MAX) {
+ pr_warn("[%s:%d] freq:%lu > F_MAX:%lu, round down\n",
+ __func__, __LINE__, freq, F_MAX);
+ freq = F_MAX;
+ }
+
+ /* DIVR 0~3 */
+ for (r = 0; r <= 3; r++) {
+ fvco = freq << r;
+ if (fvco <= FVCO_MAX)
+ break;
+ }
+ pr_info("freq:%lu fvco:%lu R:%u\n", freq, fvco, r);
+ f = F_27M >> r;
+
+ /* PH_SEL 1/0 */
+ for (ph = 1; ph >= 0; ph--) {
+ const u32 *pp = pt[ph];
+ u32 ms = 1;
+
+ /* SDM_MOD 0/1 */
+ for (sdm = 0; sdm <= 1; sdm++) {
+ u32 mod = mods[sdm];
+
+ /* DIVM 1~32 */
+ for (m = ms; m <= 32; m++) {
+ u32 diff_freq;
+ u32 diff_freq_quotient = 0, diff_freq_remainder = 0;
+ u32 diff_freq_sign = 0; /* 0:Positive number, 1:Negative number */
+
+ nf = fvco * m;
+ nint = nf / pp[3];
+
+ if (nint < pp[1])
+ continue;
+ if (nint > pp[1])
+ break;
+
+ nfra = (((nf % pp[3]) * mod * pp[4]) + (F_27M / 2)) / F_27M;
+ if (nfra)
+ diff_freq = (f * (nint + pp[2]) / pp[0]) -
+ (f * (mod - nfra) / mod / pp[4]);
+ else
+ diff_freq = (f * (nint) / pp[0]);
+
+ diff_freq_quotient = diff_freq / m;
+ diff_freq_remainder = ((diff_freq % m) * 1000) / m;
+
+ pr_info("m = %d N.f = %2d.%03d%03d, nfra = %d/%d fout = %u\n",
+ m, nint, (nfra * 1000) / mod,
+ (((nfra * 1000) % mod) * 1000) / mod,
+ nfra, mod, diff_freq_quotient);
+
+ if (freq > diff_freq_quotient) {
+ diff_freq_quotient = freq - diff_freq_quotient - 1;
+ diff_freq_remainder = 1000 - diff_freq_remainder;
+ diff_freq_sign = 1;
+ } else {
+ diff_freq_quotient = diff_freq_quotient - freq;
+ diff_freq_sign = 0;
+ }
+
+ if ((diff_min_quotient > diff_freq_quotient) ||
+ ((diff_min_quotient == diff_freq_quotient) &&
+ (diff_min_remainder > diff_freq_remainder))) {
+
+ /* found a closer freq, save parameters */
+ TRACE;
+ clk->p[SEL_FRA] = 1;
+ clk->p[SDM_MOD] = sdm;
+ clk->p[PH_SEL] = ph;
+ clk->p[NFRA] = nfra;
+ clk->p[DIVR] = r;
+ clk->p[DIVM] = m;
+
+ fout = diff_freq / m;
+ diff_min_quotient = diff_freq_quotient;
+ diff_min_remainder = diff_freq_remainder;
+ diff_min_sign = diff_freq_sign;
+ }
+ }
+ }
+ }
+
+ if (!fout) {
+ pr_err("[%s:%d] freq:%lu not found a valid setting\n", __func__, __LINE__, freq);
+ return -EINVAL;
+ }
+
+ //pr_info("MOD:%u PH_SEL:%u NFRA:%u M:%u R:%u\n",
+ // mods[clk->p[SDM_MOD]], clk->p[PH_SEL], clk->p[NFRA], clk->p[DIVM], clk->p[DIVR]);
+
+ pr_info("[%s:%d] real out:%lu/%lu Hz(%u, %u, sign %u)\n",
+ __func__, __LINE__, fout, freq,
+ diff_min_quotient, diff_min_remainder, diff_min_sign);
+
+ return fout;
+}
+
+static long plltv_div(struct sp_pll *clk, unsigned long freq)
+{
+ TRACE;
+ if (freq % 100)
+ return plltv_fractional_div(clk, freq);
+ else
+ return plltv_integer_div(clk, freq);
+}
+
+static void plltv_set_rate(struct sp_pll *clk)
+{
+ u32 reg;
+
+ //pr_info("MOD:%u PH_SEL:%u NFRA:%u M:%u R:%u\n",
+ // mods[clk->p[SDM_MOD]], clk->p[PH_SEL], clk->p[NFRA], clk->p[DIVM], clk->p[DIVR]);
+ reg = MASK_SET(1, 1, clk->p[SEL_FRA]);
+ reg |= MASK_SET(2, 1, clk->p[SDM_MOD]);
+ reg |= MASK_SET(4, 1, clk->p[PH_SEL]);
+ reg |= MASK_SET(6, 7, clk->p[NFRA]);
+ clk_writel(reg, clk->reg);
+
+ reg = MASK_SET(7, 2, clk->p[DIVR]);
+ clk_writel(reg, clk->reg + 4);
+
+ reg = MASK_SET(0, 8, clk->p[DIVN] - 1);
+ reg |= MASK_SET(8, 7, clk->p[DIVM] - 1);
+ clk_writel(reg, clk->reg + 8);
+}
+
+/******************************************** PLL_A ********************************************/
+
+/* from Q628_PLLs_REG_setting.xlsx */
+struct {
+ u32 rate;
+ u32 regs[5];
+} pa[] = {
+ {
+ .rate = 135475200,
+ .regs = {
+ 0x4801,
+ 0x02df,
+ 0x248f,
+ 0x0211,
+ 0x33e9
+ }
+ },
+ {
+ .rate = 147456000,
+ .regs = {
+ 0x4801,
+ 0x1adf,
+ 0x2490,
+ 0x0349,
+ 0x33e9
+ }
+ },
+ {
+ .rate = 196608000,
+ .regs = {
+ 0x4801,
+ 0x42ef,
+ 0x2495,
+ 0x01c6,
+ 0x33e9
+ }
+ },
+};
+
+static void plla_set_rate(struct sp_pll *clk)
+{
+ const u32 *pp = pa[clk->p[0]].regs;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pa->regs); i++) {
+ clk_writel(0xffff0000 | pp[i], clk->reg + (i * 4));
+ pr_info("%04x\n", pp[i]);
+ }
+}
+
+static long plla_round_rate(struct sp_pll *clk, unsigned long rate)
+{
+ int i = ARRAY_SIZE(pa);
+
+ while (--i) {
+ if (rate >= pa[i].rate)
+ break;
+ }
+ clk->p[0] = i;
+ return pa[i].rate;
+}
+
+/******************************************* SP_PLL ********************************************/
+
+static long sp_pll_calc_div(struct sp_pll *clk, unsigned long rate)
+{
+ u32 fbdiv;
+ u32 max = 1 << clk->div_width;
+
+ fbdiv = DIV_ROUND_CLOSEST(rate, clk->brate);
+ if (fbdiv > max)
+ fbdiv = max;
+ return fbdiv;
+}
+
+static long sp_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ long ret;
+
+ TRACE;
+ //pr_info("round_rate: %lu %lu\n", rate, *prate);
+
+ if (rate == *prate)
+ ret = *prate; /* bypass */
+ else if (clk->div_width == DIV_A) {
+ ret = plla_round_rate(clk, rate);
+ } else if (clk->div_width == DIV_TV) {
+ ret = plltv_div(clk, rate);
+ if (ret < 0)
+ ret = *prate;
+ } else
+ ret = sp_pll_calc_div(clk, rate) * clk->brate;
+
+ return ret;
+}
+
+static unsigned long sp_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long prate)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ u32 reg = clk_readl(clk->reg);
+ unsigned long ret;
+
+ //TRACE;
+ if (reg & BIT(clk->bp_bit))
+ ret = prate; /* bypass */
+ else if (clk->div_width == DIV_A) {
+ ret = pa[clk->p[0]].rate;
+ //reg = clk_readl(clk->reg + 12); // G4.10 K_SDM_A
+ } else if (clk->div_width == DIV_TV) {
+ u32 m, r, reg2;
+
+ r = MASK_GET(7, 2, clk_readl(clk->reg + 4));
+ reg2 = clk_readl(clk->reg + 8);
+ m = MASK_GET(8, 7, reg2) + 1;
+
+ if (reg & BIT(1)) { /* SEL_FRA */
+ /* fractional divider */
+ u32 sdm = MASK_GET(2, 1, reg);
+ u32 ph = MASK_GET(4, 1, reg);
+ u32 nfra = MASK_GET(6, 7, reg);
+ const u32 *pp = pt[ph];
+
+ ret = prate >> r;
+ ret = (ret * (pp[1] + pp[2]) / pp[0]) -
+ (ret * (mods[sdm] - nfra) / mods[sdm] / pp[4]);
+ ret /= m;
+ } else {
+ /* integer divider */
+ u32 n = MASK_GET(0, 8, reg2) + 1;
+
+ ret = (prate / m * n) >> r;
+ }
+ } else {
+ u32 fbdiv = MASK_GET(clk->div_shift, clk->div_width, reg) + 1;
+
+ ret = clk->brate * fbdiv;
+ }
+ //pr_info("recalc_rate: %lu -> %lu\n", prate, ret);
+
+ return ret;
+}
+
+static int sp_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ unsigned long flags;
+ u32 reg;
+
+ //TRACE;
+ pr_info("set_rate: %lu -> %lu\n", prate, rate);
+
+ spin_lock_irqsave(clk->lock, flags);
+
+ reg = BIT(clk->bp_bit + 16); /* HIWORD_MASK */
+
+ if (rate == prate)
+ reg |= BIT(clk->bp_bit); /* bypass */
+ else if (clk->div_width == DIV_A)
+ plla_set_rate(clk);
+ else if (clk->div_width == DIV_TV)
+ plltv_set_rate(clk);
+ else if (clk->div_width) {
+ u32 fbdiv = sp_pll_calc_div(clk, rate);
+
+ reg |= MASK_SET(clk->div_shift, clk->div_width, fbdiv - 1);
+ }
+
+ clk_writel(reg, clk->reg);
+
+ spin_unlock_irqrestore(clk->lock, flags);
+
+ return 0;
+}
+
+static int sp_pll_enable(struct clk_hw *hw)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ unsigned long flags;
+
+ TRACE;
+ spin_lock_irqsave(clk->lock, flags);
+ clk_writel(BIT(clk->pd_bit + 16) | BIT(clk->pd_bit), clk->reg); /* power up */
+ spin_unlock_irqrestore(clk->lock, flags);
+
+ return 0;
+}
+
+static void sp_pll_disable(struct clk_hw *hw)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ unsigned long flags;
+
+ TRACE;
+ spin_lock_irqsave(clk->lock, flags);
+ clk_writel(BIT(clk->pd_bit + 16), clk->reg); /* power down */
+ spin_unlock_irqrestore(clk->lock, flags);
+}
+
+static int sp_pll_is_enabled(struct clk_hw *hw)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+
+ return clk_readl(clk->reg) & BIT(clk->pd_bit);
+}
+
+static const struct clk_ops sp_pll_ops = {
+ .enable = sp_pll_enable,
+ .disable = sp_pll_disable,
+ .is_enabled = sp_pll_is_enabled,
+ .round_rate = sp_pll_round_rate,
+ .recalc_rate = sp_pll_recalc_rate,
+ .set_rate = sp_pll_set_rate
+};
+
+static const struct clk_ops sp_pll_sub_ops = {
+ .enable = sp_pll_enable,
+ .disable = sp_pll_disable,
+ .is_enabled = sp_pll_is_enabled,
+ .recalc_rate = sp_pll_recalc_rate,
+};
+
+struct clk *clk_register_sp_pll(const char *name, const char *parent,
+ void __iomem *reg, int pd_bit, int bp_bit,
+ unsigned long brate, int shift, int width,
+ spinlock_t *lock)
+{
+ struct sp_pll *pll;
+ struct clk *clk;
+ //unsigned long flags = 0;
+ struct clk_init_data initd = {
+ .name = name,
+ .parent_names = &parent,
+ .ops = (bp_bit >= 0) ? &sp_pll_ops : &sp_pll_sub_ops,
+ .num_parents = 1,
+ .flags = CLK_IGNORE_UNUSED
+ };
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ if (reg == PLLSYS_CTL)
+ initd.flags |= CLK_IS_CRITICAL;
+
+ pll->hw.init = &initd;
+ pll->reg = reg;
+ pll->pd_bit = pd_bit;
+ pll->bp_bit = bp_bit;
+ pll->brate = brate;
+ pll->div_shift = shift;
+ pll->div_width = width;
+ pll->lock = lock;
+
+ clk = clk_register(NULL, &pll->hw);
+ if (WARN_ON(IS_ERR(clk))) {
+ kfree(pll);
+ } else {
+ pr_info("%-20s%lu\n", name, clk_get_rate(clk));
+ clk_register_clkdev(clk, NULL, name);
+ }
+
+ return clk;
+}
+
+static void __init sp_clk_setup(struct device_node *np)
+{
+ int i, j;
+
+ pr_info("sp-clkc init\n");
+
+ moon_regs = of_iomap(np, 0);
+ if (WARN_ON(!moon_regs)) {
+ pr_warn("sp-clkc regs missing.\n");
+ return; // -EIO
+ }
+
+ /* TODO: PLLs initial */
+
+ /* PLL_A */
+ clks[PLL_A] = clk_register_sp_pll("plla", EXTCLK,
+ PLLA_CTL, 11, 12, 27000000, 0, DIV_A, &plla_lock);
+
+ /* PLL_E */
+ clks[PLL_E] = clk_register_sp_pll("plle", EXTCLK,
+ PLLE_CTL, 6, 2, 50000000, 0, 0, &plle_lock);
+ clks[PLL_E_2P5] = clk_register_sp_pll("plle_2p5", "plle",
+ PLLE_CTL, 13, -1, 2500000, 0, 0, &plle_lock);
+ clks[PLL_E_25] = clk_register_sp_pll("plle_25", "plle",
+ PLLE_CTL, 12, -1, 25000000, 0, 0, &plle_lock);
+ clks[PLL_E_112P5] = clk_register_sp_pll("plle_112p5", "plle",
+ PLLE_CTL, 11, -1, 112500000, 0, 0, &plle_lock);
+
+ /* PLL_F */
+ clks[PLL_F] = clk_register_sp_pll("pllf", EXTCLK,
+ PLLF_CTL, 0, 10, 13500000, 1, 4, &pllf_lock);
+
+ /* PLL_TV */
+ clks[PLL_TV] = clk_register_sp_pll("plltv", EXTCLK,
+ PLLTV_CTL, 0, 15, 27000000, 0, DIV_TV, &plltv_lock);
+ clks[PLL_TV_A] = clk_register_divider(NULL, "plltv_a", "plltv", 0,
+ PLLTV_CTL + 4, 5, 1,
+ CLK_DIVIDER_POWER_OF_TWO, &plltv_lock);
+ clk_register_clkdev(clks[PLL_TV_A], NULL, "plltv_a");
+
+ /* PLL_SYS */
+ clks[PLL_SYS] = clk_register_sp_pll("pllsys", EXTCLK,
+ PLLSYS_CTL, 10, 9, 13500000, 0, 4, &pllsys_lock);
+
+ /* gates */
+ for (i = 0; i < ARRAY_SIZE(gates); i++) {
+ char s[10];
+
+ j = gates[i] & 0xffff;
+ sprintf(s, "clken%02x", j);
+ clks[j] = clk_register_gate(NULL, s, parents[gates[i] >> 16], CLK_IGNORE_UNUSED,
+ clk_regs + (j >> 4) * 4, j & 0x0f,
+ CLK_GATE_HIWORD_MASK, NULL);
+ }
+
+ clk_data.clks = clks;
+ clk_data.clk_num = ARRAY_SIZE(clks);
+ of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
+}
+
+CLK_OF_DECLARE(sp_clkc, "sunplus,sp7021-clkc", sp_clk_setup);
+
+MODULE_AUTHOR("Qin Jian <[email protected]>");
+MODULE_DESCRIPTION("Sunplus SP7021 Clock Driver");
+MODULE_LICENSE("GPL v2");
--
2.33.1
Add documentation to describe Sunplus SP7021 clock driver bindings.
Signed-off-by: Qin Jian <[email protected]>
---
.../bindings/clock/sunplus,sp7021-clkc.yaml | 38 ++++++
MAINTAINERS | 2 +
include/dt-bindings/clock/sp-sp7021.h | 112 ++++++++++++++++++
3 files changed, 152 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
create mode 100644 include/dt-bindings/clock/sp-sp7021.h
diff --git a/Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml b/Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
new file mode 100644
index 000000000..1ce7e41d8
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/sunplus,sp7021-clkc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus SP7021 SoC Clock Controller Binding
+
+maintainers:
+ - Qin Jian <[email protected]>
+
+properties:
+ compatible:
+ const: sunplus,sp7021-clkc
+
+ "#clock-cells":
+ const: 1
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - "#clock-cells"
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ clkc: clkc@9c000000 {
+ compatible = "sunplus,sp7021-clkc";
+ #clock-cells = <1>;
+ reg = <0x9c000000 0x280>;
+ };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 6caffd6d0..90ebb823f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2661,8 +2661,10 @@ L: [email protected] (moderated for mon-subscribers)
S: Maintained
W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
+F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
F: drivers/reset/reset-sunplus.c
+F: include/dt-bindings/clock/sp-sp7021.h
F: include/dt-bindings/reset/sp-sp7021.h
ARM/Synaptics SoC support
diff --git a/include/dt-bindings/clock/sp-sp7021.h b/include/dt-bindings/clock/sp-sp7021.h
new file mode 100644
index 000000000..1ae9c4083
--- /dev/null
+++ b/include/dt-bindings/clock/sp-sp7021.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+#ifndef _DT_BINDINGS_CLOCK_SUNPLUS_SP7021_H
+#define _DT_BINDINGS_CLOCK_SUNPLUS_Sp7021_H
+
+#define XTAL 27000000
+
+/* plls */
+#define PLL_A 0
+#define PLL_E 1
+#define PLL_E_2P5 2
+#define PLL_E_25 3
+#define PLL_E_112P5 4
+#define PLL_F 5
+#define PLL_TV 6
+#define PLL_TV_A 7
+#define PLL_SYS 8
+
+/* gates: mo_clken0 ~ mo_clken9 */
+#define SYSTEM 0x10
+#define RTC 0x12
+#define IOCTL 0x13
+#define IOP 0x14
+#define OTPRX 0x15
+#define NOC 0x16
+#define BR 0x17
+#define RBUS_L00 0x18
+#define SPIFL 0x19
+#define SDCTRL0 0x1a
+#define PERI0 0x1b
+#define A926 0x1d
+#define UMCTL2 0x1e
+#define PERI1 0x1f
+
+#define DDR_PHY0 0x20
+#define ACHIP 0x22
+#define STC0 0x24
+#define STC_AV0 0x25
+#define STC_AV1 0x26
+#define STC_AV2 0x27
+#define UA0 0x28
+#define UA1 0x29
+#define UA2 0x2a
+#define UA3 0x2b
+#define UA4 0x2c
+#define HWUA 0x2d
+#define DDC0 0x2e
+#define UADMA 0x2f
+
+#define CBDMA0 0x30
+#define CBDMA1 0x31
+#define SPI_COMBO_0 0x32
+#define SPI_COMBO_1 0x33
+#define SPI_COMBO_2 0x34
+#define SPI_COMBO_3 0x35
+#define AUD 0x36
+#define USBC0 0x3a
+#define USBC1 0x3b
+#define UPHY0 0x3d
+#define UPHY1 0x3e
+
+#define I2CM0 0x40
+#define I2CM1 0x41
+#define I2CM2 0x42
+#define I2CM3 0x43
+#define PMC 0x4d
+#define CARD_CTL0 0x4e
+#define CARD_CTL1 0x4f
+
+#define CARD_CTL4 0x52
+#define BCH 0x54
+#define DDFCH 0x5b
+#define CSIIW0 0x5c
+#define CSIIW1 0x5d
+#define MIPICSI0 0x5e
+#define MIPICSI1 0x5f
+
+#define HDMI_TX 0x60
+#define VPOST 0x65
+
+#define TGEN 0x70
+#define DMIX 0x71
+#define TCON 0x7a
+#define INTERRUPT 0x7f
+
+#define RGST 0x80
+#define GPIO 0x83
+#define RBUS_TOP 0x84
+
+#define MAILBOX 0x96
+#define SPIND 0x9a
+#define I2C2CBUS 0x9b
+#define SEC 0x9d
+#define DVE 0x9e
+#define GPOST0 0x9f
+
+#define OSD0 0xa0
+#define DISP_PWM 0xa2
+#define UADBG 0xa3
+#define DUMMY_MASTER 0xa4
+#define FIO_CTL 0xa5
+#define FPGA 0xa6
+#define L2SW 0xa7
+#define ICM 0xa8
+#define AXI_GLOBAL 0xa9
+
+#define CLK_MAX 0xb0
+
+#endif
--
2.33.1
Add interrupt controller driver for Sunplus SP7021 SoC.
This is the interrupt controller in P-chip which collects all interrupt
sources in P-chip and routes them to parent interrupt controller in C-chip.
Signed-off-by: Qin Jian <[email protected]>
---
MAINTAINERS | 1 +
drivers/irqchip/Kconfig | 9 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-sp7021-intc.c | 279 ++++++++++++++++++++++++++++++
4 files changed, 290 insertions(+)
create mode 100644 drivers/irqchip/irq-sp7021-intc.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 6b3bbe021..febbd97bf 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2665,6 +2665,7 @@ F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
F: Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
F: drivers/clk/clk-sp7021.c
+F: drivers/irqchip/irq-sp7021-intc.c
F: drivers/reset/reset-sunplus.c
F: include/dt-bindings/clock/sp-sp7021.h
F: include/dt-bindings/reset/sp-sp7021.h
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index aca7b595c..6f0bc0871 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -602,4 +602,13 @@ config APPLE_AIC
Support for the Apple Interrupt Controller found on Apple Silicon SoCs,
such as the M1.
+config SUNPLUS_SP7021_INTC
+ bool "Sunplus SP7021 interrupt controller"
+ help
+ Support for the Sunplus SP7021 Interrupt Controller IP core.
+ SP7021 SoC has 2 Chips: C-Chip & P-Chip. This is used as a
+ chained controller, routed all interrupt source in P-Chip to
+ the primary controller on C-Chip.
+ This is selected automatically by platform config.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index f88cbf36a..75411f654 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -116,3 +116,4 @@ obj-$(CONFIG_MACH_REALTEK_RTL) += irq-realtek-rtl.o
obj-$(CONFIG_WPCM450_AIC) += irq-wpcm450-aic.o
obj-$(CONFIG_IRQ_IDT3243X) += irq-idt3243x.o
obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o
+obj-$(CONFIG_SUNPLUS_SP7021_INTC) += irq-sp7021-intc.o
diff --git a/drivers/irqchip/irq-sp7021-intc.c b/drivers/irqchip/irq-sp7021-intc.c
new file mode 100644
index 000000000..4c995d4e3
--- /dev/null
+++ b/drivers/irqchip/irq-sp7021-intc.c
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/io.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+
+#define SP_INTC_HWIRQ_MIN 0
+#define SP_INTC_HWIRQ_MAX 223
+
+#define SP_INTC_NR_IRQS (SP_INTC_HWIRQ_MAX - SP_INTC_HWIRQ_MIN + 1)
+#define SP_INTC_NR_GROUPS DIV_ROUND_UP(SP_INTC_NR_IRQS, 32)
+#define SP_INTC_REG_SIZE (SP_INTC_NR_GROUPS * 4)
+
+/* REG_GROUP_0 regs */
+#define REG_INTR_TYPE (sp_intc.g0)
+#define REG_INTR_POLARITY (REG_INTR_TYPE + SP_INTC_REG_SIZE)
+#define REG_INTR_PRIORITY (REG_INTR_POLARITY + SP_INTC_REG_SIZE)
+#define REG_INTR_MASK (REG_INTR_PRIORITY + SP_INTC_REG_SIZE)
+
+/* REG_GROUP_1 regs */
+#define REG_INTR_CLEAR (sp_intc.g1)
+#define REG_MASKED_EXT1 (REG_INTR_CLEAR + SP_INTC_REG_SIZE)
+#define REG_MASKED_EXT0 (REG_MASKED_EXT1 + SP_INTC_REG_SIZE)
+#define REG_INTR_GROUP (REG_INTR_CLEAR + 31 * 4)
+
+#define GROUP_MASK (BIT(SP_INTC_NR_GROUPS) - 1)
+#define GROUP_SHIFT_EXT1 (0)
+#define GROUP_SHIFT_EXT0 (8)
+
+/*
+ * When GPIO_INT0~7 set to edge trigger, doesn't work properly.
+ * WORKAROUND: change it to level trigger, and toggle the polarity
+ * at ACK/Handler to make the HW work.
+ */
+#define GPIO_INT0_HWIRQ 120
+#define GPIO_INT7_HWIRQ 127
+#define IS_GPIO_INT(irq) (((irq) >= GPIO_INT0_HWIRQ) && ((irq) <= GPIO_INT7_HWIRQ))
+/* state idx */
+enum {
+ _IS_EDGE = 0,
+ _IS_LOW,
+ _IS_ACTIVE
+};
+#define STATE_BIT(irq, idx) ((irq - GPIO_INT0_HWIRQ) * 3 + idx)
+#define ASSIGN_STATE(irq, idx, v) assign_bit(STATE_BIT(irq, idx), sp_intc.states, v)
+#define TEST_STATE(irq, idx) test_bit(STATE_BIT(irq, idx), sp_intc.states)
+
+static struct sp_intctl {
+ /*
+ * REG_GROUP_0: include type/polarity/priority/mask regs.
+ * REG_GROUP_1: include clear/masked_ext0/masked_ext1/group regs.
+ */
+ void __iomem *g0; // REG_GROUP_0 base
+ void __iomem *g1; // REG_GROUP_1 base
+
+ struct irq_domain *domain;
+ raw_spinlock_t lock;
+
+ /*
+ * store GPIO_INT states
+ * each interrupt has 3 states: is_edge, is_low, is_active
+ */
+ DECLARE_BITMAP(states, (GPIO_INT7_HWIRQ - GPIO_INT0_HWIRQ + 1) * 3);
+} sp_intc;
+
+static struct irq_chip sp_intc_chip;
+
+static void sp_intc_assign_bit(u32 hwirq, void __iomem *base, bool value)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&sp_intc.lock, flags);
+ __assign_bit(hwirq, base, value);
+ raw_spin_unlock_irqrestore(&sp_intc.lock, flags);
+}
+
+static void sp_intc_ack_irq(struct irq_data *d)
+{
+ u32 hwirq = d->hwirq;
+
+ if (unlikely(IS_GPIO_INT(hwirq) && TEST_STATE(hwirq, _IS_EDGE))) { // WORKAROUND
+ sp_intc_assign_bit(hwirq, REG_INTR_POLARITY, !TEST_STATE(hwirq, _IS_LOW));
+ ASSIGN_STATE(hwirq, _IS_ACTIVE, true);
+ }
+
+ sp_intc_assign_bit(hwirq, REG_INTR_CLEAR, 1);
+}
+
+static void sp_intc_mask_irq(struct irq_data *d)
+{
+ sp_intc_assign_bit(d->hwirq, REG_INTR_MASK, 0);
+}
+
+static void sp_intc_unmask_irq(struct irq_data *d)
+{
+ sp_intc_assign_bit(d->hwirq, REG_INTR_MASK, 1);
+}
+
+static int sp_intc_set_type(struct irq_data *d, unsigned int type)
+{
+ u32 hwirq = d->hwirq;
+ bool is_edge = !(type & IRQ_TYPE_LEVEL_MASK);
+ bool is_low = (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_EDGE_FALLING);
+
+ irq_set_handler_locked(d, is_edge ? handle_edge_irq : handle_level_irq);
+
+ if (unlikely(IS_GPIO_INT(hwirq) && is_edge)) { // WORKAROUND
+ /* store states */
+ ASSIGN_STATE(hwirq, _IS_EDGE, is_edge);
+ ASSIGN_STATE(hwirq, _IS_LOW, is_low);
+ ASSIGN_STATE(hwirq, _IS_ACTIVE, false);
+ /* change to level */
+ is_edge = false;
+ }
+
+ sp_intc_assign_bit(hwirq, REG_INTR_TYPE, is_edge);
+ sp_intc_assign_bit(hwirq, REG_INTR_POLARITY, is_low);
+
+ return 0;
+}
+
+static int sp_intc_get_ext_irq(int ext_num)
+{
+ void __iomem *base = ext_num ? REG_MASKED_EXT1 : REG_MASKED_EXT0;
+ u32 shift = ext_num ? GROUP_SHIFT_EXT1 : GROUP_SHIFT_EXT0;
+ u32 groups;
+ u32 pending_group;
+ u32 group;
+ u32 pending_irq;
+
+ groups = readl_relaxed(REG_INTR_GROUP);
+ pending_group = (groups >> shift) & GROUP_MASK;
+ if (!pending_group)
+ return -1;
+
+ group = fls(pending_group) - 1;
+ pending_irq = readl_relaxed(base + group * 4);
+ if (!pending_irq)
+ return -1;
+
+ return (group * 32) + fls(pending_irq) - 1;
+}
+
+static void sp_intc_handle_ext_cascaded(struct irq_desc *desc)
+{
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ int ext_num = (int)irq_desc_get_handler_data(desc);
+ int hwirq;
+
+ chained_irq_enter(chip, desc);
+
+ while ((hwirq = sp_intc_get_ext_irq(ext_num)) >= 0) {
+ if (unlikely(IS_GPIO_INT(hwirq) && TEST_STATE(hwirq, _IS_ACTIVE))) { // WORKAROUND
+ ASSIGN_STATE(hwirq, _IS_ACTIVE, false);
+ sp_intc_assign_bit(hwirq, REG_INTR_POLARITY, TEST_STATE(hwirq, _IS_LOW));
+ } else {
+ generic_handle_domain_irq(sp_intc.domain, hwirq);
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+#ifdef CONFIG_SMP
+static int sp_intc_set_affinity(struct irq_data *d, const struct cpumask *mask, bool force)
+{
+ return -EINVAL;
+}
+#endif
+
+static struct irq_chip sp_intc_chip = {
+ .name = "sp_intc",
+ .irq_ack = sp_intc_ack_irq,
+ .irq_mask = sp_intc_mask_irq,
+ .irq_unmask = sp_intc_unmask_irq,
+ .irq_set_type = sp_intc_set_type,
+#ifdef CONFIG_SMP
+ .irq_set_affinity = sp_intc_set_affinity,
+#endif
+};
+
+static int sp_intc_irq_domain_map(struct irq_domain *domain,
+ unsigned int irq, irq_hw_number_t hwirq)
+{
+ irq_set_chip_and_handler(irq, &sp_intc_chip, handle_level_irq);
+ irq_set_chip_data(irq, &sp_intc_chip);
+ irq_set_noprobe(irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops sp_intc_dm_ops = {
+ .xlate = irq_domain_xlate_twocell,
+ .map = sp_intc_irq_domain_map,
+};
+
+static int sp_intc_irq_map(struct device_node *node, int i)
+{
+ unsigned int irq;
+
+ irq = irq_of_parse_and_map(node, i);
+ if (!irq)
+ return -ENOENT;
+
+ irq_set_chained_handler_and_data(irq, sp_intc_handle_ext_cascaded, (void *)i);
+
+ return 0;
+}
+
+void sp_intc_set_ext(u32 hwirq, int ext_num)
+{
+ sp_intc_assign_bit(hwirq, REG_INTR_PRIORITY, !ext_num);
+}
+EXPORT_SYMBOL_GPL(sp_intc_set_ext);
+
+int __init sp_intc_init_dt(struct device_node *node, struct device_node *parent)
+{
+ int i, ret;
+
+ sp_intc.g0 = of_iomap(node, 0);
+ if (!sp_intc.g0)
+ return -ENXIO;
+
+ sp_intc.g1 = of_iomap(node, 1);
+ if (!sp_intc.g1) {
+ ret = -ENXIO;
+ goto out_unmap0;
+ }
+
+ /* initial regs */
+ for (i = 0; i < SP_INTC_NR_GROUPS; i++) {
+ /* all mask */
+ writel_relaxed(0, REG_INTR_MASK + i * 4);
+ /* all edge */
+ writel_relaxed(~0, REG_INTR_TYPE + i * 4);
+ /* all high-active */
+ writel_relaxed(0, REG_INTR_POLARITY + i * 4);
+ /* all EXT_INT0 */
+ writel_relaxed(~0, REG_INTR_PRIORITY + i * 4);
+ /* all clear */
+ writel_relaxed(~0, REG_INTR_CLEAR + i * 4);
+ }
+
+ sp_intc.domain = irq_domain_add_linear(node, SP_INTC_NR_IRQS,
+ &sp_intc_dm_ops, &sp_intc);
+ if (!sp_intc.domain) {
+ ret = -ENOMEM;
+ goto out_unmap1;
+ }
+
+ raw_spin_lock_init(&sp_intc.lock);
+
+ ret = sp_intc_irq_map(node, 0); // EXT_INT0
+ if (ret)
+ goto out_domain;
+
+ ret = sp_intc_irq_map(node, 1); // EXT_INT1
+ if (ret)
+ goto out_domain;
+
+ return 0;
+
+out_domain:
+ irq_domain_remove(sp_intc.domain);
+out_unmap1:
+ iounmap(sp_intc.g1);
+out_unmap0:
+ iounmap(sp_intc.g0);
+
+ return ret;
+}
+IRQCHIP_DECLARE(sp_intc, "sunplus,sp7021-intc", sp_intc_init_dt);
--
2.33.1
This introduces bindings for boards based Sunplus SP7021 SoC.
Signed-off-by: Qin Jian <[email protected]>
---
.../bindings/arm/sunplus,sp7021.yaml | 27 +++++++++++++++++++
MAINTAINERS | 7 +++++
2 files changed, 34 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
diff --git a/Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml b/Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
new file mode 100644
index 000000000..5b9985b73
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/sunplus,sp7021.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus SP7021 Boards Device Tree Bindings
+
+maintainers:
+ - qinjian <[email protected]>
+
+description: |
+ ARM platforms using Sunplus SP7021, an ARM Cortex A7 (4-cores) based SoC.
+ Wiki: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
+
+properties:
+ $nodename:
+ const: '/'
+ compatible:
+ oneOf:
+ - items:
+ - const: sunplus,sp7021-achip
+
+additionalProperties: true
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index e0bca0de0..6a5422f10 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2655,6 +2655,13 @@ F: drivers/clocksource/armv7m_systick.c
N: stm32
N: stm
+ARM/SUNPLUS SP7021 SOC SUPPORT
+M: Qin Jian <[email protected]>
+L: [email protected] (moderated for mon-subscribers)
+S: Maintained
+W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
+F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
+
ARM/Synaptics SoC support
M: Jisheng Zhang <[email protected]>
M: Sebastian Hesselbarth <[email protected]>
--
2.33.1
Add vendor prefix for Sunplus Technology Co., Ltd. (http://www.sunplus.com)
Signed-off-by: Qin Jian <[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 a867f7102..50d4ee5ac 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1131,6 +1131,8 @@ patternProperties:
description: Summit microelectronics
"^sunchip,.*":
description: Shenzhen Sunchip Technology Co., Ltd
+ "^sunplus,.*":
+ description: Sunplus Technology Co., Ltd.
"^SUNW,.*":
description: Sun Microsystems, Inc
"^supermicro,.*":
--
2.33.1