Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+170
View File
@@ -0,0 +1,170 @@
# SPDX-License-Identifier: GPL-2.0+
#
# Kernel configuration file for the UFS host controller drivers.
#
# Copyright (C) 2011-2013 Samsung India Software Operations
#
# Authors:
# Santosh Yaraganavi <santosh.sy@samsung.com>
# Vinayak Holikatti <h.vinayak@samsung.com>
config SCSI_UFSHCD_PCI
tristate "PCI bus based UFS Controller support"
depends on PCI
help
This selects the PCI UFS Host Controller Interface. Select this if
you have UFS Host Controller with PCI Interface.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config SCSI_UFS_DWC_TC_PCI
tristate "DesignWare pci support using a G210 Test Chip"
depends on SCSI_UFSHCD_PCI
help
Synopsys Test Chip is a PHY for prototyping purposes.
If unsure, say N.
config SCSI_UFSHCD_PLATFORM
tristate "Platform bus based UFS Controller support"
depends on HAS_IOMEM
help
This selects the UFS host controller support. Select this if
you have an UFS controller on Platform bus.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config SCSI_UFS_CDNS_PLATFORM
tristate "Cadence UFS Controller platform driver"
depends on SCSI_UFSHCD_PLATFORM
help
This selects the Cadence-specific additions to UFSHCD platform driver.
If unsure, say N.
config SCSI_UFS_DWC_TC_PLATFORM
tristate "DesignWare platform support using a G210 Test Chip"
depends on OF && SCSI_UFSHCD_PLATFORM
help
Synopsys Test Chip is a PHY for prototyping purposes.
If unsure, say N.
config SCSI_UFS_QCOM
tristate "Qualcomm specific hooks to UFS controller platform driver"
depends on SCSI_UFSHCD_PLATFORM && ARCH_QCOM
depends on GENERIC_MSI_IRQ
depends on RESET_CONTROLLER
select QCOM_INLINE_CRYPTO_ENGINE if SCSI_UFS_CRYPTO
help
This selects the QCOM specific additions to UFSHCD platform driver.
UFS host on QCOM needs some vendor specific configuration before
accessing the hardware which includes PHY configuration and vendor
specific registers.
Select this if you have UFS controller on QCOM chipset.
If unsure, say N.
config SCSI_UFS_MEDIATEK
tristate "Mediatek specific hooks to UFS controller platform driver"
depends on SCSI_UFSHCD_PLATFORM && ARCH_MEDIATEK
depends on PM
depends on RESET_CONTROLLER
select PHY_MTK_UFS
select RESET_TI_SYSCON
help
This selects the Mediatek specific additions to UFSHCD platform driver.
UFS host on Mediatek needs some vendor specific configuration before
accessing the hardware which includes PHY configuration and vendor
specific registers.
Select this if you have UFS controller on Mediatek chipset.
If unsure, say N.
config SCSI_UFS_HISI
tristate "Hisilicon specific hooks to UFS controller platform driver"
depends on (ARCH_HISI || COMPILE_TEST) && SCSI_UFSHCD_PLATFORM
help
This selects the Hisilicon specific additions to UFSHCD platform driver.
Select this if you have UFS controller on Hisilicon chipset.
If unsure, say N.
config SCSI_UFS_RENESAS
tristate "Renesas specific hooks to UFS controller platform driver"
depends on (ARCH_RENESAS || COMPILE_TEST) && SCSI_UFSHCD_PLATFORM
help
This selects the Renesas specific additions to UFSHCD platform driver.
UFS host on Renesas needs some vendor specific configuration before
accessing the hardware.
Select this if you have UFS controller on Renesas chipset.
If unsure, say N.
config SCSI_UFS_TI_J721E
tristate "TI glue layer for Cadence UFS Controller"
depends on OF && HAS_IOMEM && (ARCH_K3 || COMPILE_TEST)
help
This selects driver for TI glue layer for Cadence UFS Host
Controller IP.
Selects this if you have TI platform with UFS controller.
If unsure, say N.
config SCSI_UFS_EXYNOS
tristate "Exynos specific hooks to UFS controller platform driver"
depends on SCSI_UFSHCD_PLATFORM && (ARCH_EXYNOS || COMPILE_TEST)
help
This selects the Samsung Exynos SoC specific additions to UFSHCD
platform driver. UFS host on Samsung Exynos SoC includes HCI and
UNIPRO layer, and associates with UFS-PHY driver.
Select this if you have UFS host controller on Samsung Exynos SoC.
If unsure, say N.
config SCSI_UFS_VARIABLE_SG_ENTRY_SIZE
bool
default y if SCSI_UFS_EXYNOS && SCSI_UFS_CRYPTO
config SCSI_UFS_SPRD
tristate "Unisoc specific hooks to UFS controller platform driver"
depends on SCSI_UFSHCD_PLATFORM && (ARCH_SPRD || COMPILE_TEST)
help
This selects the Unisoc specific additions to UFSHCD platform driver.
UFS host on Unisoc needs some vendor specific configuration before
accessing the hardware which includes PHY configuration and vendor
specific registers.
Select this if you have UFS controller on Unisoc chipset.
If unsure, say N.
config SCSI_UFS_ROCKCHIP
tristate "Rockchip UFS host controller driver"
depends on SCSI_UFSHCD_PLATFORM && (ARCH_ROCKCHIP || COMPILE_TEST)
help
This selects the Rockchip specific additions to UFSHCD platform driver.
UFS host on Rockchip needs some vendor specific configuration before
accessing the hardware which includes PHY configuration and vendor
specific registers.
Select this if you have UFS controller on Rockchip chipset.
If unsure, say N.
config SCSI_UFS_AMD_VERSAL2
tristate "AMD Versal Gen 2 UFS controller platform driver"
depends on SCSI_UFSHCD_PLATFORM && (ARCH_ZYNQMP || COMPILE_TEST)
help
This selects the AMD Versal Gen 2 specific additions on top of
the UFSHCD DWC and UFSHCD platform driver. UFS host on AMD
Versal Gen 2 needs some vendor specific configurations like PHY
and vendor specific register accesses before accessing the
hardware.
Select this if you have UFS controller on AMD Versal Gen 2 SoC.
If unsure, say N.
+16
View File
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_SCSI_UFS_DWC_TC_PCI) += tc-dwc-g210-pci.o ufshcd-dwc.o tc-dwc-g210.o
obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o tc-dwc-g210.o
obj-$(CONFIG_SCSI_UFS_CDNS_PLATFORM) += cdns-pltfrm.o
obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
obj-$(CONFIG_SCSI_UFS_EXYNOS) += ufs-exynos.o
obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
obj-$(CONFIG_SCSI_UFS_HISI) += ufs-hisi.o
obj-$(CONFIG_SCSI_UFS_MEDIATEK) += ufs-mediatek.o
obj-$(CONFIG_SCSI_UFS_RENESAS) += ufs-renesas.o
obj-$(CONFIG_SCSI_UFS_ROCKCHIP) += ufs-rockchip.o
obj-$(CONFIG_SCSI_UFS_SPRD) += ufs-sprd.o
obj-$(CONFIG_SCSI_UFS_TI_J721E) += ti-j721e-ufs.o
obj-$(CONFIG_SCSI_UFS_AMD_VERSAL2) += ufs-amd-versal2.o ufshcd-dwc.o
+334
View File
@@ -0,0 +1,334 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Platform UFS Host driver for Cadence controller
*
* Copyright (C) 2018 Cadence Design Systems, Inc.
*
* Authors:
* Jan Kotas <jank@cadence.com>
*
*/
#include <linux/clk.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/time.h>
#include "ufshcd-pltfrm.h"
#define CDNS_UFS_REG_HCLKDIV 0xFC
#define CDNS_UFS_REG_PHY_XCFGD1 0x113C
#define CDNS_UFS_MAX_L4_ATTRS 12
struct cdns_ufs_host {
/**
* cdns_ufs_dme_attr_val - for storing L4 attributes
*/
u32 cdns_ufs_dme_attr_val[CDNS_UFS_MAX_L4_ATTRS];
};
/**
* cdns_ufs_get_l4_attr - get L4 attributes on local side
* @hba: per adapter instance
*
*/
static void cdns_ufs_get_l4_attr(struct ufs_hba *hba)
{
struct cdns_ufs_host *host = ufshcd_get_variant(hba);
ufshcd_dme_get(hba, UIC_ARG_MIB(T_PEERDEVICEID),
&host->cdns_ufs_dme_attr_val[0]);
ufshcd_dme_get(hba, UIC_ARG_MIB(T_PEERCPORTID),
&host->cdns_ufs_dme_attr_val[1]);
ufshcd_dme_get(hba, UIC_ARG_MIB(T_TRAFFICCLASS),
&host->cdns_ufs_dme_attr_val[2]);
ufshcd_dme_get(hba, UIC_ARG_MIB(T_PROTOCOLID),
&host->cdns_ufs_dme_attr_val[3]);
ufshcd_dme_get(hba, UIC_ARG_MIB(T_CPORTFLAGS),
&host->cdns_ufs_dme_attr_val[4]);
ufshcd_dme_get(hba, UIC_ARG_MIB(T_TXTOKENVALUE),
&host->cdns_ufs_dme_attr_val[5]);
ufshcd_dme_get(hba, UIC_ARG_MIB(T_RXTOKENVALUE),
&host->cdns_ufs_dme_attr_val[6]);
ufshcd_dme_get(hba, UIC_ARG_MIB(T_LOCALBUFFERSPACE),
&host->cdns_ufs_dme_attr_val[7]);
ufshcd_dme_get(hba, UIC_ARG_MIB(T_PEERBUFFERSPACE),
&host->cdns_ufs_dme_attr_val[8]);
ufshcd_dme_get(hba, UIC_ARG_MIB(T_CREDITSTOSEND),
&host->cdns_ufs_dme_attr_val[9]);
ufshcd_dme_get(hba, UIC_ARG_MIB(T_CPORTMODE),
&host->cdns_ufs_dme_attr_val[10]);
ufshcd_dme_get(hba, UIC_ARG_MIB(T_CONNECTIONSTATE),
&host->cdns_ufs_dme_attr_val[11]);
}
/**
* cdns_ufs_set_l4_attr - set L4 attributes on local side
* @hba: per adapter instance
*
*/
static void cdns_ufs_set_l4_attr(struct ufs_hba *hba)
{
struct cdns_ufs_host *host = ufshcd_get_variant(hba);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE), 0);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERDEVICEID),
host->cdns_ufs_dme_attr_val[0]);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERCPORTID),
host->cdns_ufs_dme_attr_val[1]);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_TRAFFICCLASS),
host->cdns_ufs_dme_attr_val[2]);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_PROTOCOLID),
host->cdns_ufs_dme_attr_val[3]);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_CPORTFLAGS),
host->cdns_ufs_dme_attr_val[4]);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_TXTOKENVALUE),
host->cdns_ufs_dme_attr_val[5]);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_RXTOKENVALUE),
host->cdns_ufs_dme_attr_val[6]);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_LOCALBUFFERSPACE),
host->cdns_ufs_dme_attr_val[7]);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERBUFFERSPACE),
host->cdns_ufs_dme_attr_val[8]);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_CREDITSTOSEND),
host->cdns_ufs_dme_attr_val[9]);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_CPORTMODE),
host->cdns_ufs_dme_attr_val[10]);
ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE),
host->cdns_ufs_dme_attr_val[11]);
}
/**
* cdns_ufs_set_hclkdiv() - set HCLKDIV register value based on the core_clk.
* @hba: host controller instance
*
* Return: zero for success and non-zero for failure.
*/
static int cdns_ufs_set_hclkdiv(struct ufs_hba *hba)
{
struct ufs_clk_info *clki;
struct list_head *head = &hba->clk_list_head;
unsigned long core_clk_rate = 0;
u32 core_clk_div = 0;
if (list_empty(head))
return 0;
list_for_each_entry(clki, head, list) {
if (IS_ERR_OR_NULL(clki->clk))
continue;
if (!strcmp(clki->name, "core_clk"))
core_clk_rate = clk_get_rate(clki->clk);
}
if (!core_clk_rate) {
dev_err(hba->dev, "%s: unable to find core_clk rate\n",
__func__);
return -EINVAL;
}
core_clk_div = core_clk_rate / USEC_PER_SEC;
ufshcd_writel(hba, core_clk_div, CDNS_UFS_REG_HCLKDIV);
/**
* Make sure the register was updated,
* UniPro layer will not work with an incorrect value.
*/
ufshcd_readl(hba, CDNS_UFS_REG_HCLKDIV);
return 0;
}
/**
* cdns_ufs_hce_enable_notify() - set HCLKDIV register
* @hba: host controller instance
* @status: notify stage (pre, post change)
*
* Return: zero for success and non-zero for failure.
*/
static int cdns_ufs_hce_enable_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
if (status != PRE_CHANGE)
return 0;
return cdns_ufs_set_hclkdiv(hba);
}
/**
* cdns_ufs_hibern8_notify() - save and restore L4 attributes.
* @hba: host controller instance
* @cmd: UIC Command
* @status: notify stage (pre, post change)
*/
static void cdns_ufs_hibern8_notify(struct ufs_hba *hba, enum uic_cmd_dme cmd,
enum ufs_notify_change_status status)
{
if (status == PRE_CHANGE && cmd == UIC_CMD_DME_HIBER_ENTER)
cdns_ufs_get_l4_attr(hba);
if (status == POST_CHANGE && cmd == UIC_CMD_DME_HIBER_EXIT)
cdns_ufs_set_l4_attr(hba);
}
/**
* cdns_ufs_link_startup_notify() - handle link startup.
* @hba: host controller instance
* @status: notify stage (pre, post change)
*
* Return: zero for success and non-zero for failure.
*/
static int cdns_ufs_link_startup_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
if (status != PRE_CHANGE)
return 0;
/*
* Some UFS devices have issues if LCC is enabled.
* So we are setting PA_Local_TX_LCC_Enable to 0
* before link startup which will make sure that both host
* and device TX LCC are disabled once link startup is
* completed.
*/
ufshcd_disable_host_tx_lcc(hba);
/*
* Disabling Autohibern8 feature in cadence UFS
* to mask unexpected interrupt trigger.
*/
hba->ahit = 0;
return 0;
}
/**
* cdns_ufs_init - performs additional ufs initialization
* @hba: host controller instance
*
* Return: status of initialization.
*/
static int cdns_ufs_init(struct ufs_hba *hba)
{
int status = 0;
struct cdns_ufs_host *host;
struct device *dev = hba->dev;
host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
if (!host)
return -ENOMEM;
ufshcd_set_variant(hba, host);
status = ufshcd_vops_phy_initialization(hba);
return status;
}
/**
* cdns_ufs_m31_16nm_phy_initialization - performs m31 phy initialization
* @hba: host controller instance
*
* Return: 0 (success).
*/
static int cdns_ufs_m31_16nm_phy_initialization(struct ufs_hba *hba)
{
u32 data;
/* Increase RX_Advanced_Min_ActivateTime_Capability */
data = ufshcd_readl(hba, CDNS_UFS_REG_PHY_XCFGD1);
data |= BIT(24);
ufshcd_writel(hba, data, CDNS_UFS_REG_PHY_XCFGD1);
return 0;
}
static const struct ufs_hba_variant_ops cdns_ufs_pltfm_hba_vops = {
.name = "cdns-ufs-pltfm",
.init = cdns_ufs_init,
.hce_enable_notify = cdns_ufs_hce_enable_notify,
.link_startup_notify = cdns_ufs_link_startup_notify,
.hibern8_notify = cdns_ufs_hibern8_notify,
};
static const struct ufs_hba_variant_ops cdns_ufs_m31_16nm_pltfm_hba_vops = {
.name = "cdns-ufs-pltfm",
.init = cdns_ufs_init,
.hce_enable_notify = cdns_ufs_hce_enable_notify,
.link_startup_notify = cdns_ufs_link_startup_notify,
.phy_initialization = cdns_ufs_m31_16nm_phy_initialization,
.hibern8_notify = cdns_ufs_hibern8_notify,
};
static const struct of_device_id cdns_ufs_of_match[] = {
{
.compatible = "cdns,ufshc",
.data = &cdns_ufs_pltfm_hba_vops,
},
{
.compatible = "cdns,ufshc-m31-16nm",
.data = &cdns_ufs_m31_16nm_pltfm_hba_vops,
},
{ },
};
MODULE_DEVICE_TABLE(of, cdns_ufs_of_match);
/**
* cdns_ufs_pltfrm_probe - probe routine of the driver
* @pdev: pointer to platform device handle
*
* Return: zero for success and non-zero for failure.
*/
static int cdns_ufs_pltfrm_probe(struct platform_device *pdev)
{
int err;
const struct of_device_id *of_id;
struct ufs_hba_variant_ops *vops;
struct device *dev = &pdev->dev;
of_id = of_match_node(cdns_ufs_of_match, dev->of_node);
vops = (struct ufs_hba_variant_ops *)of_id->data;
/* Perform generic probe */
err = ufshcd_pltfrm_init(pdev, vops);
if (err)
dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err);
return err;
}
/**
* cdns_ufs_pltfrm_remove - removes the ufs driver
* @pdev: pointer to platform device handle
*
* Return: 0 (success).
*/
static void cdns_ufs_pltfrm_remove(struct platform_device *pdev)
{
ufshcd_pltfrm_remove(pdev);
}
static const struct dev_pm_ops cdns_ufs_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
.prepare = ufshcd_suspend_prepare,
.complete = ufshcd_resume_complete,
};
static struct platform_driver cdns_ufs_pltfrm_driver = {
.probe = cdns_ufs_pltfrm_probe,
.remove = cdns_ufs_pltfrm_remove,
.driver = {
.name = "cdns-ufshcd",
.pm = &cdns_ufs_dev_pm_ops,
.of_match_table = cdns_ufs_of_match,
},
};
module_platform_driver(cdns_ufs_pltfrm_driver);
MODULE_AUTHOR("Jan Kotas <jank@cadence.com>");
MODULE_DESCRIPTION("Cadence UFS host controller platform driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Synopsys G210 Test Chip driver
*
* Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
*
* Authors: Joao Pinto <jpinto@synopsys.com>
*/
#include <ufs/ufshcd.h>
#include "ufshcd-dwc.h"
#include "tc-dwc-g210.h"
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
/* Test Chip type expected values */
#define TC_G210_20BIT 20
#define TC_G210_40BIT 40
#define TC_G210_INV 0
static int tc_type = TC_G210_INV;
module_param(tc_type, int, 0);
MODULE_PARM_DESC(tc_type, "Test Chip Type (20 = 20-bit, 40 = 40-bit)");
/*
* struct ufs_hba_dwc_vops - UFS DWC specific variant operations
*/
static struct ufs_hba_variant_ops tc_dwc_g210_pci_hba_vops = {
.name = "tc-dwc-g210-pci",
.link_startup_notify = ufshcd_dwc_link_startup_notify,
};
/**
* tc_dwc_g210_pci_remove - de-allocate PCI/SCSI host and host memory space
* data structure memory
* @pdev: pointer to PCI handle
*/
static void tc_dwc_g210_pci_remove(struct pci_dev *pdev)
{
struct ufs_hba *hba = pci_get_drvdata(pdev);
pm_runtime_forbid(&pdev->dev);
pm_runtime_get_noresume(&pdev->dev);
ufshcd_remove(hba);
}
/**
* tc_dwc_g210_pci_probe - probe routine of the driver
* @pdev: pointer to PCI device handle
* @id: PCI device id
*
* Return: 0 on success, non-zero value on failure.
*/
static int
tc_dwc_g210_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct ufs_hba *hba;
void __iomem *mmio_base;
int err;
/* Check Test Chip type and set the specific setup routine */
if (tc_type == TC_G210_20BIT) {
tc_dwc_g210_pci_hba_vops.phy_initialization =
tc_dwc_g210_config_20_bit;
} else if (tc_type == TC_G210_40BIT) {
tc_dwc_g210_pci_hba_vops.phy_initialization =
tc_dwc_g210_config_40_bit;
} else {
dev_err(&pdev->dev, "test chip version not specified\n");
return -EPERM;
}
err = pcim_enable_device(pdev);
if (err) {
dev_err(&pdev->dev, "pcim_enable_device failed\n");
return err;
}
pci_set_master(pdev);
mmio_base = pcim_iomap_region(pdev, 0, UFSHCD);
if (IS_ERR(mmio_base)) {
dev_err(&pdev->dev, "request and iomap failed\n");
return PTR_ERR(mmio_base);
}
err = ufshcd_alloc_host(&pdev->dev, &hba);
if (err) {
dev_err(&pdev->dev, "Allocation failed\n");
return err;
}
hba->vops = &tc_dwc_g210_pci_hba_vops;
err = ufshcd_init(hba, mmio_base, pdev->irq);
if (err) {
dev_err(&pdev->dev, "Initialization failed\n");
return err;
}
pm_runtime_put_noidle(&pdev->dev);
pm_runtime_allow(&pdev->dev);
return 0;
}
static const struct dev_pm_ops tc_dwc_g210_pci_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
.prepare = ufshcd_suspend_prepare,
.complete = ufshcd_resume_complete,
};
static const struct pci_device_id tc_dwc_g210_pci_tbl[] = {
{ PCI_VDEVICE(SYNOPSYS, 0xB101) },
{ PCI_VDEVICE(SYNOPSYS, 0xB102) },
{ } /* terminate list */
};
MODULE_DEVICE_TABLE(pci, tc_dwc_g210_pci_tbl);
static struct pci_driver tc_dwc_g210_pci_driver = {
.name = "tc-dwc-g210-pci",
.id_table = tc_dwc_g210_pci_tbl,
.probe = tc_dwc_g210_pci_probe,
.remove = tc_dwc_g210_pci_remove,
.driver = {
.pm = &tc_dwc_g210_pci_pm_ops
},
};
module_pci_driver(tc_dwc_g210_pci_driver);
MODULE_AUTHOR("Joao Pinto <Joao.Pinto@synopsys.com>");
MODULE_DESCRIPTION("Synopsys Test Chip G210 PCI glue driver");
MODULE_LICENSE("Dual BSD/GPL");
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Synopsys G210 Test Chip driver
*
* Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
*
* Authors: Joao Pinto <jpinto@synopsys.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include "ufshcd-pltfrm.h"
#include "ufshcd-dwc.h"
#include "tc-dwc-g210.h"
/*
* UFS DWC specific variant operations
*/
static struct ufs_hba_variant_ops tc_dwc_g210_20bit_pltfm_hba_vops = {
.name = "tc-dwc-g210-pltfm",
.link_startup_notify = ufshcd_dwc_link_startup_notify,
.phy_initialization = tc_dwc_g210_config_20_bit,
};
static struct ufs_hba_variant_ops tc_dwc_g210_40bit_pltfm_hba_vops = {
.name = "tc-dwc-g210-pltfm",
.link_startup_notify = ufshcd_dwc_link_startup_notify,
.phy_initialization = tc_dwc_g210_config_40_bit,
};
static const struct of_device_id tc_dwc_g210_pltfm_match[] = {
{
.compatible = "snps,g210-tc-6.00-20bit",
.data = &tc_dwc_g210_20bit_pltfm_hba_vops,
},
{
.compatible = "snps,g210-tc-6.00-40bit",
.data = &tc_dwc_g210_40bit_pltfm_hba_vops,
},
{ },
};
MODULE_DEVICE_TABLE(of, tc_dwc_g210_pltfm_match);
/**
* tc_dwc_g210_pltfm_probe()
* @pdev: pointer to platform device structure
*
*/
static int tc_dwc_g210_pltfm_probe(struct platform_device *pdev)
{
int err;
const struct of_device_id *of_id;
struct ufs_hba_variant_ops *vops;
struct device *dev = &pdev->dev;
of_id = of_match_node(tc_dwc_g210_pltfm_match, dev->of_node);
vops = (struct ufs_hba_variant_ops *)of_id->data;
/* Perform generic probe */
err = ufshcd_pltfrm_init(pdev, vops);
if (err)
dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err);
return err;
}
/**
* tc_dwc_g210_pltfm_remove()
* @pdev: pointer to platform device structure
*
*/
static void tc_dwc_g210_pltfm_remove(struct platform_device *pdev)
{
ufshcd_pltfrm_remove(pdev);
}
static const struct dev_pm_ops tc_dwc_g210_pltfm_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
};
static struct platform_driver tc_dwc_g210_pltfm_driver = {
.probe = tc_dwc_g210_pltfm_probe,
.remove = tc_dwc_g210_pltfm_remove,
.driver = {
.name = "tc-dwc-g210-pltfm",
.pm = &tc_dwc_g210_pltfm_pm_ops,
.of_match_table = of_match_ptr(tc_dwc_g210_pltfm_match),
},
};
module_platform_driver(tc_dwc_g210_pltfm_driver);
MODULE_ALIAS("platform:tc-dwc-g210-pltfm");
MODULE_DESCRIPTION("Synopsys Test Chip G210 platform glue driver");
MODULE_AUTHOR("Joao Pinto <Joao.Pinto@synopsys.com>");
MODULE_LICENSE("Dual BSD/GPL");
+311
View File
@@ -0,0 +1,311 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Synopsys G210 Test Chip driver
*
* Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
*
* Authors: Joao Pinto <jpinto@synopsys.com>
*/
#include <linux/module.h>
#include <ufs/ufshcd.h>
#include <ufs/unipro.h>
#include "ufshcd-dwc.h"
#include "ufshci-dwc.h"
#include "tc-dwc-g210.h"
/**
* tc_dwc_g210_setup_40bit_rmmi() - configure 40-bit RMMI.
* @hba: Pointer to drivers structure
*
* Return: 0 on success or non-zero value on failure.
*/
static int tc_dwc_g210_setup_40bit_rmmi(struct ufs_hba *hba)
{
static const struct ufshcd_dme_attr_val setup_attrs[] = {
{ UIC_ARG_MIB(TX_GLOBALHIBERNATE), 0x00, DME_LOCAL },
{ UIC_ARG_MIB(REFCLKMODE), 0x01, DME_LOCAL },
{ UIC_ARG_MIB(CDIRECTCTRL6), 0x80, DME_LOCAL },
{ UIC_ARG_MIB(CBDIVFACTOR), 0x08, DME_LOCAL },
{ UIC_ARG_MIB(CBDCOCTRL5), 0x64, DME_LOCAL },
{ UIC_ARG_MIB(CBPRGTUNING), 0x09, DME_LOCAL },
{ UIC_ARG_MIB(RTOBSERVESELECT), 0x00, DME_LOCAL },
{ UIC_ARG_MIB_SEL(TX_REFCLKFREQ, SELIND_LN0_TX), 0x01,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(TX_CFGCLKFREQVAL, SELIND_LN0_TX), 0x19,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGEXTRATTR, SELIND_LN0_TX), 0x14,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(DITHERCTRL2, SELIND_LN0_TX), 0xd6,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RX_REFCLKFREQ, SELIND_LN0_RX), 0x01,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RX_CFGCLKFREQVAL, SELIND_LN0_RX), 0x19,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGWIDEINLN, SELIND_LN0_RX), 4,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXCDR8, SELIND_LN0_RX), 0x80,
DME_LOCAL },
{ UIC_ARG_MIB(DIRECTCTRL10), 0x04, DME_LOCAL },
{ UIC_ARG_MIB(DIRECTCTRL19), 0x02, DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXCDR8, SELIND_LN0_RX), 0x80,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(ENARXDIRECTCFG4, SELIND_LN0_RX), 0x03,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXOVR8, SELIND_LN0_RX), 0x16,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RXDIRECTCTRL2, SELIND_LN0_RX), 0x42,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(ENARXDIRECTCFG3, SELIND_LN0_RX), 0xa4,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RXCALCTRL, SELIND_LN0_RX), 0x01,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(ENARXDIRECTCFG2, SELIND_LN0_RX), 0x01,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXOVR4, SELIND_LN0_RX), 0x28,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RXSQCTRL, SELIND_LN0_RX), 0x1E,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXOVR6, SELIND_LN0_RX), 0x2f,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXOVR6, SELIND_LN0_RX), 0x2f,
DME_LOCAL },
{ UIC_ARG_MIB(CBPRGPLL2), 0x00, DME_LOCAL },
};
return ufshcd_dwc_dme_set_attrs(hba, setup_attrs,
ARRAY_SIZE(setup_attrs));
}
/**
* tc_dwc_g210_setup_20bit_rmmi_lane0() - configure 20-bit RMMI Lane 0.
* @hba: Pointer to drivers structure
*
* Return: 0 on success or non-zero value on failure.
*/
static int tc_dwc_g210_setup_20bit_rmmi_lane0(struct ufs_hba *hba)
{
static const struct ufshcd_dme_attr_val setup_attrs[] = {
{ UIC_ARG_MIB_SEL(TX_REFCLKFREQ, SELIND_LN0_TX), 0x01,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(TX_CFGCLKFREQVAL, SELIND_LN0_TX), 0x19,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RX_CFGCLKFREQVAL, SELIND_LN0_RX), 0x19,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGEXTRATTR, SELIND_LN0_TX), 0x12,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(DITHERCTRL2, SELIND_LN0_TX), 0xd6,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RX_REFCLKFREQ, SELIND_LN0_RX), 0x01,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGWIDEINLN, SELIND_LN0_RX), 2,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXCDR8, SELIND_LN0_RX), 0x80,
DME_LOCAL },
{ UIC_ARG_MIB(DIRECTCTRL10), 0x04, DME_LOCAL },
{ UIC_ARG_MIB(DIRECTCTRL19), 0x02, DME_LOCAL },
{ UIC_ARG_MIB_SEL(ENARXDIRECTCFG4, SELIND_LN0_RX), 0x03,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXOVR8, SELIND_LN0_RX), 0x16,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RXDIRECTCTRL2, SELIND_LN0_RX), 0x42,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(ENARXDIRECTCFG3, SELIND_LN0_RX), 0xa4,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RXCALCTRL, SELIND_LN0_RX), 0x01,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(ENARXDIRECTCFG2, SELIND_LN0_RX), 0x01,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXOVR4, SELIND_LN0_RX), 0x28,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RXSQCTRL, SELIND_LN0_RX), 0x1E,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXOVR6, SELIND_LN0_RX), 0x2f,
DME_LOCAL },
{ UIC_ARG_MIB(CBPRGPLL2), 0x00, DME_LOCAL },
};
return ufshcd_dwc_dme_set_attrs(hba, setup_attrs,
ARRAY_SIZE(setup_attrs));
}
/**
* tc_dwc_g210_setup_20bit_rmmi_lane1() - configure 20-bit RMMI Lane 1.
* @hba: Pointer to drivers structure
*
* Return: 0 on success or non-zero value on failure.
*/
static int tc_dwc_g210_setup_20bit_rmmi_lane1(struct ufs_hba *hba)
{
int connected_rx_lanes = 0;
int connected_tx_lanes = 0;
int ret = 0;
static const struct ufshcd_dme_attr_val setup_tx_attrs[] = {
{ UIC_ARG_MIB_SEL(TX_REFCLKFREQ, SELIND_LN1_TX), 0x0d,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(TX_CFGCLKFREQVAL, SELIND_LN1_TX), 0x19,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGEXTRATTR, SELIND_LN1_TX), 0x12,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(DITHERCTRL2, SELIND_LN0_TX), 0xd6,
DME_LOCAL },
};
static const struct ufshcd_dme_attr_val setup_rx_attrs[] = {
{ UIC_ARG_MIB_SEL(RX_REFCLKFREQ, SELIND_LN1_RX), 0x01,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RX_CFGCLKFREQVAL, SELIND_LN1_RX), 0x19,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGWIDEINLN, SELIND_LN1_RX), 2,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXCDR8, SELIND_LN1_RX), 0x80,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(ENARXDIRECTCFG4, SELIND_LN1_RX), 0x03,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXOVR8, SELIND_LN1_RX), 0x16,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RXDIRECTCTRL2, SELIND_LN1_RX), 0x42,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(ENARXDIRECTCFG3, SELIND_LN1_RX), 0xa4,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RXCALCTRL, SELIND_LN1_RX), 0x01,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(ENARXDIRECTCFG2, SELIND_LN1_RX), 0x01,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXOVR4, SELIND_LN1_RX), 0x28,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(RXSQCTRL, SELIND_LN1_RX), 0x1E,
DME_LOCAL },
{ UIC_ARG_MIB_SEL(CFGRXOVR6, SELIND_LN1_RX), 0x2f,
DME_LOCAL },
};
/* Get the available lane count */
ufshcd_dme_get(hba, UIC_ARG_MIB(PA_AVAILRXDATALANES),
&connected_rx_lanes);
ufshcd_dme_get(hba, UIC_ARG_MIB(PA_AVAILTXDATALANES),
&connected_tx_lanes);
if (connected_tx_lanes == 2) {
ret = ufshcd_dwc_dme_set_attrs(hba, setup_tx_attrs,
ARRAY_SIZE(setup_tx_attrs));
if (ret)
goto out;
}
if (connected_rx_lanes == 2) {
ret = ufshcd_dwc_dme_set_attrs(hba, setup_rx_attrs,
ARRAY_SIZE(setup_rx_attrs));
}
out:
return ret;
}
/**
* tc_dwc_g210_setup_20bit_rmmi() - configure 20-bit RMMI.
* @hba: Pointer to drivers structure
*
* Return: 0 on success or non-zero value on failure.
*/
static int tc_dwc_g210_setup_20bit_rmmi(struct ufs_hba *hba)
{
int ret = 0;
static const struct ufshcd_dme_attr_val setup_attrs[] = {
{ UIC_ARG_MIB(TX_GLOBALHIBERNATE), 0x00, DME_LOCAL },
{ UIC_ARG_MIB(REFCLKMODE), 0x01, DME_LOCAL },
{ UIC_ARG_MIB(CDIRECTCTRL6), 0xc0, DME_LOCAL },
{ UIC_ARG_MIB(CBDIVFACTOR), 0x44, DME_LOCAL },
{ UIC_ARG_MIB(CBDCOCTRL5), 0x64, DME_LOCAL },
{ UIC_ARG_MIB(CBPRGTUNING), 0x09, DME_LOCAL },
{ UIC_ARG_MIB(RTOBSERVESELECT), 0x00, DME_LOCAL },
};
ret = ufshcd_dwc_dme_set_attrs(hba, setup_attrs,
ARRAY_SIZE(setup_attrs));
if (ret)
goto out;
/* Lane 0 configuration*/
ret = tc_dwc_g210_setup_20bit_rmmi_lane0(hba);
if (ret)
goto out;
/* Lane 1 configuration*/
ret = tc_dwc_g210_setup_20bit_rmmi_lane1(hba);
if (ret)
goto out;
out:
return ret;
}
/**
* tc_dwc_g210_config_40_bit() - configure 40-bit TC specific attributes.
* @hba: Pointer to drivers structure
*
* Return: 0 on success non-zero value on failure.
*/
int tc_dwc_g210_config_40_bit(struct ufs_hba *hba)
{
int ret = 0;
dev_info(hba->dev, "Configuring Test Chip 40-bit RMMI\n");
ret = tc_dwc_g210_setup_40bit_rmmi(hba);
if (ret) {
dev_err(hba->dev, "Configuration failed\n");
goto out;
}
/* To write Shadow register bank to effective configuration block */
ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01);
if (ret)
goto out;
/* To configure Debug OMC */
ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_DEBUGOMC), 0x01);
out:
return ret;
}
EXPORT_SYMBOL(tc_dwc_g210_config_40_bit);
/**
* tc_dwc_g210_config_20_bit() - configure 20-bit TC specific attributes.
* @hba: Pointer to drivers structure
*
* Return: 0 on success non-zero value on failure.
*/
int tc_dwc_g210_config_20_bit(struct ufs_hba *hba)
{
int ret = 0;
dev_info(hba->dev, "Configuring Test Chip 20-bit RMMI\n");
ret = tc_dwc_g210_setup_20bit_rmmi(hba);
if (ret) {
dev_err(hba->dev, "Configuration failed\n");
goto out;
}
/* To write Shadow register bank to effective configuration block */
ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01);
if (ret)
goto out;
/* To configure Debug OMC */
ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_DEBUGOMC), 0x01);
out:
return ret;
}
EXPORT_SYMBOL(tc_dwc_g210_config_20_bit);
MODULE_AUTHOR("Joao Pinto <Joao.Pinto@synopsys.com>");
MODULE_DESCRIPTION("Synopsys G210 Test Chip driver");
MODULE_LICENSE("Dual BSD/GPL");
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Synopsys G210 Test Chip driver
*
* Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
*
* Authors: Joao Pinto <jpinto@synopsys.com>
*/
#ifndef _TC_DWC_G210_H
#define _TC_DWC_G210_H
struct ufs_hba;
int tc_dwc_g210_config_40_bit(struct ufs_hba *hba);
int tc_dwc_g210_config_20_bit(struct ufs_hba *hba);
#endif /* End of Header */
+117
View File
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
//
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#define TI_UFS_SS_CTRL 0x4
#define TI_UFS_SS_RST_N_PCS BIT(0)
#define TI_UFS_SS_CLK_26MHZ BIT(4)
struct ti_j721e_ufs {
void __iomem *regbase;
u32 reg;
};
static int ti_j721e_ufs_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ti_j721e_ufs *ufs;
unsigned long clk_rate;
struct clk *clk;
int ret;
ufs = devm_kzalloc(dev, sizeof(*ufs), GFP_KERNEL);
if (!ufs)
return -ENOMEM;
ufs->regbase = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ufs->regbase))
return PTR_ERR(ufs->regbase);
pm_runtime_enable(dev);
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
goto disable_pm;
/* Select MPHY refclk frequency */
clk = devm_clk_get(dev, NULL);
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
dev_err(dev, "Cannot claim MPHY clock.\n");
goto clk_err;
}
clk_rate = clk_get_rate(clk);
if (clk_rate == 26000000)
ufs->reg |= TI_UFS_SS_CLK_26MHZ;
devm_clk_put(dev, clk);
/* Take UFS slave device out of reset */
ufs->reg |= TI_UFS_SS_RST_N_PCS;
writel(ufs->reg, ufs->regbase + TI_UFS_SS_CTRL);
dev_set_drvdata(dev, ufs);
ret = of_platform_populate(pdev->dev.of_node, NULL, NULL,
dev);
if (ret) {
dev_err(dev, "failed to populate child nodes %d\n", ret);
goto clk_err;
}
return ret;
clk_err:
pm_runtime_put_sync(dev);
disable_pm:
pm_runtime_disable(dev);
return ret;
}
static void ti_j721e_ufs_remove(struct platform_device *pdev)
{
of_platform_depopulate(&pdev->dev);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
}
static int ti_j721e_ufs_resume(struct device *dev)
{
struct ti_j721e_ufs *ufs = dev_get_drvdata(dev);
writel(ufs->reg, ufs->regbase + TI_UFS_SS_CTRL);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(ti_j721e_ufs_pm_ops, NULL, ti_j721e_ufs_resume);
static const struct of_device_id ti_j721e_ufs_of_match[] = {
{
.compatible = "ti,j721e-ufs",
},
{ },
};
MODULE_DEVICE_TABLE(of, ti_j721e_ufs_of_match);
static struct platform_driver ti_j721e_ufs_driver = {
.probe = ti_j721e_ufs_probe,
.remove = ti_j721e_ufs_remove,
.driver = {
.name = "ti-j721e-ufs",
.of_match_table = ti_j721e_ufs_of_match,
.pm = pm_sleep_ptr(&ti_j721e_ufs_pm_ops),
},
};
module_platform_driver(ti_j721e_ufs_driver);
MODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>");
MODULE_DESCRIPTION("TI UFS host controller glue driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,561 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*
* Authors: Sai Krishna Potthuri <sai.krishna.potthuri@amd.com>
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/firmware/xlnx-zynqmp.h>
#include <linux/irqreturn.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <ufs/unipro.h>
#include "ufshcd-dwc.h"
#include "ufshcd-pltfrm.h"
#include "ufshci-dwc.h"
/* PHY modes */
#define UFSHCD_DWC_PHY_MODE_ROM 0
#define MPHY_FAST_RX_AFE_CAL BIT(2)
#define MPHY_FW_CALIB_CFG_VAL BIT(8)
#define MPHY_RX_OVRD_EN BIT(3)
#define MPHY_RX_OVRD_VAL BIT(2)
#define MPHY_RX_ACK_MASK BIT(0)
#define TIMEOUT_MICROSEC 1000000
struct ufs_versal2_host {
struct ufs_hba *hba;
struct reset_control *rstc;
struct reset_control *rstphy;
u32 phy_mode;
unsigned long host_clk;
u8 attcompval0;
u8 attcompval1;
u8 ctlecompval0;
u8 ctlecompval1;
};
static int ufs_versal2_phy_reg_write(struct ufs_hba *hba, u32 addr, u32 val)
{
static struct ufshcd_dme_attr_val phy_write_attrs[] = {
{ UIC_ARG_MIB(CBCREGADDRLSB), 0, DME_LOCAL },
{ UIC_ARG_MIB(CBCREGADDRMSB), 0, DME_LOCAL },
{ UIC_ARG_MIB(CBCREGWRLSB), 0, DME_LOCAL },
{ UIC_ARG_MIB(CBCREGWRMSB), 0, DME_LOCAL },
{ UIC_ARG_MIB(CBCREGRDWRSEL), 1, DME_LOCAL },
{ UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
};
phy_write_attrs[0].mib_val = (u8)addr;
phy_write_attrs[1].mib_val = (u8)(addr >> 8);
phy_write_attrs[2].mib_val = (u8)val;
phy_write_attrs[3].mib_val = (u8)(val >> 8);
return ufshcd_dwc_dme_set_attrs(hba, phy_write_attrs, ARRAY_SIZE(phy_write_attrs));
}
static int ufs_versal2_phy_reg_read(struct ufs_hba *hba, u32 addr, u32 *val)
{
u32 mib_val;
int ret;
static struct ufshcd_dme_attr_val phy_read_attrs[] = {
{ UIC_ARG_MIB(CBCREGADDRLSB), 0, DME_LOCAL },
{ UIC_ARG_MIB(CBCREGADDRMSB), 0, DME_LOCAL },
{ UIC_ARG_MIB(CBCREGRDWRSEL), 0, DME_LOCAL },
{ UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
};
phy_read_attrs[0].mib_val = (u8)addr;
phy_read_attrs[1].mib_val = (u8)(addr >> 8);
ret = ufshcd_dwc_dme_set_attrs(hba, phy_read_attrs, ARRAY_SIZE(phy_read_attrs));
if (ret)
return ret;
ret = ufshcd_dme_get(hba, UIC_ARG_MIB(CBCREGRDLSB), &mib_val);
if (ret)
return ret;
*val = mib_val;
ret = ufshcd_dme_get(hba, UIC_ARG_MIB(CBCREGRDMSB), &mib_val);
if (ret)
return ret;
*val |= (mib_val << 8);
return 0;
}
static int ufs_versal2_enable_phy(struct ufs_hba *hba)
{
u32 offset, reg;
int ret;
ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYDISABLE), 0);
if (ret)
return ret;
ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 1);
if (ret)
return ret;
/* Check Tx/Rx FSM states */
for (offset = 0; offset < 2; offset++) {
u32 time_left, mibsel;
time_left = TIMEOUT_MICROSEC;
mibsel = UIC_ARG_MIB_SEL(MTX_FSM_STATE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(offset));
do {
ret = ufshcd_dme_get(hba, mibsel, &reg);
if (ret)
return ret;
if (reg == TX_STATE_HIBERN8 || reg == TX_STATE_SLEEP ||
reg == TX_STATE_LSBURST)
break;
time_left--;
usleep_range(1, 5);
} while (time_left);
if (!time_left) {
dev_err(hba->dev, "Invalid Tx FSM state.\n");
return -ETIMEDOUT;
}
time_left = TIMEOUT_MICROSEC;
mibsel = UIC_ARG_MIB_SEL(MRX_FSM_STATE, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(offset));
do {
ret = ufshcd_dme_get(hba, mibsel, &reg);
if (ret)
return ret;
if (reg == RX_STATE_HIBERN8 || reg == RX_STATE_SLEEP ||
reg == RX_STATE_LSBURST)
break;
time_left--;
usleep_range(1, 5);
} while (time_left);
if (!time_left) {
dev_err(hba->dev, "Invalid Rx FSM state.\n");
return -ETIMEDOUT;
}
}
return 0;
}
static int ufs_versal2_setup_phy(struct ufs_hba *hba)
{
struct ufs_versal2_host *host = ufshcd_get_variant(hba);
int ret;
u32 reg;
/* Bypass RX-AFE offset calibrations (ATT/CTLE) */
ret = ufs_versal2_phy_reg_read(hba, FAST_FLAGS(0), &reg);
if (ret)
return ret;
reg |= MPHY_FAST_RX_AFE_CAL;
ret = ufs_versal2_phy_reg_write(hba, FAST_FLAGS(0), reg);
if (ret)
return ret;
ret = ufs_versal2_phy_reg_read(hba, FAST_FLAGS(1), &reg);
if (ret)
return ret;
reg |= MPHY_FAST_RX_AFE_CAL;
ret = ufs_versal2_phy_reg_write(hba, FAST_FLAGS(1), reg);
if (ret)
return ret;
/* Program ATT and CTLE compensation values */
if (host->attcompval0) {
ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(0), host->attcompval0);
if (ret)
return ret;
}
if (host->attcompval1) {
ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(1), host->attcompval1);
if (ret)
return ret;
}
if (host->ctlecompval0) {
ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(0), host->ctlecompval0);
if (ret)
return ret;
}
if (host->ctlecompval1) {
ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(1), host->ctlecompval1);
if (ret)
return ret;
}
ret = ufs_versal2_phy_reg_read(hba, FW_CALIB_CCFG(0), &reg);
if (ret)
return ret;
reg |= MPHY_FW_CALIB_CFG_VAL;
ret = ufs_versal2_phy_reg_write(hba, FW_CALIB_CCFG(0), reg);
if (ret)
return ret;
ret = ufs_versal2_phy_reg_read(hba, FW_CALIB_CCFG(1), &reg);
if (ret)
return ret;
reg |= MPHY_FW_CALIB_CFG_VAL;
return ufs_versal2_phy_reg_write(hba, FW_CALIB_CCFG(1), reg);
}
static int ufs_versal2_phy_init(struct ufs_hba *hba)
{
struct ufs_versal2_host *host = ufshcd_get_variant(hba);
u32 time_left;
bool is_ready;
int ret;
static const struct ufshcd_dme_attr_val rmmi_attrs[] = {
{ UIC_ARG_MIB(CBREFCLKCTRL2), CBREFREFCLK_GATE_OVR_EN, DME_LOCAL },
{ UIC_ARG_MIB(CBCRCTRL), 1, DME_LOCAL },
{ UIC_ARG_MIB(CBC10DIRECTCONF2), 1, DME_LOCAL },
{ UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
};
/* Wait for Tx/Rx config_rdy */
time_left = TIMEOUT_MICROSEC;
do {
time_left--;
ret = zynqmp_pm_is_mphy_tx_rx_config_ready(&is_ready);
if (ret)
return ret;
if (!is_ready)
break;
usleep_range(1, 5);
} while (time_left);
if (!time_left) {
dev_err(hba->dev, "Tx/Rx configuration signal busy.\n");
return -ETIMEDOUT;
}
ret = ufshcd_dwc_dme_set_attrs(hba, rmmi_attrs, ARRAY_SIZE(rmmi_attrs));
if (ret)
return ret;
ret = reset_control_deassert(host->rstphy);
if (ret) {
dev_err(hba->dev, "ufsphy reset deassert failed, err = %d\n", ret);
return ret;
}
/* Wait for SRAM init done */
time_left = TIMEOUT_MICROSEC;
do {
time_left--;
ret = zynqmp_pm_is_sram_init_done(&is_ready);
if (ret)
return ret;
if (is_ready)
break;
usleep_range(1, 5);
} while (time_left);
if (!time_left) {
dev_err(hba->dev, "SRAM initialization failed.\n");
return -ETIMEDOUT;
}
ret = ufs_versal2_setup_phy(hba);
if (ret)
return ret;
return ufs_versal2_enable_phy(hba);
}
static int ufs_versal2_init(struct ufs_hba *hba)
{
struct ufs_versal2_host *host;
struct device *dev = hba->dev;
struct ufs_clk_info *clki;
int ret;
u32 cal;
host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
if (!host)
return -ENOMEM;
host->hba = hba;
ufshcd_set_variant(hba, host);
host->phy_mode = UFSHCD_DWC_PHY_MODE_ROM;
list_for_each_entry(clki, &hba->clk_list_head, list) {
if (!strcmp(clki->name, "core"))
host->host_clk = clk_get_rate(clki->clk);
}
host->rstc = devm_reset_control_get_exclusive(dev, "host");
if (IS_ERR(host->rstc)) {
dev_err(dev, "failed to get reset ctrl: host\n");
return PTR_ERR(host->rstc);
}
host->rstphy = devm_reset_control_get_exclusive(dev, "phy");
if (IS_ERR(host->rstphy)) {
dev_err(dev, "failed to get reset ctrl: phy\n");
return PTR_ERR(host->rstphy);
}
ret = reset_control_assert(host->rstc);
if (ret) {
dev_err(hba->dev, "host reset assert failed, err = %d\n", ret);
return ret;
}
ret = reset_control_assert(host->rstphy);
if (ret) {
dev_err(hba->dev, "phy reset assert failed, err = %d\n", ret);
return ret;
}
ret = zynqmp_pm_set_sram_bypass();
if (ret) {
dev_err(dev, "Bypass SRAM interface failed, err = %d\n", ret);
return ret;
}
ret = reset_control_deassert(host->rstc);
if (ret)
dev_err(hba->dev, "host reset deassert failed, err = %d\n", ret);
ret = zynqmp_pm_get_ufs_calibration_values(&cal);
if (ret) {
dev_err(dev, "failed to read calibration values\n");
return ret;
}
host->attcompval0 = (u8)cal;
host->attcompval1 = (u8)(cal >> 8);
host->ctlecompval0 = (u8)(cal >> 16);
host->ctlecompval1 = (u8)(cal >> 24);
hba->quirks |= UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING;
return 0;
}
static int ufs_versal2_hce_enable_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
int ret = 0;
if (status == POST_CHANGE) {
ret = ufs_versal2_phy_init(hba);
if (ret)
dev_err(hba->dev, "Phy init failed (%d)\n", ret);
}
return ret;
}
static int ufs_versal2_link_startup_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
struct ufs_versal2_host *host = ufshcd_get_variant(hba);
int ret = 0;
switch (status) {
case PRE_CHANGE:
if (host->host_clk)
ufshcd_writel(hba, host->host_clk / 1000000, DWC_UFS_REG_HCLKDIV);
break;
case POST_CHANGE:
ret = ufshcd_dwc_link_startup_notify(hba, status);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int ufs_versal2_phy_ratesel(struct ufs_hba *hba, u32 activelanes, u32 rx_req)
{
u32 time_left, reg, lane;
int ret;
for (lane = 0; lane < activelanes; lane++) {
time_left = TIMEOUT_MICROSEC;
ret = ufs_versal2_phy_reg_read(hba, RX_OVRD_IN_1(lane), &reg);
if (ret)
return ret;
reg |= MPHY_RX_OVRD_EN;
if (rx_req)
reg |= MPHY_RX_OVRD_VAL;
else
reg &= ~MPHY_RX_OVRD_VAL;
ret = ufs_versal2_phy_reg_write(hba, RX_OVRD_IN_1(lane), reg);
if (ret)
return ret;
do {
ret = ufs_versal2_phy_reg_read(hba, RX_PCS_OUT(lane), &reg);
if (ret)
return ret;
reg &= MPHY_RX_ACK_MASK;
if (reg == rx_req)
break;
time_left--;
usleep_range(1, 5);
} while (time_left);
if (!time_left) {
dev_err(hba->dev, "Invalid Rx Ack value.\n");
return -ETIMEDOUT;
}
}
return 0;
}
static int ufs_versal2_pwr_change_notify(struct ufs_hba *hba, enum ufs_notify_change_status status,
struct ufs_pa_layer_attr *dev_req_params)
{
struct ufs_versal2_host *host = ufshcd_get_variant(hba);
u32 lane, reg, rate = 0;
int ret = 0;
if (status == PRE_CHANGE) {
/* If it is not a calibrated part, switch PWRMODE to SLOW_MODE */
if (!host->attcompval0 && !host->attcompval1 && !host->ctlecompval0 &&
!host->ctlecompval1) {
dev_req_params->pwr_rx = SLOW_MODE;
dev_req_params->pwr_tx = SLOW_MODE;
return 0;
}
if (dev_req_params->pwr_rx == SLOW_MODE || dev_req_params->pwr_rx == SLOWAUTO_MODE)
return 0;
if (dev_req_params->hs_rate == PA_HS_MODE_B)
rate = 1;
/* Select the rate */
ret = ufshcd_dme_set(hba, UIC_ARG_MIB(CBRATESEL), rate);
if (ret)
return ret;
ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 1);
if (ret)
return ret;
ret = ufs_versal2_phy_ratesel(hba, dev_req_params->lane_tx, 1);
if (ret)
return ret;
ret = ufs_versal2_phy_ratesel(hba, dev_req_params->lane_tx, 0);
if (ret)
return ret;
/* Remove rx_req override */
for (lane = 0; lane < dev_req_params->lane_tx; lane++) {
ret = ufs_versal2_phy_reg_read(hba, RX_OVRD_IN_1(lane), &reg);
if (ret)
return ret;
reg &= ~MPHY_RX_OVRD_EN;
ret = ufs_versal2_phy_reg_write(hba, RX_OVRD_IN_1(lane), reg);
if (ret)
return ret;
}
if (dev_req_params->lane_tx == UFS_LANE_2 && dev_req_params->lane_rx == UFS_LANE_2)
ret = ufshcd_dme_configure_adapt(hba, dev_req_params->gear_tx,
PA_INITIAL_ADAPT);
}
return ret;
}
static struct ufs_hba_variant_ops ufs_versal2_hba_vops = {
.name = "ufs-versal2-pltfm",
.init = ufs_versal2_init,
.link_startup_notify = ufs_versal2_link_startup_notify,
.hce_enable_notify = ufs_versal2_hce_enable_notify,
.pwr_change_notify = ufs_versal2_pwr_change_notify,
};
static const struct of_device_id ufs_versal2_pltfm_match[] = {
{
.compatible = "amd,versal2-ufs",
.data = &ufs_versal2_hba_vops,
},
{ },
};
MODULE_DEVICE_TABLE(of, ufs_versal2_pltfm_match);
static int ufs_versal2_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret;
/* Perform generic probe */
ret = ufshcd_pltfrm_init(pdev, &ufs_versal2_hba_vops);
if (ret)
dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", ret);
return ret;
}
static void ufs_versal2_remove(struct platform_device *pdev)
{
struct ufs_hba *hba = platform_get_drvdata(pdev);
pm_runtime_get_sync(&(pdev)->dev);
ufshcd_remove(hba);
}
static const struct dev_pm_ops ufs_versal2_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
};
static struct platform_driver ufs_versal2_pltfm = {
.probe = ufs_versal2_probe,
.remove = ufs_versal2_remove,
.driver = {
.name = "ufshcd-versal2",
.pm = &ufs_versal2_pm_ops,
.of_match_table = of_match_ptr(ufs_versal2_pltfm_match),
},
};
module_platform_driver(ufs_versal2_pltfm);
MODULE_AUTHOR("Sai Krishna Potthuri <sai.krishna.potthuri@amd.com>");
MODULE_DESCRIPTION("AMD Versal Gen 2 UFS Host Controller driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
+294
View File
@@ -0,0 +1,294 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* UFS Host Controller driver for Exynos specific extensions
*
* Copyright (C) 2014-2015 Samsung Electronics Co., Ltd.
*
*/
#ifndef _UFS_EXYNOS_H_
#define _UFS_EXYNOS_H_
/*
* Component registers
*/
#define COMP_CLK_PERIOD 0x44
/*
* UNIPRO registers
*/
#define UNIPRO_DBG_FORCE_DME_CTRL_STATE 0x150
/*
* MIBs for PA debug registers
*/
#define PA_DBG_CLK_PERIOD 0x9514
#define PA_DBG_TXPHY_CFGUPDT 0x9518
#define PA_DBG_RXPHY_CFGUPDT 0x9519
#define PA_DBG_MODE 0x9529
#define PA_DBG_SKIP_RESET_PHY 0x9539
#define PA_DBG_AUTOMODE_THLD 0x9536
#define PA_DBG_OV_TM 0x9540
#define PA_DBG_SKIP_LINE_RESET 0x9541
#define PA_DBG_LINE_RESET_REQ 0x9543
#define PA_DBG_OPTION_SUITE 0x9564
#define PA_DBG_OPTION_SUITE_DYN 0x9565
/*
* Note: GS101_DBG_OPTION offsets below differ from the TRM
* but match the downstream driver. Following the TRM
* results in non-functioning UFS.
*/
#define PA_GS101_DBG_OPTION_SUITE1 0x956a
#define PA_GS101_DBG_OPTION_SUITE2 0x956d
/*
* MIBs for Transport Layer debug registers
*/
#define T_DBG_SKIP_INIT_HIBERN8_EXIT 0xc001
/*
* Exynos MPHY attributes
*/
#define TX_LINERESET_N_VAL 0x0277
#define TX_LINERESET_N(v) (((v) >> 10) & 0xFF)
#define TX_LINERESET_P_VAL 0x027D
#define TX_LINERESET_P(v) (((v) >> 12) & 0xFF)
#define TX_OV_SLEEP_CNT_TIMER 0x028E
#define TX_OV_H8_ENTER_EN (1 << 7)
#define TX_OV_SLEEP_CNT(v) (((v) >> 5) & 0x7F)
#define TX_HIGH_Z_CNT_11_08 0x028C
#define TX_HIGH_Z_CNT_H(v) (((v) >> 8) & 0xF)
#define TX_HIGH_Z_CNT_07_00 0x028D
#define TX_HIGH_Z_CNT_L(v) ((v) & 0xFF)
#define TX_BASE_NVAL_07_00 0x0293
#define TX_BASE_NVAL_L(v) ((v) & 0xFF)
#define TX_BASE_NVAL_15_08 0x0294
#define TX_BASE_NVAL_H(v) (((v) >> 8) & 0xFF)
#define TX_GRAN_NVAL_07_00 0x0295
#define TX_GRAN_NVAL_L(v) ((v) & 0xFF)
#define TX_GRAN_NVAL_10_08 0x0296
#define TX_GRAN_NVAL_H(v) (((v) >> 8) & 0x3)
#define VND_TX_CLK_PRD 0xAA
#define VND_TX_CLK_PRD_EN 0xA9
#define VND_TX_LINERESET_PVALUE0 0xAD
#define VND_TX_LINERESET_PVALUE1 0xAC
#define VND_TX_LINERESET_PVALUE2 0xAB
#define TX_LINE_RESET_TIME 3200
#define VND_RX_CLK_PRD 0x12
#define VND_RX_CLK_PRD_EN 0x11
#define VND_RX_LINERESET_VALUE0 0x1D
#define VND_RX_LINERESET_VALUE1 0x1C
#define VND_RX_LINERESET_VALUE2 0x1B
#define RX_LINE_RESET_TIME 1000
#define RX_FILLER_ENABLE 0x0316
#define RX_FILLER_EN (1 << 1)
#define RX_LINERESET_VAL 0x0317
#define RX_LINERESET(v) (((v) >> 12) & 0xFF)
#define RX_LCC_IGNORE 0x0318
#define RX_SYNC_MASK_LENGTH 0x0321
#define RX_HIBERN8_WAIT_VAL_BIT_20_16 0x0331
#define RX_HIBERN8_WAIT_VAL_BIT_15_08 0x0332
#define RX_HIBERN8_WAIT_VAL_BIT_07_00 0x0333
#define RX_OV_SLEEP_CNT_TIMER 0x0340
#define RX_OV_SLEEP_CNT(v) (((v) >> 6) & 0x1F)
#define RX_OV_STALL_CNT_TIMER 0x0341
#define RX_OV_STALL_CNT(v) (((v) >> 4) & 0xFF)
#define RX_BASE_NVAL_07_00 0x0355
#define RX_BASE_NVAL_L(v) ((v) & 0xFF)
#define RX_BASE_NVAL_15_08 0x0354
#define RX_BASE_NVAL_H(v) (((v) >> 8) & 0xFF)
#define RX_GRAN_NVAL_07_00 0x0353
#define RX_GRAN_NVAL_L(v) ((v) & 0xFF)
#define RX_GRAN_NVAL_10_08 0x0352
#define RX_GRAN_NVAL_H(v) (((v) >> 8) & 0x3)
#define CMN_PWM_CLK_CTRL 0x0402
#define PWM_CLK_CTRL_MASK 0x3
#define IATOVAL_NSEC 20000 /* unit: ns */
#define UNIPRO_PCLK_PERIOD(ufs) (NSEC_PER_SEC / ufs->pclk_rate)
struct exynos_ufs;
/* vendor specific pre-defined parameters */
#define SLOW 1
#define FAST 2
#define RX_ADV_FINE_GRAN_SUP_EN 0x1
#define RX_ADV_FINE_GRAN_STEP_VAL 0x3
#define RX_ADV_MIN_ACTV_TIME_CAP 0x9
#define PA_GRANULARITY_VAL 0x6
#define PA_TACTIVATE_VAL 0x3
#define PA_HIBERN8TIME_VAL 0x20
#define PCLK_AVAIL_MIN 70000000
#define PCLK_AVAIL_MAX 267000000
struct exynos_ufs_uic_attr {
/* TX Attributes */
unsigned int tx_trailingclks;
unsigned int tx_dif_p_nsec;
unsigned int tx_dif_n_nsec;
unsigned int tx_high_z_cnt_nsec;
unsigned int tx_base_unit_nsec;
unsigned int tx_gran_unit_nsec;
unsigned int tx_sleep_cnt;
unsigned int tx_min_activatetime;
/* RX Attributes */
unsigned int rx_filler_enable;
unsigned int rx_dif_p_nsec;
unsigned int rx_hibern8_wait_nsec;
unsigned int rx_base_unit_nsec;
unsigned int rx_gran_unit_nsec;
unsigned int rx_sleep_cnt;
unsigned int rx_stall_cnt;
unsigned int rx_hs_g1_sync_len_cap;
unsigned int rx_hs_g2_sync_len_cap;
unsigned int rx_hs_g3_sync_len_cap;
unsigned int rx_hs_g1_prep_sync_len_cap;
unsigned int rx_hs_g2_prep_sync_len_cap;
unsigned int rx_hs_g3_prep_sync_len_cap;
/* Common Attributes */
unsigned int cmn_pwm_clk_ctrl;
/* Internal Attributes */
unsigned int pa_dbg_clk_period_off;
unsigned int pa_dbg_opt_suite1_val;
unsigned int pa_dbg_opt_suite1_off;
unsigned int pa_dbg_opt_suite2_val;
unsigned int pa_dbg_opt_suite2_off;
/* Changeable Attributes */
unsigned int rx_adv_fine_gran_sup_en;
unsigned int rx_adv_fine_gran_step;
unsigned int rx_min_actv_time_cap;
unsigned int rx_hibern8_time_cap;
unsigned int rx_adv_min_actv_time_cap;
unsigned int rx_adv_hibern8_time_cap;
unsigned int pa_granularity;
unsigned int pa_tactivate;
unsigned int pa_hibern8time;
};
struct exynos_ufs_drv_data {
const struct ufs_hba_variant_ops *vops;
struct exynos_ufs_uic_attr *uic_attr;
unsigned int quirks;
unsigned int opts;
u32 iocc_mask;
/* SoC's specific operations */
int (*drv_init)(struct exynos_ufs *ufs);
int (*pre_link)(struct exynos_ufs *ufs);
int (*post_link)(struct exynos_ufs *ufs);
int (*pre_pwr_change)(struct exynos_ufs *ufs,
struct ufs_pa_layer_attr *pwr);
int (*post_pwr_change)(struct exynos_ufs *ufs,
const struct ufs_pa_layer_attr *pwr);
int (*pre_hce_enable)(struct exynos_ufs *ufs);
int (*post_hce_enable)(struct exynos_ufs *ufs);
int (*suspend)(struct exynos_ufs *ufs);
};
struct ufs_phy_time_cfg {
u32 tx_linereset_p;
u32 tx_linereset_n;
u32 tx_high_z_cnt;
u32 tx_base_n_val;
u32 tx_gran_n_val;
u32 tx_sleep_cnt;
u32 rx_linereset;
u32 rx_hibern8_wait;
u32 rx_base_n_val;
u32 rx_gran_n_val;
u32 rx_sleep_cnt;
u32 rx_stall_cnt;
};
struct exynos_ufs {
struct ufs_hba *hba;
struct phy *phy;
void __iomem *reg_hci;
void __iomem *reg_unipro;
void __iomem *reg_ufsp;
struct clk *clk_hci_core;
struct clk *clk_unipro_main;
struct clk *clk_apb;
u32 pclk_rate;
u32 pclk_div;
u32 pclk_avail_min;
u32 pclk_avail_max;
unsigned long mclk_rate;
int avail_ln_rx;
int avail_ln_tx;
int rx_sel_idx;
struct ufs_pa_layer_attr dev_req_params;
struct ufs_phy_time_cfg t_cfg;
ktime_t entry_hibern8_t;
const struct exynos_ufs_drv_data *drv_data;
struct regmap *sysreg;
u32 iocc_offset;
u32 iocc_mask;
u32 iocc_val;
u32 opts;
#define EXYNOS_UFS_OPT_HAS_APB_CLK_CTRL BIT(0)
#define EXYNOS_UFS_OPT_SKIP_CONNECTION_ESTAB BIT(1)
#define EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL BIT(2)
#define EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX BIT(3)
#define EXYNOS_UFS_OPT_USE_SW_HIBERN8_TIMER BIT(4)
#define EXYNOS_UFS_OPT_SKIP_CONFIG_PHY_ATTR BIT(5)
#define EXYNOS_UFS_OPT_UFSPR_SECURE BIT(6)
#define EXYNOS_UFS_OPT_TIMER_TICK_SELECT BIT(7)
};
#define for_each_ufs_rx_lane(ufs, i) \
for (i = (ufs)->rx_sel_idx; \
i < (ufs)->rx_sel_idx + (ufs)->avail_ln_rx; i++)
#define for_each_ufs_tx_lane(ufs, i) \
for (i = 0; i < (ufs)->avail_ln_tx; i++)
#define EXYNOS_UFS_MMIO_FUNC(name) \
static inline void name##_writel(struct exynos_ufs *ufs, u32 val, u32 reg)\
{ \
writel(val, ufs->reg_##name + reg); \
} \
\
static inline u32 name##_readl(struct exynos_ufs *ufs, u32 reg) \
{ \
return readl(ufs->reg_##name + reg); \
}
EXYNOS_UFS_MMIO_FUNC(hci);
EXYNOS_UFS_MMIO_FUNC(unipro);
EXYNOS_UFS_MMIO_FUNC(ufsp);
#undef EXYNOS_UFS_MMIO_FUNC
long exynos_ufs_calc_time_cntr(struct exynos_ufs *, long);
static inline void exynos_ufs_enable_ov_tm(struct ufs_hba *hba)
{
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OV_TM), true);
}
static inline void exynos_ufs_disable_ov_tm(struct ufs_hba *hba)
{
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OV_TM), false);
}
static inline void exynos_ufs_enable_dbg_mode(struct ufs_hba *hba)
{
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_MODE), true);
}
static inline void exynos_ufs_disable_dbg_mode(struct ufs_hba *hba)
{
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_MODE), false);
}
#endif /* _UFS_EXYNOS_H_ */
+605
View File
@@ -0,0 +1,605 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* HiSilicon Hixxxx UFS Driver
*
* Copyright (c) 2016-2017 Linaro Ltd.
* Copyright (c) 2016-2017 HiSilicon Technologies Co., Ltd.
*/
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <ufs/ufshcd.h>
#include "ufshcd-pltfrm.h"
#include <ufs/unipro.h>
#include "ufs-hisi.h"
#include <ufs/ufshci.h>
#include <ufs/ufs_quirks.h>
static int ufs_hisi_check_hibern8(struct ufs_hba *hba)
{
int err = 0;
u32 tx_fsm_val_0 = 0;
u32 tx_fsm_val_1 = 0;
unsigned long timeout = jiffies + msecs_to_jiffies(HBRN8_POLL_TOUT_MS);
do {
err = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, 0),
&tx_fsm_val_0);
err |= ufshcd_dme_get(hba,
UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, 1), &tx_fsm_val_1);
if (err || (tx_fsm_val_0 == TX_FSM_HIBERN8 &&
tx_fsm_val_1 == TX_FSM_HIBERN8))
break;
/* sleep for max. 200us */
usleep_range(100, 200);
} while (time_before(jiffies, timeout));
/*
* we might have scheduled out for long during polling so
* check the state again.
*/
if (time_after(jiffies, timeout)) {
err = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, 0),
&tx_fsm_val_0);
err |= ufshcd_dme_get(hba,
UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, 1), &tx_fsm_val_1);
}
if (err) {
dev_err(hba->dev, "%s: unable to get TX_FSM_STATE, err %d\n",
__func__, err);
} else if (tx_fsm_val_0 != TX_FSM_HIBERN8 ||
tx_fsm_val_1 != TX_FSM_HIBERN8) {
err = -1;
dev_err(hba->dev, "%s: invalid TX_FSM_STATE, lane0 = %d, lane1 = %d\n",
__func__, tx_fsm_val_0, tx_fsm_val_1);
}
return err;
}
static void ufs_hisi_clk_init(struct ufs_hba *hba)
{
struct ufs_hisi_host *host = ufshcd_get_variant(hba);
ufs_sys_ctrl_clr_bits(host, BIT_SYSCTRL_REF_CLOCK_EN, PHY_CLK_CTRL);
if (ufs_sys_ctrl_readl(host, PHY_CLK_CTRL) & BIT_SYSCTRL_REF_CLOCK_EN)
mdelay(1);
/* use abb clk */
ufs_sys_ctrl_clr_bits(host, BIT_UFS_REFCLK_SRC_SEl, UFS_SYSCTRL);
ufs_sys_ctrl_clr_bits(host, BIT_UFS_REFCLK_ISO_EN, PHY_ISO_EN);
/* open mphy ref clk */
ufs_sys_ctrl_set_bits(host, BIT_SYSCTRL_REF_CLOCK_EN, PHY_CLK_CTRL);
}
static void ufs_hisi_soc_init(struct ufs_hba *hba)
{
struct ufs_hisi_host *host = ufshcd_get_variant(hba);
u32 reg;
if (!IS_ERR(host->rst))
reset_control_assert(host->rst);
/* HC_PSW powerup */
ufs_sys_ctrl_set_bits(host, BIT_UFS_PSW_MTCMOS_EN, PSW_POWER_CTRL);
udelay(10);
/* notify PWR ready */
ufs_sys_ctrl_set_bits(host, BIT_SYSCTRL_PWR_READY, HC_LP_CTRL);
ufs_sys_ctrl_writel(host, MASK_UFS_DEVICE_RESET | 0,
UFS_DEVICE_RESET_CTRL);
reg = ufs_sys_ctrl_readl(host, PHY_CLK_CTRL);
reg = (reg & ~MASK_SYSCTRL_CFG_CLOCK_FREQ) | UFS_FREQ_CFG_CLK;
/* set cfg clk freq */
ufs_sys_ctrl_writel(host, reg, PHY_CLK_CTRL);
/* set ref clk freq */
ufs_sys_ctrl_clr_bits(host, MASK_SYSCTRL_REF_CLOCK_SEL, PHY_CLK_CTRL);
/* bypass ufs clk gate */
ufs_sys_ctrl_set_bits(host, MASK_UFS_CLK_GATE_BYPASS,
CLOCK_GATE_BYPASS);
ufs_sys_ctrl_set_bits(host, MASK_UFS_SYSCRTL_BYPASS, UFS_SYSCTRL);
/* open psw clk */
ufs_sys_ctrl_set_bits(host, BIT_SYSCTRL_PSW_CLK_EN, PSW_CLK_CTRL);
/* disable ufshc iso */
ufs_sys_ctrl_clr_bits(host, BIT_UFS_PSW_ISO_CTRL, PSW_POWER_CTRL);
/* disable phy iso */
ufs_sys_ctrl_clr_bits(host, BIT_UFS_PHY_ISO_CTRL, PHY_ISO_EN);
/* notice iso disable */
ufs_sys_ctrl_clr_bits(host, BIT_SYSCTRL_LP_ISOL_EN, HC_LP_CTRL);
/* disable lp_reset_n */
ufs_sys_ctrl_set_bits(host, BIT_SYSCTRL_LP_RESET_N, RESET_CTRL_EN);
mdelay(1);
ufs_sys_ctrl_writel(host, MASK_UFS_DEVICE_RESET | BIT_UFS_DEVICE_RESET,
UFS_DEVICE_RESET_CTRL);
msleep(20);
/*
* enable the fix of linereset recovery,
* and enable rx_reset/tx_rest beat
* enable ref_clk_en override(bit5) &
* override value = 1(bit4), with mask
*/
ufs_sys_ctrl_writel(host, 0x03300330, UFS_DEVICE_RESET_CTRL);
if (!IS_ERR(host->rst))
reset_control_deassert(host->rst);
}
static int ufs_hisi_link_startup_pre_change(struct ufs_hba *hba)
{
struct ufs_hisi_host *host = ufshcd_get_variant(hba);
int err;
uint32_t value;
uint32_t reg;
/* Unipro VS_mphy_disable */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD0C1, 0x0), 0x1);
/* PA_HSSeries */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x156A, 0x0), 0x2);
/* MPHY CBRATESEL */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8114, 0x0), 0x1);
/* MPHY CBOVRCTRL2 */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8121, 0x0), 0x2D);
/* MPHY CBOVRCTRL3 */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8122, 0x0), 0x1);
if (host->caps & UFS_HISI_CAP_PHY10nm) {
/* MPHY CBOVRCTRL4 */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8127, 0x0), 0x98);
/* MPHY CBOVRCTRL5 */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8128, 0x0), 0x1);
}
/* Unipro VS_MphyCfgUpdt */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD085, 0x0), 0x1);
/* MPHY RXOVRCTRL4 rx0 */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x800D, 0x4), 0x58);
/* MPHY RXOVRCTRL4 rx1 */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x800D, 0x5), 0x58);
/* MPHY RXOVRCTRL5 rx0 */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x800E, 0x4), 0xB);
/* MPHY RXOVRCTRL5 rx1 */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x800E, 0x5), 0xB);
/* MPHY RXSQCONTROL rx0 */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8009, 0x4), 0x1);
/* MPHY RXSQCONTROL rx1 */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8009, 0x5), 0x1);
/* Unipro VS_MphyCfgUpdt */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD085, 0x0), 0x1);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8113, 0x0), 0x1);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD085, 0x0), 0x1);
if (host->caps & UFS_HISI_CAP_PHY10nm) {
/* RX_Hibern8Time_Capability*/
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0092, 0x4), 0xA);
/* RX_Hibern8Time_Capability*/
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0092, 0x5), 0xA);
/* RX_Min_ActivateTime */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008f, 0x4), 0xA);
/* RX_Min_ActivateTime*/
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008f, 0x5), 0xA);
} else {
/* Tactive RX */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008F, 0x4), 0x7);
/* Tactive RX */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008F, 0x5), 0x7);
}
/* Gear3 Synclength */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0095, 0x4), 0x4F);
/* Gear3 Synclength */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0095, 0x5), 0x4F);
/* Gear2 Synclength */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0094, 0x4), 0x4F);
/* Gear2 Synclength */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0094, 0x5), 0x4F);
/* Gear1 Synclength */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008B, 0x4), 0x4F);
/* Gear1 Synclength */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008B, 0x5), 0x4F);
/* Thibernate Tx */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x000F, 0x0), 0x5);
/* Thibernate Tx */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x000F, 0x1), 0x5);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD085, 0x0), 0x1);
/* Unipro VS_mphy_disable */
ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(0xD0C1, 0x0), &value);
if (value != 0x1)
dev_info(hba->dev,
"Warring!!! Unipro VS_mphy_disable is 0x%x\n", value);
/* Unipro VS_mphy_disable */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD0C1, 0x0), 0x0);
err = ufs_hisi_check_hibern8(hba);
if (err)
dev_err(hba->dev, "ufs_hisi_check_hibern8 error\n");
if (!(host->caps & UFS_HISI_CAP_PHY10nm))
ufshcd_writel(hba, UFS_HCLKDIV_NORMAL_VALUE, UFS_REG_HCLKDIV);
/* disable auto H8 */
reg = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER);
reg = reg & (~UFS_AHIT_AH8ITV_MASK);
ufshcd_writel(hba, reg, REG_AUTO_HIBERNATE_IDLE_TIMER);
/* Unipro PA_Local_TX_LCC_Enable */
ufshcd_disable_host_tx_lcc(hba);
/* close Unipro VS_Mk2ExtnSupport */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD0AB, 0x0), 0x0);
ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(0xD0AB, 0x0), &value);
if (value != 0) {
/* Ensure close success */
dev_info(hba->dev, "WARN: close VS_Mk2ExtnSupport failed\n");
}
return err;
}
static int ufs_hisi_link_startup_post_change(struct ufs_hba *hba)
{
struct ufs_hisi_host *host = ufshcd_get_variant(hba);
/* Unipro DL_AFC0CreditThreshold */
ufshcd_dme_set(hba, UIC_ARG_MIB(0x2044), 0x0);
/* Unipro DL_TC0OutAckThreshold */
ufshcd_dme_set(hba, UIC_ARG_MIB(0x2045), 0x0);
/* Unipro DL_TC0TXFCThreshold */
ufshcd_dme_set(hba, UIC_ARG_MIB(0x2040), 0x9);
/* not bypass ufs clk gate */
ufs_sys_ctrl_clr_bits(host, MASK_UFS_CLK_GATE_BYPASS,
CLOCK_GATE_BYPASS);
ufs_sys_ctrl_clr_bits(host, MASK_UFS_SYSCRTL_BYPASS,
UFS_SYSCTRL);
/* select received symbol cnt */
ufshcd_dme_set(hba, UIC_ARG_MIB(0xd09a), 0x80000000);
/* reset counter0 and enable */
ufshcd_dme_set(hba, UIC_ARG_MIB(0xd09c), 0x00000005);
return 0;
}
static int ufs_hisi_link_startup_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
int err = 0;
switch (status) {
case PRE_CHANGE:
err = ufs_hisi_link_startup_pre_change(hba);
break;
case POST_CHANGE:
err = ufs_hisi_link_startup_post_change(hba);
break;
default:
break;
}
return err;
}
static void ufs_hisi_set_dev_cap(struct ufs_host_params *host_params)
{
ufshcd_init_host_params(host_params);
}
static int ufs_hisi_negotiate_pwr_mode(struct ufs_hba *hba,
const struct ufs_pa_layer_attr *dev_max_params,
struct ufs_pa_layer_attr *dev_req_params)
{
struct ufs_host_params host_params;
ufs_hisi_set_dev_cap(&host_params);
return ufshcd_negotiate_pwr_params(&host_params, dev_max_params, dev_req_params);
}
static void ufs_hisi_pwr_change_pre_change(struct ufs_hba *hba)
{
struct ufs_hisi_host *host = ufshcd_get_variant(hba);
if (host->caps & UFS_HISI_CAP_PHY10nm) {
/*
* Boston platform need to set SaveConfigTime to 0x13,
* and change sync length to maximum value
*/
/* VS_DebugSaveConfigTime */
ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0xD0A0), 0x13);
/* g1 sync length */
ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0x1552), 0x4f);
/* g2 sync length */
ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0x1554), 0x4f);
/* g3 sync length */
ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0x1556), 0x4f);
/* PA_Hibern8Time */
ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0x15a7), 0xA);
/* PA_Tactivate */
ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0x15a8), 0xA);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xd085, 0x0), 0x01);
}
if (hba->dev_quirks & UFS_DEVICE_QUIRK_HOST_VS_DEBUGSAVECONFIGTIME) {
pr_info("ufs flash device must set VS_DebugSaveConfigTime 0x10\n");
/* VS_DebugSaveConfigTime */
ufshcd_dme_set(hba, UIC_ARG_MIB(0xD0A0), 0x10);
/* sync length */
ufshcd_dme_set(hba, UIC_ARG_MIB(0x1556), 0x48);
}
/* update */
ufshcd_dme_set(hba, UIC_ARG_MIB(0x15A8), 0x1);
/* PA_TxSkip */
ufshcd_dme_set(hba, UIC_ARG_MIB(0x155c), 0x0);
/*PA_PWRModeUserData0 = 8191, default is 0*/
ufshcd_dme_set(hba, UIC_ARG_MIB(0x15b0), SZ_8K - 1);
/*PA_PWRModeUserData1 = 65535, default is 0*/
ufshcd_dme_set(hba, UIC_ARG_MIB(0x15b1), SZ_64K - 1);
/*PA_PWRModeUserData2 = 32767, default is 0*/
ufshcd_dme_set(hba, UIC_ARG_MIB(0x15b2), SZ_32K - 1);
/*DME_FC0ProtectionTimeOutVal = 8191, default is 0*/
ufshcd_dme_set(hba, UIC_ARG_MIB(0xd041), SZ_8K - 1);
/*DME_TC0ReplayTimeOutVal = 65535, default is 0*/
ufshcd_dme_set(hba, UIC_ARG_MIB(0xd042), SZ_64K - 1);
/*DME_AFC0ReqTimeOutVal = 32767, default is 0*/
ufshcd_dme_set(hba, UIC_ARG_MIB(0xd043), SZ_32K - 1);
/*PA_PWRModeUserData3 = 8191, default is 0*/
ufshcd_dme_set(hba, UIC_ARG_MIB(0x15b3), SZ_8K - 1);
/*PA_PWRModeUserData4 = 65535, default is 0*/
ufshcd_dme_set(hba, UIC_ARG_MIB(0x15b4), SZ_64K - 1);
/*PA_PWRModeUserData5 = 32767, default is 0*/
ufshcd_dme_set(hba, UIC_ARG_MIB(0x15b5), SZ_32K - 1);
/*DME_FC1ProtectionTimeOutVal = 8191, default is 0*/
ufshcd_dme_set(hba, UIC_ARG_MIB(0xd044), SZ_8K - 1);
/*DME_TC1ReplayTimeOutVal = 65535, default is 0*/
ufshcd_dme_set(hba, UIC_ARG_MIB(0xd045), SZ_64K - 1);
/*DME_AFC1ReqTimeOutVal = 32767, default is 0*/
ufshcd_dme_set(hba, UIC_ARG_MIB(0xd046), SZ_32K - 1);
}
static int ufs_hisi_pwr_change_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status,
struct ufs_pa_layer_attr *dev_req_params)
{
int ret = 0;
if (!dev_req_params) {
dev_err(hba->dev,
"%s: incoming dev_req_params is NULL\n", __func__);
ret = -EINVAL;
goto out;
}
switch (status) {
case PRE_CHANGE:
ufs_hisi_pwr_change_pre_change(hba);
break;
case POST_CHANGE:
break;
default:
ret = -EINVAL;
break;
}
out:
return ret;
}
static int ufs_hisi_suspend_prepare(struct device *dev)
{
/* RPM and SPM are different. Refer ufs_hisi_suspend() */
return __ufshcd_suspend_prepare(dev, false);
}
static int ufs_hisi_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op,
enum ufs_notify_change_status status)
{
struct ufs_hisi_host *host = ufshcd_get_variant(hba);
if (status == PRE_CHANGE)
return 0;
if (pm_op == UFS_RUNTIME_PM)
return 0;
if (host->in_suspend) {
WARN_ON(1);
return 0;
}
ufs_sys_ctrl_clr_bits(host, BIT_SYSCTRL_REF_CLOCK_EN, PHY_CLK_CTRL);
udelay(10);
/* set ref_dig_clk override of PHY PCS to 0 */
ufs_sys_ctrl_writel(host, 0x00100000, UFS_DEVICE_RESET_CTRL);
host->in_suspend = true;
return 0;
}
static int ufs_hisi_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
struct ufs_hisi_host *host = ufshcd_get_variant(hba);
if (!host->in_suspend)
return 0;
/* set ref_dig_clk override of PHY PCS to 1 */
ufs_sys_ctrl_writel(host, 0x00100010, UFS_DEVICE_RESET_CTRL);
udelay(10);
ufs_sys_ctrl_set_bits(host, BIT_SYSCTRL_REF_CLOCK_EN, PHY_CLK_CTRL);
host->in_suspend = false;
return 0;
}
static int ufs_hisi_get_resource(struct ufs_hisi_host *host)
{
struct device *dev = host->hba->dev;
struct platform_device *pdev = to_platform_device(dev);
/* get resource of ufs sys ctrl */
host->ufs_sys_ctrl = devm_platform_ioremap_resource(pdev, 1);
return PTR_ERR_OR_ZERO(host->ufs_sys_ctrl);
}
static void ufs_hisi_set_pm_lvl(struct ufs_hba *hba)
{
hba->rpm_lvl = UFS_PM_LVL_1;
hba->spm_lvl = UFS_PM_LVL_3;
}
/**
* ufs_hisi_init_common
* @hba: host controller instance
*/
static int ufs_hisi_init_common(struct ufs_hba *hba)
{
int err = 0;
struct device *dev = hba->dev;
struct ufs_hisi_host *host;
host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
if (!host)
return -ENOMEM;
host->hba = hba;
ufshcd_set_variant(hba, host);
host->rst = devm_reset_control_get(dev, "rst");
if (IS_ERR(host->rst)) {
dev_err(dev, "%s: failed to get reset control\n", __func__);
err = PTR_ERR(host->rst);
goto error;
}
ufs_hisi_set_pm_lvl(hba);
err = ufs_hisi_get_resource(host);
if (err)
goto error;
return 0;
error:
ufshcd_set_variant(hba, NULL);
return err;
}
static int ufs_hi3660_init(struct ufs_hba *hba)
{
int ret = 0;
struct device *dev = hba->dev;
ret = ufs_hisi_init_common(hba);
if (ret) {
dev_err(dev, "%s: ufs common init fail\n", __func__);
return ret;
}
ufs_hisi_clk_init(hba);
ufs_hisi_soc_init(hba);
return 0;
}
static int ufs_hi3670_init(struct ufs_hba *hba)
{
int ret = 0;
struct device *dev = hba->dev;
struct ufs_hisi_host *host;
ret = ufs_hisi_init_common(hba);
if (ret) {
dev_err(dev, "%s: ufs common init fail\n", __func__);
return ret;
}
ufs_hisi_clk_init(hba);
ufs_hisi_soc_init(hba);
/* Add cap for 10nm PHY variant on HI3670 SoC */
host = ufshcd_get_variant(hba);
host->caps |= UFS_HISI_CAP_PHY10nm;
return 0;
}
static const struct ufs_hba_variant_ops ufs_hba_hi3660_vops = {
.name = "hi3660",
.init = ufs_hi3660_init,
.link_startup_notify = ufs_hisi_link_startup_notify,
.negotiate_pwr_mode = ufs_hisi_negotiate_pwr_mode,
.pwr_change_notify = ufs_hisi_pwr_change_notify,
.suspend = ufs_hisi_suspend,
.resume = ufs_hisi_resume,
};
static const struct ufs_hba_variant_ops ufs_hba_hi3670_vops = {
.name = "hi3670",
.init = ufs_hi3670_init,
.link_startup_notify = ufs_hisi_link_startup_notify,
.negotiate_pwr_mode = ufs_hisi_negotiate_pwr_mode,
.pwr_change_notify = ufs_hisi_pwr_change_notify,
.suspend = ufs_hisi_suspend,
.resume = ufs_hisi_resume,
};
static const struct of_device_id ufs_hisi_of_match[] = {
{ .compatible = "hisilicon,hi3660-ufs", .data = &ufs_hba_hi3660_vops },
{ .compatible = "hisilicon,hi3670-ufs", .data = &ufs_hba_hi3670_vops },
{},
};
MODULE_DEVICE_TABLE(of, ufs_hisi_of_match);
static int ufs_hisi_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id;
of_id = of_match_node(ufs_hisi_of_match, pdev->dev.of_node);
return ufshcd_pltfrm_init(pdev, of_id->data);
}
static void ufs_hisi_remove(struct platform_device *pdev)
{
ufshcd_pltfrm_remove(pdev);
}
static const struct dev_pm_ops ufs_hisi_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
.prepare = ufs_hisi_suspend_prepare,
.complete = ufshcd_resume_complete,
};
static struct platform_driver ufs_hisi_pltform = {
.probe = ufs_hisi_probe,
.remove = ufs_hisi_remove,
.driver = {
.name = "ufshcd-hisi",
.pm = &ufs_hisi_pm_ops,
.of_match_table = ufs_hisi_of_match,
},
};
module_platform_driver(ufs_hisi_pltform);
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:ufshcd-hisi");
MODULE_DESCRIPTION("HiSilicon Hixxxx UFS Driver");
+104
View File
@@ -0,0 +1,104 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017, HiSilicon. All rights reserved.
*/
#ifndef UFS_HISI_H_
#define UFS_HISI_H_
#define HBRN8_POLL_TOUT_MS 1000
/*
* ufs sysctrl specific define
*/
#define PSW_POWER_CTRL (0x04)
#define PHY_ISO_EN (0x08)
#define HC_LP_CTRL (0x0C)
#define PHY_CLK_CTRL (0x10)
#define PSW_CLK_CTRL (0x14)
#define CLOCK_GATE_BYPASS (0x18)
#define RESET_CTRL_EN (0x1C)
#define UFS_SYSCTRL (0x5C)
#define UFS_DEVICE_RESET_CTRL (0x60)
#define BIT_UFS_PSW_ISO_CTRL (1 << 16)
#define BIT_UFS_PSW_MTCMOS_EN (1 << 0)
#define BIT_UFS_REFCLK_ISO_EN (1 << 16)
#define BIT_UFS_PHY_ISO_CTRL (1 << 0)
#define BIT_SYSCTRL_LP_ISOL_EN (1 << 16)
#define BIT_SYSCTRL_PWR_READY (1 << 8)
#define BIT_SYSCTRL_REF_CLOCK_EN (1 << 24)
#define MASK_SYSCTRL_REF_CLOCK_SEL (0x3 << 8)
#define MASK_SYSCTRL_CFG_CLOCK_FREQ (0xFF)
#define UFS_FREQ_CFG_CLK (0x39)
#define BIT_SYSCTRL_PSW_CLK_EN (1 << 4)
#define MASK_UFS_CLK_GATE_BYPASS (0x3F)
#define BIT_SYSCTRL_LP_RESET_N (1 << 0)
#define BIT_UFS_REFCLK_SRC_SEl (1 << 0)
#define MASK_UFS_SYSCRTL_BYPASS (0x3F << 16)
#define MASK_UFS_DEVICE_RESET (0x1 << 16)
#define BIT_UFS_DEVICE_RESET (0x1)
/*
* M-TX Configuration Attributes for Hixxxx
*/
#define MPHY_TX_FSM_STATE 0x41
#define TX_FSM_HIBERN8 0x1
/*
* Hixxxx UFS HC specific Registers
*/
enum {
UFS_REG_OCPTHRTL = 0xc0,
UFS_REG_OOCPR = 0xc4,
UFS_REG_CDACFG = 0xd0,
UFS_REG_CDATX1 = 0xd4,
UFS_REG_CDATX2 = 0xd8,
UFS_REG_CDARX1 = 0xdc,
UFS_REG_CDARX2 = 0xe0,
UFS_REG_CDASTA = 0xe4,
UFS_REG_LBMCFG = 0xf0,
UFS_REG_LBMSTA = 0xf4,
UFS_REG_UFSMODE = 0xf8,
UFS_REG_HCLKDIV = 0xfc,
};
/* AHIT - Auto-Hibernate Idle Timer */
#define UFS_AHIT_AH8ITV_MASK 0x3FF
/* REG UFS_REG_OCPTHRTL definition */
#define UFS_HCLKDIV_NORMAL_VALUE 0xE4
/* vendor specific pre-defined parameters */
#define SLOW 1
#define FAST 2
#define UFS_HISI_CAP_RESERVED BIT(0)
#define UFS_HISI_CAP_PHY10nm BIT(1)
struct ufs_hisi_host {
struct ufs_hba *hba;
void __iomem *ufs_sys_ctrl;
struct reset_control *rst;
uint64_t caps;
bool in_suspend;
};
#define ufs_sys_ctrl_writel(host, val, reg) \
writel((val), (host)->ufs_sys_ctrl + (reg))
#define ufs_sys_ctrl_readl(host, reg) readl((host)->ufs_sys_ctrl + (reg))
#define ufs_sys_ctrl_set_bits(host, mask, reg) \
ufs_sys_ctrl_writel( \
(host), ((mask) | (ufs_sys_ctrl_readl((host), (reg)))), (reg))
#define ufs_sys_ctrl_clr_bits(host, mask, reg) \
ufs_sys_ctrl_writel((host), \
((~(mask)) & (ufs_sys_ctrl_readl((host), (reg)))), \
(reg))
#endif /* UFS_HISI_H_ */
@@ -0,0 +1,94 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2022 MediaTek Inc.
*/
#ifndef _UFS_MEDIATEK_SIP_H
#define _UFS_MEDIATEK_SIP_H
#include <linux/soc/mediatek/mtk_sip_svc.h>
/*
* SiP (Slicon Partner) commands
*/
#define MTK_SIP_UFS_CONTROL MTK_SIP_SMC_CMD(0x276)
#define UFS_MTK_SIP_VA09_PWR_CTRL BIT(0)
#define UFS_MTK_SIP_DEVICE_RESET BIT(1)
#define UFS_MTK_SIP_CRYPTO_CTRL BIT(2)
#define UFS_MTK_SIP_REF_CLK_NOTIFICATION BIT(3)
#define UFS_MTK_SIP_SRAM_PWR_CTRL BIT(5)
#define UFS_MTK_SIP_GET_VCC_NUM BIT(6)
#define UFS_MTK_SIP_DEVICE_PWR_CTRL BIT(7)
#define UFS_MTK_SIP_MPHY_CTRL BIT(8)
#define UFS_MTK_SIP_MTCMOS_CTRL BIT(9)
/*
* Multi-VCC by Numbering
*/
enum ufs_mtk_vcc_num {
UFS_VCC_NONE = 0,
UFS_VCC_1,
UFS_VCC_2,
UFS_VCC_MAX
};
enum ufs_mtk_mphy_op {
UFS_MPHY_BACKUP = 0,
UFS_MPHY_RESTORE
};
/*
* SMC call wrapper function
*/
struct ufs_mtk_smc_arg {
unsigned long cmd;
struct arm_smccc_res *res;
unsigned long v1;
unsigned long v2;
unsigned long v3;
unsigned long v4;
unsigned long v5;
unsigned long v6;
unsigned long v7;
};
static inline void _ufs_mtk_smc(struct ufs_mtk_smc_arg s)
{
arm_smccc_smc(MTK_SIP_UFS_CONTROL,
s.cmd,
s.v1, s.v2, s.v3, s.v4, s.v5, s.v6, s.res);
}
#define ufs_mtk_smc(...) \
_ufs_mtk_smc((struct ufs_mtk_smc_arg) {__VA_ARGS__})
/* Sip kernel interface */
#define ufs_mtk_va09_pwr_ctrl(res, on) \
ufs_mtk_smc(UFS_MTK_SIP_VA09_PWR_CTRL, &(res), on)
#define ufs_mtk_crypto_ctrl(res, enable) \
ufs_mtk_smc(UFS_MTK_SIP_CRYPTO_CTRL, &(res), enable)
#define ufs_mtk_ref_clk_notify(on, stage, res) \
ufs_mtk_smc(UFS_MTK_SIP_REF_CLK_NOTIFICATION, &(res), on, stage)
#define ufs_mtk_device_reset_ctrl(high, res) \
ufs_mtk_smc(UFS_MTK_SIP_DEVICE_RESET, &(res), high)
#define ufs_mtk_sram_pwr_ctrl(on, res) \
ufs_mtk_smc(UFS_MTK_SIP_SRAM_PWR_CTRL, &(res), on)
#define ufs_mtk_get_vcc_num(res) \
ufs_mtk_smc(UFS_MTK_SIP_GET_VCC_NUM, &(res))
#define ufs_mtk_device_pwr_ctrl(on, ufs_version, res) \
ufs_mtk_smc(UFS_MTK_SIP_DEVICE_PWR_CTRL, &(res), on, ufs_version)
#define ufs_mtk_mphy_ctrl(op, res) \
ufs_mtk_smc(UFS_MTK_SIP_MPHY_CTRL, &(res), op)
#define ufs_mtk_mtcmos_ctrl(op, res) \
ufs_mtk_smc(UFS_MTK_SIP_MTCMOS_CTRL, &(res), op)
#endif /* !_UFS_MEDIATEK_SIP_H */
@@ -0,0 +1,59 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2020 MediaTek Inc.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM ufs_mtk
#if !defined(_TRACE_EVENT_UFS_MEDIATEK_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_EVENT_UFS_MEDIATEK_H
#include <linux/tracepoint.h>
TRACE_EVENT(ufs_mtk_event,
TP_PROTO(unsigned int type, unsigned int data),
TP_ARGS(type, data),
TP_STRUCT__entry(
__field(unsigned int, type)
__field(unsigned int, data)
),
TP_fast_assign(
__entry->type = type;
__entry->data = data;
),
TP_printk("ufs: event=%u data=%u",
__entry->type, __entry->data)
);
TRACE_EVENT(ufs_mtk_clk_scale,
TP_PROTO(const char *name, bool scale_up, unsigned long clk_rate),
TP_ARGS(name, scale_up, clk_rate),
TP_STRUCT__entry(
__string(name, name)
__field(bool, scale_up)
__field(unsigned long, clk_rate)
),
TP_fast_assign(
__assign_str(name);
__entry->scale_up = scale_up;
__entry->clk_rate = clk_rate;
),
TP_printk("ufs: clk (%s) scaled %s @ %lu",
__get_str(name),
__entry->scale_up ? "up" : "down",
__entry->clk_rate)
);
#endif
#undef TRACE_INCLUDE_PATH
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_PATH ../../drivers/ufs/host
#define TRACE_INCLUDE_FILE ufs-mediatek-trace
#include <trace/define_trace.h>
File diff suppressed because it is too large Load Diff
+236
View File
@@ -0,0 +1,236 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2019 MediaTek Inc.
*/
#ifndef _UFS_MEDIATEK_H
#define _UFS_MEDIATEK_H
#include <linux/bitops.h>
/*
* MCQ define and struct
*/
#define UFSHCD_MAX_Q_NR 8
#define MTK_MCQ_INVALID_IRQ 0xFFFF
/* REG_UFS_MMIO_OPT_CTRL_0 160h */
#define EHS_EN BIT(0)
#define PFM_IMPV BIT(1)
#define MCQ_MULTI_INTR_EN BIT(2)
#define MCQ_CMB_INTR_EN BIT(3)
#define MCQ_AH8 BIT(4)
#define MON_EN BIT(5)
#define MRTT_EN BIT(25)
#define RDN_PFM_IMPV_DIS BIT(28)
#define MCQ_INTR_EN_MSK (MCQ_MULTI_INTR_EN | MCQ_CMB_INTR_EN)
/*
* Vendor specific UFSHCI Registers
*/
#define REG_UFS_XOUFS_CTRL 0x140
#define REG_UFS_REFCLK_CTRL 0x144
#define REG_UFS_UFS_MMIO_OTSD_CTRL 0x14C
#define REG_UFS_MMIO_OPT_CTRL_0 0x160
#define REG_UFS_EXTREG 0x2100
#define REG_UFS_MPHYCTRL 0x2200
#define REG_UFS_MTK_IP_VER 0x2240
#define REG_UFS_REJECT_MON 0x22AC
#define REG_UFS_DEBUG_SEL 0x22C0
#define REG_UFS_PROBE 0x22C8
#define REG_UFS_DEBUG_SEL_B0 0x22D0
#define REG_UFS_DEBUG_SEL_B1 0x22D4
#define REG_UFS_DEBUG_SEL_B2 0x22D8
#define REG_UFS_DEBUG_SEL_B3 0x22DC
#define REG_UFS_MTK_SQD 0x2800
#define REG_UFS_MTK_SQIS 0x2814
#define REG_UFS_MTK_CQD 0x281C
#define REG_UFS_MTK_CQIS 0x2824
#define REG_UFS_MCQ_STRIDE 0x30
/*
* Ref-clk control
*
* Values for register REG_UFS_REFCLK_CTRL
*/
#define REFCLK_RELEASE 0x0
#define REFCLK_REQUEST BIT(0)
#define REFCLK_ACK BIT(1)
#define REFCLK_REQ_TIMEOUT_US 3000
#define REFCLK_DEFAULT_WAIT_US 32
/*
* Other attributes
*/
#define VS_DEBUGCLOCKENABLE 0xD0A1
#define VS_SAVEPOWERCONTROL 0xD0A6
#define VS_UNIPROPOWERDOWNCONTROL 0xD0A8
/*
* Vendor specific link state
*/
enum {
VS_LINK_DISABLED = 0,
VS_LINK_DOWN = 1,
VS_LINK_UP = 2,
VS_LINK_HIBERN8 = 3,
VS_LINK_LOST = 4,
VS_LINK_CFG = 5,
};
/*
* Vendor specific host controller state
*/
enum {
VS_HCE_RESET = 0,
VS_HCE_BASE = 1,
VS_HCE_OOCPR_WAIT = 2,
VS_HCE_DME_RESET = 3,
VS_HCE_MIDDLE = 4,
VS_HCE_DME_ENABLE = 5,
VS_HCE_DEFAULTS = 6,
VS_HIB_IDLEEN = 7,
VS_HIB_ENTER = 8,
VS_HIB_ENTER_CONF = 9,
VS_HIB_MIDDLE = 10,
VS_HIB_WAITTIMER = 11,
VS_HIB_EXIT_CONF = 12,
VS_HIB_EXIT = 13,
};
/*
* VS_DEBUGCLOCKENABLE
*/
enum {
TX_SYMBOL_CLK_REQ_FORCE = 5,
};
/*
* VS_SAVEPOWERCONTROL
*/
enum {
RX_SYMBOL_CLK_GATE_EN = 0,
SYS_CLK_GATE_EN = 2,
TX_CLK_GATE_EN = 3,
};
/*
* Host capability
*/
enum ufs_mtk_host_caps {
UFS_MTK_CAP_BOOST_CRYPT_ENGINE = 1 << 0,
UFS_MTK_CAP_VA09_PWR_CTRL = 1 << 1,
UFS_MTK_CAP_DISABLE_AH8 = 1 << 2,
UFS_MTK_CAP_BROKEN_VCC = 1 << 3,
/*
* Override UFS_MTK_CAP_BROKEN_VCC's behavior to
* allow vccqx upstream to enter LPM
*/
UFS_MTK_CAP_ALLOW_VCCQX_LPM = 1 << 5,
UFS_MTK_CAP_PMC_VIA_FASTAUTO = 1 << 6,
UFS_MTK_CAP_TX_SKEW_FIX = 1 << 7,
UFS_MTK_CAP_DISABLE_MCQ = 1 << 8,
/* Control MTCMOS with RTFF */
UFS_MTK_CAP_RTFF_MTCMOS = 1 << 9,
UFS_MTK_CAP_MCQ_BROKEN_RTC = 1 << 10,
};
struct ufs_mtk_crypt_cfg {
struct regulator *reg_vcore;
struct clk *clk_crypt_perf;
struct clk *clk_crypt_mux;
struct clk *clk_crypt_lp;
int vcore_volt;
};
struct ufs_mtk_clk {
struct ufs_clk_info *ufs_sel_clki; /* Mux */
struct ufs_clk_info *ufs_sel_max_clki; /* Max src */
struct ufs_clk_info *ufs_sel_min_clki; /* Min src */
struct ufs_clk_info *ufs_fde_clki; /* Mux */
struct ufs_clk_info *ufs_fde_max_clki; /* Max src */
struct ufs_clk_info *ufs_fde_min_clki; /* Min src */
struct regulator *reg_vcore;
int vcore_volt;
};
struct ufs_mtk_hw_ver {
u8 step;
u8 minor;
u8 major;
};
struct ufs_mtk_mcq_intr_info {
struct ufs_hba *hba;
u32 irq;
u8 qid;
};
struct ufs_mtk_host {
struct phy *mphy;
struct regulator *reg_va09;
struct reset_control *hci_reset;
struct reset_control *unipro_reset;
struct reset_control *crypto_reset;
struct reset_control *mphy_reset;
struct ufs_hba *hba;
struct ufs_mtk_crypt_cfg *crypt;
struct ufs_mtk_clk mclk;
struct ufs_mtk_hw_ver hw_ver;
enum ufs_mtk_host_caps caps;
bool mphy_powered_on;
bool unipro_lpm;
bool ref_clk_enabled;
bool clk_scale_up;
u16 ref_clk_ungating_wait_us;
u16 ref_clk_gating_wait_us;
u32 ip_ver;
bool legacy_ip_ver;
bool mcq_set_intr;
bool is_mcq_intr_enabled;
int mcq_nr_intr;
struct ufs_mtk_mcq_intr_info mcq_intr_info[UFSHCD_MAX_Q_NR];
struct device *phy_dev;
};
/* MTK delay of autosuspend: 500 ms */
#define MTK_RPM_AUTOSUSPEND_DELAY_MS 500
/* MTK RTT support number */
#define MTK_MAX_NUM_RTT 2
/* UFSHCI MTK ip version value */
enum {
/* UFSHCI 3.1 */
IP_VER_MT6983 = 0x10360000,
IP_VER_MT6878 = 0x10420200,
/* UFSHCI 4.0 */
IP_VER_MT6897 = 0x10440000,
IP_VER_MT6989 = 0x10450000,
IP_VER_MT6899 = 0x10450100,
IP_VER_MT6991_A0 = 0x10460000,
IP_VER_MT6991_B0 = 0x10470000,
IP_VER_MT6993 = 0x10480000,
/* UFSHCI 5.0 */
IP_VER_MT6995_A0 = 0x10490000,
IP_VER_MT6995_B0 = 0x10500000,
IP_VER_NONE = 0xFFFFFFFF
};
enum ip_ver_legacy {
IP_LEGACY_VER_MT6781 = 0x10380000,
IP_LEGACY_VER_MT6879 = 0x10360000,
IP_LEGACY_VER_MT6893 = 0x20160706
};
#endif /* !_UFS_MEDIATEK_H */
File diff suppressed because it is too large Load Diff
+388
View File
@@ -0,0 +1,388 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
*/
#ifndef UFS_QCOM_H_
#define UFS_QCOM_H_
#include <linux/reset-controller.h>
#include <linux/reset.h>
#include <soc/qcom/ice.h>
#include <ufs/ufshcd.h>
#define MPHY_TX_FSM_STATE 0x41
#define TX_FSM_HIBERN8 0x1
#define HBRN8_POLL_TOUT_MS 100
#define DEFAULT_CLK_RATE_HZ 1000000
#define MAX_SUPP_MAC 64
#define MAX_ESI_VEC 32
#define UFS_HW_VER_MAJOR_MASK GENMASK(31, 28)
#define UFS_HW_VER_MINOR_MASK GENMASK(27, 16)
#define UFS_HW_VER_STEP_MASK GENMASK(15, 0)
#define UFS_DEV_VER_MAJOR_MASK GENMASK(7, 4)
#define UFS_QCOM_LIMIT_HS_RATE PA_HS_MODE_B
/* bit and mask definitions for PA_VS_CLK_CFG_REG attribute */
#define PA_VS_CLK_CFG_REG 0x9004
#define PA_VS_CLK_CFG_REG_MASK GENMASK(8, 0)
/* bit and mask definitions for DL_VS_CLK_CFG attribute */
#define DL_VS_CLK_CFG 0xA00B
#define DL_VS_CLK_CFG_MASK GENMASK(9, 0)
#define DME_VS_CORE_CLK_CTRL_DME_HW_CGC_EN BIT(9)
#define UFS_QCOM_EOM_VOLTAGE_STEPS_MAX 127
#define UFS_QCOM_EOM_TIMING_STEPS_MAX 63
#define UFS_QCOM_EOM_TARGET_TEST_COUNT_MIN 8
#define UFS_QCOM_EOM_TARGET_TEST_COUNT_G6 0x3F
#define SW_RX_FOM_EOM_COORDS 23
#define SW_RX_FOM_EOM_COORDS_WEIGHT (127 / SW_RX_FOM_EOM_COORDS)
struct ufs_eom_coord {
int t_step;
int v_step;
u8 eye_mask;
};
static const struct ufs_eom_coord sw_rx_fom_eom_coords_g6[SW_RX_FOM_EOM_COORDS] = {
[0] = { -2, -15, UFS_EOM_EYE_MASK_M },
[1] = { 0, -15, UFS_EOM_EYE_MASK_M },
[2] = { 2, -15, UFS_EOM_EYE_MASK_M },
[3] = { -4, -10, UFS_EOM_EYE_MASK_M },
[4] = { -2, -10, UFS_EOM_EYE_MASK_M },
[5] = { 0, -10, UFS_EOM_EYE_MASK_M },
[6] = { 2, -10, UFS_EOM_EYE_MASK_M },
[7] = { 4, -10, UFS_EOM_EYE_MASK_M },
[8] = { -6, 0, UFS_EOM_EYE_MASK_M },
[9] = { -4, 0, UFS_EOM_EYE_MASK_M },
[10] = { -2, 0, UFS_EOM_EYE_MASK_M },
[11] = { 0, 0, UFS_EOM_EYE_MASK_M },
[12] = { 2, 0, UFS_EOM_EYE_MASK_M },
[13] = { 4, 0, UFS_EOM_EYE_MASK_M },
[14] = { 6, 0, UFS_EOM_EYE_MASK_M },
[15] = { -4, 10, UFS_EOM_EYE_MASK_M },
[16] = { -2, 10, UFS_EOM_EYE_MASK_M },
[17] = { 0, 10, UFS_EOM_EYE_MASK_M },
[18] = { 2, 10, UFS_EOM_EYE_MASK_M },
[19] = { 4, 10, UFS_EOM_EYE_MASK_M },
[20] = { -2, 15, UFS_EOM_EYE_MASK_M },
[21] = { 0, 15, UFS_EOM_EYE_MASK_M },
[22] = { 2, 15, UFS_EOM_EYE_MASK_M },
};
/* Qualcomm MCQ Configuration */
#define UFS_QCOM_MCQCAP_QCFGPTR 224 /* 0xE0 in hex */
#define UFS_QCOM_MCQ_CONFIG_OFFSET (UFS_QCOM_MCQCAP_QCFGPTR * 0x200) /* 0x1C000 */
/* Doorbell offsets within MCQ region (relative to MCQ_CONFIG_BASE) */
#define UFS_QCOM_MCQ_SQD_OFFSET 0x5000
#define UFS_QCOM_MCQ_CQD_OFFSET 0x5080
#define UFS_QCOM_MCQ_SQIS_OFFSET 0x5040
#define UFS_QCOM_MCQ_CQIS_OFFSET 0x50C0
#define UFS_QCOM_MCQ_STRIDE 0x100
/* Calculated doorbell address offsets (relative to mmio_base) */
#define UFS_QCOM_SQD_ADDR_OFFSET (UFS_QCOM_MCQ_CONFIG_OFFSET + UFS_QCOM_MCQ_SQD_OFFSET)
#define UFS_QCOM_CQD_ADDR_OFFSET (UFS_QCOM_MCQ_CONFIG_OFFSET + UFS_QCOM_MCQ_CQD_OFFSET)
#define UFS_QCOM_SQIS_ADDR_OFFSET (UFS_QCOM_MCQ_CONFIG_OFFSET + UFS_QCOM_MCQ_SQIS_OFFSET)
#define UFS_QCOM_CQIS_ADDR_OFFSET (UFS_QCOM_MCQ_CONFIG_OFFSET + UFS_QCOM_MCQ_CQIS_OFFSET)
#define REG_UFS_MCQ_STRIDE UFS_QCOM_MCQ_STRIDE
/* MCQ Vendor specific address offsets (relative to MCQ_CONFIG_BASE) */
#define UFS_MEM_VS_BASE 0x4000
#define UFS_MEM_CQIS_VS 0x4008
/* QCOM UFS host controller vendor specific registers */
enum {
REG_UFS_SYS1CLK_1US = 0xC0,
REG_UFS_TX_SYMBOL_CLK_NS_US = 0xC4,
REG_UFS_LOCAL_PORT_ID_REG = 0xC8,
REG_UFS_PA_ERR_CODE = 0xCC,
/* On older UFS revisions, this register is called "RETRY_TIMER_REG" */
REG_UFS_PARAM0 = 0xD0,
/* On older UFS revisions, this register is called "REG_UFS_PA_LINK_STARTUP_TIMER" */
REG_UFS_CFG0 = 0xD8,
REG_UFS_CFG1 = 0xDC,
REG_UFS_CFG2 = 0xE0,
REG_UFS_HW_VERSION = 0xE4,
UFS_TEST_BUS = 0xE8,
UFS_TEST_BUS_CTRL_0 = 0xEC,
UFS_TEST_BUS_CTRL_1 = 0xF0,
UFS_TEST_BUS_CTRL_2 = 0xF4,
UFS_UNIPRO_CFG = 0xF8,
/*
* QCOM UFS host controller vendor specific registers
* added in HW Version 3.0.0
*/
UFS_AH8_CFG = 0xFC,
UFS_RD_REG_MCQ = 0xD00,
UFS_MEM_ICE_CFG = 0x2600,
REG_UFS_MEM_ICE_CONFIG = 0x260C,
REG_UFS_MEM_ICE_NUM_CORE = 0x2664,
REG_UFS_CFG3 = 0x271C,
REG_UFS_DEBUG_SPARE_CFG = 0x284C,
};
/* QCOM UFS host controller vendor specific debug registers */
enum {
UFS_DBG_RD_REG_UAWM = 0x100,
UFS_DBG_RD_REG_UARM = 0x200,
UFS_DBG_RD_REG_TXUC = 0x300,
UFS_DBG_RD_REG_RXUC = 0x400,
UFS_DBG_RD_REG_DFC = 0x500,
UFS_DBG_RD_REG_TRLUT = 0x600,
UFS_DBG_RD_REG_TMRLUT = 0x700,
UFS_UFS_DBG_RD_REG_OCSC = 0x800,
UFS_UFS_DBG_RD_DESC_RAM = 0x1500,
UFS_UFS_DBG_RD_PRDT_RAM = 0x1700,
UFS_UFS_DBG_RD_RESP_RAM = 0x1800,
UFS_UFS_DBG_RD_EDTL_RAM = 0x1900,
};
/* QCOM UFS HC vendor specific Hibern8 count registers */
enum {
REG_UFS_HW_H8_ENTER_CNT = 0x2700,
REG_UFS_SW_H8_ENTER_CNT = 0x2704,
REG_UFS_SW_AFTER_HW_H8_ENTER_CNT = 0x2708,
REG_UFS_HW_H8_EXIT_CNT = 0x270C,
REG_UFS_SW_H8_EXIT_CNT = 0x2710,
};
#define UFS_CNTLR_2_x_x_VEN_REGS_OFFSET(x) (0x000 + x)
#define UFS_CNTLR_3_x_x_VEN_REGS_OFFSET(x) (0x400 + x)
/* bit definitions for REG_UFS_CFG0 register */
#define QUNIPRO_G4_SEL BIT(5)
/* bit definitions for REG_UFS_CFG1 register */
#define QUNIPRO_SEL BIT(0)
#define UFS_PHY_SOFT_RESET BIT(1)
#define UTP_DBG_RAMS_EN BIT(17)
#define TEST_BUS_EN BIT(18)
#define TEST_BUS_SEL GENMASK(22, 19)
#define UFS_REG_TEST_BUS_EN BIT(30)
/* bit definitions for REG_UFS_CFG2 register */
#define UAWM_HW_CGC_EN BIT(0)
#define UARM_HW_CGC_EN BIT(1)
#define TXUC_HW_CGC_EN BIT(2)
#define RXUC_HW_CGC_EN BIT(3)
#define DFC_HW_CGC_EN BIT(4)
#define TRLUT_HW_CGC_EN BIT(5)
#define TMRLUT_HW_CGC_EN BIT(6)
#define OCSC_HW_CGC_EN BIT(7)
/* bit definitions for REG_UFS_CFG3 register */
#define ESI_VEC_MASK GENMASK(22, 12)
/* bit definitions for REG_UFS_PARAM0 */
#define MAX_HS_GEAR_MASK GENMASK(6, 4)
#define UFS_QCOM_MAX_GEAR(x) FIELD_GET(MAX_HS_GEAR_MASK, (x))
/* bit definition for UFS_UFS_TEST_BUS_CTRL_n */
#define TEST_BUS_SUB_SEL_MASK GENMASK(4, 0) /* All XXX_SEL fields are 5 bits wide */
/* bit definition for UFS Shared ICE config */
#define UFS_QCOM_CAP_ICE_CONFIG BIT(0)
#define REG_UFS_CFG2_CGC_EN_ALL (UAWM_HW_CGC_EN | UARM_HW_CGC_EN |\
TXUC_HW_CGC_EN | RXUC_HW_CGC_EN |\
DFC_HW_CGC_EN | TRLUT_HW_CGC_EN |\
TMRLUT_HW_CGC_EN | OCSC_HW_CGC_EN)
/* QUniPro Vendor specific attributes */
#define PA_TX_HSG1_SYNC_LENGTH 0x1552
#define PA_VS_CONFIG_REG1 0x9000
#define DME_VS_CORE_CLK_CTRL 0xD002
#define TX_HS_EQUALIZER 0x0037
/* bit and mask definitions for DME_VS_CORE_CLK_CTRL attribute */
#define CLK_1US_CYCLES_MASK_V4 GENMASK(27, 16)
#define CLK_1US_CYCLES_MASK GENMASK(7, 0)
#define DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT BIT(8)
#define PA_VS_CORE_CLK_40NS_CYCLES 0x9007
#define PA_VS_CORE_CLK_40NS_CYCLES_MASK GENMASK(6, 0)
/* QCOM UFS host controller core clk frequencies */
#define UNIPRO_CORE_CLK_FREQ_37_5_MHZ 38
#define UNIPRO_CORE_CLK_FREQ_75_MHZ 75
#define UNIPRO_CORE_CLK_FREQ_100_MHZ 100
#define UNIPRO_CORE_CLK_FREQ_150_MHZ 150
#define UNIPRO_CORE_CLK_FREQ_300_MHZ 300
#define UNIPRO_CORE_CLK_FREQ_201_5_MHZ 202
#define UNIPRO_CORE_CLK_FREQ_403_MHZ 403
/* TX_HSG1_SYNC_LENGTH attr value */
#define PA_TX_HSG1_SYNC_LENGTH_VAL 0x4A
/*
* Some ufs device vendors need a different TSync length.
* Enable this quirk to give an additional TX_HS_SYNC_LENGTH.
*/
#define UFS_DEVICE_QUIRK_PA_TX_HSG1_SYNC_LENGTH BIT(16)
/*
* Some ufs device vendors need a different Deemphasis setting.
* Enable this quirk to tune TX Deemphasis parameters.
*/
#define UFS_DEVICE_QUIRK_PA_TX_DEEMPHASIS_TUNING BIT(17)
/* ICE allocator type to share AES engines among TX stream and RX stream */
#define ICE_ALLOCATOR_TYPE 2
/*
* Number of cores allocated for RX stream when Read data block received and
* Write data block is not in progress
*/
#define NUM_RX_R1W0 28
/*
* Number of cores allocated for TX stream when Device asked to send write
* data block and Read data block is not in progress
*/
#define NUM_TX_R0W1 28
/*
* Number of cores allocated for RX stream when Read data block received and
* Write data block is in progress
* OR
* Device asked to send write data block and Read data block is in progress
*/
#define NUM_RX_R1W1 15
/*
* Number of cores allocated for TX stream (UFS write) when Read data block
* received and Write data block is in progress
* OR
* Device asked to send write data block and Read data block is in progress
*/
#define NUM_TX_R1W1 13
/* bit definitions for UFS_AH8_CFG register */
#define CC_UFS_SYS_CLK_REQ_EN BIT(2)
#define CC_UFS_ICE_CORE_CLK_REQ_EN BIT(3)
#define CC_UFS_UNIPRO_CORE_CLK_REQ_EN BIT(4)
#define CC_UFS_AUXCLK_REQ_EN BIT(5)
#define UFS_HW_CLK_CTRL_EN (CC_UFS_SYS_CLK_REQ_EN |\
CC_UFS_ICE_CORE_CLK_REQ_EN |\
CC_UFS_UNIPRO_CORE_CLK_REQ_EN |\
CC_UFS_AUXCLK_REQ_EN)
static inline void
ufs_qcom_get_controller_revision(struct ufs_hba *hba,
u8 *major, u16 *minor, u16 *step)
{
u32 ver = ufshcd_readl(hba, REG_UFS_HW_VERSION);
*major = FIELD_GET(UFS_HW_VER_MAJOR_MASK, ver);
*minor = FIELD_GET(UFS_HW_VER_MINOR_MASK, ver);
*step = FIELD_GET(UFS_HW_VER_STEP_MASK, ver);
};
static inline void ufs_qcom_assert_reset(struct ufs_hba *hba)
{
ufshcd_rmwl(hba, UFS_PHY_SOFT_RESET, UFS_PHY_SOFT_RESET, REG_UFS_CFG1);
/*
* Dummy read to ensure the write takes effect before doing any sort
* of delay
*/
ufshcd_readl(hba, REG_UFS_CFG1);
}
static inline void ufs_qcom_deassert_reset(struct ufs_hba *hba)
{
ufshcd_rmwl(hba, UFS_PHY_SOFT_RESET, 0, REG_UFS_CFG1);
/*
* Dummy read to ensure the write takes effect before doing any sort
* of delay
*/
ufshcd_readl(hba, REG_UFS_CFG1);
}
/* Host controller hardware version: major.minor.step */
struct ufs_hw_version {
u16 step;
u16 minor;
u8 major;
};
struct ufs_qcom_testbus {
u8 select_major;
u8 select_minor;
};
struct gpio_desc;
struct ufs_qcom_host {
struct phy *generic_phy;
struct ufs_hba *hba;
struct ufs_pa_layer_attr dev_req_params;
struct clk_bulk_data *clks;
u32 num_clks;
bool is_lane_clks_enabled;
struct icc_path *icc_ddr;
struct icc_path *icc_cpu;
#ifdef CONFIG_SCSI_UFS_CRYPTO
struct qcom_ice *ice;
#endif
u32 caps;
void __iomem *dev_ref_clk_ctrl_mmio;
bool is_dev_ref_clk_enabled;
struct ufs_hw_version hw_ver;
u32 dev_ref_clk_en_mask;
struct ufs_qcom_testbus testbus;
/* Reset control of HCI */
struct reset_control *core_reset;
struct reset_controller_dev rcdev;
struct gpio_desc *device_reset;
struct ufs_host_params host_params;
u32 phy_gear;
bool esi_enabled;
u32 saved_tx_eq_g1_setting;
};
struct ufs_qcom_drvdata {
enum ufshcd_quirks quirks;
bool no_phy_retention;
const struct ufs_hba_variant_ops *vops;
};
static inline u32
ufs_qcom_get_debug_reg_offset(struct ufs_qcom_host *host, u32 reg)
{
if (host->hw_ver.major <= 0x02)
return UFS_CNTLR_2_x_x_VEN_REGS_OFFSET(reg);
return UFS_CNTLR_3_x_x_VEN_REGS_OFFSET(reg);
};
#define ufs_qcom_is_link_off(hba) ufshcd_is_link_off(hba)
#define ufs_qcom_is_link_active(hba) ufshcd_is_link_active(hba)
#define ufs_qcom_is_link_hibern8(hba) ufshcd_is_link_hibern8(hba)
#define ceil(freq, div) ((freq) % (div) == 0 ? ((freq)/(div)) : ((freq)/(div) + 1))
int ufs_qcom_testbus_config(struct ufs_qcom_host *host);
#endif /* UFS_QCOM_H_ */
+558
View File
@@ -0,0 +1,558 @@
// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Renesas UFS host controller driver
*
* Copyright (C) 2022 Renesas Electronics Corporation
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/firmware.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/sys_soc.h>
#include <ufs/ufshcd.h>
#include "ufshcd-pltfrm.h"
#define EFUSE_CALIB_SIZE 8
struct ufs_renesas_priv {
const struct firmware *fw;
void (*pre_init)(struct ufs_hba *hba);
bool initialized; /* The hardware needs initialization once */
u8 calib[EFUSE_CALIB_SIZE];
};
#define UFS_RENESAS_FIRMWARE_NAME "r8a779f0_ufs.bin"
MODULE_FIRMWARE(UFS_RENESAS_FIRMWARE_NAME);
static void ufs_renesas_dbg_register_dump(struct ufs_hba *hba)
{
ufshcd_dump_regs(hba, 0xc0, 0x40, "regs: 0xc0 + ");
}
static void ufs_renesas_poll(struct ufs_hba *hba, u32 reg, u32 expected, u32 mask)
{
int ret;
u32 val;
ret = readl_poll_timeout_atomic(hba->mmio_base + reg,
val, (val & mask) == expected,
10, 1000);
if (ret)
dev_err(hba->dev, "%s: poll failed %d (%08x, %08x, %08x)\n",
__func__, ret, val, mask, expected);
}
static u32 ufs_renesas_read(struct ufs_hba *hba, u32 reg)
{
return ufshcd_readl(hba, reg);
}
static void ufs_renesas_write(struct ufs_hba *hba, u32 reg, u32 value)
{
ufshcd_writel(hba, value, reg);
}
static void ufs_renesas_write_d0_d4(struct ufs_hba *hba, u32 data_d0, u32 data_d4)
{
ufs_renesas_write(hba, 0xd0, data_d0);
ufs_renesas_write(hba, 0xd4, data_d4);
}
static void ufs_renesas_write_800_80c_poll(struct ufs_hba *hba, u32 addr,
u32 data_800)
{
ufs_renesas_write_d0_d4(hba, 0x0000080c, 0x00000100);
ufs_renesas_write_d0_d4(hba, 0x00000800, (data_800 << 16) | BIT(8) | addr);
ufs_renesas_write(hba, 0xd0, 0x0000080c);
ufs_renesas_poll(hba, 0xd4, BIT(8), BIT(8));
}
static void ufs_renesas_write_804_80c_poll(struct ufs_hba *hba, u32 addr, u32 data_804)
{
ufs_renesas_write_d0_d4(hba, 0x0000080c, 0x00000100);
ufs_renesas_write_d0_d4(hba, 0x00000804, (data_804 << 16) | BIT(8) | addr);
ufs_renesas_write(hba, 0xd0, 0x0000080c);
ufs_renesas_poll(hba, 0xd4, BIT(8), BIT(8));
}
static void ufs_renesas_write_828_82c_poll(struct ufs_hba *hba, u32 data_828)
{
ufs_renesas_write_d0_d4(hba, 0x0000082c, 0x0f000000);
ufs_renesas_write_d0_d4(hba, 0x00000828, data_828);
ufs_renesas_write(hba, 0xd0, 0x0000082c);
ufs_renesas_poll(hba, 0xd4, data_828, data_828);
}
static void ufs_renesas_write_phy(struct ufs_hba *hba, u32 addr16, u32 data16)
{
ufs_renesas_write(hba, 0xf0, 1);
ufs_renesas_write_800_80c_poll(hba, 0x16, addr16 & 0xff);
ufs_renesas_write_800_80c_poll(hba, 0x17, (addr16 >> 8) & 0xff);
ufs_renesas_write_800_80c_poll(hba, 0x18, data16 & 0xff);
ufs_renesas_write_800_80c_poll(hba, 0x19, (data16 >> 8) & 0xff);
ufs_renesas_write_800_80c_poll(hba, 0x1c, 0x01);
ufs_renesas_write_828_82c_poll(hba, 0x0f000000);
ufs_renesas_write(hba, 0xf0, 0);
}
static void ufs_renesas_set_phy(struct ufs_hba *hba, u32 addr16, u32 data16)
{
u32 low, high;
ufs_renesas_write(hba, 0xf0, 1);
ufs_renesas_write_800_80c_poll(hba, 0x16, addr16 & 0xff);
ufs_renesas_write_800_80c_poll(hba, 0x17, (addr16 >> 8) & 0xff);
ufs_renesas_write_800_80c_poll(hba, 0x1c, 0x01);
ufs_renesas_write_828_82c_poll(hba, 0x0f000000);
ufs_renesas_write_804_80c_poll(hba, 0x1a, 0);
ufs_renesas_write(hba, 0xd0, 0x00000808);
low = ufs_renesas_read(hba, 0xd4) & 0xff;
ufs_renesas_write_804_80c_poll(hba, 0x1b, 0);
ufs_renesas_write(hba, 0xd0, 0x00000808);
high = ufs_renesas_read(hba, 0xd4) & 0xff;
ufs_renesas_write_828_82c_poll(hba, 0x0f000000);
ufs_renesas_write(hba, 0xf0, 0);
data16 |= (high << 8) | low;
ufs_renesas_write_phy(hba, addr16, data16);
}
static void ufs_renesas_reset_indirect_write(struct ufs_hba *hba, int gpio,
u32 addr, u32 data)
{
ufs_renesas_write(hba, 0xf0, gpio);
ufs_renesas_write_800_80c_poll(hba, addr, data);
}
static void ufs_renesas_reset_indirect_update(struct ufs_hba *hba)
{
ufs_renesas_write_d0_d4(hba, 0x0000082c, 0x0f000000);
ufs_renesas_write_d0_d4(hba, 0x00000828, 0x0f000000);
ufs_renesas_write(hba, 0xd0, 0x0000082c);
ufs_renesas_poll(hba, 0xd4, BIT(27) | BIT(26) | BIT(24), BIT(27) | BIT(26) | BIT(24));
ufs_renesas_write(hba, 0xf0, 0);
}
static void ufs_renesas_indirect_write(struct ufs_hba *hba, u32 gpio, u32 addr,
u32 data_800)
{
ufs_renesas_write(hba, 0xf0, gpio);
ufs_renesas_write_800_80c_poll(hba, addr, data_800);
ufs_renesas_write_828_82c_poll(hba, 0x0f000000);
ufs_renesas_write(hba, 0xf0, 0);
}
static void ufs_renesas_indirect_poll(struct ufs_hba *hba, u32 gpio, u32 addr,
u32 expected, u32 mask)
{
ufs_renesas_write(hba, 0xf0, gpio);
ufs_renesas_write_800_80c_poll(hba, addr, 0);
ufs_renesas_write(hba, 0xd0, 0x00000808);
ufs_renesas_poll(hba, 0xd4, expected, mask);
ufs_renesas_write(hba, 0xf0, 0);
}
static void ufs_renesas_init_step1_to_3(struct ufs_hba *hba, bool init108)
{
ufs_renesas_write(hba, 0xc0, 0x49425308);
ufs_renesas_write_d0_d4(hba, 0x00000104, 0x00000002);
if (init108)
ufs_renesas_write_d0_d4(hba, 0x00000108, 0x00000002);
udelay(1);
ufs_renesas_write_d0_d4(hba, 0x00000828, 0x00000200);
udelay(1);
ufs_renesas_write_d0_d4(hba, 0x00000828, 0x00000000);
ufs_renesas_write_d0_d4(hba, 0x00000104, 0x00000001);
if (init108)
ufs_renesas_write_d0_d4(hba, 0x00000108, 0x00000001);
ufs_renesas_write_d0_d4(hba, 0x00000940, 0x00000001);
udelay(1);
ufs_renesas_write_d0_d4(hba, 0x00000940, 0x00000000);
ufs_renesas_write(hba, 0xc0, 0x49425308);
ufs_renesas_write(hba, 0xc0, 0x41584901);
}
static void ufs_renesas_init_step4_to_6(struct ufs_hba *hba)
{
ufs_renesas_write_d0_d4(hba, 0x0000080c, 0x00000100);
ufs_renesas_write_d0_d4(hba, 0x00000804, 0x00000000);
ufs_renesas_write(hba, 0xd0, 0x0000080c);
ufs_renesas_poll(hba, 0xd4, BIT(8), BIT(8));
ufs_renesas_write(hba, REG_CONTROLLER_ENABLE, 0x00000001);
ufs_renesas_write(hba, 0xd0, 0x00000804);
ufs_renesas_poll(hba, 0xd4, BIT(8) | BIT(6) | BIT(0), BIT(8) | BIT(6) | BIT(0));
}
static u32 ufs_renesas_init_disable_timer(struct ufs_hba *hba)
{
u32 timer_val;
ufs_renesas_write(hba, 0xd0, 0x00000d00);
timer_val = ufs_renesas_read(hba, 0xd4) & 0x0000ffff;
ufs_renesas_write(hba, 0xd4, 0x00000000);
ufs_renesas_write_d0_d4(hba, 0x0000082c, 0x0f000000);
ufs_renesas_write_d0_d4(hba, 0x00000828, 0x08000000);
ufs_renesas_write(hba, 0xd0, 0x0000082c);
ufs_renesas_poll(hba, 0xd4, BIT(27), BIT(27));
ufs_renesas_write(hba, 0xd0, 0x00000d2c);
ufs_renesas_poll(hba, 0xd4, BIT(0), BIT(0));
return timer_val;
}
static void ufs_renesas_init_enable_timer(struct ufs_hba *hba, u32 timer_val)
{
ufs_renesas_write(hba, 0xf0, 0);
ufs_renesas_write(hba, 0xd0, 0x00000d00);
ufs_renesas_write(hba, 0xd4, timer_val);
}
static void ufs_renesas_write_phy_10ad_10af(struct ufs_hba *hba,
u32 data_10ad, u32 data_10af)
{
ufs_renesas_write_phy(hba, 0x10ae, 0x0001);
ufs_renesas_write_phy(hba, 0x10ad, data_10ad);
ufs_renesas_write_phy(hba, 0x10af, data_10af);
ufs_renesas_write_phy(hba, 0x10b6, 0x0001);
ufs_renesas_write_phy(hba, 0x10ae, 0x0000);
}
static void ufs_renesas_init_compensation_and_slicers(struct ufs_hba *hba)
{
ufs_renesas_write_phy_10ad_10af(hba, 0x0000, 0x0001);
ufs_renesas_write_phy_10ad_10af(hba, 0x0000, 0x0002);
ufs_renesas_write_phy_10ad_10af(hba, 0x0080, 0x0000);
ufs_renesas_write_phy_10ad_10af(hba, 0x0080, 0x001a);
}
static void ufs_renesas_r8a779f0_es10_pre_init(struct ufs_hba *hba)
{
u32 timer_val;
/* This setting is for SERIES B */
ufs_renesas_init_step1_to_3(hba, false);
ufs_renesas_init_step4_to_6(hba);
timer_val = ufs_renesas_init_disable_timer(hba);
/* phy setup */
ufs_renesas_indirect_write(hba, 1, 0x01, 0x001f);
ufs_renesas_indirect_write(hba, 7, 0x5d, 0x0014);
ufs_renesas_indirect_write(hba, 7, 0x5e, 0x0014);
ufs_renesas_indirect_write(hba, 7, 0x0d, 0x0003);
ufs_renesas_indirect_write(hba, 7, 0x0e, 0x0007);
ufs_renesas_indirect_write(hba, 7, 0x5f, 0x0003);
ufs_renesas_indirect_write(hba, 7, 0x60, 0x0003);
ufs_renesas_indirect_write(hba, 7, 0x5b, 0x00a6);
ufs_renesas_indirect_write(hba, 7, 0x5c, 0x0003);
ufs_renesas_indirect_poll(hba, 7, 0x3c, 0, BIT(7));
ufs_renesas_indirect_poll(hba, 7, 0x4c, 0, BIT(4));
ufs_renesas_indirect_write(hba, 1, 0x32, 0x0080);
ufs_renesas_indirect_write(hba, 1, 0x1f, 0x0001);
ufs_renesas_indirect_write(hba, 0, 0x2c, 0x0001);
ufs_renesas_indirect_write(hba, 0, 0x32, 0x0087);
ufs_renesas_indirect_write(hba, 1, 0x4d, 0x0061);
ufs_renesas_indirect_write(hba, 4, 0x9b, 0x0009);
ufs_renesas_indirect_write(hba, 4, 0xa6, 0x0005);
ufs_renesas_indirect_write(hba, 4, 0xa5, 0x0058);
ufs_renesas_indirect_write(hba, 1, 0x39, 0x0027);
ufs_renesas_indirect_write(hba, 1, 0x47, 0x004c);
ufs_renesas_indirect_write(hba, 7, 0x0d, 0x0002);
ufs_renesas_indirect_write(hba, 7, 0x0e, 0x0007);
ufs_renesas_write_phy(hba, 0x0028, 0x0061);
ufs_renesas_write_phy(hba, 0x4014, 0x0061);
ufs_renesas_set_phy(hba, 0x401c, BIT(2));
ufs_renesas_write_phy(hba, 0x4000, 0x0000);
ufs_renesas_write_phy(hba, 0x4001, 0x0000);
ufs_renesas_init_compensation_and_slicers(hba);
ufs_renesas_indirect_write(hba, 7, 0x70, 0x0016);
ufs_renesas_indirect_write(hba, 7, 0x71, 0x0016);
ufs_renesas_indirect_write(hba, 7, 0x72, 0x0014);
ufs_renesas_indirect_write(hba, 7, 0x73, 0x0014);
ufs_renesas_indirect_write(hba, 7, 0x74, 0x0000);
ufs_renesas_indirect_write(hba, 7, 0x75, 0x0000);
ufs_renesas_indirect_write(hba, 7, 0x76, 0x0010);
ufs_renesas_indirect_write(hba, 7, 0x77, 0x0010);
ufs_renesas_indirect_write(hba, 7, 0x78, 0x00ff);
ufs_renesas_indirect_write(hba, 7, 0x79, 0x0000);
ufs_renesas_indirect_write(hba, 7, 0x19, 0x0007);
ufs_renesas_indirect_write(hba, 7, 0x1a, 0x0007);
ufs_renesas_indirect_write(hba, 7, 0x24, 0x000c);
ufs_renesas_indirect_write(hba, 7, 0x25, 0x000c);
ufs_renesas_indirect_write(hba, 7, 0x62, 0x0000);
ufs_renesas_indirect_write(hba, 7, 0x63, 0x0000);
ufs_renesas_indirect_write(hba, 7, 0x5d, 0x0014);
ufs_renesas_indirect_write(hba, 7, 0x5e, 0x0017);
ufs_renesas_indirect_write(hba, 7, 0x5d, 0x0004);
ufs_renesas_indirect_write(hba, 7, 0x5e, 0x0017);
ufs_renesas_indirect_poll(hba, 7, 0x55, 0, BIT(6));
ufs_renesas_indirect_poll(hba, 7, 0x41, 0, BIT(7));
/* end of phy setup */
ufs_renesas_init_enable_timer(hba, timer_val);
}
static void ufs_renesas_r8a779f0_init_step3_add(struct ufs_hba *hba, bool assert)
{
u32 val_2x = 0, val_3x = 0, val_4x = 0;
if (assert) {
val_2x = 0x0001;
val_3x = 0x0003;
val_4x = 0x0001;
}
ufs_renesas_reset_indirect_write(hba, 7, 0x20, val_2x);
ufs_renesas_reset_indirect_write(hba, 7, 0x4a, val_4x);
ufs_renesas_reset_indirect_write(hba, 7, 0x35, val_3x);
ufs_renesas_reset_indirect_update(hba);
ufs_renesas_reset_indirect_write(hba, 7, 0x21, val_2x);
ufs_renesas_reset_indirect_write(hba, 7, 0x4b, val_4x);
ufs_renesas_reset_indirect_write(hba, 7, 0x36, val_3x);
ufs_renesas_reset_indirect_update(hba);
}
static void ufs_renesas_r8a779f0_pre_init(struct ufs_hba *hba)
{
struct ufs_renesas_priv *priv = ufshcd_get_variant(hba);
u32 timer_val;
u32 data;
int i;
/* This setting is for SERIES B */
ufs_renesas_init_step1_to_3(hba, true);
ufs_renesas_r8a779f0_init_step3_add(hba, true);
ufs_renesas_reset_indirect_write(hba, 7, 0x5f, 0x0063);
ufs_renesas_reset_indirect_update(hba);
ufs_renesas_reset_indirect_write(hba, 7, 0x60, 0x0003);
ufs_renesas_reset_indirect_update(hba);
ufs_renesas_reset_indirect_write(hba, 7, 0x5b, 0x00a6);
ufs_renesas_reset_indirect_update(hba);
ufs_renesas_reset_indirect_write(hba, 7, 0x5c, 0x0003);
ufs_renesas_reset_indirect_update(hba);
ufs_renesas_r8a779f0_init_step3_add(hba, false);
ufs_renesas_init_step4_to_6(hba);
timer_val = ufs_renesas_init_disable_timer(hba);
ufs_renesas_indirect_write(hba, 1, 0x01, 0x001f);
ufs_renesas_indirect_write(hba, 7, 0x5d, 0x0014);
ufs_renesas_indirect_write(hba, 7, 0x5e, 0x0014);
ufs_renesas_indirect_write(hba, 7, 0x0d, 0x0007);
ufs_renesas_indirect_write(hba, 7, 0x0e, 0x0007);
ufs_renesas_indirect_poll(hba, 7, 0x3c, 0, BIT(7));
ufs_renesas_indirect_poll(hba, 7, 0x4c, 0, BIT(4));
ufs_renesas_indirect_write(hba, 1, 0x32, 0x0080);
ufs_renesas_indirect_write(hba, 1, 0x1f, 0x0001);
ufs_renesas_indirect_write(hba, 1, 0x2c, 0x0001);
ufs_renesas_indirect_write(hba, 1, 0x32, 0x0087);
ufs_renesas_indirect_write(hba, 1, 0x4d, priv->calib[2]);
ufs_renesas_indirect_write(hba, 1, 0x4e, priv->calib[3]);
ufs_renesas_indirect_write(hba, 1, 0x0d, 0x0006);
ufs_renesas_indirect_write(hba, 1, 0x0e, 0x0007);
ufs_renesas_write_phy(hba, 0x0028, priv->calib[3]);
ufs_renesas_write_phy(hba, 0x4014, priv->calib[3]);
ufs_renesas_set_phy(hba, 0x401c, BIT(2));
ufs_renesas_write_phy(hba, 0x4000, priv->calib[6]);
ufs_renesas_write_phy(hba, 0x4001, priv->calib[7]);
ufs_renesas_indirect_write(hba, 1, 0x14, 0x0001);
ufs_renesas_init_compensation_and_slicers(hba);
ufs_renesas_indirect_write(hba, 7, 0x79, 0x0000);
ufs_renesas_indirect_write(hba, 7, 0x24, 0x000c);
ufs_renesas_indirect_write(hba, 7, 0x25, 0x000c);
ufs_renesas_indirect_write(hba, 7, 0x62, 0x00c0);
ufs_renesas_indirect_write(hba, 7, 0x63, 0x0001);
for (i = 0; i < priv->fw->size / 2; i++) {
data = (priv->fw->data[i * 2 + 1] << 8) | priv->fw->data[i * 2];
ufs_renesas_write_phy(hba, 0xc000 + i, data);
}
ufs_renesas_indirect_write(hba, 7, 0x0d, 0x0002);
ufs_renesas_indirect_write(hba, 7, 0x0e, 0x0007);
ufs_renesas_indirect_write(hba, 7, 0x5d, 0x0014);
ufs_renesas_indirect_write(hba, 7, 0x5e, 0x0017);
ufs_renesas_indirect_write(hba, 7, 0x5d, 0x0004);
ufs_renesas_indirect_write(hba, 7, 0x5e, 0x0017);
ufs_renesas_indirect_poll(hba, 7, 0x55, 0, BIT(6));
ufs_renesas_indirect_poll(hba, 7, 0x41, 0, BIT(7));
ufs_renesas_init_enable_timer(hba, timer_val);
}
static int ufs_renesas_hce_enable_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
struct ufs_renesas_priv *priv = ufshcd_get_variant(hba);
if (priv->initialized)
return 0;
if (status == PRE_CHANGE)
priv->pre_init(hba);
priv->initialized = true;
return 0;
}
static int ufs_renesas_setup_clocks(struct ufs_hba *hba, bool on,
enum ufs_notify_change_status status)
{
if (on && status == PRE_CHANGE)
pm_runtime_get_sync(hba->dev);
else if (!on && status == POST_CHANGE)
pm_runtime_put(hba->dev);
return 0;
}
static const struct soc_device_attribute ufs_fallback[] = {
{ .soc_id = "r8a779f0", .revision = "ES1.[01]" },
{ /* Sentinel */ }
};
static int ufs_renesas_init(struct ufs_hba *hba)
{
const struct soc_device_attribute *attr;
struct nvmem_cell *cell = NULL;
struct device *dev = hba->dev;
struct ufs_renesas_priv *priv;
u8 *data = NULL;
size_t len;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
ufshcd_set_variant(hba, priv);
hba->quirks |= UFSHCD_QUIRK_HIBERN_FASTAUTO;
attr = soc_device_match(ufs_fallback);
if (attr)
goto fallback;
ret = request_firmware(&priv->fw, UFS_RENESAS_FIRMWARE_NAME, dev);
if (ret) {
dev_warn(dev, "Failed to load firmware\n");
goto fallback;
}
cell = nvmem_cell_get(dev, "calibration");
if (IS_ERR(cell)) {
dev_warn(dev, "No calibration data specified\n");
goto fallback;
}
data = nvmem_cell_read(cell, &len);
if (IS_ERR(data)) {
dev_warn(dev, "Failed to read calibration data: %pe\n", data);
goto fallback;
}
if (len != EFUSE_CALIB_SIZE) {
dev_warn(dev, "Invalid calibration data size %zu\n", len);
goto fallback;
}
memcpy(priv->calib, data, EFUSE_CALIB_SIZE);
priv->pre_init = ufs_renesas_r8a779f0_pre_init;
goto out;
fallback:
dev_info(dev, "Using ES1.0 init code\n");
priv->pre_init = ufs_renesas_r8a779f0_es10_pre_init;
out:
kfree(data);
if (!IS_ERR_OR_NULL(cell))
nvmem_cell_put(cell);
return 0;
}
static void ufs_renesas_exit(struct ufs_hba *hba)
{
struct ufs_renesas_priv *priv = ufshcd_get_variant(hba);
release_firmware(priv->fw);
}
static int ufs_renesas_set_dma_mask(struct ufs_hba *hba)
{
return dma_set_mask_and_coherent(hba->dev, DMA_BIT_MASK(32));
}
static const struct ufs_hba_variant_ops ufs_renesas_vops = {
.name = "renesas",
.init = ufs_renesas_init,
.exit = ufs_renesas_exit,
.set_dma_mask = ufs_renesas_set_dma_mask,
.setup_clocks = ufs_renesas_setup_clocks,
.hce_enable_notify = ufs_renesas_hce_enable_notify,
.dbg_register_dump = ufs_renesas_dbg_register_dump,
};
static const struct of_device_id __maybe_unused ufs_renesas_of_match[] = {
{ .compatible = "renesas,r8a779f0-ufs" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ufs_renesas_of_match);
static int ufs_renesas_probe(struct platform_device *pdev)
{
return ufshcd_pltfrm_init(pdev, &ufs_renesas_vops);
}
static void ufs_renesas_remove(struct platform_device *pdev)
{
ufshcd_pltfrm_remove(pdev);
}
static struct platform_driver ufs_renesas_platform = {
.probe = ufs_renesas_probe,
.remove = ufs_renesas_remove,
.driver = {
.name = "ufshcd-renesas",
.of_match_table = of_match_ptr(ufs_renesas_of_match),
},
};
module_platform_driver(ufs_renesas_platform);
MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>");
MODULE_DESCRIPTION("Renesas UFS host controller driver");
MODULE_LICENSE("Dual MIT/GPL");
+361
View File
@@ -0,0 +1,361 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Rockchip UFS Host Controller driver
*
* Copyright (C) 2025 Rockchip Electronics Co., Ltd.
*/
#include <linux/clk.h>
#include <linux/gpio/consumer.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_wakeup.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <ufs/ufshcd.h>
#include <ufs/unipro.h>
#include "ufshcd-pltfrm.h"
#include "ufs-rockchip.h"
static void ufs_rockchip_controller_reset(struct ufs_rockchip_host *host)
{
reset_control_assert(host->rst);
udelay(1);
reset_control_deassert(host->rst);
}
static int ufs_rockchip_hce_enable_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
int err = 0;
if (status == POST_CHANGE) {
err = ufshcd_dme_reset(hba);
if (err)
return err;
err = ufshcd_dme_enable(hba);
if (err)
return err;
return ufshcd_vops_phy_initialization(hba);
}
/* PRE_CHANGE */
ufs_rockchip_controller_reset(host);
return 0;
}
static void ufs_rockchip_set_pm_lvl(struct ufs_hba *hba)
{
hba->rpm_lvl = UFS_PM_LVL_5;
hba->spm_lvl = UFS_PM_LVL_5;
}
static int ufs_rockchip_rk3576_phy_init(struct ufs_hba *hba)
{
struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(PA_LOCAL_TX_LCC_ENABLE, 0x0), 0x0);
/* enable the mphy DME_SET cfg */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(MPHY_CFG, 0x0), MPHY_CFG_ENABLE);
for (int i = 0; i < 2; i++) {
/* Configuration M - TX */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_CLK_PRD, SEL_TX_LANE0 + i), 0x06);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_CLK_PRD_EN, SEL_TX_LANE0 + i), 0x02);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_VALUE, SEL_TX_LANE0 + i), 0x44);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE1, SEL_TX_LANE0 + i), 0xe6);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE2, SEL_TX_LANE0 + i), 0x07);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_TASE_VALUE, SEL_TX_LANE0 + i), 0x93);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_BASE_NVALUE, SEL_TX_LANE0 + i), 0xc9);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_POWER_SAVING_CTRL, SEL_TX_LANE0 + i), 0x00);
/* Configuration M - RX */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_CLK_PRD, SEL_RX_LANE0 + i), 0x06);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_CLK_PRD_EN, SEL_RX_LANE0 + i), 0x00);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE, SEL_RX_LANE0 + i), 0x58);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_PVALUE1, SEL_RX_LANE0 + i), 0x8c);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_PVALUE2, SEL_RX_LANE0 + i), 0x02);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_OPTION, SEL_RX_LANE0 + i), 0xf6);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_POWER_SAVING_CTRL, SEL_RX_LANE0 + i), 0x69);
}
/* disable the mphy DME_SET cfg */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(MPHY_CFG, 0x0), MPHY_CFG_DISABLE);
ufs_sys_writel(host->mphy_base, 0x80, CMN_REG23);
ufs_sys_writel(host->mphy_base, 0xB5, TRSV0_REG14);
ufs_sys_writel(host->mphy_base, 0xB5, TRSV1_REG14);
ufs_sys_writel(host->mphy_base, 0x03, TRSV0_REG15);
ufs_sys_writel(host->mphy_base, 0x03, TRSV1_REG15);
ufs_sys_writel(host->mphy_base, 0x38, TRSV0_REG08);
ufs_sys_writel(host->mphy_base, 0x38, TRSV1_REG08);
ufs_sys_writel(host->mphy_base, 0x50, TRSV0_REG29);
ufs_sys_writel(host->mphy_base, 0x50, TRSV1_REG29);
ufs_sys_writel(host->mphy_base, 0x80, TRSV0_REG2E);
ufs_sys_writel(host->mphy_base, 0x80, TRSV1_REG2E);
ufs_sys_writel(host->mphy_base, 0x18, TRSV0_REG3C);
ufs_sys_writel(host->mphy_base, 0x18, TRSV1_REG3C);
ufs_sys_writel(host->mphy_base, 0x03, TRSV0_REG16);
ufs_sys_writel(host->mphy_base, 0x03, TRSV1_REG16);
ufs_sys_writel(host->mphy_base, 0x20, TRSV0_REG17);
ufs_sys_writel(host->mphy_base, 0x20, TRSV1_REG17);
ufs_sys_writel(host->mphy_base, 0xC0, TRSV0_REG18);
ufs_sys_writel(host->mphy_base, 0xC0, TRSV1_REG18);
ufs_sys_writel(host->mphy_base, 0x03, CMN_REG25);
ufs_sys_writel(host->mphy_base, 0x03, TRSV0_REG3D);
ufs_sys_writel(host->mphy_base, 0x03, TRSV1_REG3D);
ufs_sys_writel(host->mphy_base, 0xC0, CMN_REG23);
udelay(1);
ufs_sys_writel(host->mphy_base, 0x00, CMN_REG23);
usleep_range(200, 250);
/* start link up */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(MIB_T_DBG_CPORT_TX_ENDIAN, 0), 0x0);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(MIB_T_DBG_CPORT_RX_ENDIAN, 0), 0x0);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(N_DEVICEID, 0), 0x0);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(N_DEVICEID_VALID, 0), 0x1);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(T_PEERDEVICEID, 0), 0x1);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(T_CONNECTIONSTATE, 0), 0x1);
return 0;
}
static int ufs_rockchip_common_init(struct ufs_hba *hba)
{
struct device *dev = hba->dev;
struct platform_device *pdev = to_platform_device(dev);
struct ufs_rockchip_host *host;
int err;
host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
if (!host)
return -ENOMEM;
host->ufs_sys_ctrl = devm_platform_ioremap_resource_byname(pdev, "hci_grf");
if (IS_ERR(host->ufs_sys_ctrl))
return dev_err_probe(dev, PTR_ERR(host->ufs_sys_ctrl),
"Failed to map HCI system control registers\n");
host->ufs_phy_ctrl = devm_platform_ioremap_resource_byname(pdev, "mphy_grf");
if (IS_ERR(host->ufs_phy_ctrl))
return dev_err_probe(dev, PTR_ERR(host->ufs_phy_ctrl),
"Failed to map mphy system control registers\n");
host->mphy_base = devm_platform_ioremap_resource_byname(pdev, "mphy");
if (IS_ERR(host->mphy_base))
return dev_err_probe(dev, PTR_ERR(host->mphy_base),
"Failed to map mphy base registers\n");
host->rst = devm_reset_control_array_get_exclusive(dev);
if (IS_ERR(host->rst))
return dev_err_probe(dev, PTR_ERR(host->rst),
"failed to get reset control\n");
ufs_rockchip_controller_reset(host);
host->ref_out_clk = devm_clk_get_enabled(dev, "ref_out");
if (IS_ERR(host->ref_out_clk))
return dev_err_probe(dev, PTR_ERR(host->ref_out_clk),
"ref_out clock unavailable\n");
host->rst_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(host->rst_gpio))
return dev_err_probe(dev, PTR_ERR(host->rst_gpio),
"failed to get reset gpio\n");
err = devm_clk_bulk_get_all_enabled(dev, &host->clks);
if (err < 0)
return dev_err_probe(dev, err, "failed to enable clocks\n");
host->hba = hba;
ufshcd_set_variant(hba, host);
return 0;
}
static int ufs_rockchip_rk3576_init(struct ufs_hba *hba)
{
struct device *dev = hba->dev;
int ret;
hba->quirks = UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING;
/* Enable BKOPS when suspend */
hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
/* Enable putting device into deep sleep */
hba->caps |= UFSHCD_CAP_DEEPSLEEP;
/* Enable devfreq of UFS */
hba->caps |= UFSHCD_CAP_CLK_SCALING;
/* Enable WriteBooster */
hba->caps |= UFSHCD_CAP_WB_EN;
/* Set the default desired pm level in case no users set via sysfs */
ufs_rockchip_set_pm_lvl(hba);
ret = ufs_rockchip_common_init(hba);
if (ret)
return dev_err_probe(dev, ret, "ufs common init fail\n");
return 0;
}
static int ufs_rockchip_device_reset(struct ufs_hba *hba)
{
struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
gpiod_set_value_cansleep(host->rst_gpio, 1);
usleep_range(20, 25);
gpiod_set_value_cansleep(host->rst_gpio, 0);
usleep_range(20, 25);
return 0;
}
static const struct ufs_hba_variant_ops ufs_hba_rk3576_vops = {
.name = "rk3576",
.init = ufs_rockchip_rk3576_init,
.device_reset = ufs_rockchip_device_reset,
.hce_enable_notify = ufs_rockchip_hce_enable_notify,
.phy_initialization = ufs_rockchip_rk3576_phy_init,
};
static const struct of_device_id ufs_rockchip_of_match[] = {
{ .compatible = "rockchip,rk3576-ufshc", .data = &ufs_hba_rk3576_vops },
{ },
};
MODULE_DEVICE_TABLE(of, ufs_rockchip_of_match);
static int ufs_rockchip_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct ufs_hba_variant_ops *vops;
int err;
vops = device_get_match_data(dev);
if (!vops)
return dev_err_probe(dev, -ENODATA, "ufs_hba_variant_ops not defined.\n");
err = ufshcd_pltfrm_init(pdev, vops);
if (err)
return dev_err_probe(dev, err, "ufshcd_pltfrm_init failed\n");
return 0;
}
static void ufs_rockchip_remove(struct platform_device *pdev)
{
ufshcd_pltfrm_remove(pdev);
}
#ifdef CONFIG_PM
static int ufs_rockchip_runtime_suspend(struct device *dev)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
clk_disable_unprepare(host->ref_out_clk);
/* Do not power down the genpd if rpm_lvl is less than level 5 */
dev_pm_genpd_rpm_always_on(dev, hba->rpm_lvl < UFS_PM_LVL_5);
return ufshcd_runtime_suspend(dev);
}
static int ufs_rockchip_runtime_resume(struct device *dev)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
int err;
err = clk_prepare_enable(host->ref_out_clk);
if (err) {
dev_err(hba->dev, "failed to enable ref_out clock %d\n", err);
return err;
}
ufs_rockchip_controller_reset(host);
return ufshcd_runtime_resume(dev);
}
#endif
#ifdef CONFIG_PM_SLEEP
static int ufs_rockchip_system_suspend(struct device *dev)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
int err;
/*
* If spm_lvl is less than level 5, it means we need to keep the host
* controller in powered-on state. So device_set_awake_path() is
* calling pm core to notify the genpd provider to meet this requirement
*/
if (hba->spm_lvl < UFS_PM_LVL_5)
device_set_awake_path(dev);
err = ufshcd_system_suspend(dev);
if (err) {
dev_err(hba->dev, "UFSHCD system suspend failed %d\n", err);
return err;
}
clk_disable_unprepare(host->ref_out_clk);
return 0;
}
static int ufs_rockchip_system_resume(struct device *dev)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
int err;
err = clk_prepare_enable(host->ref_out_clk);
if (err) {
dev_err(hba->dev, "failed to enable ref_out clock %d\n", err);
return err;
}
return ufshcd_system_resume(dev);
}
#endif
static const struct dev_pm_ops ufs_rockchip_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(ufs_rockchip_system_suspend, ufs_rockchip_system_resume)
SET_RUNTIME_PM_OPS(ufs_rockchip_runtime_suspend, ufs_rockchip_runtime_resume, NULL)
.prepare = ufshcd_suspend_prepare,
.complete = ufshcd_resume_complete,
};
static struct platform_driver ufs_rockchip_pltform = {
.probe = ufs_rockchip_probe,
.remove = ufs_rockchip_remove,
.driver = {
.name = "ufshcd-rockchip",
.pm = &ufs_rockchip_pm_ops,
.of_match_table = ufs_rockchip_of_match,
},
};
module_platform_driver(ufs_rockchip_pltform);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Rockchip UFS Host Driver");
@@ -0,0 +1,90 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Rockchip UFS Host Controller driver
*
* Copyright (C) 2025 Rockchip Electronics Co., Ltd.
*/
#ifndef _UFS_ROCKCHIP_H_
#define _UFS_ROCKCHIP_H_
#define SEL_TX_LANE0 0x0
#define SEL_TX_LANE1 0x1
#define SEL_TX_LANE2 0x2
#define SEL_TX_LANE3 0x3
#define SEL_RX_LANE0 0x4
#define SEL_RX_LANE1 0x5
#define SEL_RX_LANE2 0x6
#define SEL_RX_LANE3 0x7
#define VND_TX_CLK_PRD 0xAA
#define VND_TX_CLK_PRD_EN 0xA9
#define VND_TX_LINERESET_PVALUE2 0xAB
#define VND_TX_LINERESET_PVALUE1 0xAC
#define VND_TX_LINERESET_VALUE 0xAD
#define VND_TX_BASE_NVALUE 0x93
#define VND_TX_TASE_VALUE 0x94
#define VND_TX_POWER_SAVING_CTRL 0x7F
#define VND_RX_CLK_PRD 0x12
#define VND_RX_CLK_PRD_EN 0x11
#define VND_RX_LINERESET_PVALUE2 0x1B
#define VND_RX_LINERESET_PVALUE1 0x1C
#define VND_RX_LINERESET_VALUE 0x1D
#define VND_RX_LINERESET_OPTION 0x25
#define VND_RX_POWER_SAVING_CTRL 0x2F
#define VND_RX_SAVE_DET_CTRL 0x1E
#define CMN_REG23 0x8C
#define CMN_REG25 0x94
#define TRSV0_REG08 0xE0
#define TRSV1_REG08 0x220
#define TRSV0_REG14 0x110
#define TRSV1_REG14 0x250
#define TRSV0_REG15 0x134
#define TRSV1_REG15 0x274
#define TRSV0_REG16 0x128
#define TRSV1_REG16 0x268
#define TRSV0_REG17 0x12C
#define TRSV1_REG17 0x26c
#define TRSV0_REG18 0x120
#define TRSV1_REG18 0x260
#define TRSV0_REG29 0x164
#define TRSV1_REG29 0x2A4
#define TRSV0_REG2E 0x178
#define TRSV1_REG2E 0x2B8
#define TRSV0_REG3C 0x1B0
#define TRSV1_REG3C 0x2F0
#define TRSV0_REG3D 0x1B4
#define TRSV1_REG3D 0x2F4
#define MPHY_CFG 0x200
#define MPHY_CFG_ENABLE 0x40
#define MPHY_CFG_DISABLE 0x0
#define MIB_T_DBG_CPORT_TX_ENDIAN 0xc022
#define MIB_T_DBG_CPORT_RX_ENDIAN 0xc023
struct ufs_rockchip_host {
struct ufs_hba *hba;
void __iomem *ufs_phy_ctrl;
void __iomem *ufs_sys_ctrl;
void __iomem *mphy_base;
struct gpio_desc *rst_gpio;
struct reset_control *rst;
struct clk *ref_out_clk;
struct clk_bulk_data *clks;
uint64_t caps;
};
#define ufs_sys_writel(base, val, reg) \
writel((val), (base) + (reg))
#define ufs_sys_readl(base, reg) readl((base) + (reg))
#define ufs_sys_set_bits(base, mask, reg) \
ufs_sys_writel( \
(base), ((mask) | (ufs_sys_readl((base), (reg)))), (reg))
#define ufs_sys_ctrl_clr_bits(base, mask, reg) \
ufs_sys_writel((base), \
((~(mask)) & (ufs_sys_readl((base), (reg)))), \
(reg))
#endif /* _UFS_ROCKCHIP_H_ */
+450
View File
@@ -0,0 +1,450 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* UNISOC UFS Host Controller driver
*
* Copyright (C) 2022 Unisoc, Inc.
* Author: Zhe Wang <zhe.wang1@unisoc.com>
*/
#include <linux/arm-smccc.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/regulator/consumer.h>
#include <ufs/ufshcd.h>
#include "ufshcd-pltfrm.h"
#include "ufs-sprd.h"
static const struct of_device_id ufs_sprd_of_match[];
static struct ufs_sprd_priv *ufs_sprd_get_priv_data(struct ufs_hba *hba)
{
struct ufs_sprd_host *host = ufshcd_get_variant(hba);
WARN_ON(!host->priv);
return host->priv;
}
static void ufs_sprd_regmap_update(struct ufs_sprd_priv *priv, unsigned int index,
unsigned int reg, unsigned int bits, unsigned int val)
{
regmap_update_bits(priv->sysci[index].regmap, reg, bits, val);
}
static void ufs_sprd_regmap_read(struct ufs_sprd_priv *priv, unsigned int index,
unsigned int reg, unsigned int *val)
{
regmap_read(priv->sysci[index].regmap, reg, val);
}
static void ufs_sprd_get_unipro_ver(struct ufs_hba *hba)
{
struct ufs_sprd_host *host = ufshcd_get_variant(hba);
if (ufshcd_dme_get(hba, UIC_ARG_MIB(PA_LOCALVERINFO), &host->unipro_ver))
host->unipro_ver = 0;
}
static void ufs_sprd_ctrl_uic_compl(struct ufs_hba *hba, bool enable)
{
u32 set = ufshcd_readl(hba, REG_INTERRUPT_ENABLE);
if (enable == true)
set |= UIC_COMMAND_COMPL;
else
set &= ~UIC_COMMAND_COMPL;
ufshcd_writel(hba, set, REG_INTERRUPT_ENABLE);
}
static int ufs_sprd_get_reset_ctrl(struct device *dev, struct ufs_sprd_rst *rci)
{
rci->rc = devm_reset_control_get(dev, rci->name);
if (IS_ERR(rci->rc)) {
dev_err(dev, "failed to get reset ctrl:%s\n", rci->name);
return PTR_ERR(rci->rc);
}
return 0;
}
static int ufs_sprd_get_syscon_reg(struct device *dev, struct ufs_sprd_syscon *sysci)
{
sysci->regmap = syscon_regmap_lookup_by_phandle(dev->of_node, sysci->name);
if (IS_ERR(sysci->regmap)) {
dev_err(dev, "failed to get ufs syscon:%s\n", sysci->name);
return PTR_ERR(sysci->regmap);
}
return 0;
}
static int ufs_sprd_get_vreg(struct device *dev, struct ufs_sprd_vreg *vregi)
{
vregi->vreg = devm_regulator_get(dev, vregi->name);
if (IS_ERR(vregi->vreg)) {
dev_err(dev, "failed to get vreg:%s\n", vregi->name);
return PTR_ERR(vregi->vreg);
}
return 0;
}
static int ufs_sprd_parse_dt(struct device *dev, struct ufs_hba *hba, struct ufs_sprd_host *host)
{
u32 i;
struct ufs_sprd_priv *priv = host->priv;
int ret = 0;
/* Parse UFS reset ctrl info */
for (i = 0; i < SPRD_UFS_RST_MAX; i++) {
if (!priv->rci[i].name)
continue;
ret = ufs_sprd_get_reset_ctrl(dev, &priv->rci[i]);
if (ret)
goto out;
}
/* Parse UFS syscon reg info */
for (i = 0; i < SPRD_UFS_SYSCON_MAX; i++) {
if (!priv->sysci[i].name)
continue;
ret = ufs_sprd_get_syscon_reg(dev, &priv->sysci[i]);
if (ret)
goto out;
}
/* Parse UFS vreg info */
for (i = 0; i < SPRD_UFS_VREG_MAX; i++) {
if (!priv->vregi[i].name)
continue;
ret = ufs_sprd_get_vreg(dev, &priv->vregi[i]);
if (ret)
goto out;
}
out:
return ret;
}
static int ufs_sprd_common_init(struct ufs_hba *hba)
{
struct device *dev = hba->dev;
struct ufs_sprd_host *host;
struct platform_device __maybe_unused *pdev = to_platform_device(dev);
const struct of_device_id *of_id;
int ret = 0;
host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
if (!host)
return -ENOMEM;
of_id = of_match_node(ufs_sprd_of_match, pdev->dev.of_node);
if (of_id->data != NULL)
host->priv = container_of(of_id->data, struct ufs_sprd_priv,
ufs_hba_sprd_vops);
host->hba = hba;
ufshcd_set_variant(hba, host);
hba->caps |= UFSHCD_CAP_CLK_GATING |
UFSHCD_CAP_CRYPTO |
UFSHCD_CAP_WB_EN;
hba->quirks |= UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS;
ret = ufs_sprd_parse_dt(dev, hba, host);
return ret;
}
static int sprd_ufs_pwr_change_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status,
struct ufs_pa_layer_attr *dev_req_params)
{
struct ufs_sprd_host *host = ufshcd_get_variant(hba);
if (status == PRE_CHANGE) {
if (host->unipro_ver >= UFS_UNIPRO_VER_1_8)
ufshcd_dme_configure_adapt(hba, dev_req_params->gear_tx,
PA_INITIAL_ADAPT);
}
return 0;
}
static int ufs_sprd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op,
enum ufs_notify_change_status status)
{
unsigned long flags;
if (status == PRE_CHANGE) {
if (ufshcd_is_auto_hibern8_supported(hba)) {
spin_lock_irqsave(hba->host->host_lock, flags);
ufshcd_writel(hba, 0, REG_AUTO_HIBERNATE_IDLE_TIMER);
spin_unlock_irqrestore(hba->host->host_lock, flags);
}
}
return 0;
}
static void ufs_sprd_n6_host_reset(struct ufs_hba *hba)
{
struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba);
dev_info(hba->dev, "ufs host reset!\n");
reset_control_assert(priv->rci[SPRD_UFSHCI_SOFT_RST].rc);
usleep_range(1000, 1100);
reset_control_deassert(priv->rci[SPRD_UFSHCI_SOFT_RST].rc);
}
static int ufs_sprd_n6_device_reset(struct ufs_hba *hba)
{
struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba);
dev_info(hba->dev, "ufs device reset!\n");
reset_control_assert(priv->rci[SPRD_UFS_DEV_RST].rc);
usleep_range(1000, 1100);
reset_control_deassert(priv->rci[SPRD_UFS_DEV_RST].rc);
return 0;
}
static void ufs_sprd_n6_key_acc_enable(struct ufs_hba *hba)
{
u32 val;
u32 retry = 10;
struct arm_smccc_res res;
check_hce:
/* Key access only can be enabled under HCE enable */
val = ufshcd_readl(hba, REG_CONTROLLER_ENABLE);
if (!(val & CONTROLLER_ENABLE)) {
ufs_sprd_n6_host_reset(hba);
val |= CONTROLLER_ENABLE;
ufshcd_writel(hba, val, REG_CONTROLLER_ENABLE);
usleep_range(1000, 1100);
if (retry) {
retry--;
goto check_hce;
}
goto disable_crypto;
}
arm_smccc_smc(SPRD_SIP_SVC_STORAGE_UFS_CRYPTO_ENABLE,
0, 0, 0, 0, 0, 0, 0, &res);
if (!res.a0)
return;
disable_crypto:
dev_err(hba->dev, "key reg access enable fail, disable crypto\n");
hba->caps &= ~UFSHCD_CAP_CRYPTO;
}
static int ufs_sprd_n6_init(struct ufs_hba *hba)
{
struct ufs_sprd_priv *priv;
int ret = 0;
ret = ufs_sprd_common_init(hba);
if (ret != 0)
return ret;
priv = ufs_sprd_get_priv_data(hba);
ret = regulator_enable(priv->vregi[SPRD_UFS_VDD_MPHY].vreg);
if (ret)
return -ENODEV;
if (hba->caps & UFSHCD_CAP_CRYPTO)
ufs_sprd_n6_key_acc_enable(hba);
return 0;
}
static int ufs_sprd_n6_phy_init(struct ufs_hba *hba)
{
int ret = 0;
uint32_t val = 0;
uint32_t retry = 10;
uint32_t offset;
struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba);
ufshcd_dme_set(hba, UIC_ARG_MIB(CBREFCLKCTRL2), 0x90);
ufshcd_dme_set(hba, UIC_ARG_MIB(CBCRCTRL), 0x01);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RXSQCONTROL,
UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x01);
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RXSQCONTROL,
UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x01);
ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01);
ufshcd_dme_set(hba, UIC_ARG_MIB(CBRATESEL), 0x01);
do {
/* phy_sram_init_done */
ufs_sprd_regmap_read(priv, SPRD_UFS_ANLG, 0xc, &val);
if ((val & 0x1) == 0x1) {
for (offset = 0x40; offset < 0x42; offset++) {
/* Lane afe calibration */
ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGADDRLSB), 0x1c);
ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGADDRMSB), offset);
ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGWRLSB), 0x04);
ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGWRMSB), 0x00);
ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGRDWRSEL), 0x01);
ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01);
}
goto update_phy;
}
udelay(1000);
retry--;
} while (retry > 0);
ret = -ETIMEDOUT;
goto out;
update_phy:
/* phy_sram_ext_ld_done */
ufs_sprd_regmap_update(priv, SPRD_UFS_ANLG, 0xc, 0x2, 0);
ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01);
ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYDISABLE), 0x0);
out:
return ret;
}
static int sprd_ufs_n6_hce_enable_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
int err = 0;
struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba);
if (status == PRE_CHANGE) {
/* phy_sram_ext_ld_done */
ufs_sprd_regmap_update(priv, SPRD_UFS_ANLG, 0xc, 0x2, 0x2);
/* phy_sram_bypass */
ufs_sprd_regmap_update(priv, SPRD_UFS_ANLG, 0xc, 0x4, 0x4);
ufs_sprd_n6_host_reset(hba);
if (hba->caps & UFSHCD_CAP_CRYPTO)
ufs_sprd_n6_key_acc_enable(hba);
}
if (status == POST_CHANGE) {
err = ufs_sprd_n6_phy_init(hba);
if (err) {
dev_err(hba->dev, "Phy setup failed (%d)\n", err);
goto out;
}
ufs_sprd_get_unipro_ver(hba);
}
out:
return err;
}
static void sprd_ufs_n6_h8_notify(struct ufs_hba *hba,
enum uic_cmd_dme cmd,
enum ufs_notify_change_status status)
{
struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba);
if (status == PRE_CHANGE) {
if (cmd == UIC_CMD_DME_HIBER_ENTER)
/*
* Disable UIC COMPL INTR to prevent access to UFSHCI after
* checking HCS.UPMCRS
*/
ufs_sprd_ctrl_uic_compl(hba, false);
if (cmd == UIC_CMD_DME_HIBER_EXIT) {
ufs_sprd_regmap_update(priv, SPRD_UFS_AON_APB, APB_UFSDEV_REG,
APB_UFSDEV_REFCLK_EN, APB_UFSDEV_REFCLK_EN);
ufs_sprd_regmap_update(priv, SPRD_UFS_AON_APB, APB_USB31PLL_CTRL,
APB_USB31PLLV_REF2MPHY, APB_USB31PLLV_REF2MPHY);
}
}
if (status == POST_CHANGE) {
if (cmd == UIC_CMD_DME_HIBER_EXIT)
ufs_sprd_ctrl_uic_compl(hba, true);
if (cmd == UIC_CMD_DME_HIBER_ENTER) {
ufs_sprd_regmap_update(priv, SPRD_UFS_AON_APB, APB_UFSDEV_REG,
APB_UFSDEV_REFCLK_EN, 0);
ufs_sprd_regmap_update(priv, SPRD_UFS_AON_APB, APB_USB31PLL_CTRL,
APB_USB31PLLV_REF2MPHY, 0);
}
}
}
static struct ufs_sprd_priv n6_ufs = {
.rci[SPRD_UFSHCI_SOFT_RST] = { .name = "controller", },
.rci[SPRD_UFS_DEV_RST] = { .name = "device", },
.sysci[SPRD_UFS_ANLG] = { .name = "sprd,ufs-anlg-syscon", },
.sysci[SPRD_UFS_AON_APB] = { .name = "sprd,aon-apb-syscon", },
.vregi[SPRD_UFS_VDD_MPHY] = { .name = "vdd-mphy", },
.ufs_hba_sprd_vops = {
.name = "sprd,ums9620-ufs",
.init = ufs_sprd_n6_init,
.hce_enable_notify = sprd_ufs_n6_hce_enable_notify,
.pwr_change_notify = sprd_ufs_pwr_change_notify,
.hibern8_notify = sprd_ufs_n6_h8_notify,
.device_reset = ufs_sprd_n6_device_reset,
.suspend = ufs_sprd_suspend,
},
};
static const struct of_device_id __maybe_unused ufs_sprd_of_match[] = {
{ .compatible = "sprd,ums9620-ufs", .data = &n6_ufs.ufs_hba_sprd_vops},
{},
};
MODULE_DEVICE_TABLE(of, ufs_sprd_of_match);
static int ufs_sprd_probe(struct platform_device *pdev)
{
int err;
struct device *dev = &pdev->dev;
const struct of_device_id *of_id;
of_id = of_match_node(ufs_sprd_of_match, dev->of_node);
err = ufshcd_pltfrm_init(pdev, of_id->data);
if (err)
dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err);
return err;
}
static void ufs_sprd_remove(struct platform_device *pdev)
{
ufshcd_pltfrm_remove(pdev);
}
static const struct dev_pm_ops ufs_sprd_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
.prepare = ufshcd_suspend_prepare,
.complete = ufshcd_resume_complete,
};
static struct platform_driver ufs_sprd_pltform = {
.probe = ufs_sprd_probe,
.remove = ufs_sprd_remove,
.driver = {
.name = "ufshcd-sprd",
.pm = &ufs_sprd_pm_ops,
.of_match_table = of_match_ptr(ufs_sprd_of_match),
},
};
module_platform_driver(ufs_sprd_pltform);
MODULE_AUTHOR("Zhe Wang <zhe.wang1@unisoc.com>");
MODULE_DESCRIPTION("Unisoc UFS Host Driver");
MODULE_LICENSE("GPL v2");
+85
View File
@@ -0,0 +1,85 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* UNISOC UFS Host Controller driver
*
* Copyright (C) 2022 Unisoc, Inc.
* Author: Zhe Wang <zhe.wang1@unisoc.com>
*/
#ifndef _UFS_SPRD_H_
#define _UFS_SPRD_H_
/* Vendor specific attributes */
#define RXSQCONTROL 0x8009
#define CBRATESEL 0x8114
#define CBCREGADDRLSB 0x8116
#define CBCREGADDRMSB 0x8117
#define CBCREGWRLSB 0x8118
#define CBCREGWRMSB 0x8119
#define CBCREGRDWRSEL 0x811C
#define CBCRCTRL 0x811F
#define CBREFCLKCTRL2 0x8132
#define VS_MPHYDISABLE 0xD0C1
#define APB_UFSDEV_REG 0xCE8
#define APB_UFSDEV_REFCLK_EN 0x2
#define APB_USB31PLL_CTRL 0xCFC
#define APB_USB31PLLV_REF2MPHY 0x1
#define SPRD_SIP_SVC_STORAGE_UFS_CRYPTO_ENABLE \
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \
ARM_SMCCC_SMC_32, \
ARM_SMCCC_OWNER_SIP, \
0x0301)
enum SPRD_UFS_RST_INDEX {
SPRD_UFSHCI_SOFT_RST,
SPRD_UFS_DEV_RST,
SPRD_UFS_RST_MAX
};
enum SPRD_UFS_SYSCON_INDEX {
SPRD_UFS_ANLG,
SPRD_UFS_AON_APB,
SPRD_UFS_SYSCON_MAX
};
enum SPRD_UFS_VREG_INDEX {
SPRD_UFS_VDD_MPHY,
SPRD_UFS_VREG_MAX
};
struct ufs_sprd_rst {
const char *name;
struct reset_control *rc;
};
struct ufs_sprd_syscon {
const char *name;
struct regmap *regmap;
};
struct ufs_sprd_vreg {
const char *name;
struct regulator *vreg;
};
struct ufs_sprd_priv {
struct ufs_sprd_rst rci[SPRD_UFS_RST_MAX];
struct ufs_sprd_syscon sysci[SPRD_UFS_SYSCON_MAX];
struct ufs_sprd_vreg vregi[SPRD_UFS_VREG_MAX];
const struct ufs_hba_variant_ops ufs_hba_sprd_vops;
};
struct ufs_sprd_host {
struct ufs_hba *hba;
struct ufs_sprd_priv *priv;
void __iomem *ufs_dbg_mmio;
enum ufs_unipro_ver unipro_ver;
};
#endif /* _UFS_SPRD_H_ */
+146
View File
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* UFS Host driver for Synopsys Designware Core
*
* Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
*
* Authors: Joao Pinto <jpinto@synopsys.com>
*/
#include <linux/module.h>
#include <ufs/ufshcd.h>
#include <ufs/unipro.h>
#include "ufshcd-dwc.h"
#include "ufshci-dwc.h"
int ufshcd_dwc_dme_set_attrs(struct ufs_hba *hba,
const struct ufshcd_dme_attr_val *v, int n)
{
int ret = 0;
int attr_node = 0;
for (attr_node = 0; attr_node < n; attr_node++) {
ret = ufshcd_dme_set_attr(hba, v[attr_node].attr_sel,
ATTR_SET_NOR, v[attr_node].mib_val, v[attr_node].peer);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL(ufshcd_dwc_dme_set_attrs);
/**
* ufshcd_dwc_program_clk_div() - program clock divider.
* @hba: Private Structure pointer
* @divider_val: clock divider value to be programmed
*
*/
static void ufshcd_dwc_program_clk_div(struct ufs_hba *hba, u32 divider_val)
{
ufshcd_writel(hba, divider_val, DWC_UFS_REG_HCLKDIV);
}
/**
* ufshcd_dwc_link_is_up() - check if link is up.
* @hba: private structure pointer
*
* Return: 0 on success, non-zero value on failure.
*/
static int ufshcd_dwc_link_is_up(struct ufs_hba *hba)
{
int dme_result = 0;
ufshcd_dme_get(hba, UIC_ARG_MIB(VS_POWERSTATE), &dme_result);
if (dme_result == UFSHCD_LINK_IS_UP) {
ufshcd_set_link_active(hba);
return 0;
}
return 1;
}
/**
* ufshcd_dwc_connection_setup() - configure unipro attributes.
* @hba: pointer to drivers private data
*
* This function configures both the local side (host) and the peer side
* (device) unipro attributes to establish the connection to application/
* cport.
* This function is not required if the hardware is properly configured to
* have this connection setup on reset. But invoking this function does no
* harm and should be fine even working with any ufs device.
*
* Return: 0 on success non-zero value on failure.
*/
static int ufshcd_dwc_connection_setup(struct ufs_hba *hba)
{
static const struct ufshcd_dme_attr_val setup_attrs[] = {
{ UIC_ARG_MIB(T_CONNECTIONSTATE), 0, DME_LOCAL },
{ UIC_ARG_MIB(N_DEVICEID), 0, DME_LOCAL },
{ UIC_ARG_MIB(N_DEVICEID_VALID), 0, DME_LOCAL },
{ UIC_ARG_MIB(T_PEERDEVICEID), 1, DME_LOCAL },
{ UIC_ARG_MIB(T_PEERCPORTID), 0, DME_LOCAL },
{ UIC_ARG_MIB(T_TRAFFICCLASS), 0, DME_LOCAL },
{ UIC_ARG_MIB(T_CPORTFLAGS), 0x6, DME_LOCAL },
{ UIC_ARG_MIB(T_CPORTMODE), 1, DME_LOCAL },
{ UIC_ARG_MIB(T_CONNECTIONSTATE), 1, DME_LOCAL },
{ UIC_ARG_MIB(T_CONNECTIONSTATE), 0, DME_PEER },
{ UIC_ARG_MIB(N_DEVICEID), 1, DME_PEER },
{ UIC_ARG_MIB(N_DEVICEID_VALID), 1, DME_PEER },
{ UIC_ARG_MIB(T_PEERDEVICEID), 1, DME_PEER },
{ UIC_ARG_MIB(T_PEERCPORTID), 0, DME_PEER },
{ UIC_ARG_MIB(T_TRAFFICCLASS), 0, DME_PEER },
{ UIC_ARG_MIB(T_CPORTFLAGS), 0x6, DME_PEER },
{ UIC_ARG_MIB(T_CPORTMODE), 1, DME_PEER },
{ UIC_ARG_MIB(T_CONNECTIONSTATE), 1, DME_PEER }
};
return ufshcd_dwc_dme_set_attrs(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
}
/**
* ufshcd_dwc_link_startup_notify() - program clock divider.
* @hba: private structure pointer
* @status: Callback notify status
*
* Return: 0 on success, non-zero value on failure.
*/
int ufshcd_dwc_link_startup_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
int err = 0;
if (status == PRE_CHANGE) {
ufshcd_dwc_program_clk_div(hba, DWC_UFS_REG_HCLKDIV_DIV_125);
err = ufshcd_vops_phy_initialization(hba);
if (err) {
dev_err(hba->dev, "Phy setup failed (%d)\n", err);
goto out;
}
} else { /* POST_CHANGE */
err = ufshcd_dwc_link_is_up(hba);
if (err) {
dev_err(hba->dev, "Link is not up\n");
goto out;
}
err = ufshcd_dwc_connection_setup(hba);
if (err)
dev_err(hba->dev, "Connection setup failed (%d)\n",
err);
}
out:
return err;
}
EXPORT_SYMBOL(ufshcd_dwc_link_startup_notify);
MODULE_AUTHOR("Joao Pinto <Joao.Pinto@synopsys.com>");
MODULE_DESCRIPTION("UFS Host driver for Synopsys Designware Core");
MODULE_LICENSE("Dual BSD/GPL");
+71
View File
@@ -0,0 +1,71 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* UFS Host driver for Synopsys Designware Core
*
* Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
*
* Authors: Joao Pinto <jpinto@synopsys.com>
*/
#ifndef _UFSHCD_DWC_H
#define _UFSHCD_DWC_H
#include <ufs/ufshcd.h>
/* RMMI Attributes */
#define CBREFCLKCTRL2 0x8132
#define CBCRCTRL 0x811F
#define CBC10DIRECTCONF2 0x810E
#define CBRATESEL 0x8114
#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
/* M-PHY registers */
#define RX_OVRD_IN_1(n) (0x3006 + ((n) * 0x100))
#define RX_PCS_OUT(n) (0x300F + ((n) * 0x100))
#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))
/* Tx/Rx FSM state */
enum rx_fsm_state {
RX_STATE_DISABLED = 0,
RX_STATE_HIBERN8 = 1,
RX_STATE_SLEEP = 2,
RX_STATE_STALL = 3,
RX_STATE_LSBURST = 4,
RX_STATE_HSBURST = 5,
};
enum tx_fsm_state {
TX_STATE_DISABLED = 0,
TX_STATE_HIBERN8 = 1,
TX_STATE_SLEEP = 2,
TX_STATE_STALL = 3,
TX_STATE_LSBURST = 4,
TX_STATE_HSBURST = 5,
};
struct ufshcd_dme_attr_val {
u32 attr_sel;
u32 mib_val;
u8 peer;
};
int ufshcd_dwc_link_startup_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status);
int ufshcd_dwc_dme_set_attrs(struct ufs_hba *hba,
const struct ufshcd_dme_attr_val *v, int n);
#endif /* End of Header */
+718
View File
@@ -0,0 +1,718 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Universal Flash Storage Host controller PCI glue driver
*
* Copyright (C) 2011-2013 Samsung India Software Operations
*
* Authors:
* Santosh Yaraganavi <santosh.sy@samsung.com>
* Vinayak Holikatti <h.vinayak@samsung.com>
*/
#include <ufs/ufshcd.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/pm_qos.h>
#include <linux/suspend.h>
#include <linux/debugfs.h>
#include <linux/uuid.h>
#include <linux/acpi.h>
#include <linux/gpio/consumer.h>
#define MAX_SUPP_MAC 64
enum intel_ufs_dsm_func_id {
INTEL_DSM_FNS = 0,
INTEL_DSM_RESET = 1,
};
struct intel_host {
u32 dsm_fns;
u32 active_ltr;
u32 idle_ltr;
int saved_spm_lvl;
struct dentry *debugfs_root;
struct gpio_desc *reset_gpio;
};
static const guid_t intel_dsm_guid =
GUID_INIT(0x1A4832A0, 0x7D03, 0x43CA,
0xB0, 0x20, 0xF6, 0xDC, 0xD1, 0x2A, 0x19, 0x50);
static bool __intel_dsm_supported(struct intel_host *host,
enum intel_ufs_dsm_func_id fn)
{
return fn < 32 && fn >= 0 && (host->dsm_fns & (1u << fn));
}
#define INTEL_DSM_SUPPORTED(host, name) \
__intel_dsm_supported(host, INTEL_DSM_##name)
static int __intel_dsm(struct intel_host *intel_host, struct device *dev,
unsigned int fn, u32 *result)
{
union acpi_object *obj;
int err = 0;
size_t len;
obj = acpi_evaluate_dsm_typed(ACPI_HANDLE(dev), &intel_dsm_guid, 0, fn, NULL,
ACPI_TYPE_BUFFER);
if (!obj)
return -EOPNOTSUPP;
if (obj->buffer.length < 1) {
err = -EINVAL;
goto out;
}
len = min_t(size_t, obj->buffer.length, 4);
*result = 0;
memcpy(result, obj->buffer.pointer, len);
out:
ACPI_FREE(obj);
return err;
}
static int intel_dsm(struct intel_host *intel_host, struct device *dev,
unsigned int fn, u32 *result)
{
if (!__intel_dsm_supported(intel_host, fn))
return -EOPNOTSUPP;
return __intel_dsm(intel_host, dev, fn, result);
}
static void intel_dsm_init(struct intel_host *intel_host, struct device *dev)
{
int err;
err = __intel_dsm(intel_host, dev, INTEL_DSM_FNS, &intel_host->dsm_fns);
dev_dbg(dev, "DSM fns %#x, error %d\n", intel_host->dsm_fns, err);
}
static int ufs_intel_hce_enable_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
/* Cannot enable ICE until after HC enable */
if (status == POST_CHANGE && hba->caps & UFSHCD_CAP_CRYPTO) {
u32 hce = ufshcd_readl(hba, REG_CONTROLLER_ENABLE);
hce |= CRYPTO_GENERAL_ENABLE;
ufshcd_writel(hba, hce, REG_CONTROLLER_ENABLE);
}
return 0;
}
static int ufs_intel_disable_lcc(struct ufs_hba *hba)
{
u32 attr = UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE);
u32 lcc_enable = 0;
ufshcd_dme_get(hba, attr, &lcc_enable);
if (lcc_enable)
ufshcd_disable_host_tx_lcc(hba);
return 0;
}
static int ufs_intel_link_startup_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
int err = 0;
switch (status) {
case PRE_CHANGE:
err = ufs_intel_disable_lcc(hba);
break;
case POST_CHANGE:
break;
default:
break;
}
return err;
}
static int ufs_intel_set_lanes(struct ufs_hba *hba, u32 lanes)
{
struct ufs_pa_layer_attr pwr_info = hba->pwr_info;
int ret;
pwr_info.lane_rx = lanes;
pwr_info.lane_tx = lanes;
ret = ufshcd_change_power_mode(hba, &pwr_info,
UFSHCD_PMC_POLICY_DONT_FORCE);
if (ret)
dev_err(hba->dev, "%s: Setting %u lanes, err = %d\n",
__func__, lanes, ret);
return ret;
}
static int ufs_intel_lkf_pwr_change_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status,
struct ufs_pa_layer_attr *dev_req_params)
{
int err = 0;
switch (status) {
case PRE_CHANGE:
if (ufshcd_is_hs_mode(dev_req_params) &&
(hba->pwr_info.lane_rx != 2 || hba->pwr_info.lane_tx != 2))
ufs_intel_set_lanes(hba, 2);
break;
case POST_CHANGE:
if (ufshcd_is_hs_mode(dev_req_params)) {
u32 peer_granularity;
usleep_range(1000, 1250);
err = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_GRANULARITY),
&peer_granularity);
}
break;
default:
break;
}
return err;
}
static int ufs_intel_lkf_apply_dev_quirks(struct ufs_hba *hba)
{
u32 granularity, peer_granularity;
u32 pa_tactivate, peer_pa_tactivate;
int ret;
ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_GRANULARITY), &granularity);
if (ret)
goto out;
ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_GRANULARITY), &peer_granularity);
if (ret)
goto out;
ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_TACTIVATE), &pa_tactivate);
if (ret)
goto out;
ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_TACTIVATE), &peer_pa_tactivate);
if (ret)
goto out;
if (granularity == peer_granularity) {
u32 new_peer_pa_tactivate = pa_tactivate + 2;
ret = ufshcd_dme_peer_set(hba, UIC_ARG_MIB(PA_TACTIVATE), new_peer_pa_tactivate);
}
out:
return ret;
}
#define INTEL_ACTIVELTR 0x804
#define INTEL_IDLELTR 0x808
#define INTEL_LTR_REQ BIT(15)
#define INTEL_LTR_SCALE_MASK GENMASK(11, 10)
#define INTEL_LTR_SCALE_1US (2 << 10)
#define INTEL_LTR_SCALE_32US (3 << 10)
#define INTEL_LTR_VALUE_MASK GENMASK(9, 0)
static void intel_cache_ltr(struct ufs_hba *hba)
{
struct intel_host *host = ufshcd_get_variant(hba);
host->active_ltr = readl(hba->mmio_base + INTEL_ACTIVELTR);
host->idle_ltr = readl(hba->mmio_base + INTEL_IDLELTR);
}
static void intel_ltr_set(struct device *dev, s32 val)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
struct intel_host *host = ufshcd_get_variant(hba);
u32 ltr;
pm_runtime_get_sync(dev);
/*
* Program latency tolerance (LTR) accordingly what has been asked
* by the PM QoS layer or disable it in case we were passed
* negative value or PM_QOS_LATENCY_ANY.
*/
ltr = readl(hba->mmio_base + INTEL_ACTIVELTR);
if (val == PM_QOS_LATENCY_ANY || val < 0) {
ltr &= ~INTEL_LTR_REQ;
} else {
ltr |= INTEL_LTR_REQ;
ltr &= ~INTEL_LTR_SCALE_MASK;
ltr &= ~INTEL_LTR_VALUE_MASK;
if (val > INTEL_LTR_VALUE_MASK) {
val >>= 5;
if (val > INTEL_LTR_VALUE_MASK)
val = INTEL_LTR_VALUE_MASK;
ltr |= INTEL_LTR_SCALE_32US | val;
} else {
ltr |= INTEL_LTR_SCALE_1US | val;
}
}
if (ltr == host->active_ltr)
goto out;
writel(ltr, hba->mmio_base + INTEL_ACTIVELTR);
writel(ltr, hba->mmio_base + INTEL_IDLELTR);
/* Cache the values into intel_host structure */
intel_cache_ltr(hba);
out:
pm_runtime_put(dev);
}
static void intel_ltr_expose(struct device *dev)
{
dev->power.set_latency_tolerance = intel_ltr_set;
dev_pm_qos_expose_latency_tolerance(dev);
}
static void intel_ltr_hide(struct device *dev)
{
dev_pm_qos_hide_latency_tolerance(dev);
dev->power.set_latency_tolerance = NULL;
}
static void intel_add_debugfs(struct ufs_hba *hba)
{
struct dentry *dir = debugfs_create_dir(dev_name(hba->dev), NULL);
struct intel_host *host = ufshcd_get_variant(hba);
intel_cache_ltr(hba);
host->debugfs_root = dir;
debugfs_create_x32("active_ltr", 0444, dir, &host->active_ltr);
debugfs_create_x32("idle_ltr", 0444, dir, &host->idle_ltr);
}
static void intel_remove_debugfs(struct ufs_hba *hba)
{
struct intel_host *host = ufshcd_get_variant(hba);
debugfs_remove_recursive(host->debugfs_root);
}
static int ufs_intel_device_reset(struct ufs_hba *hba)
{
struct intel_host *host = ufshcd_get_variant(hba);
if (INTEL_DSM_SUPPORTED(host, RESET)) {
u32 result = 0;
int err;
err = intel_dsm(host, hba->dev, INTEL_DSM_RESET, &result);
if (!err && !result)
err = -EIO;
if (err)
dev_err(hba->dev, "%s: DSM error %d result %u\n",
__func__, err, result);
return err;
}
if (!host->reset_gpio)
return -EOPNOTSUPP;
gpiod_set_value_cansleep(host->reset_gpio, 1);
usleep_range(10, 15);
gpiod_set_value_cansleep(host->reset_gpio, 0);
usleep_range(10, 15);
return 0;
}
static struct gpio_desc *ufs_intel_get_reset_gpio(struct device *dev)
{
/* GPIO in _DSD has active low setting */
return devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
}
static int ufs_intel_common_init(struct ufs_hba *hba)
{
struct intel_host *host;
hba->caps |= UFSHCD_CAP_RPM_AUTOSUSPEND;
host = devm_kzalloc(hba->dev, sizeof(*host), GFP_KERNEL);
if (!host)
return -ENOMEM;
host->saved_spm_lvl = -1;
ufshcd_set_variant(hba, host);
intel_dsm_init(host, hba->dev);
if (INTEL_DSM_SUPPORTED(host, RESET)) {
if (hba->vops->device_reset)
hba->caps |= UFSHCD_CAP_DEEPSLEEP;
} else {
if (hba->vops->device_reset)
host->reset_gpio = ufs_intel_get_reset_gpio(hba->dev);
if (IS_ERR(host->reset_gpio)) {
dev_err(hba->dev, "%s: failed to get reset GPIO, error %ld\n",
__func__, PTR_ERR(host->reset_gpio));
host->reset_gpio = NULL;
}
if (host->reset_gpio) {
gpiod_set_value_cansleep(host->reset_gpio, 0);
hba->caps |= UFSHCD_CAP_DEEPSLEEP;
}
}
intel_ltr_expose(hba->dev);
intel_add_debugfs(hba);
return 0;
}
static void ufs_intel_common_exit(struct ufs_hba *hba)
{
intel_remove_debugfs(hba);
intel_ltr_hide(hba->dev);
}
static int ufs_intel_resume(struct ufs_hba *hba, enum ufs_pm_op op)
{
if (ufshcd_is_link_hibern8(hba)) {
int ret = ufshcd_uic_hibern8_exit(hba);
if (!ret) {
ufshcd_set_link_active(hba);
} else {
dev_err(hba->dev, "%s: hibern8 exit failed %d\n",
__func__, ret);
/*
* Force reset and restore. Any other actions can lead
* to an unrecoverable state.
*/
ufshcd_set_link_off(hba);
}
}
return 0;
}
static int ufs_intel_ehl_init(struct ufs_hba *hba)
{
hba->quirks |= UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8;
return ufs_intel_common_init(hba);
}
static int ufs_intel_lkf_init(struct ufs_hba *hba)
{
int err;
hba->nop_out_timeout = 200;
hba->quirks |= UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8;
hba->caps |= UFSHCD_CAP_CRYPTO;
err = ufs_intel_common_init(hba);
/* LKF always needs a full reset, so set PM accordingly */
if (hba->caps & UFSHCD_CAP_DEEPSLEEP) {
hba->spm_lvl = UFS_PM_LVL_6;
hba->rpm_lvl = UFS_PM_LVL_6;
} else {
hba->spm_lvl = UFS_PM_LVL_5;
hba->rpm_lvl = UFS_PM_LVL_5;
}
return err;
}
static int ufs_intel_adl_init(struct ufs_hba *hba)
{
hba->nop_out_timeout = 200;
hba->quirks |= UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8 |
UFSHCD_QUIRK_PERFORM_LINK_STARTUP_ONCE;
hba->caps |= UFSHCD_CAP_WB_EN;
return ufs_intel_common_init(hba);
}
static int ufs_intel_mtl_init(struct ufs_hba *hba)
{
hba->rpm_lvl = UFS_PM_LVL_2;
hba->spm_lvl = UFS_PM_LVL_2;
hba->caps |= UFSHCD_CAP_CRYPTO | UFSHCD_CAP_WB_EN;
return ufs_intel_common_init(hba);
}
static int ufs_qemu_get_hba_mac(struct ufs_hba *hba)
{
return MAX_SUPP_MAC;
}
static int ufs_qemu_mcq_config_resource(struct ufs_hba *hba)
{
hba->mcq_base = hba->mmio_base + ufshcd_mcq_queue_cfg_addr(hba);
return 0;
}
static int ufs_qemu_op_runtime_config(struct ufs_hba *hba)
{
struct ufshcd_mcq_opr_info_t *opr;
int i;
u32 sqdao = ufsmcq_readl(hba, ufshcd_mcq_cfg_offset(REG_SQDAO, 0));
u32 sqisao = ufsmcq_readl(hba, ufshcd_mcq_cfg_offset(REG_SQISAO, 0));
u32 cqdao = ufsmcq_readl(hba, ufshcd_mcq_cfg_offset(REG_CQDAO, 0));
u32 cqisao = ufsmcq_readl(hba, ufshcd_mcq_cfg_offset(REG_CQISAO, 0));
hba->mcq_opr[OPR_SQD].offset = sqdao;
hba->mcq_opr[OPR_SQIS].offset = sqisao;
hba->mcq_opr[OPR_CQD].offset = cqdao;
hba->mcq_opr[OPR_CQIS].offset = cqisao;
for (i = 0; i < OPR_MAX; i++) {
opr = &hba->mcq_opr[i];
opr->stride = 48;
opr->base = hba->mmio_base + opr->offset;
}
return 0;
}
static struct ufs_hba_variant_ops ufs_qemu_hba_vops = {
.name = "qemu-pci",
.get_hba_mac = ufs_qemu_get_hba_mac,
.mcq_config_resource = ufs_qemu_mcq_config_resource,
.op_runtime_config = ufs_qemu_op_runtime_config,
};
static struct ufs_hba_variant_ops ufs_intel_cnl_hba_vops = {
.name = "intel-pci",
.init = ufs_intel_common_init,
.exit = ufs_intel_common_exit,
.link_startup_notify = ufs_intel_link_startup_notify,
.resume = ufs_intel_resume,
};
static struct ufs_hba_variant_ops ufs_intel_ehl_hba_vops = {
.name = "intel-pci",
.init = ufs_intel_ehl_init,
.exit = ufs_intel_common_exit,
.link_startup_notify = ufs_intel_link_startup_notify,
.resume = ufs_intel_resume,
};
static struct ufs_hba_variant_ops ufs_intel_lkf_hba_vops = {
.name = "intel-pci",
.init = ufs_intel_lkf_init,
.exit = ufs_intel_common_exit,
.hce_enable_notify = ufs_intel_hce_enable_notify,
.link_startup_notify = ufs_intel_link_startup_notify,
.pwr_change_notify = ufs_intel_lkf_pwr_change_notify,
.apply_dev_quirks = ufs_intel_lkf_apply_dev_quirks,
.resume = ufs_intel_resume,
.device_reset = ufs_intel_device_reset,
};
static struct ufs_hba_variant_ops ufs_intel_adl_hba_vops = {
.name = "intel-pci",
.init = ufs_intel_adl_init,
.exit = ufs_intel_common_exit,
.link_startup_notify = ufs_intel_link_startup_notify,
.resume = ufs_intel_resume,
.device_reset = ufs_intel_device_reset,
};
static struct ufs_hba_variant_ops ufs_intel_mtl_hba_vops = {
.name = "intel-pci",
.init = ufs_intel_mtl_init,
.exit = ufs_intel_common_exit,
.hce_enable_notify = ufs_intel_hce_enable_notify,
.link_startup_notify = ufs_intel_link_startup_notify,
.resume = ufs_intel_resume,
.device_reset = ufs_intel_device_reset,
};
#ifdef CONFIG_PM_SLEEP
static int ufshcd_pci_restore(struct device *dev)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
/* Force a full reset and restore */
ufshcd_set_link_off(hba);
return ufshcd_system_resume(dev);
}
static int ufs_intel_suspend_prepare(struct device *dev)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
struct intel_host *host = ufshcd_get_variant(hba);
int err;
/*
* Only s2idle (S0ix) retains link state. Force power-off
* (UFS_PM_LVL_5) for any other case.
*/
if (pm_suspend_target_state != PM_SUSPEND_TO_IDLE && hba->spm_lvl < UFS_PM_LVL_5) {
host->saved_spm_lvl = hba->spm_lvl;
hba->spm_lvl = UFS_PM_LVL_5;
}
err = ufshcd_suspend_prepare(dev);
if (err < 0 && host->saved_spm_lvl != -1) {
hba->spm_lvl = host->saved_spm_lvl;
host->saved_spm_lvl = -1;
}
return err;
}
static void ufs_intel_resume_complete(struct device *dev)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
struct intel_host *host = ufshcd_get_variant(hba);
ufshcd_resume_complete(dev);
if (host->saved_spm_lvl != -1) {
hba->spm_lvl = host->saved_spm_lvl;
host->saved_spm_lvl = -1;
}
}
static int ufshcd_pci_suspend_prepare(struct device *dev)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
if (!strcmp(hba->vops->name, "intel-pci"))
return ufs_intel_suspend_prepare(dev);
return ufshcd_suspend_prepare(dev);
}
static void ufshcd_pci_resume_complete(struct device *dev)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
if (!strcmp(hba->vops->name, "intel-pci")) {
ufs_intel_resume_complete(dev);
return;
}
ufshcd_resume_complete(dev);
}
#endif
/**
* ufshcd_pci_remove - de-allocate PCI/SCSI host and host memory space
* data structure memory
* @pdev: pointer to PCI handle
*/
static void ufshcd_pci_remove(struct pci_dev *pdev)
{
struct ufs_hba *hba = pci_get_drvdata(pdev);
pm_runtime_forbid(&pdev->dev);
pm_runtime_get_noresume(&pdev->dev);
ufshcd_remove(hba);
}
/**
* ufshcd_pci_probe - probe routine of the driver
* @pdev: pointer to PCI device handle
* @id: PCI device id
*
* Return: 0 on success, non-zero value on failure.
*/
static int
ufshcd_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct ufs_hba *hba;
void __iomem *mmio_base;
int err;
err = pcim_enable_device(pdev);
if (err) {
dev_err(&pdev->dev, "pcim_enable_device failed\n");
return err;
}
pci_set_master(pdev);
mmio_base = pcim_iomap_region(pdev, 0, UFSHCD);
if (IS_ERR(mmio_base)) {
dev_err(&pdev->dev, "request and iomap failed\n");
return PTR_ERR(mmio_base);
}
err = ufshcd_alloc_host(&pdev->dev, &hba);
if (err) {
dev_err(&pdev->dev, "Allocation failed\n");
return err;
}
hba->vops = (struct ufs_hba_variant_ops *)id->driver_data;
err = ufshcd_init(hba, mmio_base, pdev->irq);
if (err) {
dev_err(&pdev->dev, "Initialization failed\n");
return err;
}
pm_runtime_put_noidle(&pdev->dev);
pm_runtime_allow(&pdev->dev);
return 0;
}
static const struct dev_pm_ops ufshcd_pci_pm_ops = {
SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
#ifdef CONFIG_PM_SLEEP
.suspend = ufshcd_system_suspend,
.resume = ufshcd_system_resume,
.freeze = ufshcd_system_suspend,
.thaw = ufshcd_system_resume,
.poweroff = ufshcd_system_suspend,
.restore = ufshcd_pci_restore,
.prepare = ufshcd_pci_suspend_prepare,
.complete = ufshcd_pci_resume_complete,
#endif
};
static const struct pci_device_id ufshcd_pci_tbl[] = {
{ PCI_VDEVICE(REDHAT, 0x0013), .driver_data = (kernel_ulong_t)&ufs_qemu_hba_vops },
{ PCI_VDEVICE(SAMSUNG, 0xC00C), .driver_data = 0 },
{ PCI_VDEVICE(INTEL, 0x9DFA), .driver_data = (kernel_ulong_t)&ufs_intel_cnl_hba_vops },
{ PCI_VDEVICE(INTEL, 0x4B41), .driver_data = (kernel_ulong_t)&ufs_intel_ehl_hba_vops },
{ PCI_VDEVICE(INTEL, 0x4B43), .driver_data = (kernel_ulong_t)&ufs_intel_ehl_hba_vops },
{ PCI_VDEVICE(INTEL, 0x98FA), .driver_data = (kernel_ulong_t)&ufs_intel_lkf_hba_vops },
{ PCI_VDEVICE(INTEL, 0x51FF), .driver_data = (kernel_ulong_t)&ufs_intel_adl_hba_vops },
{ PCI_VDEVICE(INTEL, 0x54FF), .driver_data = (kernel_ulong_t)&ufs_intel_adl_hba_vops },
{ PCI_VDEVICE(INTEL, 0x7E47), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops },
{ PCI_VDEVICE(INTEL, 0xA847), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops },
{ PCI_VDEVICE(INTEL, 0x7747), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops },
{ PCI_VDEVICE(INTEL, 0xE447), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops },
{ PCI_VDEVICE(INTEL, 0x4D47), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops },
{ PCI_VDEVICE(INTEL, 0xD335), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops },
{ PCI_VDEVICE(AMD, 0x1B29), .driver_data = 0 },
{ } /* terminate list */
};
MODULE_DEVICE_TABLE(pci, ufshcd_pci_tbl);
static struct pci_driver ufshcd_pci_driver = {
.name = UFSHCD,
.id_table = ufshcd_pci_tbl,
.probe = ufshcd_pci_probe,
.remove = ufshcd_pci_remove,
.driver = {
.pm = &ufshcd_pci_pm_ops
},
};
module_pci_driver(ufshcd_pci_driver);
MODULE_AUTHOR("Santosh Yaragnavi <santosh.sy@samsung.com>");
MODULE_AUTHOR("Vinayak Holikatti <h.vinayak@samsung.com>");
MODULE_DESCRIPTION("UFS host controller PCI glue driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,569 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Universal Flash Storage Host controller Platform bus based glue driver
* Copyright (C) 2011-2013 Samsung India Software Operations
*
* Authors:
* Santosh Yaraganavi <santosh.sy@samsung.com>
* Vinayak Holikatti <h.vinayak@samsung.com>
*/
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <linux/of.h>
#include <ufs/ufshcd.h>
#include "ufshcd-pltfrm.h"
#include <ufs/unipro.h>
#define UFSHCD_DEFAULT_LANES_PER_DIRECTION 2
static int ufshcd_parse_clock_info(struct ufs_hba *hba)
{
int ret = 0;
int cnt;
int i;
struct device *dev = hba->dev;
struct device_node *np = dev->of_node;
const char *name;
u32 *clkfreq = NULL;
struct ufs_clk_info *clki;
ssize_t sz = 0;
if (!np)
goto out;
cnt = of_property_count_strings(np, "clock-names");
if (!cnt || (cnt == -EINVAL)) {
dev_info(dev, "%s: Unable to find clocks, assuming enabled\n",
__func__);
} else if (cnt < 0) {
dev_err(dev, "%s: count clock strings failed, err %d\n",
__func__, cnt);
ret = cnt;
}
if (cnt <= 0)
goto out;
sz = of_property_count_u32_elems(np, "freq-table-hz");
if (sz <= 0) {
dev_info(dev, "freq-table-hz property not specified\n");
goto out;
}
if (sz != 2 * cnt) {
dev_err(dev, "%s len mismatch\n", "freq-table-hz");
ret = -EINVAL;
goto out;
}
clkfreq = devm_kcalloc(dev, sz, sizeof(*clkfreq),
GFP_KERNEL);
if (!clkfreq) {
ret = -ENOMEM;
goto out;
}
ret = of_property_read_u32_array(np, "freq-table-hz",
clkfreq, sz);
if (ret && (ret != -EINVAL)) {
dev_err(dev, "%s: error reading array %d\n",
"freq-table-hz", ret);
return ret;
}
for (i = 0; i < sz; i += 2) {
ret = of_property_read_string_index(np, "clock-names", i/2,
&name);
if (ret)
goto out;
clki = devm_kzalloc(dev, sizeof(*clki), GFP_KERNEL);
if (!clki) {
ret = -ENOMEM;
goto out;
}
clki->min_freq = clkfreq[i];
clki->max_freq = clkfreq[i+1];
clki->name = devm_kstrdup(dev, name, GFP_KERNEL);
if (!clki->name) {
ret = -ENOMEM;
goto out;
}
if (!strcmp(name, "ref_clk"))
clki->keep_link_active = true;
dev_dbg(dev, "%s: min %u max %u name %s\n", "freq-table-hz",
clki->min_freq, clki->max_freq, clki->name);
list_add_tail(&clki->list, &hba->clk_list_head);
}
out:
return ret;
}
static bool phandle_exists(const struct device_node *np,
const char *phandle_name, int index)
{
struct device_node *parse_np = of_parse_phandle(np, phandle_name, index);
if (parse_np)
of_node_put(parse_np);
return parse_np != NULL;
}
#define MAX_PROP_SIZE 32
int ufshcd_populate_vreg(struct device *dev, const char *name,
struct ufs_vreg **out_vreg, bool skip_current)
{
char prop_name[MAX_PROP_SIZE];
struct ufs_vreg *vreg = NULL;
struct device_node *np = dev->of_node;
if (!np) {
dev_err(dev, "%s: non DT initialization\n", __func__);
goto out;
}
snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", name);
if (!phandle_exists(np, prop_name, 0)) {
dev_info(dev, "%s: Unable to find %s regulator, assuming enabled\n",
__func__, prop_name);
goto out;
}
vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL);
if (!vreg)
return -ENOMEM;
vreg->name = devm_kstrdup(dev, name, GFP_KERNEL);
if (!vreg->name)
return -ENOMEM;
if (skip_current) {
vreg->max_uA = 0;
goto out;
}
snprintf(prop_name, MAX_PROP_SIZE, "%s-max-microamp", name);
if (of_property_read_u32(np, prop_name, &vreg->max_uA)) {
dev_info(dev, "%s: unable to find %s\n", __func__, prop_name);
vreg->max_uA = 0;
}
out:
*out_vreg = vreg;
return 0;
}
EXPORT_SYMBOL_GPL(ufshcd_populate_vreg);
/**
* ufshcd_parse_regulator_info - get regulator info from device tree
* @hba: per adapter instance
*
* Get regulator info from device tree for vcc, vccq, vccq2 power supplies.
* If any of the supplies are not defined it is assumed that they are always-on
* and hence return zero. If the property is defined but parsing is failed
* then return corresponding error.
*
* Return: 0 upon success; < 0 upon failure.
*/
static int ufshcd_parse_regulator_info(struct ufs_hba *hba)
{
int err;
struct device *dev = hba->dev;
struct ufs_vreg_info *info = &hba->vreg_info;
err = ufshcd_populate_vreg(dev, "vdd-hba", &info->vdd_hba, true);
if (err)
goto out;
err = ufshcd_populate_vreg(dev, "vcc", &info->vcc, false);
if (err)
goto out;
err = ufshcd_populate_vreg(dev, "vccq", &info->vccq, false);
if (err)
goto out;
err = ufshcd_populate_vreg(dev, "vccq2", &info->vccq2, false);
out:
return err;
}
static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba)
{
struct device *dev = hba->dev;
int ret;
ret = of_property_read_u32(dev->of_node, "lanes-per-direction",
&hba->lanes_per_direction);
if (ret) {
dev_dbg(hba->dev,
"%s: failed to read lanes-per-direction, ret=%d\n",
__func__, ret);
hba->lanes_per_direction = UFSHCD_DEFAULT_LANES_PER_DIRECTION;
}
}
/**
* ufshcd_parse_clock_min_max_freq - Parse MIN and MAX clocks freq
* @hba: per adapter instance
*
* This function parses MIN and MAX frequencies of all clocks required
* by the host drivers.
*
* Returns 0 for success and non-zero for failure
*/
static int ufshcd_parse_clock_min_max_freq(struct ufs_hba *hba)
{
struct list_head *head = &hba->clk_list_head;
struct ufs_clk_info *clki;
struct dev_pm_opp *opp;
unsigned long freq;
u8 idx = 0;
list_for_each_entry(clki, head, list) {
if (!clki->name)
continue;
clki->clk = devm_clk_get(hba->dev, clki->name);
if (IS_ERR(clki->clk))
continue;
/* Find Max Freq */
freq = ULONG_MAX;
opp = dev_pm_opp_find_freq_floor_indexed(hba->dev, &freq, idx);
if (IS_ERR(opp)) {
dev_err(hba->dev, "Failed to find OPP for MAX frequency\n");
return PTR_ERR(opp);
}
clki->max_freq = dev_pm_opp_get_freq_indexed(opp, idx);
dev_pm_opp_put(opp);
/* Find Min Freq */
freq = 0;
opp = dev_pm_opp_find_freq_ceil_indexed(hba->dev, &freq, idx);
if (IS_ERR(opp)) {
dev_err(hba->dev, "Failed to find OPP for MIN frequency\n");
return PTR_ERR(opp);
}
clki->min_freq = dev_pm_opp_get_freq_indexed(opp, idx++);
dev_pm_opp_put(opp);
}
return 0;
}
static int ufshcd_parse_operating_points(struct ufs_hba *hba)
{
struct device *dev = hba->dev;
struct device_node *np = dev->of_node;
struct dev_pm_opp_config config = {};
struct ufs_clk_info *clki;
const char **clk_names;
int cnt, i, ret;
if (!of_property_present(np, "operating-points-v2"))
return 0;
if (of_property_present(np, "freq-table-hz")) {
dev_err(dev, "%s: operating-points and freq-table-hz are incompatible\n",
__func__);
return -EINVAL;
}
cnt = of_property_count_strings(np, "clock-names");
if (cnt <= 0) {
dev_err(dev, "%s: Missing clock-names\n", __func__);
return -ENODEV;
}
/* OPP expects clk_names to be NULL terminated */
clk_names = devm_kcalloc(dev, cnt + 1, sizeof(*clk_names), GFP_KERNEL);
if (!clk_names)
return -ENOMEM;
/*
* We still need to get reference to all clocks as the UFS core uses
* them separately.
*/
for (i = 0; i < cnt; i++) {
ret = of_property_read_string_index(np, "clock-names", i,
&clk_names[i]);
if (ret)
return ret;
clki = devm_kzalloc(dev, sizeof(*clki), GFP_KERNEL);
if (!clki)
return -ENOMEM;
clki->name = devm_kstrdup(dev, clk_names[i], GFP_KERNEL);
if (!clki->name)
return -ENOMEM;
if (!strcmp(clk_names[i], "ref_clk"))
clki->keep_link_active = true;
list_add_tail(&clki->list, &hba->clk_list_head);
}
config.clk_names = clk_names,
config.config_clks = ufshcd_opp_config_clks;
ret = devm_pm_opp_set_config(dev, &config);
if (ret)
return ret;
ret = devm_pm_opp_of_add_table(dev);
if (ret) {
dev_err(dev, "Failed to add OPP table: %d\n", ret);
return ret;
}
ret = ufshcd_parse_clock_min_max_freq(hba);
if (ret)
return ret;
hba->use_pm_opp = true;
return 0;
}
/**
* ufshcd_negotiate_pwr_params - find power mode settings that are supported by
* both the controller and the device
* @host_params: pointer to host parameters
* @dev_max: pointer to device attributes
* @agreed_pwr: returned agreed attributes
*
* Return: 0 on success, non-zero value on failure.
*/
int ufshcd_negotiate_pwr_params(const struct ufs_host_params *host_params,
const struct ufs_pa_layer_attr *dev_max,
struct ufs_pa_layer_attr *agreed_pwr)
{
int min_host_gear;
int min_dev_gear;
bool is_dev_sup_hs = false;
bool is_host_max_hs = false;
if (dev_max->pwr_rx == FAST_MODE)
is_dev_sup_hs = true;
if (host_params->desired_working_mode == UFS_HS_MODE) {
is_host_max_hs = true;
min_host_gear = min_t(u32, host_params->hs_rx_gear,
host_params->hs_tx_gear);
} else {
min_host_gear = min_t(u32, host_params->pwm_rx_gear,
host_params->pwm_tx_gear);
}
/*
* device doesn't support HS but host_params->desired_working_mode is HS,
* thus device and host_params don't agree
*/
if (!is_dev_sup_hs && is_host_max_hs) {
pr_info("%s: device doesn't support HS\n",
__func__);
return -ENOTSUPP;
} else if (is_dev_sup_hs && is_host_max_hs) {
/*
* since device supports HS, it supports FAST_MODE.
* since host_params->desired_working_mode is also HS
* then final decision (FAST/FASTAUTO) is done according
* to pltfrm_params as it is the restricting factor
*/
agreed_pwr->pwr_rx = host_params->rx_pwr_hs;
agreed_pwr->pwr_tx = agreed_pwr->pwr_rx;
} else {
/*
* here host_params->desired_working_mode is PWM.
* it doesn't matter whether device supports HS or PWM,
* in both cases host_params->desired_working_mode will
* determine the mode
*/
agreed_pwr->pwr_rx = host_params->rx_pwr_pwm;
agreed_pwr->pwr_tx = agreed_pwr->pwr_rx;
}
/*
* we would like tx to work in the minimum number of lanes
* between device capability and vendor preferences.
* the same decision will be made for rx
*/
agreed_pwr->lane_tx = min_t(u32, dev_max->lane_tx,
host_params->tx_lanes);
agreed_pwr->lane_rx = min_t(u32, dev_max->lane_rx,
host_params->rx_lanes);
/* device maximum gear is the minimum between device rx and tx gears */
min_dev_gear = min_t(u32, dev_max->gear_rx, dev_max->gear_tx);
/*
* if both device capabilities and vendor pre-defined preferences are
* both HS or both PWM then set the minimum gear to be the chosen
* working gear.
* if one is PWM and one is HS then the one that is PWM get to decide
* what is the gear, as it is the one that also decided previously what
* pwr the device will be configured to.
*/
if ((is_dev_sup_hs && is_host_max_hs) ||
(!is_dev_sup_hs && !is_host_max_hs)) {
agreed_pwr->gear_rx =
min_t(u32, min_dev_gear, min_host_gear);
} else if (!is_dev_sup_hs) {
agreed_pwr->gear_rx = min_dev_gear;
} else {
agreed_pwr->gear_rx = min_host_gear;
}
agreed_pwr->gear_tx = agreed_pwr->gear_rx;
agreed_pwr->hs_rate = host_params->hs_rate;
return 0;
}
EXPORT_SYMBOL_GPL(ufshcd_negotiate_pwr_params);
/**
* ufshcd_parse_gear_limits - Parse DT-based gear and rate limits for UFS
* @hba: Pointer to UFS host bus adapter instance
* @host_params: Pointer to UFS host parameters structure to be updated
*
* This function reads optional device tree properties to apply
* platform-specific constraints.
*
* "limit-hs-gear": Specifies the max HS gear.
* "limit-gear-rate": Specifies the max High-Speed rate.
*/
void ufshcd_parse_gear_limits(struct ufs_hba *hba, struct ufs_host_params *host_params)
{
struct device_node *np = hba->dev->of_node;
u32 hs_gear;
const char *hs_rate;
if (!of_property_read_u32(np, "limit-hs-gear", &hs_gear)) {
host_params->hs_tx_gear = hs_gear;
host_params->hs_rx_gear = hs_gear;
}
if (!of_property_read_string(np, "limit-gear-rate", &hs_rate)) {
if (!strcmp(hs_rate, "rate-a"))
host_params->hs_rate = PA_HS_MODE_A;
else if (!strcmp(hs_rate, "rate-b"))
host_params->hs_rate = PA_HS_MODE_B;
else
dev_warn(hba->dev, "Invalid rate: %s\n", hs_rate);
}
}
EXPORT_SYMBOL_GPL(ufshcd_parse_gear_limits);
void ufshcd_init_host_params(struct ufs_host_params *host_params)
{
*host_params = (struct ufs_host_params){
.tx_lanes = UFS_LANE_2,
.rx_lanes = UFS_LANE_2,
.hs_rx_gear = UFS_HS_G3,
.hs_tx_gear = UFS_HS_G3,
.pwm_rx_gear = UFS_PWM_G4,
.pwm_tx_gear = UFS_PWM_G4,
.rx_pwr_pwm = SLOW_MODE,
.tx_pwr_pwm = SLOW_MODE,
.rx_pwr_hs = FAST_MODE,
.tx_pwr_hs = FAST_MODE,
.hs_rate = PA_HS_MODE_B,
.desired_working_mode = UFS_HS_MODE,
};
}
EXPORT_SYMBOL_GPL(ufshcd_init_host_params);
/**
* ufshcd_pltfrm_init - probe routine of the driver
* @pdev: pointer to Platform device handle
* @vops: pointer to variant ops
*
* Return: 0 on success, non-zero value on failure.
*/
int ufshcd_pltfrm_init(struct platform_device *pdev,
const struct ufs_hba_variant_ops *vops)
{
struct ufs_hba *hba;
void __iomem *mmio_base;
int irq, err;
struct device *dev = &pdev->dev;
mmio_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mmio_base))
return PTR_ERR(mmio_base);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
err = ufshcd_alloc_host(dev, &hba);
if (err) {
dev_err(dev, "Allocation failed\n");
return err;
}
hba->vops = vops;
err = ufshcd_parse_clock_info(hba);
if (err) {
dev_err(dev, "%s: clock parse failed %d\n",
__func__, err);
return err;
}
err = ufshcd_parse_regulator_info(hba);
if (err) {
dev_err(dev, "%s: regulator init failed %d\n",
__func__, err);
return err;
}
ufshcd_init_lanes_per_dir(hba);
err = ufshcd_parse_operating_points(hba);
if (err) {
dev_err(dev, "%s: OPP parse failed %d\n", __func__, err);
return err;
}
err = ufshcd_init(hba, mmio_base, irq);
if (err) {
dev_err_probe(dev, err, "Initialization failed with error %d\n",
err);
return err;
}
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
return 0;
}
EXPORT_SYMBOL_GPL(ufshcd_pltfrm_init);
/**
* ufshcd_pltfrm_remove - Remove ufshcd platform
* @pdev: pointer to Platform device handle
*/
void ufshcd_pltfrm_remove(struct platform_device *pdev)
{
struct ufs_hba *hba = platform_get_drvdata(pdev);
pm_runtime_get_sync(&pdev->dev);
ufshcd_remove(hba);
pm_runtime_disable(&pdev->dev);
pm_runtime_put_noidle(&pdev->dev);
}
EXPORT_SYMBOL_GPL(ufshcd_pltfrm_remove);
MODULE_AUTHOR("Santosh Yaragnavi <santosh.sy@samsung.com>");
MODULE_AUTHOR("Vinayak Holikatti <h.vinayak@samsung.com>");
MODULE_DESCRIPTION("UFS host controller Platform bus based glue driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*/
#ifndef UFSHCD_PLTFRM_H_
#define UFSHCD_PLTFRM_H_
#include <ufs/ufshcd.h>
#define UFS_PWM_MODE 1
#define UFS_HS_MODE 2
struct ufs_host_params {
u32 pwm_rx_gear; /* pwm rx gear to work in */
u32 pwm_tx_gear; /* pwm tx gear to work in */
u32 hs_rx_gear; /* hs rx gear to work in */
u32 hs_tx_gear; /* hs tx gear to work in */
u32 rx_lanes; /* number of rx lanes */
u32 tx_lanes; /* number of tx lanes */
u32 rx_pwr_pwm; /* rx pwm working pwr */
u32 tx_pwr_pwm; /* tx pwm working pwr */
u32 rx_pwr_hs; /* rx hs working pwr */
u32 tx_pwr_hs; /* tx hs working pwr */
u32 hs_rate; /* rate A/B to work in HS */
u32 desired_working_mode;
};
int ufshcd_negotiate_pwr_params(const struct ufs_host_params *host_params,
const struct ufs_pa_layer_attr *dev_max,
struct ufs_pa_layer_attr *agreed_pwr);
void ufshcd_init_host_params(struct ufs_host_params *host_params);
void ufshcd_parse_gear_limits(struct ufs_hba *hba, struct ufs_host_params *host_params);
int ufshcd_pltfrm_init(struct platform_device *pdev,
const struct ufs_hba_variant_ops *vops);
void ufshcd_pltfrm_remove(struct platform_device *pdev);
int ufshcd_populate_vreg(struct device *dev, const char *name,
struct ufs_vreg **out_vreg, bool skip_current);
#endif /* UFSHCD_PLTFRM_H_ */
+33
View File
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* UFS Host driver for Synopsys Designware Core
*
* Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
*
* Authors: Joao Pinto <jpinto@synopsys.com>
*/
#ifndef _UFSHCI_DWC_H
#define _UFSHCI_DWC_H
/* DWC HC UFSHCI specific Registers */
enum dwc_specific_registers {
DWC_UFS_REG_HCLKDIV = 0xFC,
};
/* Clock Divider Values: Hex equivalent of frequency in MHz */
enum clk_div_values {
DWC_UFS_REG_HCLKDIV_DIV_62_5 = 0x3e,
DWC_UFS_REG_HCLKDIV_DIV_125 = 0x7d,
DWC_UFS_REG_HCLKDIV_DIV_200 = 0xc8,
};
/* Selector Index */
enum selector_index {
SELIND_LN0_TX = 0x00,
SELIND_LN1_TX = 0x01,
SELIND_LN0_RX = 0x04,
SELIND_LN1_RX = 0x05,
};
#endif /* End of Header */