Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+31
View File
@@ -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
+5
View File
@@ -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/
+52
View File
@@ -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.
+10
View File
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
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
+529
View File
@@ -0,0 +1,529 @@
// 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;
}
static int ufs_debugfs_get_user_access(struct ufs_hba *hba)
__acquires(&hba->host_sem)
{
down(&hba->host_sem);
if (!ufshcd_is_user_access_allowed(hba)) {
up(&hba->host_sem);
return -EBUSY;
}
ufshcd_rpm_get_sync(hba);
return 0;
}
static void ufs_debugfs_put_user_access(struct ufs_hba *hba)
__releases(&hba->host_sem)
{
ufshcd_rpm_put_sync(hba);
up(&hba->host_sem);
}
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);
}
@@ -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
@@ -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);
}
@@ -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 */
+212
View File
@@ -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);
}
+737
View File
@@ -0,0 +1,737 @@
// 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;
/* sizeof(struct utp_transfer_cmd_desc) must be a multiple of 128 */
BUILD_BUG_ON(sizeof(struct utp_transfer_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) -
hba->ucdl_dma_addr;
return div_u64(addr, ufshcd_get_ucd_size(hba));
}
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;
}
+252
View File
@@ -0,0 +1,252 @@
// 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_type = be16_to_cpu(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));
frm_resp->req_resp = cpu_to_be16(RPMB_RESULT_READ);
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");
File diff suppressed because it is too large Load Diff
+18
View File
@@ -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
File diff suppressed because it is too large Load Diff
+275
View File
@@ -0,0 +1,275 @@
// 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_get_query_desc_size(struct ufs_hba *hba, int *desc_len,
struct utp_upiu_query *qr)
{
int desc_size = be16_to_cpu(qr->length);
if (desc_size <= 0)
return -EINVAL;
*desc_len = min_t(int, QUERY_DESC_MAX_SIZE, desc_size);
return 0;
}
static int ufs_bsg_alloc_desc_buffer(struct ufs_hba *hba, struct bsg_job *job,
uint8_t **desc_buff, int *desc_len,
enum query_opcode desc_op)
{
struct ufs_bsg_request *bsg_request = job->request;
struct utp_upiu_query *qr;
u8 *descp;
if (desc_op != UPIU_QUERY_OPCODE_WRITE_DESC &&
desc_op != UPIU_QUERY_OPCODE_READ_DESC)
goto out;
qr = &bsg_request->upiu_req.qr;
if (ufs_bsg_get_query_desc_size(hba, desc_len, qr)) {
dev_err(hba->dev, "Illegal desc size\n");
return -EINVAL;
}
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;
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;
uint8_t *buff = NULL;
int desc_len = 0;
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:
ret = ufshcd_exec_raw_upiu_cmd(hba, &bsg_request->upiu_req,
&bsg_reply->upiu_rsp, msgcode,
buff, &desc_len, desc_op);
if (ret)
dev_err(hba->dev, "exe raw upiu: error code %d\n", ret);
else if (desc_op == UPIU_QUERY_OPCODE_READ_DESC && 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;
}
+18
View File
@@ -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 */
+426
View File
@@ -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>
@@ -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_ */
@@ -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);
}
@@ -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 */
+509
View File
@@ -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_ */
File diff suppressed because it is too large Load Diff
+170
View File
@@ -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.
+16
View File
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0
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
+334
View File
@@ -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");
@@ -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");
@@ -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");
+311
View File
@@ -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");
@@ -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 */
+117
View File
@@ -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");
@@ -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");
File diff suppressed because it is too large Load Diff
+294
View File
@@ -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_ */
+605
View File
@@ -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");
+104
View File
@@ -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_ */
@@ -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 */
@@ -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>
File diff suppressed because it is too large Load Diff
+236
View File
@@ -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 */
#define MTK_MAX_NUM_RTT 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 */
File diff suppressed because it is too large Load Diff
+388
View File
@@ -0,0 +1,388 @@
/* 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;
};
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_ */
+558
View File
@@ -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");
+361
View File
@@ -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");
@@ -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_ */
+450
View File
@@ -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");
+85
View File
@@ -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_ */
+146
View File
@@ -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");
+71
View File
@@ -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 */
+718
View File
@@ -0,0 +1,718 @@
// 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_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_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,
.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");
@@ -0,0 +1,569 @@
// 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;
}
}
/**
* 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);
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");
@@ -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_ */
+33
View File
@@ -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 */