2021-10-29 15:29:36

by Marc Zyngier

[permalink] [raw]
Subject: Re: [PATCH v2 8/8] irqchip: Add support for Sunplus SP7021 interrupt controller

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.


2021-11-01 05:20:36

by Qin Jian

[permalink] [raw]
Subject: [PATCH v3 0/8] Add Sunplus SP7021 SoC Support

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


2021-11-01 05:20:40

by Qin Jian

[permalink] [raw]
Subject: [PATCH v3 3/8] dt-bindings: reset: Add bindings for SP7021 reset driver

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


2021-11-01 05:20:41

by Qin Jian

[permalink] [raw]
Subject: [PATCH v3 7/8] dt-bindings: interrupt-controller: Add bindings for SP7021 interrupt controller

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


2021-11-01 05:20:40

by Qin Jian

[permalink] [raw]
Subject: [PATCH v3 6/8] clk: Add Sunplus SP7021 clock driver

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


2021-11-01 05:20:47

by Qin Jian

[permalink] [raw]
Subject: [PATCH v3 5/8] dt-bindings: clock: Add bindings for SP7021 clock driver

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


2021-11-01 05:20:56

by Qin Jian

[permalink] [raw]
Subject: [PATCH v3 8/8] irqchip: Add Sunplus SP7021 interrupt controller driver

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


2021-11-01 05:20:59

by Qin Jian

[permalink] [raw]
Subject: [PATCH v3 2/8] dt-bindings: arm: sunplus: Add bindings for Sunplus SP7021 SoC boards

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


2021-11-01 05:21:01

by Qin Jian

[permalink] [raw]
Subject: [PATCH v3 1/8] dt-bindings: vendor-prefixes: Add Sunplus

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