summaryrefslogtreecommitdiffstats
path: root/drivers/ufs
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-09-13 12:27:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-09-13 12:27:00 -0700
commit22098763a10d9c1340827fcf6edab66f153b27f0 (patch)
tree3962f95a34df034652ff93686cdd156c7197f2d9 /drivers/ufs
downloadlinux-stable-22098763a10d9c1340827fcf6edab66f153b27f0.tar.gz
linux-stable-22098763a10d9c1340827fcf6edab66f153b27f0.zip
Merge tag 'trace-v7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt: - Don't destroy user event fields when removal fails User event fields are destroyed before the event is removed from visibility. But that can fail leaving the still visible event with no fields. Move the destroying of the fields to after the event is successfully removed from visibility. - Initialize function graph state is fork before calling copy_exec_state() For non-CLONE_VM forks, copy_exec_state() allocates a new task_exec_state. If that allocation fails, ftrace_graph_exit_task() will free the tasks ret_stack pointer. Since that pointer is still using the parent's ret_stack, it mistakenly frees the parent's pointer too. Call ftrace_graph_init() on the task first which will NULL out the new tasks's ret_stack and if the copy fails, it will not free anything. - Remove FGRAPH_MAX_INDEX The macro FGRAPH_MAX_INDEX was added but never used. Remove it. - Save ent_size in function graph printing of nested functions The function graph tracer needs to look at the next event to see if the next event is the return of the current function entry. If it is, it prints a single line: ktime_get(); Otherwise it prints it like a nested function: tick_nohz_irq_exit() { ktime_get(); kcpustat_irq_exit(); } In order to look at the next event, it must save the current event so that it has the information to print from it. It saves the event in the iterator descriptor called "ent". What it doesn't save is the ent_size of the event which is now used to know if the function graph arguments are to be printed. The peek doesn't save the size so the size used happens to be that of the size of the last event that was seen. Save the entry event size in the iterator descriptor so that the correct size is used. - Fix several errors with freeing data in the histogram code The histogram code had a lot of leaked or or incorrect accounting when failures happen. Correct them. - Fix histogram regression of .percent and .graph modifiers Up until 6.3 histogram values could have "percent" or "graph" modifiers that changed how they were printed. But a change that added restricting histograms values from being strings, stack traces and other modifiers inadvertently prevented them from using the percent and graph modifiers, which were legal use cases for values. Put back the percent and graph modifiers. - Fix various typos in the comments - Set the trace_clock before initializing a histogram with clock argument The histogram API allows the user to specific which trace clock to use via a "clock=" string. The histogram is set up first before the clock is checked. If the passed in clock is not valid, it exits without fully fixing up the histogram leaving it on the list and a use-after-free can trigger. Update the clock argument first and if it fails then exit gracefully before the histogram trigger is placed on any lists. - Restore :mod: trailer after parsing in ftrace_set_clr_event The function ftrace_set_clr_event() modifies the parse string and needs to put it back to what was passed in. It searches for ":mod:" via a strsep() but fails to put back the first ':' in the string. Add back the ':' in the passed in string. - Take trace_array reference when opening a tracer options file The options files are dynamically created and some tracers add their own options. When a tracer adds their own list of options, the trace_array holding them has an array to hold the list of options for each tracer. This array increases in size via a krealloc(), and the new entry gets a newly allocated array to hold the options of the new tracer being added. The element in each entry of the tracer's option array holds a pointer back to the trace_array, a pointer to the tracer it is associated to, a pointer to the flags of the option. The issue is that these arrays are freed when the trace_array is freed when its instance it represents is removed from the instances directory. There's a race that an open of one of these options files can happen when the instance is being removed. Add a new helper function to be called by the open function of the options file to iterate all existing trace_arrays under a lock and find the one that has the given option element in one of it's tracer arrays. If found, then update the associated trace_array's reference counter to keep it from being freed. If not found, have the open call return -ENODEV. - Disable interrupts when acquiring the lock in rb_wake_up_waiters() The function rb_wake_up_waiters() assumes it will be called in interrupt context and does not disable irqs when taking cpu_buffer->reader_lock, which can be called in hard interrupt context. The issue is in PREEMPT_RT, this function is called in thread context leaving this lock open to a deadlock. Take the lock with interrupts disabled. - Use rcu_assign_pointer() for tmp_ops filter hash The tmp_ops used in update_ftrace_direct_mod() assigns its filter_hash field directly, but that field is annotated as __rcu and sparse complains. Assign it with rcu_assign_pointer() - Fix use-after-free in enable_trigger_private_data_free() The trace_event_call is accessed through the event_trigger_data's trace_event_file pointer to put the trace_event_call on freeing. The issue is that the trace_event_file data may have been freed already causing a use-after-free. Add a field to the event_trigger_data that points directly to the trace_event_call so that it can decrement its reference directly without needing to go through the trace_event_file. - Fix accounting of buffer data remote headers trace_buffer_desc_size() and trace_remote_alloc_buffer() undercount the number of pages is needed for the asked for size as it doesn't take into account the meta data on each page. Add a helper function to do the calculation properly and use that in these functions. - Catch nr_page_va overflow in ring_buffer_desc sizing The number of pages per remote ring buffer is capped by ring_buffer_desc::nr_page_va (32 bits). A buffer_size large enough to overflow that field would silently allocate a descriptor smaller than what was asked for. - Do not resize the subbuf order if any per_cpu buffer is disabled The mmapping of ring buffers disables resizing the subbuffers, but it is done per-cpu whereas the subbuf size change is done for all the per_cpu buffers under the buffer->mutex. It could change the size of some while the mapping is happening on others. Have the resize of the subbuf order check all the per_cpu buffers under the lock to see if any of them is disabled before starting and causing an inconsistency between buffers that are being mapped. * tag 'trace-v7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (25 commits) ring-buffer: Check resize_disabled before publishing the new subbuf order tracing/remotes: Catch nr_page_va overflow in ring_buffer_desc sizing tracing/remotes: Account for ring buffer page header in size calculation tracing: Don't dereference trace_event_file in deferred trigger free ftrace: Use rcu_assign_pointer() for tmp_ops filter hash ring-buffer: Acquire the lock with irqsave in rb_wake_up_waiters() tracing: Take trace_array reference when opening a tracer options file tracing: Fix ring_buffer_read_page_size() kernel-doc tracing: Restore :mod: trailer after parsing in ftrace_set_clr_event() tracing: Fix memory corruption from a "STACKTRACE" histogram key tracing: Fix memory corruption from the histogram stacktrace modifier tracing: Undo the registration when enabling the histogram trigger fails tracing: Take the reference before publishing the named histogram trigger tracing: Set the trace clock before registering the histogram trigger tracing: Fix typo "preceeded" in comment tracing: Fix typo "availabe" in comment tracing: Let histogram values keep the percent and graph modifiers tracing: Keep the entry count when the histogram stats allocation fails tracing: Free histogram the field rejected for a bad modifier tracing: Free histogram the var ref when its initialization fails ...
Diffstat (limited to 'drivers/ufs')
-rw-r--r--drivers/ufs/Kconfig31
-rw-r--r--drivers/ufs/Makefile5
-rw-r--r--drivers/ufs/core/Kconfig52
-rw-r--r--drivers/ufs/core/Makefile12
-rw-r--r--drivers/ufs/core/ufs-debugfs.c533
-rw-r--r--drivers/ufs/core/ufs-debugfs.h27
-rw-r--r--drivers/ufs/core/ufs-fault-injection.c82
-rw-r--r--drivers/ufs/core/ufs-fault-injection.h31
-rw-r--r--drivers/ufs/core/ufs-hwmon.c212
-rw-r--r--drivers/ufs/core/ufs-mcq.c743
-rw-r--r--drivers/ufs/core/ufs-rpmb.c256
-rw-r--r--drivers/ufs/core/ufs-sysfs.c2164
-rw-r--r--drivers/ufs/core/ufs-sysfs.h18
-rw-r--r--drivers/ufs/core/ufs-txeq.c1627
-rw-r--r--drivers/ufs/core/ufs_bsg.c276
-rw-r--r--drivers/ufs/core/ufs_bsg.h18
-rw-r--r--drivers/ufs/core/ufs_trace.h426
-rw-r--r--drivers/ufs/core/ufs_trace_types.h23
-rw-r--r--drivers/ufs/core/ufshcd-crypto.c237
-rw-r--r--drivers/ufs/core/ufshcd-crypto.h113
-rw-r--r--drivers/ufs/core/ufshcd-priv.h509
-rw-r--r--drivers/ufs/core/ufshcd.c11604
-rw-r--r--drivers/ufs/host/Kconfig170
-rw-r--r--drivers/ufs/host/Makefile18
-rw-r--r--drivers/ufs/host/cdns-pltfrm.c334
-rw-r--r--drivers/ufs/host/tc-dwc-g210-pci.c138
-rw-r--r--drivers/ufs/host/tc-dwc-g210-pltfrm.c102
-rw-r--r--drivers/ufs/host/tc-dwc-g210.c311
-rw-r--r--drivers/ufs/host/tc-dwc-g210.h18
-rw-r--r--drivers/ufs/host/ti-j721e-ufs.c117
-rw-r--r--drivers/ufs/host/ufs-amd-versal2.c561
-rw-r--r--drivers/ufs/host/ufs-exynos.c2350
-rw-r--r--drivers/ufs/host/ufs-exynos.h294
-rw-r--r--drivers/ufs/host/ufs-hisi.c605
-rw-r--r--drivers/ufs/host/ufs-hisi.h104
-rw-r--r--drivers/ufs/host/ufs-mediatek-sip.h94
-rw-r--r--drivers/ufs/host/ufs-mediatek-trace.h59
-rw-r--r--drivers/ufs/host/ufs-mediatek.c2567
-rw-r--r--drivers/ufs/host/ufs-mediatek.h236
-rw-r--r--drivers/ufs/host/ufs-qcom.c3056
-rw-r--r--drivers/ufs/host/ufs-qcom.h389
-rw-r--r--drivers/ufs/host/ufs-renesas.c558
-rw-r--r--drivers/ufs/host/ufs-rockchip.c361
-rw-r--r--drivers/ufs/host/ufs-rockchip.h90
-rw-r--r--drivers/ufs/host/ufs-sprd.c450
-rw-r--r--drivers/ufs/host/ufs-sprd.h85
-rw-r--r--drivers/ufs/host/ufshcd-dwc.c146
-rw-r--r--drivers/ufs/host/ufshcd-dwc.h71
-rw-r--r--drivers/ufs/host/ufshcd-pci.c777
-rw-r--r--drivers/ufs/host/ufshcd-pltfrm.c723
-rw-r--r--drivers/ufs/host/ufshcd-pltfrm.h39
-rw-r--r--drivers/ufs/host/ufshci-dwc.h33
52 files changed, 33855 insertions, 0 deletions
diff --git a/drivers/ufs/Kconfig b/drivers/ufs/Kconfig
new file mode 100644
index 000000000..f662e7ce7
--- /dev/null
+++ b/drivers/ufs/Kconfig
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# UFS subsystem configuration
+#
+
+menuconfig SCSI_UFSHCD
+ tristate "Universal Flash Storage Controller"
+ depends on SCSI && SCSI_DMA
+ depends on RPMB || !RPMB
+ select PM_DEVFREQ
+ select DEVFREQ_GOV_SIMPLE_ONDEMAND
+ select NLS
+ help
+ Enables support for UFS (Universal Flash Storage) host controllers.
+ A UFS host controller is an electronic component that is able to
+ communicate with a UFS card. UFS host controllers occur in
+ smartphones, laptops, digital cameras and also in cars.
+ The kernel module will be called ufshcd.
+
+ To compile this driver as a module, choose M here and read
+ <file:Documentation/scsi/ufs.rst>.
+ However, do not compile this as a module if your root file system
+ (the one containing the directory /) is located on a UFS device.
+
+if SCSI_UFSHCD
+
+source "drivers/ufs/core/Kconfig"
+
+source "drivers/ufs/host/Kconfig"
+
+endif
diff --git a/drivers/ufs/Makefile b/drivers/ufs/Makefile
new file mode 100644
index 000000000..5a199ef18
--- /dev/null
+++ b/drivers/ufs/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# The link order is important here. ufshcd-core must initialize
+# before vendor drivers.
+obj-$(CONFIG_SCSI_UFSHCD) += core/ host/
diff --git a/drivers/ufs/core/Kconfig b/drivers/ufs/core/Kconfig
new file mode 100644
index 000000000..817208ee6
--- /dev/null
+++ b/drivers/ufs/core/Kconfig
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Kernel configuration file for the UFS Host Controller core.
+#
+# Copyright (C) 2011-2013 Samsung India Software Operations
+#
+# Authors:
+# Santosh Yaraganavi <santosh.sy@samsung.com>
+# Vinayak Holikatti <h.vinayak@samsung.com>
+
+config SCSI_UFS_BSG
+ bool "Universal Flash Storage BSG device node"
+ select BLK_DEV_BSGLIB
+ help
+ Universal Flash Storage (UFS) is SCSI transport specification for
+ accessing flash storage on digital cameras, mobile phones and
+ consumer electronic devices.
+ A UFS controller communicates with a UFS device by exchanging
+ UFS Protocol Information Units (UPIUs).
+ UPIUs can not only be used as a transport layer for the SCSI protocol
+ but are also used by the UFS native command set.
+ This transport driver supports exchanging UFS protocol information units
+ with a UFS device. See also the ufshcd driver, which is a SCSI driver
+ that supports UFS devices.
+
+ Select this if you need a bsg device node for your UFS controller.
+ If unsure, say N.
+
+config SCSI_UFS_CRYPTO
+ bool "UFS Crypto Engine Support"
+ depends on BLK_INLINE_ENCRYPTION
+ help
+ Enable Crypto Engine Support in UFS.
+ Enabling this makes it possible for the kernel to use the crypto
+ capabilities of the UFS device (if present) to perform crypto
+ operations on data being transferred to/from the device.
+
+config SCSI_UFS_FAULT_INJECTION
+ bool "UFS Fault Injection Support"
+ depends on FAULT_INJECTION
+ help
+ Enable fault injection support in the UFS driver. This makes it easier
+ to test the UFS error handler and abort handler.
+
+config SCSI_UFS_HWMON
+ bool "UFS Temperature Notification"
+ depends on SCSI_UFSHCD=HWMON || HWMON=y
+ help
+ This provides support for UFS hardware monitoring. If enabled,
+ a hardware monitoring device will be created for the UFS device.
+
+ If unsure, say N.
diff --git a/drivers/ufs/core/Makefile b/drivers/ufs/core/Makefile
new file mode 100644
index 000000000..67ab9ffbd
--- /dev/null
+++ b/drivers/ufs/core/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+
+CONTEXT_ANALYSIS := y
+
+obj-$(CONFIG_SCSI_UFSHCD) += ufshcd-core.o
+ufshcd-core-y += ufshcd.o ufs-sysfs.o ufs-mcq.o ufs-txeq.o
+ufshcd-core-$(CONFIG_RPMB) += ufs-rpmb.o
+ufshcd-core-$(CONFIG_DEBUG_FS) += ufs-debugfs.o
+ufshcd-core-$(CONFIG_SCSI_UFS_BSG) += ufs_bsg.o
+ufshcd-core-$(CONFIG_SCSI_UFS_CRYPTO) += ufshcd-crypto.o
+ufshcd-core-$(CONFIG_SCSI_UFS_FAULT_INJECTION) += ufs-fault-injection.o
+ufshcd-core-$(CONFIG_SCSI_UFS_HWMON) += ufs-hwmon.o
diff --git a/drivers/ufs/core/ufs-debugfs.c b/drivers/ufs/core/ufs-debugfs.c
new file mode 100644
index 000000000..ed1de70e2
--- /dev/null
+++ b/drivers/ufs/core/ufs-debugfs.c
@@ -0,0 +1,533 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2020 Intel Corporation
+
+#include <linux/debugfs.h>
+
+#include "ufs-debugfs.h"
+#include <ufs/ufshcd.h>
+#include "ufshcd-priv.h"
+
+static struct dentry *ufs_debugfs_root;
+
+struct ufs_debugfs_attr {
+ const char *name;
+ mode_t mode;
+ const struct file_operations *fops;
+};
+
+/* @file corresponds to a debugfs attribute in directory hba->debugfs_root. */
+static inline struct ufs_hba *hba_from_file(const struct file *file)
+{
+ return d_inode(file->f_path.dentry->d_parent)->i_private;
+}
+
+void __init ufs_debugfs_init(void)
+{
+ ufs_debugfs_root = debugfs_create_dir("ufshcd", NULL);
+}
+
+void ufs_debugfs_exit(void)
+{
+ debugfs_remove_recursive(ufs_debugfs_root);
+}
+
+static int ufs_debugfs_stats_show(struct seq_file *s, void *data)
+{
+ struct ufs_hba *hba = hba_from_file(s->file);
+ struct ufs_event_hist *e = hba->ufs_stats.event;
+
+#define PRT(fmt, typ) \
+ seq_printf(s, fmt, e[UFS_EVT_ ## typ].cnt)
+
+ PRT("PHY Adapter Layer errors (except LINERESET): %llu\n", PA_ERR);
+ PRT("Data Link Layer errors: %llu\n", DL_ERR);
+ PRT("Network Layer errors: %llu\n", NL_ERR);
+ PRT("Transport Layer errors: %llu\n", TL_ERR);
+ PRT("Generic DME errors: %llu\n", DME_ERR);
+ PRT("Auto-hibernate errors: %llu\n", AUTO_HIBERN8_ERR);
+ PRT("IS Fatal errors (CEFES, SBFES, HCFES, DFES): %llu\n", FATAL_ERR);
+ PRT("DME Link Startup errors: %llu\n", LINK_STARTUP_FAIL);
+ PRT("PM Resume errors: %llu\n", RESUME_ERR);
+ PRT("PM Suspend errors : %llu\n", SUSPEND_ERR);
+ PRT("Logical Unit Resets: %llu\n", DEV_RESET);
+ PRT("Host Resets: %llu\n", HOST_RESET);
+ PRT("SCSI command aborts: %llu\n", ABORT);
+#undef PRT
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ufs_debugfs_stats);
+
+static int ee_usr_mask_get(void *data, u64 *val)
+{
+ struct ufs_hba *hba = data;
+
+ *val = hba->ee_usr_mask;
+ return 0;
+}
+
+token_context_lock(ufs_debugfs);
+
+static int ufs_debugfs_get_user_access(struct ufs_hba *hba)
+ __cond_acquires(0, ufs_debugfs)
+{
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ up(&hba->host_sem);
+ return -EBUSY;
+ }
+ ufshcd_rpm_get_sync(hba);
+ __acquire(ufs_debugfs);
+ return 0;
+}
+
+static void ufs_debugfs_put_user_access(struct ufs_hba *hba)
+ __releases(ufs_debugfs)
+{
+ ufshcd_rpm_put_sync(hba);
+ up(&hba->host_sem);
+ __release(ufs_debugfs);
+}
+
+static int ee_usr_mask_set(void *data, u64 val)
+{
+ struct ufs_hba *hba = data;
+ int err;
+
+ if (val & ~(u64)MASK_EE_STATUS)
+ return -EINVAL;
+ err = ufs_debugfs_get_user_access(hba);
+ if (err)
+ return err;
+ err = ufshcd_update_ee_usr_mask(hba, val, MASK_EE_STATUS);
+ ufs_debugfs_put_user_access(hba);
+ return err;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(ee_usr_mask_fops, ee_usr_mask_get, ee_usr_mask_set, "%#llx\n");
+
+void ufs_debugfs_exception_event(struct ufs_hba *hba, u16 status)
+{
+ bool chgd = false;
+ u16 ee_ctrl_mask;
+ int err = 0;
+
+ if (!hba->debugfs_ee_rate_limit_ms || !status)
+ return;
+
+ mutex_lock(&hba->ee_ctrl_mutex);
+ ee_ctrl_mask = hba->ee_drv_mask | (hba->ee_usr_mask & ~status);
+ chgd = ee_ctrl_mask != hba->ee_ctrl_mask;
+ if (chgd) {
+ err = __ufshcd_write_ee_control(hba, ee_ctrl_mask);
+ if (err)
+ dev_err(hba->dev, "%s: failed to write ee control %d\n",
+ __func__, err);
+ }
+ mutex_unlock(&hba->ee_ctrl_mutex);
+
+ if (chgd && !err) {
+ unsigned long delay = msecs_to_jiffies(hba->debugfs_ee_rate_limit_ms);
+
+ queue_delayed_work(system_freezable_wq, &hba->debugfs_ee_work, delay);
+ }
+}
+
+static void ufs_debugfs_restart_ee(struct work_struct *work)
+{
+ struct ufs_hba *hba = container_of(work, struct ufs_hba, debugfs_ee_work.work);
+
+ if (!hba->ee_usr_mask || pm_runtime_suspended(hba->dev) ||
+ ufs_debugfs_get_user_access(hba))
+ return;
+ ufshcd_write_ee_control(hba);
+ ufs_debugfs_put_user_access(hba);
+}
+
+static int ufs_saved_err_show(struct seq_file *s, void *data)
+{
+ struct ufs_debugfs_attr *attr = s->private;
+ struct ufs_hba *hba = hba_from_file(s->file);
+ const int *p;
+
+ if (strcmp(attr->name, "saved_err") == 0) {
+ p = &hba->saved_err;
+ } else if (strcmp(attr->name, "saved_uic_err") == 0) {
+ p = &hba->saved_uic_err;
+ } else {
+ return -ENOENT;
+ }
+
+ seq_printf(s, "%d\n", *p);
+ return 0;
+}
+
+static ssize_t ufs_saved_err_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct ufs_debugfs_attr *attr = file->f_inode->i_private;
+ struct ufs_hba *hba = hba_from_file(file);
+ char val_str[16] = { };
+ int val, ret;
+
+ if (count >= sizeof(val_str))
+ return -EINVAL;
+ if (copy_from_user(val_str, buf, count))
+ return -EFAULT;
+ ret = kstrtoint(val_str, 0, &val);
+ if (ret < 0)
+ return ret;
+
+ spin_lock_irq(hba->host->host_lock);
+ if (strcmp(attr->name, "saved_err") == 0) {
+ hba->saved_err = val;
+ } else if (strcmp(attr->name, "saved_uic_err") == 0) {
+ hba->saved_uic_err = val;
+ } else {
+ ret = -ENOENT;
+ }
+ if (ret == 0)
+ ufshcd_schedule_eh_work(hba);
+ spin_unlock_irq(hba->host->host_lock);
+
+ return ret < 0 ? ret : count;
+}
+
+static int ufs_saved_err_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ufs_saved_err_show, inode->i_private);
+}
+
+static const struct file_operations ufs_saved_err_fops = {
+ .owner = THIS_MODULE,
+ .open = ufs_saved_err_open,
+ .read = seq_read,
+ .write = ufs_saved_err_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct ufs_debugfs_attr ufs_attrs[] = {
+ { "stats", 0400, &ufs_debugfs_stats_fops },
+ { "saved_err", 0600, &ufs_saved_err_fops },
+ { "saved_uic_err", 0600, &ufs_saved_err_fops },
+ { }
+};
+
+static int ufs_tx_eq_params_show(struct seq_file *s, void *data)
+{
+ const char *file_name = s->file->f_path.dentry->d_name.name;
+ u32 gear = (u32)(uintptr_t)s->file->f_inode->i_private;
+ struct ufs_hba *hba = hba_from_file(s->file);
+ struct ufshcd_tx_eq_settings *settings;
+ struct ufs_pa_layer_attr *pwr_info;
+ struct ufshcd_tx_eq_params *params;
+ u32 rate = hba->pwr_info.hs_rate;
+ u32 num_lanes;
+ int lane;
+
+ if (!ufshcd_is_tx_eq_supported(hba))
+ return -EOPNOTSUPP;
+
+ if (gear < UFS_HS_G1 || gear > UFS_HS_GEAR_MAX) {
+ seq_printf(s, "Invalid gear selected: %u\n", gear);
+ return 0;
+ }
+
+ if (!hba->max_pwr_info.is_valid) {
+ seq_puts(s, "Max power info is invalid\n");
+ return 0;
+ }
+
+ pwr_info = &hba->max_pwr_info.info;
+ params = &hba->tx_eq_params[gear - 1];
+ if (!params->is_valid) {
+ seq_printf(s, "TX EQ params are invalid for HS-G%u, Rate-%s\n",
+ gear, ufs_hs_rate_to_str(rate));
+ return 0;
+ }
+
+ if (strcmp(file_name, "host_tx_eq_params") == 0) {
+ settings = params->host;
+ num_lanes = pwr_info->lane_tx;
+ seq_printf(s, "Host TX EQ PreShoot Cap: 0x%02x, DeEmphasis Cap: 0x%02x\n",
+ hba->host_preshoot_cap, hba->host_deemphasis_cap);
+ } else if (strcmp(file_name, "device_tx_eq_params") == 0) {
+ settings = params->device;
+ num_lanes = pwr_info->lane_rx;
+ seq_printf(s, "Device TX EQ PreShoot Cap: 0x%02x, DeEmphasis Cap: 0x%02x\n",
+ hba->device_preshoot_cap, hba->device_deemphasis_cap);
+ } else {
+ return -ENOENT;
+ }
+
+ seq_printf(s, "TX EQ setting for HS-G%u, Rate-%s:\n", gear,
+ ufs_hs_rate_to_str(rate));
+ for (lane = 0; lane < num_lanes; lane++)
+ seq_printf(s, "TX Lane %d - PreShoot: %d, DeEmphasis: %d, Pre-Coding %senabled\n",
+ lane, settings[lane].preshoot,
+ settings[lane].deemphasis,
+ settings[lane].precode_en ? "" : "not ");
+
+ return 0;
+}
+
+static int ufs_tx_eq_params_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ufs_tx_eq_params_show, inode->i_private);
+}
+
+static const struct file_operations ufs_tx_eq_params_fops = {
+ .owner = THIS_MODULE,
+ .open = ufs_tx_eq_params_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct ufs_debugfs_attr ufs_tx_eq_attrs[] = {
+ { "host_tx_eq_params", 0400, &ufs_tx_eq_params_fops },
+ { "device_tx_eq_params", 0400, &ufs_tx_eq_params_fops },
+ { }
+};
+
+static int ufs_tx_eqtr_record_show(struct seq_file *s, void *data)
+{
+ const char *file_name = s->file->f_path.dentry->d_name.name;
+ u8 (*fom_array)[TX_HS_NUM_PRESHOOT][TX_HS_NUM_DEEMPHASIS];
+ u32 gear = (u32)(uintptr_t)s->file->f_inode->i_private;
+ unsigned long preshoot_bitmap, deemphasis_bitmap;
+ struct ufs_hba *hba = hba_from_file(s->file);
+ struct ufs_pa_layer_attr *pwr_info;
+ struct ufshcd_tx_eq_params *params;
+ struct ufshcd_tx_eqtr_record *rec;
+ u32 rate = hba->pwr_info.hs_rate;
+ u8 preshoot, deemphasis;
+ u32 num_lanes;
+ char name[32];
+ int lane;
+
+ if (!ufshcd_is_tx_eq_supported(hba))
+ return -EOPNOTSUPP;
+
+ if (gear < UFS_HS_G1 || gear > UFS_HS_GEAR_MAX) {
+ seq_printf(s, "Invalid gear selected: %u\n", gear);
+ return 0;
+ }
+
+ if (!hba->max_pwr_info.is_valid) {
+ seq_puts(s, "Max power info is invalid\n");
+ return 0;
+ }
+
+ pwr_info = &hba->max_pwr_info.info;
+ params = &hba->tx_eq_params[gear - 1];
+ if (!params->is_valid) {
+ seq_printf(s, "TX EQ params are invalid for HS-G%u, Rate-%s\n",
+ gear, ufs_hs_rate_to_str(rate));
+ return 0;
+ }
+
+ rec = params->eqtr_record;
+ if (!rec || !rec->last_record_index) {
+ seq_printf(s, "No TX EQTR records found for HS-G%u, Rate-%s.\n",
+ gear, ufs_hs_rate_to_str(rate));
+ return 0;
+ }
+
+ if (strcmp(file_name, "host_tx_eqtr_record") == 0) {
+ preshoot_bitmap = (hba->host_preshoot_cap << 0x1) | 0x1;
+ deemphasis_bitmap = (hba->host_deemphasis_cap << 0x1) | 0x1;
+ num_lanes = pwr_info->lane_tx;
+ fom_array = rec->host_fom;
+ snprintf(name, sizeof(name), "%s", "Host");
+ } else if (strcmp(file_name, "device_tx_eqtr_record") == 0) {
+ preshoot_bitmap = (hba->device_preshoot_cap << 0x1) | 0x1;
+ deemphasis_bitmap = (hba->device_deemphasis_cap << 0x1) | 0x1;
+ num_lanes = pwr_info->lane_rx;
+ fom_array = rec->device_fom;
+ snprintf(name, sizeof(name), "%s", "Device");
+ } else {
+ return -ENOENT;
+ }
+
+ seq_printf(s, "%s TX EQTR record summary -\n", name);
+ seq_printf(s, "Target Power Mode: HS-G%u, Rate-%s\n", gear,
+ ufs_hs_rate_to_str(rate));
+ seq_printf(s, "Most recent record index: %d\n",
+ rec->last_record_index);
+ seq_printf(s, "Most recent record timestamp: %llu us\n",
+ ktime_to_us(rec->last_record_ts));
+
+ for (lane = 0; lane < num_lanes; lane++) {
+ seq_printf(s, "\nTX Lane %d FOM - %s\n", lane, "PreShoot\\DeEmphasis");
+ seq_puts(s, "\\");
+ /* Print DeEmphasis header as X-axis. */
+ for (deemphasis = 0; deemphasis < TX_HS_NUM_DEEMPHASIS; deemphasis++)
+ seq_printf(s, "%8d%s", deemphasis, " ");
+ seq_puts(s, "\n");
+ /* Print matrix rows with PreShoot as Y-axis. */
+ for (preshoot = 0; preshoot < TX_HS_NUM_PRESHOOT; preshoot++) {
+ seq_printf(s, "%d", preshoot);
+ for (deemphasis = 0; deemphasis < TX_HS_NUM_DEEMPHASIS; deemphasis++) {
+ if (test_bit(preshoot, &preshoot_bitmap) &&
+ test_bit(deemphasis, &deemphasis_bitmap)) {
+ u8 fom = fom_array[lane][preshoot][deemphasis];
+ u8 fom_val = fom & RX_FOM_VALUE_MASK;
+ bool precode_en = fom & RX_FOM_PRECODING_EN_BIT;
+
+ if (ufshcd_is_txeq_presets_used(hba) &&
+ !ufshcd_is_txeq_preset_selected(preshoot, deemphasis))
+ seq_printf(s, "%8s%s", "-", " ");
+ else
+ seq_printf(s, "%8u%s", fom_val,
+ precode_en ? "*" : " ");
+ } else {
+ seq_printf(s, "%8s%s", "x", " ");
+ }
+ }
+ seq_puts(s, "\n");
+ }
+ }
+
+ return 0;
+}
+
+static int ufs_tx_eqtr_record_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ufs_tx_eqtr_record_show, inode->i_private);
+}
+
+static const struct file_operations ufs_tx_eqtr_record_fops = {
+ .owner = THIS_MODULE,
+ .open = ufs_tx_eqtr_record_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static ssize_t ufs_tx_eq_ctrl_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ u32 gear = (u32)(uintptr_t)file->f_inode->i_private;
+ struct ufs_hba *hba = hba_from_file(file);
+ char kbuf[32];
+ int ret;
+
+ if (count >= sizeof(kbuf))
+ return -EINVAL;
+
+ if (copy_from_user(kbuf, buf, count))
+ return -EFAULT;
+
+ if (!ufshcd_is_tx_eq_supported(hba))
+ return -EOPNOTSUPP;
+
+ if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL ||
+ !hba->max_pwr_info.is_valid)
+ return -EBUSY;
+
+ if (!hba->ufs_device_wlun)
+ return -ENODEV;
+
+ kbuf[count] = '\0';
+
+ if (sysfs_streq(kbuf, "retrain")) {
+ ret = ufs_debugfs_get_user_access(hba);
+ if (ret)
+ return ret;
+ ret = ufshcd_retrain_tx_eq(hba, gear);
+ ufs_debugfs_put_user_access(hba);
+ } else {
+ /* Unknown operation */
+ return -EINVAL;
+ }
+
+ return ret ? ret : count;
+}
+
+static int ufs_tx_eq_ctrl_show(struct seq_file *s, void *data)
+{
+ seq_puts(s, "write 'retrain' to retrain TX Equalization settings\n");
+ return 0;
+}
+
+static int ufs_tx_eq_ctrl_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ufs_tx_eq_ctrl_show, inode->i_private);
+}
+
+static const struct file_operations ufs_tx_eq_ctrl_fops = {
+ .owner = THIS_MODULE,
+ .open = ufs_tx_eq_ctrl_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .write = ufs_tx_eq_ctrl_write,
+ .release = single_release,
+};
+
+static const struct ufs_debugfs_attr ufs_tx_eqtr_attrs[] = {
+ { "host_tx_eqtr_record", 0400, &ufs_tx_eqtr_record_fops },
+ { "device_tx_eqtr_record", 0400, &ufs_tx_eqtr_record_fops },
+ { "tx_eq_ctrl", 0600, &ufs_tx_eq_ctrl_fops },
+ { }
+};
+
+void ufs_debugfs_hba_init(struct ufs_hba *hba)
+{
+ const struct ufs_debugfs_attr *attr;
+ struct dentry *root;
+
+ /* Set default exception event rate limit period to 20ms */
+ hba->debugfs_ee_rate_limit_ms = 20;
+ INIT_DELAYED_WORK(&hba->debugfs_ee_work, ufs_debugfs_restart_ee);
+
+ root = debugfs_create_dir(dev_name(hba->dev), ufs_debugfs_root);
+ if (IS_ERR_OR_NULL(root))
+ return;
+ hba->debugfs_root = root;
+ d_inode(root)->i_private = hba;
+ for (attr = ufs_attrs; attr->name; attr++)
+ debugfs_create_file(attr->name, attr->mode, root, (void *)attr,
+ attr->fops);
+ debugfs_create_file("exception_event_mask", 0600, hba->debugfs_root,
+ hba, &ee_usr_mask_fops);
+ debugfs_create_u32("exception_event_rate_limit_ms", 0600, hba->debugfs_root,
+ &hba->debugfs_ee_rate_limit_ms);
+
+ if (!(hba->caps & UFSHCD_CAP_TX_EQUALIZATION))
+ return;
+
+ for (u32 gear = UFS_HS_G1; gear <= UFS_HS_GEAR_MAX; gear++) {
+ struct dentry *txeq_dir;
+ char name[32];
+
+ snprintf(name, sizeof(name), "tx_eq_hs_gear%d", gear);
+ txeq_dir = debugfs_create_dir(name, hba->debugfs_root);
+ if (IS_ERR_OR_NULL(txeq_dir))
+ return;
+
+ d_inode(txeq_dir)->i_private = hba;
+
+ /* Create files for TX Equalization parameters */
+ for (attr = ufs_tx_eq_attrs; attr->name; attr++)
+ debugfs_create_file(attr->name, attr->mode, txeq_dir,
+ (void *)(uintptr_t)gear,
+ attr->fops);
+
+ /* TX EQTR is supported for HS-G4 and higher Gears */
+ if (gear < UFS_HS_G4)
+ continue;
+
+ /* Create files for TX EQTR related attributes */
+ for (attr = ufs_tx_eqtr_attrs; attr->name; attr++)
+ debugfs_create_file(attr->name, attr->mode, txeq_dir,
+ (void *)(uintptr_t)gear,
+ attr->fops);
+ }
+}
+
+void ufs_debugfs_hba_exit(struct ufs_hba *hba)
+{
+ debugfs_remove_recursive(hba->debugfs_root);
+ cancel_delayed_work_sync(&hba->debugfs_ee_work);
+}
diff --git a/drivers/ufs/core/ufs-debugfs.h b/drivers/ufs/core/ufs-debugfs.h
new file mode 100644
index 000000000..e5bba9671
--- /dev/null
+++ b/drivers/ufs/core/ufs-debugfs.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2020 Intel Corporation
+ */
+
+#ifndef __UFS_DEBUGFS_H__
+#define __UFS_DEBUGFS_H__
+
+#include <linux/init.h>
+#include <linux/types.h>
+
+struct ufs_hba;
+
+#ifdef CONFIG_DEBUG_FS
+void __init ufs_debugfs_init(void);
+void ufs_debugfs_exit(void);
+void ufs_debugfs_hba_init(struct ufs_hba *hba);
+void ufs_debugfs_hba_exit(struct ufs_hba *hba);
+void ufs_debugfs_exception_event(struct ufs_hba *hba, u16 status);
+#else
+static inline void ufs_debugfs_init(void) {}
+static inline void ufs_debugfs_exit(void) {}
+static inline void ufs_debugfs_hba_init(struct ufs_hba *hba) {}
+static inline void ufs_debugfs_hba_exit(struct ufs_hba *hba) {}
+static inline void ufs_debugfs_exception_event(struct ufs_hba *hba, u16 status) {}
+#endif
+
+#endif
diff --git a/drivers/ufs/core/ufs-fault-injection.c b/drivers/ufs/core/ufs-fault-injection.c
new file mode 100644
index 000000000..55db38e75
--- /dev/null
+++ b/drivers/ufs/core/ufs-fault-injection.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/kconfig.h>
+#include <linux/types.h>
+#include <linux/fault-inject.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <ufs/ufshcd.h>
+#include "ufs-fault-injection.h"
+
+static int ufs_fault_get(char *buffer, const struct kernel_param *kp);
+static int ufs_fault_set(const char *val, const struct kernel_param *kp);
+
+static const struct kernel_param_ops ufs_fault_ops = {
+ .get = ufs_fault_get,
+ .set = ufs_fault_set,
+};
+
+enum { FAULT_INJ_STR_SIZE = 80 };
+
+/*
+ * For more details about fault injection, please refer to
+ * Documentation/fault-injection/fault-injection.rst.
+ */
+static char g_trigger_eh_str[FAULT_INJ_STR_SIZE];
+module_param_cb(trigger_eh, &ufs_fault_ops, g_trigger_eh_str, 0644);
+MODULE_PARM_DESC(trigger_eh,
+ "Fault injection. trigger_eh=<interval>,<probability>,<space>,<times>");
+static DECLARE_FAULT_ATTR(ufs_trigger_eh_attr);
+
+static char g_timeout_str[FAULT_INJ_STR_SIZE];
+module_param_cb(timeout, &ufs_fault_ops, g_timeout_str, 0644);
+MODULE_PARM_DESC(timeout,
+ "Fault injection. timeout=<interval>,<probability>,<space>,<times>");
+static DECLARE_FAULT_ATTR(ufs_timeout_attr);
+
+static int ufs_fault_get(char *buffer, const struct kernel_param *kp)
+{
+ const char *fault_str = kp->arg;
+
+ return sysfs_emit(buffer, "%s\n", fault_str);
+}
+
+static int ufs_fault_set(const char *val, const struct kernel_param *kp)
+{
+ struct fault_attr *attr = NULL;
+
+ if (kp->arg == g_trigger_eh_str)
+ attr = &ufs_trigger_eh_attr;
+ else if (kp->arg == g_timeout_str)
+ attr = &ufs_timeout_attr;
+
+ if (WARN_ON_ONCE(!attr))
+ return -EINVAL;
+
+ if (!setup_fault_attr(attr, (char *)val))
+ return -EINVAL;
+
+ strscpy(kp->arg, val, FAULT_INJ_STR_SIZE);
+
+ return 0;
+}
+
+void ufs_fault_inject_hba_init(struct ufs_hba *hba)
+{
+ 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_trigger_eh(struct ufs_hba *hba)
+{
+ 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
new file mode 100644
index 000000000..d0c870e19
--- /dev/null
+++ b/drivers/ufs/core/ufs-fault-injection.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _UFS_FAULT_INJECTION_H
+#define _UFS_FAULT_INJECTION_H
+
+#include <linux/kconfig.h>
+#include <linux/types.h>
+
+struct ufs_hba;
+
+#ifdef CONFIG_SCSI_UFS_FAULT_INJECTION
+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 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(struct ufs_hba *hba)
+{
+ return false;
+}
+#endif
+
+#endif /* _UFS_FAULT_INJECTION_H */
diff --git a/drivers/ufs/core/ufs-hwmon.c b/drivers/ufs/core/ufs-hwmon.c
new file mode 100644
index 000000000..aa0b60a0e
--- /dev/null
+++ b/drivers/ufs/core/ufs-hwmon.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * UFS hardware monitoring support
+ * Copyright (c) 2021, Western Digital Corporation
+ */
+
+#include <linux/hwmon.h>
+#include <linux/units.h>
+
+#include <ufs/ufshcd.h>
+#include "ufshcd-priv.h"
+
+struct ufs_hwmon_data {
+ struct ufs_hba *hba;
+ u8 mask;
+};
+
+static int ufs_read_temp_enable(struct ufs_hba *hba, u8 mask, long *val)
+{
+ u32 ee_mask;
+ int err;
+
+ err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, QUERY_ATTR_IDN_EE_CONTROL, 0, 0,
+ &ee_mask);
+ if (err)
+ return err;
+
+ *val = (mask & ee_mask & MASK_EE_TOO_HIGH_TEMP) || (mask & ee_mask & MASK_EE_TOO_LOW_TEMP);
+
+ return 0;
+}
+
+static int ufs_get_temp(struct ufs_hba *hba, enum attr_idn idn, long *val)
+{
+ u32 value;
+ int err;
+
+ err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn, 0, 0, &value);
+ if (err)
+ return err;
+
+ if (value == 0)
+ return -ENODATA;
+
+ *val = ((long)value - 80) * MILLIDEGREE_PER_DEGREE;
+
+ return 0;
+}
+
+static int ufs_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long *val)
+{
+ struct ufs_hwmon_data *data = dev_get_drvdata(dev);
+ struct ufs_hba *hba = data->hba;
+ int err;
+
+ down(&hba->host_sem);
+
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ up(&hba->host_sem);
+ return -EBUSY;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ err = ufs_read_temp_enable(hba, data->mask, val);
+
+ break;
+ case hwmon_temp_crit:
+ err = ufs_get_temp(hba, QUERY_ATTR_IDN_HIGH_TEMP_BOUND, val);
+
+ break;
+ case hwmon_temp_lcrit:
+ err = ufs_get_temp(hba, QUERY_ATTR_IDN_LOW_TEMP_BOUND, val);
+
+ break;
+ case hwmon_temp_input:
+ err = ufs_get_temp(hba, QUERY_ATTR_IDN_CASE_ROUGH_TEMP, val);
+
+ break;
+ default:
+ err = -EOPNOTSUPP;
+
+ break;
+ }
+
+ ufshcd_rpm_put_sync(hba);
+
+ up(&hba->host_sem);
+
+ return err;
+}
+
+static int ufs_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long val)
+{
+ struct ufs_hwmon_data *data = dev_get_drvdata(dev);
+ struct ufs_hba *hba = data->hba;
+ int err;
+
+ if (attr != hwmon_temp_enable)
+ return -EINVAL;
+
+ if (val != 0 && val != 1)
+ return -EINVAL;
+
+ down(&hba->host_sem);
+
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ up(&hba->host_sem);
+ return -EBUSY;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+
+ if (val == 1)
+ err = ufshcd_update_ee_usr_mask(hba, MASK_EE_URGENT_TEMP, 0);
+ else
+ err = ufshcd_update_ee_usr_mask(hba, 0, MASK_EE_URGENT_TEMP);
+
+ ufshcd_rpm_put_sync(hba);
+
+ up(&hba->host_sem);
+
+ return err;
+}
+
+static umode_t ufs_hwmon_is_visible(const void *data,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ if (type != hwmon_temp)
+ return 0;
+
+ switch (attr) {
+ case hwmon_temp_enable:
+ return 0644;
+ case hwmon_temp_crit:
+ case hwmon_temp_lcrit:
+ case hwmon_temp_input:
+ return 0444;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static const struct hwmon_channel_info *const ufs_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp, HWMON_T_ENABLE | HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LCRIT),
+ NULL
+};
+
+static const struct hwmon_ops ufs_hwmon_ops = {
+ .is_visible = ufs_hwmon_is_visible,
+ .read = ufs_hwmon_read,
+ .write = ufs_hwmon_write,
+};
+
+static const struct hwmon_chip_info ufs_hwmon_hba_info = {
+ .ops = &ufs_hwmon_ops,
+ .info = ufs_hwmon_info,
+};
+
+void ufs_hwmon_probe(struct ufs_hba *hba, u8 mask)
+{
+ struct device *dev = hba->dev;
+ struct ufs_hwmon_data *data;
+ struct device *hwmon;
+
+ data = kzalloc_obj(*data);
+ if (!data)
+ return;
+
+ data->hba = hba;
+ data->mask = mask;
+
+ hwmon = hwmon_device_register_with_info(dev, "ufs", data, &ufs_hwmon_hba_info, NULL);
+ if (IS_ERR(hwmon)) {
+ dev_warn(dev, "Failed to instantiate hwmon device\n");
+ kfree(data);
+ return;
+ }
+
+ hba->hwmon_device = hwmon;
+}
+
+void ufs_hwmon_remove(struct ufs_hba *hba)
+{
+ struct ufs_hwmon_data *data;
+
+ if (!hba->hwmon_device)
+ return;
+
+ data = dev_get_drvdata(hba->hwmon_device);
+ hwmon_device_unregister(hba->hwmon_device);
+ hba->hwmon_device = NULL;
+ kfree(data);
+}
+
+void ufs_hwmon_notify_event(struct ufs_hba *hba, u8 ee_mask)
+{
+ if (!hba->hwmon_device)
+ return;
+
+ if (ee_mask & MASK_EE_TOO_HIGH_TEMP)
+ hwmon_notify_event(hba->hwmon_device, hwmon_temp, hwmon_temp_max_alarm, 0);
+
+ if (ee_mask & MASK_EE_TOO_LOW_TEMP)
+ hwmon_notify_event(hba->hwmon_device, hwmon_temp, hwmon_temp_min_alarm, 0);
+}
diff --git a/drivers/ufs/core/ufs-mcq.c b/drivers/ufs/core/ufs-mcq.c
new file mode 100644
index 000000000..8106d55f4
--- /dev/null
+++ b/drivers/ufs/core/ufs-mcq.c
@@ -0,0 +1,743 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center. All rights reserved.
+ *
+ * Authors:
+ * Asutosh Das <quic_asutoshd@quicinc.com>
+ * Can Guo <quic_cang@quicinc.com>
+ */
+
+#include <linux/unaligned.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include "ufshcd-priv.h"
+#include <linux/delay.h>
+#include <scsi/scsi_cmnd.h>
+#include <linux/bitfield.h>
+#include <linux/iopoll.h>
+
+#define MAX_QUEUE_SUP GENMASK(7, 0)
+#define QCFGPTR GENMASK(23, 16)
+#define UFS_MCQ_MIN_RW_QUEUES 2
+#define UFS_MCQ_MIN_READ_QUEUES 0
+#define UFS_MCQ_MIN_POLL_QUEUES 0
+#define QUEUE_EN_OFFSET 31
+#define QUEUE_ID_OFFSET 16
+
+#define MCQ_CFG_MAC_MASK GENMASK(16, 8)
+#define MCQ_ENTRY_SIZE_IN_DWORD 8
+#define CQE_UCD_BA GENMASK_ULL(63, 7)
+
+#define UFSHCD_ENABLE_MCQ_INTRS (UTP_TASK_REQ_COMPL |\
+ UFSHCD_ERROR_MASK |\
+ MCQ_CQ_EVENT_STATUS |\
+ MCQ_IAG_EVENT_STATUS)
+
+/* Max mcq register polling time in microseconds */
+#define MCQ_POLL_US 500000
+
+static int rw_queue_count_set(const char *val, const struct kernel_param *kp)
+{
+ return param_set_uint_minmax(val, kp, UFS_MCQ_MIN_RW_QUEUES,
+ num_possible_cpus());
+}
+
+static const struct kernel_param_ops rw_queue_count_ops = {
+ .set = rw_queue_count_set,
+ .get = param_get_uint,
+};
+
+static unsigned int rw_queues;
+module_param_cb(rw_queues, &rw_queue_count_ops, &rw_queues, 0644);
+MODULE_PARM_DESC(rw_queues,
+ "Number of interrupt driven I/O queues used for rw. Default value is nr_cpus");
+
+static int read_queue_count_set(const char *val, const struct kernel_param *kp)
+{
+ return param_set_uint_minmax(val, kp, UFS_MCQ_MIN_READ_QUEUES,
+ num_possible_cpus());
+}
+
+static const struct kernel_param_ops read_queue_count_ops = {
+ .set = read_queue_count_set,
+ .get = param_get_uint,
+};
+
+static unsigned int read_queues;
+module_param_cb(read_queues, &read_queue_count_ops, &read_queues, 0644);
+MODULE_PARM_DESC(read_queues,
+ "Number of interrupt driven read queues used for read. Default value is 0");
+
+static int poll_queue_count_set(const char *val, const struct kernel_param *kp)
+{
+ return param_set_uint_minmax(val, kp, UFS_MCQ_MIN_POLL_QUEUES,
+ num_possible_cpus());
+}
+
+static const struct kernel_param_ops poll_queue_count_ops = {
+ .set = poll_queue_count_set,
+ .get = param_get_uint,
+};
+
+static unsigned int poll_queues = 1;
+module_param_cb(poll_queues, &poll_queue_count_ops, &poll_queues, 0644);
+MODULE_PARM_DESC(poll_queues,
+ "Number of poll queues used for r/w. Default value is 1");
+
+/**
+ * ufshcd_mcq_config_mac - Set the #Max Activ Cmds.
+ * @hba: per adapter instance
+ * @max_active_cmds: maximum # of active commands to the device at any time.
+ *
+ * The controller won't send more than the max_active_cmds to the device at
+ * any time.
+ */
+void ufshcd_mcq_config_mac(struct ufs_hba *hba, u32 max_active_cmds)
+{
+ u32 val;
+
+ val = ufshcd_readl(hba, REG_UFS_MCQ_CFG);
+ val &= ~MCQ_CFG_MAC_MASK;
+ val |= FIELD_PREP(MCQ_CFG_MAC_MASK, max_active_cmds - 1);
+ ufshcd_writel(hba, val, REG_UFS_MCQ_CFG);
+}
+EXPORT_SYMBOL_GPL(ufshcd_mcq_config_mac);
+
+/**
+ * ufshcd_mcq_req_to_hwq - find the hardware queue on which the
+ * request would be issued.
+ * @hba: per adapter instance
+ * @req: pointer to the request to be issued
+ *
+ * Return: the hardware queue instance on which the request will be or has
+ * been queued. %NULL if the request has already been freed.
+ */
+struct ufs_hw_queue *ufshcd_mcq_req_to_hwq(struct ufs_hba *hba,
+ struct request *req)
+{
+ struct blk_mq_hw_ctx *hctx = READ_ONCE(req->mq_hctx);
+
+ return hctx ? &hba->uhq[hctx->queue_num] : NULL;
+}
+
+/**
+ * ufshcd_mcq_queue_cfg_addr - get an start address of the MCQ Queue Config
+ * Registers.
+ * @hba: per adapter instance
+ *
+ * Return: Start address of MCQ Queue Config Registers in HCI
+ */
+unsigned int ufshcd_mcq_queue_cfg_addr(struct ufs_hba *hba)
+{
+ return FIELD_GET(QCFGPTR, hba->mcq_capabilities) * 0x200;
+}
+EXPORT_SYMBOL_GPL(ufshcd_mcq_queue_cfg_addr);
+
+/**
+ * ufshcd_get_hba_mac - Maximum number of commands supported by the host
+ * controller.
+ * @hba: per adapter instance
+ *
+ * Return: queue depth on success; negative upon error.
+ *
+ * MAC = Maximum number of Active Commands supported by the Host Controller.
+ */
+int ufshcd_get_hba_mac(struct ufs_hba *hba)
+{
+ int mac;
+
+ if (!hba->vops || !hba->vops->get_hba_mac) {
+ /*
+ * Extract the maximum number of active transfer tasks value
+ * from the host controller capabilities register. This value is
+ * 0-based.
+ */
+ hba->capabilities =
+ ufshcd_readl(hba, REG_CONTROLLER_CAPABILITIES);
+ mac = hba->capabilities & MASK_TRANSFER_REQUESTS_SLOTS_MCQ;
+ mac++;
+ } else {
+ mac = hba->vops->get_hba_mac(hba);
+ }
+ if (mac < 0)
+ dev_err(hba->dev, "Failed to get mac, err=%d\n", mac);
+ return mac;
+}
+
+static int ufshcd_mcq_config_nr_queues(struct ufs_hba *hba)
+{
+ int i;
+ u32 hba_maxq, rem, tot_queues;
+ struct Scsi_Host *host = hba->host;
+
+ /* maxq is 0 based value */
+ hba_maxq = FIELD_GET(MAX_QUEUE_SUP, hba->mcq_capabilities) + 1;
+
+ tot_queues = read_queues + poll_queues + rw_queues;
+
+ if (hba_maxq < tot_queues) {
+ dev_err(hba->dev, "Total queues (%d) exceeds HC capacity (%d)\n",
+ tot_queues, hba_maxq);
+ return -EOPNOTSUPP;
+ }
+
+ /*
+ * Device should support at least one I/O queue to handle device
+ * commands via hba->dev_cmd_queue.
+ */
+ if (hba_maxq == poll_queues) {
+ dev_err(hba->dev, "At least one non-poll queue required\n");
+ return -EOPNOTSUPP;
+ }
+
+ rem = hba_maxq;
+
+ if (rw_queues) {
+ hba->nr_queues[HCTX_TYPE_DEFAULT] = rw_queues;
+ rem -= hba->nr_queues[HCTX_TYPE_DEFAULT];
+ } else {
+ rw_queues = num_possible_cpus();
+ }
+
+ if (poll_queues) {
+ hba->nr_queues[HCTX_TYPE_POLL] = poll_queues;
+ rem -= hba->nr_queues[HCTX_TYPE_POLL];
+ }
+
+ if (read_queues) {
+ hba->nr_queues[HCTX_TYPE_READ] = read_queues;
+ rem -= hba->nr_queues[HCTX_TYPE_READ];
+ }
+
+ if (!hba->nr_queues[HCTX_TYPE_DEFAULT])
+ hba->nr_queues[HCTX_TYPE_DEFAULT] = min3(rem, rw_queues,
+ num_possible_cpus());
+
+ for (i = 0; i < HCTX_MAX_TYPES; i++)
+ host->nr_hw_queues += hba->nr_queues[i];
+
+ hba->nr_hw_queues = host->nr_hw_queues;
+ return 0;
+}
+
+int ufshcd_mcq_memory_alloc(struct ufs_hba *hba)
+{
+ struct ufs_hw_queue *hwq;
+ size_t utrdl_size, cqe_size;
+ int i;
+
+ for (i = 0; i < hba->nr_hw_queues; i++) {
+ hwq = &hba->uhq[i];
+
+ utrdl_size = sizeof(struct utp_transfer_req_desc) *
+ hwq->max_entries;
+ hwq->sqe_base_addr = dmam_alloc_coherent(hba->dev, utrdl_size,
+ &hwq->sqe_dma_addr,
+ GFP_KERNEL);
+ if (!hwq->sqe_base_addr) {
+ dev_err(hba->dev, "SQE allocation failed\n");
+ return -ENOMEM;
+ }
+
+ cqe_size = sizeof(struct cq_entry) * hwq->max_entries;
+ hwq->cqe_base_addr = dmam_alloc_coherent(hba->dev, cqe_size,
+ &hwq->cqe_dma_addr,
+ GFP_KERNEL);
+ if (!hwq->cqe_base_addr) {
+ dev_err(hba->dev, "CQE allocation failed\n");
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+static void __iomem *mcq_opr_base(struct ufs_hba *hba,
+ enum ufshcd_mcq_opr n, int i)
+{
+ struct ufshcd_mcq_opr_info_t *opr = &hba->mcq_opr[n];
+
+ return opr->base + opr->stride * i;
+}
+
+u32 ufshcd_mcq_read_cqis(struct ufs_hba *hba, int i)
+{
+ return readl(mcq_opr_base(hba, OPR_CQIS, i) + REG_CQIS);
+}
+EXPORT_SYMBOL_GPL(ufshcd_mcq_read_cqis);
+
+void ufshcd_mcq_write_cqis(struct ufs_hba *hba, u32 val, int i)
+{
+ writel(val, mcq_opr_base(hba, OPR_CQIS, i) + REG_CQIS);
+}
+EXPORT_SYMBOL_GPL(ufshcd_mcq_write_cqis);
+
+u32 ufshcd_mcq_read_mcqiacr(struct ufs_hba *hba, int i)
+{
+ return readl(mcq_opr_base(hba, OPR_CQIS, i) + REG_MCQIACR);
+}
+
+void ufshcd_mcq_write_mcqiacr(struct ufs_hba *hba, u32 val, int i)
+{
+ writel(val, mcq_opr_base(hba, OPR_CQIS, i) + REG_MCQIACR);
+}
+
+/*
+ * UFSHCI 4.0 MCQ specification doesn't provide a Task Tag or its equivalent in
+ * the Completion Queue Entry. Find the Task Tag using an indirect method.
+ * UFSHCI 4.1 and above can directly return the Task Tag in the Completion Queue
+ * Entry.
+ */
+static int ufshcd_mcq_get_tag(struct ufs_hba *hba, struct cq_entry *cqe)
+{
+ u64 addr;
+
+ if (hba->ufs_version >= ufshci_version(4, 1))
+ return cqe->task_tag;
+
+ /* Both UCD types must have a size that is a multiple of 128 bytes */
+ BUILD_BUG_ON(sizeof(struct utp_transfer_cmd_desc) & GENMASK(6, 0));
+ BUILD_BUG_ON(sizeof(struct utp_devman_cmd_desc) & GENMASK(6, 0));
+
+ /* Bits 63:7 UCD base address, 6:5 are reserved, 4:0 is SQ ID */
+ addr = le64_to_cpu(cqe->command_desc_base_addr) & CQE_UCD_BA;
+
+ /* The devman UCD is outside the pool; return its reserved tag. */
+ if (unlikely(addr == hba->devman_ucd_dma_addr))
+ return hba->dev_cmd.tag;
+
+ /* Pool entries follow the reserved tags. */
+ return div_u64(addr - hba->ucdl_dma_addr, ufshcd_get_ucd_size(hba)) +
+ UFSHCD_NUM_RESERVED;
+}
+
+static void ufshcd_mcq_process_cqe(struct ufs_hba *hba,
+ struct ufs_hw_queue *hwq)
+{
+ struct cq_entry *cqe = ufshcd_mcq_cur_cqe(hwq);
+
+ if (cqe->command_desc_base_addr) {
+ int tag = ufshcd_mcq_get_tag(hba, cqe);
+
+ ufshcd_compl_one_cqe(hba, tag, cqe);
+ /* After processed the cqe, mark it empty (invalid) entry */
+ cqe->command_desc_base_addr = 0;
+ } else {
+ dev_err(hba->dev, "Abnormal CQ entry!\n");
+ }
+}
+
+/*
+ * This function is called from the UFS error handler with the UFS host
+ * controller disabled (HCE = 0). Reading host controller registers, e.g. the
+ * CQ tail pointer (CQTPy), may not be safe with the host controller disabled.
+ * Hence, iterate over all completion queue entries. This won't result in
+ * double completions because ufshcd_mcq_process_cqe() clears a CQE after it
+ * has been processed.
+ */
+void ufshcd_mcq_compl_all_cqes_lock(struct ufs_hba *hba,
+ struct ufs_hw_queue *hwq)
+{
+ unsigned long flags;
+ u32 entries = hwq->max_entries;
+
+ spin_lock_irqsave(&hwq->cq_lock, flags);
+ while (entries > 0) {
+ ufshcd_mcq_process_cqe(hba, hwq);
+ ufshcd_mcq_inc_cq_head_slot(hwq);
+ entries--;
+ }
+
+ ufshcd_mcq_update_cq_tail_slot(hwq);
+ hwq->cq_head_slot = hwq->cq_tail_slot;
+ spin_unlock_irqrestore(&hwq->cq_lock, flags);
+}
+
+unsigned long ufshcd_mcq_poll_cqe_lock(struct ufs_hba *hba,
+ struct ufs_hw_queue *hwq)
+{
+ unsigned long completed_reqs = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hwq->cq_lock, flags);
+ ufshcd_mcq_update_cq_tail_slot(hwq);
+ while (!ufshcd_mcq_is_cq_empty(hwq)) {
+ ufshcd_mcq_process_cqe(hba, hwq);
+ ufshcd_mcq_inc_cq_head_slot(hwq);
+ completed_reqs++;
+ }
+
+ if (completed_reqs)
+ ufshcd_mcq_update_cq_head(hwq);
+ spin_unlock_irqrestore(&hwq->cq_lock, flags);
+
+ return completed_reqs;
+}
+EXPORT_SYMBOL_GPL(ufshcd_mcq_poll_cqe_lock);
+
+void ufshcd_mcq_make_queues_operational(struct ufs_hba *hba)
+{
+ struct ufs_hw_queue *hwq;
+ u32 intrs;
+ u16 qsize;
+ int i;
+
+ /* Enable required interrupts */
+ intrs = UFSHCD_ENABLE_MCQ_INTRS;
+ if (hba->quirks & UFSHCD_QUIRK_MCQ_BROKEN_INTR)
+ intrs &= ~MCQ_CQ_EVENT_STATUS;
+ ufshcd_enable_intr(hba, intrs);
+
+ for (i = 0; i < hba->nr_hw_queues; i++) {
+ hwq = &hba->uhq[i];
+ hwq->id = i;
+ qsize = hwq->max_entries * MCQ_ENTRY_SIZE_IN_DWORD - 1;
+
+ /* Submission Queue Lower Base Address */
+ ufsmcq_writelx(hba, lower_32_bits(hwq->sqe_dma_addr),
+ ufshcd_mcq_cfg_offset(REG_SQLBA, i));
+ /* Submission Queue Upper Base Address */
+ ufsmcq_writelx(hba, upper_32_bits(hwq->sqe_dma_addr),
+ ufshcd_mcq_cfg_offset(REG_SQUBA, i));
+ /* Submission Queue Doorbell Address Offset */
+ ufsmcq_writelx(hba, ufshcd_mcq_opr_offset(hba, OPR_SQD, i),
+ ufshcd_mcq_cfg_offset(REG_SQDAO, i));
+ /* Submission Queue Interrupt Status Address Offset */
+ ufsmcq_writelx(hba, ufshcd_mcq_opr_offset(hba, OPR_SQIS, i),
+ ufshcd_mcq_cfg_offset(REG_SQISAO, i));
+
+ /* Completion Queue Lower Base Address */
+ ufsmcq_writelx(hba, lower_32_bits(hwq->cqe_dma_addr),
+ ufshcd_mcq_cfg_offset(REG_CQLBA, i));
+ /* Completion Queue Upper Base Address */
+ ufsmcq_writelx(hba, upper_32_bits(hwq->cqe_dma_addr),
+ ufshcd_mcq_cfg_offset(REG_CQUBA, i));
+ /* Completion Queue Doorbell Address Offset */
+ ufsmcq_writelx(hba, ufshcd_mcq_opr_offset(hba, OPR_CQD, i),
+ ufshcd_mcq_cfg_offset(REG_CQDAO, i));
+ /* Completion Queue Interrupt Status Address Offset */
+ ufsmcq_writelx(hba, ufshcd_mcq_opr_offset(hba, OPR_CQIS, i),
+ ufshcd_mcq_cfg_offset(REG_CQISAO, i));
+
+ /* Save the base addresses for quicker access */
+ hwq->mcq_sq_head = mcq_opr_base(hba, OPR_SQD, i) + REG_SQHP;
+ hwq->mcq_sq_tail = mcq_opr_base(hba, OPR_SQD, i) + REG_SQTP;
+ hwq->mcq_cq_head = mcq_opr_base(hba, OPR_CQD, i) + REG_CQHP;
+ hwq->mcq_cq_tail = mcq_opr_base(hba, OPR_CQD, i) + REG_CQTP;
+
+ /* Reinitializing is needed upon HC reset */
+ hwq->sq_tail_slot = hwq->cq_tail_slot = hwq->cq_head_slot = 0;
+
+ /* Enable Tail Entry Push Status interrupt only for non-poll queues */
+ if (i < hba->nr_hw_queues - hba->nr_queues[HCTX_TYPE_POLL])
+ writel(1, mcq_opr_base(hba, OPR_CQIS, i) + REG_CQIE);
+
+ /* Completion Queue Enable|Size to Completion Queue Attribute */
+ ufsmcq_writel(hba, (1 << QUEUE_EN_OFFSET) | qsize,
+ ufshcd_mcq_cfg_offset(REG_CQATTR, i));
+
+ /*
+ * Submission Qeueue Enable|Size|Completion Queue ID to
+ * Submission Queue Attribute
+ */
+ ufsmcq_writel(hba, (1 << QUEUE_EN_OFFSET) | qsize |
+ (i << QUEUE_ID_OFFSET),
+ ufshcd_mcq_cfg_offset(REG_SQATTR, i));
+ }
+}
+EXPORT_SYMBOL_GPL(ufshcd_mcq_make_queues_operational);
+
+void ufshcd_mcq_enable(struct ufs_hba *hba)
+{
+ ufshcd_rmwl(hba, MCQ_MODE_SELECT, MCQ_MODE_SELECT, REG_UFS_MEM_CFG);
+ hba->mcq_enabled = true;
+}
+EXPORT_SYMBOL_GPL(ufshcd_mcq_enable);
+
+void ufshcd_mcq_disable(struct ufs_hba *hba)
+{
+ ufshcd_rmwl(hba, MCQ_MODE_SELECT, 0, REG_UFS_MEM_CFG);
+ hba->mcq_enabled = false;
+}
+
+void ufshcd_mcq_enable_esi(struct ufs_hba *hba)
+{
+ ufshcd_rmwl(hba, ESI_ENABLE, ESI_ENABLE, REG_UFS_MEM_CFG);
+}
+EXPORT_SYMBOL_GPL(ufshcd_mcq_enable_esi);
+
+void ufshcd_mcq_config_esi(struct ufs_hba *hba, struct msi_msg *msg)
+{
+ ufshcd_writel(hba, msg->address_lo, REG_UFS_ESILBA);
+ ufshcd_writel(hba, msg->address_hi, REG_UFS_ESIUBA);
+}
+EXPORT_SYMBOL_GPL(ufshcd_mcq_config_esi);
+
+int ufshcd_mcq_init(struct ufs_hba *hba)
+{
+ struct ufs_hw_queue *hwq;
+ int ret, i;
+
+ ret = ufshcd_mcq_config_nr_queues(hba);
+ if (ret)
+ return ret;
+
+ ret = ufshcd_vops_mcq_config_resource(hba);
+ if (ret)
+ return ret;
+
+ ret = ufshcd_mcq_vops_op_runtime_config(hba);
+ if (ret) {
+ dev_err(hba->dev, "Operation runtime config failed, ret=%d\n",
+ ret);
+ return ret;
+ }
+ hba->uhq = devm_kzalloc(hba->dev,
+ hba->nr_hw_queues * sizeof(struct ufs_hw_queue),
+ GFP_KERNEL);
+ if (!hba->uhq) {
+ dev_err(hba->dev, "ufs hw queue memory allocation failed\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < hba->nr_hw_queues; i++) {
+ hwq = &hba->uhq[i];
+ hwq->max_entries = hba->nutrs + 1;
+ spin_lock_init(&hwq->sq_lock);
+ spin_lock_init(&hwq->cq_lock);
+ mutex_init(&hwq->sq_mutex);
+ }
+
+ return 0;
+}
+
+static int ufshcd_mcq_sq_stop(struct ufs_hba *hba, struct ufs_hw_queue *hwq)
+{
+ void __iomem *reg;
+ u32 id = hwq->id, val;
+ int err;
+
+ if (hba->quirks & UFSHCD_QUIRK_MCQ_BROKEN_RTC)
+ return -ETIMEDOUT;
+
+ writel(SQ_STOP, mcq_opr_base(hba, OPR_SQD, id) + REG_SQRTC);
+ reg = mcq_opr_base(hba, OPR_SQD, id) + REG_SQRTS;
+ err = read_poll_timeout(readl, val, val & SQ_STS, 20,
+ MCQ_POLL_US, false, reg);
+ if (err)
+ dev_err(hba->dev, "%s: failed. hwq-id=%d, err=%d\n",
+ __func__, id, err);
+ return err;
+}
+
+static int ufshcd_mcq_sq_start(struct ufs_hba *hba, struct ufs_hw_queue *hwq)
+{
+ void __iomem *reg;
+ u32 id = hwq->id, val;
+ int err;
+
+ if (hba->quirks & UFSHCD_QUIRK_MCQ_BROKEN_RTC)
+ return -ETIMEDOUT;
+
+ writel(SQ_START, mcq_opr_base(hba, OPR_SQD, id) + REG_SQRTC);
+ reg = mcq_opr_base(hba, OPR_SQD, id) + REG_SQRTS;
+ err = read_poll_timeout(readl, val, !(val & SQ_STS), 20,
+ MCQ_POLL_US, false, reg);
+ if (err)
+ dev_err(hba->dev, "%s: failed. hwq-id=%d, err=%d\n",
+ __func__, id, err);
+ return err;
+}
+
+/**
+ * ufshcd_mcq_sq_cleanup - Clean up submission queue resources
+ * associated with the pending command.
+ * @hba: per adapter instance.
+ * @task_tag: The command's task tag.
+ *
+ * Return: 0 for success; error code otherwise.
+ */
+int ufshcd_mcq_sq_cleanup(struct ufs_hba *hba, int task_tag)
+{
+ struct scsi_cmnd *cmd = ufshcd_tag_to_cmd(hba, task_tag);
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ struct request *rq = scsi_cmd_to_rq(cmd);
+ struct ufs_hw_queue *hwq;
+ void __iomem *reg, *opr_sqd_base;
+ u32 nexus, id, val;
+ int err;
+
+ if (hba->quirks & UFSHCD_QUIRK_MCQ_BROKEN_RTC)
+ return -ETIMEDOUT;
+
+ if (!cmd)
+ return -EINVAL;
+
+ hwq = ufshcd_mcq_req_to_hwq(hba, rq);
+ if (!hwq)
+ return 0;
+
+ id = hwq->id;
+
+ guard(mutex)(&hwq->sq_mutex);
+
+ /* stop the SQ fetching before working on it */
+ err = ufshcd_mcq_sq_stop(hba, hwq);
+ if (err)
+ return err;
+
+ /* SQCTI = EXT_IID, IID, LUN, Task Tag */
+ nexus = lrbp->lun << 8 | task_tag;
+ opr_sqd_base = mcq_opr_base(hba, OPR_SQD, id);
+ writel(nexus, opr_sqd_base + REG_SQCTI);
+
+ /* Initiate Cleanup */
+ writel(readl(opr_sqd_base + REG_SQRTC) | SQ_ICU,
+ opr_sqd_base + REG_SQRTC);
+
+ /* Wait until SQRTSy.CUS = 1. Report SQRTSy.RTC. */
+ reg = opr_sqd_base + REG_SQRTS;
+ err = read_poll_timeout(readl, val, val & SQ_CUS, 20,
+ MCQ_POLL_US, false, reg);
+ if (err)
+ dev_err(hba->dev, "%s: failed. hwq=%d, tag=%d err=%d\n",
+ __func__, id, task_tag, err);
+ else
+ dev_info(hba->dev,
+ "%s, hwq %d: cleanup return code (RTC) %ld\n",
+ __func__, id,
+ FIELD_GET(SQ_ICU_ERR_CODE_MASK, readl(reg)));
+
+ if (ufshcd_mcq_sq_start(hba, hwq))
+ err = -ETIMEDOUT;
+
+ return err;
+}
+
+/**
+ * ufshcd_mcq_nullify_sqe - Nullify the submission queue entry.
+ * Write the sqe's Command Type to 0xF. The host controller will not
+ * fetch any sqe with Command Type = 0xF.
+ *
+ * @utrd: UTP Transfer Request Descriptor to be nullified.
+ */
+static void ufshcd_mcq_nullify_sqe(struct utp_transfer_req_desc *utrd)
+{
+ utrd->header.command_type = 0xf;
+}
+
+/**
+ * ufshcd_mcq_sqe_search - Search for the command in the submission queue
+ * If the command is in the submission queue and not issued to the device yet,
+ * nullify the sqe so the host controller will skip fetching the sqe.
+ *
+ * @hba: per adapter instance.
+ * @hwq: Hardware Queue to be searched.
+ * @task_tag: The command's task tag.
+ *
+ * Return: true if the SQE containing the command is present in the SQ
+ * (not fetched by the controller); returns false if the SQE is not in the SQ.
+ */
+static bool ufshcd_mcq_sqe_search(struct ufs_hba *hba,
+ struct ufs_hw_queue *hwq, int task_tag)
+{
+ struct scsi_cmnd *cmd = ufshcd_tag_to_cmd(hba, task_tag);
+ struct ufshcd_lrb *lrbp;
+ struct utp_transfer_req_desc *utrd;
+ __le64 cmd_desc_base_addr;
+ bool ret = false;
+ u64 addr, match;
+ u32 sq_head_slot;
+
+ if (hba->quirks & UFSHCD_QUIRK_MCQ_BROKEN_RTC)
+ return true;
+
+ if (!cmd)
+ return false;
+
+ lrbp = scsi_cmd_priv(cmd);
+
+ mutex_lock(&hwq->sq_mutex);
+
+ ufshcd_mcq_sq_stop(hba, hwq);
+ sq_head_slot = ufshcd_mcq_get_sq_head_slot(hwq);
+ if (sq_head_slot == hwq->sq_tail_slot)
+ goto out;
+
+ cmd_desc_base_addr = lrbp->utr_descriptor_ptr->command_desc_base_addr;
+ addr = le64_to_cpu(cmd_desc_base_addr) & CQE_UCD_BA;
+
+ while (sq_head_slot != hwq->sq_tail_slot) {
+ utrd = hwq->sqe_base_addr + sq_head_slot;
+ match = le64_to_cpu(utrd->command_desc_base_addr) & CQE_UCD_BA;
+ if (addr == match) {
+ ufshcd_mcq_nullify_sqe(utrd);
+ ret = true;
+ goto out;
+ }
+
+ sq_head_slot++;
+ if (sq_head_slot == hwq->max_entries)
+ sq_head_slot = 0;
+ }
+
+out:
+ ufshcd_mcq_sq_start(hba, hwq);
+ mutex_unlock(&hwq->sq_mutex);
+ return ret;
+}
+
+/**
+ * ufshcd_mcq_abort - Abort the command in MCQ.
+ * @cmd: The command to be aborted.
+ *
+ * Return: SUCCESS or FAILED error codes
+ */
+int ufshcd_mcq_abort(struct scsi_cmnd *cmd)
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct ufs_hba *hba = shost_priv(host);
+ int tag = scsi_cmd_to_rq(cmd)->tag;
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ struct ufs_hw_queue *hwq;
+ int err;
+
+ /* Skip task abort in case previous aborts failed and report failure */
+ if (lrbp->req_abort_skip) {
+ dev_err(hba->dev, "%s: skip abort. tag %d failed earlier\n",
+ __func__, tag);
+ return FAILED;
+ }
+
+ hwq = ufshcd_mcq_req_to_hwq(hba, scsi_cmd_to_rq(cmd));
+ if (!hwq) {
+ dev_err(hba->dev, "%s: skip abort. cmd at tag %d already completed.\n",
+ __func__, tag);
+ return FAILED;
+ }
+
+ if (ufshcd_mcq_sqe_search(hba, hwq, tag)) {
+ /*
+ * Failure. The command should not be "stuck" in SQ for
+ * a long time which resulted in command being aborted.
+ */
+ dev_err(hba->dev, "%s: cmd found in sq. hwq=%d, tag=%d\n",
+ __func__, hwq->id, tag);
+ return FAILED;
+ }
+
+ /*
+ * The command is not in the submission queue, and it is not
+ * in the completion queue either. Query the device to see if
+ * the command is being processed in the device.
+ */
+ err = ufshcd_try_to_abort_task(hba, tag);
+ if (err) {
+ dev_err(hba->dev, "%s: device abort failed %d\n", __func__, err);
+ lrbp->req_abort_skip = true;
+ return FAILED;
+ }
+
+ return SUCCESS;
+}
diff --git a/drivers/ufs/core/ufs-rpmb.c b/drivers/ufs/core/ufs-rpmb.c
new file mode 100644
index 000000000..aa925cbb0
--- /dev/null
+++ b/drivers/ufs/core/ufs-rpmb.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * UFS OP-TEE based RPMB Driver
+ *
+ * Copyright (C) 2025 Micron Technology, Inc.
+ * Copyright (C) 2025 Qualcomm Technologies, Inc.
+ *
+ * Authors:
+ * Bean Huo <beanhuo@micron.com>
+ * Can Guo <can.guo@oss.qualcomm.com>
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/rpmb.h>
+#include <linux/string.h>
+#include <linux/list.h>
+#include <ufs/ufshcd.h>
+#include <linux/unaligned.h>
+#include "ufshcd-priv.h"
+
+#define UFS_RPMB_SEC_PROTOCOL 0xEC /* JEDEC UFS application */
+#define UFS_RPMB_SEC_PROTOCOL_ID 0x01 /* JEDEC UFS RPMB protocol ID, CDB byte3 */
+
+static const struct bus_type ufs_rpmb_bus_type = {
+ .name = "ufs_rpmb",
+};
+
+/* UFS RPMB device structure */
+struct ufs_rpmb_dev {
+ u8 region_id;
+ struct device dev;
+ struct rpmb_dev *rdev;
+ struct ufs_hba *hba;
+ struct list_head node;
+};
+
+static int ufs_sec_submit(struct ufs_hba *hba, u16 spsp, void *buffer, size_t len, bool send)
+{
+ struct scsi_device *sdev = hba->ufs_rpmb_wlun;
+ u8 cdb[12] = { };
+
+ cdb[0] = send ? SECURITY_PROTOCOL_OUT : SECURITY_PROTOCOL_IN;
+ cdb[1] = UFS_RPMB_SEC_PROTOCOL;
+ put_unaligned_be16(spsp, &cdb[2]);
+ put_unaligned_be32(len, &cdb[6]);
+
+ return scsi_execute_cmd(sdev, cdb, send ? REQ_OP_DRV_OUT : REQ_OP_DRV_IN,
+ buffer, len, /*timeout=*/30 * HZ, 0, NULL);
+}
+
+/* UFS RPMB route frames implementation */
+static int ufs_rpmb_route_frames(struct device *dev, u8 *req, unsigned int req_len, u8 *resp,
+ unsigned int resp_len)
+{
+ struct ufs_rpmb_dev *ufs_rpmb = dev_get_drvdata(dev);
+ struct rpmb_frame *frm_out = (struct rpmb_frame *)req;
+ bool need_result_read = true;
+ u16 req_type, protocol_id;
+ struct ufs_hba *hba;
+ int ret;
+
+ if (!ufs_rpmb) {
+ dev_err(dev, "Missing driver data\n");
+ return -ENODEV;
+ }
+
+ hba = ufs_rpmb->hba;
+
+ /* req_resp is at the end of an RPMB frame. */
+ if (req_len < sizeof(*frm_out))
+ return -EINVAL;
+
+ req_type = get_unaligned_be16(&frm_out->req_resp);
+
+ switch (req_type) {
+ case RPMB_PROGRAM_KEY:
+ if (req_len != sizeof(struct rpmb_frame) || resp_len != sizeof(struct rpmb_frame))
+ return -EINVAL;
+ break;
+ case RPMB_GET_WRITE_COUNTER:
+ if (req_len != sizeof(struct rpmb_frame) || resp_len != sizeof(struct rpmb_frame))
+ return -EINVAL;
+ need_result_read = false;
+ break;
+ case RPMB_WRITE_DATA:
+ if (req_len % sizeof(struct rpmb_frame) || resp_len != sizeof(struct rpmb_frame))
+ return -EINVAL;
+ break;
+ case RPMB_READ_DATA:
+ if (req_len != sizeof(struct rpmb_frame) || resp_len % sizeof(struct rpmb_frame))
+ return -EINVAL;
+ need_result_read = false;
+ break;
+ default:
+ dev_err(dev, "Unknown request type=0x%04x\n", req_type);
+ return -EINVAL;
+ }
+
+ protocol_id = ufs_rpmb->region_id << 8 | UFS_RPMB_SEC_PROTOCOL_ID;
+
+ ret = ufs_sec_submit(hba, protocol_id, req, req_len, true);
+ if (ret) {
+ dev_err(dev, "Command failed with ret=%d\n", ret);
+ return ret;
+ }
+
+ if (need_result_read) {
+ struct rpmb_frame *frm_resp = (struct rpmb_frame *)resp;
+
+ memset(frm_resp, 0, sizeof(*frm_resp));
+ put_unaligned_be16(RPMB_RESULT_READ, &frm_resp->req_resp);
+ ret = ufs_sec_submit(hba, protocol_id, resp, resp_len, true);
+ if (ret) {
+ dev_err(dev, "Result read request failed with ret=%d\n", ret);
+ return ret;
+ }
+ }
+
+ if (!ret) {
+ ret = ufs_sec_submit(hba, protocol_id, resp, resp_len, false);
+ if (ret)
+ dev_err(dev, "Response read failed with ret=%d\n", ret);
+ }
+
+ return ret;
+}
+
+static void ufs_rpmb_device_release(struct device *dev)
+{
+ struct ufs_rpmb_dev *ufs_rpmb = dev_get_drvdata(dev);
+
+ rpmb_dev_unregister(ufs_rpmb->rdev);
+}
+
+/* UFS RPMB device registration */
+int ufs_rpmb_probe(struct ufs_hba *hba)
+{
+ struct ufs_rpmb_dev *ufs_rpmb, *it, *tmp;
+ struct rpmb_dev *rdev;
+ char *cid = NULL;
+ int region;
+ u32 cap;
+ int ret;
+
+ if (!hba->ufs_rpmb_wlun || hba->dev_info.b_advanced_rpmb_en) {
+ dev_info(hba->dev, "Skip OP-TEE RPMB registration\n");
+ return -ENODEV;
+ }
+
+ /* Check if device_id is available */
+ if (!hba->dev_info.device_id) {
+ dev_err(hba->dev, "UFS Device ID not available\n");
+ return -EINVAL;
+ }
+
+ struct rpmb_descr descr = {
+ .type = RPMB_TYPE_UFS,
+ .route_frames = ufs_rpmb_route_frames,
+ .reliable_wr_count = hba->dev_info.rpmb_io_size,
+ };
+
+ for (region = 0; region < ARRAY_SIZE(hba->dev_info.rpmb_region_size); region++) {
+ cap = hba->dev_info.rpmb_region_size[region];
+ if (!cap)
+ continue;
+
+ ufs_rpmb = devm_kzalloc(hba->dev, sizeof(*ufs_rpmb), GFP_KERNEL);
+ if (!ufs_rpmb) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ ufs_rpmb->hba = hba;
+ ufs_rpmb->dev.parent = &hba->ufs_rpmb_wlun->sdev_gendev;
+ ufs_rpmb->dev.bus = &ufs_rpmb_bus_type;
+ ufs_rpmb->dev.release = ufs_rpmb_device_release;
+ dev_set_name(&ufs_rpmb->dev, "ufs_rpmb%d", region);
+
+ /* Set driver data BEFORE device_register */
+ dev_set_drvdata(&ufs_rpmb->dev, ufs_rpmb);
+
+ ret = device_register(&ufs_rpmb->dev);
+ if (ret) {
+ dev_err(hba->dev, "Failed to register UFS RPMB device %d\n", region);
+ put_device(&ufs_rpmb->dev);
+ goto err_out;
+ }
+
+ /* Create unique ID by appending region number to device_id */
+ cid = kasprintf(GFP_KERNEL, "%s-R%d", hba->dev_info.device_id, region);
+ if (!cid) {
+ device_unregister(&ufs_rpmb->dev);
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ descr.dev_id = cid;
+ descr.dev_id_len = strlen(cid);
+ descr.capacity = cap;
+
+ /* Register RPMB device */
+ rdev = rpmb_dev_register(&ufs_rpmb->dev, &descr);
+ if (IS_ERR(rdev)) {
+ dev_err(hba->dev, "Failed to register UFS RPMB device.\n");
+ device_unregister(&ufs_rpmb->dev);
+ ret = PTR_ERR(rdev);
+ goto err_out;
+ }
+
+ kfree(cid);
+ cid = NULL;
+
+ ufs_rpmb->rdev = rdev;
+ ufs_rpmb->region_id = region;
+
+ list_add_tail(&ufs_rpmb->node, &hba->rpmbs);
+
+ dev_info(hba->dev, "UFS RPMB region %d registered (capacity=%u)\n", region, cap);
+ }
+
+ return 0;
+err_out:
+ kfree(cid);
+ list_for_each_entry_safe(it, tmp, &hba->rpmbs, node) {
+ list_del(&it->node);
+ device_unregister(&it->dev);
+ }
+
+ return ret;
+}
+
+/* UFS RPMB remove handler */
+void ufs_rpmb_remove(struct ufs_hba *hba)
+{
+ struct ufs_rpmb_dev *ufs_rpmb, *tmp;
+
+ if (list_empty(&hba->rpmbs))
+ return;
+
+ /* Remove all registered RPMB devices */
+ list_for_each_entry_safe(ufs_rpmb, tmp, &hba->rpmbs, node) {
+ dev_info(hba->dev, "Removing UFS RPMB region %d\n", ufs_rpmb->region_id);
+ /* Remove from list first */
+ list_del(&ufs_rpmb->node);
+ /* Unregister device */
+ device_unregister(&ufs_rpmb->dev);
+ }
+
+ dev_info(hba->dev, "All UFS RPMB devices unregistered\n");
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("OP-TEE UFS RPMB driver");
diff --git a/drivers/ufs/core/ufs-sysfs.c b/drivers/ufs/core/ufs-sysfs.c
new file mode 100644
index 000000000..63e670d1a
--- /dev/null
+++ b/drivers/ufs/core/ufs-sysfs.c
@@ -0,0 +1,2164 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2018 Western Digital Corporation
+
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/bitfield.h>
+#include <linux/unaligned.h>
+#include <linux/string_choices.h>
+
+#include <ufs/ufs.h>
+#include <ufs/unipro.h>
+#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";
+ case UFS_HS_G6: return "HS_GEAR6";
+ default: return "UNKNOWN";
+ }
+}
+
+static const char *ufs_wb_resize_hint_to_string(enum wb_resize_hint hint)
+{
+ switch (hint) {
+ case WB_RESIZE_HINT_KEEP:
+ return "keep";
+ case WB_RESIZE_HINT_DECREASE:
+ return "decrease";
+ case WB_RESIZE_HINT_INCREASE:
+ return "increase";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *ufs_wb_resize_status_to_string(enum wb_resize_status status)
+{
+ switch (status) {
+ case WB_RESIZE_STATUS_IDLE:
+ return "idle";
+ case WB_RESIZE_STATUS_IN_PROGRESS:
+ return "in_progress";
+ case WB_RESIZE_STATUS_COMPLETE_SUCCESS:
+ return "complete_success";
+ case WB_RESIZE_STATUS_GENERAL_FAILURE:
+ return "general_failure";
+ default:
+ return "unknown";
+ }
+}
+
+static const char * const ufs_hid_states[] = {
+ [HID_IDLE] = "idle",
+ [ANALYSIS_IN_PROGRESS] = "analysis_in_progress",
+ [DEFRAG_REQUIRED] = "defrag_required",
+ [DEFRAG_IN_PROGRESS] = "defrag_in_progress",
+ [DEFRAG_COMPLETED] = "defrag_completed",
+ [DEFRAG_NOT_REQUIRED] = "defrag_not_required",
+};
+
+static const char *ufs_hid_state_to_string(enum ufs_hid_state state)
+{
+ if (state < NUM_UFS_HID_STATES)
+ return ufs_hid_states[state];
+
+ return "unknown";
+}
+
+static const char *ufshcd_uic_link_state_to_string(
+ enum uic_link_state state)
+{
+ switch (state) {
+ case UIC_LINK_OFF_STATE: return "OFF";
+ case UIC_LINK_ACTIVE_STATE: return "ACTIVE";
+ case UIC_LINK_HIBERN8_STATE: return "HIBERN8";
+ case UIC_LINK_BROKEN_STATE: return "BROKEN";
+ default: return "UNKNOWN";
+ }
+}
+
+static const char *ufshcd_ufs_dev_pwr_mode_to_string(
+ enum ufs_dev_pwr_mode state)
+{
+ switch (state) {
+ case UFS_ACTIVE_PWR_MODE: return "ACTIVE";
+ case UFS_SLEEP_PWR_MODE: return "SLEEP";
+ case UFS_POWERDOWN_PWR_MODE: return "POWERDOWN";
+ case UFS_DEEPSLEEP_PWR_MODE: return "DEEPSLEEP";
+ default: return "UNKNOWN";
+ }
+}
+
+static inline ssize_t ufs_sysfs_pm_lvl_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count,
+ bool rpm)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct ufs_dev_info *dev_info = &hba->dev_info;
+ unsigned long flags, value;
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ 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 &&
+ (!(hba->caps & UFSHCD_CAP_DEEPSLEEP) ||
+ !(dev_info->wspecversion >= 0x310)))
+ return -EINVAL;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (rpm)
+ hba->rpm_lvl = value;
+ else
+ hba->spm_lvl = value;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ return count;
+}
+
+static ssize_t rpm_lvl_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->rpm_lvl);
+}
+
+static ssize_t rpm_lvl_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return ufs_sysfs_pm_lvl_store(dev, attr, buf, count, true);
+}
+
+static ssize_t rpm_target_dev_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_ufs_dev_pwr_mode_to_string(
+ ufs_pm_lvl_states[hba->rpm_lvl].dev_state));
+}
+
+static ssize_t rpm_target_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(
+ ufs_pm_lvl_states[hba->rpm_lvl].link_state));
+}
+
+static ssize_t spm_lvl_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->spm_lvl);
+}
+
+static ssize_t spm_lvl_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return ufs_sysfs_pm_lvl_store(dev, attr, buf, count, false);
+}
+
+static ssize_t spm_target_dev_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_ufs_dev_pwr_mode_to_string(
+ ufs_pm_lvl_states[hba->spm_lvl].dev_state));
+}
+
+static ssize_t spm_target_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(
+ ufs_pm_lvl_states[hba->spm_lvl].link_state));
+}
+
+/* Convert Auto-Hibernate Idle Timer register value to microseconds */
+static int ufshcd_ahit_to_us(u32 ahit)
+{
+ int timer = FIELD_GET(UFSHCI_AHIBERN8_TIMER_MASK, ahit);
+ int scale = FIELD_GET(UFSHCI_AHIBERN8_SCALE_MASK, ahit);
+
+ for (; scale > 0; --scale)
+ timer *= UFSHCI_AHIBERN8_SCALE_FACTOR;
+
+ return timer;
+}
+
+/* Convert microseconds to Auto-Hibernate Idle Timer register value */
+u32 ufshcd_us_to_ahit(unsigned int timer)
+{
+ unsigned int scale;
+
+ for (scale = 0; timer > UFSHCI_AHIBERN8_TIMER_MASK; ++scale)
+ timer /= UFSHCI_AHIBERN8_SCALE_FACTOR;
+
+ return FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, timer) |
+ FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, scale);
+}
+EXPORT_SYMBOL_GPL(ufshcd_us_to_ahit);
+
+static int ufshcd_read_hci_reg(struct ufs_hba *hba, u32 *val, unsigned int reg)
+{
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ up(&hba->host_sem);
+ return -EBUSY;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+ ufshcd_hold(hba);
+ *val = ufshcd_readl(hba, reg);
+ ufshcd_release(hba);
+ ufshcd_rpm_put_sync(hba);
+
+ up(&hba->host_sem);
+ return 0;
+}
+
+static ssize_t auto_hibern8_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u32 ahit;
+ int ret;
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ if (!ufshcd_is_auto_hibern8_supported(hba))
+ return -EOPNOTSUPP;
+
+ ret = ufshcd_read_hci_reg(hba, &ahit, REG_AUTO_HIBERNATE_IDLE_TIMER);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", ufshcd_ahit_to_us(ahit));
+}
+
+static ssize_t auto_hibern8_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ unsigned int timer;
+ int ret = 0;
+
+ if (!ufshcd_is_auto_hibern8_supported(hba))
+ return -EOPNOTSUPP;
+
+ if (kstrtouint(buf, 0, &timer))
+ return -EINVAL;
+
+ if (timer > UFSHCI_AHIBERN8_MAX)
+ return -EINVAL;
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ufshcd_auto_hibern8_update(hba, ufshcd_us_to_ahit(timer));
+
+out:
+ up(&hba->host_sem);
+ return ret ? ret : count;
+}
+
+static ssize_t wb_on_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.wb_enabled);
+}
+
+static ssize_t wb_on_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ unsigned int wb_enable;
+ ssize_t res;
+
+ if (!ufshcd_is_wb_allowed(hba) || (ufshcd_is_clkscaling_supported(hba)
+ && ufshcd_enable_wb_if_scaling_up(hba))) {
+ /*
+ * If the platform supports UFSHCD_CAP_CLK_SCALING, turn WB
+ * on/off will be done while clock scaling up/down.
+ */
+ dev_warn(dev, "It is not allowed to configure WB!\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (kstrtouint(buf, 0, &wb_enable))
+ return -EINVAL;
+
+ if (wb_enable != 0 && wb_enable != 1)
+ return -EINVAL;
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ res = -EBUSY;
+ goto out;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+ res = ufshcd_wb_toggle(hba, wb_enable);
+ ufshcd_rpm_put_sync(hba);
+out:
+ up(&hba->host_sem);
+ 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)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", hba->dev_info.wb_buf_flush_enabled);
+}
+
+static ssize_t enable_wb_buf_flush_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ unsigned int enable_wb_buf_flush;
+ ssize_t res;
+
+ if (!ufshcd_is_wb_buf_flush_allowed(hba)) {
+ dev_warn(dev, "It is not allowed to configure WB buf flushing!\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (kstrtouint(buf, 0, &enable_wb_buf_flush))
+ return -EINVAL;
+
+ if (enable_wb_buf_flush != 0 && enable_wb_buf_flush != 1)
+ return -EINVAL;
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ res = -EBUSY;
+ goto out;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+ res = ufshcd_wb_toggle_buf_flush(hba, enable_wb_buf_flush);
+ ufshcd_rpm_put_sync(hba);
+
+out:
+ up(&hba->host_sem);
+ return res < 0 ? res : count;
+}
+
+static ssize_t wb_flush_threshold_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->vps->wb_flush_threshold);
+}
+
+static ssize_t wb_flush_threshold_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ unsigned int wb_flush_threshold;
+
+ if (kstrtouint(buf, 0, &wb_flush_threshold))
+ return -EINVAL;
+
+ /* The range of values for wb_flush_threshold is (0,10] */
+ if (wb_flush_threshold > UFS_WB_BUF_REMAIN_PERCENT(100) ||
+ wb_flush_threshold == 0) {
+ dev_err(dev, "The value of wb_flush_threshold is invalid!\n");
+ return -EINVAL;
+ }
+
+ hba->vps->wb_flush_threshold = wb_flush_threshold;
+
+ return count;
+}
+
+static const char * const wb_resize_en_mode[] = {
+ [WB_RESIZE_EN_IDLE] = "idle",
+ [WB_RESIZE_EN_DECREASE] = "decrease",
+ [WB_RESIZE_EN_INCREASE] = "increase",
+};
+
+static ssize_t wb_resize_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ int mode;
+ ssize_t res;
+
+ if (!ufshcd_is_wb_allowed(hba) || !hba->dev_info.wb_enabled
+ || !hba->dev_info.b_presrv_uspc_en
+ || !(hba->dev_info.ext_wb_sup & UFS_DEV_WB_BUF_RESIZE))
+ return -EOPNOTSUPP;
+
+ mode = sysfs_match_string(wb_resize_en_mode, buf);
+ if (mode < 0)
+ return -EINVAL;
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ res = -EBUSY;
+ goto out;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+ res = ufshcd_wb_set_resize_en(hba, mode);
+ ufshcd_rpm_put_sync(hba);
+
+out:
+ up(&hba->host_sem);
+ return res < 0 ? res : count;
+}
+
+/**
+ * pm_qos_enable_show - sysfs handler to show pm qos enable value
+ * @dev: device associated with the UFS controller
+ * @attr: sysfs attribute handle
+ * @buf: buffer for sysfs file
+ *
+ * Print 1 if PM QoS feature is enabled, 0 if disabled.
+ *
+ * Returns number of characters written to @buf.
+ */
+static ssize_t pm_qos_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ guard(mutex)(&hba->pm_qos_mutex);
+
+ return sysfs_emit(buf, "%d\n", hba->pm_qos_enabled);
+}
+
+/**
+ * pm_qos_enable_store - sysfs handler to store value
+ * @dev: device associated with the UFS controller
+ * @attr: sysfs attribute handle
+ * @buf: buffer for sysfs file
+ * @count: stores buffer characters count
+ *
+ * Input 0 to disable PM QoS and 1 value to enable.
+ * Default state: 1
+ *
+ * Return: number of characters written to @buf on success, < 0 upon failure.
+ */
+static ssize_t pm_qos_enable_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ bool value;
+
+ if (kstrtobool(buf, &value))
+ return -EINVAL;
+
+ if (value)
+ ufshcd_pm_qos_init(hba);
+ else
+ ufshcd_pm_qos_exit(hba);
+
+ return count;
+}
+
+static ssize_t critical_health_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->critical_health_count);
+}
+
+static ssize_t device_lvl_exception_count_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ if (hba->dev_info.wspecversion < 0x410)
+ return -EOPNOTSUPP;
+
+ return sysfs_emit(buf, "%u\n", atomic_read(&hba->dev_lvl_exception_count));
+}
+
+static ssize_t device_lvl_exception_count_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ unsigned int value;
+
+ if (kstrtouint(buf, 0, &value))
+ return -EINVAL;
+
+ /* the only supported usecase is to reset the dev_lvl_exception_count */
+ if (value)
+ return -EINVAL;
+
+ atomic_set(&hba->dev_lvl_exception_count, 0);
+
+ return count;
+}
+
+static ssize_t device_lvl_exception_id_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ u64 exception_id;
+ int err;
+
+ if (hba->dev_info.wspecversion < 0x410)
+ return -EOPNOTSUPP;
+
+ ufshcd_rpm_get_sync(hba);
+ err = ufshcd_query_attr_qword(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_DEV_LVL_EXCEPTION_ID,
+ 0, 0, &exception_id);
+ ufshcd_rpm_put_sync(hba);
+
+ if (err)
+ return err;
+
+ hba->dev_lvl_exception_id = exception_id;
+ return sysfs_emit(buf, "%llu\n", exception_id);
+}
+
+static ssize_t dme_qos_notification_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "0x%x\n", atomic_read(&hba->dme_qos_notification));
+}
+
+static ssize_t dme_qos_notification_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ unsigned int value;
+
+ if (kstrtouint(buf, 0, &value))
+ return -EINVAL;
+
+ /* the only supported usecase is to reset the dme_qos_notification */
+ if (value)
+ return -EINVAL;
+
+ atomic_set(&hba->dme_qos_notification, 0);
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(rpm_lvl);
+static DEVICE_ATTR_RO(rpm_target_dev_state);
+static DEVICE_ATTR_RO(rpm_target_link_state);
+static DEVICE_ATTR_RW(spm_lvl);
+static DEVICE_ATTR_RO(spm_target_dev_state);
+static DEVICE_ATTR_RO(spm_target_link_state);
+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_WO(wb_resize_enable);
+static DEVICE_ATTR_RW(rtc_update_ms);
+static DEVICE_ATTR_RW(pm_qos_enable);
+static DEVICE_ATTR_RO(critical_health);
+static DEVICE_ATTR_RW(device_lvl_exception_count);
+static DEVICE_ATTR_RO(device_lvl_exception_id);
+static DEVICE_ATTR_RW(dme_qos_notification);
+
+static struct attribute *ufs_sysfs_ufshcd_attrs[] = {
+ &dev_attr_rpm_lvl.attr,
+ &dev_attr_rpm_target_dev_state.attr,
+ &dev_attr_rpm_target_link_state.attr,
+ &dev_attr_spm_lvl.attr,
+ &dev_attr_spm_target_dev_state.attr,
+ &dev_attr_spm_target_link_state.attr,
+ &dev_attr_auto_hibern8.attr,
+ &dev_attr_wb_on.attr,
+ &dev_attr_enable_wb_buf_flush.attr,
+ &dev_attr_wb_flush_threshold.attr,
+ &dev_attr_wb_resize_enable.attr,
+ &dev_attr_rtc_update_ms.attr,
+ &dev_attr_pm_qos_enable.attr,
+ &dev_attr_critical_health.attr,
+ &dev_attr_device_lvl_exception_count.attr,
+ &dev_attr_device_lvl_exception_id.attr,
+ &dev_attr_dme_qos_notification.attr,
+ NULL
+};
+
+static const struct attribute_group ufs_sysfs_default_group = {
+ .attrs = ufs_sysfs_ufshcd_attrs,
+};
+
+static ssize_t clock_scaling_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", ufshcd_is_clkscaling_supported(hba));
+}
+
+static ssize_t write_booster_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", ufshcd_is_wb_allowed(hba));
+}
+
+static DEVICE_ATTR_RO(clock_scaling);
+static DEVICE_ATTR_RO(write_booster);
+
+/*
+ * See Documentation/ABI/testing/sysfs-driver-ufs for the semantics of this
+ * group.
+ */
+static struct attribute *ufs_sysfs_capabilities_attrs[] = {
+ &dev_attr_clock_scaling.attr,
+ &dev_attr_write_booster.attr,
+ NULL
+};
+
+static const struct attribute_group ufs_sysfs_capabilities_group = {
+ .name = "capabilities",
+ .attrs = ufs_sysfs_capabilities_attrs,
+};
+
+static ssize_t version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "0x%x\n", hba->ufs_version);
+}
+
+static ssize_t product_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ u32 val;
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ ret = ufshcd_read_hci_reg(hba, &val, REG_CONTROLLER_PID);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "0x%x\n", val);
+}
+
+static ssize_t man_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ u32 val;
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ ret = ufshcd_read_hci_reg(hba, &val, REG_CONTROLLER_MID);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "0x%x\n", val);
+}
+
+static DEVICE_ATTR_RO(version);
+static DEVICE_ATTR_RO(product_id);
+static DEVICE_ATTR_RO(man_id);
+
+static struct attribute *ufs_sysfs_ufshci_cap_attrs[] = {
+ &dev_attr_version.attr,
+ &dev_attr_product_id.attr,
+ &dev_attr_man_id.attr,
+ NULL
+};
+
+static const struct attribute_group ufs_sysfs_ufshci_group = {
+ .name = "ufshci_capabilities",
+ .attrs = ufs_sysfs_ufshci_cap_attrs,
+};
+
+static ssize_t monitor_enable_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->monitor.enabled);
+}
+
+static ssize_t monitor_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ unsigned long value, flags;
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ value = !!value;
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (value == hba->monitor.enabled)
+ goto out_unlock;
+
+ if (!value) {
+ memset(&hba->monitor, 0, sizeof(hba->monitor));
+ } else {
+ hba->monitor.enabled = true;
+ hba->monitor.enabled_ts = ktime_get();
+ }
+
+out_unlock:
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ return count;
+}
+
+static ssize_t monitor_chunk_size_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%lu\n", hba->monitor.chunk_size);
+}
+
+static ssize_t monitor_chunk_size_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ unsigned long value, flags;
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ /* Only allow chunk size change when monitor is disabled */
+ if (!hba->monitor.enabled)
+ hba->monitor.chunk_size = value;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ return count;
+}
+
+static ssize_t read_total_sectors_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%lu\n", hba->monitor.nr_sec_rw[READ]);
+}
+
+static ssize_t read_total_busy_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%llu\n",
+ ktime_to_us(hba->monitor.total_busy[READ]));
+}
+
+static ssize_t read_nr_requests_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%lu\n", hba->monitor.nr_req[READ]);
+}
+
+static ssize_t read_req_latency_avg_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct ufs_hba_monitor *m = &hba->monitor;
+
+ if (!m->nr_req[READ])
+ return sysfs_emit(buf, "0\n");
+
+ return sysfs_emit(buf, "%llu\n", div_u64(ktime_to_us(m->lat_sum[READ]),
+ m->nr_req[READ]));
+}
+
+static ssize_t read_req_latency_max_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%llu\n",
+ ktime_to_us(hba->monitor.lat_max[READ]));
+}
+
+static ssize_t read_req_latency_min_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%llu\n",
+ ktime_to_us(hba->monitor.lat_min[READ]));
+}
+
+static ssize_t read_req_latency_sum_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%llu\n",
+ ktime_to_us(hba->monitor.lat_sum[READ]));
+}
+
+static ssize_t write_total_sectors_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%lu\n", hba->monitor.nr_sec_rw[WRITE]);
+}
+
+static ssize_t write_total_busy_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%llu\n",
+ ktime_to_us(hba->monitor.total_busy[WRITE]));
+}
+
+static ssize_t write_nr_requests_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%lu\n", hba->monitor.nr_req[WRITE]);
+}
+
+static ssize_t write_req_latency_avg_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct ufs_hba_monitor *m = &hba->monitor;
+
+ if (!m->nr_req[WRITE])
+ return sysfs_emit(buf, "0\n");
+
+ return sysfs_emit(buf, "%llu\n", div_u64(ktime_to_us(m->lat_sum[WRITE]),
+ m->nr_req[WRITE]));
+}
+
+static ssize_t write_req_latency_max_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%llu\n",
+ ktime_to_us(hba->monitor.lat_max[WRITE]));
+}
+
+static ssize_t write_req_latency_min_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%llu\n",
+ ktime_to_us(hba->monitor.lat_min[WRITE]));
+}
+
+static ssize_t write_req_latency_sum_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%llu\n",
+ ktime_to_us(hba->monitor.lat_sum[WRITE]));
+}
+
+static DEVICE_ATTR_RW(monitor_enable);
+static DEVICE_ATTR_RW(monitor_chunk_size);
+static DEVICE_ATTR_RO(read_total_sectors);
+static DEVICE_ATTR_RO(read_total_busy);
+static DEVICE_ATTR_RO(read_nr_requests);
+static DEVICE_ATTR_RO(read_req_latency_avg);
+static DEVICE_ATTR_RO(read_req_latency_max);
+static DEVICE_ATTR_RO(read_req_latency_min);
+static DEVICE_ATTR_RO(read_req_latency_sum);
+static DEVICE_ATTR_RO(write_total_sectors);
+static DEVICE_ATTR_RO(write_total_busy);
+static DEVICE_ATTR_RO(write_nr_requests);
+static DEVICE_ATTR_RO(write_req_latency_avg);
+static DEVICE_ATTR_RO(write_req_latency_max);
+static DEVICE_ATTR_RO(write_req_latency_min);
+static DEVICE_ATTR_RO(write_req_latency_sum);
+
+static struct attribute *ufs_sysfs_monitor_attrs[] = {
+ &dev_attr_monitor_enable.attr,
+ &dev_attr_monitor_chunk_size.attr,
+ &dev_attr_read_total_sectors.attr,
+ &dev_attr_read_total_busy.attr,
+ &dev_attr_read_nr_requests.attr,
+ &dev_attr_read_req_latency_avg.attr,
+ &dev_attr_read_req_latency_max.attr,
+ &dev_attr_read_req_latency_min.attr,
+ &dev_attr_read_req_latency_sum.attr,
+ &dev_attr_write_total_sectors.attr,
+ &dev_attr_write_total_busy.attr,
+ &dev_attr_write_nr_requests.attr,
+ &dev_attr_write_req_latency_avg.attr,
+ &dev_attr_write_req_latency_max.attr,
+ &dev_attr_write_req_latency_min.attr,
+ &dev_attr_write_req_latency_sum.attr,
+ NULL
+};
+
+static const struct attribute_group ufs_sysfs_monitor_group = {
+ .name = "monitor",
+ .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,
+ u8 param_offset,
+ u8 *sysfs_buf,
+ u8 param_size)
+{
+ u8 desc_buf[8] = {0};
+ int ret;
+
+ if (param_size > 8)
+ return -EINVAL;
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+ ret = ufshcd_read_desc_param(hba, desc_id, desc_index,
+ param_offset, desc_buf, param_size);
+ ufshcd_rpm_put_sync(hba);
+ if (ret) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ switch (param_size) {
+ case 1:
+ ret = sysfs_emit(sysfs_buf, "0x%02X\n", *desc_buf);
+ break;
+ case 2:
+ ret = sysfs_emit(sysfs_buf, "0x%04X\n",
+ get_unaligned_be16(desc_buf));
+ break;
+ case 4:
+ ret = sysfs_emit(sysfs_buf, "0x%08X\n",
+ get_unaligned_be32(desc_buf));
+ break;
+ case 8:
+ ret = sysfs_emit(sysfs_buf, "0x%016llX\n",
+ get_unaligned_be64(desc_buf));
+ break;
+ }
+
+out:
+ up(&hba->host_sem);
+ return ret;
+}
+
+#define UFS_DESC_PARAM(_name, _puname, _duname, _size) \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct ufs_hba *hba = dev_get_drvdata(dev); \
+ return ufs_sysfs_read_desc_param(hba, QUERY_DESC_IDN_##_duname, \
+ 0, _duname##_DESC_PARAM##_puname, buf, _size); \
+} \
+static DEVICE_ATTR_RO(_name)
+
+#define UFS_DEVICE_DESC_PARAM(_name, _uname, _size) \
+ UFS_DESC_PARAM(_name, _uname, DEVICE, _size)
+
+UFS_DEVICE_DESC_PARAM(device_type, _DEVICE_TYPE, 1);
+UFS_DEVICE_DESC_PARAM(device_class, _DEVICE_CLASS, 1);
+UFS_DEVICE_DESC_PARAM(device_sub_class, _DEVICE_SUB_CLASS, 1);
+UFS_DEVICE_DESC_PARAM(protocol, _PRTCL, 1);
+UFS_DEVICE_DESC_PARAM(number_of_luns, _NUM_LU, 1);
+UFS_DEVICE_DESC_PARAM(number_of_wluns, _NUM_WLU, 1);
+UFS_DEVICE_DESC_PARAM(boot_enable, _BOOT_ENBL, 1);
+UFS_DEVICE_DESC_PARAM(descriptor_access_enable, _DESC_ACCSS_ENBL, 1);
+UFS_DEVICE_DESC_PARAM(initial_power_mode, _INIT_PWR_MODE, 1);
+UFS_DEVICE_DESC_PARAM(high_priority_lun, _HIGH_PR_LUN, 1);
+UFS_DEVICE_DESC_PARAM(secure_removal_type, _SEC_RMV_TYPE, 1);
+UFS_DEVICE_DESC_PARAM(support_security_lun, _SEC_LU, 1);
+UFS_DEVICE_DESC_PARAM(bkops_termination_latency, _BKOP_TERM_LT, 1);
+UFS_DEVICE_DESC_PARAM(initial_active_icc_level, _ACTVE_ICC_LVL, 1);
+UFS_DEVICE_DESC_PARAM(specification_version, _SPEC_VER, 2);
+UFS_DEVICE_DESC_PARAM(manufacturing_date, _MANF_DATE, 2);
+UFS_DEVICE_DESC_PARAM(manufacturer_id, _MANF_ID, 2);
+UFS_DEVICE_DESC_PARAM(rtt_capability, _RTT_CAP, 1);
+UFS_DEVICE_DESC_PARAM(rtc_update, _FRQ_RTC, 2);
+UFS_DEVICE_DESC_PARAM(ufs_features, _UFS_FEAT, 1);
+UFS_DEVICE_DESC_PARAM(ffu_timeout, _FFU_TMT, 1);
+UFS_DEVICE_DESC_PARAM(queue_depth, _Q_DPTH, 1);
+UFS_DEVICE_DESC_PARAM(device_version, _DEV_VER, 2);
+UFS_DEVICE_DESC_PARAM(number_of_secure_wpa, _NUM_SEC_WPA, 1);
+UFS_DEVICE_DESC_PARAM(psa_max_data_size, _PSA_MAX_DATA, 4);
+UFS_DEVICE_DESC_PARAM(psa_state_timeout, _PSA_TMT, 1);
+UFS_DEVICE_DESC_PARAM(ext_feature_sup, _EXT_UFS_FEATURE_SUP, 4);
+UFS_DEVICE_DESC_PARAM(wb_presv_us_en, _WB_PRESRV_USRSPC_EN, 1);
+UFS_DEVICE_DESC_PARAM(wb_type, _WB_TYPE, 1);
+UFS_DEVICE_DESC_PARAM(wb_shared_alloc_units, _WB_SHARED_ALLOC_UNITS, 4);
+
+static struct attribute *ufs_sysfs_device_descriptor[] = {
+ &dev_attr_device_type.attr,
+ &dev_attr_device_class.attr,
+ &dev_attr_device_sub_class.attr,
+ &dev_attr_protocol.attr,
+ &dev_attr_number_of_luns.attr,
+ &dev_attr_number_of_wluns.attr,
+ &dev_attr_boot_enable.attr,
+ &dev_attr_descriptor_access_enable.attr,
+ &dev_attr_initial_power_mode.attr,
+ &dev_attr_high_priority_lun.attr,
+ &dev_attr_secure_removal_type.attr,
+ &dev_attr_support_security_lun.attr,
+ &dev_attr_bkops_termination_latency.attr,
+ &dev_attr_initial_active_icc_level.attr,
+ &dev_attr_specification_version.attr,
+ &dev_attr_manufacturing_date.attr,
+ &dev_attr_manufacturer_id.attr,
+ &dev_attr_rtt_capability.attr,
+ &dev_attr_rtc_update.attr,
+ &dev_attr_ufs_features.attr,
+ &dev_attr_ffu_timeout.attr,
+ &dev_attr_queue_depth.attr,
+ &dev_attr_device_version.attr,
+ &dev_attr_number_of_secure_wpa.attr,
+ &dev_attr_psa_max_data_size.attr,
+ &dev_attr_psa_state_timeout.attr,
+ &dev_attr_ext_feature_sup.attr,
+ &dev_attr_wb_presv_us_en.attr,
+ &dev_attr_wb_type.attr,
+ &dev_attr_wb_shared_alloc_units.attr,
+ NULL,
+};
+
+static const struct attribute_group ufs_sysfs_device_descriptor_group = {
+ .name = "device_descriptor",
+ .attrs = ufs_sysfs_device_descriptor,
+};
+
+#define UFS_INTERCONNECT_DESC_PARAM(_name, _uname, _size) \
+ UFS_DESC_PARAM(_name, _uname, INTERCONNECT, _size)
+
+UFS_INTERCONNECT_DESC_PARAM(unipro_version, _UNIPRO_VER, 2);
+UFS_INTERCONNECT_DESC_PARAM(mphy_version, _MPHY_VER, 2);
+
+static struct attribute *ufs_sysfs_interconnect_descriptor[] = {
+ &dev_attr_unipro_version.attr,
+ &dev_attr_mphy_version.attr,
+ NULL,
+};
+
+static const struct attribute_group ufs_sysfs_interconnect_descriptor_group = {
+ .name = "interconnect_descriptor",
+ .attrs = ufs_sysfs_interconnect_descriptor,
+};
+
+#define UFS_GEOMETRY_DESC_PARAM(_name, _uname, _size) \
+ UFS_DESC_PARAM(_name, _uname, GEOMETRY, _size)
+
+UFS_GEOMETRY_DESC_PARAM(raw_device_capacity, _DEV_CAP, 8);
+UFS_GEOMETRY_DESC_PARAM(max_number_of_luns, _MAX_NUM_LUN, 1);
+UFS_GEOMETRY_DESC_PARAM(segment_size, _SEG_SIZE, 4);
+UFS_GEOMETRY_DESC_PARAM(allocation_unit_size, _ALLOC_UNIT_SIZE, 1);
+UFS_GEOMETRY_DESC_PARAM(min_addressable_block_size, _MIN_BLK_SIZE, 1);
+UFS_GEOMETRY_DESC_PARAM(optimal_read_block_size, _OPT_RD_BLK_SIZE, 1);
+UFS_GEOMETRY_DESC_PARAM(optimal_write_block_size, _OPT_WR_BLK_SIZE, 1);
+UFS_GEOMETRY_DESC_PARAM(max_in_buffer_size, _MAX_IN_BUF_SIZE, 1);
+UFS_GEOMETRY_DESC_PARAM(max_out_buffer_size, _MAX_OUT_BUF_SIZE, 1);
+UFS_GEOMETRY_DESC_PARAM(rpmb_rw_size, _RPMB_RW_SIZE, 1);
+UFS_GEOMETRY_DESC_PARAM(dyn_capacity_resource_policy, _DYN_CAP_RSRC_PLC, 1);
+UFS_GEOMETRY_DESC_PARAM(data_ordering, _DATA_ORDER, 1);
+UFS_GEOMETRY_DESC_PARAM(max_number_of_contexts, _MAX_NUM_CTX, 1);
+UFS_GEOMETRY_DESC_PARAM(sys_data_tag_unit_size, _TAG_UNIT_SIZE, 1);
+UFS_GEOMETRY_DESC_PARAM(sys_data_tag_resource_size, _TAG_RSRC_SIZE, 1);
+UFS_GEOMETRY_DESC_PARAM(secure_removal_types, _SEC_RM_TYPES, 1);
+UFS_GEOMETRY_DESC_PARAM(memory_types, _MEM_TYPES, 2);
+UFS_GEOMETRY_DESC_PARAM(sys_code_memory_max_alloc_units,
+ _SCM_MAX_NUM_UNITS, 4);
+UFS_GEOMETRY_DESC_PARAM(sys_code_memory_capacity_adjustment_factor,
+ _SCM_CAP_ADJ_FCTR, 2);
+UFS_GEOMETRY_DESC_PARAM(non_persist_memory_max_alloc_units,
+ _NPM_MAX_NUM_UNITS, 4);
+UFS_GEOMETRY_DESC_PARAM(non_persist_memory_capacity_adjustment_factor,
+ _NPM_CAP_ADJ_FCTR, 2);
+UFS_GEOMETRY_DESC_PARAM(enh1_memory_max_alloc_units,
+ _ENM1_MAX_NUM_UNITS, 4);
+UFS_GEOMETRY_DESC_PARAM(enh1_memory_capacity_adjustment_factor,
+ _ENM1_CAP_ADJ_FCTR, 2);
+UFS_GEOMETRY_DESC_PARAM(enh2_memory_max_alloc_units,
+ _ENM2_MAX_NUM_UNITS, 4);
+UFS_GEOMETRY_DESC_PARAM(enh2_memory_capacity_adjustment_factor,
+ _ENM2_CAP_ADJ_FCTR, 2);
+UFS_GEOMETRY_DESC_PARAM(enh3_memory_max_alloc_units,
+ _ENM3_MAX_NUM_UNITS, 4);
+UFS_GEOMETRY_DESC_PARAM(enh3_memory_capacity_adjustment_factor,
+ _ENM3_CAP_ADJ_FCTR, 2);
+UFS_GEOMETRY_DESC_PARAM(enh4_memory_max_alloc_units,
+ _ENM4_MAX_NUM_UNITS, 4);
+UFS_GEOMETRY_DESC_PARAM(enh4_memory_capacity_adjustment_factor,
+ _ENM4_CAP_ADJ_FCTR, 2);
+UFS_GEOMETRY_DESC_PARAM(wb_max_alloc_units, _WB_MAX_ALLOC_UNITS, 4);
+UFS_GEOMETRY_DESC_PARAM(wb_max_wb_luns, _WB_MAX_WB_LUNS, 1);
+UFS_GEOMETRY_DESC_PARAM(wb_buff_cap_adj, _WB_BUFF_CAP_ADJ, 1);
+UFS_GEOMETRY_DESC_PARAM(wb_sup_red_type, _WB_SUP_RED_TYPE, 1);
+UFS_GEOMETRY_DESC_PARAM(wb_sup_wb_type, _WB_SUP_WB_TYPE, 1);
+
+
+static struct attribute *ufs_sysfs_geometry_descriptor[] = {
+ &dev_attr_raw_device_capacity.attr,
+ &dev_attr_max_number_of_luns.attr,
+ &dev_attr_segment_size.attr,
+ &dev_attr_allocation_unit_size.attr,
+ &dev_attr_min_addressable_block_size.attr,
+ &dev_attr_optimal_read_block_size.attr,
+ &dev_attr_optimal_write_block_size.attr,
+ &dev_attr_max_in_buffer_size.attr,
+ &dev_attr_max_out_buffer_size.attr,
+ &dev_attr_rpmb_rw_size.attr,
+ &dev_attr_dyn_capacity_resource_policy.attr,
+ &dev_attr_data_ordering.attr,
+ &dev_attr_max_number_of_contexts.attr,
+ &dev_attr_sys_data_tag_unit_size.attr,
+ &dev_attr_sys_data_tag_resource_size.attr,
+ &dev_attr_secure_removal_types.attr,
+ &dev_attr_memory_types.attr,
+ &dev_attr_sys_code_memory_max_alloc_units.attr,
+ &dev_attr_sys_code_memory_capacity_adjustment_factor.attr,
+ &dev_attr_non_persist_memory_max_alloc_units.attr,
+ &dev_attr_non_persist_memory_capacity_adjustment_factor.attr,
+ &dev_attr_enh1_memory_max_alloc_units.attr,
+ &dev_attr_enh1_memory_capacity_adjustment_factor.attr,
+ &dev_attr_enh2_memory_max_alloc_units.attr,
+ &dev_attr_enh2_memory_capacity_adjustment_factor.attr,
+ &dev_attr_enh3_memory_max_alloc_units.attr,
+ &dev_attr_enh3_memory_capacity_adjustment_factor.attr,
+ &dev_attr_enh4_memory_max_alloc_units.attr,
+ &dev_attr_enh4_memory_capacity_adjustment_factor.attr,
+ &dev_attr_wb_max_alloc_units.attr,
+ &dev_attr_wb_max_wb_luns.attr,
+ &dev_attr_wb_buff_cap_adj.attr,
+ &dev_attr_wb_sup_red_type.attr,
+ &dev_attr_wb_sup_wb_type.attr,
+ NULL,
+};
+
+static const struct attribute_group ufs_sysfs_geometry_descriptor_group = {
+ .name = "geometry_descriptor",
+ .attrs = ufs_sysfs_geometry_descriptor,
+};
+
+#define UFS_HEALTH_DESC_PARAM(_name, _uname, _size) \
+ UFS_DESC_PARAM(_name, _uname, HEALTH, _size)
+
+UFS_HEALTH_DESC_PARAM(eol_info, _EOL_INFO, 1);
+UFS_HEALTH_DESC_PARAM(life_time_estimation_a, _LIFE_TIME_EST_A, 1);
+UFS_HEALTH_DESC_PARAM(life_time_estimation_b, _LIFE_TIME_EST_B, 1);
+
+static struct attribute *ufs_sysfs_health_descriptor[] = {
+ &dev_attr_eol_info.attr,
+ &dev_attr_life_time_estimation_a.attr,
+ &dev_attr_life_time_estimation_b.attr,
+ NULL,
+};
+
+static const struct attribute_group ufs_sysfs_health_descriptor_group = {
+ .name = "health_descriptor",
+ .attrs = ufs_sysfs_health_descriptor,
+};
+
+#define UFS_POWER_DESC_PARAM(_name, _uname, _index) \
+static ssize_t _name##_index##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct ufs_hba *hba = dev_get_drvdata(dev); \
+ return ufs_sysfs_read_desc_param(hba, QUERY_DESC_IDN_POWER, 0, \
+ PWR_DESC##_uname##_0 + _index * 2, buf, 2); \
+} \
+static DEVICE_ATTR_RO(_name##_index)
+
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 0);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 1);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 2);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 3);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 4);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 5);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 6);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 7);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 8);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 9);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 10);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 11);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 12);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 13);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 14);
+UFS_POWER_DESC_PARAM(active_icc_levels_vcc, _ACTIVE_LVLS_VCC, 15);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 0);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 1);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 2);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 3);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 4);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 5);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 6);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 7);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 8);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 9);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 10);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 11);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 12);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 13);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 14);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq, _ACTIVE_LVLS_VCCQ, 15);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 0);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 1);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 2);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 3);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 4);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 5);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 6);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 7);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 8);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 9);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 10);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 11);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 12);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 13);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 14);
+UFS_POWER_DESC_PARAM(active_icc_levels_vccq2, _ACTIVE_LVLS_VCCQ2, 15);
+
+static struct attribute *ufs_sysfs_power_descriptor[] = {
+ &dev_attr_active_icc_levels_vcc0.attr,
+ &dev_attr_active_icc_levels_vcc1.attr,
+ &dev_attr_active_icc_levels_vcc2.attr,
+ &dev_attr_active_icc_levels_vcc3.attr,
+ &dev_attr_active_icc_levels_vcc4.attr,
+ &dev_attr_active_icc_levels_vcc5.attr,
+ &dev_attr_active_icc_levels_vcc6.attr,
+ &dev_attr_active_icc_levels_vcc7.attr,
+ &dev_attr_active_icc_levels_vcc8.attr,
+ &dev_attr_active_icc_levels_vcc9.attr,
+ &dev_attr_active_icc_levels_vcc10.attr,
+ &dev_attr_active_icc_levels_vcc11.attr,
+ &dev_attr_active_icc_levels_vcc12.attr,
+ &dev_attr_active_icc_levels_vcc13.attr,
+ &dev_attr_active_icc_levels_vcc14.attr,
+ &dev_attr_active_icc_levels_vcc15.attr,
+ &dev_attr_active_icc_levels_vccq0.attr,
+ &dev_attr_active_icc_levels_vccq1.attr,
+ &dev_attr_active_icc_levels_vccq2.attr,
+ &dev_attr_active_icc_levels_vccq3.attr,
+ &dev_attr_active_icc_levels_vccq4.attr,
+ &dev_attr_active_icc_levels_vccq5.attr,
+ &dev_attr_active_icc_levels_vccq6.attr,
+ &dev_attr_active_icc_levels_vccq7.attr,
+ &dev_attr_active_icc_levels_vccq8.attr,
+ &dev_attr_active_icc_levels_vccq9.attr,
+ &dev_attr_active_icc_levels_vccq10.attr,
+ &dev_attr_active_icc_levels_vccq11.attr,
+ &dev_attr_active_icc_levels_vccq12.attr,
+ &dev_attr_active_icc_levels_vccq13.attr,
+ &dev_attr_active_icc_levels_vccq14.attr,
+ &dev_attr_active_icc_levels_vccq15.attr,
+ &dev_attr_active_icc_levels_vccq20.attr,
+ &dev_attr_active_icc_levels_vccq21.attr,
+ &dev_attr_active_icc_levels_vccq22.attr,
+ &dev_attr_active_icc_levels_vccq23.attr,
+ &dev_attr_active_icc_levels_vccq24.attr,
+ &dev_attr_active_icc_levels_vccq25.attr,
+ &dev_attr_active_icc_levels_vccq26.attr,
+ &dev_attr_active_icc_levels_vccq27.attr,
+ &dev_attr_active_icc_levels_vccq28.attr,
+ &dev_attr_active_icc_levels_vccq29.attr,
+ &dev_attr_active_icc_levels_vccq210.attr,
+ &dev_attr_active_icc_levels_vccq211.attr,
+ &dev_attr_active_icc_levels_vccq212.attr,
+ &dev_attr_active_icc_levels_vccq213.attr,
+ &dev_attr_active_icc_levels_vccq214.attr,
+ &dev_attr_active_icc_levels_vccq215.attr,
+ NULL,
+};
+
+static const struct attribute_group ufs_sysfs_power_descriptor_group = {
+ .name = "power_descriptor",
+ .attrs = ufs_sysfs_power_descriptor,
+};
+
+#define UFS_STRING_DESCRIPTOR(_name, _pname) \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ u8 index; \
+ struct ufs_hba *hba = dev_get_drvdata(dev); \
+ int ret; \
+ int desc_len = QUERY_DESC_MAX_SIZE; \
+ u8 *desc_buf; \
+ \
+ down(&hba->host_sem); \
+ if (!ufshcd_is_user_access_allowed(hba)) { \
+ up(&hba->host_sem); \
+ return -EBUSY; \
+ } \
+ desc_buf = kzalloc(QUERY_DESC_MAX_SIZE, GFP_ATOMIC); \
+ if (!desc_buf) { \
+ up(&hba->host_sem); \
+ return -ENOMEM; \
+ } \
+ ufshcd_rpm_get_sync(hba); \
+ ret = ufshcd_query_descriptor_retry(hba, \
+ UPIU_QUERY_OPCODE_READ_DESC, QUERY_DESC_IDN_DEVICE, \
+ 0, 0, desc_buf, &desc_len); \
+ if (ret) { \
+ ret = -EINVAL; \
+ goto out; \
+ } \
+ index = desc_buf[DEVICE_DESC_PARAM##_pname]; \
+ kfree(desc_buf); \
+ desc_buf = NULL; \
+ ret = ufshcd_read_string_desc(hba, index, &desc_buf, \
+ SD_ASCII_STD); \
+ if (ret < 0) \
+ goto out; \
+ ret = sysfs_emit(buf, "%s\n", desc_buf); \
+out: \
+ ufshcd_rpm_put_sync(hba); \
+ kfree(desc_buf); \
+ up(&hba->host_sem); \
+ return ret; \
+} \
+static DEVICE_ATTR_RO(_name)
+
+UFS_STRING_DESCRIPTOR(manufacturer_name, _MANF_NAME);
+UFS_STRING_DESCRIPTOR(product_name, _PRDCT_NAME);
+UFS_STRING_DESCRIPTOR(oem_id, _OEM_ID);
+UFS_STRING_DESCRIPTOR(serial_number, _SN);
+UFS_STRING_DESCRIPTOR(product_revision, _PRDCT_REV);
+
+static struct attribute *ufs_sysfs_string_descriptors[] = {
+ &dev_attr_manufacturer_name.attr,
+ &dev_attr_product_name.attr,
+ &dev_attr_oem_id.attr,
+ &dev_attr_serial_number.attr,
+ &dev_attr_product_revision.attr,
+ NULL,
+};
+
+static const struct attribute_group ufs_sysfs_string_descriptors_group = {
+ .name = "string_descriptors",
+ .attrs = ufs_sysfs_string_descriptors,
+};
+
+static inline bool ufshcd_is_wb_flags(enum flag_idn idn)
+{
+ return idn >= QUERY_FLAG_IDN_WB_EN &&
+ idn <= QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8;
+}
+
+#define UFS_FLAG(_name, _uname) \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ bool flag; \
+ u8 index = 0; \
+ int ret; \
+ struct ufs_hba *hba = dev_get_drvdata(dev); \
+ \
+ down(&hba->host_sem); \
+ if (!ufshcd_is_user_access_allowed(hba)) { \
+ up(&hba->host_sem); \
+ return -EBUSY; \
+ } \
+ if (ufshcd_is_wb_flags(QUERY_FLAG_IDN##_uname)) \
+ index = ufshcd_wb_get_query_index(hba); \
+ ufshcd_rpm_get_sync(hba); \
+ ret = ufshcd_query_flag(hba, UPIU_QUERY_OPCODE_READ_FLAG, \
+ QUERY_FLAG_IDN##_uname, index, &flag); \
+ ufshcd_rpm_put_sync(hba); \
+ if (ret) { \
+ ret = -EINVAL; \
+ goto out; \
+ } \
+ ret = sysfs_emit(buf, "%s\n", str_true_false(flag)); \
+out: \
+ up(&hba->host_sem); \
+ return ret; \
+} \
+static DEVICE_ATTR_RO(_name)
+
+UFS_FLAG(device_init, _FDEVICEINIT);
+UFS_FLAG(permanent_wpe, _PERMANENT_WPE);
+UFS_FLAG(power_on_wpe, _PWR_ON_WPE);
+UFS_FLAG(bkops_enable, _BKOPS_EN);
+UFS_FLAG(life_span_mode_enable, _LIFE_SPAN_MODE_ENABLE);
+UFS_FLAG(phy_resource_removal, _FPHYRESOURCEREMOVAL);
+UFS_FLAG(busy_rtc, _BUSY_RTC);
+UFS_FLAG(disable_fw_update, _PERMANENTLY_DISABLE_FW_UPDATE);
+UFS_FLAG(wb_enable, _WB_EN);
+UFS_FLAG(wb_flush_en, _WB_BUFF_FLUSH_EN);
+UFS_FLAG(wb_flush_during_h8, _WB_BUFF_FLUSH_DURING_HIBERN8);
+
+static struct attribute *ufs_sysfs_device_flags[] = {
+ &dev_attr_device_init.attr,
+ &dev_attr_permanent_wpe.attr,
+ &dev_attr_power_on_wpe.attr,
+ &dev_attr_bkops_enable.attr,
+ &dev_attr_life_span_mode_enable.attr,
+ &dev_attr_phy_resource_removal.attr,
+ &dev_attr_busy_rtc.attr,
+ &dev_attr_disable_fw_update.attr,
+ &dev_attr_wb_enable.attr,
+ &dev_attr_wb_flush_en.attr,
+ &dev_attr_wb_flush_during_h8.attr,
+ NULL,
+};
+
+static const struct attribute_group ufs_sysfs_flags_group = {
+ .name = "flags",
+ .attrs = ufs_sysfs_device_flags,
+};
+
+static ssize_t max_number_of_rtt_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ u32 rtt;
+ int ret;
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ up(&hba->host_sem);
+ return -EBUSY;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+ ret = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_MAX_NUM_OF_RTT, 0, 0, &rtt);
+ ufshcd_rpm_put_sync(hba);
+
+ if (ret)
+ goto out;
+
+ ret = sysfs_emit(buf, "0x%08X\n", rtt);
+
+out:
+ up(&hba->host_sem);
+ return ret;
+}
+
+static ssize_t max_number_of_rtt_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct ufs_dev_info *dev_info = &hba->dev_info;
+ struct scsi_device *sdev;
+ unsigned int memflags;
+ unsigned int rtt;
+ int ret;
+
+ if (kstrtouint(buf, 0, &rtt))
+ return -EINVAL;
+
+ if (rtt > dev_info->rtt_cap) {
+ dev_err(dev, "rtt can be at most bDeviceRTTCap\n");
+ return -EINVAL;
+ }
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+
+ memflags = memalloc_noio_save();
+ shost_for_each_device(sdev, hba->host)
+ blk_mq_freeze_queue_nomemsave(sdev->request_queue);
+
+ ret = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_MAX_NUM_OF_RTT, 0, 0, &rtt);
+
+ shost_for_each_device(sdev, hba->host)
+ blk_mq_unfreeze_queue_nomemrestore(sdev->request_queue);
+ memalloc_noio_restore(memflags);
+
+ ufshcd_rpm_put_sync(hba);
+
+out:
+ up(&hba->host_sem);
+ return ret < 0 ? ret : count;
+}
+
+static DEVICE_ATTR_RW(max_number_of_rtt);
+
+static inline bool ufshcd_is_wb_attrs(enum attr_idn idn)
+{
+ return idn >= QUERY_ATTR_IDN_WB_FLUSH_STATUS &&
+ idn <= QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE;
+}
+
+static inline bool ufshcd_is_qword_attr(enum attr_idn idn)
+{
+ return idn == QUERY_ATTR_IDN_TIMESTAMP ||
+ idn == QUERY_ATTR_IDN_DEV_LVL_EXCEPTION_ID;
+}
+
+static int wb_read_resize_attrs(struct ufs_hba *hba,
+ enum attr_idn idn, u32 *attr_val)
+{
+ u8 index = 0;
+ int ret;
+
+ if (!ufshcd_is_wb_allowed(hba) || !hba->dev_info.wb_enabled
+ || !hba->dev_info.b_presrv_uspc_en
+ || !(hba->dev_info.ext_wb_sup & UFS_DEV_WB_BUF_RESIZE))
+ return -EOPNOTSUPP;
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ up(&hba->host_sem);
+ return -EBUSY;
+ }
+
+ index = ufshcd_wb_get_query_index(hba);
+ ufshcd_rpm_get_sync(hba);
+ ret = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ idn, index, 0, attr_val);
+ ufshcd_rpm_put_sync(hba);
+
+ up(&hba->host_sem);
+ return ret;
+}
+
+static ssize_t wb_resize_hint_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ int ret;
+ u32 value;
+
+ ret = wb_read_resize_attrs(hba,
+ QUERY_ATTR_IDN_WB_BUF_RESIZE_HINT, &value);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", ufs_wb_resize_hint_to_string(value));
+}
+
+static DEVICE_ATTR_RO(wb_resize_hint);
+
+static ssize_t wb_resize_status_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ int ret;
+ u32 value;
+
+ ret = wb_read_resize_attrs(hba,
+ QUERY_ATTR_IDN_WB_BUF_RESIZE_STATUS, &value);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", ufs_wb_resize_status_to_string(value));
+}
+
+static DEVICE_ATTR_RO(wb_resize_status);
+
+#define UFS_ATTRIBUTE(_name, _uname) \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct ufs_hba *hba = dev_get_drvdata(dev); \
+ u64 qword_value; \
+ u32 value; \
+ int ret; \
+ u8 index = 0; \
+ \
+ down(&hba->host_sem); \
+ if (!ufshcd_is_user_access_allowed(hba)) { \
+ up(&hba->host_sem); \
+ return -EBUSY; \
+ } \
+ if (ufshcd_is_wb_attrs(QUERY_ATTR_IDN##_uname)) \
+ index = ufshcd_wb_get_query_index(hba); \
+ ufshcd_rpm_get_sync(hba); \
+ if (ufshcd_is_qword_attr(QUERY_ATTR_IDN##_uname)) \
+ ret = ufshcd_query_attr_qword(hba, \
+ UPIU_QUERY_OPCODE_READ_ATTR, \
+ QUERY_ATTR_IDN##_uname, \
+ index, 0, &qword_value); \
+ else \
+ ret = ufshcd_query_attr(hba, \
+ UPIU_QUERY_OPCODE_READ_ATTR, \
+ QUERY_ATTR_IDN##_uname, index, 0, &value); \
+ ufshcd_rpm_put_sync(hba); \
+ if (ret) { \
+ ret = -EINVAL; \
+ goto out; \
+ } \
+ if (ufshcd_is_qword_attr(QUERY_ATTR_IDN##_uname)) \
+ ret = sysfs_emit(buf, "0x%016llX\n", qword_value); \
+ else \
+ ret = sysfs_emit(buf, "0x%08X\n", value); \
+out: \
+ up(&hba->host_sem); \
+ return ret; \
+} \
+static DEVICE_ATTR_RO(_name)
+
+UFS_ATTRIBUTE(boot_lun_enabled, _BOOT_LU_EN);
+UFS_ATTRIBUTE(current_power_mode, _POWER_MODE);
+UFS_ATTRIBUTE(active_icc_level, _ACTIVE_ICC_LVL);
+UFS_ATTRIBUTE(ooo_data_enabled, _OOO_DATA_EN);
+UFS_ATTRIBUTE(bkops_status, _BKOPS_STATUS);
+UFS_ATTRIBUTE(purge_status, _PURGE_STATUS);
+UFS_ATTRIBUTE(max_data_in_size, _MAX_DATA_IN);
+UFS_ATTRIBUTE(max_data_out_size, _MAX_DATA_OUT);
+UFS_ATTRIBUTE(reference_clock_frequency, _REF_CLK_FREQ);
+UFS_ATTRIBUTE(configuration_descriptor_lock, _CONF_DESC_LOCK);
+UFS_ATTRIBUTE(exception_event_control, _EE_CONTROL);
+UFS_ATTRIBUTE(exception_event_status, _EE_STATUS);
+UFS_ATTRIBUTE(ffu_status, _FFU_STATUS);
+UFS_ATTRIBUTE(psa_state, _PSA_STATE);
+UFS_ATTRIBUTE(psa_data_size, _PSA_DATA_SIZE);
+UFS_ATTRIBUTE(wb_flush_status, _WB_FLUSH_STATUS);
+UFS_ATTRIBUTE(wb_avail_buf, _AVAIL_WB_BUFF_SIZE);
+UFS_ATTRIBUTE(wb_life_time_est, _WB_BUFF_LIFE_TIME_EST);
+UFS_ATTRIBUTE(wb_cur_buf, _CURR_WB_BUFF_SIZE);
+
+
+static struct attribute *ufs_sysfs_attributes[] = {
+ &dev_attr_boot_lun_enabled.attr,
+ &dev_attr_current_power_mode.attr,
+ &dev_attr_active_icc_level.attr,
+ &dev_attr_ooo_data_enabled.attr,
+ &dev_attr_bkops_status.attr,
+ &dev_attr_purge_status.attr,
+ &dev_attr_max_data_in_size.attr,
+ &dev_attr_max_data_out_size.attr,
+ &dev_attr_reference_clock_frequency.attr,
+ &dev_attr_configuration_descriptor_lock.attr,
+ &dev_attr_max_number_of_rtt.attr,
+ &dev_attr_exception_event_control.attr,
+ &dev_attr_exception_event_status.attr,
+ &dev_attr_ffu_status.attr,
+ &dev_attr_psa_state.attr,
+ &dev_attr_psa_data_size.attr,
+ &dev_attr_wb_flush_status.attr,
+ &dev_attr_wb_avail_buf.attr,
+ &dev_attr_wb_life_time_est.attr,
+ &dev_attr_wb_cur_buf.attr,
+ &dev_attr_wb_resize_hint.attr,
+ &dev_attr_wb_resize_status.attr,
+ NULL,
+};
+
+static const struct attribute_group ufs_sysfs_attributes_group = {
+ .name = "attributes",
+ .attrs = ufs_sysfs_attributes,
+};
+
+static int hid_query_attr(struct ufs_hba *hba, enum query_opcode opcode,
+ enum attr_idn idn, u32 *attr_val)
+{
+ int ret;
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ up(&hba->host_sem);
+ return -EBUSY;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+ ret = ufshcd_query_attr(hba, opcode, idn, 0, 0, attr_val);
+ ufshcd_rpm_put_sync(hba);
+
+ up(&hba->host_sem);
+ return ret;
+}
+
+static ssize_t analysis_trigger_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ int mode;
+ int ret;
+
+ if (sysfs_streq(buf, "enable"))
+ mode = HID_ANALYSIS_ENABLE;
+ else if (sysfs_streq(buf, "disable"))
+ mode = HID_ANALYSIS_AND_DEFRAG_DISABLE;
+ else
+ return -EINVAL;
+
+ ret = hid_query_attr(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_HID_DEFRAG_OPERATION, &mode);
+
+ return ret < 0 ? ret : count;
+}
+
+static DEVICE_ATTR_WO(analysis_trigger);
+
+static ssize_t defrag_trigger_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ int mode;
+ int ret;
+
+ if (sysfs_streq(buf, "enable"))
+ mode = HID_ANALYSIS_AND_DEFRAG_ENABLE;
+ else if (sysfs_streq(buf, "disable"))
+ mode = HID_ANALYSIS_AND_DEFRAG_DISABLE;
+ else
+ return -EINVAL;
+
+ ret = hid_query_attr(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_HID_DEFRAG_OPERATION, &mode);
+
+ return ret < 0 ? ret : count;
+}
+
+static DEVICE_ATTR_WO(defrag_trigger);
+
+#define UFS_HID_AVAILABLE_SIZE_INVALID 0xFFFFFFFFU
+static ssize_t fragmented_size_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ u32 value;
+ int ret;
+
+ ret = hid_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_HID_AVAILABLE_SIZE, &value);
+ if (ret)
+ return ret;
+
+ if (value == UFS_HID_AVAILABLE_SIZE_INVALID)
+ return -ENODATA;
+
+ return sysfs_emit(buf, "%u\n", value);
+}
+
+static DEVICE_ATTR_RO(fragmented_size);
+
+static ssize_t defrag_size_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ u32 value;
+ int ret;
+
+ ret = hid_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_HID_SIZE, &value);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", value);
+}
+
+static ssize_t defrag_size_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ u32 value;
+ int ret;
+
+ if (kstrtou32(buf, 0, &value))
+ return -EINVAL;
+
+ ret = hid_query_attr(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_HID_SIZE, &value);
+
+ return ret < 0 ? ret : count;
+}
+
+static DEVICE_ATTR_RW(defrag_size);
+
+static ssize_t progress_ratio_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ u32 value;
+ int ret;
+
+ ret = hid_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_HID_PROGRESS_RATIO, &value);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", value);
+}
+
+static DEVICE_ATTR_RO(progress_ratio);
+
+static ssize_t state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ u32 value;
+ int ret;
+
+ ret = hid_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_HID_STATE, &value);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", ufs_hid_state_to_string(value));
+}
+
+static DEVICE_ATTR_RO(state);
+
+static struct attribute *ufs_sysfs_hid[] = {
+ &dev_attr_analysis_trigger.attr,
+ &dev_attr_defrag_trigger.attr,
+ &dev_attr_fragmented_size.attr,
+ &dev_attr_defrag_size.attr,
+ &dev_attr_progress_ratio.attr,
+ &dev_attr_state.attr,
+ NULL,
+};
+
+static umode_t ufs_sysfs_hid_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return hba->dev_info.hid_sup ? attr->mode : 0;
+}
+
+static const struct attribute_group ufs_sysfs_hid_group = {
+ .name = "hid",
+ .attrs = ufs_sysfs_hid,
+ .is_visible = ufs_sysfs_hid_is_visible,
+};
+
+static const struct attribute_group *ufs_sysfs_groups[] = {
+ &ufs_sysfs_default_group,
+ &ufs_sysfs_capabilities_group,
+ &ufs_sysfs_ufshci_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,
+ &ufs_sysfs_health_descriptor_group,
+ &ufs_sysfs_power_descriptor_group,
+ &ufs_sysfs_string_descriptors_group,
+ &ufs_sysfs_flags_group,
+ &ufs_sysfs_attributes_group,
+ &ufs_sysfs_hid_group,
+ NULL,
+};
+
+#define UFS_LUN_DESC_PARAM(_pname, _puname, _duname, _size) \
+static ssize_t _pname##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct scsi_device *sdev = to_scsi_device(dev); \
+ struct ufs_hba *hba = shost_priv(sdev->host); \
+ u8 lun = ufshcd_scsi_to_upiu_lun(sdev->lun); \
+ if (!ufs_is_valid_unit_desc_lun(&hba->dev_info, lun)) \
+ return -EINVAL; \
+ return ufs_sysfs_read_desc_param(hba, QUERY_DESC_IDN_##_duname, \
+ lun, _duname##_DESC_PARAM##_puname, buf, _size); \
+} \
+static DEVICE_ATTR_RO(_pname)
+
+#define UFS_UNIT_DESC_PARAM(_name, _uname, _size) \
+ UFS_LUN_DESC_PARAM(_name, _uname, UNIT, _size)
+
+UFS_UNIT_DESC_PARAM(lu_enable, _LU_ENABLE, 1);
+UFS_UNIT_DESC_PARAM(boot_lun_id, _BOOT_LUN_ID, 1);
+UFS_UNIT_DESC_PARAM(lun_write_protect, _LU_WR_PROTECT, 1);
+UFS_UNIT_DESC_PARAM(lun_queue_depth, _LU_Q_DEPTH, 1);
+UFS_UNIT_DESC_PARAM(psa_sensitive, _PSA_SENSITIVE, 1);
+UFS_UNIT_DESC_PARAM(lun_memory_type, _MEM_TYPE, 1);
+UFS_UNIT_DESC_PARAM(data_reliability, _DATA_RELIABILITY, 1);
+UFS_UNIT_DESC_PARAM(logical_block_size, _LOGICAL_BLK_SIZE, 1);
+UFS_UNIT_DESC_PARAM(logical_block_count, _LOGICAL_BLK_COUNT, 8);
+UFS_UNIT_DESC_PARAM(erase_block_size, _ERASE_BLK_SIZE, 4);
+UFS_UNIT_DESC_PARAM(provisioning_type, _PROVISIONING_TYPE, 1);
+UFS_UNIT_DESC_PARAM(physical_memory_resource_count, _PHY_MEM_RSRC_CNT, 8);
+UFS_UNIT_DESC_PARAM(context_capabilities, _CTX_CAPABILITIES, 2);
+UFS_UNIT_DESC_PARAM(large_unit_granularity, _LARGE_UNIT_SIZE_M1, 1);
+UFS_UNIT_DESC_PARAM(wb_buf_alloc_units, _WB_BUF_ALLOC_UNITS, 4);
+
+static struct attribute *ufs_sysfs_unit_descriptor[] = {
+ &dev_attr_lu_enable.attr,
+ &dev_attr_boot_lun_id.attr,
+ &dev_attr_lun_write_protect.attr,
+ &dev_attr_lun_queue_depth.attr,
+ &dev_attr_psa_sensitive.attr,
+ &dev_attr_lun_memory_type.attr,
+ &dev_attr_data_reliability.attr,
+ &dev_attr_logical_block_size.attr,
+ &dev_attr_logical_block_count.attr,
+ &dev_attr_erase_block_size.attr,
+ &dev_attr_provisioning_type.attr,
+ &dev_attr_physical_memory_resource_count.attr,
+ &dev_attr_context_capabilities.attr,
+ &dev_attr_large_unit_granularity.attr,
+ &dev_attr_wb_buf_alloc_units.attr,
+ NULL,
+};
+
+static umode_t ufs_unit_descriptor_is_visible(struct kobject *kobj, struct attribute *attr, int n)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct scsi_device *sdev = to_scsi_device(dev);
+ u8 lun = ufshcd_scsi_to_upiu_lun(sdev->lun);
+ umode_t mode = attr->mode;
+
+ if (lun == UFS_UPIU_BOOT_WLUN || lun == UFS_UPIU_UFS_DEVICE_WLUN)
+ /* Boot and device WLUN have no unit descriptors */
+ mode = 0;
+ if (lun == UFS_UPIU_RPMB_WLUN && attr == &dev_attr_wb_buf_alloc_units.attr)
+ mode = 0;
+
+ return mode;
+}
+
+
+const struct attribute_group ufs_sysfs_unit_descriptor_group = {
+ .name = "unit_descriptor",
+ .attrs = ufs_sysfs_unit_descriptor,
+ .is_visible = ufs_unit_descriptor_is_visible,
+};
+
+static ssize_t dyn_cap_needed_attribute_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u32 value;
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba = shost_priv(sdev->host);
+ u8 lun = ufshcd_scsi_to_upiu_lun(sdev->lun);
+ int ret;
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ufshcd_rpm_get_sync(hba);
+ ret = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_DYN_CAP_NEEDED, lun, 0, &value);
+ ufshcd_rpm_put_sync(hba);
+ if (ret) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = sysfs_emit(buf, "0x%08X\n", value);
+
+out:
+ up(&hba->host_sem);
+ return ret;
+}
+static DEVICE_ATTR_RO(dyn_cap_needed_attribute);
+
+static struct attribute *ufs_sysfs_lun_attributes[] = {
+ &dev_attr_dyn_cap_needed_attribute.attr,
+ NULL,
+};
+
+const struct attribute_group ufs_sysfs_lun_attributes_group = {
+ .attrs = ufs_sysfs_lun_attributes,
+};
+
+void ufs_sysfs_add_nodes(struct device *dev)
+{
+ int ret;
+
+ ret = sysfs_create_groups(&dev->kobj, ufs_sysfs_groups);
+ if (ret)
+ dev_err(dev,
+ "%s: sysfs groups creation failed (err = %d)\n",
+ __func__, ret);
+}
+
+void ufs_sysfs_remove_nodes(struct device *dev)
+{
+ sysfs_remove_groups(&dev->kobj, ufs_sysfs_groups);
+}
diff --git a/drivers/ufs/core/ufs-sysfs.h b/drivers/ufs/core/ufs-sysfs.h
new file mode 100644
index 000000000..8d94af3b8
--- /dev/null
+++ b/drivers/ufs/core/ufs-sysfs.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2018 Western Digital Corporation
+ */
+
+#ifndef __UFS_SYSFS_H__
+#define __UFS_SYSFS_H__
+
+#include <linux/sysfs.h>
+
+struct device;
+
+void ufs_sysfs_add_nodes(struct device *dev);
+void ufs_sysfs_remove_nodes(struct device *dev);
+
+extern const struct attribute_group ufs_sysfs_unit_descriptor_group;
+extern const struct attribute_group ufs_sysfs_lun_attributes_group;
+
+#endif
diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c
new file mode 100644
index 000000000..6192512e2
--- /dev/null
+++ b/drivers/ufs/core/ufs-txeq.c
@@ -0,0 +1,1627 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Qualcomm Technologies, Inc.
+ *
+ * Author:
+ * Can Guo <can.guo@oss.qualcomm.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/sched/mm.h>
+#include <ufs/ufshcd.h>
+#include <ufs/unipro.h>
+#include "ufshcd-priv.h"
+
+#define TX_EQ_SETTING_MASK 0x7
+#define TX_EQ_SETTINGS_VALID_BIT BIT(15)
+
+static bool use_adaptive_txeq;
+module_param(use_adaptive_txeq, bool, 0644);
+MODULE_PARM_DESC(use_adaptive_txeq, "Find and apply optimal TX Equalization settings before changing Power Mode (default: false)");
+
+static int txeq_gear_set(const char *val, const struct kernel_param *kp)
+{
+ return param_set_uint_minmax(val, kp, UFS_HS_G1, UFS_HS_GEAR_MAX);
+}
+
+static const struct kernel_param_ops txeq_gear_ops = {
+ .set = txeq_gear_set,
+ .get = param_get_uint,
+};
+
+static unsigned int adaptive_txeq_gear = UFS_HS_G6;
+module_param_cb(adaptive_txeq_gear, &txeq_gear_ops, &adaptive_txeq_gear, 0644);
+MODULE_PARM_DESC(adaptive_txeq_gear, "For HS-Gear[n] and above, adaptive txeq shall be used");
+
+static bool use_txeq_presets;
+module_param(use_txeq_presets, bool, 0644);
+MODULE_PARM_DESC(use_txeq_presets, "Use only the 8 TX Equalization Presets (pre-defined Pre-Shoot & De-Emphasis combinations) for TX EQTR (default: false)");
+
+static bool txeq_presets_selected[UFS_TX_EQ_PRESET_MAX] = {[0 ... (UFS_TX_EQ_PRESET_MAX - 1)] = 1};
+module_param_array(txeq_presets_selected, bool, NULL, 0644);
+MODULE_PARM_DESC(txeq_presets_selected, "Use only the selected Presets out of the 8 TX Equalization Presets for TX EQTR");
+
+static int txeq_setting_sel_set(const char *val, const struct kernel_param *kp)
+{
+ return param_set_uint_minmax(val, kp, 0, 1);
+}
+
+static const struct kernel_param_ops txeq_setting_sel_ops = {
+ .set = txeq_setting_sel_set,
+ .get = param_get_uint,
+};
+
+static unsigned int txeq_setting_sel;
+module_param_cb(txeq_setting_sel, &txeq_setting_sel_ops, &txeq_setting_sel, 0644);
+MODULE_PARM_DESC(txeq_setting_sel, "The qTxEQGnSettings and wTxEQGnSettingsExt Attributes selector used to retrieve and store TX Equalization settings");
+
+static bool retrieve_txeq_setting = true;
+module_param(retrieve_txeq_setting, bool, 0644);
+MODULE_PARM_DESC(retrieve_txeq_setting, "Retrieve TX Equalization settings from qTxEQGnSettings and wTxEQGnSettingsExt Attributes (default: true)");
+
+static bool store_txeq_setting = true;
+module_param(store_txeq_setting, bool, 0644);
+MODULE_PARM_DESC(store_txeq_setting, "Store the optimal TX Equalization settings to qTxEQGnSettings and wTxEQGnSettingsExt Attributes (default: true)");
+
+/*
+ * ufs_tx_eq_preset - Table of minimum required list of presets.
+ *
+ * A HS-G6 capable M-TX shall support the presets defined in M-PHY v6.0 spec.
+ * Preset Pre-Shoot(dB) De-Emphasis(dB)
+ * P0 0.0 0.0
+ * P1 0.0 0.8
+ * P2 0.0 1.6
+ * P3 0.8 0.0
+ * P4 1.6 0.0
+ * P5 0.8 0.8
+ * P6 0.8 1.6
+ * P7 1.6 0.8
+ */
+static const struct __ufs_tx_eq_preset {
+ u8 preshoot;
+ u8 deemphasis;
+} ufs_tx_eq_preset[UFS_TX_EQ_PRESET_MAX] = {
+ [UFS_TX_EQ_PRESET_P0] = {UFS_TX_HS_PRESHOOT_DB_0P0, UFS_TX_HS_DEEMPHASIS_DB_0P0},
+ [UFS_TX_EQ_PRESET_P1] = {UFS_TX_HS_PRESHOOT_DB_0P0, UFS_TX_HS_DEEMPHASIS_DB_0P8},
+ [UFS_TX_EQ_PRESET_P2] = {UFS_TX_HS_PRESHOOT_DB_0P0, UFS_TX_HS_DEEMPHASIS_DB_1P6},
+ [UFS_TX_EQ_PRESET_P3] = {UFS_TX_HS_PRESHOOT_DB_0P8, UFS_TX_HS_DEEMPHASIS_DB_0P0},
+ [UFS_TX_EQ_PRESET_P4] = {UFS_TX_HS_PRESHOOT_DB_1P6, UFS_TX_HS_DEEMPHASIS_DB_0P0},
+ [UFS_TX_EQ_PRESET_P5] = {UFS_TX_HS_PRESHOOT_DB_0P8, UFS_TX_HS_DEEMPHASIS_DB_0P8},
+ [UFS_TX_EQ_PRESET_P6] = {UFS_TX_HS_PRESHOOT_DB_0P8, UFS_TX_HS_DEEMPHASIS_DB_1P6},
+ [UFS_TX_EQ_PRESET_P7] = {UFS_TX_HS_PRESHOOT_DB_1P6, UFS_TX_HS_DEEMPHASIS_DB_0P8},
+};
+
+/*
+ * pa_peer_rx_adapt_initial - Table of UniPro PA_PeerRxHSGnAdaptInitial
+ * attribute IDs for High Speed (HS) Gears.
+ *
+ * This table maps HS Gears to their respective UniPro PA_PeerRxHSGnAdaptInitial
+ * attribute IDs. Entries for Gears 1-3 are 0 (unsupported).
+ */
+static const u32 pa_peer_rx_adapt_initial[UFS_HS_GEAR_MAX] = {
+ 0,
+ 0,
+ 0,
+ PA_PEERRXHSG4ADAPTINITIAL,
+ PA_PEERRXHSG5ADAPTINITIAL,
+ PA_PEERRXHSG6ADAPTINITIALL0L3
+};
+
+/*
+ * rx_adapt_initial_cap - Table of M-PHY RX_HS_Gn_ADAPT_INITIAL_Capability
+ * attribute IDs for High Speed (HS) Gears.
+ *
+ * This table maps HS Gears to their respective M-PHY
+ * RX_HS_Gn_ADAPT_INITIAL_Capability attribute IDs. Entries for Gears 1-3 are 0
+ * (unsupported).
+ */
+static const u32 rx_adapt_initial_cap[UFS_HS_GEAR_MAX] = {
+ 0,
+ 0,
+ 0,
+ RX_HS_G4_ADAPT_INITIAL_CAP,
+ RX_HS_G5_ADAPT_INITIAL_CAP,
+ RX_HS_G6_ADAPT_INITIAL_CAP
+};
+
+/*
+ * pa_tx_eq_setting - Table of UniPro PA_TxEQGnSetting attribute IDs for High
+ * Speed (HS) Gears.
+ *
+ * This table maps HS Gears to their respective UniPro PA_TxEQGnSetting
+ * attribute IDs.
+ */
+static const u32 pa_tx_eq_setting[UFS_HS_GEAR_MAX] = {
+ PA_TXEQG1SETTING,
+ PA_TXEQG2SETTING,
+ PA_TXEQG3SETTING,
+ PA_TXEQG4SETTING,
+ PA_TXEQG5SETTING,
+ PA_TXEQG6SETTING
+};
+
+/*
+ * Decode Device TX Equalization PreShoot value based on qTxEQGnSettings bit assignment:
+ * bit[3:0]: Device TX Logical LANE 0 PreShoot
+ * bit[7:4]: Device TX Logical LANE 1 PreShoot
+ */
+static inline u8 tx_eq_device_preshoot_decode(u64 eq, u8 lane)
+{
+ return (u8)((eq >> (lane * TX_HS_PRESHOOT_SHIFT)) & TX_EQ_SETTING_MASK);
+}
+
+/*
+ * Decode Device TX Equalization DeEmphasis value based on qTxEQGnSettings bit assignment:
+ * bit[19:16]: Device TX Logical LANE 0 DeEmphasis
+ * bit[23:20]: Device TX Logical LANE 1 DeEmphasis
+ */
+static inline u8 tx_eq_device_deemphasis_decode(u64 eq, u8 lane)
+{
+ return (u8)((eq >> (lane * TX_HS_DEEMPHASIS_SHIFT + 16)) & TX_EQ_SETTING_MASK);
+}
+
+/*
+ * Decode Host TX Equalization PreShoot value based on qTxEQGnSettings bit assignment:
+ * bit[35:32]: Host TX Logical LANE 0 PreShoot
+ * bit[39:36]: Host TX Logical LANE 1 PreShoot
+ */
+static inline u8 tx_eq_host_preshoot_decode(u64 eq, u8 lane)
+{
+ return (u8)((eq >> (lane * TX_HS_PRESHOOT_SHIFT + 32)) & TX_EQ_SETTING_MASK);
+}
+
+/*
+ * Decode Host TX Equalization DeEmphasis value based on qTxEQGnSettings bit assignment:
+ * bit[51:48]: Host TX Logical LANE 0 DeEmphasis
+ * bit[55:52]: Host TX Logical LANE 1 DeEmphasis
+ */
+static inline u8 tx_eq_host_deemphasis_decode(u64 eq, u8 lane)
+{
+ return (u8)((eq >> (lane * TX_HS_DEEMPHASIS_SHIFT + 48)) & TX_EQ_SETTING_MASK);
+}
+
+/*
+ * Decode Device TX precode_en indication based on wTxEQGnSettingsExt bit assignment:
+ * bit[0]: PreCodeEn for Device TX Logical LANE 0
+ * bit[1]: PreCodeEn for Device TX Logical LANE 1
+ */
+static inline bool tx_eq_device_precode_en_decode(u16 eq_ext, u8 lane)
+{
+ return eq_ext & BIT(lane);
+}
+
+/*
+ * Decode Host TX precode_en indication based on wTxEQGnSettingsExt bit assignment:
+ * bit[4]: PreCodeEn for Device RX Logical LANE 0
+ * bit[5]: PreCodeEn for Device RX Logical LANE 1
+ */
+static inline bool tx_eq_host_precode_en_decode(u16 eq_ext, u8 lane)
+{
+ return eq_ext & BIT(lane + 4);
+}
+
+/*
+ * Encode Device TX Equalization PreShoot value based on qTxEQGnSettings bit assignment:
+ * bit[3:0]: Device TX Logical LANE 0 PreShoot
+ * bit[7:4]: Device TX Logical LANE 1 PreShoot
+ */
+static inline u64 tx_eq_device_preshoot_encode(u64 val, u8 lane)
+{
+ return (val & TX_EQ_SETTING_MASK) << (lane * TX_HS_PRESHOOT_SHIFT);
+}
+
+/*
+ * Encode Device TX Equalization DeEmphasis value based on qTxEQGnSettings bit assignment:
+ * bit[19:16]: Device TX Logical LANE 0 DeEmphasis
+ * bit[23:20]: Device TX Logical LANE 1 DeEmphasis
+ */
+static inline u64 tx_eq_device_deemphasis_encode(u64 val, u8 lane)
+{
+ return (val & TX_EQ_SETTING_MASK) << (lane * TX_HS_DEEMPHASIS_SHIFT + 16);
+}
+
+/*
+ * Encode Host TX Equalization PreShoot value based on qTxEQGnSettings bit assignment:
+ * bit[35:32]: Host TX Logical LANE 0 PreShoot
+ * bit[39:36]: Host TX Logical LANE 1 PreShoot
+ */
+static inline u64 tx_eq_host_preshoot_encode(u64 val, u8 lane)
+{
+ return (val & TX_EQ_SETTING_MASK) << (lane * TX_HS_PRESHOOT_SHIFT + 32);
+}
+
+/*
+ * Encode Host TX Equalization DeEmphasis value based on qTxEQGnSettings bit assignment:
+ * bit[51:48]: Host TX Logical LANE 0 DeEmphasis
+ * bit[55:52]: Host TX Logical LANE 1 DeEmphasis
+ */
+static inline u64 tx_eq_host_deemphasis_encode(u64 val, u8 lane)
+{
+ return (val & TX_EQ_SETTING_MASK) << (lane * TX_HS_DEEMPHASIS_SHIFT + 48);
+}
+
+/*
+ * Encode Device precode_en based on wTxEQGnSettingsExt bit assignment:
+ * bit[0]: PreCodeEn for Device TX Logical LANE 0
+ * bit[1]: PreCodeEn for Device TX Logical LANE 1
+ */
+static inline u16 tx_eq_device_precode_en_encode(bool en, u8 lane)
+{
+ return (u16)en << lane;
+}
+
+/*
+ * Encode Host precode_en based on wTxEQGnSettingsExt bit assignment:
+ * bit[4]: PreCodeEn for Device RX Logical LANE 0
+ * bit[5]: PreCodeEn for Device RX Logical LANE 1
+ */
+static inline u16 tx_eq_host_precode_en_encode(bool en, u8 lane)
+{
+ return (u16)en << (lane + 4);
+}
+
+/**
+ * ufshcd_configure_precoding - Configure Pre-Coding for all active lanes
+ * @hba: per adapter instance
+ * @params: TX EQ parameters data structure
+ *
+ * Bit[7] in RX_FOM indicates that the receiver needs to enable Pre-Coding when
+ * set. Pre-Coding must be enabled on both the transmitter and receiver to
+ * ensure proper operation.
+ *
+ * Returns 0 on success, non-zero error code otherwise
+ */
+static int ufshcd_configure_precoding(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params)
+{
+ struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
+ u32 local_precode_en = 0;
+ u32 peer_precode_en = 0;
+ int lane, ret;
+
+ /* Enable Pre-Coding for Host's TX & Device's RX pair */
+ for (lane = 0; lane < pwr_info->lane_tx; lane++) {
+ if (params->host[lane].precode_en) {
+ local_precode_en |= PRECODEEN_TX_BIT(lane);
+ peer_precode_en |= PRECODEEN_RX_BIT(lane);
+ }
+ }
+
+ /* Enable Pre-Coding for Device's TX & Host's RX pair */
+ for (lane = 0; lane < pwr_info->lane_rx; lane++) {
+ if (params->device[lane].precode_en) {
+ peer_precode_en |= PRECODEEN_TX_BIT(lane);
+ local_precode_en |= PRECODEEN_RX_BIT(lane);
+ }
+ }
+
+ if (!local_precode_en && !peer_precode_en) {
+ dev_dbg(hba->dev, "Pre-Coding is not required for Host and Device\n");
+ return 0;
+ }
+
+ /* Set local PA_PreCodeEn */
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PRECODEEN), local_precode_en);
+ if (ret) {
+ dev_err(hba->dev, "Failed to set local PA_PreCodeEn: %d\n", ret);
+ return ret;
+ }
+
+ /* Set peer PA_PreCodeEn */
+ ret = ufshcd_dme_peer_set(hba, UIC_ARG_MIB(PA_PRECODEEN), peer_precode_en);
+ if (ret) {
+ dev_err(hba->dev, "Failed to set peer PA_PreCodeEn: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(hba->dev, "Local PA_PreCodeEn: 0x%02x, Peer PA_PreCodeEn: 0x%02x\n",
+ local_precode_en, peer_precode_en);
+
+ return 0;
+}
+
+void ufshcd_print_tx_eq_params(struct ufs_hba *hba)
+{
+ struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
+ struct ufshcd_tx_eq_params *params;
+ u32 gear = hba->pwr_info.gear_tx;
+ int lane;
+
+ if (!ufshcd_is_tx_eq_supported(hba))
+ return;
+
+ if (gear < UFS_HS_G1 || gear > UFS_HS_GEAR_MAX)
+ return;
+
+ params = &hba->tx_eq_params[gear - 1];
+ if (!params->is_valid || !params->is_applied)
+ return;
+
+ for (lane = 0; lane < pwr_info->lane_tx; lane++)
+ dev_dbg(hba->dev, "Host TX Lane %d: PreShoot %u, DeEmphasis %u, FOM %u, PreCodeEn %d\n",
+ lane, params->host[lane].preshoot,
+ params->host[lane].deemphasis,
+ params->host[lane].fom_val,
+ params->host[lane].precode_en);
+
+ for (lane = 0; lane < pwr_info->lane_rx; lane++)
+ dev_dbg(hba->dev, "Device TX Lane %d: PreShoot %u, DeEmphasis %u, FOM %u, PreCodeEn %d\n",
+ lane, params->device[lane].preshoot,
+ params->device[lane].deemphasis,
+ params->device[lane].fom_val,
+ params->device[lane].precode_en);
+}
+
+static inline u32
+ufshcd_compose_tx_eq_setting(struct ufshcd_tx_eq_settings *settings,
+ int num_lanes)
+{
+ u32 setting = 0;
+ int lane;
+
+ for (lane = 0; lane < num_lanes; lane++, settings++) {
+ setting |= TX_HS_PRESHOOT_BITS(lane, settings->preshoot);
+ setting |= TX_HS_DEEMPHASIS_BITS(lane, settings->deemphasis);
+ }
+
+ return setting;
+}
+
+/**
+ * ufshcd_apply_tx_eq_settings - Apply TX Equalization settings for target gear
+ * @hba: per adapter instance
+ * @params: TX EQ parameters data structure
+ * @gear: target gear
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params, u32 gear)
+{
+ struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
+ u32 setting;
+ int ret;
+
+ /* Compose settings for Host's TX Lanes */
+ setting = ufshcd_compose_tx_eq_setting(params->host, pwr_info->lane_tx);
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(pa_tx_eq_setting[gear - 1]), setting);
+ if (ret)
+ return ret;
+
+ /* Compose settings for Device's TX Lanes */
+ setting = ufshcd_compose_tx_eq_setting(params->device, pwr_info->lane_rx);
+ ret = ufshcd_dme_peer_set(hba, UIC_ARG_MIB(pa_tx_eq_setting[gear - 1]), setting);
+ if (ret)
+ return ret;
+
+ /* Configure Pre-Coding */
+ if (gear >= UFS_HS_G6) {
+ ret = ufshcd_configure_precoding(hba, params);
+ if (ret) {
+ dev_err(hba->dev, "Failed to configure pre-coding: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ufshcd_apply_tx_eq_settings);
+
+/**
+ * ufshcd_evaluate_tx_eqtr_fom - Evaluate TX EQTR FOM results
+ * @hba: per adapter instance
+ * @pwr_mode: target power mode containing gear and rate information
+ * @eqtr_data: TX EQTR data structure
+ * @h_iter: host TX EQTR iterator data structure
+ * @d_iter: device TX EQTR iterator data structure
+ *
+ * Evaluate TX EQTR FOM results, update host and device TX EQTR data accordingy
+ * if FOM have been improved compared to previous iteration, and record TX EQTR
+ * FOM results.
+ */
+static void ufshcd_evaluate_tx_eqtr_fom(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct ufshcd_tx_eqtr_data *eqtr_data,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ u8 preshoot, deemphasis, fom_value;
+ bool precode_en;
+ int lane;
+
+ for (lane = 0; h_iter->is_updated && lane < pwr_mode->lane_tx; lane++) {
+ preshoot = h_iter->preshoot;
+ deemphasis = h_iter->deemphasis;
+ fom_value = h_iter->fom[lane] & RX_FOM_VALUE_MASK;
+ precode_en = h_iter->fom[lane] & RX_FOM_PRECODING_EN_BIT;
+
+ /* Record host TX EQTR FOM */
+ eqtr_data->host_fom[lane][preshoot][deemphasis] = h_iter->fom[lane];
+
+ /* Check if FOM has been improved for host's TX Lanes */
+ if (fom_value > eqtr_data->host[lane].fom_val) {
+ eqtr_data->host[lane].preshoot = preshoot;
+ eqtr_data->host[lane].deemphasis = deemphasis;
+ eqtr_data->host[lane].fom_val = fom_value;
+ eqtr_data->host[lane].precode_en = precode_en;
+ }
+
+ dev_dbg(hba->dev, "TX EQTR: Host TX Lane %d: PreShoot %u, DeEmphasis %u, FOM value %u, PreCodeEn %d\n",
+ lane, preshoot, deemphasis, fom_value, precode_en);
+ }
+
+ for (lane = 0; d_iter->is_updated && lane < pwr_mode->lane_rx; lane++) {
+ preshoot = d_iter->preshoot;
+ deemphasis = d_iter->deemphasis;
+ fom_value = d_iter->fom[lane] & RX_FOM_VALUE_MASK;
+ precode_en = d_iter->fom[lane] & RX_FOM_PRECODING_EN_BIT;
+
+ /* Record device TX EQTR FOM */
+ eqtr_data->device_fom[lane][preshoot][deemphasis] = d_iter->fom[lane];
+
+ /* Check if FOM has been improved for Device's TX Lanes */
+ if (fom_value > eqtr_data->device[lane].fom_val) {
+ eqtr_data->device[lane].preshoot = preshoot;
+ eqtr_data->device[lane].deemphasis = deemphasis;
+ eqtr_data->device[lane].fom_val = fom_value;
+ eqtr_data->device[lane].precode_en = precode_en;
+ }
+
+ dev_dbg(hba->dev, "TX EQTR: Device TX Lane %d: PreShoot %u, DeEmphasis %u, FOM value %u, PreCodeEn %d\n",
+ lane, preshoot, deemphasis, fom_value, precode_en);
+ }
+}
+
+/**
+ * ufshcd_get_rx_fom - Get Figure of Merit (FOM) for both sides
+ * @hba: per adapter instance
+ * @pwr_mode: target power mode containing gear and rate information
+ * @h_iter: host TX EQTR iterator data structure
+ * @d_iter: device TX EQTR iterator data structure
+ *
+ * Returns 0 on success, negative error code if get_rx_fom vops fails.
+ * RX_FOM DME get failures are logged and treated as 0 FOM for that lane.
+ */
+static int ufshcd_get_rx_fom(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ int lane, ret;
+ u32 fom;
+
+ /* Get FOM of host's TX lanes from device's RX_FOM. */
+ for (lane = 0; lane < pwr_mode->lane_tx; lane++) {
+ ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB_SEL(RX_FOM,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ &fom);
+ if (ret) {
+ h_iter->fom[lane] = 0;
+ dev_dbg(hba->dev, "Failed to get FOM for Host TX Lane %d: %d\n",
+ lane, ret);
+ continue;
+ }
+
+ h_iter->fom[lane] = (u8)fom;
+ }
+
+ /* Get FOM of device's TX lanes from host's RX_FOM. */
+ for (lane = 0; lane < pwr_mode->lane_rx; lane++) {
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_FOM,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ &fom);
+ if (ret) {
+ d_iter->fom[lane] = 0;
+ dev_dbg(hba->dev, "Failed to get FOM for Device TX Lane %d: %d\n",
+ lane, ret);
+ continue;
+ }
+
+ d_iter->fom[lane] = (u8)fom;
+ }
+
+ ret = ufshcd_vops_get_rx_fom(hba, pwr_mode, h_iter, d_iter);
+ if (ret)
+ dev_err(hba->dev, "Failed to get FOM via vops: %d\n", ret);
+
+ return ret;
+}
+
+bool ufshcd_is_txeq_presets_used(struct ufs_hba *hba)
+{
+ return use_txeq_presets;
+}
+
+bool ufshcd_is_txeq_preset_selected(u8 preshoot, u8 deemphasis)
+{
+ int i;
+
+ for (i = 0; i < UFS_TX_EQ_PRESET_MAX; i++) {
+ if (!txeq_presets_selected[i])
+ continue;
+
+ if (preshoot == ufs_tx_eq_preset[i].preshoot &&
+ deemphasis == ufs_tx_eq_preset[i].deemphasis)
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * tx_eqtr_iter_try_update - Try to update a TX EQTR iterator
+ * @iter: TX EQTR iterator data structure
+ * @preshoot: PreShoot value
+ * @deemphasis: DeEmphasis value
+ *
+ * This function validates whether the provided PreShoot and DeEmphasis
+ * combination can be used or not. If yes, it updates the TX EQTR iterator with
+ * the provided PreShoot and DeEmphasis, it also sets the is_updated flag
+ * to indicate the iterator has been updated.
+ */
+static void tx_eqtr_iter_try_update(struct tx_eqtr_iter *iter,
+ u8 preshoot, u8 deemphasis)
+{
+ if (!test_bit(preshoot, &iter->preshoot_bitmap) ||
+ !test_bit(deemphasis, &iter->deemphasis_bitmap) ||
+ (use_txeq_presets && !ufshcd_is_txeq_preset_selected(preshoot, deemphasis))) {
+ iter->is_updated = false;
+ return;
+ }
+
+ iter->preshoot = preshoot;
+ iter->deemphasis = deemphasis;
+ iter->is_updated = true;
+}
+
+/**
+ * tx_eqtr_iter_update() - Update host and deviceTX EQTR iterators
+ * @preshoot: PreShoot value
+ * @deemphasis: DeEmphasis value
+ * @h_iter: Host TX EQTR iterator data structure
+ * @d_iter: Device TX EQTR iterator data structure
+ *
+ * Updates host and device TX Equalization training iterators with the
+ * provided PreShoot and DeEmphasis.
+ *
+ * Return: true if host and/or device TX Equalization training iterator has
+ * been updated to the provided PreShoot and DeEmphasis, false otherwise.
+ */
+static bool tx_eqtr_iter_update(u8 preshoot, u8 deemphasis,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ tx_eqtr_iter_try_update(h_iter, preshoot, deemphasis);
+ tx_eqtr_iter_try_update(d_iter, preshoot, deemphasis);
+
+ return h_iter->is_updated || d_iter->is_updated;
+}
+
+/**
+ * ufshcd_tx_eqtr_iter_init - Initialize host and device TX EQTR iterators
+ * @hba: per adapter instance
+ * @h_iter: host TX EQTR iterator data structure
+ * @d_iter: device TX EQTR iterator data structure
+ *
+ * This function initializes the TX EQTR iterator structures for both host and
+ * device by reading their TX equalization capabilities. The capabilities are
+ * cached in the hba structure to avoid redundant DME operations in subsequent
+ * calls. In the TX EQTR procedure, the iterator structures are updated by
+ * tx_eqtr_iter_update() to systematically iterate through supported TX
+ * Equalization setting combinations.
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+static int ufshcd_tx_eqtr_iter_init(struct ufs_hba *hba,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ u32 cap;
+ int ret;
+
+ if (!hba->host_preshoot_cap) {
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(TX_HS_PRESHOOT_SETTING_CAP), &cap);
+ if (ret)
+ return ret;
+
+ hba->host_preshoot_cap = cap & TX_EQTR_CAP_MASK;
+ }
+
+ if (!hba->host_deemphasis_cap) {
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(TX_HS_DEEMPHASIS_SETTING_CAP), &cap);
+ if (ret)
+ return ret;
+
+ hba->host_deemphasis_cap = cap & TX_EQTR_CAP_MASK;
+ }
+
+ if (!hba->device_preshoot_cap) {
+ ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(TX_HS_PRESHOOT_SETTING_CAP), &cap);
+ if (ret)
+ return ret;
+
+ hba->device_preshoot_cap = cap & TX_EQTR_CAP_MASK;
+ }
+
+ if (!hba->device_deemphasis_cap) {
+ ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(TX_HS_DEEMPHASIS_SETTING_CAP), &cap);
+ if (ret)
+ return ret;
+
+ hba->device_deemphasis_cap = cap & TX_EQTR_CAP_MASK;
+ }
+
+ /*
+ * Support PreShoot & DeEmphasis of value 0 is mandatory, hence they are
+ * not reflected in PreShoot/DeEmphasis capabilities. Left shift the
+ * capability bitmap by 1 and set bit[0] to reflect value 0 is
+ * supported, such that test_bit() can be used later for convenience.
+ */
+ h_iter->preshoot_bitmap = (hba->host_preshoot_cap << 0x1) | 0x1;
+ h_iter->deemphasis_bitmap = (hba->host_deemphasis_cap << 0x1) | 0x1;
+ d_iter->preshoot_bitmap = (hba->device_preshoot_cap << 0x1) | 0x1;
+ d_iter->deemphasis_bitmap = (hba->device_deemphasis_cap << 0x1) | 0x1;
+
+ return 0;
+}
+
+/**
+ * adapt_cap_to_t_adapt - Calculate TAdapt from adapt capability
+ * @adapt_cap: Adapt capability
+ *
+ * For NRZ:
+ * IF (ADAPT_range = FINE)
+ * TADAPT = 650 x (ADAPT_length + 1)
+ * ELSE (IF ADAPT_range = COARSE)
+ * TADAPT = 650 x 2^ADAPT_length
+ *
+ * Returns calculated TAdapt value in term of Unit Intervals (UI)
+ */
+static inline u64 adapt_cap_to_t_adapt(u32 adapt_cap)
+{
+ u64 tadapt;
+ u8 adapt_length = adapt_cap & ADAPT_LENGTH_MASK;
+
+ if (!IS_ADAPT_RANGE_COARSE(adapt_cap))
+ tadapt = TADAPT_FACTOR * (adapt_length + 1);
+ else
+ tadapt = TADAPT_FACTOR * (1 << adapt_length);
+
+ return tadapt;
+}
+
+/**
+ * adapt_cap_to_t_adapt_l0l3 - Calculate TAdapt_L0_L3 from adapt capability
+ * @adapt_cap: Adapt capability
+ *
+ * For PAM-4:
+ * IF (ADAPT_range = FINE)
+ * TADAPT_L0_L3 = 2^9 x ADAPT_length
+ * ELSE IF (ADAPT_range = COARSE)
+ * TADAPT_L0_L3 = 2^9 x (2^ADAPT_length)
+ *
+ * Returns calculated TAdapt value in term of Unit Intervals (UI)
+ */
+static inline u64 adapt_cap_to_t_adapt_l0l3(u32 adapt_cap)
+{
+ u64 tadapt;
+ u8 adapt_length = adapt_cap & ADAPT_LENGTH_MASK;
+
+ if (!IS_ADAPT_RANGE_COARSE(adapt_cap))
+ tadapt = TADAPT_L0L3_FACTOR * adapt_length;
+ else
+ tadapt = TADAPT_L0L3_FACTOR * (1 << adapt_length);
+
+ return tadapt;
+}
+
+/**
+ * adapt_cap_to_t_adapt_l0l1l2l3 - Calculate TAdapt_L0_L1_L2_L3 from adapt capability
+ * @adapt_cap: Adapt capability
+ *
+ * For PAM-4:
+ * IF (ADAPT_range_L0_L1_L2_L3 = FINE)
+ * TADAPT_L0_L1_L2_L3 = 2^15 x (ADAPT_length_L0_L1_L2_L3 + 1)
+ * ELSE IF (ADAPT_range_L0_L1_L2_L3 = COARSE)
+ * TADAPT_L0_L1_L2_L3 = 2^15 x 2^ADAPT_length_L0_L1_L2_L3
+ *
+ * Returns calculated TAdapt value in term of Unit Intervals (UI)
+ */
+static inline u64 adapt_cap_to_t_adapt_l0l1l2l3(u32 adapt_cap)
+{
+ u64 tadapt;
+ u8 adapt_length = adapt_cap & ADAPT_LENGTH_MASK;
+
+ if (!IS_ADAPT_RANGE_COARSE(adapt_cap))
+ tadapt = TADAPT_L0L1L2L3_FACTOR * (adapt_length + 1);
+ else
+ tadapt = TADAPT_L0L1L2L3_FACTOR * (1 << adapt_length);
+
+ return tadapt;
+}
+
+/**
+ * ufshcd_setup_tx_eqtr_adapt_length - Setup TX adapt length for EQTR
+ * @hba: per adapter instance
+ * @params: TX EQ parameters data structure
+ * @gear: target gear for EQTR
+ *
+ * This function determines and configures the proper TX adapt length (TAdapt)
+ * for the TX EQTR procedure based on the target gear and RX adapt capabilities
+ * of both host and device.
+ *
+ * Guidelines from MIPI UniPro v3.0 spec - select the minimum Adapt Length for
+ * the Equalization Training procedure based on the following conditions:
+ *
+ * If the target High-Speed Gear n is HS-G4 or HS-G5:
+ * PA_TxAdaptLength_EQTR[7:0] >= Max (10us, RX_HS_Gn_ADAPT_INITIAL_Capability,
+ * PA_PeerRxHsGnAdaptInitial)
+ * PA_TxAdaptLength_EQTR[7:0] shall be shorter than PACP_REQUEST_TIMER (10ms)
+ * PA_TxAdaptLength_EQTR[15:8] is not relevant for HS-G4 and HS-G5. This field
+ * is set to 255 (reserved value).
+ *
+ * If the target High-Speed Gear n is HS-G6:
+ * PA_TxAdapthLength_EQTR >= 10us
+ * PA_TxAdapthLength_EQTR[7:0] >= Max (RX_HS_G6_ADAPT_INITIAL_Capability,
+ * PA_PeerRxHsG6AdaptInitialL0L3)
+ * PA_TxAdapthLength_EQTR[15:8] >= Max (RX_HS_G6_ADAPT_INITIAL_L0_L1_L2_L3_Capability,
+ * PA_PeerRxHsG6AdaptInitialL0L1L2L3)
+ * PA_TxAdaptLength_EQTR shall be shorter than PACP_REQUEST_TIMER value of 10ms.
+ *
+ * Since adapt capabilities encode both range (fine/coarse) and length values,
+ * direct comparison is not possible. This function converts adapt capabilities
+ * to actual time durations in Unit Intervals (UI) using the Adapt time
+ * calculation formular in M-PHY v6.0 spec (Table 8), then selects the maximum
+ * to ensure both host and device use adequate TX adapt length.
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+static int ufshcd_setup_tx_eqtr_adapt_length(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params,
+ u32 gear)
+{
+ struct ufshcd_tx_eqtr_record *rec = params->eqtr_record;
+ u32 adapt_eqtr;
+ int ret;
+
+ if (rec && rec->saved_adapt_eqtr) {
+ adapt_eqtr = rec->saved_adapt_eqtr;
+ goto set_adapt_eqtr;
+ }
+
+ if (gear == UFS_HS_G4 || gear == UFS_HS_G5) {
+ u64 t_adapt, t_adapt_local, t_adapt_peer;
+ u32 adapt_cap_local, adapt_cap_peer, adapt_length;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(rx_adapt_initial_cap[gear - 1],
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
+ &adapt_cap_local);
+ if (ret)
+ return ret;
+
+ if (adapt_cap_local > ADAPT_LENGTH_MAX) {
+ dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP (0x%x) exceeds MAX\n",
+ gear, adapt_cap_local);
+ return -EINVAL;
+ }
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(pa_peer_rx_adapt_initial[gear - 1]),
+ &adapt_cap_peer);
+ if (ret)
+ return ret;
+
+ if (adapt_cap_peer > ADAPT_LENGTH_MAX) {
+ dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP (0x%x) exceeds MAX\n",
+ gear, adapt_cap_peer);
+ return -EINVAL;
+ }
+
+ t_adapt_local = adapt_cap_to_t_adapt(adapt_cap_local);
+ t_adapt_peer = adapt_cap_to_t_adapt(adapt_cap_peer);
+ t_adapt = max(t_adapt_local, t_adapt_peer);
+
+ dev_dbg(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP = 0x%x\n",
+ gear, adapt_cap_local);
+ dev_dbg(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_CAP = 0x%x\n",
+ gear, adapt_cap_peer);
+ dev_dbg(hba->dev, "t_adapt_local = %llu UI, t_adapt_peer = %llu UI\n",
+ t_adapt_local, t_adapt_peer);
+ dev_dbg(hba->dev, "TAdapt %llu UI selected for TX EQTR\n",
+ t_adapt);
+
+ adapt_length = (t_adapt_local >= t_adapt_peer) ?
+ adapt_cap_local : adapt_cap_peer;
+
+ if (gear == UFS_HS_G4 && t_adapt < TX_EQTR_HS_G4_MIN_T_ADAPT) {
+ dev_dbg(hba->dev, "TAdapt %llu UI is too short for TX EQTR for HS-G%u, use default Adapt 0x%x\n",
+ t_adapt, gear, TX_EQTR_HS_G4_ADAPT_DEFAULT);
+ adapt_length = TX_EQTR_HS_G4_ADAPT_DEFAULT;
+ } else if (gear == UFS_HS_G5 && t_adapt < TX_EQTR_HS_G5_MIN_T_ADAPT) {
+ dev_dbg(hba->dev, "TAdapt %llu UI is too short for TX EQTR for HS-G%u, use default Adapt 0x%x\n",
+ t_adapt, gear, TX_EQTR_HS_G5_ADAPT_DEFAULT);
+ adapt_length = TX_EQTR_HS_G5_ADAPT_DEFAULT;
+ }
+
+ adapt_eqtr = adapt_length |
+ (TX_EQTR_ADAPT_RESERVED << TX_EQTR_ADAPT_LENGTH_L0L1L2L3_SHIFT);
+ } else if (gear == UFS_HS_G6) {
+ u64 t_adapt, t_adapt_l0l3, t_adapt_l0l3_local, t_adapt_l0l3_peer;
+ u64 t_adapt_l0l1l2l3, t_adapt_l0l1l2l3_local, t_adapt_l0l1l2l3_peer;
+ u32 adapt_l0l3_cap_local, adapt_l0l3_cap_peer, adapt_length_l0l3;
+ u32 adapt_l0l1l2l3_cap_local, adapt_l0l1l2l3_cap_peer, adapt_length_l0l1l2l3;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(rx_adapt_initial_cap[gear - 1],
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
+ &adapt_l0l3_cap_local);
+ if (ret)
+ return ret;
+
+ if (adapt_l0l3_cap_local > ADAPT_L0L3_LENGTH_MAX) {
+ dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP (0x%x) exceeds MAX\n",
+ gear, adapt_l0l3_cap_local);
+ return -EINVAL;
+ }
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(pa_peer_rx_adapt_initial[gear - 1]),
+ &adapt_l0l3_cap_peer);
+ if (ret)
+ return ret;
+
+ if (adapt_l0l3_cap_peer > ADAPT_L0L3_LENGTH_MAX) {
+ dev_err(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_CAP (0x%x) exceeds MAX\n",
+ gear, adapt_l0l3_cap_peer);
+ return -EINVAL;
+ }
+
+ t_adapt_l0l3_local = adapt_cap_to_t_adapt_l0l3(adapt_l0l3_cap_local);
+ t_adapt_l0l3_peer = adapt_cap_to_t_adapt_l0l3(adapt_l0l3_cap_peer);
+
+ dev_dbg(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP = 0x%x\n",
+ gear, adapt_l0l3_cap_local);
+ dev_dbg(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_CAP = 0x%x\n",
+ gear, adapt_l0l3_cap_peer);
+ dev_dbg(hba->dev, "t_adapt_l0l3_local = %llu UI, t_adapt_l0l3_peer = %llu UI\n",
+ t_adapt_l0l3_local, t_adapt_l0l3_peer);
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_HS_G6_ADAPT_INITIAL_L0L1L2L3_CAP,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
+ &adapt_l0l1l2l3_cap_local);
+ if (ret)
+ return ret;
+
+ if (adapt_l0l1l2l3_cap_local > ADAPT_L0L1L2L3_LENGTH_MAX) {
+ dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP (0x%x) exceeds MAX\n",
+ gear, adapt_l0l1l2l3_cap_local);
+
+ if (!(hba->quirks & UFSHCD_QUIRK_EXTENDED_TX_EQTR_ADAPT_LENGTH_L0L1L2L3))
+ return -EINVAL;
+ }
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3),
+ &adapt_l0l1l2l3_cap_peer);
+ if (ret)
+ return ret;
+
+ if (adapt_l0l1l2l3_cap_peer > ADAPT_L0L1L2L3_LENGTH_MAX) {
+ dev_err(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP (0x%x) exceeds MAX\n",
+ gear, adapt_l0l1l2l3_cap_peer);
+
+ if (!(hba->quirks & UFSHCD_QUIRK_EXTENDED_TX_EQTR_ADAPT_LENGTH_L0L1L2L3))
+ return -EINVAL;
+ }
+
+ t_adapt_l0l1l2l3_local = adapt_cap_to_t_adapt_l0l1l2l3(adapt_l0l1l2l3_cap_local);
+ t_adapt_l0l1l2l3_peer = adapt_cap_to_t_adapt_l0l1l2l3(adapt_l0l1l2l3_cap_peer);
+
+ dev_dbg(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP = 0x%x\n",
+ gear, adapt_l0l1l2l3_cap_local);
+ dev_dbg(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP = 0x%x\n",
+ gear, adapt_l0l1l2l3_cap_peer);
+ dev_dbg(hba->dev, "t_adapt_l0l1l2l3_local = %llu UI, t_adapt_l0l1l2l3_peer = %llu UI\n",
+ t_adapt_l0l1l2l3_local, t_adapt_l0l1l2l3_peer);
+
+ t_adapt_l0l1l2l3 = max(t_adapt_l0l1l2l3_local, t_adapt_l0l1l2l3_peer);
+ t_adapt_l0l3 = max(t_adapt_l0l3_local, t_adapt_l0l3_peer);
+ t_adapt = t_adapt_l0l3 + t_adapt_l0l1l2l3;
+
+ dev_dbg(hba->dev, "TAdapt %llu PAM-4 UI selected for TX EQTR\n",
+ t_adapt);
+
+ adapt_length_l0l3 = (t_adapt_l0l3_local >= t_adapt_l0l3_peer) ?
+ adapt_l0l3_cap_local : adapt_l0l3_cap_peer;
+ adapt_length_l0l1l2l3 = (t_adapt_l0l1l2l3_local >= t_adapt_l0l1l2l3_peer) ?
+ adapt_l0l1l2l3_cap_local : adapt_l0l1l2l3_cap_peer;
+
+ if (t_adapt < TX_EQTR_HS_G6_MIN_T_ADAPT) {
+ dev_dbg(hba->dev, "TAdapt %llu UI is too short for TX EQTR for HS-G%u, use default Adapt 0x%x\n",
+ t_adapt, gear, TX_EQTR_HS_G6_ADAPT_DEFAULT);
+ adapt_length_l0l3 = TX_EQTR_HS_G6_ADAPT_DEFAULT;
+ }
+
+ adapt_eqtr = adapt_length_l0l3 |
+ (adapt_length_l0l1l2l3 << TX_EQTR_ADAPT_LENGTH_L0L1L2L3_SHIFT);
+ } else {
+ return -EINVAL;
+ }
+
+ if (rec)
+ rec->saved_adapt_eqtr = (u16)adapt_eqtr;
+
+set_adapt_eqtr:
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXADAPTLENGTH_EQTR), adapt_eqtr);
+ if (ret)
+ dev_err(hba->dev, "Failed to set adapt length for TX EQTR: %d\n", ret);
+ else
+ dev_dbg(hba->dev, "PA_TXADAPTLENGTH_EQTR configured to 0x%08x\n", adapt_eqtr);
+
+ return ret;
+}
+
+/**
+ * ufshcd_compose_tx_eqtr_setting - Compose TX EQTR setting
+ * @iter: TX EQTR iterator data structure
+ * @num_lanes: number of active lanes
+ *
+ * Returns composed TX EQTR setting, same setting is used for all active lanes
+ */
+static inline u32 ufshcd_compose_tx_eqtr_setting(struct tx_eqtr_iter *iter,
+ int num_lanes)
+{
+ u32 setting = 0;
+ int lane;
+
+ for (lane = 0; lane < num_lanes; lane++) {
+ setting |= TX_HS_PRESHOOT_BITS(lane, iter->preshoot);
+ setting |= TX_HS_DEEMPHASIS_BITS(lane, iter->deemphasis);
+ }
+
+ return setting;
+}
+
+/**
+ * ufshcd_apply_tx_eqtr_settings - Apply TX EQTR setting
+ * @hba: per adapter instance
+ * @pwr_mode: target power mode containing gear and rate information
+ * @h_iter: host TX EQTR iterator data structure
+ * @d_iter: device TX EQTR iterator data structure
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+static int ufshcd_apply_tx_eqtr_settings(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ u32 setting;
+ int ret;
+
+ setting = ufshcd_compose_tx_eqtr_setting(h_iter, pwr_mode->lane_tx);
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXEQTRSETTING), setting);
+ if (ret)
+ return ret;
+
+ setting = ufshcd_compose_tx_eqtr_setting(d_iter, pwr_mode->lane_rx);
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PEERTXEQTRSETTING), setting);
+ if (ret)
+ return ret;
+
+ ret = ufshcd_vops_apply_tx_eqtr_settings(hba, pwr_mode, h_iter, d_iter);
+
+ return ret;
+}
+
+/**
+ * ufshcd_update_tx_eq_params - Update TX Equalization params
+ * @params: TX EQ parameters data structure
+ * @pwr_mode: target power mode containing gear and rate
+ * @eqtr_data: TX EQTR data structure
+ *
+ * Update TX Equalization params using results from TX EQTR data. Check also
+ * the TX EQTR FOM value for each TX lane in the TX EQTR data. If a TX lane got
+ * a FOM value of 0, restore the TX Equalization settings from the last known
+ * valid TX Equalization params for that specific TX lane.
+ */
+static inline void
+ufshcd_update_tx_eq_params(struct ufshcd_tx_eq_params *params,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct ufshcd_tx_eqtr_data *eqtr_data)
+{
+ struct ufshcd_tx_eqtr_record *rec = params->eqtr_record;
+
+ if (params->is_valid) {
+ int lane;
+
+ for (lane = 0; lane < pwr_mode->lane_tx; lane++)
+ if (eqtr_data->host[lane].fom_val == 0)
+ eqtr_data->host[lane] = params->host[lane];
+
+ for (lane = 0; lane < pwr_mode->lane_rx; lane++)
+ if (eqtr_data->device[lane].fom_val == 0)
+ eqtr_data->device[lane] = params->device[lane];
+ }
+
+ memcpy(params->host, eqtr_data->host, sizeof(params->host));
+ memcpy(params->device, eqtr_data->device, sizeof(params->device));
+
+ if (!rec)
+ return;
+
+ memcpy(rec->host_fom, eqtr_data->host_fom, sizeof(rec->host_fom));
+ memcpy(rec->device_fom, eqtr_data->device_fom, sizeof(rec->device_fom));
+ rec->last_record_ts = ktime_get();
+ rec->last_record_index++;
+}
+
+/**
+ * __ufshcd_tx_eqtr - TX Equalization Training (EQTR) procedure
+ * @hba: per adapter instance
+ * @params: TX EQ parameters data structure
+ * @pwr_mode: target power mode containing gear and rate information
+ *
+ * This function implements the complete TX EQTR procedure as defined in UFSHCI
+ * v5.0 specification. It iterates through all possible combinations of PreShoot
+ * and DeEmphasis settings to find the optimal TX Equalization settings for all
+ * active lanes.
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+static int __ufshcd_tx_eqtr(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ struct ufshcd_tx_eqtr_data *eqtr_data __free(kfree) =
+ kzalloc_obj(*eqtr_data);
+ struct tx_eqtr_iter h_iter = {};
+ struct tx_eqtr_iter d_iter = {};
+ u32 gear = pwr_mode->gear_tx;
+ u8 preshoot, deemphasis;
+ ktime_t start;
+ int ret;
+
+ if (!eqtr_data)
+ return -ENOMEM;
+
+ dev_info(hba->dev, "Start TX EQTR procedure for HS-G%u, Rate-%s, RX Lanes: %u, TX Lanes: %u\n",
+ gear, ufs_hs_rate_to_str(pwr_mode->hs_rate),
+ pwr_mode->lane_rx, pwr_mode->lane_tx);
+
+ start = ktime_get();
+
+ /* Step 1 - Determine the TX Adapt Length for EQTR */
+ ret = ufshcd_setup_tx_eqtr_adapt_length(hba, params, gear);
+ if (ret) {
+ dev_err(hba->dev, "Failed to setup TX EQTR Adaptation length: %d\n", ret);
+ return ret;
+ }
+
+ /* Step 2 - Determine TX Equalization setting capabilities */
+ ret = ufshcd_tx_eqtr_iter_init(hba, &h_iter, &d_iter);
+ if (ret) {
+ dev_err(hba->dev, "Failed to init TX EQTR data: %d\n", ret);
+ return ret;
+ }
+
+ /* TX EQTR main loop */
+ for (preshoot = 0; preshoot < TX_HS_NUM_PRESHOOT; preshoot++) {
+ for (deemphasis = 0; deemphasis < TX_HS_NUM_DEEMPHASIS; deemphasis++) {
+ if (!tx_eqtr_iter_update(preshoot, deemphasis, &h_iter, &d_iter))
+ continue;
+
+ /* Step 3 - Apply TX EQTR settings */
+ ret = ufshcd_apply_tx_eqtr_settings(hba, pwr_mode, &h_iter, &d_iter);
+ if (ret) {
+ dev_err(hba->dev, "Failed to apply TX EQTR settings (PreShoot %u, DeEmphasis %u): %d\n",
+ preshoot, deemphasis, ret);
+ return ret;
+ }
+
+ /* Step 4 - Trigger UIC TX EQTR */
+ ret = ufshcd_uic_tx_eqtr(hba, gear);
+ if (ret) {
+ dev_err(hba->dev, "Failed to trigger UIC TX EQTR for target gear %u: %d\n",
+ gear, ret);
+ return ret;
+ }
+
+ /* Step 5 - Get FOM */
+ ret = ufshcd_get_rx_fom(hba, pwr_mode, &h_iter, &d_iter);
+ if (ret) {
+ dev_err(hba->dev, "Failed to get RX_FOM: %d\n",
+ ret);
+ return ret;
+ }
+
+ ufshcd_evaluate_tx_eqtr_fom(hba, pwr_mode, eqtr_data, &h_iter, &d_iter);
+ }
+ }
+
+ dev_info(hba->dev, "TX EQTR procedure completed! Time elapsed: %llu ms\n",
+ ktime_to_ms(ktime_sub(ktime_get(), start)));
+
+ ufshcd_update_tx_eq_params(params, pwr_mode, eqtr_data);
+
+ return ret;
+}
+
+/**
+ * ufshcd_tx_eqtr_prepare - Prepare UFS link for TX EQTR procedure
+ * @hba: per adapter instance
+ * @pwr_mode: target power mode containing gear and rate
+ *
+ * This function prepares the UFS link for TX Equalization Training (EQTR) by
+ * establishing the proper initial conditions required by the EQTR procedure.
+ * It ensures that EQTR starts from the most reliable Power Mode (HS-G1) with
+ * all connected lanes activated and sets host TX HS Adapt Type to INITIAL.
+ *
+ * Returns 0 on successful preparation, negative error code on failure
+ */
+static int ufshcd_tx_eqtr_prepare(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ struct ufs_pa_layer_attr pwr_mode_hs_g1 = {
+ /* TX EQTR shall be initiated from the most reliable HS-G1 */
+ .gear_rx = UFS_HS_G1,
+ .gear_tx = UFS_HS_G1,
+ .lane_rx = pwr_mode->lane_rx,
+ .lane_tx = pwr_mode->lane_tx,
+ .pwr_rx = FAST_MODE,
+ .pwr_tx = FAST_MODE,
+ /* Use the target power mode's HS rate */
+ .hs_rate = pwr_mode->hs_rate,
+ };
+ u32 rate = pwr_mode->hs_rate;
+ int ret;
+
+ /* Change power mode to HS-G1, activate all connected lanes. */
+ ret = ufshcd_change_power_mode(hba, &pwr_mode_hs_g1,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+ if (ret) {
+ dev_err(hba->dev, "TX EQTR: Failed to change power mode to HS-G1, Rate-%s: %d\n",
+ ufs_hs_rate_to_str(rate), ret);
+ return ret;
+ }
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXHSADAPTTYPE),
+ PA_INITIAL_ADAPT);
+ if (ret)
+ dev_err(hba->dev, "TX EQTR: Failed to set Host Adapt type to INITIAL: %d\n",
+ ret);
+
+ return ret;
+}
+
+static void ufshcd_tx_eqtr_unprepare(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ int err;
+
+ if (pwr_mode->pwr_rx == SLOWAUTO_MODE || pwr_mode->hs_rate == 0)
+ return;
+
+ err = ufshcd_change_power_mode(hba, pwr_mode,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+ if (err)
+ dev_err(hba->dev, "%s: Failed to restore Power Mode: %d\n",
+ __func__, err);
+}
+
+/**
+ * ufshcd_tx_eqtr - Perform TX EQTR procedures with vops callbacks
+ * @hba: per adapter instance
+ * @params: TX EQ parameters data structure to populate
+ * @pwr_mode: target power mode containing gear and rate information
+ *
+ * This is the main entry point for performing TX Equalization Training (EQTR)
+ * procedure as defined in UFSCHI v5.0 specification. It serves as a wrapper
+ * around __ufshcd_tx_eqtr() to provide vops support through the variant
+ * operations framework.
+ *
+ * Returns 0 on success, negative error code on failure
+ */
+static int ufshcd_tx_eqtr(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ struct ufs_pa_layer_attr old_pwr_info;
+ unsigned int noio_flag;
+ int notify_ret;
+ int ret;
+
+ /*
+ * ufshcd_tx_eqtr() is called from a power-mode-change context where
+ * I/O is suspended. Use memalloc_noio_save() to propagate GFP_NOIO
+ * to all allocations in the call tree instead of tagging each call
+ * site individually.
+ */
+ noio_flag = memalloc_noio_save();
+
+ if (!params->eqtr_record) {
+ params->eqtr_record = devm_kzalloc(hba->dev,
+ sizeof(*params->eqtr_record),
+ GFP_KERNEL);
+ if (!params->eqtr_record) {
+ ret = -ENOMEM;
+ goto out_noio_restore;
+ }
+ }
+
+ memcpy(&old_pwr_info, &hba->pwr_info, sizeof(struct ufs_pa_layer_attr));
+
+ ret = ufshcd_tx_eqtr_prepare(hba, pwr_mode);
+ if (ret) {
+ dev_err(hba->dev, "Failed to prepare TX EQTR: %d\n", ret);
+ goto out_unprepare;
+ }
+
+ ret = ufshcd_vops_tx_eqtr_notify(hba, PRE_CHANGE, pwr_mode);
+ if (ret) {
+ dev_err(hba->dev, "TX EQTR PRE_CHANGE notify failed: %d\n", ret);
+ goto out_unprepare;
+ }
+
+ ret = __ufshcd_tx_eqtr(hba, params, pwr_mode);
+
+ notify_ret = ufshcd_vops_tx_eqtr_notify(hba, POST_CHANGE, pwr_mode);
+ if (notify_ret)
+ dev_err(hba->dev, "TX EQTR POST_CHANGE notify failed: %d\n", notify_ret);
+
+ if (!ret)
+ ret = notify_ret;
+
+out_unprepare:
+ if (ret)
+ ufshcd_tx_eqtr_unprepare(hba, &old_pwr_info);
+
+out_noio_restore:
+ memalloc_noio_restore(noio_flag);
+
+ return ret;
+}
+
+/**
+ * ufshcd_config_tx_eq_settings - Configure TX Equalization settings
+ * @hba: per adapter instance
+ * @pwr_mode: target power mode containing gear and rate information
+ * @force_tx_eqtr: execute the TX EQTR procedure
+ *
+ * This function finds and sets the TX Equalization settings for the given
+ * target power mode.
+ *
+ * Returns 0 on success, error code otherwise
+ */
+int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ bool force_tx_eqtr)
+{
+ struct ufshcd_tx_eq_params *params;
+ u32 gear, rate;
+
+ if (!ufshcd_is_tx_eq_supported(hba) || !use_adaptive_txeq)
+ return 0;
+
+ if (!hba->max_pwr_info.is_valid) {
+ dev_err(hba->dev, "Max power info is invalid\n");
+ return -EINVAL;
+ }
+
+ if (!pwr_mode) {
+ dev_err(hba->dev, "Target power mode is NULL\n");
+ return -EINVAL;
+ }
+
+ gear = pwr_mode->gear_tx;
+ rate = pwr_mode->hs_rate;
+
+ if (gear < UFS_HS_G1 || gear > UFS_HS_GEAR_MAX) {
+ dev_err(hba->dev, "Invalid HS-Gear (%u) for TX Equalization\n",
+ gear);
+ return -EINVAL;
+ } else if (gear < max_t(u32, adaptive_txeq_gear, UFS_HS_G4)) {
+ /* TX EQTR is supported for HS-G4 and higher Gears */
+ return 0;
+ }
+
+ if (rate != PA_HS_MODE_A && rate != PA_HS_MODE_B) {
+ dev_err(hba->dev, "Invalid HS-Rate (%u) for TX Equalization\n",
+ rate);
+ return -EINVAL;
+ }
+
+ params = &hba->tx_eq_params[gear - 1];
+ /*
+ * TX EQTR must run for the following cases:
+ * 1. TX EQ settings are invalid.
+ * 2. TX EQ settings are from Device Tree.
+ * 3. TX EQTR procedure is forced.
+ */
+ if (!params->is_valid || params->from_dt || force_tx_eqtr) {
+ int ret;
+
+ ret = ufshcd_tx_eqtr(hba, params, pwr_mode);
+ if (ret) {
+ dev_err(hba->dev, "Failed to train TX Equalization for HS-G%u, Rate-%s: %d\n",
+ gear, ufs_hs_rate_to_str(rate), ret);
+ return ret;
+ }
+
+ /* Mark TX Equalization settings as valid */
+ params->is_valid = true;
+ params->is_trained = true;
+ /* TX EQTR succeeds, clear from_dt flag */
+ params->from_dt = false;
+ params->is_applied = false;
+ }
+
+ if (params->is_valid && !params->is_applied) {
+ int ret;
+
+ ret = ufshcd_apply_tx_eq_settings(hba, params, gear);
+ if (ret) {
+ dev_err(hba->dev, "Failed to apply TX Equalization settings for HS-G%u, Rate-%s: %d\n",
+ gear, ufs_hs_rate_to_str(rate), ret);
+ return ret;
+ }
+
+ params->is_applied = true;
+ }
+
+ return 0;
+}
+
+/**
+ * ufshcd_apply_valid_tx_eq_settings - Apply valid TX Equalization settings
+ * @hba: per-adapter instance
+ *
+ * This function iterates through all supported High-Speed (HS) gears and
+ * applies valid TX Equalization settings to both Host and Device.
+ */
+void ufshcd_apply_valid_tx_eq_settings(struct ufs_hba *hba)
+{
+ struct ufshcd_tx_eq_params *params;
+ int gear, err;
+
+ if (!ufshcd_is_tx_eq_supported(hba))
+ return;
+
+ if (!hba->max_pwr_info.is_valid) {
+ dev_err(hba->dev, "Max power info is invalid, cannot apply TX Equalization settings\n");
+ return;
+ }
+
+ for (gear = UFS_HS_G1; gear <= UFS_HS_GEAR_MAX; gear++) {
+ params = &hba->tx_eq_params[gear - 1];
+
+ if (params->is_valid) {
+ err = ufshcd_apply_tx_eq_settings(hba, params, gear);
+ if (err) {
+ params->is_applied = false;
+ dev_err(hba->dev, "Failed to apply TX Equalization settings for HS-G%u: %d\n",
+ gear, err);
+ } else {
+ params->is_applied = true;
+ }
+ }
+ }
+}
+
+/**
+ * ufshcd_retrain_tx_eq - Retrain TX Equalization and apply new settings
+ * @hba: per-adapter instance
+ * @gear: target High-Speed (HS) gear for retraining
+ *
+ * This function initiates a refresh of the TX Equalization settings for a
+ * specific HS gear. It scales the clocks to maximum frequency, negotiates the
+ * power mode with the device, retrains TX EQ and applies new TX EQ settings
+ * by conducting a Power Mode change.
+ *
+ * Returns 0 on success, non-zero error code otherwise
+ */
+int ufshcd_retrain_tx_eq(struct ufs_hba *hba, u32 gear)
+{
+ struct ufs_pa_layer_attr new_pwr_info, final_params = {};
+ int ret;
+
+ if (!ufshcd_is_tx_eq_supported(hba) || !use_adaptive_txeq)
+ return -EOPNOTSUPP;
+
+ if (gear < adaptive_txeq_gear)
+ return -ERANGE;
+
+ ufshcd_hold(hba);
+
+ ret = ufshcd_pause_command_processing(hba, 1 * USEC_PER_SEC);
+ if (ret) {
+ ufshcd_release(hba);
+ return ret;
+ }
+
+ /* scale up clocks to max frequency before TX EQTR */
+ if (ufshcd_is_clkscaling_supported(hba))
+ ufshcd_scale_clks(hba, ULONG_MAX, true);
+
+ new_pwr_info = hba->pwr_info;
+ new_pwr_info.gear_tx = gear;
+ new_pwr_info.gear_rx = gear;
+
+ ret = ufshcd_vops_negotiate_pwr_mode(hba, &new_pwr_info, &final_params);
+ if (ret)
+ memcpy(&final_params, &new_pwr_info, sizeof(final_params));
+
+ if (final_params.gear_tx != gear) {
+ dev_err(hba->dev, "Negotiated Gear (%u) does not match target Gear (%u)\n",
+ final_params.gear_tx, gear);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = ufshcd_config_tx_eq_settings(hba, &final_params, true);
+ if (ret) {
+ dev_err(hba->dev, "Failed to config TX Equalization for HS-G%u, Rate-%s: %d\n",
+ final_params.gear_tx,
+ ufs_hs_rate_to_str(final_params.hs_rate), ret);
+ goto out;
+ }
+
+ /* Change Power Mode to apply the new TX EQ settings */
+ ret = ufshcd_change_power_mode(hba, &final_params,
+ UFSHCD_PMC_POLICY_FORCE);
+ if (ret)
+ dev_err(hba->dev, "%s: Failed to change Power Mode to HS-G%u, Rate-%s: %d\n",
+ __func__, final_params.gear_tx,
+ ufs_hs_rate_to_str(final_params.hs_rate), ret);
+
+out:
+ ufshcd_resume_command_processing(hba);
+ ufshcd_release(hba);
+
+ return ret;
+}
+
+/**
+ * ufshcd_extract_tx_eq_settings_attrs - Extract TX Equalization settings from UFS attributes
+ * @hba: per adapter instance
+ * @gear: target gear
+ *
+ * This function extracts previously stored TX Equalization settings from UFS
+ * attributes qTxEQGnSettings and wTxEQGnSettingsExt. These attributes contain
+ * the optimal TX Equalization parameters (PreShoot, DeEmphasis, and PreCoding
+ * enable) that were determined during a previous EQTR procedure.
+ *
+ * The function reads:
+ * 1. qTxEQGnSettings (64-bit): Main attribute containing PreShoot and
+ * DeEmphasis values for both host and device TX lanes
+ * 2. wTxEQGnSettingsExt (16-bit): Extended attribute containing PreCoding
+ * enable flags and validity indicator
+ */
+static void ufshcd_extract_tx_eq_settings_attrs(struct ufs_hba *hba, u8 gear)
+{
+ struct ufshcd_tx_eq_params *params;
+ u32 lane, eq_ext;
+ int ret;
+ u64 eq;
+
+ ret = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_TX_EQ_GN_SETTINGS_EXT, gear - 1,
+ (u8)txeq_setting_sel, &eq_ext);
+ if (ret)
+ return;
+
+ dev_dbg(hba->dev, "%s: HS-G%u wTxEQGnSettingsExt (Selector %u) = 0x%08x\n",
+ __func__, gear, txeq_setting_sel, eq_ext);
+
+ if (!(eq_ext & TX_EQ_SETTINGS_VALID_BIT))
+ return;
+
+ ret = ufshcd_query_attr_qword(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_TX_EQ_GN_SETTINGS,
+ gear - 1, (u8)txeq_setting_sel, &eq);
+ if (ret)
+ return;
+
+ dev_dbg(hba->dev, "%s: HS-G%u qTxEQGnSettings (Selector %u) = 0x%016llx\n",
+ __func__, gear, txeq_setting_sel, eq);
+
+ params = &hba->tx_eq_params[gear - 1];
+
+ for (lane = 0; lane < UFS_MAX_LANES; lane++) {
+ params->host[lane].preshoot = tx_eq_host_preshoot_decode(eq, lane);
+ params->host[lane].deemphasis = tx_eq_host_deemphasis_decode(eq, lane);
+ params->host[lane].precode_en = tx_eq_host_precode_en_decode(eq_ext, lane);
+
+ params->device[lane].preshoot = tx_eq_device_preshoot_decode(eq, lane);
+ params->device[lane].deemphasis = tx_eq_device_deemphasis_decode(eq, lane);
+ params->device[lane].precode_en = tx_eq_device_precode_en_decode(eq_ext, lane);
+ }
+
+ params->is_valid = true;
+ /*
+ * Optimal TX EQ settings are retrieved from UFS device attributes,
+ * clear from_dt flag to avoid TX EQTR procedure.
+ */
+ params->from_dt = false;
+}
+
+void ufshcd_retrieve_tx_eq_settings(struct ufs_hba *hba)
+{
+ u8 gear = (u8)adaptive_txeq_gear;
+
+ if (!hba->max_pwr_info.is_valid || !ufshcd_is_tx_eq_supported(hba) ||
+ !use_adaptive_txeq || !retrieve_txeq_setting)
+ return;
+
+ for (; gear <= UFS_HS_GEAR_MAX; gear++)
+ ufshcd_extract_tx_eq_settings_attrs(hba, gear);
+}
+
+/**
+ * ufshcd_update_tx_eq_settings_attrs - Update TX EQ settings in UFS attributes
+ * @hba: per adapter instance
+ * @gear: target gear
+ *
+ * This function stores the optimal TX Equalization settings obtained from
+ * TX EQTR procedure into UFS device attributes for future fast-path retrieval.
+ * The settings are stored in two complementary attributes:
+ *
+ * 1. qTxEQGnSettings (64-bit): Main attribute containing PreShoot and
+ * DeEmphasis values for both host and device TX lanes
+ * 2. wTxEQGnSettingsExt (16-bit): Extended attribute containing PreCoding
+ * enable flags and validity indicator
+ */
+static void ufshcd_update_tx_eq_settings_attrs(struct ufs_hba *hba, u8 gear)
+{
+ struct ufshcd_tx_eq_params *params;
+ u32 lane, eq_ext = 0;
+ u64 eq = 0;
+ int ret;
+
+ params = &hba->tx_eq_params[gear - 1];
+ if (!params->is_valid || !params->is_trained)
+ return;
+
+ for (lane = 0; lane < UFS_MAX_LANES; lane++) {
+ eq |= tx_eq_host_preshoot_encode((u64)params->host[lane].preshoot, lane);
+ eq |= tx_eq_host_deemphasis_encode((u64)params->host[lane].deemphasis, lane);
+ eq_ext |= tx_eq_host_precode_en_encode(params->host[lane].precode_en, lane);
+
+ eq |= tx_eq_device_preshoot_encode((u64)params->device[lane].preshoot, lane);
+ eq |= tx_eq_device_deemphasis_encode((u64)params->device[lane].deemphasis, lane);
+ eq_ext |= tx_eq_device_precode_en_encode(params->device[lane].precode_en, lane);
+ }
+
+ /* Set validity flag to indicate valid settings are stored */
+ eq_ext |= TX_EQ_SETTINGS_VALID_BIT;
+
+ /* Write qTxEQGnSettings */
+ ret = ufshcd_query_attr_qword(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_TX_EQ_GN_SETTINGS,
+ gear - 1, (u8)txeq_setting_sel, &eq);
+ if (ret)
+ return;
+
+ /* Write wTxEQGnSettingsExt */
+ ret = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_TX_EQ_GN_SETTINGS_EXT, gear - 1,
+ (u8)txeq_setting_sel, &eq_ext);
+ if (ret)
+ return;
+
+ dev_dbg(hba->dev, "%s: Saved HS-G%u qTxEQGnSettings (Selector %u) = 0x%016llx\n",
+ __func__, gear, txeq_setting_sel, eq);
+ dev_dbg(hba->dev, "%s: Saved HS-G%u wTxEQGnSettingsExt (Selector %u) = 0x%08x\n",
+ __func__, gear, txeq_setting_sel, eq_ext);
+}
+
+void ufshcd_store_tx_eq_settings(struct ufs_hba *hba)
+{
+ u8 gear = (u8)adaptive_txeq_gear;
+
+ if (!hba->max_pwr_info.is_valid || !ufshcd_is_tx_eq_supported(hba) ||
+ !use_adaptive_txeq || !store_txeq_setting)
+ return;
+
+ for (; gear <= UFS_HS_GEAR_MAX; gear++)
+ ufshcd_update_tx_eq_settings_attrs(hba, gear);
+}
diff --git a/drivers/ufs/core/ufs_bsg.c b/drivers/ufs/core/ufs_bsg.c
new file mode 100644
index 000000000..7a66917b3
--- /dev/null
+++ b/drivers/ufs/core/ufs_bsg.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * bsg endpoint that supports UPIUs
+ *
+ * Copyright (C) 2018 Western Digital Corporation
+ */
+
+#include <linux/bsg-lib.h>
+#include <linux/dma-mapping.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_host.h>
+#include "ufs_bsg.h"
+#include <ufs/ufshcd.h>
+#include "ufshcd-priv.h"
+
+static int ufs_bsg_alloc_desc_buffer(struct ufs_hba *hba, struct bsg_job *job,
+ u8 **desc_buff, u16 *desc_len,
+ enum query_opcode desc_op)
+{
+ struct ufs_bsg_request *bsg_request = job->request;
+ struct utp_upiu_query *qr;
+ u16 max_desc_len;
+ u8 *descp;
+
+ if (desc_op != UPIU_QUERY_OPCODE_WRITE_DESC &&
+ desc_op != UPIU_QUERY_OPCODE_READ_DESC &&
+ desc_op != UPIU_QUERY_OPCODE_AGGREGATED_READ)
+ goto out;
+
+ qr = &bsg_request->upiu_req.qr;
+ *desc_len = be16_to_cpu(qr->length);
+ if (*desc_len == 0) {
+ dev_err(hba->dev, "Illegal desc size\n");
+ return -EINVAL;
+ }
+
+ max_desc_len = desc_op == UPIU_QUERY_OPCODE_AGGREGATED_READ ?
+ QUERY_AGGREGATED_MAX_SIZE : QUERY_DESC_MAX_SIZE;
+ *desc_len = min(*desc_len, max_desc_len);
+
+ if (*desc_len > job->request_payload.payload_len) {
+ dev_err(hba->dev, "Illegal desc size\n");
+ return -EINVAL;
+ }
+
+ descp = kzalloc(*desc_len, GFP_KERNEL);
+ if (!descp)
+ return -ENOMEM;
+
+ if (desc_op == UPIU_QUERY_OPCODE_WRITE_DESC)
+ sg_copy_to_buffer(job->request_payload.sg_list,
+ job->request_payload.sg_cnt, descp,
+ *desc_len);
+
+ *desc_buff = descp;
+
+out:
+ return 0;
+}
+
+static int ufs_bsg_exec_advanced_rpmb_req(struct ufs_hba *hba, struct bsg_job *job)
+{
+ struct ufs_rpmb_request *rpmb_request = job->request;
+ struct ufs_rpmb_reply *rpmb_reply = job->reply;
+ struct bsg_buffer *payload = NULL;
+ enum dma_data_direction dir;
+ struct scatterlist *sg_list = NULL;
+ int rpmb_req_type;
+ int sg_cnt = 0;
+ int ret;
+ int data_len;
+
+ if (hba->ufs_version < ufshci_version(4, 0) || !hba->dev_info.b_advanced_rpmb_en)
+ return -EINVAL;
+
+ if (rpmb_request->ehs_req.length != 2 || rpmb_request->ehs_req.ehs_type != 1)
+ return -EINVAL;
+
+ rpmb_req_type = be16_to_cpu(rpmb_request->ehs_req.meta.req_resp_type);
+
+ switch (rpmb_req_type) {
+ case UFS_RPMB_WRITE_KEY:
+ case UFS_RPMB_READ_CNT:
+ case UFS_RPMB_PURGE_ENABLE:
+ dir = DMA_NONE;
+ break;
+ case UFS_RPMB_WRITE:
+ case UFS_RPMB_SEC_CONF_WRITE:
+ dir = DMA_TO_DEVICE;
+ break;
+ case UFS_RPMB_READ:
+ case UFS_RPMB_SEC_CONF_READ:
+ case UFS_RPMB_PURGE_STATUS_READ:
+ dir = DMA_FROM_DEVICE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (dir != DMA_NONE) {
+ payload = &job->request_payload;
+ if (!payload->payload_len || !payload->sg_cnt)
+ return -EINVAL;
+
+ if (payload->sg_cnt > UFSHCD_DEVMAN_SG_ENTRIES)
+ return -EINVAL;
+
+ sg_cnt = dma_map_sg(hba->host->dma_dev, payload->sg_list, payload->sg_cnt, dir);
+ if (unlikely(!sg_cnt))
+ return -ENOMEM;
+ sg_list = payload->sg_list;
+ data_len = payload->payload_len;
+ }
+
+ ret = ufshcd_advanced_rpmb_req_handler(hba, &rpmb_request->bsg_request.upiu_req,
+ &rpmb_reply->bsg_reply.upiu_rsp, &rpmb_request->ehs_req,
+ &rpmb_reply->ehs_rsp, sg_cnt, sg_list, dir);
+
+ if (dir != DMA_NONE) {
+ dma_unmap_sg(hba->host->dma_dev, payload->sg_list, payload->sg_cnt, dir);
+
+ if (!ret)
+ rpmb_reply->bsg_reply.reply_payload_rcv_len = data_len;
+ }
+
+ return ret;
+}
+
+static int ufs_bsg_request(struct bsg_job *job)
+{
+ struct ufs_bsg_request *bsg_request = job->request;
+ struct ufs_bsg_reply *bsg_reply = job->reply;
+ struct ufs_hba *hba = shost_priv(dev_to_shost(job->dev->parent));
+ struct uic_command uc = {};
+ int msgcode;
+ u8 *buff = NULL;
+ u16 desc_len = 0;
+ int buff_len;
+ enum query_opcode desc_op = UPIU_QUERY_OPCODE_NOP;
+ int ret;
+ bool rpmb = false;
+
+ bsg_reply->reply_payload_rcv_len = 0;
+
+ ufshcd_rpm_get_sync(hba);
+
+ msgcode = bsg_request->msgcode;
+ switch (msgcode) {
+ case UPIU_TRANSACTION_QUERY_REQ:
+ desc_op = bsg_request->upiu_req.qr.opcode;
+ ret = ufs_bsg_alloc_desc_buffer(hba, job, &buff, &desc_len, desc_op);
+ if (ret)
+ goto out;
+ fallthrough;
+ case UPIU_TRANSACTION_NOP_OUT:
+ case UPIU_TRANSACTION_TASK_REQ:
+ buff_len = desc_len;
+ ret = ufshcd_exec_raw_upiu_cmd(hba, &bsg_request->upiu_req,
+ &bsg_reply->upiu_rsp, msgcode,
+ buff, &buff_len, desc_op);
+ desc_len = buff_len;
+ if (ret)
+ dev_err(hba->dev, "exe raw upiu: error code %d\n", ret);
+ else if ((desc_op == UPIU_QUERY_OPCODE_READ_DESC ||
+ desc_op == UPIU_QUERY_OPCODE_AGGREGATED_READ) && desc_len) {
+ bsg_reply->reply_payload_rcv_len =
+ sg_copy_from_buffer(job->request_payload.sg_list,
+ job->request_payload.sg_cnt,
+ buff, desc_len);
+ }
+ break;
+ case UPIU_TRANSACTION_UIC_CMD:
+ memcpy(&uc, &bsg_request->upiu_req.uc, UIC_CMD_SIZE);
+ ret = ufshcd_send_bsg_uic_cmd(hba, &uc);
+ if (ret)
+ dev_err(hba->dev, "send uic cmd: error code %d\n", ret);
+
+ memcpy(&bsg_reply->upiu_rsp.uc, &uc, UIC_CMD_SIZE);
+
+ break;
+ case UPIU_TRANSACTION_ARPMB_CMD:
+ rpmb = true;
+ ret = ufs_bsg_exec_advanced_rpmb_req(hba, job);
+ if (ret)
+ dev_err(hba->dev, "ARPMB OP failed: error code %d\n", ret);
+ break;
+ default:
+ ret = -ENOTSUPP;
+ dev_err(hba->dev, "unsupported msgcode 0x%x\n", msgcode);
+
+ break;
+ }
+
+out:
+ ufshcd_rpm_put_sync(hba);
+ kfree(buff);
+ bsg_reply->result = ret;
+ /* complete the job here only if no error */
+ if (ret == 0) {
+ job->reply_len = rpmb ? sizeof(struct ufs_rpmb_reply) :
+ sizeof(struct ufs_bsg_reply);
+ bsg_job_done(job, ret, bsg_reply->reply_payload_rcv_len);
+ }
+
+ return ret;
+}
+
+/**
+ * ufs_bsg_remove - detach and remove the added ufs-bsg node
+ * @hba: per adapter object
+ *
+ * Should be called when unloading the driver.
+ */
+void ufs_bsg_remove(struct ufs_hba *hba)
+{
+ struct device *bsg_dev = &hba->bsg_dev;
+
+ if (!hba->bsg_queue)
+ return;
+
+ bsg_remove_queue(hba->bsg_queue);
+ hba->bsg_queue = NULL;
+
+ device_del(bsg_dev);
+ put_device(bsg_dev);
+}
+
+static inline void ufs_bsg_node_release(struct device *dev)
+{
+ put_device(dev->parent);
+}
+
+/**
+ * ufs_bsg_probe - Add ufs bsg device node
+ * @hba: per adapter object
+ *
+ * Called during initial loading of the driver, and before scsi_scan_host.
+ *
+ * Returns: 0 (success).
+ */
+int ufs_bsg_probe(struct ufs_hba *hba)
+{
+ struct device *bsg_dev = &hba->bsg_dev;
+ struct Scsi_Host *shost = hba->host;
+ struct device *parent = &shost->shost_gendev;
+ struct request_queue *q;
+ int ret;
+
+ device_initialize(bsg_dev);
+
+ bsg_dev->parent = get_device(parent);
+ bsg_dev->release = ufs_bsg_node_release;
+
+ dev_set_name(bsg_dev, "ufs-bsg%u", shost->host_no);
+
+ ret = device_add(bsg_dev);
+ if (ret)
+ goto out;
+
+ q = bsg_setup_queue(bsg_dev, dev_name(bsg_dev), NULL, ufs_bsg_request,
+ NULL, 0);
+ if (IS_ERR(q)) {
+ ret = PTR_ERR(q);
+ device_del(bsg_dev);
+ goto out;
+ }
+
+ hba->bsg_queue = q;
+
+ return 0;
+
+out:
+ dev_err(bsg_dev, "fail to initialize a bsg dev %d\n", shost->host_no);
+ put_device(bsg_dev);
+ return ret;
+}
diff --git a/drivers/ufs/core/ufs_bsg.h b/drivers/ufs/core/ufs_bsg.h
new file mode 100644
index 000000000..57712d265
--- /dev/null
+++ b/drivers/ufs/core/ufs_bsg.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Western Digital Corporation
+ */
+#ifndef UFS_BSG_H
+#define UFS_BSG_H
+
+struct ufs_hba;
+
+#ifdef CONFIG_SCSI_UFS_BSG
+void ufs_bsg_remove(struct ufs_hba *hba);
+int ufs_bsg_probe(struct ufs_hba *hba);
+#else
+static inline void ufs_bsg_remove(struct ufs_hba *hba) {}
+static inline int ufs_bsg_probe(struct ufs_hba *hba) {return 0; }
+#endif
+
+#endif /* UFS_BSG_H */
diff --git a/drivers/ufs/core/ufs_trace.h b/drivers/ufs/core/ufs_trace.h
new file mode 100644
index 000000000..377a3c54b
--- /dev/null
+++ b/drivers/ufs/core/ufs_trace.h
@@ -0,0 +1,426 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM ufs
+
+#if !defined(_TRACE_UFS_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_UFS_H
+
+#include <ufs/ufs.h>
+#include <linux/tracepoint.h>
+#include "ufs_trace_types.h"
+
+#define str_opcode(opcode) \
+ __print_symbolic(opcode, \
+ { WRITE_16, "WRITE_16" }, \
+ { WRITE_10, "WRITE_10" }, \
+ { READ_16, "READ_16" }, \
+ { READ_10, "READ_10" }, \
+ { SYNCHRONIZE_CACHE, "SYNC" }, \
+ { UNMAP, "UNMAP" })
+
+#define UFS_LINK_STATES \
+ EM(UIC_LINK_OFF_STATE, "UIC_LINK_OFF_STATE") \
+ EM(UIC_LINK_ACTIVE_STATE, "UIC_LINK_ACTIVE_STATE") \
+ EMe(UIC_LINK_HIBERN8_STATE, "UIC_LINK_HIBERN8_STATE")
+
+#define UFS_PWR_MODES \
+ EM(UFS_ACTIVE_PWR_MODE, "UFS_ACTIVE_PWR_MODE") \
+ EM(UFS_SLEEP_PWR_MODE, "UFS_SLEEP_PWR_MODE") \
+ EM(UFS_POWERDOWN_PWR_MODE, "UFS_POWERDOWN_PWR_MODE") \
+ EMe(UFS_DEEPSLEEP_PWR_MODE, "UFS_DEEPSLEEP_PWR_MODE")
+
+#define UFSCHD_CLK_GATING_STATES \
+ EM(CLKS_OFF, "CLKS_OFF") \
+ EM(CLKS_ON, "CLKS_ON") \
+ EM(REQ_CLKS_OFF, "REQ_CLKS_OFF") \
+ EMe(REQ_CLKS_ON, "REQ_CLKS_ON")
+
+#define UFS_CMD_TRACE_STRINGS \
+ EM(UFS_CMD_SEND, "send_req") \
+ EM(UFS_CMD_COMP, "complete_rsp") \
+ EM(UFS_QUERY_SEND, "query_send") \
+ EM(UFS_QUERY_COMP, "query_complete") \
+ EM(UFS_QUERY_ERR, "query_complete_err") \
+ EM(UFS_TM_SEND, "tm_send") \
+ EM(UFS_TM_COMP, "tm_complete") \
+ EMe(UFS_TM_ERR, "tm_complete_err")
+
+#define UFS_CMD_TRACE_TSF_TYPES \
+ EM(UFS_TSF_CDB, "CDB") \
+ EM(UFS_TSF_OSF, "OSF") \
+ EM(UFS_TSF_TM_INPUT, "TM_INPUT") \
+ EMe(UFS_TSF_TM_OUTPUT, "TM_OUTPUT")
+
+/* Enums require being exported to userspace, for user tool parsing */
+#undef EM
+#undef EMe
+#define EM(a, b) TRACE_DEFINE_ENUM(a);
+#define EMe(a, b) TRACE_DEFINE_ENUM(a);
+
+UFS_LINK_STATES;
+UFS_PWR_MODES;
+UFSCHD_CLK_GATING_STATES;
+UFS_CMD_TRACE_STRINGS
+UFS_CMD_TRACE_TSF_TYPES
+
+/*
+ * Now redefine the EM() and EMe() macros to map the enums to the strings
+ * that will be printed in the output.
+ */
+#undef EM
+#undef EMe
+#define EM(a, b) {a, b},
+#define EMe(a, b) {a, b}
+
+#define show_ufs_cmd_trace_str(str_t) \
+ __print_symbolic(str_t, UFS_CMD_TRACE_STRINGS)
+#define show_ufs_cmd_trace_tsf(tsf) \
+ __print_symbolic(tsf, UFS_CMD_TRACE_TSF_TYPES)
+
+TRACE_EVENT(ufshcd_clk_gating,
+
+ TP_PROTO(struct ufs_hba *hba, int state),
+
+ TP_ARGS(hba, state),
+
+ TP_STRUCT__entry(
+ __field(struct ufs_hba *, hba)
+ __string(dev_name, dev_name(hba->dev))
+ __field(int, state)
+ ),
+
+ TP_fast_assign(
+ __assign_str(dev_name);
+ __entry->hba = hba;
+ __entry->state = state;
+ ),
+
+ TP_printk("%s: gating state changed to %s",
+ __get_str(dev_name),
+ __print_symbolic(__entry->state, UFSCHD_CLK_GATING_STATES))
+);
+
+TRACE_EVENT(ufshcd_clk_scaling,
+
+ TP_PROTO(struct ufs_hba *hba, const char *state, const char *clk,
+ u32 prev_state, u32 curr_state),
+
+ TP_ARGS(hba, state, clk, prev_state, curr_state),
+
+ TP_STRUCT__entry(
+ __field(struct ufs_hba *, hba)
+ __string(dev_name, dev_name(hba->dev))
+ __string(state, state)
+ __string(clk, clk)
+ __field(u32, prev_state)
+ __field(u32, curr_state)
+ ),
+
+ TP_fast_assign(
+ __entry->hba = hba;
+ __assign_str(dev_name);
+ __assign_str(state);
+ __assign_str(clk);
+ __entry->prev_state = prev_state;
+ __entry->curr_state = curr_state;
+ ),
+
+ TP_printk("%s: %s %s from %u to %u Hz",
+ __get_str(dev_name), __get_str(state), __get_str(clk),
+ __entry->prev_state, __entry->curr_state)
+);
+
+TRACE_EVENT(ufshcd_auto_bkops_state,
+
+ TP_PROTO(struct ufs_hba *hba, const char *state),
+
+ TP_ARGS(hba, state),
+
+ TP_STRUCT__entry(
+ __field(struct ufs_hba *, hba)
+ __string(dev_name, dev_name(hba->dev))
+ __string(state, state)
+ ),
+
+ TP_fast_assign(
+ __entry->hba = hba;
+ __assign_str(dev_name);
+ __assign_str(state);
+ ),
+
+ TP_printk("%s: auto bkops - %s",
+ __get_str(dev_name), __get_str(state))
+);
+
+DECLARE_EVENT_CLASS(ufshcd_profiling_template,
+ TP_PROTO(struct ufs_hba *hba, const char *profile_info, s64 time_us,
+ int err),
+
+ TP_ARGS(hba, profile_info, time_us, err),
+
+ TP_STRUCT__entry(
+ __field(struct ufs_hba *, hba)
+ __string(dev_name, dev_name(hba->dev))
+ __string(profile_info, profile_info)
+ __field(s64, time_us)
+ __field(int, err)
+ ),
+
+ TP_fast_assign(
+ __entry->hba = hba;
+ __assign_str(dev_name);
+ __assign_str(profile_info);
+ __entry->time_us = time_us;
+ __entry->err = err;
+ ),
+
+ TP_printk("%s: %s: took %lld usecs, err %d",
+ __get_str(dev_name), __get_str(profile_info),
+ __entry->time_us, __entry->err)
+);
+
+DEFINE_EVENT(ufshcd_profiling_template, ufshcd_profile_hibern8,
+ TP_PROTO(struct ufs_hba *hba, const char *profile_info, s64 time_us,
+ int err),
+ TP_ARGS(hba, profile_info, time_us, err));
+
+DEFINE_EVENT(ufshcd_profiling_template, ufshcd_profile_clk_gating,
+ TP_PROTO(struct ufs_hba *hba, const char *profile_info, s64 time_us,
+ int err),
+ TP_ARGS(hba, profile_info, time_us, err));
+
+DEFINE_EVENT(ufshcd_profiling_template, ufshcd_profile_clk_scaling,
+ TP_PROTO(struct ufs_hba *hba, const char *profile_info, s64 time_us,
+ int err),
+ TP_ARGS(hba, profile_info, time_us, err));
+
+DECLARE_EVENT_CLASS(ufshcd_template,
+ TP_PROTO(struct ufs_hba *hba, int err, s64 usecs,
+ int dev_state, int link_state),
+
+ TP_ARGS(hba, err, usecs, dev_state, link_state),
+
+ TP_STRUCT__entry(
+ __field(s64, usecs)
+ __field(int, err)
+ __field(struct ufs_hba *, hba)
+ __string(dev_name, dev_name(hba->dev))
+ __field(int, dev_state)
+ __field(int, link_state)
+ ),
+
+ TP_fast_assign(
+ __entry->usecs = usecs;
+ __entry->err = err;
+ __entry->hba = hba;
+ __assign_str(dev_name);
+ __entry->dev_state = dev_state;
+ __entry->link_state = link_state;
+ ),
+
+ TP_printk(
+ "%s: took %lld usecs, dev_state: %s, link_state: %s, err %d",
+ __get_str(dev_name),
+ __entry->usecs,
+ __print_symbolic(__entry->dev_state, UFS_PWR_MODES),
+ __print_symbolic(__entry->link_state, UFS_LINK_STATES),
+ __entry->err
+ )
+);
+
+DEFINE_EVENT(ufshcd_template, ufshcd_system_suspend,
+ TP_PROTO(struct ufs_hba *hba, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(hba, err, usecs, dev_state, link_state));
+
+DEFINE_EVENT(ufshcd_template, ufshcd_system_resume,
+ TP_PROTO(struct ufs_hba *hba, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(hba, err, usecs, dev_state, link_state));
+
+DEFINE_EVENT(ufshcd_template, ufshcd_runtime_suspend,
+ TP_PROTO(struct ufs_hba *hba, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(hba, err, usecs, dev_state, link_state));
+
+DEFINE_EVENT(ufshcd_template, ufshcd_runtime_resume,
+ TP_PROTO(struct ufs_hba *hba, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(hba, err, usecs, dev_state, link_state));
+
+DEFINE_EVENT(ufshcd_template, ufshcd_init,
+ TP_PROTO(struct ufs_hba *hba, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(hba, err, usecs, dev_state, link_state));
+
+DEFINE_EVENT(ufshcd_template, ufshcd_wl_suspend,
+ TP_PROTO(struct ufs_hba *hba, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(hba, err, usecs, dev_state, link_state));
+
+DEFINE_EVENT(ufshcd_template, ufshcd_wl_resume,
+ TP_PROTO(struct ufs_hba *hba, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(hba, err, usecs, dev_state, link_state));
+
+DEFINE_EVENT(ufshcd_template, ufshcd_wl_runtime_suspend,
+ TP_PROTO(struct ufs_hba *hba, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(hba, err, usecs, dev_state, link_state));
+
+DEFINE_EVENT(ufshcd_template, ufshcd_wl_runtime_resume,
+ TP_PROTO(struct ufs_hba *hba, int err, s64 usecs,
+ int dev_state, int link_state),
+ TP_ARGS(hba, err, usecs, dev_state, link_state));
+
+TRACE_EVENT(ufshcd_command,
+ TP_PROTO(struct scsi_device *sdev, struct ufs_hba *hba,
+ enum ufs_trace_str_t str_t,
+ unsigned int tag, u32 doorbell, u32 hwq_id, int transfer_len,
+ u32 intr, u64 lba, u8 opcode, u8 group_id),
+
+ TP_ARGS(sdev, hba, str_t, tag, doorbell, hwq_id, transfer_len, intr, lba,
+ opcode, group_id),
+
+ TP_STRUCT__entry(
+ __field(struct scsi_device *, sdev)
+ __field(struct ufs_hba *, hba)
+ __string(dev_name, dev_name(&sdev->sdev_dev))
+ __field(enum ufs_trace_str_t, str_t)
+ __field(unsigned int, tag)
+ __field(u32, doorbell)
+ __field(u32, hwq_id)
+ __field(u32, intr)
+ __field(u64, lba)
+ __field(int, transfer_len)
+ __field(u8, opcode)
+ __field(u8, group_id)
+ ),
+
+ TP_fast_assign(
+ __assign_str(dev_name);
+ __entry->sdev = sdev;
+ __entry->hba = hba;
+ __entry->str_t = str_t;
+ __entry->tag = tag;
+ __entry->doorbell = doorbell;
+ __entry->hwq_id = hwq_id;
+ __entry->intr = intr;
+ __entry->lba = lba;
+ __entry->transfer_len = transfer_len;
+ __entry->opcode = opcode;
+ __entry->group_id = group_id;
+ ),
+
+ TP_printk(
+ "%s: %s: tag: %u, DB: 0x%x, size: %d, IS: %u, LBA: %llu, opcode: 0x%x (%s), group_id: 0x%x, hwq_id: %d",
+ show_ufs_cmd_trace_str(__entry->str_t),
+ __get_str(dev_name), __entry->tag,
+ __entry->doorbell, __entry->transfer_len, __entry->intr,
+ __entry->lba, (u32)__entry->opcode, str_opcode(__entry->opcode),
+ (u32)__entry->group_id, __entry->hwq_id
+ )
+);
+
+TRACE_EVENT(ufshcd_uic_command,
+ TP_PROTO(struct ufs_hba *hba, enum ufs_trace_str_t str_t, u32 cmd,
+ u32 arg1, u32 arg2, u32 arg3),
+
+ TP_ARGS(hba, str_t, cmd, arg1, arg2, arg3),
+
+ TP_STRUCT__entry(
+ __field(struct ufs_hba *, hba)
+ __string(dev_name, dev_name(hba->dev))
+ __field(enum ufs_trace_str_t, str_t)
+ __field(u32, cmd)
+ __field(u32, arg1)
+ __field(u32, arg2)
+ __field(u32, arg3)
+ ),
+
+ TP_fast_assign(
+ __entry->hba = hba;
+ __assign_str(dev_name);
+ __entry->str_t = str_t;
+ __entry->cmd = cmd;
+ __entry->arg1 = arg1;
+ __entry->arg2 = arg2;
+ __entry->arg3 = arg3;
+ ),
+
+ TP_printk(
+ "%s: %s: cmd: 0x%x, arg1: 0x%x, arg2: 0x%x, arg3: 0x%x",
+ show_ufs_cmd_trace_str(__entry->str_t), __get_str(dev_name),
+ __entry->cmd, __entry->arg1, __entry->arg2, __entry->arg3
+ )
+);
+
+TRACE_EVENT(ufshcd_upiu,
+ TP_PROTO(struct ufs_hba *hba, enum ufs_trace_str_t str_t, void *hdr,
+ void *tsf, enum ufs_trace_tsf_t tsf_t),
+
+ TP_ARGS(hba, str_t, hdr, tsf, tsf_t),
+
+ TP_STRUCT__entry(
+ __field(struct ufs_hba *, hba)
+ __string(dev_name, dev_name(hba->dev))
+ __field(enum ufs_trace_str_t, str_t)
+ __array(unsigned char, hdr, 12)
+ __array(unsigned char, tsf, 16)
+ __field(enum ufs_trace_tsf_t, tsf_t)
+ ),
+
+ TP_fast_assign(
+ __entry->hba = hba;
+ __assign_str(dev_name);
+ __entry->str_t = str_t;
+ memcpy(__entry->hdr, hdr, sizeof(__entry->hdr));
+ memcpy(__entry->tsf, tsf, sizeof(__entry->tsf));
+ __entry->tsf_t = tsf_t;
+ ),
+
+ TP_printk(
+ "%s: %s: HDR:%s, %s:%s",
+ show_ufs_cmd_trace_str(__entry->str_t), __get_str(dev_name),
+ __print_hex(__entry->hdr, sizeof(__entry->hdr)),
+ show_ufs_cmd_trace_tsf(__entry->tsf_t),
+ __print_hex(__entry->tsf, sizeof(__entry->tsf))
+ )
+);
+
+TRACE_EVENT(ufshcd_exception_event,
+
+ TP_PROTO(struct ufs_hba *hba, u16 status),
+
+ TP_ARGS(hba, status),
+
+ TP_STRUCT__entry(
+ __field(struct ufs_hba *, hba)
+ __string(dev_name, dev_name(hba->dev))
+ __field(u16, status)
+ ),
+
+ TP_fast_assign(
+ __entry->hba = hba;
+ __assign_str(dev_name);
+ __entry->status = status;
+ ),
+
+ TP_printk("%s: status 0x%x",
+ __get_str(dev_name), __entry->status
+ )
+);
+
+#endif /* if !defined(_TRACE_UFS_H) || defined(TRACE_HEADER_MULTI_READ) */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH ../../drivers/ufs/core
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE ufs_trace
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
diff --git a/drivers/ufs/core/ufs_trace_types.h b/drivers/ufs/core/ufs_trace_types.h
new file mode 100644
index 000000000..bf821970f
--- /dev/null
+++ b/drivers/ufs/core/ufs_trace_types.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _UFS_TRACE_TYPES_H_
+#define _UFS_TRACE_TYPES_H_
+
+enum ufs_trace_str_t {
+ UFS_CMD_SEND,
+ UFS_CMD_COMP,
+ UFS_QUERY_SEND,
+ UFS_QUERY_COMP,
+ UFS_QUERY_ERR,
+ UFS_TM_SEND,
+ UFS_TM_COMP,
+ UFS_TM_ERR
+};
+
+enum ufs_trace_tsf_t {
+ UFS_TSF_CDB,
+ UFS_TSF_OSF,
+ UFS_TSF_TM_INPUT,
+ UFS_TSF_TM_OUTPUT
+};
+
+#endif /* _UFS_TRACE_TYPES_H_ */
diff --git a/drivers/ufs/core/ufshcd-crypto.c b/drivers/ufs/core/ufshcd-crypto.c
new file mode 100644
index 000000000..9e63a9d3c
--- /dev/null
+++ b/drivers/ufs/core/ufshcd-crypto.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 Google LLC
+ */
+
+#include <ufs/ufshcd.h>
+#include "ufshcd-crypto.h"
+
+/* Blk-crypto modes supported by UFS crypto */
+static const struct ufs_crypto_alg_entry {
+ enum ufs_crypto_alg ufs_alg;
+ enum ufs_crypto_key_size ufs_key_size;
+} ufs_crypto_algs[BLK_ENCRYPTION_MODE_MAX] = {
+ [BLK_ENCRYPTION_MODE_AES_256_XTS] = {
+ .ufs_alg = UFS_CRYPTO_ALG_AES_XTS,
+ .ufs_key_size = UFS_CRYPTO_KEY_SIZE_256,
+ },
+};
+
+static void ufshcd_program_key(struct ufs_hba *hba,
+ const union ufs_crypto_cfg_entry *cfg, int slot)
+{
+ int i;
+ u32 slot_offset = hba->crypto_cfg_register + slot * sizeof(*cfg);
+
+ ufshcd_hold(hba);
+
+ /* Ensure that CFGE is cleared before programming the key */
+ ufshcd_writel(hba, 0, slot_offset + 16 * sizeof(cfg->reg_val[0]));
+ for (i = 0; i < 16; i++) {
+ ufshcd_writel(hba, le32_to_cpu(cfg->reg_val[i]),
+ slot_offset + i * sizeof(cfg->reg_val[0]));
+ }
+ /* Write dword 17 */
+ ufshcd_writel(hba, le32_to_cpu(cfg->reg_val[17]),
+ slot_offset + 17 * sizeof(cfg->reg_val[0]));
+ /* Dword 16 must be written last */
+ ufshcd_writel(hba, le32_to_cpu(cfg->reg_val[16]),
+ slot_offset + 16 * sizeof(cfg->reg_val[0]));
+ ufshcd_release(hba);
+}
+
+static int ufshcd_crypto_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);
+ const union ufs_crypto_cap_entry *ccap_array = hba->crypto_cap_array;
+ const struct ufs_crypto_alg_entry *alg =
+ &ufs_crypto_algs[key->crypto_cfg.crypto_mode];
+ u8 data_unit_mask = key->crypto_cfg.data_unit_size / 512;
+ int i;
+ int cap_idx = -1;
+ union ufs_crypto_cfg_entry cfg = {};
+
+ BUILD_BUG_ON(UFS_CRYPTO_KEY_SIZE_INVALID != 0);
+ for (i = 0; i < hba->crypto_capabilities.num_crypto_cap; i++) {
+ if (ccap_array[i].algorithm_id == alg->ufs_alg &&
+ ccap_array[i].key_size == alg->ufs_key_size &&
+ (ccap_array[i].sdus_mask & data_unit_mask)) {
+ cap_idx = i;
+ break;
+ }
+ }
+
+ if (WARN_ON(cap_idx < 0))
+ return -EOPNOTSUPP;
+
+ cfg.data_unit_size = data_unit_mask;
+ cfg.crypto_cap_idx = cap_idx;
+ cfg.config_enable = UFS_CRYPTO_CONFIGURATION_ENABLE;
+
+ if (ccap_array[cap_idx].algorithm_id == UFS_CRYPTO_ALG_AES_XTS) {
+ /* In XTS mode, the blk_crypto_key's size is already doubled */
+ memcpy(cfg.crypto_key, key->bytes, key->size/2);
+ memcpy(cfg.crypto_key + UFS_CRYPTO_KEY_MAX_SIZE/2,
+ key->bytes + key->size/2, key->size/2);
+ } else {
+ memcpy(cfg.crypto_key, key->bytes, key->size);
+ }
+
+ ufshcd_program_key(hba, &cfg, slot);
+
+ memzero_explicit(&cfg, sizeof(cfg));
+ return 0;
+}
+
+static int ufshcd_crypto_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);
+ /*
+ * Clear the crypto cfg on the device. Clearing CFGE
+ * might not be sufficient, so just clear the entire cfg.
+ */
+ union ufs_crypto_cfg_entry cfg = {};
+
+ ufshcd_program_key(hba, &cfg, slot);
+ return 0;
+}
+
+/*
+ * Reprogram the keyslots if needed, and return true if CRYPTO_GENERAL_ENABLE
+ * should be used in the host controller initialization sequence.
+ */
+bool ufshcd_crypto_enable(struct ufs_hba *hba)
+{
+ if (!(hba->caps & UFSHCD_CAP_CRYPTO))
+ return false;
+
+ /* Reset might clear all keys, so reprogram all the keys. */
+ blk_crypto_reprogram_all_keys(&hba->crypto_profile);
+
+ if (hba->quirks & UFSHCD_QUIRK_BROKEN_CRYPTO_ENABLE)
+ return false;
+
+ return true;
+}
+
+static const struct blk_crypto_ll_ops ufshcd_crypto_ops = {
+ .keyslot_program = ufshcd_crypto_keyslot_program,
+ .keyslot_evict = ufshcd_crypto_keyslot_evict,
+};
+
+static enum blk_crypto_mode_num
+ufshcd_find_blk_crypto_mode(union ufs_crypto_cap_entry cap)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ufs_crypto_algs); i++) {
+ BUILD_BUG_ON(UFS_CRYPTO_KEY_SIZE_INVALID != 0);
+ if (ufs_crypto_algs[i].ufs_alg == cap.algorithm_id &&
+ ufs_crypto_algs[i].ufs_key_size == cap.key_size) {
+ return i;
+ }
+ }
+ return BLK_ENCRYPTION_MODE_INVALID;
+}
+
+/**
+ * ufshcd_hba_init_crypto_capabilities - Read crypto capabilities, init crypto
+ * fields in hba
+ * @hba: Per adapter instance
+ *
+ * Return: 0 if crypto was initialized or is not supported, else a -errno value.
+ */
+int ufshcd_hba_init_crypto_capabilities(struct ufs_hba *hba)
+{
+ int cap_idx;
+ int err = 0;
+ enum blk_crypto_mode_num blk_mode_num;
+
+ if (hba->quirks & UFSHCD_QUIRK_CUSTOM_CRYPTO_PROFILE)
+ return 0;
+
+ /*
+ * Don't use crypto if either the hardware doesn't advertise the
+ * standard crypto capability bit *or* if the vendor specific driver
+ * hasn't advertised that crypto is supported.
+ */
+ if (!(hba->capabilities & MASK_CRYPTO_SUPPORT) ||
+ !(hba->caps & UFSHCD_CAP_CRYPTO))
+ goto out;
+
+ hba->crypto_capabilities.reg_val =
+ cpu_to_le32(ufshcd_readl(hba, REG_UFS_CCAP));
+ hba->crypto_cfg_register =
+ (u32)hba->crypto_capabilities.config_array_ptr * 0x100;
+ hba->crypto_cap_array =
+ devm_kcalloc(hba->dev, hba->crypto_capabilities.num_crypto_cap,
+ sizeof(hba->crypto_cap_array[0]), GFP_KERNEL);
+ if (!hba->crypto_cap_array) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ /* The actual number of configurations supported is (CFGC+1) */
+ err = devm_blk_crypto_profile_init(
+ hba->dev, &hba->crypto_profile,
+ hba->crypto_capabilities.config_count + 1);
+ if (err)
+ goto out;
+
+ hba->crypto_profile.ll_ops = ufshcd_crypto_ops;
+ /* UFS only supports 8 bytes for any DUN */
+ hba->crypto_profile.max_dun_bytes_supported = 8;
+ hba->crypto_profile.key_types_supported = BLK_CRYPTO_KEY_TYPE_RAW;
+ hba->crypto_profile.dev = hba->dev;
+
+ /*
+ * Cache all the UFS crypto capabilities and advertise the supported
+ * crypto modes and data unit sizes to the block layer.
+ */
+ for (cap_idx = 0; cap_idx < hba->crypto_capabilities.num_crypto_cap;
+ cap_idx++) {
+ hba->crypto_cap_array[cap_idx].reg_val =
+ cpu_to_le32(ufshcd_readl(hba,
+ REG_UFS_CRYPTOCAP +
+ cap_idx * sizeof(__le32)));
+ blk_mode_num = ufshcd_find_blk_crypto_mode(
+ hba->crypto_cap_array[cap_idx]);
+ if (blk_mode_num != BLK_ENCRYPTION_MODE_INVALID)
+ hba->crypto_profile.modes_supported[blk_mode_num] |=
+ hba->crypto_cap_array[cap_idx].sdus_mask * 512;
+ }
+
+ return 0;
+
+out:
+ /* Indicate that init failed by clearing UFSHCD_CAP_CRYPTO */
+ hba->caps &= ~UFSHCD_CAP_CRYPTO;
+ return err;
+}
+
+/**
+ * ufshcd_init_crypto - Initialize crypto hardware
+ * @hba: Per adapter instance
+ */
+void ufshcd_init_crypto(struct ufs_hba *hba)
+{
+ int slot;
+
+ if (!(hba->caps & UFSHCD_CAP_CRYPTO))
+ return;
+
+ /* Clear all keyslots. */
+ for (slot = 0; slot < hba->crypto_profile.num_slots; slot++)
+ hba->crypto_profile.ll_ops.keyslot_evict(&hba->crypto_profile,
+ NULL, slot);
+}
+
+void ufshcd_crypto_register(struct ufs_hba *hba, struct request_queue *q)
+{
+ if (hba->caps & UFSHCD_CAP_CRYPTO)
+ blk_crypto_register(&hba->crypto_profile, q);
+}
diff --git a/drivers/ufs/core/ufshcd-crypto.h b/drivers/ufs/core/ufshcd-crypto.h
new file mode 100644
index 000000000..8f66db94e
--- /dev/null
+++ b/drivers/ufs/core/ufshcd-crypto.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2019 Google LLC
+ */
+
+#ifndef _UFSHCD_CRYPTO_H
+#define _UFSHCD_CRYPTO_H
+
+#include <scsi/scsi_cmnd.h>
+#include <ufs/ufshcd.h>
+#include <ufs/ufshci.h>
+
+#ifdef CONFIG_SCSI_UFS_CRYPTO
+
+static inline void ufshcd_prepare_lrbp_crypto(struct request *rq,
+ struct ufshcd_lrb *lrbp)
+{
+ if (!rq || !rq->crypt_keyslot) {
+ lrbp->crypto_key_slot = -1;
+ return;
+ }
+
+ lrbp->crypto_key_slot = blk_crypto_keyslot_index(rq->crypt_keyslot);
+ lrbp->data_unit_num = rq->crypt_ctx->bc_dun[0];
+}
+
+static inline void
+ufshcd_prepare_req_desc_hdr_crypto(struct ufshcd_lrb *lrbp,
+ struct request_desc_header *h)
+{
+ if (lrbp->crypto_key_slot < 0)
+ return;
+ h->enable_crypto = 1;
+ h->cci = lrbp->crypto_key_slot;
+ h->dunl = cpu_to_le32(lower_32_bits(lrbp->data_unit_num));
+ h->dunu = cpu_to_le32(upper_32_bits(lrbp->data_unit_num));
+}
+
+static inline int ufshcd_crypto_fill_prdt(struct ufs_hba *hba,
+ struct scsi_cmnd *cmd)
+{
+ const struct bio_crypt_ctx *crypt_ctx = scsi_cmd_to_rq(cmd)->crypt_ctx;
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+
+ if (crypt_ctx && hba->vops && hba->vops->fill_crypto_prdt)
+ return hba->vops->fill_crypto_prdt(hba, crypt_ctx,
+ lrbp->ucd_prdt_ptr,
+ scsi_sg_count(cmd));
+ return 0;
+}
+
+static inline void ufshcd_crypto_clear_prdt(struct ufs_hba *hba,
+ struct scsi_cmnd *cmd)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+
+ if (!(hba->quirks & UFSHCD_QUIRK_KEYS_IN_PRDT))
+ return;
+
+ if (!(scsi_cmd_to_rq(cmd)->crypt_ctx))
+ return;
+
+ /* Zeroize the PRDT because it can contain cryptographic keys. */
+ memzero_explicit(lrbp->ucd_prdt_ptr,
+ ufshcd_sg_entry_size(hba) * scsi_sg_count(cmd));
+}
+
+bool ufshcd_crypto_enable(struct ufs_hba *hba);
+
+int ufshcd_hba_init_crypto_capabilities(struct ufs_hba *hba);
+
+void ufshcd_init_crypto(struct ufs_hba *hba);
+
+void ufshcd_crypto_register(struct ufs_hba *hba, struct request_queue *q);
+
+#else /* CONFIG_SCSI_UFS_CRYPTO */
+
+static inline void ufshcd_prepare_lrbp_crypto(struct request *rq,
+ struct ufshcd_lrb *lrbp) { }
+
+static inline void
+ufshcd_prepare_req_desc_hdr_crypto(struct ufshcd_lrb *lrbp,
+ struct request_desc_header *h) { }
+
+static inline int ufshcd_crypto_fill_prdt(struct ufs_hba *hba,
+ struct scsi_cmnd *cmd)
+{
+ return 0;
+}
+
+static inline void ufshcd_crypto_clear_prdt(struct ufs_hba *hba,
+ struct scsi_cmnd *cmd)
+{
+}
+
+static inline bool ufshcd_crypto_enable(struct ufs_hba *hba)
+{
+ return false;
+}
+
+static inline int ufshcd_hba_init_crypto_capabilities(struct ufs_hba *hba)
+{
+ return 0;
+}
+
+static inline void ufshcd_init_crypto(struct ufs_hba *hba) { }
+
+static inline void ufshcd_crypto_register(struct ufs_hba *hba,
+ struct request_queue *q) { }
+
+#endif /* CONFIG_SCSI_UFS_CRYPTO */
+
+#endif /* _UFSHCD_CRYPTO_H */
diff --git a/drivers/ufs/core/ufshcd-priv.h b/drivers/ufs/core/ufshcd-priv.h
new file mode 100644
index 000000000..e55c2a02c
--- /dev/null
+++ b/drivers/ufs/core/ufshcd-priv.h
@@ -0,0 +1,509 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _UFSHCD_PRIV_H_
+#define _UFSHCD_PRIV_H_
+
+#include <linux/pm_runtime.h>
+#include <ufs/ufshcd.h>
+
+void ufshcd_enable_intr(struct ufs_hba *hba, u32 intrs);
+
+static inline bool ufshcd_is_user_access_allowed(struct ufs_hba *hba)
+{
+ return !hba->shutting_down;
+}
+
+void ufshcd_schedule_eh_work(struct ufs_hba *hba);
+
+static inline bool ufshcd_keep_autobkops_enabled_except_suspend(
+ struct ufs_hba *hba)
+{
+ return hba->caps & UFSHCD_CAP_KEEP_AUTO_BKOPS_ENABLED_EXCEPT_SUSPEND;
+}
+
+static inline u8 ufshcd_wb_get_query_index(struct ufs_hba *hba)
+{
+ if (hba->dev_info.wb_buffer_type == WB_BUF_MODE_LU_DEDICATED)
+ return hba->dev_info.wb_dedicated_lu;
+ return 0;
+}
+
+static inline bool ufshcd_is_wb_buf_flush_allowed(struct ufs_hba *hba)
+{
+ return ufshcd_is_wb_allowed(hba) &&
+ !(hba->quirks & UFSHCI_QUIRK_SKIP_MANUAL_WB_FLUSH_CTRL);
+}
+
+#ifdef CONFIG_SCSI_UFS_HWMON
+void ufs_hwmon_probe(struct ufs_hba *hba, u8 mask);
+void ufs_hwmon_remove(struct ufs_hba *hba);
+void ufs_hwmon_notify_event(struct ufs_hba *hba, u8 ee_mask);
+#else
+static inline void ufs_hwmon_probe(struct ufs_hba *hba, u8 mask) {}
+static inline void ufs_hwmon_remove(struct ufs_hba *hba) {}
+static inline void ufs_hwmon_notify_event(struct ufs_hba *hba, u8 ee_mask) {}
+#endif
+
+int ufshcd_query_descriptor_retry(struct ufs_hba *hba,
+ enum query_opcode opcode,
+ enum desc_idn idn, u8 index,
+ u8 selector,
+ u8 *desc_buf, int *buf_len);
+int ufshcd_read_desc_param(struct ufs_hba *hba,
+ enum desc_idn desc_id,
+ int desc_index,
+ u8 param_offset,
+ u8 *param_read_buf,
+ u8 param_size);
+int ufshcd_query_attr_retry(struct ufs_hba *hba, enum query_opcode opcode,
+ enum attr_idn idn, u8 index, u8 selector,
+ u32 *attr_val);
+int ufshcd_query_attr(struct ufs_hba *hba, enum query_opcode opcode,
+ enum attr_idn idn, u8 index, u8 selector, u32 *attr_val);
+int ufshcd_query_attr_qword(struct ufs_hba *hba, enum query_opcode opcode,
+ enum attr_idn idn, u8 index, u8 sel, u64 *attr_val);
+int ufshcd_query_flag(struct ufs_hba *hba, enum query_opcode opcode,
+ enum flag_idn idn, u8 index, bool *flag_res);
+void ufshcd_auto_hibern8_update(struct ufs_hba *hba, u32 ahit);
+void ufshcd_compl_one_cqe(struct ufs_hba *hba, int task_tag,
+ struct cq_entry *cqe);
+int ufshcd_mcq_init(struct ufs_hba *hba);
+void ufshcd_mcq_disable(struct ufs_hba *hba);
+int ufshcd_get_hba_mac(struct ufs_hba *hba);
+int ufshcd_mcq_memory_alloc(struct ufs_hba *hba);
+struct ufs_hw_queue *ufshcd_mcq_req_to_hwq(struct ufs_hba *hba,
+ struct request *req);
+void ufshcd_mcq_compl_all_cqes_lock(struct ufs_hba *hba,
+ struct ufs_hw_queue *hwq);
+bool ufshcd_cmd_inflight(struct scsi_cmnd *cmd);
+int ufshcd_mcq_sq_cleanup(struct ufs_hba *hba, int task_tag);
+int ufshcd_mcq_abort(struct scsi_cmnd *cmd);
+u32 ufshcd_mcq_read_mcqiacr(struct ufs_hba *hba, int i);
+void ufshcd_mcq_write_mcqiacr(struct ufs_hba *hba, u32 val, int i);
+int ufshcd_try_to_abort_task(struct ufs_hba *hba, int tag);
+void ufshcd_release_scsi_cmd(struct ufs_hba *hba, struct scsi_cmnd *cmd);
+int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us);
+void ufshcd_resume_command_processing(struct ufs_hba *hba);
+int ufshcd_scale_clks(struct ufs_hba *hba, unsigned long freq, bool scale_up);
+
+/**
+ * enum ufs_descr_fmt - UFS string descriptor format
+ * @SD_RAW: Raw UTF-16 format
+ * @SD_ASCII_STD: Convert to null-terminated ASCII string
+ */
+enum ufs_descr_fmt {
+ SD_RAW = 0,
+ SD_ASCII_STD = 1,
+};
+
+int ufshcd_read_string_desc(struct ufs_hba *hba, u8 desc_index, u8 **buf, enum ufs_descr_fmt fmt);
+int ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd);
+int ufshcd_send_bsg_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd);
+
+int ufshcd_exec_raw_upiu_cmd(struct ufs_hba *hba,
+ struct utp_upiu_req *req_upiu,
+ struct utp_upiu_req *rsp_upiu,
+ enum upiu_request_transaction msgcode,
+ u8 *desc_buff, int *buff_len,
+ enum query_opcode desc_op);
+
+int ufshcd_wb_toggle(struct ufs_hba *hba, bool enable);
+
+int ufshcd_uic_tx_eqtr(struct ufs_hba *hba, int gear);
+void ufshcd_apply_valid_tx_eq_settings(struct ufs_hba *hba);
+int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ bool force_tx_eqtr);
+void ufshcd_print_tx_eq_params(struct ufs_hba *hba);
+bool ufshcd_is_txeq_presets_used(struct ufs_hba *hba);
+bool ufshcd_is_txeq_preset_selected(u8 preshoot, u8 deemphasis);
+int ufshcd_retrain_tx_eq(struct ufs_hba *hba, u32 gear);
+void ufshcd_retrieve_tx_eq_settings(struct ufs_hba *hba);
+void ufshcd_store_tx_eq_settings(struct ufs_hba *hba);
+
+/* Wrapper functions for safely calling variant operations */
+static inline const char *ufshcd_get_var_name(struct ufs_hba *hba)
+{
+ if (hba->vops)
+ return hba->vops->name;
+ return "";
+}
+
+static inline void ufshcd_vops_exit(struct ufs_hba *hba)
+{
+ if (hba->vops && hba->vops->exit)
+ hba->vops->exit(hba);
+}
+
+static inline u32 ufshcd_vops_get_ufs_hci_version(struct ufs_hba *hba)
+{
+ if (hba->vops && hba->vops->get_ufs_hci_version)
+ return hba->vops->get_ufs_hci_version(hba);
+
+ return ufshcd_readl(hba, REG_UFS_VERSION);
+}
+
+static inline int ufshcd_vops_clk_scale_notify(struct ufs_hba *hba, bool up,
+ unsigned long target_freq,
+ enum ufs_notify_change_status status)
+{
+ if (hba->vops && hba->vops->clk_scale_notify)
+ return hba->vops->clk_scale_notify(hba, up, target_freq, status);
+ return 0;
+}
+
+static inline void ufshcd_vops_event_notify(struct ufs_hba *hba,
+ enum ufs_event_type evt,
+ void *data)
+{
+ if (hba->vops && hba->vops->event_notify)
+ hba->vops->event_notify(hba, evt, data);
+}
+
+static inline int ufshcd_vops_setup_clocks(struct ufs_hba *hba, bool on,
+ enum ufs_notify_change_status status)
+{
+ if (hba->vops && hba->vops->setup_clocks)
+ return hba->vops->setup_clocks(hba, on, status);
+ return 0;
+}
+
+static inline int ufshcd_vops_hce_enable_notify(struct ufs_hba *hba,
+ bool status)
+{
+ if (hba->vops && hba->vops->hce_enable_notify)
+ return hba->vops->hce_enable_notify(hba, status);
+
+ return 0;
+}
+static inline int ufshcd_vops_link_startup_notify(struct ufs_hba *hba,
+ bool status)
+{
+ if (hba->vops && hba->vops->link_startup_notify)
+ return hba->vops->link_startup_notify(hba, status);
+
+ return 0;
+}
+
+static inline int ufshcd_vops_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ if (hba->vops && hba->vops->negotiate_pwr_mode)
+ return hba->vops->negotiate_pwr_mode(hba, dev_max_params,
+ dev_req_params);
+
+ return -ENOTSUPP;
+}
+
+static inline int ufshcd_vops_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ if (hba->vops && hba->vops->pwr_change_notify)
+ return hba->vops->pwr_change_notify(hba, status,
+ dev_req_params);
+
+ return -ENOTSUPP;
+}
+
+static inline void ufshcd_vops_setup_task_mgmt(struct ufs_hba *hba,
+ int tag, u8 tm_function)
+{
+ if (hba->vops && hba->vops->setup_task_mgmt)
+ hba->vops->setup_task_mgmt(hba, tag, tm_function);
+}
+
+static inline void ufshcd_vops_hibern8_notify(struct ufs_hba *hba,
+ enum uic_cmd_dme cmd,
+ enum ufs_notify_change_status status)
+{
+ if (hba->vops && hba->vops->hibern8_notify)
+ hba->vops->hibern8_notify(hba, cmd, status);
+}
+
+static inline int ufshcd_vops_apply_dev_quirks(struct ufs_hba *hba)
+{
+ if (hba->vops && hba->vops->apply_dev_quirks)
+ return hba->vops->apply_dev_quirks(hba);
+ return 0;
+}
+
+static inline void ufshcd_vops_fixup_dev_quirks(struct ufs_hba *hba)
+{
+ if (hba->vops && hba->vops->fixup_dev_quirks)
+ hba->vops->fixup_dev_quirks(hba);
+}
+
+static inline int ufshcd_vops_suspend(struct ufs_hba *hba, enum ufs_pm_op op,
+ enum ufs_notify_change_status status)
+{
+ if (hba->vops && hba->vops->suspend)
+ return hba->vops->suspend(hba, op, status);
+
+ return 0;
+}
+
+static inline int ufshcd_vops_resume(struct ufs_hba *hba, enum ufs_pm_op op)
+{
+ if (hba->vops && hba->vops->resume)
+ return hba->vops->resume(hba, op);
+
+ return 0;
+}
+
+static inline void ufshcd_vops_dbg_register_dump(struct ufs_hba *hba)
+{
+ if (hba->vops && hba->vops->dbg_register_dump)
+ hba->vops->dbg_register_dump(hba);
+}
+
+static inline int ufshcd_vops_device_reset(struct ufs_hba *hba)
+{
+ if (hba->vops && hba->vops->device_reset)
+ return hba->vops->device_reset(hba);
+
+ return -EOPNOTSUPP;
+}
+
+static inline void ufshcd_vops_config_scaling_param(struct ufs_hba *hba,
+ struct devfreq_dev_profile *p,
+ struct devfreq_simple_ondemand_data *data)
+{
+ if (hba->vops && hba->vops->config_scaling_param)
+ hba->vops->config_scaling_param(hba, p, data);
+}
+
+static inline int ufshcd_vops_mcq_config_resource(struct ufs_hba *hba)
+{
+ if (hba->vops && hba->vops->mcq_config_resource)
+ return hba->vops->mcq_config_resource(hba);
+
+ return -EOPNOTSUPP;
+}
+
+static inline int ufshcd_mcq_vops_op_runtime_config(struct ufs_hba *hba)
+{
+ if (hba->vops && hba->vops->op_runtime_config)
+ return hba->vops->op_runtime_config(hba);
+
+ return -EOPNOTSUPP;
+}
+
+static inline int ufshcd_vops_get_outstanding_cqs(struct ufs_hba *hba,
+ unsigned long *ocqs)
+{
+ if (hba->vops && hba->vops->get_outstanding_cqs)
+ return hba->vops->get_outstanding_cqs(hba, ocqs);
+
+ return -EOPNOTSUPP;
+}
+
+static inline int ufshcd_mcq_vops_config_esi(struct ufs_hba *hba)
+{
+ if (hba->vops && hba->vops->config_esi)
+ return hba->vops->config_esi(hba);
+
+ return -EOPNOTSUPP;
+}
+
+static inline u32 ufshcd_vops_freq_to_gear_speed(struct ufs_hba *hba, unsigned long freq)
+{
+ if (hba->vops && hba->vops->freq_to_gear_speed)
+ return hba->vops->freq_to_gear_speed(hba, freq);
+
+ return 0;
+}
+
+static inline int ufshcd_vops_get_rx_fom(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ if (hba->vops && hba->vops->get_rx_fom)
+ return hba->vops->get_rx_fom(hba, pwr_mode, h_iter, d_iter);
+
+ return 0;
+}
+
+static inline int ufshcd_vops_apply_tx_eqtr_settings(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ if (hba->vops && hba->vops->apply_tx_eqtr_settings)
+ return hba->vops->apply_tx_eqtr_settings(hba, pwr_mode, h_iter, d_iter);
+
+ return 0;
+}
+
+static inline int ufshcd_vops_tx_eqtr_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ if (hba->vops && hba->vops->tx_eqtr_notify)
+ return hba->vops->tx_eqtr_notify(hba, status, pwr_mode);
+
+ return 0;
+}
+
+extern const struct ufs_pm_lvl_states ufs_pm_lvl_states[];
+
+/**
+ * ufshcd_scsi_to_upiu_lun - maps scsi LUN to UPIU LUN
+ * @scsi_lun: scsi LUN id
+ *
+ * Return: UPIU LUN id
+ */
+static inline u8 ufshcd_scsi_to_upiu_lun(unsigned int scsi_lun)
+{
+ if (scsi_is_wlun(scsi_lun))
+ return (scsi_lun & UFS_UPIU_MAX_UNIT_NUM_ID)
+ | UFS_UPIU_WLUN_ID;
+ else
+ return scsi_lun & UFS_UPIU_MAX_UNIT_NUM_ID;
+}
+
+int __ufshcd_write_ee_control(struct ufs_hba *hba, u32 ee_ctrl_mask);
+int ufshcd_write_ee_control(struct ufs_hba *hba);
+int ufshcd_update_ee_control(struct ufs_hba *hba, u16 *mask,
+ const u16 *other_mask, u16 set, u16 clr);
+
+static inline int ufshcd_update_ee_drv_mask(struct ufs_hba *hba,
+ u16 set, u16 clr)
+{
+ return ufshcd_update_ee_control(hba, &hba->ee_drv_mask,
+ &hba->ee_usr_mask, set, clr);
+}
+
+static inline int ufshcd_update_ee_usr_mask(struct ufs_hba *hba,
+ u16 set, u16 clr)
+{
+ return ufshcd_update_ee_control(hba, &hba->ee_usr_mask,
+ &hba->ee_drv_mask, set, clr);
+}
+
+static inline int ufshcd_rpm_get_sync(struct ufs_hba *hba)
+{
+ return pm_runtime_get_sync(&hba->ufs_device_wlun->sdev_gendev);
+}
+
+static inline int ufshcd_rpm_get_if_active(struct ufs_hba *hba)
+{
+ return pm_runtime_get_if_active(&hba->ufs_device_wlun->sdev_gendev);
+}
+
+static inline int ufshcd_rpm_put_sync(struct ufs_hba *hba)
+{
+ return pm_runtime_put_sync(&hba->ufs_device_wlun->sdev_gendev);
+}
+
+static inline void ufshcd_rpm_get_noresume(struct ufs_hba *hba)
+{
+ pm_runtime_get_noresume(&hba->ufs_device_wlun->sdev_gendev);
+}
+
+static inline int ufshcd_rpm_resume(struct ufs_hba *hba)
+{
+ return pm_runtime_resume(&hba->ufs_device_wlun->sdev_gendev);
+}
+
+static inline void ufshcd_rpm_put(struct ufs_hba *hba)
+{
+ pm_runtime_put(&hba->ufs_device_wlun->sdev_gendev);
+}
+
+/**
+ * ufs_is_valid_unit_desc_lun - checks if the given LUN has a unit descriptor
+ * @dev_info: pointer of instance of struct ufs_dev_info
+ * @lun: LU number to check
+ * @return: true if the lun has a matching unit descriptor, false otherwise
+ */
+static inline bool ufs_is_valid_unit_desc_lun(struct ufs_dev_info *dev_info, u8 lun)
+{
+ if (!dev_info || !dev_info->max_lu_supported) {
+ pr_err("Max General LU supported by UFS isn't initialized\n");
+ return false;
+ }
+ return lun == UFS_UPIU_RPMB_WLUN || (lun < dev_info->max_lu_supported);
+}
+
+/*
+ * Convert a block layer tag into a SCSI command pointer. This function is
+ * called once per I/O completion path and is also called from error paths.
+ */
+static inline struct scsi_cmnd *ufshcd_tag_to_cmd(struct ufs_hba *hba, u32 tag)
+{
+ struct blk_mq_tags *tags = hba->host->tag_set.shared_tags;
+ struct request *rq = blk_mq_tag_to_rq(tags, tag);
+
+ if (WARN_ON_ONCE(!rq))
+ return NULL;
+
+ return blk_mq_rq_to_pdu(rq);
+}
+
+static inline void ufshcd_inc_sq_tail(struct ufs_hw_queue *q)
+ __must_hold(&q->sq_lock)
+{
+ u32 val;
+
+ q->sq_tail_slot++;
+ if (q->sq_tail_slot == q->max_entries)
+ q->sq_tail_slot = 0;
+ val = q->sq_tail_slot * sizeof(struct utp_transfer_req_desc);
+ writel(val, q->mcq_sq_tail);
+}
+
+static inline void ufshcd_mcq_update_cq_tail_slot(struct ufs_hw_queue *q)
+{
+ u32 val = readl(q->mcq_cq_tail);
+
+ q->cq_tail_slot = val / sizeof(struct cq_entry);
+}
+
+static inline bool ufshcd_mcq_is_cq_empty(struct ufs_hw_queue *q)
+{
+ return q->cq_head_slot == q->cq_tail_slot;
+}
+
+static inline void ufshcd_mcq_inc_cq_head_slot(struct ufs_hw_queue *q)
+{
+ q->cq_head_slot++;
+ if (q->cq_head_slot == q->max_entries)
+ q->cq_head_slot = 0;
+}
+
+static inline void ufshcd_mcq_update_cq_head(struct ufs_hw_queue *q)
+{
+ writel(q->cq_head_slot * sizeof(struct cq_entry), q->mcq_cq_head);
+}
+
+static inline struct cq_entry *ufshcd_mcq_cur_cqe(struct ufs_hw_queue *q)
+{
+ struct cq_entry *cqe = q->cqe_base_addr;
+
+ return cqe + q->cq_head_slot;
+}
+
+static inline u32 ufshcd_mcq_get_sq_head_slot(struct ufs_hw_queue *q)
+{
+ u32 val = readl(q->mcq_sq_head);
+
+ return val / sizeof(struct utp_transfer_req_desc);
+}
+
+#if IS_ENABLED(CONFIG_RPMB)
+int ufs_rpmb_probe(struct ufs_hba *hba);
+void ufs_rpmb_remove(struct ufs_hba *hba);
+#else
+static inline int ufs_rpmb_probe(struct ufs_hba *hba)
+{
+ return 0;
+}
+static inline void ufs_rpmb_remove(struct ufs_hba *hba)
+{
+}
+#endif
+
+#endif /* _UFSHCD_PRIV_H_ */
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
new file mode 100644
index 000000000..2ba244cf4
--- /dev/null
+++ b/drivers/ufs/core/ufshcd.c
@@ -0,0 +1,11604 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Flash Storage Host controller driver Core
+ * Copyright (C) 2011-2013 Samsung India Software Operations
+ * Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
+ *
+ * Authors:
+ * Santosh Yaraganavi <santosh.sy@samsung.com>
+ * Vinayak Holikatti <h.vinayak@samsung.com>
+ */
+
+#include <linux/async.h>
+#include <linux/devfreq.h>
+#include <linux/nls.h>
+#include <linux/of.h>
+#include <linux/bitfield.h>
+#include <linux/blk-pm.h>
+#include <linux/blkdev.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/hex.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pm_opp.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sched/clock.h>
+#include <linux/sizes.h>
+#include <linux/iopoll.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_dbg.h>
+#include <scsi/scsi_driver.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_tcq.h>
+#include "ufshcd-priv.h"
+#include <ufs/ufs_quirks.h>
+#include <ufs/unipro.h>
+#include "ufs-sysfs.h"
+#include "ufs-debugfs.h"
+#include "ufs-fault-injection.h"
+#include "ufs_bsg.h"
+#include "ufshcd-crypto.h"
+#include <linux/unaligned.h>
+
+#define CREATE_TRACE_POINTS
+#include "ufs_trace.h"
+
+#define UFSHCD_ENABLE_INTRS (UTP_TRANSFER_REQ_COMPL |\
+ UTP_TASK_REQ_COMPL |\
+ UFSHCD_ERROR_MASK)
+
+/* UIC command timeout, unit: ms */
+enum {
+ UIC_CMD_TIMEOUT_DEFAULT = 500,
+ UIC_CMD_TIMEOUT_MAX = 5000,
+};
+/* NOP OUT retries waiting for NOP IN response */
+#define NOP_OUT_RETRIES 10
+/* Timeout after 50 msecs if NOP OUT hangs without response */
+#define NOP_OUT_TIMEOUT 50 /* msecs */
+
+/* Query request retries */
+#define QUERY_REQ_RETRIES 3
+/* Query request timeout */
+enum {
+ QUERY_REQ_TIMEOUT_MIN = 1,
+ QUERY_REQ_TIMEOUT_DEFAULT = 1500,
+ QUERY_REQ_TIMEOUT_MAX = 30000
+};
+
+/* Advanced RPMB request timeout */
+#define ADVANCED_RPMB_REQ_TIMEOUT 3000 /* 3 seconds */
+
+/* Task management command timeout */
+#define TM_CMD_TIMEOUT 100 /* msecs */
+
+/* maximum number of retries for a general UIC command */
+#define UFS_UIC_COMMAND_RETRIES 3
+
+/* maximum number of link-startup retries */
+#define DME_LINKSTARTUP_RETRIES 3
+
+/* maximum number of reset retries before giving up */
+#define MAX_HOST_RESET_RETRIES 5
+
+/* Maximum number of error handler retries before giving up */
+#define MAX_ERR_HANDLER_RETRIES 5
+
+/* Expose the flag value from utp_upiu_query.value */
+#define MASK_QUERY_UPIU_FLAG_LOC 0xFF
+
+/* Interrupt aggregation default timeout, unit: 40us */
+#define INT_AGGR_DEF_TO 0x02
+
+/* default delay of autosuspend: 2000 ms */
+#define RPM_AUTOSUSPEND_DELAY_MS 2000
+
+/* Default delay of RPM device flush delayed work */
+#define RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS 5000
+
+/* Default value of wait time before gating device ref clock */
+#define UFSHCD_REF_CLK_GATING_WAIT_US 0xFF /* microsecs */
+
+/* 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)
+
+/* bMaxNumOfRTT is equal to two after device manufacturing */
+#define DEFAULT_MAX_NUM_RTT 2
+
+/* UFSHC 4.0 compliant HC support this mode. */
+static bool use_mcq_mode = true;
+
+static bool is_mcq_supported(struct ufs_hba *hba)
+{
+ return hba->mcq_sup && use_mcq_mode;
+}
+
+module_param(use_mcq_mode, bool, 0644);
+MODULE_PARM_DESC(use_mcq_mode, "Control MCQ mode for controllers starting from UFSHCI 4.0. 1 - enable MCQ, 0 - disable MCQ. MCQ is enabled by default");
+
+static unsigned int uic_cmd_timeout = UIC_CMD_TIMEOUT_DEFAULT;
+
+static int uic_cmd_timeout_set(const char *val, const struct kernel_param *kp)
+{
+ return param_set_uint_minmax(val, kp, UIC_CMD_TIMEOUT_DEFAULT,
+ UIC_CMD_TIMEOUT_MAX);
+}
+
+static const struct kernel_param_ops uic_cmd_timeout_ops = {
+ .set = uic_cmd_timeout_set,
+ .get = param_get_uint,
+};
+
+module_param_cb(uic_cmd_timeout, &uic_cmd_timeout_ops, &uic_cmd_timeout, 0644);
+MODULE_PARM_DESC(uic_cmd_timeout,
+ "UFS UIC command timeout in milliseconds. Defaults to 500ms. Supported values range from 500ms to 5 seconds inclusively");
+
+static unsigned int dev_cmd_timeout = QUERY_REQ_TIMEOUT_DEFAULT;
+
+static int dev_cmd_timeout_set(const char *val, const struct kernel_param *kp)
+{
+ return param_set_uint_minmax(val, kp, QUERY_REQ_TIMEOUT_MIN,
+ QUERY_REQ_TIMEOUT_MAX);
+}
+
+static const struct kernel_param_ops dev_cmd_timeout_ops = {
+ .set = dev_cmd_timeout_set,
+ .get = param_get_uint,
+};
+
+module_param_cb(dev_cmd_timeout, &dev_cmd_timeout_ops, &dev_cmd_timeout, 0644);
+MODULE_PARM_DESC(dev_cmd_timeout,
+ "UFS Device command timeout in milliseconds. Defaults to 1.5s. Supported values range from 1ms to 30 seconds inclusively");
+
+#define ufshcd_toggle_vreg(_dev, _vreg, _on) \
+ ({ \
+ int _ret; \
+ if (_on) \
+ _ret = ufshcd_enable_vreg(_dev, _vreg); \
+ else \
+ _ret = ufshcd_disable_vreg(_dev, _vreg); \
+ _ret; \
+ })
+
+#define ufshcd_hex_dump(prefix_str, buf, len) do { \
+ size_t __len = (len); \
+ print_hex_dump(KERN_ERR, prefix_str, \
+ __len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE,\
+ 16, 4, buf, __len, false); \
+} while (0)
+
+int ufshcd_dump_regs(struct ufs_hba *hba, size_t offset, size_t len,
+ const char *prefix)
+{
+ u32 *regs;
+ size_t pos;
+
+ if (offset % 4 != 0 || len % 4 != 0) /* keep readl happy */
+ return -EINVAL;
+
+ regs = kzalloc(len, GFP_ATOMIC);
+ if (!regs)
+ return -ENOMEM;
+
+ for (pos = 0; pos < len; pos += 4) {
+ if (offset == 0 &&
+ pos >= REG_UIC_ERROR_CODE_PHY_ADAPTER_LAYER &&
+ pos <= REG_UIC_ERROR_CODE_DME)
+ continue;
+ regs[pos / 4] = ufshcd_readl(hba, offset + pos);
+ }
+
+ ufshcd_hex_dump(prefix, regs, len);
+ kfree(regs);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ufshcd_dump_regs);
+
+enum {
+ UFSHCD_MAX_CHANNEL = 0,
+ UFSHCD_MAX_ID = 1,
+};
+
+static const char *const ufshcd_state_name[] = {
+ [UFSHCD_STATE_RESET] = "reset",
+ [UFSHCD_STATE_OPERATIONAL] = "operational",
+ [UFSHCD_STATE_ERROR] = "error",
+ [UFSHCD_STATE_EH_SCHEDULED_FATAL] = "eh_fatal",
+ [UFSHCD_STATE_EH_SCHEDULED_NON_FATAL] = "eh_non_fatal",
+};
+
+/* UFSHCD error handling flags */
+enum {
+ UFSHCD_EH_IN_PROGRESS = (1 << 0),
+};
+
+/* UFSHCD UIC layer error flags */
+enum {
+ UFSHCD_UIC_DL_PA_INIT_ERROR = (1 << 0), /* Data link layer error */
+ UFSHCD_UIC_DL_NAC_RECEIVED_ERROR = (1 << 1), /* Data link layer error */
+ UFSHCD_UIC_DL_TCx_REPLAY_ERROR = (1 << 2), /* Data link layer error */
+ UFSHCD_UIC_NL_ERROR = (1 << 3), /* Network layer error */
+ UFSHCD_UIC_TL_ERROR = (1 << 4), /* Transport Layer error */
+ UFSHCD_UIC_DME_ERROR = (1 << 5), /* DME error */
+ UFSHCD_UIC_PA_GENERIC_ERROR = (1 << 6), /* Generic PA error */
+};
+
+#define ufshcd_set_eh_in_progress(h) \
+ ((h)->eh_flags |= UFSHCD_EH_IN_PROGRESS)
+#define ufshcd_eh_in_progress(h) \
+ ((h)->eh_flags & UFSHCD_EH_IN_PROGRESS)
+#define ufshcd_clear_eh_in_progress(h) \
+ ((h)->eh_flags &= ~UFSHCD_EH_IN_PROGRESS)
+
+const struct ufs_pm_lvl_states ufs_pm_lvl_states[] = {
+ [UFS_PM_LVL_0] = {UFS_ACTIVE_PWR_MODE, UIC_LINK_ACTIVE_STATE},
+ [UFS_PM_LVL_1] = {UFS_ACTIVE_PWR_MODE, UIC_LINK_HIBERN8_STATE},
+ [UFS_PM_LVL_2] = {UFS_SLEEP_PWR_MODE, UIC_LINK_ACTIVE_STATE},
+ [UFS_PM_LVL_3] = {UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE},
+ [UFS_PM_LVL_4] = {UFS_POWERDOWN_PWR_MODE, UIC_LINK_HIBERN8_STATE},
+ [UFS_PM_LVL_5] = {UFS_POWERDOWN_PWR_MODE, UIC_LINK_OFF_STATE},
+ /*
+ * For DeepSleep, the link is first put in hibern8 and then off.
+ * Leaving the link in hibern8 is not supported.
+ */
+ [UFS_PM_LVL_6] = {UFS_DEEPSLEEP_PWR_MODE, UIC_LINK_OFF_STATE},
+};
+
+static inline enum ufs_dev_pwr_mode
+ufs_get_pm_lvl_to_dev_pwr_mode(enum ufs_pm_level lvl)
+{
+ return ufs_pm_lvl_states[lvl].dev_state;
+}
+
+static inline enum uic_link_state
+ufs_get_pm_lvl_to_link_pwr_state(enum ufs_pm_level lvl)
+{
+ return ufs_pm_lvl_states[lvl].link_state;
+}
+
+static inline enum ufs_pm_level
+ufs_get_desired_pm_lvl_for_dev_link_state(enum ufs_dev_pwr_mode dev_state,
+ enum uic_link_state link_state)
+{
+ enum ufs_pm_level lvl;
+
+ for (lvl = UFS_PM_LVL_0; lvl < UFS_PM_LVL_MAX; lvl++) {
+ if ((ufs_pm_lvl_states[lvl].dev_state == dev_state) &&
+ (ufs_pm_lvl_states[lvl].link_state == link_state))
+ return lvl;
+ }
+
+ /* if no match found, return the level 0 */
+ return UFS_PM_LVL_0;
+}
+
+static bool ufshcd_has_pending_tasks(struct ufs_hba *hba)
+{
+ return hba->outstanding_tasks || hba->active_uic_cmd ||
+ hba->uic_async_done;
+}
+
+static bool ufshcd_is_ufs_dev_busy(struct ufs_hba *hba)
+{
+ return (hba->scsi_host_added && scsi_host_busy(hba->host)) ||
+ ufshcd_has_pending_tasks(hba);
+}
+
+static const struct ufs_dev_quirk ufs_fixups[] = {
+ /* UFS cards deviations table */
+ { .wmanufacturerid = UFS_VENDOR_MICRON,
+ .model = UFS_ANY_MODEL,
+ .quirk = UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM },
+ { .wmanufacturerid = UFS_VENDOR_SAMSUNG,
+ .model = UFS_ANY_MODEL,
+ .quirk = UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM |
+ UFS_DEVICE_QUIRK_HOST_PA_TACTIVATE |
+ UFS_DEVICE_QUIRK_PA_HIBER8TIME |
+ UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS },
+ { .wmanufacturerid = UFS_VENDOR_SKHYNIX,
+ .model = UFS_ANY_MODEL,
+ .quirk = UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME },
+ { .wmanufacturerid = UFS_VENDOR_SKHYNIX,
+ .model = "hB8aL1" /*H28U62301AMR*/,
+ .quirk = UFS_DEVICE_QUIRK_HOST_VS_DEBUGSAVECONFIGTIME },
+ { .wmanufacturerid = UFS_VENDOR_TOSHIBA,
+ .model = UFS_ANY_MODEL,
+ .quirk = UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM },
+ { .wmanufacturerid = UFS_VENDOR_TOSHIBA,
+ .model = "THGLF2G9C8KBADG",
+ .quirk = UFS_DEVICE_QUIRK_PA_TACTIVATE },
+ { .wmanufacturerid = UFS_VENDOR_TOSHIBA,
+ .model = "THGLF2G9D8KBADG",
+ .quirk = UFS_DEVICE_QUIRK_PA_TACTIVATE },
+ { .wmanufacturerid = UFS_VENDOR_TOSHIBA,
+ .model = "THGJFJT0E25BAIP",
+ .quirk = UFS_DEVICE_QUIRK_NO_TIMESTAMP_SUPPORT },
+ { .wmanufacturerid = UFS_VENDOR_TOSHIBA,
+ .model = "THGJFJT1E45BATP",
+ .quirk = UFS_DEVICE_QUIRK_NO_TIMESTAMP_SUPPORT },
+ {}
+};
+
+static irqreturn_t ufshcd_tmc_handler(struct ufs_hba *hba);
+static void ufshcd_async_scan(void *data, async_cookie_t cookie);
+static int ufshcd_reset_and_restore(struct ufs_hba *hba);
+static int ufshcd_eh_host_reset_handler(struct scsi_cmnd *cmd);
+static int ufshcd_clear_tm_cmd(struct ufs_hba *hba, int tag);
+static void ufshcd_hba_exit(struct ufs_hba *hba);
+static int ufshcd_device_init(struct ufs_hba *hba, bool init_dev_params);
+static int ufshcd_probe_hba(struct ufs_hba *hba, bool init_dev_params);
+static int ufshcd_setup_clocks(struct ufs_hba *hba, bool on);
+static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba);
+static int ufshcd_host_reset_and_restore(struct ufs_hba *hba);
+static void ufshcd_resume_clkscaling(struct ufs_hba *hba);
+static void ufshcd_suspend_clkscaling(struct ufs_hba *hba);
+static irqreturn_t ufshcd_intr(int irq, void *__hba);
+static int ufshcd_setup_hba_vreg(struct ufs_hba *hba, bool on);
+static int ufshcd_setup_vreg(struct ufs_hba *hba, bool on);
+static inline int ufshcd_config_vreg_hpm(struct ufs_hba *hba,
+ struct ufs_vreg *vreg);
+static void ufshcd_wb_toggle_buf_flush_during_h8(struct ufs_hba *hba,
+ bool enable);
+static void ufshcd_hba_vreg_set_lpm(struct ufs_hba *hba);
+static void ufshcd_hba_vreg_set_hpm(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);
+
+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);
+
+/**
+ * ufshcd_enable_intr - enable interrupts
+ * @hba: per adapter instance
+ * @intrs: interrupt bits
+ */
+void ufshcd_enable_intr(struct ufs_hba *hba, u32 intrs)
+{
+ u32 old_val = ufshcd_readl(hba, REG_INTERRUPT_ENABLE);
+ u32 new_val = old_val | intrs;
+
+ if (new_val != old_val)
+ ufshcd_writel(hba, new_val, REG_INTERRUPT_ENABLE);
+}
+
+/**
+ * ufshcd_disable_intr - disable interrupts
+ * @hba: per adapter instance
+ * @intrs: interrupt bits
+ */
+static void ufshcd_disable_intr(struct ufs_hba *hba, u32 intrs)
+{
+ u32 old_val = ufshcd_readl(hba, REG_INTERRUPT_ENABLE);
+ u32 new_val = old_val & ~intrs;
+
+ if (new_val != old_val)
+ ufshcd_writel(hba, new_val, REG_INTERRUPT_ENABLE);
+}
+
+static void ufshcd_configure_wb(struct ufs_hba *hba)
+{
+ if (!ufshcd_is_wb_allowed(hba))
+ return;
+
+ ufshcd_wb_toggle(hba, true);
+
+ ufshcd_wb_toggle_buf_flush_during_h8(hba, true);
+
+ if (ufshcd_is_wb_buf_flush_allowed(hba))
+ ufshcd_wb_toggle_buf_flush(hba, true);
+}
+
+static void ufshcd_add_cmd_upiu_trace(struct ufs_hba *hba,
+ struct ufshcd_lrb *lrb,
+ enum ufs_trace_str_t str_t)
+{
+ struct utp_upiu_req *rq = lrb->ucd_req_ptr;
+ struct utp_upiu_header *header;
+
+ if (!trace_ufshcd_upiu_enabled())
+ return;
+
+ if (str_t == UFS_CMD_SEND)
+ header = &rq->header;
+ else
+ header = &lrb->ucd_rsp_ptr->header;
+
+ trace_ufshcd_upiu(hba, str_t, header, &rq->sc.cdb,
+ UFS_TSF_CDB);
+}
+
+static void ufshcd_add_query_upiu_trace(struct ufs_hba *hba,
+ enum ufs_trace_str_t str_t,
+ struct utp_upiu_req *rq_rsp)
+{
+ if (!trace_ufshcd_upiu_enabled())
+ return;
+
+ trace_ufshcd_upiu(hba, str_t, &rq_rsp->header,
+ &rq_rsp->qr, UFS_TSF_OSF);
+}
+
+static void ufshcd_add_tm_upiu_trace(struct ufs_hba *hba, unsigned int tag,
+ enum ufs_trace_str_t str_t)
+{
+ struct utp_task_req_desc *descp = &hba->utmrdl_base_addr[tag];
+
+ if (!trace_ufshcd_upiu_enabled())
+ return;
+
+ if (str_t == UFS_TM_SEND)
+ trace_ufshcd_upiu(hba, str_t,
+ &descp->upiu_req.req_header,
+ &descp->upiu_req.input_param1,
+ UFS_TSF_TM_INPUT);
+ else
+ trace_ufshcd_upiu(hba, str_t,
+ &descp->upiu_rsp.rsp_header,
+ &descp->upiu_rsp.output_param1,
+ UFS_TSF_TM_OUTPUT);
+}
+
+static void ufshcd_add_uic_command_trace(struct ufs_hba *hba,
+ const struct uic_command *ucmd,
+ enum ufs_trace_str_t str_t)
+{
+ u32 cmd, arg1, arg2, arg3;
+
+ if (!trace_ufshcd_uic_command_enabled())
+ return;
+
+ if (str_t == UFS_CMD_SEND) {
+ cmd = ucmd->command;
+ arg1 = ucmd->argument1;
+ arg2 = ucmd->argument2;
+ arg3 = ucmd->argument3;
+ } else {
+ cmd = ufshcd_readl(hba, REG_UIC_COMMAND);
+ arg1 = ufshcd_readl(hba, REG_UIC_COMMAND_ARG_1);
+ arg2 = ufshcd_readl(hba, REG_UIC_COMMAND_ARG_2);
+ arg3 = ufshcd_readl(hba, REG_UIC_COMMAND_ARG_3);
+ }
+
+ trace_ufshcd_uic_command(hba, str_t, cmd, arg1, arg2, arg3);
+}
+
+static void ufshcd_add_command_trace(struct ufs_hba *hba, struct scsi_cmnd *cmd,
+ enum ufs_trace_str_t str_t)
+{
+ u64 lba = 0;
+ u8 opcode = 0, group_id = 0;
+ u32 doorbell = 0;
+ u32 intr;
+ u32 hwq_id = 0;
+ struct request *rq = scsi_cmd_to_rq(cmd);
+ unsigned int tag = rq->tag;
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ int transfer_len = -1;
+
+ /* trace UPIU also */
+ ufshcd_add_cmd_upiu_trace(hba, lrbp, str_t);
+ if (!trace_ufshcd_command_enabled())
+ return;
+
+ opcode = cmd->cmnd[0];
+
+ if (opcode == READ_10 || opcode == WRITE_10) {
+ /*
+ * Currently we only fully trace read(10) and write(10) commands
+ */
+ transfer_len =
+ be32_to_cpu(lrbp->ucd_req_ptr->sc.exp_data_transfer_len);
+ lba = scsi_get_lba(cmd);
+ if (opcode == WRITE_10)
+ group_id = cmd->cmnd[6];
+ } else if (opcode == UNMAP) {
+ /*
+ * The number of Bytes to be unmapped beginning with the lba.
+ */
+ transfer_len = blk_rq_bytes(rq);
+ lba = scsi_get_lba(cmd);
+ }
+
+ intr = ufshcd_readl(hba, REG_INTERRUPT_STATUS);
+
+ if (hba->mcq_enabled) {
+ struct ufs_hw_queue *hwq = ufshcd_mcq_req_to_hwq(hba, rq);
+ if (hwq)
+ hwq_id = hwq->id;
+ } else {
+ doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
+ }
+ trace_ufshcd_command(cmd->device, hba, str_t, tag, doorbell, hwq_id,
+ transfer_len, intr, lba, opcode, group_id);
+}
+
+static void ufshcd_print_clk_freqs(struct ufs_hba *hba)
+{
+ struct ufs_clk_info *clki;
+ struct list_head *head = &hba->clk_list_head;
+
+ if (list_empty(head))
+ return;
+
+ list_for_each_entry(clki, head, list) {
+ if (!IS_ERR_OR_NULL(clki->clk) && clki->min_freq &&
+ clki->max_freq)
+ dev_err(hba->dev, "clk: %s, rate: %u\n",
+ clki->name, clki->curr_freq);
+ }
+}
+
+static void ufshcd_print_evt(struct ufs_hba *hba, u32 id,
+ const char *err_name)
+{
+ int i;
+ bool found = false;
+ const struct ufs_event_hist *e;
+
+ if (id >= UFS_EVT_CNT)
+ return;
+
+ e = &hba->ufs_stats.event[id];
+
+ for (i = 0; i < UFS_EVENT_HIST_LENGTH; i++) {
+ int p = (i + e->pos) % UFS_EVENT_HIST_LENGTH;
+
+ if (e->tstamp[p] == 0)
+ continue;
+ dev_err(hba->dev, "%s[%d] = 0x%x at %lld us\n", err_name, p,
+ e->val[p], div_u64(e->tstamp[p], 1000));
+ found = true;
+ }
+
+ if (!found)
+ dev_err(hba->dev, "No record of %s\n", err_name);
+ else
+ dev_err(hba->dev, "%s: total cnt=%llu\n", err_name, e->cnt);
+}
+
+static void ufshcd_print_evt_hist(struct ufs_hba *hba)
+{
+ ufshcd_dump_regs(hba, 0, UFSHCI_REG_SPACE_SIZE, "host_regs: ");
+
+ ufshcd_print_evt(hba, UFS_EVT_PA_ERR, "pa_err");
+ ufshcd_print_evt(hba, UFS_EVT_DL_ERR, "dl_err");
+ ufshcd_print_evt(hba, UFS_EVT_NL_ERR, "nl_err");
+ ufshcd_print_evt(hba, UFS_EVT_TL_ERR, "tl_err");
+ ufshcd_print_evt(hba, UFS_EVT_DME_ERR, "dme_err");
+ ufshcd_print_evt(hba, UFS_EVT_AUTO_HIBERN8_ERR,
+ "auto_hibern8_err");
+ ufshcd_print_evt(hba, UFS_EVT_FATAL_ERR, "fatal_err");
+ ufshcd_print_evt(hba, UFS_EVT_LINK_STARTUP_FAIL,
+ "link_startup_fail");
+ ufshcd_print_evt(hba, UFS_EVT_RESUME_ERR, "resume_fail");
+ ufshcd_print_evt(hba, UFS_EVT_SUSPEND_ERR,
+ "suspend_fail");
+ ufshcd_print_evt(hba, UFS_EVT_WL_RES_ERR, "wlun resume_fail");
+ ufshcd_print_evt(hba, UFS_EVT_WL_SUSP_ERR,
+ "wlun suspend_fail");
+ ufshcd_print_evt(hba, UFS_EVT_DEV_RESET, "dev_reset");
+ ufshcd_print_evt(hba, UFS_EVT_HOST_RESET, "host_reset");
+ ufshcd_print_evt(hba, UFS_EVT_ABORT, "task_abort");
+
+ ufshcd_vops_dbg_register_dump(hba);
+}
+
+static void ufshcd_print_tr(struct ufs_hba *hba, struct scsi_cmnd *cmd,
+ bool pr_prdt)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ const int tag = scsi_cmd_to_rq(cmd)->tag;
+ int prdt_length;
+
+ if (hba->monitor.enabled) {
+ dev_err(hba->dev, "UPIU[%d] - issue time %lld us\n", tag,
+ div_u64(lrbp->issue_time_stamp_local_clock, 1000));
+ dev_err(hba->dev, "UPIU[%d] - complete time %lld us\n", tag,
+ div_u64(lrbp->compl_time_stamp_local_clock, 1000));
+ }
+ dev_err(hba->dev,
+ "UPIU[%d] - Transfer Request Descriptor phys@0x%llx\n",
+ tag, (u64)lrbp->utrd_dma_addr);
+
+ ufshcd_hex_dump("UPIU TRD: ", lrbp->utr_descriptor_ptr,
+ sizeof(struct utp_transfer_req_desc));
+ dev_err(hba->dev, "UPIU[%d] - Request UPIU phys@0x%llx\n", tag,
+ (u64)lrbp->ucd_req_dma_addr);
+ ufshcd_hex_dump("UPIU REQ: ", lrbp->ucd_req_ptr,
+ sizeof(struct utp_upiu_req));
+ dev_err(hba->dev, "UPIU[%d] - Response UPIU phys@0x%llx\n", tag,
+ (u64)lrbp->ucd_rsp_dma_addr);
+ ufshcd_hex_dump("UPIU RSP: ", lrbp->ucd_rsp_ptr,
+ sizeof(struct utp_upiu_rsp));
+
+ prdt_length = le16_to_cpu(
+ lrbp->utr_descriptor_ptr->prd_table_length);
+ if (hba->quirks & UFSHCD_QUIRK_PRDT_BYTE_GRAN)
+ prdt_length /= ufshcd_sg_entry_size(hba);
+
+ dev_err(hba->dev,
+ "UPIU[%d] - PRDT - %d entries phys@0x%llx\n",
+ tag, prdt_length,
+ (u64)lrbp->ucd_prdt_dma_addr);
+
+ if (pr_prdt)
+ ufshcd_hex_dump("UPIU PRDT: ", lrbp->ucd_prdt_ptr,
+ ufshcd_sg_entry_size(hba) * prdt_length);
+}
+
+static bool ufshcd_print_tr_iter(struct request *req, void *priv)
+{
+ struct scsi_device *sdev = req->q->queuedata;
+ struct Scsi_Host *shost = sdev->host;
+ struct ufs_hba *hba = shost_priv(shost);
+
+ if (!blk_mq_is_reserved_rq(req))
+ ufshcd_print_tr(hba, blk_mq_rq_to_pdu(req), *(bool *)priv);
+
+ return true;
+}
+
+/**
+ * ufshcd_print_trs_all - print trs for all started requests.
+ * @hba: per-adapter instance.
+ * @pr_prdt: need to print prdt or not.
+ */
+static void ufshcd_print_trs_all(struct ufs_hba *hba, bool pr_prdt)
+{
+ blk_mq_tagset_busy_iter(&hba->host->tag_set, ufshcd_print_tr_iter, &pr_prdt);
+}
+
+static void ufshcd_print_tmrs(struct ufs_hba *hba, unsigned long bitmap)
+{
+ int tag;
+
+ for_each_set_bit(tag, &bitmap, hba->nutmrs) {
+ struct utp_task_req_desc *tmrdp = &hba->utmrdl_base_addr[tag];
+
+ dev_err(hba->dev, "TM[%d] - Task Management Header\n", tag);
+ ufshcd_hex_dump("", tmrdp, sizeof(*tmrdp));
+ }
+}
+
+static void ufshcd_print_host_state(struct ufs_hba *hba)
+{
+ const struct scsi_device *sdev_ufs = hba->ufs_device_wlun;
+
+ dev_err(hba->dev, "UFS Host state=%d\n", hba->ufshcd_state);
+ dev_err(hba->dev, "%d outstanding reqs, tasks=0x%lx\n",
+ hba->scsi_host_added ? scsi_host_busy(hba->host) : 0,
+ hba->outstanding_tasks);
+ dev_err(hba->dev, "saved_err=0x%x, saved_uic_err=0x%x\n",
+ hba->saved_err, hba->saved_uic_err);
+ dev_err(hba->dev, "Device power mode=%d, UIC link state=%d\n",
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+ dev_err(hba->dev, "PM in progress=%d, sys. suspended=%d\n",
+ hba->pm_op_in_progress, hba->is_sys_suspended);
+ dev_err(hba->dev, "Auto BKOPS=%d, Host self-block=%d\n",
+ hba->auto_bkops_enabled, hba->host->host_self_blocked);
+ dev_err(hba->dev, "Clk gate=%d\n", hba->clk_gating.state);
+ dev_err(hba->dev,
+ "last_hibern8_exit_tstamp at %lld us, hibern8_exit_cnt=%d\n",
+ div_u64(hba->ufs_stats.last_hibern8_exit_tstamp, 1000),
+ hba->ufs_stats.hibern8_exit_cnt);
+ dev_err(hba->dev, "error handling flags=0x%x, req. abort count=%d\n",
+ hba->eh_flags, hba->req_abort_count);
+ dev_err(hba->dev, "hba->ufs_version=0x%x, Host capabilities=0x%x, caps=0x%x\n",
+ hba->ufs_version, hba->capabilities, hba->caps);
+ dev_err(hba->dev, "quirks=0x%x, dev. quirks=0x%x\n", hba->quirks,
+ hba->dev_quirks);
+ if (sdev_ufs)
+ dev_err(hba->dev, "UFS dev info: %.8s %.16s rev %.4s\n",
+ sdev_ufs->vendor, sdev_ufs->model, sdev_ufs->rev);
+
+ ufshcd_print_clk_freqs(hba);
+}
+
+/**
+ * ufshcd_print_pwr_info - print power params as saved in hba
+ * power info
+ * @hba: per-adapter instance
+ */
+static void ufshcd_print_pwr_info(struct ufs_hba *hba)
+{
+ static const char * const names[] = {
+ "INVALID MODE",
+ "FAST MODE",
+ "SLOW_MODE",
+ "INVALID MODE",
+ "FASTAUTO_MODE",
+ "SLOWAUTO_MODE",
+ "INVALID MODE",
+ };
+
+ /*
+ * Using dev_dbg to avoid messages during runtime PM to avoid
+ * never-ending cycles of messages written back to storage by user space
+ * causing runtime resume, causing more messages and so on.
+ */
+ dev_dbg(hba->dev, "%s:[RX, TX]: gear=[%d, %d], lane[%d, %d], pwr[%s, %s], rate = %d\n",
+ __func__,
+ hba->pwr_info.gear_rx, hba->pwr_info.gear_tx,
+ hba->pwr_info.lane_rx, hba->pwr_info.lane_tx,
+ names[hba->pwr_info.pwr_rx],
+ names[hba->pwr_info.pwr_tx],
+ hba->pwr_info.hs_rate);
+}
+
+static void ufshcd_device_reset(struct ufs_hba *hba)
+{
+ int err;
+
+ err = ufshcd_vops_device_reset(hba);
+
+ if (!err) {
+ ufshcd_set_ufs_dev_active(hba);
+ if (ufshcd_is_wb_allowed(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);
+}
+
+void ufshcd_delay_us(unsigned long us, unsigned long tolerance)
+{
+ if (!us)
+ return;
+
+ if (us < 10)
+ udelay(us);
+ else
+ usleep_range(us, us + tolerance);
+}
+EXPORT_SYMBOL_GPL(ufshcd_delay_us);
+
+/**
+ * ufshcd_wait_for_register - wait for register value to change
+ * @hba: per-adapter interface
+ * @reg: mmio register offset
+ * @mask: mask to apply to the read register value
+ * @val: value to wait for
+ * @interval_us: polling interval in microseconds
+ * @timeout_ms: timeout in milliseconds
+ *
+ * Return: -ETIMEDOUT on error, zero on success.
+ */
+static int ufshcd_wait_for_register(struct ufs_hba *hba, u32 reg, u32 mask,
+ u32 val, unsigned long interval_us,
+ unsigned long timeout_ms)
+{
+ u32 v;
+
+ val &= mask; /* ignore bits that we don't intend to wait on */
+
+ return read_poll_timeout(ufshcd_readl, v, (v & mask) == val,
+ interval_us, timeout_ms * 1000, false, hba, reg);
+}
+
+/**
+ * ufshcd_get_intr_mask - Get the interrupt bit mask
+ * @hba: Pointer to adapter instance
+ *
+ * Return: interrupt bit mask per version
+ */
+static inline u32 ufshcd_get_intr_mask(struct ufs_hba *hba)
+{
+ if (hba->ufs_version <= ufshci_version(2, 0))
+ return INTERRUPT_MASK_ALL_VER_11;
+
+ return INTERRUPT_MASK_ALL_VER_21;
+}
+
+/**
+ * ufshcd_get_ufs_version - Get the UFS version supported by the HBA
+ * @hba: Pointer to adapter instance
+ *
+ * Return: UFSHCI version supported by the controller
+ */
+static inline u32 ufshcd_get_ufs_version(struct ufs_hba *hba)
+{
+ u32 ufshci_ver;
+
+ if (hba->quirks & UFSHCD_QUIRK_BROKEN_UFS_HCI_VERSION)
+ ufshci_ver = ufshcd_vops_get_ufs_hci_version(hba);
+ else
+ ufshci_ver = ufshcd_readl(hba, REG_UFS_VERSION);
+
+ /*
+ * UFSHCI v1.x uses a different version scheme, in order
+ * to allow the use of comparisons with the ufshci_version
+ * function, we convert it to the same scheme as ufs 2.0+.
+ */
+ if (ufshci_ver & 0x00010000)
+ return ufshci_version(1, ufshci_ver & 0x00000100);
+
+ return ufshci_ver;
+}
+
+/**
+ * ufshcd_is_device_present - Check if any device connected to
+ * the host controller
+ * @hba: pointer to adapter instance
+ *
+ * Return: true if device present, false if no device detected
+ */
+static inline bool ufshcd_is_device_present(struct ufs_hba *hba)
+{
+ return ufshcd_readl(hba, REG_CONTROLLER_STATUS) & DEVICE_PRESENT;
+}
+
+/**
+ * ufshcd_get_tr_ocs - Get the UTRD Overall Command Status
+ * @lrbp: pointer to local command reference block
+ * @cqe: pointer to the completion queue entry
+ *
+ * This function is used to get the OCS field from UTRD
+ *
+ * Return: the OCS field in the UTRD.
+ */
+static enum utp_ocs ufshcd_get_tr_ocs(struct ufshcd_lrb *lrbp,
+ struct cq_entry *cqe)
+{
+ if (cqe)
+ return cqe->overall_status & MASK_OCS;
+
+ return lrbp->utr_descriptor_ptr->header.ocs & MASK_OCS;
+}
+
+/**
+ * ufshcd_utrl_clear() - Clear requests from the controller request list.
+ * @hba: per adapter instance
+ * @mask: mask with one bit set for each request to be cleared
+ */
+static inline void ufshcd_utrl_clear(struct ufs_hba *hba, u32 mask)
+{
+ if (hba->quirks & UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR)
+ mask = ~mask;
+ /*
+ * From the UFSHCI specification: "UTP Transfer Request List CLear
+ * Register (UTRLCLR): This field is bit significant. Each bit
+ * corresponds to a slot in the UTP Transfer Request List, where bit 0
+ * corresponds to request slot 0. A bit in this field is set to ‘0’
+ * by host software to indicate to the host controller that a transfer
+ * request slot is cleared. The host controller
+ * shall free up any resources associated to the request slot
+ * immediately, and shall set the associated bit in UTRLDBR to ‘0’. The
+ * host software indicates no change to request slots by setting the
+ * associated bits in this field to ‘1’. Bits in this field shall only
+ * be set ‘1’ or ‘0’ by host software when UTRLRSR is set to ‘1’."
+ */
+ ufshcd_writel(hba, ~mask, REG_UTP_TRANSFER_REQ_LIST_CLEAR);
+}
+
+/**
+ * ufshcd_utmrl_clear - Clear a bit in UTMRLCLR register
+ * @hba: per adapter instance
+ * @pos: position of the bit to be cleared
+ */
+static inline void ufshcd_utmrl_clear(struct ufs_hba *hba, u32 pos)
+{
+ if (hba->quirks & UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR)
+ ufshcd_writel(hba, (1 << pos), REG_UTP_TASK_REQ_LIST_CLEAR);
+ else
+ ufshcd_writel(hba, ~(1 << pos), REG_UTP_TASK_REQ_LIST_CLEAR);
+}
+
+/**
+ * ufshcd_get_lists_status - Check UCRDY, UTRLRDY and UTMRLRDY
+ * @reg: Register value of host controller status
+ *
+ * Return: 0 on success; a positive value if failed.
+ */
+static inline int ufshcd_get_lists_status(u32 reg)
+{
+ return !((reg & UFSHCD_STATUS_READY) == UFSHCD_STATUS_READY);
+}
+
+/**
+ * ufshcd_get_req_rsp - returns the TR response transaction type
+ * @ucd_rsp_ptr: pointer to response UPIU
+ *
+ * Return: UPIU type.
+ */
+static inline enum upiu_response_transaction
+ufshcd_get_req_rsp(struct utp_upiu_rsp *ucd_rsp_ptr)
+{
+ return ucd_rsp_ptr->header.transaction_code;
+}
+
+/**
+ * ufshcd_is_exception_event - Check if the device raised an exception event
+ * @ucd_rsp_ptr: pointer to response UPIU
+ *
+ * The function checks if the device raised an exception event indicated in
+ * the Device Information field of response UPIU.
+ *
+ * Return: true if exception is raised, false otherwise.
+ */
+static inline bool ufshcd_is_exception_event(struct utp_upiu_rsp *ucd_rsp_ptr)
+{
+ return ucd_rsp_ptr->header.device_information & 1;
+}
+
+/**
+ * ufshcd_reset_intr_aggr - Reset interrupt aggregation values.
+ * @hba: per adapter instance
+ */
+static inline void
+ufshcd_reset_intr_aggr(struct ufs_hba *hba)
+{
+ ufshcd_writel(hba, INT_AGGR_ENABLE |
+ INT_AGGR_COUNTER_AND_TIMER_RESET,
+ REG_UTP_TRANSFER_REQ_INT_AGG_CONTROL);
+}
+
+/**
+ * ufshcd_config_intr_aggr - Configure interrupt aggregation values.
+ * @hba: per adapter instance
+ * @cnt: Interrupt aggregation counter threshold
+ * @tmout: Interrupt aggregation timeout value
+ */
+static inline void
+ufshcd_config_intr_aggr(struct ufs_hba *hba, u8 cnt, u8 tmout)
+{
+ ufshcd_writel(hba, INT_AGGR_ENABLE | INT_AGGR_PARAM_WRITE |
+ INT_AGGR_COUNTER_THLD_VAL(cnt) |
+ INT_AGGR_TIMEOUT_VAL(tmout),
+ REG_UTP_TRANSFER_REQ_INT_AGG_CONTROL);
+}
+
+/**
+ * ufshcd_disable_intr_aggr - Disables interrupt aggregation.
+ * @hba: per adapter instance
+ */
+static inline void ufshcd_disable_intr_aggr(struct ufs_hba *hba)
+{
+ ufshcd_writel(hba, 0, REG_UTP_TRANSFER_REQ_INT_AGG_CONTROL);
+}
+
+/**
+ * ufshcd_enable_run_stop_reg - Enable run-stop registers,
+ * When run-stop registers are set to 1, it indicates the
+ * host controller that it can process the requests
+ * @hba: per adapter instance
+ */
+static void ufshcd_enable_run_stop_reg(struct ufs_hba *hba)
+{
+ ufshcd_writel(hba, UTP_TASK_REQ_LIST_RUN_STOP_BIT,
+ REG_UTP_TASK_REQ_LIST_RUN_STOP);
+ ufshcd_writel(hba, UTP_TRANSFER_REQ_LIST_RUN_STOP_BIT,
+ REG_UTP_TRANSFER_REQ_LIST_RUN_STOP);
+}
+
+/**
+ * ufshcd_hba_start - Start controller initialization sequence
+ * @hba: per adapter instance
+ */
+static inline void ufshcd_hba_start(struct ufs_hba *hba)
+{
+ u32 val = CONTROLLER_ENABLE;
+
+ if (ufshcd_crypto_enable(hba))
+ val |= CRYPTO_GENERAL_ENABLE;
+
+ ufshcd_writel(hba, val, REG_CONTROLLER_ENABLE);
+}
+
+/**
+ * ufshcd_is_hba_active - Get controller state
+ * @hba: per adapter instance
+ *
+ * Return: true if and only if the controller is active.
+ */
+bool ufshcd_is_hba_active(struct ufs_hba *hba)
+{
+ return ufshcd_readl(hba, REG_CONTROLLER_ENABLE) & CONTROLLER_ENABLE;
+}
+EXPORT_SYMBOL_GPL(ufshcd_is_hba_active);
+
+/**
+ * ufshcd_pm_qos_init - initialize PM QoS request
+ * @hba: per adapter instance
+ */
+void ufshcd_pm_qos_init(struct ufs_hba *hba)
+{
+ guard(mutex)(&hba->pm_qos_mutex);
+
+ if (hba->pm_qos_enabled)
+ return;
+
+ cpu_latency_qos_add_request(&hba->pm_qos_req, PM_QOS_DEFAULT_VALUE);
+
+ if (cpu_latency_qos_request_active(&hba->pm_qos_req))
+ hba->pm_qos_enabled = true;
+}
+
+/**
+ * ufshcd_pm_qos_exit - remove request from PM QoS
+ * @hba: per adapter instance
+ */
+void ufshcd_pm_qos_exit(struct ufs_hba *hba)
+{
+ guard(mutex)(&hba->pm_qos_mutex);
+
+ if (!hba->pm_qos_enabled)
+ return;
+
+ cpu_latency_qos_remove_request(&hba->pm_qos_req);
+ hba->pm_qos_enabled = false;
+}
+
+/**
+ * ufshcd_pm_qos_update - update PM QoS request
+ * @hba: per adapter instance
+ * @on: If True, vote for perf PM QoS mode otherwise power save mode
+ */
+void ufshcd_pm_qos_update(struct ufs_hba *hba, bool on)
+{
+ guard(mutex)(&hba->pm_qos_mutex);
+
+ if (!hba->pm_qos_enabled)
+ return;
+
+ cpu_latency_qos_update_request(&hba->pm_qos_req, on ? 0 : PM_QOS_DEFAULT_VALUE);
+}
+EXPORT_SYMBOL_GPL(ufshcd_pm_qos_update);
+
+/**
+ * ufshcd_set_clk_freq - set UFS controller clock frequencies
+ * @hba: per adapter instance
+ * @scale_up: If True, set max possible frequency othewise set low frequency
+ *
+ * Return: 0 if successful; < 0 upon failure.
+ */
+static int ufshcd_set_clk_freq(struct ufs_hba *hba, bool scale_up)
+{
+ int ret = 0;
+ struct ufs_clk_info *clki;
+ struct list_head *head = &hba->clk_list_head;
+
+ if (list_empty(head))
+ goto out;
+
+ list_for_each_entry(clki, head, list) {
+ if (!IS_ERR_OR_NULL(clki->clk)) {
+ if (scale_up && clki->max_freq) {
+ if (clki->curr_freq == clki->max_freq)
+ continue;
+
+ ret = clk_set_rate(clki->clk, clki->max_freq);
+ if (ret) {
+ dev_err(hba->dev, "%s: %s clk set rate(%dHz) failed, %d\n",
+ __func__, clki->name,
+ clki->max_freq, ret);
+ break;
+ }
+ trace_ufshcd_clk_scaling(hba,
+ "scaled up", clki->name,
+ clki->curr_freq,
+ clki->max_freq);
+
+ clki->curr_freq = clki->max_freq;
+
+ } else if (!scale_up && clki->min_freq) {
+ if (clki->curr_freq == clki->min_freq)
+ continue;
+
+ ret = clk_set_rate(clki->clk, clki->min_freq);
+ if (ret) {
+ dev_err(hba->dev, "%s: %s clk set rate(%dHz) failed, %d\n",
+ __func__, clki->name,
+ clki->min_freq, ret);
+ break;
+ }
+ trace_ufshcd_clk_scaling(hba,
+ "scaled down", clki->name,
+ clki->curr_freq,
+ clki->min_freq);
+ clki->curr_freq = clki->min_freq;
+ }
+ }
+ dev_dbg(hba->dev, "%s: clk: %s, rate: %lu\n", __func__,
+ clki->name, clk_get_rate(clki->clk));
+ }
+
+out:
+ return ret;
+}
+
+int ufshcd_opp_config_clks(struct device *dev, struct opp_table *opp_table,
+ struct dev_pm_opp *opp, void *data,
+ bool scaling_down)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct list_head *head = &hba->clk_list_head;
+ struct ufs_clk_info *clki;
+ unsigned long freq;
+ u8 idx = 0;
+ int ret;
+
+ list_for_each_entry(clki, head, list) {
+ if (!IS_ERR_OR_NULL(clki->clk)) {
+ freq = dev_pm_opp_get_freq_indexed(opp, idx++);
+
+ /* Do not set rate for clocks having frequency as 0 */
+ if (!freq)
+ continue;
+
+ ret = clk_set_rate(clki->clk, freq);
+ if (ret) {
+ dev_err(dev, "%s: %s clk set rate(%ldHz) failed, %d\n",
+ __func__, clki->name, freq, ret);
+ return ret;
+ }
+
+ trace_ufshcd_clk_scaling(hba,
+ (scaling_down ? "scaled down" : "scaled up"),
+ clki->name, hba->clk_scaling.target_freq, freq);
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ufshcd_opp_config_clks);
+
+static int ufshcd_opp_set_rate(struct ufs_hba *hba, unsigned long freq)
+{
+ struct dev_pm_opp *opp;
+ int ret;
+
+ opp = dev_pm_opp_find_freq_floor_indexed(hba->dev,
+ &freq, 0);
+ if (IS_ERR(opp))
+ return PTR_ERR(opp);
+
+ ret = dev_pm_opp_set_opp(hba->dev, opp);
+ dev_pm_opp_put(opp);
+
+ return ret;
+}
+
+/**
+ * ufshcd_scale_clks - scale up or scale down UFS controller clocks
+ * @hba: per adapter instance
+ * @freq: frequency to scale
+ * @scale_up: True if scaling up and false if scaling down
+ *
+ * Return: 0 if successful; < 0 upon failure.
+ */
+int ufshcd_scale_clks(struct ufs_hba *hba, unsigned long freq, bool scale_up)
+{
+ int ret = 0;
+ ktime_t start = ktime_get();
+
+ ret = ufshcd_vops_clk_scale_notify(hba, scale_up, freq, PRE_CHANGE);
+ if (ret)
+ goto out;
+
+ if (hba->use_pm_opp)
+ ret = ufshcd_opp_set_rate(hba, freq);
+ else
+ ret = ufshcd_set_clk_freq(hba, scale_up);
+ if (ret)
+ goto out;
+
+ ret = ufshcd_vops_clk_scale_notify(hba, scale_up, freq, POST_CHANGE);
+ if (ret) {
+ if (hba->use_pm_opp)
+ ufshcd_opp_set_rate(hba,
+ hba->devfreq->previous_freq);
+ else
+ ufshcd_set_clk_freq(hba, !scale_up);
+ goto out;
+ }
+
+ ufshcd_pm_qos_update(hba, scale_up);
+
+out:
+ trace_ufshcd_profile_clk_scaling(hba,
+ (scale_up ? "up" : "down"),
+ ktime_to_us(ktime_sub(ktime_get(), start)), ret);
+ return ret;
+}
+
+/**
+ * ufshcd_is_devfreq_scaling_required - check if scaling is required or not
+ * @hba: per adapter instance
+ * @freq: frequency to scale
+ * @scale_up: True if scaling up and false if scaling down
+ *
+ * Return: true if scaling is required, false otherwise.
+ */
+static bool ufshcd_is_devfreq_scaling_required(struct ufs_hba *hba,
+ unsigned long freq, bool scale_up)
+{
+ struct ufs_clk_info *clki;
+ struct list_head *head = &hba->clk_list_head;
+
+ if (list_empty(head))
+ return false;
+
+ if (hba->use_pm_opp)
+ return freq != hba->clk_scaling.target_freq;
+
+ list_for_each_entry(clki, head, list) {
+ if (!IS_ERR_OR_NULL(clki->clk)) {
+ if (scale_up && clki->max_freq) {
+ if (clki->curr_freq == clki->max_freq)
+ continue;
+ return true;
+ } else if (!scale_up && clki->min_freq) {
+ if (clki->curr_freq == clki->min_freq)
+ continue;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Determine the number of pending commands by counting the bits in the SCSI
+ * device budget maps. This approach has been selected because a bit is set in
+ * the budget map before scsi_host_queue_ready() checks the host_self_blocked
+ * flag. The host_self_blocked flag can be modified by calling
+ * scsi_block_requests() or scsi_unblock_requests().
+ */
+static u32 ufshcd_pending_cmds(struct ufs_hba *hba)
+{
+ struct scsi_device *sdev;
+ unsigned long flags;
+ u32 pending = 0;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ __shost_for_each_device(sdev, hba->host)
+ pending += scsi_device_busy(sdev);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ return pending;
+}
+
+/*
+ * Wait until all pending SCSI commands and TMFs have finished or the timeout
+ * has expired.
+ *
+ * Return: 0 upon success; -EBUSY upon timeout.
+ */
+static int ufshcd_wait_for_pending_cmds(struct ufs_hba *hba,
+ u64 wait_timeout_us)
+{
+ int ret = 0;
+ u32 tm_doorbell;
+ u32 tr_pending;
+ bool timeout = false, do_last_check = false;
+ ktime_t start;
+
+ ufshcd_hold(hba);
+ /*
+ * Wait for all the outstanding tasks/transfer requests.
+ * Verify by checking the doorbell registers are clear.
+ */
+ start = ktime_get();
+ do {
+ if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ tm_doorbell = ufshcd_readl(hba, REG_UTP_TASK_REQ_DOOR_BELL);
+ tr_pending = ufshcd_pending_cmds(hba);
+ if (!tm_doorbell && !tr_pending) {
+ timeout = false;
+ break;
+ } else if (do_last_check) {
+ break;
+ }
+
+ __set_current_state(TASK_UNINTERRUPTIBLE);
+ io_schedule_timeout(msecs_to_jiffies(20));
+ if (ktime_to_us(ktime_sub(ktime_get(), start)) >
+ wait_timeout_us) {
+ timeout = true;
+ /*
+ * We might have scheduled out for long time so make
+ * sure to check if doorbells are cleared by this time
+ * or not.
+ */
+ do_last_check = true;
+ }
+ } while (tm_doorbell || tr_pending);
+
+ if (timeout) {
+ dev_err(hba->dev,
+ "%s: timedout waiting for doorbell to clear (tm=0x%x, tr=0x%x)\n",
+ __func__, tm_doorbell, tr_pending);
+ ret = -EBUSY;
+ }
+out:
+ ufshcd_release(hba);
+ return ret;
+}
+
+/**
+ * ufshcd_pause_command_processing - Pause command processing
+ * @hba: per-adapter instance
+ * @timeout_us: timeout in microseconds to wait for pending commands to finish
+ *
+ * This function stops new command submissions and waits for existing commands
+ * to complete.
+ *
+ * Return: 0 on success, %-EBUSY if commands did not finish within @timeout_us.
+ * On failure, all acquired locks are released and the tagset is unquiesced.
+ */
+int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us)
+ __cond_acquires(0, &hba->host->scan_mutex)
+ __cond_acquires(0, &hba->clk_scaling_lock)
+{
+ int ret = 0;
+
+ mutex_lock(&hba->host->scan_mutex);
+ blk_mq_quiesce_tagset(&hba->host->tag_set);
+ down_write(&hba->clk_scaling_lock);
+
+ if (ufshcd_wait_for_pending_cmds(hba, timeout_us)) {
+ ret = -EBUSY;
+ up_write(&hba->clk_scaling_lock);
+ blk_mq_unquiesce_tagset(&hba->host->tag_set);
+ mutex_unlock(&hba->host->scan_mutex);
+ }
+
+ return ret;
+}
+
+/**
+ * ufshcd_resume_command_processing - Resume command processing
+ * @hba: per-adapter instance
+ *
+ * This function resumes command submissions.
+ */
+void ufshcd_resume_command_processing(struct ufs_hba *hba)
+ __releases(&hba->clk_scaling_lock)
+ __releases(&hba->host->scan_mutex)
+{
+ up_write(&hba->clk_scaling_lock);
+ blk_mq_unquiesce_tagset(&hba->host->tag_set);
+ mutex_unlock(&hba->host->scan_mutex);
+}
+
+/**
+ * ufshcd_scale_gear - scale up/down UFS gear
+ * @hba: per adapter instance
+ * @target_gear: target gear to scale to
+ * @scale_up: True for scaling up gear and false for scaling down
+ *
+ * Return: 0 for success; -EBUSY if scaling can't happen at this time;
+ * non-zero for any other errors.
+ */
+static int ufshcd_scale_gear(struct ufs_hba *hba, u32 target_gear, bool scale_up)
+{
+ int ret = 0;
+ struct ufs_pa_layer_attr new_pwr_info;
+
+ if (target_gear) {
+ new_pwr_info = hba->pwr_info;
+ new_pwr_info.gear_tx = target_gear;
+ new_pwr_info.gear_rx = target_gear;
+
+ goto config_pwr_mode;
+ }
+
+ /* Legacy gear scaling, in case vops_freq_to_gear_speed() is not implemented */
+ if (scale_up) {
+ memcpy(&new_pwr_info, &hba->clk_scaling.saved_pwr_info,
+ sizeof(struct ufs_pa_layer_attr));
+ } else {
+ memcpy(&new_pwr_info, &hba->pwr_info,
+ sizeof(struct ufs_pa_layer_attr));
+
+ if (hba->pwr_info.gear_tx > hba->clk_scaling.min_gear ||
+ hba->pwr_info.gear_rx > hba->clk_scaling.min_gear) {
+ /* save the current power mode */
+ memcpy(&hba->clk_scaling.saved_pwr_info,
+ &hba->pwr_info,
+ sizeof(struct ufs_pa_layer_attr));
+
+ /* scale down gear */
+ new_pwr_info.gear_tx = hba->clk_scaling.min_gear;
+ new_pwr_info.gear_rx = hba->clk_scaling.min_gear;
+ }
+ }
+
+config_pwr_mode:
+ /* check if the power mode needs to be changed or not? */
+ ret = ufshcd_config_pwr_mode(hba, &new_pwr_info,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+ if (ret)
+ dev_err(hba->dev, "%s: failed err %d, old gear: (tx %d rx %d), new gear: (tx %d rx %d)",
+ __func__, ret,
+ hba->pwr_info.gear_tx, hba->pwr_info.gear_rx,
+ new_pwr_info.gear_tx, new_pwr_info.gear_rx);
+
+ return ret;
+}
+
+/*
+ * Wait until all pending SCSI commands and TMFs have finished or the timeout
+ * has expired.
+ *
+ * Return: 0 upon success; -EBUSY upon timeout.
+ */
+static int ufshcd_clock_scaling_prepare(struct ufs_hba *hba, u64 timeout_us)
+ __cond_acquires(0, &hba->host->scan_mutex)
+ __cond_acquires(0, &hba->wb_mutex)
+ __cond_acquires(0, &hba->clk_scaling_lock)
+{
+ int ret = 0;
+ /*
+ * make sure that there are no outstanding requests when
+ * clock scaling is in progress
+ */
+ mutex_lock(&hba->host->scan_mutex);
+ blk_mq_quiesce_tagset(&hba->host->tag_set);
+ mutex_lock(&hba->wb_mutex);
+ down_write(&hba->clk_scaling_lock);
+
+ if (!hba->clk_scaling.is_allowed ||
+ ufshcd_wait_for_pending_cmds(hba, timeout_us)) {
+ ret = -EBUSY;
+ up_write(&hba->clk_scaling_lock);
+ mutex_unlock(&hba->wb_mutex);
+ blk_mq_unquiesce_tagset(&hba->host->tag_set);
+ mutex_unlock(&hba->host->scan_mutex);
+ goto out;
+ }
+
+ /* let's not get into low power until clock scaling is completed */
+ ufshcd_hold(hba);
+
+out:
+ return ret;
+}
+
+static void ufshcd_clock_scaling_unprepare(struct ufs_hba *hba, int err)
+ __releases(&hba->clk_scaling_lock)
+ __releases(&hba->wb_mutex)
+ __releases(&hba->host->scan_mutex)
+{
+ up_write(&hba->clk_scaling_lock);
+ mutex_unlock(&hba->wb_mutex);
+ blk_mq_unquiesce_tagset(&hba->host->tag_set);
+ mutex_unlock(&hba->host->scan_mutex);
+
+ /* Enable Write Booster if current gear requires it else disable it */
+ if (ufshcd_enable_wb_if_scaling_up(hba) && !err)
+ ufshcd_wb_toggle(hba, hba->pwr_info.gear_rx >= hba->clk_scaling.wb_gear);
+
+ ufshcd_release(hba);
+}
+
+/**
+ * ufshcd_devfreq_scale - scale up/down UFS clocks and gear
+ * @hba: per adapter instance
+ * @freq: frequency to scale
+ * @scale_up: True for scaling up and false for scalin down
+ *
+ * Return: 0 for success; -EBUSY if scaling can't happen at this time; non-zero
+ * for any other errors.
+ */
+static int ufshcd_devfreq_scale(struct ufs_hba *hba, unsigned long freq,
+ bool scale_up)
+{
+ u32 old_gear = hba->pwr_info.gear_rx;
+ u32 new_gear = 0;
+ int ret = 0;
+
+ new_gear = ufshcd_vops_freq_to_gear_speed(hba, freq);
+
+ ret = ufshcd_clock_scaling_prepare(hba, 1 * USEC_PER_SEC);
+ if (ret)
+ return ret;
+
+ /* scale down the gear before scaling down clocks */
+ if (!scale_up) {
+ ret = ufshcd_scale_gear(hba, new_gear, false);
+ if (ret)
+ goto out_unprepare;
+ }
+
+ ret = ufshcd_scale_clks(hba, freq, scale_up);
+ if (ret) {
+ if (!scale_up)
+ ufshcd_scale_gear(hba, old_gear, true);
+ goto out_unprepare;
+ }
+
+ /* scale up the gear after scaling up clocks */
+ if (scale_up) {
+ ret = ufshcd_scale_gear(hba, new_gear, true);
+ if (ret) {
+ ufshcd_scale_clks(hba, hba->devfreq->previous_freq,
+ false);
+ goto out_unprepare;
+ }
+ }
+
+out_unprepare:
+ ufshcd_clock_scaling_unprepare(hba, ret);
+ return ret;
+}
+
+static void ufshcd_clk_scaling_suspend_work(struct work_struct *work)
+{
+ struct ufs_hba *hba = container_of(work, struct ufs_hba,
+ clk_scaling.suspend_work);
+
+ scoped_guard(spinlock_irqsave, &hba->clk_scaling.lock)
+ {
+ if (hba->clk_scaling.active_reqs ||
+ hba->clk_scaling.is_suspended)
+ return;
+
+ hba->clk_scaling.is_suspended = true;
+ hba->clk_scaling.window_start_t = 0;
+ }
+
+ devfreq_suspend_device(hba->devfreq);
+}
+
+static void ufshcd_clk_scaling_resume_work(struct work_struct *work)
+{
+ struct ufs_hba *hba = container_of(work, struct ufs_hba,
+ clk_scaling.resume_work);
+
+ scoped_guard(spinlock_irqsave, &hba->clk_scaling.lock)
+ {
+ if (!hba->clk_scaling.is_suspended)
+ return;
+ hba->clk_scaling.is_suspended = false;
+ }
+
+ devfreq_resume_device(hba->devfreq);
+}
+
+static int ufshcd_devfreq_target(struct device *dev,
+ unsigned long *freq, u32 flags)
+{
+ int ret = 0;
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ ktime_t start;
+ bool scale_up = false, sched_clk_scaling_suspend_work = false;
+ struct list_head *clk_list = &hba->clk_list_head;
+ struct ufs_clk_info *clki;
+
+ if (!ufshcd_is_clkscaling_supported(hba))
+ return -EINVAL;
+
+ if (hba->use_pm_opp) {
+ struct dev_pm_opp *opp;
+
+ /* Get the recommended frequency from OPP framework */
+ opp = devfreq_recommended_opp(dev, freq, flags);
+ if (IS_ERR(opp))
+ return PTR_ERR(opp);
+
+ dev_pm_opp_put(opp);
+ } else {
+ /* Override with the closest supported frequency */
+ clki = list_first_entry(&hba->clk_list_head, struct ufs_clk_info,
+ list);
+ *freq = (unsigned long) clk_round_rate(clki->clk, *freq);
+ }
+
+ scoped_guard(spinlock_irqsave, &hba->clk_scaling.lock)
+ {
+ if (ufshcd_eh_in_progress(hba))
+ return 0;
+
+ /* Skip scaling clock when clock scaling is suspended */
+ if (hba->clk_scaling.is_suspended) {
+ dev_warn(hba->dev, "clock scaling is suspended, skip");
+ return 0;
+ }
+
+ if (!hba->clk_scaling.active_reqs)
+ sched_clk_scaling_suspend_work = true;
+
+ if (list_empty(clk_list))
+ goto out;
+
+ /* Decide based on the target or rounded-off frequency and update */
+ if (hba->use_pm_opp)
+ scale_up = *freq > hba->clk_scaling.target_freq;
+ else
+ scale_up = *freq == clki->max_freq;
+
+ if (!hba->use_pm_opp && !scale_up)
+ *freq = clki->min_freq;
+
+ /* Update the frequency */
+ if (!ufshcd_is_devfreq_scaling_required(hba, *freq, scale_up)) {
+ ret = 0;
+ goto out; /* no state change required */
+ }
+ }
+
+ start = ktime_get();
+ ret = ufshcd_devfreq_scale(hba, *freq, scale_up);
+ if (!ret)
+ hba->clk_scaling.target_freq = *freq;
+
+ trace_ufshcd_profile_clk_scaling(hba,
+ (scale_up ? "up" : "down"),
+ ktime_to_us(ktime_sub(ktime_get(), start)), ret);
+
+out:
+ if (sched_clk_scaling_suspend_work &&
+ (!scale_up || hba->clk_scaling.suspend_on_no_request))
+ queue_work(hba->clk_scaling.workq,
+ &hba->clk_scaling.suspend_work);
+
+ return ret;
+}
+
+static int ufshcd_devfreq_get_dev_status(struct device *dev,
+ struct devfreq_dev_status *stat)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct ufs_clk_scaling *scaling = &hba->clk_scaling;
+ ktime_t curr_t;
+
+ if (!ufshcd_is_clkscaling_supported(hba))
+ return -EINVAL;
+
+ memset(stat, 0, sizeof(*stat));
+
+ guard(spinlock_irqsave)(&hba->clk_scaling.lock);
+
+ curr_t = ktime_get();
+ if (!scaling->window_start_t)
+ goto start_window;
+
+ /*
+ * If current frequency is 0, then the ondemand governor considers
+ * there's no initial frequency set. And it always requests to set
+ * to max. frequency.
+ */
+ if (hba->use_pm_opp) {
+ stat->current_frequency = hba->clk_scaling.target_freq;
+ } else {
+ struct list_head *clk_list = &hba->clk_list_head;
+ struct ufs_clk_info *clki;
+
+ clki = list_first_entry(clk_list, struct ufs_clk_info, list);
+ stat->current_frequency = clki->curr_freq;
+ }
+
+ if (scaling->is_busy_started)
+ scaling->tot_busy_t += ktime_us_delta(curr_t,
+ scaling->busy_start_t);
+ stat->total_time = ktime_us_delta(curr_t, scaling->window_start_t);
+ stat->busy_time = scaling->tot_busy_t;
+start_window:
+ scaling->window_start_t = curr_t;
+ scaling->tot_busy_t = 0;
+
+ if (scaling->active_reqs) {
+ scaling->busy_start_t = curr_t;
+ scaling->is_busy_started = true;
+ } else {
+ scaling->busy_start_t = 0;
+ scaling->is_busy_started = false;
+ }
+
+ return 0;
+}
+
+static int ufshcd_devfreq_init(struct ufs_hba *hba)
+{
+ struct list_head *clk_list = &hba->clk_list_head;
+ struct ufs_clk_info *clki;
+ struct devfreq *devfreq;
+ int ret;
+
+ /* Skip devfreq if we don't have any clocks in the list */
+ if (list_empty(clk_list))
+ return 0;
+
+ if (!hba->use_pm_opp) {
+ clki = list_first_entry(clk_list, struct ufs_clk_info, list);
+ dev_pm_opp_add(hba->dev, clki->min_freq, 0);
+ dev_pm_opp_add(hba->dev, clki->max_freq, 0);
+ }
+
+ ufshcd_vops_config_scaling_param(hba, &hba->vps->devfreq_profile,
+ &hba->vps->ondemand_data);
+ devfreq = devfreq_add_device(hba->dev,
+ &hba->vps->devfreq_profile,
+ DEVFREQ_GOV_SIMPLE_ONDEMAND,
+ &hba->vps->ondemand_data);
+ if (IS_ERR(devfreq)) {
+ ret = PTR_ERR(devfreq);
+ dev_err(hba->dev, "Unable to register with devfreq %d\n", ret);
+
+ if (!hba->use_pm_opp) {
+ dev_pm_opp_remove(hba->dev, clki->min_freq);
+ dev_pm_opp_remove(hba->dev, clki->max_freq);
+ }
+ return ret;
+ }
+
+ hba->devfreq = devfreq;
+
+ return 0;
+}
+
+static void ufshcd_devfreq_remove(struct ufs_hba *hba)
+{
+ struct list_head *clk_list = &hba->clk_list_head;
+
+ if (!hba->devfreq)
+ return;
+
+ devfreq_remove_device(hba->devfreq);
+ hba->devfreq = NULL;
+
+ if (!hba->use_pm_opp) {
+ struct ufs_clk_info *clki;
+
+ clki = list_first_entry(clk_list, struct ufs_clk_info, list);
+ dev_pm_opp_remove(hba->dev, clki->min_freq);
+ dev_pm_opp_remove(hba->dev, clki->max_freq);
+ }
+}
+
+static void ufshcd_suspend_clkscaling(struct ufs_hba *hba)
+{
+ bool suspend = false;
+
+ cancel_work_sync(&hba->clk_scaling.suspend_work);
+ cancel_work_sync(&hba->clk_scaling.resume_work);
+
+ scoped_guard(spinlock_irqsave, &hba->clk_scaling.lock)
+ {
+ if (!hba->clk_scaling.is_suspended) {
+ suspend = true;
+ hba->clk_scaling.is_suspended = true;
+ hba->clk_scaling.window_start_t = 0;
+ }
+ }
+
+ if (suspend)
+ devfreq_suspend_device(hba->devfreq);
+}
+
+static void ufshcd_resume_clkscaling(struct ufs_hba *hba)
+{
+ bool resume = false;
+
+ scoped_guard(spinlock_irqsave, &hba->clk_scaling.lock)
+ {
+ if (hba->clk_scaling.is_suspended) {
+ resume = true;
+ hba->clk_scaling.is_suspended = false;
+ }
+ }
+
+ if (resume)
+ devfreq_resume_device(hba->devfreq);
+}
+
+static ssize_t ufshcd_clkscale_enable_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->clk_scaling.is_enabled);
+}
+
+static ssize_t ufshcd_clkscale_enable_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct ufs_clk_info *clki;
+ unsigned long freq;
+ u32 value;
+ int err = 0;
+
+ if (kstrtou32(buf, 0, &value))
+ return -EINVAL;
+
+ down(&hba->host_sem);
+ if (!ufshcd_is_user_access_allowed(hba)) {
+ err = -EBUSY;
+ goto out;
+ }
+
+ value = !!value;
+ if (value == hba->clk_scaling.is_enabled)
+ goto out;
+
+ ufshcd_rpm_get_sync(hba);
+ ufshcd_hold(hba);
+
+ hba->clk_scaling.is_enabled = value;
+
+ if (value) {
+ ufshcd_resume_clkscaling(hba);
+ goto out_rel;
+ }
+
+ clki = list_first_entry(&hba->clk_list_head, struct ufs_clk_info, list);
+ freq = clki->max_freq;
+
+ ufshcd_suspend_clkscaling(hba);
+
+ if (!ufshcd_is_devfreq_scaling_required(hba, freq, true))
+ goto out_rel;
+
+ err = ufshcd_devfreq_scale(hba, freq, true);
+ if (err)
+ dev_err(hba->dev, "%s: failed to scale clocks up %d\n",
+ __func__, err);
+ else
+ hba->clk_scaling.target_freq = freq;
+
+out_rel:
+ ufshcd_release(hba);
+ ufshcd_rpm_put_sync(hba);
+out:
+ up(&hba->host_sem);
+ return err ? err : count;
+}
+
+static void ufshcd_init_clk_scaling_sysfs(struct ufs_hba *hba)
+{
+ hba->clk_scaling.enable_attr.show = ufshcd_clkscale_enable_show;
+ hba->clk_scaling.enable_attr.store = ufshcd_clkscale_enable_store;
+ sysfs_attr_init(&hba->clk_scaling.enable_attr.attr);
+ hba->clk_scaling.enable_attr.attr.name = "clkscale_enable";
+ hba->clk_scaling.enable_attr.attr.mode = 0644;
+ if (device_create_file(hba->dev, &hba->clk_scaling.enable_attr))
+ dev_err(hba->dev, "Failed to create sysfs for clkscale_enable\n");
+}
+
+static void ufshcd_remove_clk_scaling_sysfs(struct ufs_hba *hba)
+{
+ if (hba->clk_scaling.enable_attr.attr.name)
+ device_remove_file(hba->dev, &hba->clk_scaling.enable_attr);
+}
+
+static void ufshcd_init_clk_scaling(struct ufs_hba *hba)
+{
+ if (!ufshcd_is_clkscaling_supported(hba))
+ return;
+
+ if (!hba->clk_scaling.min_gear)
+ hba->clk_scaling.min_gear = UFS_HS_G1;
+
+ if (!hba->clk_scaling.wb_gear)
+ /* Use intermediate gear speed HS_G3 as the default wb_gear */
+ hba->clk_scaling.wb_gear = UFS_HS_G3;
+
+ INIT_WORK(&hba->clk_scaling.suspend_work,
+ ufshcd_clk_scaling_suspend_work);
+ INIT_WORK(&hba->clk_scaling.resume_work,
+ ufshcd_clk_scaling_resume_work);
+
+ spin_lock_init(&hba->clk_scaling.lock);
+
+ hba->clk_scaling.workq = alloc_ordered_workqueue(
+ "ufs_clkscaling_%d", WQ_MEM_RECLAIM, hba->host->host_no);
+
+ hba->clk_scaling.is_initialized = true;
+}
+
+static void ufshcd_exit_clk_scaling(struct ufs_hba *hba)
+{
+ if (!hba->clk_scaling.is_initialized)
+ return;
+
+ ufshcd_remove_clk_scaling_sysfs(hba);
+ destroy_workqueue(hba->clk_scaling.workq);
+ ufshcd_devfreq_remove(hba);
+ hba->clk_scaling.is_initialized = false;
+}
+
+static void ufshcd_ungate_work(struct work_struct *work)
+{
+ int ret;
+ struct ufs_hba *hba = container_of(work, struct ufs_hba,
+ clk_gating.ungate_work);
+
+ cancel_delayed_work_sync(&hba->clk_gating.gate_work);
+
+ scoped_guard(spinlock_irqsave, &hba->clk_gating.lock) {
+ if (hba->clk_gating.state == CLKS_ON)
+ return;
+ }
+
+ ufshcd_hba_vreg_set_hpm(hba);
+ ufshcd_setup_clocks(hba, true);
+
+ ufshcd_enable_irq(hba);
+
+ /* Exit from hibern8 */
+ if (ufshcd_can_hibern8_during_gating(hba)) {
+ /* Prevent gating in this path */
+ hba->clk_gating.is_suspended = true;
+ if (ufshcd_is_link_hibern8(hba)) {
+ ret = ufshcd_uic_hibern8_exit(hba);
+ if (ret)
+ dev_err(hba->dev, "%s: hibern8 exit failed %d\n",
+ __func__, ret);
+ else
+ ufshcd_set_link_active(hba);
+ }
+ hba->clk_gating.is_suspended = false;
+ }
+}
+
+/**
+ * ufshcd_hold - Enable clocks that were gated earlier due to ufshcd_release.
+ * Also, exit from hibern8 mode and set the link as active.
+ * @hba: per adapter instance
+ */
+void ufshcd_hold(struct ufs_hba *hba)
+{
+ bool flush_result;
+ unsigned long flags;
+
+ if (!ufshcd_is_clkgating_allowed(hba) ||
+ !hba->clk_gating.is_initialized)
+ return;
+ spin_lock_irqsave(&hba->clk_gating.lock, flags);
+ hba->clk_gating.active_reqs++;
+
+start:
+ switch (hba->clk_gating.state) {
+ case CLKS_ON:
+ /*
+ * Wait for the ungate work to complete if in progress.
+ * Though the clocks may be in ON state, the link could
+ * still be in hibner8 state if hibern8 is allowed
+ * during clock gating.
+ * Make sure we exit hibern8 state also in addition to
+ * clocks being ON.
+ */
+ if (ufshcd_can_hibern8_during_gating(hba) &&
+ ufshcd_is_link_hibern8(hba)) {
+ spin_unlock_irqrestore(&hba->clk_gating.lock, flags);
+ flush_result = flush_work(&hba->clk_gating.ungate_work);
+ if (hba->clk_gating.is_suspended && !flush_result)
+ return;
+ spin_lock_irqsave(&hba->clk_gating.lock, flags);
+ goto start;
+ }
+ break;
+ case REQ_CLKS_OFF:
+ if (cancel_delayed_work(&hba->clk_gating.gate_work)) {
+ hba->clk_gating.state = CLKS_ON;
+ trace_ufshcd_clk_gating(hba,
+ hba->clk_gating.state);
+ break;
+ }
+ /*
+ * If we are here, it means gating work is either done or
+ * currently running. Hence, fall through to cancel gating
+ * work and to enable clocks.
+ */
+ fallthrough;
+ case CLKS_OFF:
+ hba->clk_gating.state = REQ_CLKS_ON;
+ trace_ufshcd_clk_gating(hba,
+ hba->clk_gating.state);
+ queue_work(hba->clk_gating.clk_gating_workq,
+ &hba->clk_gating.ungate_work);
+ /*
+ * fall through to check if we should wait for this
+ * work to be done or not.
+ */
+ fallthrough;
+ case REQ_CLKS_ON:
+ spin_unlock_irqrestore(&hba->clk_gating.lock, flags);
+ flush_work(&hba->clk_gating.ungate_work);
+ /* Make sure state is CLKS_ON before returning */
+ spin_lock_irqsave(&hba->clk_gating.lock, flags);
+ goto start;
+ default:
+ dev_err(hba->dev, "%s: clk gating is in invalid state %d\n",
+ __func__, hba->clk_gating.state);
+ break;
+ }
+ spin_unlock_irqrestore(&hba->clk_gating.lock, flags);
+}
+EXPORT_SYMBOL_GPL(ufshcd_hold);
+
+static void ufshcd_gate_work(struct work_struct *work)
+{
+ struct ufs_hba *hba = container_of(work, struct ufs_hba,
+ clk_gating.gate_work.work);
+ int ret;
+
+ scoped_guard(spinlock_irqsave, &hba->clk_gating.lock) {
+ /*
+ * In case you are here to cancel this work the gating state
+ * would be marked as REQ_CLKS_ON. In this case save time by
+ * skipping the gating work and exit after changing the clock
+ * state to CLKS_ON.
+ */
+ if (hba->clk_gating.is_suspended ||
+ hba->clk_gating.state != REQ_CLKS_OFF) {
+ hba->clk_gating.state = CLKS_ON;
+ trace_ufshcd_clk_gating(hba,
+ hba->clk_gating.state);
+ return;
+ }
+
+ if (hba->clk_gating.active_reqs)
+ return;
+ }
+
+ scoped_guard(spinlock_irqsave, hba->host->host_lock) {
+ if (ufshcd_is_ufs_dev_busy(hba) ||
+ hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL)
+ return;
+ }
+
+ /* put the link into hibern8 mode before turning off clocks */
+ if (ufshcd_can_hibern8_during_gating(hba)) {
+ ret = ufshcd_uic_hibern8_enter(hba);
+ if (ret) {
+ hba->clk_gating.state = CLKS_ON;
+ dev_err(hba->dev, "%s: hibern8 enter failed %d\n",
+ __func__, ret);
+ trace_ufshcd_clk_gating(hba,
+ hba->clk_gating.state);
+ return;
+ }
+ ufshcd_set_link_hibern8(hba);
+ }
+
+ ufshcd_disable_irq(hba);
+
+ ufshcd_setup_clocks(hba, false);
+
+ /* Put the host controller in low power mode if possible */
+ ufshcd_hba_vreg_set_lpm(hba);
+ /*
+ * In case you are here to cancel this work the gating state
+ * would be marked as REQ_CLKS_ON. In this case keep the state
+ * as REQ_CLKS_ON which would anyway imply that clocks are off
+ * and a request to turn them on is pending. By doing this way,
+ * we keep the state machine in tact and this would ultimately
+ * prevent from doing cancel work multiple times when there are
+ * new requests arriving before the current cancel work is done.
+ */
+ guard(spinlock_irqsave)(&hba->clk_gating.lock);
+ if (hba->clk_gating.state == REQ_CLKS_OFF) {
+ hba->clk_gating.state = CLKS_OFF;
+ trace_ufshcd_clk_gating(hba,
+ hba->clk_gating.state);
+ }
+}
+
+static void __ufshcd_release(struct ufs_hba *hba)
+{
+ lockdep_assert_held(&hba->clk_gating.lock);
+
+ if (!ufshcd_is_clkgating_allowed(hba))
+ return;
+
+ hba->clk_gating.active_reqs--;
+
+ if (hba->clk_gating.active_reqs || hba->clk_gating.is_suspended ||
+ !hba->clk_gating.is_initialized ||
+ hba->clk_gating.state == CLKS_OFF)
+ return;
+
+ scoped_guard(spinlock_irqsave, hba->host->host_lock) {
+ if (ufshcd_has_pending_tasks(hba) ||
+ hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL)
+ return;
+ }
+
+ hba->clk_gating.state = REQ_CLKS_OFF;
+ trace_ufshcd_clk_gating(hba, hba->clk_gating.state);
+ queue_delayed_work(hba->clk_gating.clk_gating_workq,
+ &hba->clk_gating.gate_work,
+ msecs_to_jiffies(hba->clk_gating.delay_ms));
+}
+
+void ufshcd_release(struct ufs_hba *hba)
+{
+ guard(spinlock_irqsave)(&hba->clk_gating.lock);
+ __ufshcd_release(hba);
+}
+EXPORT_SYMBOL_GPL(ufshcd_release);
+
+static ssize_t ufshcd_clkgate_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%lu\n", hba->clk_gating.delay_ms);
+}
+
+void ufshcd_clkgate_delay_set(struct device *dev, unsigned long value)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ guard(spinlock_irqsave)(&hba->clk_gating.lock);
+ hba->clk_gating.delay_ms = value;
+}
+EXPORT_SYMBOL_GPL(ufshcd_clkgate_delay_set);
+
+static ssize_t ufshcd_clkgate_delay_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ unsigned long value;
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ ufshcd_clkgate_delay_set(dev, value);
+ return count;
+}
+
+static ssize_t ufshcd_clkgate_enable_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->clk_gating.is_enabled);
+}
+
+static ssize_t ufshcd_clkgate_enable_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ u32 value;
+
+ if (kstrtou32(buf, 0, &value))
+ return -EINVAL;
+
+ value = !!value;
+
+ guard(spinlock_irqsave)(&hba->clk_gating.lock);
+
+ if (value == hba->clk_gating.is_enabled)
+ return count;
+
+ if (value)
+ __ufshcd_release(hba);
+ else
+ hba->clk_gating.active_reqs++;
+
+ hba->clk_gating.is_enabled = value;
+
+ return count;
+}
+
+static void ufshcd_init_clk_gating_sysfs(struct ufs_hba *hba)
+{
+ hba->clk_gating.delay_attr.show = ufshcd_clkgate_delay_show;
+ hba->clk_gating.delay_attr.store = ufshcd_clkgate_delay_store;
+ sysfs_attr_init(&hba->clk_gating.delay_attr.attr);
+ hba->clk_gating.delay_attr.attr.name = "clkgate_delay_ms";
+ hba->clk_gating.delay_attr.attr.mode = 0644;
+ if (device_create_file(hba->dev, &hba->clk_gating.delay_attr))
+ dev_err(hba->dev, "Failed to create sysfs for clkgate_delay\n");
+
+ hba->clk_gating.enable_attr.show = ufshcd_clkgate_enable_show;
+ hba->clk_gating.enable_attr.store = ufshcd_clkgate_enable_store;
+ sysfs_attr_init(&hba->clk_gating.enable_attr.attr);
+ hba->clk_gating.enable_attr.attr.name = "clkgate_enable";
+ hba->clk_gating.enable_attr.attr.mode = 0644;
+ if (device_create_file(hba->dev, &hba->clk_gating.enable_attr))
+ dev_err(hba->dev, "Failed to create sysfs for clkgate_enable\n");
+}
+
+static void ufshcd_remove_clk_gating_sysfs(struct ufs_hba *hba)
+{
+ if (hba->clk_gating.delay_attr.attr.name)
+ device_remove_file(hba->dev, &hba->clk_gating.delay_attr);
+ if (hba->clk_gating.enable_attr.attr.name)
+ device_remove_file(hba->dev, &hba->clk_gating.enable_attr);
+}
+
+static void ufshcd_init_clk_gating(struct ufs_hba *hba)
+{
+ if (!ufshcd_is_clkgating_allowed(hba))
+ return;
+
+ hba->clk_gating.state = CLKS_ON;
+
+ hba->clk_gating.delay_ms = 150;
+ INIT_DELAYED_WORK(&hba->clk_gating.gate_work, ufshcd_gate_work);
+ INIT_WORK(&hba->clk_gating.ungate_work, ufshcd_ungate_work);
+
+ hba->clk_gating.clk_gating_workq = alloc_ordered_workqueue(
+ "ufs_clk_gating_%d", WQ_MEM_RECLAIM | WQ_HIGHPRI,
+ hba->host->host_no);
+
+ ufshcd_init_clk_gating_sysfs(hba);
+
+ hba->clk_gating.is_enabled = true;
+ hba->clk_gating.is_initialized = true;
+}
+
+static void ufshcd_exit_clk_gating(struct ufs_hba *hba)
+{
+ if (!hba->clk_gating.is_initialized)
+ return;
+
+ ufshcd_remove_clk_gating_sysfs(hba);
+
+ /* Ungate the clock if necessary. */
+ ufshcd_hold(hba);
+ hba->clk_gating.is_initialized = false;
+ ufshcd_release(hba);
+
+ destroy_workqueue(hba->clk_gating.clk_gating_workq);
+}
+
+static void ufshcd_clk_scaling_start_busy(struct ufs_hba *hba)
+{
+ bool queue_resume_work = false;
+ ktime_t curr_t;
+
+ if (!ufshcd_is_clkscaling_supported(hba))
+ return;
+
+ curr_t = ktime_get();
+
+ guard(spinlock_irqsave)(&hba->clk_scaling.lock);
+
+ if (!hba->clk_scaling.active_reqs++)
+ queue_resume_work = true;
+
+ if (!hba->clk_scaling.is_enabled || hba->pm_op_in_progress)
+ return;
+
+ if (queue_resume_work)
+ queue_work(hba->clk_scaling.workq,
+ &hba->clk_scaling.resume_work);
+
+ if (!hba->clk_scaling.window_start_t) {
+ hba->clk_scaling.window_start_t = curr_t;
+ hba->clk_scaling.tot_busy_t = 0;
+ hba->clk_scaling.is_busy_started = false;
+ }
+
+ if (!hba->clk_scaling.is_busy_started) {
+ hba->clk_scaling.busy_start_t = curr_t;
+ hba->clk_scaling.is_busy_started = true;
+ }
+}
+
+static void ufshcd_clk_scaling_update_busy(struct ufs_hba *hba)
+{
+ struct ufs_clk_scaling *scaling = &hba->clk_scaling;
+
+ if (!ufshcd_is_clkscaling_supported(hba))
+ return;
+
+ guard(spinlock_irqsave)(&hba->clk_scaling.lock);
+
+ hba->clk_scaling.active_reqs--;
+ if (!scaling->active_reqs && scaling->is_busy_started) {
+ scaling->tot_busy_t += ktime_to_us(ktime_sub(ktime_get(),
+ scaling->busy_start_t));
+ scaling->busy_start_t = 0;
+ scaling->is_busy_started = false;
+ }
+}
+
+static inline int ufshcd_monitor_opcode2dir(u8 opcode)
+{
+ if (opcode == READ_6 || opcode == READ_10 || opcode == READ_16)
+ return READ;
+ else if (opcode == WRITE_6 || opcode == WRITE_10 || opcode == WRITE_16)
+ return WRITE;
+ else
+ return -EINVAL;
+}
+
+/* Must only be called for SCSI commands. */
+static inline bool ufshcd_should_inform_monitor(struct ufs_hba *hba,
+ struct scsi_cmnd *cmd)
+{
+ const struct ufs_hba_monitor *m = &hba->monitor;
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+
+ return m->enabled &&
+ (!m->chunk_size || m->chunk_size == cmd->sdb.length) &&
+ ktime_before(hba->monitor.enabled_ts, lrbp->issue_time_stamp);
+}
+
+static void ufshcd_start_monitor(struct ufs_hba *hba, struct scsi_cmnd *cmd)
+{
+ int dir = ufshcd_monitor_opcode2dir(cmd->cmnd[0]);
+ unsigned long flags;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (dir >= 0 && hba->monitor.nr_queued[dir]++ == 0)
+ hba->monitor.busy_start_ts[dir] = ktime_get();
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+}
+
+static void ufshcd_update_monitor(struct ufs_hba *hba, struct scsi_cmnd *cmd)
+{
+ struct request *req = scsi_cmd_to_rq(cmd);
+ const struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ int dir = ufshcd_monitor_opcode2dir(cmd->cmnd[0]);
+ unsigned long flags;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (dir >= 0 && hba->monitor.nr_queued[dir] > 0) {
+ struct ufs_hba_monitor *m = &hba->monitor;
+ ktime_t now, inc, lat;
+
+ now = lrbp->compl_time_stamp;
+ inc = ktime_sub(now, m->busy_start_ts[dir]);
+ m->total_busy[dir] = ktime_add(m->total_busy[dir], inc);
+ m->nr_sec_rw[dir] += blk_rq_sectors(req);
+
+ /* Update latencies */
+ m->nr_req[dir]++;
+ lat = ktime_sub(now, lrbp->issue_time_stamp);
+ m->lat_sum[dir] += lat;
+ if (m->lat_max[dir] < lat || !m->lat_max[dir])
+ m->lat_max[dir] = lat;
+ if (m->lat_min[dir] > lat || !m->lat_min[dir])
+ m->lat_min[dir] = lat;
+
+ m->nr_queued[dir]--;
+ /* Push forward the busy start of monitor */
+ m->busy_start_ts[dir] = now;
+ }
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+}
+
+/* Returns %true for SCSI commands and %false for device management commands. */
+static bool ufshcd_is_scsi_cmd(struct scsi_cmnd *cmd)
+{
+ return !blk_mq_is_reserved_rq(scsi_cmd_to_rq(cmd));
+}
+
+/**
+ * ufshcd_send_command - Send SCSI or device management commands
+ * @hba: per adapter instance
+ * @cmd: SCSI command or device management command pointer
+ * @hwq: pointer to hardware queue instance
+ */
+static inline void ufshcd_send_command(struct ufs_hba *hba,
+ struct scsi_cmnd *cmd,
+ struct ufs_hw_queue *hwq)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ const int tag = scsi_cmd_to_rq(cmd)->tag;
+ unsigned long flags;
+
+ if (hba->monitor.enabled) {
+ lrbp->issue_time_stamp = ktime_get();
+ lrbp->issue_time_stamp_local_clock = local_clock();
+ lrbp->compl_time_stamp = ktime_set(0, 0);
+ lrbp->compl_time_stamp_local_clock = 0;
+ }
+ if (ufshcd_is_scsi_cmd(cmd)) {
+ ufshcd_add_command_trace(hba, cmd, UFS_CMD_SEND);
+ ufshcd_clk_scaling_start_busy(hba);
+ if (unlikely(ufshcd_should_inform_monitor(hba, cmd)))
+ ufshcd_start_monitor(hba, cmd);
+ }
+
+ if (hba->mcq_enabled) {
+ int utrd_size = sizeof(struct utp_transfer_req_desc);
+ struct utp_transfer_req_desc *src = lrbp->utr_descriptor_ptr;
+ struct utp_transfer_req_desc *dest;
+
+ spin_lock(&hwq->sq_lock);
+ dest = hwq->sqe_base_addr + hwq->sq_tail_slot;
+ memcpy(dest, src, utrd_size);
+ ufshcd_inc_sq_tail(hwq);
+ spin_unlock(&hwq->sq_lock);
+ } else {
+ spin_lock_irqsave(&hba->outstanding_lock, flags);
+ if (hba->vops && hba->vops->setup_xfer_req)
+ hba->vops->setup_xfer_req(hba, tag,
+ ufshcd_is_scsi_cmd(cmd));
+ __set_bit(tag, &hba->outstanding_reqs);
+ ufshcd_writel(hba, 1 << tag, REG_UTP_TRANSFER_REQ_DOOR_BELL);
+ spin_unlock_irqrestore(&hba->outstanding_lock, flags);
+ }
+}
+
+/**
+ * ufshcd_copy_sense_data - Copy sense data in case of check condition
+ * @cmd: SCSI command
+ */
+static inline void ufshcd_copy_sense_data(struct scsi_cmnd *cmd)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ u8 *const sense_buffer = cmd->sense_buffer;
+ u16 resp_len;
+ int len;
+
+ resp_len = be16_to_cpu(lrbp->ucd_rsp_ptr->header.data_segment_length);
+ if (sense_buffer && resp_len) {
+ int len_to_copy;
+
+ len = be16_to_cpu(lrbp->ucd_rsp_ptr->sr.sense_data_len);
+ len_to_copy = min_t(int, UFS_SENSE_SIZE, len);
+
+ memcpy(sense_buffer, lrbp->ucd_rsp_ptr->sr.sense_data,
+ len_to_copy);
+ }
+}
+
+/**
+ * ufshcd_copy_query_response() - Copy the Query Response and the data
+ * descriptor
+ * @hba: per adapter instance
+ * @lrbp: pointer to local reference block
+ *
+ * Return: 0 upon success; < 0 upon failure.
+ */
+static
+int ufshcd_copy_query_response(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
+{
+ struct ufs_query_res *query_res = &hba->dev_cmd.query.response;
+
+ memcpy(&query_res->upiu_res, &lrbp->ucd_rsp_ptr->qr, QUERY_OSF_SIZE);
+
+ /* Get the descriptor */
+ if (hba->dev_cmd.query.descriptor &&
+ lrbp->ucd_rsp_ptr->qr.opcode == UPIU_QUERY_OPCODE_READ_DESC) {
+ u8 *descp = (u8 *)lrbp->ucd_rsp_ptr +
+ GENERAL_UPIU_REQUEST_SIZE;
+ u16 resp_len;
+ u16 buf_len;
+
+ /* data segment length */
+ resp_len = be16_to_cpu(lrbp->ucd_rsp_ptr->header
+ .data_segment_length);
+ buf_len = be16_to_cpu(
+ hba->dev_cmd.query.request.upiu_req.length);
+ if (likely(buf_len >= resp_len)) {
+ memcpy(hba->dev_cmd.query.descriptor, descp, resp_len);
+ } else {
+ dev_warn(hba->dev,
+ "%s: rsp size %d is bigger than buffer size %d",
+ __func__, resp_len, buf_len);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * ufshcd_hba_capabilities - Read controller capabilities
+ * @hba: per adapter instance
+ *
+ * Return: 0 on success, negative on error.
+ */
+static inline int ufshcd_hba_capabilities(struct ufs_hba *hba)
+{
+ int err;
+
+ hba->capabilities = ufshcd_readl(hba, REG_CONTROLLER_CAPABILITIES);
+
+ /* nutrs and nutmrs are 0 based values */
+ hba->nutrs = (hba->capabilities & MASK_TRANSFER_REQUESTS_SLOTS_SDB) + 1;
+ hba->nutmrs =
+ ((hba->capabilities & MASK_TASK_MANAGEMENT_REQUEST_SLOTS) >> 16) + 1;
+
+ if (hba->vops && hba->vops->get_hba_nortt)
+ hba->nortt = hba->vops->get_hba_nortt(hba);
+ else
+ hba->nortt = FIELD_GET(MASK_NUMBER_OUTSTANDING_RTT, hba->capabilities) + 1;
+
+ /* Read crypto capabilities */
+ err = ufshcd_hba_init_crypto_capabilities(hba);
+ if (err) {
+ dev_err(hba->dev, "crypto setup failed\n");
+ return err;
+ }
+
+ /*
+ * The UFSHCI 3.0 specification does not define MCQ_SUPPORT and
+ * LSDB_SUPPORT, but [31:29] as reserved bits with reset value 0s, which
+ * means we can simply read values regardless of version.
+ */
+ hba->mcq_sup = FIELD_GET(MASK_MCQ_SUPPORT, hba->capabilities);
+ /*
+ * 0h: legacy single doorbell support is available
+ * 1h: indicate that legacy single doorbell support has been removed
+ */
+ if (!(hba->quirks & UFSHCD_QUIRK_BROKEN_LSDBS_CAP))
+ hba->lsdb_sup = !FIELD_GET(MASK_LSDB_SUPPORT, hba->capabilities);
+ else
+ hba->lsdb_sup = true;
+
+ hba->mcq_capabilities = ufshcd_readl(hba, REG_MCQCAP);
+
+ return 0;
+}
+
+/**
+ * ufshcd_ready_for_uic_cmd - Check if controller is ready
+ * to accept UIC commands
+ * @hba: per adapter instance
+ *
+ * Return: true on success, else false.
+ */
+static inline bool ufshcd_ready_for_uic_cmd(struct ufs_hba *hba)
+{
+ u32 val;
+ int ret = read_poll_timeout(ufshcd_readl, val, val & UIC_COMMAND_READY,
+ 500, uic_cmd_timeout * 1000, false, hba,
+ REG_CONTROLLER_STATUS);
+ return ret == 0;
+}
+
+/**
+ * ufshcd_get_upmcrs - Get the power mode change request status
+ * @hba: Pointer to adapter instance
+ *
+ * This function gets the UPMCRS field of HCS register
+ *
+ * Return: value of UPMCRS field.
+ */
+static inline u8 ufshcd_get_upmcrs(struct ufs_hba *hba)
+{
+ return (ufshcd_readl(hba, REG_CONTROLLER_STATUS) >> 8) & 0x7;
+}
+
+/**
+ * ufshcd_dispatch_uic_cmd - Dispatch an UIC command to the Unipro layer
+ * @hba: per adapter instance
+ * @uic_cmd: UIC command
+ */
+static inline void
+ufshcd_dispatch_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
+{
+ lockdep_assert_held(&hba->uic_cmd_mutex);
+
+ WARN_ON(hba->active_uic_cmd);
+ WARN_ON_ONCE(uic_cmd->argument2 & MASK_UIC_COMMAND_RESULT);
+
+ hba->active_uic_cmd = uic_cmd;
+
+ /* Write Args */
+ ufshcd_writel(hba, uic_cmd->argument1, REG_UIC_COMMAND_ARG_1);
+ ufshcd_writel(hba, uic_cmd->argument2, REG_UIC_COMMAND_ARG_2);
+ ufshcd_writel(hba, uic_cmd->argument3, REG_UIC_COMMAND_ARG_3);
+
+ ufshcd_add_uic_command_trace(hba, uic_cmd, UFS_CMD_SEND);
+
+ /* Write UIC Cmd */
+ ufshcd_writel(hba, uic_cmd->command & COMMAND_OPCODE_MASK,
+ REG_UIC_COMMAND);
+}
+
+/**
+ * ufshcd_wait_for_uic_cmd - Wait for completion of an UIC command
+ * @hba: per adapter instance
+ * @uic_cmd: UIC command
+ *
+ * Return: 0 only if success.
+ */
+static int
+ufshcd_wait_for_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
+{
+ int ret;
+ unsigned long flags;
+
+ lockdep_assert_held(&hba->uic_cmd_mutex);
+
+ if (wait_for_completion_timeout(&uic_cmd->done,
+ msecs_to_jiffies(uic_cmd_timeout))) {
+ ret = uic_cmd->argument2 & MASK_UIC_COMMAND_RESULT;
+ } else {
+ ret = -ETIMEDOUT;
+ dev_err(hba->dev,
+ "uic cmd 0x%x with arg3 0x%x completion timeout\n",
+ uic_cmd->command, uic_cmd->argument3);
+
+ if (!uic_cmd->cmd_active) {
+ dev_err(hba->dev, "%s: UIC cmd has been completed, return the result\n",
+ __func__);
+ ret = uic_cmd->argument2 & MASK_UIC_COMMAND_RESULT;
+ }
+ }
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ hba->active_uic_cmd = NULL;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ return ret;
+}
+
+/**
+ * __ufshcd_send_uic_cmd - Send UIC commands and retrieve the result
+ * @hba: per adapter instance
+ * @uic_cmd: UIC command
+ *
+ * Return: 0 if successful; < 0 upon failure.
+ */
+static int
+__ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
+{
+ lockdep_assert_held(&hba->uic_cmd_mutex);
+
+ if (!ufshcd_ready_for_uic_cmd(hba)) {
+ dev_err(hba->dev,
+ "Controller not ready to accept UIC commands\n");
+ return -EIO;
+ }
+
+ init_completion(&uic_cmd->done);
+
+ uic_cmd->cmd_active = true;
+ ufshcd_dispatch_uic_cmd(hba, uic_cmd);
+
+ return 0;
+}
+
+/**
+ * ufshcd_send_uic_cmd - Send UIC commands and retrieve the result
+ * @hba: per adapter instance
+ * @uic_cmd: UIC command
+ *
+ * Return: 0 only if success.
+ */
+int ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
+{
+ unsigned long flags;
+ int ret;
+
+ if (hba->quirks & UFSHCD_QUIRK_BROKEN_UIC_CMD)
+ return 0;
+
+ ufshcd_hold(hba);
+ mutex_lock(&hba->uic_cmd_mutex);
+ ufshcd_add_delay_before_dme_cmd(hba);
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ ufshcd_enable_intr(hba, UIC_COMMAND_COMPL);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ ret = __ufshcd_send_uic_cmd(hba, uic_cmd);
+ if (!ret)
+ ret = ufshcd_wait_for_uic_cmd(hba, uic_cmd);
+
+ mutex_unlock(&hba->uic_cmd_mutex);
+
+ ufshcd_release(hba);
+ return ret;
+}
+
+/**
+ * ufshcd_sgl_to_prdt - SG list to PRTD (Physical Region Description Table, 4DW format)
+ * @hba: per-adapter instance
+ * @lrbp: pointer to local reference block
+ * @sg_entries: The number of sg lists actually used
+ * @sg_list: Pointer to SG list
+ */
+static void ufshcd_sgl_to_prdt(struct ufs_hba *hba, struct ufshcd_lrb *lrbp, int sg_entries,
+ struct scatterlist *sg_list)
+{
+ struct ufshcd_sg_entry *prd;
+ struct scatterlist *sg;
+ int i;
+
+ if (sg_entries) {
+
+ if (hba->quirks & UFSHCD_QUIRK_PRDT_BYTE_GRAN)
+ lrbp->utr_descriptor_ptr->prd_table_length =
+ cpu_to_le16(sg_entries * ufshcd_sg_entry_size(hba));
+ else
+ lrbp->utr_descriptor_ptr->prd_table_length = cpu_to_le16(sg_entries);
+
+ prd = lrbp->ucd_prdt_ptr;
+
+ for_each_sg(sg_list, sg, sg_entries, i) {
+ const unsigned int len = sg_dma_len(sg);
+
+ /*
+ * From the UFSHCI spec: "Data Byte Count (DBC): A '0'
+ * based value that indicates the length, in bytes, of
+ * the data block. A maximum of length of 256KB may
+ * exist for any entry. Bits 1:0 of this field shall be
+ * 11b to indicate Dword granularity. A value of '3'
+ * indicates 4 bytes, '7' indicates 8 bytes, etc."
+ */
+ WARN_ONCE(len > SZ_256K, "len = %#x\n", len);
+ prd->size = cpu_to_le32(len - 1);
+ prd->addr = cpu_to_le64(sg->dma_address);
+ prd->reserved = 0;
+ prd = (void *)prd + ufshcd_sg_entry_size(hba);
+ }
+ } else {
+ lrbp->utr_descriptor_ptr->prd_table_length = 0;
+ }
+}
+
+/**
+ * ufshcd_map_sg - Map scatter-gather list to prdt
+ * @hba: per adapter instance
+ * @cmd: SCSI command
+ *
+ * Return: 0 in case of success, non-zero value in case of failure.
+ */
+static int ufshcd_map_sg(struct ufs_hba *hba, struct scsi_cmnd *cmd)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ int sg_segments = scsi_dma_map(cmd);
+
+ if (sg_segments < 0)
+ return sg_segments;
+
+ ufshcd_sgl_to_prdt(hba, lrbp, sg_segments, scsi_sglist(cmd));
+
+ return ufshcd_crypto_fill_prdt(hba, cmd);
+}
+
+/**
+ * ufshcd_prepare_req_desc_hdr - Fill UTP Transfer request descriptor header according to request
+ * descriptor according to request
+ * @hba: per adapter instance
+ * @lrbp: pointer to local reference block
+ * @upiu_flags: flags required in the header
+ * @cmd_dir: requests data direction
+ * @ehs_length: Total EHS Length (in 32‐bytes units of all Extra Header Segments)
+ */
+static void
+ufshcd_prepare_req_desc_hdr(struct ufs_hba *hba, struct ufshcd_lrb *lrbp,
+ u8 *upiu_flags, enum dma_data_direction cmd_dir,
+ int ehs_length)
+{
+ struct utp_transfer_req_desc *req_desc = lrbp->utr_descriptor_ptr;
+ struct request_desc_header *h = &req_desc->header;
+ enum utp_data_direction data_direction;
+
+ lrbp->command_type = UTP_CMD_TYPE_UFS_STORAGE;
+
+ *h = (typeof(*h)){ };
+
+ if (cmd_dir == DMA_FROM_DEVICE) {
+ data_direction = UTP_DEVICE_TO_HOST;
+ *upiu_flags = UPIU_CMD_FLAGS_READ;
+ } else if (cmd_dir == DMA_TO_DEVICE) {
+ data_direction = UTP_HOST_TO_DEVICE;
+ *upiu_flags = UPIU_CMD_FLAGS_WRITE;
+ } else {
+ data_direction = UTP_NO_DATA_TRANSFER;
+ *upiu_flags = UPIU_CMD_FLAGS_NONE;
+ }
+
+ h->command_type = lrbp->command_type;
+ h->data_direction = data_direction;
+ h->ehs_length = ehs_length;
+
+ if (lrbp->intr_cmd)
+ h->interrupt = 1;
+
+ /* Prepare crypto related dwords */
+ ufshcd_prepare_req_desc_hdr_crypto(lrbp, h);
+
+ /*
+ * assigning invalid value for command status. Controller
+ * updates OCS on command completion, with the command
+ * status
+ */
+ h->ocs = OCS_INVALID_COMMAND_STATUS;
+
+ req_desc->prd_table_length = 0;
+}
+
+/**
+ * ufshcd_prepare_utp_scsi_cmd_upiu() - fills the utp_transfer_req_desc,
+ * for scsi commands
+ * @cmd: SCSI command
+ * @upiu_flags: flags
+ */
+static void ufshcd_prepare_utp_scsi_cmd_upiu(struct scsi_cmnd *cmd,
+ u8 upiu_flags)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ const int tag = scsi_cmd_to_rq(cmd)->tag;
+ struct utp_upiu_req *ucd_req_ptr = lrbp->ucd_req_ptr;
+ unsigned short cdb_len;
+
+ ucd_req_ptr->header = (struct utp_upiu_header){
+ .transaction_code = UPIU_TRANSACTION_COMMAND,
+ .flags = upiu_flags,
+ .lun = lrbp->lun,
+ .task_tag = tag,
+ .command_set_type = UPIU_COMMAND_SET_TYPE_SCSI,
+ };
+
+ WARN_ON_ONCE(ucd_req_ptr->header.task_tag != 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);
+ memcpy(ucd_req_ptr->sc.cdb, cmd->cmnd, cdb_len);
+
+ memset(lrbp->ucd_rsp_ptr, 0, sizeof(struct utp_upiu_rsp));
+}
+
+/**
+ * ufshcd_prepare_utp_query_req_upiu() - fill the utp_transfer_req_desc for query request
+ * @hba: UFS hba
+ * @cmd: SCSI command pointer
+ * @upiu_flags: flags
+ */
+static void ufshcd_prepare_utp_query_req_upiu(struct ufs_hba *hba,
+ struct scsi_cmnd *cmd, u8 upiu_flags)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ struct utp_upiu_req *ucd_req_ptr = lrbp->ucd_req_ptr;
+ const int tag = scsi_cmd_to_rq(cmd)->tag;
+ struct ufs_query *query = &hba->dev_cmd.query;
+ u16 len = be16_to_cpu(query->request.upiu_req.length);
+
+ /* Query request header */
+ ucd_req_ptr->header = (struct utp_upiu_header){
+ .transaction_code = UPIU_TRANSACTION_QUERY_REQ,
+ .flags = upiu_flags,
+ .lun = lrbp->lun,
+ .task_tag = tag,
+ .query_function = query->request.query_func,
+ /* Data segment length only need for WRITE_DESC */
+ .data_segment_length =
+ query->request.upiu_req.opcode ==
+ UPIU_QUERY_OPCODE_WRITE_DESC ?
+ cpu_to_be16(len) :
+ 0,
+ };
+
+ /* Copy the Query Request buffer as is */
+ memcpy(&ucd_req_ptr->qr, &query->request.upiu_req,
+ QUERY_OSF_SIZE);
+
+ /* Copy the Descriptor */
+ if (query->request.upiu_req.opcode == UPIU_QUERY_OPCODE_WRITE_DESC)
+ memcpy(ucd_req_ptr + 1, query->descriptor, len);
+}
+
+static inline void ufshcd_prepare_utp_nop_upiu(struct scsi_cmnd *cmd)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ struct utp_upiu_req *ucd_req_ptr = lrbp->ucd_req_ptr;
+ const int tag = scsi_cmd_to_rq(cmd)->tag;
+
+ memset(ucd_req_ptr, 0, sizeof(struct utp_upiu_req));
+
+ ucd_req_ptr->header = (struct utp_upiu_header){
+ .transaction_code = UPIU_TRANSACTION_NOP_OUT,
+ .task_tag = tag,
+ };
+}
+
+/**
+ * ufshcd_compose_devman_upiu - UFS Protocol Information Unit(UPIU)
+ * for Device Management Purposes
+ * @hba: per adapter instance
+ * @cmd: SCSI command pointer
+ *
+ * Return: 0 upon success; < 0 upon failure.
+ */
+static int ufshcd_compose_devman_upiu(struct ufs_hba *hba,
+ struct scsi_cmnd *cmd)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ u8 upiu_flags;
+ int ret = 0;
+
+ ufshcd_prepare_req_desc_hdr(hba, lrbp, &upiu_flags, DMA_NONE, 0);
+
+ if (hba->dev_cmd.type == DEV_CMD_TYPE_QUERY)
+ ufshcd_prepare_utp_query_req_upiu(hba, cmd, upiu_flags);
+ else if (hba->dev_cmd.type == DEV_CMD_TYPE_NOP)
+ ufshcd_prepare_utp_nop_upiu(cmd);
+ else
+ ret = -EINVAL;
+
+ memset(lrbp->ucd_rsp_ptr, 0, sizeof(struct utp_upiu_rsp));
+
+ return ret;
+}
+
+/**
+ * ufshcd_comp_scsi_upiu - UFS Protocol Information Unit(UPIU)
+ * for SCSI Purposes
+ * @hba: per adapter instance
+ * @cmd: SCSI command
+ */
+static void ufshcd_comp_scsi_upiu(struct ufs_hba *hba, struct scsi_cmnd *cmd)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ struct request *rq = scsi_cmd_to_rq(cmd);
+ unsigned int ioprio_class = IOPRIO_PRIO_CLASS(req_get_ioprio(rq));
+ u8 upiu_flags;
+
+ ufshcd_prepare_req_desc_hdr(hba, lrbp, &upiu_flags,
+ cmd->sc_data_direction, 0);
+ if (ioprio_class == IOPRIO_CLASS_RT)
+ upiu_flags |= UPIU_CMD_FLAGS_CP;
+ ufshcd_prepare_utp_scsi_cmd_upiu(cmd, upiu_flags);
+}
+
+static void ufshcd_init_lrb(struct ufs_hba *hba, struct scsi_cmnd *cmd)
+{
+ const int i = scsi_cmd_to_rq(cmd)->tag;
+ struct utp_transfer_req_desc *utrdlp = hba->utrdl_base_addr;
+ u16 response_offset = le16_to_cpu(utrdlp[i].response_upiu_offset);
+ u16 prdt_offset = le16_to_cpu(utrdlp[i].prd_table_offset);
+ struct ufshcd_lrb *lrb = scsi_cmd_priv(cmd);
+ u8 *command_upiu, *response_upiu, *prd_table;
+ dma_addr_t cmd_desc_element_addr;
+
+ /* The reserved tag uses a dedicated UCD outside the pool. */
+ if (unlikely(blk_mq_is_reserved_rq(scsi_cmd_to_rq(cmd)))) {
+ struct utp_devman_cmd_desc *cmd_descp = hba->devman_ucd_base_addr;
+
+ cmd_desc_element_addr = hba->devman_ucd_dma_addr;
+ command_upiu = cmd_descp->command_upiu;
+ response_upiu = cmd_descp->response_upiu;
+ prd_table = cmd_descp->prd_table;
+ } else {
+ int slot = i - UFSHCD_NUM_RESERVED;
+ struct utp_transfer_cmd_desc *cmd_descp;
+
+ /* Non-reserved tags start at UFSHCD_NUM_RESERVED, so slot >= 0. */
+ WARN_ON_ONCE(slot < 0);
+ cmd_descp = (void *)hba->ucdl_base_addr + slot * ufshcd_get_ucd_size(hba);
+
+ cmd_desc_element_addr =
+ hba->ucdl_dma_addr + slot * ufshcd_get_ucd_size(hba);
+ command_upiu = cmd_descp->command_upiu;
+ response_upiu = cmd_descp->response_upiu;
+ prd_table = cmd_descp->prd_table;
+ }
+
+ lrb->utr_descriptor_ptr = utrdlp + i;
+ lrb->utrd_dma_addr =
+ hba->utrdl_dma_addr + i * sizeof(struct utp_transfer_req_desc);
+ lrb->ucd_req_ptr = (struct utp_upiu_req *)command_upiu;
+ lrb->ucd_req_dma_addr = cmd_desc_element_addr;
+ lrb->ucd_rsp_ptr = (struct utp_upiu_rsp *)response_upiu;
+ lrb->ucd_rsp_dma_addr = cmd_desc_element_addr + response_offset;
+ lrb->ucd_prdt_ptr = (struct ufshcd_sg_entry *)prd_table;
+ lrb->ucd_prdt_dma_addr = cmd_desc_element_addr + prdt_offset;
+}
+
+static void __ufshcd_setup_cmd(struct ufs_hba *hba, struct scsi_cmnd *cmd,
+ u8 lun, int tag)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+
+ ufshcd_init_lrb(hba, cmd);
+
+ memset(lrbp->ucd_req_ptr, 0, sizeof(*lrbp->ucd_req_ptr));
+
+ lrbp->lun = lun;
+ ufshcd_prepare_lrbp_crypto(ufshcd_is_scsi_cmd(cmd) ?
+ scsi_cmd_to_rq(cmd) : NULL, lrbp);
+}
+
+static void ufshcd_setup_scsi_cmd(struct ufs_hba *hba, struct scsi_cmnd *cmd,
+ u8 lun, int tag)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+
+ __ufshcd_setup_cmd(hba, cmd, lun, tag);
+ lrbp->intr_cmd = !ufshcd_is_intr_aggr_allowed(hba);
+ lrbp->req_abort_skip = false;
+
+ ufshcd_comp_scsi_upiu(hba, cmd);
+}
+
+/**
+ * ufshcd_upiu_wlun_to_scsi_wlun - maps UPIU W-LUN id to SCSI W-LUN ID
+ * @upiu_wlun_id: UPIU W-LUN id
+ *
+ * Return: SCSI W-LUN id.
+ */
+static inline u16 ufshcd_upiu_wlun_to_scsi_wlun(u8 upiu_wlun_id)
+{
+ return (upiu_wlun_id & ~UFS_UPIU_WLUN_ID) | SCSI_W_LUN_BASE;
+}
+
+static inline bool is_device_wlun(struct scsi_device *sdev)
+{
+ return sdev->lun ==
+ ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_UFS_DEVICE_WLUN);
+}
+
+/*
+ * Associate the UFS controller queue with the default and poll HCTX types.
+ * Initialize the mq_map[] arrays.
+ */
+static void ufshcd_map_queues(struct Scsi_Host *shost)
+{
+ struct ufs_hba *hba = shost_priv(shost);
+ int i, queue_offset = 0;
+
+ if (!is_mcq_supported(hba)) {
+ hba->nr_queues[HCTX_TYPE_DEFAULT] = 1;
+ hba->nr_queues[HCTX_TYPE_READ] = 0;
+ hba->nr_queues[HCTX_TYPE_POLL] = 1;
+ hba->nr_hw_queues = 1;
+ }
+
+ for (i = 0; i < shost->nr_maps; i++) {
+ struct blk_mq_queue_map *map = &shost->tag_set.map[i];
+
+ map->nr_queues = hba->nr_queues[i];
+ if (!map->nr_queues)
+ continue;
+ map->queue_offset = queue_offset;
+ if (i == HCTX_TYPE_POLL && !is_mcq_supported(hba))
+ map->queue_offset = 0;
+
+ blk_mq_map_queues(map);
+ queue_offset += map->nr_queues;
+ }
+}
+
+/*
+ * The only purpose of this function is to make the SCSI core skip the memset()
+ * call for the private command data.
+ */
+static int ufshcd_init_cmd_priv(struct Scsi_Host *host, struct scsi_cmnd *cmd)
+{
+ return 0;
+}
+
+/**
+ * ufshcd_queuecommand - main entry point for SCSI requests
+ * @host: SCSI host pointer
+ * @cmd: command from SCSI Midlayer
+ *
+ * Return: 0 for success, non-zero in case of failure.
+ */
+static enum scsi_qc_status ufshcd_queuecommand(struct Scsi_Host *host,
+ struct scsi_cmnd *cmd)
+{
+ struct ufs_hba *hba = shost_priv(host);
+ int tag = scsi_cmd_to_rq(cmd)->tag;
+ int err = 0;
+ struct ufs_hw_queue *hwq = NULL;
+
+ switch (hba->ufshcd_state) {
+ case UFSHCD_STATE_OPERATIONAL:
+ break;
+ case UFSHCD_STATE_EH_SCHEDULED_NON_FATAL:
+ /*
+ * SCSI error handler can call ->queuecommand() while UFS error
+ * handler is in progress. Error interrupts could change the
+ * state from UFSHCD_STATE_RESET to
+ * UFSHCD_STATE_EH_SCHEDULED_NON_FATAL. Prevent requests
+ * being issued in that case.
+ */
+ if (ufshcd_eh_in_progress(hba)) {
+ err = SCSI_MLQUEUE_HOST_BUSY;
+ goto out;
+ }
+ break;
+ case UFSHCD_STATE_EH_SCHEDULED_FATAL:
+ /*
+ * pm_runtime_get_sync() is used at error handling preparation
+ * stage. If a scsi cmd, e.g. the SSU cmd, is sent from hba's
+ * PM ops, it can never be finished if we let SCSI layer keep
+ * retrying it, which gets err handler stuck forever. Neither
+ * can we let the scsi cmd pass through, because UFS is in bad
+ * state, the scsi cmd may eventually time out, which will get
+ * err handler blocked for too long. So, just fail the scsi cmd
+ * sent from PM ops, err handler can recover PM error anyways.
+ */
+ if (hba->pm_op_in_progress) {
+ hba->force_reset = true;
+ set_host_byte(cmd, DID_BAD_TARGET);
+ scsi_done(cmd);
+ goto out;
+ }
+ fallthrough;
+ case UFSHCD_STATE_RESET:
+ err = SCSI_MLQUEUE_HOST_BUSY;
+ goto out;
+ case UFSHCD_STATE_ERROR:
+ set_host_byte(cmd, DID_ERROR);
+ scsi_done(cmd);
+ goto out;
+ }
+
+ hba->req_abort_count = 0;
+
+ ufshcd_hold(hba);
+
+ ufshcd_setup_scsi_cmd(hba, cmd,
+ ufshcd_scsi_to_upiu_lun(cmd->device->lun), tag);
+
+ err = ufshcd_map_sg(hba, cmd);
+ if (err) {
+ ufshcd_release(hba);
+ goto out;
+ }
+
+ if (hba->mcq_enabled)
+ hwq = ufshcd_mcq_req_to_hwq(hba, scsi_cmd_to_rq(cmd));
+
+ ufshcd_send_command(hba, cmd, hwq);
+
+out:
+ if (ufs_trigger_eh(hba)) {
+ unsigned long flags;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ ufshcd_schedule_eh_work(hba);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ }
+
+ return err;
+}
+
+static enum scsi_qc_status ufshcd_queue_reserved_command(struct Scsi_Host *host,
+ struct scsi_cmnd *cmd)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ struct request *rq = scsi_cmd_to_rq(cmd);
+ struct ufs_hba *hba = shost_priv(host);
+ struct ufs_hw_queue *hwq =
+ hba->mcq_enabled ? ufshcd_mcq_req_to_hwq(hba, rq) : NULL;
+
+ ufshcd_add_query_upiu_trace(hba, UFS_QUERY_SEND, lrbp->ucd_req_ptr);
+ ufshcd_send_command(hba, cmd, hwq);
+ return 0;
+}
+
+static void ufshcd_setup_dev_cmd(struct ufs_hba *hba, struct scsi_cmnd *cmd,
+ enum dev_cmd_type cmd_type, u8 lun, int tag)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+
+ __ufshcd_setup_cmd(hba, cmd, lun, tag);
+ lrbp->intr_cmd = true; /* No interrupt aggregation */
+ hba->dev_cmd.type = cmd_type;
+ hba->dev_cmd.tag = tag;
+}
+
+/*
+ * Return: 0 upon success; < 0 upon failure.
+ */
+static int ufshcd_compose_dev_cmd(struct ufs_hba *hba, struct scsi_cmnd *cmd,
+ enum dev_cmd_type cmd_type, int tag)
+{
+ ufshcd_setup_dev_cmd(hba, cmd, cmd_type, 0, tag);
+
+ return ufshcd_compose_devman_upiu(hba, cmd);
+}
+
+/*
+ * Check with the block layer if the command is inflight
+ * @cmd: command to check.
+ *
+ * Return: true if command is inflight; false if not.
+ */
+bool ufshcd_cmd_inflight(struct scsi_cmnd *cmd)
+{
+ return cmd && blk_mq_rq_state(scsi_cmd_to_rq(cmd)) == MQ_RQ_IN_FLIGHT;
+}
+
+/*
+ * Clear the pending command in the controller and wait until
+ * the controller confirms that the command has been cleared.
+ * @hba: per adapter instance
+ * @task_tag: The tag number of the command to be cleared.
+ */
+static int ufshcd_clear_cmd(struct ufs_hba *hba, u32 task_tag)
+{
+ u32 mask;
+ int err;
+
+ if (hba->mcq_enabled) {
+ /*
+ * MCQ mode. Clean up the MCQ resources similar to
+ * what the ufshcd_utrl_clear() does for SDB mode.
+ */
+ err = ufshcd_mcq_sq_cleanup(hba, task_tag);
+ if (err) {
+ dev_err(hba->dev, "%s: failed tag=%d. err=%d\n",
+ __func__, task_tag, err);
+ return err;
+ }
+ return 0;
+ }
+
+ mask = 1U << task_tag;
+
+ /* clear outstanding transaction before retry */
+ ufshcd_utrl_clear(hba, mask);
+
+ /*
+ * wait for h/w to clear corresponding bit in door-bell.
+ * max. wait is 1 sec.
+ */
+ return ufshcd_wait_for_register(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL,
+ mask, ~mask, 1000, 1000);
+}
+
+/**
+ * ufshcd_dev_cmd_completion() - handles device management command responses
+ * @hba: per adapter instance
+ * @lrbp: pointer to local reference block
+ *
+ * Return: 0 upon success; < 0 upon failure.
+ */
+static int
+ufshcd_dev_cmd_completion(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
+{
+ enum upiu_response_transaction resp;
+ int err = 0;
+
+ hba->ufs_stats.last_hibern8_exit_tstamp = ktime_set(0, 0);
+ resp = ufshcd_get_req_rsp(lrbp->ucd_rsp_ptr);
+
+ switch (resp) {
+ case UPIU_TRANSACTION_NOP_IN:
+ if (hba->dev_cmd.type != DEV_CMD_TYPE_NOP) {
+ err = -EINVAL;
+ dev_err(hba->dev, "%s: unexpected response %x\n",
+ __func__, resp);
+ }
+ break;
+ case UPIU_TRANSACTION_QUERY_RSP: {
+ u8 response = lrbp->ucd_rsp_ptr->header.response;
+
+ if (response == 0) {
+ err = ufshcd_copy_query_response(hba, lrbp);
+ } else {
+ err = -EINVAL;
+ dev_err(hba->dev, "%s: unexpected response in Query RSP: %x\n",
+ __func__, response);
+ }
+ break;
+ }
+ case UPIU_TRANSACTION_REJECT_UPIU:
+ /* TODO: handle Reject UPIU Response */
+ err = -EPERM;
+ dev_err(hba->dev, "%s: Reject UPIU not fully implemented\n",
+ __func__);
+ break;
+ case UPIU_TRANSACTION_RESPONSE:
+ if (hba->dev_cmd.type != DEV_CMD_TYPE_RPMB) {
+ err = -EINVAL;
+ dev_err(hba->dev, "%s: unexpected response %x\n", __func__, resp);
+ }
+ break;
+ default:
+ err = -EINVAL;
+ dev_err(hba->dev, "%s: Invalid device management cmd response: %x\n",
+ __func__, resp);
+ break;
+ }
+
+ WARN_ONCE(err > 0, "Incorrect return value %d > 0\n", err);
+ return err;
+}
+
+static void ufshcd_dev_man_lock(struct ufs_hba *hba)
+ __acquires(&hba->dev_cmd.lock)
+ __acquires_shared(&hba->clk_scaling_lock)
+{
+ ufshcd_hold(hba);
+ mutex_lock(&hba->dev_cmd.lock);
+ down_read(&hba->clk_scaling_lock);
+}
+
+static void ufshcd_dev_man_unlock(struct ufs_hba *hba)
+ __releases_shared(&hba->clk_scaling_lock)
+ __releases(&hba->dev_cmd.lock)
+{
+ up_read(&hba->clk_scaling_lock);
+ mutex_unlock(&hba->dev_cmd.lock);
+ ufshcd_release(hba);
+}
+
+static struct scsi_cmnd *ufshcd_get_dev_mgmt_cmd(struct ufs_hba *hba)
+{
+ /*
+ * The caller must hold this lock to guarantee that the NOWAIT
+ * allocation will succeed.
+ */
+ lockdep_assert_held(&hba->dev_cmd.lock);
+
+ return scsi_get_internal_cmd(
+ hba->host->pseudo_sdev, DMA_TO_DEVICE,
+ BLK_MQ_REQ_RESERVED | BLK_MQ_REQ_NOWAIT);
+}
+
+static void ufshcd_put_dev_mgmt_cmd(struct scsi_cmnd *cmd)
+{
+ scsi_put_internal_cmd(cmd);
+}
+
+/*
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+static int ufshcd_issue_dev_cmd(struct ufs_hba *hba, struct scsi_cmnd *cmd,
+ const u32 tag, int timeout)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ struct request *rq = scsi_cmd_to_rq(cmd);
+ blk_status_t sts;
+
+ rq->timeout = timeout;
+ sts = blk_execute_rq(rq, true);
+ if (sts != BLK_STS_OK)
+ return blk_status_to_errno(sts);
+ return lrbp->utr_descriptor_ptr->header.ocs;
+}
+
+/**
+ * ufshcd_exec_dev_cmd - API for sending device management requests
+ * @hba: UFS hba
+ * @cmd_type: specifies the type (NOP, Query...)
+ * @timeout: timeout in milliseconds
+ *
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ *
+ * NOTE: Since there is only one available tag for device management commands,
+ * it is expected you hold the hba->dev_cmd.lock mutex.
+ */
+static int ufshcd_exec_dev_cmd(struct ufs_hba *hba,
+ enum dev_cmd_type cmd_type, int timeout)
+{
+ struct scsi_cmnd *cmd = ufshcd_get_dev_mgmt_cmd(hba);
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ u32 tag;
+ int err;
+
+ /* Protects use of hba->dev_cmd. */
+ lockdep_assert_held(&hba->dev_cmd.lock);
+
+ if (WARN_ON_ONCE(!cmd))
+ return -ENOMEM;
+
+ tag = scsi_cmd_to_rq(cmd)->tag;
+
+ err = ufshcd_compose_dev_cmd(hba, cmd, cmd_type, tag);
+ if (unlikely(err))
+ goto out;
+
+ err = ufshcd_issue_dev_cmd(hba, cmd, tag, timeout);
+ if (err == 0)
+ err = ufshcd_dev_cmd_completion(hba, lrbp);
+
+out:
+ ufshcd_put_dev_mgmt_cmd(cmd);
+
+ return err;
+}
+
+/**
+ * ufshcd_init_query() - init the query response and request parameters
+ * @hba: per-adapter instance
+ * @request: address of the request pointer to be initialized
+ * @response: address of the response pointer to be initialized
+ * @opcode: operation to perform
+ * @idn: flag idn to access
+ * @index: LU number to access
+ * @selector: query/flag/descriptor further identification
+ */
+static inline void ufshcd_init_query(struct ufs_hba *hba,
+ struct ufs_query_req **request, struct ufs_query_res **response,
+ enum query_opcode opcode, u8 idn, u8 index, u8 selector)
+{
+ *request = &hba->dev_cmd.query.request;
+ *response = &hba->dev_cmd.query.response;
+ memset(*request, 0, sizeof(struct ufs_query_req));
+ memset(*response, 0, sizeof(struct ufs_query_res));
+ (*request)->upiu_req.opcode = opcode;
+ (*request)->upiu_req.idn = idn;
+ (*request)->upiu_req.index = index;
+ (*request)->upiu_req.selector = selector;
+}
+
+/*
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+static int ufshcd_query_flag_retry(struct ufs_hba *hba,
+ enum query_opcode opcode, enum flag_idn idn, u8 index, bool *flag_res)
+{
+ int ret;
+ int retries;
+
+ for (retries = 0; retries < QUERY_REQ_RETRIES; retries++) {
+ ret = ufshcd_query_flag(hba, opcode, idn, index, flag_res);
+ if (ret)
+ dev_dbg(hba->dev,
+ "%s: failed with error %d, retries %d\n",
+ __func__, ret, retries);
+ else
+ break;
+ }
+
+ if (ret)
+ dev_err(hba->dev,
+ "%s: query flag, opcode %d, idn %d, failed with error %d after %d retries\n",
+ __func__, opcode, idn, ret, retries);
+ return ret;
+}
+
+/**
+ * ufshcd_query_flag() - API function for sending flag query requests
+ * @hba: per-adapter instance
+ * @opcode: flag query to perform
+ * @idn: flag idn to access
+ * @index: flag index to access
+ * @flag_res: the flag value after the query request completes
+ *
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+int ufshcd_query_flag(struct ufs_hba *hba, enum query_opcode opcode,
+ enum flag_idn idn, u8 index, bool *flag_res)
+{
+ struct ufs_query_req *request = NULL;
+ struct ufs_query_res *response = NULL;
+ int err, selector = 0;
+ int timeout = dev_cmd_timeout;
+
+ BUG_ON(!hba);
+
+ ufshcd_dev_man_lock(hba);
+
+ ufshcd_init_query(hba, &request, &response, opcode, idn, index,
+ selector);
+
+ switch (opcode) {
+ case UPIU_QUERY_OPCODE_SET_FLAG:
+ case UPIU_QUERY_OPCODE_CLEAR_FLAG:
+ case UPIU_QUERY_OPCODE_TOGGLE_FLAG:
+ request->query_func = UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST;
+ break;
+ case UPIU_QUERY_OPCODE_READ_FLAG:
+ request->query_func = UPIU_QUERY_FUNC_STANDARD_READ_REQUEST;
+ if (!flag_res) {
+ /* No dummy reads */
+ dev_err(hba->dev, "%s: Invalid argument for read request\n",
+ __func__);
+ err = -EINVAL;
+ goto out_unlock;
+ }
+ break;
+ default:
+ dev_err(hba->dev,
+ "%s: Expected query flag opcode but got = %d\n",
+ __func__, opcode);
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
+ err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY, timeout);
+
+ if (err) {
+ dev_err(hba->dev,
+ "%s: Sending flag query for idn %d failed, err = %d\n",
+ __func__, idn, err);
+ goto out_unlock;
+ }
+
+ if (flag_res)
+ *flag_res = (be32_to_cpu(response->upiu_res.value) &
+ MASK_QUERY_UPIU_FLAG_LOC) & 0x1;
+
+out_unlock:
+ ufshcd_dev_man_unlock(hba);
+ return err;
+}
+
+/**
+ * ufshcd_query_attr - API function for sending attribute requests
+ * @hba: per-adapter instance
+ * @opcode: attribute opcode
+ * @idn: attribute idn to access
+ * @index: index field
+ * @selector: selector field
+ * @attr_val: the attribute value after the query request completes
+ *
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+int ufshcd_query_attr(struct ufs_hba *hba, enum query_opcode opcode,
+ enum attr_idn idn, u8 index, u8 selector, u32 *attr_val)
+{
+ struct ufs_query_req *request = NULL;
+ struct ufs_query_res *response = NULL;
+ int err;
+
+ BUG_ON(!hba);
+
+ if (!attr_val) {
+ dev_err(hba->dev, "%s: attribute value required for opcode 0x%x\n",
+ __func__, opcode);
+ return -EINVAL;
+ }
+
+ ufshcd_dev_man_lock(hba);
+
+ ufshcd_init_query(hba, &request, &response, opcode, idn, index,
+ selector);
+
+ switch (opcode) {
+ case UPIU_QUERY_OPCODE_WRITE_ATTR:
+ request->query_func = UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST;
+ request->upiu_req.value = cpu_to_be32(*attr_val);
+ break;
+ case UPIU_QUERY_OPCODE_READ_ATTR:
+ request->query_func = UPIU_QUERY_FUNC_STANDARD_READ_REQUEST;
+ break;
+ default:
+ dev_err(hba->dev, "%s: Expected query attr opcode but got = 0x%.2x\n",
+ __func__, opcode);
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
+ err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY, dev_cmd_timeout);
+
+ if (err) {
+ dev_err(hba->dev, "%s: opcode 0x%.2x for idn %d failed, index %d, err = %d\n",
+ __func__, opcode, idn, index, err);
+ goto out_unlock;
+ }
+
+ *attr_val = be32_to_cpu(response->upiu_res.value);
+
+out_unlock:
+ ufshcd_dev_man_unlock(hba);
+ return err;
+}
+
+/**
+ * ufshcd_query_attr_retry() - API function for sending query
+ * attribute with retries
+ * @hba: per-adapter instance
+ * @opcode: attribute opcode
+ * @idn: attribute idn to access
+ * @index: index field
+ * @selector: selector field
+ * @attr_val: the attribute value after the query request
+ * completes
+ *
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+int ufshcd_query_attr_retry(struct ufs_hba *hba,
+ enum query_opcode opcode, enum attr_idn idn, u8 index, u8 selector,
+ u32 *attr_val)
+{
+ int ret = 0;
+ u32 retries;
+
+ for (retries = QUERY_REQ_RETRIES; retries > 0; retries--) {
+ ret = ufshcd_query_attr(hba, opcode, idn, index,
+ selector, attr_val);
+ if (ret)
+ dev_dbg(hba->dev, "%s: failed with error %d, retries %d\n",
+ __func__, ret, retries);
+ else
+ break;
+ }
+
+ if (ret)
+ dev_err(hba->dev,
+ "%s: query attribute, idn %d, failed with error %d after %d retries\n",
+ __func__, idn, ret, QUERY_REQ_RETRIES);
+ return ret;
+}
+
+/**
+ * ufshcd_query_attr_qword - Function of sending query requests for quad-word attributes
+ * @hba: per-adapter instance
+ * @opcode: attribute opcode
+ * @idn: attribute idn to access
+ * @index: index field
+ * @sel: selector field
+ * @attr_val: the attribute value after the query request completes
+ *
+ * Return: 0 for success, non-zero in case of failure.
+ */
+int ufshcd_query_attr_qword(struct ufs_hba *hba, enum query_opcode opcode,
+ enum attr_idn idn, u8 index, u8 sel, u64 *attr_val)
+{
+ struct utp_upiu_query_v4_0 *upiu_req;
+ struct utp_upiu_query_v4_0 *upiu_resp;
+ struct ufs_query_req *request = NULL;
+ struct ufs_query_res *response = NULL;
+ int err;
+
+ if (!attr_val) {
+ dev_err(hba->dev, "%s: attribute value required for opcode 0x%x\n",
+ __func__, opcode);
+ return -EINVAL;
+ }
+
+ ufshcd_dev_man_lock(hba);
+
+ ufshcd_init_query(hba, &request, &response, opcode, idn, index, sel);
+
+ switch (opcode) {
+ case UPIU_QUERY_OPCODE_WRITE_ATTR:
+ request->query_func = UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST;
+ upiu_req = (struct utp_upiu_query_v4_0 *)&request->upiu_req;
+ put_unaligned_be64(*attr_val, &upiu_req->osf3);
+ break;
+ case UPIU_QUERY_OPCODE_READ_ATTR:
+ request->query_func = UPIU_QUERY_FUNC_STANDARD_READ_REQUEST;
+ break;
+ default:
+ dev_err(hba->dev, "%s: Expected query attr opcode but got = 0x%.2x\n",
+ __func__, opcode);
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
+ err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY, dev_cmd_timeout);
+ if (err) {
+ dev_err(hba->dev, "%s: opcode 0x%.2x for idn %d failed, index %d, selector %d, err = %d\n",
+ __func__, opcode, idn, index, sel, err);
+ goto out_unlock;
+ }
+
+ upiu_resp = (struct utp_upiu_query_v4_0 *)response;
+ *attr_val = get_unaligned_be64(&upiu_resp->osf3);
+
+out_unlock:
+ ufshcd_dev_man_unlock(hba);
+ return err;
+}
+
+/*
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+static int __ufshcd_query_descriptor(struct ufs_hba *hba,
+ enum query_opcode opcode, enum desc_idn idn, u8 index,
+ u8 selector, u8 *desc_buf, int *buf_len)
+{
+ struct ufs_query_req *request = NULL;
+ struct ufs_query_res *response = NULL;
+ int err;
+
+ BUG_ON(!hba);
+
+ if (!desc_buf) {
+ dev_err(hba->dev, "%s: descriptor buffer required for opcode 0x%x\n",
+ __func__, opcode);
+ return -EINVAL;
+ }
+
+ if (*buf_len < QUERY_DESC_MIN_SIZE || *buf_len > QUERY_DESC_MAX_SIZE) {
+ dev_err(hba->dev, "%s: descriptor buffer size (%d) is out of range\n",
+ __func__, *buf_len);
+ return -EINVAL;
+ }
+
+ ufshcd_dev_man_lock(hba);
+
+ ufshcd_init_query(hba, &request, &response, opcode, idn, index,
+ selector);
+ hba->dev_cmd.query.descriptor = desc_buf;
+ request->upiu_req.length = cpu_to_be16(*buf_len);
+
+ switch (opcode) {
+ case UPIU_QUERY_OPCODE_WRITE_DESC:
+ request->query_func = UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST;
+ break;
+ case UPIU_QUERY_OPCODE_READ_DESC:
+ request->query_func = UPIU_QUERY_FUNC_STANDARD_READ_REQUEST;
+ break;
+ default:
+ dev_err(hba->dev,
+ "%s: Expected query descriptor opcode but got = 0x%.2x\n",
+ __func__, opcode);
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
+ err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY, dev_cmd_timeout);
+
+ if (err) {
+ dev_err(hba->dev, "%s: opcode 0x%.2x for idn %d failed, index %d, err = %d\n",
+ __func__, opcode, idn, index, err);
+ goto out_unlock;
+ }
+
+ *buf_len = be16_to_cpu(response->upiu_res.length);
+
+out_unlock:
+ hba->dev_cmd.query.descriptor = NULL;
+ ufshcd_dev_man_unlock(hba);
+ return err;
+}
+
+/**
+ * ufshcd_query_descriptor_retry - API function for sending descriptor requests
+ * @hba: per-adapter instance
+ * @opcode: attribute opcode
+ * @idn: attribute idn to access
+ * @index: index field
+ * @selector: selector field
+ * @desc_buf: the buffer that contains the descriptor
+ * @buf_len: length parameter passed to the device
+ *
+ * The buf_len parameter will contain, on return, the length parameter
+ * received on the response.
+ *
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+int ufshcd_query_descriptor_retry(struct ufs_hba *hba,
+ enum query_opcode opcode,
+ enum desc_idn idn, u8 index,
+ u8 selector,
+ u8 *desc_buf, int *buf_len)
+{
+ int err;
+ int retries;
+
+ for (retries = QUERY_REQ_RETRIES; retries > 0; retries--) {
+ err = __ufshcd_query_descriptor(hba, opcode, idn, index,
+ selector, desc_buf, buf_len);
+ if (!err || err == -EINVAL)
+ break;
+ }
+
+ return err;
+}
+
+/**
+ * ufshcd_read_desc_param - read the specified descriptor parameter
+ * @hba: Pointer to adapter instance
+ * @desc_id: descriptor idn value
+ * @desc_index: descriptor index
+ * @param_offset: offset of the parameter to read
+ * @param_read_buf: pointer to buffer where parameter would be read
+ * @param_size: sizeof(param_read_buf)
+ *
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+int ufshcd_read_desc_param(struct ufs_hba *hba,
+ enum desc_idn desc_id,
+ int desc_index,
+ u8 param_offset,
+ u8 *param_read_buf,
+ u8 param_size)
+{
+ int ret;
+ u8 *desc_buf;
+ int buff_len = QUERY_DESC_MAX_SIZE;
+ bool is_kmalloc = true;
+
+ /* Safety check */
+ if (desc_id >= QUERY_DESC_IDN_MAX || !param_size)
+ return -EINVAL;
+
+ /* Check whether we need temp memory */
+ if (param_offset != 0 || param_size < buff_len) {
+ desc_buf = kzalloc(buff_len, GFP_KERNEL);
+ if (!desc_buf)
+ return -ENOMEM;
+ } else {
+ desc_buf = param_read_buf;
+ is_kmalloc = false;
+ }
+
+ /* Request for full descriptor */
+ ret = ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC,
+ desc_id, desc_index, 0,
+ desc_buf, &buff_len);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed reading descriptor. desc_id %d, desc_index %d, param_offset %d, ret %d\n",
+ __func__, desc_id, desc_index, param_offset, ret);
+ goto out;
+ }
+
+ /* Update descriptor length */
+ buff_len = desc_buf[QUERY_DESC_LENGTH_OFFSET];
+
+ if (param_offset >= buff_len) {
+ dev_err(hba->dev, "%s: Invalid offset 0x%x in descriptor IDN 0x%x, length 0x%x\n",
+ __func__, param_offset, desc_id, buff_len);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Sanity check */
+ if (desc_buf[QUERY_DESC_DESC_TYPE_OFFSET] != desc_id) {
+ dev_err(hba->dev, "%s: invalid desc_id %d in descriptor header\n",
+ __func__, desc_buf[QUERY_DESC_DESC_TYPE_OFFSET]);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (is_kmalloc) {
+ /* Make sure we don't copy more data than available */
+ if (param_offset >= buff_len)
+ ret = -EINVAL;
+ else
+ memcpy(param_read_buf, &desc_buf[param_offset],
+ min_t(u32, param_size, buff_len - param_offset));
+ }
+out:
+ if (is_kmalloc)
+ kfree(desc_buf);
+ return ret;
+}
+
+/**
+ * struct uc_string_id - unicode string
+ *
+ * @len: size of this descriptor inclusive
+ * @type: descriptor type
+ * @uc: unicode string character
+ */
+struct uc_string_id {
+ u8 len;
+ u8 type;
+ wchar_t uc[];
+} __packed;
+
+/* replace non-printable or non-ASCII characters with spaces */
+static inline char ufshcd_remove_non_printable(u8 ch)
+{
+ return (ch >= 0x20 && ch <= 0x7e) ? ch : ' ';
+}
+
+/**
+ * ufshcd_read_string_desc - read string descriptor
+ * @hba: pointer to adapter instance
+ * @desc_index: descriptor index
+ * @buf: pointer to buffer where descriptor would be read,
+ * the caller should free the memory.
+ * @fmt: if %SD_ASCII_STD, convert from UTF-16 to ASCII
+ *
+ * Return:
+ * * string size on success.
+ * * -ENOMEM: on allocation failure
+ * * -EINVAL: on a wrong parameter
+ */
+int ufshcd_read_string_desc(struct ufs_hba *hba, u8 desc_index, u8 **buf, enum ufs_descr_fmt fmt)
+{
+ struct uc_string_id *uc_str;
+ u8 *str;
+ int ret, uc_len;
+
+ if (!buf)
+ return -EINVAL;
+
+ uc_str = kzalloc(QUERY_DESC_MAX_SIZE, GFP_KERNEL);
+ if (!uc_str)
+ return -ENOMEM;
+
+ ret = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_STRING, desc_index, 0,
+ (u8 *)uc_str, QUERY_DESC_MAX_SIZE);
+ if (ret < 0) {
+ dev_err(hba->dev, "Reading String Desc failed after %d retries. err = %d\n",
+ QUERY_REQ_RETRIES, ret);
+ str = NULL;
+ goto out;
+ }
+
+ if (uc_str->len <= QUERY_DESC_HDR_SIZE) {
+ dev_dbg(hba->dev, "String Desc is of zero length\n");
+ str = NULL;
+ ret = 0;
+ goto out;
+ }
+
+ uc_len = uc_str->len - QUERY_DESC_HDR_SIZE;
+ if (uc_len % sizeof(*uc_str->uc)) {
+ dev_err(hba->dev, "String Desc has an odd UTF-16 payload length\n");
+ str = NULL;
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (fmt == SD_ASCII_STD) {
+ ssize_t ascii_len;
+ int i;
+ /* Allow up to three UTF-8 bytes per UTF-16 code unit plus a NUL. */
+ ascii_len = uc_len / sizeof(*uc_str->uc) * 3 + 1;
+ str = kzalloc(ascii_len, GFP_KERNEL);
+ if (!str) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /*
+ * the descriptor contains string in UTF16 format
+ * we need to convert to utf-8 so it can be displayed
+ */
+ ret = utf16s_to_utf8s(uc_str->uc,
+ uc_len / sizeof(*uc_str->uc),
+ UTF16_BIG_ENDIAN, str, ascii_len - 1);
+
+ /* replace non-printable or non-ASCII characters with spaces */
+ for (i = 0; i < ret; i++)
+ str[i] = ufshcd_remove_non_printable(str[i]);
+
+ str[ret++] = '\0';
+
+ } else {
+ /*
+ * Keep the bLength-sized raw output for the RPMB device ID ABI.
+ * The two bytes beyond the UTF-16 payload are explicitly zeroed
+ * instead of being read past the descriptor buffer.
+ */
+ str = kzalloc(uc_str->len, GFP_KERNEL);
+ if (!str) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ memcpy(str, uc_str->uc, uc_len);
+ ret = uc_str->len;
+ }
+out:
+ *buf = str;
+ kfree(uc_str);
+ return ret;
+}
+
+/**
+ * ufshcd_read_unit_desc_param - read the specified unit descriptor parameter
+ * @hba: Pointer to adapter instance
+ * @lun: lun id
+ * @param_offset: offset of the parameter to read
+ * @param_read_buf: pointer to buffer where parameter would be read
+ * @param_size: sizeof(param_read_buf)
+ *
+ * Return: 0 in case of success; < 0 upon failure.
+ */
+static inline int ufshcd_read_unit_desc_param(struct ufs_hba *hba,
+ int lun,
+ enum unit_desc_param param_offset,
+ u8 *param_read_buf,
+ u32 param_size)
+{
+ /*
+ * Unit descriptors are only available for general purpose LUs (LUN id
+ * from 0 to 7) and RPMB Well known LU.
+ */
+ if (!ufs_is_valid_unit_desc_lun(&hba->dev_info, lun))
+ return -EOPNOTSUPP;
+
+ return ufshcd_read_desc_param(hba, QUERY_DESC_IDN_UNIT, lun,
+ param_offset, param_read_buf, param_size);
+}
+
+static int ufshcd_get_ref_clk_gating_wait(struct ufs_hba *hba)
+{
+ int err = 0;
+ u32 gating_wait = UFSHCD_REF_CLK_GATING_WAIT_US;
+
+ if (hba->dev_info.wspecversion >= 0x300) {
+ err = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME, 0, 0,
+ &gating_wait);
+ if (err)
+ dev_err(hba->dev, "Failed reading bRefClkGatingWait. err = %d, use default %uus\n",
+ err, gating_wait);
+
+ if (gating_wait == 0) {
+ gating_wait = UFSHCD_REF_CLK_GATING_WAIT_US;
+ dev_err(hba->dev, "Undefined ref clk gating wait time, use default %uus\n",
+ gating_wait);
+ }
+
+ hba->dev_info.clk_gating_wait_us = gating_wait;
+ }
+
+ return err;
+}
+
+/**
+ * ufshcd_memory_alloc - allocate memory for host memory space data structures
+ * @hba: per adapter instance
+ *
+ * 1. Allocate DMA memory for Command Descriptor array
+ * Each command descriptor consist of Command UPIU, Response UPIU and PRDT
+ * 2. Allocate DMA memory for UTP Transfer Request Descriptor List (UTRDL).
+ * 3. Allocate DMA memory for UTP Task Management Request Descriptor List
+ * (UTMRDL)
+ * 4. Allocate memory for local reference block(lrb).
+ *
+ * Return: 0 for success, non-zero in case of failure.
+ */
+static int ufshcd_memory_alloc(struct ufs_hba *hba)
+{
+ size_t utmrdl_size, utrdl_size, ucdl_size;
+
+ /* The reserved tag uses the dedicated UCD below, not this pool. */
+ ucdl_size = ufshcd_get_ucd_size(hba) * (hba->nutrs - UFSHCD_NUM_RESERVED);
+ hba->ucdl_base_addr = dmam_alloc_coherent(hba->dev,
+ ucdl_size,
+ &hba->ucdl_dma_addr,
+ GFP_KERNEL);
+
+ /*
+ * UFSHCI requires UTP command descriptor to be 128 byte aligned.
+ */
+ if (!hba->ucdl_base_addr ||
+ WARN_ON(hba->ucdl_dma_addr & (128 - 1))) {
+ dev_err(hba->dev,
+ "Command Descriptor Memory allocation failed\n");
+ goto out;
+ }
+
+ /* Dedicated UCD for the reserved tag; allocate once (survives MCQ re-init). */
+ if (!hba->devman_ucd_base_addr) {
+ hba->devman_ucd_base_addr =
+ dmam_alloc_coherent(hba->dev,
+ ufshcd_get_devman_ucd_size(hba),
+ &hba->devman_ucd_dma_addr,
+ GFP_KERNEL);
+ if (!hba->devman_ucd_base_addr ||
+ WARN_ON(hba->devman_ucd_dma_addr & (128 - 1))) {
+ dev_err(hba->dev,
+ "Devman Command Descriptor Memory allocation failed\n");
+ goto out;
+ }
+ }
+
+ /*
+ * Allocate memory for UTP Transfer descriptors
+ * UFSHCI requires 1KB alignment of UTRD
+ */
+ utrdl_size = (sizeof(struct utp_transfer_req_desc) * hba->nutrs);
+ hba->utrdl_base_addr = dmam_alloc_coherent(hba->dev,
+ utrdl_size,
+ &hba->utrdl_dma_addr,
+ GFP_KERNEL);
+ if (!hba->utrdl_base_addr ||
+ WARN_ON(hba->utrdl_dma_addr & (SZ_1K - 1))) {
+ dev_err(hba->dev,
+ "Transfer Descriptor Memory allocation failed\n");
+ goto out;
+ }
+
+ /*
+ * Skip utmrdl allocation; it may have been
+ * allocated during first pass and not released during
+ * MCQ memory allocation.
+ * See ufshcd_release_sdb_queue() and ufshcd_config_mcq()
+ */
+ if (hba->utmrdl_base_addr)
+ goto skip_utmrdl;
+ /*
+ * Allocate memory for UTP Task Management descriptors
+ * UFSHCI requires 1KB alignment of UTMRD
+ */
+ utmrdl_size = sizeof(struct utp_task_req_desc) * hba->nutmrs;
+ hba->utmrdl_base_addr = dmam_alloc_coherent(hba->dev,
+ utmrdl_size,
+ &hba->utmrdl_dma_addr,
+ GFP_KERNEL);
+ if (!hba->utmrdl_base_addr ||
+ WARN_ON(hba->utmrdl_dma_addr & (SZ_1K - 1))) {
+ dev_err(hba->dev,
+ "Task Management Descriptor Memory allocation failed\n");
+ goto out;
+ }
+
+skip_utmrdl:
+ return 0;
+out:
+ return -ENOMEM;
+}
+
+/**
+ * ufshcd_host_memory_configure - configure local reference block with
+ * memory offsets
+ * @hba: per adapter instance
+ *
+ * Configure Host memory space
+ * 1. Update Corresponding UTRD.UCDBA and UTRD.UCDBAU with UCD DMA
+ * address.
+ * 2. Update each UTRD with Response UPIU offset, Response UPIU length
+ * and PRDT offset.
+ * 3. Save the corresponding addresses of UTRD, UCD.CMD, UCD.RSP and UCD.PRDT
+ * into local reference block.
+ */
+static void ufshcd_host_memory_configure(struct ufs_hba *hba)
+{
+ struct utp_transfer_req_desc *utrdlp;
+ dma_addr_t cmd_desc_dma_addr;
+ dma_addr_t cmd_desc_element_addr;
+ u16 response_offset;
+ u16 prdt_offset;
+ u16 response_len;
+ int cmd_desc_size;
+ int i;
+
+ utrdlp = hba->utrdl_base_addr;
+
+ cmd_desc_size = ufshcd_get_ucd_size(hba);
+ cmd_desc_dma_addr = hba->ucdl_dma_addr;
+
+ for (i = 0; i < hba->nutrs; i++) {
+ /*
+ * Reserved tags (low end) use the dedicated devman UCD with a
+ * larger response area; other tags index the pool at i - RESERVED.
+ */
+ if (i < UFSHCD_NUM_RESERVED) {
+ cmd_desc_element_addr = hba->devman_ucd_dma_addr;
+ response_offset = offsetof(struct utp_devman_cmd_desc,
+ response_upiu);
+ prdt_offset = offsetof(struct utp_devman_cmd_desc,
+ prd_table);
+ response_len = ALIGNED_DEVMAN_RSP_SIZE;
+ } else {
+ cmd_desc_element_addr = cmd_desc_dma_addr +
+ cmd_desc_size * (i - UFSHCD_NUM_RESERVED);
+ response_offset = offsetof(struct utp_transfer_cmd_desc,
+ response_upiu);
+ prdt_offset = offsetof(struct utp_transfer_cmd_desc,
+ prd_table);
+ response_len = ALIGNED_UPIU_SIZE;
+ }
+
+ /* Configure UTRD with command descriptor base address */
+ utrdlp[i].command_desc_base_addr =
+ cpu_to_le64(cmd_desc_element_addr);
+
+ /* Response upiu and prdt offset should be in double words */
+ if (hba->quirks & UFSHCD_QUIRK_PRDT_BYTE_GRAN) {
+ utrdlp[i].response_upiu_offset =
+ cpu_to_le16(response_offset);
+ utrdlp[i].prd_table_offset =
+ cpu_to_le16(prdt_offset);
+ utrdlp[i].response_upiu_length =
+ cpu_to_le16(response_len);
+ } else {
+ utrdlp[i].response_upiu_offset =
+ cpu_to_le16(response_offset >> 2);
+ utrdlp[i].prd_table_offset =
+ cpu_to_le16(prdt_offset >> 2);
+ utrdlp[i].response_upiu_length =
+ cpu_to_le16(response_len >> 2);
+ }
+ }
+}
+
+/**
+ * ufshcd_dme_link_startup - Notify Unipro to perform link startup
+ * @hba: per adapter instance
+ *
+ * UIC_CMD_DME_LINK_STARTUP command must be issued to Unipro layer,
+ * in order to initialize the Unipro link startup procedure.
+ * Once the Unipro links are up, the device connected to the controller
+ * is detected.
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+static int ufshcd_dme_link_startup(struct ufs_hba *hba)
+{
+ struct uic_command uic_cmd = {
+ .command = UIC_CMD_DME_LINK_STARTUP,
+ };
+ int ret;
+
+ ret = ufshcd_send_uic_cmd(hba, &uic_cmd);
+ if (ret)
+ dev_dbg(hba->dev,
+ "dme-link-startup: error code %d\n", ret);
+ return ret;
+}
+/**
+ * ufshcd_dme_reset - UIC command for DME_RESET
+ * @hba: per adapter instance
+ *
+ * DME_RESET command is issued in order to reset UniPro stack.
+ * This function now deals with cold reset.
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+int ufshcd_dme_reset(struct ufs_hba *hba)
+{
+ struct uic_command uic_cmd = {
+ .command = UIC_CMD_DME_RESET,
+ };
+ int ret;
+
+ ret = ufshcd_send_uic_cmd(hba, &uic_cmd);
+ if (ret)
+ dev_err(hba->dev,
+ "dme-reset: error code %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_dme_reset);
+
+int ufshcd_dme_configure_adapt(struct ufs_hba *hba,
+ int agreed_gear,
+ int adapt_val)
+{
+ int ret;
+
+ if (agreed_gear < UFS_HS_G4)
+ adapt_val = PA_NO_ADAPT;
+
+ ret = ufshcd_dme_set(hba,
+ UIC_ARG_MIB(PA_TXHSADAPTTYPE),
+ adapt_val);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_dme_configure_adapt);
+
+/**
+ * ufshcd_dme_enable - UIC command for DME_ENABLE
+ * @hba: per adapter instance
+ *
+ * DME_ENABLE command is issued in order to enable UniPro stack.
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+int ufshcd_dme_enable(struct ufs_hba *hba)
+{
+ struct uic_command uic_cmd = {
+ .command = UIC_CMD_DME_ENABLE,
+ };
+ int ret;
+
+ ret = ufshcd_send_uic_cmd(hba, &uic_cmd);
+ if (ret)
+ dev_err(hba->dev,
+ "dme-enable: error code %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_dme_enable);
+
+static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba)
+{
+ #define MIN_DELAY_BEFORE_DME_CMDS_US 1000
+ unsigned long min_sleep_time_us;
+
+ if (!(hba->quirks & UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS))
+ return;
+
+ /*
+ * last_dme_cmd_tstamp will be 0 only for 1st call to
+ * this function
+ */
+ if (unlikely(!ktime_to_us(hba->last_dme_cmd_tstamp))) {
+ min_sleep_time_us = MIN_DELAY_BEFORE_DME_CMDS_US;
+ } else {
+ unsigned long delta =
+ (unsigned long) ktime_to_us(
+ ktime_sub(ktime_get(),
+ hba->last_dme_cmd_tstamp));
+
+ if (delta < MIN_DELAY_BEFORE_DME_CMDS_US)
+ min_sleep_time_us =
+ MIN_DELAY_BEFORE_DME_CMDS_US - delta;
+ else
+ min_sleep_time_us = 0; /* no more delay required */
+ }
+
+ if (min_sleep_time_us > 0) {
+ /* allow sleep for extra 50us if needed */
+ usleep_range(min_sleep_time_us, min_sleep_time_us + 50);
+ }
+
+ /* update the last_dme_cmd_tstamp */
+ hba->last_dme_cmd_tstamp = ktime_get();
+}
+
+/**
+ * ufshcd_dme_set_attr - UIC command for DME_SET, DME_PEER_SET
+ * @hba: per adapter instance
+ * @attr_sel: uic command argument1
+ * @attr_set: attribute set type as uic command argument2
+ * @mib_val: setting value as uic command argument3
+ * @peer: indicate whether peer or local
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+int ufshcd_dme_set_attr(struct ufs_hba *hba, u32 attr_sel,
+ u8 attr_set, u32 mib_val, u8 peer)
+{
+ struct uic_command uic_cmd = {
+ .command = peer ? UIC_CMD_DME_PEER_SET : UIC_CMD_DME_SET,
+ .argument1 = attr_sel,
+ .argument2 = UIC_ARG_ATTR_TYPE(attr_set),
+ .argument3 = mib_val,
+ };
+ static const char *const action[] = {
+ "dme-set",
+ "dme-peer-set"
+ };
+ const char *set = action[!!peer];
+ int ret;
+ int retries = UFS_UIC_COMMAND_RETRIES;
+
+ do {
+ /* for peer attributes we retry upon failure */
+ ret = ufshcd_send_uic_cmd(hba, &uic_cmd);
+ if (ret)
+ dev_dbg(hba->dev, "%s: attr-id 0x%x val 0x%x error code %d\n",
+ set, UIC_GET_ATTR_ID(attr_sel), mib_val, ret);
+ } while (ret && peer && --retries);
+
+ if (ret)
+ dev_err(hba->dev, "%s: attr-id 0x%x val 0x%x failed %d retries\n",
+ set, UIC_GET_ATTR_ID(attr_sel), mib_val,
+ UFS_UIC_COMMAND_RETRIES - retries);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_dme_set_attr);
+
+/**
+ * ufshcd_dme_get_attr - UIC command for DME_GET, DME_PEER_GET
+ * @hba: per adapter instance
+ * @attr_sel: uic command argument1
+ * @mib_val: the value of the attribute as returned by the UIC command
+ * @peer: indicate whether peer or local
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,
+ u32 *mib_val, u8 peer)
+{
+ struct uic_command uic_cmd = {
+ .command = peer ? UIC_CMD_DME_PEER_GET : UIC_CMD_DME_GET,
+ .argument1 = attr_sel,
+ };
+ static const char *const action[] = {
+ "dme-get",
+ "dme-peer-get"
+ };
+ const char *get = action[!!peer];
+ int ret;
+ int retries = UFS_UIC_COMMAND_RETRIES;
+ struct ufs_pa_layer_attr orig_pwr_info;
+ struct ufs_pa_layer_attr temp_pwr_info;
+ bool pwr_mode_change = false;
+
+ if (peer && (hba->quirks & UFSHCD_QUIRK_DME_PEER_ACCESS_AUTO_MODE)) {
+ orig_pwr_info = hba->pwr_info;
+ temp_pwr_info = orig_pwr_info;
+
+ if (orig_pwr_info.pwr_tx == FAST_MODE ||
+ orig_pwr_info.pwr_rx == FAST_MODE) {
+ temp_pwr_info.pwr_tx = FASTAUTO_MODE;
+ temp_pwr_info.pwr_rx = FASTAUTO_MODE;
+ pwr_mode_change = true;
+ } else if (orig_pwr_info.pwr_tx == SLOW_MODE ||
+ orig_pwr_info.pwr_rx == SLOW_MODE) {
+ temp_pwr_info.pwr_tx = SLOWAUTO_MODE;
+ temp_pwr_info.pwr_rx = SLOWAUTO_MODE;
+ pwr_mode_change = true;
+ }
+ if (pwr_mode_change) {
+ ret = ufshcd_change_power_mode(hba, &temp_pwr_info,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+ if (ret)
+ goto out;
+ }
+ }
+
+ do {
+ /* for peer attributes we retry upon failure */
+ ret = ufshcd_send_uic_cmd(hba, &uic_cmd);
+ if (ret)
+ dev_dbg(hba->dev, "%s: attr-id 0x%x error code %d\n",
+ get, UIC_GET_ATTR_ID(attr_sel), ret);
+ } while (ret && peer && --retries);
+
+ if (ret)
+ dev_err(hba->dev, "%s: attr-id 0x%x failed %d retries\n",
+ get, UIC_GET_ATTR_ID(attr_sel),
+ UFS_UIC_COMMAND_RETRIES - retries);
+
+ if (mib_val)
+ *mib_val = ret == 0 ? uic_cmd.argument3 : 0;
+
+ if (peer && (hba->quirks & UFSHCD_QUIRK_DME_PEER_ACCESS_AUTO_MODE)
+ && pwr_mode_change)
+ ufshcd_change_power_mode(hba, &orig_pwr_info,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_dme_get_attr);
+
+/**
+ * ufshcd_dme_rmw - get modify set a DME attribute
+ * @hba: per adapter instance
+ * @mask: indicates which bits to clear from the value that has been read
+ * @val: actual value to write
+ * @attr: dme attribute
+ */
+int ufshcd_dme_rmw(struct ufs_hba *hba, u32 mask,
+ u32 val, u32 attr)
+{
+ u32 cfg = 0;
+ int err;
+
+ err = ufshcd_dme_get(hba, UIC_ARG_MIB(attr), &cfg);
+ if (err)
+ return err;
+
+ cfg &= ~mask;
+ cfg |= (val & mask);
+
+ return ufshcd_dme_set(hba, UIC_ARG_MIB(attr), cfg);
+}
+EXPORT_SYMBOL_GPL(ufshcd_dme_rmw);
+
+/**
+ * ufshcd_uic_pwr_ctrl - executes UIC commands (which affects the link power
+ * state) and waits for it to take effect.
+ *
+ * @hba: per adapter instance
+ * @cmd: UIC command to execute
+ *
+ * DME operations like DME_SET(PA_PWRMODE), DME_HIBERNATE_ENTER &
+ * DME_HIBERNATE_EXIT commands take some time to take its effect on both host
+ * and device UniPro link and hence it's final completion would be indicated by
+ * dedicated status bits in Interrupt Status register (UPMS, UHES, UHXS) in
+ * addition to normal UIC command completion Status (UCCS). This function only
+ * returns after the relevant status bits indicate the completion.
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+static int ufshcd_uic_pwr_ctrl(struct ufs_hba *hba, struct uic_command *cmd)
+{
+ DECLARE_COMPLETION_ONSTACK(uic_async_done);
+ unsigned long flags;
+ u8 status;
+ int ret;
+
+ mutex_lock(&hba->uic_cmd_mutex);
+ ufshcd_add_delay_before_dme_cmd(hba);
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (ufshcd_is_link_broken(hba)) {
+ ret = -ENOLINK;
+ goto out_unlock;
+ }
+ hba->uic_async_done = &uic_async_done;
+ ufshcd_disable_intr(hba, UIC_COMMAND_COMPL);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ ret = __ufshcd_send_uic_cmd(hba, cmd);
+ if (ret) {
+ dev_err(hba->dev,
+ "pwr ctrl cmd 0x%x with (MIBattribute 0x%x, mode 0x%x) uic error %d\n",
+ cmd->command, UIC_GET_ATTR_ID(cmd->argument1),
+ cmd->argument3, ret);
+ goto out;
+ }
+
+ if (!wait_for_completion_timeout(hba->uic_async_done,
+ msecs_to_jiffies(uic_cmd_timeout))) {
+ dev_err(hba->dev,
+ "pwr ctrl cmd 0x%x with (MIBattribute 0x%x, mode 0x%x) completion timeout\n",
+ cmd->command, UIC_GET_ATTR_ID(cmd->argument1),
+ cmd->argument3);
+
+ if (!cmd->cmd_active) {
+ dev_err(hba->dev, "%s: Power Mode Change operation has been completed, go check UPMCRS\n",
+ __func__);
+ goto check_upmcrs;
+ }
+
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+check_upmcrs:
+ status = ufshcd_get_upmcrs(hba);
+ if (status != PWR_LOCAL) {
+ dev_err(hba->dev,
+ "pwr ctrl cmd 0x%x with (MIBattribute 0x%x, mode 0x%x) failed, host upmcrs:0x%x\n",
+ cmd->command, UIC_GET_ATTR_ID(cmd->argument1),
+ cmd->argument3, status);
+ ret = (status != PWR_OK) ? status : -1;
+ }
+out:
+ if (ret) {
+ ufshcd_print_host_state(hba);
+ ufshcd_print_pwr_info(hba);
+ ufshcd_print_tx_eq_params(hba);
+ ufshcd_print_evt_hist(hba);
+ }
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ hba->active_uic_cmd = NULL;
+ hba->uic_async_done = NULL;
+ if (ret && !hba->pm_op_in_progress) {
+ ufshcd_set_link_broken(hba);
+ ufshcd_schedule_eh_work(hba);
+ }
+out_unlock:
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ mutex_unlock(&hba->uic_cmd_mutex);
+
+ return ret;
+}
+
+/**
+ * ufshcd_uic_tx_eqtr - Perform UIC TX Equalization Training
+ * @hba: per adapter instance
+ * @gear: target gear for EQTR
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+int ufshcd_uic_tx_eqtr(struct ufs_hba *hba, int gear)
+{
+ struct uic_command uic_cmd = {
+ .command = UIC_CMD_DME_SET,
+ .argument1 = UIC_ARG_MIB(PA_EQTR_GEAR),
+ .argument3 = gear,
+ };
+ int ret;
+
+ ufshcd_hold(hba);
+ ret = ufshcd_uic_pwr_ctrl(hba, &uic_cmd);
+ ufshcd_release(hba);
+
+ return ret;
+}
+
+/**
+ * ufshcd_send_bsg_uic_cmd - Send UIC commands requested via BSG layer and retrieve the result
+ * @hba: per adapter instance
+ * @uic_cmd: UIC command
+ *
+ * Return: 0 only if success.
+ */
+int ufshcd_send_bsg_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
+{
+ int ret;
+
+ if (uic_cmd->argument1 != UIC_ARG_MIB(PA_PWRMODE) ||
+ uic_cmd->command != UIC_CMD_DME_SET)
+ return ufshcd_send_uic_cmd(hba, uic_cmd);
+
+ if (hba->quirks & UFSHCD_QUIRK_BROKEN_UIC_CMD)
+ return 0;
+
+ ufshcd_hold(hba);
+ ret = ufshcd_uic_pwr_ctrl(hba, uic_cmd);
+ ufshcd_release(hba);
+
+ return ret;
+}
+
+/**
+ * ufshcd_uic_change_pwr_mode - Perform the UIC power mode chage
+ * using DME_SET primitives.
+ * @hba: per adapter instance
+ * @mode: powr mode value
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode)
+{
+ struct uic_command uic_cmd = {
+ .command = UIC_CMD_DME_SET,
+ .argument1 = UIC_ARG_MIB(PA_PWRMODE),
+ .argument3 = mode,
+ };
+ int ret;
+
+ if (hba->quirks & UFSHCD_QUIRK_BROKEN_PA_RXHSUNTERMCAP) {
+ ret = ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(PA_RXHSUNTERMCAP, 0), 1);
+ if (ret) {
+ dev_err(hba->dev, "%s: failed to enable PA_RXHSUNTERMCAP ret %d\n",
+ __func__, ret);
+ goto out;
+ }
+ }
+
+ ufshcd_hold(hba);
+ ret = ufshcd_uic_pwr_ctrl(hba, &uic_cmd);
+ ufshcd_release(hba);
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_uic_change_pwr_mode);
+
+int ufshcd_link_recovery(struct ufs_hba *hba)
+{
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ hba->ufshcd_state = UFSHCD_STATE_RESET;
+ ufshcd_set_eh_in_progress(hba);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ /* Reset the attached device */
+ ufshcd_device_reset(hba);
+
+ ret = ufshcd_host_reset_and_restore(hba);
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (ret)
+ hba->ufshcd_state = UFSHCD_STATE_ERROR;
+ ufshcd_clear_eh_in_progress(hba);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ if (ret)
+ dev_err(hba->dev, "%s: link recovery failed, err %d",
+ __func__, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_link_recovery);
+
+int ufshcd_uic_hibern8_enter(struct ufs_hba *hba)
+{
+ struct uic_command uic_cmd = {
+ .command = UIC_CMD_DME_HIBER_ENTER,
+ };
+ ktime_t start = ktime_get();
+ int ret;
+
+ ufshcd_vops_hibern8_notify(hba, UIC_CMD_DME_HIBER_ENTER, PRE_CHANGE);
+
+ ret = ufshcd_uic_pwr_ctrl(hba, &uic_cmd);
+ trace_ufshcd_profile_hibern8(hba, "enter",
+ ktime_to_us(ktime_sub(ktime_get(), start)), ret);
+
+ if (ret)
+ dev_err(hba->dev, "%s: hibern8 enter failed. ret = %d\n",
+ __func__, ret);
+ else
+ ufshcd_vops_hibern8_notify(hba, UIC_CMD_DME_HIBER_ENTER,
+ POST_CHANGE);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_uic_hibern8_enter);
+
+int ufshcd_uic_hibern8_exit(struct ufs_hba *hba)
+{
+ struct uic_command uic_cmd = {
+ .command = UIC_CMD_DME_HIBER_EXIT,
+ };
+ int ret;
+ ktime_t start = ktime_get();
+
+ ufshcd_vops_hibern8_notify(hba, UIC_CMD_DME_HIBER_EXIT, PRE_CHANGE);
+
+ ret = ufshcd_uic_pwr_ctrl(hba, &uic_cmd);
+ trace_ufshcd_profile_hibern8(hba, "exit",
+ ktime_to_us(ktime_sub(ktime_get(), start)), ret);
+
+ if (ret) {
+ dev_err(hba->dev, "%s: hibern8 exit failed. ret = %d\n",
+ __func__, ret);
+ } else {
+ ufshcd_vops_hibern8_notify(hba, UIC_CMD_DME_HIBER_EXIT,
+ POST_CHANGE);
+ hba->ufs_stats.last_hibern8_exit_tstamp = local_clock();
+ hba->ufs_stats.hibern8_exit_cnt++;
+ }
+
+ return ret;
+}
+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)
+{
+ const u32 cur_ahit = READ_ONCE(hba->ahit);
+
+ if (!ufshcd_is_auto_hibern8_supported(hba) || cur_ahit == ahit)
+ return;
+
+ 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);
+ ufshcd_release(hba);
+ ufshcd_rpm_put_sync(hba);
+ }
+}
+EXPORT_SYMBOL_GPL(ufshcd_auto_hibern8_update);
+
+ /**
+ * ufshcd_init_pwr_info - setting the POR (power on reset)
+ * values in hba power info
+ * @hba: per-adapter instance
+ */
+static void ufshcd_init_pwr_info(struct ufs_hba *hba)
+{
+ hba->pwr_info.gear_rx = UFS_PWM_G1;
+ hba->pwr_info.gear_tx = UFS_PWM_G1;
+ hba->pwr_info.lane_rx = UFS_LANE_1;
+ hba->pwr_info.lane_tx = UFS_LANE_1;
+ hba->pwr_info.pwr_rx = SLOWAUTO_MODE;
+ hba->pwr_info.pwr_tx = SLOWAUTO_MODE;
+ hba->pwr_info.hs_rate = 0;
+}
+
+/**
+ * ufshcd_get_max_pwr_mode - reads the max power mode negotiated with device
+ * @hba: per-adapter instance
+ *
+ * Return: 0 upon success; < 0 upon failure.
+ */
+static int ufshcd_get_max_pwr_mode(struct ufs_hba *hba)
+{
+ struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
+
+ if (hba->max_pwr_info.is_valid)
+ return 0;
+
+ if (hba->quirks & UFSHCD_QUIRK_HIBERN_FASTAUTO) {
+ pwr_info->pwr_tx = FASTAUTO_MODE;
+ pwr_info->pwr_rx = FASTAUTO_MODE;
+ } else {
+ pwr_info->pwr_tx = FAST_MODE;
+ pwr_info->pwr_rx = FAST_MODE;
+ }
+ pwr_info->hs_rate = PA_HS_MODE_B;
+
+ /* Get the connected lane count */
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_CONNECTEDRXDATALANES),
+ &pwr_info->lane_rx);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_CONNECTEDTXDATALANES),
+ &pwr_info->lane_tx);
+
+ if (!pwr_info->lane_rx || !pwr_info->lane_tx ||
+ pwr_info->lane_rx > UFS_MAX_LANES ||
+ pwr_info->lane_tx > UFS_MAX_LANES) {
+ dev_err(hba->dev, "%s: invalid connected lanes value. rx=%d, tx=%d\n",
+ __func__,
+ pwr_info->lane_rx,
+ pwr_info->lane_tx);
+ return -EINVAL;
+ }
+
+ if (pwr_info->lane_rx != pwr_info->lane_tx) {
+ dev_err(hba->dev, "%s: asymmetric connected lanes. rx=%d, tx=%d\n",
+ __func__,
+ pwr_info->lane_rx,
+ pwr_info->lane_tx);
+ return -EINVAL;
+ }
+
+ /*
+ * First, get the maximum gears of HS speed.
+ * If a zero value, it means there is no HSGEAR capability.
+ * Then, get the maximum gears of PWM speed.
+ */
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_MAXRXHSGEAR), &pwr_info->gear_rx);
+ if (!pwr_info->gear_rx) {
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_MAXRXPWMGEAR),
+ &pwr_info->gear_rx);
+ if (!pwr_info->gear_rx) {
+ dev_err(hba->dev, "%s: invalid max pwm rx gear read = %d\n",
+ __func__, pwr_info->gear_rx);
+ return -EINVAL;
+ }
+ pwr_info->pwr_rx = SLOW_MODE;
+ }
+
+ ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_MAXRXHSGEAR),
+ &pwr_info->gear_tx);
+ if (!pwr_info->gear_tx) {
+ ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_MAXRXPWMGEAR),
+ &pwr_info->gear_tx);
+ if (!pwr_info->gear_tx) {
+ dev_err(hba->dev, "%s: invalid max pwm tx gear read = %d\n",
+ __func__, pwr_info->gear_tx);
+ return -EINVAL;
+ }
+ pwr_info->pwr_tx = SLOW_MODE;
+ }
+
+ hba->max_pwr_info.is_valid = true;
+ return 0;
+}
+
+/**
+ * ufshcd_dme_change_power_mode() - UniPro DME Power Mode change sequence
+ * @hba: per-adapter instance
+ * @pwr_mode: pointer to the target power mode (gear/lane) attributes
+ * @pmc_policy: Power Mode change policy
+ *
+ * This function handles the low-level DME (Device Management Entity)
+ * configuration required to transition the UFS link to a new power mode. It
+ * performs the following steps:
+ * 1. Checks if the requested mode matches the current state.
+ * 2. Sets M-PHY and UniPro attributes including Gear (PA_RXGEAR/TXGEAR),
+ * Lanes, Termination, and HS Series (PA_HSSERIES).
+ * 3. Configures default UniPro timeout values (DL_FC0, etc.) unless
+ * explicitly skipped via quirks.
+ * 4. Triggers the actual hardware mode change via ufshcd_uic_change_pwr_mode().
+ * 5. Updates the HBA's cached power information on success.
+ *
+ * Return: 0 on success, non-zero error code on failure.
+ */
+static int ufshcd_dme_change_power_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ enum ufshcd_pmc_policy pmc_policy)
+{
+ int ret;
+
+ /* if already configured to the requested pwr_mode */
+ if (pmc_policy == UFSHCD_PMC_POLICY_DONT_FORCE &&
+ pwr_mode->gear_rx == hba->pwr_info.gear_rx &&
+ pwr_mode->gear_tx == hba->pwr_info.gear_tx &&
+ pwr_mode->lane_rx == hba->pwr_info.lane_rx &&
+ pwr_mode->lane_tx == hba->pwr_info.lane_tx &&
+ pwr_mode->pwr_rx == hba->pwr_info.pwr_rx &&
+ pwr_mode->pwr_tx == hba->pwr_info.pwr_tx &&
+ pwr_mode->hs_rate == hba->pwr_info.hs_rate) {
+ dev_dbg(hba->dev, "%s: power already configured\n", __func__);
+ return 0;
+ }
+
+ /*
+ * Configure attributes for power mode change with below.
+ * - PA_RXGEAR, PA_ACTIVERXDATALANES, PA_RXTERMINATION,
+ * - PA_TXGEAR, PA_ACTIVETXDATALANES, PA_TXTERMINATION,
+ * - PA_HSSERIES
+ */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_RXGEAR), pwr_mode->gear_rx);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_ACTIVERXDATALANES),
+ pwr_mode->lane_rx);
+ if (pwr_mode->pwr_rx == FASTAUTO_MODE ||
+ pwr_mode->pwr_rx == FAST_MODE)
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_RXTERMINATION), true);
+ else
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_RXTERMINATION), false);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXGEAR), pwr_mode->gear_tx);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_ACTIVETXDATALANES),
+ pwr_mode->lane_tx);
+ if (pwr_mode->pwr_tx == FASTAUTO_MODE ||
+ pwr_mode->pwr_tx == FAST_MODE)
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXTERMINATION), true);
+ else
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXTERMINATION), false);
+
+ if (pwr_mode->pwr_rx == FASTAUTO_MODE ||
+ pwr_mode->pwr_tx == FASTAUTO_MODE ||
+ pwr_mode->pwr_rx == FAST_MODE ||
+ pwr_mode->pwr_tx == FAST_MODE)
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HSSERIES),
+ pwr_mode->hs_rate);
+
+ if (!(hba->quirks & UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING)) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA0),
+ DL_FC0ProtectionTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA1),
+ DL_TC0ReplayTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA2),
+ DL_AFC0ReqTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA3),
+ DL_FC1ProtectionTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA4),
+ DL_TC1ReplayTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA5),
+ DL_AFC1ReqTimeOutVal_Default);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DME_LocalFC0ProtectionTimeOutVal),
+ DL_FC0ProtectionTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DME_LocalTC0ReplayTimeOutVal),
+ DL_TC0ReplayTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DME_LocalAFC0ReqTimeOutVal),
+ DL_AFC0ReqTimeOutVal_Default);
+ }
+
+ ret = ufshcd_uic_change_pwr_mode(hba, pwr_mode->pwr_rx << 4
+ | pwr_mode->pwr_tx);
+
+ if (ret) {
+ dev_err(hba->dev,
+ "%s: power mode change failed %d\n", __func__, ret);
+ } else {
+ memcpy(&hba->pwr_info, pwr_mode,
+ sizeof(struct ufs_pa_layer_attr));
+ }
+
+ return ret;
+}
+
+/**
+ * ufshcd_change_power_mode() - Change UFS Link Power Mode
+ * @hba: per-adapter instance
+ * @pwr_mode: pointer to the target power mode (gear/lane) attributes
+ * @pmc_policy: Power Mode change policy
+ *
+ * This function handles the high-level sequence for changing the UFS link
+ * power mode. It triggers vendor-specific pre-change notification,
+ * executes the DME (Device Management Entity) power mode change sequence,
+ * and, upon success, triggers vendor-specific post-change notification.
+ *
+ * Return: 0 on success, non-zero error code on failure.
+ */
+int ufshcd_change_power_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ enum ufshcd_pmc_policy pmc_policy)
+{
+ int ret;
+
+ ufshcd_vops_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
+
+ ret = ufshcd_dme_change_power_mode(hba, pwr_mode, pmc_policy);
+
+ if (!ret)
+ ufshcd_vops_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_change_power_mode);
+
+/**
+ * ufshcd_config_pwr_mode - configure a new power mode
+ * @hba: per-adapter instance
+ * @desired_pwr_mode: desired power configuration
+ * @pmc_policy: Power Mode change policy
+ *
+ * Return: 0 upon success; < 0 upon failure.
+ */
+int ufshcd_config_pwr_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *desired_pwr_mode,
+ enum ufshcd_pmc_policy pmc_policy)
+{
+ struct ufs_pa_layer_attr final_params = { 0 };
+ int ret;
+
+ ret = ufshcd_vops_negotiate_pwr_mode(hba, desired_pwr_mode,
+ &final_params);
+ if (ret) {
+ if (ret != -ENOTSUPP)
+ dev_err(hba->dev, "Failed to negotiate power mode: %d, use desired as is\n",
+ ret);
+
+ memcpy(&final_params, desired_pwr_mode, sizeof(final_params));
+ }
+
+ ret = ufshcd_config_tx_eq_settings(hba, &final_params, false);
+ if (ret)
+ dev_warn(hba->dev, "Failed to configure TX Equalization for HS-G%u, Rate-%s: %d\n",
+ final_params.gear_tx,
+ ufs_hs_rate_to_str(final_params.hs_rate), ret);
+
+ return ufshcd_change_power_mode(hba, &final_params, pmc_policy);
+}
+EXPORT_SYMBOL_GPL(ufshcd_config_pwr_mode);
+
+/**
+ * ufshcd_complete_dev_init() - checks device readiness
+ * @hba: per-adapter instance
+ *
+ * Set fDeviceInit flag and poll until device toggles it.
+ *
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+static int ufshcd_complete_dev_init(struct ufs_hba *hba)
+{
+ int err;
+ bool flag_res = true;
+ ktime_t timeout;
+
+ err = ufshcd_query_flag_retry(hba, UPIU_QUERY_OPCODE_SET_FLAG,
+ QUERY_FLAG_IDN_FDEVICEINIT, 0, NULL);
+ if (err) {
+ dev_err(hba->dev,
+ "%s: setting fDeviceInit flag failed with error %d\n",
+ __func__, err);
+ goto out;
+ }
+
+ /* Poll fDeviceInit flag to be cleared */
+ timeout = ktime_add_ms(ktime_get(), FDEVICEINIT_COMPL_TIMEOUT);
+ do {
+ err = ufshcd_query_flag(hba, UPIU_QUERY_OPCODE_READ_FLAG,
+ QUERY_FLAG_IDN_FDEVICEINIT, 0, &flag_res);
+ if (!flag_res)
+ break;
+ usleep_range(500, 1000);
+ } while (ktime_before(ktime_get(), timeout));
+
+ if (err) {
+ dev_err(hba->dev,
+ "%s: reading fDeviceInit flag failed with error %d\n",
+ __func__, err);
+ } else if (flag_res) {
+ dev_err(hba->dev,
+ "%s: fDeviceInit was not cleared by the device\n",
+ __func__);
+ err = -EBUSY;
+ }
+out:
+ return err;
+}
+
+/**
+ * ufshcd_make_hba_operational - Make UFS controller operational
+ * @hba: per adapter instance
+ *
+ * To bring UFS host controller to operational state,
+ * 1. Enable required interrupts
+ * 2. Configure interrupt aggregation
+ * 3. Program UTRL and UTMRL base address
+ * 4. Configure run-stop-registers
+ *
+ * Return: 0 if successful; < 0 upon failure.
+ */
+int ufshcd_make_hba_operational(struct ufs_hba *hba)
+{
+ int err = 0;
+ u32 reg;
+
+ /* Enable required interrupts */
+ ufshcd_enable_intr(hba, UFSHCD_ENABLE_INTRS);
+
+ /* Configure interrupt aggregation */
+ if (ufshcd_is_intr_aggr_allowed(hba))
+ ufshcd_config_intr_aggr(hba, hba->nutrs - 1, INT_AGGR_DEF_TO);
+ else
+ ufshcd_disable_intr_aggr(hba);
+
+ /* Configure UTRL and UTMRL base address registers */
+ ufshcd_writel(hba, lower_32_bits(hba->utrdl_dma_addr),
+ REG_UTP_TRANSFER_REQ_LIST_BASE_L);
+ ufshcd_writel(hba, upper_32_bits(hba->utrdl_dma_addr),
+ REG_UTP_TRANSFER_REQ_LIST_BASE_H);
+ ufshcd_writel(hba, lower_32_bits(hba->utmrdl_dma_addr),
+ REG_UTP_TASK_REQ_LIST_BASE_L);
+ ufshcd_writel(hba, upper_32_bits(hba->utmrdl_dma_addr),
+ REG_UTP_TASK_REQ_LIST_BASE_H);
+
+ /*
+ * UCRDY, UTMRLDY and UTRLRDY bits must be 1
+ */
+ reg = ufshcd_readl(hba, REG_CONTROLLER_STATUS);
+ if (!(ufshcd_get_lists_status(reg))) {
+ ufshcd_enable_run_stop_reg(hba);
+ } else {
+ dev_err(hba->dev,
+ "Host controller not ready to process requests");
+ err = -EIO;
+ }
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ufshcd_make_hba_operational);
+
+/**
+ * ufshcd_hba_stop - Send controller to reset state
+ * @hba: per adapter instance
+ */
+void ufshcd_hba_stop(struct ufs_hba *hba)
+{
+ int err;
+
+ ufshcd_disable_irq(hba);
+ ufshcd_writel(hba, CONTROLLER_DISABLE, REG_CONTROLLER_ENABLE);
+ err = ufshcd_wait_for_register(hba, REG_CONTROLLER_ENABLE,
+ CONTROLLER_ENABLE, CONTROLLER_DISABLE,
+ 10, 1);
+ ufshcd_enable_irq(hba);
+ if (err)
+ dev_err(hba->dev, "%s: Controller disable failed\n", __func__);
+}
+EXPORT_SYMBOL_GPL(ufshcd_hba_stop);
+
+/**
+ * ufshcd_hba_execute_hce - initialize the controller
+ * @hba: per adapter instance
+ *
+ * The controller resets itself and controller firmware initialization
+ * sequence kicks off. When controller is ready it will set
+ * the Host Controller Enable bit to 1.
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+static int ufshcd_hba_execute_hce(struct ufs_hba *hba)
+{
+ int retry;
+
+ for (retry = 3; retry > 0; retry--) {
+ if (ufshcd_is_hba_active(hba))
+ /* change controller state to "reset state" */
+ ufshcd_hba_stop(hba);
+
+ /* UniPro link is disabled at this point */
+ ufshcd_set_link_off(hba);
+
+ ufshcd_vops_hce_enable_notify(hba, PRE_CHANGE);
+
+ /* start controller initialization sequence */
+ ufshcd_hba_start(hba);
+
+ /*
+ * To initialize a UFS host controller HCE bit must be set to 1.
+ * During initialization the HCE bit value changes from 1->0->1.
+ * When the host controller completes initialization sequence
+ * it sets the value of HCE bit to 1. The same HCE bit is read back
+ * to check if the controller has completed initialization sequence.
+ * So without this delay the value HCE = 1, set in the previous
+ * instruction might be read back.
+ * This delay can be changed based on the controller.
+ */
+ ufshcd_delay_us(hba->vps->hba_enable_delay_us, 100);
+
+ /* wait for the host controller to complete initialization */
+ if (!ufshcd_wait_for_register(hba, REG_CONTROLLER_ENABLE, CONTROLLER_ENABLE,
+ CONTROLLER_ENABLE, 1000, 50))
+ break;
+
+ dev_err(hba->dev, "Enabling the controller failed\n");
+ }
+
+ if (!retry)
+ return -EIO;
+
+ /* enable UIC related interrupts */
+ ufshcd_enable_intr(hba, UFSHCD_UIC_MASK);
+
+ ufshcd_vops_hce_enable_notify(hba, POST_CHANGE);
+
+ return 0;
+}
+
+int ufshcd_hba_enable(struct ufs_hba *hba)
+{
+ int ret;
+
+ if (hba->quirks & UFSHCI_QUIRK_BROKEN_HCE) {
+ ufshcd_set_link_off(hba);
+ ufshcd_vops_hce_enable_notify(hba, PRE_CHANGE);
+
+ /* enable UIC related interrupts */
+ ufshcd_enable_intr(hba, UFSHCD_UIC_MASK);
+ ret = ufshcd_dme_reset(hba);
+ if (ret) {
+ dev_err(hba->dev, "DME_RESET failed\n");
+ return ret;
+ }
+
+ ret = ufshcd_dme_enable(hba);
+ if (ret) {
+ dev_err(hba->dev, "Enabling DME failed\n");
+ return ret;
+ }
+
+ ufshcd_vops_hce_enable_notify(hba, POST_CHANGE);
+ } else {
+ ret = ufshcd_hba_execute_hce(hba);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_hba_enable);
+
+static int ufshcd_disable_tx_lcc(struct ufs_hba *hba, bool peer)
+{
+ int tx_lanes, i, err = 0;
+
+ if (!peer)
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_CONNECTEDTXDATALANES),
+ &tx_lanes);
+ else
+ ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_CONNECTEDTXDATALANES),
+ &tx_lanes);
+ for (i = 0; i < tx_lanes; i++) {
+ if (!peer)
+ err = ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(TX_LCC_ENABLE,
+ UIC_ARG_MPHY_TX_GEN_SEL_INDEX(i)),
+ 0);
+ else
+ err = ufshcd_dme_peer_set(hba,
+ UIC_ARG_MIB_SEL(TX_LCC_ENABLE,
+ UIC_ARG_MPHY_TX_GEN_SEL_INDEX(i)),
+ 0);
+ if (err) {
+ dev_err(hba->dev, "%s: TX LCC Disable failed, peer = %d, lane = %d, err = %d",
+ __func__, peer, i, err);
+ break;
+ }
+ }
+
+ return err;
+}
+
+static inline int ufshcd_disable_device_tx_lcc(struct ufs_hba *hba)
+{
+ return ufshcd_disable_tx_lcc(hba, true);
+}
+
+void ufshcd_update_evt_hist(struct ufs_hba *hba, u32 id, u32 val)
+{
+ struct ufs_event_hist *e;
+
+ if (id >= UFS_EVT_CNT)
+ return;
+
+ e = &hba->ufs_stats.event[id];
+ e->val[e->pos] = val;
+ e->tstamp[e->pos] = local_clock();
+ e->cnt += 1;
+ e->pos = (e->pos + 1) % UFS_EVENT_HIST_LENGTH;
+
+ ufshcd_vops_event_notify(hba, id, &val);
+}
+EXPORT_SYMBOL_GPL(ufshcd_update_evt_hist);
+
+static int ufshcd_validate_link_params(struct ufs_hba *hba)
+{
+ int ret, val;
+
+ /*
+ * lanes_per_direction is only populated by the platform glue (it
+ * defaults to 2 or is read from the "lanes-per-direction" devicetree
+ * property). Controllers probed via ufshcd-pci leave it unset (0), in
+ * which case there is no expected lane count to validate the connected
+ * lanes against. Skip the check instead of failing link startup.
+ */
+ if (!hba->lanes_per_direction)
+ return 0;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_CONNECTEDTXDATALANES),
+ &val);
+ if (ret)
+ return ret;
+
+ if (val != hba->lanes_per_direction) {
+ dev_err(hba->dev, "Tx lane mismatch [config,reported] [%d,%d]\n",
+ hba->lanes_per_direction, val);
+ return -ENOLINK;
+ }
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_CONNECTEDRXDATALANES),
+ &val);
+ if (ret)
+ return ret;
+
+ if (val != hba->lanes_per_direction) {
+ dev_err(hba->dev, "Rx lane mismatch [config,reported] [%d,%d]\n",
+ hba->lanes_per_direction, val);
+ return -ENOLINK;
+ }
+
+ return 0;
+}
+
+/**
+ * ufshcd_link_startup - Initialize unipro link startup
+ * @hba: per adapter instance
+ *
+ * Return: 0 for success, non-zero in case of failure.
+ */
+static int ufshcd_link_startup(struct ufs_hba *hba)
+{
+ int ret;
+ int retries = DME_LINKSTARTUP_RETRIES;
+ bool link_startup_again = false;
+
+ /*
+ * If UFS device isn't active then we will have to issue link startup
+ * 2 times to make sure the device state move to active.
+ */
+ if (!(hba->quirks & UFSHCD_QUIRK_PERFORM_LINK_STARTUP_ONCE) &&
+ !ufshcd_is_ufs_dev_active(hba))
+ link_startup_again = true;
+
+link_startup:
+ do {
+ ufshcd_vops_link_startup_notify(hba, PRE_CHANGE);
+
+ ret = ufshcd_dme_link_startup(hba);
+
+ /* check if device is detected by inter-connect layer */
+ if (!ret && !ufshcd_is_device_present(hba)) {
+ ufshcd_update_evt_hist(hba,
+ UFS_EVT_LINK_STARTUP_FAIL,
+ 0);
+ dev_err(hba->dev, "%s: Device not present\n", __func__);
+ ret = -ENXIO;
+ goto out;
+ }
+
+ /*
+ * DME link lost indication is only received when link is up,
+ * but we can't be sure if the link is up until link startup
+ * succeeds. So reset the local Uni-Pro and try again.
+ */
+ if (ret && retries && ufshcd_hba_enable(hba)) {
+ ufshcd_update_evt_hist(hba,
+ UFS_EVT_LINK_STARTUP_FAIL,
+ (u32)ret);
+ goto out;
+ }
+ } while (ret && retries--);
+
+ if (ret) {
+ /* failed to get the link up... retire */
+ ufshcd_update_evt_hist(hba,
+ UFS_EVT_LINK_STARTUP_FAIL,
+ (u32)ret);
+ goto out;
+ }
+
+ if (link_startup_again) {
+ link_startup_again = false;
+ retries = DME_LINKSTARTUP_RETRIES;
+ goto link_startup;
+ }
+
+ /* Mark that link is up in PWM-G1, 1-lane, SLOW-AUTO mode */
+ ufshcd_init_pwr_info(hba);
+ ufshcd_print_pwr_info(hba);
+
+ if (hba->quirks & UFSHCD_QUIRK_BROKEN_LCC) {
+ ret = ufshcd_disable_device_tx_lcc(hba);
+ if (ret)
+ goto out;
+ }
+
+ ret = ufshcd_validate_link_params(hba);
+ if (ret)
+ goto out;
+
+ /* Include any host controller configuration via UIC commands */
+ ret = ufshcd_vops_link_startup_notify(hba, POST_CHANGE);
+ if (ret)
+ goto out;
+
+ /* Clear UECPA once due to LINERESET has happened during LINK_STARTUP */
+ ufshcd_readl(hba, REG_UIC_ERROR_CODE_PHY_ADAPTER_LAYER);
+ ret = ufshcd_make_hba_operational(hba);
+out:
+ if (ret)
+ dev_err(hba->dev, "link startup failed %d\n", ret);
+ return ret;
+}
+
+/**
+ * ufshcd_verify_dev_init() - Verify device initialization
+ * @hba: per-adapter instance
+ *
+ * Send NOP OUT UPIU and wait for NOP IN response to check whether the
+ * device Transport Protocol (UTP) layer is ready after a reset.
+ * If the UTP layer at the device side is not initialized, it may
+ * not respond with NOP IN UPIU within timeout of %NOP_OUT_TIMEOUT
+ * and we retry sending NOP OUT for %NOP_OUT_RETRIES iterations.
+ *
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+static int ufshcd_verify_dev_init(struct ufs_hba *hba)
+{
+ int err = 0;
+ int retries;
+
+ ufshcd_dev_man_lock(hba);
+
+ for (retries = NOP_OUT_RETRIES; retries > 0; retries--) {
+ err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_NOP,
+ hba->nop_out_timeout);
+
+ if (!err || err == -ETIMEDOUT)
+ break;
+
+ dev_dbg(hba->dev, "%s: error %d retrying\n", __func__, err);
+ }
+
+ ufshcd_dev_man_unlock(hba);
+
+ if (err)
+ dev_err(hba->dev, "%s: NOP OUT failed %d\n", __func__, err);
+ return err;
+}
+
+/**
+ * ufshcd_setup_links - associate link b/w device wlun and other luns
+ * @sdev: pointer to SCSI device
+ * @hba: pointer to ufs hba
+ */
+static void ufshcd_setup_links(struct ufs_hba *hba, struct scsi_device *sdev)
+{
+ struct device_link *link;
+
+ /*
+ * Device wlun is the supplier & rest of the luns are consumers.
+ * This ensures that device wlun suspends after all other luns.
+ */
+ if (hba->ufs_device_wlun) {
+ link = device_link_add(&sdev->sdev_gendev,
+ &hba->ufs_device_wlun->sdev_gendev,
+ DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE);
+ if (!link) {
+ dev_err(&sdev->sdev_gendev, "Failed establishing link - %s\n",
+ dev_name(&hba->ufs_device_wlun->sdev_gendev));
+ return;
+ }
+ hba->luns_avail--;
+ /* Ignore REPORT_LUN wlun probing */
+ if (hba->luns_avail == 1) {
+ ufshcd_rpm_put(hba);
+ return;
+ }
+ } else {
+ /*
+ * Device wlun is probed. The assumption is that WLUNs are
+ * scanned before other LUNs.
+ */
+ hba->luns_avail--;
+ }
+}
+
+/**
+ * ufshcd_lu_init - Initialize the relevant parameters of the LU
+ * @hba: per-adapter instance
+ * @sdev: pointer to SCSI device
+ */
+static void ufshcd_lu_init(struct ufs_hba *hba, struct scsi_device *sdev)
+{
+ int len = QUERY_DESC_MAX_SIZE;
+ u8 lun = ufshcd_scsi_to_upiu_lun(sdev->lun);
+ u8 lun_qdepth = hba->nutrs;
+ u8 *desc_buf;
+ int ret;
+
+ desc_buf = kzalloc(len, GFP_KERNEL);
+ if (!desc_buf)
+ goto set_qdepth;
+
+ ret = ufshcd_read_unit_desc_param(hba, lun, 0, desc_buf, len);
+ if (ret < 0) {
+ if (ret == -EOPNOTSUPP)
+ /* If LU doesn't support unit descriptor, its queue depth is set to 1 */
+ lun_qdepth = 1;
+ kfree(desc_buf);
+ goto set_qdepth;
+ }
+
+ if (desc_buf[UNIT_DESC_PARAM_LU_Q_DEPTH]) {
+ /*
+ * In per-LU queueing architecture, bLUQueueDepth will not be 0, then we will
+ * use the smaller between UFSHCI CAP.NUTRS and UFS LU bLUQueueDepth
+ */
+ lun_qdepth = min_t(int, desc_buf[UNIT_DESC_PARAM_LU_Q_DEPTH], hba->nutrs);
+ }
+ /*
+ * According to UFS device specification, the write protection mode is only supported by
+ * normal LU, not supported by WLUN.
+ */
+ if (hba->dev_info.f_power_on_wp_en && lun < hba->dev_info.max_lu_supported &&
+ !hba->dev_info.is_lu_power_on_wp &&
+ desc_buf[UNIT_DESC_PARAM_LU_WR_PROTECT] == UFS_LU_POWER_ON_WP)
+ hba->dev_info.is_lu_power_on_wp = true;
+
+ /* In case of RPMB LU, check if advanced RPMB mode is enabled, and get region size */
+ if (desc_buf[UNIT_DESC_PARAM_UNIT_INDEX] == UFS_UPIU_RPMB_WLUN) {
+ if (desc_buf[RPMB_UNIT_DESC_PARAM_REGION_EN] & BIT(4))
+ hba->dev_info.b_advanced_rpmb_en = true;
+ hba->dev_info.rpmb_region_size[0] = desc_buf[RPMB_UNIT_DESC_PARAM_REGION0_SIZE];
+ hba->dev_info.rpmb_region_size[1] = desc_buf[RPMB_UNIT_DESC_PARAM_REGION1_SIZE];
+ hba->dev_info.rpmb_region_size[2] = desc_buf[RPMB_UNIT_DESC_PARAM_REGION2_SIZE];
+ hba->dev_info.rpmb_region_size[3] = desc_buf[RPMB_UNIT_DESC_PARAM_REGION3_SIZE];
+
+ if (hba->dev_info.wspecversion <= 0x0220) {
+ /*
+ * These older spec chips have only one RPMB region,
+ * sized between 128 kB minimum and 16 MB maximum.
+ * No per region size fields are provided (respective
+ * REGIONX_SIZE fields always contain zeros), so get
+ * it from the logical block count and size fields for
+ * compatibility
+ *
+ * (See JESD220C-2_2 Section 14.1.4.6
+ * RPMB Unit Descriptor,* offset 13h, 4 bytes)
+ */
+ hba->dev_info.rpmb_region_size[0] =
+ (get_unaligned_be64(desc_buf
+ + RPMB_UNIT_DESC_PARAM_LOGICAL_BLK_COUNT)
+ << desc_buf[RPMB_UNIT_DESC_PARAM_LOGICAL_BLK_SIZE])
+ / SZ_128K;
+ }
+ }
+
+
+ kfree(desc_buf);
+set_qdepth:
+ /*
+ * For WLUNs that don't support unit descriptor, queue depth is set to 1. For LUs whose
+ * bLUQueueDepth == 0, the queue depth is set to a maximum value that host can queue.
+ */
+ dev_dbg(hba->dev, "Set LU %x queue depth %d\n", lun, lun_qdepth);
+ scsi_change_queue_depth(sdev, lun_qdepth);
+}
+
+/**
+ * ufshcd_sdev_init - handle initial SCSI device configurations
+ * @sdev: pointer to SCSI device
+ *
+ * Return: success.
+ */
+static int ufshcd_sdev_init(struct scsi_device *sdev)
+{
+ struct ufs_hba *hba;
+
+ hba = shost_priv(sdev->host);
+
+ /* Mode sense(6) is not supported by UFS, so use Mode sense(10) */
+ sdev->use_10_for_ms = 1;
+
+ /* DBD field should be set to 1 in mode sense(10) */
+ sdev->set_dbd_for_ms = 1;
+
+ /* allow SCSI layer to restart the device in case of errors */
+ sdev->allow_restart = 1;
+
+ /* REPORT SUPPORTED OPERATION CODES is not supported */
+ sdev->no_report_opcodes = 1;
+
+ /* WRITE_SAME command is not supported */
+ sdev->no_write_same = 1;
+
+ ufshcd_lu_init(hba, sdev);
+
+ ufshcd_setup_links(hba, sdev);
+
+ return 0;
+}
+
+/**
+ * ufshcd_change_queue_depth - change queue depth
+ * @sdev: pointer to SCSI device
+ * @depth: required depth to set
+ *
+ * Change queue depth and make sure the max. limits are not crossed.
+ *
+ * Return: new queue depth.
+ */
+static int ufshcd_change_queue_depth(struct scsi_device *sdev, int depth)
+{
+ return scsi_change_queue_depth(sdev, min(depth, sdev->host->can_queue));
+}
+
+/**
+ * ufshcd_sdev_configure - adjust SCSI device configurations
+ * @sdev: pointer to SCSI device
+ * @lim: queue limits
+ *
+ * Return: 0 (success).
+ */
+static int ufshcd_sdev_configure(struct scsi_device *sdev,
+ struct queue_limits *lim)
+{
+ struct ufs_hba *hba = shost_priv(sdev->host);
+ struct request_queue *q = sdev->request_queue;
+
+ lim->dma_pad_mask = PRDT_DATA_BYTE_COUNT_PAD - 1;
+
+ /*
+ * Block runtime-pm until all consumers are added.
+ * Refer ufshcd_setup_links().
+ */
+ if (is_device_wlun(sdev))
+ pm_runtime_get_noresume(&sdev->sdev_gendev);
+ else if (ufshcd_is_rpm_autosuspend_allowed(hba))
+ sdev->rpm_autosuspend = 1;
+ /*
+ * Do not print messages during runtime PM to avoid never-ending cycles
+ * of messages written back to storage by user space causing runtime
+ * resume, causing more messages and so on.
+ */
+ sdev->silence_suspend = 1;
+
+ if (hba->vops && hba->vops->config_scsi_dev)
+ hba->vops->config_scsi_dev(sdev);
+
+ ufshcd_crypto_register(hba, q);
+
+ return 0;
+}
+
+/**
+ * ufshcd_sdev_destroy - remove SCSI device configurations
+ * @sdev: pointer to SCSI device
+ */
+static void ufshcd_sdev_destroy(struct scsi_device *sdev)
+{
+ struct ufs_hba *hba;
+ unsigned long flags;
+
+ hba = shost_priv(sdev->host);
+
+ /* Drop the reference as it won't be needed anymore */
+ if (ufshcd_scsi_to_upiu_lun(sdev->lun) == UFS_UPIU_UFS_DEVICE_WLUN) {
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ hba->ufs_device_wlun = NULL;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ } else if (hba->ufs_device_wlun) {
+ struct device *supplier = NULL;
+
+ /* Ensure UFS Device WLUN exists and does not disappear */
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (hba->ufs_device_wlun) {
+ supplier = &hba->ufs_device_wlun->sdev_gendev;
+ get_device(supplier);
+ }
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ if (supplier) {
+ /*
+ * If a LUN fails to probe (e.g. absent BOOT WLUN), the
+ * device will not have been registered but can still
+ * have a device link holding a reference to the device.
+ */
+ device_link_remove(&sdev->sdev_gendev, supplier);
+ put_device(supplier);
+ }
+ }
+}
+
+/**
+ * ufshcd_scsi_cmd_status - Update SCSI command result based on SCSI status
+ * @cmd: SCSI command
+ * @scsi_status: SCSI command status
+ *
+ * Return: value base on SCSI command status.
+ */
+static inline int ufshcd_scsi_cmd_status(struct scsi_cmnd *cmd, int scsi_status)
+{
+ int result = 0;
+
+ switch (scsi_status) {
+ case SAM_STAT_CHECK_CONDITION:
+ ufshcd_copy_sense_data(cmd);
+ fallthrough;
+ case SAM_STAT_GOOD:
+ result |= DID_OK << 16 | scsi_status;
+ break;
+ case SAM_STAT_TASK_SET_FULL:
+ case SAM_STAT_BUSY:
+ case SAM_STAT_TASK_ABORTED:
+ ufshcd_copy_sense_data(cmd);
+ result |= scsi_status;
+ break;
+ default:
+ result |= DID_ERROR << 16;
+ break;
+ } /* end of switch */
+
+ return result;
+}
+
+/**
+ * ufshcd_transfer_rsp_status - Get overall status of the response
+ * @hba: per adapter instance
+ * @cmd: SCSI command
+ * @cqe: pointer to the completion queue entry
+ *
+ * Return: result of the command to notify SCSI midlayer.
+ */
+static inline int ufshcd_transfer_rsp_status(struct ufs_hba *hba,
+ struct scsi_cmnd *cmd,
+ struct cq_entry *cqe)
+{
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ const int tag = scsi_cmd_to_rq(cmd)->tag;
+ int result = 0;
+ int scsi_status;
+ enum utp_ocs ocs;
+ u8 upiu_flags;
+ u32 resid;
+
+ upiu_flags = lrbp->ucd_rsp_ptr->header.flags;
+ resid = be32_to_cpu(lrbp->ucd_rsp_ptr->sr.residual_transfer_count);
+ /*
+ * Test !overflow instead of underflow to support UFS devices that do
+ * not set either flag.
+ */
+ if (resid && !(upiu_flags & UPIU_RSP_FLAG_OVERFLOW))
+ scsi_set_resid(cmd, resid);
+
+ /* overall command status of utrd */
+ ocs = ufshcd_get_tr_ocs(lrbp, cqe);
+
+ if (hba->quirks & UFSHCD_QUIRK_BROKEN_OCS_FATAL_ERROR) {
+ if (lrbp->ucd_rsp_ptr->header.response ||
+ lrbp->ucd_rsp_ptr->header.status)
+ ocs = OCS_SUCCESS;
+ }
+
+ switch (ocs) {
+ case OCS_SUCCESS:
+ hba->ufs_stats.last_hibern8_exit_tstamp = ktime_set(0, 0);
+ switch (ufshcd_get_req_rsp(lrbp->ucd_rsp_ptr)) {
+ case UPIU_TRANSACTION_RESPONSE:
+ /*
+ * get the result based on SCSI status response
+ * to notify the SCSI midlayer of the command status
+ */
+ scsi_status = lrbp->ucd_rsp_ptr->header.status;
+ result = ufshcd_scsi_cmd_status(cmd, scsi_status);
+
+ /*
+ * Currently we are only supporting BKOPs exception
+ * events hence we can ignore BKOPs exception event
+ * during power management callbacks. BKOPs exception
+ * event is not expected to be raised in runtime suspend
+ * callback as it allows the urgent bkops.
+ * During system suspend, we are anyway forcefully
+ * disabling the bkops and if urgent bkops is needed
+ * it will be enabled on system resume. Long term
+ * solution could be to abort the system suspend if
+ * UFS device needs urgent BKOPs.
+ */
+ if (!hba->pm_op_in_progress &&
+ !ufshcd_eh_in_progress(hba) &&
+ ufshcd_is_exception_event(lrbp->ucd_rsp_ptr))
+ /* Flushed in suspend */
+ schedule_work(&hba->eeh_work);
+ break;
+ case UPIU_TRANSACTION_REJECT_UPIU:
+ /* TODO: handle Reject UPIU Response */
+ result = DID_ERROR << 16;
+ dev_err(hba->dev,
+ "Reject UPIU not fully implemented\n");
+ break;
+ default:
+ dev_err(hba->dev,
+ "Unexpected request response code = %x\n",
+ ufshcd_get_req_rsp(lrbp->ucd_rsp_ptr));
+ result = DID_ERROR << 16;
+ break;
+ }
+ break;
+ case OCS_ABORTED:
+ case OCS_INVALID_COMMAND_STATUS:
+ result |= DID_REQUEUE << 16;
+ dev_warn(hba->dev, "OCS %s from controller for tag %d\n",
+ ocs == OCS_ABORTED ? "aborted" : "invalid", tag);
+ break;
+ case OCS_INVALID_CMD_TABLE_ATTR:
+ case OCS_INVALID_PRDT_ATTR:
+ case OCS_MISMATCH_DATA_BUF_SIZE:
+ case OCS_MISMATCH_RESP_UPIU_SIZE:
+ case OCS_PEER_COMM_FAILURE:
+ case OCS_FATAL_ERROR:
+ case OCS_DEVICE_FATAL_ERROR:
+ case OCS_INVALID_CRYPTO_CONFIG:
+ case OCS_GENERAL_CRYPTO_ERROR:
+ default:
+ result |= DID_ERROR << 16;
+ dev_err(hba->dev, "OCS error from controller = %x for tag %d\n",
+ ocs, tag);
+ ufshcd_print_evt_hist(hba);
+ ufshcd_print_host_state(hba);
+ break;
+ } /* end of switch */
+
+ if ((host_byte(result) != DID_OK) &&
+ (host_byte(result) != DID_REQUEUE) && !hba->silence_err_logs) {
+ if (cqe)
+ ufshcd_hex_dump("UPIU CQE: ", cqe, sizeof(struct cq_entry));
+ ufshcd_print_tr(hba, cmd, true);
+ }
+ return result;
+}
+
+static bool ufshcd_is_auto_hibern8_error(struct ufs_hba *hba,
+ u32 intr_mask)
+{
+ if (!ufshcd_is_auto_hibern8_supported(hba) ||
+ !ufshcd_is_auto_hibern8_enabled(hba))
+ return false;
+
+ if (!(intr_mask & UFSHCD_UIC_HIBERN8_MASK))
+ return false;
+
+ if (hba->active_uic_cmd &&
+ (hba->active_uic_cmd->command == UIC_CMD_DME_HIBER_ENTER ||
+ hba->active_uic_cmd->command == UIC_CMD_DME_HIBER_EXIT))
+ return false;
+
+ return true;
+}
+
+/**
+ * ufshcd_uic_cmd_compl - handle completion of uic command
+ * @hba: per adapter instance
+ * @intr_status: interrupt status generated by the controller
+ *
+ * Return:
+ * IRQ_HANDLED - If interrupt is valid
+ * IRQ_NONE - If invalid interrupt
+ */
+static irqreturn_t ufshcd_uic_cmd_compl(struct ufs_hba *hba, u32 intr_status)
+{
+ irqreturn_t retval = IRQ_NONE;
+ struct uic_command *cmd;
+
+ guard(spinlock_irqsave)(hba->host->host_lock);
+ cmd = hba->active_uic_cmd;
+ if (!cmd) {
+ dev_err(hba->dev,
+ "No active UIC command. Maybe a timeout occurred?\n");
+ return retval;
+ }
+
+ if (ufshcd_is_auto_hibern8_error(hba, intr_status))
+ hba->errors |= (UFSHCD_UIC_HIBERN8_MASK & intr_status);
+
+ if (intr_status & UIC_COMMAND_COMPL) {
+ /*
+ * Store the UIC command result in the lowest byte of
+ * cmd->argument2.
+ */
+ cmd->argument2 |= ufshcd_readl(hba, REG_UIC_COMMAND_ARG_2) &
+ MASK_UIC_COMMAND_RESULT;
+ /* Store the DME attribute value in cmd->argument3. */
+ cmd->argument3 = ufshcd_readl(hba, REG_UIC_COMMAND_ARG_3);
+ if (!hba->uic_async_done)
+ cmd->cmd_active = false;
+ complete(&cmd->done);
+ retval = IRQ_HANDLED;
+ }
+
+ if (intr_status & UFSHCD_UIC_PWR_MASK && hba->uic_async_done) {
+ cmd->cmd_active = false;
+ complete(hba->uic_async_done);
+ retval = IRQ_HANDLED;
+ }
+
+ if (retval == IRQ_HANDLED)
+ ufshcd_add_uic_command_trace(hba, cmd, UFS_CMD_COMP);
+
+ return retval;
+}
+
+/* Release the resources allocated for processing a SCSI command. */
+void ufshcd_release_scsi_cmd(struct ufs_hba *hba, struct scsi_cmnd *cmd)
+{
+ scsi_dma_unmap(cmd);
+ ufshcd_crypto_clear_prdt(hba, cmd);
+ ufshcd_release(hba);
+ ufshcd_clk_scaling_update_busy(hba);
+}
+
+/**
+ * ufshcd_compl_one_cqe - handle a completion queue entry
+ * @hba: per adapter instance
+ * @task_tag: the task tag of the request to be completed
+ * @cqe: pointer to the completion queue entry
+ */
+void ufshcd_compl_one_cqe(struct ufs_hba *hba, int task_tag,
+ struct cq_entry *cqe)
+{
+ struct scsi_cmnd *cmd = ufshcd_tag_to_cmd(hba, task_tag);
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ enum utp_ocs ocs;
+
+ if (WARN_ONCE(!cmd, "invalid completion tag %d, cqe->command_desc_base_addr = %#llx\n",
+ task_tag, cqe ? le64_to_cpu(cqe->command_desc_base_addr) : 0ULL))
+ return;
+
+ if (hba->monitor.enabled) {
+ lrbp->compl_time_stamp = ktime_get();
+ lrbp->compl_time_stamp_local_clock = local_clock();
+ }
+ if (ufshcd_is_scsi_cmd(cmd)) {
+ if (unlikely(ufshcd_should_inform_monitor(hba, cmd)))
+ ufshcd_update_monitor(hba, cmd);
+ ufshcd_add_command_trace(hba, cmd, UFS_CMD_COMP);
+ cmd->result = ufshcd_transfer_rsp_status(hba, cmd, cqe);
+ ufshcd_release_scsi_cmd(hba, cmd);
+ } else {
+ if (cqe) {
+ ocs = cqe->overall_status & MASK_OCS;
+ lrbp->utr_descriptor_ptr->header.ocs = ocs;
+ } else {
+ ocs = lrbp->utr_descriptor_ptr->header.ocs;
+ }
+ ufshcd_add_query_upiu_trace(
+ hba,
+ ocs == OCS_SUCCESS ? UFS_QUERY_COMP : UFS_QUERY_ERR,
+ (struct utp_upiu_req *)lrbp->ucd_rsp_ptr);
+ cmd->result = 0;
+ }
+ /* Do not touch lrbp after scsi_done() has been called. */
+ scsi_done(cmd);
+}
+
+/**
+ * __ufshcd_transfer_req_compl - handle SCSI and query command completion
+ * @hba: per adapter instance
+ * @completed_reqs: bitmask that indicates which requests to complete
+ */
+static void __ufshcd_transfer_req_compl(struct ufs_hba *hba,
+ unsigned long completed_reqs)
+{
+ int tag;
+
+ for_each_set_bit(tag, &completed_reqs, hba->nutrs)
+ ufshcd_compl_one_cqe(hba, tag, NULL);
+}
+
+/* Any value that is not an existing queue number is fine for this constant. */
+enum {
+ UFSHCD_POLL_FROM_INTERRUPT_CONTEXT = -1
+};
+
+static void ufshcd_clear_polled(struct ufs_hba *hba,
+ unsigned long *completed_reqs)
+{
+ int tag;
+
+ for_each_set_bit(tag, completed_reqs, hba->nutrs) {
+ struct scsi_cmnd *cmd = scsi_host_find_tag(hba->host, tag);
+
+ if (!cmd)
+ continue;
+ if (scsi_cmd_to_rq(cmd)->cmd_flags & REQ_POLLED)
+ __clear_bit(tag, completed_reqs);
+ }
+}
+
+/*
+ * Return: > 0 if one or more commands have been completed or 0 if no
+ * requests have been completed.
+ */
+static int ufshcd_poll(struct Scsi_Host *shost, unsigned int queue_num)
+{
+ struct ufs_hba *hba = shost_priv(shost);
+ unsigned long completed_reqs, flags;
+ u32 tr_doorbell;
+ struct ufs_hw_queue *hwq;
+
+ if (hba->mcq_enabled) {
+ hwq = &hba->uhq[queue_num];
+
+ return ufshcd_mcq_poll_cqe_lock(hba, hwq);
+ }
+
+ spin_lock_irqsave(&hba->outstanding_lock, flags);
+ tr_doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
+ completed_reqs = ~tr_doorbell & hba->outstanding_reqs;
+ WARN_ONCE(completed_reqs & ~hba->outstanding_reqs,
+ "completed: %#lx; outstanding: %#lx\n", completed_reqs,
+ hba->outstanding_reqs);
+ if (queue_num == UFSHCD_POLL_FROM_INTERRUPT_CONTEXT) {
+ /* Do not complete polled requests from interrupt context. */
+ ufshcd_clear_polled(hba, &completed_reqs);
+ }
+ hba->outstanding_reqs &= ~completed_reqs;
+ spin_unlock_irqrestore(&hba->outstanding_lock, flags);
+
+ if (completed_reqs)
+ __ufshcd_transfer_req_compl(hba, completed_reqs);
+
+ return completed_reqs != 0;
+}
+
+static bool ufshcd_mcq_force_compl_one(struct request *rq, void *priv)
+{
+ struct scsi_cmnd *cmd = blk_mq_rq_to_pdu(rq);
+ struct scsi_device *sdev = rq->q->queuedata;
+ struct Scsi_Host *shost = sdev->host;
+ struct ufs_hba *hba = shost_priv(shost);
+ struct ufs_hw_queue *hwq = ufshcd_mcq_req_to_hwq(hba, rq);
+
+ if (blk_mq_is_reserved_rq(rq) || !hwq)
+ return true;
+
+ ufshcd_mcq_compl_all_cqes_lock(hba, hwq);
+
+ /*
+ * For those cmds of which the cqes are not present in the cq, complete
+ * them explicitly.
+ */
+ scoped_guard(spinlock_irqsave, &hwq->cq_lock) {
+ if (!test_bit(SCMD_STATE_COMPLETE, &cmd->state)) {
+ set_host_byte(cmd, DID_REQUEUE);
+ ufshcd_release_scsi_cmd(hba, cmd);
+ scsi_done(cmd);
+ }
+ }
+
+ return true;
+}
+
+static bool ufshcd_mcq_compl_one(struct request *rq, void *priv)
+{
+ struct scsi_device *sdev = rq->q->queuedata;
+ struct Scsi_Host *shost = sdev->host;
+ struct ufs_hba *hba = shost_priv(shost);
+ struct ufs_hw_queue *hwq = ufshcd_mcq_req_to_hwq(hba, rq);
+
+ if (!blk_mq_is_reserved_rq(rq) && hwq)
+ ufshcd_mcq_poll_cqe_lock(hba, hwq);
+
+ return true;
+}
+
+/**
+ * ufshcd_mcq_compl_pending_transfer - MCQ mode function. It is
+ * invoked from the error handler context or ufshcd_host_reset_and_restore()
+ * to complete the pending transfers and free the resources associated with
+ * the scsi command.
+ *
+ * @hba: per adapter instance
+ * @force_compl: This flag is set to true when invoked
+ * from ufshcd_host_reset_and_restore() in which case it requires special
+ * handling because the host controller has been reset by ufshcd_hba_stop().
+ */
+static void ufshcd_mcq_compl_pending_transfer(struct ufs_hba *hba,
+ bool force_compl)
+{
+ blk_mq_tagset_busy_iter(&hba->host->tag_set,
+ force_compl ? ufshcd_mcq_force_compl_one :
+ ufshcd_mcq_compl_one,
+ NULL);
+}
+
+/**
+ * ufshcd_transfer_req_compl - handle SCSI and query command completion
+ * @hba: per adapter instance
+ *
+ * Return:
+ * IRQ_HANDLED - If interrupt is valid
+ * IRQ_NONE - If invalid interrupt
+ */
+static irqreturn_t ufshcd_transfer_req_compl(struct ufs_hba *hba)
+{
+ /* Resetting interrupt aggregation counters first and reading the
+ * DOOR_BELL afterward allows us to handle all the completed requests.
+ * In order to prevent other interrupts starvation the DB is read once
+ * after reset. The down side of this solution is the possibility of
+ * false interrupt if device completes another request after resetting
+ * aggregation and before reading the DB.
+ */
+ if (ufshcd_is_intr_aggr_allowed(hba) &&
+ !(hba->quirks & UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR))
+ ufshcd_reset_intr_aggr(hba);
+
+ if (ufs_fail_completion(hba))
+ return IRQ_HANDLED;
+
+ /*
+ * Ignore the ufshcd_poll() return value and return IRQ_HANDLED since we
+ * do not want polling to trigger spurious interrupt complaints.
+ */
+ ufshcd_poll(hba->host, UFSHCD_POLL_FROM_INTERRUPT_CONTEXT);
+
+ return IRQ_HANDLED;
+}
+
+int __ufshcd_write_ee_control(struct ufs_hba *hba, u32 ee_ctrl_mask)
+{
+ return ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_EE_CONTROL, 0, 0,
+ &ee_ctrl_mask);
+}
+
+int ufshcd_write_ee_control(struct ufs_hba *hba)
+{
+ int err;
+
+ mutex_lock(&hba->ee_ctrl_mutex);
+ err = __ufshcd_write_ee_control(hba, hba->ee_ctrl_mask);
+ mutex_unlock(&hba->ee_ctrl_mutex);
+ if (err)
+ dev_err(hba->dev, "%s: failed to write ee control %d\n",
+ __func__, err);
+ return err;
+}
+
+int ufshcd_update_ee_control(struct ufs_hba *hba, u16 *mask,
+ const u16 *other_mask, u16 set, u16 clr)
+{
+ u16 new_mask, ee_ctrl_mask;
+ int err = 0;
+
+ mutex_lock(&hba->ee_ctrl_mutex);
+ new_mask = (*mask & ~clr) | set;
+ ee_ctrl_mask = new_mask | *other_mask;
+ if (ee_ctrl_mask != hba->ee_ctrl_mask)
+ err = __ufshcd_write_ee_control(hba, ee_ctrl_mask);
+ /* Still need to update 'mask' even if 'ee_ctrl_mask' was unchanged */
+ if (!err) {
+ hba->ee_ctrl_mask = ee_ctrl_mask;
+ *mask = new_mask;
+ }
+ mutex_unlock(&hba->ee_ctrl_mutex);
+ return err;
+}
+
+/**
+ * ufshcd_disable_ee - disable exception event
+ * @hba: per-adapter instance
+ * @mask: exception event to disable
+ *
+ * Disables exception event in the device so that the EVENT_ALERT
+ * bit is not set.
+ *
+ * Return: zero on success, non-zero error value on failure.
+ */
+static inline int ufshcd_disable_ee(struct ufs_hba *hba, u16 mask)
+{
+ return ufshcd_update_ee_drv_mask(hba, 0, mask);
+}
+
+/**
+ * ufshcd_enable_ee - enable exception event
+ * @hba: per-adapter instance
+ * @mask: exception event to enable
+ *
+ * Enable corresponding exception event in the device to allow
+ * device to alert host in critical scenarios.
+ *
+ * Return: zero on success, non-zero error value on failure.
+ */
+static inline int ufshcd_enable_ee(struct ufs_hba *hba, u16 mask)
+{
+ return ufshcd_update_ee_drv_mask(hba, mask, 0);
+}
+
+/**
+ * ufshcd_enable_auto_bkops - Allow device managed BKOPS
+ * @hba: per-adapter instance
+ *
+ * Allow device to manage background operations on its own. Enabling
+ * this might lead to inconsistent latencies during normal data transfers
+ * as the device is allowed to manage its own way of handling background
+ * operations.
+ *
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+static int ufshcd_enable_auto_bkops(struct ufs_hba *hba)
+{
+ int err = 0;
+
+ if (hba->auto_bkops_enabled)
+ goto out;
+
+ err = ufshcd_query_flag_retry(hba, UPIU_QUERY_OPCODE_SET_FLAG,
+ QUERY_FLAG_IDN_BKOPS_EN, 0, NULL);
+ if (err) {
+ dev_err(hba->dev, "%s: failed to enable bkops %d\n",
+ __func__, err);
+ goto out;
+ }
+
+ hba->auto_bkops_enabled = true;
+ trace_ufshcd_auto_bkops_state(hba, "Enabled");
+
+ /* No need of URGENT_BKOPS exception from the device */
+ err = ufshcd_disable_ee(hba, MASK_EE_URGENT_BKOPS);
+ if (err)
+ dev_err(hba->dev, "%s: failed to disable exception event %d\n",
+ __func__, err);
+out:
+ return err;
+}
+
+/**
+ * ufshcd_disable_auto_bkops - block device in doing background operations
+ * @hba: per-adapter instance
+ *
+ * Disabling background operations improves command response latency but
+ * has drawback of device moving into critical state where the device is
+ * not-operable. Make sure to call ufshcd_enable_auto_bkops() whenever the
+ * host is idle so that BKOPS are managed effectively without any negative
+ * impacts.
+ *
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+static int ufshcd_disable_auto_bkops(struct ufs_hba *hba)
+{
+ int err = 0;
+
+ if (!hba->auto_bkops_enabled)
+ goto out;
+
+ /*
+ * If host assisted BKOPs is to be enabled, make sure
+ * urgent bkops exception is allowed.
+ */
+ err = ufshcd_enable_ee(hba, MASK_EE_URGENT_BKOPS);
+ if (err) {
+ dev_err(hba->dev, "%s: failed to enable exception event %d\n",
+ __func__, err);
+ goto out;
+ }
+
+ err = ufshcd_query_flag_retry(hba, UPIU_QUERY_OPCODE_CLEAR_FLAG,
+ QUERY_FLAG_IDN_BKOPS_EN, 0, NULL);
+ if (err) {
+ dev_err(hba->dev, "%s: failed to disable bkops %d\n",
+ __func__, err);
+ ufshcd_disable_ee(hba, MASK_EE_URGENT_BKOPS);
+ goto out;
+ }
+
+ hba->auto_bkops_enabled = false;
+ trace_ufshcd_auto_bkops_state(hba, "Disabled");
+ hba->urgent_bkops_lvl = BKOPS_STATUS_PERF_IMPACT;
+ hba->is_urgent_bkops_lvl_checked = false;
+out:
+ return err;
+}
+
+/**
+ * ufshcd_force_reset_auto_bkops - force reset auto bkops state
+ * @hba: per adapter instance
+ *
+ * After a device reset the device may toggle the BKOPS_EN flag
+ * to default value. The s/w tracking variables should be updated
+ * as well. This function would change the auto-bkops state based on
+ * UFSHCD_CAP_KEEP_AUTO_BKOPS_ENABLED_EXCEPT_SUSPEND.
+ */
+static void ufshcd_force_reset_auto_bkops(struct ufs_hba *hba)
+{
+ if (ufshcd_keep_autobkops_enabled_except_suspend(hba)) {
+ hba->auto_bkops_enabled = false;
+ hba->ee_ctrl_mask |= MASK_EE_URGENT_BKOPS;
+ ufshcd_enable_auto_bkops(hba);
+ } else {
+ hba->auto_bkops_enabled = true;
+ hba->ee_ctrl_mask &= ~MASK_EE_URGENT_BKOPS;
+ ufshcd_disable_auto_bkops(hba);
+ }
+ hba->urgent_bkops_lvl = BKOPS_STATUS_PERF_IMPACT;
+ hba->is_urgent_bkops_lvl_checked = false;
+}
+
+static inline int ufshcd_get_bkops_status(struct ufs_hba *hba, u32 *status)
+{
+ return ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_BKOPS_STATUS, 0, 0, status);
+}
+
+/**
+ * ufshcd_bkops_ctrl - control the auto bkops based on current bkops status
+ * @hba: per-adapter instance
+ *
+ * Read the bkops_status from the UFS device and Enable fBackgroundOpsEn
+ * flag in the device to permit background operations if the device
+ * bkops_status is greater than or equal to the "hba->urgent_bkops_lvl",
+ * disable otherwise.
+ *
+ * Return: 0 for success, non-zero in case of failure.
+ *
+ * NOTE: Caller of this function can check the "hba->auto_bkops_enabled" flag
+ * to know whether auto bkops is enabled or disabled after this function
+ * returns control to it.
+ */
+static int ufshcd_bkops_ctrl(struct ufs_hba *hba)
+{
+ enum bkops_status status = hba->urgent_bkops_lvl;
+ u32 curr_status = 0;
+ int err;
+
+ err = ufshcd_get_bkops_status(hba, &curr_status);
+ if (err) {
+ dev_err(hba->dev, "%s: failed to get BKOPS status %d\n",
+ __func__, err);
+ goto out;
+ } else if (curr_status > BKOPS_STATUS_MAX) {
+ dev_err(hba->dev, "%s: invalid BKOPS status %d\n",
+ __func__, curr_status);
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (curr_status >= status)
+ err = ufshcd_enable_auto_bkops(hba);
+ else
+ err = ufshcd_disable_auto_bkops(hba);
+out:
+ return err;
+}
+
+static inline int ufshcd_get_ee_status(struct ufs_hba *hba, u32 *status)
+{
+ return ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_EE_STATUS, 0, 0, status);
+}
+
+static void ufshcd_bkops_exception_event_handler(struct ufs_hba *hba)
+{
+ int err;
+ u32 curr_status = 0;
+
+ if (hba->is_urgent_bkops_lvl_checked)
+ goto enable_auto_bkops;
+
+ err = ufshcd_get_bkops_status(hba, &curr_status);
+ if (err) {
+ dev_err(hba->dev, "%s: failed to get BKOPS status %d\n",
+ __func__, err);
+ goto out;
+ }
+
+ /*
+ * We are seeing that some devices are raising the urgent bkops
+ * exception events even when BKOPS status doesn't indicate performace
+ * impacted or critical. Handle these device by determining their urgent
+ * bkops status at runtime.
+ */
+ if ((curr_status > BKOPS_STATUS_NO_OP) && (curr_status < BKOPS_STATUS_PERF_IMPACT)) {
+ dev_err(hba->dev, "%s: device raised urgent BKOPS exception for bkops status %d\n",
+ __func__, curr_status);
+ /* update the current status as the urgent bkops level */
+ hba->urgent_bkops_lvl = curr_status;
+ hba->is_urgent_bkops_lvl_checked = true;
+ }
+
+enable_auto_bkops:
+ err = ufshcd_enable_auto_bkops(hba);
+out:
+ if (err < 0)
+ dev_err(hba->dev, "%s: failed to handle urgent bkops %d\n",
+ __func__, err);
+}
+
+static int __ufshcd_wb_toggle(struct ufs_hba *hba, bool set, enum flag_idn idn)
+{
+ u8 index;
+ enum query_opcode opcode = set ? UPIU_QUERY_OPCODE_SET_FLAG :
+ UPIU_QUERY_OPCODE_CLEAR_FLAG;
+
+ index = ufshcd_wb_get_query_index(hba);
+ return ufshcd_query_flag_retry(hba, opcode, idn, index, NULL);
+}
+
+int ufshcd_wb_toggle(struct ufs_hba *hba, bool enable)
+{
+ int ret;
+
+ if (!ufshcd_is_wb_allowed(hba) ||
+ hba->dev_info.wb_enabled == enable)
+ return 0;
+
+ ret = __ufshcd_wb_toggle(hba, enable, QUERY_FLAG_IDN_WB_EN);
+ if (ret) {
+ dev_err(hba->dev, "%s: Write Booster %s failed %d\n",
+ __func__, enable ? "enabling" : "disabling", ret);
+ return ret;
+ }
+
+ hba->dev_info.wb_enabled = enable;
+ dev_dbg(hba->dev, "%s: Write Booster %s\n",
+ __func__, enable ? "enabled" : "disabled");
+
+ return ret;
+}
+
+static void ufshcd_wb_toggle_buf_flush_during_h8(struct ufs_hba *hba,
+ bool enable)
+{
+ int ret;
+
+ ret = __ufshcd_wb_toggle(hba, enable,
+ QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8);
+ if (ret) {
+ dev_err(hba->dev, "%s: WB-Buf Flush during H8 %s failed %d\n",
+ __func__, enable ? "enabling" : "disabling", ret);
+ return;
+ }
+ dev_dbg(hba->dev, "%s: WB-Buf Flush during H8 %s\n",
+ __func__, enable ? "enabled" : "disabled");
+}
+
+int ufshcd_wb_toggle_buf_flush(struct ufs_hba *hba, bool enable)
+{
+ int ret;
+
+ if (!ufshcd_is_wb_allowed(hba) ||
+ hba->dev_info.wb_buf_flush_enabled == enable)
+ return 0;
+
+ ret = __ufshcd_wb_toggle(hba, enable, QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN);
+ if (ret) {
+ dev_err(hba->dev, "%s: WB-Buf Flush %s failed %d\n",
+ __func__, enable ? "enabling" : "disabling", ret);
+ return ret;
+ }
+
+ hba->dev_info.wb_buf_flush_enabled = enable;
+ dev_dbg(hba->dev, "%s: WB-Buf Flush %s\n",
+ __func__, enable ? "enabled" : "disabled");
+
+ return ret;
+}
+
+int ufshcd_wb_set_resize_en(struct ufs_hba *hba, enum wb_resize_en en_mode)
+{
+ int ret;
+ u8 index;
+
+ index = ufshcd_wb_get_query_index(hba);
+ ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_WB_BUF_RESIZE_EN, index, 0, &en_mode);
+ if (ret)
+ dev_err(hba->dev, "%s: Enable WB buf resize operation failed %d\n",
+ __func__, ret);
+
+ return ret;
+}
+
+static bool ufshcd_wb_curr_buff_threshold_check(struct ufs_hba *hba,
+ u32 avail_buf)
+{
+ u32 cur_buf;
+ int ret;
+ u8 index;
+
+ index = ufshcd_wb_get_query_index(hba);
+ ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE,
+ index, 0, &cur_buf);
+ if (ret) {
+ dev_err(hba->dev, "%s: dCurWriteBoosterBufferSize read failed %d\n",
+ __func__, ret);
+ return false;
+ }
+
+ if (!cur_buf) {
+ dev_warn_once(hba->dev, "dCurWBBuf: %d WB disabled until free-space is available\n",
+ cur_buf);
+ return false;
+ }
+ /* Let it continue to flush when available buffer exceeds threshold */
+ return avail_buf < hba->vps->wb_flush_threshold;
+}
+
+static void ufshcd_wb_force_disable(struct ufs_hba *hba)
+{
+ if (ufshcd_is_wb_buf_flush_allowed(hba))
+ ufshcd_wb_toggle_buf_flush(hba, false);
+
+ ufshcd_wb_toggle_buf_flush_during_h8(hba, false);
+ ufshcd_wb_toggle(hba, false);
+ hba->caps &= ~UFSHCD_CAP_WB_EN;
+
+ dev_info(hba->dev, "%s: WB force disabled\n", __func__);
+}
+
+static bool ufshcd_is_wb_buf_lifetime_available(struct ufs_hba *hba)
+{
+ u32 lifetime;
+ int ret;
+ u8 index;
+
+ index = ufshcd_wb_get_query_index(hba);
+ ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST,
+ index, 0, &lifetime);
+ if (ret) {
+ dev_err(hba->dev,
+ "%s: bWriteBoosterBufferLifeTimeEst read failed %d\n",
+ __func__, ret);
+ return false;
+ }
+
+ if (lifetime == UFS_WB_EXCEED_LIFETIME) {
+ dev_err(hba->dev, "%s: WB buf lifetime is exhausted 0x%02X\n",
+ __func__, lifetime);
+ return false;
+ }
+
+ dev_dbg(hba->dev, "%s: WB buf lifetime is 0x%02X\n",
+ __func__, lifetime);
+
+ return true;
+}
+
+static bool ufshcd_wb_need_flush(struct ufs_hba *hba)
+{
+ int ret;
+ u32 avail_buf;
+ u8 index;
+
+ if (!ufshcd_is_wb_allowed(hba))
+ return false;
+
+ if (!ufshcd_is_wb_buf_lifetime_available(hba)) {
+ ufshcd_wb_force_disable(hba);
+ return false;
+ }
+
+ /*
+ * With user-space reduction enabled, it's enough to enable flush
+ * by checking only the available buffer. The threshold
+ * defined here is > 90% full.
+ * With user-space preserved enabled, the current-buffer
+ * should be checked too because the wb buffer size can reduce
+ * when disk tends to be full. This info is provided by current
+ * buffer (dCurrentWriteBoosterBufferSize).
+ */
+ index = ufshcd_wb_get_query_index(hba);
+ ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE,
+ index, 0, &avail_buf);
+ if (ret) {
+ dev_warn(hba->dev, "%s: dAvailableWriteBoosterBufferSize read failed %d\n",
+ __func__, ret);
+ return false;
+ }
+
+ if (!hba->dev_info.b_presrv_uspc_en)
+ return avail_buf <= UFS_WB_BUF_REMAIN_PERCENT(10);
+
+ return ufshcd_wb_curr_buff_threshold_check(hba, avail_buf);
+}
+
+static void ufshcd_rpm_dev_flush_recheck_work(struct work_struct *work)
+{
+ struct ufs_hba *hba = container_of(to_delayed_work(work),
+ struct ufs_hba,
+ rpm_dev_flush_recheck_work);
+ /*
+ * To prevent unnecessary VCC power drain after device finishes
+ * WriteBooster buffer flush or Auto BKOPs, force runtime resume
+ * after a certain delay to recheck the threshold by next runtime
+ * suspend.
+ */
+ ufshcd_rpm_get_sync(hba);
+ ufshcd_rpm_put_sync(hba);
+}
+
+/**
+ * ufshcd_exception_event_handler - handle exceptions raised by device
+ * @work: pointer to work data
+ *
+ * Read bExceptionEventStatus attribute from the device and handle the
+ * exception event accordingly.
+ */
+static void ufshcd_exception_event_handler(struct work_struct *work)
+{
+ struct ufs_hba *hba;
+ int err;
+ u32 status = 0;
+ hba = container_of(work, struct ufs_hba, eeh_work);
+
+ err = ufshcd_get_ee_status(hba, &status);
+ if (err) {
+ dev_err(hba->dev, "%s: failed to get exception status %d\n",
+ __func__, err);
+ return;
+ }
+
+ trace_ufshcd_exception_event(hba, status);
+
+ if (status & hba->ee_drv_mask & MASK_EE_URGENT_BKOPS)
+ ufshcd_bkops_exception_event_handler(hba);
+
+ if (status & hba->ee_drv_mask & MASK_EE_URGENT_TEMP)
+ ufs_hwmon_notify_event(hba, status & MASK_EE_URGENT_TEMP);
+
+ if (status & hba->ee_drv_mask & MASK_EE_HEALTH_CRITICAL) {
+ hba->critical_health_count++;
+ sysfs_notify(&hba->dev->kobj, NULL, "critical_health");
+ }
+
+ if (status & hba->ee_drv_mask & MASK_EE_DEV_LVL_EXCEPTION) {
+ atomic_inc(&hba->dev_lvl_exception_count);
+ sysfs_notify(&hba->dev->kobj, NULL, "device_lvl_exception_count");
+ }
+
+ ufs_debugfs_exception_event(hba, status);
+}
+
+/* Complete requests that have door-bell cleared */
+static void ufshcd_complete_requests(struct ufs_hba *hba, bool force_compl)
+{
+ if (hba->mcq_enabled)
+ ufshcd_mcq_compl_pending_transfer(hba, force_compl);
+ else
+ ufshcd_transfer_req_compl(hba);
+
+ ufshcd_tmc_handler(hba);
+}
+
+/**
+ * ufshcd_quirk_dl_nac_errors - This function checks if error handling is
+ * to recover from the DL NAC errors or not.
+ * @hba: per-adapter instance
+ *
+ * Return: true if error handling is required, false otherwise.
+ */
+static bool ufshcd_quirk_dl_nac_errors(struct ufs_hba *hba)
+{
+ unsigned long flags;
+ bool err_handling = true;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ /*
+ * UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS only workaround the
+ * device fatal error and/or DL NAC & REPLAY timeout errors.
+ */
+ if (hba->saved_err & (CONTROLLER_FATAL_ERROR | SYSTEM_BUS_FATAL_ERROR))
+ goto out;
+
+ if ((hba->saved_err & DEVICE_FATAL_ERROR) ||
+ ((hba->saved_err & UIC_ERROR) &&
+ (hba->saved_uic_err & UFSHCD_UIC_DL_TCx_REPLAY_ERROR)))
+ goto out;
+
+ if ((hba->saved_err & UIC_ERROR) &&
+ (hba->saved_uic_err & UFSHCD_UIC_DL_NAC_RECEIVED_ERROR)) {
+ int err;
+ /*
+ * wait for 50ms to see if we can get any other errors or not.
+ */
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ msleep(50);
+ spin_lock_irqsave(hba->host->host_lock, flags);
+
+ /*
+ * now check if we have got any other severe errors other than
+ * DL NAC error?
+ */
+ if ((hba->saved_err & INT_FATAL_ERRORS) ||
+ ((hba->saved_err & UIC_ERROR) &&
+ (hba->saved_uic_err & ~UFSHCD_UIC_DL_NAC_RECEIVED_ERROR)))
+ goto out;
+
+ /*
+ * As DL NAC is the only error received so far, send out NOP
+ * command to confirm if link is still active or not.
+ * - If we don't get any response then do error recovery.
+ * - If we get response then clear the DL NAC error bit.
+ */
+
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ err = ufshcd_verify_dev_init(hba);
+ spin_lock_irqsave(hba->host->host_lock, flags);
+
+ if (err)
+ goto out;
+
+ /* Link seems to be alive hence ignore the DL NAC errors */
+ if (hba->saved_uic_err == UFSHCD_UIC_DL_NAC_RECEIVED_ERROR)
+ hba->saved_err &= ~UIC_ERROR;
+ /* clear NAC error */
+ hba->saved_uic_err &= ~UFSHCD_UIC_DL_NAC_RECEIVED_ERROR;
+ if (!hba->saved_uic_err)
+ err_handling = false;
+ }
+out:
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ return err_handling;
+}
+
+/* host lock must be held before calling this func */
+static inline bool ufshcd_is_saved_err_fatal(struct ufs_hba *hba)
+{
+ return (hba->saved_uic_err & UFSHCD_UIC_DL_PA_INIT_ERROR) ||
+ (hba->saved_err & (INT_FATAL_ERRORS | UFSHCD_UIC_HIBERN8_MASK));
+}
+
+void ufshcd_schedule_eh_work(struct ufs_hba *hba)
+{
+ lockdep_assert_held(hba->host->host_lock);
+
+ /* handle fatal errors only when link is not in error state */
+ if (hba->ufshcd_state != UFSHCD_STATE_ERROR) {
+ if (hba->force_reset || ufshcd_is_link_broken(hba) ||
+ ufshcd_is_saved_err_fatal(hba))
+ hba->ufshcd_state = UFSHCD_STATE_EH_SCHEDULED_FATAL;
+ else
+ hba->ufshcd_state = UFSHCD_STATE_EH_SCHEDULED_NON_FATAL;
+ queue_work(hba->eh_wq, &hba->eh_work);
+ }
+}
+
+void ufshcd_force_error_recovery(struct ufs_hba *hba)
+{
+ spin_lock_irq(hba->host->host_lock);
+ hba->force_reset = true;
+ ufshcd_schedule_eh_work(hba);
+ spin_unlock_irq(hba->host->host_lock);
+}
+EXPORT_SYMBOL_GPL(ufshcd_force_error_recovery);
+
+static void ufshcd_clk_scaling_allow(struct ufs_hba *hba, bool allow)
+{
+ mutex_lock(&hba->wb_mutex);
+ down_write(&hba->clk_scaling_lock);
+ hba->clk_scaling.is_allowed = allow;
+ up_write(&hba->clk_scaling_lock);
+ mutex_unlock(&hba->wb_mutex);
+}
+
+static void ufshcd_clk_scaling_suspend(struct ufs_hba *hba, bool suspend)
+{
+ if (suspend) {
+ if (hba->clk_scaling.is_enabled)
+ ufshcd_suspend_clkscaling(hba);
+ ufshcd_clk_scaling_allow(hba, false);
+ } else {
+ ufshcd_clk_scaling_allow(hba, true);
+ if (hba->clk_scaling.is_enabled)
+ ufshcd_resume_clkscaling(hba);
+ }
+}
+
+static void ufshcd_err_handling_prepare(struct ufs_hba *hba)
+{
+ /*
+ * A WLUN resume failure could potentially lead to the HBA being
+ * runtime suspended, so take an extra reference on hba->dev.
+ */
+ pm_runtime_get_sync(hba->dev);
+ ufshcd_rpm_get_sync(hba);
+ if (pm_runtime_status_suspended(&hba->ufs_device_wlun->sdev_gendev) ||
+ hba->is_sys_suspended) {
+ enum ufs_pm_op pm_op;
+
+ /*
+ * Don't assume anything of resume, if
+ * resume fails, irq and clocks can be OFF, and powers
+ * can be OFF or in LPM.
+ */
+ ufshcd_setup_hba_vreg(hba, true);
+ ufshcd_enable_irq(hba);
+ ufshcd_setup_vreg(hba, true);
+ ufshcd_config_vreg_hpm(hba, hba->vreg_info.vccq);
+ ufshcd_config_vreg_hpm(hba, hba->vreg_info.vccq2);
+ ufshcd_hold(hba);
+ if (!ufshcd_is_clkgating_allowed(hba))
+ ufshcd_setup_clocks(hba, true);
+ pm_op = hba->is_sys_suspended ? UFS_SYSTEM_PM : UFS_RUNTIME_PM;
+ ufshcd_vops_resume(hba, pm_op);
+ } else {
+ ufshcd_hold(hba);
+ if (ufshcd_is_clkscaling_supported(hba) &&
+ hba->clk_scaling.is_enabled)
+ ufshcd_suspend_clkscaling(hba);
+ ufshcd_clk_scaling_allow(hba, false);
+ }
+ /* Wait for ongoing ufshcd_queuecommand() calls to finish. */
+ blk_mq_quiesce_tagset(&hba->host->tag_set);
+ cancel_work_sync(&hba->eeh_work);
+}
+
+static void ufshcd_err_handling_unprepare(struct ufs_hba *hba)
+{
+ blk_mq_unquiesce_tagset(&hba->host->tag_set);
+ ufshcd_release(hba);
+ if (ufshcd_is_clkscaling_supported(hba))
+ ufshcd_clk_scaling_suspend(hba, false);
+ ufshcd_rpm_put(hba);
+ pm_runtime_put(hba->dev);
+}
+
+static inline bool ufshcd_err_handling_should_stop(struct ufs_hba *hba)
+{
+ return (!hba->is_powered || hba->shutting_down ||
+ !hba->ufs_device_wlun ||
+ hba->ufshcd_state == UFSHCD_STATE_ERROR ||
+ (!(hba->saved_err || hba->saved_uic_err || hba->force_reset ||
+ ufshcd_is_link_broken(hba))));
+}
+
+#ifdef CONFIG_PM
+static void ufshcd_recover_pm_error(struct ufs_hba *hba)
+{
+ struct scsi_target *starget = hba->ufs_device_wlun->sdev_target;
+ struct Scsi_Host *shost = hba->host;
+ struct scsi_device *sdev;
+ struct request_queue *q;
+ bool resume_sdev_queues = false;
+
+ hba->is_sys_suspended = false;
+
+ /*
+ * Ensure the parent's error status is cleared before proceeding
+ * to the child, as the parent must be active to activate the child.
+ */
+ if (hba->dev->power.runtime_error) {
+ /* hba->dev has no functional parent thus simplily set RPM_ACTIVE */
+ pm_runtime_set_active(hba->dev);
+ resume_sdev_queues = true;
+ }
+
+ if (hba->ufs_device_wlun->sdev_gendev.power.runtime_error) {
+ /*
+ * starget, parent of wlun, might be suspended if wlun resume failed.
+ * Make sure parent is resumed before set child (wlun) active.
+ */
+ pm_runtime_get_sync(&starget->dev);
+ pm_runtime_set_active(&hba->ufs_device_wlun->sdev_gendev);
+ pm_runtime_put_sync(&starget->dev);
+ resume_sdev_queues = true;
+ }
+
+ /*
+ * If wlun device had runtime error, we also need to resume those
+ * consumer scsi devices in case any of them has failed to be
+ * resumed due to supplier runtime resume failure. This is to unblock
+ * blk_queue_enter in case there are bios waiting inside it.
+ */
+ if (resume_sdev_queues) {
+ shost_for_each_device(sdev, shost) {
+ q = sdev->request_queue;
+ if (q->dev && (q->rpm_status == RPM_SUSPENDED ||
+ q->rpm_status == RPM_SUSPENDING))
+ pm_request_resume(q->dev);
+ }
+ }
+}
+#else
+static inline void ufshcd_recover_pm_error(struct ufs_hba *hba)
+{
+}
+#endif
+
+static bool ufshcd_is_pwr_mode_restore_needed(struct ufs_hba *hba)
+{
+ struct ufs_pa_layer_attr *pwr_info = &hba->pwr_info;
+ u32 mode;
+
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PWRMODE), &mode);
+
+ if (pwr_info->pwr_rx != ((mode >> PWRMODE_RX_OFFSET) & PWRMODE_MASK))
+ return true;
+
+ if (pwr_info->pwr_tx != (mode & PWRMODE_MASK))
+ return true;
+
+ return false;
+}
+
+static bool ufshcd_abort_one(struct request *rq, void *priv)
+{
+ int *ret = priv;
+ u32 tag = rq->tag;
+ struct scsi_cmnd *cmd = blk_mq_rq_to_pdu(rq);
+ struct scsi_device *sdev = cmd->device;
+ struct Scsi_Host *shost = sdev->host;
+ struct ufs_hba *hba = shost_priv(shost);
+
+ if (blk_mq_is_reserved_rq(rq))
+ return true;
+
+ *ret = ufshcd_try_to_abort_task(hba, tag);
+ dev_err(hba->dev, "Aborting tag %d / CDB %#02x %s\n", tag,
+ ufshcd_is_scsi_cmd(cmd) ? cmd->cmnd[0] : -1,
+ *ret ? "failed" : "succeeded");
+
+ return *ret == 0;
+}
+
+/**
+ * ufshcd_abort_all - Abort all pending commands.
+ * @hba: Host bus adapter pointer.
+ *
+ * Return: true if and only if the host controller needs to be reset.
+ */
+static bool ufshcd_abort_all(struct ufs_hba *hba)
+{
+ int tag, ret = 0;
+
+ blk_mq_tagset_busy_iter(&hba->host->tag_set, ufshcd_abort_one, &ret);
+ if (ret)
+ goto out;
+
+ /* Clear pending task management requests */
+ for_each_set_bit(tag, &hba->outstanding_tasks, hba->nutmrs) {
+ ret = ufshcd_clear_tm_cmd(hba, tag);
+ if (ret)
+ goto out;
+ }
+
+out:
+ /* Complete the requests that are cleared by s/w */
+ ufshcd_complete_requests(hba, false);
+
+ return ret != 0;
+}
+
+/**
+ * ufshcd_err_handler - handle UFS errors that require s/w attention
+ * @work: pointer to work structure
+ */
+static void ufshcd_err_handler(struct work_struct *work)
+{
+ int retries = MAX_ERR_HANDLER_RETRIES;
+ struct ufs_hba *hba;
+ unsigned long flags;
+ bool needs_restore;
+ bool needs_reset;
+ int pmc_err;
+
+ hba = container_of(work, struct ufs_hba, eh_work);
+
+ dev_info(hba->dev,
+ "%s started; HBA state %s; powered %d; shutting down %d; saved_err = 0x%x; saved_uic_err = 0x%x; force_reset = %d%s\n",
+ __func__, ufshcd_state_name[hba->ufshcd_state],
+ hba->is_powered, hba->shutting_down, hba->saved_err,
+ hba->saved_uic_err, hba->force_reset,
+ ufshcd_is_link_broken(hba) ? "; link is broken" : "");
+
+ if (hba->ufs_device_wlun) {
+ /*
+ * Use ufshcd_rpm_get_noresume() here to safely perform link
+ * recovery even if an error occurs during runtime suspend or
+ * runtime resume. This avoids potential deadlocks that could
+ * happen if we tried to resume the device while a PM operation
+ * is already in progress.
+ */
+ ufshcd_rpm_get_noresume(hba);
+ if (hba->pm_op_in_progress) {
+ ufshcd_link_recovery(hba);
+ ufshcd_rpm_put(hba);
+ return;
+ }
+ ufshcd_rpm_put(hba);
+ }
+
+ down(&hba->host_sem);
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (ufshcd_err_handling_should_stop(hba)) {
+ if (hba->ufshcd_state != UFSHCD_STATE_ERROR)
+ hba->ufshcd_state = UFSHCD_STATE_OPERATIONAL;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ up(&hba->host_sem);
+ return;
+ }
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ ufshcd_err_handling_prepare(hba);
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ ufshcd_set_eh_in_progress(hba);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ /* Complete requests that have door-bell cleared by h/w */
+ ufshcd_complete_requests(hba, false);
+ spin_lock_irqsave(hba->host->host_lock, flags);
+again:
+ needs_restore = false;
+ needs_reset = false;
+
+ if (hba->ufshcd_state != UFSHCD_STATE_ERROR)
+ hba->ufshcd_state = UFSHCD_STATE_RESET;
+ /*
+ * A full reset and restore might have happened after preparation
+ * is finished, double check whether we should stop.
+ */
+ if (ufshcd_err_handling_should_stop(hba))
+ goto skip_err_handling;
+
+ if ((hba->dev_quirks & UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS) &&
+ !hba->force_reset) {
+ bool ret;
+
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ /* release the lock as ufshcd_quirk_dl_nac_errors() may sleep */
+ ret = ufshcd_quirk_dl_nac_errors(hba);
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (!ret && ufshcd_err_handling_should_stop(hba))
+ goto skip_err_handling;
+ }
+
+ if ((hba->saved_err & (INT_FATAL_ERRORS | UFSHCD_UIC_HIBERN8_MASK)) ||
+ (hba->saved_uic_err &&
+ (hba->saved_uic_err != UFSHCD_UIC_PA_GENERIC_ERROR))) {
+ bool pr_prdt = !!(hba->saved_err & SYSTEM_BUS_FATAL_ERROR);
+
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ ufshcd_print_host_state(hba);
+ ufshcd_print_pwr_info(hba);
+ ufshcd_print_tx_eq_params(hba);
+ ufshcd_print_evt_hist(hba);
+ ufshcd_print_tmrs(hba, hba->outstanding_tasks);
+ ufshcd_print_trs_all(hba, pr_prdt);
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ }
+
+ /*
+ * if host reset is required then skip clearing the pending
+ * transfers forcefully because they will get cleared during
+ * host reset and restore
+ */
+ if (hba->force_reset || ufshcd_is_link_broken(hba) ||
+ ufshcd_is_saved_err_fatal(hba) ||
+ ((hba->saved_err & UIC_ERROR) &&
+ (hba->saved_uic_err & (UFSHCD_UIC_DL_NAC_RECEIVED_ERROR |
+ UFSHCD_UIC_DL_TCx_REPLAY_ERROR)))) {
+ needs_reset = true;
+ goto do_reset;
+ }
+
+ /*
+ * If LINERESET was caught, UFS might have been put to PWM mode,
+ * check if power mode restore is needed.
+ */
+ if (hba->saved_uic_err & UFSHCD_UIC_PA_GENERIC_ERROR) {
+ hba->saved_uic_err &= ~UFSHCD_UIC_PA_GENERIC_ERROR;
+ if (!hba->saved_uic_err)
+ hba->saved_err &= ~UIC_ERROR;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ if (ufshcd_is_pwr_mode_restore_needed(hba))
+ needs_restore = true;
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (!hba->saved_err && !needs_restore)
+ goto skip_err_handling;
+ }
+
+ hba->silence_err_logs = true;
+ /* release lock as clear command might sleep */
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ needs_reset = ufshcd_abort_all(hba);
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ hba->silence_err_logs = false;
+ if (needs_reset)
+ goto do_reset;
+
+ /*
+ * After all reqs and tasks are cleared from doorbell,
+ * now it is safe to retore power mode.
+ */
+ if (needs_restore) {
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ /*
+ * Hold the scaling lock just in case dev cmds
+ * are sent via bsg and/or sysfs.
+ */
+ down_write(&hba->clk_scaling_lock);
+ pmc_err = ufshcd_config_pwr_mode(hba, &hba->pwr_info,
+ UFSHCD_PMC_POLICY_FORCE);
+ if (pmc_err) {
+ needs_reset = true;
+ dev_err(hba->dev, "%s: Failed to restore power mode, err = %d\n",
+ __func__, pmc_err);
+ }
+ ufshcd_print_pwr_info(hba);
+ up_write(&hba->clk_scaling_lock);
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ }
+
+do_reset:
+ /* Fatal errors need reset */
+ if (needs_reset) {
+ int err;
+
+ hba->force_reset = false;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ err = ufshcd_reset_and_restore(hba);
+ if (err)
+ dev_err(hba->dev, "%s: reset and restore failed with err %d\n",
+ __func__, err);
+ else
+ ufshcd_recover_pm_error(hba);
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ }
+
+skip_err_handling:
+ if (!needs_reset) {
+ if (hba->ufshcd_state == UFSHCD_STATE_RESET)
+ hba->ufshcd_state = UFSHCD_STATE_OPERATIONAL;
+ if (hba->saved_err || hba->saved_uic_err)
+ dev_err_ratelimited(hba->dev, "%s: exit: saved_err 0x%x saved_uic_err 0x%x",
+ __func__, hba->saved_err, hba->saved_uic_err);
+ }
+ /* Exit in an operational state or dead */
+ if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL &&
+ hba->ufshcd_state != UFSHCD_STATE_ERROR) {
+ if (--retries)
+ goto again;
+ hba->ufshcd_state = UFSHCD_STATE_ERROR;
+ }
+ ufshcd_clear_eh_in_progress(hba);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ ufshcd_err_handling_unprepare(hba);
+ up(&hba->host_sem);
+
+ dev_info(hba->dev, "%s finished; HBA state %s\n", __func__,
+ ufshcd_state_name[hba->ufshcd_state]);
+}
+
+/**
+ * ufshcd_update_uic_error - check and set fatal UIC error flags.
+ * @hba: per-adapter instance
+ *
+ * Return:
+ * IRQ_HANDLED - If interrupt is valid
+ * IRQ_NONE - If invalid interrupt
+ */
+static irqreturn_t ufshcd_update_uic_error(struct ufs_hba *hba)
+{
+ u32 reg;
+ irqreturn_t retval = IRQ_NONE;
+
+ /* PHY layer error */
+ reg = ufshcd_readl(hba, REG_UIC_ERROR_CODE_PHY_ADAPTER_LAYER);
+ if ((reg & UIC_PHY_ADAPTER_LAYER_ERROR) &&
+ (reg & UIC_PHY_ADAPTER_LAYER_ERROR_CODE_MASK)) {
+ ufshcd_update_evt_hist(hba, UFS_EVT_PA_ERR, reg);
+ /*
+ * To know whether this error is fatal or not, DB timeout
+ * must be checked but this error is handled separately.
+ */
+ if (reg & UIC_PHY_ADAPTER_LAYER_LANE_ERR_MASK)
+ dev_dbg(hba->dev, "%s: UIC Lane error reported\n",
+ __func__);
+
+ /* Got a LINERESET indication. */
+ if (reg & UIC_PHY_ADAPTER_LAYER_GENERIC_ERROR) {
+ struct uic_command *cmd = NULL;
+
+ hba->uic_error |= UFSHCD_UIC_PA_GENERIC_ERROR;
+ if (hba->uic_async_done && hba->active_uic_cmd)
+ cmd = hba->active_uic_cmd;
+ /*
+ * Ignore the LINERESET during power mode change
+ * operation via DME_SET command.
+ */
+ if (cmd && (cmd->command == UIC_CMD_DME_SET))
+ hba->uic_error &= ~UFSHCD_UIC_PA_GENERIC_ERROR;
+ }
+ retval |= IRQ_HANDLED;
+ }
+
+ /* PA_INIT_ERROR is fatal and needs UIC reset */
+ reg = ufshcd_readl(hba, REG_UIC_ERROR_CODE_DATA_LINK_LAYER);
+ if ((reg & UIC_DATA_LINK_LAYER_ERROR) &&
+ (reg & UIC_DATA_LINK_LAYER_ERROR_CODE_MASK)) {
+ ufshcd_update_evt_hist(hba, UFS_EVT_DL_ERR, reg);
+
+ if (reg & UIC_DATA_LINK_LAYER_ERROR_PA_INIT)
+ hba->uic_error |= UFSHCD_UIC_DL_PA_INIT_ERROR;
+ else if (hba->dev_quirks &
+ UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS) {
+ if (reg & UIC_DATA_LINK_LAYER_ERROR_NAC_RECEIVED)
+ hba->uic_error |=
+ UFSHCD_UIC_DL_NAC_RECEIVED_ERROR;
+ else if (reg & UIC_DATA_LINK_LAYER_ERROR_TCx_REPLAY_TIMEOUT)
+ hba->uic_error |= UFSHCD_UIC_DL_TCx_REPLAY_ERROR;
+ }
+ retval |= IRQ_HANDLED;
+ }
+
+ /* UIC NL/TL/DME errors needs software retry */
+ reg = ufshcd_readl(hba, REG_UIC_ERROR_CODE_NETWORK_LAYER);
+ if ((reg & UIC_NETWORK_LAYER_ERROR) &&
+ (reg & UIC_NETWORK_LAYER_ERROR_CODE_MASK)) {
+ ufshcd_update_evt_hist(hba, UFS_EVT_NL_ERR, reg);
+ hba->uic_error |= UFSHCD_UIC_NL_ERROR;
+ retval |= IRQ_HANDLED;
+ }
+
+ reg = ufshcd_readl(hba, REG_UIC_ERROR_CODE_TRANSPORT_LAYER);
+ if ((reg & UIC_TRANSPORT_LAYER_ERROR) &&
+ (reg & UIC_TRANSPORT_LAYER_ERROR_CODE_MASK)) {
+ ufshcd_update_evt_hist(hba, UFS_EVT_TL_ERR, reg);
+ hba->uic_error |= UFSHCD_UIC_TL_ERROR;
+ retval |= IRQ_HANDLED;
+ }
+
+ reg = ufshcd_readl(hba, REG_UIC_ERROR_CODE_DME);
+ if (reg & UIC_DME_ERROR) {
+ ufshcd_update_evt_hist(hba, UFS_EVT_DME_ERR, reg);
+
+ if (reg & UIC_DME_ERROR_CODE_MASK)
+ hba->uic_error |= UFSHCD_UIC_DME_ERROR;
+
+ if (reg & UIC_DME_QOS_MASK) {
+ atomic_set(&hba->dme_qos_notification,
+ reg & UIC_DME_QOS_MASK);
+ if (hba->dme_qos_sysfs_handle)
+ sysfs_notify_dirent(hba->dme_qos_sysfs_handle);
+ }
+
+ retval |= IRQ_HANDLED;
+ }
+
+ dev_dbg(hba->dev, "%s: UIC error flags = 0x%08x\n",
+ __func__, hba->uic_error);
+ return retval;
+}
+
+/**
+ * ufshcd_check_errors - Check for errors that need s/w attention
+ * @hba: per-adapter instance
+ * @intr_status: interrupt status generated by the controller
+ *
+ * Return:
+ * IRQ_HANDLED - If interrupt is valid
+ * IRQ_NONE - If invalid interrupt
+ */
+static irqreturn_t ufshcd_check_errors(struct ufs_hba *hba, u32 intr_status)
+{
+ bool queue_eh_work = false;
+ irqreturn_t retval = IRQ_NONE;
+
+ guard(spinlock_irqsave)(hba->host->host_lock);
+ hba->errors |= UFSHCD_ERROR_MASK & intr_status;
+
+ if (hba->errors & INT_FATAL_ERRORS) {
+ ufshcd_update_evt_hist(hba, UFS_EVT_FATAL_ERR,
+ hba->errors);
+ queue_eh_work = true;
+ }
+
+ if (hba->errors & UIC_ERROR) {
+ hba->uic_error = 0;
+ retval = ufshcd_update_uic_error(hba);
+ if (hba->uic_error)
+ queue_eh_work = true;
+ }
+
+ if (hba->errors & UFSHCD_UIC_HIBERN8_MASK) {
+ dev_err(hba->dev,
+ "%s: Auto Hibern8 %s failed - status: 0x%08x, upmcrs: 0x%08x\n",
+ __func__, (hba->errors & UIC_HIBERNATE_ENTER) ?
+ "Enter" : "Exit",
+ hba->errors, ufshcd_get_upmcrs(hba));
+ ufshcd_update_evt_hist(hba, UFS_EVT_AUTO_HIBERN8_ERR,
+ hba->errors);
+ ufshcd_set_link_broken(hba);
+ queue_eh_work = true;
+ }
+
+ if (queue_eh_work) {
+ /*
+ * update the transfer error masks to sticky bits, let's do this
+ * irrespective of current ufshcd_state.
+ */
+ hba->saved_err |= hba->errors;
+ hba->saved_uic_err |= hba->uic_error;
+
+ /* dump controller state before resetting */
+ if ((hba->saved_err &
+ (INT_FATAL_ERRORS | UFSHCD_UIC_HIBERN8_MASK)) ||
+ (hba->saved_uic_err &&
+ (hba->saved_uic_err != UFSHCD_UIC_PA_GENERIC_ERROR))) {
+ dev_err(hba->dev, "%s: saved_err 0x%x saved_uic_err 0x%x\n",
+ __func__, hba->saved_err,
+ hba->saved_uic_err);
+ ufshcd_dump_regs(hba, 0, UFSHCI_REG_SPACE_SIZE,
+ "host_regs: ");
+ ufshcd_print_pwr_info(hba);
+ ufshcd_print_tx_eq_params(hba);
+ }
+ ufshcd_schedule_eh_work(hba);
+ retval |= IRQ_HANDLED;
+ }
+ /*
+ * if (!queue_eh_work) -
+ * Other errors are either non-fatal where host recovers
+ * itself without s/w intervention or errors that will be
+ * handled by the SCSI core layer.
+ */
+ hba->errors = 0;
+ hba->uic_error = 0;
+
+ return retval;
+}
+
+/**
+ * ufshcd_tmc_handler - handle task management function completion
+ * @hba: per adapter instance
+ *
+ * Return:
+ * IRQ_HANDLED - If interrupt is valid
+ * IRQ_NONE - If invalid interrupt
+ */
+static irqreturn_t ufshcd_tmc_handler(struct ufs_hba *hba)
+{
+ unsigned long flags, pending, issued;
+ irqreturn_t ret = IRQ_NONE;
+ int tag;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ pending = ufshcd_readl(hba, REG_UTP_TASK_REQ_DOOR_BELL);
+ issued = hba->outstanding_tasks & ~pending;
+ for_each_set_bit(tag, &issued, hba->nutmrs) {
+ struct request *req = hba->tmf_rqs[tag];
+ struct completion *c = req->end_io_data;
+
+ complete(c);
+ ret = IRQ_HANDLED;
+ }
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ return ret;
+}
+
+/**
+ * ufshcd_handle_mcq_cq_events - handle MCQ completion queue events
+ * @hba: per adapter instance
+ * @reset_iag: true, to reset MCQ IAG counter and timer of the CQ
+ *
+ * Return: IRQ_HANDLED if interrupt is handled.
+ */
+static irqreturn_t ufshcd_handle_mcq_cq_events(struct ufs_hba *hba, bool reset_iag)
+{
+ struct ufs_hw_queue *hwq;
+ unsigned long outstanding_cqs;
+ unsigned int nr_queues;
+ int i, ret;
+ u32 events, reg;
+
+ ret = ufshcd_vops_get_outstanding_cqs(hba, &outstanding_cqs);
+ if (ret)
+ outstanding_cqs = (1ULL << hba->nr_hw_queues) - 1;
+
+ /* Exclude the poll queues */
+ nr_queues = hba->nr_hw_queues - hba->nr_queues[HCTX_TYPE_POLL];
+ for_each_set_bit(i, &outstanding_cqs, nr_queues) {
+ hwq = &hba->uhq[i];
+
+ events = ufshcd_mcq_read_cqis(hba, i);
+ if (events)
+ ufshcd_mcq_write_cqis(hba, events, i);
+
+ if (reset_iag) {
+ reg = ufshcd_mcq_read_mcqiacr(hba, i);
+ reg |= INT_AGGR_COUNTER_AND_TIMER_RESET;
+ ufshcd_mcq_write_mcqiacr(hba, reg, i);
+ }
+
+ if (events & UFSHCD_MCQ_CQIS_TAIL_ENT_PUSH_STS)
+ ufshcd_mcq_poll_cqe_lock(hba, hwq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ufshcd_sl_intr - Interrupt service routine
+ * @hba: per adapter instance
+ * @intr_status: contains interrupts generated by the controller
+ *
+ * Return:
+ * IRQ_HANDLED - If interrupt is valid
+ * IRQ_NONE - If invalid interrupt
+ */
+static irqreturn_t ufshcd_sl_intr(struct ufs_hba *hba, u32 intr_status)
+{
+ irqreturn_t retval = IRQ_NONE;
+
+ if (intr_status & UFSHCD_UIC_MASK)
+ retval |= ufshcd_uic_cmd_compl(hba, intr_status);
+
+ if (intr_status & UFSHCD_ERROR_MASK || hba->errors)
+ retval |= ufshcd_check_errors(hba, intr_status);
+
+ if (intr_status & UTP_TASK_REQ_COMPL)
+ retval |= ufshcd_tmc_handler(hba);
+
+ if (intr_status & UTP_TRANSFER_REQ_COMPL)
+ retval |= ufshcd_transfer_req_compl(hba);
+
+ if (intr_status & MCQ_CQ_EVENT_STATUS)
+ retval |= ufshcd_handle_mcq_cq_events(hba, false);
+
+ if (intr_status & MCQ_IAG_EVENT_STATUS)
+ retval |= ufshcd_handle_mcq_cq_events(hba, true);
+
+ return retval;
+}
+
+/**
+ * ufshcd_intr - Main interrupt service routine
+ * @irq: irq number
+ * @__hba: pointer to adapter instance
+ *
+ * Return:
+ * IRQ_HANDLED - If interrupt is valid
+ * IRQ_NONE - If invalid interrupt
+ */
+static irqreturn_t ufshcd_intr(int irq, void *__hba)
+{
+ u32 last_intr_status, intr_status, enabled_intr_status = 0;
+ irqreturn_t retval = IRQ_NONE;
+ struct ufs_hba *hba = __hba;
+ int retries = hba->nutrs;
+
+ last_intr_status = intr_status = ufshcd_readl(hba, REG_INTERRUPT_STATUS);
+
+ /*
+ * There could be max of hba->nutrs reqs in flight and in worst case
+ * if the reqs get finished 1 by 1 after the interrupt status is
+ * read, make sure we handle them by checking the interrupt status
+ * again in a loop until we process all of the reqs before returning.
+ */
+ while (intr_status && retries--) {
+ enabled_intr_status =
+ intr_status & ufshcd_readl(hba, REG_INTERRUPT_ENABLE);
+ ufshcd_writel(hba, intr_status, REG_INTERRUPT_STATUS);
+ if (enabled_intr_status)
+ retval |= ufshcd_sl_intr(hba, enabled_intr_status);
+
+ intr_status = ufshcd_readl(hba, REG_INTERRUPT_STATUS);
+ }
+
+ if (enabled_intr_status && retval == IRQ_NONE &&
+ (!(enabled_intr_status & UTP_TRANSFER_REQ_COMPL) ||
+ hba->outstanding_reqs) && !ufshcd_eh_in_progress(hba)) {
+ dev_err(hba->dev, "%s: Unhandled interrupt 0x%08x (0x%08x, 0x%08x)\n",
+ __func__,
+ intr_status,
+ last_intr_status,
+ enabled_intr_status);
+ ufshcd_dump_regs(hba, 0, UFSHCI_REG_SPACE_SIZE, "host_regs: ");
+ }
+
+ return retval;
+}
+
+static int ufshcd_clear_tm_cmd(struct ufs_hba *hba, int tag)
+{
+ int err = 0;
+ u32 mask = 1 << tag;
+
+ if (!test_bit(tag, &hba->outstanding_tasks))
+ goto out;
+
+ ufshcd_utmrl_clear(hba, tag);
+
+ /* poll for max. 1 sec to clear door bell register by h/w */
+ err = ufshcd_wait_for_register(hba,
+ REG_UTP_TASK_REQ_DOOR_BELL,
+ mask, 0, 1000, 1000);
+
+ dev_err(hba->dev, "Clearing task management function with tag %d %s\n",
+ tag, err < 0 ? "failed" : "succeeded");
+
+out:
+ return err;
+}
+
+static int __ufshcd_issue_tm_cmd(struct ufs_hba *hba,
+ struct utp_task_req_desc *treq, u8 tm_function)
+{
+ struct request_queue *q = hba->tmf_queue;
+ struct Scsi_Host *host = hba->host;
+ DECLARE_COMPLETION_ONSTACK(wait);
+ struct request *req;
+ unsigned long flags;
+ int task_tag, err;
+
+ /*
+ * blk_mq_alloc_request() is used here only to get a free tag.
+ */
+ req = blk_mq_alloc_request(q, REQ_OP_DRV_OUT, 0);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ req->end_io_data = &wait;
+ ufshcd_hold(hba);
+
+ spin_lock_irqsave(host->host_lock, flags);
+
+ task_tag = req->tag;
+ hba->tmf_rqs[req->tag] = req;
+ treq->upiu_req.req_header.task_tag = task_tag;
+
+ memcpy(hba->utmrdl_base_addr + task_tag, treq, sizeof(*treq));
+ ufshcd_vops_setup_task_mgmt(hba, task_tag, tm_function);
+
+ __set_bit(task_tag, &hba->outstanding_tasks);
+
+ spin_unlock_irqrestore(host->host_lock, flags);
+
+ /* send command to the controller */
+ ufshcd_writel(hba, 1 << task_tag, REG_UTP_TASK_REQ_DOOR_BELL);
+
+ ufshcd_add_tm_upiu_trace(hba, task_tag, UFS_TM_SEND);
+
+ /* wait until the task management command is completed */
+ err = wait_for_completion_io_timeout(&wait,
+ msecs_to_jiffies(TM_CMD_TIMEOUT));
+ if (!err) {
+ ufshcd_add_tm_upiu_trace(hba, task_tag, UFS_TM_ERR);
+ dev_err(hba->dev, "%s: task management cmd 0x%.2x timed-out\n",
+ __func__, tm_function);
+ if (ufshcd_clear_tm_cmd(hba, task_tag))
+ dev_WARN(hba->dev, "%s: unable to clear tm cmd (slot %d) after timeout\n",
+ __func__, task_tag);
+ err = -ETIMEDOUT;
+ } else {
+ err = 0;
+ memcpy(treq, hba->utmrdl_base_addr + task_tag, sizeof(*treq));
+
+ ufshcd_add_tm_upiu_trace(hba, task_tag, UFS_TM_COMP);
+ }
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ hba->tmf_rqs[req->tag] = NULL;
+ __clear_bit(task_tag, &hba->outstanding_tasks);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ ufshcd_release(hba);
+ blk_mq_free_request(req);
+
+ return err;
+}
+
+/**
+ * ufshcd_issue_tm_cmd - issues task management commands to controller
+ * @hba: per adapter instance
+ * @lun_id: LUN ID to which TM command is sent
+ * @task_id: task ID to which the TM command is applicable
+ * @tm_function: task management function opcode
+ * @tm_response: task management service response return value
+ *
+ * Return: non-zero value on error, zero on success.
+ */
+static int ufshcd_issue_tm_cmd(struct ufs_hba *hba, int lun_id, int task_id,
+ u8 tm_function, u8 *tm_response)
+{
+ struct utp_task_req_desc treq = { };
+ enum utp_ocs ocs_value;
+ int err;
+
+ /* Configure task request descriptor */
+ treq.header.interrupt = 1;
+ treq.header.ocs = OCS_INVALID_COMMAND_STATUS;
+
+ /* Configure task request UPIU */
+ treq.upiu_req.req_header.transaction_code = UPIU_TRANSACTION_TASK_REQ;
+ treq.upiu_req.req_header.lun = lun_id;
+ treq.upiu_req.req_header.tm_function = tm_function;
+
+ /*
+ * The host shall provide the same value for LUN field in the basic
+ * header and for Input Parameter.
+ */
+ treq.upiu_req.input_param1 = cpu_to_be32(lun_id);
+ treq.upiu_req.input_param2 = cpu_to_be32(task_id);
+
+ err = __ufshcd_issue_tm_cmd(hba, &treq, tm_function);
+ if (err == -ETIMEDOUT)
+ return err;
+
+ ocs_value = treq.header.ocs & MASK_OCS;
+ if (ocs_value != OCS_SUCCESS)
+ dev_err(hba->dev, "%s: failed, ocs = 0x%x\n",
+ __func__, ocs_value);
+ else if (tm_response)
+ *tm_response = be32_to_cpu(treq.upiu_rsp.output_param1) &
+ MASK_TM_SERVICE_RESP;
+ return err;
+}
+
+/**
+ * ufshcd_issue_devman_upiu_cmd - API for sending "utrd" type requests
+ * @hba: per-adapter instance
+ * @req_upiu: upiu request
+ * @rsp_upiu: upiu reply
+ * @desc_buff: pointer to descriptor buffer, NULL if NA
+ * @buff_len: descriptor size, 0 if NA
+ * @cmd_type: specifies the type (NOP, Query...)
+ * @desc_op: descriptor operation
+ *
+ * Those type of requests uses UTP Transfer Request Descriptor - utrd.
+ * Therefore, it "rides" the device management infrastructure: uses its tag and
+ * tasks work queues.
+ *
+ * Since there is only one available tag for device management commands,
+ * the caller is expected to hold the hba->dev_cmd.lock mutex.
+ *
+ * Return: 0 upon success; < 0 upon failure.
+ */
+static int ufshcd_issue_devman_upiu_cmd(struct ufs_hba *hba,
+ struct utp_upiu_req *req_upiu,
+ struct utp_upiu_req *rsp_upiu,
+ u8 *desc_buff, int *buff_len,
+ enum dev_cmd_type cmd_type,
+ enum query_opcode desc_op)
+{
+ struct scsi_cmnd *cmd = ufshcd_get_dev_mgmt_cmd(hba);
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ u32 tag;
+ int err = 0;
+ u8 upiu_flags;
+
+ /* Protects use of hba->dev_cmd. */
+ lockdep_assert_held(&hba->dev_cmd.lock);
+
+ if (WARN_ON_ONCE(!cmd))
+ return -ENOMEM;
+
+ tag = scsi_cmd_to_rq(cmd)->tag;
+
+ ufshcd_setup_dev_cmd(hba, cmd, cmd_type, 0, tag);
+
+ ufshcd_prepare_req_desc_hdr(hba, lrbp, &upiu_flags, DMA_NONE, 0);
+
+ /* update the task tag in the request upiu */
+ req_upiu->header.task_tag = tag;
+
+ /* just copy the upiu request as it is */
+ memcpy(lrbp->ucd_req_ptr, req_upiu, sizeof(*lrbp->ucd_req_ptr));
+ if (desc_buff && desc_op == UPIU_QUERY_OPCODE_WRITE_DESC) {
+ /* The Data Segment Area is optional depending upon the query
+ * function value. for WRITE DESCRIPTOR, the data segment
+ * follows right after the tsf.
+ */
+ memcpy(lrbp->ucd_req_ptr + 1, desc_buff, *buff_len);
+ *buff_len = 0;
+ }
+
+ memset(lrbp->ucd_rsp_ptr, 0, sizeof(struct utp_upiu_rsp));
+
+ err = ufshcd_issue_dev_cmd(hba, cmd, tag, dev_cmd_timeout);
+ if (err)
+ goto put_dev_mgmt_cmd;
+
+ /* just copy the upiu response as it is */
+ memcpy(rsp_upiu, lrbp->ucd_rsp_ptr, sizeof(*rsp_upiu));
+ if (desc_buff && (desc_op == UPIU_QUERY_OPCODE_READ_DESC ||
+ desc_op == UPIU_QUERY_OPCODE_AGGREGATED_READ)) {
+ u8 *descp = (u8 *)lrbp->ucd_rsp_ptr + sizeof(*rsp_upiu);
+ u16 resp_len = be16_to_cpu(lrbp->ucd_rsp_ptr->header
+ .data_segment_length);
+
+ if (*buff_len >= resp_len) {
+ memcpy(desc_buff, descp, resp_len);
+ *buff_len = resp_len;
+ } else {
+ dev_warn(hba->dev,
+ "%s: rsp size %d is bigger than buffer size %d",
+ __func__, resp_len, *buff_len);
+ *buff_len = 0;
+ err = -EINVAL;
+ }
+ }
+
+put_dev_mgmt_cmd:
+ ufshcd_put_dev_mgmt_cmd(cmd);
+
+ return err;
+}
+
+/**
+ * ufshcd_exec_raw_upiu_cmd - API function for sending raw upiu commands
+ * @hba: per-adapter instance
+ * @req_upiu: upiu request
+ * @rsp_upiu: upiu reply - only 8 DW as we do not support scsi commands
+ * @msgcode: message code, one of UPIU Transaction Codes Initiator to Target
+ * @desc_buff: pointer to descriptor buffer, NULL if NA
+ * @buff_len: descriptor size, 0 if NA
+ * @desc_op: descriptor operation
+ *
+ * Supports UTP Transfer requests (nop and query), and UTP Task
+ * Management requests.
+ * It is up to the caller to fill the upiu conent properly, as it will
+ * be copied without any further input validations.
+ *
+ * Return: 0 upon success; < 0 upon failure.
+ */
+int ufshcd_exec_raw_upiu_cmd(struct ufs_hba *hba,
+ struct utp_upiu_req *req_upiu,
+ struct utp_upiu_req *rsp_upiu,
+ enum upiu_request_transaction msgcode,
+ u8 *desc_buff, int *buff_len,
+ enum query_opcode desc_op)
+{
+ int err;
+ enum dev_cmd_type cmd_type = DEV_CMD_TYPE_QUERY;
+ struct utp_task_req_desc treq = { };
+ enum utp_ocs ocs_value;
+ u8 tm_f = req_upiu->header.tm_function;
+
+ switch (msgcode) {
+ case UPIU_TRANSACTION_NOP_OUT:
+ cmd_type = DEV_CMD_TYPE_NOP;
+ fallthrough;
+ case UPIU_TRANSACTION_QUERY_REQ:
+ ufshcd_dev_man_lock(hba);
+ err = ufshcd_issue_devman_upiu_cmd(hba, req_upiu, rsp_upiu,
+ desc_buff, buff_len,
+ cmd_type, desc_op);
+ ufshcd_dev_man_unlock(hba);
+
+ break;
+ case UPIU_TRANSACTION_TASK_REQ:
+ treq.header.interrupt = 1;
+ treq.header.ocs = OCS_INVALID_COMMAND_STATUS;
+
+ memcpy(&treq.upiu_req, req_upiu, sizeof(*req_upiu));
+
+ err = __ufshcd_issue_tm_cmd(hba, &treq, tm_f);
+ if (err == -ETIMEDOUT)
+ break;
+
+ ocs_value = treq.header.ocs & MASK_OCS;
+ if (ocs_value != OCS_SUCCESS) {
+ dev_err(hba->dev, "%s: failed, ocs = 0x%x\n", __func__,
+ ocs_value);
+ break;
+ }
+
+ memcpy(rsp_upiu, &treq.upiu_rsp, sizeof(*rsp_upiu));
+
+ break;
+ default:
+ err = -EINVAL;
+
+ break;
+ }
+
+ return err;
+}
+
+/**
+ * ufshcd_advanced_rpmb_req_handler - handle advanced RPMB request
+ * @hba: per adapter instance
+ * @req_upiu: upiu request
+ * @rsp_upiu: upiu reply
+ * @req_ehs: EHS field which contains Advanced RPMB Request Message
+ * @rsp_ehs: EHS field which returns Advanced RPMB Response Message
+ * @sg_cnt: The number of sg lists actually used
+ * @sg_list: Pointer to SG list when DATA IN/OUT UPIU is required in ARPMB operation
+ * @dir: DMA direction
+ *
+ * Return: 0 upon success; > 0 in case the UFS device reported an OCS error;
+ * < 0 if another error occurred.
+ */
+int ufshcd_advanced_rpmb_req_handler(struct ufs_hba *hba, struct utp_upiu_req *req_upiu,
+ struct utp_upiu_req *rsp_upiu, struct ufs_ehs *req_ehs,
+ struct ufs_ehs *rsp_ehs, int sg_cnt, struct scatterlist *sg_list,
+ enum dma_data_direction dir)
+{
+ struct scsi_cmnd *cmd;
+ struct ufshcd_lrb *lrbp;
+ u32 tag;
+ int err = 0;
+ int result;
+ u8 upiu_flags;
+ u8 *ehs_data;
+ u16 ehs_len;
+ int ehs = (hba->capabilities & MASK_EHSLUTRD_SUPPORTED) ? 2 : 0;
+
+ ufshcd_dev_man_lock(hba);
+
+ cmd = ufshcd_get_dev_mgmt_cmd(hba);
+
+ if (WARN_ON_ONCE(!cmd)) {
+ err = -ENOMEM;
+ goto unlock;
+ }
+
+ lrbp = scsi_cmd_priv(cmd);
+ tag = scsi_cmd_to_rq(cmd)->tag;
+
+ ufshcd_setup_dev_cmd(hba, cmd, DEV_CMD_TYPE_RPMB, UFS_UPIU_RPMB_WLUN,
+ tag);
+
+ ufshcd_prepare_req_desc_hdr(hba, lrbp, &upiu_flags, DMA_NONE, ehs);
+
+ /* update the task tag */
+ req_upiu->header.task_tag = tag;
+
+ /* copy the UPIU(contains CDB) request as it is */
+ memcpy(lrbp->ucd_req_ptr, req_upiu, sizeof(*lrbp->ucd_req_ptr));
+ /* Copy EHS, starting with byte32, immediately after the CDB package */
+ memcpy(lrbp->ucd_req_ptr + 1, req_ehs, sizeof(*req_ehs));
+
+ if (dir != DMA_NONE && sg_list)
+ ufshcd_sgl_to_prdt(hba, lrbp, sg_cnt, sg_list);
+
+ memset(lrbp->ucd_rsp_ptr, 0, sizeof(struct utp_upiu_rsp));
+
+ err = ufshcd_issue_dev_cmd(hba, cmd, tag, ADVANCED_RPMB_REQ_TIMEOUT);
+ if (err)
+ goto put_dev_mgmt_cmd;
+
+ err = ufshcd_dev_cmd_completion(hba, lrbp);
+ if (!err) {
+ /* Just copy the upiu response as it is */
+ memcpy(rsp_upiu, lrbp->ucd_rsp_ptr, sizeof(*rsp_upiu));
+ /* Get the response UPIU result */
+ result = (lrbp->ucd_rsp_ptr->header.response << 8) |
+ lrbp->ucd_rsp_ptr->header.status;
+
+ ehs_len = lrbp->ucd_rsp_ptr->header.ehs_length;
+ /*
+ * Since the bLength in EHS indicates the total size of the EHS Header and EHS Data
+ * in 32 Byte units, the value of the bLength Request/Response for Advanced RPMB
+ * Message is 02h
+ */
+ if (ehs_len == 2 && rsp_ehs) {
+ /* EHS data starts from byte32 of the devman UCD response area. */
+ ehs_data = (u8 *)lrbp->ucd_rsp_ptr + EHS_OFFSET_IN_RESPONSE;
+ memcpy(rsp_ehs, ehs_data, ehs_len * 32);
+ }
+ }
+
+put_dev_mgmt_cmd:
+ ufshcd_put_dev_mgmt_cmd(cmd);
+
+unlock:
+ ufshcd_dev_man_unlock(hba);
+
+ return err ? : result;
+}
+
+static bool ufshcd_clear_lu_cmds(struct request *req, void *priv)
+{
+ struct scsi_cmnd *cmd = blk_mq_rq_to_pdu(req);
+ struct scsi_device *sdev = cmd->device;
+ struct Scsi_Host *shost = sdev->host;
+ struct ufs_hba *hba = shost_priv(shost);
+ const u64 lun = *(u64 *)priv;
+ const u32 tag = req->tag;
+
+ if (blk_mq_is_reserved_rq(req) || sdev->lun != lun)
+ return true;
+
+ if (ufshcd_clear_cmd(hba, tag) < 0) {
+ dev_err(hba->dev, "%s: failed to clear request %d\n", __func__,
+ tag);
+ return true;
+ }
+
+ if (hba->mcq_enabled) {
+ struct ufs_hw_queue *hwq = ufshcd_mcq_req_to_hwq(hba, req);
+
+ if (hwq)
+ ufshcd_mcq_poll_cqe_lock(hba, hwq);
+ return true;
+ }
+
+ ufshcd_compl_one_cqe(hba, tag, NULL);
+ return true;
+}
+
+/**
+ * ufshcd_eh_device_reset_handler() - Reset a single logical unit.
+ * @cmd: SCSI command pointer
+ *
+ * Return: SUCCESS or FAILED.
+ */
+static int ufshcd_eh_device_reset_handler(struct scsi_cmnd *cmd)
+{
+ struct Scsi_Host *host;
+ struct ufs_hba *hba;
+ int err;
+ u8 resp = 0xF, lun;
+
+ host = cmd->device->host;
+ hba = shost_priv(host);
+
+ lun = ufshcd_scsi_to_upiu_lun(cmd->device->lun);
+ err = ufshcd_issue_tm_cmd(hba, lun, 0, UFS_LOGICAL_RESET, &resp);
+ if (err) {
+ } else if (resp != UPIU_TASK_MANAGEMENT_FUNC_COMPL) {
+ err = resp;
+ } else {
+ /* clear the commands that were pending for corresponding LUN */
+ blk_mq_tagset_busy_iter(&hba->host->tag_set,
+ ufshcd_clear_lu_cmds,
+ &cmd->device->lun);
+ }
+
+ hba->req_abort_count = 0;
+ ufshcd_update_evt_hist(hba, UFS_EVT_DEV_RESET, (u32)err);
+ if (!err) {
+ err = SUCCESS;
+ } else {
+ dev_err(hba->dev, "%s: failed with err %d\n", __func__, err);
+ err = FAILED;
+ }
+ return err;
+}
+
+static void ufshcd_set_req_abort_skip(struct ufs_hba *hba, unsigned long bitmap)
+{
+ int tag;
+
+ for_each_set_bit(tag, &bitmap, hba->nutrs) {
+ struct scsi_cmnd *cmd = ufshcd_tag_to_cmd(hba, tag);
+ struct ufshcd_lrb *lrbp;
+
+ if (!cmd)
+ continue;
+
+ lrbp = scsi_cmd_priv(cmd);
+ lrbp->req_abort_skip = true;
+ }
+}
+
+/**
+ * ufshcd_try_to_abort_task - abort a specific task
+ * @hba: Pointer to adapter instance
+ * @tag: Tag of the task to be aborted
+ *
+ * Abort the pending command in device by sending UFS_ABORT_TASK task management
+ * command, and in host controller by clearing the door-bell register. There can
+ * be race between controller sending the command to the device while abort is
+ * issued. To avoid that, first issue UFS_QUERY_TASK to check if the command is
+ * really issued and then try to abort it.
+ *
+ * Return: zero on success, non-zero on failure.
+ */
+int ufshcd_try_to_abort_task(struct ufs_hba *hba, int tag)
+{
+ struct scsi_cmnd *cmd = ufshcd_tag_to_cmd(hba, tag);
+ struct ufshcd_lrb *lrbp;
+ int err;
+ int poll_cnt;
+ u8 resp = 0xF;
+
+ if (!cmd)
+ return -EINVAL;
+
+ lrbp = scsi_cmd_priv(cmd);
+
+ for (poll_cnt = 100; poll_cnt; poll_cnt--) {
+ err = ufshcd_issue_tm_cmd(hba, lrbp->lun, tag, UFS_QUERY_TASK,
+ &resp);
+ if (!err && resp == UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED) {
+ /* cmd pending in the device */
+ dev_err(hba->dev, "%s: cmd pending in the device. tag = %d\n",
+ __func__, tag);
+ break;
+ } else if (!err && resp == UPIU_TASK_MANAGEMENT_FUNC_COMPL) {
+ /*
+ * cmd not pending in the device, check if it is
+ * in transition.
+ */
+ dev_info(
+ hba->dev,
+ "%s: cmd with tag %d not pending in the device.\n",
+ __func__, tag);
+ if (!ufshcd_cmd_inflight(cmd)) {
+ dev_info(hba->dev,
+ "%s: cmd with tag=%d completed.\n",
+ __func__, tag);
+ return 0;
+ }
+ usleep_range(100, 200);
+ } else {
+ dev_err(hba->dev,
+ "%s: no response from device. tag = %d, err %d\n",
+ __func__, tag, err);
+ return err ? : resp;
+ }
+ }
+
+ if (!poll_cnt)
+ return -EBUSY;
+
+ err = ufshcd_issue_tm_cmd(hba, lrbp->lun, tag, UFS_ABORT_TASK, &resp);
+ if (err || resp != UPIU_TASK_MANAGEMENT_FUNC_COMPL) {
+ if (!err) {
+ err = resp; /* service response error */
+ dev_err(hba->dev, "%s: issued. tag = %d, err %d\n",
+ __func__, tag, err);
+ }
+ return err;
+ }
+
+ err = ufshcd_clear_cmd(hba, tag);
+ if (err)
+ dev_err(hba->dev, "%s: Failed clearing cmd at tag %d, err %d\n",
+ __func__, tag, err);
+
+ return err;
+}
+
+/**
+ * ufshcd_abort - scsi host template eh_abort_handler callback
+ * @cmd: SCSI command pointer
+ *
+ * Return: SUCCESS or FAILED.
+ */
+static int ufshcd_abort(struct scsi_cmnd *cmd)
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct ufs_hba *hba = shost_priv(host);
+ struct request *rq = scsi_cmd_to_rq(cmd);
+ int tag = rq->tag;
+ struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
+ unsigned long flags;
+ int err = FAILED;
+ bool outstanding;
+ u32 reg;
+
+ ufshcd_hold(hba);
+
+ if (!hba->mcq_enabled) {
+ reg = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
+ if (!test_bit(tag, &hba->outstanding_reqs)) {
+ /* If command is already aborted/completed, return FAILED. */
+ dev_err(hba->dev,
+ "%s: cmd at tag %d already completed, outstanding=0x%lx, doorbell=0x%x\n",
+ __func__, tag, hba->outstanding_reqs, reg);
+ goto release;
+ }
+ }
+
+ /* Print Transfer Request of aborted task */
+ dev_info(hba->dev, "%s: Device abort task at tag %d\n", __func__, tag);
+
+ /*
+ * Print detailed info about aborted request.
+ * As more than one request might get aborted at the same time,
+ * print full information only for the first aborted request in order
+ * to reduce repeated printouts. For other aborted requests only print
+ * basic details.
+ */
+ if (ufshcd_is_scsi_cmd(cmd))
+ scsi_print_command(cmd);
+ if (!hba->req_abort_count) {
+ ufshcd_update_evt_hist(hba, UFS_EVT_ABORT, tag);
+ ufshcd_print_evt_hist(hba);
+ ufshcd_print_host_state(hba);
+ ufshcd_print_pwr_info(hba);
+ ufshcd_print_tx_eq_params(hba);
+ ufshcd_print_tr(hba, cmd, true);
+ } else {
+ ufshcd_print_tr(hba, cmd, false);
+ }
+ hba->req_abort_count++;
+
+ if (!hba->mcq_enabled && !(reg & (1 << tag))) {
+ /* only execute this code in single doorbell mode */
+ dev_err(hba->dev,
+ "%s: cmd was completed, but without a notifying intr, tag = %d",
+ __func__, tag);
+ __ufshcd_transfer_req_compl(hba, 1UL << tag);
+ goto release;
+ }
+
+ /*
+ * Task abort to the device W-LUN is illegal. When this command
+ * will fail, due to spec violation, scsi err handling next step
+ * will be to send LU reset which, again, is a spec violation.
+ * To avoid these unnecessary/illegal steps, first we clean up
+ * the lrb taken by this cmd and re-set it in outstanding_reqs,
+ * then queue the eh_work and bail.
+ */
+ if (lrbp->lun == UFS_UPIU_UFS_DEVICE_WLUN) {
+ ufshcd_update_evt_hist(hba, UFS_EVT_ABORT, lrbp->lun);
+
+ spin_lock_irqsave(host->host_lock, flags);
+ hba->force_reset = true;
+ ufshcd_schedule_eh_work(hba);
+ spin_unlock_irqrestore(host->host_lock, flags);
+ goto release;
+ }
+
+ if (hba->mcq_enabled) {
+ /* MCQ mode. Branch off to handle abort for mcq mode */
+ err = ufshcd_mcq_abort(cmd);
+ goto release;
+ }
+
+ /* Skip task abort in case previous aborts failed and report failure */
+ if (lrbp->req_abort_skip) {
+ dev_err(hba->dev, "%s: skipping abort\n", __func__);
+ ufshcd_set_req_abort_skip(hba, hba->outstanding_reqs);
+ goto release;
+ }
+
+ if (blk_mq_is_reserved_rq(rq))
+ err = ufshcd_clear_cmd(hba, tag);
+ else
+ err = ufshcd_try_to_abort_task(hba, tag);
+ if (err) {
+ dev_err(hba->dev, "%s: failed with err %d\n", __func__, err);
+ ufshcd_set_req_abort_skip(hba, hba->outstanding_reqs);
+ err = FAILED;
+ goto release;
+ }
+
+ /*
+ * Clear the corresponding bit from outstanding_reqs since the command
+ * has been aborted successfully.
+ */
+ spin_lock_irqsave(&hba->outstanding_lock, flags);
+ outstanding = __test_and_clear_bit(tag, &hba->outstanding_reqs);
+ spin_unlock_irqrestore(&hba->outstanding_lock, flags);
+
+ if (outstanding)
+ ufshcd_release_scsi_cmd(hba, cmd);
+
+ err = SUCCESS;
+
+release:
+ /* Matches the ufshcd_hold() call at the start of this function. */
+ ufshcd_release(hba);
+ return err;
+}
+
+/**
+ * ufshcd_process_probe_result - Process the ufshcd_probe_hba() result.
+ * @hba: UFS host controller instance.
+ * @probe_start: time when the ufshcd_probe_hba() call started.
+ * @ret: ufshcd_probe_hba() return value.
+ */
+static void ufshcd_process_probe_result(struct ufs_hba *hba,
+ ktime_t probe_start, int ret)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (ret)
+ hba->ufshcd_state = UFSHCD_STATE_ERROR;
+ else if (hba->ufshcd_state == UFSHCD_STATE_RESET)
+ hba->ufshcd_state = UFSHCD_STATE_OPERATIONAL;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ trace_ufshcd_init(hba, ret,
+ ktime_to_us(ktime_sub(ktime_get(), probe_start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+}
+
+/**
+ * ufshcd_host_reset_and_restore - reset and restore host controller
+ * @hba: per-adapter instance
+ *
+ * Note that host controller reset may issue DME_RESET to
+ * local and remote (device) Uni-Pro stack and the attributes
+ * are reset to default state.
+ *
+ * Return: zero on success, non-zero on failure.
+ */
+static int ufshcd_host_reset_and_restore(struct ufs_hba *hba)
+{
+ int err;
+
+ /*
+ * Stop the host controller and complete the requests
+ * cleared by h/w
+ */
+ ufshcd_hba_stop(hba);
+ hba->silence_err_logs = true;
+ ufshcd_complete_requests(hba, true);
+ hba->silence_err_logs = false;
+
+ /* scale up clocks to max frequency before full reinitialization */
+ if (ufshcd_is_clkscaling_supported(hba))
+ ufshcd_scale_clks(hba, ULONG_MAX, true);
+
+ err = ufshcd_hba_enable(hba);
+
+ /* Establish the link again and restore the device */
+ if (!err) {
+ ktime_t probe_start = ktime_get();
+
+ err = ufshcd_device_init(hba, /*init_dev_params=*/false);
+ if (!err)
+ err = ufshcd_probe_hba(hba, false);
+ ufshcd_process_probe_result(hba, probe_start, err);
+ }
+
+ if (err)
+ dev_err(hba->dev, "%s: Host init failed %d\n", __func__, err);
+ ufshcd_update_evt_hist(hba, UFS_EVT_HOST_RESET, (u32)err);
+ return err;
+}
+
+/**
+ * ufshcd_reset_and_restore - reset and re-initialize host/device
+ * @hba: per-adapter instance
+ *
+ * Reset and recover device, host and re-establish link. This
+ * is helpful to recover the communication in fatal error conditions.
+ *
+ * Return: zero on success, non-zero on failure.
+ */
+static int ufshcd_reset_and_restore(struct ufs_hba *hba)
+{
+ u32 saved_err = 0;
+ u32 saved_uic_err = 0;
+ int err = 0;
+ unsigned long flags;
+ int retries = MAX_HOST_RESET_RETRIES;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ do {
+ /*
+ * This is a fresh start, cache and clear saved error first,
+ * in case new error generated during reset and restore.
+ */
+ saved_err |= hba->saved_err;
+ saved_uic_err |= hba->saved_uic_err;
+ hba->saved_err = 0;
+ hba->saved_uic_err = 0;
+ hba->force_reset = false;
+ hba->ufshcd_state = UFSHCD_STATE_RESET;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ /* Reset the attached device */
+ ufshcd_device_reset(hba);
+
+ err = ufshcd_host_reset_and_restore(hba);
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (err)
+ continue;
+ /* Do not exit unless operational or dead */
+ if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL &&
+ hba->ufshcd_state != UFSHCD_STATE_ERROR &&
+ hba->ufshcd_state != UFSHCD_STATE_EH_SCHEDULED_NON_FATAL)
+ err = -EAGAIN;
+ } while (err && --retries);
+
+ /*
+ * Inform scsi mid-layer that we did reset and allow to handle
+ * Unit Attention properly.
+ */
+ scsi_report_bus_reset(hba->host, 0);
+ if (err) {
+ hba->ufshcd_state = UFSHCD_STATE_ERROR;
+ hba->saved_err |= saved_err;
+ hba->saved_uic_err |= saved_uic_err;
+ }
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ return err;
+}
+
+/**
+ * ufshcd_eh_host_reset_handler - host reset handler registered to scsi layer
+ * @cmd: SCSI command pointer
+ *
+ * Return: SUCCESS or FAILED.
+ */
+static int ufshcd_eh_host_reset_handler(struct scsi_cmnd *cmd)
+{
+ int err = SUCCESS;
+ unsigned long flags;
+ struct ufs_hba *hba;
+
+ hba = shost_priv(cmd->device->host);
+
+ /*
+ * If runtime PM sent SSU and got a timeout, scsi_error_handler is
+ * stuck in this function waiting for flush_work(&hba->eh_work). And
+ * ufshcd_err_handler(eh_work) is stuck waiting for runtime PM. Do
+ * ufshcd_link_recovery instead of eh_work to prevent deadlock.
+ */
+ if (hba->pm_op_in_progress) {
+ if (ufshcd_link_recovery(hba))
+ err = FAILED;
+
+ return err;
+ }
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ hba->force_reset = true;
+ ufshcd_schedule_eh_work(hba);
+ dev_err(hba->dev, "%s: reset in progress - 1\n", __func__);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ flush_work(&hba->eh_work);
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (hba->ufshcd_state == UFSHCD_STATE_ERROR)
+ err = FAILED;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ return err;
+}
+
+/**
+ * ufshcd_get_max_icc_level - calculate the ICC level
+ * @sup_curr_uA: max. current supported by the regulator
+ * @start_scan: row at the desc table to start scan from
+ * @buff: power descriptor buffer
+ *
+ * Return: calculated max ICC level for specific regulator.
+ */
+static u32 ufshcd_get_max_icc_level(int sup_curr_uA, u32 start_scan,
+ const char *buff)
+{
+ int i;
+ int curr_uA;
+ u16 data;
+ u16 unit;
+
+ for (i = start_scan; i >= 0; i--) {
+ data = get_unaligned_be16(&buff[2 * i]);
+ unit = (data & ATTR_ICC_LVL_UNIT_MASK) >>
+ ATTR_ICC_LVL_UNIT_OFFSET;
+ curr_uA = data & ATTR_ICC_LVL_VALUE_MASK;
+ switch (unit) {
+ case UFSHCD_NANO_AMP:
+ curr_uA = curr_uA / 1000;
+ break;
+ case UFSHCD_MILI_AMP:
+ curr_uA = curr_uA * 1000;
+ break;
+ case UFSHCD_AMP:
+ curr_uA = curr_uA * 1000 * 1000;
+ break;
+ case UFSHCD_MICRO_AMP:
+ default:
+ break;
+ }
+ if (sup_curr_uA >= curr_uA)
+ break;
+ }
+ if (i < 0) {
+ i = 0;
+ pr_err("%s: Couldn't find valid icc_level = %d", __func__, i);
+ }
+
+ return (u32)i;
+}
+
+/**
+ * ufshcd_find_max_sup_active_icc_level - calculate the max ICC level
+ * In case regulators are not initialized we'll return 0
+ * @hba: per-adapter instance
+ * @desc_buf: power descriptor buffer to extract ICC levels from.
+ *
+ * Return: calculated ICC level.
+ */
+static u32 ufshcd_find_max_sup_active_icc_level(struct ufs_hba *hba,
+ const u8 *desc_buf)
+{
+ u32 icc_level = 0;
+
+ if (!hba->vreg_info.vcc || !hba->vreg_info.vccq ||
+ !hba->vreg_info.vccq2) {
+ /*
+ * Using dev_dbg to avoid messages during runtime PM to avoid
+ * never-ending cycles of messages written back to storage by
+ * user space causing runtime resume, causing more messages and
+ * so on.
+ */
+ dev_dbg(hba->dev,
+ "%s: Regulator capability was not set, actvIccLevel=%d",
+ __func__, icc_level);
+ goto out;
+ }
+
+ if (hba->vreg_info.vcc->max_uA)
+ icc_level = ufshcd_get_max_icc_level(
+ hba->vreg_info.vcc->max_uA,
+ POWER_DESC_MAX_ACTV_ICC_LVLS - 1,
+ &desc_buf[PWR_DESC_ACTIVE_LVLS_VCC_0]);
+
+ if (hba->vreg_info.vccq->max_uA)
+ icc_level = ufshcd_get_max_icc_level(
+ hba->vreg_info.vccq->max_uA,
+ icc_level,
+ &desc_buf[PWR_DESC_ACTIVE_LVLS_VCCQ_0]);
+
+ if (hba->vreg_info.vccq2->max_uA)
+ icc_level = ufshcd_get_max_icc_level(
+ hba->vreg_info.vccq2->max_uA,
+ icc_level,
+ &desc_buf[PWR_DESC_ACTIVE_LVLS_VCCQ2_0]);
+out:
+ return icc_level;
+}
+
+static void ufshcd_set_active_icc_lvl(struct ufs_hba *hba)
+{
+ int ret;
+ u8 *desc_buf;
+ u32 icc_level;
+
+ desc_buf = kzalloc(QUERY_DESC_MAX_SIZE, GFP_KERNEL);
+ if (!desc_buf)
+ return;
+
+ ret = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_POWER, 0, 0,
+ desc_buf, QUERY_DESC_MAX_SIZE);
+ if (ret) {
+ dev_err(hba->dev,
+ "%s: Failed reading power descriptor ret = %d",
+ __func__, ret);
+ goto out;
+ }
+
+ icc_level = ufshcd_find_max_sup_active_icc_level(hba, desc_buf);
+ dev_dbg(hba->dev, "%s: setting icc_level 0x%x", __func__, icc_level);
+
+ ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, &icc_level);
+
+ if (ret)
+ dev_err(hba->dev,
+ "%s: Failed configuring bActiveICCLevel = %d ret = %d",
+ __func__, icc_level, ret);
+
+out:
+ kfree(desc_buf);
+}
+
+static inline void ufshcd_blk_pm_runtime_init(struct scsi_device *sdev)
+{
+ struct Scsi_Host *shost = sdev->host;
+
+ scsi_autopm_get_device(sdev);
+ blk_pm_runtime_init(sdev->request_queue, &sdev->sdev_gendev);
+ if (sdev->rpm_autosuspend)
+ pm_runtime_set_autosuspend_delay(&sdev->sdev_gendev,
+ shost->rpm_autosuspend_delay);
+ scsi_autopm_put_device(sdev);
+}
+
+/**
+ * ufshcd_scsi_add_wlus - Adds required W-LUs
+ * @hba: per-adapter instance
+ *
+ * UFS device specification requires the UFS devices to support 4 well known
+ * logical units:
+ * "REPORT_LUNS" (address: 01h)
+ * "UFS Device" (address: 50h)
+ * "RPMB" (address: 44h)
+ * "BOOT" (address: 30h)
+ * UFS device's power management needs to be controlled by "POWER CONDITION"
+ * field of SSU (START STOP UNIT) command. But this "power condition" field
+ * will take effect only when its sent to "UFS device" well known logical unit
+ * hence we require the scsi_device instance to represent this logical unit in
+ * order for the UFS host driver to send the SSU command for power management.
+ *
+ * We also require the scsi_device instance for "RPMB" (Replay Protected Memory
+ * Block) LU so user space process can control this LU. User space may also
+ * want to have access to BOOT LU.
+ *
+ * This function adds scsi device instances for each of all well known LUs
+ * (except "REPORT LUNS" LU).
+ *
+ * Return: zero on success (all required W-LUs are added successfully),
+ * non-zero error value on failure (if failed to add any of the required W-LU).
+ */
+static int ufshcd_scsi_add_wlus(struct ufs_hba *hba)
+{
+ int ret = 0;
+ struct scsi_device *sdev_boot, *sdev_rpmb;
+
+ hba->ufs_device_wlun = __scsi_add_device(hba->host, 0, 0,
+ ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_UFS_DEVICE_WLUN), NULL);
+ if (IS_ERR(hba->ufs_device_wlun)) {
+ ret = PTR_ERR(hba->ufs_device_wlun);
+ hba->ufs_device_wlun = NULL;
+ goto out;
+ }
+ scsi_device_put(hba->ufs_device_wlun);
+
+ sdev_rpmb = __scsi_add_device(hba->host, 0, 0,
+ ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_RPMB_WLUN), NULL);
+ if (IS_ERR(sdev_rpmb)) {
+ ret = PTR_ERR(sdev_rpmb);
+ hba->ufs_rpmb_wlun = NULL;
+ dev_err(hba->dev, "%s: RPMB WLUN not found\n", __func__);
+ goto remove_ufs_device_wlun;
+ }
+ hba->ufs_rpmb_wlun = sdev_rpmb;
+ ufshcd_blk_pm_runtime_init(sdev_rpmb);
+ scsi_device_put(sdev_rpmb);
+
+ sdev_boot = __scsi_add_device(hba->host, 0, 0,
+ ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_BOOT_WLUN), NULL);
+ if (IS_ERR(sdev_boot)) {
+ dev_err(hba->dev, "%s: BOOT WLUN not found\n", __func__);
+ } else {
+ ufshcd_blk_pm_runtime_init(sdev_boot);
+ scsi_device_put(sdev_boot);
+ }
+ goto out;
+
+remove_ufs_device_wlun:
+ scsi_remove_device(hba->ufs_device_wlun);
+out:
+ return ret;
+}
+
+static void ufshcd_wb_probe(struct ufs_hba *hba, const u8 *desc_buf)
+{
+ struct ufs_dev_info *dev_info = &hba->dev_info;
+ u8 lun;
+ u32 d_lu_wb_buf_alloc;
+ u32 ext_ufs_feature;
+
+ if (!ufshcd_is_wb_allowed(hba))
+ return;
+
+ /*
+ * Probe WB only for UFS-2.2 and UFS-3.1 (and later) devices or
+ * UFS devices with quirk UFS_DEVICE_QUIRK_SUPPORT_EXTENDED_FEATURES
+ * enabled
+ */
+ if (!(dev_info->wspecversion >= 0x310 ||
+ dev_info->wspecversion == 0x220 ||
+ (hba->dev_quirks & UFS_DEVICE_QUIRK_SUPPORT_EXTENDED_FEATURES)))
+ goto wb_disabled;
+
+ ext_ufs_feature = get_unaligned_be32(desc_buf +
+ DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP);
+
+ if (!(ext_ufs_feature & UFS_DEV_WRITE_BOOSTER_SUP))
+ goto wb_disabled;
+
+ /*
+ * WB may be supported but not configured while provisioning. The spec
+ * says, in dedicated wb buffer mode, a max of 1 lun would have wb
+ * buffer configured.
+ */
+ dev_info->wb_buffer_type = desc_buf[DEVICE_DESC_PARAM_WB_TYPE];
+
+ dev_info->ext_wb_sup = get_unaligned_be16(desc_buf +
+ DEVICE_DESC_PARAM_EXT_WB_SUP);
+
+ dev_info->b_presrv_uspc_en =
+ desc_buf[DEVICE_DESC_PARAM_WB_PRESRV_USRSPC_EN];
+
+ if (dev_info->wb_buffer_type == WB_BUF_MODE_SHARED) {
+ if (!get_unaligned_be32(desc_buf +
+ DEVICE_DESC_PARAM_WB_SHARED_ALLOC_UNITS))
+ goto wb_disabled;
+ } else {
+ for (lun = 0; lun < UFS_UPIU_MAX_WB_LUN_ID; lun++) {
+ d_lu_wb_buf_alloc = 0;
+ ufshcd_read_unit_desc_param(hba,
+ lun,
+ UNIT_DESC_PARAM_WB_BUF_ALLOC_UNITS,
+ (u8 *)&d_lu_wb_buf_alloc,
+ sizeof(d_lu_wb_buf_alloc));
+ if (d_lu_wb_buf_alloc) {
+ dev_info->wb_dedicated_lu = lun;
+ break;
+ }
+ }
+
+ if (!d_lu_wb_buf_alloc)
+ goto wb_disabled;
+ }
+
+ if (!ufshcd_is_wb_buf_lifetime_available(hba))
+ goto wb_disabled;
+
+ return;
+
+wb_disabled:
+ hba->caps &= ~UFSHCD_CAP_WB_EN;
+}
+
+static void ufshcd_temp_notif_probe(struct ufs_hba *hba, const u8 *desc_buf)
+{
+ struct ufs_dev_info *dev_info = &hba->dev_info;
+ u32 ext_ufs_feature;
+ u8 mask = 0;
+
+ if (!(hba->caps & UFSHCD_CAP_TEMP_NOTIF) || dev_info->wspecversion < 0x300)
+ return;
+
+ ext_ufs_feature = get_unaligned_be32(desc_buf + DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP);
+
+ if (ext_ufs_feature & UFS_DEV_LOW_TEMP_NOTIF)
+ mask |= MASK_EE_TOO_LOW_TEMP;
+
+ if (ext_ufs_feature & UFS_DEV_HIGH_TEMP_NOTIF)
+ mask |= MASK_EE_TOO_HIGH_TEMP;
+
+ if (mask) {
+ ufshcd_enable_ee(hba, mask);
+ ufs_hwmon_probe(hba, mask);
+ }
+}
+
+static void ufshcd_device_lvl_exception_probe(struct ufs_hba *hba, u8 *desc_buf)
+{
+ u32 ext_ufs_feature;
+
+ if (hba->dev_info.wspecversion < 0x410)
+ return;
+
+ ext_ufs_feature = get_unaligned_be32(desc_buf +
+ DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP);
+ if (!(ext_ufs_feature & UFS_DEV_LVL_EXCEPTION_SUP))
+ return;
+
+ atomic_set(&hba->dev_lvl_exception_count, 0);
+ ufshcd_enable_ee(hba, MASK_EE_DEV_LVL_EXCEPTION);
+}
+
+static void ufshcd_set_rtt(struct ufs_hba *hba)
+{
+ struct ufs_dev_info *dev_info = &hba->dev_info;
+ u32 rtt = 0;
+ u32 dev_rtt = 0;
+
+ /* RTT override makes sense only for UFS-4.0 and above */
+ if (dev_info->wspecversion < 0x400)
+ return;
+
+ if (ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_MAX_NUM_OF_RTT, 0, 0, &dev_rtt)) {
+ dev_err(hba->dev, "failed reading bMaxNumOfRTT\n");
+ return;
+ }
+
+ /* do not override if it was already written */
+ if (dev_rtt != DEFAULT_MAX_NUM_RTT)
+ return;
+
+ rtt = min_t(int, dev_info->rtt_cap, hba->nortt);
+
+ if (rtt == dev_rtt)
+ return;
+
+ if (ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_MAX_NUM_OF_RTT, 0, 0, &rtt))
+ dev_err(hba->dev, "failed writing bMaxNumOfRTT\n");
+}
+
+void ufshcd_fixup_dev_quirks(struct ufs_hba *hba,
+ const struct ufs_dev_quirk *fixups)
+{
+ const struct ufs_dev_quirk *f;
+ struct ufs_dev_info *dev_info = &hba->dev_info;
+
+ if (!fixups)
+ return;
+
+ for (f = fixups; f->quirk; f++) {
+ if ((f->wmanufacturerid == dev_info->wmanufacturerid ||
+ f->wmanufacturerid == UFS_ANY_VENDOR) &&
+ ((dev_info->model &&
+ STR_PRFX_EQUAL(f->model, dev_info->model)) ||
+ !strcmp(f->model, UFS_ANY_MODEL)))
+ hba->dev_quirks |= f->quirk;
+ }
+}
+EXPORT_SYMBOL_GPL(ufshcd_fixup_dev_quirks);
+
+static void ufs_fixup_device_setup(struct ufs_hba *hba)
+{
+ /* fix by general quirk table */
+ ufshcd_fixup_dev_quirks(hba, ufs_fixups);
+
+ /* allow vendors to fix quirks */
+ 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;
+
+ /* Skip update RTC if RPM state is not RPM_ACTIVE */
+ if (ufshcd_rpm_get_if_active(hba) <= 0)
+ return;
+
+ err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_WRITE_ATTR, QUERY_ATTR_IDN_SECONDS_PASSED,
+ 0, 0, &val);
+ ufshcd_rpm_put(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 &&
+ !hba->clk_gating.active_reqs)
+ ufshcd_update_rtc(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(hba->dev_info.rtc_update_period));
+}
+
+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;
+ }
+
+ /*
+ * 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;
+}
+
+/**
+ * ufshcd_create_device_id - Generate unique device identifier string
+ * @hba: per-adapter instance
+ * @desc_buf: device descriptor buffer
+ *
+ * Creates a unique device ID string combining manufacturer ID, spec version,
+ * model name, serial number (as hex), device version, and manufacture date.
+ *
+ * Returns: Allocated device ID string on success, NULL on failure
+ */
+static char *ufshcd_create_device_id(struct ufs_hba *hba, u8 *desc_buf)
+{
+ struct ufs_dev_info *dev_info = &hba->dev_info;
+ u16 manufacture_date;
+ u16 device_version;
+ u8 *serial_number;
+ char *serial_hex;
+ char *device_id;
+ u8 serial_index;
+ int serial_len;
+ int ret;
+
+ serial_index = desc_buf[DEVICE_DESC_PARAM_SN];
+
+ ret = ufshcd_read_string_desc(hba, serial_index, &serial_number, SD_RAW);
+ if (ret < 0) {
+ dev_err(hba->dev, "Failed reading Serial Number. err = %d\n", ret);
+ return NULL;
+ }
+
+ device_version = get_unaligned_be16(&desc_buf[DEVICE_DESC_PARAM_DEV_VER]);
+ manufacture_date = get_unaligned_be16(&desc_buf[DEVICE_DESC_PARAM_MANF_DATE]);
+
+ serial_len = ret;
+ /* Allocate buffer for hex string: 2 chars per byte + null terminator */
+ serial_hex = kzalloc(serial_len * 2 + 1, GFP_KERNEL);
+ if (!serial_hex) {
+ kfree(serial_number);
+ return NULL;
+ }
+
+ bin2hex(serial_hex, serial_number, serial_len);
+
+ /*
+ * Device ID format is ABI with secure world - do not change without firmware
+ * coordination.
+ */
+ device_id = kasprintf(GFP_KERNEL, "%04X-%04X-%s-%s-%04X-%04X",
+ dev_info->wmanufacturerid, dev_info->wspecversion,
+ dev_info->model, serial_hex, device_version,
+ manufacture_date);
+
+ kfree(serial_hex);
+ kfree(serial_number);
+
+ if (!device_id)
+ dev_warn(hba->dev, "Failed to allocate unique device ID\n");
+
+ return device_id;
+}
+
+static int ufs_get_device_desc(struct ufs_hba *hba)
+{
+ struct ufs_dev_info *dev_info = &hba->dev_info;
+ struct Scsi_Host *shost = hba->host;
+ int err;
+ u8 model_index;
+ u8 *desc_buf;
+
+ desc_buf = kzalloc(QUERY_DESC_MAX_SIZE, GFP_KERNEL);
+ if (!desc_buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_DEVICE, 0, 0, desc_buf,
+ QUERY_DESC_MAX_SIZE);
+ if (err) {
+ dev_err(hba->dev, "%s: Failed reading Device Desc. err = %d\n",
+ __func__, err);
+ goto out;
+ }
+
+ /*
+ * getting vendor (manufacturerID) and Bank Index in big endian
+ * format
+ */
+ dev_info->wmanufacturerid = desc_buf[DEVICE_DESC_PARAM_MANF_ID] << 8 |
+ desc_buf[DEVICE_DESC_PARAM_MANF_ID + 1];
+
+ /* getting Specification Version in big endian format */
+ dev_info->wspecversion = desc_buf[DEVICE_DESC_PARAM_SPEC_VER] << 8 |
+ desc_buf[DEVICE_DESC_PARAM_SPEC_VER + 1];
+ dev_info->bqueuedepth = desc_buf[DEVICE_DESC_PARAM_Q_DPTH];
+
+ /*
+ * According to the UFS standard, the UFS device queue depth
+ * (bQueueDepth) must be in the range 1..255 if the shared queueing
+ * architecture is supported. bQueueDepth is zero if the shared queueing
+ * architecture is not supported.
+ */
+ if (dev_info->bqueuedepth)
+ shost->cmd_per_lun = min(hba->nutrs, dev_info->bqueuedepth) -
+ UFSHCD_NUM_RESERVED;
+ else
+ shost->cmd_per_lun = shost->can_queue;
+
+ dev_info->rtt_cap = desc_buf[DEVICE_DESC_PARAM_RTT_CAP];
+
+ dev_info->hid_sup = get_unaligned_be32(desc_buf +
+ DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP) &
+ UFS_DEV_HID_SUPPORT;
+
+ model_index = desc_buf[DEVICE_DESC_PARAM_PRDCT_NAME];
+
+ err = ufshcd_read_string_desc(hba, model_index,
+ &dev_info->model, SD_ASCII_STD);
+ if (err < 0) {
+ dev_err(hba->dev, "%s: Failed reading Product Name. err = %d\n",
+ __func__, err);
+ goto out;
+ }
+
+ /* Generate unique device ID */
+ dev_info->device_id = ufshcd_create_device_id(hba, desc_buf);
+
+ hba->luns_avail = desc_buf[DEVICE_DESC_PARAM_NUM_LU] +
+ desc_buf[DEVICE_DESC_PARAM_NUM_WLU];
+
+ ufs_fixup_device_setup(hba);
+
+ ufshcd_wb_probe(hba, desc_buf);
+
+ ufshcd_temp_notif_probe(hba, desc_buf);
+
+ if (dev_info->wspecversion >= 0x410) {
+ hba->critical_health_count = 0;
+ ufshcd_enable_ee(hba, MASK_EE_HEALTH_CRITICAL);
+ }
+
+ ufs_init_rtc(hba, desc_buf);
+
+ ufshcd_device_lvl_exception_probe(hba, desc_buf);
+
+ /*
+ * ufshcd_read_string_desc returns size of the string
+ * reset the error value
+ */
+ err = 0;
+
+out:
+ kfree(desc_buf);
+ return err;
+}
+
+static void ufs_put_device_desc(struct ufs_hba *hba)
+{
+ struct ufs_dev_info *dev_info = &hba->dev_info;
+
+ kfree(dev_info->model);
+ dev_info->model = NULL;
+ kfree(dev_info->device_id);
+ dev_info->device_id = NULL;
+}
+
+/**
+ * ufshcd_quirk_tune_host_pa_tactivate - Ensures that host PA_TACTIVATE is
+ * less than device PA_TACTIVATE time.
+ * @hba: per-adapter instance
+ *
+ * Some UFS devices require host PA_TACTIVATE to be lower than device
+ * PA_TACTIVATE, we need to enable UFS_DEVICE_QUIRK_HOST_PA_TACTIVATE quirk
+ * for such devices.
+ *
+ * Return: zero on success, non-zero error value on failure.
+ */
+static int ufshcd_quirk_tune_host_pa_tactivate(struct ufs_hba *hba)
+{
+ int ret = 0;
+ u32 granularity, peer_granularity;
+ u32 pa_tactivate, peer_pa_tactivate;
+ u32 pa_tactivate_us, peer_pa_tactivate_us;
+ static const u8 gran_to_us_table[] = {1, 4, 8, 16, 32, 100};
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_GRANULARITY),
+ &granularity);
+ if (ret)
+ goto out;
+
+ ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_GRANULARITY),
+ &peer_granularity);
+ if (ret)
+ goto out;
+
+ if ((granularity < PA_GRANULARITY_MIN_VAL) ||
+ (granularity > PA_GRANULARITY_MAX_VAL)) {
+ dev_err(hba->dev, "%s: invalid host PA_GRANULARITY %d",
+ __func__, granularity);
+ return -EINVAL;
+ }
+
+ if ((peer_granularity < PA_GRANULARITY_MIN_VAL) ||
+ (peer_granularity > PA_GRANULARITY_MAX_VAL)) {
+ dev_err(hba->dev, "%s: invalid device PA_GRANULARITY %d",
+ __func__, peer_granularity);
+ return -EINVAL;
+ }
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_TACTIVATE), &pa_tactivate);
+ if (ret)
+ goto out;
+
+ ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_TACTIVATE),
+ &peer_pa_tactivate);
+ if (ret)
+ goto out;
+
+ pa_tactivate_us = pa_tactivate * gran_to_us_table[granularity - 1];
+ peer_pa_tactivate_us = peer_pa_tactivate *
+ gran_to_us_table[peer_granularity - 1];
+
+ if (pa_tactivate_us >= peer_pa_tactivate_us) {
+ u32 new_peer_pa_tactivate;
+
+ new_peer_pa_tactivate = pa_tactivate_us /
+ gran_to_us_table[peer_granularity - 1];
+ new_peer_pa_tactivate++;
+ ret = ufshcd_dme_peer_set(hba, UIC_ARG_MIB(PA_TACTIVATE),
+ new_peer_pa_tactivate);
+ }
+
+out:
+ return ret;
+}
+
+/**
+ * ufshcd_quirk_override_pa_h8time - Ensures proper adjustment of PA_HIBERN8TIME.
+ * @hba: per-adapter instance
+ *
+ * Some UFS devices require specific adjustments to the PA_HIBERN8TIME parameter
+ * to ensure proper hibernation timing. This function retrieves the current
+ * PA_HIBERN8TIME value and increments it by 100us.
+ */
+static void ufshcd_quirk_override_pa_h8time(struct ufs_hba *hba)
+{
+ u32 pa_h8time;
+ int ret;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_HIBERN8TIME), &pa_h8time);
+ if (ret) {
+ dev_err(hba->dev, "Failed to get PA_HIBERN8TIME: %d\n", ret);
+ return;
+ }
+
+ /* Increment by 1 to increase hibernation time by 100 µs */
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HIBERN8TIME), pa_h8time + 1);
+ if (ret)
+ dev_err(hba->dev, "Failed updating PA_HIBERN8TIME: %d\n", ret);
+}
+
+static void ufshcd_tune_unipro_params(struct ufs_hba *hba)
+{
+ ufshcd_vops_apply_dev_quirks(hba);
+
+ if (hba->dev_quirks & UFS_DEVICE_QUIRK_PA_TACTIVATE)
+ /* set 1ms timeout for PA_TACTIVATE */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TACTIVATE), 10);
+
+ if (hba->dev_quirks & UFS_DEVICE_QUIRK_HOST_PA_TACTIVATE)
+ ufshcd_quirk_tune_host_pa_tactivate(hba);
+
+ if (hba->dev_quirks & UFS_DEVICE_QUIRK_PA_HIBER8TIME)
+ ufshcd_quirk_override_pa_h8time(hba);
+
+ ufshcd_apply_valid_tx_eq_settings(hba);
+}
+
+static void ufshcd_clear_dbg_ufs_stats(struct ufs_hba *hba)
+{
+ hba->ufs_stats.hibern8_exit_cnt = 0;
+ hba->ufs_stats.last_hibern8_exit_tstamp = ktime_set(0, 0);
+ hba->req_abort_count = 0;
+}
+
+static int ufshcd_device_geo_params_init(struct ufs_hba *hba)
+{
+ int err;
+ u8 *desc_buf;
+
+ desc_buf = kzalloc(QUERY_DESC_MAX_SIZE, GFP_KERNEL);
+ if (!desc_buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_GEOMETRY, 0, 0,
+ desc_buf, QUERY_DESC_MAX_SIZE);
+ if (err) {
+ dev_err(hba->dev, "%s: Failed reading Geometry Desc. err = %d\n",
+ __func__, err);
+ goto out;
+ }
+
+ if (desc_buf[GEOMETRY_DESC_PARAM_MAX_NUM_LUN] == 1)
+ hba->dev_info.max_lu_supported = 32;
+ else if (desc_buf[GEOMETRY_DESC_PARAM_MAX_NUM_LUN] == 0)
+ hba->dev_info.max_lu_supported = 8;
+
+ hba->dev_info.rpmb_io_size = desc_buf[GEOMETRY_DESC_PARAM_RPMB_RW_SIZE];
+
+out:
+ kfree(desc_buf);
+ return err;
+}
+
+struct ufs_ref_clk {
+ unsigned long freq_hz;
+ enum ufs_ref_clk_freq val;
+};
+
+static const struct ufs_ref_clk ufs_ref_clk_freqs[] = {
+ {19200000, REF_CLK_FREQ_19_2_MHZ},
+ {26000000, REF_CLK_FREQ_26_MHZ},
+ {38400000, REF_CLK_FREQ_38_4_MHZ},
+ {52000000, REF_CLK_FREQ_52_MHZ},
+ {0, REF_CLK_FREQ_INVAL},
+};
+
+static enum ufs_ref_clk_freq
+ufs_get_bref_clk_from_hz(unsigned long freq)
+{
+ int i;
+
+ for (i = 0; ufs_ref_clk_freqs[i].freq_hz; i++)
+ if (ufs_ref_clk_freqs[i].freq_hz == freq)
+ return ufs_ref_clk_freqs[i].val;
+
+ return REF_CLK_FREQ_INVAL;
+}
+
+void ufshcd_parse_dev_ref_clk_freq(struct ufs_hba *hba, struct clk *refclk)
+{
+ unsigned long freq;
+
+ freq = clk_get_rate(refclk);
+
+ hba->dev_ref_clk_freq =
+ ufs_get_bref_clk_from_hz(freq);
+
+ if (hba->dev_ref_clk_freq == REF_CLK_FREQ_INVAL)
+ dev_err(hba->dev,
+ "invalid ref_clk setting = %ld\n", freq);
+}
+
+static int ufshcd_set_dev_ref_clk(struct ufs_hba *hba)
+{
+ int err;
+ u32 ref_clk;
+ u32 freq = hba->dev_ref_clk_freq;
+
+ err = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_REF_CLK_FREQ, 0, 0, &ref_clk);
+
+ if (err) {
+ dev_err(hba->dev, "failed reading bRefClkFreq. err = %d\n",
+ err);
+ goto out;
+ }
+
+ if (ref_clk == freq)
+ goto out; /* nothing to update */
+
+ err = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_REF_CLK_FREQ, 0, 0, &freq);
+
+ if (err) {
+ dev_err(hba->dev, "bRefClkFreq setting to %lu Hz failed\n",
+ ufs_ref_clk_freqs[freq].freq_hz);
+ goto out;
+ }
+
+ dev_dbg(hba->dev, "bRefClkFreq setting to %lu Hz succeeded\n",
+ ufs_ref_clk_freqs[freq].freq_hz);
+
+out:
+ return err;
+}
+
+static int ufshcd_device_params_init(struct ufs_hba *hba)
+{
+ bool flag;
+ int ret;
+
+ /* Init UFS geometry descriptor related parameters */
+ ret = ufshcd_device_geo_params_init(hba);
+ if (ret)
+ goto out;
+
+ /* Check and apply UFS device quirks */
+ ret = ufs_get_device_desc(hba);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed getting device info. err = %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ufshcd_set_rtt(hba);
+
+ ufshcd_get_ref_clk_gating_wait(hba);
+
+ if (!ufshcd_query_flag_retry(hba, UPIU_QUERY_OPCODE_READ_FLAG,
+ QUERY_FLAG_IDN_PWR_ON_WPE, 0, &flag))
+ hba->dev_info.f_power_on_wp_en = flag;
+
+ /* Probe maximum power mode co-supported by both UFS host and device */
+ if (ufshcd_get_max_pwr_mode(hba))
+ dev_err(hba->dev,
+ "%s: Failed getting max supported power mode\n",
+ __func__);
+
+ ufshcd_retrieve_tx_eq_settings(hba);
+out:
+ return ret;
+}
+
+static void ufshcd_set_timestamp_attr(struct ufs_hba *hba)
+{
+ struct ufs_dev_info *dev_info = &hba->dev_info;
+ u64 ts_ns;
+ int err;
+
+ if (dev_info->wspecversion < 0x400 ||
+ hba->dev_quirks & UFS_DEVICE_QUIRK_NO_TIMESTAMP_SUPPORT)
+ return;
+
+ ts_ns = ktime_get_real_ns();
+ err = ufshcd_query_attr_qword(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
+ QUERY_ATTR_IDN_TIMESTAMP, 0, 0, &ts_ns);
+ if (err)
+ dev_err(hba->dev, "%s: failed to set timestamp %d\n",
+ __func__, err);
+}
+
+/**
+ * ufshcd_add_lus - probe and add UFS logical units
+ * @hba: per-adapter instance
+ *
+ * Return: 0 upon success; < 0 upon failure.
+ */
+static int ufshcd_add_lus(struct ufs_hba *hba)
+{
+ int ret;
+
+ /* Add required well known logical units to scsi mid layer */
+ ret = ufshcd_scsi_add_wlus(hba);
+ if (ret)
+ goto out;
+
+ /* Initialize devfreq after UFS device is detected */
+ if (ufshcd_is_clkscaling_supported(hba)) {
+ memcpy(&hba->clk_scaling.saved_pwr_info,
+ &hba->pwr_info,
+ sizeof(struct ufs_pa_layer_attr));
+ hba->clk_scaling.is_allowed = true;
+
+ ret = ufshcd_devfreq_init(hba);
+ if (ret)
+ goto out;
+
+ hba->clk_scaling.is_enabled = true;
+ ufshcd_init_clk_scaling_sysfs(hba);
+ }
+
+ /*
+ * The RTC update code accesses the hba->ufs_device_wlun->sdev_gendev
+ * pointer and hence must only be started after the WLUN pointer has
+ * been initialized by ufshcd_scsi_add_wlus().
+ */
+ schedule_delayed_work(&hba->ufs_rtc_update_work,
+ msecs_to_jiffies(UFS_RTC_UPDATE_INTERVAL_MS));
+
+ ufs_bsg_probe(hba);
+ scsi_scan_host(hba->host);
+ ufs_rpmb_probe(hba);
+
+out:
+ return ret;
+}
+
+/* SDB - Single Doorbell */
+static void ufshcd_release_sdb_queue(struct ufs_hba *hba, int nutrs)
+{
+ size_t ucdl_size, utrdl_size;
+
+ ucdl_size = ufshcd_get_ucd_size(hba) * (nutrs - UFSHCD_NUM_RESERVED);
+ dmam_free_coherent(hba->dev, ucdl_size, hba->ucdl_base_addr,
+ hba->ucdl_dma_addr);
+
+ utrdl_size = sizeof(struct utp_transfer_req_desc) * nutrs;
+ dmam_free_coherent(hba->dev, utrdl_size, hba->utrdl_base_addr,
+ hba->utrdl_dma_addr);
+}
+
+static int ufshcd_alloc_mcq(struct ufs_hba *hba)
+{
+ int ret;
+ int old_nutrs = hba->nutrs;
+
+ ret = ufshcd_get_hba_mac(hba);
+ if (ret < 0)
+ return ret;
+
+ hba->nutrs = ret;
+ ret = ufshcd_mcq_init(hba);
+ if (ret)
+ goto err;
+
+ /*
+ * Previously allocated memory for nutrs may not be enough in MCQ mode.
+ * Number of supported tags in MCQ mode may be larger than SDB mode.
+ */
+ if (hba->nutrs != old_nutrs) {
+ ufshcd_release_sdb_queue(hba, old_nutrs);
+ ret = ufshcd_memory_alloc(hba);
+ if (ret)
+ goto err;
+ ufshcd_host_memory_configure(hba);
+ }
+
+ ret = ufshcd_mcq_memory_alloc(hba);
+ if (ret)
+ goto err;
+
+ hba->host->can_queue = hba->nutrs - UFSHCD_NUM_RESERVED;
+
+ return 0;
+err:
+ hba->nutrs = old_nutrs;
+ return ret;
+}
+
+static void ufshcd_config_mcq(struct ufs_hba *hba)
+{
+ int ret;
+
+ ret = ufshcd_mcq_vops_config_esi(hba);
+ hba->mcq_esi_enabled = !ret;
+ dev_info(hba->dev, "ESI %sconfigured\n", ret ? "is not " : "");
+
+ ufshcd_mcq_make_queues_operational(hba);
+ ufshcd_mcq_config_mac(hba, hba->nutrs);
+
+ dev_info(hba->dev, "MCQ configured, nr_queues=%d, io_queues=%d, read_queue=%d, poll_queues=%d, queue_depth=%d\n",
+ hba->nr_hw_queues, hba->nr_queues[HCTX_TYPE_DEFAULT],
+ hba->nr_queues[HCTX_TYPE_READ], hba->nr_queues[HCTX_TYPE_POLL],
+ hba->nutrs);
+}
+
+/**
+ * ufshcd_get_op_mode - get UFS operating mode.
+ * @hba: per-adapter instance
+ *
+ * Use the PA_PWRMODE value to represent the operating mode of UFS.
+ *
+ */
+static enum ufs_op_mode ufshcd_get_op_mode(struct ufs_hba *hba)
+{
+ u32 mode;
+ u8 rx_mode;
+ u8 tx_mode;
+
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PWRMODE), &mode);
+ rx_mode = (mode >> PWRMODE_RX_OFFSET) & PWRMODE_MASK;
+ tx_mode = mode & PWRMODE_MASK;
+
+ if ((rx_mode == SLOW_MODE || rx_mode == SLOWAUTO_MODE) &&
+ (tx_mode == SLOW_MODE || tx_mode == SLOWAUTO_MODE))
+ return LS_MODE;
+
+ return HS_MODE;
+}
+
+static int ufshcd_post_device_init(struct ufs_hba *hba)
+{
+ int ret;
+
+ ufshcd_tune_unipro_params(hba);
+
+ /* UFS device is also active now */
+ ufshcd_set_ufs_dev_active(hba);
+
+ /* Indicate that DME QoS Monitor has been reset */
+ atomic_set(&hba->dme_qos_notification, 0x1);
+ if (hba->dme_qos_sysfs_handle)
+ sysfs_notify_dirent(hba->dme_qos_sysfs_handle);
+
+ ufshcd_force_reset_auto_bkops(hba);
+
+ ufshcd_set_timestamp_attr(hba);
+
+ if (!hba->max_pwr_info.is_valid)
+ return 0;
+
+ /*
+ * Set the right value to bRefClkFreq in LS_MODE before attempting to
+ * switch to HS gears.
+ */
+ if (ufshcd_get_op_mode(hba) == LS_MODE &&
+ hba->dev_ref_clk_freq != REF_CLK_FREQ_INVAL)
+ ufshcd_set_dev_ref_clk(hba);
+
+ /* Gear up to HS gear. */
+ ret = ufshcd_config_pwr_mode(hba, &hba->max_pwr_info.info,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed setting power mode, err = %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ufshcd_device_init(struct ufs_hba *hba, bool init_dev_params)
+{
+ int ret;
+
+ WARN_ON_ONCE(!hba->scsi_host_added);
+
+ hba->ufshcd_state = UFSHCD_STATE_RESET;
+
+ ret = ufshcd_link_startup(hba);
+ if (ret)
+ return ret;
+
+ if (hba->quirks & UFSHCD_QUIRK_SKIP_PH_CONFIGURATION)
+ return ret;
+
+ /* Debug counters initialization */
+ ufshcd_clear_dbg_ufs_stats(hba);
+
+ /* UniPro link is active now */
+ ufshcd_set_link_active(hba);
+
+ /* Reconfigure MCQ upon reset */
+ if (hba->mcq_enabled && !init_dev_params) {
+ ufshcd_config_mcq(hba);
+ ufshcd_mcq_enable(hba);
+ }
+
+ /* Verify device initialization by sending NOP OUT UPIU */
+ ret = ufshcd_verify_dev_init(hba);
+ if (ret)
+ return ret;
+
+ /* Initiate UFS initialization, and waiting until completion */
+ ret = ufshcd_complete_dev_init(hba);
+ if (ret)
+ return ret;
+
+ /*
+ * Initialize UFS device parameters used by driver, these
+ * parameters are associated with UFS descriptors.
+ */
+ if (init_dev_params) {
+ ret = ufshcd_device_params_init(hba);
+ if (ret)
+ return ret;
+ if (is_mcq_supported(hba) &&
+ hba->quirks & UFSHCD_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH) {
+ ufshcd_config_mcq(hba);
+ ufshcd_mcq_enable(hba);
+ }
+ }
+
+ return ufshcd_post_device_init(hba);
+}
+
+/**
+ * ufshcd_probe_hba - probe hba to detect device and initialize it
+ * @hba: per-adapter instance
+ * @init_dev_params: whether or not to call ufshcd_device_params_init().
+ *
+ * Execute link-startup and verify device initialization
+ *
+ * Return: 0 upon success; < 0 upon failure.
+ */
+static int ufshcd_probe_hba(struct ufs_hba *hba, bool init_dev_params)
+{
+ int ret;
+
+ if (!hba->pm_op_in_progress &&
+ (hba->quirks & UFSHCD_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH)) {
+ /* Reset the device and controller before doing reinit */
+ ufshcd_device_reset(hba);
+ ufs_put_device_desc(hba);
+ ufshcd_hba_stop(hba);
+ ret = ufshcd_hba_enable(hba);
+ if (ret) {
+ dev_err(hba->dev, "Host controller enable failed\n");
+ ufshcd_print_evt_hist(hba);
+ ufshcd_print_host_state(hba);
+ return ret;
+ }
+
+ /* Reinit the device */
+ ret = ufshcd_device_init(hba, init_dev_params);
+ if (ret)
+ return ret;
+ }
+
+ ufshcd_print_pwr_info(hba);
+
+ /*
+ * bActiveICCLevel is volatile for UFS device (as per latest v2.1 spec)
+ * and for removable UFS card as well, hence always set the parameter.
+ * Note: Error handler may issue the device reset hence resetting
+ * bActiveICCLevel as well so it is always safe to set this here.
+ */
+ ufshcd_set_active_icc_lvl(hba);
+
+ /* Enable UFS Write Booster if supported */
+ ufshcd_configure_wb(hba);
+
+ if (hba->ee_usr_mask)
+ ufshcd_write_ee_control(hba);
+ ufshcd_configure_auto_hibern8(hba);
+
+ return 0;
+}
+
+/**
+ * ufshcd_async_scan - asynchronous execution for probing hba
+ * @data: data pointer to pass to this function
+ * @cookie: cookie data
+ */
+static void ufshcd_async_scan(void *data, async_cookie_t cookie)
+{
+ struct ufs_hba *hba = (struct ufs_hba *)data;
+ ktime_t probe_start;
+ int ret;
+
+ down(&hba->host_sem);
+ /* Initialize hba, detect and initialize UFS device */
+ probe_start = ktime_get();
+ ret = ufshcd_probe_hba(hba, true);
+ ufshcd_process_probe_result(hba, probe_start, ret);
+ up(&hba->host_sem);
+ if (ret)
+ goto out;
+
+ /* Probe and add UFS logical units */
+ ret = ufshcd_add_lus(hba);
+
+out:
+ pm_runtime_put_sync(hba->dev);
+
+ if (ret)
+ dev_err(hba->dev, "%s failed: %d\n", __func__, ret);
+}
+
+static enum scsi_timeout_action ufshcd_eh_timed_out(struct scsi_cmnd *scmd)
+{
+ struct ufs_hba *hba = shost_priv(scmd->device->host);
+
+ if (!hba->pm_op_in_progress) {
+ /* Activate the error handler in the SCSI core. */
+ return SCSI_EH_NOT_HANDLED;
+ }
+
+ /*
+ * Handle the timeout directly to prevent a deadlock between
+ * ufshcd_set_dev_pwr_mode() and ufshcd_err_handler().
+ */
+ ufshcd_link_recovery(hba);
+ dev_info(hba->dev, "%s() finished; outstanding_tasks = %#lx.\n",
+ __func__, hba->outstanding_tasks);
+
+ /*
+ * ufshcd_link_recovery() may already have completed @scmd, e.g. via
+ * the existing MCQ force-completion path.
+ */
+ if (!test_bit(SCMD_STATE_COMPLETE, &scmd->state)) {
+ if (!hba->mcq_enabled) {
+ unsigned long flags;
+ struct request *rq = scsi_cmd_to_rq(scmd);
+
+ spin_lock_irqsave(&hba->outstanding_lock, flags);
+ __clear_bit(rq->tag, &hba->outstanding_reqs);
+ spin_unlock_irqrestore(&hba->outstanding_lock, flags);
+ }
+
+ if (ufshcd_is_scsi_cmd(scmd)) {
+ set_host_byte(scmd, DID_REQUEUE);
+ ufshcd_release_scsi_cmd(hba, scmd);
+ } else {
+ set_host_byte(scmd, DID_TIME_OUT);
+ }
+
+ scsi_done(scmd);
+ }
+
+ return SCSI_EH_DONE;
+}
+
+static const struct attribute_group *ufshcd_driver_groups[] = {
+ &ufs_sysfs_unit_descriptor_group,
+ &ufs_sysfs_lun_attributes_group,
+ NULL,
+};
+
+static struct ufs_hba_variant_params ufs_hba_vps = {
+ .hba_enable_delay_us = 1000,
+ .wb_flush_threshold = UFS_WB_BUF_REMAIN_PERCENT(40),
+ .devfreq_profile.polling_ms = 100,
+ .devfreq_profile.target = ufshcd_devfreq_target,
+ .devfreq_profile.get_dev_status = ufshcd_devfreq_get_dev_status,
+ .ondemand_data.upthreshold = 70,
+ .ondemand_data.downdifferential = 5,
+};
+
+static const struct scsi_host_template ufshcd_driver_template = {
+ .module = THIS_MODULE,
+ .name = UFSHCD,
+ .proc_name = UFSHCD,
+ .map_queues = ufshcd_map_queues,
+ .cmd_size = sizeof(struct ufshcd_lrb),
+ .init_cmd_priv = ufshcd_init_cmd_priv,
+ .queuecommand = ufshcd_queuecommand,
+ .queue_reserved_command = ufshcd_queue_reserved_command,
+ .nr_reserved_cmds = UFSHCD_NUM_RESERVED,
+ .mq_poll = ufshcd_poll,
+ .sdev_init = ufshcd_sdev_init,
+ .sdev_configure = ufshcd_sdev_configure,
+ .sdev_destroy = ufshcd_sdev_destroy,
+ .change_queue_depth = ufshcd_change_queue_depth,
+ .eh_abort_handler = ufshcd_abort,
+ .eh_device_reset_handler = ufshcd_eh_device_reset_handler,
+ .eh_host_reset_handler = ufshcd_eh_host_reset_handler,
+ .eh_timed_out = ufshcd_eh_timed_out,
+ .this_id = -1,
+ .sg_tablesize = SG_ALL,
+ .max_segment_size = PRDT_DATA_BYTE_COUNT_MAX,
+ .max_sectors = SZ_1M / SECTOR_SIZE,
+ .max_host_blocked = 1,
+ .host_tagset = true,
+ .track_queue_depth = 1,
+ .skip_settle_delay = 1,
+ .sdev_groups = ufshcd_driver_groups,
+};
+
+static int ufshcd_config_vreg_load(struct device *dev, struct ufs_vreg *vreg,
+ int ua)
+{
+ int ret;
+
+ if (!vreg)
+ return 0;
+
+ /*
+ * "set_load" operation shall be required on those regulators
+ * which specifically configured current limitation. Otherwise
+ * zero max_uA may cause unexpected behavior when regulator is
+ * enabled or set as high power mode.
+ */
+ if (!vreg->max_uA)
+ return 0;
+
+ ret = regulator_set_load(vreg->reg, ua);
+ if (ret < 0) {
+ dev_err(dev, "%s: %s set load (ua=%d) failed, err=%d\n",
+ __func__, vreg->name, ua, ret);
+ }
+
+ return ret;
+}
+
+static inline int ufshcd_config_vreg_lpm(struct ufs_hba *hba,
+ struct ufs_vreg *vreg)
+{
+ return ufshcd_config_vreg_load(hba->dev, vreg, UFS_VREG_LPM_LOAD_UA);
+}
+
+static inline int ufshcd_config_vreg_hpm(struct ufs_hba *hba,
+ struct ufs_vreg *vreg)
+{
+ if (!vreg)
+ return 0;
+
+ return ufshcd_config_vreg_load(hba->dev, vreg, vreg->max_uA);
+}
+
+static int ufshcd_config_vreg(struct device *dev,
+ struct ufs_vreg *vreg, bool on)
+{
+ if (regulator_count_voltages(vreg->reg) <= 0)
+ return 0;
+
+ return ufshcd_config_vreg_load(dev, vreg, on ? vreg->max_uA : 0);
+}
+
+static int ufshcd_enable_vreg(struct device *dev, struct ufs_vreg *vreg)
+{
+ int ret = 0;
+
+ if (!vreg || vreg->enabled)
+ goto out;
+
+ ret = ufshcd_config_vreg(dev, vreg, true);
+ if (!ret)
+ ret = regulator_enable(vreg->reg);
+
+ if (!ret)
+ vreg->enabled = true;
+ else
+ dev_err(dev, "%s: %s enable failed, err=%d\n",
+ __func__, vreg->name, ret);
+out:
+ return ret;
+}
+
+static int ufshcd_disable_vreg(struct device *dev, struct ufs_vreg *vreg)
+{
+ int ret = 0;
+
+ if (!vreg || !vreg->enabled || vreg->always_on)
+ goto out;
+
+ ret = regulator_disable(vreg->reg);
+
+ if (!ret) {
+ /* ignore errors on applying disable config */
+ ufshcd_config_vreg(dev, vreg, false);
+ vreg->enabled = false;
+ } else {
+ dev_err(dev, "%s: %s disable failed, err=%d\n",
+ __func__, vreg->name, ret);
+ }
+out:
+ return ret;
+}
+
+static int ufshcd_setup_vreg(struct ufs_hba *hba, bool on)
+{
+ int ret = 0;
+ struct device *dev = hba->dev;
+ struct ufs_vreg_info *info = &hba->vreg_info;
+
+ ret = ufshcd_toggle_vreg(dev, info->vcc, on);
+ if (ret)
+ goto out;
+
+ ret = ufshcd_toggle_vreg(dev, info->vccq, on);
+ if (ret)
+ goto out;
+
+ ret = ufshcd_toggle_vreg(dev, info->vccq2, on);
+
+out:
+ if (ret) {
+ ufshcd_toggle_vreg(dev, info->vccq2, false);
+ ufshcd_toggle_vreg(dev, info->vccq, false);
+ ufshcd_toggle_vreg(dev, info->vcc, false);
+ }
+ return ret;
+}
+
+static int ufshcd_setup_hba_vreg(struct ufs_hba *hba, bool on)
+{
+ struct ufs_vreg_info *info = &hba->vreg_info;
+
+ return ufshcd_toggle_vreg(hba->dev, info->vdd_hba, on);
+}
+
+int ufshcd_get_vreg(struct device *dev, struct ufs_vreg *vreg)
+{
+ int ret = 0;
+
+ if (!vreg)
+ goto out;
+
+ vreg->reg = devm_regulator_get(dev, vreg->name);
+ if (IS_ERR(vreg->reg)) {
+ ret = PTR_ERR(vreg->reg);
+ dev_err(dev, "%s: %s get failed, err=%d\n",
+ __func__, vreg->name, ret);
+ }
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_get_vreg);
+
+static int ufshcd_init_vreg(struct ufs_hba *hba)
+{
+ int ret = 0;
+ struct device *dev = hba->dev;
+ struct ufs_vreg_info *info = &hba->vreg_info;
+
+ ret = ufshcd_get_vreg(dev, info->vcc);
+ if (ret)
+ goto out;
+
+ ret = ufshcd_get_vreg(dev, info->vccq);
+ if (!ret)
+ ret = ufshcd_get_vreg(dev, info->vccq2);
+out:
+ return ret;
+}
+
+static int ufshcd_init_hba_vreg(struct ufs_hba *hba)
+{
+ struct ufs_vreg_info *info = &hba->vreg_info;
+
+ return ufshcd_get_vreg(hba->dev, info->vdd_hba);
+}
+
+static int ufshcd_setup_clocks(struct ufs_hba *hba, bool on)
+{
+ int ret = 0;
+ struct ufs_clk_info *clki;
+ struct list_head *head = &hba->clk_list_head;
+ ktime_t start = ktime_get();
+ bool clk_state_changed = false;
+
+ if (list_empty(head))
+ goto out;
+
+ ret = ufshcd_vops_setup_clocks(hba, on, PRE_CHANGE);
+ if (ret)
+ return ret;
+
+ list_for_each_entry(clki, head, list) {
+ if (!IS_ERR_OR_NULL(clki->clk)) {
+ /*
+ * Don't disable clocks which are needed
+ * to keep the link active.
+ */
+ if (ufshcd_is_link_active(hba) &&
+ clki->keep_link_active)
+ continue;
+
+ clk_state_changed = on ^ clki->enabled;
+ if (on && !clki->enabled) {
+ ret = clk_prepare_enable(clki->clk);
+ if (ret) {
+ dev_err(hba->dev, "%s: %s prepare enable failed, %d\n",
+ __func__, clki->name, ret);
+ goto out;
+ }
+ } else if (!on && clki->enabled) {
+ clk_disable_unprepare(clki->clk);
+ }
+ clki->enabled = on;
+ dev_dbg(hba->dev, "%s: clk: %s %sabled\n", __func__,
+ clki->name, on ? "en" : "dis");
+ }
+ }
+
+ ret = ufshcd_vops_setup_clocks(hba, on, POST_CHANGE);
+ if (ret)
+ return ret;
+
+ if (!ufshcd_is_clkscaling_supported(hba))
+ ufshcd_pm_qos_update(hba, on);
+out:
+ if (ret) {
+ list_for_each_entry(clki, head, list) {
+ if (!IS_ERR_OR_NULL(clki->clk) && clki->enabled)
+ clk_disable_unprepare(clki->clk);
+ }
+ } else if (!ret && on && hba->clk_gating.is_initialized) {
+ scoped_guard(spinlock_irqsave, &hba->clk_gating.lock)
+ hba->clk_gating.state = CLKS_ON;
+ trace_ufshcd_clk_gating(hba,
+ hba->clk_gating.state);
+ }
+
+ if (clk_state_changed)
+ trace_ufshcd_profile_clk_gating(hba,
+ (on ? "on" : "off"),
+ ktime_to_us(ktime_sub(ktime_get(), start)), ret);
+ return ret;
+}
+
+static enum ufs_ref_clk_freq ufshcd_parse_ref_clk_property(struct ufs_hba *hba)
+{
+ u32 freq;
+ int ret = device_property_read_u32(hba->dev, "ref-clk-freq", &freq);
+
+ if (ret) {
+ dev_dbg(hba->dev, "Cannot query 'ref-clk-freq' property = %d", ret);
+ return REF_CLK_FREQ_INVAL;
+ }
+
+ return ufs_get_bref_clk_from_hz(freq);
+}
+
+static int ufshcd_init_clocks(struct ufs_hba *hba)
+{
+ int ret = 0;
+ struct ufs_clk_info *clki;
+ struct device *dev = hba->dev;
+ struct list_head *head = &hba->clk_list_head;
+
+ if (list_empty(head))
+ goto out;
+
+ list_for_each_entry(clki, head, list) {
+ if (!clki->name)
+ continue;
+
+ clki->clk = devm_clk_get(dev, clki->name);
+ if (IS_ERR(clki->clk)) {
+ ret = PTR_ERR(clki->clk);
+ dev_err(dev, "%s: %s clk get failed, %d\n",
+ __func__, clki->name, ret);
+ goto out;
+ }
+
+ /*
+ * Parse device ref clk freq as per device tree "ref_clk".
+ * Default dev_ref_clk_freq is set to REF_CLK_FREQ_INVAL
+ * in ufshcd_alloc_host().
+ */
+ if (!strcmp(clki->name, "ref_clk"))
+ ufshcd_parse_dev_ref_clk_freq(hba, clki->clk);
+
+ if (clki->max_freq) {
+ ret = clk_set_rate(clki->clk, clki->max_freq);
+ if (ret) {
+ dev_err(hba->dev, "%s: %s clk set rate(%dHz) failed, %d\n",
+ __func__, clki->name,
+ clki->max_freq, ret);
+ goto out;
+ }
+ clki->curr_freq = clki->max_freq;
+ }
+ dev_dbg(dev, "%s: clk: %s, rate: %lu\n", __func__,
+ clki->name, clk_get_rate(clki->clk));
+ }
+
+ /* Set Max. frequency for all clocks */
+ if (hba->use_pm_opp) {
+ ret = ufshcd_opp_set_rate(hba, ULONG_MAX);
+ if (ret) {
+ dev_err(hba->dev, "%s: failed to set OPP: %d", __func__,
+ ret);
+ goto out;
+ }
+ }
+
+out:
+ return ret;
+}
+
+static int ufshcd_variant_hba_init(struct ufs_hba *hba)
+{
+ int err = ufshcd_vops_init(hba);
+
+ if (err)
+ dev_err_probe(hba->dev, err,
+ "%s: variant %s init failed with err %d\n",
+ __func__, ufshcd_get_var_name(hba), err);
+ return err;
+}
+
+static int ufshcd_hba_init(struct ufs_hba *hba)
+{
+ int err;
+
+ /*
+ * Handle host controller power separately from the UFS device power
+ * rails as it will help controlling the UFS host controller power
+ * collapse easily which is different than UFS device power collapse.
+ * Also, enable the host controller power before we go ahead with rest
+ * of the initialization here.
+ */
+ err = ufshcd_init_hba_vreg(hba);
+ if (err)
+ goto out;
+
+ err = ufshcd_setup_hba_vreg(hba, true);
+ if (err)
+ goto out;
+
+ err = ufshcd_init_clocks(hba);
+ if (err)
+ goto out_disable_hba_vreg;
+
+ if (hba->dev_ref_clk_freq == REF_CLK_FREQ_INVAL)
+ hba->dev_ref_clk_freq = ufshcd_parse_ref_clk_property(hba);
+
+ err = ufshcd_setup_clocks(hba, true);
+ if (err)
+ goto out_disable_hba_vreg;
+
+ err = ufshcd_init_vreg(hba);
+ if (err)
+ goto out_disable_clks;
+
+ err = ufshcd_setup_vreg(hba, true);
+ if (err)
+ goto out_disable_clks;
+
+ err = ufshcd_variant_hba_init(hba);
+ if (err)
+ goto out_disable_vreg;
+
+ ufs_debugfs_hba_init(hba);
+ ufs_fault_inject_hba_init(hba);
+
+ hba->is_powered = true;
+ goto out;
+
+out_disable_vreg:
+ ufshcd_setup_vreg(hba, false);
+out_disable_clks:
+ ufshcd_setup_clocks(hba, false);
+out_disable_hba_vreg:
+ ufshcd_setup_hba_vreg(hba, false);
+out:
+ return err;
+}
+
+static void ufshcd_hba_exit(struct ufs_hba *hba)
+{
+ if (hba->is_powered) {
+ ufshcd_pm_qos_exit(hba);
+ ufshcd_exit_clk_scaling(hba);
+ ufshcd_exit_clk_gating(hba);
+ if (hba->eh_wq)
+ destroy_workqueue(hba->eh_wq);
+ ufs_debugfs_hba_exit(hba);
+ ufshcd_vops_exit(hba);
+ ufshcd_setup_vreg(hba, false);
+ ufshcd_setup_clocks(hba, false);
+ ufshcd_setup_hba_vreg(hba, false);
+ hba->is_powered = false;
+ ufs_put_device_desc(hba);
+ }
+ sysfs_put(hba->dme_qos_sysfs_handle);
+}
+
+static int ufshcd_execute_start_stop(struct scsi_device *sdev,
+ enum ufs_dev_pwr_mode pwr_mode,
+ struct scsi_sense_hdr *sshdr)
+{
+ const unsigned char cdb[6] = { START_STOP, 0, 0, 0, pwr_mode << 4, 0 };
+ struct scsi_failure failure_defs[] = {
+ {
+ .allowed = 2,
+ .result = SCMD_FAILURE_RESULT_ANY,
+ },
+ };
+ struct scsi_failures failures = {
+ .failure_definitions = failure_defs,
+ };
+ const struct scsi_exec_args args = {
+ .failures = &failures,
+ .sshdr = sshdr,
+ .req_flags = BLK_MQ_REQ_PM,
+ .scmd_flags = SCMD_FAIL_IF_RECOVERING,
+ };
+
+ return scsi_execute_cmd(sdev, cdb, REQ_OP_DRV_IN, /*buffer=*/NULL,
+ /*bufflen=*/0, /*timeout=*/10 * HZ, /*retries=*/0,
+ &args);
+}
+
+/**
+ * ufshcd_set_dev_pwr_mode - sends START STOP UNIT command to set device
+ * power mode
+ * @hba: per adapter instance
+ * @pwr_mode: device power mode to set
+ *
+ * Return: 0 if requested power mode is set successfully;
+ * < 0 if failed to set the requested power mode.
+ */
+static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba,
+ enum ufs_dev_pwr_mode pwr_mode)
+{
+ struct scsi_sense_hdr sshdr;
+ struct scsi_device *sdp;
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ sdp = hba->ufs_device_wlun;
+ if (sdp && scsi_device_online(sdp))
+ ret = scsi_device_get(sdp);
+ else
+ ret = -ENODEV;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ if (ret)
+ return ret;
+
+ /*
+ * If scsi commands fail, the scsi mid-layer schedules scsi error-
+ * handling, which would wait for host to be resumed. Since we know
+ * we are functional while we are here, skip host resume in error
+ * handling context.
+ */
+ WRITE_ONCE(hba->host->eh_noresume, 1);
+
+ /*
+ * Current function would be generally called from the power management
+ * callbacks hence set the RQF_PM flag so that it doesn't resume the
+ * already suspended childs.
+ */
+ ret = ufshcd_execute_start_stop(sdp, pwr_mode, &sshdr);
+ if (ret) {
+ sdev_printk(KERN_WARNING, sdp,
+ "START_STOP failed for power mode: %d, result %x\n",
+ pwr_mode, ret);
+ if (ret > 0) {
+ if (scsi_sense_valid(&sshdr))
+ scsi_print_sense_hdr(sdp, NULL, &sshdr);
+ ret = -EIO;
+ }
+ } else {
+ hba->curr_dev_pwr_mode = pwr_mode;
+ }
+
+ scsi_device_put(sdp);
+ WRITE_ONCE(hba->host->eh_noresume, 0);
+ return ret;
+}
+
+static int ufshcd_link_state_transition(struct ufs_hba *hba,
+ enum uic_link_state req_link_state,
+ bool check_for_bkops)
+{
+ int ret = 0;
+
+ if (req_link_state == hba->uic_link_state)
+ return 0;
+
+ if (req_link_state == UIC_LINK_HIBERN8_STATE) {
+ ret = ufshcd_uic_hibern8_enter(hba);
+ if (!ret) {
+ ufshcd_set_link_hibern8(hba);
+ } else {
+ dev_err(hba->dev, "%s: hibern8 enter failed %d\n",
+ __func__, ret);
+ goto out;
+ }
+ }
+ /*
+ * If autobkops is enabled, link can't be turned off because
+ * turning off the link would also turn off the device, except in the
+ * case of DeepSleep where the device is expected to remain powered.
+ */
+ else if ((req_link_state == UIC_LINK_OFF_STATE) &&
+ (!check_for_bkops || !hba->auto_bkops_enabled)) {
+ /*
+ * Let's make sure that link is in low power mode, we are doing
+ * this currently by putting the link in Hibern8. Otherway to
+ * put the link in low power mode is to send the DME end point
+ * to device and then send the DME reset command to local
+ * unipro. But putting the link in hibern8 is much faster.
+ *
+ * Note also that putting the link in Hibern8 is a requirement
+ * for entering DeepSleep.
+ */
+ ret = ufshcd_uic_hibern8_enter(hba);
+ if (ret) {
+ dev_err(hba->dev, "%s: hibern8 enter failed %d\n",
+ __func__, ret);
+ goto out;
+ }
+ /*
+ * Change controller state to "reset state" which
+ * should also put the link in off/reset state
+ */
+ ufshcd_hba_stop(hba);
+ /*
+ * TODO: Check if we need any delay to make sure that
+ * controller is reset
+ */
+ ufshcd_set_link_off(hba);
+ }
+
+out:
+ return ret;
+}
+
+static void ufshcd_vreg_set_lpm(struct ufs_hba *hba)
+{
+ bool vcc_off = false;
+
+ /*
+ * It seems some UFS devices may keep drawing more than sleep current
+ * (atleast for 500us) from UFS rails (especially from VCCQ rail).
+ * To avoid this situation, add 2ms delay before putting these UFS
+ * rails in LPM mode.
+ */
+ if (!ufshcd_is_link_active(hba) &&
+ hba->dev_quirks & UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM)
+ usleep_range(2000, 2100);
+
+ /*
+ * If UFS device is either in UFS_Sleep turn off VCC rail to save some
+ * power.
+ *
+ * If UFS device and link is in OFF state, all power supplies (VCC,
+ * VCCQ, VCCQ2) can be turned off if power on write protect is not
+ * required. If UFS link is inactive (Hibern8 or OFF state) and device
+ * is in sleep state, put VCCQ & VCCQ2 rails in LPM mode.
+ *
+ * Ignore the error returned by ufshcd_toggle_vreg() as device is anyway
+ * in low power state which would save some power.
+ *
+ * If Write Booster is enabled and the device needs to flush the WB
+ * buffer OR if bkops status is urgent for WB, keep Vcc on.
+ */
+ if (ufshcd_is_ufs_dev_poweroff(hba) && ufshcd_is_link_off(hba) &&
+ !hba->dev_info.is_lu_power_on_wp) {
+ ufshcd_setup_vreg(hba, false);
+ vcc_off = true;
+ } else if (!ufshcd_is_ufs_dev_active(hba)) {
+ ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, false);
+ vcc_off = true;
+ if (ufshcd_is_link_hibern8(hba) || ufshcd_is_link_off(hba)) {
+ ufshcd_config_vreg_lpm(hba, hba->vreg_info.vccq);
+ ufshcd_config_vreg_lpm(hba, hba->vreg_info.vccq2);
+ }
+ }
+
+ /*
+ * All UFS devices require delay after VCC power rail is turned-off.
+ */
+ if (vcc_off && hba->vreg_info.vcc && !hba->vreg_info.vcc->always_on)
+ usleep_range(hba->vcc_off_delay_us,
+ hba->vcc_off_delay_us + 100);
+}
+
+#ifdef CONFIG_PM
+static int ufshcd_vreg_set_hpm(struct ufs_hba *hba)
+{
+ bool vcc_on = false;
+ int ret = 0;
+
+ if (ufshcd_is_ufs_dev_poweroff(hba) && ufshcd_is_link_off(hba) &&
+ !hba->dev_info.is_lu_power_on_wp) {
+ ret = ufshcd_setup_vreg(hba, true);
+ vcc_on = true;
+ } else if (!ufshcd_is_ufs_dev_active(hba)) {
+ if (!ufshcd_is_link_active(hba)) {
+ ret = ufshcd_config_vreg_hpm(hba, hba->vreg_info.vccq);
+ if (ret)
+ goto vcc_disable;
+ ret = ufshcd_config_vreg_hpm(hba, hba->vreg_info.vccq2);
+ if (ret)
+ goto vccq_lpm;
+ }
+ ret = ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, true);
+ vcc_on = true;
+ }
+ goto out;
+
+vccq_lpm:
+ ufshcd_config_vreg_lpm(hba, hba->vreg_info.vccq);
+vcc_disable:
+ ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, false);
+out:
+ /*
+ * On platforms with a slow VCC ramp-up, a delay is needed after
+ * turning on VCC to ensure the voltage is stable before the
+ * reference clock is enabled.
+ */
+ if (hba->quirks & UFSHCD_QUIRK_VCC_ON_DELAY && !ret && vcc_on &&
+ hba->vreg_info.vcc && !hba->vreg_info.vcc->always_on)
+ usleep_range(1000, 1100);
+
+ return ret;
+}
+#endif /* CONFIG_PM */
+
+static void ufshcd_hba_vreg_set_lpm(struct ufs_hba *hba)
+{
+ if (ufshcd_is_link_off(hba) || ufshcd_can_aggressive_pc(hba))
+ ufshcd_setup_hba_vreg(hba, false);
+}
+
+static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba)
+{
+ if (ufshcd_is_link_off(hba) || ufshcd_can_aggressive_pc(hba))
+ ufshcd_setup_hba_vreg(hba, true);
+}
+
+static int __ufshcd_wl_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+ int ret = 0;
+ bool check_for_bkops;
+ enum ufs_pm_level pm_lvl;
+ enum ufs_dev_pwr_mode req_dev_pwr_mode;
+ enum uic_link_state req_link_state;
+
+ hba->pm_op_in_progress = true;
+ if (pm_op != UFS_SHUTDOWN_PM) {
+ pm_lvl = pm_op == UFS_RUNTIME_PM ?
+ hba->rpm_lvl : hba->spm_lvl;
+ req_dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(pm_lvl);
+ req_link_state = ufs_get_pm_lvl_to_link_pwr_state(pm_lvl);
+ } else {
+ req_dev_pwr_mode = UFS_POWERDOWN_PWR_MODE;
+ req_link_state = UIC_LINK_OFF_STATE;
+ }
+
+ /*
+ * If we can't transition into any of the low power modes
+ * just gate the clocks.
+ */
+ ufshcd_hold(hba);
+ hba->clk_gating.is_suspended = true;
+
+ if (ufshcd_is_clkscaling_supported(hba))
+ ufshcd_clk_scaling_suspend(hba, true);
+
+ if (req_dev_pwr_mode == UFS_ACTIVE_PWR_MODE &&
+ req_link_state == UIC_LINK_ACTIVE_STATE) {
+ ufshcd_disable_auto_bkops(hba);
+ flush_work(&hba->eeh_work);
+ cancel_delayed_work_sync(&hba->ufs_rtc_update_work);
+ goto vops_suspend;
+ }
+
+ if ((req_dev_pwr_mode == hba->curr_dev_pwr_mode) &&
+ (req_link_state == hba->uic_link_state))
+ goto enable_scaling;
+
+ /* UFS device & link must be active before we enter in this function */
+ if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba)) {
+ /* Wait err handler finish or trigger err recovery */
+ if (!ufshcd_eh_in_progress(hba))
+ ufshcd_force_error_recovery(hba);
+ ret = -EBUSY;
+ goto enable_scaling;
+ }
+
+ if (pm_op == UFS_RUNTIME_PM) {
+ if (ufshcd_can_autobkops_during_suspend(hba)) {
+ /*
+ * The device is idle with no requests in the queue,
+ * allow background operations if bkops status shows
+ * that performance might be impacted.
+ */
+ ret = ufshcd_bkops_ctrl(hba);
+ if (ret) {
+ /*
+ * If return err in suspend flow, IO will hang.
+ * Trigger error handler and break suspend for
+ * error recovery.
+ */
+ ufshcd_force_error_recovery(hba);
+ ret = -EBUSY;
+ goto enable_scaling;
+ }
+ } else {
+ /* make sure that auto bkops is disabled */
+ ufshcd_disable_auto_bkops(hba);
+ }
+ /*
+ * If device needs to do BKOP or WB buffer flush during
+ * Hibern8, keep device power mode as "active power mode"
+ * and VCC supply.
+ */
+ hba->dev_info.b_rpm_dev_flush_capable =
+ hba->auto_bkops_enabled ||
+ (((req_link_state == UIC_LINK_HIBERN8_STATE) ||
+ ((req_link_state == UIC_LINK_ACTIVE_STATE) &&
+ ufshcd_is_auto_hibern8_enabled(hba))) &&
+ ufshcd_wb_need_flush(hba));
+ }
+
+ flush_work(&hba->eeh_work);
+ cancel_delayed_work_sync(&hba->ufs_rtc_update_work);
+
+ ret = ufshcd_vops_suspend(hba, pm_op, PRE_CHANGE);
+ if (ret)
+ goto enable_scaling;
+
+ if (req_dev_pwr_mode != hba->curr_dev_pwr_mode) {
+ if (pm_op != UFS_RUNTIME_PM)
+ /* ensure that bkops is disabled */
+ ufshcd_disable_auto_bkops(hba);
+
+ if (!hba->dev_info.b_rpm_dev_flush_capable) {
+ ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);
+ if (ret && pm_op != UFS_SHUTDOWN_PM) {
+ /*
+ * If return err in suspend flow, IO will hang.
+ * Trigger error handler and break suspend for
+ * error recovery.
+ */
+ ufshcd_force_error_recovery(hba);
+ ret = -EBUSY;
+ }
+ if (ret)
+ goto enable_scaling;
+ }
+ }
+
+ /*
+ * In the case of DeepSleep, the device is expected to remain powered
+ * with the link off, so do not check for bkops.
+ */
+ check_for_bkops = !ufshcd_is_ufs_dev_deepsleep(hba);
+ ret = ufshcd_link_state_transition(hba, req_link_state, check_for_bkops);
+ if (ret && pm_op != UFS_SHUTDOWN_PM) {
+ /*
+ * If return err in suspend flow, IO will hang.
+ * Trigger error handler and break suspend for
+ * error recovery.
+ */
+ ufshcd_force_error_recovery(hba);
+ ret = -EBUSY;
+ }
+ if (ret)
+ goto set_dev_active;
+
+vops_suspend:
+ /*
+ * Call vendor specific suspend callback. As these callbacks may access
+ * vendor specific host controller register space call them before the
+ * host clocks are ON.
+ */
+ ret = ufshcd_vops_suspend(hba, pm_op, POST_CHANGE);
+ if (ret)
+ goto set_link_active;
+
+ goto out;
+
+set_link_active:
+ /*
+ * Device hardware reset is required to exit DeepSleep. Also, for
+ * DeepSleep, the link is off so host reset and restore will be done
+ * further below.
+ */
+ if (ufshcd_is_ufs_dev_deepsleep(hba)) {
+ ufshcd_device_reset(hba);
+ WARN_ON(!ufshcd_is_link_off(hba));
+ }
+ if (ufshcd_is_link_hibern8(hba) && !ufshcd_uic_hibern8_exit(hba))
+ ufshcd_set_link_active(hba);
+ else if (ufshcd_is_link_off(hba))
+ ufshcd_host_reset_and_restore(hba);
+set_dev_active:
+ /* Can also get here needing to exit DeepSleep */
+ if (ufshcd_is_ufs_dev_deepsleep(hba)) {
+ ufshcd_device_reset(hba);
+ ufshcd_host_reset_and_restore(hba);
+ }
+ if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE))
+ ufshcd_disable_auto_bkops(hba);
+enable_scaling:
+ if (ufshcd_is_clkscaling_supported(hba))
+ ufshcd_clk_scaling_suspend(hba, false);
+
+ hba->dev_info.b_rpm_dev_flush_capable = false;
+out:
+ if (hba->dev_info.b_rpm_dev_flush_capable) {
+ schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
+ msecs_to_jiffies(RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS));
+ }
+
+ if (ret) {
+ ufshcd_update_evt_hist(hba, UFS_EVT_WL_SUSP_ERR, (u32)ret);
+ hba->clk_gating.is_suspended = false;
+ ufshcd_release(hba);
+ }
+ hba->pm_op_in_progress = false;
+ return ret;
+}
+
+#ifdef CONFIG_PM
+static int __ufshcd_wl_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+ int ret;
+ enum uic_link_state old_link_state = hba->uic_link_state;
+
+ hba->pm_op_in_progress = true;
+
+ /*
+ * Call vendor specific resume callback. As these callbacks may access
+ * vendor specific host controller register space call them when the
+ * host clocks are ON.
+ */
+ ret = ufshcd_vops_resume(hba, pm_op);
+ if (ret)
+ goto out;
+
+ /* For DeepSleep, the only supported option is to have the link off */
+ WARN_ON(ufshcd_is_ufs_dev_deepsleep(hba) && !ufshcd_is_link_off(hba));
+
+ if (ufshcd_is_link_hibern8(hba)) {
+ ret = ufshcd_uic_hibern8_exit(hba);
+ if (!ret) {
+ ufshcd_set_link_active(hba);
+ } else {
+ dev_err(hba->dev, "%s: hibern8 exit failed %d\n",
+ __func__, ret);
+ /*
+ * If the h8 exit fails during the runtime resume
+ * process, it becomes stuck and cannot be recovered
+ * through the error handler. To fix this, use link
+ * recovery instead of the error handler.
+ */
+ ret = ufshcd_link_recovery(hba);
+ if (ret)
+ goto vendor_suspend;
+ }
+ } else if (ufshcd_is_link_off(hba)) {
+ /*
+ * A full initialization of the host and the device is
+ * required since the link was put to off during suspend.
+ * Note, in the case of DeepSleep, the device will exit
+ * DeepSleep due to device reset.
+ */
+ ret = ufshcd_reset_and_restore(hba);
+ /*
+ * ufshcd_reset_and_restore() should have already
+ * set the link state as active
+ */
+ if (ret || !ufshcd_is_link_active(hba))
+ goto vendor_suspend;
+ }
+
+ if (!ufshcd_is_ufs_dev_active(hba)) {
+ ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE);
+ 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))
+ ufshcd_enable_auto_bkops(hba);
+ else
+ /*
+ * If BKOPs operations are urgently needed at this moment then
+ * keep auto-bkops enabled or else disable it.
+ */
+ ufshcd_bkops_ctrl(hba);
+
+ if (hba->ee_usr_mask)
+ ufshcd_write_ee_control(hba);
+
+ if (ufshcd_is_clkscaling_supported(hba))
+ ufshcd_clk_scaling_suspend(hba, false);
+
+ if (hba->dev_info.b_rpm_dev_flush_capable) {
+ hba->dev_info.b_rpm_dev_flush_capable = false;
+ cancel_delayed_work(&hba->rpm_dev_flush_recheck_work);
+ }
+
+ ufshcd_configure_auto_hibern8(hba);
+
+ goto out;
+
+set_old_link_state:
+ ufshcd_link_state_transition(hba, old_link_state, 0);
+vendor_suspend:
+ ufshcd_vops_suspend(hba, pm_op, PRE_CHANGE);
+ ufshcd_vops_suspend(hba, pm_op, POST_CHANGE);
+out:
+ if (ret)
+ ufshcd_update_evt_hist(hba, UFS_EVT_WL_RES_ERR, (u32)ret);
+ hba->clk_gating.is_suspended = false;
+ ufshcd_release(hba);
+ hba->pm_op_in_progress = false;
+ return ret;
+}
+
+static int ufshcd_wl_runtime_suspend(struct device *dev)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba;
+ int ret;
+ ktime_t start = ktime_get();
+
+ hba = shost_priv(sdev->host);
+
+ ret = __ufshcd_wl_suspend(hba, UFS_RUNTIME_PM);
+ if (ret)
+ dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
+
+ trace_ufshcd_wl_runtime_suspend(hba, ret,
+ ktime_to_us(ktime_sub(ktime_get(), start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+
+ return ret;
+}
+
+static int ufshcd_wl_runtime_resume(struct device *dev)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba;
+ int ret = 0;
+ ktime_t start = ktime_get();
+
+ hba = shost_priv(sdev->host);
+
+ ret = __ufshcd_wl_resume(hba, UFS_RUNTIME_PM);
+ if (ret)
+ dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
+
+ trace_ufshcd_wl_runtime_resume(hba, ret,
+ ktime_to_us(ktime_sub(ktime_get(), start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+
+ return ret;
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int ufshcd_wl_suspend(struct device *dev)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba;
+ int ret = 0;
+ ktime_t start = ktime_get();
+
+ hba = shost_priv(sdev->host);
+ down(&hba->host_sem);
+
+ if (pm_runtime_suspended(dev))
+ goto out;
+
+ ret = __ufshcd_wl_suspend(hba, UFS_SYSTEM_PM);
+ if (ret) {
+ dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
+ up(&hba->host_sem);
+ }
+
+out:
+ if (!ret)
+ hba->is_sys_suspended = true;
+ trace_ufshcd_wl_suspend(hba, ret,
+ ktime_to_us(ktime_sub(ktime_get(), start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+
+ return ret;
+}
+
+static int ufshcd_wl_resume(struct device *dev)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba;
+ int ret = 0;
+ ktime_t start = ktime_get();
+
+ hba = shost_priv(sdev->host);
+
+ if (pm_runtime_suspended(dev))
+ goto out;
+
+ ret = __ufshcd_wl_resume(hba, UFS_SYSTEM_PM);
+ if (ret)
+ dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
+out:
+ trace_ufshcd_wl_resume(hba, ret,
+ ktime_to_us(ktime_sub(ktime_get(), start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+ if (!ret)
+ hba->is_sys_suspended = false;
+ up(&hba->host_sem);
+ return ret;
+}
+#endif
+
+/**
+ * ufshcd_suspend - helper function for suspend operations
+ * @hba: per adapter instance
+ *
+ * This function will put disable irqs, turn off clocks
+ * and set vreg and hba-vreg in lpm mode.
+ *
+ * Return: 0 upon success; < 0 upon failure.
+ */
+static int ufshcd_suspend(struct ufs_hba *hba)
+{
+ int ret;
+
+ if (!hba->is_powered)
+ return 0;
+ /*
+ * Disable the host irq as host controller as there won't be any
+ * host controller transaction expected till resume.
+ */
+ ufshcd_disable_irq(hba);
+ ret = ufshcd_setup_clocks(hba, false);
+ if (ret) {
+ ufshcd_enable_irq(hba);
+ goto out;
+ }
+ if (ufshcd_is_clkgating_allowed(hba)) {
+ hba->clk_gating.state = CLKS_OFF;
+ trace_ufshcd_clk_gating(hba,
+ hba->clk_gating.state);
+ }
+
+ ufshcd_vreg_set_lpm(hba);
+ /* Put the host controller in low power mode if possible */
+ ufshcd_hba_vreg_set_lpm(hba);
+ ufshcd_pm_qos_update(hba, false);
+out:
+ if (ret)
+ ufshcd_update_evt_hist(hba, UFS_EVT_SUSPEND_ERR, (u32)ret);
+ return ret;
+}
+
+#ifdef CONFIG_PM
+/**
+ * ufshcd_resume - helper function for resume operations
+ * @hba: per adapter instance
+ *
+ * This function basically turns on the regulators, clocks and
+ * irqs of the hba.
+ *
+ * Return: 0 for success and non-zero for failure.
+ */
+static int ufshcd_resume(struct ufs_hba *hba)
+{
+ int ret;
+
+ if (!hba->is_powered)
+ return 0;
+
+ ufshcd_hba_vreg_set_hpm(hba);
+ ret = ufshcd_vreg_set_hpm(hba);
+ if (ret)
+ goto out;
+
+ /* Make sure clocks are enabled before accessing controller */
+ ret = ufshcd_setup_clocks(hba, true);
+ if (ret)
+ goto disable_vreg;
+
+ /* enable the host irq as host controller would be active soon */
+ ufshcd_enable_irq(hba);
+
+ goto out;
+
+disable_vreg:
+ ufshcd_vreg_set_lpm(hba);
+out:
+ if (ret)
+ ufshcd_update_evt_hist(hba, UFS_EVT_RESUME_ERR, (u32)ret);
+ return ret;
+}
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_PM_SLEEP
+/**
+ * ufshcd_system_suspend - system suspend callback
+ * @dev: Device associated with the UFS controller.
+ *
+ * Executed before putting the system into a sleep state in which the contents
+ * of main memory are preserved.
+ *
+ * Return: 0 for success and non-zero for failure.
+ */
+int ufshcd_system_suspend(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ int ret = 0;
+ ktime_t start = ktime_get();
+
+ if (pm_runtime_suspended(hba->dev))
+ goto out;
+
+ ret = ufshcd_suspend(hba);
+out:
+ trace_ufshcd_system_suspend(hba, ret,
+ ktime_to_us(ktime_sub(ktime_get(), start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+ return ret;
+}
+EXPORT_SYMBOL(ufshcd_system_suspend);
+
+/**
+ * ufshcd_system_resume - system resume callback
+ * @dev: Device associated with the UFS controller.
+ *
+ * Executed after waking the system up from a sleep state in which the contents
+ * of main memory were preserved.
+ *
+ * Return: 0 for success and non-zero for failure.
+ */
+int ufshcd_system_resume(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ ktime_t start = ktime_get();
+ int ret = 0;
+
+ if (pm_runtime_suspended(hba->dev))
+ goto out;
+
+ ret = ufshcd_resume(hba);
+
+out:
+ trace_ufshcd_system_resume(hba, ret,
+ ktime_to_us(ktime_sub(ktime_get(), start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+
+ return ret;
+}
+EXPORT_SYMBOL(ufshcd_system_resume);
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM
+/**
+ * ufshcd_runtime_suspend - runtime suspend callback
+ * @dev: Device associated with the UFS controller.
+ *
+ * Check the description of ufshcd_suspend() function for more details.
+ *
+ * Return: 0 for success and non-zero for failure.
+ */
+int ufshcd_runtime_suspend(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ int ret;
+ ktime_t start = ktime_get();
+
+ ret = ufshcd_suspend(hba);
+
+ trace_ufshcd_runtime_suspend(hba, ret,
+ ktime_to_us(ktime_sub(ktime_get(), start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+ return ret;
+}
+EXPORT_SYMBOL(ufshcd_runtime_suspend);
+
+/**
+ * ufshcd_runtime_resume - runtime resume routine
+ * @dev: Device associated with the UFS controller.
+ *
+ * This function basically brings controller
+ * to active state. Following operations are done in this function:
+ *
+ * 1. Turn on all the controller related clocks
+ * 2. Turn ON VCC rail
+ *
+ * Return: 0 upon success; < 0 upon failure.
+ */
+int ufshcd_runtime_resume(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ int ret;
+ ktime_t start = ktime_get();
+
+ ret = ufshcd_resume(hba);
+
+ trace_ufshcd_runtime_resume(hba, ret,
+ ktime_to_us(ktime_sub(ktime_get(), start)),
+ hba->curr_dev_pwr_mode, hba->uic_link_state);
+ return ret;
+}
+EXPORT_SYMBOL(ufshcd_runtime_resume);
+#endif /* CONFIG_PM */
+
+static void ufshcd_wl_shutdown(struct scsi_device *sdev)
+{
+ struct ufs_hba *hba = shost_priv(sdev->host);
+
+ down(&hba->host_sem);
+ hba->shutting_down = true;
+ up(&hba->host_sem);
+
+ /* Turn on everything while shutting down */
+ ufshcd_rpm_get_sync(hba);
+
+ ufshcd_store_tx_eq_settings(hba);
+
+ scsi_device_quiesce(sdev);
+ shost_for_each_device(sdev, hba->host) {
+ if (sdev == hba->ufs_device_wlun)
+ continue;
+ mutex_lock(&sdev->state_mutex);
+ scsi_device_set_state(sdev, SDEV_OFFLINE);
+ mutex_unlock(&sdev->state_mutex);
+ }
+ __ufshcd_wl_suspend(hba, UFS_SHUTDOWN_PM);
+
+ /*
+ * Next, turn off the UFS controller and the UFS regulators. Disable
+ * clocks.
+ */
+ if (ufshcd_is_ufs_dev_poweroff(hba) && ufshcd_is_link_off(hba))
+ ufshcd_suspend(hba);
+
+ hba->is_powered = false;
+}
+
+/**
+ * ufshcd_remove - de-allocate SCSI host and host memory space
+ * data structure memory
+ * @hba: per adapter instance
+ */
+void ufshcd_remove(struct ufs_hba *hba)
+{
+ if (hba->ufs_device_wlun)
+ ufshcd_rpm_get_sync(hba);
+ ufs_hwmon_remove(hba);
+ ufs_bsg_remove(hba);
+ ufs_rpmb_remove(hba);
+ ufs_sysfs_remove_nodes(hba->dev);
+ cancel_delayed_work_sync(&hba->ufs_rtc_update_work);
+ blk_mq_destroy_queue(hba->tmf_queue);
+ blk_put_queue(hba->tmf_queue);
+ blk_mq_free_tag_set(&hba->tmf_tag_set);
+ if (hba->scsi_host_added)
+ scsi_remove_host(hba->host);
+ /* disable interrupts */
+ ufshcd_disable_intr(hba, hba->intr_mask);
+ ufshcd_hba_stop(hba);
+ ufshcd_hba_exit(hba);
+}
+EXPORT_SYMBOL_GPL(ufshcd_remove);
+
+#ifdef CONFIG_PM_SLEEP
+int ufshcd_system_freeze(struct device *dev)
+{
+
+ return ufshcd_system_suspend(dev);
+
+}
+EXPORT_SYMBOL_GPL(ufshcd_system_freeze);
+
+int ufshcd_system_restore(struct device *dev)
+{
+
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ int ret;
+
+ ret = ufshcd_system_resume(dev);
+ if (ret)
+ return ret;
+
+ /* Configure UTRL and UTMRL base address registers */
+ ufshcd_writel(hba, lower_32_bits(hba->utrdl_dma_addr),
+ REG_UTP_TRANSFER_REQ_LIST_BASE_L);
+ ufshcd_writel(hba, upper_32_bits(hba->utrdl_dma_addr),
+ REG_UTP_TRANSFER_REQ_LIST_BASE_H);
+ ufshcd_writel(hba, lower_32_bits(hba->utmrdl_dma_addr),
+ REG_UTP_TASK_REQ_LIST_BASE_L);
+ ufshcd_writel(hba, upper_32_bits(hba->utmrdl_dma_addr),
+ REG_UTP_TASK_REQ_LIST_BASE_H);
+ /*
+ * Make sure that UTRL and UTMRL base address registers
+ * are updated with the latest queue addresses. Only after
+ * updating these addresses, we can queue the new commands.
+ */
+ ufshcd_readl(hba, REG_UTP_TASK_REQ_LIST_BASE_H);
+
+ return 0;
+
+}
+EXPORT_SYMBOL_GPL(ufshcd_system_restore);
+
+int ufshcd_system_thaw(struct device *dev)
+{
+ return ufshcd_system_resume(dev);
+}
+EXPORT_SYMBOL_GPL(ufshcd_system_thaw);
+#endif /* CONFIG_PM_SLEEP */
+
+/**
+ * ufshcd_set_dma_mask - Set dma mask based on the controller
+ * addressing capability
+ * @hba: per adapter instance
+ *
+ * Return: 0 for success, non-zero for failure.
+ */
+static int ufshcd_set_dma_mask(struct ufs_hba *hba)
+{
+ if (hba->vops && hba->vops->set_dma_mask)
+ return hba->vops->set_dma_mask(hba);
+ if (hba->capabilities & MASK_64_ADDRESSING_SUPPORT) {
+ if (!dma_set_mask_and_coherent(hba->dev, DMA_BIT_MASK(64)))
+ return 0;
+ }
+ return dma_set_mask_and_coherent(hba->dev, DMA_BIT_MASK(32));
+}
+
+/**
+ * ufshcd_devres_release - devres cleanup handler, invoked during release of
+ * hba->dev
+ * @host: pointer to SCSI host
+ */
+static void ufshcd_devres_release(void *host)
+{
+ scsi_host_put(host);
+}
+
+/**
+ * ufshcd_alloc_host - allocate Host Bus Adapter (HBA)
+ * @dev: pointer to device handle
+ * @hba_handle: driver private handle
+ *
+ * Return: 0 on success, non-zero value on failure.
+ *
+ * NOTE: There is no corresponding ufshcd_dealloc_host() because this function
+ * keeps track of its allocations using devres and deallocates everything on
+ * device removal automatically.
+ */
+int ufshcd_alloc_host(struct device *dev, struct ufs_hba **hba_handle)
+{
+ struct Scsi_Host *host;
+ struct ufs_hba *hba;
+ int err = 0;
+
+ if (!dev) {
+ dev_err(dev,
+ "Invalid memory reference for dev is NULL\n");
+ err = -ENODEV;
+ goto out_error;
+ }
+
+ host = scsi_host_alloc(&ufshcd_driver_template,
+ sizeof(struct ufs_hba));
+ if (!host) {
+ dev_err(dev, "scsi_host_alloc failed\n");
+ err = -ENOMEM;
+ goto out_error;
+ }
+
+ err = devm_add_action_or_reset(dev, ufshcd_devres_release,
+ host);
+ if (err)
+ return err;
+
+ host->nr_maps = HCTX_TYPE_POLL + 1;
+ hba = shost_priv(host);
+ hba->host = host;
+ hba->dev = dev;
+ hba->dev_ref_clk_freq = REF_CLK_FREQ_INVAL;
+ hba->nop_out_timeout = NOP_OUT_TIMEOUT;
+ ufshcd_set_sg_entry_size(hba, sizeof(struct ufshcd_sg_entry));
+ INIT_LIST_HEAD(&hba->clk_list_head);
+ INIT_LIST_HEAD(&hba->rpmbs);
+ spin_lock_init(&hba->outstanding_lock);
+
+ *hba_handle = hba;
+
+out_error:
+ return err;
+}
+EXPORT_SYMBOL(ufshcd_alloc_host);
+
+/* This function exists because blk_mq_alloc_tag_set() requires this. */
+static blk_status_t ufshcd_queue_tmf(struct blk_mq_hw_ctx *hctx,
+ const struct blk_mq_queue_data *qd)
+{
+ WARN_ON_ONCE(true);
+ return BLK_STS_NOTSUPP;
+}
+
+static const struct blk_mq_ops ufshcd_tmf_ops = {
+ .queue_rq = ufshcd_queue_tmf,
+};
+
+static int ufshcd_add_scsi_host(struct ufs_hba *hba)
+{
+ int err;
+
+ WARN_ON_ONCE(!hba->host->can_queue);
+ WARN_ON_ONCE(!hba->host->cmd_per_lun);
+
+ if (is_mcq_supported(hba)) {
+ ufshcd_mcq_enable(hba);
+ err = ufshcd_alloc_mcq(hba);
+ if (err) {
+ /* Continue with SDB mode */
+ ufshcd_mcq_disable(hba);
+ use_mcq_mode = false;
+ dev_err(hba->dev, "MCQ mode is disabled, err=%d\n",
+ err);
+ }
+ }
+ if (!is_mcq_supported(hba) && !hba->lsdb_sup) {
+ dev_err(hba->dev,
+ "%s: failed to initialize (legacy doorbell mode not supported)\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ err = scsi_add_host(hba->host, hba->dev);
+ if (err) {
+ dev_err(hba->dev, "scsi_add_host failed\n");
+ return err;
+ }
+ hba->scsi_host_added = true;
+
+ hba->tmf_tag_set = (struct blk_mq_tag_set) {
+ .nr_hw_queues = 1,
+ .queue_depth = hba->nutmrs,
+ .ops = &ufshcd_tmf_ops,
+ };
+ err = blk_mq_alloc_tag_set(&hba->tmf_tag_set);
+ if (err < 0)
+ goto remove_scsi_host;
+ hba->tmf_queue = blk_mq_alloc_queue(&hba->tmf_tag_set, NULL, NULL);
+ if (IS_ERR(hba->tmf_queue)) {
+ err = PTR_ERR(hba->tmf_queue);
+ goto free_tmf_tag_set;
+ }
+ hba->tmf_rqs = devm_kcalloc(hba->dev, hba->nutmrs,
+ sizeof(*hba->tmf_rqs), GFP_KERNEL);
+ if (!hba->tmf_rqs) {
+ err = -ENOMEM;
+ goto free_tmf_queue;
+ }
+
+ return 0;
+
+free_tmf_queue:
+ blk_mq_destroy_queue(hba->tmf_queue);
+ blk_put_queue(hba->tmf_queue);
+
+free_tmf_tag_set:
+ blk_mq_free_tag_set(&hba->tmf_tag_set);
+
+remove_scsi_host:
+ if (hba->scsi_host_added)
+ scsi_remove_host(hba->host);
+
+ return err;
+}
+
+/**
+ * ufshcd_init - Driver initialization routine
+ * @hba: per-adapter instance
+ * @mmio_base: base register address
+ * @irq: Interrupt line of device
+ *
+ * Return: 0 on success; < 0 on failure.
+ */
+int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
+{
+ int err;
+ struct Scsi_Host *host = hba->host;
+ struct device *dev = hba->dev;
+
+ /*
+ * dev_set_drvdata() must be called before any callbacks are registered
+ * that use dev_get_drvdata() (frequency scaling, clock scaling, hwmon,
+ * sysfs).
+ */
+ dev_set_drvdata(dev, hba);
+
+ if (!mmio_base) {
+ dev_err(hba->dev,
+ "Invalid memory reference for mmio_base is NULL\n");
+ err = -ENODEV;
+ goto out_error;
+ }
+
+ hba->mmio_base = mmio_base;
+ hba->irq = irq;
+ hba->vps = &ufs_hba_vps;
+
+ /*
+ * Initialize clk_gating.lock early since it is being used in
+ * ufshcd_setup_clocks()
+ */
+ spin_lock_init(&hba->clk_gating.lock);
+
+ /* Initialize mutex for PM QoS request synchronization */
+ mutex_init(&hba->pm_qos_mutex);
+
+ /*
+ * Set the default power management level for runtime and system PM.
+ * Host controller drivers can override them in their
+ * 'ufs_hba_variant_ops::init' callback.
+ *
+ * Default power saving mode is to keep UFS link in Hibern8 state
+ * and UFS device in sleep state.
+ */
+ hba->rpm_lvl = ufs_get_desired_pm_lvl_for_dev_link_state(
+ UFS_SLEEP_PWR_MODE,
+ UIC_LINK_HIBERN8_STATE);
+ hba->spm_lvl = ufs_get_desired_pm_lvl_for_dev_link_state(
+ UFS_SLEEP_PWR_MODE,
+ UIC_LINK_HIBERN8_STATE);
+
+ /*
+ * Most ufs devices require 1ms delay after vcc is powered off before
+ * it can be powered on again. Set the default to 2ms. The platform
+ * drivers can override this setting as needed.
+ */
+ hba->vcc_off_delay_us = 2000;
+
+ err = ufshcd_hba_init(hba);
+ if (err)
+ goto out_error;
+
+ /* Read capabilities registers */
+ err = ufshcd_hba_capabilities(hba);
+ if (err)
+ goto out_disable;
+
+ /* Get UFS version supported by the controller */
+ hba->ufs_version = ufshcd_get_ufs_version(hba);
+
+ /* Get Interrupt bit mask per version */
+ hba->intr_mask = ufshcd_get_intr_mask(hba);
+
+ err = ufshcd_set_dma_mask(hba);
+ if (err) {
+ dev_err(hba->dev, "set dma mask failed\n");
+ goto out_disable;
+ }
+
+ /* Allocate memory for host memory space */
+ err = ufshcd_memory_alloc(hba);
+ if (err) {
+ dev_err(hba->dev, "Memory allocation failed\n");
+ goto out_disable;
+ }
+
+ /* Configure LRB */
+ ufshcd_host_memory_configure(hba);
+
+ host->can_queue = hba->nutrs - UFSHCD_NUM_RESERVED;
+ /*
+ * Set the queue depth for WLUNs. ufs_get_device_desc() will increase
+ * host->cmd_per_lun to a larger value.
+ */
+ host->cmd_per_lun = 1;
+ host->max_id = UFSHCD_MAX_ID;
+ host->max_lun = UFS_MAX_LUNS;
+ host->max_channel = UFSHCD_MAX_CHANNEL;
+ host->unique_id = host->host_no;
+ host->max_cmd_len = UFS_CDB_SIZE;
+ host->queuecommand_may_block = !!(hba->caps & UFSHCD_CAP_CLK_GATING);
+
+ /* Use default RPM delay if host not set */
+ if (host->rpm_autosuspend_delay == 0)
+ host->rpm_autosuspend_delay = RPM_AUTOSUSPEND_DELAY_MS;
+
+ hba->max_pwr_info.is_valid = false;
+
+ /* Initialize work queues */
+ hba->eh_wq = alloc_ordered_workqueue("ufs_eh_wq_%d", WQ_MEM_RECLAIM,
+ hba->host->host_no);
+ if (!hba->eh_wq) {
+ dev_err(hba->dev, "%s: failed to create eh workqueue\n",
+ __func__);
+ err = -ENOMEM;
+ goto out_disable;
+ }
+ INIT_WORK(&hba->eh_work, ufshcd_err_handler);
+ INIT_WORK(&hba->eeh_work, ufshcd_exception_event_handler);
+
+ sema_init(&hba->host_sem, 1);
+
+ /* Initialize UIC command mutex */
+ mutex_init(&hba->uic_cmd_mutex);
+
+ /* Initialize mutex for device management commands */
+ mutex_init(&hba->dev_cmd.lock);
+
+ /* Initialize mutex for exception event control */
+ mutex_init(&hba->ee_ctrl_mutex);
+
+ mutex_init(&hba->wb_mutex);
+
+ init_rwsem(&hba->clk_scaling_lock);
+
+ ufshcd_init_clk_gating(hba);
+
+ ufshcd_init_clk_scaling(hba);
+
+ /*
+ * In order to avoid any spurious interrupt immediately after
+ * registering UFS controller interrupt handler, clear any pending UFS
+ * interrupt status and disable all the UFS interrupts.
+ */
+ ufshcd_writel(hba, ufshcd_readl(hba, REG_INTERRUPT_STATUS),
+ REG_INTERRUPT_STATUS);
+ ufshcd_writel(hba, 0, REG_INTERRUPT_ENABLE);
+ /*
+ * Make sure that UFS interrupts are disabled and any pending interrupt
+ * status is cleared before registering UFS interrupt handler.
+ */
+ ufshcd_readl(hba, REG_INTERRUPT_ENABLE);
+
+ /* IRQ registration */
+ err = devm_request_irq(dev, irq, ufshcd_intr, IRQF_SHARED, UFSHCD, hba);
+ if (err) {
+ dev_err(hba->dev, "request irq failed\n");
+ goto out_disable;
+ } else {
+ hba->is_irq_enabled = true;
+ }
+
+ /* Reset the attached device */
+ ufshcd_device_reset(hba);
+
+ ufshcd_init_crypto(hba);
+
+ /* Host controller enable */
+ err = ufshcd_hba_enable(hba);
+ if (err) {
+ dev_err(hba->dev, "Host controller enable failed\n");
+ ufshcd_print_evt_hist(hba);
+ ufshcd_print_host_state(hba);
+ goto out_disable;
+ }
+
+ 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) {
+ hba->ahit = FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, 150) |
+ FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, 3);
+ }
+
+ err = ufshcd_add_scsi_host(hba);
+ if (err)
+ goto out_disable;
+
+ /* Hold auto suspend until async scan completes */
+ pm_runtime_get_sync(dev);
+
+ /*
+ * We are assuming that device wasn't put in sleep/power-down
+ * state exclusively during the boot stage before kernel.
+ * This assumption helps avoid doing link startup twice during
+ * ufshcd_probe_hba().
+ */
+ ufshcd_set_ufs_dev_active(hba);
+
+ /* Initialize hba, detect and initialize UFS device */
+ ktime_t probe_start = ktime_get();
+
+ hba->ufshcd_state = UFSHCD_STATE_RESET;
+
+ err = ufshcd_link_startup(hba);
+ if (err)
+ goto out_disable;
+
+ if (hba->mcq_enabled)
+ ufshcd_config_mcq(hba);
+
+ if (hba->quirks & UFSHCD_QUIRK_SKIP_PH_CONFIGURATION)
+ goto initialized;
+
+ /* Debug counters initialization */
+ ufshcd_clear_dbg_ufs_stats(hba);
+
+ /* UniPro link is active now */
+ ufshcd_set_link_active(hba);
+
+ /* Verify device initialization by sending NOP OUT UPIU */
+ err = ufshcd_verify_dev_init(hba);
+ if (err)
+ goto out_disable;
+
+ /* Initiate UFS initialization, and waiting until completion */
+ err = ufshcd_complete_dev_init(hba);
+ if (err)
+ goto out_disable;
+
+ err = ufshcd_device_params_init(hba);
+ if (err)
+ goto out_disable;
+
+ err = ufshcd_post_device_init(hba);
+
+initialized:
+ ufshcd_process_probe_result(hba, probe_start, err);
+ if (err)
+ goto out_disable;
+
+ ufs_sysfs_add_nodes(hba->dev);
+ hba->dme_qos_sysfs_handle = sysfs_get_dirent(hba->dev->kobj.sd,
+ "dme_qos_notification");
+ async_schedule(ufshcd_async_scan, hba);
+
+ device_enable_async_suspend(dev);
+ ufshcd_pm_qos_init(hba);
+ return 0;
+
+out_disable:
+ hba->is_irq_enabled = false;
+ ufshcd_hba_exit(hba);
+out_error:
+ return err > 0 ? -EIO : err;
+}
+EXPORT_SYMBOL_GPL(ufshcd_init);
+
+void ufshcd_resume_complete(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ if (hba->complete_put) {
+ ufshcd_rpm_put(hba);
+ hba->complete_put = false;
+ }
+}
+EXPORT_SYMBOL_GPL(ufshcd_resume_complete);
+
+static bool ufshcd_rpm_ok_for_spm(struct ufs_hba *hba)
+{
+ struct device *dev = &hba->ufs_device_wlun->sdev_gendev;
+ enum ufs_dev_pwr_mode dev_pwr_mode;
+ enum uic_link_state link_state;
+ unsigned long flags;
+ bool res;
+
+ spin_lock_irqsave(&dev->power.lock, flags);
+ dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(hba->spm_lvl);
+ link_state = ufs_get_pm_lvl_to_link_pwr_state(hba->spm_lvl);
+ res = pm_runtime_suspended(dev) &&
+ hba->curr_dev_pwr_mode == dev_pwr_mode &&
+ hba->uic_link_state == link_state &&
+ !hba->dev_info.b_rpm_dev_flush_capable;
+ spin_unlock_irqrestore(&dev->power.lock, flags);
+
+ return res;
+}
+
+int __ufshcd_suspend_prepare(struct device *dev, bool rpm_ok_for_spm)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ int ret;
+
+ /*
+ * SCSI assumes that runtime-pm and system-pm for scsi drivers
+ * are same. And it doesn't wake up the device for system-suspend
+ * if it's runtime suspended. But ufs doesn't follow that.
+ * Refer ufshcd_resume_complete()
+ */
+ if (hba->ufs_device_wlun) {
+ /* Prevent runtime suspend */
+ ufshcd_rpm_get_noresume(hba);
+ /*
+ * Check if already runtime suspended in same state as system
+ * suspend would be.
+ */
+ if (!rpm_ok_for_spm || !ufshcd_rpm_ok_for_spm(hba)) {
+ /* RPM state is not ok for SPM, so runtime resume */
+ ret = ufshcd_rpm_resume(hba);
+ if (ret < 0 && ret != -EACCES) {
+ ufshcd_rpm_put(hba);
+ return ret;
+ }
+ }
+ hba->complete_put = true;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__ufshcd_suspend_prepare);
+
+int ufshcd_suspend_prepare(struct device *dev)
+{
+ return __ufshcd_suspend_prepare(dev, true);
+}
+EXPORT_SYMBOL_GPL(ufshcd_suspend_prepare);
+
+#ifdef CONFIG_PM_SLEEP
+static int ufshcd_wl_poweroff(struct device *dev)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ struct ufs_hba *hba = shost_priv(sdev->host);
+
+ __ufshcd_wl_suspend(hba, UFS_SHUTDOWN_PM);
+ return 0;
+}
+#endif
+
+static int ufshcd_wl_probe(struct scsi_device *sdev)
+{
+ struct device *dev = &sdev->sdev_gendev;
+
+ if (!is_device_wlun(sdev))
+ return -ENODEV;
+
+ blk_pm_runtime_init(sdev->request_queue, dev);
+ pm_runtime_set_autosuspend_delay(dev, 0);
+ pm_runtime_allow(dev);
+
+ return 0;
+}
+
+static void ufshcd_wl_remove(struct scsi_device *sdev)
+{
+ struct device *dev = &sdev->sdev_gendev;
+
+ pm_runtime_forbid(dev);
+}
+
+static const struct dev_pm_ops ufshcd_wl_pm_ops = {
+#ifdef CONFIG_PM_SLEEP
+ .suspend = ufshcd_wl_suspend,
+ .resume = ufshcd_wl_resume,
+ .freeze = ufshcd_wl_suspend,
+ .thaw = ufshcd_wl_resume,
+ .poweroff = ufshcd_wl_poweroff,
+ .restore = ufshcd_wl_resume,
+#endif
+ SET_RUNTIME_PM_OPS(ufshcd_wl_runtime_suspend, ufshcd_wl_runtime_resume, NULL)
+};
+
+static void ufshcd_check_header_layout(void)
+{
+ /*
+ * gcc compilers before version 10 cannot do constant-folding for
+ * sub-byte bitfields. Hence skip the layout checks for gcc 9 and
+ * before.
+ */
+ if (IS_ENABLED(CONFIG_CC_IS_GCC) && CONFIG_GCC_VERSION < 100000)
+ return;
+
+ BUILD_BUG_ON(((u8 *)&(struct request_desc_header){
+ .cci = 3})[0] != 3);
+
+ BUILD_BUG_ON(((u8 *)&(struct request_desc_header){
+ .ehs_length = 2})[1] != 2);
+
+ BUILD_BUG_ON(((u8 *)&(struct request_desc_header){
+ .enable_crypto = 1})[2]
+ != 0x80);
+
+ BUILD_BUG_ON((((u8 *)&(struct request_desc_header){
+ .command_type = 5,
+ .data_direction = 3,
+ .interrupt = 1,
+ })[3]) != ((5 << 4) | (3 << 1) | 1));
+
+ BUILD_BUG_ON(((__le32 *)&(struct request_desc_header){
+ .dunl = cpu_to_le32(0xdeadbeef)})[1] !=
+ cpu_to_le32(0xdeadbeef));
+
+ BUILD_BUG_ON(((u8 *)&(struct request_desc_header){
+ .ocs = 4})[8] != 4);
+
+ BUILD_BUG_ON(((u8 *)&(struct request_desc_header){
+ .cds = 5})[9] != 5);
+
+ BUILD_BUG_ON(((__le32 *)&(struct request_desc_header){
+ .dunu = cpu_to_le32(0xbadcafe)})[3] !=
+ cpu_to_le32(0xbadcafe));
+
+ BUILD_BUG_ON(((u8 *)&(struct utp_upiu_header){
+ .iid = 0xf })[4] != 0xf0);
+
+ BUILD_BUG_ON(((u8 *)&(struct utp_upiu_header){
+ .command_set_type = 0xf })[4] != 0xf);
+}
+
+/*
+ * ufs_dev_wlun_template - describes ufs device wlun
+ * ufs-device wlun - used to send pm commands
+ * All luns are consumers of ufs-device wlun.
+ *
+ * Currently, no sd driver is present for wluns.
+ * Hence the no specific pm operations are performed.
+ * With ufs design, SSU should be sent to ufs-device wlun.
+ * Hence register a scsi driver for ufs wluns only.
+ */
+static struct scsi_driver ufs_dev_wlun_template = {
+ .probe = ufshcd_wl_probe,
+ .remove = ufshcd_wl_remove,
+ .shutdown = ufshcd_wl_shutdown,
+ .gendrv = {
+ .name = "ufs_device_wlun",
+ .pm = &ufshcd_wl_pm_ops,
+ },
+};
+
+static int __init ufshcd_core_init(void)
+{
+ int ret;
+
+ ufshcd_check_header_layout();
+
+ ufs_debugfs_init();
+
+ ret = scsi_register_driver(&ufs_dev_wlun_template);
+ if (ret)
+ ufs_debugfs_exit();
+ return ret;
+}
+
+static void __exit ufshcd_core_exit(void)
+{
+ ufs_debugfs_exit();
+ scsi_unregister_driver(&ufs_dev_wlun_template);
+}
+
+module_init(ufshcd_core_init);
+module_exit(ufshcd_core_exit);
+
+MODULE_AUTHOR("Santosh Yaragnavi <santosh.sy@samsung.com>");
+MODULE_AUTHOR("Vinayak Holikatti <h.vinayak@samsung.com>");
+MODULE_DESCRIPTION("Generic UFS host controller driver Core");
+MODULE_SOFTDEP("pre: governor_simpleondemand");
+MODULE_LICENSE("GPL");
diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig
new file mode 100644
index 000000000..ff170c0b6
--- /dev/null
+++ b/drivers/ufs/host/Kconfig
@@ -0,0 +1,170 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Kernel configuration file for the UFS host controller drivers.
+#
+# Copyright (C) 2011-2013 Samsung India Software Operations
+#
+# Authors:
+# Santosh Yaraganavi <santosh.sy@samsung.com>
+# Vinayak Holikatti <h.vinayak@samsung.com>
+
+config SCSI_UFSHCD_PCI
+ tristate "PCI bus based UFS Controller support"
+ depends on PCI
+ help
+ This selects the PCI UFS Host Controller Interface. Select this if
+ you have UFS Host Controller with PCI Interface.
+
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
+
+config SCSI_UFS_DWC_TC_PCI
+ tristate "DesignWare pci support using a G210 Test Chip"
+ depends on SCSI_UFSHCD_PCI
+ help
+ Synopsys Test Chip is a PHY for prototyping purposes.
+
+ If unsure, say N.
+
+config SCSI_UFSHCD_PLATFORM
+ tristate "Platform bus based UFS Controller support"
+ depends on HAS_IOMEM
+ help
+ This selects the UFS host controller support. Select this if
+ you have an UFS controller on Platform bus.
+
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
+
+config SCSI_UFS_CDNS_PLATFORM
+ tristate "Cadence UFS Controller platform driver"
+ depends on SCSI_UFSHCD_PLATFORM
+ help
+ This selects the Cadence-specific additions to UFSHCD platform driver.
+
+ If unsure, say N.
+
+config SCSI_UFS_DWC_TC_PLATFORM
+ tristate "DesignWare platform support using a G210 Test Chip"
+ depends on OF && SCSI_UFSHCD_PLATFORM
+ help
+ Synopsys Test Chip is a PHY for prototyping purposes.
+
+ If unsure, say N.
+
+config SCSI_UFS_QCOM
+ tristate "Qualcomm specific hooks to UFS controller platform driver"
+ depends on SCSI_UFSHCD_PLATFORM && ARCH_QCOM
+ depends on GENERIC_MSI_IRQ
+ depends on RESET_CONTROLLER
+ select QCOM_INLINE_CRYPTO_ENGINE if SCSI_UFS_CRYPTO
+ help
+ This selects the QCOM specific additions to UFSHCD platform driver.
+ UFS host on QCOM needs some vendor specific configuration before
+ accessing the hardware which includes PHY configuration and vendor
+ specific registers.
+
+ Select this if you have UFS controller on QCOM chipset.
+ If unsure, say N.
+
+config SCSI_UFS_MEDIATEK
+ tristate "Mediatek specific hooks to UFS controller platform driver"
+ depends on SCSI_UFSHCD_PLATFORM && ARCH_MEDIATEK
+ depends on PM
+ depends on RESET_CONTROLLER
+ select PHY_MTK_UFS
+ select RESET_TI_SYSCON
+ help
+ This selects the Mediatek specific additions to UFSHCD platform driver.
+ UFS host on Mediatek needs some vendor specific configuration before
+ accessing the hardware which includes PHY configuration and vendor
+ specific registers.
+
+ Select this if you have UFS controller on Mediatek chipset.
+
+ If unsure, say N.
+
+config SCSI_UFS_HISI
+ tristate "Hisilicon specific hooks to UFS controller platform driver"
+ depends on (ARCH_HISI || COMPILE_TEST) && SCSI_UFSHCD_PLATFORM
+ help
+ This selects the Hisilicon specific additions to UFSHCD platform driver.
+
+ Select this if you have UFS controller on Hisilicon chipset.
+ If unsure, say N.
+
+config SCSI_UFS_RENESAS
+ tristate "Renesas specific hooks to UFS controller platform driver"
+ depends on (ARCH_RENESAS || COMPILE_TEST) && SCSI_UFSHCD_PLATFORM
+ help
+ This selects the Renesas specific additions to UFSHCD platform driver.
+ UFS host on Renesas needs some vendor specific configuration before
+ accessing the hardware.
+
+ Select this if you have UFS controller on Renesas chipset.
+
+ If unsure, say N.
+
+config SCSI_UFS_TI_J721E
+ tristate "TI glue layer for Cadence UFS Controller"
+ depends on OF && HAS_IOMEM && (ARCH_K3 || COMPILE_TEST)
+ help
+ This selects driver for TI glue layer for Cadence UFS Host
+ Controller IP.
+
+ Selects this if you have TI platform with UFS controller.
+ If unsure, say N.
+
+config SCSI_UFS_EXYNOS
+ tristate "Exynos specific hooks to UFS controller platform driver"
+ depends on SCSI_UFSHCD_PLATFORM && (ARCH_EXYNOS || COMPILE_TEST)
+ help
+ This selects the Samsung Exynos SoC specific additions to UFSHCD
+ platform driver. UFS host on Samsung Exynos SoC includes HCI and
+ UNIPRO layer, and associates with UFS-PHY driver.
+
+ Select this if you have UFS host controller on Samsung Exynos SoC.
+ If unsure, say N.
+
+config SCSI_UFS_VARIABLE_SG_ENTRY_SIZE
+ bool
+ default y if SCSI_UFS_EXYNOS && SCSI_UFS_CRYPTO
+
+config SCSI_UFS_SPRD
+ tristate "Unisoc specific hooks to UFS controller platform driver"
+ depends on SCSI_UFSHCD_PLATFORM && (ARCH_SPRD || COMPILE_TEST)
+ help
+ This selects the Unisoc specific additions to UFSHCD platform driver.
+ UFS host on Unisoc needs some vendor specific configuration before
+ accessing the hardware which includes PHY configuration and vendor
+ specific registers.
+
+ Select this if you have UFS controller on Unisoc chipset.
+ If unsure, say N.
+
+config SCSI_UFS_ROCKCHIP
+ tristate "Rockchip UFS host controller driver"
+ depends on SCSI_UFSHCD_PLATFORM && (ARCH_ROCKCHIP || COMPILE_TEST)
+ help
+ This selects the Rockchip specific additions to UFSHCD platform driver.
+ UFS host on Rockchip needs some vendor specific configuration before
+ accessing the hardware which includes PHY configuration and vendor
+ specific registers.
+
+ Select this if you have UFS controller on Rockchip chipset.
+ If unsure, say N.
+
+config SCSI_UFS_AMD_VERSAL2
+ tristate "AMD Versal Gen 2 UFS controller platform driver"
+ depends on SCSI_UFSHCD_PLATFORM && (ARCH_ZYNQMP || COMPILE_TEST)
+ help
+ This selects the AMD Versal Gen 2 specific additions on top of
+ the UFSHCD DWC and UFSHCD platform driver. UFS host on AMD
+ Versal Gen 2 needs some vendor specific configurations like PHY
+ and vendor specific register accesses before accessing the
+ hardware.
+
+ Select this if you have UFS controller on AMD Versal Gen 2 SoC.
+ If unsure, say N.
diff --git a/drivers/ufs/host/Makefile b/drivers/ufs/host/Makefile
new file mode 100644
index 000000000..7d8db67eb
--- /dev/null
+++ b/drivers/ufs/host/Makefile
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0
+
+CONTEXT_ANALYSIS := y
+
+obj-$(CONFIG_SCSI_UFS_DWC_TC_PCI) += tc-dwc-g210-pci.o ufshcd-dwc.o tc-dwc-g210.o
+obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o tc-dwc-g210.o
+obj-$(CONFIG_SCSI_UFS_CDNS_PLATFORM) += cdns-pltfrm.o
+obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
+obj-$(CONFIG_SCSI_UFS_EXYNOS) += ufs-exynos.o
+obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
+obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
+obj-$(CONFIG_SCSI_UFS_HISI) += ufs-hisi.o
+obj-$(CONFIG_SCSI_UFS_MEDIATEK) += ufs-mediatek.o
+obj-$(CONFIG_SCSI_UFS_RENESAS) += ufs-renesas.o
+obj-$(CONFIG_SCSI_UFS_ROCKCHIP) += ufs-rockchip.o
+obj-$(CONFIG_SCSI_UFS_SPRD) += ufs-sprd.o
+obj-$(CONFIG_SCSI_UFS_TI_J721E) += ti-j721e-ufs.o
+obj-$(CONFIG_SCSI_UFS_AMD_VERSAL2) += ufs-amd-versal2.o ufshcd-dwc.o
diff --git a/drivers/ufs/host/cdns-pltfrm.c b/drivers/ufs/host/cdns-pltfrm.c
new file mode 100644
index 000000000..e793e3538
--- /dev/null
+++ b/drivers/ufs/host/cdns-pltfrm.c
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Platform UFS Host driver for Cadence controller
+ *
+ * Copyright (C) 2018 Cadence Design Systems, Inc.
+ *
+ * Authors:
+ * Jan Kotas <jank@cadence.com>
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/time.h>
+
+#include "ufshcd-pltfrm.h"
+
+#define CDNS_UFS_REG_HCLKDIV 0xFC
+#define CDNS_UFS_REG_PHY_XCFGD1 0x113C
+#define CDNS_UFS_MAX_L4_ATTRS 12
+
+struct cdns_ufs_host {
+ /**
+ * cdns_ufs_dme_attr_val - for storing L4 attributes
+ */
+ u32 cdns_ufs_dme_attr_val[CDNS_UFS_MAX_L4_ATTRS];
+};
+
+/**
+ * cdns_ufs_get_l4_attr - get L4 attributes on local side
+ * @hba: per adapter instance
+ *
+ */
+static void cdns_ufs_get_l4_attr(struct ufs_hba *hba)
+{
+ struct cdns_ufs_host *host = ufshcd_get_variant(hba);
+
+ ufshcd_dme_get(hba, UIC_ARG_MIB(T_PEERDEVICEID),
+ &host->cdns_ufs_dme_attr_val[0]);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(T_PEERCPORTID),
+ &host->cdns_ufs_dme_attr_val[1]);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(T_TRAFFICCLASS),
+ &host->cdns_ufs_dme_attr_val[2]);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(T_PROTOCOLID),
+ &host->cdns_ufs_dme_attr_val[3]);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(T_CPORTFLAGS),
+ &host->cdns_ufs_dme_attr_val[4]);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(T_TXTOKENVALUE),
+ &host->cdns_ufs_dme_attr_val[5]);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(T_RXTOKENVALUE),
+ &host->cdns_ufs_dme_attr_val[6]);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(T_LOCALBUFFERSPACE),
+ &host->cdns_ufs_dme_attr_val[7]);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(T_PEERBUFFERSPACE),
+ &host->cdns_ufs_dme_attr_val[8]);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(T_CREDITSTOSEND),
+ &host->cdns_ufs_dme_attr_val[9]);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(T_CPORTMODE),
+ &host->cdns_ufs_dme_attr_val[10]);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(T_CONNECTIONSTATE),
+ &host->cdns_ufs_dme_attr_val[11]);
+}
+
+/**
+ * cdns_ufs_set_l4_attr - set L4 attributes on local side
+ * @hba: per adapter instance
+ *
+ */
+static void cdns_ufs_set_l4_attr(struct ufs_hba *hba)
+{
+ struct cdns_ufs_host *host = ufshcd_get_variant(hba);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE), 0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERDEVICEID),
+ host->cdns_ufs_dme_attr_val[0]);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERCPORTID),
+ host->cdns_ufs_dme_attr_val[1]);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_TRAFFICCLASS),
+ host->cdns_ufs_dme_attr_val[2]);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_PROTOCOLID),
+ host->cdns_ufs_dme_attr_val[3]);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_CPORTFLAGS),
+ host->cdns_ufs_dme_attr_val[4]);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_TXTOKENVALUE),
+ host->cdns_ufs_dme_attr_val[5]);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_RXTOKENVALUE),
+ host->cdns_ufs_dme_attr_val[6]);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_LOCALBUFFERSPACE),
+ host->cdns_ufs_dme_attr_val[7]);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERBUFFERSPACE),
+ host->cdns_ufs_dme_attr_val[8]);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_CREDITSTOSEND),
+ host->cdns_ufs_dme_attr_val[9]);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_CPORTMODE),
+ host->cdns_ufs_dme_attr_val[10]);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE),
+ host->cdns_ufs_dme_attr_val[11]);
+}
+
+/**
+ * cdns_ufs_set_hclkdiv() - set HCLKDIV register value based on the core_clk.
+ * @hba: host controller instance
+ *
+ * Return: zero for success and non-zero for failure.
+ */
+static int cdns_ufs_set_hclkdiv(struct ufs_hba *hba)
+{
+ struct ufs_clk_info *clki;
+ struct list_head *head = &hba->clk_list_head;
+ unsigned long core_clk_rate = 0;
+ u32 core_clk_div = 0;
+
+ if (list_empty(head))
+ return 0;
+
+ list_for_each_entry(clki, head, list) {
+ if (IS_ERR_OR_NULL(clki->clk))
+ continue;
+ if (!strcmp(clki->name, "core_clk"))
+ core_clk_rate = clk_get_rate(clki->clk);
+ }
+
+ if (!core_clk_rate) {
+ dev_err(hba->dev, "%s: unable to find core_clk rate\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ core_clk_div = core_clk_rate / USEC_PER_SEC;
+
+ ufshcd_writel(hba, core_clk_div, CDNS_UFS_REG_HCLKDIV);
+ /**
+ * Make sure the register was updated,
+ * UniPro layer will not work with an incorrect value.
+ */
+ ufshcd_readl(hba, CDNS_UFS_REG_HCLKDIV);
+
+ return 0;
+}
+
+/**
+ * cdns_ufs_hce_enable_notify() - set HCLKDIV register
+ * @hba: host controller instance
+ * @status: notify stage (pre, post change)
+ *
+ * Return: zero for success and non-zero for failure.
+ */
+static int cdns_ufs_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ if (status != PRE_CHANGE)
+ return 0;
+
+ return cdns_ufs_set_hclkdiv(hba);
+}
+
+/**
+ * cdns_ufs_hibern8_notify() - save and restore L4 attributes.
+ * @hba: host controller instance
+ * @cmd: UIC Command
+ * @status: notify stage (pre, post change)
+ */
+static void cdns_ufs_hibern8_notify(struct ufs_hba *hba, enum uic_cmd_dme cmd,
+ enum ufs_notify_change_status status)
+{
+ if (status == PRE_CHANGE && cmd == UIC_CMD_DME_HIBER_ENTER)
+ cdns_ufs_get_l4_attr(hba);
+ if (status == POST_CHANGE && cmd == UIC_CMD_DME_HIBER_EXIT)
+ cdns_ufs_set_l4_attr(hba);
+}
+
+/**
+ * cdns_ufs_link_startup_notify() - handle link startup.
+ * @hba: host controller instance
+ * @status: notify stage (pre, post change)
+ *
+ * Return: zero for success and non-zero for failure.
+ */
+static int cdns_ufs_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ if (status != PRE_CHANGE)
+ return 0;
+
+ /*
+ * Some UFS devices 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.
+ */
+ ufshcd_disable_host_tx_lcc(hba);
+
+ /*
+ * Disabling Autohibern8 feature in cadence UFS
+ * to mask unexpected interrupt trigger.
+ */
+ hba->ahit = 0;
+
+ return 0;
+}
+
+/**
+ * cdns_ufs_init - performs additional ufs initialization
+ * @hba: host controller instance
+ *
+ * Return: status of initialization.
+ */
+static int cdns_ufs_init(struct ufs_hba *hba)
+{
+ int status = 0;
+ struct cdns_ufs_host *host;
+ struct device *dev = hba->dev;
+
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+
+ if (!host)
+ return -ENOMEM;
+ ufshcd_set_variant(hba, host);
+
+ status = ufshcd_vops_phy_initialization(hba);
+
+ return status;
+}
+
+/**
+ * cdns_ufs_m31_16nm_phy_initialization - performs m31 phy initialization
+ * @hba: host controller instance
+ *
+ * Return: 0 (success).
+ */
+static int cdns_ufs_m31_16nm_phy_initialization(struct ufs_hba *hba)
+{
+ u32 data;
+
+ /* Increase RX_Advanced_Min_ActivateTime_Capability */
+ data = ufshcd_readl(hba, CDNS_UFS_REG_PHY_XCFGD1);
+ data |= BIT(24);
+ ufshcd_writel(hba, data, CDNS_UFS_REG_PHY_XCFGD1);
+
+ return 0;
+}
+
+static const struct ufs_hba_variant_ops cdns_ufs_pltfm_hba_vops = {
+ .name = "cdns-ufs-pltfm",
+ .init = cdns_ufs_init,
+ .hce_enable_notify = cdns_ufs_hce_enable_notify,
+ .link_startup_notify = cdns_ufs_link_startup_notify,
+ .hibern8_notify = cdns_ufs_hibern8_notify,
+};
+
+static const struct ufs_hba_variant_ops cdns_ufs_m31_16nm_pltfm_hba_vops = {
+ .name = "cdns-ufs-pltfm",
+ .init = cdns_ufs_init,
+ .hce_enable_notify = cdns_ufs_hce_enable_notify,
+ .link_startup_notify = cdns_ufs_link_startup_notify,
+ .phy_initialization = cdns_ufs_m31_16nm_phy_initialization,
+ .hibern8_notify = cdns_ufs_hibern8_notify,
+};
+
+static const struct of_device_id cdns_ufs_of_match[] = {
+ {
+ .compatible = "cdns,ufshc",
+ .data = &cdns_ufs_pltfm_hba_vops,
+ },
+ {
+ .compatible = "cdns,ufshc-m31-16nm",
+ .data = &cdns_ufs_m31_16nm_pltfm_hba_vops,
+ },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, cdns_ufs_of_match);
+
+/**
+ * cdns_ufs_pltfrm_probe - probe routine of the driver
+ * @pdev: pointer to platform device handle
+ *
+ * Return: zero for success and non-zero for failure.
+ */
+static int cdns_ufs_pltfrm_probe(struct platform_device *pdev)
+{
+ int err;
+ const struct of_device_id *of_id;
+ struct ufs_hba_variant_ops *vops;
+ struct device *dev = &pdev->dev;
+
+ of_id = of_match_node(cdns_ufs_of_match, dev->of_node);
+ vops = (struct ufs_hba_variant_ops *)of_id->data;
+
+ /* Perform generic probe */
+ err = ufshcd_pltfrm_init(pdev, vops);
+ if (err)
+ dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err);
+
+ return err;
+}
+
+/**
+ * cdns_ufs_pltfrm_remove - removes the ufs driver
+ * @pdev: pointer to platform device handle
+ *
+ * Return: 0 (success).
+ */
+static void cdns_ufs_pltfrm_remove(struct platform_device *pdev)
+{
+ ufshcd_pltfrm_remove(pdev);
+}
+
+static const struct dev_pm_ops cdns_ufs_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
+ SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
+ .prepare = ufshcd_suspend_prepare,
+ .complete = ufshcd_resume_complete,
+};
+
+static struct platform_driver cdns_ufs_pltfrm_driver = {
+ .probe = cdns_ufs_pltfrm_probe,
+ .remove = cdns_ufs_pltfrm_remove,
+ .driver = {
+ .name = "cdns-ufshcd",
+ .pm = &cdns_ufs_dev_pm_ops,
+ .of_match_table = cdns_ufs_of_match,
+ },
+};
+
+module_platform_driver(cdns_ufs_pltfrm_driver);
+
+MODULE_AUTHOR("Jan Kotas <jank@cadence.com>");
+MODULE_DESCRIPTION("Cadence UFS host controller platform driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/ufs/host/tc-dwc-g210-pci.c b/drivers/ufs/host/tc-dwc-g210-pci.c
new file mode 100644
index 000000000..c6d89f9c4
--- /dev/null
+++ b/drivers/ufs/host/tc-dwc-g210-pci.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Synopsys G210 Test Chip driver
+ *
+ * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
+ *
+ * Authors: Joao Pinto <jpinto@synopsys.com>
+ */
+
+#include <ufs/ufshcd.h>
+#include "ufshcd-dwc.h"
+#include "tc-dwc-g210.h"
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+
+/* Test Chip type expected values */
+#define TC_G210_20BIT 20
+#define TC_G210_40BIT 40
+#define TC_G210_INV 0
+
+static int tc_type = TC_G210_INV;
+module_param(tc_type, int, 0);
+MODULE_PARM_DESC(tc_type, "Test Chip Type (20 = 20-bit, 40 = 40-bit)");
+
+/*
+ * struct ufs_hba_dwc_vops - UFS DWC specific variant operations
+ */
+static struct ufs_hba_variant_ops tc_dwc_g210_pci_hba_vops = {
+ .name = "tc-dwc-g210-pci",
+ .link_startup_notify = ufshcd_dwc_link_startup_notify,
+};
+
+/**
+ * tc_dwc_g210_pci_remove - de-allocate PCI/SCSI host and host memory space
+ * data structure memory
+ * @pdev: pointer to PCI handle
+ */
+static void tc_dwc_g210_pci_remove(struct pci_dev *pdev)
+{
+ struct ufs_hba *hba = pci_get_drvdata(pdev);
+
+ pm_runtime_forbid(&pdev->dev);
+ pm_runtime_get_noresume(&pdev->dev);
+ ufshcd_remove(hba);
+}
+
+/**
+ * tc_dwc_g210_pci_probe - probe routine of the driver
+ * @pdev: pointer to PCI device handle
+ * @id: PCI device id
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+static int
+tc_dwc_g210_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct ufs_hba *hba;
+ void __iomem *mmio_base;
+ int err;
+
+ /* Check Test Chip type and set the specific setup routine */
+ if (tc_type == TC_G210_20BIT) {
+ tc_dwc_g210_pci_hba_vops.phy_initialization =
+ tc_dwc_g210_config_20_bit;
+ } else if (tc_type == TC_G210_40BIT) {
+ tc_dwc_g210_pci_hba_vops.phy_initialization =
+ tc_dwc_g210_config_40_bit;
+ } else {
+ dev_err(&pdev->dev, "test chip version not specified\n");
+ return -EPERM;
+ }
+
+ err = pcim_enable_device(pdev);
+ if (err) {
+ dev_err(&pdev->dev, "pcim_enable_device failed\n");
+ return err;
+ }
+
+ pci_set_master(pdev);
+
+ mmio_base = pcim_iomap_region(pdev, 0, UFSHCD);
+ if (IS_ERR(mmio_base)) {
+ dev_err(&pdev->dev, "request and iomap failed\n");
+ return PTR_ERR(mmio_base);
+ }
+
+ err = ufshcd_alloc_host(&pdev->dev, &hba);
+ if (err) {
+ dev_err(&pdev->dev, "Allocation failed\n");
+ return err;
+ }
+
+ hba->vops = &tc_dwc_g210_pci_hba_vops;
+
+ err = ufshcd_init(hba, mmio_base, pdev->irq);
+ if (err) {
+ dev_err(&pdev->dev, "Initialization failed\n");
+ return err;
+ }
+
+ pm_runtime_put_noidle(&pdev->dev);
+ pm_runtime_allow(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tc_dwc_g210_pci_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
+ SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
+ .prepare = ufshcd_suspend_prepare,
+ .complete = ufshcd_resume_complete,
+};
+
+static const struct pci_device_id tc_dwc_g210_pci_tbl[] = {
+ { PCI_VDEVICE(SYNOPSYS, 0xB101) },
+ { PCI_VDEVICE(SYNOPSYS, 0xB102) },
+ { } /* terminate list */
+};
+
+MODULE_DEVICE_TABLE(pci, tc_dwc_g210_pci_tbl);
+
+static struct pci_driver tc_dwc_g210_pci_driver = {
+ .name = "tc-dwc-g210-pci",
+ .id_table = tc_dwc_g210_pci_tbl,
+ .probe = tc_dwc_g210_pci_probe,
+ .remove = tc_dwc_g210_pci_remove,
+ .driver = {
+ .pm = &tc_dwc_g210_pci_pm_ops
+ },
+};
+
+module_pci_driver(tc_dwc_g210_pci_driver);
+
+MODULE_AUTHOR("Joao Pinto <Joao.Pinto@synopsys.com>");
+MODULE_DESCRIPTION("Synopsys Test Chip G210 PCI glue driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/ufs/host/tc-dwc-g210-pltfrm.c b/drivers/ufs/host/tc-dwc-g210-pltfrm.c
new file mode 100644
index 000000000..454ac88c3
--- /dev/null
+++ b/drivers/ufs/host/tc-dwc-g210-pltfrm.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Synopsys G210 Test Chip driver
+ *
+ * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
+ *
+ * Authors: Joao Pinto <jpinto@synopsys.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+
+#include "ufshcd-pltfrm.h"
+#include "ufshcd-dwc.h"
+#include "tc-dwc-g210.h"
+
+/*
+ * UFS DWC specific variant operations
+ */
+static struct ufs_hba_variant_ops tc_dwc_g210_20bit_pltfm_hba_vops = {
+ .name = "tc-dwc-g210-pltfm",
+ .link_startup_notify = ufshcd_dwc_link_startup_notify,
+ .phy_initialization = tc_dwc_g210_config_20_bit,
+};
+
+static struct ufs_hba_variant_ops tc_dwc_g210_40bit_pltfm_hba_vops = {
+ .name = "tc-dwc-g210-pltfm",
+ .link_startup_notify = ufshcd_dwc_link_startup_notify,
+ .phy_initialization = tc_dwc_g210_config_40_bit,
+};
+
+static const struct of_device_id tc_dwc_g210_pltfm_match[] = {
+ {
+ .compatible = "snps,g210-tc-6.00-20bit",
+ .data = &tc_dwc_g210_20bit_pltfm_hba_vops,
+ },
+ {
+ .compatible = "snps,g210-tc-6.00-40bit",
+ .data = &tc_dwc_g210_40bit_pltfm_hba_vops,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, tc_dwc_g210_pltfm_match);
+
+/**
+ * tc_dwc_g210_pltfm_probe()
+ * @pdev: pointer to platform device structure
+ *
+ */
+static int tc_dwc_g210_pltfm_probe(struct platform_device *pdev)
+{
+ int err;
+ const struct of_device_id *of_id;
+ struct ufs_hba_variant_ops *vops;
+ struct device *dev = &pdev->dev;
+
+ of_id = of_match_node(tc_dwc_g210_pltfm_match, dev->of_node);
+ vops = (struct ufs_hba_variant_ops *)of_id->data;
+
+ /* Perform generic probe */
+ err = ufshcd_pltfrm_init(pdev, vops);
+ if (err)
+ dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err);
+
+ return err;
+}
+
+/**
+ * tc_dwc_g210_pltfm_remove()
+ * @pdev: pointer to platform device structure
+ *
+ */
+static void tc_dwc_g210_pltfm_remove(struct platform_device *pdev)
+{
+ ufshcd_pltfrm_remove(pdev);
+}
+
+static const struct dev_pm_ops tc_dwc_g210_pltfm_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
+ SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
+};
+
+static struct platform_driver tc_dwc_g210_pltfm_driver = {
+ .probe = tc_dwc_g210_pltfm_probe,
+ .remove = tc_dwc_g210_pltfm_remove,
+ .driver = {
+ .name = "tc-dwc-g210-pltfm",
+ .pm = &tc_dwc_g210_pltfm_pm_ops,
+ .of_match_table = of_match_ptr(tc_dwc_g210_pltfm_match),
+ },
+};
+
+module_platform_driver(tc_dwc_g210_pltfm_driver);
+
+MODULE_ALIAS("platform:tc-dwc-g210-pltfm");
+MODULE_DESCRIPTION("Synopsys Test Chip G210 platform glue driver");
+MODULE_AUTHOR("Joao Pinto <Joao.Pinto@synopsys.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/ufs/host/tc-dwc-g210.c b/drivers/ufs/host/tc-dwc-g210.c
new file mode 100644
index 000000000..0ac53cc84
--- /dev/null
+++ b/drivers/ufs/host/tc-dwc-g210.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Synopsys G210 Test Chip driver
+ *
+ * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
+ *
+ * Authors: Joao Pinto <jpinto@synopsys.com>
+ */
+
+#include <linux/module.h>
+
+#include <ufs/ufshcd.h>
+#include <ufs/unipro.h>
+
+#include "ufshcd-dwc.h"
+#include "ufshci-dwc.h"
+#include "tc-dwc-g210.h"
+
+/**
+ * tc_dwc_g210_setup_40bit_rmmi() - configure 40-bit RMMI.
+ * @hba: Pointer to drivers structure
+ *
+ * Return: 0 on success or non-zero value on failure.
+ */
+static int tc_dwc_g210_setup_40bit_rmmi(struct ufs_hba *hba)
+{
+ static const struct ufshcd_dme_attr_val setup_attrs[] = {
+ { UIC_ARG_MIB(TX_GLOBALHIBERNATE), 0x00, DME_LOCAL },
+ { UIC_ARG_MIB(REFCLKMODE), 0x01, DME_LOCAL },
+ { UIC_ARG_MIB(CDIRECTCTRL6), 0x80, DME_LOCAL },
+ { UIC_ARG_MIB(CBDIVFACTOR), 0x08, DME_LOCAL },
+ { UIC_ARG_MIB(CBDCOCTRL5), 0x64, DME_LOCAL },
+ { UIC_ARG_MIB(CBPRGTUNING), 0x09, DME_LOCAL },
+ { UIC_ARG_MIB(RTOBSERVESELECT), 0x00, DME_LOCAL },
+ { UIC_ARG_MIB_SEL(TX_REFCLKFREQ, SELIND_LN0_TX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(TX_CFGCLKFREQVAL, SELIND_LN0_TX), 0x19,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGEXTRATTR, SELIND_LN0_TX), 0x14,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(DITHERCTRL2, SELIND_LN0_TX), 0xd6,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RX_REFCLKFREQ, SELIND_LN0_RX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RX_CFGCLKFREQVAL, SELIND_LN0_RX), 0x19,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGWIDEINLN, SELIND_LN0_RX), 4,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXCDR8, SELIND_LN0_RX), 0x80,
+ DME_LOCAL },
+ { UIC_ARG_MIB(DIRECTCTRL10), 0x04, DME_LOCAL },
+ { UIC_ARG_MIB(DIRECTCTRL19), 0x02, DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXCDR8, SELIND_LN0_RX), 0x80,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(ENARXDIRECTCFG4, SELIND_LN0_RX), 0x03,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXOVR8, SELIND_LN0_RX), 0x16,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXDIRECTCTRL2, SELIND_LN0_RX), 0x42,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(ENARXDIRECTCFG3, SELIND_LN0_RX), 0xa4,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXCALCTRL, SELIND_LN0_RX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(ENARXDIRECTCFG2, SELIND_LN0_RX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXOVR4, SELIND_LN0_RX), 0x28,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXSQCTRL, SELIND_LN0_RX), 0x1E,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXOVR6, SELIND_LN0_RX), 0x2f,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXOVR6, SELIND_LN0_RX), 0x2f,
+ DME_LOCAL },
+ { UIC_ARG_MIB(CBPRGPLL2), 0x00, DME_LOCAL },
+ };
+
+ return ufshcd_dwc_dme_set_attrs(hba, setup_attrs,
+ ARRAY_SIZE(setup_attrs));
+}
+
+/**
+ * tc_dwc_g210_setup_20bit_rmmi_lane0() - configure 20-bit RMMI Lane 0.
+ * @hba: Pointer to drivers structure
+ *
+ * Return: 0 on success or non-zero value on failure.
+ */
+static int tc_dwc_g210_setup_20bit_rmmi_lane0(struct ufs_hba *hba)
+{
+ static const struct ufshcd_dme_attr_val setup_attrs[] = {
+ { UIC_ARG_MIB_SEL(TX_REFCLKFREQ, SELIND_LN0_TX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(TX_CFGCLKFREQVAL, SELIND_LN0_TX), 0x19,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RX_CFGCLKFREQVAL, SELIND_LN0_RX), 0x19,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGEXTRATTR, SELIND_LN0_TX), 0x12,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(DITHERCTRL2, SELIND_LN0_TX), 0xd6,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RX_REFCLKFREQ, SELIND_LN0_RX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGWIDEINLN, SELIND_LN0_RX), 2,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXCDR8, SELIND_LN0_RX), 0x80,
+ DME_LOCAL },
+ { UIC_ARG_MIB(DIRECTCTRL10), 0x04, DME_LOCAL },
+ { UIC_ARG_MIB(DIRECTCTRL19), 0x02, DME_LOCAL },
+ { UIC_ARG_MIB_SEL(ENARXDIRECTCFG4, SELIND_LN0_RX), 0x03,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXOVR8, SELIND_LN0_RX), 0x16,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXDIRECTCTRL2, SELIND_LN0_RX), 0x42,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(ENARXDIRECTCFG3, SELIND_LN0_RX), 0xa4,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXCALCTRL, SELIND_LN0_RX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(ENARXDIRECTCFG2, SELIND_LN0_RX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXOVR4, SELIND_LN0_RX), 0x28,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXSQCTRL, SELIND_LN0_RX), 0x1E,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXOVR6, SELIND_LN0_RX), 0x2f,
+ DME_LOCAL },
+ { UIC_ARG_MIB(CBPRGPLL2), 0x00, DME_LOCAL },
+ };
+
+ return ufshcd_dwc_dme_set_attrs(hba, setup_attrs,
+ ARRAY_SIZE(setup_attrs));
+}
+
+/**
+ * tc_dwc_g210_setup_20bit_rmmi_lane1() - configure 20-bit RMMI Lane 1.
+ * @hba: Pointer to drivers structure
+ *
+ * Return: 0 on success or non-zero value on failure.
+ */
+static int tc_dwc_g210_setup_20bit_rmmi_lane1(struct ufs_hba *hba)
+{
+ int connected_rx_lanes = 0;
+ int connected_tx_lanes = 0;
+ int ret = 0;
+
+ static const struct ufshcd_dme_attr_val setup_tx_attrs[] = {
+ { UIC_ARG_MIB_SEL(TX_REFCLKFREQ, SELIND_LN1_TX), 0x0d,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(TX_CFGCLKFREQVAL, SELIND_LN1_TX), 0x19,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGEXTRATTR, SELIND_LN1_TX), 0x12,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(DITHERCTRL2, SELIND_LN0_TX), 0xd6,
+ DME_LOCAL },
+ };
+
+ static const struct ufshcd_dme_attr_val setup_rx_attrs[] = {
+ { UIC_ARG_MIB_SEL(RX_REFCLKFREQ, SELIND_LN1_RX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RX_CFGCLKFREQVAL, SELIND_LN1_RX), 0x19,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGWIDEINLN, SELIND_LN1_RX), 2,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXCDR8, SELIND_LN1_RX), 0x80,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(ENARXDIRECTCFG4, SELIND_LN1_RX), 0x03,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXOVR8, SELIND_LN1_RX), 0x16,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXDIRECTCTRL2, SELIND_LN1_RX), 0x42,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(ENARXDIRECTCFG3, SELIND_LN1_RX), 0xa4,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXCALCTRL, SELIND_LN1_RX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(ENARXDIRECTCFG2, SELIND_LN1_RX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXOVR4, SELIND_LN1_RX), 0x28,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXSQCTRL, SELIND_LN1_RX), 0x1E,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(CFGRXOVR6, SELIND_LN1_RX), 0x2f,
+ DME_LOCAL },
+ };
+
+ /* Get the available lane count */
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_AVAILRXDATALANES),
+ &connected_rx_lanes);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_AVAILTXDATALANES),
+ &connected_tx_lanes);
+
+ if (connected_tx_lanes == 2) {
+
+ ret = ufshcd_dwc_dme_set_attrs(hba, setup_tx_attrs,
+ ARRAY_SIZE(setup_tx_attrs));
+
+ if (ret)
+ goto out;
+ }
+
+ if (connected_rx_lanes == 2) {
+ ret = ufshcd_dwc_dme_set_attrs(hba, setup_rx_attrs,
+ ARRAY_SIZE(setup_rx_attrs));
+ }
+
+out:
+ return ret;
+}
+
+/**
+ * tc_dwc_g210_setup_20bit_rmmi() - configure 20-bit RMMI.
+ * @hba: Pointer to drivers structure
+ *
+ * Return: 0 on success or non-zero value on failure.
+ */
+static int tc_dwc_g210_setup_20bit_rmmi(struct ufs_hba *hba)
+{
+ int ret = 0;
+
+ static const struct ufshcd_dme_attr_val setup_attrs[] = {
+ { UIC_ARG_MIB(TX_GLOBALHIBERNATE), 0x00, DME_LOCAL },
+ { UIC_ARG_MIB(REFCLKMODE), 0x01, DME_LOCAL },
+ { UIC_ARG_MIB(CDIRECTCTRL6), 0xc0, DME_LOCAL },
+ { UIC_ARG_MIB(CBDIVFACTOR), 0x44, DME_LOCAL },
+ { UIC_ARG_MIB(CBDCOCTRL5), 0x64, DME_LOCAL },
+ { UIC_ARG_MIB(CBPRGTUNING), 0x09, DME_LOCAL },
+ { UIC_ARG_MIB(RTOBSERVESELECT), 0x00, DME_LOCAL },
+ };
+
+ ret = ufshcd_dwc_dme_set_attrs(hba, setup_attrs,
+ ARRAY_SIZE(setup_attrs));
+ if (ret)
+ goto out;
+
+ /* Lane 0 configuration*/
+ ret = tc_dwc_g210_setup_20bit_rmmi_lane0(hba);
+ if (ret)
+ goto out;
+
+ /* Lane 1 configuration*/
+ ret = tc_dwc_g210_setup_20bit_rmmi_lane1(hba);
+ if (ret)
+ goto out;
+
+out:
+ return ret;
+}
+
+/**
+ * tc_dwc_g210_config_40_bit() - configure 40-bit TC specific attributes.
+ * @hba: Pointer to drivers structure
+ *
+ * Return: 0 on success non-zero value on failure.
+ */
+int tc_dwc_g210_config_40_bit(struct ufs_hba *hba)
+{
+ int ret = 0;
+
+ dev_info(hba->dev, "Configuring Test Chip 40-bit RMMI\n");
+ ret = tc_dwc_g210_setup_40bit_rmmi(hba);
+ if (ret) {
+ dev_err(hba->dev, "Configuration failed\n");
+ goto out;
+ }
+
+ /* To write Shadow register bank to effective configuration block */
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01);
+ if (ret)
+ goto out;
+
+ /* To configure Debug OMC */
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_DEBUGOMC), 0x01);
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL(tc_dwc_g210_config_40_bit);
+
+/**
+ * tc_dwc_g210_config_20_bit() - configure 20-bit TC specific attributes.
+ * @hba: Pointer to drivers structure
+ *
+ * Return: 0 on success non-zero value on failure.
+ */
+int tc_dwc_g210_config_20_bit(struct ufs_hba *hba)
+{
+ int ret = 0;
+
+ dev_info(hba->dev, "Configuring Test Chip 20-bit RMMI\n");
+ ret = tc_dwc_g210_setup_20bit_rmmi(hba);
+ if (ret) {
+ dev_err(hba->dev, "Configuration failed\n");
+ goto out;
+ }
+
+ /* To write Shadow register bank to effective configuration block */
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01);
+ if (ret)
+ goto out;
+
+ /* To configure Debug OMC */
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_DEBUGOMC), 0x01);
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL(tc_dwc_g210_config_20_bit);
+
+MODULE_AUTHOR("Joao Pinto <Joao.Pinto@synopsys.com>");
+MODULE_DESCRIPTION("Synopsys G210 Test Chip driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/ufs/host/tc-dwc-g210.h b/drivers/ufs/host/tc-dwc-g210.h
new file mode 100644
index 000000000..f7154012f
--- /dev/null
+++ b/drivers/ufs/host/tc-dwc-g210.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Synopsys G210 Test Chip driver
+ *
+ * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
+ *
+ * Authors: Joao Pinto <jpinto@synopsys.com>
+ */
+
+#ifndef _TC_DWC_G210_H
+#define _TC_DWC_G210_H
+
+struct ufs_hba;
+
+int tc_dwc_g210_config_40_bit(struct ufs_hba *hba);
+int tc_dwc_g210_config_20_bit(struct ufs_hba *hba);
+
+#endif /* End of Header */
diff --git a/drivers/ufs/host/ti-j721e-ufs.c b/drivers/ufs/host/ti-j721e-ufs.c
new file mode 100644
index 000000000..43781593b
--- /dev/null
+++ b/drivers/ufs/host/ti-j721e-ufs.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+//
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#define TI_UFS_SS_CTRL 0x4
+#define TI_UFS_SS_RST_N_PCS BIT(0)
+#define TI_UFS_SS_CLK_26MHZ BIT(4)
+
+struct ti_j721e_ufs {
+ void __iomem *regbase;
+ u32 reg;
+};
+
+static int ti_j721e_ufs_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ti_j721e_ufs *ufs;
+ unsigned long clk_rate;
+ struct clk *clk;
+ int ret;
+
+ ufs = devm_kzalloc(dev, sizeof(*ufs), GFP_KERNEL);
+ if (!ufs)
+ return -ENOMEM;
+
+ ufs->regbase = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ufs->regbase))
+ return PTR_ERR(ufs->regbase);
+
+ pm_runtime_enable(dev);
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ goto disable_pm;
+
+ /* Select MPHY refclk frequency */
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ dev_err(dev, "Cannot claim MPHY clock.\n");
+ goto clk_err;
+ }
+ clk_rate = clk_get_rate(clk);
+ if (clk_rate == 26000000)
+ ufs->reg |= TI_UFS_SS_CLK_26MHZ;
+ devm_clk_put(dev, clk);
+
+ /* Take UFS slave device out of reset */
+ ufs->reg |= TI_UFS_SS_RST_N_PCS;
+ writel(ufs->reg, ufs->regbase + TI_UFS_SS_CTRL);
+
+ dev_set_drvdata(dev, ufs);
+
+ ret = of_platform_populate(pdev->dev.of_node, NULL, NULL,
+ dev);
+ if (ret) {
+ dev_err(dev, "failed to populate child nodes %d\n", ret);
+ goto clk_err;
+ }
+
+ return ret;
+
+clk_err:
+ pm_runtime_put_sync(dev);
+disable_pm:
+ pm_runtime_disable(dev);
+ return ret;
+}
+
+static void ti_j721e_ufs_remove(struct platform_device *pdev)
+{
+ of_platform_depopulate(&pdev->dev);
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+}
+
+static int ti_j721e_ufs_resume(struct device *dev)
+{
+ struct ti_j721e_ufs *ufs = dev_get_drvdata(dev);
+
+ writel(ufs->reg, ufs->regbase + TI_UFS_SS_CTRL);
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(ti_j721e_ufs_pm_ops, NULL, ti_j721e_ufs_resume);
+
+static const struct of_device_id ti_j721e_ufs_of_match[] = {
+ {
+ .compatible = "ti,j721e-ufs",
+ },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, ti_j721e_ufs_of_match);
+
+static struct platform_driver ti_j721e_ufs_driver = {
+ .probe = ti_j721e_ufs_probe,
+ .remove = ti_j721e_ufs_remove,
+ .driver = {
+ .name = "ti-j721e-ufs",
+ .of_match_table = ti_j721e_ufs_of_match,
+ .pm = pm_sleep_ptr(&ti_j721e_ufs_pm_ops),
+ },
+};
+module_platform_driver(ti_j721e_ufs_driver);
+
+MODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>");
+MODULE_DESCRIPTION("TI UFS host controller glue driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/ufs/host/ufs-amd-versal2.c b/drivers/ufs/host/ufs-amd-versal2.c
new file mode 100644
index 000000000..2154d6286
--- /dev/null
+++ b/drivers/ufs/host/ufs-amd-versal2.c
@@ -0,0 +1,561 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
+ *
+ * Authors: Sai Krishna Potthuri <sai.krishna.potthuri@amd.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/firmware/xlnx-zynqmp.h>
+#include <linux/irqreturn.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <ufs/unipro.h>
+
+#include "ufshcd-dwc.h"
+#include "ufshcd-pltfrm.h"
+#include "ufshci-dwc.h"
+
+/* PHY modes */
+#define UFSHCD_DWC_PHY_MODE_ROM 0
+
+#define MPHY_FAST_RX_AFE_CAL BIT(2)
+#define MPHY_FW_CALIB_CFG_VAL BIT(8)
+
+#define MPHY_RX_OVRD_EN BIT(3)
+#define MPHY_RX_OVRD_VAL BIT(2)
+#define MPHY_RX_ACK_MASK BIT(0)
+
+#define TIMEOUT_MICROSEC 1000000
+
+struct ufs_versal2_host {
+ struct ufs_hba *hba;
+ struct reset_control *rstc;
+ struct reset_control *rstphy;
+ u32 phy_mode;
+ unsigned long host_clk;
+ u8 attcompval0;
+ u8 attcompval1;
+ u8 ctlecompval0;
+ u8 ctlecompval1;
+};
+
+static int ufs_versal2_phy_reg_write(struct ufs_hba *hba, u32 addr, u32 val)
+{
+ static struct ufshcd_dme_attr_val phy_write_attrs[] = {
+ { UIC_ARG_MIB(CBCREGADDRLSB), 0, DME_LOCAL },
+ { UIC_ARG_MIB(CBCREGADDRMSB), 0, DME_LOCAL },
+ { UIC_ARG_MIB(CBCREGWRLSB), 0, DME_LOCAL },
+ { UIC_ARG_MIB(CBCREGWRMSB), 0, DME_LOCAL },
+ { UIC_ARG_MIB(CBCREGRDWRSEL), 1, DME_LOCAL },
+ { UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
+ };
+
+ phy_write_attrs[0].mib_val = (u8)addr;
+ phy_write_attrs[1].mib_val = (u8)(addr >> 8);
+ phy_write_attrs[2].mib_val = (u8)val;
+ phy_write_attrs[3].mib_val = (u8)(val >> 8);
+
+ return ufshcd_dwc_dme_set_attrs(hba, phy_write_attrs, ARRAY_SIZE(phy_write_attrs));
+}
+
+static int ufs_versal2_phy_reg_read(struct ufs_hba *hba, u32 addr, u32 *val)
+{
+ u32 mib_val;
+ int ret;
+ static struct ufshcd_dme_attr_val phy_read_attrs[] = {
+ { UIC_ARG_MIB(CBCREGADDRLSB), 0, DME_LOCAL },
+ { UIC_ARG_MIB(CBCREGADDRMSB), 0, DME_LOCAL },
+ { UIC_ARG_MIB(CBCREGRDWRSEL), 0, DME_LOCAL },
+ { UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
+ };
+
+ phy_read_attrs[0].mib_val = (u8)addr;
+ phy_read_attrs[1].mib_val = (u8)(addr >> 8);
+
+ ret = ufshcd_dwc_dme_set_attrs(hba, phy_read_attrs, ARRAY_SIZE(phy_read_attrs));
+ if (ret)
+ return ret;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(CBCREGRDLSB), &mib_val);
+ if (ret)
+ return ret;
+
+ *val = mib_val;
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(CBCREGRDMSB), &mib_val);
+ if (ret)
+ return ret;
+
+ *val |= (mib_val << 8);
+
+ return 0;
+}
+
+static int ufs_versal2_enable_phy(struct ufs_hba *hba)
+{
+ u32 offset, reg;
+ int ret;
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYDISABLE), 0);
+ if (ret)
+ return ret;
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 1);
+ if (ret)
+ return ret;
+
+ /* Check Tx/Rx FSM states */
+ for (offset = 0; offset < 2; offset++) {
+ u32 time_left, mibsel;
+
+ time_left = TIMEOUT_MICROSEC;
+ mibsel = UIC_ARG_MIB_SEL(MTX_FSM_STATE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(offset));
+ do {
+ ret = ufshcd_dme_get(hba, mibsel, &reg);
+ if (ret)
+ return ret;
+
+ if (reg == TX_STATE_HIBERN8 || reg == TX_STATE_SLEEP ||
+ reg == TX_STATE_LSBURST)
+ break;
+
+ time_left--;
+ usleep_range(1, 5);
+ } while (time_left);
+
+ if (!time_left) {
+ dev_err(hba->dev, "Invalid Tx FSM state.\n");
+ return -ETIMEDOUT;
+ }
+
+ time_left = TIMEOUT_MICROSEC;
+ mibsel = UIC_ARG_MIB_SEL(MRX_FSM_STATE, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(offset));
+ do {
+ ret = ufshcd_dme_get(hba, mibsel, &reg);
+ if (ret)
+ return ret;
+
+ if (reg == RX_STATE_HIBERN8 || reg == RX_STATE_SLEEP ||
+ reg == RX_STATE_LSBURST)
+ break;
+
+ time_left--;
+ usleep_range(1, 5);
+ } while (time_left);
+
+ if (!time_left) {
+ dev_err(hba->dev, "Invalid Rx FSM state.\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ return 0;
+}
+
+static int ufs_versal2_setup_phy(struct ufs_hba *hba)
+{
+ struct ufs_versal2_host *host = ufshcd_get_variant(hba);
+ int ret;
+ u32 reg;
+
+ /* Bypass RX-AFE offset calibrations (ATT/CTLE) */
+ ret = ufs_versal2_phy_reg_read(hba, FAST_FLAGS(0), &reg);
+ if (ret)
+ return ret;
+
+ reg |= MPHY_FAST_RX_AFE_CAL;
+ ret = ufs_versal2_phy_reg_write(hba, FAST_FLAGS(0), reg);
+ if (ret)
+ return ret;
+
+ ret = ufs_versal2_phy_reg_read(hba, FAST_FLAGS(1), &reg);
+ if (ret)
+ return ret;
+
+ reg |= MPHY_FAST_RX_AFE_CAL;
+ ret = ufs_versal2_phy_reg_write(hba, FAST_FLAGS(1), reg);
+ if (ret)
+ return ret;
+
+ /* Program ATT and CTLE compensation values */
+ if (host->attcompval0) {
+ ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(0), host->attcompval0);
+ if (ret)
+ return ret;
+ }
+
+ if (host->attcompval1) {
+ ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(1), host->attcompval1);
+ if (ret)
+ return ret;
+ }
+
+ if (host->ctlecompval0) {
+ ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(0), host->ctlecompval0);
+ if (ret)
+ return ret;
+ }
+
+ if (host->ctlecompval1) {
+ ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(1), host->ctlecompval1);
+ if (ret)
+ return ret;
+ }
+
+ ret = ufs_versal2_phy_reg_read(hba, FW_CALIB_CCFG(0), &reg);
+ if (ret)
+ return ret;
+
+ reg |= MPHY_FW_CALIB_CFG_VAL;
+ ret = ufs_versal2_phy_reg_write(hba, FW_CALIB_CCFG(0), reg);
+ if (ret)
+ return ret;
+
+ ret = ufs_versal2_phy_reg_read(hba, FW_CALIB_CCFG(1), &reg);
+ if (ret)
+ return ret;
+
+ reg |= MPHY_FW_CALIB_CFG_VAL;
+ return ufs_versal2_phy_reg_write(hba, FW_CALIB_CCFG(1), reg);
+}
+
+static int ufs_versal2_phy_init(struct ufs_hba *hba)
+{
+ struct ufs_versal2_host *host = ufshcd_get_variant(hba);
+ u32 time_left;
+ bool is_ready;
+ int ret;
+ static const struct ufshcd_dme_attr_val rmmi_attrs[] = {
+ { UIC_ARG_MIB(CBREFCLKCTRL2), CBREFREFCLK_GATE_OVR_EN, DME_LOCAL },
+ { UIC_ARG_MIB(CBCRCTRL), 1, DME_LOCAL },
+ { UIC_ARG_MIB(CBC10DIRECTCONF2), 1, DME_LOCAL },
+ { UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
+ };
+
+ /* Wait for Tx/Rx config_rdy */
+ time_left = TIMEOUT_MICROSEC;
+ do {
+ time_left--;
+ ret = zynqmp_pm_is_mphy_tx_rx_config_ready(&is_ready);
+ if (ret)
+ return ret;
+
+ if (!is_ready)
+ break;
+
+ usleep_range(1, 5);
+ } while (time_left);
+
+ if (!time_left) {
+ dev_err(hba->dev, "Tx/Rx configuration signal busy.\n");
+ return -ETIMEDOUT;
+ }
+
+ ret = ufshcd_dwc_dme_set_attrs(hba, rmmi_attrs, ARRAY_SIZE(rmmi_attrs));
+ if (ret)
+ return ret;
+
+ ret = reset_control_deassert(host->rstphy);
+ if (ret) {
+ dev_err(hba->dev, "ufsphy reset deassert failed, err = %d\n", ret);
+ return ret;
+ }
+
+ /* Wait for SRAM init done */
+ time_left = TIMEOUT_MICROSEC;
+ do {
+ time_left--;
+ ret = zynqmp_pm_is_sram_init_done(&is_ready);
+ if (ret)
+ return ret;
+
+ if (is_ready)
+ break;
+
+ usleep_range(1, 5);
+ } while (time_left);
+
+ if (!time_left) {
+ dev_err(hba->dev, "SRAM initialization failed.\n");
+ return -ETIMEDOUT;
+ }
+
+ ret = ufs_versal2_setup_phy(hba);
+ if (ret)
+ return ret;
+
+ return ufs_versal2_enable_phy(hba);
+}
+
+static int ufs_versal2_init(struct ufs_hba *hba)
+{
+ struct ufs_versal2_host *host;
+ struct device *dev = hba->dev;
+ struct ufs_clk_info *clki;
+ int ret;
+ u32 cal;
+
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ host->hba = hba;
+ ufshcd_set_variant(hba, host);
+
+ host->phy_mode = UFSHCD_DWC_PHY_MODE_ROM;
+
+ list_for_each_entry(clki, &hba->clk_list_head, list) {
+ if (!strcmp(clki->name, "core"))
+ host->host_clk = clk_get_rate(clki->clk);
+ }
+
+ host->rstc = devm_reset_control_get_exclusive(dev, "host");
+ if (IS_ERR(host->rstc)) {
+ dev_err(dev, "failed to get reset ctrl: host\n");
+ return PTR_ERR(host->rstc);
+ }
+
+ host->rstphy = devm_reset_control_get_exclusive(dev, "phy");
+ if (IS_ERR(host->rstphy)) {
+ dev_err(dev, "failed to get reset ctrl: phy\n");
+ return PTR_ERR(host->rstphy);
+ }
+
+ ret = reset_control_assert(host->rstc);
+ if (ret) {
+ dev_err(hba->dev, "host reset assert failed, err = %d\n", ret);
+ return ret;
+ }
+
+ ret = reset_control_assert(host->rstphy);
+ if (ret) {
+ dev_err(hba->dev, "phy reset assert failed, err = %d\n", ret);
+ return ret;
+ }
+
+ ret = zynqmp_pm_set_sram_bypass();
+ if (ret) {
+ dev_err(dev, "Bypass SRAM interface failed, err = %d\n", ret);
+ return ret;
+ }
+
+ ret = reset_control_deassert(host->rstc);
+ if (ret)
+ dev_err(hba->dev, "host reset deassert failed, err = %d\n", ret);
+
+ ret = zynqmp_pm_get_ufs_calibration_values(&cal);
+ if (ret) {
+ dev_err(dev, "failed to read calibration values\n");
+ return ret;
+ }
+
+ host->attcompval0 = (u8)cal;
+ host->attcompval1 = (u8)(cal >> 8);
+ host->ctlecompval0 = (u8)(cal >> 16);
+ host->ctlecompval1 = (u8)(cal >> 24);
+
+ hba->quirks |= UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING;
+
+ return 0;
+}
+
+static int ufs_versal2_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ int ret = 0;
+
+ if (status == POST_CHANGE) {
+ ret = ufs_versal2_phy_init(hba);
+ if (ret)
+ dev_err(hba->dev, "Phy init failed (%d)\n", ret);
+ }
+
+ return ret;
+}
+
+static int ufs_versal2_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ struct ufs_versal2_host *host = ufshcd_get_variant(hba);
+ int ret = 0;
+
+ switch (status) {
+ case PRE_CHANGE:
+ if (host->host_clk)
+ ufshcd_writel(hba, host->host_clk / 1000000, DWC_UFS_REG_HCLKDIV);
+
+ break;
+ case POST_CHANGE:
+ ret = ufshcd_dwc_link_startup_notify(hba, status);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int ufs_versal2_phy_ratesel(struct ufs_hba *hba, u32 activelanes, u32 rx_req)
+{
+ u32 time_left, reg, lane;
+ int ret;
+
+ for (lane = 0; lane < activelanes; lane++) {
+ time_left = TIMEOUT_MICROSEC;
+ ret = ufs_versal2_phy_reg_read(hba, RX_OVRD_IN_1(lane), &reg);
+ if (ret)
+ return ret;
+
+ reg |= MPHY_RX_OVRD_EN;
+ if (rx_req)
+ reg |= MPHY_RX_OVRD_VAL;
+ else
+ reg &= ~MPHY_RX_OVRD_VAL;
+
+ ret = ufs_versal2_phy_reg_write(hba, RX_OVRD_IN_1(lane), reg);
+ if (ret)
+ return ret;
+
+ do {
+ ret = ufs_versal2_phy_reg_read(hba, RX_PCS_OUT(lane), &reg);
+ if (ret)
+ return ret;
+
+ reg &= MPHY_RX_ACK_MASK;
+ if (reg == rx_req)
+ break;
+
+ time_left--;
+ usleep_range(1, 5);
+ } while (time_left);
+
+ if (!time_left) {
+ dev_err(hba->dev, "Invalid Rx Ack value.\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ return 0;
+}
+
+static int ufs_versal2_pwr_change_notify(struct ufs_hba *hba, enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_versal2_host *host = ufshcd_get_variant(hba);
+ u32 lane, reg, rate = 0;
+ int ret = 0;
+
+ if (status == PRE_CHANGE) {
+ /* If it is not a calibrated part, switch PWRMODE to SLOW_MODE */
+ if (!host->attcompval0 && !host->attcompval1 && !host->ctlecompval0 &&
+ !host->ctlecompval1) {
+ dev_req_params->pwr_rx = SLOW_MODE;
+ dev_req_params->pwr_tx = SLOW_MODE;
+ return 0;
+ }
+
+ if (dev_req_params->pwr_rx == SLOW_MODE || dev_req_params->pwr_rx == SLOWAUTO_MODE)
+ return 0;
+
+ if (dev_req_params->hs_rate == PA_HS_MODE_B)
+ rate = 1;
+
+ /* Select the rate */
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(CBRATESEL), rate);
+ if (ret)
+ return ret;
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 1);
+ if (ret)
+ return ret;
+
+ ret = ufs_versal2_phy_ratesel(hba, dev_req_params->lane_tx, 1);
+ if (ret)
+ return ret;
+
+ ret = ufs_versal2_phy_ratesel(hba, dev_req_params->lane_tx, 0);
+ if (ret)
+ return ret;
+
+ /* Remove rx_req override */
+ for (lane = 0; lane < dev_req_params->lane_tx; lane++) {
+ ret = ufs_versal2_phy_reg_read(hba, RX_OVRD_IN_1(lane), &reg);
+ if (ret)
+ return ret;
+
+ reg &= ~MPHY_RX_OVRD_EN;
+ ret = ufs_versal2_phy_reg_write(hba, RX_OVRD_IN_1(lane), reg);
+ if (ret)
+ return ret;
+ }
+
+ if (dev_req_params->lane_tx == UFS_LANE_2 && dev_req_params->lane_rx == UFS_LANE_2)
+ ret = ufshcd_dme_configure_adapt(hba, dev_req_params->gear_tx,
+ PA_INITIAL_ADAPT);
+ }
+
+ return ret;
+}
+
+static struct ufs_hba_variant_ops ufs_versal2_hba_vops = {
+ .name = "ufs-versal2-pltfm",
+ .init = ufs_versal2_init,
+ .link_startup_notify = ufs_versal2_link_startup_notify,
+ .hce_enable_notify = ufs_versal2_hce_enable_notify,
+ .pwr_change_notify = ufs_versal2_pwr_change_notify,
+};
+
+static const struct of_device_id ufs_versal2_pltfm_match[] = {
+ {
+ .compatible = "amd,versal2-ufs",
+ .data = &ufs_versal2_hba_vops,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ufs_versal2_pltfm_match);
+
+static int ufs_versal2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ /* Perform generic probe */
+ ret = ufshcd_pltfrm_init(pdev, &ufs_versal2_hba_vops);
+ if (ret)
+ dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", ret);
+
+ return ret;
+}
+
+static void ufs_versal2_remove(struct platform_device *pdev)
+{
+ struct ufs_hba *hba = platform_get_drvdata(pdev);
+
+ pm_runtime_get_sync(&(pdev)->dev);
+ ufshcd_remove(hba);
+}
+
+static const struct dev_pm_ops ufs_versal2_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
+ SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
+};
+
+static struct platform_driver ufs_versal2_pltfm = {
+ .probe = ufs_versal2_probe,
+ .remove = ufs_versal2_remove,
+ .driver = {
+ .name = "ufshcd-versal2",
+ .pm = &ufs_versal2_pm_ops,
+ .of_match_table = of_match_ptr(ufs_versal2_pltfm_match),
+ },
+};
+
+module_platform_driver(ufs_versal2_pltfm);
+
+MODULE_AUTHOR("Sai Krishna Potthuri <sai.krishna.potthuri@amd.com>");
+MODULE_DESCRIPTION("AMD Versal Gen 2 UFS Host Controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/ufs/host/ufs-exynos.c b/drivers/ufs/host/ufs-exynos.c
new file mode 100644
index 000000000..b2f65c465
--- /dev/null
+++ b/drivers/ufs/host/ufs-exynos.c
@@ -0,0 +1,2350 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UFS Host Controller driver for Exynos specific extensions
+ *
+ * Copyright (C) 2014-2015 Samsung Electronics Co., Ltd.
+ * Author: Seungwon Jeon <essuuj@gmail.com>
+ * Author: Alim Akhtar <alim.akhtar@samsung.com>
+ *
+ */
+
+#include <linux/unaligned.h>
+#include <crypto/aes.h>
+#include <linux/arm-smccc.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/mfd/syscon.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <ufs/ufshcd.h>
+#include "ufshcd-pltfrm.h"
+#include <ufs/ufshci.h>
+#include <ufs/unipro.h>
+
+#include "ufs-exynos.h"
+
+#define DATA_UNIT_SIZE 4096
+
+/*
+ * Exynos's Vendor specific registers for UFSHCI
+ */
+#define HCI_TXPRDT_ENTRY_SIZE 0x00
+#define PRDT_PREFETCH_EN BIT(31)
+#define HCI_RXPRDT_ENTRY_SIZE 0x04
+#define HCI_1US_TO_CNT_VAL 0x0C
+#define CNT_VAL_1US_MASK 0x3FF
+#define HCI_UTRL_NEXUS_TYPE 0x40
+#define HCI_UTMRL_NEXUS_TYPE 0x44
+#define HCI_SW_RST 0x50
+#define UFS_LINK_SW_RST BIT(0)
+#define UFS_UNIPRO_SW_RST BIT(1)
+#define UFS_SW_RST_MASK (UFS_UNIPRO_SW_RST | UFS_LINK_SW_RST)
+#define HCI_DATA_REORDER 0x60
+#define HCI_UNIPRO_APB_CLK_CTRL 0x68
+#define UNIPRO_APB_CLK(v, x) (((v) & ~0xF) | ((x) & 0xF))
+#define HCI_AXIDMA_RWDATA_BURST_LEN 0x6C
+#define WLU_EN BIT(31)
+#define WLU_BURST_LEN(x) ((x) << 27 | ((x) & 0xF))
+#define HCI_GPIO_OUT 0x70
+#define HCI_ERR_EN_PA_LAYER 0x78
+#define HCI_ERR_EN_DL_LAYER 0x7C
+#define HCI_ERR_EN_N_LAYER 0x80
+#define HCI_ERR_EN_T_LAYER 0x84
+#define HCI_ERR_EN_DME_LAYER 0x88
+#define HCI_V2P1_CTRL 0x8C
+#define IA_TICK_SEL BIT(16)
+#define HCI_CLKSTOP_CTRL 0xB0
+#define REFCLKOUT_STOP BIT(4)
+#define MPHY_APBCLK_STOP BIT(3)
+#define REFCLK_STOP BIT(2)
+#define UNIPRO_MCLK_STOP BIT(1)
+#define UNIPRO_PCLK_STOP BIT(0)
+#define CLK_STOP_MASK (REFCLKOUT_STOP | REFCLK_STOP |\
+ UNIPRO_MCLK_STOP | MPHY_APBCLK_STOP|\
+ UNIPRO_PCLK_STOP)
+/* HCI_MISC is also known as HCI_FORCE_HCS */
+#define HCI_MISC 0xB4
+#define REFCLK_CTRL_EN BIT(7)
+#define UNIPRO_PCLK_CTRL_EN BIT(6)
+#define UNIPRO_MCLK_CTRL_EN BIT(5)
+#define HCI_CORECLK_CTRL_EN BIT(4)
+#define CLK_CTRL_EN_MASK (REFCLK_CTRL_EN |\
+ UNIPRO_PCLK_CTRL_EN |\
+ UNIPRO_MCLK_CTRL_EN)
+
+#define HCI_IOP_ACG_DISABLE 0x100
+#define HCI_IOP_ACG_DISABLE_EN BIT(0)
+
+/* Device fatal error */
+#define DFES_ERR_EN BIT(31)
+#define DFES_DEF_L2_ERRS (UIC_DATA_LINK_LAYER_ERROR_RX_BUF_OF |\
+ UIC_DATA_LINK_LAYER_ERROR_PA_INIT)
+#define DFES_DEF_L3_ERRS (UIC_NETWORK_UNSUPPORTED_HEADER_TYPE |\
+ UIC_NETWORK_BAD_DEVICEID_ENC |\
+ UIC_NETWORK_LHDR_TRAP_PACKET_DROPPING)
+#define DFES_DEF_L4_ERRS (UIC_TRANSPORT_UNSUPPORTED_HEADER_TYPE |\
+ UIC_TRANSPORT_UNKNOWN_CPORTID |\
+ UIC_TRANSPORT_NO_CONNECTION_RX |\
+ UIC_TRANSPORT_BAD_TC)
+
+/* UFS Shareability */
+#define UFS_EXYNOSAUTO_WR_SHARABLE BIT(2)
+#define UFS_EXYNOSAUTO_RD_SHARABLE BIT(1)
+#define UFS_EXYNOSAUTO_SHARABLE (UFS_EXYNOSAUTO_WR_SHARABLE | \
+ UFS_EXYNOSAUTO_RD_SHARABLE)
+#define UFS_EXYNOSAUTOV920_WR_SHARABLE BIT(3)
+#define UFS_EXYNOSAUTOV920_RD_SHARABLE BIT(2)
+#define UFS_EXYNOSAUTOV920_SHARABLE (UFS_EXYNOSAUTOV920_WR_SHARABLE |\
+ UFS_EXYNOSAUTOV920_RD_SHARABLE)
+#define UFS_GS101_WR_SHARABLE BIT(1)
+#define UFS_GS101_RD_SHARABLE BIT(0)
+#define UFS_GS101_SHARABLE (UFS_GS101_WR_SHARABLE | \
+ UFS_GS101_RD_SHARABLE)
+#define UFS_SHAREABILITY_OFFSET 0x710
+
+/* Multi-host registers */
+#define MHCTRL 0xC4
+#define MHCTRL_EN_VH_MASK (0xE)
+#define MHCTRL_EN_VH(vh) (vh << 1)
+#define PH2VH_MBOX 0xD8
+
+#define MH_MSG_MASK (0xFF)
+
+#define MH_MSG(id, msg) ((id << 8) | (msg & 0xFF))
+#define MH_MSG_PH_READY 0x1
+#define MH_MSG_VH_READY 0x2
+
+#define ALLOW_INQUIRY BIT(25)
+#define ALLOW_MODE_SELECT BIT(24)
+#define ALLOW_MODE_SENSE BIT(23)
+#define ALLOW_PRE_FETCH GENMASK(22, 21)
+#define ALLOW_READ_CMD_ALL GENMASK(20, 18) /* read_6/10/16 */
+#define ALLOW_READ_BUFFER BIT(17)
+#define ALLOW_READ_CAPACITY GENMASK(16, 15)
+#define ALLOW_REPORT_LUNS BIT(14)
+#define ALLOW_REQUEST_SENSE BIT(13)
+#define ALLOW_SYNCHRONIZE_CACHE GENMASK(8, 7)
+#define ALLOW_TEST_UNIT_READY BIT(6)
+#define ALLOW_UNMAP BIT(5)
+#define ALLOW_VERIFY BIT(4)
+#define ALLOW_WRITE_CMD_ALL GENMASK(3, 1) /* write_6/10/16 */
+
+#define ALLOW_TRANS_VH_DEFAULT (ALLOW_INQUIRY | ALLOW_MODE_SELECT | \
+ ALLOW_MODE_SENSE | ALLOW_PRE_FETCH | \
+ ALLOW_READ_CMD_ALL | ALLOW_READ_BUFFER | \
+ ALLOW_READ_CAPACITY | ALLOW_REPORT_LUNS | \
+ ALLOW_REQUEST_SENSE | ALLOW_SYNCHRONIZE_CACHE | \
+ ALLOW_TEST_UNIT_READY | ALLOW_UNMAP | \
+ ALLOW_VERIFY | ALLOW_WRITE_CMD_ALL)
+
+#define HCI_MH_ALLOWABLE_TRAN_OF_VH 0x30C
+#define HCI_MH_IID_IN_TASK_TAG 0X308
+
+#define PH_READY_TIMEOUT_MS (5 * MSEC_PER_SEC)
+
+enum {
+ UNIPRO_L1_5 = 0,/* PHY Adapter */
+ UNIPRO_L2, /* Data Link */
+ UNIPRO_L3, /* Network */
+ UNIPRO_L4, /* Transport */
+ UNIPRO_DME, /* DME */
+};
+
+/*
+ * UNIPRO registers
+ */
+#define UNIPRO_DME_POWERMODE_REQ_LOCALL2TIMER0 0x7888
+#define UNIPRO_DME_POWERMODE_REQ_LOCALL2TIMER1 0x788c
+#define UNIPRO_DME_POWERMODE_REQ_LOCALL2TIMER2 0x7890
+#define UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER0 0x78B8
+#define UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER1 0x78BC
+#define UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER2 0x78C0
+
+/*
+ * UFS Protector registers
+ */
+#define UFSPRSECURITY 0x010
+#define NSSMU BIT(14)
+#define UFSPSBEGIN0 0x200
+#define UFSPSEND0 0x204
+#define UFSPSLUN0 0x208
+#define UFSPSCTRL0 0x20C
+
+#define CNTR_DIV_VAL 40
+
+static void exynos_ufs_auto_ctrl_hcc(struct exynos_ufs *ufs, bool en);
+static void exynos_ufs_ctrl_clkstop(struct exynos_ufs *ufs, bool en);
+
+static inline void exynos_ufs_enable_auto_ctrl_hcc(struct exynos_ufs *ufs)
+{
+ exynos_ufs_auto_ctrl_hcc(ufs, true);
+}
+
+static inline void exynos_ufs_disable_auto_ctrl_hcc(struct exynos_ufs *ufs)
+{
+ exynos_ufs_auto_ctrl_hcc(ufs, false);
+}
+
+static inline void exynos_ufs_disable_auto_ctrl_hcc_save(
+ struct exynos_ufs *ufs, u32 *val)
+{
+ *val = hci_readl(ufs, HCI_MISC);
+ exynos_ufs_auto_ctrl_hcc(ufs, false);
+}
+
+static inline void exynos_ufs_auto_ctrl_hcc_restore(
+ struct exynos_ufs *ufs, u32 *val)
+{
+ hci_writel(ufs, *val, HCI_MISC);
+}
+
+static inline void exynos_ufs_gate_clks(struct exynos_ufs *ufs)
+{
+ exynos_ufs_ctrl_clkstop(ufs, true);
+}
+
+static inline void exynos_ufs_ungate_clks(struct exynos_ufs *ufs)
+{
+ exynos_ufs_ctrl_clkstop(ufs, false);
+}
+
+static int exynos_ufs_shareability(struct exynos_ufs *ufs)
+{
+ /* IO Coherency setting */
+ if (ufs->sysreg) {
+ return regmap_update_bits(ufs->sysreg,
+ ufs->iocc_offset,
+ ufs->iocc_mask, ufs->iocc_val);
+ }
+
+ return 0;
+}
+
+static int gs101_ufs_drv_init(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ u32 reg;
+
+ /* Enable WriteBooster */
+ hba->caps |= UFSHCD_CAP_WB_EN;
+
+ /* Enable clock gating and hibern8 */
+ hba->caps |= UFSHCD_CAP_CLK_GATING | UFSHCD_CAP_HIBERN8_WITH_CLK_GATING;
+
+ /* set ACG to be controlled by UFS_ACG_DISABLE */
+ reg = hci_readl(ufs, HCI_IOP_ACG_DISABLE);
+ hci_writel(ufs, reg & (~HCI_IOP_ACG_DISABLE_EN), HCI_IOP_ACG_DISABLE);
+
+ return exynos_ufs_shareability(ufs);
+}
+
+static int exynosauto_ufs_drv_init(struct exynos_ufs *ufs)
+{
+ return exynos_ufs_shareability(ufs);
+}
+
+static int exynosauto_ufs_post_hce_enable(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+
+ /* Enable Virtual Host #1 */
+ ufshcd_rmwl(hba, MHCTRL_EN_VH_MASK, MHCTRL_EN_VH(1), MHCTRL);
+ /* Default VH Transfer permissions */
+ hci_writel(ufs, ALLOW_TRANS_VH_DEFAULT, HCI_MH_ALLOWABLE_TRAN_OF_VH);
+ /* IID information is replaced in TASKTAG[7:5] instead of IID in UCD */
+ hci_writel(ufs, 0x1, HCI_MH_IID_IN_TASK_TAG);
+
+ return 0;
+}
+
+static int exynosauto_ufs_pre_link(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ int i;
+ u32 tx_line_reset_period, rx_line_reset_period;
+
+ rx_line_reset_period = (RX_LINE_RESET_TIME * ufs->mclk_rate) / NSEC_PER_MSEC;
+ tx_line_reset_period = (TX_LINE_RESET_TIME * ufs->mclk_rate) / NSEC_PER_MSEC;
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x200), 0x40);
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_CLK_PRD, i),
+ DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_CLK_PRD_EN, i), 0x0);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE2, i),
+ (rx_line_reset_period >> 16) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE1, i),
+ (rx_line_reset_period >> 8) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE0, i),
+ (rx_line_reset_period) & 0xFF);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x2f, i), 0x79);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x84, i), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x25, i), 0xf6);
+ }
+
+ for_each_ufs_tx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_CLK_PRD, i),
+ DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate));
+ /* Not to affect VND_TX_LINERESET_PVALUE to VND_TX_CLK_PRD */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_CLK_PRD_EN, i),
+ 0x02);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE2, i),
+ (tx_line_reset_period >> 16) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE1, i),
+ (tx_line_reset_period >> 8) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE0, i),
+ (tx_line_reset_period) & 0xFF);
+
+ /* TX PWM Gear Capability / PWM_G1_ONLY */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x04, i), 0x1);
+ }
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x200), 0x0);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), 0x0);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xa011), 0x8000);
+
+ return 0;
+}
+
+static int exynosauto_ufs_pre_pwr_change(struct exynos_ufs *ufs,
+ struct ufs_pa_layer_attr *pwr)
+{
+ struct ufs_hba *hba = ufs->hba;
+
+ /* PACP_PWR_req and delivered to the remote DME */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA0), 12000);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA1), 32000);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA2), 16000);
+
+ return 0;
+}
+
+static int exynosauto_ufs_post_pwr_change(struct exynos_ufs *ufs,
+ const struct ufs_pa_layer_attr *pwr)
+{
+ struct ufs_hba *hba = ufs->hba;
+ u32 enabled_vh;
+
+ enabled_vh = ufshcd_readl(hba, MHCTRL) & MHCTRL_EN_VH_MASK;
+
+ /* Send physical host ready message to virtual hosts */
+ ufshcd_writel(hba, MH_MSG(enabled_vh, MH_MSG_PH_READY), PH2VH_MBOX);
+
+ return 0;
+}
+
+static int exynos7_ufs_pre_link(struct exynos_ufs *ufs)
+{
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+ u32 val = attr->pa_dbg_opt_suite1_val;
+ struct ufs_hba *hba = ufs->hba;
+ int i;
+
+ exynos_ufs_enable_ov_tm(hba);
+ for_each_ufs_tx_lane(ufs, i)
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x297, i), 0x17);
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x362, i), 0xff);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x363, i), 0x00);
+ }
+ exynos_ufs_disable_ov_tm(hba);
+
+ for_each_ufs_tx_lane(ufs, i)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(TX_HIBERN8_CONTROL, i), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_TXPHY_CFGUPDT), 0x1);
+ udelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(attr->pa_dbg_opt_suite1_off),
+ val | (1 << 12));
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_SKIP_RESET_PHY), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_SKIP_LINE_RESET), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_LINE_RESET_REQ), 0x1);
+ udelay(1600);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(attr->pa_dbg_opt_suite1_off), val);
+
+ return 0;
+}
+
+static int exynos7_ufs_post_link(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ int i;
+
+ exynos_ufs_enable_ov_tm(hba);
+ for_each_ufs_tx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x28b, i), 0x83);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x29a, i), 0x07);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x277, i),
+ TX_LINERESET_N(exynos_ufs_calc_time_cntr(ufs, 200000)));
+ }
+ exynos_ufs_disable_ov_tm(hba);
+
+ exynos_ufs_enable_dbg_mode(hba);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_SAVECONFIGTIME), 0xbb8);
+ exynos_ufs_disable_dbg_mode(hba);
+
+ return 0;
+}
+
+static int exynos7_ufs_pre_pwr_change(struct exynos_ufs *ufs,
+ struct ufs_pa_layer_attr *pwr)
+{
+ unipro_writel(ufs, 0x22, UNIPRO_DBG_FORCE_DME_CTRL_STATE);
+
+ return 0;
+}
+
+static int exynos7_ufs_post_pwr_change(struct exynos_ufs *ufs,
+ const struct ufs_pa_layer_attr *pwr)
+{
+ struct ufs_hba *hba = ufs->hba;
+ int lanes = max_t(u32, pwr->lane_rx, pwr->lane_tx);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_RXPHY_CFGUPDT), 0x1);
+
+ if (lanes == 1) {
+ exynos_ufs_enable_dbg_mode(hba);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_CONNECTEDTXDATALANES), 0x1);
+ exynos_ufs_disable_dbg_mode(hba);
+ }
+
+ return 0;
+}
+
+static int exynosautov920_ufs_pre_link(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ int i;
+ u32 tx_line_reset_period, rx_line_reset_period;
+
+ rx_line_reset_period = (RX_LINE_RESET_TIME * ufs->mclk_rate)
+ / NSEC_PER_MSEC;
+ tx_line_reset_period = (TX_LINE_RESET_TIME * ufs->mclk_rate)
+ / NSEC_PER_MSEC;
+
+ unipro_writel(ufs, 0x5f, 0x44);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x200), 0x40);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x202), 0x02);
+
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_CLK_PRD, i),
+ DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_CLK_PRD_EN, i), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE2, i),
+ (rx_line_reset_period >> 16) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE1, i),
+ (rx_line_reset_period >> 8) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE0, i),
+ (rx_line_reset_period) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x2f, i), 0x69);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x84, i), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x25, i), 0xf6);
+ }
+
+ for_each_ufs_tx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_CLK_PRD, i),
+ DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_CLK_PRD_EN, i),
+ 0x02);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE2, i),
+ (tx_line_reset_period >> 16) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE1, i),
+ (tx_line_reset_period >> 8) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE0, i),
+ (tx_line_reset_period) & 0xFF);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x04, i), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x7f, i), 0x0);
+ }
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x200), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xa011), 0x8000);
+
+ return 0;
+}
+
+static int exynosautov920_ufs_post_link(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x9529), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15a4), 0x3e8);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x9529), 0x0);
+
+ return 0;
+}
+
+static int exynosautov920_ufs_pre_pwr_change(struct exynos_ufs *ufs,
+ struct ufs_pa_layer_attr *pwr)
+{
+ struct ufs_hba *hba = ufs->hba;
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15d4), 0x1);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DL_FC0PROTTIMEOUTVAL), 8064);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DL_TC0REPLAYTIMEOUTVAL), 28224);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DL_AFC0REQTIMEOUTVAL), 20160);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA0), 12000);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA1), 32000);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA2), 16000);
+
+ unipro_writel(ufs, 8064, UNIPRO_DME_POWERMODE_REQ_LOCALL2TIMER0);
+ unipro_writel(ufs, 28224, UNIPRO_DME_POWERMODE_REQ_LOCALL2TIMER1);
+ unipro_writel(ufs, 20160, UNIPRO_DME_POWERMODE_REQ_LOCALL2TIMER2);
+ unipro_writel(ufs, 12000, UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER0);
+ unipro_writel(ufs, 32000, UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER1);
+ unipro_writel(ufs, 16000, UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER2);
+
+ return 0;
+}
+
+/*
+ * exynos_ufs_auto_ctrl_hcc - HCI core clock control by h/w
+ * Control should be disabled in the below cases
+ * - Before host controller S/W reset
+ * - Access to UFS protector's register
+ */
+static void exynos_ufs_auto_ctrl_hcc(struct exynos_ufs *ufs, bool en)
+{
+ u32 misc = hci_readl(ufs, HCI_MISC);
+
+ if (en)
+ hci_writel(ufs, misc | HCI_CORECLK_CTRL_EN, HCI_MISC);
+ else
+ hci_writel(ufs, misc & ~HCI_CORECLK_CTRL_EN, HCI_MISC);
+}
+
+static void exynos_ufs_ctrl_clkstop(struct exynos_ufs *ufs, bool en)
+{
+ u32 ctrl = hci_readl(ufs, HCI_CLKSTOP_CTRL);
+ u32 misc = hci_readl(ufs, HCI_MISC);
+
+ if (en) {
+ hci_writel(ufs, misc | CLK_CTRL_EN_MASK, HCI_MISC);
+ hci_writel(ufs, ctrl | CLK_STOP_MASK, HCI_CLKSTOP_CTRL);
+ } else {
+ hci_writel(ufs, ctrl & ~CLK_STOP_MASK, HCI_CLKSTOP_CTRL);
+ hci_writel(ufs, misc & ~CLK_CTRL_EN_MASK, HCI_MISC);
+ }
+}
+
+static int exynos_ufs_get_clk_info(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ struct list_head *head = &hba->clk_list_head;
+ struct ufs_clk_info *clki;
+ unsigned long pclk_rate;
+ u32 f_min, f_max;
+ u8 div = 0;
+ int ret = 0;
+
+ if (list_empty(head))
+ goto out;
+
+ list_for_each_entry(clki, head, list) {
+ if (!IS_ERR(clki->clk)) {
+ if (!strcmp(clki->name, "core_clk"))
+ ufs->clk_hci_core = clki->clk;
+ else if (!strcmp(clki->name, "sclk_unipro_main"))
+ ufs->clk_unipro_main = clki->clk;
+ }
+ }
+
+ if (!ufs->clk_hci_core || !ufs->clk_unipro_main) {
+ dev_err(hba->dev, "failed to get clk info\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ufs->mclk_rate = clk_get_rate(ufs->clk_unipro_main);
+ pclk_rate = clk_get_rate(ufs->clk_hci_core);
+ f_min = ufs->pclk_avail_min;
+ f_max = ufs->pclk_avail_max;
+
+ if (ufs->opts & EXYNOS_UFS_OPT_HAS_APB_CLK_CTRL) {
+ do {
+ pclk_rate /= (div + 1);
+
+ if (pclk_rate <= f_max)
+ break;
+ div++;
+ } while (pclk_rate >= f_min);
+ }
+
+ if (unlikely(pclk_rate < f_min || pclk_rate > f_max)) {
+ dev_err(hba->dev, "not available pclk range %lu\n", pclk_rate);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ufs->pclk_rate = pclk_rate;
+ ufs->pclk_div = div;
+
+out:
+ return ret;
+}
+
+static void exynos_ufs_set_unipro_pclk_div(struct exynos_ufs *ufs)
+{
+ if (ufs->opts & EXYNOS_UFS_OPT_HAS_APB_CLK_CTRL) {
+ u32 val;
+
+ val = hci_readl(ufs, HCI_UNIPRO_APB_CLK_CTRL);
+ hci_writel(ufs, UNIPRO_APB_CLK(val, ufs->pclk_div),
+ HCI_UNIPRO_APB_CLK_CTRL);
+ }
+}
+
+static void exynos_ufs_set_pwm_clk_div(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(CMN_PWM_CLK_CTRL), attr->cmn_pwm_clk_ctrl);
+}
+
+static void exynos_ufs_calc_pwm_clk_div(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+ const unsigned int div = 30, mult = 20;
+ const unsigned long pwm_min = 3 * 1000 * 1000;
+ const unsigned long pwm_max = 9 * 1000 * 1000;
+ const int divs[] = {32, 16, 8, 4};
+ unsigned long clk = 0, _clk, clk_period;
+ int i = 0, clk_idx = -1;
+
+ clk_period = UNIPRO_PCLK_PERIOD(ufs);
+ for (i = 0; i < ARRAY_SIZE(divs); i++) {
+ _clk = NSEC_PER_SEC * mult / (clk_period * divs[i] * div);
+ if (_clk >= pwm_min && _clk <= pwm_max) {
+ if (_clk > clk) {
+ clk_idx = i;
+ clk = _clk;
+ }
+ }
+ }
+
+ if (clk_idx == -1) {
+ ufshcd_dme_get(hba, UIC_ARG_MIB(CMN_PWM_CLK_CTRL), &clk_idx);
+ dev_err(hba->dev,
+ "failed to decide pwm clock divider, will not change\n");
+ }
+
+ attr->cmn_pwm_clk_ctrl = clk_idx & PWM_CLK_CTRL_MASK;
+}
+
+long exynos_ufs_calc_time_cntr(struct exynos_ufs *ufs, long period)
+{
+ const int precise = 10;
+ long pclk_rate = ufs->pclk_rate;
+ long clk_period, fraction;
+
+ clk_period = UNIPRO_PCLK_PERIOD(ufs);
+ fraction = ((NSEC_PER_SEC % pclk_rate) * precise) / pclk_rate;
+
+ return (period * precise) / ((clk_period * precise) + fraction);
+}
+
+static void exynos_ufs_specify_phy_time_attr(struct exynos_ufs *ufs)
+{
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+ struct ufs_phy_time_cfg *t_cfg = &ufs->t_cfg;
+
+ if (ufs->opts & EXYNOS_UFS_OPT_SKIP_CONFIG_PHY_ATTR)
+ return;
+
+ t_cfg->tx_linereset_p =
+ exynos_ufs_calc_time_cntr(ufs, attr->tx_dif_p_nsec);
+ t_cfg->tx_linereset_n =
+ exynos_ufs_calc_time_cntr(ufs, attr->tx_dif_n_nsec);
+ t_cfg->tx_high_z_cnt =
+ exynos_ufs_calc_time_cntr(ufs, attr->tx_high_z_cnt_nsec);
+ t_cfg->tx_base_n_val =
+ exynos_ufs_calc_time_cntr(ufs, attr->tx_base_unit_nsec);
+ t_cfg->tx_gran_n_val =
+ exynos_ufs_calc_time_cntr(ufs, attr->tx_gran_unit_nsec);
+ t_cfg->tx_sleep_cnt =
+ exynos_ufs_calc_time_cntr(ufs, attr->tx_sleep_cnt);
+
+ t_cfg->rx_linereset =
+ exynos_ufs_calc_time_cntr(ufs, attr->rx_dif_p_nsec);
+ t_cfg->rx_hibern8_wait =
+ exynos_ufs_calc_time_cntr(ufs, attr->rx_hibern8_wait_nsec);
+ t_cfg->rx_base_n_val =
+ exynos_ufs_calc_time_cntr(ufs, attr->rx_base_unit_nsec);
+ t_cfg->rx_gran_n_val =
+ exynos_ufs_calc_time_cntr(ufs, attr->rx_gran_unit_nsec);
+ t_cfg->rx_sleep_cnt =
+ exynos_ufs_calc_time_cntr(ufs, attr->rx_sleep_cnt);
+ t_cfg->rx_stall_cnt =
+ exynos_ufs_calc_time_cntr(ufs, attr->rx_stall_cnt);
+}
+
+static void exynos_ufs_config_phy_time_attr(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ struct ufs_phy_time_cfg *t_cfg = &ufs->t_cfg;
+ int i;
+
+ exynos_ufs_set_pwm_clk_div(ufs);
+
+ exynos_ufs_enable_ov_tm(hba);
+
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_FILLER_ENABLE, i),
+ ufs->drv_data->uic_attr->rx_filler_enable);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_LINERESET_VAL, i),
+ RX_LINERESET(t_cfg->rx_linereset));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_BASE_NVAL_07_00, i),
+ RX_BASE_NVAL_L(t_cfg->rx_base_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_BASE_NVAL_15_08, i),
+ RX_BASE_NVAL_H(t_cfg->rx_base_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_GRAN_NVAL_07_00, i),
+ RX_GRAN_NVAL_L(t_cfg->rx_gran_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_GRAN_NVAL_10_08, i),
+ RX_GRAN_NVAL_H(t_cfg->rx_gran_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_OV_SLEEP_CNT_TIMER, i),
+ RX_OV_SLEEP_CNT(t_cfg->rx_sleep_cnt));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_OV_STALL_CNT_TIMER, i),
+ RX_OV_STALL_CNT(t_cfg->rx_stall_cnt));
+ }
+
+ for_each_ufs_tx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_LINERESET_P_VAL, i),
+ TX_LINERESET_P(t_cfg->tx_linereset_p));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_HIGH_Z_CNT_07_00, i),
+ TX_HIGH_Z_CNT_L(t_cfg->tx_high_z_cnt));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_HIGH_Z_CNT_11_08, i),
+ TX_HIGH_Z_CNT_H(t_cfg->tx_high_z_cnt));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_BASE_NVAL_07_00, i),
+ TX_BASE_NVAL_L(t_cfg->tx_base_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_BASE_NVAL_15_08, i),
+ TX_BASE_NVAL_H(t_cfg->tx_base_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_GRAN_NVAL_07_00, i),
+ TX_GRAN_NVAL_L(t_cfg->tx_gran_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_GRAN_NVAL_10_08, i),
+ TX_GRAN_NVAL_H(t_cfg->tx_gran_n_val));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_OV_SLEEP_CNT_TIMER, i),
+ TX_OV_H8_ENTER_EN |
+ TX_OV_SLEEP_CNT(t_cfg->tx_sleep_cnt));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_MIN_ACTIVATETIME, i),
+ ufs->drv_data->uic_attr->tx_min_activatetime);
+ }
+
+ exynos_ufs_disable_ov_tm(hba);
+}
+
+static void exynos_ufs_config_phy_cap_attr(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+ int i;
+
+ exynos_ufs_enable_ov_tm(hba);
+
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HS_G1_SYNC_LENGTH_CAP, i),
+ attr->rx_hs_g1_sync_len_cap);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HS_G2_SYNC_LENGTH_CAP, i),
+ attr->rx_hs_g2_sync_len_cap);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HS_G3_SYNC_LENGTH_CAP, i),
+ attr->rx_hs_g3_sync_len_cap);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HS_G1_PREP_LENGTH_CAP, i),
+ attr->rx_hs_g1_prep_sync_len_cap);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HS_G2_PREP_LENGTH_CAP, i),
+ attr->rx_hs_g2_prep_sync_len_cap);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HS_G3_PREP_LENGTH_CAP, i),
+ attr->rx_hs_g3_prep_sync_len_cap);
+ }
+
+ if (attr->rx_adv_fine_gran_sup_en == 0) {
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_ADV_GRANULARITY_CAP, i), 0);
+
+ if (attr->rx_min_actv_time_cap)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(
+ RX_MIN_ACTIVATETIME_CAPABILITY, i),
+ attr->rx_min_actv_time_cap);
+
+ if (attr->rx_hibern8_time_cap)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_HIBERN8TIME_CAP, i),
+ attr->rx_hibern8_time_cap);
+ }
+ } else if (attr->rx_adv_fine_gran_sup_en == 1) {
+ for_each_ufs_rx_lane(ufs, i) {
+ if (attr->rx_adv_fine_gran_step)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_ADV_GRANULARITY_CAP,
+ i), RX_ADV_FINE_GRAN_STEP(
+ attr->rx_adv_fine_gran_step));
+
+ if (attr->rx_adv_min_actv_time_cap)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(
+ RX_ADV_MIN_ACTIVATETIME_CAP, i),
+ attr->rx_adv_min_actv_time_cap);
+
+ if (attr->rx_adv_hibern8_time_cap)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_ADV_HIBERN8TIME_CAP,
+ i),
+ attr->rx_adv_hibern8_time_cap);
+ }
+ }
+
+ exynos_ufs_disable_ov_tm(hba);
+}
+
+static void exynos_ufs_establish_connt(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ enum {
+ DEV_ID = 0x00,
+ PEER_DEV_ID = 0x01,
+ PEER_CPORT_ID = 0x00,
+ TRAFFIC_CLASS = 0x00,
+ };
+
+ /* allow cport attributes to be set */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE), CPORT_IDLE);
+
+ /* local unipro attributes */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(N_DEVICEID), DEV_ID);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(N_DEVICEID_VALID), true);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERDEVICEID), PEER_DEV_ID);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERCPORTID), PEER_CPORT_ID);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_CPORTFLAGS), CPORT_DEF_FLAGS);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_TRAFFICCLASS), TRAFFIC_CLASS);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE), CPORT_CONNECTED);
+}
+
+static void exynos_ufs_config_smu(struct exynos_ufs *ufs)
+{
+ u32 reg, val;
+
+ if (ufs->opts & EXYNOS_UFS_OPT_UFSPR_SECURE)
+ return;
+
+ exynos_ufs_disable_auto_ctrl_hcc_save(ufs, &val);
+
+ /* make encryption disabled by default */
+ reg = ufsp_readl(ufs, UFSPRSECURITY);
+ ufsp_writel(ufs, reg | NSSMU, UFSPRSECURITY);
+ ufsp_writel(ufs, 0x0, UFSPSBEGIN0);
+ ufsp_writel(ufs, 0xffffffff, UFSPSEND0);
+ ufsp_writel(ufs, 0xff, UFSPSLUN0);
+ ufsp_writel(ufs, 0xf1, UFSPSCTRL0);
+
+ exynos_ufs_auto_ctrl_hcc_restore(ufs, &val);
+}
+
+static void exynos_ufs_config_sync_pattern_mask(struct exynos_ufs *ufs,
+ struct ufs_pa_layer_attr *pwr)
+{
+ struct ufs_hba *hba = ufs->hba;
+ u8 g = max_t(u32, pwr->gear_rx, pwr->gear_tx);
+ u32 mask, sync_len;
+ enum {
+ SYNC_LEN_G1 = 80 * 1000, /* 80us */
+ SYNC_LEN_G2 = 40 * 1000, /* 40us */
+ SYNC_LEN_G3 = 20 * 1000, /* 20us */
+ };
+ int i;
+
+ if (g == 1)
+ sync_len = SYNC_LEN_G1;
+ else if (g == 2)
+ sync_len = SYNC_LEN_G2;
+ else if (g == 3)
+ sync_len = SYNC_LEN_G3;
+ else
+ return;
+
+ mask = exynos_ufs_calc_time_cntr(ufs, sync_len);
+ mask = (mask >> 8) & 0xff;
+
+ exynos_ufs_enable_ov_tm(hba);
+
+ for_each_ufs_rx_lane(ufs, i)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(RX_SYNC_MASK_LENGTH, i), mask);
+
+ exynos_ufs_disable_ov_tm(hba);
+}
+
+#define UFS_HW_VER_MAJOR_MASK GENMASK(15, 8)
+
+static u32 exynos_ufs_get_hs_gear(struct ufs_hba *hba)
+{
+ u8 major;
+
+ major = FIELD_GET(UFS_HW_VER_MAJOR_MASK, hba->ufs_version);
+
+ if (major >= 3)
+ return UFS_HS_G4;
+
+ /* Default is HS-G3 */
+ return UFS_HS_G3;
+}
+
+static int exynos_ufs_pre_pwr_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ struct phy *generic_phy = ufs->phy;
+ int ret;
+
+ if (!dev_req_params) {
+ pr_err("%s: incoming dev_req_params is NULL\n", __func__);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (ufs->drv_data->pre_pwr_change)
+ ufs->drv_data->pre_pwr_change(ufs, dev_req_params);
+
+ if (ufshcd_is_hs_mode(dev_req_params)) {
+ exynos_ufs_config_sync_pattern_mask(ufs, dev_req_params);
+
+ switch (dev_req_params->hs_rate) {
+ case PA_HS_MODE_A:
+ case PA_HS_MODE_B:
+ phy_calibrate(generic_phy);
+ break;
+ }
+ }
+
+ /* setting for three timeout values for traffic class #0 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DL_FC0PROTTIMEOUTVAL), 8064);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DL_TC0REPLAYTIMEOUTVAL), 28224);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DL_AFC0REQTIMEOUTVAL), 20160);
+
+ return 0;
+out:
+ return ret;
+}
+
+#define PWR_MODE_STR_LEN 64
+static int exynos_ufs_post_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *pwr_req)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ struct phy *generic_phy = ufs->phy;
+ int gear = max_t(u32, pwr_req->gear_rx, pwr_req->gear_tx);
+ int lanes = max_t(u32, pwr_req->lane_rx, pwr_req->lane_tx);
+ char pwr_str[PWR_MODE_STR_LEN] = "";
+
+ /* let default be PWM Gear 1, Lane 1 */
+ if (!gear)
+ gear = 1;
+
+ if (!lanes)
+ lanes = 1;
+
+ if (ufs->drv_data->post_pwr_change)
+ ufs->drv_data->post_pwr_change(ufs, pwr_req);
+
+ if ((ufshcd_is_hs_mode(pwr_req))) {
+ switch (pwr_req->hs_rate) {
+ case PA_HS_MODE_A:
+ case PA_HS_MODE_B:
+ phy_calibrate(generic_phy);
+ break;
+ }
+
+ snprintf(pwr_str, PWR_MODE_STR_LEN, "%s series_%s G_%d L_%d",
+ "FAST", pwr_req->hs_rate == PA_HS_MODE_A ? "A" : "B",
+ gear, lanes);
+ } else {
+ snprintf(pwr_str, PWR_MODE_STR_LEN, "%s G_%d L_%d",
+ "SLOW", gear, lanes);
+ }
+
+ dev_info(hba->dev, "Power mode changed to : %s\n", pwr_str);
+
+ return 0;
+}
+
+static void exynos_ufs_specify_nexus_t_xfer_req(struct ufs_hba *hba,
+ int tag, bool is_scsi_cmd)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ u32 type;
+
+ type = hci_readl(ufs, HCI_UTRL_NEXUS_TYPE);
+
+ if (is_scsi_cmd)
+ hci_writel(ufs, type | (1 << tag), HCI_UTRL_NEXUS_TYPE);
+ else
+ hci_writel(ufs, type & ~(1 << tag), HCI_UTRL_NEXUS_TYPE);
+}
+
+static void exynos_ufs_specify_nexus_t_tm_req(struct ufs_hba *hba,
+ int tag, u8 func)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ u32 type;
+
+ type = hci_readl(ufs, HCI_UTMRL_NEXUS_TYPE);
+
+ switch (func) {
+ case UFS_ABORT_TASK:
+ case UFS_QUERY_TASK:
+ hci_writel(ufs, type | (1 << tag), HCI_UTMRL_NEXUS_TYPE);
+ break;
+ case UFS_ABORT_TASK_SET:
+ case UFS_CLEAR_TASK_SET:
+ case UFS_LOGICAL_RESET:
+ case UFS_QUERY_TASK_SET:
+ hci_writel(ufs, type & ~(1 << tag), HCI_UTMRL_NEXUS_TYPE);
+ break;
+ }
+}
+
+static int exynos_ufs_phy_init(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ struct phy *generic_phy = ufs->phy;
+ int ret = 0;
+
+ if (ufs->avail_ln_rx == 0 || ufs->avail_ln_tx == 0) {
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_AVAILRXDATALANES),
+ &ufs->avail_ln_rx);
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_AVAILTXDATALANES),
+ &ufs->avail_ln_tx);
+ WARN(ufs->avail_ln_rx != ufs->avail_ln_tx,
+ "available data lane is not equal(rx:%d, tx:%d)\n",
+ ufs->avail_ln_rx, ufs->avail_ln_tx);
+ }
+
+ phy_set_bus_width(generic_phy, ufs->avail_ln_rx);
+
+ if (generic_phy->power_count) {
+ phy_power_off(generic_phy);
+ phy_exit(generic_phy);
+ }
+
+ ret = phy_init(generic_phy);
+ if (ret) {
+ dev_err(hba->dev, "%s: phy init failed, ret = %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = phy_power_on(generic_phy);
+ if (ret)
+ goto out_exit_phy;
+
+ return 0;
+
+out_exit_phy:
+ phy_exit(generic_phy);
+
+ return ret;
+}
+
+static void exynos_ufs_config_unipro(struct exynos_ufs *ufs)
+{
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+ struct ufs_hba *hba = ufs->hba;
+
+ if (attr->pa_dbg_clk_period_off)
+ ufshcd_dme_set(hba, UIC_ARG_MIB(attr->pa_dbg_clk_period_off),
+ DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate));
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXTRAILINGCLOCKS),
+ ufs->drv_data->uic_attr->tx_trailingclks);
+
+ if (attr->pa_dbg_opt_suite1_off)
+ ufshcd_dme_set(hba, UIC_ARG_MIB(attr->pa_dbg_opt_suite1_off),
+ attr->pa_dbg_opt_suite1_val);
+
+ if (attr->pa_dbg_opt_suite2_off)
+ ufshcd_dme_set(hba, UIC_ARG_MIB(attr->pa_dbg_opt_suite2_off),
+ attr->pa_dbg_opt_suite2_val);
+}
+
+static void exynos_ufs_config_intr(struct exynos_ufs *ufs, u32 errs, u8 index)
+{
+ switch (index) {
+ case UNIPRO_L1_5:
+ hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERR_EN_PA_LAYER);
+ break;
+ case UNIPRO_L2:
+ hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERR_EN_DL_LAYER);
+ break;
+ case UNIPRO_L3:
+ hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERR_EN_N_LAYER);
+ break;
+ case UNIPRO_L4:
+ hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERR_EN_T_LAYER);
+ break;
+ case UNIPRO_DME:
+ hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERR_EN_DME_LAYER);
+ break;
+ }
+}
+
+static int exynos_ufs_setup_clocks(struct ufs_hba *hba, bool on,
+ enum ufs_notify_change_status status)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+
+ if (!ufs)
+ return 0;
+
+ if (on && status == PRE_CHANGE) {
+ if (ufs->opts & EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL)
+ exynos_ufs_disable_auto_ctrl_hcc(ufs);
+ exynos_ufs_ungate_clks(ufs);
+ } else if (!on && status == POST_CHANGE) {
+ exynos_ufs_gate_clks(ufs);
+ if (ufs->opts & EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL)
+ exynos_ufs_enable_auto_ctrl_hcc(ufs);
+ }
+
+ return 0;
+}
+
+static int exynos_ufs_pre_link(struct ufs_hba *hba)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+
+ /* hci */
+ exynos_ufs_config_intr(ufs, DFES_DEF_L2_ERRS, UNIPRO_L2);
+ exynos_ufs_config_intr(ufs, DFES_DEF_L3_ERRS, UNIPRO_L3);
+ exynos_ufs_config_intr(ufs, DFES_DEF_L4_ERRS, UNIPRO_L4);
+ exynos_ufs_set_unipro_pclk_div(ufs);
+
+ exynos_ufs_setup_clocks(hba, true, PRE_CHANGE);
+
+ /* unipro */
+ exynos_ufs_config_unipro(ufs);
+
+ if (ufs->drv_data->pre_link)
+ ufs->drv_data->pre_link(ufs);
+
+ /* m-phy */
+ exynos_ufs_phy_init(ufs);
+ if (!(ufs->opts & EXYNOS_UFS_OPT_SKIP_CONFIG_PHY_ATTR)) {
+ exynos_ufs_config_phy_time_attr(ufs);
+ exynos_ufs_config_phy_cap_attr(ufs);
+ }
+
+ return 0;
+}
+
+static void exynos_ufs_fit_aggr_timeout(struct exynos_ufs *ufs)
+{
+ u32 val;
+
+ /* Select function clock (mclk) for timer tick */
+ if (ufs->opts & EXYNOS_UFS_OPT_TIMER_TICK_SELECT) {
+ val = hci_readl(ufs, HCI_V2P1_CTRL);
+ val |= IA_TICK_SEL;
+ hci_writel(ufs, val, HCI_V2P1_CTRL);
+ }
+
+ val = exynos_ufs_calc_time_cntr(ufs, IATOVAL_NSEC / CNTR_DIV_VAL);
+ hci_writel(ufs, val & CNT_VAL_1US_MASK, HCI_1US_TO_CNT_VAL);
+}
+
+static int exynos_ufs_post_link(struct ufs_hba *hba)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ struct phy *generic_phy = ufs->phy;
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+ u32 val = ilog2(DATA_UNIT_SIZE);
+
+ exynos_ufs_establish_connt(ufs);
+ exynos_ufs_fit_aggr_timeout(ufs);
+
+ hci_writel(ufs, 0xa, HCI_DATA_REORDER);
+
+ if (hba->caps & UFSHCD_CAP_CRYPTO)
+ val |= PRDT_PREFETCH_EN;
+ hci_writel(ufs, val, HCI_TXPRDT_ENTRY_SIZE);
+
+ hci_writel(ufs, ilog2(DATA_UNIT_SIZE), HCI_RXPRDT_ENTRY_SIZE);
+ hci_writel(ufs, BIT(hba->nutrs) - 1, HCI_UTRL_NEXUS_TYPE);
+ hci_writel(ufs, BIT(hba->nutmrs) - 1, HCI_UTMRL_NEXUS_TYPE);
+ hci_writel(ufs, 0xf, HCI_AXIDMA_RWDATA_BURST_LEN);
+
+ if (ufs->opts & EXYNOS_UFS_OPT_SKIP_CONNECTION_ESTAB)
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(T_DBG_SKIP_INIT_HIBERN8_EXIT), true);
+
+ if (attr->pa_granularity) {
+ exynos_ufs_enable_dbg_mode(hba);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_GRANULARITY),
+ attr->pa_granularity);
+ exynos_ufs_disable_dbg_mode(hba);
+
+ if (attr->pa_tactivate)
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TACTIVATE),
+ attr->pa_tactivate);
+ if (attr->pa_hibern8time &&
+ !(ufs->opts & EXYNOS_UFS_OPT_USE_SW_HIBERN8_TIMER))
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HIBERN8TIME),
+ attr->pa_hibern8time);
+ }
+
+ if (ufs->opts & EXYNOS_UFS_OPT_USE_SW_HIBERN8_TIMER) {
+ if (!attr->pa_granularity)
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_GRANULARITY),
+ &attr->pa_granularity);
+ if (!attr->pa_hibern8time)
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_HIBERN8TIME),
+ &attr->pa_hibern8time);
+ /*
+ * not wait for HIBERN8 time to exit hibernation
+ */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HIBERN8TIME), 0);
+
+ if (attr->pa_granularity < 1 || attr->pa_granularity > 6) {
+ /* Valid range for granularity: 1 ~ 6 */
+ dev_warn(hba->dev,
+ "%s: pa_granularity %d is invalid, assuming backwards compatibility\n",
+ __func__,
+ attr->pa_granularity);
+ attr->pa_granularity = 6;
+ }
+ }
+
+ phy_calibrate(generic_phy);
+
+ if (ufs->drv_data->post_link)
+ ufs->drv_data->post_link(ufs);
+
+ return 0;
+}
+
+static int exynos_ufs_parse_dt(struct device *dev, struct exynos_ufs *ufs)
+{
+ struct device_node *np = dev->of_node;
+ struct exynos_ufs_uic_attr *attr;
+ int ret = 0;
+
+ ufs->drv_data = device_get_match_data(dev);
+
+ if (ufs->drv_data && ufs->drv_data->uic_attr) {
+ attr = ufs->drv_data->uic_attr;
+ } else {
+ dev_err(dev, "failed to get uic attributes\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ufs->sysreg = syscon_regmap_lookup_by_phandle(np, "samsung,sysreg");
+ if (IS_ERR(ufs->sysreg))
+ ufs->sysreg = NULL;
+ else {
+ if (of_property_read_u32_index(np, "samsung,sysreg", 1,
+ &ufs->iocc_offset)) {
+ dev_warn(dev, "can't get an offset from sysreg. Set to default value\n");
+ ufs->iocc_offset = UFS_SHAREABILITY_OFFSET;
+ }
+ }
+
+ ufs->iocc_mask = ufs->drv_data->iocc_mask;
+ /*
+ * no 'dma-coherent' property means the descriptors are
+ * non-cacheable so iocc shareability should be disabled.
+ */
+ if (of_dma_is_coherent(dev->of_node))
+ ufs->iocc_val = ufs->iocc_mask;
+ else
+ ufs->iocc_val = 0;
+
+ ufs->pclk_avail_min = PCLK_AVAIL_MIN;
+ ufs->pclk_avail_max = PCLK_AVAIL_MAX;
+
+ attr->rx_adv_fine_gran_sup_en = RX_ADV_FINE_GRAN_SUP_EN;
+ attr->rx_adv_fine_gran_step = RX_ADV_FINE_GRAN_STEP_VAL;
+ attr->rx_adv_min_actv_time_cap = RX_ADV_MIN_ACTV_TIME_CAP;
+ attr->pa_granularity = PA_GRANULARITY_VAL;
+ attr->pa_tactivate = PA_TACTIVATE_VAL;
+ attr->pa_hibern8time = PA_HIBERN8TIME_VAL;
+
+out:
+ return ret;
+}
+
+static inline void exynos_ufs_priv_init(struct ufs_hba *hba,
+ struct exynos_ufs *ufs)
+{
+ ufs->hba = hba;
+ ufs->opts = ufs->drv_data->opts;
+ ufs->rx_sel_idx = PA_MAXDATALANES;
+ if (ufs->opts & EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX)
+ ufs->rx_sel_idx = 0;
+ hba->priv = (void *)ufs;
+ hba->quirks = ufs->drv_data->quirks;
+}
+
+#ifdef CONFIG_SCSI_UFS_CRYPTO
+
+/*
+ * Support for Flash Memory Protector (FMP), which is the inline encryption
+ * hardware on Exynos and Exynos-based SoCs. The interface to this hardware is
+ * not compatible with the standard UFS crypto. It requires that encryption be
+ * configured in the PRDT using a nonstandard extension.
+ */
+
+enum fmp_crypto_algo_mode {
+ FMP_BYPASS_MODE = 0,
+ FMP_ALGO_MODE_AES_CBC = 1,
+ FMP_ALGO_MODE_AES_XTS = 2,
+};
+enum fmp_crypto_key_length {
+ FMP_KEYLEN_256BIT = 1,
+};
+
+/**
+ * struct fmp_sg_entry - nonstandard format of PRDT entries when FMP is enabled
+ *
+ * @base: The standard PRDT entry, but with nonstandard bitfields in the high
+ * bits of the 'size' field, i.e. the last 32-bit word. When these
+ * nonstandard bitfields are zero, the data segment won't be encrypted or
+ * decrypted. Otherwise they specify the algorithm and key length with
+ * which the data segment will be encrypted or decrypted.
+ * @file_iv: The initialization vector (IV) with all bytes reversed
+ * @file_enckey: The first half of the AES-XTS key with all bytes reserved
+ * @file_twkey: The second half of the AES-XTS key with all bytes reserved
+ * @disk_iv: Unused
+ * @reserved: Unused
+ */
+struct fmp_sg_entry {
+ struct ufshcd_sg_entry base;
+ __be64 file_iv[2];
+ __be64 file_enckey[4];
+ __be64 file_twkey[4];
+ __be64 disk_iv[2];
+ __be64 reserved[2];
+};
+
+#define SMC_CMD_FMP_SECURITY \
+ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
+ ARM_SMCCC_OWNER_SIP, 0x1810)
+#define SMC_CMD_SMU \
+ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
+ ARM_SMCCC_OWNER_SIP, 0x1850)
+#define SMC_CMD_FMP_SMU_RESUME \
+ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
+ ARM_SMCCC_OWNER_SIP, 0x1860)
+#define SMU_EMBEDDED 0
+#define SMU_INIT 0
+#define CFG_DESCTYPE_3 3
+
+static void exynos_ufs_fmp_init(struct ufs_hba *hba, struct exynos_ufs *ufs)
+{
+ struct blk_crypto_profile *profile = &hba->crypto_profile;
+ struct arm_smccc_res res;
+ int err;
+
+ /*
+ * Check for the standard crypto support bit, since it's available even
+ * though the rest of the interface to FMP is nonstandard.
+ *
+ * This check should have the effect of preventing the driver from
+ * trying to use FMP on old Exynos SoCs that don't have FMP.
+ */
+ if (!(ufshcd_readl(hba, REG_CONTROLLER_CAPABILITIES) &
+ MASK_CRYPTO_SUPPORT))
+ return;
+
+ /*
+ * The below sequence of SMC calls to enable FMP can be found in the
+ * downstream driver source for gs101 and other Exynos-based SoCs. It
+ * is the only way to enable FMP that works on SoCs such as gs101 that
+ * don't make the FMP registers accessible to Linux. It probably works
+ * on other Exynos-based SoCs too, and might even still be the only way
+ * that works. But this hasn't been properly tested, and this code is
+ * mutually exclusive with exynos_ufs_config_smu(). So for now only
+ * enable FMP support on SoCs with EXYNOS_UFS_OPT_UFSPR_SECURE.
+ */
+ if (!(ufs->opts & EXYNOS_UFS_OPT_UFSPR_SECURE))
+ return;
+
+ /*
+ * This call (which sets DESCTYPE to 0x3 in the FMPSECURITY0 register)
+ * is needed to make the hardware use the larger PRDT entry size.
+ */
+ BUILD_BUG_ON(sizeof(struct fmp_sg_entry) != 128);
+ arm_smccc_smc(SMC_CMD_FMP_SECURITY, 0, SMU_EMBEDDED, CFG_DESCTYPE_3,
+ 0, 0, 0, 0, &res);
+ if (res.a0) {
+ dev_warn(hba->dev,
+ "SMC_CMD_FMP_SECURITY failed on init: %ld. Disabling FMP support.\n",
+ res.a0);
+ return;
+ }
+ ufshcd_set_sg_entry_size(hba, sizeof(struct fmp_sg_entry));
+
+ /*
+ * This is needed to initialize FMP. Without it, errors occur when
+ * inline encryption is used.
+ */
+ arm_smccc_smc(SMC_CMD_SMU, SMU_INIT, SMU_EMBEDDED, 0, 0, 0, 0, 0, &res);
+ if (res.a0) {
+ dev_err(hba->dev,
+ "SMC_CMD_SMU(SMU_INIT) failed: %ld. Disabling FMP support.\n",
+ res.a0);
+ return;
+ }
+
+ /* Advertise crypto capabilities to the block layer. */
+ err = devm_blk_crypto_profile_init(hba->dev, profile, 0);
+ if (err) {
+ /* Only ENOMEM should be possible here. */
+ dev_err(hba->dev, "Failed to initialize crypto profile: %d\n",
+ err);
+ return;
+ }
+ profile->max_dun_bytes_supported = AES_BLOCK_SIZE;
+ profile->key_types_supported = BLK_CRYPTO_KEY_TYPE_RAW;
+ profile->dev = hba->dev;
+ profile->modes_supported[BLK_ENCRYPTION_MODE_AES_256_XTS] =
+ DATA_UNIT_SIZE;
+
+ /* Advertise crypto support to ufshcd-core. */
+ hba->caps |= UFSHCD_CAP_CRYPTO;
+
+ /* Advertise crypto quirks to ufshcd-core. */
+ hba->quirks |= UFSHCD_QUIRK_CUSTOM_CRYPTO_PROFILE |
+ UFSHCD_QUIRK_BROKEN_CRYPTO_ENABLE |
+ UFSHCD_QUIRK_KEYS_IN_PRDT;
+
+}
+
+static void exynos_ufs_fmp_resume(struct ufs_hba *hba)
+{
+ struct arm_smccc_res res;
+
+ if (!(hba->caps & UFSHCD_CAP_CRYPTO))
+ return;
+
+ arm_smccc_smc(SMC_CMD_FMP_SECURITY, 0, SMU_EMBEDDED, CFG_DESCTYPE_3,
+ 0, 0, 0, 0, &res);
+ if (res.a0)
+ dev_err(hba->dev,
+ "SMC_CMD_FMP_SECURITY failed on resume: %ld\n", res.a0);
+
+ arm_smccc_smc(SMC_CMD_FMP_SMU_RESUME, 0, SMU_EMBEDDED, 0, 0, 0, 0, 0,
+ &res);
+ if (res.a0)
+ dev_err(hba->dev,
+ "SMC_CMD_FMP_SMU_RESUME failed: %ld\n", res.a0);
+}
+
+static inline __be64 fmp_key_word(const u8 *key, int j)
+{
+ return cpu_to_be64(get_unaligned_le64(
+ key + AES_KEYSIZE_256 - (j + 1) * sizeof(u64)));
+}
+
+/* Fill the PRDT for a request according to the given encryption context. */
+static int exynos_ufs_fmp_fill_prdt(struct ufs_hba *hba,
+ const struct bio_crypt_ctx *crypt_ctx,
+ void *prdt, unsigned int num_segments)
+{
+ struct fmp_sg_entry *fmp_prdt = prdt;
+ const u8 *enckey = crypt_ctx->bc_key->bytes;
+ const u8 *twkey = enckey + AES_KEYSIZE_256;
+ u64 dun_lo = crypt_ctx->bc_dun[0];
+ u64 dun_hi = crypt_ctx->bc_dun[1];
+ unsigned int i;
+
+ /* If FMP wasn't enabled, we shouldn't get any encrypted requests. */
+ if (WARN_ON_ONCE(!(hba->caps & UFSHCD_CAP_CRYPTO)))
+ return -EIO;
+
+ /* Configure FMP on each segment of the request. */
+ for (i = 0; i < num_segments; i++) {
+ struct fmp_sg_entry *prd = &fmp_prdt[i];
+ int j;
+
+ /* Each segment must be exactly one data unit. */
+ if (prd->base.size != cpu_to_le32(DATA_UNIT_SIZE - 1)) {
+ dev_err(hba->dev,
+ "data segment is misaligned for FMP\n");
+ return -EIO;
+ }
+
+ /* Set the algorithm and key length. */
+ prd->base.size |= cpu_to_le32((FMP_ALGO_MODE_AES_XTS << 28) |
+ (FMP_KEYLEN_256BIT << 26));
+
+ /* Set the IV. */
+ prd->file_iv[0] = cpu_to_be64(dun_hi);
+ prd->file_iv[1] = cpu_to_be64(dun_lo);
+
+ /* Set the key. */
+ for (j = 0; j < AES_KEYSIZE_256 / sizeof(u64); j++) {
+ prd->file_enckey[j] = fmp_key_word(enckey, j);
+ prd->file_twkey[j] = fmp_key_word(twkey, j);
+ }
+
+ /* Increment the data unit number. */
+ dun_lo++;
+ if (dun_lo == 0)
+ dun_hi++;
+ }
+ return 0;
+}
+
+#else /* CONFIG_SCSI_UFS_CRYPTO */
+
+static void exynos_ufs_fmp_init(struct ufs_hba *hba, struct exynos_ufs *ufs)
+{
+}
+
+static void exynos_ufs_fmp_resume(struct ufs_hba *hba)
+{
+}
+
+#define exynos_ufs_fmp_fill_prdt NULL
+
+#endif /* !CONFIG_SCSI_UFS_CRYPTO */
+
+static int exynos_ufs_init(struct ufs_hba *hba)
+{
+ struct device *dev = hba->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_ufs *ufs;
+ int ret;
+
+ ufs = devm_kzalloc(dev, sizeof(*ufs), GFP_KERNEL);
+ if (!ufs)
+ return -ENOMEM;
+
+ /* exynos-specific hci */
+ ufs->reg_hci = devm_platform_ioremap_resource_byname(pdev, "vs_hci");
+ if (IS_ERR(ufs->reg_hci)) {
+ dev_err(dev, "cannot ioremap for hci vendor register\n");
+ return PTR_ERR(ufs->reg_hci);
+ }
+
+ /* unipro */
+ ufs->reg_unipro = devm_platform_ioremap_resource_byname(pdev, "unipro");
+ if (IS_ERR(ufs->reg_unipro)) {
+ dev_err(dev, "cannot ioremap for unipro register\n");
+ return PTR_ERR(ufs->reg_unipro);
+ }
+
+ /* ufs protector */
+ ufs->reg_ufsp = devm_platform_ioremap_resource_byname(pdev, "ufsp");
+ if (IS_ERR(ufs->reg_ufsp)) {
+ dev_err(dev, "cannot ioremap for ufs protector register\n");
+ return PTR_ERR(ufs->reg_ufsp);
+ }
+
+ ret = exynos_ufs_parse_dt(dev, ufs);
+ if (ret) {
+ dev_err(dev, "failed to get dt info.\n");
+ goto out;
+ }
+
+ ufs->phy = devm_phy_get(dev, "ufs-phy");
+ if (IS_ERR(ufs->phy)) {
+ ret = PTR_ERR(ufs->phy);
+ dev_err(dev, "failed to get ufs-phy\n");
+ goto out;
+ }
+
+ exynos_ufs_priv_init(hba, ufs);
+
+ exynos_ufs_fmp_init(hba, ufs);
+
+ if (ufs->drv_data->drv_init) {
+ ret = ufs->drv_data->drv_init(ufs);
+ if (ret) {
+ dev_err(dev, "failed to init drv-data\n");
+ goto out;
+ }
+ }
+
+ ret = exynos_ufs_get_clk_info(ufs);
+ if (ret)
+ goto out;
+ exynos_ufs_specify_phy_time_attr(ufs);
+
+ exynos_ufs_config_smu(ufs);
+
+ hba->host->dma_alignment = DATA_UNIT_SIZE - 1;
+ return 0;
+
+out:
+ hba->priv = NULL;
+ return ret;
+}
+
+static void exynos_ufs_exit(struct ufs_hba *hba)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+
+ phy_power_off(ufs->phy);
+ phy_exit(ufs->phy);
+}
+
+static int exynos_ufs_host_reset(struct ufs_hba *hba)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ unsigned long timeout = jiffies + msecs_to_jiffies(1);
+ u32 val;
+ int ret = 0;
+
+ exynos_ufs_disable_auto_ctrl_hcc_save(ufs, &val);
+
+ hci_writel(ufs, UFS_SW_RST_MASK, HCI_SW_RST);
+
+ do {
+ if (!(hci_readl(ufs, HCI_SW_RST) & UFS_SW_RST_MASK))
+ goto out;
+ } while (time_before(jiffies, timeout));
+
+ dev_err(hba->dev, "timeout host sw-reset\n");
+ ret = -ETIMEDOUT;
+
+out:
+ exynos_ufs_auto_ctrl_hcc_restore(ufs, &val);
+ return ret;
+}
+
+static void exynos_ufs_dev_hw_reset(struct ufs_hba *hba)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+
+ hci_writel(ufs, 0 << 0, HCI_GPIO_OUT);
+ udelay(5);
+ hci_writel(ufs, 1 << 0, HCI_GPIO_OUT);
+}
+
+static void exynos_ufs_pre_hibern8(struct ufs_hba *hba, enum uic_cmd_dme cmd)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+ static const union phy_notify phystate = {
+ .ufs_state = PHY_UFS_HIBERN8_EXIT
+ };
+
+ if (cmd == UIC_CMD_DME_HIBER_EXIT) {
+ if (ufs->opts & EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL)
+ exynos_ufs_disable_auto_ctrl_hcc(ufs);
+ exynos_ufs_ungate_clks(ufs);
+
+ phy_notify_state(ufs->phy, phystate);
+
+ if (ufs->opts & EXYNOS_UFS_OPT_USE_SW_HIBERN8_TIMER) {
+ static const unsigned int granularity_tbl[] = {
+ 1, 4, 8, 16, 32, 100
+ };
+ int h8_time = attr->pa_hibern8time *
+ granularity_tbl[attr->pa_granularity - 1];
+ unsigned long us;
+ s64 delta;
+
+ do {
+ delta = h8_time - ktime_us_delta(ktime_get(),
+ ufs->entry_hibern8_t);
+ if (delta <= 0)
+ break;
+
+ us = min_t(s64, delta, USEC_PER_MSEC);
+ if (us >= 10)
+ usleep_range(us, us + 10);
+ } while (1);
+ }
+ }
+}
+
+static void exynos_ufs_post_hibern8(struct ufs_hba *hba, enum uic_cmd_dme cmd)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ static const union phy_notify phystate = {
+ .ufs_state = PHY_UFS_HIBERN8_ENTER
+ };
+
+ if (cmd == UIC_CMD_DME_HIBER_ENTER) {
+ ufs->entry_hibern8_t = ktime_get();
+ exynos_ufs_gate_clks(ufs);
+ if (ufs->opts & EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL)
+ exynos_ufs_enable_auto_ctrl_hcc(ufs);
+
+ phy_notify_state(ufs->phy, phystate);
+ }
+}
+
+static int exynos_ufs_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+ int ret = 0;
+
+ switch (status) {
+ case PRE_CHANGE:
+ /*
+ * The maximum segment size must be set after scsi_host_alloc()
+ * has been called and before LUN scanning starts
+ * (ufshcd_async_scan()). Note: this callback may also be called
+ * from other functions than ufshcd_init().
+ */
+ hba->host->max_segment_size = DATA_UNIT_SIZE;
+
+ if (ufs->drv_data->pre_hce_enable) {
+ ret = ufs->drv_data->pre_hce_enable(ufs);
+ if (ret)
+ return ret;
+ }
+
+ ret = exynos_ufs_host_reset(hba);
+ if (ret)
+ return ret;
+ exynos_ufs_dev_hw_reset(hba);
+ break;
+ case POST_CHANGE:
+ exynos_ufs_calc_pwm_clk_div(ufs);
+ if (!(ufs->opts & EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL))
+ exynos_ufs_enable_auto_ctrl_hcc(ufs);
+
+ if (ufs->drv_data->post_hce_enable)
+ ret = ufs->drv_data->post_hce_enable(ufs);
+
+ break;
+ }
+
+ return ret;
+}
+
+static int exynos_ufs_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ int ret = 0;
+
+ switch (status) {
+ case PRE_CHANGE:
+ ret = exynos_ufs_pre_link(hba);
+ break;
+ case POST_CHANGE:
+ ret = exynos_ufs_post_link(hba);
+ break;
+ }
+
+ return ret;
+}
+
+static int exynos_ufs_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_host_params host_params;
+
+ ufshcd_init_host_params(&host_params);
+
+ /* This driver only support symmetric gear setting e.g. hs_tx_gear == hs_rx_gear */
+ host_params.hs_tx_gear = exynos_ufs_get_hs_gear(hba);
+ host_params.hs_rx_gear = exynos_ufs_get_hs_gear(hba);
+
+ return ufshcd_negotiate_pwr_params(&host_params, dev_max_params, dev_req_params);
+}
+
+static int exynos_ufs_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ int ret = 0;
+
+ switch (status) {
+ case PRE_CHANGE:
+ ret = exynos_ufs_pre_pwr_mode(hba, dev_req_params);
+ break;
+ case POST_CHANGE:
+ ret = exynos_ufs_post_pwr_mode(hba, dev_req_params);
+ break;
+ }
+
+ return ret;
+}
+
+static void exynos_ufs_hibern8_notify(struct ufs_hba *hba,
+ enum uic_cmd_dme cmd,
+ enum ufs_notify_change_status notify)
+{
+ switch ((u8)notify) {
+ case PRE_CHANGE:
+ exynos_ufs_pre_hibern8(hba, cmd);
+ break;
+ case POST_CHANGE:
+ exynos_ufs_post_hibern8(hba, cmd);
+ break;
+ }
+}
+
+static int gs101_ufs_suspend(struct exynos_ufs *ufs)
+{
+ hci_writel(ufs, 0 << 0, HCI_GPIO_OUT);
+ return 0;
+}
+
+static int exynos_ufs_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op,
+ enum ufs_notify_change_status status)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+
+ if (status == PRE_CHANGE)
+ return 0;
+
+ if (ufs->drv_data->suspend)
+ ufs->drv_data->suspend(ufs);
+
+ if (!ufshcd_is_link_active(hba))
+ phy_power_off(ufs->phy);
+
+ return 0;
+}
+
+static int exynos_ufs_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+ struct exynos_ufs *ufs = ufshcd_get_variant(hba);
+
+ if (!ufshcd_is_link_active(hba))
+ phy_power_on(ufs->phy);
+
+ exynos_ufs_config_smu(ufs);
+ exynos_ufs_fmp_resume(hba);
+ return 0;
+}
+
+static int exynosauto_ufs_vh_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ if (status == POST_CHANGE) {
+ ufshcd_set_link_active(hba);
+ ufshcd_set_ufs_dev_active(hba);
+ }
+
+ return 0;
+}
+
+static int exynosauto_ufs_vh_wait_ph_ready(struct ufs_hba *hba)
+{
+ u32 mbox;
+ ktime_t start, stop;
+
+ start = ktime_get();
+ stop = ktime_add(start, ms_to_ktime(PH_READY_TIMEOUT_MS));
+
+ do {
+ mbox = ufshcd_readl(hba, PH2VH_MBOX);
+ /* TODO: Mailbox message protocols between the PH and VHs are
+ * not implemented yet. This will be supported later
+ */
+ if ((mbox & MH_MSG_MASK) == MH_MSG_PH_READY)
+ return 0;
+
+ usleep_range(40, 50);
+ } while (ktime_before(ktime_get(), stop));
+
+ return -ETIME;
+}
+
+static int exynosauto_ufs_vh_init(struct ufs_hba *hba)
+{
+ struct device *dev = hba->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_ufs *ufs;
+ int ret;
+
+ ufs = devm_kzalloc(dev, sizeof(*ufs), GFP_KERNEL);
+ if (!ufs)
+ return -ENOMEM;
+
+ /* exynos-specific hci */
+ ufs->reg_hci = devm_platform_ioremap_resource_byname(pdev, "vs_hci");
+ if (IS_ERR(ufs->reg_hci)) {
+ dev_err(dev, "cannot ioremap for hci vendor register\n");
+ return PTR_ERR(ufs->reg_hci);
+ }
+
+ ret = exynosauto_ufs_vh_wait_ph_ready(hba);
+ if (ret)
+ return ret;
+
+ ufs->drv_data = device_get_match_data(dev);
+ if (!ufs->drv_data)
+ return -ENODEV;
+
+ exynos_ufs_priv_init(hba, ufs);
+
+ return 0;
+}
+
+static int fsd_ufs_pre_link(struct exynos_ufs *ufs)
+{
+ struct exynos_ufs_uic_attr *attr = ufs->drv_data->uic_attr;
+ struct ufs_hba *hba = ufs->hba;
+ int i;
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(attr->pa_dbg_clk_period_off),
+ DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate));
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x201), 0x12);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x200), 0x40);
+
+ for_each_ufs_tx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xAA, i),
+ DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8F, i), 0x3F);
+ }
+
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x12, i),
+ DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x5C, i), 0x38);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0F, i), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x65, i), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x69, i), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x21, i), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x22, i), 0x0);
+ }
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x200), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_AUTOMODE_THLD), 0x4E20);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(attr->pa_dbg_opt_suite1_off),
+ 0x2e820183);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), 0x0);
+
+ exynos_ufs_establish_connt(ufs);
+
+ return 0;
+}
+
+static int fsd_ufs_post_link(struct exynos_ufs *ufs)
+{
+ int i;
+ struct ufs_hba *hba = ufs->hba;
+ u32 hw_cap_min_tactivate;
+ u32 peer_rx_min_actv_time_cap;
+ u32 max_rx_hibern8_time_cap;
+
+ ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(0x8F, 4),
+ &hw_cap_min_tactivate); /* HW Capability of MIN_TACTIVATE */
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_TACTIVATE),
+ &peer_rx_min_actv_time_cap); /* PA_TActivate */
+ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_HIBERN8TIME),
+ &max_rx_hibern8_time_cap); /* PA_Hibern8Time */
+
+ if (peer_rx_min_actv_time_cap >= hw_cap_min_tactivate)
+ ufshcd_dme_peer_set(hba, UIC_ARG_MIB(PA_TACTIVATE),
+ peer_rx_min_actv_time_cap + 1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HIBERN8TIME), max_rx_hibern8_time_cap + 1);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_MODE), 0x01);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_SAVECONFIGTIME), 0xFA);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_MODE), 0x00);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x200), 0x40);
+
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x35, i), 0x05);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x73, i), 0x01);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x41, i), 0x02);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x42, i), 0xAC);
+ }
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x200), 0x0);
+
+ return 0;
+}
+
+static int fsd_ufs_pre_pwr_change(struct exynos_ufs *ufs,
+ struct ufs_pa_layer_attr *pwr)
+{
+ struct ufs_hba *hba = ufs->hba;
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXTERMINATION), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_RXTERMINATION), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA0), 12000);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA1), 32000);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA2), 16000);
+
+ unipro_writel(ufs, 12000, UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER0);
+ unipro_writel(ufs, 32000, UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER1);
+ unipro_writel(ufs, 16000, UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER2);
+
+ return 0;
+}
+
+static int fsd_ufs_suspend(struct exynos_ufs *ufs)
+{
+ exynos_ufs_gate_clks(ufs);
+ hci_writel(ufs, 0, HCI_GPIO_OUT);
+ return 0;
+}
+
+static inline u32 get_mclk_period_unipro_18(struct exynos_ufs *ufs)
+{
+ return (16 * 1000 * 1000000UL / ufs->mclk_rate);
+}
+
+static int gs101_ufs_pre_link(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+ int i;
+ u32 tx_line_reset_period, rx_line_reset_period;
+
+ rx_line_reset_period = (RX_LINE_RESET_TIME * ufs->mclk_rate)
+ / NSEC_PER_MSEC;
+ tx_line_reset_period = (TX_LINE_RESET_TIME * ufs->mclk_rate)
+ / NSEC_PER_MSEC;
+
+ unipro_writel(ufs, get_mclk_period_unipro_18(ufs), COMP_CLK_PERIOD);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x200), 0x40);
+
+ for_each_ufs_rx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_CLK_PRD, i),
+ DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_CLK_PRD_EN, i), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE2, i),
+ (rx_line_reset_period >> 16) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE1, i),
+ (rx_line_reset_period >> 8) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE0, i),
+ (rx_line_reset_period) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x2f, i), 0x69);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x84, i), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x25, i), 0xf6);
+ }
+
+ for_each_ufs_tx_lane(ufs, i) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_CLK_PRD, i),
+ DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate));
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_CLK_PRD_EN, i),
+ 0x02);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE2, i),
+ (tx_line_reset_period >> 16) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE1, i),
+ (tx_line_reset_period >> 8) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE0, i),
+ (tx_line_reset_period) & 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x04, i), 1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x7F, i), 0);
+ }
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x200), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(N_DEVICEID), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(N_DEVICEID_VALID), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_PEERDEVICEID), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(T_CONNECTIONSTATE), CPORT_CONNECTED);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xA006), 0x8000);
+
+ return 0;
+}
+
+static int gs101_ufs_post_link(struct exynos_ufs *ufs)
+{
+ struct ufs_hba *hba = ufs->hba;
+
+ /*
+ * Enable Write Line Unique. This field has to be 0x3
+ * to support Write Line Unique transaction on gs101.
+ */
+ hci_writel(ufs, WLU_EN | WLU_BURST_LEN(3), HCI_AXIDMA_RWDATA_BURST_LEN);
+
+ exynos_ufs_enable_dbg_mode(hba);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_SAVECONFIGTIME), 0x3e8);
+ exynos_ufs_disable_dbg_mode(hba);
+
+ return 0;
+}
+
+static int gs101_ufs_pre_pwr_change(struct exynos_ufs *ufs,
+ struct ufs_pa_layer_attr *pwr)
+{
+ struct ufs_hba *hba = ufs->hba;
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA0), 12000);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA1), 32000);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA2), 16000);
+ unipro_writel(ufs, 8064, UNIPRO_DME_POWERMODE_REQ_LOCALL2TIMER0);
+ unipro_writel(ufs, 28224, UNIPRO_DME_POWERMODE_REQ_LOCALL2TIMER1);
+ unipro_writel(ufs, 20160, UNIPRO_DME_POWERMODE_REQ_LOCALL2TIMER2);
+ unipro_writel(ufs, 12000, UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER0);
+ unipro_writel(ufs, 32000, UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER1);
+ unipro_writel(ufs, 16000, UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER2);
+
+ return 0;
+}
+
+static const struct ufs_hba_variant_ops ufs_hba_exynos_ops = {
+ .name = "exynos_ufs",
+ .init = exynos_ufs_init,
+ .exit = exynos_ufs_exit,
+ .hce_enable_notify = exynos_ufs_hce_enable_notify,
+ .link_startup_notify = exynos_ufs_link_startup_notify,
+ .negotiate_pwr_mode = exynos_ufs_negotiate_pwr_mode,
+ .pwr_change_notify = exynos_ufs_pwr_change_notify,
+ .setup_clocks = exynos_ufs_setup_clocks,
+ .setup_xfer_req = exynos_ufs_specify_nexus_t_xfer_req,
+ .setup_task_mgmt = exynos_ufs_specify_nexus_t_tm_req,
+ .hibern8_notify = exynos_ufs_hibern8_notify,
+ .suspend = exynos_ufs_suspend,
+ .resume = exynos_ufs_resume,
+ .fill_crypto_prdt = exynos_ufs_fmp_fill_prdt,
+};
+
+static struct ufs_hba_variant_ops ufs_hba_exynosauto_vh_ops = {
+ .name = "exynosauto_ufs_vh",
+ .init = exynosauto_ufs_vh_init,
+ .link_startup_notify = exynosauto_ufs_vh_link_startup_notify,
+};
+
+static int exynos_ufs_probe(struct platform_device *pdev)
+{
+ int err;
+ struct device *dev = &pdev->dev;
+ const struct ufs_hba_variant_ops *vops = &ufs_hba_exynos_ops;
+ const struct exynos_ufs_drv_data *drv_data =
+ device_get_match_data(dev);
+
+ if (drv_data && drv_data->vops)
+ vops = drv_data->vops;
+
+ err = ufshcd_pltfrm_init(pdev, vops);
+ if (err)
+ dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err);
+
+ return err;
+}
+
+static void exynos_ufs_remove(struct platform_device *pdev)
+{
+ ufshcd_pltfrm_remove(pdev);
+}
+
+static struct exynos_ufs_uic_attr exynos7_uic_attr = {
+ .tx_trailingclks = 0x10,
+ .tx_dif_p_nsec = 3000000, /* unit: ns */
+ .tx_dif_n_nsec = 1000000, /* unit: ns */
+ .tx_high_z_cnt_nsec = 20000, /* unit: ns */
+ .tx_base_unit_nsec = 100000, /* unit: ns */
+ .tx_gran_unit_nsec = 4000, /* unit: ns */
+ .tx_sleep_cnt = 1000, /* unit: ns */
+ .tx_min_activatetime = 0xa,
+ .rx_filler_enable = 0x2,
+ .rx_dif_p_nsec = 1000000, /* unit: ns */
+ .rx_hibern8_wait_nsec = 4000000, /* unit: ns */
+ .rx_base_unit_nsec = 100000, /* unit: ns */
+ .rx_gran_unit_nsec = 4000, /* unit: ns */
+ .rx_sleep_cnt = 1280, /* unit: ns */
+ .rx_stall_cnt = 320, /* unit: ns */
+ .rx_hs_g1_sync_len_cap = SYNC_LEN_COARSE(0xf),
+ .rx_hs_g2_sync_len_cap = SYNC_LEN_COARSE(0xf),
+ .rx_hs_g3_sync_len_cap = SYNC_LEN_COARSE(0xf),
+ .rx_hs_g1_prep_sync_len_cap = PREP_LEN(0xf),
+ .rx_hs_g2_prep_sync_len_cap = PREP_LEN(0xf),
+ .rx_hs_g3_prep_sync_len_cap = PREP_LEN(0xf),
+ .pa_dbg_clk_period_off = PA_DBG_CLK_PERIOD,
+ .pa_dbg_opt_suite1_val = 0x30103,
+ .pa_dbg_opt_suite1_off = PA_DBG_OPTION_SUITE,
+};
+
+static const struct exynos_ufs_drv_data exynosauto_ufs_drvs = {
+ .uic_attr = &exynos7_uic_attr,
+ .quirks = UFSHCD_QUIRK_PRDT_BYTE_GRAN |
+ UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR |
+ UFSHCD_QUIRK_BROKEN_OCS_FATAL_ERROR |
+ UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING,
+ .opts = EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL |
+ EXYNOS_UFS_OPT_SKIP_CONFIG_PHY_ATTR |
+ EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX,
+ .iocc_mask = UFS_EXYNOSAUTO_SHARABLE,
+ .drv_init = exynosauto_ufs_drv_init,
+ .post_hce_enable = exynosauto_ufs_post_hce_enable,
+ .pre_link = exynosauto_ufs_pre_link,
+ .pre_pwr_change = exynosauto_ufs_pre_pwr_change,
+ .post_pwr_change = exynosauto_ufs_post_pwr_change,
+};
+
+static const struct exynos_ufs_drv_data exynosauto_ufs_vh_drvs = {
+ .vops = &ufs_hba_exynosauto_vh_ops,
+ .quirks = UFSHCD_QUIRK_PRDT_BYTE_GRAN |
+ UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR |
+ UFSHCD_QUIRK_BROKEN_OCS_FATAL_ERROR |
+ UFSHCI_QUIRK_BROKEN_HCE |
+ UFSHCD_QUIRK_BROKEN_UIC_CMD |
+ UFSHCD_QUIRK_SKIP_PH_CONFIGURATION |
+ UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING,
+ .opts = EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX,
+};
+
+static const struct exynos_ufs_drv_data exynos_ufs_drvs = {
+ .uic_attr = &exynos7_uic_attr,
+ .quirks = UFSHCD_QUIRK_PRDT_BYTE_GRAN |
+ UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR |
+ UFSHCI_QUIRK_BROKEN_HCE |
+ UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR |
+ UFSHCD_QUIRK_BROKEN_OCS_FATAL_ERROR |
+ UFSHCI_QUIRK_SKIP_MANUAL_WB_FLUSH_CTRL |
+ UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING,
+ .opts = EXYNOS_UFS_OPT_HAS_APB_CLK_CTRL |
+ EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL |
+ EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX |
+ EXYNOS_UFS_OPT_SKIP_CONNECTION_ESTAB |
+ EXYNOS_UFS_OPT_USE_SW_HIBERN8_TIMER,
+ .pre_link = exynos7_ufs_pre_link,
+ .post_link = exynos7_ufs_post_link,
+ .pre_pwr_change = exynos7_ufs_pre_pwr_change,
+ .post_pwr_change = exynos7_ufs_post_pwr_change,
+};
+
+static struct exynos_ufs_uic_attr gs101_uic_attr = {
+ .tx_trailingclks = 0xff,
+ .pa_dbg_opt_suite1_val = 0x90913C1C,
+ .pa_dbg_opt_suite1_off = PA_GS101_DBG_OPTION_SUITE1,
+ .pa_dbg_opt_suite2_val = 0xE01C115F,
+ .pa_dbg_opt_suite2_off = PA_GS101_DBG_OPTION_SUITE2,
+};
+
+static struct exynos_ufs_uic_attr fsd_uic_attr = {
+ .tx_trailingclks = 0x10,
+ .tx_dif_p_nsec = 3000000, /* unit: ns */
+ .tx_dif_n_nsec = 1000000, /* unit: ns */
+ .tx_high_z_cnt_nsec = 20000, /* unit: ns */
+ .tx_base_unit_nsec = 100000, /* unit: ns */
+ .tx_gran_unit_nsec = 4000, /* unit: ns */
+ .tx_sleep_cnt = 1000, /* unit: ns */
+ .tx_min_activatetime = 0xa,
+ .rx_filler_enable = 0x2,
+ .rx_dif_p_nsec = 1000000, /* unit: ns */
+ .rx_hibern8_wait_nsec = 4000000, /* unit: ns */
+ .rx_base_unit_nsec = 100000, /* unit: ns */
+ .rx_gran_unit_nsec = 4000, /* unit: ns */
+ .rx_sleep_cnt = 1280, /* unit: ns */
+ .rx_stall_cnt = 320, /* unit: ns */
+ .rx_hs_g1_sync_len_cap = SYNC_LEN_COARSE(0xf),
+ .rx_hs_g2_sync_len_cap = SYNC_LEN_COARSE(0xf),
+ .rx_hs_g3_sync_len_cap = SYNC_LEN_COARSE(0xf),
+ .rx_hs_g1_prep_sync_len_cap = PREP_LEN(0xf),
+ .rx_hs_g2_prep_sync_len_cap = PREP_LEN(0xf),
+ .rx_hs_g3_prep_sync_len_cap = PREP_LEN(0xf),
+ .pa_dbg_clk_period_off = PA_DBG_CLK_PERIOD,
+ .pa_dbg_opt_suite1_val = 0x2E820183,
+ .pa_dbg_opt_suite1_off = PA_DBG_OPTION_SUITE,
+};
+
+static const struct exynos_ufs_drv_data fsd_ufs_drvs = {
+ .uic_attr = &fsd_uic_attr,
+ .quirks = UFSHCD_QUIRK_PRDT_BYTE_GRAN |
+ UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR |
+ UFSHCD_QUIRK_BROKEN_OCS_FATAL_ERROR |
+ UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING |
+ UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR,
+ .opts = EXYNOS_UFS_OPT_HAS_APB_CLK_CTRL |
+ EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL |
+ EXYNOS_UFS_OPT_SKIP_CONFIG_PHY_ATTR |
+ EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX,
+ .pre_link = fsd_ufs_pre_link,
+ .post_link = fsd_ufs_post_link,
+ .pre_pwr_change = fsd_ufs_pre_pwr_change,
+ .suspend = fsd_ufs_suspend,
+};
+
+static const struct exynos_ufs_drv_data gs101_ufs_drvs = {
+ .uic_attr = &gs101_uic_attr,
+ .quirks = UFSHCD_QUIRK_PRDT_BYTE_GRAN |
+ UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR |
+ UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR |
+ UFSHCD_QUIRK_BROKEN_OCS_FATAL_ERROR |
+ UFSHCI_QUIRK_SKIP_MANUAL_WB_FLUSH_CTRL |
+ UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING,
+ .opts = EXYNOS_UFS_OPT_SKIP_CONFIG_PHY_ATTR |
+ EXYNOS_UFS_OPT_UFSPR_SECURE |
+ EXYNOS_UFS_OPT_TIMER_TICK_SELECT,
+ .iocc_mask = UFS_GS101_SHARABLE,
+ .drv_init = gs101_ufs_drv_init,
+ .pre_link = gs101_ufs_pre_link,
+ .post_link = gs101_ufs_post_link,
+ .pre_pwr_change = gs101_ufs_pre_pwr_change,
+ .suspend = gs101_ufs_suspend,
+};
+
+static const struct exynos_ufs_drv_data exynosautov920_ufs_drvs = {
+ .uic_attr = &exynos7_uic_attr,
+ .quirks = UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING,
+ .opts = EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL |
+ EXYNOS_UFS_OPT_SKIP_CONFIG_PHY_ATTR |
+ EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX |
+ EXYNOS_UFS_OPT_TIMER_TICK_SELECT,
+ .iocc_mask = UFS_EXYNOSAUTOV920_SHARABLE,
+ .drv_init = exynosauto_ufs_drv_init,
+ .post_hce_enable = exynosauto_ufs_post_hce_enable,
+ .pre_link = exynosautov920_ufs_pre_link,
+ .post_link = exynosautov920_ufs_post_link,
+ .pre_pwr_change = exynosautov920_ufs_pre_pwr_change,
+};
+
+static const struct of_device_id exynos_ufs_of_match[] = {
+ { .compatible = "google,gs101-ufs",
+ .data = &gs101_ufs_drvs },
+ { .compatible = "samsung,exynos7-ufs",
+ .data = &exynos_ufs_drvs },
+ { .compatible = "samsung,exynosautov9-ufs",
+ .data = &exynosauto_ufs_drvs },
+ { .compatible = "samsung,exynosautov9-ufs-vh",
+ .data = &exynosauto_ufs_vh_drvs },
+ { .compatible = "samsung,exynosautov920-ufs",
+ .data = &exynosautov920_ufs_drvs },
+ { .compatible = "tesla,fsd-ufs",
+ .data = &fsd_ufs_drvs },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos_ufs_of_match);
+
+static const struct dev_pm_ops exynos_ufs_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
+ SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
+ .prepare = ufshcd_suspend_prepare,
+ .complete = ufshcd_resume_complete,
+};
+
+static struct platform_driver exynos_ufs_pltform = {
+ .probe = exynos_ufs_probe,
+ .remove = exynos_ufs_remove,
+ .driver = {
+ .name = "exynos-ufshc",
+ .pm = &exynos_ufs_pm_ops,
+ .of_match_table = exynos_ufs_of_match,
+ },
+};
+module_platform_driver(exynos_ufs_pltform);
+
+MODULE_AUTHOR("Alim Akhtar <alim.akhtar@samsung.com>");
+MODULE_AUTHOR("Seungwon Jeon <essuuj@gmail.com>");
+MODULE_DESCRIPTION("Exynos UFS HCI Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/ufs/host/ufs-exynos.h b/drivers/ufs/host/ufs-exynos.h
new file mode 100644
index 000000000..abe7e4727
--- /dev/null
+++ b/drivers/ufs/host/ufs-exynos.h
@@ -0,0 +1,294 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * UFS Host Controller driver for Exynos specific extensions
+ *
+ * Copyright (C) 2014-2015 Samsung Electronics Co., Ltd.
+ *
+ */
+
+#ifndef _UFS_EXYNOS_H_
+#define _UFS_EXYNOS_H_
+
+/*
+ * Component registers
+ */
+
+#define COMP_CLK_PERIOD 0x44
+
+/*
+ * UNIPRO registers
+ */
+#define UNIPRO_DBG_FORCE_DME_CTRL_STATE 0x150
+
+/*
+ * MIBs for PA debug registers
+ */
+#define PA_DBG_CLK_PERIOD 0x9514
+#define PA_DBG_TXPHY_CFGUPDT 0x9518
+#define PA_DBG_RXPHY_CFGUPDT 0x9519
+#define PA_DBG_MODE 0x9529
+#define PA_DBG_SKIP_RESET_PHY 0x9539
+#define PA_DBG_AUTOMODE_THLD 0x9536
+#define PA_DBG_OV_TM 0x9540
+#define PA_DBG_SKIP_LINE_RESET 0x9541
+#define PA_DBG_LINE_RESET_REQ 0x9543
+#define PA_DBG_OPTION_SUITE 0x9564
+#define PA_DBG_OPTION_SUITE_DYN 0x9565
+
+/*
+ * Note: GS101_DBG_OPTION offsets below differ from the TRM
+ * but match the downstream driver. Following the TRM
+ * results in non-functioning UFS.
+ */
+#define PA_GS101_DBG_OPTION_SUITE1 0x956a
+#define PA_GS101_DBG_OPTION_SUITE2 0x956d
+
+/*
+ * MIBs for Transport Layer debug registers
+ */
+#define T_DBG_SKIP_INIT_HIBERN8_EXIT 0xc001
+
+/*
+ * Exynos MPHY attributes
+ */
+#define TX_LINERESET_N_VAL 0x0277
+#define TX_LINERESET_N(v) (((v) >> 10) & 0xFF)
+#define TX_LINERESET_P_VAL 0x027D
+#define TX_LINERESET_P(v) (((v) >> 12) & 0xFF)
+#define TX_OV_SLEEP_CNT_TIMER 0x028E
+#define TX_OV_H8_ENTER_EN (1 << 7)
+#define TX_OV_SLEEP_CNT(v) (((v) >> 5) & 0x7F)
+#define TX_HIGH_Z_CNT_11_08 0x028C
+#define TX_HIGH_Z_CNT_H(v) (((v) >> 8) & 0xF)
+#define TX_HIGH_Z_CNT_07_00 0x028D
+#define TX_HIGH_Z_CNT_L(v) ((v) & 0xFF)
+#define TX_BASE_NVAL_07_00 0x0293
+#define TX_BASE_NVAL_L(v) ((v) & 0xFF)
+#define TX_BASE_NVAL_15_08 0x0294
+#define TX_BASE_NVAL_H(v) (((v) >> 8) & 0xFF)
+#define TX_GRAN_NVAL_07_00 0x0295
+#define TX_GRAN_NVAL_L(v) ((v) & 0xFF)
+#define TX_GRAN_NVAL_10_08 0x0296
+#define TX_GRAN_NVAL_H(v) (((v) >> 8) & 0x3)
+
+#define VND_TX_CLK_PRD 0xAA
+#define VND_TX_CLK_PRD_EN 0xA9
+#define VND_TX_LINERESET_PVALUE0 0xAD
+#define VND_TX_LINERESET_PVALUE1 0xAC
+#define VND_TX_LINERESET_PVALUE2 0xAB
+
+#define TX_LINE_RESET_TIME 3200
+
+#define VND_RX_CLK_PRD 0x12
+#define VND_RX_CLK_PRD_EN 0x11
+#define VND_RX_LINERESET_VALUE0 0x1D
+#define VND_RX_LINERESET_VALUE1 0x1C
+#define VND_RX_LINERESET_VALUE2 0x1B
+
+#define RX_LINE_RESET_TIME 1000
+
+#define RX_FILLER_ENABLE 0x0316
+#define RX_FILLER_EN (1 << 1)
+#define RX_LINERESET_VAL 0x0317
+#define RX_LINERESET(v) (((v) >> 12) & 0xFF)
+#define RX_LCC_IGNORE 0x0318
+#define RX_SYNC_MASK_LENGTH 0x0321
+#define RX_HIBERN8_WAIT_VAL_BIT_20_16 0x0331
+#define RX_HIBERN8_WAIT_VAL_BIT_15_08 0x0332
+#define RX_HIBERN8_WAIT_VAL_BIT_07_00 0x0333
+#define RX_OV_SLEEP_CNT_TIMER 0x0340
+#define RX_OV_SLEEP_CNT(v) (((v) >> 6) & 0x1F)
+#define RX_OV_STALL_CNT_TIMER 0x0341
+#define RX_OV_STALL_CNT(v) (((v) >> 4) & 0xFF)
+#define RX_BASE_NVAL_07_00 0x0355
+#define RX_BASE_NVAL_L(v) ((v) & 0xFF)
+#define RX_BASE_NVAL_15_08 0x0354
+#define RX_BASE_NVAL_H(v) (((v) >> 8) & 0xFF)
+#define RX_GRAN_NVAL_07_00 0x0353
+#define RX_GRAN_NVAL_L(v) ((v) & 0xFF)
+#define RX_GRAN_NVAL_10_08 0x0352
+#define RX_GRAN_NVAL_H(v) (((v) >> 8) & 0x3)
+
+#define CMN_PWM_CLK_CTRL 0x0402
+#define PWM_CLK_CTRL_MASK 0x3
+
+#define IATOVAL_NSEC 20000 /* unit: ns */
+#define UNIPRO_PCLK_PERIOD(ufs) (NSEC_PER_SEC / ufs->pclk_rate)
+
+struct exynos_ufs;
+
+/* vendor specific pre-defined parameters */
+#define SLOW 1
+#define FAST 2
+
+#define RX_ADV_FINE_GRAN_SUP_EN 0x1
+#define RX_ADV_FINE_GRAN_STEP_VAL 0x3
+#define RX_ADV_MIN_ACTV_TIME_CAP 0x9
+
+#define PA_GRANULARITY_VAL 0x6
+#define PA_TACTIVATE_VAL 0x3
+#define PA_HIBERN8TIME_VAL 0x20
+
+#define PCLK_AVAIL_MIN 70000000
+#define PCLK_AVAIL_MAX 267000000
+
+struct exynos_ufs_uic_attr {
+ /* TX Attributes */
+ unsigned int tx_trailingclks;
+ unsigned int tx_dif_p_nsec;
+ unsigned int tx_dif_n_nsec;
+ unsigned int tx_high_z_cnt_nsec;
+ unsigned int tx_base_unit_nsec;
+ unsigned int tx_gran_unit_nsec;
+ unsigned int tx_sleep_cnt;
+ unsigned int tx_min_activatetime;
+ /* RX Attributes */
+ unsigned int rx_filler_enable;
+ unsigned int rx_dif_p_nsec;
+ unsigned int rx_hibern8_wait_nsec;
+ unsigned int rx_base_unit_nsec;
+ unsigned int rx_gran_unit_nsec;
+ unsigned int rx_sleep_cnt;
+ unsigned int rx_stall_cnt;
+ unsigned int rx_hs_g1_sync_len_cap;
+ unsigned int rx_hs_g2_sync_len_cap;
+ unsigned int rx_hs_g3_sync_len_cap;
+ unsigned int rx_hs_g1_prep_sync_len_cap;
+ unsigned int rx_hs_g2_prep_sync_len_cap;
+ unsigned int rx_hs_g3_prep_sync_len_cap;
+ /* Common Attributes */
+ unsigned int cmn_pwm_clk_ctrl;
+ /* Internal Attributes */
+ unsigned int pa_dbg_clk_period_off;
+ unsigned int pa_dbg_opt_suite1_val;
+ unsigned int pa_dbg_opt_suite1_off;
+ unsigned int pa_dbg_opt_suite2_val;
+ unsigned int pa_dbg_opt_suite2_off;
+ /* Changeable Attributes */
+ unsigned int rx_adv_fine_gran_sup_en;
+ unsigned int rx_adv_fine_gran_step;
+ unsigned int rx_min_actv_time_cap;
+ unsigned int rx_hibern8_time_cap;
+ unsigned int rx_adv_min_actv_time_cap;
+ unsigned int rx_adv_hibern8_time_cap;
+ unsigned int pa_granularity;
+ unsigned int pa_tactivate;
+ unsigned int pa_hibern8time;
+};
+
+struct exynos_ufs_drv_data {
+ const struct ufs_hba_variant_ops *vops;
+ struct exynos_ufs_uic_attr *uic_attr;
+ unsigned int quirks;
+ unsigned int opts;
+ u32 iocc_mask;
+ /* SoC's specific operations */
+ int (*drv_init)(struct exynos_ufs *ufs);
+ int (*pre_link)(struct exynos_ufs *ufs);
+ int (*post_link)(struct exynos_ufs *ufs);
+ int (*pre_pwr_change)(struct exynos_ufs *ufs,
+ struct ufs_pa_layer_attr *pwr);
+ int (*post_pwr_change)(struct exynos_ufs *ufs,
+ const struct ufs_pa_layer_attr *pwr);
+ int (*pre_hce_enable)(struct exynos_ufs *ufs);
+ int (*post_hce_enable)(struct exynos_ufs *ufs);
+ int (*suspend)(struct exynos_ufs *ufs);
+};
+
+struct ufs_phy_time_cfg {
+ u32 tx_linereset_p;
+ u32 tx_linereset_n;
+ u32 tx_high_z_cnt;
+ u32 tx_base_n_val;
+ u32 tx_gran_n_val;
+ u32 tx_sleep_cnt;
+ u32 rx_linereset;
+ u32 rx_hibern8_wait;
+ u32 rx_base_n_val;
+ u32 rx_gran_n_val;
+ u32 rx_sleep_cnt;
+ u32 rx_stall_cnt;
+};
+
+struct exynos_ufs {
+ struct ufs_hba *hba;
+ struct phy *phy;
+ void __iomem *reg_hci;
+ void __iomem *reg_unipro;
+ void __iomem *reg_ufsp;
+ struct clk *clk_hci_core;
+ struct clk *clk_unipro_main;
+ struct clk *clk_apb;
+ u32 pclk_rate;
+ u32 pclk_div;
+ u32 pclk_avail_min;
+ u32 pclk_avail_max;
+ unsigned long mclk_rate;
+ int avail_ln_rx;
+ int avail_ln_tx;
+ int rx_sel_idx;
+ struct ufs_pa_layer_attr dev_req_params;
+ struct ufs_phy_time_cfg t_cfg;
+ ktime_t entry_hibern8_t;
+ const struct exynos_ufs_drv_data *drv_data;
+ struct regmap *sysreg;
+ u32 iocc_offset;
+ u32 iocc_mask;
+ u32 iocc_val;
+
+ u32 opts;
+#define EXYNOS_UFS_OPT_HAS_APB_CLK_CTRL BIT(0)
+#define EXYNOS_UFS_OPT_SKIP_CONNECTION_ESTAB BIT(1)
+#define EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL BIT(2)
+#define EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX BIT(3)
+#define EXYNOS_UFS_OPT_USE_SW_HIBERN8_TIMER BIT(4)
+#define EXYNOS_UFS_OPT_SKIP_CONFIG_PHY_ATTR BIT(5)
+#define EXYNOS_UFS_OPT_UFSPR_SECURE BIT(6)
+#define EXYNOS_UFS_OPT_TIMER_TICK_SELECT BIT(7)
+};
+
+#define for_each_ufs_rx_lane(ufs, i) \
+ for (i = (ufs)->rx_sel_idx; \
+ i < (ufs)->rx_sel_idx + (ufs)->avail_ln_rx; i++)
+#define for_each_ufs_tx_lane(ufs, i) \
+ for (i = 0; i < (ufs)->avail_ln_tx; i++)
+
+#define EXYNOS_UFS_MMIO_FUNC(name) \
+static inline void name##_writel(struct exynos_ufs *ufs, u32 val, u32 reg)\
+{ \
+ writel(val, ufs->reg_##name + reg); \
+} \
+ \
+static inline u32 name##_readl(struct exynos_ufs *ufs, u32 reg) \
+{ \
+ return readl(ufs->reg_##name + reg); \
+}
+
+EXYNOS_UFS_MMIO_FUNC(hci);
+EXYNOS_UFS_MMIO_FUNC(unipro);
+EXYNOS_UFS_MMIO_FUNC(ufsp);
+#undef EXYNOS_UFS_MMIO_FUNC
+
+long exynos_ufs_calc_time_cntr(struct exynos_ufs *, long);
+
+static inline void exynos_ufs_enable_ov_tm(struct ufs_hba *hba)
+{
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OV_TM), true);
+}
+
+static inline void exynos_ufs_disable_ov_tm(struct ufs_hba *hba)
+{
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_OV_TM), false);
+}
+
+static inline void exynos_ufs_enable_dbg_mode(struct ufs_hba *hba)
+{
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_MODE), true);
+}
+
+static inline void exynos_ufs_disable_dbg_mode(struct ufs_hba *hba)
+{
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_DBG_MODE), false);
+}
+
+#endif /* _UFS_EXYNOS_H_ */
diff --git a/drivers/ufs/host/ufs-hisi.c b/drivers/ufs/host/ufs-hisi.c
new file mode 100644
index 000000000..993e20ac2
--- /dev/null
+++ b/drivers/ufs/host/ufs-hisi.c
@@ -0,0 +1,605 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HiSilicon Hixxxx UFS Driver
+ *
+ * Copyright (c) 2016-2017 Linaro Ltd.
+ * Copyright (c) 2016-2017 HiSilicon Technologies Co., Ltd.
+ */
+
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <ufs/ufshcd.h>
+#include "ufshcd-pltfrm.h"
+#include <ufs/unipro.h>
+#include "ufs-hisi.h"
+#include <ufs/ufshci.h>
+#include <ufs/ufs_quirks.h>
+
+static int ufs_hisi_check_hibern8(struct ufs_hba *hba)
+{
+ int err = 0;
+ u32 tx_fsm_val_0 = 0;
+ u32 tx_fsm_val_1 = 0;
+ 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, 0),
+ &tx_fsm_val_0);
+ err |= ufshcd_dme_get(hba,
+ UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, 1), &tx_fsm_val_1);
+ if (err || (tx_fsm_val_0 == TX_FSM_HIBERN8 &&
+ tx_fsm_val_1 == 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, 0),
+ &tx_fsm_val_0);
+ err |= ufshcd_dme_get(hba,
+ UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, 1), &tx_fsm_val_1);
+ }
+
+ if (err) {
+ dev_err(hba->dev, "%s: unable to get TX_FSM_STATE, err %d\n",
+ __func__, err);
+ } else if (tx_fsm_val_0 != TX_FSM_HIBERN8 ||
+ tx_fsm_val_1 != TX_FSM_HIBERN8) {
+ err = -1;
+ dev_err(hba->dev, "%s: invalid TX_FSM_STATE, lane0 = %d, lane1 = %d\n",
+ __func__, tx_fsm_val_0, tx_fsm_val_1);
+ }
+
+ return err;
+}
+
+static void ufs_hisi_clk_init(struct ufs_hba *hba)
+{
+ struct ufs_hisi_host *host = ufshcd_get_variant(hba);
+
+ ufs_sys_ctrl_clr_bits(host, BIT_SYSCTRL_REF_CLOCK_EN, PHY_CLK_CTRL);
+ if (ufs_sys_ctrl_readl(host, PHY_CLK_CTRL) & BIT_SYSCTRL_REF_CLOCK_EN)
+ mdelay(1);
+ /* use abb clk */
+ ufs_sys_ctrl_clr_bits(host, BIT_UFS_REFCLK_SRC_SEl, UFS_SYSCTRL);
+ ufs_sys_ctrl_clr_bits(host, BIT_UFS_REFCLK_ISO_EN, PHY_ISO_EN);
+ /* open mphy ref clk */
+ ufs_sys_ctrl_set_bits(host, BIT_SYSCTRL_REF_CLOCK_EN, PHY_CLK_CTRL);
+}
+
+static void ufs_hisi_soc_init(struct ufs_hba *hba)
+{
+ struct ufs_hisi_host *host = ufshcd_get_variant(hba);
+ u32 reg;
+
+ if (!IS_ERR(host->rst))
+ reset_control_assert(host->rst);
+
+ /* HC_PSW powerup */
+ ufs_sys_ctrl_set_bits(host, BIT_UFS_PSW_MTCMOS_EN, PSW_POWER_CTRL);
+ udelay(10);
+ /* notify PWR ready */
+ ufs_sys_ctrl_set_bits(host, BIT_SYSCTRL_PWR_READY, HC_LP_CTRL);
+ ufs_sys_ctrl_writel(host, MASK_UFS_DEVICE_RESET | 0,
+ UFS_DEVICE_RESET_CTRL);
+
+ reg = ufs_sys_ctrl_readl(host, PHY_CLK_CTRL);
+ reg = (reg & ~MASK_SYSCTRL_CFG_CLOCK_FREQ) | UFS_FREQ_CFG_CLK;
+ /* set cfg clk freq */
+ ufs_sys_ctrl_writel(host, reg, PHY_CLK_CTRL);
+ /* set ref clk freq */
+ ufs_sys_ctrl_clr_bits(host, MASK_SYSCTRL_REF_CLOCK_SEL, PHY_CLK_CTRL);
+ /* bypass ufs clk gate */
+ ufs_sys_ctrl_set_bits(host, MASK_UFS_CLK_GATE_BYPASS,
+ CLOCK_GATE_BYPASS);
+ ufs_sys_ctrl_set_bits(host, MASK_UFS_SYSCRTL_BYPASS, UFS_SYSCTRL);
+
+ /* open psw clk */
+ ufs_sys_ctrl_set_bits(host, BIT_SYSCTRL_PSW_CLK_EN, PSW_CLK_CTRL);
+ /* disable ufshc iso */
+ ufs_sys_ctrl_clr_bits(host, BIT_UFS_PSW_ISO_CTRL, PSW_POWER_CTRL);
+ /* disable phy iso */
+ ufs_sys_ctrl_clr_bits(host, BIT_UFS_PHY_ISO_CTRL, PHY_ISO_EN);
+ /* notice iso disable */
+ ufs_sys_ctrl_clr_bits(host, BIT_SYSCTRL_LP_ISOL_EN, HC_LP_CTRL);
+
+ /* disable lp_reset_n */
+ ufs_sys_ctrl_set_bits(host, BIT_SYSCTRL_LP_RESET_N, RESET_CTRL_EN);
+ mdelay(1);
+
+ ufs_sys_ctrl_writel(host, MASK_UFS_DEVICE_RESET | BIT_UFS_DEVICE_RESET,
+ UFS_DEVICE_RESET_CTRL);
+
+ msleep(20);
+
+ /*
+ * enable the fix of linereset recovery,
+ * and enable rx_reset/tx_rest beat
+ * enable ref_clk_en override(bit5) &
+ * override value = 1(bit4), with mask
+ */
+ ufs_sys_ctrl_writel(host, 0x03300330, UFS_DEVICE_RESET_CTRL);
+
+ if (!IS_ERR(host->rst))
+ reset_control_deassert(host->rst);
+}
+
+static int ufs_hisi_link_startup_pre_change(struct ufs_hba *hba)
+{
+ struct ufs_hisi_host *host = ufshcd_get_variant(hba);
+ int err;
+ uint32_t value;
+ uint32_t reg;
+
+ /* Unipro VS_mphy_disable */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD0C1, 0x0), 0x1);
+ /* PA_HSSeries */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x156A, 0x0), 0x2);
+ /* MPHY CBRATESEL */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8114, 0x0), 0x1);
+ /* MPHY CBOVRCTRL2 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8121, 0x0), 0x2D);
+ /* MPHY CBOVRCTRL3 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8122, 0x0), 0x1);
+
+ if (host->caps & UFS_HISI_CAP_PHY10nm) {
+ /* MPHY CBOVRCTRL4 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8127, 0x0), 0x98);
+ /* MPHY CBOVRCTRL5 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8128, 0x0), 0x1);
+ }
+
+ /* Unipro VS_MphyCfgUpdt */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD085, 0x0), 0x1);
+ /* MPHY RXOVRCTRL4 rx0 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x800D, 0x4), 0x58);
+ /* MPHY RXOVRCTRL4 rx1 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x800D, 0x5), 0x58);
+ /* MPHY RXOVRCTRL5 rx0 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x800E, 0x4), 0xB);
+ /* MPHY RXOVRCTRL5 rx1 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x800E, 0x5), 0xB);
+ /* MPHY RXSQCONTROL rx0 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8009, 0x4), 0x1);
+ /* MPHY RXSQCONTROL rx1 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8009, 0x5), 0x1);
+ /* Unipro VS_MphyCfgUpdt */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD085, 0x0), 0x1);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x8113, 0x0), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD085, 0x0), 0x1);
+
+ if (host->caps & UFS_HISI_CAP_PHY10nm) {
+ /* RX_Hibern8Time_Capability*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0092, 0x4), 0xA);
+ /* RX_Hibern8Time_Capability*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0092, 0x5), 0xA);
+ /* RX_Min_ActivateTime */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008f, 0x4), 0xA);
+ /* RX_Min_ActivateTime*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008f, 0x5), 0xA);
+ } else {
+ /* Tactive RX */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008F, 0x4), 0x7);
+ /* Tactive RX */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008F, 0x5), 0x7);
+ }
+
+ /* Gear3 Synclength */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0095, 0x4), 0x4F);
+ /* Gear3 Synclength */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0095, 0x5), 0x4F);
+ /* Gear2 Synclength */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0094, 0x4), 0x4F);
+ /* Gear2 Synclength */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0094, 0x5), 0x4F);
+ /* Gear1 Synclength */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008B, 0x4), 0x4F);
+ /* Gear1 Synclength */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008B, 0x5), 0x4F);
+ /* Thibernate Tx */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x000F, 0x0), 0x5);
+ /* Thibernate Tx */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x000F, 0x1), 0x5);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD085, 0x0), 0x1);
+ /* Unipro VS_mphy_disable */
+ ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(0xD0C1, 0x0), &value);
+ if (value != 0x1)
+ dev_info(hba->dev,
+ "Warring!!! Unipro VS_mphy_disable is 0x%x\n", value);
+
+ /* Unipro VS_mphy_disable */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD0C1, 0x0), 0x0);
+ err = ufs_hisi_check_hibern8(hba);
+ if (err)
+ dev_err(hba->dev, "ufs_hisi_check_hibern8 error\n");
+
+ if (!(host->caps & UFS_HISI_CAP_PHY10nm))
+ ufshcd_writel(hba, UFS_HCLKDIV_NORMAL_VALUE, UFS_REG_HCLKDIV);
+
+ /* disable auto H8 */
+ reg = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER);
+ reg = reg & (~UFS_AHIT_AH8ITV_MASK);
+ ufshcd_writel(hba, reg, REG_AUTO_HIBERNATE_IDLE_TIMER);
+
+ /* Unipro PA_Local_TX_LCC_Enable */
+ ufshcd_disable_host_tx_lcc(hba);
+ /* close Unipro VS_Mk2ExtnSupport */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD0AB, 0x0), 0x0);
+ ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(0xD0AB, 0x0), &value);
+ if (value != 0) {
+ /* Ensure close success */
+ dev_info(hba->dev, "WARN: close VS_Mk2ExtnSupport failed\n");
+ }
+
+ return err;
+}
+
+static int ufs_hisi_link_startup_post_change(struct ufs_hba *hba)
+{
+ struct ufs_hisi_host *host = ufshcd_get_variant(hba);
+
+ /* Unipro DL_AFC0CreditThreshold */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x2044), 0x0);
+ /* Unipro DL_TC0OutAckThreshold */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x2045), 0x0);
+ /* Unipro DL_TC0TXFCThreshold */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x2040), 0x9);
+
+ /* not bypass ufs clk gate */
+ ufs_sys_ctrl_clr_bits(host, MASK_UFS_CLK_GATE_BYPASS,
+ CLOCK_GATE_BYPASS);
+ ufs_sys_ctrl_clr_bits(host, MASK_UFS_SYSCRTL_BYPASS,
+ UFS_SYSCTRL);
+
+ /* select received symbol cnt */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xd09a), 0x80000000);
+ /* reset counter0 and enable */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xd09c), 0x00000005);
+
+ return 0;
+}
+
+static int ufs_hisi_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ int err = 0;
+
+ switch (status) {
+ case PRE_CHANGE:
+ err = ufs_hisi_link_startup_pre_change(hba);
+ break;
+ case POST_CHANGE:
+ err = ufs_hisi_link_startup_post_change(hba);
+ break;
+ default:
+ break;
+ }
+
+ return err;
+}
+
+static void ufs_hisi_set_dev_cap(struct ufs_host_params *host_params)
+{
+ ufshcd_init_host_params(host_params);
+}
+
+static int ufs_hisi_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_host_params host_params;
+
+ ufs_hisi_set_dev_cap(&host_params);
+
+ return ufshcd_negotiate_pwr_params(&host_params, dev_max_params, dev_req_params);
+}
+
+static void ufs_hisi_pwr_change_pre_change(struct ufs_hba *hba)
+{
+ struct ufs_hisi_host *host = ufshcd_get_variant(hba);
+
+ if (host->caps & UFS_HISI_CAP_PHY10nm) {
+ /*
+ * Boston platform need to set SaveConfigTime to 0x13,
+ * and change sync length to maximum value
+ */
+ /* VS_DebugSaveConfigTime */
+ ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0xD0A0), 0x13);
+ /* g1 sync length */
+ ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0x1552), 0x4f);
+ /* g2 sync length */
+ ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0x1554), 0x4f);
+ /* g3 sync length */
+ ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0x1556), 0x4f);
+ /* PA_Hibern8Time */
+ ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0x15a7), 0xA);
+ /* PA_Tactivate */
+ ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0x15a8), 0xA);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xd085, 0x0), 0x01);
+ }
+
+ if (hba->dev_quirks & UFS_DEVICE_QUIRK_HOST_VS_DEBUGSAVECONFIGTIME) {
+ pr_info("ufs flash device must set VS_DebugSaveConfigTime 0x10\n");
+ /* VS_DebugSaveConfigTime */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xD0A0), 0x10);
+ /* sync length */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x1556), 0x48);
+ }
+
+ /* update */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15A8), 0x1);
+ /* PA_TxSkip */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x155c), 0x0);
+ /*PA_PWRModeUserData0 = 8191, default is 0*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15b0), SZ_8K - 1);
+ /*PA_PWRModeUserData1 = 65535, default is 0*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15b1), SZ_64K - 1);
+ /*PA_PWRModeUserData2 = 32767, default is 0*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15b2), SZ_32K - 1);
+ /*DME_FC0ProtectionTimeOutVal = 8191, default is 0*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xd041), SZ_8K - 1);
+ /*DME_TC0ReplayTimeOutVal = 65535, default is 0*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xd042), SZ_64K - 1);
+ /*DME_AFC0ReqTimeOutVal = 32767, default is 0*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xd043), SZ_32K - 1);
+ /*PA_PWRModeUserData3 = 8191, default is 0*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15b3), SZ_8K - 1);
+ /*PA_PWRModeUserData4 = 65535, default is 0*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15b4), SZ_64K - 1);
+ /*PA_PWRModeUserData5 = 32767, default is 0*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15b5), SZ_32K - 1);
+ /*DME_FC1ProtectionTimeOutVal = 8191, default is 0*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xd044), SZ_8K - 1);
+ /*DME_TC1ReplayTimeOutVal = 65535, default is 0*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xd045), SZ_64K - 1);
+ /*DME_AFC1ReqTimeOutVal = 32767, default is 0*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xd046), SZ_32K - 1);
+}
+
+static int ufs_hisi_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ int ret = 0;
+
+ if (!dev_req_params) {
+ dev_err(hba->dev,
+ "%s: incoming dev_req_params is NULL\n", __func__);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ switch (status) {
+ case PRE_CHANGE:
+ ufs_hisi_pwr_change_pre_change(hba);
+ break;
+ case POST_CHANGE:
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+out:
+ return ret;
+}
+
+static int ufs_hisi_suspend_prepare(struct device *dev)
+{
+ /* RPM and SPM are different. Refer ufs_hisi_suspend() */
+ return __ufshcd_suspend_prepare(dev, false);
+}
+
+static int ufs_hisi_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op,
+ enum ufs_notify_change_status status)
+{
+ struct ufs_hisi_host *host = ufshcd_get_variant(hba);
+
+ if (status == PRE_CHANGE)
+ return 0;
+
+ if (pm_op == UFS_RUNTIME_PM)
+ return 0;
+
+ if (host->in_suspend) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ ufs_sys_ctrl_clr_bits(host, BIT_SYSCTRL_REF_CLOCK_EN, PHY_CLK_CTRL);
+ udelay(10);
+ /* set ref_dig_clk override of PHY PCS to 0 */
+ ufs_sys_ctrl_writel(host, 0x00100000, UFS_DEVICE_RESET_CTRL);
+
+ host->in_suspend = true;
+
+ return 0;
+}
+
+static int ufs_hisi_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+ struct ufs_hisi_host *host = ufshcd_get_variant(hba);
+
+ if (!host->in_suspend)
+ return 0;
+
+ /* set ref_dig_clk override of PHY PCS to 1 */
+ ufs_sys_ctrl_writel(host, 0x00100010, UFS_DEVICE_RESET_CTRL);
+ udelay(10);
+ ufs_sys_ctrl_set_bits(host, BIT_SYSCTRL_REF_CLOCK_EN, PHY_CLK_CTRL);
+
+ host->in_suspend = false;
+ return 0;
+}
+
+static int ufs_hisi_get_resource(struct ufs_hisi_host *host)
+{
+ struct device *dev = host->hba->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+
+ /* get resource of ufs sys ctrl */
+ host->ufs_sys_ctrl = devm_platform_ioremap_resource(pdev, 1);
+ return PTR_ERR_OR_ZERO(host->ufs_sys_ctrl);
+}
+
+static void ufs_hisi_set_pm_lvl(struct ufs_hba *hba)
+{
+ hba->rpm_lvl = UFS_PM_LVL_1;
+ hba->spm_lvl = UFS_PM_LVL_3;
+}
+
+/**
+ * ufs_hisi_init_common
+ * @hba: host controller instance
+ */
+static int ufs_hisi_init_common(struct ufs_hba *hba)
+{
+ int err = 0;
+ struct device *dev = hba->dev;
+ struct ufs_hisi_host *host;
+
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ host->hba = hba;
+ ufshcd_set_variant(hba, host);
+
+ host->rst = devm_reset_control_get(dev, "rst");
+ if (IS_ERR(host->rst)) {
+ dev_err(dev, "%s: failed to get reset control\n", __func__);
+ err = PTR_ERR(host->rst);
+ goto error;
+ }
+
+ ufs_hisi_set_pm_lvl(hba);
+
+ err = ufs_hisi_get_resource(host);
+ if (err)
+ goto error;
+
+ return 0;
+
+error:
+ ufshcd_set_variant(hba, NULL);
+ return err;
+}
+
+static int ufs_hi3660_init(struct ufs_hba *hba)
+{
+ int ret = 0;
+ struct device *dev = hba->dev;
+
+ ret = ufs_hisi_init_common(hba);
+ if (ret) {
+ dev_err(dev, "%s: ufs common init fail\n", __func__);
+ return ret;
+ }
+
+ ufs_hisi_clk_init(hba);
+
+ ufs_hisi_soc_init(hba);
+
+ return 0;
+}
+
+static int ufs_hi3670_init(struct ufs_hba *hba)
+{
+ int ret = 0;
+ struct device *dev = hba->dev;
+ struct ufs_hisi_host *host;
+
+ ret = ufs_hisi_init_common(hba);
+ if (ret) {
+ dev_err(dev, "%s: ufs common init fail\n", __func__);
+ return ret;
+ }
+
+ ufs_hisi_clk_init(hba);
+
+ ufs_hisi_soc_init(hba);
+
+ /* Add cap for 10nm PHY variant on HI3670 SoC */
+ host = ufshcd_get_variant(hba);
+ host->caps |= UFS_HISI_CAP_PHY10nm;
+
+ return 0;
+}
+
+static const struct ufs_hba_variant_ops ufs_hba_hi3660_vops = {
+ .name = "hi3660",
+ .init = ufs_hi3660_init,
+ .link_startup_notify = ufs_hisi_link_startup_notify,
+ .negotiate_pwr_mode = ufs_hisi_negotiate_pwr_mode,
+ .pwr_change_notify = ufs_hisi_pwr_change_notify,
+ .suspend = ufs_hisi_suspend,
+ .resume = ufs_hisi_resume,
+};
+
+static const struct ufs_hba_variant_ops ufs_hba_hi3670_vops = {
+ .name = "hi3670",
+ .init = ufs_hi3670_init,
+ .link_startup_notify = ufs_hisi_link_startup_notify,
+ .negotiate_pwr_mode = ufs_hisi_negotiate_pwr_mode,
+ .pwr_change_notify = ufs_hisi_pwr_change_notify,
+ .suspend = ufs_hisi_suspend,
+ .resume = ufs_hisi_resume,
+};
+
+static const struct of_device_id ufs_hisi_of_match[] = {
+ { .compatible = "hisilicon,hi3660-ufs", .data = &ufs_hba_hi3660_vops },
+ { .compatible = "hisilicon,hi3670-ufs", .data = &ufs_hba_hi3670_vops },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, ufs_hisi_of_match);
+
+static int ufs_hisi_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *of_id;
+
+ of_id = of_match_node(ufs_hisi_of_match, pdev->dev.of_node);
+
+ return ufshcd_pltfrm_init(pdev, of_id->data);
+}
+
+static void ufs_hisi_remove(struct platform_device *pdev)
+{
+ ufshcd_pltfrm_remove(pdev);
+}
+
+static const struct dev_pm_ops ufs_hisi_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
+ SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
+ .prepare = ufs_hisi_suspend_prepare,
+ .complete = ufshcd_resume_complete,
+};
+
+static struct platform_driver ufs_hisi_pltform = {
+ .probe = ufs_hisi_probe,
+ .remove = ufs_hisi_remove,
+ .driver = {
+ .name = "ufshcd-hisi",
+ .pm = &ufs_hisi_pm_ops,
+ .of_match_table = ufs_hisi_of_match,
+ },
+};
+module_platform_driver(ufs_hisi_pltform);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ufshcd-hisi");
+MODULE_DESCRIPTION("HiSilicon Hixxxx UFS Driver");
diff --git a/drivers/ufs/host/ufs-hisi.h b/drivers/ufs/host/ufs-hisi.h
new file mode 100644
index 000000000..5a90c0f4e
--- /dev/null
+++ b/drivers/ufs/host/ufs-hisi.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017, HiSilicon. All rights reserved.
+ */
+
+#ifndef UFS_HISI_H_
+#define UFS_HISI_H_
+
+#define HBRN8_POLL_TOUT_MS 1000
+
+/*
+ * ufs sysctrl specific define
+ */
+#define PSW_POWER_CTRL (0x04)
+#define PHY_ISO_EN (0x08)
+#define HC_LP_CTRL (0x0C)
+#define PHY_CLK_CTRL (0x10)
+#define PSW_CLK_CTRL (0x14)
+#define CLOCK_GATE_BYPASS (0x18)
+#define RESET_CTRL_EN (0x1C)
+#define UFS_SYSCTRL (0x5C)
+#define UFS_DEVICE_RESET_CTRL (0x60)
+
+#define BIT_UFS_PSW_ISO_CTRL (1 << 16)
+#define BIT_UFS_PSW_MTCMOS_EN (1 << 0)
+#define BIT_UFS_REFCLK_ISO_EN (1 << 16)
+#define BIT_UFS_PHY_ISO_CTRL (1 << 0)
+#define BIT_SYSCTRL_LP_ISOL_EN (1 << 16)
+#define BIT_SYSCTRL_PWR_READY (1 << 8)
+#define BIT_SYSCTRL_REF_CLOCK_EN (1 << 24)
+#define MASK_SYSCTRL_REF_CLOCK_SEL (0x3 << 8)
+#define MASK_SYSCTRL_CFG_CLOCK_FREQ (0xFF)
+#define UFS_FREQ_CFG_CLK (0x39)
+#define BIT_SYSCTRL_PSW_CLK_EN (1 << 4)
+#define MASK_UFS_CLK_GATE_BYPASS (0x3F)
+#define BIT_SYSCTRL_LP_RESET_N (1 << 0)
+#define BIT_UFS_REFCLK_SRC_SEl (1 << 0)
+#define MASK_UFS_SYSCRTL_BYPASS (0x3F << 16)
+#define MASK_UFS_DEVICE_RESET (0x1 << 16)
+#define BIT_UFS_DEVICE_RESET (0x1)
+
+/*
+ * M-TX Configuration Attributes for Hixxxx
+ */
+#define MPHY_TX_FSM_STATE 0x41
+#define TX_FSM_HIBERN8 0x1
+
+/*
+ * Hixxxx UFS HC specific Registers
+ */
+enum {
+ UFS_REG_OCPTHRTL = 0xc0,
+ UFS_REG_OOCPR = 0xc4,
+
+ UFS_REG_CDACFG = 0xd0,
+ UFS_REG_CDATX1 = 0xd4,
+ UFS_REG_CDATX2 = 0xd8,
+ UFS_REG_CDARX1 = 0xdc,
+ UFS_REG_CDARX2 = 0xe0,
+ UFS_REG_CDASTA = 0xe4,
+
+ UFS_REG_LBMCFG = 0xf0,
+ UFS_REG_LBMSTA = 0xf4,
+ UFS_REG_UFSMODE = 0xf8,
+
+ UFS_REG_HCLKDIV = 0xfc,
+};
+
+/* AHIT - Auto-Hibernate Idle Timer */
+#define UFS_AHIT_AH8ITV_MASK 0x3FF
+
+/* REG UFS_REG_OCPTHRTL definition */
+#define UFS_HCLKDIV_NORMAL_VALUE 0xE4
+
+/* vendor specific pre-defined parameters */
+#define SLOW 1
+#define FAST 2
+
+#define UFS_HISI_CAP_RESERVED BIT(0)
+#define UFS_HISI_CAP_PHY10nm BIT(1)
+
+struct ufs_hisi_host {
+ struct ufs_hba *hba;
+ void __iomem *ufs_sys_ctrl;
+
+ struct reset_control *rst;
+
+ uint64_t caps;
+
+ bool in_suspend;
+};
+
+#define ufs_sys_ctrl_writel(host, val, reg) \
+ writel((val), (host)->ufs_sys_ctrl + (reg))
+#define ufs_sys_ctrl_readl(host, reg) readl((host)->ufs_sys_ctrl + (reg))
+#define ufs_sys_ctrl_set_bits(host, mask, reg) \
+ ufs_sys_ctrl_writel( \
+ (host), ((mask) | (ufs_sys_ctrl_readl((host), (reg)))), (reg))
+#define ufs_sys_ctrl_clr_bits(host, mask, reg) \
+ ufs_sys_ctrl_writel((host), \
+ ((~(mask)) & (ufs_sys_ctrl_readl((host), (reg)))), \
+ (reg))
+
+#endif /* UFS_HISI_H_ */
diff --git a/drivers/ufs/host/ufs-mediatek-sip.h b/drivers/ufs/host/ufs-mediatek-sip.h
new file mode 100644
index 000000000..7d17aedf6
--- /dev/null
+++ b/drivers/ufs/host/ufs-mediatek-sip.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 MediaTek Inc.
+ */
+
+#ifndef _UFS_MEDIATEK_SIP_H
+#define _UFS_MEDIATEK_SIP_H
+
+#include <linux/soc/mediatek/mtk_sip_svc.h>
+
+/*
+ * SiP (Slicon Partner) commands
+ */
+#define MTK_SIP_UFS_CONTROL MTK_SIP_SMC_CMD(0x276)
+#define UFS_MTK_SIP_VA09_PWR_CTRL BIT(0)
+#define UFS_MTK_SIP_DEVICE_RESET BIT(1)
+#define UFS_MTK_SIP_CRYPTO_CTRL BIT(2)
+#define UFS_MTK_SIP_REF_CLK_NOTIFICATION BIT(3)
+#define UFS_MTK_SIP_SRAM_PWR_CTRL BIT(5)
+#define UFS_MTK_SIP_GET_VCC_NUM BIT(6)
+#define UFS_MTK_SIP_DEVICE_PWR_CTRL BIT(7)
+#define UFS_MTK_SIP_MPHY_CTRL BIT(8)
+#define UFS_MTK_SIP_MTCMOS_CTRL BIT(9)
+
+/*
+ * Multi-VCC by Numbering
+ */
+enum ufs_mtk_vcc_num {
+ UFS_VCC_NONE = 0,
+ UFS_VCC_1,
+ UFS_VCC_2,
+ UFS_VCC_MAX
+};
+
+enum ufs_mtk_mphy_op {
+ UFS_MPHY_BACKUP = 0,
+ UFS_MPHY_RESTORE
+};
+
+/*
+ * SMC call wrapper function
+ */
+struct ufs_mtk_smc_arg {
+ unsigned long cmd;
+ struct arm_smccc_res *res;
+ unsigned long v1;
+ unsigned long v2;
+ unsigned long v3;
+ unsigned long v4;
+ unsigned long v5;
+ unsigned long v6;
+ unsigned long v7;
+};
+
+
+static inline void _ufs_mtk_smc(struct ufs_mtk_smc_arg s)
+{
+ arm_smccc_smc(MTK_SIP_UFS_CONTROL,
+ s.cmd,
+ s.v1, s.v2, s.v3, s.v4, s.v5, s.v6, s.res);
+}
+
+#define ufs_mtk_smc(...) \
+ _ufs_mtk_smc((struct ufs_mtk_smc_arg) {__VA_ARGS__})
+
+/* Sip kernel interface */
+#define ufs_mtk_va09_pwr_ctrl(res, on) \
+ ufs_mtk_smc(UFS_MTK_SIP_VA09_PWR_CTRL, &(res), on)
+
+#define ufs_mtk_crypto_ctrl(res, enable) \
+ ufs_mtk_smc(UFS_MTK_SIP_CRYPTO_CTRL, &(res), enable)
+
+#define ufs_mtk_ref_clk_notify(on, stage, res) \
+ ufs_mtk_smc(UFS_MTK_SIP_REF_CLK_NOTIFICATION, &(res), on, stage)
+
+#define ufs_mtk_device_reset_ctrl(high, res) \
+ ufs_mtk_smc(UFS_MTK_SIP_DEVICE_RESET, &(res), high)
+
+#define ufs_mtk_sram_pwr_ctrl(on, res) \
+ ufs_mtk_smc(UFS_MTK_SIP_SRAM_PWR_CTRL, &(res), on)
+
+#define ufs_mtk_get_vcc_num(res) \
+ ufs_mtk_smc(UFS_MTK_SIP_GET_VCC_NUM, &(res))
+
+#define ufs_mtk_device_pwr_ctrl(on, ufs_version, res) \
+ ufs_mtk_smc(UFS_MTK_SIP_DEVICE_PWR_CTRL, &(res), on, ufs_version)
+
+#define ufs_mtk_mphy_ctrl(op, res) \
+ ufs_mtk_smc(UFS_MTK_SIP_MPHY_CTRL, &(res), op)
+
+#define ufs_mtk_mtcmos_ctrl(op, res) \
+ ufs_mtk_smc(UFS_MTK_SIP_MTCMOS_CTRL, &(res), op)
+
+#endif /* !_UFS_MEDIATEK_SIP_H */
diff --git a/drivers/ufs/host/ufs-mediatek-trace.h b/drivers/ufs/host/ufs-mediatek-trace.h
new file mode 100644
index 000000000..0df8ac843
--- /dev/null
+++ b/drivers/ufs/host/ufs-mediatek-trace.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 MediaTek Inc.
+ */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM ufs_mtk
+
+#if !defined(_TRACE_EVENT_UFS_MEDIATEK_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_EVENT_UFS_MEDIATEK_H
+
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(ufs_mtk_event,
+ TP_PROTO(unsigned int type, unsigned int data),
+ TP_ARGS(type, data),
+
+ TP_STRUCT__entry(
+ __field(unsigned int, type)
+ __field(unsigned int, data)
+ ),
+
+ TP_fast_assign(
+ __entry->type = type;
+ __entry->data = data;
+ ),
+
+ TP_printk("ufs: event=%u data=%u",
+ __entry->type, __entry->data)
+);
+
+TRACE_EVENT(ufs_mtk_clk_scale,
+ TP_PROTO(const char *name, bool scale_up, unsigned long clk_rate),
+ TP_ARGS(name, scale_up, clk_rate),
+
+ TP_STRUCT__entry(
+ __string(name, name)
+ __field(bool, scale_up)
+ __field(unsigned long, clk_rate)
+ ),
+
+ TP_fast_assign(
+ __assign_str(name);
+ __entry->scale_up = scale_up;
+ __entry->clk_rate = clk_rate;
+ ),
+
+ TP_printk("ufs: clk (%s) scaled %s @ %lu",
+ __get_str(name),
+ __entry->scale_up ? "up" : "down",
+ __entry->clk_rate)
+);
+
+#endif
+
+#undef TRACE_INCLUDE_PATH
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_PATH ../../drivers/ufs/host
+#define TRACE_INCLUDE_FILE ufs-mediatek-trace
+#include <trace/define_trace.h>
diff --git a/drivers/ufs/host/ufs-mediatek.c b/drivers/ufs/host/ufs-mediatek.c
new file mode 100644
index 000000000..814c1b734
--- /dev/null
+++ b/drivers/ufs/host/ufs-mediatek.c
@@ -0,0 +1,2567 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 MediaTek Inc.
+ * Authors:
+ * Stanley Chu <stanley.chu@mediatek.com>
+ * Peter Wang <peter.wang@mediatek.com>
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+
+#include <ufs/ufshcd.h>
+#include "ufshcd-pltfrm.h"
+#include <ufs/ufs_quirks.h>
+#include <ufs/unipro.h>
+
+#include "ufs-mediatek.h"
+#include "ufs-mediatek-sip.h"
+
+static int ufs_mtk_config_mcq(struct ufs_hba *hba, bool irq);
+static void _ufs_mtk_clk_scale(struct ufs_hba *hba, bool scale_up);
+
+#define CREATE_TRACE_POINTS
+#include "ufs-mediatek-trace.h"
+#undef CREATE_TRACE_POINTS
+
+#define MAX_SUPP_MAC 64
+#define MCQ_QUEUE_OFFSET(c) ((((c) >> 16) & 0xFF) * 0x200)
+
+static const struct ufs_dev_quirk ufs_mtk_dev_fixups[] = {
+ { .wmanufacturerid = UFS_ANY_VENDOR,
+ .model = UFS_ANY_MODEL,
+ .quirk = UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM },
+ { .wmanufacturerid = UFS_VENDOR_SKHYNIX,
+ .model = "H9HQ21AFAMZDAR",
+ .quirk = UFS_DEVICE_QUIRK_SUPPORT_EXTENDED_FEATURES },
+ {}
+};
+
+static const struct of_device_id ufs_mtk_of_match[] = {
+ { .compatible = "mediatek,mt8183-ufshci" },
+ { .compatible = "mediatek,mt8195-ufshci" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ufs_mtk_of_match);
+
+/*
+ * Details of UIC Errors
+ */
+static const char *const ufs_uic_err_str[] = {
+ "PHY Adapter Layer",
+ "Data Link Layer",
+ "Network Link Layer",
+ "Transport Link Layer",
+ "DME"
+};
+
+static const char *const ufs_uic_pa_err_str[] = {
+ "PHY error on Lane 0",
+ "PHY error on Lane 1",
+ "PHY error on Lane 2",
+ "PHY error on Lane 3",
+ "Generic PHY Adapter Error. This should be the LINERESET indication"
+};
+
+static const char *const ufs_uic_dl_err_str[] = {
+ "NAC_RECEIVED",
+ "TCx_REPLAY_TIMER_EXPIRED",
+ "AFCx_REQUEST_TIMER_EXPIRED",
+ "FCx_PROTECTION_TIMER_EXPIRED",
+ "CRC_ERROR",
+ "RX_BUFFER_OVERFLOW",
+ "MAX_FRAME_LENGTH_EXCEEDED",
+ "WRONG_SEQUENCE_NUMBER",
+ "AFC_FRAME_SYNTAX_ERROR",
+ "NAC_FRAME_SYNTAX_ERROR",
+ "EOF_SYNTAX_ERROR",
+ "FRAME_SYNTAX_ERROR",
+ "BAD_CTRL_SYMBOL_TYPE",
+ "PA_INIT_ERROR",
+ "PA_ERROR_IND_RECEIVED",
+ "PA_INIT"
+};
+
+static bool ufs_mtk_is_boost_crypt_enabled(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ return host->caps & UFS_MTK_CAP_BOOST_CRYPT_ENGINE;
+}
+
+static bool ufs_mtk_is_va09_supported(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ return host->caps & UFS_MTK_CAP_VA09_PWR_CTRL;
+}
+
+static bool ufs_mtk_is_broken_vcc(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ return host->caps & UFS_MTK_CAP_BROKEN_VCC;
+}
+
+static bool ufs_mtk_is_pmc_via_fastauto(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ return host->caps & UFS_MTK_CAP_PMC_VIA_FASTAUTO;
+}
+
+static bool ufs_mtk_is_tx_skew_fix(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ return host->caps & UFS_MTK_CAP_TX_SKEW_FIX;
+}
+
+static bool ufs_mtk_is_rtff_mtcmos(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ return host->caps & UFS_MTK_CAP_RTFF_MTCMOS;
+}
+
+static bool ufs_mtk_is_allow_vccqx_lpm(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ return host->caps & UFS_MTK_CAP_ALLOW_VCCQX_LPM;
+}
+
+static bool ufs_mtk_is_clk_scale_ready(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct ufs_mtk_clk *mclk = &host->mclk;
+
+ return mclk->ufs_sel_clki &&
+ mclk->ufs_sel_max_clki &&
+ mclk->ufs_sel_min_clki;
+}
+
+static void ufs_mtk_cfg_unipro_cg(struct ufs_hba *hba, bool enable)
+{
+ u32 tmp;
+
+ if (enable) {
+ ufshcd_dme_get(hba,
+ UIC_ARG_MIB(VS_SAVEPOWERCONTROL), &tmp);
+ tmp = tmp |
+ (1 << RX_SYMBOL_CLK_GATE_EN) |
+ (1 << SYS_CLK_GATE_EN) |
+ (1 << TX_CLK_GATE_EN);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(VS_SAVEPOWERCONTROL), tmp);
+
+ ufshcd_dme_get(hba,
+ UIC_ARG_MIB(VS_DEBUGCLOCKENABLE), &tmp);
+ tmp = tmp & ~(1 << TX_SYMBOL_CLK_REQ_FORCE);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(VS_DEBUGCLOCKENABLE), tmp);
+ } else {
+ ufshcd_dme_get(hba,
+ UIC_ARG_MIB(VS_SAVEPOWERCONTROL), &tmp);
+ tmp = tmp & ~((1 << RX_SYMBOL_CLK_GATE_EN) |
+ (1 << SYS_CLK_GATE_EN) |
+ (1 << TX_CLK_GATE_EN));
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(VS_SAVEPOWERCONTROL), tmp);
+
+ ufshcd_dme_get(hba,
+ UIC_ARG_MIB(VS_DEBUGCLOCKENABLE), &tmp);
+ tmp = tmp | (1 << TX_SYMBOL_CLK_REQ_FORCE);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB(VS_DEBUGCLOCKENABLE), tmp);
+ }
+}
+
+static void ufs_mtk_crypto_enable(struct ufs_hba *hba)
+{
+ struct arm_smccc_res res;
+
+ ufs_mtk_crypto_ctrl(res, 1);
+ if (res.a0) {
+ dev_info(hba->dev, "%s: crypto enable failed, err: %lu\n",
+ __func__, res.a0);
+ hba->caps &= ~UFSHCD_CAP_CRYPTO;
+ }
+}
+
+static void ufs_mtk_host_reset(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct arm_smccc_res res;
+
+ reset_control_assert(host->hci_reset);
+ reset_control_assert(host->crypto_reset);
+ reset_control_assert(host->unipro_reset);
+ reset_control_assert(host->mphy_reset);
+
+ usleep_range(100, 110);
+
+ reset_control_deassert(host->unipro_reset);
+ reset_control_deassert(host->crypto_reset);
+ reset_control_deassert(host->hci_reset);
+ reset_control_deassert(host->mphy_reset);
+
+ /* restore mphy setting aftre mphy reset */
+ if (host->mphy_reset)
+ ufs_mtk_mphy_ctrl(UFS_MPHY_RESTORE, res);
+}
+
+static void ufs_mtk_init_reset_control(struct ufs_hba *hba,
+ struct reset_control **rc,
+ char *str)
+{
+ *rc = devm_reset_control_get(hba->dev, str);
+ if (IS_ERR(*rc)) {
+ dev_info(hba->dev, "Failed to get reset control %s: %ld\n",
+ str, PTR_ERR(*rc));
+ *rc = NULL;
+ }
+}
+
+static void ufs_mtk_init_reset(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ ufs_mtk_init_reset_control(hba, &host->hci_reset,
+ "hci_rst");
+ ufs_mtk_init_reset_control(hba, &host->unipro_reset,
+ "unipro_rst");
+ ufs_mtk_init_reset_control(hba, &host->crypto_reset,
+ "crypto_rst");
+ ufs_mtk_init_reset_control(hba, &host->mphy_reset,
+ "mphy_rst");
+}
+
+static int ufs_mtk_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ if (status == PRE_CHANGE) {
+ if (host->unipro_lpm) {
+ hba->vps->hba_enable_delay_us = 0;
+ } else {
+ hba->vps->hba_enable_delay_us = 600;
+ ufs_mtk_host_reset(hba);
+ }
+
+ if (hba->caps & UFSHCD_CAP_CRYPTO)
+ ufs_mtk_crypto_enable(hba);
+
+ if (host->caps & UFS_MTK_CAP_DISABLE_AH8) {
+ ufshcd_writel(hba, 0,
+ REG_AUTO_HIBERNATE_IDLE_TIMER);
+ hba->capabilities &= ~MASK_AUTO_HIBERN8_SUPPORT;
+ hba->ahit = 0;
+ }
+
+ /*
+ * Turn on CLK_CG early to bypass abnormal ERR_CHK signal
+ * to prevent host hang issue
+ */
+ ufshcd_writel(hba,
+ ufshcd_readl(hba, REG_UFS_XOUFS_CTRL) | 0x80,
+ REG_UFS_XOUFS_CTRL);
+
+ if (host->legacy_ip_ver)
+ return 0;
+
+ /* DDR_EN setting */
+ if (host->ip_ver >= IP_VER_MT6989) {
+ ufshcd_rmwl(hba, UFS_MASK(0x7FFF, 8),
+ 0x453000, REG_UFS_MMIO_OPT_CTRL_0);
+ }
+
+ if (host->ip_ver >= IP_VER_MT6991_A0) {
+ /* Enable multi-rtt */
+ ufshcd_rmwl(hba, MRTT_EN, MRTT_EN, REG_UFS_MMIO_OPT_CTRL_0);
+ /* Enable random performance improvement */
+ ufshcd_rmwl(hba, RDN_PFM_IMPV_DIS, 0, REG_UFS_MMIO_OPT_CTRL_0);
+ }
+ }
+
+ return 0;
+}
+
+static int ufs_mtk_bind_mphy(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct device *dev = hba->dev;
+ struct device_node *np = dev->of_node;
+ int err = 0;
+
+ host->mphy = devm_of_phy_get_by_index(dev, np, 0);
+
+ if (host->mphy == ERR_PTR(-EPROBE_DEFER)) {
+ /*
+ * UFS driver might be probed before the phy driver does.
+ * In that case we would like to return EPROBE_DEFER code.
+ */
+ err = -EPROBE_DEFER;
+ dev_info(dev,
+ "%s: required phy hasn't probed yet. err = %d\n",
+ __func__, err);
+ } else if (IS_ERR(host->mphy)) {
+ err = PTR_ERR(host->mphy);
+ if (err != -ENODEV) {
+ dev_info(dev, "%s: PHY get failed %d\n", __func__,
+ err);
+ }
+ }
+
+ if (err)
+ host->mphy = NULL;
+ /*
+ * Allow unbound mphy because not every platform needs specific
+ * mphy control.
+ */
+ if (err == -ENODEV)
+ err = 0;
+
+ return err;
+}
+
+static int ufs_mtk_setup_ref_clk(struct ufs_hba *hba, bool on)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct arm_smccc_res res;
+ ktime_t timeout, time_checked;
+ u32 value;
+
+ if (host->ref_clk_enabled == on)
+ return 0;
+
+ ufs_mtk_ref_clk_notify(on, PRE_CHANGE, res);
+
+ if (on) {
+ ufshcd_writel(hba, REFCLK_REQUEST, REG_UFS_REFCLK_CTRL);
+ } else {
+ ufshcd_delay_us(host->ref_clk_gating_wait_us, 10);
+ ufshcd_writel(hba, REFCLK_RELEASE, REG_UFS_REFCLK_CTRL);
+ }
+
+ /* Wait for ack */
+ timeout = ktime_add_us(ktime_get(), REFCLK_REQ_TIMEOUT_US);
+ do {
+ time_checked = ktime_get();
+ value = ufshcd_readl(hba, REG_UFS_REFCLK_CTRL);
+
+ /* Wait until ack bit equals to req bit */
+ if (((value & REFCLK_ACK) >> 1) == (value & REFCLK_REQUEST))
+ goto out;
+
+ usleep_range(100, 200);
+ } while (ktime_before(time_checked, timeout));
+
+ dev_err(hba->dev, "missing ack of refclk req, reg: 0x%x\n", value);
+
+ /*
+ * If clock on timeout, assume clock is off, notify tfa do clock
+ * off setting.(keep DIFN disable, release resource)
+ * If clock off timeout, assume clock will off finally,
+ * set ref_clk_enabled directly.(keep DIFN disable, keep resource)
+ */
+ if (on)
+ ufs_mtk_ref_clk_notify(false, POST_CHANGE, res);
+ else
+ host->ref_clk_enabled = false;
+
+ return -ETIMEDOUT;
+
+out:
+ host->ref_clk_enabled = on;
+ if (on)
+ ufshcd_delay_us(host->ref_clk_ungating_wait_us, 10);
+
+ ufs_mtk_ref_clk_notify(on, POST_CHANGE, res);
+
+ return 0;
+}
+
+static void ufs_mtk_setup_ref_clk_wait_us(struct ufs_hba *hba,
+ u16 gating_us)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ if (hba->dev_info.clk_gating_wait_us) {
+ host->ref_clk_gating_wait_us =
+ hba->dev_info.clk_gating_wait_us;
+ } else {
+ host->ref_clk_gating_wait_us = gating_us;
+ }
+
+ host->ref_clk_ungating_wait_us = REFCLK_DEFAULT_WAIT_US;
+}
+
+static void ufs_mtk_dbg_sel(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ if (!host->legacy_ip_ver && host->ip_ver >= IP_VER_MT6983) {
+ ufshcd_writel(hba, 0x820820, REG_UFS_DEBUG_SEL);
+ ufshcd_writel(hba, 0x0, REG_UFS_DEBUG_SEL_B0);
+ ufshcd_writel(hba, 0x55555555, REG_UFS_DEBUG_SEL_B1);
+ ufshcd_writel(hba, 0xaaaaaaaa, REG_UFS_DEBUG_SEL_B2);
+ ufshcd_writel(hba, 0xffffffff, REG_UFS_DEBUG_SEL_B3);
+ } else {
+ ufshcd_writel(hba, 0x20, REG_UFS_DEBUG_SEL);
+ }
+}
+
+static int ufs_mtk_wait_idle_state(struct ufs_hba *hba,
+ unsigned long retry_ms)
+{
+ u64 timeout, time_checked;
+ u32 val, sm;
+ bool wait_idle;
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ /* cannot use plain ktime_get() in suspend */
+ timeout = ktime_get_mono_fast_ns() + retry_ms * 1000000UL;
+
+ /* wait a specific time after check base */
+ udelay(10);
+ wait_idle = false;
+
+ do {
+ time_checked = ktime_get_mono_fast_ns();
+ if (host->legacy_ip_ver || host->ip_ver < IP_VER_MT6899) {
+ ufs_mtk_dbg_sel(hba);
+ val = ufshcd_readl(hba, REG_UFS_PROBE);
+ } else {
+ val = ufshcd_readl(hba, REG_UFS_UFS_MMIO_OTSD_CTRL);
+ val = val >> 16;
+ }
+
+ sm = val & 0x1f;
+
+ /*
+ * if state is in H8 enter and H8 enter confirm
+ * wait until return to idle state.
+ */
+ if ((sm >= VS_HIB_ENTER) && (sm <= VS_HIB_EXIT)) {
+ wait_idle = true;
+ udelay(50);
+ continue;
+ } else if (!wait_idle)
+ break;
+
+ if (wait_idle && (sm == VS_HCE_BASE))
+ break;
+ } while (time_checked < timeout);
+
+ if (wait_idle && sm != VS_HCE_BASE) {
+ dev_info(hba->dev, "wait idle tmo: 0x%x\n", val);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int ufs_mtk_wait_link_state(struct ufs_hba *hba, u32 state,
+ unsigned long max_wait_ms)
+{
+ ktime_t timeout, time_checked;
+ u32 val;
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ timeout = ktime_add_ms(ktime_get(), max_wait_ms);
+ do {
+ time_checked = ktime_get();
+
+ if (host->legacy_ip_ver || host->ip_ver < IP_VER_MT6899) {
+ ufs_mtk_dbg_sel(hba);
+ val = ufshcd_readl(hba, REG_UFS_PROBE);
+ val = val >> 28;
+ } else {
+ val = ufshcd_readl(hba, REG_UFS_UFS_MMIO_OTSD_CTRL);
+ val = val >> 24;
+ }
+
+ if (val == state)
+ return 0;
+
+ /* Sleep for max. 200us */
+ usleep_range(100, 200);
+ } while (ktime_before(time_checked, timeout));
+
+ return -ETIMEDOUT;
+}
+
+static int ufs_mtk_mphy_power_on(struct ufs_hba *hba, bool on)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct phy *mphy = host->mphy;
+ struct arm_smccc_res res;
+ int ret = 0;
+
+ if (!mphy || !(on ^ host->mphy_powered_on))
+ return 0;
+
+ if (on) {
+ if (ufs_mtk_is_va09_supported(hba)) {
+ ret = regulator_enable(host->reg_va09);
+ if (ret < 0)
+ goto out;
+ /* wait 200 us to stablize VA09 */
+ usleep_range(200, 210);
+ ufs_mtk_va09_pwr_ctrl(res, 1);
+ }
+ phy_power_on(mphy);
+ } else {
+ phy_power_off(mphy);
+ if (ufs_mtk_is_va09_supported(hba)) {
+ ufs_mtk_va09_pwr_ctrl(res, 0);
+ ret = regulator_disable(host->reg_va09);
+ }
+ }
+out:
+ if (ret) {
+ dev_info(hba->dev,
+ "failed to %s va09: %d\n",
+ on ? "enable" : "disable",
+ ret);
+ } else {
+ host->mphy_powered_on = on;
+ }
+
+ return ret;
+}
+
+static int ufs_mtk_get_host_clk(struct device *dev, const char *name,
+ struct clk **clk_out)
+{
+ struct clk *clk;
+ int err = 0;
+
+ clk = devm_clk_get(dev, name);
+ if (IS_ERR(clk))
+ err = PTR_ERR(clk);
+ else
+ *clk_out = clk;
+
+ return err;
+}
+
+static void ufs_mtk_boost_crypt(struct ufs_hba *hba, bool boost)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct ufs_mtk_crypt_cfg *cfg;
+ struct regulator *reg;
+ int volt, ret;
+
+ if (!ufs_mtk_is_boost_crypt_enabled(hba))
+ return;
+
+ cfg = host->crypt;
+ volt = cfg->vcore_volt;
+ reg = cfg->reg_vcore;
+
+ ret = clk_prepare_enable(cfg->clk_crypt_mux);
+ if (ret) {
+ dev_info(hba->dev, "clk_prepare_enable(): %d\n",
+ ret);
+ return;
+ }
+
+ if (boost) {
+ ret = regulator_set_voltage(reg, volt, INT_MAX);
+ if (ret) {
+ dev_info(hba->dev,
+ "failed to set vcore to %d\n", volt);
+ goto out;
+ }
+
+ ret = clk_set_parent(cfg->clk_crypt_mux,
+ cfg->clk_crypt_perf);
+ if (ret) {
+ dev_info(hba->dev,
+ "failed to set clk_crypt_perf\n");
+ regulator_set_voltage(reg, 0, INT_MAX);
+ goto out;
+ }
+ } else {
+ ret = clk_set_parent(cfg->clk_crypt_mux,
+ cfg->clk_crypt_lp);
+ if (ret) {
+ dev_info(hba->dev,
+ "failed to set clk_crypt_lp\n");
+ goto out;
+ }
+
+ ret = regulator_set_voltage(reg, 0, INT_MAX);
+ if (ret) {
+ dev_info(hba->dev,
+ "failed to set vcore to MIN\n");
+ }
+ }
+out:
+ clk_disable_unprepare(cfg->clk_crypt_mux);
+}
+
+static int ufs_mtk_init_host_clk(struct ufs_hba *hba, const char *name,
+ struct clk **clk)
+{
+ int ret;
+
+ ret = ufs_mtk_get_host_clk(hba->dev, name, clk);
+ if (ret) {
+ dev_info(hba->dev, "%s: failed to get %s: %d", __func__,
+ name, ret);
+ }
+
+ return ret;
+}
+
+static void ufs_mtk_init_boost_crypt(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct ufs_mtk_crypt_cfg *cfg;
+ struct device *dev = hba->dev;
+ struct regulator *reg;
+ u32 volt;
+
+ host->crypt = devm_kzalloc(dev, sizeof(*(host->crypt)),
+ GFP_KERNEL);
+ if (!host->crypt)
+ goto disable_caps;
+
+ reg = devm_regulator_get_optional(dev, "dvfsrc-vcore");
+ if (IS_ERR(reg)) {
+ dev_info(dev, "failed to get dvfsrc-vcore: %ld",
+ PTR_ERR(reg));
+ goto disable_caps;
+ }
+
+ if (of_property_read_u32(dev->of_node, "boost-crypt-vcore-min",
+ &volt)) {
+ dev_info(dev, "failed to get boost-crypt-vcore-min");
+ goto disable_caps;
+ }
+
+ cfg = host->crypt;
+ if (ufs_mtk_init_host_clk(hba, "crypt_mux",
+ &cfg->clk_crypt_mux))
+ goto disable_caps;
+
+ if (ufs_mtk_init_host_clk(hba, "crypt_lp",
+ &cfg->clk_crypt_lp))
+ goto disable_caps;
+
+ if (ufs_mtk_init_host_clk(hba, "crypt_perf",
+ &cfg->clk_crypt_perf))
+ goto disable_caps;
+
+ cfg->reg_vcore = reg;
+ cfg->vcore_volt = volt;
+ host->caps |= UFS_MTK_CAP_BOOST_CRYPT_ENGINE;
+
+disable_caps:
+ return;
+}
+
+static void ufs_mtk_init_va09_pwr_ctrl(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ host->reg_va09 = regulator_get(hba->dev, "va09");
+ if (IS_ERR(host->reg_va09))
+ dev_info(hba->dev, "failed to get va09");
+ else
+ host->caps |= UFS_MTK_CAP_VA09_PWR_CTRL;
+}
+
+static void ufs_mtk_init_host_caps(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct device_node *np = hba->dev->of_node;
+
+ if (of_property_read_bool(np, "mediatek,ufs-boost-crypt"))
+ ufs_mtk_init_boost_crypt(hba);
+
+ if (of_property_read_bool(np, "mediatek,ufs-support-va09"))
+ ufs_mtk_init_va09_pwr_ctrl(hba);
+
+ if (of_property_read_bool(np, "mediatek,ufs-disable-ah8"))
+ host->caps |= UFS_MTK_CAP_DISABLE_AH8;
+
+ if (of_property_read_bool(np, "mediatek,ufs-broken-vcc"))
+ host->caps |= UFS_MTK_CAP_BROKEN_VCC;
+
+ if (of_property_read_bool(np, "mediatek,ufs-pmc-via-fastauto"))
+ host->caps |= UFS_MTK_CAP_PMC_VIA_FASTAUTO;
+
+ if (of_property_read_bool(np, "mediatek,ufs-tx-skew-fix"))
+ host->caps |= UFS_MTK_CAP_TX_SKEW_FIX;
+
+ if (of_property_read_bool(np, "mediatek,ufs-disable-mcq"))
+ host->caps |= UFS_MTK_CAP_DISABLE_MCQ;
+
+ if (of_property_read_bool(np, "mediatek,ufs-rtff-mtcmos"))
+ host->caps |= UFS_MTK_CAP_RTFF_MTCMOS;
+
+ if (of_property_read_bool(np, "mediatek,ufs-broken-rtc"))
+ host->caps |= UFS_MTK_CAP_MCQ_BROKEN_RTC;
+
+ dev_info(hba->dev, "caps: 0x%x", host->caps);
+}
+
+static void ufs_mtk_scale_perf(struct ufs_hba *hba, bool scale_up)
+{
+ ufs_mtk_boost_crypt(hba, scale_up);
+}
+
+static void ufs_mtk_pwr_ctrl(struct ufs_hba *hba, bool on)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ if (on) {
+ phy_power_on(host->mphy);
+ ufs_mtk_setup_ref_clk(hba, on);
+ if (!ufshcd_is_clkscaling_supported(hba))
+ ufs_mtk_scale_perf(hba, on);
+ } else {
+ if (!ufshcd_is_clkscaling_supported(hba))
+ ufs_mtk_scale_perf(hba, on);
+ ufs_mtk_setup_ref_clk(hba, on);
+ phy_power_off(host->mphy);
+ }
+}
+
+static void ufs_mtk_mcq_disable_irq(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ u32 irq, i;
+
+ if (!hba->mcq_enabled)
+ return;
+
+ if (host->mcq_nr_intr == 0)
+ return;
+
+ for (i = 0; i < host->mcq_nr_intr; i++) {
+ irq = host->mcq_intr_info[i].irq;
+ disable_irq(irq);
+ }
+ host->is_mcq_intr_enabled = false;
+}
+
+static void ufs_mtk_mcq_enable_irq(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ u32 irq, i;
+
+ if (!hba->mcq_enabled)
+ return;
+
+ if (host->mcq_nr_intr == 0)
+ return;
+
+ if (host->is_mcq_intr_enabled == true)
+ return;
+
+ for (i = 0; i < host->mcq_nr_intr; i++) {
+ irq = host->mcq_intr_info[i].irq;
+ enable_irq(irq);
+ }
+ host->is_mcq_intr_enabled = true;
+}
+
+/**
+ * ufs_mtk_setup_clocks - enables/disable clocks
+ * @hba: host controller instance
+ * @on: If true, enable clocks else disable them.
+ * @status: PRE_CHANGE or POST_CHANGE notify
+ *
+ * Return: 0 on success, non-zero on failure.
+ */
+static int ufs_mtk_setup_clocks(struct ufs_hba *hba, bool on,
+ enum ufs_notify_change_status status)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ bool clk_pwr_off = false;
+ int ret = 0;
+
+ /*
+ * In case ufs_mtk_init() is not yet done, simply ignore.
+ * This ufs_mtk_setup_clocks() shall be called from
+ * ufs_mtk_init() after init is done.
+ */
+ if (!host)
+ return 0;
+
+ if (!on && status == PRE_CHANGE) {
+ if (ufshcd_is_link_off(hba)) {
+ clk_pwr_off = true;
+ } else if (ufshcd_is_link_hibern8(hba) ||
+ (!ufshcd_can_hibern8_during_gating(hba) &&
+ ufshcd_is_auto_hibern8_enabled(hba))) {
+ /*
+ * Gate ref-clk and poweroff mphy if link state is in
+ * OFF or Hibern8 by either Auto-Hibern8 or
+ * ufshcd_link_state_transition().
+ */
+ ret = ufs_mtk_wait_link_state(hba,
+ VS_LINK_HIBERN8,
+ 15);
+ if (!ret)
+ clk_pwr_off = true;
+ }
+
+ if (clk_pwr_off) {
+ ufs_mtk_pwr_ctrl(hba, false);
+ } else {
+ dev_warn(hba->dev, "Clock is not turned off, hba->ahit = 0x%x, AHIT = 0x%x\n",
+ hba->ahit,
+ ufshcd_readl(hba,
+ REG_AUTO_HIBERNATE_IDLE_TIMER));
+ }
+ ufs_mtk_mcq_disable_irq(hba);
+ } else if (on && status == POST_CHANGE) {
+ ufs_mtk_pwr_ctrl(hba, true);
+ ufs_mtk_mcq_enable_irq(hba);
+ }
+
+ return ret;
+}
+
+static u32 ufs_mtk_mcq_get_irq(struct ufs_hba *hba, unsigned int cpu)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct blk_mq_tag_set *tag_set = &hba->host->tag_set;
+ struct blk_mq_queue_map *map = &tag_set->map[HCTX_TYPE_DEFAULT];
+ unsigned int nr = map->nr_queues;
+ unsigned int q_index;
+
+ q_index = map->mq_map[cpu];
+ if (q_index >= nr) {
+ dev_err(hba->dev, "hwq index %d exceed %d\n",
+ q_index, nr);
+ return MTK_MCQ_INVALID_IRQ;
+ }
+
+ return host->mcq_intr_info[q_index].irq;
+}
+
+static void ufs_mtk_mcq_set_irq_affinity(struct ufs_hba *hba, unsigned int cpu)
+{
+ unsigned int irq, _cpu;
+ int ret;
+
+ irq = ufs_mtk_mcq_get_irq(hba, cpu);
+ if (irq == MTK_MCQ_INVALID_IRQ) {
+ dev_err(hba->dev, "invalid irq. unable to bind irq to cpu%d", cpu);
+ return;
+ }
+
+ /* force migrate irq of cpu0 to cpu3 */
+ _cpu = (cpu == 0) ? 3 : cpu;
+ ret = irq_set_affinity(irq, cpumask_of(_cpu));
+ if (ret) {
+ dev_err(hba->dev, "set irq %d affinity to CPU %d failed\n",
+ irq, _cpu);
+ return;
+ }
+ dev_info(hba->dev, "set irq %d affinity to CPU: %d\n", irq, _cpu);
+}
+
+static bool ufs_mtk_is_legacy_chipset(struct ufs_hba *hba, u32 hw_ip_ver)
+{
+ bool is_legacy = false;
+
+ switch (hw_ip_ver) {
+ case IP_LEGACY_VER_MT6893:
+ case IP_LEGACY_VER_MT6781:
+ /* can add other legacy chipset ID here accordingly */
+ is_legacy = true;
+ break;
+ default:
+ break;
+ }
+ dev_info(hba->dev, "legacy IP version - 0x%x, is legacy : %d", hw_ip_ver, is_legacy);
+
+ return is_legacy;
+}
+
+/*
+ * HW version format has been changed from 01MMmmmm to 1MMMmmmm, since
+ * project MT6878. In order to perform correct version comparison,
+ * version number is changed by SW for the following projects.
+ * IP_VER_MT6983 0x00360000 to 0x10360000
+ * IP_VER_MT6897 0x01440000 to 0x10440000
+ * IP_VER_MT6989 0x01450000 to 0x10450000
+ * IP_VER_MT6991 0x01460000 to 0x10460000
+ */
+static void ufs_mtk_get_hw_ip_version(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ u32 hw_ip_ver;
+
+ hw_ip_ver = ufshcd_readl(hba, REG_UFS_MTK_IP_VER);
+
+ if (((hw_ip_ver & (0xFF << 24)) == (0x1 << 24)) ||
+ ((hw_ip_ver & (0xFF << 24)) == 0)) {
+ hw_ip_ver &= ~(0xFF << 24);
+ hw_ip_ver |= (0x1 << 28);
+ }
+
+ host->ip_ver = hw_ip_ver;
+
+ host->legacy_ip_ver = ufs_mtk_is_legacy_chipset(hba, hw_ip_ver);
+}
+
+static void ufs_mtk_get_controller_version(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ int ret, ver = 0;
+
+ if (host->hw_ver.major)
+ return;
+
+ /* Set default (minimum) version anyway */
+ host->hw_ver.major = 2;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_LOCALVERINFO), &ver);
+ if (!ret) {
+ if (ver >= UFS_UNIPRO_VER_1_8) {
+ host->hw_ver.major = 3;
+ /*
+ * Fix HCI version for some platforms with
+ * incorrect version
+ */
+ if (hba->ufs_version < ufshci_version(3, 0))
+ hba->ufs_version = ufshci_version(3, 0);
+ }
+ }
+}
+
+static u32 ufs_mtk_get_ufs_hci_version(struct ufs_hba *hba)
+{
+ return hba->ufs_version;
+}
+
+/**
+ * ufs_mtk_init_clocks - Init mtk driver private clocks
+ *
+ * @hba: per adapter instance
+ */
+static void ufs_mtk_init_clocks(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct list_head *head = &hba->clk_list_head;
+ struct ufs_clk_info *clki, *clki_tmp;
+ struct device *dev = hba->dev;
+ struct regulator *reg;
+ u32 volt;
+
+ /*
+ * Find private clocks and store them in struct ufs_mtk_clk.
+ * Remove "ufs_sel_min_src" and "ufs_sel_min_src" from list to avoid
+ * being switched on/off in clock gating.
+ */
+ list_for_each_entry_safe(clki, clki_tmp, head, list) {
+ if (!strcmp(clki->name, "ufs_sel")) {
+ host->mclk.ufs_sel_clki = clki;
+ } else if (!strcmp(clki->name, "ufs_sel_max_src")) {
+ host->mclk.ufs_sel_max_clki = clki;
+ clk_disable_unprepare(clki->clk);
+ list_del(&clki->list);
+ } else if (!strcmp(clki->name, "ufs_sel_min_src")) {
+ host->mclk.ufs_sel_min_clki = clki;
+ clk_disable_unprepare(clki->clk);
+ list_del(&clki->list);
+ } else if (!strcmp(clki->name, "ufs_fde")) {
+ host->mclk.ufs_fde_clki = clki;
+ } else if (!strcmp(clki->name, "ufs_fde_max_src")) {
+ host->mclk.ufs_fde_max_clki = clki;
+ clk_disable_unprepare(clki->clk);
+ list_del(&clki->list);
+ } else if (!strcmp(clki->name, "ufs_fde_min_src")) {
+ host->mclk.ufs_fde_min_clki = clki;
+ clk_disable_unprepare(clki->clk);
+ list_del(&clki->list);
+ }
+ }
+
+ list_for_each_entry(clki, head, list) {
+ dev_info(hba->dev, "clk \"%s\" present", clki->name);
+ }
+
+ if (!ufs_mtk_is_clk_scale_ready(hba)) {
+ hba->caps &= ~UFSHCD_CAP_CLK_SCALING;
+ dev_info(hba->dev,
+ "%s: Clk-scaling not ready. Feature disabled.",
+ __func__);
+ return;
+ }
+
+ /*
+ * Default get vcore if dts have these settings.
+ * No matter clock scaling support or not. (may disable by customer)
+ */
+ reg = devm_regulator_get_optional(dev, "dvfsrc-vcore");
+ if (IS_ERR(reg)) {
+ dev_info(dev, "failed to get dvfsrc-vcore: %ld",
+ PTR_ERR(reg));
+ return;
+ }
+
+ if (of_property_read_u32(dev->of_node, "clk-scale-up-vcore-min",
+ &volt)) {
+ dev_info(dev, "failed to get clk-scale-up-vcore-min");
+ return;
+ }
+
+ host->mclk.reg_vcore = reg;
+ host->mclk.vcore_volt = volt;
+
+ /* If default boot is max gear, request vcore */
+ if (reg && volt && host->clk_scale_up) {
+ if (regulator_set_voltage(reg, volt, INT_MAX)) {
+ dev_info(hba->dev,
+ "Failed to set vcore to %d\n", volt);
+ }
+ }
+}
+
+#define MAX_VCC_NAME 30
+static int ufs_mtk_vreg_fix_vcc(struct ufs_hba *hba)
+{
+ struct ufs_vreg_info *info = &hba->vreg_info;
+ struct device_node *np = hba->dev->of_node;
+ struct device *dev = hba->dev;
+ char vcc_name[MAX_VCC_NAME];
+ struct arm_smccc_res res;
+ int err, ver;
+
+ if (info->vcc)
+ return 0;
+
+ if (of_property_read_bool(np, "mediatek,ufs-vcc-by-num")) {
+ ufs_mtk_get_vcc_num(res);
+ if (res.a1 > UFS_VCC_NONE && res.a1 < UFS_VCC_MAX)
+ snprintf(vcc_name, MAX_VCC_NAME, "vcc-opt%lu", res.a1);
+ else
+ return -ENODEV;
+ } else if (of_property_read_bool(np, "mediatek,ufs-vcc-by-ver")) {
+ ver = (hba->dev_info.wspecversion & 0xF00) >> 8;
+ snprintf(vcc_name, MAX_VCC_NAME, "vcc-ufs%u", ver);
+ } else {
+ return 0;
+ }
+
+ err = ufshcd_populate_vreg(dev, vcc_name, &info->vcc, false);
+ if (err)
+ return err;
+
+ err = ufshcd_get_vreg(dev, info->vcc);
+ if (err)
+ return err;
+
+ err = regulator_enable(info->vcc->reg);
+ if (!err) {
+ info->vcc->enabled = true;
+ dev_info(dev, "%s: %s enabled\n", __func__, vcc_name);
+ }
+
+ return err;
+}
+
+static void ufs_mtk_vreg_fix_vccqx(struct ufs_hba *hba)
+{
+ struct ufs_vreg_info *info = &hba->vreg_info;
+ struct ufs_vreg **vreg_on, **vreg_off;
+
+ if (hba->dev_info.wspecversion >= 0x0300) {
+ vreg_on = &info->vccq;
+ vreg_off = &info->vccq2;
+ } else {
+ vreg_on = &info->vccq2;
+ vreg_off = &info->vccq;
+ }
+
+ if (*vreg_on)
+ (*vreg_on)->always_on = true;
+
+ if (*vreg_off) {
+ regulator_disable((*vreg_off)->reg);
+ devm_kfree(hba->dev, (*vreg_off)->name);
+ devm_kfree(hba->dev, *vreg_off);
+ *vreg_off = NULL;
+ }
+}
+
+static void ufs_mtk_setup_clk_gating(struct ufs_hba *hba)
+{
+ unsigned long flags;
+ u32 ah_ms = 10;
+ u32 ah_scale, ah_timer;
+ static const u32 scale_us[] = {1, 10, 100, 1000, 10000, 100000};
+
+ if (ufshcd_is_clkgating_allowed(hba)) {
+ if (ufshcd_is_auto_hibern8_supported(hba) && hba->ahit) {
+ ah_scale = FIELD_GET(UFSHCI_AHIBERN8_SCALE_MASK,
+ hba->ahit);
+ ah_timer = FIELD_GET(UFSHCI_AHIBERN8_TIMER_MASK,
+ hba->ahit);
+ if (ah_scale <= 5)
+ ah_ms = ah_timer * scale_us[ah_scale] / 1000;
+ }
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ hba->clk_gating.delay_ms = max(ah_ms, 10U);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ }
+}
+
+static void ufs_mtk_fix_ahit(struct ufs_hba *hba)
+{
+ unsigned int us;
+
+ if (ufshcd_is_auto_hibern8_supported(hba)) {
+ switch (hba->dev_info.wmanufacturerid) {
+ case UFS_VENDOR_SAMSUNG:
+ /* configure auto-hibern8 timer to 3.5 ms */
+ us = 3500;
+ break;
+
+ case UFS_VENDOR_MICRON:
+ /* configure auto-hibern8 timer to 2 ms */
+ us = 2000;
+ break;
+
+ default:
+ /* configure auto-hibern8 timer to 1 ms */
+ us = 1000;
+ break;
+ }
+
+ hba->ahit = ufshcd_us_to_ahit(us);
+ }
+
+ ufs_mtk_setup_clk_gating(hba);
+}
+
+static void ufs_mtk_fix_clock_scaling(struct ufs_hba *hba)
+{
+ /* UFS version is below 4.0, clock scaling is not necessary */
+ if ((hba->dev_info.wspecversion < 0x0400) &&
+ ufs_mtk_is_clk_scale_ready(hba)) {
+ hba->caps &= ~UFSHCD_CAP_CLK_SCALING;
+
+ _ufs_mtk_clk_scale(hba, false);
+ }
+}
+
+static void ufs_mtk_init_mcq_irq(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct platform_device *pdev;
+ int i;
+ int irq;
+
+ host->mcq_nr_intr = UFSHCD_MAX_Q_NR;
+ pdev = container_of(hba->dev, struct platform_device, dev);
+
+ if (host->caps & UFS_MTK_CAP_DISABLE_MCQ)
+ goto failed;
+
+ for (i = 0; i < host->mcq_nr_intr; i++) {
+ /* irq index 0 is legacy irq, sq/cq irq start from index 1 */
+ irq = platform_get_irq(pdev, i + 1);
+ if (irq < 0) {
+ host->mcq_intr_info[i].irq = MTK_MCQ_INVALID_IRQ;
+ goto failed;
+ }
+ host->mcq_intr_info[i].hba = hba;
+ host->mcq_intr_info[i].irq = irq;
+ dev_info(hba->dev, "get platform mcq irq: %d, %d\n", i, irq);
+ }
+
+ return;
+failed:
+ /* invalidate irq info */
+ for (i = 0; i < host->mcq_nr_intr; i++)
+ host->mcq_intr_info[i].irq = MTK_MCQ_INVALID_IRQ;
+
+ host->mcq_nr_intr = 0;
+}
+
+/**
+ * ufs_mtk_init - find other essential mmio bases
+ * @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_mtk_init(struct ufs_hba *hba)
+{
+ const struct of_device_id *id;
+ struct device *dev = hba->dev;
+ struct ufs_mtk_host *host;
+ struct Scsi_Host *shost = hba->host;
+ int err = 0;
+ struct arm_smccc_res res;
+
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ if (!host) {
+ err = -ENOMEM;
+ dev_info(dev, "%s: no memory for mtk ufs host\n", __func__);
+ goto out;
+ }
+
+ host->hba = hba;
+ ufshcd_set_variant(hba, host);
+
+ id = of_match_device(ufs_mtk_of_match, dev);
+ if (!id) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ /* Initialize host capability */
+ ufs_mtk_init_host_caps(hba);
+
+ ufs_mtk_init_mcq_irq(hba);
+
+ err = ufs_mtk_bind_mphy(hba);
+ if (err)
+ goto out_variant_clear;
+
+ ufs_mtk_init_reset(hba);
+
+ /* backup mphy setting if mphy can reset */
+ if (host->mphy_reset)
+ ufs_mtk_mphy_ctrl(UFS_MPHY_BACKUP, res);
+
+ /* Enable runtime autosuspend */
+ hba->caps |= UFSHCD_CAP_RPM_AUTOSUSPEND;
+
+ /* Enable clock-gating */
+ hba->caps |= UFSHCD_CAP_CLK_GATING;
+
+ /* Enable inline encryption */
+ hba->caps |= UFSHCD_CAP_CRYPTO;
+
+ /* Enable WriteBooster */
+ hba->caps |= UFSHCD_CAP_WB_EN;
+
+ /* Enable clk scaling*/
+ hba->caps |= UFSHCD_CAP_CLK_SCALING;
+ host->clk_scale_up = true; /* default is max freq */
+
+ /* Set runtime pm delay to replace default */
+ shost->rpm_autosuspend_delay = MTK_RPM_AUTOSUSPEND_DELAY_MS;
+
+ hba->quirks |= UFSHCI_QUIRK_SKIP_MANUAL_WB_FLUSH_CTRL;
+
+ hba->quirks |= UFSHCD_QUIRK_MCQ_BROKEN_INTR;
+ if (host->caps & UFS_MTK_CAP_MCQ_BROKEN_RTC)
+ hba->quirks |= UFSHCD_QUIRK_MCQ_BROKEN_RTC;
+
+ hba->vps->wb_flush_threshold = UFS_WB_BUF_REMAIN_PERCENT(80);
+
+ if (host->caps & UFS_MTK_CAP_DISABLE_AH8)
+ hba->caps |= UFSHCD_CAP_HIBERN8_WITH_CLK_GATING;
+
+ if (host->caps & UFS_MTK_CAP_DISABLE_MCQ)
+ hba->quirks |= UFSHCD_QUIRK_BROKEN_LSDBS_CAP;
+
+ ufs_mtk_init_clocks(hba);
+
+ /*
+ * ufshcd_vops_init() is invoked after
+ * ufshcd_setup_clock(true) in ufshcd_hba_init() thus
+ * phy clock setup is skipped.
+ *
+ * Enable phy clocks specifically here.
+ */
+ ufs_mtk_mphy_power_on(hba, true);
+
+ if (ufs_mtk_is_rtff_mtcmos(hba)) {
+ /* First Restore here, to avoid backup unexpected value */
+ ufs_mtk_mtcmos_ctrl(false, res);
+
+ /* Power on to init */
+ ufs_mtk_mtcmos_ctrl(true, res);
+ }
+
+ ufs_mtk_setup_clocks(hba, true, POST_CHANGE);
+
+ ufs_mtk_get_hw_ip_version(hba);
+
+ goto out;
+
+out_variant_clear:
+ ufshcd_set_variant(hba, NULL);
+out:
+ return err;
+}
+
+static int ufs_mtk_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_host_params host_params;
+
+ ufshcd_init_host_params(&host_params);
+ host_params.hs_rx_gear = UFS_HS_G5;
+ host_params.hs_tx_gear = UFS_HS_G5;
+
+ if (dev_max_params->pwr_rx == SLOW_MODE ||
+ dev_max_params->pwr_tx == SLOW_MODE)
+ host_params.desired_working_mode = UFS_PWM_MODE;
+
+ return ufshcd_negotiate_pwr_params(&host_params, dev_max_params, dev_req_params);
+}
+
+static bool ufs_mtk_pmc_via_fastauto(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ if (!ufs_mtk_is_pmc_via_fastauto(hba))
+ return false;
+
+ if (dev_req_params->hs_rate == hba->pwr_info.hs_rate)
+ return false;
+
+ if (dev_req_params->pwr_tx != FAST_MODE &&
+ dev_req_params->gear_tx < UFS_HS_G4)
+ return false;
+
+ if (dev_req_params->pwr_rx != FAST_MODE &&
+ dev_req_params->gear_rx < UFS_HS_G4)
+ return false;
+
+ if (dev_req_params->pwr_tx == SLOW_MODE ||
+ dev_req_params->pwr_rx == SLOW_MODE)
+ return false;
+
+ return true;
+}
+
+static void ufs_mtk_adjust_sync_length(struct ufs_hba *hba)
+{
+ int i;
+ u32 value;
+ u32 cnt, att, min;
+ struct attr_min {
+ u32 attr;
+ u32 min_value;
+ } pa_min_sync_length[] = {
+ {PA_TXHSG1SYNCLENGTH, 0x48},
+ {PA_TXHSG2SYNCLENGTH, 0x48},
+ {PA_TXHSG3SYNCLENGTH, 0x48},
+ {PA_TXHSG4SYNCLENGTH, 0x48},
+ {PA_TXHSG5SYNCLENGTH, 0x48}
+ };
+
+ cnt = sizeof(pa_min_sync_length) / sizeof(struct attr_min);
+ for (i = 0; i < cnt; i++) {
+ att = pa_min_sync_length[i].attr;
+ min = pa_min_sync_length[i].min_value;
+ ufshcd_dme_get(hba, UIC_ARG_MIB(att), &value);
+ if (value < min)
+ ufshcd_dme_set(hba, UIC_ARG_MIB(att), min);
+
+ ufshcd_dme_peer_get(hba, UIC_ARG_MIB(att), &value);
+ if (value < min)
+ ufshcd_dme_peer_set(hba, UIC_ARG_MIB(att), min);
+ }
+}
+
+static int ufs_mtk_pre_pwr_change(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ int ret = 0;
+
+ if (ufs_mtk_pmc_via_fastauto(hba, dev_req_params)) {
+ ufs_mtk_adjust_sync_length(hba);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXTERMINATION), true);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXGEAR), UFS_HS_G1);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_RXTERMINATION), true);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_RXGEAR), UFS_HS_G1);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_ACTIVETXDATALANES),
+ dev_req_params->lane_tx);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_ACTIVERXDATALANES),
+ dev_req_params->lane_rx);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HSSERIES),
+ dev_req_params->hs_rate);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXHSADAPTTYPE),
+ PA_NO_ADAPT);
+
+ if (!(hba->quirks & UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING)) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA0),
+ DL_FC0ProtectionTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA1),
+ DL_TC0ReplayTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA2),
+ DL_AFC0ReqTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA3),
+ DL_FC1ProtectionTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA4),
+ DL_TC1ReplayTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA5),
+ DL_AFC1ReqTimeOutVal_Default);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DME_LocalFC0ProtectionTimeOutVal),
+ DL_FC0ProtectionTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DME_LocalTC0ReplayTimeOutVal),
+ DL_TC0ReplayTimeOutVal_Default);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DME_LocalAFC0ReqTimeOutVal),
+ DL_AFC0ReqTimeOutVal_Default);
+ }
+
+ ret = ufshcd_uic_change_pwr_mode(hba,
+ FASTAUTO_MODE << 4 | FASTAUTO_MODE);
+
+ if (ret) {
+ dev_err(hba->dev, "%s: HSG1B FASTAUTO failed ret=%d\n",
+ __func__, ret);
+ }
+ }
+
+ /* if already configured to the requested pwr_mode, skip adapt */
+ if (dev_req_params->gear_rx == hba->pwr_info.gear_rx &&
+ dev_req_params->gear_tx == hba->pwr_info.gear_tx &&
+ dev_req_params->lane_rx == hba->pwr_info.lane_rx &&
+ dev_req_params->lane_tx == hba->pwr_info.lane_tx &&
+ dev_req_params->pwr_rx == hba->pwr_info.pwr_rx &&
+ dev_req_params->pwr_tx == hba->pwr_info.pwr_tx &&
+ dev_req_params->hs_rate == hba->pwr_info.hs_rate) {
+ return ret;
+ }
+
+ if (dev_req_params->pwr_rx == FAST_MODE ||
+ dev_req_params->pwr_rx == FASTAUTO_MODE) {
+ if (host->hw_ver.major >= 3) {
+ ret = ufshcd_dme_configure_adapt(hba,
+ dev_req_params->gear_tx,
+ PA_INITIAL_ADAPT);
+ } else {
+ ret = ufshcd_dme_configure_adapt(hba,
+ dev_req_params->gear_tx,
+ PA_NO_ADAPT);
+ }
+ } else {
+ ret = ufshcd_dme_configure_adapt(hba,
+ dev_req_params->gear_tx,
+ PA_NO_ADAPT);
+ }
+
+ return ret;
+}
+
+static int ufs_mtk_auto_hibern8_disable(struct ufs_hba *hba)
+{
+ int ret;
+
+ /* disable auto-hibern8 */
+ ufshcd_writel(hba, 0, REG_AUTO_HIBERNATE_IDLE_TIMER);
+
+ /* wait host return to idle state when auto-hibern8 off */
+ ret = ufs_mtk_wait_idle_state(hba, 5);
+ if (ret)
+ goto out;
+
+ ret = ufs_mtk_wait_link_state(hba, VS_LINK_UP, 100);
+
+out:
+ if (ret) {
+ dev_warn(hba->dev, "exit h8 state fail, ret=%d\n", ret);
+
+ ufshcd_force_error_recovery(hba);
+
+ /* trigger error handler and break suspend */
+ ret = -EBUSY;
+ }
+
+ return ret;
+}
+
+static int ufs_mtk_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status stage,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ int ret = 0;
+ static u32 reg;
+
+ switch (stage) {
+ case PRE_CHANGE:
+ if (ufshcd_is_auto_hibern8_supported(hba)) {
+ reg = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER);
+ ufs_mtk_auto_hibern8_disable(hba);
+ }
+ ret = ufs_mtk_pre_pwr_change(hba, dev_req_params);
+ break;
+ case POST_CHANGE:
+ if (ufshcd_is_auto_hibern8_supported(hba))
+ ufshcd_writel(hba, reg, REG_AUTO_HIBERNATE_IDLE_TIMER);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int ufs_mtk_unipro_set_lpm(struct ufs_hba *hba, bool lpm)
+{
+ int ret;
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ ret = ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(VS_UNIPROPOWERDOWNCONTROL, 0),
+ lpm ? 1 : 0);
+ if (!ret || !lpm) {
+ /*
+ * Forcibly set as non-LPM mode if UIC commands is failed
+ * to use default hba_enable_delay_us value for re-enabling
+ * the host.
+ */
+ host->unipro_lpm = lpm;
+ }
+
+ return ret;
+}
+
+static int ufs_mtk_pre_link(struct ufs_hba *hba)
+{
+ int ret;
+ u32 tmp;
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ ufs_mtk_get_controller_version(hba);
+
+ ret = ufs_mtk_unipro_set_lpm(hba, false);
+ if (ret)
+ return ret;
+
+ /*
+ * Setting PA_Local_TX_LCC_Enable to 0 before link startup
+ * to make sure that both host and device TX LCC are disabled
+ * once link startup is completed.
+ */
+ ret = ufshcd_disable_host_tx_lcc(hba);
+ if (ret)
+ return ret;
+
+ /* disable deep stall */
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(VS_SAVEPOWERCONTROL), &tmp);
+ if (ret)
+ return ret;
+
+ tmp &= ~(1 << 6);
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_SAVEPOWERCONTROL), tmp);
+
+ /* Enable the 1144 functions setting */
+ if (host->ip_ver == IP_VER_MT6989) {
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(VS_DEBUGOMC), &tmp);
+ if (ret)
+ return ret;
+
+ tmp |= 0x10;
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_DEBUGOMC), tmp);
+ }
+
+ return ret;
+}
+
+static void ufs_mtk_post_link(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ u32 tmp;
+
+ /* fix device PA_INIT no adapt */
+ if (host->ip_ver >= IP_VER_MT6899) {
+ ufshcd_dme_get(hba, UIC_ARG_MIB(VS_DEBUGOMC), &tmp);
+ tmp |= 0x100;
+ ufshcd_dme_set(hba, UIC_ARG_MIB(VS_DEBUGOMC), tmp);
+ }
+
+ /* enable unipro clock gating feature */
+ ufs_mtk_cfg_unipro_cg(hba, true);
+}
+
+static int ufs_mtk_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status stage)
+{
+ int ret = 0;
+
+ switch (stage) {
+ case PRE_CHANGE:
+ ret = ufs_mtk_pre_link(hba);
+ break;
+ case POST_CHANGE:
+ ufs_mtk_post_link(hba);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int ufs_mtk_device_reset(struct ufs_hba *hba)
+{
+ struct arm_smccc_res res;
+
+ ufs_mtk_device_reset_ctrl(0, res);
+
+ /* disable hba in middle of device reset */
+ ufshcd_hba_stop(hba);
+
+ /*
+ * The reset signal is active low. UFS devices shall detect
+ * more than or equal to 1us of positive or negative RST_n
+ * pulse width.
+ *
+ * To be on safe side, keep the reset low for at least 10us.
+ */
+ usleep_range(10, 15);
+
+ ufs_mtk_device_reset_ctrl(1, res);
+
+ /* Some devices may need time to respond to rst_n */
+ usleep_range(10000, 15000);
+
+ dev_info(hba->dev, "device reset done\n");
+
+ return 0;
+}
+
+static int ufs_mtk_link_set_hpm(struct ufs_hba *hba)
+{
+ int err;
+ u32 val;
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ err = ufshcd_hba_enable(hba);
+ if (err)
+ return err;
+
+ err = ufs_mtk_unipro_set_lpm(hba, false);
+ if (err) {
+ if (host->ip_ver < IP_VER_MT6899) {
+ ufs_mtk_dbg_sel(hba);
+ val = ufshcd_readl(hba, REG_UFS_PROBE);
+ } else {
+ val = ufshcd_readl(hba, REG_UFS_UFS_MMIO_OTSD_CTRL);
+ }
+ ufshcd_update_evt_hist(hba, UFS_EVT_RESUME_ERR, (u32)val);
+ val = ufshcd_readl(hba, REG_INTERRUPT_STATUS);
+ ufshcd_update_evt_hist(hba, UFS_EVT_RESUME_ERR, (u32)val);
+ return err;
+ }
+
+ err = ufshcd_uic_hibern8_exit(hba);
+ if (err)
+ return err;
+
+ /* Check link state to make sure exit h8 success */
+ err = ufs_mtk_wait_idle_state(hba, 5);
+ if (err) {
+ dev_warn(hba->dev, "wait idle fail, err=%d\n", err);
+ return err;
+ }
+ err = ufs_mtk_wait_link_state(hba, VS_LINK_UP, 100);
+ if (err) {
+ dev_warn(hba->dev, "exit h8 state fail, err=%d\n", err);
+ return err;
+ }
+ ufshcd_set_link_active(hba);
+
+ err = ufshcd_make_hba_operational(hba);
+ if (err)
+ return err;
+
+ if (hba->mcq_enabled) {
+ ufs_mtk_config_mcq(hba, false);
+ ufshcd_mcq_make_queues_operational(hba);
+ ufshcd_mcq_config_mac(hba, hba->nutrs);
+ ufshcd_mcq_enable(hba);
+ }
+
+ return 0;
+}
+
+static int ufs_mtk_link_set_lpm(struct ufs_hba *hba)
+{
+ int err;
+
+ /* Disable reset confirm feature by UniPro */
+ ufshcd_writel(hba,
+ (ufshcd_readl(hba, REG_UFS_XOUFS_CTRL) & ~0x100),
+ REG_UFS_XOUFS_CTRL);
+
+ err = ufs_mtk_unipro_set_lpm(hba, true);
+ if (err) {
+ /* Resume UniPro state for following error recovery */
+ ufs_mtk_unipro_set_lpm(hba, false);
+ return err;
+ }
+
+ return 0;
+}
+
+static void ufs_mtk_vccqx_set_lpm(struct ufs_hba *hba, bool lpm)
+{
+ struct ufs_vreg *vccqx = NULL;
+
+ if (!hba->vreg_info.vccq && !hba->vreg_info.vccq2)
+ return;
+
+ if (hba->vreg_info.vccq)
+ vccqx = hba->vreg_info.vccq;
+ else
+ vccqx = hba->vreg_info.vccq2;
+
+ regulator_set_mode(vccqx->reg,
+ lpm ? REGULATOR_MODE_IDLE : REGULATOR_MODE_NORMAL);
+}
+
+static void ufs_mtk_vsx_set_lpm(struct ufs_hba *hba, bool lpm)
+{
+ struct arm_smccc_res res;
+
+ ufs_mtk_device_pwr_ctrl(!lpm,
+ (unsigned long)hba->dev_info.wspecversion,
+ res);
+}
+
+static void ufs_mtk_dev_vreg_set_lpm(struct ufs_hba *hba, bool lpm)
+{
+ bool skip_vccqx = false;
+
+ /* Prevent entering LPM when device is still active */
+ if (lpm && ufshcd_is_ufs_dev_active(hba))
+ return;
+
+ /* Skip vccqx lpm control and control vsx only */
+ if (!hba->vreg_info.vccq && !hba->vreg_info.vccq2)
+ skip_vccqx = true;
+
+ /* VCC is always-on, control vsx only */
+ if (!hba->vreg_info.vcc)
+ skip_vccqx = true;
+
+ /* Broken vcc keep vcc always on, most case control vsx only */
+ if (lpm && hba->vreg_info.vcc && hba->vreg_info.vcc->enabled) {
+ /* Some device vccqx/vsx can enter lpm */
+ if (ufs_mtk_is_allow_vccqx_lpm(hba))
+ skip_vccqx = false;
+ else /* control vsx only */
+ skip_vccqx = true;
+ }
+
+ if (lpm) {
+ if (!skip_vccqx)
+ ufs_mtk_vccqx_set_lpm(hba, lpm);
+ ufs_mtk_vsx_set_lpm(hba, lpm);
+ } else {
+ ufs_mtk_vsx_set_lpm(hba, lpm);
+ if (!skip_vccqx)
+ ufs_mtk_vccqx_set_lpm(hba, lpm);
+ }
+}
+
+static int ufs_mtk_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op,
+ enum ufs_notify_change_status status)
+{
+ int err;
+ struct arm_smccc_res res;
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ if (status == PRE_CHANGE) {
+ if (ufshcd_is_auto_hibern8_supported(hba))
+ return ufs_mtk_auto_hibern8_disable(hba);
+ return 0;
+ }
+
+ if (ufshcd_is_link_hibern8(hba)) {
+ err = ufs_mtk_link_set_lpm(hba);
+ if (err)
+ goto fail;
+ }
+
+ if (!ufshcd_is_link_active(hba)) {
+ /*
+ * Make sure no error will be returned to prevent
+ * ufshcd_suspend() re-enabling regulators while vreg is still
+ * in low-power mode.
+ */
+ err = ufs_mtk_mphy_power_on(hba, false);
+ if (err)
+ goto fail;
+ }
+
+ if (ufshcd_is_link_off(hba))
+ ufs_mtk_device_reset_ctrl(0, res);
+
+ ufs_mtk_sram_pwr_ctrl(false, res);
+
+ /* Release pm_qos/clk if in scale-up mode during suspend */
+ if (ufshcd_is_clkscaling_supported(hba) && (host->clk_scale_up)) {
+ ufshcd_pm_qos_update(hba, false);
+ _ufs_mtk_clk_scale(hba, false);
+ } else if ((!ufshcd_is_clkscaling_supported(hba) &&
+ hba->pwr_info.gear_rx >= UFS_HS_G5)) {
+ _ufs_mtk_clk_scale(hba, false);
+ }
+
+ return 0;
+fail:
+ /*
+ * Set link as off state enforcedly to trigger
+ * ufshcd_host_reset_and_restore() in ufshcd_suspend()
+ * for completed host reset.
+ */
+ ufshcd_set_link_off(hba);
+ return -EAGAIN;
+}
+
+static int ufs_mtk_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+ int err;
+ struct arm_smccc_res res;
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL)
+ ufs_mtk_dev_vreg_set_lpm(hba, false);
+
+ ufs_mtk_sram_pwr_ctrl(true, res);
+
+ err = ufs_mtk_mphy_power_on(hba, true);
+ if (err)
+ goto fail;
+
+ /* Request pm_qos/clk if in scale-up mode after resume */
+ if (ufshcd_is_clkscaling_supported(hba) && (host->clk_scale_up)) {
+ ufshcd_pm_qos_update(hba, true);
+ _ufs_mtk_clk_scale(hba, true);
+ } else if ((!ufshcd_is_clkscaling_supported(hba) &&
+ hba->pwr_info.gear_rx >= UFS_HS_G5)) {
+ _ufs_mtk_clk_scale(hba, true);
+ }
+
+ if (ufshcd_is_link_hibern8(hba)) {
+ err = ufs_mtk_link_set_hpm(hba);
+ if (err)
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ /*
+ * Check if the platform (parent) device has resumed, and ensure that
+ * power, clock, and MTCMOS are all turned on.
+ */
+ err = ufshcd_link_recovery(hba);
+ if (err) {
+ dev_err(hba->dev, "Device PM: req=%d, status:%d, err:%d\n",
+ hba->dev->power.request,
+ hba->dev->power.runtime_status,
+ hba->dev->power.runtime_error);
+ }
+
+ return 0; /* Cannot return a failure, otherwise, the I/O will hang. */
+}
+
+static void ufs_mtk_dbg_register_dump(struct ufs_hba *hba)
+{
+ /* Dump ufshci register 0x140 ~ 0x14C */
+ ufshcd_dump_regs(hba, REG_UFS_XOUFS_CTRL, 0x10,
+ "XOUFS Ctrl (0x140): ");
+
+ ufshcd_dump_regs(hba, REG_UFS_EXTREG, 0x4, "Ext Reg ");
+
+ /* Dump ufshci register 0x2200 ~ 0x22AC */
+ ufshcd_dump_regs(hba, REG_UFS_MPHYCTRL,
+ REG_UFS_REJECT_MON - REG_UFS_MPHYCTRL + 4,
+ "MPHY Ctrl (0x2200): ");
+
+ /* Direct debugging information to REG_MTK_PROBE */
+ ufs_mtk_dbg_sel(hba);
+ ufshcd_dump_regs(hba, REG_UFS_PROBE, 0x4, "Debug Probe ");
+}
+
+static int ufs_mtk_apply_dev_quirks(struct ufs_hba *hba)
+{
+ struct ufs_dev_info *dev_info = &hba->dev_info;
+ u16 mid = dev_info->wmanufacturerid;
+ unsigned int cpu;
+
+ if (hba->mcq_enabled) {
+ /* Iterate all cpus to set affinity for mcq irqs */
+ for (cpu = 0; cpu < nr_cpu_ids; cpu++)
+ ufs_mtk_mcq_set_irq_affinity(hba, cpu);
+ }
+
+ if (mid == UFS_VENDOR_SAMSUNG) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TACTIVATE), 6);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HIBERN8TIME), 10);
+ } else if (mid == UFS_VENDOR_MICRON) {
+ /* Only for the host which have TX skew issue */
+ if (ufs_mtk_is_tx_skew_fix(hba) &&
+ (STR_PRFX_EQUAL("MT128GBCAV2U31", dev_info->model) ||
+ STR_PRFX_EQUAL("MT256GBCAV4U31", dev_info->model) ||
+ STR_PRFX_EQUAL("MT512GBCAV8U31", dev_info->model) ||
+ STR_PRFX_EQUAL("MT256GBEAX4U40", dev_info->model) ||
+ STR_PRFX_EQUAL("MT512GAYAX4U40", dev_info->model) ||
+ STR_PRFX_EQUAL("MT001TAYAX8U40", dev_info->model))) {
+ ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TACTIVATE), 8);
+ }
+ }
+
+ /*
+ * Decide waiting time before gating reference clock and
+ * after ungating reference clock according to vendors'
+ * requirements.
+ */
+ if (mid == UFS_VENDOR_SAMSUNG)
+ ufs_mtk_setup_ref_clk_wait_us(hba, 1);
+ else if (mid == UFS_VENDOR_SKHYNIX)
+ ufs_mtk_setup_ref_clk_wait_us(hba, 30);
+ else if (mid == UFS_VENDOR_TOSHIBA)
+ ufs_mtk_setup_ref_clk_wait_us(hba, 100);
+ else
+ ufs_mtk_setup_ref_clk_wait_us(hba,
+ REFCLK_DEFAULT_WAIT_US);
+ return 0;
+}
+
+static void ufs_mtk_fixup_dev_quirks(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ ufshcd_fixup_dev_quirks(hba, ufs_mtk_dev_fixups);
+
+ if (ufs_mtk_is_broken_vcc(hba) && hba->vreg_info.vcc) {
+ hba->vreg_info.vcc->always_on = true;
+ /*
+ * VCC will be kept always-on thus we don't
+ * need any delay before putting device's VCC in LPM mode.
+ */
+ hba->dev_quirks &= ~UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM;
+ }
+
+ /*
+ * Add a delay after enabling UFS5 VCC to ensure the voltage
+ * is stable before the refclk is enabled.
+ */
+ if (hba->dev_info.wspecversion >= 0x0500 &&
+ (host->ip_ver == IP_VER_MT6995_A0 ||
+ host->ip_ver == IP_VER_MT6995_B0))
+ hba->quirks |= UFSHCD_QUIRK_VCC_ON_DELAY;
+
+ ufs_mtk_vreg_fix_vcc(hba);
+ ufs_mtk_vreg_fix_vccqx(hba);
+ ufs_mtk_fix_ahit(hba);
+ ufs_mtk_fix_clock_scaling(hba);
+}
+
+static void ufs_mtk_event_notify(struct ufs_hba *hba,
+ enum ufs_event_type evt, void *data)
+{
+ unsigned int val = *(u32 *)data;
+ unsigned long reg;
+ u8 bit;
+
+ trace_ufs_mtk_event(evt, val);
+
+ /* Print details of UIC Errors */
+ if (evt <= UFS_EVT_DME_ERR) {
+ dev_info(hba->dev,
+ "Host UIC Error Code (%s): %08x\n",
+ ufs_uic_err_str[evt], val);
+ reg = val;
+ }
+
+ if (evt == UFS_EVT_PA_ERR) {
+ for_each_set_bit(bit, &reg, ARRAY_SIZE(ufs_uic_pa_err_str))
+ dev_info(hba->dev, "%s\n", ufs_uic_pa_err_str[bit]);
+ }
+
+ if (evt == UFS_EVT_DL_ERR) {
+ for_each_set_bit(bit, &reg, ARRAY_SIZE(ufs_uic_dl_err_str))
+ dev_info(hba->dev, "%s\n", ufs_uic_dl_err_str[bit]);
+ }
+}
+
+static void ufs_mtk_config_scaling_param(struct ufs_hba *hba,
+ struct devfreq_dev_profile *profile,
+ struct devfreq_simple_ondemand_data *data)
+{
+ /* Customize min gear in clk scaling */
+ hba->clk_scaling.min_gear = UFS_HS_G4;
+
+ hba->vps->devfreq_profile.polling_ms = 200;
+ hba->vps->ondemand_data.upthreshold = 50;
+ hba->vps->ondemand_data.downdifferential = 20;
+}
+
+static void _ufs_mtk_clk_scale(struct ufs_hba *hba, bool scale_up)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct ufs_mtk_clk *mclk = &host->mclk;
+ struct ufs_clk_info *clki = mclk->ufs_sel_clki;
+ struct ufs_clk_info *fde_clki = mclk->ufs_fde_clki;
+ struct regulator *reg;
+ int volt, ret = 0;
+ bool clk_bind_vcore = false;
+ bool clk_fde_scale = false;
+
+ if (!hba->clk_scaling.is_initialized)
+ return;
+
+ if (!clki || !fde_clki)
+ return;
+
+ reg = host->mclk.reg_vcore;
+ volt = host->mclk.vcore_volt;
+ if (reg && volt != 0)
+ clk_bind_vcore = true;
+
+ if (mclk->ufs_fde_max_clki && mclk->ufs_fde_min_clki)
+ clk_fde_scale = true;
+
+ ret = clk_prepare_enable(clki->clk);
+ if (ret) {
+ dev_info(hba->dev,
+ "clk_prepare_enable() fail, ret: %d\n", ret);
+ return;
+ }
+
+ if (clk_fde_scale) {
+ ret = clk_prepare_enable(fde_clki->clk);
+ if (ret) {
+ dev_info(hba->dev,
+ "fde clk_prepare_enable() fail, ret: %d\n", ret);
+ return;
+ }
+ }
+
+ if (scale_up) {
+ if (clk_bind_vcore) {
+ ret = regulator_set_voltage(reg, volt, INT_MAX);
+ if (ret) {
+ dev_info(hba->dev,
+ "Failed to set vcore to %d\n", volt);
+ goto out;
+ }
+ }
+
+ ret = clk_set_parent(clki->clk, mclk->ufs_sel_max_clki->clk);
+ if (ret) {
+ dev_info(hba->dev, "Failed to set clk mux, ret = %d\n",
+ ret);
+ }
+
+ if (clk_fde_scale) {
+ ret = clk_set_parent(fde_clki->clk,
+ mclk->ufs_fde_max_clki->clk);
+ if (ret) {
+ dev_info(hba->dev,
+ "Failed to set fde clk mux, ret = %d\n",
+ ret);
+ }
+ }
+ } else {
+ if (clk_fde_scale) {
+ ret = clk_set_parent(fde_clki->clk,
+ mclk->ufs_fde_min_clki->clk);
+ if (ret) {
+ dev_info(hba->dev,
+ "Failed to set fde clk mux, ret = %d\n",
+ ret);
+ goto out;
+ }
+ }
+
+ ret = clk_set_parent(clki->clk, mclk->ufs_sel_min_clki->clk);
+ if (ret) {
+ dev_info(hba->dev, "Failed to set clk mux, ret = %d\n",
+ ret);
+ goto out;
+ }
+
+ if (clk_bind_vcore) {
+ ret = regulator_set_voltage(reg, 0, INT_MAX);
+ if (ret) {
+ dev_info(hba->dev,
+ "failed to set vcore to MIN\n");
+ }
+ }
+ }
+
+out:
+ clk_disable_unprepare(clki->clk);
+
+ if (clk_fde_scale)
+ clk_disable_unprepare(fde_clki->clk);
+}
+
+/**
+ * ufs_mtk_clk_scale - Internal clk scaling operation
+ *
+ * MTK platform supports clk scaling by switching parent of ufs_sel(mux).
+ * The ufs_sel downstream to ufs_ck which feeds directly to UFS hardware.
+ * Max and min clocks rate of ufs_sel defined in dts should match rate of
+ * "ufs_sel_max_src" and "ufs_sel_min_src" respectively.
+ * This prevent changing rate of pll clock that is shared between modules.
+ *
+ * @hba: per adapter instance
+ * @scale_up: True for scaling up and false for scaling down
+ */
+static void ufs_mtk_clk_scale(struct ufs_hba *hba, bool scale_up)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct ufs_mtk_clk *mclk = &host->mclk;
+ struct ufs_clk_info *clki = mclk->ufs_sel_clki;
+
+ if (host->clk_scale_up == scale_up)
+ goto out;
+
+ if (scale_up)
+ _ufs_mtk_clk_scale(hba, true);
+ else
+ _ufs_mtk_clk_scale(hba, false);
+
+ host->clk_scale_up = scale_up;
+
+ /* Must always set before clk_set_rate() */
+ if (scale_up)
+ clki->curr_freq = clki->max_freq;
+ else
+ clki->curr_freq = clki->min_freq;
+out:
+ trace_ufs_mtk_clk_scale(clki->name, scale_up, clk_get_rate(clki->clk));
+}
+
+static int ufs_mtk_clk_scale_notify(struct ufs_hba *hba, bool scale_up,
+ unsigned long target_freq,
+ enum ufs_notify_change_status status)
+{
+ if (!ufshcd_is_clkscaling_supported(hba))
+ return 0;
+
+ if (status == PRE_CHANGE) {
+ /* Switch parent before clk_set_rate() */
+ ufs_mtk_clk_scale(hba, scale_up);
+ } else {
+ /* Request interrupt latency QoS accordingly */
+ ufs_mtk_scale_perf(hba, scale_up);
+ }
+
+ return 0;
+}
+
+static int ufs_mtk_get_hba_nortt(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ if (host->legacy_ip_ver || host->ip_ver < IP_VER_MT6995_B0)
+ return MTK_MAX_NUM_RTT_LEGACY;
+
+ return FIELD_GET(MASK_NUMBER_OUTSTANDING_RTT, hba->capabilities) + 1;
+}
+
+static int ufs_mtk_get_hba_mac(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ /* MCQ operation not permitted */
+ if (host->caps & UFS_MTK_CAP_DISABLE_MCQ)
+ return -EPERM;
+
+ return MAX_SUPP_MAC;
+}
+
+static int ufs_mtk_op_runtime_config(struct ufs_hba *hba)
+{
+ struct ufshcd_mcq_opr_info_t *opr;
+ int i;
+
+ hba->mcq_opr[OPR_SQD].offset = REG_UFS_MTK_SQD;
+ hba->mcq_opr[OPR_SQIS].offset = REG_UFS_MTK_SQIS;
+ hba->mcq_opr[OPR_CQD].offset = REG_UFS_MTK_CQD;
+ hba->mcq_opr[OPR_CQIS].offset = REG_UFS_MTK_CQIS;
+
+ for (i = 0; i < OPR_MAX; i++) {
+ opr = &hba->mcq_opr[i];
+ opr->stride = REG_UFS_MCQ_STRIDE;
+ opr->base = hba->mmio_base + opr->offset;
+ }
+
+ return 0;
+}
+
+static int ufs_mtk_mcq_config_resource(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ /* fail mcq initialization if interrupt is not filled properly */
+ if (!host->mcq_nr_intr) {
+ dev_info(hba->dev, "IRQs not ready. MCQ disabled.");
+ return -EINVAL;
+ }
+
+ hba->mcq_base = hba->mmio_base + MCQ_QUEUE_OFFSET(hba->mcq_capabilities);
+ return 0;
+}
+
+static irqreturn_t ufs_mtk_mcq_intr(int irq, void *__intr_info)
+{
+ struct ufs_mtk_mcq_intr_info *mcq_intr_info = __intr_info;
+ struct ufs_hba *hba = mcq_intr_info->hba;
+ struct ufs_hw_queue *hwq;
+ u32 events;
+ int qid = mcq_intr_info->qid;
+
+ hwq = &hba->uhq[qid];
+
+ events = ufshcd_mcq_read_cqis(hba, qid);
+ if (events)
+ ufshcd_mcq_write_cqis(hba, events, qid);
+
+ if (events & UFSHCD_MCQ_CQIS_TAIL_ENT_PUSH_STS)
+ ufshcd_mcq_poll_cqe_lock(hba, hwq);
+
+ return IRQ_HANDLED;
+}
+
+static int ufs_mtk_config_mcq_irq(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ u32 irq, i;
+ int ret;
+
+ for (i = 0; i < host->mcq_nr_intr; i++) {
+ irq = host->mcq_intr_info[i].irq;
+ if (irq == MTK_MCQ_INVALID_IRQ) {
+ dev_err(hba->dev, "invalid irq. %d\n", i);
+ return -ENOPARAM;
+ }
+
+ host->mcq_intr_info[i].qid = i;
+ ret = devm_request_irq(hba->dev, irq, ufs_mtk_mcq_intr, 0, UFSHCD,
+ &host->mcq_intr_info[i]);
+
+ dev_dbg(hba->dev, "request irq %d intr %s\n", irq, ret ? "failed" : "");
+
+ if (ret)
+ return ret;
+ }
+ host->is_mcq_intr_enabled = true;
+
+ return 0;
+}
+
+static int ufs_mtk_config_mcq(struct ufs_hba *hba, bool irq)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ int ret = 0;
+
+ if (!host->mcq_set_intr) {
+ /* Disable irq option register */
+ ufshcd_rmwl(hba, MCQ_INTR_EN_MSK, 0, REG_UFS_MMIO_OPT_CTRL_0);
+
+ if (irq) {
+ ret = ufs_mtk_config_mcq_irq(hba);
+ if (ret)
+ return ret;
+ }
+
+ host->mcq_set_intr = true;
+ }
+
+ ufshcd_rmwl(hba, MCQ_AH8, MCQ_AH8, REG_UFS_MMIO_OPT_CTRL_0);
+ ufshcd_rmwl(hba, MCQ_INTR_EN_MSK, MCQ_MULTI_INTR_EN, REG_UFS_MMIO_OPT_CTRL_0);
+
+ return 0;
+}
+
+static int ufs_mtk_config_esi(struct ufs_hba *hba)
+{
+ return ufs_mtk_config_mcq(hba, true);
+}
+
+static void ufs_mtk_config_scsi_dev(struct scsi_device *sdev)
+{
+ struct ufs_hba *hba = shost_priv(sdev->host);
+
+ dev_dbg(hba->dev, "lu %llu scsi device configured", sdev->lun);
+ if (sdev->lun == 2)
+ blk_queue_flag_set(QUEUE_FLAG_SAME_FORCE, sdev->request_queue);
+}
+
+/*
+ * struct ufs_hba_mtk_vops - UFS MTK specific variant operations
+ *
+ * The variant operations configure the necessary controller and PHY
+ * handshake during initialization.
+ */
+static const struct ufs_hba_variant_ops ufs_hba_mtk_vops = {
+ .name = "mediatek.ufshci",
+ .init = ufs_mtk_init,
+ .get_ufs_hci_version = ufs_mtk_get_ufs_hci_version,
+ .setup_clocks = ufs_mtk_setup_clocks,
+ .hce_enable_notify = ufs_mtk_hce_enable_notify,
+ .link_startup_notify = ufs_mtk_link_startup_notify,
+ .negotiate_pwr_mode = ufs_mtk_negotiate_pwr_mode,
+ .pwr_change_notify = ufs_mtk_pwr_change_notify,
+ .apply_dev_quirks = ufs_mtk_apply_dev_quirks,
+ .fixup_dev_quirks = ufs_mtk_fixup_dev_quirks,
+ .suspend = ufs_mtk_suspend,
+ .resume = ufs_mtk_resume,
+ .dbg_register_dump = ufs_mtk_dbg_register_dump,
+ .device_reset = ufs_mtk_device_reset,
+ .event_notify = ufs_mtk_event_notify,
+ .config_scaling_param = ufs_mtk_config_scaling_param,
+ .clk_scale_notify = ufs_mtk_clk_scale_notify,
+ .get_hba_nortt = ufs_mtk_get_hba_nortt,
+ /* mcq vops */
+ .get_hba_mac = ufs_mtk_get_hba_mac,
+ .op_runtime_config = ufs_mtk_op_runtime_config,
+ .mcq_config_resource = ufs_mtk_mcq_config_resource,
+ .config_esi = ufs_mtk_config_esi,
+ .config_scsi_dev = ufs_mtk_config_scsi_dev,
+};
+
+/**
+ * ufs_mtk_probe - probe routine of the driver
+ * @pdev: pointer to Platform device handle
+ *
+ * Return: zero for success and non-zero for failure.
+ */
+static int ufs_mtk_probe(struct platform_device *pdev)
+{
+ int err;
+ struct device *dev = &pdev->dev, *phy_dev = NULL;
+ struct device_node *reset_node, *phy_node = NULL;
+ struct platform_device *reset_pdev, *phy_pdev = NULL;
+ struct device_link *link;
+ struct ufs_hba *hba;
+ struct ufs_mtk_host *host;
+
+ reset_node = of_find_compatible_node(NULL, NULL,
+ "ti,syscon-reset");
+ if (!reset_node) {
+ dev_notice(dev, "find ti,syscon-reset fail\n");
+ goto skip_reset;
+ }
+ reset_pdev = of_find_device_by_node(reset_node);
+ if (!reset_pdev) {
+ dev_notice(dev, "find reset_pdev fail\n");
+ goto skip_reset;
+ }
+ link = device_link_add(dev, &reset_pdev->dev,
+ DL_FLAG_AUTOPROBE_CONSUMER);
+ put_device(&reset_pdev->dev);
+ if (!link) {
+ dev_notice(dev, "add reset device_link fail\n");
+ goto skip_reset;
+ }
+ /* supplier is not probed */
+ if (link->status == DL_STATE_DORMANT) {
+ err = -EPROBE_DEFER;
+ goto out;
+ }
+
+skip_reset:
+ /* find phy node */
+ phy_node = of_parse_phandle(dev->of_node, "phys", 0);
+
+ if (phy_node) {
+ phy_pdev = of_find_device_by_node(phy_node);
+ if (!phy_pdev)
+ goto skip_phy;
+ phy_dev = &phy_pdev->dev;
+
+ pm_runtime_set_active(phy_dev);
+ pm_runtime_enable(phy_dev);
+ pm_runtime_get_sync(phy_dev);
+
+ put_device(phy_dev);
+ dev_info(dev, "phys node found\n");
+ } else {
+ dev_notice(dev, "phys node not found\n");
+ }
+
+skip_phy:
+ /* perform generic probe */
+ err = ufshcd_pltfrm_init(pdev, &ufs_hba_mtk_vops);
+ if (err) {
+ dev_err(dev, "probe failed %d\n", err);
+ goto out;
+ }
+
+ hba = platform_get_drvdata(pdev);
+ if (!hba)
+ goto out;
+
+ if (phy_node && phy_dev) {
+ host = ufshcd_get_variant(hba);
+ host->phy_dev = phy_dev;
+ }
+
+ /*
+ * Because the default power setting of VSx (the upper layer of
+ * VCCQ/VCCQ2) is HWLP, we need to prevent VCCQ/VCCQ2 from
+ * entering LPM.
+ */
+ ufs_mtk_dev_vreg_set_lpm(hba, false);
+
+out:
+ of_node_put(phy_node);
+ of_node_put(reset_node);
+ return err;
+}
+
+/**
+ * ufs_mtk_remove - set driver_data of the device to NULL
+ * @pdev: pointer to platform device handle
+ *
+ * Always return 0
+ */
+static void ufs_mtk_remove(struct platform_device *pdev)
+{
+ ufshcd_pltfrm_remove(pdev);
+}
+
+static int ufs_mtk_system_suspend(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct arm_smccc_res res;
+ int ret;
+
+ if (hba->shutting_down) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = ufshcd_system_suspend(dev);
+ if (ret)
+ goto out;
+
+ if (pm_runtime_suspended(hba->dev))
+ goto out;
+
+ ufs_mtk_dev_vreg_set_lpm(hba, true);
+
+ if (ufs_mtk_is_rtff_mtcmos(hba))
+ ufs_mtk_mtcmos_ctrl(false, res);
+
+out:
+ return ret;
+}
+
+static int ufs_mtk_system_resume(struct device *dev)
+{
+ int ret = 0;
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct arm_smccc_res res;
+
+ if (pm_runtime_suspended(hba->dev))
+ goto out;
+
+ if (ufs_mtk_is_rtff_mtcmos(hba))
+ ufs_mtk_mtcmos_ctrl(true, res);
+
+ ufs_mtk_dev_vreg_set_lpm(hba, false);
+
+out:
+ ret = ufshcd_system_resume(dev);
+
+ return ret;
+}
+
+static int ufs_mtk_runtime_suspend(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct arm_smccc_res res;
+ int ret = 0;
+
+ ret = ufshcd_runtime_suspend(dev);
+ if (ret)
+ return ret;
+
+ ufs_mtk_dev_vreg_set_lpm(hba, true);
+
+ if (ufs_mtk_is_rtff_mtcmos(hba))
+ ufs_mtk_mtcmos_ctrl(false, res);
+
+ if (host->phy_dev)
+ pm_runtime_put_sync(host->phy_dev);
+
+ return 0;
+}
+
+static int ufs_mtk_runtime_resume(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+ struct arm_smccc_res res;
+
+ if (ufs_mtk_is_rtff_mtcmos(hba))
+ ufs_mtk_mtcmos_ctrl(true, res);
+
+ if (host->phy_dev)
+ pm_runtime_get_sync(host->phy_dev);
+
+ ufs_mtk_dev_vreg_set_lpm(hba, false);
+
+ return ufshcd_runtime_resume(dev);
+}
+
+static const struct dev_pm_ops ufs_mtk_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(ufs_mtk_system_suspend, ufs_mtk_system_resume)
+ RUNTIME_PM_OPS(ufs_mtk_runtime_suspend, ufs_mtk_runtime_resume, NULL)
+ .prepare = ufshcd_suspend_prepare,
+ .complete = ufshcd_resume_complete,
+};
+
+static struct platform_driver ufs_mtk_pltform = {
+ .probe = ufs_mtk_probe,
+ .remove = ufs_mtk_remove,
+ .driver = {
+ .name = "ufshcd-mtk",
+ .pm = pm_ptr(&ufs_mtk_pm_ops),
+ .of_match_table = ufs_mtk_of_match,
+ },
+};
+
+MODULE_AUTHOR("Stanley Chu <stanley.chu@mediatek.com>");
+MODULE_AUTHOR("Peter Wang <peter.wang@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek UFS Host Driver");
+MODULE_LICENSE("GPL v2");
+
+module_platform_driver(ufs_mtk_pltform);
diff --git a/drivers/ufs/host/ufs-mediatek.h b/drivers/ufs/host/ufs-mediatek.h
new file mode 100644
index 000000000..73cdc726f
--- /dev/null
+++ b/drivers/ufs/host/ufs-mediatek.h
@@ -0,0 +1,236 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 MediaTek Inc.
+ */
+
+#ifndef _UFS_MEDIATEK_H
+#define _UFS_MEDIATEK_H
+
+#include <linux/bitops.h>
+
+/*
+ * MCQ define and struct
+ */
+#define UFSHCD_MAX_Q_NR 8
+#define MTK_MCQ_INVALID_IRQ 0xFFFF
+
+/* REG_UFS_MMIO_OPT_CTRL_0 160h */
+#define EHS_EN BIT(0)
+#define PFM_IMPV BIT(1)
+#define MCQ_MULTI_INTR_EN BIT(2)
+#define MCQ_CMB_INTR_EN BIT(3)
+#define MCQ_AH8 BIT(4)
+#define MON_EN BIT(5)
+#define MRTT_EN BIT(25)
+#define RDN_PFM_IMPV_DIS BIT(28)
+
+#define MCQ_INTR_EN_MSK (MCQ_MULTI_INTR_EN | MCQ_CMB_INTR_EN)
+
+/*
+ * Vendor specific UFSHCI Registers
+ */
+#define REG_UFS_XOUFS_CTRL 0x140
+#define REG_UFS_REFCLK_CTRL 0x144
+#define REG_UFS_UFS_MMIO_OTSD_CTRL 0x14C
+#define REG_UFS_MMIO_OPT_CTRL_0 0x160
+#define REG_UFS_EXTREG 0x2100
+#define REG_UFS_MPHYCTRL 0x2200
+#define REG_UFS_MTK_IP_VER 0x2240
+#define REG_UFS_REJECT_MON 0x22AC
+#define REG_UFS_DEBUG_SEL 0x22C0
+#define REG_UFS_PROBE 0x22C8
+#define REG_UFS_DEBUG_SEL_B0 0x22D0
+#define REG_UFS_DEBUG_SEL_B1 0x22D4
+#define REG_UFS_DEBUG_SEL_B2 0x22D8
+#define REG_UFS_DEBUG_SEL_B3 0x22DC
+
+#define REG_UFS_MTK_SQD 0x2800
+#define REG_UFS_MTK_SQIS 0x2814
+#define REG_UFS_MTK_CQD 0x281C
+#define REG_UFS_MTK_CQIS 0x2824
+
+#define REG_UFS_MCQ_STRIDE 0x30
+
+/*
+ * Ref-clk control
+ *
+ * Values for register REG_UFS_REFCLK_CTRL
+ */
+#define REFCLK_RELEASE 0x0
+#define REFCLK_REQUEST BIT(0)
+#define REFCLK_ACK BIT(1)
+
+#define REFCLK_REQ_TIMEOUT_US 3000
+#define REFCLK_DEFAULT_WAIT_US 32
+
+/*
+ * Other attributes
+ */
+#define VS_DEBUGCLOCKENABLE 0xD0A1
+#define VS_SAVEPOWERCONTROL 0xD0A6
+#define VS_UNIPROPOWERDOWNCONTROL 0xD0A8
+
+/*
+ * Vendor specific link state
+ */
+enum {
+ VS_LINK_DISABLED = 0,
+ VS_LINK_DOWN = 1,
+ VS_LINK_UP = 2,
+ VS_LINK_HIBERN8 = 3,
+ VS_LINK_LOST = 4,
+ VS_LINK_CFG = 5,
+};
+
+/*
+ * Vendor specific host controller state
+ */
+enum {
+ VS_HCE_RESET = 0,
+ VS_HCE_BASE = 1,
+ VS_HCE_OOCPR_WAIT = 2,
+ VS_HCE_DME_RESET = 3,
+ VS_HCE_MIDDLE = 4,
+ VS_HCE_DME_ENABLE = 5,
+ VS_HCE_DEFAULTS = 6,
+ VS_HIB_IDLEEN = 7,
+ VS_HIB_ENTER = 8,
+ VS_HIB_ENTER_CONF = 9,
+ VS_HIB_MIDDLE = 10,
+ VS_HIB_WAITTIMER = 11,
+ VS_HIB_EXIT_CONF = 12,
+ VS_HIB_EXIT = 13,
+};
+
+/*
+ * VS_DEBUGCLOCKENABLE
+ */
+enum {
+ TX_SYMBOL_CLK_REQ_FORCE = 5,
+};
+
+/*
+ * VS_SAVEPOWERCONTROL
+ */
+enum {
+ RX_SYMBOL_CLK_GATE_EN = 0,
+ SYS_CLK_GATE_EN = 2,
+ TX_CLK_GATE_EN = 3,
+};
+
+/*
+ * Host capability
+ */
+enum ufs_mtk_host_caps {
+ UFS_MTK_CAP_BOOST_CRYPT_ENGINE = 1 << 0,
+ UFS_MTK_CAP_VA09_PWR_CTRL = 1 << 1,
+ UFS_MTK_CAP_DISABLE_AH8 = 1 << 2,
+ UFS_MTK_CAP_BROKEN_VCC = 1 << 3,
+
+ /*
+ * Override UFS_MTK_CAP_BROKEN_VCC's behavior to
+ * allow vccqx upstream to enter LPM
+ */
+ UFS_MTK_CAP_ALLOW_VCCQX_LPM = 1 << 5,
+ UFS_MTK_CAP_PMC_VIA_FASTAUTO = 1 << 6,
+ UFS_MTK_CAP_TX_SKEW_FIX = 1 << 7,
+ UFS_MTK_CAP_DISABLE_MCQ = 1 << 8,
+ /* Control MTCMOS with RTFF */
+ UFS_MTK_CAP_RTFF_MTCMOS = 1 << 9,
+
+ UFS_MTK_CAP_MCQ_BROKEN_RTC = 1 << 10,
+};
+
+struct ufs_mtk_crypt_cfg {
+ struct regulator *reg_vcore;
+ struct clk *clk_crypt_perf;
+ struct clk *clk_crypt_mux;
+ struct clk *clk_crypt_lp;
+ int vcore_volt;
+};
+
+struct ufs_mtk_clk {
+ struct ufs_clk_info *ufs_sel_clki; /* Mux */
+ struct ufs_clk_info *ufs_sel_max_clki; /* Max src */
+ struct ufs_clk_info *ufs_sel_min_clki; /* Min src */
+ struct ufs_clk_info *ufs_fde_clki; /* Mux */
+ struct ufs_clk_info *ufs_fde_max_clki; /* Max src */
+ struct ufs_clk_info *ufs_fde_min_clki; /* Min src */
+ struct regulator *reg_vcore;
+ int vcore_volt;
+};
+
+struct ufs_mtk_hw_ver {
+ u8 step;
+ u8 minor;
+ u8 major;
+};
+
+struct ufs_mtk_mcq_intr_info {
+ struct ufs_hba *hba;
+ u32 irq;
+ u8 qid;
+};
+
+struct ufs_mtk_host {
+ struct phy *mphy;
+ struct regulator *reg_va09;
+ struct reset_control *hci_reset;
+ struct reset_control *unipro_reset;
+ struct reset_control *crypto_reset;
+ struct reset_control *mphy_reset;
+ struct ufs_hba *hba;
+ struct ufs_mtk_crypt_cfg *crypt;
+ struct ufs_mtk_clk mclk;
+ struct ufs_mtk_hw_ver hw_ver;
+ enum ufs_mtk_host_caps caps;
+ bool mphy_powered_on;
+ bool unipro_lpm;
+ bool ref_clk_enabled;
+ bool clk_scale_up;
+ u16 ref_clk_ungating_wait_us;
+ u16 ref_clk_gating_wait_us;
+ u32 ip_ver;
+ bool legacy_ip_ver;
+
+ bool mcq_set_intr;
+ bool is_mcq_intr_enabled;
+ int mcq_nr_intr;
+ struct ufs_mtk_mcq_intr_info mcq_intr_info[UFSHCD_MAX_Q_NR];
+ struct device *phy_dev;
+};
+
+/* MTK delay of autosuspend: 500 ms */
+#define MTK_RPM_AUTOSUSPEND_DELAY_MS 500
+
+/* MTK RTT support number for platforms before MT6995 B0 */
+#define MTK_MAX_NUM_RTT_LEGACY 2
+
+/* UFSHCI MTK ip version value */
+enum {
+ /* UFSHCI 3.1 */
+ IP_VER_MT6983 = 0x10360000,
+ IP_VER_MT6878 = 0x10420200,
+
+ /* UFSHCI 4.0 */
+ IP_VER_MT6897 = 0x10440000,
+ IP_VER_MT6989 = 0x10450000,
+ IP_VER_MT6899 = 0x10450100,
+ IP_VER_MT6991_A0 = 0x10460000,
+ IP_VER_MT6991_B0 = 0x10470000,
+ IP_VER_MT6993 = 0x10480000,
+
+ /* UFSHCI 5.0 */
+ IP_VER_MT6995_A0 = 0x10490000,
+ IP_VER_MT6995_B0 = 0x10500000,
+
+ IP_VER_NONE = 0xFFFFFFFF
+};
+
+enum ip_ver_legacy {
+ IP_LEGACY_VER_MT6781 = 0x10380000,
+ IP_LEGACY_VER_MT6879 = 0x10360000,
+ IP_LEGACY_VER_MT6893 = 0x20160706
+};
+
+#endif /* !_UFS_MEDIATEK_H */
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
new file mode 100644
index 000000000..954be9343
--- /dev/null
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -0,0 +1,3056 @@
+// 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/pm_domain.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
+
+#define UFS_ICE_SYNC_RST_SEL BIT(3)
+#define UFS_ICE_SYNC_RST_SW BIT(4)
+
+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 (IS_ERR(ice)) {
+ if (ice != ERR_PTR(-EOPNOTSUPP))
+ return PTR_ERR(ice);
+
+ dev_warn(dev, "Disabling inline encryption support\n");
+ return 0;
+ }
+
+ 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 && host->phy_gear == UFS_HS_G5)
+ host_params->hs_rate = PA_HS_MODE_A;
+
+ 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;
+}
+
+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
+ *
+ * @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 void ufs_qcom_link_startup_post_change(struct ufs_hba *hba)
+{
+ if (ufshcd_is_auto_hibern8_supported(hba))
+ ufshcd_rmwl(hba, UFS_HW_CLK_CTRL_EN, UFS_HW_CLK_CTRL_EN,
+ UFS_AH8_CFG);
+}
+
+static int ufs_qcom_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ 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);
+
+ /*
+ * Restore HS/LS link startup mode set by bootloader
+ * after UFS reset clears REG_UFS_DEBUG_SPARE_CFG.
+ */
+ if (host->hw_ver.major > 0x6 ||
+ (host->hw_ver.major == 0x6 && host->hw_ver.minor >= 0x2))
+ ufshcd_writel(hba, host->boot_spare_cfg,
+ REG_UFS_DEBUG_SPARE_CFG);
+ break;
+ case POST_CHANGE:
+ ufs_qcom_link_startup_post_change(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);
+ /*
+ * After sending the SSU command, asserting the rst_n
+ * line causes the device firmware to wake up and
+ * execute its reset routine.
+ *
+ * During this process, the device may draw current
+ * beyond the permissible limit for low-power mode (LPM).
+ * A 10ms delay, based on experimental observations,
+ * allows the UFS device to complete its hardware reset
+ * before transitioning the power rail to LPM.
+ */
+ usleep_range(10000, 11000);
+ }
+
+ 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;
+ u32 reg_val;
+
+ err = ufs_qcom_enable_lane_clks(host);
+ if (err)
+ return err;
+
+ if ((!ufs_qcom_is_link_active(hba)) &&
+ host->hw_ver.major == 5 &&
+ host->hw_ver.minor == 0 &&
+ host->hw_ver.step == 0) {
+ ufshcd_writel(hba, UFS_ICE_SYNC_RST_SEL | UFS_ICE_SYNC_RST_SW, UFS_MEM_ICE_CFG);
+ reg_val = ufshcd_readl(hba, UFS_MEM_ICE_CFG);
+ reg_val &= ~(UFS_ICE_SYNC_RST_SEL | UFS_ICE_SYNC_RST_SW);
+ /*
+ * HW documentation doesn't recommend any delay between the
+ * reset set and clear. But we are enforcing an arbitrary delay
+ * to give flops enough time to settle in.
+ */
+ usleep_range(50, 100);
+ ufshcd_writel(hba, reg_val, UFS_MEM_ICE_CFG);
+ ufshcd_readl(hba, UFS_MEM_ICE_CFG);
+ }
+
+ 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 &&
+ (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_negotiate_pwr_mode(struct ufs_hba *hba,
+ 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;
+
+ return ufshcd_negotiate_pwr_params(host_params, dev_max_params, dev_req_params);
+}
+
+static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ 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:
+ /*
+ * 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);
+}
+
+/**
+ * ufs_qcom_double_t_adapt_l0l1l2l3 - Create a new adapt that doubles the
+ * adaptation duration TADAPT_L0_L1_L2_L3 derived from the old adapt.
+ *
+ * @old_adapt: Original ADAPT_L0_L1_L2_L3 capability
+ *
+ * ADAPT_length_L0_L1_L2_L3 formula from M-PHY spec:
+ * if (ADAPT_range_L0_L1_L2_L3 == COARSE) {
+ * ADAPT_length_L0_L1_L2_L3 = [0, 12]
+ * ADAPT_L0_L1_L2_L3 = 215 x 2^ADAPT_length_L0_L1_L2_L3
+ * } else if (ADAPT_range_L0_L1_L2_L3 == FINE) {
+ * ADAPT_length_L0_L1_L2_L3 = [0, 127]
+ * TADAPT_L0_L1_L2_L3 = 215 x (ADAPT_length_L0_L1_L2_L3 + 1)
+ * }
+ *
+ * To double the adaptation duration TADAPT_L0_L1_L2_L3:
+ * 1. If adapt range is COARSE (1'b1), new adapt = old adapt + 1.
+ * 2. If adapt range is FINE (1'b0):
+ * a) If old adapt length is < 64, (new adapt + 1) = 2 * (old adapt + 1).
+ * b) If old adapt length is >= 64, set new adapt to 0x88 using COARSE
+ * range, because new adapt get from equation in a) shall exceed 127.
+ *
+ * Examples:
+ * ADAPT_range_L0_L1_L2_L3 | ADAPT_length_L0_L1_L2_L3 | TADAPT_L0_L1_L2_L3 (PAM-4 UI)
+ * 0 3 131072
+ * 0 7 262144
+ * 0 63 2097152
+ * 0 64 2129920
+ * 0 127 4194304
+ * 1 8 8388608
+ * 1 9 16777216
+ * 1 10 33554432
+ * 1 11 67108864
+ * 1 12 134217728
+ *
+ * Return: new adapt.
+ */
+static u32 ufs_qcom_double_t_adapt_l0l1l2l3(u32 old_adapt)
+{
+ u32 adapt_length = old_adapt & ADAPT_LENGTH_MASK;
+ u32 new_adapt;
+
+ if (IS_ADAPT_RANGE_COARSE(old_adapt)) {
+ new_adapt = (adapt_length + 1) | ADAPT_RANGE_BIT;
+ } else {
+ if (adapt_length < 64)
+ new_adapt = (adapt_length << 1) + 1;
+ else
+ /*
+ * 0x88 is the very coarse Adapt value which is two
+ * times of the largest fine Adapt value (0x7F)
+ */
+ new_adapt = 0x88;
+ }
+
+ return new_adapt;
+}
+
+static void ufs_qcom_limit_max_gear(struct ufs_hba *hba,
+ enum ufs_hs_gear_tag gear)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
+ struct ufs_host_params *host_params = &host->host_params;
+
+ host_params->hs_tx_gear = gear;
+ host_params->hs_rx_gear = gear;
+ pwr_info->gear_tx = gear;
+ pwr_info->gear_rx = gear;
+
+ dev_warn(hba->dev, "Limited max gear of host and device to HS-G%d\n", gear);
+}
+
+static void ufs_qcom_fixup_tx_adapt_l0l1l2l3(struct ufs_hba *hba)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
+ struct ufs_host_params *host_params = &host->host_params;
+ u32 old_adapt, new_adapt, actual_adapt;
+ bool limit_speed = false;
+ int err;
+
+ if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1 ||
+ host_params->hs_tx_gear <= UFS_HS_G5 ||
+ pwr_info->gear_tx <= UFS_HS_G5)
+ return;
+
+ err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3), &old_adapt);
+ if (err)
+ goto out;
+
+ if (old_adapt > ADAPT_L0L1L2L3_LENGTH_MAX) {
+ dev_err(hba->dev, "PA_PeerRxHsG6AdaptInitialL0L1L2L3 value (0x%x) exceeds MAX\n",
+ old_adapt);
+ err = -ERANGE;
+ goto out;
+ }
+
+ new_adapt = ufs_qcom_double_t_adapt_l0l1l2l3(old_adapt);
+ dev_dbg(hba->dev, "Original PA_PeerRxHsG6AdaptInitialL0L1L2L3 = 0x%x, new value = 0x%x\n",
+ old_adapt, new_adapt);
+
+ /*
+ * 0x8C is the max possible value allowed by UniPro v3.0 spec, some HWs
+ * can accept 0x8D but some cannot.
+ */
+ if (new_adapt <= ADAPT_L0L1L2L3_LENGTH_MAX ||
+ (new_adapt == ADAPT_L0L1L2L3_LENGTH_MAX + 1 && host->hw_ver.minor == 0x1)) {
+ err = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3),
+ new_adapt);
+ if (err)
+ goto out;
+
+ err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3),
+ &actual_adapt);
+ if (err)
+ goto out;
+
+ if (actual_adapt != new_adapt) {
+ limit_speed = true;
+ dev_warn(hba->dev, "PA_PeerRxHsG6AdaptInitialL0L1L2L3 0x%x, expect 0x%x\n",
+ actual_adapt, new_adapt);
+ }
+ } else {
+ limit_speed = true;
+ dev_warn(hba->dev, "New PA_PeerRxHsG6AdaptInitialL0L1L2L3 (0x%x) is too large!\n",
+ new_adapt);
+ }
+
+ err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3), &old_adapt);
+ if (err)
+ goto out;
+
+ if (old_adapt > ADAPT_L0L1L2L3_LENGTH_MAX) {
+ dev_err(hba->dev, "PA_PeerRxHsG6AdaptRefreshL0L1L2L3 value (0x%x) exceeds MAX\n",
+ old_adapt);
+ err = -ERANGE;
+ goto out;
+ }
+
+ new_adapt = ufs_qcom_double_t_adapt_l0l1l2l3(old_adapt);
+ dev_dbg(hba->dev, "Original PA_PeerRxHsG6AdaptRefreshL0L1L2L3 = 0x%x, new value = 0x%x\n",
+ old_adapt, new_adapt);
+
+ /*
+ * 0x8C is the max possible value allowed by UniPro v3.0 spec, some HWs
+ * can accept 0x8D but some cannot.
+ */
+ if (new_adapt <= ADAPT_L0L1L2L3_LENGTH_MAX ||
+ (new_adapt == ADAPT_L0L1L2L3_LENGTH_MAX + 1 && host->hw_ver.minor == 0x1)) {
+ err = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3),
+ new_adapt);
+ if (err)
+ goto out;
+
+ err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3),
+ &actual_adapt);
+ if (err)
+ goto out;
+
+ if (actual_adapt != new_adapt) {
+ limit_speed = true;
+ dev_warn(hba->dev, "PA_PeerRxHsG6AdaptRefreshL0L1L2L3 0x%x, expect 0x%x\n",
+ new_adapt, actual_adapt);
+ }
+ } else {
+ limit_speed = true;
+ dev_warn(hba->dev, "New PA_PeerRxHsG6AdaptRefreshL0L1L2L3 (0x%x) is too large!\n",
+ new_adapt);
+ }
+
+out:
+ if (limit_speed || err)
+ ufs_qcom_limit_max_gear(hba, UFS_HS_G5);
+}
+
+static int ufs_qcom_apply_dev_quirks(struct ufs_hba *hba)
+{
+ int err = 0;
+
+ ufs_qcom_fixup_tx_adapt_l0l1l2l3(hba);
+
+ 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_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 (host->hw_ver.major == 0x7 && host->hw_ver.minor == 0x1)
+ hba->quirks |= UFSHCD_QUIRK_EXTENDED_TX_EQTR_ADAPT_LENGTH_L0L1L2L3;
+
+ 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 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) {
+ host->boot_spare_cfg = ufshcd_readl(host->hba, REG_UFS_DEBUG_SPARE_CFG);
+ dev_major = FIELD_GET(UFS_DEV_VER_MAJOR_MASK, host->boot_spare_cfg);
+
+ /*
+ * 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_parse_gear_limits(struct ufs_hba *hba)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_host_params *host_params = &host->host_params;
+ u32 hs_gear_old = host_params->hs_tx_gear;
+
+ ufshcd_parse_gear_limits(hba, host_params);
+ if (host_params->hs_tx_gear != hs_gear_old) {
+ host->phy_gear = host_params->hs_tx_gear;
+ }
+}
+
+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)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(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;
+
+ if (host->hw_ver.major >= 0x7)
+ hba->caps |= UFSHCD_CAP_TX_EQUALIZATION;
+
+ 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);
+ if (ufs_qcom_is_link_hibern8(hba)) {
+ err = ufs_qcom_enable_lane_clks(host);
+ if (err) {
+ dev_err(hba->dev, "enable lane clks failed, ret=%d\n", err);
+ return err;
+ }
+ }
+ } 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 {
+ if (ufs_qcom_is_link_hibern8(hba))
+ ufs_qcom_disable_lane_clks(host);
+
+ 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);
+ ufs_qcom_parse_gear_limits(hba);
+
+ 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);
+}
+
+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
+ *
+ * @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), &reg);
+ 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;
+ char *prefix;
+
+ u32 *testbus __free(kfree) = 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, void __iomem *base)
+{
+ size_t pos;
+
+ if (offset % 4 != 0 || len % 4 != 0)
+ return -EINVAL;
+
+ u32 *regs __free(kfree) = kzalloc(len, GFP_ATOMIC);
+ if (!regs)
+ return -ENOMEM;
+
+ for (pos = 0; pos < len; pos += 4)
+ regs[pos / 4] = readl(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 ufshcd_mcq_opr_info_t *opr = &hba->mcq_opr[0];
+ void __iomem *mcq_vs_base = hba->mcq_base + UFS_MEM_VS_BASE;
+
+ struct dump_info {
+ void __iomem *base;
+ size_t offset;
+ size_t len;
+ const char *prefix;
+ };
+
+ struct dump_info mcq_dumps[] = {
+ {hba->mcq_base, 0x0, 256 * 4, "MCQ HCI-0 "},
+ {hba->mcq_base, 0x400, 256 * 4, "MCQ HCI-1 "},
+ {mcq_vs_base, 0x0, 5 * 4, "MCQ VS-0 "},
+ {opr->base, 0x0, 256 * 4, "MCQ SQD-0 "},
+ {opr->base, 0x400, 256 * 4, "MCQ SQD-1 "},
+ {opr->base, 0x800, 256 * 4, "MCQ SQD-2 "},
+ {opr->base, 0xc00, 256 * 4, "MCQ SQD-3 "},
+ {opr->base, 0x1000, 256 * 4, "MCQ SQD-4 "},
+ {opr->base, 0x1400, 256 * 4, "MCQ SQD-5 "},
+ {opr->base, 0x1800, 256 * 4, "MCQ SQD-6 "},
+ {opr->base, 0x1c00, 256 * 4, "MCQ SQD-7 "},
+
+ };
+
+ 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].base);
+ 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;
+}
+
+/**
+ * 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)
+{
+ p->polling_ms = 60;
+ p->timer = DEVFREQ_TIMER_DELAYED;
+ d->upthreshold = 70;
+ d->downdifferential = 65;
+
+ hba->clk_scaling.suspend_on_no_request = true;
+}
+
+static int ufs_qcom_mcq_config_resource(struct ufs_hba *hba)
+{
+ struct platform_device *pdev = to_platform_device(hba->dev);
+ struct resource *res;
+
+ /* Map the MCQ configuration region */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mcq");
+ if (!res) {
+ dev_err(hba->dev, "MCQ resource not found in device tree\n");
+ return -ENODEV;
+ }
+
+ hba->mcq_base = devm_ioremap_resource(hba->dev, res);
+ if (IS_ERR(hba->mcq_base)) {
+ dev_err(hba->dev, "Failed to map MCQ region: %ld\n",
+ PTR_ERR(hba->mcq_base));
+ return PTR_ERR(hba->mcq_base);
+ }
+
+ return 0;
+}
+
+static int ufs_qcom_op_runtime_config(struct ufs_hba *hba)
+{
+ struct ufshcd_mcq_opr_info_t *opr;
+ int i;
+ u32 doorbell_offsets[OPR_MAX];
+
+ /*
+ * Configure doorbell address offsets in MCQ configuration registers.
+ * These values are offsets relative to mmio_base (UFS_HCI_BASE).
+ *
+ * Memory Layout:
+ * - mmio_base = UFS_HCI_BASE
+ * - mcq_base = MCQ_CONFIG_BASE = mmio_base + (UFS_QCOM_MCQCAP_QCFGPTR * 0x200)
+ * - Doorbell registers are at: mmio_base + (UFS_QCOM_MCQCAP_QCFGPTR * 0x200) +
+ * - UFS_QCOM_MCQ_SQD_OFFSET
+ * - Which is also: mcq_base + UFS_QCOM_MCQ_SQD_OFFSET
+ */
+
+ doorbell_offsets[OPR_SQD] = UFS_QCOM_SQD_ADDR_OFFSET;
+ doorbell_offsets[OPR_SQIS] = UFS_QCOM_SQIS_ADDR_OFFSET;
+ doorbell_offsets[OPR_CQD] = UFS_QCOM_CQD_ADDR_OFFSET;
+ doorbell_offsets[OPR_CQIS] = UFS_QCOM_CQIS_ADDR_OFFSET;
+
+ /*
+ * Configure MCQ operation registers.
+ *
+ * The doorbell registers are physically located within the MCQ region:
+ * - doorbell_physical_addr = mmio_base + doorbell_offset
+ * - doorbell_physical_addr = mcq_base + (doorbell_offset - MCQ_CONFIG_OFFSET)
+ */
+ for (i = 0; i < OPR_MAX; i++) {
+ opr = &hba->mcq_opr[i];
+ opr->offset = doorbell_offsets[i]; /* Offset relative to mmio_base */
+ opr->stride = UFS_QCOM_MCQ_STRIDE; /* 256 bytes between queues */
+
+ /*
+ * Calculate the actual doorbell base address within MCQ region:
+ * base = mcq_base + (doorbell_offset - MCQ_CONFIG_OFFSET)
+ */
+ opr->base = hba->mcq_base + (opr->offset - UFS_QCOM_MCQ_CONFIG_OFFSET);
+ }
+
+ 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)
+{
+ /* Read from MCQ vendor-specific register in MCQ region */
+ *ocqs = readl(hba->mcq_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 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];
+
+ ret = platform_device_msi_init_and_alloc_irqs(hba->dev, nr_irqs,
+ ufs_qcom_write_msi_msg);
+ if (ret) {
+ dev_warn(hba->dev, "Platform MSI not supported or failed, continuing without ESI\n");
+ return ret; /* Continue without ESI */
+ }
+
+ struct ufs_qcom_irq *qi = devm_kcalloc(hba->dev, nr_irqs, sizeof(*qi), GFP_KERNEL);
+
+ if (!qi) {
+ platform_device_msi_free_irqs_all(hba->dev);
+ return -ENOMEM;
+ }
+
+ 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) {
+ /* Free previously allocated IRQs */
+ for (int j = 0; j < idx; j++)
+ devm_free_irq(hba->dev, qi[j].irq, qi + j);
+ platform_device_msi_free_irqs_all(hba->dev);
+ devm_kfree(hba->dev, qi);
+ return ret;
+ }
+ }
+
+ 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);
+}
+
+static int ufs_qcom_host_eom_config(struct ufs_hba *hba, int lane,
+ const struct ufs_eom_coord *eom_coord,
+ u32 target_test_count)
+{
+ enum ufs_eom_eye_mask eye_mask = eom_coord->eye_mask;
+ int v_step = eom_coord->v_step;
+ int t_step = eom_coord->t_step;
+ u32 volt_step, timing_step;
+ int ret;
+
+ if (abs(v_step) > UFS_QCOM_EOM_VOLTAGE_STEPS_MAX) {
+ dev_err(hba->dev, "Invalid EOM Voltage Step: %d\n", v_step);
+ return -ERANGE;
+ }
+
+ if (abs(t_step) > UFS_QCOM_EOM_TIMING_STEPS_MAX) {
+ dev_err(hba->dev, "Invalid EOM Timing Step: %d\n", t_step);
+ return -ERANGE;
+ }
+
+ if (v_step < 0)
+ volt_step = RX_EYEMON_NEGATIVE_STEP_BIT | (u32)(-v_step);
+ else
+ volt_step = (u32)v_step;
+
+ if (t_step < 0)
+ timing_step = RX_EYEMON_NEGATIVE_STEP_BIT | (u32)(-t_step);
+ else
+ timing_step = (u32)t_step;
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_ENABLE,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ BIT(eye_mask) | RX_EYEMON_EXTENDED_VRANGE_BIT);
+ if (ret) {
+ dev_err(hba->dev, "Failed to enable Host EOM on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_TIMING_STEPS,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ timing_step);
+ if (ret) {
+ dev_err(hba->dev, "Failed to set Host EOM timing step on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_VOLTAGE_STEPS,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ volt_step);
+ if (ret) {
+ dev_err(hba->dev, "Failed to set Host EOM voltage step on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_TARGET_TEST_COUNT,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ target_test_count);
+ if (ret)
+ dev_err(hba->dev, "Failed to set Host EOM target test count on Lane %d: %d\n",
+ lane, ret);
+
+ return ret;
+}
+
+static int ufs_qcom_host_eom_may_stop(struct ufs_hba *hba, int lane,
+ u32 target_test_count, u32 *err_count)
+{
+ u32 start, tested_count, error_count;
+ int ret;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_EYEMON_START,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ &start);
+ if (ret) {
+ dev_err(hba->dev, "Failed to get Host EOM start status on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+
+ if (start & 0x1)
+ return -EAGAIN;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_EYEMON_TESTED_COUNT,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ &tested_count);
+ if (ret) {
+ dev_err(hba->dev, "Failed to get Host EOM tested count on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_EYEMON_ERROR_COUNT,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ &error_count);
+ if (ret) {
+ dev_err(hba->dev, "Failed to get Host EOM error count on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+
+ /* EOM can stop */
+ if ((tested_count >= target_test_count - 3) || error_count > 0) {
+ *err_count = error_count;
+
+ /* Disable EOM */
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_ENABLE,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ 0x0);
+ if (ret) {
+ dev_err(hba->dev, "Failed to disable Host EOM on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+ } else {
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static int ufs_qcom_host_eom_scan(struct ufs_hba *hba, int num_lanes,
+ const struct ufs_eom_coord *eom_coord,
+ u32 target_test_count, u32 *err_count)
+{
+ bool eom_stopped[PA_MAXDATALANES] = { 0 };
+ int lane, ret;
+ u32 setting;
+
+ if (!err_count || !eom_coord)
+ return -EINVAL;
+
+ if (target_test_count < UFS_QCOM_EOM_TARGET_TEST_COUNT_MIN) {
+ dev_err(hba->dev, "Target test count (%u) too small for Host EOM\n",
+ target_test_count);
+ return -ERANGE;
+ }
+
+ for (lane = 0; lane < num_lanes; lane++) {
+ ret = ufs_qcom_host_eom_config(hba, lane, eom_coord,
+ target_test_count);
+ if (ret) {
+ dev_err(hba->dev, "Failed to config Host RX EOM: %d\n", ret);
+ return ret;
+ }
+ }
+
+ /*
+ * Trigger a PACP_PWR_req to kick start EOM, but not to really change
+ * the Power Mode.
+ */
+ ret = ufshcd_uic_change_pwr_mode(hba, FAST_MODE << 4 | FAST_MODE);
+ if (ret) {
+ dev_err(hba->dev, "Failed to change power mode to kick start Host EOM: %d\n",
+ ret);
+ return ret;
+ }
+
+more_burst:
+ /* Create burst on Host RX Lane. */
+ ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_LOCALVERINFO), &setting);
+
+ for (lane = 0; lane < num_lanes; lane++) {
+ if (eom_stopped[lane])
+ continue;
+
+ ret = ufs_qcom_host_eom_may_stop(hba, lane, target_test_count,
+ &err_count[lane]);
+ if (!ret) {
+ eom_stopped[lane] = true;
+ } else if (ret == -EAGAIN) {
+ /* Need more burst to excercise EOM */
+ goto more_burst;
+ } else {
+ dev_err(hba->dev, "Failed to stop Host EOM: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(hba->dev, "Host RX Lane %d EOM, v_step %d, t_step %d, error count %u\n",
+ lane, eom_coord->v_step, eom_coord->t_step,
+ err_count[lane]);
+ }
+
+ return 0;
+}
+
+static int ufs_qcom_host_sw_rx_fom(struct ufs_hba *hba, int num_lanes, u32 *fom)
+{
+ const struct ufs_eom_coord *eom_coord = sw_rx_fom_eom_coords_g6;
+ u32 eom_err_count[PA_MAXDATALANES] = { 0 };
+ u32 curr_ahit;
+ int lane, i, ret;
+
+ if (!fom)
+ return -EINVAL;
+
+ /* Stop the auto hibernate idle timer */
+ curr_ahit = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER);
+ if (curr_ahit)
+ ufshcd_writel(hba, 0, REG_AUTO_HIBERNATE_IDLE_TIMER);
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXHSADAPTTYPE), PA_NO_ADAPT);
+ if (ret) {
+ dev_err(hba->dev, "Failed to select NO_ADAPT before starting Host EOM: %d\n", ret);
+ goto out;
+ }
+
+ for (i = 0; i < SW_RX_FOM_EOM_COORDS; i++, eom_coord++) {
+ ret = ufs_qcom_host_eom_scan(hba, num_lanes, eom_coord,
+ UFS_QCOM_EOM_TARGET_TEST_COUNT_G6,
+ eom_err_count);
+ if (ret) {
+ dev_err(hba->dev, "Failed to run Host EOM scan: %d\n", ret);
+ break;
+ }
+
+ for (lane = 0; lane < num_lanes; lane++) {
+ /* Bad coordinates have no weights */
+ if (eom_err_count[lane])
+ continue;
+ fom[lane] += SW_RX_FOM_EOM_COORDS_WEIGHT;
+ }
+ }
+
+out:
+ /* Restore the auto hibernate idle timer */
+ if (curr_ahit)
+ ufshcd_writel(hba, curr_ahit, REG_AUTO_HIBERNATE_IDLE_TIMER);
+
+ return ret;
+}
+
+static int ufs_qcom_get_rx_fom(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ struct ufshcd_tx_eq_params *params __free(kfree) =
+ kzalloc_obj(*params);
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_pa_layer_attr old_pwr_info;
+ u32 fom[PA_MAXDATALANES] = { 0 };
+ u32 gear = pwr_mode->gear_tx;
+ u32 rate = pwr_mode->hs_rate;
+ int lane, ret;
+
+ if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1 ||
+ gear <= UFS_HS_G5 || !d_iter || !d_iter->is_updated)
+ return 0;
+
+ if (gear < UFS_HS_G1 || gear > UFS_HS_GEAR_MAX)
+ return -ERANGE;
+
+ if (!params)
+ return -ENOMEM;
+
+ memcpy(&old_pwr_info, &hba->pwr_info, sizeof(struct ufs_pa_layer_attr));
+
+ memcpy(params, &hba->tx_eq_params[gear - 1], sizeof(struct ufshcd_tx_eq_params));
+ for (lane = 0; lane < pwr_mode->lane_rx; lane++) {
+ params->device[lane].preshoot = d_iter->preshoot;
+ params->device[lane].deemphasis = d_iter->deemphasis;
+ }
+
+ /* Use TX EQTR settings as Device's TX Equalization settings. */
+ ret = ufshcd_apply_tx_eq_settings(hba, params, gear);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed to apply TX EQ settings for HS-G%u: %d\n",
+ __func__, gear, ret);
+ goto link_recover_and_restore;
+ }
+
+ /* Force PMC to target HS Gear to use new TX Equalization settings. */
+ ret = ufshcd_change_power_mode(hba, pwr_mode, UFSHCD_PMC_POLICY_FORCE);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed to change power mode to HS-G%u, Rate-%s: %d\n",
+ __func__, gear, ufs_hs_rate_to_str(rate), ret);
+ goto link_recover_and_restore;
+ }
+
+ ret = ufs_qcom_host_sw_rx_fom(hba, pwr_mode->lane_rx, fom);
+ if (ret)
+ dev_err(hba->dev, "Failed to get SW FOM of TX (PreShoot: %u, DeEmphasis: %u): %d\n",
+ d_iter->preshoot, d_iter->deemphasis, ret);
+
+link_recover_and_restore:
+ /* Restore Device's TX Equalization settings. */
+ ret = ufshcd_apply_tx_eq_settings(hba, &hba->tx_eq_params[gear - 1], gear);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed to apply TX EQ settings for HS-G%u: %d\n",
+ __func__, gear, ret);
+ return ret;
+ }
+
+ /* Restore Power Mode. */
+ ret = ufshcd_change_power_mode(hba, &old_pwr_info, UFSHCD_PMC_POLICY_FORCE);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed to restore power mode to HS-G%u: %d\n",
+ __func__, old_pwr_info.gear_tx, ret);
+ return ret;
+ }
+
+ for (lane = 0; lane < pwr_mode->lane_rx; lane++)
+ d_iter->fom[lane] = fom[lane];
+
+ return 0;
+}
+
+static int ufs_qcom_apply_tx_eqtr_settings(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ u32 setting = 0;
+ int lane;
+
+ if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1)
+ return 0;
+
+ for (lane = 0; lane < pwr_mode->lane_tx; lane++) {
+ setting |= TX_HS_PRESHOOT_BITS(lane, h_iter->preshoot);
+ setting |= TX_HS_DEEMPHASIS_BITS(lane, h_iter->deemphasis);
+ }
+
+ return ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXEQG1SETTING), setting);
+}
+
+static int ufs_qcom_tx_eqtr_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_pa_layer_attr pwr_mode_hs_g1 = {
+ .gear_rx = UFS_HS_G1,
+ .gear_tx = UFS_HS_G1,
+ .lane_rx = pwr_mode->lane_rx,
+ .lane_tx = pwr_mode->lane_tx,
+ .pwr_rx = FAST_MODE,
+ .pwr_tx = FAST_MODE,
+ .hs_rate = pwr_mode->hs_rate,
+ };
+ u32 gear = pwr_mode->gear_tx;
+ u32 rate = pwr_mode->hs_rate;
+ int ret;
+
+ if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1)
+ return 0;
+
+ if (status == PRE_CHANGE) {
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_TXEQG1SETTING),
+ &host->saved_tx_eq_g1_setting);
+ if (ret)
+ return ret;
+
+ /* PMC to target HS Gear. */
+ ret = ufshcd_change_power_mode(hba, pwr_mode,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+ if (ret)
+ dev_err(hba->dev, "%s: Failed to PMC to target HS-G%u, Rate-%s: %d\n",
+ __func__, gear, ufs_hs_rate_to_str(rate), ret);
+ } else {
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXEQG1SETTING),
+ host->saved_tx_eq_g1_setting);
+ if (ret)
+ return ret;
+
+ /* PMC back to HS-G1. */
+ ret = ufshcd_change_power_mode(hba, &pwr_mode_hs_g1,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+ if (ret)
+ dev_err(hba->dev, "%s: Failed to PMC to HS-G1, Rate-%s: %d\n",
+ __func__, ufs_hs_rate_to_str(rate), ret);
+ }
+
+ return ret;
+}
+
+/*
+ * 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,
+ .negotiate_pwr_mode = ufs_qcom_negotiate_pwr_mode,
+ .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,
+ .get_rx_fom = ufs_qcom_get_rx_fom,
+ .apply_tx_eqtr_settings = ufs_qcom_apply_tx_eqtr_settings,
+ .tx_eqtr_notify = ufs_qcom_tx_eqtr_notify,
+};
+
+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
+ *
+ * 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;
+ 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, 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 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);
+
+#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");
diff --git a/drivers/ufs/host/ufs-qcom.h b/drivers/ufs/host/ufs-qcom.h
new file mode 100644
index 000000000..a5ad5ce44
--- /dev/null
+++ b/drivers/ufs/host/ufs-qcom.h
@@ -0,0 +1,389 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef UFS_QCOM_H_
+#define UFS_QCOM_H_
+
+#include <linux/reset-controller.h>
+#include <linux/reset.h>
+#include <soc/qcom/ice.h>
+#include <ufs/ufshcd.h>
+
+#define MPHY_TX_FSM_STATE 0x41
+#define TX_FSM_HIBERN8 0x1
+#define HBRN8_POLL_TOUT_MS 100
+#define DEFAULT_CLK_RATE_HZ 1000000
+#define MAX_SUPP_MAC 64
+#define MAX_ESI_VEC 32
+
+#define UFS_HW_VER_MAJOR_MASK GENMASK(31, 28)
+#define UFS_HW_VER_MINOR_MASK GENMASK(27, 16)
+#define UFS_HW_VER_STEP_MASK GENMASK(15, 0)
+#define UFS_DEV_VER_MAJOR_MASK GENMASK(7, 4)
+
+#define UFS_QCOM_LIMIT_HS_RATE PA_HS_MODE_B
+
+/* bit and mask definitions for PA_VS_CLK_CFG_REG attribute */
+#define PA_VS_CLK_CFG_REG 0x9004
+#define PA_VS_CLK_CFG_REG_MASK GENMASK(8, 0)
+
+/* bit and mask definitions for DL_VS_CLK_CFG attribute */
+#define DL_VS_CLK_CFG 0xA00B
+#define DL_VS_CLK_CFG_MASK GENMASK(9, 0)
+#define DME_VS_CORE_CLK_CTRL_DME_HW_CGC_EN BIT(9)
+
+#define UFS_QCOM_EOM_VOLTAGE_STEPS_MAX 127
+#define UFS_QCOM_EOM_TIMING_STEPS_MAX 63
+#define UFS_QCOM_EOM_TARGET_TEST_COUNT_MIN 8
+#define UFS_QCOM_EOM_TARGET_TEST_COUNT_G6 0x3F
+
+#define SW_RX_FOM_EOM_COORDS 23
+#define SW_RX_FOM_EOM_COORDS_WEIGHT (127 / SW_RX_FOM_EOM_COORDS)
+
+struct ufs_eom_coord {
+ int t_step;
+ int v_step;
+ u8 eye_mask;
+};
+
+static const struct ufs_eom_coord sw_rx_fom_eom_coords_g6[SW_RX_FOM_EOM_COORDS] = {
+ [0] = { -2, -15, UFS_EOM_EYE_MASK_M },
+ [1] = { 0, -15, UFS_EOM_EYE_MASK_M },
+ [2] = { 2, -15, UFS_EOM_EYE_MASK_M },
+ [3] = { -4, -10, UFS_EOM_EYE_MASK_M },
+ [4] = { -2, -10, UFS_EOM_EYE_MASK_M },
+ [5] = { 0, -10, UFS_EOM_EYE_MASK_M },
+ [6] = { 2, -10, UFS_EOM_EYE_MASK_M },
+ [7] = { 4, -10, UFS_EOM_EYE_MASK_M },
+ [8] = { -6, 0, UFS_EOM_EYE_MASK_M },
+ [9] = { -4, 0, UFS_EOM_EYE_MASK_M },
+ [10] = { -2, 0, UFS_EOM_EYE_MASK_M },
+ [11] = { 0, 0, UFS_EOM_EYE_MASK_M },
+ [12] = { 2, 0, UFS_EOM_EYE_MASK_M },
+ [13] = { 4, 0, UFS_EOM_EYE_MASK_M },
+ [14] = { 6, 0, UFS_EOM_EYE_MASK_M },
+ [15] = { -4, 10, UFS_EOM_EYE_MASK_M },
+ [16] = { -2, 10, UFS_EOM_EYE_MASK_M },
+ [17] = { 0, 10, UFS_EOM_EYE_MASK_M },
+ [18] = { 2, 10, UFS_EOM_EYE_MASK_M },
+ [19] = { 4, 10, UFS_EOM_EYE_MASK_M },
+ [20] = { -2, 15, UFS_EOM_EYE_MASK_M },
+ [21] = { 0, 15, UFS_EOM_EYE_MASK_M },
+ [22] = { 2, 15, UFS_EOM_EYE_MASK_M },
+};
+
+/* Qualcomm MCQ Configuration */
+#define UFS_QCOM_MCQCAP_QCFGPTR 224 /* 0xE0 in hex */
+#define UFS_QCOM_MCQ_CONFIG_OFFSET (UFS_QCOM_MCQCAP_QCFGPTR * 0x200) /* 0x1C000 */
+
+/* Doorbell offsets within MCQ region (relative to MCQ_CONFIG_BASE) */
+#define UFS_QCOM_MCQ_SQD_OFFSET 0x5000
+#define UFS_QCOM_MCQ_CQD_OFFSET 0x5080
+#define UFS_QCOM_MCQ_SQIS_OFFSET 0x5040
+#define UFS_QCOM_MCQ_CQIS_OFFSET 0x50C0
+#define UFS_QCOM_MCQ_STRIDE 0x100
+
+/* Calculated doorbell address offsets (relative to mmio_base) */
+#define UFS_QCOM_SQD_ADDR_OFFSET (UFS_QCOM_MCQ_CONFIG_OFFSET + UFS_QCOM_MCQ_SQD_OFFSET)
+#define UFS_QCOM_CQD_ADDR_OFFSET (UFS_QCOM_MCQ_CONFIG_OFFSET + UFS_QCOM_MCQ_CQD_OFFSET)
+#define UFS_QCOM_SQIS_ADDR_OFFSET (UFS_QCOM_MCQ_CONFIG_OFFSET + UFS_QCOM_MCQ_SQIS_OFFSET)
+#define UFS_QCOM_CQIS_ADDR_OFFSET (UFS_QCOM_MCQ_CONFIG_OFFSET + UFS_QCOM_MCQ_CQIS_OFFSET)
+#define REG_UFS_MCQ_STRIDE UFS_QCOM_MCQ_STRIDE
+
+/* MCQ Vendor specific address offsets (relative to MCQ_CONFIG_BASE) */
+#define UFS_MEM_VS_BASE 0x4000
+#define UFS_MEM_CQIS_VS 0x4008
+
+/* QCOM UFS host controller vendor specific registers */
+enum {
+ REG_UFS_SYS1CLK_1US = 0xC0,
+ REG_UFS_TX_SYMBOL_CLK_NS_US = 0xC4,
+ REG_UFS_LOCAL_PORT_ID_REG = 0xC8,
+ REG_UFS_PA_ERR_CODE = 0xCC,
+ /* On older UFS revisions, this register is called "RETRY_TIMER_REG" */
+ REG_UFS_PARAM0 = 0xD0,
+ /* On older UFS revisions, this register is called "REG_UFS_PA_LINK_STARTUP_TIMER" */
+ REG_UFS_CFG0 = 0xD8,
+ REG_UFS_CFG1 = 0xDC,
+ REG_UFS_CFG2 = 0xE0,
+ REG_UFS_HW_VERSION = 0xE4,
+
+ UFS_TEST_BUS = 0xE8,
+ UFS_TEST_BUS_CTRL_0 = 0xEC,
+ UFS_TEST_BUS_CTRL_1 = 0xF0,
+ UFS_TEST_BUS_CTRL_2 = 0xF4,
+ UFS_UNIPRO_CFG = 0xF8,
+
+ /*
+ * QCOM UFS host controller vendor specific registers
+ * added in HW Version 3.0.0
+ */
+ UFS_AH8_CFG = 0xFC,
+
+ UFS_RD_REG_MCQ = 0xD00,
+ UFS_MEM_ICE_CFG = 0x2600,
+ REG_UFS_MEM_ICE_CONFIG = 0x260C,
+ REG_UFS_MEM_ICE_NUM_CORE = 0x2664,
+
+ REG_UFS_CFG3 = 0x271C,
+
+ REG_UFS_DEBUG_SPARE_CFG = 0x284C,
+};
+
+/* QCOM UFS host controller vendor specific debug registers */
+enum {
+ UFS_DBG_RD_REG_UAWM = 0x100,
+ UFS_DBG_RD_REG_UARM = 0x200,
+ UFS_DBG_RD_REG_TXUC = 0x300,
+ UFS_DBG_RD_REG_RXUC = 0x400,
+ UFS_DBG_RD_REG_DFC = 0x500,
+ UFS_DBG_RD_REG_TRLUT = 0x600,
+ UFS_DBG_RD_REG_TMRLUT = 0x700,
+ UFS_UFS_DBG_RD_REG_OCSC = 0x800,
+
+ UFS_UFS_DBG_RD_DESC_RAM = 0x1500,
+ UFS_UFS_DBG_RD_PRDT_RAM = 0x1700,
+ UFS_UFS_DBG_RD_RESP_RAM = 0x1800,
+ UFS_UFS_DBG_RD_EDTL_RAM = 0x1900,
+};
+
+/* QCOM UFS HC vendor specific Hibern8 count registers */
+enum {
+ REG_UFS_HW_H8_ENTER_CNT = 0x2700,
+ REG_UFS_SW_H8_ENTER_CNT = 0x2704,
+ REG_UFS_SW_AFTER_HW_H8_ENTER_CNT = 0x2708,
+ REG_UFS_HW_H8_EXIT_CNT = 0x270C,
+ REG_UFS_SW_H8_EXIT_CNT = 0x2710,
+};
+
+#define UFS_CNTLR_2_x_x_VEN_REGS_OFFSET(x) (0x000 + x)
+#define UFS_CNTLR_3_x_x_VEN_REGS_OFFSET(x) (0x400 + x)
+
+/* bit definitions for REG_UFS_CFG0 register */
+#define QUNIPRO_G4_SEL BIT(5)
+
+/* bit definitions for REG_UFS_CFG1 register */
+#define QUNIPRO_SEL BIT(0)
+#define UFS_PHY_SOFT_RESET BIT(1)
+#define UTP_DBG_RAMS_EN BIT(17)
+#define TEST_BUS_EN BIT(18)
+#define TEST_BUS_SEL GENMASK(22, 19)
+#define UFS_REG_TEST_BUS_EN BIT(30)
+
+/* bit definitions for REG_UFS_CFG2 register */
+#define UAWM_HW_CGC_EN BIT(0)
+#define UARM_HW_CGC_EN BIT(1)
+#define TXUC_HW_CGC_EN BIT(2)
+#define RXUC_HW_CGC_EN BIT(3)
+#define DFC_HW_CGC_EN BIT(4)
+#define TRLUT_HW_CGC_EN BIT(5)
+#define TMRLUT_HW_CGC_EN BIT(6)
+#define OCSC_HW_CGC_EN BIT(7)
+
+/* bit definitions for REG_UFS_CFG3 register */
+#define ESI_VEC_MASK GENMASK(22, 12)
+
+/* bit definitions for REG_UFS_PARAM0 */
+#define MAX_HS_GEAR_MASK GENMASK(6, 4)
+#define UFS_QCOM_MAX_GEAR(x) FIELD_GET(MAX_HS_GEAR_MASK, (x))
+
+/* bit definition for UFS_UFS_TEST_BUS_CTRL_n */
+#define TEST_BUS_SUB_SEL_MASK GENMASK(4, 0) /* All XXX_SEL fields are 5 bits wide */
+
+/* bit definition for UFS Shared ICE config */
+#define UFS_QCOM_CAP_ICE_CONFIG BIT(0)
+
+#define REG_UFS_CFG2_CGC_EN_ALL (UAWM_HW_CGC_EN | UARM_HW_CGC_EN |\
+ TXUC_HW_CGC_EN | RXUC_HW_CGC_EN |\
+ DFC_HW_CGC_EN | TRLUT_HW_CGC_EN |\
+ TMRLUT_HW_CGC_EN | OCSC_HW_CGC_EN)
+
+/* QUniPro Vendor specific attributes */
+#define PA_TX_HSG1_SYNC_LENGTH 0x1552
+#define PA_VS_CONFIG_REG1 0x9000
+#define DME_VS_CORE_CLK_CTRL 0xD002
+#define TX_HS_EQUALIZER 0x0037
+
+/* bit and mask definitions for DME_VS_CORE_CLK_CTRL attribute */
+#define CLK_1US_CYCLES_MASK_V4 GENMASK(27, 16)
+#define CLK_1US_CYCLES_MASK GENMASK(7, 0)
+#define DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT BIT(8)
+#define PA_VS_CORE_CLK_40NS_CYCLES 0x9007
+#define PA_VS_CORE_CLK_40NS_CYCLES_MASK GENMASK(6, 0)
+
+
+/* QCOM UFS host controller core clk frequencies */
+#define UNIPRO_CORE_CLK_FREQ_37_5_MHZ 38
+#define UNIPRO_CORE_CLK_FREQ_75_MHZ 75
+#define UNIPRO_CORE_CLK_FREQ_100_MHZ 100
+#define UNIPRO_CORE_CLK_FREQ_150_MHZ 150
+#define UNIPRO_CORE_CLK_FREQ_300_MHZ 300
+#define UNIPRO_CORE_CLK_FREQ_201_5_MHZ 202
+#define UNIPRO_CORE_CLK_FREQ_403_MHZ 403
+
+/* TX_HSG1_SYNC_LENGTH attr value */
+#define PA_TX_HSG1_SYNC_LENGTH_VAL 0x4A
+
+/*
+ * Some ufs device vendors need a different TSync length.
+ * Enable this quirk to give an additional TX_HS_SYNC_LENGTH.
+ */
+#define UFS_DEVICE_QUIRK_PA_TX_HSG1_SYNC_LENGTH BIT(16)
+
+/*
+ * Some ufs device vendors need a different Deemphasis setting.
+ * Enable this quirk to tune TX Deemphasis parameters.
+ */
+#define UFS_DEVICE_QUIRK_PA_TX_DEEMPHASIS_TUNING BIT(17)
+
+/* ICE allocator type to share AES engines among TX stream and RX stream */
+#define ICE_ALLOCATOR_TYPE 2
+
+/*
+ * Number of cores allocated for RX stream when Read data block received and
+ * Write data block is not in progress
+ */
+#define NUM_RX_R1W0 28
+
+/*
+ * Number of cores allocated for TX stream when Device asked to send write
+ * data block and Read data block is not in progress
+ */
+#define NUM_TX_R0W1 28
+
+/*
+ * Number of cores allocated for RX stream when Read data block received and
+ * Write data block is in progress
+ * OR
+ * Device asked to send write data block and Read data block is in progress
+ */
+#define NUM_RX_R1W1 15
+
+/*
+ * Number of cores allocated for TX stream (UFS write) when Read data block
+ * received and Write data block is in progress
+ * OR
+ * Device asked to send write data block and Read data block is in progress
+ */
+#define NUM_TX_R1W1 13
+
+/* bit definitions for UFS_AH8_CFG register */
+#define CC_UFS_SYS_CLK_REQ_EN BIT(2)
+#define CC_UFS_ICE_CORE_CLK_REQ_EN BIT(3)
+#define CC_UFS_UNIPRO_CORE_CLK_REQ_EN BIT(4)
+#define CC_UFS_AUXCLK_REQ_EN BIT(5)
+
+#define UFS_HW_CLK_CTRL_EN (CC_UFS_SYS_CLK_REQ_EN |\
+ CC_UFS_ICE_CORE_CLK_REQ_EN |\
+ CC_UFS_UNIPRO_CORE_CLK_REQ_EN |\
+ CC_UFS_AUXCLK_REQ_EN)
+
+static inline void
+ufs_qcom_get_controller_revision(struct ufs_hba *hba,
+ u8 *major, u16 *minor, u16 *step)
+{
+ u32 ver = ufshcd_readl(hba, REG_UFS_HW_VERSION);
+
+ *major = FIELD_GET(UFS_HW_VER_MAJOR_MASK, ver);
+ *minor = FIELD_GET(UFS_HW_VER_MINOR_MASK, ver);
+ *step = FIELD_GET(UFS_HW_VER_STEP_MASK, ver);
+};
+
+static inline void ufs_qcom_assert_reset(struct ufs_hba *hba)
+{
+ ufshcd_rmwl(hba, UFS_PHY_SOFT_RESET, UFS_PHY_SOFT_RESET, REG_UFS_CFG1);
+
+ /*
+ * Dummy read to ensure the write takes effect before doing any sort
+ * of delay
+ */
+ ufshcd_readl(hba, REG_UFS_CFG1);
+}
+
+static inline void ufs_qcom_deassert_reset(struct ufs_hba *hba)
+{
+ ufshcd_rmwl(hba, UFS_PHY_SOFT_RESET, 0, REG_UFS_CFG1);
+
+ /*
+ * Dummy read to ensure the write takes effect before doing any sort
+ * of delay
+ */
+ ufshcd_readl(hba, REG_UFS_CFG1);
+}
+
+/* Host controller hardware version: major.minor.step */
+struct ufs_hw_version {
+ u16 step;
+ u16 minor;
+ u8 major;
+};
+
+struct ufs_qcom_testbus {
+ u8 select_major;
+ u8 select_minor;
+};
+
+struct gpio_desc;
+
+struct ufs_qcom_host {
+ struct phy *generic_phy;
+ struct ufs_hba *hba;
+ struct ufs_pa_layer_attr dev_req_params;
+ struct clk_bulk_data *clks;
+ u32 num_clks;
+ bool is_lane_clks_enabled;
+
+ struct icc_path *icc_ddr;
+ struct icc_path *icc_cpu;
+
+#ifdef CONFIG_SCSI_UFS_CRYPTO
+ struct qcom_ice *ice;
+#endif
+ u32 caps;
+ void __iomem *dev_ref_clk_ctrl_mmio;
+ bool is_dev_ref_clk_enabled;
+ struct ufs_hw_version hw_ver;
+
+ u32 dev_ref_clk_en_mask;
+
+ struct ufs_qcom_testbus testbus;
+
+ /* Reset control of HCI */
+ struct reset_control *core_reset;
+ struct reset_controller_dev rcdev;
+
+ struct gpio_desc *device_reset;
+
+ struct ufs_host_params host_params;
+ u32 phy_gear;
+
+ bool esi_enabled;
+
+ u32 saved_tx_eq_g1_setting;
+ u32 boot_spare_cfg;
+};
+
+struct ufs_qcom_drvdata {
+ enum ufshcd_quirks quirks;
+ bool no_phy_retention;
+ const struct ufs_hba_variant_ops *vops;
+};
+
+static inline u32
+ufs_qcom_get_debug_reg_offset(struct ufs_qcom_host *host, u32 reg)
+{
+ if (host->hw_ver.major <= 0x02)
+ return UFS_CNTLR_2_x_x_VEN_REGS_OFFSET(reg);
+
+ return UFS_CNTLR_3_x_x_VEN_REGS_OFFSET(reg);
+};
+
+#define ufs_qcom_is_link_off(hba) ufshcd_is_link_off(hba)
+#define ufs_qcom_is_link_active(hba) ufshcd_is_link_active(hba)
+#define ufs_qcom_is_link_hibern8(hba) ufshcd_is_link_hibern8(hba)
+#define ceil(freq, div) ((freq) % (div) == 0 ? ((freq)/(div)) : ((freq)/(div) + 1))
+
+int ufs_qcom_testbus_config(struct ufs_qcom_host *host);
+
+#endif /* UFS_QCOM_H_ */
diff --git a/drivers/ufs/host/ufs-renesas.c b/drivers/ufs/host/ufs-renesas.c
new file mode 100644
index 000000000..5bf7d0e77
--- /dev/null
+++ b/drivers/ufs/host/ufs-renesas.c
@@ -0,0 +1,558 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Renesas UFS host controller driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/sys_soc.h>
+#include <ufs/ufshcd.h>
+
+#include "ufshcd-pltfrm.h"
+
+#define EFUSE_CALIB_SIZE 8
+
+struct ufs_renesas_priv {
+ const struct firmware *fw;
+ void (*pre_init)(struct ufs_hba *hba);
+ bool initialized; /* The hardware needs initialization once */
+ u8 calib[EFUSE_CALIB_SIZE];
+};
+
+#define UFS_RENESAS_FIRMWARE_NAME "r8a779f0_ufs.bin"
+MODULE_FIRMWARE(UFS_RENESAS_FIRMWARE_NAME);
+
+static void ufs_renesas_dbg_register_dump(struct ufs_hba *hba)
+{
+ ufshcd_dump_regs(hba, 0xc0, 0x40, "regs: 0xc0 + ");
+}
+
+static void ufs_renesas_poll(struct ufs_hba *hba, u32 reg, u32 expected, u32 mask)
+{
+ int ret;
+ u32 val;
+
+ ret = readl_poll_timeout_atomic(hba->mmio_base + reg,
+ val, (val & mask) == expected,
+ 10, 1000);
+ if (ret)
+ dev_err(hba->dev, "%s: poll failed %d (%08x, %08x, %08x)\n",
+ __func__, ret, val, mask, expected);
+}
+
+static u32 ufs_renesas_read(struct ufs_hba *hba, u32 reg)
+{
+ return ufshcd_readl(hba, reg);
+}
+
+static void ufs_renesas_write(struct ufs_hba *hba, u32 reg, u32 value)
+{
+ ufshcd_writel(hba, value, reg);
+}
+
+static void ufs_renesas_write_d0_d4(struct ufs_hba *hba, u32 data_d0, u32 data_d4)
+{
+ ufs_renesas_write(hba, 0xd0, data_d0);
+ ufs_renesas_write(hba, 0xd4, data_d4);
+}
+
+static void ufs_renesas_write_800_80c_poll(struct ufs_hba *hba, u32 addr,
+ u32 data_800)
+{
+ ufs_renesas_write_d0_d4(hba, 0x0000080c, 0x00000100);
+ ufs_renesas_write_d0_d4(hba, 0x00000800, (data_800 << 16) | BIT(8) | addr);
+ ufs_renesas_write(hba, 0xd0, 0x0000080c);
+ ufs_renesas_poll(hba, 0xd4, BIT(8), BIT(8));
+}
+
+static void ufs_renesas_write_804_80c_poll(struct ufs_hba *hba, u32 addr, u32 data_804)
+{
+ ufs_renesas_write_d0_d4(hba, 0x0000080c, 0x00000100);
+ ufs_renesas_write_d0_d4(hba, 0x00000804, (data_804 << 16) | BIT(8) | addr);
+ ufs_renesas_write(hba, 0xd0, 0x0000080c);
+ ufs_renesas_poll(hba, 0xd4, BIT(8), BIT(8));
+}
+
+static void ufs_renesas_write_828_82c_poll(struct ufs_hba *hba, u32 data_828)
+{
+ ufs_renesas_write_d0_d4(hba, 0x0000082c, 0x0f000000);
+ ufs_renesas_write_d0_d4(hba, 0x00000828, data_828);
+ ufs_renesas_write(hba, 0xd0, 0x0000082c);
+ ufs_renesas_poll(hba, 0xd4, data_828, data_828);
+}
+
+static void ufs_renesas_write_phy(struct ufs_hba *hba, u32 addr16, u32 data16)
+{
+ ufs_renesas_write(hba, 0xf0, 1);
+ ufs_renesas_write_800_80c_poll(hba, 0x16, addr16 & 0xff);
+ ufs_renesas_write_800_80c_poll(hba, 0x17, (addr16 >> 8) & 0xff);
+ ufs_renesas_write_800_80c_poll(hba, 0x18, data16 & 0xff);
+ ufs_renesas_write_800_80c_poll(hba, 0x19, (data16 >> 8) & 0xff);
+ ufs_renesas_write_800_80c_poll(hba, 0x1c, 0x01);
+ ufs_renesas_write_828_82c_poll(hba, 0x0f000000);
+ ufs_renesas_write(hba, 0xf0, 0);
+}
+
+static void ufs_renesas_set_phy(struct ufs_hba *hba, u32 addr16, u32 data16)
+{
+ u32 low, high;
+
+ ufs_renesas_write(hba, 0xf0, 1);
+ ufs_renesas_write_800_80c_poll(hba, 0x16, addr16 & 0xff);
+ ufs_renesas_write_800_80c_poll(hba, 0x17, (addr16 >> 8) & 0xff);
+ ufs_renesas_write_800_80c_poll(hba, 0x1c, 0x01);
+ ufs_renesas_write_828_82c_poll(hba, 0x0f000000);
+ ufs_renesas_write_804_80c_poll(hba, 0x1a, 0);
+ ufs_renesas_write(hba, 0xd0, 0x00000808);
+ low = ufs_renesas_read(hba, 0xd4) & 0xff;
+ ufs_renesas_write_804_80c_poll(hba, 0x1b, 0);
+ ufs_renesas_write(hba, 0xd0, 0x00000808);
+ high = ufs_renesas_read(hba, 0xd4) & 0xff;
+ ufs_renesas_write_828_82c_poll(hba, 0x0f000000);
+ ufs_renesas_write(hba, 0xf0, 0);
+
+ data16 |= (high << 8) | low;
+ ufs_renesas_write_phy(hba, addr16, data16);
+}
+
+static void ufs_renesas_reset_indirect_write(struct ufs_hba *hba, int gpio,
+ u32 addr, u32 data)
+{
+ ufs_renesas_write(hba, 0xf0, gpio);
+ ufs_renesas_write_800_80c_poll(hba, addr, data);
+}
+
+static void ufs_renesas_reset_indirect_update(struct ufs_hba *hba)
+{
+ ufs_renesas_write_d0_d4(hba, 0x0000082c, 0x0f000000);
+ ufs_renesas_write_d0_d4(hba, 0x00000828, 0x0f000000);
+ ufs_renesas_write(hba, 0xd0, 0x0000082c);
+ ufs_renesas_poll(hba, 0xd4, BIT(27) | BIT(26) | BIT(24), BIT(27) | BIT(26) | BIT(24));
+ ufs_renesas_write(hba, 0xf0, 0);
+}
+
+static void ufs_renesas_indirect_write(struct ufs_hba *hba, u32 gpio, u32 addr,
+ u32 data_800)
+{
+ ufs_renesas_write(hba, 0xf0, gpio);
+ ufs_renesas_write_800_80c_poll(hba, addr, data_800);
+ ufs_renesas_write_828_82c_poll(hba, 0x0f000000);
+ ufs_renesas_write(hba, 0xf0, 0);
+}
+
+static void ufs_renesas_indirect_poll(struct ufs_hba *hba, u32 gpio, u32 addr,
+ u32 expected, u32 mask)
+{
+ ufs_renesas_write(hba, 0xf0, gpio);
+ ufs_renesas_write_800_80c_poll(hba, addr, 0);
+ ufs_renesas_write(hba, 0xd0, 0x00000808);
+ ufs_renesas_poll(hba, 0xd4, expected, mask);
+ ufs_renesas_write(hba, 0xf0, 0);
+}
+
+static void ufs_renesas_init_step1_to_3(struct ufs_hba *hba, bool init108)
+{
+ ufs_renesas_write(hba, 0xc0, 0x49425308);
+ ufs_renesas_write_d0_d4(hba, 0x00000104, 0x00000002);
+ if (init108)
+ ufs_renesas_write_d0_d4(hba, 0x00000108, 0x00000002);
+ udelay(1);
+ ufs_renesas_write_d0_d4(hba, 0x00000828, 0x00000200);
+ udelay(1);
+ ufs_renesas_write_d0_d4(hba, 0x00000828, 0x00000000);
+ ufs_renesas_write_d0_d4(hba, 0x00000104, 0x00000001);
+ if (init108)
+ ufs_renesas_write_d0_d4(hba, 0x00000108, 0x00000001);
+ ufs_renesas_write_d0_d4(hba, 0x00000940, 0x00000001);
+ udelay(1);
+ ufs_renesas_write_d0_d4(hba, 0x00000940, 0x00000000);
+
+ ufs_renesas_write(hba, 0xc0, 0x49425308);
+ ufs_renesas_write(hba, 0xc0, 0x41584901);
+}
+
+static void ufs_renesas_init_step4_to_6(struct ufs_hba *hba)
+{
+ ufs_renesas_write_d0_d4(hba, 0x0000080c, 0x00000100);
+ ufs_renesas_write_d0_d4(hba, 0x00000804, 0x00000000);
+ ufs_renesas_write(hba, 0xd0, 0x0000080c);
+ ufs_renesas_poll(hba, 0xd4, BIT(8), BIT(8));
+
+ ufs_renesas_write(hba, REG_CONTROLLER_ENABLE, 0x00000001);
+
+ ufs_renesas_write(hba, 0xd0, 0x00000804);
+ ufs_renesas_poll(hba, 0xd4, BIT(8) | BIT(6) | BIT(0), BIT(8) | BIT(6) | BIT(0));
+}
+
+static u32 ufs_renesas_init_disable_timer(struct ufs_hba *hba)
+{
+ u32 timer_val;
+
+ ufs_renesas_write(hba, 0xd0, 0x00000d00);
+ timer_val = ufs_renesas_read(hba, 0xd4) & 0x0000ffff;
+ ufs_renesas_write(hba, 0xd4, 0x00000000);
+ ufs_renesas_write_d0_d4(hba, 0x0000082c, 0x0f000000);
+ ufs_renesas_write_d0_d4(hba, 0x00000828, 0x08000000);
+ ufs_renesas_write(hba, 0xd0, 0x0000082c);
+ ufs_renesas_poll(hba, 0xd4, BIT(27), BIT(27));
+ ufs_renesas_write(hba, 0xd0, 0x00000d2c);
+ ufs_renesas_poll(hba, 0xd4, BIT(0), BIT(0));
+
+ return timer_val;
+}
+
+static void ufs_renesas_init_enable_timer(struct ufs_hba *hba, u32 timer_val)
+{
+ ufs_renesas_write(hba, 0xf0, 0);
+ ufs_renesas_write(hba, 0xd0, 0x00000d00);
+ ufs_renesas_write(hba, 0xd4, timer_val);
+}
+
+static void ufs_renesas_write_phy_10ad_10af(struct ufs_hba *hba,
+ u32 data_10ad, u32 data_10af)
+{
+ ufs_renesas_write_phy(hba, 0x10ae, 0x0001);
+ ufs_renesas_write_phy(hba, 0x10ad, data_10ad);
+ ufs_renesas_write_phy(hba, 0x10af, data_10af);
+ ufs_renesas_write_phy(hba, 0x10b6, 0x0001);
+ ufs_renesas_write_phy(hba, 0x10ae, 0x0000);
+}
+
+static void ufs_renesas_init_compensation_and_slicers(struct ufs_hba *hba)
+{
+ ufs_renesas_write_phy_10ad_10af(hba, 0x0000, 0x0001);
+ ufs_renesas_write_phy_10ad_10af(hba, 0x0000, 0x0002);
+ ufs_renesas_write_phy_10ad_10af(hba, 0x0080, 0x0000);
+ ufs_renesas_write_phy_10ad_10af(hba, 0x0080, 0x001a);
+}
+
+static void ufs_renesas_r8a779f0_es10_pre_init(struct ufs_hba *hba)
+{
+ u32 timer_val;
+
+ /* This setting is for SERIES B */
+ ufs_renesas_init_step1_to_3(hba, false);
+
+ ufs_renesas_init_step4_to_6(hba);
+
+ timer_val = ufs_renesas_init_disable_timer(hba);
+
+ /* phy setup */
+ ufs_renesas_indirect_write(hba, 1, 0x01, 0x001f);
+ ufs_renesas_indirect_write(hba, 7, 0x5d, 0x0014);
+ ufs_renesas_indirect_write(hba, 7, 0x5e, 0x0014);
+ ufs_renesas_indirect_write(hba, 7, 0x0d, 0x0003);
+ ufs_renesas_indirect_write(hba, 7, 0x0e, 0x0007);
+ ufs_renesas_indirect_write(hba, 7, 0x5f, 0x0003);
+ ufs_renesas_indirect_write(hba, 7, 0x60, 0x0003);
+ ufs_renesas_indirect_write(hba, 7, 0x5b, 0x00a6);
+ ufs_renesas_indirect_write(hba, 7, 0x5c, 0x0003);
+
+ ufs_renesas_indirect_poll(hba, 7, 0x3c, 0, BIT(7));
+ ufs_renesas_indirect_poll(hba, 7, 0x4c, 0, BIT(4));
+
+ ufs_renesas_indirect_write(hba, 1, 0x32, 0x0080);
+ ufs_renesas_indirect_write(hba, 1, 0x1f, 0x0001);
+ ufs_renesas_indirect_write(hba, 0, 0x2c, 0x0001);
+ ufs_renesas_indirect_write(hba, 0, 0x32, 0x0087);
+
+ ufs_renesas_indirect_write(hba, 1, 0x4d, 0x0061);
+ ufs_renesas_indirect_write(hba, 4, 0x9b, 0x0009);
+ ufs_renesas_indirect_write(hba, 4, 0xa6, 0x0005);
+ ufs_renesas_indirect_write(hba, 4, 0xa5, 0x0058);
+ ufs_renesas_indirect_write(hba, 1, 0x39, 0x0027);
+ ufs_renesas_indirect_write(hba, 1, 0x47, 0x004c);
+
+ ufs_renesas_indirect_write(hba, 7, 0x0d, 0x0002);
+ ufs_renesas_indirect_write(hba, 7, 0x0e, 0x0007);
+
+ ufs_renesas_write_phy(hba, 0x0028, 0x0061);
+ ufs_renesas_write_phy(hba, 0x4014, 0x0061);
+ ufs_renesas_set_phy(hba, 0x401c, BIT(2));
+ ufs_renesas_write_phy(hba, 0x4000, 0x0000);
+ ufs_renesas_write_phy(hba, 0x4001, 0x0000);
+
+ ufs_renesas_init_compensation_and_slicers(hba);
+
+ ufs_renesas_indirect_write(hba, 7, 0x70, 0x0016);
+ ufs_renesas_indirect_write(hba, 7, 0x71, 0x0016);
+ ufs_renesas_indirect_write(hba, 7, 0x72, 0x0014);
+ ufs_renesas_indirect_write(hba, 7, 0x73, 0x0014);
+ ufs_renesas_indirect_write(hba, 7, 0x74, 0x0000);
+ ufs_renesas_indirect_write(hba, 7, 0x75, 0x0000);
+ ufs_renesas_indirect_write(hba, 7, 0x76, 0x0010);
+ ufs_renesas_indirect_write(hba, 7, 0x77, 0x0010);
+ ufs_renesas_indirect_write(hba, 7, 0x78, 0x00ff);
+ ufs_renesas_indirect_write(hba, 7, 0x79, 0x0000);
+
+ ufs_renesas_indirect_write(hba, 7, 0x19, 0x0007);
+ ufs_renesas_indirect_write(hba, 7, 0x1a, 0x0007);
+ ufs_renesas_indirect_write(hba, 7, 0x24, 0x000c);
+ ufs_renesas_indirect_write(hba, 7, 0x25, 0x000c);
+ ufs_renesas_indirect_write(hba, 7, 0x62, 0x0000);
+ ufs_renesas_indirect_write(hba, 7, 0x63, 0x0000);
+ ufs_renesas_indirect_write(hba, 7, 0x5d, 0x0014);
+ ufs_renesas_indirect_write(hba, 7, 0x5e, 0x0017);
+ ufs_renesas_indirect_write(hba, 7, 0x5d, 0x0004);
+ ufs_renesas_indirect_write(hba, 7, 0x5e, 0x0017);
+ ufs_renesas_indirect_poll(hba, 7, 0x55, 0, BIT(6));
+ ufs_renesas_indirect_poll(hba, 7, 0x41, 0, BIT(7));
+ /* end of phy setup */
+
+ ufs_renesas_init_enable_timer(hba, timer_val);
+}
+
+static void ufs_renesas_r8a779f0_init_step3_add(struct ufs_hba *hba, bool assert)
+{
+ u32 val_2x = 0, val_3x = 0, val_4x = 0;
+
+ if (assert) {
+ val_2x = 0x0001;
+ val_3x = 0x0003;
+ val_4x = 0x0001;
+ }
+
+ ufs_renesas_reset_indirect_write(hba, 7, 0x20, val_2x);
+ ufs_renesas_reset_indirect_write(hba, 7, 0x4a, val_4x);
+ ufs_renesas_reset_indirect_write(hba, 7, 0x35, val_3x);
+ ufs_renesas_reset_indirect_update(hba);
+ ufs_renesas_reset_indirect_write(hba, 7, 0x21, val_2x);
+ ufs_renesas_reset_indirect_write(hba, 7, 0x4b, val_4x);
+ ufs_renesas_reset_indirect_write(hba, 7, 0x36, val_3x);
+ ufs_renesas_reset_indirect_update(hba);
+}
+
+static void ufs_renesas_r8a779f0_pre_init(struct ufs_hba *hba)
+{
+ struct ufs_renesas_priv *priv = ufshcd_get_variant(hba);
+ u32 timer_val;
+ u32 data;
+ int i;
+
+ /* This setting is for SERIES B */
+ ufs_renesas_init_step1_to_3(hba, true);
+
+ ufs_renesas_r8a779f0_init_step3_add(hba, true);
+ ufs_renesas_reset_indirect_write(hba, 7, 0x5f, 0x0063);
+ ufs_renesas_reset_indirect_update(hba);
+ ufs_renesas_reset_indirect_write(hba, 7, 0x60, 0x0003);
+ ufs_renesas_reset_indirect_update(hba);
+ ufs_renesas_reset_indirect_write(hba, 7, 0x5b, 0x00a6);
+ ufs_renesas_reset_indirect_update(hba);
+ ufs_renesas_reset_indirect_write(hba, 7, 0x5c, 0x0003);
+ ufs_renesas_reset_indirect_update(hba);
+ ufs_renesas_r8a779f0_init_step3_add(hba, false);
+
+ ufs_renesas_init_step4_to_6(hba);
+
+ timer_val = ufs_renesas_init_disable_timer(hba);
+
+ ufs_renesas_indirect_write(hba, 1, 0x01, 0x001f);
+ ufs_renesas_indirect_write(hba, 7, 0x5d, 0x0014);
+ ufs_renesas_indirect_write(hba, 7, 0x5e, 0x0014);
+ ufs_renesas_indirect_write(hba, 7, 0x0d, 0x0007);
+ ufs_renesas_indirect_write(hba, 7, 0x0e, 0x0007);
+
+ ufs_renesas_indirect_poll(hba, 7, 0x3c, 0, BIT(7));
+ ufs_renesas_indirect_poll(hba, 7, 0x4c, 0, BIT(4));
+
+ ufs_renesas_indirect_write(hba, 1, 0x32, 0x0080);
+ ufs_renesas_indirect_write(hba, 1, 0x1f, 0x0001);
+ ufs_renesas_indirect_write(hba, 1, 0x2c, 0x0001);
+ ufs_renesas_indirect_write(hba, 1, 0x32, 0x0087);
+
+ ufs_renesas_indirect_write(hba, 1, 0x4d, priv->calib[2]);
+ ufs_renesas_indirect_write(hba, 1, 0x4e, priv->calib[3]);
+ ufs_renesas_indirect_write(hba, 1, 0x0d, 0x0006);
+ ufs_renesas_indirect_write(hba, 1, 0x0e, 0x0007);
+ ufs_renesas_write_phy(hba, 0x0028, priv->calib[3]);
+ ufs_renesas_write_phy(hba, 0x4014, priv->calib[3]);
+
+ ufs_renesas_set_phy(hba, 0x401c, BIT(2));
+
+ ufs_renesas_write_phy(hba, 0x4000, priv->calib[6]);
+ ufs_renesas_write_phy(hba, 0x4001, priv->calib[7]);
+
+ ufs_renesas_indirect_write(hba, 1, 0x14, 0x0001);
+
+ ufs_renesas_init_compensation_and_slicers(hba);
+
+ ufs_renesas_indirect_write(hba, 7, 0x79, 0x0000);
+ ufs_renesas_indirect_write(hba, 7, 0x24, 0x000c);
+ ufs_renesas_indirect_write(hba, 7, 0x25, 0x000c);
+ ufs_renesas_indirect_write(hba, 7, 0x62, 0x00c0);
+ ufs_renesas_indirect_write(hba, 7, 0x63, 0x0001);
+
+ for (i = 0; i < priv->fw->size / 2; i++) {
+ data = (priv->fw->data[i * 2 + 1] << 8) | priv->fw->data[i * 2];
+ ufs_renesas_write_phy(hba, 0xc000 + i, data);
+ }
+
+ ufs_renesas_indirect_write(hba, 7, 0x0d, 0x0002);
+ ufs_renesas_indirect_write(hba, 7, 0x0e, 0x0007);
+
+ ufs_renesas_indirect_write(hba, 7, 0x5d, 0x0014);
+ ufs_renesas_indirect_write(hba, 7, 0x5e, 0x0017);
+ ufs_renesas_indirect_write(hba, 7, 0x5d, 0x0004);
+ ufs_renesas_indirect_write(hba, 7, 0x5e, 0x0017);
+ ufs_renesas_indirect_poll(hba, 7, 0x55, 0, BIT(6));
+ ufs_renesas_indirect_poll(hba, 7, 0x41, 0, BIT(7));
+
+ ufs_renesas_init_enable_timer(hba, timer_val);
+}
+
+static int ufs_renesas_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ struct ufs_renesas_priv *priv = ufshcd_get_variant(hba);
+
+ if (priv->initialized)
+ return 0;
+
+ if (status == PRE_CHANGE)
+ priv->pre_init(hba);
+
+ priv->initialized = true;
+
+ return 0;
+}
+
+static int ufs_renesas_setup_clocks(struct ufs_hba *hba, bool on,
+ enum ufs_notify_change_status status)
+{
+ if (on && status == PRE_CHANGE)
+ pm_runtime_get_sync(hba->dev);
+ else if (!on && status == POST_CHANGE)
+ pm_runtime_put(hba->dev);
+
+ return 0;
+}
+
+static const struct soc_device_attribute ufs_fallback[] = {
+ { .soc_id = "r8a779f0", .revision = "ES1.[01]" },
+ { /* Sentinel */ }
+};
+
+static int ufs_renesas_init(struct ufs_hba *hba)
+{
+ const struct soc_device_attribute *attr;
+ struct nvmem_cell *cell = NULL;
+ struct device *dev = hba->dev;
+ struct ufs_renesas_priv *priv;
+ u8 *data = NULL;
+ size_t len;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ ufshcd_set_variant(hba, priv);
+
+ hba->quirks |= UFSHCD_QUIRK_HIBERN_FASTAUTO;
+
+ attr = soc_device_match(ufs_fallback);
+ if (attr)
+ goto fallback;
+
+ ret = request_firmware(&priv->fw, UFS_RENESAS_FIRMWARE_NAME, dev);
+ if (ret) {
+ dev_warn(dev, "Failed to load firmware\n");
+ goto fallback;
+ }
+
+ cell = nvmem_cell_get(dev, "calibration");
+ if (IS_ERR(cell)) {
+ dev_warn(dev, "No calibration data specified\n");
+ goto fallback;
+ }
+
+ data = nvmem_cell_read(cell, &len);
+ if (IS_ERR(data)) {
+ dev_warn(dev, "Failed to read calibration data: %pe\n", data);
+ goto fallback;
+ }
+
+ if (len != EFUSE_CALIB_SIZE) {
+ dev_warn(dev, "Invalid calibration data size %zu\n", len);
+ goto fallback;
+ }
+
+ memcpy(priv->calib, data, EFUSE_CALIB_SIZE);
+ priv->pre_init = ufs_renesas_r8a779f0_pre_init;
+ goto out;
+
+fallback:
+ dev_info(dev, "Using ES1.0 init code\n");
+ priv->pre_init = ufs_renesas_r8a779f0_es10_pre_init;
+
+out:
+ kfree(data);
+ if (!IS_ERR_OR_NULL(cell))
+ nvmem_cell_put(cell);
+
+ return 0;
+}
+
+static void ufs_renesas_exit(struct ufs_hba *hba)
+{
+ struct ufs_renesas_priv *priv = ufshcd_get_variant(hba);
+
+ release_firmware(priv->fw);
+}
+
+static int ufs_renesas_set_dma_mask(struct ufs_hba *hba)
+{
+ return dma_set_mask_and_coherent(hba->dev, DMA_BIT_MASK(32));
+}
+
+static const struct ufs_hba_variant_ops ufs_renesas_vops = {
+ .name = "renesas",
+ .init = ufs_renesas_init,
+ .exit = ufs_renesas_exit,
+ .set_dma_mask = ufs_renesas_set_dma_mask,
+ .setup_clocks = ufs_renesas_setup_clocks,
+ .hce_enable_notify = ufs_renesas_hce_enable_notify,
+ .dbg_register_dump = ufs_renesas_dbg_register_dump,
+};
+
+static const struct of_device_id __maybe_unused ufs_renesas_of_match[] = {
+ { .compatible = "renesas,r8a779f0-ufs" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ufs_renesas_of_match);
+
+static int ufs_renesas_probe(struct platform_device *pdev)
+{
+ return ufshcd_pltfrm_init(pdev, &ufs_renesas_vops);
+}
+
+static void ufs_renesas_remove(struct platform_device *pdev)
+{
+ ufshcd_pltfrm_remove(pdev);
+}
+
+static struct platform_driver ufs_renesas_platform = {
+ .probe = ufs_renesas_probe,
+ .remove = ufs_renesas_remove,
+ .driver = {
+ .name = "ufshcd-renesas",
+ .of_match_table = of_match_ptr(ufs_renesas_of_match),
+ },
+};
+module_platform_driver(ufs_renesas_platform);
+
+MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>");
+MODULE_DESCRIPTION("Renesas UFS host controller driver");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/ufs/host/ufs-rockchip.c b/drivers/ufs/host/ufs-rockchip.c
new file mode 100644
index 000000000..bac68f238
--- /dev/null
+++ b/drivers/ufs/host/ufs-rockchip.c
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Rockchip UFS Host Controller driver
+ *
+ * Copyright (C) 2025 Rockchip Electronics Co., Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_wakeup.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <ufs/ufshcd.h>
+#include <ufs/unipro.h>
+#include "ufshcd-pltfrm.h"
+#include "ufs-rockchip.h"
+
+static void ufs_rockchip_controller_reset(struct ufs_rockchip_host *host)
+{
+ reset_control_assert(host->rst);
+ udelay(1);
+ reset_control_deassert(host->rst);
+}
+
+static int ufs_rockchip_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
+ int err = 0;
+
+ if (status == POST_CHANGE) {
+ err = ufshcd_dme_reset(hba);
+ if (err)
+ return err;
+
+ err = ufshcd_dme_enable(hba);
+ if (err)
+ return err;
+
+ return ufshcd_vops_phy_initialization(hba);
+ }
+
+ /* PRE_CHANGE */
+ ufs_rockchip_controller_reset(host);
+
+ return 0;
+}
+
+static void ufs_rockchip_set_pm_lvl(struct ufs_hba *hba)
+{
+ hba->rpm_lvl = UFS_PM_LVL_5;
+ hba->spm_lvl = UFS_PM_LVL_5;
+}
+
+static int ufs_rockchip_rk3576_phy_init(struct ufs_hba *hba)
+{
+ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(PA_LOCAL_TX_LCC_ENABLE, 0x0), 0x0);
+ /* enable the mphy DME_SET cfg */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(MPHY_CFG, 0x0), MPHY_CFG_ENABLE);
+ for (int i = 0; i < 2; i++) {
+ /* Configuration M - TX */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_CLK_PRD, SEL_TX_LANE0 + i), 0x06);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_CLK_PRD_EN, SEL_TX_LANE0 + i), 0x02);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_VALUE, SEL_TX_LANE0 + i), 0x44);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE1, SEL_TX_LANE0 + i), 0xe6);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE2, SEL_TX_LANE0 + i), 0x07);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_TASE_VALUE, SEL_TX_LANE0 + i), 0x93);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_BASE_NVALUE, SEL_TX_LANE0 + i), 0xc9);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_POWER_SAVING_CTRL, SEL_TX_LANE0 + i), 0x00);
+ /* Configuration M - RX */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_CLK_PRD, SEL_RX_LANE0 + i), 0x06);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_CLK_PRD_EN, SEL_RX_LANE0 + i), 0x00);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE, SEL_RX_LANE0 + i), 0x58);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_PVALUE1, SEL_RX_LANE0 + i), 0x8c);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_PVALUE2, SEL_RX_LANE0 + i), 0x02);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_OPTION, SEL_RX_LANE0 + i), 0xf6);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_POWER_SAVING_CTRL, SEL_RX_LANE0 + i), 0x69);
+ }
+
+ /* disable the mphy DME_SET cfg */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(MPHY_CFG, 0x0), MPHY_CFG_DISABLE);
+
+ ufs_sys_writel(host->mphy_base, 0x80, CMN_REG23);
+ ufs_sys_writel(host->mphy_base, 0xB5, TRSV0_REG14);
+ ufs_sys_writel(host->mphy_base, 0xB5, TRSV1_REG14);
+
+ ufs_sys_writel(host->mphy_base, 0x03, TRSV0_REG15);
+ ufs_sys_writel(host->mphy_base, 0x03, TRSV1_REG15);
+
+ ufs_sys_writel(host->mphy_base, 0x38, TRSV0_REG08);
+ ufs_sys_writel(host->mphy_base, 0x38, TRSV1_REG08);
+
+ ufs_sys_writel(host->mphy_base, 0x50, TRSV0_REG29);
+ ufs_sys_writel(host->mphy_base, 0x50, TRSV1_REG29);
+
+ ufs_sys_writel(host->mphy_base, 0x80, TRSV0_REG2E);
+ ufs_sys_writel(host->mphy_base, 0x80, TRSV1_REG2E);
+
+ ufs_sys_writel(host->mphy_base, 0x18, TRSV0_REG3C);
+ ufs_sys_writel(host->mphy_base, 0x18, TRSV1_REG3C);
+
+ ufs_sys_writel(host->mphy_base, 0x03, TRSV0_REG16);
+ ufs_sys_writel(host->mphy_base, 0x03, TRSV1_REG16);
+
+ ufs_sys_writel(host->mphy_base, 0x20, TRSV0_REG17);
+ ufs_sys_writel(host->mphy_base, 0x20, TRSV1_REG17);
+
+ ufs_sys_writel(host->mphy_base, 0xC0, TRSV0_REG18);
+ ufs_sys_writel(host->mphy_base, 0xC0, TRSV1_REG18);
+
+ ufs_sys_writel(host->mphy_base, 0x03, CMN_REG25);
+
+ ufs_sys_writel(host->mphy_base, 0x03, TRSV0_REG3D);
+ ufs_sys_writel(host->mphy_base, 0x03, TRSV1_REG3D);
+
+ ufs_sys_writel(host->mphy_base, 0xC0, CMN_REG23);
+ udelay(1);
+ ufs_sys_writel(host->mphy_base, 0x00, CMN_REG23);
+
+ usleep_range(200, 250);
+ /* start link up */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(MIB_T_DBG_CPORT_TX_ENDIAN, 0), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(MIB_T_DBG_CPORT_RX_ENDIAN, 0), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(N_DEVICEID, 0), 0x0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(N_DEVICEID_VALID, 0), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(T_PEERDEVICEID, 0), 0x1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(T_CONNECTIONSTATE, 0), 0x1);
+
+ return 0;
+}
+
+static int ufs_rockchip_common_init(struct ufs_hba *hba)
+{
+ struct device *dev = hba->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct ufs_rockchip_host *host;
+ int err;
+
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ host->ufs_sys_ctrl = devm_platform_ioremap_resource_byname(pdev, "hci_grf");
+ if (IS_ERR(host->ufs_sys_ctrl))
+ return dev_err_probe(dev, PTR_ERR(host->ufs_sys_ctrl),
+ "Failed to map HCI system control registers\n");
+
+ host->ufs_phy_ctrl = devm_platform_ioremap_resource_byname(pdev, "mphy_grf");
+ if (IS_ERR(host->ufs_phy_ctrl))
+ return dev_err_probe(dev, PTR_ERR(host->ufs_phy_ctrl),
+ "Failed to map mphy system control registers\n");
+
+ host->mphy_base = devm_platform_ioremap_resource_byname(pdev, "mphy");
+ if (IS_ERR(host->mphy_base))
+ return dev_err_probe(dev, PTR_ERR(host->mphy_base),
+ "Failed to map mphy base registers\n");
+
+ host->rst = devm_reset_control_array_get_exclusive(dev);
+ if (IS_ERR(host->rst))
+ return dev_err_probe(dev, PTR_ERR(host->rst),
+ "failed to get reset control\n");
+
+ ufs_rockchip_controller_reset(host);
+
+ host->ref_out_clk = devm_clk_get_enabled(dev, "ref_out");
+ if (IS_ERR(host->ref_out_clk))
+ return dev_err_probe(dev, PTR_ERR(host->ref_out_clk),
+ "ref_out clock unavailable\n");
+
+ host->rst_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(host->rst_gpio))
+ return dev_err_probe(dev, PTR_ERR(host->rst_gpio),
+ "failed to get reset gpio\n");
+
+ err = devm_clk_bulk_get_all_enabled(dev, &host->clks);
+ if (err < 0)
+ return dev_err_probe(dev, err, "failed to enable clocks\n");
+
+ host->hba = hba;
+
+ ufshcd_set_variant(hba, host);
+
+ return 0;
+}
+
+static int ufs_rockchip_rk3576_init(struct ufs_hba *hba)
+{
+ struct device *dev = hba->dev;
+ int ret;
+
+ hba->quirks = UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING;
+
+ /* Enable BKOPS when suspend */
+ hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
+ /* Enable putting device into deep sleep */
+ hba->caps |= UFSHCD_CAP_DEEPSLEEP;
+ /* Enable devfreq of UFS */
+ hba->caps |= UFSHCD_CAP_CLK_SCALING;
+ /* Enable WriteBooster */
+ hba->caps |= UFSHCD_CAP_WB_EN;
+
+ /* Set the default desired pm level in case no users set via sysfs */
+ ufs_rockchip_set_pm_lvl(hba);
+
+ ret = ufs_rockchip_common_init(hba);
+ if (ret)
+ return dev_err_probe(dev, ret, "ufs common init fail\n");
+
+ return 0;
+}
+
+static int ufs_rockchip_device_reset(struct ufs_hba *hba)
+{
+ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
+
+ gpiod_set_value_cansleep(host->rst_gpio, 1);
+ usleep_range(20, 25);
+
+ gpiod_set_value_cansleep(host->rst_gpio, 0);
+ usleep_range(20, 25);
+
+ return 0;
+}
+
+static const struct ufs_hba_variant_ops ufs_hba_rk3576_vops = {
+ .name = "rk3576",
+ .init = ufs_rockchip_rk3576_init,
+ .device_reset = ufs_rockchip_device_reset,
+ .hce_enable_notify = ufs_rockchip_hce_enable_notify,
+ .phy_initialization = ufs_rockchip_rk3576_phy_init,
+};
+
+static const struct of_device_id ufs_rockchip_of_match[] = {
+ { .compatible = "rockchip,rk3576-ufshc", .data = &ufs_hba_rk3576_vops },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ufs_rockchip_of_match);
+
+static int ufs_rockchip_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct ufs_hba_variant_ops *vops;
+ int err;
+
+ vops = device_get_match_data(dev);
+ if (!vops)
+ return dev_err_probe(dev, -ENODATA, "ufs_hba_variant_ops not defined.\n");
+
+ err = ufshcd_pltfrm_init(pdev, vops);
+ if (err)
+ return dev_err_probe(dev, err, "ufshcd_pltfrm_init failed\n");
+
+ return 0;
+}
+
+static void ufs_rockchip_remove(struct platform_device *pdev)
+{
+ ufshcd_pltfrm_remove(pdev);
+}
+
+#ifdef CONFIG_PM
+static int ufs_rockchip_runtime_suspend(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
+
+ clk_disable_unprepare(host->ref_out_clk);
+
+ /* Do not power down the genpd if rpm_lvl is less than level 5 */
+ dev_pm_genpd_rpm_always_on(dev, hba->rpm_lvl < UFS_PM_LVL_5);
+
+ return ufshcd_runtime_suspend(dev);
+}
+
+static int ufs_rockchip_runtime_resume(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
+ int err;
+
+ err = clk_prepare_enable(host->ref_out_clk);
+ if (err) {
+ dev_err(hba->dev, "failed to enable ref_out clock %d\n", err);
+ return err;
+ }
+
+ ufs_rockchip_controller_reset(host);
+
+ return ufshcd_runtime_resume(dev);
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int ufs_rockchip_system_suspend(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
+ int err;
+
+ /*
+ * If spm_lvl is less than level 5, it means we need to keep the host
+ * controller in powered-on state. So device_set_awake_path() is
+ * calling pm core to notify the genpd provider to meet this requirement
+ */
+ if (hba->spm_lvl < UFS_PM_LVL_5)
+ device_set_awake_path(dev);
+
+ err = ufshcd_system_suspend(dev);
+ if (err) {
+ dev_err(hba->dev, "UFSHCD system suspend failed %d\n", err);
+ return err;
+ }
+
+ clk_disable_unprepare(host->ref_out_clk);
+
+ return 0;
+}
+
+static int ufs_rockchip_system_resume(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
+ int err;
+
+ err = clk_prepare_enable(host->ref_out_clk);
+ if (err) {
+ dev_err(hba->dev, "failed to enable ref_out clock %d\n", err);
+ return err;
+ }
+
+ return ufshcd_system_resume(dev);
+}
+#endif
+
+static const struct dev_pm_ops ufs_rockchip_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ufs_rockchip_system_suspend, ufs_rockchip_system_resume)
+ SET_RUNTIME_PM_OPS(ufs_rockchip_runtime_suspend, ufs_rockchip_runtime_resume, NULL)
+ .prepare = ufshcd_suspend_prepare,
+ .complete = ufshcd_resume_complete,
+};
+
+static struct platform_driver ufs_rockchip_pltform = {
+ .probe = ufs_rockchip_probe,
+ .remove = ufs_rockchip_remove,
+ .driver = {
+ .name = "ufshcd-rockchip",
+ .pm = &ufs_rockchip_pm_ops,
+ .of_match_table = ufs_rockchip_of_match,
+ },
+};
+module_platform_driver(ufs_rockchip_pltform);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Rockchip UFS Host Driver");
diff --git a/drivers/ufs/host/ufs-rockchip.h b/drivers/ufs/host/ufs-rockchip.h
new file mode 100644
index 000000000..3ba6fb9f7
--- /dev/null
+++ b/drivers/ufs/host/ufs-rockchip.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Rockchip UFS Host Controller driver
+ *
+ * Copyright (C) 2025 Rockchip Electronics Co., Ltd.
+ */
+
+#ifndef _UFS_ROCKCHIP_H_
+#define _UFS_ROCKCHIP_H_
+
+#define SEL_TX_LANE0 0x0
+#define SEL_TX_LANE1 0x1
+#define SEL_TX_LANE2 0x2
+#define SEL_TX_LANE3 0x3
+#define SEL_RX_LANE0 0x4
+#define SEL_RX_LANE1 0x5
+#define SEL_RX_LANE2 0x6
+#define SEL_RX_LANE3 0x7
+
+#define VND_TX_CLK_PRD 0xAA
+#define VND_TX_CLK_PRD_EN 0xA9
+#define VND_TX_LINERESET_PVALUE2 0xAB
+#define VND_TX_LINERESET_PVALUE1 0xAC
+#define VND_TX_LINERESET_VALUE 0xAD
+#define VND_TX_BASE_NVALUE 0x93
+#define VND_TX_TASE_VALUE 0x94
+#define VND_TX_POWER_SAVING_CTRL 0x7F
+#define VND_RX_CLK_PRD 0x12
+#define VND_RX_CLK_PRD_EN 0x11
+#define VND_RX_LINERESET_PVALUE2 0x1B
+#define VND_RX_LINERESET_PVALUE1 0x1C
+#define VND_RX_LINERESET_VALUE 0x1D
+#define VND_RX_LINERESET_OPTION 0x25
+#define VND_RX_POWER_SAVING_CTRL 0x2F
+#define VND_RX_SAVE_DET_CTRL 0x1E
+
+#define CMN_REG23 0x8C
+#define CMN_REG25 0x94
+#define TRSV0_REG08 0xE0
+#define TRSV1_REG08 0x220
+#define TRSV0_REG14 0x110
+#define TRSV1_REG14 0x250
+#define TRSV0_REG15 0x134
+#define TRSV1_REG15 0x274
+#define TRSV0_REG16 0x128
+#define TRSV1_REG16 0x268
+#define TRSV0_REG17 0x12C
+#define TRSV1_REG17 0x26c
+#define TRSV0_REG18 0x120
+#define TRSV1_REG18 0x260
+#define TRSV0_REG29 0x164
+#define TRSV1_REG29 0x2A4
+#define TRSV0_REG2E 0x178
+#define TRSV1_REG2E 0x2B8
+#define TRSV0_REG3C 0x1B0
+#define TRSV1_REG3C 0x2F0
+#define TRSV0_REG3D 0x1B4
+#define TRSV1_REG3D 0x2F4
+
+#define MPHY_CFG 0x200
+#define MPHY_CFG_ENABLE 0x40
+#define MPHY_CFG_DISABLE 0x0
+
+#define MIB_T_DBG_CPORT_TX_ENDIAN 0xc022
+#define MIB_T_DBG_CPORT_RX_ENDIAN 0xc023
+
+struct ufs_rockchip_host {
+ struct ufs_hba *hba;
+ void __iomem *ufs_phy_ctrl;
+ void __iomem *ufs_sys_ctrl;
+ void __iomem *mphy_base;
+ struct gpio_desc *rst_gpio;
+ struct reset_control *rst;
+ struct clk *ref_out_clk;
+ struct clk_bulk_data *clks;
+ uint64_t caps;
+};
+
+#define ufs_sys_writel(base, val, reg) \
+ writel((val), (base) + (reg))
+#define ufs_sys_readl(base, reg) readl((base) + (reg))
+#define ufs_sys_set_bits(base, mask, reg) \
+ ufs_sys_writel( \
+ (base), ((mask) | (ufs_sys_readl((base), (reg)))), (reg))
+#define ufs_sys_ctrl_clr_bits(base, mask, reg) \
+ ufs_sys_writel((base), \
+ ((~(mask)) & (ufs_sys_readl((base), (reg)))), \
+ (reg))
+
+#endif /* _UFS_ROCKCHIP_H_ */
diff --git a/drivers/ufs/host/ufs-sprd.c b/drivers/ufs/host/ufs-sprd.c
new file mode 100644
index 000000000..a5e8c591b
--- /dev/null
+++ b/drivers/ufs/host/ufs-sprd.c
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UNISOC UFS Host Controller driver
+ *
+ * Copyright (C) 2022 Unisoc, Inc.
+ * Author: Zhe Wang <zhe.wang1@unisoc.com>
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/regulator/consumer.h>
+
+#include <ufs/ufshcd.h>
+#include "ufshcd-pltfrm.h"
+#include "ufs-sprd.h"
+
+static const struct of_device_id ufs_sprd_of_match[];
+
+static struct ufs_sprd_priv *ufs_sprd_get_priv_data(struct ufs_hba *hba)
+{
+ struct ufs_sprd_host *host = ufshcd_get_variant(hba);
+
+ WARN_ON(!host->priv);
+ return host->priv;
+}
+
+static void ufs_sprd_regmap_update(struct ufs_sprd_priv *priv, unsigned int index,
+ unsigned int reg, unsigned int bits, unsigned int val)
+{
+ regmap_update_bits(priv->sysci[index].regmap, reg, bits, val);
+}
+
+static void ufs_sprd_regmap_read(struct ufs_sprd_priv *priv, unsigned int index,
+ unsigned int reg, unsigned int *val)
+{
+ regmap_read(priv->sysci[index].regmap, reg, val);
+}
+
+static void ufs_sprd_get_unipro_ver(struct ufs_hba *hba)
+{
+ struct ufs_sprd_host *host = ufshcd_get_variant(hba);
+
+ if (ufshcd_dme_get(hba, UIC_ARG_MIB(PA_LOCALVERINFO), &host->unipro_ver))
+ host->unipro_ver = 0;
+}
+
+static void ufs_sprd_ctrl_uic_compl(struct ufs_hba *hba, bool enable)
+{
+ u32 set = ufshcd_readl(hba, REG_INTERRUPT_ENABLE);
+
+ if (enable == true)
+ set |= UIC_COMMAND_COMPL;
+ else
+ set &= ~UIC_COMMAND_COMPL;
+ ufshcd_writel(hba, set, REG_INTERRUPT_ENABLE);
+}
+
+static int ufs_sprd_get_reset_ctrl(struct device *dev, struct ufs_sprd_rst *rci)
+{
+ rci->rc = devm_reset_control_get(dev, rci->name);
+ if (IS_ERR(rci->rc)) {
+ dev_err(dev, "failed to get reset ctrl:%s\n", rci->name);
+ return PTR_ERR(rci->rc);
+ }
+
+ return 0;
+}
+
+static int ufs_sprd_get_syscon_reg(struct device *dev, struct ufs_sprd_syscon *sysci)
+{
+ sysci->regmap = syscon_regmap_lookup_by_phandle(dev->of_node, sysci->name);
+ if (IS_ERR(sysci->regmap)) {
+ dev_err(dev, "failed to get ufs syscon:%s\n", sysci->name);
+ return PTR_ERR(sysci->regmap);
+ }
+
+ return 0;
+}
+
+static int ufs_sprd_get_vreg(struct device *dev, struct ufs_sprd_vreg *vregi)
+{
+ vregi->vreg = devm_regulator_get(dev, vregi->name);
+ if (IS_ERR(vregi->vreg)) {
+ dev_err(dev, "failed to get vreg:%s\n", vregi->name);
+ return PTR_ERR(vregi->vreg);
+ }
+
+ return 0;
+}
+
+static int ufs_sprd_parse_dt(struct device *dev, struct ufs_hba *hba, struct ufs_sprd_host *host)
+{
+ u32 i;
+ struct ufs_sprd_priv *priv = host->priv;
+ int ret = 0;
+
+ /* Parse UFS reset ctrl info */
+ for (i = 0; i < SPRD_UFS_RST_MAX; i++) {
+ if (!priv->rci[i].name)
+ continue;
+ ret = ufs_sprd_get_reset_ctrl(dev, &priv->rci[i]);
+ if (ret)
+ goto out;
+ }
+
+ /* Parse UFS syscon reg info */
+ for (i = 0; i < SPRD_UFS_SYSCON_MAX; i++) {
+ if (!priv->sysci[i].name)
+ continue;
+ ret = ufs_sprd_get_syscon_reg(dev, &priv->sysci[i]);
+ if (ret)
+ goto out;
+ }
+
+ /* Parse UFS vreg info */
+ for (i = 0; i < SPRD_UFS_VREG_MAX; i++) {
+ if (!priv->vregi[i].name)
+ continue;
+ ret = ufs_sprd_get_vreg(dev, &priv->vregi[i]);
+ if (ret)
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+static int ufs_sprd_common_init(struct ufs_hba *hba)
+{
+ struct device *dev = hba->dev;
+ struct ufs_sprd_host *host;
+ struct platform_device __maybe_unused *pdev = to_platform_device(dev);
+ const struct of_device_id *of_id;
+ int ret = 0;
+
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ of_id = of_match_node(ufs_sprd_of_match, pdev->dev.of_node);
+ if (of_id->data != NULL)
+ host->priv = container_of(of_id->data, struct ufs_sprd_priv,
+ ufs_hba_sprd_vops);
+
+ host->hba = hba;
+ ufshcd_set_variant(hba, host);
+
+ hba->caps |= UFSHCD_CAP_CLK_GATING |
+ UFSHCD_CAP_CRYPTO |
+ UFSHCD_CAP_WB_EN;
+ hba->quirks |= UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS;
+
+ ret = ufs_sprd_parse_dt(dev, hba, host);
+
+ return ret;
+}
+
+static int sprd_ufs_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_sprd_host *host = ufshcd_get_variant(hba);
+
+ if (status == PRE_CHANGE) {
+ if (host->unipro_ver >= UFS_UNIPRO_VER_1_8)
+ ufshcd_dme_configure_adapt(hba, dev_req_params->gear_tx,
+ PA_INITIAL_ADAPT);
+ }
+
+ return 0;
+}
+
+static int ufs_sprd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op,
+ enum ufs_notify_change_status status)
+{
+ unsigned long flags;
+
+ if (status == PRE_CHANGE) {
+ if (ufshcd_is_auto_hibern8_supported(hba)) {
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ ufshcd_writel(hba, 0, REG_AUTO_HIBERNATE_IDLE_TIMER);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ }
+ }
+
+ return 0;
+}
+
+static void ufs_sprd_n6_host_reset(struct ufs_hba *hba)
+{
+ struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba);
+
+ dev_info(hba->dev, "ufs host reset!\n");
+
+ reset_control_assert(priv->rci[SPRD_UFSHCI_SOFT_RST].rc);
+ usleep_range(1000, 1100);
+ reset_control_deassert(priv->rci[SPRD_UFSHCI_SOFT_RST].rc);
+}
+
+static int ufs_sprd_n6_device_reset(struct ufs_hba *hba)
+{
+ struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba);
+
+ dev_info(hba->dev, "ufs device reset!\n");
+
+ reset_control_assert(priv->rci[SPRD_UFS_DEV_RST].rc);
+ usleep_range(1000, 1100);
+ reset_control_deassert(priv->rci[SPRD_UFS_DEV_RST].rc);
+
+ return 0;
+}
+
+static void ufs_sprd_n6_key_acc_enable(struct ufs_hba *hba)
+{
+ u32 val;
+ u32 retry = 10;
+ struct arm_smccc_res res;
+
+check_hce:
+ /* Key access only can be enabled under HCE enable */
+ val = ufshcd_readl(hba, REG_CONTROLLER_ENABLE);
+ if (!(val & CONTROLLER_ENABLE)) {
+ ufs_sprd_n6_host_reset(hba);
+ val |= CONTROLLER_ENABLE;
+ ufshcd_writel(hba, val, REG_CONTROLLER_ENABLE);
+ usleep_range(1000, 1100);
+ if (retry) {
+ retry--;
+ goto check_hce;
+ }
+ goto disable_crypto;
+ }
+
+ arm_smccc_smc(SPRD_SIP_SVC_STORAGE_UFS_CRYPTO_ENABLE,
+ 0, 0, 0, 0, 0, 0, 0, &res);
+ if (!res.a0)
+ return;
+
+disable_crypto:
+ dev_err(hba->dev, "key reg access enable fail, disable crypto\n");
+ hba->caps &= ~UFSHCD_CAP_CRYPTO;
+}
+
+static int ufs_sprd_n6_init(struct ufs_hba *hba)
+{
+ struct ufs_sprd_priv *priv;
+ int ret = 0;
+
+ ret = ufs_sprd_common_init(hba);
+ if (ret != 0)
+ return ret;
+
+ priv = ufs_sprd_get_priv_data(hba);
+
+ ret = regulator_enable(priv->vregi[SPRD_UFS_VDD_MPHY].vreg);
+ if (ret)
+ return -ENODEV;
+
+ if (hba->caps & UFSHCD_CAP_CRYPTO)
+ ufs_sprd_n6_key_acc_enable(hba);
+
+ return 0;
+}
+
+static int ufs_sprd_n6_phy_init(struct ufs_hba *hba)
+{
+ int ret = 0;
+ uint32_t val = 0;
+ uint32_t retry = 10;
+ uint32_t offset;
+ struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(CBREFCLKCTRL2), 0x90);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(CBCRCTRL), 0x01);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RXSQCONTROL,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x01);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RXSQCONTROL,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x01);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(CBRATESEL), 0x01);
+
+ do {
+ /* phy_sram_init_done */
+ ufs_sprd_regmap_read(priv, SPRD_UFS_ANLG, 0xc, &val);
+ if ((val & 0x1) == 0x1) {
+ for (offset = 0x40; offset < 0x42; offset++) {
+ /* Lane afe calibration */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGADDRLSB), 0x1c);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGADDRMSB), offset);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGWRLSB), 0x04);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGWRMSB), 0x00);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGRDWRSEL), 0x01);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01);
+ }
+
+ goto update_phy;
+ }
+ udelay(1000);
+ retry--;
+ } while (retry > 0);
+
+ ret = -ETIMEDOUT;
+ goto out;
+
+update_phy:
+ /* phy_sram_ext_ld_done */
+ ufs_sprd_regmap_update(priv, SPRD_UFS_ANLG, 0xc, 0x2, 0);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYDISABLE), 0x0);
+out:
+ return ret;
+}
+
+
+static int sprd_ufs_n6_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ int err = 0;
+ struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba);
+
+ if (status == PRE_CHANGE) {
+ /* phy_sram_ext_ld_done */
+ ufs_sprd_regmap_update(priv, SPRD_UFS_ANLG, 0xc, 0x2, 0x2);
+ /* phy_sram_bypass */
+ ufs_sprd_regmap_update(priv, SPRD_UFS_ANLG, 0xc, 0x4, 0x4);
+
+ ufs_sprd_n6_host_reset(hba);
+
+ if (hba->caps & UFSHCD_CAP_CRYPTO)
+ ufs_sprd_n6_key_acc_enable(hba);
+ }
+
+ if (status == POST_CHANGE) {
+ err = ufs_sprd_n6_phy_init(hba);
+ if (err) {
+ dev_err(hba->dev, "Phy setup failed (%d)\n", err);
+ goto out;
+ }
+
+ ufs_sprd_get_unipro_ver(hba);
+ }
+out:
+ return err;
+}
+
+static void sprd_ufs_n6_h8_notify(struct ufs_hba *hba,
+ enum uic_cmd_dme cmd,
+ enum ufs_notify_change_status status)
+{
+ struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba);
+
+ if (status == PRE_CHANGE) {
+ if (cmd == UIC_CMD_DME_HIBER_ENTER)
+ /*
+ * Disable UIC COMPL INTR to prevent access to UFSHCI after
+ * checking HCS.UPMCRS
+ */
+ ufs_sprd_ctrl_uic_compl(hba, false);
+
+ if (cmd == UIC_CMD_DME_HIBER_EXIT) {
+ ufs_sprd_regmap_update(priv, SPRD_UFS_AON_APB, APB_UFSDEV_REG,
+ APB_UFSDEV_REFCLK_EN, APB_UFSDEV_REFCLK_EN);
+ ufs_sprd_regmap_update(priv, SPRD_UFS_AON_APB, APB_USB31PLL_CTRL,
+ APB_USB31PLLV_REF2MPHY, APB_USB31PLLV_REF2MPHY);
+ }
+ }
+
+ if (status == POST_CHANGE) {
+ if (cmd == UIC_CMD_DME_HIBER_EXIT)
+ ufs_sprd_ctrl_uic_compl(hba, true);
+
+ if (cmd == UIC_CMD_DME_HIBER_ENTER) {
+ ufs_sprd_regmap_update(priv, SPRD_UFS_AON_APB, APB_UFSDEV_REG,
+ APB_UFSDEV_REFCLK_EN, 0);
+ ufs_sprd_regmap_update(priv, SPRD_UFS_AON_APB, APB_USB31PLL_CTRL,
+ APB_USB31PLLV_REF2MPHY, 0);
+ }
+ }
+}
+
+static struct ufs_sprd_priv n6_ufs = {
+ .rci[SPRD_UFSHCI_SOFT_RST] = { .name = "controller", },
+ .rci[SPRD_UFS_DEV_RST] = { .name = "device", },
+
+ .sysci[SPRD_UFS_ANLG] = { .name = "sprd,ufs-anlg-syscon", },
+ .sysci[SPRD_UFS_AON_APB] = { .name = "sprd,aon-apb-syscon", },
+
+ .vregi[SPRD_UFS_VDD_MPHY] = { .name = "vdd-mphy", },
+
+ .ufs_hba_sprd_vops = {
+ .name = "sprd,ums9620-ufs",
+ .init = ufs_sprd_n6_init,
+ .hce_enable_notify = sprd_ufs_n6_hce_enable_notify,
+ .pwr_change_notify = sprd_ufs_pwr_change_notify,
+ .hibern8_notify = sprd_ufs_n6_h8_notify,
+ .device_reset = ufs_sprd_n6_device_reset,
+ .suspend = ufs_sprd_suspend,
+ },
+};
+
+static const struct of_device_id __maybe_unused ufs_sprd_of_match[] = {
+ { .compatible = "sprd,ums9620-ufs", .data = &n6_ufs.ufs_hba_sprd_vops},
+ {},
+};
+MODULE_DEVICE_TABLE(of, ufs_sprd_of_match);
+
+static int ufs_sprd_probe(struct platform_device *pdev)
+{
+ int err;
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *of_id;
+
+ of_id = of_match_node(ufs_sprd_of_match, dev->of_node);
+ err = ufshcd_pltfrm_init(pdev, of_id->data);
+ if (err)
+ dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err);
+
+ return err;
+}
+
+static void ufs_sprd_remove(struct platform_device *pdev)
+{
+ ufshcd_pltfrm_remove(pdev);
+}
+
+static const struct dev_pm_ops ufs_sprd_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
+ SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
+ .prepare = ufshcd_suspend_prepare,
+ .complete = ufshcd_resume_complete,
+};
+
+static struct platform_driver ufs_sprd_pltform = {
+ .probe = ufs_sprd_probe,
+ .remove = ufs_sprd_remove,
+ .driver = {
+ .name = "ufshcd-sprd",
+ .pm = &ufs_sprd_pm_ops,
+ .of_match_table = of_match_ptr(ufs_sprd_of_match),
+ },
+};
+module_platform_driver(ufs_sprd_pltform);
+
+MODULE_AUTHOR("Zhe Wang <zhe.wang1@unisoc.com>");
+MODULE_DESCRIPTION("Unisoc UFS Host Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/ufs/host/ufs-sprd.h b/drivers/ufs/host/ufs-sprd.h
new file mode 100644
index 000000000..26ad5c3af
--- /dev/null
+++ b/drivers/ufs/host/ufs-sprd.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * UNISOC UFS Host Controller driver
+ *
+ * Copyright (C) 2022 Unisoc, Inc.
+ * Author: Zhe Wang <zhe.wang1@unisoc.com>
+ */
+
+#ifndef _UFS_SPRD_H_
+#define _UFS_SPRD_H_
+
+/* Vendor specific attributes */
+#define RXSQCONTROL 0x8009
+#define CBRATESEL 0x8114
+#define CBCREGADDRLSB 0x8116
+#define CBCREGADDRMSB 0x8117
+#define CBCREGWRLSB 0x8118
+#define CBCREGWRMSB 0x8119
+#define CBCREGRDWRSEL 0x811C
+#define CBCRCTRL 0x811F
+#define CBREFCLKCTRL2 0x8132
+#define VS_MPHYDISABLE 0xD0C1
+
+#define APB_UFSDEV_REG 0xCE8
+#define APB_UFSDEV_REFCLK_EN 0x2
+#define APB_USB31PLL_CTRL 0xCFC
+#define APB_USB31PLLV_REF2MPHY 0x1
+
+#define SPRD_SIP_SVC_STORAGE_UFS_CRYPTO_ENABLE \
+ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \
+ ARM_SMCCC_SMC_32, \
+ ARM_SMCCC_OWNER_SIP, \
+ 0x0301)
+
+enum SPRD_UFS_RST_INDEX {
+ SPRD_UFSHCI_SOFT_RST,
+ SPRD_UFS_DEV_RST,
+
+ SPRD_UFS_RST_MAX
+};
+
+enum SPRD_UFS_SYSCON_INDEX {
+ SPRD_UFS_ANLG,
+ SPRD_UFS_AON_APB,
+
+ SPRD_UFS_SYSCON_MAX
+};
+
+enum SPRD_UFS_VREG_INDEX {
+ SPRD_UFS_VDD_MPHY,
+
+ SPRD_UFS_VREG_MAX
+};
+
+struct ufs_sprd_rst {
+ const char *name;
+ struct reset_control *rc;
+};
+
+struct ufs_sprd_syscon {
+ const char *name;
+ struct regmap *regmap;
+};
+
+struct ufs_sprd_vreg {
+ const char *name;
+ struct regulator *vreg;
+};
+
+struct ufs_sprd_priv {
+ struct ufs_sprd_rst rci[SPRD_UFS_RST_MAX];
+ struct ufs_sprd_syscon sysci[SPRD_UFS_SYSCON_MAX];
+ struct ufs_sprd_vreg vregi[SPRD_UFS_VREG_MAX];
+ const struct ufs_hba_variant_ops ufs_hba_sprd_vops;
+};
+
+struct ufs_sprd_host {
+ struct ufs_hba *hba;
+ struct ufs_sprd_priv *priv;
+ void __iomem *ufs_dbg_mmio;
+
+ enum ufs_unipro_ver unipro_ver;
+};
+
+#endif /* _UFS_SPRD_H_ */
diff --git a/drivers/ufs/host/ufshcd-dwc.c b/drivers/ufs/host/ufshcd-dwc.c
new file mode 100644
index 000000000..21b1cf912
--- /dev/null
+++ b/drivers/ufs/host/ufshcd-dwc.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UFS Host driver for Synopsys Designware Core
+ *
+ * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
+ *
+ * Authors: Joao Pinto <jpinto@synopsys.com>
+ */
+
+#include <linux/module.h>
+
+#include <ufs/ufshcd.h>
+#include <ufs/unipro.h>
+
+#include "ufshcd-dwc.h"
+#include "ufshci-dwc.h"
+
+int ufshcd_dwc_dme_set_attrs(struct ufs_hba *hba,
+ const struct ufshcd_dme_attr_val *v, int n)
+{
+ int ret = 0;
+ int attr_node = 0;
+
+ for (attr_node = 0; attr_node < n; attr_node++) {
+ ret = ufshcd_dme_set_attr(hba, v[attr_node].attr_sel,
+ ATTR_SET_NOR, v[attr_node].mib_val, v[attr_node].peer);
+
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(ufshcd_dwc_dme_set_attrs);
+
+/**
+ * ufshcd_dwc_program_clk_div() - program clock divider.
+ * @hba: Private Structure pointer
+ * @divider_val: clock divider value to be programmed
+ *
+ */
+static void ufshcd_dwc_program_clk_div(struct ufs_hba *hba, u32 divider_val)
+{
+ ufshcd_writel(hba, divider_val, DWC_UFS_REG_HCLKDIV);
+}
+
+/**
+ * ufshcd_dwc_link_is_up() - check if link is up.
+ * @hba: private structure pointer
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+static int ufshcd_dwc_link_is_up(struct ufs_hba *hba)
+{
+ int dme_result = 0;
+
+ ufshcd_dme_get(hba, UIC_ARG_MIB(VS_POWERSTATE), &dme_result);
+
+ if (dme_result == UFSHCD_LINK_IS_UP) {
+ ufshcd_set_link_active(hba);
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ * ufshcd_dwc_connection_setup() - configure unipro attributes.
+ * @hba: pointer to drivers private data
+ *
+ * This function configures both the local side (host) and the peer side
+ * (device) unipro attributes to establish the connection to application/
+ * cport.
+ * This function is not required if the hardware is properly configured to
+ * have this connection setup on reset. But invoking this function does no
+ * harm and should be fine even working with any ufs device.
+ *
+ * Return: 0 on success non-zero value on failure.
+ */
+static int ufshcd_dwc_connection_setup(struct ufs_hba *hba)
+{
+ static const struct ufshcd_dme_attr_val setup_attrs[] = {
+ { UIC_ARG_MIB(T_CONNECTIONSTATE), 0, DME_LOCAL },
+ { UIC_ARG_MIB(N_DEVICEID), 0, DME_LOCAL },
+ { UIC_ARG_MIB(N_DEVICEID_VALID), 0, DME_LOCAL },
+ { UIC_ARG_MIB(T_PEERDEVICEID), 1, DME_LOCAL },
+ { UIC_ARG_MIB(T_PEERCPORTID), 0, DME_LOCAL },
+ { UIC_ARG_MIB(T_TRAFFICCLASS), 0, DME_LOCAL },
+ { UIC_ARG_MIB(T_CPORTFLAGS), 0x6, DME_LOCAL },
+ { UIC_ARG_MIB(T_CPORTMODE), 1, DME_LOCAL },
+ { UIC_ARG_MIB(T_CONNECTIONSTATE), 1, DME_LOCAL },
+ { UIC_ARG_MIB(T_CONNECTIONSTATE), 0, DME_PEER },
+ { UIC_ARG_MIB(N_DEVICEID), 1, DME_PEER },
+ { UIC_ARG_MIB(N_DEVICEID_VALID), 1, DME_PEER },
+ { UIC_ARG_MIB(T_PEERDEVICEID), 1, DME_PEER },
+ { UIC_ARG_MIB(T_PEERCPORTID), 0, DME_PEER },
+ { UIC_ARG_MIB(T_TRAFFICCLASS), 0, DME_PEER },
+ { UIC_ARG_MIB(T_CPORTFLAGS), 0x6, DME_PEER },
+ { UIC_ARG_MIB(T_CPORTMODE), 1, DME_PEER },
+ { UIC_ARG_MIB(T_CONNECTIONSTATE), 1, DME_PEER }
+ };
+
+ return ufshcd_dwc_dme_set_attrs(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
+}
+
+/**
+ * ufshcd_dwc_link_startup_notify() - program clock divider.
+ * @hba: private structure pointer
+ * @status: Callback notify status
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+int ufshcd_dwc_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ int err = 0;
+
+ if (status == PRE_CHANGE) {
+ ufshcd_dwc_program_clk_div(hba, DWC_UFS_REG_HCLKDIV_DIV_125);
+
+ err = ufshcd_vops_phy_initialization(hba);
+ if (err) {
+ dev_err(hba->dev, "Phy setup failed (%d)\n", err);
+ goto out;
+ }
+ } else { /* POST_CHANGE */
+ err = ufshcd_dwc_link_is_up(hba);
+ if (err) {
+ dev_err(hba->dev, "Link is not up\n");
+ goto out;
+ }
+
+ err = ufshcd_dwc_connection_setup(hba);
+ if (err)
+ dev_err(hba->dev, "Connection setup failed (%d)\n",
+ err);
+ }
+
+out:
+ return err;
+}
+EXPORT_SYMBOL(ufshcd_dwc_link_startup_notify);
+
+MODULE_AUTHOR("Joao Pinto <Joao.Pinto@synopsys.com>");
+MODULE_DESCRIPTION("UFS Host driver for Synopsys Designware Core");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/ufs/host/ufshcd-dwc.h b/drivers/ufs/host/ufshcd-dwc.h
new file mode 100644
index 000000000..c618bb914
--- /dev/null
+++ b/drivers/ufs/host/ufshcd-dwc.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * UFS Host driver for Synopsys Designware Core
+ *
+ * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
+ *
+ * Authors: Joao Pinto <jpinto@synopsys.com>
+ */
+
+#ifndef _UFSHCD_DWC_H
+#define _UFSHCD_DWC_H
+
+#include <ufs/ufshcd.h>
+
+/* RMMI Attributes */
+#define CBREFCLKCTRL2 0x8132
+#define CBCRCTRL 0x811F
+#define CBC10DIRECTCONF2 0x810E
+#define CBRATESEL 0x8114
+#define CBCREGADDRLSB 0x8116
+#define CBCREGADDRMSB 0x8117
+#define CBCREGWRLSB 0x8118
+#define CBCREGWRMSB 0x8119
+#define CBCREGRDLSB 0x811A
+#define CBCREGRDMSB 0x811B
+#define CBCREGRDWRSEL 0x811C
+
+#define CBREFREFCLK_GATE_OVR_EN BIT(7)
+
+/* M-PHY Attributes */
+#define MTX_FSM_STATE 0x41
+#define MRX_FSM_STATE 0xC1
+
+/* M-PHY registers */
+#define RX_OVRD_IN_1(n) (0x3006 + ((n) * 0x100))
+#define RX_PCS_OUT(n) (0x300F + ((n) * 0x100))
+#define FAST_FLAGS(n) (0x401C + ((n) * 0x100))
+#define RX_AFE_ATT_IDAC(n) (0x4000 + ((n) * 0x100))
+#define RX_AFE_CTLE_IDAC(n) (0x4001 + ((n) * 0x100))
+#define FW_CALIB_CCFG(n) (0x404D + ((n) * 0x100))
+
+/* Tx/Rx FSM state */
+enum rx_fsm_state {
+ RX_STATE_DISABLED = 0,
+ RX_STATE_HIBERN8 = 1,
+ RX_STATE_SLEEP = 2,
+ RX_STATE_STALL = 3,
+ RX_STATE_LSBURST = 4,
+ RX_STATE_HSBURST = 5,
+};
+
+enum tx_fsm_state {
+ TX_STATE_DISABLED = 0,
+ TX_STATE_HIBERN8 = 1,
+ TX_STATE_SLEEP = 2,
+ TX_STATE_STALL = 3,
+ TX_STATE_LSBURST = 4,
+ TX_STATE_HSBURST = 5,
+};
+
+struct ufshcd_dme_attr_val {
+ u32 attr_sel;
+ u32 mib_val;
+ u8 peer;
+};
+
+int ufshcd_dwc_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status);
+int ufshcd_dwc_dme_set_attrs(struct ufs_hba *hba,
+ const struct ufshcd_dme_attr_val *v, int n);
+#endif /* End of Header */
diff --git a/drivers/ufs/host/ufshcd-pci.c b/drivers/ufs/host/ufshcd-pci.c
new file mode 100644
index 000000000..21bb11c72
--- /dev/null
+++ b/drivers/ufs/host/ufshcd-pci.c
@@ -0,0 +1,777 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Flash Storage Host controller PCI glue driver
+ *
+ * Copyright (C) 2011-2013 Samsung India Software Operations
+ *
+ * Authors:
+ * Santosh Yaraganavi <santosh.sy@samsung.com>
+ * Vinayak Holikatti <h.vinayak@samsung.com>
+ */
+
+#include <ufs/ufshcd.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_qos.h>
+#include <linux/suspend.h>
+#include <linux/debugfs.h>
+#include <linux/uuid.h>
+#include <linux/acpi.h>
+#include <linux/gpio/consumer.h>
+
+#define MAX_SUPP_MAC 64
+
+enum intel_ufs_dsm_func_id {
+ INTEL_DSM_FNS = 0,
+ INTEL_DSM_RESET = 1,
+};
+
+struct intel_host {
+ u32 dsm_fns;
+ u32 active_ltr;
+ u32 idle_ltr;
+ int saved_spm_lvl;
+ struct dentry *debugfs_root;
+ struct gpio_desc *reset_gpio;
+};
+
+static const guid_t intel_dsm_guid =
+ GUID_INIT(0x1A4832A0, 0x7D03, 0x43CA,
+ 0xB0, 0x20, 0xF6, 0xDC, 0xD1, 0x2A, 0x19, 0x50);
+
+static bool __intel_dsm_supported(struct intel_host *host,
+ enum intel_ufs_dsm_func_id fn)
+{
+ return fn < 32 && fn >= 0 && (host->dsm_fns & (1u << fn));
+}
+
+#define INTEL_DSM_SUPPORTED(host, name) \
+ __intel_dsm_supported(host, INTEL_DSM_##name)
+
+static int __intel_dsm(struct intel_host *intel_host, struct device *dev,
+ unsigned int fn, u32 *result)
+{
+ union acpi_object *obj;
+ int err = 0;
+ size_t len;
+
+ obj = acpi_evaluate_dsm_typed(ACPI_HANDLE(dev), &intel_dsm_guid, 0, fn, NULL,
+ ACPI_TYPE_BUFFER);
+ if (!obj)
+ return -EOPNOTSUPP;
+
+ if (obj->buffer.length < 1) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ len = min_t(size_t, obj->buffer.length, 4);
+
+ *result = 0;
+ memcpy(result, obj->buffer.pointer, len);
+out:
+ ACPI_FREE(obj);
+
+ return err;
+}
+
+static int intel_dsm(struct intel_host *intel_host, struct device *dev,
+ unsigned int fn, u32 *result)
+{
+ if (!__intel_dsm_supported(intel_host, fn))
+ return -EOPNOTSUPP;
+
+ return __intel_dsm(intel_host, dev, fn, result);
+}
+
+static void intel_dsm_init(struct intel_host *intel_host, struct device *dev)
+{
+ int err;
+
+ err = __intel_dsm(intel_host, dev, INTEL_DSM_FNS, &intel_host->dsm_fns);
+ dev_dbg(dev, "DSM fns %#x, error %d\n", intel_host->dsm_fns, err);
+}
+
+static int ufs_intel_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ /* Cannot enable ICE until after HC enable */
+ if (status == POST_CHANGE && hba->caps & UFSHCD_CAP_CRYPTO) {
+ u32 hce = ufshcd_readl(hba, REG_CONTROLLER_ENABLE);
+
+ hce |= CRYPTO_GENERAL_ENABLE;
+ ufshcd_writel(hba, hce, REG_CONTROLLER_ENABLE);
+ }
+
+ return 0;
+}
+
+static int ufs_intel_disable_lcc(struct ufs_hba *hba)
+{
+ u32 attr = UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE);
+ u32 lcc_enable = 0;
+
+ ufshcd_dme_get(hba, attr, &lcc_enable);
+ if (lcc_enable)
+ ufshcd_disable_host_tx_lcc(hba);
+
+ return 0;
+}
+
+static int ufs_intel_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ int err = 0;
+
+ switch (status) {
+ case PRE_CHANGE:
+ err = ufs_intel_disable_lcc(hba);
+ break;
+ case POST_CHANGE:
+ break;
+ default:
+ break;
+ }
+
+ return err;
+}
+
+static int ufs_intel_set_lanes(struct ufs_hba *hba, u32 lanes)
+{
+ struct ufs_pa_layer_attr pwr_info = hba->pwr_info;
+ int ret;
+
+ pwr_info.lane_rx = lanes;
+ pwr_info.lane_tx = lanes;
+ ret = ufshcd_change_power_mode(hba, &pwr_info,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+ if (ret)
+ dev_err(hba->dev, "%s: Setting %u lanes, err = %d\n",
+ __func__, lanes, ret);
+ return ret;
+}
+
+static int ufs_intel_lkf_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ int err = 0;
+
+ switch (status) {
+ case PRE_CHANGE:
+ if (ufshcd_is_hs_mode(dev_req_params) &&
+ (hba->pwr_info.lane_rx != 2 || hba->pwr_info.lane_tx != 2))
+ ufs_intel_set_lanes(hba, 2);
+ break;
+ case POST_CHANGE:
+ if (ufshcd_is_hs_mode(dev_req_params)) {
+ u32 peer_granularity;
+
+ usleep_range(1000, 1250);
+ err = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_GRANULARITY),
+ &peer_granularity);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return err;
+}
+
+static int ufs_intel_nvl_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status stage,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ int adapt_val;
+
+ if (stage != PRE_CHANGE || hba->ufs_version < ufshci_version(4, 0))
+ return 0;
+
+ if (dev_req_params->pwr_tx == FAST_MODE || dev_req_params->pwr_tx == FASTAUTO_MODE)
+ adapt_val = PA_INITIAL_ADAPT;
+ else
+ adapt_val = PA_NO_ADAPT;
+
+ ufshcd_dme_configure_adapt(hba, dev_req_params->gear_tx, adapt_val);
+
+ return 0;
+}
+
+static int ufs_intel_lkf_apply_dev_quirks(struct ufs_hba *hba)
+{
+ u32 granularity, peer_granularity;
+ u32 pa_tactivate, peer_pa_tactivate;
+ int ret;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_GRANULARITY), &granularity);
+ if (ret)
+ goto out;
+
+ ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_GRANULARITY), &peer_granularity);
+ if (ret)
+ goto out;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_TACTIVATE), &pa_tactivate);
+ if (ret)
+ goto out;
+
+ ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_TACTIVATE), &peer_pa_tactivate);
+ if (ret)
+ goto out;
+
+ if (granularity == peer_granularity) {
+ u32 new_peer_pa_tactivate = pa_tactivate + 2;
+
+ ret = ufshcd_dme_peer_set(hba, UIC_ARG_MIB(PA_TACTIVATE), new_peer_pa_tactivate);
+ }
+out:
+ return ret;
+}
+
+#define INTEL_ACTIVELTR 0x804
+#define INTEL_IDLELTR 0x808
+
+#define INTEL_LTR_REQ BIT(15)
+#define INTEL_LTR_SCALE_MASK GENMASK(11, 10)
+#define INTEL_LTR_SCALE_1US (2 << 10)
+#define INTEL_LTR_SCALE_32US (3 << 10)
+#define INTEL_LTR_VALUE_MASK GENMASK(9, 0)
+
+static void intel_cache_ltr(struct ufs_hba *hba)
+{
+ struct intel_host *host = ufshcd_get_variant(hba);
+
+ host->active_ltr = readl(hba->mmio_base + INTEL_ACTIVELTR);
+ host->idle_ltr = readl(hba->mmio_base + INTEL_IDLELTR);
+}
+
+static void intel_ltr_set(struct device *dev, s32 val)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct intel_host *host = ufshcd_get_variant(hba);
+ u32 ltr;
+
+ pm_runtime_get_sync(dev);
+
+ /*
+ * Program latency tolerance (LTR) accordingly what has been asked
+ * by the PM QoS layer or disable it in case we were passed
+ * negative value or PM_QOS_LATENCY_ANY.
+ */
+ ltr = readl(hba->mmio_base + INTEL_ACTIVELTR);
+
+ if (val == PM_QOS_LATENCY_ANY || val < 0) {
+ ltr &= ~INTEL_LTR_REQ;
+ } else {
+ ltr |= INTEL_LTR_REQ;
+ ltr &= ~INTEL_LTR_SCALE_MASK;
+ ltr &= ~INTEL_LTR_VALUE_MASK;
+
+ if (val > INTEL_LTR_VALUE_MASK) {
+ val >>= 5;
+ if (val > INTEL_LTR_VALUE_MASK)
+ val = INTEL_LTR_VALUE_MASK;
+ ltr |= INTEL_LTR_SCALE_32US | val;
+ } else {
+ ltr |= INTEL_LTR_SCALE_1US | val;
+ }
+ }
+
+ if (ltr == host->active_ltr)
+ goto out;
+
+ writel(ltr, hba->mmio_base + INTEL_ACTIVELTR);
+ writel(ltr, hba->mmio_base + INTEL_IDLELTR);
+
+ /* Cache the values into intel_host structure */
+ intel_cache_ltr(hba);
+out:
+ pm_runtime_put(dev);
+}
+
+static void intel_ltr_expose(struct device *dev)
+{
+ dev->power.set_latency_tolerance = intel_ltr_set;
+ dev_pm_qos_expose_latency_tolerance(dev);
+}
+
+static void intel_ltr_hide(struct device *dev)
+{
+ dev_pm_qos_hide_latency_tolerance(dev);
+ dev->power.set_latency_tolerance = NULL;
+}
+
+static void intel_add_debugfs(struct ufs_hba *hba)
+{
+ struct dentry *dir = debugfs_create_dir(dev_name(hba->dev), NULL);
+ struct intel_host *host = ufshcd_get_variant(hba);
+
+ intel_cache_ltr(hba);
+
+ host->debugfs_root = dir;
+ debugfs_create_x32("active_ltr", 0444, dir, &host->active_ltr);
+ debugfs_create_x32("idle_ltr", 0444, dir, &host->idle_ltr);
+}
+
+static void intel_remove_debugfs(struct ufs_hba *hba)
+{
+ struct intel_host *host = ufshcd_get_variant(hba);
+
+ debugfs_remove_recursive(host->debugfs_root);
+}
+
+static int ufs_intel_device_reset(struct ufs_hba *hba)
+{
+ struct intel_host *host = ufshcd_get_variant(hba);
+
+ if (INTEL_DSM_SUPPORTED(host, RESET)) {
+ u32 result = 0;
+ int err;
+
+ err = intel_dsm(host, hba->dev, INTEL_DSM_RESET, &result);
+ if (!err && !result)
+ err = -EIO;
+ if (err)
+ dev_err(hba->dev, "%s: DSM error %d result %u\n",
+ __func__, err, result);
+ return err;
+ }
+
+ if (!host->reset_gpio)
+ return -EOPNOTSUPP;
+
+ gpiod_set_value_cansleep(host->reset_gpio, 1);
+ usleep_range(10, 15);
+
+ gpiod_set_value_cansleep(host->reset_gpio, 0);
+ usleep_range(10, 15);
+
+ return 0;
+}
+
+static struct gpio_desc *ufs_intel_get_reset_gpio(struct device *dev)
+{
+ /* GPIO in _DSD has active low setting */
+ return devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+}
+
+static int ufs_intel_common_init(struct ufs_hba *hba)
+{
+ struct intel_host *host;
+
+ hba->caps |= UFSHCD_CAP_RPM_AUTOSUSPEND;
+
+ host = devm_kzalloc(hba->dev, sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+ host->saved_spm_lvl = -1;
+ ufshcd_set_variant(hba, host);
+ intel_dsm_init(host, hba->dev);
+ if (INTEL_DSM_SUPPORTED(host, RESET)) {
+ if (hba->vops->device_reset)
+ hba->caps |= UFSHCD_CAP_DEEPSLEEP;
+ } else {
+ if (hba->vops->device_reset)
+ host->reset_gpio = ufs_intel_get_reset_gpio(hba->dev);
+ if (IS_ERR(host->reset_gpio)) {
+ dev_err(hba->dev, "%s: failed to get reset GPIO, error %ld\n",
+ __func__, PTR_ERR(host->reset_gpio));
+ host->reset_gpio = NULL;
+ }
+ if (host->reset_gpio) {
+ gpiod_set_value_cansleep(host->reset_gpio, 0);
+ hba->caps |= UFSHCD_CAP_DEEPSLEEP;
+ }
+ }
+ intel_ltr_expose(hba->dev);
+ intel_add_debugfs(hba);
+ return 0;
+}
+
+static void ufs_intel_common_exit(struct ufs_hba *hba)
+{
+ intel_remove_debugfs(hba);
+ intel_ltr_hide(hba->dev);
+}
+
+static int ufs_intel_resume(struct ufs_hba *hba, enum ufs_pm_op op)
+{
+ if (ufshcd_is_link_hibern8(hba)) {
+ int ret = ufshcd_uic_hibern8_exit(hba);
+
+ if (!ret) {
+ ufshcd_set_link_active(hba);
+ } else {
+ dev_err(hba->dev, "%s: hibern8 exit failed %d\n",
+ __func__, ret);
+ /*
+ * Force reset and restore. Any other actions can lead
+ * to an unrecoverable state.
+ */
+ ufshcd_set_link_off(hba);
+ }
+ }
+
+ return 0;
+}
+
+static int ufs_intel_ehl_init(struct ufs_hba *hba)
+{
+ hba->quirks |= UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8;
+ return ufs_intel_common_init(hba);
+}
+
+static int ufs_intel_lkf_init(struct ufs_hba *hba)
+{
+ int err;
+
+ hba->nop_out_timeout = 200;
+ hba->quirks |= UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8;
+ hba->caps |= UFSHCD_CAP_CRYPTO;
+ err = ufs_intel_common_init(hba);
+ /* LKF always needs a full reset, so set PM accordingly */
+ if (hba->caps & UFSHCD_CAP_DEEPSLEEP) {
+ hba->spm_lvl = UFS_PM_LVL_6;
+ hba->rpm_lvl = UFS_PM_LVL_6;
+ } else {
+ hba->spm_lvl = UFS_PM_LVL_5;
+ hba->rpm_lvl = UFS_PM_LVL_5;
+ }
+ return err;
+}
+
+static int ufs_intel_adl_init(struct ufs_hba *hba)
+{
+ hba->nop_out_timeout = 200;
+ hba->quirks |= UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8 |
+ UFSHCD_QUIRK_PERFORM_LINK_STARTUP_ONCE;
+ hba->caps |= UFSHCD_CAP_WB_EN;
+ return ufs_intel_common_init(hba);
+}
+
+static int ufs_intel_mtl_init(struct ufs_hba *hba)
+{
+ hba->rpm_lvl = UFS_PM_LVL_2;
+ hba->spm_lvl = UFS_PM_LVL_2;
+ hba->caps |= UFSHCD_CAP_CRYPTO | UFSHCD_CAP_WB_EN;
+ return ufs_intel_common_init(hba);
+}
+
+static int ufs_intel_mcq_config_resource(struct ufs_hba *hba)
+{
+ hba->mcq_base = hba->mmio_base + ufshcd_mcq_queue_cfg_addr(hba);
+
+ return 0;
+}
+
+/*
+ * This Intel UFS4.0 controller maps MCQ doorbell and interrupt-status
+ * registers into the same PCI BAR as the legacy HCI space, at this
+ * fixed offset/stride.
+ */
+#define UFS_INTEL_SQDAO0 0x2800
+#define UFS_INTEL_SQISAO0 0x2814
+#define UFS_INTEL_CQDAO0 0x281C
+#define UFS_INTEL_CQISAO0 0x2824
+#define UFS_INTEL_MCQ_STRIDE 0x30
+
+static int ufs_intel_op_runtime_config(struct ufs_hba *hba)
+{
+ struct ufshcd_mcq_opr_info_t *opr;
+ int i;
+
+ hba->mcq_opr[OPR_SQD].offset = UFS_INTEL_SQDAO0;
+ hba->mcq_opr[OPR_SQIS].offset = UFS_INTEL_SQISAO0;
+ hba->mcq_opr[OPR_CQD].offset = UFS_INTEL_CQDAO0;
+ hba->mcq_opr[OPR_CQIS].offset = UFS_INTEL_CQISAO0;
+
+ for (i = 0; i < OPR_MAX; i++) {
+ opr = &hba->mcq_opr[i];
+ opr->stride = UFS_INTEL_MCQ_STRIDE;
+ opr->base = hba->mmio_base + opr->offset;
+ }
+
+ return 0;
+}
+
+static int ufs_qemu_get_hba_mac(struct ufs_hba *hba)
+{
+ return MAX_SUPP_MAC;
+}
+
+static int ufs_qemu_mcq_config_resource(struct ufs_hba *hba)
+{
+ hba->mcq_base = hba->mmio_base + ufshcd_mcq_queue_cfg_addr(hba);
+
+ return 0;
+}
+
+static int ufs_qemu_op_runtime_config(struct ufs_hba *hba)
+{
+ struct ufshcd_mcq_opr_info_t *opr;
+ int i;
+
+ u32 sqdao = ufsmcq_readl(hba, ufshcd_mcq_cfg_offset(REG_SQDAO, 0));
+ u32 sqisao = ufsmcq_readl(hba, ufshcd_mcq_cfg_offset(REG_SQISAO, 0));
+ u32 cqdao = ufsmcq_readl(hba, ufshcd_mcq_cfg_offset(REG_CQDAO, 0));
+ u32 cqisao = ufsmcq_readl(hba, ufshcd_mcq_cfg_offset(REG_CQISAO, 0));
+
+ hba->mcq_opr[OPR_SQD].offset = sqdao;
+ hba->mcq_opr[OPR_SQIS].offset = sqisao;
+ hba->mcq_opr[OPR_CQD].offset = cqdao;
+ hba->mcq_opr[OPR_CQIS].offset = cqisao;
+
+ for (i = 0; i < OPR_MAX; i++) {
+ opr = &hba->mcq_opr[i];
+ opr->stride = 48;
+ opr->base = hba->mmio_base + opr->offset;
+ }
+
+ return 0;
+}
+
+static struct ufs_hba_variant_ops ufs_qemu_hba_vops = {
+ .name = "qemu-pci",
+ .get_hba_mac = ufs_qemu_get_hba_mac,
+ .mcq_config_resource = ufs_qemu_mcq_config_resource,
+ .op_runtime_config = ufs_qemu_op_runtime_config,
+};
+
+static struct ufs_hba_variant_ops ufs_intel_cnl_hba_vops = {
+ .name = "intel-pci",
+ .init = ufs_intel_common_init,
+ .exit = ufs_intel_common_exit,
+ .link_startup_notify = ufs_intel_link_startup_notify,
+ .resume = ufs_intel_resume,
+};
+
+static struct ufs_hba_variant_ops ufs_intel_ehl_hba_vops = {
+ .name = "intel-pci",
+ .init = ufs_intel_ehl_init,
+ .exit = ufs_intel_common_exit,
+ .link_startup_notify = ufs_intel_link_startup_notify,
+ .resume = ufs_intel_resume,
+};
+
+static struct ufs_hba_variant_ops ufs_intel_lkf_hba_vops = {
+ .name = "intel-pci",
+ .init = ufs_intel_lkf_init,
+ .exit = ufs_intel_common_exit,
+ .hce_enable_notify = ufs_intel_hce_enable_notify,
+ .link_startup_notify = ufs_intel_link_startup_notify,
+ .pwr_change_notify = ufs_intel_lkf_pwr_change_notify,
+ .apply_dev_quirks = ufs_intel_lkf_apply_dev_quirks,
+ .resume = ufs_intel_resume,
+ .device_reset = ufs_intel_device_reset,
+};
+
+static struct ufs_hba_variant_ops ufs_intel_adl_hba_vops = {
+ .name = "intel-pci",
+ .init = ufs_intel_adl_init,
+ .exit = ufs_intel_common_exit,
+ .link_startup_notify = ufs_intel_link_startup_notify,
+ .resume = ufs_intel_resume,
+ .device_reset = ufs_intel_device_reset,
+};
+
+static struct ufs_hba_variant_ops ufs_intel_mtl_hba_vops = {
+ .name = "intel-pci",
+ .init = ufs_intel_mtl_init,
+ .exit = ufs_intel_common_exit,
+ .hce_enable_notify = ufs_intel_hce_enable_notify,
+ .link_startup_notify = ufs_intel_link_startup_notify,
+ .pwr_change_notify = ufs_intel_nvl_pwr_change_notify,
+ .mcq_config_resource = ufs_intel_mcq_config_resource,
+ .op_runtime_config = ufs_intel_op_runtime_config,
+ .resume = ufs_intel_resume,
+ .device_reset = ufs_intel_device_reset,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int ufshcd_pci_restore(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ /* Force a full reset and restore */
+ ufshcd_set_link_off(hba);
+
+ return ufshcd_system_resume(dev);
+}
+
+static int ufs_intel_suspend_prepare(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct intel_host *host = ufshcd_get_variant(hba);
+ int err;
+
+ /*
+ * Only s2idle (S0ix) retains link state. Force power-off
+ * (UFS_PM_LVL_5) for any other case.
+ */
+ if (pm_suspend_target_state != PM_SUSPEND_TO_IDLE && hba->spm_lvl < UFS_PM_LVL_5) {
+ host->saved_spm_lvl = hba->spm_lvl;
+ hba->spm_lvl = UFS_PM_LVL_5;
+ }
+
+ err = ufshcd_suspend_prepare(dev);
+
+ if (err < 0 && host->saved_spm_lvl != -1) {
+ hba->spm_lvl = host->saved_spm_lvl;
+ host->saved_spm_lvl = -1;
+ }
+
+ return err;
+}
+
+static void ufs_intel_resume_complete(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ struct intel_host *host = ufshcd_get_variant(hba);
+
+ ufshcd_resume_complete(dev);
+
+ if (host->saved_spm_lvl != -1) {
+ hba->spm_lvl = host->saved_spm_lvl;
+ host->saved_spm_lvl = -1;
+ }
+}
+
+static int ufshcd_pci_suspend_prepare(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ if (!strcmp(hba->vops->name, "intel-pci"))
+ return ufs_intel_suspend_prepare(dev);
+
+ return ufshcd_suspend_prepare(dev);
+}
+
+static void ufshcd_pci_resume_complete(struct device *dev)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ if (!strcmp(hba->vops->name, "intel-pci")) {
+ ufs_intel_resume_complete(dev);
+ return;
+ }
+
+ ufshcd_resume_complete(dev);
+}
+#endif
+
+/**
+ * ufshcd_pci_remove - de-allocate PCI/SCSI host and host memory space
+ * data structure memory
+ * @pdev: pointer to PCI handle
+ */
+static void ufshcd_pci_remove(struct pci_dev *pdev)
+{
+ struct ufs_hba *hba = pci_get_drvdata(pdev);
+
+ pm_runtime_forbid(&pdev->dev);
+ pm_runtime_get_noresume(&pdev->dev);
+ ufshcd_remove(hba);
+}
+
+/**
+ * ufshcd_pci_probe - probe routine of the driver
+ * @pdev: pointer to PCI device handle
+ * @id: PCI device id
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+static int
+ufshcd_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct ufs_hba *hba;
+ void __iomem *mmio_base;
+ int err;
+
+ err = pcim_enable_device(pdev);
+ if (err) {
+ dev_err(&pdev->dev, "pcim_enable_device failed\n");
+ return err;
+ }
+
+ pci_set_master(pdev);
+
+ mmio_base = pcim_iomap_region(pdev, 0, UFSHCD);
+ if (IS_ERR(mmio_base)) {
+ dev_err(&pdev->dev, "request and iomap failed\n");
+ return PTR_ERR(mmio_base);
+ }
+
+ err = ufshcd_alloc_host(&pdev->dev, &hba);
+ if (err) {
+ dev_err(&pdev->dev, "Allocation failed\n");
+ return err;
+ }
+
+ hba->vops = (struct ufs_hba_variant_ops *)id->driver_data;
+
+ err = ufshcd_init(hba, mmio_base, pdev->irq);
+ if (err) {
+ dev_err(&pdev->dev, "Initialization failed\n");
+ return err;
+ }
+
+ pm_runtime_put_noidle(&pdev->dev);
+ pm_runtime_allow(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops ufshcd_pci_pm_ops = {
+ SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
+#ifdef CONFIG_PM_SLEEP
+ .suspend = ufshcd_system_suspend,
+ .resume = ufshcd_system_resume,
+ .freeze = ufshcd_system_suspend,
+ .thaw = ufshcd_system_resume,
+ .poweroff = ufshcd_system_suspend,
+ .restore = ufshcd_pci_restore,
+ .prepare = ufshcd_pci_suspend_prepare,
+ .complete = ufshcd_pci_resume_complete,
+#endif
+};
+
+static const struct pci_device_id ufshcd_pci_tbl[] = {
+ { PCI_VDEVICE(REDHAT, 0x0013), .driver_data = (kernel_ulong_t)&ufs_qemu_hba_vops },
+ { PCI_VDEVICE(SAMSUNG, 0xC00C), .driver_data = 0 },
+ { PCI_VDEVICE(INTEL, 0x9DFA), .driver_data = (kernel_ulong_t)&ufs_intel_cnl_hba_vops },
+ { PCI_VDEVICE(INTEL, 0x4B41), .driver_data = (kernel_ulong_t)&ufs_intel_ehl_hba_vops },
+ { PCI_VDEVICE(INTEL, 0x4B43), .driver_data = (kernel_ulong_t)&ufs_intel_ehl_hba_vops },
+ { PCI_VDEVICE(INTEL, 0x98FA), .driver_data = (kernel_ulong_t)&ufs_intel_lkf_hba_vops },
+ { PCI_VDEVICE(INTEL, 0x51FF), .driver_data = (kernel_ulong_t)&ufs_intel_adl_hba_vops },
+ { PCI_VDEVICE(INTEL, 0x54FF), .driver_data = (kernel_ulong_t)&ufs_intel_adl_hba_vops },
+ { PCI_VDEVICE(INTEL, 0x7E47), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops },
+ { PCI_VDEVICE(INTEL, 0xA847), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops },
+ { PCI_VDEVICE(INTEL, 0x7747), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops },
+ { PCI_VDEVICE(INTEL, 0xE447), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops },
+ { PCI_VDEVICE(INTEL, 0x4D47), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops },
+ { PCI_VDEVICE(INTEL, 0xD335), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops },
+ { PCI_VDEVICE(AMD, 0x1B29), .driver_data = 0 },
+ { } /* terminate list */
+};
+
+MODULE_DEVICE_TABLE(pci, ufshcd_pci_tbl);
+
+static struct pci_driver ufshcd_pci_driver = {
+ .name = UFSHCD,
+ .id_table = ufshcd_pci_tbl,
+ .probe = ufshcd_pci_probe,
+ .remove = ufshcd_pci_remove,
+ .driver = {
+ .pm = &ufshcd_pci_pm_ops
+ },
+};
+
+module_pci_driver(ufshcd_pci_driver);
+
+MODULE_AUTHOR("Santosh Yaragnavi <santosh.sy@samsung.com>");
+MODULE_AUTHOR("Vinayak Holikatti <h.vinayak@samsung.com>");
+MODULE_DESCRIPTION("UFS host controller PCI glue driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/ufs/host/ufshcd-pltfrm.c b/drivers/ufs/host/ufshcd-pltfrm.c
new file mode 100644
index 000000000..5ac7afe75
--- /dev/null
+++ b/drivers/ufs/host/ufshcd-pltfrm.c
@@ -0,0 +1,723 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal Flash Storage Host controller Platform bus based glue driver
+ * Copyright (C) 2011-2013 Samsung India Software Operations
+ *
+ * Authors:
+ * Santosh Yaraganavi <santosh.sy@samsung.com>
+ * Vinayak Holikatti <h.vinayak@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+
+#include <ufs/ufshcd.h>
+#include "ufshcd-pltfrm.h"
+#include <ufs/unipro.h>
+
+#define UFSHCD_DEFAULT_LANES_PER_DIRECTION 2
+
+static int ufshcd_parse_clock_info(struct ufs_hba *hba)
+{
+ int ret = 0;
+ int cnt;
+ int i;
+ struct device *dev = hba->dev;
+ struct device_node *np = dev->of_node;
+ const char *name;
+ u32 *clkfreq = NULL;
+ struct ufs_clk_info *clki;
+ ssize_t sz = 0;
+
+ if (!np)
+ goto out;
+
+ cnt = of_property_count_strings(np, "clock-names");
+ if (!cnt || (cnt == -EINVAL)) {
+ dev_info(dev, "%s: Unable to find clocks, assuming enabled\n",
+ __func__);
+ } else if (cnt < 0) {
+ dev_err(dev, "%s: count clock strings failed, err %d\n",
+ __func__, cnt);
+ ret = cnt;
+ }
+
+ if (cnt <= 0)
+ goto out;
+
+ sz = of_property_count_u32_elems(np, "freq-table-hz");
+ if (sz <= 0) {
+ dev_info(dev, "freq-table-hz property not specified\n");
+ goto out;
+ }
+
+ if (sz != 2 * cnt) {
+ dev_err(dev, "%s len mismatch\n", "freq-table-hz");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ clkfreq = devm_kcalloc(dev, sz, sizeof(*clkfreq),
+ GFP_KERNEL);
+ if (!clkfreq) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = of_property_read_u32_array(np, "freq-table-hz",
+ clkfreq, sz);
+ if (ret && (ret != -EINVAL)) {
+ dev_err(dev, "%s: error reading array %d\n",
+ "freq-table-hz", ret);
+ return ret;
+ }
+
+ for (i = 0; i < sz; i += 2) {
+ ret = of_property_read_string_index(np, "clock-names", i/2,
+ &name);
+ if (ret)
+ goto out;
+
+ clki = devm_kzalloc(dev, sizeof(*clki), GFP_KERNEL);
+ if (!clki) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ clki->min_freq = clkfreq[i];
+ clki->max_freq = clkfreq[i+1];
+ clki->name = devm_kstrdup(dev, name, GFP_KERNEL);
+ if (!clki->name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ if (!strcmp(name, "ref_clk"))
+ clki->keep_link_active = true;
+ dev_dbg(dev, "%s: min %u max %u name %s\n", "freq-table-hz",
+ clki->min_freq, clki->max_freq, clki->name);
+ list_add_tail(&clki->list, &hba->clk_list_head);
+ }
+out:
+ return ret;
+}
+
+static bool phandle_exists(const struct device_node *np,
+ const char *phandle_name, int index)
+{
+ struct device_node *parse_np = of_parse_phandle(np, phandle_name, index);
+
+ if (parse_np)
+ of_node_put(parse_np);
+
+ return parse_np != NULL;
+}
+
+#define MAX_PROP_SIZE 32
+int ufshcd_populate_vreg(struct device *dev, const char *name,
+ struct ufs_vreg **out_vreg, bool skip_current)
+{
+ char prop_name[MAX_PROP_SIZE];
+ struct ufs_vreg *vreg = NULL;
+ struct device_node *np = dev->of_node;
+
+ if (!np) {
+ dev_err(dev, "%s: non DT initialization\n", __func__);
+ goto out;
+ }
+
+ snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", name);
+ if (!phandle_exists(np, prop_name, 0)) {
+ dev_info(dev, "%s: Unable to find %s regulator, assuming enabled\n",
+ __func__, prop_name);
+ goto out;
+ }
+
+ vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL);
+ if (!vreg)
+ return -ENOMEM;
+
+ vreg->name = devm_kstrdup(dev, name, GFP_KERNEL);
+ if (!vreg->name)
+ return -ENOMEM;
+
+ if (skip_current) {
+ vreg->max_uA = 0;
+ goto out;
+ }
+
+ snprintf(prop_name, MAX_PROP_SIZE, "%s-max-microamp", name);
+ if (of_property_read_u32(np, prop_name, &vreg->max_uA)) {
+ dev_info(dev, "%s: unable to find %s\n", __func__, prop_name);
+ vreg->max_uA = 0;
+ }
+out:
+ *out_vreg = vreg;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ufshcd_populate_vreg);
+
+/**
+ * ufshcd_parse_regulator_info - get regulator info from device tree
+ * @hba: per adapter instance
+ *
+ * Get regulator info from device tree for vcc, vccq, vccq2 power supplies.
+ * If any of the supplies are not defined it is assumed that they are always-on
+ * and hence return zero. If the property is defined but parsing is failed
+ * then return corresponding error.
+ *
+ * Return: 0 upon success; < 0 upon failure.
+ */
+static int ufshcd_parse_regulator_info(struct ufs_hba *hba)
+{
+ int err;
+ struct device *dev = hba->dev;
+ struct ufs_vreg_info *info = &hba->vreg_info;
+
+ err = ufshcd_populate_vreg(dev, "vdd-hba", &info->vdd_hba, true);
+ if (err)
+ goto out;
+
+ err = ufshcd_populate_vreg(dev, "vcc", &info->vcc, false);
+ if (err)
+ goto out;
+
+ err = ufshcd_populate_vreg(dev, "vccq", &info->vccq, false);
+ if (err)
+ goto out;
+
+ err = ufshcd_populate_vreg(dev, "vccq2", &info->vccq2, false);
+out:
+ return err;
+}
+
+static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba)
+{
+ struct device *dev = hba->dev;
+ int ret;
+
+ ret = of_property_read_u32(dev->of_node, "lanes-per-direction",
+ &hba->lanes_per_direction);
+ if (ret) {
+ dev_dbg(hba->dev,
+ "%s: failed to read lanes-per-direction, ret=%d\n",
+ __func__, ret);
+ hba->lanes_per_direction = UFSHCD_DEFAULT_LANES_PER_DIRECTION;
+ }
+}
+
+static int ufshcd_parse_tx_precode_enable(struct ufs_hba *hba,
+ bool host_precode_en[UFS_MAX_LANES],
+ bool device_precode_en[UFS_MAX_LANES])
+{
+ const char *prop_name = "tx-precode-enable-g6";
+ u32 num_elems = 2 * hba->lanes_per_direction;
+ const u32 lpd = hba->lanes_per_direction;
+ u32 precode[UFS_MAX_LANES * 2];
+ struct device *dev = hba->dev;
+ int count, err, i;
+
+ count = of_property_count_u32_elems(dev->of_node, prop_name);
+ if (count == -EINVAL || count == -ENODATA)
+ return 0;
+
+ if (count < 0)
+ return count;
+
+ if (count != num_elems) {
+ dev_err(dev, "Property %s has invalid count (%d), expecting %u\n",
+ prop_name, count, num_elems);
+ return -EINVAL;
+ }
+
+ err = of_property_read_u32_array(dev->of_node, prop_name, precode, num_elems);
+ if (err) {
+ dev_err(dev, "Failed to read %s property, %d\n", prop_name, err);
+ return err;
+ }
+
+ for (i = 0; i < num_elems; i++) {
+ if (precode[i] > 1) {
+ dev_err(dev, "Invalid TX precode value (%u) in %s property\n",
+ precode[i], prop_name);
+ return -EINVAL;
+ }
+ }
+
+ for (i = 0; i < lpd; i++) {
+ host_precode_en[i] = precode[i * 2];
+ device_precode_en[i] = precode[i * 2 + 1];
+ }
+
+ return 0;
+}
+
+static int ufshcd_parse_tx_eq_value_array(struct ufs_hba *hba,
+ const char *prop_name,
+ const u32 max_value,
+ u32 values[UFS_MAX_LANES * 2])
+{
+ u32 num_elems = 2 * hba->lanes_per_direction;
+ struct device *dev = hba->dev;
+ int count, err, i;
+
+ count = of_property_count_u32_elems(dev->of_node, prop_name);
+ if (count < 0)
+ return count;
+
+ if (count != num_elems) {
+ dev_err(dev, "Property %s has invalid count (%d), expecting %u\n",
+ prop_name, count, num_elems);
+ return -EINVAL;
+ }
+
+ err = of_property_read_u32_array(dev->of_node, prop_name, values, num_elems);
+ if (err) {
+ dev_err(dev, "Failed to read %s property, %d\n", prop_name, err);
+ return err;
+ }
+
+ for (i = 0; i < num_elems; i++) {
+ if (values[i] >= max_value) {
+ dev_err(dev, "Invalid TX EQ value (%u) in %s property\n",
+ values[i], prop_name);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * ufshcd_parse_tx_eq_settings_for_gear - Parse static TX EQ DT settings for one gear
+ * @hba: per adapter instance
+ * @gear: target HS gear
+ *
+ * Reads the txeq-preshoot-gN, txeq-deemphasis-gN, and (for G6)
+ * tx-precode-enable-g6 device-tree properties.
+ * If all present values are valid, stores them as static TX Equalization
+ * settings for the given gear.
+ */
+static void ufshcd_parse_tx_eq_settings_for_gear(struct ufs_hba *hba, int gear)
+{
+ bool device_precode_en[UFS_MAX_LANES] = { false };
+ bool host_precode_en[UFS_MAX_LANES] = { false };
+ const u32 lpd = hba->lanes_per_direction;
+ struct ufshcd_tx_eq_params *params;
+ u32 deemphasis[UFS_MAX_LANES * 2];
+ u32 preshoot[UFS_MAX_LANES * 2];
+ char prop_name[MAX_PROP_SIZE];
+ int err, lane;
+
+ snprintf(prop_name, MAX_PROP_SIZE, "txeq-preshoot-g%d", gear);
+ err = ufshcd_parse_tx_eq_value_array(hba, prop_name, TX_HS_NUM_PRESHOOT, preshoot);
+ if (err)
+ return;
+
+ snprintf(prop_name, MAX_PROP_SIZE, "txeq-deemphasis-g%d", gear);
+ err = ufshcd_parse_tx_eq_value_array(hba, prop_name, TX_HS_NUM_DEEMPHASIS, deemphasis);
+ if (err)
+ return;
+
+ if (gear == UFS_HS_G6) {
+ err = ufshcd_parse_tx_precode_enable(hba, host_precode_en, device_precode_en);
+ if (err)
+ return;
+ }
+
+ params = &hba->tx_eq_params[gear - 1];
+ for (lane = 0; lane < lpd; lane++) {
+ params->host[lane].preshoot = preshoot[lane * 2];
+ params->host[lane].deemphasis = deemphasis[lane * 2];
+ params->host[lane].precode_en = host_precode_en[lane];
+
+ params->device[lane].preshoot = preshoot[lane * 2 + 1];
+ params->device[lane].deemphasis = deemphasis[lane * 2 + 1];
+ params->device[lane].precode_en = device_precode_en[lane];
+ }
+
+ params->is_valid = true;
+ params->from_dt = true;
+}
+
+static void ufshcd_parse_static_tx_eq_settings(struct ufs_hba *hba)
+{
+ const u32 lpd = hba->lanes_per_direction;
+ int gear;
+
+ if (!lpd)
+ return;
+
+ if (lpd > UFS_MAX_LANES) {
+ dev_warn(hba->dev, "lanes_per_direction (%u) exceeds UFS_MAX_LANES (%u)\n",
+ lpd, UFS_MAX_LANES);
+ return;
+ }
+
+ for (gear = UFS_HS_G1; gear <= UFS_HS_GEAR_MAX; gear++)
+ ufshcd_parse_tx_eq_settings_for_gear(hba, gear);
+}
+
+/**
+ * ufshcd_parse_clock_min_max_freq - Parse MIN and MAX clocks freq
+ * @hba: per adapter instance
+ *
+ * This function parses MIN and MAX frequencies of all clocks required
+ * by the host drivers.
+ *
+ * Returns 0 for success and non-zero for failure
+ */
+static int ufshcd_parse_clock_min_max_freq(struct ufs_hba *hba)
+{
+ struct list_head *head = &hba->clk_list_head;
+ struct ufs_clk_info *clki;
+ struct dev_pm_opp *opp;
+ unsigned long freq;
+ u8 idx = 0;
+
+ list_for_each_entry(clki, head, list) {
+ if (!clki->name)
+ continue;
+
+ clki->clk = devm_clk_get(hba->dev, clki->name);
+ if (IS_ERR(clki->clk))
+ continue;
+
+ /* Find Max Freq */
+ freq = ULONG_MAX;
+ opp = dev_pm_opp_find_freq_floor_indexed(hba->dev, &freq, idx);
+ if (IS_ERR(opp)) {
+ dev_err(hba->dev, "Failed to find OPP for MAX frequency\n");
+ return PTR_ERR(opp);
+ }
+ clki->max_freq = dev_pm_opp_get_freq_indexed(opp, idx);
+ dev_pm_opp_put(opp);
+
+ /* Find Min Freq */
+ freq = 0;
+ opp = dev_pm_opp_find_freq_ceil_indexed(hba->dev, &freq, idx);
+ if (IS_ERR(opp)) {
+ dev_err(hba->dev, "Failed to find OPP for MIN frequency\n");
+ return PTR_ERR(opp);
+ }
+ clki->min_freq = dev_pm_opp_get_freq_indexed(opp, idx++);
+ dev_pm_opp_put(opp);
+ }
+
+ return 0;
+}
+
+static int ufshcd_parse_operating_points(struct ufs_hba *hba)
+{
+ struct device *dev = hba->dev;
+ struct device_node *np = dev->of_node;
+ struct dev_pm_opp_config config = {};
+ struct ufs_clk_info *clki;
+ const char **clk_names;
+ int cnt, i, ret;
+
+ if (!of_property_present(np, "operating-points-v2"))
+ return 0;
+
+ if (of_property_present(np, "freq-table-hz")) {
+ dev_err(dev, "%s: operating-points and freq-table-hz are incompatible\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ cnt = of_property_count_strings(np, "clock-names");
+ if (cnt <= 0) {
+ dev_err(dev, "%s: Missing clock-names\n", __func__);
+ return -ENODEV;
+ }
+
+ /* OPP expects clk_names to be NULL terminated */
+ clk_names = devm_kcalloc(dev, cnt + 1, sizeof(*clk_names), GFP_KERNEL);
+ if (!clk_names)
+ return -ENOMEM;
+
+ /*
+ * We still need to get reference to all clocks as the UFS core uses
+ * them separately.
+ */
+ for (i = 0; i < cnt; i++) {
+ ret = of_property_read_string_index(np, "clock-names", i,
+ &clk_names[i]);
+ if (ret)
+ return ret;
+
+ clki = devm_kzalloc(dev, sizeof(*clki), GFP_KERNEL);
+ if (!clki)
+ return -ENOMEM;
+
+ clki->name = devm_kstrdup(dev, clk_names[i], GFP_KERNEL);
+ if (!clki->name)
+ return -ENOMEM;
+
+ if (!strcmp(clk_names[i], "ref_clk"))
+ clki->keep_link_active = true;
+
+ list_add_tail(&clki->list, &hba->clk_list_head);
+ }
+
+ config.clk_names = clk_names,
+ config.config_clks = ufshcd_opp_config_clks;
+
+ ret = devm_pm_opp_set_config(dev, &config);
+ if (ret)
+ return ret;
+
+ ret = devm_pm_opp_of_add_table(dev);
+ if (ret) {
+ dev_err(dev, "Failed to add OPP table: %d\n", ret);
+ return ret;
+ }
+
+ ret = ufshcd_parse_clock_min_max_freq(hba);
+ if (ret)
+ return ret;
+
+ hba->use_pm_opp = true;
+
+ return 0;
+}
+
+/**
+ * ufshcd_negotiate_pwr_params - find power mode settings that are supported by
+ * both the controller and the device
+ * @host_params: pointer to host parameters
+ * @dev_max: pointer to device attributes
+ * @agreed_pwr: returned agreed attributes
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+int ufshcd_negotiate_pwr_params(const struct ufs_host_params *host_params,
+ const struct ufs_pa_layer_attr *dev_max,
+ struct ufs_pa_layer_attr *agreed_pwr)
+{
+ int min_host_gear;
+ int min_dev_gear;
+ bool is_dev_sup_hs = false;
+ bool is_host_max_hs = false;
+
+ if (dev_max->pwr_rx == FAST_MODE)
+ is_dev_sup_hs = true;
+
+ if (host_params->desired_working_mode == UFS_HS_MODE) {
+ is_host_max_hs = true;
+ min_host_gear = min_t(u32, host_params->hs_rx_gear,
+ host_params->hs_tx_gear);
+ } else {
+ min_host_gear = min_t(u32, host_params->pwm_rx_gear,
+ host_params->pwm_tx_gear);
+ }
+
+ /*
+ * device doesn't support HS but host_params->desired_working_mode is HS,
+ * thus device and host_params don't agree
+ */
+ if (!is_dev_sup_hs && is_host_max_hs) {
+ pr_info("%s: device doesn't support HS\n",
+ __func__);
+ return -ENOTSUPP;
+ } else if (is_dev_sup_hs && is_host_max_hs) {
+ /*
+ * since device supports HS, it supports FAST_MODE.
+ * since host_params->desired_working_mode is also HS
+ * then final decision (FAST/FASTAUTO) is done according
+ * to pltfrm_params as it is the restricting factor
+ */
+ agreed_pwr->pwr_rx = host_params->rx_pwr_hs;
+ agreed_pwr->pwr_tx = agreed_pwr->pwr_rx;
+ } else {
+ /*
+ * here host_params->desired_working_mode is PWM.
+ * it doesn't matter whether device supports HS or PWM,
+ * in both cases host_params->desired_working_mode will
+ * determine the mode
+ */
+ agreed_pwr->pwr_rx = host_params->rx_pwr_pwm;
+ agreed_pwr->pwr_tx = agreed_pwr->pwr_rx;
+ }
+
+ /*
+ * we would like tx to work in the minimum number of lanes
+ * between device capability and vendor preferences.
+ * the same decision will be made for rx
+ */
+ agreed_pwr->lane_tx = min_t(u32, dev_max->lane_tx,
+ host_params->tx_lanes);
+ agreed_pwr->lane_rx = min_t(u32, dev_max->lane_rx,
+ host_params->rx_lanes);
+
+ /* device maximum gear is the minimum between device rx and tx gears */
+ min_dev_gear = min_t(u32, dev_max->gear_rx, dev_max->gear_tx);
+
+ /*
+ * if both device capabilities and vendor pre-defined preferences are
+ * both HS or both PWM then set the minimum gear to be the chosen
+ * working gear.
+ * if one is PWM and one is HS then the one that is PWM get to decide
+ * what is the gear, as it is the one that also decided previously what
+ * pwr the device will be configured to.
+ */
+ if ((is_dev_sup_hs && is_host_max_hs) ||
+ (!is_dev_sup_hs && !is_host_max_hs)) {
+ agreed_pwr->gear_rx =
+ min_t(u32, min_dev_gear, min_host_gear);
+ } else if (!is_dev_sup_hs) {
+ agreed_pwr->gear_rx = min_dev_gear;
+ } else {
+ agreed_pwr->gear_rx = min_host_gear;
+ }
+ agreed_pwr->gear_tx = agreed_pwr->gear_rx;
+
+ agreed_pwr->hs_rate = host_params->hs_rate;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ufshcd_negotiate_pwr_params);
+
+/**
+ * ufshcd_parse_gear_limits - Parse DT-based gear and rate limits for UFS
+ * @hba: Pointer to UFS host bus adapter instance
+ * @host_params: Pointer to UFS host parameters structure to be updated
+ *
+ * This function reads optional device tree properties to apply
+ * platform-specific constraints.
+ *
+ * "limit-hs-gear": Specifies the max HS gear.
+ * "limit-gear-rate": Specifies the max High-Speed rate.
+ */
+void ufshcd_parse_gear_limits(struct ufs_hba *hba, struct ufs_host_params *host_params)
+{
+ struct device_node *np = hba->dev->of_node;
+ u32 hs_gear;
+ const char *hs_rate;
+
+ if (!of_property_read_u32(np, "limit-hs-gear", &hs_gear)) {
+ host_params->hs_tx_gear = hs_gear;
+ host_params->hs_rx_gear = hs_gear;
+ }
+
+ if (!of_property_read_string(np, "limit-gear-rate", &hs_rate)) {
+ if (!strcmp(hs_rate, "rate-a"))
+ host_params->hs_rate = PA_HS_MODE_A;
+ else if (!strcmp(hs_rate, "rate-b"))
+ host_params->hs_rate = PA_HS_MODE_B;
+ else
+ dev_warn(hba->dev, "Invalid rate: %s\n", hs_rate);
+ }
+}
+EXPORT_SYMBOL_GPL(ufshcd_parse_gear_limits);
+
+void ufshcd_init_host_params(struct ufs_host_params *host_params)
+{
+ *host_params = (struct ufs_host_params){
+ .tx_lanes = UFS_LANE_2,
+ .rx_lanes = UFS_LANE_2,
+ .hs_rx_gear = UFS_HS_G3,
+ .hs_tx_gear = UFS_HS_G3,
+ .pwm_rx_gear = UFS_PWM_G4,
+ .pwm_tx_gear = UFS_PWM_G4,
+ .rx_pwr_pwm = SLOW_MODE,
+ .tx_pwr_pwm = SLOW_MODE,
+ .rx_pwr_hs = FAST_MODE,
+ .tx_pwr_hs = FAST_MODE,
+ .hs_rate = PA_HS_MODE_B,
+ .desired_working_mode = UFS_HS_MODE,
+ };
+}
+EXPORT_SYMBOL_GPL(ufshcd_init_host_params);
+
+/**
+ * ufshcd_pltfrm_init - probe routine of the driver
+ * @pdev: pointer to Platform device handle
+ * @vops: pointer to variant ops
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+int ufshcd_pltfrm_init(struct platform_device *pdev,
+ const struct ufs_hba_variant_ops *vops)
+{
+ struct ufs_hba *hba;
+ void __iomem *mmio_base;
+ int irq, err;
+ struct device *dev = &pdev->dev;
+
+ mmio_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mmio_base))
+ return PTR_ERR(mmio_base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ err = ufshcd_alloc_host(dev, &hba);
+ if (err) {
+ dev_err(dev, "Allocation failed\n");
+ return err;
+ }
+
+ hba->vops = vops;
+
+ err = ufshcd_parse_clock_info(hba);
+ if (err) {
+ dev_err(dev, "%s: clock parse failed %d\n",
+ __func__, err);
+ return err;
+ }
+ err = ufshcd_parse_regulator_info(hba);
+ if (err) {
+ dev_err(dev, "%s: regulator init failed %d\n",
+ __func__, err);
+ return err;
+ }
+
+ ufshcd_init_lanes_per_dir(hba);
+
+ ufshcd_parse_static_tx_eq_settings(hba);
+
+ err = ufshcd_parse_operating_points(hba);
+ if (err) {
+ dev_err(dev, "%s: OPP parse failed %d\n", __func__, err);
+ return err;
+ }
+
+ err = ufshcd_init(hba, mmio_base, irq);
+ if (err) {
+ dev_err_probe(dev, err, "Initialization failed with error %d\n",
+ err);
+ return err;
+ }
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ufshcd_pltfrm_init);
+
+/**
+ * ufshcd_pltfrm_remove - Remove ufshcd platform
+ * @pdev: pointer to Platform device handle
+ */
+void ufshcd_pltfrm_remove(struct platform_device *pdev)
+{
+ struct ufs_hba *hba = platform_get_drvdata(pdev);
+
+ pm_runtime_get_sync(&pdev->dev);
+ ufshcd_remove(hba);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+}
+EXPORT_SYMBOL_GPL(ufshcd_pltfrm_remove);
+
+MODULE_AUTHOR("Santosh Yaragnavi <santosh.sy@samsung.com>");
+MODULE_AUTHOR("Vinayak Holikatti <h.vinayak@samsung.com>");
+MODULE_DESCRIPTION("UFS host controller Platform bus based glue driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/ufs/host/ufshcd-pltfrm.h b/drivers/ufs/host/ufshcd-pltfrm.h
new file mode 100644
index 000000000..0a18a8aed
--- /dev/null
+++ b/drivers/ufs/host/ufshcd-pltfrm.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef UFSHCD_PLTFRM_H_
+#define UFSHCD_PLTFRM_H_
+
+#include <ufs/ufshcd.h>
+
+#define UFS_PWM_MODE 1
+#define UFS_HS_MODE 2
+
+struct ufs_host_params {
+ u32 pwm_rx_gear; /* pwm rx gear to work in */
+ u32 pwm_tx_gear; /* pwm tx gear to work in */
+ u32 hs_rx_gear; /* hs rx gear to work in */
+ u32 hs_tx_gear; /* hs tx gear to work in */
+ u32 rx_lanes; /* number of rx lanes */
+ u32 tx_lanes; /* number of tx lanes */
+ u32 rx_pwr_pwm; /* rx pwm working pwr */
+ u32 tx_pwr_pwm; /* tx pwm working pwr */
+ u32 rx_pwr_hs; /* rx hs working pwr */
+ u32 tx_pwr_hs; /* tx hs working pwr */
+ u32 hs_rate; /* rate A/B to work in HS */
+ u32 desired_working_mode;
+};
+
+int ufshcd_negotiate_pwr_params(const struct ufs_host_params *host_params,
+ const struct ufs_pa_layer_attr *dev_max,
+ struct ufs_pa_layer_attr *agreed_pwr);
+void ufshcd_init_host_params(struct ufs_host_params *host_params);
+void ufshcd_parse_gear_limits(struct ufs_hba *hba, struct ufs_host_params *host_params);
+int ufshcd_pltfrm_init(struct platform_device *pdev,
+ const struct ufs_hba_variant_ops *vops);
+void ufshcd_pltfrm_remove(struct platform_device *pdev);
+int ufshcd_populate_vreg(struct device *dev, const char *name,
+ struct ufs_vreg **out_vreg, bool skip_current);
+
+#endif /* UFSHCD_PLTFRM_H_ */
diff --git a/drivers/ufs/host/ufshci-dwc.h b/drivers/ufs/host/ufshci-dwc.h
new file mode 100644
index 000000000..6c290e272
--- /dev/null
+++ b/drivers/ufs/host/ufshci-dwc.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * UFS Host driver for Synopsys Designware Core
+ *
+ * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
+ *
+ * Authors: Joao Pinto <jpinto@synopsys.com>
+ */
+
+#ifndef _UFSHCI_DWC_H
+#define _UFSHCI_DWC_H
+
+/* DWC HC UFSHCI specific Registers */
+enum dwc_specific_registers {
+ DWC_UFS_REG_HCLKDIV = 0xFC,
+};
+
+/* Clock Divider Values: Hex equivalent of frequency in MHz */
+enum clk_div_values {
+ DWC_UFS_REG_HCLKDIV_DIV_62_5 = 0x3e,
+ DWC_UFS_REG_HCLKDIV_DIV_125 = 0x7d,
+ DWC_UFS_REG_HCLKDIV_DIV_200 = 0xc8,
+};
+
+/* Selector Index */
+enum selector_index {
+ SELIND_LN0_TX = 0x00,
+ SELIND_LN1_TX = 0x01,
+ SELIND_LN0_RX = 0x04,
+ SELIND_LN1_RX = 0x05,
+};
+
+#endif /* End of Header */