Add System Error Interrupt support to Armada SoCs
Subsystems (9)
Miquel Raynal
maintainerv4 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 v3:
=================
* Added an helper to create MSI tree domains.
ICU driver
----------
* Updated the code to use this helper.
* Removed the use of a regmap for the ICU subnodes.
* Squashed patches "irqchip/irq-mvebu-icu: make irq_domain local" and "irqchip/irq-mvebu-icu: disociate ICU and NSR".
* Fixed a regression: when using old bindings, no platform data was available for the NSR subset, preventing the ICU driver to probe correctly.
* Removed the stale comment in a commit log about using linear (instead of tree) domains.
* Pass a driver structure (called msi_domain) to mvebu_icu_init() intead of an IRQ domain from which the above structure was derived from.
SEI driver
----------
* Renamed the 'number' member of the mvebu_sei_interrupt_range structure into 'size' in the SEI driver.
* Used _relaxed accessors.
* Simplified the 'over' checking around the sei and sei->ap_domain pointers.
* Do not write GICP_SECR register if the irqmap read has no bit set (ie. nothing to clear, do not do the writel operation).
* Moved irq_set_chained_handler() and irq_set_handler_data() at the end of the probe.
* Simplified the chained handler with only one inner loop after having created a bitmap of the pending interrupts.
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"
Marc Zyngier (1):
genirq/msi: Allow creation of a tree-based irqdomain for platform-msi
Miquel Raynal (13):
dt-bindings/interrupt-controller: fix Marvell ICU length in the
example
irqchip/irq-mvebu-icu: fix wrong private data retrieval irqchip/irq-mvebu-icu: clarify the reset operation of configured
interrupts
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 | 125 +++--- drivers/base/platform-msi.c | 14 +- drivers/irqchip/Kconfig | 3 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-mvebu-icu.c | 259 +++++++++--- drivers/irqchip/irq-mvebu-sei.c | 440 +++++++++++++++++++++ include/linux/msi.h | 17 +- 11 files changed, 863 insertions(+), 132 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
Miquel Raynal
maintainerPATCH v4 1/14 genirq/msi: Allow creation of a tree-based irqdomain for platform-msi
From: Marc Zyngier <[email protected]>
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 · 1 line
2.14.1
Miquel Raynal
maintainerPATCH v4 2/14 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
Miquel Raynal
maintainerPATCH v4 3/14 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
Miquel Raynal
maintainerPATCH v4 4/14 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
Miquel Raynal
maintainerPATCH v4 5/14 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. The 'icu' structure is passed as driver data and not as a parameter for the same reason.
--- drivers/irqchip/irq-mvebu-icu.c | 57 +++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 25 deletions(-)
drivers/irqchip/irq-mvebu-icu.c +32 -25
@@ -39,7 +39,6 @@struct mvebu_icu {struct irq_chip irq_chip;void __iomem *base; -struct irq_domain *domain;struct device *dev;atomic_t initialized; };
@@ -204,11 +203,39 @@ 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 device *dev = &pdev->dev; +struct irq_domain *irq_domain; +struct mvebu_icu *icu; + + icu =dev_get_drvdata (dev); + + dev->msi_domain =of_msi_get_domain (dev, dev->of_node, +DOMAIN_BUS_PLATFORM_MSI ); +if (!dev->msi_domain) +return -EPROBE_DEFER ; + + msi_parent_dn =irq_domain_get_of_node (dev->msi_domain); +if (!msi_parent_dn) +return -ENODEV ; + + irq_domain =platform_msi_create_device_tree_domain (dev,ICU_MAX_IRQS , + mvebu_icu_write_msg, + &mvebu_icu_domain_ops, + icu); +if (!irq_domain) { +dev_err (dev,"Failed to create ICU MSI domain\n" ); +return -ENOMEM ; + } + +return 0 ; +} +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;int i;
@@ -240,19 +267,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.
@@ -267,16 +281,9 @@ static int mvebu_icu_probe(struct platform_device *pdev)writel_relaxed (0x0 , icu->base +ICU_INT_CFG (i)); } - icu->domain = -platform_msi_create_device_domain (&pdev->dev,ICU_MAX_IRQS , - mvebu_icu_write_msg, - &mvebu_icu_domain_ops, icu); -if (!icu->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
Miquel Raynal
maintainerPATCH v4 6/14 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 | 70 +++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 13 deletions(-)
drivers/irqchip/irq-mvebu-icu.c +57 -13
@@ -40,6 +40,7 @@ struct mvebu_icu {struct irq_chip irq_chip;void __iomem *base;struct device *dev; +bool is_legacy;atomic_t initialized; };
@@ -105,24 +106,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->is_legacy ?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->is_legacy) { + *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 ;
@@ -155,7 +160,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->is_legacy) + 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);
@@ -203,6 +211,13 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = { .free = mvebu_icu_irq_domain_free, }; +static const struct of_device_id mvebu_icu_subset_of_match[] = { + { + .compatible ="marvell,cp110-icu-nsr" , + }, + {}, +}; +static int mvebu_icu_subset_probe (struct platform_device *pdev) {struct device_node *msi_parent_dn;
@@ -210,7 +225,14 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)struct irq_domain *irq_domain;struct mvebu_icu *icu; - icu =dev_get_drvdata (dev); +/* +* Device data being populated means we are using the legacy bindings. +* Using the parent device data means we are using the new bindings. +*/ +if (dev_get_drvdata (dev)) + icu =dev_get_drvdata (dev); +else + icu =dev_get_drvdata (dev->parent); dev->msi_domain =of_msi_get_domain (dev, dev->of_node,DOMAIN_BUS_PLATFORM_MSI );
@@ -233,6 +255,15 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)return 0 ; } +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 int mvebu_icu_probe (struct platform_device *pdev) {struct mvebu_icu *icu;
@@ -259,6 +290,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->is_legacy = 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;
@@ -277,13 +317,17 @@ static int mvebu_icu_probe(struct platform_device *pdev) icu_int =readl_relaxed (icu->base +ICU_INT_CFG (i)); 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->is_legacy))writel_relaxed (0x0 , icu->base +ICU_INT_CFG (i)); }platform_set_drvdata (pdev, icu); -return mvebu_icu_subset_probe (pdev); +if (icu->is_legacy) +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 v4 6/14 irqchip/irq-mvebu-icu: support ICU subnodes
Quoted 130 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 | 70 +++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 13 deletions(-)drivers/irqchip/irq-mvebu-icu.c +42
-11@@ -40,6 +40,7 @@ struct mvebu_icu {struct irq_chip irq_chip;void __iomem *base;struct device *dev; +bool is_legacy;atomic_t initialized; };@@ -105,24 +106,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->is_legacy ?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->is_legacy) { + *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 ;@@ -155,7 +160,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->is_legacy) + 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);@@ -203,6 +211,13 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = { .free = mvebu_icu_irq_domain_free, }; +static const struct of_device_id mvebu_icu_subset_of_match[] = { + { + .compatible ="marvell,cp110-icu-nsr" , + }, + {}, +}; +static int mvebu_icu_subset_probe (struct platform_device *pdev) {struct device_node *msi_parent_dn;@@ -210,7 +225,14 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)struct irq_domain *irq_domain;struct mvebu_icu *icu; - icu =dev_get_drvdata (dev); +/* +* Device data being populated means we are using the legacy bindings. +* Using the parent device data means we are using the new bindings. +*/ +if (dev_get_drvdata (dev)) + icu =dev_get_drvdata (dev); +else + icu =dev_get_drvdata (dev->parent); dev->msi_domain =of_msi_get_domain (dev, dev->of_node,DOMAIN_BUS_PLATFORM_MSI );@@ -233,6 +255,15 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)return 0 ; } +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 int mvebu_icu_probe (struct platform_device *pdev) {struct mvebu_icu *icu;@@ -259,6 +290,15 @@ static int mvebu_icu_probe(struct platform_device *pdev)if (!icu->irq_chip.name)
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 +8
-0return -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->is_legacy = true;
I keep coming back to this issue because it really irks me to have such conditional code everywhere.
Since we know at probe time whether we have a legacy or a modern binding, can't we just flip a global static key and have two distinct paths that do not depend on a data structure? It doesn't change much for the readability of the code, but at least it doesn't give the impression that we can support some ICUs using the legacy binding and some others using the modern one.
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
Miquel Raynal
maintainerPATCH v4 7/14 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 | 440 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 444 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 +440 -0
440 lines not on this page · raw message
Signature · 1 line
2.14.1
PATCH v4 7/14 irqchip/irq-mvebu-sei: add new driver for Marvell SEI
Quoted 134 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 | 440 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 444 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 +82
-0@@ -0,0 +1,440 @@ +// 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) +#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 size; +}; + +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_relaxed (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; + +/* 1 disables the interrupt */ + reg =readl_relaxed (sei->base +GICP_SEMR (reg_idx)); + reg |=BIT (SEI_IRQ_REG_BIT (sei_irq)); +writel_relaxed (reg, sei->base +GICP_SEMR (reg_idx)); +} + +static void mvebu_sei_unmask_irq (struct irq_data *d) +{
drivers/irqchip/irq-mvebu-sei.c line 92
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; + +/* 0 enables the interrupt */ + reg =readl_relaxed (sei->base +GICP_SEMR (reg_idx)); + reg &= ~BIT (SEI_IRQ_REG_BIT (sei_irq)); +writel_relaxed (reg, sei->base +GICP_SEMR (reg_idx)); +}
Don't you need to ensure mutual exclusion between concurrent mask/unmask operations, as they seem to be hitting the same HW register?
Quoted 59 lines
drivers/irqchip/irq-mvebu-sei.c new file +59
-0+ +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 )
drivers/irqchip/irq-mvebu-sei.c line 161
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+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.size,0 );
Given that you only deal with a single bit at a time, find_first_zero_bit() + set_bit() seem like a clearer and simpler option.
Quoted 4 lines
drivers/irqchip/irq-mvebu-sei.c new file +4
-0+mutex_unlock (&sei->cp_msi_lock); +if (hwirq <0 ) +return -ENOSPC ; + }
drivers/irqchip/irq-mvebu-sei.c line 175
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+ + 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);
I'm puzzled here. I though this was a chained interrupt controller, and not a hierarchical one, since everything is funnelled through SPI32. Why are you now allocating a GIC interrupt that matches the SEI hwirq?
Quoted 5 lines
drivers/irqchip/irq-mvebu-sei.c new file +5
-0+if (ret) + goto release_region; + + ret =irq_domain_set_hwirq_and_chip (domain, virq, hwirq, irq_chip, sei); +if (ret)
drivers/irqchip/irq-mvebu-sei.c line 190
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+ 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 );
clear_bit?
Quoted 12 lines
drivers/irqchip/irq-mvebu-sei.c new file +12
-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.size + sei->cp_interrupts.size;
drivers/irqchip/irq-mvebu-sei.c line 212
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+ +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); + +mutex_lock (&sei->cp_msi_lock); +bitmap_release_region (sei->cp_msi_bitmap, d->hwirq,0 );
clear_bit?
Quoted 45 lines
drivers/irqchip/irq-mvebu-sei.c new file +45
-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 (sei->dev->of_node != node) +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 267
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); +DECLARE_BITMAP (irqmap,SEI_IRQ_COUNT ); +u32 idx, irqn; + +chained_irq_enter (chip, desc); + +/* Create the bitmap of the pending interrupts */ +for (idx =0 ; idx <BITS_TO_LONGS (SEI_IRQ_COUNT ); idx++) + irqmap[idx] =readl_relaxed (sei->base +GICP_SECR (idx));
This feels very wrong. irqmap is an array of unsigned long, and readl_relaxed operates on 32bit quantities. If unsigned long is 64bit (like it is on arm64), you will only read half of the bits you expect to deal with... Or am I missing something obvious?
Quoted 7 lines
drivers/irqchip/irq-mvebu-sei.c new file +7
-0+ +/* Call handler for each pending interrupt */ +for_each_set_bit (irqn, irqmap,SEI_IRQ_COUNT ) { +u32 virq, hwirq; + +/* +* Finding Linux mapping (virq) needs the right domain
drivers/irqchip/irq-mvebu-sei.c line 284
Quoted 10 lines
drivers/irqchip/irq-mvebu-sei.c new file +10
-0+* and the relative hwirq (which start at 0 in both +* cases, while irqn is relative to all SEI interrupts). +*/ +if (irqn < sei->ap_interrupts.size) { + hwirq = irqn; + virq =irq_find_mapping (sei->ap_domain, hwirq); + }else { + hwirq = irqn - sei->ap_interrupts.size; + virq =irq_find_mapping (sei->cp_domain, hwirq); + }
nit: consider rewriting this so that there is only a single call to irq_find_mapping():
if ((irqn < sei->ap_interrupts.size) {
domain = sei->ap_domain;
hwirq = irqn;
} else {
domain = sei->cp_domain;
hwirq = irqn - sei->ap_interrupts.size;
}
virq = irq_find_mapping(domain, hwirq);
[error on virq being 0...]drivers/irqchip/irq-mvebu-sei.c line 293
Quoted 9 lines
drivers/irqchip/irq-mvebu-sei.c new file +9
-0+ +/* Call IRQ handler */ +generic_handle_irq (virq); + } + +/* Clear the pending interrupts by writing 1 to the set bits */ +for (idx =0 ; idx <BITS_TO_LONGS (SEI_IRQ_COUNT ); idx++) +if (irqmap[idx]) +writel_relaxed (irqmap[idx], sei->base +GICP_SECR (idx));
See above.
Quoted 138 lines
drivers/irqchip/irq-mvebu-sei.c new file +137
-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 ; + } + +/* +* 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) { +dev_err (sei->dev,"Failed to find parent IRQ node\n" ); + ret = -ENODEV ; + goto dispose_irq; + } + + 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.size =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.size =be32_to_cpu (property[1 ]); + +/* Create the 'wired' hierarchy */ + sei->ap_domain =irq_domain_create_hierarchy (parent_domain,0 , + sei->ap_interrupts.size, +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.size, +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); + +irq_set_chained_handler (parent_irq, mvebu_sei_handle_cascade_irq); +irq_set_handler_data (parent_irq, 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 440
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);
I think there is something fundamentally flawed in this driver. The more I look at your ASCII diagram in the cover letter, the less I see it matching the code. Can you please explain what is going on here?
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
Miquel Raynal
maintainerPATCH v4 8/14 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
Miquel Raynal
maintainerPATCH v4 9/14 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).
Each interrupt domain is a tree domain because of maximum 207 entries. Reallocating an ICU slot is prevented by the use of an ICU-wide bitmap.
--- drivers/irqchip/irq-mvebu-icu.c | 148 +++++++++++++++++++++++++++++++++------- 1 file changed, 123 insertions(+), 25 deletions(-)
drivers/irqchip/irq-mvebu-icu.c +123 -25
@@ -26,6 +26,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)
@@ -36,12 +40,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;void __iomem *base;struct device *dev;bool is_legacy; +/* 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 {
@@ -50,28 +70,38 @@ struct mvebu_icu_irq_data {unsigned int type; }; -static void mvebu_icu_init (struct mvebu_icu *icu,struct msi_msg *msg) +static void mvebu_icu_init (struct mvebu_icu *icu, +struct mvebu_icu_msi_data *msi_data, +struct msi_msg *msg) { -if (atomic_cmpxchg (&icu->initialized, false, true)) +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 */ +writel_relaxed (msg[0 ].address_hi, icu->base + subset->offset_set_ah); +writel_relaxed (msg[0 ].address_lo, icu->base + subset->offset_set_al); + +if (subset->icu_group !=ICU_GRP_NSR )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 ); +/* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */ +writel_relaxed (msg[1 ].address_hi, icu->base + subset->offset_clr_ah); +writel_relaxed (msg[1 ].address_lo, icu->base + subset->offset_clr_al); }static void mvebu_icu_write_msg (struct msi_desc *desc,struct msi_msg *msg) {struct irq_data *d =irq_get_irq_data (desc->irq); +struct mvebu_icu_msi_data *msi_data =platform_msi_get_host_data (d->domain);struct mvebu_icu_irq_data *icu_irqd = d->chip_data;struct mvebu_icu *icu = icu_irqd->icu;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, msi_data, msg);/* Configure the ICU with irq number & type */ icu_int = msg->data |ICU_INT_ENABLE ;if (icu_irqd->type &IRQ_TYPE_EDGE_RISING )
@@ -105,7 +135,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->is_legacy ?3 :2 ;/* Check the count of the parameters in dt */
@@ -117,7 +148,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,if (icu->is_legacy) { *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 ]);
@@ -125,7 +156,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 ) {
@@ -133,12 +164,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)
@@ -146,7 +201,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 );
@@ -160,16 +217,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->is_legacy) 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 */
@@ -188,6 +249,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;
@@ -197,12 +260,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 = {
@@ -211,28 +279,54 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = { .free = mvebu_icu_irq_domain_free, }; +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, }, {}, };static int mvebu_icu_subset_probe (struct platform_device *pdev) { +struct mvebu_icu_msi_data *msi_data;struct device_node *msi_parent_dn;struct device *dev = &pdev->dev;struct irq_domain *irq_domain; -struct mvebu_icu *icu; + + msi_data =devm_kzalloc (dev,sizeof (*msi_data),GFP_KERNEL ); +if (!msi_data) +return -ENOMEM ;/* * Device data being populated means we are using the legacy bindings. * Using the parent device data means we are using the new bindings. */ -if (dev_get_drvdata (dev)) - icu =dev_get_drvdata (dev); -else - icu =dev_get_drvdata (dev->parent); +if (dev_get_drvdata (dev)) { + msi_data->icu =dev_get_drvdata (dev); + msi_data->subset_data = &mvebu_icu_nsr_subset_data; + }else { + msi_data->icu =dev_get_drvdata (dev->parent); + msi_data->subset_data =of_device_get_match_data (dev); + } dev->msi_domain =of_msi_get_domain (dev, dev->of_node,DOMAIN_BUS_PLATFORM_MSI );
@@ -246,7 +340,7 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev) irq_domain =platform_msi_create_device_tree_domain (dev,ICU_MAX_IRQS , mvebu_icu_write_msg, &mvebu_icu_domain_ops, - icu); + msi_data);if (!irq_domain) {dev_err (dev,"Failed to create ICU MSI domain\n" );return -ENOMEM ;
@@ -284,6 +378,8 @@ static int mvebu_icu_probe(struct platform_device *pdev)return PTR_ERR (icu->base); } +mutex_init (&icu->msi_lock); + icu->irq_chip.name =devm_kasprintf (&pdev->dev,GFP_KERNEL ,"ICU.%x" , (unsigned int )res->start);
@@ -308,7 +404,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++) {
@@ -331,7 +427,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 v4 9/14 irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
Quoted 2 lines · Miquel Raynal
An SEI driver provides an MSI domain through which it is possible to raise SEIs.
Is that really relevant to this patch? It is about the ICU driver, and I would expect you to explain how the ICU so far only handled NSR interrupts through GICP, but now can also generate SEIs by routing MSIs through the SEI block.
Quoted 124 lines
Handle the NSR probe function in a more generic way to support other type of interrupts (ie. the SEIs).
Each interrupt domain is a tree domain because of maximum 207 entries. 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 | 148 +++++++++++++++++++++++++++++++++------- 1 file changed, 123 insertions(+), 25 deletions(-)drivers/irqchip/irq-mvebu-icu.c +42
-11@@ -26,6 +26,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)@@ -36,12 +40,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;void __iomem *base;struct device *dev;bool is_legacy; +/* 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 {@@ -50,28 +70,38 @@ struct mvebu_icu_irq_data {unsigned int type; }; -static void mvebu_icu_init (struct mvebu_icu *icu,struct msi_msg *msg) +static void mvebu_icu_init (struct mvebu_icu *icu, +struct mvebu_icu_msi_data *msi_data, +struct msi_msg *msg) { -if (atomic_cmpxchg (&icu->initialized, false, true)) +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 */ +writel_relaxed (msg[0 ].address_hi, icu->base + subset->offset_set_ah); +writel_relaxed (msg[0 ].address_lo, icu->base + subset->offset_set_al); + +if (subset->icu_group !=ICU_GRP_NSR )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 ); +/* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */ +writel_relaxed (msg[1 ].address_hi, icu->base + subset->offset_clr_ah); +writel_relaxed (msg[1 ].address_lo, icu->base + subset->offset_clr_al); }static void mvebu_icu_write_msg (struct msi_desc *desc,struct msi_msg *msg) {struct irq_data *d =irq_get_irq_data (desc->irq); +struct mvebu_icu_msi_data *msi_data =platform_msi_get_host_data (d->domain);struct mvebu_icu_irq_data *icu_irqd = d->chip_data;struct mvebu_icu *icu = icu_irqd->icu;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, msi_data, msg);/* Configure the ICU with irq number & type */ icu_int = msg->data |ICU_INT_ENABLE ;if (icu_irqd->type &IRQ_TYPE_EDGE_RISING )@@ -105,7 +135,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->is_legacy ?3 :2 ;/* Check the count of the parameters in dt */ @@ -117,7 +148,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,if (icu->is_legacy) { *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 ]);
drivers/irqchip/irq-mvebu-icu.c mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, · line 159
Quoted 5 lines
drivers/irqchip/irq-mvebu-icu.c +1
-1@@ -125,7 +156,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 ;
Shouldn't this change (and the one just above) be moved to another patch (such as patch #6)? They feel oddly out of place here.
Quoted 6 lines
drivers/irqchip/irq-mvebu-icu.c +0
-0}if (*hwirq >=ICU_MAX_IRQS ) {@@ -133,12 +164,36 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,return -EINVAL ; }
drivers/irqchip/irq-mvebu-icu.c mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, · line 173
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +7
-2-/* 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 ;
That's interesting. How is the resampling done here?
drivers/irqchip/irq-mvebu-icu.c mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, · line 183
Quoted 10 lines
drivers/irqchip/irq-mvebu-icu.c +6
-0return 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 );
See my earlier comment about the use of find_first_free/set_bit.
Quoted 17 lines
drivers/irqchip/irq-mvebu-icu.c +13
-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)
drivers/irqchip/irq-mvebu-icu.c mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, · line 205
Quoted 6 lines
drivers/irqchip/irq-mvebu-icu.c +2
-1@@ -146,7 +201,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);
On a single line, please.
Quoted 37 lines
drivers/irqchip/irq-mvebu-icu.c +9
-2+struct mvebu_icu *icu = msi_data->icu;struct mvebu_icu_irq_data *icu_irqd; icu_irqd =kmalloc (sizeof (*icu_irqd),GFP_KERNEL );@@ -160,16 +217,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->is_legacy) 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 */ @@ -188,6 +249,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;
drivers/irqchip/irq-mvebu-icu.c static void · line 264
Quoted 5 lines
drivers/irqchip/irq-mvebu-icu.c +2
-0@@ -197,12 +260,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);
Single line.
Quoted 100 lines
drivers/irqchip/irq-mvebu-icu.c +38
-7+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 = {@@ -211,28 +279,54 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = { .free = mvebu_icu_irq_domain_free, }; +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, }, {}, };static int mvebu_icu_subset_probe (struct platform_device *pdev) { +struct mvebu_icu_msi_data *msi_data;struct device_node *msi_parent_dn;struct device *dev = &pdev->dev;struct irq_domain *irq_domain; -struct mvebu_icu *icu; + + msi_data =devm_kzalloc (dev,sizeof (*msi_data),GFP_KERNEL ); +if (!msi_data) +return -ENOMEM ;/* * Device data being populated means we are using the legacy bindings. * Using the parent device data means we are using the new bindings. */ -if (dev_get_drvdata (dev)) - icu =dev_get_drvdata (dev); -else - icu =dev_get_drvdata (dev->parent); +if (dev_get_drvdata (dev)) { + msi_data->icu =dev_get_drvdata (dev); + msi_data->subset_data = &mvebu_icu_nsr_subset_data; + }else { + msi_data->icu =dev_get_drvdata (dev->parent); + msi_data->subset_data =of_device_get_match_data (dev); + } dev->msi_domain =of_msi_get_domain (dev, dev->of_node,DOMAIN_BUS_PLATFORM_MSI );@@ -246,7 +340,7 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev) irq_domain =platform_msi_create_device_tree_domain (dev,ICU_MAX_IRQS , mvebu_icu_write_msg, &mvebu_icu_domain_ops, - icu); + msi_data);if (!irq_domain) {dev_err (dev,"Failed to create ICU MSI domain\n" );return -ENOMEM ;@@ -284,6 +378,8 @@ static int mvebu_icu_probe(struct platform_device *pdev)return PTR_ERR (icu->base); } +mutex_init (&icu->msi_lock); + icu->irq_chip.name =devm_kasprintf (&pdev->dev,GFP_KERNEL ,"ICU.%x" , (unsigned int )res->start);@@ -308,7 +404,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++) {
drivers/irqchip/irq-mvebu-icu.c static int mvebu_icu_probe(struct platform_device *pdev) · line 432
Quoted 7 lines
drivers/irqchip/irq-mvebu-icu.c +3
-1@@ -331,7 +427,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" , + },
Pointless change?
drivers/irqchip/irq-mvebu-icu.c static int mvebu_icu_probe(struct platform_device *pdev) · line 435
Quoted 4 lines
drivers/irqchip/irq-mvebu-icu.c +0
-0{}, };
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
Miquel Raynal
maintainerPATCH v4 9/14 irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
Hi Marc,
I'm fine with the rest of the comments, please find just one last question below.
[...]
Quoted 4 lines
Quoted 4 lines
drivers/irqchip/irq-mvebu-icu.c +0
-0@@ -133,12 +164,36 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,return -EINVAL ; }
Quoted 11 lines
drivers/irqchip/irq-mvebu-icu.c mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, · line 173
Quoted 9 lines
drivers/irqchip/irq-mvebu-icu.c +7
-2-/* 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 ;That's interesting. How is the resampling done here?
I'm not sure to understand the question. What does 'resampling' means in such context? MSI SEIs are of type "edge" and use the traditional MSI signalling infrastructure. I'm asking to be sure not to ignore something wrong in my code.
Quoted 4 lines
drivers/irqchip/irq-mvebu-icu.c mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, · line 176
Quoted 4 lines
drivers/irqchip/irq-mvebu-icu.c +0
-0return 0 ; }
Thanks,
Miqu?l
PATCH v4 9/14 irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
Quoted 23 lines · Miquel Raynal
Hi Marc,
I'm fine with the rest of the comments, please find just one last question below.
[...]
Quoted 4 lines
Quoted 4 lines
drivers/irqchip/irq-mvebu-icu.c +0
-0@@ -133,12 +164,36 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,return -EINVAL ; }Quoted 11 lines
drivers/irqchip/irq-mvebu-icu.c mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, · line 173
Quoted 9 lines
drivers/irqchip/irq-mvebu-icu.c +7
-2-/* 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 ;That's interesting. How is the resampling done here?
Quoted 4 lines
I'm not sure to understand the question. What does 'resampling' means in such context? MSI SEIs are of type "edge" and use the traditional MSI signalling infrastructure. I'm asking to be sure not to ignore something wrong in my code.
You seems to be turning a level interrupt into an edge. You can do that, but only if you resample the level on EOI (and regenerate the corresponding edge if the level is still high).
Or am I reading it the wrong way?
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
Miquel Raynal
maintainerPATCH v4 9/14 irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
Hi Marc,
Marc Zyngier <[email protected]> wrote on Tue, 21 Aug 2018 10:19:04 +0100:
Quoted 24 lines · Miquel Raynal
Quoted 23 lines
Hi Marc,
I'm fine with the rest of the comments, please find just one last question below.
[...]
Quoted 15 lines
drivers/irqchip/irq-mvebu-icu.c mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, · line 173
Quoted 13 lines
drivers/irqchip/irq-mvebu-icu.c +7
-2@@ -133,12 +164,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 ;That's interesting. How is the resampling done here?
Quoted 6 lines
Quoted 4 lines
I'm not sure to understand the question. What does 'resampling' means in such context? MSI SEIs are of type "edge" and use the traditional MSI signalling infrastructure. I'm asking to be sure not to ignore something wrong in my code.
You seems to be turning a level interrupt into an edge.
If is an SEI interrupt, it cannot be a level interrupt and the type will be IRQ_TYPE_EDGE_RISING.
Quoted 3 lines
You can do that,
but only if you resample the level on EOI (and regenerate the corresponding edge if the level is still high).
What is before is a '& IRQ_TYPE_SENSE_MASK' operation. In theory, *type could be anything of IRQ_TYPE_{EDGE,LEVEL}_* at this moment. But, as stated above, it cannot be anything else than IRQ_TYPE_EDGE_RISING. I thought more clear to enforce it but if this unclear and useless, let's drop it?
Quoted 2 lines
Or am I reading it the wrong way?
No, that's probably me not understanding correctly the purpose of the above function.
Thanks,
Miqu?l
PATCH v4 9/14 irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
Quoted 43 lines · Miquel Raynal
Hi Marc,
Marc Zyngier <[email protected]> wrote on Tue, 21 Aug 2018 10:19:04 +0100:
Quoted 24 lines · Miquel Raynal
Quoted 23 lines
Hi Marc,
I'm fine with the rest of the comments, please find just one last question below.
[...]
Quoted 15 lines
drivers/irqchip/irq-mvebu-icu.c mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, · line 173
Quoted 13 lines
drivers/irqchip/irq-mvebu-icu.c +7
-2@@ -133,12 +164,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 ;That's interesting. How is the resampling done here?
Quoted 6 lines
Quoted 4 lines
I'm not sure to understand the question. What does 'resampling' means in such context? MSI SEIs are of type "edge" and use the traditional MSI signalling infrastructure. I'm asking to be sure not to ignore something wrong in my code.
You seems to be turning a level interrupt into an edge.
If is an SEI interrupt, it cannot be a level interrupt and the type will be
IRQ_TYPE_EDGE_RISING.Quoted 3 lines
You can do that,
but only if you resample the level on EOI (and regenerate the corresponding edge if the level is still high).
Quoted 5 lines
What is before is a '&
IRQ_TYPE_SENSE_MASK' operation. In theory, *type could be anything of IRQ_TYPE_{EDGE,LEVEL}_* at this moment. But, as stated above, it cannot be anything else thanIRQ_TYPE_EDGE_RISING. I thought more clear to enforce it but if this unclear and useless, let's drop it?
I think overriding the trigger that way is very confusing. Either it comes from DT, or it comes from the MSI framwork. In both cases, it has to be correct, and overriding it is just papering over other bugs.
I'd rather you put a WARN_ON if you encounter an illegal interrupt trigger.
Thanks,
M.
Signature · 1 line
Jazz is not dead. It just smells funny...
Miquel Raynal
maintainerPATCH v4 9/14 irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
Hi Marc,
Marc Zyngier <[email protected]> wrote on Tue, 21 Aug 2018 11:37:47 +0100:
Quoted 50 lines · Miquel Raynal
Quoted 43 lines
Hi Marc,
Marc Zyngier <[email protected]> wrote on Tue, 21 Aug 2018 10:19:04 +0100:
Quoted 24 lines · Miquel Raynal
Quoted 23 lines
Hi Marc,
I'm fine with the rest of the comments, please find just one last question below.
[...]
Quoted 15 lines
drivers/irqchip/irq-mvebu-icu.c mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, · line 173
Quoted 13 lines
drivers/irqchip/irq-mvebu-icu.c +7
-2@@ -133,12 +164,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 ;That's interesting. How is the resampling done here?
Quoted 6 lines
Quoted 4 lines
I'm not sure to understand the question. What does 'resampling' means in such context? MSI SEIs are of type "edge" and use the traditional MSI signalling infrastructure. I'm asking to be sure not to ignore something wrong in my code.
You seems to be turning a level interrupt into an edge.
If is an SEI interrupt, it cannot be a level interrupt and the type will be
IRQ_TYPE_EDGE_RISING.Quoted 3 lines
You can do that,
but only if you resample the level on EOI (and regenerate the corresponding edge if the level is still high).Quoted 5 lines
What is before is a '&
IRQ_TYPE_SENSE_MASK' operation. In theory, *type could be anything of IRQ_TYPE_{EDGE,LEVEL}_* at this moment. But, as stated above, it cannot be anything else thanIRQ_TYPE_EDGE_RISING. I thought more clear to enforce it but if this unclear and useless, let's drop it?
Quoted 5 lines
I think overriding the trigger that way is very confusing. Either it comes from DT, or it comes from the MSI framwork. In both cases, it has to be correct, and overriding it is just papering over other bugs.
I'd rather you put a
WARN_ONif you encounter an illegal interrupt trigger.
Fine by me.
Thanks for all the guidance, I'll send a new iteration ASAP (with the bindings updated).
Thanks,
Miqu?l
Miquel Raynal
maintainerPATCH v4 9/14 irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
In reply to Marc ZyngierHi Marc,
Marc Zyngier <[email protected]> wrote on Tue, 21 Aug 2018 11:37:47 +0100:
Quoted 50 lines · Miquel Raynal
Quoted 43 lines
Hi Marc,
Marc Zyngier <[email protected]> wrote on Tue, 21 Aug 2018 10:19:04 +0100:
Quoted 24 lines · Miquel Raynal
Quoted 23 lines
Hi Marc,
I'm fine with the rest of the comments, please find just one last question below.
[...]
Quoted 15 lines
drivers/irqchip/irq-mvebu-icu.c mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, · line 173
Quoted 13 lines
drivers/irqchip/irq-mvebu-icu.c +7
-2@@ -133,12 +164,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 ;That's interesting. How is the resampling done here?
Quoted 6 lines
Quoted 4 lines
I'm not sure to understand the question. What does 'resampling' means in such context? MSI SEIs are of type "edge" and use the traditional MSI signalling infrastructure. I'm asking to be sure not to ignore something wrong in my code.
You seems to be turning a level interrupt into an edge.
If is an SEI interrupt, it cannot be a level interrupt and the type will be
IRQ_TYPE_EDGE_RISING.Quoted 3 lines
You can do that,
but only if you resample the level on EOI (and regenerate the corresponding edge if the level is still high).Quoted 5 lines
What is before is a '&
IRQ_TYPE_SENSE_MASK' operation. In theory, *type could be anything of IRQ_TYPE_{EDGE,LEVEL}_* at this moment. But, as stated above, it cannot be anything else thanIRQ_TYPE_EDGE_RISING. I thought more clear to enforce it but if this unclear and useless, let's drop it?
Quoted 5 lines
I think overriding the trigger that way is very confusing. Either it comes from DT, or it comes from the MSI framwork. In both cases, it has to be correct, and overriding it is just papering over other bugs.
I'd rather you put a
WARN_ONif you encounter an illegal interrupt trigger.
Actually I do remember now why I enforced the type:
Let's say my thermal IP in a CP110 triggers an interrupt. This interrupt is of type LEVEL_HIGH, and it is declared in the DT as:
thermal: thermal-sensor at ... {
[...]
interrupts-extended = <&icu_sei 116 IRQ_TYPE_LEVEL_HIGH>;
};The ICU callback ->translate() will be called with fwspec being the C view of the above "interrupts-extended" property, so the interrupt type will be LEVEL_HIGH.
However, the "icu_sei" parent is the SEI IP in the AP806 and this IP only receives edge interrupts. What happens is that the SEI's ->set_type() callback is called with LEVEL_HIGH type (while it only supports EDGE_RISING interrupts) and I want the driver to throw an error in this case.
My way of handling this was to force *type to be EDGE_RISING in the ICU ->translate() callback whenever an SEI was triggered. As this seems not to be correct, how would you handle such situation?
Thanks,
Miqu?l
Miquel Raynal
maintainerPATCH v4 10/14 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
Rob Herring
maintainerPATCH v4 10/14 dt-bindings/interrupt-controller: update Marvell ICU bindings
Quoted 26 lines · Miquel Raynal
Change 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.
Signed-off-by Miquel Raynal <[email protected]>--- .../bindings/interrupt-controller/marvell,icu.txt | 83 ++++++++++++++++++---- 1 file changed, 71 insertions(+), 12 deletions(-)Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt +2
-0@@ -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:
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt Required properties: · line 23
Quoted 10 lines
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt +7
-0@@ -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". +
I raised this before and still don't understand. You had 4 types before and now you only have 2 types? How do you handle SR and REI with the new binding?
Rob
Miquel Raynal
maintainerPATCH v4 10/14 dt-bindings/interrupt-controller: update Marvell ICU bindings
Hi Rob,
Rob Herring <[email protected]> wrote on Mon, 16 Jul 2018 09:27:34 -0600:
Quoted 30 lines · Miquel Raynal
Quoted 29 lines
Change 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.
Signed-off-by Miquel Raynal <[email protected]>--- .../bindings/interrupt-controller/marvell,icu.txt | 83 ++++++++++++++++++---- 1 file changed, 71 insertions(+), 12 deletions(-)Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt +2
-0@@ -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.
Quoted 11 lines
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt Required properties: · line 23
Quoted 7 lines
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt +7
-0+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". +I raised this before and still don't understand. You had 4 types before and now you only have 2 types? How do you handle SR and REI with the new binding?
Indeed there are 4 types: NSR, SR, SEI, REI.
Until now only NSR were supported.
I'm adding SEI support.
In the future, people might want to add support for SR/REI as well but they have never been supported in mainline. When support for these interrupts will have been contributed, I suppose it will easy to add two other compatibles with the same formatting "marvell,cp110-icu-xxx"?
I hope the changes in the driver will make such contribution almost trivial.
Thanks,
Miqu?l
Rob Herring
maintainerPATCH v4 10/14 dt-bindings/interrupt-controller: update Marvell ICU bindings
Quoted 46 lines · Miquel Raynal
Hi Rob,
Rob Herring <[email protected]> wrote on Mon, 16 Jul 2018 09:27:34 -0600:
Quoted 30 lines · Miquel Raynal
Quoted 29 lines
Change 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.
Signed-off-by Miquel Raynal <[email protected]>--- .../bindings/interrupt-controller/marvell,icu.txt | 83 ++++++++++++++++++---- 1 file changed, 71 insertions(+), 12 deletions(-)Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt +2
-0@@ -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.Quoted 11 lines
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt Required properties: · line 23
Quoted 7 lines
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt +7
-0+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". +I raised this before and still don't understand. You had 4 types before and now you only have 2 types? How do you handle SR and REI with the new binding?
Quoted 3 lines
Indeed there are 4 types: NSR, SR, SEI, REI.
Until now only NSR were supported.
All 4 were supported by the binding and now only 2 though.
Quoted 6 lines
I'm adding SEI support.
In the future, people might want to add support for SR/REI as well but they have never been supported in mainline. When support for these interrupts will have been contributed, I suppose it will easy to add two other compatibles with the same formatting "marvell,cp110-icu-xxx"?
Perhaps, but I have know way to tell.
Quoted 2 lines
I hope the changes in the driver will make such contribution almost trivial.
You can add bindings without adding driver.
Rob
PATCH v4 10/14 dt-bindings/interrupt-controller: update Marvell ICU bindings
Hello,
Quoted 9 lines · Rob Herring
Quoted 7 lines
Quoted 3 lines
I raised this before and still don't understand. You had 4 types before and now you only have 2 types? How do you handle SR and REI with the new binding?
Indeed there are 4 types: NSR, SR, SEI, REI.
Until now only NSR were supported.
All 4 were supported by the binding and now only 2 though.
In fact, no, the old binding was not working for anything but NSR. It pretended to support SEI, REI and SR, but because these had never been implemented, the binding was not sufficient to over them.
To support SEI or REI, we need to point to a different msi-parent than the one used for NSR, which wasn't possible with the previous binding.
So there is no regression of functionality by this binding change, quite the contrary, as it really allows to add SEI support, which was not possible before. You can consider that the previous binding was buggy and dysfunctional.
Best regards,
Thomas Petazzoni
Signature · 3 lines
Thomas Petazzoni, CTO, Bootlin (formerly Free Electrons) Embedded Linux and Kernel engineering https://bootlin.com
PATCH v4 10/14 dt-bindings/interrupt-controller: update Marvell ICU bindings
In reply to Rob HerringHello,
Quoted 8 lines · Rob Herring
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt Required properties: · line 23
Quoted 4 lines
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt +4
-0+Required properties for the icu_nsr/icu_sei subnodes: + +- compatible: Should be "marvell,cp110-icu-nsr" or "marvell,cp110-icu-sei". +I raised this before and still don't understand. You had 4 types before and now you only have 2 types? How do you handle SR and REI with the new binding?
As I replied to a different e-mail, the current binding pretended to support SR, SEI and REI, but it definitely could not.
The ICU collects wired interrupts from the CP, and forwards them to the AP as MSIs. And contrary to what we thought when designing the current binding, the target of the MSIs is different depending on the type of interrupt. NSRs go to the GICP controller in the AP, SEIs go to a special SEI controller in the AP, REIs go to a special REI controller in the AP.
In order to represent that in the Device Tree, you need to have a different msi-parent property for each of NSR, SEI and REI. This is not something that the current broken binding allows to express, and it's the reason why we're migrating to this new binding.
So even if the current binding documents that it supports NSR, SEI, REI and SR, it definitely cannot support anything but NSR. Therefore, the introduction of the new binding by Miqu?l does not introduce any regression in functionality: it simply allows to really support SEIs.
Does this explanation clarify the situation, and what we're trying to achieve ?
Thanks!
Thomas
Signature · 3 lines
Thomas Petazzoni, CTO, Bootlin (formerly Free Electrons) Embedded Linux and Kernel engineering https://bootlin.com
Miquel Raynal
maintainerPATCH v4 11/14 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
Miquel Raynal
maintainerPATCH v4 12/14 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
Rob Herring
maintainerPATCH v4 12/14 arm64: dts: marvell: add AP806 SEI subnode
Quoted 3 lines · Miquel Raynal
Add 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).
Where are the 2 sub-nodes?
Quoted 31 lines
Signed-off-by Miquel Raynal <[email protected]>--- 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
Miquel Raynal
maintainerRe: [PATCH v4 12/14] arm64: dts: marvell: add AP806 SEI subnode
Hi Rob,
Rob Herring <[email protected]> wrote on Mon, 16 Jul 2018 09:31:58 -0600:
Quoted 6 lines · Miquel Raynal
Quoted 3 lines
Add 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).Where are the 2 sub-nodes?
Indeed I did not update the commit log.
[...]
Quoted 6 lines
arch/arm64/boot/dts/marvell/armada-ap806.dtsi line 132
Quoted 6 lines
arch/arm64/boot/dts/marvell/armada-ap806.dtsi +6
-0+ sei: interrupt-controller@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>;
After more discussion, these ranges seems to be only related to the IP internal organization itself and should not appear in the device tree at all. Instead, I could probably have a more meaningful compatible string, like "marvell,ap806-sei". Next time an AP has a different internal distribution, we'll just add a different compatible and handle the differences in the driver directly.
This does not have a big impact on the rest of the driver, I should probably let Marc review this version first.
Thanks,
Miquèl
Miquel Raynal
maintainerPATCH v4 13/14 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
Miquel Raynal
maintainerPATCH v4 14/14 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
Miquel Raynal
maintainerPATCH 1/2 mtd: rawnand: marvell: document a bit more the driver
A stale document about the old pxa3cc_nand.c driver is available in Documentation/mtd/nand/. Rewrite the parts that explain the IP itself and some non-trivial choices made in the driver directly in marvell_nand.c to then be able to remove this file.
--- drivers/mtd/nand/raw/marvell_nand.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+)
drivers/mtd/nand/raw/marvell_nand.c +31 -0
@@ -5,6 +5,37 @@* Copyright (C) 2017 Marvell * Author: Miquel RAYNAL <[email protected]> * +* +* This NAND controller driver handles two versions of the hardware, +* one is called NFCv1 and is available on PXA SoCs and the other is +* called NFCv2 and is available on almost all the Armada SoCs. +* +* The main differences are that the NFCv1 has DMA support and only +* has Hamming ECC capabilities, while NFCv2 does not support DMA but +* has hardware BCH support. +* +* The internal ECC operations are depicted in details in Marvell +* AN-379. +* +* The controller has certain limitations that are handled by the +* driver: +* - It can only read 2k at a time. To overcome this limitation, the +* driver makes use of 'naked' operations. +* - The ECC strength in BCH mode cannot be tuned easily. It is a +* fixed 16 bytes. What can be tuned is the area on which this +* correction occurs. Hence, using 2048B ECC chunks makes the +* strength to be 4b/512B. +* - The controller will always treat data bytes, spare bytes and +* ECC bytes in that order, no matter the real factory layout +* (which is usually all data then all OOB bytes). But depending +* on the chosen layout, the areas of each section may vary or be +* absent. The same data/spare/ecc layout is repeated until the +* next chunk were each section may be different again. The +* marvell_nfc_layouts array below contains the currently +* supported layouts. +* - Because of these weird layouts, the Bad Block Markers can be +* located in data. In this case, the NAND_BBT_NO_OOB_BBM option +* must be set. */ #include <linux/module.h>
Signature · 1 line
2.14.1
Miquel Raynal
maintainerPATCH 2/2 Documentation: mtd: remove stale pxa3xx NAND controller documentation
It is preferred to have the documentation about the drivers directly embedded in the driver itself. Remove this file now that the most important information from this file have been re-written in marvell_nand.c.
--- Documentation/mtd/nand/pxa3xx-nand.txt | 113 --------------------------------- 1 file changed, 113 deletions(-) delete mode 100644 Documentation/mtd/nand/pxa3xx-nand.txt
Documentation/mtd/nand/pxa3xx-nand.txt deleted +0 -113
@@ -1,113 +0,0 @@ - -About this document -=================== - -Some notes about Marvell's NAND controller available in PXA and Armada 370/XP -SoC (aka NFCv1 and NFCv2), with an emphasis on the latter. - -NFCv2 controller background -=========================== - -The controller has a 2176 bytes FIFO buffer. Therefore, in order to support -larger pages, I/O operations on 4 KiB and 8 KiB pages is done with a set of -chunked transfers. - -For instance, if we choose a 2048 data chunk and set "BCH" ECC (see below) -we'll have this layout in the pages: - - ------------------------------------------------------------------------------ - | 2048B data | 32B spare | 30B ECC || 2048B data | 32B spare | 30B ECC | ... | - ------------------------------------------------------------------------------ - -The driver reads the data and spare portions independently and builds an internal -buffer with this layout (in the 4 KiB page case): - - ------------------------------------------ - | 4096B data | 64B spare | - ------------------------------------------ - -Also, for the READOOB command the driver disables the ECC and reads a 'spare + ECC' -OOB, one per chunk read. - - ------------------------------------------------------------------- - | 4096B data | 32B spare | 30B ECC | 32B spare | 30B ECC | - ------------------------------------------------------------------- - -So, in order to achieve reading (for instance), we issue several READ0 commands -(with some additional controller-specific magic) and read two chunks of 2080B -(2048 data + 32 spare) each. -The driver accommodates this data to expose the NAND core a contiguous buffer -(4096 data + spare) or (4096 + spare + ECC + spare + ECC). - -ECC -=== - -The controller has built-in hardware ECC capabilities. In addition it is -configurable between two modes: 1) Hamming, 2) BCH. - -Note that the actual BCH mode: BCH-4 or BCH-8 will depend on the way -the controller is configured to transfer the data. - -In the BCH mode the ECC code will be calculated for each transferred chunk -and expected to be located (when reading/programming) right after the spare -bytes as the figure above shows. - -So, repeating the above scheme, a 2048B data chunk will be followed by 32B -spare, and then the ECC controller will read/write the ECC code (30B in -this case): - - ------------------------------------ - | 2048B data | 32B spare | 30B ECC | - ------------------------------------ - -If the ECC mode is 'BCH' then the ECC is *always* 30 bytes long. -If the ECC mode is 'Hamming' the ECC is 6 bytes long, for each 512B block. -So in Hamming mode, a 2048B page will have a 24B ECC. - -Despite all of the above, the controller requires the driver to only read or -write in multiples of 8-bytes, because the data buffer is 64-bits. - -OOB -=== - -Because of the above scheme, and because the "spare" OOB is really located in -the middle of a page, spare OOB cannot be read or write independently of the -data area. In other words, in order to read the OOB (aka READOOB), the entire -page (aka READ0) has to be read. - -In the same sense, in order to write to the spare OOB the driver has to write -an *entire* page. - -Factory bad blocks handling -=========================== - -Given the ECC BCH requires to layout the device's pages in a split -data/OOB/data/OOB way, the controller has a view of the flash page that's -different from the specified (aka the manufacturer's) view. In other words, - -Factory view: - - ----------------------------------------------- - | Data |x OOB | - ----------------------------------------------- - -Driver's view: - - ----------------------------------------------- - | Data | OOB | Data x | OOB | - ----------------------------------------------- - -It can be seen from the above, that the factory bad block marker must be -searched within the 'data' region, and not in the usual OOB region. - -In addition, this means under regular usage the driver will write such -position (since it belongs to the data region) and every used block is -likely to be marked as bad. - -For this reason, marking the block as bad in the OOB is explicitly -disabled by using the NAND_BBT_NO_OOB_BBM option in the driver. The rationale -for this is that there's no point in marking a block as bad, because good -blocks are also 'marked as bad' (in the OOB BBM sense) under normal usage. - -Instead, the driver relies on the bad block table alone, and should only perform -the bad block scan on the very first time (when the device hasn't been used).
Signature · 1 line
2.14.1
Re: [PATCH 2/2] Documentation: mtd: remove stale pxa3xx NAND controller documentation
Hello,
Quoted 6 lines · Miquel Raynal
It is preferred to have the documentation about the drivers directly embedded in the driver itself. Remove this file now that the most important information from this file have been re-written in
marvell_nand.c.Signed-off-by Miquel Raynal <[email protected]>
I agree with moving the documentation to the driver itself, but the documentation you've added in PATCH 1/2 into the driver is much, much more limited than the documentation present in this file.
Thomas
Signature · 3 lines
Thomas Petazzoni, CTO, Bootlin (formerly Free Electrons) Embedded Linux and Kernel engineering https://bootlin.com
Miquel Raynal
maintainerRe: [PATCH 2/2] Documentation: mtd: remove stale pxa3xx NAND controller documentation
Hi Thomas,
Thomas Petazzoni <[email protected]> wrote on Sat, 14 Jul 2018 22:46:12 +0200:
Quoted 8 lines
Hello,
Quoted 5 lines · Miquel Raynal
It is preferred to have the documentation about the drivers directly embedded in the driver itself. Remove this file now that the most important information from this file have been re-written in
marvell_nand.c.
Quoted 5 lines
Quoted 1 line
Signed-off-by Miquel Raynal <[email protected]>I agree with moving the documentation to the driver itself, but the documentation you've added in PATCH 1/2 into the driver is much, much more limited than the documentation present in this file.
I think you are right on the fact that showing how data/spare/ecc bytes might be exposed by the controller is something useful.
I will add the example given in the doc.
Thanks,
Miquèl
Re: [PATCH 1/2] mtd: rawnand: marvell: document a bit more the driver
In reply to Miquel RaynalHello,
Quoted 2 lines · Miquel Raynal
+ * The internal ECC operations are depicted in details in Marvell
+ * AN-379.
AN-379 is as far as I know not publicly available, so I'm not sure if it makes a lot of sense to mention it here.
Best regards,
Thomas
Signature · 3 lines
Thomas Petazzoni, CTO, Bootlin (formerly Free Electrons) Embedded Linux and Kernel engineering https://bootlin.com
Miquel Raynal
maintainerRe: [PATCH 1/2] mtd: rawnand: marvell: document a bit more the driver
Hi Thomas,
Thomas Petazzoni <[email protected]> wrote on Sat, 14 Jul 2018 22:46:48 +0200:
Quoted 9 lines
Hello,
Quoted 2 lines · Miquel Raynal
+ * The internal ECC operations are depicted in details in Marvell
+ * AN-379.AN-379 is as far as I know not publicly available, so I'm not sure if it makes a lot of sense to mention it here.
No it's not, but I think it's not the first time this AN is quoted in the ML and it might be useful to know about it for people having access to Marvell specifications. At least in this file all layouts are explained in details.
Thanks,
Miquèl
Re: [PATCH 1/2] mtd: rawnand: marvell: document a bit more the driver
In reply to Miquel RaynalQuoted 26 lines · On Sat, 14 Jul 2018 15:54:27 +0200 Miquel Raynal <[email protected]>…
A stale document about the old
pxa3cc_nand.cdriver is available inDocumentation/mtd/nand/. Rewrite the parts that explain the IP itself and some non-trivial choices made in the driver directly inmarvell_nand.cto then be able to remove this file.Signed-off-by Miquel Raynal <[email protected]>--- drivers/mtd/nand/raw/marvell_nand.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+)drivers/mtd/nand/raw/marvell_nand.c +8
-0@@ -5,6 +5,37 @@* Copyright (C) 2017 Marvell * Author: Miquel RAYNAL <[email protected]> * +* +* This NAND controller driver handles two versions of the hardware, +* one is called NFCv1 and is available on PXA SoCs and the other is +* called NFCv2 and is available on almost all the Armada SoCs. +* +* The main differences are that the NFCv1 has DMA support and only +* has Hamming ECC capabilities, while NFCv2 does not support DMA but +* has hardware BCH support.
drivers/mtd/nand/raw/marvell_nand.c line 25
Quoted 10 lines
drivers/mtd/nand/raw/marvell_nand.c +10
-0+* +* The internal ECC operations are depicted in details in Marvell +* AN-379. +* +* The controller has certain limitations that are handled by the +* driver: +* - It can only read 2k at a time. To overcome this limitation, the +* driver makes use of 'naked' operations. +* - The ECC strength in BCH mode cannot be tuned easily. It is a +* fixed 16 bytes. What can be tuned is the area on which this
^ bits
drivers/mtd/nand/raw/marvell_nand.c line 27
Quoted 2 lines
drivers/mtd/nand/raw/marvell_nand.c +2
-0+* correction occurs. Hence, using 2048B ECC chunks makes the +* strength to be 4b/512B.
I'd mention that max ECC step size is 2k here. So you can actually choose something between 512 and 2k based on the chip requirements.
drivers/mtd/nand/raw/marvell_nand.c line 28
Quoted 1 line
drivers/mtd/nand/raw/marvell_nand.c +1
-0+* - The controller will always treat data bytes, spare bytes and
Some people call OOB bytes, spare bytes, what you refer to here are free OOB bytes. I think it's worth clarifying that somewhere.
Quoted 13 lines
drivers/mtd/nand/raw/marvell_nand.c +10
-0+* ECC bytes in that order, no matter the real factory layout +* (which is usually all data then all OOB bytes). But depending +* on the chosen layout, the areas of each section may vary or be +* absent. The same data/spare/ecc layout is repeated until the +* next chunk were each section may be different again. The +* marvell_nfc_layouts array below contains the currently +* supported layouts. +* - Because of these weird layouts, the Bad Block Markers can be +* located in data. In this case, the NAND_BBT_NO_OOB_BBM option +* must be set. */ #include <linux/module.h>