linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v5 0/8] Add STM32MP25 SPI NOR support
@ 2025-02-19  8:00 patrice.chotard
  2025-02-19  8:00 ` [PATCH v5 1/8] dt-bindings: spi: Add STM32 OSPI controller patrice.chotard
                   ` (8 more replies)
  0 siblings, 9 replies; 19+ messages in thread
From: patrice.chotard @ 2025-02-19  8:00 UTC (permalink / raw)
  To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello, patrice.chotard

From: Patrice Chotard <patrice.chotard@foss.st.com>

This series adds SPI NOR support for STM32MP25 SoCs from STMicroelectronics.

On STM32MP25 SoCs family, an Octo Memory Manager block manages the muxing,
the memory area split, the chip select override and the time constraint 
between its 2 Octo SPI children.

Due to these depedencies, this series adds support for: 
  - Octo Memory Manager driver.
  - Octo SPI driver.
  - yaml schema for Octo Memory Manager and Octo SPI drivers.

The device tree files adds Octo Memory Manager and its 2 associated Octo 
SPI chidren in stm32mp251.dtsi and adds SPI NOR support in stm32mp257f-ev1
board.

Signed-off-by: Patrice Chotard <patrice.chotard@foss.st.com>

Changes in v5:
  - Add Reviewed-by Krzysztof Kozlowski for patch 1 and 3.

Changes in v4:
  - Add default value requested by Krzysztof for st,omm-req2ack-ns, 
    st,omm-cssel-ovr and st,omm-mux properties in st,stm32mp25-omm.yaml
  - Remove constraint in free form test for st,omm-mux property.
  - Fix drivers/memory/Kconfig by replacing TEST_COMPILE_ by COMPILE_TEST.
  - Fix SPDX-License-Identifier for stm32-omm.c.
  - Fix Kernel test robot by fixing dev_err() format in stm32-omm.c.
  - Add missing pm_runtime_disable() in the error handling path in
    stm32-omm.c.
  - Replace an int by an unsigned int in stm32-omm.c
  - Remove uneeded "," after terminator in stm32-omm.c.
  - Update cover letter description to explain dependecies between 
    Octo Memory Manager and its 2 Octo SPI children.

Changes in v3:
  - Squash defconfig patches 8 and 9.
  - Update STM32 Octo Memory Manager controller bindings.
  - Rename st,stm32-omm.yaml to st,stm32mp25-omm.yaml.
  - Update STM32 OSPI controller bindings.
  - Reorder DT properties in .dtsi and .dts files.
  - Replace devm_reset_control_get_optional() by 
    devm_reset_control_get_optional_exclusive() in stm32_omm.c.
  - Reintroduce region-memory-names management in stm32_omm.c.
  - Rename stm32_ospi_tx_poll() and stm32_ospi_tx() to respectively to 
    stm32_ospi_poll() and stm32_ospi_xfer() in spi-stm32-ospi.c.
  - Set SPI_CONTROLLER_HALF_DUPLEX in controller flags in spi-stm32-ospi.c.

Changes in v2:
  - Move STM32 Octo Memory Manager controller driver and bindings from
    misc to memory-controllers.
  - Update STM32 OSPI controller bindings.
  - Update STM32 Octo Memory Manager controller bindings.
  - Update STM32 Octo Memory Manager driver to match bindings update.
  - Update DT to match bindings update.


Patrice Chotard (8):
  dt-bindings: spi: Add STM32 OSPI controller
  spi: stm32: Add OSPI driver
  dt-bindings: memory-controllers: Add STM32 Octo Memory Manager
    controller
  memory: Add STM32 Octo Memory Manager driver
  arm64: dts: st: Add OMM node on stm32mp251
  arm64: dts: st: Add ospi port1 pinctrl entries in
    stm32mp25-pinctrl.dtsi
  arm64: dts: st: Add SPI NOR flash support on stm32mp257f-ev1 board
  arm64: defconfig: Enable STM32 Octo Memory Manager and OcstoSPI driver

 .../memory-controllers/st,stm32mp25-omm.yaml  |  204 ++++
 .../bindings/spi/st,stm32mp25-ospi.yaml       |  105 ++
 arch/arm64/boot/dts/st/stm32mp25-pinctrl.dtsi |   51 +
 arch/arm64/boot/dts/st/stm32mp251.dtsi        |   48 +
 arch/arm64/boot/dts/st/stm32mp257f-ev1.dts    |   32 +
 arch/arm64/configs/defconfig                  |    2 +
 drivers/memory/Kconfig                        |   17 +
 drivers/memory/Makefile                       |    1 +
 drivers/memory/stm32_omm.c                    |  522 ++++++++
 drivers/spi/Kconfig                           |   10 +
 drivers/spi/Makefile                          |    1 +
 drivers/spi/spi-stm32-ospi.c                  | 1065 +++++++++++++++++
 12 files changed, 2058 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/memory-controllers/st,stm32mp25-omm.yaml
 create mode 100644 Documentation/devicetree/bindings/spi/st,stm32mp25-ospi.yaml
 create mode 100644 drivers/memory/stm32_omm.c
 create mode 100644 drivers/spi/spi-stm32-ospi.c

-- 
2.25.1



^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v5 1/8] dt-bindings: spi: Add STM32 OSPI controller
  2025-02-19  8:00 [PATCH v5 0/8] Add STM32MP25 SPI NOR support patrice.chotard
@ 2025-02-19  8:00 ` patrice.chotard
  2025-02-19  8:00 ` [PATCH v5 2/8] spi: stm32: Add OSPI driver patrice.chotard
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: patrice.chotard @ 2025-02-19  8:00 UTC (permalink / raw)
  To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello, patrice.chotard,
	Krzysztof Kozlowski

From: Patrice Chotard <patrice.chotard@foss.st.com>

Add device tree bindings for the STM32 OSPI controller.

Main features of the Octo-SPI controller :
  - support sNOR / sNAND / HyperRAM™ and HyperFlash™ devices.
  - Three functional modes: indirect, automatic-status polling,
    memory-mapped.
  - Up to 4 Gbytes of external memory can be addressed in indirect
    mode (per physical port and per CS), and up to 256 Mbytes in
    memory-mapped mode (combined for both physical ports and per CS).
  - Single-, dual-, quad-, and octal-SPI communication.
  - Dual-quad communication.
  - Single data rate (SDR) and double transfer rate (DTR).
  - Maximum target frequency is 133 MHz for SDR and 133 MHz for DTR.
  - Data strobe support.
  - DMA channel for indirect mode.
  - Double CS mapping that allows two external flash devices to be
    addressed with a single OCTOSPI controller mapped on a single
    OCTOSPI port.

Signed-off-by: Patrice Chotard <patrice.chotard@foss.st.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
---
 .../bindings/spi/st,stm32mp25-ospi.yaml       | 105 ++++++++++++++++++
 1 file changed, 105 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/spi/st,stm32mp25-ospi.yaml

diff --git a/Documentation/devicetree/bindings/spi/st,stm32mp25-ospi.yaml b/Documentation/devicetree/bindings/spi/st,stm32mp25-ospi.yaml
new file mode 100644
index 000000000000..5f276f27dc4c
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/st,stm32mp25-ospi.yaml
@@ -0,0 +1,105 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/spi/st,stm32mp25-ospi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: STMicroelectronics STM32 Octal Serial Peripheral Interface (OSPI)
+
+maintainers:
+  - Patrice Chotard <patrice.chotard@foss.st.com>
+
+allOf:
+  - $ref: spi-controller.yaml#
+
+properties:
+  compatible:
+    const: st,stm32mp25-ospi
+
+  reg:
+    maxItems: 1
+
+  memory-region:
+    description:
+      Memory region to be used for memory-map read access.
+      In memory-mapped mode, read access are performed from the memory
+      device using the direct mapping.
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  resets:
+    items:
+      - description: phandle to OSPI block reset
+      - description: phandle to delay block reset
+
+  dmas:
+    maxItems: 2
+
+  dma-names:
+    items:
+      - const: tx
+      - const: rx
+
+  st,syscfg-dlyb:
+    description: configure OCTOSPI delay block.
+    $ref: /schemas/types.yaml#/definitions/phandle-array
+    items:
+      - description: phandle to syscfg
+      - description: register offset within syscfg
+
+  access-controllers:
+    description: phandle to the rifsc device to check access right
+      and in some cases, an additional phandle to the rcc device for
+      secure clock control.
+    items:
+      - description: phandle to bus controller
+      - description: phandle to clock controller
+    minItems: 1
+
+  power-domains:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - interrupts
+  - st,syscfg-dlyb
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/st,stm32mp25-rcc.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/reset/st,stm32mp25-rcc.h>
+
+    spi@40430000 {
+      compatible = "st,stm32mp25-ospi";
+      reg = <0x40430000 0x400>;
+      memory-region = <&mm_ospi1>;
+      interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>;
+      dmas = <&hpdma 2 0x62 0x00003121 0x0>,
+             <&hpdma 2 0x42 0x00003112 0x0>;
+      dma-names = "tx", "rx";
+      clocks = <&scmi_clk CK_SCMI_OSPI1>;
+      resets = <&scmi_reset RST_SCMI_OSPI1>, <&scmi_reset RST_SCMI_OSPI1DLL>;
+      access-controllers = <&rifsc 74>;
+      power-domains = <&CLUSTER_PD>;
+      st,syscfg-dlyb = <&syscfg 0x1000>;
+
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      flash@0 {
+        compatible = "jedec,spi-nor";
+        reg = <0>;
+        spi-rx-bus-width = <4>;
+        spi-max-frequency = <108000000>;
+      };
+    };
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v5 2/8] spi: stm32: Add OSPI driver
  2025-02-19  8:00 [PATCH v5 0/8] Add STM32MP25 SPI NOR support patrice.chotard
  2025-02-19  8:00 ` [PATCH v5 1/8] dt-bindings: spi: Add STM32 OSPI controller patrice.chotard
@ 2025-02-19  8:00 ` patrice.chotard
  2025-02-19  8:00 ` [PATCH v5 3/8] dt-bindings: memory-controllers: Add STM32 Octo Memory Manager controller patrice.chotard
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: patrice.chotard @ 2025-02-19  8:00 UTC (permalink / raw)
  To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello, patrice.chotard

From: Patrice Chotard <patrice.chotard@foss.st.com>

Add STM32 OSPI driver, it supports :
  - support sNOR / sNAND devices.
  - Three functional modes: indirect, automatic-status polling,
    memory-mapped.
  - Single-, dual-, quad-, and octal-SPI communication.
  - Dual-quad communication.
  - Single data rate (SDR).
  - DMA channel for indirect mode.

Signed-off-by: Patrice Chotard <patrice.chotard@foss.st.com>
---
 drivers/spi/Kconfig          |   10 +
 drivers/spi/Makefile         |    1 +
 drivers/spi/spi-stm32-ospi.c | 1065 ++++++++++++++++++++++++++++++++++
 3 files changed, 1076 insertions(+)
 create mode 100644 drivers/spi/spi-stm32-ospi.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index ea8a31032927..256a3e23639a 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -1045,6 +1045,16 @@ config SPI_STM32
 	  is not available, the driver automatically falls back to
 	  PIO mode.
 
+config SPI_STM32_OSPI
+	tristate "STMicroelectronics STM32 OCTO SPI controller"
+	depends on ARCH_STM32 || COMPILE_TEST
+	depends on OF
+	depends on SPI_MEM
+	help
+	  This enables support for the Octo SPI controller in master mode.
+	  This driver does not support generic SPI. The implementation only
+	  supports spi-mem interface.
+
 config SPI_STM32_QSPI
 	tristate "STMicroelectronics STM32 QUAD SPI controller"
 	depends on ARCH_STM32 || COMPILE_TEST
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 9db7554c1864..41cbd6df048f 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -137,6 +137,7 @@ obj-$(CONFIG_SPI_SN_F_OSPI)		+= spi-sn-f-ospi.o
 obj-$(CONFIG_SPI_SPRD)			+= spi-sprd.o
 obj-$(CONFIG_SPI_SPRD_ADI)		+= spi-sprd-adi.o
 obj-$(CONFIG_SPI_STM32) 		+= spi-stm32.o
+obj-$(CONFIG_SPI_STM32_OSPI) 		+= spi-stm32-ospi.o
 obj-$(CONFIG_SPI_STM32_QSPI) 		+= spi-stm32-qspi.o
 obj-$(CONFIG_SPI_ST_SSC4)		+= spi-st-ssc4.o
 obj-$(CONFIG_SPI_SUN4I)			+= spi-sun4i.o
diff --git a/drivers/spi/spi-stm32-ospi.c b/drivers/spi/spi-stm32-ospi.c
new file mode 100644
index 000000000000..8eadcb64f34a
--- /dev/null
+++ b/drivers/spi/spi-stm32-ospi.c
@@ -0,0 +1,1065 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2025 - All Rights Reserved
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/sizes.h>
+#include <linux/spi/spi-mem.h>
+#include <linux/types.h>
+
+#define OSPI_CR			0x00
+#define CR_EN			BIT(0)
+#define CR_ABORT		BIT(1)
+#define CR_DMAEN		BIT(2)
+#define CR_FTHRES_SHIFT		8
+#define CR_TEIE			BIT(16)
+#define CR_TCIE			BIT(17)
+#define CR_SMIE			BIT(19)
+#define CR_APMS			BIT(22)
+#define CR_CSSEL		BIT(24)
+#define CR_FMODE_MASK		GENMASK(29, 28)
+#define CR_FMODE_INDW		(0U)
+#define CR_FMODE_INDR		(1U)
+#define CR_FMODE_APM		(2U)
+#define CR_FMODE_MM		(3U)
+
+#define OSPI_DCR1		0x08
+#define DCR1_DLYBYP		BIT(3)
+#define DCR1_DEVSIZE_MASK	GENMASK(20, 16)
+#define DCR1_MTYP_MASK		GENMASK(26, 24)
+#define DCR1_MTYP_MX_MODE	1
+#define DCR1_MTYP_HP_MEMMODE	4
+
+#define OSPI_DCR2		0x0c
+#define DCR2_PRESC_MASK		GENMASK(7, 0)
+
+#define OSPI_SR			0x20
+#define SR_TEF			BIT(0)
+#define SR_TCF			BIT(1)
+#define SR_FTF			BIT(2)
+#define SR_SMF			BIT(3)
+#define SR_BUSY			BIT(5)
+
+#define OSPI_FCR		0x24
+#define FCR_CTEF		BIT(0)
+#define FCR_CTCF		BIT(1)
+#define FCR_CSMF		BIT(3)
+
+#define OSPI_DLR		0x40
+#define OSPI_AR			0x48
+#define OSPI_DR			0x50
+#define OSPI_PSMKR		0x80
+#define OSPI_PSMAR		0x88
+
+#define OSPI_CCR		0x100
+#define CCR_IMODE_MASK		GENMASK(2, 0)
+#define CCR_IDTR		BIT(3)
+#define CCR_ISIZE_MASK		GENMASK(5, 4)
+#define CCR_ADMODE_MASK		GENMASK(10, 8)
+#define CCR_ADMODE_8LINES	4
+#define CCR_ADDTR		BIT(11)
+#define CCR_ADSIZE_MASK		GENMASK(13, 12)
+#define CCR_ADSIZE_32BITS	3
+#define CCR_DMODE_MASK		GENMASK(26, 24)
+#define CCR_DMODE_8LINES	4
+#define CCR_DQSE		BIT(29)
+#define CCR_DDTR		BIT(27)
+#define CCR_BUSWIDTH_0		0x0
+#define CCR_BUSWIDTH_1		0x1
+#define CCR_BUSWIDTH_2		0x2
+#define CCR_BUSWIDTH_4		0x3
+#define CCR_BUSWIDTH_8		0x4
+
+#define OSPI_TCR		0x108
+#define TCR_DCYC_MASK		GENMASK(4, 0)
+#define TCR_DHQC		BIT(28)
+#define TCR_SSHIFT		BIT(30)
+
+#define OSPI_IR			0x110
+
+#define STM32_OSPI_MAX_MMAP_SZ	SZ_256M
+#define STM32_OSPI_MAX_NORCHIP	2
+
+#define STM32_FIFO_TIMEOUT_US		30000
+#define STM32_ABT_TIMEOUT_US		100000
+#define STM32_COMP_TIMEOUT_MS		5000
+#define STM32_BUSY_TIMEOUT_US		100000
+
+
+#define STM32_AUTOSUSPEND_DELAY -1
+
+struct stm32_ospi {
+	struct device *dev;
+	struct spi_controller *ctrl;
+	struct clk *clk;
+	struct reset_control *rstc;
+
+	struct completion data_completion;
+	struct completion match_completion;
+
+	struct dma_chan *dma_chtx;
+	struct dma_chan *dma_chrx;
+	struct completion dma_completion;
+
+	void __iomem *regs_base;
+	void __iomem *mm_base;
+	phys_addr_t regs_phys_base;
+	resource_size_t mm_size;
+	u32 clk_rate;
+	u32 fmode;
+	u32 cr_reg;
+	u32 dcr_reg;
+	u32 flash_presc[STM32_OSPI_MAX_NORCHIP];
+	int irq;
+	unsigned long status_timeout;
+
+	/*
+	 * To protect device configuration, could be different between
+	 * 2 flash access
+	 */
+	struct mutex lock;
+};
+
+static void stm32_ospi_read_fifo(u8 *val, void __iomem *addr)
+{
+	*val = readb_relaxed(addr);
+}
+
+static void stm32_ospi_write_fifo(u8 *val, void __iomem *addr)
+{
+	writeb_relaxed(*val, addr);
+}
+
+static int stm32_ospi_abort(struct stm32_ospi *ospi)
+{
+	void __iomem *regs_base = ospi->regs_base;
+	u32 cr;
+	int timeout;
+
+	cr = readl_relaxed(regs_base + OSPI_CR) | CR_ABORT;
+	writel_relaxed(cr, regs_base + OSPI_CR);
+
+	/* wait clear of abort bit by hw */
+	timeout = readl_relaxed_poll_timeout_atomic(regs_base + OSPI_CR,
+						    cr, !(cr & CR_ABORT), 1,
+						    STM32_ABT_TIMEOUT_US);
+
+	if (timeout)
+		dev_err(ospi->dev, "%s abort timeout:%d\n", __func__, timeout);
+
+	return timeout;
+}
+
+static int stm32_ospi_poll(struct stm32_ospi *ospi, u8 *buf, u32 len, bool read)
+{
+	void __iomem *regs_base = ospi->regs_base;
+	void (*fifo)(u8 *val, void __iomem *addr);
+	u32 sr;
+	int ret;
+
+	if (read)
+		fifo = stm32_ospi_read_fifo;
+	else
+		fifo = stm32_ospi_write_fifo;
+
+	while (len--) {
+		ret = readl_relaxed_poll_timeout_atomic(regs_base + OSPI_SR,
+							sr, sr & SR_FTF, 1,
+							STM32_FIFO_TIMEOUT_US);
+		if (ret) {
+			dev_err(ospi->dev, "fifo timeout (len:%d stat:%#x)\n",
+				len, sr);
+			return ret;
+		}
+		fifo(buf++, regs_base + OSPI_DR);
+	}
+
+	return 0;
+}
+
+static int stm32_ospi_wait_nobusy(struct stm32_ospi *ospi)
+{
+	u32 sr;
+
+	return readl_relaxed_poll_timeout_atomic(ospi->regs_base + OSPI_SR,
+						 sr, !(sr & SR_BUSY), 1,
+						 STM32_BUSY_TIMEOUT_US);
+}
+
+static int stm32_ospi_wait_cmd(struct stm32_ospi *ospi)
+{
+	void __iomem *regs_base = ospi->regs_base;
+	u32 cr, sr;
+	int err = 0;
+
+	if ((readl_relaxed(regs_base + OSPI_SR) & SR_TCF) ||
+	    ospi->fmode == CR_FMODE_APM)
+		goto out;
+
+	reinit_completion(&ospi->data_completion);
+	cr = readl_relaxed(regs_base + OSPI_CR);
+	writel_relaxed(cr | CR_TCIE | CR_TEIE, regs_base + OSPI_CR);
+
+	if (!wait_for_completion_timeout(&ospi->data_completion,
+				msecs_to_jiffies(STM32_COMP_TIMEOUT_MS)))
+		err = -ETIMEDOUT;
+
+	sr = readl_relaxed(regs_base + OSPI_SR);
+	if (sr & SR_TCF)
+		/* avoid false timeout */
+		err = 0;
+	if (sr & SR_TEF)
+		err = -EIO;
+
+out:
+	/* clear flags */
+	writel_relaxed(FCR_CTCF | FCR_CTEF, regs_base + OSPI_FCR);
+
+	if (!err)
+		err = stm32_ospi_wait_nobusy(ospi);
+
+	return err;
+}
+
+static void stm32_ospi_dma_callback(void *arg)
+{
+	struct completion *dma_completion = arg;
+
+	complete(dma_completion);
+}
+
+static irqreturn_t stm32_ospi_irq(int irq, void *dev_id)
+{
+	struct stm32_ospi *ospi = (struct stm32_ospi *)dev_id;
+	void __iomem *regs_base = ospi->regs_base;
+	u32 cr, sr;
+
+	cr = readl_relaxed(regs_base + OSPI_CR);
+	sr = readl_relaxed(regs_base + OSPI_SR);
+
+	if (cr & CR_SMIE && sr & SR_SMF) {
+		/* disable irq */
+		cr &= ~CR_SMIE;
+		writel_relaxed(cr, regs_base + OSPI_CR);
+		complete(&ospi->match_completion);
+
+		return IRQ_HANDLED;
+	}
+
+	if (sr & (SR_TEF | SR_TCF)) {
+		/* disable irq */
+		cr &= ~CR_TCIE & ~CR_TEIE;
+		writel_relaxed(cr, regs_base + OSPI_CR);
+		complete(&ospi->data_completion);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void stm32_ospi_dma_setup(struct stm32_ospi *ospi,
+			 struct dma_slave_config *dma_cfg)
+{
+	if (dma_cfg && ospi->dma_chrx) {
+		if (dmaengine_slave_config(ospi->dma_chrx, dma_cfg)) {
+			dev_err(ospi->dev, "dma rx config failed\n");
+			dma_release_channel(ospi->dma_chrx);
+			ospi->dma_chrx = NULL;
+		}
+	}
+
+	if (dma_cfg && ospi->dma_chtx) {
+		if (dmaengine_slave_config(ospi->dma_chtx, dma_cfg)) {
+			dev_err(ospi->dev, "dma tx config failed\n");
+			dma_release_channel(ospi->dma_chtx);
+			ospi->dma_chtx = NULL;
+		}
+	}
+
+	init_completion(&ospi->dma_completion);
+}
+
+static int stm32_ospi_tx_mm(struct stm32_ospi *ospi,
+			    const struct spi_mem_op *op)
+{
+	memcpy_fromio(op->data.buf.in, ospi->mm_base + op->addr.val,
+		      op->data.nbytes);
+	return 0;
+}
+
+static int stm32_ospi_tx_dma(struct stm32_ospi *ospi,
+			     const struct spi_mem_op *op)
+{
+	struct dma_async_tx_descriptor *desc;
+	void __iomem *regs_base = ospi->regs_base;
+	enum dma_transfer_direction dma_dir;
+	struct dma_chan *dma_ch;
+	struct sg_table sgt;
+	dma_cookie_t cookie;
+	u32 cr, t_out;
+	int err;
+
+	if (op->data.dir == SPI_MEM_DATA_IN) {
+		dma_dir = DMA_DEV_TO_MEM;
+		dma_ch = ospi->dma_chrx;
+	} else {
+		dma_dir = DMA_MEM_TO_DEV;
+		dma_ch = ospi->dma_chtx;
+	}
+
+	/*
+	 * Spi_map_buf return -EINVAL if the buffer is not DMA-able
+	 * (DMA-able: in vmalloc | kmap | virt_addr_valid)
+	 */
+	err = spi_controller_dma_map_mem_op_data(ospi->ctrl, op, &sgt);
+	if (err)
+		return err;
+
+	desc = dmaengine_prep_slave_sg(dma_ch, sgt.sgl, sgt.nents,
+				       dma_dir, DMA_PREP_INTERRUPT);
+	if (!desc) {
+		err = -ENOMEM;
+		goto out_unmap;
+	}
+
+	cr = readl_relaxed(regs_base + OSPI_CR);
+
+	reinit_completion(&ospi->dma_completion);
+	desc->callback = stm32_ospi_dma_callback;
+	desc->callback_param = &ospi->dma_completion;
+	cookie = dmaengine_submit(desc);
+	err = dma_submit_error(cookie);
+	if (err)
+		goto out;
+
+	dma_async_issue_pending(dma_ch);
+
+	writel_relaxed(cr | CR_DMAEN, regs_base + OSPI_CR);
+
+	t_out = sgt.nents * STM32_COMP_TIMEOUT_MS;
+	if (!wait_for_completion_timeout(&ospi->dma_completion,
+					 msecs_to_jiffies(t_out)))
+		err = -ETIMEDOUT;
+
+	if (err)
+		dmaengine_terminate_all(dma_ch);
+
+out:
+	writel_relaxed(cr & ~CR_DMAEN, regs_base + OSPI_CR);
+out_unmap:
+	spi_controller_dma_unmap_mem_op_data(ospi->ctrl, op, &sgt);
+
+	return err;
+}
+
+static int stm32_ospi_xfer(struct stm32_ospi *ospi, const struct spi_mem_op *op)
+{
+	u8 *buf;
+
+	if (!op->data.nbytes)
+		return 0;
+
+	if (ospi->fmode == CR_FMODE_MM)
+		return stm32_ospi_tx_mm(ospi, op);
+	else if (((op->data.dir == SPI_MEM_DATA_IN && ospi->dma_chrx) ||
+		 (op->data.dir == SPI_MEM_DATA_OUT && ospi->dma_chtx)) &&
+		  op->data.nbytes > 8)
+		if (!stm32_ospi_tx_dma(ospi, op))
+			return 0;
+
+	if (op->data.dir == SPI_MEM_DATA_IN)
+		buf = op->data.buf.in;
+	else
+		buf = (u8 *)op->data.buf.out;
+
+	return stm32_ospi_poll(ospi, buf, op->data.nbytes,
+			       op->data.dir == SPI_MEM_DATA_IN);
+}
+
+static int stm32_ospi_wait_poll_status(struct stm32_ospi *ospi,
+				       const struct spi_mem_op *op)
+{
+	void __iomem *regs_base = ospi->regs_base;
+	u32 cr;
+
+	reinit_completion(&ospi->match_completion);
+	cr = readl_relaxed(regs_base + OSPI_CR);
+	writel_relaxed(cr | CR_SMIE, regs_base + OSPI_CR);
+
+	if (!wait_for_completion_timeout(&ospi->match_completion,
+					 msecs_to_jiffies(ospi->status_timeout))) {
+		u32 sr = readl_relaxed(regs_base + OSPI_SR);
+
+		/* Avoid false timeout */
+		if (!(sr & SR_SMF))
+			return -ETIMEDOUT;
+	}
+
+	writel_relaxed(FCR_CSMF, regs_base + OSPI_FCR);
+
+	return 0;
+}
+
+static int stm32_ospi_get_mode(u8 buswidth)
+{
+	switch (buswidth) {
+	case 8:
+		return CCR_BUSWIDTH_8;
+	case 4:
+		return CCR_BUSWIDTH_4;
+	default:
+		return buswidth;
+	}
+}
+
+static int stm32_ospi_send(struct spi_device *spi, const struct spi_mem_op *op)
+{
+	struct stm32_ospi *ospi = spi_controller_get_devdata(spi->controller);
+	void __iomem *regs_base = ospi->regs_base;
+	u32 ccr, cr, dcr2, tcr;
+	int timeout, err = 0, err_poll_status = 0;
+	u8 cs = spi->chip_select[ffs(spi->cs_index_mask) - 1];
+
+	dev_dbg(ospi->dev, "cmd:%#x mode:%d.%d.%d.%d addr:%#llx len:%#x\n",
+		op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth,
+		op->dummy.buswidth, op->data.buswidth,
+		op->addr.val, op->data.nbytes);
+
+	cr = readl_relaxed(ospi->regs_base + OSPI_CR);
+	cr &= ~CR_CSSEL;
+	cr |= FIELD_PREP(CR_CSSEL, cs);
+	cr &= ~CR_FMODE_MASK;
+	cr |= FIELD_PREP(CR_FMODE_MASK, ospi->fmode);
+	writel_relaxed(cr, regs_base + OSPI_CR);
+
+	if (op->data.nbytes)
+		writel_relaxed(op->data.nbytes - 1, regs_base + OSPI_DLR);
+
+	/* set prescaler */
+	dcr2 = readl_relaxed(regs_base + OSPI_DCR2);
+	dcr2 |= FIELD_PREP(DCR2_PRESC_MASK, ospi->flash_presc[cs]);
+	writel_relaxed(dcr2, regs_base + OSPI_DCR2);
+
+	ccr = FIELD_PREP(CCR_IMODE_MASK, stm32_ospi_get_mode(op->cmd.buswidth));
+
+	if (op->addr.nbytes) {
+		ccr |= FIELD_PREP(CCR_ADMODE_MASK,
+				  stm32_ospi_get_mode(op->addr.buswidth));
+		ccr |= FIELD_PREP(CCR_ADSIZE_MASK, op->addr.nbytes - 1);
+	}
+
+	tcr = TCR_SSHIFT;
+	if (op->dummy.buswidth && op->dummy.nbytes) {
+		tcr |= FIELD_PREP(TCR_DCYC_MASK,
+				  op->dummy.nbytes * 8 / op->dummy.buswidth);
+	}
+	writel_relaxed(tcr, regs_base + OSPI_TCR);
+
+	if (op->data.nbytes) {
+		ccr |= FIELD_PREP(CCR_DMODE_MASK,
+				  stm32_ospi_get_mode(op->data.buswidth));
+	}
+
+	writel_relaxed(ccr, regs_base + OSPI_CCR);
+
+	/* set instruction, must be set after ccr register update */
+	writel_relaxed(op->cmd.opcode, regs_base + OSPI_IR);
+
+	if (op->addr.nbytes && ospi->fmode != CR_FMODE_MM)
+		writel_relaxed(op->addr.val, regs_base + OSPI_AR);
+
+	if (ospi->fmode == CR_FMODE_APM)
+		err_poll_status = stm32_ospi_wait_poll_status(ospi, op);
+
+	err = stm32_ospi_xfer(ospi, op);
+
+	/*
+	 * Abort in:
+	 * -error case
+	 * -read memory map: prefetching must be stopped if we read the last
+	 *  byte of device (device size - fifo size). like device size is not
+	 *  knows, the prefetching is always stop.
+	 */
+	if (err || err_poll_status || ospi->fmode == CR_FMODE_MM)
+		goto abort;
+
+	/* Wait end of tx in indirect mode */
+	err = stm32_ospi_wait_cmd(ospi);
+	if (err)
+		goto abort;
+
+	return 0;
+
+abort:
+	timeout = stm32_ospi_abort(ospi);
+	writel_relaxed(FCR_CTCF | FCR_CSMF, regs_base + OSPI_FCR);
+
+	if (err || err_poll_status || timeout)
+		dev_err(ospi->dev, "%s err:%d err_poll_status:%d abort timeout:%d\n",
+			__func__, err, err_poll_status, timeout);
+
+	return err;
+}
+
+static int stm32_ospi_poll_status(struct spi_mem *mem,
+				  const struct spi_mem_op *op,
+				  u16 mask, u16 match,
+				  unsigned long initial_delay_us,
+				  unsigned long polling_rate_us,
+				  unsigned long timeout_ms)
+{
+	struct stm32_ospi *ospi = spi_controller_get_devdata(mem->spi->controller);
+	void __iomem *regs_base = ospi->regs_base;
+	int ret;
+
+	ret = pm_runtime_resume_and_get(ospi->dev);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&ospi->lock);
+
+	writel_relaxed(mask, regs_base + OSPI_PSMKR);
+	writel_relaxed(match, regs_base + OSPI_PSMAR);
+	ospi->fmode = CR_FMODE_APM;
+	ospi->status_timeout = timeout_ms;
+
+	ret = stm32_ospi_send(mem->spi, op);
+	mutex_unlock(&ospi->lock);
+
+	pm_runtime_mark_last_busy(ospi->dev);
+	pm_runtime_put_autosuspend(ospi->dev);
+
+	return ret;
+}
+
+static int stm32_ospi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
+{
+	struct stm32_ospi *ospi = spi_controller_get_devdata(mem->spi->controller);
+	int ret;
+
+	ret = pm_runtime_resume_and_get(ospi->dev);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&ospi->lock);
+	if (op->data.dir == SPI_MEM_DATA_IN && op->data.nbytes)
+		ospi->fmode = CR_FMODE_INDR;
+	else
+		ospi->fmode = CR_FMODE_INDW;
+
+	ret = stm32_ospi_send(mem->spi, op);
+	mutex_unlock(&ospi->lock);
+
+	pm_runtime_mark_last_busy(ospi->dev);
+	pm_runtime_put_autosuspend(ospi->dev);
+
+	return ret;
+}
+
+static int stm32_ospi_dirmap_create(struct spi_mem_dirmap_desc *desc)
+{
+	struct stm32_ospi *ospi = spi_controller_get_devdata(desc->mem->spi->controller);
+
+	if (desc->info.op_tmpl.data.dir == SPI_MEM_DATA_OUT)
+		return -EOPNOTSUPP;
+
+	/* Should never happen, as mm_base == null is an error probe exit condition */
+	if (!ospi->mm_base && desc->info.op_tmpl.data.dir == SPI_MEM_DATA_IN)
+		return -EOPNOTSUPP;
+
+	if (!ospi->mm_size)
+		return -EOPNOTSUPP;
+
+	return 0;
+}
+
+static ssize_t stm32_ospi_dirmap_read(struct spi_mem_dirmap_desc *desc,
+				      u64 offs, size_t len, void *buf)
+{
+	struct stm32_ospi *ospi = spi_controller_get_devdata(desc->mem->spi->controller);
+	struct spi_mem_op op;
+	u32 addr_max;
+	int ret;
+
+	ret = pm_runtime_resume_and_get(ospi->dev);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&ospi->lock);
+	/*
+	 * Make a local copy of desc op_tmpl and complete dirmap rdesc
+	 * spi_mem_op template with offs, len and *buf in  order to get
+	 * all needed transfer information into struct spi_mem_op
+	 */
+	memcpy(&op, &desc->info.op_tmpl, sizeof(struct spi_mem_op));
+	dev_dbg(ospi->dev, "%s len = 0x%zx offs = 0x%llx buf = 0x%p\n", __func__, len, offs, buf);
+
+	op.data.nbytes = len;
+	op.addr.val = desc->info.offset + offs;
+	op.data.buf.in = buf;
+
+	addr_max = op.addr.val + op.data.nbytes + 1;
+	if (addr_max < ospi->mm_size && op.addr.buswidth)
+		ospi->fmode = CR_FMODE_MM;
+	else
+		ospi->fmode = CR_FMODE_INDR;
+
+	ret = stm32_ospi_send(desc->mem->spi, &op);
+	mutex_unlock(&ospi->lock);
+
+	pm_runtime_mark_last_busy(ospi->dev);
+	pm_runtime_put_autosuspend(ospi->dev);
+
+	return ret ?: len;
+}
+
+static int stm32_ospi_transfer_one_message(struct spi_controller *ctrl,
+					   struct spi_message *msg)
+{
+	struct stm32_ospi *ospi = spi_controller_get_devdata(ctrl);
+	struct spi_transfer *transfer;
+	struct spi_device *spi = msg->spi;
+	struct spi_mem_op op;
+	struct gpio_desc *cs_gpiod = spi->cs_gpiod[ffs(spi->cs_index_mask) - 1];
+	int ret = 0;
+
+	if (!cs_gpiod)
+		return -EOPNOTSUPP;
+
+	ret = pm_runtime_resume_and_get(ospi->dev);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&ospi->lock);
+
+	gpiod_set_value_cansleep(cs_gpiod, true);
+
+	list_for_each_entry(transfer, &msg->transfers, transfer_list) {
+		u8 dummy_bytes = 0;
+
+		memset(&op, 0, sizeof(op));
+
+		dev_dbg(ospi->dev, "tx_buf:%p tx_nbits:%d rx_buf:%p rx_nbits:%d len:%d dummy_data:%d\n",
+			transfer->tx_buf, transfer->tx_nbits,
+			transfer->rx_buf, transfer->rx_nbits,
+			transfer->len, transfer->dummy_data);
+
+		/*
+		 * OSPI hardware supports dummy bytes transfer.
+		 * If current transfer is dummy byte, merge it with the next
+		 * transfer in order to take into account OSPI block constraint
+		 */
+		if (transfer->dummy_data) {
+			op.dummy.buswidth = transfer->tx_nbits;
+			op.dummy.nbytes = transfer->len;
+			dummy_bytes = transfer->len;
+
+			/* If happens, means that message is not correctly built */
+			if (list_is_last(&transfer->transfer_list, &msg->transfers)) {
+				ret = -EINVAL;
+				goto end_of_transfer;
+			}
+
+			transfer = list_next_entry(transfer, transfer_list);
+		}
+
+		op.data.nbytes = transfer->len;
+
+		if (transfer->rx_buf) {
+			ospi->fmode = CR_FMODE_INDR;
+			op.data.buswidth = transfer->rx_nbits;
+			op.data.dir = SPI_MEM_DATA_IN;
+			op.data.buf.in = transfer->rx_buf;
+		} else {
+			ospi->fmode = CR_FMODE_INDW;
+			op.data.buswidth = transfer->tx_nbits;
+			op.data.dir = SPI_MEM_DATA_OUT;
+			op.data.buf.out = transfer->tx_buf;
+		}
+
+		ret = stm32_ospi_send(spi, &op);
+		if (ret)
+			goto end_of_transfer;
+
+		msg->actual_length += transfer->len + dummy_bytes;
+	}
+
+end_of_transfer:
+	gpiod_set_value_cansleep(cs_gpiod, false);
+
+	mutex_unlock(&ospi->lock);
+
+	msg->status = ret;
+	spi_finalize_current_message(ctrl);
+
+	pm_runtime_mark_last_busy(ospi->dev);
+	pm_runtime_put_autosuspend(ospi->dev);
+
+	return ret;
+}
+
+static int stm32_ospi_setup(struct spi_device *spi)
+{
+	struct spi_controller *ctrl = spi->controller;
+	struct stm32_ospi *ospi = spi_controller_get_devdata(ctrl);
+	void __iomem *regs_base = ospi->regs_base;
+	int ret;
+	u8 cs = spi->chip_select[ffs(spi->cs_index_mask) - 1];
+
+	if (ctrl->busy)
+		return -EBUSY;
+
+	if (!spi->max_speed_hz)
+		return -EINVAL;
+
+	ret = pm_runtime_resume_and_get(ospi->dev);
+	if (ret < 0)
+		return ret;
+
+	ospi->flash_presc[cs] = DIV_ROUND_UP(ospi->clk_rate, spi->max_speed_hz) - 1;
+
+	mutex_lock(&ospi->lock);
+
+	ospi->cr_reg = CR_APMS | 3 << CR_FTHRES_SHIFT | CR_EN;
+	writel_relaxed(ospi->cr_reg, regs_base + OSPI_CR);
+
+	/* set dcr fsize to max address */
+	ospi->dcr_reg = DCR1_DEVSIZE_MASK | DCR1_DLYBYP;
+	writel_relaxed(ospi->dcr_reg, regs_base + OSPI_DCR1);
+
+	mutex_unlock(&ospi->lock);
+
+	pm_runtime_mark_last_busy(ospi->dev);
+	pm_runtime_put_autosuspend(ospi->dev);
+
+	return 0;
+}
+
+/*
+ * No special host constraint, so use default spi_mem_default_supports_op
+ * to check supported mode.
+ */
+static const struct spi_controller_mem_ops stm32_ospi_mem_ops = {
+	.exec_op	= stm32_ospi_exec_op,
+	.dirmap_create	= stm32_ospi_dirmap_create,
+	.dirmap_read	= stm32_ospi_dirmap_read,
+	.poll_status	= stm32_ospi_poll_status,
+};
+
+static int stm32_ospi_get_resources(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct stm32_ospi *ospi = platform_get_drvdata(pdev);
+	struct resource *res;
+	struct reserved_mem *rmem = NULL;
+	struct device_node *node;
+	int ret;
+
+	ospi->regs_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+	if (IS_ERR(ospi->regs_base))
+		return PTR_ERR(ospi->regs_base);
+
+	ospi->regs_phys_base = res->start;
+
+	ospi->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(ospi->clk))
+		return dev_err_probe(dev, PTR_ERR(ospi->clk),
+				     "Can't get clock\n");
+
+	ospi->clk_rate = clk_get_rate(ospi->clk);
+	if (!ospi->clk_rate) {
+		dev_err(dev, "Invalid clock rate\n");
+		return -EINVAL;
+	}
+
+	ospi->irq = platform_get_irq(pdev, 0);
+	if (ospi->irq < 0) {
+		dev_err(dev, "Can't get irq %d\n", ospi->irq);
+		return ospi->irq;
+	}
+
+	ret = devm_request_irq(dev, ospi->irq, stm32_ospi_irq, 0,
+			       dev_name(dev), ospi);
+	if (ret) {
+		dev_err(dev, "Failed to request irq\n");
+		return ret;
+	}
+
+	ospi->rstc = devm_reset_control_array_get_optional_exclusive(dev);
+	if (IS_ERR(ospi->rstc))
+		return dev_err_probe(dev, PTR_ERR(ospi->rstc),
+				     "Can't get reset\n");
+
+	ospi->dma_chrx = dma_request_chan(dev, "rx");
+	if (IS_ERR(ospi->dma_chrx)) {
+		ret = PTR_ERR(ospi->dma_chrx);
+		ospi->dma_chrx = NULL;
+		if (ret == -EPROBE_DEFER)
+			goto err_dma;
+	}
+
+	ospi->dma_chtx = dma_request_chan(dev, "tx");
+	if (IS_ERR(ospi->dma_chtx)) {
+		ret = PTR_ERR(ospi->dma_chtx);
+		ospi->dma_chtx = NULL;
+		if (ret == -EPROBE_DEFER)
+			goto err_dma;
+	}
+
+	node = of_parse_phandle(dev->of_node, "memory-region", 0);
+	if (node)
+		rmem = of_reserved_mem_lookup(node);
+	of_node_put(node);
+
+	if (rmem) {
+		ospi->mm_size = rmem->size;
+		ospi->mm_base = devm_ioremap(dev, rmem->base, rmem->size);
+		if (IS_ERR(ospi->mm_base)) {
+			dev_err(dev, "unable to map memory region: %pa+%pa\n",
+				&rmem->base, &rmem->size);
+			ret = PTR_ERR(ospi->mm_base);
+			goto err_dma;
+		}
+
+		if (ospi->mm_size > STM32_OSPI_MAX_MMAP_SZ) {
+			dev_err(dev, "Memory map size outsize bounds\n");
+			ret = -EINVAL;
+			goto err_dma;
+		}
+	} else {
+		dev_info(dev, "No memory-map region found\n");
+	}
+
+	init_completion(&ospi->data_completion);
+	init_completion(&ospi->match_completion);
+
+	return 0;
+
+err_dma:
+	dev_info(dev, "Can't get all resources (%d)\n", ret);
+
+	if (ospi->dma_chtx)
+		dma_release_channel(ospi->dma_chtx);
+	if (ospi->dma_chrx)
+		dma_release_channel(ospi->dma_chrx);
+
+	return ret;
+};
+
+static int stm32_ospi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct spi_controller *ctrl;
+	struct stm32_ospi *ospi;
+	struct dma_slave_config dma_cfg;
+	struct device_node *child;
+	int ret;
+	u8 spi_flash_count = 0;
+
+	/*
+	 * Flash subnodes sanity check:
+	 *        1 or 2 spi-nand/spi-nor flashes		=> supported
+	 *	  All other flash node configuration		=> not supported
+	 */
+	for_each_available_child_of_node(dev->of_node, child) {
+		if (of_device_is_compatible(child, "jedec,spi-nor") ||
+		    of_device_is_compatible(child, "spi-nand"))
+			spi_flash_count++;
+	}
+
+	if (spi_flash_count == 0 || spi_flash_count > 2) {
+		dev_err(dev, "Incorrect DT flash node\n");
+		return -ENODEV;
+	}
+
+	ctrl = devm_spi_alloc_host(dev, sizeof(*ospi));
+	if (!ctrl)
+		return -ENOMEM;
+
+	ospi = spi_controller_get_devdata(ctrl);
+	ospi->ctrl = ctrl;
+
+	ospi->dev = &pdev->dev;
+	platform_set_drvdata(pdev, ospi);
+
+	ret = stm32_ospi_get_resources(pdev);
+	if (ret)
+		return ret;
+
+	memset(&dma_cfg, 0, sizeof(dma_cfg));
+	dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+	dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+	dma_cfg.src_addr = ospi->regs_phys_base + OSPI_DR;
+	dma_cfg.dst_addr = ospi->regs_phys_base + OSPI_DR;
+	dma_cfg.src_maxburst = 4;
+	dma_cfg.dst_maxburst = 4;
+	stm32_ospi_dma_setup(ospi, &dma_cfg);
+
+	mutex_init(&ospi->lock);
+
+	ctrl->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD |
+			  SPI_TX_DUAL | SPI_TX_QUAD |
+			  SPI_TX_OCTAL | SPI_RX_OCTAL;
+	ctrl->flags = SPI_CONTROLLER_HALF_DUPLEX;
+	ctrl->setup = stm32_ospi_setup;
+	ctrl->bus_num = -1;
+	ctrl->mem_ops = &stm32_ospi_mem_ops;
+	ctrl->use_gpio_descriptors = true;
+	ctrl->transfer_one_message = stm32_ospi_transfer_one_message;
+	ctrl->num_chipselect = STM32_OSPI_MAX_NORCHIP;
+	ctrl->dev.of_node = dev->of_node;
+
+	pm_runtime_enable(ospi->dev);
+	pm_runtime_set_autosuspend_delay(ospi->dev, STM32_AUTOSUSPEND_DELAY);
+	pm_runtime_use_autosuspend(ospi->dev);
+
+	ret = pm_runtime_resume_and_get(ospi->dev);
+	if (ret < 0)
+		goto err_pm_enable;
+
+	if (ospi->rstc) {
+		reset_control_assert(ospi->rstc);
+		udelay(2);
+		reset_control_deassert(ospi->rstc);
+	}
+
+	ret = spi_register_controller(ctrl);
+	if (ret) {
+		/* Disable ospi */
+		writel_relaxed(0, ospi->regs_base + OSPI_CR);
+		goto err_pm_resume;
+	}
+
+	pm_runtime_mark_last_busy(ospi->dev);
+	pm_runtime_put_autosuspend(ospi->dev);
+
+	return 0;
+
+err_pm_resume:
+	pm_runtime_put_sync_suspend(ospi->dev);
+
+err_pm_enable:
+	pm_runtime_force_suspend(ospi->dev);
+	mutex_destroy(&ospi->lock);
+
+	return ret;
+}
+
+static void stm32_ospi_remove(struct platform_device *pdev)
+{
+	struct stm32_ospi *ospi = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = pm_runtime_resume_and_get(ospi->dev);
+	if (ret < 0)
+		return;
+
+	spi_unregister_controller(ospi->ctrl);
+	/* Disable ospi */
+	writel_relaxed(0, ospi->regs_base + OSPI_CR);
+	mutex_destroy(&ospi->lock);
+
+	if (ospi->dma_chtx)
+		dma_release_channel(ospi->dma_chtx);
+	if (ospi->dma_chrx)
+		dma_release_channel(ospi->dma_chrx);
+
+	pm_runtime_put_sync_suspend(ospi->dev);
+	pm_runtime_force_suspend(ospi->dev);
+}
+
+static int __maybe_unused stm32_ospi_suspend(struct device *dev)
+{
+	struct stm32_ospi *ospi = dev_get_drvdata(dev);
+
+	pinctrl_pm_select_sleep_state(dev);
+
+	return pm_runtime_force_suspend(ospi->dev);
+}
+
+static int __maybe_unused stm32_ospi_resume(struct device *dev)
+{
+	struct stm32_ospi *ospi = dev_get_drvdata(dev);
+	void __iomem *regs_base = ospi->regs_base;
+	int ret;
+
+	ret = pm_runtime_force_resume(ospi->dev);
+	if (ret < 0)
+		return ret;
+
+	pinctrl_pm_select_default_state(dev);
+
+	ret = pm_runtime_resume_and_get(ospi->dev);
+	if (ret < 0)
+		return ret;
+
+	writel_relaxed(ospi->cr_reg, regs_base + OSPI_CR);
+	writel_relaxed(ospi->dcr_reg, regs_base + OSPI_DCR1);
+	pm_runtime_mark_last_busy(ospi->dev);
+	pm_runtime_put_autosuspend(ospi->dev);
+
+	return 0;
+}
+
+static int __maybe_unused stm32_ospi_runtime_suspend(struct device *dev)
+{
+	struct stm32_ospi *ospi = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(ospi->clk);
+
+	return 0;
+}
+
+static int __maybe_unused stm32_ospi_runtime_resume(struct device *dev)
+{
+	struct stm32_ospi *ospi = dev_get_drvdata(dev);
+
+	return clk_prepare_enable(ospi->clk);
+}
+
+static const struct dev_pm_ops stm32_ospi_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(stm32_ospi_suspend, stm32_ospi_resume)
+	SET_RUNTIME_PM_OPS(stm32_ospi_runtime_suspend,
+			   stm32_ospi_runtime_resume, NULL)
+};
+
+static const struct of_device_id stm32_ospi_of_match[] = {
+	{ .compatible = "st,stm32mp25-ospi" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, stm32_ospi_of_match);
+
+static struct platform_driver stm32_ospi_driver = {
+	.probe	= stm32_ospi_probe,
+	.remove	= stm32_ospi_remove,
+	.driver	= {
+		.name = "stm32-ospi",
+		.pm = &stm32_ospi_pm_ops,
+		.of_match_table = stm32_ospi_of_match,
+	},
+};
+module_platform_driver(stm32_ospi_driver);
+
+MODULE_DESCRIPTION("STMicroelectronics STM32 OCTO SPI driver");
+MODULE_LICENSE("GPL");
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v5 3/8] dt-bindings: memory-controllers: Add STM32 Octo Memory Manager controller
  2025-02-19  8:00 [PATCH v5 0/8] Add STM32MP25 SPI NOR support patrice.chotard
  2025-02-19  8:00 ` [PATCH v5 1/8] dt-bindings: spi: Add STM32 OSPI controller patrice.chotard
  2025-02-19  8:00 ` [PATCH v5 2/8] spi: stm32: Add OSPI driver patrice.chotard
@ 2025-02-19  8:00 ` patrice.chotard
  2025-02-19  8:00 ` [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver patrice.chotard
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: patrice.chotard @ 2025-02-19  8:00 UTC (permalink / raw)
  To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello, patrice.chotard,
	Krzysztof Kozlowski

From: Patrice Chotard <patrice.chotard@foss.st.com>

Add bindings for STM32 Octo Memory Manager (OMM) controller.

OMM manages:
  - the muxing between 2 OSPI busses and 2 output ports.
    There are 4 possible muxing configurations:
      - direct mode (no multiplexing): OSPI1 output is on port 1 and OSPI2
        output is on port 2
      - OSPI1 and OSPI2 are multiplexed over the same output port 1
      - swapped mode (no multiplexing), OSPI1 output is on port 2,
        OSPI2 output is on port 1
      - OSPI1 and OSPI2 are multiplexed over the same output port 2
  - the split of the memory area shared between the 2 OSPI instances.
  - chip select selection override.
  - the time between 2 transactions in multiplexed mode.

Signed-off-by: Patrice Chotard <patrice.chotard@foss.st.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
---
 .../memory-controllers/st,stm32mp25-omm.yaml  | 204 ++++++++++++++++++
 1 file changed, 204 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/memory-controllers/st,stm32mp25-omm.yaml

diff --git a/Documentation/devicetree/bindings/memory-controllers/st,stm32mp25-omm.yaml b/Documentation/devicetree/bindings/memory-controllers/st,stm32mp25-omm.yaml
new file mode 100644
index 000000000000..316e16ce238e
--- /dev/null
+++ b/Documentation/devicetree/bindings/memory-controllers/st,stm32mp25-omm.yaml
@@ -0,0 +1,204 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/memory-controllers/st,stm32mp25-omm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: STM32 Octo Memory Manager (OMM)
+
+maintainers:
+  - Patrice Chotard <patrice.chotard@foss.st.com>
+
+description: |
+  The STM32 Octo Memory Manager is a low-level interface that enables an
+  efficient OCTOSPI pin assignment with a full I/O matrix (before alternate
+  function map) and multiplex of single/dual/quad/octal SPI interfaces over
+  the same bus. It Supports up to:
+    - Two single/dual/quad/octal SPI interfaces
+    - Two ports for pin assignment
+
+properties:
+  compatible:
+    const: st,stm32mp25-omm
+
+  "#address-cells":
+    const: 2
+
+  "#size-cells":
+    const: 1
+
+  ranges:
+    description: |
+      Reflects the memory layout with four integer values per OSPI instance.
+      Format:
+      <chip-select> 0 <registers base address> <size>
+    minItems: 2
+    maxItems: 2
+
+  reg:
+    items:
+      - description: OMM registers
+      - description: OMM memory map area
+
+  reg-names:
+    items:
+      - const: regs
+      - const: memory_map
+
+  memory-region:
+    description: |
+      Memory region shared between the 2 OCTOSPI instance.
+      One or two phandle to a node describing a memory mapped region
+      depending of child number.
+    minItems: 1
+    maxItems: 2
+
+  memory-region-names:
+    description: |
+      OCTOSPI instance's name to which memory region is associated
+    items:
+      enum: [ospi1, ospi2]
+    minItems: 1
+    maxItems: 2
+
+  clocks:
+    maxItems: 1
+
+  resets:
+    maxItems: 1
+
+  access-controllers:
+    maxItems: 1
+
+  st,syscfg-amcr:
+    $ref: /schemas/types.yaml#/definitions/phandle-array
+    description: |
+      The Address Mapping Control Register (AMCR) is used to split the 256MB
+      memory map area shared between the 2 OSPI instance. The Octo Memory
+      Manager sets the AMCR depending of the memory-region configuration.
+      The memory split bitmask description is:
+        - 000: OCTOSPI1 (256 Mbytes), OCTOSPI2 unmapped
+        - 001: OCTOSPI1 (192 Mbytes), OCTOSPI2 (64 Mbytes)
+        - 010: OCTOSPI1 (128 Mbytes), OCTOSPI2 (128 Mbytes)
+        - 011: OCTOSPI1 (64 Mbytes), OCTOSPI2 (192 Mbytes)
+        - 1xx: OCTOSPI1 unmapped, OCTOSPI2 (256 Mbytes)
+    items:
+      - description: phandle to syscfg
+      - description: register offset within syscfg
+      - description: register bitmask for memory split
+
+  st,omm-req2ack-ns:
+    description: |
+      In multiplexed mode (MUXEN = 1), this field defines the time in
+      nanoseconds between two transactions.
+    default: 0
+
+  st,omm-cssel-ovr:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      Configure the chip select selector override for the 2 OCTOSPIs.
+      - 0: OCTOSPI1 chip select send to NCS1 OCTOSPI2 chip select send to NCS1
+      - 1: OCTOSPI1 chip select send to NCS2 OCTOSPI2 chip select send to NCS1
+      - 2: OCTOSPI1 chip select send to NCS1 OCTOSPI2 chip select send to NCS2
+      - 3: OCTOSPI1 chip select send to NCS2 OCTOSPI2 chip select send to NCS2
+    minimum: 0
+    maximum: 3
+    default: 0
+
+  st,omm-mux:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      Configure the muxing between the 2 OCTOSPIs busses and the 2 output ports.
+      - 0: direct mode
+      - 1: mux OCTOSPI1 and OCTOSPI2 to port 1
+      - 2: swapped mode
+      - 3: mux OCTOSPI1 and OCTOSPI2 to port 2
+    minimum: 0
+    maximum: 3
+    default: 0
+
+  power-domains:
+    maxItems: 1
+
+patternProperties:
+  ^spi@[a-f0-9]+$:
+    type: object
+    $ref: /schemas/spi/st,stm32mp25-ospi.yaml#
+    description: Required spi child node
+
+required:
+  - compatible
+  - reg
+  - "#address-cells"
+  - "#size-cells"
+  - clocks
+  - st,syscfg-amcr
+  - ranges
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/st,stm32mp25-rcc.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/reset/st,stm32mp25-rcc.h>
+    ommanager@40500000 {
+      compatible = "st,stm32mp25-omm";
+      reg = <0x40500000 0x400>, <0x60000000 0x10000000>;
+      reg-names = "regs", "memory_map";
+      ranges = <0 0 0x40430000 0x400>,
+               <1 0 0x40440000 0x400>;
+      memory-region = <&mm_ospi1>, <&mm_ospi2>;
+      memory-region-names = "ospi1", "ospi2";
+      pinctrl-0 = <&ospi_port1_clk_pins_a
+                   &ospi_port1_io03_pins_a
+                   &ospi_port1_cs0_pins_a>;
+      pinctrl-1 = <&ospi_port1_clk_sleep_pins_a
+                   &ospi_port1_io03_sleep_pins_a
+                   &ospi_port1_cs0_sleep_pins_a>;
+      pinctrl-names = "default", "sleep";
+      clocks = <&rcc CK_BUS_OSPIIOM>;
+      resets = <&rcc OSPIIOM_R>;
+      access-controllers = <&rifsc 111>;
+      power-domains = <&CLUSTER_PD>;
+      #address-cells = <2>;
+      #size-cells = <1>;
+      st,syscfg-amcr = <&syscfg 0x2c00 0x7>;
+      st,omm-req2ack-ns = <0>;
+      st,omm-mux = <0>;
+      st,omm-cssel-ovr = <0>;
+
+      spi@40430000 {
+        compatible = "st,stm32mp25-ospi";
+        reg = <0 0 0x400>;
+        memory-region = <&mm_ospi1>;
+        interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>;
+        dmas = <&hpdma 2 0x62 0x00003121 0x0>,
+               <&hpdma 2 0x42 0x00003112 0x0>;
+        dma-names = "tx", "rx";
+        clocks = <&scmi_clk CK_SCMI_OSPI1>;
+        resets = <&scmi_reset RST_SCMI_OSPI1>, <&scmi_reset RST_SCMI_OSPI1DLL>;
+        access-controllers = <&rifsc 74>;
+        power-domains = <&CLUSTER_PD>;
+        #address-cells = <1>;
+        #size-cells = <0>;
+        st,syscfg-dlyb = <&syscfg 0x1000>;
+      };
+
+      spi@40440000 {
+        compatible = "st,stm32mp25-ospi";
+        reg = <1 0 0x400>;
+        memory-region = <&mm_ospi1>;
+        interrupts = <GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>;
+        dmas = <&hpdma 3 0x62 0x00003121 0x0>,
+               <&hpdma 3 0x42 0x00003112 0x0>;
+        dma-names = "tx", "rx";
+        clocks = <&scmi_clk CK_KER_OSPI2>;
+        resets = <&scmi_reset RST_SCMI_OSPI2>, <&scmi_reset RST_SCMI_OSPI1DLL>;
+        access-controllers = <&rifsc 75>;
+        power-domains = <&CLUSTER_PD>;
+        #address-cells = <1>;
+        #size-cells = <0>;
+        st,syscfg-dlyb = <&syscfg 0x1000>;
+      };
+    };
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver
  2025-02-19  8:00 [PATCH v5 0/8] Add STM32MP25 SPI NOR support patrice.chotard
                   ` (2 preceding siblings ...)
  2025-02-19  8:00 ` [PATCH v5 3/8] dt-bindings: memory-controllers: Add STM32 Octo Memory Manager controller patrice.chotard
@ 2025-02-19  8:00 ` patrice.chotard
  2025-03-10 13:52   ` Patrice CHOTARD
  2025-03-11 16:04   ` Krzysztof Kozlowski
  2025-02-19  8:00 ` [PATCH v5 5/8] arm64: dts: st: Add OMM node on stm32mp251 patrice.chotard
                   ` (4 subsequent siblings)
  8 siblings, 2 replies; 19+ messages in thread
From: patrice.chotard @ 2025-02-19  8:00 UTC (permalink / raw)
  To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello, patrice.chotard

From: Patrice Chotard <patrice.chotard@foss.st.com>

Octo Memory Manager driver (OMM) manages:
  - the muxing between 2 OSPI busses and 2 output ports.
    There are 4 possible muxing configurations:
      - direct mode (no multiplexing): OSPI1 output is on port 1 and OSPI2
        output is on port 2
      - OSPI1 and OSPI2 are multiplexed over the same output port 1
      - swapped mode (no multiplexing), OSPI1 output is on port 2,
        OSPI2 output is on port 1
      - OSPI1 and OSPI2 are multiplexed over the same output port 2
  - the split of the memory area shared between the 2 OSPI instances.
  - chip select selection override.
  - the time between 2 transactions in multiplexed mode.
  - check firewall access.

Signed-off-by: Patrice Chotard <patrice.chotard@foss.st.com>
Signed-off-by: Christophe Kerello <christophe.kerello@foss.st.com>
---
 drivers/memory/Kconfig     |  17 ++
 drivers/memory/Makefile    |   1 +
 drivers/memory/stm32_omm.c | 522 +++++++++++++++++++++++++++++++++++++
 3 files changed, 540 insertions(+)
 create mode 100644 drivers/memory/stm32_omm.c

diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
index c82d8d8a16ea..3a0703fbfee7 100644
--- a/drivers/memory/Kconfig
+++ b/drivers/memory/Kconfig
@@ -225,6 +225,23 @@ config STM32_FMC2_EBI
 	  devices (like SRAM, ethernet adapters, FPGAs, LCD displays, ...) on
 	  SOCs containing the FMC2 External Bus Interface.
 
+config STM32_OMM
+	tristate "STM32 Octo Memory Manager"
+	depends on SPI_STM32_OSPI || COMPILE_TEST
+	help
+	  This driver manages the muxing between the 2 OSPI busses and
+	  the 2 output ports. There are 4 possible muxing configurations:
+	  - direct mode (no multiplexing): OSPI1 output is on port 1 and OSPI2
+	       output is on port 2
+	  - OSPI1 and OSPI2 are multiplexed over the same output port 1
+	  - swapped mode (no multiplexing), OSPI1 output is on port 2,
+	       OSPI2 output is on port 1
+	  - OSPI1 and OSPI2 are multiplexed over the same output port 2
+	  It also manages :
+	    - the split of the memory area shared between the 2 OSPI instances.
+	    - chip select selection override.
+	    - the time between 2 transactions in multiplexed mode.
+
 source "drivers/memory/samsung/Kconfig"
 source "drivers/memory/tegra/Kconfig"
 
diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
index d2e6ca9abbe0..c1959661bf63 100644
--- a/drivers/memory/Makefile
+++ b/drivers/memory/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_DA8XX_DDRCTL)	+= da8xx-ddrctl.o
 obj-$(CONFIG_PL353_SMC)		+= pl353-smc.o
 obj-$(CONFIG_RENESAS_RPCIF)	+= renesas-rpc-if.o
 obj-$(CONFIG_STM32_FMC2_EBI)	+= stm32-fmc2-ebi.o
+obj-$(CONFIG_STM32_OMM)		+= stm32_omm.o
 
 obj-$(CONFIG_SAMSUNG_MC)	+= samsung/
 obj-$(CONFIG_TEGRA_MC)		+= tegra/
diff --git a/drivers/memory/stm32_omm.c b/drivers/memory/stm32_omm.c
new file mode 100644
index 000000000000..8f7f475769e7
--- /dev/null
+++ b/drivers/memory/stm32_omm.c
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2025 - All Rights Reserved
+ * Author(s): Patrice Chotard <patrice.chotard@foss.st.com> for STMicroelectronics.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bus/stm32_firewall_device.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#define OMM_CR			0
+#define CR_MUXEN		BIT(0)
+#define CR_MUXENMODE_MASK	GENMASK(1, 0)
+#define CR_CSSEL_OVR_EN		BIT(4)
+#define CR_CSSEL_OVR_MASK	GENMASK(6, 5)
+#define CR_REQ2ACK_MASK		GENMASK(23, 16)
+
+#define OMM_CHILD_NB		2
+
+struct ospi_child {
+	struct device *dev;
+	struct device_node *node;
+	struct clk *clk;
+};
+
+struct stm32_omm {
+	struct ospi_child child[OMM_CHILD_NB];
+	struct resource *mm_res;
+	struct clk *clk;
+	void __iomem *io_base;
+	u32 cr;
+	u8 nb_child;
+	bool restore_omm;
+};
+
+static int stm32_omm_set_amcr(struct device *dev, bool set)
+{
+	struct stm32_omm *omm = dev_get_drvdata(dev);
+	struct regmap *syscfg_regmap;
+	struct device_node *node;
+	struct resource res, res1;
+	resource_size_t mm_ospi2_size = 0;
+	static const char * const mm_name[] = { "ospi1", "ospi2" };
+	u32 amcr_base, amcr_mask;
+	int ret, idx;
+	unsigned int i, amcr, read_amcr;
+
+	for (i = 0; i < omm->nb_child; i++) {
+		idx = of_property_match_string(dev->of_node,
+					       "memory-region-names",
+					       mm_name[i]);
+		if (idx < 0)
+			continue;
+
+		/* res1 only used on second loop iteration */
+		res1.start = res.start;
+		res1.end = res.end;
+
+		node = of_parse_phandle(dev->of_node, "memory-region", idx);
+		if (!node)
+			continue;
+
+		ret = of_address_to_resource(node, 0, &res);
+		if (ret) {
+			dev_err(dev, "unable to resolve memory region\n");
+			return ret;
+		}
+
+		/* check that memory region fits inside OMM memory map area */
+		if (!resource_contains(omm->mm_res, &res)) {
+			dev_err(dev, "%s doesn't fit inside OMM memory map area\n",
+				mm_name[i]);
+			dev_err(dev, "%pR doesn't fit inside %pR\n", &res, omm->mm_res);
+
+			return -EFAULT;
+		}
+
+		if (i == 1) {
+			mm_ospi2_size = resource_size(&res);
+
+			/* check that OMM memory region 1 doesn't overlap memory region 2 */
+			if (resource_overlaps(&res, &res1)) {
+				dev_err(dev, "OMM memory-region %s overlaps memory region %s\n",
+					mm_name[0], mm_name[1]);
+				dev_err(dev, "%pR overlaps %pR\n", &res1, &res);
+
+				return -EFAULT;
+			}
+		}
+	}
+
+	syscfg_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "st,syscfg-amcr");
+	if (IS_ERR(syscfg_regmap)) {
+		dev_err(dev, "Failed to get st,syscfg-amcr property\n");
+		return PTR_ERR(syscfg_regmap);
+	}
+
+	ret = of_property_read_u32_index(dev->of_node, "st,syscfg-amcr", 1,
+					 &amcr_base);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_u32_index(dev->of_node, "st,syscfg-amcr", 2,
+					 &amcr_mask);
+	if (ret)
+		return ret;
+
+	amcr = mm_ospi2_size / SZ_64M;
+
+	if (set)
+		regmap_update_bits(syscfg_regmap, amcr_base, amcr_mask, amcr);
+
+	/* read AMCR and check coherency with memory-map areas defined in DT */
+	regmap_read(syscfg_regmap, amcr_base, &read_amcr);
+	read_amcr = read_amcr >> (ffs(amcr_mask) - 1);
+
+	if (amcr != read_amcr) {
+		dev_err(dev, "AMCR value not coherent with DT memory-map areas\n");
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int stm32_omm_enable_child_clock(struct device *dev, bool enable)
+{
+	/* As there is only 2 children, remember first child in case of error */
+	struct clk *first_child_clk = NULL;
+	struct stm32_omm *omm = dev_get_drvdata(dev);
+	u8 i;
+	int ret;
+
+	for (i = 0; i < omm->nb_child; i++) {
+		if (enable) {
+			ret = clk_prepare_enable(omm->child[i].clk);
+			if (ret) {
+				if (first_child_clk)
+					clk_disable_unprepare(first_child_clk);
+
+				dev_err(dev, "Can not enable clock\n");
+				return ret;
+			}
+		} else {
+			clk_disable_unprepare(omm->child[i].clk);
+		}
+
+		first_child_clk = omm->child[i].clk;
+	}
+
+	return 0;
+}
+
+static int stm32_omm_configure(struct device *dev)
+{
+	struct stm32_omm *omm = dev_get_drvdata(dev);
+	struct reset_control *rstc;
+	unsigned long clk_rate, clk_rate_max = 0;
+	int ret;
+	u8 i;
+	u32 mux = 0;
+	u32 cssel_ovr = 0;
+	u32 req2ack = 0;
+
+	omm->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(omm->clk)) {
+		dev_err(dev, "Failed to get OMM clock (%ld)\n",
+			PTR_ERR(omm->clk));
+
+		return PTR_ERR(omm->clk);
+	}
+
+	ret = pm_runtime_resume_and_get(dev);
+	if (ret < 0)
+		return ret;
+
+	/* parse children's clock */
+	for (i = 0; i < omm->nb_child; i++) {
+		clk_rate = clk_get_rate(omm->child[i].clk);
+		if (!clk_rate) {
+			dev_err(dev, "Invalid clock rate\n");
+			pm_runtime_disable(dev);
+			goto err_clk_disable;
+		}
+
+		if (clk_rate > clk_rate_max)
+			clk_rate_max = clk_rate;
+	}
+
+	rstc = devm_reset_control_get_optional_exclusive(dev, NULL);
+	if (IS_ERR(rstc)) {
+		ret = dev_err_probe(dev, PTR_ERR(rstc), "reset get failed\n");
+		pm_runtime_disable(dev);
+		goto err_clk_disable;
+	}
+
+	reset_control_assert(rstc);
+	udelay(2);
+	reset_control_deassert(rstc);
+
+	omm->cr = readl_relaxed(omm->io_base + OMM_CR);
+	/* optional */
+	ret = of_property_read_u32(dev->of_node, "st,omm-mux", &mux);
+	if (!ret) {
+		if (mux & CR_MUXEN) {
+			ret = of_property_read_u32(dev->of_node, "st,omm-req2ack-ns",
+						   &req2ack);
+			if (!ret && !req2ack) {
+				req2ack = DIV_ROUND_UP(req2ack, NSEC_PER_SEC / clk_rate_max) - 1;
+
+				if (req2ack > 256)
+					req2ack = 256;
+			}
+
+			req2ack = FIELD_PREP(CR_REQ2ACK_MASK, req2ack);
+
+			omm->cr &= ~CR_REQ2ACK_MASK;
+			omm->cr |= FIELD_PREP(CR_REQ2ACK_MASK, req2ack);
+
+			/*
+			 * If the mux is enabled, the 2 OSPI clocks have to be
+			 * always enabled
+			 */
+			ret = stm32_omm_enable_child_clock(dev, true);
+			if (ret) {
+				pm_runtime_disable(dev);
+				goto err_clk_disable;
+			}
+		}
+
+		omm->cr &= ~CR_MUXENMODE_MASK;
+		omm->cr |= FIELD_PREP(CR_MUXENMODE_MASK, mux);
+	}
+
+	/* optional */
+	ret = of_property_read_u32(dev->of_node, "st,omm-cssel-ovr", &cssel_ovr);
+	if (!ret) {
+		omm->cr &= ~CR_CSSEL_OVR_MASK;
+		omm->cr |= FIELD_PREP(CR_CSSEL_OVR_MASK, cssel_ovr);
+		omm->cr |= CR_CSSEL_OVR_EN;
+	}
+
+	omm->restore_omm = true;
+	writel_relaxed(omm->cr, omm->io_base + OMM_CR);
+
+	ret = stm32_omm_set_amcr(dev, true);
+
+err_clk_disable:
+	pm_runtime_put_sync_suspend(dev);
+
+	return ret;
+}
+
+static int stm32_omm_check_access(struct device *dev, struct device_node *np)
+{
+	struct stm32_firewall firewall;
+	int ret;
+
+	ret = stm32_firewall_get_firewall(np, &firewall, 1);
+	if (ret)
+		return ret;
+
+	return stm32_firewall_grant_access(&firewall);
+}
+
+static int stm32_omm_disable_child(struct device *dev)
+{
+	struct stm32_omm *omm = dev_get_drvdata(dev);
+	struct reset_control *reset;
+	int ret;
+	u8 i;
+
+	for (i = 0; i < omm->nb_child; i++) {
+		ret = clk_prepare_enable(omm->child[i].clk);
+		if (ret) {
+			dev_err(dev, "Can not enable clock\n");
+			return ret;
+		}
+
+		reset = of_reset_control_get_exclusive(omm->child[i].node, 0);
+		if (IS_ERR(reset)) {
+			dev_err(dev, "Can't get child reset\n");
+			return PTR_ERR(reset);
+		};
+
+		/* reset OSPI to ensure CR_EN bit is set to 0 */
+		reset_control_assert(reset);
+		udelay(2);
+		reset_control_deassert(reset);
+
+		reset_control_put(reset);
+		clk_disable_unprepare(omm->child[i].clk);
+	}
+
+	return 0;
+}
+
+static int stm32_omm_probe(struct platform_device *pdev)
+{
+	struct platform_device *vdev;
+	struct device *dev = &pdev->dev;
+	struct stm32_omm *omm;
+	struct clk *clk;
+	int ret;
+	u8 child_access_granted = 0;
+	u8 i, j;
+	bool child_access[OMM_CHILD_NB];
+
+	omm = devm_kzalloc(dev, sizeof(*omm), GFP_KERNEL);
+	if (!omm)
+		return -ENOMEM;
+
+	omm->io_base = devm_platform_ioremap_resource_byname(pdev, "regs");
+	if (IS_ERR(omm->io_base))
+		return PTR_ERR(omm->io_base);
+
+	omm->mm_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "memory_map");
+	if (IS_ERR(omm->mm_res))
+		return PTR_ERR(omm->mm_res);
+
+	/* check child's access */
+	for_each_child_of_node_scoped(dev->of_node, child) {
+		if (omm->nb_child >= OMM_CHILD_NB) {
+			dev_err(dev, "Bad DT, found too much children\n");
+			ret = -E2BIG;
+			goto err_clk_release;
+		}
+
+		if (!of_device_is_compatible(child, "st,stm32mp25-ospi")) {
+			ret = -EINVAL;
+			goto err_clk_release;
+		}
+
+		ret = stm32_omm_check_access(dev, child);
+		if (ret < 0 && ret != -EACCES)
+			goto err_clk_release;
+
+		child_access[omm->nb_child] = false;
+		if (!ret) {
+			child_access_granted++;
+			child_access[omm->nb_child] = true;
+		}
+
+		omm->child[omm->nb_child].node = child;
+
+		clk = of_clk_get(child, 0);
+		if (IS_ERR(clk)) {
+			dev_err(dev, "Can't get child clock\n");
+			ret = PTR_ERR(clk);
+			goto err_clk_release;
+		};
+
+		omm->child[omm->nb_child].clk = clk;
+		omm->nb_child++;
+	}
+
+	if (omm->nb_child != OMM_CHILD_NB) {
+		ret = -EINVAL;
+		goto err_clk_release;
+	}
+
+	platform_set_drvdata(pdev, omm);
+
+	pm_runtime_enable(dev);
+
+	/* check if OMM's resource access is granted */
+	ret = stm32_omm_check_access(dev, dev->of_node);
+	if (ret < 0 && ret != -EACCES)
+		goto err_clk_release;
+
+	if (!ret && child_access_granted == OMM_CHILD_NB) {
+		/* Ensure both OSPI instance are disabled before configuring OMM */
+		ret = stm32_omm_disable_child(dev);
+		if (ret)
+			goto err_clk_release;
+
+		ret = stm32_omm_configure(dev);
+		if (ret)
+			goto err_clk_release;
+	} else {
+		dev_dbg(dev, "Octo Memory Manager resource's access not granted\n");
+		/*
+		 * AMCR can't be set, so check if current value is coherent
+		 * with memory-map areas defined in DT
+		 */
+		ret = stm32_omm_set_amcr(dev, false);
+		if (ret)
+			goto err_clk_release;
+	}
+
+	/* for each child, if resource access is granted and status "okay", probe it */
+	for (i = 0; i < omm->nb_child; i++) {
+		if (!child_access[i] || !of_device_is_available(omm->child[i].node))
+			continue;
+
+		vdev = of_platform_device_create(omm->child[i].node, NULL, NULL);
+		if (!vdev) {
+			dev_err(dev, "Failed to create Octo Memory Manager child\n");
+			for (j = i; j > 0; --j) {
+				if (omm->child[j].dev)
+					of_platform_device_destroy(omm->child[j].dev, NULL);
+			}
+
+			ret = -EINVAL;
+			goto err_clk_release;
+		}
+		omm->child[i].dev = &vdev->dev;
+	}
+
+err_clk_release:
+	for (i = 0; i < omm->nb_child; i++)
+		clk_put(omm->child[i].clk);
+
+	return ret;
+}
+
+static void stm32_omm_remove(struct platform_device *pdev)
+{
+	struct stm32_omm *omm = platform_get_drvdata(pdev);
+	int i;
+
+	for (i = 0; i < omm->nb_child; i++)
+		if (omm->child[i].dev)
+			of_platform_device_destroy(omm->child[i].dev, NULL);
+
+	if (omm->cr & CR_MUXEN)
+		stm32_omm_enable_child_clock(&pdev->dev, false);
+
+	pm_runtime_disable(&pdev->dev);
+}
+
+static const struct of_device_id stm32_omm_of_match[] = {
+	{ .compatible = "st,stm32mp25-omm", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, stm32_omm_of_match);
+
+static int __maybe_unused stm32_omm_runtime_suspend(struct device *dev)
+{
+	struct stm32_omm *omm = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(omm->clk);
+
+	return 0;
+}
+
+static int __maybe_unused stm32_omm_runtime_resume(struct device *dev)
+{
+	struct stm32_omm *omm = dev_get_drvdata(dev);
+
+	return clk_prepare_enable(omm->clk);
+}
+
+static int __maybe_unused stm32_omm_suspend(struct device *dev)
+{
+	struct stm32_omm *omm = dev_get_drvdata(dev);
+
+	if (omm->restore_omm && omm->cr & CR_MUXEN)
+		stm32_omm_enable_child_clock(dev, false);
+
+	return pinctrl_pm_select_sleep_state(dev);
+}
+
+static int __maybe_unused stm32_omm_resume(struct device *dev)
+{
+	struct stm32_omm *omm = dev_get_drvdata(dev);
+	int ret;
+
+	pinctrl_pm_select_default_state(dev);
+
+	if (!omm->restore_omm)
+		return 0;
+
+	/* Ensure both OSPI instance are disabled before configuring OMM */
+	ret = stm32_omm_disable_child(dev);
+	if (ret)
+		return ret;
+
+	ret = pm_runtime_resume_and_get(dev);
+	if (ret < 0)
+		return ret;
+
+	writel_relaxed(omm->cr, omm->io_base + OMM_CR);
+	ret = stm32_omm_set_amcr(dev, true);
+	pm_runtime_put_sync_suspend(dev);
+	if (ret)
+		return ret;
+
+	if (omm->cr & CR_MUXEN)
+		ret = stm32_omm_enable_child_clock(dev, true);
+
+	return ret;
+}
+
+static const struct dev_pm_ops stm32_omm_pm_ops = {
+	SET_RUNTIME_PM_OPS(stm32_omm_runtime_suspend,
+			   stm32_omm_runtime_resume, NULL)
+	SET_SYSTEM_SLEEP_PM_OPS(stm32_omm_suspend, stm32_omm_resume)
+};
+
+static struct platform_driver stm32_omm_driver = {
+	.probe	= stm32_omm_probe,
+	.remove = stm32_omm_remove,
+	.driver	= {
+		.name = "stm32-omm",
+		.of_match_table = stm32_omm_of_match,
+		.pm = &stm32_omm_pm_ops,
+	},
+};
+module_platform_driver(stm32_omm_driver);
+
+MODULE_DESCRIPTION("STMicroelectronics Octo Memory Manager driver");
+MODULE_LICENSE("GPL");
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v5 5/8] arm64: dts: st: Add OMM node on stm32mp251
  2025-02-19  8:00 [PATCH v5 0/8] Add STM32MP25 SPI NOR support patrice.chotard
                   ` (3 preceding siblings ...)
  2025-02-19  8:00 ` [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver patrice.chotard
@ 2025-02-19  8:00 ` patrice.chotard
  2025-02-19  8:00 ` [PATCH v5 6/8] arm64: dts: st: Add ospi port1 pinctrl entries in stm32mp25-pinctrl.dtsi patrice.chotard
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: patrice.chotard @ 2025-02-19  8:00 UTC (permalink / raw)
  To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello, patrice.chotard

From: Patrice Chotard <patrice.chotard@foss.st.com>

Add Octo Memory Manager (OMM) entry on stm32mp251 and its two
OSPI instance.

Signed-off-by: Patrice Chotard <patrice.chotard@foss.st.com>
---
 arch/arm64/boot/dts/st/stm32mp251.dtsi | 48 ++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/arch/arm64/boot/dts/st/stm32mp251.dtsi b/arch/arm64/boot/dts/st/stm32mp251.dtsi
index f3c6cdfd7008..2565236e369f 100644
--- a/arch/arm64/boot/dts/st/stm32mp251.dtsi
+++ b/arch/arm64/boot/dts/st/stm32mp251.dtsi
@@ -768,6 +768,54 @@ rng: rng@42020000 {
 				status = "disabled";
 			};
 
+			ommanager: ommanager@40500000 {
+				compatible = "st,stm32mp25-omm";
+				reg = <0x40500000 0x400>, <0x60000000 0x10000000>;
+				reg-names = "regs", "memory_map";
+				ranges = <0 0 0x40430000 0x400>,
+					 <1 0 0x40440000 0x400>;
+				clocks = <&rcc CK_BUS_OSPIIOM>;
+				resets = <&rcc OSPIIOM_R>;
+				access-controllers = <&rifsc 111>;
+				power-domains = <&CLUSTER_PD>;
+				#address-cells = <2>;
+				#size-cells = <1>;
+				st,syscfg-amcr = <&syscfg 0x2c00 0x7>;
+				status = "disabled";
+
+				ospi1: spi@40430000 {
+					compatible = "st,stm32mp25-ospi";
+					reg = <0 0 0x400>;
+					interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>;
+					dmas = <&hpdma 2 0x62 0x00003121>,
+					       <&hpdma 2 0x42 0x00003112>;
+					dma-names = "tx", "rx";
+					clocks = <&scmi_clk CK_SCMI_OSPI1>;
+					resets = <&scmi_reset RST_SCMI_OSPI1>,
+						 <&scmi_reset RST_SCMI_OSPI1DLL>;
+					access-controllers = <&rifsc 74>;
+					power-domains = <&CLUSTER_PD>;
+					st,syscfg-dlyb = <&syscfg 0x1000>;
+					status = "disabled";
+				};
+
+				ospi2: spi@40440000 {
+					compatible = "st,stm32mp25-ospi";
+					reg = <1 0 0x400>;
+					interrupts = <GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>;
+					dmas = <&hpdma 3 0x62 0x00003121>,
+					       <&hpdma 3 0x42 0x00003112>;
+					dma-names = "tx", "rx";
+					clocks = <&scmi_clk CK_SCMI_OSPI2>;
+					resets = <&scmi_reset RST_SCMI_OSPI2>,
+						 <&scmi_reset RST_SCMI_OSPI2DLL>;
+					access-controllers = <&rifsc 75>;
+					power-domains = <&CLUSTER_PD>;
+					st,syscfg-dlyb = <&syscfg 0x1400>;
+					status = "disabled";
+				};
+			};
+
 			spi8: spi@46020000 {
 				#address-cells = <1>;
 				#size-cells = <0>;
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v5 6/8] arm64: dts: st: Add ospi port1 pinctrl entries in stm32mp25-pinctrl.dtsi
  2025-02-19  8:00 [PATCH v5 0/8] Add STM32MP25 SPI NOR support patrice.chotard
                   ` (4 preceding siblings ...)
  2025-02-19  8:00 ` [PATCH v5 5/8] arm64: dts: st: Add OMM node on stm32mp251 patrice.chotard
@ 2025-02-19  8:00 ` patrice.chotard
  2025-02-19  8:00 ` [PATCH v5 7/8] arm64: dts: st: Add SPI NOR flash support on stm32mp257f-ev1 board patrice.chotard
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: patrice.chotard @ 2025-02-19  8:00 UTC (permalink / raw)
  To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello, patrice.chotard

From: Patrice Chotard <patrice.chotard@foss.st.com>

Add pinctrl entry related to OSPI's port1 in stm32mp25-pinctrl.dtsi

Signed-off-by: Patrice Chotard <patrice.chotard@foss.st.com>
---
 arch/arm64/boot/dts/st/stm32mp25-pinctrl.dtsi | 51 +++++++++++++++++++
 1 file changed, 51 insertions(+)

diff --git a/arch/arm64/boot/dts/st/stm32mp25-pinctrl.dtsi b/arch/arm64/boot/dts/st/stm32mp25-pinctrl.dtsi
index 8fdd5f020425..cf5be316de26 100644
--- a/arch/arm64/boot/dts/st/stm32mp25-pinctrl.dtsi
+++ b/arch/arm64/boot/dts/st/stm32mp25-pinctrl.dtsi
@@ -101,6 +101,57 @@ pins2 {
 		};
 	};
 
+	ospi_port1_clk_pins_a: ospi-port1-clk-0 {
+		pins {
+			pinmux = <STM32_PINMUX('D', 0, AF10)>; /* OSPI1_CLK */
+			bias-disable;
+			drive-push-pull;
+			slew-rate = <2>;
+		};
+	};
+
+	ospi_port1_clk_sleep_pins_a: ospi-port1-clk-sleep-0 {
+		pins {
+			pinmux = <STM32_PINMUX('D', 0, ANALOG)>; /* OSPI1_CLK */
+		};
+	};
+
+	ospi_port1_cs0_pins_a: ospi-port1-cs0-0 {
+		pins {
+			pinmux = <STM32_PINMUX('D', 3, AF10)>; /* OSPI_NCS0 */
+			bias-pull-up;
+			drive-push-pull;
+			slew-rate = <0>;
+		};
+	};
+
+	ospi_port1_cs0_sleep_pins_a: ospi-port1-cs0-sleep-0 {
+		pins {
+			pinmux = <STM32_PINMUX('D', 3, ANALOG)>; /* OSPI_NCS0 */
+		};
+	};
+
+	ospi_port1_io03_pins_a: ospi-port1-io03-0 {
+		pins {
+			pinmux = <STM32_PINMUX('D', 4, AF10)>, /* OSPI_IO0 */
+				 <STM32_PINMUX('D', 5, AF10)>, /* OSPI_IO1 */
+				 <STM32_PINMUX('D', 6, AF10)>, /* OSPI_IO2 */
+				 <STM32_PINMUX('D', 7, AF10)>; /* OSPI_IO3 */
+			bias-disable;
+			drive-push-pull;
+			slew-rate = <0>;
+		};
+	};
+
+	ospi_port1_io03_sleep_pins_a: ospi-port1-io03-sleep-0 {
+		pins {
+			pinmux = <STM32_PINMUX('D', 4, ANALOG)>, /* OSPI_IO0 */
+				 <STM32_PINMUX('D', 5, ANALOG)>, /* OSPI_IO1 */
+				 <STM32_PINMUX('D', 6, ANALOG)>, /* OSPI_IO2 */
+				 <STM32_PINMUX('D', 7, ANALOG)>; /* OSPI_IO3 */
+		};
+	};
+
 	sdmmc1_b4_od_pins_a: sdmmc1-b4-od-0 {
 		pins1 {
 			pinmux = <STM32_PINMUX('E', 4, AF10)>, /* SDMMC1_D0 */
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v5 7/8] arm64: dts: st: Add SPI NOR flash support on stm32mp257f-ev1 board
  2025-02-19  8:00 [PATCH v5 0/8] Add STM32MP25 SPI NOR support patrice.chotard
                   ` (5 preceding siblings ...)
  2025-02-19  8:00 ` [PATCH v5 6/8] arm64: dts: st: Add ospi port1 pinctrl entries in stm32mp25-pinctrl.dtsi patrice.chotard
@ 2025-02-19  8:00 ` patrice.chotard
  2025-02-19  8:00 ` [PATCH v5 8/8] arm64: defconfig: Enable STM32 Octo Memory Manager and OcstoSPI driver patrice.chotard
  2025-03-04 13:39 ` (subset) [PATCH v5 0/8] Add STM32MP25 SPI NOR support Mark Brown
  8 siblings, 0 replies; 19+ messages in thread
From: patrice.chotard @ 2025-02-19  8:00 UTC (permalink / raw)
  To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello, patrice.chotard

From: Patrice Chotard <patrice.chotard@foss.st.com>

Add SPI NOR flash nor support on stm32mp257f-ev1 board.

Signed-off-by: Patrice Chotard <patrice.chotard@foss.st.com>
---
 arch/arm64/boot/dts/st/stm32mp257f-ev1.dts | 32 ++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/arch/arm64/boot/dts/st/stm32mp257f-ev1.dts b/arch/arm64/boot/dts/st/stm32mp257f-ev1.dts
index 1b88485a62a1..76970c15e043 100644
--- a/arch/arm64/boot/dts/st/stm32mp257f-ev1.dts
+++ b/arch/arm64/boot/dts/st/stm32mp257f-ev1.dts
@@ -80,6 +80,11 @@ fw@80000000 {
 			reg = <0x0 0x80000000 0x0 0x4000000>;
 			no-map;
 		};
+
+		mm_ospi1: mm-ospi@60000000 {
+			reg = <0x0 0x60000000 0x0 0x10000000>;
+			no-map;
+		};
 	};
 };
 
@@ -190,6 +195,33 @@ &i2c8 {
 	status = "disabled";
 };
 
+&ommanager {
+	memory-region = <&mm_ospi1>;
+	pinctrl-0 = <&ospi_port1_clk_pins_a
+		     &ospi_port1_io03_pins_a
+		     &ospi_port1_cs0_pins_a>;
+	pinctrl-1 = <&ospi_port1_clk_sleep_pins_a
+		     &ospi_port1_io03_sleep_pins_a
+		     &ospi_port1_cs0_sleep_pins_a>;
+	pinctrl-names = "default", "sleep";
+	status = "okay";
+
+	spi@40430000 {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		memory-region = <&mm_ospi1>;
+		status = "okay";
+
+		flash0: flash@0 {
+			compatible = "jedec,spi-nor";
+			reg = <0>;
+			spi-rx-bus-width = <4>;
+			spi-tx-bus-width = <4>;
+			spi-max-frequency = <50000000>;
+		};
+	};
+};
+
 &rtc {
 	status = "okay";
 };
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH v5 8/8] arm64: defconfig: Enable STM32 Octo Memory Manager and OcstoSPI driver
  2025-02-19  8:00 [PATCH v5 0/8] Add STM32MP25 SPI NOR support patrice.chotard
                   ` (6 preceding siblings ...)
  2025-02-19  8:00 ` [PATCH v5 7/8] arm64: dts: st: Add SPI NOR flash support on stm32mp257f-ev1 board patrice.chotard
@ 2025-02-19  8:00 ` patrice.chotard
  2025-03-04 13:39 ` (subset) [PATCH v5 0/8] Add STM32MP25 SPI NOR support Mark Brown
  8 siblings, 0 replies; 19+ messages in thread
From: patrice.chotard @ 2025-02-19  8:00 UTC (permalink / raw)
  To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello, patrice.chotard

From: Patrice Chotard <patrice.chotard@foss.st.com>

Enable STM32 OctoSPI driver.
Enable STM32 Octo Memory Manager (OMM) driver which is needed
for OSPI usage on STM32MP257F-EV1 board.

Signed-off-by: Patrice Chotard <patrice.chotard@foss.st.com>
---
 arch/arm64/configs/defconfig | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 246a13412bf0..b089cf4b90a1 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -577,6 +577,7 @@ CONFIG_SPI_QUP=y
 CONFIG_SPI_QCOM_GENI=m
 CONFIG_SPI_S3C64XX=y
 CONFIG_SPI_SH_MSIOF=m
+CONFIG_SPI_STM32_OSPI=m
 CONFIG_SPI_SUN6I=y
 CONFIG_SPI_TEGRA210_QUAD=m
 CONFIG_SPI_TEGRA114=m
@@ -1506,6 +1507,7 @@ CONFIG_EXTCON_USB_GPIO=y
 CONFIG_EXTCON_USBC_CROS_EC=y
 CONFIG_FSL_IFC=y
 CONFIG_RENESAS_RPCIF=m
+CONFIG_STM32_OMM=m
 CONFIG_IIO=y
 CONFIG_EXYNOS_ADC=y
 CONFIG_IMX8QXP_ADC=m
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 19+ messages in thread

* Re: (subset) [PATCH v5 0/8] Add STM32MP25 SPI NOR support
  2025-02-19  8:00 [PATCH v5 0/8] Add STM32MP25 SPI NOR support patrice.chotard
                   ` (7 preceding siblings ...)
  2025-02-19  8:00 ` [PATCH v5 8/8] arm64: defconfig: Enable STM32 Octo Memory Manager and OcstoSPI driver patrice.chotard
@ 2025-03-04 13:39 ` Mark Brown
  8 siblings, 0 replies; 19+ messages in thread
From: Mark Brown @ 2025-03-04 13:39 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Alexandre Torgue,
	Philipp Zabel, Maxime Coquelin, Greg Kroah-Hartman, Arnd Bergmann,
	Catalin Marinas, Will Deacon, patrice.chotard
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello

On Wed, 19 Feb 2025 09:00:51 +0100, patrice.chotard@foss.st.com wrote:
> This series adds SPI NOR support for STM32MP25 SoCs from STMicroelectronics.
> 
> On STM32MP25 SoCs family, an Octo Memory Manager block manages the muxing,
> the memory area split, the chip select override and the time constraint
> between its 2 Octo SPI children.
> 
> Due to these depedencies, this series adds support for:
>   - Octo Memory Manager driver.
>   - Octo SPI driver.
>   - yaml schema for Octo Memory Manager and Octo SPI drivers.
> 
> [...]

Applied to

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-next

Thanks!

[1/8] dt-bindings: spi: Add STM32 OSPI controller
      commit: bed97e35786a7d0141d1ecaaace03c46b5435d75
[2/8] spi: stm32: Add OSPI driver
      commit: 79b8a705e26c08f8f09dd55f1dd56f2375973d2d

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark



^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver
  2025-02-19  8:00 ` [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver patrice.chotard
@ 2025-03-10 13:52   ` Patrice CHOTARD
  2025-03-10 14:10     ` Krzysztof Kozlowski
  2025-03-13  7:30     ` Krzysztof Kozlowski
  2025-03-11 16:04   ` Krzysztof Kozlowski
  1 sibling, 2 replies; 19+ messages in thread
From: Patrice CHOTARD @ 2025-03-10 13:52 UTC (permalink / raw)
  To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello



On 2/19/25 09:00, patrice.chotard@foss.st.com wrote:
> From: Patrice Chotard <patrice.chotard@foss.st.com>
> 
> Octo Memory Manager driver (OMM) manages:
>   - the muxing between 2 OSPI busses and 2 output ports.
>     There are 4 possible muxing configurations:
>       - direct mode (no multiplexing): OSPI1 output is on port 1 and OSPI2
>         output is on port 2
>       - OSPI1 and OSPI2 are multiplexed over the same output port 1
>       - swapped mode (no multiplexing), OSPI1 output is on port 2,
>         OSPI2 output is on port 1
>       - OSPI1 and OSPI2 are multiplexed over the same output port 2
>   - the split of the memory area shared between the 2 OSPI instances.
>   - chip select selection override.
>   - the time between 2 transactions in multiplexed mode.
>   - check firewall access.
> 
> Signed-off-by: Patrice Chotard <patrice.chotard@foss.st.com>
> Signed-off-by: Christophe Kerello <christophe.kerello@foss.st.com>
> ---
>  drivers/memory/Kconfig     |  17 ++
>  drivers/memory/Makefile    |   1 +
>  drivers/memory/stm32_omm.c | 522 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 540 insertions(+)
>  create mode 100644 drivers/memory/stm32_omm.c
> 
> diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
> index c82d8d8a16ea..3a0703fbfee7 100644
> --- a/drivers/memory/Kconfig
> +++ b/drivers/memory/Kconfig
> @@ -225,6 +225,23 @@ config STM32_FMC2_EBI
>  	  devices (like SRAM, ethernet adapters, FPGAs, LCD displays, ...) on
>  	  SOCs containing the FMC2 External Bus Interface.
>  
> +config STM32_OMM
> +	tristate "STM32 Octo Memory Manager"
> +	depends on SPI_STM32_OSPI || COMPILE_TEST
> +	help
> +	  This driver manages the muxing between the 2 OSPI busses and
> +	  the 2 output ports. There are 4 possible muxing configurations:
> +	  - direct mode (no multiplexing): OSPI1 output is on port 1 and OSPI2
> +	       output is on port 2
> +	  - OSPI1 and OSPI2 are multiplexed over the same output port 1
> +	  - swapped mode (no multiplexing), OSPI1 output is on port 2,
> +	       OSPI2 output is on port 1
> +	  - OSPI1 and OSPI2 are multiplexed over the same output port 2
> +	  It also manages :
> +	    - the split of the memory area shared between the 2 OSPI instances.
> +	    - chip select selection override.
> +	    - the time between 2 transactions in multiplexed mode.
> +
>  source "drivers/memory/samsung/Kconfig"
>  source "drivers/memory/tegra/Kconfig"
>  
> diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
> index d2e6ca9abbe0..c1959661bf63 100644
> --- a/drivers/memory/Makefile
> +++ b/drivers/memory/Makefile
> @@ -24,6 +24,7 @@ obj-$(CONFIG_DA8XX_DDRCTL)	+= da8xx-ddrctl.o
>  obj-$(CONFIG_PL353_SMC)		+= pl353-smc.o
>  obj-$(CONFIG_RENESAS_RPCIF)	+= renesas-rpc-if.o
>  obj-$(CONFIG_STM32_FMC2_EBI)	+= stm32-fmc2-ebi.o
> +obj-$(CONFIG_STM32_OMM)		+= stm32_omm.o
>  
>  obj-$(CONFIG_SAMSUNG_MC)	+= samsung/
>  obj-$(CONFIG_TEGRA_MC)		+= tegra/
> diff --git a/drivers/memory/stm32_omm.c b/drivers/memory/stm32_omm.c
> new file mode 100644
> index 000000000000..8f7f475769e7
> --- /dev/null
> +++ b/drivers/memory/stm32_omm.c
> @@ -0,0 +1,522 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) STMicroelectronics 2025 - All Rights Reserved
> + * Author(s): Patrice Chotard <patrice.chotard@foss.st.com> for STMicroelectronics.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bus/stm32_firewall_device.h>
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <linux/pinctrl/consumer.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +
> +#define OMM_CR			0
> +#define CR_MUXEN		BIT(0)
> +#define CR_MUXENMODE_MASK	GENMASK(1, 0)
> +#define CR_CSSEL_OVR_EN		BIT(4)
> +#define CR_CSSEL_OVR_MASK	GENMASK(6, 5)
> +#define CR_REQ2ACK_MASK		GENMASK(23, 16)
> +
> +#define OMM_CHILD_NB		2
> +
> +struct ospi_child {
> +	struct device *dev;
> +	struct device_node *node;
> +	struct clk *clk;
> +};
> +
> +struct stm32_omm {
> +	struct ospi_child child[OMM_CHILD_NB];
> +	struct resource *mm_res;
> +	struct clk *clk;
> +	void __iomem *io_base;
> +	u32 cr;
> +	u8 nb_child;
> +	bool restore_omm;
> +};
> +
> +static int stm32_omm_set_amcr(struct device *dev, bool set)
> +{
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +	struct regmap *syscfg_regmap;
> +	struct device_node *node;
> +	struct resource res, res1;
> +	resource_size_t mm_ospi2_size = 0;
> +	static const char * const mm_name[] = { "ospi1", "ospi2" };
> +	u32 amcr_base, amcr_mask;
> +	int ret, idx;
> +	unsigned int i, amcr, read_amcr;
> +
> +	for (i = 0; i < omm->nb_child; i++) {
> +		idx = of_property_match_string(dev->of_node,
> +					       "memory-region-names",
> +					       mm_name[i]);
> +		if (idx < 0)
> +			continue;
> +
> +		/* res1 only used on second loop iteration */
> +		res1.start = res.start;
> +		res1.end = res.end;
> +
> +		node = of_parse_phandle(dev->of_node, "memory-region", idx);
> +		if (!node)
> +			continue;
> +
> +		ret = of_address_to_resource(node, 0, &res);
> +		if (ret) {
> +			dev_err(dev, "unable to resolve memory region\n");
> +			return ret;
> +		}
> +
> +		/* check that memory region fits inside OMM memory map area */
> +		if (!resource_contains(omm->mm_res, &res)) {
> +			dev_err(dev, "%s doesn't fit inside OMM memory map area\n",
> +				mm_name[i]);
> +			dev_err(dev, "%pR doesn't fit inside %pR\n", &res, omm->mm_res);
> +
> +			return -EFAULT;
> +		}
> +
> +		if (i == 1) {
> +			mm_ospi2_size = resource_size(&res);
> +
> +			/* check that OMM memory region 1 doesn't overlap memory region 2 */
> +			if (resource_overlaps(&res, &res1)) {
> +				dev_err(dev, "OMM memory-region %s overlaps memory region %s\n",
> +					mm_name[0], mm_name[1]);
> +				dev_err(dev, "%pR overlaps %pR\n", &res1, &res);
> +
> +				return -EFAULT;
> +			}
> +		}
> +	}
> +
> +	syscfg_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "st,syscfg-amcr");
> +	if (IS_ERR(syscfg_regmap)) {
> +		dev_err(dev, "Failed to get st,syscfg-amcr property\n");
> +		return PTR_ERR(syscfg_regmap);
> +	}
> +
> +	ret = of_property_read_u32_index(dev->of_node, "st,syscfg-amcr", 1,
> +					 &amcr_base);
> +	if (ret)
> +		return ret;
> +
> +	ret = of_property_read_u32_index(dev->of_node, "st,syscfg-amcr", 2,
> +					 &amcr_mask);
> +	if (ret)
> +		return ret;
> +
> +	amcr = mm_ospi2_size / SZ_64M;
> +
> +	if (set)
> +		regmap_update_bits(syscfg_regmap, amcr_base, amcr_mask, amcr);
> +
> +	/* read AMCR and check coherency with memory-map areas defined in DT */
> +	regmap_read(syscfg_regmap, amcr_base, &read_amcr);
> +	read_amcr = read_amcr >> (ffs(amcr_mask) - 1);
> +
> +	if (amcr != read_amcr) {
> +		dev_err(dev, "AMCR value not coherent with DT memory-map areas\n");
> +		ret = -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int stm32_omm_enable_child_clock(struct device *dev, bool enable)
> +{
> +	/* As there is only 2 children, remember first child in case of error */
> +	struct clk *first_child_clk = NULL;
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +	u8 i;
> +	int ret;
> +
> +	for (i = 0; i < omm->nb_child; i++) {
> +		if (enable) {
> +			ret = clk_prepare_enable(omm->child[i].clk);
> +			if (ret) {
> +				if (first_child_clk)
> +					clk_disable_unprepare(first_child_clk);
> +
> +				dev_err(dev, "Can not enable clock\n");
> +				return ret;
> +			}
> +		} else {
> +			clk_disable_unprepare(omm->child[i].clk);
> +		}
> +
> +		first_child_clk = omm->child[i].clk;
> +	}
> +
> +	return 0;
> +}
> +
> +static int stm32_omm_configure(struct device *dev)
> +{
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +	struct reset_control *rstc;
> +	unsigned long clk_rate, clk_rate_max = 0;
> +	int ret;
> +	u8 i;
> +	u32 mux = 0;
> +	u32 cssel_ovr = 0;
> +	u32 req2ack = 0;
> +
> +	omm->clk = devm_clk_get(dev, NULL);
> +	if (IS_ERR(omm->clk)) {
> +		dev_err(dev, "Failed to get OMM clock (%ld)\n",
> +			PTR_ERR(omm->clk));
> +
> +		return PTR_ERR(omm->clk);
> +	}
> +
> +	ret = pm_runtime_resume_and_get(dev);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* parse children's clock */
> +	for (i = 0; i < omm->nb_child; i++) {
> +		clk_rate = clk_get_rate(omm->child[i].clk);
> +		if (!clk_rate) {
> +			dev_err(dev, "Invalid clock rate\n");
> +			pm_runtime_disable(dev);
> +			goto err_clk_disable;
> +		}
> +
> +		if (clk_rate > clk_rate_max)
> +			clk_rate_max = clk_rate;
> +	}
> +
> +	rstc = devm_reset_control_get_optional_exclusive(dev, NULL);
> +	if (IS_ERR(rstc)) {
> +		ret = dev_err_probe(dev, PTR_ERR(rstc), "reset get failed\n");
> +		pm_runtime_disable(dev);
> +		goto err_clk_disable;
> +	}
> +
> +	reset_control_assert(rstc);
> +	udelay(2);
> +	reset_control_deassert(rstc);
> +
> +	omm->cr = readl_relaxed(omm->io_base + OMM_CR);
> +	/* optional */
> +	ret = of_property_read_u32(dev->of_node, "st,omm-mux", &mux);
> +	if (!ret) {
> +		if (mux & CR_MUXEN) {
> +			ret = of_property_read_u32(dev->of_node, "st,omm-req2ack-ns",
> +						   &req2ack);
> +			if (!ret && !req2ack) {
> +				req2ack = DIV_ROUND_UP(req2ack, NSEC_PER_SEC / clk_rate_max) - 1;
> +
> +				if (req2ack > 256)
> +					req2ack = 256;
> +			}
> +
> +			req2ack = FIELD_PREP(CR_REQ2ACK_MASK, req2ack);
> +
> +			omm->cr &= ~CR_REQ2ACK_MASK;
> +			omm->cr |= FIELD_PREP(CR_REQ2ACK_MASK, req2ack);
> +
> +			/*
> +			 * If the mux is enabled, the 2 OSPI clocks have to be
> +			 * always enabled
> +			 */
> +			ret = stm32_omm_enable_child_clock(dev, true);
> +			if (ret) {
> +				pm_runtime_disable(dev);
> +				goto err_clk_disable;
> +			}
> +		}
> +
> +		omm->cr &= ~CR_MUXENMODE_MASK;
> +		omm->cr |= FIELD_PREP(CR_MUXENMODE_MASK, mux);
> +	}
> +
> +	/* optional */
> +	ret = of_property_read_u32(dev->of_node, "st,omm-cssel-ovr", &cssel_ovr);
> +	if (!ret) {
> +		omm->cr &= ~CR_CSSEL_OVR_MASK;
> +		omm->cr |= FIELD_PREP(CR_CSSEL_OVR_MASK, cssel_ovr);
> +		omm->cr |= CR_CSSEL_OVR_EN;
> +	}
> +
> +	omm->restore_omm = true;
> +	writel_relaxed(omm->cr, omm->io_base + OMM_CR);
> +
> +	ret = stm32_omm_set_amcr(dev, true);
> +
> +err_clk_disable:
> +	pm_runtime_put_sync_suspend(dev);
> +
> +	return ret;
> +}
> +
> +static int stm32_omm_check_access(struct device *dev, struct device_node *np)
> +{
> +	struct stm32_firewall firewall;
> +	int ret;
> +
> +	ret = stm32_firewall_get_firewall(np, &firewall, 1);
> +	if (ret)
> +		return ret;
> +
> +	return stm32_firewall_grant_access(&firewall);
> +}
> +
> +static int stm32_omm_disable_child(struct device *dev)
> +{
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +	struct reset_control *reset;
> +	int ret;
> +	u8 i;
> +
> +	for (i = 0; i < omm->nb_child; i++) {
> +		ret = clk_prepare_enable(omm->child[i].clk);
> +		if (ret) {
> +			dev_err(dev, "Can not enable clock\n");
> +			return ret;
> +		}
> +
> +		reset = of_reset_control_get_exclusive(omm->child[i].node, 0);
> +		if (IS_ERR(reset)) {
> +			dev_err(dev, "Can't get child reset\n");
> +			return PTR_ERR(reset);
> +		};
> +
> +		/* reset OSPI to ensure CR_EN bit is set to 0 */
> +		reset_control_assert(reset);
> +		udelay(2);
> +		reset_control_deassert(reset);
> +
> +		reset_control_put(reset);
> +		clk_disable_unprepare(omm->child[i].clk);
> +	}
> +
> +	return 0;
> +}
> +
> +static int stm32_omm_probe(struct platform_device *pdev)
> +{
> +	struct platform_device *vdev;
> +	struct device *dev = &pdev->dev;
> +	struct stm32_omm *omm;
> +	struct clk *clk;
> +	int ret;
> +	u8 child_access_granted = 0;
> +	u8 i, j;
> +	bool child_access[OMM_CHILD_NB];
> +
> +	omm = devm_kzalloc(dev, sizeof(*omm), GFP_KERNEL);
> +	if (!omm)
> +		return -ENOMEM;
> +
> +	omm->io_base = devm_platform_ioremap_resource_byname(pdev, "regs");
> +	if (IS_ERR(omm->io_base))
> +		return PTR_ERR(omm->io_base);
> +
> +	omm->mm_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "memory_map");
> +	if (IS_ERR(omm->mm_res))
> +		return PTR_ERR(omm->mm_res);
> +
> +	/* check child's access */
> +	for_each_child_of_node_scoped(dev->of_node, child) {
> +		if (omm->nb_child >= OMM_CHILD_NB) {
> +			dev_err(dev, "Bad DT, found too much children\n");
> +			ret = -E2BIG;
> +			goto err_clk_release;
> +		}
> +
> +		if (!of_device_is_compatible(child, "st,stm32mp25-ospi")) {
> +			ret = -EINVAL;
> +			goto err_clk_release;
> +		}
> +
> +		ret = stm32_omm_check_access(dev, child);
> +		if (ret < 0 && ret != -EACCES)
> +			goto err_clk_release;
> +
> +		child_access[omm->nb_child] = false;
> +		if (!ret) {
> +			child_access_granted++;
> +			child_access[omm->nb_child] = true;
> +		}
> +
> +		omm->child[omm->nb_child].node = child;
> +
> +		clk = of_clk_get(child, 0);
> +		if (IS_ERR(clk)) {
> +			dev_err(dev, "Can't get child clock\n");
> +			ret = PTR_ERR(clk);
> +			goto err_clk_release;
> +		};
> +
> +		omm->child[omm->nb_child].clk = clk;
> +		omm->nb_child++;
> +	}
> +
> +	if (omm->nb_child != OMM_CHILD_NB) {
> +		ret = -EINVAL;
> +		goto err_clk_release;
> +	}
> +
> +	platform_set_drvdata(pdev, omm);
> +
> +	pm_runtime_enable(dev);
> +
> +	/* check if OMM's resource access is granted */
> +	ret = stm32_omm_check_access(dev, dev->of_node);
> +	if (ret < 0 && ret != -EACCES)
> +		goto err_clk_release;
> +
> +	if (!ret && child_access_granted == OMM_CHILD_NB) {
> +		/* Ensure both OSPI instance are disabled before configuring OMM */
> +		ret = stm32_omm_disable_child(dev);
> +		if (ret)
> +			goto err_clk_release;
> +
> +		ret = stm32_omm_configure(dev);
> +		if (ret)
> +			goto err_clk_release;
> +	} else {
> +		dev_dbg(dev, "Octo Memory Manager resource's access not granted\n");
> +		/*
> +		 * AMCR can't be set, so check if current value is coherent
> +		 * with memory-map areas defined in DT
> +		 */
> +		ret = stm32_omm_set_amcr(dev, false);
> +		if (ret)
> +			goto err_clk_release;
> +	}
> +
> +	/* for each child, if resource access is granted and status "okay", probe it */
> +	for (i = 0; i < omm->nb_child; i++) {
> +		if (!child_access[i] || !of_device_is_available(omm->child[i].node))
> +			continue;
> +
> +		vdev = of_platform_device_create(omm->child[i].node, NULL, NULL);
> +		if (!vdev) {
> +			dev_err(dev, "Failed to create Octo Memory Manager child\n");
> +			for (j = i; j > 0; --j) {
> +				if (omm->child[j].dev)
> +					of_platform_device_destroy(omm->child[j].dev, NULL);
> +			}
> +
> +			ret = -EINVAL;
> +			goto err_clk_release;
> +		}
> +		omm->child[i].dev = &vdev->dev;
> +	}
> +
> +err_clk_release:
> +	for (i = 0; i < omm->nb_child; i++)
> +		clk_put(omm->child[i].clk);
> +
> +	return ret;
> +}
> +
> +static void stm32_omm_remove(struct platform_device *pdev)
> +{
> +	struct stm32_omm *omm = platform_get_drvdata(pdev);
> +	int i;
> +
> +	for (i = 0; i < omm->nb_child; i++)
> +		if (omm->child[i].dev)
> +			of_platform_device_destroy(omm->child[i].dev, NULL);
> +
> +	if (omm->cr & CR_MUXEN)
> +		stm32_omm_enable_child_clock(&pdev->dev, false);
> +
> +	pm_runtime_disable(&pdev->dev);
> +}
> +
> +static const struct of_device_id stm32_omm_of_match[] = {
> +	{ .compatible = "st,stm32mp25-omm", },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, stm32_omm_of_match);
> +
> +static int __maybe_unused stm32_omm_runtime_suspend(struct device *dev)
> +{
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +
> +	clk_disable_unprepare(omm->clk);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused stm32_omm_runtime_resume(struct device *dev)
> +{
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +
> +	return clk_prepare_enable(omm->clk);
> +}
> +
> +static int __maybe_unused stm32_omm_suspend(struct device *dev)
> +{
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +
> +	if (omm->restore_omm && omm->cr & CR_MUXEN)
> +		stm32_omm_enable_child_clock(dev, false);
> +
> +	return pinctrl_pm_select_sleep_state(dev);
> +}
> +
> +static int __maybe_unused stm32_omm_resume(struct device *dev)
> +{
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +	int ret;
> +
> +	pinctrl_pm_select_default_state(dev);
> +
> +	if (!omm->restore_omm)
> +		return 0;
> +
> +	/* Ensure both OSPI instance are disabled before configuring OMM */
> +	ret = stm32_omm_disable_child(dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = pm_runtime_resume_and_get(dev);
> +	if (ret < 0)
> +		return ret;
> +
> +	writel_relaxed(omm->cr, omm->io_base + OMM_CR);
> +	ret = stm32_omm_set_amcr(dev, true);
> +	pm_runtime_put_sync_suspend(dev);
> +	if (ret)
> +		return ret;
> +
> +	if (omm->cr & CR_MUXEN)
> +		ret = stm32_omm_enable_child_clock(dev, true);
> +
> +	return ret;
> +}
> +
> +static const struct dev_pm_ops stm32_omm_pm_ops = {
> +	SET_RUNTIME_PM_OPS(stm32_omm_runtime_suspend,
> +			   stm32_omm_runtime_resume, NULL)
> +	SET_SYSTEM_SLEEP_PM_OPS(stm32_omm_suspend, stm32_omm_resume)
> +};
> +
> +static struct platform_driver stm32_omm_driver = {
> +	.probe	= stm32_omm_probe,
> +	.remove = stm32_omm_remove,
> +	.driver	= {
> +		.name = "stm32-omm",
> +		.of_match_table = stm32_omm_of_match,
> +		.pm = &stm32_omm_pm_ops,
> +	},
> +};
> +module_platform_driver(stm32_omm_driver);
> +
> +MODULE_DESCRIPTION("STMicroelectronics Octo Memory Manager driver");
> +MODULE_LICENSE("GPL");


Hi all,

Anybody alse has additionnal remarks on this driver ?

Thanks
Patrice


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver
  2025-03-10 13:52   ` Patrice CHOTARD
@ 2025-03-10 14:10     ` Krzysztof Kozlowski
  2025-03-13  7:30     ` Krzysztof Kozlowski
  1 sibling, 0 replies; 19+ messages in thread
From: Krzysztof Kozlowski @ 2025-03-10 14:10 UTC (permalink / raw)
  To: Patrice CHOTARD, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello

On 10/03/2025 14:52, Patrice CHOTARD wrote:
> 
> 
> On 2/19/25 09:00, patrice.chotard@foss.st.com wrote:
>> From: Patrice Chotard <patrice.chotard@foss.st.com>
>>
>> Octo Memory Manager driver (OMM) manages:
>>   - the muxing between 2 OSPI busses and 2 output ports.
>>     There are 4 possible muxing configurations:
>>       - direct mode (no multiplexing): OSPI1 output is on port 1 and OSPI2
>>         output is on port 2
>>       - OSPI1 and OSPI2 are multiplexed over the same output port 1
>>       - swapped mode (no multiplexing), OSPI1 output is on port 2,
>>         OSPI2 output is on port 1
>>       - OSPI1 and OSPI2 are multiplexed over the same output port 2
>>   - the split of the memory area shared between the 2 OSPI instances.
>>   - chip select selection override.
>>   - the time between 2 transactions in multiplexed mode.
>>   - check firewall access.
>>



Please kindly trim the replies from unnecessary context. It makes it
much easier to find new content.

...
> 
> 
> Hi all,
> 
> Anybody alse has additionnal remarks on this driver ?

I am waiting too...

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver
  2025-02-19  8:00 ` [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver patrice.chotard
  2025-03-10 13:52   ` Patrice CHOTARD
@ 2025-03-11 16:04   ` Krzysztof Kozlowski
  2025-03-12 14:23     ` Patrice CHOTARD
  1 sibling, 1 reply; 19+ messages in thread
From: Krzysztof Kozlowski @ 2025-03-11 16:04 UTC (permalink / raw)
  To: patrice.chotard, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello

On 19/02/2025 09:00, patrice.chotard@foss.st.com wrote:
> From: Patrice Chotard <patrice.chotard@foss.st.com>
> 
> Octo Memory Manager driver (OMM) manages:
>   - the muxing between 2 OSPI busses and 2 output ports.
>     There are 4 possible muxing configurations:
>       - direct mode (no multiplexing): OSPI1 output is on port 1 and OSPI2
>         output is on port 2
>       - OSPI1 and OSPI2 are multiplexed over the same output port 1
>       - swapped mode (no multiplexing), OSPI1 output is on port 2,
>         OSPI2 output is on port 1
>       - OSPI1 and OSPI2 are multiplexed over the same output port 2
>   - the split of the memory area shared between the 2 OSPI instances.
>   - chip select selection override.
>   - the time between 2 transactions in multiplexed mode.
>   - check firewall access.
> 
> Signed-off-by: Patrice Chotard <patrice.chotard@foss.st.com>
> Signed-off-by: Christophe Kerello <christophe.kerello@foss.st.com>

Incorrect chain. You sent it, so you must be the last person signing it.

I was waiting for any ST review... did not happen, so if you wonder how
to speed things up, you got a hint. Anyway, many questions futher.


> +
> +		if (i == 1) {
> +			mm_ospi2_size = resource_size(&res);
> +
> +			/* check that OMM memory region 1 doesn't overlap memory region 2 */
> +			if (resource_overlaps(&res, &res1)) {
> +				dev_err(dev, "OMM memory-region %s overlaps memory region %s\n",
> +					mm_name[0], mm_name[1]);
> +				dev_err(dev, "%pR overlaps %pR\n", &res1, &res);
> +
> +				return -EFAULT;
> +			}
> +		}
> +	}
> +
> +	syscfg_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "st,syscfg-amcr");
> +	if (IS_ERR(syscfg_regmap)) {
> +		dev_err(dev, "Failed to get st,syscfg-amcr property\n");
> +		return PTR_ERR(syscfg_regmap);

Same comments as usual, see further.

> +	}
> +
> +	ret = of_property_read_u32_index(dev->of_node, "st,syscfg-amcr", 1,
> +					 &amcr_base);
> +	if (ret)
> +		return ret;
> +
> +	ret = of_property_read_u32_index(dev->of_node, "st,syscfg-amcr", 2,
> +					 &amcr_mask);
> +	if (ret)
> +		return ret;
> +
> +	amcr = mm_ospi2_size / SZ_64M;
> +
> +	if (set)
> +		regmap_update_bits(syscfg_regmap, amcr_base, amcr_mask, amcr);
> +
> +	/* read AMCR and check coherency with memory-map areas defined in DT */
> +	regmap_read(syscfg_regmap, amcr_base, &read_amcr);
> +	read_amcr = read_amcr >> (ffs(amcr_mask) - 1);
> +
> +	if (amcr != read_amcr) {
> +		dev_err(dev, "AMCR value not coherent with DT memory-map areas\n");
> +		ret = -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int stm32_omm_enable_child_clock(struct device *dev, bool enable)
> +{
> +	/* As there is only 2 children, remember first child in case of error */
> +	struct clk *first_child_clk = NULL;
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +	u8 i;
> +	int ret;
> +
> +	for (i = 0; i < omm->nb_child; i++) {
> +		if (enable) {
> +			ret = clk_prepare_enable(omm->child[i].clk);
> +			if (ret) {
> +				if (first_child_clk)
> +					clk_disable_unprepare(first_child_clk);

Function is called stm32_omm_enable_child_clock() but you disable.
Confusing. Probably should be called toggle.

> +
> +				dev_err(dev, "Can not enable clock\n");
> +				return ret;
> +			}
> +		} else {
> +			clk_disable_unprepare(omm->child[i].clk);
> +		}
> +
> +		first_child_clk = omm->child[i].clk;
> +	}
> +
> +	return 0;
> +}
> +
> +static int stm32_omm_configure(struct device *dev)
> +{
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +	struct reset_control *rstc;
> +	unsigned long clk_rate, clk_rate_max = 0;
> +	int ret;
> +	u8 i;
> +	u32 mux = 0;

That's one big mess. Do not mix initialized declarations with
non-initialized in the same line. Then group initialized ones together
and use some reverse christmas tree.

Then the rest also should be organized.

> +	u32 cssel_ovr = 0;
> +	u32 req2ack = 0;
> +
> +	omm->clk = devm_clk_get(dev, NULL);

So here devm_clk_get, but later of_clk_get...

> +	if (IS_ERR(omm->clk)) {
> +		dev_err(dev, "Failed to get OMM clock (%ld)\n",
> +			PTR_ERR(omm->clk));
> +

No. There is no such code anywhere. Please don't upstream downstream,
but take upstream as template.

It is *always* return dev_err_probe. You are flooding dmesg in deferral
for no reason.

> +		return PTR_ERR(omm->clk);
> +	}
> +
> +	ret = pm_runtime_resume_and_get(dev);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* parse children's clock */
> +	for (i = 0; i < omm->nb_child; i++) {
> +		clk_rate = clk_get_rate(omm->child[i].clk);
> +		if (!clk_rate) {
> +			dev_err(dev, "Invalid clock rate\n");
> +			pm_runtime_disable(dev);
> +			goto err_clk_disable;
> +		}
> +
> +		if (clk_rate > clk_rate_max)
> +			clk_rate_max = clk_rate;
> +	}
> +
> +	rstc = devm_reset_control_get_optional_exclusive(dev, NULL);
> +	if (IS_ERR(rstc)) {
> +		ret = dev_err_probe(dev, PTR_ERR(rstc), "reset get failed\n");
> +		pm_runtime_disable(dev);

Why? It was not enabled in this function. I cannot follow the logic,
feels like random set of calls. Each of your function is supposed to
reverse ONLY what it done so far.

> +		goto err_clk_disable;
> +	}
> +
> +	reset_control_assert(rstc);
> +	udelay(2);
> +	reset_control_deassert(rstc);
> +
> +	omm->cr = readl_relaxed(omm->io_base + OMM_CR);
> +	/* optional */
> +	ret = of_property_read_u32(dev->of_node, "st,omm-mux", &mux);
> +	if (!ret) {
> +		if (mux & CR_MUXEN) {
> +			ret = of_property_read_u32(dev->of_node, "st,omm-req2ack-ns",
> +						   &req2ack);
> +			if (!ret && !req2ack) {
> +				req2ack = DIV_ROUND_UP(req2ack, NSEC_PER_SEC / clk_rate_max) - 1;
> +
> +				if (req2ack > 256)
> +					req2ack = 256;
> +			}
> +
> +			req2ack = FIELD_PREP(CR_REQ2ACK_MASK, req2ack);
> +
> +			omm->cr &= ~CR_REQ2ACK_MASK;
> +			omm->cr |= FIELD_PREP(CR_REQ2ACK_MASK, req2ack);
> +
> +			/*
> +			 * If the mux is enabled, the 2 OSPI clocks have to be
> +			 * always enabled
> +			 */
> +			ret = stm32_omm_enable_child_clock(dev, true);
> +			if (ret) {
> +				pm_runtime_disable(dev);
> +				goto err_clk_disable;
> +			}
> +		}
> +
> +		omm->cr &= ~CR_MUXENMODE_MASK;
> +		omm->cr |= FIELD_PREP(CR_MUXENMODE_MASK, mux);
> +	}
> +
> +	/* optional */
> +	ret = of_property_read_u32(dev->of_node, "st,omm-cssel-ovr", &cssel_ovr);
> +	if (!ret) {
> +		omm->cr &= ~CR_CSSEL_OVR_MASK;
> +		omm->cr |= FIELD_PREP(CR_CSSEL_OVR_MASK, cssel_ovr);
> +		omm->cr |= CR_CSSEL_OVR_EN;
> +	}
> +
> +	omm->restore_omm = true;
> +	writel_relaxed(omm->cr, omm->io_base + OMM_CR);
> +
> +	ret = stm32_omm_set_amcr(dev, true);
> +
> +err_clk_disable:
> +	pm_runtime_put_sync_suspend(dev);
> +
> +	return ret;
> +}
> +
> +static int stm32_omm_check_access(struct device *dev, struct device_node *np)
> +{
> +	struct stm32_firewall firewall;
> +	int ret;
> +
> +	ret = stm32_firewall_get_firewall(np, &firewall, 1);
> +	if (ret)
> +		return ret;
> +
> +	return stm32_firewall_grant_access(&firewall);
> +}
> +
> +static int stm32_omm_disable_child(struct device *dev)
> +{
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +	struct reset_control *reset;
> +	int ret;
> +	u8 i;
> +
> +	for (i = 0; i < omm->nb_child; i++) {
> +		ret = clk_prepare_enable(omm->child[i].clk);
> +		if (ret) {
> +			dev_err(dev, "Can not enable clock\n");
> +			return ret;
> +		}
> +
> +		reset = of_reset_control_get_exclusive(omm->child[i].node, 0);
> +		if (IS_ERR(reset)) {
> +			dev_err(dev, "Can't get child reset\n");

Why do you get reset of child? Parent is not suppposed to poke there.
You might not have the reset there in the first place and it would not
be an error.


> +			return PTR_ERR(reset);
> +		};
> +
> +		/* reset OSPI to ensure CR_EN bit is set to 0 */
> +		reset_control_assert(reset);
> +		udelay(2);
> +		reset_control_deassert(reset);

No, the child should handle this, not parent.

> +
> +		reset_control_put(reset);
> +		clk_disable_unprepare(omm->child[i].clk);
> +	}
> +
> +	return 0;
> +}
> +
> +static int stm32_omm_probe(struct platform_device *pdev)
> +{
> +	struct platform_device *vdev;
> +	struct device *dev = &pdev->dev;
> +	struct stm32_omm *omm;
> +	struct clk *clk;
> +	int ret;
> +	u8 child_access_granted = 0;

Keep inits/assignments together

> +	u8 i, j;
> +	bool child_access[OMM_CHILD_NB];
> +
> +	omm = devm_kzalloc(dev, sizeof(*omm), GFP_KERNEL);
> +	if (!omm)
> +		return -ENOMEM;
> +
> +	omm->io_base = devm_platform_ioremap_resource_byname(pdev, "regs");
> +	if (IS_ERR(omm->io_base))
> +		return PTR_ERR(omm->io_base);
> +
> +	omm->mm_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "memory_map");
> +	if (IS_ERR(omm->mm_res))
> +		return PTR_ERR(omm->mm_res);
> +
> +	/* check child's access */
> +	for_each_child_of_node_scoped(dev->of_node, child) {
> +		if (omm->nb_child >= OMM_CHILD_NB) {
> +			dev_err(dev, "Bad DT, found too much children\n");
> +			ret = -E2BIG;
> +			goto err_clk_release;
> +		}
> +
> +		if (!of_device_is_compatible(child, "st,stm32mp25-ospi")) {
> +			ret = -EINVAL;
> +			goto err_clk_release;
> +		}
> +
> +		ret = stm32_omm_check_access(dev, child);
> +		if (ret < 0 && ret != -EACCES)
> +			goto err_clk_release;
> +
> +		child_access[omm->nb_child] = false;
> +		if (!ret) {
> +			child_access_granted++;
> +			child_access[omm->nb_child] = true;
> +		}
> +
> +		omm->child[omm->nb_child].node = child;
> +
> +		clk = of_clk_get(child, 0);

Why are you taking children clock? And why with this API, not clk_get?
This looks like mixing clock provider in the clock consumer.

> +		if (IS_ERR(clk)) {
> +			dev_err(dev, "Can't get child clock\n");

Syntax is always return dev_err_probe (or ret = dev_err_probe).

> +			ret = PTR_ERR(clk);
> +			goto err_clk_release;
> +		};
> +
> +		omm->child[omm->nb_child].clk = clk;
> +		omm->nb_child++;
> +	}
> +
> +	if (omm->nb_child != OMM_CHILD_NB) {
> +		ret = -EINVAL;
> +		goto err_clk_release;
> +	}
> +
> +	platform_set_drvdata(pdev, omm);
> +
> +	pm_runtime_enable(dev);
> +
> +	/* check if OMM's resource access is granted */
> +	ret = stm32_omm_check_access(dev, dev->of_node);
> +	if (ret < 0 && ret != -EACCES)
> +		goto err_clk_release;
> +
> +	if (!ret && child_access_granted == OMM_CHILD_NB) {
> +		/* Ensure both OSPI instance are disabled before configuring OMM */
> +		ret = stm32_omm_disable_child(dev);
> +		if (ret)
> +			goto err_clk_release;
> +
> +		ret = stm32_omm_configure(dev);
> +		if (ret)
> +			goto err_clk_release;
> +	} else {
> +		dev_dbg(dev, "Octo Memory Manager resource's access not granted\n");
> +		/*
> +		 * AMCR can't be set, so check if current value is coherent
> +		 * with memory-map areas defined in DT
> +		 */
> +		ret = stm32_omm_set_amcr(dev, false);
> +		if (ret)
> +			goto err_clk_release;
> +	}
> +
> +	/* for each child, if resource access is granted and status "okay", probe it */
> +	for (i = 0; i < omm->nb_child; i++) {
> +		if (!child_access[i] || !of_device_is_available(omm->child[i].node))

If you have a device available, why do you create one more platform device?

> +			continue;
> +
> +		vdev = of_platform_device_create(omm->child[i].node, NULL, NULL);

Why you cannot just populate the children?

> +		if (!vdev) {
> +			dev_err(dev, "Failed to create Octo Memory Manager child\n");
> +			for (j = i; j > 0; --j) {
> +				if (omm->child[j].dev)
> +					of_platform_device_destroy(omm->child[j].dev, NULL);
> +			}
> +
> +			ret = -EINVAL;
> +			goto err_clk_release;
> +		}
> +		omm->child[i].dev = &vdev->dev;
> +	}
> +
> +err_clk_release:
> +	for (i = 0; i < omm->nb_child; i++)
> +		clk_put(omm->child[i].clk);
> +
> +	return ret;
> +}
> +
> +static void stm32_omm_remove(struct platform_device *pdev)
> +{
> +	struct stm32_omm *omm = platform_get_drvdata(pdev);
> +	int i;
> +
> +	for (i = 0; i < omm->nb_child; i++)
> +		if (omm->child[i].dev)
> +			of_platform_device_destroy(omm->child[i].dev, NULL);
> +
> +	if (omm->cr & CR_MUXEN)
> +		stm32_omm_enable_child_clock(&pdev->dev, false);
> +
> +	pm_runtime_disable(&pdev->dev);
> +}
> +
> +static const struct of_device_id stm32_omm_of_match[] = {
> +	{ .compatible = "st,stm32mp25-omm", },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, stm32_omm_of_match);
> +
> +static int __maybe_unused stm32_omm_runtime_suspend(struct device *dev)
> +{
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +
> +	clk_disable_unprepare(omm->clk);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused stm32_omm_runtime_resume(struct device *dev)
> +{
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +
> +	return clk_prepare_enable(omm->clk);
> +}
> +
> +static int __maybe_unused stm32_omm_suspend(struct device *dev)
> +{
> +	struct stm32_omm *omm = dev_get_drvdata(dev);
> +
> +	if (omm->restore_omm && omm->cr & CR_MUXEN)
> +		stm32_omm_enable_child_clock(dev, false);

Why do you enable child clock for suspend?

> +
> +	return pinctrl_pm_select_sleep_state(dev);
> +}
> +


Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver
  2025-03-11 16:04   ` Krzysztof Kozlowski
@ 2025-03-12 14:23     ` Patrice CHOTARD
  2025-03-13  7:33       ` Krzysztof Kozlowski
  0 siblings, 1 reply; 19+ messages in thread
From: Patrice CHOTARD @ 2025-03-12 14:23 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello



On 3/11/25 17:04, Krzysztof Kozlowski wrote:
> On 19/02/2025 09:00, patrice.chotard@foss.st.com wrote:
>> From: Patrice Chotard <patrice.chotard@foss.st.com>
>>
>> Octo Memory Manager driver (OMM) manages:
>>   - the muxing between 2 OSPI busses and 2 output ports.
>>     There are 4 possible muxing configurations:
>>       - direct mode (no multiplexing): OSPI1 output is on port 1 and OSPI2
>>         output is on port 2
>>       - OSPI1 and OSPI2 are multiplexed over the same output port 1
>>       - swapped mode (no multiplexing), OSPI1 output is on port 2,
>>         OSPI2 output is on port 1
>>       - OSPI1 and OSPI2 are multiplexed over the same output port 2
>>   - the split of the memory area shared between the 2 OSPI instances.
>>   - chip select selection override.
>>   - the time between 2 transactions in multiplexed mode.
>>   - check firewall access.
>>
>> Signed-off-by: Patrice Chotard <patrice.chotard@foss.st.com>
>> Signed-off-by: Christophe Kerello <christophe.kerello@foss.st.com>
> 
> Incorrect chain. You sent it, so you must be the last person signing it.

ok

> 
> I was waiting for any ST review... did not happen, so if you wonder how
> to speed things up, you got a hint. Anyway, many questions futher.
> 
> 
>> +
>> +		if (i == 1) {
>> +			mm_ospi2_size = resource_size(&res);
>> +
>> +			/* check that OMM memory region 1 doesn't overlap memory region 2 */
>> +			if (resource_overlaps(&res, &res1)) {
>> +				dev_err(dev, "OMM memory-region %s overlaps memory region %s\n",
>> +					mm_name[0], mm_name[1]);
>> +				dev_err(dev, "%pR overlaps %pR\n", &res1, &res);
>> +
>> +				return -EFAULT;
>> +			}
>> +		}
>> +	}
>> +
>> +	syscfg_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "st,syscfg-amcr");
>> +	if (IS_ERR(syscfg_regmap)) {
>> +		dev_err(dev, "Failed to get st,syscfg-amcr property\n");
>> +		return PTR_ERR(syscfg_regmap);
> 
> Same comments as usual, see further.

ok, will use dev_err_probe()

> 
>> +	}
>> +
>> +	ret = of_property_read_u32_index(dev->of_node, "st,syscfg-amcr", 1,
>> +					 &amcr_base);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = of_property_read_u32_index(dev->of_node, "st,syscfg-amcr", 2,
>> +					 &amcr_mask);
>> +	if (ret)
>> +		return ret;
>> +
>> +	amcr = mm_ospi2_size / SZ_64M;
>> +
>> +	if (set)
>> +		regmap_update_bits(syscfg_regmap, amcr_base, amcr_mask, amcr);
>> +
>> +	/* read AMCR and check coherency with memory-map areas defined in DT */
>> +	regmap_read(syscfg_regmap, amcr_base, &read_amcr);
>> +	read_amcr = read_amcr >> (ffs(amcr_mask) - 1);
>> +
>> +	if (amcr != read_amcr) {
>> +		dev_err(dev, "AMCR value not coherent with DT memory-map areas\n");
>> +		ret = -EINVAL;
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static int stm32_omm_enable_child_clock(struct device *dev, bool enable)
>> +{
>> +	/* As there is only 2 children, remember first child in case of error */
>> +	struct clk *first_child_clk = NULL;
>> +	struct stm32_omm *omm = dev_get_drvdata(dev);
>> +	u8 i;
>> +	int ret;
>> +
>> +	for (i = 0; i < omm->nb_child; i++) {
>> +		if (enable) {
>> +			ret = clk_prepare_enable(omm->child[i].clk);
>> +			if (ret) {
>> +				if (first_child_clk)
>> +					clk_disable_unprepare(first_child_clk);
> 
> Function is called stm32_omm_enable_child_clock() but you disable.
> Confusing. Probably should be called toggle.

Agree, i will rename it to stm32_omm_toggle_child_clock

> 
>> +
>> +				dev_err(dev, "Can not enable clock\n");
>> +				return ret;
>> +			}
>> +		} else {
>> +			clk_disable_unprepare(omm->child[i].clk);
>> +		}
>> +
>> +		first_child_clk = omm->child[i].clk;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int stm32_omm_configure(struct device *dev)
>> +{
>> +	struct stm32_omm *omm = dev_get_drvdata(dev);
>> +	struct reset_control *rstc;
>> +	unsigned long clk_rate, clk_rate_max = 0;
>> +	int ret;
>> +	u8 i;
>> +	u32 mux = 0;
> 
> That's one big mess. Do not mix initialized declarations with
> non-initialized in the same line. Then group initialized ones together
> and use some reverse christmas tree.
> 
> Then the rest also should be organized.

ok

> 
>> +	u32 cssel_ovr = 0;
>> +	u32 req2ack = 0;
>> +
>> +	omm->clk = devm_clk_get(dev, NULL);
> 
> So here devm_clk_get, but later of_clk_get...
> 
>> +	if (IS_ERR(omm->clk)) {
>> +		dev_err(dev, "Failed to get OMM clock (%ld)\n",
>> +			PTR_ERR(omm->clk));
>> +
> 
> No. There is no such code anywhere. Please don't upstream downstream,
> but take upstream as template.
> 
> It is *always* return dev_err_probe. You are flooding dmesg in deferral
> for no reason.

ok, will use dev_err_probe()

> 
>> +		return PTR_ERR(omm->clk);
>> +	}
>> +
>> +	ret = pm_runtime_resume_and_get(dev);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	/* parse children's clock */
>> +	for (i = 0; i < omm->nb_child; i++) {
>> +		clk_rate = clk_get_rate(omm->child[i].clk);
>> +		if (!clk_rate) {
>> +			dev_err(dev, "Invalid clock rate\n");
>> +			pm_runtime_disable(dev);
>> +			goto err_clk_disable;
>> +		}
>> +
>> +		if (clk_rate > clk_rate_max)
>> +			clk_rate_max = clk_rate;
>> +	}
>> +
>> +	rstc = devm_reset_control_get_optional_exclusive(dev, NULL);
>> +	if (IS_ERR(rstc)) {
>> +		ret = dev_err_probe(dev, PTR_ERR(rstc), "reset get failed\n");
>> +		pm_runtime_disable(dev);
> 
> Why? It was not enabled in this function. I cannot follow the logic,
> feels like random set of calls. Each of your function is supposed to
> reverse ONLY what it done so far.

right, i will move pm_runtime_disable() outside stm32_omm_enable_child_clock()

> 
>> +		goto err_clk_disable;
>> +	}
>> +
>> +	reset_control_assert(rstc);
>> +	udelay(2);
>> +	reset_control_deassert(rstc);
>> +
>> +	omm->cr = readl_relaxed(omm->io_base + OMM_CR);
>> +	/* optional */
>> +	ret = of_property_read_u32(dev->of_node, "st,omm-mux", &mux);
>> +	if (!ret) {
>> +		if (mux & CR_MUXEN) {
>> +			ret = of_property_read_u32(dev->of_node, "st,omm-req2ack-ns",
>> +						   &req2ack);
>> +			if (!ret && !req2ack) {
>> +				req2ack = DIV_ROUND_UP(req2ack, NSEC_PER_SEC / clk_rate_max) - 1;
>> +
>> +				if (req2ack > 256)
>> +					req2ack = 256;
>> +			}
>> +
>> +			req2ack = FIELD_PREP(CR_REQ2ACK_MASK, req2ack);
>> +
>> +			omm->cr &= ~CR_REQ2ACK_MASK;
>> +			omm->cr |= FIELD_PREP(CR_REQ2ACK_MASK, req2ack);
>> +
>> +			/*
>> +			 * If the mux is enabled, the 2 OSPI clocks have to be
>> +			 * always enabled
>> +			 */
>> +			ret = stm32_omm_enable_child_clock(dev, true);
>> +			if (ret) {
>> +				pm_runtime_disable(dev);
>> +				goto err_clk_disable;
>> +			}
>> +		}
>> +
>> +		omm->cr &= ~CR_MUXENMODE_MASK;
>> +		omm->cr |= FIELD_PREP(CR_MUXENMODE_MASK, mux);
>> +	}
>> +
>> +	/* optional */
>> +	ret = of_property_read_u32(dev->of_node, "st,omm-cssel-ovr", &cssel_ovr);
>> +	if (!ret) {
>> +		omm->cr &= ~CR_CSSEL_OVR_MASK;
>> +		omm->cr |= FIELD_PREP(CR_CSSEL_OVR_MASK, cssel_ovr);
>> +		omm->cr |= CR_CSSEL_OVR_EN;
>> +	}
>> +
>> +	omm->restore_omm = true;
>> +	writel_relaxed(omm->cr, omm->io_base + OMM_CR);
>> +
>> +	ret = stm32_omm_set_amcr(dev, true);
>> +
>> +err_clk_disable:
>> +	pm_runtime_put_sync_suspend(dev);
>> +
>> +	return ret;
>> +}
>> +
>> +static int stm32_omm_check_access(struct device *dev, struct device_node *np)
>> +{
>> +	struct stm32_firewall firewall;
>> +	int ret;
>> +
>> +	ret = stm32_firewall_get_firewall(np, &firewall, 1);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return stm32_firewall_grant_access(&firewall);
>> +}
>> +
>> +static int stm32_omm_disable_child(struct device *dev)
>> +{
>> +	struct stm32_omm *omm = dev_get_drvdata(dev);
>> +	struct reset_control *reset;
>> +	int ret;
>> +	u8 i;
>> +
>> +	for (i = 0; i < omm->nb_child; i++) {
>> +		ret = clk_prepare_enable(omm->child[i].clk);
>> +		if (ret) {
>> +			dev_err(dev, "Can not enable clock\n");
>> +			return ret;
>> +		}
>> +
>> +		reset = of_reset_control_get_exclusive(omm->child[i].node, 0);
>> +		if (IS_ERR(reset)) {
>> +			dev_err(dev, "Can't get child reset\n");
> 
> Why do you get reset of child? Parent is not suppposed to poke there.
> You might not have the reset there in the first place and it would not
> be an error.

By ressetting child (OSPI), we ensure they are disabled and in a known state.
See the comment below.

> 
> 
>> +			return PTR_ERR(reset);
>> +		};
>> +
>> +		/* reset OSPI to ensure CR_EN bit is set to 0 */
>> +		reset_control_assert(reset);
>> +		udelay(2);
>> +		reset_control_deassert(reset);
> 
> No, the child should handle this, not parent.

Octo Memory Manager can only be configured if both child are disabled.
That's why here, parent handles this.

> 
>> +
>> +		reset_control_put(reset);
>> +		clk_disable_unprepare(omm->child[i].clk);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int stm32_omm_probe(struct platform_device *pdev)
>> +{
>> +	struct platform_device *vdev;
>> +	struct device *dev = &pdev->dev;
>> +	struct stm32_omm *omm;
>> +	struct clk *clk;
>> +	int ret;
>> +	u8 child_access_granted = 0;
> 
> Keep inits/assignments together

ok

> 
>> +	u8 i, j;
>> +	bool child_access[OMM_CHILD_NB];
>> +
>> +	omm = devm_kzalloc(dev, sizeof(*omm), GFP_KERNEL);
>> +	if (!omm)
>> +		return -ENOMEM;
>> +
>> +	omm->io_base = devm_platform_ioremap_resource_byname(pdev, "regs");
>> +	if (IS_ERR(omm->io_base))
>> +		return PTR_ERR(omm->io_base);
>> +
>> +	omm->mm_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "memory_map");
>> +	if (IS_ERR(omm->mm_res))
>> +		return PTR_ERR(omm->mm_res);
>> +
>> +	/* check child's access */
>> +	for_each_child_of_node_scoped(dev->of_node, child) {
>> +		if (omm->nb_child >= OMM_CHILD_NB) {
>> +			dev_err(dev, "Bad DT, found too much children\n");
>> +			ret = -E2BIG;
>> +			goto err_clk_release;
>> +		}
>> +
>> +		if (!of_device_is_compatible(child, "st,stm32mp25-ospi")) {
>> +			ret = -EINVAL;
>> +			goto err_clk_release;
>> +		}
>> +
>> +		ret = stm32_omm_check_access(dev, child);
>> +		if (ret < 0 && ret != -EACCES)
>> +			goto err_clk_release;
>> +
>> +		child_access[omm->nb_child] = false;
>> +		if (!ret) {
>> +			child_access_granted++;
>> +			child_access[omm->nb_child] = true;
>> +		}
>> +
>> +		omm->child[omm->nb_child].node = child;
>> +
>> +		clk = of_clk_get(child, 0);
> 
> Why are you taking children clock? And why with this API, not clk_get?

I need children's clock to reset them.
Why of_clk_get() usage is a problem here ? i can't get your point ?

> This looks like mixing clock provider in the clock consumer.
> 
>> +		if (IS_ERR(clk)) {
>> +			dev_err(dev, "Can't get child clock\n");
> 
> Syntax is always return dev_err_probe (or ret = dev_err_probe).

ok

> 
>> +			ret = PTR_ERR(clk);
>> +			goto err_clk_release;
>> +		};
>> +
>> +		omm->child[omm->nb_child].clk = clk;
>> +		omm->nb_child++;
>> +	}
>> +
>> +	if (omm->nb_child != OMM_CHILD_NB) {
>> +		ret = -EINVAL;
>> +		goto err_clk_release;
>> +	}
>> +
>> +	platform_set_drvdata(pdev, omm);
>> +
>> +	pm_runtime_enable(dev);
>> +
>> +	/* check if OMM's resource access is granted */
>> +	ret = stm32_omm_check_access(dev, dev->of_node);
>> +	if (ret < 0 && ret != -EACCES)
>> +		goto err_clk_release;
>> +
>> +	if (!ret && child_access_granted == OMM_CHILD_NB) {
>> +		/* Ensure both OSPI instance are disabled before configuring OMM */
>> +		ret = stm32_omm_disable_child(dev);
>> +		if (ret)
>> +			goto err_clk_release;
>> +
>> +		ret = stm32_omm_configure(dev);
>> +		if (ret)
>> +			goto err_clk_release;
>> +	} else {
>> +		dev_dbg(dev, "Octo Memory Manager resource's access not granted\n");
>> +		/*
>> +		 * AMCR can't be set, so check if current value is coherent
>> +		 * with memory-map areas defined in DT
>> +		 */
>> +		ret = stm32_omm_set_amcr(dev, false);
>> +		if (ret)
>> +			goto err_clk_release;
>> +	}
>> +
>> +	/* for each child, if resource access is granted and status "okay", probe it */
>> +	for (i = 0; i < omm->nb_child; i++) {
>> +		if (!child_access[i] || !of_device_is_available(omm->child[i].node))
> 
> If you have a device available, why do you create one more platform device?
> 
>> +			continue;
>> +
>> +		vdev = of_platform_device_create(omm->child[i].node, NULL, NULL);
> 
> Why you cannot just populate the children?

I can't use of_platform_populate(), by default it will populate all OMM's child.
Whereas here, we want to probe only the OMM's child which match our criteria.  

> 
>> +		if (!vdev) {
>> +			dev_err(dev, "Failed to create Octo Memory Manager child\n");
>> +			for (j = i; j > 0; --j) {
>> +				if (omm->child[j].dev)
>> +					of_platform_device_destroy(omm->child[j].dev, NULL);
>> +			}
>> +
>> +			ret = -EINVAL;
>> +			goto err_clk_release;
>> +		}
>> +		omm->child[i].dev = &vdev->dev;
>> +	}
>> +
>> +err_clk_release:
>> +	for (i = 0; i < omm->nb_child; i++)
>> +		clk_put(omm->child[i].clk);
>> +
>> +	return ret;
>> +}
>> +
>> +static void stm32_omm_remove(struct platform_device *pdev)
>> +{
>> +	struct stm32_omm *omm = platform_get_drvdata(pdev);
>> +	int i;
>> +
>> +	for (i = 0; i < omm->nb_child; i++)
>> +		if (omm->child[i].dev)
>> +			of_platform_device_destroy(omm->child[i].dev, NULL);
>> +
>> +	if (omm->cr & CR_MUXEN)
>> +		stm32_omm_enable_child_clock(&pdev->dev, false);
>> +
>> +	pm_runtime_disable(&pdev->dev);
>> +}
>> +
>> +static const struct of_device_id stm32_omm_of_match[] = {
>> +	{ .compatible = "st,stm32mp25-omm", },
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, stm32_omm_of_match);
>> +
>> +static int __maybe_unused stm32_omm_runtime_suspend(struct device *dev)
>> +{
>> +	struct stm32_omm *omm = dev_get_drvdata(dev);
>> +
>> +	clk_disable_unprepare(omm->clk);
>> +
>> +	return 0;
>> +}
>> +
>> +static int __maybe_unused stm32_omm_runtime_resume(struct device *dev)
>> +{
>> +	struct stm32_omm *omm = dev_get_drvdata(dev);
>> +
>> +	return clk_prepare_enable(omm->clk);
>> +}
>> +
>> +static int __maybe_unused stm32_omm_suspend(struct device *dev)
>> +{
>> +	struct stm32_omm *omm = dev_get_drvdata(dev);
>> +
>> +	if (omm->restore_omm && omm->cr & CR_MUXEN)
>> +		stm32_omm_enable_child_clock(dev, false);
> 
> Why do you enable child clock for suspend?

The child clock is disbaled here, but as you indicated earlier in this patch,
stm32_omm_enable_child_clock() will be renamed stm32_omm_toggle_child_clock() 
to avoid confussion.

Thanks
Patrice

> 
>> +
>> +	return pinctrl_pm_select_sleep_state(dev);
>> +}
>> +
> 
> 
> Best regards,
> Krzysztof


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver
  2025-03-10 13:52   ` Patrice CHOTARD
  2025-03-10 14:10     ` Krzysztof Kozlowski
@ 2025-03-13  7:30     ` Krzysztof Kozlowski
  1 sibling, 0 replies; 19+ messages in thread
From: Krzysztof Kozlowski @ 2025-03-13  7:30 UTC (permalink / raw)
  To: Patrice CHOTARD, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello

On 10/03/2025 14:52, Patrice CHOTARD wrote:
>> +module_platform_driver(stm32_omm_driver);
>> +
>> +MODULE_DESCRIPTION("STMicroelectronics Octo Memory Manager driver");
>> +MODULE_LICENSE("GPL");
> 
> 
> Hi all,
> 
> Anybody alse has additionnal remarks on this driver ?
BTW, you explained nothing about merging in the cover letter, mark
already took the patch, but I see there is dependency. This cannot be
merged and pinging will not change anything here.

In the future, ALWAYS document dependencies between patches and make it
explicit for the maintainers.

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver
  2025-03-12 14:23     ` Patrice CHOTARD
@ 2025-03-13  7:33       ` Krzysztof Kozlowski
  2025-03-18 13:40         ` Patrice CHOTARD
  0 siblings, 1 reply; 19+ messages in thread
From: Krzysztof Kozlowski @ 2025-03-13  7:33 UTC (permalink / raw)
  To: Patrice CHOTARD, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello

On 12/03/2025 15:23, Patrice CHOTARD wrote:
>>> +static int stm32_omm_disable_child(struct device *dev)
>>> +{
>>> +	struct stm32_omm *omm = dev_get_drvdata(dev);
>>> +	struct reset_control *reset;
>>> +	int ret;
>>> +	u8 i;
>>> +
>>> +	for (i = 0; i < omm->nb_child; i++) {
>>> +		ret = clk_prepare_enable(omm->child[i].clk);
>>> +		if (ret) {
>>> +			dev_err(dev, "Can not enable clock\n");
>>> +			return ret;
>>> +		}
>>> +
>>> +		reset = of_reset_control_get_exclusive(omm->child[i].node, 0);
>>> +		if (IS_ERR(reset)) {
>>> +			dev_err(dev, "Can't get child reset\n");
>>
>> Why do you get reset of child? Parent is not suppposed to poke there.
>> You might not have the reset there in the first place and it would not
>> be an error.
> 
> By ressetting child (OSPI), we ensure they are disabled and in a known state.
> See the comment below.
> 
>>
>>
>>> +			return PTR_ERR(reset);
>>> +		};
>>> +
>>> +		/* reset OSPI to ensure CR_EN bit is set to 0 */
>>> +		reset_control_assert(reset);
>>> +		udelay(2);
>>> +		reset_control_deassert(reset);
>>
>> No, the child should handle this, not parent.
> 
> Octo Memory Manager can only be configured if both child are disabled.
> That's why here, parent handles this.

So if device by any chance started and is doing some useful work, then
you cancel that work and reset it?

And what if child does not have reset line? Your binding allows that, so
how is it supposed to work then?

This also leads me to questions about bindings - if you need to assert
some reset, doesn't it mean that these resets are also coming through
this device so they are part of this device node?


> 
>>
>>> +
>>> +		reset_control_put(reset);
>>> +		clk_disable_unprepare(omm->child[i].clk);
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int stm32_omm_probe(struct platform_device *pdev)
>>> +{
>>> +	struct platform_device *vdev;
>>> +	struct device *dev = &pdev->dev;
>>> +	struct stm32_omm *omm;
>>> +	struct clk *clk;
>>> +	int ret;
>>> +	u8 child_access_granted = 0;
>>
>> Keep inits/assignments together
> 
> ok
> 
>>
>>> +	u8 i, j;
>>> +	bool child_access[OMM_CHILD_NB];
>>> +
>>> +	omm = devm_kzalloc(dev, sizeof(*omm), GFP_KERNEL);
>>> +	if (!omm)
>>> +		return -ENOMEM;
>>> +
>>> +	omm->io_base = devm_platform_ioremap_resource_byname(pdev, "regs");
>>> +	if (IS_ERR(omm->io_base))
>>> +		return PTR_ERR(omm->io_base);
>>> +
>>> +	omm->mm_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "memory_map");
>>> +	if (IS_ERR(omm->mm_res))
>>> +		return PTR_ERR(omm->mm_res);
>>> +
>>> +	/* check child's access */
>>> +	for_each_child_of_node_scoped(dev->of_node, child) {
>>> +		if (omm->nb_child >= OMM_CHILD_NB) {
>>> +			dev_err(dev, "Bad DT, found too much children\n");
>>> +			ret = -E2BIG;
>>> +			goto err_clk_release;
>>> +		}
>>> +
>>> +		if (!of_device_is_compatible(child, "st,stm32mp25-ospi")) {
>>> +			ret = -EINVAL;
>>> +			goto err_clk_release;
>>> +		}
>>> +
>>> +		ret = stm32_omm_check_access(dev, child);
>>> +		if (ret < 0 && ret != -EACCES)
>>> +			goto err_clk_release;
>>> +
>>> +		child_access[omm->nb_child] = false;
>>> +		if (!ret) {
>>> +			child_access_granted++;
>>> +			child_access[omm->nb_child] = true;
>>> +		}
>>> +
>>> +		omm->child[omm->nb_child].node = child;
>>> +
>>> +		clk = of_clk_get(child, 0);
>>
>> Why are you taking children clock? And why with this API, not clk_get?
> 
> I need children's clock to reset them.


The device driver should reset its device. It is not a discoverable bus,
that would explain power sequencing from the parent.

> Why of_clk_get() usage is a problem here ? i can't get your point ?

Because it is not the API which device drivers should use. You should
use clk_get or devm_clk_get.


> 
>> This looks like mixing clock provider in the clock consumer.
>>
>>> +		if (IS_ERR(clk)) {
>>> +			dev_err(dev, "Can't get child clock\n");
>>
>> Syntax is always return dev_err_probe (or ret = dev_err_probe).
> 
> ok
> 
>>
>>> +			ret = PTR_ERR(clk);
>>> +			goto err_clk_release;
>>> +		};
>>> +
>>> +		omm->child[omm->nb_child].clk = clk;
>>> +		omm->nb_child++;
>>> +	}
>>> +
>>> +	if (omm->nb_child != OMM_CHILD_NB) {
>>> +		ret = -EINVAL;
>>> +		goto err_clk_release;
>>> +	}
>>> +
>>> +	platform_set_drvdata(pdev, omm);
>>> +
>>> +	pm_runtime_enable(dev);
>>> +
>>> +	/* check if OMM's resource access is granted */
>>> +	ret = stm32_omm_check_access(dev, dev->of_node);
>>> +	if (ret < 0 && ret != -EACCES)
>>> +		goto err_clk_release;
>>> +
>>> +	if (!ret && child_access_granted == OMM_CHILD_NB) {
>>> +		/* Ensure both OSPI instance are disabled before configuring OMM */
>>> +		ret = stm32_omm_disable_child(dev);
>>> +		if (ret)
>>> +			goto err_clk_release;
>>> +
>>> +		ret = stm32_omm_configure(dev);
>>> +		if (ret)
>>> +			goto err_clk_release;
>>> +	} else {
>>> +		dev_dbg(dev, "Octo Memory Manager resource's access not granted\n");
>>> +		/*
>>> +		 * AMCR can't be set, so check if current value is coherent
>>> +		 * with memory-map areas defined in DT
>>> +		 */
>>> +		ret = stm32_omm_set_amcr(dev, false);
>>> +		if (ret)
>>> +			goto err_clk_release;
>>> +	}
>>> +
>>> +	/* for each child, if resource access is granted and status "okay", probe it */
>>> +	for (i = 0; i < omm->nb_child; i++) {
>>> +		if (!child_access[i] || !of_device_is_available(omm->child[i].node))
>>
>> If you have a device available, why do you create one more platform device?
>>
>>> +			continue;
>>> +
>>> +		vdev = of_platform_device_create(omm->child[i].node, NULL, NULL);
>>
>> Why you cannot just populate the children?
> 
> I can't use of_platform_populate(), by default it will populate all OMM's child.
> Whereas here, we want to probe only the OMM's child which match our criteria.  


Why wouldn't you populate everyone? The task of bus driver is not to
filter out DT. If you got such DT - with all device nodes - you are
expected to populate all of them. Otherwise, if you do not want all of
them, it is expected that firmware or bootloader will give you DT
without these nodes.

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver
  2025-03-13  7:33       ` Krzysztof Kozlowski
@ 2025-03-18 13:40         ` Patrice CHOTARD
  2025-03-19  7:37           ` Krzysztof Kozlowski
  0 siblings, 1 reply; 19+ messages in thread
From: Patrice CHOTARD @ 2025-03-18 13:40 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello



On 3/13/25 08:33, Krzysztof Kozlowski wrote:
> On 12/03/2025 15:23, Patrice CHOTARD wrote:
>>>> +static int stm32_omm_disable_child(struct device *dev)
>>>> +{
>>>> +	struct stm32_omm *omm = dev_get_drvdata(dev);
>>>> +	struct reset_control *reset;
>>>> +	int ret;
>>>> +	u8 i;
>>>> +
>>>> +	for (i = 0; i < omm->nb_child; i++) {
>>>> +		ret = clk_prepare_enable(omm->child[i].clk);
>>>> +		if (ret) {
>>>> +			dev_err(dev, "Can not enable clock\n");
>>>> +			return ret;
>>>> +		}
>>>> +
>>>> +		reset = of_reset_control_get_exclusive(omm->child[i].node, 0);
>>>> +		if (IS_ERR(reset)) {
>>>> +			dev_err(dev, "Can't get child reset\n");
>>>
>>> Why do you get reset of child? Parent is not suppposed to poke there.
>>> You might not have the reset there in the first place and it would not
>>> be an error.
>>
>> By ressetting child (OSPI), we ensure they are disabled and in a known state.
>> See the comment below.
>>
>>>
>>>
>>>> +			return PTR_ERR(reset);
>>>> +		};
>>>> +
>>>> +		/* reset OSPI to ensure CR_EN bit is set to 0 */
>>>> +		reset_control_assert(reset);
>>>> +		udelay(2);
>>>> +		reset_control_deassert(reset);
>>>
>>> No, the child should handle this, not parent.
>>
>> Octo Memory Manager can only be configured if both child are disabled.
>> That's why here, parent handles this.
> 
> So if device by any chance started and is doing some useful work, then
> you cancel that work and reset it?

stm32_omm_configure() is only called if we get access granted on both children.
That means we are authorized to use these devices, so we can reset them.

> 
> And what if child does not have reset line? Your binding allows that, so
> how is it supposed to work then?

Ah yes, you are right, the OSPI bindings need to be updated
by requiring reset lines and the driver spi-stm32-ospi.c as well.
I will send a fix for that.

Thanks for pointing this.

> 
> This also leads me to questions about bindings - if you need to assert
> some reset, doesn't it mean that these resets are also coming through
> this device so they are part of this device node?

As we are able to retrieve children's reset from their respective node,
if you don't mind, OMM bindings can be kept as it's currently.

And another information, on some MP2 SoCs family, there is only one 
OSPI instance. So for these SoCs, there is no Octo Memory Manager.

> 
>>
>>>
>>>> +
>>>> +		reset_control_put(reset);
>>>> +		clk_disable_unprepare(omm->child[i].clk);
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int stm32_omm_probe(struct platform_device *pdev)
>>>> +{
>>>> +	struct platform_device *vdev;
>>>> +	struct device *dev = &pdev->dev;
>>>> +	struct stm32_omm *omm;
>>>> +	struct clk *clk;
>>>> +	int ret;
>>>> +	u8 child_access_granted = 0;
>>>
>>> Keep inits/assignments together
>>
>> ok
>>
>>>
>>>> +	u8 i, j;
>>>> +	bool child_access[OMM_CHILD_NB];
>>>> +
>>>> +	omm = devm_kzalloc(dev, sizeof(*omm), GFP_KERNEL);
>>>> +	if (!omm)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	omm->io_base = devm_platform_ioremap_resource_byname(pdev, "regs");
>>>> +	if (IS_ERR(omm->io_base))
>>>> +		return PTR_ERR(omm->io_base);
>>>> +
>>>> +	omm->mm_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "memory_map");
>>>> +	if (IS_ERR(omm->mm_res))
>>>> +		return PTR_ERR(omm->mm_res);
>>>> +
>>>> +	/* check child's access */
>>>> +	for_each_child_of_node_scoped(dev->of_node, child) {
>>>> +		if (omm->nb_child >= OMM_CHILD_NB) {
>>>> +			dev_err(dev, "Bad DT, found too much children\n");
>>>> +			ret = -E2BIG;
>>>> +			goto err_clk_release;
>>>> +		}
>>>> +
>>>> +		if (!of_device_is_compatible(child, "st,stm32mp25-ospi")) {
>>>> +			ret = -EINVAL;
>>>> +			goto err_clk_release;
>>>> +		}
>>>> +
>>>> +		ret = stm32_omm_check_access(dev, child);
>>>> +		if (ret < 0 && ret != -EACCES)
>>>> +			goto err_clk_release;
>>>> +
>>>> +		child_access[omm->nb_child] = false;
>>>> +		if (!ret) {
>>>> +			child_access_granted++;
>>>> +			child_access[omm->nb_child] = true;
>>>> +		}
>>>> +
>>>> +		omm->child[omm->nb_child].node = child;
>>>> +
>>>> +		clk = of_clk_get(child, 0);
>>>
>>> Why are you taking children clock? And why with this API, not clk_get?
>>
>> I need children's clock to reset them.
> 
> 
> The device driver should reset its device. It is not a discoverable bus,
> that would explain power sequencing from the parent.
> 
>> Why of_clk_get() usage is a problem here ? i can't get your point ?
> 
> Because it is not the API which device drivers should use. You should
> use clk_get or devm_clk_get.


ok, i will update this part using clk_get().

> 
> 
>>
>>> This looks like mixing clock provider in the clock consumer.
>>>
>>>> +		if (IS_ERR(clk)) {
>>>> +			dev_err(dev, "Can't get child clock\n");
>>>
>>> Syntax is always return dev_err_probe (or ret = dev_err_probe).
>>
>> ok
>>
>>>
>>>> +			ret = PTR_ERR(clk);
>>>> +			goto err_clk_release;
>>>> +		};
>>>> +
>>>> +		omm->child[omm->nb_child].clk = clk;
>>>> +		omm->nb_child++;
>>>> +	}
>>>> +
>>>> +	if (omm->nb_child != OMM_CHILD_NB) {
>>>> +		ret = -EINVAL;
>>>> +		goto err_clk_release;
>>>> +	}
>>>> +
>>>> +	platform_set_drvdata(pdev, omm);
>>>> +
>>>> +	pm_runtime_enable(dev);
>>>> +
>>>> +	/* check if OMM's resource access is granted */
>>>> +	ret = stm32_omm_check_access(dev, dev->of_node);
>>>> +	if (ret < 0 && ret != -EACCES)
>>>> +		goto err_clk_release;
>>>> +
>>>> +	if (!ret && child_access_granted == OMM_CHILD_NB) {
>>>> +		/* Ensure both OSPI instance are disabled before configuring OMM */
>>>> +		ret = stm32_omm_disable_child(dev);
>>>> +		if (ret)
>>>> +			goto err_clk_release;
>>>> +
>>>> +		ret = stm32_omm_configure(dev);
>>>> +		if (ret)
>>>> +			goto err_clk_release;
>>>> +	} else {
>>>> +		dev_dbg(dev, "Octo Memory Manager resource's access not granted\n");
>>>> +		/*
>>>> +		 * AMCR can't be set, so check if current value is coherent
>>>> +		 * with memory-map areas defined in DT
>>>> +		 */
>>>> +		ret = stm32_omm_set_amcr(dev, false);
>>>> +		if (ret)
>>>> +			goto err_clk_release;
>>>> +	}
>>>> +
>>>> +	/* for each child, if resource access is granted and status "okay", probe it */
>>>> +	for (i = 0; i < omm->nb_child; i++) {
>>>> +		if (!child_access[i] || !of_device_is_available(omm->child[i].node))
>>>
>>> If you have a device available, why do you create one more platform device?
>>>
>>>> +			continue;
>>>> +
>>>> +		vdev = of_platform_device_create(omm->child[i].node, NULL, NULL);
>>>
>>> Why you cannot just populate the children?
>>
>> I can't use of_platform_populate(), by default it will populate all OMM's child.
>> Whereas here, we want to probe only the OMM's child which match our criteria.  
> 
> 
> Why wouldn't you populate everyone? The task of bus driver is not to
> filter out DT. If you got such DT - with all device nodes - you are
> expected to populate all of them. Otherwise, if you do not want all of
> them, it is expected that firmware or bootloader will give you DT
> without these nodes.

We don't want to populate every child by default because we can get 
cases where one child is shared between Cortex A and Cortex M.
That's why we must check if access is granted which ensure that 
firewall semaphore is available (RIFSC semaphore in our case).

Patrice

> 
> Best regards,
> Krzysztof


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver
  2025-03-18 13:40         ` Patrice CHOTARD
@ 2025-03-19  7:37           ` Krzysztof Kozlowski
  2025-03-20 13:28             ` Patrice CHOTARD
  0 siblings, 1 reply; 19+ messages in thread
From: Krzysztof Kozlowski @ 2025-03-19  7:37 UTC (permalink / raw)
  To: Patrice CHOTARD, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello

On 18/03/2025 14:40, Patrice CHOTARD wrote:
> 
> 
> On 3/13/25 08:33, Krzysztof Kozlowski wrote:
>> On 12/03/2025 15:23, Patrice CHOTARD wrote:
>>>>> +static int stm32_omm_disable_child(struct device *dev)
>>>>> +{
>>>>> +	struct stm32_omm *omm = dev_get_drvdata(dev);
>>>>> +	struct reset_control *reset;
>>>>> +	int ret;
>>>>> +	u8 i;
>>>>> +
>>>>> +	for (i = 0; i < omm->nb_child; i++) {
>>>>> +		ret = clk_prepare_enable(omm->child[i].clk);
>>>>> +		if (ret) {
>>>>> +			dev_err(dev, "Can not enable clock\n");
>>>>> +			return ret;
>>>>> +		}
>>>>> +
>>>>> +		reset = of_reset_control_get_exclusive(omm->child[i].node, 0);
>>>>> +		if (IS_ERR(reset)) {
>>>>> +			dev_err(dev, "Can't get child reset\n");
>>>>
>>>> Why do you get reset of child? Parent is not suppposed to poke there.
>>>> You might not have the reset there in the first place and it would not
>>>> be an error.
>>>
>>> By ressetting child (OSPI), we ensure they are disabled and in a known state.
>>> See the comment below.
>>>
>>>>
>>>>
>>>>> +			return PTR_ERR(reset);
>>>>> +		};
>>>>> +
>>>>> +		/* reset OSPI to ensure CR_EN bit is set to 0 */
>>>>> +		reset_control_assert(reset);
>>>>> +		udelay(2);
>>>>> +		reset_control_deassert(reset);
>>>>
>>>> No, the child should handle this, not parent.
>>>
>>> Octo Memory Manager can only be configured if both child are disabled.
>>> That's why here, parent handles this.
>>
>> So if device by any chance started and is doing some useful work, then
>> you cancel that work and reset it?
> 
> stm32_omm_configure() is only called if we get access granted on both children.
> That means we are authorized to use these devices, so we can reset them.
> 
>>
>> And what if child does not have reset line? Your binding allows that, so
>> how is it supposed to work then?
> 
> Ah yes, you are right, the OSPI bindings need to be updated
> by requiring reset lines and the driver spi-stm32-ospi.c as well.
> I will send a fix for that.
> 
> Thanks for pointing this.
> 
>>
>> This also leads me to questions about bindings - if you need to assert
>> some reset, doesn't it mean that these resets are also coming through
>> this device so they are part of this device node?
> 
> As we are able to retrieve children's reset from their respective node,
> if you don't mind, OMM bindings can be kept as it's currently.

But that is what the entire discussion is about - I do mind. I said it
already - you are not supposed to poke into child's node.

If you need to toggle child's resources, then I claim these are your
resources as well.

> 
> And another information, on some MP2 SoCs family, there is only one 
> OSPI instance. So for these SoCs, there is no Octo Memory Manager.
> 
>>
>>>
>>>>
>>>>> +
>>>>> +		reset_control_put(reset);
>>>>> +		clk_disable_unprepare(omm->child[i].clk);
>>>>> +	}
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static int stm32_omm_probe(struct platform_device *pdev)
>>>>> +{
>>>>> +	struct platform_device *vdev;
>>>>> +	struct device *dev = &pdev->dev;
>>>>> +	struct stm32_omm *omm;
>>>>> +	struct clk *clk;
>>>>> +	int ret;
>>>>> +	u8 child_access_granted = 0;
>>>>
>>>> Keep inits/assignments together
>>>
>>> ok
>>>
>>>>
>>>>> +	u8 i, j;
>>>>> +	bool child_access[OMM_CHILD_NB];
>>>>> +
>>>>> +	omm = devm_kzalloc(dev, sizeof(*omm), GFP_KERNEL);
>>>>> +	if (!omm)
>>>>> +		return -ENOMEM;
>>>>> +
>>>>> +	omm->io_base = devm_platform_ioremap_resource_byname(pdev, "regs");
>>>>> +	if (IS_ERR(omm->io_base))
>>>>> +		return PTR_ERR(omm->io_base);
>>>>> +
>>>>> +	omm->mm_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "memory_map");
>>>>> +	if (IS_ERR(omm->mm_res))
>>>>> +		return PTR_ERR(omm->mm_res);
>>>>> +
>>>>> +	/* check child's access */
>>>>> +	for_each_child_of_node_scoped(dev->of_node, child) {
>>>>> +		if (omm->nb_child >= OMM_CHILD_NB) {
>>>>> +			dev_err(dev, "Bad DT, found too much children\n");
>>>>> +			ret = -E2BIG;
>>>>> +			goto err_clk_release;
>>>>> +		}
>>>>> +
>>>>> +		if (!of_device_is_compatible(child, "st,stm32mp25-ospi")) {
>>>>> +			ret = -EINVAL;
>>>>> +			goto err_clk_release;
>>>>> +		}
>>>>> +
>>>>> +		ret = stm32_omm_check_access(dev, child);
>>>>> +		if (ret < 0 && ret != -EACCES)
>>>>> +			goto err_clk_release;
>>>>> +
>>>>> +		child_access[omm->nb_child] = false;
>>>>> +		if (!ret) {
>>>>> +			child_access_granted++;
>>>>> +			child_access[omm->nb_child] = true;
>>>>> +		}
>>>>> +
>>>>> +		omm->child[omm->nb_child].node = child;
>>>>> +
>>>>> +		clk = of_clk_get(child, 0);
>>>>
>>>> Why are you taking children clock? And why with this API, not clk_get?
>>>
>>> I need children's clock to reset them.
>>
>>
>> The device driver should reset its device. It is not a discoverable bus,
>> that would explain power sequencing from the parent.
>>
>>> Why of_clk_get() usage is a problem here ? i can't get your point ?
>>
>> Because it is not the API which device drivers should use. You should
>> use clk_get or devm_clk_get.
> 
> 
> ok, i will update this part using clk_get().
> 
>>
>>
>>>
>>>> This looks like mixing clock provider in the clock consumer.
>>>>
>>>>> +		if (IS_ERR(clk)) {
>>>>> +			dev_err(dev, "Can't get child clock\n");
>>>>
>>>> Syntax is always return dev_err_probe (or ret = dev_err_probe).
>>>
>>> ok
>>>
>>>>
>>>>> +			ret = PTR_ERR(clk);
>>>>> +			goto err_clk_release;
>>>>> +		};
>>>>> +
>>>>> +		omm->child[omm->nb_child].clk = clk;
>>>>> +		omm->nb_child++;
>>>>> +	}
>>>>> +
>>>>> +	if (omm->nb_child != OMM_CHILD_NB) {
>>>>> +		ret = -EINVAL;
>>>>> +		goto err_clk_release;
>>>>> +	}
>>>>> +
>>>>> +	platform_set_drvdata(pdev, omm);
>>>>> +
>>>>> +	pm_runtime_enable(dev);
>>>>> +
>>>>> +	/* check if OMM's resource access is granted */
>>>>> +	ret = stm32_omm_check_access(dev, dev->of_node);
>>>>> +	if (ret < 0 && ret != -EACCES)
>>>>> +		goto err_clk_release;
>>>>> +
>>>>> +	if (!ret && child_access_granted == OMM_CHILD_NB) {
>>>>> +		/* Ensure both OSPI instance are disabled before configuring OMM */
>>>>> +		ret = stm32_omm_disable_child(dev);
>>>>> +		if (ret)
>>>>> +			goto err_clk_release;
>>>>> +
>>>>> +		ret = stm32_omm_configure(dev);
>>>>> +		if (ret)
>>>>> +			goto err_clk_release;
>>>>> +	} else {
>>>>> +		dev_dbg(dev, "Octo Memory Manager resource's access not granted\n");
>>>>> +		/*
>>>>> +		 * AMCR can't be set, so check if current value is coherent
>>>>> +		 * with memory-map areas defined in DT
>>>>> +		 */
>>>>> +		ret = stm32_omm_set_amcr(dev, false);
>>>>> +		if (ret)
>>>>> +			goto err_clk_release;
>>>>> +	}
>>>>> +
>>>>> +	/* for each child, if resource access is granted and status "okay", probe it */
>>>>> +	for (i = 0; i < omm->nb_child; i++) {
>>>>> +		if (!child_access[i] || !of_device_is_available(omm->child[i].node))
>>>>
>>>> If you have a device available, why do you create one more platform device?
>>>>
>>>>> +			continue;
>>>>> +
>>>>> +		vdev = of_platform_device_create(omm->child[i].node, NULL, NULL);
>>>>
>>>> Why you cannot just populate the children?
>>>
>>> I can't use of_platform_populate(), by default it will populate all OMM's child.
>>> Whereas here, we want to probe only the OMM's child which match our criteria.  
>>
>>
>> Why wouldn't you populate everyone? The task of bus driver is not to
>> filter out DT. If you got such DT - with all device nodes - you are
>> expected to populate all of them. Otherwise, if you do not want all of
>> them, it is expected that firmware or bootloader will give you DT
>> without these nodes.
> 
> We don't want to populate every child by default because we can get 
> cases where one child is shared between Cortex A and Cortex M.

But in such case DTB would not have that child enabled.

> That's why we must check if access is granted which ensure that 
> firewall semaphore is available (RIFSC semaphore in our case).

If you do not have access, means child is assigned to other processor,
right? In that case that child would not have been enabled in your DTB.

Fix your DTB instead of creating another layer of handling children
inside drivers.


Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver
  2025-03-19  7:37           ` Krzysztof Kozlowski
@ 2025-03-20 13:28             ` Patrice CHOTARD
  0 siblings, 0 replies; 19+ messages in thread
From: Patrice CHOTARD @ 2025-03-20 13:28 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Alexandre Torgue, Philipp Zabel, Maxime Coquelin,
	Greg Kroah-Hartman, Arnd Bergmann, Catalin Marinas, Will Deacon
  Cc: linux-spi, devicetree, linux-stm32, linux-arm-kernel,
	linux-kernel, christophe.kerello



On 3/19/25 08:37, Krzysztof Kozlowski wrote:
> On 18/03/2025 14:40, Patrice CHOTARD wrote:
>>
>>
>> On 3/13/25 08:33, Krzysztof Kozlowski wrote:
>>> On 12/03/2025 15:23, Patrice CHOTARD wrote:
>>>>>> +static int stm32_omm_disable_child(struct device *dev)
>>>>>> +{
>>>>>> +	struct stm32_omm *omm = dev_get_drvdata(dev);
>>>>>> +	struct reset_control *reset;
>>>>>> +	int ret;
>>>>>> +	u8 i;
>>>>>> +
>>>>>> +	for (i = 0; i < omm->nb_child; i++) {
>>>>>> +		ret = clk_prepare_enable(omm->child[i].clk);
>>>>>> +		if (ret) {
>>>>>> +			dev_err(dev, "Can not enable clock\n");
>>>>>> +			return ret;
>>>>>> +		}
>>>>>> +
>>>>>> +		reset = of_reset_control_get_exclusive(omm->child[i].node, 0);
>>>>>> +		if (IS_ERR(reset)) {
>>>>>> +			dev_err(dev, "Can't get child reset\n");
>>>>>
>>>>> Why do you get reset of child? Parent is not suppposed to poke there.
>>>>> You might not have the reset there in the first place and it would not
>>>>> be an error.
>>>>
>>>> By ressetting child (OSPI), we ensure they are disabled and in a known state.
>>>> See the comment below.
>>>>
>>>>>
>>>>>
>>>>>> +			return PTR_ERR(reset);
>>>>>> +		};
>>>>>> +
>>>>>> +		/* reset OSPI to ensure CR_EN bit is set to 0 */
>>>>>> +		reset_control_assert(reset);
>>>>>> +		udelay(2);
>>>>>> +		reset_control_deassert(reset);
>>>>>
>>>>> No, the child should handle this, not parent.
>>>>
>>>> Octo Memory Manager can only be configured if both child are disabled.
>>>> That's why here, parent handles this.
>>>
>>> So if device by any chance started and is doing some useful work, then
>>> you cancel that work and reset it?
>>
>> stm32_omm_configure() is only called if we get access granted on both children.
>> That means we are authorized to use these devices, so we can reset them.
>>
>>>
>>> And what if child does not have reset line? Your binding allows that, so
>>> how is it supposed to work then?
>>
>> Ah yes, you are right, the OSPI bindings need to be updated
>> by requiring reset lines and the driver spi-stm32-ospi.c as well.
>> I will send a fix for that.
>>
>> Thanks for pointing this.
>>
>>>
>>> This also leads me to questions about bindings - if you need to assert
>>> some reset, doesn't it mean that these resets are also coming through
>>> this device so they are part of this device node?
>>
>> As we are able to retrieve children's reset from their respective node,
>> if you don't mind, OMM bindings can be kept as it's currently.
> 
> But that is what the entire discussion is about - I do mind. I said it
> already - you are not supposed to poke into child's node.
> 
> If you need to toggle child's resources, then I claim these are your
> resources as well.

Hi Krzysztof 

Ok i will update both OMM driver and bindings accordingly.

> 
>>
>> And another information, on some MP2 SoCs family, there is only one 
>> OSPI instance. So for these SoCs, there is no Octo Memory Manager.
>>
>>>
>>>>
>>>>>
>>>>>> +
>>>>>> +		reset_control_put(reset);
>>>>>> +		clk_disable_unprepare(omm->child[i].clk);
>>>>>> +	}
>>>>>> +
>>>>>> +	return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int stm32_omm_probe(struct platform_device *pdev)
>>>>>> +{
>>>>>> +	struct platform_device *vdev;
>>>>>> +	struct device *dev = &pdev->dev;
>>>>>> +	struct stm32_omm *omm;
>>>>>> +	struct clk *clk;
>>>>>> +	int ret;
>>>>>> +	u8 child_access_granted = 0;
>>>>>
>>>>> Keep inits/assignments together
>>>>
>>>> ok
>>>>
>>>>>
>>>>>> +	u8 i, j;
>>>>>> +	bool child_access[OMM_CHILD_NB];
>>>>>> +
>>>>>> +	omm = devm_kzalloc(dev, sizeof(*omm), GFP_KERNEL);
>>>>>> +	if (!omm)
>>>>>> +		return -ENOMEM;
>>>>>> +
>>>>>> +	omm->io_base = devm_platform_ioremap_resource_byname(pdev, "regs");
>>>>>> +	if (IS_ERR(omm->io_base))
>>>>>> +		return PTR_ERR(omm->io_base);
>>>>>> +
>>>>>> +	omm->mm_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "memory_map");
>>>>>> +	if (IS_ERR(omm->mm_res))
>>>>>> +		return PTR_ERR(omm->mm_res);
>>>>>> +
>>>>>> +	/* check child's access */
>>>>>> +	for_each_child_of_node_scoped(dev->of_node, child) {
>>>>>> +		if (omm->nb_child >= OMM_CHILD_NB) {
>>>>>> +			dev_err(dev, "Bad DT, found too much children\n");
>>>>>> +			ret = -E2BIG;
>>>>>> +			goto err_clk_release;
>>>>>> +		}
>>>>>> +
>>>>>> +		if (!of_device_is_compatible(child, "st,stm32mp25-ospi")) {
>>>>>> +			ret = -EINVAL;
>>>>>> +			goto err_clk_release;
>>>>>> +		}
>>>>>> +
>>>>>> +		ret = stm32_omm_check_access(dev, child);
>>>>>> +		if (ret < 0 && ret != -EACCES)
>>>>>> +			goto err_clk_release;
>>>>>> +
>>>>>> +		child_access[omm->nb_child] = false;
>>>>>> +		if (!ret) {
>>>>>> +			child_access_granted++;
>>>>>> +			child_access[omm->nb_child] = true;
>>>>>> +		}
>>>>>> +
>>>>>> +		omm->child[omm->nb_child].node = child;
>>>>>> +
>>>>>> +		clk = of_clk_get(child, 0);
>>>>>
>>>>> Why are you taking children clock? And why with this API, not clk_get?
>>>>
>>>> I need children's clock to reset them.
>>>
>>>
>>> The device driver should reset its device. It is not a discoverable bus,
>>> that would explain power sequencing from the parent.
>>>
>>>> Why of_clk_get() usage is a problem here ? i can't get your point ?
>>>
>>> Because it is not the API which device drivers should use. You should
>>> use clk_get or devm_clk_get.
>>
>>
>> ok, i will update this part using clk_get().
>>
>>>
>>>
>>>>
>>>>> This looks like mixing clock provider in the clock consumer.
>>>>>
>>>>>> +		if (IS_ERR(clk)) {
>>>>>> +			dev_err(dev, "Can't get child clock\n");
>>>>>
>>>>> Syntax is always return dev_err_probe (or ret = dev_err_probe).
>>>>
>>>> ok
>>>>
>>>>>
>>>>>> +			ret = PTR_ERR(clk);
>>>>>> +			goto err_clk_release;
>>>>>> +		};
>>>>>> +
>>>>>> +		omm->child[omm->nb_child].clk = clk;
>>>>>> +		omm->nb_child++;
>>>>>> +	}
>>>>>> +
>>>>>> +	if (omm->nb_child != OMM_CHILD_NB) {
>>>>>> +		ret = -EINVAL;
>>>>>> +		goto err_clk_release;
>>>>>> +	}
>>>>>> +
>>>>>> +	platform_set_drvdata(pdev, omm);
>>>>>> +
>>>>>> +	pm_runtime_enable(dev);
>>>>>> +
>>>>>> +	/* check if OMM's resource access is granted */
>>>>>> +	ret = stm32_omm_check_access(dev, dev->of_node);
>>>>>> +	if (ret < 0 && ret != -EACCES)
>>>>>> +		goto err_clk_release;
>>>>>> +
>>>>>> +	if (!ret && child_access_granted == OMM_CHILD_NB) {
>>>>>> +		/* Ensure both OSPI instance are disabled before configuring OMM */
>>>>>> +		ret = stm32_omm_disable_child(dev);
>>>>>> +		if (ret)
>>>>>> +			goto err_clk_release;
>>>>>> +
>>>>>> +		ret = stm32_omm_configure(dev);
>>>>>> +		if (ret)
>>>>>> +			goto err_clk_release;
>>>>>> +	} else {
>>>>>> +		dev_dbg(dev, "Octo Memory Manager resource's access not granted\n");
>>>>>> +		/*
>>>>>> +		 * AMCR can't be set, so check if current value is coherent
>>>>>> +		 * with memory-map areas defined in DT
>>>>>> +		 */
>>>>>> +		ret = stm32_omm_set_amcr(dev, false);
>>>>>> +		if (ret)
>>>>>> +			goto err_clk_release;
>>>>>> +	}
>>>>>> +
>>>>>> +	/* for each child, if resource access is granted and status "okay", probe it */
>>>>>> +	for (i = 0; i < omm->nb_child; i++) {
>>>>>> +		if (!child_access[i] || !of_device_is_available(omm->child[i].node))
>>>>>
>>>>> If you have a device available, why do you create one more platform device?
>>>>>
>>>>>> +			continue;
>>>>>> +
>>>>>> +		vdev = of_platform_device_create(omm->child[i].node, NULL, NULL);
>>>>>
>>>>> Why you cannot just populate the children?
>>>>
>>>> I can't use of_platform_populate(), by default it will populate all OMM's child.
>>>> Whereas here, we want to probe only the OMM's child which match our criteria.  
>>>
>>>
>>> Why wouldn't you populate everyone? The task of bus driver is not to
>>> filter out DT. If you got such DT - with all device nodes - you are
>>> expected to populate all of them. Otherwise, if you do not want all of
>>> them, it is expected that firmware or bootloader will give you DT
>>> without these nodes.
>>
>> We don't want to populate every child by default because we can get 
>> cases where one child is shared between Cortex A and Cortex M.
> 
> But in such case DTB would not have that child enabled.
> 
>> That's why we must check if access is granted which ensure that 
>> firewall semaphore is available (RIFSC semaphore in our case).
> 
> If you do not have access, means child is assigned to other processor,
> right? In that case that child would not have been enabled in your DTB.
> 
> Fix your DTB instead of creating another layer of handling children
> inside drivers.

In fact, initially, we wanted to avoid to trigger Illegal Access in case user 
didn't use correct DT, that's why, here, double checks have been implemented.
But Ok, i will clean this part and simply populate children.

Thanks
Patrice.

> 
> 
> Best regards,
> Krzysztof


^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2025-03-20 13:35 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-02-19  8:00 [PATCH v5 0/8] Add STM32MP25 SPI NOR support patrice.chotard
2025-02-19  8:00 ` [PATCH v5 1/8] dt-bindings: spi: Add STM32 OSPI controller patrice.chotard
2025-02-19  8:00 ` [PATCH v5 2/8] spi: stm32: Add OSPI driver patrice.chotard
2025-02-19  8:00 ` [PATCH v5 3/8] dt-bindings: memory-controllers: Add STM32 Octo Memory Manager controller patrice.chotard
2025-02-19  8:00 ` [PATCH v5 4/8] memory: Add STM32 Octo Memory Manager driver patrice.chotard
2025-03-10 13:52   ` Patrice CHOTARD
2025-03-10 14:10     ` Krzysztof Kozlowski
2025-03-13  7:30     ` Krzysztof Kozlowski
2025-03-11 16:04   ` Krzysztof Kozlowski
2025-03-12 14:23     ` Patrice CHOTARD
2025-03-13  7:33       ` Krzysztof Kozlowski
2025-03-18 13:40         ` Patrice CHOTARD
2025-03-19  7:37           ` Krzysztof Kozlowski
2025-03-20 13:28             ` Patrice CHOTARD
2025-02-19  8:00 ` [PATCH v5 5/8] arm64: dts: st: Add OMM node on stm32mp251 patrice.chotard
2025-02-19  8:00 ` [PATCH v5 6/8] arm64: dts: st: Add ospi port1 pinctrl entries in stm32mp25-pinctrl.dtsi patrice.chotard
2025-02-19  8:00 ` [PATCH v5 7/8] arm64: dts: st: Add SPI NOR flash support on stm32mp257f-ev1 board patrice.chotard
2025-02-19  8:00 ` [PATCH v5 8/8] arm64: defconfig: Enable STM32 Octo Memory Manager and OcstoSPI driver patrice.chotard
2025-03-04 13:39 ` (subset) [PATCH v5 0/8] Add STM32MP25 SPI NOR support Mark Brown

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).