Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/drivers/ufs/core/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 295 kB image not shown  

Quelle  ufshcd.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Universal Flash Storage Host controller driver Core
 * Copyright (C) 2011-2013 Samsung India Software Operations
 * Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
 *
 * Authors:
 * Santosh Yaraganavi <santosh.sy@samsung.com>
 * Vinayak Holikatti <h.vinayak@samsung.com>
 */


#include <linux/async.h>
#include <linux/devfreq.h>
#include <linux/nls.h>
#include <linux/of.h>
#include <linux/bitfield.h>
#include <linux/blk-pm.h>
#include <linux/blkdev.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/pm_opp.h>
#include <linux/regulator/consumer.h>
#include <linux/sched/clock.h>
#include <linux/iopoll.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_dbg.h>
#include <scsi/scsi_driver.h>
#include <scsi/scsi_eh.h>
#include "ufshcd-priv.h"
#include <ufs/ufs_quirks.h>
#include <ufs/unipro.h>
#include "ufs-sysfs.h"
#include "ufs-debugfs.h"
#include "ufs-fault-injection.h"
#include "ufs_bsg.h"
#include "ufshcd-crypto.h"
#include <linux/unaligned.h>

#define CREATE_TRACE_POINTS
#include "ufs_trace.h"

#define UFSHCD_ENABLE_INTRS (UTP_TRANSFER_REQ_COMPL |\
     UTP_TASK_REQ_COMPL |\
     UFSHCD_ERROR_MASK)

/* UIC command timeout, unit: ms */
enum {
 UIC_CMD_TIMEOUT_DEFAULT = 500,
 UIC_CMD_TIMEOUT_MAX = 5000,
};
/* NOP OUT retries waiting for NOP IN response */
#define NOP_OUT_RETRIES    10
/* Timeout after 50 msecs if NOP OUT hangs without response */
#define NOP_OUT_TIMEOUT    50 /* msecs */

/* Query request retries */
#define QUERY_REQ_RETRIES 3
/* Query request timeout */
enum {
 QUERY_REQ_TIMEOUT_MIN     = 1,
 QUERY_REQ_TIMEOUT_DEFAULT = 1500,
 QUERY_REQ_TIMEOUT_MAX     = 30000
};

/* Advanced RPMB request timeout */
#define ADVANCED_RPMB_REQ_TIMEOUT  3000 /* 3 seconds */

/* Task management command timeout */
#define TM_CMD_TIMEOUT 100 /* msecs */

/* maximum number of retries for a general UIC command  */
#define UFS_UIC_COMMAND_RETRIES 3

/* maximum number of link-startup retries */
#define DME_LINKSTARTUP_RETRIES 3

/* maximum number of reset retries before giving up */
#define MAX_HOST_RESET_RETRIES 5

/* Maximum number of error handler retries before giving up */
#define MAX_ERR_HANDLER_RETRIES 5

/* Expose the flag value from utp_upiu_query.value */
#define MASK_QUERY_UPIU_FLAG_LOC 0xFF

/* Interrupt aggregation default timeout, unit: 40us */
#define INT_AGGR_DEF_TO 0x02

/* default delay of autosuspend: 2000 ms */
#define RPM_AUTOSUSPEND_DELAY_MS 2000

/* Default delay of RPM device flush delayed work */
#define RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS 5000

/* Default value of wait time before gating device ref clock */
#define UFSHCD_REF_CLK_GATING_WAIT_US 0xFF /* microsecs */

/* Polling time to wait for fDeviceInit */
#define FDEVICEINIT_COMPL_TIMEOUT 1500 /* millisecs */

/* Default RTC update every 10 seconds */
#define UFS_RTC_UPDATE_INTERVAL_MS (10 * MSEC_PER_SEC)

/* bMaxNumOfRTT is equal to two after device manufacturing */
#define DEFAULT_MAX_NUM_RTT 2

/* UFSHC 4.0 compliant HC support this mode. */
static bool use_mcq_mode = true;

static bool is_mcq_supported(struct ufs_hba *hba)
{
 return hba->mcq_sup && use_mcq_mode;
}

module_param(use_mcq_mode, bool, 0644);
MODULE_PARM_DESC(use_mcq_mode, "Control MCQ mode for controllers starting from UFSHCI 4.0. 1 - enable MCQ, 0 - disable MCQ. MCQ is enabled by default");

static unsigned int uic_cmd_timeout = UIC_CMD_TIMEOUT_DEFAULT;

static int uic_cmd_timeout_set(const char *val, const struct kernel_param *kp)
{
 return param_set_uint_minmax(val, kp, UIC_CMD_TIMEOUT_DEFAULT,
         UIC_CMD_TIMEOUT_MAX);
}

static const struct kernel_param_ops uic_cmd_timeout_ops = {
 .set = uic_cmd_timeout_set,
 .get = param_get_uint,
};

module_param_cb(uic_cmd_timeout, &uic_cmd_timeout_ops, &uic_cmd_timeout, 0644);
MODULE_PARM_DESC(uic_cmd_timeout,
   "UFS UIC command timeout in milliseconds. Defaults to 500ms. Supported values range from 500ms to 5 seconds inclusively");

static unsigned int dev_cmd_timeout = QUERY_REQ_TIMEOUT_DEFAULT;

static int dev_cmd_timeout_set(const char *val, const struct kernel_param *kp)
{
 return param_set_uint_minmax(val, kp, QUERY_REQ_TIMEOUT_MIN,
         QUERY_REQ_TIMEOUT_MAX);
}

static const struct kernel_param_ops dev_cmd_timeout_ops = {
 .set = dev_cmd_timeout_set,
 .get = param_get_uint,
};

module_param_cb(dev_cmd_timeout, &dev_cmd_timeout_ops, &dev_cmd_timeout, 0644);
MODULE_PARM_DESC(dev_cmd_timeout,
   "UFS Device command timeout in milliseconds. Defaults to 1.5s. Supported values range from 1ms to 30 seconds inclusively");

#define ufshcd_toggle_vreg(_dev, _vreg, _on)    \
 ({                                                              \
  int _ret;                                               \
  if (_on)                                                \
   _ret = ufshcd_enable_vreg(_dev, _vreg);         \
  else                                                    \
   _ret = ufshcd_disable_vreg(_dev, _vreg);        \
  _ret;                                                   \
 })

#define ufshcd_hex_dump(prefix_str, buf, len) do {                       \
 size_t __len = (len);                                            \
 print_hex_dump(KERN_ERR, prefix_str,                             \
         __len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE,\
         16, 4, buf, __len, false);                        \
while (0)

int ufshcd_dump_regs(struct ufs_hba *hba, size_t offset, size_t len,
       const char *prefix)
{
 u32 *regs;
 size_t pos;

 if (offset % 4 != 0 || len % 4 != 0) /* keep readl happy */
  return -EINVAL;

 regs = kzalloc(len, GFP_ATOMIC);
 if (!regs)
  return -ENOMEM;

 for (pos = 0; pos < len; pos += 4) {
  if (offset == 0 &&
      pos >= REG_UIC_ERROR_CODE_PHY_ADAPTER_LAYER &&
      pos <= REG_UIC_ERROR_CODE_DME)
   continue;
  regs[pos / 4] = ufshcd_readl(hba, offset + pos);
 }

 ufshcd_hex_dump(prefix, regs, len);
 kfree(regs);

 return 0;
}
EXPORT_SYMBOL_GPL(ufshcd_dump_regs);

enum {
 UFSHCD_MAX_CHANNEL = 0,
 UFSHCD_MAX_ID  = 1,
};

static const char *const ufshcd_state_name[] = {
 [UFSHCD_STATE_RESET]   = "reset",
 [UFSHCD_STATE_OPERATIONAL]  = "operational",
 [UFSHCD_STATE_ERROR]   = "error",
 [UFSHCD_STATE_EH_SCHEDULED_FATAL] = "eh_fatal",
 [UFSHCD_STATE_EH_SCHEDULED_NON_FATAL] = "eh_non_fatal",
};

/* UFSHCD error handling flags */
enum {
 UFSHCD_EH_IN_PROGRESS = (1 << 0),
};

/* UFSHCD UIC layer error flags */
enum {
 UFSHCD_UIC_DL_PA_INIT_ERROR = (1 << 0), /* Data link layer error */
 UFSHCD_UIC_DL_NAC_RECEIVED_ERROR = (1 << 1), /* Data link layer error */
 UFSHCD_UIC_DL_TCx_REPLAY_ERROR = (1 << 2), /* Data link layer error */
 UFSHCD_UIC_NL_ERROR = (1 << 3), /* Network layer error */
 UFSHCD_UIC_TL_ERROR = (1 << 4), /* Transport Layer error */
 UFSHCD_UIC_DME_ERROR = (1 << 5), /* DME error */
 UFSHCD_UIC_PA_GENERIC_ERROR = (1 << 6), /* Generic PA error */
};

#define ufshcd_set_eh_in_progress(h) \
 ((h)->eh_flags |= UFSHCD_EH_IN_PROGRESS)
#define ufshcd_eh_in_progress(h) \
 ((h)->eh_flags & UFSHCD_EH_IN_PROGRESS)
#define ufshcd_clear_eh_in_progress(h) \
 ((h)->eh_flags &= ~UFSHCD_EH_IN_PROGRESS)

const struct ufs_pm_lvl_states ufs_pm_lvl_states[] = {
 [UFS_PM_LVL_0] = {UFS_ACTIVE_PWR_MODE, UIC_LINK_ACTIVE_STATE},
 [UFS_PM_LVL_1] = {UFS_ACTIVE_PWR_MODE, UIC_LINK_HIBERN8_STATE},
 [UFS_PM_LVL_2] = {UFS_SLEEP_PWR_MODE, UIC_LINK_ACTIVE_STATE},
 [UFS_PM_LVL_3] = {UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE},
 [UFS_PM_LVL_4] = {UFS_POWERDOWN_PWR_MODE, UIC_LINK_HIBERN8_STATE},
 [UFS_PM_LVL_5] = {UFS_POWERDOWN_PWR_MODE, UIC_LINK_OFF_STATE},
 /*
 * For DeepSleep, the link is first put in hibern8 and then off.
 * Leaving the link in hibern8 is not supported.
 */

 [UFS_PM_LVL_6] = {UFS_DEEPSLEEP_PWR_MODE, UIC_LINK_OFF_STATE},
};

static inline enum ufs_dev_pwr_mode
ufs_get_pm_lvl_to_dev_pwr_mode(enum ufs_pm_level lvl)
{
 return ufs_pm_lvl_states[lvl].dev_state;
}

static inline enum uic_link_state
ufs_get_pm_lvl_to_link_pwr_state(enum ufs_pm_level lvl)
{
 return ufs_pm_lvl_states[lvl].link_state;
}

static inline enum ufs_pm_level
ufs_get_desired_pm_lvl_for_dev_link_state(enum ufs_dev_pwr_mode dev_state,
     enum uic_link_state link_state)
{
 enum ufs_pm_level lvl;

 for (lvl = UFS_PM_LVL_0; lvl < UFS_PM_LVL_MAX; lvl++) {
  if ((ufs_pm_lvl_states[lvl].dev_state == dev_state) &&
   (ufs_pm_lvl_states[lvl].link_state == link_state))
   return lvl;
 }

 /* if no match found, return the level 0 */
 return UFS_PM_LVL_0;
}

static bool ufshcd_has_pending_tasks(struct ufs_hba *hba)
{
 return hba->outstanding_tasks || hba->active_uic_cmd ||
        hba->uic_async_done;
}

static bool ufshcd_is_ufs_dev_busy(struct ufs_hba *hba)
{
 return scsi_host_busy(hba->host) || ufshcd_has_pending_tasks(hba);
}

static const struct ufs_dev_quirk ufs_fixups[] = {
 /* UFS cards deviations table */
 { .wmanufacturerid = UFS_VENDOR_MICRON,
   .model = UFS_ANY_MODEL,
   .quirk = UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM },
 { .wmanufacturerid = UFS_VENDOR_SAMSUNG,
   .model = UFS_ANY_MODEL,
   .quirk = UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM |
     UFS_DEVICE_QUIRK_HOST_PA_TACTIVATE |
     UFS_DEVICE_QUIRK_PA_HIBER8TIME |
     UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS },
 { .wmanufacturerid = UFS_VENDOR_SKHYNIX,
   .model = UFS_ANY_MODEL,
   .quirk = UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME },
 { .wmanufacturerid = UFS_VENDOR_SKHYNIX,
   .model = "hB8aL1" /*H28U62301AMR*/,
   .quirk = UFS_DEVICE_QUIRK_HOST_VS_DEBUGSAVECONFIGTIME },
 { .wmanufacturerid = UFS_VENDOR_TOSHIBA,
   .model = UFS_ANY_MODEL,
   .quirk = UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM },
 { .wmanufacturerid = UFS_VENDOR_TOSHIBA,
   .model = "THGLF2G9C8KBADG",
   .quirk = UFS_DEVICE_QUIRK_PA_TACTIVATE },
 { .wmanufacturerid = UFS_VENDOR_TOSHIBA,
   .model = "THGLF2G9D8KBADG",
   .quirk = UFS_DEVICE_QUIRK_PA_TACTIVATE },
 { .wmanufacturerid = UFS_VENDOR_TOSHIBA,
   .model = "THGJFJT1E45BATP",
   .quirk = UFS_DEVICE_QUIRK_NO_TIMESTAMP_SUPPORT },
 {}
};

static irqreturn_t ufshcd_tmc_handler(struct ufs_hba *hba);
static void ufshcd_async_scan(void *data, async_cookie_t cookie);
static int ufshcd_reset_and_restore(struct ufs_hba *hba);
static int ufshcd_eh_host_reset_handler(struct scsi_cmnd *cmd);
static int ufshcd_clear_tm_cmd(struct ufs_hba *hba, int tag);
static void ufshcd_hba_exit(struct ufs_hba *hba);
static int ufshcd_device_init(struct ufs_hba *hba, bool init_dev_params);
static int ufshcd_probe_hba(struct ufs_hba *hba, bool init_dev_params);
static int ufshcd_setup_clocks(struct ufs_hba *hba, bool on);
static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba);
static int ufshcd_host_reset_and_restore(struct ufs_hba *hba);
static void ufshcd_resume_clkscaling(struct ufs_hba *hba);
static void ufshcd_suspend_clkscaling(struct ufs_hba *hba);
static int ufshcd_scale_clks(struct ufs_hba *hba, unsigned long freq,
        bool scale_up);
static irqreturn_t ufshcd_intr(int irq, void *__hba);
static int ufshcd_change_power_mode(struct ufs_hba *hba,
        struct ufs_pa_layer_attr *pwr_mode);
static int ufshcd_setup_hba_vreg(struct ufs_hba *hba, bool on);
static int ufshcd_setup_vreg(struct ufs_hba *hba, bool on);
static inline int ufshcd_config_vreg_hpm(struct ufs_hba *hba,
      struct ufs_vreg *vreg);
static void ufshcd_wb_toggle_buf_flush_during_h8(struct ufs_hba *hba,
       bool enable);
static void ufshcd_hba_vreg_set_lpm(struct ufs_hba *hba);
static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba);

void ufshcd_enable_irq(struct ufs_hba *hba)
{
 if (!hba->is_irq_enabled) {
  enable_irq(hba->irq);
  hba->is_irq_enabled = true;
 }
}
EXPORT_SYMBOL_GPL(ufshcd_enable_irq);

void ufshcd_disable_irq(struct ufs_hba *hba)
{
 if (hba->is_irq_enabled) {
  disable_irq(hba->irq);
  hba->is_irq_enabled = false;
 }
}
EXPORT_SYMBOL_GPL(ufshcd_disable_irq);

/**
 * ufshcd_enable_intr - enable interrupts
 * @hba: per adapter instance
 * @intrs: interrupt bits
 */

void ufshcd_enable_intr(struct ufs_hba *hba, u32 intrs)
{
 u32 old_val = ufshcd_readl(hba, REG_INTERRUPT_ENABLE);
 u32 new_val = old_val | intrs;

 if (new_val != old_val)
  ufshcd_writel(hba, new_val, REG_INTERRUPT_ENABLE);
}

/**
 * ufshcd_disable_intr - disable interrupts
 * @hba: per adapter instance
 * @intrs: interrupt bits
 */

static void ufshcd_disable_intr(struct ufs_hba *hba, u32 intrs)
{
 u32 old_val = ufshcd_readl(hba, REG_INTERRUPT_ENABLE);
 u32 new_val = old_val & ~intrs;

 if (new_val != old_val)
  ufshcd_writel(hba, new_val, REG_INTERRUPT_ENABLE);
}

static void ufshcd_configure_wb(struct ufs_hba *hba)
{
 if (!ufshcd_is_wb_allowed(hba))
  return;

 ufshcd_wb_toggle(hba, true);

 ufshcd_wb_toggle_buf_flush_during_h8(hba, true);

 if (ufshcd_is_wb_buf_flush_allowed(hba))
  ufshcd_wb_toggle_buf_flush(hba, true);
}

static void ufshcd_add_cmd_upiu_trace(struct ufs_hba *hba, unsigned int tag,
          enum ufs_trace_str_t str_t)
{
 struct utp_upiu_req *rq = hba->lrb[tag].ucd_req_ptr;
 struct utp_upiu_header *header;

 if (!trace_ufshcd_upiu_enabled())
  return;

 if (str_t == UFS_CMD_SEND)
  header = &rq->header;
 else
  header = &hba->lrb[tag].ucd_rsp_ptr->header;

 trace_ufshcd_upiu(hba, str_t, header, &rq->sc.cdb,
     UFS_TSF_CDB);
}

static void ufshcd_add_query_upiu_trace(struct ufs_hba *hba,
     enum ufs_trace_str_t str_t,
     struct utp_upiu_req *rq_rsp)
{
 if (!trace_ufshcd_upiu_enabled())
  return;

 trace_ufshcd_upiu(hba, str_t, &rq_rsp->header,
     &rq_rsp->qr, UFS_TSF_OSF);
}

static void ufshcd_add_tm_upiu_trace(struct ufs_hba *hba, unsigned int tag,
         enum ufs_trace_str_t str_t)
{
 struct utp_task_req_desc *descp = &hba->utmrdl_base_addr[tag];

 if (!trace_ufshcd_upiu_enabled())
  return;

 if (str_t == UFS_TM_SEND)
  trace_ufshcd_upiu(hba, str_t,
      &descp->upiu_req.req_header,
      &descp->upiu_req.input_param1,
      UFS_TSF_TM_INPUT);
 else
  trace_ufshcd_upiu(hba, str_t,
      &descp->upiu_rsp.rsp_header,
      &descp->upiu_rsp.output_param1,
      UFS_TSF_TM_OUTPUT);
}

static void ufshcd_add_uic_command_trace(struct ufs_hba *hba,
      const struct uic_command *ucmd,
      enum ufs_trace_str_t str_t)
{
 u32 cmd;

 if (!trace_ufshcd_uic_command_enabled())
  return;

 if (str_t == UFS_CMD_SEND)
  cmd = ucmd->command;
 else
  cmd = ufshcd_readl(hba, REG_UIC_COMMAND);

 trace_ufshcd_uic_command(hba, str_t, cmd,
     ufshcd_readl(hba, REG_UIC_COMMAND_ARG_1),
     ufshcd_readl(hba, REG_UIC_COMMAND_ARG_2),
     ufshcd_readl(hba, REG_UIC_COMMAND_ARG_3));
}

static void ufshcd_add_command_trace(struct ufs_hba *hba, unsigned int tag,
         enum ufs_trace_str_t str_t)
{
 u64 lba = 0;
 u8 opcode = 0, group_id = 0;
 u32 doorbell = 0;
 u32 intr;
 u32 hwq_id = 0;
 struct ufshcd_lrb *lrbp = &hba->lrb[tag];
 struct scsi_cmnd *cmd = lrbp->cmd;
 struct request *rq = scsi_cmd_to_rq(cmd);
 int transfer_len = -1;

 if (!cmd)
  return;

 /* trace UPIU also */
 ufshcd_add_cmd_upiu_trace(hba, tag, str_t);
 if (!trace_ufshcd_command_enabled())
  return;

 opcode = cmd->cmnd[0];

 if (opcode == READ_10 || opcode == WRITE_10) {
  /*
 * Currently we only fully trace read(10) and write(10) commands
 */

  transfer_len =
         be32_to_cpu(lrbp->ucd_req_ptr->sc.exp_data_transfer_len);
  lba = scsi_get_lba(cmd);
  if (opcode == WRITE_10)
   group_id = lrbp->cmd->cmnd[6];
 } else if (opcode == UNMAP) {
  /*
 * The number of Bytes to be unmapped beginning with the lba.
 */

  transfer_len = blk_rq_bytes(rq);
  lba = scsi_get_lba(cmd);
 }

 intr = ufshcd_readl(hba, REG_INTERRUPT_STATUS);

 if (hba->mcq_enabled) {
  struct ufs_hw_queue *hwq = ufshcd_mcq_req_to_hwq(hba, rq);

  hwq_id = hwq->id;
 } else {
  doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
 }
 trace_ufshcd_command(cmd->device, hba, str_t, tag, doorbell, hwq_id,
        transfer_len, intr, lba, opcode, group_id);
}

static void ufshcd_print_clk_freqs(struct ufs_hba *hba)
{
 struct ufs_clk_info *clki;
 struct list_head *head = &hba->clk_list_head;

 if (list_empty(head))
  return;

 list_for_each_entry(clki, head, list) {
  if (!IS_ERR_OR_NULL(clki->clk) && clki->min_freq &&
    clki->max_freq)
   dev_err(hba->dev, "clk: %s, rate: %u\n",
     clki->name, clki->curr_freq);
 }
}

static void ufshcd_print_evt(struct ufs_hba *hba, u32 id,
        const char *err_name)
{
 int i;
 bool found = false;
 const struct ufs_event_hist *e;

 if (id >= UFS_EVT_CNT)
  return;

 e = &hba->ufs_stats.event[id];

 for (i = 0; i < UFS_EVENT_HIST_LENGTH; i++) {
  int p = (i + e->pos) % UFS_EVENT_HIST_LENGTH;

  if (e->tstamp[p] == 0)
   continue;
  dev_err(hba->dev, "%s[%d] = 0x%x at %lld us\n", err_name, p,
   e->val[p], div_u64(e->tstamp[p], 1000));
  found = true;
 }

 if (!found)
  dev_err(hba->dev, "No record of %s\n", err_name);
 else
  dev_err(hba->dev, "%s: total cnt=%llu\n", err_name, e->cnt);
}

static void ufshcd_print_evt_hist(struct ufs_hba *hba)
{
 ufshcd_dump_regs(hba, 0, UFSHCI_REG_SPACE_SIZE, "host_regs: ");

 ufshcd_print_evt(hba, UFS_EVT_PA_ERR, "pa_err");
 ufshcd_print_evt(hba, UFS_EVT_DL_ERR, "dl_err");
 ufshcd_print_evt(hba, UFS_EVT_NL_ERR, "nl_err");
 ufshcd_print_evt(hba, UFS_EVT_TL_ERR, "tl_err");
 ufshcd_print_evt(hba, UFS_EVT_DME_ERR, "dme_err");
 ufshcd_print_evt(hba, UFS_EVT_AUTO_HIBERN8_ERR,
    "auto_hibern8_err");
 ufshcd_print_evt(hba, UFS_EVT_FATAL_ERR, "fatal_err");
 ufshcd_print_evt(hba, UFS_EVT_LINK_STARTUP_FAIL,
    "link_startup_fail");
 ufshcd_print_evt(hba, UFS_EVT_RESUME_ERR, "resume_fail");
 ufshcd_print_evt(hba, UFS_EVT_SUSPEND_ERR,
    "suspend_fail");
 ufshcd_print_evt(hba, UFS_EVT_WL_RES_ERR, "wlun resume_fail");
 ufshcd_print_evt(hba, UFS_EVT_WL_SUSP_ERR,
    "wlun suspend_fail");
 ufshcd_print_evt(hba, UFS_EVT_DEV_RESET, "dev_reset");
 ufshcd_print_evt(hba, UFS_EVT_HOST_RESET, "host_reset");
 ufshcd_print_evt(hba, UFS_EVT_ABORT, "task_abort");

 ufshcd_vops_dbg_register_dump(hba);
}

static
void ufshcd_print_tr(struct ufs_hba *hba, int tag, bool pr_prdt)
{
 const struct ufshcd_lrb *lrbp;
 int prdt_length;

 lrbp = &hba->lrb[tag];

 dev_err(hba->dev, "UPIU[%d] - issue time %lld us\n",
   tag, div_u64(lrbp->issue_time_stamp_local_clock, 1000));
 dev_err(hba->dev, "UPIU[%d] - complete time %lld us\n",
   tag, div_u64(lrbp->compl_time_stamp_local_clock, 1000));
 dev_err(hba->dev,
  "UPIU[%d] - Transfer Request Descriptor phys@0x%llx\n",
  tag, (u64)lrbp->utrd_dma_addr);

 ufshcd_hex_dump("UPIU TRD: ", lrbp->utr_descriptor_ptr,
   sizeof(struct utp_transfer_req_desc));
 dev_err(hba->dev, "UPIU[%d] - Request UPIU phys@0x%llx\n", tag,
  (u64)lrbp->ucd_req_dma_addr);
 ufshcd_hex_dump("UPIU REQ: ", lrbp->ucd_req_ptr,
   sizeof(struct utp_upiu_req));
 dev_err(hba->dev, "UPIU[%d] - Response UPIU phys@0x%llx\n", tag,
  (u64)lrbp->ucd_rsp_dma_addr);
 ufshcd_hex_dump("UPIU RSP: ", lrbp->ucd_rsp_ptr,
   sizeof(struct utp_upiu_rsp));

 prdt_length = le16_to_cpu(
  lrbp->utr_descriptor_ptr->prd_table_length);
 if (hba->quirks & UFSHCD_QUIRK_PRDT_BYTE_GRAN)
  prdt_length /= ufshcd_sg_entry_size(hba);

 dev_err(hba->dev,
  "UPIU[%d] - PRDT - %d entries phys@0x%llx\n",
  tag, prdt_length,
  (u64)lrbp->ucd_prdt_dma_addr);

 if (pr_prdt)
  ufshcd_hex_dump("UPIU PRDT: ", lrbp->ucd_prdt_ptr,
   ufshcd_sg_entry_size(hba) * prdt_length);
}

static bool ufshcd_print_tr_iter(struct request *req, void *priv)
{
 struct scsi_device *sdev = req->q->queuedata;
 struct Scsi_Host *shost = sdev->host;
 struct ufs_hba *hba = shost_priv(shost);

 ufshcd_print_tr(hba, req->tag, *(bool *)priv);

 return true;
}

/**
 * ufshcd_print_trs_all - print trs for all started requests.
 * @hba: per-adapter instance.
 * @pr_prdt: need to print prdt or not.
 */

static void ufshcd_print_trs_all(struct ufs_hba *hba, bool pr_prdt)
{
 blk_mq_tagset_busy_iter(&hba->host->tag_set, ufshcd_print_tr_iter, &pr_prdt);
}

static void ufshcd_print_tmrs(struct ufs_hba *hba, unsigned long bitmap)
{
 int tag;

 for_each_set_bit(tag, &bitmap, hba->nutmrs) {
  struct utp_task_req_desc *tmrdp = &hba->utmrdl_base_addr[tag];

  dev_err(hba->dev, "TM[%d] - Task Management Header\n", tag);
  ufshcd_hex_dump("", tmrdp, sizeof(*tmrdp));
 }
}

static void ufshcd_print_host_state(struct ufs_hba *hba)
{
 const struct scsi_device *sdev_ufs = hba->ufs_device_wlun;

 dev_err(hba->dev, "UFS Host state=%d\n", hba->ufshcd_state);
 dev_err(hba->dev, "%d outstanding reqs, tasks=0x%lx\n",
  scsi_host_busy(hba->host), hba->outstanding_tasks);
 dev_err(hba->dev, "saved_err=0x%x, saved_uic_err=0x%x\n",
  hba->saved_err, hba->saved_uic_err);
 dev_err(hba->dev, "Device power mode=%d, UIC link state=%d\n",
  hba->curr_dev_pwr_mode, hba->uic_link_state);
 dev_err(hba->dev, "PM in progress=%d, sys. suspended=%d\n",
  hba->pm_op_in_progress, hba->is_sys_suspended);
 dev_err(hba->dev, "Auto BKOPS=%d, Host self-block=%d\n",
  hba->auto_bkops_enabled, hba->host->host_self_blocked);
 dev_err(hba->dev, "Clk gate=%d\n", hba->clk_gating.state);
 dev_err(hba->dev,
  "last_hibern8_exit_tstamp at %lld us, hibern8_exit_cnt=%d\n",
  div_u64(hba->ufs_stats.last_hibern8_exit_tstamp, 1000),
  hba->ufs_stats.hibern8_exit_cnt);
 dev_err(hba->dev, "error handling flags=0x%x, req. abort count=%d\n",
  hba->eh_flags, hba->req_abort_count);
 dev_err(hba->dev, "hba->ufs_version=0x%x, Host capabilities=0x%x, caps=0x%x\n",
  hba->ufs_version, hba->capabilities, hba->caps);
 dev_err(hba->dev, "quirks=0x%x, dev. quirks=0x%x\n", hba->quirks,
  hba->dev_quirks);
 if (sdev_ufs)
  dev_err(hba->dev, "UFS dev info: %.8s %.16s rev %.4s\n",
   sdev_ufs->vendor, sdev_ufs->model, sdev_ufs->rev);

 ufshcd_print_clk_freqs(hba);
}

/**
 * ufshcd_print_pwr_info - print power params as saved in hba
 * power info
 * @hba: per-adapter instance
 */

static void ufshcd_print_pwr_info(struct ufs_hba *hba)
{
 static const char * const names[] = {
  "INVALID MODE",
  "FAST MODE",
  "SLOW_MODE",
  "INVALID MODE",
  "FASTAUTO_MODE",
  "SLOWAUTO_MODE",
  "INVALID MODE",
 };

 /*
 * Using dev_dbg to avoid messages during runtime PM to avoid
 * never-ending cycles of messages written back to storage by user space
 * causing runtime resume, causing more messages and so on.
 */

 dev_dbg(hba->dev, "%s:[RX, TX]: gear=[%d, %d], lane[%d, %d], pwr[%s, %s], rate = %d\n",
   __func__,
   hba->pwr_info.gear_rx, hba->pwr_info.gear_tx,
   hba->pwr_info.lane_rx, hba->pwr_info.lane_tx,
   names[hba->pwr_info.pwr_rx],
   names[hba->pwr_info.pwr_tx],
   hba->pwr_info.hs_rate);
}

static void ufshcd_device_reset(struct ufs_hba *hba)
{
 int err;

 err = ufshcd_vops_device_reset(hba);

 if (!err) {
  ufshcd_set_ufs_dev_active(hba);
  if (ufshcd_is_wb_allowed(hba)) {
   hba->dev_info.wb_enabled = false;
   hba->dev_info.wb_buf_flush_enabled = false;
  }
  if (hba->dev_info.rtc_type == UFS_RTC_RELATIVE)
   hba->dev_info.rtc_time_baseline = 0;
 }
 if (err != -EOPNOTSUPP)
  ufshcd_update_evt_hist(hba, UFS_EVT_DEV_RESET, err);
}

void ufshcd_delay_us(unsigned long us, unsigned long tolerance)
{
 if (!us)
  return;

 if (us < 10)
  udelay(us);
 else
  usleep_range(us, us + tolerance);
}
EXPORT_SYMBOL_GPL(ufshcd_delay_us);

/**
 * ufshcd_wait_for_register - wait for register value to change
 * @hba: per-adapter interface
 * @reg: mmio register offset
 * @mask: mask to apply to the read register value
 * @val: value to wait for
 * @interval_us: polling interval in microseconds
 * @timeout_ms: timeout in milliseconds
 *
 * Return: -ETIMEDOUT on error, zero on success.
 */

static int ufshcd_wait_for_register(struct ufs_hba *hba, u32 reg, u32 mask,
        u32 val, unsigned long interval_us,
        unsigned long timeout_ms)
{
 u32 v;

 val &= mask; /* ignore bits that we don't intend to wait on */

 return read_poll_timeout(ufshcd_readl, v, (v & mask) == val,
     interval_us, timeout_ms * 1000, false, hba, reg);
}

/**
 * ufshcd_get_intr_mask - Get the interrupt bit mask
 * @hba: Pointer to adapter instance
 *
 * Return: interrupt bit mask per version
 */

static inline u32 ufshcd_get_intr_mask(struct ufs_hba *hba)
{
 if (hba->ufs_version <= ufshci_version(2, 0))
  return INTERRUPT_MASK_ALL_VER_11;

 return INTERRUPT_MASK_ALL_VER_21;
}

/**
 * ufshcd_get_ufs_version - Get the UFS version supported by the HBA
 * @hba: Pointer to adapter instance
 *
 * Return: UFSHCI version supported by the controller
 */

static inline u32 ufshcd_get_ufs_version(struct ufs_hba *hba)
{
 u32 ufshci_ver;

 if (hba->quirks & UFSHCD_QUIRK_BROKEN_UFS_HCI_VERSION)
  ufshci_ver = ufshcd_vops_get_ufs_hci_version(hba);
 else
  ufshci_ver = ufshcd_readl(hba, REG_UFS_VERSION);

 /*
 * UFSHCI v1.x uses a different version scheme, in order
 * to allow the use of comparisons with the ufshci_version
 * function, we convert it to the same scheme as ufs 2.0+.
 */

 if (ufshci_ver & 0x00010000)
  return ufshci_version(1, ufshci_ver & 0x00000100);

 return ufshci_ver;
}

/**
 * ufshcd_is_device_present - Check if any device connected to
 *       the host controller
 * @hba: pointer to adapter instance
 *
 * Return: true if device present, false if no device detected
 */

static inline bool ufshcd_is_device_present(struct ufs_hba *hba)
{
 return ufshcd_readl(hba, REG_CONTROLLER_STATUS) & DEVICE_PRESENT;
}

/**
 * ufshcd_get_tr_ocs - Get the UTRD Overall Command Status
 * @lrbp: pointer to local command reference block
 * @cqe: pointer to the completion queue entry
 *
 * This function is used to get the OCS field from UTRD
 *
 * Return: the OCS field in the UTRD.
 */

static enum utp_ocs ufshcd_get_tr_ocs(struct ufshcd_lrb *lrbp,
          struct cq_entry *cqe)
{
 if (cqe)
  return le32_to_cpu(cqe->status) & MASK_OCS;

 return lrbp->utr_descriptor_ptr->header.ocs & MASK_OCS;
}

/**
 * ufshcd_utrl_clear() - Clear requests from the controller request list.
 * @hba: per adapter instance
 * @mask: mask with one bit set for each request to be cleared
 */

static inline void ufshcd_utrl_clear(struct ufs_hba *hba, u32 mask)
{
 if (hba->quirks & UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR)
  mask = ~mask;
 /*
 * From the UFSHCI specification: "UTP Transfer Request List CLear
 * Register (UTRLCLR): This field is bit significant. Each bit
 * corresponds to a slot in the UTP Transfer Request List, where bit 0
 * corresponds to request slot 0. A bit in this field is set to ‘0’
 * by host software to indicate to the host controller that a transfer
 * request slot is cleared. The host controller
 * shall free up any resources associated to the request slot
 * immediately, and shall set the associated bit in UTRLDBR to ‘0’. The
 * host software indicates no change to request slots by setting the
 * associated bits in this field to ‘1’. Bits in this field shall only
 * be set ‘1’ or ‘0’ by host software when UTRLRSR is set to ‘1’."
 */

 ufshcd_writel(hba, ~mask, REG_UTP_TRANSFER_REQ_LIST_CLEAR);
}

/**
 * ufshcd_utmrl_clear - Clear a bit in UTMRLCLR register
 * @hba: per adapter instance
 * @pos: position of the bit to be cleared
 */

static inline void ufshcd_utmrl_clear(struct ufs_hba *hba, u32 pos)
{
 if (hba->quirks & UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR)
  ufshcd_writel(hba, (1 << pos), REG_UTP_TASK_REQ_LIST_CLEAR);
 else
  ufshcd_writel(hba, ~(1 << pos), REG_UTP_TASK_REQ_LIST_CLEAR);
}

/**
 * ufshcd_get_lists_status - Check UCRDY, UTRLRDY and UTMRLRDY
 * @reg: Register value of host controller status
 *
 * Return: 0 on success; a positive value if failed.
 */

static inline int ufshcd_get_lists_status(u32 reg)
{
 return !((reg & UFSHCD_STATUS_READY) == UFSHCD_STATUS_READY);
}

/**
 * ufshcd_get_uic_cmd_result - Get the UIC command result
 * @hba: Pointer to adapter instance
 *
 * This function gets the result of UIC command completion
 *
 * Return: 0 on success; non-zero value on error.
 */

static inline int ufshcd_get_uic_cmd_result(struct ufs_hba *hba)
{
 return ufshcd_readl(hba, REG_UIC_COMMAND_ARG_2) &
        MASK_UIC_COMMAND_RESULT;
}

/**
 * ufshcd_get_dme_attr_val - Get the value of attribute returned by UIC command
 * @hba: Pointer to adapter instance
 *
 * This function gets UIC command argument3
 *
 * Return: 0 on success; non-zero value on error.
 */

static inline u32 ufshcd_get_dme_attr_val(struct ufs_hba *hba)
{
 return ufshcd_readl(hba, REG_UIC_COMMAND_ARG_3);
}

/**
 * ufshcd_get_req_rsp - returns the TR response transaction type
 * @ucd_rsp_ptr: pointer to response UPIU
 *
 * Return: UPIU type.
 */

static inline enum upiu_response_transaction
ufshcd_get_req_rsp(struct utp_upiu_rsp *ucd_rsp_ptr)
{
 return ucd_rsp_ptr->header.transaction_code;
}

/**
 * ufshcd_is_exception_event - Check if the device raised an exception event
 * @ucd_rsp_ptr: pointer to response UPIU
 *
 * The function checks if the device raised an exception event indicated in
 * the Device Information field of response UPIU.
 *
 * Return: true if exception is raised, false otherwise.
 */

static inline bool ufshcd_is_exception_event(struct utp_upiu_rsp *ucd_rsp_ptr)
{
 return ucd_rsp_ptr->header.device_information & 1;
}

/**
 * ufshcd_reset_intr_aggr - Reset interrupt aggregation values.
 * @hba: per adapter instance
 */

static inline void
ufshcd_reset_intr_aggr(struct ufs_hba *hba)
{
 ufshcd_writel(hba, INT_AGGR_ENABLE |
        INT_AGGR_COUNTER_AND_TIMER_RESET,
        REG_UTP_TRANSFER_REQ_INT_AGG_CONTROL);
}

/**
 * ufshcd_config_intr_aggr - Configure interrupt aggregation values.
 * @hba: per adapter instance
 * @cnt: Interrupt aggregation counter threshold
 * @tmout: Interrupt aggregation timeout value
 */

static inline void
ufshcd_config_intr_aggr(struct ufs_hba *hba, u8 cnt, u8 tmout)
{
 ufshcd_writel(hba, INT_AGGR_ENABLE | INT_AGGR_PARAM_WRITE |
        INT_AGGR_COUNTER_THLD_VAL(cnt) |
        INT_AGGR_TIMEOUT_VAL(tmout),
        REG_UTP_TRANSFER_REQ_INT_AGG_CONTROL);
}

/**
 * ufshcd_disable_intr_aggr - Disables interrupt aggregation.
 * @hba: per adapter instance
 */

static inline void ufshcd_disable_intr_aggr(struct ufs_hba *hba)
{
 ufshcd_writel(hba, 0, REG_UTP_TRANSFER_REQ_INT_AGG_CONTROL);
}

/**
 * ufshcd_enable_run_stop_reg - Enable run-stop registers,
 * When run-stop registers are set to 1, it indicates the
 * host controller that it can process the requests
 * @hba: per adapter instance
 */

static void ufshcd_enable_run_stop_reg(struct ufs_hba *hba)
{
 ufshcd_writel(hba, UTP_TASK_REQ_LIST_RUN_STOP_BIT,
        REG_UTP_TASK_REQ_LIST_RUN_STOP);
 ufshcd_writel(hba, UTP_TRANSFER_REQ_LIST_RUN_STOP_BIT,
        REG_UTP_TRANSFER_REQ_LIST_RUN_STOP);
}

/**
 * ufshcd_hba_start - Start controller initialization sequence
 * @hba: per adapter instance
 */

static inline void ufshcd_hba_start(struct ufs_hba *hba)
{
 u32 val = CONTROLLER_ENABLE;

 if (ufshcd_crypto_enable(hba))
  val |= CRYPTO_GENERAL_ENABLE;

 ufshcd_writel(hba, val, REG_CONTROLLER_ENABLE);
}

/**
 * ufshcd_is_hba_active - Get controller state
 * @hba: per adapter instance
 *
 * Return: true if and only if the controller is active.
 */

bool ufshcd_is_hba_active(struct ufs_hba *hba)
{
 return ufshcd_readl(hba, REG_CONTROLLER_ENABLE) & CONTROLLER_ENABLE;
}
EXPORT_SYMBOL_GPL(ufshcd_is_hba_active);

/**
 * ufshcd_pm_qos_init - initialize PM QoS request
 * @hba: per adapter instance
 */

void ufshcd_pm_qos_init(struct ufs_hba *hba)
{
 guard(mutex)(&hba->pm_qos_mutex);

 if (hba->pm_qos_enabled)
  return;

 cpu_latency_qos_add_request(&hba->pm_qos_req, PM_QOS_DEFAULT_VALUE);

 if (cpu_latency_qos_request_active(&hba->pm_qos_req))
  hba->pm_qos_enabled = true;
}

/**
 * ufshcd_pm_qos_exit - remove request from PM QoS
 * @hba: per adapter instance
 */

void ufshcd_pm_qos_exit(struct ufs_hba *hba)
{
 guard(mutex)(&hba->pm_qos_mutex);

 if (!hba->pm_qos_enabled)
  return;

 cpu_latency_qos_remove_request(&hba->pm_qos_req);
 hba->pm_qos_enabled = false;
}

/**
 * ufshcd_pm_qos_update - update PM QoS request
 * @hba: per adapter instance
 * @on: If True, vote for perf PM QoS mode otherwise power save mode
 */

static void ufshcd_pm_qos_update(struct ufs_hba *hba, bool on)
{
 guard(mutex)(&hba->pm_qos_mutex);

 if (!hba->pm_qos_enabled)
  return;

 cpu_latency_qos_update_request(&hba->pm_qos_req, on ? 0 : PM_QOS_DEFAULT_VALUE);
}

/**
 * ufshcd_set_clk_freq - set UFS controller clock frequencies
 * @hba: per adapter instance
 * @scale_up: If True, set max possible frequency othewise set low frequency
 *
 * Return: 0 if successful; < 0 upon failure.
 */

static int ufshcd_set_clk_freq(struct ufs_hba *hba, bool scale_up)
{
 int ret = 0;
 struct ufs_clk_info *clki;
 struct list_head *head = &hba->clk_list_head;

 if (list_empty(head))
  goto out;

 list_for_each_entry(clki, head, list) {
  if (!IS_ERR_OR_NULL(clki->clk)) {
   if (scale_up && clki->max_freq) {
    if (clki->curr_freq == clki->max_freq)
     continue;

    ret = clk_set_rate(clki->clk, clki->max_freq);
    if (ret) {
     dev_err(hba->dev, "%s: %s clk set rate(%dHz) failed, %d\n",
      __func__, clki->name,
      clki->max_freq, ret);
     break;
    }
    trace_ufshcd_clk_scaling(hba,
      "scaled up", clki->name,
      clki->curr_freq,
      clki->max_freq);

    clki->curr_freq = clki->max_freq;

   } else if (!scale_up && clki->min_freq) {
    if (clki->curr_freq == clki->min_freq)
     continue;

    ret = clk_set_rate(clki->clk, clki->min_freq);
    if (ret) {
     dev_err(hba->dev, "%s: %s clk set rate(%dHz) failed, %d\n",
      __func__, clki->name,
      clki->min_freq, ret);
     break;
    }
    trace_ufshcd_clk_scaling(hba,
      "scaled down", clki->name,
      clki->curr_freq,
      clki->min_freq);
    clki->curr_freq = clki->min_freq;
   }
  }
  dev_dbg(hba->dev, "%s: clk: %s, rate: %lu\n", __func__,
    clki->name, clk_get_rate(clki->clk));
 }

out:
 return ret;
}

int ufshcd_opp_config_clks(struct device *dev, struct opp_table *opp_table,
      struct dev_pm_opp *opp, void *data,
      bool scaling_down)
{
 struct ufs_hba *hba = dev_get_drvdata(dev);
 struct list_head *head = &hba->clk_list_head;
 struct ufs_clk_info *clki;
 unsigned long freq;
 u8 idx = 0;
 int ret;

 list_for_each_entry(clki, head, list) {
  if (!IS_ERR_OR_NULL(clki->clk)) {
   freq = dev_pm_opp_get_freq_indexed(opp, idx++);

   /* Do not set rate for clocks having frequency as 0 */
   if (!freq)
    continue;

   ret = clk_set_rate(clki->clk, freq);
   if (ret) {
    dev_err(dev, "%s: %s clk set rate(%ldHz) failed, %d\n",
     __func__, clki->name, freq, ret);
    return ret;
   }

   trace_ufshcd_clk_scaling(hba,
    (scaling_down ? "scaled down" : "scaled up"),
    clki->name, hba->clk_scaling.target_freq, freq);
  }
 }

 return 0;
}
EXPORT_SYMBOL_GPL(ufshcd_opp_config_clks);

static int ufshcd_opp_set_rate(struct ufs_hba *hba, unsigned long freq)
{
 struct dev_pm_opp *opp;
 int ret;

 opp = dev_pm_opp_find_freq_floor_indexed(hba->dev,
       &freq, 0);
 if (IS_ERR(opp))
  return PTR_ERR(opp);

 ret = dev_pm_opp_set_opp(hba->dev, opp);
 dev_pm_opp_put(opp);

 return ret;
}

/**
 * ufshcd_scale_clks - scale up or scale down UFS controller clocks
 * @hba: per adapter instance
 * @freq: frequency to scale
 * @scale_up: True if scaling up and false if scaling down
 *
 * Return: 0 if successful; < 0 upon failure.
 */

static int ufshcd_scale_clks(struct ufs_hba *hba, unsigned long freq,
        bool scale_up)
{
 int ret = 0;
 ktime_t start = ktime_get();

 ret = ufshcd_vops_clk_scale_notify(hba, scale_up, freq, PRE_CHANGE);
 if (ret)
  goto out;

 if (hba->use_pm_opp)
  ret = ufshcd_opp_set_rate(hba, freq);
 else
  ret = ufshcd_set_clk_freq(hba, scale_up);
 if (ret)
  goto out;

 ret = ufshcd_vops_clk_scale_notify(hba, scale_up, freq, POST_CHANGE);
 if (ret) {
  if (hba->use_pm_opp)
   ufshcd_opp_set_rate(hba,
         hba->devfreq->previous_freq);
  else
   ufshcd_set_clk_freq(hba, !scale_up);
  goto out;
 }

 ufshcd_pm_qos_update(hba, scale_up);

out:
 trace_ufshcd_profile_clk_scaling(hba,
   (scale_up ? "up" : "down"),
   ktime_to_us(ktime_sub(ktime_get(), start)), ret);
 return ret;
}

/**
 * ufshcd_is_devfreq_scaling_required - check if scaling is required or not
 * @hba: per adapter instance
 * @freq: frequency to scale
 * @scale_up: True if scaling up and false if scaling down
 *
 * Return: true if scaling is required, false otherwise.
 */

static bool ufshcd_is_devfreq_scaling_required(struct ufs_hba *hba,
            unsigned long freq, bool scale_up)
{
 struct ufs_clk_info *clki;
 struct list_head *head = &hba->clk_list_head;

 if (list_empty(head))
  return false;

 if (hba->use_pm_opp)
  return freq != hba->clk_scaling.target_freq;

 list_for_each_entry(clki, head, list) {
  if (!IS_ERR_OR_NULL(clki->clk)) {
   if (scale_up && clki->max_freq) {
    if (clki->curr_freq == clki->max_freq)
     continue;
    return true;
   } else if (!scale_up && clki->min_freq) {
    if (clki->curr_freq == clki->min_freq)
     continue;
    return true;
   }
  }
 }

 return false;
}

/*
 * Determine the number of pending commands by counting the bits in the SCSI
 * device budget maps. This approach has been selected because a bit is set in
 * the budget map before scsi_host_queue_ready() checks the host_self_blocked
 * flag. The host_self_blocked flag can be modified by calling
 * scsi_block_requests() or scsi_unblock_requests().
 */

static u32 ufshcd_pending_cmds(struct ufs_hba *hba)
{
 const struct scsi_device *sdev;
 unsigned long flags;
 u32 pending = 0;

 spin_lock_irqsave(hba->host->host_lock, flags);
 __shost_for_each_device(sdev, hba->host)
  pending += sbitmap_weight(&sdev->budget_map);
 spin_unlock_irqrestore(hba->host->host_lock, flags);

 return pending;
}

/*
 * Wait until all pending SCSI commands and TMFs have finished or the timeout
 * has expired.
 *
 * Return: 0 upon success; -EBUSY upon timeout.
 */

static int ufshcd_wait_for_pending_cmds(struct ufs_hba *hba,
     u64 wait_timeout_us)
{
 int ret = 0;
 u32 tm_doorbell;
 u32 tr_pending;
 bool timeout = false, do_last_check = false;
 ktime_t start;

 ufshcd_hold(hba);
 /*
 * Wait for all the outstanding tasks/transfer requests.
 * Verify by checking the doorbell registers are clear.
 */

 start = ktime_get();
 do {
  if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL) {
   ret = -EBUSY;
   goto out;
  }

  tm_doorbell = ufshcd_readl(hba, REG_UTP_TASK_REQ_DOOR_BELL);
  tr_pending = ufshcd_pending_cmds(hba);
  if (!tm_doorbell && !tr_pending) {
   timeout = false;
   break;
  } else if (do_last_check) {
   break;
  }

  io_schedule_timeout(msecs_to_jiffies(20));
  if (ktime_to_us(ktime_sub(ktime_get(), start)) >
      wait_timeout_us) {
   timeout = true;
   /*
 * We might have scheduled out for long time so make
 * sure to check if doorbells are cleared by this time
 * or not.
 */

   do_last_check = true;
  }
 } while (tm_doorbell || tr_pending);

 if (timeout) {
  dev_err(hba->dev,
   "%s: timedout waiting for doorbell to clear (tm=0x%x, tr=0x%x)\n",
   __func__, tm_doorbell, tr_pending);
  ret = -EBUSY;
 }
out:
 ufshcd_release(hba);
 return ret;
}

/**
 * ufshcd_scale_gear - scale up/down UFS gear
 * @hba: per adapter instance
 * @target_gear: target gear to scale to
 * @scale_up: True for scaling up gear and false for scaling down
 *
 * Return: 0 for success; -EBUSY if scaling can't happen at this time;
 * non-zero for any other errors.
 */

static int ufshcd_scale_gear(struct ufs_hba *hba, u32 target_gear, bool scale_up)
{
 int ret = 0;
 struct ufs_pa_layer_attr new_pwr_info;

 if (target_gear) {
  new_pwr_info = hba->pwr_info;
  new_pwr_info.gear_tx = target_gear;
  new_pwr_info.gear_rx = target_gear;

  goto config_pwr_mode;
 }

 /* Legacy gear scaling, in case vops_freq_to_gear_speed() is not implemented */
 if (scale_up) {
  memcpy(&new_pwr_info, &hba->clk_scaling.saved_pwr_info,
         sizeof(struct ufs_pa_layer_attr));
 } else {
  memcpy(&new_pwr_info, &hba->pwr_info,
         sizeof(struct ufs_pa_layer_attr));

  if (hba->pwr_info.gear_tx > hba->clk_scaling.min_gear ||
      hba->pwr_info.gear_rx > hba->clk_scaling.min_gear) {
   /* save the current power mode */
   memcpy(&hba->clk_scaling.saved_pwr_info,
    &hba->pwr_info,
    sizeof(struct ufs_pa_layer_attr));

   /* scale down gear */
   new_pwr_info.gear_tx = hba->clk_scaling.min_gear;
   new_pwr_info.gear_rx = hba->clk_scaling.min_gear;
  }
 }

config_pwr_mode:
 /* check if the power mode needs to be changed or not? */
 ret = ufshcd_config_pwr_mode(hba, &new_pwr_info);
 if (ret)
  dev_err(hba->dev, "%s: failed err %d, old gear: (tx %d rx %d), new gear: (tx %d rx %d)",
   __func__, ret,
   hba->pwr_info.gear_tx, hba->pwr_info.gear_rx,
   new_pwr_info.gear_tx, new_pwr_info.gear_rx);

 return ret;
}

/*
 * Wait until all pending SCSI commands and TMFs have finished or the timeout
 * has expired.
 *
 * Return: 0 upon success; -EBUSY upon timeout.
 */

static int ufshcd_clock_scaling_prepare(struct ufs_hba *hba, u64 timeout_us)
{
 int ret = 0;
 /*
 * make sure that there are no outstanding requests when
 * clock scaling is in progress
 */

 mutex_lock(&hba->host->scan_mutex);
 blk_mq_quiesce_tagset(&hba->host->tag_set);
 mutex_lock(&hba->wb_mutex);
 down_write(&hba->clk_scaling_lock);

 if (!hba->clk_scaling.is_allowed ||
     ufshcd_wait_for_pending_cmds(hba, timeout_us)) {
  ret = -EBUSY;
  up_write(&hba->clk_scaling_lock);
  mutex_unlock(&hba->wb_mutex);
  blk_mq_unquiesce_tagset(&hba->host->tag_set);
  mutex_unlock(&hba->host->scan_mutex);
  goto out;
 }

 /* let's not get into low power until clock scaling is completed */
 ufshcd_hold(hba);

out:
 return ret;
}

static void ufshcd_clock_scaling_unprepare(struct ufs_hba *hba, int err)
{
 up_write(&hba->clk_scaling_lock);

 /* Enable Write Booster if current gear requires it else disable it */
 if (ufshcd_enable_wb_if_scaling_up(hba) && !err)
  ufshcd_wb_toggle(hba, hba->pwr_info.gear_rx >= hba->clk_scaling.wb_gear);

 mutex_unlock(&hba->wb_mutex);

 blk_mq_unquiesce_tagset(&hba->host->tag_set);
 mutex_unlock(&hba->host->scan_mutex);
 ufshcd_release(hba);
}

/**
 * ufshcd_devfreq_scale - scale up/down UFS clocks and gear
 * @hba: per adapter instance
 * @freq: frequency to scale
 * @scale_up: True for scaling up and false for scalin down
 *
 * Return: 0 for success; -EBUSY if scaling can't happen at this time; non-zero
 * for any other errors.
 */

static int ufshcd_devfreq_scale(struct ufs_hba *hba, unsigned long freq,
    bool scale_up)
{
 u32 old_gear = hba->pwr_info.gear_rx;
 u32 new_gear = 0;
 int ret = 0;

 new_gear = ufshcd_vops_freq_to_gear_speed(hba, freq);

 ret = ufshcd_clock_scaling_prepare(hba, 1 * USEC_PER_SEC);
 if (ret)
  return ret;

 /* scale down the gear before scaling down clocks */
 if (!scale_up) {
  ret = ufshcd_scale_gear(hba, new_gear, false);
  if (ret)
   goto out_unprepare;
 }

 ret = ufshcd_scale_clks(hba, freq, scale_up);
 if (ret) {
  if (!scale_up)
   ufshcd_scale_gear(hba, old_gear, true);
  goto out_unprepare;
 }

 /* scale up the gear after scaling up clocks */
 if (scale_up) {
  ret = ufshcd_scale_gear(hba, new_gear, true);
  if (ret) {
   ufshcd_scale_clks(hba, hba->devfreq->previous_freq,
       false);
   goto out_unprepare;
  }
 }

out_unprepare:
 ufshcd_clock_scaling_unprepare(hba, ret);
 return ret;
}

static void ufshcd_clk_scaling_suspend_work(struct work_struct *work)
{
 struct ufs_hba *hba = container_of(work, struct ufs_hba,
        clk_scaling.suspend_work);

 scoped_guard(spinlock_irqsave, &hba->clk_scaling.lock)
 {
  if (hba->clk_scaling.active_reqs ||
      hba->clk_scaling.is_suspended)
   return;

  hba->clk_scaling.is_suspended = true;
  hba->clk_scaling.window_start_t = 0;
 }

 devfreq_suspend_device(hba->devfreq);
}

static void ufshcd_clk_scaling_resume_work(struct work_struct *work)
{
 struct ufs_hba *hba = container_of(work, struct ufs_hba,
        clk_scaling.resume_work);

 scoped_guard(spinlock_irqsave, &hba->clk_scaling.lock)
 {
  if (!hba->clk_scaling.is_suspended)
   return;
  hba->clk_scaling.is_suspended = false;
 }

 devfreq_resume_device(hba->devfreq);
}

static int ufshcd_devfreq_target(struct device *dev,
    unsigned long *freq, u32 flags)
{
 int ret = 0;
 struct ufs_hba *hba = dev_get_drvdata(dev);
 ktime_t start;
 bool scale_up = false, sched_clk_scaling_suspend_work = false;
 struct list_head *clk_list = &hba->clk_list_head;
 struct ufs_clk_info *clki;

 if (!ufshcd_is_clkscaling_supported(hba))
  return -EINVAL;

 if (hba->use_pm_opp) {
  struct dev_pm_opp *opp;

  /* Get the recommended frequency from OPP framework */
  opp = devfreq_recommended_opp(dev, freq, flags);
  if (IS_ERR(opp))
   return PTR_ERR(opp);

  dev_pm_opp_put(opp);
 } else {
  /* Override with the closest supported frequency */
  clki = list_first_entry(&hba->clk_list_head, struct ufs_clk_info,
     list);
  *freq = (unsigned long) clk_round_rate(clki->clk, *freq);
 }

 scoped_guard(spinlock_irqsave, &hba->clk_scaling.lock)
 {
  if (ufshcd_eh_in_progress(hba))
   return 0;

  /* Skip scaling clock when clock scaling is suspended */
  if (hba->clk_scaling.is_suspended) {
   dev_warn(hba->dev, "clock scaling is suspended, skip");
   return 0;
  }

  if (!hba->clk_scaling.active_reqs)
   sched_clk_scaling_suspend_work = true;

  if (list_empty(clk_list))
   goto out;

  /* Decide based on the target or rounded-off frequency and update */
  if (hba->use_pm_opp)
   scale_up = *freq > hba->clk_scaling.target_freq;
  else
   scale_up = *freq == clki->max_freq;

  if (!hba->use_pm_opp && !scale_up)
   *freq = clki->min_freq;

  /* Update the frequency */
  if (!ufshcd_is_devfreq_scaling_required(hba, *freq, scale_up)) {
   ret = 0;
   goto out; /* no state change required */
  }
 }

 start = ktime_get();
 ret = ufshcd_devfreq_scale(hba, *freq, scale_up);
 if (!ret)
  hba->clk_scaling.target_freq = *freq;

 trace_ufshcd_profile_clk_scaling(hba,
  (scale_up ? "up" : "down"),
  ktime_to_us(ktime_sub(ktime_get(), start)), ret);

out:
 if (sched_clk_scaling_suspend_work &&
   (!scale_up || hba->clk_scaling.suspend_on_no_request))
  queue_work(hba->clk_scaling.workq,
      &hba->clk_scaling.suspend_work);

 return ret;
}

static int ufshcd_devfreq_get_dev_status(struct device *dev,
  struct devfreq_dev_status *stat)
{
 struct ufs_hba *hba = dev_get_drvdata(dev);
 struct ufs_clk_scaling *scaling = &hba->clk_scaling;
 ktime_t curr_t;

 if (!ufshcd_is_clkscaling_supported(hba))
  return -EINVAL;

 memset(stat, 0, sizeof(*stat));

 guard(spinlock_irqsave)(&hba->clk_scaling.lock);

 curr_t = ktime_get();
 if (!scaling->window_start_t)
  goto start_window;

 /*
 * If current frequency is 0, then the ondemand governor considers
 * there's no initial frequency set. And it always requests to set
 * to max. frequency.
 */

 if (hba->use_pm_opp) {
  stat->current_frequency = hba->clk_scaling.target_freq;
 } else {
  struct list_head *clk_list = &hba->clk_list_head;
  struct ufs_clk_info *clki;

  clki = list_first_entry(clk_list, struct ufs_clk_info, list);
  stat->current_frequency = clki->curr_freq;
 }

 if (scaling->is_busy_started)
  scaling->tot_busy_t += ktime_us_delta(curr_t,
    scaling->busy_start_t);
 stat->total_time = ktime_us_delta(curr_t, scaling->window_start_t);
 stat->busy_time = scaling->tot_busy_t;
start_window:
 scaling->window_start_t = curr_t;
 scaling->tot_busy_t = 0;

 if (scaling->active_reqs) {
  scaling->busy_start_t = curr_t;
  scaling->is_busy_started = true;
 } else {
  scaling->busy_start_t = 0;
  scaling->is_busy_started = false;
 }

 return 0;
}

static int ufshcd_devfreq_init(struct ufs_hba *hba)
{
 struct list_head *clk_list = &hba->clk_list_head;
 struct ufs_clk_info *clki;
 struct devfreq *devfreq;
 int ret;

 /* Skip devfreq if we don't have any clocks in the list */
 if (list_empty(clk_list))
  return 0;

 if (!hba->use_pm_opp) {
  clki = list_first_entry(clk_list, struct ufs_clk_info, list);
  dev_pm_opp_add(hba->dev, clki->min_freq, 0);
  dev_pm_opp_add(hba->dev, clki->max_freq, 0);
 }

 ufshcd_vops_config_scaling_param(hba, &hba->vps->devfreq_profile,
      &hba->vps->ondemand_data);
 devfreq = devfreq_add_device(hba->dev,
   &hba->vps->devfreq_profile,
   DEVFREQ_GOV_SIMPLE_ONDEMAND,
   &hba->vps->ondemand_data);
 if (IS_ERR(devfreq)) {
  ret = PTR_ERR(devfreq);
  dev_err(hba->dev, "Unable to register with devfreq %d\n", ret);

  if (!hba->use_pm_opp) {
   dev_pm_opp_remove(hba->dev, clki->min_freq);
   dev_pm_opp_remove(hba->dev, clki->max_freq);
  }
  return ret;
 }

 hba->devfreq = devfreq;

 return 0;
}

static void ufshcd_devfreq_remove(struct ufs_hba *hba)
{
 struct list_head *clk_list = &hba->clk_list_head;

 if (!hba->devfreq)
  return;

 devfreq_remove_device(hba->devfreq);
 hba->devfreq = NULL;

 if (!hba->use_pm_opp) {
  struct ufs_clk_info *clki;

  clki = list_first_entry(clk_list, struct ufs_clk_info, list);
  dev_pm_opp_remove(hba->dev, clki->min_freq);
  dev_pm_opp_remove(hba->dev, clki->max_freq);
 }
}

static void ufshcd_suspend_clkscaling(struct ufs_hba *hba)
{
 bool suspend = false;

 cancel_work_sync(&hba->clk_scaling.suspend_work);
 cancel_work_sync(&hba->clk_scaling.resume_work);

 scoped_guard(spinlock_irqsave, &hba->clk_scaling.lock)
 {
  if (!hba->clk_scaling.is_suspended) {
   suspend = true;
   hba->clk_scaling.is_suspended = true;
   hba->clk_scaling.window_start_t = 0;
  }
 }

 if (suspend)
  devfreq_suspend_device(hba->devfreq);
}

static void ufshcd_resume_clkscaling(struct ufs_hba *hba)
{
 bool resume = false;

 scoped_guard(spinlock_irqsave, &hba->clk_scaling.lock)
 {
  if (hba->clk_scaling.is_suspended) {
   resume = true;
   hba->clk_scaling.is_suspended = false;
  }
 }

 if (resume)
  devfreq_resume_device(hba->devfreq);
}

static ssize_t ufshcd_clkscale_enable_show(struct device *dev,
  struct device_attribute *attr, char *buf)
{
 struct ufs_hba *hba = dev_get_drvdata(dev);

 return sysfs_emit(buf, "%d\n", hba->clk_scaling.is_enabled);
}

static ssize_t ufshcd_clkscale_enable_store(struct device *dev,
  struct device_attribute *attr, const char *buf, size_t count)
{
 struct ufs_hba *hba = dev_get_drvdata(dev);
 struct ufs_clk_info *clki;
 unsigned long freq;
 u32 value;
 int err = 0;

 if (kstrtou32(buf, 0, &value))
  return -EINVAL;

 down(&hba->host_sem);
 if (!ufshcd_is_user_access_allowed(hba)) {
  err = -EBUSY;
  goto out;
 }

 value = !!value;
 if (value == hba->clk_scaling.is_enabled)
  goto out;

 ufshcd_rpm_get_sync(hba);
 ufshcd_hold(hba);

 hba->clk_scaling.is_enabled = value;

 if (value) {
  ufshcd_resume_clkscaling(hba);
  goto out_rel;
 }

 clki = list_first_entry(&hba->clk_list_head, struct ufs_clk_info, list);
 freq = clki->max_freq;

 ufshcd_suspend_clkscaling(hba);

 if (!ufshcd_is_devfreq_scaling_required(hba, freq, true))
  goto out_rel;

 err = ufshcd_devfreq_scale(hba, freq, true);
 if (err)
  dev_err(hba->dev, "%s: failed to scale clocks up %d\n",
    __func__, err);
 else
  hba->clk_scaling.target_freq = freq;

out_rel:
 ufshcd_release(hba);
 ufshcd_rpm_put_sync(hba);
out:
 up(&hba->host_sem);
 return err ? err : count;
}

static void ufshcd_init_clk_scaling_sysfs(struct ufs_hba *hba)
{
 hba->clk_scaling.enable_attr.show = ufshcd_clkscale_enable_show;
 hba->clk_scaling.enable_attr.store = ufshcd_clkscale_enable_store;
 sysfs_attr_init(&hba->clk_scaling.enable_attr.attr);
 hba->clk_scaling.enable_attr.attr.name = "clkscale_enable";
 hba->clk_scaling.enable_attr.attr.mode = 0644;
 if (device_create_file(hba->dev, &hba->clk_scaling.enable_attr))
  dev_err(hba->dev, "Failed to create sysfs for clkscale_enable\n");
}

static void ufshcd_remove_clk_scaling_sysfs(struct ufs_hba *hba)
{
 if (hba->clk_scaling.enable_attr.attr.name)
  device_remove_file(hba->dev, &hba->clk_scaling.enable_attr);
}

static void ufshcd_init_clk_scaling(struct ufs_hba *hba)
{
 if (!ufshcd_is_clkscaling_supported(hba))
  return;

 if (!hba->clk_scaling.min_gear)
  hba->clk_scaling.min_gear = UFS_HS_G1;

 if (!hba->clk_scaling.wb_gear)
  /* Use intermediate gear speed HS_G3 as the default wb_gear */
  hba->clk_scaling.wb_gear = UFS_HS_G3;

 INIT_WORK(&hba->clk_scaling.suspend_work,
    ufshcd_clk_scaling_suspend_work);
 INIT_WORK(&hba->clk_scaling.resume_work,
    ufshcd_clk_scaling_resume_work);

 spin_lock_init(&hba->clk_scaling.lock);

 hba->clk_scaling.workq = alloc_ordered_workqueue(
  "ufs_clkscaling_%d", WQ_MEM_RECLAIM, hba->host->host_no);

 hba->clk_scaling.is_initialized = true;
}

static void ufshcd_exit_clk_scaling(struct ufs_hba *hba)
{
 if (!hba->clk_scaling.is_initialized)
  return;

 ufshcd_remove_clk_scaling_sysfs(hba);
 destroy_workqueue(hba->clk_scaling.workq);
 ufshcd_devfreq_remove(hba);
 hba->clk_scaling.is_initialized = false;
}

static void ufshcd_ungate_work(struct work_struct *work)
{
 int ret;
 struct ufs_hba *hba = container_of(work, struct ufs_hba,
   clk_gating.ungate_work);

 cancel_delayed_work_sync(&hba->clk_gating.gate_work);

 scoped_guard(spinlock_irqsave, &hba->clk_gating.lock) {
  if (hba->clk_gating.state == CLKS_ON)
   return;
 }

 ufshcd_hba_vreg_set_hpm(hba);
 ufshcd_setup_clocks(hba, true);

 ufshcd_enable_irq(hba);

 /* Exit from hibern8 */
 if (ufshcd_can_hibern8_during_gating(hba)) {
  /* Prevent gating in this path */
  hba->clk_gating.is_suspended = true;
  if (ufshcd_is_link_hibern8(hba)) {
   ret = ufshcd_uic_hibern8_exit(hba);
   if (ret)
    dev_err(hba->dev, "%s: hibern8 exit failed %d\n",
     __func__, ret);
   else
    ufshcd_set_link_active(hba);
  }
  hba->clk_gating.is_suspended = false;
 }
}

/**
 * ufshcd_hold - Enable clocks that were gated earlier due to ufshcd_release.
 * Also, exit from hibern8 mode and set the link as active.
 * @hba: per adapter instance
 */

void ufshcd_hold(struct ufs_hba *hba)
{
 bool flush_result;
 unsigned long flags;

 if (!ufshcd_is_clkgating_allowed(hba) ||
     !hba->clk_gating.is_initialized)
  return;
 spin_lock_irqsave(&hba->clk_gating.lock, flags);
 hba->clk_gating.active_reqs++;

start:
 switch (hba->clk_gating.state) {
 case CLKS_ON:
  /*
 * Wait for the ungate work to complete if in progress.
 * Though the clocks may be in ON state, the link could
 * still be in hibner8 state if hibern8 is allowed
 * during clock gating.
 * Make sure we exit hibern8 state also in addition to
 * clocks being ON.
 */

  if (ufshcd_can_hibern8_during_gating(hba) &&
      ufshcd_is_link_hibern8(hba)) {
   spin_unlock_irqrestore(&hba->clk_gating.lock, flags);
   flush_result = flush_work(&hba->clk_gating.ungate_work);
   if (hba->clk_gating.is_suspended && !flush_result)
    return;
   spin_lock_irqsave(&hba->clk_gating.lock, flags);
   goto start;
  }
  break;
 case REQ_CLKS_OFF:
  if (cancel_delayed_work(&hba->clk_gating.gate_work)) {
   hba->clk_gating.state = CLKS_ON;
   trace_ufshcd_clk_gating(hba,
      hba->clk_gating.state);
   break;
  }
  /*
 * If we are here, it means gating work is either done or
 * currently running. Hence, fall through to cancel gating
 * work and to enable clocks.
 */

  fallthrough;
 case CLKS_OFF:
  hba->clk_gating.state = REQ_CLKS_ON;
  trace_ufshcd_clk_gating(hba,
     hba->clk_gating.state);
  queue_work(hba->clk_gating.clk_gating_workq,
      &hba->clk_gating.ungate_work);
  /*
 * fall through to check if we should wait for this
 * work to be done or not.
 */

  fallthrough;
 case REQ_CLKS_ON:
  spin_unlock_irqrestore(&hba->clk_gating.lock, flags);
  flush_work(&hba->clk_gating.ungate_work);
  /* Make sure state is CLKS_ON before returning */
  spin_lock_irqsave(&hba->clk_gating.lock, flags);
  goto start;
 default:
  dev_err(hba->dev, "%s: clk gating is in invalid state %d\n",
    __func__, hba->clk_gating.state);
  break;
 }
 spin_unlock_irqrestore(&hba->clk_gating.lock, flags);
}
EXPORT_SYMBOL_GPL(ufshcd_hold);

static void ufshcd_gate_work(struct work_struct *work)
{
 struct ufs_hba *hba = container_of(work, struct ufs_hba,
   clk_gating.gate_work.work);
 int ret;

 scoped_guard(spinlock_irqsave, &hba->clk_gating.lock) {
  /*
 * In case you are here to cancel this work the gating state
 * would be marked as REQ_CLKS_ON. In this case save time by
 * skipping the gating work and exit after changing the clock
 * state to CLKS_ON.
 */

  if (hba->clk_gating.is_suspended ||
      hba->clk_gating.state != REQ_CLKS_OFF) {
   hba->clk_gating.state = CLKS_ON;
   trace_ufshcd_clk_gating(hba,
      hba->clk_gating.state);
   return;
  }

  if (hba->clk_gating.active_reqs)
   return;
 }

 scoped_guard(spinlock_irqsave, hba->host->host_lock) {
  if (ufshcd_is_ufs_dev_busy(hba) ||
      hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL)
   return;
 }

 /* put the link into hibern8 mode before turning off clocks */
 if (ufshcd_can_hibern8_during_gating(hba)) {
  ret = ufshcd_uic_hibern8_enter(hba);
  if (ret) {
   hba->clk_gating.state = CLKS_ON;
   dev_err(hba->dev, "%s: hibern8 enter failed %d\n",
     __func__, ret);
   trace_ufshcd_clk_gating(hba,
      hba->clk_gating.state);
   return;
  }
  ufshcd_set_link_hibern8(hba);
 }

 ufshcd_disable_irq(hba);

 ufshcd_setup_clocks(hba, false);

 /* Put the host controller in low power mode if possible */
 ufshcd_hba_vreg_set_lpm(hba);
 /*
 * In case you are here to cancel this work the gating state
 * would be marked as REQ_CLKS_ON. In this case keep the state
 * as REQ_CLKS_ON which would anyway imply that clocks are off
 * and a request to turn them on is pending. By doing this way,
 * we keep the state machine in tact and this would ultimately
 * prevent from doing cancel work multiple times when there are
 * new requests arriving before the current cancel work is done.
 */

 guard(spinlock_irqsave)(&hba->clk_gating.lock);
 if (hba->clk_gating.state == REQ_CLKS_OFF) {
  hba->clk_gating.state = CLKS_OFF;
  trace_ufshcd_clk_gating(hba,
     hba->clk_gating.state);
 }
}

static void __ufshcd_release(struct ufs_hba *hba)
{
 lockdep_assert_held(&hba->clk_gating.lock);

 if (!ufshcd_is_clkgating_allowed(hba))
  return;

 hba->clk_gating.active_reqs--;

 if (hba->clk_gating.active_reqs || hba->clk_gating.is_suspended ||
     !hba->clk_gating.is_initialized ||
     hba->clk_gating.state == CLKS_OFF)
  return;

 scoped_guard(spinlock_irqsave, hba->host->host_lock) {
  if (ufshcd_has_pending_tasks(hba) ||
      hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL)
   return;
 }

 hba->clk_gating.state = REQ_CLKS_OFF;
 trace_ufshcd_clk_gating(hba, hba->clk_gating.state);
 queue_delayed_work(hba->clk_gating.clk_gating_workq,
      &hba->clk_gating.gate_work,
      msecs_to_jiffies(hba->clk_gating.delay_ms));
}

void ufshcd_release(struct ufs_hba *hba)
{
 guard(spinlock_irqsave)(&hba->clk_gating.lock);
 __ufshcd_release(hba);
}
EXPORT_SYMBOL_GPL(ufshcd_release);

static ssize_t ufshcd_clkgate_delay_show(struct device *dev,
  struct device_attribute *attr, char *buf)
{
 struct ufs_hba *hba = dev_get_drvdata(dev);

 return sysfs_emit(buf, "%lu\n", hba->clk_gating.delay_ms);
}

void ufshcd_clkgate_delay_set(struct device *dev, unsigned long value)
{
 struct ufs_hba *hba = dev_get_drvdata(dev);

 guard(spinlock_irqsave)(&hba->clk_gating.lock);
 hba->clk_gating.delay_ms = value;
}
EXPORT_SYMBOL_GPL(ufshcd_clkgate_delay_set);

static ssize_t ufshcd_clkgate_delay_store(struct device *dev,
  struct device_attribute *attr, const char *buf, size_t count)
{
 unsigned long value;

 if (kstrtoul(buf, 0, &value))
  return -EINVAL;

 ufshcd_clkgate_delay_set(dev, value);
 return count;
}

static ssize_t ufshcd_clkgate_enable_show(struct device *dev,
  struct device_attribute *attr, char *buf)
{
 struct ufs_hba *hba = dev_get_drvdata(dev);

 return sysfs_emit(buf, "%d\n", hba->clk_gating.is_enabled);
}

static ssize_t ufshcd_clkgate_enable_store(struct device *dev,
  struct device_attribute *attr, const char *buf, size_t count)
{
 struct ufs_hba *hba = dev_get_drvdata(dev);
 u32 value;

 if (kstrtou32(buf, 0, &value))
  return -EINVAL;

 value = !!value;

 guard(spinlock_irqsave)(&hba->clk_gating.lock);

 if (value == hba->clk_gating.is_enabled)
  return count;

 if (value)
  __ufshcd_release(hba);
 else
  hba->clk_gating.active_reqs++;

 hba->clk_gating.is_enabled = value;

 return count;
}

static void ufshcd_init_clk_gating_sysfs(struct ufs_hba *hba)
{
 hba->clk_gating.delay_attr.show = ufshcd_clkgate_delay_show;
 hba->clk_gating.delay_attr.store = ufshcd_clkgate_delay_store;
 sysfs_attr_init(&hba->clk_gating.delay_attr.attr);
 hba->clk_gating.delay_attr.attr.name = "clkgate_delay_ms";
 hba->clk_gating.delay_attr.attr.mode = 0644;
 if (device_create_file(hba->dev, &hba->clk_gating.delay_attr))
  dev_err(hba->dev, "Failed to create sysfs for clkgate_delay\n");

 hba->clk_gating.enable_attr.show = ufshcd_clkgate_enable_show;
 hba->clk_gating.enable_attr.store = ufshcd_clkgate_enable_store;
 sysfs_attr_init(&hba->clk_gating.enable_attr.attr);
 hba->clk_gating.enable_attr.attr.name = "clkgate_enable";
 hba->clk_gating.enable_attr.attr.mode = 0644;
 if (device_create_file(hba->dev, &hba->clk_gating.enable_attr))
  dev_err(hba->dev, "Failed to create sysfs for clkgate_enable\n");
}

static void ufshcd_remove_clk_gating_sysfs(struct ufs_hba *hba)
{
 if (hba->clk_gating.delay_attr.attr.name)
  device_remove_file(hba->dev, &hba->clk_gating.delay_attr);
 if (hba->clk_gating.enable_attr.attr.name)
  device_remove_file(hba->dev, &hba->clk_gating.enable_attr);
}

static void ufshcd_init_clk_gating(struct ufs_hba *hba)
{
 if (!ufshcd_is_clkgating_allowed(hba))
  return;

 hba->clk_gating.state = CLKS_ON;

 hba->clk_gating.delay_ms = 150;
 INIT_DELAYED_WORK(&hba->clk_gating.gate_work, ufshcd_gate_work);
 INIT_WORK(&hba->clk_gating.ungate_work, ufshcd_ungate_work);

 hba->clk_gating.clk_gating_workq = alloc_ordered_workqueue(
  "ufs_clk_gating_%d", WQ_MEM_RECLAIM | WQ_HIGHPRI,
  hba->host->host_no);

 ufshcd_init_clk_gating_sysfs(hba);

 hba->clk_gating.is_enabled = true;
 hba->clk_gating.is_initialized = true;
}

static void ufshcd_exit_clk_gating(struct ufs_hba *hba)
{
 if (!hba->clk_gating.is_initialized)
  return;

 ufshcd_remove_clk_gating_sysfs(hba);

 /* Ungate the clock if necessary. */
 ufshcd_hold(hba);
 hba->clk_gating.is_initialized = false;
 ufshcd_release(hba);

 destroy_workqueue(hba->clk_gating.clk_gating_workq);
}

static void ufshcd_clk_scaling_start_busy(struct ufs_hba *hba)
{
 bool queue_resume_work = false;
 ktime_t curr_t = ktime_get();

 if (!ufshcd_is_clkscaling_supported(hba))
  return;

 guard(spinlock_irqsave)(&hba->clk_scaling.lock);

 if (!hba->clk_scaling.active_reqs++)
  queue_resume_work = true;

 if (!hba->clk_scaling.is_enabled || hba->pm_op_in_progress)
  return;

 if (queue_resume_work)
  queue_work(hba->clk_scaling.workq,
      &hba->clk_scaling.resume_work);

 if (!hba->clk_scaling.window_start_t) {
  hba->clk_scaling.window_start_t = curr_t;
  hba->clk_scaling.tot_busy_t = 0;
  hba->clk_scaling.is_busy_started = false;
 }

 if (!hba->clk_scaling.is_busy_started) {
  hba->clk_scaling.busy_start_t = curr_t;
  hba->clk_scaling.is_busy_started = true;
 }
}

static void ufshcd_clk_scaling_update_busy(struct ufs_hba *hba)
{
 struct ufs_clk_scaling *scaling = &hba->clk_scaling;

 if (!ufshcd_is_clkscaling_supported(hba))
  return;

 guard(spinlock_irqsave)(&hba->clk_scaling.lock);

 hba->clk_scaling.active_reqs--;
 if (!scaling->active_reqs && scaling->is_busy_started) {
  scaling->tot_busy_t += ktime_to_us(ktime_sub(ktime_get(),
     scaling->busy_start_t));
  scaling->busy_start_t = 0;
  scaling->is_busy_started = false;
 }
}

static inline int ufshcd_monitor_opcode2dir(u8 opcode)
{
 if (opcode == READ_6 || opcode == READ_10 || opcode == READ_16)
  return READ;
 else if (opcode == WRITE_6 || opcode == WRITE_10 || opcode == WRITE_16)
  return WRITE;
 else
  return -EINVAL;
}

static inline bool ufshcd_should_inform_monitor(struct ufs_hba *hba,
      struct ufshcd_lrb *lrbp)
{
 const struct ufs_hba_monitor *m = &hba->monitor;

 return (m->enabled && lrbp && lrbp->cmd &&
  (!m->chunk_size || m->chunk_size == lrbp->cmd->sdb.length) &&
  ktime_before(hba->monitor.enabled_ts, lrbp->issue_time_stamp));
}

static void ufshcd_start_monitor(struct ufs_hba *hba,
     const struct ufshcd_lrb *lrbp)
{
 int dir = ufshcd_monitor_opcode2dir(*lrbp->cmd->cmnd);
 unsigned long flags;

 spin_lock_irqsave(hba->host->host_lock, flags);
 if (dir >= 0 && hba->monitor.nr_queued[dir]++ == 0)
  hba->monitor.busy_start_ts[dir] = ktime_get();
 spin_unlock_irqrestore(hba->host->host_lock, flags);
}

static void ufshcd_update_monitor(struct ufs_hba *hba, const struct ufshcd_lrb *lrbp)
{
 int dir = ufshcd_monitor_opcode2dir(*lrbp->cmd->cmnd);
 unsigned long flags;

 spin_lock_irqsave(hba->host->host_lock, flags);
 if (dir >= 0 && hba->monitor.nr_queued[dir] > 0) {
  const struct request *req = scsi_cmd_to_rq(lrbp->cmd);
  struct ufs_hba_monitor *m = &hba->monitor;
  ktime_t now, inc, lat;

  now = lrbp->compl_time_stamp;
  inc = ktime_sub(now, m->busy_start_ts[dir]);
  m->total_busy[dir] = ktime_add(m->total_busy[dir], inc);
  m->nr_sec_rw[dir] += blk_rq_sectors(req);

  /* Update latencies */
  m->nr_req[dir]++;
  lat = ktime_sub(now, lrbp->issue_time_stamp);
  m->lat_sum[dir] += lat;
  if (m->lat_max[dir] < lat || !m->lat_max[dir])
   m->lat_max[dir] = lat;
  if (m->lat_min[dir] > lat || !m->lat_min[dir])
   m->lat_min[dir] = lat;

  m->nr_queued[dir]--;
  /* Push forward the busy start of monitor */
  m->busy_start_ts[dir] = now;
 }
 spin_unlock_irqrestore(hba->host->host_lock, flags);
}

/**
 * ufshcd_send_command - Send SCSI or device management commands
 * @hba: per adapter instance
 * @task_tag: Task tag of the command
 * @hwq: pointer to hardware queue instance
 */

static inline
void ufshcd_send_command(struct ufs_hba *hba, unsigned int task_tag,
    struct ufs_hw_queue *hwq)
{
 struct ufshcd_lrb *lrbp = &hba->lrb[task_tag];
 unsigned long flags;

 lrbp->issue_time_stamp = ktime_get();
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=98 H=95 G=96

¤ Dauer der Verarbeitung: 0.48 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.