diff options
| -rw-r--r-- | Documentation/devicetree/bindings/ufs/qcom,sa8255p-ufshc.yaml | 56 | ||||
| -rw-r--r-- | MAINTAINERS | 2 | ||||
| -rw-r--r-- | drivers/ufs/core/ufs-sysfs.c | 2 | ||||
| -rw-r--r-- | drivers/ufs/host/ufs-qcom.c | 156 | ||||
| -rw-r--r-- | drivers/ufs/host/ufs-qcom.h | 1 | ||||
| -rw-r--r-- | include/ufs/ufshcd.h | 2 |
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 */ |
