mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-27 06:50:37 +00:00

This is mostly fixes and cleanups and code reworks that trickled in across the merge window and the weeks leading up. The only substantive update is the Mediatek ufs driver which accounts for the bulk of the additions. Signed-off-by: James E.J. Bottomley <James.Bottomley@HansenPartnership.com> -----BEGIN PGP SIGNATURE----- iJwEABMIAEQWIQTnYEDbdso9F2cI+arnQslM7pishQUCaJNGsyYcamFtZXMuYm90 dG9tbGV5QGhhbnNlbnBhcnRuZXJzaGlwLmNvbQAKCRDnQslM7pishbe3AQCWaCas yQj/3S7dK17qdRQa7ooU3xeXt1A1CLlhkJEyWwD/TmmxUSFvbxjm/+Wdu0l+JX15 EGVmwp+bX/p2ea+s6AE= =ZYNP -----END PGP SIGNATURE----- Merge tag 'scsi-misc' of git://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi Pull more SCSI updates from James Bottomley: "This is mostly fixes and cleanups and code reworks that trickled in across the merge window and the weeks leading up. The only substantive update is the Mediatek ufs driver which accounts for the bulk of the additions" * tag 'scsi-misc' of git://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi: (37 commits) scsi: libsas: Use a bool for sas_deform_port() second argument scsi: libsas: Move declarations of internal functions to sas_internal.h scsi: libsas: Make sas_get_ata_info() static scsi: libsas: Simplify sas_ata_wait_eh() scsi: libsas: Refactor dev_is_sata() scsi: sd: Make sd shutdown issue START STOP UNIT appropriately scsi: arm64: dts: mediatek: mt8195: Add UFSHCI node scsi: dt-bindings: mediatek,ufs: add MT8195 compatible and update clock nodes scsi: dt-bindings: mediatek,ufs: Add ufs-disable-mcq flag for UFS host scsi: ufs: ufs-mediatek: Add UFS host support for MT8195 SoC scsi: ufs: ufs-pci: Remove control of UIC Completion interrupt for Intel MTL scsi: ufs: core: Do not write interrupt enable register unnecessarily scsi: ufs: core: Set and clear UIC Completion interrupt as needed scsi: ufs: core: Remove duplicated code in ufshcd_send_bsg_uic_cmd() scsi: ufs: core: Move ufshcd_enable_intr() and ufshcd_disable_intr() scsi: ufs: ufs-pci: Remove UFS PCI driver's ->late_init() call back scsi: ufs: ufs-pci: Fix default runtime and system PM levels scsi: ufs: ufs-pci: Fix hibernate state transition for Intel MTL-like host controllers scsi: ufs: host: mediatek: Support FDE (AES) clock scaling scsi: ufs: host: mediatek: Support clock scaling with Vcore binding ...
2324 lines
61 KiB
C
2324 lines
61 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2013-2016, Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/cleanup.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/devfreq.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/interconnect.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/reset-controller.h>
|
|
#include <linux/time.h>
|
|
#include <linux/unaligned.h>
|
|
#include <linux/units.h>
|
|
|
|
#include <soc/qcom/ice.h>
|
|
|
|
#include <ufs/ufshcd.h>
|
|
#include <ufs/ufshci.h>
|
|
#include <ufs/ufs_quirks.h>
|
|
#include <ufs/unipro.h>
|
|
#include "ufshcd-pltfrm.h"
|
|
#include "ufs-qcom.h"
|
|
|
|
#define MCQ_QCFGPTR_MASK GENMASK(7, 0)
|
|
#define MCQ_QCFGPTR_UNIT 0x200
|
|
#define MCQ_SQATTR_OFFSET(c) \
|
|
((((c) >> 16) & MCQ_QCFGPTR_MASK) * MCQ_QCFGPTR_UNIT)
|
|
#define MCQ_QCFG_SIZE 0x40
|
|
|
|
/* De-emphasis for gear-5 */
|
|
#define DEEMPHASIS_3_5_dB 0x04
|
|
#define NO_DEEMPHASIS 0x0
|
|
|
|
enum {
|
|
TSTBUS_UAWM,
|
|
TSTBUS_UARM,
|
|
TSTBUS_TXUC,
|
|
TSTBUS_RXUC,
|
|
TSTBUS_DFC,
|
|
TSTBUS_TRLUT,
|
|
TSTBUS_TMRLUT,
|
|
TSTBUS_OCSC,
|
|
TSTBUS_UTP_HCI,
|
|
TSTBUS_COMBINED,
|
|
TSTBUS_WRAPPER,
|
|
TSTBUS_UNIPRO,
|
|
TSTBUS_MAX,
|
|
};
|
|
|
|
#define QCOM_UFS_MAX_GEAR 5
|
|
#define QCOM_UFS_MAX_LANE 2
|
|
|
|
enum {
|
|
MODE_MIN,
|
|
MODE_PWM,
|
|
MODE_HS_RA,
|
|
MODE_HS_RB,
|
|
MODE_MAX,
|
|
};
|
|
|
|
static const struct __ufs_qcom_bw_table {
|
|
u32 mem_bw;
|
|
u32 cfg_bw;
|
|
} ufs_qcom_bw_table[MODE_MAX + 1][QCOM_UFS_MAX_GEAR + 1][QCOM_UFS_MAX_LANE + 1] = {
|
|
[MODE_MIN][0][0] = { 0, 0 }, /* Bandwidth values in KB/s */
|
|
[MODE_PWM][UFS_PWM_G1][UFS_LANE_1] = { 922, 1000 },
|
|
[MODE_PWM][UFS_PWM_G2][UFS_LANE_1] = { 1844, 1000 },
|
|
[MODE_PWM][UFS_PWM_G3][UFS_LANE_1] = { 3688, 1000 },
|
|
[MODE_PWM][UFS_PWM_G4][UFS_LANE_1] = { 7376, 1000 },
|
|
[MODE_PWM][UFS_PWM_G5][UFS_LANE_1] = { 14752, 1000 },
|
|
[MODE_PWM][UFS_PWM_G1][UFS_LANE_2] = { 1844, 1000 },
|
|
[MODE_PWM][UFS_PWM_G2][UFS_LANE_2] = { 3688, 1000 },
|
|
[MODE_PWM][UFS_PWM_G3][UFS_LANE_2] = { 7376, 1000 },
|
|
[MODE_PWM][UFS_PWM_G4][UFS_LANE_2] = { 14752, 1000 },
|
|
[MODE_PWM][UFS_PWM_G5][UFS_LANE_2] = { 29504, 1000 },
|
|
[MODE_HS_RA][UFS_HS_G1][UFS_LANE_1] = { 127796, 1000 },
|
|
[MODE_HS_RA][UFS_HS_G2][UFS_LANE_1] = { 255591, 1000 },
|
|
[MODE_HS_RA][UFS_HS_G3][UFS_LANE_1] = { 1492582, 102400 },
|
|
[MODE_HS_RA][UFS_HS_G4][UFS_LANE_1] = { 2915200, 204800 },
|
|
[MODE_HS_RA][UFS_HS_G5][UFS_LANE_1] = { 5836800, 409600 },
|
|
[MODE_HS_RA][UFS_HS_G1][UFS_LANE_2] = { 255591, 1000 },
|
|
[MODE_HS_RA][UFS_HS_G2][UFS_LANE_2] = { 511181, 1000 },
|
|
[MODE_HS_RA][UFS_HS_G3][UFS_LANE_2] = { 1492582, 204800 },
|
|
[MODE_HS_RA][UFS_HS_G4][UFS_LANE_2] = { 2915200, 409600 },
|
|
[MODE_HS_RA][UFS_HS_G5][UFS_LANE_2] = { 5836800, 819200 },
|
|
[MODE_HS_RB][UFS_HS_G1][UFS_LANE_1] = { 149422, 1000 },
|
|
[MODE_HS_RB][UFS_HS_G2][UFS_LANE_1] = { 298189, 1000 },
|
|
[MODE_HS_RB][UFS_HS_G3][UFS_LANE_1] = { 1492582, 102400 },
|
|
[MODE_HS_RB][UFS_HS_G4][UFS_LANE_1] = { 2915200, 204800 },
|
|
[MODE_HS_RB][UFS_HS_G5][UFS_LANE_1] = { 5836800, 409600 },
|
|
[MODE_HS_RB][UFS_HS_G1][UFS_LANE_2] = { 298189, 1000 },
|
|
[MODE_HS_RB][UFS_HS_G2][UFS_LANE_2] = { 596378, 1000 },
|
|
[MODE_HS_RB][UFS_HS_G3][UFS_LANE_2] = { 1492582, 204800 },
|
|
[MODE_HS_RB][UFS_HS_G4][UFS_LANE_2] = { 2915200, 409600 },
|
|
[MODE_HS_RB][UFS_HS_G5][UFS_LANE_2] = { 5836800, 819200 },
|
|
[MODE_MAX][0][0] = { 7643136, 819200 },
|
|
};
|
|
|
|
static const struct {
|
|
int nminor;
|
|
char *prefix;
|
|
} testbus_info[TSTBUS_MAX] = {
|
|
[TSTBUS_UAWM] = {32, "TSTBUS_UAWM"},
|
|
[TSTBUS_UARM] = {32, "TSTBUS_UARM"},
|
|
[TSTBUS_TXUC] = {32, "TSTBUS_TXUC"},
|
|
[TSTBUS_RXUC] = {32, "TSTBUS_RXUC"},
|
|
[TSTBUS_DFC] = {32, "TSTBUS_DFC"},
|
|
[TSTBUS_TRLUT] = {32, "TSTBUS_TRLUT"},
|
|
[TSTBUS_TMRLUT] = {32, "TSTBUS_TMRLUT"},
|
|
[TSTBUS_OCSC] = {32, "TSTBUS_OCSC"},
|
|
[TSTBUS_UTP_HCI] = {32, "TSTBUS_UTP_HCI"},
|
|
[TSTBUS_COMBINED] = {32, "TSTBUS_COMBINED"},
|
|
[TSTBUS_WRAPPER] = {32, "TSTBUS_WRAPPER"},
|
|
[TSTBUS_UNIPRO] = {256, "TSTBUS_UNIPRO"},
|
|
};
|
|
|
|
static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host);
|
|
static unsigned long ufs_qcom_opp_freq_to_clk_freq(struct ufs_hba *hba,
|
|
unsigned long freq, char *name);
|
|
static int ufs_qcom_set_core_clk_ctrl(struct ufs_hba *hba, bool is_scale_up, unsigned long freq);
|
|
|
|
static struct ufs_qcom_host *rcdev_to_ufs_host(struct reset_controller_dev *rcd)
|
|
{
|
|
return container_of(rcd, struct ufs_qcom_host, rcdev);
|
|
}
|
|
|
|
#ifdef CONFIG_SCSI_UFS_CRYPTO
|
|
/**
|
|
* ufs_qcom_config_ice_allocator() - ICE core allocator configuration
|
|
*
|
|
* @host: pointer to qcom specific variant structure.
|
|
*/
|
|
static void ufs_qcom_config_ice_allocator(struct ufs_qcom_host *host)
|
|
{
|
|
struct ufs_hba *hba = host->hba;
|
|
static const uint8_t val[4] = { NUM_RX_R1W0, NUM_TX_R0W1, NUM_RX_R1W1, NUM_TX_R1W1 };
|
|
u32 config;
|
|
|
|
if (!(host->caps & UFS_QCOM_CAP_ICE_CONFIG) ||
|
|
!(host->hba->caps & UFSHCD_CAP_CRYPTO))
|
|
return;
|
|
|
|
config = get_unaligned_le32(val);
|
|
|
|
ufshcd_writel(hba, ICE_ALLOCATOR_TYPE, REG_UFS_MEM_ICE_CONFIG);
|
|
ufshcd_writel(hba, config, REG_UFS_MEM_ICE_NUM_CORE);
|
|
}
|
|
|
|
static inline void ufs_qcom_ice_enable(struct ufs_qcom_host *host)
|
|
{
|
|
if (host->hba->caps & UFSHCD_CAP_CRYPTO)
|
|
qcom_ice_enable(host->ice);
|
|
}
|
|
|
|
static const struct blk_crypto_ll_ops ufs_qcom_crypto_ops; /* forward decl */
|
|
|
|
static int ufs_qcom_ice_init(struct ufs_qcom_host *host)
|
|
{
|
|
struct ufs_hba *hba = host->hba;
|
|
struct blk_crypto_profile *profile = &hba->crypto_profile;
|
|
struct device *dev = hba->dev;
|
|
struct qcom_ice *ice;
|
|
union ufs_crypto_capabilities caps;
|
|
union ufs_crypto_cap_entry cap;
|
|
int err;
|
|
int i;
|
|
|
|
ice = devm_of_qcom_ice_get(dev);
|
|
if (ice == ERR_PTR(-EOPNOTSUPP)) {
|
|
dev_warn(dev, "Disabling inline encryption support\n");
|
|
ice = NULL;
|
|
}
|
|
|
|
if (IS_ERR_OR_NULL(ice))
|
|
return PTR_ERR_OR_ZERO(ice);
|
|
|
|
host->ice = ice;
|
|
|
|
/* Initialize the blk_crypto_profile */
|
|
|
|
caps.reg_val = cpu_to_le32(ufshcd_readl(hba, REG_UFS_CCAP));
|
|
|
|
/* The number of keyslots supported is (CFGC+1) */
|
|
err = devm_blk_crypto_profile_init(dev, profile, caps.config_count + 1);
|
|
if (err)
|
|
return err;
|
|
|
|
profile->ll_ops = ufs_qcom_crypto_ops;
|
|
profile->max_dun_bytes_supported = 8;
|
|
profile->key_types_supported = qcom_ice_get_supported_key_type(ice);
|
|
profile->dev = dev;
|
|
|
|
/*
|
|
* Currently this driver only supports AES-256-XTS. All known versions
|
|
* of ICE support it, but to be safe make sure it is really declared in
|
|
* the crypto capability registers. The crypto capability registers
|
|
* also give the supported data unit size(s).
|
|
*/
|
|
for (i = 0; i < caps.num_crypto_cap; i++) {
|
|
cap.reg_val = cpu_to_le32(ufshcd_readl(hba,
|
|
REG_UFS_CRYPTOCAP +
|
|
i * sizeof(__le32)));
|
|
if (cap.algorithm_id == UFS_CRYPTO_ALG_AES_XTS &&
|
|
cap.key_size == UFS_CRYPTO_KEY_SIZE_256)
|
|
profile->modes_supported[BLK_ENCRYPTION_MODE_AES_256_XTS] |=
|
|
cap.sdus_mask * 512;
|
|
}
|
|
|
|
hba->caps |= UFSHCD_CAP_CRYPTO;
|
|
hba->quirks |= UFSHCD_QUIRK_CUSTOM_CRYPTO_PROFILE;
|
|
return 0;
|
|
}
|
|
|
|
static inline int ufs_qcom_ice_resume(struct ufs_qcom_host *host)
|
|
{
|
|
if (host->hba->caps & UFSHCD_CAP_CRYPTO)
|
|
return qcom_ice_resume(host->ice);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int ufs_qcom_ice_suspend(struct ufs_qcom_host *host)
|
|
{
|
|
if (host->hba->caps & UFSHCD_CAP_CRYPTO)
|
|
return qcom_ice_suspend(host->ice);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ufs_qcom_ice_keyslot_program(struct blk_crypto_profile *profile,
|
|
const struct blk_crypto_key *key,
|
|
unsigned int slot)
|
|
{
|
|
struct ufs_hba *hba = ufs_hba_from_crypto_profile(profile);
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
int err;
|
|
|
|
ufshcd_hold(hba);
|
|
err = qcom_ice_program_key(host->ice, slot, key);
|
|
ufshcd_release(hba);
|
|
return err;
|
|
}
|
|
|
|
static int ufs_qcom_ice_keyslot_evict(struct blk_crypto_profile *profile,
|
|
const struct blk_crypto_key *key,
|
|
unsigned int slot)
|
|
{
|
|
struct ufs_hba *hba = ufs_hba_from_crypto_profile(profile);
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
int err;
|
|
|
|
ufshcd_hold(hba);
|
|
err = qcom_ice_evict_key(host->ice, slot);
|
|
ufshcd_release(hba);
|
|
return err;
|
|
}
|
|
|
|
static int ufs_qcom_ice_derive_sw_secret(struct blk_crypto_profile *profile,
|
|
const u8 *eph_key, size_t eph_key_size,
|
|
u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE])
|
|
{
|
|
struct ufs_hba *hba = ufs_hba_from_crypto_profile(profile);
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
|
|
return qcom_ice_derive_sw_secret(host->ice, eph_key, eph_key_size,
|
|
sw_secret);
|
|
}
|
|
|
|
static int ufs_qcom_ice_import_key(struct blk_crypto_profile *profile,
|
|
const u8 *raw_key, size_t raw_key_size,
|
|
u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
|
|
{
|
|
struct ufs_hba *hba = ufs_hba_from_crypto_profile(profile);
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
|
|
return qcom_ice_import_key(host->ice, raw_key, raw_key_size, lt_key);
|
|
}
|
|
|
|
static int ufs_qcom_ice_generate_key(struct blk_crypto_profile *profile,
|
|
u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
|
|
{
|
|
struct ufs_hba *hba = ufs_hba_from_crypto_profile(profile);
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
|
|
return qcom_ice_generate_key(host->ice, lt_key);
|
|
}
|
|
|
|
static int ufs_qcom_ice_prepare_key(struct blk_crypto_profile *profile,
|
|
const u8 *lt_key, size_t lt_key_size,
|
|
u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
|
|
{
|
|
struct ufs_hba *hba = ufs_hba_from_crypto_profile(profile);
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
|
|
return qcom_ice_prepare_key(host->ice, lt_key, lt_key_size, eph_key);
|
|
}
|
|
|
|
static const struct blk_crypto_ll_ops ufs_qcom_crypto_ops = {
|
|
.keyslot_program = ufs_qcom_ice_keyslot_program,
|
|
.keyslot_evict = ufs_qcom_ice_keyslot_evict,
|
|
.derive_sw_secret = ufs_qcom_ice_derive_sw_secret,
|
|
.import_key = ufs_qcom_ice_import_key,
|
|
.generate_key = ufs_qcom_ice_generate_key,
|
|
.prepare_key = ufs_qcom_ice_prepare_key,
|
|
};
|
|
|
|
#else
|
|
|
|
static inline void ufs_qcom_ice_enable(struct ufs_qcom_host *host)
|
|
{
|
|
}
|
|
|
|
static int ufs_qcom_ice_init(struct ufs_qcom_host *host)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline int ufs_qcom_ice_resume(struct ufs_qcom_host *host)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline int ufs_qcom_ice_suspend(struct ufs_qcom_host *host)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void ufs_qcom_config_ice_allocator(struct ufs_qcom_host *host)
|
|
{
|
|
}
|
|
|
|
#endif
|
|
|
|
static void ufs_qcom_disable_lane_clks(struct ufs_qcom_host *host)
|
|
{
|
|
if (!host->is_lane_clks_enabled)
|
|
return;
|
|
|
|
clk_bulk_disable_unprepare(host->num_clks, host->clks);
|
|
|
|
host->is_lane_clks_enabled = false;
|
|
}
|
|
|
|
static int ufs_qcom_enable_lane_clks(struct ufs_qcom_host *host)
|
|
{
|
|
int err;
|
|
|
|
err = clk_bulk_prepare_enable(host->num_clks, host->clks);
|
|
if (err)
|
|
return err;
|
|
|
|
host->is_lane_clks_enabled = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ufs_qcom_init_lane_clks(struct ufs_qcom_host *host)
|
|
{
|
|
int err;
|
|
struct device *dev = host->hba->dev;
|
|
|
|
if (has_acpi_companion(dev))
|
|
return 0;
|
|
|
|
err = devm_clk_bulk_get_all(dev, &host->clks);
|
|
if (err <= 0)
|
|
return err;
|
|
|
|
host->num_clks = err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ufs_qcom_check_hibern8(struct ufs_hba *hba)
|
|
{
|
|
int err;
|
|
u32 tx_fsm_val;
|
|
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,
|
|
UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
|
|
&tx_fsm_val);
|
|
if (err || tx_fsm_val == 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,
|
|
UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
|
|
&tx_fsm_val);
|
|
|
|
if (err) {
|
|
dev_err(hba->dev, "%s: unable to get TX_FSM_STATE, err %d\n",
|
|
__func__, err);
|
|
} else if (tx_fsm_val != TX_FSM_HIBERN8) {
|
|
err = tx_fsm_val;
|
|
dev_err(hba->dev, "%s: invalid TX_FSM_STATE = %d\n",
|
|
__func__, err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static void ufs_qcom_select_unipro_mode(struct ufs_qcom_host *host)
|
|
{
|
|
ufshcd_rmwl(host->hba, QUNIPRO_SEL, QUNIPRO_SEL, REG_UFS_CFG1);
|
|
|
|
if (host->hw_ver.major >= 0x05)
|
|
ufshcd_rmwl(host->hba, QUNIPRO_G4_SEL, 0, REG_UFS_CFG0);
|
|
}
|
|
|
|
/*
|
|
* ufs_qcom_host_reset - reset host controller and PHY
|
|
*/
|
|
static int ufs_qcom_host_reset(struct ufs_hba *hba)
|
|
{
|
|
int ret;
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
bool reenable_intr;
|
|
|
|
if (!host->core_reset)
|
|
return 0;
|
|
|
|
reenable_intr = hba->is_irq_enabled;
|
|
ufshcd_disable_irq(hba);
|
|
|
|
ret = reset_control_assert(host->core_reset);
|
|
if (ret) {
|
|
dev_err(hba->dev, "%s: core_reset assert failed, err = %d\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* The hardware requirement for delay between assert/deassert
|
|
* is at least 3-4 sleep clock (32.7KHz) cycles, which comes to
|
|
* ~125us (4/32768). To be on the safe side add 200us delay.
|
|
*/
|
|
usleep_range(200, 210);
|
|
|
|
ret = reset_control_deassert(host->core_reset);
|
|
if (ret) {
|
|
dev_err(hba->dev, "%s: core_reset deassert failed, err = %d\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
usleep_range(1000, 1100);
|
|
|
|
if (reenable_intr)
|
|
ufshcd_enable_irq(hba);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 ufs_qcom_get_hs_gear(struct ufs_hba *hba)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
|
|
if (host->hw_ver.major >= 0x4)
|
|
return UFS_QCOM_MAX_GEAR(ufshcd_readl(hba, REG_UFS_PARAM0));
|
|
|
|
/* Default is HS-G3 */
|
|
return UFS_HS_G3;
|
|
}
|
|
|
|
static int ufs_qcom_power_up_sequence(struct ufs_hba *hba)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
struct ufs_host_params *host_params = &host->host_params;
|
|
struct phy *phy = host->generic_phy;
|
|
enum phy_mode mode;
|
|
int ret;
|
|
|
|
/*
|
|
* HW ver 5 can only support up to HS-G5 Rate-A due to HW limitations.
|
|
* If the HS-G5 PHY gear is used, update host_params->hs_rate to Rate-A,
|
|
* so that the subsequent power mode change shall stick to Rate-A.
|
|
*/
|
|
if (host->hw_ver.major == 0x5) {
|
|
if (host->phy_gear == UFS_HS_G5)
|
|
host_params->hs_rate = PA_HS_MODE_A;
|
|
else
|
|
host_params->hs_rate = PA_HS_MODE_B;
|
|
}
|
|
|
|
mode = host_params->hs_rate == PA_HS_MODE_B ? PHY_MODE_UFS_HS_B : PHY_MODE_UFS_HS_A;
|
|
|
|
/* Reset UFS Host Controller and PHY */
|
|
ret = ufs_qcom_host_reset(hba);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (phy->power_count)
|
|
phy_power_off(phy);
|
|
|
|
|
|
/* phy initialization - calibrate the phy */
|
|
ret = phy_init(phy);
|
|
if (ret) {
|
|
dev_err(hba->dev, "%s: phy init failed, ret = %d\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = phy_set_mode_ext(phy, mode, host->phy_gear);
|
|
if (ret)
|
|
goto out_disable_phy;
|
|
|
|
/* power on phy - start serdes and phy's power and clocks */
|
|
ret = phy_power_on(phy);
|
|
if (ret) {
|
|
dev_err(hba->dev, "%s: phy power on failed, ret = %d\n",
|
|
__func__, ret);
|
|
goto out_disable_phy;
|
|
}
|
|
|
|
ret = phy_calibrate(phy);
|
|
if (ret) {
|
|
dev_err(hba->dev, "Failed to calibrate PHY: %d\n", ret);
|
|
goto out_disable_phy;
|
|
}
|
|
|
|
ufs_qcom_select_unipro_mode(host);
|
|
|
|
return 0;
|
|
|
|
out_disable_phy:
|
|
phy_exit(phy);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* The UTP controller has a number of internal clock gating cells (CGCs).
|
|
* Internal hardware sub-modules within the UTP controller control the CGCs.
|
|
* Hardware CGCs disable the clock to inactivate UTP sub-modules not involved
|
|
* in a specific operation, UTP controller CGCs are by default disabled and
|
|
* this function enables them (after every UFS link startup) to save some power
|
|
* leakage.
|
|
*/
|
|
static void ufs_qcom_enable_hw_clk_gating(struct ufs_hba *hba)
|
|
{
|
|
int err;
|
|
|
|
/* Enable UTP internal clock gating */
|
|
ufshcd_rmwl(hba, REG_UFS_CFG2_CGC_EN_ALL, REG_UFS_CFG2_CGC_EN_ALL,
|
|
REG_UFS_CFG2);
|
|
|
|
/* Ensure that HW clock gating is enabled before next operations */
|
|
ufshcd_readl(hba, REG_UFS_CFG2);
|
|
|
|
/* Enable Unipro internal clock gating */
|
|
err = ufshcd_dme_rmw(hba, DL_VS_CLK_CFG_MASK,
|
|
DL_VS_CLK_CFG_MASK, DL_VS_CLK_CFG);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = ufshcd_dme_rmw(hba, PA_VS_CLK_CFG_REG_MASK,
|
|
PA_VS_CLK_CFG_REG_MASK, PA_VS_CLK_CFG_REG);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = ufshcd_dme_rmw(hba, DME_VS_CORE_CLK_CTRL_DME_HW_CGC_EN,
|
|
DME_VS_CORE_CLK_CTRL_DME_HW_CGC_EN,
|
|
DME_VS_CORE_CLK_CTRL);
|
|
out:
|
|
if (err)
|
|
dev_err(hba->dev, "hw clk gating enabled failed\n");
|
|
}
|
|
|
|
static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba,
|
|
enum ufs_notify_change_status status)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
int err;
|
|
|
|
switch (status) {
|
|
case PRE_CHANGE:
|
|
err = ufs_qcom_power_up_sequence(hba);
|
|
if (err)
|
|
return err;
|
|
|
|
/*
|
|
* The PHY PLL output is the source of tx/rx lane symbol
|
|
* clocks, hence, enable the lane clocks only after PHY
|
|
* is initialized.
|
|
*/
|
|
err = ufs_qcom_enable_lane_clks(host);
|
|
break;
|
|
case POST_CHANGE:
|
|
/* check if UFS PHY moved from DISABLED to HIBERN8 */
|
|
err = ufs_qcom_check_hibern8(hba);
|
|
ufs_qcom_enable_hw_clk_gating(hba);
|
|
ufs_qcom_ice_enable(host);
|
|
ufs_qcom_config_ice_allocator(host);
|
|
break;
|
|
default:
|
|
dev_err(hba->dev, "%s: invalid status %d\n", __func__, status);
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* ufs_qcom_cfg_timers - Configure ufs qcom cfg timers
|
|
*
|
|
* @hba: host controller instance
|
|
* @is_pre_scale_up: flag to check if pre scale up condition.
|
|
* @freq: target opp freq
|
|
* Return: zero for success and non-zero in case of a failure.
|
|
*/
|
|
static int ufs_qcom_cfg_timers(struct ufs_hba *hba, bool is_pre_scale_up, unsigned long freq)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
struct ufs_clk_info *clki;
|
|
unsigned long clk_freq = 0;
|
|
u32 core_clk_cycles_per_us;
|
|
|
|
/*
|
|
* UTP controller uses SYS1CLK_1US_REG register for Interrupt
|
|
* Aggregation logic.
|
|
* It is mandatory to write SYS1CLK_1US_REG register on UFS host
|
|
* controller V4.0.0 onwards.
|
|
*/
|
|
if (host->hw_ver.major < 4 && !ufshcd_is_intr_aggr_allowed(hba))
|
|
return 0;
|
|
|
|
if (hba->use_pm_opp && freq != ULONG_MAX) {
|
|
clk_freq = ufs_qcom_opp_freq_to_clk_freq(hba, freq, "core_clk");
|
|
if (clk_freq)
|
|
goto cfg_timers;
|
|
}
|
|
|
|
list_for_each_entry(clki, &hba->clk_list_head, list) {
|
|
if (!strcmp(clki->name, "core_clk")) {
|
|
if (freq == ULONG_MAX) {
|
|
clk_freq = clki->max_freq;
|
|
break;
|
|
}
|
|
|
|
if (is_pre_scale_up)
|
|
clk_freq = clki->max_freq;
|
|
else
|
|
clk_freq = clk_get_rate(clki->clk);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
cfg_timers:
|
|
/* If frequency is smaller than 1MHz, set to 1MHz */
|
|
if (clk_freq < DEFAULT_CLK_RATE_HZ)
|
|
clk_freq = DEFAULT_CLK_RATE_HZ;
|
|
|
|
core_clk_cycles_per_us = clk_freq / USEC_PER_SEC;
|
|
if (ufshcd_readl(hba, REG_UFS_SYS1CLK_1US) != core_clk_cycles_per_us) {
|
|
ufshcd_writel(hba, core_clk_cycles_per_us, REG_UFS_SYS1CLK_1US);
|
|
/*
|
|
* make sure above write gets applied before we return from
|
|
* this function.
|
|
*/
|
|
ufshcd_readl(hba, REG_UFS_SYS1CLK_1US);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ufs_qcom_link_startup_notify(struct ufs_hba *hba,
|
|
enum ufs_notify_change_status status)
|
|
{
|
|
int err = 0;
|
|
|
|
switch (status) {
|
|
case PRE_CHANGE:
|
|
if (ufs_qcom_cfg_timers(hba, false, ULONG_MAX)) {
|
|
dev_err(hba->dev, "%s: ufs_qcom_cfg_timers() failed\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = ufs_qcom_set_core_clk_ctrl(hba, true, ULONG_MAX);
|
|
if (err)
|
|
dev_err(hba->dev, "cfg core clk ctrl failed\n");
|
|
/*
|
|
* Some UFS devices (and may be host) 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.
|
|
*/
|
|
err = ufshcd_disable_host_tx_lcc(hba);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static void ufs_qcom_device_reset_ctrl(struct ufs_hba *hba, bool asserted)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
|
|
/* reset gpio is optional */
|
|
if (!host->device_reset)
|
|
return;
|
|
|
|
gpiod_set_value_cansleep(host->device_reset, asserted);
|
|
}
|
|
|
|
static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op,
|
|
enum ufs_notify_change_status status)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
|
|
if (status == PRE_CHANGE)
|
|
return 0;
|
|
|
|
if (!ufs_qcom_is_link_active(hba))
|
|
ufs_qcom_disable_lane_clks(host);
|
|
|
|
|
|
/* reset the connected UFS device during power down */
|
|
if (ufs_qcom_is_link_off(hba) && host->device_reset)
|
|
ufs_qcom_device_reset_ctrl(hba, true);
|
|
|
|
return ufs_qcom_ice_suspend(host);
|
|
}
|
|
|
|
static int ufs_qcom_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
int err;
|
|
|
|
err = ufs_qcom_enable_lane_clks(host);
|
|
if (err)
|
|
return err;
|
|
|
|
return ufs_qcom_ice_resume(host);
|
|
}
|
|
|
|
static void ufs_qcom_dev_ref_clk_ctrl(struct ufs_qcom_host *host, bool enable)
|
|
{
|
|
if (host->dev_ref_clk_ctrl_mmio &&
|
|
(enable ^ host->is_dev_ref_clk_enabled)) {
|
|
u32 temp = readl_relaxed(host->dev_ref_clk_ctrl_mmio);
|
|
|
|
if (enable)
|
|
temp |= host->dev_ref_clk_en_mask;
|
|
else
|
|
temp &= ~host->dev_ref_clk_en_mask;
|
|
|
|
/*
|
|
* If we are here to disable this clock it might be immediately
|
|
* after entering into hibern8 in which case we need to make
|
|
* sure that device ref_clk is active for specific time after
|
|
* hibern8 enter.
|
|
*/
|
|
if (!enable) {
|
|
unsigned long gating_wait;
|
|
|
|
gating_wait = host->hba->dev_info.clk_gating_wait_us;
|
|
if (!gating_wait) {
|
|
udelay(1);
|
|
} else {
|
|
/*
|
|
* bRefClkGatingWaitTime defines the minimum
|
|
* time for which the reference clock is
|
|
* required by device during transition from
|
|
* HS-MODE to LS-MODE or HIBERN8 state. Give it
|
|
* more delay to be on the safe side.
|
|
*/
|
|
gating_wait += 10;
|
|
usleep_range(gating_wait, gating_wait + 10);
|
|
}
|
|
}
|
|
|
|
writel_relaxed(temp, host->dev_ref_clk_ctrl_mmio);
|
|
|
|
/*
|
|
* Make sure the write to ref_clk reaches the destination and
|
|
* not stored in a Write Buffer (WB).
|
|
*/
|
|
readl(host->dev_ref_clk_ctrl_mmio);
|
|
|
|
/*
|
|
* If we call hibern8 exit after this, we need to make sure that
|
|
* device ref_clk is stable for at least 1us before the hibern8
|
|
* exit command.
|
|
*/
|
|
if (enable)
|
|
udelay(1);
|
|
|
|
host->is_dev_ref_clk_enabled = enable;
|
|
}
|
|
}
|
|
|
|
static int ufs_qcom_icc_set_bw(struct ufs_qcom_host *host, u32 mem_bw, u32 cfg_bw)
|
|
{
|
|
struct device *dev = host->hba->dev;
|
|
int ret;
|
|
|
|
ret = icc_set_bw(host->icc_ddr, 0, mem_bw);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to set bandwidth request: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = icc_set_bw(host->icc_cpu, 0, cfg_bw);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to set bandwidth request: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct __ufs_qcom_bw_table ufs_qcom_get_bw_table(struct ufs_qcom_host *host)
|
|
{
|
|
struct ufs_pa_layer_attr *p = &host->dev_req_params;
|
|
int gear = max_t(u32, p->gear_rx, p->gear_tx);
|
|
int lane = max_t(u32, p->lane_rx, p->lane_tx);
|
|
|
|
if (WARN_ONCE(gear > QCOM_UFS_MAX_GEAR,
|
|
"ICC scaling for UFS Gear (%d) not supported. Using Gear (%d) bandwidth\n",
|
|
gear, QCOM_UFS_MAX_GEAR))
|
|
gear = QCOM_UFS_MAX_GEAR;
|
|
|
|
if (WARN_ONCE(lane > QCOM_UFS_MAX_LANE,
|
|
"ICC scaling for UFS Lane (%d) not supported. Using Lane (%d) bandwidth\n",
|
|
lane, QCOM_UFS_MAX_LANE))
|
|
lane = QCOM_UFS_MAX_LANE;
|
|
|
|
if (ufshcd_is_hs_mode(p)) {
|
|
if (p->hs_rate == PA_HS_MODE_B)
|
|
return ufs_qcom_bw_table[MODE_HS_RB][gear][lane];
|
|
else
|
|
return ufs_qcom_bw_table[MODE_HS_RA][gear][lane];
|
|
} else {
|
|
return ufs_qcom_bw_table[MODE_PWM][gear][lane];
|
|
}
|
|
}
|
|
|
|
static int ufs_qcom_icc_update_bw(struct ufs_qcom_host *host)
|
|
{
|
|
struct __ufs_qcom_bw_table bw_table;
|
|
|
|
bw_table = ufs_qcom_get_bw_table(host);
|
|
|
|
return ufs_qcom_icc_set_bw(host, bw_table.mem_bw, bw_table.cfg_bw);
|
|
}
|
|
|
|
static void ufs_qcom_set_tx_hs_equalizer(struct ufs_hba *hba, u32 gear, u32 tx_lanes)
|
|
{
|
|
u32 equalizer_val;
|
|
int ret, i;
|
|
|
|
/* Determine the equalizer value based on the gear */
|
|
equalizer_val = (gear == 5) ? DEEMPHASIS_3_5_dB : NO_DEEMPHASIS;
|
|
|
|
for (i = 0; i < tx_lanes; i++) {
|
|
ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_HS_EQUALIZER, i),
|
|
equalizer_val);
|
|
if (ret)
|
|
dev_err(hba->dev, "%s: failed equalizer lane %d\n",
|
|
__func__, i);
|
|
}
|
|
}
|
|
|
|
static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba,
|
|
enum ufs_notify_change_status status,
|
|
const struct ufs_pa_layer_attr *dev_max_params,
|
|
struct ufs_pa_layer_attr *dev_req_params)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
struct ufs_host_params *host_params = &host->host_params;
|
|
int ret = 0;
|
|
|
|
if (!dev_req_params) {
|
|
pr_err("%s: incoming dev_req_params is NULL\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (status) {
|
|
case PRE_CHANGE:
|
|
ret = ufshcd_negotiate_pwr_params(host_params, dev_max_params, dev_req_params);
|
|
if (ret) {
|
|
dev_err(hba->dev, "%s: failed to determine capabilities\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* During UFS driver probe, always update the PHY gear to match the negotiated
|
|
* gear, so that, if quirk UFSHCD_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH is enabled,
|
|
* the second init can program the optimal PHY settings. This allows one to start
|
|
* the first init with either the minimum or the maximum support gear.
|
|
*/
|
|
if (hba->ufshcd_state == UFSHCD_STATE_RESET) {
|
|
/*
|
|
* Skip REINIT if the negotiated gear matches with the
|
|
* initial phy_gear. Otherwise, update the phy_gear to
|
|
* program the optimal gear setting during REINIT.
|
|
*/
|
|
if (host->phy_gear == dev_req_params->gear_tx)
|
|
hba->quirks &= ~UFSHCD_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH;
|
|
else
|
|
host->phy_gear = dev_req_params->gear_tx;
|
|
}
|
|
|
|
/* enable the device ref clock before changing to HS mode */
|
|
if (!ufshcd_is_hs_mode(&hba->pwr_info) &&
|
|
ufshcd_is_hs_mode(dev_req_params))
|
|
ufs_qcom_dev_ref_clk_ctrl(host, true);
|
|
|
|
if (host->hw_ver.major >= 0x4) {
|
|
ufshcd_dme_configure_adapt(hba,
|
|
dev_req_params->gear_tx,
|
|
PA_INITIAL_ADAPT);
|
|
}
|
|
|
|
if (hba->dev_quirks & UFS_DEVICE_QUIRK_PA_TX_DEEMPHASIS_TUNING)
|
|
ufs_qcom_set_tx_hs_equalizer(hba,
|
|
dev_req_params->gear_tx, dev_req_params->lane_tx);
|
|
|
|
break;
|
|
case POST_CHANGE:
|
|
/* cache the power mode parameters to use internally */
|
|
memcpy(&host->dev_req_params,
|
|
dev_req_params, sizeof(*dev_req_params));
|
|
|
|
ufs_qcom_icc_update_bw(host);
|
|
|
|
/* disable the device ref clock if entered PWM mode */
|
|
if (ufshcd_is_hs_mode(&hba->pwr_info) &&
|
|
!ufshcd_is_hs_mode(dev_req_params))
|
|
ufs_qcom_dev_ref_clk_ctrl(host, false);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ufs_qcom_quirk_host_pa_saveconfigtime(struct ufs_hba *hba)
|
|
{
|
|
int err;
|
|
u32 pa_vs_config_reg1;
|
|
|
|
err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_VS_CONFIG_REG1),
|
|
&pa_vs_config_reg1);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Allow extension of MSB bits of PA_SaveConfigTime attribute */
|
|
return ufshcd_dme_set(hba, UIC_ARG_MIB(PA_VS_CONFIG_REG1),
|
|
(pa_vs_config_reg1 | (1 << 12)));
|
|
}
|
|
|
|
static void ufs_qcom_override_pa_tx_hsg1_sync_len(struct ufs_hba *hba)
|
|
{
|
|
int err;
|
|
|
|
err = ufshcd_dme_peer_set(hba, UIC_ARG_MIB(PA_TX_HSG1_SYNC_LENGTH),
|
|
PA_TX_HSG1_SYNC_LENGTH_VAL);
|
|
if (err)
|
|
dev_err(hba->dev, "Failed (%d) set PA_TX_HSG1_SYNC_LENGTH\n", err);
|
|
}
|
|
|
|
static int ufs_qcom_apply_dev_quirks(struct ufs_hba *hba)
|
|
{
|
|
int err = 0;
|
|
|
|
if (hba->dev_quirks & UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME)
|
|
err = ufs_qcom_quirk_host_pa_saveconfigtime(hba);
|
|
|
|
if (hba->dev_quirks & UFS_DEVICE_QUIRK_PA_TX_HSG1_SYNC_LENGTH)
|
|
ufs_qcom_override_pa_tx_hsg1_sync_len(hba);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* UFS device-specific quirks */
|
|
static struct ufs_dev_quirk ufs_qcom_dev_fixups[] = {
|
|
{ .wmanufacturerid = UFS_VENDOR_SKHYNIX,
|
|
.model = UFS_ANY_MODEL,
|
|
.quirk = UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM },
|
|
{ .wmanufacturerid = UFS_VENDOR_TOSHIBA,
|
|
.model = UFS_ANY_MODEL,
|
|
.quirk = UFS_DEVICE_QUIRK_DELAY_AFTER_LPM },
|
|
{ .wmanufacturerid = UFS_VENDOR_WDC,
|
|
.model = UFS_ANY_MODEL,
|
|
.quirk = UFS_DEVICE_QUIRK_HOST_PA_TACTIVATE },
|
|
{ .wmanufacturerid = UFS_VENDOR_SAMSUNG,
|
|
.model = UFS_ANY_MODEL,
|
|
.quirk = UFS_DEVICE_QUIRK_PA_TX_HSG1_SYNC_LENGTH |
|
|
UFS_DEVICE_QUIRK_PA_TX_DEEMPHASIS_TUNING },
|
|
{}
|
|
};
|
|
|
|
static void ufs_qcom_fixup_dev_quirks(struct ufs_hba *hba)
|
|
{
|
|
ufshcd_fixup_dev_quirks(hba, ufs_qcom_dev_fixups);
|
|
}
|
|
|
|
static u32 ufs_qcom_get_ufs_hci_version(struct ufs_hba *hba)
|
|
{
|
|
return ufshci_version(2, 0);
|
|
}
|
|
|
|
/**
|
|
* ufs_qcom_advertise_quirks - advertise the known QCOM UFS controller quirks
|
|
* @hba: host controller instance
|
|
*
|
|
* QCOM UFS host controller might have some non standard behaviours (quirks)
|
|
* than what is specified by UFSHCI specification. Advertise all such
|
|
* quirks to standard UFS host controller driver so standard takes them into
|
|
* account.
|
|
*/
|
|
static void ufs_qcom_advertise_quirks(struct ufs_hba *hba)
|
|
{
|
|
const struct ufs_qcom_drvdata *drvdata = of_device_get_match_data(hba->dev);
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
|
|
if (host->hw_ver.major == 0x2)
|
|
hba->quirks |= UFSHCD_QUIRK_BROKEN_UFS_HCI_VERSION;
|
|
|
|
if (host->hw_ver.major > 0x3)
|
|
hba->quirks |= UFSHCD_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH;
|
|
|
|
if (drvdata && drvdata->quirks)
|
|
hba->quirks |= drvdata->quirks;
|
|
}
|
|
|
|
static void ufs_qcom_set_phy_gear(struct ufs_qcom_host *host)
|
|
{
|
|
struct ufs_host_params *host_params = &host->host_params;
|
|
u32 val, dev_major;
|
|
|
|
/*
|
|
* Default to powering up the PHY to the max gear possible, which is
|
|
* backwards compatible with lower gears but not optimal from
|
|
* a power usage point of view. After device negotiation, if the
|
|
* gear is lower a reinit will be performed to program the PHY
|
|
* to the ideal gear for this combo of controller and device.
|
|
*/
|
|
host->phy_gear = host_params->hs_tx_gear;
|
|
|
|
if (host->hw_ver.major < 0x4) {
|
|
/*
|
|
* These controllers only have one PHY init sequence,
|
|
* let's power up the PHY using that (the minimum supported
|
|
* gear, UFS_HS_G2).
|
|
*/
|
|
host->phy_gear = UFS_HS_G2;
|
|
} else if (host->hw_ver.major >= 0x5) {
|
|
val = ufshcd_readl(host->hba, REG_UFS_DEBUG_SPARE_CFG);
|
|
dev_major = FIELD_GET(UFS_DEV_VER_MAJOR_MASK, val);
|
|
|
|
/*
|
|
* Since the UFS device version is populated, let's remove the
|
|
* REINIT quirk as the negotiated gear won't change during boot.
|
|
* So there is no need to do reinit.
|
|
*/
|
|
if (dev_major != 0x0)
|
|
host->hba->quirks &= ~UFSHCD_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH;
|
|
|
|
/*
|
|
* For UFS 3.1 device and older, power up the PHY using HS-G4
|
|
* PHY gear to save power.
|
|
*/
|
|
if (dev_major > 0x0 && dev_major < 0x4)
|
|
host->phy_gear = UFS_HS_G4;
|
|
}
|
|
}
|
|
|
|
static void ufs_qcom_set_host_params(struct ufs_hba *hba)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
struct ufs_host_params *host_params = &host->host_params;
|
|
|
|
ufshcd_init_host_params(host_params);
|
|
|
|
/* This driver only supports symmetic gear setting i.e., hs_tx_gear == hs_rx_gear */
|
|
host_params->hs_tx_gear = host_params->hs_rx_gear = ufs_qcom_get_hs_gear(hba);
|
|
}
|
|
|
|
static void ufs_qcom_set_host_caps(struct ufs_hba *hba)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
|
|
if (host->hw_ver.major >= 0x5)
|
|
host->caps |= UFS_QCOM_CAP_ICE_CONFIG;
|
|
}
|
|
|
|
static void ufs_qcom_set_caps(struct ufs_hba *hba)
|
|
{
|
|
hba->caps |= UFSHCD_CAP_CLK_GATING | UFSHCD_CAP_HIBERN8_WITH_CLK_GATING;
|
|
hba->caps |= UFSHCD_CAP_CLK_SCALING | UFSHCD_CAP_WB_WITH_CLK_SCALING;
|
|
hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
|
|
hba->caps |= UFSHCD_CAP_WB_EN;
|
|
hba->caps |= UFSHCD_CAP_AGGR_POWER_COLLAPSE;
|
|
hba->caps |= UFSHCD_CAP_RPM_AUTOSUSPEND;
|
|
|
|
ufs_qcom_set_host_caps(hba);
|
|
}
|
|
|
|
/**
|
|
* ufs_qcom_setup_clocks - enables/disable clocks
|
|
* @hba: host controller instance
|
|
* @on: If true, enable clocks else disable them.
|
|
* @status: PRE_CHANGE or POST_CHANGE notify
|
|
*
|
|
* There are certain clocks which comes from the PHY so it needs
|
|
* to be managed together along with controller clocks which also
|
|
* provides a better power saving. Hence keep phy_power_off/on calls
|
|
* in ufs_qcom_setup_clocks, so that PHY's regulators & clks can be
|
|
* turned on/off along with UFS's clocks.
|
|
*
|
|
* Return: 0 on success, non-zero on failure.
|
|
*/
|
|
static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on,
|
|
enum ufs_notify_change_status status)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
struct phy *phy;
|
|
int err;
|
|
|
|
/*
|
|
* In case ufs_qcom_init() is not yet done, simply ignore.
|
|
* This ufs_qcom_setup_clocks() shall be called from
|
|
* ufs_qcom_init() after init is done.
|
|
*/
|
|
if (!host)
|
|
return 0;
|
|
|
|
phy = host->generic_phy;
|
|
|
|
switch (status) {
|
|
case PRE_CHANGE:
|
|
if (on) {
|
|
ufs_qcom_icc_update_bw(host);
|
|
} else {
|
|
if (!ufs_qcom_is_link_active(hba)) {
|
|
/* disable device ref_clk */
|
|
ufs_qcom_dev_ref_clk_ctrl(host, false);
|
|
}
|
|
|
|
err = phy_power_off(phy);
|
|
if (err) {
|
|
dev_err(hba->dev, "phy power off failed, ret=%d\n", err);
|
|
return err;
|
|
}
|
|
}
|
|
break;
|
|
case POST_CHANGE:
|
|
if (on) {
|
|
err = phy_power_on(phy);
|
|
if (err) {
|
|
dev_err(hba->dev, "phy power on failed, ret = %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/* enable the device ref clock for HS mode*/
|
|
if (ufshcd_is_hs_mode(&hba->pwr_info))
|
|
ufs_qcom_dev_ref_clk_ctrl(host, true);
|
|
} else {
|
|
ufs_qcom_icc_set_bw(host, ufs_qcom_bw_table[MODE_MIN][0][0].mem_bw,
|
|
ufs_qcom_bw_table[MODE_MIN][0][0].cfg_bw);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ufs_qcom_reset_assert(struct reset_controller_dev *rcdev, unsigned long id)
|
|
{
|
|
struct ufs_qcom_host *host = rcdev_to_ufs_host(rcdev);
|
|
|
|
ufs_qcom_assert_reset(host->hba);
|
|
/* provide 1ms delay to let the reset pulse propagate. */
|
|
usleep_range(1000, 1100);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ufs_qcom_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id)
|
|
{
|
|
struct ufs_qcom_host *host = rcdev_to_ufs_host(rcdev);
|
|
|
|
ufs_qcom_deassert_reset(host->hba);
|
|
|
|
/*
|
|
* after reset deassertion, phy will need all ref clocks,
|
|
* voltage, current to settle down before starting serdes.
|
|
*/
|
|
usleep_range(1000, 1100);
|
|
return 0;
|
|
}
|
|
|
|
static const struct reset_control_ops ufs_qcom_reset_ops = {
|
|
.assert = ufs_qcom_reset_assert,
|
|
.deassert = ufs_qcom_reset_deassert,
|
|
};
|
|
|
|
static int ufs_qcom_icc_init(struct ufs_qcom_host *host)
|
|
{
|
|
struct device *dev = host->hba->dev;
|
|
int ret;
|
|
|
|
host->icc_ddr = devm_of_icc_get(dev, "ufs-ddr");
|
|
if (IS_ERR(host->icc_ddr))
|
|
return dev_err_probe(dev, PTR_ERR(host->icc_ddr),
|
|
"failed to acquire interconnect path\n");
|
|
|
|
host->icc_cpu = devm_of_icc_get(dev, "cpu-ufs");
|
|
if (IS_ERR(host->icc_cpu))
|
|
return dev_err_probe(dev, PTR_ERR(host->icc_cpu),
|
|
"failed to acquire interconnect path\n");
|
|
|
|
/*
|
|
* Set Maximum bandwidth vote before initializing the UFS controller and
|
|
* device. Ideally, a minimal interconnect vote would suffice for the
|
|
* initialization, but a max vote would allow faster initialization.
|
|
*/
|
|
ret = ufs_qcom_icc_set_bw(host, ufs_qcom_bw_table[MODE_MAX][0][0].mem_bw,
|
|
ufs_qcom_bw_table[MODE_MAX][0][0].cfg_bw);
|
|
if (ret < 0)
|
|
return dev_err_probe(dev, ret, "failed to set bandwidth request\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ufs_qcom_init - bind phy with controller
|
|
* @hba: host controller instance
|
|
*
|
|
* Binds PHY with controller and powers up PHY enabling clocks
|
|
* and regulators.
|
|
*
|
|
* Return: -EPROBE_DEFER if binding fails, returns negative error
|
|
* on phy power up failure and returns zero on success.
|
|
*/
|
|
static int ufs_qcom_init(struct ufs_hba *hba)
|
|
{
|
|
int err;
|
|
struct device *dev = hba->dev;
|
|
struct ufs_qcom_host *host;
|
|
struct ufs_clk_info *clki;
|
|
const struct ufs_qcom_drvdata *drvdata = of_device_get_match_data(hba->dev);
|
|
|
|
host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
|
|
if (!host)
|
|
return -ENOMEM;
|
|
|
|
/* Make a two way bind between the qcom host and the hba */
|
|
host->hba = hba;
|
|
ufshcd_set_variant(hba, host);
|
|
|
|
/* Setup the optional reset control of HCI */
|
|
host->core_reset = devm_reset_control_get_optional(hba->dev, "rst");
|
|
if (IS_ERR(host->core_reset)) {
|
|
err = dev_err_probe(dev, PTR_ERR(host->core_reset),
|
|
"Failed to get reset control\n");
|
|
goto out_variant_clear;
|
|
}
|
|
|
|
/* Fire up the reset controller. Failure here is non-fatal. */
|
|
host->rcdev.of_node = dev->of_node;
|
|
host->rcdev.ops = &ufs_qcom_reset_ops;
|
|
host->rcdev.owner = dev->driver->owner;
|
|
host->rcdev.nr_resets = 1;
|
|
err = devm_reset_controller_register(dev, &host->rcdev);
|
|
if (err)
|
|
dev_warn(dev, "Failed to register reset controller\n");
|
|
|
|
if (!has_acpi_companion(dev)) {
|
|
host->generic_phy = devm_phy_get(dev, "ufsphy");
|
|
if (IS_ERR(host->generic_phy)) {
|
|
err = dev_err_probe(dev, PTR_ERR(host->generic_phy), "Failed to get PHY\n");
|
|
goto out_variant_clear;
|
|
}
|
|
}
|
|
|
|
err = ufs_qcom_icc_init(host);
|
|
if (err)
|
|
goto out_variant_clear;
|
|
|
|
host->device_reset = devm_gpiod_get_optional(dev, "reset",
|
|
GPIOD_OUT_HIGH);
|
|
if (IS_ERR(host->device_reset)) {
|
|
err = dev_err_probe(dev, PTR_ERR(host->device_reset),
|
|
"Failed to acquire device reset gpio\n");
|
|
goto out_variant_clear;
|
|
}
|
|
|
|
ufs_qcom_get_controller_revision(hba, &host->hw_ver.major,
|
|
&host->hw_ver.minor, &host->hw_ver.step);
|
|
|
|
host->dev_ref_clk_ctrl_mmio = hba->mmio_base + REG_UFS_CFG1;
|
|
host->dev_ref_clk_en_mask = BIT(26);
|
|
|
|
list_for_each_entry(clki, &hba->clk_list_head, list) {
|
|
if (!strcmp(clki->name, "core_clk_unipro"))
|
|
clki->keep_link_active = true;
|
|
}
|
|
|
|
err = ufs_qcom_init_lane_clks(host);
|
|
if (err)
|
|
goto out_variant_clear;
|
|
|
|
ufs_qcom_set_caps(hba);
|
|
ufs_qcom_advertise_quirks(hba);
|
|
ufs_qcom_set_host_params(hba);
|
|
ufs_qcom_set_phy_gear(host);
|
|
|
|
err = ufs_qcom_ice_init(host);
|
|
if (err)
|
|
goto out_variant_clear;
|
|
|
|
ufs_qcom_setup_clocks(hba, true, POST_CHANGE);
|
|
|
|
ufs_qcom_get_default_testbus_cfg(host);
|
|
err = ufs_qcom_testbus_config(host);
|
|
if (err)
|
|
/* Failure is non-fatal */
|
|
dev_warn(dev, "%s: failed to configure the testbus %d\n",
|
|
__func__, err);
|
|
|
|
if (drvdata && drvdata->no_phy_retention)
|
|
hba->spm_lvl = UFS_PM_LVL_5;
|
|
|
|
return 0;
|
|
|
|
out_variant_clear:
|
|
ufshcd_set_variant(hba, NULL);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void ufs_qcom_exit(struct ufs_hba *hba)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
|
|
ufs_qcom_disable_lane_clks(host);
|
|
phy_power_off(host->generic_phy);
|
|
phy_exit(host->generic_phy);
|
|
}
|
|
|
|
/**
|
|
* ufs_qcom_set_clk_40ns_cycles - Configure 40ns clk cycles
|
|
*
|
|
* @hba: host controller instance
|
|
* @cycles_in_1us: No of cycles in 1us to be configured
|
|
*
|
|
* Returns error if dme get/set configuration for 40ns fails
|
|
* and returns zero on success.
|
|
*/
|
|
static int ufs_qcom_set_clk_40ns_cycles(struct ufs_hba *hba,
|
|
u32 cycles_in_1us)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
u32 cycles_in_40ns;
|
|
u32 reg;
|
|
int err;
|
|
|
|
/*
|
|
* UFS host controller V4.0.0 onwards needs to program
|
|
* PA_VS_CORE_CLK_40NS_CYCLES attribute per programmed
|
|
* frequency of unipro core clk of UFS host controller.
|
|
*/
|
|
if (host->hw_ver.major < 4)
|
|
return 0;
|
|
|
|
/*
|
|
* Generic formulae for cycles_in_40ns = (freq_unipro/25) is not
|
|
* applicable for all frequencies. For ex: ceil(37.5 MHz/25) will
|
|
* be 2 and ceil(403 MHZ/25) will be 17 whereas Hardware
|
|
* specification expect to be 16. Hence use exact hardware spec
|
|
* mandated value for cycles_in_40ns instead of calculating using
|
|
* generic formulae.
|
|
*/
|
|
switch (cycles_in_1us) {
|
|
case UNIPRO_CORE_CLK_FREQ_403_MHZ:
|
|
cycles_in_40ns = 16;
|
|
break;
|
|
case UNIPRO_CORE_CLK_FREQ_300_MHZ:
|
|
cycles_in_40ns = 12;
|
|
break;
|
|
case UNIPRO_CORE_CLK_FREQ_201_5_MHZ:
|
|
cycles_in_40ns = 8;
|
|
break;
|
|
case UNIPRO_CORE_CLK_FREQ_150_MHZ:
|
|
cycles_in_40ns = 6;
|
|
break;
|
|
case UNIPRO_CORE_CLK_FREQ_100_MHZ:
|
|
cycles_in_40ns = 4;
|
|
break;
|
|
case UNIPRO_CORE_CLK_FREQ_75_MHZ:
|
|
cycles_in_40ns = 3;
|
|
break;
|
|
case UNIPRO_CORE_CLK_FREQ_37_5_MHZ:
|
|
cycles_in_40ns = 2;
|
|
break;
|
|
default:
|
|
dev_err(hba->dev, "UNIPRO clk freq %u MHz not supported\n",
|
|
cycles_in_1us);
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_VS_CORE_CLK_40NS_CYCLES), ®);
|
|
if (err)
|
|
return err;
|
|
|
|
reg &= ~PA_VS_CORE_CLK_40NS_CYCLES_MASK;
|
|
reg |= cycles_in_40ns;
|
|
|
|
return ufshcd_dme_set(hba, UIC_ARG_MIB(PA_VS_CORE_CLK_40NS_CYCLES), reg);
|
|
}
|
|
|
|
static int ufs_qcom_set_core_clk_ctrl(struct ufs_hba *hba, bool is_scale_up, unsigned long freq)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
struct list_head *head = &hba->clk_list_head;
|
|
struct ufs_clk_info *clki;
|
|
u32 cycles_in_1us = 0;
|
|
u32 core_clk_ctrl_reg;
|
|
unsigned long clk_freq;
|
|
int err;
|
|
|
|
if (hba->use_pm_opp && freq != ULONG_MAX) {
|
|
clk_freq = ufs_qcom_opp_freq_to_clk_freq(hba, freq, "core_clk_unipro");
|
|
if (clk_freq) {
|
|
cycles_in_1us = ceil(clk_freq, HZ_PER_MHZ);
|
|
goto set_core_clk_ctrl;
|
|
}
|
|
}
|
|
|
|
list_for_each_entry(clki, head, list) {
|
|
if (!IS_ERR_OR_NULL(clki->clk) &&
|
|
!strcmp(clki->name, "core_clk_unipro")) {
|
|
if (!clki->max_freq) {
|
|
cycles_in_1us = 150; /* default for backwards compatibility */
|
|
break;
|
|
}
|
|
|
|
if (freq == ULONG_MAX) {
|
|
cycles_in_1us = ceil(clki->max_freq, HZ_PER_MHZ);
|
|
break;
|
|
}
|
|
|
|
if (is_scale_up)
|
|
cycles_in_1us = ceil(clki->max_freq, HZ_PER_MHZ);
|
|
else
|
|
cycles_in_1us = ceil(clk_get_rate(clki->clk), HZ_PER_MHZ);
|
|
break;
|
|
}
|
|
}
|
|
|
|
set_core_clk_ctrl:
|
|
err = ufshcd_dme_get(hba,
|
|
UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
|
|
&core_clk_ctrl_reg);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Bit mask is different for UFS host controller V4.0.0 onwards */
|
|
if (host->hw_ver.major >= 4) {
|
|
if (!FIELD_FIT(CLK_1US_CYCLES_MASK_V4, cycles_in_1us))
|
|
return -ERANGE;
|
|
core_clk_ctrl_reg &= ~CLK_1US_CYCLES_MASK_V4;
|
|
core_clk_ctrl_reg |= FIELD_PREP(CLK_1US_CYCLES_MASK_V4, cycles_in_1us);
|
|
} else {
|
|
if (!FIELD_FIT(CLK_1US_CYCLES_MASK, cycles_in_1us))
|
|
return -ERANGE;
|
|
core_clk_ctrl_reg &= ~CLK_1US_CYCLES_MASK;
|
|
core_clk_ctrl_reg |= FIELD_PREP(CLK_1US_CYCLES_MASK, cycles_in_1us);
|
|
}
|
|
|
|
/* Clear CORE_CLK_DIV_EN */
|
|
core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT;
|
|
|
|
err = ufshcd_dme_set(hba,
|
|
UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
|
|
core_clk_ctrl_reg);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Configure unipro core clk 40ns attribute */
|
|
return ufs_qcom_set_clk_40ns_cycles(hba, cycles_in_1us);
|
|
}
|
|
|
|
static int ufs_qcom_clk_scale_up_pre_change(struct ufs_hba *hba, unsigned long freq)
|
|
{
|
|
int ret;
|
|
|
|
ret = ufs_qcom_cfg_timers(hba, true, freq);
|
|
if (ret) {
|
|
dev_err(hba->dev, "%s ufs cfg timer failed\n", __func__);
|
|
return ret;
|
|
}
|
|
/* set unipro core clock attributes and clear clock divider */
|
|
return ufs_qcom_set_core_clk_ctrl(hba, true, freq);
|
|
}
|
|
|
|
static int ufs_qcom_clk_scale_up_post_change(struct ufs_hba *hba)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int ufs_qcom_clk_scale_down_pre_change(struct ufs_hba *hba)
|
|
{
|
|
int err;
|
|
u32 core_clk_ctrl_reg;
|
|
|
|
err = ufshcd_dme_get(hba,
|
|
UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
|
|
&core_clk_ctrl_reg);
|
|
|
|
/* make sure CORE_CLK_DIV_EN is cleared */
|
|
if (!err &&
|
|
(core_clk_ctrl_reg & DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT)) {
|
|
core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT;
|
|
err = ufshcd_dme_set(hba,
|
|
UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
|
|
core_clk_ctrl_reg);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ufs_qcom_clk_scale_down_post_change(struct ufs_hba *hba, unsigned long freq)
|
|
{
|
|
int ret;
|
|
|
|
ret = ufs_qcom_cfg_timers(hba, false, freq);
|
|
if (ret) {
|
|
dev_err(hba->dev, "%s: ufs_qcom_cfg_timers() failed\n", __func__);
|
|
return ret;
|
|
}
|
|
/* set unipro core clock attributes and clear clock divider */
|
|
return ufs_qcom_set_core_clk_ctrl(hba, false, freq);
|
|
}
|
|
|
|
static int ufs_qcom_clk_scale_notify(struct ufs_hba *hba, bool scale_up,
|
|
unsigned long target_freq,
|
|
enum ufs_notify_change_status status)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
int err;
|
|
|
|
/* check the host controller state before sending hibern8 cmd */
|
|
if (!ufshcd_is_hba_active(hba))
|
|
return 0;
|
|
|
|
if (status == PRE_CHANGE) {
|
|
err = ufshcd_uic_hibern8_enter(hba);
|
|
if (err)
|
|
return err;
|
|
if (scale_up)
|
|
err = ufs_qcom_clk_scale_up_pre_change(hba, target_freq);
|
|
else
|
|
err = ufs_qcom_clk_scale_down_pre_change(hba);
|
|
|
|
if (err) {
|
|
ufshcd_uic_hibern8_exit(hba);
|
|
return err;
|
|
}
|
|
} else {
|
|
if (scale_up)
|
|
err = ufs_qcom_clk_scale_up_post_change(hba);
|
|
else
|
|
err = ufs_qcom_clk_scale_down_post_change(hba, target_freq);
|
|
|
|
|
|
if (err) {
|
|
ufshcd_uic_hibern8_exit(hba);
|
|
return err;
|
|
}
|
|
|
|
ufs_qcom_icc_update_bw(host);
|
|
ufshcd_uic_hibern8_exit(hba);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ufs_qcom_enable_test_bus(struct ufs_qcom_host *host)
|
|
{
|
|
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);
|
|
}
|
|
|
|
static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host)
|
|
{
|
|
/* provide a legal default configuration */
|
|
host->testbus.select_major = TSTBUS_UNIPRO;
|
|
host->testbus.select_minor = 37;
|
|
}
|
|
|
|
static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host)
|
|
{
|
|
if (host->testbus.select_major >= TSTBUS_MAX) {
|
|
dev_err(host->hba->dev,
|
|
"%s: UFS_CFG1[TEST_BUS_SEL} may not equal 0x%05X\n",
|
|
__func__, host->testbus.select_major);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int ufs_qcom_testbus_config(struct ufs_qcom_host *host)
|
|
{
|
|
int reg;
|
|
int offset;
|
|
u32 mask = TEST_BUS_SUB_SEL_MASK;
|
|
|
|
if (!host)
|
|
return -EINVAL;
|
|
|
|
if (!ufs_qcom_testbus_cfg_is_ok(host))
|
|
return -EPERM;
|
|
|
|
switch (host->testbus.select_major) {
|
|
case TSTBUS_UAWM:
|
|
reg = UFS_TEST_BUS_CTRL_0;
|
|
offset = 24;
|
|
break;
|
|
case TSTBUS_UARM:
|
|
reg = UFS_TEST_BUS_CTRL_0;
|
|
offset = 16;
|
|
break;
|
|
case TSTBUS_TXUC:
|
|
reg = UFS_TEST_BUS_CTRL_0;
|
|
offset = 8;
|
|
break;
|
|
case TSTBUS_RXUC:
|
|
reg = UFS_TEST_BUS_CTRL_0;
|
|
offset = 0;
|
|
break;
|
|
case TSTBUS_DFC:
|
|
reg = UFS_TEST_BUS_CTRL_1;
|
|
offset = 24;
|
|
break;
|
|
case TSTBUS_TRLUT:
|
|
reg = UFS_TEST_BUS_CTRL_1;
|
|
offset = 16;
|
|
break;
|
|
case TSTBUS_TMRLUT:
|
|
reg = UFS_TEST_BUS_CTRL_1;
|
|
offset = 8;
|
|
break;
|
|
case TSTBUS_OCSC:
|
|
reg = UFS_TEST_BUS_CTRL_1;
|
|
offset = 0;
|
|
break;
|
|
case TSTBUS_WRAPPER:
|
|
reg = UFS_TEST_BUS_CTRL_2;
|
|
offset = 16;
|
|
break;
|
|
case TSTBUS_COMBINED:
|
|
reg = UFS_TEST_BUS_CTRL_2;
|
|
offset = 8;
|
|
break;
|
|
case TSTBUS_UTP_HCI:
|
|
reg = UFS_TEST_BUS_CTRL_2;
|
|
offset = 0;
|
|
break;
|
|
case TSTBUS_UNIPRO:
|
|
reg = UFS_UNIPRO_CFG;
|
|
offset = 20;
|
|
mask = 0xFFF;
|
|
break;
|
|
/*
|
|
* No need for a default case, since
|
|
* ufs_qcom_testbus_cfg_is_ok() checks that the configuration
|
|
* is legal
|
|
*/
|
|
}
|
|
mask <<= offset;
|
|
ufshcd_rmwl(host->hba, TEST_BUS_SEL,
|
|
(u32)host->testbus.select_major << 19,
|
|
REG_UFS_CFG1);
|
|
ufshcd_rmwl(host->hba, mask,
|
|
(u32)host->testbus.select_minor << offset,
|
|
reg);
|
|
ufs_qcom_enable_test_bus(host);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ufs_qcom_dump_testbus(struct ufs_hba *hba)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
int i, j, nminor = 0, testbus_len = 0;
|
|
u32 *testbus __free(kfree) = NULL;
|
|
char *prefix;
|
|
|
|
testbus = kmalloc_array(256, sizeof(u32), GFP_KERNEL);
|
|
if (!testbus)
|
|
return;
|
|
|
|
for (j = 0; j < TSTBUS_MAX; j++) {
|
|
nminor = testbus_info[j].nminor;
|
|
prefix = testbus_info[j].prefix;
|
|
host->testbus.select_major = j;
|
|
testbus_len = nminor * sizeof(u32);
|
|
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, prefix, DUMP_PREFIX_OFFSET,
|
|
16, 4, testbus, testbus_len, false);
|
|
}
|
|
}
|
|
|
|
static int ufs_qcom_dump_regs(struct ufs_hba *hba, size_t offset, size_t len,
|
|
const char *prefix, enum ufshcd_res id)
|
|
{
|
|
u32 *regs __free(kfree) = NULL;
|
|
size_t pos;
|
|
|
|
if (offset % 4 != 0 || len % 4 != 0)
|
|
return -EINVAL;
|
|
|
|
regs = kzalloc(len, GFP_ATOMIC);
|
|
if (!regs)
|
|
return -ENOMEM;
|
|
|
|
for (pos = 0; pos < len; pos += 4)
|
|
regs[pos / 4] = readl(hba->res[id].base + offset + pos);
|
|
|
|
print_hex_dump(KERN_ERR, prefix,
|
|
len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE,
|
|
16, 4, regs, len, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ufs_qcom_dump_mcq_hci_regs(struct ufs_hba *hba)
|
|
{
|
|
struct dump_info {
|
|
size_t offset;
|
|
size_t len;
|
|
const char *prefix;
|
|
enum ufshcd_res id;
|
|
};
|
|
|
|
struct dump_info mcq_dumps[] = {
|
|
{0x0, 256 * 4, "MCQ HCI-0 ", RES_MCQ},
|
|
{0x400, 256 * 4, "MCQ HCI-1 ", RES_MCQ},
|
|
{0x0, 5 * 4, "MCQ VS-0 ", RES_MCQ_VS},
|
|
{0x0, 256 * 4, "MCQ SQD-0 ", RES_MCQ_SQD},
|
|
{0x400, 256 * 4, "MCQ SQD-1 ", RES_MCQ_SQD},
|
|
{0x800, 256 * 4, "MCQ SQD-2 ", RES_MCQ_SQD},
|
|
{0xc00, 256 * 4, "MCQ SQD-3 ", RES_MCQ_SQD},
|
|
{0x1000, 256 * 4, "MCQ SQD-4 ", RES_MCQ_SQD},
|
|
{0x1400, 256 * 4, "MCQ SQD-5 ", RES_MCQ_SQD},
|
|
{0x1800, 256 * 4, "MCQ SQD-6 ", RES_MCQ_SQD},
|
|
{0x1c00, 256 * 4, "MCQ SQD-7 ", RES_MCQ_SQD},
|
|
};
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(mcq_dumps); i++) {
|
|
ufs_qcom_dump_regs(hba, mcq_dumps[i].offset, mcq_dumps[i].len,
|
|
mcq_dumps[i].prefix, mcq_dumps[i].id);
|
|
cond_resched();
|
|
}
|
|
}
|
|
|
|
static void ufs_qcom_dump_dbg_regs(struct ufs_hba *hba)
|
|
{
|
|
u32 reg;
|
|
struct ufs_qcom_host *host;
|
|
|
|
host = ufshcd_get_variant(hba);
|
|
|
|
dev_err(hba->dev, "HW_H8_ENTER_CNT=%d\n", ufshcd_readl(hba, REG_UFS_HW_H8_ENTER_CNT));
|
|
dev_err(hba->dev, "HW_H8_EXIT_CNT=%d\n", ufshcd_readl(hba, REG_UFS_HW_H8_EXIT_CNT));
|
|
|
|
dev_err(hba->dev, "SW_H8_ENTER_CNT=%d\n", ufshcd_readl(hba, REG_UFS_SW_H8_ENTER_CNT));
|
|
dev_err(hba->dev, "SW_H8_EXIT_CNT=%d\n", ufshcd_readl(hba, REG_UFS_SW_H8_EXIT_CNT));
|
|
|
|
dev_err(hba->dev, "SW_AFTER_HW_H8_ENTER_CNT=%d\n",
|
|
ufshcd_readl(hba, REG_UFS_SW_AFTER_HW_H8_ENTER_CNT));
|
|
|
|
ufshcd_dump_regs(hba, REG_UFS_SYS1CLK_1US, 16 * 4,
|
|
"HCI Vendor Specific Registers ");
|
|
|
|
reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_REG_OCSC);
|
|
ufshcd_dump_regs(hba, reg, 44 * 4, "UFS_UFS_DBG_RD_REG_OCSC ");
|
|
|
|
reg = ufshcd_readl(hba, REG_UFS_CFG1);
|
|
reg |= UTP_DBG_RAMS_EN;
|
|
ufshcd_writel(hba, reg, REG_UFS_CFG1);
|
|
|
|
reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_EDTL_RAM);
|
|
ufshcd_dump_regs(hba, reg, 32 * 4, "UFS_UFS_DBG_RD_EDTL_RAM ");
|
|
|
|
reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_DESC_RAM);
|
|
ufshcd_dump_regs(hba, reg, 128 * 4, "UFS_UFS_DBG_RD_DESC_RAM ");
|
|
|
|
reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_PRDT_RAM);
|
|
ufshcd_dump_regs(hba, reg, 64 * 4, "UFS_UFS_DBG_RD_PRDT_RAM ");
|
|
|
|
/* clear bit 17 - UTP_DBG_RAMS_EN */
|
|
ufshcd_rmwl(hba, UTP_DBG_RAMS_EN, 0, REG_UFS_CFG1);
|
|
|
|
reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_UAWM);
|
|
ufshcd_dump_regs(hba, reg, 4 * 4, "UFS_DBG_RD_REG_UAWM ");
|
|
|
|
reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_UARM);
|
|
ufshcd_dump_regs(hba, reg, 4 * 4, "UFS_DBG_RD_REG_UARM ");
|
|
|
|
reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_TXUC);
|
|
ufshcd_dump_regs(hba, reg, 48 * 4, "UFS_DBG_RD_REG_TXUC ");
|
|
|
|
reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_RXUC);
|
|
ufshcd_dump_regs(hba, reg, 27 * 4, "UFS_DBG_RD_REG_RXUC ");
|
|
|
|
reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_DFC);
|
|
ufshcd_dump_regs(hba, reg, 19 * 4, "UFS_DBG_RD_REG_DFC ");
|
|
|
|
reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_TRLUT);
|
|
ufshcd_dump_regs(hba, reg, 34 * 4, "UFS_DBG_RD_REG_TRLUT ");
|
|
|
|
reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_TMRLUT);
|
|
ufshcd_dump_regs(hba, reg, 9 * 4, "UFS_DBG_RD_REG_TMRLUT ");
|
|
|
|
if (hba->mcq_enabled) {
|
|
reg = ufs_qcom_get_debug_reg_offset(host, UFS_RD_REG_MCQ);
|
|
ufshcd_dump_regs(hba, reg, 64 * 4, "HCI MCQ Debug Registers ");
|
|
}
|
|
|
|
/* ensure below dumps occur only in task context due to blocking calls. */
|
|
if (in_task()) {
|
|
/* Dump MCQ Host Vendor Specific Registers */
|
|
if (hba->mcq_enabled)
|
|
ufs_qcom_dump_mcq_hci_regs(hba);
|
|
|
|
/* voluntarily yield the CPU as we are dumping too much data */
|
|
ufshcd_dump_regs(hba, UFS_TEST_BUS, 4, "UFS_TEST_BUS ");
|
|
cond_resched();
|
|
ufs_qcom_dump_testbus(hba);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ufs_qcom_device_reset() - toggle the (optional) device reset line
|
|
* @hba: per-adapter instance
|
|
*
|
|
* Toggles the (optional) reset line to reset the attached device.
|
|
*/
|
|
static int ufs_qcom_device_reset(struct ufs_hba *hba)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
|
|
/* reset gpio is optional */
|
|
if (!host->device_reset)
|
|
return -EOPNOTSUPP;
|
|
|
|
/*
|
|
* The UFS device shall detect reset pulses of 1us, sleep for 10us to
|
|
* be on the safe side.
|
|
*/
|
|
ufs_qcom_device_reset_ctrl(hba, true);
|
|
usleep_range(10, 15);
|
|
|
|
ufs_qcom_device_reset_ctrl(hba, false);
|
|
usleep_range(10, 15);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ufs_qcom_config_scaling_param(struct ufs_hba *hba,
|
|
struct devfreq_dev_profile *p,
|
|
struct devfreq_simple_ondemand_data *d)
|
|
{
|
|
p->polling_ms = 60;
|
|
p->timer = DEVFREQ_TIMER_DELAYED;
|
|
d->upthreshold = 70;
|
|
d->downdifferential = 5;
|
|
|
|
hba->clk_scaling.suspend_on_no_request = true;
|
|
}
|
|
|
|
/* Resources */
|
|
static const struct ufshcd_res_info ufs_res_info[RES_MAX] = {
|
|
{.name = "ufs_mem",},
|
|
{.name = "mcq",},
|
|
/* Submission Queue DAO */
|
|
{.name = "mcq_sqd",},
|
|
/* Submission Queue Interrupt Status */
|
|
{.name = "mcq_sqis",},
|
|
/* Completion Queue DAO */
|
|
{.name = "mcq_cqd",},
|
|
/* Completion Queue Interrupt Status */
|
|
{.name = "mcq_cqis",},
|
|
/* MCQ vendor specific */
|
|
{.name = "mcq_vs",},
|
|
};
|
|
|
|
static int ufs_qcom_mcq_config_resource(struct ufs_hba *hba)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(hba->dev);
|
|
struct ufshcd_res_info *res;
|
|
struct resource *res_mem, *res_mcq;
|
|
int i, ret;
|
|
|
|
memcpy(hba->res, ufs_res_info, sizeof(ufs_res_info));
|
|
|
|
for (i = 0; i < RES_MAX; i++) {
|
|
res = &hba->res[i];
|
|
res->resource = platform_get_resource_byname(pdev,
|
|
IORESOURCE_MEM,
|
|
res->name);
|
|
if (!res->resource) {
|
|
dev_info(hba->dev, "Resource %s not provided\n", res->name);
|
|
if (i == RES_UFS)
|
|
return -ENODEV;
|
|
continue;
|
|
} else if (i == RES_UFS) {
|
|
res_mem = res->resource;
|
|
res->base = hba->mmio_base;
|
|
continue;
|
|
}
|
|
|
|
res->base = devm_ioremap_resource(hba->dev, res->resource);
|
|
if (IS_ERR(res->base)) {
|
|
dev_err(hba->dev, "Failed to map res %s, err=%d\n",
|
|
res->name, (int)PTR_ERR(res->base));
|
|
ret = PTR_ERR(res->base);
|
|
res->base = NULL;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* MCQ resource provided in DT */
|
|
res = &hba->res[RES_MCQ];
|
|
/* Bail if MCQ resource is provided */
|
|
if (res->base)
|
|
goto out;
|
|
|
|
/* Explicitly allocate MCQ resource from ufs_mem */
|
|
res_mcq = devm_kzalloc(hba->dev, sizeof(*res_mcq), GFP_KERNEL);
|
|
if (!res_mcq)
|
|
return -ENOMEM;
|
|
|
|
res_mcq->start = res_mem->start +
|
|
MCQ_SQATTR_OFFSET(hba->mcq_capabilities);
|
|
res_mcq->end = res_mcq->start + hba->nr_hw_queues * MCQ_QCFG_SIZE - 1;
|
|
res_mcq->flags = res_mem->flags;
|
|
res_mcq->name = "mcq";
|
|
|
|
ret = insert_resource(&iomem_resource, res_mcq);
|
|
if (ret) {
|
|
dev_err(hba->dev, "Failed to insert MCQ resource, err=%d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
res->base = devm_ioremap_resource(hba->dev, res_mcq);
|
|
if (IS_ERR(res->base)) {
|
|
dev_err(hba->dev, "MCQ registers mapping failed, err=%d\n",
|
|
(int)PTR_ERR(res->base));
|
|
ret = PTR_ERR(res->base);
|
|
goto ioremap_err;
|
|
}
|
|
|
|
out:
|
|
hba->mcq_base = res->base;
|
|
return 0;
|
|
ioremap_err:
|
|
res->base = NULL;
|
|
remove_resource(res_mcq);
|
|
return ret;
|
|
}
|
|
|
|
static int ufs_qcom_op_runtime_config(struct ufs_hba *hba)
|
|
{
|
|
struct ufshcd_res_info *mem_res, *sqdao_res;
|
|
struct ufshcd_mcq_opr_info_t *opr;
|
|
int i;
|
|
|
|
mem_res = &hba->res[RES_UFS];
|
|
sqdao_res = &hba->res[RES_MCQ_SQD];
|
|
|
|
if (!mem_res->base || !sqdao_res->base)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < OPR_MAX; i++) {
|
|
opr = &hba->mcq_opr[i];
|
|
opr->offset = sqdao_res->resource->start -
|
|
mem_res->resource->start + 0x40 * i;
|
|
opr->stride = 0x100;
|
|
opr->base = sqdao_res->base + 0x40 * i;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ufs_qcom_get_hba_mac(struct ufs_hba *hba)
|
|
{
|
|
/* Qualcomm HC supports up to 64 */
|
|
return MAX_SUPP_MAC;
|
|
}
|
|
|
|
static int ufs_qcom_get_outstanding_cqs(struct ufs_hba *hba,
|
|
unsigned long *ocqs)
|
|
{
|
|
struct ufshcd_res_info *mcq_vs_res = &hba->res[RES_MCQ_VS];
|
|
|
|
if (!mcq_vs_res->base)
|
|
return -EINVAL;
|
|
|
|
*ocqs = readl(mcq_vs_res->base + UFS_MEM_CQIS_VS);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ufs_qcom_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
|
|
{
|
|
struct device *dev = msi_desc_to_dev(desc);
|
|
struct ufs_hba *hba = dev_get_drvdata(dev);
|
|
|
|
ufshcd_mcq_config_esi(hba, msg);
|
|
}
|
|
|
|
struct ufs_qcom_irq {
|
|
unsigned int irq;
|
|
unsigned int idx;
|
|
struct ufs_hba *hba;
|
|
};
|
|
|
|
static irqreturn_t ufs_qcom_mcq_esi_handler(int irq, void *data)
|
|
{
|
|
struct ufs_qcom_irq *qi = data;
|
|
struct ufs_hba *hba = qi->hba;
|
|
struct ufs_hw_queue *hwq = &hba->uhq[qi->idx];
|
|
|
|
ufshcd_mcq_write_cqis(hba, 0x1, qi->idx);
|
|
ufshcd_mcq_poll_cqe_lock(hba, hwq);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void ufs_qcom_irq_free(struct ufs_qcom_irq *uqi)
|
|
{
|
|
for (struct ufs_qcom_irq *q = uqi; q->irq; q++)
|
|
devm_free_irq(q->hba->dev, q->irq, q->hba);
|
|
|
|
platform_device_msi_free_irqs_all(uqi->hba->dev);
|
|
devm_kfree(uqi->hba->dev, uqi);
|
|
}
|
|
|
|
DEFINE_FREE(ufs_qcom_irq, struct ufs_qcom_irq *, if (_T) ufs_qcom_irq_free(_T))
|
|
|
|
static int ufs_qcom_config_esi(struct ufs_hba *hba)
|
|
{
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
int nr_irqs, ret;
|
|
|
|
if (host->esi_enabled)
|
|
return 0;
|
|
|
|
/*
|
|
* 1. We only handle CQs as of now.
|
|
* 2. Poll queues do not need ESI.
|
|
*/
|
|
nr_irqs = hba->nr_hw_queues - hba->nr_queues[HCTX_TYPE_POLL];
|
|
|
|
struct ufs_qcom_irq *qi __free(ufs_qcom_irq) =
|
|
devm_kcalloc(hba->dev, nr_irqs, sizeof(*qi), GFP_KERNEL);
|
|
if (!qi)
|
|
return -ENOMEM;
|
|
/* Preset so __free() has a pointer to hba in all error paths */
|
|
qi[0].hba = hba;
|
|
|
|
ret = platform_device_msi_init_and_alloc_irqs(hba->dev, nr_irqs,
|
|
ufs_qcom_write_msi_msg);
|
|
if (ret) {
|
|
dev_err(hba->dev, "Failed to request Platform MSI %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
for (int idx = 0; idx < nr_irqs; idx++) {
|
|
qi[idx].irq = msi_get_virq(hba->dev, idx);
|
|
qi[idx].idx = idx;
|
|
qi[idx].hba = hba;
|
|
|
|
ret = devm_request_irq(hba->dev, qi[idx].irq, ufs_qcom_mcq_esi_handler,
|
|
IRQF_SHARED, "qcom-mcq-esi", qi + idx);
|
|
if (ret) {
|
|
dev_err(hba->dev, "%s: Fail to request IRQ for %d, err = %d\n",
|
|
__func__, qi[idx].irq, ret);
|
|
qi[idx].irq = 0;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
retain_and_null_ptr(qi);
|
|
|
|
if (host->hw_ver.major >= 6) {
|
|
ufshcd_rmwl(hba, ESI_VEC_MASK, FIELD_PREP(ESI_VEC_MASK, MAX_ESI_VEC - 1),
|
|
REG_UFS_CFG3);
|
|
}
|
|
ufshcd_mcq_enable_esi(hba);
|
|
host->esi_enabled = true;
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long ufs_qcom_opp_freq_to_clk_freq(struct ufs_hba *hba,
|
|
unsigned long freq, char *name)
|
|
{
|
|
struct ufs_clk_info *clki;
|
|
struct dev_pm_opp *opp;
|
|
unsigned long clk_freq;
|
|
int idx = 0;
|
|
bool found = false;
|
|
|
|
opp = dev_pm_opp_find_freq_exact_indexed(hba->dev, freq, 0, true);
|
|
if (IS_ERR(opp)) {
|
|
dev_err(hba->dev, "Failed to find OPP for exact frequency %lu\n", freq);
|
|
return 0;
|
|
}
|
|
|
|
list_for_each_entry(clki, &hba->clk_list_head, list) {
|
|
if (!strcmp(clki->name, name)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
idx++;
|
|
}
|
|
|
|
if (!found) {
|
|
dev_err(hba->dev, "Failed to find clock '%s' in clk list\n", name);
|
|
dev_pm_opp_put(opp);
|
|
return 0;
|
|
}
|
|
|
|
clk_freq = dev_pm_opp_get_freq_indexed(opp, idx);
|
|
|
|
dev_pm_opp_put(opp);
|
|
|
|
return clk_freq;
|
|
}
|
|
|
|
static u32 ufs_qcom_freq_to_gear_speed(struct ufs_hba *hba, unsigned long freq)
|
|
{
|
|
u32 gear = UFS_HS_DONT_CHANGE;
|
|
unsigned long unipro_freq;
|
|
|
|
if (!hba->use_pm_opp)
|
|
return gear;
|
|
|
|
unipro_freq = ufs_qcom_opp_freq_to_clk_freq(hba, freq, "core_clk_unipro");
|
|
switch (unipro_freq) {
|
|
case 403000000:
|
|
gear = UFS_HS_G5;
|
|
break;
|
|
case 300000000:
|
|
gear = UFS_HS_G4;
|
|
break;
|
|
case 201500000:
|
|
gear = UFS_HS_G3;
|
|
break;
|
|
case 150000000:
|
|
case 100000000:
|
|
gear = UFS_HS_G2;
|
|
break;
|
|
case 75000000:
|
|
case 37500000:
|
|
gear = UFS_HS_G1;
|
|
break;
|
|
default:
|
|
dev_err(hba->dev, "%s: Unsupported clock freq : %lu\n", __func__, freq);
|
|
return UFS_HS_DONT_CHANGE;
|
|
}
|
|
|
|
return min_t(u32, gear, hba->max_pwr_info.info.gear_rx);
|
|
}
|
|
|
|
/*
|
|
* struct ufs_hba_qcom_vops - UFS QCOM specific variant operations
|
|
*
|
|
* The variant operations configure the necessary controller and PHY
|
|
* handshake during initialization.
|
|
*/
|
|
static const struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
|
|
.name = "qcom",
|
|
.init = ufs_qcom_init,
|
|
.exit = ufs_qcom_exit,
|
|
.get_ufs_hci_version = ufs_qcom_get_ufs_hci_version,
|
|
.clk_scale_notify = ufs_qcom_clk_scale_notify,
|
|
.setup_clocks = ufs_qcom_setup_clocks,
|
|
.hce_enable_notify = ufs_qcom_hce_enable_notify,
|
|
.link_startup_notify = ufs_qcom_link_startup_notify,
|
|
.pwr_change_notify = ufs_qcom_pwr_change_notify,
|
|
.apply_dev_quirks = ufs_qcom_apply_dev_quirks,
|
|
.fixup_dev_quirks = ufs_qcom_fixup_dev_quirks,
|
|
.suspend = ufs_qcom_suspend,
|
|
.resume = ufs_qcom_resume,
|
|
.dbg_register_dump = ufs_qcom_dump_dbg_regs,
|
|
.device_reset = ufs_qcom_device_reset,
|
|
.config_scaling_param = ufs_qcom_config_scaling_param,
|
|
.mcq_config_resource = ufs_qcom_mcq_config_resource,
|
|
.get_hba_mac = ufs_qcom_get_hba_mac,
|
|
.op_runtime_config = ufs_qcom_op_runtime_config,
|
|
.get_outstanding_cqs = ufs_qcom_get_outstanding_cqs,
|
|
.config_esi = ufs_qcom_config_esi,
|
|
.freq_to_gear_speed = ufs_qcom_freq_to_gear_speed,
|
|
};
|
|
|
|
/**
|
|
* ufs_qcom_probe - probe routine of the driver
|
|
* @pdev: pointer to Platform device handle
|
|
*
|
|
* Return: zero for success and non-zero for failure.
|
|
*/
|
|
static int ufs_qcom_probe(struct platform_device *pdev)
|
|
{
|
|
int err;
|
|
struct device *dev = &pdev->dev;
|
|
|
|
/* Perform generic probe */
|
|
err = ufshcd_pltfrm_init(pdev, &ufs_hba_qcom_vops);
|
|
if (err)
|
|
return dev_err_probe(dev, err, "ufshcd_pltfrm_init() failed\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ufs_qcom_remove - set driver_data of the device to NULL
|
|
* @pdev: pointer to platform device handle
|
|
*
|
|
* Always returns 0
|
|
*/
|
|
static void ufs_qcom_remove(struct platform_device *pdev)
|
|
{
|
|
struct ufs_hba *hba = platform_get_drvdata(pdev);
|
|
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
|
|
|
|
ufshcd_pltfrm_remove(pdev);
|
|
if (host->esi_enabled)
|
|
platform_device_msi_free_irqs_all(hba->dev);
|
|
}
|
|
|
|
static const struct ufs_qcom_drvdata ufs_qcom_sm8550_drvdata = {
|
|
.quirks = UFSHCD_QUIRK_BROKEN_LSDBS_CAP,
|
|
.no_phy_retention = true,
|
|
};
|
|
|
|
static const struct of_device_id ufs_qcom_of_match[] __maybe_unused = {
|
|
{ .compatible = "qcom,ufshc" },
|
|
{ .compatible = "qcom,sm8550-ufshc", .data = &ufs_qcom_sm8550_drvdata },
|
|
{ .compatible = "qcom,sm8650-ufshc", .data = &ufs_qcom_sm8550_drvdata },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, ufs_qcom_of_match);
|
|
|
|
#ifdef CONFIG_ACPI
|
|
static const struct acpi_device_id ufs_qcom_acpi_match[] = {
|
|
{ "QCOM24A5" },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, ufs_qcom_acpi_match);
|
|
#endif
|
|
|
|
static const struct dev_pm_ops ufs_qcom_pm_ops = {
|
|
SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
|
|
.prepare = ufshcd_suspend_prepare,
|
|
.complete = ufshcd_resume_complete,
|
|
#ifdef CONFIG_PM_SLEEP
|
|
.suspend = ufshcd_system_suspend,
|
|
.resume = ufshcd_system_resume,
|
|
.freeze = ufshcd_system_freeze,
|
|
.restore = ufshcd_system_restore,
|
|
.thaw = ufshcd_system_thaw,
|
|
#endif
|
|
};
|
|
|
|
static struct platform_driver ufs_qcom_pltform = {
|
|
.probe = ufs_qcom_probe,
|
|
.remove = ufs_qcom_remove,
|
|
.driver = {
|
|
.name = "ufshcd-qcom",
|
|
.pm = &ufs_qcom_pm_ops,
|
|
.of_match_table = of_match_ptr(ufs_qcom_of_match),
|
|
.acpi_match_table = ACPI_PTR(ufs_qcom_acpi_match),
|
|
},
|
|
};
|
|
module_platform_driver(ufs_qcom_pltform);
|
|
|
|
MODULE_DESCRIPTION("Qualcomm UFS host controller driver");
|
|
MODULE_LICENSE("GPL v2");
|