diff options
author | Bean Huo <beanhuo@micron.com> | 2023-12-12 23:08:24 +0100 |
---|---|---|
committer | Martin K. Petersen <martin.petersen@oracle.com> | 2023-12-13 23:16:11 -0500 |
commit | 6bf999e0eb41850d5c857102535d5c53b2ede224 (patch) | |
tree | b5fa910183e009dcd897130c1ebd5ceaf87feda9 /drivers/ufs | |
parent | 9fa268875ca4ff5cad0c1b957388a0aef39920c3 (diff) | |
download | lwn-6bf999e0eb41850d5c857102535d5c53b2ede224.tar.gz lwn-6bf999e0eb41850d5c857102535d5c53b2ede224.zip |
scsi: ufs: core: Add UFS RTC support
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 <beanhuo@micron.com>
Signed-off-by: Mike Bi <mikebi@micron.com>
Signed-off-by: Luca Porzio <lporzio@micron.com>
Link: https://lore.kernel.org/r/20231212220825.85255-3-beanhuo@iokpp.de
Acked-by: Avri Altman <avri.altman@wdc.com>
Reviewed-by: Thomas Weißschuh <linux@weissschuh.net>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Diffstat (limited to 'drivers/ufs')
-rw-r--r-- | drivers/ufs/core/ufshcd.c | 83 |
1 files changed, 81 insertions, 2 deletions
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) { |