diff --git a/arch/arm/dts/lemans-evk-u-boot.dtsi b/arch/arm/dts/lemans-evk-u-boot.dtsi index fd4eaa2fec81..5f05a3226054 100644 --- a/arch/arm/dts/lemans-evk-u-boot.dtsi +++ b/arch/arm/dts/lemans-evk-u-boot.dtsi @@ -36,7 +36,7 @@ }; &gcc { - bootph-all; /* Enable GCC for SPL */ + bootph-all; }; /* RPMH clock controller - required by GCC */ @@ -56,6 +56,13 @@ &tlmm { bootph-all; /* Make TLMM GPIO controller available in SPL */ + + qup_spi21_default: qup-spi21-state { + pins = "gpio13", "gpio14", "gpio15", "gpio16"; + function = "sail_top"; + drive-strength = <2>; + bias-disable; + }; }; /* Enter Qualcomm EDL/download mode via "reset -edl". */ @@ -64,3 +71,26 @@ mode-edl = <0x80000000 0x00000001>; }; }; + +&{/aliases} { + spi0 = &spi21; +}; + +&qupv3_id_3 { + bootph-all; + status = "okay"; +}; + +&spi21 { + assigned-clocks = <&gcc GCC_QUPV3_WRAP3_S0_CLK>; + assigned-clock-rates = <48000000>; + status = "okay"; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <48000000>; + spi-tx-bus-width = <1>; + spi-rx-bus-width = <1>; + }; +}; diff --git a/configs/qcom_lemans_snagboot_defconfig b/configs/qcom_lemans_snagboot_defconfig index d6906c1e5012..4c7cbdad8773 100644 --- a/configs/qcom_lemans_snagboot_defconfig +++ b/configs/qcom_lemans_snagboot_defconfig @@ -15,4 +15,54 @@ CONFIG_FASTBOOT_BUF_ADDR=0xdb300000 CONFIG_DEFAULT_DEVICE_TREE="qcom/lemans-evk" # Timer frequency for Lemans platform -CONFIG_COUNTER_FREQUENCY=19200000 \ No newline at end of file +CONFIG_COUNTER_FREQUENCY=19200000 + +# Use upstream DTS +CONFIG_OF_UPSTREAM=y + +# CMD_DB required for RPMH clock resource lookups (used by SMMU/RPMH setup below) +CONFIG_QCOM_COMMAND_DB=y + +# SMMU: the QUP3 GENI SE-DMA stream needs the Qualcomm hypervisor-backed SMMU +# enabled or DMA silently faults instead of landing in DRAM. +CONFIG_IOMMU=y +CONFIG_QCOM_HYP_SMMU=y + +# SPI-NOR on QUPV3 wrapper 3, SE0 +CONFIG_SPI=y +CONFIG_DM_SPI=y +CONFIG_SPI_GENI_QCOM=y +CONFIG_MTD=y +CONFIG_DM_MTD=y +CONFIG_CMD_MTD=y +CONFIG_MTD_BLOCK=y +CONFIG_DM_SPI_FLASH=y +CONFIG_SPI_FLASH_MTD=y +CONFIG_SPI_FLASH_SFDP_SUPPORT=y +CONFIG_SF_DEFAULT_BUS=0 +CONFIG_SF_DEFAULT_CS=0 +CONFIG_SF_DEFAULT_SPEED=48000000 +CONFIG_SF_DEFAULT_MODE=0 +CONFIG_CMD_SF_TEST=y + +# SPI-NOR flash vendors +CONFIG_SPI_FLASH_ATMEL=y +CONFIG_SPI_FLASH_DOSILICON=y +CONFIG_SPI_FLASH_EON=y +CONFIG_SPI_FLASH_GIGADEVICE=y +CONFIG_SPI_FLASH_ISSI=y +CONFIG_SPI_FLASH_MACRONIX=y +CONFIG_SPI_FLASH_PUYA=y +CONFIG_SPI_FLASH_SILICONKAISER=y +CONFIG_SPI_FLASH_SPANSION=y +CONFIG_SPI_FLASH_STMICRO=y +CONFIG_SPI_FLASH_SST=y +CONFIG_SPI_FLASH_WINBOND=y +CONFIG_SPI_FLASH_XMC=y +CONFIG_SPI_FLASH_XTX=y +CONFIG_SPI_FLASH_ZBIT=y + +# Fastboot: switch the block backend from the snagboot default ("scsi") to +# "mtd" at runtime, once SPI-NOR has been probed +CONFIG_FASTBOOT_CMD_OEM_SET_BLOCK_TARGET=y +CONFIG_FASTBOOT_CMD_OEM_SPI_NOR_INIT=y diff --git a/disk/part_efi.c b/disk/part_efi.c index d8b17ec2e91a..d1266165fa15 100644 --- a/disk/part_efi.c +++ b/disk/part_efi.c @@ -975,6 +975,9 @@ int write_mbr_and_gpt_partitions(struct blk_desc *desc, void *buf) return 1; } + if (desc->part_type == PART_TYPE_MTD) + desc->part_type = PART_TYPE_UNKNOWN; + /* Update the partition table entries*/ part_init(desc); diff --git a/drivers/block/blk-uclass.c b/drivers/block/blk-uclass.c index 73c24fd91763..37f39817bc38 100644 --- a/drivers/block/blk-uclass.c +++ b/drivers/block/blk-uclass.c @@ -128,6 +128,9 @@ struct blk_desc *blk_get_devnum_by_uclass_idname(const char *uclass_idname, int return NULL; } + if (uclass_id == UCLASS_MTD) + return blk_get_devnum_by_uclass_id(UCLASS_MTD, devnum); + ret = uclass_get(UCLASS_BLK, &uc); if (ret) return NULL; diff --git a/drivers/clk/qcom/clock-sa8775p.c b/drivers/clk/qcom/clock-sa8775p.c index eafa8baf0218..a352689375be 100644 --- a/drivers/clk/qcom/clock-sa8775p.c +++ b/drivers/clk/qcom/clock-sa8775p.c @@ -25,6 +25,7 @@ #define UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR 0x8308c #define SDCC1_APPS_CLK_CMD_RCGR 0x20014 +#define QUPV3_WRAP3_S0_CLK_CMD_RCGR 0xc4154 #define APCS_GPLL9_STATUS 0x9000 #define APCS_GPLLX_ENA_REG 0x4b028 @@ -53,6 +54,26 @@ #define GCC_QUPV3_WRAP2_S6_CLK_ENA_BIT BIT(29) #define GCC_QUPV3_WRAP3_S0_CLK_ENA_BIT BIT(25) +#define GCC_QUPV3_WRAP_3_M_AHB_ENA_BIT BIT(27) +#define GCC_QUPV3_WRAP_3_S_AHB_ENA_BIT BIT(20) +#define GCC_QUPV3_WRAP3_QSPI_ENA_BIT BIT(26) +#define GCC_AGGRE_NOC_QUPV3_AXI_ENA_BIT BIT(28) + +/* QUP Wrapper 3 SE0 (SPI) clock frequency table */ +static const struct freq_tbl ftbl_gcc_qupv3_wrap3_s0_clk_src[] = { + F(7372800, CFG_CLK_SRC_GPLL0_EVEN, 1, 384, 15625), + F(14745600, CFG_CLK_SRC_GPLL0_EVEN, 1, 768, 15625), + F(19200000, CFG_CLK_SRC_CXO, 1, 0, 0), + F(29491200, CFG_CLK_SRC_GPLL0_EVEN, 1, 1536, 15625), + F(32000000, CFG_CLK_SRC_GPLL0_EVEN, 1, 8, 75), + F(48000000, CFG_CLK_SRC_GPLL0, 2, 4, 25), + F(64000000, CFG_CLK_SRC_GPLL0_EVEN, 1, 16, 75), + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(80000000, CFG_CLK_SRC_GPLL0_EVEN, 1, 4, 15), + F(96000000, CFG_CLK_SRC_GPLL0_EVEN, 1, 8, 25), + F(100000000, CFG_CLK_SRC_GPLL0, 6, 0, 0), + { } +}; /* UFS AXI clock frequency table */ static const struct freq_tbl ftbl_gcc_ufs_phy_axi_clk_src[] = { @@ -147,6 +168,12 @@ static ulong sa8775p_set_rate(struct clk *clk, ulong rate) clk_rcg_set_rate_mnd(priv->base, SDCC1_APPS_CLK_CMD_RCGR, freq->pre_div, freq->m, freq->n, freq->src, 8); return freq->freq; + case GCC_QUPV3_WRAP3_S0_CLK: + freq = qcom_find_freq(ftbl_gcc_qupv3_wrap3_s0_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, QUPV3_WRAP3_S0_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, + freq->src, 16); + return freq->freq; default: return 0; } @@ -189,7 +216,11 @@ static const struct gate_clk sa8775p_clks[] = { GATE_CLK(GCC_QUPV3_WRAP2_S6_CLK, 0x4b018, GCC_QUPV3_WRAP2_S6_CLK_ENA_BIT), /* QUP Wrapper 3 clocks */ + GATE_CLK(GCC_QUPV3_WRAP_3_M_AHB_CLK, 0x4b000, GCC_QUPV3_WRAP_3_M_AHB_ENA_BIT), + GATE_CLK(GCC_QUPV3_WRAP_3_S_AHB_CLK, 0x4b000, GCC_QUPV3_WRAP_3_S_AHB_ENA_BIT), GATE_CLK(GCC_QUPV3_WRAP3_S0_CLK, 0x4b000, GCC_QUPV3_WRAP3_S0_CLK_ENA_BIT), + GATE_CLK(GCC_QUPV3_WRAP3_QSPI_CLK, 0x4b000, GCC_QUPV3_WRAP3_QSPI_ENA_BIT), + GATE_CLK(GCC_AGGRE_NOC_QUPV3_AXI_CLK, 0x4b000, GCC_AGGRE_NOC_QUPV3_AXI_ENA_BIT), /* UFS PHY clocks */ GATE_CLK_POLLED(GCC_UFS_PHY_AXI_CLK, 0x83018, BIT(0), 0x83018), @@ -229,6 +260,10 @@ static int sa8775p_enable(struct clk *clk) qcom_gate_clk_en(priv, GCC_USB3_PRIM_PHY_AUX_CLK); qcom_gate_clk_en(priv, GCC_USB3_PRIM_PHY_COM_AUX_CLK); break; + case GCC_QUPV3_WRAP3_S0_CLK: + qcom_gate_clk_en(priv, GCC_QUPV3_WRAP3_QSPI_CLK); + qcom_gate_clk_en(priv, GCC_AGGRE_NOC_QUPV3_AXI_CLK); + break; } return qcom_gate_clk_en(priv, clk->id); diff --git a/drivers/fastboot/Kconfig b/drivers/fastboot/Kconfig index a4539844d898..0d07d0602a3b 100644 --- a/drivers/fastboot/Kconfig +++ b/drivers/fastboot/Kconfig @@ -225,6 +225,28 @@ config FASTBOOT_FLASH_BLOCK_DEVICE_ID Device identifiers are numbered starting from 0 and the most common case is to use the first controller on the system. +config FASTBOOT_CMD_OEM_SET_BLOCK_TARGET + bool "Enable the 'oem set-block-target' command" + depends on FASTBOOT_FLASH_BLOCK + help + Add support for the "oem set-block-target" command from a client. + This allows switching the block interface (e.g. scsi, mtd) and + device number used by the fastboot "flash"/"erase" commands at + runtime, instead of relying solely on the + CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME / + CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID build-time defaults. + +config FASTBOOT_CMD_OEM_SPI_NOR_INIT + bool "Enable the 'oem spi-nor-init' command" + depends on DM_SPI_FLASH + help + Add support for the "oem spi-nor-init" command from a client. This + removes and reprobes the default SPI-NOR device, letting its + driver's probe() handle any vendor-specific bring-up (e.g. firmware + load) it needs. Use together with "oem set-block-target" to bring up + SPI-NOR via fastboot on boards where SPI-NOR is not usable until + after fastboot has flashed something it depends on. + config FASTBOOT_GPT_NAME string "Target name for updating GPT" depends on (FASTBOOT_FLASH_MMC || FASTBOOT_FLASH_BLOCK) && EFI_PARTITION diff --git a/drivers/fastboot/fb_block.c b/drivers/fastboot/fb_block.c index a8c74e35d0d1..d3e5f6ed1f3f 100644 --- a/drivers/fastboot/fb_block.c +++ b/drivers/fastboot/fb_block.c @@ -125,6 +125,66 @@ static lbaint_t fb_block_sparse_reserve(struct sparse_storage *info, return blkcnt; } +/** + * block_interface_override - runtime-set block interface name, empty if unset + */ +static char block_interface_override[16]; +/** + * block_device_override - runtime-set block device number, -1 if unset + */ +static int block_device_override = -1; + +/** + * fb_block_interface() - Resolve the block interface name to use + * + * Returns the runtime override set via fastboot_block_set_target() if one + * is active, otherwise falls back to the build-time config default. + */ +static const char *fb_block_interface(void) +{ + if (!IS_ENABLED(CONFIG_FASTBOOT_FLASH_BLOCK)) + return NULL; + + if (block_interface_override[0]) + return block_interface_override; + + return config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, + CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME, NULL); +} + +/** + * fb_block_device() - Resolve the block device number to use + * + * Returns the runtime override set via fastboot_block_set_target() if one + * is active, otherwise falls back to the build-time config default. + */ +static int fb_block_device(void) +{ + if (!IS_ENABLED(CONFIG_FASTBOOT_FLASH_BLOCK)) + return -1; + + if (block_device_override >= 0) + return block_device_override; + + return config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, + CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID, -1); +} + +int fastboot_block_set_target(const char *interface, int device) +{ + if (interface) { + if (strlen(interface) >= sizeof(block_interface_override)) + return -EINVAL; + strlcpy(block_interface_override, interface, + sizeof(block_interface_override)); + } + + if (device >= 0) + block_device_override = device; + + return 0; +} + /** * parse_device_partition() - Parse and validate device:partition format * @part_name: Input string in format "N:partition" or "partition" @@ -140,8 +200,7 @@ static int parse_device_partition(const char *part_name, int *device, { const char *colon_pos; - *device = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, - CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID, -1); + *device = fb_block_device(); /* Check for colon in partition name */ colon_pos = strchr(part_name, ':'); @@ -190,9 +249,7 @@ int fastboot_block_get_part_info(const char *part_name, char *response) { int ret; - const char *interface = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, - CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME, - NULL); + const char *interface = fb_block_interface(); int device; const char *partition_name; @@ -385,9 +442,7 @@ void fastboot_block_flash_write(const char *part_name, void *download_buffer, #if CONFIG_IS_ENABLED(EFI_PARTITION) if (is_partition_table_name(part_name, CONFIG_FASTBOOT_GPT_NAME)) { int device; - const char *interface = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, - CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME, - NULL); + const char *interface = fb_block_interface(); parse_device_partition(part_name, &device, NULL); fastboot_flash_gpt_partition_table(interface, device, @@ -399,9 +454,7 @@ void fastboot_block_flash_write(const char *part_name, void *download_buffer, #if CONFIG_IS_ENABLED(DOS_PARTITION) if (is_partition_table_name(part_name, CONFIG_FASTBOOT_MBR_NAME)) { int device; - const char *interface = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, - CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME, - NULL); + const char *interface = fb_block_interface(); parse_device_partition(part_name, &device, NULL); fastboot_flash_mbr_partition_table(interface, device, diff --git a/drivers/fastboot/fb_command.c b/drivers/fastboot/fb_command.c index 18d86988f4c9..4bc5fd7b5ee9 100644 --- a/drivers/fastboot/fb_command.c +++ b/drivers/fastboot/fb_command.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -13,8 +14,10 @@ #include #include #include +#include #include #include +#include #include /** @@ -45,6 +48,8 @@ static void oem_partconf(char *, char *); static void oem_bootbus(char *, char *); static void oem_console(char *, char *); static void oem_board(char *, char *); +static void oem_set_block_target(char *, char *); +static void oem_spi_nor_init(char *, char *); static void run_ucmd(char *, char *); static void run_acmd(char *, char *); @@ -120,6 +125,16 @@ static const struct { .command = "oem board", .dispatch = CONFIG_IS_ENABLED(FASTBOOT_OEM_BOARD, (oem_board), (NULL)) }, + [FASTBOOT_COMMAND_OEM_SET_BLOCK_TARGET] = { + .command = "oem set-block-target", + .dispatch = CONFIG_IS_ENABLED(FASTBOOT_CMD_OEM_SET_BLOCK_TARGET, + (oem_set_block_target), (NULL)) + }, + [FASTBOOT_COMMAND_OEM_SPI_NOR_INIT] = { + .command = "oem spi-nor-init", + .dispatch = CONFIG_IS_ENABLED(FASTBOOT_CMD_OEM_SPI_NOR_INIT, + (oem_spi_nor_init), (NULL)) + }, [FASTBOOT_COMMAND_UCMD] = { .command = "UCmd", .dispatch = CONFIG_IS_ENABLED(FASTBOOT_UUU_SUPPORT, (run_ucmd), (NULL)) @@ -589,3 +604,82 @@ static void __maybe_unused oem_board(char *cmd_parameter, char *response) { fastboot_oem_board(cmd_parameter, (void *)fastboot_buf_addr, image_size, response); } + +/** + * oem_set_block_target() - Execute the OEM set-block-target command + * + * @cmd_parameter: Pointer to " [device]" command parameter + * @response: Pointer to fastboot response buffer + * + * Switches the block interface (e.g. "scsi", "mtd") and, optionally, + * device number used by the fastboot block backend at runtime. + */ +static void __maybe_unused oem_set_block_target(char *cmd_parameter, char *response) +{ + int device = -1; + char *space; + + if (!cmd_parameter) { + fastboot_fail("Expected command parameter", response); + return; + } + + space = strchr(cmd_parameter, ' '); + if (space) { + *space = '\0'; + device = simple_strtoul(space + 1, NULL, 10); + } + + if (fastboot_block_set_target(cmd_parameter, device)) + fastboot_fail("invalid interface name", response); + else + fastboot_okay(NULL, response); +} + +/** + * fastboot_oem_spi_nor_reinit() - Vendor hook for SPI-NOR re-bring-up. + * + * This is a default weak implementation, which may be overridden in + * SoC-specific code. It runs after the default SPI-NOR device has been + * removed and before it is reprobed, and can be used to redo any one-time, + * driver-external bring-up (e.g. reloading firmware from storage) that the + * device's own probe() cannot repeat on its own. + * + * Return: 0 on success, or a negative error code. + */ +int __weak fastboot_oem_spi_nor_reinit(void) +{ + return 0; +} + +/** + * oem_spi_nor_init() - Execute the OEM spi-nor-init command + * + * @cmd_parameter: Pointer to command parameter + * @response: Pointer to fastboot response buffer + * + * Removes the default SPI-NOR device and reprobes it, so the driver's + * probe() can perform any bring-up it needs. Useful when SPI-NOR is not + * usable until after fastboot has flashed something it depends on. + */ +static void __maybe_unused oem_spi_nor_init(char *cmd_parameter, char *response) +{ + const int bus = config_opt_enabled(CONFIG_DM_SPI_FLASH, CONFIG_SF_DEFAULT_BUS, 0); + const int cs = config_opt_enabled(CONFIG_DM_SPI_FLASH, CONFIG_SF_DEFAULT_CS, 0); + struct udevice *bus_dev, *new; + int ret; + + ret = fastboot_oem_spi_nor_reinit(); + if (ret) { + fastboot_fail("SPI-NOR vendor reinit failed", response); + return; + } + + ret = spi_get_bus_and_cs(bus, cs, &bus_dev, (struct spi_slave **)&new); + if (ret) { + fastboot_fail("failed to probe SPI-NOR", response); + return; + } + + fastboot_okay(NULL, response); +} diff --git a/drivers/misc/qcom_geni.c b/drivers/misc/qcom_geni.c index 40044e5d48a6..a0a7b7c943ba 100644 --- a/drivers/misc/qcom_geni.c +++ b/drivers/misc/qcom_geni.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -176,6 +177,7 @@ static int load_se_firmware(struct qup_se_rsc *rsc, bool elf, void *info) const u32 *fw_val_arr, *cfg_val_arr; const u8 *cfg_idx_arr; u32 i, reg_value, mask, ramn_cnt; + u32 hw_version, rx_fifo_depth_mask; int ret; if (elf) { @@ -251,8 +253,12 @@ static int load_se_firmware(struct qup_se_rsc *rsc, bool elf, void *info) rsc->base + GENI_CFG_REG0 + (cfg_idx_arr[i] * sizeof(u32))); /* Configure condition for assertion of RX_RFR_WATERMARK condition. */ - reg_value = readl_relaxed(rsc->base + QUPV3_SE_HW_PARAM_1); - mask = (reg_value >> RX_FIFO_WIDTH_BIT) & RX_FIFO_WIDTH_MASK; + hw_version = readl_relaxed(rsc->wrapper_base + QUP_HW_VER_REG); + rx_fifo_depth_mask = geni_se_fifo_depth_mask(hw_version, RX_FIFO_DEPTH_MSK_256_BYTES, + RX_FIFO_DEPTH_MSK); + + reg_value = readl_relaxed(rsc->base + SE_HW_PARAM_1); + mask = (reg_value & rx_fifo_depth_mask) >> RX_FIFO_DEPTH_SHFT; writel_relaxed(mask - 2, rsc->base + GENI_RX_RFR_WATERMARK_REG); /* Let hardware control CGC */ @@ -421,6 +427,10 @@ int qcom_geni_load_firmware(phys_addr_t qup_base, /* The firmware blob is the private data of the GENI wrapper (parent) */ fw = dev_get_priv(dev->parent); + if (!fw) { + dev_err(dev, "QUP firmware not available\n"); + return -ENOENT; + } if (IS_ELF(*(Elf32_Ehdr *)fw)) { ret = read_elf(&rsc, fw, &hdr); @@ -638,6 +648,11 @@ int qcom_geni_fw_probe(struct udevice *dev) EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, qcom_geni_fw_initialise); #endif +int fastboot_oem_spi_nor_reinit(void) +{ + return qcom_geni_fw_initialise(); +} + static const struct udevice_id geni_ids[] = { { .compatible = "qcom,geni-se-qup" }, {} diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 38d6dd142ddc..0c33c9c7fd1a 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -39,6 +39,15 @@ config MTD_BLOCK Enable support for block device access to MTD devices using blk_ops abstraction. +config MTD_BLOCK_SIZE + hex "MTD logical block size" + depends on MTD_BLOCK + default 0x1000 + help + Logical block size exposed by mtdblock/ubiblock devices to the + block layer. This is the sector size used for GPT partitioning, + not the underlying flash erase size. + config SYS_MTDPARTS_RUNTIME bool "Allow MTDPARTS to be configured at runtime" depends on !COMPILE_TEST diff --git a/drivers/mtd/mtdblock.c b/drivers/mtd/mtdblock.c index 66a79b8c56ae..27ba08304607 100644 --- a/drivers/mtd/mtdblock.c +++ b/drivers/mtd/mtdblock.c @@ -211,6 +211,8 @@ static int mtd_blk_probe(struct udevice *dev) pr_warn("MTD device '%s' is NAND, please use UBI devices instead\n", mtd->name); + bdesc->lba = lldiv(mtd->size, bdesc->blksz); + return 0; } diff --git a/drivers/mtd/spi/sf_probe.c b/drivers/mtd/spi/sf_probe.c index dee03662d45d..1540dfdb6171 100644 --- a/drivers/mtd/spi/sf_probe.c +++ b/drivers/mtd/spi/sf_probe.c @@ -9,12 +9,15 @@ #include #include +#include #include #include #include +#include #include #include #include +#include #include "sf_internal.h" @@ -100,6 +103,28 @@ static int spi_flash_probe_slave(struct spi_flash *flash) if (ret) goto err_read_id; +#if CONFIG_IS_ENABLED(MTD_BLOCK) + if (CONFIG_IS_ENABLED(DM_SPI_FLASH)) { + struct mtd_info **plat = dev_get_plat(flash->dev); + struct udevice *bdev; + struct blk_desc *bdesc; + + /* The mtdblock child is created by jedec_spi_nor_bind(). */ + ret = blk_find_from_parent(flash->dev, &bdev); + if (ret) { + dev_err(flash->dev, "mtdblock child not found: %d\n", ret); + goto err_read_id; + } + + bdesc = dev_get_uclass_plat(bdev); + *plat = &flash->mtd; + bdesc->blksz = CONFIG_MTD_BLOCK_SIZE; + bdesc->log2blksz = LOG2(bdesc->blksz); + bdesc->lba = lldiv(flash->mtd.size, bdesc->blksz); + bdesc->part_type = PART_TYPE_UNKNOWN; + } +#endif + if (CONFIG_IS_ENABLED(SPI_DIRMAP)) { ret = spi_nor_create_read_dirmap(flash); if (ret) @@ -253,13 +278,23 @@ static const struct udevice_id spi_flash_std_ids[] = { { } }; +static int jedec_spi_nor_bind(struct udevice *dev) +{ + if (!CONFIG_IS_ENABLED(MTD_BLOCK)) + return 0; + + return mtd_bind(dev, dev_get_plat(dev)); +} + U_BOOT_DRIVER(jedec_spi_nor) = { .name = "jedec_spi_nor", .id = UCLASS_SPI_FLASH, .of_match = spi_flash_std_ids, + .bind = jedec_spi_nor_bind, .probe = spi_flash_std_probe, .remove = spi_flash_std_remove, .priv_auto = sizeof(struct spi_nor), + .plat_auto = sizeof(struct mtd_info *), .ops = &spi_flash_std_ops, .flags = DM_FLAG_OS_PREPARE, }; diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 007ad5e77334..691b317a9fe0 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -463,6 +463,15 @@ config SPI_QUP mode supports up to 50MHz, up to four chip selects, programmable data path from 4 bits to 32 bits and numerous protocol variants. +config SPI_GENI_QCOM + bool "Qualcomm GENI SPI controller" + depends on DM_SPI && QCOM_GENI + help + Enable support for the SPI Serial Engine of the Qualcomm Generic + Interface (GENI) based Qualcomm Universal Peripheral (QUP) wrapper. + Used to access SPI devices such as SPI-NOR flash on Qualcomm SoCs + that have a GENI/QUPv3 wrapper, e.g. SDM845, SM8250, SA8775P. + config RENESAS_RPC_SPI bool "Renesas RPC SPI driver" depends on RCAR_64 || RZA1 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 13d9c5dce80b..5271e20ebd8c 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_OCTEON_SPI) += octeon_spi.o obj-$(CONFIG_OMAP3_SPI) += omap3_spi.o obj-$(CONFIG_PIC32_SPI) += pic32_spi.o obj-$(CONFIG_PL022_SPI) += pl022_spi.o +obj-$(CONFIG_SPI_GENI_QCOM) += qcom_geni_spi.o obj-$(CONFIG_SPI_QUP) += spi-qup.o obj-$(CONFIG_SPI_MXIC) += spi-mxic.o obj-$(CONFIG_RENESAS_RPC_SPI) += renesas_rpc_spi.o diff --git a/drivers/spi/qcom_geni_spi.c b/drivers/spi/qcom_geni_spi.c new file mode 100644 index 000000000000..7d114fed60b3 --- /dev/null +++ b/drivers/spi/qcom_geni_spi.c @@ -0,0 +1,779 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Qualcomm GENI QUPv3 SPI controller driver + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* SPI-protocol-specific SE registers, on top of the generic ones in geni-se.h */ +#define SE_SPI_CPHA 0x224 +#define SE_SPI_LOOPBACK 0x22c +#define SE_SPI_CPOL 0x230 +#define SE_SPI_DEMUX_OUTPUT_INV 0x24c +#define SE_SPI_DEMUX_SEL 0x250 +#define SE_SPI_TRANS_CFG 0x25c +#define SE_SPI_DELAYS_COUNTERS 0x278 +#define SE_SPI_WORD_LEN 0x268 +#define SE_SPI_TX_TRANS_LEN 0x26c +#define SE_SPI_RX_TRANS_LEN 0x270 + +#define CPHA BIT(0) + +#define LOOPBACK_ENABLE 0x1 +#define LOOPBACK_MSK GENMASK(1, 0) + +#define CPOL BIT(2) + +/* SE_SPI_TRANS_CFG */ +#define CS_TOGGLE BIT(1) + +#define WORD_LEN_MSK GENMASK(9, 0) +#define SPI_WORD_LEN_BITS 8 +#define MIN_WORD_LEN 4 + +#define SPI_TX_ONLY 1 +#define SPI_RX_ONLY 2 +#define SPI_TX_RX 7 +#define FRAGMENTATION BIT(2) + +#define SPI_ERR (M_CMD_OVERRUN_EN | M_ILLEGAL_CMD_EN | M_CMD_FAILURE_EN | \ + M_RX_FIFO_RD_ERR_EN | M_RX_FIFO_WR_ERR_EN | \ + M_TX_FIFO_RD_ERR_EN | M_TX_FIFO_WR_ERR_EN) + +#define SPI_XFER_TIMEOUT_MS 250 + +/* SPI-NOR reads/writes are page/sector sized; skip DMA setup below this */ +#define SPI_DMA_MIN_XFER_BYTES 64 + +struct qcom_geni_spi_priv { + phys_addr_t wrapper; + phys_addr_t base; + struct clk se; + u32 tx_wm; + u32 tx_fifo_depth; + u32 bpw; + u32 bytes_per_fifo_word; + u32 oversampling; + bool fifo_capable; +}; + +/* Bytes packed into each 32-bit FIFO word, based on word length */ +static unsigned int qcom_geni_spi_bytes_per_fifo_word(unsigned int bpw) +{ + if (bpw <= 8) + return 4; + else if (bpw <= 16) + return 2; + return 1; +} + +/* Scale the wait budget with transfer size instead of a flat constant */ +static ulong qcom_geni_spi_xfer_timeout_ms(struct qcom_geni_spi_priv *priv, + unsigned int len) +{ + ulong rate = clk_get_rate(&priv->se); + ulong ms; + + if (IS_ERR_VALUE(rate) || !rate) + return SPI_XFER_TIMEOUT_MS + len / 1000; + + /* len is in bytes; add generous margin for controller/DMA overhead */ + ms = DIV_ROUND_UP((u64)len * 8 * 1000, rate) * 4; + + return max_t(ulong, ms, SPI_XFER_TIMEOUT_MS); +} + +#define NUM_PACKING_VECTORS 4 +#define PACKING_START_SHIFT 5 +#define PACKING_DIR_SHIFT 4 +#define PACKING_LEN_SHIFT 1 +#define PACKING_STOP_BIT BIT(0) +#define PACKING_VECTOR_SHIFT 10 + +/* Configure how the SE packs/unpacks "bpw"-bit words into 32-bit FIFO entries */ +static void qcom_geni_spi_config_packing(struct qcom_geni_spi_priv *priv, int bpw, + bool msb_to_lsb) +{ + u32 cfg0, cfg1, cfg[NUM_PACKING_VECTORS] = {0}; + int len, temp_bpw = bpw; + int idx_start = msb_to_lsb ? bpw - 1 : 0; + int idx = idx_start; + int idx_delta = msb_to_lsb ? -BITS_PER_BYTE : BITS_PER_BYTE; + int i, iter, pack_words; + unsigned int ceil_bpw; + + if (bpw <= 8) + pack_words = 4; + else if (bpw <= 16) + pack_words = 2; + else + pack_words = 1; + + ceil_bpw = (bpw & (BITS_PER_BYTE - 1)) ? + ((bpw & ~(BITS_PER_BYTE - 1)) + BITS_PER_BYTE) : bpw; + + iter = (ceil_bpw * pack_words) >> 3; + if (iter <= 0 || iter > NUM_PACKING_VECTORS) + return; + + for (i = 0; i < iter; i++) { + len = min_t(int, temp_bpw, BITS_PER_BYTE) - 1; + cfg[i] = idx << PACKING_START_SHIFT; + cfg[i] |= msb_to_lsb << PACKING_DIR_SHIFT; + cfg[i] |= len << PACKING_LEN_SHIFT; + + if (temp_bpw <= BITS_PER_BYTE) { + idx = ((i + 1) * BITS_PER_BYTE) + idx_start; + temp_bpw = bpw; + } else { + idx = idx + idx_delta; + temp_bpw = temp_bpw - BITS_PER_BYTE; + } + } + cfg[iter - 1] |= PACKING_STOP_BIT; + cfg0 = cfg[0] | (cfg[1] << PACKING_VECTOR_SHIFT); + cfg1 = cfg[2] | (cfg[3] << PACKING_VECTOR_SHIFT); + + writel(cfg0, priv->base + SE_GENI_TX_PACKING_CFG0); + writel(cfg1, priv->base + SE_GENI_TX_PACKING_CFG1); + writel(cfg0, priv->base + SE_GENI_RX_PACKING_CFG0); + writel(cfg1, priv->base + SE_GENI_RX_PACKING_CFG1); + + writel(bpw / 16, priv->base + SE_GENI_BYTE_GRAN); +} + +static int qcom_geni_spi_fifo_xfer(struct qcom_geni_spi_priv *priv, const u8 *tx, + u8 *rx, unsigned int len, ulong timeout_ms) +{ + ulong start = get_timer(0); + unsigned int tx_cur = 0, rx_cur = 0; + + while (get_timer(start) < timeout_ms) { + u32 status = readl(priv->base + SE_GENI_M_IRQ_STATUS); + unsigned int i; + + if (status & SPI_ERR) { + writel(status, priv->base + SE_GENI_M_IRQ_CLEAR); + if (tx) + writel(0, priv->base + SE_GENI_TX_WATERMARK_REG); + return -EIO; + } + + if (tx && (status & M_TX_FIFO_WATERMARK_EN)) { + for (i = 0; i < priv->tx_wm && tx_cur < len; i++) { + u32 word = 0; + unsigned int p; + + for (p = 0; p < priv->bytes_per_fifo_word && tx_cur < len; p++) + word |= tx[tx_cur++] << (p * 8); + + writel(word, priv->base + SE_GENI_TX_FIFOn); + } + + if (tx_cur == len) + writel(0, priv->base + SE_GENI_TX_WATERMARK_REG); + } + + if (status & (M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN)) { + u32 rxstatus = readl(priv->base + SE_GENI_RX_FIFO_STATUS); + u32 rxcnt = rxstatus & RX_FIFO_WC_MSK; + + if (rx) { + for (i = 0; rx_cur < len && i < rxcnt; i++) { + u32 word = readl(priv->base + SE_GENI_RX_FIFOn); + unsigned int p; + + for (p = 0; p < priv->bytes_per_fifo_word && + rx_cur < len; p++) { + rx[rx_cur++] = word & 0xff; + word >>= 8; + } + } + } else { + for (i = 0; i < rxcnt; i++) + readl(priv->base + SE_GENI_RX_FIFOn); + } + } + + writel(status, priv->base + SE_GENI_M_IRQ_CLEAR); + + if (status & M_CMD_DONE_EN) { + /* Drain any residual RX words after CMD_DONE */ + if (rx && rx_cur < len) { + u32 rxstatus = readl(priv->base + SE_GENI_RX_FIFO_STATUS); + u32 rxcnt = rxstatus & RX_FIFO_WC_MSK; + unsigned int i; + + for (i = 0; rx_cur < len && i < rxcnt; i++) { + u32 word = readl(priv->base + SE_GENI_RX_FIFOn); + unsigned int p; + + for (p = 0; p < priv->bytes_per_fifo_word && + rx_cur < len; p++) { + rx[rx_cur++] = word & 0xff; + word >>= 8; + } + } + } + return 0; + } + } + + return -ETIMEDOUT; +} + +static int qcom_geni_spi_abort(struct udevice *dev, struct qcom_geni_spi_priv *priv) +{ + ulong start = get_timer(0); + u32 status; + + writel(M_GENI_CMD_ABORT, priv->base + SE_GENI_M_CMD_CTRL_REG); + + do { + status = readl(priv->base + SE_GENI_M_IRQ_STATUS); + if (get_timer(start) > SPI_XFER_TIMEOUT_MS) + return -ETIMEDOUT; + } while (!(status & M_CMD_ABORT_EN)); + + writel(status, priv->base + SE_GENI_M_IRQ_CLEAR); + + /* Reset TX/RX DMA FSMs so the next transfer starts clean */ + start = get_timer(0); + writel(1, priv->base + SE_DMA_TX_FSM_RST); + do { + status = readl(priv->base + SE_DMA_TX_IRQ_STAT); + if (get_timer(start) > SPI_XFER_TIMEOUT_MS) { + dev_err(dev, "DMA TX RESET failed\n"); + break; + } + } while (!(status & TX_RESET_DONE)); + writel(status, priv->base + SE_DMA_TX_IRQ_CLR); + + start = get_timer(0); + writel(1, priv->base + SE_DMA_RX_FSM_RST); + do { + status = readl(priv->base + SE_DMA_RX_IRQ_STAT); + if (get_timer(start) > SPI_XFER_TIMEOUT_MS) { + dev_err(dev, "DMA RX RESET failed\n"); + break; + } + } while (!(status & RX_RESET_DONE)); + writel(status, priv->base + SE_DMA_RX_IRQ_CLR); + + return 0; +} + +/* SE-DMA: the SE moves data directly to/from a physical buffer */ +static void qcom_geni_spi_dma_tx_start(struct qcom_geni_spi_priv *priv, const u8 *tx, + unsigned int len) +{ + phys_addr_t buf = (phys_addr_t)(uintptr_t)tx; + + flush_dcache_range(ALIGN_DOWN((ulong)tx, ARCH_DMA_MINALIGN), + ALIGN((ulong)tx + len, ARCH_DMA_MINALIGN)); + + writel(lower_32_bits(buf), priv->base + SE_DMA_TX_PTR_L); + writel(upper_32_bits(buf), priv->base + SE_DMA_TX_PTR_H); + writel(GENI_SE_DMA_EOT_BUF, priv->base + SE_DMA_TX_ATTR); + writel(len, priv->base + SE_DMA_TX_LEN); +} + +static void qcom_geni_spi_dma_rx_start(struct qcom_geni_spi_priv *priv, u8 *rx, + unsigned int len) +{ + phys_addr_t buf = (phys_addr_t)(uintptr_t)rx; + + /* Discard stale dirty cache lines before the DMA write lands */ + invalidate_dcache_range(ALIGN_DOWN((ulong)rx, ARCH_DMA_MINALIGN), + ALIGN((ulong)rx + len, ARCH_DMA_MINALIGN)); + + writel(lower_32_bits(buf), priv->base + SE_DMA_RX_PTR_L); + writel(upper_32_bits(buf), priv->base + SE_DMA_RX_PTR_H); + writel(0, priv->base + SE_DMA_RX_ATTR); + writel(len, priv->base + SE_DMA_RX_LEN); +} + +static int qcom_geni_spi_dma_wait_tx(struct qcom_geni_spi_priv *priv, ulong timeout_ms) +{ + ulong start = get_timer(0); + u32 status; + + while (get_timer(start) < timeout_ms) { + status = readl(priv->base + SE_DMA_TX_IRQ_STAT); + if (!status) { + udelay(1); + continue; + } + + writel(status, priv->base + SE_DMA_TX_IRQ_CLR); + + if (status & TX_SBE) + return -EIO; + if (status & TX_DMA_DONE) + return 0; + } + + return -ETIMEDOUT; +} + +static int qcom_geni_spi_dma_wait_rx(struct qcom_geni_spi_priv *priv, u8 *rx, + unsigned int len, ulong timeout_ms) +{ + ulong start = get_timer(0); + u32 status; + + while (get_timer(start) < timeout_ms) { + status = readl(priv->base + SE_DMA_RX_IRQ_STAT); + if (!status) { + udelay(1); + continue; + } + + writel(status, priv->base + SE_DMA_RX_IRQ_CLR); + + if (status & RX_SBE) + return -EIO; + if (status & RX_DMA_DONE) { + /* Force the AXI write to retire before trusting the buffer */ + readl(priv->base + SE_DMA_RX_LEN); + invalidate_dcache_range(ALIGN_DOWN((ulong)rx, ARCH_DMA_MINALIGN), + ALIGN((ulong)rx + len, ARCH_DMA_MINALIGN)); + return 0; + } + } + + return -ETIMEDOUT; +} + +static int qcom_geni_spi_dma_xfer_wait(struct qcom_geni_spi_priv *priv, const u8 *tx, + u8 *rx, unsigned int len, ulong timeout_ms) +{ + int ret; + + if (tx) { + ret = qcom_geni_spi_dma_wait_tx(priv, timeout_ms); + if (ret) + return ret; + } + + if (rx) { + ret = qcom_geni_spi_dma_wait_rx(priv, rx, len, timeout_ms); + if (ret) + return ret; + } + + return 0; +} + +/* TX/RX_TRANS_LEN and DMA_TX/RX_LEN are 24-bit HW fields; split large transfers */ +#define SPI_GENI_MAX_XFER_BYTES 0xFFFFFF + +/* RX DMA requires a cache-line-aligned start and length to invalidate safely */ +static bool qcom_geni_spi_rx_needs_bounce(const void *rx, unsigned int len) +{ + return !IS_ALIGNED((ulong)rx, ARCH_DMA_MINALIGN) || + !IS_ALIGNED(len, ARCH_DMA_MINALIGN); +} + +static int qcom_geni_spi_xfer_chunk(struct udevice *dev, struct qcom_geni_spi_priv *priv, + unsigned int len, const void *dout, void *din, + bool use_dma, bool last_chunk, ulong timeout_ms) +{ + u32 cmd, m_param = 0; + int ret; + + writel(dout ? len : 0, priv->base + SE_SPI_TX_TRANS_LEN); + writel(din ? len : 0, priv->base + SE_SPI_RX_TRANS_LEN); + + /* SPI_TX_ONLY | SPI_RX_ONLY is not a valid opcode, use SPI_TX_RX instead */ + if (dout && din) + cmd = SPI_TX_RX; + else if (din) + cmd = SPI_RX_ONLY; + else if (dout) + cmd = SPI_TX_ONLY; + else + cmd = 0; + + if (!last_chunk) + m_param |= FRAGMENTATION; + + if (use_dma) { + setbits_le32(priv->base + SE_GENI_DMA_MODE_EN, GENI_DMA_MODE_EN); + + /* DMA: issue M_CMD0 first, then arm DMA descriptors */ + writel((cmd << M_OPCODE_SHFT) | (m_param & M_PARAMS_MSK), + priv->base + SE_GENI_M_CMD0); + if (din) + qcom_geni_spi_dma_rx_start(priv, din, len); + if (dout) + qcom_geni_spi_dma_tx_start(priv, dout, len); + + ret = qcom_geni_spi_dma_xfer_wait(priv, dout, din, len, timeout_ms); + + /* Clear M_CMD_DONE so status doesn't accumulate across transfers */ + writel(readl(priv->base + SE_GENI_M_IRQ_STATUS), + priv->base + SE_GENI_M_IRQ_CLEAR); + } else { + clrbits_le32(priv->base + SE_GENI_DMA_MODE_EN, GENI_DMA_MODE_EN); + + /* + * Set watermarks before M_CMD0. Only set TX_WATERMARK when + * we have TX data, since SPI_RX_ONLY doesn't use the TX FIFO. + */ + if (dout) + writel(1, priv->base + SE_GENI_TX_WATERMARK_REG); + /* RX_WATERMARK=0: SE fires RX_FIFO_LAST at end of transfer */ + if (din) + writel(0, priv->base + SE_GENI_RX_WATERMARK_REG); + + writel((cmd << M_OPCODE_SHFT) | (m_param & M_PARAMS_MSK), + priv->base + SE_GENI_M_CMD0); + + ret = qcom_geni_spi_fifo_xfer(priv, dout, din, len, timeout_ms); + } + + return ret; +} + +static int qcom_geni_spi_xfer_once(struct udevice *dev, unsigned int len, + const void *dout, void *din, bool xfer_end) +{ + struct udevice *bus = dev_get_parent(dev); + struct qcom_geni_spi_priv *priv = dev_get_priv(bus); + ulong timeout_ms = qcom_geni_spi_xfer_timeout_ms(priv, len); + bool use_dma = !priv->fifo_capable || len >= SPI_DMA_MIN_XFER_BYTES; + u8 edge_buf[2][ARCH_DMA_MINALIGN] __aligned(ARCH_DMA_MINALIGN); + unsigned int head_len = 0, tail_len = 0, mid_len = len, done = 0; + int ret = 0; + + if (din && use_dma && qcom_geni_spi_rx_needs_bounce(din, len)) { + head_len = min_t(unsigned int, + ALIGN((ulong)din, ARCH_DMA_MINALIGN) - (ulong)din, + len); + tail_len = (len - head_len) % ARCH_DMA_MINALIGN; + mid_len = len - head_len - tail_len; + } + + if (head_len) { + ret = qcom_geni_spi_xfer_chunk(dev, priv, head_len, dout, edge_buf[0], + use_dma, xfer_end && !mid_len && !tail_len, + timeout_ms); + if (!ret) + memcpy(din, edge_buf[0], head_len); + done += head_len; + } + + if (!ret && mid_len) { + ret = qcom_geni_spi_xfer_chunk(dev, priv, mid_len, + dout ? (const u8 *)dout + done : NULL, + din ? (u8 *)din + done : NULL, + use_dma, xfer_end && !tail_len, timeout_ms); + done += mid_len; + } + + if (!ret && tail_len) { + ret = qcom_geni_spi_xfer_chunk(dev, priv, tail_len, + dout ? (const u8 *)dout + done : NULL, + edge_buf[1], use_dma, xfer_end, timeout_ms); + if (!ret) + memcpy((u8 *)din + done, edge_buf[1], tail_len); + } + + if (ret) { + int abort_ret = qcom_geni_spi_abort(dev, priv); + + if (abort_ret) + dev_err(dev, "abort failed: %d\n", abort_ret); + } + + return ret; +} + +static int qcom_geni_spi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev_get_parent(dev); + struct qcom_geni_spi_priv *priv = dev_get_priv(bus); + unsigned int len = DIV_ROUND_UP(bitlen, 8); + unsigned int done = 0; + int ret; + + if (flags & SPI_XFER_BEGIN) + writel(0xffffffff, priv->base + SE_GENI_M_IRQ_CLEAR); + + while (done < len) { + unsigned int chunk = min_t(unsigned int, len - done, + SPI_GENI_MAX_XFER_BYTES); + bool xfer_end = (flags & SPI_XFER_END) && (done + chunk == len); + + ret = qcom_geni_spi_xfer_once(dev, + chunk, + dout ? (const u8 *)dout + done : NULL, + din ? (u8 *)din + done : NULL, + xfer_end); + if (ret) + return ret; + + done += chunk; + } + + return 0; +} + +static int qcom_geni_spi_set_speed(struct udevice *bus, uint speed) +{ + struct qcom_geni_spi_priv *priv = dev_get_priv(bus); + ulong parent_rate; + u32 div; + + if (!speed) + return -EINVAL; + + parent_rate = clk_get_rate(&priv->se); + if (IS_ERR_VALUE(parent_rate) || !parent_rate) + div = priv->oversampling; + else + div = DIV_ROUND_UP(parent_rate, priv->oversampling * speed); + + div = clamp_t(u32, div, 1, CLK_DIV_MSK >> CLK_DIV_SHFT); + + writel(0, priv->base + SE_GENI_CLK_SEL); + writel((div << CLK_DIV_SHFT) | SER_CLK_EN, priv->base + GENI_SER_M_CLK_CFG); + + return 0; +} + +static int qcom_geni_spi_set_mode(struct udevice *bus, uint mode) +{ + struct qcom_geni_spi_priv *priv = dev_get_priv(bus); + u32 val; + + val = readl(priv->base + SE_SPI_LOOPBACK); + val &= ~LOOPBACK_MSK; + if (mode & SPI_LOOP) + val |= LOOPBACK_ENABLE; + writel(val, priv->base + SE_SPI_LOOPBACK); + + val = readl(priv->base + SE_SPI_CPHA); + if (mode & SPI_CPHA) + val |= CPHA; + else + val &= ~CPHA; + writel(val, priv->base + SE_SPI_CPHA); + + val = readl(priv->base + SE_SPI_CPOL); + if (mode & SPI_CPOL) + val |= CPOL; + else + val &= ~CPOL; + writel(val, priv->base + SE_SPI_CPOL); + + return 0; +} + +static int qcom_geni_spi_claim_bus(struct udevice *dev) +{ + struct udevice *bus = dev_get_parent(dev); + struct qcom_geni_spi_priv *priv = dev_get_priv(bus); + struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev); + unsigned int cs = slave_plat->cs[0]; + unsigned int bpw = slave_plat->wordlen ? slave_plat->wordlen : SPI_WORD_LEN_BITS; + + priv->bpw = bpw; + priv->bytes_per_fifo_word = qcom_geni_spi_bytes_per_fifo_word(bpw); + + writel((bpw - MIN_WORD_LEN) & WORD_LEN_MSK, priv->base + SE_SPI_WORD_LEN); + writel(cs, priv->base + SE_SPI_DEMUX_SEL); + writel(slave_plat->mode & SPI_CS_HIGH ? BIT(cs) : 0, + priv->base + SE_SPI_DEMUX_OUTPUT_INV); + + qcom_geni_spi_config_packing(priv, bpw, true); + + writel(0xffffffff, priv->base + SE_GENI_M_IRQ_CLEAR); + + return 0; +} + +static int qcom_geni_spi_release_bus(struct udevice *dev) +{ + return 0; +} + +static u32 qcom_geni_spi_get_tx_fifo_depth(struct qcom_geni_spi_priv *priv) +{ + u32 val, hw_version, depth_mask; + + hw_version = readl(priv->wrapper + QUP_HW_VER_REG); + depth_mask = geni_se_fifo_depth_mask(hw_version, TX_FIFO_DEPTH_MSK_256_BYTES, + TX_FIFO_DEPTH_MSK); + + val = readl(priv->base + SE_HW_PARAM_0); + + return (val & depth_mask) >> TX_FIFO_DEPTH_SHFT; +} + +/* QUP v1.0 undersamples the SPI clock and needs 2x the requested bit rate */ +static u32 qcom_geni_spi_get_oversampling(struct qcom_geni_spi_priv *priv) +{ + u32 hw_version = readl(priv->wrapper + QUP_HW_VER_REG); + u32 hw_major = GENI_SE_VERSION_MAJOR(hw_version); + u32 hw_minor = GENI_SE_VERSION_MINOR(hw_version); + + if (hw_major == 1 && hw_minor == 0) + return 2; + + return 1; +} + +static void qcom_geni_spi_hw_init(struct qcom_geni_spi_priv *priv) +{ + u32 val; + + writel(0xffffffff, priv->base + SE_GENI_M_IRQ_CLEAR); + + val = readl(priv->base + GENI_CGC_CTRL); + val |= DEFAULT_CGC_EN; + writel(val, priv->base + GENI_CGC_CTRL); + + writel(DEFAULT_IO_OUTPUT_CTRL_MSK, priv->base + GENI_OUTPUT_CTRL); + writel(FORCE_DEFAULT, priv->base + GENI_FORCE_DEFAULT_REG); + + val = readl(priv->base + SE_IRQ_EN); + val |= GENI_M_IRQ_EN; + writel(val, priv->base + SE_IRQ_EN); + + writel(priv->tx_wm, priv->base + SE_GENI_TX_WATERMARK_REG); + + val = readl(priv->base + SE_GENI_M_IRQ_EN); + val |= M_COMMON_GENI_M_IRQ_EN | M_CMD_DONE_EN | SPI_ERR; + writel(val, priv->base + SE_GENI_M_IRQ_EN); + + writel(0xffffffff, priv->base + SE_DMA_TX_IRQ_CLR); + writel(0xffffffff, priv->base + SE_DMA_RX_IRQ_CLR); + writel(TX_DMA_DONE | TX_SBE, priv->base + SE_DMA_TX_IRQ_EN_SET); + writel(RX_DMA_DONE | RX_SBE, priv->base + SE_DMA_RX_IRQ_EN_SET); + + /* We always control CS manually, don't let the SE auto-toggle it */ + val = readl(priv->base + SE_SPI_TRANS_CFG); + val &= ~CS_TOGGLE; + writel(val, priv->base + SE_SPI_TRANS_CFG); +} + +static int qcom_geni_spi_probe(struct udevice *dev) +{ + struct qcom_geni_spi_priv *priv = dev_get_priv(dev); + u32 proto; + int ret; + + priv->wrapper = dev_read_addr(dev->parent); + if (priv->wrapper == FDT_ADDR_T_NONE) + return -EINVAL; + + priv->base = dev_read_addr(dev); + if (priv->base == FDT_ADDR_T_NONE) + return -EINVAL; + + ret = clk_get_by_name(dev, "se", &priv->se); + if (ret) { + dev_err(dev, "clk_get_by_name(se) failed: %d\n", ret); + return ret; + } + + ret = clk_enable(&priv->se); + if (ret) { + dev_err(dev, "clk_enable(se) failed: %d\n", ret); + return ret; + } + + proto = readl(priv->base + GENI_FW_REVISION_RO); + proto &= FW_REV_PROTOCOL_MSK; + proto >>= FW_REV_PROTOCOL_SHFT; + + if (proto == GENI_SE_INVALID_PROTO) { + dev_dbg(dev, "firmware not loaded, loading now\n"); + ret = qcom_geni_load_firmware(priv->base, dev); + if (ret) { + dev_err(dev, "firmware load failed: %d\n", ret); + clk_disable(&priv->se); + return ret; + } + proto = readl(priv->base + GENI_FW_REVISION_RO); + proto &= FW_REV_PROTOCOL_MSK; + proto >>= FW_REV_PROTOCOL_SHFT; + dev_info(dev, "firmware loaded, proto=0x%x\n", proto); + } else { + dev_info(dev, "firmware already loaded, proto=0x%x\n", proto); + } + + if (proto != GENI_SE_SPI) { + dev_err(dev, "Invalid proto %d\n", proto); + clk_disable(&priv->se); + return -ENXIO; + } + + /* + * SE-DMA is an inherent capability of the GENI SE core (mirrors + * upstream Linux's spi-geni-qcom.c, which never gates SE-DMA by DT + * or hardware version). Only FIFO availability needs checking here. + */ + priv->fifo_capable = !(readl(priv->base + GENI_IF_DISABLE_RO) & FIFO_IF_DISABLE); + + priv->tx_fifo_depth = qcom_geni_spi_get_tx_fifo_depth(priv); + if (!priv->tx_fifo_depth) { + dev_err(dev, "Invalid TX FIFO depth\n"); + clk_disable(&priv->se); + return -ENXIO; + } + priv->tx_wm = priv->tx_fifo_depth - 1; + priv->oversampling = qcom_geni_spi_get_oversampling(priv); + + qcom_geni_spi_hw_init(priv); + + return 0; +} + +static const struct dm_spi_ops qcom_geni_spi_ops = { + .claim_bus = qcom_geni_spi_claim_bus, + .release_bus = qcom_geni_spi_release_bus, + .xfer = qcom_geni_spi_xfer, + .set_speed = qcom_geni_spi_set_speed, + .set_mode = qcom_geni_spi_set_mode, + /* + * cs_info is not needed, since we require all chip selects to be + * in the device tree explicitly + */ +}; + +static const struct udevice_id qcom_geni_spi_ids[] = { + { .compatible = "qcom,geni-spi" }, + { } +}; + +U_BOOT_DRIVER(qcom_geni_spi) = { + .name = "qcom_geni_spi", + .id = UCLASS_SPI, + .of_match = qcom_geni_spi_ids, + .probe = qcom_geni_spi_probe, + .priv_auto = sizeof(struct qcom_geni_spi_priv), + .ops = &qcom_geni_spi_ops, +}; diff --git a/include/fastboot.h b/include/fastboot.h index b106d6177493..f6496b3bf99d 100644 --- a/include/fastboot.h +++ b/include/fastboot.h @@ -51,6 +51,8 @@ enum { FASTBOOT_COMMAND_OEM_RUN, FASTBOOT_COMMAND_OEM_CONSOLE, FASTBOOT_COMMAND_OEM_BOARD, + FASTBOOT_COMMAND_OEM_SET_BLOCK_TARGET, + FASTBOOT_COMMAND_OEM_SPI_NOR_INIT, FASTBOOT_COMMAND_ACMD, FASTBOOT_COMMAND_UCMD, FASTBOOT_COMMAND_COUNT @@ -105,6 +107,19 @@ void fastboot_okay(const char *reason, char *response); */ int fastboot_set_reboot_flag(enum fastboot_reboot_reason reason); +/** + * fastboot_oem_spi_nor_reinit() - Vendor hook for SPI-NOR re-bring-up + * + * Called by the "oem spi-nor-init" command after removing the default + * SPI-NOR device and before reprobing it. The default implementation is a + * no-op; SoC-specific code may override it to redo any one-time bring-up + * (e.g. reloading firmware from storage) that the device's own probe() + * cannot repeat on its own. + * + * Return: 0 on success, or a negative error code. + */ +int fastboot_oem_spi_nor_reinit(void); + /** * fastboot_set_progress_callback() - set progress callback * diff --git a/include/fb_block.h b/include/fb_block.h index 189c708e2f0e..fd981d95316e 100644 --- a/include/fb_block.h +++ b/include/fb_block.h @@ -102,4 +102,13 @@ void fastboot_block_write_sparse_image(struct blk_desc *dev_desc, struct disk_pa void fastboot_block_flash_write(const char *part_name, void *download_buffer, u32 download_bytes, char *response); +/** + * fastboot_block_set_target() - Override block interface/device at runtime + * + * @interface: New interface name (e.g. "scsi", "mtd"), or NULL to leave unchanged + * @device: New device number, or -1 to leave unchanged + * Return: 0 on success, -EINVAL if interface name too long + */ +int fastboot_block_set_target(const char *interface, int device); + #endif // _FB_BLOCK_H_ diff --git a/include/soc/qcom/geni-se.h b/include/soc/qcom/geni-se.h index 3063b37010de..d76365b3b9cf 100644 --- a/include/soc/qcom/geni-se.h +++ b/include/soc/qcom/geni-se.h @@ -64,10 +64,18 @@ enum geni_se_protocol_type { #define SE_GENI_RX_WATERMARK_REG 0x810 #define SE_GENI_RX_RFR_WATERMARK_REG 0x814 #define SE_GENI_IOS 0x908 +#define SE_DMA_TX_PTR_L 0xc30 +#define SE_DMA_TX_PTR_H 0xc34 +#define SE_DMA_TX_ATTR 0xc38 +#define SE_DMA_TX_LEN 0xc3c #define SE_DMA_TX_IRQ_STAT 0xc40 #define SE_DMA_TX_IRQ_CLR 0xc44 #define SE_DMA_TX_IRQ_EN_SET 0xc4c #define SE_DMA_TX_FSM_RST 0xc58 +#define SE_DMA_RX_PTR_L 0xd30 +#define SE_DMA_RX_PTR_H 0xd34 +#define SE_DMA_RX_ATTR 0xd38 +#define SE_DMA_RX_LEN 0xd3c #define SE_DMA_RX_IRQ_STAT 0xd40 #define SE_DMA_RX_IRQ_CLR 0xd44 #define SE_DMA_RX_IRQ_EN_SET 0xd4c @@ -282,6 +290,30 @@ enum geni_se_protocol_type { #define GENI_SE_VERSION_MINOR(ver) ((ver & HW_VER_MINOR_MASK) >> HW_VER_MINOR_SHFT) #define GENI_SE_VERSION_STEP(ver) (ver & HW_VER_STEP_MASK) +/* + * geni_se_fifo_depth_mask() - Pick the TX/RX SE_HW_PARAM_x fifo depth mask + * for a given QUP_HW_VER_REG value. + * @hw_version: value read from QUP_HW_VER_REG + * @depth_mask_256: mask to use on HW that supports 256-byte-deep fifos + * (QUP HW version >= 3.10, 8-bit depth field) + * @depth_mask: mask to use on older HW (6-bit depth field) + * + * QUP HW version >= 3.10 widened the fifo depth field in SE_HW_PARAM_0 + * (TX) and SE_HW_PARAM_1 (RX) from 6 bits to 8 bits; both fields are + * gated by the same major/minor check. + */ +static inline u32 geni_se_fifo_depth_mask(u32 hw_version, u32 depth_mask_256, + u32 depth_mask) +{ + u32 hw_major = GENI_SE_VERSION_MAJOR(hw_version); + u32 hw_minor = GENI_SE_VERSION_MINOR(hw_version); + + if ((hw_major == 3 && hw_minor >= 10) || hw_major > 3) + return depth_mask_256; + + return depth_mask; +} + /* QUP SE VERSION value for major number 2 and minor number 5 */ #define QUP_SE_VERSION_2_5 0x20050000