[PATCH 0/3] Add AMD UFS platfrom driver support

Enable UFS DWC controller driver support and add AMD UFS platform driver support.
NOTE: These changes are rebased on top of this series [PATCH 00/13] ufs: enhancements to support Qualcomm UFS controllers
Venkatesh Yadav Abbarapu (3): ufs: add support for DesignWare Controller ufs: ufs-amd-versal2: Add support for AMD UFS controller configs: versal2: Add support for AMD UFS platform driver
configs/amd_versal2_virt_defconfig | 2 +- drivers/ufs/Kconfig | 8 + drivers/ufs/Makefile | 1 + drivers/ufs/ufs-amd-versal2.c | 501 +++++++++++++++++++++++++++++ drivers/ufs/ufs.h | 11 + drivers/ufs/ufshcd-dwc.c | 141 ++++++++ drivers/ufs/ufshcd-dwc.h | 69 ++++ drivers/ufs/ufshci-dwc.h | 32 ++ drivers/ufs/unipro.h | 1 + 9 files changed, 765 insertions(+), 1 deletion(-) create mode 100644 drivers/ufs/ufs-amd-versal2.c create mode 100644 drivers/ufs/ufshcd-dwc.c create mode 100644 drivers/ufs/ufshcd-dwc.h create mode 100644 drivers/ufs/ufshci-dwc.h

This patch has the goal to add support for DesignWare UFS Controller specific operations.
This is based on linux kernel commit: "drivers/scsi/ufs/ufshcd-dwc.c: ufs: add support for DesignWare Controller" (sha1: 4b9ffb5a353bdee49f1f477ffe2b95ab3f9cbc0c) It is ported from linux kernel 6.11-rc1.
Signed-off-by: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com --- drivers/ufs/ufs.h | 11 +++ drivers/ufs/ufshcd-dwc.c | 141 +++++++++++++++++++++++++++++++++++++++ drivers/ufs/ufshcd-dwc.h | 23 +++++++ drivers/ufs/ufshci-dwc.h | 32 +++++++++ 4 files changed, 207 insertions(+) create mode 100644 drivers/ufs/ufshcd-dwc.c create mode 100644 drivers/ufs/ufshcd-dwc.h create mode 100644 drivers/ufs/ufshci-dwc.h
diff --git a/drivers/ufs/ufs.h b/drivers/ufs/ufs.h index e8a1441156..83837d5817 100644 --- a/drivers/ufs/ufs.h +++ b/drivers/ufs/ufs.h @@ -850,6 +850,7 @@ struct ufs_hba { struct ufs_pwr_mode_info max_pwr_info;
struct ufs_dev_cmd dev_cmd; + enum uic_link_state uic_link_state; };
static inline int ufshcd_ops_init(struct ufs_hba *hba) @@ -878,6 +879,14 @@ static inline int ufshcd_ops_link_startup_notify(struct ufs_hba *hba, return 0; }
+static inline int ufshcd_vops_phy_initialization(struct ufs_hba *hba) +{ + if (hba->ops && hba->ops->phy_initialization) + return hba->ops->phy_initialization(hba); + + return 0; +} + /* Controller UFSHCI version */ enum { UFSHCI_VERSION_10 = 0x00010000, /* 1.0 */ @@ -1022,6 +1031,8 @@ enum { writel((val), (hba)->mmio_base + (reg)) #define ufshcd_readl(hba, reg) \ readl((hba)->mmio_base + (reg)) +#define ufshcd_set_link_active(hba) ((hba)->uic_link_state = \ + UIC_LINK_ACTIVE_STATE)
/** * ufshcd_rmwl - perform read/modify/write for a controller register diff --git a/drivers/ufs/ufshcd-dwc.c b/drivers/ufs/ufshcd-dwc.c new file mode 100644 index 0000000000..db51a3b807 --- /dev/null +++ b/drivers/ufs/ufshcd-dwc.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * UFS Host driver for Synopsys Designware Core + * + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + */ +#include <clk.h> +#include <dm.h> +#include <ufs.h> +#include <asm/io.h> +#include <dm/device_compat.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/time.h> + +#include "ufs.h" +#include "ufshci-dwc.h" +#include "ufshcd-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; +} + +/** + * 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); + return err; + } + } else { /* POST_CHANGE */ + err = ufshcd_dwc_link_is_up(hba); + if (err) { + dev_err(hba->dev, "Link is not up\n"); + return err; + } + + err = ufshcd_dwc_connection_setup(hba); + if (err) + dev_err(hba->dev, "Connection setup failed (%d)\n", + err); + } + + return err; +} diff --git a/drivers/ufs/ufshcd-dwc.h b/drivers/ufs/ufshcd-dwc.h new file mode 100644 index 0000000000..d997045d3c --- /dev/null +++ b/drivers/ufs/ufshcd-dwc.h @@ -0,0 +1,23 @@ +/* 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 + +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 */ diff --git a/drivers/ufs/ufshci-dwc.h b/drivers/ufs/ufshci-dwc.h new file mode 100644 index 0000000000..9e24c230c6 --- /dev/null +++ b/drivers/ufs/ufshci-dwc.h @@ -0,0 +1,32 @@ +/* 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

Hi,
On 19/09/2024 11:17, Venkatesh Yadav Abbarapu wrote:
This patch has the goal to add support for DesignWare UFS Controller specific operations.
This is based on linux kernel commit: "drivers/scsi/ufs/ufshcd-dwc.c: ufs: add support for DesignWare Controller" (sha1: 4b9ffb5a353bdee49f1f477ffe2b95ab3f9cbc0c) It is ported from linux kernel 6.11-rc1.
Signed-off-by: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com
drivers/ufs/ufs.h | 11 +++ drivers/ufs/ufshcd-dwc.c | 141 +++++++++++++++++++++++++++++++++++++++ drivers/ufs/ufshcd-dwc.h | 23 +++++++ drivers/ufs/ufshci-dwc.h | 32 +++++++++ 4 files changed, 207 insertions(+) create mode 100644 drivers/ufs/ufshcd-dwc.c create mode 100644 drivers/ufs/ufshcd-dwc.h create mode 100644 drivers/ufs/ufshci-dwc.h
diff --git a/drivers/ufs/ufs.h b/drivers/ufs/ufs.h index e8a1441156..83837d5817 100644 --- a/drivers/ufs/ufs.h +++ b/drivers/ufs/ufs.h @@ -850,6 +850,7 @@ struct ufs_hba { struct ufs_pwr_mode_info max_pwr_info;
struct ufs_dev_cmd dev_cmd;
- enum uic_link_state uic_link_state;
Will this be really used ?
};
static inline int ufshcd_ops_init(struct ufs_hba *hba) @@ -878,6 +879,14 @@ static inline int ufshcd_ops_link_startup_notify(struct ufs_hba *hba, return 0; }
+static inline int ufshcd_vops_phy_initialization(struct ufs_hba *hba) +{
- if (hba->ops && hba->ops->phy_initialization)
return hba->ops->phy_initialization(hba);
- return 0;
+}
There's no users of hba->ops->phy_initialization, and the patch 2 doesn't even add an user, so I think you can also drop this....
- /* Controller UFSHCI version */ enum { UFSHCI_VERSION_10 = 0x00010000, /* 1.0 */
@@ -1022,6 +1031,8 @@ enum { writel((val), (hba)->mmio_base + (reg)) #define ufshcd_readl(hba, reg) \ readl((hba)->mmio_base + (reg)) +#define ufshcd_set_link_active(hba) ((hba)->uic_link_state = \
UIC_LINK_ACTIVE_STATE)
Same here, you set as active, but there's no users of uic_link_state
I think you can drop this
/**
- ufshcd_rmwl - perform read/modify/write for a controller register
diff --git a/drivers/ufs/ufshcd-dwc.c b/drivers/ufs/ufshcd-dwc.c new file mode 100644 index 0000000000..db51a3b807 --- /dev/null +++ b/drivers/ufs/ufshcd-dwc.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- UFS Host driver for Synopsys Designware Core
- Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
- */
+#include <clk.h> +#include <dm.h> +#include <ufs.h> +#include <asm/io.h> +#include <dm/device_compat.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/time.h>
+#include "ufs.h" +#include "ufshci-dwc.h" +#include "ufshcd-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;
+}
+/**
- 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);
You can drop this, we don't track and use the link state
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);
return err;
}
Drop this call, it's dead code, and weird to call core ops from a dwc helper...
- } else { /* POST_CHANGE */
err = ufshcd_dwc_link_is_up(hba);
if (err) {
dev_err(hba->dev, "Link is not up\n");
return err;
}
err = ufshcd_dwc_connection_setup(hba);
if (err)
dev_err(hba->dev, "Connection setup failed (%d)\n",
err);
- }
- return err;
+} diff --git a/drivers/ufs/ufshcd-dwc.h b/drivers/ufs/ufshcd-dwc.h new file mode 100644 index 0000000000..d997045d3c --- /dev/null +++ b/drivers/ufs/ufshcd-dwc.h @@ -0,0 +1,23 @@ +/* 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
+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 */ diff --git a/drivers/ufs/ufshci-dwc.h b/drivers/ufs/ufshci-dwc.h new file mode 100644 index 0000000000..9e24c230c6 --- /dev/null +++ b/drivers/ufs/ufshci-dwc.h @@ -0,0 +1,32 @@ +/* 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
Thanks, Neil

Add UFS AMD platform support on top of the UFS DWC and UFS platform driver. UFS AMD platform requires some platform specific configurations like M-PHY/RMMI/UniPro and vendor specific registers programming before doing the LINKSTARTUP.
Signed-off-by: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com --- drivers/ufs/Kconfig | 8 + drivers/ufs/Makefile | 1 + drivers/ufs/ufs-amd-versal2.c | 501 ++++++++++++++++++++++++++++++++++ drivers/ufs/ufshcd-dwc.h | 46 ++++ drivers/ufs/unipro.h | 1 + 5 files changed, 557 insertions(+) create mode 100644 drivers/ufs/ufs-amd-versal2.c
diff --git a/drivers/ufs/Kconfig b/drivers/ufs/Kconfig index 7da46faed6..ad5d0316f6 100644 --- a/drivers/ufs/Kconfig +++ b/drivers/ufs/Kconfig @@ -41,4 +41,12 @@ config UFS_RENESAS UFS host on Renesas needs some vendor specific configuration before accessing the hardware.
+config UFS_AMD_VERSAL2 + bool "AMD Versal Gen 2 UFS controller platform driver" + depends on UFS && ZYNQMP_FIRMWARE + help + This selects the AMD specific additions to UFSHCD platform driver. + UFS host on AMD needs some vendor specific configuration before accessing + the hardware. + endmenu diff --git a/drivers/ufs/Makefile b/drivers/ufs/Makefile index 67c42621ab..745de5c3db 100644 --- a/drivers/ufs/Makefile +++ b/drivers/ufs/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_CADENCE_UFS) += cdns-platform.o obj-$(CONFIG_TI_J721E_UFS) += ti-j721e-ufs.o obj-$(CONFIG_UFS_PCI) += ufs-pci.o obj-$(CONFIG_UFS_RENESAS) += ufs-renesas.o +obj-$(CONFIG_UFS_AMD_VERSAL2) += ufs-amd-versal2.o ufshcd-dwc.o diff --git a/drivers/ufs/ufs-amd-versal2.c b/drivers/ufs/ufs-amd-versal2.c new file mode 100644 index 0000000000..bfd844e419 --- /dev/null +++ b/drivers/ufs/ufs-amd-versal2.c @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Advanced Micro Devices, Inc. + */ + +#include <clk.h> +#include <dm.h> +#include <ufs.h> +#include <asm/io.h> +#include <dm/device_compat.h> +#include <zynqmp_firmware.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/time.h> +#include <reset.h> + +#include "ufs.h" +#include "ufshcd-dwc.h" +#include "ufshci-dwc.h" + +#define VERSAL2_UFS_DEVICE_ID 4 + +#define SRAM_CSR_INIT_DONE_MASK BIT(0) +#define SRAM_CSR_EXT_LD_DONE_MASK BIT(1) +#define SRAM_CSR_BYPASS_MASK BIT(2) + +#define MPHY_FAST_RX_AFE_CAL BIT(2) +#define MPHY_FW_CALIB_CFG_VAL BIT(8) + +#define TX_RX_CFG_RDY_MASK GENMASK(3, 0) + +#define TIMEOUT_MICROSEC 1000000L + +#define IOCTL_UFS_TXRX_CFGRDY_GET 40 +#define IOCTL_UFS_SRAM_CSR_SEL 41 + +#define PM_UFS_SRAM_CSR_WRITE 0 +#define PM_UFS_SRAM_CSR_READ 1 + +struct ufs_versal2_priv { + struct ufs_hba *hba; + struct reset_ctl *rstc; + struct reset_ctl *rstphy; + u32 phy_mode; + u32 host_clk; + u32 pd_dev_id; + 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; +} + +int versal2_pm_ufs_get_txrx_cfgrdy(u32 node_id, u32 *value) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!value) + return -EINVAL; + + ret = xilinx_pm_request(PM_IOCTL, node_id, IOCTL_UFS_TXRX_CFGRDY_GET, + 0, 0, ret_payload); + *value = ret_payload[1]; + + return ret; +} + +int versal2_pm_ufs_sram_csr_sel(u32 node_id, u32 type, u32 *value) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!value) + return -EINVAL; + + if (type == PM_UFS_SRAM_CSR_READ) { + ret = xilinx_pm_request(PM_IOCTL, node_id, IOCTL_UFS_SRAM_CSR_SEL, + type, 0, ret_payload); + *value = ret_payload[1]; + } else { + ret = xilinx_pm_request(PM_IOCTL, node_id, IOCTL_UFS_SRAM_CSR_SEL, + type, *value, 0); + } + + return ret; +} + +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, ®); + if (ret) + return ret; + + if (reg == TX_STATE_HIBERN8 || reg == TX_STATE_SLEEP || + reg == TX_STATE_LSBURST) + break; + + time_left--; + mdelay(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, ®); + if (ret) + return ret; + + if (reg == RX_STATE_HIBERN8 || reg == RX_STATE_SLEEP || + reg == RX_STATE_LSBURST) + break; + + time_left--; + mdelay(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_priv *priv = dev_get_priv(hba->dev); + int ret; + u32 reg; + + /* Bypass RX-AFE offset calibrations (ATT/CTLE) */ + ret = ufs_versal2_phy_reg_read(hba, FAST_FLAGS(0), ®); + 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), ®); + 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 (priv->attcompval0) { + ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(0), priv->attcompval0); + if (ret) + return ret; + } + + if (priv->attcompval1) { + ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(1), priv->attcompval1); + if (ret) + return ret; + } + + if (priv->ctlecompval0) { + ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(0), priv->ctlecompval0); + if (ret) + return ret; + } + + if (priv->ctlecompval1) { + ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(1), priv->ctlecompval1); + if (ret) + return ret; + } + + ret = ufs_versal2_phy_reg_read(hba, FW_CALIB_CCFG(0), ®); + 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), ®); + 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_priv *priv = dev_get_priv(hba->dev); + u32 reg, time_left; + 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 = versal2_pm_ufs_get_txrx_cfgrdy(priv->pd_dev_id, ®); + if (ret) + return ret; + + reg &= TX_RX_CFG_RDY_MASK; + if (!reg) + break; + + mdelay(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; + + /* DeAssert PHY reset */ + ret = reset_deassert(priv->rstphy); + if (ret) { + dev_err(hba->dev, "ufsphy reset deassert failed\n"); + return ret; + } + + /* Wait for SRAM init done */ + time_left = TIMEOUT_MICROSEC; + do { + time_left--; + ret = versal2_pm_ufs_sram_csr_sel(priv->pd_dev_id, + PM_UFS_SRAM_CSR_READ, ®); + if (ret) + return ret; + + reg &= SRAM_CSR_INIT_DONE_MASK; + if (reg) + break; + + mdelay(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_priv *priv = dev_get_priv(hba->dev); + struct clk clk; + unsigned long core_clk_rate = 0; + int ret = 0; + + priv->phy_mode = UFSHCD_DWC_PHY_MODE_ROM; + priv->pd_dev_id = VERSAL2_UFS_DEVICE_ID; + + ret = clk_get_by_name(hba->dev, "core_clk", &clk); + if (ret) { + dev_err(hba->dev, "failed to get core_clk clock\n"); + return ret; + } + + core_clk_rate = clk_get_rate(&clk); + if (IS_ERR_VALUE(core_clk_rate)) { + dev_err(hba->dev, "%s: unable to find core_clk rate\n", + __func__); + return core_clk_rate; + } + priv->host_clk = core_clk_rate; + + priv->rstc = devm_reset_control_get(hba->dev, "ufshc-rst"); + if (IS_ERR(priv->rstc)) { + dev_err(hba->dev, "failed to get reset ctl: ufshc-rst\n"); + return PTR_ERR(priv->rstc); + } + priv->rstphy = devm_reset_control_get(hba->dev, "ufsphy-rst"); + if (IS_ERR(priv->rstphy)) { + dev_err(hba->dev, "failed to get reset ctl: ufsphy-rst\n"); + return PTR_ERR(priv->rstphy); + } + + return ret; +} + +static int ufs_versal2_hce_enable_notify(struct ufs_hba *hba, + enum ufs_notify_change_status status) +{ + struct ufs_versal2_priv *priv = dev_get_priv(hba->dev); + u32 sram_csr; + int ret; + + switch (status) { + case PRE_CHANGE: + /* Assert RST_UFS Reset for UFS block in PMX_IOU */ + ret = reset_assert(priv->rstc); + if (ret) { + dev_err(hba->dev, "ufshc reset assert failed, err = %d\n", ret); + return ret; + } + + /* Assert PHY reset */ + ret = reset_assert(priv->rstphy); + if (ret) { + dev_err(hba->dev, "ufsphy reset assert failed, err = %d\n", ret); + return ret; + } + + ret = versal2_pm_ufs_sram_csr_sel(priv->pd_dev_id, + PM_UFS_SRAM_CSR_READ, &sram_csr); + if (ret) + return ret; + + if (!priv->phy_mode) { + sram_csr &= ~SRAM_CSR_EXT_LD_DONE_MASK; + sram_csr |= SRAM_CSR_BYPASS_MASK; + } else { + dev_err(hba->dev, "Invalid phy-mode %d.\n", priv->phy_mode); + return -EINVAL; + } + + ret = versal2_pm_ufs_sram_csr_sel(priv->pd_dev_id, + PM_UFS_SRAM_CSR_WRITE, &sram_csr); + if (ret) + return ret; + + /* De Assert RST_UFS Reset for UFS block in PMX_IOU */ + ret = reset_deassert(priv->rstc); + if (ret) + dev_err(hba->dev, "ufshc reset deassert failed, err = %d\n", ret); + + break; + case POST_CHANGE: + ret = ufs_versal2_phy_init(hba); + if (ret) + dev_err(hba->dev, "Phy init failed (%d)\n", ret); + + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int ufs_versal2_link_startup_notify(struct ufs_hba *hba, + enum ufs_notify_change_status status) +{ + struct ufs_versal2_priv *priv = dev_get_priv(hba->dev); + int ret = 0; + + switch (status) { + case PRE_CHANGE: + if (priv->host_clk) { + u32 core_clk_div = priv->host_clk / TIMEOUT_MICROSEC; + + ufshcd_writel(hba, core_clk_div, DWC_UFS_REG_HCLKDIV); + } + break; + case POST_CHANGE: + ret = ufshcd_dwc_link_startup_notify(hba, status); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static struct ufs_hba_ops ufs_versal2_hba_ops = { + .init = ufs_versal2_init, + .link_startup_notify = ufs_versal2_link_startup_notify, + .hce_enable_notify = ufs_versal2_hce_enable_notify, +}; + +static int ufs_versal2_probe(struct udevice *dev) +{ + int ret; + + /* Perform generic probe */ + ret = ufshcd_probe(dev, &ufs_versal2_hba_ops); + if (ret) + dev_err(dev, "ufshcd_probe() failed %d\n", ret); + + return ret; +} + +static int ufs_versal2_bind(struct udevice *dev) +{ + struct udevice *scsi_dev; + + return ufs_scsi_bind(dev, &scsi_dev); +} + +static const struct udevice_id ufs_versal2_ids[] = { + { + .compatible = "amd,versal2-ufs", + }, + {}, +}; + +U_BOOT_DRIVER(ufs_versal2_pltfm) = { + .name = "ufs-versal2-pltfm", + .id = UCLASS_UFS, + .of_match = ufs_versal2_ids, + .probe = ufs_versal2_probe, + .bind = ufs_versal2_bind, +}; diff --git a/drivers/ufs/ufshcd-dwc.h b/drivers/ufs/ufshcd-dwc.h index d997045d3c..fc1bcca8cc 100644 --- a/drivers/ufs/ufshcd-dwc.h +++ b/drivers/ufs/ufshcd-dwc.h @@ -10,6 +10,52 @@ #ifndef _UFSHCD_DWC_H #define _UFSHCD_DWC_H
+/* PHY modes */ +#define UFSHCD_DWC_PHY_MODE_ROM 0 + +/* RMMI Attributes */ +#define CBREFCLKCTRL2 0x8132 +#define CBCRCTRL 0x811F +#define CBC10DIRECTCONF2 0x810E +#define CBCREGADDRLSB 0x8116 +#define CBCREGADDRMSB 0x8117 +#define CBCREGWRLSB 0x8118 +#define CBCREGWRMSB 0x8119 +#define CBCREGRDLSB 0x811A +#define CBCREGRDMSB 0x811B +#define CBCREGRDWRSEL 0x811C + +#define CBREFREFCLK_GATE_OVR_EN BIT(7) + +/* M-PHY Attributes */ +#define MTX_FSM_STATE 0x41 +#define MRX_FSM_STATE 0xC1 + +/* M-PHY registers */ +#define FAST_FLAGS(n) (0x401C + ((n) * 0x100)) +#define RX_AFE_ATT_IDAC(n) (0x4000 + ((n) * 0x100)) +#define RX_AFE_CTLE_IDAC(n) (0x4001 + ((n) * 0x100)) +#define FW_CALIB_CCFG(n) (0x404D + ((n) * 0x100)) + +/* 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; diff --git a/drivers/ufs/unipro.h b/drivers/ufs/unipro.h index b30b17fa5a..2f5726d4d3 100644 --- a/drivers/ufs/unipro.h +++ b/drivers/ufs/unipro.h @@ -148,6 +148,7 @@ #define VS_MPHYCFGUPDT 0xD085 #define VS_DEBUGOMC 0xD09E #define VS_POWERSTATE 0xD083 +#define VS_MPHYDISABLE 0xD0C1
#define PA_GRANULARITY_MIN_VAL 1 #define PA_GRANULARITY_MAX_VAL 6

Hi,
On 19/09/2024 11:17, Venkatesh Yadav Abbarapu wrote:
Add UFS AMD platform support on top of the UFS DWC and UFS platform driver. UFS AMD platform requires some platform specific configurations like M-PHY/RMMI/UniPro and vendor specific registers programming before doing the LINKSTARTUP.
Signed-off-by: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com
drivers/ufs/Kconfig | 8 + drivers/ufs/Makefile | 1 + drivers/ufs/ufs-amd-versal2.c | 501 ++++++++++++++++++++++++++++++++++ drivers/ufs/ufshcd-dwc.h | 46 ++++ drivers/ufs/unipro.h | 1 + 5 files changed, 557 insertions(+) create mode 100644 drivers/ufs/ufs-amd-versal2.c
diff --git a/drivers/ufs/Kconfig b/drivers/ufs/Kconfig index 7da46faed6..ad5d0316f6 100644 --- a/drivers/ufs/Kconfig +++ b/drivers/ufs/Kconfig @@ -41,4 +41,12 @@ config UFS_RENESAS UFS host on Renesas needs some vendor specific configuration before accessing the hardware.
+config UFS_AMD_VERSAL2
- bool "AMD Versal Gen 2 UFS controller platform driver"
- depends on UFS && ZYNQMP_FIRMWARE
- help
This selects the AMD specific additions to UFSHCD platform driver.
UFS host on AMD needs some vendor specific configuration before accessing
the hardware.
- endmenu
diff --git a/drivers/ufs/Makefile b/drivers/ufs/Makefile index 67c42621ab..745de5c3db 100644 --- a/drivers/ufs/Makefile +++ b/drivers/ufs/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_CADENCE_UFS) += cdns-platform.o obj-$(CONFIG_TI_J721E_UFS) += ti-j721e-ufs.o obj-$(CONFIG_UFS_PCI) += ufs-pci.o obj-$(CONFIG_UFS_RENESAS) += ufs-renesas.o +obj-$(CONFIG_UFS_AMD_VERSAL2) += ufs-amd-versal2.o ufshcd-dwc.o diff --git a/drivers/ufs/ufs-amd-versal2.c b/drivers/ufs/ufs-amd-versal2.c new file mode 100644 index 0000000000..bfd844e419 --- /dev/null +++ b/drivers/ufs/ufs-amd-versal2.c @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright (C) 2024 Advanced Micro Devices, Inc.
- */
+#include <clk.h> +#include <dm.h> +#include <ufs.h> +#include <asm/io.h> +#include <dm/device_compat.h> +#include <zynqmp_firmware.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/time.h> +#include <reset.h>
+#include "ufs.h" +#include "ufshcd-dwc.h" +#include "ufshci-dwc.h"
+#define VERSAL2_UFS_DEVICE_ID 4
+#define SRAM_CSR_INIT_DONE_MASK BIT(0) +#define SRAM_CSR_EXT_LD_DONE_MASK BIT(1) +#define SRAM_CSR_BYPASS_MASK BIT(2)
+#define MPHY_FAST_RX_AFE_CAL BIT(2) +#define MPHY_FW_CALIB_CFG_VAL BIT(8)
+#define TX_RX_CFG_RDY_MASK GENMASK(3, 0)
+#define TIMEOUT_MICROSEC 1000000L
+#define IOCTL_UFS_TXRX_CFGRDY_GET 40 +#define IOCTL_UFS_SRAM_CSR_SEL 41
+#define PM_UFS_SRAM_CSR_WRITE 0 +#define PM_UFS_SRAM_CSR_READ 1
+struct ufs_versal2_priv {
- struct ufs_hba *hba;
- struct reset_ctl *rstc;
- struct reset_ctl *rstphy;
- u32 phy_mode;
- u32 host_clk;
- u32 pd_dev_id;
- 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;
+}
+int versal2_pm_ufs_get_txrx_cfgrdy(u32 node_id, u32 *value) +{
- u32 ret_payload[PAYLOAD_ARG_CNT];
- int ret;
- if (!value)
return -EINVAL;
- ret = xilinx_pm_request(PM_IOCTL, node_id, IOCTL_UFS_TXRX_CFGRDY_GET,
0, 0, ret_payload);
- *value = ret_payload[1];
- return ret;
+}
+int versal2_pm_ufs_sram_csr_sel(u32 node_id, u32 type, u32 *value) +{
- u32 ret_payload[PAYLOAD_ARG_CNT];
- int ret;
- if (!value)
return -EINVAL;
- if (type == PM_UFS_SRAM_CSR_READ) {
ret = xilinx_pm_request(PM_IOCTL, node_id, IOCTL_UFS_SRAM_CSR_SEL,
type, 0, ret_payload);
*value = ret_payload[1];
- } else {
ret = xilinx_pm_request(PM_IOCTL, node_id, IOCTL_UFS_SRAM_CSR_SEL,
type, *value, 0);
- }
- return ret;
+}
+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, ®);
if (ret)
return ret;
if (reg == TX_STATE_HIBERN8 || reg == TX_STATE_SLEEP ||
reg == TX_STATE_LSBURST)
break;
time_left--;
mdelay(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, ®);
if (ret)
return ret;
if (reg == RX_STATE_HIBERN8 || reg == RX_STATE_SLEEP ||
reg == RX_STATE_LSBURST)
break;
time_left--;
mdelay(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_priv *priv = dev_get_priv(hba->dev);
- int ret;
- u32 reg;
- /* Bypass RX-AFE offset calibrations (ATT/CTLE) */
- ret = ufs_versal2_phy_reg_read(hba, FAST_FLAGS(0), ®);
- 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), ®);
- 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 (priv->attcompval0) {
ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(0), priv->attcompval0);
if (ret)
return ret;
- }
- if (priv->attcompval1) {
ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(1), priv->attcompval1);
if (ret)
return ret;
- }
- if (priv->ctlecompval0) {
ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(0), priv->ctlecompval0);
if (ret)
return ret;
- }
- if (priv->ctlecompval1) {
ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(1), priv->ctlecompval1);
if (ret)
return ret;
- }
- ret = ufs_versal2_phy_reg_read(hba, FW_CALIB_CCFG(0), ®);
- 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), ®);
- 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_priv *priv = dev_get_priv(hba->dev);
- u32 reg, time_left;
- 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 = versal2_pm_ufs_get_txrx_cfgrdy(priv->pd_dev_id, ®);
if (ret)
return ret;
reg &= TX_RX_CFG_RDY_MASK;
if (!reg)
break;
mdelay(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;
- /* DeAssert PHY reset */
- ret = reset_deassert(priv->rstphy);
- if (ret) {
dev_err(hba->dev, "ufsphy reset deassert failed\n");
return ret;
- }
- /* Wait for SRAM init done */
- time_left = TIMEOUT_MICROSEC;
- do {
time_left--;
ret = versal2_pm_ufs_sram_csr_sel(priv->pd_dev_id,
PM_UFS_SRAM_CSR_READ, ®);
if (ret)
return ret;
reg &= SRAM_CSR_INIT_DONE_MASK;
if (reg)
break;
mdelay(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_priv *priv = dev_get_priv(hba->dev);
- struct clk clk;
- unsigned long core_clk_rate = 0;
- int ret = 0;
- priv->phy_mode = UFSHCD_DWC_PHY_MODE_ROM;
- priv->pd_dev_id = VERSAL2_UFS_DEVICE_ID;
- ret = clk_get_by_name(hba->dev, "core_clk", &clk);
- if (ret) {
dev_err(hba->dev, "failed to get core_clk clock\n");
return ret;
- }
- core_clk_rate = clk_get_rate(&clk);
- if (IS_ERR_VALUE(core_clk_rate)) {
dev_err(hba->dev, "%s: unable to find core_clk rate\n",
__func__);
return core_clk_rate;
- }
- priv->host_clk = core_clk_rate;
- priv->rstc = devm_reset_control_get(hba->dev, "ufshc-rst");
- if (IS_ERR(priv->rstc)) {
dev_err(hba->dev, "failed to get reset ctl: ufshc-rst\n");
return PTR_ERR(priv->rstc);
- }
- priv->rstphy = devm_reset_control_get(hba->dev, "ufsphy-rst");
- if (IS_ERR(priv->rstphy)) {
dev_err(hba->dev, "failed to get reset ctl: ufsphy-rst\n");
return PTR_ERR(priv->rstphy);
- }
- return ret;
+}
+static int ufs_versal2_hce_enable_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
+{
- struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
- u32 sram_csr;
- int ret;
- switch (status) {
- case PRE_CHANGE:
/* Assert RST_UFS Reset for UFS block in PMX_IOU */
ret = reset_assert(priv->rstc);
if (ret) {
dev_err(hba->dev, "ufshc reset assert failed, err = %d\n", ret);
return ret;
}
/* Assert PHY reset */
ret = reset_assert(priv->rstphy);
if (ret) {
dev_err(hba->dev, "ufsphy reset assert failed, err = %d\n", ret);
return ret;
}
ret = versal2_pm_ufs_sram_csr_sel(priv->pd_dev_id,
PM_UFS_SRAM_CSR_READ, &sram_csr);
if (ret)
return ret;
if (!priv->phy_mode) {
sram_csr &= ~SRAM_CSR_EXT_LD_DONE_MASK;
sram_csr |= SRAM_CSR_BYPASS_MASK;
} else {
dev_err(hba->dev, "Invalid phy-mode %d.\n", priv->phy_mode);
return -EINVAL;
}
ret = versal2_pm_ufs_sram_csr_sel(priv->pd_dev_id,
PM_UFS_SRAM_CSR_WRITE, &sram_csr);
if (ret)
return ret;
/* De Assert RST_UFS Reset for UFS block in PMX_IOU */
ret = reset_deassert(priv->rstc);
if (ret)
dev_err(hba->dev, "ufshc reset deassert failed, err = %d\n", ret);
break;
- case POST_CHANGE:
ret = ufs_versal2_phy_init(hba);
if (ret)
dev_err(hba->dev, "Phy init failed (%d)\n", ret);
break;
- default:
ret = -EINVAL;
break;
- }
- return ret;
+}
+static int ufs_versal2_link_startup_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
+{
- struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
- int ret = 0;
- switch (status) {
- case PRE_CHANGE:
if (priv->host_clk) {
u32 core_clk_div = priv->host_clk / TIMEOUT_MICROSEC;
ufshcd_writel(hba, core_clk_div, DWC_UFS_REG_HCLKDIV);
}
break;
- case POST_CHANGE:
ret = ufshcd_dwc_link_startup_notify(hba, status);
break;
- default:
ret = -EINVAL;
break;
- }
- return ret;
+}
+static struct ufs_hba_ops ufs_versal2_hba_ops = {
- .init = ufs_versal2_init,
- .link_startup_notify = ufs_versal2_link_startup_notify,
- .hce_enable_notify = ufs_versal2_hce_enable_notify,
+};
+static int ufs_versal2_probe(struct udevice *dev) +{
- int ret;
- /* Perform generic probe */
- ret = ufshcd_probe(dev, &ufs_versal2_hba_ops);
- if (ret)
dev_err(dev, "ufshcd_probe() failed %d\n", ret);
- return ret;
+}
+static int ufs_versal2_bind(struct udevice *dev) +{
- struct udevice *scsi_dev;
- return ufs_scsi_bind(dev, &scsi_dev);
+}
+static const struct udevice_id ufs_versal2_ids[] = {
- {
.compatible = "amd,versal2-ufs",
- },
- {},
+};
+U_BOOT_DRIVER(ufs_versal2_pltfm) = {
- .name = "ufs-versal2-pltfm",
- .id = UCLASS_UFS,
- .of_match = ufs_versal2_ids,
- .probe = ufs_versal2_probe,
- .bind = ufs_versal2_bind,
+}; diff --git a/drivers/ufs/ufshcd-dwc.h b/drivers/ufs/ufshcd-dwc.h index d997045d3c..fc1bcca8cc 100644 --- a/drivers/ufs/ufshcd-dwc.h +++ b/drivers/ufs/ufshcd-dwc.h @@ -10,6 +10,52 @@ #ifndef _UFSHCD_DWC_H #define _UFSHCD_DWC_H
+/* PHY modes */ +#define UFSHCD_DWC_PHY_MODE_ROM 0
+/* RMMI Attributes */ +#define CBREFCLKCTRL2 0x8132 +#define CBCRCTRL 0x811F +#define CBC10DIRECTCONF2 0x810E +#define CBCREGADDRLSB 0x8116 +#define CBCREGADDRMSB 0x8117 +#define CBCREGWRLSB 0x8118 +#define CBCREGWRMSB 0x8119 +#define CBCREGRDLSB 0x811A +#define CBCREGRDMSB 0x811B +#define CBCREGRDWRSEL 0x811C
+#define CBREFREFCLK_GATE_OVR_EN BIT(7)
+/* M-PHY Attributes */ +#define MTX_FSM_STATE 0x41 +#define MRX_FSM_STATE 0xC1
+/* M-PHY registers */ +#define FAST_FLAGS(n) (0x401C + ((n) * 0x100)) +#define RX_AFE_ATT_IDAC(n) (0x4000 + ((n) * 0x100)) +#define RX_AFE_CTLE_IDAC(n) (0x4001 + ((n) * 0x100)) +#define FW_CALIB_CCFG(n) (0x404D + ((n) * 0x100))
+/* 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;
diff --git a/drivers/ufs/unipro.h b/drivers/ufs/unipro.h index b30b17fa5a..2f5726d4d3 100644 --- a/drivers/ufs/unipro.h +++ b/drivers/ufs/unipro.h @@ -148,6 +148,7 @@ #define VS_MPHYCFGUPDT 0xD085 #define VS_DEBUGOMC 0xD09E #define VS_POWERSTATE 0xD083 +#define VS_MPHYDISABLE 0xD0C1
#define PA_GRANULARITY_MIN_VAL 1 #define PA_GRANULARITY_MAX_VAL 6
Looks fine!
Reviewed-by: Neil Armstrong neil.armstrong@linaro.org
Neil

Enable AMD UFS platform driver.
Signed-off-by: Venkatesh Yadav Abbarapu venkatesh.abbarapu@amd.com --- configs/amd_versal2_virt_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configs/amd_versal2_virt_defconfig b/configs/amd_versal2_virt_defconfig index c4bf77186c..666d76c449 100644 --- a/configs/amd_versal2_virt_defconfig +++ b/configs/amd_versal2_virt_defconfig @@ -145,7 +145,7 @@ CONFIG_USB_GADGET_PRODUCT_NUM=0x0300 CONFIG_USB_GADGET_DOWNLOAD=y CONFIG_USB_FUNCTION_THOR=y CONFIG_UFS=y -CONFIG_CADENCE_UFS=y +CONFIG_UFS_AMD_VERSAL2=y CONFIG_VIRTIO_MMIO=y CONFIG_VIRTIO_NET=y CONFIG_VIRTIO_BLK=y
participants (2)
-
neil.armstrong@linaro.org
-
Venkatesh Yadav Abbarapu