summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/ufs/qcom,sa8255p-ufshc.yaml56
-rw-r--r--MAINTAINERS2
-rw-r--r--drivers/ufs/core/ufs-sysfs.c2
-rw-r--r--drivers/ufs/host/ufs-qcom.c156
-rw-r--r--drivers/ufs/host/ufs-qcom.h1
-rw-r--r--include/ufs/ufshcd.h2
6 files changed, 216 insertions, 3 deletions
diff --git a/Documentation/devicetree/bindings/ufs/qcom,sa8255p-ufshc.yaml b/Documentation/devicetree/bindings/ufs/qcom,sa8255p-ufshc.yaml
new file mode 100644
index 000000000000..75fae9f1eba7
--- /dev/null
+++ b/Documentation/devicetree/bindings/ufs/qcom,sa8255p-ufshc.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/ufs/qcom,sa8255p-ufshc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm SA8255P UFS Host Controller
+
+maintainers:
+ - Ram Kumar Dwivedi <ram.dwivedi@oss.qualcomm.com>
+
+properties:
+ compatible:
+ const: qcom,sa8255p-ufshc
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ iommus:
+ maxItems: 1
+
+ dma-coherent: true
+
+ power-domains:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - power-domains
+ - iommus
+ - dma-coherent
+
+allOf:
+ - $ref: ufs-common.yaml
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ ufshc@1d84000 {
+ compatible = "qcom,sa8255p-ufshc";
+ reg = <0x01d84000 0x3000>;
+ interrupts = <GIC_SPI 265 IRQ_TYPE_LEVEL_HIGH>;
+ lanes-per-direction = <2>;
+
+ iommus = <&apps_smmu 0x100 0x0>;
+ power-domains = <&scmi3_pd 0>;
+ dma-coherent;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 5b11839cba9d..70292bd3ed71 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26803,7 +26803,7 @@ M: Manivannan Sadhasivam <mani@kernel.org>
L: linux-arm-msm@vger.kernel.org
L: linux-scsi@vger.kernel.org
S: Maintained
-F: Documentation/devicetree/bindings/ufs/qcom,ufs.yaml
+F: Documentation/devicetree/bindings/ufs/qcom*
F: drivers/ufs/host/ufs-qcom*
UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER RENESAS HOOKS
diff --git a/drivers/ufs/core/ufs-sysfs.c b/drivers/ufs/core/ufs-sysfs.c
index 1017dd3ae5d3..384d958615d7 100644
--- a/drivers/ufs/core/ufs-sysfs.c
+++ b/drivers/ufs/core/ufs-sysfs.c
@@ -141,7 +141,7 @@ static inline ssize_t ufs_sysfs_pm_lvl_store(struct device *dev,
if (kstrtoul(buf, 0, &value))
return -EINVAL;
- if (value >= UFS_PM_LVL_MAX)
+ if (value >= UFS_PM_LVL_MAX || value < hba->pm_lvl_min)
return -EINVAL;
if (ufs_pm_lvl_states[value].dev_state == UFS_DEEPSLEEP_PWR_MODE &&
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index 8ebee0cc5313..375fd24ba458 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -14,6 +14,7 @@
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
#include <linux/reset-controller.h>
#include <linux/time.h>
#include <linux/unaligned.h>
@@ -619,6 +620,27 @@ static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba,
return err;
}
+static int ufs_qcom_fw_managed_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+
+ switch (status) {
+ case PRE_CHANGE:
+ ufs_qcom_select_unipro_mode(host);
+ break;
+ case POST_CHANGE:
+ ufs_qcom_enable_hw_clk_gating(hba);
+ ufs_qcom_ice_enable(host);
+ break;
+ default:
+ dev_err(hba->dev, "Invalid status %d\n", status);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
/**
* ufs_qcom_cfg_timers - Configure ufs qcom cfg timers
*
@@ -789,6 +811,33 @@ static int ufs_qcom_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
return ufs_qcom_ice_resume(host);
}
+static int ufs_qcom_fw_managed_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;
+
+ pm_runtime_put_sync(hba->dev);
+
+ return ufs_qcom_ice_suspend(host);
+}
+
+static int ufs_qcom_fw_managed_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ int err;
+
+ err = pm_runtime_resume_and_get(hba->dev);
+ if (err) {
+ dev_err(hba->dev, "PM runtime resume failed: %d\n", 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 &&
@@ -1421,6 +1470,54 @@ static void ufs_qcom_exit(struct ufs_hba *hba)
phy_exit(host->generic_phy);
}
+static int ufs_qcom_fw_managed_init(struct ufs_hba *hba)
+{
+ struct device *dev = hba->dev;
+ struct ufs_qcom_host *host;
+ int err;
+
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ host->hba = hba;
+ ufshcd_set_variant(hba, host);
+
+ ufs_qcom_get_controller_revision(hba, &host->hw_ver.major,
+ &host->hw_ver.minor, &host->hw_ver.step);
+
+ err = ufs_qcom_ice_init(host);
+ if (err)
+ goto out_variant_clear;
+
+ ufs_qcom_get_default_testbus_cfg(host);
+ err = ufs_qcom_testbus_config(host);
+ if (err)
+ /* Failure is non-fatal */
+ dev_warn(dev, "Failed to configure the testbus %d\n", err);
+
+ hba->caps |= UFSHCD_CAP_WB_EN;
+
+ ufs_qcom_advertise_quirks(hba);
+ host->hba->quirks &= ~UFSHCD_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH;
+
+ hba->spm_lvl = hba->rpm_lvl = hba->pm_lvl_min = UFS_PM_LVL_5;
+
+ ufs_qcom_set_host_params(hba);
+ ufs_qcom_parse_gear_limits(hba);
+
+ return 0;
+
+out_variant_clear:
+ ufshcd_set_variant(hba, NULL);
+ return err;
+}
+
+static void ufs_qcom_fw_managed_exit(struct ufs_hba *hba)
+{
+ pm_runtime_put_sync(hba->dev);
+}
+
/**
* ufs_qcom_set_clk_40ns_cycles - Configure 40ns clk cycles
*
@@ -1950,6 +2047,37 @@ static int ufs_qcom_device_reset(struct ufs_hba *hba)
return 0;
}
+/**
+ * ufs_qcom_fw_managed_device_reset - Reset UFS device under FW-managed design
+ * @hba: pointer to UFS host bus adapter
+ *
+ * In the firmware-managed reset model, the power domain is powered on by genpd
+ * before the UFS controller driver probes. For subsequent resets (such as
+ * suspend/resume or recovery), the UFS driver must explicitly invoke PM runtime
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+static int ufs_qcom_fw_managed_device_reset(struct ufs_hba *hba)
+{
+ static bool is_boot = true;
+ int err;
+
+ /* Skip reset on cold boot; perform it on subsequent calls */
+ if (is_boot) {
+ is_boot = false;
+ return 0;
+ }
+
+ pm_runtime_put_sync(hba->dev);
+ err = pm_runtime_resume_and_get(hba->dev);
+ if (err < 0) {
+ dev_err(hba->dev, "PM runtime resume failed: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
static void ufs_qcom_config_scaling_param(struct ufs_hba *hba,
struct devfreq_dev_profile *p,
struct devfreq_simple_ondemand_data *d)
@@ -2229,6 +2357,20 @@ static const struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
.freq_to_gear_speed = ufs_qcom_freq_to_gear_speed,
};
+static const struct ufs_hba_variant_ops ufs_hba_qcom_sa8255p_vops = {
+ .name = "qcom-sa8255p",
+ .init = ufs_qcom_fw_managed_init,
+ .exit = ufs_qcom_fw_managed_exit,
+ .hce_enable_notify = ufs_qcom_fw_managed_hce_enable_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_fw_managed_suspend,
+ .resume = ufs_qcom_fw_managed_resume,
+ .dbg_register_dump = ufs_qcom_dump_dbg_regs,
+ .device_reset = ufs_qcom_fw_managed_device_reset,
+};
+
/**
* ufs_qcom_probe - probe routine of the driver
* @pdev: pointer to Platform device handle
@@ -2239,9 +2381,16 @@ static int ufs_qcom_probe(struct platform_device *pdev)
{
int err;
struct device *dev = &pdev->dev;
+ const struct ufs_hba_variant_ops *vops;
+ const struct ufs_qcom_drvdata *drvdata = device_get_match_data(dev);
+
+ if (drvdata && drvdata->vops)
+ vops = drvdata->vops;
+ else
+ vops = &ufs_hba_qcom_vops;
/* Perform generic probe */
- err = ufshcd_pltfrm_init(pdev, &ufs_hba_qcom_vops);
+ err = ufshcd_pltfrm_init(pdev, vops);
if (err)
return dev_err_probe(dev, err, "ufshcd_pltfrm_init() failed\n");
@@ -2269,10 +2418,15 @@ static const struct ufs_qcom_drvdata ufs_qcom_sm8550_drvdata = {
.no_phy_retention = true,
};
+static const struct ufs_qcom_drvdata ufs_qcom_sa8255p_drvdata = {
+ .vops = &ufs_hba_qcom_sa8255p_vops
+};
+
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 },
+ { .compatible = "qcom,sa8255p-ufshc", .data = &ufs_qcom_sa8255p_drvdata },
{},
};
MODULE_DEVICE_TABLE(of, ufs_qcom_of_match);
diff --git a/drivers/ufs/host/ufs-qcom.h b/drivers/ufs/host/ufs-qcom.h
index 380d02333d38..1111ab34da01 100644
--- a/drivers/ufs/host/ufs-qcom.h
+++ b/drivers/ufs/host/ufs-qcom.h
@@ -313,6 +313,7 @@ struct ufs_qcom_host {
struct ufs_qcom_drvdata {
enum ufshcd_quirks quirks;
bool no_phy_retention;
+ const struct ufs_hba_variant_ops *vops;
};
static inline u32
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index 19154228780b..a64c19563b03 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -834,6 +834,7 @@ enum ufshcd_mcq_opr {
* @uic_link_state: active state of the link to the UFS device.
* @rpm_lvl: desired UFS power management level during runtime PM.
* @spm_lvl: desired UFS power management level during system PM.
+ * @pm_lvl_min: minimum supported power management level.
* @pm_op_in_progress: whether or not a PM operation is in progress.
* @ahit: value of Auto-Hibernate Idle Timer register.
* @outstanding_tasks: Bits representing outstanding task requests
@@ -972,6 +973,7 @@ struct ufs_hba {
enum ufs_pm_level rpm_lvl;
/* Desired UFS power management level during system PM */
enum ufs_pm_level spm_lvl;
+ enum ufs_pm_level pm_lvl_min;
int pm_op_in_progress;
/* Auto-Hibernate Idle Timer register value */