blob: 9dd7f70e7c513bc79b18cfc2c0c3ebbc75253e5c [file] [log] [blame]
/*
* Copyright (C) 2018 MediaTek Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See http://www.gnu.org/licenses/gpl-2.0.html for more details.
*/
#include "ufs.h"
#include <linux/bitfield.h>
#include <linux/blk_types.h>
#include <linux/blkdev.h>
#include <linux/nls.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/pm_qos.h>
#include <linux/reboot.h>
#include <linux/rpmb.h>
#include "ufshcd.h"
#include "ufshcd-pltfrm.h"
#include "unipro.h"
#include "ufs_quirks.h"
#include "ufs-mtk.h"
#include "ufs-mtk-block.h"
#include "ufs-mtk-platform.h"
#include "ufs-mtk-dbg.h"
#ifdef CONFIG_MTK_AEE_FEATURE
#include <mt-plat/aee.h>
#endif
#include <mt-plat/mtk_partition.h>
#include <mt-plat/mtk_secure_api.h>
#include <mt-plat/mtk_boot.h>
#include <mt-plat/upmu_common.h>
#include <scsi/ufs/ufs-mtk-ioctl.h>
#ifdef CONFIG_MTK_UFS_LBA_CRC16_CHECK
#include <linux/crc16.h>
#endif
#include "mtk_spm_resource_req.h"
/* Query request retries */
#define QUERY_REQ_RETRIES 10
#define MAX_WRITE_BUFFER_SIZE (512 * 1024)
/* refer to ufs_mtk_init() for default value of these globals */
int ufs_mtk_rpm_autosuspend_delay; /* runtime PM: auto suspend delay */
bool ufs_mtk_rpm_enabled; /* runtime PM: on/off */
bool ufs_mtk_auto_hibern8_enabled;
bool ufs_mtk_host_deep_stall_enable;
bool ufs_mtk_host_scramble_enable;
int ufs_mtk_hs_gear;
struct ufs_hba *ufs_mtk_hba;
static bool ufs_mtk_is_data_cmd(char cmd_op);
static bool ufs_mtk_is_unmap_cmd(char cmd_op);
#if defined(PMIC_RG_LDO_VUFS_LP_ADDR) && defined(pmic_config_interface)
#define ufs_mtk_vufs_lpm(on) \
pmic_config_interface(PMIC_RG_LDO_VUFS_LP_ADDR, \
(on), \
PMIC_RG_LDO_VUFS_LP_MASK, \
PMIC_RG_LDO_VUFS_LP_SHIFT)
#else
#define ufs_mtk_vufs_lpm(on)
#endif
#ifdef CONFIG_MTK_UFS_LBA_CRC16_CHECK
/*
* Sometimes a lba may use encrypted r/w sometime
* and use raw r/w at other times.
* Use two CRC16 Arrays to record Encrypted and NoEncrypted data
*/
/* Self init Encryption and No Encryption array */
static u8 di_init;
/* Logical block count */
static u64 di_blkcnt;
/* CRC value of each 4KB block */
static u16 *di_crc;
/* private data */
static u8 *di_priv;
/* only do crc to first 32 byte of a 4KB block to reduce cpu overhead */
#define DI_CRC_DATA_SIZE (32)
/* Init Encryption and No Encryption array and others */
void ufs_mtk_di_init(struct ufs_hba *hba)
{
u8 ud_buf[UNIT_DESC_PARAM_ERASE_BLK_SIZE] = { 0 };
int len = UNIT_DESC_PARAM_ERASE_BLK_SIZE;
/* Already init */
if (di_init != 0)
return;
/* Read LU2 unit descriptor */
ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC,
QUERY_DESC_IDN_UNIT, 0x2, 0, ud_buf, &len);
di_blkcnt = (
((u64)ud_buf[UNIT_DESC_PARAM_LOGICAL_BLK_COUNT] << 56) |
((u64)ud_buf[UNIT_DESC_PARAM_LOGICAL_BLK_COUNT+1] << 48) |
((u64)ud_buf[UNIT_DESC_PARAM_LOGICAL_BLK_COUNT+2] << 40) |
((u64)ud_buf[UNIT_DESC_PARAM_LOGICAL_BLK_COUNT+3] << 32) |
((u64)ud_buf[UNIT_DESC_PARAM_LOGICAL_BLK_COUNT+4] << 24) |
((u64)ud_buf[UNIT_DESC_PARAM_LOGICAL_BLK_COUNT+5] << 16) |
((u64)ud_buf[UNIT_DESC_PARAM_LOGICAL_BLK_COUNT+6] << 8) |
((u64)ud_buf[UNIT_DESC_PARAM_LOGICAL_BLK_COUNT+7]));
di_crc = vzalloc(di_blkcnt * sizeof(u16));
if (!di_crc)
return;
di_priv = vzalloc(di_blkcnt * sizeof(u8));
if (!di_priv) {
vfree(di_crc);
di_crc = NULL;
return;
}
dev_info(hba->dev, "%s: need %llu MB memory for total lba %llu(0x%llx)\n",
__func__, di_blkcnt * (sizeof(u16) + sizeof(u8)) / 1024 / 1024
, di_blkcnt, di_blkcnt);
di_init = 1;
}
static void ufs_mtk_di_reset(struct ufs_hba *hba)
{
if (di_crc)
memset(di_crc, 0, di_blkcnt * sizeof(u16));
if (di_priv)
memset(di_priv, 0, di_blkcnt * sizeof(u8));
dev_info(hba->dev, "%s: Reset di %llu MB memory done!",
__func__,
di_blkcnt * (sizeof(u16) + sizeof(u8)) / 1024 / 1024);
}
/* Clear record for UNMAP command */
static int ufs_mtk_di_clr(struct scsi_cmnd *cmd)
{
u32 lba, lba_cnt, i;
/*
* Discard request has only one continuous sector range,
* so we can assume UNMAP command has only one UNMAP block
* descriptor.
*/
/* __sector: 512-byte based */
lba = cmd->request->__sector >> 3;
/* __data_len: bytes */
lba_cnt = cmd->request->__data_len >> 12;
/* clear both encrypted and non-encrypted records */
for (i = 0; i < lba_cnt; i++) {
di_crc[lba + i] =
di_priv[lba + i] = 0;
}
return 0;
}
static int ufs_mtk_di_cmp_read(struct ufs_hba *hba,
u32 lba, int mode,
u16 crc, u8 priv)
{
/*
* For read, update CRC16 value
* if corresponding CRC16[lba] is 0 (not set before),
* Otherwise, compare current calculated crc16
* value to CRC16[lba].
*/
if (mode == UFS_CRYPTO_HW_FDE) {
if (di_crc[lba] == 0 || di_priv[lba] == 0) {
di_crc[lba] = crc;
di_priv[lba] = priv;
} else {
if (di_crc[lba] != crc) {
dev_info(hba->dev,
"%s: crc err! existed: 0x%x, read: 0x%x, lba: %u\n",
__func__, di_crc[lba],
crc, lba);
WARN_ON(1);
return -EIO;
}
}
} else if (mode == UFS_CRYPTO_HW_FBE) {
if (di_crc[lba] == 0 || di_priv[lba] == 0) {
di_crc[lba] = crc;
di_priv[lba] = priv;
} else {
if (priv != di_priv[lba]) {
dev_info(hba->dev,
"%s: key err (fde)! existed: 0x%x, read: 0x%x, lba: %u\n",
__func__,
di_priv[lba],
priv, lba);
return 0;
} else if (crc != di_crc[lba]) {
dev_info(hba->dev,
"%s: crc err (fbe)! existed: 0x%x, read: 0x%x, lba: %u\n",
__func__,
di_crc[lba],
crc, lba);
return 0;
}
}
} else {
if (di_crc[lba] == 0 || di_priv[lba] != 0) {
di_crc[lba] = crc;
di_priv[lba] = priv;
} else {
if (di_crc[lba] != crc) {
dev_info(hba->dev,
"%s: crc err (ne)! existed: 0x%x, read: 0x%x, lba: %u\n",
__func__,
di_crc[lba],
crc, lba);
WARN_ON(1);
return -EIO;
}
}
}
return 0;
}
static int ufs_mtk_di_cmp(struct ufs_hba *hba, struct scsi_cmnd *cmd)
{
char *buffer;
int i, len, err = 0;
struct scatterlist *sg;
u32 lba, blk_cnt, end_lba;
u16 crc = 0;
int mode = 0, tag;
u8 priv = 0;
sg = scsi_sglist(cmd);
tag = cmd->request->tag;
lba = cmd->cmnd[5] | (cmd->cmnd[4] << 8) | (cmd->cmnd[3] << 16) |
(cmd->cmnd[2] << 24);
if ((u64)lba >= di_blkcnt) {
dev_info(hba->dev,
"%s: lba err! expected: di_blkcnt: 0x%llx, LBA: 0x%x\n",
__func__, di_blkcnt, lba);
WARN_ON(1);
return -EIO;
}
/* HPB use READ_16, Transfer_len in cmd[15]*/
if (cmd->cmnd[0] == READ_16) {
if ((hba->card->wmanufacturerid == UFS_VENDOR_SAMSUNG) ||
(hba->card->wmanufacturerid == UFS_VENDOR_MICRON))
blk_cnt = cmd->cmnd[15];
else
blk_cnt = cmd->cmnd[14]; /* JEDEC version */
#if defined(CONFIG_SCSI_SKHPB)
} else if (cmd->cmnd[0] == SKHPB_READ) { /* JEDEC version */
blk_cnt = cmd->cmnd[14];
#endif
} else {
blk_cnt = cmd->cmnd[8] | (cmd->cmnd[7] << 8);
}
end_lba = lba + blk_cnt;
/* Only data commands in LU2 need to check crc */
if (ufshcd_scsi_to_upiu_lun(cmd->device->lun) != 0x2)
return 0;
if (!scsi_sg_count(cmd))
return 0;
if (hba->lrb[tag].crypto_enable) {
mode = UFS_CRYPTO_HW_FBE;
/*
* ufshcd_crypto_enable() will re-program all of the keys with
* the same index in keyslot_manager_reprogram_all_keys()
* so use key slot as key index for checking different keys
*/
priv = hba->lrb[tag].crypto_key_slot;
priv++;
}
for (i = 0; i < scsi_sg_count(cmd); i++) {
buffer = (char *)sg_virt(sg);
for (len = 0; len < sg->length; len = len + 0x1000, lba++) {
/*
* Use value 0 as empty slot in crc array
*
* if calculated crc value is 0, use crc + 1
* instead to avoid conflict
*/
crc = crc16(0x0, &buffer[len], DI_CRC_DATA_SIZE);
if (crc == 0)
crc++;
if (ufs_mtk_is_data_write_cmd(cmd->cmnd[0])) {
/* For write, update crc value */
di_crc[lba] = crc;
di_priv[lba] = priv;
} else {
err = ufs_mtk_di_cmp_read(hba,
lba, mode, crc, priv);
if (err)
return err;
}
}
sg = sg_next(sg);
}
/*
* Check lba # traverse from
* scatter is the same as end_lba
*/
if (end_lba != lba) {
dev_info(hba->dev,
"expect end_lba is 0x%x, but 0x%x, cmd=0x%x, blk_cnt=0x%x\n",
end_lba, lba, cmd->cmnd[0], blk_cnt);
}
return 0;
}
int ufs_mtk_di_inspect(struct ufs_hba *hba, struct scsi_cmnd *cmd)
{
if (!di_crc)
return 0;
/* do inspection in LU2 (user LU) only */
if (ufshcd_scsi_to_upiu_lun(cmd->device->lun) != 0x2)
return -ENODEV;
if (ufs_mtk_is_data_cmd(cmd->cmnd[0]))
return ufs_mtk_di_cmp(hba, cmd);
if (ufs_mtk_is_unmap_cmd(cmd->cmnd[0]))
return ufs_mtk_di_clr(cmd);
return -ENODEV;
}
#endif
static int ufs_mtk_query_desc(struct ufs_hba *hba, enum query_opcode opcode,
enum desc_idn idn, u8 index, void *desc, int len)
{
return ufshcd_query_descriptor_retry(hba,
opcode, idn, index, 0, desc, &len);
}
static int ufs_mtk_send_uic_command(struct ufs_hba *hba, u32 cmd,
u32 arg1, u32 arg2, u32 *arg3, u8 *err_code)
{
int result;
struct uic_command uic_cmd = {
.command = cmd,
.argument1 = arg1,
.argument2 = arg2,
.argument3 = *arg3,
};
result = ufshcd_send_uic_cmd(hba, &uic_cmd);
if (err_code)
*err_code = uic_cmd.argument2 & MASK_UIC_COMMAND_RESULT;
if (result) {
dev_err(hba->dev, "UIC command error: %#x\n", result);
return -EIO;
}
if (cmd == UIC_CMD_DME_GET || cmd == UIC_CMD_DME_PEER_GET)
*arg3 = uic_cmd.argument3;
return 0;
}
int ufs_mtk_run_batch_uic_cmd(struct ufs_hba *hba,
struct uic_command *cmds, int ncmds)
{
int i;
int err = 0;
for (i = 0; i < ncmds; i++) {
err = ufshcd_send_uic_cmd(hba, &cmds[i]);
if (err) {
dev_err(hba->dev, "%s fail, cmd: %x, arg1: %x\n",
__func__, cmds->command, cmds->argument1);
/* return err; */
}
}
return err;
}
int ufs_mtk_cfg_unipro_cg(struct ufs_hba *hba, bool enable)
{
u32 tmp = 0;
if (enable) {
ufshcd_dme_get(hba, UIC_ARG_MIB(VENDOR_SAVEPOWERCONTROL), &tmp);
tmp = tmp | (1 << RX_SYMBOL_CLK_GATE_EN) |
(1 << SYS_CLK_GATE_EN) |
(1 << TX_CLK_GATE_EN);
ufshcd_dme_set(hba, UIC_ARG_MIB(VENDOR_SAVEPOWERCONTROL), tmp);
ufshcd_dme_get(hba, UIC_ARG_MIB(VENDOR_DEBUGCLOCKENABLE), &tmp);
tmp = tmp & ~(1 << TX_SYMBOL_CLK_REQ_FORCE);
ufshcd_dme_set(hba, UIC_ARG_MIB(VENDOR_DEBUGCLOCKENABLE), tmp);
} else {
ufshcd_dme_get(hba, UIC_ARG_MIB(VENDOR_SAVEPOWERCONTROL), &tmp);
tmp = tmp & ~((1 << RX_SYMBOL_CLK_GATE_EN) |
(1 << SYS_CLK_GATE_EN) |
(1 << TX_CLK_GATE_EN));
ufshcd_dme_set(hba, UIC_ARG_MIB(VENDOR_SAVEPOWERCONTROL), tmp);
ufshcd_dme_get(hba, UIC_ARG_MIB(VENDOR_DEBUGCLOCKENABLE), &tmp);
tmp = tmp | (1 << TX_SYMBOL_CLK_REQ_FORCE);
ufshcd_dme_set(hba, UIC_ARG_MIB(VENDOR_DEBUGCLOCKENABLE), tmp);
}
return 0;
}
/**
* ufs_mtk_advertise_quirks - advertise the known mtk UFS controller quirks
* @hba: host controller instance
*
* mtk UFS host controller might have some non standard behaviours (quirks)
* than what is specified by UFSHCI specification. Advertise all such
* quirks to standard UFS host controller driver so standard takes them into
* account.
*/
static void ufs_mtk_advertise_hci_quirks(struct ufs_hba *hba)
{
#if defined(CONFIG_MTK_HW_FDE)
#if defined(UFS_MTK_PLATFORM_UFS_HCI_PERF_HEURISTIC)
hba->quirks |= UFSHCD_QUIRK_UFS_HCI_PERF_HEURISTIC;
#endif
#endif
#if defined(UFS_MTK_PLATFORM_UFS_HCI_RST_DEV_FOR_LINKUP_FAIL)
hba->quirks |= UFSHCD_QUIRK_UFS_HCI_DEV_RST_FOR_LINKUP_FAIL;
#endif
#if defined(UFS_MTK_PLATFORM_UFS_HCI_VENDOR_HOST_RST)
hba->quirks |= UFSHCD_QUIRK_UFS_HCI_VENDOR_HOST_RST;
#endif
/* Always enable "Disable AH8 before RDB" */
hba->quirks |= UFSHCD_QUIRK_UFS_HCI_DISABLE_AH8_BEFORE_RDB;
dev_info(hba->dev, "hci quirks: %#x\n", hba->quirks);
}
#ifdef CONFIG_MTK_HW_FDE
/**
* ufs_mtk_hwfde_cfg_cmd - configure command for hw fde
* @hba: host controller instance
* @cmd: scsi command instance
*
* HW FDE key may be changed by updating master key by end-user's behavior.
* Update new key to crypto IP if necessary.
*
* Host lock must be held during key update in atf.
*/
void ufs_mtk_hwfde_cfg_cmd(struct ufs_hba *hba, struct scsi_cmnd *cmd)
{
u64 lba;
u32 dunl, dunu;
unsigned long flags;
int hwfde_key_idx_old;
struct ufshcd_lrb *lrbp;
lrbp = &hba->lrb[cmd->request->tag];
/* for r/w request only */
if (cmd->request->bio && cmd->request->bio->bi_hw_fde) {
/* in case hw fde is enabled and key index is updated by
* dm-crypt
*/
if (cmd->request->bio->bi_key_idx !=
hba->crypto_hwfde_key_idx) {
/* acquire host lock to ensure atomicity during key
* change in atf
*/
spin_lock_irqsave(hba->host->host_lock, flags);
/* do key change */
mt_secure_call(MTK_SIP_KERNEL_HW_FDE_UFS_CTL, (1 << 3),
0, 0, 0);
hwfde_key_idx_old = hba->crypto_hwfde_key_idx;
hba->crypto_hwfde_key_idx =
cmd->request->bio->bi_key_idx;
spin_unlock_irqrestore(hba->host->host_lock, flags);
dev_info(hba->dev, "update hw-fde key, ver %d->%d\n",
hwfde_key_idx_old,
hba->crypto_hwfde_key_idx);
}
lba = blk_rq_pos(cmd->request) >> 3;
ufs_mtk_crypto_cal_dun(UFS_CRYPTO_ALGO_ESSIV_AES_CBC,
lba, &dunl, &dunu);
lrbp->crypto_key_slot = 0;
lrbp->crypto_enable = true;
lrbp->data_unit_num = ((u64)dunu << 32 || dunl);
/* mark data has ever gone through encryption/decryption path */
hba->crypto_feature |= UFS_CRYPTO_HW_FDE_ENCRYPTED;
}
}
#else
void ufs_mtk_hwfde_cfg_cmd(struct ufs_hba *hba, struct scsi_cmnd *cmd)
{
};
#endif
static enum ufs_pm_level
ufs_mtk_get_desired_pm_lvl_for_dev_link_state(enum ufs_dev_pwr_mode dev_state,
enum uic_link_state link_state)
{
enum ufs_pm_level lvl;
for (lvl = UFS_PM_LVL_0; lvl < UFS_PM_LVL_MAX; lvl++) {
if ((ufs_pm_lvl_states[lvl].dev_state == dev_state) &&
(ufs_pm_lvl_states[lvl].link_state == link_state))
return lvl;
}
/* if no match found, return the level 0 */
return UFS_PM_LVL_0;
}
static bool ufs_mtk_is_valid_pm_lvl(int lvl)
{
if (lvl >= 0 && lvl < ufs_pm_lvl_states_size)
return true;
else
return false;
}
static int ufs_mtk_host_clk_get(struct device *dev, const char *name,
struct clk **clk_out)
{
struct clk *clk;
int err = 0;
clk = devm_clk_get(dev, name);
if (IS_ERR(clk))
err = PTR_ERR(clk);
else
*clk_out = clk;
return err;
}
bool ufs_mtk_perf_is_supported(struct ufs_mtk_host *host)
{
if (!host->crypto_clk_mux ||
!host->crypto_parent_clk_normal ||
!host->crypto_parent_clk_perf ||
!host->req_vcore ||
host->crypto_vcore_opp < 0)
return false;
else
return true;
}
int ufs_mtk_perf_setup_req(struct ufs_mtk_host *host, bool perf)
{
int err = 0;
err = clk_prepare_enable(host->crypto_clk_mux);
if (err) {
dev_info(host->hba->dev, "%s: clk_prepare_enable(): %d\n",
__func__, err);
goto out;
}
if (perf) {
pm_qos_update_request(host->req_vcore,
host->crypto_vcore_opp);
err = clk_set_parent(host->crypto_clk_mux,
host->crypto_parent_clk_perf);
} else {
err = clk_set_parent(host->crypto_clk_mux,
host->crypto_parent_clk_normal);
pm_qos_update_request(host->req_vcore,
PM_QOS_VCORE_OPP_DEFAULT_VALUE);
}
if (err)
dev_info(host->hba->dev, "%s: clk_set_parent(): %d\n",
__func__, err);
clk_disable_unprepare(host->crypto_clk_mux);
out:
ufs_mtk_dbg_add_trace(host->hba, UFS_TRACE_PERF_MODE,
perf, 0, (u32)err,
0, 0, 0, 0, 0, 0);
return err;
}
int ufs_mtk_perf_setup_crypto_clk(struct ufs_mtk_host *host, bool perf)
{
int err = 0;
bool rpm_resumed = false;
bool clk_prepared = false;
if (!ufs_mtk_perf_is_supported(host)) {
dev_info(host->hba->dev, "%s: perf mode is unsupported\n",
__func__);
err = -ENOTSUPP;
goto out;
}
/* runtime resume shall be prior to blocking requests */
pm_runtime_get_sync(host->hba->dev);
rpm_resumed = true;
/*
* reuse clk scaling preparation function to wait until all
* on-going commands are done, and then block future commands
*/
err = ufshcd_clock_scaling_prepare(host->hba);
if (err) {
dev_info(host->hba->dev,
"%s: ufshcd_clock_scaling_prepare(): %d\n",
__func__, err);
goto out;
}
clk_prepared = true;
err = ufs_mtk_perf_setup_req(host, perf);
out:
/*
* add event before any possible incoming commands
* by unblocking requests in ufshcd_clock_scaling_unprepare()
*/
dev_info(host->hba->dev, "perf mode: request %s %s\n",
perf ? "on" : "off",
err ? "failed" : "ok");
if (clk_prepared)
ufshcd_clock_scaling_unprepare(host->hba);
if (rpm_resumed)
pm_runtime_put_sync(host->hba->dev);
if (!err)
host->perf_en = perf;
return err;
}
int ufs_mtk_perf_setup(struct ufs_mtk_host *host,
bool perf)
{
int err = 0;
if (!ufs_mtk_perf_is_supported(host) ||
(host->perf_mode != PERF_AUTO)) {
/* return without error */
return 0;
}
#ifdef CONFIG_UFSTW
/* Turbo write may disable or not support */
if (!host->hba->ufsf.tw_lup[2] || !host->hba->ufsf.tw_lup[2]->tw_enable)
return 0;
#endif
err = ufs_mtk_perf_setup_req(host, perf);
if (!err)
host->perf_en = perf;
else
dev_info(host->hba->dev, "%s: %s fail %d\n",
__func__, perf ? "en":"dis", err);
return err;
}
static int ufs_mtk_perf_init_crypto(struct ufs_hba *hba)
{
int err = 0;
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
struct device_node *np = hba->dev->of_node;
err = ufs_mtk_host_clk_get(hba->dev,
"ufs-vendor-crypto-clk-mux",
&host->crypto_clk_mux);
if (err) {
dev_info(hba->dev,
"%s: failed to get ufs-vendor-crypto-clk-mux, err: %d",
__func__, err);
goto out;
}
err = ufs_mtk_host_clk_get(hba->dev,
"ufs-vendor-crypto-normal-parent-clk",
&host->crypto_parent_clk_normal);
if (err) {
dev_info(hba->dev,
"%s: failed to get ufs-vendor-crypto-normal-parent-clk, err: %d",
__func__, err);
goto out;
}
err = ufs_mtk_host_clk_get(hba->dev,
"ufs-vendor-crypto-perf-parent-clk",
&host->crypto_parent_clk_perf);
if (err) {
dev_info(hba->dev,
"%s: failed to get ufs-vendor-crypto-perf-parent-clk, err: %d",
__func__, err);
goto out;
}
err = of_property_read_s32(np, "mediatek,perf-crypto-vcore",
&host->crypto_vcore_opp);
if (err) {
dev_info(hba->dev,
"%s: failed to get mediatek,perf-crypto-vcore",
__func__);
host->crypto_vcore_opp = -1;
goto out;
}
/* init VCORE QOS */
host->req_vcore = devm_kzalloc(hba->dev, sizeof(*host->req_vcore),
GFP_KERNEL);
if (!host->req_vcore) {
err = -ENOMEM;
goto out;
}
pm_qos_add_request(host->req_vcore, PM_QOS_VCORE_OPP,
PM_QOS_VCORE_OPP_DEFAULT_VALUE);
out:
#ifdef CONFIG_UFSTW
if (!err)
host->perf_mode = PERF_AUTO;
else
#endif
host->perf_mode = PERF_FORCE_DISABLE;
return err;
}
static int ufs_mtk_setup_clocks(struct ufs_hba *hba, bool on,
enum ufs_notify_change_status stage)
{
int ret = 0;
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
switch (stage) {
case PRE_CHANGE:
if (!on) {
if (host && host->pm_qos_init) {
pm_qos_update_request(
&host->req_cpu_dma_latency,
PM_QOS_DEFAULT_VALUE);
ret = ufs_mtk_perf_setup(host, false);
if (ret)
goto out;
}
ret = ufs_mtk_pltfrm_ref_clk_ctrl(hba, false);
if (ret)
goto out;
}
break;
case POST_CHANGE:
if (on) {
ret = ufs_mtk_pltfrm_ref_clk_ctrl(hba, true);
if (ret)
goto out;
if (host && host->pm_qos_init) {
pm_qos_update_request(
&host->req_cpu_dma_latency, 0);
ret = ufs_mtk_perf_setup(host, true);
if (ret)
goto out;
}
}
break;
default:
break;
}
out:
return ret;
}
static void ufs_mtk_set_caps(struct ufs_hba *hba)
{
hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
}
/**
* ufs_mtk_init - find other essential mmio bases
* @hba: host controller instance
*/
static int ufs_mtk_init(struct ufs_hba *hba)
{
struct ufs_mtk_host *host;
int err = 0;
struct platform_device *pdev;
host = devm_kzalloc(hba->dev, sizeof(*host), GFP_KERNEL);
if (!host) {
err = -ENOMEM;
dev_info(hba->dev,
"%s: no memory for mtk ufs host\n", __func__);
goto out;
}
host->hba = hba;
ufshcd_set_variant(hba, host);
/* initialize globals */
ufs_mtk_rpm_autosuspend_delay = -1;
ufs_mtk_rpm_enabled = false;
ufs_mtk_auto_hibern8_enabled = false;
ufs_mtk_host_deep_stall_enable = 0;
ufs_mtk_host_scramble_enable = 0;
ufs_mtk_hba = hba;
hba->crypto_hwfde_key_idx = -1;
/*
* Rename device to unify device path for booting storage device.
*
* Device rename shall be prior to any pinctrl operation to avoid
* known kernel panic issue which can be triggered by dumping pin
* information, for example,
*
* "cat /sys/kernel/debug/pinctrl/10005000.pinctrl/pinmux-pins".
*
* The panic is because create_pinctrl() will keep the original
* device name string instance in kobject. However, old name string
* instance will be freed during device_rename() but NOT awared by
* pinctrl.
*
* Please also remove default pin state in device tree and related
* code because create_pinctrl() will be activated before device
* probing if default pin state is declared.
*/
device_rename(hba->dev, "bootdevice");
/*
* fix uaf(use afer free) issue: modify pdev->name,
* device_rename will free pdev->name
*/
pdev = to_platform_device(hba->dev);
pdev->name = pdev->dev.kobj.name;
ufs_mtk_pltfrm_init();
ufs_mtk_pltfrm_parse_dt(hba);
ufs_mtk_set_caps(hba);
ufs_mtk_advertise_hci_quirks(hba);
ufs_mtk_parse_dt(hba);
/*
* If rpm_lvl and and spm_lvl are not already set to valid levels,
* set the default power management level for UFS runtime and system
* suspend. Default power saving mode selected is keeping UFS link in
* Hibern8 state and UFS device in sleep.
*/
if (!ufs_mtk_is_valid_pm_lvl(hba->rpm_lvl))
hba->rpm_lvl = ufs_mtk_get_desired_pm_lvl_for_dev_link_state(
UFS_SLEEP_PWR_MODE,
UIC_LINK_HIBERN8_STATE);
if (!ufs_mtk_is_valid_pm_lvl(hba->spm_lvl))
hba->spm_lvl = ufs_mtk_get_desired_pm_lvl_for_dev_link_state(
UFS_SLEEP_PWR_MODE,
UIC_LINK_HIBERN8_STATE);
/* Get auto-hibern8 timeout from device tree */
ufs_mtk_parse_auto_hibern8_timer(hba);
ufs_mtk_perf_init_crypto(hba);
pm_qos_add_request(&host->req_cpu_dma_latency, PM_QOS_CPU_DMA_LATENCY,
PM_QOS_DEFAULT_VALUE);
host->pm_qos_init = true;
out:
return err;
}
/**
* ufs_mtk_exit - release resource
* @hba: host controller instance
*/
void ufs_mtk_exit(struct ufs_hba *hba)
{
struct ufs_mtk_host *host;
host = ufshcd_get_variant(hba);
if (host->pm_qos_init) {
/* remove pm_qos when exit */
pm_qos_remove_request(host->req_vcore);
pm_qos_remove_request(&host->req_cpu_dma_latency);
host->pm_qos_init = false;
}
/* prevent pointer is used after hba is freed */
ufs_mtk_hba = NULL;
}
static int ufs_mtk_pre_pwr_change(struct ufs_hba *hba,
struct ufs_pa_layer_attr *desired,
struct ufs_pa_layer_attr *final)
{
int err = 0;
struct ufs_descriptor desc;
/* get manu ID for vendor specific configuration in the future */
if (hba->manu_id == 0) {
/* read device descriptor */
desc.descriptor_idn = 0;
desc.index = 0;
err = ufs_mtk_query_desc(hba,
UPIU_QUERY_FUNC_STANDARD_READ_REQUEST,
desc.descriptor_idn, desc.index,
desc.descriptor, sizeof(desc.descriptor));
if (err)
return err;
/* get wManufacturerID */
hba->manu_id = desc.descriptor[0x18] << 8 |
desc.descriptor[0x19];
dev_dbg(hba->dev, "wManufacturerID: 0x%x\n", hba->manu_id);
}
/* HSG3B as default power mode, only use HSG1B at FPGA */
#ifndef CONFIG_FPGA_EARLY_PORTING
if (ufs_mtk_hs_gear == UFS_HS_G4) {
if ((desired->gear_rx == UFS_HS_G4) &&
(desired->gear_tx == UFS_HS_G4)) {
final->gear_rx = UFS_HS_G4;
final->gear_tx = UFS_HS_G4;
/* INITIAL ADAPT */
ufshcd_dme_set(hba,
UIC_ARG_MIB(PA_TXHSADAPTTYPE),
PA_INITIAL_ADAPT);
} else {
final->gear_rx = UFS_HS_G3;
final->gear_tx = UFS_HS_G3;
/* NO ADAPT */
ufshcd_dme_set(hba,
UIC_ARG_MIB(PA_TXHSADAPTTYPE),
PA_NO_ADAPT);
}
} else {
final->gear_rx = UFS_HS_G3;
final->gear_tx = UFS_HS_G3;
}
#else
final->gear_rx = UFS_HS_G1;
final->gear_tx = UFS_HS_G1;
#endif
/* Change by dts setting */
if (hba->lanes_per_direction == 2) {
final->lane_rx = 2;
final->lane_tx = 2;
} else {
final->lane_rx = 1;
final->lane_tx = 1;
}
final->hs_rate = PA_HS_MODE_B;
final->pwr_rx = FAST_MODE;
final->pwr_tx = FAST_MODE;
ufs_mtk_pltfrm_pwr_change_final_gear(hba, final);
/* Set PAPowerModeUserData[0~5] = 0xffff, default is 0 */
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA0), 0x1fff);
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA1), 0xffff);
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA2), 0x7fff);
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA3), 0x1fff);
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA4), 0xffff);
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA5), 0x7fff);
return err;
}
static int ufs_mtk_pwr_change_notify(struct ufs_hba *hba,
enum ufs_notify_change_status stage,
struct ufs_pa_layer_attr *desired,
struct ufs_pa_layer_attr *final)
{
int ret = 0;
switch (stage) {
case PRE_CHANGE:
ret = ufs_mtk_pre_pwr_change(hba, desired, final);
break;
case POST_CHANGE:
break;
default:
break;
}
return ret;
}
static int ufs_mtk_init_mphy(struct ufs_hba *hba)
{
return 0;
}
static int ufs_mtk_enable_crypto(struct ufs_hba *hba)
{
/* restore vendor crypto setting by re-using resume operation */
mt_secure_call(MTK_SIP_KERNEL_HW_FDE_UFS_CTL, (1 << 2), 0, 0, 0);
return 0;
}
static int ufs_mtk_reset_device(struct ufs_hba *hba)
{
dev_info(hba->dev, "reset device\n");
/* do device hw reset */
mt_secure_call(MTK_SIP_KERNEL_HW_FDE_UFS_CTL, (1 << 5), 0, 0, 0);
return 0;
}
int ufs_mtk_linkup_fail_handler(struct ufs_hba *hba, int left_retry)
{
if (!(hba->quirks & UFSHCD_QUIRK_UFS_HCI_DEV_RST_FOR_LINKUP_FAIL))
return 0;
if (left_retry <= 1)
ufs_mtk_reset_device(hba);
return 0;
}
int ufs_mtk_check_powerctl(struct ufs_hba *hba)
{
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
int err = 0;
u32 val = 0;
/* check if host in power saving */
err = ufshcd_dme_get(hba,
UIC_ARG_MIB(VENDOR_UNIPROPOWERDOWNCONTROL), &val);
if (!err && val == 0x1) {
err = ufshcd_dme_set(hba,
UIC_ARG_MIB(VENDOR_UNIPROPOWERDOWNCONTROL), 0);
dev_info(hba->dev, "get dme 0x%x = %d, set 0 (%d)\n",
VENDOR_UNIPROPOWERDOWNCONTROL, val, err);
}
/*
* Set unipro as non-lpm mode anyway for initialization and error
* recovery
*/
host->unipro_lpm = false;
return err;
}
static int ufs_mtk_hce_enable_notify(struct ufs_hba *hba,
enum ufs_notify_change_status stage)
{
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
int ret = 0;
switch (stage) {
case PRE_CHANGE:
if (host->unipro_lpm)
hba->hba_enable_delay_us = 0;
else
hba->hba_enable_delay_us = 600;
break;
case POST_CHANGE:
ret = ufs_mtk_enable_crypto(hba);
/*
* After HCE enable, need disable xoufs_req_s in ufshci
* when xoufs hw solution is not ready.
*/
#ifndef UFS_REF_CLK_CTRL_BY_UFSHCI
/*
* Old chip not use this, disable it always after HCE enable
* to deactivate UFS_SRCCLKENA/UFS_INFRA_REQ/UFS_VRF18_REQ
* after ufs_mtk_pltfrm_resume.
*/
ufshcd_writel(hba, 0, REG_UFS_ADDR_XOUFS_ST);
#endif
break;
default:
break;
}
return ret;
}
static int ufs_mtk_pre_link(struct ufs_hba *hba)
{
int ret = 0;
u32 tmp;
ufs_mtk_pltfrm_bootrom_deputy(hba);
ufs_mtk_init_mphy(hba);
/* ensure auto-hibern8 is disabled during hba probing */
ufshcd_vops_auto_hibern8(hba, false);
/* powerup unipro if unipro powerdown */
ret = ufs_mtk_check_powerctl(hba);
if (ret)
return ret;
/* configure deep stall */
ret = ufshcd_dme_get(hba, UIC_ARG_MIB(VENDOR_SAVEPOWERCONTROL), &tmp);
if (ret)
return ret;
if (ufs_mtk_host_deep_stall_enable) /* enable deep stall */
tmp |= (1 << 6);
else
tmp &= ~(1 << 6); /* disable deep stall */
ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VENDOR_SAVEPOWERCONTROL), tmp);
if (ret)
return ret;
/* configure scrambling */
if (ufs_mtk_host_scramble_enable)
ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_SCRAMBLING), 1);
else
ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_SCRAMBLING), 0);
return ret;
}
static int ufs_mtk_post_link(struct ufs_hba *hba)
{
int ret = 0;
u32 arg = 0;
u32 ah_ms;
/* disable device LCC */
ret = ufs_mtk_send_uic_command(hba, UIC_CMD_DME_SET,
UIC_ARG_MIB(PA_LOCALTXLCCENABLE), 0, &arg, NULL);
if (ret) {
dev_err(hba->dev, "dme_setting_after_link fail\n");
ret = 0; /* skip error */
}
/* enable unipro clock gating feature */
ufs_mtk_cfg_unipro_cg(hba, true);
/* configure clk gating delay */
if (ufshcd_is_clkgating_allowed(hba)) {
if (ufshcd_is_auto_hibern8_supported(hba) && hba->ahit)
ah_ms = FIELD_GET(UFSHCI_AHIBERN8_TIMER_MASK,
hba->ahit);
else
ah_ms = 10;
hba->clk_gating.delay_ms = ah_ms + 5;
} else
hba->clk_gating.delay_ms = 0;
return ret;
}
static void ufs_mtk_vreg_lpm(struct ufs_hba *hba, bool lpm)
{
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
if (!host->vreg_lpm_supported || !hba->vreg_info.vcc)
return;
if (lpm & !hba->vreg_info.vcc->enabled)
ufs_mtk_vufs_lpm(1);
else if (!lpm)
ufs_mtk_vufs_lpm(0);
}
static int ufs_mtk_link_startup_notify(struct ufs_hba *hba,
enum ufs_notify_change_status stage)
{
int ret = 0;
switch (stage) {
case PRE_CHANGE:
ret = ufs_mtk_pre_link(hba);
break;
case POST_CHANGE:
ret = ufs_mtk_post_link(hba);
break;
default:
break;
}
return ret;
}
static int ufs_mtk_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
int ret = 0;
if (ufshcd_is_link_hibern8(hba)) {
/*
* HCI power-down flow with link in hibern8
* Enter vendor-specific power down mode to keep UniPro state
*/
ret = ufshcd_dme_set(hba,
UIC_ARG_MIB_SEL(VENDOR_UNIPROPOWERDOWNCONTROL, 0), 1);
if (ret) {
/* dump ufs debug Info like XO_UFS/VEMC/VUFS18 */
ufs_mtk_pltfrm_gpio_trigger_and_debugInfo_dump(hba);
/*
* Power down fail leave vendor-specific power down mode
* to resume UniPro state
*/
(void)ufshcd_dme_set(hba,
UIC_ARG_MIB_SEL(VENDOR_UNIPROPOWERDOWNCONTROL,
0), 0);
ret = -EAGAIN;
goto out;
}
host->unipro_lpm = true;
ufs_mtk_pltfrm_suspend(hba);
/* vendor-specific crypto suspend */
mt_secure_call(MTK_SIP_KERNEL_HW_FDE_UFS_CTL, (1 << 1),
0, 0, 0);
/*
* Make sure no error will be returned by suspend callback
* before making regulators enter low-power mode because any
* error will lead to re-enable regulators by error handling
* in ufshcd_suspend().
*/
ufs_mtk_vreg_lpm(hba, true);
}
out:
if (ret) {
ufs_mtk_pltfrm_gpio_trigger_and_debugInfo_dump(hba);
ufshcd_print_host_state(hba, 0, NULL, NULL, NULL);
ufs_mtk_dbg_dump_trace(NULL, NULL,
50, NULL);
}
return ret;
}
static void ufs_mtk_dbg_register_dump(struct ufs_hba *hba)
{
u32 val;
/* read debugging register REG_UFS_MTK_PROBE */
/*
* configure REG_UFS_MTK_DEBUG_SEL to direct debugging information
* to REG_UFS_MTK_PROBE.
*
* REG_UFS_MTK_DEBUG_SEL is not required to set back
* as 0x0 (default value) after dump.
*/
ufshcd_writel(hba, 0x20, REG_UFS_MTK_DEBUG_SEL);
val = ufshcd_readl(hba, REG_UFS_MTK_PROBE);
dev_info(hba->dev, "REG_UFS_MTK_PROBE: 0x%x\n", val);
}
static int ufs_mtk_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
int ret = 0;
if (ufshcd_is_link_hibern8(hba)) {
ufs_mtk_vreg_lpm(hba, false);
ufs_mtk_pltfrm_resume(hba);
/*
* HCI power-on flow with link in hibern8
*
* Enable UFSHCI
* NOTE: interrupt mask UFSHCD_UIC_MASK
* (UIC_COMMAND_COMPL | UFSHCD_UIC_PWR_MASK)
* will be enabled inside.
*/
ret = ufshcd_hba_enable(hba);
if (ret) {
dev_err(hba->dev, "%s: hba_enable failed. ret = %d\n",
__func__, ret);
goto out;
}
/* Leave vendor-specific power down mode to resume
* UniPro state
*/
ret = ufshcd_dme_set(hba,
UIC_ARG_MIB_SEL(VENDOR_UNIPROPOWERDOWNCONTROL, 0), 0);
if (ret) {
dev_err(hba->dev, "%s: UniProPowerDownControl failed. ret = %d\n",
__func__, ret);
goto out;
}
host->unipro_lpm = false;
/*
* Leave hibern8 state
* NOTE: ufshcd_make_hba_operational will be ok only if link
* is in active state.
*
* The reason is: ufshcd_make_hba_operational
* will check if REG_CONTROLLER_STATUS
* is OK. In REG_CONTROLLER_STATUS, UTMRLRDY
* and UTRLRDY will be ready only if
* DP is set. DP will be set only if link
* is in active state in MTK design.
*/
ret = ufshcd_uic_hibern8_exit(hba);
if (!ret)
ufshcd_set_link_active(hba);
/* Re-start hba */
ret = ufshcd_make_hba_operational(hba);
if (ret) {
dev_err(hba->dev, "%s: make_hba_operational failed. ret = %d\n",
__func__, ret);
goto out;
}
/* vendor-specific crypto resume */
mt_secure_call(MTK_SIP_KERNEL_HW_FDE_UFS_CTL, (1 << 2),
0, 0, 0);
}
out:
if (ret) {
ufs_mtk_pltfrm_gpio_trigger_and_debugInfo_dump(hba);
ufshcd_print_host_state(hba, 0, NULL, NULL, NULL);
ufs_mtk_dbg_dump_trace(NULL, NULL,
50, NULL);
}
return ret;
}
void ufs_mtk_parse_dt(struct ufs_hba *hba)
{
struct device *dev = hba->dev;
struct device_node *np = dev->of_node;
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
u32 val;
if (np) {
/* system PM */
if (of_property_read_u32(np, "mediatek,spm-level",
&hba->spm_lvl))
hba->spm_lvl = -1;
/* runtime PM */
if (of_property_read_u32(np, "mediatek,rpm-level",
&hba->rpm_lvl))
hba->rpm_lvl = -1;
if (!of_property_read_u32(np, "mediatek,rpm-enable", &val)) {
if (val)
ufs_mtk_rpm_enabled = true;
}
if (of_property_read_u32(np, "mediatek,rpm-autosuspend-delay",
&ufs_mtk_rpm_autosuspend_delay))
ufs_mtk_rpm_autosuspend_delay = -1;
if (of_property_read_bool(np, "mediatek,spm_sw_mode"))
host->spm_sw_mode = true;
else
host->spm_sw_mode = false;
if (of_property_read_u32(np, "highspeed-gear",
&ufs_mtk_hs_gear))
ufs_mtk_hs_gear = UFS_HS_G3;
}
}
void ufs_mtk_parse_auto_hibern8_timer(struct ufs_hba *hba)
{
struct device *dev = hba->dev;
struct device_node *np = dev->of_node;
unsigned long flags;
u32 ahit;
u32 ah_ms = 0;
if (np) {
if (of_property_read_u32(np, "mediatek,auto-hibern8-timer",
&ah_ms))
ah_ms = 0;
}
dev_info(hba->dev, "auto-hibern8 timer %d ms\n", ah_ms);
if (!ah_ms)
return;
/* update auto-hibern8 timer */
ahit = FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, ah_ms) |
FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, 3);
spin_lock_irqsave(hba->host->host_lock, flags);
if (hba->ahit == ahit)
goto out_unlock;
hba->ahit = ahit;
out_unlock:
spin_unlock_irqrestore(hba->host->host_lock, flags);
}
/**
* ufs_mtk_ffu_send_cmd - sends WRITE BUFFER command to do FFU
* @hba: per adapter instance
* @idata: ioctl data for ffu
*
* Returns 0 if ffu operation is sccessfull
* Returns non-zero if failed to do ffu
*/
static int ufs_mtk_ffu_send_cmd(struct scsi_device *dev,
struct ufs_ioctl_ffu_data *idata)
{
struct ufs_hba *hba;
unsigned char cmd[10];
struct scsi_sense_hdr sshdr;
unsigned long flags;
int ret;
int size_to_write, written;
if (dev)
hba = shost_priv(dev->host);
else
return -ENODEV;
spin_lock_irqsave(hba->host->host_lock, flags);
ret = scsi_device_get(dev);
if (!ret && !scsi_device_online(dev)) {
ret = -ENODEV;
scsi_device_put(dev);
}
spin_unlock_irqrestore(hba->host->host_lock, flags);
if (ret)
return ret;
for (written = 0; written < idata->buf_byte;
written += size_to_write) {
if ((idata->buf_byte - written) > MAX_WRITE_BUFFER_SIZE)
size_to_write = MAX_WRITE_BUFFER_SIZE;
else
size_to_write = (idata->buf_byte - written);
/*
* If scsi commands fail, the scsi mid-layer schedules scsi
* error-handling, which would wait for host to be resumed.
* Since we know we are functional while we are here, skip
* host resume in error handling context.
*/
hba->host->eh_noresume = 1;
cmd[0] = WRITE_BUFFER; /* Opcode */
/* 0xE: Download firmware */
cmd[1] = 0xE;
cmd[2] = 0; /* Buffer ID = 0 */
/* Buffer Offset[23:16] = 0 */
cmd[3] = (unsigned char)((written >> 16) & 0xff);
/* Buffer Offset[15:08] = 0 */
cmd[4] = (unsigned char)((written >> 8) & 0xff);
/* Buffer Offset[07:00] = 0 */
cmd[5] = (unsigned char)(written & 0xff);
cmd[6] = (size_to_write >> 16) & 0xff; /* Length[23:16] */
cmd[7] = (size_to_write >> 8) & 0xff; /* Length[15:08] */
cmd[8] = (size_to_write) & 0xff; /* Length[07:00] */
cmd[9] = 0x0; /* Control = 0 */
/*
* Current function would be generally called from the power
* management callbacks hence set the RQF_PM flag so that it
* doesn't resume the already suspended children.
*/
ret = scsi_execute(dev, cmd, DMA_TO_DEVICE,
idata->buf_ptr + written,
size_to_write, NULL, &sshdr,
msecs_to_jiffies(1000), 0, 0, RQF_PM, NULL);
if (ret) {
sdev_printk(KERN_ERR, dev,
"WRITE BUFFER failed for firmware upgrade\n");
}
}
scsi_device_put(dev);
hba->host->eh_noresume = 0;
return ret;
}
/**
* ufs_mtk_ioctl_get_fw_ver - perform user request: query fw ver
* @hba: per-adapter instance
* @buffer: user space buffer for ffu ioctl data
* @return: 0 for success negative error code otherwise
*
* Expected/Submitted buffer structure is struct ufs_ioctl_ffu_data.
* It will read the buffer information of new firmware.
*/
int ufs_mtk_ioctl_get_fw_ver(struct scsi_device *dev, void __user *buf_user)
{
struct ufs_hba *hba;
struct ufs_ioctl_query_fw_ver_data *idata = NULL;
int err;
if (dev)
hba = shost_priv(dev->host);
else
return -ENODEV;
/* check scsi device instance */
if (!dev->rev) {
dev_err(hba->dev, "%s: scsi_device or rev is NULL\n", __func__);
err = -ENOENT;
goto out;
}
idata = kzalloc(sizeof(struct ufs_ioctl_query_fw_ver_data), GFP_KERNEL);
if (!idata) {
err = -ENOMEM;
goto out;
}
/* extract params from user buffer */
err = copy_from_user(idata, buf_user,
sizeof(struct ufs_ioctl_query_fw_ver_data));
if (err) {
dev_err(hba->dev,
"%s: failed copying buffer from user, err %d\n",
__func__, err);
goto out_release_mem;
}
idata->buf_byte = min_t(int, UFS_IOCTL_FFU_MAX_FW_VER_BYTES,
idata->buf_byte);
/*
* Copy firmware version string to user buffer
*
* We get firmware version from scsi_device->rev,
* which is ready in scsi_add_lun()
* during SCSI device probe process.
*
* If probe failed, rev will be NULL.
* We checked it in the beginning of this function.
*/
err = copy_to_user(idata->buf_ptr, dev->rev, idata->buf_byte);
if (err) {
dev_err(hba->dev, "%s: err %d copying back to user.\n",
__func__, err);
goto out_release_mem;
}
out_release_mem:
kfree(idata);
out:
return 0;
}
void ufs_mtk_device_quiesce(struct ufs_hba *hba)
{
struct scsi_device *scsi_d;
int i;
/*
* Wait all cmds done & block user issue cmds to
* general LUs, wlun device, wlun rpmb and wlun boot.
* To avoid new cmds coming after device has been
* stopped by SSU cmd in ufshcd_suspend().
*/
for (i = 0; i < UFS_UPIU_MAX_GENERAL_LUN; i++) {
scsi_d = scsi_device_lookup(hba->host, 0, 0, i);
if (scsi_d)
scsi_device_quiesce(scsi_d);
}
scsi_d = scsi_device_lookup(hba->host, 0, 0,
ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_BOOT_WLUN));
if (scsi_d)
scsi_device_quiesce(scsi_d);
if (hba->sdev_ufs_device)
scsi_device_quiesce(hba->sdev_ufs_device);
if (hba->sdev_ufs_rpmb)
scsi_device_quiesce(hba->sdev_ufs_rpmb);
}
void ufs_mtk_device_resume(struct ufs_hba *hba)
{
struct scsi_device *scsi_d;
int i;
for (i = 0; i < UFS_UPIU_MAX_GENERAL_LUN; i++) {
scsi_d = scsi_device_lookup(hba->host, 0, 0, i);
if (scsi_d)
scsi_device_resume(scsi_d);
}
scsi_d = scsi_device_lookup(hba->host, 0, 0,
ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_BOOT_WLUN));
if (scsi_d)
scsi_device_resume(scsi_d);
if (hba->sdev_ufs_device)
scsi_device_resume(hba->sdev_ufs_device);
if (hba->sdev_ufs_rpmb)
scsi_device_resume(hba->sdev_ufs_rpmb);
}
/**
* ufs_mtk_ioctl_ffu - perform user ffu
* @hba: per-adapter instance
* @buffer: user space buffer for ffu ioctl data
* @return: 0 for success negative error code otherwise
*
* Expected/Submitted buffer structure is struct ufs_ioctl_ffu_data.
* It will read the buffer information of new firmware.
*/
int ufs_mtk_ioctl_ffu(struct scsi_device *dev, void __user *buf_user)
{
struct ufs_hba *hba = shost_priv(dev->host);
struct ufs_ioctl_ffu_data *idata = NULL;
struct ufs_ioctl_ffu_data *idata_user = NULL;
int err = 0;
u32 attr = 0;
idata = kzalloc(sizeof(struct ufs_ioctl_ffu_data), GFP_KERNEL);
if (!idata) {
err = -ENOMEM;
goto out;
}
idata_user = kzalloc(sizeof(struct ufs_ioctl_ffu_data), GFP_KERNEL);
if (!idata_user) {
kfree(idata);
err = -ENOMEM;
goto out;
}
/* extract struct idata from user buffer */
err = copy_from_user(idata_user, buf_user,
sizeof(struct ufs_ioctl_ffu_data));
if (err) {
dev_err(hba->dev,
"%s: failed copying buffer from user, err %d\n",
__func__, err);
goto out_release_mem;
}
memcpy(idata, idata_user, sizeof(struct ufs_ioctl_ffu_data));
/* extract firmware from user buffer */
if (idata->buf_byte > (u32)UFS_IOCTL_FFU_MAX_FW_SIZE_BYTES) {
dev_err(hba->dev, "%s: idata->buf_byte:0x%x > max 0x%x bytes\n",
__func__, idata->buf_byte,
(u32)UFS_IOCTL_FFU_MAX_FW_SIZE_BYTES);
err = -ENOMEM;
goto out_release_mem;
}
idata->buf_ptr = kzalloc(idata->buf_byte, GFP_KERNEL);
if (!idata->buf_ptr) {
err = -ENOMEM;
goto out_release_mem;
}
if (copy_from_user(idata->buf_ptr,
(void __user *)idata_user->buf_ptr, idata->buf_byte)) {
err = -EFAULT;
goto out_release_idata_buf_ptr;
}
ufs_mtk_device_quiesce(hba);
/* do FFU */
err = ufs_mtk_ffu_send_cmd(dev, idata);
if (err) {
dev_err(hba->dev, "%s: ffu failed, err %d\n", __func__, err);
ufs_mtk_device_resume(hba);
} else {
dev_info(hba->dev, "%s: ffu ok\n", __func__);
}
/*
* Check bDeviceFFUStatus attribute
*
* For reference only since UFS spec. said the status is valid after
* device power cycle.
*/
err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
QUERY_ATTR_IDN_DEVICE_FFU_STATUS, 0, 0, &attr);
if (err) {
dev_err(hba->dev, "%s: query bDeviceFFUStatus failed, err %d\n",
__func__, err);
goto out_release_idata_buf_ptr;
}
if (attr > UFS_FFU_STATUS_OK)
dev_err(hba->dev, "%s: bDeviceFFUStatus shows fail %d (ref only)\n",
__func__, attr);
out_release_idata_buf_ptr:
kfree(idata->buf_ptr);
out_release_mem:
kfree(idata);
kfree(idata_user);
out:
/*
* UFS might not be used normally after FFU.
* Just reboot system (including device) to avoid following
* false alarm. For example, I/O errors.
*/
emergency_restart();
return err;
}
/**
* ufs_mtk_ioctl_query - perform user read queries
* @hba: per-adapter instance
* @lun: used for lun specific queries
* @buffer: user space buffer for reading and submitting query data and params
* @return: 0 for success negative error code otherwise
*
* Expected/Submitted buffer structure is struct ufs_ioctl_query_data.
* It will read the opcode, idn and buf_length parameters, and, put the
* response in the buffer field while updating the used size in buf_length.
*/
#if defined(CONFIG_UFSFEATURE)
int ufsf_query_ioctl(struct ufsf_feature *ufsf, unsigned int lun,
void __user *buffer,
struct ufs_ioctl_query_data_hpb *ioctl_data, u8 selector);
#endif
int ufs_mtk_ioctl_query(struct ufs_hba *hba, u8 lun, void __user *buf_user)
{
struct ufs_ioctl_query_data *idata;
void __user *user_buf_ptr;
int err = 0;
int length = 0;
void *data_ptr;
bool flag;
u32 att = 0;
u8 *desc = NULL;
u8 read_desc, read_attr, write_attr, read_flag;
#if defined(CONFIG_UFSFEATURE)
u8 selector;
#endif
idata = kzalloc(sizeof(struct ufs_ioctl_query_data), GFP_KERNEL);
if (!idata) {
err = -ENOMEM;
goto out;
}
/* extract params from user buffer */
err = copy_from_user(idata, buf_user,
sizeof(struct ufs_ioctl_query_data));
if (err) {
dev_err(hba->dev,
"%s: failed copying buffer from user, err %d\n",
__func__, err);
goto out_release_mem;
}
#if defined(CONFIG_UFSFEATURE)
if (hba->card->wmanufacturerid == UFS_VENDOR_SAMSUNG ||
hba->card->wmanufacturerid == UFS_VENDOR_MICRON)
selector = UFSFEATURE_SELECTOR;
else
selector = 0;
if (ufsf_check_query(idata->opcode)) {
err = ufsf_query_ioctl(&hba->ufsf, lun, buf_user,
(struct ufs_ioctl_query_data_hpb *)idata,
selector);
goto out_release_mem;
}
#endif
user_buf_ptr = idata->buf_ptr;
/*
* save idata->idn to specific local variable to avoid
* confusion by comapring idata->idn with different enums below.
*/
switch (idata->opcode) {
case UPIU_QUERY_OPCODE_READ_DESC:
read_desc = idata->idn;
break;
case UPIU_QUERY_OPCODE_READ_ATTR:
read_attr = idata->idn;
break;
case UPIU_QUERY_OPCODE_WRITE_ATTR:
write_attr = idata->idn;
break;
case UPIU_QUERY_OPCODE_READ_FLAG:
read_flag = idata->idn;
break;
default:
goto out_einval;
}
/* verify legal parameters & send query */
switch (idata->opcode) {
case UPIU_QUERY_OPCODE_READ_DESC:
switch (read_desc) {
case QUERY_DESC_IDN_DEVICE:
case QUERY_DESC_IDN_STRING:
#ifdef OPLUS_FEATURE_STORAGE_TOOL
//jason.wu@BSP.Storage.UFS, 2020/8/14 used for memory monitor.
case QUERY_DESC_IDN_HEALTH:
#endif
break;
default:
goto out_einval;
}
length = min_t(int, QUERY_DESC_MAX_SIZE,
idata->buf_byte);
desc = kzalloc(length, GFP_KERNEL);
if (!desc) {
err = -ENOMEM;
goto out_release_mem;
}
err = ufshcd_query_descriptor_retry(hba, idata->opcode,
read_desc, idata->idx, 0, desc, &length);
break;
case UPIU_QUERY_OPCODE_READ_ATTR:
switch (read_attr) {
case QUERY_ATTR_IDN_BOOT_LUN_EN:
break;
case QUERY_ATTR_IDN_DEVICE_FFU_STATUS:
break;
default:
goto out_einval;
}
err = ufshcd_query_attr(hba, idata->opcode,
read_attr, idata->idx, 0, &att);
break;
case UPIU_QUERY_OPCODE_WRITE_ATTR:
switch (write_attr) {
case QUERY_ATTR_IDN_BOOT_LUN_EN:
break;
default:
goto out_einval;
}
length = min_t(int, sizeof(int), idata->buf_byte);
if (copy_from_user(&att, (void __user *)(unsigned long)
idata->buf_ptr, length)) {
err = -EFAULT;
goto out_release_mem;
}
err = ufshcd_query_attr(hba, idata->opcode,
write_attr, idata->idx, 0, &att);
break;
case UPIU_QUERY_OPCODE_READ_FLAG:
switch (read_flag) {
case QUERY_FLAG_IDN_PERMANENTLY_DISABLE_FW_UPDATE:
break;
default:
goto out_einval;
}
err = ufshcd_query_flag(hba, idata->opcode,
read_flag, &flag);
break;
default:
goto out_einval;
}
if (err) {
dev_err(hba->dev, "%s: query for idn %d failed\n", __func__,
idata->idn);
goto out_release_mem;
}
/*
* copy response data
* As we might end up reading less data then what is specified in
* "ioct_data->buf_byte". So we are updating "ioct_data->
* buf_byte" to what exactly we have read.
*/
switch (idata->opcode) {
case UPIU_QUERY_OPCODE_READ_DESC:
idata->buf_byte = min_t(int, idata->buf_byte, length);
data_ptr = desc;
break;
case UPIU_QUERY_OPCODE_READ_ATTR:
idata->buf_byte = sizeof(att);
data_ptr = &att;
break;
case UPIU_QUERY_OPCODE_READ_FLAG:
idata->buf_byte = 1;
data_ptr = &flag;
break;
case UPIU_QUERY_OPCODE_WRITE_ATTR:
/* write attribute does not require coping response data */
goto out_release_mem;
default:
goto out_einval;
}
/* copy to user */
err = copy_to_user(buf_user, idata,
sizeof(struct ufs_ioctl_query_data));
if (err)
dev_err(hba->dev, "%s: failed copying back to user.\n",
__func__);
err = copy_to_user(user_buf_ptr, data_ptr, idata->buf_byte);
if (err)
dev_err(hba->dev, "%s: err %d copying back to user.\n",
__func__, err);
goto out_release_mem;
out_einval:
dev_err(hba->dev,
"%s: illegal ufs query ioctl data, opcode 0x%x, idn 0x%x\n",
__func__, idata->opcode, (unsigned int)idata->idn);
err = -EINVAL;
out_release_mem:
kfree(idata);
kfree(desc);
out:
return err;
}
/**
* ufs_mtk_ioctl_rpmb - perform user rpmb read/write request
* @hba: per-adapter instance
* @buf_user: user space buffer for ioctl rpmb_cmd data
* @return: 0 for success negative error code otherwise
*
* Expected/Submitted buffer structure is struct rpmb_cmd.
* It will read/write data to rpmb
*/
int ufs_mtk_ioctl_rpmb(struct ufs_hba *hba, void __user *buf_user)
{
struct rpmb_cmd cmd[3];
struct rpmb_frame *frame_buf = NULL;
struct rpmb_frame *frames = NULL;
int size = 0;
int nframes = 0;
unsigned long flags;
struct scsi_device *sdev;
int ret;
int i;
/* Get scsi device */
spin_lock_irqsave(hba->host->host_lock, flags);
sdev = hba->sdev_ufs_rpmb;
if (sdev) {
ret = scsi_device_get(sdev);
if (!ret && !scsi_device_online(sdev)) {
ret = -ENODEV;
scsi_device_put(sdev);
}
} else {
ret = -ENODEV;
}
spin_unlock_irqrestore(hba->host->host_lock, flags);
if (ret) {
dev_info(hba->dev,
"%s: failed get rpmb device, ret %d\n",
__func__, ret);
goto out;
}
/* Get cmd params from user buffer */
ret = copy_from_user((void *) cmd,
buf_user, sizeof(struct rpmb_cmd) * 3);
if (ret) {
dev_info(hba->dev,
"%s: failed copying cmd buffer from user, ret %d\n",
__func__, ret);
goto out_put;
}
/* Check number of rpmb frames */
for (i = 0; i < 3; i++) {
ret = (int)rpmb_get_rw_size(ufs_mtk_rpmb_get_raw_dev());
if (cmd[i].nframes > ret) {
dev_info(hba->dev,
"%s: number of rpmb frames %u exceeds limit %d\n",
__func__, cmd[i].nframes, ret);
ret = -EINVAL;
goto out_put;
}
}
/* Prepaer frame buffer */
for (i = 0; i < 3; i++)
nframes += cmd[i].nframes;
frame_buf = kcalloc(nframes, sizeof(struct rpmb_frame), GFP_KERNEL);
if (!frame_buf) {
ret = -ENOMEM;
goto out_put;
}
frames = frame_buf;
/*
* Send all command one by one.
* Use rpmb lock to prevent other rpmb read/write threads cut in line.
* Use mutex not spin lock because in/out function might sleep.
*/
mutex_lock(&hba->rpmb_lock);
for (i = 0; i < 3; i++) {
if (cmd[i].nframes == 0)
break;
/* Get frames from user buffer */
size = sizeof(struct rpmb_frame) * cmd[i].nframes;
ret = copy_from_user((void *) frames, cmd[i].frames, size);
if (ret) {
dev_err(hba->dev,
"%s: failed from user, ret %d\n",
__func__, ret);
break;
}
/* Do rpmb in out */
if (cmd[i].flags & RPMB_F_WRITE) {
ret = ufshcd_rpmb_security_out(sdev, frames,
cmd[i].nframes);
if (ret) {
dev_err(hba->dev,
"%s: failed rpmb out, err %d\n",
__func__, ret);
break;
}
} else {
ret = ufshcd_rpmb_security_in(sdev, frames,
cmd[i].nframes);
if (ret) {
dev_err(hba->dev,
"%s: failed rpmb in, err %d\n",
__func__, ret);
break;
}
/* Copy frames to user buffer */
ret = copy_to_user((void *) cmd[i].frames,
frames, size);
if (ret) {
dev_err(hba->dev,
"%s: failed to user, err %d\n",
__func__, ret);
break;
}
}
frames += cmd[i].nframes;
}
mutex_unlock(&hba->rpmb_lock);
kfree(frame_buf);
out_put:
scsi_device_put(sdev);
out:
return ret;
}
static int ufs_mtk_scsi_dev_cfg(struct scsi_device *sdev,
enum ufs_scsi_dev_cfg op)
{
if (op == UFS_SCSI_DEV_SLAVE_CONFIGURE) {
if (ufs_mtk_rpm_enabled) {
sdev->use_rpm_auto = 1;
sdev->autosuspend_delay = ufs_mtk_rpm_autosuspend_delay;
} else {
sdev->use_rpm_auto = 0;
sdev->autosuspend_delay = -1;
}
}
return 0;
}
#if !defined(HIE_CHANGE_KEY_IN_NORMAL_WORLD)
enum bc_flags_bits {
__BC_CRYPT, /* marks the request needs crypt */
__BC_IV_PAGE_IDX, /* use page index as iv. */
__BC_IV_CTX, /* use the iv saved in crypt context */
__BC_AES_128_XTS, /* crypt algorithms */
__BC_AES_192_XTS,
__BC_AES_256_XTS,
__BC_AES_128_CBC,
__BC_AES_256_CBC,
__BC_AES_128_ECB,
__BC_AES_256_ECB,
};
#define BC_CRYPT (1UL << __BC_CRYPT)
#define BC_IV_PAGE_IDX (1UL << __BC_IV_PAGE_IDX)
#define BC_IV_CTX (1UL << __BC_IV_CTX)
#define BC_AES_128_XTS (1UL << __BC_AES_128_XTS)
#define BC_AES_192_XTS (1UL << __BC_AES_192_XTS)
#define BC_AES_256_XTS (1UL << __BC_AES_256_XTS)
#define BC_AES_128_CBC (1UL << __BC_AES_128_CBC)
#define BC_AES_256_CBC (1UL << __BC_AES_256_CBC)
#define BC_AES_128_ECB (1UL << __BC_AES_128_ECB)
#define BC_AES_256_ECB (1UL << __BC_AES_256_ECB)
static u8 ufshcd_crypto_gie_get_mode(u8 cap_idx)
{
if (cap_idx == 0)
return BC_AES_128_XTS;
else if (cap_idx == 1)
return BC_AES_256_XTS;
else
return -1;
}
static int ufs_mtk_program_key(struct ufs_hba *hba,
const union ufs_crypto_cfg_entry *cfg, int slot)
{
int i;
unsigned long flags;
u32 gie_para;
u8 mode;
mode = ufshcd_crypto_gie_get_mode(cfg->crypto_cap_idx);
gie_para = ((slot & 0xFF) << UFS_HIE_PARAM_OFS_CFG_ID) |
((mode & 0xFF) << UFS_HIE_PARAM_OFS_MODE) |
((0x40 & 0xFF) << UFS_HIE_PARAM_OFS_KEY_TOTAL_BYTE);
/* disable encryption */
if (cfg->config_enable == 0)
gie_para |= 0x01;
spin_lock_irqsave(hba->host->host_lock, flags);
/* init ufs crypto IP for program key by first 8B */
mt_secure_call(MTK_SIP_KERNEL_CRYPTO_HIE_CFG_REQUEST,
gie_para,
le32_to_cpu(cfg->reg_val[0]),
le32_to_cpu(cfg->reg_val[1]), 0);
/* program remaining key */
for (i = 2; i < 16; i += 3) {
mt_secure_call(MTK_SIP_KERNEL_CRYPTO_HIE_CFG_REQUEST,
le32_to_cpu(cfg->reg_val[i]),
le32_to_cpu(cfg->reg_val[i + 1]),
le32_to_cpu(cfg->reg_val[i + 2]), 0);
}
spin_unlock_irqrestore(hba->host->host_lock, flags);
return 0;
}
#endif
void ufs_mtk_runtime_pm_init(struct scsi_device *sdev)
{
/*
* If runtime PM is enabled for UFS device, use auto-suspend mechanism
* if assigned.
*
* This is only for those SCSI devices which runtime PM is not managed
* by block layer. Thus autosuspend of this device is not configured by
* sd_probe_async.
*/
if (ufs_mtk_rpm_enabled && sdev->autosuspend_delay >= 0) {
pm_runtime_set_autosuspend_delay(&sdev->sdev_gendev,
sdev->autosuspend_delay);
pm_runtime_use_autosuspend(&sdev->sdev_gendev);
}
}
static void ufs_mtk_device_reset(struct ufs_hba *hba)
{
(void)ufs_mtk_pltfrm_ufs_device_reset(hba);
#ifdef CONFIG_MTK_UFS_LBA_CRC16_CHECK
/* Clear di memory to avoid false alarm */
ufs_mtk_di_reset(hba);
#endif
}
static void ufs_mtk_auto_hibern8(struct ufs_hba *hba, bool enable)
{
/* if auto-hibern8 is not enabled by device tree, return */
if (!hba->ahit)
return;
dev_dbg(hba->dev, "ah8: %d, ahit: 0x%x\n", enable, hba->ahit);
/* if already enabled or disabled, return */
if (!(ufs_mtk_auto_hibern8_enabled ^ enable))
return;
if (enable) {
ufshcd_writel(hba, hba->ahit, REG_AUTO_HIBERNATE_IDLE_TIMER);
ufs_mtk_auto_hibern8_enabled = true;
} else {
/* disable auto-hibern8 */
ufshcd_writel(hba, 0, REG_AUTO_HIBERNATE_IDLE_TIMER);
ufs_mtk_auto_hibern8_enabled = false;
}
}
int ufs_mtk_auto_hiber8_quirk_handler(struct ufs_hba *hba, bool enable)
{
/*
* do not toggle ah8 during suspend/resume callback
* since ah8 policy is always fixed
* as below,
*
* 1. suspend: ah8 is disabled before suspend flow starts.
* 2. resume: ah8 is enabled after resume flow is finished.
*/
if (hba->quirks & UFSHCD_QUIRK_UFS_HCI_DISABLE_AH8_BEFORE_RDB &&
!hba->outstanding_reqs &&
!hba->outstanding_tasks &&
!hba->pm_op_in_progress) {
ufshcd_vops_auto_hibern8(hba, enable);
}
return 0;
}
int ufs_mtk_wait_link_state(struct ufs_hba *hba, u32 *state,
unsigned long retry_ms)
{
u64 timeout, time_checked;
u32 val;
timeout = sched_clock() + retry_ms * 1000000UL;
do {
time_checked = sched_clock();
ufshcd_writel(hba, 0x20, REG_UFS_MTK_DEBUG_SEL);
val = ufshcd_readl(hba, REG_UFS_MTK_PROBE);
val = val >> 28;
if (val == *state)
break;
/* sleep for max. 200us */
usleep_range(100, 200);
} while (time_checked < timeout);
if (val == *state)
return 0;
*state = val;
return -ETIMEDOUT;
}
int ufs_mtk_generic_read_dme_no_check(u32 uic_cmd, u16 mib_attribute,
u16 gen_select_index, u32 *value, unsigned long retry_ms)
{
struct ufs_hba *hba = ufs_mtk_hba;
struct uic_command ucmd = {0};
u32 val;
int ret = 0;
unsigned long elapsed_us = 0;
bool reenable_intr = false;
val = ufshcd_readl(hba, REG_CONTROLLER_STATUS);
if (!(val & UIC_COMMAND_READY) ||
hba->active_uic_cmd) {
dev_info(hba->dev,
"uic not rdy, host sta(0x%x), uic cmd(%d)\n",
val,
hba->active_uic_cmd ?
hba->active_uic_cmd->command : -1);
return -EIO;
}
if (ufshcd_readl(hba, REG_INTERRUPT_ENABLE)
& UIC_COMMAND_COMPL) {
ufshcd_disable_intr(hba, UIC_COMMAND_COMPL);
/*
* Make sure UIC command completion interrupt is disabled before
* issuing UIC command.
*/
wmb();
reenable_intr = true;
}
ucmd.command = uic_cmd;
ucmd.argument1 = UIC_ARG_MIB_SEL(
(u32)mib_attribute, (u32)gen_select_index);
ucmd.argument2 = 0;
ucmd.argument3 = 0;
ufshcd_writel(hba, ucmd.argument1, REG_UIC_COMMAND_ARG_1);
ufshcd_writel(hba, ucmd.argument2, REG_UIC_COMMAND_ARG_2);
ufshcd_writel(hba, ucmd.argument3, REG_UIC_COMMAND_ARG_3);
ufs_mtk_dme_cmd_log(hba, &ucmd, UFS_TRACE_UIC_SEND);
ufshcd_writel(hba,
ucmd.command & COMMAND_OPCODE_MASK, REG_UIC_COMMAND);
while ((ufshcd_readl(hba,
REG_INTERRUPT_STATUS) & UIC_COMMAND_COMPL)
!= UIC_COMMAND_COMPL) {
/* busy waiting 1us */
udelay(1);
elapsed_us += 1;
if (elapsed_us > (retry_ms * 1000)) {
ret = -ETIMEDOUT;
goto out;
}
}
ufshcd_writel(hba, UIC_COMMAND_COMPL, REG_INTERRUPT_STATUS);
ufs_mtk_dme_cmd_log(hba, &ucmd, UFS_TRACE_UIC_CMPL_GENERAL);
val = ufshcd_readl(hba, REG_UIC_COMMAND_ARG_2);
if (val & MASK_UIC_COMMAND_RESULT) {
ret = val;
goto out;
}
*value = ufshcd_readl(hba, REG_UIC_COMMAND_ARG_3);
out:
if (reenable_intr)
ufshcd_enable_intr(hba, UIC_COMMAND_COMPL);
return ret;
}
/* Notice: this function must be called in automic context */
/* Because it is not protected by ufs spin_lock or mutex */
/* it access ufs host directly. */
int ufs_mtk_generic_read_dme(u32 uic_cmd, u16 mib_attribute,
u16 gen_select_index, u32 *value, unsigned long retry_ms)
{
if (ufs_mtk_hba->outstanding_reqs || ufs_mtk_hba->outstanding_tasks
|| ufs_mtk_hba->active_uic_cmd ||
ufs_mtk_hba->pm_op_in_progress) {
dev_dbg(ufs_mtk_hba->dev, "req: %lx, task %lx, pm %x\n",
ufs_mtk_hba->outstanding_reqs
, ufs_mtk_hba->outstanding_tasks,
ufs_mtk_hba->pm_op_in_progress);
if (ufs_mtk_hba->active_uic_cmd != NULL)
dev_dbg(ufs_mtk_hba->dev, "uic not null\n");
return -1;
}
return ufs_mtk_generic_read_dme_no_check(uic_cmd, mib_attribute,
gen_select_index, value, retry_ms);
}
/**
* ufs_mtk_deepidle_hibern8_check - callback function for Deepidle & SODI.
* Release all resources: DRAM/26M clk/Main PLL and dsiable 26M ref clk if in H8
*
* @return: 0 for success, negative/postive error code otherwise
*/
int ufs_mtk_deepidle_hibern8_check(void)
{
return ufs_mtk_pltfrm_deepidle_check_h8();
}
/**
* ufs_mtk_deepidle_leave - callback function for leaving Deepidle & SODI.
*/
void ufs_mtk_deepidle_leave(void)
{
ufs_mtk_pltfrm_deepidle_leave();
}
struct rpmb_dev *ufs_mtk_rpmb_get_raw_dev(void)
{
return ufs_mtk_hba->rawdev_ufs_rpmb;
}
#ifdef CONFIG_MTK_UFS_DEBUG
void ufs_mtk_dump_asc_ascq(struct ufs_hba *hba, u8 asc, u8 ascq)
{
dev_info(hba->dev, "Sense Data: ASC=%#04x, ASCQ=%#04x\n", asc, ascq);
if (asc == 0x25) {
if (ascq == 0x00)
dev_err(hba->dev, "Logical unit not supported!\n");
} else if (asc == 0x29) {
if (ascq == 0x00)
dev_err(hba->dev,
"Power on, reset, or bus device reset occupied\n");
}
}
#endif
void ufs_mtk_crypto_cal_dun(u32 alg_id, u64 iv, u32 *dunl, u32 *dunu)
{
/* bitlocker dun use byte address */
if (alg_id == UFS_CRYPTO_ALGO_BITLOCKER_AES_CBC)
iv = iv << 12;
*dunl = iv & 0xffffffff;
*dunu = (iv >> 32) & 0xffffffff;
}
bool ufs_mtk_is_data_write_cmd(char cmd_op)
{
if (cmd_op == WRITE_10 || cmd_op == WRITE_16 || cmd_op == WRITE_6)
return true;
return false;
}
static inline bool ufs_mtk_is_unmap_cmd(char cmd_op)
{
if (cmd_op == UNMAP)
return true;
return false;
}
static bool ufs_mtk_is_data_cmd(char cmd_op)
{
if (cmd_op == WRITE_10 || cmd_op == READ_10 ||
cmd_op == WRITE_16 || cmd_op == READ_16 ||
cmd_op == WRITE_6 || cmd_op == READ_6)
return true;
return false;
}
static struct ufs_cmd_str_struct ufs_cmd_str_tbl[] = {
{"TEST_UNIT_READY", 0x00},
{"REQUEST_SENSE", 0x03},
{"FORMAT_UNIT", 0x04},
{"READ_BLOCK_LIMITS", 0x05},
{"INQUIRY", 0x12},
{"RECOVER_BUFFERED_DATA", 0x14},
{"MODE_SENSE", 0x1a},
{"START_STOP", 0x1b},
{"SEND_DIAGNOSTIC", 0x1d},
{"READ_FORMAT_CAPACITIES", 0x23},
{"READ_CAPACITY", 0x25},
{"READ_10", 0x28},
{"WRITE_10", 0x2a},
{"PRE_FETCH", 0x34},
{"SYNCHRONIZE_CACHE", 0x35},
{"WRITE_BUFFER", 0x3b},
{"READ_BUFFER", 0x3c},
{"UNMAP", 0x42},
{"MODE_SELECT_10", 0x55},
{"MODE_SENSE_10", 0x5a},
{"REPORT_LUNS", 0xa0},
{"READ_CAPACITY_16", 0x9e},
{"SECURITY_PROTOCOL_IN", 0xa2},
{"MAINTENANCE_IN", 0xa3},
{"MAINTENANCE_OUT", 0xa4},
{"SECURITY_PROTOCOL_OUT", 0xb5},
{"UNKNOWN", 0xFF}
};
static unsigned int ufs_mtk_get_cmd_str_idx(char cmd)
{
unsigned int i;
for (i = 0; ufs_cmd_str_tbl[i].cmd != 0xFF; i++) {
if (ufs_cmd_str_tbl[i].cmd == cmd)
return i;
}
return i;
}
void ufs_mtk_dbg_dump_scsi_cmd(struct ufs_hba *hba,
struct scsi_cmnd *cmd, u32 flag)
{
#ifdef CONFIG_MTK_UFS_DEBUG
u32 lba = 0;
u32 blk_cnt;
u32 fua, flush;
char str[32];
strncpy(str,
ufs_cmd_str_tbl[ufs_mtk_get_cmd_str_idx(cmd->cmnd[0])].str,
32 - 1);
if (ufs_mtk_is_data_cmd(cmd->cmnd[0])) {
lba = cmd->cmnd[5] | (cmd->cmnd[4] << 8) |
(cmd->cmnd[3] << 16) | (cmd->cmnd[2] << 24);
blk_cnt = cmd->cmnd[8] | (cmd->cmnd[7] << 8);
fua = (cmd->cmnd[1] & 0x8) ? 1 : 0;
flush = (cmd->request->cmd_flags & REQ_FUA) ? 1 : 0;
#ifdef CONFIG_MTK_HW_FDE
if (cmd->request->bio && cmd->request->bio->bi_hw_fde) {
dev_dbg(hba->dev,
"QCMD(C),L:%x,T:%d,0x%x,%s,LBA:%d,BCNT:%d,FUA:%d,FLUSH:%d\n",
ufshcd_scsi_to_upiu_lun(cmd->device->lun),
cmd->request->tag, cmd->cmnd[0],
str,
lba, blk_cnt, fua, flush);
} else
#endif
{
dev_dbg(hba->dev,
"QCMD,L:%x,T:%d,0x%x,%s,LBA:%d,BCNT:%d,FUA:%d,FLUSH:%d\n",
ufshcd_scsi_to_upiu_lun(cmd->device->lun),
cmd->request->tag, cmd->cmnd[0],
str,
lba, blk_cnt, fua, flush);
}
} else {
dev_dbg(hba->dev, "QCMD,L:%x,T:%d,0x%x,%s\n",
ufshcd_scsi_to_upiu_lun(cmd->device->lun),
cmd->request->tag, cmd->cmnd[0],
str);
}
#endif
}
#ifdef SPM_READY
void ufs_mtk_res_ctrl(struct ufs_hba *hba, unsigned int op)
{
int res_type = 0;
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
if (!host->spm_sw_mode)
return;
if (!hba->outstanding_tasks && !hba->outstanding_reqs) {
if (op == UFS_RESCTL_CMD_SEND) {
if (hba->active_uic_cmd)
res_type = 1;
else
res_type = 2;
} else if (op == UFS_RESCTL_CMD_COMP)
res_type = 1;
}
if (res_type == 1)
spm_resource_req(SPM_RESOURCE_USER_UFS,
SPM_RESOURCE_MAINPLL | SPM_RESOURCE_CK_26M);
else if (res_type == 2)
spm_resource_req(SPM_RESOURCE_USER_UFS, SPM_RESOURCE_ALL);
}
#else
#define ufs_mtk_res_ctrl NULL
#endif
static void ufs_mtk_abort_handler(struct ufs_hba *hba, int tag,
char *file, int line)
{
#ifdef CONFIG_MTK_AEE_FEATURE
u8 cmd = 0;
dev_info(hba->dev, "%s: tag: %d\n", __func__, tag);
if (tag == -1) {
aee_kernel_warning_api(file, line, DB_OPT_FS_IO_LOG,
"[UFS] Host and Device Reset Event",
"Host and Device Reset, %s:%d",
file, line);
} else {
if (hba->lrb[tag].cmd)
cmd = hba->lrb[tag].cmd->cmnd[0];
aee_kernel_warning_api(file, line, DB_OPT_FS_IO_LOG,
"[UFS] Command Timeout",
"Command 0x%x timeout, %s:%d",
cmd, file, line);
}
#endif
}
/**
* struct ufs_hba_mtk_vops - UFS MTK specific variant operations
*
* The variant operations configure the necessary controller and PHY
* handshake during initialization.
*/
static struct ufs_hba_variant_ops ufs_hba_mtk_vops = {
"mediatek.ufshci", /* name */
ufs_mtk_init, /* init */
ufs_mtk_exit, /* exit */
NULL, /* get_ufs_hci_version */
NULL, /* clk_scale_notify */
ufs_mtk_setup_clocks, /* setup_clocks */
NULL, /* setup_regulators */
ufs_mtk_hce_enable_notify, /* hce_enable_notify */
ufs_mtk_link_startup_notify, /* link_startup_notify */
ufs_mtk_pwr_change_notify, /* pwr_change_notify */
NULL, /* setup_xfer_req */
NULL, /* setup_task_mgmt */
NULL, /* hibern8_notify */
NULL, /* apply_dev_quirks */
ufs_mtk_suspend, /* suspend */
ufs_mtk_resume, /* resume */
ufs_mtk_dbg_register_dump, /* dbg_register_dump */
NULL, /* phy_initialization */
ufs_mtk_device_reset, /* device_reset */
ufs_mtk_auto_hibern8, /* auto_hibern8 */
ufs_mtk_res_ctrl, /* res_ctrl */
ufs_mtk_pltfrm_deepidle_lock, /* deepidle_lock */
ufs_mtk_scsi_dev_cfg, /* scsi_dev_cfg */
#if defined(HIE_CHANGE_KEY_IN_NORMAL_WORLD)
NULL, /* program_key */
#else
ufs_mtk_program_key, /* program_key */
#endif
ufs_mtk_abort_handler /* abort_handler */
};
/**
* ufs_mtk_probe - probe routine of the driver
* @pdev: pointer to Platform device handle
*
* Return zero for success and non-zero for failure
*/
static int ufs_mtk_probe(struct platform_device *pdev)
{
int err;
struct ufs_hba *hba;
struct ufs_mtk_host *host;
struct device *dev = &pdev->dev;
int boot_type;
void __iomem *ufs_base;
/*
* Disable xoufs_req_s in ufshci to deactivate
* 1. UFS_SRCCLKENA
* 2. UFS_INFRA_REQ
* 3. UFS_VRF18_REQ
* Which block 26M off
*/
ufs_base = of_iomap(pdev->dev.of_node, 0);
if (!ufs_base) {
dev_err(dev, "ufs iomap failed\n");
return -ENODEV;
};
#ifndef UFS_REF_CLK_CTRL_BY_UFSHCI
/* Old chip not use this, disable it always */
writel(0, ufs_base + REG_UFS_ADDR_XOUFS_ST);
#endif
/* Add get_boot_type check and return ENODEV if not ufs boot */
boot_type = get_boot_type();
if (boot_type != BOOTDEV_UFS) {
#ifdef UFS_REF_CLK_CTRL_BY_UFSHCI
/* New chip disable it when eMMC boot */
writel(0, ufs_base + REG_UFS_ADDR_XOUFS_ST);
#endif
return -ENODEV;
}
ufs_mtk_biolog_init();
/* perform generic probe */
err = ufshcd_pltfrm_init(pdev, &ufs_hba_mtk_vops);
if (err) {
dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err);
goto out;
}
out:
return err;
}
/**
* ufs_mtk_remove - set driver_data of the device to NULL
* @pdev: pointer to platform device handle
*
* Always return 0
*/
static int ufs_mtk_remove(struct platform_device *pdev)
{
struct ufs_hba *hba = platform_get_drvdata(pdev);
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
pm_runtime_get_sync(&(pdev)->dev);
ufshcd_remove(hba);
ufs_mtk_biolog_exit();
return 0;
}
const struct of_device_id ufs_mtk_of_match[] = {
{ .compatible = "mediatek,ufshci"},
{},
};
static const struct dev_pm_ops ufs_mtk_pm_ops = {
.suspend = ufshcd_pltfrm_suspend,
.resume = ufshcd_pltfrm_resume,
.runtime_suspend = ufshcd_pltfrm_runtime_suspend,
.runtime_resume = ufshcd_pltfrm_runtime_resume,
.runtime_idle = ufshcd_pltfrm_runtime_idle,
};
static struct platform_driver ufs_mtk_pltform = {
.probe = ufs_mtk_probe,
.remove = ufs_mtk_remove,
.shutdown = ufshcd_pltfrm_shutdown,
.driver = {
.name = "ufshcd",
.owner = THIS_MODULE,
.pm = &ufs_mtk_pm_ops,
.of_match_table = ufs_mtk_of_match,
},
};
module_platform_driver(ufs_mtk_pltform);