diff --git a/doc/hardware/peripherals/index.rst b/doc/hardware/peripherals/index.rst index acb12942c236d..6302a81bb60d6 100644 --- a/doc/hardware/peripherals/index.rst +++ b/doc/hardware/peripherals/index.rst @@ -58,6 +58,7 @@ Peripherals stepper.rst smbus.rst uart.rst + ufshc.rst usbc_vbus.rst tcpc.rst tgpio.rst diff --git a/doc/hardware/peripherals/ufshc.rst b/doc/hardware/peripherals/ufshc.rst new file mode 100644 index 0000000000000..0b469e572f225 --- /dev/null +++ b/doc/hardware/peripherals/ufshc.rst @@ -0,0 +1,61 @@ +.. _ufs_api: + +Universal Flash Storage (UFS) +############################# + +The UFS Host Controller driver api offers a generic interface for interacting +with a UFS host controller device. It is used by the UFS subsystem, and is +not intended to be directly used by the application. + +UFS Host Controller (UFSHC) Overview +************************************ + +The UFS host controller driver is based on SCSI Framework. UFSHC is a low +level device driver for UFS subsystem. The UFS subsystem acts as an interface +between SCSI and UFS Host controllers. + +The current UFS subsystem implementation supports the following functionality: + +UFS controller initialization +============================= + +Prepares the UFS Host Controller to transfer commands/responses between UFSHC +and UFS device. + +.. _ufs-init-api: + +Transfer requests +================= + +It handles SCSI commands from SCSI subsystem, form UPIUs and issues UPIUs to +the UFS Host controller. + +.. _ufs-scsi-api: + +Query Request +============= + +It handles query requests which are used to modify and retrieve configuration +information of the device. + +.. _ufs-query-api: + +Configuration Options +********************* + +Related configuration options: + +* :kconfig:option:`CONFIG_UFSHC` +* :kconfig:option:`CONFIG_UFS_STACK` +* :kconfig:option:`CONFIG_SCSI` + +API Reference +************* + +UFS subsystem APIs are provided by ufs.h + +.. doxygengroup:: ufs_interface + +UFS Host Controller (UFSHC) APIs are provided by ufshc.h + +.. doxygengroup:: ufshc_interface diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 50f64359a3916..b1ffe74f6f7d6 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -88,6 +88,7 @@ add_subdirectory_ifdef(CONFIG_STEPPER stepper) add_subdirectory_ifdef(CONFIG_SYSCON syscon) add_subdirectory_ifdef(CONFIG_SYS_CLOCK_EXISTS timer) add_subdirectory_ifdef(CONFIG_TEE tee) +add_subdirectory_ifdef(CONFIG_UFSHC ufshc) add_subdirectory_ifdef(CONFIG_VIDEO video) add_subdirectory_ifdef(CONFIG_VIRTUALIZATION virtualization) add_subdirectory_ifdef(CONFIG_W1 w1) diff --git a/drivers/Kconfig b/drivers/Kconfig index 401220c49177a..f876d43b839ad 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -85,6 +85,7 @@ source "drivers/spi/Kconfig" source "drivers/stepper/Kconfig" source "drivers/syscon/Kconfig" source "drivers/timer/Kconfig" +source "drivers/ufshc/Kconfig" source "drivers/usb/Kconfig" source "drivers/usb_c/Kconfig" source "drivers/video/Kconfig" diff --git a/drivers/disk/CMakeLists.txt b/drivers/disk/CMakeLists.txt index 0202238a1d2ac..5bde04b4d5f36 100644 --- a/drivers/disk/CMakeLists.txt +++ b/drivers/disk/CMakeLists.txt @@ -8,6 +8,7 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_FLASH flashdisk.c) zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_RAM ramdisk.c) zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_LOOPBACK loopback_disk.c) +zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_UFS ufsdisk.c) zephyr_library_sources_ifdef(CONFIG_SDMMC_STM32 sdmmc_stm32.c) zephyr_library_sources_ifdef(CONFIG_SDMMC_SUBSYS sdmmc_subsys.c) diff --git a/drivers/disk/Kconfig b/drivers/disk/Kconfig index 9c1723e6e8d3d..3a608afbb468c 100644 --- a/drivers/disk/Kconfig +++ b/drivers/disk/Kconfig @@ -13,6 +13,7 @@ source "drivers/disk/Kconfig.flash" source "drivers/disk/Kconfig.sdmmc" source "drivers/disk/Kconfig.mmc" source "drivers/disk/Kconfig.loopback" +source "drivers/disk/Kconfig.ufs" rsource "nvme/Kconfig" diff --git a/drivers/disk/Kconfig.ufs b/drivers/disk/Kconfig.ufs new file mode 100644 index 0000000000000..e0831daac35df --- /dev/null +++ b/drivers/disk/Kconfig.ufs @@ -0,0 +1,30 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +config DISK_DRIVER_UFS + bool "Disk operations on UFS card" + default y if DT_HAS_ZEPHYR_UFS_DISK_ENABLED + select SCSI + select UFS_STACK + help + UFS device as disk driver. + +if DISK_DRIVER_UFS + +config UFSDISK_INIT_PRIORITY + int "Init priority" + default 90 + help + UFS disk device driver initialization priority + +config UFS_BUFFER_SIZE + int + default 4096 + help + Size in bytes of internal buffer UFS card uses for unaligned reads. + +module = UFSDISK +module-str = ufsdisk +source "subsys/logging/Kconfig.template.log_config" + +endif # DISK_DRIVER_UFS diff --git a/drivers/disk/ufsdisk.c b/drivers/disk/ufsdisk.c new file mode 100644 index 0000000000000..4fa488a16eea4 --- /dev/null +++ b/drivers/disk/ufsdisk.c @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * UFS disk driver using zephyr UFS-SCSI subsystem + * - Uses scsi read/write APIs for disk read/write operations. + * - Uses IOCTL to issue UFS Query requests. + * - LUN number from device tree should be enabled in UFS device. + * - LUN-0 is used, when LUN is not specified in device tree. + * - For unaligned memory read/writes, a local buffer is used. + */ + +#define DT_DRV_COMPAT zephyr_ufs_disk + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ufsdisk, CONFIG_UFSDISK_LOG_LEVEL); + +/** + * @brief UFS disk status. + */ +enum ufsdisk_status { + UFS_UNINIT, /**< Un-Initialized UFS Disk. */ + UFS_ERROR, /**< UFS Disk Initialization Failed. */ + UFS_OK, /**< UFS Disk Operational. */ +}; + +/** + * @brief Runtime data structure for a UFS disk. + */ +struct ufsdisk_data { + struct disk_info info; /**< Disk information structure. */ + const struct device *ufshc_device; /**< UFS Host Controller device. */ + uint8_t lun; /**< Logical Unit Number (LUN) for the disk. */ + struct ufs_host_controller *ufshc; /**< UFS Host Controller instance. */ + struct scsi_device *sdev; /**< SCSI device associated with the UFS disk. */ + uint8_t *card_buffer; /**< Temporary buffer for unaligned I/O operations. */ + enum ufsdisk_status status; /**< Current status of the UFS disk. */ +}; + +/** + * @brief Initializes the UFS disk access. + * + * This function registers the host controller and verify the device is ready. + * It also allocates a buffer for unaligned reads/writes. + * + * @param disk Pointer to the disk_info structure representing the UFS disk. + * + * @return 0 on success, or a negative error code on failure. + */ +static int32_t disk_ufs_access_init(struct disk_info *disk) +{ + struct ufsdisk_data *data = (struct ufsdisk_data *)disk; + int32_t err; + + if (data->status == UFS_OK) { + /* called twice, don't reinit */ + err = 0; + goto out; + } + + /* set status as error */ + data->status = UFS_ERROR; + + /* Register host controller with UFS card */ + if ((data->ufshc != NULL) && (data->ufshc->is_initialized)) { + err = 0; /* already initialized */ + } else { + err = ufs_init(data->ufshc_device, &data->ufshc); + if (err != 0) { + LOG_ERR("UFS initialization failed %d", err); + goto out; + } + } + + /* Look up the SCSI device for the specified LUN */ + data->sdev = scsi_device_lookup_by_host(data->ufshc->host, data->lun); + if (data->sdev == NULL) { + LOG_ERR("SCSI device for lun:%d is NULL", data->lun); + err = -ENOTSUP; + goto out; + } + + /* Verify device readiness */ + err = scsi_ioctl(data->sdev, SCSI_IOCTL_TEST_UNIT_READY, NULL); + if (err != 0) { + LOG_ERR("Failed to execute TUR, lun:%d", data->lun); + goto out; + } + + /* Allocate a temporary buffer for unaligned reads/writes */ + data->card_buffer = (uint8_t *)k_aligned_alloc(CONFIG_UFSHC_BUFFER_ALIGNMENT, + CONFIG_UFS_BUFFER_SIZE); + if (data->card_buffer == NULL) { + err = -ENOMEM; + goto out; + } + + /* Initialization complete */ + data->status = UFS_OK; + +out: + return err; +} + +/** + * @brief Returns the current status of the UFS disk. + * + * @param disk Pointer to the disk_info structure representing the UFS disk. + * + * @return Disk status + */ +static int32_t disk_ufs_access_status(struct disk_info *disk) +{ + struct ufsdisk_data *data = (struct ufsdisk_data *)disk; + int32_t err; + + if (data->status == UFS_OK) { + err = DISK_STATUS_OK; + } else { + err = DISK_STATUS_UNINIT; + } + + return err; +} + +/** + * @brief Reads data from UFS card using SCSI read API. + * + * @param disk Pointer to the disk_info structure representing the UFS disk. + * @param rbuf Pointer to the buffer where the read data will be stored. + * @param start_block The starting block number to read from. + * @param num_blocks The number of blocks to read. + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t ufs_card_read_blocks(struct disk_info *disk, + uint8_t *rbuf, + uint32_t start_block, + uint32_t num_blocks) +{ + int32_t ret; + uint32_t rlen; + uint32_t sector = 0; + uint32_t buf_offset = 0; + + /* Get the UFS device and host controller from the SCSI device */ + struct ufsdisk_data *disk_data = (struct ufsdisk_data *)disk; + struct scsi_device *scsi_dev = disk_data->sdev; + const struct device *ufs_dev = scsi_dev->host->parent; + struct ufs_host_controller *ufshc = (struct ufs_host_controller *)ufs_dev->data; + + /* Validate if requested blocks are within the disk's capacity */ + if ((start_block + num_blocks) > (scsi_dev->capacity)) { + return -EINVAL; + } + + /* Lock UFS host controller for access */ + ret = k_mutex_lock(&ufshc->ufs_lock, K_FOREVER); + if (ret != 0) { + LOG_ERR("could not get UFS card mutex"); + return -EBUSY; + } + + /* Handle aligned and unaligned buffer reads */ + if ((((uintptr_t)rbuf) & ((uintptr_t)CONFIG_UFSHC_BUFFER_ALIGNMENT - (uintptr_t)1)) + != (uintptr_t)0) { + /* Unaligned buffer, use temporary internal buffer */ + rlen = (uint32_t)((uint32_t)CONFIG_UFS_BUFFER_SIZE / (scsi_dev->sector_size)); + if (rlen == 0U) { + LOG_ERR("Card buffer size is less than block size - unaligned"); + ret = -ENOBUFS; + goto out; + } + rlen = MIN(num_blocks, rlen); + if (disk_data->card_buffer == NULL) { + ret = -ENOMEM; + goto out; + } + (void)memset(disk_data->card_buffer, 0x0, + (size_t)(uint32_t)(rlen * (scsi_dev->sector_size))); + while (sector < num_blocks) { + /* Read from UFS device to internal buffer */ + ret = scsi_read(scsi_dev, + (uint64_t)(uint32_t)(sector + start_block), + rlen, + disk_data->card_buffer); + if (ret != 0) { + LOG_ERR("UFS Card read failed"); + goto out; + } + /* Copy data from internal buffer to user buffer */ + (void)memcpy(&rbuf[buf_offset], + disk_data->card_buffer, + (size_t)(uint32_t)(rlen * (scsi_dev->sector_size))); + sector += rlen; + buf_offset += rlen * (scsi_dev->sector_size); + } + + } else { + /* Aligned buffer, use directly */ + ret = scsi_read(scsi_dev, start_block, num_blocks, rbuf); + if (ret != 0) { + LOG_ERR("UFS Card read failed"); + goto out; + } + } + +out: + (void)k_mutex_unlock(&ufshc->ufs_lock); + + return ret; +} + +/** + * @brief Reads data from the UFS disk. + * + * @param disk Pointer to the disk_info structure representing the UFS disk. + * @param buf Pointer to the buffer where the read data will be stored. + * @param sector The starting sector number to read from. + * @param count The number of sectors to read. + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t disk_ufs_access_read(struct disk_info *disk, + uint8_t *buf, + uint32_t sector, + uint32_t count) +{ + uint32_t last_sector = sector + count; + + if (last_sector < sector) { + return -EIO; /* Overflow check */ + } + + return ufs_card_read_blocks(disk, buf, sector, count); +} + +/** + * @brief Writes data to UFS card using SCSI write API. + * + * @param disk Pointer to the disk_info structure representing the UFS disk. + * @param wbuf Pointer to the buffer containing the data to be written. + * @param start_block The starting block number to write to. + * @param num_blocks The number of blocks to write. + * + * @return zero if successful or an error if Write failed. + */ +static int32_t ufs_card_write_blocks(struct disk_info *disk, + const uint8_t *wbuf, + uint32_t start_block, + uint32_t num_blocks) +{ + int32_t ret; + uint32_t wlen; + uint32_t sector; + const uint8_t *buf_offset; + + /* Get the UFS device and host controller from the SCSI device */ + struct ufsdisk_data *disk_data = (struct ufsdisk_data *)disk; + struct scsi_device *scsi_dev = disk_data->sdev; + const struct device *ufs_dev = scsi_dev->host->parent; + struct ufs_host_controller *ufshc = (struct ufs_host_controller *)ufs_dev->data; + + /* Validate if requested blocks are within the disk's capacity */ + if ((start_block + num_blocks) > (scsi_dev->capacity)) { + return -EINVAL; + } + + /* Lock UFS host controller for access */ + ret = k_mutex_lock(&ufshc->ufs_lock, K_FOREVER); + if (ret != 0) { + LOG_ERR("could not get UFS card mutex"); + return -EBUSY; + } + + /* Handle aligned and unaligned buffer writes */ + if ((((uintptr_t)wbuf) & ((uintptr_t)CONFIG_UFSHC_BUFFER_ALIGNMENT - (uintptr_t)1)) + != (uintptr_t)0) { + /* Unaligned buffer, use temporary internal buffer */ + wlen = (uint32_t)((uint32_t)CONFIG_UFS_BUFFER_SIZE / (scsi_dev->sector_size)); + if (wlen == 0U) { + LOG_ERR("Card buffer size is less than block size - unaligned"); + ret = -ENOBUFS; + goto out; + } + wlen = MIN(num_blocks, wlen); + sector = 0; + buf_offset = wbuf; + if (disk_data->card_buffer == NULL) { + ret = -ENOMEM; + goto out; + } + (void)memset(disk_data->card_buffer, + 0x0, + (size_t)(uint32_t)(wlen * (scsi_dev->sector_size))); + while (sector < num_blocks) { + /* Copy data from user buffer to internal buffer */ + (void)memcpy(disk_data->card_buffer, buf_offset, + (size_t)(uint32_t)(wlen * (scsi_dev->sector_size))); + /* Write from internal buffer to UFS device */ + ret = scsi_write(scsi_dev, + (uint64_t)(uint32_t)(sector + start_block), + wlen, + disk_data->card_buffer); + if (ret != 0) { + LOG_ERR("UFS Card write failed"); + goto out; + } + /* Increase sector count and buffer offset */ + sector += wlen; + buf_offset += wlen * (scsi_dev->sector_size); + } + } else { + /* Aligned buffer, use directly */ + ret = scsi_write(scsi_dev, start_block, num_blocks, wbuf); + if (ret != 0) { + LOG_ERR("UFS Card write failed"); + goto out; + } + } + +out: + (void)k_mutex_unlock(&ufshc->ufs_lock); + + return ret; +} + +/** + * @brief Write data to UFS disk. + * + * @param disk Pointer to the disk information structure. + * @param buf Pointer to the buffer containing data to be written. + * @param sector The starting sector to write to. + * @param count The number of sectors to write. + * + * @return 0 on success, negative error code on failure. + */ +static int32_t disk_ufs_access_write(struct disk_info *disk, + const uint8_t *buf, + uint32_t sector, + uint32_t count) +{ + uint32_t last_sector = sector + count; + + if (last_sector < sector) { + return -EINVAL; /* Overflow check */ + } + + return ufs_card_write_blocks(disk, buf, sector, count); +} + +/** + * @brief Handle IOCTL commands for UFS disk. + * + * This function handles various IOCTL commands such as initialization, + * de-initialization, sector size retrieval, or executing SCSI or UFS requests. + * + * The behavior of the function depends on the value of the @cmd parameter: + * + * - DISK_IOCTL_CTRL_INIT: Initialize the UFS disk. + * - DISK_IOCTL_CTRL_DEINIT: Deinitialize the UFS disk. + * - DISK_IOCTL_CTRL_SYNC: No operation (typically used for synchronization). + * - DISK_IOCTL_GET_SECTOR_COUNT: Return the total number of sectors in the disk. + * - DISK_IOCTL_GET_SECTOR_SIZE: Return the sector size of the disk. + * - DISK_IOCTL_GET_ERASE_BLOCK_SZ: Return the erase block size of the disk (in sectors). + * - SG_IO: Handle SCSI or UFS-specific IO requests via the sg_io_req structure + * pointed to by buff. This includes processing SCSI commands or UFS-specific + * commands using the appropriate protocol and subprotocol. + * + * @param disk Pointer to the disk_info structure representing the UFS disk. + * @param cmd The IOCTL command to process. + * @param buff Pointer to the buffer for input/output data (depends on command). + * + * @return 0 on success, negative error code on failure. + */ +static int32_t disk_ufs_access_ioctl(struct disk_info *disk, uint8_t cmd, void *buff) +{ + struct ufsdisk_data *data = (struct ufsdisk_data *)disk; + struct scsi_device *sdev = data->sdev; + struct ufs_host_controller *ufshc = data->ufshc; + struct sg_io_req *req; + int32_t ret = 0; + + switch (cmd) { + case DISK_IOCTL_CTRL_INIT: + /* Initialize the disk */ + ret = disk_ufs_access_init(disk); + break; + case DISK_IOCTL_CTRL_DEINIT: + /* De-initialize the disk */ + /* mark the disk as uninitialized and free memory */ + data->status = UFS_UNINIT; + k_free(data->card_buffer); + data->card_buffer = NULL; + break; + case DISK_IOCTL_CTRL_SYNC: + break; + case DISK_IOCTL_GET_SECTOR_COUNT: + /* Retrieve the total number of sectors on the disk */ + if (buff != NULL) { + *(uint32_t *)buff = (uint32_t)(sdev->capacity); + } else { + ret = -EINVAL; + } + break; + case DISK_IOCTL_GET_SECTOR_SIZE: + /* Retrieve the size of each sector on the disk */ + if (buff != NULL) { + *(uint32_t *)buff = (sdev->sector_size); + } else { + ret = -EINVAL; + } + break; + case DISK_IOCTL_GET_ERASE_BLOCK_SZ: + /* Retrieve the block size for erase */ + if (buff != NULL) { + *(uint32_t *)buff = 1U; + } else { + ret = -EINVAL; + } + break; + case SG_IO: + /* Handle SCSI or UFS-specific I/O requests */ + req = (struct sg_io_req *)buff; + + if ((req == NULL) || + (req->protocol != BSG_PROTOCOL_SCSI)) { + ret = -EINVAL; + } else { + /*Process SCSI or UFS depending on the subprotocol */ + if (req->subprotocol == BSG_SUB_PROTOCOL_SCSI_CMD) { + /* Handle SCSI-specific I/O operation */ + ret = scsi_ioctl(sdev, (int32_t)cmd, req); + } else { + /* Handle UFS-specific I/O operation */ + ret = ufs_sg_request(ufshc, req); + } + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/** + * @brief Disk operations for UFS disk. + * + * This structure contains the disk operations for UFS disk management, + * including initialization, status checking, read, write, and ioctl handling. + */ +static const struct disk_operations ufs_disk_ops = { + .init = disk_ufs_access_init, + .status = disk_ufs_access_status, + .read = disk_ufs_access_read, + .write = disk_ufs_access_write, + .ioctl = disk_ufs_access_ioctl, +}; + +#define DEFINE_UFSDISKS_DEVICE(n) \ + { \ + .info = { \ + .ops = &ufs_disk_ops, \ + .name = DT_INST_PROP(n, disk_name), \ + }, \ + .ufshc_device = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + .lun = DT_INST_PROP(n, lun), \ + }, + +/** + * @brief Array of UFS disk data structures. + * + * This array is populated using the device tree macros, which define the + * UFS disks and their properties such as the disk name and LUN. + */ +static struct ufsdisk_data ufs_disks[] = { + DT_INST_FOREACH_STATUS_OKAY(DEFINE_UFSDISKS_DEVICE) +}; + +/** + * @brief Register UFS disks. + * + * This function registers the UFS disk devices during the + * system initialization phase. + * + * @return 0 on success, negative error code on failure. + */ +static int32_t disk_ufs_register(void) +{ + int32_t err = 0, rc; + int32_t index, num_of_disks; + + num_of_disks = (int32_t)ARRAY_SIZE(ufs_disks); + for (index = 0; index < num_of_disks; index++) { + rc = disk_access_register(&ufs_disks[index].info); + if (rc < 0) { + LOG_ERR("Failed to register disk %s error %d", + ufs_disks[index].info.name, rc); + err = rc; + } + } + + return err; +} + +SYS_INIT(disk_ufs_register, POST_KERNEL, CONFIG_UFSDISK_INIT_PRIORITY); diff --git a/drivers/ufshc/CMakeLists.txt b/drivers/ufshc/CMakeLists.txt new file mode 100644 index 0000000000000..761129d770baa --- /dev/null +++ b/drivers/ufshc/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +if (CONFIG_UFSHC) + +zephyr_library() +zephyr_library_sources_ifdef(CONFIG_UFS_AMD_VERSAL2 ufshc_amd_versal2.c) + +endif() diff --git a/drivers/ufshc/Kconfig b/drivers/ufshc/Kconfig new file mode 100644 index 0000000000000..b5c8c509ecec5 --- /dev/null +++ b/drivers/ufshc/Kconfig @@ -0,0 +1,34 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +# UFS Host Controller (UFSHC) Drivers Configuration +menuconfig UFSHC + bool "UFS Host Controller (UFSHC) drivers" + help + Enable support for UFS host controller drivers. + +if UFSHC + +source "drivers/ufshc/Kconfig.amd_versal2" + +# UFS Host Controller driver initialization priority +config UFSHC_INIT_PRIORITY + int "UFSHC driver init priority" + default 85 + help + Set the initialization priority for the UFSHC driver during system boot. + +# Buffer alignment for UFS Host Controller drivers +config UFSHC_BUFFER_ALIGNMENT + int + default 64 + help + Some UFS host controllers require specific buffer alignment for DMA operations. + Devices should adjust this value if a different alignment is necessary for operation. + This value represents the alignment of data buffers in bytes (default: 64 bytes). + +module = UFSHC +module-str = ufshc +source "subsys/logging/Kconfig.template.log_config" + +endif # UFSHC diff --git a/drivers/ufshc/Kconfig.amd_versal2 b/drivers/ufshc/Kconfig.amd_versal2 new file mode 100644 index 0000000000000..6f9be4b7ddef6 --- /dev/null +++ b/drivers/ufshc/Kconfig.amd_versal2 @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +config UFS_AMD_VERSAL2 + bool "AMD Versal Gen 2 UFS controller driver" + default y + depends on DT_HAS_AMD_VERSAL2_UFS_ENABLED + help + This option enables support for the AMD Versal Gen2 UFS (Universal Flash Storage) + Host Controller. diff --git a/drivers/ufshc/ufshc_amd_versal2.c b/drivers/ufshc/ufshc_amd_versal2.c new file mode 100644 index 0000000000000..dee0fe684a171 --- /dev/null +++ b/drivers/ufshc/ufshc_amd_versal2.c @@ -0,0 +1,787 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ufshc_amd_versal2, CONFIG_UFSHC_LOG_LEVEL); + +#define DT_DRV_COMPAT amd_versal2_ufs + +/* UFS Clock Divider Register offset */ +#define VERSAL2_UFS_REG_HCLKDIV_OFFSET 0xFCU + +/* UFS Reset Register offset */ +#define VERSAL2_UFS_CRP_RST_OFFSET 0x340U + +/* UFS Calibration Efuse Register offset */ +#define VERSAL2_UFS_EFUSE_CAL_OFFSET 0xBE8U + +/* SRAM Control and Status Register (CSR) offset */ +#define VERSAL2_UFS_IOU_SLCR_SRAM_CSR_OFFSET 0x104CU + +#define VERSAL2_UFS_SRAM_CSR_BYPASS_MASK 0x4U +#define VERSAL2_UFS_SRAM_CSR_EXTID_DONE_MASK 0x2U +#define VERSAL2_UFS_SRAM_CSR_INIT_DONE_MASK 0x1U + +/* PHY Reset Register offset */ +#define VERSAL2_UFS_IOU_SLCR_PHY_RST_OFFSET 0x1050U + +/* Transmit/Receive Config Ready Register */ +#define VERSAL2_UFS_IOU_SLCR_TX_RX_CFGRDY_OFFSET 0x1054U + +#define VERSAL2_UFS_TX_RX_CFGRDY_MASK GENMASK(3, 0) + +/* RMMI Attributes */ +#define CBREFCLKCTRL2 0x8132 +#define CBCRCTRL 0x811F +#define CBC10DIRECTCONF2 0x810E +#define CBCREGADDRLSB 0x8116 +#define CBCREGADDRMSB 0x8117 +#define CBCREGWRLSB 0x8118 +#define CBCREGWRMSB 0x8119 +#define CBCREGRDLSB 0x811A +#define CBCREGRDMSB 0x811B +#define CBCREGRDWRSEL 0x811C + +#define CBREFREFCLK_GATE_OVR_EN BIT(7) + +/* M-PHY Attributes */ +#define MTX_FSM_STATE 0x41 +#define MRX_FSM_STATE 0xC1 + +#define VERSAL2_UFS_HIBERN8_STATE 0x1U +#define VERSAL2_UFS_SLEEP_STATE 0x2U +#define VERSAL2_UFS_LS_BURST_STATE 0x4U + +/* M-PHY registers */ +#define FAST_FLAGS(n) (0x401C + ((n) * 0x100)) +#define RX_AFE_ATT_IDAC(n) (0x4000 + ((n) * 0x100)) +#define RX_AFE_CTLE_IDAC(n) (0x4001 + ((n) * 0x100)) +#define FW_CALIB_CCFG(n) (0x404D + ((n) * 0x100)) + +#define MPHY_FAST_RX_AFE_CAL BIT(2) +#define MPHY_FW_CALIB_CFG_VAL BIT(8) + +/** + * @brief Configuration for the Versal Gen2 UFS Host Controller. + */ +struct ufshc_versal2_config { + mem_addr_t mmio_base; /**< Base address for the UFS controller memory-mapped I/O */ + uint32_t core_clk_rate; /**< UFS core clock rate in Hz */ + uint32_t irq_id; /**< IRQ line for the UFS controller interrupt */ + mem_addr_t reg_iou_slcr; /**< IOU SLCR register address for UFS configuration */ + mem_addr_t reg_efuse_cache; /**< eFuse cache register address */ + mem_addr_t reg_ufs_crp; /**< UFS CRP register address */ +}; + +/** + * @brief Runtime data for the Versal Gen2 UFS Host Controller. + */ +struct ufshc_versal2_data { + struct ufs_host_controller ufshc; /**< UFS host controller structure */ + uint32_t rx_att_comp_val_l0; /**< Receive AFE compensation value for lane 0 */ + uint32_t rx_att_comp_val_l1; /**< Receive AFE compensation value for lane 1 */ + uint32_t rx_ctle_comp_val_l0; /**< Receive CTLE compensation value for lane 0 */ + uint32_t rx_ctle_comp_val_l1; /**< Receive CTLE compensation value for lane 1 */ +}; + +/** + * @brief Perform variant-specific initialization of the UFS Host Controller. + * + * This function performs a sequence of initialization steps + * including asserting resets, configuring the SRAM and clock dividers, + * and programming the necessary calibration values. + * + * @param dev Pointer to the UFS device handle. + */ +static void ufshc_versal2_initialization(const struct device *dev) +{ + const struct ufshc_versal2_config *cfg = dev->config; + struct ufshc_versal2_data *drvdata = dev->data; + struct ufs_host_controller *ufshc = &drvdata->ufshc; + uint32_t read_reg; + + /* Assert UFS Host Controller Reset */ + sys_write32((1U), ((cfg->reg_ufs_crp) + (mem_addr_t)(VERSAL2_UFS_CRP_RST_OFFSET))); + + /* Assert PHY Reset */ + sys_write32((1U), + ((cfg->reg_iou_slcr) + + (mem_addr_t)(VERSAL2_UFS_IOU_SLCR_PHY_RST_OFFSET))); + + /* Set ROM Mode (SRAM Bypass) */ + read_reg = sys_read32(((cfg->reg_iou_slcr) + + (mem_addr_t)(VERSAL2_UFS_IOU_SLCR_SRAM_CSR_OFFSET))); + read_reg |= VERSAL2_UFS_SRAM_CSR_BYPASS_MASK; + read_reg &= ~VERSAL2_UFS_SRAM_CSR_EXTID_DONE_MASK; + sys_write32((read_reg), + ((cfg->reg_iou_slcr) + + (mem_addr_t)(VERSAL2_UFS_IOU_SLCR_SRAM_CSR_OFFSET))); + + /* Release UFS Host Controller Reset */ + sys_write32((0U), + ((cfg->reg_ufs_crp) + + (mem_addr_t)(VERSAL2_UFS_CRP_RST_OFFSET))); + + /* Program the HCLK_DIV based on the input reference clock */ + ufshc_write_reg(ufshc, + VERSAL2_UFS_REG_HCLKDIV_OFFSET, + (cfg->core_clk_rate / 1000000U)); + + /* Read the calibration values from the eFuse cache */ + read_reg = sys_read32(((cfg->reg_efuse_cache) + + (mem_addr_t)(VERSAL2_UFS_EFUSE_CAL_OFFSET))); + drvdata->rx_att_comp_val_l0 = (uint8_t)read_reg; + drvdata->rx_att_comp_val_l1 = (uint8_t)(read_reg >> 8U); + drvdata->rx_ctle_comp_val_l0 = (uint8_t)(read_reg >> 16U); + drvdata->rx_ctle_comp_val_l1 = (uint8_t)(read_reg >> 24U); +} + +/** + * @brief Notify the UFS controller about link startup. + * + * This function sends a UIC command to change the connection state. + * + * @param dev Pointer to the UFS device handle. + * @param status Notification status (pre-change or post-change). + * + * @return 0 on success, or a negative error code on failure. + */ +static int ufshc_versal2_link_startup_notify(const struct device *dev, uint8_t status) +{ + struct ufshc_versal2_data *drvdata = dev->data; + struct ufs_host_controller *ufshc = &drvdata->ufshc; + struct ufshc_uic_cmd uic_cmd = {0}; + int err = 0; + + if (status == (uint8_t)POST_CHANGE) { + /* change the connection state to the ready state */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)T_CONNECTIONSTATE << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + err = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (err != 0) { + LOG_ERR("Connection setup failed (%d)", err); + } + } + + return err; +} + +/** + * @brief Configures RMMI (Remote Memory-Mapped Interface). + * + * This function configures the UFS host controller's RMMI settings by + * issuing a series of UIC commands to the controller. + * + * @param ufshc Pointer to the UFS host controller driver structure. + * + * @return 0 (success) or non-zero value on failure. + */ +static int32_t ufshc_versal2_set_rmmi(struct ufs_host_controller *ufshc) +{ + struct ufshc_uic_cmd uic_cmd = {0}; + int32_t ret = 0; + + /* Configure the clock reference control to enable ref clock gating */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)CBREFCLKCTRL2 << 16U), + (uint32_t)CBREFREFCLK_GATE_OVR_EN, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Set specific clock configuration */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)CBCRCTRL << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Configure for direct PHY interface mode */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)CBC10DIRECTCONF2 << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Update the M-PHY configuration to apply changes */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)VS_MPHYCFGUPDT << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + +out: + return ret; +} + +/** + * @brief Writes to mPHY registers. + * + * This function writes a 16-bit value to the mPHY register specified by the address. + * + * @param ufshc Pointer to the UFS host controller driver structure. + * @param uic_cmd_ptr Pointer to the UIC command structure to be filled with commands. + * @param address The address of the mPHY register to write. + * @param value The value to write to the specified mPHY register. + * + * @return 0 (success) or non-zero value on failure. + */ +static int32_t ufshc_versal2_write_phy_reg(struct ufs_host_controller *ufshc, + struct ufshc_uic_cmd *uic_cmd_ptr, + uint32_t address, uint32_t value) +{ + int32_t ret = 0; + + /* Write the least significant byte of the register address */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGADDRLSB << 16U), + (uint8_t)address, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Write the most significant byte of the register address */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGADDRMSB << 16U), + (uint8_t)(address >> 8U), 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Write the least significant byte of the register value */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGWRLSB << 16U), + (uint8_t)value, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Write the most significant byte of the register value */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGWRMSB << 16U), + (uint8_t)(value >> 8U), 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Set the read/write select bit in the register */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGRDWRSEL << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Update the M-PHY configuration to apply changes */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)VS_MPHYCFGUPDT << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + +out: + return ret; +} + +/** + * @brief Reads from mPHY registers. + * + * This function reads a 16-bit value from the mPHY register specified by the address. + * + * @param ufshc Pointer to the UFS host controller driver structure. + * @param uic_cmd_ptr Pointer to the UIC command structure to be filled with commands. + * @param address The address of the mPHY register to read. + * @param value Pointer to the variable where the read value will be stored. + * + * @return 0 (success) or non-zero value on failure. + */ +static int32_t ufshc_versal2_read_phy_reg(struct ufs_host_controller *ufshc, + struct ufshc_uic_cmd *uic_cmd_ptr, + uint32_t address, uint32_t *value) +{ + int32_t ret = 0; + + /* Write the least significant byte of the register address */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGADDRLSB << 16U), + (uint8_t)address, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Write the most significant byte of the register address */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGADDRMSB << 16U), + (uint8_t)(address >> 8U), 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Set the read/write select bit in the register */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGRDWRSEL << 16U), + 0U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Update the M-PHY configuration to apply changes */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)VS_MPHYCFGUPDT << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Read the least significant byte of the register value */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGRDLSB << 16U), + 0U, 0U, + (uint32_t)UFSHC_DME_GET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Read the most significant byte of the register value */ + *value = uic_cmd_ptr->mib_value; + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGRDMSB << 16U), + 0U, 0U, + (uint32_t)UFSHC_DME_GET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Combine to form the complete register value */ + *value |= (uic_cmd_ptr->mib_value << 8U); + +out: + return ret; +} + +/** + * @brief Configures and sets up UFS Versal Gen2 PHY + * + * This function is used to configure and set up the UFS Versal Gen2 PHY. + * It starts with bypassing the RX-AFE (ATT/CTLE) offset calibrations, + * then programs the ATT and CTLE compensation values. + * Finally, it enables the RX-AFE calibration for Firmware control. + * + * @param dev A pointer to the device structure representing the UFS device. + * + * @return 0 if the setup is successful, or an error code in case of failure. + */ +static int32_t ufs_versal2_setup_phy(const struct device *dev) +{ + struct ufshc_versal2_data *drvdata = dev->data; + struct ufs_host_controller *ufshc = &drvdata->ufshc; + struct ufshc_uic_cmd uic_cmd = {0}; + int32_t ret = 0; + uint32_t read_reg = 0U; + + /* Bypass RX-AFE Calibration for both lanes */ + ret = ufshc_versal2_read_phy_reg(ufshc, &uic_cmd, + FAST_FLAGS(0), + &read_reg); + if (ret != 0) { + return ret; + } + + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + FAST_FLAGS(0), + (read_reg | (uint32_t)MPHY_FAST_RX_AFE_CAL)); + if (ret != 0) { + return ret; + } + + ret = ufshc_versal2_read_phy_reg(ufshc, &uic_cmd, + FAST_FLAGS(1), + &read_reg); + if (ret != 0) { + return ret; + } + + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + FAST_FLAGS(1), + (read_reg | (uint32_t)MPHY_FAST_RX_AFE_CAL)); + if (ret != 0) { + return ret; + } + + /* Program ATT Compensation Value for RX */ + if ((drvdata->rx_att_comp_val_l0 != (uint32_t)0x0U) + && (drvdata->rx_att_comp_val_l0 != (uint32_t)0xFFU)) { + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + RX_AFE_ATT_IDAC(0), + drvdata->rx_att_comp_val_l0); + if (ret != 0) { + return ret; + } + } + + if ((drvdata->rx_att_comp_val_l1 != (uint32_t)0x0U) + && (drvdata->rx_att_comp_val_l1 != (uint32_t)0xFFU)) { + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + RX_AFE_ATT_IDAC(1), + drvdata->rx_att_comp_val_l1); + if (ret != 0) { + return ret; + } + } + + /* Program CTLE Compensation Value for RX */ + if ((drvdata->rx_ctle_comp_val_l0 != (uint32_t)0x0U) + && (drvdata->rx_ctle_comp_val_l0 != (uint32_t)0xFFU)) { + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + RX_AFE_CTLE_IDAC(0), + drvdata->rx_ctle_comp_val_l0); + if (ret != 0) { + return ret; + } + } + + if ((drvdata->rx_ctle_comp_val_l1 != (uint32_t)0x0U) + && (drvdata->rx_ctle_comp_val_l1 != (uint32_t)0xFFU)) { + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + RX_AFE_CTLE_IDAC(1), + drvdata->rx_ctle_comp_val_l1); + if (ret != 0) { + return ret; + } + } + + /* Program FW Calibration Config */ + ret = ufshc_versal2_read_phy_reg(ufshc, &uic_cmd, + FW_CALIB_CCFG(0), + &read_reg); + if (ret != 0) { + return ret; + } + + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + FW_CALIB_CCFG(0), + (read_reg | (uint32_t)MPHY_FW_CALIB_CFG_VAL)); + if (ret != 0) { + return ret; + } + + ret = ufshc_versal2_read_phy_reg(ufshc, &uic_cmd, + FW_CALIB_CCFG(1), + &read_reg); + if (ret != 0) { + return ret; + } + + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + FW_CALIB_CCFG(1), + (read_reg | (uint32_t)MPHY_FW_CALIB_CFG_VAL)); + + return ret; +} + +/** + * @brief Enable the mphy for the Versal Gen2 UFS host controller. + * + * This function performs the necessary steps to enable the mphy in the Versal Gen2 UFS + * host controller, including sending UIC commands to disable the mphy, update the mphy + * configuration, and wait for the Rx/Tx state machines to de-assert, ensuring that the + * controller is ready for further operations. + * + * @param ufshc Pointer to the UFS host controller structure. + * + * @return 0 (success) or non-zero value on failure. + */ +static int32_t ufshc_versal2_enable_mphy(struct ufs_host_controller *ufshc) +{ + struct ufshc_uic_cmd uic_cmd = {0}; + int32_t ret = 0; + uint32_t time_out; + uint32_t val; + uint32_t index; + + /* Disable the mphy (de-assert mphy_disable) */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)VS_MPHYDISABLE << 16U), + 0U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Update the mphy configuration with new settings */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)VS_MPHYCFGUPDT << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Wait for both Tx and Rx state machines to exit busy state */ + for (index = 0U; index < 2U; index++) { + time_out = UFS_TIMEOUT_US; + do { + time_out = time_out - 1U; + (void)k_usleep(1); + /* Read TX_FSM_STATE for the current index */ + ufshc_fill_uic_cmd(&uic_cmd, + (((uint32_t)MTX_FSM_STATE << 16U) | index), + 0U, 0U, + (uint32_t)UFSHC_DME_GET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + val = uic_cmd.mib_value; + /* Check if the state machine is in a valid state */ + if ((val == VERSAL2_UFS_HIBERN8_STATE) || + (val == VERSAL2_UFS_SLEEP_STATE) || + (val == VERSAL2_UFS_LS_BURST_STATE)) { + break; + } + } while (time_out != 0U); + + if (time_out == 0U) { + LOG_ERR("Invalid Tx FSM state."); + ret = -ETIMEDOUT; + goto out; + } + + time_out = UFS_TIMEOUT_US; + do { + time_out = time_out - 1U; + (void)k_usleep(1); + /* Read RX_FSM_STATE for the current index */ + ufshc_fill_uic_cmd(&uic_cmd, + (((uint32_t)MRX_FSM_STATE << 16U) | (4U + index)), + 0U, 0U, + (uint32_t)UFSHC_DME_GET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + val = uic_cmd.mib_value; + /* Check if the state machine is in a valid state */ + if ((val == VERSAL2_UFS_HIBERN8_STATE) || + (val == VERSAL2_UFS_SLEEP_STATE) || + (val == VERSAL2_UFS_LS_BURST_STATE)) { + break; + } + } while (time_out != 0U); + + if (time_out == 0U) { + LOG_ERR("Invalid Rx FSM state."); + ret = -ETIMEDOUT; + goto out; + } + } + +out: + return ret; +} + +/** + * @brief Initializes the PHY for the Versal Gen2 UFS Host Controller + * + * This function performs the initialization of the PHY for the Versal Gen2 UFS Host Controller. + * It involves several steps including waiting for various status registers to de-assert, + * de-asserting the PHY reset, waiting for SRAM initialization, performing calibration + * bypasses, and programming various compensation values. Additionally, firmware calibration + * settings are configured, and the mPHY is enabled. + * + * @param dev Pointer to the device structure representing the UFS device. + * + * @return 0 (success) or non-zero value on failure. + */ +static int ufshc_versal2_phy_init(const struct device *dev) +{ + const struct ufshc_versal2_config *cfg = dev->config; + struct ufshc_versal2_data *drvdata = dev->data; + struct ufs_host_controller *ufshc = &drvdata->ufshc; + int ret = 0; + uint32_t read_reg; + uint32_t time_out; + + /* + * Wait for the Tx/Rx CfgRdyn signal to de-assert, indicating that + * configuration of the UFS Tx/Rx lanes is complete and stable. This + * ensures that the UFS PHY is ready for further initialization. + */ + time_out = UFS_TIMEOUT_US; + do { + read_reg = sys_read32(((cfg->reg_iou_slcr) + + (mem_addr_t)(VERSAL2_UFS_IOU_SLCR_TX_RX_CFGRDY_OFFSET))); + if ((read_reg & (uint32_t)VERSAL2_UFS_TX_RX_CFGRDY_MASK) == 0U) { + break; + } + time_out = time_out - 1U; + (void)k_usleep(1); + } while (time_out != 0U); + + if (time_out == 0U) { + LOG_ERR("Tx/Rx configuration signal busy."); + ret = -ETIMEDOUT; + goto out; + } + + ret = ufshc_versal2_set_rmmi(ufshc); + if (ret != 0) { + goto out; + } + + /* De-assert PHY reset */ + sys_write32((0U), + ((cfg->reg_iou_slcr) + + (mem_addr_t)(VERSAL2_UFS_IOU_SLCR_PHY_RST_OFFSET))); + + /* Wait for SRAM initialization to complete */ + time_out = UFS_TIMEOUT_US; + do { + read_reg = sys_read32(((cfg->reg_iou_slcr) + + (mem_addr_t)(VERSAL2_UFS_IOU_SLCR_SRAM_CSR_OFFSET))); + if ((read_reg & VERSAL2_UFS_SRAM_CSR_INIT_DONE_MASK) != 0U) { + break; + } + time_out = time_out - 1U; + (void)k_usleep(1); + } while (time_out != 0U); + + if (time_out == 0U) { + LOG_ERR("SRAM initialization failed."); + ret = -ETIMEDOUT; + goto out; + } + + /* Setup calibration settings */ + ret = ufs_versal2_setup_phy(dev); + if (ret != 0) { + goto out; + } + + /* Enable mPHY */ + ret = ufshc_versal2_enable_mphy(ufshc); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +/** + * @brief Performs device instance init for Versal Gen2 UFS controller + * + * This function initializes the UFS controller instance by setting up + * the various fields in the host controller structure, configuring the + * mutexes and events, and performing the required PHY reset and reference + * clock programming for the UFS device. + * + * @param dev Pointer to the device structure for the UFS controller instance + * + * @return 0 on success. + */ +static int32_t ufshc_versal2_init(const struct device *dev) +{ + const struct ufshc_versal2_config *cfg = dev->config; + struct ufshc_versal2_data *drvdata = dev->data; + struct ufs_host_controller *ufshc = &drvdata->ufshc; + + /* Initialize UFS driver structure and device data */ + (void)memset(ufshc, 0x0, sizeof(*ufshc)); + ufshc->dev = (struct device *)((uintptr_t)dev); + ufshc->mmio_base = cfg->mmio_base; + ufshc->is_initialized = false; + ufshc->irq = cfg->irq_id; + drvdata->rx_att_comp_val_l0 = 0U; + drvdata->rx_att_comp_val_l1 = 0U; + drvdata->rx_ctle_comp_val_l0 = 0U; + drvdata->rx_ctle_comp_val_l1 = 0U; + ufshc->is_cache_coherent = 0U; + + /* Initialize and lock UFS card mutex and events */ + k_event_init(&ufshc->irq_event); + (void)k_mutex_init(&ufshc->ufs_lock); + + /* Perform PHY reset and program reference clock */ + ufshc_versal2_initialization(dev); + + return 0; +} + +static DEVICE_API(ufshc, ufshc_versal2_api) = { + .phy_initialization = ufshc_versal2_phy_init, + .link_startup_notify = ufshc_versal2_link_startup_notify, +}; + +/** + * UFSHC_VERSAL2_INIT - Macro to initialize a UFS host controller instance for Versal Gen2 + * @n: The instance index of the UFS controller + * + * This macro initializes the configuration, data structures, and device + * instance for a UFS host controller instance. Creates a device entry for the + * UFS host controller in Zephyr device model. + */ +#define UFSHC_VERSAL2_INIT(n) \ + \ + static const struct ufshc_versal2_config ufshc_versal2_config_##n = { \ + .mmio_base = DT_INST_REG_ADDR_BY_IDX(n, 0), \ + .core_clk_rate = DT_PROP(DT_INST_PHANDLE_BY_NAME(n, clocks, core_clk), \ + clock_frequency), \ + .irq_id = DT_INST_IRQN(n), \ + .reg_iou_slcr = DT_INST_REG_ADDR_BY_IDX(n, 1), \ + .reg_efuse_cache = DT_INST_REG_ADDR_BY_IDX(n, 2), \ + .reg_ufs_crp = DT_INST_REG_ADDR_BY_IDX(n, 3), \ + }; \ + \ + static struct ufshc_versal2_data ufshc_versal2_data_##n = { \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, &ufshc_versal2_init, NULL, \ + &ufshc_versal2_data_##n, &ufshc_versal2_config_##n, \ + POST_KERNEL, CONFIG_UFSHC_INIT_PRIORITY, \ + &ufshc_versal2_api); + +DT_INST_FOREACH_STATUS_OKAY(UFSHC_VERSAL2_INIT) diff --git a/dts/bindings/ufs/amd,versal2-ufs.yaml b/dts/bindings/ufs/amd,versal2-ufs.yaml new file mode 100644 index 0000000000000..d6a18ba707048 --- /dev/null +++ b/dts/bindings/ufs/amd,versal2-ufs.yaml @@ -0,0 +1,47 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: AMD Versal Gen2 UFS Host Controller. + +description: | + Example usage: + ufs@f10b0000 { + compatible = "amd,versal2-ufs"; + reg = <0xf10b0000 0x1000>; + interrupts = ; + clocks = <&ufs_core_clk>; + clock-names = "core_clk"; + freq-table-hz = <0 0>; + resets = <&scmi_reset 4>, <&scmi_reset 35>; + reset-names = "ufshc-rst", "ufsphy-rst"; + }; + +compatible: "amd,versal2-ufs" + +include: [ufs-common.yaml, reset-device.yaml] + +properties: + clocks: + required: true + description: The list of clocks used by the UFS Host Controller. + + clock-names: + required: true + description: core_clk - Clock for the core logic + + reg: + required: true + description: | + - Host Controller Base address + - IOU(Input-Output Unit) SLCR(System-Level Control Register) + - EFUSE Cache register + - UFS CRP(Clock reset Power) register + + resets: + description: Reset control for both UFS Host Controller and PHY. + + reset-names: + description: | + - Valid names of RESET signals: + - "ufshc-rst" - Reset signal for the UFS Host Controller + - "ufsphy-rst" - Reset signal for the UFS PHY diff --git a/dts/bindings/ufs/ufs-common.yaml b/dts/bindings/ufs/ufs-common.yaml new file mode 100644 index 0000000000000..7de381c34244f --- /dev/null +++ b/dts/bindings/ufs/ufs-common.yaml @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Common properties for Universal Flash Storage (UFS) Host Controllers + +include: [base.yaml] + +properties: + freq-table-hz: + type: array + description: | + Array of operating frequencies in Hz stored in the same order + as the clocks property. If this property is not defined or a value in the + array is "0" then it is assumed that the frequency is set by the parent + clock or a fixed rate clock source. + + interrupts: + required: true + description: Interrupts used by the UFS Host Controller. + + lanes-per-direction: + type: int + enum: [1, 2] + description: | + Number of lanes available per direction. Note that it is assumed that + same number of lanes are used in both transmit and receive. diff --git a/dts/bindings/ufs/zephyr,ufs-disk.yaml b/dts/bindings/ufs/zephyr,ufs-disk.yaml new file mode 100644 index 0000000000000..06c85d13a3998 --- /dev/null +++ b/dts/bindings/ufs/zephyr,ufs-disk.yaml @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: | + Zephyr UFS disk. + + A binding with this compatible present within a UFS host controller device node indicates + that a UFS disk device is attached to a specific UFS LUN. It enables the disk to be used + with the disk driver API and any relevant subsystems, such as Filesystems. + +compatible: "zephyr,ufs-disk" + +include: base.yaml + +properties: + lun: + type: int + default: 0 + description: | + Specifies the UFS LUN for disk operations. The default is LUN 0 if not + explicitly defined. The range for LUN values is 0 to 31. + + disk-name: + type: string + required: true + description: The name of the disk, used by the Disk subsystem for registering the UFS disk. diff --git a/include/zephyr/drivers/ufshc/ufshc.h b/include/zephyr/drivers/ufshc/ufshc.h new file mode 100644 index 0000000000000..40f251968991d --- /dev/null +++ b/include/zephyr/drivers/ufshc/ufshc.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHY_INCLUDE_DRIVERS_UFS_UFSHC_H_ +#define ZEPHY_INCLUDE_DRIVERS_UFS_UFSHC_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief UFSHC Driver APIs + * @defgroup ufshc_interface UFSHC Driver APIs + * @ingroup io_interfaces + * @{ + */ + +/** + * @brief UFS Host Controller (UFSHC) Driver API interface + * + * This structure defines the set of functions that must be implemented by the + * UFS Host Controller driver for PHY initialization and link startup notifications. + */ +__subsystem struct ufshc_driver_api { + /** + * @brief PHY initialization for the UFS Host Controller. + * + * This function is called to initialize the PHY layer of the UFS Host Controller. + * + * @param dev The device pointer to the UFS Host Controller device. + * + * @return 0 on success, negative errno value on failure. + * -ETIMEDOUT: command timed out during execution. + * -ENOTSUP: host controller does not support this operation. + * -EIO: I/O error. + */ + int (*phy_initialization)(const struct device *dev); + + /** + * @brief Link startup notification for UFS Host Controller. + * + * This function notifies the host controller about the link startup status. + * + * @param dev The device pointer to the UFS Host Controller device. + * @param status The status of the link startup, either PRE or POST. + * + * @return 0 on success, negative errno value on failure. + * -ETIMEDOUT: command timed out during execution. + * -ENOTSUP: host controller does not support this operation. + * -EIO: I/O error. + */ + int (*link_startup_notify)(const struct device *dev, uint8_t status); +}; + +/** + * @brief PHY initialization function for UFS Host Controller. + * + * This system call is used to initialize the PHY layer of the UFS Host Controller. + * It interacts with the driver through the `ufshc_driver_api` interface. + * + * @param dev The device pointer to the UFS Host Controller device. + * + * @return 0 on success, negative errno value on failure. + * -ETIMEDOUT: command timed out during execution. + * -ENOTSUP: host controller does not support this operation. + * -EIO: I/O error. + */ +__syscall int ufshc_variant_phy_initialization(const struct device *dev); + +static inline int z_impl_ufshc_variant_phy_initialization(const struct device *dev) +{ + const struct ufshc_driver_api *api = (const struct ufshc_driver_api *)dev->api; + + if (api->phy_initialization == NULL) { + return -ENOSYS; + } + + return api->phy_initialization(dev); +} + +/** + * @brief Link startup notification function for UFS Host Controller. + * + * This system call is used to notify the UFS Host Controller about the status of + * the link startup (PRE or POST). + * + * @param dev The device pointer to the UFS Host Controller device. + * @param status The status of the link startup, either PRE or POST. + * + * @return 0 on success, negative errno value on failure. + * -ETIMEDOUT: command timed out during execution. + * -ENOTSUP: host controller does not support this operation. + * -EIO: I/O error. + */ +__syscall int ufshc_variant_link_startup_notify(const struct device *dev, + uint8_t status); + +static inline int z_impl_ufshc_variant_link_startup_notify(const struct device *dev, + uint8_t status) +{ + const struct ufshc_driver_api *api = (const struct ufshc_driver_api *)dev->api; + + if (api->link_startup_notify == NULL) { + return -ENOSYS; + } + + return api->link_startup_notify(dev, status); +} + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#include + +#endif /* ZEPHY_INCLUDE_DRIVERS_UFS_UFSHC_H_ */ diff --git a/include/zephyr/scsi/scsi.h b/include/zephyr/scsi/scsi.h new file mode 100644 index 0000000000000..6bec430aba067 --- /dev/null +++ b/include/zephyr/scsi/scsi.h @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_SCSI_H_ +#define ZEPHYR_INCLUDE_SCSI_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* MACROS */ + +/* SCSI Command Operation Codes */ +#define SCSI_TST_U_RDY 0x00 /* Test Unit Ready */ +#define SCSI_READ10 0x28 /* Read 10-byte */ +#define SCSI_READ16 0x88 /* Read 16-byte */ +#define SCSI_WRITE10 0x2A /* Write 10-byte */ +#define SCSI_WRITE16 0x8A /* Write 16-byte */ + +/* Maximum Number of Retries for SCSI Cmds */ +#define SCSI_MAX_RETRIES (3) + +/* Maximum size of the Command Descriptor Block (CDB) */ +#define MAX_CDB_SIZE (16U) + +/* SCSI IOCTL Command Codes */ +#define SCSI_IOCTL_TEST_UNIT_READY (0x02) /* checks if the device is ready */ +#define SG_IO (0x85) /* issue SCSI commands to the device */ + +/* Block Storage Generic (BSG) Protocol Constants */ +#define BSG_PROTOCOL_SCSI (0U) +#define BSG_SUB_PROTOCOL_SCSI_CMD (0U) +#define BSG_SUB_PROTOCOL_SCSI_TRANSPORT (1U) + +/* Data Transfer Directions for SCSI Generic (SG) Commands */ +#define SG_DXFER_NONE (1) +#define SG_DXFER_TO_DEV (2) +#define SG_DXFER_FROM_DEV (3) + +/* STRUCTURES */ + +/** + * @brief Structure to hold information about a SCSI command to be processed + */ +struct scsi_cmd { + uint8_t cmd[MAX_CDB_SIZE]; /**< Command descriptor block (CDB) */ + uint8_t lun; /**< Target LUN (Logical Unit Number) */ + uint8_t cmdlen; /**< Length of the command */ + uint64_t datalen; /**< Total length of data to be transferred */ + uint8_t *pdata; /**< Pointer to data to be transferred */ + uint8_t dma_dir; /**< Direction of data transfer (e.g., read/write) */ +} __packed; + +/** + * @brief Structure representing LUN (Logical Unit Number) information. + */ +struct lun_info { + uint32_t lun_id; /**< ID of the LUN */ + bool lun_enabled; /**< Indicates if the LUN is enabled */ + uint32_t block_size; /**< Size of each block in bytes */ + uint64_t block_count; /**< Total number of blocks in the LUN */ +} __packed; + +/** + * @brief Information about a SCSI host controller. + */ +struct scsi_host_info { + sys_slist_t sdevices; /**< List of connected SCSI devices */ + const struct scsi_ops *ops; /**< Pointer to the SCSI operations structure */ + void *hostdata; /**< Pointer to host-specific data */ + struct device *parent; /**< Parent device associated with the host */ +}; + +/** + * @brief Struct representing a SCSI device + */ +struct scsi_device { + sys_snode_t node; /**< Node in the list of SCSI devices */ + struct scsi_host_info *host; /**< Pointer to the associated SCSI host controller */ + uint8_t lun; /**< LUN ID for the device */ + uint32_t sector_size; /**< Size of sectors in bytes */ + uint32_t capacity; /**< Total capacity of the device in blocks */ +}; + +/** + * @brief Structure to represent a SCSI Generic (SG) I/O request + */ +struct sg_io_req { + uint32_t protocol; /**< SCSI protocol type */ + uint32_t subprotocol; /**< Sub-protocol type (0 - SCSI command, 1 - SCSI transport) */ + void *request; /**< Pointer to the SCSI CDB or transport layer request */ + uint32_t request_len; /**< Length of the request in bytes */ + void *response; /**< Pointer to the response buffer */ + uint32_t max_response_len; /**< Maximum length of the response buffer */ + int32_t dxfer_dir; /**< Data transfer direction */ + uint32_t dxfer_len; /**< Length of the data to transfer */ + void *dxferp; /**< Pointer to the data to be transferred */ +}; + +/** + * @brief Operations for managing SCSI commands + */ +struct scsi_ops { + /** + * @brief Execute a SCSI command on a given SCSI device. + * + * @param sdev Pointer to the SCSI device handle + * @param cmd Pointer to the SCSI command to execute + * + * @return 0 on success, negative value on error + */ + int (*exec)(struct scsi_device *sdev, struct scsi_cmd *cmd); +}; + +/* FUNCTION PROTOTYPES */ + +/** + * @brief Perform a write operation on the SCSI device. + * + * This function sends a write command to the SCSI device. + * + * @param scsi_dev Pointer to the scsi_device structure. + * @param sector Starting sector number. + * @param count Number of sectors to write. + * @param buf Pointer to the data buffer to write. + * + * @return 0 if successful, or a negative error code on failure. + */ +int scsi_write(struct scsi_device *scsi_dev, uint64_t sector, uint32_t count, const uint8_t *buf); + +/** + * @brief Perform a read operation on the SCSI device. + * + * This function sends a read command to the SCSI device. + * + * @param scsi_dev Pointer to the scsi_device structure. + * @param sector Starting sector number. + * @param count Number of sectors to read. + * @param buf Pointer to the buffer to store the read data. + * + * @return 0 if successful, or a negative error code on failure. + */ +int scsi_read(struct scsi_device *scsi_dev, uint64_t sector, uint32_t count, uint8_t *buf); + +/** + * @brief Allocate a new scsi_host_info. + * + * This function allocates and initializes a new scsi_host_info structure, + * setting the operations field to the given scsi operations. + * + * @param sops Pointer to the scsi operations structure. + * + * @return Pointer to the newly allocated scsi_host_info, or NULL on failure. + */ +struct scsi_host_info *scsi_host_alloc(const struct scsi_ops *sops); + +/** + * @brief Add a new LUN to the SCSI host. + * + * This function adds a new LUN to the host, initializing the associated + * scsi_device if necessary. It also stores the sector size and capacity + * for the device. + * + * @param shost Pointer to the SCSI host. + * @param lun Pointer to the lun_info structure containing LUN information. + * + * @return 0 on success, or a negative error code on failure. + */ +int scsi_add_lun_host(struct scsi_host_info *shost, struct lun_info *lun); + +/** + * @brief Find a SCSI device by host and LUN. + * + * This function searches the list of devices attached to the given host + * and returns the device with the specified LUN. + * + * @param shost Pointer to the SCSI host. + * @param lun Logical Unit Number (LUN) of the SCSI device. + * + * @return Pointer to the scsi_device structure, or NULL if no matching + * device is found. + */ +struct scsi_device *scsi_device_lookup_by_host(struct scsi_host_info *shost, uint32_t lun); + +/** + * @brief Dispatch an ioctl to a SCSI device. + * + * This function dispatches the appropriate ioctl to the SCSI device based on + * the provided command. + * + * @param sdev Pointer to the scsi_device structure. + * @param cmd The ioctl command. + * @param arg Data associated with the ioctl. + * + * @return 0 if successful, or a negative error code on failure. + */ +int scsi_ioctl(struct scsi_device *sdev, int32_t cmd, void *arg); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_SCSI_H_ */ diff --git a/include/zephyr/ufs/ufs.h b/include/zephyr/ufs/ufs.h new file mode 100644 index 0000000000000..498657704df6e --- /dev/null +++ b/include/zephyr/ufs/ufs.h @@ -0,0 +1,554 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_UFS_UFS_H_ +#define ZEPHYR_INCLUDE_UFS_UFS_H_ + +#include +#include +#include + +/* MACROS */ + +/* Maximum size for UFS Host Controller Query Descriptor */ +#define UFSHC_QRY_DESC_MAX_SIZE (255U) + +/* Maximum number of Physical Region Descriptor Table entries */ +#define UFSHC_PRDT_ENTRIES (128U) + +/* Maximum Logical Unit Numbers supported by UFS controller */ +#define UFSHC_MAX_LUNS (32U) + +/* Maximum length of Command Descriptor Block */ +#define UFSHC_MAX_CDB_LEN (16U) + +/* Maximum number of link-startup retries */ +#define UFSHC_DME_LINKSTARTUP_RETRIES (3) + +/* Timeout in microseconds used in UFS operations */ +#define UFS_TIMEOUT_US (1000000U) + +/* Host Controller Enable Register (0x34h) */ +#define UFSHC_HCE_MASK (0x1U) + +/* Host Controller Status Register (0x30h) */ +#define UFSHC_HCS_DP_MASK (0x1U) +#define UFSHC_HCS_UTRLRDY_MASK (0x2U) +#define UFSHC_HCS_UCRDY_MASK (0x8U) +#define UFSHC_HCS_UPMCRS_MASK (0x700U) +#define UFSHC_HCS_CCS_MASK (0x800U) + +#define UFSHCD_STATUS_READY (UFSHC_HCS_UTRLRDY_MASK | UFSHC_HCS_UCRDY_MASK) + +/* Interrupt Status Register (0x20h) */ +#define UFSHC_IS_UTRCS_MASK (0x1U) +#define UFSHC_IS_UE_MASK (0x4U) +#define UFSHC_IS_PWR_STS_MASK (0x10U) +#define UFSHC_IS_ULSS_MASK (0x100U) +#define UFSHC_IS_UCCS_MASK (0x400U) + +#define UFSHCD_UIC_MASK (UFSHC_IS_UCCS_MASK | UFSHC_IS_PWR_STS_MASK) + +#define UFSHCD_ERROR_MASK (UFSHC_IS_UE_MASK) + +#define UFSHCD_ENABLE_INTRS (UFSHC_IS_UTRCS_MASK | UFSHCD_ERROR_MASK) + +#define UFSHC_UCMDARG2_RESCODE_MASK GENMASK(7, 0) + +/* UTP Transfer Request Run-Stop Register (0x60h) */ +#define UFSHC_UTRL_RUN (0x1U) + +#define UFSHC_256KB (0x40000U) + +/* Events for UFS interrupt */ +#define UFS_UIC_CMD_COMPLETION_EVENT BIT(0) +#define UFS_UPIU_COMPLETION_EVENT BIT(1) +#define UFS_UIC_PWR_COMPLETION_EVENT BIT(2) + +/* UFS Host Controller Registers */ +#define UFSHC_HOST_CTRL_CAP 0x00U +#define UFSHC_IS 0x20U +#define UFSHC_IE 0x24U +#define UFSHC_HCS 0x30U +#define UFSHC_HCE 0x34U +#define UFSHC_UTRLBA 0x50U +#define UFSHC_UTRLBAU 0x54U +#define UFSHC_UTRLDBR 0x58U +#define UFSHC_UTRLRSR 0x60U +#define UFSHC_UICCMD 0x90U +#define UFSHC_UCMDARG1 0x94U +#define UFSHC_UCMDARG2 0x98U +#define UFSHC_UCMDARG3 0x9CU + +/* Controller capability masks */ +#define UFSHC_TRANSFER_REQ_SLOT_MASK GENMASK(4, 0) + +/* DME Operations supported */ +#define UFSHC_DME_GET_OPCODE 0x01U +#define UFSHC_DME_SET_OPCODE 0x02U +#define UFSHC_DME_LINKSTARTUP_OPCODE 0x16U + +/* Transaction Codes */ +#define UFSHC_NOP_UPIU_TRANS_CODE 0x00U +#define UFSHC_CMD_UPIU_TRANS_CODE 0x01U +#define UFSHC_TSK_UPIU_TRANS_CODE 0x04U +#define UFSHC_QRY_UPIU_TRANS_CODE 0x16U + +/* UPIU Read/Write flags */ +#define UFSHC_UPIU_FLAGS_WRITE 0x20U +#define UFSHC_UPIU_FLAGS_READ 0x40U + +/* Query function */ +#define UFSHC_QRY_READ 0x01U +#define UFSHC_QRY_WRITE 0x81U + +/* Flag IDN for Query Requests*/ +#define UFSHC_FDEVINIT_FLAG_IDN 0x01U + +/* Attribute IDN for Query Requests */ +#define UFSHC_BLUEN_ATTRID 0x00U + +/* Descriptor IDN for Query Requests */ +#define UFSHC_DEVICE_DESC_IDN 0x0U +#define UFSHC_CONFIG_DESC_IDN 0x1U +#define UFSHC_UNIT_DESC_IDN 0x2U +#define UFSHC_GEOMETRY_DESC_IDN 0x7U + +/* Unit Descriptor Parameters Offsets (bytes) */ +#define UFSHC_UD_PARAM_UNIT_INDEX 0x2U +#define UFSHC_UD_PARAM_LU_ENABLE 0x3U +#define UFSHC_UD_PARAM_LOGICAL_BLKSZ 0xAU +#define UFSHC_UD_PARAM_LOGICAL_BLKCNT 0xBU + +/* Geometry Descriptor Parameters Offsets (bytes) */ +#define UFSHC_GEO_DESC_PARAM_MAX_NUM_LUN 0xCU + +/* UTP QUERY Transaction Specific Fields OpCode */ +#define UFSHC_QRY_READ_DESC_CMD 0x1U +#define UFSHC_QRY_WRITE_DESC_CMD 0x2U +#define UFSHC_QRY_READ_ATTR_CMD 0x3U +#define UFSHC_QRY_WRITE_ATTR_CMD 0x4U +#define UFSHC_QRY_READ_FLAG_CMD 0x5U +#define UFSHC_QRY_SET_FLAG_CMD 0x6U +#define UFSHC_QRY_CLR_FLAG_CMD 0x7U + +/* UTP Transfer Request Descriptor definitions */ +#define UFSHC_CT_UFS_STORAGE_MASK 0x10000000U +#define UFSHC_INTERRUPT_CMD_MASK 0x01000000U +#define UFSHC_DD_DEV_TO_MEM_MASK 0x04000000U +#define UFSHC_DD_MEM_TO_DEV_MASK 0x02000000U + +/* Speed Gears */ + +/* PWM */ +#define UFSHC_PWM_G1 0x2201U +#define UFSHC_PWM_G2 0x2202U +#define UFSHC_PWM_G3 0x2203U +#define UFSHC_PWM_G4 0x2204U + +/* HS RATE-A */ +#define UFSHC_HS_G1 0x11101U +#define UFSHC_HS_G2 0x11102U +#define UFSHC_HS_G3 0x11103U +#define UFSHC_HS_G4 0x11104U + +/* HS RATE-B */ +#define UFSHC_HS_G1_B 0x21101U +#define UFSHC_HS_G2_B 0x21102U +#define UFSHC_HS_G3_B 0x21103U +#define UFSHC_HS_G4_B 0x21104U + +#define UFSHC_TX_RX_FAST 0x11U +#define UFSHC_TX_RX_SLOW 0x22U + +#define UFSHC_HSSERIES_A 1U +#define UFSHC_HSSERIES_B 2U + +#define UFSHC_GEAR4 4U + +/* Power Mode change request status */ +#define UFSHC_PWR_OK 0U +#define UFSHC_PWR_LOCAL 1U +#define UFSHC_PWR_ERR_CAP 4U +#define UFSHC_PWR_FATAL_ERR 5U + +#define UFSHC_PWR_MODE_VAL 0x100U + +/* ENUMS */ +enum ChangeStage { + PRE_CHANGE, + POST_CHANGE, +}; + +/* Overall command status values */ +enum CommandStatus { + OCS_SUCCESS = 0x0, + OCS_INVALID_CMD_TABLE_ATTR = 0x1, + OCS_INVALID_PRDT_ATTR = 0x2, + OCS_MISMATCH_DATA_BUF_SIZE = 0x3, + OCS_MISMATCH_RESP_UPIU_SIZE = 0x4, + OCS_PEER_COMM_FAILURE = 0x5, + OCS_ABORTED = 0x6, + OCS_FATAL_ERROR = 0x7, + OCS_DEVICE_FATAL_ERROR = 0x8, + OCS_INVALID_CRYPTO_CONFIG = 0x9, + OCS_GENERAL_CRYPTO_ERROR = 0xA, + OCS_INVALID_COMMAND_STATUS = 0x0F, +}; + +/* STRUCTURES */ + +/** + * @brief UIC Command Structure + * + * This structure represents the UIC (Unified Host Controller Interface) command + * that is used for various operations in the UFS Host Controller. + */ +struct ufshc_uic_cmd { + uint8_t command; /**< Command identifier. */ + uint8_t attr_set_type; /**< Attribute set type. */ + uint8_t reset_level; /**< Reset level. */ + uint8_t result_code; /**< Result of the command execution. */ + uint32_t mib_value; /**< MIB (Management Information Base) value. */ + uint32_t gen_sel_index; /**< Generic selector index. */ + uint16_t mib_attribute; /**< MIB attribute. */ +} __packed; + +/** + * @brief Physical Region Descriptor Table + * + * This structure describes a physical region descriptor used in data transfer + * operations. It contains the address and byte count of a data block being + * transferred, aligned to an 8-byte boundary. + */ +struct ufshc_xfer_prdt { + uint32_t bufaddr_lower; /**< Lower 32 bits of the physical address of the data block. */ + uint32_t bufaddr_upper; /**< Upper 32 bits of the physical address of the data block. */ + uint32_t reserved; /**< Reserved for future use. */ + uint32_t data_bytecount; /**< Byte count of the data block. */ +} __packed; + +/** + * @brief UTP Transfer Request Descriptor Structure + */ +struct ufshc_xfer_req_desc { + uint32_t dw0_config; /**< Configuration bits for UTP transfer. */ + uint32_t dw1_dunl; /**< Lower 32 bits of Data Unit Number (DUN). */ + uint32_t dw2_ocs; /**< Overall Command Status (OCS). */ + uint32_t dw3_dunu; /**< Upper 32 bits of Data Unit Number (DUN). */ + uint32_t dw4_utp_cmd_desc_base_addr_lo; /**< Lower 32 bits of the UTP Command Desc base. */ + uint32_t dw5_utp_cmd_desc_base_addr_up; /**< Upper 32 bits of the UTP Command Desc base. */ + uint32_t dw6_resp_upiu_info; /**< [31:16] - Resp UPIU Offset, [15:0] - Resp UPIU Length */ + uint32_t dw7_prdt_info; /**< [31:16] - PRDT Offset, [15:0] - PRDT Length */ +} __packed; + +/** + * @brief UPIU header structure + */ +struct ufshc_upiu_header { + uint8_t transaction_type; /**< Type of the transaction (e.g., command, response) */ + uint8_t flags; /**< Various flags (e.g., task management, response expected) */ + uint8_t lun; /**< Logical Unit Number (LUN) */ + uint8_t task_tag; /**< Task tag associated with the UPIU */ + uint8_t iid_cmd_type; /**< Initiator Command Type */ + uint8_t query_task_mang_fn; /**< Task Management Function (for query UPIUs) */ + uint8_t response; /**< Response code for the transaction */ + uint8_t status; /**< Transaction status (e.g., success, failure) */ + uint8_t total_ehs_len; /**< Length of the Extended Header Segment (EHS) */ + uint8_t device_info; /**< Device-specific information */ + uint16_t data_segment_len; /**< Length of the data segment (in Big Endian format) */ +} __packed; + +/** + * @brief Command UPIU Structure + */ +struct ufshc_cmd_upiu { + uint32_t exp_data_xfer_len; /**< Expected data transfer length for the command */ + uint8_t scsi_cdb[UFSHC_MAX_CDB_LEN]; /**< SCSI Command Descriptor Block (CDB) */ +} __packed; + +/** + * @brief Transaction Specific Fields Structure + */ +struct ufshc_trans_spec_flds { + uint8_t opcode; /**< Operation code for the transaction */ + uint8_t desc_id; /**< Descriptor ID */ + uint8_t index; /**< Index for the specific transaction */ + uint8_t selector; /**< Selector value */ + uint16_t reserved0; /**< Reserved field for padding or future use */ + uint16_t length; /**< Length of the transaction (Big Endian format) */ + uint32_t value; /**< Value associated with the transaction (Big Endian format) */ + uint32_t reserved1; /**< Reserved field for padding or future use */ +} __packed; + +/** + * @brief Query UPIU Structure + */ +struct ufshc_query_upiu { + struct ufshc_trans_spec_flds tsf; /**< Transaction specific fields for the query */ + uint32_t reserved; /**< Reserved field for future use */ + uint8_t data[256]; /**< Query-specific data */ +} __packed; + +/** + * @brief NOP IN/OUT UPIU Structure + */ +struct ufshc_nop_upiu { + uint8_t reserved[20]; /**< Reserved for future use */ +} __packed; + +/** + * @brief UTP Transfer Request UPIU Structure + */ +struct ufshc_xfer_req_upiu { + struct ufshc_upiu_header upiu_header; /**< UPIU header structure */ + union { + struct ufshc_cmd_upiu cmd_upiu; /**< Command UPIU structure */ + struct ufshc_query_upiu query_req_upiu; /**< Query UPIU structure */ + struct ufshc_nop_upiu nop_out_upiu; /**< NOP OUT UPIU structure */ + }; +} __packed; + +/** + * @brief Response UPIU Structure + */ +struct ufshc_resp_upiu { + uint32_t residual_trans_count; /**< Residual transfer count (Big Endian format) */ + uint32_t reserved[4]; /**< Reserved fields */ + uint16_t sense_data_len; /**< Sense data length (Big Endian format) */ + uint8_t sense_data[20]; /**< Sense data (if any) */ +} __packed; + +/** + * @brief UTP Transfer Response UPIU Structure + */ +struct ufshc_xfer_resp_upiu { + struct ufshc_upiu_header upiu_header; /**< UPIU header structure */ + union { + struct ufshc_resp_upiu resp_upiu; /**< Response UPIU structure */ + struct ufshc_query_upiu query_resp_upiu; /**< Query response UPIU structure */ + struct ufshc_nop_upiu nop_in_upiu; /**< NOP IN UPIU structure */ + }; +} __packed; + +/** + * @brief UTP Command Request Descriptor Structure + */ +struct ufshc_xfer_cmd_desc { + struct ufshc_xfer_req_upiu req_upiu; + /**< UTP Transfer Request UPIU */ + struct ufshc_xfer_resp_upiu resp_upiu __aligned(8); + /**< UTP Transfer Response UPIU (aligned to 8 bytes) */ + struct ufshc_xfer_prdt prdt[UFSHC_PRDT_ENTRIES] __aligned(8); + /**< PRDT (Physical Region Descriptor Table) (aligned to 8 bytes) */ +} __packed; + +/** + * @brief UFS Device Information Structure + */ +struct ufs_dev_info { + uint8_t max_lu_supported; /**< Maximum number of Logical Units supported */ + struct lun_info lun[UFSHC_MAX_LUNS]; /**< Array of LUN information structures */ +}; + +/** + * @brief UFS host controller private structure + */ +struct ufs_host_controller { + struct device *dev; /**< Pointer to the driver device handle. */ + mem_addr_t mmio_base; /**< Base address for the UFSHCI memory-mapped I/O registers. */ + bool is_initialized; /**< Flag indicating if the host controller has been initialized. */ + uint32_t irq; /**< IRQ number for the UFS host controller. */ + uint8_t is_cache_coherent; /**< UFS controller supports cache coherency. */ + struct ufshc_xfer_cmd_desc *ucdl_base_addr; /**< UFS Command Desc base address */ + struct ufshc_xfer_req_desc *utrdl_base_addr; /**< UTP Transfer Request Desc base address */ + uint32_t xfer_req_depth; /**< Maximum depth of the Transfer Request Queue supported */ + uint32_t outstanding_xfer_reqs; /**< Outstanding transfer requests. */ + struct k_event irq_event; /**< Event object used to signal interrupt handling. */ + struct k_mutex ufs_lock; /**< Mutex to synchronize access to UFS driver resources. */ + struct ufs_dev_info dev_info; /**< Structure holding information about the UFS device. */ + struct scsi_host_info *host; /**< SCSI host associated with the UFS controller. */ +}; + +/* INLINE FUNCTIONS */ + +/** + * @brief Write a 32-bit value to a register in UFS host controller + * + * This function writes a 32-bit data to a specific register in the UFS host controller's + * memory-mapped I/O space. + * + * @param ufshc Pointer to the UFS host controller structure + * @param reg_offset Offset from the base address of the register to write to + * @param data Data to be written to the register + */ +static inline void ufshc_write_reg(const struct ufs_host_controller *ufshc, + uintptr_t reg_offset, + uint32_t data) +{ + sys_write32((uint32_t)(data), ((uintptr_t)((ufshc)->mmio_base) + (uintptr_t)(reg_offset))); +} + +/** + * @brief Read a 32-bit value from a register in UFS host controller + * + * This function reads a 32-bit value from a specific register in the UFS host controller's + * memory-mapped I/O space. + * + * @param ufshc Pointer to the UFS host controller structure + * @param reg_offset Offset from the base address of the register to read from + * + * @return 32-bit value read from the specified register in the UFS host controller + */ +static inline uint32_t ufshc_read_reg(const struct ufs_host_controller *ufshc, + uintptr_t reg_offset) +{ + return sys_read32(((uintptr_t)((ufshc)->mmio_base) + (uintptr_t)(reg_offset))); +} + +/* PUBLIC FUNCTIONS */ + +/** + * @brief UFS Interface APIs + * @defgroup ufs_interface UFS Subsystem Interface + * @ingroup os_services + * @{ + */ + +/** + * @brief Initialize the UFS host controller + * + * This function is responsible for initializing the UFS driver by linking the UFS + * device structure to its corresponding host controller instance and performing + * the necessary initialization steps. + * + * @param ufshc_dev Pointer to the device structure for the UFS host controller + * @param ufshc Pointer to the UFS host controller structure that will be initialized + * + * @return 0 on success, negative error code on failure. + */ +int ufs_init(const struct device *ufshc_dev, struct ufs_host_controller **ufshc); + +/** + * @brief Fill UIC command structure + * + * This function fills the provided UIC command structure with the values + * for the command, MIB attribute, generation selection, attribute set type, + * and MIB value, based on the function arguments. + * + * @param uic_cmd_ptr Pointer to the UIC command structure to be filled + * @param mib_attr_gen_sel MIB attribute and generation selection value + * (upper 16-bits for attribute, lower 16-bits for selection) + * @param attr_set_type Attribute set type for the UIC command + * @param mib_val MIB value for the UIC command + * @param cmd UIC command value + */ +void ufshc_fill_uic_cmd(struct ufshc_uic_cmd *uic_cmd_ptr, + uint32_t mib_attr_gen_sel, uint32_t mib_val, + uint32_t attr_set_type, uint32_t cmd); + +/** + * @brief Send a UIC command to the UFS host controller + * + * This function sends a UIC (UFS Interface command) to the UFS host controller + * and waits for its completion. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param uic_cmd Pointer to the UIC command structure to be sent. + * + * @return 0 on success, negative error code on failure + */ +int ufshc_send_uic_cmd(struct ufs_host_controller *ufshc, struct ufshc_uic_cmd *uic_cmd); + +/** + * @brief Send raw UPIU commands to the UFS host controller + * + * This function executes raw UPIU commands such as NOP, Query, and SCSI commands + * by using the provided `msgcode`. The caller must fill the UPIU request content + * correctly, as no further validation is performed. The function copies the response + * to the `rsp` parameter if it's not NULL. + * + * @param ufshc Pointer to the UFS host controller structure + * @param msgcode Message code, one of UPIU Transaction Codes Initiator to Target + * @param req Pointer to the request data (raw UPIU command) + * @param rsp Pointer to the response data (raw UPIU reply) + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_exec_raw_upiu_cmd(struct ufs_host_controller *ufshc, + uint32_t msgcode, void *req, void *rsp); + +/** + * @brief Read or write descriptors in the UFS host controller + * + * This function reads or writes the specified UFS descriptor based on the operation mode. + * If writing, the contents of `param_buff` are written to the descriptor. If reading, + * the descriptor contents are copied into `param_buff` starting at `param_offset`. + * + * @param ufshc Pointer to the UFS host controller structure + * @param write Boolean indicating whether to write (true) or read (false) the descriptors + * @param idn Descriptor identifier + * @param index Descriptor index + * @param param_offset Offset for the parameters in the descriptor + * @param param_buff Pointer to the buffer holding the parameters to read/write + * @param param_size Size of the parameter buffer + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_rw_descriptors(struct ufs_host_controller *ufshc, bool write, + uint8_t idn, uint8_t index, uint8_t param_offset, + void *param_buff, uint8_t param_size); + +/** + * @brief Send attribute query requests to the UFS host controller + * + * This function performs a read or write operation for a specific UFS attribute + * identified by `idn`. + * + * @param ufshc Pointer to the UFS host controller structure + * @param write Boolean indicating whether to write (true) or read (false) the attribute + * @param idn Attribute identifier + * @param data Pointer to the attribute value to read/write + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_rw_attributes(struct ufs_host_controller *ufshc, bool write, uint8_t idn, void *data); + +/** + * @brief Read or write flags in the UFS host controller + * + * This function performs a read or write operation for a UFS flag specified by + * `idn` and `index`. Supports setting, clearing, or reading the flag value. + * + * @param ufshc Pointer to the UFS host controller structure + * @param write Boolean indicating whether to write (true) or read (false) the flags + * @param idn Flag identifier + * @param index Flag index + * @param data Pointer to the data buffer for reading/writing the flag + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_rw_flags(struct ufs_host_controller *ufshc, bool write, + uint8_t idn, uint8_t index, void *data); + +/** + * @brief Configure the UFS speed gear setting + * + * This function sets the UFS host controller's speed gear by configuring the tx and rx + * attributes (such as termination capabilities) + * + * @param ufshc Pointer to the UFS host controller structure + * @param speed_gear Desired speed gear setting + * + * @return 0 on success, negative error code on failure + */ +int ufshc_configure_speedgear(struct ufs_host_controller *ufshc, uint32_t speed_gear); + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_UFS_UFS_H_ */ diff --git a/include/zephyr/ufs/ufs_ops.h b/include/zephyr/ufs/ufs_ops.h new file mode 100644 index 0000000000000..55592fe1c58d6 --- /dev/null +++ b/include/zephyr/ufs/ufs_ops.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief UFS Operations Header File + * + * This file contains the declarations and definitions for UFS operations + */ + +#ifndef ZEPHYR_INCLUDE_UFS_UFS_OPS_H_ +#define ZEPHYR_INCLUDE_UFS_UFS_OPS_H_ + +/* INCLUDES */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* UFS QUERY IOCTL macros */ + +/* UFS_QRY_IOCTL_ATTR - IOCTL request code for UFS attribute query */ +#define UFS_QRY_IOCTL_ATTR 0x51 + +/* UFS_QRY_IOCTL_FLAG - IOCTL request code for UFS flag query */ +#define UFS_QRY_IOCTL_FLAG 0x52 + +/* UFS_QRY_IOCTL_DESC - IOCTL request code for UFS descriptor query */ +#define UFS_QRY_IOCTL_DESC 0x53 + +/* ENUMS */ +/** + * @brief Message codes for SCSI Generic (SG) requests + */ +enum ufs_sg_req_msgcode { + UFS_SG_QUERY_REQ = 0x00, /**< Request for UFS query operation */ + UFS_SG_TASK_REQ = 0x01, /**< Request for UFS task operation */ +}; + +/* STRUCTURES */ + +/** + * @brief UFS attribute query IOCTL request + */ +struct ufs_qry_ioctl_attr { + uint8_t attr_id; /**< UFS attribute identifier for the query */ +}; + +/** + * @brief UFS flag query IOCTL request + */ +struct ufs_qry_ioctl_flag { + uint8_t flag_id; /**< UFS flag identifier for the query */ +}; + +/** + * @brief UFS descriptor query IOCTL request + */ +struct ufs_qry_ioctl_desc { + uint8_t desc_id; /**< Descriptor identifier for the query */ + uint8_t index; /**< Index for the descriptor query */ + uint8_t param_offset; /**< Offset for the parameter in the descriptor query */ +}; + +/** + * @brief UFS IOCTL request + * + * This structure holds data for different types of UFS query IOCTL requests, + * including attribute, flag, and descriptor queries. + */ +struct ufs_qry_ioctl_req { + uint32_t ioctl_id; /**< IOCTL identifier for the request */ + union { + struct ufs_qry_ioctl_attr attr; /**< UFS attribute query data */ + struct ufs_qry_ioctl_flag flag; /**< UFS flag query data */ + struct ufs_qry_ioctl_desc desc; /**< UFS descriptor query data */ + }; +}; + +/** + * @brief UFS SCSI Generic (SG) request + * + * This structure is used for representing SCSI Generic requests, + * querying UFS attributes, flags, or descriptors. + */ +struct ufs_sg_req { + int32_t msgcode; /**< Message code for the request */ + struct ufs_qry_ioctl_req *req_qry_ioctl; /**< Pointer to the UFS query IOCTL request data */ +}; + +/* PUBLIC FUNCTIONS */ + +/** + * @brief Handle a SCSI Generic (SG) request for UFS + * + * This function dispatches an SG_IO request to the appropriate UFS operation. + * + * @param ufshc Pointer to the UFS host controller structure + * @param arg Argument for the request, which could be a UFS query or other data + * + * @return 0 on success, negative error code on failure + */ +int ufs_sg_request(struct ufs_host_controller *ufshc, void *arg); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_UFS_UFS_OPS_H_ */ diff --git a/include/zephyr/ufs/unipro.h b/include/zephyr/ufs/unipro.h new file mode 100644 index 0000000000000..86a801a5bdb71 --- /dev/null +++ b/include/zephyr/ufs/unipro.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_UFS_UNIPRO_H_ +#define ZEPHYR_INCLUDE_UFS_UNIPRO_H_ + +/* PHY Adapter Attributes */ +#define PA_ACTIVETXDATALANES 0x1560 +#define PA_CONNECTEDTXDATALANES 0x1561 +#define PA_TXGEAR 0x1568 +#define PA_TXTERMINATION 0x1569 +#define PA_HSSERIES 0x156A +#define PA_PWRMODE 0x1571 +#define PA_ACTIVERXDATALANES 0x1580 +#define PA_CONNECTEDRXDATALANES 0x1581 +#define PA_RXGEAR 0x1583 +#define PA_RXTERMINATION 0x1584 +#define PA_TXHSADAPTTYPE 0x15D4 + +/* Transport Layer Attributes */ +#define T_CONNECTIONSTATE 0x4020 + +/* Other Attributes */ +#define VS_MPHYCFGUPDT 0xD085 +#define VS_MPHYDISABLE 0xD0C1 + +#endif /* ZEPHYR_INCLUDE_UFS_UNIPRO_H_ */ diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index 61f7f3cf940db..8af0f9f2abfa8 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -51,10 +51,12 @@ add_subdirectory_ifdef(CONFIG_MODEM_MODULES modem) add_subdirectory_ifdef(CONFIG_NETWORKING net) add_subdirectory_ifdef(CONFIG_PROFILING profiling) add_subdirectory_ifdef(CONFIG_RETENTION retention) +add_subdirectory_ifdef(CONFIG_SCSI scsi) add_subdirectory_ifdef(CONFIG_SECURE_STORAGE secure_storage) add_subdirectory_ifdef(CONFIG_SENSING sensing) add_subdirectory_ifdef(CONFIG_SETTINGS settings) add_subdirectory_ifdef(CONFIG_SHELL shell) add_subdirectory_ifdef(CONFIG_TIMING_FUNCTIONS timing) +add_subdirectory_ifdef(CONFIG_UFS_STACK ufs) add_subdirectory_ifdef(CONFIG_ZBUS zbus) # zephyr-keep-sorted-stop diff --git a/subsys/Kconfig b/subsys/Kconfig index 93880693f51b5..f0eb6ffb36629 100644 --- a/subsys/Kconfig +++ b/subsys/Kconfig @@ -38,6 +38,7 @@ source "subsys/profiling/Kconfig" source "subsys/random/Kconfig" source "subsys/retention/Kconfig" source "subsys/rtio/Kconfig" +source "subsys/scsi/Kconfig" source "subsys/sd/Kconfig" source "subsys/secure_storage/Kconfig" source "subsys/sensing/Kconfig" @@ -50,6 +51,7 @@ source "subsys/task_wdt/Kconfig" source "subsys/testsuite/Kconfig" source "subsys/timing/Kconfig" source "subsys/tracing/Kconfig" +source "subsys/ufs/Kconfig" source "subsys/usb/device/Kconfig" source "subsys/usb/device_next/Kconfig" source "subsys/usb/host/Kconfig" diff --git a/subsys/scsi/CMakeLists.txt b/subsys/scsi/CMakeLists.txt new file mode 100644 index 0000000000000..537cb0550f540 --- /dev/null +++ b/subsys/scsi/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +if (CONFIG_SCSI) + zephyr_interface_library_named(SCSI) + + zephyr_library() + zephyr_library_sources(scsi.c) + +endif() diff --git a/subsys/scsi/Kconfig b/subsys/scsi/Kconfig new file mode 100644 index 0000000000000..260a107a568e0 --- /dev/null +++ b/subsys/scsi/Kconfig @@ -0,0 +1,27 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +# SCSI Subsystem Configuration +# This option enables support for SCSI controllers, which are commonly +# used in storage devices such as hard drives, optical drives, and more. +# Enabling this option will allow the system to use SCSI-based storage +# protocols and commands. + +menuconfig SCSI + bool "Support SCSI controllers" + default n + help + Enable this option if you want to support SCSI (Small Computer + System Interface) controllers on your platform. This will allow + the system to communicate with SCSI devices and manage storage. + +if SCSI + +# Define the SCSI subsystem as a module +module = SCSI +module-str = scsi + +# Include additional logging configuration for SCSI subsystem +source "subsys/logging/Kconfig.template.log_config" + +endif # SCSI diff --git a/subsys/scsi/scsi.c b/subsys/scsi/scsi.c new file mode 100644 index 0000000000000..a5db10d25bb5f --- /dev/null +++ b/subsys/scsi/scsi.c @@ -0,0 +1,488 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +LOG_MODULE_REGISTER(scsi, CONFIG_SCSI_LOG_LEVEL); + +/* + * This file implements SCSI subsystem support to + * interface with Storage Host Controllers like UFS + * + * supports SCSI cmds + * - TEST_UNIT_READY + * - READ + * - WRITE + */ + +/** + * @brief Dispatch a command to the low-level driver. + * + * This function calls the low-level driver to execute the specified SCSI + * command. The execution is done via the appropriate function in the + * host's operations (sdev->host->ops->exec). + * + * @param sdev Pointer to the scsi_device structure. + * @param cmd Pointer to the scsi_cmd structure to be dispatched. + * + * @return 0 on success, or a negative error code on failure. + */ +static int scsi_exec(struct scsi_device *sdev, struct scsi_cmd *cmd) +{ + const struct scsi_ops *ops = (const struct scsi_ops *)sdev->host->ops; + + if (ops->exec == NULL) { + return -EINVAL; + } + + /* Execute the command if supported */ + return ops->exec(sdev, cmd); +} + +/** + * @brief Allocate a new scsi_host_info. + * + * This function allocates and initializes a new scsi_host_info structure, + * setting the operations field to the given scsi operations. + * + * @param sops Pointer to the scsi operations structure. + * + * @return Pointer to the newly allocated scsi_host_info, or NULL on failure. + */ +struct scsi_host_info *scsi_host_alloc(const struct scsi_ops *sops) +{ + struct scsi_host_info *shost = NULL; + + /* Allocate memory for the SCSI host structure */ + shost = (struct scsi_host_info *)k_malloc(sizeof(struct scsi_host_info)); + if (shost == NULL) { + return NULL; + } + + /* Initialize the list of devices and set host operations */ + (void)memset(shost, 0, sizeof(struct scsi_host_info)); + sys_slist_init(&shost->sdevices); + shost->ops = sops; + + return shost; +} + +/** + * @brief Find a SCSI device by host and LUN. + * + * This function searches the list of devices attached to the given host + * and returns the device with the specified LUN. + * + * @param shost Pointer to the SCSI host. + * @param lun Logical Unit Number (LUN) of the SCSI device. + * + * @return Pointer to the scsi_device structure, or NULL if no matching + * device is found. + */ +struct scsi_device *scsi_device_lookup_by_host(struct scsi_host_info *shost, + uint32_t lun) +{ + struct scsi_device *sdev = NULL, *itr_sdev; + sys_snode_t *itr_node; + + /* Iterate through the list of devices attached to the host */ + SYS_SLIST_FOR_EACH_NODE(&shost->sdevices, itr_node) { + itr_sdev = (struct scsi_device *)CONTAINER_OF(itr_node, + struct scsi_device, + node); + + /* Check if the device matches the given LUN. */ + if ((itr_sdev->lun) == lun) { + sdev = itr_sdev; + break; + } + } + + return sdev; +} + +/** + * @brief Allocate and initialize a scsi_device. + * + * This function allocates and initializes a new scsi_device structure + * for the given host and LUN, and appends it to the host's device list. + * + * @param shost Pointer to the SCSI host. + * @param lun Logical Unit Number (LUN) for the new SCSI device. + * + * @return Pointer to the newly allocated scsi_device, or NULL on failure. + */ +static struct scsi_device *scsi_alloc_sdev(struct scsi_host_info *shost, + uint32_t lun) +{ + struct scsi_device *sdev; + + /* Allocate memory for the new device structure */ + sdev = (struct scsi_device *)k_malloc(sizeof(struct scsi_device)); + if (sdev == NULL) { + return NULL; + } + + /* Associate the device with the host and set its LUN */ + (void)memset(sdev, 0, sizeof(struct scsi_device)); + sdev->host = shost; + sdev->lun = (uint8_t)lun; + + /* Append the new device to the host's device list */ + sys_slist_append(&shost->sdevices, &sdev->node); + + return sdev; +} + +/** + * @brief Add a new LUN to the SCSI host. + * + * This function adds a new LUN to the host, initializing the associated + * scsi_device if necessary. It also stores the sector size and capacity + * for the device. + * + * @param shost Pointer to the SCSI host. + * @param luninfo Pointer to the lun_info structure containing LUN information. + * + * @return 0 on success, or a negative error code on failure. + */ +int scsi_add_lun_host(struct scsi_host_info *shost, struct lun_info *lun) +{ + struct scsi_device *sdev; + int res = 0; + + /* Check if the LUN information is valid and enabled */ + if ((lun == NULL) || (lun->lun_enabled == false)) { + res = -EINVAL; + goto out; + } + + /* Try to find the device associated with the given LUN */ + sdev = scsi_device_lookup_by_host(shost, lun->lun_id); + if (sdev == NULL) { + /* If no device found, allocate a new one for this LUN */ + sdev = scsi_alloc_sdev(shost, lun->lun_id); + } + + if (sdev == NULL) { + res = -ENOMEM; + goto out; + } + + /* Save sector size and capacity based on the LUN info */ + sdev->sector_size = lun->block_size; + sdev->capacity = (uint32_t)lun->block_count; + +out: + return res; +} + +/** + * @brief Frame the "Test Unit Ready" SCSI command. + * + * This function populates the command block for a "Test Unit Ready" + * command (SCSI TST_U_RDY). + * + * @param pccb: Pointer to the scsi_cmd structure to be populated. + */ +static void scsi_setup_test_unit_ready(struct scsi_cmd *pccb) +{ + pccb->cmd[0] = SCSI_TST_U_RDY; + pccb->cmd[1] = 0; + pccb->cmd[2] = 0; + pccb->cmd[3] = 0; + pccb->cmd[4] = 0; + pccb->cmd[5] = 0; + pccb->cmdlen = 6; +} + +/** + * @brief Frame the "READ_10" or "READ_16" SCSI command. + * + * This function selects either a "READ_10" or "READ_16" command based on + * the number of blocks and starting address, and populates the command + * block accordingly. + * + * @param pccb: Pointer to the scsi_cmd structure to be populated. + * @param start: Starting block number. + * @param blocks: Number of blocks to read. + */ +static void scsi_setup_read(struct scsi_cmd *pccb, uint64_t start, uint32_t blocks) +{ + if ((blocks > (uint32_t)UINT16_MAX) || (start > (uint64_t)UINT32_MAX)) { + /* Read(16) for larger addresses (64-bit) and block counts (32-bit) */ + pccb->cmd[0] = SCSI_READ16; + pccb->cmd[1] = 0; + + /* start address (64-bit) */ + pccb->cmd[2] = (uint8_t)(start >> 56) & (uint8_t)(0xff); + pccb->cmd[3] = (uint8_t)(start >> 48) & (uint8_t)(0xff); + pccb->cmd[4] = (uint8_t)(start >> 40) & (uint8_t)(0xff); + pccb->cmd[5] = (uint8_t)(start >> 32) & (uint8_t)(0xff); + pccb->cmd[6] = (uint8_t)(start >> 24) & (uint8_t)(0xff); + pccb->cmd[7] = (uint8_t)(start >> 16) & (uint8_t)(0xff); + pccb->cmd[8] = (uint8_t)(start >> 8) & (uint8_t)(0xff); + pccb->cmd[9] = (uint8_t)start & (uint8_t)(0xff); + pccb->cmd[10] = 0; + + /* block count (32-bit) */ + pccb->cmd[11] = (uint8_t)(blocks >> 24) & (uint8_t)(0xff); + pccb->cmd[12] = (uint8_t)(blocks >> 16) & (uint8_t)(0xff); + pccb->cmd[13] = (uint8_t)(blocks >> 8) & (uint8_t)(0xff); + pccb->cmd[14] = (uint8_t)blocks & (uint8_t)(0xff); + pccb->cmd[15] = 0; + pccb->cmdlen = 16U; + } else { + /* Read(10) command (for smaller addresses and block counts) */ + pccb->cmd[0] = SCSI_READ10; + pccb->cmd[1] = 0; + + /* start address (32-bit) */ + pccb->cmd[2] = (uint8_t)(start >> 24) & (uint8_t)(0xff); + pccb->cmd[3] = (uint8_t)(start >> 16) & (uint8_t)(0xff); + pccb->cmd[4] = (uint8_t)(start >> 8) & (uint8_t)(0xff); + pccb->cmd[5] = (uint8_t)start & (uint8_t)(0xff); + pccb->cmd[6] = 0; + + /* block count (16-bit) */ + pccb->cmd[7] = (uint8_t)(blocks >> 8) & (uint8_t)(0xff); + pccb->cmd[8] = (uint8_t)blocks & (uint8_t)(0xff); + pccb->cmd[9] = 0; + pccb->cmdlen = 10U; + } +} + +/** + * @brief Frame the "WRITE_10" or "WRITE_16" SCSI command. + * + * This function selects either a "WRITE_10" or "WRITE_16" command based on + * the number of blocks and starting address, and populates the command + * block accordingly. + * + * @param pccb Pointer to the scsi_cmd structure to be populated. + * @param start Starting block number. + * @param blocks Number of blocks to write. + */ +static void scsi_setup_write(struct scsi_cmd *pccb, uint64_t start, uint32_t blocks) +{ + if ((blocks > (uint32_t)UINT16_MAX) || (start > (uint64_t)UINT32_MAX)) { + /* Write(16) for larger addresses (64-bit) and block counts (32-bit) */ + pccb->cmd[0] = SCSI_WRITE16; + pccb->cmd[1] = 0; + + /* start address (64-bit) */ + pccb->cmd[2] = (uint8_t)(start >> 56) & (uint8_t)(0xff); + pccb->cmd[3] = (uint8_t)(start >> 48) & (uint8_t)(0xff); + pccb->cmd[4] = (uint8_t)(start >> 40) & (uint8_t)(0xff); + pccb->cmd[5] = (uint8_t)(start >> 32) & (uint8_t)(0xff); + pccb->cmd[6] = (uint8_t)(start >> 24) & (uint8_t)(0xff); + pccb->cmd[7] = (uint8_t)(start >> 16) & (uint8_t)(0xff); + pccb->cmd[8] = (uint8_t)(start >> 8) & (uint8_t)(0xff); + pccb->cmd[9] = (uint8_t)(start) & (uint8_t)(0xff); + pccb->cmd[10] = 0; + + /* block count (32-bit) */ + pccb->cmd[11] = (uint8_t)(blocks >> 24) & (uint8_t)(0xff); + pccb->cmd[12] = (uint8_t)(blocks >> 16) & (uint8_t)(0xff); + pccb->cmd[13] = (uint8_t)(blocks >> 8) & (uint8_t)(0xff); + pccb->cmd[14] = (uint8_t)(blocks) & (uint8_t)(0xff); + pccb->cmd[15] = 0; + pccb->cmdlen = 16U; + } else { + /* Write(10) command (for smaller addresses and block counts) */ + pccb->cmd[0] = SCSI_WRITE10; + pccb->cmd[1] = 0; + + /* start address (32-bit) */ + pccb->cmd[2] = (uint8_t)(start >> 24) & (uint8_t)(0xff); + pccb->cmd[3] = (uint8_t)(start >> 16) & (uint8_t)(0xff); + pccb->cmd[4] = (uint8_t)(start >> 8) & (uint8_t)(0xff); + pccb->cmd[5] = (uint8_t)(start) & (uint8_t)(0xff); + pccb->cmd[6] = 0; + + /* block count (16-bit) */ + pccb->cmd[7] = (uint8_t)(blocks >> 8) & (uint8_t)(0xff); + pccb->cmd[8] = (uint8_t)(blocks) & (uint8_t)(0xff); + pccb->cmd[9] = 0; + pccb->cmdlen = 10U; + } +} + +/** + * @brief Test if the SCSI unit is ready. + * + * This function sends a "Test Unit Ready" (TUR) command to the device. + * + * @param scsi_dev Pointer to the scsi_device structure. + * @param lun Logical Unit Number (LUN). + * + * @return 0 if successful, or a negative error code on failure. + */ +static int scsi_test_unit_ready(struct scsi_device *scsi_dev, int32_t lun) +{ + int ret; + int32_t retries = SCSI_MAX_RETRIES; + struct scsi_cmd cmd = {0}; + + /* Set up the SCSI command structure for TUR */ + cmd.lun = (uint8_t)lun; + cmd.datalen = (0); + cmd.dma_dir = (uint8_t)PERIPHERAL_TO_PERIPHERAL; + + /* Setup the "Test Unit Ready" command */ + scsi_setup_test_unit_ready(&cmd); + + /* Execute the SCSI command and return the result */ + do { + ret = scsi_exec(scsi_dev, &cmd); + retries--; + } while ((ret < 0) && (retries > 0)); + + return ret; +} + +/** + * @brief Perform a write operation on the SCSI device. + * + * This function sends a write command to the SCSI device. + * + * @param scsi_dev Pointer to the scsi_device structure. + * @param sector Starting sector number. + * @param count Number of sectors to write. + * @param buf Pointer to the data buffer to write. + * + * @return 0 if successful, or a negative error code on failure. + */ +int scsi_write(struct scsi_device *scsi_dev, uint64_t sector, + uint32_t count, const uint8_t *buf) +{ + int ret; + struct scsi_cmd cmd = {0}; + + /* Set up the SCSI command structure */ + cmd.lun = scsi_dev->lun; + cmd.pdata = (uint8_t *)(uintptr_t)buf; + cmd.datalen = ((uint64_t)(scsi_dev->sector_size) * (uint64_t)(count)); + cmd.dma_dir = (uint8_t)MEMORY_TO_PERIPHERAL; + + /* Setup the "Write" command with specified sector and count */ + scsi_setup_write(&cmd, sector, count); + + /* Execute the SCSI command and return the result */ + ret = scsi_exec(scsi_dev, &cmd); + + return ret; +} + +/** + * @brief Perform a read operation on the SCSI device. + * + * This function sends a read command to the SCSI device. + * + * @param scsi_dev Pointer to the scsi_device structure. + * @param sector Starting sector number. + * @param count Number of sectors to read. + * @param buf Pointer to the buffer to store the read data. + * + * @return 0 if successful, or a negative error code on failure. + */ +int scsi_read(struct scsi_device *scsi_dev, uint64_t sector, uint32_t count, uint8_t *buf) +{ + int ret; + struct scsi_cmd cmd = {0}; + + /* Set up the SCSI command structure */ + cmd.lun = scsi_dev->lun; + cmd.pdata = (uint8_t *)buf; + cmd.datalen = ((uint64_t)(scsi_dev->sector_size) * (uint64_t)(count)); + cmd.dma_dir = (uint8_t)PERIPHERAL_TO_MEMORY; + + /* Setup the "Read" command with specified sector and count */ + scsi_setup_read(&cmd, sector, count); + + /* Execute the SCSI command and return the result */ + ret = scsi_exec(scsi_dev, &cmd); + + return ret; +} + +/** + * @brief Handle SG_IO ioctl for SCSI device. + * + * This function handles the SG_IO ioctl command for the SCSI device, + * which allows for sending arbitrary SCSI commands. + * + * @param sdev Pointer to the scsi_device structure. + * @param req Pointer to the ioctl arguments. + * + * @return 0 if successful, or a negative error code on failure. + */ +static int scsi_ioctl_sg_io(struct scsi_device *sdev, struct sg_io_req *req) +{ + struct scsi_cmd scmd = {0}; + int ret; + + /* Validate the request */ + if ((req == NULL) || (req->protocol != (uint32_t)BSG_PROTOCOL_SCSI) || + (req->subprotocol != (uint32_t)BSG_SUB_PROTOCOL_SCSI_CMD)) { + ret = -EINVAL; + goto out; + } + + /* Initialize the SCSI command structure with ioctl request */ + scmd.lun = sdev->lun; + scmd.cmdlen = (uint8_t)(req->request_len); + if (scmd.cmdlen > sizeof(scmd.cmd)) { + ret = -EINVAL; + goto out; + } + (void)memcpy((void *)&scmd.cmd[0], req->request, scmd.cmdlen); + scmd.dma_dir = (uint8_t)(req->dxfer_dir); + scmd.datalen = req->dxfer_len; + scmd.pdata = req->dxferp; + + /* Execute the SCSI command and return the result */ + ret = scsi_exec(sdev, &scmd); + +out: + return ret; +} + +/** + * @brief Dispatch an ioctl to a SCSI device. + * + * This function dispatches the appropriate ioctl to the SCSI device based on + * the provided command. + * + * @param sdev Pointer to the scsi_device structure. + * @param cmd The ioctl command. + * @param arg Data associated with the ioctl. + * + * @return 0 if successful, or a negative error code on failure. + */ +int scsi_ioctl(struct scsi_device *sdev, int32_t cmd, void *arg) +{ + int ret = -EINVAL; + + /* Process the specific IOCTL command */ + switch (cmd) { + case SCSI_IOCTL_TEST_UNIT_READY: + /* Check if the device is ready */ + ret = scsi_test_unit_ready(sdev, (int32_t)sdev->lun); + break; + case SG_IO: + /* Process a SCSI command via SG_IO */ + ret = scsi_ioctl_sg_io(sdev, (struct sg_io_req *)arg); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} diff --git a/subsys/ufs/CMakeLists.txt b/subsys/ufs/CMakeLists.txt new file mode 100644 index 0000000000000..7ae651ba08b30 --- /dev/null +++ b/subsys/ufs/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +if (CONFIG_UFS_STACK) + zephyr_interface_library_named(UFS) + + zephyr_library() + zephyr_library_sources(ufs.c ufs_ops.c) + +endif() diff --git a/subsys/ufs/Kconfig b/subsys/ufs/Kconfig new file mode 100644 index 0000000000000..2ec18a5916c90 --- /dev/null +++ b/subsys/ufs/Kconfig @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +# UFS stack configuration options + +menuconfig UFS_STACK + bool "UFS protocol support" + depends on SCSI + select UFSHC + select DYNAMIC_INTERRUPTS + select EVENTS + default n + help + Enable support for the UFS (Universal Flash Storage) protocol. + This is required for the proper functioning of UFS devices. + +if UFS_STACK + +# UFS module configuration +module = UFS +module-str = UFS stack + +# Source additional logging configuration +source "subsys/logging/Kconfig.template.log_config" + +endif # UFS_STACK diff --git a/subsys/ufs/ufs.c b/subsys/ufs/ufs.c new file mode 100644 index 0000000000000..b225854c2b518 --- /dev/null +++ b/subsys/ufs/ufs.c @@ -0,0 +1,2211 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * This file implements core initialization + * for Universal Flash Storage Host Controller driver + * + * Right now the implementation: + * - support UFS Host Controller Initialization + * - support Device configuration + * - support Command Transfer Requests + * - does not support Task Management + * - does not support RPMB request + * - does not support asynchronous transfer + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ufs, CONFIG_UFS_LOG_LEVEL); + +/** + * @brief Fill the UFS Protocol Information Unit (UPIU) header + * + * @param ufshc Pointer to the UFS host controller instance + * @param trans_type Transaction type to be set in the UPIU header + * @param upiu_dw0 UPIU flags and task tag, combined into a 32-bit word + * @param query_task_mang_fn Task management function value to be set in the UPIU header + * @param data_segment_len Length of the data segment, in little-endian format + */ +static void ufshc_fill_upiu_header(struct ufs_host_controller *ufshc, + uint8_t trans_type, uint32_t upiu_dw0, + uint8_t query_task_mang_fn, + uint16_t data_segment_len) +{ + struct ufshc_xfer_req_upiu *ucd_req_ptr = &ufshc->ucdl_base_addr->req_upiu; + + ucd_req_ptr->upiu_header.transaction_type = trans_type; + ucd_req_ptr->upiu_header.flags = (uint8_t)(upiu_dw0 >> 8U); + ucd_req_ptr->upiu_header.task_tag = (uint8_t)(upiu_dw0 >> 24U); + ucd_req_ptr->upiu_header.query_task_mang_fn = query_task_mang_fn; + ucd_req_ptr->upiu_header.data_segment_len = sys_cpu_to_be16(data_segment_len); +} + +/** + * @brief Fill UTP Transfer request descriptor header + * + * @param ufshc Pointer to the UFS host controller instance + * @param slot Slot index to place the transfer request descriptor + * @param data_direction Data transfer direction (e.g., read or write) + * @param resp_upiu_len Length of the response UPIU (Protocol Information Unit) + * @param prdt_len Length of the PRDT (Physical Region Descriptor Table) entry, or zero if none + */ +static void ufshc_fill_utp_trans_req_desc(struct ufs_host_controller *ufshc, + uint32_t slot, uint32_t data_direction, + uint32_t resp_upiu_len, uint32_t prdt_len) +{ + uint32_t dw0; + uint32_t prdt_offset; + uint32_t resp_upiu_offset; + uint32_t resp_upiu_info; + uint32_t prdt_info; + struct ufshc_xfer_req_desc *utrd_ptr; + + /* Set the control word (dw0) with storage, data direction, and interrupt */ + dw0 = ((uint32_t)UFSHC_CT_UFS_STORAGE_MASK | + (uint32_t)data_direction | + (uint32_t)UFSHC_INTERRUPT_CMD_MASK); + + /* response UPIU offset and info */ + resp_upiu_offset = (uint32_t)((uintptr_t)&(ufshc->ucdl_base_addr->resp_upiu) - + (uintptr_t)(ufshc->ucdl_base_addr)); + resp_upiu_info = (uint32_t)((resp_upiu_offset >> 2U) << 16U) | + (uint32_t)((sizeof(ufshc->ucdl_base_addr->resp_upiu.upiu_header) + + resp_upiu_len) >> 2U); + + /* calculate PRDT offset and related information */ + if (prdt_len != 0U) { + prdt_offset = (uint32_t)((uintptr_t)&(ufshc->ucdl_base_addr->prdt) - + (uintptr_t)ufshc->ucdl_base_addr); + prdt_info = (uint32_t)((prdt_offset >> 2U) << 16U) | (uint32_t)prdt_len; + } else { + prdt_info = 0U; + } + + /* fill the transfer request descriptor for the given slot */ + utrd_ptr = ((struct ufshc_xfer_req_desc *)ufshc->utrdl_base_addr) + slot; + + utrd_ptr->dw0_config = dw0; + utrd_ptr->dw1_dunl = 0U; + utrd_ptr->dw2_ocs = (uint32_t)OCS_INVALID_COMMAND_STATUS; + utrd_ptr->dw3_dunu = 0U; + utrd_ptr->dw4_utp_cmd_desc_base_addr_lo = (uint32_t)((uintptr_t)(ufshc->ucdl_base_addr)); + utrd_ptr->dw5_utp_cmd_desc_base_addr_up = 0U; + utrd_ptr->dw6_resp_upiu_info = resp_upiu_info; + utrd_ptr->dw7_prdt_info = prdt_info; +} + +/** + * @brief Find an available slot in the transfer request list (TRL) + * + * This function checks the Transfer Request List (TRL) of the UFS host controller to find + * an available slot. + * + * @param ufshc Pointer to the UFS host controller private data structure + * @param slot Pointer to a variable that will hold the index of the available slot + */ +static void ufshc_find_slot_in_trl(struct ufs_host_controller *ufshc, uint32_t *slot) +{ + uint32_t reg_data; + uint32_t index; + + reg_data = ufshc_read_reg(ufshc, UFSHC_UTRLDBR); + + for (index = 0; index < ufshc->xfer_req_depth; index++) { + if ((reg_data & ((uint32_t)1U << index)) == 0U) { + *slot = index; + break; + } + } +} + +/** + * @brief Fill the UPIU (UFS Protocol Information Unit) for SCSI commands + * + * @param ufshc Pointer to the UFS host controller private data structure + * @param slot Slot index for the UFS transaction + * @param pccb Pointer to the SCSI command structure (SCSI Control Block) + */ +static void ufshc_fill_scsi_cmd_upiu(struct ufs_host_controller *ufshc, + uint32_t slot, struct scsi_cmd *pccb) +{ + uint32_t data_direction; + uint32_t prdt_entries; + uint32_t data_cnt; + uint8_t *localbuf; + uint32_t index = 0U; + uint32_t upiu_dw0, tmp_val; + uint32_t task_tag = slot; + struct ufshc_xfer_cmd_desc *ucd_ptr = ufshc->ucdl_base_addr; + + (void)memset(ucd_ptr, 0, sizeof(struct ufshc_xfer_cmd_desc)); + + /* data direction */ + if (pccb->dma_dir == (uint8_t)PERIPHERAL_TO_MEMORY) { + data_direction = UFSHC_DD_DEV_TO_MEM_MASK; + upiu_dw0 = ((uint32_t)UFSHC_UPIU_FLAGS_READ << 8U) | + (task_tag << 24U); + } else if (pccb->dma_dir == (uint8_t)MEMORY_TO_PERIPHERAL) { + data_direction = UFSHC_DD_MEM_TO_DEV_MASK; + upiu_dw0 = ((uint32_t)UFSHC_UPIU_FLAGS_WRITE << 8U) | + (task_tag << 24U); + } else { + data_direction = 0U; + upiu_dw0 = (slot << 24U); + } + ufshc_fill_upiu_header(ufshc, (uint8_t)UFSHC_CMD_UPIU_TRANS_CODE, + upiu_dw0, 0U, 0U); + ucd_ptr->req_upiu.upiu_header.lun = pccb->lun; + + /* fill scsi cmd upiu */ + (void)memcpy(ucd_ptr->req_upiu.cmd_upiu.scsi_cdb, pccb->cmd, pccb->cmdlen); + ucd_ptr->req_upiu.cmd_upiu.exp_data_xfer_len = sys_cpu_to_be32((uint32_t)pccb->datalen); + + /* fill prdt entries */ + data_cnt = (uint32_t)(pccb->datalen); + prdt_entries = data_cnt / UFSHC_256KB; + + /* iterate for full 256KB blocks */ + for (index = 0U; index < prdt_entries; index++) { + if (pccb->pdata != NULL) { + localbuf = &(pccb->pdata[(index * UFSHC_256KB)]); + } else { + localbuf = NULL; + } + ucd_ptr->prdt[index].bufaddr_lower = (uint32_t)((uintptr_t)localbuf); + tmp_val = (uint32_t)((uint64_t)((uintptr_t)localbuf) >> 32U); + ucd_ptr->prdt[index].bufaddr_upper = tmp_val; + ucd_ptr->prdt[index].data_bytecount = UFSHC_256KB - 1U; + } + + /* leftover (less than 256KB) */ + if ((data_cnt % UFSHC_256KB) != 0U) { + if (pccb->pdata != NULL) { + localbuf = &(pccb->pdata[(index * UFSHC_256KB)]); + } else { + localbuf = NULL; + } + ucd_ptr->prdt[index].bufaddr_lower = (uint32_t)((uintptr_t)localbuf); + tmp_val = (uint32_t)((uint64_t)((uintptr_t)localbuf) >> 32U); + ucd_ptr->prdt[index].bufaddr_upper = tmp_val; + ucd_ptr->prdt[index].data_bytecount = (data_cnt % UFSHC_256KB) - 1U; + prdt_entries = prdt_entries + 1U; + } + + /* Perform cache flush to ensure correct data transfer */ + (void)sys_cache_data_flush_range((void *)pccb->pdata, data_cnt); + (void)sys_cache_data_invd_range((void *)pccb->pdata, data_cnt); + + /* Fill the UTP (UFS Transport Protocol) request descriptor */ + ufshc_fill_utp_trans_req_desc(ufshc, slot, data_direction, + (uint32_t)(sizeof(ucd_ptr->resp_upiu.resp_upiu)), + prdt_entries); +} + +/** + * @brief Initialize the query request transaction fields for UPIU + * + * @param ufshc Pointer to the UFS host controller private data structure + * @param cmd Query opcode (command for the query) + * @param tsf_dw0 Query parameters packed into a 32-bit value (includes IDN, index, and selector) + * @param value The query value to be sent + * @param length The length of the query + */ +static void ufshc_fill_query_req_upiu_tsf(struct ufs_host_controller *ufshc, + uint8_t cmd, uint32_t tsf_dw0, + uint32_t value, uint16_t length) +{ + struct ufshc_xfer_cmd_desc *ucd_ptr = ufshc->ucdl_base_addr; + struct ufshc_xfer_req_upiu *req_upiu = &ucd_ptr->req_upiu; + + req_upiu->query_req_upiu.tsf.opcode = cmd; + req_upiu->query_req_upiu.tsf.desc_id = (uint8_t)(tsf_dw0 >> 8U); + req_upiu->query_req_upiu.tsf.index = (uint8_t)(tsf_dw0 >> 16U); + req_upiu->query_req_upiu.tsf.selector = (uint8_t)(tsf_dw0 >> 24U); + req_upiu->query_req_upiu.tsf.value = sys_cpu_to_be32(value); + req_upiu->query_req_upiu.tsf.length = sys_cpu_to_be16(length); +} + +/** + * @brief Initialize the query request parameters for UPIU + * + * @param ufshc Pointer to the UFS host controller private data structure + * @param slot The slot index for the UFS command + * @param opcode Query opcode (command for the query) + * @param idn Query descriptor ID + * @param idx Query index + * @param sel Query selector + * @param val Query value + * @param len Query length + */ +static void ufshc_fill_query_upiu(struct ufs_host_controller *ufshc, uint32_t slot, + int32_t opcode, uint8_t idn, uint8_t idx, + uint8_t sel, uint32_t val, uint16_t len) +{ + uint32_t task_tag = slot; + uint8_t query_task_mang_fn = 0U; + uint32_t upiu_dw0; + uint32_t tsf_dw0 = 0U; + struct ufshc_xfer_cmd_desc *ucd_ptr = ufshc->ucdl_base_addr; + + /* Determine the query type based on the provided opcode */ + switch (opcode) { + case (int32_t)UFSHC_QRY_READ_DESC_CMD: + case (int32_t)UFSHC_QRY_READ_ATTR_CMD: + case (int32_t)UFSHC_QRY_READ_FLAG_CMD: + query_task_mang_fn = UFSHC_QRY_READ; + break; + case (int32_t)UFSHC_QRY_WRITE_ATTR_CMD: + case (int32_t)UFSHC_QRY_WRITE_DESC_CMD: + case (int32_t)UFSHC_QRY_SET_FLAG_CMD: + query_task_mang_fn = UFSHC_QRY_WRITE; + break; + default: + query_task_mang_fn = 0; + break; + } + + /* Construct the UPIU header */ + upiu_dw0 = (task_tag << 24U); + ufshc_fill_upiu_header(ufshc, (uint8_t)UFSHC_QRY_UPIU_TRANS_CODE, + upiu_dw0, query_task_mang_fn, 0U); + + /* Construct the transaction-specific data word (TSF) */ + tsf_dw0 = ((uint32_t)idn << 8U) | + ((uint32_t)idx << 16U) | + ((uint32_t)sel << 24U); + ufshc_fill_query_req_upiu_tsf(ufshc, (uint8_t)opcode, + tsf_dw0, val, (uint16_t)len); + + /* Initialize the Response field with FAILURE */ + ucd_ptr->resp_upiu.upiu_header.response = 1U; + + /* Initialize the UTP transfer request descriptor */ + ufshc_fill_utp_trans_req_desc(ufshc, slot, 0U, + (uint32_t)(sizeof(ucd_ptr->resp_upiu.query_resp_upiu)), 0U); +} + +/** + * @brief Initialize the attribute UPIU request parameters. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param slot Slot number for the UPIU request. + * @param opcode Opcode specifying the operation to perform (e.g., read, write). + * @param idn Attribute identifier (IDN) to access. + * @param index Index field for the attribute request. + * @param selector Selector field for the attribute request. + * @param attr_val Attribute value to be used in the request. + */ +static inline void ufshc_fill_attr_upiu(struct ufs_host_controller *ufshc, uint32_t slot, + int32_t opcode, int32_t idn, uint8_t index, + uint8_t selector, uint32_t attr_val) +{ + ufshc_fill_query_upiu(ufshc, slot, opcode, + (uint8_t)idn, index, selector, + attr_val, 0U); +} + +/** + * @brief Initialize the descriptor UPIU request parameters. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param slot Slot number for the UPIU request. + * @param opcode Opcode specifying the operation to perform (e.g., read, write). + * @param idn Descriptor identifier (IDN) to access. + * @param index: Index field for the descriptor request. + * @param selector Selector field for the descriptor request. + * @param desc_len Length parameter of the descriptor to be used in the request. + */ +static inline void ufshc_fill_desc_upiu(struct ufs_host_controller *ufshc, uint32_t slot, + int32_t opcode, int32_t idn, uint8_t index, + uint8_t selector, int32_t desc_len) +{ + ufshc_fill_query_upiu(ufshc, slot, opcode, + (uint8_t)idn, index, selector, 0U, (uint16_t)desc_len); +} + +/** + * @brief Initialize the flag UPIU request parameters. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param slot Slot number for the UPIU request. + * @param opcode Opcode specifying the flag operation to perform. + * @param idn Flag identifier (IDN) to access. + * @param index Index field for the flag request. + * @param selector Selector field for the flag request. + */ +static inline void ufshc_fill_flag_upiu(struct ufs_host_controller *ufshc, uint32_t slot, + int32_t opcode, int32_t idn, + uint8_t index, uint8_t selector) +{ + ufshc_fill_query_upiu(ufshc, slot, opcode, + (uint8_t)idn, index, selector, 0U, 0U); +} + +/** + * @brief Fill NOP UPIU (No Operation) request parameters. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param slot Slot number for the UPIU request. + */ +static void ufshc_fill_nop_upiu(struct ufs_host_controller *ufshc, uint32_t slot) +{ + uint32_t upiu_dw0; + uint32_t task_tag = slot; + struct ufshc_xfer_cmd_desc *ucd_ptr = ufshc->ucdl_base_addr; + + /* Clear the command descriptor entry to ensure no residual data */ + (void)memset(ufshc->ucdl_base_addr, 0, sizeof(struct ufshc_xfer_cmd_desc)); + + /* Construct the UPIU header */ + upiu_dw0 = (task_tag << 24U); + ufshc_fill_upiu_header(ufshc, (uint8_t)UFSHC_NOP_UPIU_TRANS_CODE, upiu_dw0, 0, 0); + + /* Initialize the Response field with FAILURE */ + ucd_ptr->resp_upiu.upiu_header.response = 1U; + + /* Initialize the UTP transfer request descriptor */ + ufshc_fill_utp_trans_req_desc(ufshc, slot, 0U, + (uint32_t)(sizeof(ucd_ptr->resp_upiu.nop_in_upiu)), 0U); +} + +/** + * @brief Send a SCSI and UPIU command to the UFS host controller. + * + * This function sends a UPIU (Unified Protocol Information Unit) command + * to the UFS host controller. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param slot_tag Slot tag for the transfer request. + * + * @return 0 on success, or a negative error code on failure. + */ +static int32_t ufshc_send_upiu_cmd(struct ufs_host_controller *ufshc, uint32_t slot_tag) +{ + int32_t err = 0; + uint32_t read_reg; + uint32_t events; + k_timeout_t timeout_event; + struct ufshc_xfer_req_desc *utrd_ptr; + struct ufshc_xfer_cmd_desc *ucd_ptr = ufshc->ucdl_base_addr; + struct ufshc_xfer_req_upiu *req_upiu = &ucd_ptr->req_upiu; + struct ufshc_xfer_resp_upiu *resp_upiu = &ucd_ptr->resp_upiu; + uint8_t req_trans_type = req_upiu->upiu_header.transaction_type; + + /* Check UTP Transfer Request List Run-Stop Register */ + read_reg = ufshc_read_reg(ufshc, UFSHC_UTRLRSR); + if ((read_reg & UFSHC_UTRL_RUN) != UFSHC_UTRL_RUN) { + err = -EIO; + goto out; + } + + /* Ensure cache coherence */ + if (ufshc->is_cache_coherent == 0U) { + /* Flush the request UPIU header */ + (void)sys_cache_data_flush_range((void *)&req_upiu->upiu_header, + sizeof(req_upiu->upiu_header)); + + /* Depending on the transaction type, flush additional data structures */ + if (req_trans_type == (uint8_t)UFSHC_NOP_UPIU_TRANS_CODE) { + (void)sys_cache_data_flush_range((void *)&req_upiu->nop_out_upiu, + sizeof(req_upiu->nop_out_upiu)); + } else if (req_trans_type == (uint8_t)UFSHC_CMD_UPIU_TRANS_CODE) { + (void)sys_cache_data_flush_range((void *)&req_upiu->cmd_upiu, + sizeof(req_upiu->cmd_upiu)); + (void)sys_cache_data_flush_range((void *)&ucd_ptr->prdt, + sizeof(ucd_ptr->prdt)); + } else if (req_trans_type == (uint8_t)UFSHC_QRY_UPIU_TRANS_CODE) { + (void)sys_cache_data_flush_range((void *)&req_upiu->query_req_upiu, + sizeof(req_upiu->query_req_upiu)); + } else { + LOG_ERR("ufs-send-upiu: invalid upiu request, transaction_type: %d", + req_trans_type); + err = -EINVAL; + goto out; + } + + /* Invalidate the response UPIU header */ + (void)sys_cache_data_invd_range((void *)&resp_upiu->upiu_header, + sizeof(resp_upiu->upiu_header)); + + /* Invalidate fields depending on the request transaction type */ + if (req_trans_type == (uint8_t)UFSHC_NOP_UPIU_TRANS_CODE) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->nop_in_upiu, + sizeof(resp_upiu->nop_in_upiu)); + } else if (req_trans_type == (uint8_t)UFSHC_CMD_UPIU_TRANS_CODE) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->resp_upiu, + sizeof(resp_upiu->resp_upiu)); + } else if (req_trans_type == (uint8_t)UFSHC_QRY_UPIU_TRANS_CODE) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->query_resp_upiu, + sizeof(resp_upiu->query_resp_upiu)); + } else { + /* do nothing.. code is not reached */ + } + + /* Invalidate the entire UCD (Command Descriptor) to ensure it is up-to-date */ + (void)sys_cache_data_invd_range((void *)ucd_ptr, sizeof(*(ucd_ptr))); + } + + /* Clear the outstanding transfer request event */ + (void)k_event_clear(&ufshc->irq_event, + (uint32_t)UFS_UPIU_COMPLETION_EVENT); + + /* Mark this slot as having an outstanding transfer request */ + sys_set_bit((mem_addr_t)((uintptr_t)&ufshc->outstanding_xfer_reqs), slot_tag); + + /* Start the transfer */ + ufshc_write_reg(ufshc, UFSHC_UTRLDBR, ((uint32_t)1U << slot_tag)); + + /* wait for completion of the transfer */ + timeout_event = K_USEC(UFS_TIMEOUT_US); + events = k_event_wait(&ufshc->irq_event, + (uint32_t)UFS_UPIU_COMPLETION_EVENT, + false, timeout_event); + if ((events & UFS_UPIU_COMPLETION_EVENT) == 0U) { + LOG_ERR("ufs-send-upiu: cmd request timedout, tag %d", slot_tag); + err = -ETIMEDOUT; + goto out; + } + + /* Get the transfer request descriptor for this slot */ + utrd_ptr = ((struct ufshc_xfer_req_desc *)ufshc->utrdl_base_addr) + slot_tag; + + /* Ensure cache coherence */ + if (ufshc->is_cache_coherent == 0U) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->upiu_header, + sizeof(resp_upiu->upiu_header)); + if (req_trans_type == (uint8_t)UFSHC_NOP_UPIU_TRANS_CODE) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->nop_in_upiu, + sizeof(resp_upiu->nop_in_upiu)); + } else if (req_trans_type == (uint8_t)UFSHC_CMD_UPIU_TRANS_CODE) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->resp_upiu, + sizeof(resp_upiu->resp_upiu)); + } else if (req_trans_type == (uint8_t)UFSHC_QRY_UPIU_TRANS_CODE) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->query_resp_upiu, + sizeof(resp_upiu->query_resp_upiu)); + } else { + /* do nothing.. code is not reached */ + } + + (void)sys_cache_data_invd_range((void *)utrd_ptr, sizeof(*(utrd_ptr))); + } + + /* Check if the transfer was successful by evaluating the status fields */ + if (utrd_ptr->dw2_ocs != 0U) { + LOG_ERR("OCS error from controller = %x for tag %d", utrd_ptr->dw2_ocs, slot_tag); + err = -EIO; + } else { + if (resp_upiu->upiu_header.response != 0U) { + LOG_ERR("ufs-send-upiu: unexpected response: %x", + resp_upiu->upiu_header.response); + err = -EIO; + } else if (resp_upiu->upiu_header.status != 0U) { + LOG_ERR("ufs-send-upiu: unexpected status:%x", + resp_upiu->upiu_header.status); + err = -EIO; + } else if (resp_upiu->upiu_header.task_tag != + req_upiu->upiu_header.task_tag) { + err = -EIO; + } else { + err = 0; /* No error, transfer successful */ + } + } + +out: + return err; +} + +/** + * @brief Send a flag query request to the UFS host controller. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param opcode The flag query operation to perform. + * @param idn The flag identifier (IDN) to access. + * @param index The flag index to access. + * @param flag_res Pointer to store the queried flag value (true or false). + * + * @return 0 on success, or a non-zero error code on failure. + */ +static int32_t ufshc_query_flag(struct ufs_host_controller *ufshc, + int32_t opcode, int32_t idn, uint8_t index, + bool *flag_res) +{ + int32_t err, selector = 0; + uint32_t tmp_be32_val; + uint32_t slot = 0U; + struct ufshc_query_upiu *query_resp_ptr; + + /* Find an available slot in the transfer request list (TRL) */ + ufshc_find_slot_in_trl(ufshc, &slot); + + /* Clear any previous command descriptor to prepare for new transfer. */ + (void)memset(ufshc->ucdl_base_addr, 0, sizeof(struct ufshc_xfer_cmd_desc)); + + /* Fill the UPIU with query details */ + ufshc_fill_flag_upiu(ufshc, slot, opcode, idn, index, (uint8_t)selector); + + /* Send the UPIU command and wait for the result */ + err = ufshc_send_upiu_cmd(ufshc, slot); + if ((err == 0) && (flag_res != NULL)) { + query_resp_ptr = &ufshc->ucdl_base_addr->resp_upiu.query_resp_upiu; + tmp_be32_val = sys_cpu_to_be32(query_resp_ptr->tsf.value); + if ((tmp_be32_val & 0x1U) != 0U) { + *flag_res = true; + } else { + *flag_res = false; + } + } + + if (err != 0) { + LOG_ERR("ufs-query-flag: Sending for idn %d failed, err = %d", idn, err); + } + + return err; +} + +/** + * @brief Send a descriptor query request to the UFS host controller. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param opcode The descriptor operation code. + * @param idn The descriptor identifier (IDN) to access. + * @param index The index field to use in the query. + * @param selector The selector field to use in the query. + * @param desc_buf Pointer to the buffer to store the retrieved descriptor data. + * @param desc_len Length of the descriptor data to be retrieved. + * + * @return 0 on success, or a non-zero error code on failure. + */ +static int32_t ufshc_query_descriptor(struct ufs_host_controller *ufshc, + int32_t opcode, int32_t idn, + uint8_t index, uint8_t selector, + uint8_t *desc_buf, int32_t desc_len) +{ + int32_t err; + uint32_t slot = 0U; + struct ufshc_xfer_cmd_desc *cmd_desc_ptr = ufshc->ucdl_base_addr; + + /* Find an available slot in the transfer request list (TRL) */ + ufshc_find_slot_in_trl(ufshc, &slot); + + /* Clear any previous command descriptor to prepare for new transfer. */ + (void)memset(ufshc->ucdl_base_addr, 0, sizeof(struct ufshc_xfer_cmd_desc)); + + /* Fill the UPIU with query details */ + ufshc_fill_desc_upiu(ufshc, slot, opcode, idn, index, selector, desc_len); + + /* Send the UPIU command and wait for the result */ + err = ufshc_send_upiu_cmd(ufshc, slot); + if ((err == 0) && (desc_buf != NULL) && + (opcode == (int32_t)UFSHC_QRY_READ_DESC_CMD)) { + (void)memcpy(desc_buf, + cmd_desc_ptr->resp_upiu.query_resp_upiu.data, + (size_t)desc_len); + } + + if (err != 0) { + LOG_ERR("ufs-query-desc: opcode 0x%02X for idn %d failed, index %d, err = %d", + opcode, idn, index, err); + } + + return err; +} + +/** + * @brief Send an attribute query request to the UFS host controller. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param opcode The attribute operation code. + * @param idn The attribute identifier (IDN) to access. + * @param index The index field to use in the query. + * @param selector The selector field to use in the query. + * @param attr_val Pointer to store the queried attribute value. + * + * @return 0 on success, or a non-zero error code on failure. + */ +static int32_t ufshc_query_attr(struct ufs_host_controller *ufshc, + int32_t opcode, int32_t idn, + uint8_t index, uint8_t selector, + uint32_t *attr_val) +{ + int32_t err; + uint32_t slot = 0U; + struct ufshc_query_upiu *query_resp_ptr; + + if (attr_val == NULL) { + return -EINVAL; + } + + /* Find an available slot in the transfer request list (TRL) */ + ufshc_find_slot_in_trl(ufshc, &slot); + + /* Clear any previous command descriptor to prepare for new transfer. */ + (void)memset(ufshc->ucdl_base_addr, 0, sizeof(struct ufshc_xfer_cmd_desc)); + + /* Fill the UPIU with query details */ + ufshc_fill_attr_upiu(ufshc, slot, opcode, idn, index, selector, *attr_val); + + /* Send the UPIU command and wait for the result */ + err = ufshc_send_upiu_cmd(ufshc, slot); + if ((err == 0) && (opcode == (int32_t)UFSHC_QRY_READ_ATTR_CMD)) { + query_resp_ptr = &ufshc->ucdl_base_addr->resp_upiu.query_resp_upiu; + *attr_val = sys_cpu_to_be32(query_resp_ptr->tsf.value); + } + + if (err != 0) { + LOG_ERR("ufs-query-attr: opcode 0x%02X for idn %d failed, index %d, err = %d", + opcode, idn, index, err); + } + + return err; +} + +/** + * @brief Fill UIC command structure + * + * This function fills the provided UIC command structure with the values + * for the command, MIB attribute, generation selection, attribute set type, + * and MIB value, based on the function arguments. + * + * @param uic_cmd_ptr Pointer to the UIC command structure to be filled + * @param mib_attr_gen_sel MIB attribute and generation selection value + * (upper 16-bits for attribute, lower 16-bits for selection) + * @param attr_set_type Attribute set type for the UIC command + * @param mib_val MIB value for the UIC command + * @param cmd UIC command value + */ +void ufshc_fill_uic_cmd(struct ufshc_uic_cmd *uic_cmd_ptr, + uint32_t mib_attr_gen_sel, uint32_t mib_val, + uint32_t attr_set_type, uint32_t cmd) +{ + uic_cmd_ptr->command = (uint8_t)cmd; + uic_cmd_ptr->mib_attribute = (uint16_t)(mib_attr_gen_sel >> 16U); + uic_cmd_ptr->gen_sel_index = (uint16_t)mib_attr_gen_sel; + uic_cmd_ptr->attr_set_type = (uint8_t)attr_set_type; + uic_cmd_ptr->mib_value = mib_val; +} + +/** + * @brief Check if the UFS host controller is ready to accept UIC commands + * + * This function checks the readiness of the UFS host controller to accept UIC + * commands by inspecting the UCRDY (UFS Controller Ready) bit in the HCS register. + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return true if the controller is ready to accept UIC commands, false otherwise. + */ +static bool ufshc_ready_for_uic_cmd(struct ufs_host_controller *ufshc) +{ + if ((ufshc_read_reg(ufshc, UFSHC_HCS) & UFSHC_HCS_UCRDY_MASK) != 0U) { + return true; + } else { + return false; + } +} + +/** + * @brief Send a UIC command to the UFS host controller + * + * This function sends a UIC (UFS Interface command) to the UFS host controller + * and waits for its completion. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param uic_cmd Pointer to the UIC command structure to be sent. + * + * @return 0 on success, negative error code on failure + */ +int ufshc_send_uic_cmd(struct ufs_host_controller *ufshc, struct ufshc_uic_cmd *uic_cmd) +{ + int ret; + uint32_t arg1; + uint32_t events; + k_timeout_t timeout_event; + + /* Check if the UFS Host Controller is ready to accept a UIC command. */ + if (!ufshc_ready_for_uic_cmd(ufshc)) { + LOG_ERR("Controller is not ready to accept UIC commands"); + ret = -EIO; + goto out; + } + + /* Clear any previous UIC command completion event. */ + (void)k_event_clear(&ufshc->irq_event, + (uint32_t)UFS_UIC_CMD_COMPLETION_EVENT); + + /* Clear the UIC command completion status bit to prepare for the new command */ + ufshc_write_reg(ufshc, UFSHC_IS, UFSHC_IS_UCCS_MASK); + + /* Prepare the UIC command arguments */ + arg1 = (((uint32_t)uic_cmd->mib_attribute << 16U) | + (uint32_t)uic_cmd->gen_sel_index); + ufshc_write_reg(ufshc, UFSHC_UCMDARG1, arg1); + ufshc_write_reg(ufshc, UFSHC_UCMDARG2, + ((uint32_t)uic_cmd->attr_set_type << 16U)); + if (uic_cmd->command == UFSHC_DME_SET_OPCODE) { + ufshc_write_reg(ufshc, UFSHC_UCMDARG3, uic_cmd->mib_value); + } + + /* Write the UIC command opcode to the command register. */ + ufshc_write_reg(ufshc, UFSHC_UICCMD, uic_cmd->command); + + /* Wait for the UIC command completion event */ + timeout_event = K_USEC(UFS_TIMEOUT_US); + events = k_event_wait(&ufshc->irq_event, + (uint32_t)UFS_UIC_CMD_COMPLETION_EVENT, + false, timeout_event); + if ((events & UFS_UIC_CMD_COMPLETION_EVENT) == 0U) { + LOG_ERR("uic cmd 0x%x completion timeout", uic_cmd->command); + ret = -ETIMEDOUT; + goto out; + } + + /* Retrieve the result code from the UFS Host Controller status register. */ + uic_cmd->result_code = (uint8_t)(ufshc_read_reg(ufshc, UFSHC_UCMDARG2) & + UFSHC_UCMDARG2_RESCODE_MASK); + if (uic_cmd->result_code != 0U) { + ret = -EIO; + } else { + if (uic_cmd->command == (uint32_t)UFSHC_DME_GET_OPCODE) { + /* Retrieve the MIB value from the response register */ + uic_cmd->mib_value = ufshc_read_reg(ufshc, UFSHC_UCMDARG3); + } + ret = 0; + } + +out: + return ret; +} + +/** + * @brief Change UFS TX/RX Mphy attributes + * + * @param ufshc: Pointer to the UFS host controller instance. + * @param speed_gear The desired speed gear value for the configuration. + * @param rx_term_cap The RX termination capability to be set. + * @param tx_term_cap The TX termination capability to be set. + * + * @return 0 on success, negative error code on failure + */ +static int32_t ufshc_configure_TxRxAttributes(struct ufs_host_controller *ufshc, + uint32_t speed_gear, uint32_t rx_term_cap, uint32_t tx_term_cap) +{ + struct ufshc_uic_cmd uic_cmd = {0}; + int32_t ret; + uint32_t tx_gear, rx_gear; + uint32_t tx_lanes, rx_lanes; + uint32_t power_mode, rate; + + /* Extracting speed gear components */ + tx_gear = (uint8_t)speed_gear; + rx_gear = (uint8_t)speed_gear; + power_mode = (uint8_t)(speed_gear >> 8U); + rate = (uint8_t)(speed_gear >> 16U); + + /* Configure TX Gear */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_TXGEAR << 16U), + tx_gear, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Configure RX Gear */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_RXGEAR << 16U), + rx_gear, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Configure TX Termination capability */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_TXTERMINATION << 16U), + tx_term_cap, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Configure RX Termination capability */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_RXTERMINATION << 16U), + rx_term_cap, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Check for high-speed configuration (Fast Mode) */ + if (power_mode == UFSHC_TX_RX_FAST) { + /* Configure High-Speed Series based on rate */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_HSSERIES << 16U), + rate, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Program initial ADAPT for Gear 4 */ + if (tx_gear == UFSHC_GEAR4) { + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_TXHSADAPTTYPE << 16U), + 1U, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + } + } + + /* Retrieve and configure the active TX data lanes */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_CONNECTEDTXDATALANES << 16U), + 0U, 0U, + UFSHC_DME_GET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + tx_lanes = uic_cmd.mib_value; + + /* Retrieve and configure the active RX data lanes */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_CONNECTEDRXDATALANES << 16U), + 0U, 0U, + UFSHC_DME_GET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + rx_lanes = uic_cmd.mib_value; + + /* Configure the active TX data lanes */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_ACTIVETXDATALANES << 16U), + tx_lanes, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Configure the active RX data lanes */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_ACTIVERXDATALANES << 16U), + rx_lanes, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Set the power mode */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_PWRMODE << 16U), + power_mode, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + +out: + return ret; +} + +/** + * @brief Configure the UFS speed gear setting + * + * This function sets the UFS host controller's speed gear by configuring the tx and rx + * attributes (such as termination capabilities) + * + * @param ufshc Pointer to the UFS host controller structure + * @param speed_gear Desired speed gear setting + * + * @return 0 on success, negative error code on failure + */ +int ufshc_configure_speedgear(struct ufs_host_controller *ufshc, uint32_t speed_gear) +{ + int ret = 0; + uint32_t tx_term_cap; + uint32_t rx_term_cap; + uint32_t time_out; + uint32_t event_status; + uint32_t events; + k_timeout_t timeout_event; + + /* Validate the requested speed gear */ + if ((speed_gear != UFSHC_PWM_G1) && (speed_gear != UFSHC_PWM_G2) && + (speed_gear != UFSHC_PWM_G3) && (speed_gear != UFSHC_PWM_G4) && + (speed_gear != UFSHC_HS_G1) && (speed_gear != UFSHC_HS_G2) && + (speed_gear != UFSHC_HS_G3) && (speed_gear != UFSHC_HS_G4) && + (speed_gear != UFSHC_HS_G1_B) && (speed_gear != UFSHC_HS_G2_B) && + (speed_gear != UFSHC_HS_G3_B) && (speed_gear != UFSHC_HS_G4_B)) { + ret = -EINVAL; + goto out; + } + + /* Set termination capabilities based on speed gear type */ + if ((speed_gear == UFSHC_PWM_G1) || (speed_gear == UFSHC_PWM_G2) || + (speed_gear == UFSHC_PWM_G3) || (speed_gear == UFSHC_PWM_G4)) { + /* Pulse Width Modulation (PWM) gears require termination */ + tx_term_cap = 1U; + rx_term_cap = 1U; + } else { + /* High-Speed gears do not require termination */ + tx_term_cap = 0U; + rx_term_cap = 0U; + } + + /* Clear previous UIC power event flag */ + (void)k_event_clear(&ufshc->irq_event, (uint32_t)UFS_UIC_PWR_COMPLETION_EVENT); + + /* Configure the tx and rx attributes for the selected speed gear */ + ret = ufshc_configure_TxRxAttributes(ufshc, speed_gear, + rx_term_cap, tx_term_cap); + if (ret != 0) { + goto out; + } + + /* Wait for UIC power completion event */ + timeout_event = K_USEC(UFS_TIMEOUT_US); + events = k_event_wait(&ufshc->irq_event, + (uint32_t)UFS_UIC_PWR_COMPLETION_EVENT, + false, timeout_event); + if ((events & UFS_UIC_PWR_COMPLETION_EVENT) == 0U) { + ret = -ETIMEDOUT; + goto out; + } + + /* Check power mode status */ + time_out = UFS_TIMEOUT_US; + ret = -EIO; + while (time_out > 0U) { + event_status = ufshc_read_reg(ufshc, UFSHC_HCS) & UFSHC_HCS_UPMCRS_MASK; + if (event_status == UFSHC_PWR_MODE_VAL) { + /* Desired power mode, configuration successful */ + ret = 0; + break; + } + time_out--; + (void)k_usleep(1); + } + +out: + return ret; +} + +/** + * @brief Read the capabilities of the UFS host controller. + * + * This function reads the controller's capabilities register and extracts the + * maximum number of transfer request slots supported by the UFS host controller. + * + * @param ufshc Pointer to the UFS host controller instance. + */ +static inline void ufshc_host_capabilities(struct ufs_host_controller *ufshc) +{ + uint32_t capabilities = ufshc_read_reg(ufshc, UFSHC_HOST_CTRL_CAP); + + ufshc->xfer_req_depth = (capabilities & (uint32_t)UFSHC_TRANSFER_REQ_SLOT_MASK) + 1U; +} + +/** + * @brief Allocate memory for UFS host controller data structures. + * + * This function allocates the required DMA memory for two key data structures: + * 1. The Command Descriptor array (aligned to 128-byte boundaries). + * 2. The UTP Transfer Request Descriptor List (UTRDL) array (aligned to 1k-byte + * boundaries). + * + * If memory has already been allocated for these structures, the function + * avoids redundant allocations. + * + * @param ufshc Pointer to the UFS host controller instance. + * + * @return 0 on success, or a negative error code (e.g., -ENOMEM) on failure + */ +static int32_t ufshc_host_memory_alloc(struct ufs_host_controller *ufshc) +{ + size_t utrd_size, ucd_size; + + /* Allocate 1 memory to 1 Command Descriptor + * Should be aligned to 128 boundary. + */ + ucd_size = (sizeof(struct ufshc_xfer_cmd_desc)); + if (ufshc->ucdl_base_addr == NULL) { + /* don't allocate memory if already allocated - earlier */ + ufshc->ucdl_base_addr = k_aligned_alloc(128, ucd_size); + if (ufshc->ucdl_base_addr == NULL) { + LOG_ERR("Command descriptor memory allocation failed"); + return -ENOMEM; + } + } + (void)memset(ufshc->ucdl_base_addr, 0x0, ucd_size); + + /* Allocate memory to Transfer Request Descriptors based on xfer req depth + * Should be aligned to 1k boundary. + */ + utrd_size = (sizeof(struct ufshc_xfer_req_desc)) * (ufshc->xfer_req_depth); + if (ufshc->utrdl_base_addr == NULL) { + /* don't allocate memory if already allocated - earlier */ + ufshc->utrdl_base_addr = k_aligned_alloc(1024, utrd_size); + if (ufshc->utrdl_base_addr == NULL) { + LOG_ERR("Transfer Descriptor memory allocation failed"); + return -ENOMEM; + } + } + (void)memset(ufshc->utrdl_base_addr, 0x0, utrd_size); + + return 0; +} + +/** + * @brief Handle the completion of SCSI and query commands. + * + * This function processes the completion of SCSI and query commands initiated + * by the UFS host controller. It checks the doorbell register for completed + * requests. Posts an interrupt event indicating the completion of UFS UPIU operations. + * + * @param ufshc The UFS host controller instance. + */ +static void ufshc_transfer_req_compl(struct ufs_host_controller *ufshc) +{ + uint32_t tr_doorbell; + uint32_t completed_reqs; + uint32_t task_tag; + + /* Read the transfer doorbell register to get completed requests status */ + tr_doorbell = ufshc_read_reg(ufshc, UFSHC_UTRLDBR); + + /* Identify which transfer requests have been completed */ + completed_reqs = (~tr_doorbell) & (ufshc->outstanding_xfer_reqs); + + /* Clear the completed requests from the outstanding transfer requests */ + ufshc->outstanding_xfer_reqs &= ~completed_reqs; + + if (completed_reqs != 0U) { + for (task_tag = 0; + task_tag < (uint32_t)(ufshc->xfer_req_depth); + task_tag++) { + (void)sys_test_and_clear_bit((mem_addr_t)((uintptr_t)&completed_reqs), + task_tag); + } + + /* Post an event to indicate that a UPIU completion has occurred */ + (void)k_event_post(&ufshc->irq_event, + (uint32_t)UFS_UPIU_COMPLETION_EVENT); + } +} + +/** + * @brief Handle completion of UIC command interrupts. + * + * This function processes the completion of UIC commands based on the provided + * interrupt status. It handles the UIC command completion and power + * status change events. + * + * @param ufshc The UFS host controller instance. + * @param intr_status The interrupt status generated by the UFS host controller. + */ +static void ufshc_uic_cmd_compl(struct ufs_host_controller *ufshc, uint32_t intr_status) +{ + /* Check if the UIC command status interrupt is set */ + if ((intr_status & UFSHC_IS_UCCS_MASK) != 0U) { + /* Post an event indicating UIC command completion */ + (void)k_event_post(&ufshc->irq_event, + (uint32_t)UFS_UIC_CMD_COMPLETION_EVENT); + } + + /* Check if the power status interrupt is set */ + if ((intr_status & UFSHC_IS_PWR_STS_MASK) != 0U) { + /* Post an event indicating power status change */ + (void)k_event_post(&ufshc->irq_event, + (uint32_t)UFS_UIC_PWR_COMPLETION_EVENT); + } +} + +/** + * @brief Main interrupt service routine for UFS host controller. + * + * This function serves as the main interrupt service routine (ISR) for the UFS host + * controller. UIC and UPIU-related interrupts are handled separately. + * + * @param: Pointer to the UFS host controller instance. + */ +static void ufshc_main_isr(const void *param) +{ + uint32_t intr_status, enabled_intr_status = 0; + struct ufs_host_controller *ufshc = (struct ufs_host_controller *)param; + + /* Read the interrupt status register to get the current interrupt */ + intr_status = ufshc_read_reg(ufshc, UFSHC_IS); + + /* Read the enabled interrupts register and mask it with the current */ + enabled_intr_status = intr_status & ufshc_read_reg(ufshc, UFSHC_IE); + + /* Clear the interrupt status register to acknowledge the interrupts */ + ufshc_write_reg(ufshc, UFSHC_IS, intr_status); + + /* Handle UIC interrupt */ + if ((enabled_intr_status & UFSHCD_UIC_MASK) != 0U) { + ufshc_uic_cmd_compl(ufshc, enabled_intr_status); + } + + /* Handle UPIU interrupt */ + if ((enabled_intr_status & UFSHC_IS_UTRCS_MASK) != 0U) { + ufshc_transfer_req_compl(ufshc); + } +} + +/** + * @brief Enable Host Controller (HCE) + * + * This function enables the Host Controller Enable (HCE) bit, + * and waits for it to be acknowledged by the hardware. + * + * @param ufshc Pointer to the ufs-host-controller instance. + * + * @return 0 on success, or a negative error code on failure. + */ +static int32_t ufshc_set_hce(struct ufs_host_controller *ufshc) +{ + int32_t ret = 0; + uint32_t time_out; + uint32_t read_reg; + + /* Write to the register to enable HCE (Host Controller Enable) */ + ufshc_write_reg(ufshc, UFSHC_HCE, UFSHC_HCE_MASK); + + /* Wait for the HCE bit to be set */ + time_out = UFS_TIMEOUT_US; + do { + read_reg = ufshc_read_reg(ufshc, UFSHC_HCE); + if ((read_reg & UFSHC_HCE_MASK) == UFSHC_HCE_MASK) { + /* Success: HCE bit is set */ + break; + } + time_out = time_out - 1U; + (void)k_usleep(1); + } while (time_out != 0U); + + if (time_out == 0U) { + ret = -ETIMEDOUT; + } + + return ret; +} + +/** + * @brief Enable specific interrupts in the UFS host controller + * + * @param ufshc Pointer to the ufs-host-controller instance. + * @param intrs Interrupt bits to be enabled. + */ +static void ufshc_enable_intr(struct ufs_host_controller *ufshc, uint32_t intrs) +{ + uint32_t set = ufshc_read_reg(ufshc, UFSHC_IE); + + set |= intrs; + ufshc_write_reg(ufshc, UFSHC_IE, set); +} + +/** + * @brief Enable the UFS host controller + * + * This function performs the full sequence to enable the UFS host controller: + * 1. Enables the Host Controller Enable (HCE) bit. + * 2. Waits for the card connection status to indicate that the UFS device is ready. + * 3. Enables UIC-related interrupts. + * + * @param ufshc Pointer to the ufs-host-controller instance. + * + * @return 0 on success, or a negative error code on failure. + */ +static int32_t ufshc_host_enable(struct ufs_host_controller *ufshc) +{ + int32_t ret = 0; + uint32_t time_out; + uint32_t read_reg; + + /* start controller initialization sequence by enabling the HCE */ + ret = ufshc_set_hce(ufshc); + if (ret != 0) { + goto out; + } + + /* Check for Card Connection Status */ + time_out = UFS_TIMEOUT_US; + do { + read_reg = ufshc_read_reg(ufshc, UFSHC_HCS); + if ((read_reg & UFSHC_HCS_CCS_MASK) == 0U) { + /* Success: card is properly connected */ + break; + } + time_out = time_out - 1U; + (void)k_usleep(1); + } while (time_out != 0U); + + if (time_out == 0U) { + ret = -ETIMEDOUT; + } + + /* Enable UIC related interrupts after successful initialization */ + ufshc_enable_intr(ufshc, UFSHCD_UIC_MASK); + +out: + return ret; +} + +/** + * @brief Notify Unipro to perform link startup + * + * This function issues the DME_LINKSTARTUP command to the Unipro layer, + * which triggers the initialization of the Unipro link startup procedure. + * Once the Unipro link is successfully initialized, the device connected to + * the host controller will be detected. + * + * This function attempts to issue the DME_LINKSTARTUP command, and retries + * if necessary. If the link startup process times out, it returns a timeout error. + * + * @param ufshc ufs-host-controller instance + * + * @return 0 on success, non-zero value on failure. + */ +static int32_t ufshc_dme_link_startup(struct ufs_host_controller *ufshc) +{ + struct ufshc_uic_cmd uic_cmd = {0}; + int32_t ret; + uint32_t time_out; + uint32_t time_out_ulss; + uint32_t read_reg; + + /* send DME_LINKSTARTUP command */ + ufshc_fill_uic_cmd(&uic_cmd, 0U, 0U, 0U, UFSHC_DME_LINKSTARTUP_OPCODE); + + time_out = 100U; /* 100 micro seconds */ + do { + time_out = time_out - 1U; + (void)k_usleep(1); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + + /* Clear UIC error which trigger during LINKSTARTUP command */ + ufshc_write_reg(ufshc, UFSHC_IS, UFSHC_IS_UE_MASK); + if (ret == 0) { + /* Success: LINKSTARTUP command was sent successfully */ + break; + } + + /* If the command failed, check if the ULSS (UFS Link Startup) bit is set */ + time_out_ulss = UFS_TIMEOUT_US; + do { + read_reg = ufshc_read_reg(ufshc, UFSHC_IS); + if ((read_reg & UFSHC_IS_ULSS_MASK) == UFSHC_IS_ULSS_MASK) { + /* Success: ULSS bit set, send link startup again */ + break; + } + time_out_ulss = time_out_ulss - 1U; + (void)k_usleep(1); + } while (time_out_ulss != 0U); + + if (time_out_ulss == 0U) { + /* Timeout occurred while waiting for ULSS bit to be set */ + ret = -ETIMEDOUT; + break; + } + + /* prepare for next iteration */ + ufshc_write_reg(ufshc, UFSHC_IS, UFSHC_IS_ULSS_MASK); + } while (time_out != 0U); + + if (ret != 0) { + LOG_ERR("dme-link-startup: error code %d", ret); + } + + if (time_out == 0U) { + ret = -ETIMEDOUT; + } + + return ret; +} + +/** + * @brief Check if any device is connected to the host controller + * + * This function checks if a device is connected to the host controller by + * inspecting the device presence (DP) flag in the UFS Host Controller status register. + * It will time out if no device is detected within the specified timeout period. + * + * @param ufshc ufs-host-controller instance + * + * @return true if a device is present, false if no device is detected or after a timeout. + */ +static inline bool ufshc_is_device_present(struct ufs_host_controller *ufshc) +{ + uint32_t time_out; + uint32_t read_reg; + bool dp_flag = false; + + time_out = UFS_TIMEOUT_US; /* One Second */ + do { + read_reg = ufshc_read_reg(ufshc, UFSHC_HCS); + if ((read_reg & UFSHC_HCS_DP_MASK) != 0U) { + dp_flag = true; + break; + } + time_out = time_out - 1U; + (void)k_usleep(1); + } while (time_out != 0U); + + return dp_flag; +} + +/** + * @brief Initialize the Unipro link startup procedure + * + * This function performs the link startup procedure for the UFS host controller. + * It involves the following steps: + * - Perform PHY initialization. + * - Issue the DME_LINKSTARTUP command to Unipro. + * - Verify if the device is present. + * - Retry the link startup in case of failure. + * + * The function retries the link startup procedure if the device is not detected + * or if an error occurs. The maximum number of retries is controlled by the + * UFSHC_DME_LINKSTARTUP_RETRIES constant. + * + * @param ufshc ufs-host-controller instance + * + * @return 0 on success, non-zero value on failure. + */ +static int32_t ufshc_link_startup(struct ufs_host_controller *ufshc) +{ + int32_t ret; + int32_t retries = UFSHC_DME_LINKSTARTUP_RETRIES; + + /* perform phy initialization */ + ret = ufshc_variant_phy_initialization(ufshc->dev); + if (ret != 0) { + LOG_ERR("Phy setup failed (%d)", ret); + goto out; + } + + do { + /* send DME link start up */ + ret = ufshc_dme_link_startup(ufshc); + + /* check if device is detected by inter-connect layer */ + if (ret == 0) { + if (ufshc_is_device_present(ufshc) == false) { + LOG_ERR("ufs-link-startup: Device is not present"); + ret = -ENXIO; + goto out; + } + } else { + /* + * DME link lost indication is only received when link is up, + * but we can't be sure if the link is up until link startup + * succeeds. So reset the local Uni-Pro and try again. + */ + if (ufshc_host_enable(ufshc) != 0) { + goto out; + } + } + + retries--; + } while ((ret != 0) && (retries != 0)); + + if (ret != 0) { + goto out; + } + + /* Include any host controller configuration via UIC commands */ + ret = ufshc_variant_link_startup_notify(ufshc->dev, (uint8_t)POST_CHANGE); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +/** + * @brief Check the status of UCRDY, UTRLRDY bits. + * + * This function checks if the UCRDY (UFS Ready), UTRLRDY (UFS Transport Layer Ready) + * bits are set in the provided host controller status register. + * + * @param reg Register value of host controller status (HCS) + * + * @return 0 if the status bits are set correctly (ready) else a positive value (1). + */ +static inline int32_t ufshc_get_lists_status(uint32_t reg) +{ + if ((reg & UFSHCD_STATUS_READY) == UFSHCD_STATUS_READY) { + return 0; + } else { + return 1; + } +} + +/** + * @brief Enable the run-stop register to allow host controller operation + * + * This function enables the run-stop register (UTRLRSR) by writing a value that + * allows the host controller to process UFS requests. + * + * @param ufshc Pointer to the UFS host controller instance + */ +static void ufshc_enable_run_stop_reg(struct ufs_host_controller *ufshc) +{ + ufshc_write_reg(ufshc, UFSHC_UTRLRSR, UFSHC_UTRL_RUN); +} + +/** + * @brief Initialize and make UFS controller operational + * + * This function brings the UFS host controller into an operational state by performing + * the following actions: + * 1. Enabling necessary interrupts. + * 2. Programming the UTRL base address for the transport layer. + * 3. Configuring the run-stop register to allow operations. + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return 0 on success; a negative error code on failure. + */ +static int32_t ufshc_make_host_operational(struct ufs_host_controller *ufshc) +{ + int32_t err = 0; + uint32_t reg; + + /* Enable required interrupts */ + ufshc_enable_intr(ufshc, UFSHCD_ENABLE_INTRS); + + /* Configure UTRL base address registers */ + ufshc_write_reg(ufshc, UFSHC_UTRLBA, + (uint32_t)((uintptr_t)ufshc->utrdl_base_addr)); + ufshc_write_reg(ufshc, UFSHC_UTRLBAU, + (uint32_t)(((uint64_t)((uintptr_t)ufshc->utrdl_base_addr)) >> 32U)); + + /* + * UCRDY and UTRLRDY bits must be 1 + */ + reg = ufshc_read_reg(ufshc, UFSHC_HCS); + if ((ufshc_get_lists_status(reg)) == 0) { + ufshc_enable_run_stop_reg(ufshc); + } else { + LOG_ERR("Host controller not ready to process requests"); + err = -EIO; + goto out; + } + +out: + return err; +} + +/** + * @brief Initializes the UFS Host Controller. + * + * This function performs the necessary steps to initialize the UFS host controller. + * + * @param ufshc Pointer to the UFS Host Controller instance. + * + * @return 0 on success, negative value on failure. + */ +static int32_t ufshc_host_initialize(struct ufs_host_controller *ufshc) +{ + int32_t err; + + /* Read capabilities registers */ + ufshc_host_capabilities(ufshc); + + /* Allocate memory for host memory space */ + err = ufshc_host_memory_alloc(ufshc); + if (err != 0) { + LOG_ERR("Memory allocation failed"); + goto out; + } + + /* + * In order to avoid any spurious interrupt immediately after + * registering UFS controller interrupt handler, clear any pending UFS + * interrupt status and disable all the UFS interrupts. + */ + ufshc_write_reg(ufshc, UFSHC_IS, ufshc_read_reg(ufshc, UFSHC_IS)); + ufshc_write_reg(ufshc, UFSHC_IE, 0); + + /* + * Make sure that UFS interrupts are disabled and any pending interrupt + * status is cleared before registering UFS interrupt handler. + */ + if ((uint32_t)irq_connect_dynamic(ufshc->irq, 0, ufshc_main_isr, + (const void *)ufshc, 0) != (ufshc->irq)) { + LOG_ERR("request irq failed"); + err = -ENOTSUP; + goto out; + } else { + irq_enable(ufshc->irq); + } + + /* Set HCE, card connection status, enable uic interrupts */ + err = ufshc_host_enable(ufshc); + if (err != 0) { + LOG_ERR("Host controller enable failed"); + goto out; + } + + /* initialize unipro-phy, link start up */ + err = ufshc_link_startup(ufshc); + if (err != 0) { + goto out; + } + + /* make host operational - enable transfer request */ + err = ufshc_make_host_operational(ufshc); + if (err != 0) { + goto out; + } + +out: + return err; +} + +/** + * @brief Initialize UFS device and check transport layer readiness + * + * This function sends a NOP OUT UPIU to the UFS device and waits for the corresponding + * NOP IN response. It checks if the UTP (Universal Transport Protocol) layer is + * ready after initialization by polling the device's fDeviceInit flag. + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return 0 on success, negative value on failure. + */ +static int32_t ufshc_card_initialize(struct ufs_host_controller *ufshc) +{ + int32_t err; + bool flag_res = true; + uint32_t timeout, slot = 0U; + + /* Send NOP OUT UPIU */ + /* find slot for transfer request */ + ufshc_find_slot_in_trl(ufshc, &slot); + ufshc_fill_nop_upiu(ufshc, slot); + err = ufshc_send_upiu_cmd(ufshc, slot); + if (err != 0) { + LOG_ERR("ufs-card-init: NOP OUT failed %d", err); + goto out; + } + + /* Write fdeviceinit flag */ + err = ufshc_query_flag(ufshc, (int32_t)UFSHC_QRY_SET_FLAG_CMD, + (int32_t)UFSHC_FDEVINIT_FLAG_IDN, 0, NULL); + if (err != 0) { + LOG_ERR("ufs-card-init: setting fDeviceInit flag failed with error %d", err); + goto out; + } + + /* Poll fDeviceInit flag to be cleared */ + timeout = UFS_TIMEOUT_US; /* One Second */ + do { + err = ufshc_query_flag(ufshc, + (int32_t)UFSHC_QRY_READ_FLAG_CMD, + (int32_t)UFSHC_FDEVINIT_FLAG_IDN, 0, + &flag_res); + if (flag_res == false) { + break; + } + timeout = timeout - 1U; + (void)k_usleep(1); + } while (timeout != 0U); + + if ((err == 0) && (flag_res != false)) { + LOG_ERR("ufs-card-init: fDeviceInit was not cleared by the device"); + err = -EBUSY; + } + +out: + return err; +} + +/** + * @brief Reads UFS geometry descriptor + * + * This function reads the UFS Geometry descriptor from the device to determine + * the maximum number of logical units (LUNs) supported by the UFS device. + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t ufshc_read_device_geo_desc(struct ufs_host_controller *ufshc) +{ + int32_t err; + uint8_t desc_buf[UFSHC_QRY_DESC_MAX_SIZE] = {0}; + struct ufs_dev_info *dev_info = &ufshc->dev_info; + + err = ufshc_query_descriptor(ufshc, (int32_t)UFSHC_QRY_READ_DESC_CMD, + (int32_t)UFSHC_GEOMETRY_DESC_IDN, + 0, 0, desc_buf, + (int32_t)UFSHC_QRY_DESC_MAX_SIZE); + if (err != 0) { + LOG_ERR("ufs-read-desc: Failed reading Geometry Desc. err = %d", err); + goto out; + } + + if (desc_buf[UFSHC_GEO_DESC_PARAM_MAX_NUM_LUN] == 1U) { + dev_info->max_lu_supported = 32U; + } else { + dev_info->max_lu_supported = 8U; + } + +out: + return err; +} + +/** + * @brief Initializes UFS device information by reading descriptors + * + * This function reads the UFS Geometry descriptor and updates the device's + * information structure to initialize the UFS device parameters, such as + * the maximum number of supported LUNs. + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t ufshc_read_device_info(struct ufs_host_controller *ufshc) +{ + int32_t ret; + + /* Init UFS geometry descriptor related parameters */ + ret = ufshc_read_device_geo_desc(ufshc); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +/** + * @brief Retrieves information about UFS logical units (LUNs) + * + * This function queries the UFS Unit descriptor for each supported LUN to gather + * detailed information such as LUN ID, block count, block size, and whether the LUN + * is enabled. + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t ufshc_get_lun_info(struct ufs_host_controller *ufshc) +{ + int32_t ret = -1; + uint8_t desc_buf[UFSHC_QRY_DESC_MAX_SIZE] = {0}; + uint8_t Index, lun_id; + uint8_t lun_enable; + uint32_t TwoPwrblksize; + uint64_t tmpval; + + struct ufs_dev_info *dev_info = &ufshc->dev_info; + + for (Index = 0U; Index < (dev_info->max_lu_supported); Index++) { + + /* Read the Unit Descriptor for Lun Index */ + ret = ufshc_query_descriptor(ufshc, + (int32_t)UFSHC_QRY_READ_DESC_CMD, + (int32_t)UFSHC_UNIT_DESC_IDN, + Index, 0, desc_buf, + (int32_t)UFSHC_QRY_DESC_MAX_SIZE); + if (ret != 0) { + goto out; + } + + /* Check for LU Enable */ + lun_enable = (desc_buf[UFSHC_UD_PARAM_LU_ENABLE]); + + /* if found enabled -- then compute other fields */ + if (lun_enable == 1U) { + dev_info->lun[Index].lun_enabled = true; + + lun_id = (desc_buf[UFSHC_UD_PARAM_UNIT_INDEX]); + dev_info->lun[lun_id].lun_id = lun_id; + + /* compute 2^blocksize */ + TwoPwrblksize = (desc_buf[UFSHC_UD_PARAM_LOGICAL_BLKSZ]); + tmpval = 1; + for (uint32_t Idx = 0; Idx < TwoPwrblksize; Idx++) { + tmpval *= 2U; + } + dev_info->lun[lun_id].block_size = (uint32_t)tmpval; + + /* get block count */ + tmpval = sys_get_be64(&desc_buf[UFSHC_UD_PARAM_LOGICAL_BLKCNT]); + dev_info->lun[lun_id].block_count = tmpval; + } else { + dev_info->lun[Index].lun_enabled = false; + } + } + +out: + return ret; +} + +/** + * @brief Main entry point for executing SCSI requests. + * + * This function is called by the SCSI midlayer to handle SCSI requests. + * It finds a free slot for the transfer request, prepares the command, + * and sends it to the UFS Host Controller. + * + * @param sdev Pointer to the SCSI device. + * @param cmd Pointer to the SCSI command structure. + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t ufs_scsi_exec(struct scsi_device *sdev, struct scsi_cmd *pccb) +{ + int32_t err; + uint32_t slot = 0U; + struct scsi_host_info *shost = sdev->host; + struct device *ufs_dev = shost->parent; + struct ufs_host_controller *ufshc = ufs_dev->data; + + /* find slot for transfer request */ + ufshc_find_slot_in_trl(ufshc, &slot); + ufshc_fill_scsi_cmd_upiu(ufshc, slot, pccb); + err = ufshc_send_upiu_cmd(ufshc, slot); + + if (err != 0) { + err = -EINVAL; + } + + return err; +} + +static const struct scsi_ops ufs_ops = { + .exec = ufs_scsi_exec, +}; + +/** + * @brief Allocates a UFS SCSI Host Controller instance. + * + * This function allocates a new SCSI host instance for the given UFS + * Host Controller if not already allocated. It initializes the host + * structure and associates it with the UFS controller. + * + * @param ufshc Pointer to the UFS Host Controller structure. + * + * @return 0 on success, -ENOMEM if memory allocation fails. + */ +static int32_t ufshc_alloc_scsi_host(struct ufs_host_controller *ufshc) +{ + struct scsi_host_info *host; + int32_t err = 0; + + if (ufshc->host == NULL) { + /* allocate if not done earlier */ + host = scsi_host_alloc(&ufs_ops); + if (host == NULL) { + LOG_ERR("scsi-add-host: failed"); + err = -ENOMEM; + goto out; + } + ufshc->host = host; + host->parent = ufshc->dev; + host->hostdata = (void *)ufshc; + } + +out: + return err; +} + +/** + * @brief Probes and adds UFS logical units (LUs). + * + * This function probes the UFS device for supported logical units + * and adds them to the SCSI subsystem if they are enabled. + * + * @param ufshc Pointer to the UFS Host Controller structure. + * + * @return 0. + */ +static int32_t ufshc_add_lus(struct ufs_host_controller *ufshc) +{ + struct ufs_dev_info *dev_info = &ufshc->dev_info; + + for (uint32_t i = 0; i < dev_info->max_lu_supported; i++) { + if (dev_info->lun[i].lun_enabled == true) { + (void)scsi_add_lun_host(ufshc->host, &dev_info->lun[i]); + } + } + + return 0; +} + +/** + * @brief Adds a SCSI link for the UFS controller. + * + * This function binds the UFS device to the SCSI subsystem. It allocates + * the SCSI host instance and probes for logical units to be added + * to the host. + * + * @param ufs_dev Pointer to the UFS device structure. + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t ufs_scsi_bind(struct device *ufs_dev) +{ + int32_t err; + struct ufs_host_controller *ufshc = ufs_dev->data; + + err = ufshc_alloc_scsi_host(ufshc); + if (err != 0) { + goto out; + } + (void)ufshc_add_lus(ufshc); + +out: + return err; +} + +/** + * @brief Initializes the UFS host controller and its associated resources + * + * This function performs the initialization of the UFS host controller + * - Initializing the host and device + * - Reading device-specific information and parameters + * - Retrieving LUN (Logical Unit Number) information + * - Binding the SCSI layer to the UFS device + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return 0 on success, negative error code on failure. + */ +static int32_t ufshc_init(struct ufs_host_controller *ufshc) +{ + int32_t err; + + err = k_mutex_lock(&ufshc->ufs_lock, K_FOREVER); + if (err != 0) { + return err; + } + + /* host initialization */ + err = ufshc_host_initialize(ufshc); + if (err != 0) { + goto out; + } + + /* Check Device Initialization */ + err = ufshc_card_initialize(ufshc); + if (err != 0) { + goto out; + } + + /* device param - UFS descriptors */ + err = ufshc_read_device_info(ufshc); + if (err != 0) { + goto out; + } + + /* read LUN info */ + err = ufshc_get_lun_info(ufshc); + if (err != 0) { + LOG_ERR("Read LUN info failed"); + goto out; + } + + /* register scsi_exec */ + err = ufs_scsi_bind(ufshc->dev); + + /* UFS initialization succeeded */ + if (err == 0) { + ufshc->is_initialized = true; + } + +out: + /* Unlock card mutex */ + (void)k_mutex_unlock(&ufshc->ufs_lock); + + return err; +} + +/** + * @brief Initialize the UFS host controller + * + * This function is responsible for initializing the UFS driver by linking the UFS + * device structure to its corresponding host controller instance and performing + * the necessary initialization steps. + * + * @param ufshc_dev Pointer to the device structure for the UFS host controller + * @param ufshc Pointer to the UFS host controller structure that will be initialized + * + * @return 0 on success, negative error code on failure. + */ +int ufs_init(const struct device *ufshc_dev, struct ufs_host_controller **ufshc) +{ + int err; + + /* Validate input parameters */ + if ((ufshc_dev == NULL) || (ufshc == NULL)) { + return -ENODEV; + } + + /* Link the UFS device to the host controller structure */ + *ufshc = (struct ufs_host_controller *)ufshc_dev->data; + + /* Initialize the UFS host controller */ + err = ufshc_init(*ufshc); + if (err != 0) { + LOG_ERR("Initialization failed with error %d", err); + } + + return err; +} + +/** + * @brief Send raw UPIU commands to the UFS host controller + * + * This function executes raw UPIU commands such as NOP, Query, and SCSI commands + * by using the provided `msgcode`. The caller must fill the UPIU request content + * correctly, as no further validation is performed. The function copies the response + * to the `rsp` parameter if it's not NULL. + * + * @param ufshc Pointer to the UFS host controller structure + * @param msgcode Message code, one of UPIU Transaction Codes Initiator to Target + * @param req Pointer to the request data (raw UPIU command) + * @param rsp Pointer to the response data (raw UPIU reply) + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_exec_raw_upiu_cmd(struct ufs_host_controller *ufshc, + uint32_t msgcode, void *req, void *rsp) +{ + int ret = 0; + struct ufshc_xfer_req_upiu *req_upiu; + struct ufshc_xfer_resp_upiu *rsp_upiu; + struct ufshc_xfer_cmd_desc *ucd_ptr = ufshc->ucdl_base_addr; + struct scsi_cmd *pccb; + uint32_t slot = 0U; + + /* Find an available slot for the transfer request */ + ufshc_find_slot_in_trl(ufshc, &slot); + + switch (msgcode) { + case UFSHC_NOP_UPIU_TRANS_CODE: /* NOP (No Operation) UPIU */ + rsp_upiu = (struct ufshc_xfer_resp_upiu *)rsp; + /* Fill NOP UPIU fields */ + ufshc_fill_nop_upiu(ufshc, slot); + /* Send the NOP UPIU command */ + ret = ufshc_send_upiu_cmd(ufshc, slot); + if (rsp != NULL) { + /* copy the response UPIU */ + (void)memcpy(rsp_upiu, &ucd_ptr->resp_upiu, sizeof(*rsp_upiu)); + } + break; + case UFSHC_QRY_UPIU_TRANS_CODE: /* Query UPIU */ + req_upiu = (struct ufshc_xfer_req_upiu *)req; + rsp_upiu = (struct ufshc_xfer_resp_upiu *)rsp; + /* Fill query UPIU fields */ + ufshc_fill_query_upiu(ufshc, slot, + (int32_t)req_upiu->query_req_upiu.tsf.opcode, + req_upiu->query_req_upiu.tsf.desc_id, + req_upiu->query_req_upiu.tsf.index, + req_upiu->query_req_upiu.tsf.selector, + req_upiu->query_req_upiu.tsf.value, + req_upiu->query_req_upiu.tsf.length); + /* Send the query UPIU command */ + ret = ufshc_send_upiu_cmd(ufshc, slot); + if (rsp != NULL) { + /* Copy the response UPIU */ + (void)memcpy(rsp_upiu, &ucd_ptr->resp_upiu, sizeof(*rsp_upiu)); + } + break; + case UFSHC_CMD_UPIU_TRANS_CODE: /* SCSI command UPIU */ + pccb = (struct scsi_cmd *)req; + /* Fill SCSI command-specific UPIU fields */ + ufshc_fill_scsi_cmd_upiu(ufshc, slot, pccb); + /* Send the SCSI UPIU command */ + ret = ufshc_send_upiu_cmd(ufshc, slot); + if (ret != 0) { + ret = -EINVAL; + } + break; + case UFSHC_TSK_UPIU_TRANS_CODE: /* Task management UPIU command */ + ret = -ENOTSUP; + break; + default: + ret = -EINVAL; + break; + }; + + return ret; +} + +/** + * @brief Read or write descriptors in the UFS host controller + * + * This function reads or writes the specified UFS descriptor based on the operation mode. + * If writing, the contents of `param_buff` are written to the descriptor. If reading, + * the descriptor contents are copied into `param_buff` starting at `param_offset`. + * + * @param ufshc Pointer to the UFS host controller structure + * @param write Boolean indicating whether to write (true) or read (false) the descriptors + * @param idn Descriptor identifier + * @param index Descriptor index + * @param param_offset Offset for the parameters in the descriptor + * @param param_buff Pointer to the buffer holding the parameters to read/write + * @param param_size Size of the parameter buffer + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_rw_descriptors(struct ufs_host_controller *ufshc, + bool write, uint8_t idn, uint8_t index, + uint8_t param_offset, void *param_buff, + uint8_t param_size) +{ + int ret; + int32_t opcode = (int32_t)UFSHC_QRY_READ_DESC_CMD; + uint8_t desc_buf[UFSHC_QRY_DESC_MAX_SIZE] = {0}; + + /* Validate input parameters */ + if ((param_buff == NULL) || (param_size == 0U)) { + ret = -EINVAL; + goto out; + } + + /* Handle write operation */ + if (write) { + opcode = (int32_t)UFSHC_QRY_WRITE_DESC_CMD; + (void)memcpy((void *)desc_buf, param_buff, param_size); + } + + /* Perform the descriptor query operation (read or write) */ + ret = ufshc_query_descriptor(ufshc, opcode, + (int32_t)idn, index, 0, + desc_buf, (int32_t)UFSHC_QRY_DESC_MAX_SIZE); + if ((ret == 0) && (write == false)) { + /* copy from descriptor buffer to user buffer */ + (void)memcpy(param_buff, + (const void *)(&desc_buf[param_offset]), + param_size); + } + +out: + return ret; +} + +/** + * @brief Send attribute query requests to the UFS host controller + * + * This function performs a read or write operation for a specific UFS attribute + * identified by `idn`. + * + * @param ufshc Pointer to the UFS host controller structure + * @param write Boolean indicating whether to write (true) or read (false) the attribute + * @param idn Attribute identifier + * @param data Pointer to the attribute value to read/write + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_rw_attributes(struct ufs_host_controller *ufshc, + bool write, uint8_t idn, void *data) +{ + int ret; + int32_t opcode = (int32_t)UFSHC_QRY_READ_ATTR_CMD; + uint32_t attr_value = 0; + + /* Validate input parameters */ + if (data == NULL) { + ret = -EINVAL; + goto out; + } + + /* Handle write operation */ + if (write) { + opcode = (int32_t)UFSHC_QRY_WRITE_ATTR_CMD; + attr_value = *(uint32_t *)data; + } + + /* Perform the attribute query operation (read or write) */ + ret = ufshc_query_attr(ufshc, opcode, (int32_t)idn, 0, 0, &attr_value); + if ((ret == 0) && (write == false)) { + /* copy attribute value */ + (void)memcpy(data, (const void *)&attr_value, sizeof(attr_value)); + } + +out: + return ret; +} + +/** + * @brief Read or write flags in the UFS host controller + * + * This function performs a read or write operation for a UFS flag specified by + * `idn` and `index`. Supports setting, clearing, or reading the flag value. + * + * @param ufshc Pointer to the UFS host controller structure + * @param write Boolean indicating whether to write (true) or read (false) the flags + * @param idn Flag identifier + * @param index Flag index + * @param data Pointer to the data buffer for reading/writing the flag + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_rw_flags(struct ufs_host_controller *ufshc, + bool write, uint8_t idn, uint8_t index, void *data) +{ + int ret; + int32_t opcode = (int32_t)UFSHC_QRY_READ_FLAG_CMD; + bool flag_value = false; + + /* Validate input parameters */ + if (data == NULL) { + ret = -EINVAL; + goto out; + } + + /* Handle write flag operation */ + if (write) { + flag_value = *(bool *)data; + if (!flag_value) { + /* Clear flag if value is false */ + opcode = (int32_t)UFSHC_QRY_CLR_FLAG_CMD; + } else { + /* Set flag if value is true */ + opcode = (int32_t)UFSHC_QRY_SET_FLAG_CMD; + } + } + + /* Perform the flag query operation (set, clear, or read) */ + ret = ufshc_query_flag(ufshc, opcode, + (int32_t)idn, index, + (bool *)(&flag_value)); + if ((ret == 0) && (write == false)) { + /* copy flag value */ + (void)memcpy(data, (const void *)&flag_value, sizeof(flag_value)); + } + +out: + return ret; +} diff --git a/subsys/ufs/ufs_ops.c b/subsys/ufs/ufs_ops.c new file mode 100644 index 0000000000000..ae224691ed428 --- /dev/null +++ b/subsys/ufs/ufs_ops.c @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * This file implements requests to configure UFS device. + * It includes functions for handling SCSI Generic (SG) requests + * related to UFS operations querying attributes, flags, and descriptors. + */ + +#include + +/** + * @brief Handle query-based requests for UFS + * + * This function processes query-based IOCTL requests for the UFS device. + * attribute, flag, and descriptor queries. + * + * @param ufshc Pointer to the UFS host controller structure + * @param arg Pointer to the SG IO request structure containing query parameters + * + * @return 0 on success, negative error code on failure + */ +static int32_t ufs_qry_ioctl_request(struct ufs_host_controller *ufshc, + struct sg_io_req *req) +{ + int32_t ret = 0; + int32_t ioctl_id; + struct ufs_sg_req *ufs_req = (struct ufs_sg_req *)req->request; + struct ufs_qry_ioctl_req *qry_ioctl_req = ufs_req->req_qry_ioctl; + bool write_opr; + struct ufs_qry_ioctl_attr *attr_param; + struct ufs_qry_ioctl_flag *flag_param; + struct ufs_qry_ioctl_desc *desc_param; + + if (qry_ioctl_req == NULL) { + return -EINVAL; + } + + ioctl_id = (int32_t)(qry_ioctl_req->ioctl_id); + + /* read or write based on the data transfer direction */ + if (req->dxfer_dir == SG_DXFER_TO_DEV) { + write_opr = true; + } else if (req->dxfer_dir == SG_DXFER_FROM_DEV) { + write_opr = false; + } else { + return -EINVAL; + } + + /* Handle different types of queries */ + switch (ioctl_id) { + case UFS_QRY_IOCTL_ATTR: + attr_param = (struct ufs_qry_ioctl_attr *)&qry_ioctl_req->attr; + ret = ufshc_rw_attributes(ufshc, write_opr, + attr_param->attr_id, + req->dxferp); + break; + case UFS_QRY_IOCTL_FLAG: + flag_param = (struct ufs_qry_ioctl_flag *)&qry_ioctl_req->flag; + ret = ufshc_rw_flags(ufshc, write_opr, + flag_param->flag_id, 0, + req->dxferp); + break; + case UFS_QRY_IOCTL_DESC: + desc_param = (struct ufs_qry_ioctl_desc *)&qry_ioctl_req->desc; + ret = ufshc_rw_descriptors(ufshc, write_opr, + desc_param->desc_id, + desc_param->index, + desc_param->param_offset, + req->dxferp, + (uint8_t)req->dxfer_len); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/** + * @brief Handle a SCSI Generic (SG) request for UFS + * + * This function dispatches an SG_IO request to the appropriate UFS operation. + * + * @param ufshc Pointer to the UFS host controller structure + * @param arg Argument for the request, which could be a UFS query or other data + * + * @return 0 on success, negative error code on failure + */ +int ufs_sg_request(struct ufs_host_controller *ufshc, void *arg) +{ + int ret; + struct sg_io_req *req = (struct sg_io_req *)arg; + struct ufs_sg_req *ufs_req; + int32_t msgcode; + + if ((req == NULL) || (req->protocol != BSG_PROTOCOL_SCSI) || + (req->subprotocol != BSG_SUB_PROTOCOL_SCSI_TRANSPORT) || + (req->request == NULL) || (req->dxferp == NULL)) { + ret = -EINVAL; + goto out; + } + + ufs_req = (struct ufs_sg_req *)req->request; + msgcode = ufs_req->msgcode; + + /* Dispatch based on the message code */ + switch (msgcode) { + case (int32_t)UFS_SG_QUERY_REQ: + ret = ufs_qry_ioctl_request(ufshc, req); + break; + case (int32_t)UFS_SG_TASK_REQ: + ret = -ENOTSUP; + break; + default: + ret = -ENOTSUP; + break; + } + +out: + return ret; +} diff --git a/tests/drivers/disk/disk_access/src/main.c b/tests/drivers/disk/disk_access/src/main.c index 0c36f1d6121ee..479e98b409925 100644 --- a/tests/drivers/disk/disk_access/src/main.c +++ b/tests/drivers/disk/disk_access/src/main.c @@ -28,6 +28,8 @@ #define DISK_NAME_PHYS "NAND" #elif defined(CONFIG_NVME) #define DISK_NAME_PHYS "nvme0n0" +#elif defined(CONFIG_DISK_DRIVER_UFS) +#define DISK_NAME_PHYS "UFS" #elif defined(CONFIG_DISK_DRIVER_RAM) /* Since ramdisk is enabled by default on e.g. qemu boards, it needs to be checked last to not * override other backends. @@ -43,8 +45,8 @@ #define DISK_NAME DISK_NAME_PHYS #endif -/* Assume the largest sector we will encounter is 512 bytes */ -#define SECTOR_SIZE 512 +/* Assume the largest sector we will encounter is 4096 bytes */ +#define SECTOR_SIZE 4096 /* Sector counts to read */ #define SECTOR_COUNT1 8 diff --git a/tests/drivers/disk/disk_access/testcase.yaml b/tests/drivers/disk/disk_access/testcase.yaml index fea3bd706bfd9..677619b58622f 100644 --- a/tests/drivers/disk/disk_access/testcase.yaml +++ b/tests/drivers/disk/disk_access/testcase.yaml @@ -45,3 +45,5 @@ tests: platform_allow: - native_sim/native/64 - native_sim + drivers.disk.ufs: + filter: dt_compat_enabled("zephyr,ufs-disk") diff --git a/tests/subsys/scsi/scsi_ufs/CMakeLists.txt b/tests/subsys/scsi/scsi_ufs/CMakeLists.txt new file mode 100644 index 0000000000000..524d6c944f81b --- /dev/null +++ b/tests/subsys/scsi/scsi_ufs/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(scsi_subsys_test) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/subsys/scsi/scsi_ufs/Kconfig b/tests/subsys/scsi/scsi_ufs/Kconfig new file mode 100644 index 0000000000000..7d77a7517063c --- /dev/null +++ b/tests/subsys/scsi/scsi_ufs/Kconfig @@ -0,0 +1,12 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "SCSI UFS test" + +config TEST_SCSI_UFS_RW + bool "SCSI UFS RW Tests (DANGEROUS)" + help + To perform Writing to UFS device as part of SCSI RW Tests. + This will overwrite existing data on UFS device. + +source "Kconfig.zephyr" diff --git a/tests/subsys/scsi/scsi_ufs/README.txt b/tests/subsys/scsi/scsi_ufs/README.txt new file mode 100644 index 0000000000000..ffafa07d6391e --- /dev/null +++ b/tests/subsys/scsi/scsi_ufs/README.txt @@ -0,0 +1,16 @@ +SCSI-UFS Subsystem Test +####################### + +The Test is designed to verify the SCSI subsystem stack implementation for UFS +devices. It requires a UFS Device be connected to the board to pass. + +* Init test: Initialize the SCSI Device handle through UFS host controller. + +* IOCTL test: Verify the SCSI IOCTL operations based on SG_IO. + +Warning: Ensure that you have configured the test to allow writes to the UFS +device. By default, test prevents write to avoid data corruption. Please refer +to the configuration options in testcase to enable write operations if needed. + +* R/W Test: Write data to the SCSI Device and verify that it can be read back +without errors. diff --git a/tests/subsys/scsi/scsi_ufs/prj.conf b/tests/subsys/scsi/scsi_ufs/prj.conf new file mode 100644 index 0000000000000..e5d0cd0b6a06f --- /dev/null +++ b/tests/subsys/scsi/scsi_ufs/prj.conf @@ -0,0 +1,9 @@ +CONFIG_TEST=y +CONFIG_ZTEST=y +CONFIG_LOG=y + +CONFIG_SCSI=y +CONFIG_UFS_STACK=y + +CONFIG_SCSI_LOG_LEVEL_DBG=y +CONFIG_UFS_LOG_LEVEL_DBG=y diff --git a/tests/subsys/scsi/scsi_ufs/src/main.c b/tests/subsys/scsi/scsi_ufs/src/main.c new file mode 100644 index 0000000000000..c951bb71c462b --- /dev/null +++ b/tests/subsys/scsi/scsi_ufs/src/main.c @@ -0,0 +1,115 @@ +/* + * Copyright 2025 (c) Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define UFS_NODE DT_ALIAS(ufs0) + +BUILD_ASSERT(DT_NODE_HAS_STATUS(UFS_NODE, okay), "UFS node is disabled!"); + +#define SCSI_TEST_LUN 0 + +static struct scsi_host_info *tst_shost; +static struct scsi_device *tst_sdev; + +static const struct device *const tst_ufshcdev = DEVICE_DT_GET_OR_NULL(UFS_NODE); +static struct ufs_host_controller *tst_ufshc; + +#if IS_ENABLED(CONFIG_TEST_SCSI_UFS_RW) +#define SCSI_TEST_SECTOR_COUNT 3 +#define SCSI_TEST_MAX_SECTOR_SIZE 4096 +#define SCSI_TEST_BUF_SIZE (SCSI_TEST_SECTOR_COUNT * SCSI_TEST_MAX_SECTOR_SIZE) +static uint8_t wr_buf[SCSI_TEST_BUF_SIZE] __aligned(CONFIG_UFSHC_BUFFER_ALIGNMENT); +static uint8_t rd_buf[SCSI_TEST_BUF_SIZE] __aligned(CONFIG_UFSHC_BUFFER_ALIGNMENT); +#endif /* CONFIG_TEST_SCSI_UFS_RW */ + +/* Verify that SCSI Device Handle is Initialized */ +ZTEST(scsi_stack, test_0_init) +{ + tst_sdev = scsi_device_lookup_by_host(tst_shost, SCSI_TEST_LUN); + zassert_not_null(tst_sdev, "SCSI Device for SCSI_TEST_LUN is NULL"); +} + +/* Verify SCSI cmds using IOCTL */ +ZTEST(scsi_stack, test_1_scsi_cmd) +{ + int32_t rc; + + if (tst_sdev == NULL) { + ztest_test_skip(); + return; + } + + rc = scsi_ioctl(tst_sdev, SCSI_IOCTL_TEST_UNIT_READY, NULL); + zassert_equal(rc, 0, "SCSI_CMD - TUR failed: %d", rc); +} + +/* Verify SCSI Generic (SG) IOCTL */ +ZTEST(scsi_stack, test_2_scsi_sgio) +{ + int32_t rc; + struct sg_io_req req; + uint8_t tur_cmd[] = {SCSI_TST_U_RDY, 0, 0, 0, 0, 0}; + + if (tst_sdev == NULL) { + ztest_test_skip(); + return; + } + + req.protocol = BSG_PROTOCOL_SCSI; + req.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; + req.request = &tur_cmd[0]; + req.request_len = (uint32_t)sizeof(tur_cmd); + req.dxfer_dir = (int32_t)PERIPHERAL_TO_PERIPHERAL; + req.dxferp = NULL; + rc = scsi_ioctl(tst_sdev, SG_IO, &req); + zassert_equal(rc, 0, "SCSI_IOCTL - SGIO - TUR failed: %d", rc); +} + +#if IS_ENABLED(CONFIG_TEST_SCSI_UFS_RW) +/* Verify that SCSI stack can read/write to UFS card */ +ZTEST(scsi_stack, test_3_scsi_rw) +{ + int32_t rc; + int32_t block_addr = 0; + + if (tst_sdev == NULL) { + ztest_test_skip(); + return; + } + + memset(wr_buf, 0xAD, SCSI_TEST_BUF_SIZE); + memset(rd_buf, 0, SCSI_TEST_BUF_SIZE); + + rc = scsi_write(tst_sdev, block_addr, SCSI_TEST_SECTOR_COUNT, wr_buf); + zassert_equal(rc, 0, "Write SCSI failed: %d", rc); + rc = scsi_read(tst_sdev, block_addr, SCSI_TEST_SECTOR_COUNT, rd_buf); + zassert_equal(rc, 0, "Read SCSI failed: %d", rc); + zassert_mem_equal(wr_buf, rd_buf, SCSI_TEST_BUF_SIZE, + "Read of erased area was not zero"); +} +#endif /* CONFIG_TEST_SCSI_UFS_RW */ + +/* + * Scsi UFS Device Setup + * This setup must run first, to ensure UFS card is initialized. + */ +static void *scsi_test_setup(void) +{ + int32_t rc; + + zassert_true(device_is_ready(tst_ufshcdev), "UFSHC device is not ready"); + rc = ufs_init(tst_ufshcdev, &tst_ufshc); + zassert_equal(rc, 0, "UFS initialization failed: %d", rc); + tst_shost = tst_ufshc->host; + + return NULL; +} + +ZTEST_SUITE(scsi_stack, NULL, scsi_test_setup, NULL, NULL, NULL); diff --git a/tests/subsys/scsi/scsi_ufs/testcase.yaml b/tests/subsys/scsi/scsi_ufs/testcase.yaml new file mode 100644 index 0000000000000..1ff04df7cd442 --- /dev/null +++ b/tests/subsys/scsi/scsi_ufs/testcase.yaml @@ -0,0 +1,14 @@ +common: + depends_on: + - scsi + tags: + - scsi + - ufs + filter: dt_alias_exists("ufs0") +tests: + scsi.ufs.scsi_basic: + depends_on: ufshc + scsi.ufs.scsi_rw: + depends_on: ufshc + extra_configs: + - CONFIG_TEST_SCSI_UFS_RW=y diff --git a/tests/subsys/ufs/CMakeLists.txt b/tests/subsys/ufs/CMakeLists.txt new file mode 100644 index 0000000000000..ef70776deaf16 --- /dev/null +++ b/tests/subsys/ufs/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ufs_subsys_test) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/subsys/ufs/README.txt b/tests/subsys/ufs/README.txt new file mode 100644 index 0000000000000..b523588400443 --- /dev/null +++ b/tests/subsys/ufs/README.txt @@ -0,0 +1,13 @@ +UFS Subsystem Test +################## + +This test is designed to verify the UFS subsystem stack implementation for UFS +devices. Note that this test will only perform basic reads from the UFS card +after initialization. It requires an UFS card be connected to the board to pass. + +* Init test: verify the UFS host controller can detect card presence, and + test the initialization flow of the UFS subsystem to verify that the stack + can correctly initialize an UFS card. + +* Configuration test: Verify that the UFS stack reports a valid configuration + for UFS card after initialization. diff --git a/tests/subsys/ufs/prj.conf b/tests/subsys/ufs/prj.conf new file mode 100644 index 0000000000000..feeabca7c7b9e --- /dev/null +++ b/tests/subsys/ufs/prj.conf @@ -0,0 +1,9 @@ +CONFIG_TEST=y +CONFIG_ZTEST=y +CONFIG_LOG=y + +CONFIG_SCSI=y +CONFIG_UFS_STACK=y + +CONFIG_UFS_LOG_LEVEL_DBG=y +CONFIG_UFSHC_LOG_LEVEL_DBG=y diff --git a/tests/subsys/ufs/src/main.c b/tests/subsys/ufs/src/main.c new file mode 100644 index 0000000000000..89ed6be8fcccf --- /dev/null +++ b/tests/subsys/ufs/src/main.c @@ -0,0 +1,124 @@ +/* + * Copyright 2025 (c) Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define UFS_NODE DT_ALIAS(ufs0) + +BUILD_ASSERT(DT_NODE_HAS_STATUS(UFS_NODE, okay), "UFS node is disabled!"); + +static const struct device *const tst_ufshcdev = DEVICE_DT_GET_OR_NULL(UFS_NODE); +static struct ufs_host_controller *tst_ufshc; + +/* UFS card initialization */ +ZTEST(ufs_stack, test_0_init) +{ + int32_t ret; + + ret = ufs_init(tst_ufshcdev, &tst_ufshc); + zassert_equal(ret, 0, "UFS initialization failed: %d", ret); +} + +/* Read descriptor information from UFS card */ +ZTEST(ufs_stack, test_1_ioctl_desc) +{ + int32_t ret; + struct sg_io_req sg_req; + struct ufs_sg_req ufs_req; + struct ufs_qry_ioctl_req qry_ioctl_req; + uint8_t lun_enable = 0; + uint8_t test_lun = 0; + + sg_req.protocol = BSG_PROTOCOL_SCSI; + sg_req.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT; + sg_req.request = &ufs_req; + + sg_req.dxfer_dir = SG_DXFER_FROM_DEV; + sg_req.dxferp = (uint8_t *)(&lun_enable); + sg_req.dxfer_len = (uint32_t)sizeof(lun_enable); + + ufs_req.msgcode = (int32_t)UFS_SG_QUERY_REQ; + ufs_req.req_qry_ioctl = &qry_ioctl_req; + + qry_ioctl_req.ioctl_id = UFS_QRY_IOCTL_DESC; + qry_ioctl_req.desc.desc_id = UFSHC_UNIT_DESC_IDN; + qry_ioctl_req.desc.index = test_lun; + qry_ioctl_req.desc.param_offset = UFSHC_UD_PARAM_LU_ENABLE; + + ret = ufs_sg_request(tst_ufshc, &sg_req); + zassert_equal(ret, 0, "UFS IOCTL desc failed: %d", ret); + + TC_PRINT("Lun id:%d, lun_enable:%Xh\n", test_lun, lun_enable); +} + +/* Read attribute value from UFS card */ +ZTEST(ufs_stack, test_2_ioctl_attr) +{ + int32_t ret; + struct sg_io_req sg_req; + struct ufs_sg_req ufs_req; + struct ufs_qry_ioctl_req qry_ioctl_req; + uint32_t blun_attrval = 0U; + + sg_req.protocol = BSG_PROTOCOL_SCSI; + sg_req.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT; + sg_req.request = &ufs_req; + + sg_req.dxfer_dir = SG_DXFER_FROM_DEV; + sg_req.dxferp = &blun_attrval; + + ufs_req.msgcode = (int32_t)UFS_SG_QUERY_REQ; + ufs_req.req_qry_ioctl = &qry_ioctl_req; + + qry_ioctl_req.ioctl_id = UFS_QRY_IOCTL_ATTR; + qry_ioctl_req.attr.attr_id = UFSHC_BLUEN_ATTRID; + + ret = ufs_sg_request(tst_ufshc, &sg_req); + zassert_equal(ret, 0, "UFS IOCTL attr failed: %d", ret); + + TC_PRINT("bootlun_attrval:%Xh\n", blun_attrval); +} + +/* Read flag value from UFS card */ +ZTEST(ufs_stack, test_3_ioctl_flag) +{ + int32_t ret; + struct sg_io_req sg_req; + struct ufs_sg_req ufs_req; + struct ufs_qry_ioctl_req qry_ioctl_req; + bool flag_val = false; + + sg_req.protocol = BSG_PROTOCOL_SCSI; + sg_req.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT; + sg_req.request = &ufs_req; + + sg_req.dxfer_dir = SG_DXFER_FROM_DEV; + sg_req.dxferp = &flag_val; + + ufs_req.msgcode = (int32_t)UFS_SG_QUERY_REQ; + ufs_req.req_qry_ioctl = &qry_ioctl_req; + + qry_ioctl_req.ioctl_id = UFS_QRY_IOCTL_FLAG; + qry_ioctl_req.flag.flag_id = UFSHC_FDEVINIT_FLAG_IDN; + + ret = ufs_sg_request(tst_ufshc, &sg_req); + zassert_equal(ret, 0, "UFS IOCTL Flag failed: %d", ret); + + TC_PRINT("fdeviceinit_flag:%Xh\n", flag_val); +} + +/* UFS device setup */ +static void *ufs_test_setup(void) +{ + zassert_true(device_is_ready(tst_ufshcdev), "UFSHC device is not ready"); + + return NULL; +} + +ZTEST_SUITE(ufs_stack, NULL, ufs_test_setup, NULL, NULL, NULL); diff --git a/tests/subsys/ufs/testcase.yaml b/tests/subsys/ufs/testcase.yaml new file mode 100644 index 0000000000000..dfadfed298721 --- /dev/null +++ b/tests/subsys/ufs/testcase.yaml @@ -0,0 +1,12 @@ +common: + depends_on: ufs + tags: + - subsys + - ufs +tests: + ufs.ufs_api: + harness: ztest + harness_config: + fixture: fixture_ufs + filter: dt_alias_exists("ufs0") + tags: ufs