Add System Error Interrupt support to Armada SoCs
Subsystems (6)
v3 cover Add System Error Interrupt support to Armada SoCs
The ICU is an IRQ chip found in Armada CP110. It currently has 207 wired inputs. Its purpose is to aggregate all CP interrupts and report them to the AP through MSIs. The ICU writes into GIC registers (AP side) by way of the interconnect. These interrupts can be of several groups:
- SecuRe (SR);
- Non-SecuRe (NSR);
- System Error Interrupts (SEI);
- RAM Error Interrupts (REI);
- ...
Each ICU wired interrupt can be of any of these groups. The group is encoded in the MSI payload.
Until now, only the non-secure interrupts (NSR) were handled by the ICU driver. Interrupts of another group could work by chance because the ICU driver does not erase all ATF configuration; it only erases the configuration for NSR interrupts.
This series aims at adding support for the System Error Interrupts (SEI). For this purpose, the ICU driver is a bit reworked to separate the ICU 'generic' configuration from the NSR-related handling. Then, the SEI driver (part of the GIC) is introduced and finally, support for SEI interrupts are also added to the ICU driver.
The SEI driver is a bit different than its cousin the GICP because it must handle MSIs from the CPs, as well as wired interrupts from the AP itself. MSIs and wired interrupts will automatically update two registers (GICP_SECR0/GICP_SECR1) that will trigger a single top-level interrupt (SPI #32).
As this is my first contribution in the IRQ subsystem I might have missed some specificities or misunderstood the API, please do not hesitate to correct me if I'm wrong.
Also, for the sake of understandability (and because I love ASCII art), this is a try to explain the ICU/SEI architecture:
+----------------------------------------------------------------------+
| |
| |
| SPIa SPIb SPIz SPI 32 |
| ^ ^ ^ ^ |
| | | . . . | | |
| | | | | |
| | | . . . | | |
| +------------------------+ +---------------------------------+ |
| | | | | | | | | |
| | | | | | | SEI | | |
| | | | . . . | | | ________|_______ | |
| | | | | | | /___SEI_SECR_____\ | |
| | |____|___________| | | / | \\ | |
| | \_GICP_SETSPI _/ | | / | \\ | |
| | || | | / ... | \\ | |
| | GICP || | | | | \\ | |
| +----------------||------+ +--|----------|------------||-----+ |
| || | | || |
| || | ... | || |
| || | | || |
| || | | || |
| \\_______ int 0 ... int 20 // |
| \_NSR__ \ // |
| \\ ____________________// |
| \\ /________SEI_________/ |
| AP 806 \\// |
| || |
+---------------------------------||-----------------------------------+
||
|| Interconnect
||\
||\\______
|| \______ <---> Others CP 110
||
+---------------------------------||-----------------------------------+
| || |
| CP 110 || |
| || |
| +-------------------------||------------------------+ |
| | || MSI | |
| | ICU || | |
| | /--------------/ \------\ | |
| | / /-------/ \ | |
| | / / / \ | |
| | / / / . . . \ | |
| | / / / \ | |
| | NSR NSR SEI NSR | |
| | | | | | | |
| +----^-------^-------^-----------------^------------+ |
| | | | | |
| | | | . . . | |
| | | | | |
| int 0 int 1 int 2 int 206 |
| |
| |
+----------------------------------------------------------------------+Thank you,
Miqu?l
Changes since v2:
=================
* Rebased on top of v4.18-rc1
platform-msi:
-------------
* New patch to allow using MSI tree domains.
irqchip/irq-mvebu-sei: add new driver for Marvell SEI
------------------------------------------------------
* Updated commit message with Marc comments
* Wrote two functions to fill ->irq_set_type() in the irq_chip structures, one accepting only rising edge interrupts (for MSI), another one accepting only high level interrupts (for wired IRQ).
* Changed the spin lock protecting the allocated SEIs bitmap into a mutex.
* Changed the bitmap allocation line to respect the actual number of MSIs instead of pretending having SEI_IRQ_COUNT (64) MSI available.
* I did not split the code to have one function per domain because it would duplicate a _lot_ of code. Requested some advices instead.
* Stopped using the fwnode when creating the AP (wired) IRQ domain.
* Implemented the AP IRQ domain ->match() hook.
* Used marvell,sei-xx-ranges properties to get the relevant IRQ numbers from DT. 'xx' is either 'ap' or 'cp'.
irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
---------------------------------------------------------------------
* Added a patch to ease the creation of tree domains (changes in the core).
* Changed the code accordingly to use tree domains.
* Created a couple of helpers to do the bitmap allocation/release.
* Removed the .offset_clr_a[hl] entries of the sei_subset_data structure to avoid confusion. These registers actually exist, but are not used here because the upper block (SEI) only supports edge-MSI and not level-MSI like the NSR one.
dt-bindings/interrupt-controller: update Marvell ICU bindings
--------------------------------------------------------------
* Explained better in the commit message that backward compatibility is not broken.
* Changed subnodes names to be 'interrupt-controller' as requested.
* Added a range associated to each sub-node (as well as in the DT).
* Replaced spaces by tabs.
* Merged the SEI's subnodes so that there is only one SEI node and no subnodes anymore.
Changes since v1:
=================
General
-------
* Spelling/function names/comments.
* Added Reviewed-by tags.
* Rebased on top of Marc Zyngier level-MSI series (tip:irq/core).
SEI
---
* Change the license for GPL-2.0 only in irq-mvebu-sei.c C file.
* Used alphabetic ordering when adding SEI driver in Makefile.
* Re-ordered register definitions by increasing offset.
* s/NB/COUNT/ in register definitions.
* avoid enabling all interrupt by default.
* fixed mask/unmask functions using the wrong hwirq number.
* removed hackish doorbell mechanism.
* Removed the ->xlate hook assigned for CP MSIs.
* Used devm_*() helpers.
* s/top_level_spi/parent_irq/ in probe.
* Added forgotten of_node_put(child).
* Reset the SEI registers before registering the IRQ domains.
* Introduced new DT property "marvell,sei-ranges" instead of using "reg" to declare the range of MSI interrupts vs. wired interrupts in the SEI subnodes.
* Finally did not change the ->alloc() about the fwspec->param[1] line (to be checked by Marc).
ICU
---
* Updated the ICU documentation so the legacy bindings are still documented somewhere.
* Added stable tags on the commit fixing the CP110 ICU node size.
* Removed the "syscon" compatible from the ICU node, instead the syscon is created at probe time.
* s/user data/private data/ in the title of commit "irqchip/irq-mvebu-icu: fix wrong user data retrieval"
Miquel Raynal (17):
platform-msi: allow creation of MSI domain without interrupt number dt-bindings/interrupt-controller: fix Marvell ICU length in the
example
arm64: dts: marvell: fix CP110 ICU node size irqchip/irq-mvebu-icu: fix wrong private data retrieval irqchip/irq-mvebu-icu: clarify the reset operation of configured
interrupts
irqchip/irq-mvebu-icu: switch to regmap irqchip/irq-mvebu-icu: make irq_domain local irqchip/irq-mvebu-icu: disociate ICU and NSR irqchip/irq-mvebu-icu: support ICU subnodes irqchip/irq-mvebu-sei: add new driver for Marvell SEI
arm64: marvell: enable SEI driver
irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI) dt-bindings/interrupt-controller: update Marvell ICU bindings dt-bindings/interrupt-controller: add documentation for Marvell SEI
controller
arm64: dts: marvell: add AP806 SEI subnode
arm64: dts: marvell: use new bindings for CP110 interrupts
arm64: dts: marvell: add CP110 ICU SEI subnode
.../bindings/interrupt-controller/marvell,icu.txt | 85 +++- .../bindings/interrupt-controller/marvell,sei.txt | 39 ++ arch/arm64/Kconfig.platforms | 1 + arch/arm64/boot/dts/marvell/armada-ap806.dtsi | 11 + arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 127 +++--- drivers/base/platform-msi.c | 2 +- drivers/irqchip/Kconfig | 3 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-mvebu-icu.c | 319 ++++++++++++--- drivers/irqchip/irq-mvebu-sei.c | 444 +++++++++++++++++++++ 10 files changed, 899 insertions(+), 133 deletions(-) create mode 100644 Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt create mode 100644 drivers/irqchip/irq-mvebu-sei.c
Signature · 1 line
2.14.1
PATCH v3 1/17 platform-msi: allow creation of MSI domain without interrupt number
platform_msi_alloc_priv_data() checks that a number of interrupts is always given. This extra-check has no real impact and just prevents uselessly the user to create an MSI tree domain: remove it.
--- drivers/base/platform-msi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/base/platform-msi.c +1 -1
@@ -203,7 +203,7 @@ platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,* accordingly (which would impact the max number of MSI * capable devices). */ -if (!dev->msi_domain || !write_msi_msg || !nvec || nvec >MAX_DEV_MSIS ) +if (!dev->msi_domain || !write_msi_msg || nvec >MAX_DEV_MSIS )return ERR_PTR (-EINVAL );if (dev->msi_domain->bus_token !=DOMAIN_BUS_PLATFORM_MSI ) {
Signature · 1 line
2.14.1
PATCH v3 1/17 platform-msi: allow creation of MSI domain without interrupt number
Quoted 15 lines · Miquel Raynal
platform_msi_alloc_priv_data()checks that a number of interrupts is always given. This extra-check has no real impact and just prevents uselessly the user to create an MSI tree domain: remove it.Signed-off-by Miquel Raynal <[email protected]>--- drivers/base/platform-msi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)drivers/base/platform-msi.c +0
-0
drivers/base/platform-msi.c platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec, · line 209
Quoted 8 lines
drivers/base/platform-msi.c +1
-1@@ -203,7 +203,7 @@ platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,* accordingly (which would impact the max number of MSI * capable devices). */ -if (!dev->msi_domain || !write_msi_msg || !nvec || nvec >MAX_DEV_MSIS ) +if (!dev->msi_domain || !write_msi_msg || nvec >MAX_DEV_MSIS )return ERR_PTR (-EINVAL );if (dev->msi_domain->bus_token !=DOMAIN_BUS_PLATFORM_MSI ) {
Huh... It's not that simple.
Yes, it allows you to get a tree via platform_msi_create_device_domain (assuming that's why you're changing it -- your commit message doesn't say much) but it also has some impact on the way msi_domain_prepare_irqs works (see how it is called from platform_msi_create_device_domain).
Importantly, the msi_prepare callback takes nvec as a parameter, and that ends up trickling down to the irqchip, or whatever will setup the MSI domain. Things like the GICv3 ITS do rely on that to carve out the LPI space that subsequently gets used to populate the domain.
So no, you can't do it like that. If you really want a tree, add a helper that does so.
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
PATCH v3 1/17 platform-msi: allow creation of MSI domain without interrupt number
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 12:12:04 +0100:
Quoted 22 lines · Miquel Raynal
Quoted 21 lines
platform_msi_alloc_priv_data()checks that a number of interrupts is always given. This extra-check has no real impact and just prevents uselessly the user to create an MSI tree domain: remove it.Signed-off-by Miquel Raynal <[email protected]>--- drivers/base/platform-msi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)drivers/base/platform-msi.c +1
-1@@ -203,7 +203,7 @@ platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,* accordingly (which would impact the max number of MSI * capable devices). */ -if (!dev->msi_domain || !write_msi_msg || !nvec || nvec >MAX_DEV_MSIS ) +if (!dev->msi_domain || !write_msi_msg || nvec >MAX_DEV_MSIS )return ERR_PTR (-EINVAL );
Quoted 8 lines
drivers/base/platform-msi.c platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec, · line 209
Quoted 2 lines
drivers/base/platform-msi.c +0
-0if (dev->msi_domain->bus_token !=DOMAIN_BUS_PLATFORM_MSI ) {Huh... It's not that simple.
Yes, it allows you to get a tree via
platform_msi_create_device_domain(assuming that's why you're changing it -- your commit message doesn't say much)
Indeed. That was exactly my intention.
Quoted 10 lines
but it also has some impact on the way
msi_domain_prepare_irqsworks (see how it is called fromplatform_msi_create_device_domain).Importantly, the
msi_preparecallback takes nvec as a parameter, and that ends up trickling down to the irqchip, or whatever will setup the MSI domain. Things like the GICv3 ITS do rely on that to carve out the LPI space that subsequently gets used to populate the domain.So no, you can't do it like that. If you really want a tree, add a helper that does so.
So if I understand correctly, what should be done is writting a new helper that would do something similar to platform_msi_create_device_domain(), but creating instead a tree domain and still giving msi_domain_prepare_irqs() a meaningful number (as nvec) that would be the maximum number of MSIs that could be allocated. Am I right here?
Thanks,
Miqu?l
PATCH v3 1/17 platform-msi: allow creation of MSI domain without interrupt number
Quoted 50 lines · Miquel Raynal
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 12:12:04 +0100:
Quoted 22 lines · Miquel Raynal
Quoted 21 lines
platform_msi_alloc_priv_data()checks that a number of interrupts is always given. This extra-check has no real impact and just prevents uselessly the user to create an MSI tree domain: remove it.Signed-off-by Miquel Raynal <[email protected]>--- drivers/base/platform-msi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)drivers/base/platform-msi.c +1
-1@@ -203,7 +203,7 @@ platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,* accordingly (which would impact the max number of MSI * capable devices). */ -if (!dev->msi_domain || !write_msi_msg || !nvec || nvec >MAX_DEV_MSIS ) +if (!dev->msi_domain || !write_msi_msg || nvec >MAX_DEV_MSIS )return ERR_PTR (-EINVAL );Quoted 8 lines
drivers/base/platform-msi.c platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec, · line 209
Quoted 2 lines
drivers/base/platform-msi.c +0
-0if (dev->msi_domain->bus_token !=DOMAIN_BUS_PLATFORM_MSI ) {Huh... It's not that simple.
Yes, it allows you to get a tree via
platform_msi_create_device_domain(assuming that's why you're changing it -- your commit message doesn't say much)Indeed. That was exactly my intention.
Quoted 10 lines
but it also has some impact on the way
msi_domain_prepare_irqsworks (see how it is called fromplatform_msi_create_device_domain).Importantly, the
msi_preparecallback takes nvec as a parameter, and that ends up trickling down to the irqchip, or whatever will setup the MSI domain. Things like the GICv3 ITS do rely on that to carve out the LPI space that subsequently gets used to populate the domain.So no, you can't do it like that. If you really want a tree, add a helper that does so.
Quoted 6 lines
So if I understand correctly, what should be done is writting a new helper that would do something similar to
platform_msi_create_device_domain(), but creating instead a tree domain and still givingmsi_domain_prepare_irqs()a meaningful number (as nvec) that would be the maximum number of MSIs that could be allocated. Am I right here?
What I have in mind is the following (untested) patch. Let me know if that works for you.
Thanks,
M.
Quoted 1 line
From
f9bc9e331503b0d7f44fe9771fedb8bcd4a2c979Mon Sep 17 00:00:00 2001
From: Marc Zyngier <[email protected]>
Date: Fri, 29 Jun 2018 15:29:11 +0100
Subject: [PATCH] genirq/msi: Allow creation of a tree-based irqdomain for
platform-msi
platform_msi_create_device_domain() always creates a revmap-based irqdomain, which has the drawback of requiring the number of MSIs that can be allocated ahead of time. This is not always possible, and we sometimes need to use a tree-based irqdomain instead.
Add a new platform_msi_create_device_tree_domain() helper to that effect.
--- drivers/base/platform-msi.c | 14 ++++++++------ include/linux/msi.h | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 11 deletions(-)
drivers/base/platform-msi.c +8 -6
@@ -321,11 +321,12 @@ void *platform_msi_get_host_data(struct irq_domain *domain)* Returns an irqdomain for @nvec interrupts */ struct irq_domain * -platform_msi_create_device_domain (struct device *dev, -unsigned int nvec, -irq_write_msi_msg_t write_msi_msg, -const struct irq_domain_ops *ops, -void *host_data) +__platform_msi_create_device_domain (struct device *dev, +unsigned int nvec, +bool is_tree, +irq_write_msi_msg_t write_msi_msg, +const struct irq_domain_ops *ops, +void *host_data) {struct platform_msi_priv_data *data;struct irq_domain *domain;
@@ -336,7 +337,8 @@ platform_msi_create_device_domain(struct device *dev,return NULL ; data->host_data = host_data; - domain =irq_domain_create_hierarchy (dev->msi_domain,0 , nvec, + domain =irq_domain_create_hierarchy (dev->msi_domain,0 , + is_tree ?0 : nvec, dev->fwnode, ops, data);if (!domain) goto free_priv;
include/linux/msi.h +12 -5
@@ -317,11 +317,18 @@ int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev,int msi_domain_populate_irqs (struct irq_domain *domain,struct device *dev,int virq,int nvec,msi_alloc_info_t *args);struct irq_domain * -platform_msi_create_device_domain (struct device *dev, -unsigned int nvec, -irq_write_msi_msg_t write_msi_msg, -const struct irq_domain_ops *ops, -void *host_data); +__platform_msi_create_device_domain (struct device *dev, +unsigned int nvec, +bool is_tree, +irq_write_msi_msg_t write_msi_msg, +const struct irq_domain_ops *ops, +void *host_data); + +#define platform_msi_create_device_domain (dev, nvec, write, ops, data) \ + __platform_msi_create_device_domain(dev, nvec, false, write, ops, data) +#define platform_msi_create_device_tree_domain (dev, nvec, write, ops, data) \ + __platform_msi_create_device_domain(dev, nvec, true, write, ops, data) +int platform_msi_domain_alloc (struct irq_domain *domain,unsigned int virq,unsigned int nr_irqs); void platform_msi_domain_free(struct irq_domain *domain, unsigned int virq,
Signature · 5 lines
2.17.1 -- Jazz is not dead, it just smell funny.
PATCH v3 1/17 platform-msi: allow creation of MSI domain without interrupt number
Hi Marc,
Marc Zyngier <[email protected]> wrote on Fri, 29 Jun 2018 15:38:31 +0100:
Quoted 59 lines
Quoted 50 lines · Miquel Raynal
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 12:12:04 +0100:
Quoted 22 lines · Miquel Raynal
Quoted 21 lines
platform_msi_alloc_priv_data()checks that a number of interrupts is always given. This extra-check has no real impact and just prevents uselessly the user to create an MSI tree domain: remove it.Signed-off-by Miquel Raynal <[email protected]>--- drivers/base/platform-msi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)drivers/base/platform-msi.c +1
-1@@ -203,7 +203,7 @@ platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,* accordingly (which would impact the max number of MSI * capable devices). */ -if (!dev->msi_domain || !write_msi_msg || !nvec || nvec >MAX_DEV_MSIS ) +if (!dev->msi_domain || !write_msi_msg || nvec >MAX_DEV_MSIS )return ERR_PTR (-EINVAL );Quoted 8 lines
drivers/base/platform-msi.c platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec, · line 209
Quoted 2 lines
drivers/base/platform-msi.c +0
-0if (dev->msi_domain->bus_token !=DOMAIN_BUS_PLATFORM_MSI ) {Huh... It's not that simple.
Yes, it allows you to get a tree via
platform_msi_create_device_domain(assuming that's why you're changing it -- your commit message doesn't say much)Indeed. That was exactly my intention.
Quoted 10 lines
but it also has some impact on the way
msi_domain_prepare_irqsworks (see how it is called fromplatform_msi_create_device_domain).Importantly, the
msi_preparecallback takes nvec as a parameter, and that ends up trickling down to the irqchip, or whatever will setup the MSI domain. Things like the GICv3 ITS do rely on that to carve out the LPI space that subsequently gets used to populate the domain.So no, you can't do it like that. If you really want a tree, add a helper that does so.
Quoted 6 lines
So if I understand correctly, what should be done is writting a new helper that would do something similar to
platform_msi_create_device_domain(), but creating instead a tree domain and still givingmsi_domain_prepare_irqs()a meaningful number (as nvec) that would be the maximum number of MSIs that could be allocated. Am I right here?
Quoted 2 lines
What I have in mind is the following (untested) patch. Let me know if that works for you.
Ok, that's really close to what I wrote this morning, cool. I was not sure that was the only thing to modify.
Thanks,
Miqu?l
PATCH v3 2/17 dt-bindings/interrupt-controller: fix Marvell ICU length in the example
In reply to Miquel RaynalICU size in CP110 is not 0x10 but at least 0x440 bytes long (from the specification).
--- Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt +1 -1
@@ -39,7 +39,7 @@ Example: icu: interrupt-controller at 1e0000 { compatible = "marvell,cp110-icu"; - reg = <0x1e0000 0x10>; + reg = <0x1e0000 0x440>; #interrupt-cells = <3>; interrupt-controller; msi-parent = <&gicp>;
Signature · 1 line
2.14.1
PATCH v3 3/17 arm64: dts: marvell: fix CP110 ICU node size
In reply to Miquel RaynalICU size in CP110 is not 0x10 but at least 0x440 bytes long (from the specification).
--- arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/arm64/boot/dts/marvell/armada-cp110.dtsi +1 -1
@@ -149,7 +149,7 @@ CP110_LABEL(icu): interrupt-controller at 1e0000 { compatible = "marvell,cp110-icu"; - reg = <0x1e0000 0x10>; + reg = <0x1e0000 0x440>; #interrupt-cells = <3>; interrupt-controller; msi-parent = <&gicp>;
Signature · 1 line
2.14.1
Gregory CLEMENT
maintainerPATCH v3 3/17 arm64: dts: marvell: fix CP110 ICU node size
Hi Miquel,
Quoted 7 lines · Miquel Raynal
ICU size in CP110 is not
0x10but at least0x440bytes long (from the specification).Fixes 6ef84a827c37 ("arm64: dts: marvell: enable GICP and ICU on Armada 7K/8K")Cc stable at vger.kernel.orgSigned-off-by Miquel Raynal <[email protected]>Reviewed-by Thomas Petazzoni <[email protected]>
This patch was part of the last PR from arm-soc for Linus and should be part of 4.18-rc3, so if there is new version you can drop it.
Thanks,
Gregory
Quoted 20 lines
--- arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)arch/arm64/boot/dts/marvell/armada-cp110.dtsi +1
-1@@ -149,7 +149,7 @@ CP110_LABEL(icu): interrupt-controller at 1e0000 { compatible = "marvell,cp110-icu"; - reg = <0x1e0000 0x10>; + reg = <0x1e0000 0x440>; #interrupt-cells = <3>; interrupt-controller; msi-parent = <&gicp>;Signature · 1 line
2.14.1
Signature · 3 lines
Gregory Clement, Bootlin (formerly Free Electrons) Embedded Linux and Kernel engineering http://bootlin.com
PATCH v3 3/17 arm64: dts: marvell: fix CP110 ICU node size
Hi Gregory,
Quoted 7 lines · Gregory CLEMENT
Hi Miquel,
Quoted 3 lines · Miquel Raynal
ICU size in CP110 is not
0x10but at least0x440bytes long (from the specification).
Quoted 8 lines
Quoted 4 lines
Fixes 6ef84a827c37 ("arm64: dts: marvell: enable GICP and ICU on Armada 7K/8K")Cc stable at vger.kernel.orgSigned-off-by Miquel Raynal <[email protected]>Reviewed-by Thomas Petazzoni <[email protected]>This patch was part of the last PR from arm-soc for Linus and should be part of 4.18-rc3, so if there is new version you can drop it.
Sure, thanks for the reminder.
Miqu?l
PATCH v3 4/17 irqchip/irq-mvebu-icu: fix wrong private data retrieval
In reply to Miquel RaynalThe irq_domain structure has an host_data pointer that just stores private data. It is meant to not be touched by the IRQ core. However, when it comes to MSI, the MSI layer adds its own private data there with a structure that also has a host_data pointer.
Because this IRQ domain is an MSI domain, to access private data we should do a d->host_data->host_data, also wrapped as 'platform_msi_get_host_data()'.
This bug was lying there silently because the 'icu' structure retrieved this way was just called by dev_err(), only producing a '(NULL device *):' output on the console.
--- drivers/irqchip/irq-mvebu-icu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/irqchip/irq-mvebu-icu.c +1 -1
@@ -105,7 +105,7 @@ static int mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type) { - struct mvebu_icu *icu = d->host_data; + struct mvebu_icu *icu = platform_msi_get_host_data(d);unsigned int icu_group;/* Check the count of the parameters in dt */
Signature · 1 line
2.14.1
PATCH v3 5/17 irqchip/irq-mvebu-icu: clarify the reset operation of configured interrupts
In reply to Miquel RaynalRewrite a small section to clarify the reset operation of interrupts already configured by ATF that we want to handle in the driver. This will simplify the introduction of System Error Interrupts support.
--- drivers/irqchip/irq-mvebu-icu.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-)
drivers/irqchip/irq-mvebu-icu.c +6 -2
@@ -258,8 +258,12 @@ static int mvebu_icu_probe(struct platform_device *pdev)* avoid unpredictable SPI assignments done by firmware. */ for (i =0 ; i <ICU_MAX_IRQS ; i++) { -u32 icu_int =readl_relaxed (icu->base +ICU_INT_CFG (i)); -if ((icu_int >>ICU_GROUP_SHIFT ) ==ICU_GRP_NSR ) +u32 icu_int, icu_grp; + + icu_int =readl_relaxed (icu->base +ICU_INT_CFG (i)); + icu_grp = icu_int >>ICU_GROUP_SHIFT ; + +if (icu_grp ==ICU_GRP_NSR )writel_relaxed (0x0 , icu->base +ICU_INT_CFG (i)); }
Signature · 1 line
2.14.1
PATCH v3 6/17 irqchip/irq-mvebu-icu: switch to regmap
In reply to Miquel RaynalBefore splitting the code to support multiple platform devices to be probed (one for the ICU, one per interrupt group), let's switch to regmap first by creating one in the ->probe().
--- drivers/irqchip/irq-mvebu-icu.c | 45 +++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-)
drivers/irqchip/irq-mvebu-icu.c +30 -15
@@ -18,6 +18,8 @@#include <linux/of_irq.h> #include <linux/of_platform.h> #include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> #include <dt-bindings/interrupt-controller/mvebu-icu.h>
@@ -38,7 +40,7 @@struct mvebu_icu {struct irq_chip irq_chip; -void __iomem *base; +struct regmap *regmap;struct irq_domain *domain;struct device *dev;atomic_t initialized;
@@ -56,10 +58,10 @@ static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)return ;/* Set Clear/Set ICU SPI message address in AP */ -writel_relaxed (msg[0 ].address_hi, icu->base +ICU_SETSPI_NSR_AH ); -writel_relaxed (msg[0 ].address_lo, icu->base +ICU_SETSPI_NSR_AL ); -writel_relaxed (msg[1 ].address_hi, icu->base +ICU_CLRSPI_NSR_AH ); -writel_relaxed (msg[1 ].address_lo, icu->base +ICU_CLRSPI_NSR_AL ); +regmap_write (icu->regmap,ICU_SETSPI_NSR_AH , msg[0 ].address_hi); +regmap_write (icu->regmap,ICU_SETSPI_NSR_AL , msg[0 ].address_lo); +regmap_write (icu->regmap,ICU_CLRSPI_NSR_AH , msg[1 ].address_hi); +regmap_write (icu->regmap,ICU_CLRSPI_NSR_AL , msg[1 ].address_lo); }static void mvebu_icu_write_msg (struct msi_desc *desc,struct msi_msg *msg)
@@ -82,7 +84,7 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg) icu_int =0 ; } -writel_relaxed (icu_int, icu->base +ICU_INT_CFG (d->hwirq)); +regmap_write (icu->regmap,ICU_INT_CFG (d->hwirq), icu_int);/* * The SATA unit has 2 ports, and a dedicated ICU entry per
@@ -94,10 +96,10 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)* configured (regardless of which port is actually in use). */ if (d->hwirq ==ICU_SATA0_ICU_ID || d->hwirq ==ICU_SATA1_ICU_ID ) { -writel_relaxed (icu_int, - icu->base +ICU_INT_CFG (ICU_SATA0_ICU_ID )); -writel_relaxed (icu_int, - icu->base +ICU_INT_CFG (ICU_SATA1_ICU_ID )); +regmap_write (icu->regmap,ICU_INT_CFG (ICU_SATA0_ICU_ID ), + icu_int); +regmap_write (icu->regmap,ICU_INT_CFG (ICU_SATA1_ICU_ID ), + icu_int); } }
@@ -204,12 +206,20 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = { .free = mvebu_icu_irq_domain_free, }; +static struct regmap_config mvebu_icu_regmap_config = { + .reg_bits =32 , + .val_bits =32 , + .reg_stride =4 , + .name ="mvebu_icu" , +}; +static int mvebu_icu_probe (struct platform_device *pdev) {struct mvebu_icu *icu;struct device_node *node = pdev->dev.of_node;struct device_node *gicp_dn;struct resource *res; +void __iomem *regs;int i; icu = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_icu),
@@ -220,12 +230,17 @@ static int mvebu_icu_probe(struct platform_device *pdev) icu->dev = &pdev->dev; res =platform_get_resource (pdev,IORESOURCE_MEM ,0 ); - icu->base =devm_ioremap_resource (&pdev->dev, res); -if (IS_ERR (icu->base)) { + regs =devm_ioremap_resource (&pdev->dev, res); +if (IS_ERR (regs)) {dev_err (&pdev->dev,"Failed to map icu base address.\n" ); -return PTR_ERR (icu->base); +return PTR_ERR (regs); } + icu->regmap =devm_regmap_init_mmio (icu->dev, regs, + &mvebu_icu_regmap_config); +if (IS_ERR (icu->regmap)) +return PTR_ERR (icu->regmap); + icu->irq_chip.name =devm_kasprintf (&pdev->dev,GFP_KERNEL ,"ICU.%x" , (unsigned int )res->start);
@@ -260,11 +275,11 @@ static int mvebu_icu_probe(struct platform_device *pdev)for (i =0 ; i <ICU_MAX_IRQS ; i++) {u32 icu_int, icu_grp; - icu_int =readl_relaxed (icu->base +ICU_INT_CFG (i)); +regmap_read (icu->regmap,ICU_INT_CFG (i), &icu_int); icu_grp = icu_int >>ICU_GROUP_SHIFT ;if (icu_grp ==ICU_GRP_NSR ) -writel_relaxed (0x0 , icu->base +ICU_INT_CFG (i)); +regmap_write (icu->regmap,ICU_INT_CFG (i),0 ); } icu->domain =
Signature · 1 line
2.14.1
PATCH v3 6/17 irqchip/irq-mvebu-icu: switch to regmap
Quoted 3 lines · Miquel Raynal
Before splitting the code to support multiple platform devices to be probed (one for the ICU, one per interrupt group), let's switch to regmap first by creating one in the ->probe().
What's the benefit of doing so? I assume that has to do with supporting multiple devices that share an MMIO range?
Quoted 30 lines
Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 45 +++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-)drivers/irqchip/irq-mvebu-icu.c +3
-1@@ -18,6 +18,8 @@#include <linux/of_irq.h> #include <linux/of_platform.h> #include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> #include <dt-bindings/interrupt-controller/mvebu-icu.h> @@ -38,7 +40,7 @@struct mvebu_icu {struct irq_chip irq_chip; -void __iomem *base; +struct regmap *regmap;struct irq_domain *domain;struct device *dev;atomic_t initialized;@@ -56,10 +58,10 @@ static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)return ;
drivers/irqchip/irq-mvebu-icu.c static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg) · line 64
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +4
-4/* Set Clear/Set ICU SPI message address in AP */ -writel_relaxed (msg[0 ].address_hi, icu->base +ICU_SETSPI_NSR_AH ); -writel_relaxed (msg[0 ].address_lo, icu->base +ICU_SETSPI_NSR_AL ); -writel_relaxed (msg[1 ].address_hi, icu->base +ICU_CLRSPI_NSR_AH ); -writel_relaxed (msg[1 ].address_lo, icu->base +ICU_CLRSPI_NSR_AL ); +regmap_write (icu->regmap,ICU_SETSPI_NSR_AH , msg[0 ].address_hi); +regmap_write (icu->regmap,ICU_SETSPI_NSR_AL , msg[0 ].address_lo); +regmap_write (icu->regmap,ICU_CLRSPI_NSR_AH , msg[1 ].address_hi); +regmap_write (icu->regmap,ICU_CLRSPI_NSR_AL , msg[1 ].address_lo);
Isn't this a change in the way we write things to the MMIO registers? You're now trading a writel_relaxed for a writel, plus some locking...
Talking about which: Are you always in a context where you can take that lock? The bit of documentation I've just read seems to imply that the default lock is a mutex. Is that always safe? My guess is that it isn't, and any callback that can end-up here after having taken something like the desc lock is going to blow in your face.
Have you tried lockdep?
Quoted 74 lines
drivers/irqchip/irq-mvebu-icu.c +21
-8}static void mvebu_icu_write_msg (struct msi_desc *desc,struct msi_msg *msg)@@ -82,7 +84,7 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg) icu_int =0 ; } -writel_relaxed (icu_int, icu->base +ICU_INT_CFG (d->hwirq)); +regmap_write (icu->regmap,ICU_INT_CFG (d->hwirq), icu_int);/* * The SATA unit has 2 ports, and a dedicated ICU entry per @@ -94,10 +96,10 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)* configured (regardless of which port is actually in use). */ if (d->hwirq ==ICU_SATA0_ICU_ID || d->hwirq ==ICU_SATA1_ICU_ID ) { -writel_relaxed (icu_int, - icu->base +ICU_INT_CFG (ICU_SATA0_ICU_ID )); -writel_relaxed (icu_int, - icu->base +ICU_INT_CFG (ICU_SATA1_ICU_ID )); +regmap_write (icu->regmap,ICU_INT_CFG (ICU_SATA0_ICU_ID ), + icu_int); +regmap_write (icu->regmap,ICU_INT_CFG (ICU_SATA1_ICU_ID ), + icu_int); } }@@ -204,12 +206,20 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = { .free = mvebu_icu_irq_domain_free, }; +static struct regmap_config mvebu_icu_regmap_config = { + .reg_bits =32 , + .val_bits =32 , + .reg_stride =4 , + .name ="mvebu_icu" , +}; +static int mvebu_icu_probe (struct platform_device *pdev) {struct mvebu_icu *icu;struct device_node *node = pdev->dev.of_node;struct device_node *gicp_dn;struct resource *res; +void __iomem *regs;int i; icu = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_icu),@@ -220,12 +230,17 @@ static int mvebu_icu_probe(struct platform_device *pdev) icu->dev = &pdev->dev; res =platform_get_resource (pdev,IORESOURCE_MEM ,0 ); - icu->base =devm_ioremap_resource (&pdev->dev, res); -if (IS_ERR (icu->base)) { + regs =devm_ioremap_resource (&pdev->dev, res); +if (IS_ERR (regs)) {dev_err (&pdev->dev,"Failed to map icu base address.\n" ); -return PTR_ERR (icu->base); +return PTR_ERR (regs); } + icu->regmap =devm_regmap_init_mmio (icu->dev, regs, + &mvebu_icu_regmap_config); +if (IS_ERR (icu->regmap)) +return PTR_ERR (icu->regmap); + icu->irq_chip.name =devm_kasprintf (&pdev->dev,GFP_KERNEL ,"ICU.%x" , (unsigned int )res->start);@@ -260,11 +275,11 @@ static int mvebu_icu_probe(struct platform_device *pdev)for (i =0 ; i <ICU_MAX_IRQS ; i++) {u32 icu_int, icu_grp;
drivers/irqchip/irq-mvebu-icu.c static int mvebu_icu_probe(struct platform_device *pdev) · line 285
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +2
-2- icu_int =readl_relaxed (icu->base +ICU_INT_CFG (i)); +regmap_read (icu->regmap,ICU_INT_CFG (i), &icu_int); icu_grp = icu_int >>ICU_GROUP_SHIFT ;if (icu_grp ==ICU_GRP_NSR ) -writel_relaxed (0x0 , icu->base +ICU_INT_CFG (i)); +regmap_write (icu->regmap,ICU_INT_CFG (i),0 ); } icu->domain =
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
PATCH v3 6/17 irqchip/irq-mvebu-icu: switch to regmap
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 13:05:05 +0100:
Quoted 7 lines · Miquel Raynal
Quoted 3 lines
Before splitting the code to support multiple platform devices to be probed (one for the ICU, one per interrupt group), let's switch to regmap first by creating one in the ->probe().
What's the benefit of doing so? I assume that has to do with supporting multiple devices that share an MMIO range?
Yes, the ICU subnodes will share the same MMIO range.
Quoted 32 lines
Quoted 32 lines
Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 45 +++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-)drivers/irqchip/irq-mvebu-icu.c +3
-1@@ -18,6 +18,8 @@#include <linux/of_irq.h> #include <linux/of_platform.h> #include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> #include <dt-bindings/interrupt-controller/mvebu-icu.h> @@ -38,7 +40,7 @@struct mvebu_icu {struct irq_chip irq_chip; -void __iomem *base; +struct regmap *regmap;struct irq_domain *domain;struct device *dev;atomic_t initialized;@@ -56,10 +58,10 @@ static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)return ;
Quoted 12 lines
drivers/irqchip/irq-mvebu-icu.c static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg) · line 64
Quoted 9 lines
drivers/irqchip/irq-mvebu-icu.c +4
-4/* Set Clear/Set ICU SPI message address in AP */ -writel_relaxed (msg[0 ].address_hi, icu->base +ICU_SETSPI_NSR_AH ); -writel_relaxed (msg[0 ].address_lo, icu->base +ICU_SETSPI_NSR_AL ); -writel_relaxed (msg[1 ].address_hi, icu->base +ICU_CLRSPI_NSR_AH ); -writel_relaxed (msg[1 ].address_lo, icu->base +ICU_CLRSPI_NSR_AL ); +regmap_write (icu->regmap,ICU_SETSPI_NSR_AH , msg[0 ].address_hi); +regmap_write (icu->regmap,ICU_SETSPI_NSR_AL , msg[0 ].address_lo); +regmap_write (icu->regmap,ICU_CLRSPI_NSR_AH , msg[1 ].address_hi); +regmap_write (icu->regmap,ICU_CLRSPI_NSR_AL , msg[1 ].address_lo);Isn't this a change in the way we write things to the MMIO registers? You're now trading a
writel_relaxedfor a writel, plus some locking...
Is the "writel_relaxed" -> "writel" thing really an issue?
Quoted 8 lines
Talking about which: Are you always in a context where you can take that lock? The bit of documentation I've just read seems to imply that the default lock is a mutex. Is that always safe? My guess is that it isn't, and any callback that can end-up here after having taken something like the desc lock is going to blow in your face.
Have you tried lockdep?
Just did -- thanks for pointing it, it failed once the overheat interrupt fired. I'm not sure if it is because of the regmap-locking mechanism. There is definitely something to fix there, but I don't know what for now; I'll come back on it.
[for reference: logs below]
Thanks,
Miqu?l
---
[ 91.128615]
[ 91.130120] ================================
[ 91.134408] WARNING: inconsistent lock state
[ 91.138698] 4.18.0-rc1-00047-g7f7c805c2b69-dirty #1580 Not tainted
[ 91.144905] --------------------------------
[ 91.149193] inconsistent {HARDIRQ-ON-W} -> {IN-HARDIRQ-W} usage.
[ 91.155227] swapper/0/0 [HC1[1]:SC0[0]:HE0:SE1] takes:
[ 91.160388] (____ptrval____) (&tz->lock){?.+.}, at: thermal_zone_get_temp+0x60/0x158
[ 91.168186] {HARDIRQ-ON-W} state was registered at:
[ 91.173089] lock_acquire+0x44/0x60
[ 91.176683] __mutex_lock+0x74/0x880
[ 91.180362] mutex_lock_nested+0x1c/0x28
[ 91.184390] thermal_zone_of_sensor_register+0x198/0x250
[ 91.189812] devm_thermal_zone_of_sensor_register+0x5c/0xc0
[ 91.195498] armada_thermal_probe+0x220/0x5f0
[ 91.199963] platform_drv_probe+0x50/0xb0
[ 91.204078] driver_probe_device+0x224/0x308
[ 91.208454] __driver_attach+0xe8/0xf0
[ 91.212309] bus_for_each_dev+0x68/0xc8
[ 91.216248] driver_attach+0x20/0x28
[ 91.219926] bus_add_driver+0x108/0x228
[ 91.223866] driver_register+0x60/0x110
[ 91.227806] __platform_driver_register+0x44/0x50
[ 91.232622] armada_thermal_driver_init+0x18/0x20
[ 91.237436] do_one_initcall+0x58/0x170
[ 91.241378] kernel_init_freeable+0x1d4/0x27c
[ 91.245843] kernel_init+0x10/0x108
[ 91.249434] ret_from_fork+0x10/0x18
[ 91.253111] irq event stamp: 61312
[ 91.256530] hardirqs last enabled at (61309): [<ffff000008086ab0>] arch_cpu_idle+0x10/0x20
[ 91.264918] hardirqs last disabled at (61310): [<ffff000008083134>] el1_irq+0x74/0x130
[ 91.272871] softirqs last enabled at (61312): [<ffff0000080b7fa0>] _local_bh_enable+0x20/0x40
[ 91.281520] softirqs last disabled at (61311): [<ffff0000080b8398>] irq_enter+0x58/0x78
[ 91.289559]
[ 91.289559] other info that might help us debug this:
[ 91.296114] Possible unsafe locking scenario:
[ 91.296114]
[ 91.302058] CPU0
[ 91.304514] ----
[ 91.306970] lock(&tz->lock);
[ 91.310041] <Interrupt>
[ 91.312670] lock(&tz->lock);
[ 91.315916]
[ 91.315916] *** DEADLOCK ***
[ 91.315916]
[ 91.321863] no locks held by swapper/0/0.
[ 91.325890]
[ 91.325890] stack backtrace:
[ 91.330269] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 4.18.0-rc1-00047-g7f7c805c2b69-dirty #1580
[ 91.339091] Hardware name: Marvell Armada 7040 DB board (DT)
[ 91.344775] Call trace:
[ 91.347234] dump_backtrace+0x0/0x1a0
[ 91.350913] show_stack+0x14/0x20
[ 91.354244] dump_stack+0xb8/0xf4
[ 91.357574] print_usage_bug.part.24+0x264/0x27c
[ 91.362211] mark_lock+0x150/0x6b0
[ 91.365628] __lock_acquire+0x6d0/0x18f8
[ 91.369568] lock_acquire+0x44/0x60
[ 91.373073] __mutex_lock+0x74/0x880
[ 91.376666] mutex_lock_nested+0x1c/0x28
[ 91.380606] thermal_zone_get_temp+0x60/0x158
[ 91.384982] thermal_zone_device_update.part.4+0x34/0xe0
[ 91.390318] thermal_zone_device_update+0x28/0x38
[ 91.395043] armada_overheat_isr+0xb0/0xb8
[ 91.399159] __handle_irq_event_percpu+0x9c/0x128
[ 91.403883] handle_irq_event_percpu+0x34/0x88
[ 91.408346] handle_irq_event+0x48/0x78
[ 91.412201] handle_fasteoi_irq+0xa0/0x180
[ 91.416316] generic_handle_irq+0x24/0x38
[ 91.420346] mvebu_sei_handle_cascade_irq+0xc8/0x180
[ 91.425332] generic_handle_irq+0x24/0x38
[ 91.429360] __handle_domain_irq+0x5c/0xb8
[ 91.433473] gic_handle_irq+0x58/0xb0
[ 91.437151] el1_irq+0xb4/0x130
[ 91.440306] arch_cpu_idle+0x14/0x20
[ 91.443898] default_idle_call+0x18/0x30
[ 91.447840] do_idle+0x1c4/0x278
[ 91.451082] cpu_startup_entry+0x24/0x28
[ 91.455023] rest_init+0x254/0x268
[ 91.458440] start_kernel+0x3f0/0x41c
[ 91.462126] thermal thermal_zone5: critical temperature reached (54 C), shutting downPATCH v3 6/17 irqchip/irq-mvebu-icu: switch to regmap
Quoted 7 lines · Miquel Raynal
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 13:05:05 +0100:
Quoted 1 line · Miquel Raynal
Quoted 8 lines
Quoted 6 lines
Quoted 3 lines
Before splitting the code to support multiple platform devices to be probed (one for the ICU, one per interrupt group), let's switch to regmap first by creating one in the ->probe().
What's the benefit of doing so? I assume that has to do with supporting multiple devices that share an MMIO range?
Yes, the ICU subnodes will share the same MMIO range.
So, one MMIO range, shared by multiple devices managed by the same driver. Why the complexity?
Quoted 46 lines
Quoted 33 lines
Quoted 33 lines
Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 45 +++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-)drivers/irqchip/irq-mvebu-icu.c +3
-1@@ -18,6 +18,8 @@#include <linux/of_irq.h> #include <linux/of_platform.h> #include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> #include <dt-bindings/interrupt-controller/mvebu-icu.h> @@ -38,7 +40,7 @@struct mvebu_icu {struct irq_chip irq_chip; -void __iomem *base; +struct regmap *regmap;struct irq_domain *domain;struct device *dev;atomic_t initialized;@@ -56,10 +58,10 @@ static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)return ;Quoted 12 lines
drivers/irqchip/irq-mvebu-icu.c static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg) · line 64
Quoted 9 lines
drivers/irqchip/irq-mvebu-icu.c +4
-4/* Set Clear/Set ICU SPI message address in AP */ -writel_relaxed (msg[0 ].address_hi, icu->base +ICU_SETSPI_NSR_AH ); -writel_relaxed (msg[0 ].address_lo, icu->base +ICU_SETSPI_NSR_AL ); -writel_relaxed (msg[1 ].address_hi, icu->base +ICU_CLRSPI_NSR_AH ); -writel_relaxed (msg[1 ].address_lo, icu->base +ICU_CLRSPI_NSR_AL ); +regmap_write (icu->regmap,ICU_SETSPI_NSR_AH , msg[0 ].address_hi); +regmap_write (icu->regmap,ICU_SETSPI_NSR_AL , msg[0 ].address_lo); +regmap_write (icu->regmap,ICU_CLRSPI_NSR_AH , msg[1 ].address_hi); +regmap_write (icu->regmap,ICU_CLRSPI_NSR_AL , msg[1 ].address_lo);Isn't this a change in the way we write things to the MMIO registers? You're now trading a
writel_relaxedfor a writel, plus some locking...
Quoted 1 line
Is the "
writel_relaxed" -> "writel" thing really an issue?
If you're happy with system-wide barriers (dsb sy) being issued, synchronising unrelated accesses, and generally slowing down the whole system in a completely unnecessary way, then there is absolutely no issue whatsoever. Performance is completely overrated anyway, let's embrace slow computing.
Quoted 8 lines
Quoted 8 lines
Talking about which: Are you always in a context where you can take that lock? The bit of documentation I've just read seems to imply that the default lock is a mutex. Is that always safe? My guess is that it isn't, and any callback that can end-up here after having taken something like the desc lock is going to blow in your face.
Quoted 6 lines
Quoted 1 line
Have you tried lockdep?
Just did -- thanks for pointing it, it failed once the overheat interrupt fired. I'm not sure if it is because of the regmap-locking mechanism. There is definitely something to fix there, but I don't know what for now; I'll come back on it.
Well, that's interesting:
Quoted 7 lines
[ 91.376666] mutex_lock_nested+0x1c/0x28 [ 91.380606] thermal_zone_get_temp+0x60/0x158 [ 91.384982] thermal_zone_device_update.part.4+0x34/0xe0 [ 91.390318] thermal_zone_device_update+0x28/0x38 [ 91.395043] armada_overheat_isr+0xb0/0xb8 [ 91.399159] __handle_irq_event_percpu+0x9c/0x128 [ 91.403883] handle_irq_event_percpu+0x34/0x88
Quoted 8 lines
[ 91.408346] handle_irq_event+0x48/0x78 [ 91.412201] handle_fasteoi_irq+0xa0/0x180 [ 91.416316] generic_handle_irq+0x24/0x38 [ 91.420346] mvebu_sei_handle_cascade_irq+0xc8/0x180 [ 91.425332] generic_handle_irq+0x24/0x38 [ 91.429360] __handle_domain_irq+0x5c/0xb8 [ 91.433473] gic_handle_irq+0x58/0xb0 [ 91.437151] el1_irq+0xb4/0x130
Taking a mutex in an interrupt handler is... great! On the other hand, that armada_overheat_isr function doesn't seem to exist in 4.18-rc2, so that's absolutely fine.
Nonetheless, regmap usage in this context is still suspect, and my gut feeling is that you really don't need it at all.
Thanks,
M.
Signature · 1 line
Jazz is not dead, it just smell funny.
PATCH v3 6/17 irqchip/irq-mvebu-icu: switch to regmap
Hi Marc,
Marc Zyngier <[email protected]> wrote on Fri, 29 Jun 2018 18:17:21 +0100:
Quoted 13 lines
Quoted 11 lines · Miquel Raynal
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 13:05:05 +0100:
Quoted 5 lines · Miquel Raynal
Quoted 3 lines
Before splitting the code to support multiple platform devices to be probed (one for the ICU, one per interrupt group), let's switch to regmap first by creating one in the ->probe().
Quoted 7 lines
Quoted 4 lines
Quoted 2 lines
What's the benefit of doing so? I assume that has to do with supporting multiple devices that share an MMIO range?
Yes, the ICU subnodes will share the same MMIO range.
So, one MMIO range, shared by multiple devices managed by the same driver. Why the complexity?
Mmmmh... one point :)
Quoted 47 lines
Quoted 47 lines
Quoted 46 lines
Quoted 33 lines
Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 45 +++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-)drivers/irqchip/irq-mvebu-icu.c +3
-1@@ -18,6 +18,8 @@#include <linux/of_irq.h> #include <linux/of_platform.h> #include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> #include <dt-bindings/interrupt-controller/mvebu-icu.h> @@ -38,7 +40,7 @@struct mvebu_icu {struct irq_chip irq_chip; -void __iomem *base; +struct regmap *regmap;struct irq_domain *domain;struct device *dev;atomic_t initialized;@@ -56,10 +58,10 @@ static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)return ;drivers/irqchip/irq-mvebu-icu.c static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg) · line 64
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +4
-4/* Set Clear/Set ICU SPI message address in AP */ -writel_relaxed (msg[0 ].address_hi, icu->base +ICU_SETSPI_NSR_AH ); -writel_relaxed (msg[0 ].address_lo, icu->base +ICU_SETSPI_NSR_AL ); -writel_relaxed (msg[1 ].address_hi, icu->base +ICU_CLRSPI_NSR_AH ); -writel_relaxed (msg[1 ].address_lo, icu->base +ICU_CLRSPI_NSR_AL ); +regmap_write (icu->regmap,ICU_SETSPI_NSR_AH , msg[0 ].address_hi); +regmap_write (icu->regmap,ICU_SETSPI_NSR_AL , msg[0 ].address_lo); +regmap_write (icu->regmap,ICU_CLRSPI_NSR_AH , msg[1 ].address_hi); +regmap_write (icu->regmap,ICU_CLRSPI_NSR_AL , msg[1 ].address_lo);Isn't this a change in the way we write things to the MMIO registers? You're now trading a
writel_relaxedfor a writel, plus some locking...
Quoted 7 lines
Quoted 1 line
Is the "
writel_relaxed" -> "writel" thing really an issue?If you're happy with system-wide barriers (dsb sy) being issued, synchronising unrelated accesses, and generally slowing down the whole system in a completely unnecessary way, then there is absolutely no issue whatsoever. Performance is completely overrated anyway, let's embrace slow computing.
8-D
Quoted 34 lines
Quoted 9 lines
Quoted 9 lines
Talking about which: Are you always in a context where you can take that lock? The bit of documentation I've just read seems to imply that the default lock is a mutex. Is that always safe? My guess is that it isn't, and any callback that can end-up here after having taken something like the desc lock is going to blow in your face.
Quoted 6 lines
Quoted 1 line
Have you tried lockdep?
Just did -- thanks for pointing it, it failed once the overheat interrupt fired. I'm not sure if it is because of the regmap-locking mechanism. There is definitely something to fix there, but I don't know what for now; I'll come back on it.
Well, that's interesting:
Quoted 7 lines
[ 91.376666] mutex_lock_nested+0x1c/0x28 [ 91.380606] thermal_zone_get_temp+0x60/0x158 [ 91.384982] thermal_zone_device_update.part.4+0x34/0xe0 [ 91.390318] thermal_zone_device_update+0x28/0x38 [ 91.395043] armada_overheat_isr+0xb0/0xb8 [ 91.399159] __handle_irq_event_percpu+0x9c/0x128 [ 91.403883] handle_irq_event_percpu+0x34/0x88Quoted 8 lines
[ 91.408346] handle_irq_event+0x48/0x78 [ 91.412201] handle_fasteoi_irq+0xa0/0x180 [ 91.416316] generic_handle_irq+0x24/0x38 [ 91.420346] mvebu_sei_handle_cascade_irq+0xc8/0x180 [ 91.425332] generic_handle_irq+0x24/0x38 [ 91.429360] __handle_domain_irq+0x5c/0xb8 [ 91.433473] gic_handle_irq+0x58/0xb0 [ 91.437151] el1_irq+0xb4/0x130
Quoted 3 lines
Taking a mutex in an interrupt handler is... great! On the other hand, that
armada_overheat_isrfunction doesn't seem to exist in 4.18-rc2, so that's absolutely fine.
I had no troubles with NSRs so I wanted to check with SEIs too.
The only device using SEIs for now is this thermal driver which I just contributed, it is still under review. The ISR is making a thermal-core call to update on an overheat situation. This call is synchronous and the core checks for the current temperature, but first locks its tz->lock mutex, which is kind of bad in interrupt context. I just realized it. Sad.
Quoted 3 lines
Nonetheless, regmap usage in this context is still suspect, and my gut feeling is that you really don't need it at all.
You are right on this and I probably over-looked at the initial problem. I'm pretty sure I can get rid of it as long as only one driver touches this MMIO region.
Thanks for all your thoughts, I'll resend a version next week.
Thank you very much,
Miqu?l
PATCH v3 7/17 irqchip/irq-mvebu-icu: make irq_domain local
In reply to Miquel RaynalMake the current MSI irq_domain local to ease the split between ICU platform device code and NSR platform device code.
--- drivers/irqchip/irq-mvebu-icu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
drivers/irqchip/irq-mvebu-icu.c +3 -3
@@ -41,7 +41,6 @@struct mvebu_icu {struct irq_chip irq_chip;struct regmap *regmap; -struct irq_domain *domain;struct device *dev;atomic_t initialized; };
@@ -218,6 +217,7 @@ static int mvebu_icu_probe(struct platform_device *pdev)struct mvebu_icu *icu;struct device_node *node = pdev->dev.of_node;struct device_node *gicp_dn; +struct irq_domain *irq_domain;struct resource *res;void __iomem *regs;int i;
@@ -282,11 +282,11 @@ static int mvebu_icu_probe(struct platform_device *pdev)regmap_write (icu->regmap,ICU_INT_CFG (i),0 ); } - icu->domain = + irq_domain =platform_msi_create_device_domain (&pdev->dev,ICU_MAX_IRQS , mvebu_icu_write_msg, &mvebu_icu_domain_ops, icu); -if (!icu->domain) { +if (!irq_domain) {dev_err (&pdev->dev,"Failed to create ICU domain\n" );return -ENOMEM ; }
Signature · 1 line
2.14.1
PATCH v3 7/17 irqchip/irq-mvebu-icu: make irq_domain local
Quoted 30 lines · Miquel Raynal
Make the current MSI
irq_domainlocal to ease the split between ICU platform device code and NSR platform device code.Signed-off-by Miquel Raynal <[email protected]>Reviewed-by Thomas Petazzoni <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)drivers/irqchip/irq-mvebu-icu.c +1
-1@@ -41,7 +41,6 @@struct mvebu_icu {struct irq_chip irq_chip;struct regmap *regmap; -struct irq_domain *domain;struct device *dev;atomic_t initialized; };@@ -218,6 +217,7 @@ static int mvebu_icu_probe(struct platform_device *pdev)struct mvebu_icu *icu;struct device_node *node = pdev->dev.of_node;struct device_node *gicp_dn; +struct irq_domain *irq_domain;struct resource *res;void __iomem *regs;int i;
drivers/irqchip/irq-mvebu-icu.c static int mvebu_icu_probe(struct platform_device *pdev) · line 288
Quoted 8 lines
drivers/irqchip/irq-mvebu-icu.c +1
-1@@ -282,11 +282,11 @@ static int mvebu_icu_probe(struct platform_device *pdev)regmap_write (icu->regmap,ICU_INT_CFG (i),0 ); } - icu->domain = + irq_domain =platform_msi_create_device_domain (&pdev->dev,ICU_MAX_IRQS , mvebu_icu_write_msg, &mvebu_icu_domain_ops, icu);
nit: this really hurts my eyes. Please put the = sign and the function call on a single line. I really don't care if it checkpatch is having a fit because of that.
drivers/irqchip/irq-mvebu-icu.c static int mvebu_icu_probe(struct platform_device *pdev) · line 292
Quoted 6 lines
drivers/irqchip/irq-mvebu-icu.c +1
-1-if (!icu->domain) { +if (!irq_domain) {dev_err (&pdev->dev,"Failed to create ICU domain\n" );return -ENOMEM ; }
But looking at the next patch, you might as well fold the two. On its own, this patches is pretty pointless.
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
PATCH v3 7/17 irqchip/irq-mvebu-icu: make irq_domain local
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 13:10:05 +0100:
Quoted 34 lines · Miquel Raynal
Quoted 33 lines
Make the current MSI
irq_domainlocal to ease the split between ICU platform device code and NSR platform device code.Signed-off-by Miquel Raynal <[email protected]>Reviewed-by Thomas Petazzoni <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)drivers/irqchip/irq-mvebu-icu.c +1
-1@@ -41,7 +41,6 @@struct mvebu_icu {struct irq_chip irq_chip;struct regmap *regmap; -struct irq_domain *domain;struct device *dev;atomic_t initialized; };@@ -218,6 +217,7 @@ static int mvebu_icu_probe(struct platform_device *pdev)struct mvebu_icu *icu;struct device_node *node = pdev->dev.of_node;struct device_node *gicp_dn; +struct irq_domain *irq_domain;struct resource *res;void __iomem *regs;int i;@@ -282,11 +282,11 @@ static int mvebu_icu_probe(struct platform_device *pdev)regmap_write (icu->regmap,ICU_INT_CFG (i),0 ); }
Quoted 9 lines
drivers/irqchip/irq-mvebu-icu.c static int mvebu_icu_probe(struct platform_device *pdev) · line 288
Quoted 5 lines
drivers/irqchip/irq-mvebu-icu.c +1
-1- icu->domain = + irq_domain =platform_msi_create_device_domain (&pdev->dev,ICU_MAX_IRQS , mvebu_icu_write_msg, &mvebu_icu_domain_ops, icu);nit: this really hurts my eyes. Please put the = sign and the function call on a single line. I really don't care if it checkpatch is having a fit because of that.
I also dislike. Will change.
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c static int mvebu_icu_probe(struct platform_device *pdev) · line 292
Quoted 7 lines
drivers/irqchip/irq-mvebu-icu.c +1
-1-if (!icu->domain) { +if (!irq_domain) {dev_err (&pdev->dev,"Failed to create ICU domain\n" );return -ENOMEM ; }But looking at the next patch, you might as well fold the two. On its own, this patches is pretty pointless.
Most often people ask to split logical changes as much as possible. But I have no problem squashing these two patches.
Thanks,
Miqu?l
PATCH v3 8/17 irqchip/irq-mvebu-icu: disociate ICU and NSR
In reply to Miquel RaynalNSR (non-secure interrupts) are handled in the ICU driver like if there was only this type of interrupt in the ICU. Change this behavior to prepare the introduction of SEI (System Error Interrupts) support by moving the NSR code in a separate function. This is done under the form of a 'probe' function to ease future migration to NSR/SEI being platform devices part of the ICU.
--- drivers/irqchip/irq-mvebu-icu.c | 58 +++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 25 deletions(-)
drivers/irqchip/irq-mvebu-icu.c +33 -25
@@ -205,6 +205,37 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = { .free = mvebu_icu_irq_domain_free, }; +static int mvebu_icu_subset_probe (struct platform_device *pdev) +{ +struct device_node *msi_parent_dn; +struct irq_domain *irq_domain; +struct mvebu_icu *icu; + + icu =dev_get_drvdata (&pdev->dev); +if (!icu) +return -ENODEV ; + + pdev->dev.msi_domain =of_msi_get_domain (&pdev->dev, pdev->dev.of_node, +DOMAIN_BUS_PLATFORM_MSI ); +if (!pdev->dev.msi_domain) +return -EPROBE_DEFER ; + + msi_parent_dn =irq_domain_get_of_node (pdev->dev.msi_domain); +if (!msi_parent_dn) +return -ENODEV ; + + irq_domain =platform_msi_create_device_domain (&pdev->dev,ICU_MAX_IRQS , + mvebu_icu_write_msg, + &mvebu_icu_domain_ops, + icu); +if (!irq_domain) { +dev_err (&pdev->dev,"Failed to create ICU MSI domain\n" ); +return -ENOMEM ; + } + +return 0 ; +} +static struct regmap_config mvebu_icu_regmap_config = { .reg_bits =32 , .val_bits =32 ,
@@ -215,9 +246,6 @@ static struct regmap_config mvebu_icu_regmap_config = {static int mvebu_icu_probe (struct platform_device *pdev) {struct mvebu_icu *icu; -struct device_node *node = pdev->dev.of_node; -struct device_node *gicp_dn; -struct irq_domain *irq_domain;struct resource *res;void __iomem *regs;int i;
@@ -255,19 +283,6 @@ static int mvebu_icu_probe(struct platform_device *pdev) icu->irq_chip.irq_set_affinity = irq_chip_set_affinity_parent;#endif -/* -* We're probed after MSI domains have been resolved, so force -* resolution here. -*/ - pdev->dev.msi_domain =of_msi_get_domain (&pdev->dev, node, -DOMAIN_BUS_PLATFORM_MSI ); -if (!pdev->dev.msi_domain) -return -EPROBE_DEFER ; - - gicp_dn =irq_domain_get_of_node (pdev->dev.msi_domain); -if (!gicp_dn) -return -ENODEV ; -/* * Clean all ICU interrupts with type SPI_NSR, required to * avoid unpredictable SPI assignments done by firmware.
@@ -282,16 +297,9 @@ static int mvebu_icu_probe(struct platform_device *pdev)regmap_write (icu->regmap,ICU_INT_CFG (i),0 ); } - irq_domain = -platform_msi_create_device_domain (&pdev->dev,ICU_MAX_IRQS , - mvebu_icu_write_msg, - &mvebu_icu_domain_ops, icu); -if (!irq_domain) { -dev_err (&pdev->dev,"Failed to create ICU domain\n" ); -return -ENOMEM ; - } +platform_set_drvdata (pdev, icu); -return 0 ; +return mvebu_icu_subset_probe (pdev); }static const struct of_device_id mvebu_icu_of_match[] = {
Signature · 1 line
2.14.1
PATCH v3 8/17 irqchip/irq-mvebu-icu: disociate ICU and NSR
Quoted 88 lines · Miquel Raynal
NSR (non-secure interrupts) are handled in the ICU driver like if there was only this type of interrupt in the ICU. Change this behavior to prepare the introduction of SEI (System Error Interrupts) support by moving the NSR code in a separate function. This is done under the form of a 'probe' function to ease future migration to NSR/SEI being platform devices part of the ICU.
Signed-off-by Miquel Raynal <[email protected]>Reviewed-by Thomas Petazzoni <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 58 +++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 25 deletions(-)drivers/irqchip/irq-mvebu-icu.c +31
-16@@ -205,6 +205,37 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = { .free = mvebu_icu_irq_domain_free, }; +static int mvebu_icu_subset_probe (struct platform_device *pdev) +{ +struct device_node *msi_parent_dn; +struct irq_domain *irq_domain; +struct mvebu_icu *icu; + + icu =dev_get_drvdata (&pdev->dev); +if (!icu) +return -ENODEV ; + + pdev->dev.msi_domain =of_msi_get_domain (&pdev->dev, pdev->dev.of_node, +DOMAIN_BUS_PLATFORM_MSI ); +if (!pdev->dev.msi_domain) +return -EPROBE_DEFER ; + + msi_parent_dn =irq_domain_get_of_node (pdev->dev.msi_domain); +if (!msi_parent_dn) +return -ENODEV ; + + irq_domain =platform_msi_create_device_domain (&pdev->dev,ICU_MAX_IRQS , + mvebu_icu_write_msg, + &mvebu_icu_domain_ops, + icu); +if (!irq_domain) { +dev_err (&pdev->dev,"Failed to create ICU MSI domain\n" ); +return -ENOMEM ; + } + +return 0 ; +} +static struct regmap_config mvebu_icu_regmap_config = { .reg_bits =32 , .val_bits =32 ,@@ -215,9 +246,6 @@ static struct regmap_config mvebu_icu_regmap_config = {static int mvebu_icu_probe (struct platform_device *pdev) {struct mvebu_icu *icu; -struct device_node *node = pdev->dev.of_node; -struct device_node *gicp_dn; -struct irq_domain *irq_domain;struct resource *res;void __iomem *regs;int i;@@ -255,19 +283,6 @@ static int mvebu_icu_probe(struct platform_device *pdev) icu->irq_chip.irq_set_affinity = irq_chip_set_affinity_parent;#endif -/* -* We're probed after MSI domains have been resolved, so force -* resolution here. -*/ - pdev->dev.msi_domain =of_msi_get_domain (&pdev->dev, node, -DOMAIN_BUS_PLATFORM_MSI ); -if (!pdev->dev.msi_domain) -return -EPROBE_DEFER ; - - gicp_dn =irq_domain_get_of_node (pdev->dev.msi_domain); -if (!gicp_dn) -return -ENODEV ; -/* * Clean all ICU interrupts with type SPI_NSR, required to * avoid unpredictable SPI assignments done by firmware. @@ -282,16 +297,9 @@ static int mvebu_icu_probe(struct platform_device *pdev)regmap_write (icu->regmap,ICU_INT_CFG (i),0 ); }
drivers/irqchip/irq-mvebu-icu.c static int mvebu_icu_probe(struct platform_device *pdev) · line 300
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +1
-8- irq_domain = -platform_msi_create_device_domain (&pdev->dev,ICU_MAX_IRQS , - mvebu_icu_write_msg, - &mvebu_icu_domain_ops, icu); -if (!irq_domain) { -dev_err (&pdev->dev,"Failed to create ICU domain\n" ); -return -ENOMEM ; - } +platform_set_drvdata (pdev, icu);
What is the upshot of passing this icu pointer as part of the platform device structure instead of simply passing it as a parameter? Is anything else using it?
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
PATCH v3 8/17 irqchip/irq-mvebu-icu: disociate ICU and NSR
Hi Marc,
[...]
Quoted 20 lines
Quoted 20 lines
drivers/irqchip/irq-mvebu-icu.c +0
-13-/* -* We're probed after MSI domains have been resolved, so force -* resolution here. -*/ - pdev->dev.msi_domain =of_msi_get_domain (&pdev->dev, node, -DOMAIN_BUS_PLATFORM_MSI ); -if (!pdev->dev.msi_domain) -return -EPROBE_DEFER ; - - gicp_dn =irq_domain_get_of_node (pdev->dev.msi_domain); -if (!gicp_dn) -return -ENODEV ; -/* * Clean all ICU interrupts with type SPI_NSR, required to * avoid unpredictable SPI assignments done by firmware. @@ -282,16 +297,9 @@ static int mvebu_icu_probe(struct platform_device *pdev)regmap_write (icu->regmap,ICU_INT_CFG (i),0 ); }
Quoted 13 lines
drivers/irqchip/irq-mvebu-icu.c static int mvebu_icu_probe(struct platform_device *pdev) · line 300
Quoted 9 lines
drivers/irqchip/irq-mvebu-icu.c +1
-8- irq_domain = -platform_msi_create_device_domain (&pdev->dev,ICU_MAX_IRQS , - mvebu_icu_write_msg, - &mvebu_icu_domain_ops, icu); -if (!irq_domain) { -dev_err (&pdev->dev,"Failed to create ICU domain\n" ); -return -ENOMEM ; - } +platform_set_drvdata (pdev, icu);What is the upshot of passing this icu pointer as part of the platform device structure instead of simply passing it as a parameter? Is anything else using it?
Not at this stage of the series, but right after the patch "irqchip/irq-mvebu-icu: support ICU subnodes" does the following:
@@ -299,7 +356,10 @@ static int mvebu_icu_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, icu);
- return mvebu_icu_subset_probe(pdev);
+ if (icu->legacy_bindings)
+ return mvebu_icu_subset_probe(pdev);
+ else
+ return devm_of_platform_populate(&pdev->dev);
}
Using driver data in both cases was, from my point of view, the easiest (meanwhile, I'll simplify it thanks to another comment you made).
Thanks,
Miqu?l
PATCH v3 9/17 irqchip/irq-mvebu-icu: support ICU subnodes
In reply to Miquel RaynalThe ICU can handle several type of interrupt, each of them being handled differently on AP side. On CP side, the ICU should be able to make the distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU node as the parent of multiple ICU sub-nodes, each of them being an interrupt type with a different interrupt parent. ICU interrupt 'clients' now directly point to the right sub-node, avoiding the need for the extra ICU_GRP_* parameter.
ICU subnodes are probed automatically with devm_platform_populate(). If the node as no child, the probe function for NSRs will still be called 'manually' in order to preserve backward compatibility with DT using the old binding.
--- drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 14 deletions(-)
drivers/irqchip/irq-mvebu-icu.c +74 -14
@@ -43,6 +43,7 @@ struct mvebu_icu {struct regmap *regmap;struct device *dev;atomic_t initialized; +bool legacy_bindings; };struct mvebu_icu_irq_data {
@@ -51,6 +52,30 @@ struct mvebu_icu_irq_data {unsigned int type; }; +static struct mvebu_icu *mvebu_icu_dev_get_drvdata (struct platform_device *pdev) +{ +struct mvebu_icu *icu; + +/* +* Device data being populated means we should be using legacy bindings. +* Using the _parent_ device data means we should be using new bindings. +*/ + icu =dev_get_drvdata (&pdev->dev); +if (icu) { +if (!icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }else { + icu =dev_get_drvdata (pdev->dev.parent); +if (!icu) +return ERR_PTR (-ENODEV ); + +if (icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + } + +return icu; +} +static void mvebu_icu_init (struct mvebu_icu *icu,struct msi_msg *msg) {if (atomic_cmpxchg (&icu->initialized, false, true))
@@ -107,24 +132,28 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type) { struct mvebu_icu *icu = platform_msi_get_host_data(d); -unsigned int icu_group; +unsigned int param_count = icu->legacy_bindings ?3 :2 ;/* Check the count of the parameters in dt */ -if (WARN_ON (fwspec->param_count <3 )) { +if (WARN_ON (fwspec->param_count != param_count)) {dev_err (icu->dev,"wrong ICU parameter count %d\n" , fwspec->param_count);return -EINVAL ; } -/* Only ICU group type is handled */ - icu_group = fwspec->param[0 ]; -if (icu_group !=ICU_GRP_NSR && icu_group !=ICU_GRP_SR && - icu_group !=ICU_GRP_SEI && icu_group !=ICU_GRP_REI ) { -dev_err (icu->dev,"wrong ICU group type %x\n" , icu_group); -return -EINVAL ; +if (icu->legacy_bindings) { + *hwirq = fwspec->param[1 ]; + *type = fwspec->param[2 ]; +if (fwspec->param[0 ] !=ICU_GRP_NSR ) { +dev_err (icu->dev,"wrong ICU group type %x\n" , + fwspec->param[0 ]); +return -EINVAL ; + } + }else { + *hwirq = fwspec->param[0 ]; + *type = fwspec->param[1 ]; } - *hwirq = fwspec->param[1 ];if (*hwirq >=ICU_MAX_IRQS ) {dev_err (icu->dev,"invalid interrupt number %ld\n" , *hwirq);return -EINVAL ;
@@ -157,7 +186,10 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, goto free_irqd; } - icu_irqd->icu_group = fwspec->param[0 ]; +if (icu->legacy_bindings) + icu_irqd->icu_group = fwspec->param[0 ]; +else + icu_irqd->icu_group =ICU_GRP_NSR ; icu_irqd->icu = icu; err =platform_msi_domain_alloc (domain, virq, nr_irqs);
@@ -211,9 +243,9 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)struct irq_domain *irq_domain;struct mvebu_icu *icu; - icu =dev_get_drvdata (&pdev->dev); -if (!icu) -return -ENODEV ; + icu =mvebu_icu_dev_get_drvdata (pdev); +if (IS_ERR (icu)) +return PTR_ERR (icu); pdev->dev.msi_domain =of_msi_get_domain (&pdev->dev, pdev->dev.of_node,DOMAIN_BUS_PLATFORM_MSI );
@@ -236,6 +268,22 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)return 0 ; } +static const struct of_device_id mvebu_icu_subset_of_match[] = { + { + .compatible ="marvell,cp110-icu-nsr" , + }, + {}, +}; + +static struct platform_driver mvebu_icu_subset_driver = { + .probe = mvebu_icu_subset_probe, + .driver = { + .name ="mvebu-icu-subset" , + .of_match_table = mvebu_icu_subset_of_match, + }, +}; +builtin_platform_driver (mvebu_icu_subset_driver); +static struct regmap_config mvebu_icu_regmap_config = { .reg_bits =32 , .val_bits =32 ,
@@ -275,6 +323,15 @@ static int mvebu_icu_probe(struct platform_device *pdev)if (!icu->irq_chip.name)return -ENOMEM ; +/* +* Legacy bindings: ICU is one node with one MSI parent: force manually +* the probe of the NSR interrupts side. +* New bindings: ICU node has children, one per interrupt controller +* having its own MSI parent: call platform_populate(). +*/ +if (!of_get_child_count (pdev->dev.of_node)) + icu->legacy_bindings = true; + icu->irq_chip.irq_mask = irq_chip_mask_parent; icu->irq_chip.irq_unmask = irq_chip_unmask_parent; icu->irq_chip.irq_eoi = irq_chip_eoi_parent;
@@ -299,7 +356,10 @@ static int mvebu_icu_probe(struct platform_device *pdev)platform_set_drvdata (pdev, icu); -return mvebu_icu_subset_probe (pdev); +if (icu->legacy_bindings) +return mvebu_icu_subset_probe (pdev); +else +return devm_of_platform_populate (&pdev->dev); }static const struct of_device_id mvebu_icu_of_match[] = {
Signature · 1 line
2.14.1
PATCH v3 9/17 irqchip/irq-mvebu-icu: support ICU subnodes
Quoted 46 lines · Miquel Raynal
The ICU can handle several type of interrupt, each of them being handled differently on AP side. On CP side, the ICU should be able to make the distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU node as the parent of multiple ICU sub-nodes, each of them being an interrupt type with a different interrupt parent. ICU interrupt 'clients' now directly point to the right sub-node, avoiding the need for the extra ICU_GRP_* parameter.
ICU subnodes are probed automatically with
devm_platform_populate(). If the node as no child, the probe function for NSRs will still be called 'manually' in order to preserve backward compatibility with DT using the old binding.Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 14 deletions(-)drivers/irqchip/irq-mvebu-icu.c +11
-0@@ -43,6 +43,7 @@ struct mvebu_icu {struct regmap *regmap;struct device *dev;atomic_t initialized; +bool legacy_bindings; };struct mvebu_icu_irq_data {@@ -51,6 +52,30 @@ struct mvebu_icu_irq_data {unsigned int type; }; +static struct mvebu_icu *mvebu_icu_dev_get_drvdata (struct platform_device *pdev) +{ +struct mvebu_icu *icu; + +/* +* Device data being populated means we should be using legacy bindings. +* Using the _parent_ device data means we should be using new bindings. +*/ + icu =dev_get_drvdata (&pdev->dev); +if (icu) {
drivers/irqchip/irq-mvebu-icu.c struct mvebu_icu_irq_data { · line 74
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +10
-0+if (!icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }else { + icu =dev_get_drvdata (pdev->dev.parent); +if (!icu) +return ERR_PTR (-ENODEV ); + +if (icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }
Doesn't this make legacy_bindings completely redundant? Either the pointer is !NULL in the device, and this is using a legacy binging, or it is stored in the parent, and this is a new binding. You could even have a helper for that:
static bool is_legacy(struct platform_device *pdev)
{
return !dev_get_drvdata(&pdev->dev);
}The driver really doesn't need to be defending against itself, if anything, and it would save you quite a bit of error handling in the callers of this function.
Quoted 112 lines
drivers/irqchip/irq-mvebu-icu.c +49
-13+ +return icu; +} +static void mvebu_icu_init (struct mvebu_icu *icu,struct msi_msg *msg) {if (atomic_cmpxchg (&icu->initialized, false, true))@@ -107,24 +132,28 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type) { struct mvebu_icu *icu = platform_msi_get_host_data(d); -unsigned int icu_group; +unsigned int param_count = icu->legacy_bindings ?3 :2 ;/* Check the count of the parameters in dt */ -if (WARN_ON (fwspec->param_count <3 )) { +if (WARN_ON (fwspec->param_count != param_count)) {dev_err (icu->dev,"wrong ICU parameter count %d\n" , fwspec->param_count);return -EINVAL ; } -/* Only ICU group type is handled */ - icu_group = fwspec->param[0 ]; -if (icu_group !=ICU_GRP_NSR && icu_group !=ICU_GRP_SR && - icu_group !=ICU_GRP_SEI && icu_group !=ICU_GRP_REI ) { -dev_err (icu->dev,"wrong ICU group type %x\n" , icu_group); -return -EINVAL ; +if (icu->legacy_bindings) { + *hwirq = fwspec->param[1 ]; + *type = fwspec->param[2 ]; +if (fwspec->param[0 ] !=ICU_GRP_NSR ) { +dev_err (icu->dev,"wrong ICU group type %x\n" , + fwspec->param[0 ]); +return -EINVAL ; + } + }else { + *hwirq = fwspec->param[0 ]; + *type = fwspec->param[1 ]; } - *hwirq = fwspec->param[1 ];if (*hwirq >=ICU_MAX_IRQS ) {dev_err (icu->dev,"invalid interrupt number %ld\n" , *hwirq);return -EINVAL ;@@ -157,7 +186,10 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, goto free_irqd; } - icu_irqd->icu_group = fwspec->param[0 ]; +if (icu->legacy_bindings) + icu_irqd->icu_group = fwspec->param[0 ]; +else + icu_irqd->icu_group =ICU_GRP_NSR ; icu_irqd->icu = icu; err =platform_msi_domain_alloc (domain, virq, nr_irqs);@@ -211,9 +243,9 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)struct irq_domain *irq_domain;struct mvebu_icu *icu; - icu =dev_get_drvdata (&pdev->dev); -if (!icu) -return -ENODEV ; + icu =mvebu_icu_dev_get_drvdata (pdev); +if (IS_ERR (icu)) +return PTR_ERR (icu); pdev->dev.msi_domain =of_msi_get_domain (&pdev->dev, pdev->dev.of_node,DOMAIN_BUS_PLATFORM_MSI );@@ -236,6 +268,22 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)return 0 ; } +static const struct of_device_id mvebu_icu_subset_of_match[] = { + { + .compatible ="marvell,cp110-icu-nsr" , + }, + {}, +}; + +static struct platform_driver mvebu_icu_subset_driver = { + .probe = mvebu_icu_subset_probe, + .driver = { + .name ="mvebu-icu-subset" , + .of_match_table = mvebu_icu_subset_of_match, + }, +}; +builtin_platform_driver (mvebu_icu_subset_driver); +static struct regmap_config mvebu_icu_regmap_config = { .reg_bits =32 , .val_bits =32 ,@@ -275,6 +323,15 @@ static int mvebu_icu_probe(struct platform_device *pdev)if (!icu->irq_chip.name)return -ENOMEM ; +/* +* Legacy bindings: ICU is one node with one MSI parent: force manually +* the probe of the NSR interrupts side. +* New bindings: ICU node has children, one per interrupt controller +* having its own MSI parent: call platform_populate(). +*/ +if (!of_get_child_count (pdev->dev.of_node)) + icu->legacy_bindings = true; + icu->irq_chip.irq_mask = irq_chip_mask_parent; icu->irq_chip.irq_unmask = irq_chip_unmask_parent; icu->irq_chip.irq_eoi = irq_chip_eoi_parent;@@ -299,7 +356,10 @@ static int mvebu_icu_probe(struct platform_device *pdev)
drivers/irqchip/irq-mvebu-icu.c static int mvebu_icu_probe(struct platform_device *pdev) · line 365
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +4
-1platform_set_drvdata (pdev, icu); -return mvebu_icu_subset_probe (pdev); +if (icu->legacy_bindings) +return mvebu_icu_subset_probe (pdev); +else +return devm_of_platform_populate (&pdev->dev); }static const struct of_device_id mvebu_icu_of_match[] = {
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
PATCH v3 9/17 irqchip/irq-mvebu-icu: support ICU subnodes
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 13:45:09 +0100:
Quoted 63 lines · Miquel Raynal
Quoted 46 lines
The ICU can handle several type of interrupt, each of them being handled differently on AP side. On CP side, the ICU should be able to make the distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU node as the parent of multiple ICU sub-nodes, each of them being an interrupt type with a different interrupt parent. ICU interrupt 'clients' now directly point to the right sub-node, avoiding the need for the extra ICU_GRP_* parameter.
ICU subnodes are probed automatically with
devm_platform_populate(). If the node as no child, the probe function for NSRs will still be called 'manually' in order to preserve backward compatibility with DT using the old binding.Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 14 deletions(-)drivers/irqchip/irq-mvebu-icu.c +11
-0@@ -43,6 +43,7 @@ struct mvebu_icu {struct regmap *regmap;struct device *dev;atomic_t initialized; +bool legacy_bindings; };struct mvebu_icu_irq_data {@@ -51,6 +52,30 @@ struct mvebu_icu_irq_data {unsigned int type; }; +static struct mvebu_icu *mvebu_icu_dev_get_drvdata (struct platform_device *pdev) +{ +struct mvebu_icu *icu; + +/* +* Device data being populated means we should be using legacy bindings. +* Using the _parent_ device data means we should be using new bindings. +*/ + icu =dev_get_drvdata (&pdev->dev); +if (icu) {drivers/irqchip/irq-mvebu-icu.c struct mvebu_icu_irq_data { · line 74
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +10
-0+if (!icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }else { + icu =dev_get_drvdata (pdev->dev.parent); +if (!icu) +return ERR_PTR (-ENODEV ); + +if (icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }Doesn't this make
legacy_bindingscompletely redundant? Either the pointer is !NULL in the device, and this is using a legacy binging, or it is stored in the parent, and this is a new binding. You could even have a helper for that:
Quoted 8 lines
static bool
is_legacy(struct platform_device *pdev){ return !dev_get_drvdata(&pdev->dev); }The driver really doesn't need to be defending against itself, if anything, and it would save you quite a bit of error handling in the callers of this function.
You're right, I will simplify this.
Thanks,
Miqu?l
PATCH v3 9/17 irqchip/irq-mvebu-icu: support ICU subnodes
In reply to Marc ZyngierHi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 13:45:09 +0100:
Quoted 63 lines · Miquel Raynal
Quoted 46 lines
The ICU can handle several type of interrupt, each of them being handled differently on AP side. On CP side, the ICU should be able to make the distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU node as the parent of multiple ICU sub-nodes, each of them being an interrupt type with a different interrupt parent. ICU interrupt 'clients' now directly point to the right sub-node, avoiding the need for the extra ICU_GRP_* parameter.
ICU subnodes are probed automatically with
devm_platform_populate(). If the node as no child, the probe function for NSRs will still be called 'manually' in order to preserve backward compatibility with DT using the old binding.Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 14 deletions(-)drivers/irqchip/irq-mvebu-icu.c +11
-0@@ -43,6 +43,7 @@ struct mvebu_icu {struct regmap *regmap;struct device *dev;atomic_t initialized; +bool legacy_bindings; };struct mvebu_icu_irq_data {@@ -51,6 +52,30 @@ struct mvebu_icu_irq_data {unsigned int type; }; +static struct mvebu_icu *mvebu_icu_dev_get_drvdata (struct platform_device *pdev) +{ +struct mvebu_icu *icu; + +/* +* Device data being populated means we should be using legacy bindings. +* Using the _parent_ device data means we should be using new bindings. +*/ + icu =dev_get_drvdata (&pdev->dev); +if (icu) {drivers/irqchip/irq-mvebu-icu.c struct mvebu_icu_irq_data { · line 74
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +10
-0+if (!icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }else { + icu =dev_get_drvdata (pdev->dev.parent); +if (!icu) +return ERR_PTR (-ENODEV ); + +if (icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }Doesn't this make
legacy_bindingscompletely redundant? Either the pointer is !NULL in the device, and this is using a legacy binging, or it is stored in the parent, and this is a new binding. You could even have a helper for that:
Quoted 8 lines
static bool
is_legacy(struct platform_device *pdev){ return !dev_get_drvdata(&pdev->dev); }The driver really doesn't need to be defending against itself, if anything, and it would save you quite a bit of error handling in the callers of this function.
I simplified the sanity checks but I had to keep an icu->is_legacy boolean because the above function would not have worked, for instance, in the *_translate() hook. As this hook does not receive a struct device * (or platform_device) as parameter, I tried to use icu->dev instead. This cannot work as it always points to the device having the driver data attached.
Please tell me if the next version fits what you expect.
Thanks,
Miqu?l
PATCH v3 9/17 irqchip/irq-mvebu-icu: support ICU subnodes
Quoted 77 lines · Miquel Raynal
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 13:45:09 +0100:
Quoted 63 lines · Miquel Raynal
Quoted 46 lines
The ICU can handle several type of interrupt, each of them being handled differently on AP side. On CP side, the ICU should be able to make the distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU node as the parent of multiple ICU sub-nodes, each of them being an interrupt type with a different interrupt parent. ICU interrupt 'clients' now directly point to the right sub-node, avoiding the need for the extra ICU_GRP_* parameter.
ICU subnodes are probed automatically with
devm_platform_populate(). If the node as no child, the probe function for NSRs will still be called 'manually' in order to preserve backward compatibility with DT using the old binding.Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 14 deletions(-)drivers/irqchip/irq-mvebu-icu.c +11
-0@@ -43,6 +43,7 @@ struct mvebu_icu {struct regmap *regmap;struct device *dev;atomic_t initialized; +bool legacy_bindings; };struct mvebu_icu_irq_data {@@ -51,6 +52,30 @@ struct mvebu_icu_irq_data {unsigned int type; }; +static struct mvebu_icu *mvebu_icu_dev_get_drvdata (struct platform_device *pdev) +{ +struct mvebu_icu *icu; + +/* +* Device data being populated means we should be using legacy bindings. +* Using the _parent_ device data means we should be using new bindings. +*/ + icu =dev_get_drvdata (&pdev->dev); +if (icu) {drivers/irqchip/irq-mvebu-icu.c struct mvebu_icu_irq_data { · line 74
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +10
-0+if (!icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }else { + icu =dev_get_drvdata (pdev->dev.parent); +if (!icu) +return ERR_PTR (-ENODEV ); + +if (icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }Doesn't this make
legacy_bindingscompletely redundant? Either the pointer is !NULL in the device, and this is using a legacy binging, or it is stored in the parent, and this is a new binding. You could even have a helper for that:Quoted 8 lines
static bool
is_legacy(struct platform_device *pdev){ return !dev_get_drvdata(&pdev->dev); }The driver really doesn't need to be defending against itself, if anything, and it would save you quite a bit of error handling in the callers of this function.
Quoted 6 lines
I simplified the sanity checks but I had to keep an
icu->is_legacyboolean because the above function would not have worked, for instance, in the*_translate()hook. As this hook does not receive a struct device * (orplatform_device) as parameter, I tried to useicu->devinstead. This cannot work as it always points to the device having the driver data attached.
You could still have the pdev as part of the domain host_data, right? Isn't that just a matter of having a pointer to the pdev as part of the icu data structure?
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
PATCH v3 9/17 irqchip/irq-mvebu-icu: support ICU subnodes
Hi Marc,
Marc Zyngier <[email protected]> wrote on Wed, 4 Jul 2018 13:43:58 +0100:
Quoted 85 lines · Miquel Raynal
Quoted 77 lines
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 13:45:09 +0100:
Quoted 63 lines · Miquel Raynal
Quoted 46 lines
The ICU can handle several type of interrupt, each of them being handled differently on AP side. On CP side, the ICU should be able to make the distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU node as the parent of multiple ICU sub-nodes, each of them being an interrupt type with a different interrupt parent. ICU interrupt 'clients' now directly point to the right sub-node, avoiding the need for the extra ICU_GRP_* parameter.
ICU subnodes are probed automatically with
devm_platform_populate(). If the node as no child, the probe function for NSRs will still be called 'manually' in order to preserve backward compatibility with DT using the old binding.Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 14 deletions(-)drivers/irqchip/irq-mvebu-icu.c +11
-0@@ -43,6 +43,7 @@ struct mvebu_icu {struct regmap *regmap;struct device *dev;atomic_t initialized; +bool legacy_bindings; };struct mvebu_icu_irq_data {@@ -51,6 +52,30 @@ struct mvebu_icu_irq_data {unsigned int type; }; +static struct mvebu_icu *mvebu_icu_dev_get_drvdata (struct platform_device *pdev) +{ +struct mvebu_icu *icu; + +/* +* Device data being populated means we should be using legacy bindings. +* Using the _parent_ device data means we should be using new bindings. +*/ + icu =dev_get_drvdata (&pdev->dev); +if (icu) {drivers/irqchip/irq-mvebu-icu.c struct mvebu_icu_irq_data { · line 74
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +10
-0+if (!icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }else { + icu =dev_get_drvdata (pdev->dev.parent); +if (!icu) +return ERR_PTR (-ENODEV ); + +if (icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }Doesn't this make
legacy_bindingscompletely redundant? Either the pointer is !NULL in the device, and this is using a legacy binging, or it is stored in the parent, and this is a new binding. You could even have a helper for that:Quoted 8 lines
static bool
is_legacy(struct platform_device *pdev){ return !dev_get_drvdata(&pdev->dev); }The driver really doesn't need to be defending against itself, if anything, and it would save you quite a bit of error handling in the callers of this function.
Quoted 6 lines
I simplified the sanity checks but I had to keep an
icu->is_legacyboolean because the above function would not have worked, for instance, in the*_translate()hook. As this hook does not receive a struct device * (orplatform_device) as parameter, I tried to useicu->devinstead. This cannot work as it always points to the device having the driver data attached.
Quoted 3 lines
You could still have the pdev as part of the domain
host_data, right? Isn't that just a matter of having a pointer to the pdev as part of the icu data structure?
There is already a 'struct device *dev' pointer in the ICU data structure. This pointer is set to "&pdev->dev" in the main probe function.
Legacy: dev is the unique ICU device, the driver data is attached to it.
Now: dev is the 'host' ICU device that is the parent of two other devices, ICU NSR controller and SEI NSR controller. The driver data is also attached to this device.
So in both situations checking dev_get_drvdata(icu->dev) would return a valid pointer.
Does this answer your question?
Kind regards,
Miqu?l
PATCH v3 9/17 irqchip/irq-mvebu-icu: support ICU subnodes
Quoted 103 lines · Miquel Raynal
Hi Marc,
Marc Zyngier <[email protected]> wrote on Wed, 4 Jul 2018 13:43:58 +0100:
Quoted 85 lines · Miquel Raynal
Quoted 77 lines
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 13:45:09 +0100:
Quoted 63 lines · Miquel Raynal
Quoted 46 lines
The ICU can handle several type of interrupt, each of them being handled differently on AP side. On CP side, the ICU should be able to make the distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU node as the parent of multiple ICU sub-nodes, each of them being an interrupt type with a different interrupt parent. ICU interrupt 'clients' now directly point to the right sub-node, avoiding the need for the extra ICU_GRP_* parameter.
ICU subnodes are probed automatically with
devm_platform_populate(). If the node as no child, the probe function for NSRs will still be called 'manually' in order to preserve backward compatibility with DT using the old binding.Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 14 deletions(-)drivers/irqchip/irq-mvebu-icu.c +11
-0@@ -43,6 +43,7 @@ struct mvebu_icu {struct regmap *regmap;struct device *dev;atomic_t initialized; +bool legacy_bindings; };struct mvebu_icu_irq_data {@@ -51,6 +52,30 @@ struct mvebu_icu_irq_data {unsigned int type; }; +static struct mvebu_icu *mvebu_icu_dev_get_drvdata (struct platform_device *pdev) +{ +struct mvebu_icu *icu; + +/* +* Device data being populated means we should be using legacy bindings. +* Using the _parent_ device data means we should be using new bindings. +*/ + icu =dev_get_drvdata (&pdev->dev); +if (icu) {drivers/irqchip/irq-mvebu-icu.c struct mvebu_icu_irq_data { · line 74
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +10
-0+if (!icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }else { + icu =dev_get_drvdata (pdev->dev.parent); +if (!icu) +return ERR_PTR (-ENODEV ); + +if (icu->legacy_bindings) +return ERR_PTR (-EINVAL ); + }Doesn't this make
legacy_bindingscompletely redundant? Either the pointer is !NULL in the device, and this is using a legacy binging, or it is stored in the parent, and this is a new binding. You could even have a helper for that:Quoted 8 lines
static bool
is_legacy(struct platform_device *pdev){ return !dev_get_drvdata(&pdev->dev); }The driver really doesn't need to be defending against itself, if anything, and it would save you quite a bit of error handling in the callers of this function.
Quoted 6 lines
I simplified the sanity checks but I had to keep an
icu->is_legacyboolean because the above function would not have worked, for instance, in the*_translate()hook. As this hook does not receive a struct device * (orplatform_device) as parameter, I tried to useicu->devinstead. This cannot work as it always points to the device having the driver data attached.Quoted 3 lines
You could still have the pdev as part of the domain
host_data, right? Isn't that just a matter of having a pointer to the pdev as part of the icu data structure?There is already a 'struct device *dev' pointer in the ICU data structure. This pointer is set to "
&pdev->dev" in the main probe function.Legacy: dev is the unique ICU device, the driver data is attached to it.
Now: dev is the 'host' ICU device that is the parent of two other devices, ICU NSR controller and SEI NSR controller. The driver data is also attached to this device.
Quoted 4 lines
So in both situations checking
dev_get_drvdata(icu->dev)would return a valid pointer.Does this answer your question?
I think I need to see the full picture again. Post the updated series, and we'll see what we can do.
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
PATCH v3 10/17 irqchip/irq-mvebu-sei: add new driver for Marvell SEI
In reply to Miquel RaynalThis is a cascaded interrupt controller in the AP806 GIC that collapses SEIs (System Error Interrupt) coming from the AP and the CPs (through the ICU).
The SEI handles up to 64 interrupts. The first 21 interrupts are wired from the AP. The next 43 interrupts are from the CPs and are triggered through MSI messages. To handle this complexity, the driver has to declare to the upper layer: one IRQ domain for the wired interrupts, one IRQ domain for the MSIs; and acts as a MSI controller ('parent') by declaring an MSI domain.
--- drivers/irqchip/Kconfig | 3 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-mvebu-sei.c | 444 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 448 insertions(+) create mode 100644 drivers/irqchip/irq-mvebu-sei.c
drivers/irqchip/Kconfig +3 -0
@@ -310,6 +310,9 @@ config MVEBU_ODMI config MVEBU_PIC bool +config MVEBU_SEI + bool + config LS_SCFG_MSI def_bool y if SOC_LS1021A || ARCH_LAYERSCAPE depends on PCI && PCI_MSI
drivers/irqchip/Makefile +1 -0
@@ -76,6 +76,7 @@ obj-$(CONFIG_MVEBU_GICP) += irq-mvebu-gicp.o obj-$(CONFIG_MVEBU_ICU) += irq-mvebu-icu.o obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o +obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o
drivers/irqchip/irq-mvebu-sei.c new file +444 -0
444 lines not on this page · raw message
Signature · 1 line
2.14.1
PATCH v3 10/17 irqchip/irq-mvebu-sei: add new driver for Marvell SEI
Quoted 75 lines · Miquel Raynal
This is a cascaded interrupt controller in the AP806 GIC that collapses SEIs (System Error Interrupt) coming from the AP and the CPs (through the ICU).
The SEI handles up to 64 interrupts. The first 21 interrupts are wired from the AP. The next 43 interrupts are from the CPs and are triggered through MSI messages. To handle this complexity, the driver has to declare to the upper layer: one IRQ domain for the wired interrupts, one IRQ domain for the MSIs; and acts as a MSI controller ('parent') by declaring an MSI domain.
Suggested-by Haim Boot <[email protected]>Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/Kconfig | 3 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-mvebu-sei.c | 444 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 448 insertions(+) create mode 100644 drivers/irqchip/irq-mvebu-sei.cdrivers/irqchip/Kconfig +3
-0@@ -310,6 +310,9 @@ config MVEBU_ODMI config MVEBU_PIC bool +config MVEBU_SEI + bool + config LS_SCFG_MSI def_bool y if SOC_LS1021A || ARCH_LAYERSCAPE depends on PCI && PCI_MSIdrivers/irqchip/Makefile +1
-0@@ -76,6 +76,7 @@ obj-$(CONFIG_MVEBU_GICP) += irq-mvebu-gicp.o obj-$(CONFIG_MVEBU_ICU) += irq-mvebu-icu.o obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o +obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.odrivers/irqchip/irq-mvebu-sei.c new file +23
-0@@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define pr_fmt (fmt) "mvebu-sei: " fmt + +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/irqdomain.h> +#include <linux/kernel.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/msi.h> +#include <linux/platform_device.h> +#include <linux/irqchip.h> + +#include <dt-bindings/interrupt-controller/arm-gic.h> + +/* Cause register */ +#define GICP_SECR (idx) (0x0 + ((idx) * 0x4)) +/* Mask register */ +#define GICP_SEMR (idx) (0x20 + ((idx) * 0x4)) +#define GICP_SET_SEI_OFFSET 0x30
drivers/irqchip/irq-mvebu-sei.c line 33
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+ +#define SEI_IRQ_COUNT_PER_REG 32 +#define SEI_IRQ_REG_COUNT 2 +#define SEI_IRQ_COUNT (SEI_IRQ_COUNT_PER_REG * SEI_IRQ_REG_COUNT) +#define SEI_IRQ_REG_IDX (irq_id) ((irq_id) / SEI_IRQ_COUNT_PER_REG) +#define SEI_IRQ_REG_BIT (irq_id) ((irq_id) % SEI_IRQ_COUNT_PER_REG) + +struct mvebu_sei_interrupt_range { +u32 first; +u32 number;
nit: number is a bit vague. consider using size (or anything similar).
Quoted 35 lines
drivers/irqchip/irq-mvebu-sei.c new file +35
-0+}; + +struct mvebu_sei { +struct device *dev; +void __iomem *base; +struct resource *res; +struct irq_domain *ap_domain; +struct irq_domain *cp_domain; +struct mvebu_sei_interrupt_range ap_interrupts; +struct mvebu_sei_interrupt_range cp_interrupts; +/* Lock on MSI allocations/releases */ +struct mutex cp_msi_lock; + DECLARE_BITMAP(cp_msi_bitmap, SEI_IRQ_COUNT); +}; + +static int mvebu_sei_domain_to_sei_irq (struct mvebu_sei *sei, +struct irq_domain *domain, +irq_hw_number_t hwirq) +{ +if (domain == sei->ap_domain) +return sei->ap_interrupts.first + hwirq; +else +return sei->cp_interrupts.first + hwirq; +} + +static void mvebu_sei_reset (struct mvebu_sei *sei) +{ +u32 reg_idx; + +/* Clear IRQ cause registers */ +for (reg_idx =0 ; reg_idx <SEI_IRQ_REG_COUNT ; reg_idx++) +writel (0xFFFFFFFF , sei->base +GICP_SECR (reg_idx)); +} + +static void mvebu_sei_mask_irq (struct irq_data *d)
drivers/irqchip/irq-mvebu-sei.c line 78
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+{ +struct mvebu_sei *sei =irq_data_get_irq_chip_data (d); +u32 sei_irq =mvebu_sei_domain_to_sei_irq (sei, d->domain, d->hwirq); +u32 reg_idx =SEI_IRQ_REG_IDX (sei_irq); +u32 reg; + +/* 1 disables the interrupt */ + reg =readl (sei->base +GICP_SEMR (reg_idx)); + reg |=BIT (SEI_IRQ_REG_BIT (sei_irq)); +writel (reg, sei->base +GICP_SEMR (reg_idx));
Consider using _relaxed accessors, unless this relies on ordering with other memory accesses (I really don't think it does).
Quoted 131 lines
drivers/irqchip/irq-mvebu-sei.c new file +131
-0+} + +static void mvebu_sei_unmask_irq (struct irq_data *d) +{ +struct mvebu_sei *sei =irq_data_get_irq_chip_data (d); +u32 sei_irq =mvebu_sei_domain_to_sei_irq (sei, d->domain, d->hwirq); +u32 reg_idx =SEI_IRQ_REG_IDX (sei_irq); +u32 reg; + +/* 0 enables the interrupt */ + reg =readl (sei->base +GICP_SEMR (reg_idx)); + reg &= ~BIT (SEI_IRQ_REG_BIT (sei_irq)); +writel (reg, sei->base +GICP_SEMR (reg_idx)); +} + +static void mvebu_sei_compose_msi_msg (struct irq_data *data, +struct msi_msg *msg) +{ +struct mvebu_sei *sei = data->chip_data; +phys_addr_t set = sei->res->start +GICP_SET_SEI_OFFSET ; + + msg->data =mvebu_sei_domain_to_sei_irq (sei, data->domain, data->hwirq); + msg->address_lo =lower_32_bits (set); + msg->address_hi =upper_32_bits (set); +} + +static int mvebu_sei_ap_set_type (struct irq_data *data,unsigned int type) +{ +if (!(type &IRQ_TYPE_LEVEL_HIGH )) +return -EINVAL ; + +return 0 ; +} + +static int mvebu_sei_cp_set_type (struct irq_data *data,unsigned int type) +{ +if (!(type &IRQ_TYPE_EDGE_RISING )) +return -EINVAL ; + +return 0 ; +} + +static struct irq_chip mvebu_sei_ap_wired_irq_chip = { + .name ="AP wired SEI" , + .irq_mask = mvebu_sei_mask_irq, + .irq_unmask = mvebu_sei_unmask_irq, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, + .irq_set_type = mvebu_sei_ap_set_type, +}; + +static struct irq_chip mvebu_sei_cp_msi_irq_chip = { + .name ="CP MSI SEI" , + .irq_mask = mvebu_sei_mask_irq, + .irq_unmask = mvebu_sei_unmask_irq, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, + .irq_set_type = mvebu_sei_cp_set_type, + .irq_compose_msi_msg = mvebu_sei_compose_msi_msg, +}; + +static int mvebu_sei_irq_domain_alloc (struct irq_domain *domain, +unsigned int virq,unsigned int nr_irqs, +void *args) +{ +struct mvebu_sei *sei = domain->host_data; +struct irq_fwspec *fwspec = args; +struct irq_chip *irq_chip; +int sei_hwirq, hwirq; +int ret; + +/* The software only supports single allocations for now */ +if (nr_irqs !=1 ) +return -ENOTSUPP ; + +if (domain == sei->ap_domain) { + irq_chip = &mvebu_sei_ap_wired_irq_chip; + hwirq = fwspec->param[0 ]; + }else { + irq_chip = &mvebu_sei_cp_msi_irq_chip; +mutex_lock (&sei->cp_msi_lock); + hwirq =bitmap_find_free_region (sei->cp_msi_bitmap, + sei->cp_interrupts.number,0 ); +mutex_unlock (&sei->cp_msi_lock); +if (hwirq <0 ) +return -ENOSPC ; + } + + sei_hwirq =mvebu_sei_domain_to_sei_irq (sei, domain, hwirq); + + fwspec->fwnode = domain->parent->fwnode; + fwspec->param_count =3 ; + fwspec->param[0 ] =GIC_SPI ; + fwspec->param[1 ] = sei_hwirq; + fwspec->param[2 ] =IRQ_TYPE_EDGE_RISING ; + + ret =irq_domain_alloc_irqs_parent (domain, virq, nr_irqs, fwspec); +if (ret) + goto release_region; + + ret =irq_domain_set_hwirq_and_chip (domain, virq, hwirq, irq_chip, sei); +if (ret) + goto free_irq_parents; + +return 0 ; + +free_irq_parents: +irq_domain_free_irqs_parent (domain, virq, nr_irqs); +release_region: +if (domain == sei->cp_domain) { +mutex_lock (&sei->cp_msi_lock); +bitmap_release_region (sei->cp_msi_bitmap, hwirq,0 ); +mutex_unlock (&sei->cp_msi_lock); + } + +return ret; +} + +static void mvebu_sei_irq_domain_free (struct irq_domain *domain, +unsigned int virq,unsigned int nr_irqs) +{ +struct mvebu_sei *sei = domain->host_data; +struct irq_data *d =irq_domain_get_irq_data (domain, virq); +u32 irq_nb = sei->ap_interrupts.number + sei->cp_interrupts.number; + +if (nr_irqs !=1 || d->hwirq >= irq_nb) { +dev_err (sei->dev,"Invalid hwirq %lu\n" , d->hwirq); +return ; + } + +irq_domain_free_irqs_parent (domain, virq, nr_irqs);
drivers/irqchip/irq-mvebu-sei.c line 219
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+ +mutex_lock (&sei->cp_msi_lock); +bitmap_release_region (sei->cp_msi_bitmap, d->hwirq,0 ); +mutex_unlock (&sei->cp_msi_lock); +} + +static int mvebu_sei_ap_match (struct irq_domain *d,struct device_node *node, +enum irq_domain_bus_token bus_token) +{ +struct mvebu_sei *sei = d->host_data;
If you're dereferencing the domain here...
drivers/irqchip/irq-mvebu-sei.c line 227
Quoted 8 lines
drivers/irqchip/irq-mvebu-sei.c new file +8
-0+ +if (!sei) +return 0 ; + +if (sei->dev->of_node != node) +return 0 ; + +if (!d || !sei->ap_domain)
... how can it be NULL here? Actually, how can it *ever* be NULL?
Also, it is not clear to me how can sei or sei->ap_domain be NULL.
Quoted 36 lines
drivers/irqchip/irq-mvebu-sei.c new file +36
-0+return 0 ; + +if (d == sei->ap_domain) +return 1 ; + +return 0 ; +} + +static const struct irq_domain_ops mvebu_sei_ap_domain_ops = { + .match = mvebu_sei_ap_match, + .xlate = irq_domain_xlate_onecell, + .alloc = mvebu_sei_irq_domain_alloc, + .free = mvebu_sei_irq_domain_free, +}; + +static const struct irq_domain_ops mvebu_sei_cp_domain_ops = { + .alloc = mvebu_sei_irq_domain_alloc, + .free = mvebu_sei_irq_domain_free, +}; + +static struct irq_chip mvebu_sei_msi_irq_chip = { + .name ="SEI MSI controller" , + .irq_set_type = mvebu_sei_cp_set_type, +}; + +static struct msi_domain_ops mvebu_sei_msi_ops = { +}; + +static struct msi_domain_info mvebu_sei_msi_domain_info = { + .flags =MSI_FLAG_USE_DEF_DOM_OPS |MSI_FLAG_USE_DEF_CHIP_OPS , + .ops = &mvebu_sei_msi_ops, + .chip = &mvebu_sei_msi_irq_chip, +}; + +static void mvebu_sei_handle_cascade_irq (struct irq_desc *desc) +{
drivers/irqchip/irq-mvebu-sei.c line 273
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+struct mvebu_sei *sei =irq_desc_get_handler_data (desc); +struct irq_chip *chip =irq_desc_get_chip (desc); +unsigned long irqmap, irq_bit; +u32 reg_idx, virq, irqn; + +chained_irq_enter (chip, desc); + +/* Read both SEI cause registers (64 bits) */ +for (reg_idx =0 ; reg_idx <SEI_IRQ_REG_COUNT ; reg_idx++) { + irqmap =readl_relaxed (sei->base +GICP_SECR (reg_idx));
I think it'd be a lot clearer if you just read both 32bit registers and treat the resulting 64bit quantity as a single bitmap, rather than having this outer loop.
Quoted 13 lines
drivers/irqchip/irq-mvebu-sei.c new file +13
-0+ +/* Call handler for each set bit */ +for_each_set_bit (irq_bit, &irqmap,SEI_IRQ_COUNT_PER_REG ) { +/* Cause Register gives the SEI number */ + irqn = irq_bit + reg_idx *SEI_IRQ_COUNT_PER_REG ; +/* +* Finding Linux mapping (virq) needs the right domain +* and the relative hwirq (which start at 0 in both +* cases, while irqn is relative to all SEI interrupts). +*/ +if (irqn < sei->ap_interrupts.number) { + virq =irq_find_mapping (sei->ap_domain, irqn); + }else {
drivers/irqchip/irq-mvebu-sei.c line 296
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+ irqn -= sei->ap_interrupts.number; + virq =irq_find_mapping (sei->cp_domain, irqn); + } + +/* Call IRQ handler */ +generic_handle_irq (virq); + } + +/* Clear interrupt indication by writing 1 to it */ +writel (irqmap, sei->base +GICP_SECR (reg_idx));
write_relaxed. It would probably make sense not to write to that register if no bits were set to 1 the first place.
Quoted 46 lines
drivers/irqchip/irq-mvebu-sei.c new file +46
-0+ } + +chained_irq_exit (chip, desc); +} + +static int mvebu_sei_probe (struct platform_device *pdev) +{ +struct device_node *node = pdev->dev.of_node, *parent; +struct irq_domain *parent_domain, *plat_domain; +struct mvebu_sei *sei; +const __be32 *property; +u32 parent_irq, size; +int ret; + + sei =devm_kzalloc (&pdev->dev,sizeof (*sei),GFP_KERNEL ); +if (!sei) +return -ENOMEM ; + + sei->dev = &pdev->dev; + +mutex_init (&sei->cp_msi_lock); + + sei->res =platform_get_resource (pdev,IORESOURCE_MEM ,0 ); + sei->base =devm_ioremap_resource (sei->dev, sei->res); +if (!sei->base) { +dev_err (sei->dev,"Failed to remap SEI resource\n" ); +return -ENODEV ; + } + +mvebu_sei_reset (sei); + +/* +* Reserve the single (top-level) parent SPI IRQ from which all the +* interrupts handled by this driver will be signaled. +*/ + parent_irq =irq_of_parse_and_map (node,0 ); +if (parent_irq <=0 ) { +dev_err (sei->dev,"Failed to retrieve top-level SPI IRQ\n" ); +return -ENODEV ; + } + +irq_set_chained_handler (parent_irq, mvebu_sei_handle_cascade_irq); +irq_set_handler_data (parent_irq, sei); + +/* +* SEIs can be triggered from the AP through wired interrupts and from
drivers/irqchip/irq-mvebu-sei.c line 352
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+* the CPs through MSIs. +*/ + +/* Get a reference to the parent domain to create a hierarchy */ + parent =of_irq_find_parent (node); +if (!parent) { +dev_err (sei->dev,"Failed to find parent IRQ node\n" ); + ret = -ENODEV ; + goto dispose_irq; + }
Wouldn't it make sense to check this before having configured things with a chained handler? i.e. check everything that can be checked, and once you have everything in order, do the plumbing?
Quoted 83 lines
drivers/irqchip/irq-mvebu-sei.c new file +82
-0+ + parent_domain =irq_find_host (parent); +if (!parent_domain) { +dev_err (sei->dev,"Failed to find parent IRQ domain\n" ); + ret = -ENODEV ; + goto dispose_irq; + } + +/* +* Retrieve the IRQ organization (AP/CP): the index of the first one and +* the number of them for each domain. +*/ + property =of_get_property (node,"marvell,sei-ap-ranges" , &size); +if (!property || (size != (2 *sizeof (u32)))) { +dev_err (sei->dev,"Missing 'marvell,sei-ap-ranges' property\n" ); + ret = -ENODEV ; + goto dispose_irq; + } + + sei->ap_interrupts.first =be32_to_cpu (property[0 ]); + sei->ap_interrupts.number =be32_to_cpu (property[1 ]); + + property =of_get_property (node,"marvell,sei-cp-ranges" , &size); +if (!property || (size != (2 *sizeof (u32)))) { +dev_err (sei->dev,"Missing 'marvell,sei-cp-ranges' property\n" ); + ret = -ENODEV ; + goto dispose_irq; + } + + sei->cp_interrupts.first =be32_to_cpu (property[0 ]); + sei->cp_interrupts.number =be32_to_cpu (property[1 ]); + +/* Create the 'wired' hierarchy */ + sei->ap_domain =irq_domain_create_hierarchy (parent_domain,0 , + sei->ap_interrupts.number, +of_node_to_fwnode (node), + &mvebu_sei_ap_domain_ops, + sei); +if (!sei->ap_domain) { +dev_err (sei->dev,"Failed to create AP IRQ domain\n" ); + ret = -ENOMEM ; + goto dispose_irq; + } + +/* Create the 'MSI' hierarchy */ + sei->cp_domain =irq_domain_create_hierarchy (parent_domain,0 , + sei->cp_interrupts.number, +of_node_to_fwnode (node), + &mvebu_sei_cp_domain_ops, + sei); +if (!sei->cp_domain) { +pr_err ("Failed to create CPs IRQ domain\n" ); + ret = -ENOMEM ; + goto remove_ap_domain; + } + + plat_domain =platform_msi_create_irq_domain (of_node_to_fwnode (node), + &mvebu_sei_msi_domain_info, + sei->cp_domain); +if (!plat_domain) { +pr_err ("Failed to create CPs MSI domain\n" ); + ret = -ENOMEM ; + goto remove_cp_domain; + } + +platform_set_drvdata (pdev, sei); + +return 0 ; + +remove_cp_domain: +irq_domain_remove (sei->cp_domain); +remove_ap_domain: +irq_domain_remove (sei->ap_domain); +dispose_irq: +irq_dispose_mapping (parent_irq); + +return ret; +} + +static const struct of_device_id mvebu_sei_of_match[] = { + { .compatible ="marvell,armada-8k-sei" , }, + {},
drivers/irqchip/irq-mvebu-sei.c line 444
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+}; + +static struct platform_driver mvebu_sei_driver = { + .probe = mvebu_sei_probe, + .driver = { + .name ="mvebu-sei" , + .of_match_table = mvebu_sei_of_match, + }, +}; +builtin_platform_driver (mvebu_sei_driver);
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
PATCH v3 10/17 irqchip/irq-mvebu-sei: add new driver for Marvell SEI
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 15:54:30 +0100:
Quoted 80 lines · Miquel Raynal
Quoted 79 lines
This is a cascaded interrupt controller in the AP806 GIC that collapses SEIs (System Error Interrupt) coming from the AP and the CPs (through the ICU).
The SEI handles up to 64 interrupts. The first 21 interrupts are wired from the AP. The next 43 interrupts are from the CPs and are triggered through MSI messages. To handle this complexity, the driver has to declare to the upper layer: one IRQ domain for the wired interrupts, one IRQ domain for the MSIs; and acts as a MSI controller ('parent') by declaring an MSI domain.
Suggested-by Haim Boot <[email protected]>Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/Kconfig | 3 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-mvebu-sei.c | 444 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 448 insertions(+) create mode 100644 drivers/irqchip/irq-mvebu-sei.cdrivers/irqchip/Kconfig +3
-0@@ -310,6 +310,9 @@ config MVEBU_ODMI config MVEBU_PIC bool +config MVEBU_SEI + bool + config LS_SCFG_MSI def_bool y if SOC_LS1021A || ARCH_LAYERSCAPE depends on PCI && PCI_MSIdrivers/irqchip/Makefile +1
-0@@ -76,6 +76,7 @@ obj-$(CONFIG_MVEBU_GICP) += irq-mvebu-gicp.o obj-$(CONFIG_MVEBU_ICU) += irq-mvebu-icu.o obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o +obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.odrivers/irqchip/irq-mvebu-sei.c new file +27
-0@@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define pr_fmt (fmt) "mvebu-sei: " fmt + +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/irqdomain.h> +#include <linux/kernel.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/msi.h> +#include <linux/platform_device.h> +#include <linux/irqchip.h> + +#include <dt-bindings/interrupt-controller/arm-gic.h> + +/* Cause register */ +#define GICP_SECR (idx) (0x0 + ((idx) * 0x4)) +/* Mask register */ +#define GICP_SEMR (idx) (0x20 + ((idx) * 0x4)) +#define GICP_SET_SEI_OFFSET 0x30 + +#define SEI_IRQ_COUNT_PER_REG 32 +#define SEI_IRQ_REG_COUNT 2 +#define SEI_IRQ_COUNT (SEI_IRQ_COUNT_PER_REG * SEI_IRQ_REG_COUNT)
Quoted 8 lines
drivers/irqchip/irq-mvebu-sei.c line 33
Quoted 6 lines
drivers/irqchip/irq-mvebu-sei.c new file +6
-0+#define SEI_IRQ_REG_IDX (irq_id) ((irq_id) / SEI_IRQ_COUNT_PER_REG) +#define SEI_IRQ_REG_BIT (irq_id) ((irq_id) % SEI_IRQ_COUNT_PER_REG) + +struct mvebu_sei_interrupt_range { +u32 first; +u32 number;nit: number is a bit vague. consider using size (or anything similar).
Ack.
Quoted 41 lines
Quoted 41 lines
drivers/irqchip/irq-mvebu-sei.c new file +40
-0+}; + +struct mvebu_sei { +struct device *dev; +void __iomem *base; +struct resource *res; +struct irq_domain *ap_domain; +struct irq_domain *cp_domain; +struct mvebu_sei_interrupt_range ap_interrupts; +struct mvebu_sei_interrupt_range cp_interrupts; +/* Lock on MSI allocations/releases */ +struct mutex cp_msi_lock; + DECLARE_BITMAP(cp_msi_bitmap, SEI_IRQ_COUNT); +}; + +static int mvebu_sei_domain_to_sei_irq (struct mvebu_sei *sei, +struct irq_domain *domain, +irq_hw_number_t hwirq) +{ +if (domain == sei->ap_domain) +return sei->ap_interrupts.first + hwirq; +else +return sei->cp_interrupts.first + hwirq; +} + +static void mvebu_sei_reset (struct mvebu_sei *sei) +{ +u32 reg_idx; + +/* Clear IRQ cause registers */ +for (reg_idx =0 ; reg_idx <SEI_IRQ_REG_COUNT ; reg_idx++) +writel (0xFFFFFFFF , sei->base +GICP_SECR (reg_idx)); +} + +static void mvebu_sei_mask_irq (struct irq_data *d) +{ +struct mvebu_sei *sei =irq_data_get_irq_chip_data (d); +u32 sei_irq =mvebu_sei_domain_to_sei_irq (sei, d->domain, d->hwirq); +u32 reg_idx =SEI_IRQ_REG_IDX (sei_irq); +u32 reg;
Quoted 8 lines
drivers/irqchip/irq-mvebu-sei.c line 78
Quoted 5 lines
drivers/irqchip/irq-mvebu-sei.c new file +5
-0+ +/* 1 disables the interrupt */ + reg =readl (sei->base +GICP_SEMR (reg_idx)); + reg |=BIT (SEI_IRQ_REG_BIT (sei_irq)); +writel (reg, sei->base +GICP_SEMR (reg_idx));Consider using
_relaxedaccessors, unless this relies on ordering with other memory accesses (I really don't think it does).
I don't think it does neither, I just forgot about the _relaxed variants. I will update for all register reads/writes.
Quoted 145 lines
Quoted 132 lines
drivers/irqchip/irq-mvebu-sei.c new file +131
-0+} + +static void mvebu_sei_unmask_irq (struct irq_data *d) +{ +struct mvebu_sei *sei =irq_data_get_irq_chip_data (d); +u32 sei_irq =mvebu_sei_domain_to_sei_irq (sei, d->domain, d->hwirq); +u32 reg_idx =SEI_IRQ_REG_IDX (sei_irq); +u32 reg; + +/* 0 enables the interrupt */ + reg =readl (sei->base +GICP_SEMR (reg_idx)); + reg &= ~BIT (SEI_IRQ_REG_BIT (sei_irq)); +writel (reg, sei->base +GICP_SEMR (reg_idx)); +} + +static void mvebu_sei_compose_msi_msg (struct irq_data *data, +struct msi_msg *msg) +{ +struct mvebu_sei *sei = data->chip_data; +phys_addr_t set = sei->res->start +GICP_SET_SEI_OFFSET ; + + msg->data =mvebu_sei_domain_to_sei_irq (sei, data->domain, data->hwirq); + msg->address_lo =lower_32_bits (set); + msg->address_hi =upper_32_bits (set); +} + +static int mvebu_sei_ap_set_type (struct irq_data *data,unsigned int type) +{ +if (!(type &IRQ_TYPE_LEVEL_HIGH )) +return -EINVAL ; + +return 0 ; +} + +static int mvebu_sei_cp_set_type (struct irq_data *data,unsigned int type) +{ +if (!(type &IRQ_TYPE_EDGE_RISING )) +return -EINVAL ; + +return 0 ; +} + +static struct irq_chip mvebu_sei_ap_wired_irq_chip = { + .name ="AP wired SEI" , + .irq_mask = mvebu_sei_mask_irq, + .irq_unmask = mvebu_sei_unmask_irq, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, + .irq_set_type = mvebu_sei_ap_set_type, +}; + +static struct irq_chip mvebu_sei_cp_msi_irq_chip = { + .name ="CP MSI SEI" , + .irq_mask = mvebu_sei_mask_irq, + .irq_unmask = mvebu_sei_unmask_irq, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, + .irq_set_type = mvebu_sei_cp_set_type, + .irq_compose_msi_msg = mvebu_sei_compose_msi_msg, +}; + +static int mvebu_sei_irq_domain_alloc (struct irq_domain *domain, +unsigned int virq,unsigned int nr_irqs, +void *args) +{ +struct mvebu_sei *sei = domain->host_data; +struct irq_fwspec *fwspec = args; +struct irq_chip *irq_chip; +int sei_hwirq, hwirq; +int ret; + +/* The software only supports single allocations for now */ +if (nr_irqs !=1 ) +return -ENOTSUPP ; + +if (domain == sei->ap_domain) { + irq_chip = &mvebu_sei_ap_wired_irq_chip; + hwirq = fwspec->param[0 ]; + }else { + irq_chip = &mvebu_sei_cp_msi_irq_chip; +mutex_lock (&sei->cp_msi_lock); + hwirq =bitmap_find_free_region (sei->cp_msi_bitmap, + sei->cp_interrupts.number,0 ); +mutex_unlock (&sei->cp_msi_lock); +if (hwirq <0 ) +return -ENOSPC ; + } + + sei_hwirq =mvebu_sei_domain_to_sei_irq (sei, domain, hwirq); + + fwspec->fwnode = domain->parent->fwnode; + fwspec->param_count =3 ; + fwspec->param[0 ] =GIC_SPI ; + fwspec->param[1 ] = sei_hwirq; + fwspec->param[2 ] =IRQ_TYPE_EDGE_RISING ; + + ret =irq_domain_alloc_irqs_parent (domain, virq, nr_irqs, fwspec); +if (ret) + goto release_region; + + ret =irq_domain_set_hwirq_and_chip (domain, virq, hwirq, irq_chip, sei); +if (ret) + goto free_irq_parents; + +return 0 ; + +free_irq_parents: +irq_domain_free_irqs_parent (domain, virq, nr_irqs); +release_region: +if (domain == sei->cp_domain) { +mutex_lock (&sei->cp_msi_lock); +bitmap_release_region (sei->cp_msi_bitmap, hwirq,0 ); +mutex_unlock (&sei->cp_msi_lock); + } + +return ret; +} + +static void mvebu_sei_irq_domain_free (struct irq_domain *domain, +unsigned int virq,unsigned int nr_irqs) +{ +struct mvebu_sei *sei = domain->host_data; +struct irq_data *d =irq_domain_get_irq_data (domain, virq); +u32 irq_nb = sei->ap_interrupts.number + sei->cp_interrupts.number; + +if (nr_irqs !=1 || d->hwirq >= irq_nb) { +dev_err (sei->dev,"Invalid hwirq %lu\n" , d->hwirq); +return ; + } + +irq_domain_free_irqs_parent (domain, virq, nr_irqs);drivers/irqchip/irq-mvebu-sei.c line 219
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+ +mutex_lock (&sei->cp_msi_lock); +bitmap_release_region (sei->cp_msi_bitmap, d->hwirq,0 ); +mutex_unlock (&sei->cp_msi_lock); +} + +static int mvebu_sei_ap_match (struct irq_domain *d,struct device_node *node, +enum irq_domain_bus_token bus_token) +{ +struct mvebu_sei *sei = d->host_data;If you're dereferencing the domain here...
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c line 227
Quoted 8 lines
drivers/irqchip/irq-mvebu-sei.c new file +8
-0+ +if (!sei) +return 0 ; + +if (sei->dev->of_node != node) +return 0 ; + +if (!d || !sei->ap_domain)... how can it be NULL here? Actually, how can it *ever* be NULL?
Over-sanitization indeed. d cannot be NULL.
Quoted 2 lines
Also, it is not clear to me how can sei or
sei->ap_domainbe NULL.
I misread some internal functions of the core and after a second look you are right that there is no possibility for both of them to be NULL.
I'll simplify.
Quoted 43 lines
Quoted 43 lines
drivers/irqchip/irq-mvebu-sei.c new file +42
-0+return 0 ; + +if (d == sei->ap_domain) +return 1 ; + +return 0 ; +} + +static const struct irq_domain_ops mvebu_sei_ap_domain_ops = { + .match = mvebu_sei_ap_match, + .xlate = irq_domain_xlate_onecell, + .alloc = mvebu_sei_irq_domain_alloc, + .free = mvebu_sei_irq_domain_free, +}; + +static const struct irq_domain_ops mvebu_sei_cp_domain_ops = { + .alloc = mvebu_sei_irq_domain_alloc, + .free = mvebu_sei_irq_domain_free, +}; + +static struct irq_chip mvebu_sei_msi_irq_chip = { + .name ="SEI MSI controller" , + .irq_set_type = mvebu_sei_cp_set_type, +}; + +static struct msi_domain_ops mvebu_sei_msi_ops = { +}; + +static struct msi_domain_info mvebu_sei_msi_domain_info = { + .flags =MSI_FLAG_USE_DEF_DOM_OPS |MSI_FLAG_USE_DEF_CHIP_OPS , + .ops = &mvebu_sei_msi_ops, + .chip = &mvebu_sei_msi_irq_chip, +}; + +static void mvebu_sei_handle_cascade_irq (struct irq_desc *desc) +{ +struct mvebu_sei *sei =irq_desc_get_handler_data (desc); +struct irq_chip *chip =irq_desc_get_chip (desc); +unsigned long irqmap, irq_bit; +u32 reg_idx, virq, irqn; + +chained_irq_enter (chip, desc);
Quoted 8 lines
drivers/irqchip/irq-mvebu-sei.c line 273
Quoted 4 lines
drivers/irqchip/irq-mvebu-sei.c new file +4
-0+ +/* Read both SEI cause registers (64 bits) */ +for (reg_idx =0 ; reg_idx <SEI_IRQ_REG_COUNT ; reg_idx++) { + irqmap =readl_relaxed (sei->base +GICP_SECR (reg_idx));I think it'd be a lot clearer if you just read both 32bit registers and treat the resulting 64bit quantity as a single bitmap, rather than having this outer loop.
True, there is no need for this outer loop once the bitmap is 64-bit wide.
Quoted 19 lines
Quoted 19 lines
drivers/irqchip/irq-mvebu-sei.c new file +18
-0+ +/* Call handler for each set bit */ +for_each_set_bit (irq_bit, &irqmap,SEI_IRQ_COUNT_PER_REG ) { +/* Cause Register gives the SEI number */ + irqn = irq_bit + reg_idx *SEI_IRQ_COUNT_PER_REG ; +/* +* Finding Linux mapping (virq) needs the right domain +* and the relative hwirq (which start at 0 in both +* cases, while irqn is relative to all SEI interrupts). +*/ +if (irqn < sei->ap_interrupts.number) { + virq =irq_find_mapping (sei->ap_domain, irqn); + }else { + irqn -= sei->ap_interrupts.number; + virq =irq_find_mapping (sei->cp_domain, irqn); + } + +/* Call IRQ handler */
Quoted 8 lines
drivers/irqchip/irq-mvebu-sei.c line 296
Quoted 5 lines
drivers/irqchip/irq-mvebu-sei.c new file +5
-0+generic_handle_irq (virq); + } + +/* Clear interrupt indication by writing 1 to it */ +writel (irqmap, sei->base +GICP_SECR (reg_idx));
write_relaxed. It would probably make sense not to write to that register if no bits were set to 1 the first place.
Will do it.
Quoted 53 lines
Quoted 53 lines
drivers/irqchip/irq-mvebu-sei.c new file +52
-0+ } + +chained_irq_exit (chip, desc); +} + +static int mvebu_sei_probe (struct platform_device *pdev) +{ +struct device_node *node = pdev->dev.of_node, *parent; +struct irq_domain *parent_domain, *plat_domain; +struct mvebu_sei *sei; +const __be32 *property; +u32 parent_irq, size; +int ret; + + sei =devm_kzalloc (&pdev->dev,sizeof (*sei),GFP_KERNEL ); +if (!sei) +return -ENOMEM ; + + sei->dev = &pdev->dev; + +mutex_init (&sei->cp_msi_lock); + + sei->res =platform_get_resource (pdev,IORESOURCE_MEM ,0 ); + sei->base =devm_ioremap_resource (sei->dev, sei->res); +if (!sei->base) { +dev_err (sei->dev,"Failed to remap SEI resource\n" ); +return -ENODEV ; + } + +mvebu_sei_reset (sei); + +/* +* Reserve the single (top-level) parent SPI IRQ from which all the +* interrupts handled by this driver will be signaled. +*/ + parent_irq =irq_of_parse_and_map (node,0 ); +if (parent_irq <=0 ) { +dev_err (sei->dev,"Failed to retrieve top-level SPI IRQ\n" ); +return -ENODEV ; + } + +irq_set_chained_handler (parent_irq, mvebu_sei_handle_cascade_irq); +irq_set_handler_data (parent_irq, sei); + +/* +* SEIs can be triggered from the AP through wired interrupts and from +* the CPs through MSIs. +*/ + +/* Get a reference to the parent domain to create a hierarchy */ + parent =of_irq_find_parent (node); +if (!parent) {
Quoted 8 lines
drivers/irqchip/irq-mvebu-sei.c line 352
Quoted 4 lines
drivers/irqchip/irq-mvebu-sei.c new file +4
-0+dev_err (sei->dev,"Failed to find parent IRQ node\n" ); + ret = -ENODEV ; + goto dispose_irq; + }Wouldn't it make sense to check this before having configured things with a chained handler? i.e. check everything that can be checked, and once you have everything in order, do the plumbing?
Ok
Quoted 97 lines
Quoted 84 lines
drivers/irqchip/irq-mvebu-sei.c new file +82
-0+ + parent_domain =irq_find_host (parent); +if (!parent_domain) { +dev_err (sei->dev,"Failed to find parent IRQ domain\n" ); + ret = -ENODEV ; + goto dispose_irq; + } + +/* +* Retrieve the IRQ organization (AP/CP): the index of the first one and +* the number of them for each domain. +*/ + property =of_get_property (node,"marvell,sei-ap-ranges" , &size); +if (!property || (size != (2 *sizeof (u32)))) { +dev_err (sei->dev,"Missing 'marvell,sei-ap-ranges' property\n" ); + ret = -ENODEV ; + goto dispose_irq; + } + + sei->ap_interrupts.first =be32_to_cpu (property[0 ]); + sei->ap_interrupts.number =be32_to_cpu (property[1 ]); + + property =of_get_property (node,"marvell,sei-cp-ranges" , &size); +if (!property || (size != (2 *sizeof (u32)))) { +dev_err (sei->dev,"Missing 'marvell,sei-cp-ranges' property\n" ); + ret = -ENODEV ; + goto dispose_irq; + } + + sei->cp_interrupts.first =be32_to_cpu (property[0 ]); + sei->cp_interrupts.number =be32_to_cpu (property[1 ]); + +/* Create the 'wired' hierarchy */ + sei->ap_domain =irq_domain_create_hierarchy (parent_domain,0 , + sei->ap_interrupts.number, +of_node_to_fwnode (node), + &mvebu_sei_ap_domain_ops, + sei); +if (!sei->ap_domain) { +dev_err (sei->dev,"Failed to create AP IRQ domain\n" ); + ret = -ENOMEM ; + goto dispose_irq; + } + +/* Create the 'MSI' hierarchy */ + sei->cp_domain =irq_domain_create_hierarchy (parent_domain,0 , + sei->cp_interrupts.number, +of_node_to_fwnode (node), + &mvebu_sei_cp_domain_ops, + sei); +if (!sei->cp_domain) { +pr_err ("Failed to create CPs IRQ domain\n" ); + ret = -ENOMEM ; + goto remove_ap_domain; + } + + plat_domain =platform_msi_create_irq_domain (of_node_to_fwnode (node), + &mvebu_sei_msi_domain_info, + sei->cp_domain); +if (!plat_domain) { +pr_err ("Failed to create CPs MSI domain\n" ); + ret = -ENOMEM ; + goto remove_cp_domain; + } + +platform_set_drvdata (pdev, sei); + +return 0 ; + +remove_cp_domain: +irq_domain_remove (sei->cp_domain); +remove_ap_domain: +irq_domain_remove (sei->ap_domain); +dispose_irq: +irq_dispose_mapping (parent_irq); + +return ret; +} + +static const struct of_device_id mvebu_sei_of_match[] = { + { .compatible ="marvell,armada-8k-sei" , }, + {},drivers/irqchip/irq-mvebu-sei.c line 444
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+}; + +static struct platform_driver mvebu_sei_driver = { + .probe = mvebu_sei_probe, + .driver = { + .name ="mvebu-sei" , + .of_match_table = mvebu_sei_of_match, + }, +}; +builtin_platform_driver (mvebu_sei_driver);Thanks,
Quoted 1 line
M.
Thanks,
Miqu?l
PATCH v3 11/17 arm64: marvell: enable SEI driver
In reply to Miquel RaynalEnable the newly introduced Marvell SEI driver for the 64-bit Marvell EBU platforms.
--- arch/arm64/Kconfig.platforms | 1 + 1 file changed, 1 insertion(+)
arch/arm64/Kconfig.platforms +1 -0
@@ -121,6 +121,7 @@ config ARCH_MVEBU select MVEBU_ICU select MVEBU_ODMI select MVEBU_PIC + select MVEBU_SEI select OF_GPIO select PINCTRL select PINCTRL_ARMADA_37XX
Signature · 1 line
2.14.1
PATCH v3 12/17 irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
In reply to Miquel RaynalAn SEI driver provides an MSI domain through which it is possible to raise SEIs.
Handle the NSR probe function in a more generic way to support other type of interrupts (ie. the SEIs).
For clarity we do not use tree IRQ domains for now but linear ones instead, allocating the 207 ICU lines for each interrupt group. Reallocating an ICU slot is prevented by the use of an ICU-wide bitmap.
--- drivers/irqchip/irq-mvebu-icu.c | 152 ++++++++++++++++++++++++++++++++++------ 1 file changed, 129 insertions(+), 23 deletions(-)
drivers/irqchip/irq-mvebu-icu.c +129 -23
@@ -28,6 +28,10 @@#define ICU_SETSPI_NSR_AH 0x14#define ICU_CLRSPI_NSR_AL 0x18#define ICU_CLRSPI_NSR_AH 0x1c +#define ICU_SET_SEI_AL 0x50 +#define ICU_SET_SEI_AH 0x54 +#define ICU_CLR_SEI_AL 0x58 +#define ICU_CLR_SEI_AH 0x5C#define ICU_INT_CFG (x) (0x100 + 4 * (x))#define ICU_INT_ENABLE BIT(24)#define ICU_IS_EDGE BIT(28)
@@ -38,12 +42,28 @@#define ICU_SATA0_ICU_ID 109#define ICU_SATA1_ICU_ID 107 +struct mvebu_icu_subset_data { +unsigned int icu_group; +unsigned int offset_set_ah; +unsigned int offset_set_al; +unsigned int offset_clr_ah; +unsigned int offset_clr_al; +}; +struct mvebu_icu {struct irq_chip irq_chip;struct regmap *regmap;struct device *dev; -atomic_t initialized;bool legacy_bindings; +/* Lock on interrupt allocations/releases */ +struct mutex msi_lock; + DECLARE_BITMAP(msi_bitmap, ICU_MAX_IRQS); +}; + +struct mvebu_icu_msi_data { +struct mvebu_icu *icu; +atomic_t initialized; +const struct mvebu_icu_subset_data *subset_data; };struct mvebu_icu_irq_data {
@@ -76,16 +96,25 @@ static struct mvebu_icu *mvebu_icu_dev_get_drvdata(struct platform_device *pdev)return icu; } -static void mvebu_icu_init (struct mvebu_icu *icu,struct msi_msg *msg) +static void mvebu_icu_init (struct mvebu_icu *icu,struct irq_domain *d, +struct msi_msg *msg) { -if (atomic_cmpxchg (&icu->initialized, false, true)) +struct mvebu_icu_msi_data *msi_data =platform_msi_get_host_data (d); +const struct mvebu_icu_subset_data *subset = msi_data->subset_data; + +if (atomic_cmpxchg (&msi_data->initialized, false, true)) +return ; + +/* Set 'SET' ICU SPI message address in AP */ +regmap_write (icu->regmap, subset->offset_set_ah, msg[0 ].address_hi); +regmap_write (icu->regmap, subset->offset_set_al, msg[0 ].address_lo); + +if (subset->icu_group !=ICU_GRP_NSR )return ; -/* Set Clear/Set ICU SPI message address in AP */ -regmap_write (icu->regmap,ICU_SETSPI_NSR_AH , msg[0 ].address_hi); -regmap_write (icu->regmap,ICU_SETSPI_NSR_AL , msg[0 ].address_lo); -regmap_write (icu->regmap,ICU_CLRSPI_NSR_AH , msg[1 ].address_hi); -regmap_write (icu->regmap,ICU_CLRSPI_NSR_AL , msg[1 ].address_lo); +/* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */ +regmap_write (icu->regmap, subset->offset_clr_ah, msg[1 ].address_hi); +regmap_write (icu->regmap, subset->offset_clr_al, msg[1 ].address_lo); }static void mvebu_icu_write_msg (struct msi_desc *desc,struct msi_msg *msg)
@@ -96,8 +125,8 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)unsigned int icu_int;if (msg->address_lo || msg->address_hi) { -/* One off initialization */ -mvebu_icu_init (icu, msg); +/* One off initialization per domain */ +mvebu_icu_init (icu, d->domain, msg);/* Configure the ICU with irq number & type */ icu_int = msg->data |ICU_INT_ENABLE ;if (icu_irqd->type &IRQ_TYPE_EDGE_RISING )
@@ -131,7 +160,8 @@ static int mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type) { - struct mvebu_icu *icu = platform_msi_get_host_data(d); + struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d); +struct mvebu_icu *icu = msi_data->icu;unsigned int param_count = icu->legacy_bindings ?3 :2 ;/* Check the count of the parameters in dt */
@@ -143,7 +173,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,if (icu->legacy_bindings) { *hwirq = fwspec->param[1 ]; - *type = fwspec->param[2 ]; + *type = fwspec->param[2 ] &IRQ_TYPE_SENSE_MASK ;if (fwspec->param[0 ] !=ICU_GRP_NSR ) {dev_err (icu->dev,"wrong ICU group type %x\n" , fwspec->param[0 ]);
@@ -151,7 +181,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, } }else { *hwirq = fwspec->param[0 ]; - *type = fwspec->param[1 ]; + *type = fwspec->param[1 ] &IRQ_TYPE_SENSE_MASK ; }if (*hwirq >=ICU_MAX_IRQS ) {
@@ -159,12 +189,36 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,return -EINVAL ; } -/* Mask the type to prevent wrong DT configuration */ - *type = fwspec->param[2 ] &IRQ_TYPE_SENSE_MASK ; +/* +* The ICU receives level-interrupts. MSI SEI are +* edge-interrupts while MSI NSR are level-interrupts. Update the type +* accordingly for the parent irqchip. +*/ +if (msi_data->subset_data->icu_group ==ICU_GRP_SEI ) + *type =IRQ_TYPE_EDGE_RISING ;return 0 ; } +static int mvebu_icu_msi_bitmap_region_alloc (struct mvebu_icu *icu,int hwirq) +{ +int ret; + +mutex_lock (&icu->msi_lock); + ret =bitmap_allocate_region (icu->msi_bitmap, hwirq,0 ); +mutex_unlock (&icu->msi_lock); + +return ret; +} + +static void mvebu_icu_msi_bitmap_region_release (struct mvebu_icu *icu, +int hwirq) +{ +mutex_lock (&icu->msi_lock); +bitmap_release_region (icu->msi_bitmap, hwirq,0 ); +mutex_unlock (&icu->msi_lock); +} +static int mvebu_icu_irq_domain_alloc (struct irq_domain *domain,unsigned int virq,unsigned int nr_irqs,void *args)
@@ -172,7 +226,9 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,int err;unsigned long hwirq;struct irq_fwspec *fwspec = args; -struct mvebu_icu *icu =platform_msi_get_host_data (domain); +struct mvebu_icu_msi_data *msi_data = +platform_msi_get_host_data (domain); +struct mvebu_icu *icu = msi_data->icu;struct mvebu_icu_irq_data *icu_irqd; icu_irqd =kmalloc (sizeof (*icu_irqd),GFP_KERNEL );
@@ -186,16 +242,20 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, goto free_irqd; } + err =mvebu_icu_msi_bitmap_region_alloc (icu, hwirq); +if (err) + goto free_irqd; +if (icu->legacy_bindings) icu_irqd->icu_group = fwspec->param[0 ];else - icu_irqd->icu_group =ICU_GRP_NSR ; + icu_irqd->icu_group = msi_data->subset_data->icu_group; icu_irqd->icu = icu; err =platform_msi_domain_alloc (domain, virq, nr_irqs);if (err) {dev_err (icu->dev,"failed to allocate ICU interrupt in parent domain\n" ); - goto free_irqd; + goto free_bitmap; }/* Make sure there is no interrupt left pending by the firmware */
@@ -214,6 +274,8 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, free_msi:platform_msi_domain_free (domain, virq, nr_irqs); +free_bitmap: +mvebu_icu_msi_bitmap_region_release (icu, hwirq); free_irqd:kfree (icu_irqd);return err;
@@ -223,12 +285,17 @@ static void mvebu_icu_irq_domain_free(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs) { + struct mvebu_icu_msi_data *msi_data = +platform_msi_get_host_data (domain); +struct mvebu_icu *icu = msi_data->icu;struct irq_data *d =irq_get_irq_data (virq);struct mvebu_icu_irq_data *icu_irqd = d->chip_data;kfree (icu_irqd);platform_msi_domain_free (domain, virq, nr_irqs); + +mvebu_icu_msi_bitmap_region_release (icu, d->hwirq); } static const struct irq_domain_ops mvebu_icu_domain_ops = {
@@ -239,14 +306,29 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = {static int mvebu_icu_subset_probe (struct platform_device *pdev) { +const struct mvebu_icu_subset_data *subset; +struct mvebu_icu_msi_data *msi_data;struct device_node *msi_parent_dn;struct irq_domain *irq_domain;struct mvebu_icu *icu; + msi_data =devm_kzalloc (&pdev->dev,sizeof (*msi_data),GFP_KERNEL ); +if (!msi_data) +return -ENOMEM ; + icu =mvebu_icu_dev_get_drvdata (pdev);if (IS_ERR (icu))return PTR_ERR (icu); + subset =of_device_get_match_data (&pdev->dev); +if (!subset) { +dev_err (&pdev->dev,"Could not retrieve subset data\n" ); +return -EINVAL ; + } + + msi_data->icu = icu; + msi_data->subset_data = subset; + pdev->dev.msi_domain =of_msi_get_domain (&pdev->dev, pdev->dev.of_node,DOMAIN_BUS_PLATFORM_MSI );if (!pdev->dev.msi_domain)
@@ -256,10 +338,10 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)if (!msi_parent_dn)return -ENODEV ; - irq_domain =platform_msi_create_device_domain (&pdev->dev,ICU_MAX_IRQS , + irq_domain =platform_msi_create_device_domain (&pdev->dev,0 , mvebu_icu_write_msg, &mvebu_icu_domain_ops, - icu); + msi_data);if (!irq_domain) {dev_err (&pdev->dev,"Failed to create ICU MSI domain\n" );return -ENOMEM ;
@@ -268,9 +350,28 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)return 0 ; } +static const struct mvebu_icu_subset_data mvebu_icu_nsr_subset_data = { + .icu_group =ICU_GRP_NSR , + .offset_set_ah =ICU_SETSPI_NSR_AH , + .offset_set_al =ICU_SETSPI_NSR_AL , + .offset_clr_ah =ICU_CLRSPI_NSR_AH , + .offset_clr_al =ICU_CLRSPI_NSR_AL , +}; + +static const struct mvebu_icu_subset_data mvebu_icu_sei_subset_data = { + .icu_group =ICU_GRP_SEI , + .offset_set_ah =ICU_SET_SEI_AH , + .offset_set_al =ICU_SET_SEI_AL , +}; +static const struct of_device_id mvebu_icu_subset_of_match[] = { { .compatible ="marvell,cp110-icu-nsr" , + .data = &mvebu_icu_nsr_subset_data, + }, + { + .compatible ="marvell,cp110-icu-sei" , + .data = &mvebu_icu_sei_subset_data, }, {}, };
@@ -317,6 +418,8 @@ static int mvebu_icu_probe(struct platform_device *pdev)if (IS_ERR (icu->regmap))return PTR_ERR (icu->regmap); +mutex_init (&icu->msi_lock); + icu->irq_chip.name =devm_kasprintf (&pdev->dev,GFP_KERNEL ,"ICU.%x" , (unsigned int )res->start);
@@ -341,7 +444,7 @@ static int mvebu_icu_probe(struct platform_device *pdev)#endif /* -* Clean all ICU interrupts with type SPI_NSR, required to +* Clean all ICU interrupts of type NSR and SEI, required to * avoid unpredictable SPI assignments done by firmware. */ for (i =0 ; i <ICU_MAX_IRQS ; i++) {
@@ -350,7 +453,8 @@ static int mvebu_icu_probe(struct platform_device *pdev)regmap_read (icu->regmap,ICU_INT_CFG (i), &icu_int); icu_grp = icu_int >>ICU_GROUP_SHIFT ; -if (icu_grp ==ICU_GRP_NSR ) +if (icu_grp ==ICU_GRP_NSR || + (icu_grp ==ICU_GRP_SEI && !icu->legacy_bindings))regmap_write (icu->regmap,ICU_INT_CFG (i),0 ); }
@@ -363,7 +467,9 @@ static int mvebu_icu_probe(struct platform_device *pdev) }static const struct of_device_id mvebu_icu_of_match[] = { - { .compatible ="marvell,cp110-icu" , }, + { + .compatible ="marvell,cp110-icu" , + }, {}, };
Signature · 1 line
2.14.1
PATCH v3 12/17 irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
Quoted 8 lines · Miquel Raynal
An SEI driver provides an MSI domain through which it is possible to raise SEIs.
Handle the NSR probe function in a more generic way to support other type of interrupts (ie. the SEIs).
For clarity we do not use tree IRQ domains for now but linear ones instead, allocating the 207 ICU lines for each interrupt group.
Is that still the truth? or just a stale comment?
Quoted 39 lines
Reallocating an ICU slot is prevented by the use of an ICU-wide bitmap.
Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 152 ++++++++++++++++++++++++++++++++++------ 1 file changed, 129 insertions(+), 23 deletions(-)drivers/irqchip/irq-mvebu-icu.c +12
-1@@ -28,6 +28,10 @@#define ICU_SETSPI_NSR_AH 0x14#define ICU_CLRSPI_NSR_AL 0x18#define ICU_CLRSPI_NSR_AH 0x1c +#define ICU_SET_SEI_AL 0x50 +#define ICU_SET_SEI_AH 0x54 +#define ICU_CLR_SEI_AL 0x58 +#define ICU_CLR_SEI_AH 0x5C#define ICU_INT_CFG (x) (0x100 + 4 * (x))#define ICU_INT_ENABLE BIT(24)#define ICU_IS_EDGE BIT(28)@@ -38,12 +42,28 @@#define ICU_SATA0_ICU_ID 109#define ICU_SATA1_ICU_ID 107 +struct mvebu_icu_subset_data { +unsigned int icu_group; +unsigned int offset_set_ah; +unsigned int offset_set_al; +unsigned int offset_clr_ah; +unsigned int offset_clr_al; +}; +struct mvebu_icu {struct irq_chip irq_chip;struct regmap *regmap;struct device *dev; -atomic_t initialized;
drivers/irqchip/irq-mvebu-icu.c line 66
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +9
-0bool legacy_bindings; +/* Lock on interrupt allocations/releases */ +struct mutex msi_lock; +DECLARE_BITMAP (msi_bitmap,ICU_MAX_IRQS ); +}; + +struct mvebu_icu_msi_data { +struct mvebu_icu *icu; +atomic_t initialized; +const struct mvebu_icu_subset_data *subset_data;
I'm a bit lost here. Can you have more than a single mvebu_icu_msi_data per subset_data? I though you'd only have one. If so, what's the benefit of having a separate structure? Am I missing something?
Quoted 4 lines
drivers/irqchip/irq-mvebu-icu.c +0
-0};struct mvebu_icu_irq_data {
drivers/irqchip/irq-mvebu-icu.c static struct mvebu_icu *mvebu_icu_dev_get_drvdata(struct platform_device *pdev) · line 103
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +4
-2@@ -76,16 +96,25 @@ static struct mvebu_icu *mvebu_icu_dev_get_drvdata(struct platform_device *pdev)return icu; } -static void mvebu_icu_init (struct mvebu_icu *icu,struct msi_msg *msg) +static void mvebu_icu_init (struct mvebu_icu *icu,struct irq_domain *d, +struct msi_msg *msg) { -if (atomic_cmpxchg (&icu->initialized, false, true)) +struct mvebu_icu_msi_data *msi_data =platform_msi_get_host_data (d); +const struct mvebu_icu_subset_data *subset = msi_data->subset_data;
Since you don't use the domain for anything else than obtaining the subset_data, why don't you directly pass that subset_data as a parameter? Here, you are mixing two different abstraction levels, and that makes the whole thing really bizarre.
Quoted 263 lines
drivers/irqchip/irq-mvebu-icu.c +101
-19+ +if (atomic_cmpxchg (&msi_data->initialized, false, true)) +return ; + +/* Set 'SET' ICU SPI message address in AP */ +regmap_write (icu->regmap, subset->offset_set_ah, msg[0 ].address_hi); +regmap_write (icu->regmap, subset->offset_set_al, msg[0 ].address_lo); + +if (subset->icu_group !=ICU_GRP_NSR )return ; -/* Set Clear/Set ICU SPI message address in AP */ -regmap_write (icu->regmap,ICU_SETSPI_NSR_AH , msg[0 ].address_hi); -regmap_write (icu->regmap,ICU_SETSPI_NSR_AL , msg[0 ].address_lo); -regmap_write (icu->regmap,ICU_CLRSPI_NSR_AH , msg[1 ].address_hi); -regmap_write (icu->regmap,ICU_CLRSPI_NSR_AL , msg[1 ].address_lo); +/* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */ +regmap_write (icu->regmap, subset->offset_clr_ah, msg[1 ].address_hi); +regmap_write (icu->regmap, subset->offset_clr_al, msg[1 ].address_lo); }static void mvebu_icu_write_msg (struct msi_desc *desc,struct msi_msg *msg)@@ -96,8 +125,8 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)unsigned int icu_int;if (msg->address_lo || msg->address_hi) { -/* One off initialization */ -mvebu_icu_init (icu, msg); +/* One off initialization per domain */ +mvebu_icu_init (icu, d->domain, msg);/* Configure the ICU with irq number & type */ icu_int = msg->data |ICU_INT_ENABLE ;if (icu_irqd->type &IRQ_TYPE_EDGE_RISING )@@ -131,7 +160,8 @@ static int mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type) { - struct mvebu_icu *icu = platform_msi_get_host_data(d); + struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d); +struct mvebu_icu *icu = msi_data->icu;unsigned int param_count = icu->legacy_bindings ?3 :2 ;/* Check the count of the parameters in dt */ @@ -143,7 +173,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,if (icu->legacy_bindings) { *hwirq = fwspec->param[1 ]; - *type = fwspec->param[2 ]; + *type = fwspec->param[2 ] &IRQ_TYPE_SENSE_MASK ;if (fwspec->param[0 ] !=ICU_GRP_NSR ) {dev_err (icu->dev,"wrong ICU group type %x\n" , fwspec->param[0 ]);@@ -151,7 +181,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, } }else { *hwirq = fwspec->param[0 ]; - *type = fwspec->param[1 ]; + *type = fwspec->param[1 ] &IRQ_TYPE_SENSE_MASK ; }if (*hwirq >=ICU_MAX_IRQS ) {@@ -159,12 +189,36 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,return -EINVAL ; } -/* Mask the type to prevent wrong DT configuration */ - *type = fwspec->param[2 ] &IRQ_TYPE_SENSE_MASK ; +/* +* The ICU receives level-interrupts. MSI SEI are +* edge-interrupts while MSI NSR are level-interrupts. Update the type +* accordingly for the parent irqchip. +*/ +if (msi_data->subset_data->icu_group ==ICU_GRP_SEI ) + *type =IRQ_TYPE_EDGE_RISING ;return 0 ; } +static int mvebu_icu_msi_bitmap_region_alloc (struct mvebu_icu *icu,int hwirq) +{ +int ret; + +mutex_lock (&icu->msi_lock); + ret =bitmap_allocate_region (icu->msi_bitmap, hwirq,0 ); +mutex_unlock (&icu->msi_lock); + +return ret; +} + +static void mvebu_icu_msi_bitmap_region_release (struct mvebu_icu *icu, +int hwirq) +{ +mutex_lock (&icu->msi_lock); +bitmap_release_region (icu->msi_bitmap, hwirq,0 ); +mutex_unlock (&icu->msi_lock); +} +static int mvebu_icu_irq_domain_alloc (struct irq_domain *domain,unsigned int virq,unsigned int nr_irqs,void *args)@@ -172,7 +226,9 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,int err;unsigned long hwirq;struct irq_fwspec *fwspec = args; -struct mvebu_icu *icu =platform_msi_get_host_data (domain); +struct mvebu_icu_msi_data *msi_data = +platform_msi_get_host_data (domain); +struct mvebu_icu *icu = msi_data->icu;struct mvebu_icu_irq_data *icu_irqd; icu_irqd =kmalloc (sizeof (*icu_irqd),GFP_KERNEL );@@ -186,16 +242,20 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, goto free_irqd; } + err =mvebu_icu_msi_bitmap_region_alloc (icu, hwirq); +if (err) + goto free_irqd; +if (icu->legacy_bindings) icu_irqd->icu_group = fwspec->param[0 ];else - icu_irqd->icu_group =ICU_GRP_NSR ; + icu_irqd->icu_group = msi_data->subset_data->icu_group; icu_irqd->icu = icu; err =platform_msi_domain_alloc (domain, virq, nr_irqs);if (err) {dev_err (icu->dev,"failed to allocate ICU interrupt in parent domain\n" ); - goto free_irqd; + goto free_bitmap; }/* Make sure there is no interrupt left pending by the firmware */ @@ -214,6 +274,8 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, free_msi:platform_msi_domain_free (domain, virq, nr_irqs); +free_bitmap: +mvebu_icu_msi_bitmap_region_release (icu, hwirq); free_irqd:kfree (icu_irqd);return err;@@ -223,12 +285,17 @@ static void mvebu_icu_irq_domain_free(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs) { + struct mvebu_icu_msi_data *msi_data = +platform_msi_get_host_data (domain); +struct mvebu_icu *icu = msi_data->icu;struct irq_data *d =irq_get_irq_data (virq);struct mvebu_icu_irq_data *icu_irqd = d->chip_data;kfree (icu_irqd);platform_msi_domain_free (domain, virq, nr_irqs); + +mvebu_icu_msi_bitmap_region_release (icu, d->hwirq); } static const struct irq_domain_ops mvebu_icu_domain_ops = {@@ -239,14 +306,29 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = {static int mvebu_icu_subset_probe (struct platform_device *pdev) { +const struct mvebu_icu_subset_data *subset; +struct mvebu_icu_msi_data *msi_data;struct device_node *msi_parent_dn;struct irq_domain *irq_domain;struct mvebu_icu *icu; + msi_data =devm_kzalloc (&pdev->dev,sizeof (*msi_data),GFP_KERNEL ); +if (!msi_data) +return -ENOMEM ; + icu =mvebu_icu_dev_get_drvdata (pdev);if (IS_ERR (icu))return PTR_ERR (icu); + subset =of_device_get_match_data (&pdev->dev); +if (!subset) { +dev_err (&pdev->dev,"Could not retrieve subset data\n" ); +return -EINVAL ; + } + + msi_data->icu = icu; + msi_data->subset_data = subset; + pdev->dev.msi_domain =of_msi_get_domain (&pdev->dev, pdev->dev.of_node,DOMAIN_BUS_PLATFORM_MSI );if (!pdev->dev.msi_domain)@@ -256,10 +338,10 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)if (!msi_parent_dn)return -ENODEV ; - irq_domain =platform_msi_create_device_domain (&pdev->dev,ICU_MAX_IRQS , + irq_domain =platform_msi_create_device_domain (&pdev->dev,0 , mvebu_icu_write_msg, &mvebu_icu_domain_ops, - icu); + msi_data);if (!irq_domain) {dev_err (&pdev->dev,"Failed to create ICU MSI domain\n" );return -ENOMEM ;@@ -268,9 +350,28 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)return 0 ; } +static const struct mvebu_icu_subset_data mvebu_icu_nsr_subset_data = { + .icu_group =ICU_GRP_NSR , + .offset_set_ah =ICU_SETSPI_NSR_AH , + .offset_set_al =ICU_SETSPI_NSR_AL , + .offset_clr_ah =ICU_CLRSPI_NSR_AH , + .offset_clr_al =ICU_CLRSPI_NSR_AL , +}; + +static const struct mvebu_icu_subset_data mvebu_icu_sei_subset_data = { + .icu_group =ICU_GRP_SEI , + .offset_set_ah =ICU_SET_SEI_AH , + .offset_set_al =ICU_SET_SEI_AL , +}; +static const struct of_device_id mvebu_icu_subset_of_match[] = { { .compatible ="marvell,cp110-icu-nsr" , + .data = &mvebu_icu_nsr_subset_data, + }, + { + .compatible ="marvell,cp110-icu-sei" , + .data = &mvebu_icu_sei_subset_data, }, {}, };@@ -317,6 +418,8 @@ static int mvebu_icu_probe(struct platform_device *pdev)if (IS_ERR (icu->regmap))return PTR_ERR (icu->regmap); +mutex_init (&icu->msi_lock); + icu->irq_chip.name =devm_kasprintf (&pdev->dev,GFP_KERNEL ,"ICU.%x" , (unsigned int )res->start);@@ -341,7 +444,7 @@ static int mvebu_icu_probe(struct platform_device *pdev)#endif /* -* Clean all ICU interrupts with type SPI_NSR, required to +* Clean all ICU interrupts of type NSR and SEI, required to * avoid unpredictable SPI assignments done by firmware. */ for (i =0 ; i <ICU_MAX_IRQS ; i++) {@@ -350,7 +453,8 @@ static int mvebu_icu_probe(struct platform_device *pdev)regmap_read (icu->regmap,ICU_INT_CFG (i), &icu_int); icu_grp = icu_int >>ICU_GROUP_SHIFT ; -if (icu_grp ==ICU_GRP_NSR ) +if (icu_grp ==ICU_GRP_NSR || + (icu_grp ==ICU_GRP_SEI && !icu->legacy_bindings))regmap_write (icu->regmap,ICU_INT_CFG (i),0 ); }
drivers/irqchip/irq-mvebu-icu.c static int mvebu_icu_probe(struct platform_device *pdev) · line 475
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +3
-1@@ -363,7 +467,9 @@ static int mvebu_icu_probe(struct platform_device *pdev) }static const struct of_device_id mvebu_icu_of_match[] = { - { .compatible ="marvell,cp110-icu" , }, + { + .compatible ="marvell,cp110-icu" , + }, {}, };
I'm close to loosing the plot with this driver, so I'll stop here and see what you have to say... ;-)
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
PATCH v3 12/17 irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
Hi Marc,
Marc Zyngier <[email protected]> wrote on Thu, 28 Jun 2018 17:49:55 +0100:
Quoted 11 lines · Miquel Raynal
Quoted 8 lines
An SEI driver provides an MSI domain through which it is possible to raise SEIs.
Handle the NSR probe function in a more generic way to support other type of interrupts (ie. the SEIs).
For clarity we do not use tree IRQ domains for now but linear ones instead, allocating the 207 ICU lines for each interrupt group.
Is that still the truth? or just a stale comment?
Stale comment, will remove it.
Quoted 46 lines
Quoted 46 lines
Reallocating an ICU slot is prevented by the use of an ICU-wide bitmap.
Signed-off-by Miquel Raynal <[email protected]>--- drivers/irqchip/irq-mvebu-icu.c | 152 ++++++++++++++++++++++++++++++++++------ 1 file changed, 129 insertions(+), 23 deletions(-)drivers/irqchip/irq-mvebu-icu.c +17
-1@@ -28,6 +28,10 @@#define ICU_SETSPI_NSR_AH 0x14#define ICU_CLRSPI_NSR_AL 0x18#define ICU_CLRSPI_NSR_AH 0x1c +#define ICU_SET_SEI_AL 0x50 +#define ICU_SET_SEI_AH 0x54 +#define ICU_CLR_SEI_AL 0x58 +#define ICU_CLR_SEI_AH 0x5C#define ICU_INT_CFG (x) (0x100 + 4 * (x))#define ICU_INT_ENABLE BIT(24)#define ICU_IS_EDGE BIT(28)@@ -38,12 +42,28 @@#define ICU_SATA0_ICU_ID 109#define ICU_SATA1_ICU_ID 107 +struct mvebu_icu_subset_data { +unsigned int icu_group; +unsigned int offset_set_ah; +unsigned int offset_set_al; +unsigned int offset_clr_ah; +unsigned int offset_clr_al; +}; +struct mvebu_icu {struct irq_chip irq_chip;struct regmap *regmap;struct device *dev; -atomic_t initialized;bool legacy_bindings; +/* Lock on interrupt allocations/releases */ +struct mutex msi_lock; + DECLARE_BITMAP(msi_bitmap, ICU_MAX_IRQS); +}; +
Quoted 8 lines
drivers/irqchip/irq-mvebu-icu.c line 66
Quoted 4 lines
drivers/irqchip/irq-mvebu-icu.c +4
-0+struct mvebu_icu_msi_data { +struct mvebu_icu *icu; +atomic_t initialized; +const struct mvebu_icu_subset_data *subset_data;I'm a bit lost here. Can you have more than a single
mvebu_icu_msi_datapersubset_data? I though you'd only have one. If so, what's the benefit of having a separate structure? Am I missing something?
The reason for having a pointer on another structure is because I use of_device_get_match_data() to retrieve a different structure per compatible
(ie. per subset).
Quoted 8 lines
Quoted 8 lines
drivers/irqchip/irq-mvebu-icu.c +0
-0};struct mvebu_icu_irq_data {@@ -76,16 +96,25 @@ static struct mvebu_icu *mvebu_icu_dev_get_drvdata(struct platform_device *pdev)return icu; }
Quoted 12 lines
drivers/irqchip/irq-mvebu-icu.c static struct mvebu_icu *mvebu_icu_dev_get_drvdata(struct platform_device *pdev) · line 103
Quoted 7 lines
drivers/irqchip/irq-mvebu-icu.c +4
-2-static void mvebu_icu_init (struct mvebu_icu *icu,struct msi_msg *msg) +static void mvebu_icu_init (struct mvebu_icu *icu,struct irq_domain *d, +struct msi_msg *msg) { -if (atomic_cmpxchg (&icu->initialized, false, true)) +struct mvebu_icu_msi_data *msi_data =platform_msi_get_host_data (d); +const struct mvebu_icu_subset_data *subset = msi_data->subset_data;Since you don't use the domain for anything else than obtaining the
subset_data, why don't you directly pass thatsubset_dataas a parameter? Here, you are mixing two different abstraction levels, and that makes the whole thing really bizarre.
Indeed. Will edit this.
Quoted 265 lines
Quoted 265 lines
drivers/irqchip/irq-mvebu-icu.c +101
-19+ +if (atomic_cmpxchg (&msi_data->initialized, false, true)) +return ; + +/* Set 'SET' ICU SPI message address in AP */ +regmap_write (icu->regmap, subset->offset_set_ah, msg[0 ].address_hi); +regmap_write (icu->regmap, subset->offset_set_al, msg[0 ].address_lo); + +if (subset->icu_group !=ICU_GRP_NSR )return ; -/* Set Clear/Set ICU SPI message address in AP */ -regmap_write (icu->regmap,ICU_SETSPI_NSR_AH , msg[0 ].address_hi); -regmap_write (icu->regmap,ICU_SETSPI_NSR_AL , msg[0 ].address_lo); -regmap_write (icu->regmap,ICU_CLRSPI_NSR_AH , msg[1 ].address_hi); -regmap_write (icu->regmap,ICU_CLRSPI_NSR_AL , msg[1 ].address_lo); +/* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */ +regmap_write (icu->regmap, subset->offset_clr_ah, msg[1 ].address_hi); +regmap_write (icu->regmap, subset->offset_clr_al, msg[1 ].address_lo); }static void mvebu_icu_write_msg (struct msi_desc *desc,struct msi_msg *msg)@@ -96,8 +125,8 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)unsigned int icu_int;if (msg->address_lo || msg->address_hi) { -/* One off initialization */ -mvebu_icu_init (icu, msg); +/* One off initialization per domain */ +mvebu_icu_init (icu, d->domain, msg);/* Configure the ICU with irq number & type */ icu_int = msg->data |ICU_INT_ENABLE ;if (icu_irqd->type &IRQ_TYPE_EDGE_RISING )@@ -131,7 +160,8 @@ static int mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type) { - struct mvebu_icu *icu = platform_msi_get_host_data(d); + struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d); +struct mvebu_icu *icu = msi_data->icu;unsigned int param_count = icu->legacy_bindings ?3 :2 ;/* Check the count of the parameters in dt */ @@ -143,7 +173,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,if (icu->legacy_bindings) { *hwirq = fwspec->param[1 ]; - *type = fwspec->param[2 ]; + *type = fwspec->param[2 ] &IRQ_TYPE_SENSE_MASK ;if (fwspec->param[0 ] !=ICU_GRP_NSR ) {dev_err (icu->dev,"wrong ICU group type %x\n" , fwspec->param[0 ]);@@ -151,7 +181,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, } }else { *hwirq = fwspec->param[0 ]; - *type = fwspec->param[1 ]; + *type = fwspec->param[1 ] &IRQ_TYPE_SENSE_MASK ; }if (*hwirq >=ICU_MAX_IRQS ) {@@ -159,12 +189,36 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,return -EINVAL ; } -/* Mask the type to prevent wrong DT configuration */ - *type = fwspec->param[2 ] &IRQ_TYPE_SENSE_MASK ; +/* +* The ICU receives level-interrupts. MSI SEI are +* edge-interrupts while MSI NSR are level-interrupts. Update the type +* accordingly for the parent irqchip. +*/ +if (msi_data->subset_data->icu_group ==ICU_GRP_SEI ) + *type =IRQ_TYPE_EDGE_RISING ;return 0 ; } +static int mvebu_icu_msi_bitmap_region_alloc (struct mvebu_icu *icu,int hwirq) +{ +int ret; + +mutex_lock (&icu->msi_lock); + ret =bitmap_allocate_region (icu->msi_bitmap, hwirq,0 ); +mutex_unlock (&icu->msi_lock); + +return ret; +} + +static void mvebu_icu_msi_bitmap_region_release (struct mvebu_icu *icu, +int hwirq) +{ +mutex_lock (&icu->msi_lock); +bitmap_release_region (icu->msi_bitmap, hwirq,0 ); +mutex_unlock (&icu->msi_lock); +} +static int mvebu_icu_irq_domain_alloc (struct irq_domain *domain,unsigned int virq,unsigned int nr_irqs,void *args)@@ -172,7 +226,9 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,int err;unsigned long hwirq;struct irq_fwspec *fwspec = args; -struct mvebu_icu *icu =platform_msi_get_host_data (domain); +struct mvebu_icu_msi_data *msi_data = +platform_msi_get_host_data (domain); +struct mvebu_icu *icu = msi_data->icu;struct mvebu_icu_irq_data *icu_irqd; icu_irqd =kmalloc (sizeof (*icu_irqd),GFP_KERNEL );@@ -186,16 +242,20 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, goto free_irqd; } + err =mvebu_icu_msi_bitmap_region_alloc (icu, hwirq); +if (err) + goto free_irqd; +if (icu->legacy_bindings) icu_irqd->icu_group = fwspec->param[0 ];else - icu_irqd->icu_group =ICU_GRP_NSR ; + icu_irqd->icu_group = msi_data->subset_data->icu_group; icu_irqd->icu = icu; err =platform_msi_domain_alloc (domain, virq, nr_irqs);if (err) {dev_err (icu->dev,"failed to allocate ICU interrupt in parent domain\n" ); - goto free_irqd; + goto free_bitmap; }/* Make sure there is no interrupt left pending by the firmware */ @@ -214,6 +274,8 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, free_msi:platform_msi_domain_free (domain, virq, nr_irqs); +free_bitmap: +mvebu_icu_msi_bitmap_region_release (icu, hwirq); free_irqd:kfree (icu_irqd);return err;@@ -223,12 +285,17 @@ static void mvebu_icu_irq_domain_free(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs) { + struct mvebu_icu_msi_data *msi_data = +platform_msi_get_host_data (domain); +struct mvebu_icu *icu = msi_data->icu;struct irq_data *d =irq_get_irq_data (virq);struct mvebu_icu_irq_data *icu_irqd = d->chip_data;kfree (icu_irqd);platform_msi_domain_free (domain, virq, nr_irqs); + +mvebu_icu_msi_bitmap_region_release (icu, d->hwirq); } static const struct irq_domain_ops mvebu_icu_domain_ops = {@@ -239,14 +306,29 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = {static int mvebu_icu_subset_probe (struct platform_device *pdev) { +const struct mvebu_icu_subset_data *subset; +struct mvebu_icu_msi_data *msi_data;struct device_node *msi_parent_dn;struct irq_domain *irq_domain;struct mvebu_icu *icu; + msi_data =devm_kzalloc (&pdev->dev,sizeof (*msi_data),GFP_KERNEL ); +if (!msi_data) +return -ENOMEM ; + icu =mvebu_icu_dev_get_drvdata (pdev);if (IS_ERR (icu))return PTR_ERR (icu); + subset =of_device_get_match_data (&pdev->dev); +if (!subset) { +dev_err (&pdev->dev,"Could not retrieve subset data\n" ); +return -EINVAL ; + } + + msi_data->icu = icu; + msi_data->subset_data = subset; + pdev->dev.msi_domain =of_msi_get_domain (&pdev->dev, pdev->dev.of_node,DOMAIN_BUS_PLATFORM_MSI );if (!pdev->dev.msi_domain)@@ -256,10 +338,10 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)if (!msi_parent_dn)return -ENODEV ; - irq_domain =platform_msi_create_device_domain (&pdev->dev,ICU_MAX_IRQS , + irq_domain =platform_msi_create_device_domain (&pdev->dev,0 , mvebu_icu_write_msg, &mvebu_icu_domain_ops, - icu); + msi_data);if (!irq_domain) {dev_err (&pdev->dev,"Failed to create ICU MSI domain\n" );return -ENOMEM ;@@ -268,9 +350,28 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)return 0 ; } +static const struct mvebu_icu_subset_data mvebu_icu_nsr_subset_data = { + .icu_group =ICU_GRP_NSR , + .offset_set_ah =ICU_SETSPI_NSR_AH , + .offset_set_al =ICU_SETSPI_NSR_AL , + .offset_clr_ah =ICU_CLRSPI_NSR_AH , + .offset_clr_al =ICU_CLRSPI_NSR_AL , +}; + +static const struct mvebu_icu_subset_data mvebu_icu_sei_subset_data = { + .icu_group =ICU_GRP_SEI , + .offset_set_ah =ICU_SET_SEI_AH , + .offset_set_al =ICU_SET_SEI_AL , +}; +static const struct of_device_id mvebu_icu_subset_of_match[] = { { .compatible ="marvell,cp110-icu-nsr" , + .data = &mvebu_icu_nsr_subset_data, + }, + { + .compatible ="marvell,cp110-icu-sei" , + .data = &mvebu_icu_sei_subset_data, }, {}, };@@ -317,6 +418,8 @@ static int mvebu_icu_probe(struct platform_device *pdev)if (IS_ERR (icu->regmap))return PTR_ERR (icu->regmap); +mutex_init (&icu->msi_lock); + icu->irq_chip.name =devm_kasprintf (&pdev->dev,GFP_KERNEL ,"ICU.%x" , (unsigned int )res->start);@@ -341,7 +444,7 @@ static int mvebu_icu_probe(struct platform_device *pdev)#endif /* -* Clean all ICU interrupts with type SPI_NSR, required to +* Clean all ICU interrupts of type NSR and SEI, required to * avoid unpredictable SPI assignments done by firmware. */ for (i =0 ; i <ICU_MAX_IRQS ; i++) {@@ -350,7 +453,8 @@ static int mvebu_icu_probe(struct platform_device *pdev)regmap_read (icu->regmap,ICU_INT_CFG (i), &icu_int); icu_grp = icu_int >>ICU_GROUP_SHIFT ; -if (icu_grp ==ICU_GRP_NSR ) +if (icu_grp ==ICU_GRP_NSR || + (icu_grp ==ICU_GRP_SEI && !icu->legacy_bindings))regmap_write (icu->regmap,ICU_INT_CFG (i),0 ); }@@ -363,7 +467,9 @@ static int mvebu_icu_probe(struct platform_device *pdev) }
Quoted 12 lines
drivers/irqchip/irq-mvebu-icu.c static int mvebu_icu_probe(struct platform_device *pdev) · line 475
Quoted 9 lines
drivers/irqchip/irq-mvebu-icu.c +3
-1static const struct of_device_id mvebu_icu_of_match[] = { - { .compatible ="marvell,cp110-icu" , }, + { + .compatible ="marvell,cp110-icu" , + }, {}, };I'm close to loosing the plot with this driver, so I'll stop here and see what you have to say... ;-)
I'll check your previous comments tomorrow, I'll probably have a few questions. Thanks for reviewing :-)
Regards,
Miqu?l
PATCH v3 13/17 dt-bindings/interrupt-controller: update Marvell ICU bindings
In reply to Miquel RaynalChange the documentation to reflect the new bindings used for Marvell ICU. This involves describing each interrupt group as a subnode of the ICU node. Each of them having their own compatible.
The DT binding documentation still documents the legacy binding, where there was a single node with no subnode.
--- .../bindings/interrupt-controller/marvell,icu.txt | 83 ++++++++++++++++++---- 1 file changed, 71 insertions(+), 12 deletions(-)
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt +71 -12
@@ -5,6 +5,8 @@ The Marvell ICU (Interrupt Consolidation Unit) controller is responsible for collecting all wired-interrupt sources in the CP and communicating them to the GIC in the AP, the unit translates interrupt requests on input wires to MSG memory mapped transactions to the GIC. +These messages will access a different GIC memory area depending on +their type (NSR, SR, SEI, REI, etc). Required properties:
@@ -12,20 +14,19 @@ Required properties: - reg: Should contain ICU registers location and length. +Subnodes: Each group of interrupt is declared as a subnode of the ICU, +with their own compatible. + +Required properties for the icu_nsr/icu_sei subnodes: + +- compatible: Should be "marvell,cp110-icu-nsr" or "marvell,cp110-icu-sei". + - #interrupt-cells: Specifies the number of cells needed to encode an - interrupt source. The value shall be 3. + interrupt source. The value shall be 2. - The 1st cell is the group type of the ICU interrupt. Possible group - types are: + The 1st cell is the index of the interrupt in the ICU unit. - ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure - ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure - ICU_GRP_SEI (0x4) : System error interrupt - ICU_GRP_REI (0x5) : RAM error interrupt - - The 2nd cell is the index of the interrupt in the ICU unit. - - The 3rd cell is the type of the interrupt. See arm,gic.txt for + The 2nd cell is the type of the interrupt. See arm,gic.txt for details. - interrupt-controller: Identifies the node as an interrupt
@@ -35,17 +36,75 @@ Required properties: that allows to trigger interrupts using MSG memory mapped transactions. +Note: each 'interrupts' property referring to any 'icu_xxx' node shall + have a different number within [0:206]. + Example: icu: interrupt-controller at 1e0000 { compatible = "marvell,cp110-icu"; reg = <0x1e0000 0x440>; + + CP110_LABEL(icu_nsr): interrupt-controller at 10 { + compatible = "marvell,cp110-icu-nsr"; + reg = <0x10 0x20>; + #interrupt-cells = <2>; + interrupt-controller; + msi-parent = <&gicp>; + }; + + CP110_LABEL(icu_sei): interrupt-controller at 50 { + compatible = "marvell,cp110-icu-sei"; + reg = <0x50 0x10>; + #interrupt-cells = <2>; + interrupt-controller; + msi-parent = <&sei>; + }; +}; + +node1 { + interrupt-parent = <&icu_nsr>; + interrupts = <106 IRQ_TYPE_LEVEL_HIGH>; +}; + +node2 { + interrupt-parent = <&icu_sei>; + interrupts = <107 IRQ_TYPE_LEVEL_HIGH>; +}; + +/* Would not work with the above nodes */ +node3 { + interrupt-parent = <&icu_nsr>; + interrupts = <107 IRQ_TYPE_LEVEL_HIGH>; +}; + +Note on legacy bindings: +Before using a subnode for each domain, only NSR were +supported. Bindings were different in this way: + +- #interrupt-cells: The value was 3. + The 1st cell was the group type of the ICU interrupt. Possible + group types were: + ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure + ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure + ICU_GRP_SEI (0x4) : System error interrupt + ICU_GRP_REI (0x5) : RAM error interrupt + The 2nd cell was the index of the interrupt in the ICU unit. + The 3rd cell was the type of the interrupt. See arm,gic.txt for + details. + +Example: + +icu: interrupt-controller at 1e0000 { + compatible = "marvell,cp110-icu"; + reg = <0x1e0000 0x440>; + #interrupt-cells = <3>; interrupt-controller; msi-parent = <&gicp>; }; -usb3h0: usb3 at 500000 { +node1 { interrupt-parent = <&icu>; interrupts = <ICU_GRP_NSR 106 IRQ_TYPE_LEVEL_HIGH>; };
Signature · 1 line
2.14.1
PATCH v3 14/17 dt-bindings/interrupt-controller: add documentation for Marvell SEI controller
In reply to Miquel RaynalDescribe the System Error Interrupt (SEI) controller. It aggregates two types of interrupts, wired and MSIs from respectively the AP and the CPs, into a single SPI interrupt.
--- .../bindings/interrupt-controller/marvell,sei.txt | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt new file +39 -0
@@ -0,0 +1,39 @@ +Marvell SEI (System Error Interrupt) Controller +----------------------------------------------- + +Marvell SEI (System Error Interrupt) controller is an interrupt +aggregator. It receives interrupts from several sources and aggregates +them to a single interrupt line (an SPI) on the parent interrupt +controller. + +This interrupt controller can handle up to 64 SEIs, a set comes from the +AP and is wired while a second set comes from the CPs by the mean of +MSIs. Each 'domain' is represented as a subnode. + +Required properties: + +- compatible: should be "marvell,armada-8k-sei". +- reg: SEI registers location and length. +- interrupts: identifies the parent IRQ that will be triggered. +- marvell,sei-ranges: two ranges, the first one describe wired + interrupts coming from the AP, the second one + non-wired interrupts (MSI) from the CPs. +- #interrupt-cells: number of cells to define an SEI wired interrupt + coming from the AP, should be 1. The cell is the IRQ + number. +- interrupt-controller: identifies the node as an interrupt controller + (AP only). +- msi-controller: identifies the node as an MSI controller (CPs only). + +Example: + + sei: interrupt-controller at 3f0200 { + compatible = "marvell,armada-8k-sei"; + reg = <0x3f0200 0x40>; + interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>; + marvell,sei-ap-ranges = <0 21>; + marvell,sei-cp-ranges = <21 43>; + #interrupt-cells = <1>; + interrupt-controller; + msi-controller; + };
Signature · 1 line
2.14.1
PATCH v3 15/17 arm64: dts: marvell: add AP806 SEI subnode
In reply to Miquel RaynalAdd the System Error Interrupt node, representing an IRQ chip which is part of the GIC. The SEI node has two subnodes, one for each interrupt
domain: wired (from the AP) and not-wired (MSIs from the CPs).
--- arch/arm64/boot/dts/marvell/armada-ap806.dtsi | 11 +++++++++++ 1 file changed, 11 insertions(+)
arch/arm64/boot/dts/marvell/armada-ap806.dtsi +11 -0
@@ -124,6 +124,17 @@ interrupts = <GIC_PPI 15 IRQ_TYPE_LEVEL_HIGH>; }; + sei: interrupt-controller at 3f0200 { + compatible = "marvell,armada-8k-sei"; + reg = <0x3f0200 0x40>; + interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>; + marvell,sei-ap-ranges = <0 21>; + marvell,sei-cp-ranges = <21 43>; + #interrupt-cells = <1>; + interrupt-controller; + msi-controller; + }; + xor at 400000 { compatible = "marvell,armada-7k-xor", "marvell,xor-v2"; reg = <0x400000 0x1000>,
Signature · 1 line
2.14.1
PATCH v3 16/17 arm64: dts: marvell: use new bindings for CP110 interrupts
In reply to Miquel RaynalCreate an ICU subnode for the NSR interrupts. This subnode becomes the CP110 interrupt parent, removing the need for the ICU_GRP_NSR parameter. Move all DT110 nodes to use these new bindings.
--- arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 117 ++++++++++++++------------ 1 file changed, 62 insertions(+), 55 deletions(-)
arch/arm64/boot/dts/marvell/armada-cp110.dtsi +62 -55
@@ -25,7 +25,7 @@ #address-cells = <2>; #size-cells = <2>; compatible = "simple-bus"; - interrupt-parent = <&CP110_LABEL(icu)>; + interrupt-parent = <&CP110_LABEL(icu_nsr)>; ranges; config-space at CP110_BASE {
@@ -47,12 +47,12 @@ dma-coherent; CP110_LABEL(eth0): eth0 { - interrupts = <ICU_GRP_NSR 39 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 43 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 47 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 51 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 55 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 129 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <39 IRQ_TYPE_LEVEL_HIGH>, + <43 IRQ_TYPE_LEVEL_HIGH>, + <47 IRQ_TYPE_LEVEL_HIGH>, + <51 IRQ_TYPE_LEVEL_HIGH>, + <55 IRQ_TYPE_LEVEL_HIGH>, + <129 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "tx-cpu0", "tx-cpu1", "tx-cpu2", "tx-cpu3", "rx-shared", "link"; port-id = <0>;
@@ -61,12 +61,12 @@ }; CP110_LABEL(eth1): eth1 { - interrupts = <ICU_GRP_NSR 40 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 44 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 48 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 52 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 56 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 128 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <40 IRQ_TYPE_LEVEL_HIGH>, + <44 IRQ_TYPE_LEVEL_HIGH>, + <48 IRQ_TYPE_LEVEL_HIGH>, + <52 IRQ_TYPE_LEVEL_HIGH>, + <56 IRQ_TYPE_LEVEL_HIGH>, + <128 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "tx-cpu0", "tx-cpu1", "tx-cpu2", "tx-cpu3", "rx-shared", "link"; port-id = <1>;
@@ -75,12 +75,12 @@ }; CP110_LABEL(eth2): eth2 { - interrupts = <ICU_GRP_NSR 41 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 45 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 49 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 53 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 57 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 127 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <41 IRQ_TYPE_LEVEL_HIGH>, + <45 IRQ_TYPE_LEVEL_HIGH>, + <49 IRQ_TYPE_LEVEL_HIGH>, + <53 IRQ_TYPE_LEVEL_HIGH>, + <57 IRQ_TYPE_LEVEL_HIGH>, + <127 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "tx-cpu0", "tx-cpu1", "tx-cpu2", "tx-cpu3", "rx-shared", "link"; port-id = <2>;
@@ -150,16 +150,23 @@ CP110_LABEL(icu): interrupt-controller at 1e0000 { compatible = "marvell,cp110-icu"; reg = <0x1e0000 0x440>; - #interrupt-cells = <3>; - interrupt-controller; - msi-parent = <&gicp>; + #address-cells = <1>; + #size-cells = <1>; + + CP110_LABEL(icu_nsr): interrupt-controller at 10 { + compatible = "marvell,cp110-icu-nsr"; + reg = <0x10 0x20>; + #interrupt-cells = <2>; + interrupt-controller; + msi-parent = <&gicp>; + }; }; CP110_LABEL(rtc): rtc at 284000 { compatible = "marvell,armada-8k-rtc"; reg = <0x284000 0x20>, <0x284080 0x24>; reg-names = "rtc", "rtc-soc"; - interrupts = <ICU_GRP_NSR 77 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <77 IRQ_TYPE_LEVEL_HIGH>; }; CP110_LABEL(thermal): thermal at 400078 {
@@ -185,10 +192,10 @@ #gpio-cells = <2>; gpio-ranges = <&CP110_LABEL(pinctrl) 0 0 32>; interrupt-controller; - interrupts = <ICU_GRP_NSR 86 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 85 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 84 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 83 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <86 IRQ_TYPE_LEVEL_HIGH>, + <85 IRQ_TYPE_LEVEL_HIGH>, + <84 IRQ_TYPE_LEVEL_HIGH>, + <83 IRQ_TYPE_LEVEL_HIGH>; status = "disabled"; };
@@ -200,10 +207,10 @@ #gpio-cells = <2>; gpio-ranges = <&CP110_LABEL(pinctrl) 0 32 31>; interrupt-controller; - interrupts = <ICU_GRP_NSR 82 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 81 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 80 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 79 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <82 IRQ_TYPE_LEVEL_HIGH>, + <81 IRQ_TYPE_LEVEL_HIGH>, + <80 IRQ_TYPE_LEVEL_HIGH>, + <79 IRQ_TYPE_LEVEL_HIGH>; status = "disabled"; }; };
@@ -213,7 +220,7 @@ "generic-xhci"; reg = <0x500000 0x4000>; dma-coherent; - interrupts = <ICU_GRP_NSR 106 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <106 IRQ_TYPE_LEVEL_HIGH>; clock-names = "core", "reg"; clocks = <&CP110_LABEL(clk) 1 22>, <&CP110_LABEL(clk) 1 16>;
@@ -225,7 +232,7 @@ "generic-xhci"; reg = <0x510000 0x4000>; dma-coherent; - interrupts = <ICU_GRP_NSR 105 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <105 IRQ_TYPE_LEVEL_HIGH>; clock-names = "core", "reg"; clocks = <&CP110_LABEL(clk) 1 23>, <&CP110_LABEL(clk) 1 16>;
@@ -237,7 +244,7 @@ "generic-ahci"; reg = <0x540000 0x30000>; dma-coherent; - interrupts = <ICU_GRP_NSR 107 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <107 IRQ_TYPE_LEVEL_HIGH>; clocks = <&CP110_LABEL(clk) 1 15>, <&CP110_LABEL(clk) 1 16>; status = "disabled";
@@ -290,7 +297,7 @@ reg = <0x701000 0x20>; #address-cells = <1>; #size-cells = <0>; - interrupts = <ICU_GRP_NSR 120 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <120 IRQ_TYPE_LEVEL_HIGH>; clock-names = "core", "reg"; clocks = <&CP110_LABEL(clk) 1 21>, <&CP110_LABEL(clk) 1 17>;
@@ -302,7 +309,7 @@ reg = <0x701100 0x20>; #address-cells = <1>; #size-cells = <0>; - interrupts = <ICU_GRP_NSR 121 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <121 IRQ_TYPE_LEVEL_HIGH>; clock-names = "core", "reg"; clocks = <&CP110_LABEL(clk) 1 21>, <&CP110_LABEL(clk) 1 17>;
@@ -313,7 +320,7 @@ compatible = "snps,dw-apb-uart"; reg = <0x702000 0x100>; reg-shift = <2>; - interrupts = <ICU_GRP_NSR 122 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <122 IRQ_TYPE_LEVEL_HIGH>; reg-io-width = <1>; clock-names = "baudclk", "apb_pclk"; clocks = <&CP110_LABEL(clk) 1 21>,
@@ -325,7 +332,7 @@ compatible = "snps,dw-apb-uart"; reg = <0x702100 0x100>; reg-shift = <2>; - interrupts = <ICU_GRP_NSR 123 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <123 IRQ_TYPE_LEVEL_HIGH>; reg-io-width = <1>; clock-names = "baudclk", "apb_pclk"; clocks = <&CP110_LABEL(clk) 1 21>,
@@ -337,7 +344,7 @@ compatible = "snps,dw-apb-uart"; reg = <0x702200 0x100>; reg-shift = <2>; - interrupts = <ICU_GRP_NSR 124 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <124 IRQ_TYPE_LEVEL_HIGH>; reg-io-width = <1>; clock-names = "baudclk", "apb_pclk"; clocks = <&CP110_LABEL(clk) 1 21>,
@@ -349,7 +356,7 @@ compatible = "snps,dw-apb-uart"; reg = <0x702300 0x100>; reg-shift = <2>; - interrupts = <ICU_GRP_NSR 125 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <125 IRQ_TYPE_LEVEL_HIGH>; reg-io-width = <1>; clock-names = "baudclk", "apb_pclk"; clocks = <&CP110_LABEL(clk) 1 21>,
@@ -368,7 +375,7 @@ reg = <0x720000 0x54>; #address-cells = <1>; #size-cells = <0>; - interrupts = <ICU_GRP_NSR 115 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <115 IRQ_TYPE_LEVEL_HIGH>; clock-names = "core", "reg"; clocks = <&CP110_LABEL(clk) 1 2>, <&CP110_LABEL(clk) 1 17>;
@@ -380,7 +387,7 @@ compatible = "marvell,armada-8k-rng", "inside-secure,safexcel-eip76"; reg = <0x760000 0x7d>; - interrupts = <ICU_GRP_NSR 95 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <95 IRQ_TYPE_LEVEL_HIGH>; clock-names = "core", "reg"; clocks = <&CP110_LABEL(clk) 1 25>, <&CP110_LABEL(clk) 1 17>;
@@ -390,7 +397,7 @@ CP110_LABEL(sdhci0): sdhci at 780000 { compatible = "marvell,armada-cp110-sdhci"; reg = <0x780000 0x300>; - interrupts = <ICU_GRP_NSR 27 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <27 IRQ_TYPE_LEVEL_HIGH>; clock-names = "core", "axi"; clocks = <&CP110_LABEL(clk) 1 4>, <&CP110_LABEL(clk) 1 18>; dma-coherent;
@@ -400,12 +407,12 @@ CP110_LABEL(crypto): crypto at 800000 { compatible = "inside-secure,safexcel-eip197"; reg = <0x800000 0x200000>; - interrupts = <ICU_GRP_NSR 87 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 88 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 89 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 90 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 91 IRQ_TYPE_LEVEL_HIGH>, - <ICU_GRP_NSR 92 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <87 IRQ_TYPE_LEVEL_HIGH>, + <88 IRQ_TYPE_LEVEL_HIGH>, + <89 IRQ_TYPE_LEVEL_HIGH>, + <90 IRQ_TYPE_LEVEL_HIGH>, + <91 IRQ_TYPE_LEVEL_HIGH>, + <92 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "mem", "ring0", "ring1", "ring2", "ring3", "eip"; clock-names = "core", "reg";
@@ -434,8 +441,8 @@ /* non-prefetchable memory */ 0x82000000 0 CP110_PCIEx_MEM_BASE(0) 0 CP110_PCIEx_MEM_BASE(0) 0 0xf00000>; interrupt-map-mask = <0 0 0 0>; - interrupt-map = <0 0 0 0 &CP110_LABEL(icu) ICU_GRP_NSR 22 IRQ_TYPE_LEVEL_HIGH>; - interrupts = <ICU_GRP_NSR 22 IRQ_TYPE_LEVEL_HIGH>; + interrupt-map = <0 0 0 0 &CP110_LABEL(icu_nsr) 22 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <22 IRQ_TYPE_LEVEL_HIGH>; num-lanes = <1>; clock-names = "core", "reg"; clocks = <&CP110_LABEL(clk) 1 13>, <&CP110_LABEL(clk) 1 14>;
@@ -461,8 +468,8 @@ /* non-prefetchable memory */ 0x82000000 0 CP110_PCIEx_MEM_BASE(1) 0 CP110_PCIEx_MEM_BASE(1) 0 0xf00000>; interrupt-map-mask = <0 0 0 0>; - interrupt-map = <0 0 0 0 &CP110_LABEL(icu) ICU_GRP_NSR 24 IRQ_TYPE_LEVEL_HIGH>; - interrupts = <ICU_GRP_NSR 24 IRQ_TYPE_LEVEL_HIGH>; + interrupt-map = <0 0 0 0 &CP110_LABEL(icu_nsr) 24 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <24 IRQ_TYPE_LEVEL_HIGH>; num-lanes = <1>; clock-names = "core", "reg";
@@ -489,8 +496,8 @@ /* non-prefetchable memory */ 0x82000000 0 CP110_PCIEx_MEM_BASE(2) 0 CP110_PCIEx_MEM_BASE(2) 0 0xf00000>; interrupt-map-mask = <0 0 0 0>; - interrupt-map = <0 0 0 0 &CP110_LABEL(icu) ICU_GRP_NSR 23 IRQ_TYPE_LEVEL_HIGH>; - interrupts = <ICU_GRP_NSR 23 IRQ_TYPE_LEVEL_HIGH>; + interrupt-map = <0 0 0 0 &CP110_LABEL(icu_nsr) 23 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <23 IRQ_TYPE_LEVEL_HIGH>; num-lanes = <1>; clock-names = "core", "reg";
Signature · 1 line
2.14.1
PATCH v3 17/17 arm64: dts: marvell: add CP110 ICU SEI subnode
In reply to Miquel RaynalThe ICU handles several interrupt groups, each of them being a subpart of the ICU node.
--- arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 8 ++++++++ 1 file changed, 8 insertions(+)
arch/arm64/boot/dts/marvell/armada-cp110.dtsi +8 -0
@@ -160,6 +160,14 @@ interrupt-controller; msi-parent = <&gicp>; }; + + CP110_LABEL(icu_sei): interrupt-controller at 50 { + compatible = "marvell,cp110-icu-sei"; + reg = <0x50 0x10>; + #interrupt-cells = <2>; + interrupt-controller; + msi-parent = <&sei>; + }; }; CP110_LABEL(rtc): rtc at 284000 {
Signature · 1 line
2.14.1