From 4f6dd2a4bf378bc4d169296739ea7d6972c0d858 Mon Sep 17 00:00:00 2001 From: Can Guo Date: Wed, 1 Nov 2023 18:58:36 -0700 Subject: scsi: ufs: ufs-sysfs: Expose UFS power info Having UFS power info available in sysfs makes it easier to tell the state of the link during runtime considering we have a bunch of power saving features and various combinations for backward compatibility. Reviewed-by: Manivannan Sadhasivam Reviewed-by: Bean Huo Reviewed-by: Bart Van Assche Signed-off-by: Can Guo Link: https://lore.kernel.org/r/1698890324-7374-1-git-send-email-quic_cang@quicinc.com Reviewed-by: Avri Altman Signed-off-by: Martin K. Petersen --- drivers/ufs/core/ufs-sysfs.c | 120 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) (limited to 'drivers/ufs/core') diff --git a/drivers/ufs/core/ufs-sysfs.c b/drivers/ufs/core/ufs-sysfs.c index c95906443d5f..05b10ca90b50 100644 --- a/drivers/ufs/core/ufs-sysfs.c +++ b/drivers/ufs/core/ufs-sysfs.c @@ -7,9 +7,56 @@ #include #include +#include #include "ufs-sysfs.h" #include "ufshcd-priv.h" +static const char *ufs_pa_pwr_mode_to_string(enum ufs_pa_pwr_mode mode) +{ + switch (mode) { + case FAST_MODE: return "FAST_MODE"; + case SLOW_MODE: return "SLOW_MODE"; + case FASTAUTO_MODE: return "FASTAUTO_MODE"; + case SLOWAUTO_MODE: return "SLOWAUTO_MODE"; + default: return "UNKNOWN"; + } +} + +static const char *ufs_hs_gear_rate_to_string(enum ufs_hs_gear_rate rate) +{ + switch (rate) { + case PA_HS_MODE_A: return "HS_RATE_A"; + case PA_HS_MODE_B: return "HS_RATE_B"; + default: return "UNKNOWN"; + } +} + +static const char *ufs_pwm_gear_to_string(enum ufs_pwm_gear_tag gear) +{ + switch (gear) { + case UFS_PWM_G1: return "PWM_GEAR1"; + case UFS_PWM_G2: return "PWM_GEAR2"; + case UFS_PWM_G3: return "PWM_GEAR3"; + case UFS_PWM_G4: return "PWM_GEAR4"; + case UFS_PWM_G5: return "PWM_GEAR5"; + case UFS_PWM_G6: return "PWM_GEAR6"; + case UFS_PWM_G7: return "PWM_GEAR7"; + default: return "UNKNOWN"; + } +} + +static const char *ufs_hs_gear_to_string(enum ufs_hs_gear_tag gear) +{ + switch (gear) { + case UFS_HS_G1: return "HS_GEAR1"; + case UFS_HS_G2: return "HS_GEAR2"; + case UFS_HS_G3: return "HS_GEAR3"; + case UFS_HS_G4: return "HS_GEAR4"; + case UFS_HS_G5: return "HS_GEAR5"; + default: return "UNKNOWN"; + } +} + static const char *ufshcd_uic_link_state_to_string( enum uic_link_state state) { @@ -628,6 +675,78 @@ static const struct attribute_group ufs_sysfs_monitor_group = { .attrs = ufs_sysfs_monitor_attrs, }; +static ssize_t lane_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%u\n", hba->pwr_info.lane_rx); +} + +static ssize_t mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", ufs_pa_pwr_mode_to_string(hba->pwr_info.pwr_rx)); +} + +static ssize_t rate_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", ufs_hs_gear_rate_to_string(hba->pwr_info.hs_rate)); +} + +static ssize_t gear_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", hba->pwr_info.hs_rate ? + ufs_hs_gear_to_string(hba->pwr_info.gear_rx) : + ufs_pwm_gear_to_string(hba->pwr_info.gear_rx)); +} + +static ssize_t dev_pm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", ufshcd_ufs_dev_pwr_mode_to_string(hba->curr_dev_pwr_mode)); +} + +static ssize_t link_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", ufshcd_uic_link_state_to_string(hba->uic_link_state)); +} + +static DEVICE_ATTR_RO(lane); +static DEVICE_ATTR_RO(mode); +static DEVICE_ATTR_RO(rate); +static DEVICE_ATTR_RO(gear); +static DEVICE_ATTR_RO(dev_pm); +static DEVICE_ATTR_RO(link_state); + +static struct attribute *ufs_power_info_attrs[] = { + &dev_attr_lane.attr, + &dev_attr_mode.attr, + &dev_attr_rate.attr, + &dev_attr_gear.attr, + &dev_attr_dev_pm.attr, + &dev_attr_link_state.attr, + NULL +}; + +static const struct attribute_group ufs_sysfs_power_info_group = { + .name = "power_info", + .attrs = ufs_power_info_attrs, +}; + static ssize_t ufs_sysfs_read_desc_param(struct ufs_hba *hba, enum desc_idn desc_id, u8 desc_index, @@ -1233,6 +1352,7 @@ static const struct attribute_group *ufs_sysfs_groups[] = { &ufs_sysfs_default_group, &ufs_sysfs_capabilities_group, &ufs_sysfs_monitor_group, + &ufs_sysfs_power_info_group, &ufs_sysfs_device_descriptor_group, &ufs_sysfs_interconnect_descriptor_group, &ufs_sysfs_geometry_descriptor_group, -- cgit v1.2.3 From 045da3077bc57e587d0ab4cfc8945b76af03d72d Mon Sep 17 00:00:00 2001 From: Akinobu Mita Date: Sat, 18 Nov 2023 21:44:43 +0900 Subject: scsi: ufs: core: Make fault injection dynamically configurable per HBA The UFS driver has two driver-specific fault injection mechanisms (trigger_eh and timeout). Each fault injection configuration can only be specified by a module parameter and cannot be reconfigured without reloading the driver. Also, each configuration is common to all HBAs. This change adds the following subdirectories for each UFS HBA when debugfs is enabled: /sys/kernel/debug/ufshcd//timeout_inject /sys/kernel/debug/ufshcd//trigger_eh_inject Each fault injection attribute can be dynamically set per HBA by a corresponding file in these directories. This is tested with QEMU UFS devices. Signed-off-by: Akinobu Mita Link: https://lore.kernel.org/r/20231118124443.1007116-1-akinobu.mita@gmail.com Reviewed-by: Bart Van Assche Signed-off-by: Martin K. Petersen --- drivers/ufs/core/ufs-fault-injection.c | 19 +++++++++++++++---- drivers/ufs/core/ufs-fault-injection.h | 13 +++++++++---- drivers/ufs/core/ufshcd.c | 5 +++-- include/ufs/ufshcd.h | 5 +++++ 4 files changed, 32 insertions(+), 10 deletions(-) (limited to 'drivers/ufs/core') diff --git a/drivers/ufs/core/ufs-fault-injection.c b/drivers/ufs/core/ufs-fault-injection.c index 5b1184aac585..169540417079 100644 --- a/drivers/ufs/core/ufs-fault-injection.c +++ b/drivers/ufs/core/ufs-fault-injection.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "ufs-fault-injection.h" static int ufs_fault_get(char *buffer, const struct kernel_param *kp); @@ -59,12 +60,22 @@ static int ufs_fault_set(const char *val, const struct kernel_param *kp) return 0; } -bool ufs_trigger_eh(void) +void ufs_fault_inject_hba_init(struct ufs_hba *hba) { - return should_fail(&ufs_trigger_eh_attr, 1); + hba->trigger_eh_attr = ufs_trigger_eh_attr; + hba->timeout_attr = ufs_timeout_attr; +#ifdef CONFIG_FAULT_INJECTION_DEBUG_FS + fault_create_debugfs_attr("trigger_eh_inject", hba->debugfs_root, &hba->trigger_eh_attr); + fault_create_debugfs_attr("timeout_inject", hba->debugfs_root, &hba->timeout_attr); +#endif } -bool ufs_fail_completion(void) +bool ufs_trigger_eh(struct ufs_hba *hba) { - return should_fail(&ufs_timeout_attr, 1); + return should_fail(&hba->trigger_eh_attr, 1); +} + +bool ufs_fail_completion(struct ufs_hba *hba) +{ + return should_fail(&hba->timeout_attr, 1); } diff --git a/drivers/ufs/core/ufs-fault-injection.h b/drivers/ufs/core/ufs-fault-injection.h index 6d0cd8e10c87..996a35769781 100644 --- a/drivers/ufs/core/ufs-fault-injection.h +++ b/drivers/ufs/core/ufs-fault-injection.h @@ -7,15 +7,20 @@ #include #ifdef CONFIG_SCSI_UFS_FAULT_INJECTION -bool ufs_trigger_eh(void); -bool ufs_fail_completion(void); +void ufs_fault_inject_hba_init(struct ufs_hba *hba); +bool ufs_trigger_eh(struct ufs_hba *hba); +bool ufs_fail_completion(struct ufs_hba *hba); #else -static inline bool ufs_trigger_eh(void) +static inline void ufs_fault_inject_hba_init(struct ufs_hba *hba) +{ +} + +static inline bool ufs_trigger_eh(struct ufs_hba *hba) { return false; } -static inline bool ufs_fail_completion(void) +static inline bool ufs_fail_completion(struct ufs_hba *hba) { return false; } diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 8b1031fb0a44..63f0ee117399 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -2992,7 +2992,7 @@ static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd) ufshcd_send_command(hba, tag, hwq); out: - if (ufs_trigger_eh()) { + if (ufs_trigger_eh(hba)) { unsigned long flags; spin_lock_irqsave(hba->host->host_lock, flags); @@ -5649,7 +5649,7 @@ static irqreturn_t ufshcd_transfer_req_compl(struct ufs_hba *hba) !(hba->quirks & UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR)) ufshcd_reset_intr_aggr(hba); - if (ufs_fail_completion()) + if (ufs_fail_completion(hba)) return IRQ_HANDLED; /* @@ -9348,6 +9348,7 @@ static int ufshcd_hba_init(struct ufs_hba *hba) goto out_disable_vreg; ufs_debugfs_hba_init(hba); + ufs_fault_inject_hba_init(hba); hba->is_powered = true; goto out; diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h index 7f0b2c5599cd..d862c8ddce03 100644 --- a/include/ufs/ufshcd.h +++ b/include/ufs/ufshcd.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -1057,6 +1058,10 @@ struct ufs_hba { struct dentry *debugfs_root; struct delayed_work debugfs_ee_work; u32 debugfs_ee_rate_limit_ms; +#endif +#ifdef CONFIG_SCSI_UFS_FAULT_INJECTION + struct fault_attr trigger_eh_attr; + struct fault_attr timeout_attr; #endif u32 luns_avail; unsigned int nr_hw_queues; -- cgit v1.2.3 From db80df77025ee0a5e6e089cc2f2b0c4b97867bea Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 15 Nov 2023 11:33:47 -0800 Subject: scsi: ufs: core: Warn if the request tag is truncated ufshcd_prepare_utp_scsi_cmd_upiu() only uses the lowest eight bits of lrbp->task_tag. Issue a runtime warning if this results in truncation. Signed-off-by: Bart Van Assche Link: https://lore.kernel.org/r/20231115193359.2262044-1-bvanassche@acm.org Signed-off-by: Martin K. Petersen --- drivers/ufs/core/ufshcd.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/ufs/core') diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 63f0ee117399..f0b837cb0c2b 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -2720,6 +2720,8 @@ void ufshcd_prepare_utp_scsi_cmd_upiu(struct ufshcd_lrb *lrbp, u8 upiu_flags) .command_set_type = UPIU_COMMAND_SET_TYPE_SCSI, }; + WARN_ON_ONCE(ucd_req_ptr->header.task_tag != lrbp->task_tag); + ucd_req_ptr->sc.exp_data_transfer_len = cpu_to_be32(cmd->sdb.length); cdb_len = min_t(unsigned short, cmd->cmd_len, UFS_CDB_SIZE); -- cgit v1.2.3 From 0ae7a02726bca95ee9d17de0ccfcd1de0ff4e429 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Fri, 8 Dec 2023 12:28:51 +0530 Subject: scsi: ufs: qcom: Export ufshcd_{enable/disable}_irq helpers and make use of them Instead of duplicating the enable/disable IRQ part, let's export the helpers available in ufshcd driver and make use of them. This also fixes the possible redundant IRQ disable before asserting reset (when IRQ was already disabled). Fixes: 4a791574a0cc ("scsi: ufs: ufs-qcom: Disable interrupt in reset path") Reviewed-by: Bart Van Assche Signed-off-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20231208065902.11006-7-manivannan.sadhasivam@linaro.org Tested-by: Andrew Halaney # sa8775p-ride Signed-off-by: Martin K. Petersen --- drivers/ufs/core/ufshcd.c | 6 ++++-- drivers/ufs/host/ufs-qcom.c | 9 +++------ include/ufs/ufshcd.h | 2 ++ 3 files changed, 9 insertions(+), 8 deletions(-) (limited to 'drivers/ufs/core') diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 8b1031fb0a44..671facc73921 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -289,21 +289,23 @@ static void ufshcd_wb_toggle_buf_flush_during_h8(struct ufs_hba *hba, static void ufshcd_hba_vreg_set_lpm(struct ufs_hba *hba); static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba); -static inline void ufshcd_enable_irq(struct ufs_hba *hba) +void ufshcd_enable_irq(struct ufs_hba *hba) { if (!hba->is_irq_enabled) { enable_irq(hba->irq); hba->is_irq_enabled = true; } } +EXPORT_SYMBOL_GPL(ufshcd_enable_irq); -static inline void ufshcd_disable_irq(struct ufs_hba *hba) +void ufshcd_disable_irq(struct ufs_hba *hba) { if (hba->is_irq_enabled) { disable_irq(hba->irq); hba->is_irq_enabled = false; } } +EXPORT_SYMBOL_GPL(ufshcd_disable_irq); static void ufshcd_configure_wb(struct ufs_hba *hba) { diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c index d474de0739e4..604273a22afd 100644 --- a/drivers/ufs/host/ufs-qcom.c +++ b/drivers/ufs/host/ufs-qcom.c @@ -300,8 +300,7 @@ static int ufs_qcom_host_reset(struct ufs_hba *hba) return 0; reenable_intr = hba->is_irq_enabled; - disable_irq(hba->irq); - hba->is_irq_enabled = false; + ufshcd_disable_irq(hba); ret = reset_control_assert(host->core_reset); if (ret) { @@ -324,10 +323,8 @@ static int ufs_qcom_host_reset(struct ufs_hba *hba) usleep_range(1000, 1100); - if (reenable_intr) { - enable_irq(hba->irq); - hba->is_irq_enabled = true; - } + if (reenable_intr) + ufshcd_enable_irq(hba); return 0; } diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h index 7f0b2c5599cd..f1fc16ac6af2 100644 --- a/include/ufs/ufshcd.h +++ b/include/ufs/ufshcd.h @@ -1231,6 +1231,8 @@ static inline void ufshcd_rmwl(struct ufs_hba *hba, u32 mask, u32 val, u32 reg) ufshcd_writel(hba, tmp, reg); } +void ufshcd_enable_irq(struct ufs_hba *hba); +void ufshcd_disable_irq(struct ufs_hba *hba); int ufshcd_alloc_host(struct device *, struct ufs_hba **); void ufshcd_dealloc_host(struct ufs_hba *); int ufshcd_hba_enable(struct ufs_hba *hba); -- cgit v1.2.3 From 9fa268875ca4ff5cad0c1b957388a0aef39920c3 Mon Sep 17 00:00:00 2001 From: Bean Huo Date: Tue, 12 Dec 2023 23:08:23 +0100 Subject: scsi: ufs: core: Add ufshcd_is_ufs_dev_busy() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add helper inline for retrieving whether UFS device is busy or not. Signed-off-by: Bean Huo Link: https://lore.kernel.org/r/20231212220825.85255-2-beanhuo@iokpp.de Reviewed-by: Avri Altman Reviewed-by: Thomas Weißschuh Signed-off-by: Martin K. Petersen --- drivers/ufs/core/ufshcd.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'drivers/ufs/core') diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 8b1031fb0a44..fbe76a872530 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -235,6 +235,12 @@ ufs_get_desired_pm_lvl_for_dev_link_state(enum ufs_dev_pwr_mode dev_state, return UFS_PM_LVL_0; } +static bool ufshcd_is_ufs_dev_busy(struct ufs_hba *hba) +{ + return (hba->clk_gating.active_reqs || hba->outstanding_reqs || hba->outstanding_tasks || + hba->active_uic_cmd || hba->uic_async_done); +} + static const struct ufs_dev_quirk ufs_fixups[] = { /* UFS cards deviations table */ { .wmanufacturerid = UFS_VENDOR_MICRON, @@ -1917,10 +1923,7 @@ static void ufshcd_gate_work(struct work_struct *work) goto rel_lock; } - if (hba->clk_gating.active_reqs - || hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL - || hba->outstanding_reqs || hba->outstanding_tasks - || hba->active_uic_cmd || hba->uic_async_done) + if (ufshcd_is_ufs_dev_busy(hba) || hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL) goto rel_lock; spin_unlock_irqrestore(hba->host->host_lock, flags); -- cgit v1.2.3 From 6bf999e0eb41850d5c857102535d5c53b2ede224 Mon Sep 17 00:00:00 2001 From: Bean Huo Date: Tue, 12 Dec 2023 23:08:24 +0100 Subject: scsi: ufs: core: Add UFS RTC support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Real Time Clock (RTC) support for UFS device. This enhancement is crucial for the internal maintenance operations of the UFS device. The patch enables the device to handle both absolute and relative time information. Furthermore, it includes periodic task to update the RTC in accordance with the UFS Spec, ensuring the accuracy of RTC information for the device's internal processes. RTC and qTimestamp serve distinct purposes. The RTC provides a coarse level of granularity with, at best, approximate single-second resolution. This makes the RTC well-suited for the device to determine the approximate age of programmed blocks after being updated by the host. On the other hand, qTimestamp offers nanosecond granularity and is specifically designed for synchronizing Device Error Log entries with corresponding host-side logs. Given that the RTC has been a standard feature since UFS Spec 2.0, and qTimestamp was introduced in UFS Spec 4.0, the majority of UFS devices currently on the market rely on RTC. Therefore, it is advisable to continue supporting RTC in the Linux kernel. This ensures compatibility with the prevailing UFS device implementations and facilitates seamless integration with existing hardware. By maintaining support for RTC, we ensure broad compatibility and avoid potential issues arising from deviations in device specifications across different UFS versions. Signed-off-by: Bean Huo Signed-off-by: Mike Bi Signed-off-by: Luca Porzio Link: https://lore.kernel.org/r/20231212220825.85255-3-beanhuo@iokpp.de Acked-by: Avri Altman Reviewed-by: Thomas Weißschuh Signed-off-by: Martin K. Petersen --- drivers/ufs/core/ufshcd.c | 83 +++++++++++++++++++++++++++++++++++++++++++++-- include/ufs/ufs.h | 13 ++++++++ include/ufs/ufshcd.h | 4 +++ 3 files changed, 98 insertions(+), 2 deletions(-) (limited to 'drivers/ufs/core') diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index fbe76a872530..99ad9b769048 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -99,6 +99,9 @@ /* Polling time to wait for fDeviceInit */ #define FDEVICEINIT_COMPL_TIMEOUT 1500 /* millisecs */ +/* Default RTC update every 10 seconds */ +#define UFS_RTC_UPDATE_INTERVAL_MS (10 * MSEC_PER_SEC) + /* UFSHC 4.0 compliant HC support this mode. */ static bool use_mcq_mode = true; @@ -683,6 +686,8 @@ static void ufshcd_device_reset(struct ufs_hba *hba) hba->dev_info.wb_enabled = false; hba->dev_info.wb_buf_flush_enabled = false; } + if (hba->dev_info.rtc_type == UFS_RTC_RELATIVE) + hba->dev_info.rtc_time_baseline = 0; } if (err != -EOPNOTSUPP) ufshcd_update_evt_hist(hba, UFS_EVT_DEV_RESET, err); @@ -8188,6 +8193,72 @@ static void ufs_fixup_device_setup(struct ufs_hba *hba) ufshcd_vops_fixup_dev_quirks(hba); } +static void ufshcd_update_rtc(struct ufs_hba *hba) +{ + struct timespec64 ts64; + int err; + u32 val; + + ktime_get_real_ts64(&ts64); + + if (ts64.tv_sec < hba->dev_info.rtc_time_baseline) { + dev_warn_once(hba->dev, "%s: Current time precedes previous setting!\n", __func__); + return; + } + + /* + * The Absolute RTC mode has a 136-year limit, spanning from 2010 to 2146. If a time beyond + * 2146 is required, it is recommended to choose the relative RTC mode. + */ + val = ts64.tv_sec - hba->dev_info.rtc_time_baseline; + + ufshcd_rpm_get_sync(hba); + err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_WRITE_ATTR, QUERY_ATTR_IDN_SECONDS_PASSED, + 0, 0, &val); + ufshcd_rpm_put_sync(hba); + + if (err) + dev_err(hba->dev, "%s: Failed to update rtc %d\n", __func__, err); + else if (hba->dev_info.rtc_type == UFS_RTC_RELATIVE) + hba->dev_info.rtc_time_baseline = ts64.tv_sec; +} + +static void ufshcd_rtc_work(struct work_struct *work) +{ + struct ufs_hba *hba; + + hba = container_of(to_delayed_work(work), struct ufs_hba, ufs_rtc_update_work); + + /* Update RTC only when there are no requests in progress and UFSHCI is operational */ + if (!ufshcd_is_ufs_dev_busy(hba) && hba->ufshcd_state == UFSHCD_STATE_OPERATIONAL) + ufshcd_update_rtc(hba); + + if (ufshcd_is_ufs_dev_active(hba)) + schedule_delayed_work(&hba->ufs_rtc_update_work, + msecs_to_jiffies(UFS_RTC_UPDATE_INTERVAL_MS)); +} + +static void ufs_init_rtc(struct ufs_hba *hba, u8 *desc_buf) +{ + u16 periodic_rtc_update = get_unaligned_be16(&desc_buf[DEVICE_DESC_PARAM_FRQ_RTC]); + struct ufs_dev_info *dev_info = &hba->dev_info; + + if (periodic_rtc_update & UFS_RTC_TIME_BASELINE) { + dev_info->rtc_type = UFS_RTC_ABSOLUTE; + + /* + * The concept of measuring time in Linux as the number of seconds elapsed since + * 00:00:00 UTC on January 1, 1970, and UFS ABS RTC is elapsed from January 1st + * 2010 00:00, here we need to adjust ABS baseline. + */ + dev_info->rtc_time_baseline = mktime64(2010, 1, 1, 0, 0, 0) - + mktime64(1970, 1, 1, 0, 0, 0); + } else { + dev_info->rtc_type = UFS_RTC_RELATIVE; + dev_info->rtc_time_baseline = 0; + } +} + static int ufs_get_device_desc(struct ufs_hba *hba) { int err; @@ -8240,6 +8311,8 @@ static int ufs_get_device_desc(struct ufs_hba *hba) ufshcd_temp_notif_probe(hba, desc_buf); + ufs_init_rtc(hba, desc_buf); + if (hba->ext_iid_sup) ufshcd_ext_iid_probe(hba, desc_buf); @@ -8793,6 +8866,8 @@ static int ufshcd_device_init(struct ufs_hba *hba, bool init_dev_params) ufshcd_force_reset_auto_bkops(hba); ufshcd_set_timestamp_attr(hba); + schedule_delayed_work(&hba->ufs_rtc_update_work, + msecs_to_jiffies(UFS_RTC_UPDATE_INTERVAL_MS)); /* Gear up to HS gear if supported */ if (hba->max_pwr_info.is_valid) { @@ -9749,6 +9824,8 @@ vops_suspend: ret = ufshcd_vops_suspend(hba, pm_op, POST_CHANGE); if (ret) goto set_link_active; + + cancel_delayed_work_sync(&hba->ufs_rtc_update_work); goto out; set_link_active: @@ -9843,6 +9920,8 @@ static int __ufshcd_wl_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) if (ret) goto set_old_link_state; ufshcd_set_timestamp_attr(hba); + schedule_delayed_work(&hba->ufs_rtc_update_work, + msecs_to_jiffies(UFS_RTC_UPDATE_INTERVAL_MS)); } if (ufshcd_keep_autobkops_enabled_except_suspend(hba)) @@ -10539,8 +10618,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE); - INIT_DELAYED_WORK(&hba->rpm_dev_flush_recheck_work, - ufshcd_rpm_dev_flush_recheck_work); + INIT_DELAYED_WORK(&hba->rpm_dev_flush_recheck_work, ufshcd_rpm_dev_flush_recheck_work); + INIT_DELAYED_WORK(&hba->ufs_rtc_update_work, ufshcd_rtc_work); /* Set the default auto-hiberate idle timer value to 150 ms */ if (ufshcd_is_auto_hibern8_supported(hba) && !hba->ahit) { diff --git a/include/ufs/ufs.h b/include/ufs/ufs.h index e77ab1786856..7e7169d94596 100644 --- a/include/ufs/ufs.h +++ b/include/ufs/ufs.h @@ -14,6 +14,7 @@ #include #include #include +#include /* * Using static_assert() is not allowed in UAPI header files. Hence the check @@ -551,6 +552,14 @@ struct ufs_vreg_info { struct ufs_vreg *vdd_hba; }; +/* UFS device descriptor wPeriodicRTCUpdate bit9 defines RTC time baseline */ +#define UFS_RTC_TIME_BASELINE BIT(9) + +enum ufs_rtc_time { + UFS_RTC_RELATIVE, + UFS_RTC_ABSOLUTE +}; + struct ufs_dev_info { bool f_power_on_wp_en; /* Keeps information if any of the LU is power on write protected */ @@ -578,6 +587,10 @@ struct ufs_dev_info { /* UFS EXT_IID Enable */ bool b_ext_iid_en; + + /* UFS RTC */ + enum ufs_rtc_time rtc_type; + time64_t rtc_time_baseline; }; /* diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h index 7f0b2c5599cd..7bdda92fcb1c 100644 --- a/include/ufs/ufshcd.h +++ b/include/ufs/ufshcd.h @@ -911,6 +911,8 @@ enum ufshcd_mcq_opr { * @mcq_base: Multi circular queue registers base address * @uhq: array of supported hardware queues * @dev_cmd_queue: Queue for issuing device management commands + * @mcq_opr: MCQ operation and runtime registers + * @ufs_rtc_update_work: A work for UFS RTC periodic update */ struct ufs_hba { void __iomem *mmio_base; @@ -1071,6 +1073,8 @@ struct ufs_hba { struct ufs_hw_queue *uhq; struct ufs_hw_queue *dev_cmd_queue; struct ufshcd_mcq_opr_info_t mcq_opr[OPR_MAX]; + + struct delayed_work ufs_rtc_update_work; }; /** -- cgit v1.2.3 From 838f595a567257e3ac0ac33cdb6bb644ca326cc1 Mon Sep 17 00:00:00 2001 From: Bean Huo Date: Tue, 12 Dec 2023 23:08:25 +0100 Subject: scsi: ufs: core: Add sysfs node for UFS RTC update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a sysfs node named 'rtc_update_ms' within the kernel, enabling user to adjust the RTC periodic update frequency to suit the specific requirements of the system and UFS. Also, this patch allows the user to disable/enable periodic update RTC in the UFS idle time. Signed-off-by: Bean Huo Link: https://lore.kernel.org/r/20231212220825.85255-4-beanhuo@iokpp.de Acked-by: Avri Altman Reviewed-by: Thomas Weißschuh Signed-off-by: Martin K. Petersen --- Documentation/ABI/testing/sysfs-driver-ufs | 7 +++++++ drivers/ufs/core/ufs-sysfs.c | 31 ++++++++++++++++++++++++++++++ drivers/ufs/core/ufshcd.c | 11 +++++++++-- include/ufs/ufs.h | 1 + 4 files changed, 48 insertions(+), 2 deletions(-) (limited to 'drivers/ufs/core') diff --git a/Documentation/ABI/testing/sysfs-driver-ufs b/Documentation/ABI/testing/sysfs-driver-ufs index 0c7efaf62de0..ef1e27584fff 100644 --- a/Documentation/ABI/testing/sysfs-driver-ufs +++ b/Documentation/ABI/testing/sysfs-driver-ufs @@ -1474,3 +1474,10 @@ Description: Indicates status of Write Booster. The file is read only. +What: /sys/bus/platform/drivers/ufshcd/*/rtc_update_ms +What: /sys/bus/platform/devices/*.ufs/rtc_update_ms +Date: November 2023 +Contact: Bean Huo +Description: + rtc_update_ms indicates how often the host should synchronize or update the + UFS RTC. If set to 0, this will disable UFS RTC periodic update. diff --git a/drivers/ufs/core/ufs-sysfs.c b/drivers/ufs/core/ufs-sysfs.c index c95906443d5f..4ead1e3bdb12 100644 --- a/drivers/ufs/core/ufs-sysfs.c +++ b/drivers/ufs/core/ufs-sysfs.c @@ -255,6 +255,35 @@ out: return res < 0 ? res : count; } +static ssize_t rtc_update_ms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", hba->dev_info.rtc_update_period); +} + +static ssize_t rtc_update_ms_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + unsigned int ms; + bool resume_period_update = false; + + if (kstrtouint(buf, 0, &ms)) + return -EINVAL; + + if (!hba->dev_info.rtc_update_period && ms > 0) + resume_period_update = true; + /* Minimum and maximum update frequency should be synchronized with all UFS vendors */ + hba->dev_info.rtc_update_period = ms; + + if (resume_period_update) + schedule_delayed_work(&hba->ufs_rtc_update_work, + msecs_to_jiffies(hba->dev_info.rtc_update_period)); + return count; +} + static ssize_t enable_wb_buf_flush_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -339,6 +368,7 @@ static DEVICE_ATTR_RW(auto_hibern8); static DEVICE_ATTR_RW(wb_on); static DEVICE_ATTR_RW(enable_wb_buf_flush); static DEVICE_ATTR_RW(wb_flush_threshold); +static DEVICE_ATTR_RW(rtc_update_ms); static struct attribute *ufs_sysfs_ufshcd_attrs[] = { &dev_attr_rpm_lvl.attr, @@ -351,6 +381,7 @@ static struct attribute *ufs_sysfs_ufshcd_attrs[] = { &dev_attr_wb_on.attr, &dev_attr_enable_wb_buf_flush.attr, &dev_attr_wb_flush_threshold.attr, + &dev_attr_rtc_update_ms.attr, NULL }; diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 99ad9b769048..5c4ef4b072c3 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -8233,9 +8233,9 @@ static void ufshcd_rtc_work(struct work_struct *work) if (!ufshcd_is_ufs_dev_busy(hba) && hba->ufshcd_state == UFSHCD_STATE_OPERATIONAL) ufshcd_update_rtc(hba); - if (ufshcd_is_ufs_dev_active(hba)) + if (ufshcd_is_ufs_dev_active(hba) && hba->dev_info.rtc_update_period) schedule_delayed_work(&hba->ufs_rtc_update_work, - msecs_to_jiffies(UFS_RTC_UPDATE_INTERVAL_MS)); + msecs_to_jiffies(hba->dev_info.rtc_update_period)); } static void ufs_init_rtc(struct ufs_hba *hba, u8 *desc_buf) @@ -8257,6 +8257,13 @@ static void ufs_init_rtc(struct ufs_hba *hba, u8 *desc_buf) dev_info->rtc_type = UFS_RTC_RELATIVE; dev_info->rtc_time_baseline = 0; } + + /* + * We ignore TIME_PERIOD defined in wPeriodicRTCUpdate because Spec does not clearly state + * how to calculate the specific update period for each time unit. And we disable periodic + * RTC update work, let user configure by sysfs node according to specific circumstance. + */ + dev_info->rtc_update_period = 0; } static int ufs_get_device_desc(struct ufs_hba *hba) diff --git a/include/ufs/ufs.h b/include/ufs/ufs.h index 7e7169d94596..b6003749bc83 100644 --- a/include/ufs/ufs.h +++ b/include/ufs/ufs.h @@ -591,6 +591,7 @@ struct ufs_dev_info { /* UFS RTC */ enum ufs_rtc_time rtc_type; time64_t rtc_time_baseline; + u32 rtc_update_period; }; /* -- cgit v1.2.3 From bdf5c0bb4dd9e7a8a4423643e6c73a7114bd58d8 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 14 Dec 2023 11:23:57 -0800 Subject: scsi: ufs: core: Rename ufshcd_auto_hibern8_enable() and make it static Rename ufshcd_auto_hibern8_enable() into ufshcd_configure_auto_hibern8() since this function can enable or disable auto-hibernation. Since ufshcd_auto_hibern8_enable() is only used inside the UFSHCI driver core, declare it static. Additionally, move the definition of this function to just before its first caller. Suggested-by: Bao D. Nguyen Reviewed-by: Bao D. Nguyen Reviewed-by: Can Guo Cc: Avri Altman Signed-off-by: Bart Van Assche Link: https://lore.kernel.org/r/20231214192416.3638077-2-bvanassche@acm.org Reviewed-by: Peter Wang Signed-off-by: Martin K. Petersen --- drivers/ufs/core/ufshcd.c | 24 +++++++++++------------- include/ufs/ufshcd.h | 1 - 2 files changed, 11 insertions(+), 14 deletions(-) (limited to 'drivers/ufs/core') diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 219ba7b0501b..608dba595beb 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -4413,6 +4413,14 @@ int ufshcd_uic_hibern8_exit(struct ufs_hba *hba) } EXPORT_SYMBOL_GPL(ufshcd_uic_hibern8_exit); +static void ufshcd_configure_auto_hibern8(struct ufs_hba *hba) +{ + if (!ufshcd_is_auto_hibern8_supported(hba)) + return; + + ufshcd_writel(hba, hba->ahit, REG_AUTO_HIBERNATE_IDLE_TIMER); +} + void ufshcd_auto_hibern8_update(struct ufs_hba *hba, u32 ahit) { unsigned long flags; @@ -4432,21 +4440,13 @@ void ufshcd_auto_hibern8_update(struct ufs_hba *hba, u32 ahit) !pm_runtime_suspended(&hba->ufs_device_wlun->sdev_gendev)) { ufshcd_rpm_get_sync(hba); ufshcd_hold(hba); - ufshcd_auto_hibern8_enable(hba); + ufshcd_configure_auto_hibern8(hba); ufshcd_release(hba); ufshcd_rpm_put_sync(hba); } } EXPORT_SYMBOL_GPL(ufshcd_auto_hibern8_update); -void ufshcd_auto_hibern8_enable(struct ufs_hba *hba) -{ - if (!ufshcd_is_auto_hibern8_supported(hba)) - return; - - ufshcd_writel(hba, hba->ahit, REG_AUTO_HIBERNATE_IDLE_TIMER); -} - /** * ufshcd_init_pwr_info - setting the POR (power on reset) * values in hba power info @@ -8953,8 +8953,7 @@ static int ufshcd_probe_hba(struct ufs_hba *hba, bool init_dev_params) if (hba->ee_usr_mask) ufshcd_write_ee_control(hba); - /* Enable Auto-Hibernate if configured */ - ufshcd_auto_hibern8_enable(hba); + ufshcd_configure_auto_hibern8(hba); out: spin_lock_irqsave(hba->host->host_lock, flags); @@ -9956,8 +9955,7 @@ static int __ufshcd_wl_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) cancel_delayed_work(&hba->rpm_dev_flush_recheck_work); } - /* Enable Auto-Hibernate if configured */ - ufshcd_auto_hibern8_enable(hba); + ufshcd_configure_auto_hibern8(hba); goto out; diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h index 38637bea9a52..8e2bce9a4f21 100644 --- a/include/ufs/ufshcd.h +++ b/include/ufs/ufshcd.h @@ -1371,7 +1371,6 @@ static inline int ufshcd_disable_host_tx_lcc(struct ufs_hba *hba) return ufshcd_dme_set(hba, UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), 0); } -void ufshcd_auto_hibern8_enable(struct ufs_hba *hba); void ufshcd_auto_hibern8_update(struct ufs_hba *hba, u32 ahit); void ufshcd_fixup_dev_quirks(struct ufs_hba *hba, const struct ufs_dev_quirk *fixups); -- cgit v1.2.3 From 45a2c87f28ad0ee8c286bb6dd5686bc54f5b7160 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 14 Dec 2023 11:23:58 -0800 Subject: scsi: ufs: core: Simplify ufshcd_auto_hibern8_update() Calls to ufshcd_auto_hibern8_update() are already serialized: this function is either called if user space software is not running (preparing to suspend) or from a single sysfs store callback function. Kernfs serializes sysfs .store() callbacks. No functionality is changed. Reviewed-by: Bao D. Nguyen Reviewed-by: Can Guo Cc: Avri Altman Signed-off-by: Bart Van Assche Link: https://lore.kernel.org/r/20231214192416.3638077-3-bvanassche@acm.org Signed-off-by: Martin K. Petersen --- drivers/ufs/core/ufshcd.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) (limited to 'drivers/ufs/core') diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 608dba595beb..d6ae5d17892c 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -4423,21 +4423,13 @@ static void ufshcd_configure_auto_hibern8(struct ufs_hba *hba) void ufshcd_auto_hibern8_update(struct ufs_hba *hba, u32 ahit) { - unsigned long flags; - bool update = false; + const u32 cur_ahit = READ_ONCE(hba->ahit); - if (!ufshcd_is_auto_hibern8_supported(hba)) + if (!ufshcd_is_auto_hibern8_supported(hba) || cur_ahit == ahit) return; - spin_lock_irqsave(hba->host->host_lock, flags); - if (hba->ahit != ahit) { - hba->ahit = ahit; - update = true; - } - spin_unlock_irqrestore(hba->host->host_lock, flags); - - if (update && - !pm_runtime_suspended(&hba->ufs_device_wlun->sdev_gendev)) { + WRITE_ONCE(hba->ahit, ahit); + if (!pm_runtime_suspended(&hba->ufs_device_wlun->sdev_gendev)) { ufshcd_rpm_get_sync(hba); ufshcd_hold(hba); ufshcd_configure_auto_hibern8(hba); -- cgit v1.2.3