+/**
+ * ufshcd_get_upmcrs - Get the power mode change request status
+ * @hba: Pointer to adapter instance
+ *
+ * This function gets the UPMCRS field of HCS register
+ * Returns value of UPMCRS field
+ */
+static inline u8 ufshcd_get_upmcrs(struct ufs_hba *hba)
+{
+ return (ufshcd_readl(hba, REG_CONTROLLER_STATUS) >> 8) & 0x7;
+}
+
+/**
* ufshcd_free_hba_memory - Free allocated memory for LRB, request
* and task lists
* @hba: Pointer to adapter instance
@@ -804,6 +830,195 @@ static int ufshcd_dme_link_startup(struct ufs_hba *hba)
}
/**
+ * ufshcd_dme_xxx_set - UIC command for DME_SET, DME_PEER_SET
+ * @hba: per adapter instance
+ *
+ * Returns 0 on success, non-zero value on failure
+ */
+int ufshcd_dme_xxx_set(struct ufs_hba *hba, u32 attr_sel,
+ u8 attr_set, u32 mib_val, u8 peer)
+{
+ struct uic_command *uic_cmd;
+ static const char *const action[] = {
+ "dme-set", "dme-peer-set"
+ };
+ const char *set = action[!!peer];
+ unsigned long flags;
+ int ret;
+
+ if (!ufshcd_ready_uic_cmd(hba))
+ return -EIO;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+
+ /* form UIC command */
+ uic_cmd = &hba->active_uic_cmd;
+ uic_cmd->command = peer ?
+ UIC_CMD_DME_PEER_SET : UIC_CMD_DME_SET;
+ uic_cmd->argument1 = attr_sel;
+ uic_cmd->argument2 = UIC_ARG_ATTR_SET(attr_set);
+ uic_cmd->argument3 = mib_val;
+
+ /* dispatch UIC commands to controller */
+ ufshcd_dispatch_uic_cmd(hba, uic_cmd);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ ret = ufshcd_wait_for_uic_cmd(hba);
+ dev_dbg(hba->dev, "%s: error code %d returned\n", set, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_dme_xxx_set);
+
+/**
+ * ufshcd_dme_xxx_get - UIC command for DME_GET, DME_PEER_GET
+ * @hba: per adapter instance
+ *
+ * Returns 0 on success, non-zero value on failure
+ */
+int ufshcd_dme_xxx_get(struct ufs_hba *hba, u32 attr_sel, u32 *mib_val, u8 peer)
+{
+ struct uic_command *uic_cmd;
+ static const char *const action[] = {
+ "dme-get", "dme-peer-get"
+ };
+ const char *get = action[!!peer];
+ unsigned long flags;
+ int ret;
+
+ if (!ufshcd_ready_uic_cmd(hba))
+ return -EIO;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+
+ /* form UIC command */
+ uic_cmd = &hba->active_uic_cmd;
+ uic_cmd->command = peer ?
+ UIC_CMD_DME_PEER_GET : UIC_CMD_DME_GET;
+ uic_cmd->argument1 = attr_sel;
+ uic_cmd->argument2 = 0;
+ uic_cmd->argument3 = 0;
+
+ /* dispatch UIC commands to controller */
+ ufshcd_dispatch_uic_cmd(hba, uic_cmd);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ ret = ufshcd_wait_for_uic_cmd(hba);
+ if (mib_val)
+ *mib_val = ufshcd_get_dme_attr_val(hba);
+
+ dev_dbg(hba->dev, "%s: error code %d returned\n", get, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_dme_xxx_get);
+
+/**
+ * ufshcd_dme_power_xxx - UIC command for DME_POWERON, DME_POWEROFF
+ * @hba: per adapter instance
+ *
+ * Returns 0 on success, non-zero value on failure
+ */
+int ufshcd_dme_power_xxx(struct ufs_hba *hba, u8 on)
+{
+ struct uic_command *uic_cmd;
+ static const char *const action[] = {
+ "dme-power-on",
+ "dme-power-off",
+ };
+ const char *power = action[!!on];
+ unsigned long flags;
+ int ret;
+
+ if (!ufshcd_ready_uic_cmd(hba))
+ return -EIO;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+
+ /* form UIC command */
+ uic_cmd = &hba->active_uic_cmd;
+ uic_cmd->command = on ?
+ UIC_CMD_DME_POWERON : UIC_CMD_DME_POWEROFF;
+ uic_cmd->argument1 = 0;
+ uic_cmd->argument2 = 0;
+ uic_cmd->argument3 = 0;
+
+ /* dispatch UIC commands to controller */
+ ufshcd_dispatch_uic_cmd(hba, uic_cmd);
+
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ ret = ufshcd_wait_for_uic_cmd(hba);
+ if (ret)
+ dev_err(hba->dev, "%s: error code %d returned\n", power, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_dme_power_xxx);
+
+/**
+ * ufshcd_dme_hibern8_xxx - UIC command for DME_HIBERNATE_ENTER,
+ * DME_HIBERNATE_EXIT
+ * @hba: per adapter instance
+ *
+ * Returns 0 on success, non-zero value on failure
+ */
+int ufshcd_dme_hibern8_xxx(struct ufs_hba *hba, u8 enter)
+{
+ struct uic_command *uic_cmd;
+ static const char *const action[] = {
+ "dme-hibernate-enter",
+ "dme-hibernate-exit"
+ };
+ const char *hibern8 = action[!!enter];
+ unsigned long flags;
+ u8 status;
+ int ret;
+
+ if (!ufshcd_ready_uic_cmd(hba))
+ return -EIO;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+
+ /* form UIC command */
+ uic_cmd = &hba->active_uic_cmd;
+ uic_cmd->command = enter ?
+ UIC_CMD_DME_HIBER_ENTER : UIC_CMD_DME_HIBER_EXIT;
+ uic_cmd->argument1 = 0;
+ uic_cmd->argument2 = 0;
+ uic_cmd->argument3 = 0;
+
+ /* dispatch UIC commands to controller */
+ ufshcd_dispatch_uic_cmd(hba, uic_cmd);
+
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ ret= ufshcd_wait_for_uic_cmd(hba);
+ if (ret) {
+ dev_err(hba->dev, "%s: error code %d returned\n", hibern8, ret);
+ goto out;
+ }
+
+ init_completion(&hba->hibern8_done);
+
+ if (wait_for_completion_timeout(&hba->hibern8_done,
+ msecs_to_jiffies(UIC_CMD_TIMEOUT))) {
+ status = ufshcd_get_upmcrs(hba);
+ if (status != PWR_LOCAL) {
+ dev_err(hba->dev, "%s: failed, host upmcrs:%x\n",
+ hibern8, status);
+ ret = status;
+ }
+ } else {
+ dev_err(hba->dev, "%s: timeout\n", hibern8);
+ ret = -ETIMEDOUT;
+ }
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_dme_hibern8_xxx);
+
+/**
* ufshcd_make_hba_operational - Make UFS controller operational
* @hba: per adapter instance
*
@@ -1238,6 +1453,9 @@ static void ufshcd_uic_cmd_compl(struct ufs_hba *hba, u32 intr_status)
{
if (intr_status & UIC_COMMAND_COMPL)
complete(&hba->active_uic_cmd.done);
+
+ if (intr_status & UFSHCD_HIBERNATE_MASK)
+ complete(&hba->hibern8_done);
}
/**
@@ -1362,7 +1580,7 @@ static void ufshcd_sl_intr(struct ufs_hba *hba, u32 intr_status)
if (hba->errors)
ufshcd_err_handler(hba);
- if (intr_status & UIC_COMMAND_COMPL)
+ if (intr_status & UFSHCD_UIC_MASK)
ufshcd_uic_cmd_compl(hba, intr_status);
if (intr_status & UTP_TASK_REQ_COMPL)
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 2fb4d94..5bf19f5 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -140,6 +140,7 @@ struct ufshcd_lrb {
* @active_uic_cmd: handle of active UIC command
* @ufshcd_tm_wait_queue: wait queue for task management
* @tm_condition: condition variable for task management
+ * @hibern8_done: completion for hibernate
* @ufshcd_state: UFSHCD states
* @intr_mask: Interrupt Mask Bits
* @link_startup_wq: Work queue for link start-up
@@ -177,6 +178,8 @@ struct ufs_hba {
wait_queue_head_t ufshcd_tm_wait_queue;
unsigned long tm_condition;
+ struct completion hibern8_done;
+
u32 ufshcd_state;
u32 intr_mask;
@@ -197,4 +200,56 @@ void ufshcd_remove(struct ufs_hba *);
#define ufshcd_readl(hba, reg) \
readl((hba)->mmio_base + (reg))
+
+extern int ufshcd_dme_xxx_set(struct ufs_hba *hba, u32 attr_sel,
+ u8 attr_set, u32 mib_val, u8 peer);
+extern int ufshcd_dme_xxx_get(struct ufs_hba *hba, u32 attr_sel,
+ u32 *mib_val, u8 peer);
+extern int ufshcd_dme_power_xxx(struct ufs_hba *hba, u8 on);
+extern int ufshcd_dme_hibern8_xxx(struct ufs_hba *hba, u8 enter);
+
+static inline int ufshcd_dme_set(struct ufs_hba *hba, u32 attr_sel,
+ u8 attr_set, u32 mib_val)
+{
+ return ufshcd_dme_xxx_set(hba, attr_sel, attr_set, mib_val, 0);
+}
+
+static inline int ufshcd_dme_peer_set(struct ufs_hba *hba, u32 attr_sel,
+ u8 attr_set, u32 mib_val)
+{
+ return ufshcd_dme_xxx_set(hba, attr_sel, attr_set, mib_val, 1);
+}
+
+static inline int ufshcd_dme_get(struct ufs_hba *hba, u32 attr_sel,
+ u32 *mib_val)
+{
+ return ufshcd_dme_xxx_get(hba, attr_sel, mib_val, 0);
+}
+
+static inline int ufshcd_dme_peer_get(struct ufs_hba *hba, u32 attr_sel,
+ u32 *mib_val)
+{
+ return ufshcd_dme_xxx_get(hba, attr_sel, mib_val, 1);
+}
+
+static inline int ufshcd_dme_power_on(struct ufs_hba *hba)
+{
+ return ufshcd_dme_power_xxx(hba, 1);
+}
+
+static inline int ufshcd_dme_power_off(struct ufs_hba *hba)
+{
+ return ufshcd_dme_power_xxx(hba, 0);
+}
+
+static inline int ufshcd_dme_hibern8_enter(struct ufs_hba *hba, u8 enter)
+{
+ return ufshcd_dme_hibern8_xxx(hba, 1);
+}
+
+static inline int ufshcd_dme_hibern8_exit(struct ufs_hba *hba, u8 enter)
+{
+ return ufshcd_dme_hibern8_xxx(hba, 0);
+}
+
#endif /* End of Header */
diff --git a/drivers/scsi/ufs/ufshci.h b/drivers/scsi/ufs/ufshci.h
index d5c5f14..28ede2a 100644
--- a/drivers/scsi/ufs/ufshci.h
+++ b/drivers/scsi/ufs/ufshci.h
@@ -124,6 +124,12 @@ enum {
#define CONTROLLER_FATAL_ERROR UFS_BIT(16)
#define SYSTEM_BUS_FATAL_ERROR UFS_BIT(17)
+#define UFSHCD_HIBERNATE_MASK (UIC_HIBERNATE_ENTER |\
+ UIC_HIBERNATE_EXIT)
+
+#define UFSHCD_UIC_MASK (UIC_COMMAND_COMPL |\
+ UFSHCD_HIBERNATE_MASK)
+
#define UFSHCD_ERROR_MASK (UIC_ERROR |\
DEVICE_FATAL_ERROR |\
CONTROLLER_FATAL_ERROR |\
@@ -142,6 +148,15 @@ enum {
#define DEVICE_ERROR_INDICATOR UFS_BIT(5)
#define UIC_POWER_MODE_CHANGE_REQ_STATUS_MASK UFS_MASK(0x7, 8)
+enum {
+ PWR_OK = 0x0,
+ PWR_LOCAL = 0x01,
+ PWR_REMOTE = 0x02,
+ PWR_BUSY = 0x03,
+ PWR_ERROR_CAP = 0x04,
+ PWR_FATAL_ERROR = 0x05,
+};
+
/* HCE - Host Controller Enable 34h */
#define CONTROLLER_ENABLE UFS_BIT(0)
#define CONTROLLER_DISABLE 0x0
@@ -191,6 +206,10 @@ enum {
#define CONFIG_RESULT_CODE_MASK 0xFF
#define GENERIC_ERROR_CODE_MASK 0xFF
+#define UIC_ARG_MIB_SEL(attr, sel) (((attr & 0xFFFF) << 16) |\
+ (sel & 0xFFFF))
+#define UIC_ARG_ATTR_SET(type) ((type & 0xFF) << 16)
+
/* UIC Commands */
enum {
UIC_CMD_DME_GET = 0x01,