summaryrefslogtreecommitdiff
path: root/drivers/scsi/ufs/ufs-qcom-ice.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/scsi/ufs/ufs-qcom-ice.c')
-rw-r--r--drivers/scsi/ufs/ufs-qcom-ice.c655
1 files changed, 655 insertions, 0 deletions
diff --git a/drivers/scsi/ufs/ufs-qcom-ice.c b/drivers/scsi/ufs/ufs-qcom-ice.c
new file mode 100644
index 000000000000..1ba4f2bafba3
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-qcom-ice.c
@@ -0,0 +1,655 @@
+/*
+ * Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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 the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/blkdev.h>
+#include <crypto/ice.h>
+
+#include "ufs-qcom-ice.h"
+#include "ufs-qcom-debugfs.h"
+#include "ufshcd.h"
+
+#define UFS_QCOM_CRYPTO_LABEL "ufs-qcom-crypto"
+/* Timeout waiting for ICE initialization, that requires TZ access */
+#define UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS 500
+
+#define UFS_QCOM_ICE_DEFAULT_DBG_PRINT_EN 0
+
+static void ufs_qcom_ice_dump_regs(struct ufs_qcom_host *qcom_host, int offset,
+ int len, char *prefix)
+{
+ print_hex_dump(KERN_ERR, prefix,
+ len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE,
+ 16, 4, qcom_host->hba->mmio_base + offset, len * 4,
+ false);
+}
+
+void ufs_qcom_ice_print_regs(struct ufs_qcom_host *qcom_host)
+{
+ int i;
+
+ if (!(qcom_host->dbg_print_en & UFS_QCOM_DBG_PRINT_ICE_REGS_EN))
+ return;
+
+ ufs_qcom_ice_dump_regs(qcom_host, REG_UFS_QCOM_ICE_CFG, 1,
+ "REG_UFS_QCOM_ICE_CFG ");
+ for (i = 0; i < NUM_QCOM_ICE_CTRL_INFO_n_REGS; i++) {
+ pr_err("REG_UFS_QCOM_ICE_CTRL_INFO_1_%d = 0x%08X\n", i,
+ ufshcd_readl(qcom_host->hba,
+ (REG_UFS_QCOM_ICE_CTRL_INFO_1_n + 8 * i)));
+
+ pr_err("REG_UFS_QCOM_ICE_CTRL_INFO_2_%d = 0x%08X\n", i,
+ ufshcd_readl(qcom_host->hba,
+ (REG_UFS_QCOM_ICE_CTRL_INFO_2_n + 8 * i)));
+ }
+
+ if (qcom_host->ice.pdev && qcom_host->ice.vops &&
+ qcom_host->ice.vops->debug)
+ qcom_host->ice.vops->debug(qcom_host->ice.pdev);
+}
+
+static void ufs_qcom_ice_error_cb(void *host_ctrl, u32 error)
+{
+ struct ufs_qcom_host *qcom_host = (struct ufs_qcom_host *)host_ctrl;
+
+ dev_err(qcom_host->hba->dev, "%s: Error in ice operation 0x%x",
+ __func__, error);
+
+ if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_ACTIVE)
+ qcom_host->ice.state = UFS_QCOM_ICE_STATE_DISABLED;
+}
+
+static struct platform_device *ufs_qcom_ice_get_pdevice(struct device *ufs_dev)
+{
+ struct device_node *node;
+ struct platform_device *ice_pdev = NULL;
+
+ node = of_parse_phandle(ufs_dev->of_node, UFS_QCOM_CRYPTO_LABEL, 0);
+
+ if (!node) {
+ dev_err(ufs_dev, "%s: ufs-qcom-crypto property not specified\n",
+ __func__);
+ goto out;
+ }
+
+ ice_pdev = qcom_ice_get_pdevice(node);
+out:
+ return ice_pdev;
+}
+
+static
+struct qcom_ice_variant_ops *ufs_qcom_ice_get_vops(struct device *ufs_dev)
+{
+ struct qcom_ice_variant_ops *ice_vops = NULL;
+ struct device_node *node;
+
+ node = of_parse_phandle(ufs_dev->of_node, UFS_QCOM_CRYPTO_LABEL, 0);
+
+ if (!node) {
+ dev_err(ufs_dev, "%s: ufs-qcom-crypto property not specified\n",
+ __func__);
+ goto out;
+ }
+
+ ice_vops = qcom_ice_get_variant_ops(node);
+
+ if (!ice_vops)
+ dev_err(ufs_dev, "%s: invalid ice_vops\n", __func__);
+
+ of_node_put(node);
+out:
+ return ice_vops;
+}
+
+/**
+ * ufs_qcom_ice_get_dev() - sets pointers to ICE data structs in UFS QCom host
+ * @qcom_host: Pointer to a UFS QCom internal host structure.
+ *
+ * Sets ICE platform device pointer and ICE vops structure
+ * corresponding to the current UFS device.
+ *
+ * Return: -EINVAL in-case of invalid input parameters:
+ * qcom_host, qcom_host->hba or qcom_host->hba->dev
+ * -ENODEV in-case ICE device is not required
+ * -EPROBE_DEFER in-case ICE is required and hasn't been probed yet
+ * 0 otherwise
+ */
+int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host)
+{
+ struct device *ufs_dev;
+ int err = 0;
+
+ if (!qcom_host || !qcom_host->hba || !qcom_host->hba->dev) {
+ pr_err("%s: invalid qcom_host %p or qcom_host->hba or qcom_host->hba->dev\n",
+ __func__, qcom_host);
+ err = -EINVAL;
+ goto out;
+ }
+
+ ufs_dev = qcom_host->hba->dev;
+
+ qcom_host->ice.vops = ufs_qcom_ice_get_vops(ufs_dev);
+ qcom_host->ice.pdev = ufs_qcom_ice_get_pdevice(ufs_dev);
+
+ if (qcom_host->ice.pdev == ERR_PTR(-EPROBE_DEFER)) {
+ dev_err(ufs_dev, "%s: ICE device not probed yet\n",
+ __func__);
+ qcom_host->ice.pdev = NULL;
+ qcom_host->ice.vops = NULL;
+ err = -EPROBE_DEFER;
+ goto out;
+ }
+
+ if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
+ dev_err(ufs_dev, "%s: invalid platform device %p or vops %p\n",
+ __func__, qcom_host->ice.pdev, qcom_host->ice.vops);
+ qcom_host->ice.pdev = NULL;
+ qcom_host->ice.vops = NULL;
+ err = -ENODEV;
+ goto out;
+ }
+
+ qcom_host->ice.state = UFS_QCOM_ICE_STATE_DISABLED;
+
+out:
+ return err;
+}
+
+static void ufs_qcom_ice_cfg_work(struct work_struct *work)
+{
+ struct ice_data_setting ice_set;
+ struct ufs_qcom_host *qcom_host =
+ container_of(work, struct ufs_qcom_host, ice_cfg_work);
+
+ if (!qcom_host->ice.vops->config_start || !qcom_host->req_pending)
+ return;
+
+ /*
+ * config_start is called again as previous attempt returned -EAGAIN,
+ * this call shall now take care of the necessary key setup.
+ * 'ice_set' will not actually be used, instead the next call to
+ * config_start() for this request, in the normal call flow, will
+ * succeed as the key has now been setup.
+ */
+ qcom_host->ice.vops->config_start(qcom_host->ice.pdev,
+ qcom_host->req_pending, &ice_set, false);
+
+ /*
+ * Resume with requests processing. We assume config_start has been
+ * successful, but even if it wasn't we still must resume in order to
+ * allow for the request to be retried.
+ */
+ ufshcd_scsi_unblock_requests(qcom_host->hba);
+}
+
+/**
+ * ufs_qcom_ice_init() - initializes the ICE-UFS interface and ICE device
+ * @qcom_host: Pointer to a UFS QCom internal host structure.
+ * qcom_host, qcom_host->hba and qcom_host->hba->dev should all
+ * be valid pointers.
+ *
+ * Return: -EINVAL in-case of an error
+ * 0 otherwise
+ */
+int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host)
+{
+ struct device *ufs_dev = qcom_host->hba->dev;
+ int err;
+
+ err = qcom_host->ice.vops->init(qcom_host->ice.pdev,
+ qcom_host,
+ ufs_qcom_ice_error_cb);
+ if (err) {
+ dev_err(ufs_dev, "%s: ice init failed. err = %d\n",
+ __func__, err);
+ goto out;
+ } else {
+ qcom_host->ice.state = UFS_QCOM_ICE_STATE_ACTIVE;
+ }
+
+ qcom_host->dbg_print_en |= UFS_QCOM_ICE_DEFAULT_DBG_PRINT_EN;
+ INIT_WORK(&qcom_host->ice_cfg_work, ufs_qcom_ice_cfg_work);
+
+out:
+ return err;
+}
+
+static inline bool ufs_qcom_is_data_cmd(char cmd_op, bool is_write)
+{
+ if (is_write) {
+ if (cmd_op == WRITE_6 || cmd_op == WRITE_10 ||
+ cmd_op == WRITE_16)
+ return true;
+ } else {
+ if (cmd_op == READ_6 || cmd_op == READ_10 ||
+ cmd_op == READ_16)
+ return true;
+ }
+
+ return false;
+}
+
+int ufs_qcom_ice_req_setup(struct ufs_qcom_host *qcom_host,
+ struct scsi_cmnd *cmd, u8 *cc_index, bool *enable)
+{
+ struct ice_data_setting ice_set;
+ char cmd_op = cmd->cmnd[0];
+ int err;
+
+ if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
+ dev_dbg(qcom_host->hba->dev, "%s: ice device is not enabled\n",
+ __func__);
+ return 0;
+ }
+
+ if (qcom_host->ice.vops->config_start) {
+ memset(&ice_set, 0, sizeof(ice_set));
+ err = qcom_host->ice.vops->config_start(qcom_host->ice.pdev,
+ cmd->request, &ice_set, true);
+ if (err) {
+ /*
+ * config_start() returns -EAGAIN when a key slot is
+ * available but still not configured. As configuration
+ * requires a non-atomic context, this means we should
+ * call the function again from the worker thread to do
+ * the configuration. For this request the error will
+ * propagate so it will be re-queued and until the
+ * configuration is is completed we block further
+ * request processing.
+ */
+ if (err == -EAGAIN) {
+ dev_dbg(qcom_host->hba->dev,
+ "%s: scheduling task for ice setup\n",
+ __func__);
+ qcom_host->req_pending = cmd->request;
+ if (schedule_work(&qcom_host->ice_cfg_work))
+ ufshcd_scsi_block_requests(
+ qcom_host->hba);
+ } else {
+ dev_err(qcom_host->hba->dev,
+ "%s: error in ice_vops->config %d\n",
+ __func__, err);
+ }
+
+ return err;
+ }
+
+ if (ufs_qcom_is_data_cmd(cmd_op, true))
+ *enable = !ice_set.encr_bypass;
+ else if (ufs_qcom_is_data_cmd(cmd_op, false))
+ *enable = !ice_set.decr_bypass;
+
+ if (ice_set.crypto_data.key_index >= 0)
+ *cc_index = (u8)ice_set.crypto_data.key_index;
+ }
+ return 0;
+}
+
+/**
+ * ufs_qcom_ice_cfg_start() - starts configuring UFS's ICE registers
+ * for an ICE transaction
+ * @qcom_host: Pointer to a UFS QCom internal host structure.
+ * qcom_host, qcom_host->hba and qcom_host->hba->dev should all
+ * be valid pointers.
+ * @cmd: Pointer to a valid scsi command. cmd->request should also be
+ * a valid pointer.
+ *
+ * Return: -EINVAL in-case of an error
+ * 0 otherwise
+ */
+int ufs_qcom_ice_cfg_start(struct ufs_qcom_host *qcom_host,
+ struct scsi_cmnd *cmd)
+{
+ struct device *dev = qcom_host->hba->dev;
+ int err = 0;
+ struct ice_data_setting ice_set;
+ unsigned int slot = 0;
+ sector_t lba = 0;
+ unsigned int ctrl_info_val = 0;
+ unsigned int bypass = 0;
+ struct request *req;
+ char cmd_op;
+
+ if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
+ dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
+ goto out;
+ }
+
+ if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
+ dev_err(dev, "%s: ice state (%d) is not active\n",
+ __func__, qcom_host->ice.state);
+ return -EINVAL;
+ }
+
+ if (qcom_host->hw_ver.major == 0x3) {
+ /* nothing to do here for version 0x3, exit silently */
+ return 0;
+ }
+
+ req = cmd->request;
+ if (req->bio)
+ lba = req->bio->bi_iter.bi_sector;
+
+ slot = req->tag;
+ if (slot < 0 || slot > qcom_host->hba->nutrs) {
+ dev_err(dev, "%s: slot (%d) is out of boundaries (0...%d)\n",
+ __func__, slot, qcom_host->hba->nutrs);
+ return -EINVAL;
+ }
+
+ memset(&ice_set, 0, sizeof(ice_set));
+ if (qcom_host->ice.vops->config_start) {
+ err = qcom_host->ice.vops->config_start(qcom_host->ice.pdev,
+ req, &ice_set, true);
+ if (err) {
+ /*
+ * config_start() returns -EAGAIN when a key slot is
+ * available but still not configured. As configuration
+ * requires a non-atomic context, this means we should
+ * call the function again from the worker thread to do
+ * the configuration. For this request the error will
+ * propagate so it will be re-queued and until the
+ * configuration is is completed we block further
+ * request processing.
+ */
+ if (err == -EAGAIN) {
+ qcom_host->req_pending = req;
+ if (schedule_work(&qcom_host->ice_cfg_work))
+ ufshcd_scsi_block_requests(
+ qcom_host->hba);
+ }
+ goto out;
+ }
+ }
+
+ cmd_op = cmd->cmnd[0];
+
+#define UFS_QCOM_DIR_WRITE true
+#define UFS_QCOM_DIR_READ false
+ /* if non data command, bypass shall be enabled */
+ if (!ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_WRITE) &&
+ !ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_READ))
+ bypass = UFS_QCOM_ICE_ENABLE_BYPASS;
+ /* if writing data command */
+ else if (ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_WRITE))
+ bypass = ice_set.encr_bypass ? UFS_QCOM_ICE_ENABLE_BYPASS :
+ UFS_QCOM_ICE_DISABLE_BYPASS;
+ /* if reading data command */
+ else if (ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_READ))
+ bypass = ice_set.decr_bypass ? UFS_QCOM_ICE_ENABLE_BYPASS :
+ UFS_QCOM_ICE_DISABLE_BYPASS;
+
+ /* Configure ICE index */
+ ctrl_info_val =
+ (ice_set.crypto_data.key_index &
+ MASK_UFS_QCOM_ICE_CTRL_INFO_KEY_INDEX)
+ << OFFSET_UFS_QCOM_ICE_CTRL_INFO_KEY_INDEX;
+
+ /* Configure data unit size of transfer request */
+ ctrl_info_val |=
+ (UFS_QCOM_ICE_TR_DATA_UNIT_4_KB &
+ MASK_UFS_QCOM_ICE_CTRL_INFO_CDU)
+ << OFFSET_UFS_QCOM_ICE_CTRL_INFO_CDU;
+
+ /* Configure ICE bypass mode */
+ ctrl_info_val |=
+ (bypass & MASK_UFS_QCOM_ICE_CTRL_INFO_BYPASS)
+ << OFFSET_UFS_QCOM_ICE_CTRL_INFO_BYPASS;
+
+ if (qcom_host->hw_ver.major == 0x1) {
+ ufshcd_writel(qcom_host->hba, lba,
+ (REG_UFS_QCOM_ICE_CTRL_INFO_1_n + 8 * slot));
+
+ ufshcd_writel(qcom_host->hba, ctrl_info_val,
+ (REG_UFS_QCOM_ICE_CTRL_INFO_2_n + 8 * slot));
+ }
+ if (qcom_host->hw_ver.major == 0x2) {
+ ufshcd_writel(qcom_host->hba, (lba & 0xFFFFFFFF),
+ (REG_UFS_QCOM_ICE_CTRL_INFO_1_n + 16 * slot));
+
+ ufshcd_writel(qcom_host->hba, ((lba >> 32) & 0xFFFFFFFF),
+ (REG_UFS_QCOM_ICE_CTRL_INFO_2_n + 16 * slot));
+
+ ufshcd_writel(qcom_host->hba, ctrl_info_val,
+ (REG_UFS_QCOM_ICE_CTRL_INFO_3_n + 16 * slot));
+ }
+
+ /*
+ * Ensure UFS-ICE registers are being configured
+ * before next operation, otherwise UFS Host Controller might
+ * set get errors
+ */
+ mb();
+out:
+ return err;
+}
+
+/**
+ * ufs_qcom_ice_cfg_end() - finishes configuring UFS's ICE registers
+ * for an ICE transaction
+ * @qcom_host: Pointer to a UFS QCom internal host structure.
+ * qcom_host, qcom_host->hba and
+ * qcom_host->hba->dev should all
+ * be valid pointers.
+ * @cmd: Pointer to a valid scsi command. cmd->request should also be
+ * a valid pointer.
+ *
+ * Return: -EINVAL in-case of an error
+ * 0 otherwise
+ */
+int ufs_qcom_ice_cfg_end(struct ufs_qcom_host *qcom_host, struct request *req)
+{
+ int err = 0;
+ struct device *dev = qcom_host->hba->dev;
+
+ if (qcom_host->ice.vops->config_end) {
+ err = qcom_host->ice.vops->config_end(req);
+ if (err) {
+ dev_err(dev, "%s: error in ice_vops->config_end %d\n",
+ __func__, err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * ufs_qcom_ice_reset() - resets UFS-ICE interface and ICE device
+ * @qcom_host: Pointer to a UFS QCom internal host structure.
+ * qcom_host, qcom_host->hba and qcom_host->hba->dev should all
+ * be valid pointers.
+ *
+ * Return: -EINVAL in-case of an error
+ * 0 otherwise
+ */
+int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host)
+{
+ struct device *dev = qcom_host->hba->dev;
+ int err = 0;
+
+ if (!qcom_host->ice.pdev) {
+ dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
+ goto out;
+ }
+
+ if (!qcom_host->ice.vops) {
+ dev_err(dev, "%s: invalid ice_vops\n", __func__);
+ return -EINVAL;
+ }
+
+ if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE)
+ goto out;
+
+ if (qcom_host->ice.vops->reset) {
+ err = qcom_host->ice.vops->reset(qcom_host->ice.pdev);
+ if (err) {
+ dev_err(dev, "%s: ice_vops->reset failed. err %d\n",
+ __func__, err);
+ goto out;
+ }
+ }
+
+ if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
+ dev_err(qcom_host->hba->dev,
+ "%s: error. ice.state (%d) is not in active state\n",
+ __func__, qcom_host->ice.state);
+ err = -EINVAL;
+ }
+
+out:
+ return err;
+}
+
+/**
+ * ufs_qcom_ice_resume() - resumes UFS-ICE interface and ICE device from power
+ * collapse
+ * @qcom_host: Pointer to a UFS QCom internal host structure.
+ * qcom_host, qcom_host->hba and qcom_host->hba->dev should all
+ * be valid pointers.
+ *
+ * Return: -EINVAL in-case of an error
+ * 0 otherwise
+ */
+int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host)
+{
+ struct device *dev = qcom_host->hba->dev;
+ int err = 0;
+
+ if (!qcom_host->ice.pdev) {
+ dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
+ goto out;
+ }
+
+ if (qcom_host->ice.state !=
+ UFS_QCOM_ICE_STATE_SUSPENDED) {
+ goto out;
+ }
+
+ if (!qcom_host->ice.vops) {
+ dev_err(dev, "%s: invalid ice_vops\n", __func__);
+ return -EINVAL;
+ }
+
+ if (qcom_host->ice.vops->resume) {
+ err = qcom_host->ice.vops->resume(qcom_host->ice.pdev);
+ if (err) {
+ dev_err(dev, "%s: ice_vops->resume failed. err %d\n",
+ __func__, err);
+ return err;
+ }
+ }
+ qcom_host->ice.state = UFS_QCOM_ICE_STATE_ACTIVE;
+out:
+ return err;
+}
+
+/**
+ * ufs_qcom_ice_suspend() - suspends UFS-ICE interface and ICE device
+ * @qcom_host: Pointer to a UFS QCom internal host structure.
+ * qcom_host, qcom_host->hba and qcom_host->hba->dev should all
+ * be valid pointers.
+ *
+ * Return: -EINVAL in-case of an error
+ * 0 otherwise
+ */
+int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host)
+{
+ struct device *dev = qcom_host->hba->dev;
+ int err = 0;
+
+ if (!qcom_host->ice.pdev) {
+ dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
+ goto out;
+ }
+
+ if (qcom_host->ice.vops->suspend) {
+ err = qcom_host->ice.vops->suspend(qcom_host->ice.pdev);
+ if (err) {
+ dev_err(qcom_host->hba->dev,
+ "%s: ice_vops->suspend failed. err %d\n",
+ __func__, err);
+ return -EINVAL;
+ }
+ }
+
+ if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_ACTIVE) {
+ qcom_host->ice.state = UFS_QCOM_ICE_STATE_SUSPENDED;
+ } else if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_DISABLED) {
+ dev_err(qcom_host->hba->dev,
+ "%s: ice state is invalid: disabled\n",
+ __func__);
+ err = -EINVAL;
+ }
+
+out:
+ return err;
+}
+
+/**
+ * ufs_qcom_ice_get_status() - returns the status of an ICE transaction
+ * @qcom_host: Pointer to a UFS QCom internal host structure.
+ * qcom_host, qcom_host->hba and qcom_host->hba->dev should all
+ * be valid pointers.
+ * @ice_status: Pointer to a valid output parameter.
+ * < 0 in case of ICE transaction failure.
+ * 0 otherwise.
+ *
+ * Return: -EINVAL in-case of an error
+ * 0 otherwise
+ */
+int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host, int *ice_status)
+{
+ struct device *dev = NULL;
+ int err = 0;
+ int stat = -EINVAL;
+
+ *ice_status = 0;
+
+ dev = qcom_host->hba->dev;
+ if (!dev) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (!qcom_host->ice.pdev) {
+ dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
+ goto out;
+ }
+
+ if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (!qcom_host->ice.vops) {
+ dev_err(dev, "%s: invalid ice_vops\n", __func__);
+ return -EINVAL;
+ }
+
+ if (qcom_host->ice.vops->status) {
+ stat = qcom_host->ice.vops->status(qcom_host->ice.pdev);
+ if (stat < 0) {
+ dev_err(dev, "%s: ice_vops->status failed. stat %d\n",
+ __func__, stat);
+ err = -EINVAL;
+ goto out;
+ }
+
+ *ice_status = stat;
+ }
+
+out:
+ return err;
+}