| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/driver.h> |
| #include <ddk/platform-defs.h> |
| #include <ddk/protocol/platform-device-lib.h> |
| #include <hw/reg.h> |
| |
| #include "ufs.h" |
| |
| typedef struct { |
| uint32_t addr; |
| uint32_t val; |
| } ufs_cfg_attr_t; |
| |
| static ufs_cfg_attr_t hi3660_ufs_calib_of_rateb[] = { |
| {0xD0C10000, 0x1}, /* Unipro VS_Mphy_disable */ |
| {0x156A0000, 0x2}, /* PA_HSSeries */ |
| {0x81140000, 0x1}, /* MPHY CBRATESEL */ |
| {0x81210000, 0x2D}, /* MPHY CBOVRCTRL2 */ |
| {0x81220000, 0x1}, /* MPHY CBOVRCTRL3 */ |
| {0xD0850000, 0x1}, /* Unipro VS_MphyCfgUpdt */ |
| {0x800D0004, 0x58}, /* MPHY RXOVRCTRL4 rx0 */ |
| {0x800D0005, 0x58}, /* MPHY RXOVRCTRL4 rx1 */ |
| {0x800E0004, 0xB}, /* MPHY RXOVRCTRL5 rx0 */ |
| {0x800E0005, 0xB}, /* MPHY RXOVRCTRL5 rx1 */ |
| {0x80090004, 0x1}, /* MPHY RXSQCTRL rx0 */ |
| {0x80090005, 0x1}, /* MPHY RXSQCTRL rx1 */ |
| {0xD0850000, 0x1}, /* Unipro VS_MphyCfgUpdt */ |
| {0, 0}, |
| }; |
| |
| static ufs_cfg_attr_t hi3660_ufs_prelink_calib_attr[] = { |
| {0x81130000, 0x1}, |
| {0xD0850000, 0x1}, |
| {0x008F0004, 0x7}, /* RX Min Activate Time */ |
| {0x008F0005, 0x7}, /* RX Min Activate Time */ |
| {0x00950004, 0x4F}, /* Gear3 Synclength */ |
| {0x00950005, 0x4F}, /* Gear3 Synclength */ |
| {0x00940004, 0x4F}, /* Gear2 Synclength */ |
| {0x00940005, 0x4F}, /* Gear2 Synclength */ |
| {0x008B0004, 0x4F}, /* Gear1 Synclength */ |
| {0x008B0005, 0x4F}, /* Gear1 Synclength */ |
| {0x000F0000, 0x5}, /* Thibernate Tx */ |
| {0x000F0001, 0x5}, /* Thibernate Tx */ |
| {0xD0850000, 0x1}, /* Unipro VS_MphyCfgUpdt */ |
| {0, 0}, |
| }; |
| |
| static ufs_cfg_attr_t hi3660_ufs_postlink_calib_attr[] = { |
| {0x20440000, 0x0}, /* Unipro DL_AFC0 CreditThreshold */ |
| {0x20450000, 0x0}, /* Unipro DL_TC0 OutAckThreshold */ |
| {0x20400000, 0x9}, /* Unipro DL_TC0TXFC Threshold */ |
| {0, 0}, |
| }; |
| |
| static void mphy_hi3660_attr_write(volatile void* regs, |
| uint16_t addr, |
| uint16_t val) { |
| ufshc_send_uic_command(regs, DME_SET, MPHY_ATTR_DEMPH_ADDR_MSB, |
| (addr & 0xFF00) >> 8); |
| ufshc_send_uic_command(regs, DME_SET, MPHY_ATTR_DEMPH_ADDR_LSB, |
| (addr & 0xFF)); |
| ufshc_send_uic_command(regs, DME_SET, MPHY_ATTR_DEMPH_VAL_MSB, |
| (val & 0xFF00) >> 8); |
| ufshc_send_uic_command(regs, DME_SET, MPHY_ATTR_DEMPH_VAL_LSB, |
| (val & 0xFF)); |
| ufshc_send_uic_command(regs, DME_SET, MPHY_ATTR_DEMPH_CTRL, 1); |
| } |
| |
| static void mphy_hi3660_config_equalizer(volatile void* regs) { |
| mphy_hi3660_attr_write(regs, MPHY_ATTR_DEMPH_ADDR1, |
| MPHY_ATTR_DEMPH_VAL1); |
| mphy_hi3660_attr_write(regs, MPHY_ATTR_DEMPH_ADDR2, |
| MPHY_ATTR_DEMPH_VAL1); |
| mphy_hi3660_attr_write(regs, MPHY_ATTR_DEMPH_ADDR3, |
| MPHY_ATTR_DEMPH_VAL2); |
| mphy_hi3660_attr_write(regs, MPHY_ATTR_DEMPH_ADDR4, |
| MPHY_ATTR_DEMPH_VAL2); |
| } |
| |
| static zx_status_t mphy_hi3660_pre_link_startup(volatile void* regs) { |
| zx_status_t status; |
| uint32_t reg_val; |
| |
| ufshc_check_h8(regs); |
| |
| // When chip status is normal |
| writel(UFS_HCLKDIV_NORMAL_VAL, regs + REG_UFS_HCLKDIV_OFF); |
| |
| ufshc_disable_auto_h8(regs); |
| |
| // Unipro PA Local TX LCC Enable |
| status = ufshc_send_uic_command(regs, DME_SET, UPRO_PA_TX_LCC_CTRL, 0x0); |
| if (status != ZX_OK) |
| return status; |
| |
| // Close Unipro VS Mk2 Extn support |
| status = ufshc_send_uic_command(regs, DME_SET, UPRO_MK2_EXTN_SUP, 0x0); |
| if (status != ZX_OK) |
| return status; |
| |
| reg_val = ufshc_uic_cmd_read(regs, DME_GET, UPRO_MK2_EXTN_SUP); |
| if (0 != reg_val) { |
| UFS_ERROR("Unipro Mk2 close failed!\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| mphy_hi3660_config_equalizer(regs); |
| return ZX_OK; |
| } |
| |
| static zx_status_t ufs_hi3660_calibrate(volatile void* regs, |
| ufs_cfg_attr_t* cfg) { |
| uint32_t i; |
| zx_status_t status = ZX_OK; |
| |
| for (i = 0; cfg[i].addr; i++) { |
| status = ufshc_send_uic_command(regs, DME_SET, |
| cfg[i].addr, |
| cfg[i].val); |
| if (status != ZX_OK) { |
| UFS_ERROR("UFS calib Fail! index=%u\n", i); |
| return status; |
| } |
| } |
| |
| return status; |
| } |
| |
| static zx_status_t ufs_hi3660_pre_link_startup(volatile void* regs) { |
| zx_status_t status; |
| ufs_cfg_attr_t* cfg = hi3660_ufs_calib_of_rateb; |
| |
| status = ufs_hi3660_calibrate(regs, cfg); |
| if (status != ZX_OK) |
| return status; |
| |
| cfg = hi3660_ufs_prelink_calib_attr; |
| status = ufs_hi3660_calibrate(regs, cfg); |
| if (status != ZX_OK) |
| return status; |
| |
| status = mphy_hi3660_pre_link_startup(regs); |
| return status; |
| } |
| |
| static zx_status_t ufs_hi3660_post_link_startup(volatile void* regs) { |
| zx_status_t status = ZX_OK; |
| uint32_t i = 0; |
| |
| for (; hi3660_ufs_postlink_calib_attr[i].addr; i++) { |
| status = ufshc_send_uic_command(regs, DME_SET, |
| hi3660_ufs_postlink_calib_attr[i].addr, |
| hi3660_ufs_postlink_calib_attr[i].val); |
| if (status != ZX_OK) |
| UFS_ERROR("hi3660_ufs_postlink_calib_attr index=%u Fail!\n", i); |
| } |
| |
| return status; |
| } |
| |
| static zx_status_t ufs_hi3660_link_startup(volatile void* regs, |
| uint8_t status) { |
| zx_status_t ret = 0; |
| |
| switch (status) { |
| case PRE_CHANGE: |
| ret = ufs_hi3660_pre_link_startup(regs); |
| break; |
| case POST_CHANGE: |
| ret = ufs_hi3660_post_link_startup(regs); |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static ufs_hba_variant_ops_t ufs_hi3660_vops = { |
| .name = "hi3660_ufs", |
| .link_startup = ufs_hi3660_link_startup, |
| }; |
| |
| static void hisi_ufs_release(void* ctx) { |
| ufshc_dev_t* dev = ctx; |
| mmio_buffer_release(&dev->ufshc_mmio); |
| zx_handle_close(dev->bti); |
| free(dev); |
| } |
| |
| static zx_protocol_device_t hisi_ufs_device_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .release = hisi_ufs_release, |
| }; |
| |
| static zx_status_t hisi_ufs_bind(void* ctx, zx_device_t* parent) { |
| ufshc_dev_t* dev; |
| zx_status_t status; |
| |
| zxlogf(INFO, "hisi_ufs_bind\n"); |
| |
| dev = calloc(1, sizeof(ufshc_dev_t)); |
| if (!dev) { |
| UFS_ERROR("Out of memory!\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| if ((status = device_get_protocol(parent, ZX_PROTOCOL_PDEV, |
| &dev->pdev)) != ZX_OK) { |
| UFS_ERROR("ZX_PROTOCOL_PDEV not available!\n"); |
| goto fail; |
| } |
| |
| status = pdev_get_bti(&dev->pdev, 0, &dev->bti); |
| if (status != ZX_OK) { |
| UFS_ERROR("pdev_get_bti failed!\n"); |
| goto fail; |
| } |
| |
| status = pdev_map_mmio_buffer(&dev->pdev, MMIO_UFSHC, |
| ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| &dev->ufshc_mmio); |
| if (status != ZX_OK) { |
| UFS_ERROR("pdev_map_mmio_buffer ufshc mmio failed!:%d\n", status); |
| goto fail; |
| } |
| |
| status = ufshc_init(dev, &ufs_hi3660_vops); |
| if (status != ZX_OK) { |
| UFS_ERROR("UFS HC enabling failed!status=%d\n", status); |
| goto fail; |
| } |
| UFS_DBG("UFS HC Initialization Success.\n"); |
| |
| // Create the device. |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "hisi-ufs", |
| .ops = &hisi_ufs_device_proto, |
| .ctx = dev, |
| .flags = DEVICE_ADD_NON_BINDABLE, |
| }; |
| |
| status = pdev_device_add(&dev->pdev, 0, &args, &dev->zxdev); |
| if (status != ZX_OK) { |
| status = device_add(parent, &args, &dev->zxdev); |
| if (status != ZX_OK) { |
| UFS_ERROR("hisi UFS device pdev_add status: %d\n", status); |
| goto fail; |
| } |
| } |
| |
| return ZX_OK; |
| |
| fail: |
| hisi_ufs_release(dev); |
| return status; |
| } |
| |
| static zx_driver_ops_t hisi_ufs_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = hisi_ufs_bind, |
| }; |
| |
| ZIRCON_DRIVER_BEGIN(hisi_ufs, hisi_ufs_driver_ops, "zircon", "0.1", 4) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PDEV), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_96BOARDS), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_HIKEY960), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_HISILICON_UFS), |
| ZIRCON_DRIVER_END(hisi_ufs) |