From fc8a047e3117ac9335c5ef579c27ae5b1833735c Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 26 Aug 2016 18:33:11 -0700 Subject: [PATCH 1/2] phy: qcom-ufs-qmp-v3: add ops to dump phy regs It's useful to dump phy registers on failure to debug link issues. Export a new ops for the ufs driver to invoke during link failures. Change-Id: Id3a9c9085375b783d97e7f2ffd3e11ec469aedb9 Signed-off-by: Venkat Gopalakrishnan --- drivers/phy/phy-qcom-ufs-i.h | 5 ++++- drivers/phy/phy-qcom-ufs-qmp-v3.c | 17 +++++++++++++++++ drivers/phy/phy-qcom-ufs-qmp-v3.h | 16 ++++++++++++---- drivers/phy/phy-qcom-ufs.c | 18 ++++++++++++++++++ include/linux/phy/phy-qcom-ufs.h | 1 + 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h index 35179c8be471..7acef104d5b7 100644 --- a/drivers/phy/phy-qcom-ufs-i.h +++ b/drivers/phy/phy-qcom-ufs-i.h @@ -152,6 +152,7 @@ struct ufs_qcom_phy { * and writes to QSERDES_RX_SIGDET_CNTRL attribute * @configure_lpm: pointer to a function that configures the phy * for low power mode. + * @dbg_register_dump: pointer to a function that dumps phy registers for debug. */ struct ufs_qcom_phy_specific_ops { int (*calibrate_phy)(struct ufs_qcom_phy *phy, bool is_rate_B); @@ -161,6 +162,7 @@ struct ufs_qcom_phy_specific_ops { void (*ctrl_rx_linecfg)(struct ufs_qcom_phy *phy, bool ctrl); void (*power_control)(struct ufs_qcom_phy *phy, bool val); int (*configure_lpm)(struct ufs_qcom_phy *phy, bool enable); + void (*dbg_register_dump)(struct ufs_qcom_phy *phy); }; struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy); @@ -184,5 +186,6 @@ int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, void ufs_qcom_phy_write_tbl(struct ufs_qcom_phy *ufs_qcom_phy, struct ufs_qcom_phy_calibration *tbl, int tbl_size); - +void ufs_qcom_phy_dump_regs(struct ufs_qcom_phy *phy, + int offset, int len, char *prefix); #endif diff --git a/drivers/phy/phy-qcom-ufs-qmp-v3.c b/drivers/phy/phy-qcom-ufs-qmp-v3.c index 57c23f70eb63..a9ad3a6f87cc 100644 --- a/drivers/phy/phy-qcom-ufs-qmp-v3.c +++ b/drivers/phy/phy-qcom-ufs-qmp-v3.c @@ -194,6 +194,22 @@ out: return err; } +static void ufs_qcom_phy_qmp_v3_dbg_register_dump(struct ufs_qcom_phy *phy) +{ + ufs_qcom_phy_dump_regs(phy, COM_BASE, COM_SIZE, + "PHY QSERDES COM Registers "); + ufs_qcom_phy_dump_regs(phy, PHY_BASE, PHY_SIZE, + "PHY Registers "); + ufs_qcom_phy_dump_regs(phy, RX_BASE(0), RX_SIZE, + "PHY RX0 Registers "); + ufs_qcom_phy_dump_regs(phy, TX_BASE(0), TX_SIZE, + "PHY TX0 Registers "); + ufs_qcom_phy_dump_regs(phy, RX_BASE(1), RX_SIZE, + "PHY RX1 Registers "); + ufs_qcom_phy_dump_regs(phy, TX_BASE(1), TX_SIZE, + "PHY TX1 Registers "); +} + struct phy_ops ufs_qcom_phy_qmp_v3_phy_ops = { .init = ufs_qcom_phy_qmp_v3_init, .exit = ufs_qcom_phy_exit, @@ -210,6 +226,7 @@ struct ufs_qcom_phy_specific_ops phy_v3_ops = { .ctrl_rx_linecfg = ufs_qcom_phy_qmp_v3_ctrl_rx_linecfg, .power_control = ufs_qcom_phy_qmp_v3_power_control, .configure_lpm = ufs_qcom_phy_qmp_v3_configure_lpm, + .dbg_register_dump = ufs_qcom_phy_qmp_v3_dbg_register_dump, }; static int ufs_qcom_phy_qmp_v3_probe(struct platform_device *pdev) diff --git a/drivers/phy/phy-qcom-ufs-qmp-v3.h b/drivers/phy/phy-qcom-ufs-qmp-v3.h index cda57855acb5..8b1e03eab639 100644 --- a/drivers/phy/phy-qcom-ufs-qmp-v3.h +++ b/drivers/phy/phy-qcom-ufs-qmp-v3.h @@ -18,10 +18,18 @@ #include "phy-qcom-ufs-i.h" /* QCOM UFS PHY control registers */ -#define COM_OFF(x) (0x000 + x) -#define PHY_OFF(x) (0xC00 + x) -#define TX_OFF(n, x) (0x400 + (0x400 * n) + x) -#define RX_OFF(n, x) (0x600 + (0x400 * n) + x) +#define COM_BASE 0x000 +#define COM_SIZE 0x18C +#define PHY_BASE 0xC00 +#define PHY_SIZE 0x1DC +#define TX_BASE(n) (0x400 + (0x400 * n)) +#define TX_SIZE 0x128 +#define RX_BASE(n) (0x600 + (0x400 * n)) +#define RX_SIZE 0x1FC +#define COM_OFF(x) (COM_BASE + x) +#define PHY_OFF(x) (PHY_BASE + x) +#define TX_OFF(n, x) (TX_BASE(n) + x) +#define RX_OFF(n, x) (RX_BASE(n) + x) /* UFS PHY QSERDES COM registers */ #define QSERDES_COM_ATB_SEL1 COM_OFF(0x00) diff --git a/drivers/phy/phy-qcom-ufs.c b/drivers/phy/phy-qcom-ufs.c index de32b75f4f57..b2c58430785a 100644 --- a/drivers/phy/phy-qcom-ufs.c +++ b/drivers/phy/phy-qcom-ufs.c @@ -788,3 +788,21 @@ int ufs_qcom_phy_configure_lpm(struct phy *generic_phy, bool enable) return ret; } EXPORT_SYMBOL(ufs_qcom_phy_configure_lpm); + +void ufs_qcom_phy_dump_regs(struct ufs_qcom_phy *phy, int offset, + int len, char *prefix) +{ + print_hex_dump(KERN_ERR, prefix, + len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE, + 16, 4, phy->mmio + offset, len, false); +} +EXPORT_SYMBOL(ufs_qcom_phy_dump_regs); + +void ufs_qcom_phy_dbg_register_dump(struct phy *generic_phy) +{ + struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); + + if (ufs_qcom_phy->phy_spec_ops->dbg_register_dump) + ufs_qcom_phy->phy_spec_ops->dbg_register_dump(ufs_qcom_phy); +} +EXPORT_SYMBOL(ufs_qcom_phy_dbg_register_dump); diff --git a/include/linux/phy/phy-qcom-ufs.h b/include/linux/phy/phy-qcom-ufs.h index 7945fea14d77..25e7a5f183ec 100644 --- a/include/linux/phy/phy-qcom-ufs.h +++ b/include/linux/phy/phy-qcom-ufs.h @@ -58,5 +58,6 @@ void ufs_qcom_phy_save_controller_version(struct phy *phy, u8 major, u16 minor, u16 step); const char *ufs_qcom_phy_name(struct phy *phy); int ufs_qcom_phy_configure_lpm(struct phy *generic_phy, bool enable); +void ufs_qcom_phy_dbg_register_dump(struct phy *generic_phy); #endif /* PHY_QCOM_UFS_H_ */ From b1fe3eedb24a47a04719bb6b5125b7e5af306c0e Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 16 Aug 2016 18:48:01 -0700 Subject: [PATCH 2/2] scsi: ufs-qcom: dump additional testbus registers Change testbus default config, dump additional testbus registers and ufs phy regs along with other debug vendor specific registers. These additional info are useful in debugging link related failures. Change-Id: Icfa44def6d461a5782f7f57cefb78ed5e57a50d0 Signed-off-by: Venkat Gopalakrishnan --- drivers/scsi/ufs/ufs-qcom.c | 51 ++++++++++++++++++++++++++++++++----- drivers/scsi/ufs/ufs-qcom.h | 1 + 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c index a3bcfb42ca6a..09c5b71be8cb 100644 --- a/drivers/scsi/ufs/ufs-qcom.c +++ b/drivers/scsi/ufs/ufs-qcom.c @@ -2382,17 +2382,21 @@ void ufs_qcom_print_hw_debug_reg_all(struct ufs_hba *hba, void *priv, static void ufs_qcom_enable_test_bus(struct ufs_qcom_host *host) { - if (host->dbg_print_en & UFS_QCOM_DBG_PRINT_TEST_BUS_EN) + if (host->dbg_print_en & UFS_QCOM_DBG_PRINT_TEST_BUS_EN) { + ufshcd_rmwl(host->hba, UFS_REG_TEST_BUS_EN, + UFS_REG_TEST_BUS_EN, REG_UFS_CFG1); ufshcd_rmwl(host->hba, TEST_BUS_EN, TEST_BUS_EN, REG_UFS_CFG1); - else + } else { + ufshcd_rmwl(host->hba, UFS_REG_TEST_BUS_EN, 0, REG_UFS_CFG1); ufshcd_rmwl(host->hba, TEST_BUS_EN, 0, REG_UFS_CFG1); + } } static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host) { /* provide a legal default configuration */ - host->testbus.select_major = TSTBUS_UAWM; - host->testbus.select_minor = 1; + host->testbus.select_major = TSTBUS_UNIPRO; + host->testbus.select_minor = 37; } static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host) @@ -2409,7 +2413,7 @@ static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host) * mappings of select_minor, since there is no harm in * configuring a non-existent select_minor */ - if (host->testbus.select_minor > 0x1F) { + if (host->testbus.select_minor > 0xFF) { dev_err(host->hba->dev, "%s: 0x%05X is not a legal testbus option\n", __func__, host->testbus.select_minor); @@ -2478,7 +2482,8 @@ int ufs_qcom_testbus_config(struct ufs_qcom_host *host) break; case TSTBUS_UNIPRO: reg = UFS_UNIPRO_CFG; - offset = 1; + offset = 20; + mask = 0xFFF; break; /* * No need for a default case, since @@ -2497,6 +2502,11 @@ int ufs_qcom_testbus_config(struct ufs_qcom_host *host) (u32)host->testbus.select_minor << offset, reg); ufs_qcom_enable_test_bus(host); + /* + * Make sure the test bus configuration is + * committed before returning. + */ + mb(); ufshcd_release(host->hba, false); pm_runtime_put_sync(host->hba->dev); @@ -2508,15 +2518,44 @@ static void ufs_qcom_testbus_read(struct ufs_hba *hba) ufs_qcom_dump_regs(hba, UFS_TEST_BUS, 1, "UFS_TEST_BUS "); } +static void ufs_qcom_print_unipro_testbus(struct ufs_hba *hba) +{ + struct ufs_qcom_host *host = ufshcd_get_variant(hba); + u32 *testbus = NULL; + int i, nminor = 256, testbus_len = nminor * sizeof(u32); + + testbus = kmalloc(testbus_len, GFP_KERNEL); + if (!testbus) + return; + + host->testbus.select_major = TSTBUS_UNIPRO; + for (i = 0; i < nminor; i++) { + host->testbus.select_minor = i; + ufs_qcom_testbus_config(host); + testbus[i] = ufshcd_readl(hba, UFS_TEST_BUS); + } + print_hex_dump(KERN_ERR, "UNIPRO_TEST_BUS ", DUMP_PREFIX_OFFSET, + 16, 4, testbus, testbus_len, false); + kfree(testbus); +} + static void ufs_qcom_dump_dbg_regs(struct ufs_hba *hba) { struct ufs_qcom_host *host = ufshcd_get_variant(hba); + struct phy *phy = host->generic_phy; ufs_qcom_dump_regs(hba, REG_UFS_SYS1CLK_1US, 16, "HCI Vendor Specific Registers "); + /* sleep a bit intermittently as we are dumping too much data */ ufs_qcom_print_hw_debug_reg_all(hba, NULL, ufs_qcom_dump_regs_wrapper); + usleep_range(1000, 1100); ufs_qcom_testbus_read(hba); + usleep_range(1000, 1100); + ufs_qcom_print_unipro_testbus(hba); + usleep_range(1000, 1100); + ufs_qcom_phy_dbg_register_dump(phy); + usleep_range(1000, 1100); ufs_qcom_ice_print_regs(host); } diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h index ba36d9883a0f..394de8302fd2 100644 --- a/drivers/scsi/ufs/ufs-qcom.h +++ b/drivers/scsi/ufs/ufs-qcom.h @@ -100,6 +100,7 @@ enum { #define QUNIPRO_SEL UFS_BIT(0) #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 (1 << 0)