forked from OERV-BSP/u-boot
Merge tag 'u-boot-stm32-20250919' of https://source.denx.de/u-boot/custodians/u-boot-stm into next
CI: - https://source.denx.de/u-boot/custodians/u-boot-stm/-/pipelines/27668 STM32MP2: - Add SPI flashes support - Add RIFSC system bus driver fixes
This commit is contained in:
@@ -709,6 +709,7 @@ F: drivers/gpio/stm32_gpio.c
|
||||
F: drivers/hwspinlock/stm32_hwspinlock.c
|
||||
F: drivers/i2c/stm32f7_i2c.c
|
||||
F: drivers/mailbox/stm32-ipcc.c
|
||||
F: drivers/memory/stm32-omm.c
|
||||
F: drivers/misc/stm32mp_fuse.c
|
||||
F: drivers/misc/stm32_rcc.c
|
||||
F: drivers/mmc/stm32_sdmmc2.c
|
||||
@@ -725,6 +726,7 @@ F: drivers/rng/optee_rng.c
|
||||
F: drivers/rng/stm32_rng.c
|
||||
F: drivers/rtc/stm32_rtc.c
|
||||
F: drivers/serial/serial_stm32.*
|
||||
F: drivers/spi/stm32_ospi.c
|
||||
F: drivers/spi/stm32_qspi.c
|
||||
F: drivers/spi/stm32_spi.c
|
||||
F: drivers/video/stm32/stm32_ltdc.c
|
||||
|
||||
@@ -12,6 +12,47 @@
|
||||
};
|
||||
};
|
||||
|
||||
&flash0 {
|
||||
partitions {
|
||||
compatible = "fixed-partitions";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
partition@0 {
|
||||
label = "fsbla1";
|
||||
reg = <0x00000000 0x00040000>;
|
||||
};
|
||||
partition@40000 {
|
||||
label = "fsbla2";
|
||||
reg = <0x00040000 0x00040000>;
|
||||
};
|
||||
partition@80000 {
|
||||
label = "metadata1";
|
||||
reg = <0x00080000 0x00040000>;
|
||||
};
|
||||
partition@C0000 {
|
||||
label = "metadata2";
|
||||
reg = <0x000C0000 0x00040000>;
|
||||
};
|
||||
partition@100000 {
|
||||
label = "fip-a";
|
||||
reg = <0x00100000 0x00400000>;
|
||||
};
|
||||
partition@500000 {
|
||||
label = "fip-b";
|
||||
reg = <0x00500000 0x00400000>;
|
||||
};
|
||||
partition@900000 {
|
||||
label = "u-boot-env";
|
||||
reg = <0x00900000 0x00080000>;
|
||||
};
|
||||
partition@980000 {
|
||||
label = "nor-user";
|
||||
reg = <0x00980000 0x03680000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&usart2 {
|
||||
bootph-all;
|
||||
};
|
||||
|
||||
@@ -8,19 +8,53 @@
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#if IS_ENABLED(CONFIG_STM32MP21X) || IS_ENABLED(CONFIG_STM32MP23X) || IS_ENABLED(CONFIG_STM32MP25X)
|
||||
/**
|
||||
* stm32_rifsc_check_access - Check RIF accesses for given device node
|
||||
* stm32_rifsc_grant_access_by_id - Grant RIFSC access for a given peripheral using its ID
|
||||
*
|
||||
* @device_node Node of the device for which the accesses are checked
|
||||
* @device_node Node of the peripheral
|
||||
* @id ID of the peripheral of which access should be granted
|
||||
*/
|
||||
int stm32_rifsc_check_access(ofnode device_node);
|
||||
int stm32_rifsc_grant_access_by_id(ofnode device_node, u32 id);
|
||||
|
||||
/**
|
||||
* stm32_rifsc_check_access - Check RIF accesses for given id
|
||||
* stm32_rifsc_grant_access_by_id - Grant RIFSC access for a given peripheral using its node
|
||||
*
|
||||
* @device_node Node of the device to get a reference on RIFSC
|
||||
* @id ID of the resource to check
|
||||
* @id node of the peripheral of which access should be granted
|
||||
*/
|
||||
int stm32_rifsc_check_access_by_id(ofnode device_node, u32 id);
|
||||
int stm32_rifsc_grant_access(ofnode device_node);
|
||||
|
||||
/**
|
||||
* stm32_rifsc_release_access_by_id - Release RIFSC access for a given peripheral using its ID
|
||||
*
|
||||
* @device_node Node of the peripheral
|
||||
* @id ID of the peripheral of which access should be released
|
||||
*/
|
||||
void stm32_rifsc_release_access_by_id(ofnode device_node, u32 id);
|
||||
|
||||
/**
|
||||
* stm32_rifsc_release_access_by_id - Release RIFSC access for a given peripheral using its node
|
||||
*
|
||||
* @id node of the peripheral of which access should be released
|
||||
*/
|
||||
void stm32_rifsc_release_access(ofnode device_node);
|
||||
#else
|
||||
static inline int stm32_rifsc_grant_access_by_id(ofnode device_node, u32 id)
|
||||
{
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
static inline int stm32_rifsc_grant_access(ofnode device_node)
|
||||
{
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
static inline void stm32_rifsc_release_access_by_id(ofnode device_node, u32 id)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void stm32_rifsc_release_access(ofnode device_node)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
#endif /* MACH_RIF_H*/
|
||||
|
||||
@@ -61,42 +61,41 @@ struct stm32_rifsc_child_plat {
|
||||
u32 domain_id;
|
||||
};
|
||||
|
||||
static bool stm32_rif_is_semaphore_available(void *base, u32 id)
|
||||
static bool stm32_rif_is_semaphore_available(void *addr)
|
||||
{
|
||||
void *addr = base + RIFSC_RISC_PER0_SEMCR(id);
|
||||
|
||||
return !(readl(addr) & SEMCR_MUTEX);
|
||||
}
|
||||
|
||||
static int stm32_rif_acquire_semaphore(void *base, u32 id)
|
||||
static int stm32_rifsc_acquire_semaphore(void *base, u32 id)
|
||||
{
|
||||
void *addr = base + RIFSC_RISC_PER0_SEMCR(id);
|
||||
|
||||
/* Check that the semaphore is available */
|
||||
if (!stm32_rif_is_semaphore_available(base, id))
|
||||
if (!stm32_rif_is_semaphore_available(addr) &&
|
||||
FIELD_GET(RIFSC_RISC_SCID_MASK, (readl(addr)) != RIF_CID1))
|
||||
return -EACCES;
|
||||
|
||||
setbits_le32(addr, SEMCR_MUTEX);
|
||||
|
||||
/* Check that CID1 has the semaphore */
|
||||
if (stm32_rif_is_semaphore_available(base, id) ||
|
||||
if (stm32_rif_is_semaphore_available(addr) ||
|
||||
FIELD_GET(RIFSC_RISC_SCID_MASK, (readl(addr)) != RIF_CID1))
|
||||
return -EACCES;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_rif_release_semaphore(void *base, u32 id)
|
||||
static int stm32_rifsc_release_semaphore(void *base, u32 id)
|
||||
{
|
||||
void *addr = base + RIFSC_RISC_PER0_SEMCR(id);
|
||||
|
||||
if (stm32_rif_is_semaphore_available(base, id))
|
||||
if (stm32_rif_is_semaphore_available(addr))
|
||||
return 0;
|
||||
|
||||
clrbits_le32(addr, SEMCR_MUTEX);
|
||||
|
||||
/* Ok if another compartment takes the semaphore before the check */
|
||||
if (!stm32_rif_is_semaphore_available(base, id) &&
|
||||
if (!stm32_rif_is_semaphore_available(addr) &&
|
||||
FIELD_GET(RIFSC_RISC_SCID_MASK, (readl(addr)) == RIF_CID1))
|
||||
return -EACCES;
|
||||
|
||||
@@ -105,11 +104,10 @@ static int stm32_rif_release_semaphore(void *base, u32 id)
|
||||
|
||||
static int rifsc_parse_access_controller(ofnode node, struct ofnode_phandle_args *args)
|
||||
{
|
||||
int ret;
|
||||
int ret = ofnode_parse_phandle_with_args(node, "access-controllers",
|
||||
"#access-controller-cells", 0,
|
||||
0, args);
|
||||
|
||||
ret = ofnode_parse_phandle_with_args(node, "access-controllers",
|
||||
"#access-controller-cells", 0,
|
||||
0, args);
|
||||
if (ret) {
|
||||
log_debug("failed to parse access-controller (%d)\n", ret);
|
||||
return ret;
|
||||
@@ -170,8 +168,8 @@ static int rifsc_check_access(void *base, u32 id)
|
||||
log_debug("Not in semaphore whitelist for peripheral %d\n", id);
|
||||
return -EACCES;
|
||||
}
|
||||
if (!stm32_rif_is_semaphore_available(base, id) &&
|
||||
!(FIELD_GET(RIFSC_RISC_SCID_MASK, sem_reg_value) & BIT(RIF_CID1))) {
|
||||
if (!stm32_rif_is_semaphore_available(base + RIFSC_RISC_PER0_SEMCR(id)) &&
|
||||
!(FIELD_GET(RIFSC_RISC_SCID_MASK, sem_reg_value) & RIF_CID1)) {
|
||||
log_debug("Semaphore unavailable for peripheral %d\n", id);
|
||||
return -EACCES;
|
||||
}
|
||||
@@ -187,42 +185,24 @@ skip_cid_check:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stm32_rifsc_check_access_by_id(ofnode device_node, u32 id)
|
||||
int stm32_rifsc_grant_access_by_id(ofnode device_node, u32 id)
|
||||
{
|
||||
struct ofnode_phandle_args args;
|
||||
int err;
|
||||
|
||||
if (id >= STM32MP25_RIFSC_ENTRIES)
|
||||
return -EINVAL;
|
||||
|
||||
err = rifsc_parse_access_controller(device_node, &args);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return rifsc_check_access((void *)ofnode_get_addr(args.node), id);
|
||||
}
|
||||
|
||||
int stm32_rifsc_check_access(ofnode device_node)
|
||||
{
|
||||
struct ofnode_phandle_args args;
|
||||
int err;
|
||||
|
||||
err = rifsc_parse_access_controller(device_node, &args);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return rifsc_check_access((void *)ofnode_get_addr(args.node), args.args[0]);
|
||||
}
|
||||
|
||||
static int stm32_rifsc_child_pre_probe(struct udevice *dev)
|
||||
{
|
||||
struct stm32_rifsc_plat *plat = dev_get_plat(dev->parent);
|
||||
struct stm32_rifsc_child_plat *child_plat = dev_get_parent_plat(dev);
|
||||
u32 cid_reg_value;
|
||||
void *rifsc_base;
|
||||
int err;
|
||||
u32 id = child_plat->domain_id;
|
||||
|
||||
cid_reg_value = readl(plat->base + RIFSC_RISC_PER0_CIDCFGR(id));
|
||||
err = rifsc_parse_access_controller(device_node, &args);
|
||||
if (err)
|
||||
panic("Failed to parse access-controllers property\n");
|
||||
|
||||
rifsc_base = (void *)ofnode_get_addr(args.node);
|
||||
|
||||
err = rifsc_check_access(rifsc_base, id);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
cid_reg_value = readl(rifsc_base + RIFSC_RISC_PER0_CIDCFGR(id));
|
||||
|
||||
/*
|
||||
* If the peripheral is in semaphore mode, take the semaphore so that
|
||||
@@ -230,39 +210,81 @@ static int stm32_rifsc_child_pre_probe(struct udevice *dev)
|
||||
*/
|
||||
if (cid_reg_value & CIDCFGR_SEMEN &&
|
||||
(FIELD_GET(RIFSC_RISC_SEMWL_MASK, cid_reg_value) & BIT(RIF_CID1))) {
|
||||
err = stm32_rif_acquire_semaphore(plat->base, id);
|
||||
err = stm32_rifsc_acquire_semaphore(rifsc_base, id);
|
||||
if (err) {
|
||||
dev_err(dev, "Couldn't acquire RIF semaphore for peripheral %d (%d)\n",
|
||||
id, err);
|
||||
pr_err("Couldn't acquire RIF semaphore for peripheral %d (%d)\n",
|
||||
id, err);
|
||||
return err;
|
||||
}
|
||||
dev_dbg(dev, "Acquiring semaphore for peripheral %d\n", id);
|
||||
pr_debug("Acquiring RIF semaphore for peripheral %d\n", id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_rifsc_child_post_remove(struct udevice *dev)
|
||||
int stm32_rifsc_grant_access(ofnode device_node)
|
||||
{
|
||||
struct stm32_rifsc_plat *plat = dev_get_plat(dev->parent);
|
||||
struct stm32_rifsc_child_plat *child_plat = dev_get_parent_plat(dev);
|
||||
u32 cid_reg_value;
|
||||
struct ofnode_phandle_args args;
|
||||
int err;
|
||||
u32 id = child_plat->domain_id;
|
||||
|
||||
cid_reg_value = readl(plat->base + RIFSC_RISC_PER0_CIDCFGR(id));
|
||||
err = rifsc_parse_access_controller(device_node, &args);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* If the peripheral is in semaphore mode, release the semaphore so that
|
||||
* there's no ownership.
|
||||
*/
|
||||
return stm32_rifsc_grant_access_by_id(device_node, args.args[0]);
|
||||
|
||||
}
|
||||
|
||||
void stm32_rifsc_release_access_by_id(ofnode device_node, u32 id)
|
||||
{
|
||||
struct ofnode_phandle_args args;
|
||||
u32 cid_reg_value;
|
||||
void *rifsc_base;
|
||||
int err;
|
||||
|
||||
err = rifsc_parse_access_controller(device_node, &args);
|
||||
if (err)
|
||||
panic("Failed to parse access-controllers property\n");
|
||||
|
||||
rifsc_base = (void *)ofnode_get_addr(args.node);
|
||||
|
||||
cid_reg_value = readl(rifsc_base + RIFSC_RISC_PER0_CIDCFGR(id));
|
||||
|
||||
/* If the peripheral is in semaphore mode, release it if we have the ownership */
|
||||
if (cid_reg_value & CIDCFGR_SEMEN &&
|
||||
(FIELD_GET(RIFSC_RISC_SEMWL_MASK, cid_reg_value) & BIT(RIF_CID1))) {
|
||||
err = stm32_rif_release_semaphore(plat->base, id);
|
||||
if (err)
|
||||
dev_err(dev, "Couldn't release rif semaphore for peripheral %d (%d)\n",
|
||||
id, err);
|
||||
err = stm32_rifsc_release_semaphore(rifsc_base, id);
|
||||
if (err) {
|
||||
panic("Couldn't release RIF semaphore for peripheral %d (%d)\n", id, err);
|
||||
}
|
||||
pr_debug("Releasing RIF semaphore for peripheral %d\n", id);
|
||||
}
|
||||
}
|
||||
|
||||
void stm32_rifsc_release_access(ofnode device_node)
|
||||
{
|
||||
struct ofnode_phandle_args args;
|
||||
int err;
|
||||
|
||||
err = rifsc_parse_access_controller(device_node, &args);
|
||||
if (err)
|
||||
panic("Failed to parse access-controllers property\n");
|
||||
|
||||
stm32_rifsc_release_access_by_id(device_node, args.args[0]);
|
||||
}
|
||||
|
||||
static int stm32_rifsc_child_pre_probe(struct udevice *dev)
|
||||
{
|
||||
struct stm32_rifsc_child_plat *child_plat = dev_get_parent_plat(dev);
|
||||
|
||||
return stm32_rifsc_grant_access_by_id(dev_ofnode(dev), child_plat->domain_id);
|
||||
}
|
||||
|
||||
static int stm32_rifsc_child_post_remove(struct udevice *dev)
|
||||
{
|
||||
struct stm32_rifsc_child_plat *child_plat = dev_get_parent_plat(dev);
|
||||
|
||||
stm32_rifsc_release_access_by_id(dev_ofnode(dev), child_plat->domain_id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ CONFIG_CMD_GPIO=y
|
||||
CONFIG_CMD_PWM=y
|
||||
# CONFIG_CMD_LOADB is not set
|
||||
CONFIG_CMD_MMC=y
|
||||
CONFIG_CMD_MTD=y
|
||||
CONFIG_CMD_CACHE=y
|
||||
CONFIG_CMD_TIME=y
|
||||
CONFIG_CMD_RNG=y
|
||||
@@ -61,11 +62,22 @@ CONFIG_DM_I2C=y
|
||||
CONFIG_SYS_I2C_STM32F7=y
|
||||
CONFIG_LED=y
|
||||
CONFIG_LED_GPIO=y
|
||||
CONFIG_STM32_OMM=y
|
||||
CONFIG_SUPPORT_EMMC_BOOT=y
|
||||
CONFIG_STM32_SDMMC2=y
|
||||
CONFIG_MTD=y
|
||||
CONFIG_DM_MTD=y
|
||||
CONFIG_USE_SYS_MAX_FLASH_BANKS=y
|
||||
CONFIG_SPI_FLASH=y
|
||||
CONFIG_MTD_SPI_NAND=y
|
||||
CONFIG_DM_SPI_FLASH=y
|
||||
CONFIG_SPI_FLASH_SFDP_SUPPORT=y
|
||||
CONFIG_SPI_FLASH_SOFT_RESET=y
|
||||
CONFIG_SPI_FLASH_MACRONIX=y
|
||||
CONFIG_SPI_FLASH_SPANSION=y
|
||||
CONFIG_SPI_FLASH_STMICRO=y
|
||||
CONFIG_SPI_FLASH_WINBOND=y
|
||||
# CONFIG_SPI_FLASH_USE_4K_SECTORS is not set
|
||||
CONFIG_SPI_FLASH_MTD=y
|
||||
CONFIG_PINCONF=y
|
||||
CONFIG_DM_REGULATOR_FIXED=y
|
||||
CONFIG_DM_REGULATOR_GPIO=y
|
||||
@@ -77,6 +89,7 @@ CONFIG_DM_RNG=y
|
||||
CONFIG_SERIAL_RX_BUFFER=y
|
||||
CONFIG_SPI=y
|
||||
CONFIG_DM_SPI=y
|
||||
CONFIG_STM32_OSPI=y
|
||||
# CONFIG_OPTEE_TA_AVB is not set
|
||||
CONFIG_WDT=y
|
||||
CONFIG_WDT_STM32MP=y
|
||||
|
||||
@@ -430,7 +430,7 @@ static int stm32mp25_check_security(struct udevice *dev, void __iomem *base,
|
||||
u32 index = (u32)cfg->sec_id;
|
||||
|
||||
if (index & SEC_RIFSC_FLAG)
|
||||
ret = stm32_rifsc_check_access_by_id(dev_ofnode(dev),
|
||||
ret = stm32_rifsc_grant_access_by_id(dev_ofnode(dev),
|
||||
index & ~SEC_RIFSC_FLAG);
|
||||
else
|
||||
ret = stm32_rcc_get_access(dev, index);
|
||||
|
||||
@@ -37,6 +37,23 @@ config STM32_FMC2_EBI
|
||||
devices (like SRAM, ethernet adapters, FPGAs, LCD displays, ...) on
|
||||
SOCs containing the FMC2 External Bus Interface.
|
||||
|
||||
config STM32_OMM
|
||||
bool "STM32 Octo Memory Manager"
|
||||
depends on ARCH_STM32MP
|
||||
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.
|
||||
|
||||
config TI_AEMIF
|
||||
tristate "Texas Instruments AEMIF driver"
|
||||
depends on ARCH_KEYSTONE || ARCH_DAVINCI
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
obj-$(CONFIG_MEMORY) += memory-uclass.o
|
||||
obj-$(CONFIG_SANDBOX_MEMORY) += memory-sandbox.o
|
||||
obj-$(CONFIG_STM32_FMC2_EBI) += stm32-fmc2-ebi.o
|
||||
obj-$(CONFIG_STM32_OMM) += stm32_omm.o
|
||||
obj-$(CONFIG_ATMEL_EBI) += atmel_ebi.o
|
||||
obj-$(CONFIG_TI_AEMIF) += ti-aemif.o ti-aemif-cs.o
|
||||
obj-$(CONFIG_TI_GPMC) += ti-gpmc.o
|
||||
|
||||
421
drivers/memory/stm32_omm.c
Normal file
421
drivers/memory/stm32_omm.c
Normal file
@@ -0,0 +1,421 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later OR BSD-3-Clause
|
||||
/*
|
||||
* Copyright (C) 2025, STMicroelectronics - All Rights Reserved
|
||||
*/
|
||||
|
||||
#define LOG_CATEGORY UCLASS_NOP
|
||||
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <regmap.h>
|
||||
#include <reset.h>
|
||||
#include <syscon.h>
|
||||
#include <asm/io.h>
|
||||
#include <dm/device_compat.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dm/of_addr.h>
|
||||
#include <dm/of_access.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <mach/rif.h>
|
||||
|
||||
/* OCTOSPI control register */
|
||||
#define OCTOSPIM_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
|
||||
#define OMM_CLK_NB 3
|
||||
#define OMM_RESET_NB 3
|
||||
#define NSEC_PER_SEC 1000000000L
|
||||
|
||||
struct stm32_omm_plat {
|
||||
phys_addr_t regs_base;
|
||||
struct regmap *syscfg_regmap;
|
||||
struct clk clk[OMM_CLK_NB];
|
||||
struct reset_ctl reset_ctl[OMM_RESET_NB];
|
||||
resource_size_t mm_ospi2_size;
|
||||
u32 mux;
|
||||
u32 cssel_ovr;
|
||||
u32 req2ack;
|
||||
u32 amcr_base;
|
||||
u32 amcr_mask;
|
||||
unsigned long clk_rate_max;
|
||||
u8 nb_child;
|
||||
};
|
||||
|
||||
static int stm32_omm_set_amcr(struct udevice *dev, bool set)
|
||||
{
|
||||
struct stm32_omm_plat *plat = dev_get_plat(dev);
|
||||
unsigned int amcr, read_amcr;
|
||||
|
||||
amcr = plat->mm_ospi2_size / SZ_64M;
|
||||
|
||||
if (set)
|
||||
regmap_update_bits(plat->syscfg_regmap, plat->amcr_base,
|
||||
plat->amcr_mask, amcr);
|
||||
|
||||
/* read AMCR and check coherency with memory-map areas defined in DT */
|
||||
regmap_read(plat->syscfg_regmap, plat->amcr_base, &read_amcr);
|
||||
read_amcr = read_amcr >> (ffs(plat->amcr_mask) - 1);
|
||||
|
||||
if (amcr != read_amcr) {
|
||||
dev_err(dev, "AMCR value not coherent with DT memory-map areas\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_omm_toggle_child_clock(struct udevice *dev, bool enable)
|
||||
{
|
||||
struct stm32_omm_plat *plat = dev_get_plat(dev);
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < plat->nb_child; i++) {
|
||||
if (enable) {
|
||||
ret = clk_enable(&plat->clk[i + 1]);
|
||||
if (ret) {
|
||||
dev_err(dev, "Can not enable clock\n");
|
||||
goto clk_error;
|
||||
}
|
||||
} else {
|
||||
clk_disable(&plat->clk[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
clk_error:
|
||||
while (i--)
|
||||
clk_disable(&plat->clk[i + 1]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_omm_disable_child(struct udevice *dev)
|
||||
{
|
||||
struct stm32_omm_plat *plat = dev_get_plat(dev);
|
||||
int ret;
|
||||
u8 i;
|
||||
|
||||
ret = stm32_omm_toggle_child_clock(dev, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < plat->nb_child; i++) {
|
||||
/* reset OSPI to ensure CR_EN bit is set to 0 */
|
||||
reset_assert(&plat->reset_ctl[i + 1]);
|
||||
udelay(2);
|
||||
reset_deassert(&plat->reset_ctl[i + 1]);
|
||||
}
|
||||
|
||||
return stm32_omm_toggle_child_clock(dev, false);
|
||||
}
|
||||
|
||||
static int stm32_omm_configure(struct udevice *dev)
|
||||
{
|
||||
struct stm32_omm_plat *plat = dev_get_plat(dev);
|
||||
int ret;
|
||||
u32 mux = 0;
|
||||
u32 cssel_ovr = 0;
|
||||
u32 req2ack = 0;
|
||||
|
||||
/* Ensure both OSPI instance are disabled before configuring OMM */
|
||||
ret = stm32_omm_disable_child(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_enable(&plat->clk[0]);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to enable OMM clock (%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
reset_assert(&plat->reset_ctl[0]);
|
||||
udelay(2);
|
||||
reset_deassert(&plat->reset_ctl[0]);
|
||||
|
||||
if (plat->mux & CR_MUXEN) {
|
||||
if (plat->req2ack) {
|
||||
req2ack = DIV_ROUND_UP(plat->req2ack,
|
||||
NSEC_PER_SEC / plat->clk_rate_max) - 1;
|
||||
if (req2ack > 256)
|
||||
req2ack = 256;
|
||||
}
|
||||
|
||||
req2ack = FIELD_PREP(CR_REQ2ACK_MASK, req2ack);
|
||||
clrsetbits_le32(plat->regs_base + OCTOSPIM_CR, CR_REQ2ACK_MASK,
|
||||
req2ack);
|
||||
|
||||
/*
|
||||
* If the mux is enabled, the 2 OSPI clocks have to be
|
||||
* always enabled
|
||||
*/
|
||||
ret = stm32_omm_toggle_child_clock(dev, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (plat->cssel_ovr != 0xff) {
|
||||
cssel_ovr = FIELD_PREP(CR_CSSEL_OVR_MASK, cssel_ovr);
|
||||
cssel_ovr |= CR_CSSEL_OVR_EN;
|
||||
clrsetbits_le32(plat->regs_base + OCTOSPIM_CR, CR_CSSEL_OVR_MASK,
|
||||
cssel_ovr);
|
||||
}
|
||||
|
||||
mux = FIELD_PREP(CR_MUXENMODE_MASK, plat->mux);
|
||||
clrsetbits_le32(plat->regs_base + OCTOSPIM_CR, CR_MUXENMODE_MASK, mux);
|
||||
clk_disable(&plat->clk[0]);
|
||||
|
||||
return stm32_omm_set_amcr(dev, true);
|
||||
}
|
||||
|
||||
static void stm32_omm_release_childs(ofnode *child_list, u8 nb_child)
|
||||
{
|
||||
u8 i;
|
||||
|
||||
for (i = 0; i < nb_child; i++)
|
||||
stm32_rifsc_release_access(child_list[i]);
|
||||
}
|
||||
|
||||
static int stm32_omm_probe(struct udevice *dev)
|
||||
{
|
||||
struct stm32_omm_plat *plat = dev_get_plat(dev);
|
||||
ofnode child_list[OMM_CHILD_NB];
|
||||
ofnode child;
|
||||
int ret;
|
||||
u8 child_access_granted = 0;
|
||||
bool child_access[OMM_CHILD_NB];
|
||||
|
||||
/* check child's access */
|
||||
for (child = ofnode_first_subnode(dev_ofnode(dev));
|
||||
ofnode_valid(child);
|
||||
child = ofnode_next_subnode(child)) {
|
||||
if (plat->nb_child > OMM_CHILD_NB) {
|
||||
dev_err(dev, "Bad DT, found too much children\n");
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
if (!ofnode_device_is_compatible(child, "st,stm32mp25-ospi"))
|
||||
return -EINVAL;
|
||||
|
||||
ret = stm32_rifsc_grant_access(child);
|
||||
if (ret < 0 && ret != -EACCES)
|
||||
return ret;
|
||||
|
||||
child_access[plat->nb_child] = false;
|
||||
if (!ret) {
|
||||
child_access_granted++;
|
||||
child_access[plat->nb_child] = true;
|
||||
}
|
||||
|
||||
child_list[plat->nb_child] = child;
|
||||
plat->nb_child++;
|
||||
}
|
||||
|
||||
if (plat->nb_child != OMM_CHILD_NB)
|
||||
return -EINVAL;
|
||||
|
||||
/* check if OMM's resource access is granted */
|
||||
ret = stm32_rifsc_grant_access(dev_ofnode(dev));
|
||||
if (ret < 0 && ret != -EACCES)
|
||||
goto end;
|
||||
|
||||
/* All child's access are granted ? */
|
||||
if (!ret && child_access_granted == plat->nb_child) {
|
||||
ret = stm32_omm_configure(dev);
|
||||
if (ret)
|
||||
goto end;
|
||||
} 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);
|
||||
}
|
||||
|
||||
end:
|
||||
stm32_omm_release_childs(child_list, plat->nb_child);
|
||||
stm32_rifsc_release_access(dev_ofnode(dev));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_omm_of_to_plat(struct udevice *dev)
|
||||
{
|
||||
struct stm32_omm_plat *plat = dev_get_plat(dev);
|
||||
static const char * const clocks_name[] = {"omm", "ospi1", "ospi2"};
|
||||
static const char * const mm_name[] = { "ospi1", "ospi2" };
|
||||
static const char * const resets_name[] = {"omm", "ospi1", "ospi2"};
|
||||
struct resource res, res1, mm_res;
|
||||
struct ofnode_phandle_args args;
|
||||
struct udevice *child;
|
||||
unsigned long clk_rate;
|
||||
struct clk child_clk;
|
||||
int ret, idx;
|
||||
u8 i;
|
||||
|
||||
plat->regs_base = dev_read_addr(dev);
|
||||
if (plat->regs_base == FDT_ADDR_T_NONE)
|
||||
return -EINVAL;
|
||||
|
||||
ret = dev_read_resource_byname(dev, "memory_map", &mm_res);
|
||||
if (ret) {
|
||||
dev_err(dev, "can't get omm_mm mmap resource(ret = %d)!\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < OMM_CLK_NB; i++) {
|
||||
ret = clk_get_by_name(dev, clocks_name[i], &plat->clk[i]);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Can't find I/O manager clock %s\n", clocks_name[i]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = reset_get_by_name(dev, resets_name[i], &plat->reset_ctl[i]);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Can't find I/O manager reset %s\n", resets_name[i]);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* parse children's clock */
|
||||
plat->clk_rate_max = 0;
|
||||
device_foreach_child(child, dev) {
|
||||
ret = clk_get_by_index(child, 0, &child_clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to get clock for %s\n",
|
||||
dev_read_name(child));
|
||||
return ret;
|
||||
}
|
||||
|
||||
clk_rate = clk_get_rate(&child_clk);
|
||||
if (!clk_rate) {
|
||||
dev_err(dev, "Invalid clock rate\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (clk_rate > plat->clk_rate_max)
|
||||
plat->clk_rate_max = clk_rate;
|
||||
}
|
||||
|
||||
plat->mux = dev_read_u32_default(dev, "st,omm-mux", 0);
|
||||
plat->req2ack = dev_read_u32_default(dev, "st,omm-req2ack-ns", 0);
|
||||
plat->cssel_ovr = dev_read_u32_default(dev, "st,omm-cssel-ovr", 0xff);
|
||||
plat->mm_ospi2_size = 0;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
idx = dev_read_stringlist_search(dev, "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;
|
||||
|
||||
dev_read_phandle_with_args(dev, "memory-region", NULL, 0, idx,
|
||||
&args);
|
||||
ret = ofnode_read_resource(args.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(&mm_res, &res)) {
|
||||
dev_err(dev, "%s doesn't fit inside OMM memory map area\n",
|
||||
mm_name[i]);
|
||||
dev_err(dev, "[0x%llx-0x%llx] doesn't fit inside [0x%llx-0x%llx]\n",
|
||||
res.start, res.end,
|
||||
mm_res.start, mm_res.end);
|
||||
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (i == 1) {
|
||||
plat->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, "[0x%llx-0x%llx] overlaps [0x%llx-0x%llx]\n",
|
||||
res1.start, res1.end, res.start, res.end);
|
||||
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plat->syscfg_regmap = syscon_regmap_lookup_by_phandle(dev, "st,syscfg-amcr");
|
||||
if (IS_ERR(plat->syscfg_regmap)) {
|
||||
dev_err(dev, "Failed to get st,syscfg-amcr property\n");
|
||||
ret = PTR_ERR(plat->syscfg_regmap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = dev_read_u32_index(dev, "st,syscfg-amcr", 1, &plat->amcr_base);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to get st,syscfg-amcr base\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = dev_read_u32_index(dev, "st,syscfg-amcr", 2, &plat->amcr_mask);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to get st,syscfg-amcr mask\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
static int stm32_omm_bind(struct udevice *dev)
|
||||
{
|
||||
int ret = 0, err = 0;
|
||||
ofnode node;
|
||||
|
||||
for (node = ofnode_first_subnode(dev_ofnode(dev));
|
||||
ofnode_valid(node);
|
||||
node = ofnode_next_subnode(node)) {
|
||||
const char *node_name = ofnode_get_name(node);
|
||||
|
||||
if (!ofnode_is_enabled(node) || stm32_rifsc_grant_access(node)) {
|
||||
dev_dbg(dev, "%s failed to bind\n", node_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
err = lists_bind_fdt(dev, node, NULL, NULL,
|
||||
gd->flags & GD_FLG_RELOC ? false : true);
|
||||
if (err && !ret) {
|
||||
ret = err;
|
||||
dev_dbg(dev, "%s: ret=%d\n", node_name, ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_dbg(dev, "Some drivers failed to bind\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct udevice_id stm32_omm_ids[] = {
|
||||
{ .compatible = "st,stm32mp25-omm", },
|
||||
{},
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(stm32_omm) = {
|
||||
.name = "stm32_omm",
|
||||
.id = UCLASS_NOP,
|
||||
.probe = stm32_omm_probe,
|
||||
.of_match = stm32_omm_ids,
|
||||
.of_to_plat = stm32_omm_of_to_plat,
|
||||
.plat_auto = sizeof(struct stm32_omm_plat),
|
||||
.bind = stm32_omm_bind,
|
||||
};
|
||||
@@ -530,6 +530,14 @@ config SPI_SUNXI
|
||||
|
||||
Same controller driver can reuse in all Allwinner SoC variants.
|
||||
|
||||
config STM32_OSPI
|
||||
bool "STM32MP2 OSPI driver"
|
||||
depends on STM32MP25X && STM32_OMM
|
||||
help
|
||||
Enable the STM32MP2 Octo-SPI (OSPI) driver. This driver can be
|
||||
used to access the SPI NOR flash chips on platforms embedding
|
||||
this ST IP core.
|
||||
|
||||
config STM32_QSPI
|
||||
bool "STM32F7 QSPI driver"
|
||||
depends on STM32F4 || STM32F7 || ARCH_STM32MP
|
||||
|
||||
@@ -75,6 +75,7 @@ obj-$(CONFIG_SPI_SIFIVE) += spi-sifive.o
|
||||
obj-$(CONFIG_SPI_SN_F_OSPI) += spi-sn-f-ospi.o
|
||||
obj-$(CONFIG_SPI_SUNXI) += spi-sunxi.o
|
||||
obj-$(CONFIG_SH_QSPI) += sh_qspi.o
|
||||
obj-$(CONFIG_STM32_OSPI) += stm32_ospi.o
|
||||
obj-$(CONFIG_STM32_QSPI) += stm32_qspi.o
|
||||
obj-$(CONFIG_STM32_SPI) += stm32_spi.o
|
||||
obj-$(CONFIG_TEGRA114_SPI) += tegra114_spi.o
|
||||
|
||||
623
drivers/spi/stm32_ospi.c
Normal file
623
drivers/spi/stm32_ospi.c
Normal file
@@ -0,0 +1,623 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later OR BSD-3-Clause
|
||||
/*
|
||||
* Copyright (C) 2025, STMicroelectronics - All Rights Reserved
|
||||
*/
|
||||
|
||||
#define LOG_CATEGORY UCLASS_SPI
|
||||
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <log.h>
|
||||
#include <reset.h>
|
||||
#include <spi.h>
|
||||
#include <spi-mem.h>
|
||||
#include <syscon.h>
|
||||
#include <asm/io.h>
|
||||
#include <dm/device_compat.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
/* OCTOSPI control register */
|
||||
#define OSPI_CR 0x00
|
||||
#define OSPI_CR_EN BIT(0)
|
||||
#define OSPI_CR_ABORT BIT(1)
|
||||
#define OSPI_CR_TCEN BIT(3)
|
||||
#define OSPI_CR_FSEL BIT(7)
|
||||
#define OSPI_CR_FTHRES_MASK GENMASK(13, 8)
|
||||
#define OSPI_CR_FTHRES_SHIFT 8
|
||||
#define OSPI_CR_CSSEL BIT(24)
|
||||
#define OSPI_CR_FMODE_SHIFT 28
|
||||
#define OSPI_CR_FMODE_MASK GENMASK(29, 28)
|
||||
|
||||
/* OCTOSPI device configuration register */
|
||||
#define OSPI_DCR1 0x08
|
||||
#define OSPI_DCR1_CKMODE BIT(0)
|
||||
#define OSPI_DCR1_DLYBYP BIT(3)
|
||||
#define OSPI_DCR1_CSHT_SHIFT 8
|
||||
#define OSPI_DCR1_CSHT_MASK GENMASK(13, 8)
|
||||
#define OSPI_DCR1_DEVSIZE_MASK GENMASK(20, 16)
|
||||
#define OSPI_DCR1_MTYP_MASK GENMASK(26, 24)
|
||||
|
||||
/* OCTOSPI device configuration register 2 */
|
||||
#define OSPI_DCR2 0x0c
|
||||
#define OSPI_DCR2_PRESC_SHIFT 0
|
||||
#define OSPI_DCR2_PRESC_MASK GENMASK(7, 0)
|
||||
|
||||
/* OCTOSPI status register */
|
||||
#define OSPI_SR 0x20
|
||||
#define OSPI_SR_TEF BIT(0)
|
||||
#define OSPI_SR_TCF BIT(1)
|
||||
#define OSPI_SR_FTF BIT(2)
|
||||
#define OSPI_SR_BUSY BIT(5)
|
||||
|
||||
/* OCTOSPI flag clear register */
|
||||
#define OSPI_FCR 0x24
|
||||
#define OSPI_FCR_CTEF BIT(0)
|
||||
#define OSPI_FCR_CTCF BIT(1)
|
||||
|
||||
/* OCTOSPI data length register */
|
||||
#define OSPI_DLR 0x40
|
||||
|
||||
/* OCTOSPI address register */
|
||||
#define OSPI_AR 0x48
|
||||
|
||||
/* OCTOSPI data configuration register */
|
||||
#define OSPI_DR 0x50
|
||||
|
||||
/* OCTOSPI communication configuration register */
|
||||
#define OSPI_CCR 0x100
|
||||
#define OSPI_CCR_IMODE_SHIFT 0
|
||||
#define OSPI_CCR_IMODE_MASK GENMASK(2, 0)
|
||||
#define OSPI_CCR_ADMODE_SHIFT 8
|
||||
#define OSPI_CCR_ADMODE_MASK GENMASK(10, 8)
|
||||
#define OSPI_CCR_ADSIZE_SHIFT 12
|
||||
#define OSPI_CCR_DMODE_SHIFT 24
|
||||
#define OSPI_CCR_DMODE_MASK GENMASK(26, 24)
|
||||
#define OSPI_CCR_IND_WRITE 0
|
||||
#define OSPI_CCR_IND_READ 1
|
||||
#define OSPI_CCR_MEM_MAP 3
|
||||
|
||||
/* OCTOSPI timing configuration register */
|
||||
#define OSPI_TCR 0x108
|
||||
#define OSPI_TCR_DCYC_SHIFT 0x0
|
||||
#define OSPI_TCR_DCYC_MASK GENMASK(4, 0)
|
||||
#define OSPI_TCR_SSHIFT BIT(30)
|
||||
|
||||
/* OCTOSPI instruction register */
|
||||
#define OSPI_IR 0x110
|
||||
|
||||
#define OSPI_MAX_MMAP_SZ SZ_256M
|
||||
#define OSPI_MAX_CHIP 2
|
||||
|
||||
#define OSPI_FIFO_TIMEOUT_US 30000
|
||||
#define OSPI_ABT_TIMEOUT_US 100000
|
||||
#define OSPI_BUSY_TIMEOUT_US 100000
|
||||
#define OSPI_CMD_TIMEOUT_US 1000000
|
||||
|
||||
struct stm32_ospi_flash {
|
||||
u32 cr;
|
||||
u32 dcr;
|
||||
u32 dcr2;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
struct stm32_ospi_priv {
|
||||
struct stm32_ospi_flash flash[OSPI_MAX_CHIP];
|
||||
int cs_used;
|
||||
};
|
||||
|
||||
struct stm32_ospi_plat {
|
||||
phys_addr_t regs_base; /* register base address */
|
||||
phys_addr_t mm_base; /* memory map base address */
|
||||
resource_size_t mm_size;
|
||||
struct clk clk;
|
||||
struct reset_ctl_bulk rst_ctl;
|
||||
ulong clock_rate;
|
||||
};
|
||||
|
||||
static int stm32_ospi_mm(struct udevice *dev,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
struct stm32_ospi_plat *ospi_plat = dev_get_plat(dev);
|
||||
|
||||
memcpy_fromio(op->data.buf.in,
|
||||
(void __iomem *)ospi_plat->mm_base + op->addr.val,
|
||||
op->data.nbytes);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm32_ospi_read_fifo(void *val, phys_addr_t addr, u8 len)
|
||||
{
|
||||
switch (len) {
|
||||
case sizeof(u32):
|
||||
*((u32 *)val) = readl_relaxed(addr);
|
||||
break;
|
||||
case sizeof(u16):
|
||||
*((u16 *)val) = readw_relaxed(addr);
|
||||
break;
|
||||
case sizeof(u8):
|
||||
*((u8 *)val) = readb_relaxed(addr);
|
||||
};
|
||||
schedule();
|
||||
}
|
||||
|
||||
static void stm32_ospi_write_fifo(void *val, phys_addr_t addr, u8 len)
|
||||
{
|
||||
switch (len) {
|
||||
case sizeof(u32):
|
||||
writel_relaxed(*((u32 *)val), addr);
|
||||
break;
|
||||
case sizeof(u16):
|
||||
writew_relaxed(*((u16 *)val), addr);
|
||||
break;
|
||||
case sizeof(u8):
|
||||
writeb_relaxed(*((u8 *)val), addr);
|
||||
};
|
||||
}
|
||||
|
||||
int stm32_ospi_tx_poll(struct udevice *dev, void *buf, u32 len, bool read)
|
||||
{
|
||||
struct stm32_ospi_plat *ospi_plat = dev_get_plat(dev);
|
||||
phys_addr_t regs_base = ospi_plat->regs_base;
|
||||
void (*fifo)(void *val, phys_addr_t addr, u8 len);
|
||||
u32 sr;
|
||||
int ret;
|
||||
u8 step = 1;
|
||||
|
||||
if (read)
|
||||
fifo = stm32_ospi_read_fifo;
|
||||
else
|
||||
fifo = stm32_ospi_write_fifo;
|
||||
|
||||
while (len) {
|
||||
ret = readl_poll_timeout(regs_base + OSPI_SR, sr,
|
||||
sr & OSPI_SR_FTF,
|
||||
OSPI_FIFO_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(dev, "fifo timeout (len:%d stat:%#x)\n",
|
||||
len, sr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!IS_ALIGNED((uintptr_t)buf, sizeof(u32))) {
|
||||
if (!IS_ALIGNED((uintptr_t)buf, sizeof(u16)))
|
||||
step = sizeof(u8);
|
||||
else
|
||||
step = min((u32)len, (u32)sizeof(u16));
|
||||
}
|
||||
/* Buf is aligned */
|
||||
else if (len >= sizeof(u32))
|
||||
step = sizeof(u32);
|
||||
else if (len >= sizeof(u16))
|
||||
step = sizeof(u16);
|
||||
else if (len)
|
||||
step = sizeof(u8);
|
||||
|
||||
fifo(buf, regs_base + OSPI_DR, step);
|
||||
len -= step;
|
||||
buf += step;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_ospi_tx(struct udevice *dev,
|
||||
const struct spi_mem_op *op,
|
||||
u8 mode)
|
||||
{
|
||||
void *buf;
|
||||
|
||||
if (!op->data.nbytes)
|
||||
return 0;
|
||||
|
||||
if (mode == OSPI_CCR_MEM_MAP)
|
||||
return stm32_ospi_mm(dev, op);
|
||||
|
||||
if (op->data.dir == SPI_MEM_DATA_IN)
|
||||
buf = op->data.buf.in;
|
||||
else
|
||||
buf = (void *)op->data.buf.out;
|
||||
|
||||
return stm32_ospi_tx_poll(dev, buf, op->data.nbytes,
|
||||
op->data.dir == SPI_MEM_DATA_IN);
|
||||
}
|
||||
|
||||
static int stm32_ospi_get_mode(u8 buswidth)
|
||||
{
|
||||
if (buswidth == 8)
|
||||
return 4;
|
||||
|
||||
if (buswidth == 4)
|
||||
return 3;
|
||||
|
||||
return buswidth;
|
||||
}
|
||||
|
||||
int stm32_ospi_wait_for_not_busy(struct udevice *dev)
|
||||
{
|
||||
struct stm32_ospi_plat *ospi_plat = dev_get_plat(dev);
|
||||
phys_addr_t regs_base = ospi_plat->regs_base;
|
||||
u32 sr;
|
||||
int ret;
|
||||
|
||||
ret = readl_poll_timeout(regs_base + OSPI_SR, sr, !(sr & OSPI_SR_BUSY),
|
||||
OSPI_BUSY_TIMEOUT_US);
|
||||
if (ret)
|
||||
dev_err(dev, "busy timeout (stat:%#x)\n", sr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int stm32_ospi_wait_cmd(struct udevice *dev)
|
||||
{
|
||||
struct stm32_ospi_plat *ospi_plat = dev_get_plat(dev);
|
||||
phys_addr_t regs_base = ospi_plat->regs_base;
|
||||
u32 sr;
|
||||
int ret = 0;
|
||||
|
||||
ret = readl_poll_timeout(regs_base + OSPI_SR, sr,
|
||||
sr & OSPI_SR_TCF,
|
||||
OSPI_CMD_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(dev, "cmd timeout (stat:%#x)\n", sr);
|
||||
} else if (readl(regs_base + OSPI_SR) & OSPI_SR_TEF) {
|
||||
dev_err(dev, "transfer error (stat:%#x)\n", sr);
|
||||
ret = -EIO;
|
||||
}
|
||||
|
||||
/* clear flags */
|
||||
writel(OSPI_FCR_CTCF | OSPI_FCR_CTEF, regs_base + OSPI_FCR);
|
||||
|
||||
if (!ret)
|
||||
ret = stm32_ospi_wait_for_not_busy(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_ospi_exec_op(struct spi_slave *slave,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
struct stm32_ospi_plat *ospi_plat = dev_get_plat(slave->dev->parent);
|
||||
phys_addr_t regs_base = ospi_plat->regs_base;
|
||||
u32 cr, ccr = 0, addr_max;
|
||||
int timeout, ret;
|
||||
int dmode;
|
||||
u8 mode = OSPI_CCR_IND_WRITE;
|
||||
u8 dcyc = 0;
|
||||
|
||||
dev_dbg(slave->dev, "%s: cmd:%#x mode:%d.%d.%d.%d addr:%#llx len:%#x\n",
|
||||
__func__, op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth,
|
||||
op->dummy.buswidth, op->data.buswidth,
|
||||
op->addr.val, op->data.nbytes);
|
||||
|
||||
addr_max = op->addr.val + op->data.nbytes + 1;
|
||||
|
||||
if (op->data.dir == SPI_MEM_DATA_IN && op->data.nbytes) {
|
||||
if (addr_max < ospi_plat->mm_size && op->addr.buswidth)
|
||||
mode = OSPI_CCR_MEM_MAP;
|
||||
else
|
||||
mode = OSPI_CCR_IND_READ;
|
||||
}
|
||||
|
||||
if (op->data.nbytes)
|
||||
writel(op->data.nbytes - 1, regs_base + OSPI_DLR);
|
||||
|
||||
clrsetbits_le32(regs_base + OSPI_CR, OSPI_CR_FMODE_MASK,
|
||||
mode << OSPI_CR_FMODE_SHIFT);
|
||||
|
||||
ccr |= (stm32_ospi_get_mode(op->cmd.buswidth) << OSPI_CCR_IMODE_SHIFT) &
|
||||
OSPI_CCR_IMODE_MASK;
|
||||
|
||||
if (op->addr.nbytes) {
|
||||
ccr |= ((op->addr.nbytes - 1) << OSPI_CCR_ADSIZE_SHIFT);
|
||||
ccr |= (stm32_ospi_get_mode(op->addr.buswidth)
|
||||
<< OSPI_CCR_ADMODE_SHIFT) & OSPI_CCR_ADMODE_MASK;
|
||||
}
|
||||
|
||||
if (op->dummy.buswidth && op->dummy.nbytes)
|
||||
dcyc = op->dummy.nbytes * 8 / op->dummy.buswidth;
|
||||
|
||||
clrsetbits_le32(regs_base + OSPI_TCR, OSPI_TCR_DCYC_MASK,
|
||||
dcyc << OSPI_TCR_DCYC_SHIFT);
|
||||
|
||||
if (op->data.nbytes) {
|
||||
dmode = stm32_ospi_get_mode(op->data.buswidth);
|
||||
ccr |= (dmode << OSPI_CCR_DMODE_SHIFT) & OSPI_CCR_DMODE_MASK;
|
||||
}
|
||||
|
||||
writel(ccr, regs_base + OSPI_CCR);
|
||||
|
||||
/* set instruction, must be set after ccr register update */
|
||||
writel(op->cmd.opcode, regs_base + OSPI_IR);
|
||||
|
||||
if (op->addr.nbytes && mode != OSPI_CCR_MEM_MAP)
|
||||
writel(op->addr.val, regs_base + OSPI_AR);
|
||||
|
||||
ret = stm32_ospi_tx(slave->dev->parent, op, mode);
|
||||
/*
|
||||
* 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 (ret || mode == OSPI_CCR_MEM_MAP)
|
||||
goto abort;
|
||||
|
||||
/* Wait end of tx in indirect mode */
|
||||
ret = stm32_ospi_wait_cmd(slave->dev->parent);
|
||||
if (ret)
|
||||
goto abort;
|
||||
|
||||
return 0;
|
||||
|
||||
abort:
|
||||
setbits_le32(regs_base + OSPI_CR, OSPI_CR_ABORT);
|
||||
|
||||
/* Wait clear of abort bit by hw */
|
||||
timeout = readl_poll_timeout(regs_base + OSPI_CR, cr,
|
||||
!(cr & OSPI_CR_ABORT),
|
||||
OSPI_ABT_TIMEOUT_US);
|
||||
|
||||
writel(OSPI_FCR_CTCF, regs_base + OSPI_FCR);
|
||||
|
||||
if (ret || timeout)
|
||||
dev_err(slave->dev, "%s ret:%d abort timeout:%d\n", __func__,
|
||||
ret, timeout);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_ospi_probe(struct udevice *bus)
|
||||
{
|
||||
struct stm32_ospi_priv *priv = dev_get_priv(bus);
|
||||
struct stm32_ospi_plat *ospi_plat;
|
||||
phys_addr_t regs_base;
|
||||
int ret;
|
||||
|
||||
ospi_plat = dev_get_plat(bus);
|
||||
regs_base = ospi_plat->regs_base;
|
||||
|
||||
ret = clk_enable(&ospi_plat->clk);
|
||||
if (ret) {
|
||||
dev_err(bus, "failed to enable clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Reset OSPI controller */
|
||||
reset_assert_bulk(&ospi_plat->rst_ctl);
|
||||
udelay(2);
|
||||
reset_deassert_bulk(&ospi_plat->rst_ctl);
|
||||
|
||||
priv->cs_used = -1;
|
||||
|
||||
setbits_le32(regs_base + OSPI_TCR, OSPI_TCR_SSHIFT);
|
||||
|
||||
clrsetbits_le32(regs_base + OSPI_CR, OSPI_CR_FTHRES_MASK,
|
||||
3 << OSPI_CR_FTHRES_SHIFT);
|
||||
|
||||
/* Set dcr devsize to max address */
|
||||
setbits_le32(regs_base + OSPI_DCR1,
|
||||
OSPI_DCR1_DEVSIZE_MASK | OSPI_DCR1_DLYBYP);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_ospi_claim_bus(struct udevice *dev)
|
||||
{
|
||||
struct stm32_ospi_priv *priv = dev_get_priv(dev->parent);
|
||||
struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
|
||||
struct stm32_ospi_plat *ospi_plat = dev_get_plat(dev->parent);
|
||||
phys_addr_t regs_base = ospi_plat->regs_base;
|
||||
unsigned int slave_cs = slave_plat->cs[0];
|
||||
|
||||
if (slave_cs >= OSPI_MAX_CHIP)
|
||||
return -ENODEV;
|
||||
|
||||
if (priv->cs_used != slave_cs) {
|
||||
struct stm32_ospi_flash *flash = &priv->flash[slave_cs];
|
||||
|
||||
priv->cs_used = slave_cs;
|
||||
|
||||
if (flash->initialized) {
|
||||
/* Set the configuration: speed + cs */
|
||||
writel(flash->cr, regs_base + OSPI_CR);
|
||||
writel(flash->dcr, regs_base + OSPI_DCR1);
|
||||
writel(flash->dcr2, regs_base + OSPI_DCR2);
|
||||
} else {
|
||||
/* Set chip select */
|
||||
clrsetbits_le32(regs_base + OSPI_CR,
|
||||
OSPI_CR_CSSEL,
|
||||
priv->cs_used ? OSPI_CR_CSSEL : 0);
|
||||
|
||||
/* Save the configuration: speed + cs */
|
||||
flash->cr = readl(regs_base + OSPI_CR);
|
||||
flash->dcr = readl(regs_base + OSPI_DCR1);
|
||||
flash->dcr2 = readl(regs_base + OSPI_DCR2);
|
||||
flash->initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
setbits_le32(regs_base + OSPI_CR, OSPI_CR_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_ospi_release_bus(struct udevice *dev)
|
||||
{
|
||||
struct stm32_ospi_plat *ospi_plat = dev_get_plat(dev->parent);
|
||||
phys_addr_t regs_base = ospi_plat->regs_base;
|
||||
|
||||
clrbits_le32(regs_base + OSPI_CR, OSPI_CR_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_ospi_set_speed(struct udevice *bus, uint speed)
|
||||
{
|
||||
struct stm32_ospi_plat *ospi_plat = dev_get_plat(bus);
|
||||
phys_addr_t regs_base = ospi_plat->regs_base;
|
||||
u32 ospi_clk = ospi_plat->clock_rate;
|
||||
u32 prescaler = 255;
|
||||
u32 csht;
|
||||
int ret;
|
||||
|
||||
if (speed > 0) {
|
||||
prescaler = 0;
|
||||
if (ospi_clk) {
|
||||
prescaler = DIV_ROUND_UP(ospi_clk, speed) - 1;
|
||||
if (prescaler > 255)
|
||||
prescaler = 255;
|
||||
}
|
||||
}
|
||||
|
||||
csht = (DIV_ROUND_UP((5 * ospi_clk) / (prescaler + 1), 100000000)) - 1;
|
||||
|
||||
ret = stm32_ospi_wait_for_not_busy(bus);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clrsetbits_le32(regs_base + OSPI_DCR2, OSPI_DCR2_PRESC_MASK,
|
||||
prescaler << OSPI_DCR2_PRESC_SHIFT);
|
||||
|
||||
clrsetbits_le32(regs_base + OSPI_DCR1, OSPI_DCR1_CSHT_MASK,
|
||||
csht << OSPI_DCR1_CSHT_SHIFT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_ospi_set_mode(struct udevice *bus, uint mode)
|
||||
{
|
||||
struct stm32_ospi_plat *ospi_plat = dev_get_plat(bus);
|
||||
phys_addr_t regs_base = ospi_plat->regs_base;
|
||||
const char *str_rx, *str_tx;
|
||||
int ret;
|
||||
|
||||
ret = stm32_ospi_wait_for_not_busy(bus);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if ((mode & SPI_CPHA) && (mode & SPI_CPOL))
|
||||
setbits_le32(regs_base + OSPI_DCR1, OSPI_DCR1_CKMODE);
|
||||
else if (!(mode & SPI_CPHA) && !(mode & SPI_CPOL))
|
||||
clrbits_le32(regs_base + OSPI_DCR1, OSPI_DCR1_CKMODE);
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
if (mode & SPI_CS_HIGH)
|
||||
return -ENODEV;
|
||||
|
||||
if (mode & SPI_RX_OCTAL)
|
||||
str_rx = "octal";
|
||||
else if (mode & SPI_RX_QUAD)
|
||||
str_rx = "quad";
|
||||
else if (mode & SPI_RX_DUAL)
|
||||
str_rx = "dual";
|
||||
else
|
||||
str_rx = "single";
|
||||
|
||||
if (mode & SPI_TX_OCTAL)
|
||||
str_tx = "octal";
|
||||
else if (mode & SPI_TX_QUAD)
|
||||
str_tx = "quad";
|
||||
else if (mode & SPI_TX_DUAL)
|
||||
str_tx = "dual";
|
||||
else
|
||||
str_tx = "single";
|
||||
|
||||
dev_dbg(bus, "mode=%d rx: %s, tx: %s\n", mode, str_rx, str_tx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_controller_mem_ops stm32_ospi_mem_ops = {
|
||||
.exec_op = stm32_ospi_exec_op,
|
||||
};
|
||||
|
||||
static const struct dm_spi_ops stm32_ospi_ops = {
|
||||
.claim_bus = stm32_ospi_claim_bus,
|
||||
.release_bus = stm32_ospi_release_bus,
|
||||
.set_speed = stm32_ospi_set_speed,
|
||||
.set_mode = stm32_ospi_set_mode,
|
||||
.mem_ops = &stm32_ospi_mem_ops,
|
||||
};
|
||||
|
||||
static int stm32_ospi_of_to_plat(struct udevice *dev)
|
||||
{
|
||||
struct stm32_ospi_plat *plat = dev_get_plat(dev);
|
||||
struct resource res;
|
||||
struct ofnode_phandle_args args;
|
||||
const fdt32_t *reg;
|
||||
int ret, len;
|
||||
|
||||
reg = dev_read_prop(dev, "reg", &len);
|
||||
if (!reg) {
|
||||
dev_err(dev, "Can't get regs base address\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
plat->regs_base = (phys_addr_t)dev_translate_address(dev, reg);
|
||||
|
||||
/* optional */
|
||||
ret = dev_read_phandle_with_args(dev, "memory-region", NULL, 0, 0, &args);
|
||||
if (!ret) {
|
||||
ret = ofnode_read_resource(args.node, 0, &res);
|
||||
if (ret) {
|
||||
dev_err(dev, "Can't get mmap base address(%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
plat->mm_base = res.start;
|
||||
plat->mm_size = resource_size(&res);
|
||||
|
||||
if (plat->mm_size > OSPI_MAX_MMAP_SZ) {
|
||||
dev_err(dev, "Incorrect memory-map size: %lld Bytes\n", plat->mm_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "%s: regs_base=<0x%llx> mm_base=<0x%llx> mm_size=<0x%x>\n",
|
||||
__func__, plat->regs_base, plat->mm_base, (u32)plat->mm_size);
|
||||
} else {
|
||||
plat->mm_base = 0;
|
||||
plat->mm_size = 0;
|
||||
dev_info(dev, "memory-region property not found (%d)\n", ret);
|
||||
}
|
||||
|
||||
ret = clk_get_by_index(dev, 0, &plat->clk);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to get clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = reset_get_bulk(dev, &plat->rst_ctl);
|
||||
if (ret && ret != -ENOENT) {
|
||||
dev_err(dev, "Failed to get reset\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
plat->clock_rate = clk_get_rate(&plat->clk);
|
||||
if (!plat->clock_rate)
|
||||
return -EINVAL;
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
static const struct udevice_id stm32_ospi_ids[] = {
|
||||
{ .compatible = "st,stm32mp25-ospi" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(stm32_ospi) = {
|
||||
.name = "stm32_ospi",
|
||||
.id = UCLASS_SPI,
|
||||
.of_match = stm32_ospi_ids,
|
||||
.of_to_plat = stm32_ospi_of_to_plat,
|
||||
.ops = &stm32_ospi_ops,
|
||||
.plat_auto = sizeof(struct stm32_ospi_plat),
|
||||
.priv_auto = sizeof(struct stm32_ospi_priv),
|
||||
.probe = stm32_ospi_probe,
|
||||
};
|
||||
@@ -135,6 +135,22 @@ static inline unsigned long resource_type(const struct resource *res)
|
||||
return res->flags & IORESOURCE_TYPE_BITS;
|
||||
}
|
||||
|
||||
/* True iff r1 completely contains r2 */
|
||||
static inline bool resource_contains(struct resource *r1, struct resource *r2)
|
||||
{
|
||||
if (resource_type(r1) != resource_type(r2))
|
||||
return false;
|
||||
if (r1->flags & IORESOURCE_UNSET || r2->flags & IORESOURCE_UNSET)
|
||||
return false;
|
||||
return r1->start <= r2->start && r1->end >= r2->end;
|
||||
}
|
||||
|
||||
/* True if any part of r1 overlaps r2 */
|
||||
static inline bool resource_overlaps(struct resource *r1, struct resource *r2)
|
||||
{
|
||||
return r1->start <= r2->end && r1->end >= r2->start;
|
||||
}
|
||||
|
||||
/* Convenience shorthand with allocation */
|
||||
#define request_region(start,n,name) __request_region(&ioport_resource, (start), (n), (name), 0)
|
||||
#define __request_mem_region(start,n,name, excl) __request_region(&iomem_resource, (start), (n), (name), excl)
|
||||
|
||||
Reference in New Issue
Block a user